Object Occlusion Culling

This commit is contained in:
2026-10-15 02:41:03 -07:00
parent 3273054766
commit 612a3d0a09
33 changed files with 3629 additions and 84 deletions

View File

@ -1133,6 +1133,11 @@ class LeenkxExporter:
}
out_object['properties'].append(out_property)
if bobject.lnx_is_occluder:
if 'properties' not in out_object:
out_object['properties'] = []
out_object['properties'].append({'name': 'occluder', 'value': True})
# Export the object reference and material references
objref = bobject.data
@ -2125,7 +2130,8 @@ class LeenkxExporter:
fp = self.get_meshes_file_path('mesh_' + oid, compressed=LeenkxExporter.compress_enabled)
assets.add(fp)
# No export necessary
if bobject.data.lnx_cached and os.path.exists(fp) or fp in self.build_cache.exported_mesh_files:
fp_json = fp.split('.lnx')[0] + '.json'
if bobject.data.lnx_cached and (os.path.exists(fp) or os.path.exists(fp_json)) or fp in self.build_cache.exported_mesh_files:
return
# Mesh users have different modifier stack
@ -2202,6 +2208,22 @@ class LeenkxExporter:
if armature:
self.export_skin(bobject, armature, export_mesh, out_mesh)
rpdat = lnx.utils.get_rp()
is_occluder = bobject.lnx_is_occluder
if not is_occluder:
for prop in bobject.lnx_propertylist:
if prop.name_prop == 'occluder' and getattr(prop, prop.type_prop + '_prop') == True:
is_occluder = True
break
use_global = False
if not is_occluder and rpdat is not None and rpdat.lnx_occlusion_all_objects:
is_occluder = rpdat.lnx_occlusion_min_size <= 0.0 or \
max(bobject.dimensions) >= rpdat.lnx_occlusion_min_size
use_global = is_occluder
if is_occluder and rpdat is not None and rpdat.lnx_occlusion_culling:
self.export_occluder_proxy(export_mesh, out_mesh, bobject, use_global)
# Restore the morph state after mesh export
if shape_keys:
bobject.active_shape_key_index = active_shape_key_index
@ -2311,6 +2333,139 @@ class LeenkxExporter:
def export_occluder_proxy(self, export_mesh: bpy.types.Mesh, out_mesh, bobject, use_global=False):
rpdat = lnx.utils.get_rp()
mode = bobject.lnx_occluder_mode
shrink = bobject.lnx_occluder_shrink
if use_global or mode == 'default':
mode = rpdat.lnx_occlusion_proxy_mode
if use_global or shrink < 0.0:
shrink = rpdat.lnx_occlusion_proxy_shrink
if mode == 'box':
proxy_positions, proxy_indices = self._box_proxy(export_mesh)
self._apply_inward_offset(proxy_positions, shrink)
elif mode == 'convex':
proxy_positions, proxy_indices = self._convex_proxy(export_mesh)
self._apply_inward_offset(proxy_positions, shrink)
else:
proxy_positions, proxy_indices = self._apply_decimate_to_mesh(
export_mesh, rpdat, shrink)
if len(proxy_indices) == 0:
return
out_mesh['occluder_proxy'] = {
'positions': proxy_positions,
'indices': proxy_indices
}
def _box_proxy(self, export_mesh: bpy.types.Mesh):
"""12-triangle axis-aligned bounding box proxy in local space."""
if len(export_mesh.vertices) == 0:
return [], []
xs = [v.co.x for v in export_mesh.vertices]
ys = [v.co.y for v in export_mesh.vertices]
zs = [v.co.z for v in export_mesh.vertices]
lo = (min(xs), min(ys), min(zs))
hi = (max(xs), max(ys), max(zs))
proxy_positions = [
lo[0], lo[1], lo[2], hi[0], lo[1], lo[2],
hi[0], hi[1], lo[2], lo[0], hi[1], lo[2],
lo[0], lo[1], hi[2], hi[0], lo[1], hi[2],
hi[0], hi[1], hi[2], lo[0], hi[1], hi[2]]
proxy_indices = [
0, 2, 1, 0, 3, 2,
4, 5, 6, 4, 6, 7,
0, 1, 5, 0, 5, 4,
2, 3, 7, 2, 7, 6,
1, 2, 6, 1, 6, 5,
0, 4, 7, 0, 7, 3]
return proxy_positions, proxy_indices
def _convex_proxy(self, export_mesh: bpy.types.Mesh):
proxy_positions = []
proxy_indices = []
bm = bmesh.new()
try:
bm.from_mesh(export_mesh)
bmesh.ops.convex_hull(bm, input=bm.verts)
remap = {}
for f in bm.faces:
fv = f.verts
for i in range(1, len(fv) - 1):
for v in (fv[0], fv[i], fv[i + 1]):
idx = remap.get(v.index)
if idx is None:
idx = len(remap)
remap[v.index] = idx
co = v.co
proxy_positions.append(co.x)
proxy_positions.append(co.y)
proxy_positions.append(co.z)
proxy_indices.append(idx)
finally:
bm.free()
return proxy_positions, proxy_indices
def _apply_inward_offset(self, positions, shrink_pct):
n = len(positions) // 3
if n == 0 or shrink_pct <= 0.0:
return
factor = 1.0 - shrink_pct * 0.01
cx = sum(positions[0::3]) / n
cy = sum(positions[1::3]) / n
cz = sum(positions[2::3]) / n
for i in range(n):
positions[i * 3] = cx + (positions[i * 3] - cx) * factor
positions[i * 3 + 1] = cy + (positions[i * 3 + 1] - cy) * factor
positions[i * 3 + 2] = cz + (positions[i * 3 + 2] - cz) * factor
def _apply_decimate_to_mesh(self, export_mesh: bpy.types.Mesh, rpdat, shrink):
temp_mesh = export_mesh.copy()
temp_obj = bpy.data.objects.new('lnx_decimate_temp', temp_mesh)
try:
self.scene.collection.objects.link(temp_obj)
mod = temp_obj.modifiers.new(name='lnx_decimate', type='DECIMATE')
mod.decimate_type = rpdat.lnx_occlusion_decimate_mode
if rpdat.lnx_occlusion_decimate_mode == 'COLLAPSE':
mod.ratio = rpdat.lnx_occlusion_decimate_ratio
elif rpdat.lnx_occlusion_decimate_mode == 'DISSOLVE':
mod.angle_limit = math.radians(
rpdat.lnx_occlusion_decimate_ratio * 180)
elif rpdat.lnx_occlusion_decimate_mode == 'UNSUBDIV':
mod.iterations = max(1, int(rpdat.lnx_occlusion_decimate_ratio * 10))
self.depsgraph.update()
eval_obj = temp_obj.evaluated_get(self.depsgraph)
eval_mesh = eval_obj.to_mesh(
preserve_all_data_layers=False, depsgraph=self.depsgraph)
try:
eval_mesh.calc_loop_triangles()
proxy_positions = []
for v in eval_mesh.vertices:
co = v.co
proxy_positions.append(co.x)
proxy_positions.append(co.y)
proxy_positions.append(co.z)
proxy_indices = []
for tri in eval_mesh.loop_triangles:
proxy_indices.append(tri.vertices[0])
proxy_indices.append(tri.vertices[1])
proxy_indices.append(tri.vertices[2])
self._apply_inward_offset(proxy_positions, shrink)
finally:
eval_obj.to_mesh_clear()
finally:
bpy.data.objects.remove(temp_obj, do_unlink=True)
bpy.data.meshes.remove(temp_mesh, do_unlink=True)
return proxy_positions, proxy_indices
def export_light(self, object_ref):
"""Exports a single light object."""
rpdat = lnx.utils.get_rp()
@ -2496,23 +2651,57 @@ class LeenkxExporter:
b = proj[1][1]
c = proj[2][2]
d = proj[2][3]
k = (c - 1.0) / (c + 1.0)
o['fov'] = 2.0 * math.atan(1.0 / b)
if abs(a) > 1e-6:
o['aspect'] = b / a
if with_planes:
o['near_plane'] = (d * (1.0 - k)) / (2.0 * k)
o['far_plane'] = k * o['near_plane']
denom = c + 1.0
k = (c - 1.0) / denom if abs(denom) >= 1e-6 else 0.0
if abs(k) < 1e-6:
o['near_plane'] = 0.1
o['far_plane'] = 1000.0
else:
near = (d * (1.0 - k)) / (2.0 * k)
far = k * near
o['near_plane'] = max(0.001, min(near, 100000.0))
o['far_plane'] = max(0.001, min(far, 100000.0))
@staticmethod
def extract_ortho(o, proj):
a = proj[0][0]
b = proj[1][1]
# left, right, bottom, top
o['ortho'] = [-(1 + proj[3][0]) / proj[0][0], \
(1 - proj[3][0]) / proj[0][0], \
-(1 + proj[3][1]) / proj[1][1], \
(1 - proj[3][1]) / proj[1][1]]
o['ortho'] = [-(1 + proj[3][0]) / a, \
(1 - proj[3][0]) / a, \
-(1 + proj[3][1]) / b, \
(1 - proj[3][1]) / b]
o['near_plane'] = (1 + proj[3][2]) / proj[2][2]
o['far_plane'] = -(1 - proj[3][2]) / proj[2][2]
o['near_plane'] *= 2
o['far_plane'] *= 2
if abs(a) > 1e-6:
o['aspect'] = b / a
@staticmethod
def compute_effective_par(rpdat, render):
if rpdat is None:
return render.pixel_aspect_x, render.pixel_aspect_y
mode = rpdat.lnx_aspect_mode
if mode == 'blender':
return render.pixel_aspect_x, render.pixel_aspect_y
elif mode == 'square':
return 1.0, 1.0
elif mode == 'ntsc_43':
return 0.889, 1.0
elif mode == 'pal_43':
return 1.067, 1.0
elif mode == 'ntsc_169':
return 1.212, 1.0
elif mode == 'pal_169':
return 1.459, 1.0
elif mode == 'custom':
return rpdat.lnx_aspect_custom, 1.0
return render.pixel_aspect_x, render.pixel_aspect_y
def export_camera(self, objectRef):
o = {}
@ -2521,12 +2710,16 @@ class LeenkxExporter:
camera = objectRef[1]["objectTable"][0]
render = self.scene.render
rpdat = lnx.utils.get_rp()
par_x, par_y = self.compute_effective_par(rpdat, render)
proj = camera.calc_matrix_camera(
self.depsgraph,
x=render.resolution_x,
y=render.resolution_y,
scale_x=render.pixel_aspect_x,
scale_y=render.pixel_aspect_y)
scale_x=par_x,
scale_y=par_y)
if objref.type == 'PERSP':
self.extract_projection(o, proj)
else:
@ -2594,7 +2787,7 @@ class LeenkxExporter:
self.output['material_datas'].append(o)
bpy.data.materials.remove(mat)
rpdat = lnx.utils.get_rp()
if not rpdat.lnx_culling:
if not rpdat.lnx_backface_culling:
o['override_context'] = {}
o['override_context']['cull_mode'] = 'none'
@ -2673,7 +2866,7 @@ class LeenkxExporter:
o['skip_context'] = material.lnx_skip_context
rpdat = lnx.utils.get_rp()
if material.lnx_two_sided or not rpdat.lnx_culling:
if material.lnx_two_sided or not rpdat.lnx_backface_culling:
o['override_context'] = {}
o['override_context']['cull_mode'] = 'none'
elif material.lnx_cull_mode != 'clockwise':
@ -3190,6 +3383,12 @@ class LeenkxExporter:
'clear_color': self.get_camera_clear_color()
}
render = self.scene.render
rpdat = lnx.utils.get_rp()
par_x, par_y = self.compute_effective_par(rpdat, render)
res_aspect = render.resolution_x / render.resolution_y if render.resolution_y > 0 else 1.0
effective_aspect = res_aspect * par_x / par_y
# Set viewport camera projection
proj, is_persp = self.get_viewport_projection_matrix()
if proj is not None:
@ -3197,6 +3396,9 @@ class LeenkxExporter:
self.extract_projection(out_camera, proj, with_planes=False)
else:
self.extract_ortho(out_camera, proj)
out_camera['aspect'] = effective_aspect
self.output['camera_datas'].append(out_camera)
out_object = {

View File

@ -390,12 +390,14 @@ def send_event(event_id: str, opt_data: Any = None):
elif event_id == 'cam_props':
cam: bpy.types.Camera = obj_data.data
render = bpy.context.scene.render
rpdat = lnx.utils.get_rp()
par_x, par_y = LeenkxExporter.compute_effective_par(rpdat, render)
projection = obj_data.calc_matrix_camera(
bpy.context.evaluated_depsgraph_get(),
x=render.resolution_x,
y=render.resolution_y,
scale_x=render.pixel_aspect_x,
scale_y=render.pixel_aspect_y)
scale_x=par_x,
scale_y=par_y)
focal_y = projection[1][1]
depth_z = projection[2][2]
depth_w = projection[2][3]
@ -403,8 +405,10 @@ def send_event(event_id: str, opt_data: Any = None):
fov = 2.0 * math.atan(1.0 / focal_y)
near = (depth_w * (1.0 - far_near_ratio)) / (2.0 * far_near_ratio)
far = far_near_ratio * near
res_aspect = render.resolution_x / render.resolution_y if render.resolution_y > 0 else 1.0
effective_aspect = res_aspect * par_x / par_y
cam_obj = f'iron.Scene.active.getCamera("{cam.name}")'
js = f'var c = {cam_obj}; var cRaw = c.data.raw; cRaw.fov={fov}; cRaw.near_plane={near}; cRaw.far_plane={far}; cRaw.frustum_culling={"true" if cam.lnx_frustum_culling else "false"}; c.buildProjection();'
js = f'var c = {cam_obj}; var cRaw = c.data.raw; cRaw.fov={fov}; cRaw.near_plane={near}; cRaw.far_plane={far}; cRaw.frustum_culling={"true" if cam.lnx_frustum_culling else "false"}; cRaw.aspect={effective_aspect}; c.buildProjection();'
write_patch(js)
elif event_id == 'obj_visibility':

View File

@ -190,6 +190,9 @@ def build():
rpdat = lnx.utils.get_rp()
project_path = lnx.utils.get_fp()
if rpdat.lnx_frustum_culling:
assets.add_khafile_def('lnx_frustum_culling')
if rpdat.rp_driver != 'Leenkx' and lnx.api.drivers[rpdat.rp_driver]['make_rpath'] != None:
lnx.api.drivers[rpdat.rp_driver]['make_rpath']()
return
@ -210,6 +213,9 @@ def build():
if rpdat.rp_hdr:
assets.add_khafile_def('rp_hdr')
if rpdat.lnx_occlusion_culling:
assets.add_khafile_def('lnx_occlusion_culling')
assets.add_khafile_def('rp_renderer={0}'.format(rpdat.rp_renderer))
if rpdat.rp_depthprepass:
assets.add_khafile_def('rp_depthprepass')

View File

@ -425,6 +425,16 @@ def init_properties():
bpy.types.Object.lnx_sorting_index = compatible_prop(IntProperty, name="Sorting Index", description="Sorting index for the Render's Draw Order", default=0, override={'LIBRARY_OVERRIDABLE'})
bpy.types.Object.lnx_spawn = compatible_prop(BoolProperty, name="Spawn", description="Auto-add this object when creating scene", default=True, override={'LIBRARY_OVERRIDABLE'})
bpy.types.Object.lnx_mobile = compatible_prop(BoolProperty, name="Mobile", description="Object moves during gameplay", default=False, override={'LIBRARY_OVERRIDABLE'})
bpy.types.Object.lnx_is_occluder = compatible_prop(BoolProperty, name="Is Occluder", description="Export simplified occluder geometry for software occlusion culling", default=False, override={'LIBRARY_OVERRIDABLE'})
bpy.types.Object.lnx_occluder_mode = compatible_prop(EnumProperty,
items=[('convex', 'Convex Hull', 'Convex hull of the mesh - tight on convex shapes, over-covers concave ones'),
('box', 'Box', 'Axis-aligned bounding box - fastest, 12 triangles'),
('decimate', 'Decimate', 'Decimated mesh - closest fit, slowest export')],
name="Occluder Mode",
description="Proxy geometry used for occlusion culling",
default='convex',
override={'LIBRARY_OVERRIDABLE'})
bpy.types.Object.lnx_occluder_shrink = compatible_prop(FloatProperty, name="Occluder Shrink %", description="Shrink proxy toward its center: 0 = full size, 50 = half size (default), 100 = point", default=50.0, min=0.0, max=100.0, override={'LIBRARY_OVERRIDABLE'})
bpy.types.Object.lnx_visible = compatible_prop(BoolProperty, name="Visible", description="Render this object", default=True, override={'LIBRARY_OVERRIDABLE'})
bpy.types.Object.lnx_visible_shadow = compatible_prop(BoolProperty, name="Lighting", description="Object contributes to the lighting even if invisible", default=True, override={'LIBRARY_OVERRIDABLE'})
bpy.types.Object.lnx_soft_body_margin = FloatProperty(name="Soft Body Margin", description="Collision margin", default=0.04)

View File

@ -232,6 +232,15 @@ def update_renderpath(self, context):
assets.invalidate_shader_cache(self, context)
bpy.data.worlds['Lnx'].lnx_recompile = True
def update_occlusion_culling(self, context):
for m in bpy.data.meshes:
m.lnx_cached = False
for c in bpy.data.curves:
c.lnx_cached = False
for c in bpy.data.metaballs:
c.lnx_cached = False
update_renderpath(self, context)
def udpate_shadowmap_cascades(self, context):
bpy.data.worlds['Lnx'].lnx_recompile = True
update_renderpath(self, context)
@ -461,6 +470,7 @@ class LnxRPListItem(bpy.types.PropertyGroup):
('Distance', 'Distance', 'Distance'),
('Index', 'Index', 'Index')],
name='Draw Order', description='Sort objects', default='Auto', update=assets.invalidate_compiled_data)
lnx_radix: BoolProperty(name="Radix Sort", description="Sort draw calls with the radix library instead of the comparison sort", default=True, update=update_renderpath)
rp_depth_texture: BoolProperty(name="Depth Texture", description="Current render-path state", default=False)
rp_depth_texture_state: EnumProperty(
items=[('On', 'On', 'On'),
@ -697,8 +707,48 @@ class LnxRPListItem(bpy.types.PropertyGroup):
('CPU', 'CPU', 'CPU'),
('Off', 'Off', 'Off')],
name='Particles', description='Enable particle simulation', default='GPU', update=assets.invalidate_shader_cache)
# Material override flags
lnx_culling: BoolProperty(name="Culling", default=True)
# Culling flags
lnx_backface_culling: BoolProperty(name="Backface Culling", description="Cull back-facing triangles (winding-based)", default=True)
lnx_frustum_culling: BoolProperty(name="Frustum Culling", description="Cull objects outside camera frustum", default=True)
lnx_occlusion_culling: BoolProperty(name="Object Occlusion Culling (Experimental)", description="Software occlusion culling using Hi-Z buffer", default=False, update=update_occlusion_culling)
lnx_occlusion_all_objects: BoolProperty(name="All Objects Occlude", description="Every mesh can occlude - ignores the per-object 'Is Occluder' flag", default=False, update=update_occlusion_culling)
lnx_occlusion_min_size: FloatProperty(name="Min Occluder Size", description="Objects whose largest bounding-box side is smaller than this (meters) never export as occluders", default=0.5, min=0.0, update=update_occlusion_culling)
lnx_occlusion_proxy_mode: EnumProperty(
name="Proxy Mode",
description="Occluder proxy geometry type",
items=[
('box', 'Box', 'Axis-aligned bounding box - fastest, 12 triangles'),
('convex', 'Convex Hull', 'Convex hull of the mesh - tight on convex shapes, over-covers concave ones'),
('decimate', 'Decimate', 'Decimated mesh - closest fit, slowest export'),
],
default='convex', update=update_occlusion_culling)
lnx_occlusion_proxy_shrink: FloatProperty(name="Proxy Shrink %", description="Shrink proxy toward its center: 0 = full size, 50 = half size (default), 100 = point - keeps occluders conservative", default=50.0, min=0.0, max=100.0, precision=1, update=update_occlusion_culling)
lnx_occlusion_box_shrink: IntProperty(name="Box Shrink %", description="0 = full AABB (aggressive), 50 = half-size (default, balanced), 100 = point (no occlusion)", default=50, min=0, max=100, update=update_renderpath)
lnx_occlusion_depth_eps: IntProperty(name="Depth Epsilon", description="6 = 1e-6 (default, tight), higher = more tolerant (less aggressive culling), lower = stricter", default=6, min=1, max=10, update=update_renderpath)
lnx_occlusion_decimate_ratio: FloatProperty(name="Ratio", description="Proportion of edges to collapse (COLLAPSE) or angle threshold in degrees (DISSOLVE) or iterations (UNSUBDIV)", default=0.5, min=0.01, max=1.0, precision=3, update=update_occlusion_culling)
lnx_occlusion_decimate_mode: EnumProperty(
name="Decimate Mode",
description="Decimate method applied to occluder proxy geometry",
items=[
('COLLAPSE', 'Collapse', 'Collapse edges to reduce triangle count by ratio'),
('DISSOLVE', 'Dissolve', 'Dissolve vertices with angle below threshold (ratio = degrees)'),
('UNSUBDIV', 'Unsubdivide', 'Reverse subdivision (ratio = iterations, 0.1 = 1 iter, 1.0 = 10 iter)'),
],
default='COLLAPSE', update=update_occlusion_culling)
lnx_aspect_mode: EnumProperty(
name="Aspect Ratio",
description="Pixel aspect ratio override for camera projection. 'Blender' uses the scene's resolution and pixel aspect settings",
items=[
('blender', 'Blender', 'Use Blender\'s Output Properties resolution and pixel aspect ratio'),
('square', 'Square (1.0)', 'Square pixels (1:1)'),
('ntsc_43', 'NTSC 4:3', 'NTSC pixel aspect ratio (0.889)'),
('pal_43', 'PAL 4:3', 'PAL pixel aspect ratio (1.067)'),
('ntsc_169', 'NTSC 16:9', 'NTSC widescreen pixel aspect ratio (1.212)'),
('pal_169', 'PAL 16:9', 'PAL widescreen pixel aspect ratio (1.459)'),
('custom', 'Custom', 'Custom pixel aspect ratio'),
],
default='blender')
lnx_aspect_custom: FloatProperty(name="Custom PAR", description="Custom pixel aspect ratio (width/height of a single pixel)", default=1.0, min=0.01, max=10.0, precision=3)
lnx_two_sided_area_light: BoolProperty(name="Two-Sided Area Light", description="Emit light from both faces of area plane", default=False, update=assets.invalidate_shader_cache)
@staticmethod

View File

@ -112,6 +112,10 @@ class LNX_PT_ObjectPropsPanel(bpy.types.Panel):
sub.prop(obj, 'lnx_visible_shadow')
if obj.type == 'MESH':
layout.prop(obj, 'lnx_is_occluder')
if obj.lnx_is_occluder:
layout.prop(obj, 'lnx_occluder_mode')
layout.prop(obj, 'lnx_occluder_shrink')
layout.prop(obj, 'lnx_instanced')
if 'Rot' in obj.lnx_instanced:
layout.prop(obj, 'lnx_instanced_rot_type')
@ -1843,6 +1847,7 @@ class LNX_PT_RenderPathRendererPanel(bpy.types.Panel):
layout.prop(rpdat, 'rp_blending_state')
layout.prop(rpdat, 'rp_depth_texture_state')
layout.prop(rpdat, 'rp_draw_order')
layout.prop(rpdat, 'lnx_radix')
layout.prop(rpdat, 'lnx_samples_per_pixel')
layout.prop(rpdat, 'lnx_texture_filter')
layout.prop(rpdat, 'rp_sss_state')
@ -1878,7 +1883,23 @@ class LNX_PT_RenderPathRendererPanel(bpy.types.Panel):
col = layout.column()
col.prop(rpdat, "rp_hdr")
col.prop(rpdat, "rp_stereo")
col.prop(rpdat, 'lnx_culling')
col.prop(rpdat, 'lnx_backface_culling')
col.prop(rpdat, 'lnx_frustum_culling')
col.prop(rpdat, 'lnx_occlusion_culling')
if rpdat.lnx_occlusion_culling:
col.prop(rpdat, 'lnx_occlusion_all_objects')
if rpdat.lnx_occlusion_all_objects:
col.prop(rpdat, 'lnx_occlusion_min_size')
col.prop(rpdat, 'lnx_occlusion_proxy_mode')
if rpdat.lnx_occlusion_proxy_mode == 'decimate':
col.prop(rpdat, 'lnx_occlusion_decimate_mode')
col.prop(rpdat, 'lnx_occlusion_decimate_ratio')
col.prop(rpdat, 'lnx_occlusion_proxy_shrink')
col.prop(rpdat, 'lnx_occlusion_box_shrink')
col.prop(rpdat, 'lnx_occlusion_depth_eps')
col.prop(rpdat, 'lnx_aspect_mode')
if rpdat.lnx_aspect_mode == 'custom':
col.prop(rpdat, 'lnx_aspect_custom')
col.prop(rpdat, 'rp_pp')

View File

@ -249,6 +249,9 @@ let project = new Project('""" + lnx.utils.safesrc(wrd.lnx_project_name + '-' +
if not os.path.exists(os.path.join('Libraries', 'lib/hscript')):
khafile.write(add_leenkx_library(sdk_path, 'lib/hscript', rel_path=do_relpath_sdk))
khafile.write("project.addDefine('" + 'hscript' + "');\n")
if lnx.utils.get_rp().lnx_radix:
if not os.path.exists(os.path.join('Libraries', 'lib/radix')):
khafile.write(add_leenkx_library(sdk_path, 'lib/radix', rel_path=do_relpath_sdk))
# Project libraries
if os.path.exists('Libraries'):
@ -522,6 +525,9 @@ let project = new Project('""" + lnx.utils.safesrc(wrd.lnx_project_name + '-' +
if rpdat.rp_draw_order == 'Index':
assets.add_khafile_def('lnx_draworder_index')
if rpdat.lnx_radix:
assets.add_khafile_def('lnx_radix')
if lnx.utils.get_viewport_controls() == 'azerty':
assets.add_khafile_def('lnx_azerty')
@ -721,6 +727,11 @@ class Main {
f.write("\n" + content_indent + "iron.object.LightObject.cascadeSplitFactor = " + str(rpdat.lnx_shadowmap_split) + ";")
if rpdat.lnx_shadowmap_bounds != 1.0:
f.write("\n" + content_indent + "iron.object.LightObject.cascadeBounds = " + str(rpdat.lnx_shadowmap_bounds) + ";")
if rpdat.lnx_occlusion_culling:
depth_eps = max(10.0 ** -int(rpdat.lnx_occlusion_depth_eps), 1e-6)
f.write("\n" + content_indent + "iron.culling.CullSystem.boxShrink = " + str(rpdat.lnx_occlusion_box_shrink / 100) + ";")
f.write("\n" + content_indent + "iron.culling.CullSystem.depthEps = " + str(depth_eps) + ";")
asset_references = list(set(assets.assets))