package iron.culling; import iron.object.CameraObject; import iron.object.FrustumPlane; import iron.Scene; import iron.RenderPath; import iron.math.Mat4; class CullSystem { public static var active: CullSystem = null; public var tier: CullTier; public var cullData: CullData; public var rasterizer: SoftwareOcclusionRasterizer; public var shadowRasterizer: SoftwareOcclusionRasterizer; public var occluderProxies: Array; public var bvh: ObjectBVH; public var temporal: TemporalState; public var enabled: Bool = true; public var occlusionEnabled: Bool = true; public var bvhDirty: Bool = true; public var proxiesDirty: Bool = true; var dataSyncedFrame: Int = -1; var focalLengthPx: Float = 500.0; var orthoPxPerUnit: Float = 0.0; var screenW: Int = 1920; var screenH: Int = 1080; public static var maxOccluders: Int = 48; public static var maxOccluderTris: Int = 4000; public static var maxTrisPerProxy: Int = 768; public static var maxShadowOccluders: Int = 24; public static var maxShadowOccluderTris: Int = 1536; public static var boxOccluders: Bool = false; public static var shadowBoxOccluders: Bool = false; public static var shadowBoxShrink: Float = 0.7; public static var shadowCullingEnabled: Bool = true; public static var minOccluderPx: Float = 24.0; public static var boxShrink: Float = 0.5; public static var depthEps: Float = 1e-6; public static var shadowCullInterval: Int = 2; static inline var MAX_SHADOW_SLOTS = 8; var shadowPasses: haxe.ds.ObjectMap> = new haxe.ds.ObjectMap(); var shadowCallIdx: Map = new Map(); var shadowCallFrame: Map = new Map(); var lastShadowSlot: ShadowPassSlot = null; var lastShadowFrame: Int = -1; var lastShadowCallIdx: Int = -1; #if lnx_csm var shadowVps: haxe.ds.ObjectMap = new haxe.ds.ObjectMap(); #end var sceneRev: Int = 0; var sceneRevFrame: Int = -1; var frameCounter: Int = 0; var bvhSyncedFrame: Int = -1; // Frame when BVH was built/refit var occlusionFrustumPlanes: Array = null; var frustumPlanes: Array = null; var shadowFrustumPlanes: Array = null; var uidToProxy: haxe.ds.IntMap = new haxe.ds.IntMap(); var uidScan: haxe.ds.IntMap = new haxe.ds.IntMap(); var prevVP: Mat4 = null; #if lnx_taa var unjitteredVP: Mat4 = null; #end public var shadowFlagsValid: Bool = false; var inFrustumList: Array = []; var selIdx: Array = []; var selScore: Array = []; var selCount: Int = 0; public function new() { cullData = new CullData(); rasterizer = new SoftwareOcclusionRasterizer(128, 64); shadowRasterizer = new SoftwareOcclusionRasterizer(128, 128); occluderProxies = []; temporal = new TemporalState(); tier = detectTier(); } static inline var MAT_EPS = 1e-7; static inline function matEquals(a: Mat4, b: Mat4): Bool { return Math.abs(a._00 - b._00) < MAT_EPS && Math.abs(a._10 - b._10) < MAT_EPS && Math.abs(a._20 - b._20) < MAT_EPS && Math.abs(a._30 - b._30) < MAT_EPS && Math.abs(a._01 - b._01) < MAT_EPS && Math.abs(a._11 - b._11) < MAT_EPS && Math.abs(a._21 - b._21) < MAT_EPS && Math.abs(a._31 - b._31) < MAT_EPS && Math.abs(a._02 - b._02) < MAT_EPS && Math.abs(a._12 - b._12) < MAT_EPS && Math.abs(a._22 - b._22) < MAT_EPS && Math.abs(a._32 - b._32) < MAT_EPS && Math.abs(a._03 - b._03) < MAT_EPS && Math.abs(a._13 - b._13) < MAT_EPS && Math.abs(a._23 - b._23) < MAT_EPS && Math.abs(a._33 - b._33) < MAT_EPS; } public static function detectTier(): CullTier { #if (target.threaded) return CPUMultiThread; #else return CPUSingleThread; #end } public function cull(camera: CameraObject) { frameCounter = RenderPath.active != null ? RenderPath.active.frame : frameCounter + 1; if (!enabled || Scene.active == null || Scene.active.meshes.length == 0) { if (Scene.active != null) { var meshes = Scene.active.meshes; for (i in 0...meshes.length) { meshes[i].occlusionVisible = true; meshes[i].shadowOcclusionVisible = true; } } return; } var rp = RenderPath.active; if (rp != null) { screenW = rp.currentW; screenH = rp.currentH; } var targetW = Std.int(Math.max(128, Math.min(512, screenW >> 4))); var targetH = Std.int(Math.max(64, Math.min(256, screenH >> 4))); if (rasterizer.width < (targetW >> 1) || rasterizer.width > (targetW << 1) || rasterizer.height < (targetH >> 1) || rasterizer.height > (targetH << 1)) { rasterizer = new SoftwareOcclusionRasterizer(targetW, targetH); } if (camera.data.raw.ortho == null) { focalLengthPx = ContributionCull.computeFocalLengthPx( camera.data.raw.fov, screenH ); orthoPxPerUnit = 0.0; } else { focalLengthPx = 0; var o = camera.data.raw.ortho; var halfH = (o[3] - o[2]) * 0.5; orthoPxPerUnit = halfH > 1e-9 ? screenH * 0.5 / halfH : 0.0; } rasterizer.nearClip = camera.data.raw.ortho == null ? camera.data.raw.near_plane : 0.0001; if (dataSyncedFrame != frameCounter) { cullData.syncFromScene(); dataSyncedFrame = frameCounter; } if (cullData.slotsRebuilt) temporal.clear(); if (sceneRevFrame != frameCounter) { sceneRevFrame = frameCounter; if (cullData.anyMoved || cullData.anyFlagsChanged || cullData.orderChanged) sceneRev++; } var cd = cullData; var meshes = cd.meshes; #if lnx_taa if (unjitteredVP == null) unjitteredVP = Mat4.identity(); unjitteredVP.multmats(camera.noJitterP, camera.V); var cmpVP = unjitteredVP; #else var cmpVP = camera.VP; #end var vpSame = prevVP != null && matEquals(cmpVP, prevVP); if (vpSame && !cd.anyMoved && !cd.anyFlagsChanged && !cd.orderChanged && !bvhDirty) { return; } var needBvhRebuild = bvhDirty; bvhDirty = false; if (prevVP == null) prevVP = Mat4.identity(); prevVP.setFrom(cmpVP); if (occlusionFrustumPlanes == null) { occlusionFrustumPlanes = []; for (i in 0...6) occlusionFrustumPlanes.push(new FrustumPlane()); } { var vp = camera.VP; var p = occlusionFrustumPlanes; p[0].setComponents(vp._03 + vp._00, vp._13 + vp._10, vp._23 + vp._20, vp._33 + vp._30); p[1].setComponents(vp._03 - vp._00, vp._13 - vp._10, vp._23 - vp._20, vp._33 - vp._30); p[2].setComponents(vp._03 - vp._01, vp._13 - vp._11, vp._23 - vp._21, vp._33 - vp._31); p[3].setComponents(vp._03 + vp._01, vp._13 + vp._11, vp._23 + vp._21, vp._33 + vp._31); p[4].setComponents(vp._02, vp._12, vp._22, vp._32); p[5].setComponents(vp._03 - vp._02, vp._13 - vp._12, vp._23 - vp._22, vp._33 - vp._32); } frustumPlanes = occlusionFrustumPlanes; var camX = camera.transform.worldx(); var camY = camera.transform.worldy(); var camZ = camera.transform.worldz(); if (bvh == null) bvh = new ObjectBVH(); if (bvhSyncedFrame != frameCounter) { if (needBvhRebuild || bvh.root < 0) { bvh.build(cd); } else if (cd.anyMoved || cd.orderChanged) { bvh.refit(cd); } bvhSyncedFrame = frameCounter; } inFrustumList.resize(0); var planes = frustumPlanes; var fpx = focalLengthPx; bvh.traverseFrontToBack(camX, camY, camZ, function(node: BVHNode): Bool { if (!ObjectBVH.aabbInFrustum(node, planes)) return true; if (!ContributionCull.passesNode(node, camX, camY, camZ, fpx)) return true; return false; }, function(objIdx: Int, _: Int) { if ((cd.getFlags(objIdx) & 8) == 0) return; if (!ContributionCull.passes( cd.getCenterX(objIdx), cd.getCenterY(objIdx), cd.getCenterZ(objIdx), cd.getRadius(objIdx), camX, camY, camZ, fpx )) return; inFrustumList.push(objIdx); } ); cullData.clearVisible(); if (occlusionEnabled) { selectOccluders(meshes, camX, camY, camZ); for (i in 0...meshes.length) { meshes[i].occlusionVisible = true; } rasterizer.clear(); if (selCount > 0) rasterizeSelected(meshes, camera.VP); var trisDone = rasterizer.trianglesRasterized; if (trisDone > 0) rasterizer.buildHiZ(); if (trisDone > 0) { testVisibilityBVH(camera.VP, camX, camY, camZ, planes); } else { for (k in 0...inFrustumList.length) { var objIdx = inFrustumList[k]; cd.addVisible(objIdx); } } } else { for (i in 0...meshes.length) { meshes[i].occlusionVisible = true; } for (k in 0...inFrustumList.length) { var objIdx = inFrustumList[k]; cd.addVisible(objIdx); } } temporal.advanceFrame(); } public function cullShadow(light: iron.object.LightObject) { if (!occlusionEnabled || light == null || Scene.active == null) return; if (!shadowCullingEnabled) { var ms = Scene.active.meshes; for (m in ms) m.shadowOcclusionVisible = true; return; } var frame = RenderPath.active != null ? RenderPath.active.frame : frameCounter; var vp = light.VP; if (vp == null) return; var cd = cullData; if (dataSyncedFrame != frame) { cd.syncFromScene(); dataSyncedFrame = frame; } if (sceneRevFrame != frame) { sceneRevFrame = frame; if (cd.anyMoved || cd.anyFlagsChanged || cd.orderChanged) sceneRev++; } var meshes = cd.meshes; var slots = shadowPasses.get(light); if (slots == null) { slots = []; shadowPasses.set(light, slots); } var callIdx = 0; { var f = shadowCallFrame.get(light); callIdx = (f == frame) ? shadowCallIdx.get(light) + 1 : 0; shadowCallIdx.set(light, callIdx); shadowCallFrame.set(light, frame); } var slotIdx = callIdx < MAX_SHADOW_SLOTS ? callIdx : MAX_SHADOW_SLOTS - 1; while (slots.length <= slotIdx) slots.push(new ShadowPassSlot()); var slot = slots[slotIdx]; if (slot != null && slot == lastShadowSlot && lastShadowFrame == frame && lastShadowCallIdx == callIdx && slot.appliedRev == sceneRev) return; for (i in 0...meshes.length) { meshes[i].shadowOcclusionVisible = true; } #if lnx_csm var isCsmLight = light.data.raw.type == "sun" && iron.object.LightObject.cascadeCount > 1; if (isCsmLight && callIdx == 0) { var vp0 = shadowVps.get(light); if (vp0 == null) { vp0 = Mat4.identity(); shadowVps.set(light, vp0); } vp0.setFrom(vp); } #end if (shadowCullInterval > 1) { if ((frame + slotIdx) % shadowCullInterval != 0) { var applied = false; #if lnx_csm if (isCsmLight && callIdx > 0) { var head = slots[0]; var vp0 = shadowVps.get(light); if (head.appliedRev == sceneRev && head.flagsLen > 0 && head.vp != null && vp0 != null && matEquals(vp0, head.vp)) { var hn = head.flagsLen < meshes.length ? head.flagsLen : meshes.length; for (i in 0...hn) meshes[i].shadowOcclusionVisible = head.flags[i]; } applied = true; } #end if (!applied && slot.appliedRev == sceneRev && slot.flagsLen > 0 && slot.vp != null && matEquals(vp, slot.vp)) { var n = slot.flagsLen < meshes.length ? slot.flagsLen : meshes.length; for (i in 0...n) meshes[i].shadowOcclusionVisible = slot.flags[i]; } lastShadowSlot = slot; lastShadowFrame = frame; lastShadowCallIdx = callIdx; return; } } if (bvh == null) bvh = new ObjectBVH(); if (bvhSyncedFrame != frame) { if (bvhDirty || bvh.root < 0) { bvh.build(cd); } else if (cd.anyMoved || cd.orderChanged) { bvh.refit(cd); } bvhSyncedFrame = frame; } if (shadowFrustumPlanes == null) { shadowFrustumPlanes = []; for (i in 0...6) shadowFrustumPlanes.push(new FrustumPlane()); } { var p = shadowFrustumPlanes; p[0].setComponents(vp._03 + vp._00, vp._13 + vp._10, vp._23 + vp._20, vp._33 + vp._30); p[1].setComponents(vp._03 - vp._00, vp._13 - vp._10, vp._23 - vp._20, vp._33 - vp._30); p[2].setComponents(vp._03 - vp._01, vp._13 - vp._11, vp._23 - vp._21, vp._33 - vp._31); p[3].setComponents(vp._03 + vp._01, vp._13 + vp._11, vp._23 + vp._21, vp._33 + vp._31); p[4].setComponents(vp._02, vp._12, vp._22, vp._32); p[5].setComponents(vp._03 - vp._02, vp._13 - vp._12, vp._23 - vp._22, vp._33 - vp._32); } var lightPlanes = shadowFrustumPlanes; var lx = light.transform.worldx(); var ly = light.transform.worldy(); var lz = light.transform.worldz(); var raster = slot.rasterizer; var vpSame = slot.vp != null && matEquals(vp, slot.vp); if (slot.sceneRev == sceneRev && vpSame && raster != null) { // skip } else { if (raster == null) { raster = new SoftwareOcclusionRasterizer( shadowRasterizer.width, shadowRasterizer.height); slot.rasterizer = raster; } if (slot.vp == null) slot.vp = Mat4.identity(); slot.vp.setFrom(vp); var perspective = light.data.raw.type != "sun"; selCount = 0; bvh.traverseFrontToBack(lx, ly, lz, function(node: BVHNode): Bool { if (!ObjectBVH.aabbInFrustum(node, lightPlanes)) return true; return false; }, function(i: Int, _: Int) { if ((cd.getFlags(i) & 8) == 0) return; if ((cd.getFlags(i) & 16) != 0) return; if ((cd.getFlags(i) & 4) == 0) return; if ((cd.getFlags(i) & 32) != 0) return; if (i >= meshes.length) return; var r = cd.getRadius(i); var score = r; if (perspective) { var dx = cd.getCenterX(i) - lx; var dy = cd.getCenterY(i) - ly; var dz = cd.getCenterZ(i) - lz; var dist2 = dx * dx + dy * dy + dz * dz; if (dist2 <= r * r) return; score = r / Math.sqrt(dist2); } insertTopK(i, score, maxShadowOccluders); } ); raster = slot.rasterizer; raster.clear(); var triBudget = maxShadowOccluderTris; for (s in 0...selCount) { if (triBudget < 12) break; var i = selIdx[s]; var proxy = uidToProxy.get(cd.getObjectID(i)); if (proxy != null && proxy.triangleCount > 0 && proxy.triangleCount <= maxTrisPerProxy && proxy.triangleCount <= triBudget) { proxy.rasterize(raster, meshes[i].transform.world, vp, raster.width, raster.height); triBudget -= proxy.triangleCount; } else if (shadowBoxOccluders) { rasterizeBoxOccluder(raster, i, vp, shadowBoxShrink); triBudget -= 12; } } if (raster.trianglesRasterized > 0) raster.buildHiZ(); } if (raster.trianglesRasterized > 0) { var sw = raster.width; var sh = raster.height; bvh.traverseFrontToBack(lx, ly, lz, function(node: BVHNode): Bool { if (!ObjectBVH.aabbInFrustum(node, lightPlanes)) return true; var anyVisible = false; if (node.isLeaf) { for (j in 0...node.objectCount) { var idx = bvh.objectIndices[node.firstObject + j]; if ((cd.getFlags(idx) & 8) != 0 && idx < meshes.length) { anyVisible = true; break; } } if (!anyVisible) return true; } if (raster.isOccluded( node.minX, node.minY, node.minZ, node.maxX, node.maxY, node.maxZ, vp, sw, sh )) { bvh.forEachLeafInSubtree(node, function(idx: Int) { if (idx < meshes.length && (cd.getFlags(idx) & 8) != 0) { meshes[idx].shadowOcclusionVisible = false; } }); return true; } return false; }, function(objIdx: Int, _: Int) { if ((cd.getFlags(objIdx) & 8) == 0) return; if (objIdx >= meshes.length) return; meshes[objIdx].shadowOcclusionVisible = true; if (raster.isOccluded( cd.getMinX(objIdx), cd.getMinY(objIdx), cd.getMinZ(objIdx), cd.getMaxX(objIdx), cd.getMaxY(objIdx), cd.getMaxZ(objIdx), vp, sw, sh )) { meshes[objIdx].shadowOcclusionVisible = false; } } ); } #if lnx_csm if (isCsmLight) { for (c in 1...iron.object.LightObject.cascadeCount) { if (c >= slots.length) break; var s = slots[c]; if (s == null || s == slot || s.vp == null || s.rasterizer == null) continue; for (i in 0...meshes.length) { if (meshes[i].shadowOcclusionVisible) continue; if (!s.rasterizer.isOccluded( cd.getMinX(i), cd.getMinY(i), cd.getMinZ(i), cd.getMaxX(i), cd.getMaxY(i), cd.getMaxZ(i), s.vp, s.rasterizer.width, s.rasterizer.height )) { meshes[i].shadowOcclusionVisible = true; } } } } #end if (slot.flags.length < meshes.length) slot.flags.resize(meshes.length); slot.flagsLen = meshes.length; for (i in 0...meshes.length) slot.flags[i] = meshes[i].shadowOcclusionVisible; #if lnx_csm if (isCsmLight && callIdx > 0) { var head = slots[0]; var vp0 = shadowVps.get(light); if (head.appliedRev == sceneRev && head.flagsLen > 0 && head.vp != null && vp0 != null && matEquals(vp0, head.vp)) { var hn = head.flagsLen < meshes.length ? head.flagsLen : meshes.length; for (i in 0...hn) meshes[i].shadowOcclusionVisible = head.flags[i]; } else { for (i in 0...meshes.length) meshes[i].shadowOcclusionVisible = true; } } #end slot.sceneRev = sceneRev; slot.appliedRev = sceneRev; slot.lastUsed = frame; lastShadowSlot = slot; lastShadowFrame = frame; lastShadowCallIdx = callIdx; shadowFlagsValid = true; } function selectOccluders( meshes: Array, camX: Float, camY: Float, camZ: Float ) { var cd = cullData; selCount = 0; var minPx = minOccluderPx; var autoPx = autoOccluderMinScreenRatio * screenH; for (k in 0...inFrustumList.length) { var i = inFrustumList[k]; if (i >= meshes.length) continue; if (!meshes[i].occlusionVisible) continue; var flags = cd.getFlags(i); if ((flags & 16) != 0) continue; if ((flags & 32) != 0) continue; var r = cd.getRadius(i); var px: Float; var camInside = false; if (focalLengthPx > 0) { var dx = cd.getCenterX(i) - camX; var dy = cd.getCenterY(i) - camY; var dz = cd.getCenterZ(i) - camZ; var dist2 = dx * dx + dy * dy + dz * dz; if (dist2 <= r * r) camInside = true; px = camInside ? 1e6 : focalLengthPx * r / Math.sqrt(dist2); } else { px = r * orthoPxPerUnit; } if (px < minPx) continue; if ((flags & 1) == 0 && px < autoPx) continue; if (camInside || !boxOccluders) { var p = uidToProxy.get(cd.getObjectID(i)); if (p == null || p.triangleCount <= 0 || p.triangleCount > maxTrisPerProxy) continue; } insertTopK(i, px, maxOccluders); } } function insertTopK(idx: Int, score: Float, k: Int) { if (selIdx.length < k + 1) { selIdx.resize(k + 1); selScore.resize(k + 1); } var pos = selCount; while (pos > 0 && selScore[pos - 1] < score) pos--; if (pos >= k) return; var last = selCount < k ? selCount : k - 1; var j = last; while (j > pos) { selIdx[j] = selIdx[j - 1]; selScore[j] = selScore[j - 1]; j--; } selIdx[pos] = idx; selScore[pos] = score; if (selCount < k) selCount++; } function rasterizeSelected(meshes: Array, vp: Mat4) { var cd = cullData; var triBudget = maxOccluderTris; #if (target.threaded) rasterizer.beginCollect(); #end for (s in 0...selCount) { if (triBudget < 12) break; var i = selIdx[s]; var proxy = uidToProxy.get(cd.getObjectID(i)); if (proxy != null && proxy.triangleCount > 0 && proxy.triangleCount <= maxTrisPerProxy && proxy.triangleCount <= triBudget) { var mesh = i < meshes.length ? meshes[i] : null; if (mesh == null) continue; proxy.rasterize(rasterizer, mesh.transform.world, vp, screenW, screenH); triBudget -= proxy.triangleCount; } else { if (!boxOccluders) continue; rasterizeBoxOccluder(rasterizer, i, vp); triBudget -= 12; } } #if (target.threaded) rasterizer.flushCollected(); #end } function rasterizeBoxOccluder(raster: SoftwareOcclusionRasterizer, objectIndex: Int, vp: Mat4, shrinkOverride = -1.0) { var cd = cullData; var bx0 = cd.getMinX(objectIndex), by0 = cd.getMinY(objectIndex), bz0 = cd.getMinZ(objectIndex); var bx1 = cd.getMaxX(objectIndex), by1 = cd.getMaxY(objectIndex), bz1 = cd.getMaxZ(objectIndex); var cx = (bx0 + bx1) * 0.5; var cy = (by0 + by1) * 0.5; var cz = (bz0 + bz1) * 0.5; var boxShrink = shrinkOverride >= 0.0 ? shrinkOverride : CullSystem.boxShrink; var hx = (bx1 - bx0) * 0.5 * boxShrink; var hy = (by1 - by0) * 0.5 * boxShrink; var hz = (bz1 - bz0) * 0.5 * boxShrink; raster.rasterizeBox( cx - hx, cy - hy, cz - hz, cx + hx, cy + hy, cz + hz, vp, raster.width, raster.height ); } function testVisibilityBVH( vp: Mat4, camX: Float, camY: Float, camZ: Float, planes: Array ) { var cd = cullData; var raster = rasterizer; var sw = screenW; var sh = screenH; var meshes = cd.meshes; var bvh = this.bvh; var temp = temporal; var retestInterval = 4; bvh.traverseFrontToBack(camX, camY, camZ, function(node: BVHNode): Bool { if (!ObjectBVH.aabbInFrustum(node, planes)) return true; var anyVisible = false; if (node.isLeaf) { for (j in 0...node.objectCount) { var idx = bvh.objectIndices[node.firstObject + j]; if ((cd.getFlags(idx) & 8) != 0) { anyVisible = true; break; } } if (!anyVisible) return true; } if (raster.isOccluded( node.minX, node.minY, node.minZ, node.maxX, node.maxY, node.maxZ, vp, sw, sh )) { bvh.forEachLeafInSubtree(node, function(idx: Int) { if (idx >= meshes.length || (cd.getFlags(idx) & 8) == 0) return; meshes[idx].occlusionVisible = false; var uid = cd.getObjectID(idx); temp.setVisible(uid, false); temp.markTested(uid); }); return true; } return false; }, function(objIdx: Int, _: Int) { if ((cd.getFlags(objIdx) & 8) == 0) return; if (objIdx >= meshes.length) return; var uid = cd.getObjectID(objIdx); if (temp.wasVisibleLastFrame(uid) && !temp.needsVisibleReTest(uid, retestInterval)) { meshes[objIdx].occlusionVisible = true; cd.addVisible(objIdx); return; } if (raster.isOccluded( cd.getMinX(objIdx), cd.getMinY(objIdx), cd.getMinZ(objIdx), cd.getMaxX(objIdx), cd.getMaxY(objIdx), cd.getMaxZ(objIdx), vp, sw, sh )) { meshes[objIdx].occlusionVisible = false; temp.setVisible(uid, false); temp.markTested(uid); } else { meshes[objIdx].occlusionVisible = true; cd.addVisible(objIdx); temp.setVisible(uid, true); temp.markTested(uid); } } ); } public static var autoOccluderMinScreenRatio: Float = 0.10; public inline function isVisible(objectIndex: Int): Bool { var ms = cullData.meshes; return objectIndex >= 0 && objectIndex < ms.length && ms[objectIndex].occlusionVisible; } public inline function markDirty() { bvhDirty = true; proxiesDirty = true; cullData.slotsDirty = true; shadowFlagsValid = false; } public function removeLight(light: iron.object.LightObject) { shadowPasses.remove(light); shadowCallIdx.remove(light); shadowCallFrame.remove(light); #if lnx_csm shadowVps.remove(light); #end } public static function init() { if (active == null) { active = new CullSystem(); } } public function loadOccluderProxies() { occluderProxies.resize(0); if (Scene.active == null) { uidToProxy.clear(); return; } var meshes = Scene.active.meshes; uidScan.clear(); for (i in 0...meshes.length) { var mesh = meshes[i]; uidScan.set(mesh.uid, true); if (uidToProxy.exists(mesh.uid)) continue; if (mesh.data == null || mesh.data.raw == null) continue; var raw = mesh.data.raw; if (raw.occluder_proxy == null) continue; var proxyRaw = raw.occluder_proxy; if (proxyRaw.positions == null || proxyRaw.indices == null) continue; var proxy = new OccluderProxy(); proxy.setData(proxyRaw.positions, proxyRaw.indices, mesh.uid); uidToProxy.set(mesh.uid, proxy); } var dead: Array = null; for (uid in uidToProxy.keys()) { if (!uidScan.exists(uid)) { if (dead == null) dead = []; dead.push(uid); } } if (dead != null) for (uid in dead) uidToProxy.remove(uid); for (proxy in uidToProxy.iterator()) occluderProxies.push(proxy); } public static function shutdown() { active = null; } } class ShadowPassSlot { public var vp: Mat4 = null; public var rasterizer: SoftwareOcclusionRasterizer = null; public var sceneRev: Int = -1; public var appliedRev: Int = -1; public var lastUsed: Int = -1; public var flags: Array = []; public var flagsLen: Int = 0; public function new() {} }