Object Occlusion Culling
This commit is contained in:
20
leenkx/Sources/iron/culling/BVHNode.hx
Normal file
20
leenkx/Sources/iron/culling/BVHNode.hx
Normal file
@ -0,0 +1,20 @@
|
||||
package iron.culling;
|
||||
|
||||
class BVHNode {
|
||||
public var minX: Float = 0;
|
||||
public var minY: Float = 0;
|
||||
public var minZ: Float = 0;
|
||||
public var maxX: Float = 0;
|
||||
public var maxY: Float = 0;
|
||||
public var maxZ: Float = 0;
|
||||
public var centerX: Float = 0;
|
||||
public var centerY: Float = 0;
|
||||
public var centerZ: Float = 0;
|
||||
public var isLeaf: Bool = false;
|
||||
public var leftChild: Int = -1;
|
||||
public var rightChild: Int = -1;
|
||||
public var firstObject: Int = 0;
|
||||
public var objectCount: Int = 0;
|
||||
|
||||
public function new() {}
|
||||
}
|
||||
62
leenkx/Sources/iron/culling/ContributionCull.hx
Normal file
62
leenkx/Sources/iron/culling/ContributionCull.hx
Normal file
@ -0,0 +1,62 @@
|
||||
package iron.culling;
|
||||
|
||||
class ContributionCull {
|
||||
|
||||
public static var minPixelRadius: Float = 2.0;
|
||||
public static var enabled: Bool = false;
|
||||
|
||||
public static function passes(
|
||||
centerX: Float, centerY: Float, centerZ: Float,
|
||||
radius: Float,
|
||||
camX: Float, camY: Float, camZ: Float,
|
||||
focalLengthPx: Float
|
||||
): Bool {
|
||||
if (!enabled || minPixelRadius <= 0.0 || focalLengthPx <= 0.0) return true;
|
||||
|
||||
var dx = centerX - camX;
|
||||
var dy = centerY - camY;
|
||||
var dz = centerZ - camZ;
|
||||
var dist = Math.sqrt(dx * dx + dy * dy + dz * dz);
|
||||
|
||||
if (dist <= radius) return true;
|
||||
|
||||
var projectedRadiusPx = focalLengthPx * radius / dist;
|
||||
return projectedRadiusPx >= minPixelRadius;
|
||||
}
|
||||
|
||||
public static function passesNode(
|
||||
node: BVHNode,
|
||||
camX: Float, camY: Float, camZ: Float,
|
||||
focalLengthPx: Float
|
||||
): Bool {
|
||||
if (!enabled || minPixelRadius <= 0.0 || focalLengthPx <= 0.0) return true;
|
||||
|
||||
var cx = (node.minX + node.maxX) * 0.5;
|
||||
var cy = (node.minY + node.maxY) * 0.5;
|
||||
var cz = (node.minZ + node.maxZ) * 0.5;
|
||||
var ex = (node.maxX - node.minX) * 0.5;
|
||||
var ey = (node.maxY - node.minY) * 0.5;
|
||||
var ez = (node.maxZ - node.minZ) * 0.5;
|
||||
var radius = Math.sqrt(ex * ex + ey * ey + ez * ez);
|
||||
|
||||
if (camX >= node.minX && camX <= node.maxX &&
|
||||
camY >= node.minY && camY <= node.maxY &&
|
||||
camZ >= node.minZ && camZ <= node.maxZ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
var dx = cx - camX;
|
||||
var dy = cy - camY;
|
||||
var dz = cz - camZ;
|
||||
var dist = Math.sqrt(dx * dx + dy * dy + dz * dz);
|
||||
|
||||
if (dist <= radius) return true;
|
||||
|
||||
var projectedRadiusPx = focalLengthPx * radius / dist;
|
||||
return projectedRadiusPx >= minPixelRadius;
|
||||
}
|
||||
|
||||
public static function computeFocalLengthPx(fov: Float, screenHeight: Int): Float {
|
||||
return screenHeight / (2.0 * Math.tan(fov * 0.5));
|
||||
}
|
||||
}
|
||||
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++;
|
||||
}
|
||||
}
|
||||
773
leenkx/Sources/iron/culling/CullSystem.hx
Normal file
773
leenkx/Sources/iron/culling/CullSystem.hx
Normal file
@ -0,0 +1,773 @@
|
||||
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() {}
|
||||
}
|
||||
9
leenkx/Sources/iron/culling/CullTier.hx
Normal file
9
leenkx/Sources/iron/culling/CullTier.hx
Normal file
@ -0,0 +1,9 @@
|
||||
package iron.culling;
|
||||
|
||||
enum CullTier {
|
||||
CPUSingleThread;
|
||||
CPUMultiThread;
|
||||
GPUCompute;
|
||||
GPUDriven;
|
||||
MultiGPU;
|
||||
}
|
||||
171
leenkx/Sources/iron/culling/HiZPyramid.hx
Normal file
171
leenkx/Sources/iron/culling/HiZPyramid.hx
Normal file
@ -0,0 +1,171 @@
|
||||
package iron.culling;
|
||||
|
||||
class HiZPyramid {
|
||||
|
||||
public var width: Int;
|
||||
public var height: Int;
|
||||
public var mipLevels: Int;
|
||||
public var mipBuffers: Array<kha.arrays.Float32Array>;
|
||||
public var mipWidths: Array<Int>;
|
||||
public var mipHeights: Array<Int>;
|
||||
|
||||
public var depthEps: Float;
|
||||
|
||||
var mipShifts: Array<Int>;
|
||||
|
||||
public function new(width: Int, height: Int) {
|
||||
this.width = width;
|
||||
this.height = height;
|
||||
mipBuffers = [];
|
||||
mipWidths = [];
|
||||
mipHeights = [];
|
||||
depthEps = CullSystem.depthEps;
|
||||
|
||||
var w = width;
|
||||
var h = height;
|
||||
mipLevels = 0;
|
||||
while (w > 1 || h > 1) {
|
||||
mipWidths.push(w);
|
||||
mipHeights.push(h);
|
||||
mipBuffers.push(new kha.arrays.Float32Array(w * h));
|
||||
mipLevels++;
|
||||
w = (w + 1) >> 1;
|
||||
h = (h + 1) >> 1;
|
||||
}
|
||||
mipWidths.push(1);
|
||||
mipHeights.push(1);
|
||||
mipBuffers.push(new kha.arrays.Float32Array(1));
|
||||
mipLevels++;
|
||||
|
||||
mipShifts = [0, 1, 2, 3, 4, 5, 6];
|
||||
while (mipShifts.length < mipLevels) mipShifts.push(mipShifts.length);
|
||||
}
|
||||
|
||||
public function clear(farDepth: Float = 1e20) {
|
||||
var buf = mipBuffers[0];
|
||||
for (i in 0...buf.length) buf[i] = farDepth;
|
||||
}
|
||||
|
||||
public inline function writeDepth(x: Int, y: Int, depth: Float) {
|
||||
var idx = y * width + x;
|
||||
if (x >= 0 && x < width && y >= 0 && y < height) {
|
||||
if (depth < mipBuffers[0][idx]) {
|
||||
mipBuffers[0][idx] = depth;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function buildPyramid() {
|
||||
for (mip in 1...mipLevels) {
|
||||
var prev = mipBuffers[mip - 1];
|
||||
var cur = mipBuffers[mip];
|
||||
var pw = mipWidths[mip - 1];
|
||||
var ph = mipHeights[mip - 1];
|
||||
var cw = mipWidths[mip];
|
||||
var ch = mipHeights[mip];
|
||||
|
||||
for (y in 0...ch) {
|
||||
for (x in 0...cw) {
|
||||
var sx = x * 2;
|
||||
var sy = y * 2;
|
||||
var d00 = prev[sy * pw + sx];
|
||||
var d10 = (sx + 1 < pw) ? prev[sy * pw + sx + 1] : d00;
|
||||
var d01 = (sy + 1 < ph) ? prev[(sy + 1) * pw + sx] : d00;
|
||||
var d11 = (sx + 1 < pw && sy + 1 < ph) ? prev[(sy + 1) * pw + sx + 1] : d00;
|
||||
var maxD = d00;
|
||||
if (d10 > maxD) maxD = d10;
|
||||
if (d01 > maxD) maxD = d01;
|
||||
if (d11 > maxD) maxD = d11;
|
||||
cur[y * cw + x] = maxD;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public inline function sample(x: Int, y: Int, mip: Int): Float {
|
||||
var mw = mipWidths[mip];
|
||||
var mh = mipHeights[mip];
|
||||
if (x < 0) x = 0;
|
||||
if (y < 0) y = 0;
|
||||
if (x >= mw) x = mw - 1;
|
||||
if (y >= mh) y = mh - 1;
|
||||
return mipBuffers[mip][y * mw + x];
|
||||
}
|
||||
|
||||
public static function mipForSize(rectW: Float, rectH: Float, maxMip: Int): Int {
|
||||
var maxDim = Math.max(rectW, rectH);
|
||||
if (maxDim <= 2.0) return 0;
|
||||
var mip = Std.int(Math.floor(Math.log(maxDim) / Math.log(2))) - 1;
|
||||
if (mip < 0) mip = 0;
|
||||
if (mip > maxMip) mip = maxMip;
|
||||
return mip;
|
||||
}
|
||||
|
||||
public function isOccluded(
|
||||
minX: Float, minY: Float, minZ: Float,
|
||||
maxX: Float, maxY: Float, maxZ: Float,
|
||||
vp: iron.math.Mat4,
|
||||
_screenW: Int, _screenH: Int
|
||||
): Bool {
|
||||
var minSx = 1e20, minSy = 1e20;
|
||||
var maxSx = -1e20, maxSy = -1e20;
|
||||
var minDepth = 1e20;
|
||||
|
||||
var m00 = vp._00, m10 = vp._10, m20 = vp._20, m30 = vp._30;
|
||||
var m01 = vp._01, m11 = vp._11, m21 = vp._21, m31 = vp._31;
|
||||
var m02 = vp._02, m12 = vp._12, m22 = vp._22, m32 = vp._32;
|
||||
var m03 = vp._03, m13 = vp._13, m23 = vp._23, m33 = vp._33;
|
||||
|
||||
for (c in 0...8) {
|
||||
var x = (c & 1) != 0 ? maxX : minX;
|
||||
var y = (c & 2) != 0 ? maxY : minY;
|
||||
var z = (c & 4) != 0 ? maxZ : minZ;
|
||||
var w = m03 * x + m13 * y + m23 * z + m33;
|
||||
if (w <= 0) return false;
|
||||
var invW = SimdOps.reciprocal(w);
|
||||
var ndcZ = (m02 * x + m12 * y + m22 * z + m32) * invW;
|
||||
if (ndcZ < -1.0) ndcZ = -1.0; else if (ndcZ > 1.0) ndcZ = 1.0;
|
||||
var sx = ((m00 * x + m10 * y + m20 * z + m30) * invW * 0.5 + 0.5) * width;
|
||||
var sy = ((m01 * x + m11 * y + m21 * z + m31) * invW * 0.5 + 0.5) * height;
|
||||
if (sx < minSx) minSx = sx;
|
||||
if (sy < minSy) minSy = sy;
|
||||
if (sx > maxSx) maxSx = sx;
|
||||
if (sy > maxSy) maxSy = sy;
|
||||
if (ndcZ < minDepth) minDepth = ndcZ;
|
||||
}
|
||||
|
||||
var ix0 = Std.int(Math.max(0, minSx));
|
||||
var iy0 = Std.int(Math.max(0, minSy));
|
||||
var ix1 = Std.int(Math.min(width - 1, maxSx));
|
||||
var iy1 = Std.int(Math.min(height - 1, maxSy));
|
||||
|
||||
if (ix0 > ix1 || iy0 > iy1) return false;
|
||||
|
||||
if (ix0 > 0) ix0--;
|
||||
if (iy0 > 0) iy0--;
|
||||
if (ix1 < width - 1) ix1++;
|
||||
if (iy1 < height - 1) iy1++;
|
||||
|
||||
var rectW = ix1 - ix0 + 1;
|
||||
var rectH = iy1 - iy0 + 1;
|
||||
var mip = mipForSize(rectW, rectH, mipLevels - 1);
|
||||
|
||||
var mw = mipWidths[mip];
|
||||
var mh = mipHeights[mip];
|
||||
var shift = mipShifts[mip];
|
||||
var tx0 = Std.int(Math.min(mw - 1, ix0 >> shift));
|
||||
var ty0 = Std.int(Math.min(mh - 1, iy0 >> shift));
|
||||
var tx1 = Std.int(Math.min(mw - 1, ix1 >> shift));
|
||||
var ty1 = Std.int(Math.min(mh - 1, iy1 >> shift));
|
||||
|
||||
var buf = mipBuffers[mip];
|
||||
var limit = minDepth - depthEps;
|
||||
for (ty in ty0...ty1 + 1) {
|
||||
var rowOff = ty * mw;
|
||||
for (tx in tx0...tx1 + 1) {
|
||||
if (buf[rowOff + tx] >= limit) return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
60
leenkx/Sources/iron/culling/JobSystem.hx
Normal file
60
leenkx/Sources/iron/culling/JobSystem.hx
Normal file
@ -0,0 +1,60 @@
|
||||
package iron.culling;
|
||||
|
||||
#if (target.threaded)
|
||||
|
||||
class JobSystem {
|
||||
|
||||
public static var numWorkers(default, null): Int = 0;
|
||||
|
||||
static var jobQueue: sys.thread.Deque<Void->Void> = null;
|
||||
static var doneLock: sys.thread.Lock = null;
|
||||
static var jobError: Dynamic = null;
|
||||
|
||||
public static function init(workers: Int = 3) {
|
||||
if (jobQueue != null) return;
|
||||
numWorkers = workers < 1 ? 1 : workers;
|
||||
jobQueue = new sys.thread.Deque();
|
||||
doneLock = new sys.thread.Lock();
|
||||
for (_ in 0...numWorkers) {
|
||||
sys.thread.Thread.create(workerLoop);
|
||||
}
|
||||
}
|
||||
|
||||
static function workerLoop() {
|
||||
while (true) {
|
||||
var job = jobQueue.pop(true);
|
||||
if (job == null) return;
|
||||
try {
|
||||
job();
|
||||
} catch (e: Dynamic) {
|
||||
jobError = e;
|
||||
}
|
||||
doneLock.release();
|
||||
}
|
||||
}
|
||||
|
||||
public static function parallelFor(count: Int, fn: Int -> Int -> Void) {
|
||||
init();
|
||||
var chunks = numWorkers + 1;
|
||||
var chunkSize = Std.int((count + chunks - 1) / chunks);
|
||||
if (chunkSize < 1) chunkSize = 1;
|
||||
var jobs = 0;
|
||||
var start = chunkSize; // Chunk 0 runs on the calling thread
|
||||
while (start < count) {
|
||||
var s = start;
|
||||
var e = start + chunkSize > count ? count : start + chunkSize;
|
||||
jobQueue.add(function() fn(s, e));
|
||||
jobs++;
|
||||
start = e;
|
||||
}
|
||||
fn(0, chunkSize > count ? count : chunkSize);
|
||||
for (_ in 0...jobs) doneLock.wait();
|
||||
if (jobError != null) {
|
||||
var e = jobError;
|
||||
jobError = null;
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#end
|
||||
376
leenkx/Sources/iron/culling/ObjectBVH.hx
Normal file
376
leenkx/Sources/iron/culling/ObjectBVH.hx
Normal file
@ -0,0 +1,376 @@
|
||||
package iron.culling;
|
||||
|
||||
class ObjectBVH {
|
||||
|
||||
static inline var LEAF_THRESHOLD = 4;
|
||||
static inline var NUM_BINS = 12;
|
||||
static inline var TRAVERSAL_COST = 1.0;
|
||||
static inline var INTERSECT_COST = 1.0;
|
||||
|
||||
public var nodes: Array<BVHNode>;
|
||||
public var objectIndices: Array<Int>;
|
||||
public var root: Int = -1;
|
||||
public var objectCount: Int = 0;
|
||||
|
||||
var stack: Array<Int> = [];
|
||||
|
||||
static var binCounts: Array<Int> = [for (i in 0...NUM_BINS) 0];
|
||||
static var binMinX: Array<Float> = [for (i in 0...NUM_BINS) 0];
|
||||
static var binMinY: Array<Float> = [for (i in 0...NUM_BINS) 0];
|
||||
static var binMinZ: Array<Float> = [for (i in 0...NUM_BINS) 0];
|
||||
static var binMaxX: Array<Float> = [for (i in 0...NUM_BINS) 0];
|
||||
static var binMaxY: Array<Float> = [for (i in 0...NUM_BINS) 0];
|
||||
static var binMaxZ: Array<Float> = [for (i in 0...NUM_BINS) 0];
|
||||
|
||||
public function new() {
|
||||
nodes = [];
|
||||
objectIndices = [];
|
||||
}
|
||||
|
||||
public function build(cullData: CullData) {
|
||||
objectCount = cullData.count;
|
||||
nodes = [];
|
||||
objectIndices = [];
|
||||
|
||||
if (objectCount == 0) {
|
||||
root = -1;
|
||||
return;
|
||||
}
|
||||
|
||||
var indices = [for (i in 0...objectCount) i];
|
||||
root = buildRecursive(indices, cullData);
|
||||
}
|
||||
|
||||
static inline function surfaceArea(minX: Float, minY: Float, minZ: Float,
|
||||
maxX: Float, maxY: Float, maxZ: Float): Float {
|
||||
var wx = maxX - minX;
|
||||
var wy = maxY - minY;
|
||||
var wz = maxZ - minZ;
|
||||
return 2.0 * (wx * wy + wx * wz + wy * wz);
|
||||
}
|
||||
|
||||
function buildRecursive(indices: Array<Int>, cullData: CullData): Int {
|
||||
var nodeIdx = nodes.length;
|
||||
var node = new BVHNode();
|
||||
nodes.push(node);
|
||||
|
||||
var minX = 1e20, minY = 1e20, minZ = 1e20;
|
||||
var maxX = -1e20, maxY = -1e20, maxZ = -1e20;
|
||||
var centMinX = 1e20, centMinY = 1e20, centMinZ = 1e20;
|
||||
var centMaxX = -1e20, centMaxY = -1e20, centMaxZ = -1e20;
|
||||
var cx = 0.0, cy = 0.0, cz = 0.0;
|
||||
|
||||
for (idx in indices) {
|
||||
var x = cullData.getCenterX(idx);
|
||||
var y = cullData.getCenterY(idx);
|
||||
var z = cullData.getCenterZ(idx);
|
||||
if (cullData.getMinX(idx) < minX) minX = cullData.getMinX(idx);
|
||||
if (cullData.getMinY(idx) < minY) minY = cullData.getMinY(idx);
|
||||
if (cullData.getMinZ(idx) < minZ) minZ = cullData.getMinZ(idx);
|
||||
if (cullData.getMaxX(idx) > maxX) maxX = cullData.getMaxX(idx);
|
||||
if (cullData.getMaxY(idx) > maxY) maxY = cullData.getMaxY(idx);
|
||||
if (cullData.getMaxZ(idx) > maxZ) maxZ = cullData.getMaxZ(idx);
|
||||
if (x < centMinX) centMinX = x;
|
||||
if (y < centMinY) centMinY = y;
|
||||
if (z < centMinZ) centMinZ = z;
|
||||
if (x > centMaxX) centMaxX = x;
|
||||
if (y > centMaxY) centMaxY = y;
|
||||
if (z > centMaxZ) centMaxZ = z;
|
||||
cx += x;
|
||||
cy += y;
|
||||
cz += z;
|
||||
}
|
||||
|
||||
var n = indices.length;
|
||||
cx /= n;
|
||||
cy /= n;
|
||||
cz /= n;
|
||||
|
||||
node.minX = minX; node.minY = minY; node.minZ = minZ;
|
||||
node.maxX = maxX; node.maxY = maxY; node.maxZ = maxZ;
|
||||
node.centerX = cx;
|
||||
node.centerY = cy;
|
||||
node.centerZ = cz;
|
||||
|
||||
if (n <= LEAF_THRESHOLD) {
|
||||
node.isLeaf = true;
|
||||
node.firstObject = objectIndices.length;
|
||||
for (idx in indices) objectIndices.push(idx);
|
||||
node.objectCount = n;
|
||||
return nodeIdx;
|
||||
}
|
||||
|
||||
var parentSA = surfaceArea(minX, minY, minZ, maxX, maxY, maxZ);
|
||||
var leafCost = INTERSECT_COST * n;
|
||||
|
||||
var bestCost = 1e20;
|
||||
var bestAxis = -1;
|
||||
var bestSplitBin = -1;
|
||||
|
||||
var centBounds = [
|
||||
[centMinX, centMaxX],
|
||||
[centMinY, centMaxY],
|
||||
[centMinZ, centMaxZ]
|
||||
];
|
||||
|
||||
for (axis in 0...3) {
|
||||
var lo = centBounds[axis][0];
|
||||
var hi = centBounds[axis][1];
|
||||
var range = hi - lo;
|
||||
if (range < 1e-8) continue;
|
||||
|
||||
var invRange = NUM_BINS / range;
|
||||
|
||||
for (b in 0...NUM_BINS) {
|
||||
binCounts[b] = 0;
|
||||
binMinX[b] = 1e20; binMinY[b] = 1e20; binMinZ[b] = 1e20;
|
||||
binMaxX[b] = -1e20; binMaxY[b] = -1e20; binMaxZ[b] = -1e20;
|
||||
}
|
||||
|
||||
for (idx in indices) {
|
||||
var cv = axis == 0 ? cullData.getCenterX(idx) :
|
||||
axis == 1 ? cullData.getCenterY(idx) :
|
||||
cullData.getCenterZ(idx);
|
||||
var b = Std.int((cv - lo) * invRange);
|
||||
if (b < 0) b = 0;
|
||||
if (b >= NUM_BINS) b = NUM_BINS - 1;
|
||||
binCounts[b]++;
|
||||
if (cullData.getMinX(idx) < binMinX[b]) binMinX[b] = cullData.getMinX(idx);
|
||||
if (cullData.getMinY(idx) < binMinY[b]) binMinY[b] = cullData.getMinY(idx);
|
||||
if (cullData.getMinZ(idx) < binMinZ[b]) binMinZ[b] = cullData.getMinZ(idx);
|
||||
if (cullData.getMaxX(idx) > binMaxX[b]) binMaxX[b] = cullData.getMaxX(idx);
|
||||
if (cullData.getMaxY(idx) > binMaxY[b]) binMaxY[b] = cullData.getMaxY(idx);
|
||||
if (cullData.getMaxZ(idx) > binMaxZ[b]) binMaxZ[b] = cullData.getMaxZ(idx);
|
||||
}
|
||||
|
||||
var rightCount = [for (b in 0...NUM_BINS) 0];
|
||||
var rightMinX = [for (b in 0...NUM_BINS) 0.0];
|
||||
var rightMinY = [for (b in 0...NUM_BINS) 0.0];
|
||||
var rightMinZ = [for (b in 0...NUM_BINS) 0.0];
|
||||
var rightMaxX = [for (b in 0...NUM_BINS) 0.0];
|
||||
var rightMaxY = [for (b in 0...NUM_BINS) 0.0];
|
||||
var rightMaxZ = [for (b in 0...NUM_BINS) 0.0];
|
||||
|
||||
var cumCount = 0;
|
||||
var cumMinX = 1e20, cumMinY = 1e20, cumMinZ = 1e20;
|
||||
var cumMaxX = -1e20, cumMaxY = -1e20, cumMaxZ = -1e20;
|
||||
|
||||
for (b in 0...NUM_BINS) {
|
||||
var bi = NUM_BINS - 1 - b;
|
||||
cumCount += binCounts[bi];
|
||||
if (binMinX[bi] < cumMinX) cumMinX = binMinX[bi];
|
||||
if (binMinY[bi] < cumMinY) cumMinY = binMinY[bi];
|
||||
if (binMinZ[bi] < cumMinZ) cumMinZ = binMinZ[bi];
|
||||
if (binMaxX[bi] > cumMaxX) cumMaxX = binMaxX[bi];
|
||||
if (binMaxY[bi] > cumMaxY) cumMaxY = binMaxY[bi];
|
||||
if (binMaxZ[bi] > cumMaxZ) cumMaxZ = binMaxZ[bi];
|
||||
rightCount[bi] = cumCount;
|
||||
rightMinX[bi] = cumMinX; rightMinY[bi] = cumMinY; rightMinZ[bi] = cumMinZ;
|
||||
rightMaxX[bi] = cumMaxX; rightMaxY[bi] = cumMaxY; rightMaxZ[bi] = cumMaxZ;
|
||||
}
|
||||
|
||||
var leftCount = 0;
|
||||
var leftMinX = 1e20, leftMinY = 1e20, leftMinZ = 1e20;
|
||||
var leftMaxX = -1e20, leftMaxY = -1e20, leftMaxZ = -1e20;
|
||||
|
||||
for (b in 0...NUM_BINS - 1) {
|
||||
if (binCounts[b] == 0) continue;
|
||||
leftCount += binCounts[b];
|
||||
if (binMinX[b] < leftMinX) leftMinX = binMinX[b];
|
||||
if (binMinY[b] < leftMinY) leftMinY = binMinY[b];
|
||||
if (binMinZ[b] < leftMinZ) leftMinZ = binMinZ[b];
|
||||
if (binMaxX[b] > leftMaxX) leftMaxX = binMaxX[b];
|
||||
if (binMaxY[b] > leftMaxY) leftMaxY = binMaxY[b];
|
||||
if (binMaxZ[b] > leftMaxZ) leftMaxZ = binMaxZ[b];
|
||||
|
||||
var rc = rightCount[b + 1];
|
||||
if (rc == 0) continue;
|
||||
|
||||
var leftSA = surfaceArea(leftMinX, leftMinY, leftMinZ, leftMaxX, leftMaxY, leftMaxZ);
|
||||
var rightSA = surfaceArea(rightMinX[b + 1], rightMinY[b + 1], rightMinZ[b + 1],
|
||||
rightMaxX[b + 1], rightMaxY[b + 1], rightMaxZ[b + 1]);
|
||||
var cost = TRAVERSAL_COST + (leftSA * leftCount + rightSA * rc) / parentSA;
|
||||
|
||||
if (cost < bestCost) {
|
||||
bestCost = cost;
|
||||
bestAxis = axis;
|
||||
bestSplitBin = b;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (bestAxis < 0 || bestCost >= leafCost) {
|
||||
node.isLeaf = true;
|
||||
node.firstObject = objectIndices.length;
|
||||
for (idx in indices) objectIndices.push(idx);
|
||||
node.objectCount = n;
|
||||
return nodeIdx;
|
||||
}
|
||||
|
||||
var lo = centBounds[bestAxis][0];
|
||||
var hi = centBounds[bestAxis][1];
|
||||
var invRange = NUM_BINS / (hi - lo);
|
||||
var splitBin = bestSplitBin;
|
||||
|
||||
var left = [];
|
||||
var right = [];
|
||||
for (idx in indices) {
|
||||
var cv = bestAxis == 0 ? cullData.getCenterX(idx) :
|
||||
bestAxis == 1 ? cullData.getCenterY(idx) :
|
||||
cullData.getCenterZ(idx);
|
||||
var b = Std.int((cv - lo) * invRange);
|
||||
if (b < 0) b = 0;
|
||||
if (b >= NUM_BINS) b = NUM_BINS - 1;
|
||||
if (b <= splitBin) left.push(idx);
|
||||
else right.push(idx);
|
||||
}
|
||||
|
||||
if (left.length == 0 || right.length == 0) {
|
||||
indices.sort(function(a, b): Int {
|
||||
var av = bestAxis == 0 ? cullData.getCenterX(a) :
|
||||
bestAxis == 1 ? cullData.getCenterY(a) :
|
||||
cullData.getCenterZ(a);
|
||||
var bv = bestAxis == 0 ? cullData.getCenterX(b) :
|
||||
bestAxis == 1 ? cullData.getCenterY(b) :
|
||||
cullData.getCenterZ(b);
|
||||
if (av < bv) return -1;
|
||||
if (av > bv) return 1;
|
||||
return 0;
|
||||
});
|
||||
var mid = n >> 1;
|
||||
left = indices.slice(0, mid);
|
||||
right = indices.slice(mid);
|
||||
}
|
||||
|
||||
node.isLeaf = false;
|
||||
node.leftChild = buildRecursive(left, cullData);
|
||||
node.rightChild = buildRecursive(right, cullData);
|
||||
|
||||
return nodeIdx;
|
||||
}
|
||||
|
||||
|
||||
public function refit(cullData: CullData) {
|
||||
var i = nodes.length - 1;
|
||||
while (i >= 0) {
|
||||
var node = nodes[i];
|
||||
if (node.isLeaf) {
|
||||
var minX = 1e20, minY = 1e20, minZ = 1e20;
|
||||
var maxX = -1e20, maxY = -1e20, maxZ = -1e20;
|
||||
for (j in 0...node.objectCount) {
|
||||
var idx = objectIndices[node.firstObject + j];
|
||||
if (cullData.getMinX(idx) < minX) minX = cullData.getMinX(idx);
|
||||
if (cullData.getMinY(idx) < minY) minY = cullData.getMinY(idx);
|
||||
if (cullData.getMinZ(idx) < minZ) minZ = cullData.getMinZ(idx);
|
||||
if (cullData.getMaxX(idx) > maxX) maxX = cullData.getMaxX(idx);
|
||||
if (cullData.getMaxY(idx) > maxY) maxY = cullData.getMaxY(idx);
|
||||
if (cullData.getMaxZ(idx) > maxZ) maxZ = cullData.getMaxZ(idx);
|
||||
}
|
||||
node.minX = minX; node.minY = minY; node.minZ = minZ;
|
||||
node.maxX = maxX; node.maxY = maxY; node.maxZ = maxZ;
|
||||
} else {
|
||||
var l = nodes[node.leftChild];
|
||||
var r = nodes[node.rightChild];
|
||||
node.minX = l.minX < r.minX ? l.minX : r.minX;
|
||||
node.minY = l.minY < r.minY ? l.minY : r.minY;
|
||||
node.minZ = l.minZ < r.minZ ? l.minZ : r.minZ;
|
||||
node.maxX = l.maxX > r.maxX ? l.maxX : r.maxX;
|
||||
node.maxY = l.maxY > r.maxY ? l.maxY : r.maxY;
|
||||
node.maxZ = l.maxZ > r.maxZ ? l.maxZ : r.maxZ;
|
||||
}
|
||||
node.centerX = (node.minX + node.maxX) * 0.5;
|
||||
node.centerY = (node.minY + node.maxY) * 0.5;
|
||||
node.centerZ = (node.minZ + node.maxZ) * 0.5;
|
||||
i--;
|
||||
}
|
||||
}
|
||||
|
||||
public function forEachLeafInSubtree(node: BVHNode, cb: (Int) -> Void) {
|
||||
if (node.isLeaf) {
|
||||
for (i in 0...node.objectCount) {
|
||||
cb(objectIndices[node.firstObject + i]);
|
||||
}
|
||||
} else {
|
||||
forEachLeafInSubtree(nodes[node.leftChild], cb);
|
||||
forEachLeafInSubtree(nodes[node.rightChild], cb);
|
||||
}
|
||||
}
|
||||
|
||||
public function traverseFrontToBack(
|
||||
camX: Float, camY: Float, camZ: Float,
|
||||
shouldSkip: (BVHNode) -> Bool,
|
||||
onLeaf: (Int, Int) -> Void
|
||||
) {
|
||||
if (root < 0) return;
|
||||
|
||||
stack.resize(0);
|
||||
stack.push(root);
|
||||
|
||||
while (stack.length > 0) {
|
||||
var nodeIdx = stack.pop();
|
||||
var node = nodes[nodeIdx];
|
||||
|
||||
if (shouldSkip(node)) continue;
|
||||
|
||||
if (node.isLeaf) {
|
||||
var start = node.firstObject;
|
||||
for (i in 0...node.objectCount) {
|
||||
onLeaf(objectIndices[start + i], i);
|
||||
}
|
||||
} else {
|
||||
var leftNode = nodes[node.leftChild];
|
||||
var rightNode = nodes[node.rightChild];
|
||||
var leftDist = distSq(leftNode.centerX, leftNode.centerY, leftNode.centerZ, camX, camY, camZ);
|
||||
var rightDist = distSq(rightNode.centerX, rightNode.centerY, rightNode.centerZ, camX, camY, camZ);
|
||||
|
||||
if (leftDist > rightDist) {
|
||||
stack.push(node.leftChild);
|
||||
stack.push(node.rightChild);
|
||||
} else {
|
||||
stack.push(node.rightChild);
|
||||
stack.push(node.leftChild);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
inline function distSq(ax: Float, ay: Float, az: Float, bx: Float, by: Float, bz: Float): Float {
|
||||
var dx = ax - bx;
|
||||
var dy = ay - by;
|
||||
var dz = az - bz;
|
||||
return dx * dx + dy * dy + dz * dz;
|
||||
}
|
||||
|
||||
public static function aabbInFrustumRaw(
|
||||
minX: Float, minY: Float, minZ: Float,
|
||||
maxX: Float, maxY: Float, maxZ: Float,
|
||||
planes: Array<iron.object.FrustumPlane>
|
||||
): Bool {
|
||||
for (i in 0...planes.length) {
|
||||
var plane = planes[i];
|
||||
var px = plane.normal.x >= 0 ? maxX : minX;
|
||||
var py = plane.normal.y >= 0 ? maxY : minY;
|
||||
var pz = plane.normal.z >= 0 ? maxZ : minZ;
|
||||
if (SimdOps.dot3(px, py, pz, plane.normal.x, plane.normal.y, plane.normal.z) + plane.constant < 0) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public static function aabbInFrustum(
|
||||
node: BVHNode,
|
||||
planes: Array<iron.object.FrustumPlane>
|
||||
): Bool {
|
||||
for (i in 0...planes.length) {
|
||||
var plane = planes[i];
|
||||
var px = plane.normal.x >= 0 ? node.maxX : node.minX;
|
||||
var py = plane.normal.y >= 0 ? node.maxY : node.minY;
|
||||
var pz = plane.normal.z >= 0 ? node.maxZ : node.minZ;
|
||||
if (SimdOps.dot3(px, py, pz, plane.normal.x, plane.normal.y, plane.normal.z) + plane.constant < 0) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
71
leenkx/Sources/iron/culling/OccluderProxy.hx
Normal file
71
leenkx/Sources/iron/culling/OccluderProxy.hx
Normal file
@ -0,0 +1,71 @@
|
||||
package iron.culling;
|
||||
|
||||
class OccluderProxy {
|
||||
|
||||
public var positions: kha.arrays.Float32Array;
|
||||
public var indices: kha.arrays.Uint32Array;
|
||||
public var triangleCount: Int;
|
||||
public var meshUid: Int;
|
||||
|
||||
var worldPositions: kha.arrays.Float32Array;
|
||||
var mvp: iron.math.Mat4;
|
||||
|
||||
public function new() {
|
||||
indices = new kha.arrays.Uint32Array(0);
|
||||
triangleCount = 0;
|
||||
}
|
||||
|
||||
public function setData(pos: kha.arrays.Float32Array, idx: kha.arrays.Uint32Array, uid: Int) {
|
||||
#if js
|
||||
if (!Std.isOfType(pos, js.lib.DataView)) {
|
||||
var srcF: Array<Float> = cast pos;
|
||||
var dstF = new kha.arrays.Float32Array(srcF.length);
|
||||
for (i in 0...srcF.length) dstF[i] = srcF[i];
|
||||
pos = dstF;
|
||||
}
|
||||
if (!Std.isOfType(idx, js.lib.DataView)) {
|
||||
var srcI: Array<Int> = cast idx;
|
||||
var dstI = new kha.arrays.Uint32Array(srcI.length);
|
||||
for (i in 0...srcI.length) dstI[i] = srcI[i];
|
||||
idx = dstI;
|
||||
}
|
||||
#end
|
||||
positions = pos;
|
||||
indices = idx;
|
||||
triangleCount = Std.int(idx.length / 3);
|
||||
meshUid = uid;
|
||||
}
|
||||
|
||||
public function rasterize(
|
||||
rasterizer: SoftwareOcclusionRasterizer,
|
||||
world: iron.math.Mat4,
|
||||
vp: iron.math.Mat4,
|
||||
screenW: Int, screenH: Int
|
||||
) {
|
||||
if (positions == null || indices == null || triangleCount == 0) return;
|
||||
|
||||
var vertCount = Std.int(positions.length / 3);
|
||||
|
||||
var clipSize = vertCount * 4;
|
||||
if (worldPositions == null || worldPositions.length < clipSize) {
|
||||
worldPositions = new kha.arrays.Float32Array(clipSize);
|
||||
}
|
||||
|
||||
if (mvp == null) mvp = iron.math.Mat4.identity();
|
||||
mvp.multmats(vp, world);
|
||||
|
||||
for (i in 0...vertCount) {
|
||||
var x = positions[i * 3];
|
||||
var y = positions[i * 3 + 1];
|
||||
var z = positions[i * 3 + 2];
|
||||
var off = i * 4;
|
||||
worldPositions[off] = mvp._00 * x + mvp._10 * y + mvp._20 * z + mvp._30;
|
||||
worldPositions[off + 1] = mvp._01 * x + mvp._11 * y + mvp._21 * z + mvp._31;
|
||||
worldPositions[off + 2] = mvp._02 * x + mvp._12 * y + mvp._22 * z + mvp._32;
|
||||
worldPositions[off + 3] = mvp._03 * x + mvp._13 * y + mvp._23 * z + mvp._33;
|
||||
}
|
||||
|
||||
rasterizer.rasterizeProxyTransformed(worldPositions, indices, screenW, screenH);
|
||||
}
|
||||
|
||||
}
|
||||
83
leenkx/Sources/iron/culling/SimdOps.hx
Normal file
83
leenkx/Sources/iron/culling/SimdOps.hx
Normal file
@ -0,0 +1,83 @@
|
||||
package iron.culling;
|
||||
|
||||
#if cpp
|
||||
@:include("smmintrin.h")
|
||||
@:include("emmintrin.h")
|
||||
@:include("xmmintrin.h")
|
||||
#end
|
||||
class SimdOps {
|
||||
|
||||
public static inline function dot4(
|
||||
a0: Float, a1: Float, a2: Float, a3: Float,
|
||||
b0: Float, b1: Float, b2: Float, b3: Float
|
||||
): Float {
|
||||
#if cpp
|
||||
return untyped __cpp__("_mm_cvtss_f32(_mm_dp_ps(_mm_setr_ps({0}, {1}, {2}, {3}), _mm_setr_ps({4}, {5}, {6}, {7}), 0xF1))",
|
||||
a0, a1, a2, a3, b0, b1, b2, b3);
|
||||
#else
|
||||
return a0 * b0 + a1 * b1 + a2 * b2 + a3 * b3;
|
||||
#end
|
||||
}
|
||||
|
||||
public static inline function dot3(
|
||||
a0: Float, a1: Float, a2: Float,
|
||||
b0: Float, b1: Float, b2: Float
|
||||
): Float {
|
||||
#if cpp
|
||||
return untyped __cpp__("_mm_cvtss_f32(_mm_dp_ps(_mm_setr_ps({0}, {1}, {2}, 0.0f), _mm_setr_ps({3}, {4}, {5}, 0.0f), 0x71))",
|
||||
a0, a1, a2, b0, b1, b2);
|
||||
#else
|
||||
return a0 * b0 + a1 * b1 + a2 * b2;
|
||||
#end
|
||||
}
|
||||
|
||||
public static inline function reciprocal(x: Float): Float {
|
||||
#if cpp
|
||||
return untyped __cpp__("({ auto _r = _mm_rcp_ss(_mm_set_ss({0})); _mm_cvtss_f32(_mm_add_ss(_mm_mul_ss(_r, _mm_sub_ss(_mm_set_ss(2.0f), _mm_mul_ss(_r, _mm_set_ss({0})))), _r)); })", x);
|
||||
#else
|
||||
return 1.0 / x;
|
||||
#end
|
||||
}
|
||||
|
||||
public static inline function max4f(
|
||||
a: Float, b: Float, c: Float, d: Float
|
||||
): Float {
|
||||
#if cpp
|
||||
return untyped __cpp__("_mm_cvtss_f32(_mm_max_ss(_mm_max_ss(_mm_set_ss({0}), _mm_set_ss({1})), _mm_max_ss(_mm_set_ss({2}), _mm_set_ss({3}))))",
|
||||
a, b, c, d);
|
||||
#else
|
||||
var m = a;
|
||||
if (b > m) m = b;
|
||||
if (c > m) m = c;
|
||||
if (d > m) m = d;
|
||||
return m;
|
||||
#end
|
||||
}
|
||||
|
||||
public static inline function min4f(
|
||||
a: Float, b: Float, c: Float, d: Float
|
||||
): Float {
|
||||
#if cpp
|
||||
return untyped __cpp__("_mm_cvtss_f32(_mm_min_ss(_mm_min_ss(_mm_set_ss({0}), _mm_set_ss({1})), _mm_min_ss(_mm_set_ss({2}), _mm_set_ss({3}))))",
|
||||
a, b, c, d);
|
||||
#else
|
||||
var m = a;
|
||||
if (b < m) m = b;
|
||||
if (c < m) m = c;
|
||||
if (d < m) m = d;
|
||||
return m;
|
||||
#end
|
||||
}
|
||||
|
||||
public static inline function frustumSphereTest6(
|
||||
planeData: kha.arrays.Float32Array,
|
||||
cx: Float, cy: Float, cz: Float, radius: Float
|
||||
): Bool {
|
||||
for (i in 0...6) {
|
||||
var base = i * 4;
|
||||
var dist = dot3(cx, cy, cz, planeData[base], planeData[base + 1], planeData[base + 2]) + planeData[base + 3];
|
||||
if (dist + radius < 0) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
507
leenkx/Sources/iron/culling/SoftwareOcclusionRasterizer.hx
Normal file
507
leenkx/Sources/iron/culling/SoftwareOcclusionRasterizer.hx
Normal file
@ -0,0 +1,507 @@
|
||||
package iron.culling;
|
||||
|
||||
class SoftwareOcclusionRasterizer {
|
||||
|
||||
public var hiz: HiZPyramid;
|
||||
public var width: Int;
|
||||
public var height: Int;
|
||||
|
||||
public var nearClip: Float = 0.0001;
|
||||
|
||||
static inline var TILE_SIZE = 32;
|
||||
|
||||
public var trianglesRasterized: Int = 0;
|
||||
|
||||
public var collectMode = false;
|
||||
var screenTris: Array<Float> = [];
|
||||
var screenTriCount = 0;
|
||||
|
||||
static final BOX_FACES = [
|
||||
[0, 1, 3, 2], // -Z
|
||||
[4, 5, 7, 6], // +Z
|
||||
[0, 1, 5, 4], // -Y
|
||||
[2, 3, 7, 6], // +Y
|
||||
[0, 2, 6, 4], // -X
|
||||
[1, 3, 7, 5], // +X
|
||||
];
|
||||
|
||||
var inX: Array<Float> = [0,0,0];
|
||||
var inY: Array<Float> = [0,0,0];
|
||||
var inZ: Array<Float> = [0,0,0];
|
||||
var inW: Array<Float> = [0,0,0];
|
||||
var outX: Array<Float> = [0,0,0,0];
|
||||
var outY: Array<Float> = [0,0,0,0];
|
||||
var outZ: Array<Float> = [0,0,0,0];
|
||||
var outW: Array<Float> = [0,0,0,0];
|
||||
var projSx: Array<Float> = [0,0,0,0];
|
||||
var projSy: Array<Float> = [0,0,0,0];
|
||||
var projSz: Array<Float> = [0,0,0,0];
|
||||
|
||||
var boxClipX: Array<Float> = [0,0,0,0,0,0,0,0];
|
||||
var boxClipY: Array<Float> = [0,0,0,0,0,0,0,0];
|
||||
var boxClipZ: Array<Float> = [0,0,0,0,0,0,0,0];
|
||||
var boxClipW: Array<Float> = [0,0,0,0,0,0,0,0];
|
||||
|
||||
public function new(width: Int = 256, height: Int = 128) {
|
||||
this.width = width;
|
||||
this.height = height;
|
||||
hiz = new HiZPyramid(width, height);
|
||||
}
|
||||
|
||||
public function clear() {
|
||||
hiz.clear(1e20);
|
||||
trianglesRasterized = 0;
|
||||
}
|
||||
|
||||
public function rasterizeTriangle(
|
||||
v0x: Float, v0y: Float, v0z: Float,
|
||||
v1x: Float, v1y: Float, v1z: Float,
|
||||
v2x: Float, v2y: Float, v2z: Float
|
||||
) {
|
||||
var e0a = v1y - v0y; var e0b = v0x - v1x;
|
||||
var e1a = v2y - v1y; var e1b = v1x - v2x;
|
||||
var e2a = v0y - v2y; var e2b = v2x - v0x;
|
||||
var e0c = -e0a * v0x - e0b * v0y;
|
||||
var e1c = -e1a * v1x - e1b * v1y;
|
||||
var e2c = -e2a * v2x - e2b * v2y;
|
||||
|
||||
var area = e0a * v2x + e0b * v2y + e0c;
|
||||
if (area < 0) {
|
||||
area = -area;
|
||||
e0a = -e0a; e0b = -e0b; e0c = -e0c;
|
||||
e1a = -e1a; e1b = -e1b; e1c = -e1c;
|
||||
e2a = -e2a; e2b = -e2b; e2c = -e2c;
|
||||
}
|
||||
if (area < 1e-8) return;
|
||||
|
||||
var minX = Std.int(Math.max(0, Math.floor(Math.min(v0x, Math.min(v1x, v2x)))));
|
||||
var maxX = Std.int(Math.min(width - 1, Math.ceil(Math.max(v0x, Math.max(v1x, v2x)))));
|
||||
var minY = Std.int(Math.max(0, Math.floor(Math.min(v0y, Math.min(v1y, v2y)))));
|
||||
var maxY = Std.int(Math.min(height - 1, Math.ceil(Math.max(v0y, Math.max(v1y, v2y)))));
|
||||
if (minX > maxX || minY > maxY) return;
|
||||
|
||||
var d = (v1x - v0x) * (v2y - v0y) - (v2x - v0x) * (v1y - v0y);
|
||||
var invD = 1.0 / d;
|
||||
var dzdx = ((v1z - v0z) * (v2y - v0y) - (v2z - v0z) * (v1y - v0y)) * invD;
|
||||
var dzdy = ((v2z - v0z) * (v1x - v0x) - (v1z - v0z) * (v2x - v0x)) * invD;
|
||||
|
||||
var zPush = (Math.abs(dzdx) + Math.abs(dzdy)) * 0.5;
|
||||
var zMaxTri = v0z;
|
||||
if (v1z > zMaxTri) zMaxTri = v1z;
|
||||
if (v2z > zMaxTri) zMaxTri = v2z;
|
||||
|
||||
var cx0 = minX + 0.5;
|
||||
var cy0 = minY + 0.5;
|
||||
var e0row = e0a * cx0 + e0b * cy0 + e0c;
|
||||
var e1row = e1a * cx0 + e1b * cy0 + e1c;
|
||||
var e2row = e2a * cx0 + e2b * cy0 + e2c;
|
||||
var zrow = v0z + dzdx * (cx0 - v0x) + dzdy * (cy0 - v0y);
|
||||
|
||||
var buf = hiz.mipBuffers[0];
|
||||
var w = width;
|
||||
for (py in minY...maxY + 1) {
|
||||
var e0 = e0row; var e1 = e1row; var e2 = e2row;
|
||||
var z = zrow;
|
||||
var idx = py * w + minX;
|
||||
for (px in minX...maxX + 1) {
|
||||
if (e0 >= 0 && e1 >= 0 && e2 >= 0) {
|
||||
var zPix = z + zPush;
|
||||
if (zPix > zMaxTri) zPix = zMaxTri;
|
||||
if (zPix < 0.0) zPix = 0.0;
|
||||
if (zPix > 1.0) zPix = 1.0;
|
||||
if (zPix < buf[idx]) buf[idx] = zPix;
|
||||
}
|
||||
e0 += e0a; e1 += e1a; e2 += e2a;
|
||||
z += dzdx;
|
||||
idx++;
|
||||
}
|
||||
e0row += e0b; e1row += e1b; e2row += e2b;
|
||||
zrow += dzdy;
|
||||
}
|
||||
}
|
||||
|
||||
function clipNearAndRasterize(
|
||||
cx0: Float, cy0: Float, cz0: Float, cw0: Float,
|
||||
cx1: Float, cy1: Float, cz1: Float, cw1: Float,
|
||||
cx2: Float, cy2: Float, cz2: Float, cw2: Float
|
||||
) {
|
||||
inX[0] = cx0; inY[0] = cy0; inZ[0] = cz0; inW[0] = cw0;
|
||||
inX[1] = cx1; inY[1] = cy1; inZ[1] = cz1; inW[1] = cw1;
|
||||
inX[2] = cx2; inY[2] = cy2; inZ[2] = cz2; inW[2] = cw2;
|
||||
|
||||
var outCount = 0;
|
||||
for (i in 0...3) {
|
||||
var aIdx = (i + 2) % 3;
|
||||
var bIdx = i;
|
||||
var aInside = inW[aIdx] >= nearClip;
|
||||
var bInside = inW[bIdx] >= nearClip;
|
||||
|
||||
if (aInside) {
|
||||
if (bInside) {
|
||||
outX[outCount] = inX[bIdx]; outY[outCount] = inY[bIdx];
|
||||
outZ[outCount] = inZ[bIdx]; outW[outCount] = inW[bIdx];
|
||||
outCount++;
|
||||
} else {
|
||||
var t = (inW[aIdx] - nearClip) / (inW[aIdx] - inW[bIdx]);
|
||||
outX[outCount] = inX[aIdx] + t * (inX[bIdx] - inX[aIdx]);
|
||||
outY[outCount] = inY[aIdx] + t * (inY[bIdx] - inY[aIdx]);
|
||||
outZ[outCount] = inZ[aIdx] + t * (inZ[bIdx] - inZ[aIdx]);
|
||||
outW[outCount] = inW[aIdx] + t * (inW[bIdx] - inW[aIdx]);
|
||||
outCount++;
|
||||
}
|
||||
} else {
|
||||
if (bInside) {
|
||||
var t = (inW[aIdx] - nearClip) / (inW[aIdx] - inW[bIdx]);
|
||||
outX[outCount] = inX[aIdx] + t * (inX[bIdx] - inX[aIdx]);
|
||||
outY[outCount] = inY[aIdx] + t * (inY[bIdx] - inY[aIdx]);
|
||||
outZ[outCount] = inZ[aIdx] + t * (inZ[bIdx] - inZ[aIdx]);
|
||||
outW[outCount] = inW[aIdx] + t * (inW[bIdx] - inW[aIdx]);
|
||||
outCount++;
|
||||
outX[outCount] = inX[bIdx]; outY[outCount] = inY[bIdx];
|
||||
outZ[outCount] = inZ[bIdx]; outW[outCount] = inW[bIdx];
|
||||
outCount++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (outCount < 3) return;
|
||||
|
||||
for (i in 0...outCount) {
|
||||
var invW = SimdOps.reciprocal(outW[i]);
|
||||
projSx[i] = (outX[i] * invW * 0.5 + 0.5) * width;
|
||||
projSy[i] = (outY[i] * invW * 0.5 + 0.5) * height;
|
||||
projSz[i] = outZ[i] * invW;
|
||||
}
|
||||
|
||||
var signedArea = (projSx[1] - projSx[0]) * (projSy[2] - projSy[0]) -
|
||||
(projSy[1] - projSy[0]) * (projSx[2] - projSx[0]);
|
||||
if (Math.abs(signedArea) < 1e-8) return;
|
||||
|
||||
trianglesRasterized++;
|
||||
|
||||
if (collectMode) {
|
||||
screenTris[screenTriCount++] = projSx[0]; screenTris[screenTriCount++] = projSy[0]; screenTris[screenTriCount++] = projSz[0];
|
||||
screenTris[screenTriCount++] = projSx[1]; screenTris[screenTriCount++] = projSy[1]; screenTris[screenTriCount++] = projSz[1];
|
||||
screenTris[screenTriCount++] = projSx[2]; screenTris[screenTriCount++] = projSy[2]; screenTris[screenTriCount++] = projSz[2];
|
||||
if (outCount == 4) {
|
||||
screenTris[screenTriCount++] = projSx[0]; screenTris[screenTriCount++] = projSy[0]; screenTris[screenTriCount++] = projSz[0];
|
||||
screenTris[screenTriCount++] = projSx[2]; screenTris[screenTriCount++] = projSy[2]; screenTris[screenTriCount++] = projSz[2];
|
||||
screenTris[screenTriCount++] = projSx[3]; screenTris[screenTriCount++] = projSy[3]; screenTris[screenTriCount++] = projSz[3];
|
||||
}
|
||||
} else {
|
||||
rasterizeTriangle(
|
||||
projSx[0], projSy[0], projSz[0],
|
||||
projSx[1], projSy[1], projSz[1],
|
||||
projSx[2], projSy[2], projSz[2]
|
||||
);
|
||||
if (outCount == 4) {
|
||||
rasterizeTriangle(
|
||||
projSx[0], projSy[0], projSz[0],
|
||||
projSx[2], projSy[2], projSz[2],
|
||||
projSx[3], projSy[3], projSz[3]
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function rasterizeProxy(
|
||||
positions: kha.arrays.Float32Array,
|
||||
indices: kha.arrays.Uint32Array,
|
||||
vp: iron.math.Mat4,
|
||||
screenW: Int, screenH: Int
|
||||
) {
|
||||
var triCount = Std.int(indices.length / 3);
|
||||
|
||||
var m00 = vp._00, m10 = vp._10, m20 = vp._20, m30 = vp._30;
|
||||
var m01 = vp._01, m11 = vp._11, m21 = vp._21, m31 = vp._31;
|
||||
var m02 = vp._02, m12 = vp._12, m22 = vp._22, m32 = vp._32;
|
||||
var m03 = vp._03, m13 = vp._13, m23 = vp._23, m33 = vp._33;
|
||||
|
||||
for (t in 0...triCount) {
|
||||
var i0 = indices[t * 3] * 3;
|
||||
var i1 = indices[t * 3 + 1] * 3;
|
||||
var i2 = indices[t * 3 + 2] * 3;
|
||||
|
||||
var px0 = positions[i0], py0 = positions[i0 + 1], pz0 = positions[i0 + 2];
|
||||
var px1 = positions[i1], py1 = positions[i1 + 1], pz1 = positions[i1 + 2];
|
||||
var px2 = positions[i2], py2 = positions[i2 + 1], pz2 = positions[i2 + 2];
|
||||
|
||||
var cw0 = m03 * px0 + m13 * py0 + m23 * pz0 + m33;
|
||||
var cw1 = m03 * px1 + m13 * py1 + m23 * pz1 + m33;
|
||||
var cw2 = m03 * px2 + m13 * py2 + m23 * pz2 + m33;
|
||||
|
||||
if (cw0 < nearClip && cw1 < nearClip && cw2 < nearClip) continue;
|
||||
|
||||
var cx0 = m00 * px0 + m10 * py0 + m20 * pz0 + m30;
|
||||
var cy0 = m01 * px0 + m11 * py0 + m21 * pz0 + m31;
|
||||
var cz0 = m02 * px0 + m12 * py0 + m22 * pz0 + m32;
|
||||
|
||||
var cx1 = m00 * px1 + m10 * py1 + m20 * pz1 + m30;
|
||||
var cy1 = m01 * px1 + m11 * py1 + m21 * pz1 + m31;
|
||||
var cz1 = m02 * px1 + m12 * py1 + m22 * pz1 + m32;
|
||||
|
||||
var cx2 = m00 * px2 + m10 * py2 + m20 * pz2 + m30;
|
||||
var cy2 = m01 * px2 + m11 * py2 + m21 * pz2 + m31;
|
||||
var cz2 = m02 * px2 + m12 * py2 + m22 * pz2 + m32;
|
||||
|
||||
clipNearAndRasterize(
|
||||
cx0, cy0, cz0, cw0,
|
||||
cx1, cy1, cz1, cw1,
|
||||
cx2, cy2, cz2, cw2
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
public function rasterizeProxyTransformed(
|
||||
clipPositions: kha.arrays.Float32Array,
|
||||
indices: kha.arrays.Uint32Array,
|
||||
screenW: Int, screenH: Int
|
||||
) {
|
||||
var triCount = Std.int(indices.length / 3);
|
||||
|
||||
for (t in 0...triCount) {
|
||||
var i0 = indices[t * 3] * 4;
|
||||
var i1 = indices[t * 3 + 1] * 4;
|
||||
var i2 = indices[t * 3 + 2] * 4;
|
||||
|
||||
var cx0 = clipPositions[i0], cy0 = clipPositions[i0 + 1], cz0 = clipPositions[i0 + 2], cw0 = clipPositions[i0 + 3];
|
||||
var cx1 = clipPositions[i1], cy1 = clipPositions[i1 + 1], cz1 = clipPositions[i1 + 2], cw1 = clipPositions[i1 + 3];
|
||||
var cx2 = clipPositions[i2], cy2 = clipPositions[i2 + 1], cz2 = clipPositions[i2 + 2], cw2 = clipPositions[i2 + 3];
|
||||
|
||||
if (cw0 < nearClip && cw1 < nearClip && cw2 < nearClip) continue;
|
||||
|
||||
clipNearAndRasterize(
|
||||
cx0, cy0, cz0, cw0,
|
||||
cx1, cy1, cz1, cw1,
|
||||
cx2, cy2, cz2, cw2
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
public function rasterizeBox(
|
||||
minX: Float, minY: Float, minZ: Float,
|
||||
maxX: Float, maxY: Float, maxZ: Float,
|
||||
vp: iron.math.Mat4,
|
||||
screenW: Int, screenH: Int
|
||||
) {
|
||||
var m00 = vp._00, m10 = vp._10, m20 = vp._20, m30 = vp._30;
|
||||
var m01 = vp._01, m11 = vp._11, m21 = vp._21, m31 = vp._31;
|
||||
var m02 = vp._02, m12 = vp._12, m22 = vp._22, m32 = vp._32;
|
||||
var m03 = vp._03, m13 = vp._13, m23 = vp._23, m33 = vp._33;
|
||||
|
||||
for (c in 0...8) {
|
||||
var x = (c & 1) != 0 ? maxX : minX;
|
||||
var y = (c & 2) != 0 ? maxY : minY;
|
||||
var z = (c & 4) != 0 ? maxZ : minZ;
|
||||
boxClipX[c] = m00 * x + m10 * y + m20 * z + m30;
|
||||
boxClipY[c] = m01 * x + m11 * y + m21 * z + m31;
|
||||
boxClipZ[c] = m02 * x + m12 * y + m22 * z + m32;
|
||||
boxClipW[c] = m03 * x + m13 * y + m23 * z + m33;
|
||||
}
|
||||
|
||||
for (face in BOX_FACES) {
|
||||
clipNearAndRasterize(
|
||||
boxClipX[face[0]], boxClipY[face[0]], boxClipZ[face[0]], boxClipW[face[0]],
|
||||
boxClipX[face[1]], boxClipY[face[1]], boxClipZ[face[1]], boxClipW[face[1]],
|
||||
boxClipX[face[2]], boxClipY[face[2]], boxClipZ[face[2]], boxClipW[face[2]]
|
||||
);
|
||||
clipNearAndRasterize(
|
||||
boxClipX[face[0]], boxClipY[face[0]], boxClipZ[face[0]], boxClipW[face[0]],
|
||||
boxClipX[face[2]], boxClipY[face[2]], boxClipZ[face[2]], boxClipW[face[2]],
|
||||
boxClipX[face[3]], boxClipY[face[3]], boxClipZ[face[3]], boxClipW[face[3]]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
public function buildHiZ() {
|
||||
hiz.buildPyramid();
|
||||
}
|
||||
|
||||
public function beginCollect() {
|
||||
collectMode = true;
|
||||
screenTriCount = 0;
|
||||
}
|
||||
|
||||
var tileTriCounts: Array<Int> = [];
|
||||
var tileTriOffsets: Array<Int> = [];
|
||||
var tileTriLists: Array<Array<Int>> = [];
|
||||
|
||||
public function flushCollected() {
|
||||
collectMode = false;
|
||||
var nTris = Std.int(screenTriCount / 9);
|
||||
if (nTris == 0) return;
|
||||
|
||||
var tilesX = Std.int(Math.ceil(width / TILE_SIZE));
|
||||
var tilesY = Std.int(Math.ceil(height / TILE_SIZE));
|
||||
var totalTiles = tilesX * tilesY;
|
||||
|
||||
if (tileTriCounts.length < totalTiles) {
|
||||
tileTriCounts.resize(totalTiles);
|
||||
tileTriOffsets.resize(totalTiles);
|
||||
tileTriLists.resize(totalTiles);
|
||||
}
|
||||
for (t in 0...totalTiles) tileTriCounts[t] = 0;
|
||||
for (t in 0...nTris) {
|
||||
var off = t * 9;
|
||||
var tx0 = Std.int(Math.max(0, Math.floor(Math.min(
|
||||
Math.min(screenTris[off], screenTris[off + 3]), screenTris[off + 6]) / TILE_SIZE)));
|
||||
var tx1 = Std.int(Math.min(tilesX - 1, Math.ceil(Math.max(
|
||||
Math.max(screenTris[off], screenTris[off + 3]), screenTris[off + 6]) / TILE_SIZE)));
|
||||
var ty0 = Std.int(Math.max(0, Math.floor(Math.min(
|
||||
Math.min(screenTris[off + 1], screenTris[off + 4]), screenTris[off + 7]) / TILE_SIZE)));
|
||||
var ty1 = Std.int(Math.min(tilesY - 1, Math.ceil(Math.max(
|
||||
Math.max(screenTris[off + 1], screenTris[off + 4]), screenTris[off + 7]) / TILE_SIZE)));
|
||||
for (ty in ty0...ty1 + 1) {
|
||||
for (tx in tx0...tx1 + 1) {
|
||||
tileTriCounts[ty * tilesX + tx]++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (t in 0...totalTiles) {
|
||||
var l = tileTriLists[t];
|
||||
if (l == null) { l = []; tileTriLists[t] = l; }
|
||||
if (l.length < tileTriCounts[t]) l.resize(tileTriCounts[t]);
|
||||
tileTriOffsets[t] = 0;
|
||||
}
|
||||
for (t in 0...nTris) {
|
||||
var off = t * 9;
|
||||
var tx0 = Std.int(Math.max(0, Math.floor(Math.min(
|
||||
Math.min(screenTris[off], screenTris[off + 3]), screenTris[off + 6]) / TILE_SIZE)));
|
||||
var tx1 = Std.int(Math.min(tilesX - 1, Math.ceil(Math.max(
|
||||
Math.max(screenTris[off], screenTris[off + 3]), screenTris[off + 6]) / TILE_SIZE)));
|
||||
var ty0 = Std.int(Math.max(0, Math.floor(Math.min(
|
||||
Math.min(screenTris[off + 1], screenTris[off + 4]), screenTris[off + 7]) / TILE_SIZE)));
|
||||
var ty1 = Std.int(Math.min(tilesY - 1, Math.ceil(Math.max(
|
||||
Math.max(screenTris[off + 1], screenTris[off + 4]), screenTris[off + 7]) / TILE_SIZE)));
|
||||
for (ty in ty0...ty1 + 1) {
|
||||
for (tx in tx0...tx1 + 1) {
|
||||
var ti = ty * tilesX + tx;
|
||||
tileTriLists[ti][tileTriOffsets[ti]++] = t;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var buf = hiz.mipBuffers[0];
|
||||
var w = width;
|
||||
var tris = screenTris;
|
||||
|
||||
#if (target.threaded)
|
||||
JobSystem.parallelFor(totalTiles, function(start, end) {
|
||||
for (ti in start...end) {
|
||||
var tx = ti % tilesX;
|
||||
var ty = Std.int(ti / tilesX);
|
||||
var px0 = tx * TILE_SIZE;
|
||||
var py0 = ty * TILE_SIZE;
|
||||
var px1 = Std.int(Math.min(w - 1, px0 + TILE_SIZE - 1));
|
||||
var py1 = Std.int(Math.min(height - 1, py0 + TILE_SIZE - 1));
|
||||
var list = tileTriLists[ti];
|
||||
var listLen = tileTriCounts[ti];
|
||||
for (li in 0...listLen) {
|
||||
var off = list[li] * 9;
|
||||
rasterizeTriangleBounded(
|
||||
tris[off], tris[off + 1], tris[off + 2],
|
||||
tris[off + 3], tris[off + 4], tris[off + 5],
|
||||
tris[off + 6], tris[off + 7], tris[off + 8],
|
||||
px0, py0, px1, py1, buf, w
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
#else
|
||||
for (ti in 0...totalTiles) {
|
||||
var tx = ti % tilesX;
|
||||
var ty = Std.int(ti / tilesX);
|
||||
var px0 = tx * TILE_SIZE;
|
||||
var py0 = ty * TILE_SIZE;
|
||||
var px1 = Std.int(Math.min(w - 1, px0 + TILE_SIZE - 1));
|
||||
var py1 = Std.int(Math.min(height - 1, py0 + TILE_SIZE - 1));
|
||||
var list = tileTriLists[ti];
|
||||
for (li in 0...list.length) {
|
||||
var off = list[li] * 9;
|
||||
rasterizeTriangleBounded(
|
||||
tris[off], tris[off + 1], tris[off + 2],
|
||||
tris[off + 3], tris[off + 4], tris[off + 5],
|
||||
tris[off + 6], tris[off + 7], tris[off + 8],
|
||||
px0, py0, px1, py1, buf, w
|
||||
);
|
||||
}
|
||||
}
|
||||
#end
|
||||
}
|
||||
|
||||
function rasterizeTriangleBounded(
|
||||
v0x: Float, v0y: Float, v0z: Float,
|
||||
v1x: Float, v1y: Float, v1z: Float,
|
||||
v2x: Float, v2y: Float, v2z: Float,
|
||||
tileX0: Int, tileY0: Int, tileX1: Int, tileY1: Int,
|
||||
buf: kha.arrays.Float32Array, w: Int
|
||||
) {
|
||||
var e0a = v1y - v0y; var e0b = v0x - v1x;
|
||||
var e1a = v2y - v1y; var e1b = v1x - v2x;
|
||||
var e2a = v0y - v2y; var e2b = v2x - v0x;
|
||||
var e0c = -e0a * v0x - e0b * v0y;
|
||||
var e1c = -e1a * v1x - e1b * v1y;
|
||||
var e2c = -e2a * v2x - e2b * v2y;
|
||||
|
||||
var area = e0a * v2x + e0b * v2y + e0c;
|
||||
if (area < 0) {
|
||||
area = -area;
|
||||
e0a = -e0a; e0b = -e0b; e0c = -e0c;
|
||||
e1a = -e1a; e1b = -e1b; e1c = -e1c;
|
||||
e2a = -e2a; e2b = -e2b; e2c = -e2c;
|
||||
}
|
||||
if (area < 1e-8) return;
|
||||
|
||||
var minX = Std.int(Math.max(tileX0, Math.floor(Math.min(v0x, Math.min(v1x, v2x)))));
|
||||
var maxX = Std.int(Math.min(tileX1, Math.ceil(Math.max(v0x, Math.max(v1x, v2x)))));
|
||||
var minY = Std.int(Math.max(tileY0, Math.floor(Math.min(v0y, Math.min(v1y, v2y)))));
|
||||
var maxY = Std.int(Math.min(tileY1, Math.ceil(Math.max(v0y, Math.max(v1y, v2y)))));
|
||||
if (minX > maxX || minY > maxY) return;
|
||||
|
||||
var d = (v1x - v0x) * (v2y - v0y) - (v2x - v0x) * (v1y - v0y);
|
||||
var invD = 1.0 / d;
|
||||
var dzdx = ((v1z - v0z) * (v2y - v0y) - (v2z - v0z) * (v1y - v0y)) * invD;
|
||||
var dzdy = ((v2z - v0z) * (v1x - v0x) - (v1z - v0z) * (v2x - v0x)) * invD;
|
||||
|
||||
var zPush = (Math.abs(dzdx) + Math.abs(dzdy)) * 0.5;
|
||||
var zMaxTri = v0z;
|
||||
if (v1z > zMaxTri) zMaxTri = v1z;
|
||||
if (v2z > zMaxTri) zMaxTri = v2z;
|
||||
|
||||
var cx0 = minX + 0.5;
|
||||
var cy0 = minY + 0.5;
|
||||
var e0row = e0a * cx0 + e0b * cy0 + e0c;
|
||||
var e1row = e1a * cx0 + e1b * cy0 + e1c;
|
||||
var e2row = e2a * cx0 + e2b * cy0 + e2c;
|
||||
var zrow = v0z + dzdx * (cx0 - v0x) + dzdy * (cy0 - v0y);
|
||||
|
||||
for (py in minY...maxY + 1) {
|
||||
var e0 = e0row; var e1 = e1row; var e2 = e2row;
|
||||
var z = zrow;
|
||||
var idx = py * w + minX;
|
||||
for (px in minX...maxX + 1) {
|
||||
if (e0 >= 0 && e1 >= 0 && e2 >= 0) {
|
||||
var zPix = z + zPush;
|
||||
if (zPix > zMaxTri) zPix = zMaxTri;
|
||||
if (zPix > 1.0) zPix = 1.0;
|
||||
if (zPix < buf[idx]) { buf[idx] = zPix; }
|
||||
}
|
||||
e0 += e0a; e1 += e1a; e2 += e2a;
|
||||
z += dzdx;
|
||||
idx++;
|
||||
}
|
||||
e0row += e0b; e1row += e1b; e2row += e2b;
|
||||
zrow += dzdy;
|
||||
}
|
||||
}
|
||||
|
||||
public function isOccluded(
|
||||
minX: Float, minY: Float, minZ: Float,
|
||||
maxX: Float, maxY: Float, maxZ: Float,
|
||||
vp: iron.math.Mat4,
|
||||
screenW: Int, screenH: Int
|
||||
): Bool {
|
||||
return hiz.isOccluded(minX, minY, minZ, maxX, maxY, maxZ, vp, screenW, screenH);
|
||||
}
|
||||
}
|
||||
79
leenkx/Sources/iron/culling/TemporalState.hx
Normal file
79
leenkx/Sources/iron/culling/TemporalState.hx
Normal file
@ -0,0 +1,79 @@
|
||||
package iron.culling;
|
||||
|
||||
class TemporalState {
|
||||
|
||||
public var visibleLastFrame: haxe.ds.IntMap<Bool>;
|
||||
public var visibleLastShadow: haxe.ds.IntMap<Bool>;
|
||||
public var frameLastTested: haxe.ds.IntMap<Int>;
|
||||
public var occlusionConfidence: haxe.ds.IntMap<Int>;
|
||||
public var capacity: Int = 0;
|
||||
public var frameCount: Int = 0;
|
||||
|
||||
public function new() {
|
||||
visibleLastFrame = new haxe.ds.IntMap<Bool>();
|
||||
visibleLastShadow = new haxe.ds.IntMap<Bool>();
|
||||
frameLastTested = new haxe.ds.IntMap<Int>();
|
||||
occlusionConfidence = new haxe.ds.IntMap<Int>();
|
||||
}
|
||||
|
||||
public function ensureCapacity(cap: Int) {
|
||||
if (capacity < cap) capacity = cap;
|
||||
}
|
||||
|
||||
public inline function wasVisibleLastFrame(i: Int): Bool {
|
||||
return visibleLastFrame.exists(i);
|
||||
}
|
||||
|
||||
public inline function wasVisibleShadowLastFrame(i: Int): Bool {
|
||||
return visibleLastShadow.exists(i);
|
||||
}
|
||||
|
||||
public inline function needsTest(i: Int, maxSkipFrames: Int): Bool {
|
||||
if (visibleLastFrame.exists(i)) return false; // assumed visible
|
||||
var lastTested = frameLastTested.get(i);
|
||||
if (lastTested == null) return true;
|
||||
return frameCount - lastTested >= maxSkipFrames;
|
||||
}
|
||||
|
||||
public inline function needsVisibleReTest(i: Int, retestInterval: Int): Bool {
|
||||
var lastTested = frameLastTested.get(i);
|
||||
if (lastTested == null) return true;
|
||||
return frameCount - lastTested >= retestInterval;
|
||||
}
|
||||
|
||||
public inline function setVisible(i: Int, visible: Bool) {
|
||||
if (visible) {
|
||||
visibleLastFrame.set(i, true);
|
||||
occlusionConfidence.remove(i);
|
||||
} else {
|
||||
visibleLastFrame.remove(i);
|
||||
var conf = occlusionConfidence.get(i);
|
||||
occlusionConfidence.set(i, conf == null ? 1 : Std.int(Math.min(255, conf + 1)));
|
||||
}
|
||||
}
|
||||
|
||||
public inline function markTested(i: Int) {
|
||||
frameLastTested.set(i, frameCount);
|
||||
}
|
||||
|
||||
public inline function setVisibleShadow(i: Int, visible: Bool) {
|
||||
if (visible) visibleLastShadow.set(i, true);
|
||||
else visibleLastShadow.remove(i);
|
||||
}
|
||||
|
||||
public inline function advanceFrame() {
|
||||
frameCount++;
|
||||
}
|
||||
|
||||
public function clear() {
|
||||
visibleLastFrame = new haxe.ds.IntMap<Bool>();
|
||||
visibleLastShadow = new haxe.ds.IntMap<Bool>();
|
||||
frameLastTested = new haxe.ds.IntMap<Int>();
|
||||
occlusionConfidence = new haxe.ds.IntMap<Int>();
|
||||
}
|
||||
|
||||
public inline function getConfidence(i: Int): Int {
|
||||
var c = occlusionConfidence.get(i);
|
||||
return c == null ? 0 : c;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user