Object Occlusion Culling
This commit is contained in:
@ -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 = {
|
||||
|
||||
Reference in New Issue
Block a user