main #148
@ -81,11 +81,11 @@ void main() {
|
||||
vec4 gr = textureLod(gbuffer_refraction, texCoord, 0.0);
|
||||
float ior = unpackIOR(gr.x);
|
||||
float transmittance = gr.y;
|
||||
float surfaceDepth = gr.z;
|
||||
float surfaceDepth = textureLod(gbufferD, texCoord, 0.0).r;
|
||||
float d = surfaceDepth * 2.0 - 1.0;
|
||||
|
||||
vec4 sceneSample = textureLod(tex, texCoord, 0.0);
|
||||
if (surfaceDepth == 0.0 || surfaceDepth == 1.0) {
|
||||
if (gr.z == 0.0 || surfaceDepth == 1.0) {
|
||||
fragColor = sceneSample;
|
||||
return;
|
||||
}
|
||||
|
||||
@ -96,41 +96,81 @@ vec3 sampleWaterNormals(vec2 hitXY, float speed, out vec2 tcnor0, out vec2 tcnor
|
||||
return normalize(n0 + n1 - 1.0);
|
||||
}
|
||||
|
||||
float waveHeight(vec2 xy, float speed) {
|
||||
return (sin(xy.x * 10.0 / waterDisplace + speed) * cos(xy.y * 10.0 / waterDisplace + speed) + sin(xy.x * 20.0 / waterDisplace + speed * 1.3) * cos(xy.y * 20.0 / waterDisplace + speed * 1.3) * 0.5) / 50.0 * waterDisplace;
|
||||
}
|
||||
|
||||
void main() {
|
||||
float gdepth = textureLod(gbufferD, texCoord, 0.0).r * 2.0 - 1.0;
|
||||
vec3 vray = normalize(viewRay);
|
||||
float speed = time * 2.0 * waterSpeed;
|
||||
bool isSky = (gdepth == 1.0);
|
||||
float farPlane = cameraProj.y / (1.0 - cameraProj.x);
|
||||
float cosAng = dot(eyeLook, vray);
|
||||
float domeR = farPlane * 0.94;
|
||||
float tDome = -3.5 * vray.z + sqrt(max(domeR * domeR - 12.25 * (1.0 - vray.z * vray.z), 0.0));
|
||||
float domeDepth01 = cameraProj.x + cameraProj.y / (tDome * cosAng);
|
||||
bool isSky = (gdepth * 0.5 + 0.5) >= domeDepth01;
|
||||
|
||||
// Ray-plane intersection with water surface (z = waterLevel)
|
||||
float denom = dot(vray, vec3(0.0, 0.0, 1.0));
|
||||
float denom = vray.z;
|
||||
float tWater = (waterLevel - eye.z) / denom;
|
||||
bool hasWaterHit = (abs(denom) > 0.0001) && (tWater > 0.0);
|
||||
|
||||
if (eye.z < waterLevel) {
|
||||
vec2 tc = texCoord;
|
||||
float fogFactor;
|
||||
|
||||
if (hasWaterHit && denom > 0.0) {
|
||||
// Looking up at water surface - apply normal distortion
|
||||
vec3 hit = eye + tWater * vray;
|
||||
vec2 tc0, tc1;
|
||||
vec3 n2 = sampleWaterNormals(hit.xy * waterFreq, speed, tc0, tc1);
|
||||
tc = texCoord + (n2.xy * n2.z) / 30.0 * waterRefract;
|
||||
fogFactor = clamp(tWater * waterDensity, 0.0, 0.95);
|
||||
} else {
|
||||
// Looking forward/down - distort via water surface above fragment
|
||||
vec3 p = getPos(eye, eyeLook, vray, gdepth, cameraProj);
|
||||
vec2 wxy = isSky ? (eye.xy + vray.xy * 50.0) : p.xy;
|
||||
vec2 tc0, tc1;
|
||||
vec3 n2 = sampleWaterNormals(wxy * waterFreq, speed, tc0, tc1);
|
||||
tc = texCoord + (n2.xy * n2.z) / 30.0 * waterRefract;
|
||||
float waterDist = isSky ? 50.0 : length(p - eye);
|
||||
fogFactor = clamp(waterDist * waterDensity, 0.0, 0.95);
|
||||
float depthThick = 1.0 + (waterLevel - eye.z);
|
||||
vec3 p = vec3(0.0);
|
||||
float geomDist = farPlane;
|
||||
if (!isSky) {
|
||||
p = getPos(eye, eyeLook, vray, gdepth, cameraProj);
|
||||
geomDist = length(p - eye);
|
||||
}
|
||||
bool upRay = hasWaterHit && denom > 0.0;
|
||||
float waterDist = upRay ? min(tWater, geomDist) : geomDist;
|
||||
|
||||
vec2 wxy;
|
||||
if (upRay && tWater < geomDist) {
|
||||
wxy = (eye + tWater * vray).xy;
|
||||
} else if (!isSky) {
|
||||
wxy = p.xy;
|
||||
} else {
|
||||
wxy = eye.xy + vray.xy * 50.0;
|
||||
}
|
||||
vec2 tc0, tc1;
|
||||
vec3 n2 = sampleWaterNormals(wxy * waterFreq, speed, tc0, tc1);
|
||||
tc = texCoord + (n2.xy * n2.z) / 30.0 * waterRefract;
|
||||
float ddepth2 = textureLod(gbufferD, tc, 0.0).r * 2.0 - 1.0;
|
||||
float dist2 = cameraProj.y / ((ddepth2 * 0.5 + 0.5) - cameraProj.x) / cosAng;
|
||||
if (dist2 > geomDist + 1.0) tc = texCoord;
|
||||
|
||||
vec3 refracted = textureLod(tex, tc, 0.0).rgb;
|
||||
fragColor.rgb = mix(refracted, waterColor, fogFactor);
|
||||
vec3 outCol = refracted;
|
||||
|
||||
if (upRay && tWater < geomDist) {
|
||||
outCol *= max(1.0 - length(n2.xy) * 1.5, 0.0);
|
||||
vec3 h2 = normalize(-vray + ld);
|
||||
outCol += pow(max(dot(n2, h2), 0.0), 80.0) * 0.6;
|
||||
}
|
||||
|
||||
if (!isSky) {
|
||||
float fdw = abs(p.z - (waterLevel + waveHeight(p.xy, speed)));
|
||||
float bandw = 0.1 + waterDist * 0.003;
|
||||
if (fdw < bandw) {
|
||||
float foamLod = clamp(waterDist / 15.0, 0.0, 5.0);
|
||||
vec3 fm0 = textureLod(sfoam, tc0 * 3.0 + vec2(speed / 30.0, speed / 50.0), foamLod).rgb;
|
||||
vec3 fm1 = textureLod(sfoam, tc1 * 4.0 + vec2(-speed / 35.0, speed / 45.0), foamLod).rgb;
|
||||
float fstr = clamp(1.0 - fm0.r * 0.5 - fm1.b * 0.5, 0.0, 1.0);
|
||||
float ffac = clamp(1.0 - fdw / bandw, 0.0, 1.0);
|
||||
float foamFade = clamp(1.0 - (waterLevel - eye.z) * 0.4, 0.0, 1.0);
|
||||
vec3 foamCol = mix(waterColor, vec3(1.0), 0.6);
|
||||
outCol = mix(outCol, foamCol, ffac * fstr * 0.5 * foamFade);
|
||||
}
|
||||
}
|
||||
|
||||
float fogFactor = 1.0 - exp(-waterDist * waterDensity * 0.006 * depthThick);
|
||||
outCol = mix(outCol, waterColor, clamp(fogFactor, 0.0, 1.0));
|
||||
|
||||
fragColor.rgb = outCol;
|
||||
fragColor.a = 1.0;
|
||||
return;
|
||||
}
|
||||
@ -144,21 +184,19 @@ void main() {
|
||||
if (isSky) tWater = min(tWater, 100.0); // Clamp to prevent aliasing at horizon
|
||||
vec3 p = isSky ? (eye + tWater * vray) : getPos(eye, eyeLook, vray, gdepth, cameraProj);
|
||||
|
||||
float horizonFactor = clamp(1.0 - tWater / 60.0, 0.0, 1.0);
|
||||
if (!isSky && p.z > waterLevel) {
|
||||
fragColor = vec4(0.0);
|
||||
if (!isSky && p.z > waterLevel - 0.01) {
|
||||
fragColor = textureLod(tex, texCoord, 0.0);
|
||||
return;
|
||||
}
|
||||
float horizonFactor = clamp(1.0 - tWater / 60.0, 0.0, 1.0);
|
||||
|
||||
// Displace surface
|
||||
float geomZ = p.z; // undisplaced depth for foam/fog tests
|
||||
p.z += (sin(p.x * 10.0 / waterDisplace + speed) * cos(p.y * 10.0 / waterDisplace + speed)
|
||||
+ sin(p.x * 20.0 / waterDisplace + speed * 1.3) * cos(p.y * 20.0 / waterDisplace + speed * 1.3) * 0.5)
|
||||
/ 50.0 * waterDisplace;
|
||||
p.z += waveHeight(p.xy, speed);
|
||||
|
||||
// Hit plane to determine uvs
|
||||
vec3 v = normalize(eye - p);
|
||||
float t = (waterLevel - eye.z) / dot(v, vec3(0.0, 0.0, 1.0));
|
||||
float t = (waterLevel - eye.z) / v.z;
|
||||
vec3 hit = eye + t * v;
|
||||
|
||||
// Sample normal maps
|
||||
@ -171,8 +209,8 @@ void main() {
|
||||
tc = texCoord + (n2.xy * n2.z) / 30.0 * waterRefract;
|
||||
} else {
|
||||
float ddepth = textureLod(gbufferD, texCoord + (n2.xy * n2.z) / 40.0, 0.0).r * 2.0 - 1.0;
|
||||
vec3 p2 = getPos(eye, eyeLook, vray, ddepth, cameraProj);
|
||||
tc = p2.z > waterLevel ? texCoord : texCoord + (n2.xy * n2.z) / 30.0 * waterRefract;
|
||||
float dist2 = cameraProj.y / ((ddepth * 0.5 + 0.5) - cameraProj.x) / cosAng;
|
||||
tc = (eye.z + vray.z * dist2) > waterLevel ? texCoord : texCoord + (n2.xy * n2.z) / 30.0 * waterRefract;
|
||||
}
|
||||
|
||||
// Light
|
||||
@ -222,26 +260,25 @@ void main() {
|
||||
#else
|
||||
fragColor.rgb = mix(refracted, reflectedEnv, waterReflect * fresnel);
|
||||
#endif
|
||||
// Water color tint - blend rather than multiply to preserve brightness
|
||||
float colorMix = isSky ? 0.7 : 0.5;
|
||||
fragColor.rgb = mix(fragColor.rgb, fragColor.rgb * waterColor, colorMix * horizonFactor);
|
||||
fragColor.rgb = mix(fragColor.rgb, waterColor, colorMix);
|
||||
// Blinn-Phong specular using half-vector, faded at horizon
|
||||
vec3 h = normalize(v + ld);
|
||||
float specAmount = pow(max(dot(n2, h), 0.0), 200.0) * (200.0 + 8.0) / (PI * 8.0);
|
||||
fragColor.rgb += specAmount * (isSky ? 0.3 : 1.0);
|
||||
// Depth fog - blend toward waterColor with depth, faded at horizon
|
||||
float depthFog = clamp(-(geomZ - waterLevel) * waterDensity, 0.0, 0.9);
|
||||
fragColor.rgb = mix(fragColor.rgb, waterColor, depthFog * horizonFactor);
|
||||
fragColor.rgb += specAmount * (isSky ? 0.3 : 1.0) * horizonFactor;
|
||||
float waterColumn = isSky ? 1.0 : max(length(p - eye) - tWater, 0.0);
|
||||
float depthFog = 1.0 - exp(-waterColumn * waterDensity * 0.006);
|
||||
vec3 surfaceCol = mix(waterColor, reflectedEnv, waterReflect * fresnel);
|
||||
fragColor.rgb = mix(fragColor.rgb, surfaceCol, min(depthFog, 0.9));
|
||||
// Alpha fades smoothly at horizon instead of hard cut
|
||||
// Skydome sits 3.5 below the camera (_skydomeMatrix), so its horizon
|
||||
// appears where the ray z reaches -3.5 / domeRadius
|
||||
float farPlane = cameraProj.y / (1.0 - cameraProj.x);
|
||||
float horizonFade = clamp((-3.5 / (farPlane * 0.95) - vray.z) * 20.0, 0.0, 1.0);
|
||||
fragColor.a = isSky ? horizonFade : clamp(abs(geomZ - waterLevel) * 5.0, 0.0, 1.0);
|
||||
|
||||
// Foam - based on actual geometry depth below water surface
|
||||
float fd = isSky ? 1.0 : abs(geomZ - waterLevel);
|
||||
if (fd < 0.1) {
|
||||
fragColor.a = isSky ? horizonFade : clamp(abs(geomZ - waterLevel) * 5.0 + 0.1, 0.0, 1.0);
|
||||
float waveH = p.z - geomZ; // displacement already applied above
|
||||
float band = 0.1 + tWater * 0.003;
|
||||
float fd = isSky ? 1.0 : abs(geomZ - (waterLevel + waveH));
|
||||
if (fd < band) {
|
||||
// Based on foam by Owen Deery
|
||||
// http://fire-face.com/personal/water
|
||||
// Distance-based LOD blurs foam at range to reduce noise
|
||||
@ -250,8 +287,10 @@ void main() {
|
||||
vec2 foamUV1 = tcnor1 * 4.0 + vec2(-speed / 35.0, speed / 45.0);
|
||||
vec3 foamMask0 = textureLod(sfoam, foamUV0, foamLod).rgb;
|
||||
vec3 foamMask1 = textureLod(sfoam, foamUV1, foamLod).rgb;
|
||||
float foamStrength = clamp(1.0 - foamMask0.r * 0.5 - foamMask1.b * 0.5, 0.0, 1.0);
|
||||
float fac = (1.0 - (fd * (1.0 / 0.1))) * horizonFactor;
|
||||
fragColor.rgb = mix(fragColor.rgb, mix(fragColor.rgb, waterColor + 0.2, foamStrength), clamp(fac, 0.0, 1.0) * 0.5);
|
||||
float foamMask = clamp(1.0 - foamMask0.r * 0.5 - foamMask1.b * 0.5, 0.0, 1.0);
|
||||
float fac = clamp(1.0 - fd / band, 0.0, 1.0);
|
||||
vec3 foamCol = mix(waterColor, vec3(1.0), 0.6);
|
||||
fragColor.rgb = mix(fragColor.rgb, foamCol, fac * foamMask * 0.8);
|
||||
fragColor.a = max(fragColor.a, fac * foamMask);
|
||||
}
|
||||
}
|
||||
|
||||
@ -18,6 +18,13 @@ import iron.object.LightObject;
|
||||
import iron.object.MeshObject;
|
||||
import iron.object.Uniforms;
|
||||
import iron.object.Clipmap;
|
||||
#if lnx_radix
|
||||
import haxe.io.FPHelper;
|
||||
import radix.Radix;
|
||||
#end
|
||||
#if lnx_occlusion_culling
|
||||
import iron.culling.CullSystem;
|
||||
#end
|
||||
#if lnx_vr
|
||||
import iron.math.Vec4;
|
||||
import iron.math.Mat4;
|
||||
@ -50,10 +57,6 @@ class RenderPath {
|
||||
return vrSimulateMode;
|
||||
}
|
||||
|
||||
// TODO: done remove safely
|
||||
public static inline function debugLog(msg: String, once: Bool = true): Void {
|
||||
return;
|
||||
}
|
||||
#end
|
||||
|
||||
public var frameScissor = false;
|
||||
@ -81,6 +84,18 @@ class RenderPath {
|
||||
var renderToXRFramebuffer = false;
|
||||
#end
|
||||
public var drawOrder = DrawOrder.Distance;
|
||||
#if lnx_frustum_culling
|
||||
public var frustumCulling = true;
|
||||
#else
|
||||
public var frustumCulling = false;
|
||||
#end
|
||||
|
||||
#if lnx_occlusion_culling
|
||||
public var occlusionCulling = true;
|
||||
#else
|
||||
public var occlusionCulling = false;
|
||||
#end
|
||||
|
||||
public var paused = false;
|
||||
public var ready(get, never): Bool;
|
||||
function get_ready(): Bool { return loading == 0; }
|
||||
@ -95,6 +110,8 @@ class RenderPath {
|
||||
var lastH = 0;
|
||||
var bindParams: Array<String>;
|
||||
var meshesSorted: Bool;
|
||||
var cullComputed: Bool;
|
||||
var cullFlagsReset: Bool;
|
||||
var scissorSet = false;
|
||||
var viewportScaled = false;
|
||||
var lastFrameTime = 0.0;
|
||||
@ -304,6 +321,8 @@ class RenderPath {
|
||||
currentD = 1;
|
||||
currentFace = -1;
|
||||
meshesSorted = false;
|
||||
cullComputed = false;
|
||||
cullFlagsReset = false;
|
||||
sun = null;
|
||||
|
||||
for (l in Scene.active.lights) {
|
||||
@ -470,6 +489,60 @@ class RenderPath {
|
||||
rt.image.generateMipmaps(1000);
|
||||
}
|
||||
|
||||
#if lnx_radix
|
||||
static var sortDistanceKeys = new Array<Int>();
|
||||
static var sortDistanceMeshesBuf = new Array<MeshObject>();
|
||||
static var sortDistanceKeysBuf = new Array<Int>();
|
||||
|
||||
static var sortIndexKeys = new Array<Int>();
|
||||
static var sortIndexMeshesBuf = new Array<MeshObject>();
|
||||
static var sortIndexKeysBuf = new Array<Int>();
|
||||
|
||||
public static function sortMeshesDistance(meshes: Array<MeshObject>) {
|
||||
var n = meshes.length;
|
||||
var keys = sortDistanceKeys;
|
||||
var meshesBuf = sortDistanceMeshesBuf;
|
||||
var keysBuf = sortDistanceKeysBuf;
|
||||
if (keys.length < n) {
|
||||
keys.resize(n);
|
||||
meshesBuf.resize(n);
|
||||
keysBuf.resize(n);
|
||||
}
|
||||
for (i in 0...n) {
|
||||
var m = meshes[i];
|
||||
var key = FPHelper.floatToI32(m.cameraDistance) >>> 1;
|
||||
#if rp_depth_texture
|
||||
if (m.depthRead) key |= 0x80000000;
|
||||
#end
|
||||
keys[i] = key;
|
||||
}
|
||||
Radix.sortCopyKeyed(meshes, keys, meshesBuf, keysBuf);
|
||||
if (meshesBuf.length > n) meshesBuf.resize(n);
|
||||
}
|
||||
|
||||
public static function sortMeshesIndex(meshes: Array<MeshObject>) {
|
||||
var n = meshes.length;
|
||||
var keys = sortIndexKeys;
|
||||
var meshesBuf = sortIndexMeshesBuf;
|
||||
var keysBuf = sortIndexKeysBuf;
|
||||
if (keys.length < n) {
|
||||
keys.resize(n);
|
||||
meshesBuf.resize(n);
|
||||
keysBuf.resize(n);
|
||||
}
|
||||
for (i in 0...n) {
|
||||
var m = meshes[i];
|
||||
var si = m.data.sortingIndex;
|
||||
var key = si < -0x40000000 ? 0 : si > 0x3fffffff ? 0x7fffffff : si + 0x40000000;
|
||||
#if rp_depth_texture
|
||||
if (m.depthRead) key |= 0x80000000;
|
||||
#end
|
||||
keys[i] = key;
|
||||
}
|
||||
Radix.sortCopyKeyed(meshes, keys, meshesBuf, keysBuf);
|
||||
if (meshesBuf.length > n) meshesBuf.resize(n);
|
||||
}
|
||||
#else
|
||||
static inline function boolToInt(b: Bool): Int {
|
||||
return b ? 1 : 0;
|
||||
}
|
||||
@ -501,6 +574,7 @@ class RenderPath {
|
||||
return a.data.name > b.data.name ? 1 : -1;
|
||||
});
|
||||
}
|
||||
#end
|
||||
|
||||
public function drawMeshes(context: String) {
|
||||
var isShadows = context == "shadowmap" || context == "shadowmap_transparent";
|
||||
@ -519,6 +593,11 @@ class RenderPath {
|
||||
for (i in 0...LightObject.cascadeCount) {
|
||||
light.setCascade(Scene.active.camera, i);
|
||||
currentG.viewport(i * step, 0, step, step);
|
||||
#if lnx_occlusion_culling
|
||||
if (occlusionCulling && CullSystem.active != null) {
|
||||
CullSystem.active.cullShadow(light);
|
||||
}
|
||||
#end
|
||||
submitDraw(context);
|
||||
}
|
||||
drawn = true;
|
||||
@ -529,7 +608,14 @@ class RenderPath {
|
||||
if (context == "mesh") LightObject.updateClusters(Scene.active.camera);
|
||||
#end
|
||||
|
||||
if (!drawn) submitDraw(context);
|
||||
if (!drawn) {
|
||||
#if lnx_occlusion_culling
|
||||
if (isShadows && occlusionCulling && CullSystem.active != null) {
|
||||
CullSystem.active.cullShadow(light);
|
||||
}
|
||||
#end
|
||||
submitDraw(context);
|
||||
}
|
||||
|
||||
// Callbacks to specific context
|
||||
if (contextEvents != null) {
|
||||
@ -546,7 +632,34 @@ class RenderPath {
|
||||
var meshes = Scene.active.meshes;
|
||||
MeshObject.lastPipeline = null;
|
||||
|
||||
if (!meshesSorted && camera != null) { // Order max once per frame for now
|
||||
#if lnx_occlusion_culling
|
||||
if (!cullFlagsReset && !occlusionCulling) {
|
||||
cullFlagsReset = true;
|
||||
for (m in meshes) {
|
||||
m.occlusionVisible = true;
|
||||
m.shadowOcclusionVisible = true;
|
||||
}
|
||||
}
|
||||
#end
|
||||
|
||||
if (!cullComputed && (context == "mesh" || context == "depth")) {
|
||||
#if lnx_occlusion_culling
|
||||
if (occlusionCulling) {
|
||||
if (CullSystem.active == null) CullSystem.init();
|
||||
if (CullSystem.active != null && camera != null) {
|
||||
if (CullSystem.active.proxiesDirty) {
|
||||
CullSystem.active.loadOccluderProxies();
|
||||
CullSystem.active.proxiesDirty = false;
|
||||
}
|
||||
CullSystem.active.cull(camera);
|
||||
}
|
||||
}
|
||||
#end
|
||||
cullComputed = true;
|
||||
}
|
||||
|
||||
var isShadowCtx = context == "shadowmap" || context == "shadowmap_transparent";
|
||||
if (!meshesSorted && camera != null && !isShadowCtx) {
|
||||
var camX = camera.transform.worldx();
|
||||
var camY = camera.transform.worldy();
|
||||
var camZ = camera.transform.worldz();
|
||||
@ -571,8 +684,20 @@ class RenderPath {
|
||||
|
||||
static inline function meshRenderLoop(g: Graphics, context: String, _bindParams: Array<String>, _meshes: Array<MeshObject>) {
|
||||
var isReadingDepth = false;
|
||||
#if lnx_occlusion_culling
|
||||
var cullSys = CullSystem.active;
|
||||
#end
|
||||
|
||||
for (m in _meshes) {
|
||||
#if lnx_occlusion_culling
|
||||
if (cullSys != null && !m.occlusionVisible && (context == "mesh" || context == "depth" || context == "translucent")) {
|
||||
continue;
|
||||
}
|
||||
if (cullSys != null && !m.shadowOcclusionVisible && (context == "shadowmap" || context == "shadowmap_transparent")) {
|
||||
continue;
|
||||
}
|
||||
#end
|
||||
|
||||
#if rp_depth_texture
|
||||
// First mesh that reads depth
|
||||
if (!isReadingDepth && m.depthRead) {
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
@ -14,6 +14,7 @@ import iron.math.Vec4;
|
||||
import iron.math.Mat4;
|
||||
import iron.data.SceneFormat;
|
||||
import iron.data.MeshData;
|
||||
import iron.data.ShaderData;
|
||||
|
||||
class Geometry {
|
||||
#if lnx_deinterleaved
|
||||
@ -208,7 +209,7 @@ class Geometry {
|
||||
for (v in 0...vertexBuffers.length)
|
||||
if (vertexBuffers[v].name == e.name) {
|
||||
vbs.push(vertexBuffers[v].buffer);
|
||||
continue;
|
||||
break;
|
||||
}
|
||||
if (e.name == "ipos" && instancedVB != null) {
|
||||
vbs.push(instancedVB);
|
||||
@ -264,7 +265,7 @@ class Geometry {
|
||||
for (i in 0...vaLength)
|
||||
vertexBuffers[i] = {
|
||||
name: vertexArrays[i].attrib,
|
||||
buffer: makeDeinterleavedVB(vertexArrays[i].values, vertexArrays[i].attrib, vertexArrays[i].size)
|
||||
buffer: makeDeinterleavedVB(vertexArrays[i])
|
||||
};
|
||||
#else
|
||||
|
||||
@ -295,14 +296,20 @@ class Geometry {
|
||||
}
|
||||
|
||||
#if lnx_deinterleaved
|
||||
function makeDeinterleavedVB(data: ByteArray, name: String, structLength: Int): VertexBuffer {
|
||||
function makeDeinterleavedVB(va: TVertexArray): VertexBuffer {
|
||||
var struct = new VertexStructure();
|
||||
struct.add(name, structLength == 2 ? VertexData.Short2Norm : VertexData.Short4Norm);
|
||||
struct.add(va.attrib, ShaderContext.parseData(va.data));
|
||||
|
||||
var vertexBuffer = new VertexBuffer(Std.int(data.byteLength / 2 / structLength), struct, usage);
|
||||
var vcount = verticesCount(va);
|
||||
var vertexBuffer = new VertexBuffer(vcount, struct, usage);
|
||||
|
||||
var stride = Std.int(struct.byteSize() / 2);
|
||||
var vertices = vertexBuffer.lock();
|
||||
for (i in 0...Std.int(vertices.byteLength / 2)) vertices.setInt16(i * 2, data.getInt16(i * 2));
|
||||
var di = -1;
|
||||
for (i in 0...vcount) {
|
||||
for (o in 0...va.size) vertices.setInt16(++di * 2, va.values[i * va.size + o]);
|
||||
for (o in va.size...stride) vertices.setInt16(++di * 2, 0);
|
||||
}
|
||||
vertexBuffer.unlock();
|
||||
|
||||
return vertexBuffer;
|
||||
|
||||
@ -71,30 +71,52 @@ class MeshBatch {
|
||||
if (b.meshes.length > 0 && b.meshes[0].cullMaterial(context)) continue;
|
||||
|
||||
var scontext = b.shader.getContext(context);
|
||||
if (scontext == null) continue;
|
||||
g.setPipeline(scontext.pipeState);
|
||||
// #if lnx_deinterleaved // TODO
|
||||
// g.setVertexBuffers(b.vertexBuffers);
|
||||
// #else
|
||||
#if lnx_deinterleaved
|
||||
g.setVertexBuffers(b.getVertexBuffers(scontext.raw.vertex_elements));
|
||||
#else
|
||||
g.setVertexBuffer(b.getVertexBuffer(scontext.raw.vertex_elements));
|
||||
// #end
|
||||
#end
|
||||
g.setIndexBuffer(b.indexBuffer);
|
||||
|
||||
Uniforms.setContextConstants(g, scontext, bindParams);
|
||||
|
||||
RenderPath.sortMeshesDistance(b.meshes);
|
||||
var isShadowCtx = context == "shadowmap" || context == "shadowmap_transparent";
|
||||
if (!isShadowCtx) RenderPath.sortMeshesDistance(b.meshes);
|
||||
|
||||
for (m in b.meshes) {
|
||||
if (!m.visible) continue; // Skip render if object is hidden
|
||||
#if lnx_occlusion_culling
|
||||
if (!m.occlusionVisible && (context == "mesh" || context == "depth" || context == "translucent")) continue;
|
||||
if (!m.shadowOcclusionVisible && isShadowCtx) continue;
|
||||
#end
|
||||
if (m.cullMesh(context, Scene.active.camera, RenderPath.active.light)) continue;
|
||||
|
||||
// Get context
|
||||
var materialContexts: Array<MaterialContext> = [];
|
||||
var shaderContexts: Array<ShaderContext> = [];
|
||||
m.getContexts(context, m.materials, materialContexts, shaderContexts);
|
||||
var materialContexts: Array<MaterialContext> = null;
|
||||
var shaderContexts: Array<ShaderContext> = null;
|
||||
if (m.cachedMaterialContexts != null && MeshObject.matsEqual(m.cachedMats.get(context), m.materials)) {
|
||||
materialContexts = m.cachedMaterialContexts.get(context);
|
||||
shaderContexts = m.cachedShaderContexts.get(context);
|
||||
}
|
||||
if (materialContexts == null) {
|
||||
materialContexts = [];
|
||||
shaderContexts = [];
|
||||
m.getContexts(context, m.materials, materialContexts, shaderContexts);
|
||||
if (m.cachedMaterialContexts == null) {
|
||||
m.cachedMats = new Map();
|
||||
m.cachedMaterialContexts = new Map();
|
||||
m.cachedShaderContexts = new Map();
|
||||
}
|
||||
m.cachedMats.set(context, m.materials);
|
||||
m.cachedMaterialContexts.set(context, materialContexts);
|
||||
m.cachedShaderContexts.set(context, shaderContexts);
|
||||
}
|
||||
|
||||
Uniforms.posUnpack = m.data.scalePos;
|
||||
Uniforms.texUnpack = m.data.scaleTex;
|
||||
m.transform.update();
|
||||
if (!isShadowCtx) m.transform.update();
|
||||
|
||||
// Render mesh
|
||||
Uniforms.setObjectConstants(g, scontext, m);
|
||||
@ -126,8 +148,12 @@ class Bucket {
|
||||
|
||||
public var batched = false;
|
||||
public var shader: ShaderData;
|
||||
#if lnx_deinterleaved
|
||||
public var vertexBuffers: Array<InterleavedVertexBuffer> = [];
|
||||
#else
|
||||
var vertexBuffer: VertexBuffer;
|
||||
var vertexBufferMap: Map<String, VertexBuffer> = new Map();
|
||||
#end
|
||||
public var indexBuffer: IndexBuffer;
|
||||
public var meshes: Array<MeshObject> = [];
|
||||
|
||||
@ -137,8 +163,12 @@ class Bucket {
|
||||
|
||||
public function remove() {
|
||||
indexBuffer.delete();
|
||||
#if lnx_deinterleaved
|
||||
for (buf in vertexBuffers) buf.buffer.delete();
|
||||
#else
|
||||
// this.vertexBuffer is in the map, so it's also deleted here
|
||||
for (buf in vertexBufferMap) buf.delete();
|
||||
#end
|
||||
meshes = [];
|
||||
}
|
||||
|
||||
@ -150,6 +180,20 @@ class Bucket {
|
||||
meshes.remove(m);
|
||||
}
|
||||
|
||||
#if lnx_deinterleaved
|
||||
public function getVertexBuffers(elems: Array<TVertexElement>): Array<VertexBuffer> {
|
||||
var vbs: Array<VertexBuffer> = [];
|
||||
for (e in elems) {
|
||||
for (v in vertexBuffers) {
|
||||
if (v.name == e.name) {
|
||||
vbs.push(v.buffer);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return vbs;
|
||||
}
|
||||
#else
|
||||
function copyAttribute(attribSize: Int, count: Int, to: ByteArray, toStride: Int, toOffset: Int, from: ByteArray, fromStride: Int, fromOffset: Int) {
|
||||
for (i in 0...count) {
|
||||
for (j in 0...attribSize) {
|
||||
@ -195,6 +239,7 @@ class Bucket {
|
||||
}
|
||||
return vb;
|
||||
}
|
||||
#end
|
||||
|
||||
function vertexCount(g: Geometry, hasUVs: Bool): Int {
|
||||
var vcount = g.getVerticesLength();
|
||||
@ -239,6 +284,50 @@ class Bucket {
|
||||
|
||||
if (mdatas.length == 0) return;
|
||||
|
||||
#if lnx_deinterleaved
|
||||
var attrNames: Array<String> = [];
|
||||
var attrReps = new Map<String, TVertexArray>();
|
||||
for (md in mdatas) {
|
||||
for (va in md.geom.vertexArrays) {
|
||||
if (attrReps.get(va.attrib) == null) {
|
||||
attrReps.set(va.attrib, va);
|
||||
attrNames.push(va.attrib);
|
||||
}
|
||||
}
|
||||
}
|
||||
for (name in attrNames) {
|
||||
var rep = attrReps.get(name);
|
||||
var attrStruct = new VertexStructure();
|
||||
attrStruct.add(name, ShaderContext.parseData(rep.data));
|
||||
var stride = Std.int(attrStruct.byteSize() / 2);
|
||||
var vtotal = 0;
|
||||
for (md in mdatas) vtotal += md.geom.getVerticesCount();
|
||||
var vb = new VertexBuffer(vtotal, attrStruct, Usage.StaticUsage);
|
||||
var vertices = vb.lock();
|
||||
var ai = -1;
|
||||
for (md in mdatas) {
|
||||
var va: TVertexArray = null;
|
||||
for (v in md.geom.vertexArrays) {
|
||||
if (v.attrib == name) {
|
||||
va = v;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (va != null && va.values != null) {
|
||||
var nverts = md.geom.getVerticesCount();
|
||||
for (i in 0...nverts) {
|
||||
for (o in 0...va.size) vertices.setInt16(++ai * 2, va.values[i * va.size + o]);
|
||||
for (o in va.size...stride) vertices.setInt16(++ai * 2, 0);
|
||||
}
|
||||
}
|
||||
else {
|
||||
for (i in 0...md.geom.getVerticesCount() * stride) vertices.setInt16(++ai * 2, 0);
|
||||
}
|
||||
}
|
||||
vb.unlock();
|
||||
vertexBuffers.push({name: name, buffer: vb});
|
||||
}
|
||||
#else
|
||||
// Pick UVs if present
|
||||
var vs = mdatas[0].geom.struct;
|
||||
for (md in mdatas) if (md.geom.struct.size() > vs.size()) vs = md.geom.struct;
|
||||
@ -256,6 +345,7 @@ class Bucket {
|
||||
var s = "";
|
||||
for (e in vs.elements) s += e.name;
|
||||
vertexBufferMap.set(s, vertexBuffer);
|
||||
#end
|
||||
|
||||
indexBuffer = new IndexBuffer(icount, Usage.StaticUsage);
|
||||
var indices = indexBuffer.lock();
|
||||
|
||||
@ -60,6 +60,16 @@ typedef TMeshData = {
|
||||
@:optional public var scale_pos: Null<FastFloat>; // Unpack pos from (-1,1) coords
|
||||
@:optional public var scale_tex: Null<FastFloat>; // Unpack tex from (-1,1) coords
|
||||
@:optional public var morph_target: TMorphTarget;
|
||||
@:optional public var occluder_proxy: TOccluderProxy;
|
||||
}
|
||||
|
||||
#if js
|
||||
typedef TOccluderProxy = {
|
||||
#else
|
||||
@:structInit class TOccluderProxy {
|
||||
#end
|
||||
public var positions: Float32Array;
|
||||
public var indices: Uint32Array;
|
||||
}
|
||||
|
||||
#if js
|
||||
|
||||
@ -102,8 +102,9 @@ class ShaderContext {
|
||||
constants = [];
|
||||
textureUnits = [];
|
||||
|
||||
var instStruct: VertexStructure = null;
|
||||
if (instancingType > 0) { // Instancing
|
||||
var instStruct = new VertexStructure();
|
||||
instStruct = new VertexStructure();
|
||||
instStruct.add("ipos", VertexData.Float3);
|
||||
if (instancingType == 2 || instancingType == 4) {
|
||||
instStruct.add("irot", VertexData.Float3);
|
||||
@ -117,11 +118,19 @@ class ShaderContext {
|
||||
this.instanceStride = Std.int(instStruct.byteSize() / 4);
|
||||
|
||||
instStruct.instanced = true;
|
||||
pipeState.inputLayout = [structure, instStruct];
|
||||
}
|
||||
else { // Regular
|
||||
pipeState.inputLayout = [structure];
|
||||
#if lnx_deinterleaved
|
||||
var layouts: Array<VertexStructure> = [];
|
||||
for (e in structure.elements) {
|
||||
var s = new VertexStructure();
|
||||
s.add(e.name, e.data);
|
||||
layouts.push(s);
|
||||
}
|
||||
#else
|
||||
var layouts = [structure];
|
||||
#end
|
||||
if (instStruct != null) layouts.push(instStruct);
|
||||
pipeState.inputLayout = layouts;
|
||||
|
||||
// Depth
|
||||
pipeState.depthWrite = raw.depth_write;
|
||||
|
||||
@ -62,7 +62,20 @@ class CameraObject extends Object {
|
||||
|
||||
public function buildProjection(screenAspect: Null<Float> = null) {
|
||||
if (data.raw.ortho != null) {
|
||||
P = Mat4.ortho(data.raw.ortho[0], data.raw.ortho[1], data.raw.ortho[2], data.raw.ortho[3], data.raw.near_plane, data.raw.far_plane);
|
||||
var left = data.raw.ortho[0];
|
||||
var right = data.raw.ortho[1];
|
||||
var bottom = data.raw.ortho[2];
|
||||
var top = data.raw.ortho[3];
|
||||
if (data.raw.aspect != null) {
|
||||
var aspect: kha.FastFloat = data.raw.aspect;
|
||||
if (screenAspect == null) screenAspect = iron.App.w() / iron.App.h();
|
||||
var scale = aspect / screenAspect;
|
||||
var cx = (left + right) * 0.5;
|
||||
var hw = (right - left) * 0.5 * scale;
|
||||
left = cx - hw;
|
||||
right = cx + hw;
|
||||
}
|
||||
P = Mat4.ortho(left, right, bottom, top, data.raw.near_plane, data.raw.far_plane);
|
||||
}
|
||||
else {
|
||||
if (screenAspect == null) screenAspect = iron.App.w() / iron.App.h();
|
||||
@ -247,25 +260,3 @@ class CameraObject extends Object {
|
||||
return new Vec4(-transform.world._20, -transform.world._21, -transform.world._22);
|
||||
}
|
||||
}
|
||||
|
||||
class FrustumPlane {
|
||||
public var normal = new Vec4(1.0, 0.0, 0.0);
|
||||
public var constant = 0.0;
|
||||
|
||||
public function new() {}
|
||||
|
||||
public function normalize() {
|
||||
var inverseNormalLength = 1.0 / normal.length();
|
||||
normal.mult(inverseNormalLength);
|
||||
constant *= inverseNormalLength;
|
||||
}
|
||||
|
||||
public function distanceToSphere(sphereCenter: Vec4, sphereRadius: Float): Float {
|
||||
return (normal.dot(sphereCenter) + constant) - sphereRadius;
|
||||
}
|
||||
|
||||
public inline function setComponents(x: Float, y: Float, z: Float, w: Float) {
|
||||
normal.set(x, y, z);
|
||||
constant = w;
|
||||
}
|
||||
}
|
||||
|
||||
25
leenkx/Sources/iron/object/FrustumPlane.hx
Normal file
25
leenkx/Sources/iron/object/FrustumPlane.hx
Normal file
@ -0,0 +1,25 @@
|
||||
package iron.object;
|
||||
|
||||
import iron.math.Vec4;
|
||||
|
||||
class FrustumPlane {
|
||||
public var normal = new Vec4(1.0, 0.0, 0.0);
|
||||
public var constant = 0.0;
|
||||
|
||||
public function new() {}
|
||||
|
||||
public function normalize() {
|
||||
var inverseNormalLength = 1.0 / normal.length();
|
||||
normal.mult(inverseNormalLength);
|
||||
constant *= inverseNormalLength;
|
||||
}
|
||||
|
||||
public function distanceToSphere(sphereCenter: Vec4, sphereRadius: Float): Float {
|
||||
return (normal.dot(sphereCenter) + constant) - sphereRadius;
|
||||
}
|
||||
|
||||
public inline function setComponents(x: Float, y: Float, z: Float, w: Float) {
|
||||
normal.set(x, y, z);
|
||||
constant = w;
|
||||
}
|
||||
}
|
||||
@ -34,7 +34,7 @@ class LightObject extends Object {
|
||||
// Cascades
|
||||
public static var cascadeCount = 1;
|
||||
public static var cascadeSplitFactor = 0.8;
|
||||
public static var cascadeBounds = 1.0;
|
||||
public static var cascadeBounds = 2.0; // occlusion culling shadows pop with 1.0
|
||||
#if lnx_csm
|
||||
var cascadeData: Float32Array = null;
|
||||
var cascadeVP: Array<Mat4>;
|
||||
@ -140,6 +140,9 @@ class LightObject extends Object {
|
||||
}
|
||||
#end
|
||||
#end
|
||||
#if lnx_occlusion_culling
|
||||
if (iron.culling.CullSystem.active != null) iron.culling.CullSystem.active.removeLight(this);
|
||||
#end
|
||||
super.remove();
|
||||
}
|
||||
|
||||
@ -278,12 +281,13 @@ class LightObject extends Object {
|
||||
var worldPerTexelX = (maxx - minx) / smsize;
|
||||
var worldPerTexelY = (maxy - miny) / smsize;
|
||||
var worldPerTexelZ = (maxz - minz) / smsize;
|
||||
var ex = maxx - minx, ey = maxy - miny, ez = maxz - minz;
|
||||
minx = Math.floor(minx / worldPerTexelX) * worldPerTexelX;
|
||||
miny = Math.floor(miny / worldPerTexelY) * worldPerTexelY;
|
||||
minz = Math.floor(minz / worldPerTexelZ) * worldPerTexelZ;
|
||||
maxx = Math.floor(maxx / worldPerTexelX) * worldPerTexelX;
|
||||
maxy = Math.floor(maxy / worldPerTexelY) * worldPerTexelY;
|
||||
maxz = Math.floor(maxz / worldPerTexelZ) * worldPerTexelZ;
|
||||
maxx = minx + ex;
|
||||
maxy = miny + ey;
|
||||
maxz = minz + ez;
|
||||
|
||||
var hx = (maxx - minx) / 2;
|
||||
var hy = (maxy - miny) / 2;
|
||||
|
||||
@ -30,9 +30,21 @@ class MeshObject extends Object {
|
||||
public var frustumCulling = true;
|
||||
public var activeTilesheet: Tilesheet = null;
|
||||
public var tilesheets: Array<Tilesheet> = null;
|
||||
#if lnx_occlusion_culling
|
||||
public var occlusionVisible = true;
|
||||
public var shadowOcclusionVisible = true;
|
||||
#end
|
||||
var shadowCameraCulled: Bool = false;
|
||||
var shadowCameraCullFrame: Int = -1;
|
||||
var shadowCameraCullCamera: CameraObject = null;
|
||||
var shadowCameraCullLight: LightObject = null;
|
||||
public var skip_context: String = null; // Do not draw this context
|
||||
public var force_context: String = null; // Draw only this context
|
||||
static var lastPipeline: PipelineState = null;
|
||||
|
||||
var cachedMats: Map<String, Vector<MaterialData>> = null;
|
||||
var cachedMaterialContexts: Map<String, Array<MaterialContext>> = null;
|
||||
var cachedShaderContexts: Map<String, Array<ShaderContext>> = null;
|
||||
#if lnx_morph_target
|
||||
public var morphTarget: MorphTarget = null;
|
||||
#end
|
||||
@ -48,6 +60,9 @@ class MeshObject extends Object {
|
||||
this.materials = materials;
|
||||
setData(data);
|
||||
Scene.active.meshes.push(this);
|
||||
#if lnx_occlusion_culling
|
||||
if (iron.culling.CullSystem.active != null) iron.culling.CullSystem.active.markDirty();
|
||||
#end
|
||||
}
|
||||
|
||||
public function setData(data: MeshData) {
|
||||
@ -96,6 +111,9 @@ class MeshObject extends Object {
|
||||
if (activeTilesheet != null) activeTilesheet.remove();
|
||||
if (tilesheets != null) { for (ts in tilesheets) { ts.remove(); } tilesheets = null; }
|
||||
if (Scene.active != null) Scene.active.meshes.remove(this);
|
||||
#if lnx_occlusion_culling
|
||||
if (iron.culling.CullSystem.active != null) iron.culling.CullSystem.active.markDirty();
|
||||
#end
|
||||
#if (rp_renderer == "Deferred")
|
||||
if (Scene.active != null) Scene.active.markMaterialParamsDirty();
|
||||
#end
|
||||
@ -196,7 +214,7 @@ class MeshObject extends Object {
|
||||
var isShadow = context == "shadowmap";
|
||||
if (camera == null) return false;
|
||||
|
||||
if (camera.data.raw.frustum_culling && frustumCulling) {
|
||||
if (camera.data.raw.frustum_culling && frustumCulling && RenderPath.active != null && RenderPath.active.frustumCulling) {
|
||||
// Scale radius for skinned mesh and particle system
|
||||
// TODO: define skin & particle bounds
|
||||
var radiusScale = data.isSkinned ? 2.0 : 1.0;
|
||||
@ -211,7 +229,8 @@ class MeshObject extends Object {
|
||||
var frustumPlanes = isShadow ? light.frustumPlanes : camera.frustumPlanes;
|
||||
|
||||
if (isShadow && light.data.raw.type != "sun") { // Non-sun light bounds intersect camera frustum
|
||||
light.transform.radius = light.data.raw.far_plane;
|
||||
var farPlane: kha.FastFloat = light.data.raw.far_plane;
|
||||
light.transform.radius = farPlane * 1.25;
|
||||
if (!CameraObject.sphereInFrustum(camera.frustumPlanes, light.transform)) {
|
||||
return setCulled(isShadow, true);
|
||||
}
|
||||
@ -220,6 +239,37 @@ class MeshObject extends Object {
|
||||
if (!CameraObject.sphereInFrustum(frustumPlanes, transform, radiusScale)) {
|
||||
return setCulled(isShadow, true);
|
||||
}
|
||||
|
||||
if (isShadow && light.data.raw.type == "sun") {
|
||||
var frame = RenderPath.active.frame;
|
||||
if (shadowCameraCullFrame == frame && shadowCameraCullCamera == camera &&
|
||||
shadowCameraCullLight == light) {
|
||||
if (shadowCameraCulled) return setCulled(isShadow, true);
|
||||
} else {
|
||||
shadowCameraCullFrame = frame;
|
||||
shadowCameraCullCamera = camera;
|
||||
shadowCameraCullLight = light;
|
||||
shadowCameraCulled = false;
|
||||
var ldx = -light.V._02;
|
||||
var ldy = -light.V._12;
|
||||
var ldz = -light.V._22;
|
||||
var radius = transform.radius * radiusScale;
|
||||
var cx = transform.worldx();
|
||||
var cy = transform.worldy();
|
||||
var cz = transform.worldz();
|
||||
var range = camera.data.raw.far_plane * (LightObject.cascadeBounds * 4.0);
|
||||
var margin = radius * 0.1;
|
||||
for (plane in camera.frustumPlanes) {
|
||||
var reach = plane.normal.x * cx + plane.normal.y * cy + plane.normal.z * cz + plane.constant + radius + margin;
|
||||
var dot = plane.normal.x * ldx + plane.normal.y * ldy + plane.normal.z * ldz;
|
||||
if (dot > 0) reach += dot * range;
|
||||
if (reach < 0) {
|
||||
shadowCameraCulled = true;
|
||||
return setCulled(isShadow, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//culled = false;
|
||||
@ -253,6 +303,12 @@ class MeshObject extends Object {
|
||||
}
|
||||
}
|
||||
|
||||
static function matsEqual(a: Vector<MaterialData>, b: Vector<MaterialData>): Bool {
|
||||
if (a == null || b == null || a.length != b.length) return false;
|
||||
for (i in 0...a.length) if (a[i] != b[i]) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
public function render(g: Graphics, context: String, bindParams: Array<String>) {
|
||||
if (data == null || !data.geom.ready) return; // Data not yet streamed
|
||||
if (!visible) return; // Skip render if object is hidden
|
||||
@ -293,6 +349,7 @@ class MeshObject extends Object {
|
||||
// Get lod
|
||||
var mats = materials;
|
||||
var lod = this;
|
||||
var isShadowCtx = context == "shadowmap" || context == "shadowmap_transparent";
|
||||
if (raw != null && raw.lods != null && raw.lods.length > 0) {
|
||||
computeScreenSize(Scene.active.camera);
|
||||
initLods();
|
||||
@ -317,13 +374,29 @@ class MeshObject extends Object {
|
||||
if (isLodMaterial() && !validContext(mats, context)) return;
|
||||
|
||||
// Get context
|
||||
var materialContexts: Array<MaterialContext> = [];
|
||||
var shaderContexts: Array<ShaderContext> = [];
|
||||
getContexts(context, mats, materialContexts, shaderContexts);
|
||||
var materialContexts: Array<MaterialContext> = null;
|
||||
var shaderContexts: Array<ShaderContext> = null;
|
||||
if (cachedMaterialContexts != null && matsEqual(cachedMats.get(context), mats)) {
|
||||
materialContexts = cachedMaterialContexts.get(context);
|
||||
shaderContexts = cachedShaderContexts.get(context);
|
||||
}
|
||||
if (materialContexts == null) {
|
||||
materialContexts = [];
|
||||
shaderContexts = [];
|
||||
getContexts(context, mats, materialContexts, shaderContexts);
|
||||
if (cachedMaterialContexts == null) {
|
||||
cachedMats = new Map();
|
||||
cachedMaterialContexts = new Map();
|
||||
cachedShaderContexts = new Map();
|
||||
}
|
||||
cachedMats.set(context, mats);
|
||||
cachedMaterialContexts.set(context, materialContexts);
|
||||
cachedShaderContexts.set(context, shaderContexts);
|
||||
}
|
||||
|
||||
Uniforms.posUnpack = data.scalePos;
|
||||
Uniforms.texUnpack = data.scaleTex;
|
||||
transform.update();
|
||||
if (!isShadowCtx) transform.update();
|
||||
|
||||
// Render mesh
|
||||
var ldata = lod.data;
|
||||
|
||||
@ -166,6 +166,7 @@ class LnxPack {
|
||||
case "anim": TAnimation;
|
||||
case "tracks": TTrack;
|
||||
case "morph_target": TMorphTarget;
|
||||
case "occluder_proxy": TOccluderProxy;
|
||||
case "vertex_groups": TVertex_groups;
|
||||
case "tilesheet": TTilesheetData;
|
||||
case "events": TTilesheetEvent;
|
||||
|
||||
@ -933,26 +933,6 @@ class RenderPathDeferred {
|
||||
}
|
||||
#end
|
||||
|
||||
#if rp_water
|
||||
{
|
||||
#if (!kha_opengl)
|
||||
path.setDepthFrom("tex", "gbuffer1");
|
||||
#end
|
||||
|
||||
path.setTarget("buf");
|
||||
path.bindTarget("tex", "tex");
|
||||
path.drawShader("shader_datas/copy_pass/copy_pass");
|
||||
path.setTarget("tex");
|
||||
path.bindTarget("_main", "gbufferD");
|
||||
path.bindTarget("buf", "tex");
|
||||
path.drawShader("shader_datas/water_pass/water_pass");
|
||||
|
||||
#if (!kha_opengl)
|
||||
path.setDepthFrom("tex", "gbuffer0");
|
||||
#end
|
||||
}
|
||||
#end
|
||||
|
||||
#if rp_ssrefr
|
||||
{
|
||||
if (leenkx.data.Config.raw.rp_ssrefr != false)
|
||||
@ -1027,6 +1007,26 @@ class RenderPathDeferred {
|
||||
}
|
||||
#end
|
||||
|
||||
#if rp_water
|
||||
{
|
||||
#if (!kha_opengl)
|
||||
path.setDepthFrom("tex", "gbuffer1");
|
||||
#end
|
||||
|
||||
path.setTarget("buf");
|
||||
path.bindTarget("tex", "tex");
|
||||
path.drawShader("shader_datas/copy_pass/copy_pass");
|
||||
path.setTarget("tex");
|
||||
path.bindTarget("_main", "gbufferD");
|
||||
path.bindTarget("buf", "tex");
|
||||
path.drawShader("shader_datas/water_pass/water_pass");
|
||||
|
||||
#if (!kha_opengl)
|
||||
path.setDepthFrom("tex", "gbuffer0");
|
||||
#end
|
||||
}
|
||||
#end
|
||||
|
||||
#if rp_ssr
|
||||
{
|
||||
if (leenkx.data.Config.raw.rp_ssr != false) {
|
||||
|
||||
@ -1133,6 +1133,11 @@ class LeenkxExporter:
|
||||
}
|
||||
out_object['properties'].append(out_property)
|
||||
|
||||
if bobject.lnx_is_occluder:
|
||||
if 'properties' not in out_object:
|
||||
out_object['properties'] = []
|
||||
out_object['properties'].append({'name': 'occluder', 'value': True})
|
||||
|
||||
|
||||
# Export the object reference and material references
|
||||
objref = bobject.data
|
||||
@ -2125,7 +2130,8 @@ class LeenkxExporter:
|
||||
fp = self.get_meshes_file_path('mesh_' + oid, compressed=LeenkxExporter.compress_enabled)
|
||||
assets.add(fp)
|
||||
# No export necessary
|
||||
if bobject.data.lnx_cached and os.path.exists(fp) or fp in self.build_cache.exported_mesh_files:
|
||||
fp_json = fp.split('.lnx')[0] + '.json'
|
||||
if bobject.data.lnx_cached and (os.path.exists(fp) or os.path.exists(fp_json)) or fp in self.build_cache.exported_mesh_files:
|
||||
return
|
||||
|
||||
# Mesh users have different modifier stack
|
||||
@ -2202,6 +2208,22 @@ class LeenkxExporter:
|
||||
if armature:
|
||||
self.export_skin(bobject, armature, export_mesh, out_mesh)
|
||||
|
||||
rpdat = lnx.utils.get_rp()
|
||||
is_occluder = bobject.lnx_is_occluder
|
||||
if not is_occluder:
|
||||
for prop in bobject.lnx_propertylist:
|
||||
if prop.name_prop == 'occluder' and getattr(prop, prop.type_prop + '_prop') == True:
|
||||
is_occluder = True
|
||||
break
|
||||
use_global = False
|
||||
if not is_occluder and rpdat is not None and rpdat.lnx_occlusion_all_objects:
|
||||
is_occluder = rpdat.lnx_occlusion_min_size <= 0.0 or \
|
||||
max(bobject.dimensions) >= rpdat.lnx_occlusion_min_size
|
||||
use_global = is_occluder
|
||||
if is_occluder and rpdat is not None and rpdat.lnx_occlusion_culling:
|
||||
self.export_occluder_proxy(export_mesh, out_mesh, bobject, use_global)
|
||||
|
||||
|
||||
# Restore the morph state after mesh export
|
||||
if shape_keys:
|
||||
bobject.active_shape_key_index = active_shape_key_index
|
||||
@ -2311,6 +2333,139 @@ class LeenkxExporter:
|
||||
|
||||
|
||||
|
||||
def export_occluder_proxy(self, export_mesh: bpy.types.Mesh, out_mesh, bobject, use_global=False):
|
||||
rpdat = lnx.utils.get_rp()
|
||||
mode = bobject.lnx_occluder_mode
|
||||
shrink = bobject.lnx_occluder_shrink
|
||||
if use_global or mode == 'default':
|
||||
mode = rpdat.lnx_occlusion_proxy_mode
|
||||
if use_global or shrink < 0.0:
|
||||
shrink = rpdat.lnx_occlusion_proxy_shrink
|
||||
|
||||
if mode == 'box':
|
||||
proxy_positions, proxy_indices = self._box_proxy(export_mesh)
|
||||
self._apply_inward_offset(proxy_positions, shrink)
|
||||
elif mode == 'convex':
|
||||
proxy_positions, proxy_indices = self._convex_proxy(export_mesh)
|
||||
self._apply_inward_offset(proxy_positions, shrink)
|
||||
else:
|
||||
proxy_positions, proxy_indices = self._apply_decimate_to_mesh(
|
||||
export_mesh, rpdat, shrink)
|
||||
|
||||
if len(proxy_indices) == 0:
|
||||
return
|
||||
|
||||
out_mesh['occluder_proxy'] = {
|
||||
'positions': proxy_positions,
|
||||
'indices': proxy_indices
|
||||
}
|
||||
|
||||
def _box_proxy(self, export_mesh: bpy.types.Mesh):
|
||||
"""12-triangle axis-aligned bounding box proxy in local space."""
|
||||
if len(export_mesh.vertices) == 0:
|
||||
return [], []
|
||||
xs = [v.co.x for v in export_mesh.vertices]
|
||||
ys = [v.co.y for v in export_mesh.vertices]
|
||||
zs = [v.co.z for v in export_mesh.vertices]
|
||||
lo = (min(xs), min(ys), min(zs))
|
||||
hi = (max(xs), max(ys), max(zs))
|
||||
proxy_positions = [
|
||||
lo[0], lo[1], lo[2], hi[0], lo[1], lo[2],
|
||||
hi[0], hi[1], lo[2], lo[0], hi[1], lo[2],
|
||||
lo[0], lo[1], hi[2], hi[0], lo[1], hi[2],
|
||||
hi[0], hi[1], hi[2], lo[0], hi[1], hi[2]]
|
||||
proxy_indices = [
|
||||
0, 2, 1, 0, 3, 2,
|
||||
4, 5, 6, 4, 6, 7,
|
||||
0, 1, 5, 0, 5, 4,
|
||||
2, 3, 7, 2, 7, 6,
|
||||
1, 2, 6, 1, 6, 5,
|
||||
0, 4, 7, 0, 7, 3]
|
||||
return proxy_positions, proxy_indices
|
||||
|
||||
def _convex_proxy(self, export_mesh: bpy.types.Mesh):
|
||||
proxy_positions = []
|
||||
proxy_indices = []
|
||||
bm = bmesh.new()
|
||||
try:
|
||||
bm.from_mesh(export_mesh)
|
||||
bmesh.ops.convex_hull(bm, input=bm.verts)
|
||||
remap = {}
|
||||
for f in bm.faces:
|
||||
fv = f.verts
|
||||
for i in range(1, len(fv) - 1):
|
||||
for v in (fv[0], fv[i], fv[i + 1]):
|
||||
idx = remap.get(v.index)
|
||||
if idx is None:
|
||||
idx = len(remap)
|
||||
remap[v.index] = idx
|
||||
co = v.co
|
||||
proxy_positions.append(co.x)
|
||||
proxy_positions.append(co.y)
|
||||
proxy_positions.append(co.z)
|
||||
proxy_indices.append(idx)
|
||||
finally:
|
||||
bm.free()
|
||||
return proxy_positions, proxy_indices
|
||||
|
||||
def _apply_inward_offset(self, positions, shrink_pct):
|
||||
n = len(positions) // 3
|
||||
if n == 0 or shrink_pct <= 0.0:
|
||||
return
|
||||
factor = 1.0 - shrink_pct * 0.01
|
||||
cx = sum(positions[0::3]) / n
|
||||
cy = sum(positions[1::3]) / n
|
||||
cz = sum(positions[2::3]) / n
|
||||
for i in range(n):
|
||||
positions[i * 3] = cx + (positions[i * 3] - cx) * factor
|
||||
positions[i * 3 + 1] = cy + (positions[i * 3 + 1] - cy) * factor
|
||||
positions[i * 3 + 2] = cz + (positions[i * 3 + 2] - cz) * factor
|
||||
|
||||
def _apply_decimate_to_mesh(self, export_mesh: bpy.types.Mesh, rpdat, shrink):
|
||||
temp_mesh = export_mesh.copy()
|
||||
temp_obj = bpy.data.objects.new('lnx_decimate_temp', temp_mesh)
|
||||
try:
|
||||
self.scene.collection.objects.link(temp_obj)
|
||||
|
||||
mod = temp_obj.modifiers.new(name='lnx_decimate', type='DECIMATE')
|
||||
mod.decimate_type = rpdat.lnx_occlusion_decimate_mode
|
||||
if rpdat.lnx_occlusion_decimate_mode == 'COLLAPSE':
|
||||
mod.ratio = rpdat.lnx_occlusion_decimate_ratio
|
||||
elif rpdat.lnx_occlusion_decimate_mode == 'DISSOLVE':
|
||||
mod.angle_limit = math.radians(
|
||||
rpdat.lnx_occlusion_decimate_ratio * 180)
|
||||
elif rpdat.lnx_occlusion_decimate_mode == 'UNSUBDIV':
|
||||
mod.iterations = max(1, int(rpdat.lnx_occlusion_decimate_ratio * 10))
|
||||
|
||||
self.depsgraph.update()
|
||||
eval_obj = temp_obj.evaluated_get(self.depsgraph)
|
||||
eval_mesh = eval_obj.to_mesh(
|
||||
preserve_all_data_layers=False, depsgraph=self.depsgraph)
|
||||
try:
|
||||
eval_mesh.calc_loop_triangles()
|
||||
|
||||
proxy_positions = []
|
||||
for v in eval_mesh.vertices:
|
||||
co = v.co
|
||||
proxy_positions.append(co.x)
|
||||
proxy_positions.append(co.y)
|
||||
proxy_positions.append(co.z)
|
||||
|
||||
proxy_indices = []
|
||||
for tri in eval_mesh.loop_triangles:
|
||||
proxy_indices.append(tri.vertices[0])
|
||||
proxy_indices.append(tri.vertices[1])
|
||||
proxy_indices.append(tri.vertices[2])
|
||||
|
||||
self._apply_inward_offset(proxy_positions, shrink)
|
||||
finally:
|
||||
eval_obj.to_mesh_clear()
|
||||
finally:
|
||||
bpy.data.objects.remove(temp_obj, do_unlink=True)
|
||||
bpy.data.meshes.remove(temp_mesh, do_unlink=True)
|
||||
|
||||
return proxy_positions, proxy_indices
|
||||
|
||||
def export_light(self, object_ref):
|
||||
"""Exports a single light object."""
|
||||
rpdat = lnx.utils.get_rp()
|
||||
@ -2496,23 +2651,57 @@ class LeenkxExporter:
|
||||
b = proj[1][1]
|
||||
c = proj[2][2]
|
||||
d = proj[2][3]
|
||||
k = (c - 1.0) / (c + 1.0)
|
||||
o['fov'] = 2.0 * math.atan(1.0 / b)
|
||||
if abs(a) > 1e-6:
|
||||
o['aspect'] = b / a
|
||||
if with_planes:
|
||||
o['near_plane'] = (d * (1.0 - k)) / (2.0 * k)
|
||||
o['far_plane'] = k * o['near_plane']
|
||||
denom = c + 1.0
|
||||
k = (c - 1.0) / denom if abs(denom) >= 1e-6 else 0.0
|
||||
if abs(k) < 1e-6:
|
||||
o['near_plane'] = 0.1
|
||||
o['far_plane'] = 1000.0
|
||||
else:
|
||||
near = (d * (1.0 - k)) / (2.0 * k)
|
||||
far = k * near
|
||||
o['near_plane'] = max(0.001, min(near, 100000.0))
|
||||
o['far_plane'] = max(0.001, min(far, 100000.0))
|
||||
|
||||
@staticmethod
|
||||
def extract_ortho(o, proj):
|
||||
a = proj[0][0]
|
||||
b = proj[1][1]
|
||||
# left, right, bottom, top
|
||||
o['ortho'] = [-(1 + proj[3][0]) / proj[0][0], \
|
||||
(1 - proj[3][0]) / proj[0][0], \
|
||||
-(1 + proj[3][1]) / proj[1][1], \
|
||||
(1 - proj[3][1]) / proj[1][1]]
|
||||
o['ortho'] = [-(1 + proj[3][0]) / a, \
|
||||
(1 - proj[3][0]) / a, \
|
||||
-(1 + proj[3][1]) / b, \
|
||||
(1 - proj[3][1]) / b]
|
||||
o['near_plane'] = (1 + proj[3][2]) / proj[2][2]
|
||||
o['far_plane'] = -(1 - proj[3][2]) / proj[2][2]
|
||||
o['near_plane'] *= 2
|
||||
o['far_plane'] *= 2
|
||||
if abs(a) > 1e-6:
|
||||
o['aspect'] = b / a
|
||||
|
||||
@staticmethod
|
||||
def compute_effective_par(rpdat, render):
|
||||
if rpdat is None:
|
||||
return render.pixel_aspect_x, render.pixel_aspect_y
|
||||
mode = rpdat.lnx_aspect_mode
|
||||
if mode == 'blender':
|
||||
return render.pixel_aspect_x, render.pixel_aspect_y
|
||||
elif mode == 'square':
|
||||
return 1.0, 1.0
|
||||
elif mode == 'ntsc_43':
|
||||
return 0.889, 1.0
|
||||
elif mode == 'pal_43':
|
||||
return 1.067, 1.0
|
||||
elif mode == 'ntsc_169':
|
||||
return 1.212, 1.0
|
||||
elif mode == 'pal_169':
|
||||
return 1.459, 1.0
|
||||
elif mode == 'custom':
|
||||
return rpdat.lnx_aspect_custom, 1.0
|
||||
return render.pixel_aspect_x, render.pixel_aspect_y
|
||||
|
||||
def export_camera(self, objectRef):
|
||||
o = {}
|
||||
@ -2521,12 +2710,16 @@ class LeenkxExporter:
|
||||
|
||||
camera = objectRef[1]["objectTable"][0]
|
||||
render = self.scene.render
|
||||
rpdat = lnx.utils.get_rp()
|
||||
|
||||
par_x, par_y = self.compute_effective_par(rpdat, render)
|
||||
|
||||
proj = camera.calc_matrix_camera(
|
||||
self.depsgraph,
|
||||
x=render.resolution_x,
|
||||
y=render.resolution_y,
|
||||
scale_x=render.pixel_aspect_x,
|
||||
scale_y=render.pixel_aspect_y)
|
||||
scale_x=par_x,
|
||||
scale_y=par_y)
|
||||
if objref.type == 'PERSP':
|
||||
self.extract_projection(o, proj)
|
||||
else:
|
||||
@ -2594,7 +2787,7 @@ class LeenkxExporter:
|
||||
self.output['material_datas'].append(o)
|
||||
bpy.data.materials.remove(mat)
|
||||
rpdat = lnx.utils.get_rp()
|
||||
if not rpdat.lnx_culling:
|
||||
if not rpdat.lnx_backface_culling:
|
||||
o['override_context'] = {}
|
||||
o['override_context']['cull_mode'] = 'none'
|
||||
|
||||
@ -2673,7 +2866,7 @@ class LeenkxExporter:
|
||||
o['skip_context'] = material.lnx_skip_context
|
||||
|
||||
rpdat = lnx.utils.get_rp()
|
||||
if material.lnx_two_sided or not rpdat.lnx_culling:
|
||||
if material.lnx_two_sided or not rpdat.lnx_backface_culling:
|
||||
o['override_context'] = {}
|
||||
o['override_context']['cull_mode'] = 'none'
|
||||
elif material.lnx_cull_mode != 'clockwise':
|
||||
@ -3190,6 +3383,12 @@ class LeenkxExporter:
|
||||
'clear_color': self.get_camera_clear_color()
|
||||
}
|
||||
|
||||
render = self.scene.render
|
||||
rpdat = lnx.utils.get_rp()
|
||||
par_x, par_y = self.compute_effective_par(rpdat, render)
|
||||
res_aspect = render.resolution_x / render.resolution_y if render.resolution_y > 0 else 1.0
|
||||
effective_aspect = res_aspect * par_x / par_y
|
||||
|
||||
# Set viewport camera projection
|
||||
proj, is_persp = self.get_viewport_projection_matrix()
|
||||
if proj is not None:
|
||||
@ -3197,6 +3396,9 @@ class LeenkxExporter:
|
||||
self.extract_projection(out_camera, proj, with_planes=False)
|
||||
else:
|
||||
self.extract_ortho(out_camera, proj)
|
||||
|
||||
out_camera['aspect'] = effective_aspect
|
||||
|
||||
self.output['camera_datas'].append(out_camera)
|
||||
|
||||
out_object = {
|
||||
|
||||
@ -390,12 +390,14 @@ def send_event(event_id: str, opt_data: Any = None):
|
||||
elif event_id == 'cam_props':
|
||||
cam: bpy.types.Camera = obj_data.data
|
||||
render = bpy.context.scene.render
|
||||
rpdat = lnx.utils.get_rp()
|
||||
par_x, par_y = LeenkxExporter.compute_effective_par(rpdat, render)
|
||||
projection = obj_data.calc_matrix_camera(
|
||||
bpy.context.evaluated_depsgraph_get(),
|
||||
x=render.resolution_x,
|
||||
y=render.resolution_y,
|
||||
scale_x=render.pixel_aspect_x,
|
||||
scale_y=render.pixel_aspect_y)
|
||||
scale_x=par_x,
|
||||
scale_y=par_y)
|
||||
focal_y = projection[1][1]
|
||||
depth_z = projection[2][2]
|
||||
depth_w = projection[2][3]
|
||||
@ -403,8 +405,10 @@ def send_event(event_id: str, opt_data: Any = None):
|
||||
fov = 2.0 * math.atan(1.0 / focal_y)
|
||||
near = (depth_w * (1.0 - far_near_ratio)) / (2.0 * far_near_ratio)
|
||||
far = far_near_ratio * near
|
||||
res_aspect = render.resolution_x / render.resolution_y if render.resolution_y > 0 else 1.0
|
||||
effective_aspect = res_aspect * par_x / par_y
|
||||
cam_obj = f'iron.Scene.active.getCamera("{cam.name}")'
|
||||
js = f'var c = {cam_obj}; var cRaw = c.data.raw; cRaw.fov={fov}; cRaw.near_plane={near}; cRaw.far_plane={far}; cRaw.frustum_culling={"true" if cam.lnx_frustum_culling else "false"}; c.buildProjection();'
|
||||
js = f'var c = {cam_obj}; var cRaw = c.data.raw; cRaw.fov={fov}; cRaw.near_plane={near}; cRaw.far_plane={far}; cRaw.frustum_culling={"true" if cam.lnx_frustum_culling else "false"}; cRaw.aspect={effective_aspect}; c.buildProjection();'
|
||||
write_patch(js)
|
||||
|
||||
elif event_id == 'obj_visibility':
|
||||
|
||||
@ -190,6 +190,9 @@ def build():
|
||||
rpdat = lnx.utils.get_rp()
|
||||
project_path = lnx.utils.get_fp()
|
||||
|
||||
if rpdat.lnx_frustum_culling:
|
||||
assets.add_khafile_def('lnx_frustum_culling')
|
||||
|
||||
if rpdat.rp_driver != 'Leenkx' and lnx.api.drivers[rpdat.rp_driver]['make_rpath'] != None:
|
||||
lnx.api.drivers[rpdat.rp_driver]['make_rpath']()
|
||||
return
|
||||
@ -210,6 +213,9 @@ def build():
|
||||
if rpdat.rp_hdr:
|
||||
assets.add_khafile_def('rp_hdr')
|
||||
|
||||
if rpdat.lnx_occlusion_culling:
|
||||
assets.add_khafile_def('lnx_occlusion_culling')
|
||||
|
||||
assets.add_khafile_def('rp_renderer={0}'.format(rpdat.rp_renderer))
|
||||
if rpdat.rp_depthprepass:
|
||||
assets.add_khafile_def('rp_depthprepass')
|
||||
|
||||
@ -386,6 +386,10 @@ def parse_tex_sky(node: bpy.types.ShaderNodeTexSky, out_socket: bpy.types.NodeSo
|
||||
elif node.sky_type == 'MULTIPLE_SCATTERING':
|
||||
return parse_sky_multiple_scattering(node, state)
|
||||
|
||||
elif node.sky_type == '':
|
||||
state.curshader.add_uniform('vec3 sunDir', link='_sunPosition')
|
||||
return c.to_vec3([0.0, 0.0, 0.0])
|
||||
|
||||
else:
|
||||
log.error(f'Unsupported sky model: {node.sky_type}!')
|
||||
return c.to_vec3([0.0, 0.0, 0.0])
|
||||
|
||||
@ -425,6 +425,16 @@ def init_properties():
|
||||
bpy.types.Object.lnx_sorting_index = compatible_prop(IntProperty, name="Sorting Index", description="Sorting index for the Render's Draw Order", default=0, override={'LIBRARY_OVERRIDABLE'})
|
||||
bpy.types.Object.lnx_spawn = compatible_prop(BoolProperty, name="Spawn", description="Auto-add this object when creating scene", default=True, override={'LIBRARY_OVERRIDABLE'})
|
||||
bpy.types.Object.lnx_mobile = compatible_prop(BoolProperty, name="Mobile", description="Object moves during gameplay", default=False, override={'LIBRARY_OVERRIDABLE'})
|
||||
bpy.types.Object.lnx_is_occluder = compatible_prop(BoolProperty, name="Is Occluder", description="Export simplified occluder geometry for software occlusion culling", default=False, override={'LIBRARY_OVERRIDABLE'})
|
||||
bpy.types.Object.lnx_occluder_mode = compatible_prop(EnumProperty,
|
||||
items=[('convex', 'Convex Hull', 'Convex hull of the mesh - tight on convex shapes, over-covers concave ones'),
|
||||
('box', 'Box', 'Axis-aligned bounding box - fastest, 12 triangles'),
|
||||
('decimate', 'Decimate', 'Decimated mesh - closest fit, slowest export')],
|
||||
name="Occluder Mode",
|
||||
description="Proxy geometry used for occlusion culling",
|
||||
default='convex',
|
||||
override={'LIBRARY_OVERRIDABLE'})
|
||||
bpy.types.Object.lnx_occluder_shrink = compatible_prop(FloatProperty, name="Occluder Shrink %", description="Shrink proxy toward its center: 0 = full size, 50 = half size (default), 100 = point", default=50.0, min=0.0, max=100.0, override={'LIBRARY_OVERRIDABLE'})
|
||||
bpy.types.Object.lnx_visible = compatible_prop(BoolProperty, name="Visible", description="Render this object", default=True, override={'LIBRARY_OVERRIDABLE'})
|
||||
bpy.types.Object.lnx_visible_shadow = compatible_prop(BoolProperty, name="Lighting", description="Object contributes to the lighting even if invisible", default=True, override={'LIBRARY_OVERRIDABLE'})
|
||||
bpy.types.Object.lnx_soft_body_margin = FloatProperty(name="Soft Body Margin", description="Collision margin", default=0.04)
|
||||
|
||||
@ -232,6 +232,15 @@ def update_renderpath(self, context):
|
||||
assets.invalidate_shader_cache(self, context)
|
||||
bpy.data.worlds['Lnx'].lnx_recompile = True
|
||||
|
||||
def update_occlusion_culling(self, context):
|
||||
for m in bpy.data.meshes:
|
||||
m.lnx_cached = False
|
||||
for c in bpy.data.curves:
|
||||
c.lnx_cached = False
|
||||
for c in bpy.data.metaballs:
|
||||
c.lnx_cached = False
|
||||
update_renderpath(self, context)
|
||||
|
||||
def udpate_shadowmap_cascades(self, context):
|
||||
bpy.data.worlds['Lnx'].lnx_recompile = True
|
||||
update_renderpath(self, context)
|
||||
@ -461,6 +470,7 @@ class LnxRPListItem(bpy.types.PropertyGroup):
|
||||
('Distance', 'Distance', 'Distance'),
|
||||
('Index', 'Index', 'Index')],
|
||||
name='Draw Order', description='Sort objects', default='Auto', update=assets.invalidate_compiled_data)
|
||||
lnx_radix: BoolProperty(name="Radix Sort", description="Sort draw calls with the radix library instead of the comparison sort", default=True, update=update_renderpath)
|
||||
rp_depth_texture: BoolProperty(name="Depth Texture", description="Current render-path state", default=False)
|
||||
rp_depth_texture_state: EnumProperty(
|
||||
items=[('On', 'On', 'On'),
|
||||
@ -697,8 +707,48 @@ class LnxRPListItem(bpy.types.PropertyGroup):
|
||||
('CPU', 'CPU', 'CPU'),
|
||||
('Off', 'Off', 'Off')],
|
||||
name='Particles', description='Enable particle simulation', default='GPU', update=assets.invalidate_shader_cache)
|
||||
# Material override flags
|
||||
lnx_culling: BoolProperty(name="Culling", default=True)
|
||||
# Culling flags
|
||||
lnx_backface_culling: BoolProperty(name="Backface Culling", description="Cull back-facing triangles (winding-based)", default=True)
|
||||
lnx_frustum_culling: BoolProperty(name="Frustum Culling", description="Cull objects outside camera frustum", default=True)
|
||||
lnx_occlusion_culling: BoolProperty(name="Object Occlusion Culling (Experimental)", description="Software occlusion culling using Hi-Z buffer", default=False, update=update_occlusion_culling)
|
||||
lnx_occlusion_all_objects: BoolProperty(name="All Objects Occlude", description="Every mesh can occlude - ignores the per-object 'Is Occluder' flag", default=False, update=update_occlusion_culling)
|
||||
lnx_occlusion_min_size: FloatProperty(name="Min Occluder Size", description="Objects whose largest bounding-box side is smaller than this (meters) never export as occluders", default=0.5, min=0.0, update=update_occlusion_culling)
|
||||
lnx_occlusion_proxy_mode: EnumProperty(
|
||||
name="Proxy Mode",
|
||||
description="Occluder proxy geometry type",
|
||||
items=[
|
||||
('box', 'Box', 'Axis-aligned bounding box - fastest, 12 triangles'),
|
||||
('convex', 'Convex Hull', 'Convex hull of the mesh - tight on convex shapes, over-covers concave ones'),
|
||||
('decimate', 'Decimate', 'Decimated mesh - closest fit, slowest export'),
|
||||
],
|
||||
default='convex', update=update_occlusion_culling)
|
||||
lnx_occlusion_proxy_shrink: FloatProperty(name="Proxy Shrink %", description="Shrink proxy toward its center: 0 = full size, 50 = half size (default), 100 = point - keeps occluders conservative", default=50.0, min=0.0, max=100.0, precision=1, update=update_occlusion_culling)
|
||||
lnx_occlusion_box_shrink: IntProperty(name="Box Shrink %", description="0 = full AABB (aggressive), 50 = half-size (default, balanced), 100 = point (no occlusion)", default=50, min=0, max=100, update=update_renderpath)
|
||||
lnx_occlusion_depth_eps: IntProperty(name="Depth Epsilon", description="6 = 1e-6 (default, tight), higher = more tolerant (less aggressive culling), lower = stricter", default=6, min=1, max=10, update=update_renderpath)
|
||||
lnx_occlusion_decimate_ratio: FloatProperty(name="Ratio", description="Proportion of edges to collapse (COLLAPSE) or angle threshold in degrees (DISSOLVE) or iterations (UNSUBDIV)", default=0.5, min=0.01, max=1.0, precision=3, update=update_occlusion_culling)
|
||||
lnx_occlusion_decimate_mode: EnumProperty(
|
||||
name="Decimate Mode",
|
||||
description="Decimate method applied to occluder proxy geometry",
|
||||
items=[
|
||||
('COLLAPSE', 'Collapse', 'Collapse edges to reduce triangle count by ratio'),
|
||||
('DISSOLVE', 'Dissolve', 'Dissolve vertices with angle below threshold (ratio = degrees)'),
|
||||
('UNSUBDIV', 'Unsubdivide', 'Reverse subdivision (ratio = iterations, 0.1 = 1 iter, 1.0 = 10 iter)'),
|
||||
],
|
||||
default='COLLAPSE', update=update_occlusion_culling)
|
||||
lnx_aspect_mode: EnumProperty(
|
||||
name="Aspect Ratio",
|
||||
description="Pixel aspect ratio override for camera projection. 'Blender' uses the scene's resolution and pixel aspect settings",
|
||||
items=[
|
||||
('blender', 'Blender', 'Use Blender\'s Output Properties resolution and pixel aspect ratio'),
|
||||
('square', 'Square (1.0)', 'Square pixels (1:1)'),
|
||||
('ntsc_43', 'NTSC 4:3', 'NTSC pixel aspect ratio (0.889)'),
|
||||
('pal_43', 'PAL 4:3', 'PAL pixel aspect ratio (1.067)'),
|
||||
('ntsc_169', 'NTSC 16:9', 'NTSC widescreen pixel aspect ratio (1.212)'),
|
||||
('pal_169', 'PAL 16:9', 'PAL widescreen pixel aspect ratio (1.459)'),
|
||||
('custom', 'Custom', 'Custom pixel aspect ratio'),
|
||||
],
|
||||
default='blender')
|
||||
lnx_aspect_custom: FloatProperty(name="Custom PAR", description="Custom pixel aspect ratio (width/height of a single pixel)", default=1.0, min=0.01, max=10.0, precision=3)
|
||||
lnx_two_sided_area_light: BoolProperty(name="Two-Sided Area Light", description="Emit light from both faces of area plane", default=False, update=assets.invalidate_shader_cache)
|
||||
|
||||
@staticmethod
|
||||
|
||||
@ -112,6 +112,10 @@ class LNX_PT_ObjectPropsPanel(bpy.types.Panel):
|
||||
sub.prop(obj, 'lnx_visible_shadow')
|
||||
|
||||
if obj.type == 'MESH':
|
||||
layout.prop(obj, 'lnx_is_occluder')
|
||||
if obj.lnx_is_occluder:
|
||||
layout.prop(obj, 'lnx_occluder_mode')
|
||||
layout.prop(obj, 'lnx_occluder_shrink')
|
||||
layout.prop(obj, 'lnx_instanced')
|
||||
if 'Rot' in obj.lnx_instanced:
|
||||
layout.prop(obj, 'lnx_instanced_rot_type')
|
||||
@ -1843,6 +1847,7 @@ class LNX_PT_RenderPathRendererPanel(bpy.types.Panel):
|
||||
layout.prop(rpdat, 'rp_blending_state')
|
||||
layout.prop(rpdat, 'rp_depth_texture_state')
|
||||
layout.prop(rpdat, 'rp_draw_order')
|
||||
layout.prop(rpdat, 'lnx_radix')
|
||||
layout.prop(rpdat, 'lnx_samples_per_pixel')
|
||||
layout.prop(rpdat, 'lnx_texture_filter')
|
||||
layout.prop(rpdat, 'rp_sss_state')
|
||||
@ -1878,7 +1883,23 @@ class LNX_PT_RenderPathRendererPanel(bpy.types.Panel):
|
||||
col = layout.column()
|
||||
col.prop(rpdat, "rp_hdr")
|
||||
col.prop(rpdat, "rp_stereo")
|
||||
col.prop(rpdat, 'lnx_culling')
|
||||
col.prop(rpdat, 'lnx_backface_culling')
|
||||
col.prop(rpdat, 'lnx_frustum_culling')
|
||||
col.prop(rpdat, 'lnx_occlusion_culling')
|
||||
if rpdat.lnx_occlusion_culling:
|
||||
col.prop(rpdat, 'lnx_occlusion_all_objects')
|
||||
if rpdat.lnx_occlusion_all_objects:
|
||||
col.prop(rpdat, 'lnx_occlusion_min_size')
|
||||
col.prop(rpdat, 'lnx_occlusion_proxy_mode')
|
||||
if rpdat.lnx_occlusion_proxy_mode == 'decimate':
|
||||
col.prop(rpdat, 'lnx_occlusion_decimate_mode')
|
||||
col.prop(rpdat, 'lnx_occlusion_decimate_ratio')
|
||||
col.prop(rpdat, 'lnx_occlusion_proxy_shrink')
|
||||
col.prop(rpdat, 'lnx_occlusion_box_shrink')
|
||||
col.prop(rpdat, 'lnx_occlusion_depth_eps')
|
||||
col.prop(rpdat, 'lnx_aspect_mode')
|
||||
if rpdat.lnx_aspect_mode == 'custom':
|
||||
col.prop(rpdat, 'lnx_aspect_custom')
|
||||
col.prop(rpdat, 'rp_pp')
|
||||
|
||||
|
||||
|
||||
@ -249,6 +249,9 @@ let project = new Project('""" + lnx.utils.safesrc(wrd.lnx_project_name + '-' +
|
||||
if not os.path.exists(os.path.join('Libraries', 'lib/hscript')):
|
||||
khafile.write(add_leenkx_library(sdk_path, 'lib/hscript', rel_path=do_relpath_sdk))
|
||||
khafile.write("project.addDefine('" + 'hscript' + "');\n")
|
||||
if lnx.utils.get_rp().lnx_radix:
|
||||
if not os.path.exists(os.path.join('Libraries', 'lib/radix')):
|
||||
khafile.write(add_leenkx_library(sdk_path, 'lib/radix', rel_path=do_relpath_sdk))
|
||||
|
||||
# Project libraries
|
||||
if os.path.exists('Libraries'):
|
||||
@ -522,6 +525,9 @@ let project = new Project('""" + lnx.utils.safesrc(wrd.lnx_project_name + '-' +
|
||||
if rpdat.rp_draw_order == 'Index':
|
||||
assets.add_khafile_def('lnx_draworder_index')
|
||||
|
||||
if rpdat.lnx_radix:
|
||||
assets.add_khafile_def('lnx_radix')
|
||||
|
||||
if lnx.utils.get_viewport_controls() == 'azerty':
|
||||
assets.add_khafile_def('lnx_azerty')
|
||||
|
||||
@ -721,6 +727,11 @@ class Main {
|
||||
f.write("\n" + content_indent + "iron.object.LightObject.cascadeSplitFactor = " + str(rpdat.lnx_shadowmap_split) + ";")
|
||||
if rpdat.lnx_shadowmap_bounds != 1.0:
|
||||
f.write("\n" + content_indent + "iron.object.LightObject.cascadeBounds = " + str(rpdat.lnx_shadowmap_bounds) + ";")
|
||||
|
||||
if rpdat.lnx_occlusion_culling:
|
||||
depth_eps = max(10.0 ** -int(rpdat.lnx_occlusion_depth_eps), 1e-6)
|
||||
f.write("\n" + content_indent + "iron.culling.CullSystem.boxShrink = " + str(rpdat.lnx_occlusion_box_shrink / 100) + ";")
|
||||
f.write("\n" + content_indent + "iron.culling.CullSystem.depthEps = " + str(depth_eps) + ";")
|
||||
|
||||
asset_references = list(set(assets.assets))
|
||||
|
||||
|
||||
@ -123,7 +123,16 @@ def render_envmap(target_dir: str, world: bpy.types.World) -> str:
|
||||
render_path = os.path.join(target_dir, image_name)
|
||||
scene.render.filepath = render_path
|
||||
|
||||
bpy.ops.render.render(write_still=True, scene=scene.name)
|
||||
try:
|
||||
bpy.ops.render.render(write_still=True, scene=scene.name)
|
||||
except RuntimeError as e:
|
||||
log.warn(f'Environment map render failed ({e}), writing a grey image instead')
|
||||
img = bpy.data.images.new('_lnx_noworld', 64, 32)
|
||||
img.pixels = [0.05] * len(img.pixels)
|
||||
img.filepath_raw = render_path
|
||||
img.file_format = ENVMAP_FORMAT
|
||||
img.save()
|
||||
bpy.data.images.remove(img)
|
||||
|
||||
return image_name
|
||||
|
||||
|
||||
23
lib/radix/LICENSE_1_0.txt
Normal file
23
lib/radix/LICENSE_1_0.txt
Normal file
@ -0,0 +1,23 @@
|
||||
Boost Software License - Version 1.0 - August 17th, 2003
|
||||
|
||||
Permission is hereby granted, free of charge, to any person or organization
|
||||
obtaining a copy of the software and accompanying documentation covered by
|
||||
this license (the "Software") to use, reproduce, display, distribute,
|
||||
execute, and transmit the Software, and to prepare derivative works of the
|
||||
Software, and to permit third-parties to whom the Software is furnished to
|
||||
do so, all subject to the following:
|
||||
|
||||
The copyright notices in the Software and this entire statement, including
|
||||
the above license grant, this restriction and the following disclaimer,
|
||||
must be included in all copies of the Software, in whole or in part, and
|
||||
all derivative works of the Software, unless such copies or derivative
|
||||
works are solely in the form of machine-executable object code generated by
|
||||
a source language processor.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
|
||||
SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
|
||||
FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
|
||||
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
DEALINGS IN THE SOFTWARE.
|
||||
23
lib/radix/README.md
Normal file
23
lib/radix/README.md
Normal file
@ -0,0 +1,23 @@
|
||||
# Radix
|
||||
Haxe port: in-place MSD radix sort (American flag sort) with an insertion sort fallback for small sub-ranges implemented in the Leenkx SDK by Riccardo Mazzucco
|
||||
|
||||
Original reference source Malte Skarupke's [ska_sort](https://github.com/skarupke/ska_sort) - https://github.com/skarupke/ska_sort
|
||||
|
||||
## Usage
|
||||
|
||||
```haxe
|
||||
import radix.Radix;
|
||||
|
||||
// Stable sort: counting-sort scatter between `a` and `buffer`.
|
||||
// Equal keys keep input order - matches haxe Array.sort semantics.
|
||||
Radix.sortCopyKeyed(meshes, keys, meshesBuf, keysBuf);
|
||||
|
||||
// In-place MSD sort on the same (element, keys) arrays.
|
||||
// No scratch buffer needed, but NOT stable - equal keys may reorder.
|
||||
Radix.sortKeyed(meshes, keys);
|
||||
|
||||
// Or let the sorter build keys from an extractor
|
||||
Radix.sort(meshes, function(m) {
|
||||
return (m.depthRead ? 0x80000000 : 0) | (FPHelper.floatToI32(m.cameraDistance) >>> 1);
|
||||
});
|
||||
```
|
||||
350
lib/radix/Sources/radix/Radix.hx
Normal file
350
lib/radix/Sources/radix/Radix.hx
Normal file
@ -0,0 +1,350 @@
|
||||
package radix;
|
||||
|
||||
import haxe.ds.Vector;
|
||||
import haxe.io.FPHelper;
|
||||
|
||||
/**
|
||||
In-place MSD radix sort - Haxe port of Malte Skarupke's ska_sort
|
||||
(ref/ska_sort.hpp, Boost Software License 1.0). Original:
|
||||
https://github.com/skarupke/ska_sort
|
||||
**/
|
||||
|
||||
class Radix {
|
||||
|
||||
static inline var STD_SORT_THRESHOLD = 128;
|
||||
static inline var AMERICAN_FLAG_SORT_THRESHOLD = 1024;
|
||||
|
||||
public static inline function intToUnsigned(i:Int):Int {
|
||||
return i ^ 0x80000000;
|
||||
}
|
||||
|
||||
public static inline function floatToUnsigned(f:Float):Int {
|
||||
var u = FPHelper.floatToI32(f);
|
||||
var signBit = u >> 31;
|
||||
return u ^ (signBit | 0x80000000);
|
||||
}
|
||||
|
||||
public static inline function uLess(a:Int, b:Int):Bool {
|
||||
return (a ^ 0x80000000) < (b ^ 0x80000000);
|
||||
}
|
||||
|
||||
public static function sort<T>(a:Array<T>, extractKey:T->Int, ?keys:Array<Int>):Void {
|
||||
if (keys == null) keys = new Array<Int>();
|
||||
for (i in 0...a.length) keys[i] = extractKey(a[i]);
|
||||
sortKeyed(a, keys);
|
||||
}
|
||||
|
||||
public static function sortFloat<T>(a:Array<T>, extractKey:T->Float, ?keys:Array<Int>):Void {
|
||||
if (keys == null) keys = new Array<Int>();
|
||||
for (i in 0...a.length) keys[i] = floatToUnsigned(extractKey(a[i]));
|
||||
sortKeyed(a, keys);
|
||||
}
|
||||
|
||||
public static function sortKeyed<T>(a:Array<T>, keys:Array<Int>):Void {
|
||||
var n = a.length;
|
||||
if (stdSortIfLessThanThreshold(a, keys, 0, n)) return;
|
||||
msdSort(a, keys, 0, n, 24, new RadixScratch());
|
||||
}
|
||||
|
||||
static function msdSort<T>(a:Array<T>, keys:Array<Int>, begin:Int, end:Int, shift:Int, scratch:RadixScratch):Void {
|
||||
var numElements = end - begin;
|
||||
if (numElements < AMERICAN_FLAG_SORT_THRESHOLD)
|
||||
americanFlagSort(a, keys, begin, end, shift, scratch);
|
||||
else
|
||||
skaByteSort(a, keys, begin, end, shift, scratch);
|
||||
}
|
||||
|
||||
static function stdSortIfLessThanThreshold<T>(a:Array<T>, keys:Array<Int>, begin:Int, end:Int):Bool {
|
||||
var numElements = end - begin;
|
||||
if (numElements <= 1) return true;
|
||||
if (numElements >= STD_SORT_THRESHOLD) return false;
|
||||
insertionSort(a, keys, begin, end);
|
||||
return true;
|
||||
}
|
||||
|
||||
static function insertionSort<T>(a:Array<T>, keys:Array<Int>, begin:Int, end:Int):Void {
|
||||
var i = begin + 1;
|
||||
while (i < end) {
|
||||
var key = keys[i];
|
||||
var v = a[i];
|
||||
var j = i - 1;
|
||||
while (j >= begin && uLess(key, keys[j])) {
|
||||
keys[j + 1] = keys[j];
|
||||
a[j + 1] = a[j];
|
||||
j--;
|
||||
}
|
||||
keys[j + 1] = key;
|
||||
a[j + 1] = v;
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
static function americanFlagSort<T>(a:Array<T>, keys:Array<Int>, begin:Int, end:Int, shift:Int, scratch:RadixScratch):Void {
|
||||
var frame = scratch.frame(3 - (shift >> 3));
|
||||
var offset = frame.offset;
|
||||
var nextOffset = frame.nextOffset;
|
||||
var remaining = frame.remaining;
|
||||
zeroTable(offset);
|
||||
for (i in begin...end) {
|
||||
offset[(keys[i] >>> shift) & 0xff]++;
|
||||
}
|
||||
var numPartitions = 0;
|
||||
var total = 0;
|
||||
for (i in 0...256) {
|
||||
var count = offset[i];
|
||||
if (count == 0) continue;
|
||||
offset[i] = total;
|
||||
total += count;
|
||||
nextOffset[i] = total;
|
||||
remaining[numPartitions] = i;
|
||||
numPartitions++;
|
||||
}
|
||||
if (numPartitions > 1) {
|
||||
var curIdx = 0;
|
||||
var cur = remaining[0];
|
||||
var lastIdx = numPartitions - 1;
|
||||
var it = begin;
|
||||
var blockEnd = begin + nextOffset[cur];
|
||||
var lastElement = end - 1;
|
||||
var done = false;
|
||||
while (true) {
|
||||
var block = (keys[it] >>> shift) & 0xff;
|
||||
if (block == cur) {
|
||||
it++;
|
||||
if (it == lastElement) break;
|
||||
if (it == blockEnd) {
|
||||
while (true) {
|
||||
curIdx++;
|
||||
if (curIdx == lastIdx) {
|
||||
done = true;
|
||||
break;
|
||||
}
|
||||
cur = remaining[curIdx];
|
||||
if (offset[cur] != nextOffset[cur]) break;
|
||||
}
|
||||
if (done) break;
|
||||
it = begin + offset[cur];
|
||||
blockEnd = begin + nextOffset[cur];
|
||||
}
|
||||
}
|
||||
else {
|
||||
var t = offset[block];
|
||||
offset[block] = t + 1;
|
||||
swapAt(a, keys, it, begin + t);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (shift > 0) {
|
||||
var startOffset = 0;
|
||||
var partitionBegin = begin;
|
||||
for (i in 0...numPartitions) {
|
||||
var endOffset = nextOffset[remaining[i]];
|
||||
var partitionEnd = begin + endOffset;
|
||||
if (!stdSortIfLessThanThreshold(a, keys, partitionBegin, partitionEnd)) {
|
||||
msdSort(a, keys, partitionBegin, partitionEnd, shift - 8, scratch);
|
||||
}
|
||||
startOffset = endOffset;
|
||||
partitionBegin = partitionEnd;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static function skaByteSort<T>(a:Array<T>, keys:Array<Int>, begin:Int, end:Int, shift:Int, scratch:RadixScratch):Void {
|
||||
var frame = scratch.frame(3 - (shift >> 3));
|
||||
var offset = frame.offset;
|
||||
var nextOffset = frame.nextOffset;
|
||||
var remaining = frame.remaining;
|
||||
zeroTable(offset);
|
||||
for (i in begin...end) {
|
||||
offset[(keys[i] >>> shift) & 0xff]++;
|
||||
}
|
||||
var numPartitions = 0;
|
||||
var total = 0;
|
||||
for (i in 0...256) {
|
||||
var count = offset[i];
|
||||
if (count != 0) {
|
||||
offset[i] = total;
|
||||
total += count;
|
||||
remaining[numPartitions] = i;
|
||||
numPartitions++;
|
||||
}
|
||||
nextOffset[i] = total;
|
||||
}
|
||||
var lastRemaining = numPartitions;
|
||||
while (lastRemaining > 1) {
|
||||
var b = 0;
|
||||
while (b < lastRemaining && processPending(a, keys, offset, nextOffset, remaining[b], begin, shift)) b++;
|
||||
var it = b + 1;
|
||||
while (it < lastRemaining) {
|
||||
if (processPending(a, keys, offset, nextOffset, remaining[it], begin, shift)) {
|
||||
var t = remaining[b];
|
||||
remaining[b] = remaining[it];
|
||||
remaining[it] = t;
|
||||
b++;
|
||||
}
|
||||
it++;
|
||||
}
|
||||
lastRemaining = b;
|
||||
}
|
||||
if (shift > 0) {
|
||||
var i = numPartitions;
|
||||
while (i > 0) {
|
||||
i--;
|
||||
var partition = remaining[i];
|
||||
var start = partition == 0 ? 0 : nextOffset[partition - 1];
|
||||
var endOff = nextOffset[partition];
|
||||
if (!stdSortIfLessThanThreshold(a, keys, begin + start, begin + endOff)) {
|
||||
msdSort(a, keys, begin + start, begin + endOff, shift - 8, scratch);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static inline function newTable():Vector<Int> {
|
||||
var v = new Vector<Int>(256);
|
||||
#if !(cpp || hl || java || cs || flash)
|
||||
for (i in 0...256) v[i] = 0;
|
||||
#end
|
||||
return v;
|
||||
}
|
||||
|
||||
static function processPending<T>(a:Array<T>, keys:Array<Int>, offset:Vector<Int>, nextOffset:Vector<Int>, partition:Int, begin:Int, shift:Int):Bool {
|
||||
var endOff = nextOffset[partition];
|
||||
if (offset[partition] == endOff) return false;
|
||||
var it = begin + offset[partition];
|
||||
var count = endOff - offset[partition];
|
||||
while (count >= 4) {
|
||||
placeAt(a, keys, offset, it, begin, shift); it++;
|
||||
placeAt(a, keys, offset, it, begin, shift); it++;
|
||||
placeAt(a, keys, offset, it, begin, shift); it++;
|
||||
placeAt(a, keys, offset, it, begin, shift); it++;
|
||||
count -= 4;
|
||||
}
|
||||
while (count > 0) {
|
||||
placeAt(a, keys, offset, it, begin, shift);
|
||||
it++;
|
||||
count--;
|
||||
}
|
||||
return offset[partition] != endOff;
|
||||
}
|
||||
|
||||
static inline function placeAt<T>(a:Array<T>, keys:Array<Int>, offset:Vector<Int>, it:Int, begin:Int, shift:Int):Void {
|
||||
var p = (keys[it] >>> shift) & 0xff;
|
||||
var t = offset[p];
|
||||
offset[p] = t + 1;
|
||||
swapAt(a, keys, it, begin + t);
|
||||
}
|
||||
|
||||
static inline function zeroTable(v:Vector<Int>):Void {
|
||||
for (i in 0...256) v[i] = 0;
|
||||
}
|
||||
|
||||
static inline function swapAt<T>(a:Array<T>, keys:Array<Int>, i:Int, j:Int):Void {
|
||||
var ta = a[i];
|
||||
a[i] = a[j];
|
||||
a[j] = ta;
|
||||
var tk = keys[i];
|
||||
keys[i] = keys[j];
|
||||
keys[j] = tk;
|
||||
}
|
||||
|
||||
public static function sortCopy<T>(a:Array<T>, buffer:Array<T>, extractKey:T->Int):Bool {
|
||||
var numElements = a.length;
|
||||
if (numElements < STD_SORT_THRESHOLD) {
|
||||
sort(a, extractKey);
|
||||
return false;
|
||||
}
|
||||
var keys = new Array<Int>();
|
||||
var bufKeys = new Array<Int>();
|
||||
for (i in 0...numElements) keys[i] = extractKey(a[i]);
|
||||
sortCopyKeyed(a, keys, buffer, bufKeys);
|
||||
return false;
|
||||
}
|
||||
|
||||
public static function sortCopyKeyed<T>(a:Array<T>, keys:Array<Int>, buffer:Array<T>, bufKeys:Array<Int>):Void {
|
||||
var n = a.length;
|
||||
var counts0 = newTable();
|
||||
var counts1 = newTable();
|
||||
var counts2 = newTable();
|
||||
var counts3 = newTable();
|
||||
for (i in 0...n) {
|
||||
var key = keys[i];
|
||||
counts0[key & 0xff]++;
|
||||
counts1[(key >>> 8) & 0xff]++;
|
||||
counts2[(key >>> 16) & 0xff]++;
|
||||
counts3[(key >>> 24) & 0xff]++;
|
||||
}
|
||||
var total0 = 0, total1 = 0, total2 = 0, total3 = 0;
|
||||
for (i in 0...256) {
|
||||
var old0 = counts0[i];
|
||||
var old1 = counts1[i];
|
||||
var old2 = counts2[i];
|
||||
var old3 = counts3[i];
|
||||
counts0[i] = total0;
|
||||
counts1[i] = total1;
|
||||
counts2[i] = total2;
|
||||
counts3[i] = total3;
|
||||
total0 += old0;
|
||||
total1 += old1;
|
||||
total2 += old2;
|
||||
total3 += old3;
|
||||
}
|
||||
for (i in 0...n) {
|
||||
var b = keys[i] & 0xff;
|
||||
var t = counts0[b];
|
||||
counts0[b] = t + 1;
|
||||
buffer[t] = a[i];
|
||||
bufKeys[t] = keys[i];
|
||||
}
|
||||
for (i in 0...n) {
|
||||
var b = (bufKeys[i] >>> 8) & 0xff;
|
||||
var t = counts1[b];
|
||||
counts1[b] = t + 1;
|
||||
a[t] = buffer[i];
|
||||
keys[t] = bufKeys[i];
|
||||
}
|
||||
for (i in 0...n) {
|
||||
var b = (keys[i] >>> 16) & 0xff;
|
||||
var t = counts2[b];
|
||||
counts2[b] = t + 1;
|
||||
buffer[t] = a[i];
|
||||
bufKeys[t] = keys[i];
|
||||
}
|
||||
for (i in 0...n) {
|
||||
var b = (bufKeys[i] >>> 24) & 0xff;
|
||||
var t = counts3[b];
|
||||
counts3[b] = t + 1;
|
||||
a[t] = buffer[i];
|
||||
keys[t] = bufKeys[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class RadixFrame {
|
||||
public var offset:Vector<Int>;
|
||||
public var nextOffset:Vector<Int>;
|
||||
public var remaining:Vector<Int>;
|
||||
|
||||
public function new() {
|
||||
offset = new Vector<Int>(256);
|
||||
nextOffset = new Vector<Int>(256);
|
||||
remaining = new Vector<Int>(256);
|
||||
}
|
||||
}
|
||||
|
||||
private class RadixScratch {
|
||||
public var frames:Array<RadixFrame>;
|
||||
|
||||
public function new() {
|
||||
frames = [];
|
||||
}
|
||||
|
||||
public inline function frame(level:Int):RadixFrame {
|
||||
var f = frames[level];
|
||||
if (f == null) {
|
||||
f = new RadixFrame();
|
||||
frames[level] = f;
|
||||
}
|
||||
return f;
|
||||
}
|
||||
}
|
||||
11
lib/radix/haxelib.json
Normal file
11
lib/radix/haxelib.json
Normal file
@ -0,0 +1,11 @@
|
||||
{
|
||||
"name": "radix",
|
||||
"url": "https://github.com/skarupke/ska_sort",
|
||||
"license": "BSL-1.0",
|
||||
"tags": ["sort", "radix", "msd", "in-place"],
|
||||
"description": "Haxe port : in-place MSD radix sort (American flag sort) for Leenkx SDK from Malte Skarupke's ska_sort with insertion sort fallback and stable LSD copy path",
|
||||
"version": "1.0.0",
|
||||
"classPath": "Sources/",
|
||||
"releasenote": "Leenkx HAXE port",
|
||||
"contributors": ["Riccardo Mazzucco"]
|
||||
}
|
||||
Reference in New Issue
Block a user