package iron.culling; import iron.object.MeshObject; import iron.Scene; import kha.arrays.Float32Array; class CullData { static inline var STRIDE = 16; static inline var BASIS_STRIDE = 12; public var data: Float32Array; public var prevData: Float32Array; public var uidData: kha.arrays.Int32Array; public var prevUidData: kha.arrays.Int32Array; var basisData: Float32Array; var prevBasisData: Float32Array; public var count: Int = 0; public var prevCount: Int = 0; public var anyMoved: Bool = false; public var anyFlagsChanged: Bool = false; public var orderChanged: Bool = false; public var meshes: Array = []; public var slotsDirty: Bool = true; public var slotsRebuilt: Bool = false; public var visibleList: Array; public var visibleCount: Int = 0; var staticFlagsCache: haxe.ds.IntMap = new haxe.ds.IntMap(); var matTranspCache: haxe.ds.IntMap = new haxe.ds.IntMap(); var matSnapshots: haxe.ds.IntMap> = new haxe.ds.IntMap(); var prevUidMap: haxe.ds.IntMap = new haxe.ds.IntMap(); public function new() { visibleList = []; } public inline function stride(): Int { return STRIDE; } public function ensureCapacity(cap: Int) { if (data == null || data.length < cap * STRIDE) { var newCap = cap; if (data != null) newCap = Std.int(Math.max(cap, data.length / STRIDE * 2)); var oldData = data; var oldPrev = prevData; var oldUid = uidData; var oldPrevUid = prevUidData; var oldBasis = basisData; var oldPrevBasis = prevBasisData; data = new Float32Array(newCap * STRIDE); prevData = new Float32Array(newCap * STRIDE); uidData = new kha.arrays.Int32Array(newCap); prevUidData = new kha.arrays.Int32Array(newCap); basisData = new Float32Array(newCap * BASIS_STRIDE); prevBasisData = new Float32Array(newCap * BASIS_STRIDE); if (oldData != null) { var copyLen = oldData.length; for (i in 0...copyLen) data[i] = oldData[i]; } if (oldPrev != null) { var copyLen = oldPrev.length; for (i in 0...copyLen) prevData[i] = oldPrev[i]; } if (oldUid != null) { var copyLen = oldUid.length; for (i in 0...copyLen) uidData[i] = oldUid[i]; } if (oldPrevUid != null) { var copyLen = oldPrevUid.length; for (i in 0...copyLen) prevUidData[i] = oldPrevUid[i]; } if (oldBasis != null) { var copyLen = oldBasis.length; for (i in 0...copyLen) basisData[i] = oldBasis[i]; } if (oldPrevBasis != null) { var copyLen = oldPrevBasis.length; for (i in 0...copyLen) prevBasisData[i] = oldPrevBasis[i]; } } } public function syncFromScene() { var sceneMeshes = Scene.active.meshes; slotsRebuilt = false; if (slotsDirty || meshes.length != sceneMeshes.length) { meshes = sceneMeshes.copy(); slotsDirty = false; slotsRebuilt = true; staticFlagsCache.clear(); matTranspCache.clear(); matSnapshots.clear(); } count = meshes.length; ensureCapacity(count); var tmpD = prevData; prevData = data; data = tmpD; var tmpU = prevUidData; prevUidData = uidData; uidData = tmpU; var tmpB = prevBasisData; prevBasisData = basisData; basisData = tmpB; prevUidMap.clear(); var prevUidMapBuilt = false; anyMoved = false; anyFlagsChanged = false; orderChanged = false; for (i in 0...count) { var mesh = meshes[i]; var t = mesh.transform; var uid = mesh.uid; var p = mesh.parent; while (p != null) { p.transform.update(); p = p.parent; } t.update(); var off = i * STRIDE; var r = t.radius; var wx = t.worldx(); var wy = t.worldy(); var wz = t.worldz(); var radiusScale = mesh.data.isSkinned ? 2.0 : 1.0; #if lnx_gpu_particles if (mesh.particleSystems != null || mesh.particleOwner != null) radiusScale *= 1000; #end if (mesh.data.geom.instanced) radiusScale *= 100; r *= radiusScale; data[off + 3] = r; data[off + 4] = wx; data[off + 5] = wy; data[off + 6] = wz; var w = t.world; var prevIdx: Null = null; if (prevUidData != null && i < prevCount && prevUidData[i] == uid) { prevIdx = i; } else { if (!prevUidMapBuilt) { prevUidMapBuilt = true; if (prevUidData != null) { for (pi in 0...prevCount) prevUidMap.set(prevUidData[pi], pi); } } prevIdx = prevUidMap.get(uid); } if (prevIdx != null && prevIdx != i) orderChanged = true; var aabbCached = false; if (prevIdx != null) { var poff = prevIdx * STRIDE; var pb = prevIdx * BASIS_STRIDE; if (prevData[poff + 4] == wx && prevData[poff + 5] == wy && prevData[poff + 6] == wz && prevBasisData[pb + 0] == w._00 && prevBasisData[pb + 1] == w._10 && prevBasisData[pb + 2] == w._20 && prevBasisData[pb + 3] == w._01 && prevBasisData[pb + 4] == w._11 && prevBasisData[pb + 5] == w._21 && prevBasisData[pb + 6] == w._02 && prevBasisData[pb + 7] == w._12 && prevBasisData[pb + 8] == w._22 && prevBasisData[pb + 9] == t.dim.x && prevBasisData[pb + 10] == t.dim.y && prevBasisData[pb + 11] == t.dim.z) { data[off + 0] = prevData[poff + 0]; data[off + 1] = prevData[poff + 1]; data[off + 2] = prevData[poff + 2]; data[off + 8] = prevData[poff + 8]; data[off + 9] = prevData[poff + 9]; data[off + 10] = prevData[poff + 10]; aabbCached = true; } } if (!aabbCached) { var r00 = w._00, r10 = w._10, r20 = w._20; var r01 = w._01, r11 = w._11, r21 = w._21; var r02 = w._02, r12 = w._12, r22 = w._22; var lenX = Math.sqrt(r00 * r00 + r01 * r01 + r02 * r02); var lenY = Math.sqrt(r10 * r10 + r11 * r11 + r12 * r12); var lenZ = Math.sqrt(r20 * r20 + r21 * r21 + r22 * r22); if (lenX > 1e-8) { r00 /= lenX; r01 /= lenX; r02 /= lenX; } if (lenY > 1e-8) { r10 /= lenY; r11 /= lenY; r12 /= lenY; } if (lenZ > 1e-8) { r20 /= lenZ; r21 /= lenZ; r22 /= lenZ; } var hx = t.dim.x * 0.5 * radiusScale; var hy = t.dim.y * 0.5 * radiusScale; var hz = t.dim.z * 0.5 * radiusScale; var aabbMinX = 1e20, aabbMinY = 1e20, aabbMinZ = 1e20; var aabbMaxX = -1e20, aabbMaxY = -1e20, aabbMaxZ = -1e20; for (c in 0...8) { var cx = (c & 1) != 0 ? hx : -hx; var cy = (c & 2) != 0 ? hy : -hy; var cz = (c & 4) != 0 ? hz : -hz; var px = cx * r00 + cy * r10 + cz * r20 + wx; var py = cx * r01 + cy * r11 + cz * r21 + wy; var pz = cx * r02 + cy * r12 + cz * r22 + wz; if (px < aabbMinX) aabbMinX = px; if (py < aabbMinY) aabbMinY = py; if (pz < aabbMinZ) aabbMinZ = pz; if (px > aabbMaxX) aabbMaxX = px; if (py > aabbMaxY) aabbMaxY = py; if (pz > aabbMaxZ) aabbMaxZ = pz; } data[off + 0] = aabbMinX; data[off + 1] = aabbMinY; data[off + 2] = aabbMinZ; data[off + 8] = aabbMaxX; data[off + 9] = aabbMaxY; data[off + 10] = aabbMaxZ; } var flags: Int; var cachedFlags = staticFlagsCache.get(uid); if (cachedFlags != null) { flags = cachedFlags; } else { flags = 0; if (mesh.raw != null && mesh.raw.properties != null) { for (prop in mesh.raw.properties) { if (prop.name == "occluder" && prop.value == true) { flags |= 1; break; } } } if ((flags & 1) == 0 && mesh.data != null && mesh.data.raw != null && mesh.data.raw.occluder_proxy != null) { flags |= 1; } if (mesh.raw != null && mesh.raw.visible_shadow != false) flags |= 4; staticFlagsCache.set(uid, flags); } var mats = mesh.materials; var snap = matSnapshots.get(uid); var matsChanged = snap == null || mats == null || snap.length != mats.length; if (!matsChanged) { for (mi in 0...mats.length) { if (snap[mi] != mats[mi]) { matsChanged = true; break; } } } if (matsChanged) { var tbit = 0; if (mats != null) { for (mi in 0...mats.length) { var mat = mats[mi]; if (mat == null || mat.shader == null) continue; for (sc in mat.shader.contexts) { var sr = sc.raw; if (sr != null && sr.name != null && sr.name.substr(0, 4) == "mesh" && (sr.depth_write != true || sr.blend_source != null)) { tbit = 32; break; } } if (tbit != 0) break; } } matTranspCache.set(uid, tbit); var nsnap = new haxe.ds.Vector(mats != null ? mats.length : 0); for (mi in 0...nsnap.length) nsnap[mi] = mats[mi]; matSnapshots.set(uid, nsnap); } var tbitCached = matTranspCache.get(uid); if (tbitCached != null) flags |= tbitCached; if (mesh.visible) flags |= 8; if (radiusScale != 1.0) flags |= 16; if (prevIdx != null) { var poff = prevIdx * STRIDE; if (prevData[poff + 0] != data[off + 0] || prevData[poff + 1] != data[off + 1] || prevData[poff + 2] != data[off + 2] || prevData[poff + 8] != data[off + 8] || prevData[poff + 9] != data[off + 9] || prevData[poff + 10] != data[off + 10]) { flags |= 2; anyMoved = true; } } else { flags |= 2; anyMoved = true; anyFlagsChanged = true; } if (prevIdx != null) { var poff = prevIdx * STRIDE; if (((flags ^ Std.int(prevData[poff + 7])) & ~2) != 0) anyFlagsChanged = true; } data[off + 7] = flags; var boff = i * BASIS_STRIDE; basisData[boff + 0] = w._00; basisData[boff + 1] = w._10; basisData[boff + 2] = w._20; basisData[boff + 3] = w._01; basisData[boff + 4] = w._11; basisData[boff + 5] = w._21; basisData[boff + 6] = w._02; basisData[boff + 7] = w._12; basisData[boff + 8] = w._22; basisData[boff + 9] = t.dim.x; basisData[boff + 10] = t.dim.y; basisData[boff + 11] = t.dim.z; uidData[i] = uid; } prevCount = count; } public inline function getMinX(i: Int): Float { return data[i * STRIDE + 0]; } public inline function getMinY(i: Int): Float { return data[i * STRIDE + 1]; } public inline function getMinZ(i: Int): Float { return data[i * STRIDE + 2]; } public inline function getRadius(i: Int): Float { return data[i * STRIDE + 3]; } public inline function getCenterX(i: Int): Float { return data[i * STRIDE + 4]; } public inline function getCenterY(i: Int): Float { return data[i * STRIDE + 5]; } public inline function getCenterZ(i: Int): Float { return data[i * STRIDE + 6]; } public inline function getFlags(i: Int): Int { return Std.int(data[i * STRIDE + 7]); } public inline function isOccluder(i: Int): Bool { return (getFlags(i) & 1) != 0; } public inline function isDynamic(i: Int): Bool { return (getFlags(i) & 2) != 0; } public inline function isShadowCaster(i: Int): Bool { return (getFlags(i) & 4) != 0; } public inline function isMeshVisible(i: Int): Bool { return (getFlags(i) & 8) != 0; } public inline function getObjectID(i: Int): Int { return uidData[i]; } public inline function getMaxX(i: Int): Float { return data[i * STRIDE + 8]; } public inline function getMaxY(i: Int): Float { return data[i * STRIDE + 9]; } public inline function getMaxZ(i: Int): Float { return data[i * STRIDE + 10]; } public function clearVisible() { visibleCount = 0; if (visibleList.length > 0) visibleList.resize(0); } public function addVisible(objectIndex: Int) { visibleList.push(objectIndex); visibleCount++; } }