Files
LNXSDK/leenkx/Sources/iron/culling/CullSystem.hx
2026-10-15 02:41:03 -07:00

774 lines
24 KiB
Haxe

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<OccluderProxy>;
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<iron.object.LightObject, Array<ShadowPassSlot>> = new haxe.ds.ObjectMap();
var shadowCallIdx: Map<iron.object.LightObject, Int> = new Map();
var shadowCallFrame: Map<iron.object.LightObject, Int> = new Map();
var lastShadowSlot: ShadowPassSlot = null;
var lastShadowFrame: Int = -1;
var lastShadowCallIdx: Int = -1;
#if lnx_csm
var shadowVps: haxe.ds.ObjectMap<iron.object.LightObject, Mat4> = 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<FrustumPlane> = null;
var frustumPlanes: Array<FrustumPlane> = null;
var shadowFrustumPlanes: Array<FrustumPlane> = null;
var uidToProxy: haxe.ds.IntMap<OccluderProxy> = new haxe.ds.IntMap<OccluderProxy>();
var uidScan: haxe.ds.IntMap<Bool> = new haxe.ds.IntMap<Bool>();
var prevVP: Mat4 = null;
#if lnx_taa
var unjitteredVP: Mat4 = null;
#end
public var shadowFlagsValid: Bool = false;
var inFrustumList: Array<Int> = [];
var selIdx: Array<Int> = [];
var selScore: Array<Float> = [];
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<iron.object.MeshObject>,
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<iron.object.MeshObject>, 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<FrustumPlane>
) {
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<Int> = 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<Bool> = [];
public var flagsLen: Int = 0;
public function new() {}
}