Merge pull request 'main' (#148) from Onek8/LNXSDK:main into main
Reviewed-on: #148
This commit is contained in:
@ -81,11 +81,11 @@ void main() {
|
|||||||
vec4 gr = textureLod(gbuffer_refraction, texCoord, 0.0);
|
vec4 gr = textureLod(gbuffer_refraction, texCoord, 0.0);
|
||||||
float ior = unpackIOR(gr.x);
|
float ior = unpackIOR(gr.x);
|
||||||
float transmittance = gr.y;
|
float transmittance = gr.y;
|
||||||
float surfaceDepth = gr.z;
|
float surfaceDepth = textureLod(gbufferD, texCoord, 0.0).r;
|
||||||
float d = surfaceDepth * 2.0 - 1.0;
|
float d = surfaceDepth * 2.0 - 1.0;
|
||||||
|
|
||||||
vec4 sceneSample = textureLod(tex, texCoord, 0.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;
|
fragColor = sceneSample;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -96,41 +96,81 @@ vec3 sampleWaterNormals(vec2 hitXY, float speed, out vec2 tcnor0, out vec2 tcnor
|
|||||||
return normalize(n0 + n1 - 1.0);
|
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() {
|
void main() {
|
||||||
float gdepth = textureLod(gbufferD, texCoord, 0.0).r * 2.0 - 1.0;
|
float gdepth = textureLod(gbufferD, texCoord, 0.0).r * 2.0 - 1.0;
|
||||||
vec3 vray = normalize(viewRay);
|
vec3 vray = normalize(viewRay);
|
||||||
float speed = time * 2.0 * waterSpeed;
|
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)
|
// 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;
|
float tWater = (waterLevel - eye.z) / denom;
|
||||||
bool hasWaterHit = (abs(denom) > 0.0001) && (tWater > 0.0);
|
bool hasWaterHit = (abs(denom) > 0.0001) && (tWater > 0.0);
|
||||||
|
|
||||||
if (eye.z < waterLevel) {
|
if (eye.z < waterLevel) {
|
||||||
vec2 tc = texCoord;
|
vec2 tc = texCoord;
|
||||||
float fogFactor;
|
float depthThick = 1.0 + (waterLevel - eye.z);
|
||||||
|
vec3 p = vec3(0.0);
|
||||||
if (hasWaterHit && denom > 0.0) {
|
float geomDist = farPlane;
|
||||||
// Looking up at water surface - apply normal distortion
|
if (!isSky) {
|
||||||
vec3 hit = eye + tWater * vray;
|
p = getPos(eye, eyeLook, vray, gdepth, cameraProj);
|
||||||
vec2 tc0, tc1;
|
geomDist = length(p - eye);
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
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;
|
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;
|
fragColor.a = 1.0;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -144,21 +184,19 @@ void main() {
|
|||||||
if (isSky) tWater = min(tWater, 100.0); // Clamp to prevent aliasing at horizon
|
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);
|
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 - 0.01) {
|
||||||
if (!isSky && p.z > waterLevel) {
|
fragColor = textureLod(tex, texCoord, 0.0);
|
||||||
fragColor = vec4(0.0);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
float horizonFactor = clamp(1.0 - tWater / 60.0, 0.0, 1.0);
|
||||||
|
|
||||||
// Displace surface
|
// Displace surface
|
||||||
float geomZ = p.z; // undisplaced depth for foam/fog tests
|
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)
|
p.z += waveHeight(p.xy, speed);
|
||||||
+ sin(p.x * 20.0 / waterDisplace + speed * 1.3) * cos(p.y * 20.0 / waterDisplace + speed * 1.3) * 0.5)
|
|
||||||
/ 50.0 * waterDisplace;
|
|
||||||
|
|
||||||
// Hit plane to determine uvs
|
// Hit plane to determine uvs
|
||||||
vec3 v = normalize(eye - p);
|
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;
|
vec3 hit = eye + t * v;
|
||||||
|
|
||||||
// Sample normal maps
|
// Sample normal maps
|
||||||
@ -171,8 +209,8 @@ void main() {
|
|||||||
tc = texCoord + (n2.xy * n2.z) / 30.0 * waterRefract;
|
tc = texCoord + (n2.xy * n2.z) / 30.0 * waterRefract;
|
||||||
} else {
|
} else {
|
||||||
float ddepth = textureLod(gbufferD, texCoord + (n2.xy * n2.z) / 40.0, 0.0).r * 2.0 - 1.0;
|
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);
|
float dist2 = cameraProj.y / ((ddepth * 0.5 + 0.5) - cameraProj.x) / cosAng;
|
||||||
tc = p2.z > waterLevel ? texCoord : texCoord + (n2.xy * n2.z) / 30.0 * waterRefract;
|
tc = (eye.z + vray.z * dist2) > waterLevel ? texCoord : texCoord + (n2.xy * n2.z) / 30.0 * waterRefract;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Light
|
// Light
|
||||||
@ -222,26 +260,25 @@ void main() {
|
|||||||
#else
|
#else
|
||||||
fragColor.rgb = mix(refracted, reflectedEnv, waterReflect * fresnel);
|
fragColor.rgb = mix(refracted, reflectedEnv, waterReflect * fresnel);
|
||||||
#endif
|
#endif
|
||||||
// Water color tint - blend rather than multiply to preserve brightness
|
|
||||||
float colorMix = isSky ? 0.7 : 0.5;
|
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
|
// Blinn-Phong specular using half-vector, faded at horizon
|
||||||
vec3 h = normalize(v + ld);
|
vec3 h = normalize(v + ld);
|
||||||
float specAmount = pow(max(dot(n2, h), 0.0), 200.0) * (200.0 + 8.0) / (PI * 8.0);
|
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);
|
fragColor.rgb += specAmount * (isSky ? 0.3 : 1.0) * horizonFactor;
|
||||||
// Depth fog - blend toward waterColor with depth, faded at horizon
|
float waterColumn = isSky ? 1.0 : max(length(p - eye) - tWater, 0.0);
|
||||||
float depthFog = clamp(-(geomZ - waterLevel) * waterDensity, 0.0, 0.9);
|
float depthFog = 1.0 - exp(-waterColumn * waterDensity * 0.006);
|
||||||
fragColor.rgb = mix(fragColor.rgb, waterColor, depthFog * horizonFactor);
|
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
|
// Alpha fades smoothly at horizon instead of hard cut
|
||||||
// Skydome sits 3.5 below the camera (_skydomeMatrix), so its horizon
|
// Skydome sits 3.5 below the camera (_skydomeMatrix), so its horizon
|
||||||
// appears where the ray z reaches -3.5 / domeRadius
|
// 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);
|
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);
|
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
|
||||||
// Foam - based on actual geometry depth below water surface
|
float band = 0.1 + tWater * 0.003;
|
||||||
float fd = isSky ? 1.0 : abs(geomZ - waterLevel);
|
float fd = isSky ? 1.0 : abs(geomZ - (waterLevel + waveH));
|
||||||
if (fd < 0.1) {
|
if (fd < band) {
|
||||||
// Based on foam by Owen Deery
|
// Based on foam by Owen Deery
|
||||||
// http://fire-face.com/personal/water
|
// http://fire-face.com/personal/water
|
||||||
// Distance-based LOD blurs foam at range to reduce noise
|
// 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);
|
vec2 foamUV1 = tcnor1 * 4.0 + vec2(-speed / 35.0, speed / 45.0);
|
||||||
vec3 foamMask0 = textureLod(sfoam, foamUV0, foamLod).rgb;
|
vec3 foamMask0 = textureLod(sfoam, foamUV0, foamLod).rgb;
|
||||||
vec3 foamMask1 = textureLod(sfoam, foamUV1, 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 foamMask = 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;
|
float fac = clamp(1.0 - fd / band, 0.0, 1.0);
|
||||||
fragColor.rgb = mix(fragColor.rgb, mix(fragColor.rgb, waterColor + 0.2, foamStrength), clamp(fac, 0.0, 1.0) * 0.5);
|
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.MeshObject;
|
||||||
import iron.object.Uniforms;
|
import iron.object.Uniforms;
|
||||||
import iron.object.Clipmap;
|
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
|
#if lnx_vr
|
||||||
import iron.math.Vec4;
|
import iron.math.Vec4;
|
||||||
import iron.math.Mat4;
|
import iron.math.Mat4;
|
||||||
@ -50,10 +57,6 @@ class RenderPath {
|
|||||||
return vrSimulateMode;
|
return vrSimulateMode;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: done remove safely
|
|
||||||
public static inline function debugLog(msg: String, once: Bool = true): Void {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
#end
|
#end
|
||||||
|
|
||||||
public var frameScissor = false;
|
public var frameScissor = false;
|
||||||
@ -81,6 +84,18 @@ class RenderPath {
|
|||||||
var renderToXRFramebuffer = false;
|
var renderToXRFramebuffer = false;
|
||||||
#end
|
#end
|
||||||
public var drawOrder = DrawOrder.Distance;
|
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 paused = false;
|
||||||
public var ready(get, never): Bool;
|
public var ready(get, never): Bool;
|
||||||
function get_ready(): Bool { return loading == 0; }
|
function get_ready(): Bool { return loading == 0; }
|
||||||
@ -95,6 +110,8 @@ class RenderPath {
|
|||||||
var lastH = 0;
|
var lastH = 0;
|
||||||
var bindParams: Array<String>;
|
var bindParams: Array<String>;
|
||||||
var meshesSorted: Bool;
|
var meshesSorted: Bool;
|
||||||
|
var cullComputed: Bool;
|
||||||
|
var cullFlagsReset: Bool;
|
||||||
var scissorSet = false;
|
var scissorSet = false;
|
||||||
var viewportScaled = false;
|
var viewportScaled = false;
|
||||||
var lastFrameTime = 0.0;
|
var lastFrameTime = 0.0;
|
||||||
@ -304,6 +321,8 @@ class RenderPath {
|
|||||||
currentD = 1;
|
currentD = 1;
|
||||||
currentFace = -1;
|
currentFace = -1;
|
||||||
meshesSorted = false;
|
meshesSorted = false;
|
||||||
|
cullComputed = false;
|
||||||
|
cullFlagsReset = false;
|
||||||
sun = null;
|
sun = null;
|
||||||
|
|
||||||
for (l in Scene.active.lights) {
|
for (l in Scene.active.lights) {
|
||||||
@ -470,6 +489,60 @@ class RenderPath {
|
|||||||
rt.image.generateMipmaps(1000);
|
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 {
|
static inline function boolToInt(b: Bool): Int {
|
||||||
return b ? 1 : 0;
|
return b ? 1 : 0;
|
||||||
}
|
}
|
||||||
@ -501,6 +574,7 @@ class RenderPath {
|
|||||||
return a.data.name > b.data.name ? 1 : -1;
|
return a.data.name > b.data.name ? 1 : -1;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
#end
|
||||||
|
|
||||||
public function drawMeshes(context: String) {
|
public function drawMeshes(context: String) {
|
||||||
var isShadows = context == "shadowmap" || context == "shadowmap_transparent";
|
var isShadows = context == "shadowmap" || context == "shadowmap_transparent";
|
||||||
@ -519,6 +593,11 @@ class RenderPath {
|
|||||||
for (i in 0...LightObject.cascadeCount) {
|
for (i in 0...LightObject.cascadeCount) {
|
||||||
light.setCascade(Scene.active.camera, i);
|
light.setCascade(Scene.active.camera, i);
|
||||||
currentG.viewport(i * step, 0, step, step);
|
currentG.viewport(i * step, 0, step, step);
|
||||||
|
#if lnx_occlusion_culling
|
||||||
|
if (occlusionCulling && CullSystem.active != null) {
|
||||||
|
CullSystem.active.cullShadow(light);
|
||||||
|
}
|
||||||
|
#end
|
||||||
submitDraw(context);
|
submitDraw(context);
|
||||||
}
|
}
|
||||||
drawn = true;
|
drawn = true;
|
||||||
@ -529,7 +608,14 @@ class RenderPath {
|
|||||||
if (context == "mesh") LightObject.updateClusters(Scene.active.camera);
|
if (context == "mesh") LightObject.updateClusters(Scene.active.camera);
|
||||||
#end
|
#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
|
// Callbacks to specific context
|
||||||
if (contextEvents != null) {
|
if (contextEvents != null) {
|
||||||
@ -546,7 +632,34 @@ class RenderPath {
|
|||||||
var meshes = Scene.active.meshes;
|
var meshes = Scene.active.meshes;
|
||||||
MeshObject.lastPipeline = null;
|
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 camX = camera.transform.worldx();
|
||||||
var camY = camera.transform.worldy();
|
var camY = camera.transform.worldy();
|
||||||
var camZ = camera.transform.worldz();
|
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>) {
|
static inline function meshRenderLoop(g: Graphics, context: String, _bindParams: Array<String>, _meshes: Array<MeshObject>) {
|
||||||
var isReadingDepth = false;
|
var isReadingDepth = false;
|
||||||
|
#if lnx_occlusion_culling
|
||||||
|
var cullSys = CullSystem.active;
|
||||||
|
#end
|
||||||
|
|
||||||
for (m in _meshes) {
|
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
|
#if rp_depth_texture
|
||||||
// First mesh that reads depth
|
// First mesh that reads depth
|
||||||
if (!isReadingDepth && m.depthRead) {
|
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.math.Mat4;
|
||||||
import iron.data.SceneFormat;
|
import iron.data.SceneFormat;
|
||||||
import iron.data.MeshData;
|
import iron.data.MeshData;
|
||||||
|
import iron.data.ShaderData;
|
||||||
|
|
||||||
class Geometry {
|
class Geometry {
|
||||||
#if lnx_deinterleaved
|
#if lnx_deinterleaved
|
||||||
@ -208,7 +209,7 @@ class Geometry {
|
|||||||
for (v in 0...vertexBuffers.length)
|
for (v in 0...vertexBuffers.length)
|
||||||
if (vertexBuffers[v].name == e.name) {
|
if (vertexBuffers[v].name == e.name) {
|
||||||
vbs.push(vertexBuffers[v].buffer);
|
vbs.push(vertexBuffers[v].buffer);
|
||||||
continue;
|
break;
|
||||||
}
|
}
|
||||||
if (e.name == "ipos" && instancedVB != null) {
|
if (e.name == "ipos" && instancedVB != null) {
|
||||||
vbs.push(instancedVB);
|
vbs.push(instancedVB);
|
||||||
@ -264,7 +265,7 @@ class Geometry {
|
|||||||
for (i in 0...vaLength)
|
for (i in 0...vaLength)
|
||||||
vertexBuffers[i] = {
|
vertexBuffers[i] = {
|
||||||
name: vertexArrays[i].attrib,
|
name: vertexArrays[i].attrib,
|
||||||
buffer: makeDeinterleavedVB(vertexArrays[i].values, vertexArrays[i].attrib, vertexArrays[i].size)
|
buffer: makeDeinterleavedVB(vertexArrays[i])
|
||||||
};
|
};
|
||||||
#else
|
#else
|
||||||
|
|
||||||
@ -295,14 +296,20 @@ class Geometry {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#if lnx_deinterleaved
|
#if lnx_deinterleaved
|
||||||
function makeDeinterleavedVB(data: ByteArray, name: String, structLength: Int): VertexBuffer {
|
function makeDeinterleavedVB(va: TVertexArray): VertexBuffer {
|
||||||
var struct = new VertexStructure();
|
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();
|
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();
|
vertexBuffer.unlock();
|
||||||
|
|
||||||
return vertexBuffer;
|
return vertexBuffer;
|
||||||
|
|||||||
@ -71,30 +71,52 @@ class MeshBatch {
|
|||||||
if (b.meshes.length > 0 && b.meshes[0].cullMaterial(context)) continue;
|
if (b.meshes.length > 0 && b.meshes[0].cullMaterial(context)) continue;
|
||||||
|
|
||||||
var scontext = b.shader.getContext(context);
|
var scontext = b.shader.getContext(context);
|
||||||
|
if (scontext == null) continue;
|
||||||
g.setPipeline(scontext.pipeState);
|
g.setPipeline(scontext.pipeState);
|
||||||
// #if lnx_deinterleaved // TODO
|
#if lnx_deinterleaved
|
||||||
// g.setVertexBuffers(b.vertexBuffers);
|
g.setVertexBuffers(b.getVertexBuffers(scontext.raw.vertex_elements));
|
||||||
// #else
|
#else
|
||||||
g.setVertexBuffer(b.getVertexBuffer(scontext.raw.vertex_elements));
|
g.setVertexBuffer(b.getVertexBuffer(scontext.raw.vertex_elements));
|
||||||
// #end
|
#end
|
||||||
g.setIndexBuffer(b.indexBuffer);
|
g.setIndexBuffer(b.indexBuffer);
|
||||||
|
|
||||||
Uniforms.setContextConstants(g, scontext, bindParams);
|
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) {
|
for (m in b.meshes) {
|
||||||
if (!m.visible) continue; // Skip render if object is hidden
|
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;
|
if (m.cullMesh(context, Scene.active.camera, RenderPath.active.light)) continue;
|
||||||
|
|
||||||
// Get context
|
// Get context
|
||||||
var materialContexts: Array<MaterialContext> = [];
|
var materialContexts: Array<MaterialContext> = null;
|
||||||
var shaderContexts: Array<ShaderContext> = [];
|
var shaderContexts: Array<ShaderContext> = null;
|
||||||
m.getContexts(context, m.materials, materialContexts, shaderContexts);
|
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.posUnpack = m.data.scalePos;
|
||||||
Uniforms.texUnpack = m.data.scaleTex;
|
Uniforms.texUnpack = m.data.scaleTex;
|
||||||
m.transform.update();
|
if (!isShadowCtx) m.transform.update();
|
||||||
|
|
||||||
// Render mesh
|
// Render mesh
|
||||||
Uniforms.setObjectConstants(g, scontext, m);
|
Uniforms.setObjectConstants(g, scontext, m);
|
||||||
@ -126,8 +148,12 @@ class Bucket {
|
|||||||
|
|
||||||
public var batched = false;
|
public var batched = false;
|
||||||
public var shader: ShaderData;
|
public var shader: ShaderData;
|
||||||
|
#if lnx_deinterleaved
|
||||||
|
public var vertexBuffers: Array<InterleavedVertexBuffer> = [];
|
||||||
|
#else
|
||||||
var vertexBuffer: VertexBuffer;
|
var vertexBuffer: VertexBuffer;
|
||||||
var vertexBufferMap: Map<String, VertexBuffer> = new Map();
|
var vertexBufferMap: Map<String, VertexBuffer> = new Map();
|
||||||
|
#end
|
||||||
public var indexBuffer: IndexBuffer;
|
public var indexBuffer: IndexBuffer;
|
||||||
public var meshes: Array<MeshObject> = [];
|
public var meshes: Array<MeshObject> = [];
|
||||||
|
|
||||||
@ -137,8 +163,12 @@ class Bucket {
|
|||||||
|
|
||||||
public function remove() {
|
public function remove() {
|
||||||
indexBuffer.delete();
|
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
|
// this.vertexBuffer is in the map, so it's also deleted here
|
||||||
for (buf in vertexBufferMap) buf.delete();
|
for (buf in vertexBufferMap) buf.delete();
|
||||||
|
#end
|
||||||
meshes = [];
|
meshes = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -150,6 +180,20 @@ class Bucket {
|
|||||||
meshes.remove(m);
|
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) {
|
function copyAttribute(attribSize: Int, count: Int, to: ByteArray, toStride: Int, toOffset: Int, from: ByteArray, fromStride: Int, fromOffset: Int) {
|
||||||
for (i in 0...count) {
|
for (i in 0...count) {
|
||||||
for (j in 0...attribSize) {
|
for (j in 0...attribSize) {
|
||||||
@ -195,6 +239,7 @@ class Bucket {
|
|||||||
}
|
}
|
||||||
return vb;
|
return vb;
|
||||||
}
|
}
|
||||||
|
#end
|
||||||
|
|
||||||
function vertexCount(g: Geometry, hasUVs: Bool): Int {
|
function vertexCount(g: Geometry, hasUVs: Bool): Int {
|
||||||
var vcount = g.getVerticesLength();
|
var vcount = g.getVerticesLength();
|
||||||
@ -239,6 +284,50 @@ class Bucket {
|
|||||||
|
|
||||||
if (mdatas.length == 0) return;
|
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
|
// Pick UVs if present
|
||||||
var vs = mdatas[0].geom.struct;
|
var vs = mdatas[0].geom.struct;
|
||||||
for (md in mdatas) if (md.geom.struct.size() > vs.size()) vs = md.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 = "";
|
var s = "";
|
||||||
for (e in vs.elements) s += e.name;
|
for (e in vs.elements) s += e.name;
|
||||||
vertexBufferMap.set(s, vertexBuffer);
|
vertexBufferMap.set(s, vertexBuffer);
|
||||||
|
#end
|
||||||
|
|
||||||
indexBuffer = new IndexBuffer(icount, Usage.StaticUsage);
|
indexBuffer = new IndexBuffer(icount, Usage.StaticUsage);
|
||||||
var indices = indexBuffer.lock();
|
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_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 scale_tex: Null<FastFloat>; // Unpack tex from (-1,1) coords
|
||||||
@:optional public var morph_target: TMorphTarget;
|
@: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
|
#if js
|
||||||
|
|||||||
@ -102,8 +102,9 @@ class ShaderContext {
|
|||||||
constants = [];
|
constants = [];
|
||||||
textureUnits = [];
|
textureUnits = [];
|
||||||
|
|
||||||
|
var instStruct: VertexStructure = null;
|
||||||
if (instancingType > 0) { // Instancing
|
if (instancingType > 0) { // Instancing
|
||||||
var instStruct = new VertexStructure();
|
instStruct = new VertexStructure();
|
||||||
instStruct.add("ipos", VertexData.Float3);
|
instStruct.add("ipos", VertexData.Float3);
|
||||||
if (instancingType == 2 || instancingType == 4) {
|
if (instancingType == 2 || instancingType == 4) {
|
||||||
instStruct.add("irot", VertexData.Float3);
|
instStruct.add("irot", VertexData.Float3);
|
||||||
@ -117,11 +118,19 @@ class ShaderContext {
|
|||||||
this.instanceStride = Std.int(instStruct.byteSize() / 4);
|
this.instanceStride = Std.int(instStruct.byteSize() / 4);
|
||||||
|
|
||||||
instStruct.instanced = true;
|
instStruct.instanced = true;
|
||||||
pipeState.inputLayout = [structure, instStruct];
|
|
||||||
}
|
}
|
||||||
else { // Regular
|
#if lnx_deinterleaved
|
||||||
pipeState.inputLayout = [structure];
|
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
|
// Depth
|
||||||
pipeState.depthWrite = raw.depth_write;
|
pipeState.depthWrite = raw.depth_write;
|
||||||
|
|||||||
@ -62,7 +62,20 @@ class CameraObject extends Object {
|
|||||||
|
|
||||||
public function buildProjection(screenAspect: Null<Float> = null) {
|
public function buildProjection(screenAspect: Null<Float> = null) {
|
||||||
if (data.raw.ortho != 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 {
|
else {
|
||||||
if (screenAspect == null) screenAspect = iron.App.w() / iron.App.h();
|
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);
|
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
|
// Cascades
|
||||||
public static var cascadeCount = 1;
|
public static var cascadeCount = 1;
|
||||||
public static var cascadeSplitFactor = 0.8;
|
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
|
#if lnx_csm
|
||||||
var cascadeData: Float32Array = null;
|
var cascadeData: Float32Array = null;
|
||||||
var cascadeVP: Array<Mat4>;
|
var cascadeVP: Array<Mat4>;
|
||||||
@ -140,6 +140,9 @@ class LightObject extends Object {
|
|||||||
}
|
}
|
||||||
#end
|
#end
|
||||||
#end
|
#end
|
||||||
|
#if lnx_occlusion_culling
|
||||||
|
if (iron.culling.CullSystem.active != null) iron.culling.CullSystem.active.removeLight(this);
|
||||||
|
#end
|
||||||
super.remove();
|
super.remove();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -278,12 +281,13 @@ class LightObject extends Object {
|
|||||||
var worldPerTexelX = (maxx - minx) / smsize;
|
var worldPerTexelX = (maxx - minx) / smsize;
|
||||||
var worldPerTexelY = (maxy - miny) / smsize;
|
var worldPerTexelY = (maxy - miny) / smsize;
|
||||||
var worldPerTexelZ = (maxz - minz) / smsize;
|
var worldPerTexelZ = (maxz - minz) / smsize;
|
||||||
|
var ex = maxx - minx, ey = maxy - miny, ez = maxz - minz;
|
||||||
minx = Math.floor(minx / worldPerTexelX) * worldPerTexelX;
|
minx = Math.floor(minx / worldPerTexelX) * worldPerTexelX;
|
||||||
miny = Math.floor(miny / worldPerTexelY) * worldPerTexelY;
|
miny = Math.floor(miny / worldPerTexelY) * worldPerTexelY;
|
||||||
minz = Math.floor(minz / worldPerTexelZ) * worldPerTexelZ;
|
minz = Math.floor(minz / worldPerTexelZ) * worldPerTexelZ;
|
||||||
maxx = Math.floor(maxx / worldPerTexelX) * worldPerTexelX;
|
maxx = minx + ex;
|
||||||
maxy = Math.floor(maxy / worldPerTexelY) * worldPerTexelY;
|
maxy = miny + ey;
|
||||||
maxz = Math.floor(maxz / worldPerTexelZ) * worldPerTexelZ;
|
maxz = minz + ez;
|
||||||
|
|
||||||
var hx = (maxx - minx) / 2;
|
var hx = (maxx - minx) / 2;
|
||||||
var hy = (maxy - miny) / 2;
|
var hy = (maxy - miny) / 2;
|
||||||
|
|||||||
@ -30,9 +30,21 @@ class MeshObject extends Object {
|
|||||||
public var frustumCulling = true;
|
public var frustumCulling = true;
|
||||||
public var activeTilesheet: Tilesheet = null;
|
public var activeTilesheet: Tilesheet = null;
|
||||||
public var tilesheets: Array<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 skip_context: String = null; // Do not draw this context
|
||||||
public var force_context: String = null; // Draw only this context
|
public var force_context: String = null; // Draw only this context
|
||||||
static var lastPipeline: PipelineState = null;
|
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
|
#if lnx_morph_target
|
||||||
public var morphTarget: MorphTarget = null;
|
public var morphTarget: MorphTarget = null;
|
||||||
#end
|
#end
|
||||||
@ -48,6 +60,9 @@ class MeshObject extends Object {
|
|||||||
this.materials = materials;
|
this.materials = materials;
|
||||||
setData(data);
|
setData(data);
|
||||||
Scene.active.meshes.push(this);
|
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) {
|
public function setData(data: MeshData) {
|
||||||
@ -96,6 +111,9 @@ class MeshObject extends Object {
|
|||||||
if (activeTilesheet != null) activeTilesheet.remove();
|
if (activeTilesheet != null) activeTilesheet.remove();
|
||||||
if (tilesheets != null) { for (ts in tilesheets) { ts.remove(); } tilesheets = null; }
|
if (tilesheets != null) { for (ts in tilesheets) { ts.remove(); } tilesheets = null; }
|
||||||
if (Scene.active != null) Scene.active.meshes.remove(this);
|
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 (rp_renderer == "Deferred")
|
||||||
if (Scene.active != null) Scene.active.markMaterialParamsDirty();
|
if (Scene.active != null) Scene.active.markMaterialParamsDirty();
|
||||||
#end
|
#end
|
||||||
@ -196,7 +214,7 @@ class MeshObject extends Object {
|
|||||||
var isShadow = context == "shadowmap";
|
var isShadow = context == "shadowmap";
|
||||||
if (camera == null) return false;
|
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
|
// Scale radius for skinned mesh and particle system
|
||||||
// TODO: define skin & particle bounds
|
// TODO: define skin & particle bounds
|
||||||
var radiusScale = data.isSkinned ? 2.0 : 1.0;
|
var radiusScale = data.isSkinned ? 2.0 : 1.0;
|
||||||
@ -211,7 +229,8 @@ class MeshObject extends Object {
|
|||||||
var frustumPlanes = isShadow ? light.frustumPlanes : camera.frustumPlanes;
|
var frustumPlanes = isShadow ? light.frustumPlanes : camera.frustumPlanes;
|
||||||
|
|
||||||
if (isShadow && light.data.raw.type != "sun") { // Non-sun light bounds intersect camera frustum
|
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)) {
|
if (!CameraObject.sphereInFrustum(camera.frustumPlanes, light.transform)) {
|
||||||
return setCulled(isShadow, true);
|
return setCulled(isShadow, true);
|
||||||
}
|
}
|
||||||
@ -220,6 +239,37 @@ class MeshObject extends Object {
|
|||||||
if (!CameraObject.sphereInFrustum(frustumPlanes, transform, radiusScale)) {
|
if (!CameraObject.sphereInFrustum(frustumPlanes, transform, radiusScale)) {
|
||||||
return setCulled(isShadow, true);
|
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;
|
//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>) {
|
public function render(g: Graphics, context: String, bindParams: Array<String>) {
|
||||||
if (data == null || !data.geom.ready) return; // Data not yet streamed
|
if (data == null || !data.geom.ready) return; // Data not yet streamed
|
||||||
if (!visible) return; // Skip render if object is hidden
|
if (!visible) return; // Skip render if object is hidden
|
||||||
@ -293,6 +349,7 @@ class MeshObject extends Object {
|
|||||||
// Get lod
|
// Get lod
|
||||||
var mats = materials;
|
var mats = materials;
|
||||||
var lod = this;
|
var lod = this;
|
||||||
|
var isShadowCtx = context == "shadowmap" || context == "shadowmap_transparent";
|
||||||
if (raw != null && raw.lods != null && raw.lods.length > 0) {
|
if (raw != null && raw.lods != null && raw.lods.length > 0) {
|
||||||
computeScreenSize(Scene.active.camera);
|
computeScreenSize(Scene.active.camera);
|
||||||
initLods();
|
initLods();
|
||||||
@ -317,13 +374,29 @@ class MeshObject extends Object {
|
|||||||
if (isLodMaterial() && !validContext(mats, context)) return;
|
if (isLodMaterial() && !validContext(mats, context)) return;
|
||||||
|
|
||||||
// Get context
|
// Get context
|
||||||
var materialContexts: Array<MaterialContext> = [];
|
var materialContexts: Array<MaterialContext> = null;
|
||||||
var shaderContexts: Array<ShaderContext> = [];
|
var shaderContexts: Array<ShaderContext> = null;
|
||||||
getContexts(context, mats, materialContexts, shaderContexts);
|
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.posUnpack = data.scalePos;
|
||||||
Uniforms.texUnpack = data.scaleTex;
|
Uniforms.texUnpack = data.scaleTex;
|
||||||
transform.update();
|
if (!isShadowCtx) transform.update();
|
||||||
|
|
||||||
// Render mesh
|
// Render mesh
|
||||||
var ldata = lod.data;
|
var ldata = lod.data;
|
||||||
|
|||||||
@ -166,6 +166,7 @@ class LnxPack {
|
|||||||
case "anim": TAnimation;
|
case "anim": TAnimation;
|
||||||
case "tracks": TTrack;
|
case "tracks": TTrack;
|
||||||
case "morph_target": TMorphTarget;
|
case "morph_target": TMorphTarget;
|
||||||
|
case "occluder_proxy": TOccluderProxy;
|
||||||
case "vertex_groups": TVertex_groups;
|
case "vertex_groups": TVertex_groups;
|
||||||
case "tilesheet": TTilesheetData;
|
case "tilesheet": TTilesheetData;
|
||||||
case "events": TTilesheetEvent;
|
case "events": TTilesheetEvent;
|
||||||
|
|||||||
@ -933,26 +933,6 @@ class RenderPathDeferred {
|
|||||||
}
|
}
|
||||||
#end
|
#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 rp_ssrefr
|
||||||
{
|
{
|
||||||
if (leenkx.data.Config.raw.rp_ssrefr != false)
|
if (leenkx.data.Config.raw.rp_ssrefr != false)
|
||||||
@ -1027,6 +1007,26 @@ class RenderPathDeferred {
|
|||||||
}
|
}
|
||||||
#end
|
#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 rp_ssr
|
||||||
{
|
{
|
||||||
if (leenkx.data.Config.raw.rp_ssr != false) {
|
if (leenkx.data.Config.raw.rp_ssr != false) {
|
||||||
|
|||||||
@ -1133,6 +1133,11 @@ class LeenkxExporter:
|
|||||||
}
|
}
|
||||||
out_object['properties'].append(out_property)
|
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
|
# Export the object reference and material references
|
||||||
objref = bobject.data
|
objref = bobject.data
|
||||||
@ -2125,7 +2130,8 @@ class LeenkxExporter:
|
|||||||
fp = self.get_meshes_file_path('mesh_' + oid, compressed=LeenkxExporter.compress_enabled)
|
fp = self.get_meshes_file_path('mesh_' + oid, compressed=LeenkxExporter.compress_enabled)
|
||||||
assets.add(fp)
|
assets.add(fp)
|
||||||
# No export necessary
|
# 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
|
return
|
||||||
|
|
||||||
# Mesh users have different modifier stack
|
# Mesh users have different modifier stack
|
||||||
@ -2202,6 +2208,22 @@ class LeenkxExporter:
|
|||||||
if armature:
|
if armature:
|
||||||
self.export_skin(bobject, armature, export_mesh, out_mesh)
|
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
|
# Restore the morph state after mesh export
|
||||||
if shape_keys:
|
if shape_keys:
|
||||||
bobject.active_shape_key_index = active_shape_key_index
|
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):
|
def export_light(self, object_ref):
|
||||||
"""Exports a single light object."""
|
"""Exports a single light object."""
|
||||||
rpdat = lnx.utils.get_rp()
|
rpdat = lnx.utils.get_rp()
|
||||||
@ -2496,23 +2651,57 @@ class LeenkxExporter:
|
|||||||
b = proj[1][1]
|
b = proj[1][1]
|
||||||
c = proj[2][2]
|
c = proj[2][2]
|
||||||
d = proj[2][3]
|
d = proj[2][3]
|
||||||
k = (c - 1.0) / (c + 1.0)
|
|
||||||
o['fov'] = 2.0 * math.atan(1.0 / b)
|
o['fov'] = 2.0 * math.atan(1.0 / b)
|
||||||
|
if abs(a) > 1e-6:
|
||||||
|
o['aspect'] = b / a
|
||||||
if with_planes:
|
if with_planes:
|
||||||
o['near_plane'] = (d * (1.0 - k)) / (2.0 * k)
|
denom = c + 1.0
|
||||||
o['far_plane'] = k * o['near_plane']
|
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
|
@staticmethod
|
||||||
def extract_ortho(o, proj):
|
def extract_ortho(o, proj):
|
||||||
|
a = proj[0][0]
|
||||||
|
b = proj[1][1]
|
||||||
# left, right, bottom, top
|
# left, right, bottom, top
|
||||||
o['ortho'] = [-(1 + proj[3][0]) / proj[0][0], \
|
o['ortho'] = [-(1 + proj[3][0]) / a, \
|
||||||
(1 - proj[3][0]) / proj[0][0], \
|
(1 - proj[3][0]) / a, \
|
||||||
-(1 + proj[3][1]) / proj[1][1], \
|
-(1 + proj[3][1]) / b, \
|
||||||
(1 - proj[3][1]) / proj[1][1]]
|
(1 - proj[3][1]) / b]
|
||||||
o['near_plane'] = (1 + proj[3][2]) / proj[2][2]
|
o['near_plane'] = (1 + proj[3][2]) / proj[2][2]
|
||||||
o['far_plane'] = -(1 - proj[3][2]) / proj[2][2]
|
o['far_plane'] = -(1 - proj[3][2]) / proj[2][2]
|
||||||
o['near_plane'] *= 2
|
o['near_plane'] *= 2
|
||||||
o['far_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):
|
def export_camera(self, objectRef):
|
||||||
o = {}
|
o = {}
|
||||||
@ -2521,12 +2710,16 @@ class LeenkxExporter:
|
|||||||
|
|
||||||
camera = objectRef[1]["objectTable"][0]
|
camera = objectRef[1]["objectTable"][0]
|
||||||
render = self.scene.render
|
render = self.scene.render
|
||||||
|
rpdat = lnx.utils.get_rp()
|
||||||
|
|
||||||
|
par_x, par_y = self.compute_effective_par(rpdat, render)
|
||||||
|
|
||||||
proj = camera.calc_matrix_camera(
|
proj = camera.calc_matrix_camera(
|
||||||
self.depsgraph,
|
self.depsgraph,
|
||||||
x=render.resolution_x,
|
x=render.resolution_x,
|
||||||
y=render.resolution_y,
|
y=render.resolution_y,
|
||||||
scale_x=render.pixel_aspect_x,
|
scale_x=par_x,
|
||||||
scale_y=render.pixel_aspect_y)
|
scale_y=par_y)
|
||||||
if objref.type == 'PERSP':
|
if objref.type == 'PERSP':
|
||||||
self.extract_projection(o, proj)
|
self.extract_projection(o, proj)
|
||||||
else:
|
else:
|
||||||
@ -2594,7 +2787,7 @@ class LeenkxExporter:
|
|||||||
self.output['material_datas'].append(o)
|
self.output['material_datas'].append(o)
|
||||||
bpy.data.materials.remove(mat)
|
bpy.data.materials.remove(mat)
|
||||||
rpdat = lnx.utils.get_rp()
|
rpdat = lnx.utils.get_rp()
|
||||||
if not rpdat.lnx_culling:
|
if not rpdat.lnx_backface_culling:
|
||||||
o['override_context'] = {}
|
o['override_context'] = {}
|
||||||
o['override_context']['cull_mode'] = 'none'
|
o['override_context']['cull_mode'] = 'none'
|
||||||
|
|
||||||
@ -2673,7 +2866,7 @@ class LeenkxExporter:
|
|||||||
o['skip_context'] = material.lnx_skip_context
|
o['skip_context'] = material.lnx_skip_context
|
||||||
|
|
||||||
rpdat = lnx.utils.get_rp()
|
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'] = {}
|
||||||
o['override_context']['cull_mode'] = 'none'
|
o['override_context']['cull_mode'] = 'none'
|
||||||
elif material.lnx_cull_mode != 'clockwise':
|
elif material.lnx_cull_mode != 'clockwise':
|
||||||
@ -3190,6 +3383,12 @@ class LeenkxExporter:
|
|||||||
'clear_color': self.get_camera_clear_color()
|
'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
|
# Set viewport camera projection
|
||||||
proj, is_persp = self.get_viewport_projection_matrix()
|
proj, is_persp = self.get_viewport_projection_matrix()
|
||||||
if proj is not None:
|
if proj is not None:
|
||||||
@ -3197,6 +3396,9 @@ class LeenkxExporter:
|
|||||||
self.extract_projection(out_camera, proj, with_planes=False)
|
self.extract_projection(out_camera, proj, with_planes=False)
|
||||||
else:
|
else:
|
||||||
self.extract_ortho(out_camera, proj)
|
self.extract_ortho(out_camera, proj)
|
||||||
|
|
||||||
|
out_camera['aspect'] = effective_aspect
|
||||||
|
|
||||||
self.output['camera_datas'].append(out_camera)
|
self.output['camera_datas'].append(out_camera)
|
||||||
|
|
||||||
out_object = {
|
out_object = {
|
||||||
|
|||||||
@ -390,12 +390,14 @@ def send_event(event_id: str, opt_data: Any = None):
|
|||||||
elif event_id == 'cam_props':
|
elif event_id == 'cam_props':
|
||||||
cam: bpy.types.Camera = obj_data.data
|
cam: bpy.types.Camera = obj_data.data
|
||||||
render = bpy.context.scene.render
|
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(
|
projection = obj_data.calc_matrix_camera(
|
||||||
bpy.context.evaluated_depsgraph_get(),
|
bpy.context.evaluated_depsgraph_get(),
|
||||||
x=render.resolution_x,
|
x=render.resolution_x,
|
||||||
y=render.resolution_y,
|
y=render.resolution_y,
|
||||||
scale_x=render.pixel_aspect_x,
|
scale_x=par_x,
|
||||||
scale_y=render.pixel_aspect_y)
|
scale_y=par_y)
|
||||||
focal_y = projection[1][1]
|
focal_y = projection[1][1]
|
||||||
depth_z = projection[2][2]
|
depth_z = projection[2][2]
|
||||||
depth_w = projection[2][3]
|
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)
|
fov = 2.0 * math.atan(1.0 / focal_y)
|
||||||
near = (depth_w * (1.0 - far_near_ratio)) / (2.0 * far_near_ratio)
|
near = (depth_w * (1.0 - far_near_ratio)) / (2.0 * far_near_ratio)
|
||||||
far = far_near_ratio * near
|
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}")'
|
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)
|
write_patch(js)
|
||||||
|
|
||||||
elif event_id == 'obj_visibility':
|
elif event_id == 'obj_visibility':
|
||||||
|
|||||||
@ -190,6 +190,9 @@ def build():
|
|||||||
rpdat = lnx.utils.get_rp()
|
rpdat = lnx.utils.get_rp()
|
||||||
project_path = lnx.utils.get_fp()
|
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:
|
if rpdat.rp_driver != 'Leenkx' and lnx.api.drivers[rpdat.rp_driver]['make_rpath'] != None:
|
||||||
lnx.api.drivers[rpdat.rp_driver]['make_rpath']()
|
lnx.api.drivers[rpdat.rp_driver]['make_rpath']()
|
||||||
return
|
return
|
||||||
@ -210,6 +213,9 @@ def build():
|
|||||||
if rpdat.rp_hdr:
|
if rpdat.rp_hdr:
|
||||||
assets.add_khafile_def('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))
|
assets.add_khafile_def('rp_renderer={0}'.format(rpdat.rp_renderer))
|
||||||
if rpdat.rp_depthprepass:
|
if rpdat.rp_depthprepass:
|
||||||
assets.add_khafile_def('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':
|
elif node.sky_type == 'MULTIPLE_SCATTERING':
|
||||||
return parse_sky_multiple_scattering(node, state)
|
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:
|
else:
|
||||||
log.error(f'Unsupported sky model: {node.sky_type}!')
|
log.error(f'Unsupported sky model: {node.sky_type}!')
|
||||||
return c.to_vec3([0.0, 0.0, 0.0])
|
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_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_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_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 = 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_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)
|
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)
|
assets.invalidate_shader_cache(self, context)
|
||||||
bpy.data.worlds['Lnx'].lnx_recompile = True
|
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):
|
def udpate_shadowmap_cascades(self, context):
|
||||||
bpy.data.worlds['Lnx'].lnx_recompile = True
|
bpy.data.worlds['Lnx'].lnx_recompile = True
|
||||||
update_renderpath(self, context)
|
update_renderpath(self, context)
|
||||||
@ -461,6 +470,7 @@ class LnxRPListItem(bpy.types.PropertyGroup):
|
|||||||
('Distance', 'Distance', 'Distance'),
|
('Distance', 'Distance', 'Distance'),
|
||||||
('Index', 'Index', 'Index')],
|
('Index', 'Index', 'Index')],
|
||||||
name='Draw Order', description='Sort objects', default='Auto', update=assets.invalidate_compiled_data)
|
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: BoolProperty(name="Depth Texture", description="Current render-path state", default=False)
|
||||||
rp_depth_texture_state: EnumProperty(
|
rp_depth_texture_state: EnumProperty(
|
||||||
items=[('On', 'On', 'On'),
|
items=[('On', 'On', 'On'),
|
||||||
@ -697,8 +707,48 @@ class LnxRPListItem(bpy.types.PropertyGroup):
|
|||||||
('CPU', 'CPU', 'CPU'),
|
('CPU', 'CPU', 'CPU'),
|
||||||
('Off', 'Off', 'Off')],
|
('Off', 'Off', 'Off')],
|
||||||
name='Particles', description='Enable particle simulation', default='GPU', update=assets.invalidate_shader_cache)
|
name='Particles', description='Enable particle simulation', default='GPU', update=assets.invalidate_shader_cache)
|
||||||
# Material override flags
|
# Culling flags
|
||||||
lnx_culling: BoolProperty(name="Culling", default=True)
|
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)
|
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
|
@staticmethod
|
||||||
|
|||||||
@ -112,6 +112,10 @@ class LNX_PT_ObjectPropsPanel(bpy.types.Panel):
|
|||||||
sub.prop(obj, 'lnx_visible_shadow')
|
sub.prop(obj, 'lnx_visible_shadow')
|
||||||
|
|
||||||
if obj.type == 'MESH':
|
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')
|
layout.prop(obj, 'lnx_instanced')
|
||||||
if 'Rot' in obj.lnx_instanced:
|
if 'Rot' in obj.lnx_instanced:
|
||||||
layout.prop(obj, 'lnx_instanced_rot_type')
|
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_blending_state')
|
||||||
layout.prop(rpdat, 'rp_depth_texture_state')
|
layout.prop(rpdat, 'rp_depth_texture_state')
|
||||||
layout.prop(rpdat, 'rp_draw_order')
|
layout.prop(rpdat, 'rp_draw_order')
|
||||||
|
layout.prop(rpdat, 'lnx_radix')
|
||||||
layout.prop(rpdat, 'lnx_samples_per_pixel')
|
layout.prop(rpdat, 'lnx_samples_per_pixel')
|
||||||
layout.prop(rpdat, 'lnx_texture_filter')
|
layout.prop(rpdat, 'lnx_texture_filter')
|
||||||
layout.prop(rpdat, 'rp_sss_state')
|
layout.prop(rpdat, 'rp_sss_state')
|
||||||
@ -1878,7 +1883,23 @@ class LNX_PT_RenderPathRendererPanel(bpy.types.Panel):
|
|||||||
col = layout.column()
|
col = layout.column()
|
||||||
col.prop(rpdat, "rp_hdr")
|
col.prop(rpdat, "rp_hdr")
|
||||||
col.prop(rpdat, "rp_stereo")
|
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')
|
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')):
|
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(add_leenkx_library(sdk_path, 'lib/hscript', rel_path=do_relpath_sdk))
|
||||||
khafile.write("project.addDefine('" + 'hscript' + "');\n")
|
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
|
# Project libraries
|
||||||
if os.path.exists('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':
|
if rpdat.rp_draw_order == 'Index':
|
||||||
assets.add_khafile_def('lnx_draworder_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':
|
if lnx.utils.get_viewport_controls() == 'azerty':
|
||||||
assets.add_khafile_def('lnx_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) + ";")
|
f.write("\n" + content_indent + "iron.object.LightObject.cascadeSplitFactor = " + str(rpdat.lnx_shadowmap_split) + ";")
|
||||||
if rpdat.lnx_shadowmap_bounds != 1.0:
|
if rpdat.lnx_shadowmap_bounds != 1.0:
|
||||||
f.write("\n" + content_indent + "iron.object.LightObject.cascadeBounds = " + str(rpdat.lnx_shadowmap_bounds) + ";")
|
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))
|
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)
|
render_path = os.path.join(target_dir, image_name)
|
||||||
scene.render.filepath = render_path
|
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
|
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