Object Occlusion Culling
This commit is contained in:
373
leenkx/Sources/iron/culling/CullData.hx
Normal file
373
leenkx/Sources/iron/culling/CullData.hx
Normal file
@ -0,0 +1,373 @@
|
||||
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<MeshObject> = [];
|
||||
public var slotsDirty: Bool = true;
|
||||
public var slotsRebuilt: Bool = false;
|
||||
|
||||
public var visibleList: Array<Int>;
|
||||
public var visibleCount: Int = 0;
|
||||
|
||||
var staticFlagsCache: haxe.ds.IntMap<Int> = new haxe.ds.IntMap<Int>();
|
||||
|
||||
var matTranspCache: haxe.ds.IntMap<Int> = new haxe.ds.IntMap<Int>();
|
||||
var matSnapshots: haxe.ds.IntMap<haxe.ds.Vector<iron.data.MaterialData>> = new haxe.ds.IntMap();
|
||||
|
||||
var prevUidMap: haxe.ds.IntMap<Int> = new haxe.ds.IntMap<Int>();
|
||||
|
||||
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<Int> = 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<iron.data.MaterialData>(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++;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user