topazcube 0.1.31 → 0.1.35

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (103) hide show
  1. package/LICENSE.txt +0 -0
  2. package/README.md +0 -0
  3. package/dist/Renderer.cjs +20844 -0
  4. package/dist/Renderer.cjs.map +1 -0
  5. package/dist/Renderer.js +20827 -0
  6. package/dist/Renderer.js.map +1 -0
  7. package/dist/client.cjs +91 -260
  8. package/dist/client.cjs.map +1 -1
  9. package/dist/client.js +68 -215
  10. package/dist/client.js.map +1 -1
  11. package/dist/server.cjs +165 -432
  12. package/dist/server.cjs.map +1 -1
  13. package/dist/server.js +117 -370
  14. package/dist/server.js.map +1 -1
  15. package/dist/terminal.cjs +113 -200
  16. package/dist/terminal.cjs.map +1 -1
  17. package/dist/terminal.js +50 -51
  18. package/dist/terminal.js.map +1 -1
  19. package/dist/utils-CRhi1BDa.cjs +259 -0
  20. package/dist/utils-CRhi1BDa.cjs.map +1 -0
  21. package/dist/utils-D7tXt6-2.js +260 -0
  22. package/dist/utils-D7tXt6-2.js.map +1 -0
  23. package/package.json +19 -15
  24. package/src/{client.ts → network/client.js} +170 -403
  25. package/src/{compress-browser.ts → network/compress-browser.js} +2 -4
  26. package/src/{compress-node.ts → network/compress-node.js} +8 -14
  27. package/src/{server.ts → network/server.js} +229 -317
  28. package/src/{terminal.js → network/terminal.js} +0 -0
  29. package/src/{topazcube.ts → network/topazcube.js} +2 -2
  30. package/src/network/utils.js +375 -0
  31. package/src/renderer/Camera.js +191 -0
  32. package/src/renderer/DebugUI.js +703 -0
  33. package/src/renderer/Geometry.js +1049 -0
  34. package/src/renderer/Material.js +64 -0
  35. package/src/renderer/Mesh.js +211 -0
  36. package/src/renderer/Node.js +112 -0
  37. package/src/renderer/Pipeline.js +645 -0
  38. package/src/renderer/Renderer.js +1496 -0
  39. package/src/renderer/Skin.js +792 -0
  40. package/src/renderer/Texture.js +584 -0
  41. package/src/renderer/core/AssetManager.js +394 -0
  42. package/src/renderer/core/CullingSystem.js +308 -0
  43. package/src/renderer/core/EntityManager.js +541 -0
  44. package/src/renderer/core/InstanceManager.js +343 -0
  45. package/src/renderer/core/ParticleEmitter.js +358 -0
  46. package/src/renderer/core/ParticleSystem.js +564 -0
  47. package/src/renderer/core/SpriteSystem.js +349 -0
  48. package/src/renderer/gltf.js +563 -0
  49. package/src/renderer/math.js +161 -0
  50. package/src/renderer/rendering/HistoryBufferManager.js +333 -0
  51. package/src/renderer/rendering/ProbeCapture.js +1495 -0
  52. package/src/renderer/rendering/ReflectionProbeManager.js +352 -0
  53. package/src/renderer/rendering/RenderGraph.js +2258 -0
  54. package/src/renderer/rendering/passes/AOPass.js +308 -0
  55. package/src/renderer/rendering/passes/AmbientCapturePass.js +593 -0
  56. package/src/renderer/rendering/passes/BasePass.js +101 -0
  57. package/src/renderer/rendering/passes/BloomPass.js +420 -0
  58. package/src/renderer/rendering/passes/CRTPass.js +724 -0
  59. package/src/renderer/rendering/passes/FogPass.js +445 -0
  60. package/src/renderer/rendering/passes/GBufferPass.js +730 -0
  61. package/src/renderer/rendering/passes/HiZPass.js +744 -0
  62. package/src/renderer/rendering/passes/LightingPass.js +753 -0
  63. package/src/renderer/rendering/passes/ParticlePass.js +841 -0
  64. package/src/renderer/rendering/passes/PlanarReflectionPass.js +456 -0
  65. package/src/renderer/rendering/passes/PostProcessPass.js +405 -0
  66. package/src/renderer/rendering/passes/ReflectionPass.js +157 -0
  67. package/src/renderer/rendering/passes/RenderPostPass.js +364 -0
  68. package/src/renderer/rendering/passes/SSGIPass.js +266 -0
  69. package/src/renderer/rendering/passes/SSGITilePass.js +305 -0
  70. package/src/renderer/rendering/passes/ShadowPass.js +2072 -0
  71. package/src/renderer/rendering/passes/TransparentPass.js +831 -0
  72. package/src/renderer/rendering/passes/VolumetricFogPass.js +715 -0
  73. package/src/renderer/rendering/shaders/ao.wgsl +182 -0
  74. package/src/renderer/rendering/shaders/bloom.wgsl +97 -0
  75. package/src/renderer/rendering/shaders/bloom_blur.wgsl +80 -0
  76. package/src/renderer/rendering/shaders/crt.wgsl +455 -0
  77. package/src/renderer/rendering/shaders/depth_copy.wgsl +17 -0
  78. package/src/renderer/rendering/shaders/geometry.wgsl +580 -0
  79. package/src/renderer/rendering/shaders/hiz_reduce.wgsl +114 -0
  80. package/src/renderer/rendering/shaders/light_culling.wgsl +204 -0
  81. package/src/renderer/rendering/shaders/lighting.wgsl +932 -0
  82. package/src/renderer/rendering/shaders/lighting_common.wgsl +143 -0
  83. package/src/renderer/rendering/shaders/particle_render.wgsl +672 -0
  84. package/src/renderer/rendering/shaders/particle_simulate.wgsl +440 -0
  85. package/src/renderer/rendering/shaders/postproc.wgsl +293 -0
  86. package/src/renderer/rendering/shaders/render_post.wgsl +289 -0
  87. package/src/renderer/rendering/shaders/shadow.wgsl +117 -0
  88. package/src/renderer/rendering/shaders/ssgi.wgsl +266 -0
  89. package/src/renderer/rendering/shaders/ssgi_accumulate.wgsl +114 -0
  90. package/src/renderer/rendering/shaders/ssgi_propagate.wgsl +132 -0
  91. package/src/renderer/rendering/shaders/volumetric_blur.wgsl +80 -0
  92. package/src/renderer/rendering/shaders/volumetric_composite.wgsl +80 -0
  93. package/src/renderer/rendering/shaders/volumetric_raymarch.wgsl +634 -0
  94. package/src/renderer/utils/BoundingSphere.js +439 -0
  95. package/src/renderer/utils/Frustum.js +281 -0
  96. package/src/renderer/utils/Raycaster.js +761 -0
  97. package/dist/client.d.cts +0 -211
  98. package/dist/client.d.ts +0 -211
  99. package/dist/server.d.cts +0 -120
  100. package/dist/server.d.ts +0 -120
  101. package/dist/terminal.d.cts +0 -64
  102. package/dist/terminal.d.ts +0 -64
  103. package/src/utils.ts +0 -403
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Renderer.cjs","sources":["../src/renderer/math.js","../src/renderer/Texture.js","../src/renderer/utils/BoundingSphere.js","../src/renderer/Geometry.js","../src/renderer/Material.js","../src/renderer/Mesh.js","../src/renderer/Node.js","../src/renderer/Camera.js","../src/renderer/rendering/passes/BasePass.js","../src/renderer/utils/Frustum.js","../src/renderer/rendering/passes/ShadowPass.js","../src/renderer/rendering/ProbeCapture.js","../src/renderer/rendering/ReflectionProbeManager.js","../src/renderer/rendering/passes/ReflectionPass.js","../src/renderer/Pipeline.js","../src/renderer/rendering/shaders/geometry.wgsl","../src/renderer/rendering/passes/GBufferPass.js","../src/renderer/rendering/shaders/lighting.wgsl","../src/renderer/rendering/shaders/light_culling.wgsl","../src/renderer/rendering/passes/LightingPass.js","../src/renderer/rendering/shaders/particle_simulate.wgsl","../src/renderer/rendering/shaders/particle_render.wgsl","../src/renderer/rendering/passes/ParticlePass.js","../src/renderer/rendering/passes/FogPass.js","../src/renderer/rendering/passes/PlanarReflectionPass.js","../src/renderer/rendering/shaders/hiz_reduce.wgsl","../src/renderer/rendering/passes/HiZPass.js","../src/renderer/rendering/shaders/ao.wgsl","../src/renderer/rendering/passes/AOPass.js","../src/renderer/rendering/shaders/ssgi_accumulate.wgsl","../src/renderer/rendering/shaders/ssgi_propagate.wgsl","../src/renderer/rendering/passes/SSGITilePass.js","../src/renderer/rendering/shaders/ssgi.wgsl","../src/renderer/rendering/passes/SSGIPass.js","../src/renderer/rendering/shaders/render_post.wgsl","../src/renderer/rendering/passes/RenderPostPass.js","../src/renderer/rendering/shaders/bloom.wgsl","../src/renderer/rendering/shaders/bloom_blur.wgsl","../src/renderer/rendering/passes/BloomPass.js","../src/renderer/rendering/shaders/lighting_common.wgsl","../src/renderer/rendering/passes/TransparentPass.js","../src/renderer/rendering/shaders/volumetric_raymarch.wgsl","../src/renderer/rendering/shaders/volumetric_blur.wgsl","../src/renderer/rendering/shaders/volumetric_composite.wgsl","../src/renderer/rendering/passes/VolumetricFogPass.js","../src/renderer/rendering/shaders/postproc.wgsl","../src/renderer/rendering/passes/PostProcessPass.js","../src/renderer/rendering/shaders/crt.wgsl","../src/renderer/rendering/passes/CRTPass.js","../src/renderer/rendering/passes/AmbientCapturePass.js","../src/renderer/rendering/HistoryBufferManager.js","../src/renderer/core/CullingSystem.js","../src/renderer/core/InstanceManager.js","../src/renderer/core/SpriteSystem.js","../src/renderer/core/ParticleEmitter.js","../src/renderer/core/ParticleSystem.js","../src/renderer/rendering/RenderGraph.js","../src/renderer/Skin.js","../src/renderer/gltf.js","../src/renderer/core/EntityManager.js","../src/renderer/core/AssetManager.js","../src/renderer/DebugUI.js","../src/renderer/utils/Raycaster.js","../src/renderer/Renderer.js"],"sourcesContent":["import * as glMatrix from \"gl-matrix\"\n\n// Extract gl-matrix modules for both window globals and ES module exports\nconst { mat4, mat3, mat2, mat2d, vec2, vec3, vec4, quat, quat2 } = glMatrix\n\nwindow.mat4 = mat4\nwindow.mat2 = mat2\nwindow.mat2d = mat2d\nwindow.mat3 = mat3\nwindow.quat = quat\nwindow.quat2 = quat2\nwindow.vec2 = vec2\nwindow.vec3 = vec3\nwindow.vec4 = vec4\n\n// Make all Math functions globally accessible\nfor (let name of Object.getOwnPropertyNames(Math)) {\n window[name] = Math[name]\n}\n// Add PI2 constant\nwindow.PI2 = Math.PI * 2\n\nconst UP = vec3.fromValues(0, 1, 0)\nconst RIGHT = vec3.fromValues(1, 0, 0)\nconst FORWARD = vec3.fromValues(0, 0, -1)\n\nlet V_T = vec3.create()\nlet V_T2 = vec3.create()\n\n// YXZ\nfunction toEuler(q, dst) {\n var qx = q[0]\n var qy = q[1]\n var qz = q[2]\n var qw = q[3]\n\n var sqw = qw * qw\n var sqz = qz * qz\n var sqx = qx * qx\n var sqy = qy * qy\n\n var zAxisY = qy * qz - qx * qw\n var limit = 0.4999999\n\n if (zAxisY < -limit) {\n dst[1] = 2 * atan2(qy, qw)\n dst[0] = PI / 2\n dst[2] = 0\n } else if (zAxisY > limit) {\n dst[1] = 2 * atan2(qy, qw)\n dst[0] = -PI / 2\n dst[2] = 0\n } else {\n dst[2] = atan2(2.0 * (qx * qy + qz * qw), -sqz - sqx + sqy + sqw)\n dst[0] = asin(-2.0 * (qz * qy - qx * qw))\n dst[1] = atan2(2.0 * (qz * qx + qy * qw), sqz - sqx - sqy + sqw)\n }\n return dst\n}\n\nfunction fromEuler(v, dst) {\n let hx = v[0] * 0.5\n let hy = v[1] * 0.5\n let hz = v[2] * 0.5\n let sx = sin(hx)\n let cx = cos(hx)\n let sy = sin(hy)\n let cy = cos(hy)\n let sz = sin(hz)\n let cz = cos(hz)\n \n // YXZ\n dst[0] = sx * cy * cz + cx * sy * sz\n dst[1] = cx * sy * cz - sx * cy * sz\n dst[2] = cx * cy * sz - sx * sy * cz\n dst[3] = cx * cy * cz + sx * sy * sz\n return dst\n}\n\n/*\nOctahedron-normal vectors\n\n// https://knarkowicz.wordpress.com/2014/04/16/octahedron-normal-vector-encoding/\n\nfloat2 OctWrap(float2 v)\n{\n return (1.0 - abs(v.yx)) * (v.xy >= 0.0 ? 1.0 : -1.0);\n}\n \nfloat2 Encode(float3 n)\n{\n n /= (abs(n.x) + abs(n.y) + abs(n.z));\n n.xy = n.z >= 0.0 ? n.xy : OctWrap(n.xy);\n n.xy = n.xy * 0.5 + 0.5;\n return n.xy;\n}\n \nfloat3 Decode(float2 f)\n{\n f = f * 2.0 - 1.0;\n \n // https://twitter.com/Stubbesaurus/status/937994790553227264\n float3 n = float3(f.x, f.y, 1.0 - abs(f.x) - abs(f.y));\n float t = saturate(-n.z);\n n.xy += n.xy >= 0.0 ? -t : t;\n return normalize(n);\n}\n*/\n\nfunction octWrap(out, v) {\n out[0] = (1.0 - Math.abs(v[1])) * (v[0] >= 0.0 ? 1.0 : -1.0)\n out[1] = (1.0 - Math.abs(v[0])) * (v[1] >= 0.0 ? 1.0 : -1.0)\n return out\n}\n\nfunction encodeNormal(out, n) {\n let sum = Math.abs(n[0]) + Math.abs(n[1]) + Math.abs(n[2])\n let nx = n[0] / sum\n let ny = n[1] / sum\n \n if (n[2] >= 0.0) {\n out[0] = nx\n out[1] = ny\n } else {\n let temp = vec2.create()\n temp[0] = nx\n temp[1] = ny\n octWrap(out, temp)\n }\n \n out[0] = out[0] * 0.5 + 0.5\n out[1] = out[1] * 0.5 + 0.5\n return out\n}\n\nfunction decodeNormal(out, f) {\n let fx = f[0] * 2.0 - 1.0\n let fy = f[1] * 2.0 - 1.0\n\n let fz = 1.0 - Math.abs(fx) - Math.abs(fy)\n \n let t = Math.max(0.0, Math.min(1.0, -fz))\n \n out[0] = fx + (fx >= 0.0 ? -t : t)\n out[1] = fy + (fy >= 0.0 ? -t : t) \n out[2] = fz\n \n vec3.normalize(out, out)\n return out\n}\n\nexport {\n toEuler,\n fromEuler,\n UP, RIGHT, FORWARD,\n V_T, V_T2,\n // gl-matrix exports\n mat4, mat3, mat2,\n vec2, vec3, vec4,\n quat, quat2\n}\n","// Texture class for WebGPU texture management\n\n\n// https://enkimute.github.io/hdrpng.js/\n//From https://raw.githubusercontent.com/enkimute/hdrpng.js/refs/heads/master/hdrpng.js\n\nfunction loadHDR( url ) {\n function m(a,b) { for (var i in b) a[i]=b[i]; return a; };\n\n let p = new Promise((resolve, reject) => {\n var req = m(new XMLHttpRequest(),{responseType:\"arraybuffer\"});\n req.onerror = reject.bind(req,false);\n req.onload = function() {\n if (this.status>=400) return this.onerror();\n var header='',pos=0,d8=new Uint8Array(this.response),format;\n // read header.\n while (!header.match(/\\n\\n[^\\n]+\\n/g)) header += String.fromCharCode(d8[pos++]);\n // check format.\n format = header.match(/FORMAT=(.*)$/m)[1];\n if (format!='32-bit_rle_rgbe') return console.warn('unknown format : '+format),this.onerror();\n // parse resolution\n var rez=header.split(/\\n/).reverse()[1].split(' '), width=rez[3]*1, height=rez[1]*1;\n // Create image.\n var img=new Uint8Array(width*height*4),ipos=0;\n // Read all scanlines\n for (var j=0; j<height; j++) {\n var rgbe=d8.slice(pos,pos+=4),scanline=[];\n if (rgbe[0]!=2||(rgbe[1]!=2)||(rgbe[2]&0x80)) {\n var len=width,rs=0; pos-=4; while (len>0) {\n img.set(d8.slice(pos,pos+=4),ipos);\n if (img[ipos]==1&&img[ipos+1]==1&&img[ipos+2]==1) {\n for (img[ipos+3]<<rs; i>0; i--) {\n img.set(img.slice(ipos-4,ipos),ipos);\n ipos+=4;\n len--\n }\n rs+=8;\n } else { len--; ipos+=4; rs=0; }\n }\n } else {\n if ((rgbe[2]<<8)+rgbe[3]!=width) return console.warn('HDR line mismatch ..'),this.onerror();\n for (var i=0;i<4;i++) {\n var ptr=i*width,ptr_end=(i+1)*width,buf,count;\n while (ptr<ptr_end){\n buf = d8.slice(pos,pos+=2);\n if (buf[0] > 128) { count = buf[0]-128; while(count-- > 0) scanline[ptr++] = buf[1]; }\n else { count = buf[0]-1; scanline[ptr++]=buf[1]; while(count-->0) scanline[ptr++]=d8[pos++]; }\n }\n }\n for (var i=0;i<width;i++) { img[ipos++]=scanline[i]; img[ipos++]=scanline[i+width]; img[ipos++]=scanline[i+2*width]; img[ipos++]=scanline[i+3*width]; }\n }\n }\n resolve({data:img,width: width,height: height});\n }\n req.open(\"GET\",url,true);\n req.send(null);\n return req;\n })\n return p\n}\n\n//https://webgpufundamentals.org/webgpu/lessons/webgpu-importing-textures.html\n\nconst numMipLevels = (...sizes) => {\n const maxSize = Math.max(sizes[0], sizes[1]);\n const mipFactor = 2\n return 1 + Math.floor(Math.log(maxSize) / Math.log(mipFactor));\n}\n\nconst generateMips = (() => {\n let sampler, module, pipeline;\n let modules = {};\n const pipelines = {};\n const samplers = {};\n\n return function generateMips(device, texture, rgbe = false) {\n let v = rgbe ? 'rgbe' : 'rgb';\n v = v + texture.format\n if (modules[v]) {\n module = modules[v]\n pipeline = pipelines[v]\n sampler = samplers[v]\n } else {\n let code = ''\n code = `\n struct VSOutput {\n @builtin(position) position: vec4f,\n @location(0) texcoord: vec2f,\n };\n\n @vertex fn vs(\n @builtin(vertex_index) vertexIndex : u32\n ) -> VSOutput {\n let pos = array(\n // 1st triangle\n vec2f( 0.0, 0.0), // center\n vec2f( 1.0, 0.0), // right, center\n vec2f( 0.0, 1.0), // center, top\n\n // 2nd triangle\n vec2f( 0.0, 1.0), // center, top\n vec2f( 1.0, 0.0), // right, center\n vec2f( 1.0, 1.0), // right, top\n );\n\n var vsOutput: VSOutput;\n let xy = pos[vertexIndex];\n vsOutput.position = vec4f(xy * 2.0 - 1.0, 0.0, 1.0);\n vsOutput.texcoord = vec2f(xy.x, 1.0 - xy.y);\n return vsOutput;\n }\n\n @group(0) @binding(0) var ourSampler: sampler;\n @group(0) @binding(1) var ourTexture: texture_2d<f32>;\n\n fn gaussian3x3(uv: vec2f) -> vec4f {\n let texelSize = 1.0 / vec2f(textureDimensions(ourTexture));\n var color = vec3f(0.0);\n\n // 3x3 Gaussian kernel weights\n let weights = array(\n 0.0625, 0.125, 0.0625, // 1/16, 2/16, 1/16\n 0.125, 0.25, 0.125, // 2/16, 4/16, 2/16\n 0.0625, 0.125, 0.0625 // 1/16, 2/16, 1/16\n );\n\n for (var i = -1; i <= 1; i++) {\n for (var j = -1; j <= 1; j++) {\n let offset = vec2f(f32(i), f32(j)) * texelSize * 1.0;\n let weight = weights[(i + 1) * 3 + (j + 1)];\n\n var rgbe = textureSample(ourTexture, ourSampler, uv + offset);\n var rgb = rgbe.rgb * pow(2.0, rgbe.a * 255.0 - 128.0);\n color += rgb * weight;\n }\n }\n\n // Encode back to RGBE format\n let maxComponent = max(max(color.r, color.g), color.b);\n let exponent = ceil(log2(maxComponent));\n let mantissa = color * pow(2.0, -exponent);\n return vec4f(mantissa, (exponent + 128.0) / 255.0);\n }\n\n @fragment fn fs(fsInput: VSOutput) -> @location(0) vec4f {\n `\n if (rgbe) {\n code += `return gaussian3x3(fsInput.texcoord);\\n`\n } else {\n code += `return textureSample(ourTexture, ourSampler, fsInput.texcoord);\\n`\n }\n code += `\n }\n `\n module = device.createShaderModule({\n label: 'textured quad shaders for mip level generation',\n code: code\n });\n modules[v] = module\n\n sampler = device.createSampler({\n minFilter: 'linear',\n magFilter: 'linear',\n addressModeU: 'mirror-repeat',\n addressModeV: 'mirror-repeat',\n });\n samplers[v] = sampler\n\n pipeline = device.createRenderPipeline({\n label: 'mip level generator pipeline',\n layout: 'auto',\n vertex: {\n module,\n },\n fragment: {\n module,\n targets: [{ format: texture.format }],\n },\n });\n pipelines[v] = pipeline\n }\n\n const encoder = device.createCommandEncoder({\n label: 'mip gen encoder',\n });\n\n let width = texture.width;\n let height = texture.height;\n let baseMipLevel = 0;\n while (width > 1 || height > 1) {\n width = Math.max(1, width / 2 | 0);\n height = Math.max(1, height / 2 | 0);\n\n const bindGroup = device.createBindGroup({\n layout: pipeline.getBindGroupLayout(0),\n entries: [\n { binding: 0, resource: sampler },\n { binding: 1, resource: texture.createView({baseMipLevel, mipLevelCount: 1}) },\n ],\n });\n\n ++baseMipLevel;\n\n const renderPassDescriptor = {\n label: 'our basic canvas renderPass',\n colorAttachments: [\n {\n view: texture.createView({baseMipLevel, mipLevelCount: 1}),\n loadOp: 'clear',\n storeOp: 'store',\n },\n ],\n };\n\n const pass = encoder.beginRenderPass(renderPassDescriptor);\n pass.setPipeline(pipeline);\n pass.setBindGroup(0, bindGroup);\n pass.draw(6); // call our vertex shader 6 times\n pass.end();\n }\n\n const commandBuffer = encoder.finish();\n device.queue.submit([commandBuffer]);\n };\n })();\n\nclass Texture {\n texture = null\n sampler = null\n engine = null\n sourceUrl = ''\n\n constructor(engine = null) {\n this.engine = engine\n }\n\n // Static async factory method\n static async fromImage(engine, url_or_image, options = {}) {\n options = {\n flipY: true,\n srgb: true, // Whether to interpret image as sRGB (true) or linear (false)\n generateMips: true,\n ...options\n }\n const { device, options: webgpuOptions } = engine\n\n let imageBitmap\n let isHDR = false\n if (typeof url_or_image === 'string') {\n if (url_or_image.endsWith('.hdr')) {\n isHDR = true\n imageBitmap = await loadHDR(url_or_image)\n } else {\n const response = await fetch(url_or_image)\n imageBitmap = await createImageBitmap(await response.blob())\n }\n } else {\n imageBitmap = url_or_image\n }\n\n let format = options.srgb ? \"rgba8unorm-srgb\" : \"rgba8unorm\"\n if (isHDR) {\n format = \"rgba8unorm\"\n }\n const texture = await device.createTexture({\n size: [imageBitmap.width, imageBitmap.height, 1],\n mipLevelCount: options.generateMips ? numMipLevels(imageBitmap.width, imageBitmap.height) : 1,\n format: format,\n usage: GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_DST | GPUTextureUsage.RENDER_ATTACHMENT,\n })\n\n let source = imageBitmap\n if (isHDR) {\n source = imageBitmap.data\n device.queue.writeTexture(\n { texture, mipLevel: 0, origin: [0, 0, 0], aspect: \"all\" },\n source,\n { offset: 0, bytesPerRow: imageBitmap.width * 4, rowsPerImage: imageBitmap.height },\n [ imageBitmap.width, imageBitmap.height, 1 ]\n )\n } else {\n device.queue.copyExternalImageToTexture(\n { source: source, flipY: options.flipY },\n { texture: texture },\n [imageBitmap.width, imageBitmap.height]\n )\n }\n\n let mipCount = options.generateMips ? numMipLevels(imageBitmap.width, imageBitmap.height) : 1\n if (options.generateMips) {\n generateMips(device, texture, isHDR)\n }\n\n let t = Texture._createTexture(engine, texture, {\n forceLinear: isHDR,\n addressModeU: options.addressModeU,\n addressModeV: options.addressModeV\n })\n t.isHDR = isHDR\n t.mipCount = mipCount\n if (typeof url_or_image === 'string') {\n t.sourceUrl = url_or_image\n }\n return t\n }\n\n static async fromColor(engine, color) {\n // Parse hex color to rgb values\n color = color.replace('#', '')\n const r = parseInt(color.substring(0,2), 16) / 255\n const g = parseInt(color.substring(2,4), 16) / 255\n const b = parseInt(color.substring(4,6), 16) / 255\n\n return Texture.fromRGBA(engine, r, g, b, 1.0)\n }\n\n /**\n * Create a texture from raw RGBA Uint8Array data\n * @param {Engine} engine\n * @param {Uint8Array} data - RGBA pixel data (4 bytes per pixel)\n * @param {number} width - Texture width\n * @param {number} height - Texture height\n * @param {Object} options - { srgb, generateMips }\n */\n static async fromRawData(engine, data, width, height, options = {}) {\n options = {\n srgb: true,\n generateMips: false,\n ...options\n }\n const { device } = engine\n\n const format = options.srgb ? \"rgba8unorm-srgb\" : \"rgba8unorm\"\n const mipCount = options.generateMips ? numMipLevels(width, height) : 1\n\n const texture = await device.createTexture({\n size: [width, height, 1],\n mipLevelCount: mipCount,\n format: format,\n usage: GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_DST | GPUTextureUsage.RENDER_ATTACHMENT,\n })\n\n device.queue.writeTexture(\n { texture, mipLevel: 0, origin: [0, 0, 0] },\n data,\n { bytesPerRow: width * 4, rowsPerImage: height },\n [width, height, 1]\n )\n\n if (options.generateMips && mipCount > 1) {\n generateMips(device, texture, false)\n }\n\n let t = Texture._createTexture(engine, texture)\n t.mipCount = mipCount\n return t\n }\n\n /**\n * Create a 1x1 texture from RGBA values (0-1 range)\n */\n static async fromRGBA(engine, r, g, b, a = 1.0) {\n const { device } = engine\n\n // Create 1x1 pixel data with the color (as Uint8)\n const colorData = new Uint8Array([\n Math.round(r * 255),\n Math.round(g * 255),\n Math.round(b * 255),\n Math.round(a * 255)\n ])\n\n const texture = await device.createTexture({\n size: [1, 1],\n format: \"rgba8unorm\",\n usage: GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_DST | GPUTextureUsage.RENDER_ATTACHMENT,\n })\n\n device.queue.writeTexture(\n { texture },\n colorData,\n { bytesPerRow: 4 },\n [1, 1]\n )\n\n let t = Texture._createTexture(engine, texture)\n return t\n }\n\n /**\n * Load HDR texture from RGBM JPG pair (RGB with sRGB gamma + log multiplier)\n * RGB stores actual color values (gamma corrected) - values <= 1.0 stored directly\n * Multiplier: black = 1.0, white = 32768, logarithmic encoding\n * @param {string} rgbUrl - URL to RGB JPG (e.g., 'probe_01.jpg')\n * @param {string} multUrl - URL to multiplier JPG (e.g., 'probe_01.mult.jpg'), or null to auto-derive\n * @param {Object} options - { generateMips: true }\n */\n static async fromJPGPair(engine, rgbUrl, multUrl = null, options = {}) {\n options = {\n generateMips: true,\n ...options\n }\n const { device } = engine\n\n // Auto-derive multiplier URL if not provided\n if (!multUrl) {\n multUrl = rgbUrl.replace(/\\.jpg$/i, '.mult.jpg')\n }\n\n // Load both images\n const [rgbResponse, multResponse] = await Promise.all([\n fetch(rgbUrl),\n fetch(multUrl)\n ])\n\n const [rgbBlob, multBlob] = await Promise.all([\n rgbResponse.blob(),\n multResponse.blob()\n ])\n\n const [rgbBitmap, multBitmap] = await Promise.all([\n createImageBitmap(rgbBlob),\n createImageBitmap(multBlob)\n ])\n\n const width = rgbBitmap.width\n const height = rgbBitmap.height\n\n // Draw to canvases to get pixel data\n const rgbCanvas = document.createElement('canvas')\n rgbCanvas.width = width\n rgbCanvas.height = height\n const rgbCtx = rgbCanvas.getContext('2d')\n rgbCtx.drawImage(rgbBitmap, 0, 0)\n const rgbImageData = rgbCtx.getImageData(0, 0, width, height)\n\n const multCanvas = document.createElement('canvas')\n multCanvas.width = width\n multCanvas.height = height\n const multCtx = multCanvas.getContext('2d')\n multCtx.drawImage(multBitmap, 0, 0)\n const multImageData = multCtx.getImageData(0, 0, width, height)\n\n // RGBM decoding parameters (must match encoder in ProbeCapture.saveAsJPG)\n const LOG_MULT_MAX = 15 // log2(32768)\n const SRGB_GAMMA = 2.2\n\n // Convert RGBM to RGBE for GPU texture\n // We store as RGBE because that's what the shader expects\n const rgbeData = new Uint8Array(width * height * 4)\n for (let i = 0; i < width * height; i++) {\n const idx = i * 4\n\n // Get sRGB gamma encoded RGB (0-255 -> 0-1)\n const rGamma = rgbImageData.data[idx] / 255\n const gGamma = rgbImageData.data[idx + 1] / 255\n const bGamma = rgbImageData.data[idx + 2] / 255\n\n // Convert from sRGB to linear\n const rLinear = Math.pow(rGamma, SRGB_GAMMA)\n const gLinear = Math.pow(gGamma, SRGB_GAMMA)\n const bLinear = Math.pow(bGamma, SRGB_GAMMA)\n\n // Decode multiplier: 0 = 1.0, 255 = 32768, logarithmic\n const multByte = multImageData.data[idx]\n const multNorm = multByte / 255 // 0 to 1\n const logMult = multNorm * LOG_MULT_MAX // 0 to 15\n const multiplier = Math.pow(2, logMult) // 1 to 32768\n\n // Reconstruct HDR color\n const r = rLinear * multiplier\n const g = gLinear * multiplier\n const b = bLinear * multiplier\n\n // Convert to RGBE for GPU\n const maxVal = Math.max(r, g, b)\n if (maxVal < 1e-32) {\n rgbeData[idx] = 0\n rgbeData[idx + 1] = 0\n rgbeData[idx + 2] = 0\n rgbeData[idx + 3] = 0\n } else {\n const exp = Math.ceil(Math.log2(maxVal))\n const scale = Math.pow(2, -exp) * 255\n\n rgbeData[idx] = Math.min(255, Math.max(0, Math.round(r * scale)))\n rgbeData[idx + 1] = Math.min(255, Math.max(0, Math.round(g * scale)))\n rgbeData[idx + 2] = Math.min(255, Math.max(0, Math.round(b * scale)))\n rgbeData[idx + 3] = exp + 128\n }\n }\n\n // Create texture\n const mipCount = options.generateMips ? numMipLevels(width, height) : 1\n const texture = device.createTexture({\n size: [width, height],\n mipLevelCount: mipCount,\n format: 'rgba8unorm',\n usage: GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_DST | GPUTextureUsage.RENDER_ATTACHMENT,\n })\n\n device.queue.writeTexture(\n { texture },\n rgbeData,\n { bytesPerRow: width * 4 },\n [width, height]\n )\n\n // Generate mips with RGBE-aware filtering\n if (options.generateMips) {\n generateMips(device, texture, true) // true = RGBE mode\n }\n\n let t = Texture._createTexture(engine, texture, {forceLinear: true})\n t.isHDR = true\n t.mipCount = mipCount\n return t\n }\n\n static async renderTarget(engine, format = 'rgba8unorm', width = null, height = null) {\n const { device, canvas, options } = engine\n const w = width ?? canvas.width\n const h = height ?? canvas.height\n const texture = await device.createTexture({\n size: [w, h],\n format: format,\n //sampleCount: options.msaa > 1 ? options.msaa : 1,\n usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_SRC | GPUTextureUsage.COPY_DST,\n });\n let t = Texture._createTexture(engine, texture)\n t.renderTarget = true\n t.format = format\n return t\n }\n\n static async depth(engine, width = null, height = null) {\n const { device, canvas, options } = engine\n const w = width ?? canvas.width\n const h = height ?? canvas.height\n\n const texture = await device.createTexture({\n size: [w, h, 1],\n //sampleCount: options.msaa > 1 ? options.msaa : 1,\n format: \"depth32float\",\n usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_SRC,\n })\n\n let t = Texture._createTexture(engine, texture)\n t.depth = true\n return t\n }\n\n static _createTexture(engine, texture, options = {}) {\n const { device, canvas, settings } = engine\n let t = new Texture(engine)\n t.texture = texture\n t.view = texture.createView()\n // Default to mirror-repeat to avoid black border artifacts\n const addressModeU = options.addressModeU || 'mirror-repeat'\n const addressModeV = options.addressModeV || 'mirror-repeat'\n if (settings.rendering.nearestFiltering && !options.forceLinear) {\n t.sampler = device.createSampler({\n magFilter: \"nearest\",\n minFilter: \"nearest\",\n addressModeU: addressModeU,\n addressModeV: addressModeV,\n mipmapFilter: \"nearest\",\n maxAnisotropy: 1,\n })\n } else {\n t.sampler = device.createSampler({\n magFilter: \"linear\",\n minFilter: \"linear\",\n addressModeU: addressModeU,\n addressModeV: addressModeV,\n mipmapFilter: \"linear\",\n maxAnisotropy: 1,\n })\n }\n return t\n }\n}\n\nexport { Texture, generateMips, numMipLevels }\n","import { vec3 } from \"../math.js\"\n\n/**\n * Calculate a bounding sphere from position data\n * Uses Ritter's bounding sphere algorithm for a good approximation\n *\n * @param {Float32Array|Array} positions - Position data (x, y, z, x, y, z, ...)\n * @returns {{ center: [number, number, number], radius: number }}\n */\nfunction calculateBoundingSphere(positions) {\n if (!positions || positions.length < 3) {\n return { center: [0, 0, 0], radius: 0 }\n }\n\n const vertexCount = Math.floor(positions.length / 3)\n\n // Step 1: Find the centroid (average of all points)\n let cx = 0, cy = 0, cz = 0\n for (let i = 0; i < vertexCount; i++) {\n cx += positions[i * 3]\n cy += positions[i * 3 + 1]\n cz += positions[i * 3 + 2]\n }\n cx /= vertexCount\n cy /= vertexCount\n cz /= vertexCount\n\n // Step 2: Find the point farthest from the centroid\n let maxDistSq = 0\n let farthestIdx = 0\n for (let i = 0; i < vertexCount; i++) {\n const dx = positions[i * 3] - cx\n const dy = positions[i * 3 + 1] - cy\n const dz = positions[i * 3 + 2] - cz\n const distSq = dx * dx + dy * dy + dz * dz\n if (distSq > maxDistSq) {\n maxDistSq = distSq\n farthestIdx = i\n }\n }\n\n // Step 3: Find the point farthest from that point\n let p1x = positions[farthestIdx * 3]\n let p1y = positions[farthestIdx * 3 + 1]\n let p1z = positions[farthestIdx * 3 + 2]\n\n maxDistSq = 0\n let oppositeIdx = 0\n for (let i = 0; i < vertexCount; i++) {\n const dx = positions[i * 3] - p1x\n const dy = positions[i * 3 + 1] - p1y\n const dz = positions[i * 3 + 2] - p1z\n const distSq = dx * dx + dy * dy + dz * dz\n if (distSq > maxDistSq) {\n maxDistSq = distSq\n oppositeIdx = i\n }\n }\n\n let p2x = positions[oppositeIdx * 3]\n let p2y = positions[oppositeIdx * 3 + 1]\n let p2z = positions[oppositeIdx * 3 + 2]\n\n // Step 4: Initial sphere from these two extreme points\n let sphereCx = (p1x + p2x) * 0.5\n let sphereCy = (p1y + p2y) * 0.5\n let sphereCz = (p1z + p2z) * 0.5\n let radius = Math.sqrt(maxDistSq) * 0.5\n\n // Step 5: Ritter's expansion - grow sphere to include all points\n for (let i = 0; i < vertexCount; i++) {\n const px = positions[i * 3]\n const py = positions[i * 3 + 1]\n const pz = positions[i * 3 + 2]\n\n const dx = px - sphereCx\n const dy = py - sphereCy\n const dz = pz - sphereCz\n const dist = Math.sqrt(dx * dx + dy * dy + dz * dz)\n\n if (dist > radius) {\n // Point is outside sphere, expand to include it\n const newRadius = (radius + dist) * 0.5\n const ratio = (newRadius - radius) / dist\n\n sphereCx += dx * ratio\n sphereCy += dy * ratio\n sphereCz += dz * ratio\n radius = newRadius\n }\n }\n\n // Add a small epsilon to avoid floating-point issues\n radius *= 1.001\n\n return {\n center: [sphereCx, sphereCy, sphereCz],\n radius: radius\n }\n}\n\n/**\n * Calculate axis-aligned bounding box from positions\n * @param {Float32Array|Array} positions - Position data\n * @returns {{ min: [number, number, number], max: [number, number, number] }}\n */\nfunction calculateAABB(positions) {\n if (!positions || positions.length < 3) {\n return { min: [0, 0, 0], max: [0, 0, 0] }\n }\n\n const vertexCount = Math.floor(positions.length / 3)\n\n let minX = Infinity, minY = Infinity, minZ = Infinity\n let maxX = -Infinity, maxY = -Infinity, maxZ = -Infinity\n\n for (let i = 0; i < vertexCount; i++) {\n const x = positions[i * 3]\n const y = positions[i * 3 + 1]\n const z = positions[i * 3 + 2]\n\n if (x < minX) minX = x\n if (y < minY) minY = y\n if (z < minZ) minZ = z\n if (x > maxX) maxX = x\n if (y > maxY) maxY = y\n if (z > maxZ) maxZ = z\n }\n\n return {\n min: [minX, minY, minZ],\n max: [maxX, maxY, maxZ]\n }\n}\n\n/**\n * Create bounding sphere from AABB\n */\nfunction boundingSphereFromAABB(aabb) {\n const center = [\n (aabb.min[0] + aabb.max[0]) * 0.5,\n (aabb.min[1] + aabb.max[1]) * 0.5,\n (aabb.min[2] + aabb.max[2]) * 0.5\n ]\n\n const dx = aabb.max[0] - aabb.min[0]\n const dy = aabb.max[1] - aabb.min[1]\n const dz = aabb.max[2] - aabb.min[2]\n const radius = Math.sqrt(dx * dx + dy * dy + dz * dz) * 0.5\n\n return { center, radius }\n}\n\n/**\n * Merge two bounding spheres\n */\nfunction mergeBoundingSpheres(a, b) {\n const dx = b.center[0] - a.center[0]\n const dy = b.center[1] - a.center[1]\n const dz = b.center[2] - a.center[2]\n const dist = Math.sqrt(dx * dx + dy * dy + dz * dz)\n\n // Check if one sphere contains the other\n if (dist + b.radius <= a.radius) {\n return { center: [...a.center], radius: a.radius }\n }\n if (dist + a.radius <= b.radius) {\n return { center: [...b.center], radius: b.radius }\n }\n\n // Calculate new sphere that contains both\n const newRadius = (dist + a.radius + b.radius) * 0.5\n const ratio = (newRadius - a.radius) / dist\n\n return {\n center: [\n a.center[0] + dx * ratio,\n a.center[1] + dy * ratio,\n a.center[2] + dz * ratio\n ],\n radius: newRadius\n }\n}\n\n/**\n * Transform a bounding sphere by a matrix\n * @param {Object} bsphere - Bounding sphere\n * @param {mat4} matrix - Transform matrix\n * @returns {Object} Transformed bounding sphere\n */\nfunction transformBoundingSphere(bsphere, matrix) {\n // Transform center\n const center = vec3.create()\n vec3.transformMat4(center, bsphere.center, matrix)\n\n // Get scale factor from matrix (approximate as max axis scale)\n const sx = Math.sqrt(matrix[0] * matrix[0] + matrix[1] * matrix[1] + matrix[2] * matrix[2])\n const sy = Math.sqrt(matrix[4] * matrix[4] + matrix[5] * matrix[5] + matrix[6] * matrix[6])\n const sz = Math.sqrt(matrix[8] * matrix[8] + matrix[9] * matrix[9] + matrix[10] * matrix[10])\n const maxScale = Math.max(sx, sy, sz)\n\n return {\n center: [center[0], center[1], center[2]],\n radius: bsphere.radius * maxScale\n }\n}\n\n/**\n * Test if a bounding sphere is visible in a frustum\n * @param {Object} bsphere - Bounding sphere { center, radius }\n * @param {Float32Array} planes - Frustum planes (6 vec4s)\n * @returns {boolean} True if visible\n */\nfunction sphereInFrustum(bsphere, planes) {\n for (let i = 0; i < 6; i++) {\n const offset = i * 4\n const nx = planes[offset]\n const ny = planes[offset + 1]\n const nz = planes[offset + 2]\n const d = planes[offset + 3]\n\n const dist = nx * bsphere.center[0] + ny * bsphere.center[1] + nz * bsphere.center[2] + d\n\n if (dist < -bsphere.radius) {\n return false // Completely outside this plane\n }\n }\n return true\n}\n\n/**\n * Test if a bounding sphere is within a distance from a point\n * @param {Object} bsphere - Bounding sphere\n * @param {Array} point - Test point [x, y, z]\n * @param {number} maxDistance - Maximum distance\n * @returns {boolean} True if within distance\n */\nfunction sphereWithinDistance(bsphere, point, maxDistance) {\n const dx = bsphere.center[0] - point[0]\n const dy = bsphere.center[1] - point[1]\n const dz = bsphere.center[2] - point[2]\n const dist = Math.sqrt(dx * dx + dy * dy + dz * dz) - bsphere.radius\n return dist <= maxDistance\n}\n\n/**\n * Calculate a \"shadow bounding sphere\" that encompasses both the object\n * and its shadow projection on the ground plane.\n *\n * This is used for shadow map culling optimization - we can cull objects\n * whose shadow spheres are not visible to the camera (frustum/occlusion).\n *\n * The algorithm:\n * 1. Start with the object's world-space bounding sphere\n * 2. Project the sphere onto the ground plane along the light direction\n * - This creates an ellipse on the ground\n * 3. Calculate a bounding sphere that contains both the object sphere\n * and the shadow ellipse\n *\n * @param {Object} bsphere - Object's bounding sphere { center: [x,y,z], radius: r }\n * @param {Array} lightDir - Normalized light direction vector [x,y,z] (pointing TO light)\n * @param {number} groundLevel - Y coordinate of the ground/shadow receiver plane\n * @returns {Object} Shadow bounding sphere { center: [x,y,z], radius: r }\n */\nfunction calculateShadowBoundingSphere(bsphere, lightDir, groundLevel = 0) {\n // Light direction should point FROM object TO light (i.e., towards the sky for sun)\n // If Y component is positive, light comes from above\n const lx = lightDir[0]\n const ly = lightDir[1]\n const lz = lightDir[2]\n\n // Object sphere properties\n const cx = bsphere.center[0]\n const cy = bsphere.center[1]\n const cz = bsphere.center[2]\n const r = bsphere.radius\n\n // If object is below ground level, its shadow is above it (not on ground)\n // In that case, just return the original sphere with some margin\n if (cy + r < groundLevel) {\n return {\n center: [...bsphere.center],\n radius: r * 1.1\n }\n }\n\n // Calculate the height of sphere center above ground\n const heightAboveGround = cy - groundLevel\n\n // If light is nearly horizontal (ly close to 0), shadow extends very far\n // Clamp to a reasonable maximum shadow distance\n const maxShadowDistance = 100\n\n // Calculate shadow center on ground:\n // The center of the sphere projects along light direction to ground\n // shadowPos = center + t * (-lightDir) where t = heightAboveGround / ly\n let shadowCenterX, shadowCenterZ\n\n if (Math.abs(ly) < 0.01) {\n // Nearly horizontal light - shadow extends very far\n // Use a reasonable estimate: shadow at maxShadowDistance in light direction\n const horizLen = Math.sqrt(lx * lx + lz * lz)\n if (horizLen > 0.001) {\n shadowCenterX = cx - (lx / horizLen) * maxShadowDistance\n shadowCenterZ = cz - (lz / horizLen) * maxShadowDistance\n } else {\n shadowCenterX = cx\n shadowCenterZ = cz\n }\n } else {\n // Normal case: calculate projection along light ray\n const t = heightAboveGround / ly\n // Clamp shadow distance\n const clampedT = Math.min(Math.abs(t), maxShadowDistance) * Math.sign(t)\n shadowCenterX = cx - lx * clampedT\n shadowCenterZ = cz - lz * clampedT\n }\n\n // The shadow of a sphere is an ellipse when light is not vertical\n // The ellipse's major axis extends along the light direction\n // For simplicity, we approximate the shadow as a circle with radius\n // that encompasses the ellipse\n\n // Shadow radius depends on the angle of the light\n // When light is vertical (ly=1), shadow radius = object radius\n // When light is at an angle, shadow stretches along the light direction\n const cosAngle = Math.abs(ly)\n const sinAngle = Math.sqrt(1 - cosAngle * cosAngle)\n\n // Shadow elongation factor (how much the shadow stretches)\n // When light angle is 45°, shadow is ~1.4x longer\n // When light angle is 30° (from horizon), shadow is ~2x longer\n const elongation = cosAngle > 0.01 ? 1 / cosAngle : maxShadowDistance / r\n const clampedElongation = Math.min(elongation, maxShadowDistance / Math.max(r, 0.1))\n\n // Shadow \"radius\" (bounding circle of the ellipse)\n const shadowRadius = r * Math.max(1, clampedElongation)\n\n // Now we have two spheres to encompass:\n // 1. Object sphere: center=(cx, cy, cz), radius=r\n // 2. Shadow \"sphere\" on ground: center=(shadowCenterX, groundLevel, shadowCenterZ), radius=shadowRadius\n\n // Create a sphere that contains both\n // Method: find the enclosing sphere of two spheres\n const s1 = {\n center: [cx, cy, cz],\n radius: r\n }\n const s2 = {\n center: [shadowCenterX, groundLevel, shadowCenterZ],\n radius: shadowRadius\n }\n\n // Distance between sphere centers\n const dx = s2.center[0] - s1.center[0]\n const dy = s2.center[1] - s1.center[1]\n const dz = s2.center[2] - s1.center[2]\n const dist = Math.sqrt(dx * dx + dy * dy + dz * dz)\n\n // Check containment\n if (dist + s2.radius <= s1.radius) {\n // Shadow is inside object sphere (unlikely but handle it)\n return { center: [...s1.center], radius: s1.radius * 1.01 }\n }\n if (dist + s1.radius <= s2.radius) {\n // Object is inside shadow sphere\n return { center: [...s2.center], radius: s2.radius * 1.01 }\n }\n\n // Calculate enclosing sphere\n const newRadius = (dist + s1.radius + s2.radius) * 0.5\n\n // Center is along the line between sphere centers\n // Position it so both spheres fit\n const ratio = dist > 0.001 ? (newRadius - s1.radius) / dist : 0.5\n\n return {\n center: [\n s1.center[0] + dx * ratio,\n s1.center[1] + dy * ratio,\n s1.center[2] + dz * ratio\n ],\n radius: newRadius * 1.01 // Small margin\n }\n}\n\n/**\n * Test if a bounding sphere intersects a cascade's orthographic box\n *\n * @param {Object} bsphere - Bounding sphere { center: [x,y,z], radius: r }\n * @param {mat4} cascadeMatrix - Cascade's light view-projection matrix\n * @returns {boolean} True if sphere intersects the cascade box\n */\nfunction sphereInCascade(bsphere, cascadeMatrix) {\n // Transform sphere center to cascade clip space\n const cx = bsphere.center[0]\n const cy = bsphere.center[1]\n const cz = bsphere.center[2]\n\n // Apply cascade view-projection matrix to center\n // clipPos = cascadeMatrix * vec4(center, 1)\n const m = cascadeMatrix\n const w = m[3] * cx + m[7] * cy + m[11] * cz + m[15]\n\n if (Math.abs(w) < 0.0001) return true // Degenerate case, include it\n\n const invW = 1.0 / w\n const clipX = (m[0] * cx + m[4] * cy + m[8] * cz + m[12]) * invW\n const clipY = (m[1] * cx + m[5] * cy + m[9] * cz + m[13]) * invW\n const clipZ = (m[2] * cx + m[6] * cy + m[10] * cz + m[14]) * invW\n\n // Get scale factor for radius (approximate as max axis scale)\n // For orthographic projections, this is simpler\n const sx = Math.sqrt(m[0] * m[0] + m[1] * m[1] + m[2] * m[2])\n const sy = Math.sqrt(m[4] * m[4] + m[5] * m[5] + m[6] * m[6])\n const sz = Math.sqrt(m[8] * m[8] + m[9] * m[9] + m[10] * m[10])\n const maxScale = Math.max(sx, sy, sz)\n const clipRadius = bsphere.radius * maxScale * invW\n\n // NDC box is [-1, 1] for X/Y, [0, 1] for Z in WebGPU\n // Check if sphere (with radius) intersects this box\n if (clipX + clipRadius < -1 || clipX - clipRadius > 1) return false\n if (clipY + clipRadius < -1 || clipY - clipRadius > 1) return false\n if (clipZ + clipRadius < 0 || clipZ - clipRadius > 1) return false\n\n return true\n}\n\nexport {\n calculateBoundingSphere,\n calculateAABB,\n boundingSphereFromAABB,\n mergeBoundingSpheres,\n transformBoundingSphere,\n sphereInFrustum,\n sphereWithinDistance,\n calculateShadowBoundingSphere,\n sphereInCascade\n}\n","import { calculateBoundingSphere } from \"./utils/BoundingSphere.js\"\n\nvar _UID = 30001\n\nclass Geometry {\n\n constructor(engine, attributes) {\n this.uid = _UID++\n this.engine = engine\n const { device } = engine\n\n // Bounding sphere (lazy calculated)\n this._bsphere = null\n\n this.vertexBufferLayout = {\n arrayStride: 80, // 20 floats * 4 bytes each\n attributes: [\n { format: \"float32x3\", offset: 0, shaderLocation: 0 }, // position\n { format: \"float32x2\", offset: 12, shaderLocation: 1 }, // uv\n { format: \"float32x3\", offset: 20, shaderLocation: 2 }, // normal\n { format: \"float32x4\", offset: 32, shaderLocation: 3 }, // color\n { format: \"float32x4\", offset: 48, shaderLocation: 4 }, // weights\n { format: \"uint32x4\", offset: 64, shaderLocation: 5 }, // joints\n ],\n stepMode: 'vertex'\n };\n\n // Instance buffer layout for model matrix + sprite data\n this.instanceBufferLayout = {\n arrayStride: 112, // 28 floats * 4 bytes each\n stepMode: 'instance',\n attributes: [\n { format: \"float32x4\", offset: 0, shaderLocation: 6 }, // matrix column 0\n { format: \"float32x4\", offset: 16, shaderLocation: 7 }, // matrix column 1\n { format: \"float32x4\", offset: 32, shaderLocation: 8 }, // matrix column 2\n { format: \"float32x4\", offset: 48, shaderLocation: 9 }, // matrix column 3\n { format: \"float32x4\", offset: 64, shaderLocation: 10 }, // position + radius\n { format: \"float32x4\", offset: 80, shaderLocation: 11 }, // uvTransform (offset.xy, scale.xy)\n { format: \"float32x4\", offset: 96, shaderLocation: 12 }, // color (r, g, b, a)\n ]\n };\n\n const vertexCount = attributes.position.length / 3\n\n if (attributes.indices == false) {\n // Generate indices for non-indexed geometry\n const positions = attributes.position;\n const vertexCount = positions.length / 3; // 3 components per vertex\n const indices = new Uint32Array(vertexCount);\n for (let i = 0; i < vertexCount; i++) {\n indices[i] = i;\n }\n attributes.indices = indices\n }\n\n if (attributes.normal == false) {\n // Generate flat normals for non-normal geometry\n const positions = attributes.position;\n const indices = attributes.indices;\n const normals = new Float32Array(positions.length);\n\n // Calculate normals for each triangle\n for (let i = 0; i < indices.length; i += 3) {\n const i0 = indices[i] * 3;\n const i1 = indices[i + 1] * 3;\n const i2 = indices[i + 2] * 3;\n\n // Get vertices of triangle\n const v0 = [positions[i0], positions[i0 + 1], positions[i0 + 2]];\n const v1 = [positions[i1], positions[i1 + 1], positions[i1 + 2]];\n const v2 = [positions[i2], positions[i2 + 1], positions[i2 + 2]];\n\n // Calculate vectors for cross product\n const vec1 = [v1[0] - v0[0], v1[1] - v0[1], v1[2] - v0[2]];\n const vec2 = [v2[0] - v0[0], v2[1] - v0[1], v2[2] - v0[2]];\n\n // Calculate cross product\n const normal = [\n vec1[1] * vec2[2] - vec1[2] * vec2[1],\n vec1[2] * vec2[0] - vec1[0] * vec2[2],\n vec1[0] * vec2[1] - vec1[1] * vec2[0]\n ];\n\n // Normalize\n const length = Math.sqrt(normal[0] * normal[0] + normal[1] * normal[1] + normal[2] * normal[2]);\n normal[0] /= length;\n normal[1] /= length;\n normal[2] /= length;\n\n // Assign same normal to all vertices of triangle\n normals[i0] = normal[0];\n normals[i0 + 1] = normal[1];\n normals[i0 + 2] = normal[2];\n normals[i1] = normal[0];\n normals[i1 + 1] = normal[1];\n normals[i1 + 2] = normal[2];\n normals[i2] = normal[0];\n normals[i2 + 1] = normal[1];\n normals[i2 + 2] = normal[2];\n }\n\n attributes.normal = normals\n }\n\n\n const vertexArray = new Float32Array(vertexCount * 20);\n this.vertexCount = vertexCount;\n const vertexArrayUint32 = new Uint32Array(vertexArray.buffer);\n const vertexBuffer = device.createBuffer({\n size: vertexArray.byteLength,\n usage: GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_DST,\n })\n\n // Create instance buffer (start small, grows dynamically)\n const initialMaxInstances = 16;\n const instanceBuffer = device.createBuffer({\n size: 112 * initialMaxInstances, // 28 floats per instance\n usage: GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_DST,\n });\n this.instanceBuffer = instanceBuffer;\n this.maxInstances = initialMaxInstances;\n this.instanceCount = 0;\n this.instanceData = new Float32Array(28 * initialMaxInstances);\n this._instanceDataDirty = true\n\n const indexArray = new Uint32Array(attributes.indices.length); \n const indexBuffer = device.createBuffer({\n size: indexArray.byteLength,\n usage: GPUBufferUsage.INDEX | GPUBufferUsage.COPY_DST,\n })\n\n this.attributes = attributes\n this.vertexArray = vertexArray\n this.vertexArrayUint32 = vertexArrayUint32\n this.indexArray = indexArray\n\n this.vertexBuffer = vertexBuffer\n this.indexBuffer = indexBuffer\n\n this.updateVertexBuffer()\n }\n\n\n /**\n * Grow instance buffer capacity by doubling it\n * @param {number} minCapacity - Minimum required capacity (optional)\n */\n growInstanceBuffer(minCapacity = 0) {\n const { device } = this.engine;\n\n // Calculate new size: double current, or enough for minCapacity\n let newMaxInstances = this.maxInstances * 2;\n while (newMaxInstances < minCapacity) {\n newMaxInstances *= 2;\n }\n\n // Create new larger buffer\n const newInstanceBuffer = device.createBuffer({\n size: 112 * newMaxInstances, // 28 floats per instance\n usage: GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_DST,\n });\n\n // Create new larger data array and copy existing data\n const newInstanceData = new Float32Array(28 * newMaxInstances);\n newInstanceData.set(this.instanceData);\n\n // Destroy old buffer\n this.instanceBuffer.destroy();\n\n // Update references\n this.instanceBuffer = newInstanceBuffer;\n this.instanceData = newInstanceData;\n this.maxInstances = newMaxInstances;\n this._instanceDataDirty = true;\n }\n\n addInstance(position, radius = 1, uvTransform = [0, 0, 1, 1], color = [1, 1, 1, 1]) {\n if (this.instanceCount >= this.maxInstances) {\n this.growInstanceBuffer();\n }\n\n // Create a 4x4 transform matrix for this instance\n const matrix = mat4.create();\n mat4.translate(matrix, matrix, position);\n\n // Add the matrix to instance data at the next available slot\n const offset = this.instanceCount * 28;\n this.instanceData.set(matrix, offset);\n // Position + radius (would normally be set from bsphere)\n this.instanceData[offset + 16] = position[0];\n this.instanceData[offset + 17] = position[1];\n this.instanceData[offset + 18] = position[2];\n this.instanceData[offset + 19] = radius;\n // UV transform\n this.instanceData[offset + 20] = uvTransform[0];\n this.instanceData[offset + 21] = uvTransform[1];\n this.instanceData[offset + 22] = uvTransform[2];\n this.instanceData[offset + 23] = uvTransform[3];\n // Color\n this.instanceData[offset + 24] = color[0];\n this.instanceData[offset + 25] = color[1];\n this.instanceData[offset + 26] = color[2];\n this.instanceData[offset + 27] = color[3];\n\n this.instanceCount++;\n this._instanceDataDirty = true\n }\n\n updateAllInstances(matrices) {\n const { device } = this.engine;\n this.instanceCount = matrices.length;\n if (this.instanceCount > this.maxInstances) {\n this.growInstanceBuffer(this.instanceCount);\n }\n\n // Copy all matrices into instance data (28 floats per instance)\n for (let i = 0; i < matrices.length; i++) {\n const offset = i * 28;\n this.instanceData.set(matrices[i], offset);\n // Set default UV transform and color if not provided\n this.instanceData[offset + 20] = 0; // uvOffset.x\n this.instanceData[offset + 21] = 0; // uvOffset.y\n this.instanceData[offset + 22] = 1; // uvScale.x\n this.instanceData[offset + 23] = 1; // uvScale.y\n this.instanceData[offset + 24] = 1; // color.r\n this.instanceData[offset + 25] = 1; // color.g\n this.instanceData[offset + 26] = 1; // color.b\n this.instanceData[offset + 27] = 1; // color.a\n }\n\n this._instanceDataDirty = true\n }\n\n updateInstance(index, matrix) {\n this.instanceData.set(matrix, index * 28);\n this._instanceDataDirty = true\n }\n\n writeInstanceBuffer() {\n const { device } = this.engine;\n\n device.queue.writeBuffer(this.instanceBuffer, 0, this.instanceData);\n }\n\n updateVertexBuffer() {\n const { device } = this.engine\n const { attributes, vertexArray, vertexArrayUint32 } = this\n // Interleave vertex attributes into a single array\n for (let i = 0; i < this.vertexCount; i++) {\n const vertexOffset = i * 20; // 20 floats per vertex\n \n // Position (xyz)\n vertexArray[vertexOffset] = attributes.position[i * 3];\n vertexArray[vertexOffset + 1] = attributes.position[i * 3 + 1];\n vertexArray[vertexOffset + 2] = attributes.position[i * 3 + 2];\n\n // UV (xy) \n vertexArray[vertexOffset + 3] = attributes.uv ? attributes.uv[i * 2] : 0;\n vertexArray[vertexOffset + 4] = attributes.uv ? attributes.uv[i * 2 + 1] : 0;\n\n // Normal (xyz)\n vertexArray[vertexOffset + 5] = attributes.normal[i * 3]\n vertexArray[vertexOffset + 6] = attributes.normal[i * 3 + 1]\n vertexArray[vertexOffset + 7] = attributes.normal[i * 3 + 2]\n\n // Color (rgba)\n vertexArray[vertexOffset + 8] = attributes.color ? attributes.color[i * 4] : 1;\n vertexArray[vertexOffset + 9] = attributes.color ? attributes.color[i * 4 + 1] : 1;\n vertexArray[vertexOffset + 10] = attributes.color ? attributes.color[i * 4 + 2] : 1;\n vertexArray[vertexOffset + 11] = attributes.color ? attributes.color[i * 4 + 3] : 1;\n\n // Weights (xyzw)\n vertexArray[vertexOffset + 12] = attributes.weights ? attributes.weights[i * 4] : 0;\n vertexArray[vertexOffset + 13] = attributes.weights ? attributes.weights[i * 4 + 1] : 0;\n vertexArray[vertexOffset + 14] = attributes.weights ? attributes.weights[i * 4 + 2] : 0;\n vertexArray[vertexOffset + 15] = attributes.weights ? attributes.weights[i * 4 + 3] : 0;\n\n // Joints (xyzw) - using Float32Array view to write uint32 values\n const jointsOffset = vertexOffset + 16;\n vertexArrayUint32[jointsOffset] = attributes.joints ? attributes.joints[i * 4] : 0;\n vertexArrayUint32[jointsOffset + 1] = attributes.joints ? attributes.joints[i * 4 + 1] : 0;\n vertexArrayUint32[jointsOffset + 2] = attributes.joints ? attributes.joints[i * 4 + 2] : 0;\n vertexArrayUint32[jointsOffset + 3] = attributes.joints ? attributes.joints[i * 4 + 3] : 0;\n }\n \n // Copy indices\n for (let i = 0; i < attributes.indices.length; i++) {\n this.indexArray[i] = attributes.indices[i];\n }\n\n device.queue.writeBuffer(this.vertexBuffer, 0, this.vertexArray)\n device.queue.writeBuffer(this.indexBuffer, 0, this.indexArray)\n }\n\n update() {\n if (this._instanceDataDirty) {\n this.writeInstanceBuffer()\n this._instanceDataDirty = false\n }\n }\n\n /**\n * Get bounding sphere for this geometry (lazy calculated)\n * @returns {{ center: [number, number, number], radius: number }}\n */\n getBoundingSphere() {\n if (!this._bsphere) {\n this._bsphere = calculateBoundingSphere(this.attributes.position)\n }\n return this._bsphere\n }\n\n /**\n * Invalidate cached bounding sphere (call after modifying positions)\n */\n invalidateBoundingSphere() {\n this._bsphere = null\n }\n\n static createCullingBuffers(device, maxInstances) {\n // Input buffer containing instance data (position + radius)\n const instanceDataBuffer = device.createBuffer({\n size: maxInstances * 16, // vec3 position + float radius\n usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST,\n });\n\n // Output buffer containing active instance indices and count\n const culledInstancesBuffer = device.createBuffer({\n size: (maxInstances + 1) * 4, // indices + count at start\n usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC,\n });\n\n // Uniform buffer for camera frustum planes\n const frustumBuffer = device.createBuffer({\n size: 96, // 6 planes * vec4\n usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST,\n });\n\n const computePipeline = device.createComputePipeline({\n layout: 'auto',\n compute: {\n module: device.createShaderModule({\n code: `\n struct Instance {\n position: vec3f,\n radius: f32\n }\n\n struct FrustumPlanes {\n planes: array<vec4f, 6>\n }\n\n @group(0) @binding(0) var<storage, read> instances: array<Instance>;\n @group(0) @binding(1) var<storage, read_write> culledInstances: array<u32>;\n @group(0) @binding(2) var<uniform> frustum: FrustumPlanes;\n\n fn sphereAgainstPlane(center: vec3f, radius: f32, plane: vec4f) -> bool {\n let dist = dot(vec4f(center, 1.0), plane);\n return dist > -radius;\n }\n\n @compute @workgroup_size(64)\n fn main(@builtin(global_invocation_id) global_id: vec3u) {\n let index = global_id.x;\n if (index >= arrayLength(&instances)) {\n return;\n }\n\n let instance = instances[index];\n \n // Test against all frustum planes\n var visible = true;\n for (var i = 0u; i < 6u; i++) {\n if (!sphereAgainstPlane(instance.position, instance.radius, frustum.planes[i])) {\n visible = false;\n break;\n }\n }\n\n if (visible) {\n let oldCount = atomicAdd(&culledInstances[0], 1);\n culledInstances[oldCount + 1] = index;\n }\n }\n `\n }),\n entryPoint: 'main'\n }\n });\n\n const bindGroup = device.createBindGroup({\n layout: computePipeline.getBindGroupLayout(0),\n entries: [\n {\n binding: 0,\n resource: { buffer: instanceDataBuffer }\n },\n {\n binding: 1, \n resource: { buffer: culledInstancesBuffer }\n },\n {\n binding: 2,\n resource: { buffer: frustumBuffer }\n }\n ]\n });\n\n return {\n pipeline: computePipeline,\n bindGroup: bindGroup,\n instanceDataBuffer,\n culledInstancesBuffer,\n frustumBuffer\n };\n }\n\n static cullInstances(engine, commandEncoder, cullingData, instances, frustumPlanes) {\n const { device } = engine;\n const { pipeline, bindGroup, instanceDataBuffer, culledInstancesBuffer, frustumBuffer } = cullingData;\n\n // Write instance data\n device.queue.writeBuffer(instanceDataBuffer, 0, instances);\n \n // Write frustum planes\n device.queue.writeBuffer(frustumBuffer, 0, frustumPlanes);\n\n // Clear count to 0\n const zeros = new Uint32Array(1);\n device.queue.writeBuffer(culledInstancesBuffer, 0, zeros);\n\n // Dispatch compute shader\n const computePass = commandEncoder.beginComputePass();\n computePass.setPipeline(pipeline);\n computePass.setBindGroup(0, bindGroup);\n computePass.dispatchWorkgroups(Math.ceil(instances.length / 64));\n computePass.end();\n\n return culledInstancesBuffer;\n }\n\n static cube(engine) {\n return Geometry.box(engine, 1, 1, 1, 1)\n }\n\n static box(engine, width = 1, height = 1, depth = 1, uvSize = 1) {\n const w = width / 2\n const h = height / 2 \n const d = depth / 2\n\n // UV scale based on dimensions and uvSize\n const uw = width / uvSize\n const uh = height / uvSize\n const ud = depth / uvSize\n\n return new Geometry(engine, {\n position: new Float32Array([\n // Front face\n -w, -h, d,\n w, -h, d,\n w, h, d,\n -w, h, d,\n // Back face\n -w, -h, -d,\n -w, h, -d,\n w, h, -d,\n w, -h, -d,\n // Top face\n -w, h, -d,\n -w, h, d,\n w, h, d,\n w, h, -d,\n // Bottom face\n -w, -h, -d,\n w, -h, -d,\n w, -h, d,\n -w, -h, d,\n // Right face\n w, -h, -d,\n w, h, -d,\n w, h, d,\n w, -h, d,\n // Left face\n -w, -h, -d,\n -w, -h, d,\n -w, h, d,\n -w, h, -d,\n ]),\n uv: new Float32Array([\n // Front face\n 0, 0,\n uw, 0,\n uw, uh,\n 0, uh,\n // Back face\n 0, 0,\n 0, uh,\n uw, uh,\n uw, 0,\n // Top face\n 0, 0,\n 0, ud,\n uw, ud,\n uw, 0,\n // Bottom face\n 0, 0,\n uw, 0,\n uw, ud,\n 0, ud,\n // Right face\n 0, 0,\n 0, uh,\n ud, uh,\n ud, 0,\n // Left face\n 0, 0,\n ud, 0,\n ud, uh,\n 0, uh,\n ]),\n normal: new Float32Array([\n // Front face\n 0, 0, 1,\n 0, 0, 1,\n 0, 0, 1,\n 0, 0, 1,\n // Back face\n 0, 0, -1,\n 0, 0, -1,\n 0, 0, -1,\n 0, 0, -1,\n // Top face\n 0, 1, 0,\n 0, 1, 0,\n 0, 1, 0,\n 0, 1, 0,\n // Bottom face\n 0, -1, 0,\n 0, -1, 0,\n 0, -1, 0,\n 0, -1, 0,\n // Right face\n 1, 0, 0,\n 1, 0, 0,\n 1, 0, 0,\n 1, 0, 0,\n // Left face\n -1, 0, 0,\n -1, 0, 0,\n -1, 0, 0,\n -1, 0, 0,\n ]),\n indices: new Uint32Array([\n 0, 1, 2, 2, 3, 0, // Front face\n 4, 5, 6, 6, 7, 4, // Back face\n 8, 9, 10, 10, 11, 8, // Top face\n 12, 13, 14, 14, 15, 12, // Bottom face\n 16, 17, 18, 18, 19, 16, // Right face\n 20, 21, 22, 22, 23, 20 // Left face\n ])\n })\n }\n static sphere(engine, radius = 1, widthSegments = 32, heightSegments = 16) {\n const positions = []\n const normals = []\n const uvs = []\n const indices = []\n\n // Generate vertices\n for (let y = 0; y <= heightSegments; y++) {\n const v = y / heightSegments\n const phi = v * Math.PI\n\n for (let x = 0; x <= widthSegments; x++) {\n const u = x / widthSegments\n const theta = u * Math.PI * 2\n\n // Calculate vertex position\n const px = -radius * Math.cos(theta) * Math.sin(phi)\n const py = radius * Math.cos(phi)\n const pz = radius * Math.sin(theta) * Math.sin(phi)\n\n positions.push(px, py, pz)\n\n // Normal is just the normalized position for a sphere\n const length = Math.sqrt(px * px + py * py + pz * pz)\n normals.push(px / length, py / length, pz / length)\n\n // UV coordinates\n uvs.push(u, 1 - v)\n }\n }\n\n // Generate indices\n for (let y = 0; y < heightSegments; y++) {\n for (let x = 0; x < widthSegments; x++) {\n const a = y * (widthSegments + 1) + x\n const b = a + 1\n const c = a + widthSegments + 1\n const d = c + 1\n\n // Generate two triangles for each quad (counter-clockwise winding)\n indices.push(a, d, b)\n indices.push(a, c, d)\n }\n }\n\n return new Geometry(engine, {\n position: new Float32Array(positions),\n normal: new Float32Array(normals),\n uv: new Float32Array(uvs),\n indices: new Uint32Array(indices)\n })\n }\n\n static quad(engine) {\n return new Geometry(engine, {\n position: [\n -1, -1, 0,\n 1, -1, 0,\n 1, 1, 0,\n -1, 1, 0,\n ],\n uv: [\n 0, 0,\n 1, 0,\n 1, 1,\n 0, 1,\n ],\n normal: [\n // Normals up\n 0, 1, 0,\n 0, 1, 0,\n 0, 1, 0,\n 0, 1, 0,\n ],\n index: [\n 0, 1, 2,\n 2, 3, 0\n ]\n\n })\n }\n\n /**\n * Create a quad geometry for billboard/sprite rendering\n * @param {Engine} engine - Engine instance\n * @param {string} pivot - Pivot mode: 'center', 'bottom', or 'horizontal'\n * @returns {Geometry} Billboard quad geometry\n */\n static billboardQuad(engine, pivot = 'center') {\n let position, normal\n\n if (pivot === 'center') {\n // Center pivot: quad centered at origin, facing +Z\n position = new Float32Array([\n -0.5, -0.5, 0,\n 0.5, -0.5, 0,\n 0.5, 0.5, 0,\n -0.5, 0.5, 0,\n ])\n normal = new Float32Array([\n 0, 0, 1,\n 0, 0, 1,\n 0, 0, 1,\n 0, 0, 1,\n ])\n } else if (pivot === 'bottom') {\n // Bottom pivot: quad with bottom edge at origin, facing +Z\n position = new Float32Array([\n -0.5, 0, 0,\n 0.5, 0, 0,\n 0.5, 1, 0,\n -0.5, 1, 0,\n ])\n normal = new Float32Array([\n 0, 0, 1,\n 0, 0, 1,\n 0, 0, 1,\n 0, 0, 1,\n ])\n } else if (pivot === 'horizontal') {\n // Horizontal pivot: quad flat on XZ plane (ground decal)\n // Front face points UP (+Y) with CCW winding when viewed from above\n position = new Float32Array([\n -0.5, 0, -0.5, // 0: back left\n 0.5, 0, -0.5, // 1: back right\n 0.5, 0, 0.5, // 2: front right\n -0.5, 0, 0.5, // 3: front left\n ])\n normal = new Float32Array([\n 0, 1, 0,\n 0, 1, 0,\n 0, 1, 0,\n 0, 1, 0,\n ])\n // Use reversed winding for horizontal (CCW from above)\n return new Geometry(engine, {\n position,\n uv: new Float32Array([\n 0, 1, // back left -> top-left of texture\n 1, 1, // back right -> top-right\n 1, 0, // front right -> bottom-right\n 0, 0, // front left -> bottom-left\n ]),\n normal,\n indices: new Uint32Array([\n 0, 3, 2, // CCW: back-left, front-left, front-right\n 2, 1, 0 // CCW: front-right, back-right, back-left\n ])\n })\n } else {\n // Default to center pivot\n return Geometry.billboardQuad(engine, 'center')\n }\n\n return new Geometry(engine, {\n position,\n uv: new Float32Array([\n 0, 0, // Bottom-left: sample from v=0 (bottom of texture)\n 1, 0, // Bottom-right\n 1, 1, // Top-right: sample from v=1 (top of texture)\n 0, 1, // Top-left\n ]),\n normal,\n indices: new Uint32Array([\n 0, 1, 2,\n 2, 3, 0\n ])\n })\n }\n\n simplify(angleThreshold = 0.1) {\n // Early return if no UV coordinates or not enough vertices\n if (!this.attributes.uv || this.attributes.indices.length < 6) {\n return this;\n }\n\n const positions = this.attributes.position;\n const normals = this.attributes.normal;\n const uvs = this.attributes.uv;\n const indices = this.attributes.indices;\n const colors = this.attributes.color;\n const joints = this.attributes.joints;\n const weights = this.attributes.weights;\n\n let newPositions = [];\n let newNormals = [];\n let newUVs = [];\n let newIndices = [];\n let newColors = colors ? [] : null;\n let newJoints = joints ? [] : null;\n let newWeights = weights ? [] : null;\n \n // Process triangles in pairs\n for (let i = 0; i < indices.length; i += 6) {\n if (i + 5 >= indices.length) {\n // Add remaining triangle if we can't make a pair\n for (let j = 0; j < 3; j++) {\n const idx = indices[i + j];\n newPositions.push(positions[idx * 3], positions[idx * 3 + 1], positions[idx * 3 + 2]);\n newNormals.push(normals[idx * 3], normals[idx * 3 + 1], normals[idx * 3 + 2]);\n newUVs.push(uvs[idx * 2], uvs[idx * 2 + 1]);\n if (colors) {\n newColors.push(colors[idx * 4], colors[idx * 4 + 1], colors[idx * 4 + 2], colors[idx * 4 + 3]);\n }\n if (joints) {\n newJoints.push(joints[idx * 4], joints[idx * 4 + 1], joints[idx * 4 + 2], joints[idx * 4 + 3]);\n }\n if (weights) {\n newWeights.push(weights[idx * 4], weights[idx * 4 + 1], weights[idx * 4 + 2], weights[idx * 4 + 3]);\n }\n }\n newIndices.push(newPositions.length / 3 - 3, newPositions.length / 3 - 2, newPositions.length / 3 - 1);\n continue;\n }\n\n // Get the two triangles\n const tri1 = [indices[i], indices[i + 1], indices[i + 2]];\n const tri2 = [indices[i + 3], indices[i + 4], indices[i + 5]];\n\n // Calculate normals of both triangles\n const normal1 = new Vector3(\n normals[tri1[0] * 3], normals[tri1[0] * 3 + 1], normals[tri1[0] * 3 + 2]\n );\n const normal2 = new Vector3(\n normals[tri2[0] * 3], normals[tri2[0] * 3 + 1], normals[tri2[0] * 3 + 2]\n );\n\n // Check if triangles are nearly coplanar\n const angle = Math.acos(normal1.dot(normal2));\n \n if (angle < angleThreshold) {\n // Find shared vertices\n const sharedVertices = tri1.filter(v => tri2.includes(v));\n \n if (sharedVertices.length === 2) {\n // Get unique vertices\n const uniqueFromTri1 = tri1.find(v => !tri2.includes(v));\n const uniqueFromTri2 = tri2.find(v => !tri1.includes(v));\n \n // Create new quad from the four points\n const quadIndices = [uniqueFromTri1, ...sharedVertices, uniqueFromTri2];\n \n // Add vertices to new arrays\n for (const idx of quadIndices) {\n newPositions.push(positions[idx * 3], positions[idx * 3 + 1], positions[idx * 3 + 2]);\n newNormals.push(normals[idx * 3], normals[idx * 3 + 1], normals[idx * 3 + 2]);\n newUVs.push(uvs[idx * 2], uvs[idx * 2 + 1]);\n if (colors) {\n newColors.push(colors[idx * 4], colors[idx * 4 + 1], colors[idx * 4 + 2], colors[idx * 4 + 3]);\n }\n if (joints) {\n newJoints.push(joints[idx * 4], joints[idx * 4 + 1], joints[idx * 4 + 2], joints[idx * 4 + 3]);\n }\n if (weights) {\n newWeights.push(weights[idx * 4], weights[idx * 4 + 1], weights[idx * 4 + 2], weights[idx * 4 + 3]);\n }\n }\n \n // Add indices for the simplified quad (as two triangles)\n const baseIndex = newPositions.length / 3 - 4;\n newIndices.push(\n baseIndex, baseIndex + 1, baseIndex + 2,\n baseIndex + 2, baseIndex + 3, baseIndex\n );\n continue;\n }\n }\n \n // If we can't simplify, add original triangles\n for (let j = 0; j < 6; j++) {\n const idx = indices[i + j];\n newPositions.push(positions[idx * 3], positions[idx * 3 + 1], positions[idx * 3 + 2]);\n newNormals.push(normals[idx * 3], normals[idx * 3 + 1], normals[idx * 3 + 2]);\n newUVs.push(uvs[idx * 2], uvs[idx * 2 + 1]);\n if (colors) {\n newColors.push(colors[idx * 4], colors[idx * 4 + 1], colors[idx * 4 + 2], colors[idx * 4 + 3]);\n }\n if (joints) {\n newJoints.push(joints[idx * 4], joints[idx * 4 + 1], joints[idx * 4 + 2], joints[idx * 4 + 3]);\n }\n if (weights) {\n newWeights.push(weights[idx * 4], weights[idx * 4 + 1], weights[idx * 4 + 2], weights[idx * 4 + 3]);\n }\n newIndices.push(newPositions.length / 3 - 1);\n }\n }\n\n const geometry = {\n position: new Float32Array(newPositions),\n normal: new Float32Array(newNormals),\n uv: new Float32Array(newUVs),\n index: new Uint32Array(newIndices)\n };\n\n if (colors) {\n geometry.color = new Float32Array(newColors);\n }\n if (joints) {\n geometry.joints = new Uint32Array(newJoints);\n }\n if (weights) {\n geometry.weights = new Float32Array(newWeights);\n }\n\n return new Geometry(geometry);\n }\n\n static unrepetizeUVs(geometry) {\n const positions = geometry.attributes.position;\n const normals = geometry.attributes.normal;\n const uvs = geometry.attributes.uv;\n const indices = geometry.attributes.indices;\n const colors = geometry.attributes.color;\n const joints = geometry.attributes.joints;\n const weights = geometry.attributes.weights;\n\n const newPositions = [];\n const newNormals = [];\n const newUVs = [];\n const newIndices = [];\n const newColors = colors ? [] : null;\n const newJoints = joints ? [] : null;\n const newWeights = weights ? [] : null;\n\n // Process each triangle\n for (let i = 0; i < indices.length; i += 3) {\n const tri = [indices[i], indices[i + 1], indices[i + 2]];\n \n // Get UV coordinates for the triangle\n const uvCoords = tri.map(idx => [uvs[idx * 2], uvs[idx * 2 + 1]]);\n \n // Get integer and fractional parts of UVs\n const uvInts = uvCoords.map(uv => [\n Math.floor(Math.abs(uv[0])),\n Math.floor(Math.abs(uv[1]))\n ]);\n const uvFracs = uvCoords.map(uv => [\n Math.abs(uv[0]) % 1,\n Math.abs(uv[1]) % 1\n ]);\n \n // Find max UV integer values to determine grid size\n const maxU = Math.max(...uvInts.map(uv => uv[0]));\n const maxV = Math.max(...uvInts.map(uv => uv[1]));\n \n if (maxU > 0 || maxV > 0) {\n // Need to split into grid\n for (let u = 0; u <= maxU; u++) {\n for (let v = 0; v <= maxV; v++) {\n // Generate vertices for this grid cell\n const cellVertices = [];\n \n // For each vertex of original triangle\n for (let j = 0; j < 3; j++) {\n const origPos = [\n positions[tri[j] * 3],\n positions[tri[j] * 3 + 1], \n positions[tri[j] * 3 + 2]\n ];\n const origNorm = [\n normals[tri[j] * 3],\n normals[tri[j] * 3 + 1],\n normals[tri[j] * 3 + 2]\n ];\n \n // Calculate UV for this cell\n const cellUV = [\n (uvFracs[j][0] + u) / (maxU + 1),\n (uvFracs[j][1] + v) / (maxV + 1)\n ];\n \n const idx = newPositions.length / 3;\n newPositions.push(...origPos);\n newNormals.push(...origNorm);\n newUVs.push(...cellUV);\n\n if (colors) {\n newColors.push(\n colors[tri[j] * 4],\n colors[tri[j] * 4 + 1],\n colors[tri[j] * 4 + 2],\n colors[tri[j] * 4 + 3]\n );\n }\n if (joints) {\n newJoints.push(\n joints[tri[j] * 4],\n joints[tri[j] * 4 + 1],\n joints[tri[j] * 4 + 2],\n joints[tri[j] * 4 + 3]\n );\n }\n if (weights) {\n newWeights.push(\n weights[tri[j] * 4],\n weights[tri[j] * 4 + 1],\n weights[tri[j] * 4 + 2],\n weights[tri[j] * 4 + 3]\n );\n }\n\n cellVertices.push(idx);\n }\n \n // Add triangle indices for this cell\n newIndices.push(\n cellVertices[0],\n cellVertices[1],\n cellVertices[2]\n );\n }\n }\n \n } else {\n // Triangle doesn't need splitting, add as-is with normalized UVs\n for (let j = 0; j < 3; j++) {\n const idx = tri[j];\n newPositions.push(\n positions[idx * 3],\n positions[idx * 3 + 1],\n positions[idx * 3 + 2]\n );\n newNormals.push(\n normals[idx * 3],\n normals[idx * 3 + 1],\n normals[idx * 3 + 2]\n );\n newUVs.push(\n uvFracs[j][0],\n uvFracs[j][1]\n );\n\n if (colors) {\n newColors.push(\n colors[idx * 4],\n colors[idx * 4 + 1],\n colors[idx * 4 + 2],\n colors[idx * 4 + 3]\n );\n }\n if (joints) {\n newJoints.push(\n joints[idx * 4],\n joints[idx * 4 + 1],\n joints[idx * 4 + 2],\n joints[idx * 4 + 3]\n );\n }\n if (weights) {\n newWeights.push(\n weights[idx * 4],\n weights[idx * 4 + 1],\n weights[idx * 4 + 2],\n weights[idx * 4 + 3]\n );\n }\n }\n \n const baseIdx = newPositions.length / 3 - 3;\n newIndices.push(baseIdx, baseIdx + 1, baseIdx + 2);\n }\n }\n\n const result = {\n position: new Float32Array(newPositions),\n normal: new Float32Array(newNormals),\n uv: new Float32Array(newUVs),\n index: new Uint32Array(newIndices)\n };\n\n if (colors) {\n result.color = new Float32Array(newColors);\n }\n if (joints) {\n result.joints = new Uint32Array(newJoints);\n }\n if (weights) {\n result.weights = new Float32Array(newWeights);\n }\n\n return new Geometry(result);\n }\n \n}\n\n\nexport { Geometry }\n","var _UID = 10001\n\nclass Material {\n\n constructor(textures = [], uniforms = {}, name = null, engine = null) {\n this.uid = _UID++\n this.name = name || `Material_${this.uid}`\n this.uniforms = uniforms\n this._textures = textures\n\n this.engine = engine\n // If any textures have .engine, use that\n for (const tex of textures) {\n if (tex && tex.engine) {\n this.engine = tex.engine\n break\n }\n }\n\n // Transparency settings\n this.transparent = false // True for alpha blended materials (glass, water)\n this.opacity = 1.0 // Base opacity value (0-1)\n this.opacityTexture = null // Optional opacity texture\n\n // Alpha hashing settings (for cutout transparency like leaves)\n this.alphaHash = false\n this.alphaHashScale = 1.0\n\n // Alpha source settings\n this.luminanceToAlpha = false // Use base color luminance as alpha (black=transparent)\n\n // Force emissive: use base color (albedo) as emission\n this.forceEmissive = false\n\n // Specular boost: enables 3 additional specular lights for shiny materials (0-1, default 0 = disabled)\n this.specularBoost = 0\n\n // Double-sided rendering: disable backface culling\n this.doubleSided = false\n }\n\n /**\n * Get textures array, substituting albedo for emission if forceEmissive is true\n * Texture order: [albedo, normal, ambient, rm, emission]\n */\n get textures() {\n if (this.forceEmissive && this._textures.length >= 5 && this._textures[0]) {\n // Return copy with albedo texture as emission (index 4)\n const result = [...this._textures]\n result[4] = this._textures[0] // Use albedo as emission\n return result\n }\n return this._textures\n }\n\n /**\n * Set textures array directly\n */\n set textures(value) {\n this._textures = value\n }\n}\n\nexport { Material }\n","import { mat4, vec3 } from \"./math.js\"\n\nvar _UID = 20001\n\nclass Mesh {\n constructor(geometry, material, name = null) {\n this.engine = geometry.engine || material.engine\n this.uid = _UID++\n this.name = name || `Mesh_${this.uid}`\n this.geometry = geometry\n this.material = material\n\n // Cache for rotation to avoid recalculating from matrix\n this._rotation = { yaw: 0, pitch: 0, roll: 0 }\n this._scale = [1, 1, 1]\n }\n\n /**\n * Add an instance with position and optional bounding sphere radius\n * @param {Array} position - Position [x, y, z] or bounding sphere center\n * @param {number} radius - Bounding sphere radius (default 1)\n * @param {Array} uvTransform - UV transform [offsetX, offsetY, scaleX, scaleY] (default [0,0,1,1])\n * @param {Array} color - RGBA color tint (default [1,1,1,1])\n */\n addInstance(position, radius = 1, uvTransform = [0, 0, 1, 1], color = [1, 1, 1, 1]) {\n this.geometry.addInstance(position, radius, uvTransform, color)\n }\n\n updateInstance(index, matrix) {\n this.geometry.updateInstance(index, matrix)\n }\n\n /**\n * Get position of the first instance (legacy mesh)\n * @returns {[number, number, number]} Position [x, y, z]\n */\n get position() {\n const data = this.geometry.instanceData\n if (!data) return [0, 0, 0]\n return [data[12], data[13], data[14]]\n }\n\n /**\n * Set position of the first instance (legacy mesh)\n * @param {[number, number, number]} pos - Position [x, y, z]\n */\n set position(pos) {\n const data = this.geometry.instanceData\n if (!data) return\n data[12] = pos[0]\n data[13] = pos[1]\n data[14] = pos[2]\n this.geometry._instanceDataDirty = true\n }\n\n /**\n * Get rotation of the first instance as euler angles\n * @returns {{yaw: number, pitch: number, roll: number}} Rotation in radians\n */\n get rotation() {\n const data = this.geometry.instanceData\n if (!data) return { yaw: 0, pitch: 0, roll: 0 }\n\n // Extract rotation from matrix (assuming no shear)\n // Matrix layout: column-major [m0,m1,m2,m3, m4,m5,m6,m7, m8,m9,m10,m11, m12,m13,m14,m15]\n // Column 0: [m0,m1,m2] = right * scaleX\n // Column 1: [m4,m5,m6] = up * scaleY\n // Column 2: [m8,m9,m10] = forward * scaleZ\n\n // Get scale to normalize rotation matrix\n const scaleX = Math.sqrt(data[0]*data[0] + data[1]*data[1] + data[2]*data[2])\n const scaleY = Math.sqrt(data[4]*data[4] + data[5]*data[5] + data[6]*data[6])\n const scaleZ = Math.sqrt(data[8]*data[8] + data[9]*data[9] + data[10]*data[10])\n\n // Normalized rotation matrix elements\n const m00 = data[0] / scaleX, m01 = data[4] / scaleY, m02 = data[8] / scaleZ\n const m10 = data[1] / scaleX, m11 = data[5] / scaleY, m12 = data[9] / scaleZ\n const m20 = data[2] / scaleX, m21 = data[6] / scaleY, m22 = data[10] / scaleZ\n\n // Extract euler angles (YXZ order: yaw, pitch, roll)\n let pitch, yaw, roll\n\n if (Math.abs(m12) < 0.99999) {\n pitch = Math.asin(-m12)\n yaw = Math.atan2(m02, m22)\n roll = Math.atan2(m10, m11)\n } else {\n // Gimbal lock\n pitch = m12 < 0 ? Math.PI / 2 : -Math.PI / 2\n yaw = Math.atan2(-m20, m00)\n roll = 0\n }\n\n return { yaw, pitch, roll }\n }\n\n /**\n * Set rotation of the first instance using euler angles\n * Rebuilds the transform matrix preserving position and scale\n * @param {{yaw?: number, pitch?: number, roll?: number}} rot - Rotation in radians\n */\n set rotation(rot) {\n const data = this.geometry.instanceData\n if (!data) return\n\n // Get current position\n const pos = [data[12], data[13], data[14]]\n\n // Get current scale\n const scaleX = Math.sqrt(data[0]*data[0] + data[1]*data[1] + data[2]*data[2])\n const scaleY = Math.sqrt(data[4]*data[4] + data[5]*data[5] + data[6]*data[6])\n const scaleZ = Math.sqrt(data[8]*data[8] + data[9]*data[9] + data[10]*data[10])\n\n // Update cached rotation\n const yaw = rot.yaw ?? this._rotation.yaw\n const pitch = rot.pitch ?? this._rotation.pitch\n const roll = rot.roll ?? this._rotation.roll\n this._rotation = { yaw, pitch, roll }\n\n // Build rotation matrix (YXZ order)\n const cy = Math.cos(yaw), sy = Math.sin(yaw)\n const cp = Math.cos(pitch), sp = Math.sin(pitch)\n const cr = Math.cos(roll), sr = Math.sin(roll)\n\n // Combined rotation matrix R = Ry * Rx * Rz\n const m00 = cy * cr + sy * sp * sr\n const m01 = -cy * sr + sy * sp * cr\n const m02 = sy * cp\n const m10 = cp * sr\n const m11 = cp * cr\n const m12 = -sp\n const m20 = -sy * cr + cy * sp * sr\n const m21 = sy * sr + cy * sp * cr\n const m22 = cy * cp\n\n // Apply scale and write to instance data\n data[0] = m00 * scaleX; data[1] = m10 * scaleX; data[2] = m20 * scaleX; data[3] = 0\n data[4] = m01 * scaleY; data[5] = m11 * scaleY; data[6] = m21 * scaleY; data[7] = 0\n data[8] = m02 * scaleZ; data[9] = m12 * scaleZ; data[10] = m22 * scaleZ; data[11] = 0\n data[12] = pos[0]; data[13] = pos[1]; data[14] = pos[2]; data[15] = 1\n\n this.geometry._instanceDataDirty = true\n }\n\n /**\n * Get scale of the first instance\n * @returns {[number, number, number]} Scale [x, y, z]\n */\n get scale() {\n const data = this.geometry.instanceData\n if (!data) return [1, 1, 1]\n\n const scaleX = Math.sqrt(data[0]*data[0] + data[1]*data[1] + data[2]*data[2])\n const scaleY = Math.sqrt(data[4]*data[4] + data[5]*data[5] + data[6]*data[6])\n const scaleZ = Math.sqrt(data[8]*data[8] + data[9]*data[9] + data[10]*data[10])\n\n return [scaleX, scaleY, scaleZ]\n }\n\n /**\n * Set scale of the first instance\n * Rebuilds the transform matrix preserving position and rotation\n * @param {[number, number, number]} s - Scale [x, y, z]\n */\n set scale(s) {\n const data = this.geometry.instanceData\n if (!data) return\n\n // Get current scale to compute ratio\n const oldScaleX = Math.sqrt(data[0]*data[0] + data[1]*data[1] + data[2]*data[2])\n const oldScaleY = Math.sqrt(data[4]*data[4] + data[5]*data[5] + data[6]*data[6])\n const oldScaleZ = Math.sqrt(data[8]*data[8] + data[9]*data[9] + data[10]*data[10])\n\n // Scale ratio\n const rx = oldScaleX > 0 ? s[0] / oldScaleX : s[0]\n const ry = oldScaleY > 0 ? s[1] / oldScaleY : s[1]\n const rz = oldScaleZ > 0 ? s[2] / oldScaleZ : s[2]\n\n // Apply new scale to rotation columns\n data[0] *= rx; data[1] *= rx; data[2] *= rx\n data[4] *= ry; data[5] *= ry; data[6] *= ry\n data[8] *= rz; data[9] *= rz; data[10] *= rz\n\n this.geometry._instanceDataDirty = true\n }\n\n /**\n * Get the full transform matrix of the first instance\n * @returns {Float32Array} 4x4 transform matrix (16 floats)\n */\n get matrix() {\n const data = this.geometry.instanceData\n if (!data) return mat4.create()\n return new Float32Array(data.buffer, data.byteOffset, 16)\n }\n\n /**\n * Set the full transform matrix of the first instance\n * @param {Float32Array|Array} m - 4x4 transform matrix\n */\n set matrix(m) {\n const data = this.geometry.instanceData\n if (!data) return\n for (let i = 0; i < 16; i++) {\n data[i] = m[i]\n }\n this.geometry._instanceDataDirty = true\n }\n}\n\nexport { Mesh }\n","import { fromEuler, toEuler, UP, RIGHT, FORWARD, V_T, V_T2 } from \"./math.js\"\n\nclass Node {\n\n constructor() {\n this.name = null\n this.position = vec3.create()\n this.rotation = quat.create()\n this.scale = vec3.fromValues(1, 1, 1)\n this.children = []\n\n // calculated matrices\n this.matrix = mat4.create()\n this.world = mat4.create()\n this.inv = mat4.create()\n this.normal = mat4.create()\n }\n\n fromEuler(euler) {\n //console.log('before fromEuler', this.rotation)\n fromEuler(euler, this.rotation)\n //console.log('after fromEuler', this.rotation)\n }\n\n get yaw() {\n toEuler(this.rotation, V_T)\n //console.log('get yaw', V_T[0])\n return V_T[1]\n }\n\n set yaw(yaw) {\n //console.log('--- set yaw', yaw)\n //console.log('--- set before rotation', this.rotation)\n toEuler(this.rotation, V_T2)\n //console.log('toEuler', V_T2)\n V_T2[1] = yaw\n this.fromEuler(V_T2)\n }\n\n rotateYaw(yaw) {\n this.yaw += yaw\n }\n\n get pitch() {\n toEuler(this.rotation, V_T)\n return V_T[0]\n }\n\n set pitch(pitch) {\n toEuler(this.rotation, V_T2)\n V_T2[0] = pitch\n this.fromEuler(V_T2)\n }\n\n rotatePitch(pitch) {\n this.pitch += pitch\n }\n\n limitPitch() {\n // Convert current pitch to degrees\n let pitchDegrees = this.pitch * (180 / Math.PI);\n \n // Clamp pitch between -89.9 and 89.9 degrees\n pitchDegrees = Math.max(-89, Math.min(89, pitchDegrees));\n \n // Convert back to radians and set the pitch\n this.pitch = pitchDegrees * (Math.PI / 180);\n }\n\n get roll() {\n toEuler(this.rotation, V_T)\n return V_T[2]\n }\n\n set roll(roll) {\n toEuler(this.rotation, V_T2)\n V_T2[2] = roll\n this.fromEuler(V_T2)\n }\n\n rotateRoll(roll) {\n this.roll += roll\n }\n\n updateMatrix(parentMatrix) {\n mat4.fromRotationTranslationScale(this.matrix, this.rotation, this.position, this.scale)\n mat4.identity(this.world)\n mat4.multiply(this.world, this.world, this.matrix)\n if (parentMatrix) {\n mat4.multiply(this.world, this.world, parentMatrix)\n }\n mat4.invert(this.inv, this.world)\n mat4.transpose(this.normal, this.inv)\n if (this.direction) {\n vec3.set(this.direction, 0, 0, -1)\n vec3.transformQuat(this.direction, this.direction, this.rotation)\n }\n if (this.right) {\n vec3.set(this.right, 1, 0, 0)\n vec3.transformQuat(this.right, this.right, this.rotation)\n }\n for (let child of this.children) {\n child.updateMatrix(this.world)\n }\n }\n\n addChild(child) {\n this.children.push(child)\n }\n}\n\nexport { Node, toEuler, fromEuler }\n","import { Node } from './Node.js'\n\nconst UP = vec3.fromValues(0, 1, 0)\n\n// Perspective projection for WebGPU's [0, 1] depth range\nfunction perspectiveZO(out, fovy, aspect, near, far) {\n const f = 1.0 / Math.tan(fovy / 2)\n const nf = 1 / (near - far)\n\n out[0] = f / aspect\n out[1] = 0\n out[2] = 0\n out[3] = 0\n out[4] = 0\n out[5] = f\n out[6] = 0\n out[7] = 0\n out[8] = 0\n out[9] = 0\n out[10] = far * nf\n out[11] = -1\n out[12] = 0\n out[13] = 0\n out[14] = near * far * nf\n out[15] = 0\n\n return out\n}\n\nclass Camera extends Node {\n\n constructor(engine = null) {\n super()\n this.engine = engine\n\n // Initialize from engine settings or use defaults\n const cameraSettings = engine?.settings?.camera || { fov: 70, near: 0.05, far: 5000 }\n\n this.fov = cameraSettings.fov\n this.direction = vec3.fromValues(0, 0, -1)\n this.right = vec3.fromValues(1, 0, 0)\n this.target = vec3.create()\n this.aspect = 16 / 9\n this.near = cameraSettings.near\n this.far = cameraSettings.far\n this.view = mat4.create()\n this.proj = mat4.create()\n this.viewProj = mat4.create()\n this.iViewProj = mat4.create()\n this.iProj = mat4.create()\n this.iView = mat4.create()\n this.planes = new Array(6).fill(null).map(() => ({\n normal: vec3.create(),\n distance: 0\n }));\n\n // TAA jitter - sub-pixel offset for temporal anti-aliasing\n this.jitterEnabled = false\n this.jitterOffset = [0, 0] // Current frame's jitter in pixels\n this.jitterAngle = 0 // Current jitter angle in radians\n this.screenSize = [1920, 1080] // Updated by RenderGraph\n }\n\n updateView() {\n // this.limitPitch(), this.updateMatrix() before updating view\n vec3.add(this.target, this.position, this.direction)\n mat4.lookAt(this.view, this.position, this.target, UP)\n // Use perspectiveZO for WebGPU's [0, 1] depth range (not OpenGL's [-1, 1])\n perspectiveZO(this.proj, this.fov * (Math.PI / 180), this.aspect, this.near, this.far)\n\n // Update TAA jitter offset (applied in vertex shader, not projection matrix)\n if (this.jitterEnabled) {\n // Rotate jitter vector by golden angle (137.5°) for optimal coverage\n // Golden angle ensures successive samples are maximally spread apart\n this.jitterAngle += 137.5 * Math.PI / 180\n const amount = this.engine?.settings?.rendering?.jitterAmount ?? 0.39\n this.jitterOffset[0] = Math.cos(this.jitterAngle) * amount\n this.jitterOffset[1] = Math.sin(this.jitterAngle) * amount\n } else {\n this.jitterOffset[0] = 0\n this.jitterOffset[1] = 0\n }\n\n mat4.multiply(this.viewProj, this.proj, this.view)\n mat4.invert(this.iViewProj, this.viewProj)\n mat4.invert(this.iProj, this.proj)\n mat4.invert(this.iView, this.view)\n this._updatePlanes()\n }\n\n isBoxVisible(min, max) {\n // For each plane\n for (let i = 0; i < 6; i++) {\n const plane = this.planes[i];\n\n // Calculate the positive vertex (p-vertex)\n const px = plane.normal[0] > 0 ? max[0] : min[0];\n const py = plane.normal[1] > 0 ? max[1] : min[1];\n const pz = plane.normal[2] > 0 ? max[2] : min[2];\n\n // If the positive vertex is outside, the whole box is outside\n if (px * plane.normal[0] +\n py * plane.normal[1] +\n pz * plane.normal[2] +\n plane.distance < 0) {\n return false;\n }\n }\n\n return true;\n }\n\n // Check if a sphere is visible\n isSphereVisible(center, radius) {\n // For each plane\n for (let i = 0; i < 6; i++) {\n const plane = this.planes[i];\n\n // Calculate signed distance from sphere center to plane\n const distance = vec3.dot(plane.normal, center) + plane.distance;\n\n // If the distance is less than -radius, the sphere is completely outside\n if (distance < -radius) {\n return false;\n }\n }\n\n return true;\n }\n\n // Extract frustum planes from view-projection matrix (column-major format)\n // For column-major: row i elements are at indices [i, i+4, i+8, i+12]\n _updatePlanes() {\n const m = this.viewProj;\n\n // Left plane: row3 + row0\n this._setPlane(0,\n m[3] + m[0],\n m[7] + m[4],\n m[11] + m[8],\n m[15] + m[12]);\n\n // Right plane: row3 - row0\n this._setPlane(1,\n m[3] - m[0],\n m[7] - m[4],\n m[11] - m[8],\n m[15] - m[12]);\n\n // Bottom plane: row3 + row1\n this._setPlane(2,\n m[3] + m[1],\n m[7] + m[5],\n m[11] + m[9],\n m[15] + m[13]);\n\n // Top plane: row3 - row1\n this._setPlane(3,\n m[3] - m[1],\n m[7] - m[5],\n m[11] - m[9],\n m[15] - m[13]);\n\n // Near plane (WebGPU: z >= 0, so just row2)\n this._setPlane(4,\n m[2],\n m[6],\n m[10],\n m[14]);\n\n // Far plane: row3 - row2\n this._setPlane(5,\n m[3] - m[2],\n m[7] - m[6],\n m[11] - m[10],\n m[15] - m[14]);\n }\n\n _setPlane(index, x, y, z, w) {\n const length = sqrt(x * x + y * y + z * z);\n const plane = this.planes[index];\n\n plane.normal[0] = x / length;\n plane.normal[1] = y / length;\n plane.normal[2] = z / length;\n plane.distance = w / length;\n }\n\n}\n\nexport { Camera }\n","/**\n * BasePass - Abstract base class for render passes\n *\n * All render passes in the 7-pass pipeline inherit from this.\n */\nclass BasePass {\n constructor(name, engine = null) {\n this.name = name\n this.engine = engine\n this.enabled = true\n this._initialized = false\n }\n\n // Convenience getter for settings (with fallback for passes without engine)\n get settings() {\n return this.engine?.settings\n }\n\n /**\n * Initialize the pass (create pipelines, textures, etc.)\n * Called once before first use.\n * @returns {Promise<void>}\n */\n async initialize() {\n if (this._initialized) return\n await this._init()\n this._initialized = true\n }\n\n /**\n * Override in subclass to perform initialization\n * @protected\n */\n async _init() {\n // Override in subclass\n }\n\n /**\n * Execute the render pass\n * @param {Object} context - Render context with camera, entities, etc.\n * @returns {Promise<void>}\n */\n async execute(context) {\n if (!this.enabled) return\n if (!this._initialized) {\n await this.initialize()\n }\n await this._execute(context)\n }\n\n /**\n * Override in subclass to perform rendering\n * @param {Object} context - Render context\n * @protected\n */\n async _execute(context) {\n // Override in subclass\n }\n\n /**\n * Resize pass resources (called on window resize)\n * @param {number} width - New width\n * @param {number} height - New height\n */\n async resize(width, height) {\n await this._resize(width, height)\n }\n\n /**\n * Override in subclass to handle resize\n * @protected\n */\n async _resize(width, height) {\n // Override in subclass\n }\n\n /**\n * Destroy pass resources\n */\n destroy() {\n this._destroy()\n this._initialized = false\n }\n\n /**\n * Override in subclass to clean up resources\n * @protected\n */\n _destroy() {\n // Override in subclass\n }\n\n /**\n * Get debug name for profiling\n */\n getDebugName() {\n return `Pass: ${this.name}`\n }\n}\n\nexport { BasePass }\n","import { vec3, vec4, mat4 } from \"../math.js\"\n\n/**\n * Frustum - Camera frustum for culling\n *\n * Implements cone-based frustum culling which is faster than plane-based\n * for rectangular viewports while maintaining accuracy.\n */\nclass Frustum {\n constructor() {\n // Standard frustum planes (for fallback)\n // Order: left, right, bottom, top, near, far\n this.planes = new Float32Array(24) // 6 planes * 4 components\n\n // Cone-based frustum (more efficient for spheres)\n this.coneAxis = vec3.create() // Camera forward direction\n this.coneOrigin = vec3.create() // Camera position\n this.coneAngle = 0 // Half-angle of the cone (radians)\n this.coneCos = 0 // cos(coneAngle) for fast tests\n this.coneSin = 0 // sin(coneAngle) for fast tests\n this.nearPlane = 0.1\n this.farPlane = 1000\n\n // View-projection matrix for plane extraction\n this._viewProj = mat4.create()\n\n // Screen dimensions for pixel size culling\n this.screenWidth = 1920\n this.screenHeight = 1080\n this.projectionScale = 1 // screenHeight / (2 * tan(fov/2))\n }\n\n /**\n * Update frustum from camera matrices\n * @param {mat4} viewMatrix - View matrix\n * @param {mat4} projectionMatrix - Projection matrix\n * @param {vec3} cameraPosition - Camera world position\n * @param {vec3} cameraForward - Camera forward direction (normalized)\n * @param {number} fov - Field of view in radians (vertical)\n * @param {number} aspect - Aspect ratio (width/height)\n * @param {number} near - Near plane distance\n * @param {number} far - Far plane distance\n * @param {number} screenWidth - Screen width in pixels (optional)\n * @param {number} screenHeight - Screen height in pixels (optional)\n */\n update(viewMatrix, projectionMatrix, cameraPosition, cameraForward, fov, aspect, near, far, screenWidth, screenHeight) {\n // Store near/far for distance culling\n this.nearPlane = near\n this.farPlane = far\n\n // Store screen dimensions for pixel size culling\n if (screenWidth) this.screenWidth = screenWidth\n if (screenHeight) this.screenHeight = screenHeight\n\n // Compute projection scale: converts world-space radius to screen pixels\n // projectedPixels = radius * projectionScale / distance\n const halfFov = fov * 0.5\n this.projectionScale = this.screenHeight / (2 * Math.tan(halfFov))\n\n // Compute view-projection matrix\n mat4.multiply(this._viewProj, projectionMatrix, viewMatrix)\n\n // Extract standard frustum planes from view-projection matrix\n this._extractPlanes(this._viewProj)\n\n // Setup cone-based frustum\n vec3.copy(this.coneOrigin, cameraPosition)\n vec3.copy(this.coneAxis, cameraForward)\n\n // Calculate cone angle that encompasses the frustum\n // For a rectangular viewport, use the diagonal angle\n const halfFovH = Math.atan(Math.tan(halfFov) * aspect)\n // Diagonal half-angle (slightly larger to ensure we don't cull visible objects)\n this.coneAngle = Math.sqrt(halfFov * halfFov + halfFovH * halfFovH) * 1.1\n\n this.coneCos = Math.cos(this.coneAngle)\n this.coneSin = Math.sin(this.coneAngle)\n }\n\n /**\n * Extract frustum planes from view-projection matrix\n * Planes are in the form: ax + by + cz + d = 0\n * @private\n */\n _extractPlanes(vp) {\n // Left plane\n this.planes[0] = vp[3] + vp[0]\n this.planes[1] = vp[7] + vp[4]\n this.planes[2] = vp[11] + vp[8]\n this.planes[3] = vp[15] + vp[12]\n this._normalizePlane(0)\n\n // Right plane\n this.planes[4] = vp[3] - vp[0]\n this.planes[5] = vp[7] - vp[4]\n this.planes[6] = vp[11] - vp[8]\n this.planes[7] = vp[15] - vp[12]\n this._normalizePlane(1)\n\n // Bottom plane\n this.planes[8] = vp[3] + vp[1]\n this.planes[9] = vp[7] + vp[5]\n this.planes[10] = vp[11] + vp[9]\n this.planes[11] = vp[15] + vp[13]\n this._normalizePlane(2)\n\n // Top plane\n this.planes[12] = vp[3] - vp[1]\n this.planes[13] = vp[7] - vp[5]\n this.planes[14] = vp[11] - vp[9]\n this.planes[15] = vp[15] - vp[13]\n this._normalizePlane(3)\n\n // Near plane (WebGPU: z >= 0, so just row 2)\n this.planes[16] = vp[2]\n this.planes[17] = vp[6]\n this.planes[18] = vp[10]\n this.planes[19] = vp[14]\n this._normalizePlane(4)\n\n // Far plane (z <= w)\n this.planes[20] = vp[3] - vp[2]\n this.planes[21] = vp[7] - vp[6]\n this.planes[22] = vp[11] - vp[10]\n this.planes[23] = vp[15] - vp[14]\n this._normalizePlane(5)\n }\n\n /**\n * Normalize a frustum plane\n * @private\n */\n _normalizePlane(index) {\n const offset = index * 4\n const length = Math.sqrt(\n this.planes[offset] * this.planes[offset] +\n this.planes[offset + 1] * this.planes[offset + 1] +\n this.planes[offset + 2] * this.planes[offset + 2]\n )\n if (length > 0) {\n this.planes[offset] /= length\n this.planes[offset + 1] /= length\n this.planes[offset + 2] /= length\n this.planes[offset + 3] /= length\n }\n }\n\n /**\n * Test if a sphere is visible using cone-based culling\n * This is faster than plane testing for large numbers of objects\n *\n * @param {Object} bsphere - Bounding sphere { center: [x,y,z], radius: r }\n * @returns {boolean} True if potentially visible\n */\n testSphere(bsphere) {\n // Vector from cone origin to sphere center\n const dx = bsphere.center[0] - this.coneOrigin[0]\n const dy = bsphere.center[1] - this.coneOrigin[1]\n const dz = bsphere.center[2] - this.coneOrigin[2]\n\n // Distance along cone axis\n const distAlongAxis = dx * this.coneAxis[0] + dy * this.coneAxis[1] + dz * this.coneAxis[2]\n\n // Quick near/far plane test\n if (distAlongAxis + bsphere.radius < this.nearPlane || distAlongAxis - bsphere.radius > this.farPlane) {\n return false\n }\n\n // For objects behind the camera\n if (distAlongAxis < -bsphere.radius) {\n return false\n }\n\n // Distance from cone axis\n const dist2 = dx * dx + dy * dy + dz * dz\n const distFromAxis2 = dist2 - distAlongAxis * distAlongAxis\n const distFromAxis = Math.sqrt(Math.max(0, distFromAxis2))\n\n // Angle from cone axis to sphere edge\n // sphere is visible if its closest edge is within the cone angle\n const sphereAngle = Math.atan2(distFromAxis - bsphere.radius, Math.max(0.001, distAlongAxis))\n\n return sphereAngle < this.coneAngle\n }\n\n /**\n * Test if a sphere is visible using standard plane-based culling\n * More accurate but slower than cone test\n *\n * @param {Object} bsphere - Bounding sphere { center: [x,y,z], radius: r }\n * @returns {boolean} True if potentially visible\n */\n testSpherePlanes(bsphere) {\n for (let i = 0; i < 6; i++) {\n const offset = i * 4\n const dist =\n this.planes[offset] * bsphere.center[0] +\n this.planes[offset + 1] * bsphere.center[1] +\n this.planes[offset + 2] * bsphere.center[2] +\n this.planes[offset + 3]\n\n if (dist < -bsphere.radius) {\n return false\n }\n }\n return true\n }\n\n /**\n * Test if a sphere is within a maximum distance from the camera\n *\n * @param {Object} bsphere - Bounding sphere\n * @param {number} maxDistance - Maximum distance\n * @returns {boolean} True if within distance\n */\n testSphereDistance(bsphere, maxDistance) {\n const dx = bsphere.center[0] - this.coneOrigin[0]\n const dy = bsphere.center[1] - this.coneOrigin[1]\n const dz = bsphere.center[2] - this.coneOrigin[2]\n const dist = Math.sqrt(dx * dx + dy * dy + dz * dz) - bsphere.radius\n return dist <= maxDistance\n }\n\n /**\n * Get the distance from camera to sphere center\n *\n * @param {Object} bsphere - Bounding sphere\n * @returns {number} Distance\n */\n getDistance(bsphere) {\n const dx = bsphere.center[0] - this.coneOrigin[0]\n const dy = bsphere.center[1] - this.coneOrigin[1]\n const dz = bsphere.center[2] - this.coneOrigin[2]\n return Math.sqrt(dx * dx + dy * dy + dz * dz)\n }\n\n /**\n * Get the projected size of a bounding sphere in pixels\n *\n * @param {Object} bsphere - Bounding sphere\n * @param {number} distance - Pre-computed distance (optional)\n * @returns {number} Projected diameter in pixels\n */\n getProjectedSize(bsphere, distance) {\n if (distance === undefined) {\n distance = this.getDistance(bsphere)\n }\n // Avoid division by zero for very close objects\n if (distance < this.nearPlane) {\n return this.screenHeight // Fill screen\n }\n // Projected diameter = 2 * radius * projectionScale / distance\n return 2 * bsphere.radius * this.projectionScale / distance\n }\n\n /**\n * Test both frustum visibility and distance\n *\n * @param {Object} bsphere - Bounding sphere\n * @param {number} maxDistance - Maximum distance (optional)\n * @returns {boolean} True if visible and within distance\n */\n test(bsphere, maxDistance = Infinity) {\n if (!this.testSphere(bsphere)) {\n return false\n }\n if (maxDistance < Infinity) {\n return this.testSphereDistance(bsphere, maxDistance)\n }\n return true\n }\n\n /**\n * Get frustum planes as Float32Array for GPU upload\n */\n getPlanesBuffer() {\n return this.planes\n }\n}\n\nexport { Frustum }\n","import { BasePass } from \"./BasePass.js\"\nimport { mat4, vec3 } from \"../../math.js\"\nimport { Frustum } from \"../../utils/Frustum.js\"\nimport { calculateShadowBoundingSphere, sphereInCascade, transformBoundingSphere } from \"../../utils/BoundingSphere.js\"\n\n/**\n * ShadowPass - Shadow map generation for directional and spot lights\n *\n * Pass 1 in the 7-pass pipeline.\n * Generates depth maps from light perspectives.\n */\nclass ShadowPass extends BasePass {\n constructor(engine = null) {\n super('Shadow', engine)\n\n // Shadow textures - use texture array for cascades\n this.directionalShadowMap = null // Depth texture array for cascades\n this.spotShadowMaps = [] // Array of depth textures for spot lights\n\n // Spotlight shadow atlas\n this.spotShadowAtlas = null\n this.spotShadowAtlasView = null\n\n // Light matrices\n this.directionalLightMatrix = mat4.create() // For backward compatibility\n this.cascadeMatrices = [] // Array of mat4 for each cascade\n this.cascadeViews = [] // Texture views for each cascade layer\n this.spotLightMatrices = [] // Array of mat4 for each shadow slot\n\n // Shadow slot assignments (lightIndex -> slotIndex, -1 if no shadow)\n this.spotShadowSlots = new Int32Array(128) // Max 128 lights\n this.spotShadowMatrices = [] // Matrices for lights with shadows\n\n // Pipeline and bind groups\n this.pipeline = null\n this.uniformBuffer = null\n this.bindGroup = null\n\n // Scene bounds for directional light\n this.sceneBounds = {\n min: [-50, -10, -50],\n max: [50, 50, 50]\n }\n\n // Noise texture for alpha hashing\n this.noiseTexture = null\n this.noiseSize = 64\n this.noiseAnimated = true\n\n // HiZ pass reference for occlusion culling of static meshes\n this.hizPass = null\n\n // Per-mesh bind group cache (for alpha hashing with different albedo textures)\n this._meshBindGroups = new WeakMap()\n\n // Camera shadow detection state\n this._cameraShadowBuffer = null\n this._cameraShadowReadBuffer = null\n this._cameraShadowPipeline = null\n this._cameraShadowBindGroup = null\n this._cameraShadowUniformBuffer = null\n this._cameraInShadow = false\n this._cameraShadowPending = false\n }\n\n /**\n * Set the HiZ pass for occlusion culling of static meshes\n * @param {HiZPass} hizPass - HiZ pass instance\n */\n setHiZPass(hizPass) {\n this.hizPass = hizPass\n }\n\n /**\n * Set the noise texture for alpha hashing in shadows\n * @param {Texture} noise - Noise texture (blue noise or bayer dither)\n * @param {number} size - Texture size\n * @param {boolean} animated - Whether to animate noise offset each frame\n */\n setNoise(noise, size = 64, animated = true) {\n this.noiseTexture = noise\n this.noiseSize = size\n this.noiseAnimated = animated\n // Clear bind group cache since noise texture changed\n this._meshBindGroups = new WeakMap()\n this._skinBindGroups = new WeakMap()\n }\n\n // Convenience getters for shadow settings (with defaults for backward compatibility)\n get shadowMapSize() { return this.settings?.shadow?.mapSize ?? 2048 }\n get cascadeCount() { return this.settings?.shadow?.cascadeCount ?? 3 }\n get cascadeSizes() { return this.settings?.shadow?.cascadeSizes ?? [20, 60, 300] }\n get maxSpotShadows() { return this.settings?.shadow?.maxSpotShadows ?? 16 }\n get spotTileSize() { return this.settings?.shadow?.spotTileSize ?? 512 }\n get spotAtlasSize() { return this.settings?.shadow?.spotAtlasSize ?? 2048 }\n get spotTilesPerRow() { return this.spotAtlasSize / this.spotTileSize }\n get shadowMaxDistance() { return this.settings?.shadow?.spotMaxDistance ?? 60 }\n get shadowFadeStart() { return this.settings?.shadow?.spotFadeStart ?? 55 }\n\n async _init() {\n const { device } = this.engine\n\n // Create directional shadow map as 2D texture array (one layer per cascade)\n this.directionalShadowMap = device.createTexture({\n size: [this.shadowMapSize, this.shadowMapSize, this.cascadeCount],\n format: 'depth32float',\n dimension: '2d',\n usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.TEXTURE_BINDING,\n label: 'Cascaded Shadow Map'\n })\n\n // Create view for entire array (for sampling in shader)\n this.directionalShadowMapView = this.directionalShadowMap.createView({\n dimension: '2d-array',\n arrayLayerCount: this.cascadeCount,\n })\n\n // Create individual views for each cascade layer (for rendering)\n this.cascadeViews = []\n for (let i = 0; i < this.cascadeCount; i++) {\n this.cascadeViews.push(this.directionalShadowMap.createView({\n dimension: '2d',\n baseArrayLayer: i,\n arrayLayerCount: 1,\n }))\n }\n\n // Initialize cascade matrices\n this.cascadeMatrices = []\n for (let i = 0; i < this.cascadeCount; i++) {\n this.cascadeMatrices.push(mat4.create())\n }\n\n // Create storage buffer for cascade matrices (3 matrices * 64 bytes = 192 bytes)\n this.cascadeMatricesBuffer = device.createBuffer({\n label: 'Cascade Shadow Matrices',\n size: this.cascadeCount * 16 * 4,\n usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST,\n })\n this.cascadeMatricesData = new Float32Array(this.cascadeCount * 16)\n\n // Create spotlight shadow atlas\n // spotAtlasSize, spotTileSize, spotTilesPerRow, maxSpotShadows come from settings via getters\n const atlasSize = this.spotAtlasSize\n this.spotAtlasHeight = atlasSize\n\n this.spotShadowAtlas = device.createTexture({\n size: [this.spotAtlasSize, this.spotAtlasHeight, 1],\n format: 'depth32float',\n usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.TEXTURE_BINDING,\n label: 'Spot Shadow Atlas'\n })\n\n this.spotShadowAtlasView = this.spotShadowAtlas.createView()\n\n // Initialize spot shadow slot data\n this.spotShadowSlots.fill(-1)\n for (let i = 0; i < this.maxSpotShadows; i++) {\n this.spotLightMatrices.push(mat4.create())\n }\n\n // Create storage buffer for spot shadow matrices (8 matrices * 64 bytes = 512 bytes)\n this.spotMatricesBuffer = device.createBuffer({\n label: 'Spot Shadow Matrices',\n size: this.maxSpotShadows * 16 * 4, // 8 mat4x4 * 16 floats * 4 bytes\n usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST,\n })\n this.spotMatricesData = new Float32Array(this.maxSpotShadows * 16)\n\n // Create sampler for shadow map\n this.shadowSampler = device.createSampler({\n compare: 'less',\n magFilter: 'linear',\n minFilter: 'linear',\n })\n\n // Create regular sampler for reading depth\n this.depthSampler = device.createSampler({\n magFilter: 'nearest',\n minFilter: 'nearest',\n })\n\n // Create uniform buffer for light matrix + alpha hash params + surface bias\n // mat4 (64) + vec3+f32 (16) + alpha hash (16) + surfaceBias+padding (16) + lightDir+padding (16) = 128 bytes\n this.uniformBuffer = device.createBuffer({\n size: 128,\n usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST,\n label: 'Shadow Uniforms'\n })\n\n // Create placeholder textures\n this._createPlaceholderTextures()\n\n // Create shadow pipeline\n await this._createPipeline()\n\n // Create camera shadow detection resources\n await this._createCameraShadowDetection()\n }\n\n async _createPipeline() {\n const { device } = this.engine\n\n const shaderModule = device.createShaderModule({\n label: 'Shadow Shader',\n code: `\n struct Uniforms {\n lightViewProjection: mat4x4f,\n lightPosition: vec3f,\n lightType: f32,\n // Alpha hash params\n alphaHashEnabled: f32,\n alphaHashScale: f32,\n luminanceToAlpha: f32,\n noiseSize: f32,\n noiseOffsetX: f32,\n surfaceBias: f32, // Expand triangles along normals (meters)\n _padding: vec2f,\n lightDirection: vec3f, // Light direction (for surface bias)\n _padding2: f32,\n }\n\n struct VertexInput {\n @location(0) position: vec3f,\n @location(1) uv: vec2f,\n @location(2) normal: vec3f,\n @location(3) color: vec4f,\n @location(4) weights: vec4f,\n @location(5) joints: vec4u,\n @location(6) model0: vec4f,\n @location(7) model1: vec4f,\n @location(8) model2: vec4f,\n @location(9) model3: vec4f,\n @location(10) instancePosRadius: vec4f,\n }\n\n struct VertexOutput {\n @builtin(position) position: vec4f,\n @location(0) uv: vec2f,\n }\n\n @group(0) @binding(0) var<uniform> uniforms: Uniforms;\n @group(0) @binding(1) var jointTexture: texture_2d<f32>;\n @group(0) @binding(2) var jointSampler: sampler;\n @group(0) @binding(3) var albedoTexture: texture_2d<f32>;\n @group(0) @binding(4) var albedoSampler: sampler;\n @group(0) @binding(5) var noiseTexture: texture_2d<f32>;\n\n // Get a 4x4 matrix from the joint texture\n fn getJointMatrix(jointIndex: u32) -> mat4x4f {\n let row = i32(jointIndex);\n let col0 = textureLoad(jointTexture, vec2i(0, row), 0);\n let col1 = textureLoad(jointTexture, vec2i(1, row), 0);\n let col2 = textureLoad(jointTexture, vec2i(2, row), 0);\n let col3 = textureLoad(jointTexture, vec2i(3, row), 0);\n return mat4x4f(col0, col1, col2, col3);\n }\n\n // Apply skinning to a position\n fn applySkinning(position: vec3f, joints: vec4u, weights: vec4f) -> vec3f {\n // Check if skinning is active (weights sum > 0)\n let weightSum = weights.x + weights.y + weights.z + weights.w;\n if (weightSum < 0.001) {\n return position;\n }\n\n var skinnedPos = vec3f(0.0);\n let m0 = getJointMatrix(joints.x);\n let m1 = getJointMatrix(joints.y);\n let m2 = getJointMatrix(joints.z);\n let m3 = getJointMatrix(joints.w);\n\n skinnedPos += (m0 * vec4f(position, 1.0)).xyz * weights.x;\n skinnedPos += (m1 * vec4f(position, 1.0)).xyz * weights.y;\n skinnedPos += (m2 * vec4f(position, 1.0)).xyz * weights.z;\n skinnedPos += (m3 * vec4f(position, 1.0)).xyz * weights.w;\n\n return skinnedPos;\n }\n\n // Sample noise at screen position (tiled, no animation for shadows)\n fn sampleNoise(screenPos: vec2f) -> f32 {\n let noiseSize = i32(uniforms.noiseSize);\n let noiseOffsetX = i32(uniforms.noiseOffsetX * f32(noiseSize));\n\n let texCoord = vec2i(\n (i32(screenPos.x) + noiseOffsetX) % noiseSize,\n i32(screenPos.y) % noiseSize\n );\n return textureLoad(noiseTexture, texCoord, 0).r;\n }\n\n @vertex\n fn vertexMain(input: VertexInput) -> VertexOutput {\n var output: VertexOutput;\n\n let modelMatrix = mat4x4f(\n input.model0,\n input.model1,\n input.model2,\n input.model3\n );\n\n // Apply skinning\n let skinnedPos = applySkinning(input.position, input.joints, input.weights);\n let worldPos = modelMatrix * vec4f(skinnedPos, 1.0);\n\n var clipPos = uniforms.lightViewProjection * worldPos;\n\n // Apply surface bias - scale shadow projection to make shadows larger\n // surfaceBias is treated as a percentage (0.01 = 1% larger shadows)\n if (uniforms.surfaceBias > 0.0) {\n let scale = 1.0 + uniforms.surfaceBias;\n clipPos = vec4f(clipPos.xy * scale, clipPos.z, clipPos.w);\n }\n\n output.position = clipPos;\n output.uv = input.uv;\n\n return output;\n }\n\n @fragment\n fn fragmentMain(input: VertexOutput) {\n // Luminance to alpha: hard discard for pure black (no noise)\n if (uniforms.luminanceToAlpha > 0.5) {\n let albedo = textureSample(albedoTexture, albedoSampler, input.uv);\n let luminance = dot(albedo.rgb, vec3f(0.299, 0.587, 0.114));\n if (luminance < 0.004) {\n discard;\n }\n }\n // Simple alpha cutoff for shadows (no hashing - too noisy at shadow resolution)\n else if (uniforms.alphaHashEnabled > 0.5) {\n let albedo = textureSample(albedoTexture, albedoSampler, input.uv);\n let alpha = albedo.a * uniforms.alphaHashScale;\n if (alpha < 0.5) {\n discard;\n }\n }\n // Depth-only pass, no color output needed\n }\n `\n })\n\n // Vertex buffer layout (must match geometry)\n const vertexBufferLayout = {\n arrayStride: 80,\n attributes: [\n { format: \"float32x3\", offset: 0, shaderLocation: 0 },\n { format: \"float32x2\", offset: 12, shaderLocation: 1 },\n { format: \"float32x3\", offset: 20, shaderLocation: 2 },\n { format: \"float32x4\", offset: 32, shaderLocation: 3 },\n { format: \"float32x4\", offset: 48, shaderLocation: 4 },\n { format: \"uint32x4\", offset: 64, shaderLocation: 5 },\n ],\n stepMode: 'vertex'\n }\n\n const instanceBufferLayout = {\n arrayStride: 112, // 28 floats: matrix(16) + posRadius(4) + uvTransform(4) + color(4)\n stepMode: 'instance',\n attributes: [\n { format: \"float32x4\", offset: 0, shaderLocation: 6 },\n { format: \"float32x4\", offset: 16, shaderLocation: 7 },\n { format: \"float32x4\", offset: 32, shaderLocation: 8 },\n { format: \"float32x4\", offset: 48, shaderLocation: 9 },\n { format: \"float32x4\", offset: 64, shaderLocation: 10 },\n { format: \"float32x4\", offset: 80, shaderLocation: 11 }, // uvTransform\n { format: \"float32x4\", offset: 96, shaderLocation: 12 }, // color\n ]\n }\n\n this.bindGroupLayout = device.createBindGroupLayout({\n entries: [\n {\n binding: 0,\n visibility: GPUShaderStage.VERTEX | GPUShaderStage.FRAGMENT,\n buffer: { type: 'uniform' }\n },\n {\n binding: 1,\n visibility: GPUShaderStage.VERTEX,\n texture: { sampleType: 'unfilterable-float' }\n },\n {\n binding: 2,\n visibility: GPUShaderStage.VERTEX,\n sampler: { type: 'non-filtering' }\n },\n // Albedo texture for alpha hashing\n {\n binding: 3,\n visibility: GPUShaderStage.FRAGMENT,\n texture: { sampleType: 'float' }\n },\n {\n binding: 4,\n visibility: GPUShaderStage.FRAGMENT,\n sampler: { type: 'filtering' }\n },\n // Noise texture for alpha hashing\n {\n binding: 5,\n visibility: GPUShaderStage.FRAGMENT,\n texture: { sampleType: 'float' }\n }\n ]\n })\n\n const pipelineLayout = device.createPipelineLayout({\n bindGroupLayouts: [this.bindGroupLayout]\n })\n\n // Use async pipeline creation for non-blocking initialization\n this.pipeline = await device.createRenderPipelineAsync({\n label: 'Shadow Pipeline',\n layout: pipelineLayout,\n vertex: {\n module: shaderModule,\n entryPoint: 'vertexMain',\n buffers: [vertexBufferLayout, instanceBufferLayout]\n },\n fragment: {\n module: shaderModule,\n entryPoint: 'fragmentMain',\n targets: [] // No color attachments\n },\n depthStencil: {\n format: 'depth32float',\n depthWriteEnabled: true,\n depthCompare: 'less',\n },\n primitive: {\n topology: 'triangle-list',\n cullMode: 'none', // No culling for shadow map (debug)\n }\n })\n\n // Create placeholder joint texture for non-skinned meshes\n this._createPlaceholderJointTexture()\n\n // Default bind group with placeholder textures\n this.bindGroup = device.createBindGroup({\n layout: this.bindGroupLayout,\n entries: [\n { binding: 0, resource: { buffer: this.uniformBuffer } },\n { binding: 1, resource: this.placeholderJointTextureView },\n { binding: 2, resource: this.placeholderJointSampler },\n { binding: 3, resource: this.placeholderAlbedoTextureView },\n { binding: 4, resource: this.placeholderAlbedoSampler },\n { binding: 5, resource: this.placeholderNoiseTextureView }\n ]\n })\n }\n\n _createPlaceholderJointTexture() {\n const { device } = this.engine\n\n // Create a 4x1 rgba32float texture (one identity matrix)\n this.placeholderJointTexture = device.createTexture({\n size: [4, 1, 1],\n format: 'rgba32float',\n usage: GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_DST,\n })\n\n // Write identity matrix\n const identityData = new Float32Array([\n 1, 0, 0, 0, // column 0\n 0, 1, 0, 0, // column 1\n 0, 0, 1, 0, // column 2\n 0, 0, 0, 1, // column 3\n ])\n device.queue.writeTexture(\n { texture: this.placeholderJointTexture },\n identityData,\n { bytesPerRow: 4 * 4 * 4, rowsPerImage: 1 },\n [4, 1, 1]\n )\n\n this.placeholderJointTextureView = this.placeholderJointTexture.createView()\n this.placeholderJointSampler = device.createSampler({\n magFilter: 'nearest',\n minFilter: 'nearest',\n })\n }\n\n _createPlaceholderTextures() {\n const { device } = this.engine\n\n // Create placeholder albedo texture (1x1 white with alpha=1)\n // Used for meshes without alpha hashing - alpha=1 means no discard\n this.placeholderAlbedoTexture = device.createTexture({\n size: [1, 1, 1],\n format: 'rgba8unorm',\n usage: GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_DST,\n })\n device.queue.writeTexture(\n { texture: this.placeholderAlbedoTexture },\n new Uint8Array([255, 255, 255, 255]),\n { bytesPerRow: 4, rowsPerImage: 1 },\n [1, 1, 1]\n )\n this.placeholderAlbedoTextureView = this.placeholderAlbedoTexture.createView()\n this.placeholderAlbedoSampler = device.createSampler({\n magFilter: 'linear',\n minFilter: 'linear',\n })\n\n // Create placeholder noise texture (1x1 gray = 0.5)\n // Used when no noise texture is configured\n this.placeholderNoiseTexture = device.createTexture({\n size: [1, 1, 1],\n format: 'rgba8unorm',\n usage: GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_DST,\n })\n device.queue.writeTexture(\n { texture: this.placeholderNoiseTexture },\n new Uint8Array([128, 128, 128, 255]),\n { bytesPerRow: 4, rowsPerImage: 1 },\n [1, 1, 1]\n )\n this.placeholderNoiseTextureView = this.placeholderNoiseTexture.createView()\n }\n\n /**\n * Get or create a bind group for a mesh (handles skin and albedo for alpha hashing)\n * @param {Mesh} mesh - The mesh to get bind group for\n * @returns {GPUBindGroup} The bind group for this mesh\n */\n getBindGroupForMesh(mesh) {\n const { device } = this.engine\n\n const skin = mesh?.skin\n const material = mesh?.material\n const hasAlphaHash = material?.alphaHash || mesh?.alphaHash\n const hasLuminanceToAlpha = material?.luminanceToAlpha\n const needsAlbedo = hasAlphaHash || hasLuminanceToAlpha\n\n // Get albedo texture (first texture in material) or placeholder\n let albedoView = this.placeholderAlbedoTextureView\n let albedoSampler = this.placeholderAlbedoSampler\n if (needsAlbedo && material?.textures?.[0]) {\n albedoView = material.textures[0].view\n albedoSampler = material.textures[0].sampler\n }\n\n // Get noise texture or placeholder\n const noiseView = this.noiseTexture?.view || this.placeholderNoiseTextureView\n\n // Get joint texture (from skin or placeholder)\n let jointView = this.placeholderJointTextureView\n let jointSampler = this.placeholderJointSampler\n if (skin?.jointTexture) {\n jointView = skin.jointTextureView\n jointSampler = skin.jointSampler\n }\n\n // For meshes without alpha hash, luminanceToAlpha, and without skin, use default bind group\n if (!needsAlbedo && !skin?.jointTexture) {\n return this.bindGroup\n }\n\n // Cache bind groups by mesh\n let bindGroup = this._meshBindGroups.get(mesh)\n if (!bindGroup) {\n bindGroup = device.createBindGroup({\n layout: this.bindGroupLayout,\n entries: [\n { binding: 0, resource: { buffer: this.uniformBuffer } },\n { binding: 1, resource: jointView },\n { binding: 2, resource: jointSampler },\n { binding: 3, resource: albedoView },\n { binding: 4, resource: albedoSampler },\n { binding: 5, resource: noiseView }\n ]\n })\n this._meshBindGroups.set(mesh, bindGroup)\n }\n\n return bindGroup\n }\n\n /**\n * Get or create a bind group for a specific joint texture (legacy, for backward compatibility)\n */\n getBindGroupForSkin(skin) {\n // For backward compatibility, create a minimal bind group for skinned meshes\n // without alpha hashing\n const { device } = this.engine\n\n if (!skin || !skin.jointTexture) {\n return this.bindGroup\n }\n\n // Cache bind groups by skin\n if (!this._skinBindGroups) {\n this._skinBindGroups = new WeakMap()\n }\n\n let bindGroup = this._skinBindGroups.get(skin)\n if (!bindGroup) {\n bindGroup = device.createBindGroup({\n layout: this.bindGroupLayout,\n entries: [\n { binding: 0, resource: { buffer: this.uniformBuffer } },\n { binding: 1, resource: skin.jointTextureView },\n { binding: 2, resource: skin.jointSampler },\n { binding: 3, resource: this.placeholderAlbedoTextureView },\n { binding: 4, resource: this.placeholderAlbedoSampler },\n { binding: 5, resource: this.noiseTexture?.view || this.placeholderNoiseTextureView }\n ]\n })\n this._skinBindGroups.set(skin, bindGroup)\n }\n\n return bindGroup\n }\n\n /**\n * Create a frustum from a view-projection matrix for culling\n */\n _createFrustumFromMatrix(viewProj) {\n const frustum = new Frustum()\n frustum._extractPlanes(viewProj)\n return frustum\n }\n\n /**\n * Test if an instance's bounding sphere is visible to a spotlight using cone culling\n * @param {Object} bsphere - Bounding sphere { center: [x,y,z], radius: r }\n * @param {Array} lightPos - Light position [x, y, z]\n * @param {Array} lightDir - Normalized light direction\n * @param {number} maxDistance - Max shadow distance\n * @param {number} coneAngle - Half-angle of spotlight cone in radians\n * @returns {boolean} True if instance should be rendered\n */\n _isInstanceVisibleToSpotlight(bsphere, lightPos, lightDir, maxDistance, coneAngle) {\n // Vector from light to sphere center\n const dx = bsphere.center[0] - lightPos[0]\n const dy = bsphere.center[1] - lightPos[1]\n const dz = bsphere.center[2] - lightPos[2]\n const dist = Math.sqrt(dx * dx + dy * dy + dz * dz)\n\n // Distance test: closest surface must be within max shadow distance\n if (dist - bsphere.radius > maxDistance) {\n return false\n }\n\n // Skip objects too close (behind light or at light position)\n if (dist < 0.1) {\n return true // Include objects at light position\n }\n\n // Cone test: check if sphere intersects the spotlight cone\n // Normalize direction to sphere\n const invDist = 1.0 / dist\n const toDirX = dx * invDist\n const toDirY = dy * invDist\n const toDirZ = dz * invDist\n\n // Dot product with light direction = cos(angle to sphere center)\n const cosAngle = toDirX * lightDir[0] + toDirY * lightDir[1] + toDirZ * lightDir[2]\n\n // Angular radius of sphere as seen from light (sin approximation for small angles)\n // For larger spheres, use proper asin\n const sinAngularRadius = Math.min(bsphere.radius / dist, 1.0)\n const angularRadius = Math.asin(sinAngularRadius)\n\n // Sphere is visible if: angle to center - angular radius < cone angle\n // cos(angle) > cos(coneAngle + angularRadius)\n // For efficiency, compare cosines (reversed inequality since cos is decreasing)\n const expandedConeAngle = coneAngle + angularRadius\n const cosExpandedCone = Math.cos(Math.min(expandedConeAngle, Math.PI))\n\n if (cosAngle < cosExpandedCone) {\n return false // Sphere is outside the expanded cone\n }\n\n return true\n }\n\n /**\n * Build filtered instance data for a cascade\n * Returns a Float32Array with only the instances visible to this cascade\n * @param {Object} geometry - Geometry with instanceData\n * @param {mat4} cascadeMatrix - Cascade's view-projection matrix\n * @param {Array} lightDir - Normalized light direction (pointing to light)\n * @param {number} groundLevel - Ground plane Y coordinate\n * @param {Object|null} combinedBsphere - Combined bsphere for skinned models (optional)\n * @returns {{ data: Float32Array, count: number }}\n */\n _buildCascadeFilteredInstances(geometry, cascadeMatrix, lightDir, groundLevel, combinedBsphere = null) {\n const instanceStride = 28 // floats per instance (matrix + posRadius + uvTransform + color)\n const visibleIndices = []\n\n // Use combined bsphere for skinned models, otherwise fall back to geometry's sphere\n const localBsphere = combinedBsphere || geometry.getBoundingSphere?.()\n\n for (let i = 0; i < geometry.instanceCount; i++) {\n const offset = i * instanceStride\n let bsphere = {\n center: [\n geometry.instanceData[offset + 16],\n geometry.instanceData[offset + 17],\n geometry.instanceData[offset + 18]\n ],\n radius: Math.abs(geometry.instanceData[offset + 19])\n }\n\n // If no valid bsphere in instance data, use geometry's local bsphere + transform\n if (bsphere.radius <= 0 && localBsphere && localBsphere.radius > 0) {\n // Extract transform matrix from instance data\n const matrix = geometry.instanceData.subarray(offset, offset + 16)\n // Transform local bsphere by instance matrix\n bsphere = transformBoundingSphere(localBsphere, matrix)\n }\n\n // Still no valid bsphere - include by default\n if (!bsphere || bsphere.radius <= 0) {\n visibleIndices.push(i)\n continue\n }\n\n // Calculate shadow bounding sphere for this instance\n const shadowBsphere = calculateShadowBoundingSphere(bsphere, lightDir, groundLevel)\n\n // Test if shadow bounding sphere intersects this cascade's box\n if (sphereInCascade(shadowBsphere, cascadeMatrix)) {\n visibleIndices.push(i)\n }\n }\n\n if (visibleIndices.length === 0) {\n return { data: null, count: 0 }\n }\n\n // If all instances are visible, no need to copy data\n if (visibleIndices.length === geometry.instanceCount) {\n return { data: null, count: geometry.instanceCount, useOriginal: true }\n }\n\n // Build filtered instance data\n const filteredData = new Float32Array(visibleIndices.length * instanceStride)\n for (let i = 0; i < visibleIndices.length; i++) {\n const srcOffset = visibleIndices[i] * instanceStride\n const dstOffset = i * instanceStride\n for (let j = 0; j < instanceStride; j++) {\n filteredData[dstOffset + j] = geometry.instanceData[srcOffset + j]\n }\n }\n\n return { data: filteredData, count: visibleIndices.length }\n }\n\n /**\n * Build filtered instance data for a spotlight using cone culling\n * Returns a Float32Array with only the instances visible to this light\n * @param {Object} geometry - Geometry with instanceData\n * @param {Array} lightPos - Light position\n * @param {Array} lightDir - Normalized light direction\n * @param {number} maxDistance - Max shadow distance (min of light radius and spotMaxDistance)\n * @param {number} coneAngle - Half-angle of spotlight cone in radians\n * @param {Object|null} combinedBsphere - Combined bsphere for skinned models (optional)\n * @returns {{ data: Float32Array, count: number }}\n */\n _buildFilteredInstances(geometry, lightPos, lightDir, maxDistance, coneAngle, combinedBsphere = null) {\n const instanceStride = 28 // floats per instance (matrix + posRadius + uvTransform + color)\n const visibleIndices = []\n\n // Use combined bsphere for skinned models, otherwise fall back to geometry's sphere\n const localBsphere = combinedBsphere || geometry.getBoundingSphere?.()\n\n for (let i = 0; i < geometry.instanceCount; i++) {\n const offset = i * instanceStride\n let bsphere = {\n center: [\n geometry.instanceData[offset + 16],\n geometry.instanceData[offset + 17],\n geometry.instanceData[offset + 18]\n ],\n radius: Math.abs(geometry.instanceData[offset + 19])\n }\n\n // If no valid bsphere in instance data, use geometry's local bsphere + transform\n if (bsphere.radius <= 0 && localBsphere && localBsphere.radius > 0) {\n // Extract transform matrix from instance data\n const matrix = geometry.instanceData.subarray(offset, offset + 16)\n // Transform local bsphere by instance matrix\n bsphere = transformBoundingSphere(localBsphere, matrix)\n }\n\n // Still no valid bsphere - include by default\n if (!bsphere || bsphere.radius <= 0) {\n visibleIndices.push(i)\n continue\n }\n\n if (this._isInstanceVisibleToSpotlight(bsphere, lightPos, lightDir, maxDistance, coneAngle)) {\n visibleIndices.push(i)\n }\n }\n\n if (visibleIndices.length === 0) {\n return { data: null, count: 0 }\n }\n\n // If all instances are visible, no need to copy data\n if (visibleIndices.length === geometry.instanceCount) {\n return { data: null, count: geometry.instanceCount, useOriginal: true }\n }\n\n // Build filtered instance data\n const filteredData = new Float32Array(visibleIndices.length * instanceStride)\n for (let i = 0; i < visibleIndices.length; i++) {\n const srcOffset = visibleIndices[i] * instanceStride\n const dstOffset = i * instanceStride\n for (let j = 0; j < instanceStride; j++) {\n filteredData[dstOffset + j] = geometry.instanceData[srcOffset + j]\n }\n }\n\n return { data: filteredData, count: visibleIndices.length }\n }\n\n /**\n * Create perspective projection matrix for WebGPU (0-1 depth range)\n */\n perspectiveZO(out, fovy, aspect, near, far) {\n const f = 1.0 / Math.tan(fovy / 2)\n const nf = 1 / (near - far)\n\n out[0] = f / aspect\n out[1] = 0\n out[2] = 0\n out[3] = 0\n out[4] = 0\n out[5] = f\n out[6] = 0\n out[7] = 0\n out[8] = 0\n out[9] = 0\n out[10] = far * nf // WebGPU: f/(n-f)\n out[11] = -1\n out[12] = 0\n out[13] = 0\n out[14] = near * far * nf // WebGPU: n*f/(n-f)\n out[15] = 0\n\n return out\n }\n\n /**\n * Create orthographic projection matrix for WebGPU (0-1 depth range)\n * gl-matrix uses OpenGL convention (-1 to 1), so we need a custom version\n */\n orthoZO(out, left, right, bottom, top, near, far) {\n const lr = 1 / (left - right)\n const bt = 1 / (bottom - top)\n const nf = 1 / (near - far)\n\n out[0] = -2 * lr\n out[1] = 0\n out[2] = 0\n out[3] = 0\n out[4] = 0\n out[5] = -2 * bt\n out[6] = 0\n out[7] = 0\n out[8] = 0\n out[9] = 0\n out[10] = nf // WebGPU: -1/(f-n) = 1/(n-f) = nf\n out[11] = 0\n out[12] = (left + right) * lr\n out[13] = (top + bottom) * bt\n out[14] = near * nf // WebGPU: -n/(f-n) = n/(n-f) = near*nf\n out[15] = 1\n\n return out\n }\n\n /**\n * Calculate light view-projection matrices for all cascades\n * Each cascade is centered on camera's XZ position for best shadow utilization\n */\n calculateCascadeMatrices(lightDir, camera) {\n const dir = vec3.create()\n vec3.normalize(dir, lightDir)\n\n // Fixed up vector\n const up = Math.abs(dir[1]) > 0.99\n ? vec3.fromValues(0, 0, 1)\n : vec3.fromValues(0, 1, 0)\n\n // Camera's XZ position (center cascades here)\n const cameraXZ = vec3.fromValues(camera.position[0], 0, camera.position[2])\n\n for (let i = 0; i < this.cascadeCount; i++) {\n const lightView = mat4.create()\n const lightProj = mat4.create()\n\n const frustumSize = this.cascadeSizes[i]\n // Light needs to be far enough to avoid near-plane clipping\n const lightDistance = frustumSize * 2 + 50\n const nearPlane = 1\n const farPlane = lightDistance * 2 + frustumSize\n\n // Light position: camera XZ + light direction * distance\n const lightPos = vec3.fromValues(\n cameraXZ[0] + dir[0] * lightDistance,\n dir[1] * lightDistance,\n cameraXZ[2] + dir[2] * lightDistance\n )\n\n // Target is camera's XZ position\n const target = vec3.clone(cameraXZ)\n\n mat4.lookAt(lightView, lightPos, target, up)\n this.orthoZO(lightProj, -frustumSize, frustumSize, -frustumSize, frustumSize, nearPlane, farPlane)\n mat4.multiply(this.cascadeMatrices[i], lightProj, lightView)\n }\n\n // For backward compatibility, copy cascade 0 to directionalLightMatrix\n mat4.copy(this.directionalLightMatrix, this.cascadeMatrices[0])\n\n return this.cascadeMatrices\n }\n\n /**\n * Calculate spotlight view-projection matrix\n * @param {Object} light - Light with position, direction, geom (radius, innerCone, outerCone)\n * @param {number} slotIndex - Which slot this light is assigned to\n * @returns {mat4} Light view-projection matrix\n */\n calculateSpotLightMatrix(light, slotIndex) {\n const lightView = mat4.create()\n const lightProj = mat4.create()\n\n const pos = vec3.fromValues(light.position[0], light.position[1], light.position[2])\n const dir = vec3.create()\n vec3.normalize(dir, light.direction)\n\n // Target = position + direction\n const target = vec3.create()\n vec3.add(target, pos, dir)\n\n // Up vector - avoid parallel with direction\n const up = Math.abs(dir[1]) > 0.9\n ? vec3.fromValues(1, 0, 0)\n : vec3.fromValues(0, 1, 0)\n\n mat4.lookAt(lightView, pos, target, up)\n\n // FOV based on outer cone angle (geom.z is cosine of angle)\n // Convert from cosine to angle, double it for full cone\n // Cap at 120 degrees (60 degree half-angle) for shadow quality\n const outerCone = light.geom[2] || 0.7\n const coneAngle = Math.acos(outerCone)\n const maxShadowAngle = Math.PI / 3 // 60 degrees = 120 degree total FOV\n const shadowAngle = Math.min(coneAngle, maxShadowAngle)\n const fov = shadowAngle * 2.0 + 0.05 // Small margin\n\n const near = 0.5 // Close enough to capture nearby shadows\n const far = light.geom[0] || 10 // radius\n\n this.perspectiveZO(lightProj, fov, 1.0, near, far)\n\n const matrix = this.spotLightMatrices[slotIndex]\n mat4.multiply(matrix, lightProj, lightView)\n\n return matrix\n }\n\n /**\n * Assign shadow slots to spotlights based on distance to camera and frustum visibility\n * @param {Array} lights - Array of light objects\n * @param {vec3} cameraPosition - Camera position\n * @param {Frustum} cameraFrustum - Camera frustum for culling\n * @returns {Object} Mapping info for shader\n */\n assignSpotShadowSlots(lights, cameraPosition, cameraFrustum) {\n // Reset all slots\n this.spotShadowSlots.fill(-1)\n\n // Filter to spotlights (lightType == 2) that affect the visible area\n const spotLights = []\n let culledByFrustum = 0\n let culledByDistance = 0\n\n for (let i = 0; i < lights.length; i++) {\n const light = lights[i]\n if (!light || !light.enabled) continue\n if (light.lightType !== 2) continue // Only spotlights\n\n const lightRadius = light.geom?.[0] || 10\n\n // Distance from light to camera\n const dx = light.position[0] - cameraPosition[0]\n const dy = light.position[1] - cameraPosition[1]\n const dz = light.position[2] - cameraPosition[2]\n const distance = Math.sqrt(dx * dx + dy * dy + dz * dz)\n\n // Skip lights too far from camera (shadow max distance + light radius)\n // The light could still affect visible geometry even if the light itself is far\n if (distance - lightRadius > this.shadowMaxDistance) {\n culledByDistance++\n continue\n }\n\n // Frustum cull: check if light's bounding sphere intersects camera frustum\n // The bounding sphere is the light's area of effect\n if (cameraFrustum) {\n const lightBsphere = {\n center: light.position,\n radius: lightRadius\n }\n if (!cameraFrustum.testSpherePlanes(lightBsphere)) {\n culledByFrustum++\n continue\n }\n }\n\n spotLights.push({\n index: i,\n light: light,\n distance: distance,\n radius: lightRadius\n })\n }\n\n // Sort by distance (closest first)\n spotLights.sort((a, b) => a.distance - b.distance)\n\n // Assign closest visible lights to shadow slots (up to maxSpotShadows)\n const assignments = []\n for (let slot = 0; slot < Math.min(spotLights.length, this.maxSpotShadows); slot++) {\n const spotLight = spotLights[slot]\n\n this.spotShadowSlots[spotLight.index] = slot\n\n // Calculate shadow fade factor (1.0 at shadowFadeStart, 0.0 at shadowMaxDistance)\n let fadeFactor = 1.0\n if (spotLight.distance > this.shadowFadeStart) {\n fadeFactor = 1.0 - (spotLight.distance - this.shadowFadeStart) /\n (this.shadowMaxDistance - this.shadowFadeStart)\n fadeFactor = Math.max(0, fadeFactor)\n }\n\n assignments.push({\n slot: slot,\n lightIndex: spotLight.index,\n light: spotLight.light,\n distance: spotLight.distance,\n fadeFactor: fadeFactor\n })\n }\n\n return {\n assignments: assignments,\n totalSpotLights: spotLights.length,\n culledByFrustum: culledByFrustum,\n culledByDistance: culledByDistance\n }\n }\n\n async _execute(context) {\n const { device, stats } = this.engine\n const { camera, meshes, mainLight, lights } = context\n\n if (!this.pipeline || !meshes) {\n console.warn('ShadowPass: No pipeline or meshes', { pipeline: !!this.pipeline, meshes: !!meshes })\n return\n }\n\n // Clear bind group caches for skinned meshes to ensure fresh joint textures are bound\n // This prevents stale bind groups from causing shadow artifacts on animated meshes\n this._meshBindGroups = new WeakMap()\n if (this._skinBindGroups) {\n this._skinBindGroups = new WeakMap()\n }\n\n // Track shadow pass stats\n let shadowDrawCalls = 0\n let shadowTriangles = 0\n let shadowCulledInstances = 0\n\n // Check if main directional light is enabled\n const mainLightEnabled = !mainLight || mainLight.enabled !== false\n\n // Calculate cascade matrices (centered on camera XZ) - even if disabled, for consistent state\n const dir = vec3.fromValues(\n mainLight?.direction?.[0] ?? -1,\n mainLight?.direction?.[1] ?? 1,\n mainLight?.direction?.[2] ?? -0.5\n )\n this.calculateCascadeMatrices(dir, camera)\n\n // ===================\n // CASCADED DIRECTIONAL SHADOWS\n // ===================\n\n // Uniform buffer layout: mat4 (16) + vec3+f32 (4) + alpha hash params (5) + padding (3) = 28 floats (112 bytes)\n const uniformData = new Float32Array(28)\n let totalInstances = 0\n let totalTriangles = 0\n\n // Noise offset for alpha hashing - always static to avoid shimmer on cutout edges\n const noiseOffsetX = 0\n const noiseOffsetY = 0\n\n // Get light direction for shadow bounding sphere calculation\n const lightDir = vec3.fromValues(\n mainLight?.direction?.[0] ?? -1,\n mainLight?.direction?.[1] ?? 1,\n mainLight?.direction?.[2] ?? -0.5\n )\n vec3.normalize(lightDir, lightDir)\n\n // Ground level for shadow projection\n const groundLevel = this.settings?.planarReflection?.groundLevel ?? 0\n\n // Create camera frustum for culling static meshes\n const cameraFrustum = this._createFrustumFromMatrix(camera.viewProj)\n const shadowConfig = this.settings?.culling?.shadow\n const shadowFrustumCullingEnabled = shadowConfig?.frustum !== false\n const shadowHiZEnabled = shadowConfig?.hiZ !== false && this.hizPass\n const shadowMaxDistance = shadowConfig?.maxDistance ?? 100\n\n // Pre-filter static meshes by shadow bounding sphere visibility\n // This is used for BOTH cascade and spotlight shadows\n // Entity-managed meshes are already filtered in RenderGraph, but static meshes aren't\n const visibleMeshes = {}\n let meshFrustumCulled = 0\n let meshDistanceCulled = 0\n let meshOcclusionCulled = 0\n let meshNoBsphere = 0\n\n for (const name in meshes) {\n const mesh = meshes[name]\n const geometry = mesh.geometry\n if (!geometry || geometry.instanceCount === 0) continue\n\n // Entity-managed meshes (not static) are already culled - include them\n if (!mesh.static) {\n visibleMeshes[name] = mesh\n continue\n }\n\n // For static meshes, apply shadow bounding sphere culling\n // For skinned meshes with multiple submeshes, use combined bsphere if available\n // This ensures all submeshes are culled together as a unit\n const localBsphere = mesh.combinedBsphere || geometry.getBoundingSphere?.()\n if (!localBsphere || localBsphere.radius <= 0) {\n // No bsphere - include but track it\n meshNoBsphere++\n visibleMeshes[name] = mesh\n continue\n }\n\n // Has valid bsphere - apply culling\n // Get world bounding sphere (transform by first instance matrix)\n const matrix = geometry.instanceData?.subarray(0, 16)\n const worldBsphere = matrix ?\n transformBoundingSphere(localBsphere, matrix) :\n localBsphere\n\n // Calculate shadow bounding sphere (only if main light enabled)\n // For spotlights only, use object's own bsphere for culling\n const shadowBsphere = mainLightEnabled\n ? calculateShadowBoundingSphere(worldBsphere, lightDir, groundLevel)\n : worldBsphere\n\n // For skinned meshes, expand the shadow bsphere to account for animation\n // Animated poses can extend beyond the rest pose bounding sphere\n const skinnedExpansion = this.engine?.settings?.shadow?.skinnedBsphereExpansion ?? 2.0\n const cullBsphere = mesh.hasSkin ? {\n center: shadowBsphere.center,\n radius: shadowBsphere.radius * skinnedExpansion\n } : shadowBsphere\n\n // Distance culling - skip if shadow sphere is too far from camera\n const dx = cullBsphere.center[0] - camera.position[0]\n const dy = cullBsphere.center[1] - camera.position[1]\n const dz = cullBsphere.center[2] - camera.position[2]\n const distance = Math.sqrt(dx * dx + dy * dy + dz * dz) - cullBsphere.radius\n if (distance > shadowMaxDistance) {\n meshDistanceCulled++\n continue\n }\n\n // Frustum culling - skip if shadow not visible to camera\n if (shadowFrustumCullingEnabled && cameraFrustum) {\n if (!cameraFrustum.testSpherePlanes(cullBsphere)) {\n meshFrustumCulled++\n continue\n }\n }\n\n // HiZ occlusion culling - skip if shadow sphere is fully occluded\n if (shadowHiZEnabled && this.hizPass) {\n const occluded = this.hizPass.testSphereOcclusion(\n cullBsphere,\n camera.viewProj,\n camera.near,\n camera.far,\n camera.position\n )\n if (occluded) {\n meshOcclusionCulled++\n continue\n }\n }\n\n visibleMeshes[name] = mesh\n }\n\n // Store mesh culling stats for reporting\n this._lastMeshFrustumCulled = meshFrustumCulled\n this._lastMeshDistanceCulled = meshDistanceCulled\n this._lastMeshOcclusionCulled = meshOcclusionCulled\n this._lastMeshNoBsphere = meshNoBsphere\n\n // Only render cascade shadows if main light is enabled\n if (mainLightEnabled) {\n // Check if per-cascade filtering is enabled\n const cascadeFilterEnabled = this.settings?.culling?.shadow?.cascadeFilter !== false\n\n // Per-cascade culling stats\n let cascadeCulledInstances = 0\n\n // Render each cascade - submit separately to ensure correct matrix\n for (let cascade = 0; cascade < this.cascadeCount; cascade++) {\n // Update uniform buffer with this cascade's matrix\n uniformData.set(this.cascadeMatrices[cascade], 0) // 0-15\n uniformData.set([0, 100, 0], 16) // 16-18 (lightPosition)\n uniformData[19] = 0 // lightType: directional\n // Alpha hash params (enabled globally - per-mesh control via albedo texture)\n const globalLuminanceToAlpha = this.settings?.rendering?.luminanceToAlpha ? 1.0 : 0.0\n uniformData[20] = 1.0 // alphaHashEnabled\n uniformData[21] = 1.0 // alphaHashScale\n uniformData[22] = globalLuminanceToAlpha // luminanceToAlpha\n uniformData[23] = this.noiseSize // noiseSize\n uniformData[24] = noiseOffsetX // noiseOffsetX\n uniformData[25] = this.settings?.shadow?.surfaceBias ?? 0 // surfaceBias\n // 26-27 are padding\n uniformData[28] = lightDir[0] // lightDirection.x\n uniformData[29] = lightDir[1] // lightDirection.y\n uniformData[30] = lightDir[2] // lightDirection.z\n // 31 is padding\n device.queue.writeBuffer(this.uniformBuffer, 0, uniformData)\n\n // Create command encoder for this cascade\n const cascadeEncoder = device.createCommandEncoder({\n label: `Shadow Cascade ${cascade}`\n })\n\n // Render to this cascade's layer\n const cascadePass = cascadeEncoder.beginRenderPass({\n colorAttachments: [],\n depthStencilAttachment: {\n view: this.cascadeViews[cascade],\n depthClearValue: 1.0,\n depthLoadOp: 'clear',\n depthStoreOp: 'store',\n }\n })\n\n cascadePass.setPipeline(this.pipeline)\n\n // Collect filtered instances for this cascade\n const meshFilters = []\n let totalFilteredFloats = 0\n const instanceStride = 28 // floats per instance (matrix + posRadius + uvTransform + color)\n\n for (const name in visibleMeshes) {\n const mesh = visibleMeshes[name]\n const geometry = mesh.geometry\n if (geometry.instanceCount === 0) continue\n if (cascade === 0) geometry.update() // Only update geometry once\n\n // Apply per-cascade filtering if enabled\n let filtered = null\n if (cascadeFilterEnabled) {\n filtered = this._buildCascadeFilteredInstances(\n geometry,\n this.cascadeMatrices[cascade],\n lightDir,\n groundLevel,\n mesh.combinedBsphere // Use combined bsphere for skinned models\n )\n\n if (filtered.count === 0) {\n cascadeCulledInstances += geometry.instanceCount\n continue\n }\n\n cascadeCulledInstances += geometry.instanceCount - filtered.count\n }\n\n // If filtering returned useOriginal, or filtering disabled, use original buffer\n if (!cascadeFilterEnabled || filtered?.useOriginal) {\n meshFilters.push({\n mesh,\n geometry,\n useOriginal: true,\n count: geometry.instanceCount\n })\n } else {\n // Need to use filtered data\n meshFilters.push({\n mesh,\n geometry,\n filtered,\n byteOffset: totalFilteredFloats * 4\n })\n totalFilteredFloats += filtered.count * instanceStride\n }\n }\n\n // Create/resize cascade temp buffer if needed\n const totalBufferSize = totalFilteredFloats * 4\n if (totalBufferSize > 0) {\n if (!this._cascadeTempBuffer || this._cascadeTempBufferSize < totalBufferSize) {\n if (this._cascadeTempBuffer) {\n this._cascadeTempBuffer.destroy()\n }\n const allocSize = Math.max(totalBufferSize, 65536) // Min 64KB\n this._cascadeTempBuffer = device.createBuffer({\n size: allocSize,\n usage: GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_DST,\n label: 'Cascade Shadow Temp Instance Buffer'\n })\n this._cascadeTempBufferSize = allocSize\n }\n\n // Write filtered instance data at their respective offsets\n for (const mf of meshFilters) {\n if (!mf.useOriginal && mf.filtered?.data) {\n device.queue.writeBuffer(this._cascadeTempBuffer, mf.byteOffset, mf.filtered.data)\n }\n }\n }\n\n // Separate meshes by luminanceToAlpha flag for proper uniform handling\n const regularMeshes = meshFilters.filter(mf => !mf.mesh.material?.luminanceToAlpha)\n const luminanceMeshes = meshFilters.filter(mf => mf.mesh.material?.luminanceToAlpha)\n\n // Render regular meshes (luminanceToAlpha = 0)\n for (const mf of regularMeshes) {\n const bindGroup = this.getBindGroupForMesh(mf.mesh)\n cascadePass.setBindGroup(0, bindGroup)\n\n cascadePass.setVertexBuffer(0, mf.geometry.vertexBuffer)\n\n if (mf.useOriginal) {\n cascadePass.setVertexBuffer(1, mf.geometry.instanceBuffer)\n cascadePass.setIndexBuffer(mf.geometry.indexBuffer, 'uint32')\n cascadePass.drawIndexed(mf.geometry.indexArray.length, mf.count)\n\n shadowDrawCalls++\n shadowTriangles += (mf.geometry.indexArray.length / 3) * mf.count\n } else {\n cascadePass.setVertexBuffer(1, this._cascadeTempBuffer, mf.byteOffset)\n cascadePass.setIndexBuffer(mf.geometry.indexBuffer, 'uint32')\n cascadePass.drawIndexed(mf.geometry.indexArray.length, mf.filtered.count)\n\n shadowDrawCalls++\n shadowTriangles += (mf.geometry.indexArray.length / 3) * mf.filtered.count\n }\n\n if (cascade === 0) {\n const count = mf.useOriginal ? mf.count : mf.filtered.count\n totalInstances += count\n totalTriangles += (mf.geometry.indexArray.length / 3) * count\n }\n }\n\n cascadePass.end()\n device.queue.submit([cascadeEncoder.finish()])\n\n // Render luminanceToAlpha meshes in separate pass with updated uniform\n if (luminanceMeshes.length > 0) {\n uniformData[22] = 1.0 // Enable luminanceToAlpha\n device.queue.writeBuffer(this.uniformBuffer, 0, uniformData)\n\n const lumEncoder = device.createCommandEncoder({ label: `Shadow Cascade ${cascade} LumAlpha` })\n const lumPass = lumEncoder.beginRenderPass({\n colorAttachments: [],\n depthStencilAttachment: {\n view: this.cascadeViews[cascade],\n depthClearValue: 1.0,\n depthLoadOp: 'load', // Keep existing depth\n depthStoreOp: 'store',\n }\n })\n\n lumPass.setPipeline(this.pipeline)\n\n for (const mf of luminanceMeshes) {\n const bindGroup = this.getBindGroupForMesh(mf.mesh)\n lumPass.setBindGroup(0, bindGroup)\n\n lumPass.setVertexBuffer(0, mf.geometry.vertexBuffer)\n\n if (mf.useOriginal) {\n lumPass.setVertexBuffer(1, mf.geometry.instanceBuffer)\n lumPass.setIndexBuffer(mf.geometry.indexBuffer, 'uint32')\n lumPass.drawIndexed(mf.geometry.indexArray.length, mf.count)\n\n shadowDrawCalls++\n shadowTriangles += (mf.geometry.indexArray.length / 3) * mf.count\n } else {\n lumPass.setVertexBuffer(1, this._cascadeTempBuffer, mf.byteOffset)\n lumPass.setIndexBuffer(mf.geometry.indexBuffer, 'uint32')\n lumPass.drawIndexed(mf.geometry.indexArray.length, mf.filtered.count)\n\n shadowDrawCalls++\n shadowTriangles += (mf.geometry.indexArray.length / 3) * mf.filtered.count\n }\n\n if (cascade === 0) {\n const count = mf.useOriginal ? mf.count : mf.filtered.count\n totalInstances += count\n totalTriangles += (mf.geometry.indexArray.length / 3) * count\n }\n }\n\n lumPass.end()\n device.queue.submit([lumEncoder.finish()])\n\n // Reset luminanceToAlpha for next cascade\n uniformData[22] = 0.0\n }\n }\n\n // Store cascade culling stats\n shadowCulledInstances += cascadeCulledInstances\n\n } // End if (mainLightEnabled)\n\n // Update cascade matrices storage buffer (always, for consistent state)\n for (let i = 0; i < this.cascadeCount; i++) {\n this.cascadeMatricesData.set(this.cascadeMatrices[i], i * 16)\n }\n device.queue.writeBuffer(this.cascadeMatricesBuffer, 0, this.cascadeMatricesData)\n\n // Update camera shadow detection (for adaptive volumetric fog)\n if (mainLightEnabled) {\n this._updateCameraShadowDetection(camera)\n }\n\n // ===================\n // SPOTLIGHT SHADOWS (always runs, even when main light is disabled)\n // ===================\n\n // If main light was disabled, we need to update geometry buffers here\n // (normally done in cascade loop, but that was skipped)\n if (!mainLightEnabled) {\n for (const name in meshes) {\n const mesh = meshes[name]\n const geometry = mesh.geometry\n if (geometry.instanceCount > 0) {\n geometry.update()\n }\n }\n }\n\n // Reset slot info\n this.lastSlotInfo = { assignments: [], totalSpotLights: 0, culledByFrustum: 0, culledByDistance: 0 }\n this.spotShadowSlots.fill(-1)\n\n // Assign shadow slots to closest spotlights that affect visible area\n if (lights && lights.length > 0 && this.spotShadowAtlas) {\n // Create camera frustum for culling spotlights\n const cameraFrustum = this._createFrustumFromMatrix(camera.viewProj)\n const slotInfo = this.assignSpotShadowSlots(lights, camera.position, cameraFrustum)\n this.lastSlotInfo = slotInfo\n\n // Clear the atlas first - use separate encoder and submit immediately\n const clearEncoder = device.createCommandEncoder({ label: 'Spot Shadow Clear' })\n const clearPass = clearEncoder.beginRenderPass({\n colorAttachments: [],\n depthStencilAttachment: {\n view: this.spotShadowAtlasView,\n depthClearValue: 1.0,\n depthLoadOp: 'clear',\n depthStoreOp: 'store',\n }\n })\n clearPass.end()\n device.queue.submit([clearEncoder.finish()])\n\n // Render each spotlight shadow - IMPORTANT: Submit each one separately\n // because writeBuffer calls are queued and would all execute before any render pass\n for (const assignment of slotInfo.assignments) {\n // Calculate spotlight matrix\n this.calculateSpotLightMatrix(assignment.light, assignment.slot)\n const spotMatrix = this.spotLightMatrices[assignment.slot]\n\n // Extract spotlight parameters for cone culling\n const lightPos = assignment.light.position\n const lightRadius = assignment.light.geom[0] || 10\n\n // Normalize light direction\n const spotLightDir = vec3.create()\n vec3.normalize(spotLightDir, assignment.light.direction)\n\n // Max shadow distance is minimum of light radius and spotMaxDistance setting\n const spotShadowMaxDist = Math.min(lightRadius, this.shadowMaxDistance)\n\n // Cone angle from outer cone (geom[2] is cosine of half-angle)\n const outerConeCos = assignment.light.geom[2] || 0.7\n const coneAngle = Math.acos(outerConeCos)\n\n // Update uniform buffer with spotlight matrix and alpha hash params\n uniformData.set(spotMatrix, 0) // 0-15\n uniformData.set(lightPos, 16) // 16-18 (lightPosition)\n uniformData[19] = 2 // lightType: spotlight\n // Alpha hash params (same as cascaded shadows)\n const spotLuminanceToAlpha = this.settings?.rendering?.luminanceToAlpha ? 1.0 : 0.0\n uniformData[20] = 1.0 // alphaHashEnabled\n uniformData[21] = 1.0 // alphaHashScale\n uniformData[22] = spotLuminanceToAlpha // luminanceToAlpha\n uniformData[23] = this.noiseSize // noiseSize\n uniformData[24] = noiseOffsetX // noiseOffsetX\n uniformData[25] = this.settings?.shadow?.surfaceBias ?? 0 // surfaceBias\n // 26-27 are padding\n uniformData[28] = spotLightDir[0] // lightDirection.x\n uniformData[29] = spotLightDir[1] // lightDirection.y\n uniformData[30] = spotLightDir[2] // lightDirection.z\n // 31 is padding\n device.queue.writeBuffer(this.uniformBuffer, 0, uniformData)\n\n // Calculate viewport for this slot in atlas\n const col = assignment.slot % this.spotTilesPerRow\n const row = Math.floor(assignment.slot / this.spotTilesPerRow)\n const x = col * this.spotTileSize\n const y = row * this.spotTileSize\n\n // Create a separate command encoder for each spotlight to ensure\n // the writeBuffer takes effect before rendering\n const spotEncoder = device.createCommandEncoder({\n label: `Spot Shadow ${assignment.slot}`\n })\n\n // Render to this tile using viewport\n const spotPass = spotEncoder.beginRenderPass({\n colorAttachments: [],\n depthStencilAttachment: {\n view: this.spotShadowAtlasView,\n depthClearValue: 1.0,\n depthLoadOp: 'load', // Don't clear, we already did\n depthStoreOp: 'store',\n }\n })\n\n spotPass.setPipeline(this.pipeline)\n spotPass.setViewport(x, y, this.spotTileSize, this.spotTileSize, 0, 1)\n spotPass.setScissorRect(x, y, this.spotTileSize, this.spotTileSize)\n\n // First pass: collect all filtered instances and calculate offsets\n const meshFilters = []\n let totalFilteredFloats = 0\n let spotCulledInstances = 0\n const instanceStride = 28 // floats per instance (matrix + posRadius + uvTransform + color)\n\n // Use visibleMeshes for spotlight shadows - applies same static mesh\n // culling (frustum, distance, HiZ) as cascade shadows\n for (const name in visibleMeshes) {\n const mesh = visibleMeshes[name]\n const geometry = mesh.geometry\n if (geometry.instanceCount === 0) continue\n\n // Build filtered instances using cone culling\n const filtered = this._buildFilteredInstances(\n geometry, lightPos, spotLightDir, spotShadowMaxDist, coneAngle,\n mesh.combinedBsphere // Use combined bsphere for skinned models\n )\n\n if (filtered.count === 0) {\n spotCulledInstances += geometry.instanceCount\n continue\n }\n\n spotCulledInstances += geometry.instanceCount - filtered.count\n\n // Handle useOriginal optimization (all instances visible)\n if (filtered.useOriginal) {\n meshFilters.push({\n mesh,\n geometry,\n useOriginal: true,\n count: filtered.count\n })\n } else {\n meshFilters.push({\n mesh,\n geometry,\n filtered,\n byteOffset: totalFilteredFloats * 4 // offset in bytes\n })\n totalFilteredFloats += filtered.count * instanceStride\n }\n }\n\n // Create/resize buffer if needed for filtered instances\n const totalBufferSize = totalFilteredFloats * 4\n if (totalBufferSize > 0) {\n if (!this._tempInstanceBuffer || this._tempInstanceBufferSize < totalBufferSize) {\n if (this._tempInstanceBuffer) {\n this._tempInstanceBuffer.destroy()\n }\n const allocSize = Math.max(totalBufferSize, 16384) // Min 16KB\n this._tempInstanceBuffer = device.createBuffer({\n size: allocSize,\n usage: GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_DST,\n label: 'Spot Shadow Temp Instance Buffer'\n })\n this._tempInstanceBufferSize = allocSize\n }\n\n // Write filtered instance data at their respective offsets\n for (const mf of meshFilters) {\n if (!mf.useOriginal && mf.filtered?.data) {\n device.queue.writeBuffer(this._tempInstanceBuffer, mf.byteOffset, mf.filtered.data)\n }\n }\n }\n\n // Separate meshes by luminanceToAlpha flag\n const regularMeshes = meshFilters.filter(mf => !mf.mesh.material?.luminanceToAlpha)\n const luminanceMeshes = meshFilters.filter(mf => mf.mesh.material?.luminanceToAlpha)\n\n // Render regular meshes (luminanceToAlpha = 0)\n for (const mf of regularMeshes) {\n const bindGroup = this.getBindGroupForMesh(mf.mesh)\n spotPass.setBindGroup(0, bindGroup)\n spotPass.setVertexBuffer(0, mf.geometry.vertexBuffer)\n\n if (mf.useOriginal) {\n spotPass.setVertexBuffer(1, mf.geometry.instanceBuffer)\n spotPass.setIndexBuffer(mf.geometry.indexBuffer, 'uint32')\n spotPass.drawIndexed(mf.geometry.indexArray.length, mf.count)\n } else {\n spotPass.setVertexBuffer(1, this._tempInstanceBuffer, mf.byteOffset)\n spotPass.setIndexBuffer(mf.geometry.indexBuffer, 'uint32')\n spotPass.drawIndexed(mf.geometry.indexArray.length, mf.filtered.count)\n }\n }\n\n spotPass.end()\n device.queue.submit([spotEncoder.finish()])\n\n // Render luminanceToAlpha meshes in separate pass\n if (luminanceMeshes.length > 0) {\n uniformData[22] = 1.0 // Enable luminanceToAlpha\n device.queue.writeBuffer(this.uniformBuffer, 0, uniformData)\n\n const lumEncoder = device.createCommandEncoder({ label: `Spot Shadow ${assignment.slot} LumAlpha` })\n const lumPass = lumEncoder.beginRenderPass({\n colorAttachments: [],\n depthStencilAttachment: {\n view: this.spotShadowAtlasView,\n depthClearValue: 1.0,\n depthLoadOp: 'load', // Keep existing depth\n depthStoreOp: 'store',\n }\n })\n\n lumPass.setPipeline(this.pipeline)\n lumPass.setViewport(x, y, this.spotTileSize, this.spotTileSize, 0, 1)\n lumPass.setScissorRect(x, y, this.spotTileSize, this.spotTileSize)\n\n for (const mf of luminanceMeshes) {\n const bindGroup = this.getBindGroupForMesh(mf.mesh)\n lumPass.setBindGroup(0, bindGroup)\n lumPass.setVertexBuffer(0, mf.geometry.vertexBuffer)\n\n if (mf.useOriginal) {\n lumPass.setVertexBuffer(1, mf.geometry.instanceBuffer)\n lumPass.setIndexBuffer(mf.geometry.indexBuffer, 'uint32')\n lumPass.drawIndexed(mf.geometry.indexArray.length, mf.count)\n } else {\n lumPass.setVertexBuffer(1, this._tempInstanceBuffer, mf.byteOffset)\n lumPass.setIndexBuffer(mf.geometry.indexBuffer, 'uint32')\n lumPass.drawIndexed(mf.geometry.indexArray.length, mf.filtered.count)\n }\n }\n\n lumPass.end()\n device.queue.submit([lumEncoder.finish()])\n\n // Reset luminanceToAlpha\n uniformData[22] = 0.0\n }\n\n // Track stats for spotlight shadows\n for (const mf of meshFilters) {\n shadowDrawCalls++\n const count = mf.useOriginal ? mf.count : mf.filtered.count\n shadowTriangles += (mf.geometry.indexArray.length / 3) * count\n }\n shadowCulledInstances += spotCulledInstances\n }\n\n // Update spot matrices storage buffer with all calculated matrices\n for (let i = 0; i < this.maxSpotShadows; i++) {\n this.spotMatricesData.set(this.spotLightMatrices[i], i * 16)\n }\n device.queue.writeBuffer(this.spotMatricesBuffer, 0, this.spotMatricesData)\n\n\n // IMPORTANT: Restore directional light matrix to uniform buffer\n // This prevents the last spotlight matrix from corrupting subsequent passes\n uniformData.set(this.directionalLightMatrix, 0)\n uniformData.set([0, 100, 0], 16)\n uniformData[19] = 0 // Light type: directional\n device.queue.writeBuffer(this.uniformBuffer, 0, uniformData)\n }\n\n // Add shadow stats to global stats\n stats.shadowDrawCalls = shadowDrawCalls\n stats.shadowTriangles = shadowTriangles\n stats.shadowCulledInstances = shadowCulledInstances\n\n // Add mesh culling stats (stored from mainLightEnabled block)\n stats.shadowMeshFrustumCulled = this._lastMeshFrustumCulled || 0\n stats.shadowMeshDistanceCulled = this._lastMeshDistanceCulled || 0\n stats.shadowMeshOcclusionCulled = this._lastMeshOcclusionCulled || 0\n stats.shadowMeshNoBsphere = this._lastMeshNoBsphere || 0\n }\n\n async _resize(width, height) {\n // Shadow maps don't resize with screen\n }\n\n _destroy() {\n if (this.directionalShadowMap) {\n this.directionalShadowMap.destroy()\n }\n if (this.spotShadowAtlas) {\n this.spotShadowAtlas.destroy()\n }\n }\n\n /**\n * Get shadow map texture for lighting pass (texture array)\n */\n getShadowMap() {\n return this.directionalShadowMap\n }\n\n /**\n * Get shadow map view for binding (2d-array view)\n */\n getShadowMapView() {\n return this.directionalShadowMapView\n }\n\n /**\n * Get cascade matrices storage buffer\n */\n getCascadeMatricesBuffer() {\n return this.cascadeMatricesBuffer\n }\n\n /**\n * Get cascade sizes array\n */\n getCascadeSizes() {\n return this.cascadeSizes\n }\n\n /**\n * Get number of cascades\n */\n getCascadeCount() {\n return this.cascadeCount\n }\n\n /**\n * Get shadow sampler (comparison sampler)\n */\n getShadowSampler() {\n return this.shadowSampler\n }\n\n /**\n * Get depth sampler (regular sampler)\n */\n getDepthSampler() {\n return this.depthSampler\n }\n\n /**\n * Get light matrix for shader\n */\n getLightMatrix() {\n return this.directionalLightMatrix\n }\n\n /**\n * Get spot shadow atlas texture\n */\n getSpotShadowAtlas() {\n return this.spotShadowAtlas\n }\n\n /**\n * Get spot shadow atlas view\n */\n getSpotShadowAtlasView() {\n return this.spotShadowAtlasView\n }\n\n /**\n * Get spotlight shadow matrices (array of mat4)\n */\n getSpotLightMatrices() {\n return this.spotLightMatrices\n }\n\n /**\n * Get the storage buffer containing spot shadow matrices\n */\n getSpotMatricesBuffer() {\n return this.spotMatricesBuffer\n }\n\n /**\n * Get slot assignments for each light (-1 if no shadow)\n */\n getSpotShadowSlots() {\n return this.spotShadowSlots\n }\n\n /**\n * Get shadow parameters for shader\n */\n getSpotShadowParams() {\n return {\n atlasSize: [this.spotAtlasSize, this.spotAtlasHeight],\n tileSize: this.spotTileSize,\n tilesPerRow: this.spotTilesPerRow,\n maxSlots: this.maxSpotShadows,\n fadeStart: this.shadowFadeStart,\n maxDistance: this.shadowMaxDistance\n }\n }\n\n /**\n * Get last frame's slot assignment info (for debugging)\n */\n getLastSlotInfo() {\n return this.lastSlotInfo\n }\n\n /**\n * Get cascade matrices as JavaScript arrays (for CPU-side calculations)\n */\n getCascadeMatrices() {\n return this.cascadeMatrices\n }\n\n /**\n * Create resources for camera shadow detection\n * Uses a compute shader to sample shadow at camera position\n */\n async _createCameraShadowDetection() {\n const { device } = this.engine\n\n // Create uniform buffer for camera position and cascade matrices\n // vec3 cameraPos + pad + 3 x mat4 cascadeMatrices = 4 + 48 = 52 floats = 208 bytes\n this._cameraShadowUniformBuffer = device.createBuffer({\n label: 'Camera Shadow Detection Uniforms',\n size: 256, // Aligned\n usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST,\n })\n\n // Create output buffer (1 float for shadow result)\n this._cameraShadowBuffer = device.createBuffer({\n label: 'Camera Shadow Result',\n size: 4,\n usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC,\n })\n\n // Create readback buffer\n this._cameraShadowReadBuffer = device.createBuffer({\n label: 'Camera Shadow Readback',\n size: 4,\n usage: GPUBufferUsage.MAP_READ | GPUBufferUsage.COPY_DST,\n })\n\n // Create compute shader\n const shaderModule = device.createShaderModule({\n label: 'Camera Shadow Detection Shader',\n code: `\n struct Uniforms {\n cameraPosition: vec3f,\n _pad0: f32,\n cascadeMatrix0: mat4x4f,\n cascadeMatrix1: mat4x4f,\n cascadeMatrix2: mat4x4f,\n }\n\n @group(0) @binding(0) var<uniform> uniforms: Uniforms;\n @group(0) @binding(1) var shadowMap: texture_depth_2d_array;\n @group(0) @binding(2) var shadowSampler: sampler_comparison;\n @group(0) @binding(3) var<storage, read_write> result: f32;\n\n fn sampleShadowCascade(worldPos: vec3f, cascadeMatrix: mat4x4f, cascadeIndex: i32) -> f32 {\n let lightSpacePos = cascadeMatrix * vec4f(worldPos, 1.0);\n let projCoords = lightSpacePos.xyz / lightSpacePos.w;\n\n // Convert to UV space\n let uv = vec2f(projCoords.x * 0.5 + 0.5, 0.5 - projCoords.y * 0.5);\n\n // Check bounds\n if (uv.x < 0.01 || uv.x > 0.99 || uv.y < 0.01 || uv.y > 0.99 ||\n projCoords.z < 0.0 || projCoords.z > 1.0) {\n return -1.0; // Out of bounds, try next cascade\n }\n\n let bias = 0.005;\n let depth = projCoords.z - bias;\n return textureSampleCompareLevel(shadowMap, shadowSampler, uv, cascadeIndex, depth);\n }\n\n @compute @workgroup_size(1)\n fn main() {\n let pos = uniforms.cameraPosition;\n\n // Sample multiple points around camera (5m sphere)\n var totalShadow = 0.0;\n var sampleCount = 0.0;\n\n let offsets = array<vec3f, 7>(\n vec3f(0.0, 0.0, 0.0), // Center\n vec3f(0.0, 3.0, 0.0), // Above\n vec3f(0.0, -2.0, 0.0), // Below\n vec3f(4.0, 0.0, 0.0), // Right\n vec3f(-4.0, 0.0, 0.0), // Left\n vec3f(0.0, 0.0, 4.0), // Front\n vec3f(0.0, 0.0, -4.0), // Back\n );\n\n for (var i = 0; i < 7; i++) {\n let samplePos = pos + offsets[i];\n\n // Try cascade 0 first (closest)\n var shadow = sampleShadowCascade(samplePos, uniforms.cascadeMatrix0, 0);\n if (shadow < 0.0) {\n // Try cascade 1\n shadow = sampleShadowCascade(samplePos, uniforms.cascadeMatrix1, 1);\n }\n if (shadow < 0.0) {\n // Try cascade 2\n shadow = sampleShadowCascade(samplePos, uniforms.cascadeMatrix2, 2);\n }\n\n if (shadow >= 0.0) {\n totalShadow += shadow;\n sampleCount += 1.0;\n }\n }\n\n // Average shadow (0 = all in shadow, 1 = all lit)\n // If no valid samples, assume lit\n if (sampleCount > 0.0) {\n result = totalShadow / sampleCount;\n } else {\n result = 1.0;\n }\n }\n `\n })\n\n // Create bind group layout\n this._cameraShadowBGL = device.createBindGroupLayout({\n label: 'Camera Shadow Detection BGL',\n entries: [\n { binding: 0, visibility: GPUShaderStage.COMPUTE, buffer: { type: 'uniform' } },\n { binding: 1, visibility: GPUShaderStage.COMPUTE, texture: { sampleType: 'depth', viewDimension: '2d-array' } },\n { binding: 2, visibility: GPUShaderStage.COMPUTE, sampler: { type: 'comparison' } },\n { binding: 3, visibility: GPUShaderStage.COMPUTE, buffer: { type: 'storage' } },\n ]\n })\n\n // Create pipeline\n this._cameraShadowPipeline = await device.createComputePipelineAsync({\n label: 'Camera Shadow Detection Pipeline',\n layout: device.createPipelineLayout({ bindGroupLayouts: [this._cameraShadowBGL] }),\n compute: { module: shaderModule, entryPoint: 'main' },\n })\n }\n\n /**\n * Update camera shadow detection (called during execute)\n * Dispatches compute shader and starts async readback\n */\n _updateCameraShadowDetection(camera) {\n if (!this._cameraShadowPipeline || !this.directionalShadowMap) return\n\n // Skip if a readback is already pending (buffer is mapped)\n if (this._cameraShadowPending) return\n\n const { device } = this.engine\n const cameraPos = camera.position || [0, 0, 0]\n\n // Update uniform buffer\n const data = new Float32Array(64) // 256 bytes / 4\n data[0] = cameraPos[0]\n data[1] = cameraPos[1]\n data[2] = cameraPos[2]\n data[3] = 0 // padding\n\n // Copy cascade matrices\n if (this.cascadeMatrices[0]) data.set(this.cascadeMatrices[0], 4)\n if (this.cascadeMatrices[1]) data.set(this.cascadeMatrices[1], 20)\n if (this.cascadeMatrices[2]) data.set(this.cascadeMatrices[2], 36)\n\n device.queue.writeBuffer(this._cameraShadowUniformBuffer, 0, data)\n\n // Create bind group (recreated each frame as shadow map view might change)\n const bindGroup = device.createBindGroup({\n layout: this._cameraShadowBGL,\n entries: [\n { binding: 0, resource: { buffer: this._cameraShadowUniformBuffer } },\n { binding: 1, resource: this.directionalShadowMapView },\n { binding: 2, resource: this.shadowSampler },\n { binding: 3, resource: { buffer: this._cameraShadowBuffer } },\n ]\n })\n\n // Dispatch compute shader\n const encoder = device.createCommandEncoder({ label: 'Camera Shadow Detection' })\n const pass = encoder.beginComputePass()\n pass.setPipeline(this._cameraShadowPipeline)\n pass.setBindGroup(0, bindGroup)\n pass.dispatchWorkgroups(1)\n pass.end()\n\n // Copy result to readback buffer\n encoder.copyBufferToBuffer(this._cameraShadowBuffer, 0, this._cameraShadowReadBuffer, 0, 4)\n device.queue.submit([encoder.finish()])\n\n // Start async readback\n this._cameraShadowPending = true\n this._cameraShadowReadBuffer.mapAsync(GPUMapMode.READ).then(() => {\n const data = new Float32Array(this._cameraShadowReadBuffer.getMappedRange())\n const shadowValue = data[0]\n this._cameraShadowReadBuffer.unmap()\n this._cameraShadowPending = false\n\n // Camera is \"in shadow\" if average shadow value is low\n // Threshold of 0.3 means mostly in shadow\n this._cameraInShadow = shadowValue < 0.3\n }).catch(() => {\n this._cameraShadowPending = false\n })\n }\n\n /**\n * Check if camera is in shadow (uses async readback result from previous frames)\n * @returns {boolean} True if camera is mostly in shadow\n */\n isCameraInShadow() {\n return this._cameraInShadow\n }\n}\n\nexport { ShadowPass }\n","import { Texture, generateMips, numMipLevels } from \"../Texture.js\"\nimport { mat4, vec3 } from \"../math.js\"\n\n/**\n * ProbeCapture - Captures environment reflections from a position\n *\n * Renders 6 cube faces and encodes to octahedral format.\n * Can export as HDR PNG for server storage.\n */\nclass ProbeCapture {\n constructor(engine) {\n this.engine = engine\n\n // Capture resolution per cube face\n this.faceSize = 1024\n\n // Output octahedral texture size\n this.octahedralSize = 4096\n\n // Cube face render targets (6 faces)\n this.faceTextures = []\n\n // Depth textures for each face\n this.faceDepthTextures = []\n\n // Output octahedral texture\n this.octahedralTexture = null\n\n // Mip levels for roughness\n this.mipLevels = 6\n\n // Previous environment map (used during capture to avoid recursion)\n this.fallbackEnvironment = null\n\n // Environment encoding: 0 = equirectangular, 1 = octahedral\n this.envEncoding = 0\n\n // Scene render callback (set by RenderGraph)\n this.sceneRenderCallback = null\n\n // Capture state\n this.isCapturing = false\n this.capturePosition = [0, 0, 0]\n\n // Cube face camera directions\n // +X, -X, +Y, -Y, +Z, -Z\n this.faceDirections = [\n { dir: [1, 0, 0], up: [0, 1, 0] }, // +X\n { dir: [-1, 0, 0], up: [0, 1, 0] }, // -X\n { dir: [0, 1, 0], up: [0, 0, -1] }, // +Y (up)\n { dir: [0, -1, 0], up: [0, 0, 1] }, // -Y (down)\n { dir: [0, 0, 1], up: [0, 1, 0] }, // +Z\n { dir: [0, 0, -1], up: [0, 1, 0] }, // -Z\n ]\n\n // Pipelines\n this.skyboxPipeline = null\n this.scenePipeline = null\n this.convertPipeline = null\n this.faceBindGroupLayout = null\n this.faceSampler = null\n\n // Scene rendering resources\n this.sceneBindGroupLayout = null\n this.sceneUniformBuffer = null\n }\n\n /**\n * Set scene render callback - called to render scene for each face\n * @param {Function} callback - (viewMatrix, projMatrix, colorTarget, depthTarget) => void\n */\n setSceneRenderCallback(callback) {\n this.sceneRenderCallback = callback\n }\n\n /**\n * Initialize capture resources\n */\n async initialize() {\n const { device } = this.engine\n\n // Create 6 face render targets with depth\n for (let i = 0; i < 6; i++) {\n // Color target (needs COPY_DST for scene render callback, COPY_SRC for debug save)\n const colorTexture = device.createTexture({\n label: `probeFace${i}`,\n size: [this.faceSize, this.faceSize],\n format: 'rgba16float',\n usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_DST | GPUTextureUsage.COPY_SRC\n })\n this.faceTextures.push({\n texture: colorTexture,\n view: colorTexture.createView()\n })\n\n // Depth target (depth32float to match GBuffer depth for copying)\n const depthTexture = device.createTexture({\n label: `probeFaceDepth${i}`,\n size: [this.faceSize, this.faceSize],\n format: 'depth32float',\n usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.COPY_DST\n })\n this.faceDepthTextures.push({\n texture: depthTexture,\n view: depthTexture.createView()\n })\n }\n\n // Create octahedral output texture\n const octTexture = device.createTexture({\n label: 'probeOctahedral',\n size: [this.octahedralSize, this.octahedralSize],\n format: 'rgba16float',\n usage: GPUTextureUsage.STORAGE_BINDING | GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_SRC\n })\n this.octahedralTexture = {\n texture: octTexture,\n view: octTexture.createView()\n }\n\n // Create sampler\n this.faceSampler = device.createSampler({\n magFilter: 'linear',\n minFilter: 'linear',\n })\n\n // Create skybox render pipeline (samples environment as background)\n await this._createSkyboxPipeline()\n\n // Create compute pipeline for cubemap → octahedral conversion\n await this._createConvertPipeline()\n }\n\n /**\n * Set fallback environment map (used during capture)\n */\n setFallbackEnvironment(envMap) {\n this.fallbackEnvironment = envMap\n }\n\n /**\n * Create pipeline to render skybox (environment map as background)\n */\n async _createSkyboxPipeline() {\n const { device } = this.engine\n\n const shaderCode = /* wgsl */`\n struct Uniforms {\n invViewProj: mat4x4f,\n envEncoding: f32, // 0 = equirectangular, 1 = octahedral\n padding: vec3f,\n }\n\n @group(0) @binding(0) var<uniform> uniforms: Uniforms;\n @group(0) @binding(1) var envTexture: texture_2d<f32>;\n @group(0) @binding(2) var envSampler: sampler;\n\n struct VertexOutput {\n @builtin(position) position: vec4f,\n @location(0) uv: vec2f,\n }\n\n @vertex\n fn vertexMain(@builtin(vertex_index) vertexIndex: u32) -> VertexOutput {\n var output: VertexOutput;\n let x = f32(vertexIndex & 1u) * 4.0 - 1.0;\n let y = f32(vertexIndex >> 1u) * 4.0 - 1.0;\n output.position = vec4f(x, y, 0.0, 1.0);\n output.uv = vec2f((x + 1.0) * 0.5, (1.0 - y) * 0.5);\n return output;\n }\n\n const PI: f32 = 3.14159265359;\n\n // Equirectangular UV mapping\n fn SphToUV(n: vec3f) -> vec2f {\n var uv: vec2f;\n uv.x = atan2(-n.x, n.z);\n uv.x = (uv.x + PI / 2.0) / (PI * 2.0) + PI * (28.670 / 360.0);\n uv.y = acos(n.y) / PI;\n return uv;\n }\n\n // Octahedral UV mapping\n fn octEncode(n: vec3f) -> vec2f {\n var p = n.xz / (abs(n.x) + abs(n.y) + abs(n.z));\n if (n.y < 0.0) {\n p = (1.0 - abs(p.yx)) * vec2f(\n select(-1.0, 1.0, p.x >= 0.0),\n select(-1.0, 1.0, p.y >= 0.0)\n );\n }\n return p * 0.5 + 0.5;\n }\n\n fn getEnvUV(dir: vec3f) -> vec2f {\n if (uniforms.envEncoding > 0.5) {\n return octEncode(dir);\n }\n return SphToUV(dir);\n }\n\n @fragment\n fn fragmentMain(input: VertexOutput) -> @location(0) vec4f {\n // Convert UV to clip space\n let clipPos = vec4f(input.uv * 2.0 - 1.0, 1.0, 1.0);\n\n // Transform to world direction\n let worldPos = uniforms.invViewProj * clipPos;\n var dir = normalize(worldPos.xyz / worldPos.w);\n\n // For octahedral, negate direction (same as main lighting shader)\n if (uniforms.envEncoding > 0.5) {\n dir = -dir;\n }\n\n // Sample environment map using correct UV mapping\n let uv = getEnvUV(dir);\n let envRGBE = textureSample(envTexture, envSampler, uv);\n var color = envRGBE.rgb * pow(2.0, envRGBE.a * 255.0 - 128.0);\n\n // No exposure or environment levels for probe capture\n // We capture raw HDR values - exposure is applied in main render only\n return vec4f(color, 1.0);\n }\n `\n\n const shaderModule = device.createShaderModule({\n label: 'probeSkybox',\n code: shaderCode\n })\n\n this.faceBindGroupLayout = device.createBindGroupLayout({\n entries: [\n { binding: 0, visibility: GPUShaderStage.VERTEX | GPUShaderStage.FRAGMENT, buffer: { type: 'uniform' } },\n { binding: 1, visibility: GPUShaderStage.FRAGMENT, texture: { sampleType: 'float' } },\n { binding: 2, visibility: GPUShaderStage.FRAGMENT, sampler: {} },\n ]\n })\n\n this.skyboxPipeline = device.createRenderPipeline({\n label: 'probeSkybox',\n layout: device.createPipelineLayout({ bindGroupLayouts: [this.faceBindGroupLayout] }),\n vertex: {\n module: shaderModule,\n entryPoint: 'vertexMain',\n },\n fragment: {\n module: shaderModule,\n entryPoint: 'fragmentMain',\n targets: [{ format: 'rgba16float' }]\n },\n primitive: { topology: 'triangle-list' },\n depthStencil: {\n format: 'depth32float',\n depthWriteEnabled: false, // Don't write depth\n depthCompare: 'greater-equal', // Only draw where depth is at far plane (1.0)\n }\n })\n }\n\n /**\n * Create compute pipeline for cubemap to octahedral conversion\n */\n async _createConvertPipeline() {\n const { device } = this.engine\n\n const shaderCode = /* wgsl */`\n @group(0) @binding(0) var outputTex: texture_storage_2d<rgba16float, write>;\n @group(0) @binding(1) var cubeFace0: texture_2d<f32>;\n @group(0) @binding(2) var cubeFace1: texture_2d<f32>;\n @group(0) @binding(3) var cubeFace2: texture_2d<f32>;\n @group(0) @binding(4) var cubeFace3: texture_2d<f32>;\n @group(0) @binding(5) var cubeFace4: texture_2d<f32>;\n @group(0) @binding(6) var cubeFace5: texture_2d<f32>;\n @group(0) @binding(7) var cubeSampler: sampler;\n\n // Octahedral decode: UV to direction\n // Maps 2D octahedral UV to 3D direction vector\n fn octDecode(uv: vec2f) -> vec3f {\n var uv2 = uv * 2.0 - 1.0;\n\n // Upper hemisphere mapping\n var n = vec3f(uv2.x, 1.0 - abs(uv2.x) - abs(uv2.y), uv2.y);\n\n // Lower hemisphere wrapping\n if (n.y < 0.0) {\n let signX = select(-1.0, 1.0, n.x >= 0.0);\n let signZ = select(-1.0, 1.0, n.z >= 0.0);\n n = vec3f(\n (1.0 - abs(n.z)) * signX,\n n.y,\n (1.0 - abs(n.x)) * signZ\n );\n }\n return normalize(n);\n }\n\n // Sample cubemap from direction\n // Standard cubemap UV mapping for faces rendered with lookAt\n // V coordinate is negated for Y because texture v=0 is top, world Y=+1 is up\n // Y faces use Z for vertical since they look along Y axis with Z as up vector\n fn sampleCube(dir: vec3f) -> vec4f {\n let absDir = abs(dir);\n var uv: vec2f;\n var faceColor: vec4f;\n\n if (absDir.x >= absDir.y && absDir.x >= absDir.z) {\n if (dir.x > 0.0) {\n // +X face: use face1\n uv = vec2f(-dir.z, -dir.y) / absDir.x * 0.5 + 0.5;\n faceColor = textureSampleLevel(cubeFace1, cubeSampler, uv, 0.0);\n } else {\n // -X face: use face0\n uv = vec2f(dir.z, -dir.y) / absDir.x * 0.5 + 0.5;\n faceColor = textureSampleLevel(cubeFace0, cubeSampler, uv, 0.0);\n }\n } else if (absDir.y >= absDir.x && absDir.y >= absDir.z) {\n if (dir.y > 0.0) {\n // +Y face: looking at +Y (up), up vector = -Z, right = +X\n // Screen top (-Z) should map to v=0, so use +z\n uv = vec2f(dir.x, dir.z) / absDir.y * 0.5 + 0.5;\n faceColor = textureSampleLevel(cubeFace2, cubeSampler, uv, 0.0);\n } else {\n // -Y face: looking at -Y (down), up vector = +Z, right = +X\n // Screen top (+Z) should map to v=0, so use -z\n uv = vec2f(dir.x, -dir.z) / absDir.y * 0.5 + 0.5;\n faceColor = textureSampleLevel(cubeFace3, cubeSampler, uv, 0.0);\n }\n } else {\n if (dir.z > 0.0) {\n // +Z face: looking at +Z, up = +Y, right = +X\n uv = vec2f(dir.x, -dir.y) / absDir.z * 0.5 + 0.5;\n faceColor = textureSampleLevel(cubeFace4, cubeSampler, uv, 0.0);\n } else {\n // -Z face: looking at -Z, up = +Y, right = -X\n uv = vec2f(-dir.x, -dir.y) / absDir.z * 0.5 + 0.5;\n faceColor = textureSampleLevel(cubeFace5, cubeSampler, uv, 0.0);\n }\n }\n\n return faceColor;\n }\n\n @compute @workgroup_size(8, 8)\n fn main(@builtin(global_invocation_id) gid: vec3u) {\n let size = textureDimensions(outputTex);\n if (gid.x >= size.x || gid.y >= size.y) {\n return;\n }\n\n let uv = (vec2f(gid.xy) + 0.5) / vec2f(size);\n let dir = octDecode(uv);\n let color = sampleCube(dir);\n textureStore(outputTex, gid.xy, color);\n }\n `\n\n const shaderModule = device.createShaderModule({\n label: 'probeConvert',\n code: shaderCode\n })\n\n this.convertPipeline = device.createComputePipeline({\n label: 'probeConvert',\n layout: 'auto',\n compute: {\n module: shaderModule,\n entryPoint: 'main'\n }\n })\n\n // Create equirectangular to octahedral conversion pipeline\n await this._createEquirectToOctPipeline()\n }\n\n /**\n * Create compute pipeline for equirectangular to octahedral conversion\n */\n async _createEquirectToOctPipeline() {\n const { device } = this.engine\n\n const shaderCode = /* wgsl */`\n @group(0) @binding(0) var outputTex: texture_storage_2d<rgba16float, write>;\n @group(0) @binding(1) var envTexture: texture_2d<f32>;\n @group(0) @binding(2) var envSampler: sampler;\n\n const PI: f32 = 3.14159265359;\n\n // Octahedral decode: UV to direction\n fn octDecode(uv: vec2f) -> vec3f {\n var uv2 = uv * 2.0 - 1.0;\n var n = vec3f(uv2.x, 1.0 - abs(uv2.x) - abs(uv2.y), uv2.y);\n if (n.y < 0.0) {\n let signX = select(-1.0, 1.0, n.x >= 0.0);\n let signZ = select(-1.0, 1.0, n.z >= 0.0);\n n = vec3f(\n (1.0 - abs(n.z)) * signX,\n n.y,\n (1.0 - abs(n.x)) * signZ\n );\n }\n return normalize(n);\n }\n\n // Equirectangular UV from direction (matching lighting.wgsl SphToUV)\n fn SphToUV(n: vec3f) -> vec2f {\n var uv: vec2f;\n uv.x = atan2(-n.x, n.z);\n uv.x = (uv.x + PI / 2.0) / (PI * 2.0) + PI * (28.670 / 360.0);\n uv.y = acos(n.y) / PI;\n return uv;\n }\n\n @compute @workgroup_size(8, 8)\n fn main(@builtin(global_invocation_id) gid: vec3u) {\n let size = textureDimensions(outputTex);\n if (gid.x >= size.x || gid.y >= size.y) {\n return;\n }\n\n let uv = (vec2f(gid.xy) + 0.5) / vec2f(size);\n let dir = octDecode(uv);\n\n // Sample equirectangular environment\n let envUV = SphToUV(dir);\n let envRGBE = textureSampleLevel(envTexture, envSampler, envUV, 0.0);\n\n // Decode RGBE to linear HDR\n let color = envRGBE.rgb * pow(2.0, envRGBE.a * 255.0 - 128.0);\n\n textureStore(outputTex, gid.xy, vec4f(color, 1.0));\n }\n `;\n\n const shaderModule = device.createShaderModule({\n label: 'equirectToOct',\n code: shaderCode\n })\n\n this.equirectToOctPipeline = device.createComputePipeline({\n label: 'equirectToOct',\n layout: 'auto',\n compute: {\n module: shaderModule,\n entryPoint: 'main'\n }\n })\n }\n\n /**\n * Convert equirectangular environment map to octahedral format\n * @param {Texture} envMap - Equirectangular RGBE environment map\n */\n async convertEquirectToOctahedral(envMap) {\n const { device } = this.engine\n\n if (!this.equirectToOctPipeline) {\n await this._createEquirectToOctPipeline()\n }\n\n // Reset RGBE texture so it gets regenerated\n if (this.octahedralRGBE?.texture) {\n this.octahedralRGBE.texture.destroy()\n this.octahedralRGBE = null\n }\n\n const bindGroup = device.createBindGroup({\n layout: this.equirectToOctPipeline.getBindGroupLayout(0),\n entries: [\n { binding: 0, resource: this.octahedralTexture.view },\n { binding: 1, resource: envMap.view },\n { binding: 2, resource: this.faceSampler },\n ]\n })\n\n const commandEncoder = device.createCommandEncoder()\n const passEncoder = commandEncoder.beginComputePass()\n\n passEncoder.setPipeline(this.equirectToOctPipeline)\n passEncoder.setBindGroup(0, bindGroup)\n\n const workgroupsX = Math.ceil(this.octahedralSize / 8)\n const workgroupsY = Math.ceil(this.octahedralSize / 8)\n passEncoder.dispatchWorkgroups(workgroupsX, workgroupsY)\n\n passEncoder.end()\n device.queue.submit([commandEncoder.finish()])\n\n // Wait for GPU to finish\n await device.queue.onSubmittedWorkDone()\n\n console.log('ProbeCapture: Converted equirectangular to octahedral')\n }\n\n /**\n * Create view/projection matrix for a cube face\n */\n _createFaceMatrix(faceIndex, position) {\n const face = this.faceDirections[faceIndex]\n\n const view = mat4.create()\n const target = [\n position[0] + face.dir[0],\n position[1] + face.dir[1],\n position[2] + face.dir[2]\n ]\n mat4.lookAt(view, position, target, face.up)\n\n const proj = mat4.create()\n mat4.perspective(proj, Math.PI / 2, 1.0, 0.1, 1000.0)\n\n const viewProj = mat4.create()\n mat4.multiply(viewProj, proj, view)\n\n const invViewProj = mat4.create()\n mat4.invert(invViewProj, viewProj)\n\n return invViewProj\n }\n\n /**\n * Create view and projection matrices for a cube face\n * Returns both matrices separately (for scene rendering)\n */\n _createFaceMatrices(faceIndex, position) {\n const face = this.faceDirections[faceIndex]\n\n const view = mat4.create()\n const target = [\n position[0] + face.dir[0],\n position[1] + face.dir[1],\n position[2] + face.dir[2]\n ]\n mat4.lookAt(view, position, target, face.up)\n\n const proj = mat4.create()\n mat4.perspective(proj, Math.PI / 2, 1.0, 0.1, 10000.0) // Far plane 10000 for distant objects\n\n return { view, proj }\n }\n\n /**\n * Capture probe from a position\n * Renders scene geometry (if callback set) then skybox as background\n * @param {vec3} position - World position to capture from\n * @returns {Promise<void>}\n */\n async capture(position) {\n if (this.isCapturing) {\n console.warn('ProbeCapture: Already capturing')\n return\n }\n\n if (!this.fallbackEnvironment) {\n console.error('ProbeCapture: No environment map set')\n return\n }\n\n this.isCapturing = true\n this.capturePosition = [...position]\n\n // Reset RGBE texture so it gets regenerated after new capture\n if (this.octahedralRGBE?.texture) {\n this.octahedralRGBE.texture.destroy()\n this.octahedralRGBE = null\n }\n\n const { device } = this.engine\n\n // Wait for any previous GPU work to complete before starting capture\n // This ensures all previous frame's buffer writes are complete\n await device.queue.onSubmittedWorkDone()\n\n // Capture started - position logged by caller\n\n try {\n // Create uniform buffer for skybox (mat4x4 + envEncoding + padding)\n const uniformBuffer = device.createBuffer({\n size: 80, // mat4x4 (64) + vec4 (16) for envEncoding + padding\n usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST\n })\n\n // Render each cube face\n for (let i = 0; i < 6; i++) {\n const { view, proj } = this._createFaceMatrices(i, position)\n const invViewProj = this._createFaceMatrix(i, position)\n\n if (this.sceneRenderCallback) {\n // Render scene using RenderGraph's deferred pipeline\n // LightingPass handles BOTH scene geometry AND background (environment)\n // No separate skybox render needed - LightingPass does it all\n await this.sceneRenderCallback(\n view,\n proj,\n this.faceTextures[i],\n this.faceDepthTextures[i],\n i,\n position\n )\n } else {\n // Fallback: No scene callback - render skybox only\n const commandEncoder = device.createCommandEncoder()\n const passEncoder = commandEncoder.beginRenderPass({\n colorAttachments: [{\n view: this.faceTextures[i].view,\n clearValue: { r: 0, g: 0, b: 0, a: 0 },\n loadOp: 'clear',\n storeOp: 'store'\n }],\n depthStencilAttachment: {\n view: this.faceDepthTextures[i].view,\n depthClearValue: 1.0,\n depthLoadOp: 'clear',\n depthStoreOp: 'store'\n }\n })\n passEncoder.end()\n device.queue.submit([commandEncoder.finish()])\n\n // Render skybox for fallback path only\n const uniformData = new Float32Array(20)\n uniformData.set(invViewProj, 0)\n uniformData[16] = this.envEncoding // 0 = equirect, 1 = octahedral\n uniformData[17] = 0 // padding\n uniformData[18] = 0 // padding\n uniformData[19] = 0 // padding\n device.queue.writeBuffer(uniformBuffer, 0, uniformData)\n\n const skyboxBindGroup = device.createBindGroup({\n layout: this.faceBindGroupLayout,\n entries: [\n { binding: 0, resource: { buffer: uniformBuffer } },\n { binding: 1, resource: this.fallbackEnvironment.view },\n { binding: 2, resource: this.faceSampler },\n ]\n })\n\n const skyboxEncoder = device.createCommandEncoder()\n const skyboxPass = skyboxEncoder.beginRenderPass({\n colorAttachments: [{\n view: this.faceTextures[i].view,\n loadOp: 'load',\n storeOp: 'store'\n }],\n depthStencilAttachment: {\n view: this.faceDepthTextures[i].view,\n depthLoadOp: 'load',\n depthStoreOp: 'store'\n }\n })\n\n skyboxPass.setPipeline(this.skyboxPipeline)\n skyboxPass.setBindGroup(0, skyboxBindGroup)\n skyboxPass.draw(3)\n skyboxPass.end()\n\n device.queue.submit([skyboxEncoder.finish()])\n }\n }\n\n uniformBuffer.destroy()\n\n // Convert cubemap to octahedral\n await this._convertToOctahedral()\n\n // Capture complete\n\n } finally {\n this.isCapturing = false\n }\n }\n\n /**\n * Convert 6 cube faces to octahedral encoding\n */\n async _convertToOctahedral() {\n const { device } = this.engine\n\n const bindGroup = device.createBindGroup({\n layout: this.convertPipeline.getBindGroupLayout(0),\n entries: [\n { binding: 0, resource: this.octahedralTexture.view },\n { binding: 1, resource: this.faceTextures[0].view },\n { binding: 2, resource: this.faceTextures[1].view },\n { binding: 3, resource: this.faceTextures[2].view },\n { binding: 4, resource: this.faceTextures[3].view },\n { binding: 5, resource: this.faceTextures[4].view },\n { binding: 6, resource: this.faceTextures[5].view },\n { binding: 7, resource: this.faceSampler },\n ]\n })\n\n const commandEncoder = device.createCommandEncoder()\n const passEncoder = commandEncoder.beginComputePass()\n\n passEncoder.setPipeline(this.convertPipeline)\n passEncoder.setBindGroup(0, bindGroup)\n\n const workgroupsX = Math.ceil(this.octahedralSize / 8)\n const workgroupsY = Math.ceil(this.octahedralSize / 8)\n passEncoder.dispatchWorkgroups(workgroupsX, workgroupsY)\n\n passEncoder.end()\n device.queue.submit([commandEncoder.finish()])\n }\n\n /**\n * Export probe as DEBUG PNG (regular LDR, tone mapped) for visual inspection\n * @param {string} filename - Download filename\n * @param {number} exposure - Exposure value for tone mapping (uses engine setting if not provided)\n */\n async saveAsDebugPNG(filename = 'probe_debug.png', exposure = null) {\n // Use engine's exposure setting if not explicitly provided\n if (exposure === null) {\n exposure = this.engine?.settings?.environment?.exposure ?? 1.6\n }\n const { device } = this.engine\n\n // Read back texture data\n const bytesPerPixel = 8 // rgba16float = 4 * 2 bytes\n const bytesPerRow = Math.ceil(this.octahedralSize * bytesPerPixel / 256) * 256\n const bufferSize = bytesPerRow * this.octahedralSize\n\n const readBuffer = device.createBuffer({\n size: bufferSize,\n usage: GPUBufferUsage.COPY_DST | GPUBufferUsage.MAP_READ\n })\n\n const commandEncoder = device.createCommandEncoder()\n commandEncoder.copyTextureToBuffer(\n { texture: this.octahedralTexture.texture },\n { buffer: readBuffer, bytesPerRow: bytesPerRow },\n { width: this.octahedralSize, height: this.octahedralSize }\n )\n device.queue.submit([commandEncoder.finish()])\n\n await readBuffer.mapAsync(GPUMapMode.READ)\n const data = new Uint16Array(readBuffer.getMappedRange())\n\n // Convert float16 to regular 8-bit RGBA with ACES tone mapping (matches PostProcess)\n const pixelsPerRow = bytesPerRow / 8 // 8 bytes per pixel (rgba16float)\n const rgbaData = new Uint8ClampedArray(this.octahedralSize * this.octahedralSize * 4)\n\n for (let y = 0; y < this.octahedralSize; y++) {\n for (let x = 0; x < this.octahedralSize; x++) {\n const srcIdx = (y * pixelsPerRow + x) * 4\n const dstIdx = (y * this.octahedralSize + x) * 4\n\n // Decode float16 values\n const r = this._float16ToFloat32(data[srcIdx])\n const g = this._float16ToFloat32(data[srcIdx + 1])\n const b = this._float16ToFloat32(data[srcIdx + 2])\n\n // ACES tone mapping (matches postproc.wgsl)\n const [tr, tg, tb] = this._acesToneMap(r, g, b)\n\n // ACES already outputs in sRGB, just clamp and convert to 8-bit\n rgbaData[dstIdx] = Math.min(255, Math.max(0, tr * 255))\n rgbaData[dstIdx + 1] = Math.min(255, Math.max(0, tg * 255))\n rgbaData[dstIdx + 2] = Math.min(255, Math.max(0, tb * 255))\n rgbaData[dstIdx + 3] = 255\n }\n }\n\n readBuffer.unmap()\n readBuffer.destroy()\n\n // Create canvas and draw image data\n const canvas = document.createElement('canvas')\n canvas.width = this.octahedralSize\n canvas.height = this.octahedralSize\n const ctx = canvas.getContext('2d')\n const imageData = new ImageData(rgbaData, this.octahedralSize, this.octahedralSize)\n ctx.putImageData(imageData, 0, 0)\n\n // Trigger download\n const link = document.createElement('a')\n link.download = filename\n link.href = canvas.toDataURL('image/png')\n link.click()\n\n console.log(`ProbeCapture: Saved debug PNG as ${filename}`)\n }\n\n /**\n * Save individual cube faces for debugging\n * @param {string} prefix - Filename prefix\n * @param {number} exposure - Exposure value for tone mapping (uses engine setting if not provided)\n */\n async saveCubeFaces(prefix = 'face', exposure = null) {\n // Use engine's exposure setting if not explicitly provided\n if (exposure === null) {\n exposure = this.engine?.settings?.environment?.exposure ?? 1.6\n }\n const { device } = this.engine\n const faceNames = ['+X', '-X', '+Y', '-Y', '+Z', '-Z']\n\n for (let i = 0; i < 6; i++) {\n const bytesPerPixel = 8\n const bytesPerRow = Math.ceil(this.faceSize * bytesPerPixel / 256) * 256\n const bufferSize = bytesPerRow * this.faceSize\n\n const readBuffer = device.createBuffer({\n size: bufferSize,\n usage: GPUBufferUsage.COPY_DST | GPUBufferUsage.MAP_READ\n })\n\n const commandEncoder = device.createCommandEncoder()\n commandEncoder.copyTextureToBuffer(\n { texture: this.faceTextures[i].texture },\n { buffer: readBuffer, bytesPerRow },\n { width: this.faceSize, height: this.faceSize }\n )\n device.queue.submit([commandEncoder.finish()])\n\n await readBuffer.mapAsync(GPUMapMode.READ)\n const data = new Uint16Array(readBuffer.getMappedRange())\n\n const pixelsPerRow = bytesPerRow / 8\n const rgbaData = new Uint8ClampedArray(this.faceSize * this.faceSize * 4)\n\n for (let y = 0; y < this.faceSize; y++) {\n for (let x = 0; x < this.faceSize; x++) {\n const srcIdx = (y * pixelsPerRow + x) * 4\n const dstIdx = (y * this.faceSize + x) * 4\n\n const r = this._float16ToFloat32(data[srcIdx])\n const g = this._float16ToFloat32(data[srcIdx + 1])\n const b = this._float16ToFloat32(data[srcIdx + 2])\n\n // ACES tone mapping (matches postproc.wgsl)\n const [tr, tg, tb] = this._acesToneMap(r, g, b)\n\n rgbaData[dstIdx] = Math.min(255, Math.max(0, tr * 255))\n rgbaData[dstIdx + 1] = Math.min(255, Math.max(0, tg * 255))\n rgbaData[dstIdx + 2] = Math.min(255, Math.max(0, tb * 255))\n rgbaData[dstIdx + 3] = 255\n }\n }\n\n readBuffer.unmap()\n readBuffer.destroy()\n\n const canvas = document.createElement('canvas')\n canvas.width = this.faceSize\n canvas.height = this.faceSize\n const ctx = canvas.getContext('2d')\n const imageData = new ImageData(rgbaData, this.faceSize, this.faceSize)\n ctx.putImageData(imageData, 0, 0)\n\n const link = document.createElement('a')\n link.download = `${prefix}_${i}_${faceNames[i].replace(/[+-]/g, m => m === '+' ? 'pos' : 'neg')}.png`\n link.href = canvas.toDataURL('image/png')\n link.click()\n\n // Small delay between downloads\n await new Promise(r => setTimeout(r, 100))\n }\n\n console.log('ProbeCapture: Saved all 6 cube faces')\n }\n\n /**\n * Convert float16 to float32\n */\n _float16ToFloat32(h) {\n const s = (h & 0x8000) >> 15\n const e = (h & 0x7C00) >> 10\n const f = h & 0x03FF\n\n if (e === 0) {\n return (s ? -1 : 1) * Math.pow(2, -14) * (f / 1024)\n } else if (e === 0x1F) {\n return f ? NaN : ((s ? -1 : 1) * Infinity)\n }\n\n return (s ? -1 : 1) * Math.pow(2, e - 15) * (1 + f / 1024)\n }\n\n /**\n * ACES tone mapping (matches postproc.wgsl)\n * @param {number} r - Red HDR value\n * @param {number} g - Green HDR value\n * @param {number} b - Blue HDR value\n * @returns {number[]} Tone-mapped [r, g, b] in 0-1 range\n */\n _acesToneMap(r, g, b) {\n // Input transform matrix (RRT_SAT)\n const m1 = [\n [0.59719, 0.35458, 0.04823],\n [0.07600, 0.90834, 0.01566],\n [0.02840, 0.13383, 0.83777]\n ]\n // Output transform matrix (ODT_SAT)\n const m2 = [\n [ 1.60475, -0.53108, -0.07367],\n [-0.10208, 1.10813, -0.00605],\n [-0.00327, -0.07276, 1.07602]\n ]\n\n // Apply input transform\n const v0 = m1[0][0] * r + m1[0][1] * g + m1[0][2] * b\n const v1 = m1[1][0] * r + m1[1][1] * g + m1[1][2] * b\n const v2 = m1[2][0] * r + m1[2][1] * g + m1[2][2] * b\n\n // RRT and ODT fit\n const a0 = v0 * (v0 + 0.0245786) - 0.000090537\n const b0 = v0 * (0.983729 * v0 + 0.4329510) + 0.238081\n const a1 = v1 * (v1 + 0.0245786) - 0.000090537\n const b1 = v1 * (0.983729 * v1 + 0.4329510) + 0.238081\n const a2 = v2 * (v2 + 0.0245786) - 0.000090537\n const b2 = v2 * (0.983729 * v2 + 0.4329510) + 0.238081\n\n const c0 = a0 / b0\n const c1 = a1 / b1\n const c2 = a2 / b2\n\n // Apply output transform\n const out0 = m2[0][0] * c0 + m2[0][1] * c1 + m2[0][2] * c2\n const out1 = m2[1][0] * c0 + m2[1][1] * c1 + m2[1][2] * c2\n const out2 = m2[2][0] * c0 + m2[2][1] * c1 + m2[2][2] * c2\n\n // Clamp to 0-1\n return [\n Math.max(0, Math.min(1, out0)),\n Math.max(0, Math.min(1, out1)),\n Math.max(0, Math.min(1, out2))\n ]\n }\n\n /**\n * Export probe as two JPG files: RGB + Multiplier (RGBM format)\n * RGB stores actual color (with sRGB gamma) - values <= 1.0 stored directly\n * Multiplier only encodes values > 1.0: black = 1.0, white = 32768, logarithmic\n * This means most pixels have multiplier = 1.0 (black), compressing very well\n * Blue noise dithering is applied to reduce banding\n * @param {string} basename - Base filename (without extension), e.g. 'probe_01'\n * @param {number} quality - JPG quality 0-1 (default 0.95 = 95%)\n */\n async saveAsJPG(basename = 'probe_01', quality = 0.95) {\n const { device } = this.engine\n\n // Load blue noise texture for dithering\n let blueNoiseData = null\n let blueNoiseSize = 1024\n try {\n const response = await fetch('/bluenoise1024.png')\n const blob = await response.blob()\n const bitmap = await createImageBitmap(blob)\n blueNoiseSize = bitmap.width\n const noiseCanvas = document.createElement('canvas')\n noiseCanvas.width = blueNoiseSize\n noiseCanvas.height = blueNoiseSize\n const noiseCtx = noiseCanvas.getContext('2d')\n noiseCtx.drawImage(bitmap, 0, 0)\n blueNoiseData = noiseCtx.getImageData(0, 0, blueNoiseSize, blueNoiseSize).data\n } catch (e) {\n console.warn('ProbeCapture: Could not load blue noise, using white noise fallback')\n }\n\n // Read back texture data\n const bytesPerPixel = 8 // rgba16float = 4 * 2 bytes\n const bytesPerRow = Math.ceil(this.octahedralSize * bytesPerPixel / 256) * 256\n const bufferSize = bytesPerRow * this.octahedralSize\n\n const readBuffer = device.createBuffer({\n size: bufferSize,\n usage: GPUBufferUsage.COPY_DST | GPUBufferUsage.MAP_READ\n })\n\n const commandEncoder = device.createCommandEncoder()\n commandEncoder.copyTextureToBuffer(\n { texture: this.octahedralTexture.texture },\n { buffer: readBuffer, bytesPerRow: bytesPerRow },\n { width: this.octahedralSize, height: this.octahedralSize }\n )\n device.queue.submit([commandEncoder.finish()])\n\n await readBuffer.mapAsync(GPUMapMode.READ)\n const float16Data = new Uint16Array(readBuffer.getMappedRange())\n\n // RGBM encoding parameters\n // Multiplier range: 1.0 (black) to 32768 (white), logarithmic\n // Most pixels will have multiplier = 1.0, so intensity image is mostly black\n const MULT_MAX = 32768 // 2^15\n const LOG_MULT_MAX = 15 // log2(32768)\n const SRGB_GAMMA = 2.2\n\n const size = this.octahedralSize\n const rgbData = new Uint8ClampedArray(size * size * 4) // RGBA for canvas\n const multData = new Uint8ClampedArray(size * size * 4) // RGBA grayscale for canvas\n const stride = bytesPerRow / 2 // stride in uint16 elements\n\n for (let y = 0; y < size; y++) {\n for (let x = 0; x < size; x++) {\n const srcIdx = y * stride + x * 4\n const dstIdx = (y * size + x) * 4\n\n // Decode float16 to float32\n let r = this._float16ToFloat32(float16Data[srcIdx])\n let g = this._float16ToFloat32(float16Data[srcIdx + 1])\n let b = this._float16ToFloat32(float16Data[srcIdx + 2])\n\n // Find max component\n const maxVal = Math.max(r, g, b, 1e-10)\n\n let multiplier = 1.0\n if (maxVal > 1.0) {\n // HDR range: scale down so max = 1.0\n multiplier = Math.min(maxVal, MULT_MAX)\n const scale = 1.0 / multiplier\n r *= scale\n g *= scale\n b *= scale\n }\n\n // Apply sRGB gamma and store RGB\n rgbData[dstIdx] = Math.round(Math.pow(Math.min(1, r), 1 / SRGB_GAMMA) * 255)\n rgbData[dstIdx + 1] = Math.round(Math.pow(Math.min(1, g), 1 / SRGB_GAMMA) * 255)\n rgbData[dstIdx + 2] = Math.round(Math.pow(Math.min(1, b), 1 / SRGB_GAMMA) * 255)\n rgbData[dstIdx + 3] = 255\n\n // Encode multiplier: 1.0 = black (0), 32768 = white (255), logarithmic\n // log2(1) = 0 → 0, log2(32768) = 15 → 255\n const logMult = Math.log2(Math.max(1.0, multiplier)) // 0 to 15\n const multNorm = logMult / LOG_MULT_MAX // 0 to 1\n\n // Add blue noise dithering (-0.5 to +0.5) before quantization\n let dither = 0\n if (blueNoiseData) {\n const noiseX = x % blueNoiseSize\n const noiseY = y % blueNoiseSize\n const noiseIdx = (noiseY * blueNoiseSize + noiseX) * 4\n dither = (blueNoiseData[noiseIdx] / 255) - 0.5\n } else {\n dither = Math.random() - 0.5\n }\n\n const multByte = Math.min(255, Math.max(0, Math.round(multNorm * 255 + dither)))\n\n multData[dstIdx] = multByte\n multData[dstIdx + 1] = multByte\n multData[dstIdx + 2] = multByte\n multData[dstIdx + 3] = 255\n }\n }\n\n readBuffer.unmap()\n readBuffer.destroy()\n\n // Create RGB canvas and save as JPG\n const rgbCanvas = document.createElement('canvas')\n rgbCanvas.width = size\n rgbCanvas.height = size\n const rgbCtx = rgbCanvas.getContext('2d')\n const rgbImageData = new ImageData(rgbData, size, size)\n rgbCtx.putImageData(rgbImageData, 0, 0)\n\n // Create multiplier canvas and save as JPG\n const multCanvas = document.createElement('canvas')\n multCanvas.width = size\n multCanvas.height = size\n const multCtx = multCanvas.getContext('2d')\n const multImageData = new ImageData(multData, size, size)\n multCtx.putImageData(multImageData, 0, 0)\n\n // Download RGB JPG\n const rgbLink = document.createElement('a')\n rgbLink.download = `${basename}.jpg`\n rgbLink.href = rgbCanvas.toDataURL('image/jpeg', quality)\n rgbLink.click()\n\n // Small delay between downloads\n await new Promise(r => setTimeout(r, 100))\n\n // Download multiplier JPG\n const multLink = document.createElement('a')\n multLink.download = `${basename}.mult.jpg`\n multLink.href = multCanvas.toDataURL('image/jpeg', quality)\n multLink.click()\n\n console.log(`ProbeCapture: Saved RGBM pair as ${basename}.jpg + ${basename}.mult.jpg (${size}x${size})`)\n }\n\n /**\n * Export probe as Radiance HDR file and trigger download\n * @param {string} filename - Download filename\n */\n async saveAsHDR(filename = 'probe.hdr') {\n const { device } = this.engine\n\n // Read back texture data\n const bytesPerPixel = 8 // rgba16float = 4 * 2 bytes\n const bytesPerRow = Math.ceil(this.octahedralSize * bytesPerPixel / 256) * 256\n const bufferSize = bytesPerRow * this.octahedralSize\n\n const readBuffer = device.createBuffer({\n size: bufferSize,\n usage: GPUBufferUsage.COPY_DST | GPUBufferUsage.MAP_READ\n })\n\n const commandEncoder = device.createCommandEncoder()\n commandEncoder.copyTextureToBuffer(\n { texture: this.octahedralTexture.texture },\n { buffer: readBuffer, bytesPerRow: bytesPerRow },\n { width: this.octahedralSize, height: this.octahedralSize }\n )\n device.queue.submit([commandEncoder.finish()])\n\n await readBuffer.mapAsync(GPUMapMode.READ)\n const float16Data = new Uint16Array(readBuffer.getMappedRange())\n\n // Convert to RGBE format\n const rgbeData = this._float16ToRGBE(float16Data, bytesPerRow / 2)\n\n readBuffer.unmap()\n readBuffer.destroy()\n\n // Build Radiance HDR file\n const hdrData = this._buildHDRFile(rgbeData, this.octahedralSize, this.octahedralSize)\n\n // Download\n const blob = new Blob([hdrData], { type: 'application/octet-stream' })\n const link = document.createElement('a')\n link.download = filename\n link.href = URL.createObjectURL(blob)\n link.click()\n URL.revokeObjectURL(link.href)\n\n console.log(`ProbeCapture: Saved HDR as ${filename}`)\n }\n\n /**\n * Build Radiance HDR file from RGBE data\n * Uses simple RLE compression per scanline\n */\n _buildHDRFile(rgbeData, width, height) {\n // Header\n const header = `#?RADIANCE\\nFORMAT=32-bit_rle_rgbe\\n\\n-Y ${height} +X ${width}\\n`\n const headerBytes = new TextEncoder().encode(header)\n\n // Encode scanlines with RLE\n const scanlines = []\n for (let y = 0; y < height; y++) {\n const scanline = this._encodeRLEScanline(rgbeData, y, width)\n scanlines.push(scanline)\n }\n\n // Calculate total size\n const dataSize = scanlines.reduce((sum, s) => sum + s.length, 0)\n const totalSize = headerBytes.length + dataSize\n\n // Build final buffer\n const result = new Uint8Array(totalSize)\n result.set(headerBytes, 0)\n\n let offset = headerBytes.length\n for (const scanline of scanlines) {\n result.set(scanline, offset)\n offset += scanline.length\n }\n\n return result\n }\n\n /**\n * Encode a single scanline with RLE compression\n */\n _encodeRLEScanline(rgbeData, y, width) {\n // New RLE format for width >= 8 and <= 32767\n if (width < 8 || width > 32767) {\n // Fallback to uncompressed\n const scanline = new Uint8Array(width * 4)\n for (let x = 0; x < width; x++) {\n const srcIdx = (y * width + x) * 4\n scanline[x * 4 + 0] = rgbeData[srcIdx + 0]\n scanline[x * 4 + 1] = rgbeData[srcIdx + 1]\n scanline[x * 4 + 2] = rgbeData[srcIdx + 2]\n scanline[x * 4 + 3] = rgbeData[srcIdx + 3]\n }\n return scanline\n }\n\n // New RLE format: 4 bytes header + RLE encoded channels\n const header = new Uint8Array([2, 2, (width >> 8) & 0xFF, width & 0xFF])\n\n // Separate channels\n const channels = [[], [], [], []]\n for (let x = 0; x < width; x++) {\n const srcIdx = (y * width + x) * 4\n channels[0].push(rgbeData[srcIdx + 0])\n channels[1].push(rgbeData[srcIdx + 1])\n channels[2].push(rgbeData[srcIdx + 2])\n channels[3].push(rgbeData[srcIdx + 3])\n }\n\n // RLE encode each channel\n const encodedChannels = channels.map(ch => this._rleEncodeChannel(ch))\n\n // Combine\n const totalLen = header.length + encodedChannels.reduce((s, c) => s + c.length, 0)\n const result = new Uint8Array(totalLen)\n result.set(header, 0)\n\n let offset = header.length\n for (const encoded of encodedChannels) {\n result.set(encoded, offset)\n offset += encoded.length\n }\n\n return result\n }\n\n /**\n * RLE encode a single channel\n */\n _rleEncodeChannel(data) {\n const result = []\n let i = 0\n\n while (i < data.length) {\n // Check for run\n let runLen = 1\n while (i + runLen < data.length && runLen < 127 && data[i + runLen] === data[i]) {\n runLen++\n }\n\n if (runLen > 2) {\n // Encode run\n result.push(128 + runLen)\n result.push(data[i])\n i += runLen\n } else {\n // Encode non-run (literal)\n let litLen = 1\n while (i + litLen < data.length && litLen < 128) {\n // Check if next would start a run\n if (i + litLen + 2 < data.length &&\n data[i + litLen] === data[i + litLen + 1] &&\n data[i + litLen] === data[i + litLen + 2]) {\n break\n }\n litLen++\n }\n result.push(litLen)\n for (let j = 0; j < litLen; j++) {\n result.push(data[i + j])\n }\n i += litLen\n }\n }\n\n return new Uint8Array(result)\n }\n\n /**\n * Export probe as RGBE PNG (for debugging/preview)\n * @param {string} filename - Download filename\n */\n async saveAsPNG(filename = 'probe.png') {\n const { device } = this.engine\n\n // Read back texture data\n const bytesPerPixel = 8 // rgba16float = 4 * 2 bytes\n const bytesPerRow = Math.ceil(this.octahedralSize * bytesPerPixel / 256) * 256\n const bufferSize = bytesPerRow * this.octahedralSize\n\n const readBuffer = device.createBuffer({\n size: bufferSize,\n usage: GPUBufferUsage.COPY_DST | GPUBufferUsage.MAP_READ\n })\n\n const commandEncoder = device.createCommandEncoder()\n commandEncoder.copyTextureToBuffer(\n { texture: this.octahedralTexture.texture },\n { buffer: readBuffer, bytesPerRow: bytesPerRow },\n { width: this.octahedralSize, height: this.octahedralSize }\n )\n device.queue.submit([commandEncoder.finish()])\n\n await readBuffer.mapAsync(GPUMapMode.READ)\n const data = new Uint16Array(readBuffer.getMappedRange())\n\n // Convert float16 to RGBE format for HDR storage\n const rgbeData = this._float16ToRGBE(data, bytesPerRow / 2)\n\n readBuffer.unmap()\n readBuffer.destroy()\n\n // Create PNG with RGBE data\n const canvas = document.createElement('canvas')\n canvas.width = this.octahedralSize\n canvas.height = this.octahedralSize\n const ctx = canvas.getContext('2d')\n const imageData = ctx.createImageData(this.octahedralSize, this.octahedralSize)\n imageData.data.set(rgbeData)\n ctx.putImageData(imageData, 0, 0)\n\n // Download\n const link = document.createElement('a')\n link.download = filename\n link.href = canvas.toDataURL('image/png')\n link.click()\n\n console.log(`ProbeCapture: Saved as ${filename}`)\n }\n\n /**\n * Convert float16 RGBA to RGBE format\n */\n _float16ToRGBE(float16Data, stride) {\n const size = this.octahedralSize\n const rgbe = new Uint8ClampedArray(size * size * 4)\n\n for (let y = 0; y < size; y++) {\n for (let x = 0; x < size; x++) {\n const srcIdx = y * stride + x * 4\n const dstIdx = (y * size + x) * 4\n\n // Decode float16 to float32\n const r = this._float16ToFloat32(float16Data[srcIdx])\n const g = this._float16ToFloat32(float16Data[srcIdx + 1])\n const b = this._float16ToFloat32(float16Data[srcIdx + 2])\n\n // Find max component\n const maxVal = Math.max(r, g, b)\n\n if (maxVal < 1e-32) {\n rgbe[dstIdx] = 0\n rgbe[dstIdx + 1] = 0\n rgbe[dstIdx + 2] = 0\n rgbe[dstIdx + 3] = 0\n } else {\n // Compute exponent\n let exp = Math.ceil(Math.log2(maxVal))\n const scale = Math.pow(2, -exp) * 255\n\n rgbe[dstIdx] = Math.min(255, Math.max(0, r * scale))\n rgbe[dstIdx + 1] = Math.min(255, Math.max(0, g * scale))\n rgbe[dstIdx + 2] = Math.min(255, Math.max(0, b * scale))\n rgbe[dstIdx + 3] = exp + 128\n }\n }\n }\n\n return rgbe\n }\n\n /**\n * Convert float16 (as uint16) to float32\n */\n _float16ToFloat32(h) {\n const sign = (h & 0x8000) >> 15\n const exp = (h & 0x7C00) >> 10\n const frac = h & 0x03FF\n\n if (exp === 0) {\n if (frac === 0) return sign ? -0 : 0\n // Denormalized\n return (sign ? -1 : 1) * Math.pow(2, -14) * (frac / 1024)\n } else if (exp === 31) {\n return frac ? NaN : (sign ? -Infinity : Infinity)\n }\n\n return (sign ? -1 : 1) * Math.pow(2, exp - 15) * (1 + frac / 1024)\n }\n\n /**\n * Capture and save as PNG\n * @param {vec3} position - Capture position\n * @param {string} filename - Download filename\n */\n async captureAndSave(position, filename = 'probe.png') {\n await this.capture(position)\n await this.saveAsPNG(filename)\n }\n\n /**\n * Get the octahedral probe texture (raw float16)\n */\n getProbeTexture() {\n return this.octahedralTexture\n }\n\n /**\n * Convert float16 octahedral texture to RGBE-encoded rgba8unorm texture with mips\n * This makes it compatible with the standard environment map pipeline\n */\n async _createRGBETexture() {\n const { device } = this.engine\n\n // Read back float16 data\n const bytesPerPixel = 8 // rgba16float = 4 * 2 bytes\n const bytesPerRow = Math.ceil(this.octahedralSize * bytesPerPixel / 256) * 256\n const bufferSize = bytesPerRow * this.octahedralSize\n\n const readBuffer = device.createBuffer({\n size: bufferSize,\n usage: GPUBufferUsage.COPY_DST | GPUBufferUsage.MAP_READ\n })\n\n const commandEncoder = device.createCommandEncoder()\n commandEncoder.copyTextureToBuffer(\n { texture: this.octahedralTexture.texture },\n { buffer: readBuffer, bytesPerRow: bytesPerRow },\n { width: this.octahedralSize, height: this.octahedralSize }\n )\n device.queue.submit([commandEncoder.finish()])\n\n await readBuffer.mapAsync(GPUMapMode.READ)\n const float16Data = new Uint16Array(readBuffer.getMappedRange())\n\n // Convert to RGBE\n const rgbeData = this._float16ToRGBE(float16Data, bytesPerRow / 2)\n\n readBuffer.unmap()\n readBuffer.destroy()\n\n // Calculate mip levels\n const mipCount = numMipLevels(this.octahedralSize, this.octahedralSize)\n\n // Create RGBE texture with mips (rgba8unorm like loaded HDR files)\n const rgbeTexture = device.createTexture({\n label: 'probeOctahedralRGBE',\n size: [this.octahedralSize, this.octahedralSize],\n mipLevelCount: mipCount,\n format: 'rgba8unorm',\n usage: GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_DST | GPUTextureUsage.RENDER_ATTACHMENT\n })\n\n device.queue.writeTexture(\n { texture: rgbeTexture },\n rgbeData,\n { bytesPerRow: this.octahedralSize * 4 },\n { width: this.octahedralSize, height: this.octahedralSize }\n )\n\n // Generate mip levels with RGBE-aware filtering\n generateMips(device, rgbeTexture, true) // true = RGBE mode\n\n this.octahedralRGBE = {\n texture: rgbeTexture,\n view: rgbeTexture.createView(),\n mipCount: mipCount\n }\n }\n\n /**\n * Get texture in format compatible with lighting pass (RGBE encoded with mips)\n * Creates a wrapper with .view and .sampler properties\n */\n async getAsEnvironmentTexture() {\n if (!this.octahedralTexture) return null\n\n // Create RGBE texture with mips if not already done\n if (!this.octahedralRGBE) {\n await this._createRGBETexture()\n }\n\n return {\n texture: this.octahedralRGBE.texture,\n view: this.octahedralRGBE.view,\n sampler: this.faceSampler,\n width: this.octahedralSize,\n height: this.octahedralSize,\n mipCount: this.octahedralRGBE.mipCount,\n isHDR: true // Mark as HDR for proper handling\n }\n }\n\n /**\n * Destroy resources\n */\n destroy() {\n for (const tex of this.faceTextures) {\n if (tex?.texture) tex.texture.destroy()\n }\n for (const tex of this.faceDepthTextures) {\n if (tex?.texture) tex.texture.destroy()\n }\n if (this.octahedralTexture?.texture) {\n this.octahedralTexture.texture.destroy()\n }\n if (this.octahedralRGBE?.texture) {\n this.octahedralRGBE.texture.destroy()\n }\n this.faceTextures = []\n this.faceDepthTextures = []\n this.octahedralTexture = null\n this.octahedralRGBE = null\n }\n}\n\nexport { ProbeCapture }\n","import { Texture } from \"../Texture.js\"\nimport { vec3 } from \"../math.js\"\n\n/**\n * ReflectionProbe - A single reflection probe with position and texture\n */\nclass ReflectionProbe {\n constructor(id, position, worldId = 'default') {\n this.id = id\n this.position = [...position]\n this.worldId = worldId\n this.texture = null\n this.loaded = false\n this.url = null\n this.mipLevels = 6\n }\n}\n\n/**\n * ReflectionProbeManager - Manages loading, caching, and interpolating reflection probes\n *\n * Handles:\n * - Loading probes from server by worldId/position\n * - Finding closest probes to camera\n * - Interpolating between probes\n * - Uploading new captures to server\n */\nclass ReflectionProbeManager {\n constructor(engine) {\n this.engine = engine\n\n // All loaded probes: Map<probeId, ReflectionProbe>\n this.probes = new Map()\n\n // Probes indexed by world: Map<worldId, ReflectionProbe[]>\n this.probesByWorld = new Map()\n\n // Currently active probes for rendering (closest 2)\n this.activeProbes = [null, null]\n this.activeWeights = [1.0, 0.0]\n\n // Base/fallback environment map (used when no probes available)\n this.fallbackEnvironment = null\n\n // Server configuration\n this.serverBaseUrl = '/api/probes'\n\n // Cache settings\n this.maxCachedProbes = 10\n this.loadingProbes = new Set() // URLs currently being loaded\n }\n\n /**\n * Set fallback environment map\n */\n setFallbackEnvironment(envMap) {\n this.fallbackEnvironment = envMap\n }\n\n /**\n * Register a probe (from capture or loaded from server)\n */\n registerProbe(probe) {\n this.probes.set(probe.id, probe)\n\n // Index by world\n if (!this.probesByWorld.has(probe.worldId)) {\n this.probesByWorld.set(probe.worldId, [])\n }\n this.probesByWorld.get(probe.worldId).push(probe)\n\n console.log(`ReflectionProbeManager: Registered probe ${probe.id} at [${probe.position.join(', ')}]`)\n }\n\n /**\n * Load probe from URL\n * @param {string} url - URL to HDR probe image\n * @param {vec3} position - World position of probe\n * @param {string} worldId - World identifier\n * @returns {Promise<ReflectionProbe>}\n */\n async loadProbe(url, position, worldId = 'default') {\n // Check if already loaded\n const existingId = `${worldId}_${position.join('_')}`\n if (this.probes.has(existingId)) {\n return this.probes.get(existingId)\n }\n\n // Check if already loading\n if (this.loadingProbes.has(url)) {\n // Wait for existing load\n await new Promise(resolve => {\n const check = () => {\n if (!this.loadingProbes.has(url)) {\n resolve()\n } else {\n setTimeout(check, 100)\n }\n }\n check()\n })\n return this.probes.get(existingId)\n }\n\n this.loadingProbes.add(url)\n\n try {\n // Load texture (supports HDR)\n const texture = await Texture.fromImage(this.engine, url, {\n flipY: false,\n srgb: false,\n generateMips: true,\n addressMode: 'clamp-to-edge'\n })\n\n const probe = new ReflectionProbe(existingId, position, worldId)\n probe.texture = texture\n probe.url = url\n probe.loaded = true\n\n this.registerProbe(probe)\n\n // Enforce cache limit\n this._enforceeCacheLimit()\n\n return probe\n } catch (error) {\n console.error(`ReflectionProbeManager: Failed to load probe from ${url}:`, error)\n return null\n } finally {\n this.loadingProbes.delete(url)\n }\n }\n\n /**\n * Load probes for a world from server\n * @param {string} worldId - World identifier\n * @returns {Promise<ReflectionProbe[]>}\n */\n async loadWorldProbes(worldId) {\n try {\n // Fetch probe manifest from server\n const response = await fetch(`${this.serverBaseUrl}/${worldId}/manifest.json`)\n if (!response.ok) {\n console.warn(`ReflectionProbeManager: No probes found for world ${worldId}`)\n return []\n }\n\n const manifest = await response.json()\n const loadedProbes = []\n\n for (const probeInfo of manifest.probes) {\n const probe = await this.loadProbe(\n `${this.serverBaseUrl}/${worldId}/${probeInfo.file}`,\n probeInfo.position,\n worldId\n )\n if (probe) {\n loadedProbes.push(probe)\n }\n }\n\n return loadedProbes\n } catch (error) {\n console.warn(`ReflectionProbeManager: Failed to load world probes for ${worldId}:`, error)\n return []\n }\n }\n\n /**\n * Find closest probes to a position\n * @param {vec3} position - World position\n * @param {string} worldId - World identifier\n * @param {number} count - Number of probes to find (default 2)\n * @returns {{ probes: ReflectionProbe[], weights: number[] }}\n */\n findClosestProbes(position, worldId = 'default', count = 2) {\n const worldProbes = this.probesByWorld.get(worldId) || []\n\n if (worldProbes.length === 0) {\n return { probes: [], weights: [] }\n }\n\n // Calculate distances\n const probesWithDistance = worldProbes\n .filter(p => p.loaded && p.texture)\n .map(probe => {\n const dx = probe.position[0] - position[0]\n const dy = probe.position[1] - position[1]\n const dz = probe.position[2] - position[2]\n const distSq = dx * dx + dy * dy + dz * dz\n return { probe, distance: Math.sqrt(distSq) }\n })\n .sort((a, b) => a.distance - b.distance)\n\n // Get closest N probes\n const closest = probesWithDistance.slice(0, count)\n\n if (closest.length === 0) {\n return { probes: [], weights: [] }\n }\n\n if (closest.length === 1) {\n return {\n probes: [closest[0].probe],\n weights: [1.0]\n }\n }\n\n // Calculate interpolation weights based on inverse distance\n const totalInvDist = closest.reduce((sum, p) => sum + 1.0 / Math.max(p.distance, 0.001), 0)\n const weights = closest.map(p => (1.0 / Math.max(p.distance, 0.001)) / totalInvDist)\n\n return {\n probes: closest.map(p => p.probe),\n weights\n }\n }\n\n /**\n * Update active probes based on camera position\n * @param {vec3} cameraPosition - Camera world position\n * @param {string} worldId - Current world\n */\n updateActiveProbes(cameraPosition, worldId = 'default') {\n const { probes, weights } = this.findClosestProbes(cameraPosition, worldId, 2)\n\n this.activeProbes = [probes[0] || null, probes[1] || null]\n this.activeWeights = [weights[0] || 1.0, weights[1] || 0.0]\n }\n\n /**\n * Get active probe textures and weights for shader\n * @returns {{ textures: [Texture, Texture], weights: [number, number] }}\n */\n getActiveProbeData() {\n return {\n textures: [\n this.activeProbes[0]?.texture || this.fallbackEnvironment,\n this.activeProbes[1]?.texture || this.fallbackEnvironment\n ],\n weights: this.activeWeights\n }\n }\n\n /**\n * Upload a captured probe to server\n * @param {Blob} probeData - HDR PNG blob\n * @param {vec3} position - Capture position\n * @param {string} worldId - World identifier\n * @returns {Promise<boolean>} Success\n */\n async uploadProbe(probeData, position, worldId = 'default') {\n try {\n const formData = new FormData()\n formData.append('probe', probeData, 'probe.png')\n formData.append('position', JSON.stringify(position))\n formData.append('worldId', worldId)\n\n const response = await fetch(`${this.serverBaseUrl}/upload`, {\n method: 'POST',\n body: formData\n })\n\n if (!response.ok) {\n throw new Error(`Upload failed: ${response.status}`)\n }\n\n const result = await response.json()\n console.log(`ReflectionProbeManager: Uploaded probe to ${result.url}`)\n\n // Load the uploaded probe\n await this.loadProbe(result.url, position, worldId)\n\n return true\n } catch (error) {\n console.error('ReflectionProbeManager: Failed to upload probe:', error)\n return false\n }\n }\n\n /**\n * Remove a probe\n */\n removeProbe(probeId) {\n const probe = this.probes.get(probeId)\n if (!probe) return\n\n // Remove from world index\n const worldProbes = this.probesByWorld.get(probe.worldId)\n if (worldProbes) {\n const idx = worldProbes.indexOf(probe)\n if (idx >= 0) worldProbes.splice(idx, 1)\n }\n\n // Destroy texture\n if (probe.texture?.texture) {\n probe.texture.texture.destroy()\n }\n\n this.probes.delete(probeId)\n }\n\n /**\n * Enforce cache limit by removing least recently used probes\n */\n _enforceeCacheLimit() {\n if (this.probes.size <= this.maxCachedProbes) return\n\n // Simple LRU: remove oldest probes not currently active\n const toRemove = []\n for (const [id, probe] of this.probes) {\n if (probe !== this.activeProbes[0] && probe !== this.activeProbes[1]) {\n toRemove.push(id)\n if (this.probes.size - toRemove.length <= this.maxCachedProbes) {\n break\n }\n }\n }\n\n for (const id of toRemove) {\n this.removeProbe(id)\n }\n }\n\n /**\n * Get statistics\n */\n getStats() {\n return {\n totalProbes: this.probes.size,\n loadedProbes: [...this.probes.values()].filter(p => p.loaded).length,\n worldCount: this.probesByWorld.size,\n activeProbe0: this.activeProbes[0]?.id || 'none',\n activeProbe1: this.activeProbes[1]?.id || 'none',\n weights: this.activeWeights\n }\n }\n\n /**\n * Destroy all resources\n */\n destroy() {\n for (const [id] of this.probes) {\n this.removeProbe(id)\n }\n this.probes.clear()\n this.probesByWorld.clear()\n }\n}\n\nexport { ReflectionProbeManager, ReflectionProbe }\n","import { BasePass } from \"./BasePass.js\"\nimport { Texture } from \"../../Texture.js\"\nimport { ProbeCapture } from \"../ProbeCapture.js\"\nimport { ReflectionProbeManager } from \"../ReflectionProbeManager.js\"\n\n/**\n * ReflectionPass - Manages reflection probes and environment lighting\n *\n * Pass 2 in the 7-pass pipeline.\n * Handles:\n * - Loading/managing reflection probes\n * - Real-time probe capture (when triggered)\n * - Probe interpolation based on camera position\n *\n * Configuration:\n * - 1024x1024 octahedral HDR texture per probe\n * - Interpolates between closest 2 probes\n */\nclass ReflectionPass extends BasePass {\n constructor(engine = null) {\n super('Reflection', engine)\n\n // Probe capture system\n this.probeCapture = null\n\n // Probe manager for loading/interpolating\n this.probeManager = null\n\n // Current world ID\n this.currentWorldId = 'default'\n\n // Capture request queue\n this.captureRequests = []\n\n // Output: combined/interpolated probe texture\n this.outputTexture = null\n }\n\n async _init() {\n // Initialize probe manager (lightweight, always works)\n this.probeManager = new ReflectionProbeManager(this.engine)\n\n // Initialize probe capture system (can fail on some systems)\n try {\n this.probeCapture = new ProbeCapture(this.engine)\n await this.probeCapture.initialize()\n } catch (e) {\n console.warn('ReflectionPass: Probe capture initialization failed:', e)\n this.probeCapture = null\n }\n }\n\n /**\n * Set fallback environment map (used when no probes available)\n * @param {Texture} envMap - Environment map texture\n * @param {number} encoding - 0 = equirectangular, 1 = octahedral\n */\n setFallbackEnvironment(envMap, encoding = 0) {\n if (this.probeManager) {\n this.probeManager.setFallbackEnvironment(envMap)\n }\n if (this.probeCapture) {\n this.probeCapture.setFallbackEnvironment(envMap)\n this.probeCapture.envEncoding = encoding\n }\n }\n\n /**\n * Load probes for a world\n */\n async loadWorldProbes(worldId) {\n this.currentWorldId = worldId\n if (this.probeManager) {\n await this.probeManager.loadWorldProbes(worldId)\n }\n }\n\n /**\n * Request a probe capture at position\n * Will be processed during next execute()\n */\n requestCapture(position, worldId = null) {\n this.captureRequests.push({\n position: [...position],\n worldId: worldId || this.currentWorldId\n })\n }\n\n /**\n * Load a specific probe from URL\n */\n async loadProbe(url, position, worldId = null) {\n if (this.probeManager) {\n return await this.probeManager.loadProbe(url, position, worldId || this.currentWorldId)\n }\n return null\n }\n\n async _execute(context) {\n const { camera } = context\n\n // Update active probes based on camera position\n if (this.probeManager && camera) {\n this.probeManager.updateActiveProbes(camera.position, this.currentWorldId)\n }\n\n // Process capture requests (one per frame max)\n if (this.captureRequests.length > 0 && !this.probeCapture?.isCapturing) {\n const request = this.captureRequests.shift()\n // Capture would happen here - requires renderGraph reference\n // For now, log the request\n console.log(`ReflectionPass: Capture requested at [${request.position.join(', ')}]`)\n }\n }\n\n async _resize(width, height) {\n // Reflection maps are fixed size, doesn't resize with screen\n }\n\n _destroy() {\n if (this.probeCapture) {\n this.probeCapture.destroy()\n }\n if (this.probeManager) {\n this.probeManager.destroy()\n }\n }\n\n /**\n * Get probe manager for external access\n */\n getProbeManager() {\n return this.probeManager\n }\n\n /**\n * Get probe capture system for manual triggering\n */\n getProbeCapture() {\n return this.probeCapture\n }\n\n /**\n * Get active probe data for lighting pass\n */\n getActiveProbeData() {\n if (this.probeManager) {\n return this.probeManager.getActiveProbeData()\n }\n return {\n textures: [null, null],\n weights: [1.0, 0.0]\n }\n }\n}\n\nexport { ReflectionPass }\n","import {\n makeShaderDataDefinitions,\n makeStructuredView,\n } from 'webgpu-utils';\n\n// Placeholder joint texture for non-skinned meshes\nlet _placeholderJointTexture = null\nlet _placeholderJointTextureView = null\nlet _placeholderJointSampler = null\n\nfunction getPlaceholderJointTexture(engine) {\n const { device } = engine\n if (!_placeholderJointTexture) {\n // Create a 4x1 rgba32float texture (one identity matrix)\n _placeholderJointTexture = device.createTexture({\n size: [4, 1, 1],\n format: 'rgba32float',\n usage: GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_DST,\n })\n // Write identity matrix\n const identityData = new Float32Array([\n 1, 0, 0, 0, // column 0\n 0, 1, 0, 0, // column 1\n 0, 0, 1, 0, // column 2\n 0, 0, 0, 1, // column 3\n ])\n device.queue.writeTexture(\n { texture: _placeholderJointTexture },\n identityData,\n { bytesPerRow: 4 * 4 * 4, rowsPerImage: 1 },\n [4, 1, 1]\n )\n _placeholderJointTextureView = _placeholderJointTexture.createView()\n _placeholderJointSampler = device.createSampler({\n magFilter: 'nearest',\n minFilter: 'nearest',\n })\n }\n return {\n texture: _placeholderJointTexture,\n view: _placeholderJointTextureView,\n sampler: _placeholderJointSampler,\n }\n}\n\n// Placeholder noise texture for alpha hashing when no noise texture is configured\nlet _placeholderNoiseTexture = null\nlet _placeholderNoiseTextureView = null\n\nfunction getPlaceholderNoiseTexture(engine) {\n const { device } = engine\n if (!_placeholderNoiseTexture) {\n // Create a 1x1 rgba8unorm texture with 0.5 value (middle gray)\n _placeholderNoiseTexture = device.createTexture({\n size: [1, 1, 1],\n format: 'rgba8unorm',\n usage: GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_DST,\n })\n // Write 0.5 gray value\n const noiseData = new Uint8Array([128, 128, 128, 255])\n device.queue.writeTexture(\n { texture: _placeholderNoiseTexture },\n noiseData,\n { bytesPerRow: 4, rowsPerImage: 1 },\n [1, 1, 1]\n )\n _placeholderNoiseTextureView = _placeholderNoiseTexture.createView()\n }\n return {\n texture: _placeholderNoiseTexture,\n view: _placeholderNoiseTextureView,\n }\n}\n\nclass Pipeline {\n engine = null\n\n static async create(engine, {\n wgslSource,\n geometry,\n textures = [],\n isPostProcessing = false,\n renderTarget = null,\n uniforms = null,\n label = 'pipeline',\n skin = null, // Optional skin for skeletal animation\n shadowPass = null, // Optional shadow pass for shadow mapping\n tileLightBuffer = null, // Optional tile light indices buffer for tiled lighting\n lightBuffer = null, // Optional light storage buffer for tiled lighting\n noiseTexture = null, // Optional noise texture for alpha hashing\n doubleSided = false, // Optional: disable backface culling for double-sided materials\n }) {\n let texture = textures[0]\n const { canvas, device, canvasFormat, options } = engine\n\n const shaderModule = device.createShaderModule({\n code: wgslSource,\n })\n \n const compilationInfo = await shaderModule.getCompilationInfo()\n for (const message of compilationInfo.messages) {\n let formattedMessage = \"\"\n if (message.lineNum) {\n formattedMessage += `Shader Error, ${label} Line ${message.lineNum}:${message.linePos} - ${wgslSource.substr(\n message.offset,\n message.length\n )}\\n`\n }\n formattedMessage += message.message\n\n switch (message.type) {\n case \"error\":\n console.error(formattedMessage)\n engine.rendering = false\n return false\n break\n case \"warning\":\n console.warn(formattedMessage)\n break\n case \"info\":\n console.log(formattedMessage)\n break\n }\n }\n\n let targets = []\n if (renderTarget) {\n if (renderTarget && renderTarget.isGBuffer) {\n targets = renderTarget.getTargets()\n } else {\n targets = [{ format: renderTarget.format }]\n }\n } else {\n targets = [{ format: canvasFormat }]\n }\n let pipelineDescriptor = {\n label: label,\n layout: \"auto\",\n vertex: {\n module: shaderModule,\n entryPoint: \"vertexMain\",\n },\n fragment: {\n module: shaderModule,\n entryPoint: \"fragmentMain\",\n targets: targets,\n },\n primitive: {\n topology: \"triangle-list\",\n },\n };\n\n if (!isPostProcessing) {\n pipelineDescriptor.vertex.buffers = [\n geometry.vertexBufferLayout,\n geometry.instanceBufferLayout\n ];\n pipelineDescriptor.primitive.cullMode = doubleSided ? \"none\" : \"back\";\n pipelineDescriptor.depthStencil = {\n depthWriteEnabled: true,\n depthCompare: \"less\",\n format: \"depth32float\",\n };\n }\n\n // Use async pipeline creation for parallel shader compilation\n let pipeline = await device.createRenderPipelineAsync(pipelineDescriptor);\n\n const defs = makeShaderDataDefinitions(wgslSource);\n const uniformValues = makeStructuredView(defs.uniforms.uniforms);\n\n const uniformBuffer = device.createBuffer({\n label: label+'_uniformBuffer',\n size: uniformValues.arrayBuffer.byteLength,\n usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST,\n })\n\n\n // Create bind group\n let bindGroup;\n let entries, bgl\n if (isPostProcessing) {\n if (texture.isGBuffer) {\n let env = textures[1]\n if (!env || !env.view || !env.sampler) {\n console.error('Pipeline: environment texture missing or incomplete', env)\n throw new Error('Pipeline: environment texture required for lighting')\n }\n let sampleType = 'unfilterable-float'\n let sampleCount = 1\n\n // Base bind group layout entries\n let bglEntries = [\n {\n binding: 0,\n visibility: GPUShaderStage.FRAGMENT,\n buffer: { type: 'uniform' },\n },\n {\n binding: 1,\n visibility: GPUShaderStage.FRAGMENT,\n texture: {\n sampleType: sampleType,\n sampleCount: sampleCount,\n },\n },\n {\n binding: 2,\n visibility: GPUShaderStage.FRAGMENT,\n texture: {\n sampleType: sampleType,\n sampleCount: sampleCount,\n },\n },\n {\n binding: 3,\n visibility: GPUShaderStage.FRAGMENT,\n texture: {\n sampleType: sampleType,\n sampleCount: sampleCount,\n },\n },\n {\n binding: 4,\n visibility: GPUShaderStage.FRAGMENT,\n texture: {\n sampleType: sampleType,\n sampleCount: sampleCount,\n },\n },\n {\n binding: 5,\n visibility: GPUShaderStage.FRAGMENT,\n texture: {\n sampleType: 'depth',\n sampleCount: sampleCount,\n },\n },\n {\n binding: 6,\n visibility: GPUShaderStage.FRAGMENT,\n texture: { sampleType: 'float' },\n },\n {\n binding: 7,\n visibility: GPUShaderStage.FRAGMENT,\n sampler: { type: 'filtering' },\n },\n ];\n\n // Base bind group entries\n entries = [\n { binding: 0, resource: { buffer: uniformBuffer } },\n { binding: 1, resource: texture.albedo.view },\n { binding: 2, resource: texture.normal.view },\n { binding: 3, resource: texture.arm.view },\n { binding: 4, resource: texture.emission.view },\n { binding: 5, resource: texture.depth.view },\n { binding: 6, resource: env.view },\n { binding: 7, resource: env.sampler },\n ];\n\n // Add shadow map bindings if shadowPass is provided\n if (shadowPass) {\n // Cascaded directional shadow map (2d-array texture)\n bglEntries.push({\n binding: 8,\n visibility: GPUShaderStage.FRAGMENT,\n texture: {\n sampleType: 'depth',\n viewDimension: '2d-array',\n sampleCount: 1,\n },\n });\n bglEntries.push({\n binding: 9,\n visibility: GPUShaderStage.FRAGMENT,\n sampler: { type: 'comparison' },\n });\n\n // Cascade matrices storage buffer\n bglEntries.push({\n binding: 13,\n visibility: GPUShaderStage.FRAGMENT,\n buffer: { type: 'read-only-storage' },\n });\n\n entries.push({ binding: 8, resource: shadowPass.getShadowMapView() });\n entries.push({ binding: 9, resource: shadowPass.getShadowSampler() });\n entries.push({ binding: 13, resource: { buffer: shadowPass.getCascadeMatricesBuffer() } });\n\n // Spot shadow atlas\n bglEntries.push({\n binding: 10,\n visibility: GPUShaderStage.FRAGMENT,\n texture: {\n sampleType: 'depth',\n sampleCount: 1,\n },\n });\n bglEntries.push({\n binding: 11,\n visibility: GPUShaderStage.FRAGMENT,\n sampler: { type: 'comparison' },\n });\n\n // Spot shadow matrices storage buffer\n bglEntries.push({\n binding: 12,\n visibility: GPUShaderStage.FRAGMENT,\n buffer: { type: 'read-only-storage' },\n });\n\n entries.push({ binding: 10, resource: shadowPass.getSpotShadowAtlasView() });\n entries.push({ binding: 11, resource: shadowPass.getShadowSampler() }); // Reuse same sampler\n entries.push({ binding: 12, resource: { buffer: shadowPass.getSpotMatricesBuffer() } });\n }\n\n // Add tile light indices buffer binding (binding 14) if provided\n if (tileLightBuffer) {\n bglEntries.push({\n binding: 14,\n visibility: GPUShaderStage.FRAGMENT,\n buffer: { type: 'read-only-storage' },\n });\n entries.push({ binding: 14, resource: { buffer: tileLightBuffer } });\n }\n\n // Add light buffer binding (binding 15) if provided\n if (lightBuffer) {\n bglEntries.push({\n binding: 15,\n visibility: GPUShaderStage.FRAGMENT,\n buffer: { type: 'read-only-storage' },\n });\n entries.push({ binding: 15, resource: { buffer: lightBuffer } });\n }\n\n // Add noise texture (binding 16, 17) if provided in textures[2]\n let noiseTexture = textures[2]\n if (noiseTexture && noiseTexture.view && noiseTexture.sampler) {\n bglEntries.push({\n binding: 16,\n visibility: GPUShaderStage.FRAGMENT,\n texture: { sampleType: 'float' },\n });\n bglEntries.push({\n binding: 17,\n visibility: GPUShaderStage.FRAGMENT,\n sampler: { type: 'filtering' },\n });\n entries.push({ binding: 16, resource: noiseTexture.view });\n entries.push({ binding: 17, resource: noiseTexture.sampler });\n } else if (noiseTexture) {\n console.warn('Pipeline: noise texture missing view or sampler')\n }\n\n // Add AO texture (binding 18) if provided in textures[3]\n let aoTexture = textures[3]\n if (aoTexture && aoTexture.view) {\n bglEntries.push({\n binding: 18,\n visibility: GPUShaderStage.FRAGMENT,\n texture: { sampleType: 'unfilterable-float' },\n });\n entries.push({ binding: 18, resource: aoTexture.view });\n } else if (aoTexture) {\n console.warn('Pipeline: aoTexture missing view')\n }\n\n // Create bind group layout\n bgl = device.createBindGroupLayout({\n label: label,\n entries: bglEntries\n });\n\n // Update pipeline descriptor to use explicit layout\n pipelineDescriptor.layout = device.createPipelineLayout({\n label: label+'_layout',\n bindGroupLayouts: [bgl]\n });\n\n // Create pipeline with explicit layout (async for parallel compilation)\n pipeline = await device.createRenderPipelineAsync(pipelineDescriptor);\n\n let bgdesc = {\n label: label,\n entries: entries,\n }\n if (bgl) {\n bgdesc.layout = bgl\n }\n bindGroup = device.createBindGroup(bgdesc);\n } else {\n let pb = await Pipeline.pipelineFromTextures(engine, pipelineDescriptor, label, textures, uniformBuffer)\n pipeline = pb[0]\n bindGroup = pb[1]\n }\n } else {\n let pb = await Pipeline.pipelineFromTextures(engine, pipelineDescriptor, label, textures, uniformBuffer, skin, noiseTexture)\n pipeline = pb[0]\n bindGroup = pb[1]\n bgl = pb[2]\n }\n\n let p = new Pipeline()\n p.engine = engine\n p.label = label\n p.wgslSource = wgslSource\n p.geometry = geometry\n p.textures = textures\n p.pipeline = pipeline\n p.uniformBuffer = uniformBuffer\n p.uniformValues = uniformValues\n p.bindGroup = bindGroup\n p.bindGroupLayout = bgl\n p.isPostProcessing = isPostProcessing\n p.renderTarget = renderTarget\n p.skin = skin\n p.shadowPass = shadowPass\n p.tileLightBuffer = tileLightBuffer\n p.lightBuffer = lightBuffer\n p.noiseTexture = noiseTexture\n p.doubleSided = doubleSided\n return p\n }\n\n static async pipelineFromTextures(engine, pipelineDescriptor, label, textures, uniformBuffer, skin = null, noiseTexture = null) {\n const { device } = engine\n\n // Get joint texture (either from skin or placeholder)\n let jointTextureData\n if (skin && skin.jointTexture) {\n jointTextureData = {\n view: skin.jointTextureView,\n sampler: skin.jointSampler,\n }\n } else {\n jointTextureData = getPlaceholderJointTexture(engine)\n }\n\n let entries = [\n { binding: 0, resource: { buffer: uniformBuffer } },\n ]\n let bgle = [\n {\n binding: 0,\n visibility: GPUShaderStage.FRAGMENT | GPUShaderStage.VERTEX,\n buffer: { type: 'uniform' },\n },\n ]\n let b = 1, b2 = 1\n for (let i = 0; i < textures.length; i++) {\n entries.push({ binding: b++, resource: textures[i].view })\n entries.push({ binding: b++, resource: textures[i].sampler })\n bgle.push({ binding: b2++, visibility: GPUShaderStage.FRAGMENT | GPUShaderStage.VERTEX, texture: { sampleType: 'float' } })\n bgle.push({ binding: b2++, visibility: GPUShaderStage.FRAGMENT | GPUShaderStage.VERTEX, sampler: { type: 'filtering' } })\n }\n\n // Add joint texture bindings (bindings 11 and 12 after 5 textures with 2 bindings each = 10 + uniform = 11)\n entries.push({ binding: b++, resource: jointTextureData.view })\n entries.push({ binding: b++, resource: jointTextureData.sampler })\n bgle.push({ binding: b2++, visibility: GPUShaderStage.VERTEX, texture: { sampleType: 'unfilterable-float' } })\n bgle.push({ binding: b2++, visibility: GPUShaderStage.VERTEX, sampler: { type: 'non-filtering' } })\n\n // Add previous joint texture for motion vectors (binding 13)\n // Use same texture as current for placeholder (no motion when no history)\n let prevJointTextureView = skin?.prevJointTextureView ?? jointTextureData.view\n entries.push({ binding: b++, resource: prevJointTextureView })\n bgle.push({ binding: b2++, visibility: GPUShaderStage.VERTEX, texture: { sampleType: 'unfilterable-float' } })\n\n // Add noise texture for alpha hashing (binding 14)\n // Always add binding - use placeholder if no noise texture provided\n const noiseTextureView = (noiseTexture && noiseTexture.view) ? noiseTexture.view : getPlaceholderNoiseTexture(engine).view\n entries.push({ binding: b++, resource: noiseTextureView })\n bgle.push({ binding: b2++, visibility: GPUShaderStage.FRAGMENT, texture: { sampleType: 'float' } })\n\n let bgl = device.createBindGroupLayout({\n label: label+'_bgl',\n entries: bgle\n });\n\n pipelineDescriptor.layout = device.createPipelineLayout({\n label: label+'_pipelineLayout',\n bindGroupLayouts: [bgl]\n });\n\n // Create pipeline with explicit layout (async for parallel compilation)\n let pipeline = await device.createRenderPipelineAsync(pipelineDescriptor);\n\n let bgdesc = {\n label: label,\n entries: entries,\n }\n if (bgl) {\n bgdesc.layout = bgl\n }\n let bindGroup = device.createBindGroup(bgdesc);\n return [ pipeline, bindGroup, bgl ]\n }\n\n // Update bind group with new skin's joint texture (for reusing pipeline with different skins)\n updateBindGroupForSkin(skin) {\n // Cache bind group per skin to avoid recreating every frame\n const skinId = skin?.jointTexture ? skin : null\n if (this._lastSkin === skinId && this.bindGroup) {\n return // Same skin, reuse existing bind group\n }\n this._lastSkin = skinId\n\n const { device } = this.engine\n\n // Get joint texture (either from skin or placeholder)\n let jointTextureData\n if (skin && skin.jointTexture) {\n jointTextureData = {\n view: skin.jointTextureView,\n sampler: skin.jointSampler,\n }\n } else {\n jointTextureData = getPlaceholderJointTexture(this.engine)\n }\n\n let entries = [\n { binding: 0, resource: { buffer: this.uniformBuffer } },\n ]\n let b = 1\n for (let i = 0; i < this.textures.length; i++) {\n entries.push({ binding: b++, resource: this.textures[i].view })\n entries.push({ binding: b++, resource: this.textures[i].sampler })\n }\n // Add joint texture bindings\n entries.push({ binding: b++, resource: jointTextureData.view })\n entries.push({ binding: b++, resource: jointTextureData.sampler })\n\n // Add previous joint texture for motion vectors\n let prevJointTextureView = skin?.prevJointTextureView ?? jointTextureData.view\n entries.push({ binding: b++, resource: prevJointTextureView })\n\n // Add noise texture for alpha hashing\n // Always add binding - use placeholder if no noise texture provided\n const noiseTextureView = (this.noiseTexture && this.noiseTexture.view) ? this.noiseTexture.view : getPlaceholderNoiseTexture(this.engine).view\n entries.push({ binding: b++, resource: noiseTextureView })\n\n this.bindGroup = device.createBindGroup({\n label: this.label + '_bindGroup',\n layout: this.bindGroupLayout,\n entries: entries,\n })\n }\n\n render(options = {}) {\n let { renderTarget } = this\n const { device, context, stats } = this.engine\n const depthTexture = this.engine.depthTexture\n \n let commandEncoder, passEncoder\n if (options.passEncoder) {\n commandEncoder = options.commandEncoder\n passEncoder = options.passEncoder\n } else {\n commandEncoder = device.createCommandEncoder()\n \n const textureView = renderTarget ? renderTarget.view : context.getCurrentTexture().createView()\n \n let renderPassDescriptor = {}\n if (renderTarget && renderTarget.isGBuffer) {\n renderPassDescriptor.colorAttachments = renderTarget.getColorAttachments()\n } else {\n renderPassDescriptor.colorAttachments = [{\n view: textureView,\n clearValue: { r: 0, g: 0, b: 0, a: 1 },\n loadOp: \"clear\",\n storeOp: \"store\",\n }]\n }\n\n if (!this.isPostProcessing) {\n if (renderTarget && renderTarget.isGBuffer) {\n renderPassDescriptor.depthStencilAttachment = renderTarget.getDepthStencilAttachment()\n } else {\n renderPassDescriptor.depthStencilAttachment = {\n view: depthTexture.view,\n depthClearValue: 1.0,\n depthLoadOp: \"clear\",\n depthStoreOp: \"store\",\n }\n }\n }\n\n passEncoder = commandEncoder.beginRenderPass(renderPassDescriptor)\n }\n\n // Draw actual pipeline\n\n device.queue.writeBuffer(this.uniformBuffer, 0, this.uniformValues.arrayBuffer)\n passEncoder.setPipeline(this.pipeline)\n if (!this.isPostProcessing) {\n passEncoder.setVertexBuffer(0, this.geometry.vertexBuffer)\n passEncoder.setVertexBuffer(1, this.geometry.instanceBuffer)\n passEncoder.setIndexBuffer(this.geometry.indexBuffer, \"uint32\")\n }\n passEncoder.setBindGroup(0, this.bindGroup)\n if (this.isPostProcessing) {\n passEncoder.draw(3);\n stats.triangles += 1\n stats.drawCalls++\n } else {\n const instanceCount = this.geometry.instanceCount ?? 0\n if (instanceCount > 0) {\n passEncoder.drawIndexed(this.geometry.indexArray.length, instanceCount)\n stats.triangles += this.geometry.indexArray.length / 3 * instanceCount\n stats.drawCalls++\n }\n }\n\n if (!options.dontFinish) {\n passEncoder.end()\n device.queue.submit([commandEncoder.finish()])\n }\n\n\n return {\n commandEncoder: commandEncoder,\n passEncoder: passEncoder,\n }\n }\n\n finish(options = {}) {\n const { device } = this.engine\n let commandEncoder, passEncoder\n if (options.passEncoder) {\n commandEncoder = options.commandEncoder\n passEncoder = options.passEncoder\n passEncoder.end()\n device.queue.submit([commandEncoder.finish()])\n } else {\n console.warn('finish: no passEncoder')\n }\n }\n}\n\nexport {\n Pipeline\n}\n","struct VertexInput {\n @location(0) position: vec3f,\n @location(1) uv: vec2f,\n @location(2) normal: vec3f,\n @location(3) color: vec4f,\n @location(4) weights: vec4f,\n @location(5) joints: vec4u,\n}\n\nstruct VertexOutput {\n @invariant @builtin(position) position: vec4f, // @invariant ensures shared edges compute identical positions\n @location(0) worldPos: vec3f,\n @location(1) uv: vec2f,\n @location(2) normal: vec3f,\n @location(3) viewZ: f32, // Linear view-space depth for logarithmic encoding\n @location(4) currClipPos: vec4f, // Current frame clip position (for motion vectors)\n @location(5) prevClipPos: vec4f, // Previous frame clip position (for motion vectors)\n @location(6) instanceColor: vec4f, // Per-instance color tint for sprites\n @location(7) anchorY: f32, // Entity anchor Y position (for billboard clip plane testing)\n}\n\nstruct Uniforms {\n viewMatrix: mat4x4f,\n projectionMatrix: mat4x4f,\n prevViewProjMatrix: mat4x4f, // Previous frame view-projection for motion vectors\n mipBias: f32,\n skinEnabled: f32, // 1.0 if skinning enabled, 0.0 otherwise\n numJoints: f32, // Number of joints in the skin\n near: f32, // Camera near plane\n far: f32, // Camera far plane\n jitterFadeDistance: f32, // Distance at which jitter fades to 0 (meters)\n jitterOffset: vec2f, // TAA jitter offset in pixels\n screenSize: vec2f, // Screen dimensions for pixel-to-clip conversion\n emissionFactor: vec4f,\n clipPlaneY: f32, // Y level for clip plane\n clipPlaneEnabled: f32, // 1.0 to enable clip plane, 0.0 to disable\n clipPlaneDirection: f32, // 1.0 = discard below clipPlaneY, -1.0 = discard above clipPlaneY\n pixelRounding: f32, // Pixel grid size (0=off, 1=every pixel, 2=every 2px, etc.)\n pixelExpansion: f32, // Sub-pixel expansion to convert gaps to overlaps (0=off, 0.05=default)\n positionRounding: f32, // If > 0, round view-space position to this precision (simulates fixed-point)\n alphaHashEnabled: f32, // 1.0 to enable alpha hashing/dithering for cutout transparency\n alphaHashScale: f32, // Scale factor for alpha hash (default 1.0, higher = more opaque)\n luminanceToAlpha: f32, // 1.0 to derive alpha from base color luminance (black=transparent)\n noiseSize: f32, // Size of noise texture (8 for bayer, 64/128 for blue noise)\n noiseOffsetX: f32, // Animated noise offset X\n noiseOffsetY: f32, // Animated noise offset Y\n cameraPosition: vec3f, // World-space camera position for distance fade\n distanceFadeStart: f32, // Distance where fade begins (A)\n distanceFadeEnd: f32, // Distance where fade completes (B) - object invisible\n // Billboard/sprite uniforms\n billboardMode: f32, // 0=none, 1=center (spherical), 2=bottom (cylindrical), 3=horizontal\n billboardCameraRight: vec3f, // Camera right vector for billboarding\n _pad1: f32,\n billboardCameraUp: vec3f, // Camera up vector for billboarding\n _pad2: f32,\n billboardCameraForward: vec3f, // Camera forward vector for billboarding\n specularBoost: f32, // Per-material specular boost (0-1), scales the 3-light specular effect\n}\n\nstruct InstanceData {\n modelMatrix: mat4x4f,\n posRadius: vec4f,\n}\n\n@group(0) @binding(0) var<uniform> uniforms: Uniforms;\n@group(0) @binding(1) var albedoTexture: texture_2d<f32>;\n@group(0) @binding(2) var albedoSampler: sampler;\n@group(0) @binding(3) var normalTexture: texture_2d<f32>;\n@group(0) @binding(4) var normalSampler: sampler;\n@group(0) @binding(5) var ambientTexture: texture_2d<f32>;\n@group(0) @binding(6) var ambientSampler: sampler;\n@group(0) @binding(7) var rmTexture: texture_2d<f32>;\n@group(0) @binding(8) var rmSampler: sampler;\n@group(0) @binding(9) var emissionTexture: texture_2d<f32>;\n@group(0) @binding(10) var emissionSampler: sampler;\n@group(0) @binding(11) var jointTexture: texture_2d<f32>;\n@group(0) @binding(12) var jointSampler: sampler;\n@group(0) @binding(13) var prevJointTexture: texture_2d<f32>; // Previous frame joint matrices for skinned motion vectors\n@group(0) @binding(14) var noiseTexture: texture_2d<f32>; // Noise texture for alpha hashing (blue noise or bayer)\n\n// Sample noise at screen position (tiled with animated offset)\nfn sampleNoise(screenPos: vec2f) -> f32 {\n let noiseSize = i32(uniforms.noiseSize);\n let noiseOffsetX = i32(uniforms.noiseOffsetX * f32(noiseSize));\n let noiseOffsetY = i32(uniforms.noiseOffsetY * f32(noiseSize));\n\n let texCoord = vec2i(\n (i32(screenPos.x) + noiseOffsetX) % noiseSize,\n (i32(screenPos.y) + noiseOffsetY) % noiseSize\n );\n return textureLoad(noiseTexture, texCoord, 0).r;\n}\n\n// Get a 4x4 matrix from the joint texture\n// Each row of the texture stores one joint's matrix (4 RGBA pixels = 16 floats)\nfn getJointMatrix(jointIndex: u32) -> mat4x4f {\n let row = i32(jointIndex);\n let col0 = textureLoad(jointTexture, vec2i(0, row), 0);\n let col1 = textureLoad(jointTexture, vec2i(1, row), 0);\n let col2 = textureLoad(jointTexture, vec2i(2, row), 0);\n let col3 = textureLoad(jointTexture, vec2i(3, row), 0);\n return mat4x4f(col0, col1, col2, col3);\n}\n\n// Get a 4x4 matrix from the previous frame joint texture (for motion vectors)\nfn getPrevJointMatrix(jointIndex: u32) -> mat4x4f {\n let row = i32(jointIndex);\n let col0 = textureLoad(prevJointTexture, vec2i(0, row), 0);\n let col1 = textureLoad(prevJointTexture, vec2i(1, row), 0);\n let col2 = textureLoad(prevJointTexture, vec2i(2, row), 0);\n let col3 = textureLoad(prevJointTexture, vec2i(3, row), 0);\n return mat4x4f(col0, col1, col2, col3);\n}\n\n// Apply skinning to a position\nfn applySkinning(position: vec3f, joints: vec4u, weights: vec4f) -> vec3f {\n var skinnedPos = vec3f(0.0);\n\n let m0 = getJointMatrix(joints.x);\n let m1 = getJointMatrix(joints.y);\n let m2 = getJointMatrix(joints.z);\n let m3 = getJointMatrix(joints.w);\n\n skinnedPos += (m0 * vec4f(position, 1.0)).xyz * weights.x;\n skinnedPos += (m1 * vec4f(position, 1.0)).xyz * weights.y;\n skinnedPos += (m2 * vec4f(position, 1.0)).xyz * weights.z;\n skinnedPos += (m3 * vec4f(position, 1.0)).xyz * weights.w;\n\n return skinnedPos;\n}\n\n// Apply skinning with previous frame joint matrices (for motion vectors)\nfn applySkinningPrev(position: vec3f, joints: vec4u, weights: vec4f) -> vec3f {\n var skinnedPos = vec3f(0.0);\n\n let m0 = getPrevJointMatrix(joints.x);\n let m1 = getPrevJointMatrix(joints.y);\n let m2 = getPrevJointMatrix(joints.z);\n let m3 = getPrevJointMatrix(joints.w);\n\n skinnedPos += (m0 * vec4f(position, 1.0)).xyz * weights.x;\n skinnedPos += (m1 * vec4f(position, 1.0)).xyz * weights.y;\n skinnedPos += (m2 * vec4f(position, 1.0)).xyz * weights.z;\n skinnedPos += (m3 * vec4f(position, 1.0)).xyz * weights.w;\n\n return skinnedPos;\n}\n\n// Apply skinning to a normal (using 3x3 rotation part of matrices)\nfn applySkinningNormal(normal: vec3f, joints: vec4u, weights: vec4f) -> vec3f {\n var skinnedNormal = vec3f(0.0);\n\n let m0 = getJointMatrix(joints.x);\n let m1 = getJointMatrix(joints.y);\n let m2 = getJointMatrix(joints.z);\n let m3 = getJointMatrix(joints.w);\n\n // Extract 3x3 rotation matrices\n let r0 = mat3x3f(m0[0].xyz, m0[1].xyz, m0[2].xyz);\n let r1 = mat3x3f(m1[0].xyz, m1[1].xyz, m1[2].xyz);\n let r2 = mat3x3f(m2[0].xyz, m2[1].xyz, m2[2].xyz);\n let r3 = mat3x3f(m3[0].xyz, m3[1].xyz, m3[2].xyz);\n\n skinnedNormal += (r0 * normal) * weights.x;\n skinnedNormal += (r1 * normal) * weights.y;\n skinnedNormal += (r2 * normal) * weights.z;\n skinnedNormal += (r3 * normal) * weights.w;\n\n return normalize(skinnedNormal);\n}\n\n@vertex\nfn vertexMain(\n input: VertexInput,\n @builtin(instance_index) instanceIdx : u32,\n @location(6) instanceModelMatrix0: vec4f,\n @location(7) instanceModelMatrix1: vec4f,\n @location(8) instanceModelMatrix2: vec4f,\n @location(9) instanceModelMatrix3: vec4f,\n @location(10) posRadius: vec4f,\n @location(11) uvTransform: vec4f, // UV offset (xy) and scale (zw) for sprite sheets\n @location(12) instanceColor: vec4f, // Per-instance color tint\n) -> VertexOutput {\n var output: VertexOutput;\n\n // Construct instance model matrix\n let instanceModelMatrix = mat4x4f(\n instanceModelMatrix0,\n instanceModelMatrix1,\n instanceModelMatrix2,\n instanceModelMatrix3\n );\n\n var localPos = input.position;\n var prevLocalPos = input.position; // For motion vectors\n var localNormal = input.normal;\n var billboardWorldPos: vec3f;\n var useBillboardWorldPos = false;\n\n // Apply billboard transform if enabled\n if (uniforms.billboardMode > 0.5) {\n // Extract entity position (translation) from instance matrix\n let entityPos = instanceModelMatrix[3].xyz;\n\n // Extract scale from matrix (length of each column's xyz)\n let scaleX = length(instanceModelMatrix[0].xyz);\n let scaleY = length(instanceModelMatrix[1].xyz);\n\n if (uniforms.billboardMode < 1.5) {\n // Mode 1: Center (spherical billboard) - faces camera, centered on entity\n // Calculate from camera position and world-up to avoid view matrix flip issues\n let toCamera = uniforms.cameraPosition - entityPos;\n let toCameraDist = length(toCamera);\n\n var right: vec3f;\n var up: vec3f;\n\n if (toCameraDist > 0.001) {\n let forward = toCamera / toCameraDist;\n // Right = cross(worldUp, forward)\n right = cross(vec3f(0.0, 1.0, 0.0), forward);\n let rightLen = length(right);\n if (rightLen < 0.001) {\n // Camera directly above/below - use camera right as fallback\n right = uniforms.billboardCameraRight;\n up = vec3f(0.0, 0.0, 1.0); // Use Z as up when looking straight down\n } else {\n right = right / rightLen;\n // Up is perpendicular to both forward and right\n up = cross(forward, right);\n }\n } else {\n right = uniforms.billboardCameraRight;\n up = vec3f(0.0, 1.0, 0.0);\n }\n\n let offset = right * input.position.x * scaleX + up * input.position.y * scaleY;\n billboardWorldPos = entityPos + offset;\n useBillboardWorldPos = true;\n localNormal = vec3f(0.0, 1.0, 0.0); // Normal points up\n\n } else if (uniforms.billboardMode < 2.5) {\n // Mode 2: Bottom (face camera position) - tilts toward camera, pivots at bottom\n // Like a sunflower facing the sun - when viewed from above, nearly horizontal\n let toCamera = uniforms.cameraPosition - entityPos;\n let toCameraDist = length(toCamera);\n\n var right: vec3f;\n var billUp: vec3f;\n\n if (toCameraDist > 0.001) {\n let forward = toCamera / toCameraDist; // Direction toward camera\n // Right = cross(worldUp, forward)\n right = cross(vec3f(0.0, 1.0, 0.0), forward);\n let rightLen = length(right);\n if (rightLen < 0.001) {\n // Camera directly above/below - use camera right as fallback\n right = uniforms.billboardCameraRight;\n billUp = uniforms.billboardCameraUp;\n } else {\n right = right / rightLen;\n // Billboard \"up\" is perpendicular to both forward and right\n billUp = cross(forward, right);\n }\n } else {\n // Camera at entity position - use camera vectors as fallback\n right = uniforms.billboardCameraRight;\n billUp = uniforms.billboardCameraUp;\n }\n\n let offset = right * input.position.x * scaleX + billUp * input.position.y * scaleY;\n billboardWorldPos = entityPos + offset;\n useBillboardWorldPos = true;\n localNormal = vec3f(0.0, 1.0, 0.0); // Normal points up\n\n } else {\n // Mode 3: Horizontal - fixed in world space on XZ plane\n // Uses entity's model matrix rotation (yaw only in practice)\n // Don't modify localPos - let model matrix apply the transform\n // Geometry is already on XZ plane from Geometry.billboardQuad('horizontal')\n localNormal = vec3f(0.0, 1.0, 0.0); // Normal points up\n }\n }\n\n // Apply skinning if enabled\n if (uniforms.skinEnabled > 0.5) {\n // Check if weights sum to something meaningful\n let weightSum = input.weights.x + input.weights.y + input.weights.z + input.weights.w;\n if (weightSum > 0.001) {\n localPos = applySkinning(input.position, input.joints, input.weights);\n prevLocalPos = applySkinningPrev(input.position, input.joints, input.weights);\n localNormal = applySkinningNormal(input.normal, input.joints, input.weights);\n }\n }\n\n // Apply instance transform\n // For billboard modes 1 and 2, we computed worldPos directly (bypassing matrix rotation)\n // For billboard mode 3 and non-billboard, use standard matrix transform\n var worldPos: vec3f;\n if (useBillboardWorldPos) {\n worldPos = billboardWorldPos;\n } else {\n worldPos = (instanceModelMatrix * vec4f(localPos, 1.0)).xyz;\n }\n var viewPos = uniforms.viewMatrix * vec4f(worldPos, 1.0);\n\n // Check if this instance allows rounding (negative radius = no rounding)\n let allowRounding = posRadius.w >= 0.0;\n\n // Position rounding: simulate fixed-point by rounding view-space position\n // Only round X and Y - rounding Z can cause clipping issues when vertices are near camera\n if (allowRounding && uniforms.positionRounding > 0.0) {\n let snap = uniforms.positionRounding;\n viewPos = vec4f(\n floor(viewPos.x / snap) * snap,\n floor(viewPos.y / snap) * snap,\n viewPos.z,\n viewPos.w\n );\n }\n\n var clipPos = uniforms.projectionMatrix * viewPos;\n\n // Compute previous world position (using same instance matrix - static objects)\n // For moving objects, we'd need prevInstanceModelMatrix in the instance buffer\n // For billboards, use same worldPos (billboard faces camera each frame)\n var prevWorldPos: vec3f;\n if (useBillboardWorldPos) {\n prevWorldPos = billboardWorldPos; // Billboard - same as current (TODO: proper motion vectors)\n } else {\n prevWorldPos = (instanceModelMatrix * vec4f(prevLocalPos, 1.0)).xyz;\n }\n let prevClipPos = uniforms.prevViewProjMatrix * vec4f(prevWorldPos, 1.0);\n\n // Apply TAA jitter with distance-based fade\n // Full jitter near camera, fading to 0 at jitterFadeDistance\n let viewDist = -viewPos.z; // Positive distance from camera\n let jitterFade = saturate(1.0 - viewDist / uniforms.jitterFadeDistance);\n\n // Convert pixel offset to clip space and apply with fade\n // In clip space, 1 pixel = 2/screenSize (since clip space is -1 to 1)\n let jitterClip = uniforms.jitterOffset * 2.0 / uniforms.screenSize * jitterFade;\n clipPos.x += jitterClip.x * clipPos.w;\n clipPos.y += jitterClip.y * clipPos.w;\n\n // Pixel rounding: snap vertices to pixel grid for retro/PSX aesthetic\n // pixelRounding value = grid size in pixels (1.0 = every pixel, 2.0 = every 2 pixels, etc.)\n if (allowRounding && uniforms.pixelRounding > 0.5) {\n let gridSize = uniforms.pixelRounding; // Grid size in pixels\n let ndc = clipPos.xy / clipPos.w;\n\n // Convert NDC (-1..1) to pixel coordinates (0..screenSize)\n let pixelCoords = (ndc + 1.0) * 0.5 * uniforms.screenSize;\n\n // Snap to coarse grid (every gridSize pixels)\n var snappedPixel = floor(pixelCoords / gridSize) * gridSize;\n\n // Expansion to convert gaps to overlaps (push vertices slightly outward from screen center)\n if (uniforms.pixelExpansion > 0.0) {\n let screenCenter = uniforms.screenSize * 0.5;\n let fromCenter = snappedPixel - screenCenter;\n snappedPixel += sign(fromCenter) * uniforms.pixelExpansion;\n }\n\n // Convert back to NDC\n let snappedNDC = (snappedPixel / uniforms.screenSize) * 2.0 - 1.0;\n\n // Convert back to clip space\n clipPos.x = snappedNDC.x * clipPos.w;\n clipPos.y = snappedNDC.y * clipPos.w;\n }\n\n output.position = clipPos;\n output.worldPos = worldPos;\n // Apply UV transform for sprite sheets: uv * scale + offset\n output.uv = input.uv * uvTransform.zw + uvTransform.xy;\n output.viewZ = viewDist;\n output.currClipPos = clipPos;\n output.prevClipPos = prevClipPos;\n output.instanceColor = instanceColor;\n\n // For billboard sprites, use entity position Y for clip plane testing\n // In planar reflections, the billboard camera vectors are flipped, which causes\n // worldPos to be computed at wrong Y values. Using entityPos directly is simpler\n // and more reliable - if the entity is above ground, the sprite shows.\n if (uniforms.billboardMode > 0.5) {\n let entityPos = instanceModelMatrix[3].xyz;\n output.anchorY = entityPos.y;\n } else {\n output.anchorY = worldPos.y; // For regular meshes, use fragment world Y\n }\n\n // Apply instance transform to normal\n // For billboard sprites, the normal is already in world space (0, 1, 0)\n if (uniforms.billboardMode > 0.5) {\n output.normal = localNormal; // Already world-space (0, 1, 0) for all billboard modes\n } else {\n let normalMatrix = mat3x3f(\n instanceModelMatrix[0].xyz,\n instanceModelMatrix[1].xyz,\n instanceModelMatrix[2].xyz\n );\n output.normal = normalMatrix * localNormal;\n }\n return output;\n}\n\nstruct GBufferOutput {\n @location(0) albedo: vec4f,\n @location(1) normal: vec4f,\n @location(2) arm: vec4f, // Ambient, Roughness, Metallic\n @location(3) emission: vec4f,\n @location(4) velocity: vec2f, // Motion vectors in pixels\n @builtin(frag_depth) depth: f32, // Linear depth\n}\n\n// Interleaved Gradient Noise for alpha hashing\n// From: http://www.iryoku.com/next-generation-post-processing-in-call-of-duty-advanced-warfare\nfn interleavedGradientNoise(screenPos: vec2f) -> f32 {\n let magic = vec3f(0.06711056, 0.00583715, 52.9829189);\n return fract(magic.z * fract(dot(screenPos, magic.xy)));\n}\n\n// Alternative: simple hash for variety\nfn screenHash(screenPos: vec2f) -> f32 {\n let p = fract(screenPos * vec2f(0.1031, 0.1030));\n let p3 = p.xyx * (p.yxy + 33.33);\n return fract((p3.x + p3.y) * p3.z);\n}\n\n@fragment\nfn fragmentMain(input: VertexOutput, @builtin(front_facing) frontFacing: bool) -> GBufferOutput {\n // Clip plane: discard fragments based on clip plane Y level and direction\n // Used for planar reflections to avoid rendering geometry on the wrong side of the water\n // Direction > 0: discard below clipPlaneY (camera above water, show above-water objects)\n // Direction < 0: discard above clipPlaneY (camera below water, show below-water objects)\n // For billboard sprites (anchorY != worldPos.y), skip clip plane - entity placement handles it\n if (uniforms.clipPlaneEnabled > 0.5 && uniforms.billboardMode < 0.5) {\n let clipDist = (input.worldPos.y - uniforms.clipPlaneY) * uniforms.clipPlaneDirection;\n if (clipDist < 0.0) {\n discard;\n }\n }\n\n // Distance fade: noise-based dithered fade to prevent popping at culling distance\n // Fade from fully visible at distanceFadeStart to invisible at distanceFadeEnd\n if (uniforms.distanceFadeEnd > 0.0) {\n let distToCamera = length(input.worldPos - uniforms.cameraPosition);\n if (distToCamera >= uniforms.distanceFadeEnd) {\n discard; // Beyond fade end - fully invisible\n }\n if (distToCamera > uniforms.distanceFadeStart) {\n // Calculate fade factor: 1.0 at fadeStart, 0.0 at fadeEnd\n let fadeRange = uniforms.distanceFadeEnd - uniforms.distanceFadeStart;\n let fadeFactor = 1.0 - (distToCamera - uniforms.distanceFadeStart) / fadeRange;\n // Use noise-based dithering for smooth fade\n let noise = sampleNoise(input.position.xy);\n if (fadeFactor < noise) {\n discard; // Dithered fade out\n }\n }\n }\n\n var output: GBufferOutput;\n\n // Write albedo with mip offset\n let mipBias = uniforms.mipBias; // Offset mip level\n output.albedo = textureSampleBias(albedoTexture, albedoSampler, input.uv, mipBias);\n\n // Apply instance color tint (for sprites)\n output.albedo = output.albedo * input.instanceColor;\n\n // Luminance to alpha: derive alpha from base color brightness (for old game assets where black=transparent)\n // Only pure black (luminance < 1/255) becomes transparent - HARD discard, no noise\n if (uniforms.luminanceToAlpha > 0.5) {\n let luminance = dot(output.albedo.rgb, vec3f(0.299, 0.587, 0.114));\n if (luminance < 0.004) {\n discard; // Hard discard for pure black - no noise dithering\n }\n output.albedo.a = 1.0; // Everything else is fully opaque\n }\n\n // Alpha hashing: screen-space dithered alpha test (only for non-luminanceToAlpha materials)\n // Uses noise texture (blue noise or bayer) for stable, temporally coherent cutout\n if (uniforms.alphaHashEnabled > 0.5 && uniforms.luminanceToAlpha < 0.5) {\n let alpha = output.albedo.a * uniforms.alphaHashScale;\n let noise = sampleNoise(input.position.xy);\n\n // Hard cutoff for very transparent areas (avoid random dots)\n if (alpha < 0.5) {\n discard;\n }\n // Hash only the semi-transparent edge (0.5 to 1.0 range)\n // Remap alpha from [0.5, 1.0] to [0.0, 1.0] for noise comparison\n let remappedAlpha = (alpha - 0.5) * 2.0;\n if (remappedAlpha < noise) {\n discard;\n }\n }\n\n\n // Write world-space normal\n\n // Sample normal map and convert from [0,1] to [-1,1] range\n let nsample = textureSampleBias(normalTexture, normalSampler, input.uv, mipBias).rgb;\n let tangentNormal = normalize(nsample * 2.0 - 1.0);\n\n // Calculate cotangent frame from screen-space derivatives (runtime tangent generation)\n // Based on: http://www.thetenthplanet.de/archives/1180\n let dPdx = dpdx(input.worldPos);\n let dPdy = dpdy(input.worldPos);\n let dUVdx = dpdx(input.uv);\n let dUVdy = dpdy(input.uv);\n\n let N = normalize(input.normal);\n\n // Get edge vectors perpendicular to N\n let dp2perp = cross(dPdy, N);\n let dp1perp = cross(N, dPdx);\n\n // Construct tangent and bitangent\n // Negate T to match glTF/OpenGL tangent space convention\n var T = -(dp2perp * dUVdx.x + dp1perp * dUVdy.x);\n var B = dp2perp * dUVdx.y + dp1perp * dUVdy.y;\n\n // Scale-invariant normalization\n let invmax = inverseSqrt(max(dot(T, T), dot(B, B)));\n T = T * invmax;\n B = B * invmax;\n\n // Handle degenerate cases (no valid UVs)\n if (length(T) < 0.001 || length(B) < 0.001) {\n let refVec = select(vec3f(0.0, 1.0, 0.0), vec3f(1.0, 0.0, 0.0), abs(N.y) > 0.9);\n T = normalize(cross(N, refVec));\n B = cross(N, T);\n }\n\n // For double-sided materials: flip tangent and bitangent for back faces\n if (!frontFacing) {\n T = -T;\n B = -B;\n }\n\n let TBN = mat3x3f(T, B, N);\n\n // Transform tangent space normal to world space\n let normal = normalize(TBN * tangentNormal);\n // Store world Y position in .w for planar reflection distance fade\n output.normal = vec4f(normal, input.worldPos.y);\n\n // Write ARM values (Ambient, Roughness, Metallic, SpecularBoost)\n var ambient = textureSampleBias(ambientTexture, ambientSampler, input.uv, mipBias).rgb;\n if (ambient.r < 0.04) {\n ambient.r = 1.0;\n }\n let rm = textureSampleBias(rmTexture, rmSampler, input.uv, mipBias).rgb;\n output.arm = vec4f(ambient.r, rm.g, rm.b, uniforms.specularBoost);\n\n // Write emission\n output.emission = textureSampleBias(emissionTexture, emissionSampler, input.uv, mipBias) * uniforms.emissionFactor;\n\n // Compute motion vectors (velocity) in pixels\n // Convert clip positions to NDC\n let currNDC = input.currClipPos.xy / input.currClipPos.w;\n let prevNDC = input.prevClipPos.xy / input.prevClipPos.w;\n\n // Convert NDC difference to pixel velocity\n // NDC is -1 to 1, screen is 0 to screenSize\n // velocity = (currNDC - prevNDC) * screenSize / 2\n let velocityNDC = currNDC - prevNDC;\n output.velocity = velocityNDC * uniforms.screenSize * 0.5;\n\n // Linear depth: maps [near, far] to [0, 1]\n let near = uniforms.near;\n let far = uniforms.far;\n let z = input.viewZ;\n output.depth = (z - near) / (far - near);\n\n return output;\n}\n","import { BasePass } from \"./BasePass.js\"\nimport { Pipeline } from \"../../Pipeline.js\"\nimport { Texture } from \"../../Texture.js\"\nimport { Frustum } from \"../../utils/Frustum.js\"\nimport { transformBoundingSphere } from \"../../utils/BoundingSphere.js\"\n\nimport geometryWGSL from \"../shaders/geometry.wgsl\"\n\n/**\n * GBuffer textures container\n */\nclass GBuffer {\n constructor() {\n this.isGBuffer = true\n this.albedo = null // rgba8unorm - Base color\n this.normal = null // rgba16float - World-space normals\n this.arm = null // rgba8unorm - Ambient Occlusion, Roughness, Metallic\n this.emission = null // rgba16float - Emissive color\n this.velocity = null // rg16float - Motion vectors (screen-space pixels)\n this.depth = null // depth32float - Scene depth\n }\n\n static async create(engine, width, height) {\n const gbuffer = new GBuffer()\n gbuffer.albedo = await Texture.renderTarget(engine, 'rgba8unorm', width, height)\n gbuffer.normal = await Texture.renderTarget(engine, 'rgba16float', width, height)\n gbuffer.arm = await Texture.renderTarget(engine, 'rgba8unorm', width, height)\n gbuffer.emission = await Texture.renderTarget(engine, 'rgba16float', width, height)\n gbuffer.velocity = await Texture.renderTarget(engine, 'rg16float', width, height)\n gbuffer.depth = await Texture.depth(engine, width, height)\n gbuffer.width = width\n gbuffer.height = height\n return gbuffer\n }\n\n getTargets() {\n return [\n { format: \"rgba8unorm\" },\n { format: \"rgba16float\" },\n { format: \"rgba8unorm\" },\n { format: \"rgba16float\" },\n { format: \"rg16float\" },\n ]\n }\n\n getColorAttachments() {\n return [\n {\n view: this.albedo.view,\n clearValue: { r: 0.0, g: 0.0, b: 0.0, a: 1.0 },\n loadOp: 'clear',\n storeOp: 'store',\n },\n {\n view: this.normal.view,\n clearValue: { r: 0.0, g: 0.0, b: 0.0, a: 1.0 },\n loadOp: 'clear',\n storeOp: 'store',\n },\n {\n view: this.arm.view,\n clearValue: { r: 0.0, g: 0.0, b: 0.0, a: 1.0 },\n loadOp: 'clear',\n storeOp: 'store',\n },\n {\n view: this.emission.view,\n clearValue: { r: 0.0, g: 0.0, b: 0.0, a: 1.0 },\n loadOp: 'clear',\n storeOp: 'store',\n },\n {\n view: this.velocity.view,\n clearValue: { r: 0.0, g: 0.0, b: 0.0, a: 0.0 },\n loadOp: 'clear',\n storeOp: 'store',\n },\n ]\n }\n\n getDepthStencilAttachment() {\n return {\n view: this.depth.view,\n depthClearValue: 1.0,\n depthLoadOp: 'clear',\n depthStoreOp: 'store',\n }\n }\n}\n\n/**\n * GBufferPass - Renders scene geometry to GBuffer textures\n *\n * Pass 4 in the 7-pass pipeline.\n * Outputs: Albedo, Normal, ARM, Emission, Depth\n */\nclass GBufferPass extends BasePass {\n constructor(engine = null) {\n super('GBuffer', engine)\n\n this.gbuffer = null\n this.pipelines = new Map() // materialId -> pipeline (ready)\n this.skinnedPipelines = new Map() // materialId -> skinned pipeline (ready)\n this.pendingPipelines = new Map() // materialId -> Promise<pipeline> (compiling)\n\n // Clip plane for planar reflections\n this.clipPlaneY = 0\n this.clipPlaneEnabled = false\n this.clipPlaneDirection = 1.0 // 1.0 = discard below, -1.0 = discard above\n\n // Distance fade for preventing object popping at culling distance\n this.distanceFadeStart = 0 // Distance where fade begins\n this.distanceFadeEnd = 0 // Distance where fade completes (0 = disabled)\n\n // Noise texture for alpha hashing\n this.noiseTexture = null\n this.noiseSize = 64\n this.noiseAnimated = true\n\n // HiZ pass reference for occlusion culling of legacy meshes\n this.hizPass = null\n\n // Frustum for legacy mesh culling\n this.frustum = new Frustum()\n\n // Billboard camera vectors (extracted from view matrix)\n this._billboardCameraRight = [1, 0, 0]\n this._billboardCameraUp = [0, 1, 0]\n this._billboardCameraForward = [0, 0, -1]\n\n // Culling stats for legacy meshes\n this.legacyCullingStats = {\n total: 0,\n rendered: 0,\n culledByFrustum: 0,\n culledByDistance: 0,\n culledByOcclusion: 0\n }\n }\n\n /**\n * Set the HiZ pass for occlusion culling of legacy meshes\n * @param {HiZPass} hizPass - The HiZ pass instance\n */\n setHiZPass(hizPass) {\n this.hizPass = hizPass\n }\n\n /**\n * Test if a legacy mesh should be culled (per-instance occlusion culling)\n * @param {Mesh} mesh - The mesh to test\n * @param {Camera} camera - Current camera\n * @param {boolean} canCull - Whether frustum/occlusion culling is available\n * @returns {string|null} - Reason for culling or null if visible\n */\n _shouldCullLegacyMesh(mesh, camera, canCull) {\n // Skip culling if disabled or no HiZ pass\n if (!canCull || !this.hizPass || !camera) {\n return null\n }\n\n // Skip for entity-managed meshes - they're already culled by CullingSystem\n // Only perform per-mesh occlusion culling for static (non-entity-managed) meshes\n if (!mesh.static) {\n return null\n }\n\n const occlusionEnabled = this.settings?.occlusionCulling?.enabled\n if (!occlusionEnabled) {\n return null\n }\n\n // Get local bounding sphere from geometry\n const localBsphere = mesh.geometry?.getBoundingSphere?.()\n if (!localBsphere || localBsphere.radius <= 0) {\n this.legacyCullingStats.skippedNoBsphere = (this.legacyCullingStats.skippedNoBsphere || 0) + 1\n return null // No valid bsphere, don't cull\n }\n\n const instanceCount = mesh.geometry?.instanceCount || 0\n if (instanceCount === 0) {\n return null // No instances to test\n }\n\n const instanceData = mesh.geometry?.instanceData\n if (!instanceData) {\n return null // No instance data\n }\n\n // Test each instance - if ANY is visible, mesh is visible\n // Instance data layout: 20 floats per instance (4x4 matrix + 4 extra)\n const floatsPerInstance = 20\n let allOccluded = true\n\n // Copy camera data to avoid mutable reference issues\n const cameraPos = [camera.position[0], camera.position[1], camera.position[2]]\n const viewProj = camera.viewProj\n const near = camera.near\n const far = camera.far\n\n for (let i = 0; i < instanceCount; i++) {\n const offset = i * floatsPerInstance\n\n // Extract 4x4 matrix from instance data\n const matrix = instanceData.subarray(offset, offset + 16)\n\n // Transform local bsphere by instance matrix\n const worldBsphere = transformBoundingSphere(localBsphere, matrix)\n\n // Test against HiZ - if visible, mesh is visible\n const occluded = this.hizPass.testSphereOcclusion(\n worldBsphere,\n viewProj,\n near,\n far,\n cameraPos\n )\n\n if (!occluded) {\n allOccluded = false\n break // At least one instance visible, no need to test more\n }\n }\n\n return allOccluded ? 'occlusion' : null\n }\n\n /**\n * Extract camera vectors from view matrix for billboarding\n * The view matrix transforms world to view space. Camera basis vectors:\n * - Right: first row of view matrix\n * - Up: second row of view matrix\n * - Forward: negative third row (camera looks down -Z in view space)\n * @param {Float32Array|Array} viewMatrix - 4x4 view matrix\n */\n _extractCameraVectors(viewMatrix) {\n // View matrix is column-major, so row vectors are at indices:\n // Row 0 (right): [0], [4], [8]\n // Row 1 (up): [1], [5], [9]\n // Row 2 (forward): [2], [6], [10] (negated because -Z is forward)\n this._billboardCameraRight[0] = viewMatrix[0]\n this._billboardCameraRight[1] = viewMatrix[4]\n this._billboardCameraRight[2] = viewMatrix[8]\n\n this._billboardCameraUp[0] = viewMatrix[1]\n this._billboardCameraUp[1] = viewMatrix[5]\n this._billboardCameraUp[2] = viewMatrix[9]\n\n // Negate Z row for forward direction\n this._billboardCameraForward[0] = -viewMatrix[2]\n this._billboardCameraForward[1] = -viewMatrix[6]\n this._billboardCameraForward[2] = -viewMatrix[10]\n }\n\n /**\n * Get billboard mode from material\n * @param {Material} material - Material with optional billboardMode uniform\n * @returns {number} Billboard mode: 0=none, 1=center, 2=bottom, 3=horizontal\n */\n _getBillboardMode(material) {\n const mode = material?.uniforms?.billboardMode\n if (typeof mode === 'number') return mode\n if (mode === 'center') return 1\n if (mode === 'bottom') return 2\n if (mode === 'horizontal') return 3\n return 0\n }\n\n /**\n * Set the noise texture for alpha hashing\n * @param {Texture} noise - Noise texture (blue noise or bayer dither)\n * @param {number} size - Texture size\n * @param {boolean} animated - Whether to animate noise offset each frame\n */\n setNoise(noise, size = 64, animated = true) {\n this.noiseTexture = noise\n this.noiseSize = size\n this.noiseAnimated = animated\n // Mark all pipelines for rebuild (they need noise texture binding)\n this.pipelines.clear()\n this.skinnedPipelines.clear()\n }\n\n async _init() {\n const { canvas } = this.engine\n this.gbuffer = await GBuffer.create(this.engine, canvas.width, canvas.height)\n }\n\n /**\n * Get pipeline key for a mesh\n */\n _getPipelineKey(mesh) {\n const isSkinned = mesh.hasSkin && mesh.skin\n const meshId = mesh.uid || mesh.geometry?.uid || 'default'\n const forceEmissive = mesh.material?.forceEmissive ? '_emissive' : ''\n const doubleSided = mesh.material?.doubleSided ? '_dbl' : ''\n return `${mesh.material.uid}_${meshId}${isSkinned ? '_skinned' : ''}${forceEmissive}${doubleSided}`\n }\n\n /**\n * Check if pipeline is ready for a mesh (non-blocking)\n * @param {Mesh} mesh - The mesh to check\n * @returns {Pipeline|null} The pipeline if ready, null if still compiling\n */\n _getPipelineIfReady(mesh) {\n const isSkinned = mesh.hasSkin && mesh.skin\n const pipelinesMap = isSkinned ? this.skinnedPipelines : this.pipelines\n const key = this._getPipelineKey(mesh)\n return pipelinesMap.get(key) || null\n }\n\n /**\n * Check if pipeline is ready AND warmed up (stable for rendering)\n * @param {Mesh} mesh - The mesh to check\n * @returns {boolean} True if pipeline is ready and warmed up\n */\n isPipelineStable(mesh) {\n const pipeline = this._getPipelineIfReady(mesh)\n return pipeline && (!pipeline._warmupFrames || pipeline._warmupFrames <= 0)\n }\n\n /**\n * Start pipeline creation in background (non-blocking)\n * @param {Mesh} mesh - The mesh to create pipeline for\n */\n _startPipelineCreation(mesh) {\n const isSkinned = mesh.hasSkin && mesh.skin\n const pipelinesMap = isSkinned ? this.skinnedPipelines : this.pipelines\n const key = this._getPipelineKey(mesh)\n\n // Already ready or already pending\n if (pipelinesMap.has(key) || this.pendingPipelines.has(key)) {\n return\n }\n\n // Start async compilation without awaiting\n const pipelinePromise = Pipeline.create(this.engine, {\n label: `gbuffer-${key}`,\n wgslSource: geometryWGSL,\n geometry: mesh.geometry,\n textures: mesh.material.textures,\n renderTarget: this.gbuffer,\n skin: isSkinned ? mesh.skin : null,\n noiseTexture: this.noiseTexture,\n doubleSided: mesh.material?.doubleSided ?? false,\n }).then(pipeline => {\n // Move from pending to ready\n this.pendingPipelines.delete(key)\n // Mark as warming up - needs 2 frames to stabilize\n pipeline._warmupFrames = 2\n pipelinesMap.set(key, pipeline)\n return pipeline\n }).catch(err => {\n console.error(`Failed to create pipeline for ${key}:`, err)\n this.pendingPipelines.delete(key)\n return null\n })\n\n this.pendingPipelines.set(key, pipelinePromise)\n }\n\n /**\n * Get or create pipeline for a mesh (blocking - for batch system)\n * @param {Mesh} mesh - The mesh to render\n * @returns {Pipeline} The pipeline for this mesh\n */\n async _getOrCreatePipeline(mesh) {\n const isSkinned = mesh.hasSkin && mesh.skin\n const pipelinesMap = isSkinned ? this.skinnedPipelines : this.pipelines\n const key = this._getPipelineKey(mesh)\n\n // Return if already ready\n if (pipelinesMap.has(key)) {\n return pipelinesMap.get(key)\n }\n\n // Wait for pending if exists\n if (this.pendingPipelines.has(key)) {\n return await this.pendingPipelines.get(key)\n }\n\n // Create new pipeline\n const pipeline = await Pipeline.create(this.engine, {\n label: `gbuffer-${key}`,\n wgslSource: geometryWGSL,\n geometry: mesh.geometry,\n textures: mesh.material.textures,\n renderTarget: this.gbuffer,\n skin: isSkinned ? mesh.skin : null,\n noiseTexture: this.noiseTexture,\n doubleSided: mesh.material?.doubleSided ?? false,\n })\n // Mark as warming up - needs 2 frames to stabilize\n pipeline._warmupFrames = 2\n pipelinesMap.set(key, pipeline)\n return pipeline\n }\n\n /**\n * Execute GBuffer pass\n *\n * @param {Object} context\n * @param {Camera} context.camera - Current camera\n * @param {Object} context.meshes - Legacy mesh dictionary (for backward compatibility)\n * @param {Map} context.batches - Instance batches from InstanceManager (new system)\n * @param {number} context.dt - Delta time for animation\n * @param {HistoryBufferManager} context.historyManager - History buffer manager for motion vectors\n */\n async _execute(context) {\n const { device, canvas, options, stats } = this.engine\n const { camera, meshes, batches, dt = 0, historyManager } = context\n\n // Get previous frame camera matrices for motion vectors\n // If no valid history, use current viewProj (zero motion)\n const prevData = historyManager?.getPrevious()\n const prevViewProjMatrix = prevData?.hasValidHistory\n ? prevData.viewProj\n : camera.viewProj\n\n // Get settings from engine (with fallbacks)\n const emissionFactor = this.settings?.environment?.emissionFactor ?? [1.0, 1.0, 1.0, 4.0]\n const mipBias = this.settings?.rendering?.mipBias ?? options.mipBias ?? 0\n\n // Use absolute time for scene-loaded skins (same as entity animations)\n // This ensures consistent timing regardless of frame rate fluctuations\n const animationSpeed = this.settings?.animation?.speed ?? 1.0\n const globalAnimTime = (performance.now() / 1000) * animationSpeed\n\n stats.drawCalls = 0\n stats.triangles = 0\n\n // Update camera\n camera.aspect = canvas.width / canvas.height\n camera.updateMatrix()\n camera.updateView()\n\n // Extract camera vectors for billboarding\n this._extractCameraVectors(camera.view)\n\n let commandEncoder = null\n let passEncoder = null\n\n // Track which skins have been updated this frame (avoids duplicate updates)\n const updatedSkins = new Set()\n\n // New system: render batches from InstanceManager\n if (batches && batches.size > 0) {\n for (const [modelId, batch] of batches) {\n const mesh = batch.mesh\n if (!mesh) continue\n\n // Update skin animation if skinned (skip if externally managed or already updated)\n // Use absolute time (same as entities) for consistent animation speed\n if (batch.hasSkin && batch.skin && !batch.skin.externallyManaged && !updatedSkins.has(batch.skin)) {\n // Track animation start time per skin for absolute timing\n if (batch.skin._animStartTime === undefined) {\n batch.skin._animStartTime = globalAnimTime\n }\n const skinAnimTime = globalAnimTime - batch.skin._animStartTime\n batch.skin.updateAtTime(skinAnimTime)\n updatedSkins.add(batch.skin)\n }\n\n const pipeline = await this._getOrCreatePipeline(mesh)\n\n // On first render after pipeline creation, force skin update to ensure proper state\n if (pipeline._warmupFrames > 0) {\n pipeline._warmupFrames--\n if (batch.hasSkin && batch.skin) {\n // Force immediate skin update to avoid stale joint matrices\n batch.skin.update(0)\n }\n }\n\n // Update bind group if skinned\n if (batch.hasSkin && batch.skin) {\n pipeline.updateBindGroupForSkin(batch.skin)\n }\n\n // Update geometry buffers\n mesh.geometry.update()\n\n // Set uniforms\n const jitterFadeDistance = this.settings?.rendering?.jitterFadeDistance ?? 30.0\n // Get alpha hash settings (per-material or global)\n const alphaHashEnabled = mesh.material?.alphaHash ?? this.settings?.rendering?.alphaHash ?? false\n const alphaHashScale = mesh.material?.alphaHashScale ?? this.settings?.rendering?.alphaHashScale ?? 1.0\n const luminanceToAlpha = mesh.material?.luminanceToAlpha ?? this.settings?.rendering?.luminanceToAlpha ?? false\n\n pipeline.uniformValues.set({\n viewMatrix: camera.view,\n projectionMatrix: camera.proj,\n prevViewProjMatrix: prevViewProjMatrix,\n mipBias: mipBias,\n skinEnabled: batch.hasSkin ? 1.0 : 0.0,\n numJoints: batch.hasSkin && batch.skin ? batch.skin.numJoints : 0,\n near: camera.near || 0.05,\n far: camera.far || 1000,\n jitterFadeDistance: jitterFadeDistance,\n jitterOffset: camera.jitterOffset || [0, 0],\n screenSize: camera.screenSize || [canvas.width, canvas.height],\n emissionFactor: emissionFactor,\n clipPlaneY: this.clipPlaneY,\n clipPlaneEnabled: this.clipPlaneEnabled ? 1.0 : 0.0,\n clipPlaneDirection: this.clipPlaneDirection,\n pixelRounding: this.settings?.rendering?.pixelRounding || 0.0,\n pixelExpansion: this.settings?.rendering?.pixelExpansion ?? 0.05,\n positionRounding: this.settings?.rendering?.positionRounding || 0.0,\n alphaHashEnabled: alphaHashEnabled ? 1.0 : 0.0,\n alphaHashScale: alphaHashScale,\n luminanceToAlpha: luminanceToAlpha ? 1.0 : 0.0,\n noiseSize: this.noiseSize,\n // Always use static noise for alpha hash to avoid shimmer on cutout edges\n noiseOffsetX: 0,\n noiseOffsetY: 0,\n cameraPosition: camera.position,\n distanceFadeStart: this.distanceFadeStart,\n distanceFadeEnd: this.distanceFadeEnd,\n // Billboard uniforms\n billboardMode: this._getBillboardMode(mesh.material),\n billboardCameraRight: this._billboardCameraRight,\n billboardCameraUp: this._billboardCameraUp,\n billboardCameraForward: this._billboardCameraForward,\n // Per-material specular boost (0-1, default 0 = disabled)\n specularBoost: mesh.material?.specularBoost ?? 0,\n })\n\n // Render\n if (commandEncoder) {\n pipeline.render({\n commandEncoder,\n passEncoder,\n dontFinish: true,\n instanceBuffer: batch.buffer?.gpuBuffer,\n instanceCount: batch.instanceCount\n })\n } else {\n const result = pipeline.render({\n dontFinish: true,\n instanceBuffer: batch.buffer?.gpuBuffer,\n instanceCount: batch.instanceCount\n })\n commandEncoder = result.commandEncoder\n passEncoder = result.passEncoder\n }\n }\n }\n\n // Legacy system: render individual meshes with progressive loading\n // Meshes appear as their shaders compile - no blocking wait\n let totalInstances = 0\n\n if (meshes && Object.keys(meshes).length > 0) {\n // Update frustum for legacy mesh culling (only if camera has required properties)\n const canCull = camera.view && camera.proj && camera.position && camera.direction\n if (canCull) {\n const fovRadians = (camera.fov || 60) * (Math.PI / 180)\n this.frustum.update(\n camera.view,\n camera.proj,\n camera.position,\n camera.direction,\n fovRadians,\n camera.aspect || (canvas.width / canvas.height),\n camera.near || 0.05,\n camera.far || 1000,\n canvas.width,\n canvas.height\n )\n }\n\n // Reset culling stats\n this.legacyCullingStats.total = 0\n this.legacyCullingStats.rendered = 0\n this.legacyCullingStats.culledByFrustum = 0\n this.legacyCullingStats.culledByDistance = 0\n this.legacyCullingStats.culledByOcclusion = 0\n this.legacyCullingStats.skippedNoBsphere = 0\n\n // Start pipeline creation for ALL meshes (non-blocking)\n // This kicks off parallel shader compilation in the background\n for (const name in meshes) {\n const mesh = meshes[name]\n if (!mesh || !mesh.geometry || !mesh.material) continue\n this._startPipelineCreation(mesh)\n }\n\n // Render only meshes with READY pipelines (others will appear next frame)\n for (const name in meshes) {\n const mesh = meshes[name]\n const instanceCount = mesh.geometry?.instanceCount || 0\n totalInstances += instanceCount\n\n // Skip meshes with no instances\n if (instanceCount === 0) continue\n\n this.legacyCullingStats.total++\n\n // Apply culling to legacy meshes (frustum, distance, occlusion)\n const cullReason = this._shouldCullLegacyMesh(mesh, camera, canCull)\n if (cullReason) {\n if (cullReason === 'frustum') this.legacyCullingStats.culledByFrustum++\n else if (cullReason === 'distance') this.legacyCullingStats.culledByDistance++\n else if (cullReason === 'occlusion') this.legacyCullingStats.culledByOcclusion++\n continue\n }\n\n // Check if pipeline is ready (non-blocking)\n const pipeline = this._getPipelineIfReady(mesh)\n if (!pipeline) continue // Still compiling, skip for now\n\n // Track warmup frames (pipeline just became ready)\n if (pipeline._warmupFrames > 0) {\n pipeline._warmupFrames--\n }\n\n this.legacyCullingStats.rendered++\n\n // Update skin animation using absolute time (skip if externally managed or already updated)\n if (mesh.skin && mesh.hasSkin && !mesh.skin.externallyManaged && !updatedSkins.has(mesh.skin)) {\n // Track animation start time per skin for absolute timing\n if (mesh.skin._animStartTime === undefined) {\n mesh.skin._animStartTime = globalAnimTime\n }\n const skinAnimTime = globalAnimTime - mesh.skin._animStartTime\n mesh.skin.updateAtTime(skinAnimTime)\n updatedSkins.add(mesh.skin)\n }\n\n // Ensure pipeline geometry matches mesh geometry\n if (pipeline.geometry !== mesh.geometry) {\n pipeline.geometry = mesh.geometry\n }\n\n // Update bind group if skinned\n if (mesh.hasSkin && mesh.skin) {\n pipeline.updateBindGroupForSkin(mesh.skin)\n }\n\n // Update geometry buffers\n mesh.geometry.update()\n\n // Set uniforms\n const jitterFadeDist = this.settings?.rendering?.jitterFadeDistance ?? 30.0\n // Get alpha hash settings - check mesh material first, then global setting\n const meshAlphaHash = mesh.material?.alphaHash ?? mesh.alphaHash\n const alphaHashEnabled = meshAlphaHash ?? this.settings?.rendering?.alphaHash ?? false\n const alphaHashScale = mesh.material?.alphaHashScale ?? this.settings?.rendering?.alphaHashScale ?? 1.0\n const luminanceToAlpha = mesh.material?.luminanceToAlpha ?? this.settings?.rendering?.luminanceToAlpha ?? false\n\n pipeline.uniformValues.set({\n viewMatrix: camera.view,\n projectionMatrix: camera.proj,\n prevViewProjMatrix: prevViewProjMatrix,\n mipBias: mipBias,\n skinEnabled: mesh.hasSkin ? 1.0 : 0.0,\n numJoints: mesh.hasSkin && mesh.skin ? mesh.skin.numJoints : 0,\n near: camera.near || 0.05,\n far: camera.far || 1000,\n jitterFadeDistance: jitterFadeDist,\n jitterOffset: camera.jitterOffset || [0, 0],\n screenSize: camera.screenSize || [canvas.width, canvas.height],\n emissionFactor: emissionFactor,\n clipPlaneY: this.clipPlaneY,\n clipPlaneEnabled: this.clipPlaneEnabled ? 1.0 : 0.0,\n clipPlaneDirection: this.clipPlaneDirection,\n pixelRounding: this.settings?.rendering?.pixelRounding || 0.0,\n pixelExpansion: this.settings?.rendering?.pixelExpansion ?? 0.05,\n positionRounding: this.settings?.rendering?.positionRounding || 0.0,\n alphaHashEnabled: alphaHashEnabled ? 1.0 : 0.0,\n alphaHashScale: alphaHashScale,\n luminanceToAlpha: luminanceToAlpha ? 1.0 : 0.0,\n noiseSize: this.noiseSize,\n // Always use static noise for alpha hash to avoid shimmer on cutout edges\n noiseOffsetX: 0,\n noiseOffsetY: 0,\n cameraPosition: camera.position,\n distanceFadeStart: this.distanceFadeStart,\n distanceFadeEnd: this.distanceFadeEnd,\n // Billboard uniforms\n billboardMode: this._getBillboardMode(mesh.material),\n billboardCameraRight: this._billboardCameraRight,\n billboardCameraUp: this._billboardCameraUp,\n billboardCameraForward: this._billboardCameraForward,\n // Per-material specular boost (0-1, default 0 = disabled)\n specularBoost: mesh.material?.specularBoost ?? 0,\n })\n\n // Render\n if (commandEncoder) {\n pipeline.render({ commandEncoder, passEncoder, dontFinish: true })\n } else {\n const result = pipeline.render({ dontFinish: true })\n commandEncoder = result.commandEncoder\n passEncoder = result.passEncoder\n }\n }\n }\n\n // Finish the pass\n if (passEncoder && commandEncoder) {\n passEncoder.end()\n device.queue.submit([commandEncoder.finish()])\n }\n }\n\n async _resize(width, height) {\n // Recreate GBuffer at new size\n this.gbuffer = await GBuffer.create(this.engine, width, height)\n\n // Clear pipeline caches (they reference old GBuffer)\n this.pipelines.clear()\n this.skinnedPipelines.clear()\n }\n\n _destroy() {\n this.pipelines.clear()\n this.skinnedPipelines.clear()\n this.gbuffer = null\n }\n\n /**\n * Get the GBuffer for use by subsequent passes\n */\n getGBuffer() {\n return this.gbuffer\n }\n}\n\nexport { GBuffer, GBufferPass }\n","const PI = 3.14159;\nconst MAX_LIGHTS = 768;\nconst CASCADE_COUNT = 3;\nconst MAX_LIGHTS_PER_TILE = 256;\n\nstruct VertexOutput {\n @builtin(position) position: vec4<f32>,\n @location(0) uv: vec2<f32>,\n}\n\nconst MAX_SPOT_SHADOWS = 16;\n\nstruct Light {\n enabled: u32,\n position: vec3f,\n color: vec4f,\n direction: vec3f,\n geom: vec4f, // x = radius, y = inner cone, z = outer cone\n shadowIndex: i32, // -1 if no shadow, 0-7 for spot shadow slot\n}\n\nstruct Uniforms {\n inverseViewProjection: mat4x4<f32>,\n inverseProjection: mat4x4<f32>, // For logarithmic depth reconstruction\n inverseView: mat4x4<f32>, // For logarithmic depth reconstruction\n cameraPosition: vec3f,\n canvasSize: vec2f,\n lightDir: vec3f,\n lightColor: vec4f,\n ambientColor: vec4f,\n environmentParams: vec4f, // x = diffuse level, y = specular level, z = env mip count, a = exposure\n shadowParams: vec4f, // x = bias, y = normalBias, z = shadow strength, w = shadow map size\n cascadeSizes: vec4f, // x, y, z = cascade half-widths in meters\n tileParams: vec4u, // x = tile size, y = tile count X, z = max lights per tile, w = actual light count\n noiseParams: vec4f, // x = noise texture size, y = noise offset X, z = noise offset Y, w = env encoding (0=equirect, 1=octahedral)\n cameraParams: vec4f, // x = near, y = far, z = reflection mode (flip env Y), w = direct specular multiplier\n specularBoost: vec4f, // x = intensity, y = roughness cutoff, z = unused, w = unused\n}\n\n// Hard-coded spot shadow parameters (to avoid uniform buffer alignment issues)\nconst SPOT_ATLAS_WIDTH: f32 = 2048.0;\nconst SPOT_ATLAS_HEIGHT: f32 = 2048.0;\nconst SPOT_TILE_SIZE: f32 = 512.0;\nconst SPOT_TILES_PER_ROW: i32 = 4;\nconst SPOT_FADE_START: f32 = 25.0;\nconst SPOT_MAX_DISTANCE: f32 = 30.0;\nconst SPOT_MIN_SHADOW: f32 = 0.5;\n\n// Storage buffer for spotlight matrices (avoids uniform buffer size limits)\nstruct SpotShadowMatrices {\n matrices: array<mat4x4<f32>, MAX_SPOT_SHADOWS>,\n}\n\n// Storage buffer for cascade matrices\nstruct CascadeMatrices {\n matrices: array<mat4x4<f32>, CASCADE_COUNT>,\n}\n\n\n@group(0) @binding(0) var<uniform> uniforms: Uniforms;\n@group(0) @binding(12) var<storage, read> spotMatrices: SpotShadowMatrices;\n@group(0) @binding(13) var<storage, read> cascadeMatrices: CascadeMatrices;\n@group(0) @binding(14) var<storage, read> tileLightIndices: array<u32>;\n@group(0) @binding(15) var<storage, read> lights: array<Light, MAX_LIGHTS>;\n@group(0) @binding(1) var gAlbedo: texture_2d<f32>;\n@group(0) @binding(2) var gNormal: texture_2d<f32>;\n@group(0) @binding(3) var gArm: texture_2d<f32>;\n@group(0) @binding(4) var gEmission: texture_2d<f32>;\n@group(0) @binding(5) var gDepth: texture_depth_2d;\n@group(0) @binding(6) var env: texture_2d<f32>;\n@group(0) @binding(7) var envSampler: sampler;\n@group(0) @binding(8) var shadowMapArray: texture_depth_2d_array;\n@group(0) @binding(9) var shadowSampler: sampler_comparison;\n@group(0) @binding(10) var spotShadowAtlas: texture_depth_2d;\n@group(0) @binding(11) var spotShadowSampler: sampler_comparison;\n@group(0) @binding(16) var noiseTexture: texture_2d<f32>;\n@group(0) @binding(17) var noiseSampler: sampler;\n@group(0) @binding(18) var aoTexture: texture_2d<f32>;\n\nfn SphToUV(n: vec3<f32>) -> vec2<f32> {\n var uv: vec2<f32>;\n\n uv.x = atan2(-n.x, n.z);\n uv.x = (uv.x + PI / 2.0) / (PI * 2.0) + PI * (28.670 / 360.0);\n\n uv.y = acos(n.y) / PI;\n\n return uv;\n}\n\n// Octahedral encoding: direction to UV\n// Maps sphere to square: yaw around edges, pitch from center (top) to edges (bottom)\nfn octEncode(n: vec3<f32>) -> vec2<f32> {\n var n2 = n / (abs(n.x) + abs(n.y) + abs(n.z));\n if (n2.y < 0.0) {\n let signX = select(-1.0, 1.0, n2.x >= 0.0);\n let signZ = select(-1.0, 1.0, n2.z >= 0.0);\n n2 = vec3f(\n (1.0 - abs(n2.z)) * signX,\n n2.y,\n (1.0 - abs(n2.x)) * signZ\n );\n }\n return n2.xz * 0.5 + 0.5;\n}\n\n// Octahedral decoding: UV to direction\nfn octDecode(uv: vec2<f32>) -> vec3<f32> {\n var uv2 = uv * 2.0 - 1.0;\n var n = vec3f(uv2.x, 1.0 - abs(uv2.x) - abs(uv2.y), uv2.y);\n if (n.y < 0.0) {\n let signX = select(-1.0, 1.0, n.x >= 0.0);\n let signZ = select(-1.0, 1.0, n.z >= 0.0);\n n = vec3f(\n (1.0 - abs(n.z)) * signX,\n n.y,\n (1.0 - abs(n.x)) * signZ\n );\n }\n return normalize(n);\n}\n\n// Sample noise texture at screen position with optional animation offset\n// Uses textureLoad to avoid uniform control flow issues\nfn sampleNoise(screenPos: vec2f) -> f32 {\n let noiseSize = i32(uniforms.noiseParams.x);\n let noiseOffsetX = i32(uniforms.noiseParams.y * f32(noiseSize));\n let noiseOffsetY = i32(uniforms.noiseParams.z * f32(noiseSize));\n\n // Tile the noise across screen space using modulo\n let texCoord = vec2i(\n (i32(screenPos.x) + noiseOffsetX) % noiseSize,\n (i32(screenPos.y) + noiseOffsetY) % noiseSize\n );\n return textureLoad(noiseTexture, texCoord, 0).r;\n}\n\n// Get 2D jitter offset from noise (-1 to 1 range)\n// Uses textureLoad to avoid uniform control flow issues\nfn getNoiseJitter(screenPos: vec2f) -> vec2f {\n let noiseSize = i32(uniforms.noiseParams.x);\n let noiseOffsetX = i32(uniforms.noiseParams.y * f32(noiseSize));\n let noiseOffsetY = i32(uniforms.noiseParams.z * f32(noiseSize));\n\n // Load at two offset positions for X and Y\n let texCoord1 = vec2i(\n (i32(screenPos.x) + noiseOffsetX) % noiseSize,\n (i32(screenPos.y) + noiseOffsetY) % noiseSize\n );\n let texCoord2 = vec2i(\n (i32(screenPos.x) + 37 + noiseOffsetX) % noiseSize,\n (i32(screenPos.y) + 17 + noiseOffsetY) % noiseSize\n );\n\n let n1 = textureLoad(noiseTexture, texCoord1, 0).r;\n let n2 = textureLoad(noiseTexture, texCoord2, 0).r;\n\n return vec2f(n1, n2) * 2.0 - 1.0;\n}\n\n// Get 4-channel noise for IBL multisampling\nfn getNoise4(screenPos: vec2f) -> vec4f {\n let noiseSize = i32(uniforms.noiseParams.x);\n let noiseOffsetX = i32(uniforms.noiseParams.y * f32(noiseSize));\n let noiseOffsetY = i32(uniforms.noiseParams.z * f32(noiseSize));\n\n let texCoord = vec2i(\n (i32(screenPos.x) + noiseOffsetX) % noiseSize,\n (i32(screenPos.y) + noiseOffsetY) % noiseSize\n );\n return textureLoad(noiseTexture, texCoord, 0);\n}\n\n// Vogel disk sample pattern - gives good 2D distribution\nfn vogelDiskSample(sampleIndex: i32, numSamples: i32, rotation: f32) -> vec2f {\n let goldenAngle = 2.399963229728653; // pi * (3 - sqrt(5))\n let r = sqrt((f32(sampleIndex) + 0.5) / f32(numSamples));\n let theta = f32(sampleIndex) * goldenAngle + rotation;\n return vec2f(r * cos(theta), r * sin(theta));\n}\n\n// Build orthonormal basis around a direction (for cone sampling)\nfn buildOrthonormalBasis(n: vec3f) -> mat3x3<f32> {\n // Choose a vector not parallel to n\n let up = select(vec3f(1.0, 0.0, 0.0), vec3f(0.0, 1.0, 0.0), abs(n.y) < 0.999);\n let tangent = normalize(cross(up, n));\n let bitangent = cross(n, tangent);\n return mat3x3<f32>(tangent, bitangent, n);\n}\n\nfn clampedDot(x: vec3<f32>, y: vec3<f32>) -> f32 {\n return clamp(dot(x, y), 0.0, 1.0);\n}\n\nfn F_Schlick(f0: vec3<f32>, f90: vec3<f32>, VdotH: f32) -> vec3<f32> {\n return f0 + (f90 - f0) * pow(clamp(1.0 - VdotH, 0.0, 1.0), 5.0);\n}\n\nfn V_GGX(NdotL: f32, NdotV: f32, alphaRoughness: f32) -> f32 {\n let alphaRoughnessSq = alphaRoughness * alphaRoughness;\n\n let GGXV = NdotL * sqrt(NdotV * NdotV * (1.0 - alphaRoughnessSq) + alphaRoughnessSq);\n let GGXL = NdotV * sqrt(NdotL * NdotL * (1.0 - alphaRoughnessSq) + alphaRoughnessSq);\n\n let GGX = GGXV + GGXL;\n if (GGX > 0.0) {\n return 0.5 / GGX;\n }\n return 0.0;\n}\n\nfn D_GGX(NdotH: f32, alphaRoughness: f32) -> f32 {\n let alphaRoughnessSq = alphaRoughness * alphaRoughness;\n let f = (NdotH * NdotH) * (alphaRoughnessSq - 1.0) + 1.0;\n return alphaRoughnessSq / (3.14159 * f * f);\n}\n\nfn BRDF_lambertian(f0: vec3<f32>, f90: vec3<f32>, diffuseColor: vec3<f32>, specularWeight: f32, VdotH: f32) -> vec3<f32> {\n return (1.0 - specularWeight * F_Schlick(f0, f90, VdotH)) * (diffuseColor / 3.14159);\n}\n\nfn BRDF_specularGGX(f0: vec3<f32>, f90: vec3<f32>, alphaRoughness: f32, specularWeight: f32, VdotH: f32, NdotL: f32, NdotV: f32, NdotH: f32) -> vec3<f32> {\n let F = F_Schlick(f0, f90, VdotH);\n let Vis = V_GGX(NdotL, NdotV, alphaRoughness);\n let D = D_GGX(NdotH, alphaRoughness);\n\n return specularWeight * F * Vis * D;\n}\n\n// Get environment UV based on encoding type (0=equirectangular, 1=octahedral)\nfn getEnvUV(dir: vec3<f32>) -> vec2<f32> {\n if (uniforms.noiseParams.w > 0.5) {\n return octEncode(dir);\n }\n return SphToUV(dir);\n}\n\nfn getIBLSample(reflection: vec3<f32>, lod: f32) -> vec3<f32> {\n let envRGBE = textureSampleLevel(env, envSampler, getEnvUV(reflection), lod);\n // Both equirectangular and octahedral textures are RGBE encoded\n let envColor = envRGBE.rgb * pow(2.0, envRGBE.a * 255.0 - 128.0);\n return envColor;\n}\n\n// IBL sample with noise-jittered UV to reduce banding\nfn getIBLSampleJittered(reflection: vec3<f32>, lod: f32, screenPos: vec2f) -> vec3<f32> {\n let baseUV = getEnvUV(reflection);\n\n // Get 2D jitter from blue noise\n let jitter = getNoiseJitter(screenPos);\n\n // Jitter by 0.5 texels of the environment texture\n let envSize = vec2f(textureDimensions(env, 0));\n let jitterScale = 16 / envSize;\n let jitteredUV = baseUV + jitter * jitterScale;\n\n let envRGBE = textureSampleLevel(env, envSampler, jitteredUV, lod);\n // Both equirectangular and octahedral textures are RGBE encoded\n let envColor = envRGBE.rgb * pow(2.0, envRGBE.a * 255.0 - 128.0);\n return envColor;\n}\n\n// IBL specular with multisampled cone jitter based on roughness\n// Uses Vogel disk sampling with blue noise rotation (same technique as planar reflections)\nfn getIBLRadianceGGX(n: vec3<f32>, v: vec3<f32>, roughness: f32, F0: vec3<f32>, specularWeight: f32, screenPos: vec2f) -> vec3<f32> {\n let NdotV = clampedDot(n, v);\n let lod = roughness * (uniforms.environmentParams.z - 1);\n let reflection = normalize(reflect(-v, n));\n\n // Fresnel calculation\n let Fr = max(vec3<f32>(1.0 - roughness), F0) - F0;\n let k_S = F0 + Fr * pow(1.0 - NdotV, 5.0);\n\n // For very smooth surfaces (roughness < 0.1), use single sample\n if (roughness < 0.1) {\n let specularSample = getIBLSampleJittered(reflection, lod, screenPos);\n return specularWeight * specularSample * k_S;\n }\n\n // Multisample with roughness-based cone jitter\n let numSamples = 4;\n\n // Get blue noise for rotation and radius jitter\n let noise = getNoise4(screenPos);\n let rotationAngle = noise.r * 6.283185307; // 0 to 2*PI rotation\n let radiusJitter = noise.g;\n\n // Build orthonormal basis around reflection direction\n let basis = buildOrthonormalBasis(reflection);\n\n // Cone angle based on roughness (roughness^2 scaling, max ~30 degrees)\n // Similar to planar reflection blur scaling\n let maxConeAngle = 0.5; // ~30 degrees in radians\n let coneAngle = roughness * roughness * maxConeAngle;\n\n var colorSum = vec3f(0.0);\n\n for (var i = 0; i < numSamples; i++) {\n // Get Vogel disk sample position (unit disk, rotated by blue noise)\n let diskSample = vogelDiskSample(i, numSamples, rotationAngle);\n\n // Apply radius jitter per-sample\n let sampleRadius = mix(0.5, 1.0, fract(radiusJitter + f32(i) * 0.618033988749895));\n\n // Convert disk position to cone offset\n let offset2D = diskSample * sampleRadius * coneAngle;\n\n // Perturb reflection direction within cone\n let perturbedDir = normalize(\n basis[2] + // reflection direction (z-axis of basis)\n basis[0] * offset2D.x + // tangent\n basis[1] * offset2D.y // bitangent\n );\n\n // Sample environment at perturbed direction\n let sampleColor = getIBLSample(perturbedDir, lod);\n colorSum += sampleColor;\n }\n\n let specularLight = colorSum / f32(numSamples);\n return specularWeight * specularLight * k_S;\n}\n\n// Sample shadow from a specific cascade with blue noise jittered PCF\n// Returns: x = shadow value (0=shadow, 1=lit), y = 1 if in bounds, 0 if out of bounds\nfn sampleCascadeShadowWithBounds(worldPos: vec3<f32>, normal: vec3<f32>, cascadeIndex: i32, screenPos: vec2f) -> vec2f {\n let baseBias = uniforms.shadowParams.x;\n let baseNormalBias = uniforms.shadowParams.y;\n let shadowMapSize = uniforms.shadowParams.w;\n\n // Slightly increase bias for cascade 2 (larger coverage, lower resolution)\n var biasScale = 1.0;\n if (cascadeIndex == 2) {\n biasScale = 1.5;\n }\n let bias = baseBias * biasScale;\n let normalBias = baseNormalBias * biasScale;\n\n // Apply normal bias\n let biasedPos = worldPos + normal * normalBias;\n\n // Get cascade matrix from storage buffer\n let lightMatrix = cascadeMatrices.matrices[cascadeIndex];\n\n // Transform to light space\n let lightSpacePos = lightMatrix * vec4f(biasedPos, 1.0);\n let projCoords = lightSpacePos.xyz / lightSpacePos.w;\n\n // Transform to [0,1] UV space\n let shadowUV = vec2f(projCoords.x * 0.5 + 0.5, 0.5 - projCoords.y * 0.5);\n let currentDepth = projCoords.z - bias;\n\n // Check bounds\n let inBoundsX = shadowUV.x >= 0.0 && shadowUV.x <= 1.0;\n let inBoundsY = shadowUV.y >= 0.0 && shadowUV.y <= 1.0;\n let inBoundsZ = currentDepth >= 0.0 && currentDepth <= 1.0;\n let inBounds = inBoundsX && inBoundsY && inBoundsZ;\n\n if (!inBounds) {\n return vec2f(1.0, 0.0); // Out of bounds\n }\n\n let clampedUV = clamp(shadowUV, vec2f(0.001), vec2f(0.999));\n let clampedDepth = clamp(currentDepth, 0.001, 0.999);\n\n // Get blue noise jitter for this pixel\n let jitter = getNoiseJitter(screenPos);\n\n // Blue noise jittered PCF - rotated disk pattern\n // Use noise to rotate and offset sample positions\n let texelSize = 1.0 / shadowMapSize;\n // More blur for closer cascades, less for distant ones\n var cascadeBlurScale = 1.0; // Default for cascade 2\n\n if (cascadeIndex == 0) {\n cascadeBlurScale = 2.0;\n } else if (cascadeIndex == 1) {\n cascadeBlurScale = 1.7;\n }\n\n let filterRadius = texelSize * cascadeBlurScale;\n\n // Create rotation matrix from noise\n let angle = jitter.x * PI;\n let cosA = cos(angle);\n let sinA = sin(angle);\n\n // Poisson disk sample offsets (pre-computed, 8 samples)\n let offsets = array<vec2f, 8>(\n vec2f(-0.94201624, -0.39906216),\n vec2f(0.94558609, -0.76890725),\n vec2f(-0.094184101, -0.92938870),\n vec2f(0.34495938, 0.29387760),\n vec2f(-0.91588581, 0.45771432),\n vec2f(-0.81544232, -0.87912464),\n vec2f(-0.38277543, 0.27676845),\n vec2f(0.97484398, 0.75648379)\n );\n\n var shadow = 0.0;\n for (var i = 0; i < 8; i++) {\n // Rotate offset by noise angle\n let offset = offsets[i];\n let rotatedOffset = vec2f(\n offset.x * cosA - offset.y * sinA,\n offset.x * sinA + offset.y * cosA\n ) * filterRadius;\n\n // Add subtle jitter offset\n let jitteredOffset = rotatedOffset + jitter * texelSize * 0.15;\n\n shadow += textureSampleCompareLevel(shadowMapArray, shadowSampler, clampedUV + jitteredOffset, cascadeIndex, clampedDepth);\n }\n shadow /= 8.0;\n\n return vec2f(shadow, 1.0); // In bounds\n}\n\n// Wrapper for simple usage\nfn sampleCascadeShadow(worldPos: vec3<f32>, normal: vec3<f32>, cascadeIndex: i32, screenPos: vec2f) -> f32 {\n return sampleCascadeShadowWithBounds(worldPos, normal, cascadeIndex, screenPos).x;\n}\n\n// Squircle distance function for smooth fade at edges\n// Returns 0-1 where 1 is at the edge of the squircle\nfn squircleDistance(uv: vec2<f32>, power: f32) -> f32 {\n // Squircle: |x|^n + |y|^n = 1 where n > 2 gives squircle shape\n let centered = (uv - 0.5) * 2.0; // Convert to -1 to 1\n let absCentered = abs(centered);\n return pow(pow(absCentered.x, power) + pow(absCentered.y, power), 1.0 / power);\n}\n\n// Squircle distance - returns distance normalized to cascade size\n// Power 4 gives nice rounded corners\nfn squircleDistanceXZ(offset: vec2f, size: f32) -> f32 {\n let normalized = offset / size;\n let absNorm = abs(normalized);\n return pow(pow(absNorm.x, 4.0) + pow(absNorm.y, 4.0), 0.25);\n}\n\n// Calculate cascaded shadow with smooth blending between cascades\nfn calculateShadow(worldPos: vec3<f32>, normal: vec3<f32>, screenPos: vec2f) -> f32 {\n let shadowStrength = uniforms.shadowParams.z;\n\n // Calculate XZ offset from camera to world position\n let camXZ = vec2f(uniforms.cameraPosition.x, uniforms.cameraPosition.z);\n let posXZ = vec2f(worldPos.x, worldPos.z);\n let offsetXZ = posXZ - camXZ;\n\n // Cascade sizes from uniforms\n let cascade0Size = uniforms.cascadeSizes.x;\n let cascade1Size = uniforms.cascadeSizes.y;\n let cascade2Size = uniforms.cascadeSizes.z;\n\n // Use squircle distance for cascade selection - rounded square shape\n let dist0 = squircleDistanceXZ(offsetXZ, cascade0Size);\n let dist1 = squircleDistanceXZ(offsetXZ, cascade1Size);\n let dist2 = squircleDistanceXZ(offsetXZ, cascade2Size);\n\n // Determine which cascade(s) to sample based on squircle distance\n var shadow = 1.0;\n var cascadeUsed = -1;\n var blendFactor = 0.0;\n\n // Sample cascades with bounds checking (pass screen position for blue noise)\n let sample0 = sampleCascadeShadowWithBounds(worldPos, normal, 0, screenPos);\n let sample1 = sampleCascadeShadowWithBounds(worldPos, normal, 1, screenPos);\n let sample2 = sampleCascadeShadowWithBounds(worldPos, normal, 2, screenPos);\n\n let inBounds0 = sample0.y > 0.5;\n let inBounds1 = sample1.y > 0.5;\n let inBounds2 = sample2.y > 0.5;\n\n // Cascade 0: highest detail, smallest area\n if (dist0 < 1.0) {\n if (inBounds0) {\n // Blend towards cascade 1 at 90-100% of cascade 0 size\n let blend0to1 = smoothstep(0.9, 1.0, dist0);\n if (blend0to1 > 0.0 && inBounds1) {\n shadow = mix(sample0.x, sample1.x, blend0to1);\n } else {\n shadow = sample0.x;\n }\n cascadeUsed = 0;\n } else if (inBounds1) {\n // Cascade 0 out of bounds, use cascade 1 100%\n shadow = sample1.x;\n cascadeUsed = 1;\n } else if (inBounds2) {\n // Cascade 0 and 1 out of bounds, use cascade 2 100%\n shadow = sample2.x;\n cascadeUsed = 2;\n } else {\n return mix(1.0 - shadowStrength, 1.0, 0.5);\n }\n }\n // Cascade 1: medium detail\n else if (dist1 < 1.0) {\n if (inBounds1) {\n // Blend towards cascade 2 at 90-100% of cascade 1 size\n let blend1to2 = smoothstep(0.9, 1.0, dist1);\n if (blend1to2 > 0.0 && inBounds2) {\n shadow = mix(sample1.x, sample2.x, blend1to2);\n } else {\n shadow = sample1.x;\n }\n cascadeUsed = 1;\n } else if (inBounds2) {\n // Cascade 1 out of bounds, use cascade 2 100%\n shadow = sample2.x;\n cascadeUsed = 2;\n } else {\n return mix(1.0 - shadowStrength, 1.0, 0.5);\n }\n }\n // Cascade 2: lowest detail, largest area\n else if (dist2 < 1.0) {\n if (inBounds2) {\n shadow = sample2.x;\n cascadeUsed = 2;\n\n // Gradual fade for cascade 2:\n // 50-95%: fade shadow towards half-lit\n // 95-100%: fade from shadow to half-lit (0.5)\n let lightnessFade = smoothstep(0.5, 0.95, dist2);\n shadow = mix(shadow, 0.5, lightnessFade);\n\n let edgeFade = smoothstep(0.95, 1.0, dist2);\n shadow = mix(shadow, 0.5, edgeFade);\n } else {\n return mix(1.0 - shadowStrength, 1.0, 0.5);\n }\n }\n // Beyond all cascades: half-lit\n else {\n return mix(1.0 - shadowStrength, 1.0, 0.5);\n }\n\n // Apply shadow strength\n let shadowResult = mix(1.0 - shadowStrength, 1.0, shadow);\n return shadowResult;\n}\n\n// Calculate spot light shadow from atlas using storage buffer for matrices\n// lightPos: spotlight position (for calculating distance to camera)\n// slotIndex: shadow atlas slot (-1 if no shadow)\nfn calculateSpotShadow(worldPos: vec3<f32>, normal: vec3<f32>, lightPos: vec3<f32>, slotIndex: i32, screenPos: vec2f) -> f32 {\n // Use hard-coded constants to avoid uniform buffer alignment issues\n let fadeStart = SPOT_FADE_START;\n let maxDist = SPOT_MAX_DISTANCE;\n let minShadow = SPOT_MIN_SHADOW;\n let bias = uniforms.shadowParams.x;\n let normalBias = uniforms.shadowParams.y;\n let shadowStrength = uniforms.shadowParams.z;\n\n // Calculate distance from spotlight to camera (not light to pixel!)\n let lightToCam = lightPos - uniforms.cameraPosition;\n let lightDistance = length(lightToCam);\n\n // Get atlas parameters from constants\n let atlasWidth = SPOT_ATLAS_WIDTH;\n let atlasHeight = SPOT_ATLAS_HEIGHT;\n let tileSize = SPOT_TILE_SIZE;\n let tilesPerRow = SPOT_TILES_PER_ROW;\n\n // Check validity\n let hasValidSlot = f32(slotIndex >= 0 && slotIndex < MAX_SPOT_SHADOWS);\n\n // Clamp slot index to valid range for matrix access\n let safeSlot = clamp(slotIndex, 0, MAX_SPOT_SHADOWS - 1);\n\n // Calculate tile position in atlas\n let col = safeSlot % tilesPerRow;\n let row = safeSlot / tilesPerRow;\n\n // Apply normal bias (increased for spot lights due to perspective projection)\n let spotNormalBias = normalBias * 2.0;\n let biasedPos = worldPos + normal * spotNormalBias;\n\n // Get the light matrix from storage buffer\n let lightMatrix = spotMatrices.matrices[safeSlot];\n\n // Transform to light space\n let lightSpacePos = lightMatrix * vec4f(biasedPos, 1.0);\n\n // Perspective divide (avoid divide by zero)\n let w = max(abs(lightSpacePos.w), 0.0001) * sign(lightSpacePos.w + 0.0001);\n let projCoords = lightSpacePos.xyz / w;\n\n // Calculate edge fade for soft frustum boundaries\n // Use distance from center in clip space (max of abs x,y)\n let edgeDist = max(abs(projCoords.x), abs(projCoords.y));\n // Fade from 0.8 to 1.0 in clip space (soft edge)\n let edgeFade = 1.0 - smoothstep(0.8, 1.0, edgeDist);\n // Also fade based on depth (behind camera or too far)\n let depthFade = smoothstep(-0.1, 0.1, projCoords.z) * (1.0 - smoothstep(0.9, 1.0, projCoords.z));\n let frustumFade = edgeFade * depthFade;\n\n // Transform to [0,1] UV space within the tile\n let tileUV = vec2f(projCoords.x * 0.5 + 0.5, 0.5 - projCoords.y * 0.5);\n\n // Calculate UV in atlas (normalized to atlas size)\n let tileOffsetX = f32(col) * tileSize;\n let tileOffsetY = f32(row) * tileSize;\n let atlasUV = vec2f(\n (tileOffsetX + tileUV.x * tileSize) / atlasWidth,\n (tileOffsetY + tileUV.y * tileSize) / atlasHeight\n );\n\n // Blue noise jittered PCF for spot shadows\n let jitter = getNoiseJitter(screenPos);\n let texelSize = 1.0 / tileSize;\n let filterRadius = texelSize * 0.5; // Subtle filter for spot shadows\n\n // Create rotation from noise\n let angle = jitter.x * PI;\n let cosA = cos(angle);\n let sinA = sin(angle);\n\n // 4-sample rotated PCF for spot shadows (less samples for performance)\n let offsets = array<vec2f, 4>(\n vec2f(-0.7, -0.7),\n vec2f(0.7, -0.7),\n vec2f(-0.7, 0.7),\n vec2f(0.7, 0.7)\n );\n\n // Current depth with bias (increased for spot lights)\n let spotBias = bias * 3.0;\n let currentDepth = clamp(projCoords.z - spotBias, 0.001, 0.999);\n\n var shadowSample = 0.0;\n for (var i = 0; i < 4; i++) {\n let offset = offsets[i];\n let rotatedOffset = vec2f(\n offset.x * cosA - offset.y * sinA,\n offset.x * sinA + offset.y * cosA\n ) * filterRadius;\n\n let sampleUV = clamp(atlasUV + rotatedOffset, vec2f(0.001), vec2f(0.999));\n shadowSample += textureSampleCompareLevel(spotShadowAtlas, spotShadowSampler, sampleUV, currentDepth);\n }\n shadowSample /= 4.0;\n\n // Apply shadow strength\n let shadowWithStrength = mix(1.0 - shadowStrength, 1.0, shadowSample);\n\n // Apply frustum edge fade (fade to 1.0 = no shadow at edges)\n let edgeFadedShadow = mix(1.0, shadowWithStrength, frustumFade);\n\n // Apply distance-based fade\n let fadeT = clamp((lightDistance - fadeStart) / (maxDist - fadeStart), 0.0, 1.0);\n let fadedShadow = mix(edgeFadedShadow, minShadow, fadeT);\n\n // Calculate result for no-slot case (distance-based constant shadow)\n let noSlotFadeT = clamp((lightDistance - fadeStart) / (maxDist - fadeStart), 0.0, 1.0);\n let noSlotResult = mix(1.0, minShadow, noSlotFadeT);\n\n // Select final result based on slot validity (frustum fade already applied)\n let shadowResult = select(noSlotResult, fadedShadow, hasValidSlot > 0.5);\n\n // Debug visualization stored in global for fragment shader to use\n // Using projCoords.z as a debug value\n\n return shadowResult;\n}\n\n@vertex\nfn vertexMain(@builtin(vertex_index) vertexIndex: u32) -> VertexOutput {\nvar output : VertexOutput;\n let x = f32(vertexIndex & 1u) * 4.0 - 1.0;\n let y = f32(vertexIndex >> 1u) * 4.0 - 1.0;\n output.position = vec4<f32>(x, y, 0.0, 1.0);\n output.uv = vec2<f32>((x + 1.0) * 0.5, (1.0 - y) * 0.5);\n return output;\n}\n\n@fragment\nfn fragmentMain(input: VertexOutput) -> @location(0) vec4f {\n let cs = uniforms.canvasSize;\n let fuv = vec2i(floor(input.uv * cs));\n let baseColor = textureLoad(gAlbedo, fuv, 0);\n let normal = normalize(textureLoad(gNormal, fuv, 0).xyz);\n let arm = textureLoad(gArm, fuv, 0);\n let emissive = textureLoad(gEmission, fuv, 0).rgb;\n let depth = textureLoad(gDepth, fuv, 0);\n\n // Unpack ARM values (Ambient, Roughness, Metallic, per-material SpecularBoost)\n var ao = arm.r;\n var roughness = arm.g;\n var metallic = arm.b;\n let materialSpecularBoost = arm.a; // Per-material boost (0-1)\n\n // Sample SSAO texture - this affects only ambient/environment lighting\n let ssao = textureLoad(aoTexture, fuv, 0).r;\n\n var specular = vec3<f32>(0.0);\n var diffuse = vec3<f32>(0.0);\n\n let ior = 1.5;\n var f0 = vec3<f32>(0.04);\n let f90 = vec3<f32>(1.0);\n let specularWeight = 1.0;\n\n // Reconstruct position from linear depth and UV\n // Linear depth: depth = (z - near) / (far - near), so z = near + depth * (far - near)\n let near = uniforms.cameraParams.x;\n let far = uniforms.cameraParams.y;\n let linearDepth = near + depth * (far - near);\n\n // Get view-space ray direction from UV\n var iuv = input.uv;\n iuv.y = 1.0 - iuv.y;\n let ndc = vec4f(iuv * 2.0 - 1.0, 0.0, 1.0);\n let viewRay = uniforms.inverseProjection * ndc;\n let rayDir = normalize(viewRay.xyz / viewRay.w);\n\n // Reconstruct view-space position using linear depth\n let viewPos = rayDir * (linearDepth / -rayDir.z);\n\n // Transform to world space\n let worldPos4 = uniforms.inverseView * vec4f(viewPos, 1.0);\n let position = worldPos4.xyz;\n let toCamera = uniforms.cameraPosition.xyz - position;\n let v = normalize(toCamera);\n\n let n = normal;\n\n f0 = mix(f0, baseColor.rgb, metallic);\n roughness = max(roughness, 0.04);\n let alphaRoughness = roughness * roughness;\n\n let c_diff = mix(baseColor.rgb, vec3<f32>(0.0), metallic);\n\n // Combine material AO with SSAO for ambient/environment lighting only\n let combinedAO = ao * ssao;\n\n diffuse += combinedAO * uniforms.ambientColor.rgb * uniforms.ambientColor.a * BRDF_lambertian(f0, f90, c_diff, specularWeight, 1.0);\n\n // Use jittered IBL sampling to reduce banding\n let screenPos = input.position.xy;\n let iblSample = getIBLSampleJittered(n, uniforms.environmentParams.z - 2, screenPos);\n diffuse += combinedAO * uniforms.environmentParams.x * iblSample.rgb * BRDF_lambertian(f0, f90, c_diff, specularWeight, 1.0);\n\n let reflection = normalize(reflect(-v, n));\n specular += combinedAO * uniforms.environmentParams.y * getIBLRadianceGGX(n, v, roughness, f0, specularWeight, screenPos);\n\n // calculate lighting\n\n let onormal = n;\n let l = normalize(uniforms.lightDir.xyz);\n let h = normalize(l + v);\n let NdotV = clampedDot(n, v);\n let ONdotV = clampedDot(onormal, v);\n let ONdotL = clampedDot(onormal, l);\n let NdotLF = dot(n, l);\n let NdotL = clamp(NdotLF, 0.0, 1.0);\n let NdotLF_adjusted = NdotLF * 0.5 + 0.5;\n let NdotH = clampedDot(n, h);\n let LdotH = clampedDot(l, h);\n let VdotH = clampedDot(v, h);\n\n let intensity = uniforms.lightColor.a;\n // Calculate shadow outside of non-uniform control flow\n let shadow = calculateShadow(position, n, screenPos);\n let fallOff = smoothstep(-0.1, 0.3, NdotLF); // Soft gradual falloff\n\n // Apply lighting with shadow (using select to avoid non-uniform branching)\n let lightActive = select(0.0, 1.0, (NdotL > 0.0 || NdotV > 0.0) && intensity > 0.0);\n diffuse += lightActive * intensity * uniforms.lightColor.rgb * fallOff * shadow * BRDF_lambertian(f0, f90, c_diff, specularWeight, VdotH);\n specular += lightActive * intensity * uniforms.lightColor.rgb * fallOff * shadow * BRDF_specularGGX(f0, f90, alphaRoughness, specularWeight, VdotH, fallOff, NdotV, NdotH) * uniforms.cameraParams.w;\n\n // Tiled lighting: get tile index from pixel position\n let tileSize = uniforms.tileParams.x;\n let tileCountX = uniforms.tileParams.y;\n let maxLightsPerTile = uniforms.tileParams.z;\n let actualLightCount = uniforms.tileParams.w;\n\n // Calculate tileCountY from canvas size\n let tileCountY = (u32(uniforms.canvasSize.y) + tileSize - 1u) / tileSize;\n\n let tileX = u32(floor(input.uv.x * uniforms.canvasSize.x)) / tileSize;\n // Flip Y because UV.y increases downward but NDC Y (used in compute shader) increases upward\n let rawTileY = u32(floor(input.uv.y * uniforms.canvasSize.y)) / tileSize;\n let tileY = tileCountY - 1u - rawTileY;\n let tileIndex = tileY * tileCountX + tileX;\n let tileDataOffset = tileIndex * (maxLightsPerTile + 1u);\n\n // Read number of lights affecting this tile\n let tileLightCount = min(tileLightIndices[tileDataOffset], maxLightsPerTile);\n\n // Process only lights that affect this tile\n for (var i = 0u; i < tileLightCount; i++) {\n let lightIndex = tileLightIndices[tileDataOffset + 1u + i];\n if (lightIndex >= actualLightCount) {\n continue;\n }\n\n let light = lights[lightIndex];\n let isEnabled = f32(light.enabled);\n\n // Calculate light direction and distance\n let lightVec = light.position - position;\n let dist = length(lightVec);\n let lightDir = normalize(lightVec);\n\n // Distance fade from CPU culling (stored in geom.w) - smooth fade to avoid popping\n let distanceFade = light.geom.w;\n\n // Attenuation (inverse square with radius falloff)\n let radius = max(light.geom.x, 0.001);\n let attenuation = max(0.0, 1.0 - dist / radius);\n let attenuationSq = attenuation * attenuation * distanceFade;\n\n // Spot light cone attenuation\n let innerCone = light.geom.y;\n let outerCone = light.geom.z;\n let spotCos = dot(-lightDir, normalize(light.direction + vec3f(0.0001)));\n let spotAttenuation = select(1.0, smoothstep(outerCone, innerCone, spotCos), outerCone > 0.0);\n\n // Spot shadow calculation (only for spotlights, not point lights)\n let isSpotlight = outerCone > 0.0;\n let spotShadow = calculateSpotShadow(position, n, light.position, light.shadowIndex, screenPos);\n let lightShadow = select(1.0, spotShadow, isSpotlight); // Point lights: no shadow\n\n // Calculate lighting vectors\n let pl = lightDir;\n let ph = normalize(pl + v);\n let pNdotL = clampedDot(n, pl);\n let pNdotH = clampedDot(n, ph);\n let pVdotH = clampedDot(v, ph);\n\n // Light intensity from color.a, masked by enabled, attenuation, and shadow\n let pIntensity = isEnabled * light.color.a * attenuationSq * spotAttenuation * lightShadow * pNdotL;\n\n // Add light contribution (always compute, multiply by intensity mask)\n // Use simpler diffuse calculation for point lights\n diffuse += pIntensity * light.color.rgb * c_diff / PI;\n specular += pIntensity * light.color.rgb * BRDF_specularGGX(f0, f90, alphaRoughness, specularWeight, pVdotH, pNdotL, NdotV, pNdotH) * uniforms.cameraParams.w;\n }\n\n // Specular boost: 3 fake lights at 60° elevation, 120° apart\n // Only affects materials with specularBoost >= 0.04 and roughness < cutoff, ignores shadows\n let boostIntensity = uniforms.specularBoost.x;\n let boostRoughnessCutoff = uniforms.specularBoost.y;\n\n // Per-material specularBoost controls this effect (0 = disabled, 1 = full effect)\n // Skip calculation entirely if material boost is below threshold (default 0 means no boost)\n if (materialSpecularBoost >= 0.04 && boostIntensity > 0.0 && roughness < boostRoughnessCutoff && depth < 0.999999) {\n\n // 3 light directions at 30° above horizon (sin30=0.5, cos30=0.866), 120° apart\n // Pre-calculated normalized directions:\n let boostDir0 = vec3f(0.0, 0.5, 0.866025); // 0° yaw\n let boostDir1 = vec3f(0.75, 0.5, -0.433013); // 120° yaw\n let boostDir2 = vec3f(-0.75, 0.5, -0.433013); // 240° yaw\n\n // Fade based on roughness: full at 0, zero at cutoff\n let roughnessFade = 1.0 - (roughness / boostRoughnessCutoff);\n // Include per-material boost in final strength\n let boostStrength = boostIntensity * roughnessFade * roughnessFade * materialSpecularBoost;\n\n // Use sharper roughness for boost highlights (smaller, more concentrated)\n // Square the roughness to make highlights much tighter\n let boostRoughness = roughness * 0.5;\n let boostAlphaRoughness = boostRoughness * 0.5;\n\n // Calculate specular for each boost light\n var boostSpecular = vec3f(0.0);\n\n // Light 0\n let bNdotL0 = max(dot(n, boostDir0), 0.0);\n if (bNdotL0 > 0.0) {\n let bH0 = normalize(boostDir0 + v);\n let bNdotH0 = max(dot(n, bH0), 0.0);\n let bVdotH0 = max(dot(v, bH0), 0.0);\n boostSpecular += bNdotL0 * BRDF_specularGGX(f0, f90, boostAlphaRoughness, specularWeight, bVdotH0, bNdotL0, NdotV, bNdotH0);\n }\n\n // Light 1\n let bNdotL1 = max(dot(n, boostDir1), 0.0);\n if (bNdotL1 > 0.0) {\n let bH1 = normalize(boostDir1 + v);\n let bNdotH1 = max(dot(n, bH1), 0.0);\n let bVdotH1 = max(dot(v, bH1), 0.0);\n boostSpecular += bNdotL1 * BRDF_specularGGX(f0, f90, boostAlphaRoughness, specularWeight, bVdotH1, bNdotL1, NdotV, bNdotH1);\n }\n\n // Light 2\n let bNdotL2 = max(dot(n, boostDir2), 0.0);\n if (bNdotL2 > 0.0) {\n let bH2 = normalize(boostDir2 + v);\n let bNdotH2 = max(dot(n, bH2), 0.0);\n let bVdotH2 = max(dot(v, bH2), 0.0);\n boostSpecular += bNdotL2 * BRDF_specularGGX(f0, f90, boostAlphaRoughness, specularWeight, bVdotH2, bNdotL2, NdotV, bNdotH2);\n }\n\n // Add boost specular (white light, same intensity as main light would have)\n specular += boostSpecular * boostStrength;\n }\n\n // calculate final color\n\n let specularColor = specular + emissive;\n let diffuseColor = diffuse;\n\n var color = diffuseColor + specularColor;\n color *= uniforms.environmentParams.a;\n\n if (depth > 0.999999) {\n var bgDir = v;\n if (uniforms.noiseParams.w > 0.5) {\n // Octahedral encoding: captured probe uses standard coordinate system\n // Just negate view direction to get world direction\n bgDir = -v;\n } else {\n // Equirectangular encoding: flip to match IBL lighting orientation\n bgDir.x *= -1.0;\n bgDir.z *= -1.0;\n bgDir.y *= -1.0;\n }\n // In reflection mode, flip Y to mirror the sky\n if (uniforms.cameraParams.z > 0.5) {\n bgDir.y *= -1.0;\n }\n // Background sky: only apply exposure, NOT diffuse/specular IBL levels\n // Those levels are for PBR lighting, not for displaying the actual sky\n let bgSample = getIBLSample(bgDir, 0.0) * uniforms.environmentParams.a;\n color = bgSample;\n }\n\n return vec4f(color, 1.0);\n}\n","// Light culling compute shader for tiled deferred lighting\n// Divides screen into tiles and determines which lights affect each tile\n\nconst TILE_SIZE: u32 = 16u;\nconst MAX_LIGHTS_PER_TILE: u32 = 256u;\nconst MAX_LIGHTS: u32 = 512u;\n\nstruct Light {\n enabled: u32,\n position: vec3f,\n color: vec4f,\n direction: vec3f,\n geom: vec4f, // x = radius, y = inner cone, z = outer cone\n shadowIndex: i32,\n}\n\nstruct LightCullUniforms {\n viewMatrix: mat4x4f,\n projectionMatrix: mat4x4f,\n inverseProjection: mat4x4f,\n screenSize: vec2f,\n tileCount: vec2u,\n lightCount: u32,\n nearPlane: f32,\n farPlane: f32,\n padding: f32,\n}\n\n@group(0) @binding(0) var<uniform> uniforms: LightCullUniforms;\n@group(0) @binding(1) var<storage, read> lights: array<Light, MAX_LIGHTS>;\n@group(0) @binding(2) var<storage, read_write> tileLightIndices: array<u32>;\n@group(0) @binding(3) var gDepth: texture_depth_2d;\n\n// Get view-space frustum planes for a tile\n// Returns 4 planes (left, right, bottom, top) in view space\nfn getTileFrustumPlanes(tileX: u32, tileY: u32, tileCount: vec2u) -> array<vec4f, 4> {\n var planes: array<vec4f, 4>;\n\n // Calculate tile bounds in NDC space [-1, 1]\n let tileMinX = f32(tileX) / f32(tileCount.x) * 2.0 - 1.0;\n let tileMaxX = f32(tileX + 1u) / f32(tileCount.x) * 2.0 - 1.0;\n let tileMinY = f32(tileY) / f32(tileCount.y) * 2.0 - 1.0;\n let tileMaxY = f32(tileY + 1u) / f32(tileCount.y) * 2.0 - 1.0;\n\n // Create planes from NDC frustum edges\n // Each plane normal points inward\n\n // Left plane: normal = (1, 0, -tileMinX)\n let leftNormal = normalize(vec3f(1.0, 0.0, -tileMinX));\n planes[0] = vec4f(leftNormal, 0.0);\n\n // Right plane: normal = (-1, 0, tileMaxX)\n let rightNormal = normalize(vec3f(-1.0, 0.0, tileMaxX));\n planes[1] = vec4f(rightNormal, 0.0);\n\n // Bottom plane: normal = (0, 1, -tileMinY)\n let bottomNormal = normalize(vec3f(0.0, 1.0, -tileMinY));\n planes[2] = vec4f(bottomNormal, 0.0);\n\n // Top plane: normal = (0, -1, tileMaxY)\n let topNormal = normalize(vec3f(0.0, -1.0, tileMaxY));\n planes[3] = vec4f(topNormal, 0.0);\n\n return planes;\n}\n\n// Test if a sphere intersects with a frustum plane\nfn sphereInsidePlane(center: vec3f, radius: f32, plane: vec4f) -> bool {\n let dist = dot(plane.xyz, center) + plane.w;\n return dist >= -radius;\n}\n\n// Test if a light affects a tile\nfn lightAffectsTile(lightIndex: u32, tileX: u32, tileY: u32, minDepth: f32, maxDepth: f32) -> bool {\n let light = lights[lightIndex];\n\n if (light.enabled == 0u) {\n return false;\n }\n\n let lightRadius = light.geom.x;\n if (lightRadius <= 0.0) {\n return false;\n }\n\n // Transform light position to view space\n let lightPosView = uniforms.viewMatrix * vec4f(light.position, 1.0);\n let lightCenter = lightPosView.xyz;\n\n // View-space depth (positive = in front of camera)\n let lightDepth = -lightCenter.z;\n\n // Skip lights that are entirely behind the camera\n if (lightDepth + lightRadius < 0.0) {\n return false;\n }\n\n // Quick depth test: check if light sphere overlaps tile depth range\n // Only cull if entirely in front of near plane (behind camera already handled above)\n let lightMinZ = lightDepth - lightRadius;\n if (lightMinZ > maxDepth) {\n return false;\n }\n\n // Project light center to clip space for tile bounds test\n let lightPosClip = uniforms.projectionMatrix * lightPosView;\n\n // Handle lights very close to or behind camera - use conservative estimate\n let w = lightPosClip.w;\n if (w < 0.1) {\n // Light is very close to or behind camera - include in all relevant tiles\n // Just do a simple distance check instead\n return true;\n }\n\n let lightPosNDC = lightPosClip.xyz / w;\n\n // Calculate tile bounds in NDC\n let tileCount = uniforms.tileCount;\n let tileMinX = f32(tileX) / f32(tileCount.x) * 2.0 - 1.0;\n let tileMaxX = f32(tileX + 1u) / f32(tileCount.x) * 2.0 - 1.0;\n let tileMinY = f32(tileY) / f32(tileCount.y) * 2.0 - 1.0;\n let tileMaxY = f32(tileY + 1u) / f32(tileCount.y) * 2.0 - 1.0;\n\n // Minimum radius in NDC - ensure lights are assigned to at least their containing tile\n // Scale minimum with depth to handle grazing angle views where distant lights project thin\n let tileWidthNDC = 2.0 / f32(tileCount.x);\n let tileHeightNDC = 2.0 / f32(tileCount.y);\n let depthFactor = 1.0 + lightDepth * 0.002; // Grow minimum radius with distance\n let minRadiusX = tileWidthNDC * depthFactor;\n let minRadiusY = tileHeightNDC * depthFactor;\n\n // Approximate radius in NDC - use separate X and Y scales due to aspect ratio\n // Add 1.5x multiplier to account for perspective distortion at screen edges\n let depthScale = 1.0 / max(lightDepth, 0.1);\n let radiusNDC_X = max(lightRadius * depthScale * uniforms.projectionMatrix[0][0] * 1.5, minRadiusX);\n let radiusNDC_Y = max(lightRadius * depthScale * uniforms.projectionMatrix[1][1] * 1.5, minRadiusY);\n\n // AABB test: check if light sphere (in NDC) overlaps tile bounds\n if (lightPosNDC.x + radiusNDC_X < tileMinX || lightPosNDC.x - radiusNDC_X > tileMaxX) {\n return false;\n }\n if (lightPosNDC.y + radiusNDC_Y < tileMinY || lightPosNDC.y - radiusNDC_Y > tileMaxY) {\n return false;\n }\n\n return true;\n}\n\n@compute @workgroup_size(16, 16, 1)\nfn main(@builtin(global_invocation_id) globalId: vec3u, @builtin(workgroup_id) workgroupId: vec3u) {\n let tileX = workgroupId.x;\n let tileY = workgroupId.y;\n let tileCount = uniforms.tileCount;\n\n // Bounds check\n if (tileX >= tileCount.x || tileY >= tileCount.y) {\n return;\n }\n\n let tileIndex = tileY * tileCount.x + tileX;\n let tileDataOffset = tileIndex * (MAX_LIGHTS_PER_TILE + 1u);\n\n // Local thread coordinates within the tile\n let localX = globalId.x % TILE_SIZE;\n let localY = globalId.y % TILE_SIZE;\n let localIndex = localY * TILE_SIZE + localX;\n\n // Calculate pixel coordinates for this thread\n let pixelX = tileX * TILE_SIZE + localX;\n let pixelY = tileY * TILE_SIZE + localY;\n\n // Sample depth at this pixel (for min/max depth calculation)\n var depth = 1.0;\n if (pixelX < u32(uniforms.screenSize.x) && pixelY < u32(uniforms.screenSize.y)) {\n depth = textureLoad(gDepth, vec2i(i32(pixelX), i32(pixelY)), 0);\n }\n\n // Use shared memory for min/max depth reduction\n // For simplicity, we'll use a conservative estimate here\n // In a real implementation, you'd use workgroup shared memory for reduction\n\n // Convert depth to view-space Z\n let minDepth = uniforms.nearPlane;\n let maxDepth = uniforms.farPlane;\n\n // Only thread 0 of each tile does the light culling\n if (localIndex == 0u) {\n var lightCount = 0u;\n\n for (var i = 0u; i < uniforms.lightCount && i < MAX_LIGHTS; i++) {\n if (lightAffectsTile(i, tileX, tileY, minDepth, maxDepth)) {\n if (lightCount < MAX_LIGHTS_PER_TILE) {\n // Store light index (offset by 1 to leave room for count)\n tileLightIndices[tileDataOffset + 1u + lightCount] = i;\n lightCount++;\n }\n }\n }\n\n // Store light count at the start of tile data\n tileLightIndices[tileDataOffset] = lightCount;\n }\n}\n","import { BasePass } from \"./BasePass.js\"\nimport { Pipeline } from \"../../Pipeline.js\"\nimport { Texture } from \"../../Texture.js\"\nimport { mat4, vec3 } from \"../../math.js\"\nimport { Frustum } from \"../../utils/Frustum.js\"\n\nimport lightingWGSL from \"../shaders/lighting.wgsl\"\nimport lightCullingWGSL from \"../shaders/light_culling.wgsl\"\n\n/**\n * LightingPass - Tiled deferred lighting calculation\n *\n * Pass 6 in the 7-pass pipeline.\n * Uses compute shader to cull lights per tile, then fragment shader for lighting.\n *\n * Inputs: GBuffer (albedo, normal, ARM, emission, depth), environment map\n * Output: HDR lit image (rgba16float)\n */\nclass LightingPass extends BasePass {\n constructor(engine = null) {\n super('Lighting', engine)\n\n this.pipeline = null\n this.computePipeline = null\n this.outputTexture = null\n this.environmentMap = null\n this.gbuffer = null\n this.shadowPass = null\n\n // Light data\n this.lights = []\n\n // Noise texture for shadow jittering (blue noise or bayer dither)\n this.noiseTexture = null\n this.noiseSize = 64\n this.noiseAnimated = true\n\n // AO texture from AOPass\n this.aoTexture = null\n\n // Environment encoding: 0 = equirectangular (default), 1 = octahedral (for captured probes)\n this.envEncoding = 0\n\n // Exposure override for probe rendering (null = use settings, number = override)\n // When capturing probes, we want raw HDR values without exposure\n this.exposureOverride = null\n\n // Reflection mode: flips environment sampling Y for planar reflections\n this.reflectionMode = false\n\n // Ambient capture mode: disables IBL on geometry, only direct lights + emissive + skybox background\n this.ambientCaptureMode = false\n\n // Tiled lighting buffers\n this.lightBuffer = null // Storage buffer for light data\n this.tileLightBuffer = null // Storage buffer for per-tile light indices\n this.tileCountBuffer = null // For debug: light counts per tile\n\n // Stats\n this.stats = {\n totalLights: 0,\n visibleLights: 0,\n pointLights: 0,\n spotLights: 0,\n culledByFrustum: 0,\n culledByDistance: 0,\n culledByOcclusion: 0\n }\n\n // HiZ pass reference for occlusion culling\n this.hizPass = null\n }\n\n /**\n * Set the HiZ pass for occlusion culling of lights\n * @param {HiZPass} hizPass - The HiZ pass instance\n */\n setHiZPass(hizPass) {\n this.hizPass = hizPass\n }\n\n // Convenience getters for lighting settings (with defaults for backward compatibility)\n get maxLights() { return this.settings?.lighting?.maxLights ?? 768 }\n get tileSize() { return this.settings?.lighting?.tileSize ?? 16 }\n get maxLightsPerTile() { return this.settings?.lighting?.maxLightsPerTile ?? 256 }\n get lightMaxDistance() { return this.settings?.lighting?.maxDistance ?? 240 }\n get lightCullingEnabled() { return this.settings?.lighting?.cullingEnabled ?? true }\n get shadowBias() { return this.settings?.shadow?.bias ?? 0.0005 }\n get shadowNormalBias() { return this.settings?.shadow?.normalBias ?? 0.015 }\n get shadowStrength() { return this.settings?.shadow?.strength ?? 1.0 }\n\n /**\n * Set the environment map for IBL\n * @param {Texture} envMap - HDR environment map\n * @param {number} encoding - 0 = equirectangular (default), 1 = octahedral (for captured probes)\n */\n setEnvironmentMap(envMap, encoding = 0) {\n this.environmentMap = envMap\n this.envEncoding = encoding\n this._needsRebuild = true\n }\n\n /**\n * Set the environment encoding type\n * @param {number} encoding - 0 = equirectangular (default), 1 = octahedral (for captured probes)\n */\n setEnvironmentEncoding(encoding) {\n this.envEncoding = encoding\n }\n\n /**\n * Set the GBuffer from GBufferPass\n * @param {GBuffer} gbuffer - GBuffer textures\n */\n async setGBuffer(gbuffer) {\n this.gbuffer = gbuffer\n this._needsRebuild = true\n this._computeBindGroupDirty = true\n // Create/recreate compute pipeline now that we have gbuffer\n if (!this.computePipeline && this.lightBuffer) {\n await this._createComputePipeline()\n }\n }\n\n /**\n * Set the shadow pass for shadow mapping\n * @param {ShadowPass} shadowPass - Shadow pass instance\n */\n setShadowPass(shadowPass) {\n this.shadowPass = shadowPass\n this._needsRebuild = true\n }\n\n /**\n * Set the noise texture for shadow jittering\n * @param {Texture} noise - Noise texture (blue noise or bayer dither)\n * @param {number} size - Texture size (assumed square)\n * @param {boolean} animated - Whether to animate noise offset each frame\n */\n setNoise(noise, size = 64, animated = true) {\n this.noiseTexture = noise\n this.noiseSize = size\n this.noiseAnimated = animated\n this._needsRebuild = true\n }\n\n /**\n * Set the AO texture from AOPass\n * @param {Texture} aoTexture - AO texture (r8unorm)\n */\n setAOTexture(aoTexture) {\n this.aoTexture = aoTexture\n this._needsRebuild = true\n }\n\n async _init() {\n const { canvas, device } = this.engine\n\n // Create output texture (HDR)\n this.outputTexture = await Texture.renderTarget(this.engine, 'rgba16float')\n\n // Initialize tiled lighting resources\n await this._initTiledLighting(canvas.width, canvas.height)\n }\n\n /**\n * Initialize tiled lighting compute resources\n */\n async _initTiledLighting(width, height) {\n const { device } = this.engine\n\n // Calculate tile counts\n this.tileCountX = Math.ceil(width / this.tileSize)\n this.tileCountY = Math.ceil(height / this.tileSize)\n const totalTiles = this.tileCountX * this.tileCountY\n\n // Light buffer: store all light data for GPU access\n // Each light: 96 bytes to match WGSL storage buffer alignment\n const lightBufferSize = this.maxLights * 96\n this.lightBuffer = device.createBuffer({\n label: 'Light Buffer',\n size: lightBufferSize,\n usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST,\n })\n this.lightBufferData = new ArrayBuffer(lightBufferSize)\n\n // Tile light indices buffer: for each tile, store count + up to maxLightsPerTile indices\n // Each entry is a u32 (4 bytes)\n const tileLightBufferSize = totalTiles * (this.maxLightsPerTile + 1) * 4\n this.tileLightBuffer = device.createBuffer({\n label: 'Tile Light Indices',\n size: tileLightBufferSize,\n usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST,\n })\n\n // Uniform buffer for compute shader\n // viewMatrix(64) + projectionMatrix(64) + inverseProjection(64) + screenSize(8) + tileCount(8) + lightCount(4) + near(4) + far(4) + padding(4) = 224 bytes\n this.cullUniformBuffer = device.createBuffer({\n label: 'Light Cull Uniforms',\n size: 224,\n usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST,\n })\n\n // Create compute pipeline for light culling\n await this._createComputePipeline()\n\n }\n\n /**\n * Create compute pipeline for light culling\n */\n async _createComputePipeline() {\n const { device } = this.engine\n\n if (!this.gbuffer) return\n\n const computeModule = device.createShaderModule({\n label: 'Light Culling Compute',\n code: lightCullingWGSL,\n })\n\n const computeBindGroupLayout = device.createBindGroupLayout({\n label: 'Light Culling Bind Group Layout',\n entries: [\n { binding: 0, visibility: GPUShaderStage.COMPUTE, buffer: { type: 'uniform' } },\n { binding: 1, visibility: GPUShaderStage.COMPUTE, buffer: { type: 'read-only-storage' } },\n { binding: 2, visibility: GPUShaderStage.COMPUTE, buffer: { type: 'storage' } },\n { binding: 3, visibility: GPUShaderStage.COMPUTE, texture: { sampleType: 'depth' } },\n ],\n })\n\n // Use async pipeline creation for non-blocking initialization\n this.computePipeline = await device.createComputePipelineAsync({\n label: 'Light Culling Pipeline',\n layout: device.createPipelineLayout({\n bindGroupLayouts: [computeBindGroupLayout],\n }),\n compute: {\n module: computeModule,\n entryPoint: 'main',\n },\n })\n\n this.computeBindGroupLayout = computeBindGroupLayout\n this._computeBindGroupDirty = true\n }\n\n /**\n * Create or update compute bind group\n */\n _updateComputeBindGroup() {\n const { device } = this.engine\n\n if (!this.gbuffer || !this.computePipeline) return\n\n this.computeBindGroup = device.createBindGroup({\n label: 'Light Culling Bind Group',\n layout: this.computeBindGroupLayout,\n entries: [\n { binding: 0, resource: { buffer: this.cullUniformBuffer } },\n { binding: 1, resource: { buffer: this.lightBuffer } },\n { binding: 2, resource: { buffer: this.tileLightBuffer } },\n { binding: 3, resource: this.gbuffer.depth.view },\n ],\n })\n\n this._computeBindGroupDirty = false\n }\n\n /**\n * Build or rebuild the fragment pipeline\n */\n async _buildPipeline() {\n if (!this.gbuffer || !this.environmentMap) {\n console.warn('LightingPass: Missing gbuffer or environmentMap')\n return\n }\n\n // Check all required textures\n if (!this.noiseTexture) {\n console.warn('LightingPass: Missing noiseTexture')\n return\n }\n if (!this.aoTexture) {\n console.warn('LightingPass: Missing aoTexture')\n return\n }\n\n this.pipeline = await Pipeline.create(this.engine, {\n label: 'lighting',\n wgslSource: lightingWGSL,\n isPostProcessing: true,\n textures: [this.gbuffer, this.environmentMap, this.noiseTexture, this.aoTexture],\n renderTarget: this.outputTexture,\n shadowPass: this.shadowPass,\n tileLightBuffer: this.tileLightBuffer,\n lightBuffer: this.lightBuffer,\n tileSize: this.tileSize,\n tileCountX: this.tileCountX,\n maxLightsPerTile: this.maxLightsPerTile,\n })\n\n this._needsRebuild = false\n }\n\n /**\n * Add a light to the scene\n * @param {Object} light - Light data\n */\n addLight(light) {\n if (this.lights.length < this.maxLights) {\n this.lights.push({\n enabled: light.enabled !== false ? 1 : 0,\n position: light.position || [0, 0, 0],\n color: light.color || [1, 1, 1, 1],\n direction: light.direction || [0, -1, 0],\n geom: light.geom || [10, 0.3, 0.5, 0], // radius, innerCone, outerCone\n lightType: light.lightType || 0, // 0=dir, 1=point, 2=spot\n shadowIndex: light.shadowIndex || -1\n })\n }\n }\n\n /**\n * Clear all lights\n */\n clearLights() {\n this.lights = []\n }\n\n /**\n * Update lights from entity system with frustum culling and distance ordering\n * @param {Array} lightEntities - Array of entities with lights\n * @param {Camera} camera - Camera for frustum culling and distance calculation\n */\n updateLightsFromEntities(lightEntities, camera) {\n this.clearLights()\n\n // Reset stats\n this.stats.totalLights = 0\n this.stats.visibleLights = 0\n this.stats.culledByFrustum = 0\n this.stats.culledByDistance = 0\n this.stats.culledByOcclusion = 0\n\n // Separate lights by type for different handling\n const spotlights = []\n const pointLights = []\n\n // Create camera frustum for culling\n let cameraFrustum = null\n if (camera && this.lightCullingEnabled) {\n cameraFrustum = new Frustum()\n cameraFrustum.update(\n camera.view,\n camera.proj,\n camera.position,\n camera.direction,\n camera.fov || Math.PI / 4,\n camera.aspect || 1.0,\n camera.near || 0.1,\n camera.far || 1000\n )\n }\n\n for (const { id, entity } of lightEntities) {\n if (!entity.light || !entity.light.enabled) continue\n\n const light = entity.light\n this.stats.totalLights++\n\n // Transform light position to world space\n const worldPos = [\n entity.position[0] + (light.position?.[0] || 0),\n entity.position[1] + (light.position?.[1] || 0),\n entity.position[2] + (light.position?.[2] || 0)\n ]\n\n const lightData = {\n enabled: true,\n position: worldPos,\n direction: light.direction || [0, -1, 0],\n color: light.color || [1, 1, 1, 1],\n geom: [...(light.geom || [10, 0.3, 0.5, 0])], // Copy array to avoid modifying original\n lightType: light.lightType || 1\n }\n\n const lightRadius = lightData.geom[0] || 10\n\n // Calculate distance and fade for ALL light types (including spotlights)\n let distance = 0\n let distanceFade = 1.0\n if (camera) {\n const dx = worldPos[0] - camera.position[0]\n const dy = worldPos[1] - camera.position[1]\n const dz = worldPos[2] - camera.position[2]\n distance = Math.sqrt(dx * dx + dy * dy + dz * dz)\n\n let maxDist = lightData.geom[0] < 5 ? this.lightMaxDistance * 0.25 : this.lightMaxDistance\n // Distance cull: skip if light is too far (accounting for light radius)\n const effectiveDistance = distance - lightRadius\n if (effectiveDistance > maxDist) {\n this.stats.culledByDistance++\n continue\n }\n\n // Calculate fade: 1.0 at 80% distance, 0.0 at 100% distance (smooth fade to avoid popping)\n const fadeStart = maxDist * 0.8\n if (effectiveDistance > fadeStart) {\n distanceFade = 1.0 - (effectiveDistance - fadeStart) / (maxDist - fadeStart)\n distanceFade = Math.max(0, Math.min(1, distanceFade))\n }\n }\n\n // Store distance fade in geom.w (will be applied in shader)\n lightData.geom[3] = distanceFade\n\n // Skip lights that are nearly invisible (faded out)\n if (distanceFade <= 0.01) {\n this.stats.culledByDistance++\n continue\n }\n\n // Create bounding sphere for culling tests\n const lightBsphere = { center: worldPos, radius: lightRadius }\n\n // Spotlights\n if (lightData.lightType === 2) {\n // Frustum cull spotlights\n if (cameraFrustum && this.lightCullingEnabled) {\n if (!cameraFrustum.testSpherePlanes(lightBsphere)) {\n this.stats.culledByFrustum++\n continue\n }\n }\n\n // HiZ occlusion cull spotlights\n if (this.hizPass && camera && this.settings?.occlusionCulling?.enabled) {\n if (this.hizPass.testSphereOcclusion(lightBsphere, camera.viewProj, camera.near, camera.far, camera.position)) {\n this.stats.culledByOcclusion++\n continue\n }\n }\n\n lightData._distance = distance\n spotlights.push(lightData)\n continue\n }\n\n // Point lights: apply frustum culling\n if (cameraFrustum && this.lightCullingEnabled) {\n if (!cameraFrustum.testSpherePlanes(lightBsphere)) {\n this.stats.culledByFrustum++\n continue\n }\n }\n\n // HiZ occlusion cull point lights\n if (this.hizPass && camera && this.settings?.occlusionCulling?.enabled) {\n if (this.hizPass.testSphereOcclusion(lightBsphere, camera.viewProj, camera.near, camera.far, camera.position)) {\n this.stats.culledByOcclusion++\n continue\n }\n }\n\n // Store distance for sorting\n lightData._distance = distance\n pointLights.push(lightData)\n }\n\n // Sort point lights by distance (closest first)\n pointLights.sort((a, b) => a._distance - b._distance)\n\n // Add spotlights first (they need consistent indices for shadow mapping)\n for (const light of spotlights) {\n this.addLight(light)\n }\n this.stats.spotLights = spotlights.length\n\n // Add point lights (closest first, up to remaining capacity)\n let addedPointLights = 0\n for (const light of pointLights) {\n if (this.lights.length >= this.maxLights) break\n this.addLight(light)\n addedPointLights++\n }\n this.stats.pointLights = addedPointLights\n\n this.stats.visibleLights = this.lights.length\n\n }\n\n async _execute(context) {\n const { device, canvas } = this.engine\n const { camera, lights: contextLights, mainLight } = context\n\n // Use output texture size (not canvas size) for proper probe rendering\n const targetWidth = this.outputTexture?.texture?.width || canvas.width\n const targetHeight = this.outputTexture?.texture?.height || canvas.height\n\n // Get environment settings from engine (with fallbacks)\n // In ambient capture mode (emissive only): disable ALL lighting, only emissive + skybox\n const ambientColor = this.ambientCaptureMode\n ? [0, 0, 0, 0] // No ambient in emissive-only mode\n : (this.settings?.environment?.ambientColor ?? [0.7, 0.75, 0.9, 0.2])\n const environmentDiffuse = this.ambientCaptureMode ? 0.0 : (this.settings?.environment?.diffuse ?? 4.0)\n const environmentSpecular = this.ambientCaptureMode ? 0.0 : (this.settings?.environment?.specular ?? 4.0)\n // Use override if set (for probe capture), otherwise use settings\n const exposure = this.exposureOverride ?? this.settings?.environment?.exposure ?? 1.6\n\n // Rebuild pipeline if needed\n if (this._needsRebuild) {\n await this._buildPipeline()\n }\n\n // If rebuild was attempted but failed, don't use stale pipeline with old bind groups\n if (!this.pipeline || this._needsRebuild) {\n return\n }\n\n // Update compute bind group if needed\n if (this._computeBindGroupDirty && this.gbuffer) {\n this._updateComputeBindGroup()\n }\n\n // ===================\n // LIGHT CULLING COMPUTE PASS\n // ===================\n // Skip light culling if:\n // - In ambient capture mode (emissive only - no point/spot lights)\n // - Light culling is disabled in settings\n // - No lights to process\n const shouldRunLightCulling = this.computePipeline &&\n this.computeBindGroup &&\n this.lights.length > 0 &&\n !this.ambientCaptureMode &&\n this.lightCullingEnabled\n\n if (shouldRunLightCulling) {\n // Update light buffer with current lights data\n this._updateLightBuffer()\n\n // Update cull uniforms\n const cullUniformData = new Float32Array(56) // 224 bytes / 4\n cullUniformData.set(camera.view, 0) // viewMatrix (16 floats)\n cullUniformData.set(camera.proj, 16) // projectionMatrix (16 floats)\n cullUniformData.set(camera.iProj || mat4.create(), 32) // inverseProjection (16 floats)\n cullUniformData[48] = targetWidth // screenSize.x\n cullUniformData[49] = targetHeight // screenSize.y\n const cullUniformDataU32 = new Uint32Array(cullUniformData.buffer)\n cullUniformDataU32[50] = this.tileCountX // tileCount.x\n cullUniformDataU32[51] = this.tileCountY // tileCount.y\n cullUniformDataU32[52] = this.lights.length // lightCount\n cullUniformData[53] = camera.near || 0.1 // nearPlane from camera\n cullUniformData[54] = camera.far || 10000 // farPlane from camera (use large default)\n cullUniformData[55] = 0 // padding\n\n device.queue.writeBuffer(this.cullUniformBuffer, 0, cullUniformData)\n\n // Run compute pass\n const computeEncoder = device.createCommandEncoder({ label: 'Light Culling' })\n const computePass = computeEncoder.beginComputePass({ label: 'Light Culling Pass' })\n\n computePass.setPipeline(this.computePipeline)\n computePass.setBindGroup(0, this.computeBindGroup)\n computePass.dispatchWorkgroups(this.tileCountX, this.tileCountY, 1)\n computePass.end()\n\n device.queue.submit([computeEncoder.finish()])\n }\n\n // ===================\n // LIGHTING FRAGMENT PASS\n // ===================\n\n // Get main light settings (use defaults if not provided)\n // In ambient capture mode (emissive only): disable main light\n const mainLightEnabled = this.ambientCaptureMode ? false : (mainLight?.enabled !== false)\n const mainLightIntensity = mainLight?.intensity ?? 1.0\n const mainLightColor = mainLight?.color || [1.0, 0.95, 0.9]\n const lightDir = vec3.fromValues(\n mainLight?.direction?.[0] ?? -1.0,\n mainLight?.direction?.[1] ?? 1.0,\n mainLight?.direction?.[2] ?? -0.5\n )\n vec3.normalize(lightDir, lightDir)\n\n // Get shadow info\n let shadowMapSize = 2048\n if (this.shadowPass) {\n shadowMapSize = this.shadowPass.shadowMapSize\n\n }\n\n // Get cascade sizes from shadow pass\n const cascadeSizes = this.shadowPass ? this.shadowPass.getCascadeSizes() : [50, 200, 1000]\n\n // Noise offset (0..1) - random each frame if animated, 0 if static\n const noiseOffsetX = this.noiseAnimated ? Math.random() : 0\n const noiseOffsetY = this.noiseAnimated ? Math.random() : 0\n\n // Set uniforms\n this.pipeline.uniformValues.set({\n inverseViewProjection: camera.iViewProj,\n inverseProjection: camera.iProj,\n inverseView: camera.iView,\n cameraPosition: camera.position,\n canvasSize: [targetWidth, targetHeight],\n lightDir: lightDir,\n lightColor: [\n mainLightColor[0],\n mainLightColor[1],\n mainLightColor[2],\n mainLightEnabled ? mainLightIntensity * 12.0 : 0.0\n ],\n ambientColor: ambientColor,\n environmentParams: [\n environmentDiffuse,\n environmentSpecular,\n this.environmentMap?.mipCount || 1,\n exposure\n ],\n shadowParams: [\n this.shadowBias,\n this.shadowNormalBias,\n this.shadowStrength,\n shadowMapSize\n ],\n cascadeSizes: [cascadeSizes[0], cascadeSizes[1], cascadeSizes[2], 0],\n tileParams: [this.tileSize, this.tileCountX, this.maxLightsPerTile, this.ambientCaptureMode ? 0 : this.lights.length],\n noiseParams: [this.noiseSize, noiseOffsetX, noiseOffsetY, this.envEncoding],\n cameraParams: [camera.near || 0.05, camera.far || 1000, this.reflectionMode ? 1.0 : 0.0, this.settings?.lighting?.directSpecularMultiplier ?? 3.0],\n specularBoost: [\n this.settings?.lighting?.specularBoost ?? 0.0,\n this.settings?.lighting?.specularBoostRoughnessCutoff ?? 0.5,\n 0.0,\n 0.0\n ],\n })\n\n // Render lighting pass\n this.pipeline.render()\n }\n\n /**\n * Update light buffer with current lights data\n * Must match WGSL struct Light alignment in storage buffer:\n * - enabled: u32 at offset 0\n * - position: vec3f at offset 16 (aligned to 16)\n * - color: vec4f at offset 32\n * - direction: vec3f at offset 48\n * - geom: vec4f at offset 64\n * - shadowIndex: i32 at offset 80\n * - struct size: 96 bytes (24 floats)\n */\n _updateLightBuffer() {\n const { device } = this.engine\n\n // Get spotlight shadow slot assignments\n let spotShadowSlots = null\n if (this.shadowPass) {\n spotShadowSlots = this.shadowPass.getSpotShadowSlots()\n }\n\n // Each light is 96 bytes (24 floats) to match WGSL storage buffer alignment\n const lightData = new Float32Array(this.maxLights * 24)\n const lightDataU32 = new Uint32Array(lightData.buffer)\n const lightDataI32 = new Int32Array(lightData.buffer)\n\n for (let i = 0; i < this.maxLights; i++) {\n const light = this.lights[i]\n const offset = i * 24\n\n if (light) {\n // enabled: u32 at offset 0\n lightDataU32[offset + 0] = light.enabled ? 1 : 0\n // padding: 12 bytes (3 floats)\n\n // position: vec3f at offset 16 (4 floats)\n lightData[offset + 4] = light.position[0]\n lightData[offset + 5] = light.position[1]\n lightData[offset + 6] = light.position[2]\n // padding: 4 bytes\n\n // color: vec4f at offset 32 (8 floats)\n lightData[offset + 8] = light.color[0]\n lightData[offset + 9] = light.color[1]\n lightData[offset + 10] = light.color[2]\n lightData[offset + 11] = light.color[3]\n\n // direction: vec3f at offset 48 (12 floats)\n lightData[offset + 12] = light.direction[0]\n lightData[offset + 13] = light.direction[1]\n lightData[offset + 14] = light.direction[2]\n // padding: 4 bytes\n\n // geom: vec4f at offset 64 (16 floats)\n lightData[offset + 16] = light.geom[0]\n lightData[offset + 17] = light.geom[1]\n lightData[offset + 18] = light.geom[2]\n lightData[offset + 19] = light.geom[3]\n\n // shadowIndex: i32 at offset 80 (20 floats)\n const shadowIndex = spotShadowSlots ? (spotShadowSlots[i] !== undefined ? spotShadowSlots[i] : -1) : -1\n lightDataI32[offset + 20] = shadowIndex\n // padding: 12 bytes to reach 96\n } else {\n lightDataU32[offset] = 0 // disabled\n }\n }\n\n device.queue.writeBuffer(this.lightBuffer, 0, lightData)\n }\n\n async _resize(width, height) {\n // Recreate output texture at new size\n this.outputTexture = await Texture.renderTarget(this.engine, 'rgba16float', width, height)\n\n // Recreate tiled lighting buffers for new size\n await this._initTiledLighting(width, height)\n\n this._needsRebuild = true\n this._computeBindGroupDirty = true\n }\n\n _destroy() {\n this.pipeline = null\n this.outputTexture = null\n }\n\n /**\n * Get the output texture for use by subsequent passes\n */\n getOutputTexture() {\n return this.outputTexture\n }\n\n /**\n * Get the light buffer for volumetric fog\n */\n getLightBuffer() {\n return this.lightBuffer\n }\n\n /**\n * Get the current light count\n */\n getLightCount() {\n return this.lights?.length ?? 0\n }\n}\n\nexport { LightingPass }\n","// Particle simulation compute shader\n// Updates particle positions, velocities, lifetimes, and handles spawning\n// Also calculates per-particle lighting with temporal smoothing\n\nconst WORKGROUP_SIZE: u32 = 64u;\nconst MAX_EMITTERS: u32 = 16u;\nconst CASCADE_COUNT = 3;\nconst MAX_LIGHTS = 64u;\nconst MAX_SPOT_SHADOWS = 8;\nconst LIGHTING_FADE_TIME: f32 = 0.3; // Seconds to smooth lighting changes\n\n// Spot shadow atlas constants\nconst SPOT_ATLAS_WIDTH: f32 = 2048.0;\nconst SPOT_ATLAS_HEIGHT: f32 = 2048.0;\nconst SPOT_TILE_SIZE: f32 = 512.0;\nconst SPOT_TILES_PER_ROW: i32 = 4;\n\n// Particle data structure (80 bytes, matches JS PARTICLE_STRIDE)\n// flags: bit 0 = alive, bit 1 = additive, bits 8-15 = emitter index\nstruct Particle {\n position: vec3f, // World position\n lifetime: f32, // Remaining life (seconds), <=0 = dead\n velocity: vec3f, // Movement per second\n maxLifetime: f32, // Initial lifetime for fade calculation\n color: vec4f, // RGBA with computed alpha\n size: vec2f, // Current width/height\n rotation: f32, // Current rotation in radians\n flags: u32, // Bit 0 = alive, bit 1 = additive, bits 8-15 = emitter index\n lighting: vec3f, // Pre-computed lighting (smoothed over time)\n lightingPad: f32, // Padding for alignment\n}\n\n// Light structure (must match LightingPass)\nstruct Light {\n enabled: u32,\n position: vec3f,\n color: vec4f,\n direction: vec3f,\n geom: vec4f, // x = radius, y = inner cone, z = outer cone, w = distance fade\n shadowIndex: i32,\n}\n\nstruct CascadeMatrices {\n matrices: array<mat4x4<f32>, CASCADE_COUNT>,\n}\n\nstruct SpotShadowMatrices {\n matrices: array<mat4x4<f32>, MAX_SPOT_SHADOWS>,\n}\n\n// Spawn request structure (64 bytes, matches JS SPAWN_REQUEST_STRIDE)\nstruct SpawnRequest {\n position: vec3f,\n lifetime: f32,\n velocity: vec3f,\n maxLifetime: f32,\n color: vec4f,\n startSize: f32,\n endSize: f32,\n rotation: f32, // Initial rotation (random)\n flags: u32, // Bit 0 = alive, bit 1 = additive, bits 8-15 = emitter index\n}\n\n// Per-emitter simulation settings (64 bytes each)\nstruct EmitterSettings {\n gravity: vec3f,\n drag: f32,\n turbulence: f32,\n fadeIn: f32,\n fadeOut: f32,\n rotationSpeed: f32,\n startSize: f32,\n endSize: f32,\n baseAlpha: f32,\n padding: f32,\n}\n\nstruct SimulationUniforms {\n dt: f32,\n time: f32,\n maxParticles: u32,\n emitterCount: u32,\n // Lighting parameters\n cameraPosition: vec3f,\n shadowBias: f32,\n lightDir: vec3f,\n shadowStrength: f32,\n lightColor: vec4f,\n ambientColor: vec4f,\n cascadeSizes: vec4f,\n lightCount: u32,\n pad1: u32,\n pad2: u32,\n pad3: u32,\n}\n\nstruct Counters {\n aliveCount: atomic<u32>,\n nextFreeIndex: atomic<u32>,\n spawnCount: u32,\n frameCount: u32,\n}\n\n// Core bindings\n@group(0) @binding(0) var<uniform> uniforms: SimulationUniforms;\n@group(0) @binding(1) var<storage, read_write> particles: array<Particle>;\n@group(0) @binding(2) var<storage, read_write> counters: Counters;\n@group(0) @binding(3) var<storage, read> spawnRequests: array<SpawnRequest>;\n@group(0) @binding(4) var<storage, read> emitterSettings: array<EmitterSettings>;\n\n// Lighting bindings\n@group(0) @binding(5) var<storage, read> emitterRenderSettings: array<EmitterRenderSettings>;\n@group(0) @binding(6) var shadowMapArray: texture_depth_2d_array;\n@group(0) @binding(7) var shadowSampler: sampler_comparison;\n@group(0) @binding(8) var<storage, read> cascadeMatrices: CascadeMatrices;\n@group(0) @binding(9) var<storage, read> lights: array<Light, MAX_LIGHTS>;\n@group(0) @binding(10) var spotShadowAtlas: texture_depth_2d;\n@group(0) @binding(11) var<storage, read> spotMatrices: SpotShadowMatrices;\n\n// Emitter render settings (lit, emissive)\nstruct EmitterRenderSettings {\n lit: f32,\n emissive: f32,\n softness: f32,\n zOffset: f32,\n}\n\n// Simple noise function for turbulence\nfn hash(p: vec3f) -> f32 {\n var p3 = fract(p * 0.1031);\n p3 += dot(p3, p3.yzx + 33.33);\n return fract((p3.x + p3.y) * p3.z);\n}\n\nfn noise3D(p: vec3f) -> vec3f {\n let i = floor(p);\n let f = fract(p);\n let u = f * f * (3.0 - 2.0 * f);\n\n return vec3f(\n mix(hash(i), hash(i + vec3f(1.0, 0.0, 0.0)), u.x),\n mix(hash(i + vec3f(0.0, 1.0, 0.0)), hash(i + vec3f(1.0, 1.0, 0.0)), u.x),\n mix(hash(i + vec3f(0.0, 0.0, 1.0)), hash(i + vec3f(1.0, 0.0, 1.0)), u.x)\n ) * 2.0 - 1.0;\n}\n\n// ============= LIGHTING FUNCTIONS =============\n\n// Squircle distance for cascade selection\nfn squircleDistanceXZ(offset: vec2f, size: f32) -> f32 {\n let normalized = offset / size;\n let absNorm = abs(normalized);\n return pow(pow(absNorm.x, 4.0) + pow(absNorm.y, 4.0), 0.25);\n}\n\n// Sample shadow from cascade (single tap for compute shader performance)\nfn sampleCascadeShadow(worldPos: vec3f, cascadeIndex: i32) -> f32 {\n let bias = uniforms.shadowBias;\n let lightMatrix = cascadeMatrices.matrices[cascadeIndex];\n let lightSpacePos = lightMatrix * vec4f(worldPos, 1.0);\n let projCoords = lightSpacePos.xyz / lightSpacePos.w;\n let shadowUV = vec2f(projCoords.x * 0.5 + 0.5, 0.5 - projCoords.y * 0.5);\n let currentDepth = projCoords.z - bias;\n\n // Check bounds\n if (shadowUV.x < 0.0 || shadowUV.x > 1.0 || shadowUV.y < 0.0 || shadowUV.y > 1.0 ||\n currentDepth < 0.0 || currentDepth > 1.0) {\n return 1.0;\n }\n\n // Single tap shadow for compute shader (faster)\n return textureSampleCompareLevel(shadowMapArray, shadowSampler, shadowUV, cascadeIndex, currentDepth);\n}\n\n// Calculate cascade shadow for particle\nfn calculateParticleShadow(worldPos: vec3f) -> f32 {\n let camXZ = vec2f(uniforms.cameraPosition.x, uniforms.cameraPosition.z);\n let posXZ = vec2f(worldPos.x, worldPos.z);\n let offsetXZ = posXZ - camXZ;\n\n let dist0 = squircleDistanceXZ(offsetXZ, uniforms.cascadeSizes.x);\n let dist1 = squircleDistanceXZ(offsetXZ, uniforms.cascadeSizes.y);\n let dist2 = squircleDistanceXZ(offsetXZ, uniforms.cascadeSizes.z);\n\n var shadow = 1.0;\n if (dist0 < 0.95) {\n shadow = sampleCascadeShadow(worldPos, 0);\n } else if (dist1 < 0.95) {\n shadow = sampleCascadeShadow(worldPos, 1);\n } else if (dist2 < 0.95) {\n shadow = sampleCascadeShadow(worldPos, 2);\n }\n\n return mix(1.0 - uniforms.shadowStrength, 1.0, shadow);\n}\n\n// Calculate spot shadow (single tap)\nfn calculateSpotShadow(worldPos: vec3f, slotIndex: i32) -> f32 {\n if (slotIndex < 0 || slotIndex >= MAX_SPOT_SHADOWS) {\n return 1.0;\n }\n\n let lightMatrix = spotMatrices.matrices[slotIndex];\n let lightSpacePos = lightMatrix * vec4f(worldPos, 1.0);\n let w = max(abs(lightSpacePos.w), 0.0001) * sign(lightSpacePos.w + 0.0001);\n let projCoords = lightSpacePos.xyz / w;\n\n if (projCoords.z < 0.0 || projCoords.z > 1.0 || abs(projCoords.x) > 1.0 || abs(projCoords.y) > 1.0) {\n return 1.0;\n }\n\n let col = slotIndex % SPOT_TILES_PER_ROW;\n let row = slotIndex / SPOT_TILES_PER_ROW;\n let localUV = vec2f(projCoords.x * 0.5 + 0.5, 0.5 - projCoords.y * 0.5);\n let tileOffset = vec2f(f32(col), f32(row)) * SPOT_TILE_SIZE;\n let atlasUV = (tileOffset + localUV * SPOT_TILE_SIZE) / vec2f(SPOT_ATLAS_WIDTH, SPOT_ATLAS_HEIGHT);\n let currentDepth = clamp(projCoords.z - uniforms.shadowBias * 3.0, 0.001, 0.999);\n\n return textureSampleCompareLevel(spotShadowAtlas, shadowSampler, atlasUV, currentDepth);\n}\n\n// Calculate full lighting for a particle at given position\nfn calculateParticleLighting(worldPos: vec3f, emitterIdx: u32) -> vec3f {\n let renderSettings = emitterRenderSettings[min(emitterIdx, MAX_EMITTERS - 1u)];\n\n // Unlit particles - just return emissive multiplier as 1.0\n if (renderSettings.lit < 0.5) {\n return vec3f(renderSettings.emissive);\n }\n\n // Particle normal is always up\n let normal = vec3f(0.0, 1.0, 0.0);\n\n // Start with ambient\n var lighting = uniforms.ambientColor.rgb * uniforms.ambientColor.a;\n\n // Main directional light with shadow\n let shadow = calculateParticleShadow(worldPos);\n let NdotL = max(dot(normal, uniforms.lightDir), 0.0);\n lighting += uniforms.lightColor.rgb * uniforms.lightColor.a * NdotL * shadow;\n\n // Point and spot lights\n let lightCount = uniforms.lightCount;\n for (var i = 0u; i < min(lightCount, MAX_LIGHTS); i++) {\n let light = lights[i];\n if (light.enabled == 0u) { continue; }\n\n let lightVec = light.position - worldPos;\n let dist = length(lightVec);\n let lightDir = normalize(lightVec);\n let distanceFade = light.geom.w;\n\n // Attenuation\n let radius = max(light.geom.x, 0.001);\n let attenuation = max(0.0, 1.0 - dist / radius);\n let attenuationSq = attenuation * attenuation * distanceFade;\n if (attenuationSq <= 0.001) { continue; }\n\n // Spot cone\n let innerCone = light.geom.y;\n let outerCone = light.geom.z;\n var spotAttenuation = 1.0;\n let isSpotlight = outerCone > 0.0;\n if (isSpotlight) {\n let spotCos = dot(-lightDir, normalize(light.direction));\n spotAttenuation = smoothstep(outerCone, innerCone, spotCos);\n }\n if (spotAttenuation <= 0.001) { continue; }\n\n // Spot shadow\n var lightShadow = 1.0;\n if (isSpotlight && light.shadowIndex >= 0) {\n lightShadow = calculateSpotShadow(worldPos, light.shadowIndex);\n }\n\n // Add contribution\n let pNdotL = max(dot(normal, lightDir), 0.0);\n let pIntensity = light.color.a * attenuationSq * spotAttenuation * lightShadow * pNdotL;\n lighting += pIntensity * light.color.rgb;\n }\n\n return lighting * renderSettings.emissive;\n}\n\n// Spawn pass: initialize new particles from spawn requests\n@compute @workgroup_size(WORKGROUP_SIZE)\nfn spawn(@builtin(global_invocation_id) globalId: vec3u) {\n let idx = globalId.x;\n\n // Check if this thread should process a spawn request\n if (idx >= counters.spawnCount) {\n return;\n }\n\n // Find a free particle slot (try multiple slots if needed)\n let maxAttempts = 8u; // Limit search to avoid infinite loop\n var particleIdx = 0u;\n var foundSlot = false;\n\n for (var attempt = 0u; attempt < maxAttempts; attempt++) {\n let rawIdx = atomicAdd(&counters.nextFreeIndex, 1u);\n particleIdx = rawIdx % uniforms.maxParticles;\n\n // Check if this slot is free (particle is dead)\n let existing = particles[particleIdx];\n if ((existing.flags & 1u) == 0u || existing.lifetime <= 0.0) {\n foundSlot = true;\n break;\n }\n }\n\n // If no free slot found, skip this spawn\n if (!foundSlot) {\n return;\n }\n\n // Read spawn request\n let req = spawnRequests[idx];\n\n // Initialize particle from spawn request\n var p: Particle;\n p.position = req.position;\n p.lifetime = req.lifetime;\n p.velocity = req.velocity;\n p.maxLifetime = req.maxLifetime;\n p.color = req.color;\n p.size = vec2f(req.startSize, req.startSize);\n p.rotation = req.rotation; // Random initial rotation\n p.flags = req.flags;\n // Initialize lighting to ambient (will smooth towards actual lighting)\n p.lighting = uniforms.ambientColor.rgb * uniforms.ambientColor.a;\n p.lightingPad = 0.0;\n\n // Store particle\n particles[particleIdx] = p;\n\n // Increment alive count\n atomicAdd(&counters.aliveCount, 1u);\n}\n\n// Simulate pass: update existing particles\n@compute @workgroup_size(WORKGROUP_SIZE)\nfn simulate(@builtin(global_invocation_id) globalId: vec3u) {\n let idx = globalId.x;\n\n // Bounds check\n if (idx >= uniforms.maxParticles) {\n return;\n }\n\n var p = particles[idx];\n\n // Skip dead particles\n if ((p.flags & 1u) == 0u || p.lifetime <= 0.0) {\n return;\n }\n\n // Get emitter index from flags (bits 8-15)\n let emitterIdx = (p.flags >> 8u) & 0xFFu;\n let settings = emitterSettings[min(emitterIdx, MAX_EMITTERS - 1u)];\n\n let dt = uniforms.dt;\n\n // Update lifetime\n p.lifetime -= dt;\n\n // Check if particle died\n if (p.lifetime <= 0.0) {\n p.flags = 0u; // Mark as dead\n p.lifetime = 0.0;\n atomicSub(&counters.aliveCount, 1u);\n particles[idx] = p;\n return;\n }\n\n // Calculate life progress (0 = just born, 1 = about to die)\n let lifeProgress = 1.0 - (p.lifetime / p.maxLifetime);\n\n // Apply gravity (per-emitter)\n p.velocity += settings.gravity * dt;\n\n // Apply drag (per-emitter)\n let dragFactor = 1.0 - settings.drag * dt;\n p.velocity *= max(dragFactor, 0.0);\n\n // Apply turbulence (per-emitter)\n if (settings.turbulence > 0.0) {\n let noisePos = p.position * 0.5 + vec3f(uniforms.time * 0.1, 0.0, 0.0);\n let turbulenceForce = noise3D(noisePos + vec3f(p.rotation * 10.0)) * settings.turbulence;\n p.velocity += turbulenceForce * dt * 10.0;\n }\n\n // Update position\n p.position += p.velocity * dt;\n\n // Interpolate size over lifetime (per-emitter)\n let currentSize = mix(settings.startSize, settings.endSize, lifeProgress);\n p.size = vec2f(currentSize, currentSize);\n\n // Update rotation (per-emitter)\n let rotationDir = select(-1.0, 1.0, p.rotation >= 0.0);\n p.rotation += rotationDir * settings.rotationSpeed * dt;\n\n // Calculate alpha with fade in/out (per-emitter)\n var alpha = settings.baseAlpha;\n\n // Fade in\n let fadeInDuration = settings.fadeIn / p.maxLifetime;\n if (lifeProgress < fadeInDuration && fadeInDuration > 0.0) {\n alpha *= lifeProgress / fadeInDuration;\n }\n\n // Fade out\n let fadeOutStart = 1.0 - (settings.fadeOut / p.maxLifetime);\n if (lifeProgress > fadeOutStart && fadeOutStart < 1.0) {\n let fadeOutProgress = (lifeProgress - fadeOutStart) / (1.0 - fadeOutStart);\n alpha *= 1.0 - fadeOutProgress;\n }\n\n // Only update alpha, preserve RGB from spawn\n p.color.a = alpha;\n\n // Calculate target lighting at particle center\n let targetLighting = calculateParticleLighting(p.position, emitterIdx);\n\n // Smooth lighting over time (exponential moving average)\n // lerpFactor = 1 - exp(-dt / fadeTime) ≈ dt / fadeTime for small dt\n let lerpFactor = clamp(dt / LIGHTING_FADE_TIME, 0.0, 1.0);\n p.lighting = mix(p.lighting, targetLighting, lerpFactor);\n\n // Write updated particle\n particles[idx] = p;\n}\n\n// Reset counters (run once per frame before spawn)\n@compute @workgroup_size(1)\nfn resetCounters() {\n // Reset spawn-related counter but preserve alive count\n atomicStore(&counters.nextFreeIndex, 0u);\n}\n","// Particle rendering shader\n// Renders billboarded quads for each alive particle with soft depth fade\n// Supports lighting, shadows, point/spot lights, IBL, and emissive brightness\n\nconst PI = 3.14159265359;\nconst CASCADE_COUNT = 3;\nconst MAX_EMITTERS = 16u;\nconst MAX_LIGHTS = 64u;\nconst MAX_SPOT_SHADOWS = 8;\n\n// Spot shadow atlas constants (must match LightingPass)\nconst SPOT_ATLAS_WIDTH: f32 = 2048.0;\nconst SPOT_ATLAS_HEIGHT: f32 = 2048.0;\nconst SPOT_TILE_SIZE: f32 = 512.0;\nconst SPOT_TILES_PER_ROW: i32 = 4;\n\n// Particle data structure (must match particle_simulate.wgsl)\nstruct Particle {\n position: vec3f,\n lifetime: f32,\n velocity: vec3f,\n maxLifetime: f32,\n color: vec4f,\n size: vec2f,\n rotation: f32, // Current rotation in radians\n flags: u32,\n lighting: vec3f, // Pre-computed lighting (smoothed in compute shader)\n lightingPad: f32,\n}\n\n// Light structure (must match LightingPass)\nstruct Light {\n enabled: u32,\n position: vec3f,\n color: vec4f,\n direction: vec3f,\n geom: vec4f, // x = radius, y = inner cone, z = outer cone, w = distance fade\n shadowIndex: i32, // -1 if no shadow, 0-7 for spot shadow slot\n}\n\n// Per-emitter settings (must match ParticlePass.js)\nstruct EmitterRenderSettings {\n lit: f32, // 0 = unlit, 1 = lit\n emissive: f32, // Brightness multiplier (1 = normal, >1 = glow)\n softness: f32,\n zOffset: f32,\n}\n\n// Spot shadow matrices\nstruct SpotShadowMatrices {\n matrices: array<mat4x4<f32>, MAX_SPOT_SHADOWS>,\n}\n\nstruct ParticleUniforms {\n viewMatrix: mat4x4f,\n projectionMatrix: mat4x4f,\n cameraPosition: vec3f,\n time: f32,\n cameraRight: vec3f,\n softness: f32,\n cameraUp: vec3f,\n zOffset: f32,\n screenSize: vec2f,\n near: f32,\n far: f32,\n blendMode: f32, // 0 = alpha, 1 = additive\n lit: f32, // 0 = unlit, 1 = simple lighting (global fallback)\n shadowBias: f32,\n shadowStrength: f32,\n // Lighting uniforms\n lightDir: vec3f,\n shadowMapSize: f32,\n lightColor: vec4f,\n ambientColor: vec4f,\n cascadeSizes: vec4f, // x, y, z = cascade half-widths\n // IBL uniforms\n envParams: vec4f, // x = diffuse level, y = mip count, z = encoding (0=equirect, 1=octahedral), w = exposure\n // Light count\n lightParams: vec4u, // x = light count, y = unused, z = unused, w = unused\n // Fog uniforms\n fogColor: vec3f,\n fogEnabled: f32,\n fogDistances: vec3f, // [near, mid, far]\n fogBrightResist: f32,\n fogAlphas: vec3f, // [nearAlpha, midAlpha, farAlpha]\n fogPad1: f32,\n fogHeightFade: vec2f, // [bottomY, topY]\n fogDebug: f32, // 0 = off, 2 = show distance\n fogPad2: f32,\n}\n\nstruct CascadeMatrices {\n matrices: array<mat4x4<f32>, CASCADE_COUNT>,\n}\n\nstruct VertexOutput {\n @builtin(position) position: vec4f,\n @location(0) uv: vec2f,\n @location(1) color: vec4f,\n @location(2) viewZ: f32,\n @location(3) linearDepth: f32, // For frag_depth output\n @location(4) lighting: vec3f, // Pre-computed lighting from particle\n @location(5) @interpolate(flat) emitterIdx: u32, // For per-emitter settings\n @location(6) worldPos: vec3f, // For fog height fade\n @location(7) @interpolate(flat) centerViewZ: f32, // View-space Z of particle center (for fog)\n}\n\n@group(0) @binding(0) var<uniform> uniforms: ParticleUniforms;\n@group(0) @binding(1) var<storage, read> particles: array<Particle>;\n@group(0) @binding(2) var particleTexture: texture_2d<f32>;\n@group(0) @binding(3) var particleSampler: sampler;\n@group(0) @binding(4) var depthTexture: texture_depth_2d;\n@group(0) @binding(5) var shadowMapArray: texture_depth_2d_array;\n@group(0) @binding(6) var shadowSampler: sampler_comparison;\n@group(0) @binding(7) var<storage, read> cascadeMatrices: CascadeMatrices;\n@group(0) @binding(8) var<storage, read> emitterSettings: array<EmitterRenderSettings, MAX_EMITTERS>;\n@group(0) @binding(9) var envMap: texture_2d<f32>;\n@group(0) @binding(10) var envSampler: sampler;\n// Point/spot lights\n@group(0) @binding(11) var<storage, read> lights: array<Light, MAX_LIGHTS>;\n// Spot shadow atlas\n@group(0) @binding(12) var spotShadowAtlas: texture_depth_2d;\n@group(0) @binding(13) var spotShadowSampler: sampler_comparison;\n@group(0) @binding(14) var<storage, read> spotMatrices: SpotShadowMatrices;\n\n// Quad vertices: 0=bottom-left, 1=bottom-right, 2=top-left, 3=top-right\n// Two triangles: 0,1,2 and 1,3,2\nfn getQuadVertex(vertexId: u32) -> vec2f {\n // Map 6 vertices to 4 quad corners\n // Triangle 1: 0,1,2 -> BL, BR, TL\n // Triangle 2: 3,4,5 -> BR, TR, TL\n var corners = array<vec2f, 6>(\n vec2f(-0.5, -0.5), // 0: BL\n vec2f(0.5, -0.5), // 1: BR\n vec2f(-0.5, 0.5), // 2: TL\n vec2f(0.5, -0.5), // 3: BR\n vec2f(0.5, 0.5), // 4: TR\n vec2f(-0.5, 0.5) // 5: TL\n );\n return corners[vertexId];\n}\n\nfn getQuadUV(vertexId: u32) -> vec2f {\n var uvs = array<vec2f, 6>(\n vec2f(0.0, 1.0), // 0: BL\n vec2f(1.0, 1.0), // 1: BR\n vec2f(0.0, 0.0), // 2: TL\n vec2f(1.0, 1.0), // 3: BR\n vec2f(1.0, 0.0), // 4: TR\n vec2f(0.0, 0.0) // 5: TL\n );\n return uvs[vertexId];\n}\n\n@vertex\nfn vertexMain(\n @builtin(vertex_index) vertexIndex: u32,\n @builtin(instance_index) instanceIndex: u32\n) -> VertexOutput {\n var output: VertexOutput;\n\n // Get particle data\n let particle = particles[instanceIndex];\n\n // Get emitter index from flags (bits 8-15)\n let emitterIdx = (particle.flags >> 8u) & 0xFFu;\n output.emitterIdx = emitterIdx;\n\n // Check if particle is alive\n if ((particle.flags & 1u) == 0u || particle.lifetime <= 0.0) {\n // Dead particle - render degenerate triangle\n output.position = vec4f(0.0, 0.0, 0.0, 0.0);\n output.uv = vec2f(0.0, 0.0);\n output.color = vec4f(0.0, 0.0, 0.0, 0.0);\n output.viewZ = 0.0;\n output.linearDepth = 0.0;\n output.lighting = vec3f(0.0);\n output.worldPos = vec3f(0.0);\n output.centerViewZ = 0.0;\n return output;\n }\n\n // Check blend mode: bit 1 of flags = additive (1) or alpha (0)\n // uniforms.blendMode: 1.0 = additive, 0.0 = alpha\n let particleIsAdditive = (particle.flags & 2u) != 0u;\n let renderingAdditive = uniforms.blendMode > 0.5;\n\n if (particleIsAdditive != renderingAdditive) {\n // Wrong blend mode for this pass - skip\n output.position = vec4f(0.0, 0.0, 0.0, 0.0);\n output.uv = vec2f(0.0, 0.0);\n output.color = vec4f(0.0, 0.0, 0.0, 0.0);\n output.viewZ = 0.0;\n output.linearDepth = 0.0;\n output.lighting = vec3f(0.0);\n output.worldPos = vec3f(0.0);\n output.centerViewZ = 0.0;\n return output;\n }\n\n // Get quad vertex position and UV\n let localVertexId = vertexIndex % 6u;\n var quadPos = getQuadVertex(localVertexId);\n output.uv = getQuadUV(localVertexId);\n\n // Apply rotation to quad position\n let cosR = cos(particle.rotation);\n let sinR = sin(particle.rotation);\n let rotatedPos = vec2f(\n quadPos.x * cosR - quadPos.y * sinR,\n quadPos.x * sinR + quadPos.y * cosR\n );\n\n // Billboard: create quad facing camera\n let particleWorldPos = particle.position;\n\n // Scale rotated quad by particle size\n let scaledOffset = rotatedPos * particle.size;\n\n // Create billboard position using camera vectors\n let right = uniforms.cameraRight;\n let up = uniforms.cameraUp;\n let billboardPos = particleWorldPos + right * scaledOffset.x + up * scaledOffset.y;\n\n // Apply z-offset along view direction to prevent z-fighting\n let toCamera = normalize(uniforms.cameraPosition - particleWorldPos);\n let offsetPos = billboardPos + toCamera * uniforms.zOffset;\n\n // Transform to clip space\n let viewPos = uniforms.viewMatrix * vec4f(offsetPos, 1.0);\n output.position = uniforms.projectionMatrix * viewPos;\n output.viewZ = -viewPos.z; // Positive depth\n\n // Calculate linear depth matching GBuffer format: (z - near) / (far - near)\n let z = -viewPos.z; // View space Z (positive into screen)\n output.linearDepth = (z - uniforms.near) / (uniforms.far - uniforms.near);\n\n // Pass pre-computed lighting from particle (calculated in compute shader)\n output.lighting = particle.lighting;\n\n // Pass through particle color\n output.color = particle.color;\n\n // Pass world position for fog height\n output.worldPos = particleWorldPos;\n\n // Use the billboard's viewZ for fog distance\n // This matches scene fog which uses view-space Z (linear depth)\n // The billboard viewZ is already calculated correctly: -viewPos.z\n output.centerViewZ = output.viewZ;\n\n return output;\n}\n\n// Equirectangular UV from direction\nfn SphToUV(n: vec3f) -> vec2f {\n var uv: vec2f;\n uv.x = atan2(-n.x, n.z);\n uv.x = (uv.x + PI / 2.0) / (PI * 2.0) + PI * (28.670 / 360.0);\n uv.y = acos(n.y) / PI;\n return uv;\n}\n\n// Octahedral encoding: direction to UV\nfn octEncode(n: vec3f) -> vec2f {\n var n2 = n / (abs(n.x) + abs(n.y) + abs(n.z));\n if (n2.y < 0.0) {\n let signX = select(-1.0, 1.0, n2.x >= 0.0);\n let signZ = select(-1.0, 1.0, n2.z >= 0.0);\n n2 = vec3f(\n (1.0 - abs(n2.z)) * signX,\n n2.y,\n (1.0 - abs(n2.x)) * signZ\n );\n }\n return n2.xz * 0.5 + 0.5;\n}\n\n// Get environment UV based on encoding type\nfn getEnvUV(dir: vec3f) -> vec2f {\n if (uniforms.envParams.z > 0.5) {\n return octEncode(dir);\n }\n return SphToUV(dir);\n}\n\n// Sample IBL at direction with LOD\nfn getIBLSample(dir: vec3f, lod: f32) -> vec3f {\n let envRGBE = textureSampleLevel(envMap, envSampler, getEnvUV(dir), lod);\n // RGBE decode\n let envColor = envRGBE.rgb * pow(2.0, envRGBE.a * 255.0 - 128.0);\n return envColor;\n}\n\n// Squircle distance - returns distance normalized to cascade size\nfn squircleDistanceXZ(offset: vec2f, size: f32) -> f32 {\n let normalized = offset / size;\n let absNorm = abs(normalized);\n return pow(pow(absNorm.x, 4.0) + pow(absNorm.y, 4.0), 0.25);\n}\n\n// Sample shadow from a specific cascade (simplified for particles)\nfn sampleCascadeShadow(worldPos: vec3f, normal: vec3f, cascadeIndex: i32) -> f32 {\n let bias = uniforms.shadowBias;\n let shadowMapSize = uniforms.shadowMapSize;\n\n // Apply normal bias\n let biasedPos = worldPos + normal * bias * 0.5;\n\n // Get cascade matrix\n let lightMatrix = cascadeMatrices.matrices[cascadeIndex];\n\n // Transform to light space\n let lightSpacePos = lightMatrix * vec4f(biasedPos, 1.0);\n let projCoords = lightSpacePos.xyz / lightSpacePos.w;\n\n // Transform to [0,1] UV space\n let shadowUV = vec2f(projCoords.x * 0.5 + 0.5, 0.5 - projCoords.y * 0.5);\n let currentDepth = projCoords.z - bias;\n\n // Check bounds\n let inBoundsX = shadowUV.x >= 0.0 && shadowUV.x <= 1.0;\n let inBoundsY = shadowUV.y >= 0.0 && shadowUV.y <= 1.0;\n let inBoundsZ = currentDepth >= 0.0 && currentDepth <= 1.0;\n\n if (!inBoundsX || !inBoundsY || !inBoundsZ) {\n return 1.0; // Out of bounds = lit\n }\n\n let clampedUV = clamp(shadowUV, vec2f(0.001), vec2f(0.999));\n let clampedDepth = clamp(currentDepth, 0.001, 0.999);\n\n // Simple 4-tap PCF for particles (fast)\n let texelSize = 1.0 / shadowMapSize;\n var shadow = 0.0;\n shadow += textureSampleCompareLevel(shadowMapArray, shadowSampler, clampedUV + vec2f(-texelSize, 0.0), cascadeIndex, clampedDepth);\n shadow += textureSampleCompareLevel(shadowMapArray, shadowSampler, clampedUV + vec2f(texelSize, 0.0), cascadeIndex, clampedDepth);\n shadow += textureSampleCompareLevel(shadowMapArray, shadowSampler, clampedUV + vec2f(0.0, -texelSize), cascadeIndex, clampedDepth);\n shadow += textureSampleCompareLevel(shadowMapArray, shadowSampler, clampedUV + vec2f(0.0, texelSize), cascadeIndex, clampedDepth);\n shadow /= 4.0;\n\n return shadow;\n}\n\n// Calculate cascaded shadow for particles (simplified cascade selection)\nfn calculateParticleShadow(worldPos: vec3f, normal: vec3f) -> f32 {\n let shadowStrength = uniforms.shadowStrength;\n\n // Calculate XZ offset from camera\n let camXZ = vec2f(uniforms.cameraPosition.x, uniforms.cameraPosition.z);\n let posXZ = vec2f(worldPos.x, worldPos.z);\n let offsetXZ = posXZ - camXZ;\n\n // Cascade sizes\n let cascade0Size = uniforms.cascadeSizes.x;\n let cascade1Size = uniforms.cascadeSizes.y;\n let cascade2Size = uniforms.cascadeSizes.z;\n\n let dist0 = squircleDistanceXZ(offsetXZ, cascade0Size);\n let dist1 = squircleDistanceXZ(offsetXZ, cascade1Size);\n let dist2 = squircleDistanceXZ(offsetXZ, cascade2Size);\n\n var shadow = 1.0;\n\n // Simple cascade selection (no blending for performance)\n if (dist0 < 0.95) {\n shadow = sampleCascadeShadow(worldPos, normal, 0);\n } else if (dist1 < 0.95) {\n shadow = sampleCascadeShadow(worldPos, normal, 1);\n } else if (dist2 < 0.95) {\n shadow = sampleCascadeShadow(worldPos, normal, 2);\n }\n\n // Apply shadow strength\n return mix(1.0 - shadowStrength, 1.0, shadow);\n}\n\n// Calculate spot light shadow (simplified for particles)\nfn calculateSpotShadow(worldPos: vec3f, normal: vec3f, slotIndex: i32) -> f32 {\n if (slotIndex < 0 || slotIndex >= MAX_SPOT_SHADOWS) {\n return 1.0; // No shadow\n }\n\n let bias = uniforms.shadowBias;\n let normalBias = bias * 2.0; // Increased for spot lights\n\n // Apply normal bias\n let biasedPos = worldPos + normal * normalBias;\n\n // Get the light matrix from storage buffer\n let lightMatrix = spotMatrices.matrices[slotIndex];\n\n // Transform to light space\n let lightSpacePos = lightMatrix * vec4f(biasedPos, 1.0);\n\n // Perspective divide\n let w = max(abs(lightSpacePos.w), 0.0001) * sign(lightSpacePos.w + 0.0001);\n let projCoords = lightSpacePos.xyz / w;\n\n // Check if outside frustum\n if (projCoords.z < 0.0 || projCoords.z > 1.0 ||\n abs(projCoords.x) > 1.0 || abs(projCoords.y) > 1.0) {\n return 1.0; // Outside shadow frustum\n }\n\n // Calculate tile position in atlas\n let col = slotIndex % SPOT_TILES_PER_ROW;\n let row = slotIndex / SPOT_TILES_PER_ROW;\n\n // Transform to [0,1] UV within tile\n let localUV = vec2f(projCoords.x * 0.5 + 0.5, 0.5 - projCoords.y * 0.5);\n\n // Transform to atlas UV\n let tileOffset = vec2f(f32(col), f32(row)) * SPOT_TILE_SIZE;\n let atlasUV = (tileOffset + localUV * SPOT_TILE_SIZE) / vec2f(SPOT_ATLAS_WIDTH, SPOT_ATLAS_HEIGHT);\n\n // Sample shadow with simple 4-tap PCF\n let texelSize = 1.0 / SPOT_TILE_SIZE;\n let currentDepth = clamp(projCoords.z - bias * 3.0, 0.001, 0.999);\n\n var shadowSample = 0.0;\n shadowSample += textureSampleCompareLevel(spotShadowAtlas, spotShadowSampler, atlasUV + vec2f(-texelSize, 0.0), currentDepth);\n shadowSample += textureSampleCompareLevel(spotShadowAtlas, spotShadowSampler, atlasUV + vec2f(texelSize, 0.0), currentDepth);\n shadowSample += textureSampleCompareLevel(spotShadowAtlas, spotShadowSampler, atlasUV + vec2f(0.0, -texelSize), currentDepth);\n shadowSample += textureSampleCompareLevel(spotShadowAtlas, spotShadowSampler, atlasUV + vec2f(0.0, texelSize), currentDepth);\n shadowSample /= 4.0;\n\n return shadowSample;\n}\n\n// Apply pre-computed lighting to particle color\n// Lighting is calculated per-particle in the compute shader with temporal smoothing\nfn applyLighting(baseColor: vec3f, lighting: vec3f) -> vec3f {\n // Just multiply base color by pre-computed lighting\n // Lighting already includes ambient, shadows, point/spot lights, and emissive\n return baseColor * lighting;\n}\n\nstruct FragmentOutput {\n @location(0) color: vec4f,\n @builtin(frag_depth) depth: f32,\n}\n\n// Calculate soft particle fade based on depth difference\nfn calcSoftFade(fragPos: vec4f, particleLinearDepth: f32) -> f32 {\n if (uniforms.softness <= 0.0) {\n return 1.0;\n }\n\n // Get screen coordinates\n let screenPos = vec2i(fragPos.xy);\n let screenSize = vec2i(uniforms.screenSize);\n\n // Bounds check\n if (screenPos.x < 0 || screenPos.x >= screenSize.x ||\n screenPos.y < 0 || screenPos.y >= screenSize.y) {\n return 1.0;\n }\n\n // Sample scene depth (linear depth in 0-1 range from GBuffer)\n let sceneDepthNorm = textureLoad(depthTexture, screenPos, 0);\n\n // If depth is 0 (no valid data), skip soft fade\n if (sceneDepthNorm <= 0.0) {\n return 1.0;\n }\n\n // Convert both to world units for comparison\n let sceneDepth = uniforms.near + sceneDepthNorm * (uniforms.far - uniforms.near);\n let particleDepth = uniforms.near + particleLinearDepth * (uniforms.far - uniforms.near);\n\n // Fade based on depth difference (in world units)\n let depthDiff = sceneDepth - particleDepth;\n return saturate(depthDiff / uniforms.softness);\n}\n\n// Calculate fog based on particle's world position (not scene depth)\n// This allows particles to fog correctly even over sky\nfn calcFog(cameraDistance: f32, worldPosY: f32) -> f32 {\n if (uniforms.fogEnabled < 0.5) {\n return 0.0;\n }\n\n // Distance fog - two gradients\n var distanceFog: f32;\n let d0 = uniforms.fogDistances.x;\n let d1 = uniforms.fogDistances.y;\n let d2 = uniforms.fogDistances.z;\n let a0 = uniforms.fogAlphas.x;\n let a1 = uniforms.fogAlphas.y;\n let a2 = uniforms.fogAlphas.z;\n\n if (cameraDistance <= d0) {\n distanceFog = a0;\n } else if (cameraDistance <= d1) {\n let t = (cameraDistance - d0) / max(d1 - d0, 0.001);\n distanceFog = mix(a0, a1, t);\n } else if (cameraDistance <= d2) {\n let t = (cameraDistance - d1) / max(d2 - d1, 0.001);\n distanceFog = mix(a1, a2, t);\n } else {\n distanceFog = a2;\n }\n\n // Height fade - full fog at bottomY, zero fog at topY\n let bottomY = uniforms.fogHeightFade.x;\n let topY = uniforms.fogHeightFade.y;\n var heightFactor = clamp((worldPosY - bottomY) / max(topY - bottomY, 0.001), 0.0, 1.0);\n if (worldPosY < bottomY) {\n heightFactor = 0.0;\n }\n\n return distanceFog * (1.0 - heightFactor);\n}\n\n// Apply fog to color (blend toward fog color)\n// Particles use same fog calculation as scene\nfn applyFog(color: vec3f, cameraDistance: f32, worldPosY: f32) -> vec3f {\n let fogAlpha = calcFog(cameraDistance, worldPosY);\n if (fogAlpha <= 0.0) {\n return color;\n }\n\n return mix(color, uniforms.fogColor, fogAlpha);\n}\n\n// Fragment for alpha blend mode\n@fragment\nfn fragmentMainAlpha(input: VertexOutput) -> FragmentOutput {\n var output: FragmentOutput;\n\n // Sample texture directly\n let texColor = textureSample(particleTexture, particleSampler, input.uv);\n\n // Combine texture with particle color\n var alpha = texColor.a * input.color.a;\n\n // Apply soft particle fade\n alpha *= calcSoftFade(input.position, input.linearDepth);\n\n // Discard nearly transparent pixels\n if (alpha < 0.001) {\n discard;\n }\n\n // Apply pre-computed lighting from particle\n let baseColor = texColor.rgb * input.color.rgb;\n let litColor = applyLighting(baseColor, input.lighting);\n\n // Use pre-computed center view-space Z from vertex shader (flat interpolated)\n let centerViewZ = input.centerViewZ;\n\n // Debug modes using centerViewZ (flat interpolated = same value across whole particle)\n // Use full alpha to avoid blending artifacts in debug view\n if (uniforms.fogDebug > 0.5) {\n // Debug mode 1: show centerViewZ/100 as grayscale (should match scene fog)\n if (uniforms.fogDebug < 1.5) {\n let dist = clamp(centerViewZ / 100.0, 0.0, 1.0);\n output.color = vec4f(vec3f(dist), 1.0);\n }\n // Debug mode 2: show centerViewZ at 3 scales to find correct range\n // R = /10, G = /100, B = /1000\n else if (uniforms.fogDebug < 2.5) {\n let r = clamp(centerViewZ / 10.0, 0.0, 1.0);\n let g = clamp(centerViewZ / 100.0, 0.0, 1.0);\n let b = clamp(centerViewZ / 1000.0, 0.0, 1.0);\n output.color = vec4f(r, g, b, 1.0);\n }\n // Debug mode 3: compare viewZ (interpolated) vs centerViewZ (flat)\n // R = centerViewZ/100, G = viewZ/100, B = difference\n else if (uniforms.fogDebug < 3.5) {\n let center = clamp(centerViewZ / 100.0, 0.0, 1.0);\n let vertex = clamp(input.viewZ / 100.0, 0.0, 1.0);\n let diff = abs(center - vertex);\n output.color = vec4f(center, vertex, diff * 10.0, 1.0);\n }\n // Debug mode 4: show worldPos\n else if (uniforms.fogDebug < 4.5) {\n let r = clamp(abs(input.worldPos.x) / 100.0, 0.0, 1.0);\n let g = clamp(abs(input.worldPos.y) / 100.0, 0.0, 1.0);\n let b = clamp(abs(input.worldPos.z) / 100.0, 0.0, 1.0);\n output.color = vec4f(r, g, b, 1.0);\n }\n // Debug mode 5: show the actual fog color being used\n else {\n output.color = vec4f(uniforms.fogColor, 1.0);\n }\n output.depth = input.linearDepth;\n return output;\n }\n\n let foggedColor = applyFog(litColor, centerViewZ, input.worldPos.y);\n\n output.color = vec4f(foggedColor, alpha);\n output.depth = input.linearDepth;\n return output;\n}\n\n// Fragment for additive mode\n@fragment\nfn fragmentMainAdditive(input: VertexOutput) -> FragmentOutput {\n var output: FragmentOutput;\n\n // Sample texture directly\n let texColor = textureSample(particleTexture, particleSampler, input.uv);\n\n // Combine texture with particle color\n var alpha = texColor.a * input.color.a;\n\n // Apply soft particle fade\n alpha *= calcSoftFade(input.position, input.linearDepth);\n\n // Discard nearly transparent pixels\n if (alpha < 0.001) {\n discard;\n }\n\n // Apply pre-computed lighting from particle\n let baseColor = texColor.rgb * input.color.rgb;\n let litColor = applyLighting(baseColor, input.lighting);\n\n // Use pre-computed center view-space Z from vertex shader (flat interpolated)\n let centerViewZ = input.centerViewZ;\n\n // Debug modes using centerViewZ (flat interpolated = same value across whole particle)\n // Use full alpha to avoid blending artifacts in debug view\n if (uniforms.fogDebug > 0.5) {\n // Debug mode 1: show centerViewZ/100 as grayscale (should match scene fog)\n if (uniforms.fogDebug < 1.5) {\n let dist = clamp(centerViewZ / 100.0, 0.0, 1.0);\n output.color = vec4f(vec3f(dist), 1.0);\n }\n // Debug mode 2: show centerViewZ at 3 scales\n else if (uniforms.fogDebug < 2.5) {\n let r = clamp(centerViewZ / 10.0, 0.0, 1.0);\n let g = clamp(centerViewZ / 100.0, 0.0, 1.0);\n let b = clamp(centerViewZ / 1000.0, 0.0, 1.0);\n output.color = vec4f(r, g, b, 1.0);\n }\n // Debug mode 3: compare viewZ vs centerViewZ\n else if (uniforms.fogDebug < 3.5) {\n let center = clamp(centerViewZ / 100.0, 0.0, 1.0);\n let vertex = clamp(input.viewZ / 100.0, 0.0, 1.0);\n let diff = abs(center - vertex);\n output.color = vec4f(center, vertex, diff * 10.0, 1.0);\n }\n // Debug mode 4: show worldPos\n else if (uniforms.fogDebug < 4.5) {\n let r = clamp(abs(input.worldPos.x) / 100.0, 0.0, 1.0);\n let g = clamp(abs(input.worldPos.y) / 100.0, 0.0, 1.0);\n let b = clamp(abs(input.worldPos.z) / 100.0, 0.0, 1.0);\n output.color = vec4f(r, g, b, 1.0);\n }\n // Debug mode 5: show the actual fog color being used\n else {\n output.color = vec4f(uniforms.fogColor, 1.0);\n }\n output.depth = input.linearDepth;\n return output;\n }\n\n // For additive particles, fog should fade the contribution to zero\n // (not mix with fog color, which would add fog color to the already-fogged background)\n let fogAlpha = calcFog(centerViewZ, input.worldPos.y);\n let fadedColor = litColor * (1.0 - fogAlpha);\n\n // Premultiply for additive blending\n let rgb = fadedColor * alpha;\n output.color = vec4f(rgb, alpha);\n output.depth = input.linearDepth;\n return output;\n}\n","import { BasePass } from \"./BasePass.js\"\nimport { Texture } from \"../../Texture.js\"\n\nimport particleSimulateWGSL from \"../shaders/particle_simulate.wgsl\"\nimport particleRenderWGSL from \"../shaders/particle_render.wgsl\"\n\n/**\n * ParticlePass - GPU particle simulation and rendering\n *\n * Handles:\n * - Compute shader dispatch for particle spawning and simulation\n * - Forward rendering with additive or alpha blending\n * - Soft particle depth fade\n * - Billboard rendering using camera vectors\n */\nclass ParticlePass extends BasePass {\n constructor(engine = null) {\n super('Particles', engine)\n\n // Compute pipelines\n this.spawnPipeline = null\n this.simulatePipeline = null\n this.resetPipeline = null\n\n // Render pipelines (by blend mode)\n this.renderPipelineAdditive = null\n this.renderPipelineAlpha = null\n\n // Bind groups\n this.computeBindGroupLayout = null\n this.renderBindGroupLayout = null\n\n // Uniform buffers\n this.simulationUniformBuffer = null\n this.renderUniformBuffer = null\n this.emitterSettingsBuffer = null\n this.emitterRenderSettingsBuffer = null // For lit + emissive per emitter\n\n // References\n this.particleSystem = null\n this.gbuffer = null\n this.outputTexture = null\n this.shadowPass = null // For shadow maps\n this.environmentMap = null // For IBL\n this.environmentEncoding = 0 // 0=equirect, 1=octahedral\n this.lightingPass = null // For point/spot lights buffer\n\n // Per-emitter texture cache\n this._textureCache = new Map() // textureUrl -> {texture, bindGroup}\n\n // Placeholder resources\n this._placeholderTexture = null\n this._placeholderSampler = null\n\n // Frame timing\n this._lastTime = 0\n this._deltaTime = 0\n }\n\n /**\n * Set particle system reference\n */\n setParticleSystem(particleSystem) {\n this.particleSystem = particleSystem\n }\n\n /**\n * Set GBuffer for depth testing\n */\n setGBuffer(gbuffer) {\n this.gbuffer = gbuffer\n }\n\n /**\n * Set output texture to render onto\n */\n setOutputTexture(texture) {\n this.outputTexture = texture\n }\n\n /**\n * Set shadow pass for accessing shadow maps\n */\n setShadowPass(shadowPass) {\n this.shadowPass = shadowPass\n }\n\n /**\n * Set environment map for IBL lighting\n * @param {Texture} envMap - Environment map texture\n * @param {number} encoding - 0=equirectangular, 1=octahedral\n */\n setEnvironmentMap(envMap, encoding = 0) {\n this.environmentMap = envMap\n this.environmentEncoding = encoding\n }\n\n /**\n * Set lighting pass for accessing point/spot lights buffer\n * @param {LightingPass} lightingPass\n */\n setLightingPass(lightingPass) {\n this.lightingPass = lightingPass\n }\n\n async _init() {\n const { device } = this.engine\n\n // Create uniform buffers\n // SimulationUniforms: dt, time, maxParticles, emitterCount + lighting params\n // dt(f32) + time(f32) + maxParticles(u32) + emitterCount(u32) = 16 bytes\n // cameraPosition(vec3f) + shadowBias(f32) = 16 bytes\n // lightDir(vec3f) + shadowStrength(f32) = 16 bytes\n // lightColor(vec4f) = 16 bytes\n // ambientColor(vec4f) = 16 bytes\n // cascadeSizes(vec4f) = 16 bytes\n // lightCount(u32) + pad(3xu32) = 16 bytes\n // Total: 112 bytes\n this.simulationUniformBuffer = device.createBuffer({\n size: 112,\n usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST,\n label: 'Particle Simulation Uniforms'\n })\n\n this.renderUniformBuffer = device.createBuffer({\n size: 368, // ParticleUniforms struct (increased for lighting + IBL + light count)\n usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST,\n label: 'Particle Render Uniforms'\n })\n\n // Emitter settings buffer: 16 emitters * 48 bytes each = 768 bytes\n this.emitterSettingsBuffer = device.createBuffer({\n size: 16 * 48,\n usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST,\n label: 'Particle Emitter Settings'\n })\n\n // Emitter render settings buffer: 16 emitters * 16 bytes each = 256 bytes\n // Contains: lit, emissive, softness, zOffset per emitter\n this.emitterRenderSettingsBuffer = device.createBuffer({\n size: 16 * 16,\n usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST,\n label: 'Particle Emitter Render Settings'\n })\n\n // Create placeholder resources\n this._placeholderTexture = await Texture.fromRGBA(this.engine, 1, 1, 1, 1)\n this._placeholderSampler = device.createSampler({\n magFilter: 'linear',\n minFilter: 'linear',\n mipmapFilter: 'linear'\n })\n\n // Placeholder comparison sampler for shadow sampling\n this._placeholderComparisonSampler = device.createSampler({\n compare: 'less',\n magFilter: 'linear',\n minFilter: 'linear'\n })\n\n // Placeholder depth texture array for shadow maps (when shadows disabled)\n this._placeholderDepthTexture = device.createTexture({\n size: [1, 1, 1],\n format: 'depth32float',\n usage: GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.RENDER_ATTACHMENT\n })\n this._placeholderDepthView = this._placeholderDepthTexture.createView({\n dimension: '2d-array',\n arrayLayerCount: 1\n })\n\n // Placeholder buffer for cascade matrices (3 mat4 = 192 bytes)\n this._placeholderMatricesBuffer = device.createBuffer({\n size: 192,\n usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST,\n label: 'Placeholder Cascade Matrices'\n })\n\n // Placeholder lights buffer (64 lights * 64 bytes each = 4096 bytes)\n // Light struct: enabled(u32) + position(vec3f) + color(vec4f) + direction(vec3f) + geom(vec4f) + shadowIndex(i32)\n this._placeholderLightsBuffer = device.createBuffer({\n size: 64 * 64,\n usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST,\n label: 'Placeholder Lights Buffer'\n })\n\n // Placeholder spot shadow atlas (1x1 depth texture)\n this._placeholderSpotShadowTexture = device.createTexture({\n size: [1, 1],\n format: 'depth32float',\n usage: GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.RENDER_ATTACHMENT\n })\n this._placeholderSpotShadowView = this._placeholderSpotShadowTexture.createView()\n\n // Placeholder spot shadow matrices buffer (8 mat4 = 512 bytes)\n this._placeholderSpotMatricesBuffer = device.createBuffer({\n size: 8 * 64,\n usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST,\n label: 'Placeholder Spot Shadow Matrices'\n })\n\n // Create compute pipelines\n await this._createComputePipelines()\n\n // Create render pipelines\n await this._createRenderPipelines()\n }\n\n async _createComputePipelines() {\n const { device } = this.engine\n\n // Compute shader module\n const computeModule = device.createShaderModule({\n label: 'Particle Compute Shader',\n code: particleSimulateWGSL\n })\n\n // Bind group layout for compute (includes lighting bindings for shadow sampling)\n this.computeBindGroupLayout = device.createBindGroupLayout({\n label: 'Particle Compute BindGroup Layout',\n entries: [\n // Core bindings\n { binding: 0, visibility: GPUShaderStage.COMPUTE, buffer: { type: 'uniform' } },\n { binding: 1, visibility: GPUShaderStage.COMPUTE, buffer: { type: 'storage' } },\n { binding: 2, visibility: GPUShaderStage.COMPUTE, buffer: { type: 'storage' } },\n { binding: 3, visibility: GPUShaderStage.COMPUTE, buffer: { type: 'read-only-storage' } },\n { binding: 4, visibility: GPUShaderStage.COMPUTE, buffer: { type: 'read-only-storage' } },\n // Lighting bindings\n { binding: 5, visibility: GPUShaderStage.COMPUTE, buffer: { type: 'read-only-storage' } }, // emitterRenderSettings\n { binding: 6, visibility: GPUShaderStage.COMPUTE, texture: { sampleType: 'depth', viewDimension: '2d-array' } }, // shadowMapArray\n { binding: 7, visibility: GPUShaderStage.COMPUTE, sampler: { type: 'comparison' } }, // shadowSampler\n { binding: 8, visibility: GPUShaderStage.COMPUTE, buffer: { type: 'read-only-storage' } }, // cascadeMatrices\n { binding: 9, visibility: GPUShaderStage.COMPUTE, buffer: { type: 'read-only-storage' } }, // lights\n { binding: 10, visibility: GPUShaderStage.COMPUTE, texture: { sampleType: 'depth' } }, // spotShadowAtlas\n { binding: 11, visibility: GPUShaderStage.COMPUTE, buffer: { type: 'read-only-storage' } }, // spotMatrices\n ]\n })\n\n const computePipelineLayout = device.createPipelineLayout({\n bindGroupLayouts: [this.computeBindGroupLayout]\n })\n\n // Spawn pipeline\n this.spawnPipeline = await device.createComputePipelineAsync({\n label: 'Particle Spawn Pipeline',\n layout: computePipelineLayout,\n compute: {\n module: computeModule,\n entryPoint: 'spawn'\n }\n })\n\n // Simulate pipeline\n this.simulatePipeline = await device.createComputePipelineAsync({\n label: 'Particle Simulate Pipeline',\n layout: computePipelineLayout,\n compute: {\n module: computeModule,\n entryPoint: 'simulate'\n }\n })\n\n // Reset counters pipeline\n this.resetPipeline = await device.createComputePipelineAsync({\n label: 'Particle Reset Pipeline',\n layout: computePipelineLayout,\n compute: {\n module: computeModule,\n entryPoint: 'resetCounters'\n }\n })\n }\n\n async _createRenderPipelines() {\n const { device } = this.engine\n\n // Render shader module\n const renderModule = device.createShaderModule({\n label: 'Particle Render Shader',\n code: particleRenderWGSL\n })\n\n // Bind group layout for rendering\n this.renderBindGroupLayout = device.createBindGroupLayout({\n label: 'Particle Render BindGroup Layout',\n entries: [\n // Uniforms\n { binding: 0, visibility: GPUShaderStage.VERTEX | GPUShaderStage.FRAGMENT, buffer: { type: 'uniform' } },\n // Particle storage buffer (read only in render)\n { binding: 1, visibility: GPUShaderStage.VERTEX, buffer: { type: 'read-only-storage' } },\n // Particle texture\n { binding: 2, visibility: GPUShaderStage.FRAGMENT, texture: { sampleType: 'float' } },\n { binding: 3, visibility: GPUShaderStage.FRAGMENT, sampler: { type: 'filtering' } },\n // Depth texture for soft particles (GBuffer depth32float)\n { binding: 4, visibility: GPUShaderStage.FRAGMENT, texture: { sampleType: 'depth' } },\n // Shadow map array (cascade shadows)\n { binding: 5, visibility: GPUShaderStage.FRAGMENT, texture: { sampleType: 'depth', viewDimension: '2d-array' } },\n // Shadow comparison sampler\n { binding: 6, visibility: GPUShaderStage.FRAGMENT, sampler: { type: 'comparison' } },\n // Cascade matrices (storage buffer)\n { binding: 7, visibility: GPUShaderStage.FRAGMENT, buffer: { type: 'read-only-storage' } },\n // Emitter render settings (lit, emissive, softness, zOffset)\n { binding: 8, visibility: GPUShaderStage.FRAGMENT, buffer: { type: 'read-only-storage' } },\n // Environment map for IBL\n { binding: 9, visibility: GPUShaderStage.FRAGMENT, texture: { sampleType: 'float' } },\n { binding: 10, visibility: GPUShaderStage.FRAGMENT, sampler: { type: 'filtering' } },\n // Point/spot lights buffer\n { binding: 11, visibility: GPUShaderStage.FRAGMENT, buffer: { type: 'read-only-storage' } },\n // Spot shadow atlas\n { binding: 12, visibility: GPUShaderStage.FRAGMENT, texture: { sampleType: 'depth' } },\n { binding: 13, visibility: GPUShaderStage.FRAGMENT, sampler: { type: 'comparison' } },\n // Spot shadow matrices\n { binding: 14, visibility: GPUShaderStage.FRAGMENT, buffer: { type: 'read-only-storage' } },\n ]\n })\n\n const renderPipelineLayout = device.createPipelineLayout({\n bindGroupLayouts: [this.renderBindGroupLayout]\n })\n\n // Additive blend pipeline\n this.renderPipelineAdditive = await device.createRenderPipelineAsync({\n label: 'Particle Render Pipeline (Additive)',\n layout: renderPipelineLayout,\n vertex: {\n module: renderModule,\n entryPoint: 'vertexMain',\n buffers: [] // All data from storage buffer\n },\n fragment: {\n module: renderModule,\n entryPoint: 'fragmentMainAdditive',\n targets: [{\n format: 'rgba16float',\n blend: {\n color: {\n srcFactor: 'one', // Additive: src + dst\n dstFactor: 'one',\n operation: 'add'\n },\n alpha: {\n srcFactor: 'one',\n dstFactor: 'one',\n operation: 'add'\n }\n }\n }]\n },\n depthStencil: {\n format: 'depth32float',\n depthWriteEnabled: false, // No depth write for particles\n depthCompare: 'less-equal', // Standard depth test with linear depth\n },\n primitive: {\n topology: 'triangle-list',\n cullMode: 'none',\n }\n })\n\n // Alpha blend pipeline\n this.renderPipelineAlpha = await device.createRenderPipelineAsync({\n label: 'Particle Render Pipeline (Alpha)',\n layout: renderPipelineLayout,\n vertex: {\n module: renderModule,\n entryPoint: 'vertexMain',\n buffers: []\n },\n fragment: {\n module: renderModule,\n entryPoint: 'fragmentMainAlpha',\n targets: [{\n format: 'rgba16float',\n blend: {\n color: {\n srcFactor: 'src-alpha',\n dstFactor: 'one-minus-src-alpha',\n operation: 'add'\n },\n alpha: {\n srcFactor: 'one',\n dstFactor: 'one-minus-src-alpha',\n operation: 'add'\n }\n }\n }]\n },\n depthStencil: {\n format: 'depth32float',\n depthWriteEnabled: false,\n depthCompare: 'less-equal', // Standard depth test with linear depth\n },\n primitive: {\n topology: 'triangle-list',\n cullMode: 'none',\n }\n })\n }\n\n async _execute(context) {\n const { device, canvas, stats } = this.engine\n const { camera, time, mainLight } = context\n\n if (!this.particleSystem || !this.outputTexture || !this.gbuffer) {\n return\n }\n\n // Initialize particle system if needed\n await this.particleSystem.init()\n\n // Calculate delta time\n const currentTime = time ?? (performance.now() / 1000)\n this._deltaTime = this._lastTime > 0 ? currentTime - this._lastTime : 0.016\n this._lastTime = currentTime\n\n // Clamp delta time to prevent huge jumps\n this._deltaTime = Math.min(this._deltaTime, 0.1)\n\n // Update particle system (CPU side - spawn rate accumulation)\n this.particleSystem.update(this._deltaTime)\n\n const emitters = this.particleSystem.getActiveEmitters()\n if (emitters.length === 0) {\n return\n }\n\n // Create command encoder\n const commandEncoder = device.createCommandEncoder({ label: 'Particle Pass' })\n\n // === COMPUTE PHASE (includes lighting calculation) ===\n await this._executeCompute(commandEncoder, emitters, camera, mainLight)\n\n // === RENDER PHASE ===\n await this._executeRender(commandEncoder, emitters, camera, mainLight)\n\n // Submit\n device.queue.submit([commandEncoder.finish()])\n\n // Update stats\n if (stats) {\n stats.particleEmitters = emitters.length\n stats.particleCount = this.particleSystem.getTotalParticleCount()\n }\n }\n\n async _executeCompute(commandEncoder, emitters, camera, mainLight) {\n const { device } = this.engine\n\n // Get spawn count BEFORE executing (executeSpawn clears the queue)\n const spawnCount = Math.min(this.particleSystem._spawnQueue?.length || 0, 1000)\n\n // Execute spawns - writes data to GPU buffers\n this.particleSystem.executeSpawn(commandEncoder)\n\n // Get lighting settings\n const mainLightEnabled = mainLight?.enabled !== false\n const mainLightIntensity = mainLight?.intensity ?? 1.0\n const mainLightColor = mainLight?.color || [1.0, 0.95, 0.9]\n const lightDir = mainLight?.direction || [-1, 1, -0.5]\n\n // Normalize light direction\n const lightDirLen = Math.sqrt(lightDir[0] * lightDir[0] + lightDir[1] * lightDir[1] + lightDir[2] * lightDir[2])\n const normalizedLightDir = [\n lightDir[0] / lightDirLen,\n lightDir[1] / lightDirLen,\n lightDir[2] / lightDirLen\n ]\n\n // Get ambient and shadow settings\n const ambientColor = this.settings?.ambient?.color || [0.1, 0.12, 0.15]\n const ambientIntensity = this.settings?.ambient?.intensity ?? 0.3\n const shadowConfig = this.settings?.shadow || {}\n const shadowBias = shadowConfig.bias ?? 0.0005\n const shadowStrength = shadowConfig.strength ?? 0.8\n const cascadeSizes = shadowConfig.cascadeSizes || [10, 25, 100]\n const lightCount = this.lightingPass?.lights?.length ?? 0\n\n // Update simulation uniforms (28 floats = 112 bytes)\n const uniformData = new Float32Array(28)\n uniformData[0] = this._deltaTime // dt\n uniformData[1] = this._lastTime // time\n const uniformIntView = new Uint32Array(uniformData.buffer)\n uniformIntView[2] = this.particleSystem.globalMaxParticles // maxParticles\n uniformIntView[3] = emitters.length // emitterCount\n // cameraPosition (vec3f) + shadowBias (f32) - floats 4-7\n uniformData[4] = camera.position[0]\n uniformData[5] = camera.position[1]\n uniformData[6] = camera.position[2]\n uniformData[7] = shadowBias\n // lightDir (vec3f) + shadowStrength (f32) - floats 8-11\n uniformData[8] = normalizedLightDir[0]\n uniformData[9] = normalizedLightDir[1]\n uniformData[10] = normalizedLightDir[2]\n uniformData[11] = shadowStrength\n // lightColor (vec4f) - floats 12-15\n uniformData[12] = mainLightColor[0]\n uniformData[13] = mainLightColor[1]\n uniformData[14] = mainLightColor[2]\n uniformData[15] = mainLightEnabled ? mainLightIntensity * 12.0 : 0.0\n // ambientColor (vec4f) - floats 16-19\n uniformData[16] = ambientColor[0]\n uniformData[17] = ambientColor[1]\n uniformData[18] = ambientColor[2]\n uniformData[19] = ambientIntensity\n // cascadeSizes (vec4f) - floats 20-23\n uniformData[20] = cascadeSizes[0]\n uniformData[21] = cascadeSizes[1]\n uniformData[22] = cascadeSizes[2]\n uniformData[23] = 0.0 // padding\n // lightCount (u32) + padding - floats 24-27\n uniformIntView[24] = lightCount\n uniformIntView[25] = 0\n uniformIntView[26] = 0\n uniformIntView[27] = 0\n\n device.queue.writeBuffer(this.simulationUniformBuffer, 0, uniformData)\n\n // Update per-emitter settings buffer\n // EmitterSettings: gravity(vec3f), drag, turbulence, fadeIn, fadeOut, rotationSpeed, startSize, endSize, baseAlpha, padding\n // = 12 floats = 48 bytes per emitter\n const emitterData = new Float32Array(16 * 12) // 16 emitters max\n for (let i = 0; i < Math.min(emitters.length, 16); i++) {\n const e = emitters[i]\n const offset = i * 12\n // gravity (vec3f) + drag (f32)\n emitterData[offset + 0] = e.gravity[0]\n emitterData[offset + 1] = e.gravity[1]\n emitterData[offset + 2] = e.gravity[2]\n emitterData[offset + 3] = e.drag\n // turbulence + fadeIn + fadeOut + rotationSpeed\n emitterData[offset + 4] = e.turbulence\n emitterData[offset + 5] = e.fadeIn\n emitterData[offset + 6] = e.fadeOut\n emitterData[offset + 7] = e.rotationSpeed ?? 0.5\n // startSize + endSize + baseAlpha + padding\n emitterData[offset + 8] = e.size[0]\n emitterData[offset + 9] = e.size[1]\n emitterData[offset + 10] = e.color[3] // baseAlpha\n emitterData[offset + 11] = 0 // padding\n }\n\n device.queue.writeBuffer(this.emitterSettingsBuffer, 0, emitterData)\n\n // Create compute bind group with lighting resources\n const computeBindGroup = device.createBindGroup({\n layout: this.computeBindGroupLayout,\n entries: [\n // Core bindings\n { binding: 0, resource: { buffer: this.simulationUniformBuffer } },\n { binding: 1, resource: { buffer: this.particleSystem.getParticleBuffer() } },\n { binding: 2, resource: { buffer: this.particleSystem.getCounterBuffer() } },\n { binding: 3, resource: { buffer: this.particleSystem.getSpawnBuffer() } },\n { binding: 4, resource: { buffer: this.emitterSettingsBuffer } },\n // Lighting bindings\n { binding: 5, resource: { buffer: this.emitterRenderSettingsBuffer } },\n { binding: 6, resource: this.shadowPass?.getShadowMapView() || this._placeholderDepthView },\n { binding: 7, resource: this.shadowPass?.getShadowSampler() || this._placeholderComparisonSampler },\n { binding: 8, resource: { buffer: this.shadowPass?.getCascadeMatricesBuffer() || this._placeholderMatricesBuffer } },\n { binding: 9, resource: { buffer: this.lightingPass?.lightBuffer || this._placeholderLightsBuffer } },\n { binding: 10, resource: this.shadowPass?.getSpotShadowAtlasView?.() || this._placeholderSpotShadowView },\n { binding: 11, resource: { buffer: this.shadowPass?.getSpotMatricesBuffer?.() || this._placeholderSpotMatricesBuffer } },\n ]\n })\n\n // Dispatch spawn compute (using spawnCount captured before executeSpawn cleared the queue)\n if (spawnCount > 0) {\n const spawnPass = commandEncoder.beginComputePass({ label: 'Particle Spawn' })\n spawnPass.setPipeline(this.spawnPipeline)\n spawnPass.setBindGroup(0, computeBindGroup)\n spawnPass.dispatchWorkgroups(Math.ceil(spawnCount / 64))\n spawnPass.end()\n }\n\n // Dispatch simulate compute\n const maxParticles = this.particleSystem.globalMaxParticles\n const simulatePass = commandEncoder.beginComputePass({ label: 'Particle Simulate' })\n simulatePass.setPipeline(this.simulatePipeline)\n simulatePass.setBindGroup(0, computeBindGroup)\n simulatePass.dispatchWorkgroups(Math.ceil(maxParticles / 64))\n simulatePass.end()\n }\n\n async _executeRender(commandEncoder, emitters, camera, mainLight) {\n const { device, canvas } = this.engine\n\n // Extract camera right/up from view matrix if not provided\n // View matrix rows are: right, up, forward (transposed world axes)\n let cameraRight = camera.right\n let cameraUp = camera.up\n\n if (!cameraRight && camera.view) {\n // First row of view matrix is camera right vector\n cameraRight = [camera.view[0], camera.view[4], camera.view[8]]\n }\n if (!cameraUp && camera.view) {\n // Second row of view matrix is camera up vector\n cameraUp = [camera.view[1], camera.view[5], camera.view[9]]\n }\n\n // Fallback defaults\n cameraRight = cameraRight || [1, 0, 0]\n cameraUp = cameraUp || [0, 1, 0]\n\n // Get lighting settings\n const mainLightEnabled = mainLight?.enabled !== false\n const mainLightIntensity = mainLight?.intensity ?? 1.0\n const mainLightColor = mainLight?.color || [1.0, 0.95, 0.9]\n const lightDir = mainLight?.direction || [-1, 1, -0.5]\n\n // Normalize light direction\n const lightDirLen = Math.sqrt(lightDir[0] * lightDir[0] + lightDir[1] * lightDir[1] + lightDir[2] * lightDir[2])\n const normalizedLightDir = [\n lightDir[0] / lightDirLen,\n lightDir[1] / lightDirLen,\n lightDir[2] / lightDirLen\n ]\n\n // Get ambient color from settings\n const ambientColor = this.settings?.ambient?.color || [0.1, 0.12, 0.15]\n const ambientIntensity = this.settings?.ambient?.intensity ?? 0.3\n\n // Get shadow settings\n const shadowConfig = this.settings?.shadow || {}\n const shadowBias = shadowConfig.bias ?? 0.0005\n const shadowStrength = shadowConfig.strength ?? 0.8\n const shadowMapSize = shadowConfig.mapSize ?? 2048\n const cascadeSizes = shadowConfig.cascadeSizes || [10, 25, 100]\n\n // Get IBL settings - use actual mip count from environment map\n const envSettings = this.settings?.environment || {}\n const envDiffuseLevel = envSettings.diffuseLevel ?? 0.5\n const envMipCount = this.environmentMap?.mipCount ?? envSettings.envMipCount ?? 8\n const envExposure = envSettings.exposure ?? 1.0\n\n // Get light count from lighting pass\n const lightCount = this.lightingPass?.lights?.length ?? 0\n\n // Update render uniforms (92 floats = 368 bytes)\n const uniformData = new Float32Array(92)\n // viewMatrix (mat4x4f) - floats 0-15\n uniformData.set(camera.view, 0)\n // projectionMatrix (mat4x4f) - floats 16-31\n uniformData.set(camera.proj, 16)\n // cameraPosition (vec3f) + time (f32) - floats 32-35\n uniformData.set(camera.position, 32)\n uniformData[35] = this._lastTime\n // cameraRight (vec3f) + softness (f32) - floats 36-39\n uniformData.set(cameraRight, 36)\n uniformData[39] = emitters[0]?.softness ?? 0.25\n // cameraUp (vec3f) + zOffset (f32) - floats 40-43\n uniformData.set(cameraUp, 40)\n uniformData[43] = emitters[0]?.zOffset ?? 0.01\n // screenSize (vec2f) + near + far - floats 44-47\n uniformData[44] = canvas.width\n uniformData[45] = canvas.height\n uniformData[46] = camera.near ?? 0.1\n uniformData[47] = camera.far ?? 1000\n // blendMode + lit + shadowBias + shadowStrength - floats 48-51\n // blendMode set per-pass below\n uniformData[49] = emitters[0]?.lit ? 1.0 : 0.0\n uniformData[50] = shadowBias\n uniformData[51] = shadowStrength\n // lightDir (vec3f) + shadowMapSize (f32) - floats 52-55\n uniformData.set(normalizedLightDir, 52)\n uniformData[55] = shadowMapSize\n // lightColor (vec4f) - floats 56-59\n // Multiply intensity by 12.0 to match LightingPass convention\n uniformData[56] = mainLightColor[0]\n uniformData[57] = mainLightColor[1]\n uniformData[58] = mainLightColor[2]\n uniformData[59] = mainLightEnabled ? mainLightIntensity * 12.0 : 0.0\n // ambientColor (vec4f) - floats 60-63\n uniformData[60] = ambientColor[0]\n uniformData[61] = ambientColor[1]\n uniformData[62] = ambientColor[2]\n uniformData[63] = ambientIntensity\n // cascadeSizes (vec4f) - floats 64-67\n uniformData[64] = cascadeSizes[0]\n uniformData[65] = cascadeSizes[1]\n uniformData[66] = cascadeSizes[2]\n uniformData[67] = 0.0 // padding\n // envParams (vec4f) - floats 68-71\n // x = diffuse level, y = mip count, z = encoding (0=equirect, 1=octahedral), w = exposure\n uniformData[68] = this.environmentMap ? envDiffuseLevel : 0.0\n uniformData[69] = envMipCount\n uniformData[70] = this.environmentEncoding === 'octahedral' || this.environmentEncoding === 1 ? 1.0 : 0.0\n uniformData[71] = envExposure\n\n // lightParams (vec4u) - floats 72-75 (stored as u32)\n // x = light count, y = unused, z = unused, w = unused\n const uniformIntView = new Uint32Array(uniformData.buffer)\n uniformIntView[72] = lightCount\n uniformIntView[73] = 0\n uniformIntView[74] = 0\n uniformIntView[75] = 0\n\n // Fog uniforms - floats 76-91\n const fogSettings = this.settings?.environment?.fog || {}\n const fogEnabled = fogSettings.enabled ?? false\n const fogColor = fogSettings.color ?? [0.8, 0.85, 0.9]\n const fogDistances = fogSettings.distances ?? [0, 50, 200]\n const fogAlphas = fogSettings.alpha ?? [0.0, 0.3, 0.8]\n const fogHeightFade = fogSettings.heightFade ?? [-10, 100]\n const fogBrightResist = fogSettings.brightResist ?? 0.8\n\n // fogColor (vec3f) + fogEnabled (f32) - floats 76-79\n uniformData[76] = fogColor[0]\n uniformData[77] = fogColor[1]\n uniformData[78] = fogColor[2]\n uniformData[79] = fogEnabled ? 1.0 : 0.0\n // fogDistances (vec3f) + fogBrightResist (f32) - floats 80-83\n uniformData[80] = fogDistances[0]\n uniformData[81] = fogDistances[1]\n uniformData[82] = fogDistances[2]\n uniformData[83] = fogBrightResist\n // fogAlphas (vec3f) + fogPad1 (f32) - floats 84-87\n uniformData[84] = fogAlphas[0]\n uniformData[85] = fogAlphas[1]\n uniformData[86] = fogAlphas[2]\n uniformData[87] = 0.0 // padding\n // fogHeightFade (vec2f) + fogDebug (f32) + fogPad2 (f32) - floats 88-91\n uniformData[88] = fogHeightFade[0] // bottomY\n uniformData[89] = fogHeightFade[1] // topY\n uniformData[90] = fogSettings.debug ?? 0 // fogDebug (0=off, 2=show distance)\n uniformData[91] = 0.0 // padding\n\n // Update emitter render settings buffer (lit, emissive, softness, zOffset per emitter)\n const emitterRenderData = new Float32Array(16 * 4) // 16 emitters * 4 floats\n for (let i = 0; i < Math.min(emitters.length, 16); i++) {\n const e = emitters[i]\n const offset = i * 4\n emitterRenderData[offset + 0] = e.lit ? 1.0 : 0.0\n emitterRenderData[offset + 1] = e.emissive ?? 1.0\n emitterRenderData[offset + 2] = e.softness ?? 0.25\n emitterRenderData[offset + 3] = e.zOffset ?? 0.01\n }\n device.queue.writeBuffer(this.emitterRenderSettingsBuffer, 0, emitterRenderData)\n\n // Get or load particle texture\n const textureUrl = emitters[0]?.texture\n const particleTexture = textureUrl\n ? await this.particleSystem.loadTexture(textureUrl)\n : this.particleSystem.getDefaultTexture()\n\n // Create render bind group - use GBuffer depth directly (depthReadOnly allows this)\n const renderBindGroup = device.createBindGroup({\n layout: this.renderBindGroupLayout,\n entries: [\n { binding: 0, resource: { buffer: this.renderUniformBuffer } },\n { binding: 1, resource: { buffer: this.particleSystem.getParticleBuffer() } },\n { binding: 2, resource: particleTexture?.view || this._placeholderTexture.view },\n { binding: 3, resource: particleTexture?.sampler || this._placeholderSampler },\n { binding: 4, resource: this.gbuffer.depth.view },\n { binding: 5, resource: this.shadowPass?.getShadowMapView() || this._placeholderDepthView },\n { binding: 6, resource: this.shadowPass?.getShadowSampler() || this._placeholderComparisonSampler },\n { binding: 7, resource: { buffer: this.shadowPass?.getCascadeMatricesBuffer() || this._placeholderMatricesBuffer } },\n { binding: 8, resource: { buffer: this.emitterRenderSettingsBuffer } },\n { binding: 9, resource: this.environmentMap?.view || this._placeholderTexture.view },\n { binding: 10, resource: this.environmentMap?.sampler || this._placeholderSampler },\n // Point/spot lights buffer from lighting pass\n { binding: 11, resource: { buffer: this.lightingPass?.lightBuffer || this._placeholderLightsBuffer } },\n // Spot shadow atlas (uses same sampler as cascade shadows)\n { binding: 12, resource: this.shadowPass?.getSpotShadowAtlasView?.() || this._placeholderSpotShadowView },\n { binding: 13, resource: this.shadowPass?.getShadowSampler?.() || this._placeholderComparisonSampler },\n // Spot shadow matrices\n { binding: 14, resource: { buffer: this.shadowPass?.getSpotMatricesBuffer?.() || this._placeholderSpotMatricesBuffer } },\n ]\n })\n\n const maxParticles = this.particleSystem.globalMaxParticles\n\n // Check which blend modes are in use\n const hasAlpha = emitters.some(e => e.blendMode !== 'additive')\n const hasAdditive = emitters.some(e => e.blendMode === 'additive')\n\n // Draw alpha-blended particles first (back-to-front would be ideal, but we draw all)\n // NOTE: Must submit each pass separately because writeBuffer is immediate\n // but render passes execute when command buffer is submitted\n if (hasAlpha) {\n uniformData[48] = 0.0 // blendMode = alpha\n device.queue.writeBuffer(this.renderUniformBuffer, 0, uniformData)\n\n const alphaEncoder = device.createCommandEncoder({ label: 'Particle Alpha Pass' })\n const renderPass = alphaEncoder.beginRenderPass({\n colorAttachments: [{\n view: this.outputTexture.view,\n loadOp: 'load',\n storeOp: 'store',\n }],\n depthStencilAttachment: {\n view: this.gbuffer.depth.view,\n depthReadOnly: true,\n },\n label: 'Particle Render (Alpha)'\n })\n\n renderPass.setPipeline(this.renderPipelineAlpha)\n renderPass.setBindGroup(0, renderBindGroup)\n renderPass.draw(6, maxParticles, 0, 0)\n renderPass.end()\n device.queue.submit([alphaEncoder.finish()])\n }\n\n // Draw additive particles (order doesn't matter for additive)\n if (hasAdditive) {\n uniformData[48] = 1.0 // blendMode = additive\n device.queue.writeBuffer(this.renderUniformBuffer, 0, uniformData)\n\n const additiveEncoder = device.createCommandEncoder({ label: 'Particle Additive Pass' })\n const renderPass = additiveEncoder.beginRenderPass({\n colorAttachments: [{\n view: this.outputTexture.view,\n loadOp: 'load',\n storeOp: 'store',\n }],\n depthStencilAttachment: {\n view: this.gbuffer.depth.view,\n depthReadOnly: true,\n },\n label: 'Particle Render (Additive)'\n })\n\n renderPass.setPipeline(this.renderPipelineAdditive)\n renderPass.setBindGroup(0, renderBindGroup)\n renderPass.draw(6, maxParticles, 0, 0)\n renderPass.end()\n device.queue.submit([additiveEncoder.finish()])\n }\n }\n\n async _resize(width, height) {\n // Nothing to resize - uses shared resources\n }\n\n _destroy() {\n this._textureCache.clear()\n this.particleSystem = null\n }\n}\n\nexport { ParticlePass }\n","import { BasePass } from \"./BasePass.js\"\n\n/**\n * FogPass - Distance-based fog with height fade\n *\n * Features:\n * - Distance fog based on camera distance (not Z-depth)\n * - Two-gradient system: distance[0]->distance[1] and distance[1]->distance[2]\n * - Height fade: maximum fog at bottomY, zero fog at topY\n * - Emissive/bright colors show through fog more (HDR resistance)\n * - Applied before bloom so bright objects still bloom through fog\n *\n * Input: HDR lighting output, GBuffer (for depth texture)\n * Output: Fog-applied HDR texture\n */\nclass FogPass extends BasePass {\n constructor(engine = null) {\n super('Fog', engine)\n\n this.renderPipeline = null\n this.outputTexture = null\n this.inputTexture = null\n this.gbuffer = null\n this.uniformBuffer = null\n this.bindGroup = null\n this.sampler = null\n\n // Render dimensions\n this.width = 0\n this.height = 0\n }\n\n // Fog settings getters\n get fogEnabled() { return this.settings?.environment?.fog?.enabled ?? false }\n get fogColor() { return this.settings?.environment?.fog?.color ?? [0.8, 0.85, 0.9] }\n get fogDistances() { return this.settings?.environment?.fog?.distances ?? [0, 50, 200] }\n get fogAlpha() { return this.settings?.environment?.fog?.alpha ?? [0.0, 0.3, 0.8] }\n get fogHeightFade() { return this.settings?.environment?.fog?.heightFade ?? [-10, 100] }\n get fogBrightResist() { return this.settings?.environment?.fog?.brightResist ?? 0.8 }\n get fogDebug() { return this.settings?.environment?.fog?.debug ?? 0 } // 0=off, 1=show fogAlpha, 2=show distance, 3=show heightFactor\n\n /**\n * Set the input texture (HDR lighting output)\n */\n setInputTexture(texture) {\n if (this.inputTexture !== texture) {\n this.inputTexture = texture\n this._needsRebuild = true\n }\n }\n\n /**\n * Set the GBuffer for depth/normal reconstruction\n */\n setGBuffer(gbuffer) {\n if (this.gbuffer !== gbuffer) {\n this.gbuffer = gbuffer\n this._needsRebuild = true\n }\n }\n\n async _init() {\n const { device } = this.engine\n\n // Create sampler\n this.sampler = device.createSampler({\n label: 'Fog Sampler',\n minFilter: 'linear',\n magFilter: 'linear',\n addressModeU: 'clamp-to-edge',\n addressModeV: 'clamp-to-edge',\n })\n\n // Create uniform buffer\n // inverseProj (64) + inverseView (64) + cameraPosition (12) + near (4) +\n // far (4) + fogEnabled (4) + fogColor (12) + brightResist (4) +\n // distances (12) + pad (4) + alphas (12) + pad (4) + heightFade (8) + screenSize (8) = 224 bytes\n // Round to 256 for alignment\n this.uniformBuffer = device.createBuffer({\n label: 'Fog Uniforms',\n size: 256,\n usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST,\n })\n }\n\n async _buildPipeline() {\n if (!this.inputTexture || !this.gbuffer) {\n return\n }\n\n const { device } = this.engine\n\n // Destroy old output texture before creating new one\n if (this.outputTexture) {\n this.outputTexture.destroy()\n this.outputTexture = null\n this.outputTextureView = null\n }\n\n // Create output texture (same format as input)\n // Include COPY_SRC for planar reflection pass which copies fog output\n this.outputTexture = device.createTexture({\n label: 'Fog Output',\n size: { width: this.width || 1, height: this.height || 1, depthOrArrayLayers: 1 },\n format: 'rgba16float',\n usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_SRC,\n })\n this.outputTextureView = this.outputTexture.createView({ label: 'Fog Output View' })\n\n // Create bind group layout\n const bindGroupLayout = device.createBindGroupLayout({\n label: 'Fog BGL',\n entries: [\n { binding: 0, visibility: GPUShaderStage.FRAGMENT, buffer: { type: 'uniform' } },\n { binding: 1, visibility: GPUShaderStage.FRAGMENT, texture: { sampleType: 'float' } },\n { binding: 2, visibility: GPUShaderStage.FRAGMENT, sampler: { type: 'filtering' } },\n { binding: 3, visibility: GPUShaderStage.FRAGMENT, texture: { sampleType: 'depth' } },\n ],\n })\n\n const shaderModule = device.createShaderModule({\n label: 'Fog Shader',\n code: `\n // Uniforms packed for proper WGSL alignment (vec3f needs 16-byte alignment)\n struct Uniforms {\n inverseProjection: mat4x4f, // floats 0-15\n inverseView: mat4x4f, // floats 16-31\n cameraPosition: vec3f, // floats 32-34\n near: f32, // float 35\n fogColor: vec3f, // floats 36-38\n far: f32, // float 39\n distances: vec3f, // floats 40-42\n fogEnabled: f32, // float 43\n alphas: vec3f, // floats 44-46\n brightResist: f32, // float 47\n heightFade: vec2f, // floats 48-49\n screenSize: vec2f, // floats 50-51\n debug: f32, // float 52: 0=off, 1=fogAlpha, 2=distance, 3=heightFactor\n _pad: vec3f, // floats 53-55\n }\n\n @group(0) @binding(0) var<uniform> uniforms: Uniforms;\n @group(0) @binding(1) var inputTexture: texture_2d<f32>;\n @group(0) @binding(2) var inputSampler: sampler;\n @group(0) @binding(3) var depthTexture: texture_depth_2d;\n\n struct VertexOutput {\n @builtin(position) position: vec4f,\n @location(0) uv: vec2f,\n }\n\n @vertex\n fn vertexMain(@builtin(vertex_index) vertexIndex: u32) -> VertexOutput {\n var output: VertexOutput;\n // Full-screen triangle\n let x = f32((vertexIndex << 1u) & 2u);\n let y = f32(vertexIndex & 2u);\n output.position = vec4f(x * 2.0 - 1.0, y * 2.0 - 1.0, 0.0, 1.0);\n output.uv = vec2f(x, 1.0 - y);\n return output;\n }\n\n @fragment\n fn fragmentMain(input: VertexOutput) -> @location(0) vec4f {\n let color = textureSample(inputTexture, inputSampler, input.uv);\n\n // If fog disabled, pass through\n if (uniforms.fogEnabled < 0.5) {\n return color;\n }\n\n // Sample depth from GBuffer depth texture (same as lighting shader)\n let pixelCoords = vec2i(input.uv * uniforms.screenSize);\n let depth = textureLoad(depthTexture, pixelCoords, 0);\n\n // Check if this is sky (depth = 1 means far plane / cleared depth buffer)\n let isSky = depth >= 0.9999;\n\n // Get view ray direction (needed for both sky and geometry)\n var iuv = input.uv;\n iuv.y = 1.0 - iuv.y;\n let ndc = vec4f(iuv * 2.0 - 1.0, 0.0, 1.0);\n let viewRay = uniforms.inverseProjection * ndc;\n let rayDir = normalize(viewRay.xyz / viewRay.w);\n\n // For sky, treat as far distance; otherwise reconstruct linear depth\n var cameraDistance: f32;\n var worldPosY: f32;\n\n if (isSky) {\n // Sky is at far distance\n cameraDistance = uniforms.far;\n\n // Map view ray elevation to effective Y for height fade\n // The fog clear zone in sky depends on topY:\n // - Low topY (near camera): small clear zone, fog only at horizon\n // - Medium topY (25m): clear zone extends to ~60° above horizon\n // - High topY (100m+): almost no clear zone, fog fills sky\n let worldRayDir = normalize((uniforms.inverseView * vec4f(rayDir, 0.0)).xyz);\n let camY = uniforms.cameraPosition.y;\n let topY = uniforms.heightFade.y;\n let bottomY = uniforms.heightFade.x;\n\n if (worldRayDir.y > 0.0) {\n // Looking up: map elevation to effective Y\n // Use a reference height to control how much sky can be clear\n // At referenceHeight (50m), zenith reaches topY (fully clear)\n // Above that, zenith stays fogged\n let referenceHeight = 50.0;\n let fogHeight = topY - camY;\n let clearRange = min(fogHeight, referenceHeight);\n worldPosY = camY + worldRayDir.y * clearRange;\n } else {\n // Looking down or at horizon: below camera, full fog\n worldPosY = camY + worldRayDir.y * (camY - bottomY);\n }\n } else {\n // Reconstruct linear depth: depth = (z - near) / (far - near), so z = near + depth * (far - near)\n let linearDepth = uniforms.near + depth * (uniforms.far - uniforms.near);\n\n // Use linear depth directly as camera distance\n cameraDistance = linearDepth;\n\n // Reconstruct world position for height fade\n let viewPos = rayDir * (linearDepth / -rayDir.z);\n let worldPos4 = uniforms.inverseView * vec4f(viewPos, 1.0);\n worldPosY = worldPos4.y;\n }\n\n // Distance fog - two gradients\n var distanceFog: f32;\n let d0 = uniforms.distances.x;\n let d1 = uniforms.distances.y;\n let d2 = uniforms.distances.z;\n let a0 = uniforms.alphas.x;\n let a1 = uniforms.alphas.y;\n let a2 = uniforms.alphas.z;\n\n if (cameraDistance <= d0) {\n distanceFog = a0;\n } else if (cameraDistance <= d1) {\n let t = (cameraDistance - d0) / max(d1 - d0, 0.001);\n distanceFog = mix(a0, a1, t);\n } else if (cameraDistance <= d2) {\n let t = (cameraDistance - d1) / max(d2 - d1, 0.001);\n distanceFog = mix(a1, a2, t);\n } else {\n distanceFog = a2;\n }\n\n // Height fade - full fog at bottomY, zero fog at topY\n let bottomY = uniforms.heightFade.x;\n let topY = uniforms.heightFade.y;\n var heightFactor = clamp((worldPosY - bottomY) / max(topY - bottomY, 0.001), 0.0, 1.0);\n // Below bottomY = maximum fog\n if (worldPosY < bottomY) {\n heightFactor = 0.0;\n }\n var fogAlpha = distanceFog * (1.0 - heightFactor);\n\n // Emissive/bright resistance - HDR values show through fog\n // Calculate luminance\n let luminance = dot(color.rgb, vec3f(0.299, 0.587, 0.114));\n // For HDR values > 1.0, reduce fog effect\n let brightnessResist = clamp((luminance - 1.0) / 2.0, 0.0, 1.0);\n fogAlpha *= (1.0 - brightnessResist * uniforms.brightResist);\n\n // Debug output\n let debugMode = i32(uniforms.debug);\n if (debugMode == 1) {\n // Show fog alpha as grayscale\n return vec4f(vec3f(fogAlpha), 1.0);\n } else if (debugMode == 2) {\n // Show distance (normalized to 0-100m range)\n let normDist = clamp(cameraDistance / 100.0, 0.0, 1.0);\n return vec4f(vec3f(normDist), 1.0);\n } else if (debugMode == 3) {\n // Show height factor\n return vec4f(vec3f(heightFactor), 1.0);\n } else if (debugMode == 4) {\n // Show distance fog (before height fade)\n return vec4f(vec3f(distanceFog), 1.0);\n } else if (debugMode == 5) {\n // Show the actual fog color being used (to verify it matches particles)\n return vec4f(uniforms.fogColor, 1.0);\n }\n\n // Apply fog\n let foggedColor = mix(color.rgb, uniforms.fogColor, fogAlpha);\n\n return vec4f(foggedColor, color.a);\n }\n `,\n })\n\n this.renderPipeline = await device.createRenderPipelineAsync({\n label: 'Fog Pipeline',\n layout: device.createPipelineLayout({ bindGroupLayouts: [bindGroupLayout] }),\n vertex: { module: shaderModule, entryPoint: 'vertexMain' },\n fragment: {\n module: shaderModule,\n entryPoint: 'fragmentMain',\n targets: [{ format: 'rgba16float' }],\n },\n primitive: { topology: 'triangle-list' },\n })\n\n this.bindGroup = device.createBindGroup({\n label: 'Fog Bind Group',\n layout: bindGroupLayout,\n entries: [\n { binding: 0, resource: { buffer: this.uniformBuffer } },\n { binding: 1, resource: this.inputTexture.view },\n { binding: 2, resource: this.sampler },\n { binding: 3, resource: this.gbuffer.depth.view },\n ],\n })\n\n this._needsRebuild = false\n }\n\n async _execute(context) {\n const { device } = this.engine\n const { camera } = context\n\n // Skip if fog disabled\n if (!this.fogEnabled) {\n // Just copy input to output or return input directly\n this.outputTexture = null\n return\n }\n\n // Rebuild pipeline if needed\n if (this._needsRebuild) {\n await this._buildPipeline()\n }\n\n if (!this.renderPipeline || !this.inputTexture || !this.gbuffer || this._needsRebuild) {\n return\n }\n\n // Update uniforms - 256 bytes = 64 floats (matches shader struct layout)\n const uniformData = new Float32Array(64)\n\n // inverseProjection (floats 0-15)\n if (camera.iProj) {\n uniformData.set(camera.iProj, 0)\n }\n\n // inverseView (floats 16-31)\n if (camera.iView) {\n uniformData.set(camera.iView, 16)\n }\n\n // cameraPosition (floats 32-34) + near (float 35)\n uniformData[32] = camera.position[0]\n uniformData[33] = camera.position[1]\n uniformData[34] = camera.position[2]\n uniformData[35] = camera.near || 0.1\n\n // fogColor (floats 36-38) + far (float 39)\n const fogColor = this.fogColor\n uniformData[36] = fogColor[0]\n uniformData[37] = fogColor[1]\n uniformData[38] = fogColor[2]\n uniformData[39] = camera.far || 1000\n\n // distances (floats 40-42) + fogEnabled (float 43)\n const distances = this.fogDistances\n uniformData[40] = distances[0]\n uniformData[41] = distances[1]\n uniformData[42] = distances[2]\n uniformData[43] = this.fogEnabled ? 1.0 : 0.0\n\n // alphas (floats 44-46) + brightResist (float 47)\n const alphas = this.fogAlpha\n uniformData[44] = alphas[0]\n uniformData[45] = alphas[1]\n uniformData[46] = alphas[2]\n uniformData[47] = this.fogBrightResist\n\n // heightFade (floats 48-49) + screenSize (floats 50-51)\n const heightFade = this.fogHeightFade\n uniformData[48] = heightFade[0] // bottomY\n uniformData[49] = heightFade[1] // topY\n uniformData[50] = this.width\n uniformData[51] = this.height\n\n // debug (float 52) + padding (floats 53-55)\n uniformData[52] = this.fogDebug\n\n device.queue.writeBuffer(this.uniformBuffer, 0, uniformData)\n\n // Render fog pass\n const commandEncoder = device.createCommandEncoder({ label: 'Fog Pass' })\n\n const renderPass = commandEncoder.beginRenderPass({\n label: 'Fog Render Pass',\n colorAttachments: [{\n view: this.outputTextureView,\n clearValue: { r: 0, g: 0, b: 0, a: 1 },\n loadOp: 'clear',\n storeOp: 'store',\n }],\n })\n\n renderPass.setPipeline(this.renderPipeline)\n renderPass.setBindGroup(0, this.bindGroup)\n renderPass.draw(3)\n renderPass.end()\n\n device.queue.submit([commandEncoder.finish()])\n }\n\n async _resize(width, height) {\n this.width = width\n this.height = height\n this._needsRebuild = true\n }\n\n _destroy() {\n if (this.outputTexture) {\n this.outputTexture.destroy()\n this.outputTexture = null\n }\n this.renderPipeline = null\n }\n\n /**\n * Get the output texture (fog-applied HDR)\n */\n getOutputTexture() {\n // If fog is disabled, return input texture\n if (!this.fogEnabled || !this.outputTexture) {\n return this.inputTexture\n }\n return {\n texture: this.outputTexture,\n view: this.outputTextureView,\n sampler: this.sampler,\n }\n }\n}\n\nexport { FogPass }\n","import { BasePass } from \"./BasePass.js\"\nimport { GBufferPass } from \"./GBufferPass.js\"\nimport { LightingPass } from \"./LightingPass.js\"\nimport { ParticlePass } from \"./ParticlePass.js\"\nimport { FogPass } from \"./FogPass.js\"\nimport { mat4 } from \"../../math.js\"\n\n/**\n * PlanarReflectionPass - Renders the scene from a mirrored camera for water/floor reflections\n *\n * This pass creates a true planar reflection by:\n * 1. Mirroring the camera position across the ground plane (Y = groundLevel)\n * 2. Inverting the camera's Y view direction\n * 3. Rendering the full scene from this mirrored viewpoint\n * 4. Storing the result for later compositing with reflective surfaces\n *\n * Settings (from engine.settings.planarReflection):\n * - enabled: boolean - Enable/disable planar reflections\n * - groundLevel: number - Y coordinate of the reflection plane\n * - resolution: number - Resolution multiplier (0.5 = half res, 1.0 = full res)\n */\nclass PlanarReflectionPass extends BasePass {\n constructor(engine = null) {\n super('PlanarReflection', engine)\n\n // Output texture (HDR, same format as lighting output)\n this.outputTexture = null\n\n // Internal passes for rendering the mirrored view\n this.gbufferPass = null\n this.lightingPass = null\n this.particlePass = null\n this.fogPass = null\n\n // Particle system reference (set by RenderGraph)\n this.particleSystem = null\n\n // Mirrored camera matrices\n this.mirrorView = mat4.create()\n this.mirrorProj = mat4.create()\n\n // Dimensions\n this.width = 0\n this.height = 0\n\n // Dummy AO texture (white = no occlusion)\n this.dummyAO = null\n\n // Textures pending destruction (wait for GPU to finish using them)\n this._pendingDestroyRing = [[], [], []]\n this._pendingDestroyIndex = 0\n }\n\n async _init() {\n const { canvas } = this.engine\n\n // Get resolution multiplier from settings\n const resScale = this.settings?.planarReflection?.resolution ?? 0.5\n this.width = Math.floor(canvas.width * resScale)\n this.height = Math.floor(canvas.height * resScale)\n\n await this._createResources()\n }\n\n async _createResources() {\n const { device } = this.engine\n\n // Create internal GBuffer pass at reflection resolution\n this.gbufferPass = new GBufferPass(this.engine)\n await this.gbufferPass.initialize()\n await this.gbufferPass.resize(this.width, this.height)\n\n // Create internal Lighting pass\n this.lightingPass = new LightingPass(this.engine)\n await this.lightingPass.initialize()\n await this.lightingPass.resize(this.width, this.height)\n // Use exposure = 1.0 to avoid double exposure (main render applies exposure)\n this.lightingPass.exposureOverride = 1.0\n\n // Create dummy AO texture (white = no occlusion for reflections)\n const dummyAOTexture = device.createTexture({\n label: 'planarReflectionAO',\n size: [this.width, this.height],\n format: 'r8unorm',\n usage: GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.COPY_DST\n })\n const whiteData = new Uint8Array(this.width * this.height).fill(255)\n device.queue.writeTexture(\n { texture: dummyAOTexture },\n whiteData,\n { bytesPerRow: this.width },\n { width: this.width, height: this.height }\n )\n this.dummyAO = {\n texture: dummyAOTexture,\n view: dummyAOTexture.createView()\n }\n\n // Wire up passes\n await this.lightingPass.setGBuffer(this.gbufferPass.getGBuffer())\n this.lightingPass.setAOTexture(this.dummyAO)\n\n // Create internal Particle pass for reflected particles\n this.particlePass = new ParticlePass(this.engine)\n await this.particlePass.initialize()\n this.particlePass.setGBuffer(this.gbufferPass.getGBuffer())\n\n // Create internal Fog pass for reflected fog\n this.fogPass = new FogPass(this.engine)\n await this.fogPass.initialize()\n await this.fogPass.resize(this.width, this.height)\n\n // Set output texture to lighting output (fog will copy to this if enabled)\n this.outputTexture = this.lightingPass.getOutputTexture()\n }\n\n /**\n * Set particle system reference for rendering particles in reflection\n * @param {ParticleSystem} particleSystem\n */\n setParticleSystem(particleSystem) {\n this.particleSystem = particleSystem\n if (this.particlePass) {\n this.particlePass.setParticleSystem(particleSystem)\n }\n }\n\n /**\n * Set dependencies from main render pipeline\n * @param {Object} options\n * @param {Texture} options.environmentMap - Environment map for IBL\n * @param {number} options.encoding - Environment encoding (0=equirect, 1=octahedral)\n * @param {Object} options.shadowPass - Shadow pass for shadows in reflection\n * @param {Object} options.lightingPass - Lighting pass for point/spot lights\n * @param {Texture} options.noise - Noise texture (blue noise or bayer dither)\n * @param {number} options.noiseSize - Noise texture size\n */\n setDependencies(options) {\n const { environmentMap, encoding, shadowPass, lightingPass, noise, noiseSize } = options\n\n if (environmentMap) {\n this.lightingPass.setEnvironmentMap(environmentMap, encoding ?? 0)\n // Also set environment map for particle IBL in reflections\n if (this.particlePass) {\n this.particlePass.setEnvironmentMap(environmentMap, encoding ?? 0)\n }\n }\n if (shadowPass) {\n this.lightingPass.setShadowPass(shadowPass)\n // Also set shadow pass for particle lighting in reflections\n if (this.particlePass) {\n this.particlePass.setShadowPass(shadowPass)\n }\n }\n if (lightingPass) {\n // Set lighting pass for particle point/spot lights in reflections\n if (this.particlePass) {\n this.particlePass.setLightingPass(lightingPass)\n }\n }\n if (noise) {\n // Planar reflection always uses static noise (no animation)\n this.gbufferPass.setNoise(noise, noiseSize, false)\n this.lightingPass.setNoise(noise, noiseSize, false)\n }\n }\n\n /**\n * Execute planar reflection pass\n *\n * @param {Object} context\n * @param {Camera} context.camera - Main camera\n * @param {Object} context.meshes - Meshes to render\n * @param {Array} context.lights - Processed lights array\n * @param {Object} context.mainLight - Main directional light settings\n * @param {number} context.dt - Delta time\n */\n async _execute(context) {\n const { device, canvas, stats } = this.engine\n const { camera, meshes, lights, mainLight, dt = 0 } = context\n\n // Process deferred texture destruction (3 frames delayed)\n this._pendingDestroyIndex = (this._pendingDestroyIndex + 1) % 3\n const toDestroy = this._pendingDestroyRing[this._pendingDestroyIndex]\n for (const tex of toDestroy) {\n tex.destroy()\n }\n this._pendingDestroyRing[this._pendingDestroyIndex] = []\n\n // Check if enabled (RenderGraph already skips when disabled, but double-check)\n if (!this.settings?.planarReflection?.enabled) {\n stats.planarDrawCalls = 0\n stats.planarTriangles = 0\n return\n }\n\n // Get ground level from settings (can be changed in real-time)\n const groundLevel = this.settings?.planarReflection?.groundLevel ?? 0\n\n // Create mirrored camera\n const mirrorCamera = this._createMirrorCamera(camera, groundLevel)\n\n // Copy lights to internal lighting pass\n this.lightingPass.lights = lights\n\n // Disable reflection mode for now (focus on geometry first)\n this.lightingPass.reflectionMode = false\n\n // Set clip plane based on camera position relative to ground level\n // - Camera above ground: show objects above ground in reflection (discard below)\n // - Camera below ground: show objects below ground in reflection (discard above)\n const cameraY = camera.position[1]\n const cameraAboveGround = cameraY >= groundLevel\n\n this.gbufferPass.clipPlaneEnabled = true\n if (cameraAboveGround) {\n // Camera above water: render objects above water (discard below)\n this.gbufferPass.clipPlaneY = groundLevel + 0.001\n this.gbufferPass.clipPlaneDirection = 1.0 // discard below\n } else {\n // Camera below water: render objects below water (discard above)\n this.gbufferPass.clipPlaneY = groundLevel - 0.2\n this.gbufferPass.clipPlaneDirection = -1.0 // discard above\n }\n\n // Execute GBuffer pass with mirrored camera\n await this.gbufferPass.execute({\n camera: mirrorCamera,\n meshes,\n dt\n })\n\n // Capture planar reflection stats after gbuffer (before main pass resets them)\n stats.planarDrawCalls = stats.drawCalls\n stats.planarTriangles = stats.triangles\n\n // Reset clip plane after reflection render\n this.gbufferPass.clipPlaneEnabled = false\n\n // Execute Lighting pass\n await this.lightingPass.execute({\n camera: mirrorCamera,\n meshes,\n dt,\n lights,\n mainLight\n })\n\n // Reset reflection mode\n this.lightingPass.reflectionMode = false\n\n // Execute Particle pass with mirrored camera (renders onto lighting output)\n let hdrOutput = this.lightingPass.getOutputTexture()\n if (this.particlePass && this.particleSystem?.getActiveEmitters().length > 0) {\n this.particlePass.setOutputTexture(hdrOutput)\n await this.particlePass.execute({\n camera: mirrorCamera,\n dt,\n mainLight\n })\n }\n\n // Execute Fog pass for reflected fog\n const fogEnabled = this.engine?.settings?.environment?.fog?.enabled\n if (this.fogPass && fogEnabled) {\n this.fogPass.setInputTexture(hdrOutput)\n this.fogPass.setGBuffer(this.gbufferPass.getGBuffer())\n await this.fogPass.execute({\n camera: mirrorCamera,\n dt\n })\n const fogOutput = this.fogPass.getOutputTexture()\n if (fogOutput && fogOutput !== hdrOutput) {\n hdrOutput = fogOutput\n }\n }\n\n // Copy final output to our output texture (if they're different textures)\n if (hdrOutput?.texture && this.outputTexture?.texture &&\n hdrOutput.texture !== this.outputTexture.texture) {\n const commandEncoder = device.createCommandEncoder({ label: 'planarReflectionCopy' })\n commandEncoder.copyTextureToTexture(\n { texture: hdrOutput.texture },\n { texture: this.outputTexture.texture },\n [this.width, this.height, 1]\n )\n device.queue.submit([commandEncoder.finish()])\n }\n }\n\n /**\n * Create a mirrored camera for reflection rendering\n *\n * Classic planar reflection: mirror the camera below the ground plane\n * and render the scene from that mirrored viewpoint.\n *\n * @param {Camera} camera - Original camera\n * @param {number} groundLevel - Y coordinate of reflection plane\n * @returns {Object} Camera-like object with mirrored matrices\n */\n _createMirrorCamera(camera, groundLevel) {\n // We want: same camera position, but world geometry mirrored at groundLevel\n //\n // To achieve this:\n // - Camera stays at original position\n // - Apply reflection matrix to mirror world geometry\n // - mirrorView = camera.view * reflectionMatrix\n // This transforms: world point P -> reflected P' -> view space\n\n // User wants: same camera position/angle, but world geometry mirrored at groundLevel\n //\n // To mirror world geometry without moving camera:\n // Apply reflection matrix to world coordinates BEFORE view transform\n //\n // Normal: clipPos = proj * view * world\n // Mirrored: clipPos = proj * view * reflection * world\n //\n // We combine: mirrorView = view * reflection\n // Result: geometry is reflected, camera stays the same\n\n // Reflection matrix for Y = groundLevel\n const reflectionMatrix = mat4.create()\n reflectionMatrix[5] = -1 // Flip Y\n reflectionMatrix[13] = 2 * groundLevel // Offset\n\n // Combine view with reflection: view * reflection\n mat4.multiply(this.mirrorView, camera.view, reflectionMatrix)\n\n // Copy projection and flip Y to fix winding order\n // The reflection matrix changes coordinate system handedness, which reverses\n // triangle winding. Flipping projection Y reverses it back, but also flips\n // the rendered image vertically - we compensate by flipping UV.y when sampling.\n mat4.copy(this.mirrorProj, camera.proj)\n this.mirrorProj[5] *= -1 // Flip projection Y\n\n // Create inverse matrices\n const newIView = mat4.create()\n const newIProj = mat4.create()\n const newIViewProj = mat4.create()\n const viewProj = mat4.create()\n mat4.invert(newIView, this.mirrorView)\n mat4.invert(newIProj, this.mirrorProj)\n mat4.multiply(viewProj, this.mirrorProj, this.mirrorView)\n mat4.invert(newIViewProj, viewProj)\n\n // Return camera with mirrored view matrix\n return {\n view: this.mirrorView,\n proj: this.mirrorProj,\n iView: newIView,\n iProj: newIProj,\n iViewProj: newIViewProj,\n viewProj,\n position: camera.position, // Same camera position\n near: camera.near,\n far: camera.far,\n aspect: camera.aspect,\n jitterEnabled: false,\n jitterOffset: [0, 0],\n screenSize: [this.width, this.height],\n updateMatrix: () => {},\n updateView: () => {}\n }\n }\n\n /**\n * Get the reflection texture for compositing\n * @returns {Texture} HDR reflection texture\n */\n getReflectionTexture() {\n return this.outputTexture\n }\n\n async _resize(width, height) {\n // Get resolution multiplier from settings\n const resScale = this.settings?.planarReflection?.resolution ?? 0.5\n this.width = Math.floor(width * resScale)\n this.height = Math.floor(height * resScale)\n\n // Resize internal passes\n if (this.gbufferPass) {\n await this.gbufferPass.resize(this.width, this.height)\n }\n if (this.lightingPass) {\n await this.lightingPass.resize(this.width, this.height)\n await this.lightingPass.setGBuffer(this.gbufferPass.getGBuffer())\n }\n if (this.particlePass) {\n this.particlePass.setGBuffer(this.gbufferPass.getGBuffer())\n }\n if (this.fogPass) {\n await this.fogPass.resize(this.width, this.height)\n }\n\n // Queue old dummy AO for deferred destruction\n if (this.dummyAO?.texture) {\n this._pendingDestroyRing[this._pendingDestroyIndex].push(this.dummyAO.texture)\n }\n\n const { device } = this.engine\n const dummyAOTexture = device.createTexture({\n label: 'planarReflectionAO',\n size: [this.width, this.height],\n format: 'r8unorm',\n usage: GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.COPY_DST\n })\n const whiteData = new Uint8Array(this.width * this.height).fill(255)\n device.queue.writeTexture(\n { texture: dummyAOTexture },\n whiteData,\n { bytesPerRow: this.width },\n { width: this.width, height: this.height }\n )\n this.dummyAO = {\n texture: dummyAOTexture,\n view: dummyAOTexture.createView()\n }\n this.lightingPass.setAOTexture(this.dummyAO)\n\n // Update output reference\n this.outputTexture = this.lightingPass.getOutputTexture()\n }\n\n _destroy() {\n if (this.gbufferPass) {\n this.gbufferPass.destroy()\n this.gbufferPass = null\n }\n if (this.lightingPass) {\n this.lightingPass.destroy()\n this.lightingPass = null\n }\n if (this.particlePass) {\n this.particlePass.destroy()\n this.particlePass = null\n }\n if (this.fogPass) {\n this.fogPass.destroy()\n this.fogPass = null\n }\n if (this.dummyAO?.texture) {\n this.dummyAO.texture.destroy()\n this.dummyAO = null\n }\n // Clean up any pending textures in ring buffer\n for (const slot of this._pendingDestroyRing) {\n for (const tex of slot) {\n tex.destroy()\n }\n }\n this._pendingDestroyRing = [[], [], []]\n this.outputTexture = null\n }\n}\n\nexport { PlanarReflectionPass }\n","// HiZ Reduce Compute Shader\r\n// Reduces the depth buffer to MIN and MAX depth per 64x64 tile for occlusion culling\r\n// MIN depth = closest geometry in tile (occluder surface)\r\n// MAX depth = farthest geometry in tile (if < 1.0, tile is fully covered)\r\n// Tile thickness = MAX - MIN (thin for walls, thick for angled ground)\r\n\r\nstruct Uniforms {\r\n screenWidth: f32,\r\n screenHeight: f32,\r\n tileCountX: f32,\r\n tileCountY: f32,\r\n tileSize: f32,\r\n near: f32,\r\n far: f32,\r\n clearValue: f32, // 1.0 to clear, 0.0 to accumulate\r\n}\r\n\r\n@group(0) @binding(0) var<uniform> uniforms: Uniforms;\r\n@group(0) @binding(1) var depthTexture: texture_depth_2d;\r\n@group(0) @binding(2) var<storage, read_write> hizBuffer: array<f32>;\r\n\r\n// Workgroup size: 8x8 threads, each thread processes 8x8 pixels = 64x64 per workgroup\r\nvar<workgroup> sharedMinDepth: array<f32, 64>; // 8x8 = 64 threads\r\nvar<workgroup> sharedMaxDepth: array<f32, 64>;\r\n\r\n@compute @workgroup_size(8, 8, 1)\r\nfn main(\r\n @builtin(global_invocation_id) globalId: vec3u,\r\n @builtin(local_invocation_id) localId: vec3u,\r\n @builtin(workgroup_id) workgroupId: vec3u\r\n) {\r\n let tileX = workgroupId.x;\r\n let tileY = workgroupId.y;\r\n let tileIndex = tileY * u32(uniforms.tileCountX) + tileX;\r\n\r\n // Check if this tile is within bounds\r\n if (tileX >= u32(uniforms.tileCountX) || tileY >= u32(uniforms.tileCountY)) {\r\n return;\r\n }\r\n\r\n // Each thread processes an 8x8 block within the 64x64 tile\r\n let localIndex = localId.y * 8u + localId.x;\r\n let blockStartX = tileX * 64u + localId.x * 8u;\r\n let blockStartY = tileY * 64u + localId.y * 8u;\r\n\r\n // Find MIN and MAX depth in this thread's 8x8 block\r\n var minDepth: f32 = 1.0; // Start at far plane\r\n var maxDepth: f32 = 0.0; // Start at near plane\r\n\r\n for (var y = 0u; y < 8u; y = y + 1u) {\r\n for (var x = 0u; x < 8u; x = x + 1u) {\r\n let pixelX = blockStartX + x;\r\n let pixelY = blockStartY + y;\r\n\r\n // Skip pixels outside screen bounds\r\n if (pixelX < u32(uniforms.screenWidth) && pixelY < u32(uniforms.screenHeight)) {\r\n let depth = textureLoad(depthTexture, vec2u(pixelX, pixelY), 0);\r\n minDepth = min(minDepth, depth);\r\n maxDepth = max(maxDepth, depth);\r\n }\r\n }\r\n }\r\n\r\n // Store in shared memory\r\n sharedMinDepth[localIndex] = minDepth;\r\n sharedMaxDepth[localIndex] = maxDepth;\r\n workgroupBarrier();\r\n\r\n // Parallel reduction in shared memory\r\n // Step 1: 64 -> 32\r\n if (localIndex < 32u) {\r\n sharedMinDepth[localIndex] = min(sharedMinDepth[localIndex], sharedMinDepth[localIndex + 32u]);\r\n sharedMaxDepth[localIndex] = max(sharedMaxDepth[localIndex], sharedMaxDepth[localIndex + 32u]);\r\n }\r\n workgroupBarrier();\r\n\r\n // Step 2: 32 -> 16\r\n if (localIndex < 16u) {\r\n sharedMinDepth[localIndex] = min(sharedMinDepth[localIndex], sharedMinDepth[localIndex + 16u]);\r\n sharedMaxDepth[localIndex] = max(sharedMaxDepth[localIndex], sharedMaxDepth[localIndex + 16u]);\r\n }\r\n workgroupBarrier();\r\n\r\n // Step 3: 16 -> 8\r\n if (localIndex < 8u) {\r\n sharedMinDepth[localIndex] = min(sharedMinDepth[localIndex], sharedMinDepth[localIndex + 8u]);\r\n sharedMaxDepth[localIndex] = max(sharedMaxDepth[localIndex], sharedMaxDepth[localIndex + 8u]);\r\n }\r\n workgroupBarrier();\r\n\r\n // Step 4: 8 -> 4\r\n if (localIndex < 4u) {\r\n sharedMinDepth[localIndex] = min(sharedMinDepth[localIndex], sharedMinDepth[localIndex + 4u]);\r\n sharedMaxDepth[localIndex] = max(sharedMaxDepth[localIndex], sharedMaxDepth[localIndex + 4u]);\r\n }\r\n workgroupBarrier();\r\n\r\n // Step 5: 4 -> 2\r\n if (localIndex < 2u) {\r\n sharedMinDepth[localIndex] = min(sharedMinDepth[localIndex], sharedMinDepth[localIndex + 2u]);\r\n sharedMaxDepth[localIndex] = max(sharedMaxDepth[localIndex], sharedMaxDepth[localIndex + 2u]);\r\n }\r\n workgroupBarrier();\r\n\r\n // Step 6: 2 -> 1 (final result)\r\n if (localIndex == 0u) {\r\n let finalMinDepth = min(sharedMinDepth[0], sharedMinDepth[1]);\r\n let finalMaxDepth = max(sharedMaxDepth[0], sharedMaxDepth[1]);\r\n\r\n // Store both min and max depth (2 floats per tile, interleaved)\r\n hizBuffer[tileIndex * 2u] = finalMinDepth;\r\n hizBuffer[tileIndex * 2u + 1u] = finalMaxDepth;\r\n }\r\n}\r\n","import { BasePass } from \"./BasePass.js\"\r\nimport { vec3 } from \"../../math.js\"\r\n\r\nimport hizReduceWGSL from \"../shaders/hiz_reduce.wgsl\"\r\n\r\n/**\r\n * HiZPass - Hierarchical Z-Buffer for occlusion culling\r\n *\r\n * Reduces the depth buffer to maximum depth per 64x64 tile.\r\n * The CPU can then use this data to cull objects that are behind\r\n * the geometry from the previous frame.\r\n *\r\n * Features:\r\n * - 64x64 pixel tiles for coarse occlusion testing\r\n * - Camera movement detection to invalidate stale data\r\n * - Async GPU->CPU readback for non-blocking culling\r\n */\r\n\r\n// Fixed tile size - must match compute shader (hiz_reduce.wgsl)\r\nconst TILE_SIZE = 64\r\n\r\n// Maximum tiles before disabling occlusion culling (too slow to readback)\r\nconst MAX_TILES_FOR_OCCLUSION = 2500\r\n\r\n// Thresholds for invalidating HiZ data\r\nconst POSITION_THRESHOLD = 0.5 // Units of movement before invalidation\r\nconst ROTATION_THRESHOLD = 0.02 // Radians of rotation before invalidation (~1 degree)\r\n\r\n// Maximum frames of readback latency before invalidating occlusion data\r\nconst MAX_READBACK_LATENCY = 3\r\n\r\nclass HiZPass extends BasePass {\r\n constructor(engine = null) {\r\n super('HiZ', engine)\r\n\r\n // Compute pipeline\r\n this.pipeline = null\r\n this.bindGroupLayout = null\r\n\r\n // Buffers\r\n this.hizBuffer = null // GPU storage buffer (max Z per tile)\r\n this.stagingBuffers = [null, null] // Double-buffered staging for CPU readback\r\n this.stagingBufferInUse = [false, false] // Track which buffers are currently mapped\r\n this.currentStagingIndex = 0 // Which staging buffer to use next\r\n this.uniformBuffer = null\r\n\r\n // Tile grid dimensions\r\n this.tileCountX = 0\r\n this.tileCountY = 0\r\n this.totalTiles = 0\r\n this._tooManyTiles = false // True if resolution is too high for efficient occlusion\r\n\r\n // Frame tracking for readback latency\r\n this._frameCounter = 0 // Current frame number\r\n this._lastReadbackFrame = 0 // Frame when last readback completed\r\n\r\n // CPU-side HiZ data (double-buffered to prevent race conditions)\r\n this.hizDataBuffers = [null, null] // Two Float32Array buffers for double-buffering\r\n this.readHizIndex = 0 // Which buffer is currently used for reading\r\n this.writeHizIndex = 1 // Which buffer is currently used for writing\r\n this.hizDataReady = false // Whether CPU data is valid for this frame\r\n this.pendingReadback = null // Promise for pending readback\r\n\r\n // Debug stats for occlusion testing\r\n this.debugStats = {\r\n tested: 0,\r\n occluded: 0,\r\n skippedTileSpan: 0,\r\n visibleSkyGap: 0,\r\n visibleInFront: 0,\r\n }\r\n\r\n // Camera tracking for invalidation\r\n this.lastCameraPosition = vec3.create()\r\n this.lastCameraDirection = vec3.create()\r\n this.hasValidHistory = false // Whether we have valid previous frame data\r\n\r\n // Depth texture reference\r\n this.depthTexture = null\r\n\r\n // Screen dimensions\r\n this.screenWidth = 0\r\n this.screenHeight = 0\r\n\r\n // Flag to prevent operations during destruction\r\n this._destroyed = false\r\n\r\n // Warmup frames - occlusion is disabled until scene has rendered for a few frames\r\n // This prevents false occlusion on engine creation when depth buffer is not yet populated\r\n this._warmupFramesRemaining = 5\r\n }\r\n\r\n /**\r\n * Set the depth texture to read from (from GBuffer)\r\n * @param {Object} depth - Depth texture object with .texture and .view\r\n */\r\n setDepthTexture(depth) {\r\n this.depthTexture = depth\r\n }\r\n\r\n /**\r\n * Invalidate occlusion culling data and reset warmup period.\r\n * Call this after engine creation, scene loading, or major camera changes\r\n * to prevent incorrect occlusion culling with stale data.\r\n */\r\n invalidate() {\r\n this.hasValidHistory = false\r\n this.hizDataReady = false\r\n this._warmupFramesRemaining = 5 // Wait 5 frames before enabling occlusion\r\n // Reset camera tracking to avoid false invalidations\r\n vec3.set(this.lastCameraPosition, 0, 0, 0)\r\n vec3.set(this.lastCameraDirection, 0, 0, 0)\r\n }\r\n\r\n async _init() {\r\n const { device, canvas } = this.engine\r\n await this._createResources(canvas.width, canvas.height)\r\n }\r\n\r\n async _createResources(width, height) {\r\n // Skip if dimensions haven't changed (avoid double init)\r\n if (this.screenWidth === width && this.screenHeight === height && this.hizBuffer) {\r\n return\r\n }\r\n\r\n const { device } = this.engine\r\n\r\n // Mark as destroyed to cancel any pending readback\r\n this._destroyed = true\r\n\r\n // Wait for any pending readback to complete\r\n if (this.pendingReadback) {\r\n try {\r\n await this.pendingReadback\r\n } catch (e) {\r\n // Ignore errors from cancelled readback\r\n }\r\n this.pendingReadback = null\r\n }\r\n\r\n this.screenWidth = width\r\n this.screenHeight = height\r\n\r\n // Calculate tile grid dimensions (fixed 64px tiles to match compute shader)\r\n this.tileCountX = Math.ceil(width / TILE_SIZE)\r\n this.tileCountY = Math.ceil(height / TILE_SIZE)\r\n this.totalTiles = this.tileCountX * this.tileCountY\r\n\r\n // Check if we have too many tiles for efficient occlusion culling\r\n this._tooManyTiles = this.totalTiles > MAX_TILES_FOR_OCCLUSION\r\n\r\n console.log(`HiZ: ${width}x${height} -> ${TILE_SIZE}px tiles, ${this.tileCountX}x${this.tileCountY} = ${this.totalTiles} tiles${this._tooManyTiles ? ' (occlusion disabled - too many tiles)' : ''}`)\r\n\r\n // Destroy old buffers\r\n if (this.hizBuffer) this.hizBuffer.destroy()\r\n for (let i = 0; i < 2; i++) {\r\n if (this.stagingBuffers[i]) {\r\n this.stagingBuffers[i].destroy()\r\n }\r\n }\r\n if (this.uniformBuffer) this.uniformBuffer.destroy()\r\n\r\n // Create HiZ buffer (2 floats per tile: minZ, maxZ)\r\n const bufferSize = this.totalTiles * 2 * 4 // 2 floats * 4 bytes per float\r\n this.hizBuffer = device.createBuffer({\r\n label: 'HiZ Buffer',\r\n size: bufferSize,\r\n usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC | GPUBufferUsage.COPY_DST,\r\n })\r\n\r\n // Create double-buffered staging buffers for CPU readback\r\n for (let i = 0; i < 2; i++) {\r\n this.stagingBuffers[i] = device.createBuffer({\r\n label: `HiZ Staging Buffer ${i}`,\r\n size: bufferSize,\r\n usage: GPUBufferUsage.COPY_DST | GPUBufferUsage.MAP_READ,\r\n })\r\n this.stagingBufferInUse[i] = false\r\n }\r\n this.currentStagingIndex = 0\r\n\r\n // Create uniform buffer\r\n this.uniformBuffer = device.createBuffer({\r\n label: 'HiZ Uniforms',\r\n size: 32, // 8 floats\r\n usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST,\r\n })\r\n\r\n // Create CPU-side data arrays (double-buffered, 2 floats per tile: minZ, maxZ)\r\n this.hizDataBuffers[0] = new Float32Array(this.totalTiles * 2)\r\n this.hizDataBuffers[1] = new Float32Array(this.totalTiles * 2)\r\n // Initialize: minZ=1.0, maxZ=1.0 (sky) - everything passes occlusion test\r\n for (let i = 0; i < this.totalTiles; i++) {\r\n this.hizDataBuffers[0][i * 2] = 1.0 // minZ\r\n this.hizDataBuffers[0][i * 2 + 1] = 1.0 // maxZ\r\n this.hizDataBuffers[1][i * 2] = 1.0\r\n this.hizDataBuffers[1][i * 2 + 1] = 1.0\r\n }\r\n this.readHizIndex = 0\r\n this.writeHizIndex = 1\r\n\r\n // Create compute pipeline\r\n const shaderModule = device.createShaderModule({\r\n label: 'HiZ Reduce Shader',\r\n code: hizReduceWGSL,\r\n })\r\n\r\n this.bindGroupLayout = device.createBindGroupLayout({\r\n label: 'HiZ Reduce BGL',\r\n entries: [\r\n { binding: 0, visibility: GPUShaderStage.COMPUTE, buffer: { type: 'uniform' } },\r\n { binding: 1, visibility: GPUShaderStage.COMPUTE, texture: { sampleType: 'depth' } },\r\n { binding: 2, visibility: GPUShaderStage.COMPUTE, buffer: { type: 'storage' } },\r\n ],\r\n })\r\n\r\n this.pipeline = await device.createComputePipelineAsync({\r\n label: 'HiZ Reduce Pipeline',\r\n layout: device.createPipelineLayout({ bindGroupLayouts: [this.bindGroupLayout] }),\r\n compute: { module: shaderModule, entryPoint: 'main' },\r\n })\r\n\r\n // Reset state\r\n this.hasValidHistory = false\r\n this._destroyed = false\r\n this.hizDataReady = false\r\n this.pendingReadback = null\r\n this._warmupFramesRemaining = 5 // Wait a few frames after resize before enabling occlusion\r\n }\r\n\r\n /**\r\n * Check if camera has moved significantly, requiring HiZ invalidation\r\n * @param {Camera} camera - Current camera\r\n * @returns {boolean} True if camera moved too much\r\n */\r\n _checkCameraMovement(camera) {\r\n const position = camera.position\r\n const direction = camera.direction\r\n\r\n // Calculate position delta\r\n const dx = position[0] - this.lastCameraPosition[0]\r\n const dy = position[1] - this.lastCameraPosition[1]\r\n const dz = position[2] - this.lastCameraPosition[2]\r\n const positionDelta = Math.sqrt(dx * dx + dy * dy + dz * dz)\r\n\r\n // Calculate rotation delta (dot product of directions)\r\n const dot = direction[0] * this.lastCameraDirection[0] +\r\n direction[1] * this.lastCameraDirection[1] +\r\n direction[2] * this.lastCameraDirection[2]\r\n // Clamp to avoid NaN from acos\r\n const clampedDot = Math.max(-1, Math.min(1, dot))\r\n const rotationDelta = Math.acos(clampedDot)\r\n\r\n // Update last camera state\r\n vec3.copy(this.lastCameraPosition, position)\r\n vec3.copy(this.lastCameraDirection, direction)\r\n\r\n // Check thresholds\r\n const positionThreshold = this.settings?.occlusionCulling?.positionThreshold ?? POSITION_THRESHOLD\r\n const rotationThreshold = this.settings?.occlusionCulling?.rotationThreshold ?? ROTATION_THRESHOLD\r\n\r\n return positionDelta > positionThreshold || rotationDelta > rotationThreshold\r\n }\r\n\r\n /**\r\n * Prepare HiZ data for occlusion tests - call BEFORE any culling\r\n */\r\n prepareForOcclusionTests(camera) {\r\n if (!camera) return\r\n\r\n // Reset debug stats at start of frame\r\n this.resetDebugStats()\r\n\r\n // No camera movement invalidation - the 100% depth gap requirement\r\n // handles stale data gracefully, and next frame will be correct\r\n }\r\n\r\n async _execute(context) {\r\n const { device } = this.engine\r\n const { camera } = context\r\n\r\n // Increment frame counter\r\n this._frameCounter++\r\n\r\n // Decrement warmup counter - occlusion is disabled until this reaches 0\r\n if (this._warmupFramesRemaining > 0) {\r\n this._warmupFramesRemaining--\r\n }\r\n\r\n // Check if occlusion culling is enabled\r\n if (!this.settings?.occlusionCulling?.enabled) {\r\n return\r\n }\r\n\r\n if (!this.depthTexture || !this.pipeline) {\r\n return\r\n }\r\n\r\n // Note: Camera movement check is now done in prepareForOcclusionTests()\r\n // which is called before light culling\r\n\r\n // Determine if we should clear or accumulate\r\n const shouldClear = !this.hasValidHistory\r\n\r\n // Update uniforms\r\n const near = camera.near || 0.05\r\n const far = camera.far || 1000\r\n device.queue.writeBuffer(this.uniformBuffer, 0, new Float32Array([\r\n this.screenWidth,\r\n this.screenHeight,\r\n this.tileCountX,\r\n this.tileCountY,\r\n TILE_SIZE,\r\n near,\r\n far,\r\n shouldClear ? 1.0 : 0.0, // clearValue\r\n ]))\r\n\r\n // Create bind group\r\n const bindGroup = device.createBindGroup({\r\n label: 'HiZ Reduce Bind Group',\r\n layout: this.bindGroupLayout,\r\n entries: [\r\n { binding: 0, resource: { buffer: this.uniformBuffer } },\r\n { binding: 1, resource: this.depthTexture.view },\r\n { binding: 2, resource: { buffer: this.hizBuffer } },\r\n ],\r\n })\r\n\r\n // Execute compute shader\r\n const commandEncoder = device.createCommandEncoder({ label: 'HiZ Reduce' })\r\n\r\n const computePass = commandEncoder.beginComputePass({ label: 'HiZ Reduce Pass' })\r\n computePass.setPipeline(this.pipeline)\r\n computePass.setBindGroup(0, bindGroup)\r\n computePass.dispatchWorkgroups(this.tileCountX, this.tileCountY, 1)\r\n computePass.end()\r\n\r\n // Use double-buffered staging: find a buffer that's not currently in use\r\n let stagingIndex = this.currentStagingIndex\r\n let stagingBuffer = this.stagingBuffers[stagingIndex]\r\n\r\n // If current buffer is in use, try the other one\r\n if (this.stagingBufferInUse[stagingIndex]) {\r\n stagingIndex = (stagingIndex + 1) % 2\r\n stagingBuffer = this.stagingBuffers[stagingIndex]\r\n }\r\n\r\n // If both buffers are in use, skip this frame's copy (use stale data)\r\n if (this.stagingBufferInUse[stagingIndex]) {\r\n device.queue.submit([commandEncoder.finish()])\r\n this.hasValidHistory = true\r\n return\r\n }\r\n\r\n // Update for next frame\r\n this.currentStagingIndex = (stagingIndex + 1) % 2\r\n\r\n // Copy to staging buffer for CPU readback (2 floats per tile)\r\n commandEncoder.copyBufferToBuffer(\r\n this.hizBuffer, 0,\r\n stagingBuffer, 0,\r\n this.totalTiles * 2 * 4\r\n )\r\n\r\n device.queue.submit([commandEncoder.finish()])\r\n\r\n // Mark that we now have valid history\r\n this.hasValidHistory = true\r\n\r\n // Start async readback (non-blocking)\r\n this._startReadback(stagingBuffer, stagingIndex)\r\n }\r\n\r\n /**\r\n * Start async GPU->CPU readback\r\n * @private\r\n * @param {GPUBuffer} stagingBuffer - The staging buffer to read from\r\n * @param {number} stagingIndex - Which buffer index this is\r\n */\r\n async _startReadback(stagingBuffer, stagingIndex) {\r\n // Don't start new readback if destroyed\r\n if (this._destroyed) return\r\n\r\n // Mark buffer as in use\r\n this.stagingBufferInUse[stagingIndex] = true\r\n\r\n // Capture which CPU buffer to write to and current frame\r\n const writeIndex = this.writeHizIndex\r\n const requestedFrame = this._frameCounter\r\n\r\n // Create the readback promise\r\n const readbackPromise = (async () => {\r\n try {\r\n await stagingBuffer.mapAsync(GPUMapMode.READ)\r\n\r\n // Check if we were destroyed while waiting\r\n if (this._destroyed) {\r\n try { stagingBuffer.unmap() } catch (e) {}\r\n return\r\n }\r\n\r\n // Write to the write buffer (not the read buffer)\r\n const data = new Float32Array(stagingBuffer.getMappedRange())\r\n this.hizDataBuffers[writeIndex].set(data)\r\n stagingBuffer.unmap()\r\n\r\n // Now atomically swap: make the write buffer the new read buffer\r\n // This ensures readers never see partial data\r\n this.readHizIndex = writeIndex\r\n this.writeHizIndex = writeIndex === 0 ? 1 : 0\r\n\r\n // Track when this readback completed\r\n this._lastReadbackFrame = this._frameCounter\r\n\r\n this.hizDataReady = true\r\n } catch (e) {\r\n // Buffer might be destroyed during resize - this is expected\r\n if (!this._destroyed) {\r\n console.warn('HiZ readback failed:', e)\r\n }\r\n } finally {\r\n // Mark buffer as no longer in use\r\n this.stagingBufferInUse[stagingIndex] = false\r\n }\r\n })()\r\n\r\n // Store the promise so we can wait for it during resize\r\n this.pendingReadback = readbackPromise\r\n\r\n // Don't await here - let it run in background\r\n readbackPromise.then(() => {\r\n // Only clear if this is still the pending readback\r\n if (this.pendingReadback === readbackPromise) {\r\n this.pendingReadback = null\r\n }\r\n })\r\n }\r\n\r\n /**\r\n * Get min and max depth for a tile\r\n * minDepth = closest geometry (occluder surface)\r\n * maxDepth = farthest geometry (if < 1.0, tile is fully covered)\r\n * @param {number} tileX - Tile X coordinate\r\n * @param {number} tileY - Tile Y coordinate\r\n * @returns {{ minDepth: number, maxDepth: number }} Depth values (0-1, 0=near, 1=far/sky)\r\n */\r\n getTileMinMaxDepth(tileX, tileY) {\r\n const hizData = this.hizDataBuffers[this.readHizIndex]\r\n if (!this.hizDataReady || !hizData) {\r\n return { minDepth: 1.0, maxDepth: 1.0 } // No data - assume sky (no occlusion)\r\n }\r\n\r\n if (tileX < 0 || tileX >= this.tileCountX ||\r\n tileY < 0 || tileY >= this.tileCountY) {\r\n return { minDepth: 1.0, maxDepth: 1.0 } // Out of bounds - assume sky\r\n }\r\n\r\n const index = (tileY * this.tileCountX + tileX) * 2\r\n return {\r\n minDepth: hizData[index],\r\n maxDepth: hizData[index + 1]\r\n }\r\n }\r\n\r\n /**\r\n * Get the maximum depth for a tile (backward compatibility)\r\n * @param {number} tileX - Tile X coordinate\r\n * @param {number} tileY - Tile Y coordinate\r\n * @returns {number} Maximum depth (0-1, 0=near, 1=far/sky)\r\n */\r\n getTileMaxDepth(tileX, tileY) {\r\n return this.getTileMinMaxDepth(tileX, tileY).maxDepth\r\n }\r\n\r\n /**\r\n * Test if a bounding sphere is occluded\r\n *\r\n * Uses MIN/MAX depth per tile to calculate adaptive occlusion threshold.\r\n * Tile thickness = maxDepth - minDepth (thin for walls, thick for angled ground)\r\n *\r\n * Occlusion threshold = max(1m, tileThickness, 2*sphereRadius) in linear depth\r\n * Object is occluded if sphereFrontDepth > tileMinDepth + threshold for ALL tiles\r\n *\r\n * @param {Object} bsphere - Bounding sphere with center[3] and radius\r\n * @param {mat4} viewProj - View-projection matrix\r\n * @param {number} near - Near plane distance\r\n * @param {number} far - Far plane distance\r\n * @param {Array} cameraPos - Camera position [x, y, z]\r\n * @returns {boolean} True if definitely occluded, false if potentially visible\r\n */\r\n testSphereOcclusion(bsphere, viewProj, near, far, cameraPos) {\r\n this.debugStats.tested++\r\n\r\n // Warmup period - disable occlusion for first few frames after creation/reset\r\n // This ensures the depth buffer has valid geometry before we use it for culling\r\n if (this._warmupFramesRemaining > 0) {\r\n return false // Still warming up - assume visible\r\n }\r\n\r\n // Safety checks\r\n if (!this.hizDataReady || !this.hasValidHistory) {\r\n return false // No valid data - assume visible\r\n }\r\n\r\n // Skip occlusion at very high resolutions (too many tiles = slow readback)\r\n if (this._tooManyTiles) {\r\n return false // Disabled at this resolution\r\n }\r\n\r\n // Check for stale data - if readback is lagging too far behind, skip occlusion\r\n // This prevents objects from being incorrectly culled when GPU is slow\r\n const readbackLatency = this._frameCounter - this._lastReadbackFrame\r\n if (readbackLatency > MAX_READBACK_LATENCY) {\r\n return false // Data too stale - assume visible\r\n }\r\n\r\n if (!cameraPos || !viewProj || !bsphere?.center) {\r\n return false // Missing required data - assume visible\r\n }\r\n\r\n if (this.screenWidth <= 0 || this.screenHeight <= 0 || this.tileCountX <= 0) {\r\n return false // Invalid screen size - assume visible\r\n }\r\n\r\n // Use provided near/far or defaults\r\n near = near || 0.05\r\n far = far || 1000\r\n\r\n const cx = bsphere.center[0]\r\n const cy = bsphere.center[1]\r\n const cz = bsphere.center[2]\r\n const radius = bsphere.radius\r\n\r\n // Calculate distance to sphere center\r\n const dx = cx - cameraPos[0]\r\n const dy = cy - cameraPos[1]\r\n const dz = cz - cameraPos[2]\r\n const distance = Math.sqrt(dx * dx + dy * dy + dz * dz)\r\n\r\n // Skip if sphere intersects near plane\r\n if (distance - radius < near) {\r\n return false\r\n }\r\n\r\n // Project sphere CENTER only (more stable than projecting 8 corners)\r\n const clipX = viewProj[0] * cx + viewProj[4] * cy + viewProj[8] * cz + viewProj[12]\r\n const clipY = viewProj[1] * cx + viewProj[5] * cy + viewProj[9] * cz + viewProj[13]\r\n const clipW = viewProj[3] * cx + viewProj[7] * cy + viewProj[11] * cz + viewProj[15]\r\n\r\n // Behind camera\r\n if (clipW <= near) {\r\n return false\r\n }\r\n\r\n // Perspective divide to NDC\r\n const ndcX = clipX / clipW\r\n const ndcY = clipY / clipW\r\n\r\n // Skip if center is way off screen (frustum culling handles this)\r\n if (ndcX < -1.5 || ndcX > 1.5 || ndcY < -1.5 || ndcY > 1.5) {\r\n return false\r\n }\r\n\r\n // Convert center to screen coordinates\r\n const screenCenterX = (ndcX * 0.5 + 0.5) * this.screenWidth\r\n const screenCenterY = (1.0 - (ndcY * 0.5 + 0.5)) * this.screenHeight // Flip Y\r\n\r\n // Calculate screen-space radius using clip W for proper perspective\r\n const ndcRadius = radius / clipW\r\n const screenRadius = ndcRadius * (this.screenHeight * 0.5)\r\n\r\n // Calculate screen bounds from center and radius\r\n const minScreenX = screenCenterX - screenRadius\r\n const maxScreenX = screenCenterX + screenRadius\r\n const minScreenY = screenCenterY - screenRadius\r\n const maxScreenY = screenCenterY + screenRadius\r\n\r\n // Calculate tile range from screen bounds\r\n const rawMinTileX = Math.floor(minScreenX / TILE_SIZE)\r\n const rawMaxTileX = Math.floor(maxScreenX / TILE_SIZE)\r\n const rawMinTileY = Math.floor(minScreenY / TILE_SIZE)\r\n const rawMaxTileY = Math.floor(maxScreenY / TILE_SIZE)\r\n\r\n // If bounding box extends outside screen, object is partially visible\r\n if (rawMinTileX < 0 || rawMaxTileX >= this.tileCountX ||\r\n rawMinTileY < 0 || rawMaxTileY >= this.tileCountY) {\r\n return false // Partially off-screen - assume visible\r\n }\r\n\r\n const minTileX = rawMinTileX\r\n const maxTileX = rawMaxTileX\r\n const minTileY = rawMinTileY\r\n const maxTileY = rawMaxTileY\r\n\r\n // If too many tiles, skip (let frustum culling handle large objects)\r\n const tileSpanX = maxTileX - minTileX + 1\r\n const tileSpanY = maxTileY - minTileY + 1\r\n if (tileSpanX * tileSpanY > 25) {\r\n this.debugStats.skippedTileSpan++\r\n return false // Too many tiles to check\r\n }\r\n\r\n // Calculate LINEAR depth for front of sphere\r\n const depthRange = far - near\r\n const frontDistance = Math.max(near, distance - radius)\r\n const sphereFrontDepth = (frontDistance - near) / depthRange\r\n\r\n // Convert 1 meter to linear depth units (minimum safety margin)\r\n const minMarginLinear = 1.0 / depthRange\r\n\r\n // Get configurable threshold multiplier (default 1.0 = 100% of maxZ)\r\n const cullingThreshold = this.settings?.occlusionCulling?.threshold ?? 1.0\r\n\r\n // Check all tiles - if ANY tile shows object might be visible, exit early\r\n for (let ty = minTileY; ty <= maxTileY; ty++) {\r\n for (let tx = minTileX; tx <= maxTileX; tx++) {\r\n const { minDepth, maxDepth } = this.getTileMinMaxDepth(tx, ty)\r\n\r\n // Sky gap - no occlusion possible\r\n if (maxDepth >= 0.999) {\r\n this.debugStats.visibleSkyGap++\r\n return false\r\n }\r\n\r\n // Tile thickness in linear depth\r\n const tileThickness = maxDepth - minDepth\r\n\r\n // Adaptive occlusion threshold = max(1m, tileThickness, maxZ * cullingThreshold)\r\n // - 1m: minimum safety margin\r\n // - tileThickness: thick tiles (angled ground) need more margin\r\n // - maxZ * threshold: configurable depth-based margin to prevent self-occlusion\r\n const depthBasedMargin = maxDepth * cullingThreshold\r\n const threshold = Math.max(minMarginLinear, tileThickness, depthBasedMargin)\r\n\r\n // Object is visible if its front is within threshold of tile's farthest surface\r\n // Only occlude if sphere front is beyond maxDepth + threshold\r\n if (sphereFrontDepth <= maxDepth + threshold) {\r\n this.debugStats.visibleInFront++\r\n return false // Visible - exit early\r\n }\r\n }\r\n }\r\n\r\n // Sphere is behind all tiles by more than the threshold → occluded\r\n this.debugStats.occluded++\r\n return true\r\n }\r\n\r\n /**\r\n * Reset debug stats (call at start of each frame)\r\n */\r\n resetDebugStats() {\r\n this.debugStats.tested = 0\r\n this.debugStats.occluded = 0\r\n this.debugStats.skippedTileSpan = 0\r\n this.debugStats.visibleSkyGap = 0\r\n this.debugStats.visibleInFront = 0\r\n }\r\n\r\n /**\r\n * Get debug stats for occlusion testing\r\n */\r\n getDebugStats() {\r\n return this.debugStats\r\n }\r\n\r\n /**\r\n * Get tile information for debugging\r\n */\r\n getTileInfo() {\r\n const hizData = this.hizDataBuffers[this.readHizIndex]\r\n // Calculate stats about tile coverage\r\n let globalMinDepth = 1.0, globalMaxDepth = 0.0, coveredTiles = 0\r\n let avgThickness = 0\r\n if (hizData && this.hizDataReady) {\r\n for (let i = 0; i < this.totalTiles; i++) {\r\n const minD = hizData[i * 2]\r\n const maxD = hizData[i * 2 + 1]\r\n globalMinDepth = Math.min(globalMinDepth, minD)\r\n globalMaxDepth = Math.max(globalMaxDepth, maxD)\r\n if (maxD < 0.999) {\r\n coveredTiles++ // Tile has geometry (not just sky)\r\n avgThickness += maxD - minD\r\n }\r\n }\r\n if (coveredTiles > 0) {\r\n avgThickness /= coveredTiles\r\n }\r\n }\r\n\r\n return {\r\n tileCountX: this.tileCountX,\r\n tileCountY: this.tileCountY,\r\n tileSize: TILE_SIZE,\r\n totalTiles: this.totalTiles,\r\n hasValidData: this.hizDataReady && this.hasValidHistory,\r\n hizDataReady: this.hizDataReady,\r\n hasValidHistory: this.hasValidHistory,\r\n readbackLatency: this._frameCounter - this._lastReadbackFrame,\r\n coveredTiles,\r\n globalMinDepth: globalMinDepth.toFixed(4),\r\n globalMaxDepth: globalMaxDepth.toFixed(4),\r\n avgTileThickness: avgThickness.toFixed(4),\r\n }\r\n }\r\n\r\n /**\r\n * Get HiZ data for debugging visualization\r\n */\r\n getHiZData() {\r\n return this.hizDataBuffers[this.readHizIndex]\r\n }\r\n\r\n async _resize(width, height) {\r\n await this._createResources(width, height)\r\n }\r\n\r\n _destroy() {\r\n this._destroyed = true\r\n\r\n if (this.hizBuffer) {\r\n this.hizBuffer.destroy()\r\n this.hizBuffer = null\r\n }\r\n for (let i = 0; i < 2; i++) {\r\n if (this.stagingBuffers[i]) {\r\n this.stagingBuffers[i].destroy()\r\n this.stagingBuffers[i] = null\r\n }\r\n }\r\n if (this.uniformBuffer) {\r\n this.uniformBuffer.destroy()\r\n this.uniformBuffer = null\r\n }\r\n this.pipeline = null\r\n this.hizDataBuffers = [null, null]\r\n this.hizDataReady = false\r\n this.hasValidHistory = false\r\n this.pendingReadback = null\r\n }\r\n}\r\n\r\nexport { HiZPass, TILE_SIZE }\r\n","const PI = 3.14159265359;\n\nstruct VertexOutput {\n @builtin(position) position: vec4<f32>,\n @location(0) uv: vec2<f32>,\n}\n\nstruct Uniforms {\n inverseProjection: mat4x4<f32>,\n projection: mat4x4<f32>,\n view: mat4x4<f32>,\n aoSize: vec2f, // AO render target size (may be scaled)\n gbufferSize: vec2f, // GBuffer full resolution size\n // AO params: x = intensity, y = radius, z = fadeDistance, w = bias\n aoParams: vec4f,\n // Noise params: x = size, y = offsetX, z = offsetY, w = time\n noiseParams: vec4f,\n // Camera params: x = near, y = far\n cameraParams: vec2f,\n}\n\n@group(0) @binding(0) var<uniform> uniforms: Uniforms;\n@group(0) @binding(1) var gDepth: texture_depth_2d;\n@group(0) @binding(2) var gNormal: texture_2d<f32>;\n@group(0) @binding(3) var gArm: texture_2d<f32>;\n@group(0) @binding(4) var noiseTexture: texture_2d<f32>;\n\n// Poisson disk offsets for 16 samples - well distributed pattern\nconst OFFSETS_16 = array<vec2f, 16>(\n vec2f(0.063, 0.000),\n vec2f(0.079, 0.097),\n vec2f(-0.039, 0.183),\n vec2f(-0.223, 0.113),\n vec2f(-0.285, -0.127),\n vec2f(-0.097, -0.362),\n vec2f(0.257, -0.354),\n vec2f(0.499, -0.026),\n vec2f(0.376, 0.418),\n vec2f(-0.098, 0.617),\n vec2f(-0.595, 0.344),\n vec2f(-0.700, -0.269),\n vec2f(-0.251, -0.773),\n vec2f(0.477, -0.734),\n vec2f(0.932, -0.098),\n vec2f(0.707, 0.707)\n);\n\n// Convert linear depth buffer value back to view-space distance\n// Inverse of: depth = (z - near) / (far - near)\n// Result: z = near + depth * (far - near)\nfn depthToLinear(depth: f32) -> f32 {\n let near = uniforms.cameraParams.x;\n let far = uniforms.cameraParams.y;\n return near + depth * (far - near);\n}\n\n// Get linear depth at pixel coordinate (converts from depth buffer)\nfn getDepth(coord: vec2i) -> f32 {\n let bufferDepth = textureLoad(gDepth, coord, 0);\n return depthToLinear(bufferDepth);\n}\n\n// Sample noise at screen position\nfn sampleNoise(screenPos: vec2f) -> f32 {\n let noiseSize = i32(uniforms.noiseParams.x);\n let noiseOffsetX = i32(uniforms.noiseParams.y * f32(noiseSize));\n let noiseOffsetY = i32(uniforms.noiseParams.z * f32(noiseSize));\n\n let texCoord = vec2i(\n (i32(screenPos.x) + noiseOffsetX) % noiseSize,\n (i32(screenPos.y) + noiseOffsetY) % noiseSize\n );\n return textureLoad(noiseTexture, texCoord, 0).r;\n}\n\n// Rotate a 2D vector by angle\nfn rotate2D(v: vec2f, angle: f32) -> vec2f {\n let s = sin(angle);\n let c = cos(angle);\n return vec2f(v.x * c - v.y * s, v.x * s + v.y * c);\n}\n\n@vertex\nfn vertexMain(@builtin(vertex_index) vertexIndex: u32) -> VertexOutput {\n var output: VertexOutput;\n let x = f32(vertexIndex & 1u) * 4.0 - 1.0;\n let y = f32(vertexIndex >> 1u) * 4.0 - 1.0;\n output.position = vec4f(x, y, 0.0, 1.0);\n output.uv = vec2f((x + 1.0) * 0.5, (1.0 - y) * 0.5);\n return output;\n}\n\n@fragment\nfn fragmentMain(input: VertexOutput) -> @location(0) f32 {\n // Use UV to sample GBuffer at correct position regardless of AO resolution\n let gbufferCoord = vec2i(floor(input.uv * uniforms.gbufferSize));\n let aoCoord = vec2i(floor(input.uv * uniforms.aoSize));\n let screenPos = input.position.xy;\n\n // Get linear depth in meters (sample from full-res GBuffer)\n let depth = getDepth(gbufferCoord);\n let normal = normalize(textureLoad(gNormal, gbufferCoord, 0).xyz);\n\n // Parameters\n let aoIntensity = uniforms.aoParams.x;\n let aoRadius = uniforms.aoParams.y;\n let aoFadeDistance = uniforms.aoParams.z;\n let aoBias = uniforms.aoParams.w;\n let far = uniforms.cameraParams.y;\n\n // Early out for sky\n if (depth > far * 0.95) {\n return 1.0;\n }\n\n // Distance fade\n let distanceFade = 1.0 - smoothstep(aoFadeDistance * 0.5, aoFadeDistance, depth);\n if (distanceFade < 0.01) {\n return 1.0;\n }\n\n // Blue noise jitter\n let noise = sampleNoise(screenPos);\n let rotationAngle = noise * PI * 2.0;\n\n // Sample radius in pixels - scale with depth so AO is consistent across distances\n let sampleRadius = aoRadius / max(depth * 0.2, 1.0);\n\n var occlusion = 0.0;\n var validSamples = 0.0;\n\n // Occlusion threshold in meters - scales with depth\n let occlusionThreshold = 0.1 + depth * 0.02; // 10cm + 2% of depth\n let maxOcclusionDist = occlusionThreshold * 5.0;\n\n // Scale factor from AO space to GBuffer space\n let scaleToGBuffer = uniforms.gbufferSize / uniforms.aoSize;\n\n for (var i = 0; i < 16; i++) {\n let offset = rotate2D(OFFSETS_16[i], rotationAngle) * sampleRadius;\n // Scale offset from AO-space to GBuffer-space pixels\n let scaledOffset = offset * scaleToGBuffer;\n let sampleCoord = gbufferCoord + vec2i(i32(scaledOffset.x), i32(scaledOffset.y));\n\n // Bounds check against GBuffer size\n if (sampleCoord.x < 0 || sampleCoord.x >= i32(uniforms.gbufferSize.x) ||\n sampleCoord.y < 0 || sampleCoord.y >= i32(uniforms.gbufferSize.y)) {\n continue;\n }\n\n let sampleDepth = getDepth(sampleCoord);\n\n // Simple depth-based AO like the reference shader\n // ddiff > 0 means sample is closer to camera (we're behind it = occluded)\n let ddiff = depth - sampleDepth;\n\n // If sample is closer (ddiff > 0), we're occluded\n // If sample is further or same (ddiff <= 0), we're not occluded\n let unoccluded = select(1.0, 0.0, ddiff > aoBias);\n\n // Ignore samples that are much closer (edge/discontinuity)\n let relevant = 1.0 - smoothstep(occlusionThreshold, maxOcclusionDist, ddiff);\n\n occlusion += unoccluded * relevant;\n validSamples += relevant;\n }\n\n // Final AO calculation\n // occlusion = sum of unoccluded samples, validSamples = sum of relevant weights\n // Higher unoccluded ratio = less darkening\n var ao = 1.0;\n if (validSamples > 0.5) {\n let unoccludedRatio = occlusion / (validSamples * 0.5); // Like reference shader\n let aoFactor = 1.0 - clamp(unoccludedRatio, 0.0, 1.0);\n ao = 1.0 - aoFactor * aoIntensity * 0.5;\n }\n\n // Apply distance fade\n ao = mix(1.0, ao, distanceFade);\n\n return ao;\n}\n","import { BasePass } from \"./BasePass.js\"\nimport { Texture } from \"../../Texture.js\"\n\nimport aoWGSL from \"../shaders/ao.wgsl\"\n\n/**\n * AOPass - Screen Space Ambient Occlusion\n *\n * Pass 5 in the 7-pass pipeline.\n * Calculates ambient occlusion from depth and normal buffers.\n *\n * Features:\n * - Cavity/corner darkening (traditional SSAO)\n * - Normal-based darkening (plasticity for objects in shadow)\n * - Blue noise jittered sampling\n * - Reflectivity-based fade (reflective surfaces have no AO)\n * - Distance fade (fades to 0 at configurable distance)\n *\n * Inputs: GBuffer (depth, normal, ARM)\n * Output: AO texture (r8unorm - single channel)\n */\nclass AOPass extends BasePass {\n constructor(engine = null) {\n super('AO', engine)\n\n this.renderPipeline = null\n this.outputTexture = null\n this.gbuffer = null\n this.noiseTexture = null\n this.noiseSize = 128\n this.noiseAnimated = true\n\n // Render dimensions (may differ from canvas when effect scaling is applied)\n this.width = 0\n this.height = 0\n }\n\n // Convenience getters for AO settings (with defaults for backward compatibility)\n get aoIntensity() { return this.settings?.ao?.intensity ?? 1.0 }\n get aoRadius() {\n // Scale radius by renderScale and height relative to 1080p\n // Settings are authored for 1080p, so scale proportionally\n const baseRadius = this.settings?.ao?.radius ?? 64.0\n const renderScale = this.settings?.rendering?.renderScale ?? 1.0\n const heightScale = this.height > 0 ? this.height / 1080 : 1.0\n return baseRadius * renderScale * heightScale\n }\n get aoFadeDistance() { return this.settings?.ao?.fadeDistance ?? 40.0 }\n get aoBias() { return this.settings?.ao?.bias ?? 0.005 }\n get sampleCount() { return this.settings?.ao?.sampleCount ?? 16 }\n get aoLevel() { return this.settings?.ao?.level ?? 0.5 }\n\n /**\n * Set the GBuffer from GBufferPass\n * @param {GBuffer} gbuffer - GBuffer textures\n */\n async setGBuffer(gbuffer) {\n this.gbuffer = gbuffer\n this._needsRebuild = true\n }\n\n /**\n * Set the noise texture for jittering\n * @param {Texture} noise - Noise texture (blue noise or bayer dither)\n * @param {number} size - Texture size\n * @param {boolean} animated - Whether to animate noise offset each frame\n */\n setNoise(noise, size = 64, animated = true) {\n this.noiseTexture = noise\n this.noiseSize = size\n this.noiseAnimated = animated\n this._needsRebuild = true\n }\n\n async _init() {\n // Create output texture (single channel AO)\n this.outputTexture = await Texture.renderTarget(this.engine, 'r8unorm')\n }\n\n async _buildPipeline() {\n if (!this.gbuffer) {\n return\n }\n\n const { device } = this.engine\n\n // Create bind group layout\n const bglEntries = [\n // Uniforms\n { binding: 0, visibility: GPUShaderStage.FRAGMENT, buffer: { type: 'uniform' } },\n // Depth texture\n { binding: 1, visibility: GPUShaderStage.FRAGMENT, texture: { sampleType: 'depth' } },\n // Normal texture\n { binding: 2, visibility: GPUShaderStage.FRAGMENT, texture: { sampleType: 'unfilterable-float' } },\n // ARM texture (for reflectivity)\n { binding: 3, visibility: GPUShaderStage.FRAGMENT, texture: { sampleType: 'unfilterable-float' } },\n ]\n\n // Add blue noise if available\n if (this.noiseTexture) {\n bglEntries.push(\n { binding: 4, visibility: GPUShaderStage.FRAGMENT, texture: { sampleType: 'float' } },\n )\n }\n\n const bindGroupLayout = device.createBindGroupLayout({\n label: 'AO Bind Group Layout',\n entries: bglEntries,\n })\n\n // Create uniform buffer (256 bytes minimum required by WebGPU)\n // inverseProjection(64) + projection(64) + view(64) + canvasSize(8) + aoParams(16) + noiseParams(16) + cameraParams(8) + padding(16) = 256 bytes\n this.uniformBuffer = device.createBuffer({\n label: 'AO Uniforms',\n size: 256,\n usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST,\n })\n\n // Create shader module\n const shaderModule = device.createShaderModule({\n label: 'AO Shader',\n code: aoWGSL,\n })\n\n // Check for compilation errors\n const compilationInfo = await shaderModule.getCompilationInfo()\n for (const message of compilationInfo.messages) {\n if (message.type === 'error') {\n console.error('AO Shader Error:', message.message)\n return\n }\n }\n\n // Create pipeline\n const pipelineLayout = device.createPipelineLayout({\n label: 'AO Pipeline Layout',\n bindGroupLayouts: [bindGroupLayout],\n })\n\n // Use async pipeline creation for non-blocking initialization\n this.renderPipeline = await device.createRenderPipelineAsync({\n label: 'AO Pipeline',\n layout: pipelineLayout,\n vertex: {\n module: shaderModule,\n entryPoint: 'vertexMain',\n },\n fragment: {\n module: shaderModule,\n entryPoint: 'fragmentMain',\n targets: [{ format: 'r8unorm' }],\n },\n primitive: {\n topology: 'triangle-list',\n },\n })\n\n // Create bind group\n const entries = [\n { binding: 0, resource: { buffer: this.uniformBuffer } },\n { binding: 1, resource: this.gbuffer.depth.view },\n { binding: 2, resource: this.gbuffer.normal.view },\n { binding: 3, resource: this.gbuffer.arm.view },\n ]\n\n if (this.noiseTexture) {\n entries.push({ binding: 4, resource: this.noiseTexture.view })\n }\n\n this.bindGroup = device.createBindGroup({\n label: 'AO Bind Group',\n layout: bindGroupLayout,\n entries: entries,\n })\n\n this.bindGroupLayout = bindGroupLayout\n this._needsRebuild = false\n }\n\n async _execute(context) {\n const { device, canvas } = this.engine\n const { camera } = context\n\n // Check if AO is enabled - if not, clear to white (no occlusion)\n if (!this.settings?.ao?.enabled) {\n if (this.outputTexture) {\n const commandEncoder = device.createCommandEncoder({ label: 'AOClear' })\n const passEncoder = commandEncoder.beginRenderPass({\n colorAttachments: [{\n view: this.outputTexture.view,\n clearValue: { r: 1, g: 1, b: 1, a: 1 }, // White = no occlusion\n loadOp: 'clear',\n storeOp: 'store',\n }],\n })\n passEncoder.end()\n device.queue.submit([commandEncoder.finish()])\n }\n return\n }\n\n // Rebuild pipeline if needed\n if (this._needsRebuild) {\n await this._buildPipeline()\n }\n\n // If rebuild was attempted but failed, don't use stale pipeline with old bind groups\n if (!this.renderPipeline || !this.gbuffer || this._needsRebuild) {\n return\n }\n\n // Update uniforms\n const uniformData = new Float32Array(64) // 256 bytes / 4\n\n // Inverse projection matrix (for reconstructing view-space position)\n if (camera.iProj) {\n uniformData.set(camera.iProj, 0)\n }\n\n // Projection matrix\n uniformData.set(camera.proj, 16)\n\n // View matrix\n uniformData.set(camera.view, 32)\n\n // AO size (vec2f at offset 192 = float index 48)\n // Use stored dimensions (may differ from canvas when effect scaling is applied)\n uniformData[48] = this.width || canvas.width\n uniformData[49] = this.height || canvas.height\n\n // GBuffer size (vec2f at offset 200 = float index 50)\n // Always full resolution canvas size for GBuffer sampling\n uniformData[50] = canvas.width\n uniformData[51] = canvas.height\n\n // AO parameters: intensity, radius, fadeDistance, bias (vec4f at offset 208 = float index 52)\n uniformData[52] = this.aoIntensity\n uniformData[53] = this.aoRadius\n uniformData[54] = this.aoFadeDistance\n uniformData[55] = this.aoBias\n\n // Noise parameters: size, offsetX, offsetY, frame (vec4f at offset 224 = float index 56)\n uniformData[56] = this.noiseSize\n uniformData[57] = this.noiseAnimated ? (Math.random() * 0.1) : 0 // Animated offset X\n uniformData[58] = this.noiseAnimated ? (Math.random() * 0.1) : 0 // Animated offset Y\n uniformData[59] = performance.now() / 1000 // Time for animation\n\n // Camera near/far (vec2f at offset 240 = float index 60)\n uniformData[60] = camera.near || 0.1\n uniformData[61] = camera.far || 1000\n\n device.queue.writeBuffer(this.uniformBuffer, 0, uniformData)\n\n // Render AO pass\n const commandEncoder = device.createCommandEncoder({ label: 'AO Pass' })\n\n const renderPass = commandEncoder.beginRenderPass({\n label: 'AO Render Pass',\n colorAttachments: [{\n view: this.outputTexture.view,\n clearValue: { r: 1, g: 1, b: 1, a: 1 }, // White = no occlusion\n loadOp: 'clear',\n storeOp: 'store',\n }],\n })\n\n renderPass.setPipeline(this.renderPipeline)\n renderPass.setBindGroup(0, this.bindGroup)\n renderPass.draw(3) // Full-screen triangle\n renderPass.end()\n\n device.queue.submit([commandEncoder.finish()])\n }\n\n async _resize(width, height) {\n // Store dimensions for height-based scaling\n this.width = width\n this.height = height\n\n // Recreate output texture at new size\n this.outputTexture = await Texture.renderTarget(this.engine, 'r8unorm')\n this._needsRebuild = true\n }\n\n _destroy() {\n this.renderPipeline = null\n this.outputTexture = null\n }\n\n /**\n * Get the output AO texture\n */\n getOutputTexture() {\n return this.outputTexture\n }\n\n /**\n * Configure AO parameters\n */\n configure(options) {\n if (options.intensity !== undefined) this.aoIntensity = options.intensity\n if (options.radius !== undefined) this.aoRadius = options.radius\n if (options.fadeDistance !== undefined) this.aoFadeDistance = options.fadeDistance\n if (options.bias !== undefined) this.aoBias = options.bias\n }\n}\n\nexport { AOPass }\n","// SSGI Tile Accumulation Compute Shader\n//\n// For each tile, accumulates the average light from:\n// - Previous frame HDR (lit surfaces)\n// - Emissive texture (boosted)\n//\n// Output: Single vec4f per tile (RGB = average color, A = count)\n\nstruct TileUniforms {\n screenWidth: f32,\n screenHeight: f32,\n tileCountX: f32,\n tileCountY: f32,\n tileSize: f32,\n emissiveBoost: f32,\n maxBrightness: f32,\n _pad0: f32,\n}\n\n@group(0) @binding(0) var<uniform> uniforms: TileUniforms;\n@group(0) @binding(1) var prevHDR: texture_2d<f32>;\n@group(0) @binding(2) var emissiveTexture: texture_2d<f32>;\n@group(0) @binding(3) var linearSampler: sampler;\n@group(0) @binding(4) var<storage, read_write> tileAccumBuffer: array<vec4f>;\n\nconst WORKGROUP_SIZE: u32 = 8u;\nconst PIXELS_PER_THREAD: u32 = 8u;\nconst TILE_SIZE: u32 = 64u;\n\n// Shared memory for parallel reduction (64 threads × 4 floats)\nvar<workgroup> sharedAccum: array<vec4f, 64>;\n\n@compute @workgroup_size(8, 8, 1)\nfn main(\n @builtin(local_invocation_id) localId: vec3u,\n @builtin(workgroup_id) workgroupId: vec3u,\n) {\n let tileX = workgroupId.x;\n let tileY = workgroupId.y;\n let threadIdx = localId.y * WORKGROUP_SIZE + localId.x;\n\n // Base pixel position for this tile\n let tileBaseX = tileX * TILE_SIZE;\n let tileBaseY = tileY * TILE_SIZE;\n\n // This thread processes an 8×8 block of pixels\n let blockBaseX = tileBaseX + localId.x * PIXELS_PER_THREAD;\n let blockBaseY = tileBaseY + localId.y * PIXELS_PER_THREAD;\n\n // Local accumulator\n var localAccum = vec4f(0.0);\n\n // Process 8×8 pixels\n for (var py = 0u; py < PIXELS_PER_THREAD; py++) {\n for (var px = 0u; px < PIXELS_PER_THREAD; px++) {\n let pixelX = blockBaseX + px;\n let pixelY = blockBaseY + py;\n\n // Skip pixels outside screen bounds\n if (pixelX >= u32(uniforms.screenWidth) || pixelY >= u32(uniforms.screenHeight)) {\n continue;\n }\n\n let pixelCoord = vec2i(i32(pixelX), i32(pixelY));\n\n // Sample previous frame HDR\n let hdrColor = textureLoad(prevHDR, pixelCoord, 0).rgb;\n\n // Sample emissive and boost\n let emissive = textureLoad(emissiveTexture, pixelCoord, 0).rgb;\n let boostedEmissive = emissive * uniforms.emissiveBoost;\n\n // Combine HDR + boosted emissive\n var totalLight = hdrColor + boostedEmissive;\n\n // Clamp brightness to maxBrightness (excludes specular highlights)\n // Use max RGB instead of luminance to treat all colors equally\n let bright = max(max(totalLight.r, totalLight.g), totalLight.b);\n if (bright > uniforms.maxBrightness) {\n totalLight *= uniforms.maxBrightness / bright;\n }\n\n // Accumulate\n localAccum += vec4f(totalLight, 1.0);\n }\n }\n\n // Store to shared memory\n sharedAccum[threadIdx] = localAccum;\n\n workgroupBarrier();\n\n // Parallel reduction - 64 -> 32 -> 16 -> 8 -> 4 -> 2 -> 1\n for (var stride = 32u; stride > 0u; stride >>= 1u) {\n if (threadIdx < stride) {\n sharedAccum[threadIdx] += sharedAccum[threadIdx + stride];\n }\n workgroupBarrier();\n }\n\n // Thread 0 writes final averaged result to tile buffer\n if (threadIdx == 0u) {\n let tileIndex = tileY * u32(uniforms.tileCountX) + tileX;\n let accum = sharedAccum[0];\n\n // Average the accumulated light\n var avgColor = vec4f(0.0);\n if (accum.w > 0.0) {\n avgColor = vec4f(accum.rgb / accum.w, accum.w);\n }\n\n tileAccumBuffer[tileIndex] = avgColor;\n }\n}\n","// SSGI Light Propagation Compute Shader\n//\n// For each tile, collects indirect lighting from all other tiles in 4 directions.\n// Attenuation: tiles more than half screen away contribute 0.\n//\n// Output: 4 vec4f per tile (left, right, up, down directions)\n// Each direction stores: RGB = accumulated light from that direction, A = weight\n\nstruct TileUniforms {\n screenWidth: f32,\n screenHeight: f32,\n tileCountX: f32,\n tileCountY: f32,\n tileSize: f32,\n emissiveBoost: f32,\n _pad0: f32,\n _pad1: f32,\n}\n\n@group(0) @binding(0) var<uniform> uniforms: TileUniforms;\n@group(0) @binding(1) var<storage, read> tileAccumBuffer: array<vec4f>;\n@group(0) @binding(2) var<storage, read_write> tilePropagateBuffer: array<vec4f>;\n\n// Direction indices\nconst DIR_LEFT: u32 = 0u;\nconst DIR_RIGHT: u32 = 1u;\nconst DIR_UP: u32 = 2u;\nconst DIR_DOWN: u32 = 3u;\n\n@compute @workgroup_size(1, 1, 1)\nfn main(@builtin(global_invocation_id) globalId: vec3u) {\n let tileX = globalId.x;\n let tileY = globalId.y;\n\n let tileCountX = u32(uniforms.tileCountX);\n let tileCountY = u32(uniforms.tileCountY);\n\n // Skip if out of bounds\n if (tileX >= tileCountX || tileY >= tileCountY) {\n return;\n }\n\n let thisTileIdx = tileY * tileCountX + tileX;\n\n // Half screen distance in tiles (for attenuation cutoff)\n // Use max of X/Y so vertical propagation has same range as horizontal\n let halfScreenTiles = max(f32(tileCountX), f32(tileCountY)) * 0.5;\n\n // Accumulators for each direction\n var accumLeft = vec4f(0.0);\n var accumRight = vec4f(0.0);\n var accumUp = vec4f(0.0);\n var accumDown = vec4f(0.0);\n\n // === Collect light from LEFT (all tiles with smaller X) ===\n // Light traveling RIGHT toward surfaces facing left\n // No self-lighting (dist=0), neighbor (dist=1) contributes 1.0, linear falloff to 0 at half screen\n for (var x = 0u; x < tileX; x++) {\n let dist = f32(tileX - x);\n var weight = 0.0;\n if (dist >= 1.0 && dist < halfScreenTiles) {\n weight = 1.0 - (dist - 1.0) / (halfScreenTiles - 1.0);\n weight = max(weight, 0.0);\n }\n if (weight > 0.0) {\n let srcIdx = tileY * tileCountX + x;\n let srcLight = tileAccumBuffer[srcIdx];\n accumLeft += vec4f(srcLight.rgb * weight, weight);\n }\n }\n\n // === Collect light from RIGHT (all tiles with larger X) ===\n // Light traveling LEFT toward surfaces facing right\n for (var x = tileX + 1u; x < tileCountX; x++) {\n let dist = f32(x - tileX);\n var weight = 0.0;\n if (dist >= 1.0 && dist < halfScreenTiles) {\n weight = 1.0 - (dist - 1.0) / (halfScreenTiles - 1.0);\n weight = max(weight, 0.0);\n }\n if (weight > 0.0) {\n let srcIdx = tileY * tileCountX + x;\n let srcLight = tileAccumBuffer[srcIdx];\n accumRight += vec4f(srcLight.rgb * weight, weight);\n }\n }\n\n // === Collect light from UP (all tiles with smaller Y) ===\n // Light traveling DOWN toward surfaces facing up\n for (var y = 0u; y < tileY; y++) {\n let dist = f32(tileY - y);\n var weight = 0.0;\n if (dist >= 1.0 && dist < halfScreenTiles) {\n weight = 1.0 - (dist - 1.0) / (halfScreenTiles - 1.0);\n weight = max(weight, 0.0);\n }\n if (weight > 0.0) {\n let srcIdx = y * tileCountX + tileX;\n let srcLight = tileAccumBuffer[srcIdx];\n accumUp += vec4f(srcLight.rgb * weight, weight);\n }\n }\n\n // === Collect light from DOWN (all tiles with larger Y) ===\n // Light traveling UP toward surfaces facing down\n for (var y = tileY + 1u; y < tileCountY; y++) {\n let dist = f32(y - tileY);\n var weight = 0.0;\n if (dist >= 1.0 && dist < halfScreenTiles) {\n weight = 1.0 - (dist - 1.0) / (halfScreenTiles - 1.0);\n weight = max(weight, 0.0);\n }\n if (weight > 0.0) {\n let srcIdx = y * tileCountX + tileX;\n let srcLight = tileAccumBuffer[srcIdx];\n accumDown += vec4f(srcLight.rgb * weight, weight);\n }\n }\n\n // Normalize accumulated light by weight\n if (accumLeft.w > 0.0) { accumLeft = vec4f(accumLeft.rgb / accumLeft.w, accumLeft.w); }\n if (accumRight.w > 0.0) { accumRight = vec4f(accumRight.rgb / accumRight.w, accumRight.w); }\n if (accumUp.w > 0.0) { accumUp = vec4f(accumUp.rgb / accumUp.w, accumUp.w); }\n if (accumDown.w > 0.0) { accumDown = vec4f(accumDown.rgb / accumDown.w, accumDown.w); }\n\n // Write to propagate buffer (4 directions per tile)\n let baseIdx = thisTileIdx * 4u;\n tilePropagateBuffer[baseIdx + DIR_LEFT] = accumLeft;\n tilePropagateBuffer[baseIdx + DIR_RIGHT] = accumRight;\n tilePropagateBuffer[baseIdx + DIR_UP] = accumUp;\n tilePropagateBuffer[baseIdx + DIR_DOWN] = accumDown;\n}\n","import { BasePass } from \"./BasePass.js\"\n\nimport ssgiAccumulateWGSL from \"../shaders/ssgi_accumulate.wgsl\"\nimport ssgiPropagateWGSL from \"../shaders/ssgi_propagate.wgsl\"\n\n/**\n * SSGITilePass - Two-pass tile-based light propagation\n *\n * Pass 1 (Accumulate): For each tile, average the light content from prev HDR + boosted emissive\n * Pass 2 (Propagate): For each tile, collect indirect light from all other tiles in 4 directions\n *\n * Input: Previous frame HDR, GBuffer emissive\n * Output: Propagated directional light buffer (4 directions per tile)\n */\n\nconst TILE_SIZE = 64\n\nclass SSGITilePass extends BasePass {\n constructor(engine = null) {\n super('SSGITile', engine)\n\n // Compute pipelines\n this.accumulatePipeline = null\n this.propagatePipeline = null\n\n // Bind group layouts\n this.accumulateBGL = null\n this.propagateBGL = null\n\n // Buffers\n this.tileAccumBuffer = null // Accumulated light per tile (vec4f per tile)\n this.tilePropagateBuffer = null // Propagated directional light (4 directions per tile)\n\n // Tile grid dimensions\n this.tileCountX = 0\n this.tileCountY = 0\n\n // Stored render dimensions (from resize)\n this.renderWidth = 0\n this.renderHeight = 0\n\n // Input textures\n this.prevHDRTexture = null\n this.emissiveTexture = null\n\n // Uniform buffer\n this.uniformBuffer = null\n\n // Sampler for HDR texture\n this.sampler = null\n }\n\n /**\n * Set the previous frame HDR texture\n */\n setPrevHDRTexture(texture) {\n this.prevHDRTexture = texture\n }\n\n /**\n * Set the emissive texture from GBuffer\n */\n setEmissiveTexture(texture) {\n this.emissiveTexture = texture\n }\n\n async _init() {\n const { device, canvas } = this.engine\n\n this.sampler = device.createSampler({\n label: 'SSGI Tile Sampler',\n minFilter: 'linear',\n magFilter: 'linear',\n })\n\n await this._createResources(canvas.width, canvas.height)\n }\n\n async _createResources(width, height) {\n const { device } = this.engine\n\n // Store render dimensions for use in _execute\n this.renderWidth = width\n this.renderHeight = height\n\n // Calculate tile grid dimensions\n this.tileCountX = Math.ceil(width / TILE_SIZE)\n this.tileCountY = Math.ceil(height / TILE_SIZE)\n const totalTiles = this.tileCountX * this.tileCountY\n\n // Destroy old buffers\n if (this.tileAccumBuffer) this.tileAccumBuffer.destroy()\n if (this.tilePropagateBuffer) this.tilePropagateBuffer.destroy()\n if (this.uniformBuffer) this.uniformBuffer.destroy()\n\n // Create tile accumulation buffer (1 vec4f per tile - RGB + weight)\n this.tileAccumBuffer = device.createBuffer({\n label: 'SSGI Tile Accum Buffer',\n size: totalTiles * 4 * 4, // tiles × 4 floats × 4 bytes\n usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST,\n })\n\n // Create propagated light buffer (4 directions per tile × vec4f)\n // Directions: 0=left, 1=right, 2=up, 3=down\n this.tilePropagateBuffer = device.createBuffer({\n label: 'SSGI Tile Propagate Buffer',\n size: totalTiles * 4 * 4 * 4, // tiles × 4 directions × 4 floats × 4 bytes\n usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST,\n })\n\n // Create uniform buffer\n this.uniformBuffer = device.createBuffer({\n label: 'SSGI Tile Uniforms',\n size: 32, // 8 floats\n usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST,\n })\n\n // Create accumulate pipeline\n const accumulateModule = device.createShaderModule({\n label: 'SSGI Accumulate Shader',\n code: ssgiAccumulateWGSL,\n })\n\n this.accumulateBGL = device.createBindGroupLayout({\n label: 'SSGI Accumulate BGL',\n entries: [\n { binding: 0, visibility: GPUShaderStage.COMPUTE, buffer: { type: 'uniform' } },\n { binding: 1, visibility: GPUShaderStage.COMPUTE, texture: { sampleType: 'float' } },\n { binding: 2, visibility: GPUShaderStage.COMPUTE, texture: { sampleType: 'float' } },\n { binding: 3, visibility: GPUShaderStage.COMPUTE, sampler: { type: 'filtering' } },\n { binding: 4, visibility: GPUShaderStage.COMPUTE, buffer: { type: 'storage' } },\n ],\n })\n\n // Create propagate pipeline shader module\n const propagateModule = device.createShaderModule({\n label: 'SSGI Propagate Shader',\n code: ssgiPropagateWGSL,\n })\n\n this.propagateBGL = device.createBindGroupLayout({\n label: 'SSGI Propagate BGL',\n entries: [\n { binding: 0, visibility: GPUShaderStage.COMPUTE, buffer: { type: 'uniform' } },\n { binding: 1, visibility: GPUShaderStage.COMPUTE, buffer: { type: 'read-only-storage' } },\n { binding: 2, visibility: GPUShaderStage.COMPUTE, buffer: { type: 'storage' } },\n ],\n })\n\n // Create both compute pipelines in parallel for faster initialization\n const [accumulatePipeline, propagatePipeline] = await Promise.all([\n device.createComputePipelineAsync({\n label: 'SSGI Accumulate Pipeline',\n layout: device.createPipelineLayout({ bindGroupLayouts: [this.accumulateBGL] }),\n compute: { module: accumulateModule, entryPoint: 'main' },\n }),\n device.createComputePipelineAsync({\n label: 'SSGI Propagate Pipeline',\n layout: device.createPipelineLayout({ bindGroupLayouts: [this.propagateBGL] }),\n compute: { module: propagateModule, entryPoint: 'main' },\n })\n ])\n\n this.accumulatePipeline = accumulatePipeline\n this.propagatePipeline = propagatePipeline\n\n this._needsRebuild = false\n }\n\n async _execute(context) {\n const { device } = this.engine\n\n // Check if SSGI is enabled\n const ssgiSettings = this.settings?.ssgi\n if (!ssgiSettings?.enabled) {\n return\n }\n\n // Rebuild if needed (use stored render dimensions, not canvas)\n if (this._needsRebuild) {\n await this._createResources(this.renderWidth, this.renderHeight)\n }\n\n // Check required textures\n if (!this.prevHDRTexture || !this.emissiveTexture) {\n return\n }\n\n if (!this.accumulatePipeline || !this.propagatePipeline) {\n return\n }\n\n // Use stored render dimensions, not canvas dimensions\n const width = this.renderWidth\n const height = this.renderHeight\n const emissiveBoost = ssgiSettings.emissiveBoost ?? 2.0\n const maxBrightness = ssgiSettings.maxBrightness ?? 4.0\n\n // Update uniforms\n device.queue.writeBuffer(this.uniformBuffer, 0, new Float32Array([\n width,\n height,\n this.tileCountX,\n this.tileCountY,\n TILE_SIZE,\n emissiveBoost,\n maxBrightness,\n 0, // padding\n ]))\n\n // Clear buffers\n const clearAccum = new Float32Array(this.tileCountX * this.tileCountY * 4)\n device.queue.writeBuffer(this.tileAccumBuffer, 0, clearAccum)\n const clearPropagate = new Float32Array(this.tileCountX * this.tileCountY * 4 * 4)\n device.queue.writeBuffer(this.tilePropagateBuffer, 0, clearPropagate)\n\n // === PASS 1: Accumulate light per tile ===\n const accumulateBindGroup = device.createBindGroup({\n label: 'SSGI Accumulate Bind Group',\n layout: this.accumulateBGL,\n entries: [\n { binding: 0, resource: { buffer: this.uniformBuffer } },\n { binding: 1, resource: this.prevHDRTexture.view },\n { binding: 2, resource: this.emissiveTexture.view },\n { binding: 3, resource: this.sampler },\n { binding: 4, resource: { buffer: this.tileAccumBuffer } },\n ],\n })\n\n const commandEncoder = device.createCommandEncoder({ label: 'SSGI Tile Pass' })\n\n const accumulatePass = commandEncoder.beginComputePass({ label: 'SSGI Accumulate' })\n accumulatePass.setPipeline(this.accumulatePipeline)\n accumulatePass.setBindGroup(0, accumulateBindGroup)\n accumulatePass.dispatchWorkgroups(this.tileCountX, this.tileCountY, 1)\n accumulatePass.end()\n\n // === PASS 2: Propagate light between tiles ===\n const propagateBindGroup = device.createBindGroup({\n label: 'SSGI Propagate Bind Group',\n layout: this.propagateBGL,\n entries: [\n { binding: 0, resource: { buffer: this.uniformBuffer } },\n { binding: 1, resource: { buffer: this.tileAccumBuffer } },\n { binding: 2, resource: { buffer: this.tilePropagateBuffer } },\n ],\n })\n\n const propagatePass = commandEncoder.beginComputePass({ label: 'SSGI Propagate' })\n propagatePass.setPipeline(this.propagatePipeline)\n propagatePass.setBindGroup(0, propagateBindGroup)\n propagatePass.dispatchWorkgroups(this.tileCountX, this.tileCountY, 1)\n propagatePass.end()\n\n device.queue.submit([commandEncoder.finish()])\n }\n\n /**\n * Get the propagated light buffer for SSGIPass to sample from\n */\n getPropagateBuffer() {\n return this.tilePropagateBuffer\n }\n\n /**\n * Get the accumulated light buffer (for debugging)\n */\n getAccumBuffer() {\n return this.tileAccumBuffer\n }\n\n /**\n * Get tile grid dimensions\n */\n getTileInfo() {\n return {\n tileCountX: this.tileCountX,\n tileCountY: this.tileCountY,\n tileSize: TILE_SIZE,\n }\n }\n\n async _resize(width, height) {\n await this._createResources(width, height)\n }\n\n _destroy() {\n if (this.tileAccumBuffer) {\n this.tileAccumBuffer.destroy()\n this.tileAccumBuffer = null\n }\n if (this.tilePropagateBuffer) {\n this.tilePropagateBuffer.destroy()\n this.tilePropagateBuffer = null\n }\n if (this.uniformBuffer) {\n this.uniformBuffer.destroy()\n this.uniformBuffer = null\n }\n this.accumulatePipeline = null\n this.propagatePipeline = null\n }\n}\n\nexport { SSGITilePass, TILE_SIZE }\n","// Screen Space Global Illumination - Vogel Disk Sampling\n//\n// Samples propagated directional light from tiles using 16-sample Vogel disk.\n// Uses screen-space normal to weight directional contributions.\n\nstruct SSGIUniforms {\n // screenParams: fullWidth, fullHeight, halfWidth, halfHeight\n screenParams: vec4f,\n // tileParams: tileCountX, tileCountY, tileSize, sampleRadius (in tiles)\n tileParams: vec4f,\n // ssgiParams: intensity, frameIndex, unused, unused\n ssgiParams: vec4f,\n}\n\n@group(0) @binding(0) var<uniform> uniforms: SSGIUniforms;\n@group(0) @binding(1) var gbufferNormal: texture_2d<f32>;\n@group(0) @binding(2) var<storage, read> propagateBuffer: array<vec4f>;\n\n// Direction indices (must match ssgi_propagate.wgsl)\nconst DIR_LEFT: u32 = 0u;\nconst DIR_RIGHT: u32 = 1u;\nconst DIR_UP: u32 = 2u;\nconst DIR_DOWN: u32 = 3u;\n\nconst PI: f32 = 3.14159265359;\nconst GOLDEN_ANGLE: f32 = 2.39996323; // PI * (3 - sqrt(5))\nconst SAMPLE_COUNT: i32 = 16;\n\nstruct VertexOutput {\n @builtin(position) position: vec4f,\n @location(0) uv: vec2f,\n}\n\n// Hash function for noise\nfn hash21(p: vec2f) -> f32 {\n var p3 = fract(vec3f(p.x, p.y, p.x) * 0.1031);\n p3 += dot(p3, vec3f(p3.y, p3.z, p3.x) + 33.33);\n return fract((p3.x + p3.y) * p3.z);\n}\n\nfn hash22(p: vec2f) -> vec2f {\n let n = sin(dot(p, vec2f(41.0, 289.0)));\n return fract(vec2f(262144.0, 32768.0) * n);\n}\n\n@vertex\nfn vertexMain(@builtin(vertex_index) vertexIndex: u32) -> VertexOutput {\n var positions = array<vec2f, 3>(\n vec2f(-1.0, -1.0),\n vec2f(3.0, -1.0),\n vec2f(-1.0, 3.0)\n );\n var uvs = array<vec2f, 3>(\n vec2f(0.0, 1.0),\n vec2f(2.0, 1.0),\n vec2f(0.0, -1.0)\n );\n\n var output: VertexOutput;\n output.position = vec4f(positions[vertexIndex], 0.0, 1.0);\n output.uv = uvs[vertexIndex];\n return output;\n}\n\n// Get propagated light for a tile and direction (with clamping)\n// Returns vec4f: RGB = light, A = weight (for detecting sky/empty tiles)\nfn getTileLightWithWeight(tileX: i32, tileY: i32, direction: u32) -> vec4f {\n let tileCountX = i32(uniforms.tileParams.x);\n let tileCountY = i32(uniforms.tileParams.y);\n\n // Clamp to valid tile range\n let clampedX = clamp(tileX, 0, tileCountX - 1);\n let clampedY = clamp(tileY, 0, tileCountY - 1);\n\n let tileIdx = u32(clampedY * tileCountX + clampedX);\n let bufferIdx = tileIdx * 4u + direction;\n\n return propagateBuffer[bufferIdx];\n}\n\n// Bilinear sample tile light at fractional position\n// Returns vec4f: RGB = light, A = validity weight (0 = sky, 1+ = has geometry)\nfn sampleTileLightWeighted(tilePos: vec2f, direction: u32) -> vec4f {\n let tileX = i32(floor(tilePos.x));\n let tileY = i32(floor(tilePos.y));\n let fracX = fract(tilePos.x);\n let fracY = fract(tilePos.y);\n\n // Bilinear weights\n let w_tl = (1.0 - fracX) * (1.0 - fracY);\n let w_tr = fracX * (1.0 - fracY);\n let w_bl = (1.0 - fracX) * fracY;\n let w_br = fracX * fracY;\n\n // Sample 4 neighboring tiles (RGBA: light + validity weight)\n let tl = getTileLightWithWeight(tileX, tileY, direction);\n let tr = getTileLightWithWeight(tileX + 1, tileY, direction);\n let bl = getTileLightWithWeight(tileX, tileY + 1, direction);\n let br = getTileLightWithWeight(tileX + 1, tileY + 1, direction);\n\n // Weight-aware bilinear interpolation\n // Use tile weight (.w) to reduce contribution from sky tiles\n let validity_tl = min(tl.w, 1.0);\n let validity_tr = min(tr.w, 1.0);\n let validity_bl = min(bl.w, 1.0);\n let validity_br = min(br.w, 1.0);\n\n // Combined weights (bilinear * validity)\n let cw_tl = w_tl * validity_tl;\n let cw_tr = w_tr * validity_tr;\n let cw_bl = w_bl * validity_bl;\n let cw_br = w_br * validity_br;\n let totalWeight = cw_tl + cw_tr + cw_bl + cw_br;\n\n // Weight-averaged light\n var light = vec3f(0.0);\n if (totalWeight > 0.001) {\n light = (tl.rgb * cw_tl + tr.rgb * cw_tr + bl.rgb * cw_bl + br.rgb * cw_br) / totalWeight;\n }\n\n return vec4f(light, totalWeight);\n}\n\n// Sample all 4 directions at a position, weighted by normal\n// Returns vec4f: RGB = weighted light, A = total validity weight\nfn sampleAllDirectionsWeighted(samplePos: vec2f, leftWeight: f32, rightWeight: f32, upWeight: f32, downWeight: f32, ambient: f32) -> vec4f {\n let fromLeft = sampleTileLightWeighted(samplePos, DIR_LEFT);\n let fromRight = sampleTileLightWeighted(samplePos, DIR_RIGHT);\n let fromUp = sampleTileLightWeighted(samplePos, DIR_UP);\n let fromDown = sampleTileLightWeighted(samplePos, DIR_DOWN);\n\n // Direction weights\n let dw_left = leftWeight + ambient;\n let dw_right = rightWeight + ambient;\n let dw_up = upWeight + ambient;\n let dw_down = downWeight + ambient;\n\n // Combine direction weight with tile validity\n let w_left = dw_left * fromLeft.w;\n let w_right = dw_right * fromRight.w;\n let w_up = dw_up * fromUp.w;\n let w_down = dw_down * fromDown.w;\n let totalValidity = fromLeft.w + fromRight.w + fromUp.w + fromDown.w;\n\n var light = vec3f(0.0);\n light += fromLeft.rgb * dw_left;\n light += fromRight.rgb * dw_right;\n light += fromUp.rgb * dw_up;\n light += fromDown.rgb * dw_down;\n\n return vec4f(light, totalValidity);\n}\n\n// Vogel disk sample position\nfn vogelDiskSample(sampleIndex: i32, samplesCount: i32, rotation: f32) -> vec2f {\n let theta = f32(sampleIndex) * GOLDEN_ANGLE + rotation;\n let radius = sqrt((f32(sampleIndex) + 0.5) / f32(samplesCount));\n return vec2f(cos(theta), sin(theta)) * radius;\n}\n\n@fragment\nfn fragmentMain(input: VertexOutput) -> @location(0) vec4f {\n let uv = input.uv;\n let halfWidth = uniforms.screenParams.z;\n let halfHeight = uniforms.screenParams.w;\n let fullWidth = uniforms.screenParams.x;\n let fullHeight = uniforms.screenParams.y;\n let pixelCoord = vec2i(i32(uv.x * halfWidth), i32(uv.y * halfHeight));\n\n // Sample GBuffer normal using UV (works regardless of resolution ratio)\n let fullPixelCoord = vec2i(i32(uv.x * fullWidth), i32(uv.y * fullHeight));\n let normalSample = textureLoad(gbufferNormal, fullPixelCoord, 0).xyz;\n\n // Skip sky pixels (zero normal)\n let normalLen = length(normalSample);\n if (normalLen < 0.1) {\n return vec4f(0.0, 0.0, 0.0, 0.0);\n }\n\n let normal = normalize(normalSample);\n\n // Convert pixel position to tile position\n let tileSize = uniforms.tileParams.z;\n\n // Current pixel in full resolution\n let fullPixelX = uv.x * fullWidth;\n let fullPixelY = uv.y * fullHeight;\n\n // Current tile position (fractional)\n let tilePos = vec2f(fullPixelX / tileSize, fullPixelY / tileSize);\n\n // Sample radius in tiles (default 2.0)\n let sampleRadius = uniforms.tileParams.w;\n\n // Convert screen-space normal to 2D directional weights\n // Light FROM a direction hits surfaces FACING toward that direction\n let screenNormalX = normal.x; // Positive = faces right in screen\n let screenNormalY = -normal.y; // Flip for screen coords (positive = faces down)\n\n // Calculate directional weights based on normal\n let leftWeight = max(-screenNormalX, 0.0); // Faces left (toward left light)\n let rightWeight = max(screenNormalX, 0.0); // Faces right (toward right light)\n let upWeight = max(-screenNormalY, 0.0); // Faces up in world (toward up light)\n let downWeight = max(screenNormalY, 0.0); // Faces down in world (toward down light)\n\n // Ambient contribution from all directions\n let ambient = 0.25;\n\n // Per-pixel noise for jittering the Vogel disk rotation\n let frameIndex = uniforms.ssgiParams.y;\n let noiseInput = vec2f(f32(pixelCoord.x), f32(pixelCoord.y)) + frameIndex * 0.618;\n let rotation = hash21(noiseInput) * 2.0 * PI;\n\n // Accumulate irradiance from 16 Vogel disk samples\n var irradiance = vec3f(0.0);\n var totalSampleWeight = 0.0;\n let totalWeight = leftWeight + rightWeight + upWeight + downWeight + ambient * 4.0;\n\n for (var i = 0; i < SAMPLE_COUNT; i++) {\n // Get Vogel disk offset, scaled by radius\n let diskOffset = vogelDiskSample(i, SAMPLE_COUNT, rotation) * sampleRadius;\n let samplePos = tilePos + diskOffset;\n\n // Sample all 4 directions at this position\n let sampleResult = sampleAllDirectionsWeighted(samplePos, leftWeight, rightWeight, upWeight, downWeight, ambient);\n\n // Calculate brightness of this sample\n let sampleBrightness = max(max(sampleResult.rgb.r, sampleResult.rgb.g), sampleResult.rgb.b);\n\n // Weight by validity from tile data (propagation weights)\n // Tiles with low validity are likely at screen edges or sky\n let validityWeight = smoothstep(0.0, 2.0, sampleResult.w);\n\n // Also weight by minimum absolute brightness (very dark = likely sky before sky pass)\n let absoluteBrightnessWeight = smoothstep(0.001, 0.02, sampleBrightness);\n\n // Combined weight\n let sampleWeight = validityWeight * absoluteBrightnessWeight;\n\n irradiance += sampleResult.rgb * sampleWeight;\n totalSampleWeight += sampleWeight;\n }\n\n // Average over weighted samples\n if (totalSampleWeight > 0.1) {\n irradiance /= totalSampleWeight;\n }\n\n // Normalize by directional weights\n if (totalWeight > 0.0) {\n irradiance /= totalWeight;\n }\n\n // Apply intensity\n let intensity = uniforms.ssgiParams.x;\n irradiance *= intensity;\n\n // Clamp to prevent fireflies\n let maxBrightness = 4.0;\n let brightness = max(max(irradiance.r, irradiance.g), irradiance.b);\n if (brightness > maxBrightness) {\n irradiance *= maxBrightness / brightness;\n }\n\n return vec4f(irradiance, 1.0);\n}\n","import { BasePass } from \"./BasePass.js\"\nimport { Texture } from \"../../Texture.js\"\n\nimport ssgiWGSL from \"../shaders/ssgi.wgsl\"\n\n/**\n * SSGIPass - Screen Space Global Illumination (Tile-Based)\n *\n * Samples propagated directional light from tiles in a cross pattern.\n * Uses screen-space normal to weight directional contributions.\n *\n * Input: Propagate buffer from SSGITilePass, GBuffer normals\n * Output: SSGI texture (half-res, HDR)\n */\n\nconst TILE_SIZE = 64\n\nclass SSGIPass extends BasePass {\n constructor(engine = null) {\n super('SSGI', engine)\n\n this.ssgiTexture = null\n this.pipeline = null\n this.bindGroupLayout = null\n this.uniformBuffer = null\n this.uniformData = null\n this.width = 0\n this.height = 0\n\n // Propagate buffer from SSGITilePass\n this.propagateBuffer = null\n this.tileCountX = 0\n this.tileCountY = 0\n\n // GBuffer reference\n this.gbuffer = null\n\n // Frame counter for temporal jittering\n this.frameIndex = 0\n\n // Textures pending destruction\n this._pendingDestroyRing = [[], [], []]\n this._pendingDestroyIndex = 0\n }\n\n async _init() {\n const { canvas } = this.engine\n await this._createResources(canvas.width, canvas.height)\n }\n\n /**\n * Set the propagate buffer from SSGITilePass\n */\n setPropagateBuffer(buffer, tileCountX, tileCountY) {\n this.propagateBuffer = buffer\n this.tileCountX = tileCountX\n this.tileCountY = tileCountY\n }\n\n /**\n * Set GBuffer for normal access\n */\n setGBuffer(gbuffer) {\n this.gbuffer = gbuffer\n }\n\n async _createResources(width, height) {\n const { device } = this.engine\n\n // Half resolution for performance\n this.width = Math.floor(width / 2)\n this.height = Math.floor(height / 2)\n\n // Create SSGI output texture (half-res, HDR)\n this.ssgiTexture = await Texture.renderTarget(this.engine, 'rgba16float', this.width, this.height)\n this.ssgiTexture.label = 'ssgiOutput'\n\n // Uniform buffer: screenParams(16) + tileParams(16) + ssgiParams(16) = 48 bytes\n const uniformSize = 48\n this.uniformData = new ArrayBuffer(uniformSize)\n this.uniformBuffer = device.createBuffer({\n label: 'ssgiUniformBuffer',\n size: uniformSize,\n usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST,\n })\n\n // Create shader module\n const shaderModule = device.createShaderModule({\n label: 'ssgiShaderModule',\n code: ssgiWGSL,\n })\n\n // Create bind group layout\n this.bindGroupLayout = device.createBindGroupLayout({\n label: 'ssgiBindGroupLayout',\n entries: [\n { binding: 0, visibility: GPUShaderStage.FRAGMENT | GPUShaderStage.VERTEX, buffer: { type: 'uniform' } },\n { binding: 1, visibility: GPUShaderStage.FRAGMENT, texture: { sampleType: 'unfilterable-float' } }, // gbufferNormal\n { binding: 2, visibility: GPUShaderStage.FRAGMENT, buffer: { type: 'read-only-storage' } }, // propagateBuffer\n ],\n })\n\n // Create render pipeline (async for non-blocking initialization)\n this.pipeline = await device.createRenderPipelineAsync({\n label: 'ssgiPipeline',\n layout: device.createPipelineLayout({\n bindGroupLayouts: [this.bindGroupLayout],\n }),\n vertex: {\n module: shaderModule,\n entryPoint: 'vertexMain',\n },\n fragment: {\n module: shaderModule,\n entryPoint: 'fragmentMain',\n targets: [{ format: 'rgba16float' }],\n },\n primitive: {\n topology: 'triangle-list',\n },\n })\n }\n\n async _execute(context) {\n const { device, canvas } = this.engine\n const { camera } = context\n\n // Process deferred texture destruction\n this._pendingDestroyIndex = (this._pendingDestroyIndex + 1) % 3\n const toDestroy = this._pendingDestroyRing[this._pendingDestroyIndex]\n for (const res of toDestroy) {\n res.destroy()\n }\n this._pendingDestroyRing[this._pendingDestroyIndex] = []\n\n // Check if SSGI is enabled\n const ssgiSettings = this.settings?.ssgi\n if (!ssgiSettings?.enabled) {\n return\n }\n\n // Check required resources\n if (!this.gbuffer || !this.propagateBuffer) {\n return\n }\n\n if (!this.pipeline) {\n return\n }\n\n // Update uniforms - use render dimensions (this.width/height are half-res)\n // Don't use canvas dimensions as they may differ from render dimensions\n this._updateUniforms(ssgiSettings, this.width * 2, this.height * 2)\n\n // Create bind group\n const bindGroup = device.createBindGroup({\n label: 'ssgiBindGroup',\n layout: this.bindGroupLayout,\n entries: [\n { binding: 0, resource: { buffer: this.uniformBuffer } },\n { binding: 1, resource: this.gbuffer.normal.view },\n { binding: 2, resource: { buffer: this.propagateBuffer } },\n ],\n })\n\n // Render\n const commandEncoder = device.createCommandEncoder({ label: 'ssgiCommandEncoder' })\n\n const passEncoder = commandEncoder.beginRenderPass({\n label: 'ssgiRenderPass',\n colorAttachments: [{\n view: this.ssgiTexture.view,\n clearValue: { r: 0, g: 0, b: 0, a: 0 },\n loadOp: 'clear',\n storeOp: 'store',\n }],\n })\n\n passEncoder.setPipeline(this.pipeline)\n passEncoder.setBindGroup(0, bindGroup)\n passEncoder.draw(3)\n passEncoder.end()\n\n device.queue.submit([commandEncoder.finish()])\n }\n\n _updateUniforms(ssgiSettings, fullWidth, fullHeight) {\n const { device } = this.engine\n\n const view = new DataView(this.uniformData)\n let offset = 0\n\n // screenParams: fullWidth, fullHeight, halfWidth, halfHeight\n view.setFloat32(offset, fullWidth, true); offset += 4\n view.setFloat32(offset, fullHeight, true); offset += 4\n view.setFloat32(offset, this.width, true); offset += 4\n view.setFloat32(offset, this.height, true); offset += 4\n\n // tileParams: tileCountX, tileCountY, tileSize, sampleRadius\n const sampleRadius = ssgiSettings.sampleRadius ?? 2.0 // Vogel disk radius in tiles\n view.setFloat32(offset, this.tileCountX, true); offset += 4\n view.setFloat32(offset, this.tileCountY, true); offset += 4\n view.setFloat32(offset, TILE_SIZE, true); offset += 4\n view.setFloat32(offset, sampleRadius, true); offset += 4\n\n // ssgiParams: intensity, frameIndex, unused, unused\n view.setFloat32(offset, ssgiSettings.intensity ?? 1.0, true); offset += 4\n view.setFloat32(offset, this.frameIndex, true); offset += 4\n view.setFloat32(offset, 0.0, true); offset += 4\n view.setFloat32(offset, 0.0, true); offset += 4\n\n // Increment frame index for temporal jittering\n this.frameIndex++\n\n device.queue.writeBuffer(this.uniformBuffer, 0, this.uniformData)\n }\n\n /**\n * Get SSGI output texture\n */\n getSSGITexture() {\n return this.ssgiTexture\n }\n\n async _resize(width, height) {\n this._queueResourcesForDestruction()\n await this._createResources(width, height)\n }\n\n _queueResourcesForDestruction() {\n const slot = this._pendingDestroyRing[this._pendingDestroyIndex]\n if (this.ssgiTexture?.texture) {\n slot.push(this.ssgiTexture.texture)\n this.ssgiTexture = null\n }\n if (this.uniformBuffer) {\n slot.push(this.uniformBuffer)\n this.uniformBuffer = null\n }\n }\n\n _destroyResources() {\n if (this.ssgiTexture?.texture) {\n this.ssgiTexture.texture.destroy()\n this.ssgiTexture = null\n }\n if (this.uniformBuffer) {\n this.uniformBuffer.destroy()\n this.uniformBuffer = null\n }\n }\n\n _destroy() {\n this._destroyResources()\n for (const slot of this._pendingDestroyRing) {\n for (const res of slot) {\n res.destroy()\n }\n }\n this._pendingDestroyRing = [[], [], []]\n this.pipeline = null\n this.bindGroupLayout = null\n }\n}\n\nexport { SSGIPass }\n","// Render Post-Processing shader\n// Combines SSGI/Planar Reflection with lighting output in HDR space\n\nstruct RenderPostUniforms {\n screenSize: vec2f,\n ssgiEnabled: f32,\n ssgiIntensity: f32,\n planarEnabled: f32,\n planarGroundLevel: f32,\n planarRoughnessCutoff: f32,\n planarNormalPerturbation: f32,\n noiseSize: f32,\n frameCount: f32,\n planarBlurSamples: f32,\n planarIntensity: f32,\n renderScale: f32, // Render resolution multiplier for scaling pixel-based effects\n ssgiSaturateLevel: f32, // Logarithmic saturation level for indirect light\n planarDistanceFade: f32, // Distance from ground for full reflection (meters)\n ambientCaptureEnabled: f32, // Enable 6-directional ambient capture\n ambientCaptureIntensity: f32, // Intensity of ambient capture contribution\n ambientCaptureFadeDistance: f32, // Distance from camera where ambient fades to zero\n cameraNear: f32, // Camera near plane\n cameraFar: f32, // Camera far plane\n ambientCaptureSaturateLevel: f32, // Logarithmic saturation level for ambient capture\n}\n\n@group(0) @binding(0) var<uniform> uniforms: RenderPostUniforms;\n@group(0) @binding(1) var lightingOutput: texture_2d<f32>; // HDR lighting result\n@group(0) @binding(2) var ssgiTexture: texture_2d<f32>; // SSGI result (half-res)\n@group(0) @binding(3) var gbufferARM: texture_2d<f32>; // For metallic/roughness\n@group(0) @binding(4) var gbufferNormal: texture_2d<f32>; // For surface normal\n@group(0) @binding(5) var planarReflection: texture_2d<f32>; // Planar reflection (mirrored render)\n@group(0) @binding(6) var noiseTexture: texture_2d<f32>; // Noise texture for jitter\n@group(0) @binding(7) var linearSampler: sampler; // Mirror-repeat sampler\n@group(0) @binding(8) var nearestSampler: sampler;\n@group(0) @binding(9) var<storage, read> ambientCapture: array<vec4f, 6>; // 6 directional colors: up, down, left, right, front, back\n@group(0) @binding(10) var gbufferDepth: texture_2d<f32>; // Depth buffer for distance calculation\n\nstruct VertexOutput {\n @builtin(position) position: vec4f,\n @location(0) uv: vec2f,\n}\n\n@vertex\nfn vertexMain(@builtin(vertex_index) vertexIndex: u32) -> VertexOutput {\n // Full-screen triangle\n var positions = array<vec2f, 3>(\n vec2f(-1.0, -1.0),\n vec2f(3.0, -1.0),\n vec2f(-1.0, 3.0)\n );\n var uvs = array<vec2f, 3>(\n vec2f(0.0, 1.0),\n vec2f(2.0, 1.0),\n vec2f(0.0, -1.0)\n );\n\n var output: VertexOutput;\n output.position = vec4f(positions[vertexIndex], 0.0, 1.0);\n output.uv = uvs[vertexIndex];\n return output;\n}\n\n// Get noise value at pixel position with frame-based offset\nfn getNoise(pixelCoord: vec2f) -> vec4f {\n let noiseSize = uniforms.noiseSize;\n // Offset by frame count for temporal variation (0 if not animated)\n let frameOffset = vec2f(\n fract(uniforms.frameCount * 0.618033988749895), // Golden ratio\n fract(uniforms.frameCount * 0.381966011250105)\n ) * noiseSize;\n let coord = vec2i((pixelCoord + frameOffset) % noiseSize);\n return textureLoad(noiseTexture, coord, 0);\n}\n\n// Bilateral upscale for half-res textures\nfn bilateralUpscale(tex: texture_2d<f32>, uv: vec2f, fullSize: vec2f) -> vec4f {\n // Simple bilinear for now - could add depth-aware bilateral\n return textureSampleLevel(tex, linearSampler, uv, 0.0);\n}\n\n// Vogel disk sample pattern - gives good 2D distribution\nfn vogelDiskSample(sampleIndex: i32, numSamples: i32, rotation: f32) -> vec2f {\n let goldenAngle = 2.399963229728653; // pi * (3 - sqrt(5))\n let r = sqrt((f32(sampleIndex) + 0.5) / f32(numSamples));\n let theta = f32(sampleIndex) * goldenAngle + rotation;\n return vec2f(r * cos(theta), r * sin(theta));\n}\n\n// Sample planar reflection with roughness-based blur using blue noise jitter\nfn samplePlanarReflection(baseUV: vec2f, roughness: f32, pixelCoord: vec2f) -> vec3f {\n let numSamples = i32(uniforms.planarBlurSamples);\n\n // Calculate blur radius based on roughness\n // Minimum 1px blur, scaling up to 128px at roughness cutoff\n // Scale by renderScale to maintain consistent visual blur at different render resolutions\n let maxBlurPixels = 128.0 * uniforms.renderScale;\n let minBlurPixels = 1.0;\n let normalizedRoughness = roughness / uniforms.planarRoughnessCutoff;\n let blurRadius = max(minBlurPixels, normalizedRoughness * normalizedRoughness * maxBlurPixels);\n\n // Convert blur radius to UV space\n let blurUV = blurRadius / uniforms.screenSize;\n\n // Get blue noise for this pixel - use for rotation and radius jitter\n let noise = getNoise(pixelCoord);\n let rotationAngle = noise.r * 6.283185307; // 0 to 2*PI rotation\n let radiusJitter = noise.g; // 0 to 1 radius variation\n\n var colorSum = vec3f(0.0);\n\n for (var i = 0; i < numSamples; i++) {\n // Get Vogel disk sample position (unit disk, rotated by blue noise)\n let diskSample = vogelDiskSample(i, numSamples, rotationAngle);\n\n // Apply radius jitter per-sample using additional noise channels\n let sampleRadius = mix(0.5, 1.0, fract(radiusJitter + f32(i) * 0.618033988749895));\n\n // Calculate final offset\n let offset = diskSample * sampleRadius * blurUV;\n\n // Sample with mirror-repeat sampler handles edge cases automatically\n let jitteredUV = baseUV + offset;\n let sampleColor = textureSampleLevel(planarReflection, linearSampler, jitteredUV, 0.0).rgb;\n colorSum += sampleColor;\n }\n\n // Apply reflection intensity (default 0.9 = 90% brightness for realism)\n return (colorSum / f32(numSamples)) * uniforms.planarIntensity;\n}\n\n@fragment\nfn fragmentMain(input: VertexOutput) -> @location(0) vec4f {\n let uv = input.uv;\n let pixelCoord = input.position.xy;\n\n // Sample lighting output\n var color = textureSampleLevel(lightingOutput, nearestSampler, uv, 0.0);\n\n // Sample material properties for blending\n let arm = textureSampleLevel(gbufferARM, nearestSampler, uv, 0.0);\n let roughness = arm.g;\n let metallic = arm.b;\n\n // Sample world-space normal (xyz) and world Y position (w)\n let normalSample = textureSampleLevel(gbufferNormal, nearestSampler, uv, 0.0);\n let normal = normalSample.xyz;\n let worldY = normalSample.w;\n\n // Apply Planar Reflection (for water/floor)\n // Blends between environment reflection (in lighting) and planar reflection based on:\n // - Roughness: r=0 → full planar, r=cutoff/2 → half, r=cutoff → full env\n // - Normal direction: up → full weight, 45° → 0.5×, horizontal/down → 0\n // - Distance from ground: within distanceFade → full, 2x distanceFade → 0\n if (uniforms.planarEnabled > 0.5 && roughness < uniforms.planarRoughnessCutoff && normal.y > 0.0) {\n // Calculate distance from ground level (in any direction)\n let distFromGround = abs(worldY - uniforms.planarGroundLevel);\n let fadeDist = uniforms.planarDistanceFade;\n\n // Distance-based weight: full at fadeDist, fade to 0 at 2x fadeDist\n // within fadeDist → 1.0, at 2x fadeDist → 0.0\n let distanceWeight = saturate(1.0 - (distFromGround - fadeDist) / fadeDist);\n\n // Skip if too far from ground\n if (distanceWeight > 0.0) {\n // Flip UV.y to compensate for projection Y flip in PlanarReflectionPass\n var reflectUV = vec2f(uv.x, 1.0 - uv.y);\n\n // Apply normal perturbation for water ripples\n let perturbAmount = uniforms.planarNormalPerturbation;\n reflectUV.x += normal.x * perturbAmount;\n reflectUV.y += normal.z * perturbAmount;\n\n // Sample planar reflection with roughness-based blur\n let reflectionColor = samplePlanarReflection(reflectUV, roughness, pixelCoord);\n\n // Calculate roughness-based weight (aggressive fade near cutoff)\n // r≤0.40 → 1.0 (full planar), r=0.45 → 0.5, r≥0.50 → 0.0 (full env)\n let fadeRange = 0.10;\n let fadeStart = uniforms.planarRoughnessCutoff - fadeRange;\n let roughnessWeight = saturate(1.0 - (roughness - fadeStart) / fadeRange);\n\n // Calculate normal-based weight (how \"up-facing\" is the surface)\n // up (y=1) → 1.0, 45° from up (y≈0.707) → 0.5, horizontal (y=0) → 0.0\n // Formula: 1 - acos(y) * (2/PI) maps angle linearly\n let upDot = clamp(normal.y, 0.0, 1.0);\n let normalWeight = saturate(1.0 - acos(upDot) * 0.6366197723675814); // 2/PI\n\n // Final blend: planar vs environment (already in color from lighting pass)\n let planarBlend = roughnessWeight * normalWeight * distanceWeight;\n color = vec4f(mix(color.rgb, reflectionColor, planarBlend), color.a);\n }\n }\n\n // Apply SSGI\n if (uniforms.ssgiEnabled > 0.5) {\n let ssgi = bilateralUpscale(ssgiTexture, uv, uniforms.screenSize);\n\n // Add indirect lighting - ssgi.a = 1 means valid data\n let ssgiBlend = ssgi.a * uniforms.ssgiIntensity;\n\n // SSGI adds to diffuse component (less effect on metallic surfaces)\n // Also apply AO - indirect light is occluded in corners/crevices\n let ao = arm.r;\n let diffuseBlend = (1.0 - metallic * 0.5) * ssgiBlend * ao;\n\n // Calculate raw indirect light contribution\n var indirectLight = ssgi.rgb * diffuseBlend;\n\n // Apply logarithmic saturation to prevent indirect light from going too bright\n // Formula: saturateLevel * (1 - exp(-value / saturateLevel))\n // This smoothly approaches saturateLevel as value increases\n let satLevel = uniforms.ssgiSaturateLevel;\n if (satLevel > 0.0) {\n let indirectLum = max(indirectLight.r, max(indirectLight.g, indirectLight.b));\n if (indirectLum > 0.0) {\n let saturatedLum = satLevel * (1.0 - exp(-indirectLum / satLevel));\n indirectLight = indirectLight * (saturatedLum / indirectLum);\n }\n }\n\n color = vec4f(color.rgb + indirectLight, color.a);\n }\n\n // Apply Ambient Capture (6-directional sky/environment sampling)\n // Adds ambient light based on captured sky visibility in each direction\n // Main benefit: sky-facing surfaces get blue tint when outdoors, darker when under roof\n // Effect is local: fades to zero at ambientCaptureFadeDistance from camera\n if (uniforms.ambientCaptureEnabled > 0.5) {\n // Calculate linear depth for distance fade\n let depth = textureSampleLevel(gbufferDepth, nearestSampler, uv, 0.0).r;\n let near = uniforms.cameraNear;\n let far = uniforms.cameraFar;\n // GBuffer stores linear depth normalized to [0,1]\n let linearDepth = near + depth * (far - near);\n\n // Distance fade: full effect at 0, fades to 0 at fadeDistance (smooth curve)\n let fadeDistance = uniforms.ambientCaptureFadeDistance;\n // Square root fade: starts fading immediately but with gentler curve than linear\n // At distance 0: fade = 1.0, at fadeDistance: fade = 0\n let normalizedDist = saturate(linearDepth / fadeDistance);\n let distanceFade = 1.0 - sqrt(normalizedDist);\n\n // Only process if within fade distance\n if (distanceFade > 0.0) {\n let N = normal;\n\n // Sample ambient from 6 directions weighted by how much the surface faces each direction\n // Directions: 0=up, 1=down, 2=left(-X), 3=right(+X), 4=front(+Z), 5=back(-Z)\n var ambient = vec3f(0.0);\n\n // Up/Down (Y axis)\n ambient += ambientCapture[0].rgb * max(N.y, 0.0); // up: surface facing up receives sky\n ambient += ambientCapture[1].rgb * max(-N.y, 0.0); // down: surface facing down receives ground\n\n // Left/Right (X axis in world space)\n ambient += ambientCapture[2].rgb * max(-N.x, 0.0); // left (-X)\n ambient += ambientCapture[3].rgb * max(N.x, 0.0); // right (+X)\n\n // Front/Back (Z axis in world space)\n ambient += ambientCapture[4].rgb * max(N.z, 0.0); // front (+Z)\n ambient += ambientCapture[5].rgb * max(-N.z, 0.0); // back (-Z)\n\n // Apply AO from ARM texture (ambient only affects diffuse surfaces)\n let ao = arm.r;\n let diffuseFactor = 1.0 - metallic * 0.5; // Metallic surfaces less affected\n\n // Calculate ambient contribution\n var ambientContrib = ambient * ao * diffuseFactor * uniforms.ambientCaptureIntensity * distanceFade;\n\n // Apply logarithmic saturation to prevent ambient from going too bright\n // Formula: saturateLevel * (1 - exp(-value / saturateLevel))\n // This smoothly approaches saturateLevel as value increases\n let ambientSatLevel = uniforms.ambientCaptureSaturateLevel;\n if (ambientSatLevel > 0.0) {\n let ambientLum = max(ambientContrib.r, max(ambientContrib.g, ambientContrib.b));\n if (ambientLum > 0.0) {\n let saturatedLum = ambientSatLevel * (1.0 - exp(-ambientLum / ambientSatLevel));\n ambientContrib = ambientContrib * (saturatedLum / ambientLum);\n }\n }\n\n // Add ambient contribution\n color = vec4f(color.rgb + ambientContrib, color.a);\n }\n }\n\n return color;\n}\n","import { BasePass } from \"./BasePass.js\"\nimport { Texture } from \"../../Texture.js\"\n\nimport renderPostWGSL from \"../shaders/render_post.wgsl\"\n\n/**\n * RenderPostPass - Combines SSGI/Planar Reflection with lighting output in HDR space\n *\n * This pass runs after lighting and SSGI, blending the screen-space\n * effects with the lighting result before tone mapping.\n */\nclass RenderPostPass extends BasePass {\n constructor(engine = null) {\n super('RenderPost', engine)\n\n this.outputTexture = null // HDR output (rgba16float)\n this.pipeline = null\n this.bindGroupLayout = null\n this.uniformBuffer = null\n this.uniformData = null\n this.width = 0\n this.height = 0\n\n // Noise for jittered sampling (blue noise or bayer dither)\n this.noiseTexture = null\n this.noiseSize = 64\n this.noiseAnimated = true\n this.frameCount = 0\n\n // Ambient capture (6 directional colors buffer)\n this.ambientCaptureBuffer = null\n\n // Resources pending destruction (wait for GPU to finish using them)\n this._pendingDestroyRing = [[], [], []]\n this._pendingDestroyIndex = 0\n }\n\n async _init() {\n const { canvas } = this.engine\n await this._createResources(canvas.width, canvas.height)\n }\n\n async _createResources(width, height) {\n const { device } = this.engine\n\n this.width = width\n this.height = height\n\n // Create output texture (full-res HDR)\n this.outputTexture = await Texture.renderTarget(this.engine, 'rgba16float', width, height)\n this.outputTexture.label = 'renderPostOutput'\n\n // Uniform buffer: screenSize + flags + intensities + planar settings + blue noise + ambient capture\n // vec2f screenSize (8)\n // f32 ssgiEnabled (4)\n // f32 ssgiIntensity (4)\n // f32 planarEnabled (4)\n // f32 planarGroundLevel (4)\n // f32 planarRoughnessCutoff (4)\n // f32 planarNormalPerturbation (4)\n // f32 noiseSize (4)\n // f32 frameCount (4)\n // f32 planarBlurSamples (4)\n // f32 planarIntensity (4)\n // f32 renderScale (4)\n // f32 ssgiSaturateLevel (4)\n // f32 planarDistanceFade (4)\n // f32 ambientCaptureEnabled (4)\n // f32 ambientCaptureIntensity (4)\n // f32 ambientCaptureFadeDistance (4)\n // f32 cameraNear (4)\n // f32 cameraFar (4)\n // f32 ambientCaptureSaturateLevel (4)\n const uniformSize = 96\n this.uniformData = new ArrayBuffer(uniformSize)\n this.uniformBuffer = device.createBuffer({\n label: 'renderPostUniformBuffer',\n size: uniformSize,\n usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST,\n })\n\n // Create shader module\n const shaderModule = device.createShaderModule({\n label: 'renderPostShaderModule',\n code: renderPostWGSL,\n })\n\n // Create bind group layout\n this.bindGroupLayout = device.createBindGroupLayout({\n label: 'renderPostBindGroupLayout',\n entries: [\n { binding: 0, visibility: GPUShaderStage.FRAGMENT | GPUShaderStage.VERTEX, buffer: { type: 'uniform' } },\n { binding: 1, visibility: GPUShaderStage.FRAGMENT, texture: { sampleType: 'float' } }, // lightingOutput\n { binding: 2, visibility: GPUShaderStage.FRAGMENT, texture: { sampleType: 'float' } }, // ssgiTexture\n { binding: 3, visibility: GPUShaderStage.FRAGMENT, texture: { sampleType: 'float' } }, // gbufferARM\n { binding: 4, visibility: GPUShaderStage.FRAGMENT, texture: { sampleType: 'float' } }, // gbufferNormal\n { binding: 5, visibility: GPUShaderStage.FRAGMENT, texture: { sampleType: 'float' } }, // planarReflection\n { binding: 6, visibility: GPUShaderStage.FRAGMENT, texture: { sampleType: 'float' } }, // noise\n { binding: 7, visibility: GPUShaderStage.FRAGMENT, sampler: { type: 'filtering' } }, // linearSampler\n { binding: 8, visibility: GPUShaderStage.FRAGMENT, sampler: { type: 'non-filtering' } }, // nearestSampler\n { binding: 9, visibility: GPUShaderStage.FRAGMENT, buffer: { type: 'read-only-storage' } }, // ambientCapture (6 vec4f)\n { binding: 10, visibility: GPUShaderStage.FRAGMENT, texture: { sampleType: 'unfilterable-float' } }, // gbufferDepth\n ],\n })\n\n // Create placeholder buffer for ambient capture (6 vec4f = 96 bytes)\n this.placeholderAmbientBuffer = device.createBuffer({\n label: 'placeholderAmbientBuffer',\n size: 6 * 4 * 4, // 6 vec4f\n usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST\n })\n // Initialize with neutral gray\n const initColors = new Float32Array(6 * 4)\n for (let i = 0; i < 6; i++) {\n initColors[i * 4 + 0] = 0.0\n initColors[i * 4 + 1] = 0.0\n initColors[i * 4 + 2] = 0.0\n initColors[i * 4 + 3] = 1.0\n }\n device.queue.writeBuffer(this.placeholderAmbientBuffer, 0, initColors)\n\n // Create render pipeline (async for non-blocking initialization)\n this.pipeline = await device.createRenderPipelineAsync({\n label: 'renderPostPipeline',\n layout: device.createPipelineLayout({\n bindGroupLayouts: [this.bindGroupLayout],\n }),\n vertex: {\n module: shaderModule,\n entryPoint: 'vertexMain',\n },\n fragment: {\n module: shaderModule,\n entryPoint: 'fragmentMain',\n targets: [{ format: 'rgba16float' }],\n },\n primitive: {\n topology: 'triangle-list',\n },\n })\n\n // Create samplers\n this.linearSampler = device.createSampler({\n magFilter: 'linear',\n minFilter: 'linear',\n mipmapFilter: 'linear',\n addressModeU: 'mirror-repeat', // Mirror repeat for planar reflection edges\n addressModeV: 'mirror-repeat',\n })\n\n this.nearestSampler = device.createSampler({\n magFilter: 'nearest',\n minFilter: 'nearest',\n })\n\n // Create placeholder texture for when effects are disabled (black)\n this.placeholderTexture = await Texture.renderTarget(this.engine, 'rgba16float', 1, 1)\n\n // Clear placeholder to black\n const clearEncoder = device.createCommandEncoder({ label: 'clearPlaceholder' })\n const clearPass = clearEncoder.beginRenderPass({\n colorAttachments: [{\n view: this.placeholderTexture.view,\n loadOp: 'clear',\n storeOp: 'store',\n clearValue: { r: 0, g: 0, b: 0, a: 1 }\n }]\n })\n clearPass.end()\n device.queue.submit([clearEncoder.finish()])\n }\n\n /**\n * Set noise texture for jittered sampling\n * @param {Texture} texture - Noise texture (blue noise or bayer dither)\n * @param {number} size - Texture size (width/height)\n * @param {boolean} animated - Whether to animate noise offset each frame\n */\n setNoise(texture, size, animated = true) {\n this.noiseTexture = texture\n this.noiseSize = size || 64\n this.noiseAnimated = animated\n }\n\n /**\n * Set ambient capture buffer (6 directional colors from AmbientCapturePass)\n * @param {GPUBuffer} buffer - Storage buffer with 6 vec4f colors\n */\n setAmbientCaptureBuffer(buffer) {\n this.ambientCaptureBuffer = buffer\n }\n\n /**\n * Execute RenderPost pass\n *\n * @param {Object} context\n * @param {Object} context.lightingOutput - HDR output from lighting pass\n * @param {Object} context.gbuffer - GBuffer for ARM and Normal\n * @param {Object} context.ssgi - SSGI texture (or null)\n * @param {Object} context.planarReflection - Planar reflection texture (or null)\n */\n async _execute(context) {\n const { device } = this.engine\n const { lightingOutput, gbuffer, ssgi, planarReflection, camera } = context\n\n // Process deferred resource destruction (3 frames delayed)\n this._pendingDestroyIndex = (this._pendingDestroyIndex + 1) % 3\n const toDestroy = this._pendingDestroyRing[this._pendingDestroyIndex]\n for (const res of toDestroy) {\n res.destroy()\n }\n this._pendingDestroyRing[this._pendingDestroyIndex] = []\n\n if (!lightingOutput || !gbuffer) {\n return\n }\n\n // Increment frame counter for temporal jitter\n this.frameCount++\n\n // Get settings\n const ssgiSettings = this.settings?.ssgi\n const planarSettings = this.settings?.planarReflection\n const ambientSettings = this.settings?.ambientCapture\n\n const ssgiEnabled = ssgiSettings?.enabled && ssgi\n const planarEnabled = planarSettings?.enabled && planarReflection\n const ambientEnabled = ambientSettings?.enabled && this.ambientCaptureBuffer\n\n // Update uniforms\n const view = new DataView(this.uniformData)\n view.setFloat32(0, this.width, true)\n view.setFloat32(4, this.height, true)\n view.setFloat32(8, ssgiEnabled ? 1.0 : 0.0, true)\n view.setFloat32(12, ssgiSettings?.intensity || 1.0, true) // ssgiIntensity\n view.setFloat32(16, planarEnabled ? 1.0 : 0.0, true)\n view.setFloat32(20, planarSettings?.groundLevel ?? 0.0, true)\n view.setFloat32(24, planarSettings?.roughnessCutoff ?? 0.5, true)\n view.setFloat32(28, planarSettings?.normalPerturbation ?? 0.1, true)\n view.setFloat32(32, this.noiseSize, true)\n view.setFloat32(36, this.noiseAnimated ? this.frameCount : 0, true)\n view.setFloat32(40, planarSettings?.blurSamples ?? 8, true)\n view.setFloat32(44, planarSettings?.intensity ?? 0.9, true)\n view.setFloat32(48, this.settings?.rendering?.renderScale ?? 1.0, true)\n view.setFloat32(52, ssgiSettings?.saturateLevel ?? 0.5, true)\n view.setFloat32(56, planarSettings?.distanceFade ?? 1.0, true)\n view.setFloat32(60, ambientEnabled ? 1.0 : 0.0, true)\n view.setFloat32(64, ambientSettings?.intensity ?? 0.2, true)\n view.setFloat32(68, ambientSettings?.maxDistance ?? 25, true) // fade distance\n view.setFloat32(72, camera?.near ?? 0.1, true) // cameraNear\n view.setFloat32(76, camera?.far ?? 1000, true) // cameraFar\n view.setFloat32(80, ambientSettings?.saturateLevel ?? 0.5, true) // ambientCaptureSaturateLevel\n\n device.queue.writeBuffer(this.uniformBuffer, 0, this.uniformData)\n\n // Use placeholder textures/buffers if effects are disabled\n const ssgiTexture = ssgiEnabled ? ssgi : this.placeholderTexture\n const planarTexture = planarEnabled ? planarReflection : this.placeholderTexture\n const noiseView = this.noiseTexture?.view || this.placeholderTexture.view\n const ambientBuffer = this.ambientCaptureBuffer || this.placeholderAmbientBuffer\n\n // Create bind group\n const bindGroup = device.createBindGroup({\n label: 'renderPostBindGroup',\n layout: this.bindGroupLayout,\n entries: [\n { binding: 0, resource: { buffer: this.uniformBuffer } },\n { binding: 1, resource: lightingOutput.view },\n { binding: 2, resource: ssgiTexture.view },\n { binding: 3, resource: gbuffer.arm.view },\n { binding: 4, resource: gbuffer.normal.view },\n { binding: 5, resource: planarTexture.view },\n { binding: 6, resource: noiseView },\n { binding: 7, resource: this.linearSampler },\n { binding: 8, resource: this.nearestSampler },\n { binding: 9, resource: { buffer: ambientBuffer } },\n { binding: 10, resource: gbuffer.depth.view },\n ],\n })\n\n // Render\n const commandEncoder = device.createCommandEncoder({ label: 'renderPostCommandEncoder' })\n\n const passEncoder = commandEncoder.beginRenderPass({\n label: 'renderPostRenderPass',\n colorAttachments: [{\n view: this.outputTexture.view,\n clearValue: { r: 0, g: 0, b: 0, a: 1 },\n loadOp: 'clear',\n storeOp: 'store',\n }],\n })\n\n passEncoder.setPipeline(this.pipeline)\n passEncoder.setBindGroup(0, bindGroup)\n passEncoder.draw(3)\n passEncoder.end()\n\n device.queue.submit([commandEncoder.finish()])\n }\n\n /**\n * Get output texture for ScreenPostPass\n * @returns {Texture} HDR output with SSGI/Planar applied\n */\n getOutputTexture() {\n return this.outputTexture\n }\n\n async _resize(width, height) {\n this._queueResourcesForDestruction()\n await this._createResources(width, height)\n }\n\n _queueResourcesForDestruction() {\n const slot = this._pendingDestroyRing[this._pendingDestroyIndex]\n if (this.outputTexture?.texture) {\n slot.push(this.outputTexture.texture)\n this.outputTexture = null\n }\n if (this.placeholderTexture?.texture) {\n slot.push(this.placeholderTexture.texture)\n this.placeholderTexture = null\n }\n if (this.uniformBuffer) {\n slot.push(this.uniformBuffer)\n this.uniformBuffer = null\n }\n }\n\n _destroyResources() {\n if (this.outputTexture?.texture) {\n this.outputTexture.texture.destroy()\n this.outputTexture = null\n }\n if (this.placeholderTexture?.texture) {\n this.placeholderTexture.texture.destroy()\n this.placeholderTexture = null\n }\n if (this.uniformBuffer) {\n this.uniformBuffer.destroy()\n this.uniformBuffer = null\n }\n if (this.placeholderAmbientBuffer) {\n this.placeholderAmbientBuffer.destroy()\n this.placeholderAmbientBuffer = null\n }\n }\n\n _destroy() {\n this._destroyResources()\n // Clean up any pending resources in ring buffer\n for (const slot of this._pendingDestroyRing) {\n for (const res of slot) {\n res.destroy()\n }\n }\n this._pendingDestroyRing = [[], [], []]\n this.pipeline = null\n this.bindGroupLayout = null\n }\n}\n\nexport { RenderPostPass }\n","// Bloom bright pass shader\n// Extracts bright and emissive pixels from HDR lighting output\n// Uses exponential falloff so dark pixels contribute exponentially less\n\nstruct Uniforms {\n threshold: f32, // Brightness threshold (e.g., 0.8)\n softThreshold: f32, // Soft knee (0 = hard cutoff, 1 = very soft)\n intensity: f32, // Overall bloom intensity\n emissiveBoost: f32, // Extra boost for emissive pixels\n maxBrightness: f32, // Clamp input brightness (prevents specular halos)\n}\n\n@group(0) @binding(0) var<uniform> uniforms: Uniforms;\n@group(0) @binding(1) var inputTexture: texture_2d<f32>; // HDR lighting output\n@group(0) @binding(2) var inputSampler: sampler;\n\nstruct VertexOutput {\n @builtin(position) position: vec4f,\n @location(0) uv: vec2f,\n}\n\n@vertex\nfn vertexMain(@builtin(vertex_index) vertexIndex: u32) -> VertexOutput {\n // Full-screen triangle\n var positions = array<vec2f, 3>(\n vec2f(-1.0, -1.0),\n vec2f(3.0, -1.0),\n vec2f(-1.0, 3.0)\n );\n var uvs = array<vec2f, 3>(\n vec2f(0.0, 1.0),\n vec2f(2.0, 1.0),\n vec2f(0.0, -1.0)\n );\n\n var output: VertexOutput;\n output.position = vec4f(positions[vertexIndex], 0.0, 1.0);\n output.uv = uvs[vertexIndex];\n return output;\n}\n\n// Calculate brightness as max RGB (treats all colors equally for bloom threshold)\nfn brightness(color: vec3f) -> f32 {\n return max(max(color.r, color.g), color.b);\n}\n\n// Soft threshold with knee curve\n// Creates smooth transition around threshold instead of hard cutoff\nfn softThresholdCurve(brightness: f32, threshold: f32, knee: f32) -> f32 {\n let soft = threshold * knee;\n let softMin = threshold - soft;\n let softMax = threshold + soft;\n\n if (brightness <= softMin) {\n // Below threshold - exponential falloff\n // Instead of 0, use exponential curve so bright-ish pixels still contribute slightly\n let ratio = brightness / max(softMin, 0.001);\n return pow(ratio, 4.0) * brightness; // Very aggressive falloff\n } else if (brightness >= softMax) {\n // Above threshold - full contribution\n return brightness;\n } else {\n // In the soft knee region - smooth interpolation\n let t = (brightness - softMin) / (softMax - softMin);\n let smoothT = t * t * (3.0 - 2.0 * t); // Smoothstep\n return mix(pow(brightness / threshold, 4.0) * brightness, brightness, smoothT);\n }\n}\n\n@fragment\nfn fragmentMain(input: VertexOutput) -> @location(0) vec4f {\n var color = textureSample(inputTexture, inputSampler, input.uv).rgb;\n\n // Clamp extremely bright values (specular highlights) to prevent excessive bloom\n let bright = brightness(color);\n if (bright > uniforms.maxBrightness) {\n color = color * (uniforms.maxBrightness / bright);\n }\n let clampedBright = min(bright, uniforms.maxBrightness);\n\n // Apply soft threshold with exponential falloff\n let contribution = softThresholdCurve(clampedBright, uniforms.threshold, uniforms.softThreshold);\n\n // Calculate the extraction factor (how much of the original color to keep)\n let factor = contribution / max(clampedBright, 0.001);\n\n // Extract bloom color\n var bloomColor = color * factor * uniforms.intensity;\n\n // Note: Emissive is already included in the HDR color from lighting pass\n // Very bright pixels (brightness > 1.0) are likely emissive or highly lit\n // Give them extra boost based on how much they exceed 1.0\n let emissiveFactor = max(0.0, clampedBright - 1.0);\n bloomColor *= 1.0 + emissiveFactor * uniforms.emissiveBoost;\n\n return vec4f(bloomColor, 1.0);\n}\n","// Bloom blur shader - Separable Gaussian blur\n// Run twice: once horizontal, once vertical\n\nstruct Uniforms {\n direction: vec2f, // (1,0) for horizontal, (0,1) for vertical\n texelSize: vec2f, // 1.0 / textureSize\n blurRadius: f32, // Blur radius in pixels\n padding: f32,\n}\n\n@group(0) @binding(0) var<uniform> uniforms: Uniforms;\n@group(0) @binding(1) var inputTexture: texture_2d<f32>;\n@group(0) @binding(2) var inputSampler: sampler;\n\nstruct VertexOutput {\n @builtin(position) position: vec4f,\n @location(0) uv: vec2f,\n}\n\n@vertex\nfn vertexMain(@builtin(vertex_index) vertexIndex: u32) -> VertexOutput {\n // Full-screen triangle\n var positions = array<vec2f, 3>(\n vec2f(-1.0, -1.0),\n vec2f(3.0, -1.0),\n vec2f(-1.0, 3.0)\n );\n var uvs = array<vec2f, 3>(\n vec2f(0.0, 1.0),\n vec2f(2.0, 1.0),\n vec2f(0.0, -1.0)\n );\n\n var output: VertexOutput;\n output.position = vec4f(positions[vertexIndex], 0.0, 1.0);\n output.uv = uvs[vertexIndex];\n return output;\n}\n\n// Gaussian weight function\nfn gaussian(x: f32, sigma: f32) -> f32 {\n return exp(-(x * x) / (2.0 * sigma * sigma));\n}\n\n@fragment\nfn fragmentMain(input: VertexOutput) -> @location(0) vec4f {\n let uv = input.uv;\n let direction = uniforms.direction;\n let texelSize = uniforms.texelSize;\n let radius = uniforms.blurRadius;\n\n // Sigma is radius / 3 for good coverage (99.7% of gaussian within 3 sigma)\n let sigma = radius / 3.0;\n\n // Sample center\n var color = textureSample(inputTexture, inputSampler, uv).rgb;\n var totalWeight = 1.0;\n\n // Use incremental offsets for better cache coherency\n // Sample in both directions from center\n let stepSize = 1.5; // Sample every 1.5 pixels for quality/performance balance\n let numSamples = i32(ceil(radius / stepSize));\n\n for (var i = 1; i <= numSamples; i++) {\n let offset = f32(i) * stepSize;\n let weight = gaussian(offset, sigma);\n\n // Positive direction\n let uvPos = uv + direction * texelSize * offset;\n color += textureSample(inputTexture, inputSampler, uvPos).rgb * weight;\n\n // Negative direction\n let uvNeg = uv - direction * texelSize * offset;\n color += textureSample(inputTexture, inputSampler, uvNeg).rgb * weight;\n\n totalWeight += weight * 2.0;\n }\n\n return vec4f(color / totalWeight, 1.0);\n}\n","import { BasePass } from \"./BasePass.js\"\n\nimport bloomExtractWGSL from \"../shaders/bloom.wgsl\"\nimport bloomBlurWGSL from \"../shaders/bloom_blur.wgsl\"\n\n/**\n * BloomPass - HDR Bloom/Glare effect\n *\n * Extracts bright pixels with exponential falloff and applies\n * two-pass diagonal Gaussian blur for X-shaped glare effect.\n *\n * Input: HDR lighting output\n * Output: Blurred bloom texture with X-shaped glare\n */\nclass BloomPass extends BasePass {\n constructor(engine = null) {\n super('Bloom', engine)\n\n // Pipelines\n this.extractPipeline = null\n this.blurPipeline = null\n\n // Textures (ping-pong for blur)\n this.brightTexture = null // Extracted bright pixels\n this.blurTextureA = null // After horizontal blur\n this.blurTextureB = null // After vertical blur (final output)\n\n // Resources\n this.inputTexture = null\n this.extractUniformBuffer = null\n this.blurUniformBufferH = null // Horizontal blur uniforms\n this.blurUniformBufferV = null // Vertical blur uniforms\n this.extractBindGroup = null\n this.blurBindGroupH = null // Horizontal blur\n this.blurBindGroupV = null // Vertical blur\n this.sampler = null\n\n // Textures pending destruction (wait for GPU to finish using them)\n // Use a ring buffer of 3 frames to ensure GPU is definitely done\n this._pendingDestroyRing = [[], [], []]\n this._pendingDestroyIndex = 0\n\n // Render dimensions (may differ from canvas when effect scaling is applied)\n this.width = 0\n this.height = 0\n // Bloom internal resolution (scaled down for performance)\n this.bloomWidth = 0\n this.bloomHeight = 0\n }\n\n // Convenience getters for bloom settings\n get bloomEnabled() { return this.settings?.bloom?.enabled ?? true }\n get intensity() { return this.settings?.bloom?.intensity ?? 1.0 }\n get threshold() { return this.settings?.bloom?.threshold ?? 0.8 }\n get softThreshold() { return this.settings?.bloom?.softThreshold ?? 0.5 }\n get radius() { return this.settings?.bloom?.radius ?? 32 }\n get emissiveBoost() { return this.settings?.bloom?.emissiveBoost ?? 2.0 }\n get maxBrightness() { return this.settings?.bloom?.maxBrightness ?? 4.0 }\n get renderScale() { return this.settings?.rendering?.renderScale ?? 1.0 }\n // Bloom resolution scale - 0.5 = half res (faster), 1.0 = full res (quality)\n get bloomScale() { return this.settings?.bloom?.scale ?? 0.5 }\n\n /**\n * Set the input texture (HDR lighting output)\n * @param {Object} texture - Input texture with view property\n */\n setInputTexture(texture) {\n // Only rebuild if the texture actually changed\n if (this.inputTexture !== texture) {\n this.inputTexture = texture\n this._needsRebuild = true\n }\n }\n\n async _init() {\n const { device } = this.engine\n\n // Create sampler for all bloom textures\n this.sampler = device.createSampler({\n label: 'Bloom Sampler',\n minFilter: 'linear',\n magFilter: 'linear',\n addressModeU: 'clamp-to-edge',\n addressModeV: 'clamp-to-edge',\n })\n }\n\n /**\n * Create or recreate bloom textures at scaled resolution\n */\n _createTextures(width, height) {\n const { device } = this.engine\n\n // Queue old textures for deferred destruction (GPU may still be using them)\n // Add to current slot in ring buffer - will be destroyed 3 frames later\n const slot = this._pendingDestroyRing[this._pendingDestroyIndex]\n if (this.brightTexture?.texture) slot.push(this.brightTexture.texture)\n if (this.blurTextureA?.texture) slot.push(this.blurTextureA.texture)\n if (this.blurTextureB?.texture) slot.push(this.blurTextureB.texture)\n\n // Calculate scaled resolution for bloom (0.5 = half res for performance)\n // Bloom is blurry anyway, so half-res is usually sufficient\n const scale = this.bloomScale\n const bloomWidth = Math.max(1, Math.floor(width * scale))\n const bloomHeight = Math.max(1, Math.floor(height * scale))\n\n // Only log if dimensions actually changed\n if (this.bloomWidth !== bloomWidth || this.bloomHeight !== bloomHeight) {\n console.log(`Bloom: ${width}x${height} -> ${bloomWidth}x${bloomHeight} (scale: ${scale})`)\n }\n\n this.bloomWidth = bloomWidth\n this.bloomHeight = bloomHeight\n\n // Create textures at scaled resolution\n const createBloomTexture = (label) => {\n const texture = device.createTexture({\n label,\n size: { width: bloomWidth, height: bloomHeight, depthOrArrayLayers: 1 },\n format: 'rgba16float',\n usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.TEXTURE_BINDING,\n })\n return {\n texture,\n view: texture.createView({ label: `${label} View` }),\n sampler: this.sampler,\n width: bloomWidth,\n height: bloomHeight,\n }\n }\n\n this.brightTexture = createBloomTexture('Bloom Bright')\n this.blurTextureA = createBloomTexture('Bloom Blur A')\n this.blurTextureB = createBloomTexture('Bloom Blur B')\n }\n\n async _buildPipeline() {\n if (!this.inputTexture) {\n return\n }\n\n const { device, canvas } = this.engine\n // Store initial dimensions (may be updated by resize)\n this.width = canvas.width\n this.height = canvas.height\n\n // Create bloom textures\n this._createTextures(this.width, this.height)\n\n // Create uniform buffers\n this.extractUniformBuffer = device.createBuffer({\n label: 'Bloom Extract Uniforms',\n size: 32, // 5 floats + padding for 16-byte alignment\n usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST,\n })\n\n this.blurUniformBufferH = device.createBuffer({\n label: 'Bloom Blur H Uniforms',\n size: 32, // 8 floats (2 vec2 + 2 float + padding)\n usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST,\n })\n\n this.blurUniformBufferV = device.createBuffer({\n label: 'Bloom Blur V Uniforms',\n size: 32,\n usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST,\n })\n\n // ===== EXTRACT PIPELINE =====\n const extractBGL = device.createBindGroupLayout({\n label: 'Bloom Extract BGL',\n entries: [\n { binding: 0, visibility: GPUShaderStage.FRAGMENT, buffer: { type: 'uniform' } },\n { binding: 1, visibility: GPUShaderStage.FRAGMENT, texture: { sampleType: 'float' } },\n { binding: 2, visibility: GPUShaderStage.FRAGMENT, sampler: { type: 'filtering' } },\n ],\n })\n\n const extractModule = device.createShaderModule({\n label: 'Bloom Extract Shader',\n code: bloomExtractWGSL,\n })\n\n // ===== BLUR PIPELINE =====\n const blurBGL = device.createBindGroupLayout({\n label: 'Bloom Blur BGL',\n entries: [\n { binding: 0, visibility: GPUShaderStage.FRAGMENT, buffer: { type: 'uniform' } },\n { binding: 1, visibility: GPUShaderStage.FRAGMENT, texture: { sampleType: 'float' } },\n { binding: 2, visibility: GPUShaderStage.FRAGMENT, sampler: { type: 'filtering' } },\n ],\n })\n\n const blurModule = device.createShaderModule({\n label: 'Bloom Blur Shader',\n code: bloomBlurWGSL,\n })\n\n // Create both pipelines in parallel for faster initialization\n const [extractPipeline, blurPipeline] = await Promise.all([\n device.createRenderPipelineAsync({\n label: 'Bloom Extract Pipeline',\n layout: device.createPipelineLayout({ bindGroupLayouts: [extractBGL] }),\n vertex: { module: extractModule, entryPoint: 'vertexMain' },\n fragment: {\n module: extractModule,\n entryPoint: 'fragmentMain',\n targets: [{ format: 'rgba16float' }],\n },\n primitive: { topology: 'triangle-list' },\n }),\n device.createRenderPipelineAsync({\n label: 'Bloom Blur Pipeline',\n layout: device.createPipelineLayout({ bindGroupLayouts: [blurBGL] }),\n vertex: { module: blurModule, entryPoint: 'vertexMain' },\n fragment: {\n module: blurModule,\n entryPoint: 'fragmentMain',\n targets: [{ format: 'rgba16float' }],\n },\n primitive: { topology: 'triangle-list' },\n })\n ])\n\n this.extractPipeline = extractPipeline\n this.blurPipeline = blurPipeline\n\n this.extractBindGroup = device.createBindGroup({\n label: 'Bloom Extract Bind Group',\n layout: extractBGL,\n entries: [\n { binding: 0, resource: { buffer: this.extractUniformBuffer } },\n { binding: 1, resource: this.inputTexture.view },\n { binding: 2, resource: this.sampler },\n ],\n })\n\n // Horizontal blur: brightTexture -> blurTextureA\n this.blurBindGroupH = device.createBindGroup({\n label: 'Bloom Blur H Bind Group',\n layout: blurBGL,\n entries: [\n { binding: 0, resource: { buffer: this.blurUniformBufferH } },\n { binding: 1, resource: this.brightTexture.view },\n { binding: 2, resource: this.sampler },\n ],\n })\n\n // Vertical blur: blurTextureA -> blurTextureB\n this.blurBindGroupV = device.createBindGroup({\n label: 'Bloom Blur V Bind Group',\n layout: blurBGL,\n entries: [\n { binding: 0, resource: { buffer: this.blurUniformBufferV } },\n { binding: 1, resource: this.blurTextureA.view },\n { binding: 2, resource: this.sampler },\n ],\n })\n\n this._needsRebuild = false\n }\n\n async _execute(context) {\n // Skip if bloom is disabled in settings\n if (!this.bloomEnabled) {\n return\n }\n\n const { device, canvas } = this.engine\n\n // Rotate ring buffer and destroy textures from 3 frames ago\n this._pendingDestroyIndex = (this._pendingDestroyIndex + 1) % 3\n const toDestroy = this._pendingDestroyRing[this._pendingDestroyIndex]\n for (const tex of toDestroy) {\n tex.destroy()\n }\n this._pendingDestroyRing[this._pendingDestroyIndex] = []\n\n // Rebuild pipeline if needed\n if (this._needsRebuild) {\n await this._buildPipeline()\n }\n\n // If rebuild was attempted but failed, don't use stale pipeline with old bind groups\n if (!this.extractPipeline || !this.blurPipeline || !this.inputTexture || this._needsRebuild) {\n return\n }\n\n // Use bloom-specific dimensions (may be scaled down for performance)\n const bloomWidth = this.bloomWidth\n const bloomHeight = this.bloomHeight\n\n // Scale radius based on bloom height relative to 1080p (settings are authored for 1080p)\n // Also scale by bloomScale since we're working at lower resolution\n const heightScale = bloomHeight / 1080\n const blurRadius = this.radius * this.renderScale * heightScale\n\n // Update all uniforms BEFORE creating command encoder\n // (writeBuffer is immediate, commands are batched)\n device.queue.writeBuffer(this.extractUniformBuffer, 0, new Float32Array([\n this.threshold,\n this.softThreshold,\n this.intensity,\n this.emissiveBoost,\n this.maxBrightness,\n 0.0, 0.0, 0.0, // padding\n ]))\n\n // Diagonal blur uniforms (X-shaped glare)\n const diag = 0.7071067811865476 // 1/sqrt(2)\n device.queue.writeBuffer(this.blurUniformBufferH, 0, new Float32Array([\n diag, diag, // direction (diagonal: top-left to bottom-right)\n 1.0 / bloomWidth, 1.0 / bloomHeight, // texelSize (at bloom resolution)\n blurRadius, 0.0, // blurRadius, padding\n 0.0, 0.0, // more padding for alignment\n ]))\n\n // Second diagonal blur uniforms\n device.queue.writeBuffer(this.blurUniformBufferV, 0, new Float32Array([\n diag, -diag, // direction (diagonal: bottom-left to top-right)\n 1.0 / bloomWidth, 1.0 / bloomHeight, // texelSize (at bloom resolution)\n blurRadius, 0.0, // blurRadius, padding\n 0.0, 0.0, // more padding for alignment\n ]))\n\n const commandEncoder = device.createCommandEncoder({ label: 'Bloom Pass' })\n\n // ===== PASS 1: Extract bright pixels =====\n {\n const pass = commandEncoder.beginRenderPass({\n label: 'Bloom Extract',\n colorAttachments: [{\n view: this.brightTexture.view,\n clearValue: { r: 0, g: 0, b: 0, a: 0 },\n loadOp: 'clear',\n storeOp: 'store',\n }],\n })\n pass.setPipeline(this.extractPipeline)\n pass.setBindGroup(0, this.extractBindGroup)\n pass.draw(3)\n pass.end()\n }\n\n // ===== PASS 2: Diagonal blur (top-left to bottom-right) =====\n {\n const pass = commandEncoder.beginRenderPass({\n label: 'Bloom Blur Diag1',\n colorAttachments: [{\n view: this.blurTextureA.view,\n clearValue: { r: 0, g: 0, b: 0, a: 0 },\n loadOp: 'clear',\n storeOp: 'store',\n }],\n })\n pass.setPipeline(this.blurPipeline)\n pass.setBindGroup(0, this.blurBindGroupH)\n pass.draw(3)\n pass.end()\n }\n\n // ===== PASS 3: Diagonal blur (bottom-left to top-right) =====\n {\n const pass = commandEncoder.beginRenderPass({\n label: 'Bloom Blur Diag2',\n colorAttachments: [{\n view: this.blurTextureB.view,\n clearValue: { r: 0, g: 0, b: 0, a: 0 },\n loadOp: 'clear',\n storeOp: 'store',\n }],\n })\n pass.setPipeline(this.blurPipeline)\n pass.setBindGroup(0, this.blurBindGroupV)\n pass.draw(3)\n pass.end()\n }\n\n device.queue.submit([commandEncoder.finish()])\n }\n\n async _resize(width, height) {\n this.width = width\n this.height = height\n this._needsRebuild = true\n }\n\n _destroy() {\n if (this.brightTexture?.texture) this.brightTexture.texture.destroy()\n if (this.blurTextureA?.texture) this.blurTextureA.texture.destroy()\n if (this.blurTextureB?.texture) this.blurTextureB.texture.destroy()\n // Clean up any pending textures in all ring buffer slots\n for (const slot of this._pendingDestroyRing) {\n for (const tex of slot) {\n tex.destroy()\n }\n }\n this._pendingDestroyRing = [[], [], []]\n this.extractPipeline = null\n this.blurPipeline = null\n }\n\n /**\n * Get the final bloom texture (after blur)\n */\n getOutputTexture() {\n return this.blurTextureB\n }\n\n /**\n * Get the bright extraction texture (before blur)\n * Used by SSGITilePass for directional light accumulation\n */\n getBrightTexture() {\n return this.brightTexture\n }\n}\n\n\nexport { BloomPass }\n","// Shared lighting functions for deferred and forward rendering\n// This file is imported as a string and concatenated with other shaders\n\nconst PI = 3.14159;\nconst MAX_LIGHTS = 768;\nconst CASCADE_COUNT = 3;\nconst MAX_LIGHTS_PER_TILE = 256;\nconst MAX_SPOT_SHADOWS = 16;\n\n// Hard-coded spot shadow parameters\nconst SPOT_ATLAS_WIDTH: f32 = 2048.0;\nconst SPOT_ATLAS_HEIGHT: f32 = 2048.0;\nconst SPOT_TILE_SIZE: f32 = 512.0;\nconst SPOT_TILES_PER_ROW: i32 = 4;\nconst SPOT_FADE_START: f32 = 25.0;\nconst SPOT_MAX_DISTANCE: f32 = 30.0;\nconst SPOT_MIN_SHADOW: f32 = 0.5;\n\nstruct Light {\n enabled: u32,\n position: vec3f,\n color: vec4f,\n direction: vec3f,\n geom: vec4f,\n shadowIndex: i32,\n}\n\nstruct SpotShadowMatrices {\n matrices: array<mat4x4<f32>, MAX_SPOT_SHADOWS>,\n}\n\nstruct CascadeMatrices {\n matrices: array<mat4x4<f32>, CASCADE_COUNT>,\n}\n\n// ============================================\n// Environment mapping functions\n// ============================================\n\nfn SphToUV(n: vec3<f32>) -> vec2<f32> {\n var uv: vec2<f32>;\n uv.x = atan2(-n.x, n.z);\n uv.x = (uv.x + PI / 2.0) / (PI * 2.0) + PI * (28.670 / 360.0);\n uv.y = acos(n.y) / PI;\n return uv;\n}\n\nfn octEncode(n: vec3<f32>) -> vec2<f32> {\n var n2 = n / (abs(n.x) + abs(n.y) + abs(n.z));\n if (n2.y < 0.0) {\n let signX = select(-1.0, 1.0, n2.x >= 0.0);\n let signZ = select(-1.0, 1.0, n2.z >= 0.0);\n n2 = vec3f(\n (1.0 - abs(n2.z)) * signX,\n n2.y,\n (1.0 - abs(n2.x)) * signZ\n );\n }\n return n2.xz * 0.5 + 0.5;\n}\n\nfn octDecode(uv: vec2<f32>) -> vec3<f32> {\n var uv2 = uv * 2.0 - 1.0;\n var n = vec3f(uv2.x, 1.0 - abs(uv2.x) - abs(uv2.y), uv2.y);\n if (n.y < 0.0) {\n let signX = select(-1.0, 1.0, n.x >= 0.0);\n let signZ = select(-1.0, 1.0, n.z >= 0.0);\n n = vec3f(\n (1.0 - abs(n.z)) * signX,\n n.y,\n (1.0 - abs(n.x)) * signZ\n );\n }\n return normalize(n);\n}\n\n// ============================================\n// Noise sampling functions\n// ============================================\n\nfn vogelDiskSample(sampleIndex: i32, numSamples: i32, rotation: f32) -> vec2f {\n let goldenAngle = 2.399963229728653;\n let r = sqrt((f32(sampleIndex) + 0.5) / f32(numSamples));\n let theta = f32(sampleIndex) * goldenAngle + rotation;\n return vec2f(r * cos(theta), r * sin(theta));\n}\n\nfn buildOrthonormalBasis(n: vec3f) -> mat3x3<f32> {\n let up = select(vec3f(1.0, 0.0, 0.0), vec3f(0.0, 1.0, 0.0), abs(n.y) < 0.999);\n let tangent = normalize(cross(up, n));\n let bitangent = cross(n, tangent);\n return mat3x3<f32>(tangent, bitangent, n);\n}\n\n// ============================================\n// PBR BRDF functions\n// ============================================\n\nfn clampedDot(x: vec3<f32>, y: vec3<f32>) -> f32 {\n return clamp(dot(x, y), 0.0, 1.0);\n}\n\nfn F_Schlick(f0: vec3<f32>, f90: vec3<f32>, VdotH: f32) -> vec3<f32> {\n return f0 + (f90 - f0) * pow(clamp(1.0 - VdotH, 0.0, 1.0), 5.0);\n}\n\nfn V_GGX(NdotL: f32, NdotV: f32, alphaRoughness: f32) -> f32 {\n let alphaRoughnessSq = alphaRoughness * alphaRoughness;\n let GGXV = NdotL * sqrt(NdotV * NdotV * (1.0 - alphaRoughnessSq) + alphaRoughnessSq);\n let GGXL = NdotV * sqrt(NdotL * NdotL * (1.0 - alphaRoughnessSq) + alphaRoughnessSq);\n let GGX = GGXV + GGXL;\n if (GGX > 0.0) {\n return 0.5 / GGX;\n }\n return 0.0;\n}\n\nfn D_GGX(NdotH: f32, alphaRoughness: f32) -> f32 {\n let alphaRoughnessSq = alphaRoughness * alphaRoughness;\n let f = (NdotH * NdotH) * (alphaRoughnessSq - 1.0) + 1.0;\n return alphaRoughnessSq / (PI * f * f);\n}\n\nfn BRDF_lambertian(f0: vec3<f32>, f90: vec3<f32>, diffuseColor: vec3<f32>, specularWeight: f32, VdotH: f32) -> vec3<f32> {\n return (1.0 - specularWeight * F_Schlick(f0, f90, VdotH)) * (diffuseColor / PI);\n}\n\nfn BRDF_specularGGX(f0: vec3<f32>, f90: vec3<f32>, alphaRoughness: f32, specularWeight: f32, VdotH: f32, NdotL: f32, NdotV: f32, NdotH: f32) -> vec3<f32> {\n let F = F_Schlick(f0, f90, VdotH);\n let Vis = V_GGX(NdotL, NdotV, alphaRoughness);\n let D = D_GGX(NdotH, alphaRoughness);\n return specularWeight * F * Vis * D;\n}\n\n// ============================================\n// Squircle distance for cascade blending\n// ============================================\n\nfn squircleDistanceXZ(offset: vec2f, size: f32) -> f32 {\n let normalized = offset / size;\n let absNorm = abs(normalized);\n return pow(pow(absNorm.x, 4.0) + pow(absNorm.y, 4.0), 0.25);\n}\n","import { BasePass } from \"./BasePass.js\"\nimport { Texture } from \"../../Texture.js\"\nimport { Frustum } from \"../../utils/Frustum.js\"\n\nimport lightingCommonWGSL from \"../shaders/lighting_common.wgsl\"\n\n/**\n * TransparentPass - Forward rendering pass for transparent objects\n *\n * Renders transparent meshes with full PBR lighting on top of the HDR buffer.\n * Uses back-to-front sorting for correct alpha blending.\n * Reads depth from GBuffer for occlusion testing but does NOT write depth,\n * so fog pass uses scene depth (what's behind transparent objects).\n */\nclass TransparentPass extends BasePass {\n constructor(engine = null) {\n super('Transparent', engine)\n\n this.pipeline = null\n this.bindGroupLayout = null\n this.uniformBuffer = null\n this.pipelineCache = new Map()\n\n // References to shared resources\n this.gbuffer = null\n this.lightingUniforms = null\n this.shadowPass = null\n this.environmentMap = null\n this.noiseTexture = null\n this.noiseSize = 64\n this.noiseAnimated = true\n\n // Light buffers\n this.tileLightBuffer = null\n this.lightBuffer = null\n\n // HiZ pass reference for occlusion culling\n this.hizPass = null\n\n // Frustum for transparent mesh culling\n this.frustum = new Frustum()\n\n // Distance fade for preventing object popping at culling distance\n this.distanceFadeStart = 0 // Distance where fade begins\n this.distanceFadeEnd = 0 // Distance where fade completes (0 = disabled)\n\n // Culling stats for transparent meshes\n this.cullingStats = {\n total: 0,\n rendered: 0,\n culledByFrustum: 0,\n culledByDistance: 0,\n culledByOcclusion: 0\n }\n }\n\n /**\n * Set the HiZ pass for occlusion culling\n * @param {HiZPass} hizPass - The HiZ pass instance\n */\n setHiZPass(hizPass) {\n this.hizPass = hizPass\n }\n\n /**\n * Test if a transparent mesh should be culled\n * @param {Mesh} mesh - The mesh to test\n * @param {Camera} camera - Current camera\n * @param {boolean} canCull - Whether frustum/occlusion culling is available\n * @returns {string|null} - Reason for culling or null if visible\n */\n _shouldCullMesh(mesh, camera, canCull) {\n // Transparent mesh culling is disabled - same issues as legacy mesh culling\n // where bounding spheres may be in local space or represent root nodes\n return null\n }\n\n async _init() {\n const { device } = this.engine\n\n // Create uniform buffer (same layout as lighting pass + material params)\n this.uniformBuffer = device.createBuffer({\n size: 512, // Plenty of space for uniforms\n usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST,\n label: 'Transparent Uniforms'\n })\n\n // Create placeholder resources for missing bindings\n await this._createPlaceholders()\n }\n\n /**\n * Set the GBuffer for depth testing\n */\n setGBuffer(gbuffer) {\n this.gbuffer = gbuffer\n }\n\n /**\n * Set the HDR output texture to render onto\n */\n setOutputTexture(texture) {\n this.outputTexture = texture\n }\n\n /**\n * Set shadow pass reference\n */\n setShadowPass(shadowPass) {\n this.shadowPass = shadowPass\n }\n\n /**\n * Set environment map\n */\n setEnvironmentMap(envMap, encoding = 'equirect') {\n this.environmentMap = envMap\n this.environmentEncoding = encoding\n }\n\n /**\n * Set noise texture for effects\n */\n setNoise(noise, size = 64, animated = true) {\n this.noiseTexture = noise\n this.noiseSize = size\n this.noiseAnimated = animated\n }\n\n /**\n * Set light buffers for tiled lighting\n */\n setLightBuffers(tileLightBuffer, lightBuffer) {\n this.tileLightBuffer = tileLightBuffer\n this.lightBuffer = lightBuffer\n }\n\n /**\n * Create or get pipeline for a mesh\n */\n async _getOrCreatePipeline(mesh) {\n const { device } = this.engine\n const key = `transparent_${mesh.material.uid}`\n\n if (this.pipelineCache.has(key)) {\n return this.pipelineCache.get(key)\n }\n\n // Build the shader\n const shaderCode = this._buildShaderCode()\n\n const shaderModule = device.createShaderModule({\n label: 'Transparent Shader',\n code: shaderCode\n })\n\n // Bind group layout\n const bindGroupLayout = device.createBindGroupLayout({\n label: 'Transparent BindGroup Layout',\n entries: [\n // Uniforms\n { binding: 0, visibility: GPUShaderStage.VERTEX | GPUShaderStage.FRAGMENT, buffer: { type: 'uniform' } },\n // Albedo texture\n { binding: 1, visibility: GPUShaderStage.FRAGMENT, texture: { sampleType: 'float' } },\n { binding: 2, visibility: GPUShaderStage.FRAGMENT, sampler: { type: 'filtering' } },\n // Normal texture\n { binding: 3, visibility: GPUShaderStage.FRAGMENT, texture: { sampleType: 'float' } },\n { binding: 4, visibility: GPUShaderStage.FRAGMENT, sampler: { type: 'filtering' } },\n // ARM texture\n { binding: 5, visibility: GPUShaderStage.FRAGMENT, texture: { sampleType: 'float' } },\n { binding: 6, visibility: GPUShaderStage.FRAGMENT, sampler: { type: 'filtering' } },\n // Environment map\n { binding: 7, visibility: GPUShaderStage.FRAGMENT, texture: { sampleType: 'float' } },\n { binding: 8, visibility: GPUShaderStage.FRAGMENT, sampler: { type: 'filtering' } },\n // Shadow map array (cascades)\n { binding: 9, visibility: GPUShaderStage.FRAGMENT, texture: { sampleType: 'depth', viewDimension: '2d-array' } },\n { binding: 10, visibility: GPUShaderStage.FRAGMENT, sampler: { type: 'comparison' } },\n // Cascade matrices\n { binding: 11, visibility: GPUShaderStage.FRAGMENT, buffer: { type: 'read-only-storage' } },\n // Noise texture\n { binding: 12, visibility: GPUShaderStage.FRAGMENT, texture: { sampleType: 'float' } },\n ]\n })\n\n const pipelineLayout = device.createPipelineLayout({\n bindGroupLayouts: [bindGroupLayout]\n })\n\n // Vertex buffer layout (same as geometry pass)\n const vertexBufferLayout = {\n arrayStride: 80,\n attributes: [\n { format: \"float32x3\", offset: 0, shaderLocation: 0 }, // position\n { format: \"float32x2\", offset: 12, shaderLocation: 1 }, // uv\n { format: \"float32x3\", offset: 20, shaderLocation: 2 }, // normal\n { format: \"float32x4\", offset: 32, shaderLocation: 3 }, // color\n { format: \"float32x4\", offset: 48, shaderLocation: 4 }, // weights\n { format: \"uint32x4\", offset: 64, shaderLocation: 5 }, // joints\n ],\n stepMode: 'vertex'\n }\n\n const instanceBufferLayout = {\n arrayStride: 112, // 28 floats: matrix(16) + posRadius(4) + uvTransform(4) + color(4)\n stepMode: 'instance',\n attributes: [\n { format: \"float32x4\", offset: 0, shaderLocation: 6 },\n { format: \"float32x4\", offset: 16, shaderLocation: 7 },\n { format: \"float32x4\", offset: 32, shaderLocation: 8 },\n { format: \"float32x4\", offset: 48, shaderLocation: 9 },\n { format: \"float32x4\", offset: 64, shaderLocation: 10 },\n { format: \"float32x4\", offset: 80, shaderLocation: 11 }, // uvTransform\n { format: \"float32x4\", offset: 96, shaderLocation: 12 }, // color\n ]\n }\n\n const pipeline = await device.createRenderPipelineAsync({\n label: 'Transparent Pipeline',\n layout: pipelineLayout,\n vertex: {\n module: shaderModule,\n entryPoint: 'vertexMain',\n buffers: [vertexBufferLayout, instanceBufferLayout]\n },\n fragment: {\n module: shaderModule,\n entryPoint: 'fragmentMain',\n targets: [{\n format: 'rgba16float',\n blend: {\n color: {\n srcFactor: 'src-alpha',\n dstFactor: 'one-minus-src-alpha',\n operation: 'add'\n },\n alpha: {\n srcFactor: 'one',\n dstFactor: 'one-minus-src-alpha',\n operation: 'add'\n }\n }\n }]\n },\n depthStencil: {\n format: 'depth32float',\n depthWriteEnabled: false, // Don't write depth - fog needs scene depth behind transparent objects\n depthCompare: 'less',\n },\n primitive: {\n topology: 'triangle-list',\n cullMode: 'none', // No culling for transparent objects\n }\n })\n\n this.pipelineCache.set(key, { pipeline, bindGroupLayout })\n return { pipeline, bindGroupLayout }\n }\n\n /**\n * Build the forward transparent shader\n */\n _buildShaderCode() {\n return `\n${lightingCommonWGSL}\n\nstruct TransparentUniforms {\n viewMatrix: mat4x4f,\n projectionMatrix: mat4x4f,\n cameraPosition: vec3f,\n opacity: f32,\n lightDir: vec3f,\n lightIntensity: f32,\n lightColor: vec3f,\n ambientIntensity: f32,\n ambientColor: vec3f,\n envMipCount: f32,\n envDiffuse: f32,\n envSpecular: f32,\n exposure: f32,\n envEncoding: f32, // 0=equirect, 1=octahedral\n shadowBias: f32,\n shadowNormalBias: f32,\n shadowStrength: f32,\n cascadeSize0: f32,\n cascadeSize1: f32,\n cascadeSize2: f32,\n noiseSize: f32,\n noiseOffsetX: f32,\n noiseOffsetY: f32,\n distanceFadeStart: f32, // Distance where fade begins\n distanceFadeEnd: f32, // Distance where fade completes (0 = disabled)\n fogEnabled: f32, // Whether fog is enabled\n fogPad1: f32, // Padding for vec3 alignment\n fogPad2: f32,\n fogPad3: f32,\n fogColor: vec3f, // Fog color\n fogBrightResist: f32, // HDR brightness resistance\n fogDistances: vec3f, // Distance thresholds [near, mid, far]\n fogPad4: f32,\n fogAlphas: vec3f, // Alpha values at distances\n fogPad5: f32,\n fogHeightFade: vec2f, // [bottomY, topY]\n cameraNear: f32,\n cameraFar: f32,\n}\n\nstruct VertexInput {\n @location(0) position: vec3f,\n @location(1) uv: vec2f,\n @location(2) normal: vec3f,\n @location(3) color: vec4f,\n @location(4) weights: vec4f,\n @location(5) joints: vec4u,\n @location(6) model0: vec4f,\n @location(7) model1: vec4f,\n @location(8) model2: vec4f,\n @location(9) model3: vec4f,\n @location(10) posRadius: vec4f,\n}\n\nstruct VertexOutput {\n @builtin(position) position: vec4f,\n @location(0) worldPos: vec3f,\n @location(1) uv: vec2f,\n @location(2) normal: vec3f,\n}\n\n@group(0) @binding(0) var<uniform> uniforms: TransparentUniforms;\n@group(0) @binding(1) var albedoTexture: texture_2d<f32>;\n@group(0) @binding(2) var albedoSampler: sampler;\n@group(0) @binding(3) var normalTexture: texture_2d<f32>;\n@group(0) @binding(4) var normalSampler: sampler;\n@group(0) @binding(5) var armTexture: texture_2d<f32>;\n@group(0) @binding(6) var armSampler: sampler;\n@group(0) @binding(7) var envTexture: texture_2d<f32>;\n@group(0) @binding(8) var envSampler: sampler;\n@group(0) @binding(9) var shadowMapArray: texture_depth_2d_array;\n@group(0) @binding(10) var shadowSampler: sampler_comparison;\n@group(0) @binding(11) var<storage, read> cascadeMatrices: CascadeMatrices;\n@group(0) @binding(12) var noiseTexture: texture_2d<f32>;\n\nfn getEnvUV(dir: vec3f) -> vec2f {\n if (uniforms.envEncoding > 0.5) {\n return octEncode(dir);\n }\n return SphToUV(dir);\n}\n\nfn sampleNoise(screenPos: vec2f) -> f32 {\n let noiseSize = i32(uniforms.noiseSize);\n let noiseOffsetX = i32(uniforms.noiseOffsetX * f32(noiseSize));\n let noiseOffsetY = i32(uniforms.noiseOffsetY * f32(noiseSize));\n let texCoord = vec2i(\n (i32(screenPos.x) + noiseOffsetX) % noiseSize,\n (i32(screenPos.y) + noiseOffsetY) % noiseSize\n );\n return textureLoad(noiseTexture, texCoord, 0).r;\n}\n\nfn getNoiseJitter(screenPos: vec2f) -> vec2f {\n let noiseSize = i32(uniforms.noiseSize);\n let noiseOffsetX = i32(uniforms.noiseOffsetX * f32(noiseSize));\n let noiseOffsetY = i32(uniforms.noiseOffsetY * f32(noiseSize));\n let texCoord1 = vec2i(\n (i32(screenPos.x) + noiseOffsetX) % noiseSize,\n (i32(screenPos.y) + noiseOffsetY) % noiseSize\n );\n let texCoord2 = vec2i(\n (i32(screenPos.x) + 37 + noiseOffsetX) % noiseSize,\n (i32(screenPos.y) + 17 + noiseOffsetY) % noiseSize\n );\n let n1 = textureLoad(noiseTexture, texCoord1, 0).r;\n let n2 = textureLoad(noiseTexture, texCoord2, 0).r;\n return vec2f(n1, n2) * 2.0 - 1.0;\n}\n\nfn getIBLSample(reflection: vec3f, lod: f32) -> vec3f {\n let envRGBE = textureSampleLevel(envTexture, envSampler, getEnvUV(reflection), lod);\n let envColor = envRGBE.rgb * pow(2.0, envRGBE.a * 255.0 - 128.0);\n return envColor;\n}\n\n// Sample cascade shadow\nfn sampleCascadeShadow(worldPos: vec3f, normal: vec3f, cascadeIndex: i32, screenPos: vec2f) -> f32 {\n let bias = uniforms.shadowBias;\n let normalBias = uniforms.shadowNormalBias;\n let biasedPos = worldPos + normal * normalBias;\n let lightMatrix = cascadeMatrices.matrices[cascadeIndex];\n let lightSpacePos = lightMatrix * vec4f(biasedPos, 1.0);\n let projCoords = lightSpacePos.xyz / lightSpacePos.w;\n let shadowUV = vec2f(projCoords.x * 0.5 + 0.5, 0.5 - projCoords.y * 0.5);\n let currentDepth = projCoords.z - bias;\n\n if (shadowUV.x < 0.0 || shadowUV.x > 1.0 || shadowUV.y < 0.0 || shadowUV.y > 1.0) {\n return 1.0;\n }\n\n // Simple shadow sampling with jitter\n let jitter = getNoiseJitter(screenPos);\n let texelSize = 1.0 / 2048.0;\n var shadow = 0.0;\n for (var i = 0; i < 4; i++) {\n let offset = vogelDiskSample(i, 4, jitter.x * PI) * texelSize * 2.0;\n shadow += textureSampleCompareLevel(shadowMapArray, shadowSampler, shadowUV + offset, cascadeIndex, currentDepth);\n }\n return shadow / 4.0;\n}\n\nfn calculateShadow(worldPos: vec3f, normal: vec3f, screenPos: vec2f) -> f32 {\n let camXZ = vec2f(uniforms.cameraPosition.x, uniforms.cameraPosition.z);\n let posXZ = vec2f(worldPos.x, worldPos.z);\n let offsetXZ = posXZ - camXZ;\n\n let dist0 = squircleDistanceXZ(offsetXZ, uniforms.cascadeSize0);\n let dist1 = squircleDistanceXZ(offsetXZ, uniforms.cascadeSize1);\n let dist2 = squircleDistanceXZ(offsetXZ, uniforms.cascadeSize2);\n\n var shadow = 1.0;\n if (dist0 < 1.0) {\n shadow = sampleCascadeShadow(worldPos, normal, 0, screenPos);\n } else if (dist1 < 1.0) {\n shadow = sampleCascadeShadow(worldPos, normal, 1, screenPos);\n } else if (dist2 < 1.0) {\n shadow = sampleCascadeShadow(worldPos, normal, 2, screenPos);\n }\n\n return mix(1.0 - uniforms.shadowStrength, 1.0, shadow);\n}\n\n@vertex\nfn vertexMain(input: VertexInput) -> VertexOutput {\n var output: VertexOutput;\n\n let modelMatrix = mat4x4f(\n input.model0,\n input.model1,\n input.model2,\n input.model3\n );\n\n let worldPos = (modelMatrix * vec4f(input.position, 1.0)).xyz;\n let viewPos = uniforms.viewMatrix * vec4f(worldPos, 1.0);\n output.position = uniforms.projectionMatrix * viewPos;\n output.worldPos = worldPos;\n output.uv = input.uv;\n\n let normalMatrix = mat3x3f(\n modelMatrix[0].xyz,\n modelMatrix[1].xyz,\n modelMatrix[2].xyz\n );\n output.normal = normalize(normalMatrix * input.normal);\n\n return output;\n}\n\n@fragment\nfn fragmentMain(input: VertexOutput) -> @location(0) vec4f {\n // Sample textures\n let albedoSample = textureSample(albedoTexture, albedoSampler, input.uv);\n let armSample = textureSample(armTexture, armSampler, input.uv);\n\n // Calculate alpha (albedo alpha * material opacity)\n var alpha = albedoSample.a * uniforms.opacity;\n\n // Distance fade: modulate alpha to prevent popping at culling distance\n if (uniforms.distanceFadeEnd > 0.0) {\n let distToCamera = length(input.worldPos - uniforms.cameraPosition);\n if (distToCamera >= uniforms.distanceFadeEnd) {\n discard; // Beyond fade end - fully invisible\n }\n if (distToCamera > uniforms.distanceFadeStart) {\n // Calculate fade factor: 1.0 at fadeStart, 0.0 at fadeEnd\n let fadeRange = uniforms.distanceFadeEnd - uniforms.distanceFadeStart;\n let fadeFactor = 1.0 - (distToCamera - uniforms.distanceFadeStart) / fadeRange;\n alpha *= fadeFactor;\n }\n }\n\n // Discard very transparent fragments\n if (alpha < 0.01) {\n discard;\n }\n\n let baseColor = albedoSample.rgb;\n let ao = armSample.r;\n let roughness = max(armSample.g, 0.04);\n let metallic = armSample.b;\n let alphaRoughness = roughness * roughness;\n\n // Normal mapping\n let normalSample = textureSample(normalTexture, normalSampler, input.uv).rgb;\n let tangentNormal = normalize(normalSample * 2.0 - 1.0);\n let N = normalize(input.normal);\n let refVec = select(vec3f(0.0, 1.0, 0.0), vec3f(1.0, 0.0, 0.0), abs(N.y) > 0.9);\n let T = normalize(cross(N, refVec));\n let B = cross(N, T);\n let TBN = mat3x3f(T, B, N);\n let n = normalize(TBN * tangentNormal);\n\n // View direction\n let v = normalize(uniforms.cameraPosition - input.worldPos);\n let NdotV = clampedDot(n, v);\n\n // PBR setup\n var f0 = vec3f(0.04);\n f0 = mix(f0, baseColor, metallic);\n let f90 = vec3f(1.0);\n let c_diff = mix(baseColor, vec3f(0.0), metallic);\n let specularWeight = 1.0;\n\n var diffuse = vec3f(0.0);\n var specular = vec3f(0.0);\n\n // Ambient lighting\n diffuse += ao * uniforms.ambientColor * uniforms.ambientIntensity * BRDF_lambertian(f0, f90, c_diff, specularWeight, 1.0);\n\n // Environment diffuse\n let envDiffuseSample = getIBLSample(n, uniforms.envMipCount - 2.0);\n diffuse += ao * uniforms.envDiffuse * envDiffuseSample * BRDF_lambertian(f0, f90, c_diff, specularWeight, 1.0);\n\n // Environment specular\n let reflection = normalize(reflect(-v, n));\n let lod = roughness * (uniforms.envMipCount - 1.0);\n let Fr = max(vec3f(1.0 - roughness), f0) - f0;\n let k_S = f0 + Fr * pow(1.0 - NdotV, 5.0);\n let envSpecSample = getIBLSample(reflection, lod);\n specular += ao * uniforms.envSpecular * envSpecSample * k_S;\n\n // Directional light\n let l = normalize(uniforms.lightDir);\n let h = normalize(l + v);\n let NdotL = clampedDot(n, l);\n let NdotH = clampedDot(n, h);\n let VdotH = clampedDot(v, h);\n\n let shadow = calculateShadow(input.worldPos, n, input.position.xy);\n let lightContrib = uniforms.lightIntensity * uniforms.lightColor * NdotL * shadow;\n\n diffuse += lightContrib * BRDF_lambertian(f0, f90, c_diff, specularWeight, VdotH);\n specular += lightContrib * BRDF_specularGGX(f0, f90, alphaRoughness, specularWeight, VdotH, NdotL, NdotV, NdotH);\n\n // Final color\n var color = (diffuse + specular) * uniforms.exposure;\n\n // Fog is applied as post-process after all transparent/particle rendering\n\n // For glass-like materials, reduce color intensity based on transparency\n // More transparent = more of background shows through\n color *= alpha;\n\n return vec4f(color, alpha);\n}\n`\n }\n\n /**\n * Execute the transparent pass\n */\n async _execute(context) {\n const { device, canvas, stats } = this.engine\n const { camera, meshes, mainLight } = context\n\n // Initialize transparent stats\n stats.transparentDrawCalls = 0\n stats.transparentTriangles = 0\n\n if (!this.outputTexture || !this.gbuffer) {\n return\n }\n\n // Update frustum for transparent mesh culling (only if camera has required properties)\n const canCull = camera.view && camera.proj && camera.position && camera.direction\n if (canCull) {\n const fovRadians = (camera.fov || 60) * (Math.PI / 180)\n this.frustum.update(\n camera.view,\n camera.proj,\n camera.position,\n camera.direction,\n fovRadians,\n camera.aspect || (canvas.width / canvas.height),\n camera.near || 0.05,\n camera.far || 1000,\n canvas.width,\n canvas.height\n )\n }\n\n // Reset culling stats\n this.cullingStats.total = 0\n this.cullingStats.rendered = 0\n this.cullingStats.culledByFrustum = 0\n this.cullingStats.culledByDistance = 0\n this.cullingStats.culledByOcclusion = 0\n\n // Collect transparent meshes with culling\n const transparentMeshes = []\n for (const name in meshes) {\n const mesh = meshes[name]\n if (mesh.material?.transparent && mesh.geometry?.instanceCount > 0) {\n this.cullingStats.total++\n\n // Apply culling (frustum, distance, occlusion)\n const cullReason = this._shouldCullMesh(mesh, camera, canCull)\n if (cullReason) {\n if (cullReason === 'frustum') this.cullingStats.culledByFrustum++\n else if (cullReason === 'distance') this.cullingStats.culledByDistance++\n else if (cullReason === 'occlusion') this.cullingStats.culledByOcclusion++\n continue\n }\n\n this.cullingStats.rendered++\n transparentMeshes.push({ name, mesh })\n }\n }\n\n if (transparentMeshes.length === 0) {\n return\n }\n\n // Sort back-to-front by distance to camera\n transparentMeshes.sort((a, b) => {\n // Get center position from first instance (simplified)\n const aPos = a.mesh.geometry.instanceData ?\n [a.mesh.geometry.instanceData[12], a.mesh.geometry.instanceData[13], a.mesh.geometry.instanceData[14]] :\n [0, 0, 0]\n const bPos = b.mesh.geometry.instanceData ?\n [b.mesh.geometry.instanceData[12], b.mesh.geometry.instanceData[13], b.mesh.geometry.instanceData[14]] :\n [0, 0, 0]\n\n const aDist = (aPos[0] - camera.position[0]) ** 2 +\n (aPos[1] - camera.position[1]) ** 2 +\n (aPos[2] - camera.position[2]) ** 2\n const bDist = (bPos[0] - camera.position[0]) ** 2 +\n (bPos[1] - camera.position[1]) ** 2 +\n (bPos[2] - camera.position[2]) ** 2\n\n return bDist - aDist // Back to front\n })\n\n // Render each transparent mesh\n const commandEncoder = device.createCommandEncoder({ label: 'Transparent Pass' })\n\n const passEncoder = commandEncoder.beginRenderPass({\n colorAttachments: [{\n view: this.outputTexture.view,\n loadOp: 'load', // Preserve existing content\n storeOp: 'store',\n }],\n depthStencilAttachment: {\n view: this.gbuffer.depth.view,\n depthLoadOp: 'load',\n depthStoreOp: 'store',\n }\n })\n\n for (const { mesh } of transparentMeshes) {\n const { pipeline, bindGroupLayout } = await this._getOrCreatePipeline(mesh)\n\n // Update uniforms\n const uniformData = new Float32Array(96)\n uniformData.set(camera.view, 0)\n uniformData.set(camera.proj, 16)\n uniformData.set(camera.position, 32)\n uniformData[35] = mesh.material.opacity ?? 1.0\n\n // Light direction\n const lightDir = mainLight?.direction || [-1, 1, -0.5]\n uniformData.set(lightDir, 36)\n uniformData[39] = mainLight?.color?.[3] ?? 1.0 // intensity\n\n // Light color\n const lightColor = mainLight?.color || [1, 1, 1]\n uniformData.set(lightColor, 40)\n uniformData[43] = this.settings?.environment?.ambientIntensity ?? 0.3\n\n // Ambient color\n const ambientColor = this.settings?.environment?.ambientColor || [0.5, 0.5, 0.6]\n uniformData.set(ambientColor, 44)\n uniformData[47] = this.settings?.environment?.envMipCount ?? 8 // envMipCount\n\n // Environment params\n uniformData[48] = this.settings?.environment?.diffuseLevel ?? 0.5\n uniformData[49] = this.settings?.environment?.specularLevel ?? 1.0\n uniformData[50] = this.settings?.environment?.exposure ?? 1.0\n uniformData[51] = this.environmentEncoding === 'octahedral' ? 1.0 : 0.0\n\n // Shadow params\n uniformData[52] = this.settings?.shadow?.bias ?? 0.001\n uniformData[53] = this.settings?.shadow?.normalBias ?? 0.02\n uniformData[54] = this.settings?.shadow?.strength ?? 0.7\n\n // Cascade sizes\n const cascadeSizes = this.shadowPass?.getCascadeSizes() || [20, 60, 300]\n uniformData[55] = cascadeSizes[0]\n uniformData[56] = cascadeSizes[1]\n uniformData[57] = cascadeSizes[2]\n\n // Noise params\n uniformData[58] = this.noiseSize\n uniformData[59] = this.noiseAnimated ? Math.random() : 0\n uniformData[60] = this.noiseAnimated ? Math.random() : 0\n\n // Distance fade params\n uniformData[61] = this.distanceFadeStart\n uniformData[62] = this.distanceFadeEnd\n\n // Fog params\n const fogSettings = this.settings?.environment?.fog\n uniformData[63] = fogSettings?.enabled ? 1.0 : 0.0\n // Padding at 64-66 for vec3 alignment (indices 64, 65, 66 are pad)\n // fogColor at indices 68-70 (aligned to 16 bytes at byte 272)\n const fogColor = fogSettings?.color ?? [0.8, 0.85, 0.9]\n uniformData[68] = fogColor[0]\n uniformData[69] = fogColor[1]\n uniformData[70] = fogColor[2]\n uniformData[71] = fogSettings?.brightResist ?? 0.8\n // fogDistances at indices 72-74\n const fogDistances = fogSettings?.distances ?? [0, 50, 200]\n uniformData[72] = fogDistances[0]\n uniformData[73] = fogDistances[1]\n uniformData[74] = fogDistances[2]\n // fogAlphas at indices 76-78\n const fogAlphas = fogSettings?.alpha ?? [0, 0.3, 0.8]\n uniformData[76] = fogAlphas[0]\n uniformData[77] = fogAlphas[1]\n uniformData[78] = fogAlphas[2]\n // fogHeightFade at indices 80-81\n const fogHeightFade = fogSettings?.heightFade ?? [-10, 100]\n uniformData[80] = fogHeightFade[0]\n uniformData[81] = fogHeightFade[1]\n // cameraNear/Far at indices 82-83\n uniformData[82] = camera.near || 0.1\n uniformData[83] = camera.far || 1000\n\n device.queue.writeBuffer(this.uniformBuffer, 0, uniformData)\n\n // Create bind group\n const bindGroup = device.createBindGroup({\n layout: bindGroupLayout,\n entries: [\n { binding: 0, resource: { buffer: this.uniformBuffer } },\n { binding: 1, resource: mesh.material.textures[0]?.view || this._placeholderTexture.view },\n { binding: 2, resource: mesh.material.textures[0]?.sampler || this._placeholderSampler },\n { binding: 3, resource: mesh.material.textures[1]?.view || this._placeholderNormal.view },\n { binding: 4, resource: mesh.material.textures[1]?.sampler || this._placeholderSampler },\n { binding: 5, resource: mesh.material.textures[3]?.view || this._placeholderTexture.view },\n { binding: 6, resource: mesh.material.textures[3]?.sampler || this._placeholderSampler },\n { binding: 7, resource: this.environmentMap?.view || this._placeholderTexture.view },\n { binding: 8, resource: this.environmentMap?.sampler || this._placeholderSampler },\n { binding: 9, resource: this.shadowPass?.getShadowMapView() || this._placeholderDepth.view },\n { binding: 10, resource: this.shadowPass?.getShadowSampler() || this._placeholderComparisonSampler },\n { binding: 11, resource: { buffer: this.shadowPass?.getCascadeMatricesBuffer() || this._placeholderBuffer } },\n { binding: 12, resource: this.noiseTexture?.view || this._placeholderTexture.view },\n ]\n })\n\n // Update geometry\n mesh.geometry.update()\n\n // Render\n passEncoder.setPipeline(pipeline)\n passEncoder.setBindGroup(0, bindGroup)\n passEncoder.setVertexBuffer(0, mesh.geometry.vertexBuffer)\n passEncoder.setVertexBuffer(1, mesh.geometry.instanceBuffer)\n passEncoder.setIndexBuffer(mesh.geometry.indexBuffer, 'uint32')\n passEncoder.drawIndexed(mesh.geometry.indexArray.length, mesh.geometry.instanceCount)\n\n // Track stats\n stats.transparentDrawCalls++\n stats.transparentTriangles += (mesh.geometry.indexArray.length / 3) * mesh.geometry.instanceCount\n }\n\n passEncoder.end()\n device.queue.submit([commandEncoder.finish()])\n }\n\n /**\n * Create placeholder resources for missing bindings\n */\n async _createPlaceholders() {\n const { device } = this.engine\n\n // 1x1 white texture\n this._placeholderTexture = await Texture.fromRGBA(this.engine, 1, 1, 1, 1)\n\n // 1x1 normal texture (pointing up)\n this._placeholderNormal = await Texture.fromColor(this.engine, \"#8080FF\")\n\n // Sampler\n this._placeholderSampler = device.createSampler({\n magFilter: 'linear',\n minFilter: 'linear',\n })\n\n // Comparison sampler\n this._placeholderComparisonSampler = device.createSampler({\n compare: 'less',\n magFilter: 'linear',\n minFilter: 'linear',\n })\n\n // 1x1 depth texture\n this._placeholderDepthTexture = device.createTexture({\n size: [1, 1, 1],\n format: 'depth32float',\n usage: GPUTextureUsage.TEXTURE_BINDING,\n dimension: '2d',\n })\n this._placeholderDepth = {\n view: this._placeholderDepthTexture.createView({ dimension: '2d-array', arrayLayerCount: 1 })\n }\n\n // Placeholder buffer\n this._placeholderBuffer = device.createBuffer({\n size: 256,\n usage: GPUBufferUsage.STORAGE,\n })\n }\n\n async _resize(width, height) {\n // Nothing to resize\n }\n\n _destroy() {\n this.pipelineCache.clear()\n }\n}\n\nexport { TransparentPass }\n","// Volumetric Fog - Simple Ray Marching with Direct Shadow Sampling\n// Supports main directional light + point/spot lights with shadows\n\nconst MAX_LIGHTS: u32 = 768u;\nconst MAX_SPOT_SHADOWS: i32 = 16;\nconst SPOT_ATLAS_SIZE: f32 = 2048.0;\nconst SPOT_TILE_SIZE: f32 = 512.0;\nconst SPOT_TILES_PER_ROW: i32 = 4;\n\nstruct Uniforms {\n inverseProjection: mat4x4f,\n inverseView: mat4x4f,\n cameraPosition: vec3f,\n nearPlane: f32,\n farPlane: f32,\n maxSamples: f32,\n time: f32,\n fogDensity: f32,\n fogColor: vec3f,\n shadowsEnabled: f32,\n mainLightDir: vec3f,\n mainLightIntensity: f32,\n mainLightColor: vec3f,\n scatterStrength: f32,\n fogHeightFade: vec2f,\n maxDistance: f32,\n lightCount: f32,\n debugMode: f32,\n noiseStrength: f32, // 0 = uniform fog, 1 = full noise variation\n noiseAnimated: f32, // 0 = static noise, 1 = animated\n mainLightScatter: f32, // Separate scatter strength for main directional light\n noiseScale: f32, // Noise frequency multiplier (higher = smaller details)\n mainLightSaturation: f32, // Max brightness for main light (logarithmic cap)\n}\n\n// Light struct must match LightingPass buffer layout (96 bytes per light)\nstruct Light {\n enabled: u32, // offset 0\n _pad0: u32, // offset 4\n _pad1: u32, // offset 8\n _pad2: u32, // offset 12\n position: vec3f, // offset 16 (vec3f has 16-byte alignment)\n _pad3: f32, // offset 28\n color: vec4f, // offset 32 (rgb + intensity in alpha)\n direction: vec3f, // offset 48\n _pad4: f32, // offset 60\n geom: vec4f, // offset 64 (x = radius, y = inner cone, z = outer cone)\n shadowIndex: i32, // offset 80 (-1 if no shadow, 0-15 for spot shadow slot)\n _pad5: u32, // offset 84\n _pad6: u32, // offset 88\n _pad7: u32, // offset 92\n}\n\nstruct VertexOutput {\n @builtin(position) position: vec4f,\n @location(0) uv: vec2f,\n}\n\n@group(0) @binding(0) var<uniform> uniforms: Uniforms;\n@group(0) @binding(1) var depthTexture: texture_depth_2d;\n@group(0) @binding(2) var cascadeShadowMaps: texture_depth_2d_array;\n@group(0) @binding(3) var shadowSampler: sampler_comparison;\n@group(0) @binding(4) var<storage, read> cascadeMatrices: array<mat4x4f>;\n@group(0) @binding(5) var<storage, read> lights: array<Light, MAX_LIGHTS>;\n@group(0) @binding(6) var spotShadowAtlas: texture_depth_2d;\n@group(0) @binding(7) var<storage, read> spotMatrices: array<mat4x4f, MAX_SPOT_SHADOWS>;\n\n// Full-screen triangle vertex shader\n@vertex\nfn vertexMain(@builtin(vertex_index) vertexIndex: u32) -> VertexOutput {\n var output: VertexOutput;\n let x = f32(vertexIndex & 1u) * 4.0 - 1.0;\n let y = f32(vertexIndex >> 1u) * 4.0 - 1.0;\n output.position = vec4f(x, y, 0.0, 1.0);\n output.uv = vec2f((x + 1.0) * 0.5, (1.0 - y) * 0.5);\n return output;\n}\n\n// Simple 3D noise for fog variation\nfn hash(p: vec3f) -> f32 {\n var p3 = fract(p * 0.1031);\n p3 += dot(p3, p3.yzx + 33.33);\n return fract((p3.x + p3.y) * p3.z);\n}\n\nfn noise3d(p: vec3f) -> f32 {\n let i = floor(p);\n let f = fract(p);\n let u = f * f * (3.0 - 2.0 * f);\n\n return mix(\n mix(mix(hash(i + vec3f(0,0,0)), hash(i + vec3f(1,0,0)), u.x),\n mix(hash(i + vec3f(0,1,0)), hash(i + vec3f(1,1,0)), u.x), u.y),\n mix(mix(hash(i + vec3f(0,0,1)), hash(i + vec3f(1,0,1)), u.x),\n mix(hash(i + vec3f(0,1,1)), hash(i + vec3f(1,1,1)), u.x), u.y),\n u.z\n );\n}\n\nfn fbm(p: vec3f) -> f32 {\n var value = 0.0;\n var amplitude = 0.5;\n var pos = p;\n for (var i = 0; i < 3; i++) {\n value += amplitude * noise3d(pos);\n pos *= 2.0;\n amplitude *= 0.5;\n }\n return value;\n}\n\n// Get fog density at world position\nfn getFogDensity(worldPos: vec3f, dist: f32) -> f32 {\n // Soft height-based fade at boundaries (smooth transition rather than hard cutoff)\n let heightFade = uniforms.fogHeightFade;\n var heightMod = 1.0;\n if (heightFade.y > heightFade.x) {\n let range = heightFade.y - heightFade.x;\n let fadeZone = range * 0.1; // 10% fade zone at top\n let topFade = 1.0 - smoothstep(heightFade.y - fadeZone, heightFade.y, worldPos.y);\n let bottomFade = smoothstep(heightFade.x, heightFade.x + fadeZone, worldPos.y);\n heightMod = topFade * bottomFade;\n }\n\n // If noise strength is 0, return uniform fog\n if (uniforms.noiseStrength < 0.001) {\n return uniforms.fogDensity * heightMod;\n }\n\n // Time offset: 0 if not animated, actual time if animated\n let timeOffset = select(0.0, uniforms.time, uniforms.noiseAnimated > 0.5);\n\n // Noise variation for natural look - use multiple scales\n // noiseScale controls detail size: higher = finer detail, lower = larger billows\n let scale1 = uniforms.noiseScale; // Fine detail\n let scale2 = uniforms.noiseScale * 0.32; // Large-scale variation (about 1/3 of fine)\n\n let noisePos = worldPos * scale1 + vec3f(timeOffset * 0.15, timeOffset * 0.02, timeOffset * 0.08);\n let noiseVal = fbm(noisePos);\n\n // Add a second layer of larger-scale variation\n let noisePos2 = worldPos * scale2 + vec3f(timeOffset * 0.05, 0.0, timeOffset * 0.03);\n let noiseVal2 = fbm(noisePos2);\n\n // Combine noises: fbm returns [0, ~0.875], combine for more range\n let combinedNoise = noiseVal * 0.6 + noiseVal2 * 0.4;\n\n // Map noise to density multiplier based on noiseStrength\n // noiseStrength=0: multiplier = 1.0 (uniform)\n // noiseStrength=1: multiplier = [0.2, 1.2] (full variation)\n let noiseRange = combinedNoise * 1.0 + 0.2; // [0.2, 1.2]\n let noiseMapped = mix(1.0, noiseRange, uniforms.noiseStrength);\n\n return uniforms.fogDensity * heightMod * noiseMapped;\n}\n\n// Sample cascade shadow at world position\nfn sampleShadow(worldPos: vec3f) -> f32 {\n if (uniforms.shadowsEnabled < 0.5) {\n return 1.0;\n }\n\n // Try each cascade\n for (var cascade = 0; cascade < 3; cascade++) {\n let lightSpacePos = cascadeMatrices[cascade] * vec4f(worldPos, 1.0);\n let projCoords = lightSpacePos.xyz / lightSpacePos.w;\n\n // Convert to UV space\n let uv = vec2f(projCoords.x * 0.5 + 0.5, 0.5 - projCoords.y * 0.5);\n\n // Check bounds\n if (uv.x >= 0.0 && uv.x <= 1.0 && uv.y >= 0.0 && uv.y <= 1.0 &&\n projCoords.z >= 0.0 && projCoords.z <= 1.0) {\n\n let bias = 0.003 * f32(cascade + 1);\n let depth = projCoords.z - bias;\n let clampedUV = clamp(uv, vec2f(0.002), vec2f(0.998));\n let clampedDepth = clamp(depth, 0.001, 0.999);\n\n return textureSampleCompareLevel(cascadeShadowMaps, shadowSampler, clampedUV, cascade, clampedDepth);\n }\n }\n\n return 1.0;\n}\n\n// Sample spot light shadow from atlas\nfn sampleSpotShadow(worldPos: vec3f, slotIndex: i32) -> f32 {\n if (slotIndex < 0 || slotIndex >= MAX_SPOT_SHADOWS) {\n return 1.0;\n }\n\n // Get the light matrix\n let lightMatrix = spotMatrices[slotIndex];\n\n // Transform to light space\n let lightSpacePos = lightMatrix * vec4f(worldPos, 1.0);\n\n // Perspective divide\n let w = max(abs(lightSpacePos.w), 0.0001) * sign(lightSpacePos.w + 0.0001);\n let projCoords = lightSpacePos.xyz / w;\n\n // Check if in frustum\n if (abs(projCoords.x) > 1.0 || abs(projCoords.y) > 1.0 ||\n projCoords.z < 0.0 || projCoords.z > 1.0) {\n return 1.0;\n }\n\n // Calculate tile position in atlas\n let col = slotIndex % SPOT_TILES_PER_ROW;\n let row = slotIndex / SPOT_TILES_PER_ROW;\n\n // Transform to [0,1] UV space within the tile\n let tileUV = vec2f(projCoords.x * 0.5 + 0.5, 0.5 - projCoords.y * 0.5);\n\n // Calculate UV in atlas\n let tileOffsetX = f32(col) * SPOT_TILE_SIZE;\n let tileOffsetY = f32(row) * SPOT_TILE_SIZE;\n let atlasUV = vec2f(\n (tileOffsetX + tileUV.x * SPOT_TILE_SIZE) / SPOT_ATLAS_SIZE,\n (tileOffsetY + tileUV.y * SPOT_TILE_SIZE) / SPOT_ATLAS_SIZE\n );\n\n // Sample shadow with bias\n let bias = 0.005;\n let depth = clamp(projCoords.z - bias, 0.001, 0.999);\n\n return textureSampleCompareLevel(spotShadowAtlas, shadowSampler, atlasUV, depth);\n}\n\n// Calculate lighting contribution from a single light\nfn calculateLightContribution(worldPos: vec3f, light: Light, worldRayDir: vec3f) -> vec3f {\n let toLight = light.position - worldPos;\n let dist = length(toLight);\n let radius = light.geom.x;\n\n // Distance check\n if (dist > radius) {\n return vec3f(0.0);\n }\n\n // Smooth falloff for volumetric fog - gentler than inverse square\n // so lights are visible throughout their radius\n let normalizedDist = dist / radius;\n\n // Smooth falloff: strong near light, gradual fade to edge\n // Using smoothstep gives S-curve that looks natural\n let attenuation = 1.0 - smoothstep(0.0, 1.0, normalizedDist);\n\n // Spotlight cone check\n let innerCone = light.geom.y;\n let outerCone = light.geom.z;\n var spotAttenuation = 1.0;\n let isSpotlight = outerCone > 0.0;\n\n if (isSpotlight) {\n let lightDir = normalize(-toLight);\n let spotCos = dot(lightDir, normalize(light.direction));\n spotAttenuation = smoothstep(outerCone, innerCone, spotCos);\n if (spotAttenuation <= 0.0) {\n return vec3f(0.0);\n }\n }\n\n // Shadow\n var shadow = 1.0;\n if (isSpotlight && light.shadowIndex >= 0 && uniforms.shadowsEnabled > 0.5) {\n shadow = sampleSpotShadow(worldPos, light.shadowIndex);\n }\n\n // Phase function for scattering (isotropic for point/spot)\n let phase = 0.25; // Isotropic\n\n // Final contribution\n let intensity = light.color.a;\n return light.color.rgb * intensity * attenuation * spotAttenuation * shadow * phase;\n}\n\n// Henyey-Greenstein phase function for forward scattering\nfn phaseHG(cosTheta: f32, g: f32) -> f32 {\n let g2 = g * g;\n let denom = 1.0 + g2 - 2.0 * g * cosTheta;\n return (1.0 - g2) / (4.0 * 3.14159 * pow(denom, 1.5));\n}\n\n// Convert depth buffer value to linear distance\nfn linearizeDepth(depth: f32) -> f32 {\n let near = uniforms.nearPlane;\n let far = uniforms.farPlane;\n return near + depth * (far - near);\n}\n\n@fragment\nfn fragmentMain(input: VertexOutput) -> @location(0) vec4f {\n let uv = input.uv;\n\n // Debug modes: 0=normal, 1=depth, 2=ray dir, 3=noise, 4=viewDir.z, 5=worldPos, 6=accum color, 7=light positions\n let DEBUG = i32(uniforms.debugMode);\n\n // Reconstruct world ray direction from screen UV\n // Use clip space at z=0 (near plane) for ray direction - matches FogPass pattern\n var clipUV = uv;\n clipUV.y = 1.0 - clipUV.y; // Flip Y for clip space\n let ndc = vec4f(clipUV * 2.0 - 1.0, 0.0, 1.0);\n\n // Transform to view space\n let viewRay4 = uniforms.inverseProjection * ndc;\n let viewDir = normalize(viewRay4.xyz / viewRay4.w);\n\n // Transform view direction to world space\n let worldRayDir = normalize((uniforms.inverseView * vec4f(viewDir, 0.0)).xyz);\n\n // Get scene depth from full-resolution depth texture\n let depthTexSize = vec2f(textureDimensions(depthTexture));\n let depthCoord = vec2i(uv * depthTexSize);\n let rawDepth = textureLoad(depthTexture, depthCoord, 0);\n\n // Debug: show raw depth\n if (DEBUG == 1) {\n return vec4f(rawDepth, rawDepth, rawDepth, 1.0);\n }\n\n // Debug: show world ray direction (should vary across screen)\n if (DEBUG == 2) {\n return vec4f(worldRayDir * 0.5 + 0.5, 1.0);\n }\n\n // Debug: show viewDir.z (should vary across screen)\n if (DEBUG == 4) {\n let vzVis = abs(viewDir.z);\n return vec4f(vzVis, vzVis, vzVis, 1.0);\n }\n\n // Debug: visualize first light position (should show light location, not camera)\n // Red = distance from camera to light (normalized by 50m)\n // Green = light enabled (0.5 if enabled)\n // Blue = light radius / 50\n if (DEBUG == 7) {\n let numLights = i32(uniforms.lightCount);\n if (numLights > 0) {\n let light = lights[0];\n // Distance from CAMERA to light (not from ray sample)\n let camToLightDist = length(light.position - uniforms.cameraPosition);\n let radius = light.geom.x;\n // Show: R=distance/50, G=enabled, B=radius/50\n return vec4f(\n clamp(camToLightDist / 50.0, 0.0, 1.0),\n f32(light.enabled) * 0.5,\n clamp(radius / 50.0, 0.0, 1.0),\n 1.0\n );\n }\n return vec4f(1.0, 0.0, 0.0, 1.0); // Red = no lights\n }\n\n // Debug: visualize light position in world space\n // Shows light position components normalized\n if (DEBUG == 8) {\n let numLights = i32(uniforms.lightCount);\n if (numLights > 0) {\n let light = lights[0];\n // Show light position components (scaled to [0,1] range assuming -100 to 100 world coords)\n return vec4f(\n (light.position.x + 100.0) / 200.0,\n (light.position.y + 10.0) / 60.0,\n (light.position.z + 100.0) / 200.0,\n 1.0\n );\n }\n return vec4f(0.0, 0.0, 0.0, 1.0);\n }\n\n // Calculate max ray distance to geometry (or infinite for sky)\n var geometryDist = 1000000.0;\n if (rawDepth < 0.9999) {\n let linearDepth = linearizeDepth(rawDepth);\n geometryDist = linearDepth / max(0.001, -viewDir.z) * 0.98;\n }\n\n // Calculate ray start/end based on fog height bounds\n // Fog exists between fogHeightFade.x (bottom) and fogHeightFade.y (top)\n let fogBottom = uniforms.fogHeightFade.x;\n let fogTop = uniforms.fogHeightFade.y;\n let camY = uniforms.cameraPosition.y;\n\n // Find where ray intersects fog volume (height planes)\n // Ray: P = camPos + t * worldRayDir\n // For y = fogBottom: t = (fogBottom - camY) / worldRayDir.y\n // For y = fogTop: t = (fogTop - camY) / worldRayDir.y\n var tStart = 0.0;\n var tEnd = geometryDist;\n\n if (abs(worldRayDir.y) > 0.0001) {\n let tBottom = (fogBottom - camY) / worldRayDir.y;\n let tTop = (fogTop - camY) / worldRayDir.y;\n\n // Determine entry and exit based on ray direction\n let tEnter = min(tBottom, tTop);\n let tExit = max(tBottom, tTop);\n\n // If camera is outside fog, start from where ray enters\n if (camY < fogBottom || camY > fogTop) {\n tStart = max(0.0, tEnter);\n }\n\n // End at fog boundary or geometry\n tEnd = min(tEnd, max(0.0, tExit));\n } else {\n // Ray is horizontal - check if we're in the fog layer\n if (camY < fogBottom || camY > fogTop) {\n return vec4f(0.0); // Camera outside fog, horizontal ray never enters\n }\n }\n\n // Skip if ray doesn't travel through fog\n if (tStart >= tEnd || (tEnd - tStart) < 0.5) {\n return vec4f(0.0);\n }\n\n // Debug 9: show ray distance coverage\n // R = tStart/50, G = tEnd/50, B = rayLength/50\n if (DEBUG == 9) {\n let rayLength = tEnd - tStart;\n return vec4f(tStart / 50.0, tEnd / 50.0, rayLength / 50.0, 1.0);\n }\n\n // Debug 10: test if ray intersects first light's volume\n // Sample at 10 points along ray and check if any are inside light\n if (DEBUG == 10) {\n let numLights = i32(uniforms.lightCount);\n if (numLights > 0) {\n let light = lights[0];\n let radius = light.geom.x;\n var minDist = 9999.0;\n var hitCount = 0.0;\n let rayLength = tEnd - tStart;\n for (var ti = 0; ti < 20; ti++) {\n let testT = tStart + rayLength * f32(ti) / 20.0;\n let testPos = uniforms.cameraPosition + worldRayDir * testT;\n let dist = length(light.position - testPos);\n minDist = min(minDist, dist);\n if (dist < radius) {\n hitCount += 1.0;\n }\n }\n // R = min distance / 50, G = hit count / 20, B = light radius / 50\n return vec4f(minDist / 50.0, hitCount / 20.0, radius / 50.0, 1.0);\n }\n return vec4f(1.0, 0.0, 0.0, 1.0);\n }\n\n // Debug: show noise at sample position (should vary across screen)\n if (DEBUG == 3) {\n let samplePos = uniforms.cameraPosition + worldRayDir * min(10.0, tEnd);\n let n = getFogDensity(samplePos, 10.0);\n return vec4f(n, n, n, 1.0);\n }\n\n // Debug: show world position at fixed distance (should vary)\n if (DEBUG == 5) {\n let worldPos = uniforms.cameraPosition + worldRayDir * min(5.0, tEnd);\n return vec4f(fract(worldPos * 0.1), 1.0);\n }\n\n // Ray marching with fixed step sizes (not scaled to ray length)\n // This ensures we always have good sample density near lights\n let numSamples = i32(uniforms.maxSamples);\n let fNumSamples = f32(numSamples);\n\n // Spatial jitter to reduce banding\n let jitter = fract(sin(dot(input.position.xy, vec2f(12.9898, 78.233))) * 43758.5453);\n\n var accumulatedColor = vec3f(0.0);\n var accumulatedMainLight = vec3f(0.0); // Track main light separately (unshaded)\n var accumulatedShadow = 0.0; // Weighted shadow accumulation\n var shadowWeight = 0.0; // Total weight for shadow averaging\n var accumulatedAlpha = 0.0;\n\n // Phase function for main light scattering\n let lightDir = normalize(uniforms.mainLightDir);\n let viewToLight = dot(worldRayDir, lightDir);\n // Boosted phase - more visible from all angles, extra bright when looking toward sun\n let hgPhase = phaseHG(viewToLight, 0.6);\n let phase = max(0.4, hgPhase); // Minimum 0.4 so shadows are always visible\n\n // Adaptive step size: cover full ray but with minimum step for quality\n\n let rayLength = tEnd - tStart;\n let minStepSize = 0.25; // Never step smaller than this\n let maxStepSize = 2.0; // Never step larger than this (to not skip lights)\n let stepSize = clamp(rayLength / fNumSamples, minStepSize, maxStepSize);\n //let stepSize = 0.25;\n\n // Track current position along ray\n var t = tStart + jitter * stepSize;\n\n // Debug 11: track max light contribution found\n var debugMaxLight = vec3f(0.0);\n var debugLightHits = 0.0;\n\n for (var i = 0; i < numSamples; i++) {\n // Only stop at fog boundary or surface, never due to density\n if (t > tEnd) { break; }\n\n let samplePos = uniforms.cameraPosition + worldRayDir * t;\n\n // Advance t for next iteration (do this early so continue doesn't skip it)\n t += stepSize;\n\n // Get fog density at this point\n let density = getFogDensity(samplePos, t);\n // Don't skip low density - lights still illuminate thin fog\n // Only skip if truly zero\n if (density <= 0.0) { continue; }\n\n // Calculate lighting at this point - only from actual lights, no ambient\n // Separate main light from point/spot lights for independent control\n var mainLighting = vec3f(0.0);\n var pointLighting = vec3f(0.0);\n\n // Main directional light - accumulate shadow weighted by density\n if (uniforms.mainLightIntensity > 0.0) {\n let rawShadow = sampleShadow(samplePos);\n\n // Accumulate shadow weighted by density (where fog actually is)\n let weight = density * stepSize;\n accumulatedShadow += rawShadow * weight;\n shadowWeight += weight;\n\n // Accumulate main light base (without shadow factor - applied at end)\n mainLighting = uniforms.mainLightColor * uniforms.mainLightIntensity * phase * uniforms.mainLightScatter;\n }\n\n // Point and spot lights\n let numLights = i32(uniforms.lightCount);\n for (var li = 0; li < numLights; li++) {\n let light = lights[li];\n if (light.enabled == 0u) { continue; }\n\n let contrib = calculateLightContribution(samplePos, light, worldRayDir);\n pointLighting += contrib;\n\n // Track for debug\n if (length(contrib) > 0.001) {\n debugLightHits += 1.0;\n debugMaxLight = max(debugMaxLight, contrib);\n }\n }\n\n // Apply fog color - scatterStrength only affects point/spot lights\n let pointColor = pointLighting * uniforms.scatterStrength * uniforms.fogColor;\n let mainColor = mainLighting * uniforms.fogColor;\n\n // Accumulate with density - multiply by step size for proper integration\n // Cap per-sample alpha to prevent rapid saturation near camera\n // This ensures distant lights remain visible even when inside fog\n let rawSampleAlpha = density * stepSize;\n let sampleAlpha = min(rawSampleAlpha, 0.03); // Max 3% opacity per sample\n\n // Color contribution is proportional to density but uses uncapped value for brightness\n let colorWeight = rawSampleAlpha * (1.0 - accumulatedAlpha);\n accumulatedColor += pointColor * colorWeight;\n accumulatedMainLight += mainColor * colorWeight;\n accumulatedAlpha += sampleAlpha * (1.0 - accumulatedAlpha);\n }\n\n // Clamp alpha to valid range\n accumulatedAlpha = min(accumulatedAlpha, 1.0);\n\n // Calculate weighted average shadow (where fog density is)\n let avgShadow = select(1.0, accumulatedShadow / shadowWeight, shadowWeight > 0.001);\n\n // Apply strong contrast curve to shadow boundary\n // This creates high contrast god rays at shadow edges\n // smoothstep with tight range pushes values toward 0 or 1\n let contrastShadow = smoothstep(0.25, 0.75, avgShadow);\n // Mix with original to keep some softness\n let finalShadow = mix(avgShadow, contrastShadow, 0.88);\n\n // Apply saturation cap with logarithmic curve\n let sat = uniforms.mainLightSaturation;\n let saturatedShadow = sat * (1.0 - exp(-finalShadow * 3.0 / max(sat, 0.01)));\n\n // Add main light contribution with contrast-boosted shadow\n accumulatedColor += accumulatedMainLight * saturatedShadow;\n\n // Debug 11: show max light contribution found along ray\n // R = max light intensity, G = number of lit samples / 100, B = 0\n if (DEBUG == 11) {\n return vec4f(length(debugMaxLight), debugLightHits / 100.0, 0.0, 1.0);\n }\n\n // Debug 12: show actual ray march distance and iterations\n // R = distance actually marched / 50, G = iterations / numSamples, B = accumulatedAlpha\n if (DEBUG == 12) {\n let distMarched = t - tStart;\n let iterCount = distMarched / stepSize;\n return vec4f(distMarched / 50.0, iterCount / fNumSamples, accumulatedAlpha, 1.0);\n }\n\n // Debug 13: check light distances at sample points along ray\n // Sample 10 points and show min distance to first light\n if (DEBUG == 13) {\n let numLights = i32(uniforms.lightCount);\n if (numLights > 0) {\n let light = lights[0];\n let radius = light.geom.x;\n var minDistToLight = 9999.0;\n var closestT = 0.0;\n let testRayLen = t - tStart; // actual marched distance\n for (var ti = 0; ti < 32; ti++) {\n let testT = tStart + testRayLen * f32(ti) / 32.0;\n let testPos = uniforms.cameraPosition + worldRayDir * testT;\n let distToLight = length(light.position - testPos);\n if (distToLight < minDistToLight) {\n minDistToLight = distToLight;\n closestT = testT;\n }\n }\n // R = min dist to light / radius (< 1 means inside light)\n // G = closestT / 50 (where along ray is closest point)\n // B = radius / 50\n return vec4f(minDistToLight / radius, closestT / 50.0, radius / 50.0, 1.0);\n }\n return vec4f(1.0, 0.0, 0.0, 1.0);\n }\n\n // Debug: show raw accumulated color (before compositing)\n if (DEBUG == 6) {\n return vec4f(accumulatedColor, 1.0);\n }\n\n return vec4f(accumulatedColor, accumulatedAlpha);\n}\n","// Volumetric Fog - Gaussian Blur\n// Blurs the ray-marched fog for softer edges\n\nstruct Uniforms {\n direction: vec2f,\n texelSize: vec2f,\n radius: f32,\n _pad0: f32,\n _pad1: f32,\n _pad2: f32,\n}\n\nstruct VertexOutput {\n @builtin(position) position: vec4f,\n @location(0) uv: vec2f,\n}\n\n@group(0) @binding(0) var<uniform> uniforms: Uniforms;\n@group(0) @binding(1) var inputTexture: texture_2d<f32>;\n@group(0) @binding(2) var inputSampler: sampler;\n\n// Full-screen triangle vertex shader\n@vertex\nfn vertexMain(@builtin(vertex_index) vertexIndex: u32) -> VertexOutput {\n var output: VertexOutput;\n\n let x = f32(vertexIndex & 1u) * 4.0 - 1.0;\n let y = f32(vertexIndex >> 1u) * 4.0 - 1.0;\n\n output.position = vec4f(x, y, 0.0, 1.0);\n output.uv = vec2f((x + 1.0) * 0.5, (1.0 - y) * 0.5);\n\n return output;\n}\n\n// Gaussian weight function\nfn gaussian(x: f32, sigma: f32) -> f32 {\n return exp(-(x * x) / (2.0 * sigma * sigma));\n}\n\n@fragment\nfn fragmentMain(input: VertexOutput) -> @location(0) vec4f {\n let uv = input.uv;\n let radius = uniforms.radius;\n let sigma = radius / 3.0; // 99.7% of Gaussian within 3 sigma\n\n // Sample center pixel\n var color = textureSample(inputTexture, inputSampler, uv);\n var totalWeight = 1.0;\n\n // Step size (sample every 1.5 texels for efficiency)\n let stepSize = 1.5;\n let numSamples = i32(ceil(radius / stepSize));\n\n // Blur direction (scaled by texel size)\n let dir = uniforms.direction * uniforms.texelSize;\n\n // Bidirectional sampling\n for (var i = 1; i <= numSamples; i++) {\n let offset = f32(i) * stepSize;\n let weight = gaussian(offset, sigma);\n\n // Positive direction\n let uvPos = uv + dir * offset;\n let samplePos = textureSample(inputTexture, inputSampler, uvPos);\n color += samplePos * weight;\n\n // Negative direction\n let uvNeg = uv - dir * offset;\n let sampleNeg = textureSample(inputTexture, inputSampler, uvNeg);\n color += sampleNeg * weight;\n\n totalWeight += weight * 2.0;\n }\n\n // Normalize\n color /= totalWeight;\n\n return color;\n}\n","// Volumetric Fog - Composite\n// Blends the blurred fog into the scene (additive)\n\nstruct Uniforms {\n canvasSize: vec2f,\n renderSize: vec2f,\n texelSize: vec2f,\n // Brightness-based fog attenuation (like bloom)\n brightnessThreshold: f32, // Scene luminance where fog starts fading\n minVisibility: f32, // Minimum fog visibility over bright surfaces (0-1)\n skyBrightness: f32, // Virtual brightness for sky pixels (depth at far plane)\n _pad: f32,\n}\n\nstruct VertexOutput {\n @builtin(position) position: vec4f,\n @location(0) uv: vec2f,\n}\n\n@group(0) @binding(0) var<uniform> uniforms: Uniforms;\n@group(0) @binding(1) var sceneTexture: texture_2d<f32>;\n@group(0) @binding(2) var fogTexture: texture_2d<f32>;\n@group(0) @binding(3) var linearSampler: sampler;\n@group(0) @binding(4) var depthTexture: texture_depth_2d;\n\n// Full-screen triangle vertex shader\n@vertex\nfn vertexMain(@builtin(vertex_index) vertexIndex: u32) -> VertexOutput {\n var output: VertexOutput;\n\n let x = f32(vertexIndex & 1u) * 4.0 - 1.0;\n let y = f32(vertexIndex >> 1u) * 4.0 - 1.0;\n\n output.position = vec4f(x, y, 0.0, 1.0);\n output.uv = vec2f((x + 1.0) * 0.5, (1.0 - y) * 0.5);\n\n return output;\n}\n\n@fragment\nfn fragmentMain(input: VertexOutput) -> @location(0) vec4f {\n let uv = input.uv;\n\n // Sample scene color at full resolution\n let sceneColor = textureSample(sceneTexture, linearSampler, uv).rgb;\n\n // Sample fog at reduced resolution (will be upsampled by linear filtering)\n let fog = textureSample(fogTexture, linearSampler, uv);\n\n // Sample depth to detect sky (depth near 1.0 = far plane = sky)\n let depthCoord = vec2i(input.position.xy);\n let depth = textureLoad(depthTexture, depthCoord, 0);\n\n // Calculate scene luminance for brightness-based attenuation\n var luminance = dot(sceneColor, vec3f(0.299, 0.587, 0.114));\n\n // Sky detection: if depth is very close to 1.0 (far plane), treat as bright\n // This ensures fog is less visible over sky even if sky color isn't bright\n let isSky = depth > 0.9999;\n if (isSky) {\n luminance = max(luminance, uniforms.skyBrightness);\n }\n\n // Fog visibility decreases over bright surfaces (like bloom behavior)\n // Full visibility at dark, reduced visibility at bright, but never zero\n let threshold = uniforms.brightnessThreshold;\n let minVis = uniforms.minVisibility;\n\n // Soft falloff using inverse relationship\n // At luminance=0: visibility=1\n // At luminance=threshold: visibility≈0.5\n // At luminance>>threshold: visibility→minVis\n let falloff = 1.0 / (1.0 + luminance / max(threshold, 0.01));\n let fogVisibility = mix(minVis, 1.0, falloff);\n\n // Additive blend with brightness attenuation\n let finalColor = sceneColor + fog.rgb * fogVisibility;\n\n return vec4f(finalColor, 1.0);\n}\n","import { BasePass } from \"./BasePass.js\"\nimport { Texture } from \"../../Texture.js\"\n\nimport volumetricRaymarchWGSL from \"../shaders/volumetric_raymarch.wgsl\"\nimport volumetricBlurWGSL from \"../shaders/volumetric_blur.wgsl\"\nimport volumetricCompositeWGSL from \"../shaders/volumetric_composite.wgsl\"\n\n/**\n * VolumetricFogPass - Simple ray marching volumetric fog\n *\n * Pipeline:\n * 1. Ray Marching (Fragment) - March rays, sample shadows directly at each step\n * 2. Blur (Fragment) - Gaussian blur to soften edges\n * 3. Composite (Fragment) - Blend fog into scene\n */\nclass VolumetricFogPass extends BasePass {\n constructor(engine = null) {\n super('VolumetricFog', engine)\n\n // Pipelines\n this.raymarchPipeline = null\n this.blurHPipeline = null\n this.blurVPipeline = null\n this.compositePipeline = null\n\n // Bind group layouts\n this.raymarchBGL = null\n this.blurBGL = null\n this.compositeBGL = null\n\n // 2D textures\n this.raymarchTexture = null\n this.blurTempTexture = null\n this.blurredTexture = null\n this.outputTexture = null\n\n // Uniform buffers\n this.raymarchUniformBuffer = null\n this.blurHUniformBuffer = null\n this.blurVUniformBuffer = null\n this.compositeUniformBuffer = null\n\n // Samplers\n this.linearSampler = null\n\n // External dependencies\n this.inputTexture = null\n this.gbuffer = null\n this.shadowPass = null\n this.lightingPass = null\n\n // Dimensions\n this.canvasWidth = 0\n this.canvasHeight = 0\n this.renderWidth = 0\n this.renderHeight = 0\n\n // Adaptive scatter state\n this._currentMainLightScatter = null // Will be initialized on first execute\n this._lastUpdateTime = 0\n this._cameraInShadowSmooth = 0.0 // 0 = in light, 1 = in shadow\n\n // Sky detection state (for adaptive scatter)\n this._skyVisible = true // Assume sky visible until proven otherwise\n this._skyCheckPending = false\n this._lastSkyCheckTime = 0\n }\n\n // Settings getters\n get volumetricSettings() { return this.settings?.volumetricFog ?? {} }\n get fogSettings() { return this.settings?.fog ?? {} }\n get isVolumetricEnabled() { return this.volumetricSettings.enabled ?? false }\n get resolution() { return this.volumetricSettings.resolution ?? 0.25 }\n get maxSamples() { return this.volumetricSettings.maxSamples ?? 32 }\n get blurRadius() { return this.volumetricSettings.blurRadius ?? 4.0 }\n get fogDensity() { return this.volumetricSettings.density ?? this.volumetricSettings.densityMultiplier ?? 0.5 }\n get scatterStrength() { return this.volumetricSettings.scatterStrength ?? 1.0 }\n get maxDistance() { return this.volumetricSettings.maxDistance ?? 20.0 }\n get heightRange() { return this.volumetricSettings.heightRange ?? [-5, 20] }\n get shadowsEnabled() { return this.volumetricSettings.shadowsEnabled ?? true }\n get noiseStrength() { return this.volumetricSettings.noiseStrength ?? 1.0 } // 0 = uniform fog, 1 = full noise\n get noiseAnimated() { return this.volumetricSettings.noiseAnimated ?? true }\n get noiseScale() { return this.volumetricSettings.noiseScale ?? 0.25 } // Noise frequency (higher = finer detail)\n get mainLightScatter() { return this.volumetricSettings.mainLightScatter ?? 1.0 } // Scatter when camera in light\n get mainLightScatterDark() { return this.volumetricSettings.mainLightScatterDark ?? 3.0 } // Scatter when camera in shadow\n get mainLightSaturation() { return this.volumetricSettings.mainLightSaturation ?? 1.0 } // Max brightness cap\n // Brightness-based attenuation (fog less visible over bright surfaces)\n get brightnessThreshold() { return this.volumetricSettings.brightnessThreshold ?? 1.0 } // Scene luminance where fog starts fading\n get minVisibility() { return this.volumetricSettings.minVisibility ?? 0.15 } // Minimum fog visibility over very bright surfaces\n get skyBrightness() { return this.volumetricSettings.skyBrightness ?? 5.0 } // Virtual brightness for sky (far depth)\n // Debug mode: 0=normal, 1=depth, 2=ray dir, 3=noise, 4=viewDir.z, 5=worldPos, 6=accum, 7=light dist, 8=light pos\n get debugMode() { return this.volumetricSettings.debug ?? 0 }\n\n setInputTexture(texture) { this.inputTexture = texture }\n setGBuffer(gbuffer) { this.gbuffer = gbuffer }\n setShadowPass(shadowPass) { this.shadowPass = shadowPass }\n setLightingPass(lightingPass) { this.lightingPass = lightingPass }\n getOutputTexture() { return this.outputTexture }\n\n // Unused setters (kept for API compatibility)\n setHiZPass() {}\n\n async _init() {\n const { device } = this.engine\n\n this.linearSampler = device.createSampler({\n label: 'Volumetric Linear Sampler',\n minFilter: 'linear',\n magFilter: 'linear',\n addressModeU: 'clamp-to-edge',\n addressModeV: 'clamp-to-edge',\n })\n\n // Fallback shadow resources\n this.fallbackShadowSampler = device.createSampler({\n label: 'Volumetric Fallback Shadow Sampler',\n compare: 'less',\n })\n\n this.fallbackCascadeShadowMap = device.createTexture({\n label: 'Volumetric Fallback Cascade Shadow',\n size: [1, 1, 3],\n format: 'depth32float',\n usage: GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.RENDER_ATTACHMENT,\n })\n\n const identityMatrix = new Float32Array([1,0,0,0, 0,1,0,0, 0,0,1,0, 0,0,0,1])\n const matrixData = new Float32Array(16 * 3)\n for (let i = 0; i < 3; i++) matrixData.set(identityMatrix, i * 16)\n\n this.fallbackCascadeMatrices = device.createBuffer({\n label: 'Volumetric Fallback Cascade Matrices',\n size: 16 * 4 * 3,\n usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST,\n })\n device.queue.writeBuffer(this.fallbackCascadeMatrices, 0, matrixData)\n\n // Fallback lights buffer (empty) - 96 bytes per light to match WGSL alignment\n this.fallbackLightsBuffer = device.createBuffer({\n label: 'Volumetric Fallback Lights',\n size: 768 * 96, // MAX_LIGHTS * 96 bytes per light\n usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST,\n })\n\n // Fallback spot shadow atlas (1x1 depth texture)\n this.fallbackSpotShadowAtlas = device.createTexture({\n label: 'Volumetric Fallback Spot Shadow Atlas',\n size: [1, 1, 1],\n format: 'depth32float',\n usage: GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.RENDER_ATTACHMENT,\n })\n\n // Fallback spot matrices buffer\n const spotMatrixData = new Float32Array(16 * 16) // 16 spot shadows max\n for (let i = 0; i < 16; i++) spotMatrixData.set(identityMatrix, i * 16)\n this.fallbackSpotMatrices = device.createBuffer({\n label: 'Volumetric Fallback Spot Matrices',\n size: 16 * 4 * 16,\n usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST,\n })\n device.queue.writeBuffer(this.fallbackSpotMatrices, 0, spotMatrixData)\n\n await this._createResources(this.engine.canvas.width, this.engine.canvas.height)\n }\n\n async _createResources(width, height) {\n const { device } = this.engine\n\n this.canvasWidth = width\n this.canvasHeight = height\n this.renderWidth = Math.max(1, Math.floor(width * this.resolution))\n this.renderHeight = Math.max(1, Math.floor(height * this.resolution))\n\n this._destroyTextures()\n\n // Create 2D textures at reduced resolution\n this.raymarchTexture = this._create2DTexture('Raymarch Output', this.renderWidth, this.renderHeight)\n this.blurTempTexture = this._create2DTexture('Blur Temp', this.renderWidth, this.renderHeight)\n this.blurredTexture = this._create2DTexture('Blurred Fog', this.renderWidth, this.renderHeight)\n this.outputTexture = await Texture.renderTarget(this.engine, 'rgba16float', width, height)\n\n // Uniform buffers\n this.raymarchUniformBuffer = this._createUniformBuffer('Raymarch Uniforms', 256)\n this.blurHUniformBuffer = this._createUniformBuffer('Blur H Uniforms', 32)\n this.blurVUniformBuffer = this._createUniformBuffer('Blur V Uniforms', 32)\n this.compositeUniformBuffer = this._createUniformBuffer('Composite Uniforms', 48)\n\n await this._createPipelines()\n }\n\n _create2DTexture(label, width, height) {\n const texture = this.engine.device.createTexture({\n label,\n size: [width, height, 1],\n format: 'rgba16float',\n usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.TEXTURE_BINDING,\n })\n return { texture, view: texture.createView(), width, height }\n }\n\n _createUniformBuffer(label, size) {\n return this.engine.device.createBuffer({\n label,\n size,\n usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST,\n })\n }\n\n async _createPipelines() {\n const { device } = this.engine\n\n // === Raymarch Pipeline ===\n const raymarchModule = device.createShaderModule({\n label: 'Volumetric Raymarch Shader',\n code: volumetricRaymarchWGSL,\n })\n\n this.raymarchBGL = device.createBindGroupLayout({\n label: 'Volumetric Raymarch BGL',\n entries: [\n { binding: 0, visibility: GPUShaderStage.FRAGMENT | GPUShaderStage.VERTEX, buffer: { type: 'uniform' } },\n { binding: 1, visibility: GPUShaderStage.FRAGMENT, texture: { sampleType: 'depth' } },\n { binding: 2, visibility: GPUShaderStage.FRAGMENT, texture: { sampleType: 'depth', viewDimension: '2d-array' } },\n { binding: 3, visibility: GPUShaderStage.FRAGMENT, sampler: { type: 'comparison' } },\n { binding: 4, visibility: GPUShaderStage.FRAGMENT, buffer: { type: 'read-only-storage' } }, // cascade matrices\n { binding: 5, visibility: GPUShaderStage.FRAGMENT, buffer: { type: 'read-only-storage' } }, // lights\n { binding: 6, visibility: GPUShaderStage.FRAGMENT, texture: { sampleType: 'depth' } }, // spot shadow atlas\n { binding: 7, visibility: GPUShaderStage.FRAGMENT, buffer: { type: 'read-only-storage' } }, // spot matrices\n ],\n })\n\n this.raymarchPipeline = await device.createRenderPipelineAsync({\n label: 'Volumetric Raymarch Pipeline',\n layout: device.createPipelineLayout({ bindGroupLayouts: [this.raymarchBGL] }),\n vertex: { module: raymarchModule, entryPoint: 'vertexMain' },\n fragment: {\n module: raymarchModule,\n entryPoint: 'fragmentMain',\n targets: [{ format: 'rgba16float' }],\n },\n primitive: { topology: 'triangle-list' },\n })\n\n // === Blur Pipeline ===\n const blurModule = device.createShaderModule({\n label: 'Volumetric Blur Shader',\n code: volumetricBlurWGSL,\n })\n\n this.blurBGL = device.createBindGroupLayout({\n label: 'Volumetric Blur BGL',\n entries: [\n { binding: 0, visibility: GPUShaderStage.FRAGMENT | GPUShaderStage.VERTEX, buffer: { type: 'uniform' } },\n { binding: 1, visibility: GPUShaderStage.FRAGMENT, texture: { sampleType: 'float' } },\n { binding: 2, visibility: GPUShaderStage.FRAGMENT, sampler: { type: 'filtering' } },\n ],\n })\n\n const blurPipelineDesc = {\n layout: device.createPipelineLayout({ bindGroupLayouts: [this.blurBGL] }),\n vertex: { module: blurModule, entryPoint: 'vertexMain' },\n fragment: {\n module: blurModule,\n entryPoint: 'fragmentMain',\n targets: [{ format: 'rgba16float' }],\n },\n primitive: { topology: 'triangle-list' },\n }\n\n this.blurHPipeline = await device.createRenderPipelineAsync({ ...blurPipelineDesc, label: 'Volumetric Blur H' })\n this.blurVPipeline = await device.createRenderPipelineAsync({ ...blurPipelineDesc, label: 'Volumetric Blur V' })\n\n // === Composite Pipeline ===\n const compositeModule = device.createShaderModule({\n label: 'Volumetric Composite Shader',\n code: volumetricCompositeWGSL,\n })\n\n this.compositeBGL = device.createBindGroupLayout({\n label: 'Volumetric Composite BGL',\n entries: [\n { binding: 0, visibility: GPUShaderStage.FRAGMENT | GPUShaderStage.VERTEX, buffer: { type: 'uniform' } },\n { binding: 1, visibility: GPUShaderStage.FRAGMENT, texture: { sampleType: 'float' } },\n { binding: 2, visibility: GPUShaderStage.FRAGMENT, texture: { sampleType: 'float' } },\n { binding: 3, visibility: GPUShaderStage.FRAGMENT, sampler: { type: 'filtering' } },\n { binding: 4, visibility: GPUShaderStage.FRAGMENT, texture: { sampleType: 'depth' } },\n ],\n })\n\n this.compositePipeline = await device.createRenderPipelineAsync({\n label: 'Volumetric Composite Pipeline',\n layout: device.createPipelineLayout({ bindGroupLayouts: [this.compositeBGL] }),\n vertex: { module: compositeModule, entryPoint: 'vertexMain' },\n fragment: {\n module: compositeModule,\n entryPoint: 'fragmentMain',\n targets: [{ format: 'rgba16float' }],\n },\n primitive: { topology: 'triangle-list' },\n })\n }\n\n async _execute(context) {\n if (!this.isVolumetricEnabled) return\n if (!this.inputTexture || !this.gbuffer) return\n\n const { device } = this.engine\n const { camera, mainLight, lights } = context\n const time = performance.now() / 1000\n\n // Update adaptive main light scatter based on camera shadow state\n this._updateAdaptiveScatter(camera, mainLight, time)\n\n // Get light count from context or lighting pass\n const lightCount = lights?.length ?? this.lightingPass?.lightCount ?? 0\n\n const commandEncoder = device.createCommandEncoder({ label: 'Volumetric Fog Pass' })\n\n // === Stage 1: Ray Marching ===\n this._updateRaymarchUniforms(camera, mainLight, time, lightCount)\n const raymarchBindGroup = this._createRaymarchBindGroup()\n if (raymarchBindGroup) {\n const raymarchPass = commandEncoder.beginRenderPass({\n label: 'Volumetric Raymarch',\n colorAttachments: [{\n view: this.raymarchTexture.view,\n loadOp: 'clear',\n storeOp: 'store',\n clearValue: { r: 0, g: 0, b: 0, a: 0 },\n }],\n })\n raymarchPass.setPipeline(this.raymarchPipeline)\n raymarchPass.setBindGroup(0, raymarchBindGroup)\n raymarchPass.draw(3)\n raymarchPass.end()\n }\n\n // === Stage 2: Blur ===\n this._updateBlurUniforms(this.blurHUniformBuffer, 1, 0)\n this._updateBlurUniforms(this.blurVUniformBuffer, 0, 1)\n\n const blurHBindGroup = this._createBlurBindGroup(this.raymarchTexture, this.blurHUniformBuffer)\n const blurHPass = commandEncoder.beginRenderPass({\n label: 'Volumetric Blur H',\n colorAttachments: [{\n view: this.blurTempTexture.view,\n loadOp: 'clear',\n storeOp: 'store',\n clearValue: { r: 0, g: 0, b: 0, a: 0 },\n }],\n })\n blurHPass.setPipeline(this.blurHPipeline)\n blurHPass.setBindGroup(0, blurHBindGroup)\n blurHPass.draw(3)\n blurHPass.end()\n\n const blurVBindGroup = this._createBlurBindGroup(this.blurTempTexture, this.blurVUniformBuffer)\n const blurVPass = commandEncoder.beginRenderPass({\n label: 'Volumetric Blur V',\n colorAttachments: [{\n view: this.blurredTexture.view,\n loadOp: 'clear',\n storeOp: 'store',\n clearValue: { r: 0, g: 0, b: 0, a: 0 },\n }],\n })\n blurVPass.setPipeline(this.blurVPipeline)\n blurVPass.setBindGroup(0, blurVBindGroup)\n blurVPass.draw(3)\n blurVPass.end()\n\n // === Stage 3: Composite ===\n this._updateCompositeUniforms()\n const compositeBindGroup = this._createCompositeBindGroup()\n if (compositeBindGroup) {\n const compositePass = commandEncoder.beginRenderPass({\n label: 'Volumetric Composite',\n colorAttachments: [{\n view: this.outputTexture.view,\n loadOp: 'clear',\n storeOp: 'store',\n clearValue: { r: 0, g: 0, b: 0, a: 1 },\n }],\n })\n compositePass.setPipeline(this.compositePipeline)\n compositePass.setBindGroup(0, compositeBindGroup)\n compositePass.draw(3)\n compositePass.end()\n }\n\n device.queue.submit([commandEncoder.finish()])\n }\n\n /**\n * Update adaptive main light scatter based on camera shadow state and sky visibility\n * Smoothly transitions between light/dark scatter values\n * Only uses dark scatter if camera is in shadow AND there's something overhead (no sky)\n */\n _updateAdaptiveScatter(camera, mainLight, currentTime) {\n // Initialize on first call\n if (this._currentMainLightScatter === null) {\n this._currentMainLightScatter = this.mainLightScatter\n }\n\n const deltaTime = this._lastUpdateTime > 0 ? (currentTime - this._lastUpdateTime) : 0.016\n this._lastUpdateTime = currentTime\n\n // Check if camera is in shadow using cascade shadow matrices\n let cameraInShadow = false\n\n if (this.shadowPass && this.shadowsEnabled && mainLight?.enabled !== false) {\n const cameraPos = camera.position || [0, 0, 0]\n cameraInShadow = this._isCameraInShadow(cameraPos)\n\n // Periodically check sky visibility using raycaster (every 0.5s)\n if (!this._skyCheckPending && currentTime - this._lastSkyCheckTime > 0.5) {\n this._checkSkyVisibility(cameraPos)\n }\n }\n\n // Only consider \"in dark area\" if in shadow AND sky is NOT visible\n // If sky is visible (outdoors), don't boost scatter even in shadow\n const inDarkArea = cameraInShadow && !this._skyVisible\n\n // Smooth transition toward target state\n // Going into dark area: 5 seconds (slow), exiting: 1 second (fast)\n const targetShadowState = inDarkArea ? 1.0 : 0.0\n const transitionSpeed = inDarkArea ? (1.0 / 5.0) : (1.0 / 1.0) // per second\n\n // Exponential smoothing toward target\n const t = 1.0 - Math.exp(-transitionSpeed * deltaTime * 3.0)\n this._cameraInShadowSmooth += (targetShadowState - this._cameraInShadowSmooth) * t\n\n // Clamp to valid range\n this._cameraInShadowSmooth = Math.max(0, Math.min(1, this._cameraInShadowSmooth))\n\n // Interpolate scatter value\n const lightScatter = this.mainLightScatter\n const darkScatter = this.mainLightScatterDark\n this._currentMainLightScatter = lightScatter + (darkScatter - lightScatter) * this._cameraInShadowSmooth\n }\n\n /**\n * Check if sky is visible above the camera using raycaster\n * This is async and updates _skyVisible when complete\n */\n _checkSkyVisibility(cameraPos) {\n const raycaster = this.engine?.raycaster\n if (!raycaster) return\n\n this._skyCheckPending = true\n this._lastSkyCheckTime = this._lastUpdateTime\n\n // Cast ray upward from camera position\n const skyCheckDistance = this.volumetricSettings.skyCheckDistance ?? 100\n const debugSkyCheck = this.volumetricSettings.debugSkyCheck ?? false\n\n raycaster.cast(\n cameraPos,\n [0, 1, 0], // Straight up\n skyCheckDistance,\n (result) => {\n const wasVisible = this._skyVisible\n this._skyVisible = !result.hit\n this._skyCheckPending = false\n\n if (debugSkyCheck) {\n const pos = cameraPos.map(v => v.toFixed(1)).join(', ')\n if (result.hit) {\n console.log(`Sky check from [${pos}]: HIT ${result.meshName || result.candidateId} at dist=${result.distance?.toFixed(1)}`)\n } else {\n console.log(`Sky check from [${pos}]: NO HIT (sky visible)`)\n }\n }\n },\n { backfaces: true, debug: debugSkyCheck } // Need backfaces to hit ceilings from below\n )\n }\n\n /**\n * Check if camera position is in shadow\n * Uses the shadow pass's isCameraInShadow method if available,\n * otherwise falls back to checking fog height bounds\n */\n _isCameraInShadow(cameraPos) {\n // Try shadow pass method first (if it has GPU shadow readback)\n if (typeof this.shadowPass?.isCameraInShadow === 'function') {\n return this.shadowPass.isCameraInShadow(cameraPos)\n }\n\n // Fallback: check if camera is below the fog height range\n // This is a simple heuristic - if camera is at the bottom of fog volume,\n // it's more likely to be in a shadowed area (cave, corridor, etc.)\n const heightRange = this.heightRange\n const fogBottom = heightRange[0]\n const fogTop = heightRange[1]\n const fogMiddle = (fogBottom + fogTop) / 2\n\n // Consider \"in shadow\" if camera is in the lower third of fog volume\n // This is a rough approximation - works for indoor/cave scenarios\n const lowerThird = fogBottom + (fogTop - fogBottom) * 0.33\n if (cameraPos[1] < lowerThird) {\n return true\n }\n\n // Also check if main light direction is mostly blocked (sun very low)\n // This helps with sunset/sunrise scenarios\n const mainLightDir = this.engine?.settings?.mainLight?.direction\n if (mainLightDir) {\n // If sun direction Y component is positive (sun below horizon in our convention)\n // or very low angle, consider shadowed\n const sunAngle = mainLightDir[1] // Y component of direction\n if (sunAngle > 0.7) { // Sun mostly pointing up = below horizon\n return true\n }\n }\n\n return false\n }\n\n _updateRaymarchUniforms(camera, mainLight, time, lightCount) {\n const { device } = this.engine\n\n // Verify camera matrices exist\n if (!camera.iProj || !camera.iView) {\n console.warn('VolumetricFogPass: Camera missing iProj or iView matrices')\n return\n }\n\n const invProj = camera.iProj\n const invView = camera.iView\n const cameraPos = camera.position || [0, 0, 0]\n\n const mainLightEnabled = mainLight?.enabled !== false\n const mainLightDir = mainLight?.direction ?? [-1.0, 1.0, -0.5]\n const mainLightColor = mainLight?.color ?? [1, 0.95, 0.9]\n const mainLightIntensity = mainLightEnabled ? (mainLight?.intensity ?? 1.0) : 0.0\n\n const fogColor = this.fogSettings.color ?? [0.8, 0.85, 0.9]\n const heightFade = this.heightRange\n\n const shadowsEnabled = this.shadowsEnabled && this.shadowPass != null\n\n const data = new Float32Array(64)\n let offset = 0\n\n // mat4 inverseProjection\n data.set(invProj, offset); offset += 16\n\n // mat4 inverseView\n data.set(invView, offset); offset += 16\n\n // vec3 cameraPosition + nearPlane\n data[offset++] = cameraPos[0]\n data[offset++] = cameraPos[1]\n data[offset++] = cameraPos[2]\n data[offset++] = camera.near ?? 0.1\n\n // farPlane + maxSamples + time + fogDensity\n data[offset++] = camera.far ?? 1000\n data[offset++] = this.maxSamples\n data[offset++] = time\n data[offset++] = this.fogDensity\n\n // vec3 fogColor + shadowsEnabled\n data[offset++] = fogColor[0]\n data[offset++] = fogColor[1]\n data[offset++] = fogColor[2]\n data[offset++] = shadowsEnabled ? 1.0 : 0.0\n\n // vec3 mainLightDir + mainLightIntensity\n data[offset++] = mainLightDir[0]\n data[offset++] = mainLightDir[1]\n data[offset++] = mainLightDir[2]\n data[offset++] = mainLightIntensity\n\n // vec3 mainLightColor + scatterStrength\n data[offset++] = mainLightColor[0]\n data[offset++] = mainLightColor[1]\n data[offset++] = mainLightColor[2]\n data[offset++] = this.scatterStrength\n\n // vec2 fogHeightFade + maxDistance + lightCount\n data[offset++] = heightFade[0]\n data[offset++] = heightFade[1]\n data[offset++] = this.maxDistance\n data[offset++] = lightCount\n\n // debugMode + noiseStrength + noiseAnimated + mainLightScatter (adaptive)\n data[offset++] = this.debugMode\n data[offset++] = this.noiseStrength\n data[offset++] = this.noiseAnimated ? 1.0 : 0.0\n data[offset++] = this._currentMainLightScatter // Uses adaptive value\n\n // noiseScale + mainLightSaturation (ends the struct)\n data[offset++] = this.noiseScale\n data[offset++] = this.mainLightSaturation\n\n device.queue.writeBuffer(this.raymarchUniformBuffer, 0, data)\n }\n\n _updateBlurUniforms(buffer, dirX, dirY) {\n const data = new Float32Array([\n dirX, dirY,\n 1.0 / this.renderWidth, 1.0 / this.renderHeight,\n this.blurRadius, 0, 0, 0,\n ])\n this.engine.device.queue.writeBuffer(buffer, 0, data)\n }\n\n _updateCompositeUniforms() {\n const data = new Float32Array([\n this.canvasWidth, this.canvasHeight,\n this.renderWidth, this.renderHeight,\n 1.0 / this.canvasWidth, 1.0 / this.canvasHeight,\n this.brightnessThreshold, this.minVisibility,\n this.skyBrightness, 0, // skyBrightness + padding\n 0, 0, // extra padding to 48 bytes\n ])\n this.engine.device.queue.writeBuffer(this.compositeUniformBuffer, 0, data)\n }\n\n _createRaymarchBindGroup() {\n const { device } = this.engine\n\n const depthTexture = this.gbuffer?.depth\n if (!depthTexture) return null\n\n const cascadeShadows = this.shadowPass?.getShadowMap?.() ?? this.fallbackCascadeShadowMap\n const cascadeMatrices = this.shadowPass?.getCascadeMatricesBuffer?.() ?? this.fallbackCascadeMatrices\n const shadowSampler = this.shadowPass?.getShadowSampler?.() ?? this.fallbackShadowSampler\n\n // Light resources\n const lightsBuffer = this.lightingPass?.getLightBuffer?.() ?? this.fallbackLightsBuffer\n const spotShadowAtlas = this.shadowPass?.getSpotShadowAtlasView?.() ?? this.fallbackSpotShadowAtlas.createView()\n const spotMatrices = this.shadowPass?.getSpotMatricesBuffer?.() ?? this.fallbackSpotMatrices\n\n return device.createBindGroup({\n label: 'Volumetric Raymarch Bind Group',\n layout: this.raymarchBGL,\n entries: [\n { binding: 0, resource: { buffer: this.raymarchUniformBuffer } },\n { binding: 1, resource: depthTexture.texture.createView({ aspect: 'depth-only' }) },\n { binding: 2, resource: cascadeShadows.createView({ dimension: '2d-array', aspect: 'depth-only' }) },\n { binding: 3, resource: shadowSampler },\n { binding: 4, resource: { buffer: cascadeMatrices } },\n { binding: 5, resource: { buffer: lightsBuffer } },\n { binding: 6, resource: spotShadowAtlas },\n { binding: 7, resource: { buffer: spotMatrices } },\n ],\n })\n }\n\n _createBlurBindGroup(inputTexture, uniformBuffer) {\n return this.engine.device.createBindGroup({\n label: 'Volumetric Blur Bind Group',\n layout: this.blurBGL,\n entries: [\n { binding: 0, resource: { buffer: uniformBuffer } },\n { binding: 1, resource: inputTexture.view },\n { binding: 2, resource: this.linearSampler },\n ],\n })\n }\n\n _createCompositeBindGroup() {\n if (!this.inputTexture) return null\n\n const depthTexture = this.gbuffer?.depth\n if (!depthTexture) return null\n\n return this.engine.device.createBindGroup({\n label: 'Volumetric Composite Bind Group',\n layout: this.compositeBGL,\n entries: [\n { binding: 0, resource: { buffer: this.compositeUniformBuffer } },\n { binding: 1, resource: this.inputTexture.view },\n { binding: 2, resource: this.blurredTexture.view },\n { binding: 3, resource: this.linearSampler },\n { binding: 4, resource: depthTexture.texture.createView({ aspect: 'depth-only' }) },\n ],\n })\n }\n\n _destroyTextures() {\n const textures = [this.raymarchTexture, this.blurTempTexture, this.blurredTexture, this.outputTexture]\n for (const tex of textures) {\n if (tex?.texture) tex.texture.destroy()\n }\n this.raymarchTexture = null\n this.blurTempTexture = null\n this.blurredTexture = null\n this.outputTexture = null\n }\n\n async _resize(width, height) {\n await this._createResources(width, height)\n }\n\n _destroy() {\n this._destroyTextures()\n\n const buffers = [this.raymarchUniformBuffer, this.blurHUniformBuffer, this.blurVUniformBuffer, this.compositeUniformBuffer]\n for (const buf of buffers) {\n if (buf) buf.destroy()\n }\n\n this.raymarchPipeline = null\n this.blurHPipeline = null\n this.blurVPipeline = null\n this.compositePipeline = null\n }\n}\n\nexport { VolumetricFogPass }\n","struct VertexOutput {\n @builtin(position) position : vec4<f32>,\n @location(0) uv : vec2<f32>,\n}\n\nstruct Uniforms {\n canvasSize: vec2f,\n noiseParams: vec4f, // x = size, y = offsetX, z = offsetY, w = fxaaEnabled\n ditherParams: vec4f, // x = enabled, y = colorLevels (32 = 5-bit PS1 style), z = tonemapMode (0=ACES, 1=Reinhard, 2=None/Linear), w = unused\n bloomParams: vec4f, // x = enabled, y = intensity, z = radius (mip levels to sample), w = mipCount\n}\n\n@group(0) @binding(0) var<uniform> uniforms: Uniforms;\n@group(0) @binding(1) var inputTexture : texture_2d<f32>;\n@group(0) @binding(2) var inputSampler : sampler;\n@group(0) @binding(3) var noiseTexture : texture_2d<f32>;\n@group(0) @binding(4) var noiseSampler : sampler;\n@group(0) @binding(5) var bloomTexture : texture_2d<f32>;\n@group(0) @binding(6) var bloomSampler : sampler;\n@group(0) @binding(7) var guiTexture : texture_2d<f32>;\n@group(0) @binding(8) var guiSampler : sampler;\n\n// FXAA constants - more aggressive settings for visible effect\nconst FXAA_EDGE_THRESHOLD: f32 = 0.125; // Lower = more edges detected (was 0.0625)\nconst FXAA_EDGE_THRESHOLD_MIN: f32 = 0.0156; // Lower = catch more subtle edges (was 0.0312)\nconst FXAA_SUBPIX_QUALITY: f32 = 1.0; // Higher = more sub-pixel smoothing (was 0.75)\n\n// Sample noise for dithering\nfn sampleNoise(screenPos: vec2f) -> f32 {\n let noiseSize = i32(uniforms.noiseParams.x);\n let noiseOffsetX = i32(uniforms.noiseParams.y * f32(noiseSize));\n let noiseOffsetY = i32(uniforms.noiseParams.z * f32(noiseSize));\n\n let texCoord = vec2i(\n (i32(screenPos.x) + noiseOffsetX) % noiseSize,\n (i32(screenPos.y) + noiseOffsetY) % noiseSize\n );\n return textureLoad(noiseTexture, texCoord, 0).r;\n}\n\n// Convert RGB to luminance\nfn rgb2luma(rgb: vec3f) -> f32 {\n return dot(rgb, vec3f(0.299, 0.587, 0.114));\n}\n\n// Load pixel with clamping\nfn loadPixel(coord: vec2i) -> vec3f {\n let size = vec2i(textureDimensions(inputTexture, 0));\n let clampedCoord = clamp(coord, vec2i(0), size - vec2i(1));\n return textureLoad(inputTexture, clampedCoord, 0).rgb;\n}\n\n// Manual bilinear interpolation using textureLoad\nfn sampleBilinear(uv: vec2f) -> vec3f {\n let texSize = vec2f(textureDimensions(inputTexture, 0));\n let texelPos = uv * texSize - 0.5;\n let baseCoord = vec2i(floor(texelPos));\n let frac = fract(texelPos);\n\n let c00 = loadPixel(baseCoord);\n let c10 = loadPixel(baseCoord + vec2i(1, 0));\n let c01 = loadPixel(baseCoord + vec2i(0, 1));\n let c11 = loadPixel(baseCoord + vec2i(1, 1));\n\n let c0 = mix(c00, c10, frac.x);\n let c1 = mix(c01, c11, frac.x);\n return mix(c0, c1, frac.y);\n}\n\n// Simplified FXAA without early returns or textureSample\nfn fxaa(uv: vec2f) -> vec3f {\n let texSize = vec2f(textureDimensions(inputTexture, 0));\n let pixelCoord = vec2i(uv * texSize);\n\n // Sample center and neighbors\n let rgbM = loadPixel(pixelCoord);\n let rgbN = loadPixel(pixelCoord + vec2i(0, -1));\n let rgbS = loadPixel(pixelCoord + vec2i(0, 1));\n let rgbW = loadPixel(pixelCoord + vec2i(-1, 0));\n let rgbE = loadPixel(pixelCoord + vec2i(1, 0));\n let rgbNW = loadPixel(pixelCoord + vec2i(-1, -1));\n let rgbNE = loadPixel(pixelCoord + vec2i(1, -1));\n let rgbSW = loadPixel(pixelCoord + vec2i(-1, 1));\n let rgbSE = loadPixel(pixelCoord + vec2i(1, 1));\n\n // Get luma values\n let lumaM = rgb2luma(rgbM);\n let lumaN = rgb2luma(rgbN);\n let lumaS = rgb2luma(rgbS);\n let lumaW = rgb2luma(rgbW);\n let lumaE = rgb2luma(rgbE);\n let lumaNW = rgb2luma(rgbNW);\n let lumaNE = rgb2luma(rgbNE);\n let lumaSW = rgb2luma(rgbSW);\n let lumaSE = rgb2luma(rgbSE);\n\n // Luma range\n let lumaMin = min(lumaM, min(min(lumaN, lumaS), min(lumaW, lumaE)));\n let lumaMax = max(lumaM, max(max(lumaN, lumaS), max(lumaW, lumaE)));\n let lumaRange = lumaMax - lumaMin;\n\n // Edge threshold check - compute but don't early return\n let isEdge = lumaRange >= max(FXAA_EDGE_THRESHOLD_MIN, lumaMax * FXAA_EDGE_THRESHOLD);\n\n // Compute sub-pixel aliasing factor\n let lumaL = (lumaN + lumaS + lumaW + lumaE) * 0.25;\n let rangeL = abs(lumaL - lumaM);\n var blendL = max(0.0, (rangeL / max(lumaRange, 0.0001)) - 0.25) * (1.0 / 0.75);\n blendL = min(1.0, blendL) * blendL * FXAA_SUBPIX_QUALITY;\n\n // Determine edge orientation\n let edgeHorz = abs((lumaNW + lumaNE) - 2.0 * lumaN) +\n 2.0 * abs((lumaW + lumaE) - 2.0 * lumaM) +\n abs((lumaSW + lumaSE) - 2.0 * lumaS);\n let edgeVert = abs((lumaNW + lumaSW) - 2.0 * lumaW) +\n 2.0 * abs((lumaN + lumaS) - 2.0 * lumaM) +\n abs((lumaNE + lumaSE) - 2.0 * lumaE);\n let isHorizontal = edgeHorz >= edgeVert;\n\n // Choose gradient direction\n let luma1 = select(lumaE, lumaS, isHorizontal);\n let luma2 = select(lumaW, lumaN, isHorizontal);\n let gradient1 = abs(luma1 - lumaM);\n let gradient2 = abs(luma2 - lumaM);\n let is1Steepest = gradient1 >= gradient2;\n let gradientScaled = 0.25 * max(gradient1, gradient2);\n\n // Step direction\n let stepSign = select(-1.0, 1.0, is1Steepest);\n let lumaLocalAverage = 0.5 * (select(luma2, luma1, is1Steepest) + lumaM);\n\n // Search for edge endpoints (simplified - 4 steps each direction)\n let searchDir = select(vec2i(0, 1), vec2i(1, 0), isHorizontal);\n\n let luma1_1 = rgb2luma(loadPixel(pixelCoord - searchDir)) - lumaLocalAverage;\n let luma2_1 = rgb2luma(loadPixel(pixelCoord + searchDir)) - lumaLocalAverage;\n let luma1_2 = rgb2luma(loadPixel(pixelCoord - searchDir * 2)) - lumaLocalAverage;\n let luma2_2 = rgb2luma(loadPixel(pixelCoord + searchDir * 2)) - lumaLocalAverage;\n let luma1_3 = rgb2luma(loadPixel(pixelCoord - searchDir * 3)) - lumaLocalAverage;\n let luma2_3 = rgb2luma(loadPixel(pixelCoord + searchDir * 3)) - lumaLocalAverage;\n let luma1_4 = rgb2luma(loadPixel(pixelCoord - searchDir * 4)) - lumaLocalAverage;\n let luma2_4 = rgb2luma(loadPixel(pixelCoord + searchDir * 4)) - lumaLocalAverage;\n\n // Find distance to edge end - check from closest to farthest\n let reached1_1 = abs(luma1_1) >= gradientScaled;\n let reached1_2 = abs(luma1_2) >= gradientScaled;\n let reached1_3 = abs(luma1_3) >= gradientScaled;\n let reached2_1 = abs(luma2_1) >= gradientScaled;\n let reached2_2 = abs(luma2_2) >= gradientScaled;\n let reached2_3 = abs(luma2_3) >= gradientScaled;\n\n // Distance = first position where edge was found (or 4 if not found)\n let dist1 = select(select(select(4.0, 3.0, reached1_3), 2.0, reached1_2), 1.0, reached1_1);\n let dist2 = select(select(select(4.0, 3.0, reached2_3), 2.0, reached2_2), 1.0, reached2_1);\n\n // Get the luma at the edge end\n let lumaEnd1 = select(select(select(luma1_4, luma1_3, reached1_3), luma1_2, reached1_2), luma1_1, reached1_1);\n let lumaEnd2 = select(select(select(luma2_4, luma2_3, reached2_3), luma2_2, reached2_2), luma2_1, reached2_1);\n\n // Compute offset\n let distFinal = min(dist1, dist2);\n let edgeThickness = dist1 + dist2;\n let lumaEndCloser = select(lumaEnd2, lumaEnd1, dist1 < dist2);\n let correctVariation = (lumaEndCloser < 0.0) != (lumaM < lumaLocalAverage);\n var pixelOffset = select(0.0, -distFinal / max(edgeThickness, 0.0001) + 0.5, correctVariation);\n\n // Ensure minimum blending for detected edges\n let finalOffset = max(max(pixelOffset, blendL), 0.5);\n\n // Compute final UV\n let inverseVP = 1.0 / texSize;\n var finalUv = uv;\n let offsetAmount = finalOffset * stepSign;\n finalUv.x += select(offsetAmount * inverseVP.x, 0.0, isHorizontal);\n finalUv.y += select(0.0, offsetAmount * inverseVP.y, isHorizontal);\n\n // Sample at offset position using bilinear\n let offsetColor = sampleBilinear(finalUv);\n\n // Blend original with offset sample for smoother result\n // Also blend with perpendicular neighbors for better coverage\n let perpDir = select(vec2i(0, 1), vec2i(1, 0), isHorizontal);\n let perpColor1 = loadPixel(pixelCoord + perpDir);\n let perpColor2 = loadPixel(pixelCoord - perpDir);\n let neighborAvg = (perpColor1 + perpColor2) * 0.5;\n\n // Weighted blend: offset sample + neighbor average + original\n let fxaaColor = mix(mix(offsetColor, neighborAvg, 0.7), rgbM, 0.1);\n\n // Return original if not an edge, otherwise return FXAA result\n return select(rgbM, fxaaColor, isEdge);\n}\n\n// Sample bloom texture (already blurred by BloomPass)\n// Masked by scene brightness - bloom only shows in darker areas around bright pixels\nfn sampleBloom(uv: vec2f, sceneBrightness: f32) -> vec3f {\n let bloom = textureSample(bloomTexture, bloomSampler, uv).rgb;\n\n // Mask: bloom is visible where scene is dark, fades out where scene is bright\n // Using smooth falloff so bloom doesn't abruptly cut off\n let threshold = 0.5; // Start fading bloom at this brightness\n let mask = saturate(1.0 - (sceneBrightness - threshold) / (1.0 - threshold));\n\n return bloom * mask * mask; // Square for smoother falloff\n}\n\n// ACES tone mapping\nfn aces_tone_map(hdr: vec3<f32>) -> vec3<f32> {\n let m1 = mat3x3(\n 0.59719, 0.07600, 0.02840,\n 0.35458, 0.90834, 0.13383,\n 0.04823, 0.01566, 0.83777,\n );\n let m2 = mat3x3(\n 1.60475, -0.10208, -0.00327,\n -0.53108, 1.10813, -0.07276,\n -0.07367, -0.00605, 1.07602,\n );\n let v = m1 * hdr;\n let a = v * (v + 0.0245786) - 0.000090537;\n let b = v * (0.983729 * v + 0.4329510) + 0.238081;\n return clamp(m2 * (a / b), vec3(0.0), vec3(1.0));\n}\n\n// Reinhard tone mapping\nfn reinhard_tone_map(hdr: vec3<f32>) -> vec3<f32> {\n return hdr / (hdr + vec3(1.0));\n}\n\n// No tone mapping - just gamma correction and clamp\nfn linear_tone_map(hdr: vec3<f32>) -> vec3<f32> {\n return clamp(hdr, vec3(0.0), vec3(1.0));\n}\n\n// Apply selected tone mapping\nfn apply_tone_map(hdr: vec3<f32>, mode: i32) -> vec3<f32> {\n if (mode == 1) {\n return reinhard_tone_map(hdr);\n } else if (mode == 2) {\n return linear_tone_map(hdr);\n }\n return aces_tone_map(hdr); // Default: ACES (mode 0)\n}\n\n@vertex\nfn vertexMain(@builtin(vertex_index) vertexIndex : u32) -> VertexOutput {\n var output : VertexOutput;\n let x = f32(vertexIndex & 1u) * 4.0 - 1.0;\n let y = f32(vertexIndex >> 1u) * 4.0 - 1.0;\n output.position = vec4<f32>(x, y, 0.0, 1.0);\n output.uv = vec2<f32>((x + 1.0) * 0.5, (1.0 - y) * 0.5);\n return output;\n}\n\n@fragment\nfn fragmentMain(input: VertexOutput) -> @location(0) vec4<f32> {\n // Always sample - choose FXAA or direct based on uniform\n let fxaaEnabled = uniforms.noiseParams.w > 0.5;\n let fxaaColor = fxaa(input.uv);\n let directColor = textureSample(inputTexture, inputSampler, input.uv).rgb;\n var color = select(directColor, fxaaColor, fxaaEnabled);\n\n // Add bloom before tone mapping (in HDR space)\n // Bloom is masked by scene brightness - only shows in darker areas around bright pixels\n if (uniforms.bloomParams.x > 0.5) {\n let sceneBrightness = rgb2luma(color);\n let bloom = sampleBloom(input.uv, sceneBrightness);\n color += bloom * uniforms.bloomParams.y; // y = intensity\n }\n\n let tonemapMode = i32(uniforms.ditherParams.z);\n var sdr = apply_tone_map(color, tonemapMode);\n\n // Blend GUI overlay (after tone mapping, before dithering)\n // GUI is premultiplied alpha blending\n let gui = textureSample(guiTexture, guiSampler, input.uv);\n sdr = sdr * (1.0 - gui.a) + gui.rgb;\n\n // PS1-style ordered dithering with configurable color levels\n // Quantizes to reduced bit depth (e.g., 32 levels = 5-bit per channel)\n if (uniforms.ditherParams.x > 0.5 && uniforms.noiseParams.x > 0.0) {\n let levels = uniforms.ditherParams.y;\n let noise = sampleNoise(input.position.xy);\n\n // Scale color to target levels, add dither, round, scale back\n // dither value is -0.5 to +0.5, which shifts the rounding threshold\n let dither = noise - 0.5;\n sdr = floor(sdr * (levels - 1.0) + dither + 0.5) / (levels - 1.0);\n sdr = clamp(sdr, vec3f(0.0), vec3f(1.0));\n }\n\n return vec4<f32>(sdr, 1.0);\n}\n","import { BasePass } from \"./BasePass.js\"\nimport { Pipeline } from \"../../Pipeline.js\"\n\nimport postProcessingWGSL from \"../shaders/postproc.wgsl\"\n\n/**\n * PostProcessPass - Final post-processing and tone mapping\n *\n * Pass 7 in the 7-pass pipeline (final pass).\n * Applies tone mapping and outputs to the canvas.\n *\n * Input: HDR lit image from LightingPass\n * Output: Final SDR image to canvas\n */\nclass PostProcessPass extends BasePass {\n constructor(engine = null) {\n super('PostProcess', engine)\n\n this.pipeline = null\n this.inputTexture = null\n this.bloomTexture = null\n this.dummyBloomTexture = null // 1x1 black texture when bloom disabled\n this.noiseTexture = null\n this.noiseSize = 64\n this.noiseAnimated = true\n this.guiCanvas = null // 2D canvas for GUI overlay\n this.guiTexture = null // GPU texture for GUI\n this.guiSampler = null\n\n // CRT support: intermediate texture when CRT is enabled\n this.intermediateTexture = null\n this._outputWidth = 0\n this._outputHeight = 0\n this._lastOutputToTexture = false // Track output mode changes\n\n // Store resize dimensions for runtime texture creation\n this._resizeWidth = 0\n this._resizeHeight = 0\n }\n\n // Convenience getter for exposure setting\n get exposure() { return this.settings?.environment?.exposure ?? 1.6 }\n\n // Convenience getter for fxaa setting\n get fxaa() { return this.settings?.rendering?.fxaa ?? true }\n\n // Convenience getter for dithering settings\n get ditheringEnabled() { return this.settings?.dithering?.enabled ?? true }\n get colorLevels() { return this.settings?.dithering?.colorLevels ?? 32 }\n\n // Tonemap mode: 0=ACES, 1=Reinhard, 2=None/Linear\n get tonemapMode() { return this.settings?.rendering?.tonemapMode ?? 0 }\n\n // Convenience getters for bloom settings\n get bloomEnabled() { return this.settings?.bloom?.enabled ?? true }\n get bloomIntensity() { return this.settings?.bloom?.intensity ?? 1.0 }\n get bloomRadius() { return this.settings?.bloom?.radius ?? 5 }\n\n // CRT settings (determines if we output to intermediate texture)\n get crtEnabled() { return this.settings?.crt?.enabled ?? false }\n get crtUpscaleEnabled() { return this.settings?.crt?.upscaleEnabled ?? false }\n get shouldOutputToTexture() { return this.crtEnabled || this.crtUpscaleEnabled }\n\n /**\n * Set the input texture (HDR image from LightingPass)\n * @param {Texture} texture - Input HDR texture\n */\n setInputTexture(texture) {\n if (this.inputTexture !== texture) {\n this.inputTexture = texture\n this._needsRebuild = true\n }\n }\n\n /**\n * Set the bloom texture (from BloomPass)\n * @param {Object} bloomTexture - Bloom texture with mip levels\n */\n setBloomTexture(bloomTexture) {\n if (this.bloomTexture !== bloomTexture) {\n this.bloomTexture = bloomTexture\n this._needsRebuild = true\n }\n }\n\n /**\n * Set the noise texture for dithering\n * @param {Texture} texture - Noise texture (blue noise or bayer dither)\n * @param {number} size - Texture size\n * @param {boolean} animated - Whether to animate noise offset each frame\n */\n setNoise(texture, size = 64, animated = true) {\n this.noiseTexture = texture\n this.noiseSize = size\n this.noiseAnimated = animated\n this._needsRebuild = true\n }\n\n /**\n * Set the GUI canvas for overlay rendering\n * @param {HTMLCanvasElement} canvas - 2D canvas with GUI content\n */\n setGuiCanvas(canvas) {\n this.guiCanvas = canvas\n }\n\n /**\n * Get the output texture (for CRT pass to use)\n * Returns null if outputting directly to canvas\n */\n getOutputTexture() {\n return this.shouldOutputToTexture ? this.intermediateTexture : null\n }\n\n /**\n * Create or resize the intermediate texture for CRT\n */\n async _createIntermediateTexture(width, height) {\n if (!this.shouldOutputToTexture) {\n // Destroy if no longer needed\n if (this.intermediateTexture?.texture) {\n this.intermediateTexture.texture.destroy()\n this.intermediateTexture = null\n }\n return\n }\n\n // Skip if size hasn't changed\n if (this._outputWidth === width && this._outputHeight === height && this.intermediateTexture) {\n return\n }\n\n const { device } = this.engine\n\n // Destroy old texture\n if (this.intermediateTexture?.texture) {\n this.intermediateTexture.texture.destroy()\n }\n\n // Create intermediate texture (SDR format for CRT input)\n const texture = device.createTexture({\n label: 'PostProcess Intermediate',\n size: [width, height, 1],\n format: 'rgba8unorm',\n usage: GPUTextureUsage.TEXTURE_BINDING |\n GPUTextureUsage.RENDER_ATTACHMENT |\n GPUTextureUsage.COPY_SRC,\n })\n\n const sampler = device.createSampler({\n label: 'PostProcess Intermediate Sampler',\n minFilter: 'nearest',\n magFilter: 'nearest',\n })\n\n this.intermediateTexture = {\n texture,\n view: texture.createView(),\n sampler,\n width,\n height,\n format: 'rgba8unorm', // Required by Pipeline.create()\n }\n\n this._outputWidth = width\n this._outputHeight = height\n this._needsRebuild = true\n\n console.log(`PostProcessPass: Created intermediate texture ${width}x${height} for CRT`)\n }\n\n async _init() {\n // Create dummy 1x1 black bloom texture for when bloom is disabled\n // This ensures shader bindings are always valid\n const { device } = this.engine\n\n const dummyTexture = device.createTexture({\n label: 'Dummy Bloom Texture',\n size: [1, 1, 1],\n format: 'rgba16float',\n mipLevelCount: 1,\n usage: GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_DST,\n })\n\n // Fill with black (no bloom)\n device.queue.writeTexture(\n { texture: dummyTexture },\n new Float32Array([0, 0, 0, 0]).buffer,\n { bytesPerRow: 8 },\n { width: 1, height: 1 }\n )\n\n const dummySampler = device.createSampler({\n label: 'Dummy Bloom Sampler',\n minFilter: 'linear',\n magFilter: 'linear',\n })\n\n this.dummyBloomTexture = {\n texture: dummyTexture,\n view: dummyTexture.createView(),\n sampler: dummySampler,\n mipCount: 1,\n }\n\n // Create sampler for GUI texture\n this.guiSampler = device.createSampler({\n label: 'GUI Sampler',\n minFilter: 'linear',\n magFilter: 'linear',\n })\n\n // Create dummy 1x1 transparent GUI texture\n const dummyGuiTexture = device.createTexture({\n label: 'Dummy GUI Texture',\n size: [1, 1, 1],\n format: 'rgba8unorm',\n usage: GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_DST,\n })\n device.queue.writeTexture(\n { texture: dummyGuiTexture },\n new Uint8Array([0, 0, 0, 0]),\n { bytesPerRow: 4 },\n { width: 1, height: 1 }\n )\n this.dummyGuiTexture = {\n texture: dummyGuiTexture,\n view: dummyGuiTexture.createView(),\n sampler: this.guiSampler,\n }\n }\n\n /**\n * Build or rebuild the pipeline\n */\n async _buildPipeline() {\n if (!this.inputTexture) {\n return\n }\n\n const textures = [this.inputTexture]\n if (this.noiseTexture) {\n textures.push(this.noiseTexture)\n }\n // Always include bloom texture (real or dummy) so shader bindings are valid\n const effectiveBloomTexture = this.bloomTexture || this.dummyBloomTexture\n textures.push(effectiveBloomTexture)\n\n // Always include GUI texture (real or dummy) so shader bindings are valid\n const effectiveGuiTexture = this.guiTexture || this.dummyGuiTexture\n textures.push(effectiveGuiTexture)\n\n const hasBloom = this.bloomTexture && this.bloomEnabled\n\n // Determine render target: intermediate texture (for CRT) or canvas\n const renderTarget = this.shouldOutputToTexture && this.intermediateTexture\n ? this.intermediateTexture\n : null\n\n this.pipeline = await Pipeline.create(this.engine, {\n label: 'postProcess',\n wgslSource: postProcessingWGSL,\n isPostProcessing: true,\n textures: textures,\n uniforms: () => ({\n noiseParams: [this.noiseSize, this.noiseAnimated ? Math.random() : 0, this.noiseAnimated ? Math.random() : 0, this.fxaa ? 1.0 : 0.0],\n ditherParams: [this.ditheringEnabled ? 1.0 : 0.0, this.colorLevels, this.tonemapMode, 0],\n bloomParams: [hasBloom ? 1.0 : 0.0, this.bloomIntensity, this.bloomRadius, effectiveBloomTexture?.mipCount ?? 1]\n }),\n renderTarget: renderTarget,\n })\n\n this._needsRebuild = false\n }\n\n async _execute(context) {\n const { device, canvas } = this.engine\n\n // Check if CRT state changed (need to switch between canvas/texture output)\n const needsOutputToTexture = this.shouldOutputToTexture\n const hasIntermediateTexture = !!this.intermediateTexture\n\n // Detect output mode change\n if (needsOutputToTexture !== this._lastOutputToTexture) {\n this._lastOutputToTexture = needsOutputToTexture\n this._needsRebuild = true\n\n if (needsOutputToTexture && !hasIntermediateTexture) {\n // CRT was just enabled - create intermediate texture at stored resize dimensions\n // Use resize dimensions (render-scaled), not canvas dimensions (full resolution)\n const w = this._resizeWidth || canvas.width\n const h = this._resizeHeight || canvas.height\n await this._createIntermediateTexture(w, h)\n } else if (!needsOutputToTexture && hasIntermediateTexture) {\n // CRT was just disabled - destroy intermediate texture\n if (this.intermediateTexture?.texture) {\n this.intermediateTexture.texture.destroy()\n this.intermediateTexture = null\n }\n }\n }\n\n // Update GUI texture from canvas if available\n if (this.guiCanvas && this.guiCanvas.width > 0 && this.guiCanvas.height > 0) {\n // Check if we need to recreate the texture (size changed)\n const needsNewTexture = !this.guiTexture ||\n this.guiTexture.width !== this.guiCanvas.width ||\n this.guiTexture.height !== this.guiCanvas.height\n\n if (needsNewTexture) {\n // Destroy old texture if it exists\n if (this.guiTexture?.texture) {\n this.guiTexture.texture.destroy()\n }\n\n // Create new texture matching canvas size\n const texture = device.createTexture({\n label: 'GUI Texture',\n size: [this.guiCanvas.width, this.guiCanvas.height, 1],\n format: 'rgba8unorm',\n usage: GPUTextureUsage.TEXTURE_BINDING |\n GPUTextureUsage.COPY_DST |\n GPUTextureUsage.RENDER_ATTACHMENT,\n })\n\n this.guiTexture = {\n texture: texture,\n view: texture.createView(),\n sampler: this.guiSampler,\n width: this.guiCanvas.width,\n height: this.guiCanvas.height,\n }\n\n // Force pipeline rebuild to use new texture\n this._needsRebuild = true\n }\n\n // Copy canvas content to GPU texture\n device.queue.copyExternalImageToTexture(\n { source: this.guiCanvas },\n { texture: this.guiTexture.texture },\n [this.guiCanvas.width, this.guiCanvas.height]\n )\n }\n\n // Rebuild pipeline if needed\n if (this._needsRebuild) {\n await this._buildPipeline()\n }\n\n // If rebuild was attempted but failed, don't use stale pipeline with old bind groups\n if (!this.pipeline || this._needsRebuild) {\n console.warn('PostProcessPass: Pipeline not ready')\n return\n }\n\n // Determine if bloom is effectively enabled\n const hasBloom = this.bloomTexture && this.bloomEnabled\n const effectiveBloomTexture = this.bloomTexture || this.dummyBloomTexture\n\n // Update uniforms each frame\n this.pipeline.uniformValues.set({\n noiseParams: [this.noiseSize, this.noiseAnimated ? Math.random() : 0, this.noiseAnimated ? Math.random() : 0, this.fxaa ? 1.0 : 0.0],\n ditherParams: [this.ditheringEnabled ? 1.0 : 0.0, this.colorLevels, this.tonemapMode, 0],\n bloomParams: [hasBloom ? 1.0 : 0.0, this.bloomIntensity, this.bloomRadius, effectiveBloomTexture?.mipCount ?? 1]\n })\n\n // Render to canvas\n this.pipeline.render()\n }\n\n async _resize(width, height) {\n // Store resize dimensions for runtime texture creation\n this._resizeWidth = width\n this._resizeHeight = height\n\n // Create intermediate texture for CRT if enabled\n await this._createIntermediateTexture(width, height)\n\n // Pipeline needs rebuild since canvas size changed\n this._needsRebuild = true\n }\n\n _destroy() {\n this.pipeline = null\n if (this.dummyBloomTexture?.texture) {\n this.dummyBloomTexture.texture.destroy()\n this.dummyBloomTexture = null\n }\n if (this.guiTexture?.texture) {\n this.guiTexture.texture.destroy()\n this.guiTexture = null\n }\n if (this.dummyGuiTexture?.texture) {\n this.dummyGuiTexture.texture.destroy()\n this.dummyGuiTexture = null\n }\n if (this.intermediateTexture?.texture) {\n this.intermediateTexture.texture.destroy()\n this.intermediateTexture = null\n }\n }\n}\n\nexport { PostProcessPass }\n","// CRT Effect Shader\n// Simulates a CRT monitor with geometry distortion, scanlines,\n// RGB convergence errors, phosphor mask, and vignette.\n\nstruct VertexOutput {\n @builtin(position) position: vec4f,\n @location(0) uv: vec2f,\n}\n\nstruct Uniforms {\n canvasSize: vec2f, // Output canvas size\n inputSize: vec2f, // Upscaled input texture size\n renderSize: vec2f, // Original render size (before upscale)\n\n // Geometry\n curvature: f32, // Screen curvature (0-0.15)\n cornerRadius: f32, // Corner rounding (0-0.1)\n zoom: f32, // Zoom to compensate for curvature shrinkage\n _padGeom: f32,\n\n // Scanlines\n scanlineIntensity: f32, // Scanline darkness (0-1)\n scanlineWidth: f32, // Beam width (0=thin line, 1=no gap)\n scanlineBrightBoost: f32, // Bright pixels widen beam to fill gaps\n scanlineHeight: f32, // Scanline height in canvas pixels (e.g. 3)\n\n // Convergence (RGB X offset in source pixels)\n convergence: vec3f,\n _pad2: f32,\n\n // Phosphor mask\n maskType: f32, // 0=none, 1=aperture, 2=slot, 3=shadow\n maskIntensity: f32, // Mask strength (0-1)\n maskScale: f32, // Mask size multiplier\n maskCompensation: f32, // Pre-calculated brightness compensation\n\n // Vignette\n vignetteIntensity: f32,\n vignetteSize: f32,\n\n // Blur\n blurSize: f32, // Horizontal blur size in pixels (0-2)\n\n // Flags\n crtEnabled: f32, // 1.0 = CRT effects on, 0.0 = passthrough\n upscaleEnabled: f32, // 1.0 = use upscaled texture, 0.0 = direct\n}\n\n@group(0) @binding(0) var<uniform> uniforms: Uniforms;\n@group(0) @binding(1) var inputTexture: texture_2d<f32>;\n@group(0) @binding(2) var inputSampler: sampler; // Linear sampler for CRT\n@group(0) @binding(3) var nearestSampler: sampler; // Nearest sampler for sharp pixels\n@group(0) @binding(4) var phosphorMask: texture_2d<f32>;\n@group(0) @binding(5) var phosphorSampler: sampler;\n\n// Constants\nconst PI: f32 = 3.14159265359;\n\n// ============================================================================\n// Geometry Distortion (Barrel/Curvature)\n// ============================================================================\n\n// Apply barrel distortion to simulate CRT screen curvature\nfn applyBarrelDistortion(uv: vec2f, curvature: f32) -> vec2f {\n // Center UV around origin\n let centered = uv - 0.5;\n\n // Calculate radial distance squared from center\n let r2 = dot(centered, centered);\n\n // Apply barrel distortion\n let distorted = centered * (1.0 + curvature * r2);\n\n return distorted + 0.5;\n}\n\n// Check if UV is outside the visible area (for corner masking)\nfn getCornerMask(uv: vec2f, radius: f32) -> f32 {\n // Distance from edges\n let d = abs(uv - 0.5) * 2.0;\n\n // Rounded rectangle SDF\n let corner = max(d.x, d.y);\n let roundedCorner = length(max(d - (1.0 - radius), vec2f(0.0)));\n\n // Soft edge\n let mask = 1.0 - smoothstep(1.0 - radius * 0.5, 1.0, max(corner, roundedCorner));\n\n return mask;\n}\n\n// ============================================================================\n// Scanlines (Electron Beam Simulation - Gaussian Profile)\n// ============================================================================\n\nfn applyScanlines(color: vec3f, scanlinePos: f32, intensity: f32, width: f32, brightBoost: f32) -> vec3f {\n if (intensity <= 0.0) {\n return color;\n }\n\n // scanlinePos is position within one scanline period (0-1)\n // 0.5 = center of the beam (brightest), 0.0/1.0 = between scanlines (darkest)\n let pos = fract(scanlinePos);\n\n // Distance from scanline center (0 at center, 0.5 at edges)\n // For 3px scanlines: center px at dist=0, edge px at dist≈0.33\n let distFromCenter = abs(pos - 0.5);\n\n // Calculate beam width based on base width and brightness\n let brightness = dot(color, vec3f(0.299, 0.587, 0.114));\n\n // Width 0 = thin beam (center bright, edges black)\n // Width 1 = full beam (all pixels equally bright, no visible scanline)\n // BrightBoost: bright pixels widen beam toward filling gaps\n let baseWidth = width;\n let brightWidening = brightBoost * brightness * (1.0 - width);\n let effectiveWidth = clamp(baseWidth + brightWidening, 0.0, 1.0);\n\n // Gaussian sigma: controls how quickly brightness falls off from center\n // At width=0: sigma small → sharp falloff, edges dark\n // At width=1: sigma large → flat response, edges bright\n let sigma = mix(0.08, 0.8, effectiveWidth);\n let gaussian = exp(-0.5 * pow(distFromCenter / sigma, 2.0));\n\n // Apply intensity: 0 = no effect, 1 = full scanline effect\n let scanline = clamp(gaussian, 0.0, 1.0);\n let darkening = mix(1.0, scanline, intensity);\n\n return color * darkening;\n}\n\n// ============================================================================\n// RGB Convergence Error\n// ============================================================================\n\nfn sampleWithConvergence(uv: vec2f, convergence: vec3f, texelSize: vec2f) -> vec3f {\n // Sample each color channel with its own offset\n // Use textureSampleLevel to avoid non-uniform control flow issues\n let offsetR = vec2f(convergence.r * texelSize.x, 0.0);\n let offsetG = vec2f(convergence.g * texelSize.x, 0.0);\n let offsetB = vec2f(convergence.b * texelSize.x, 0.0);\n\n let r = textureSampleLevel(inputTexture, inputSampler, uv + offsetR, 0.0).r;\n let g = textureSampleLevel(inputTexture, inputSampler, uv + offsetG, 0.0).g;\n let b = textureSampleLevel(inputTexture, inputSampler, uv + offsetB, 0.0).b;\n\n return vec3f(r, g, b);\n}\n\n// ============================================================================\n// Phosphor Mask\n// ============================================================================\n\nfn applyPhosphorMask(color: vec3f, screenPos: vec2f, maskType: f32, intensity: f32, scale: f32) -> vec3f {\n if (intensity <= 0.0 || maskType < 0.5) {\n return color;\n }\n\n // Sample prerendered phosphor mask texture\n // Scale determines how many mask tiles fit on screen\n // Use textureSampleLevel to avoid non-uniform control flow issues\n let maskUV = screenPos / (3.0 * scale);\n let mask = textureSampleLevel(phosphorMask, phosphorSampler, maskUV, 0.0).rgb;\n\n // Blend mask with color\n // The mask modulates each RGB channel independently\n return mix(color, color * mask, intensity);\n}\n\n// Procedural aperture grille (fallback if no texture)\nfn proceduralApertureGrille(screenX: f32, scale: f32) -> vec3f {\n let phase = fract(screenX / (3.0 * scale));\n\n // RGB stripes\n var mask = vec3f(0.0);\n if (phase < 0.333) {\n mask = vec3f(1.0, 0.2, 0.2); // Red phosphor\n } else if (phase < 0.666) {\n mask = vec3f(0.2, 1.0, 0.2); // Green phosphor\n } else {\n mask = vec3f(0.2, 0.2, 1.0); // Blue phosphor\n }\n\n return mask;\n}\n\n// Procedural slot mask - height matches scanline height for alignment\nfn proceduralSlotMask(screenPos: vec2f, scale: f32, scanlineHeight: f32) -> vec3f {\n // X uses 3-pixel RGB cells, Y uses scanline height for alignment\n let cellWidth = 3.0 * scale;\n let cellHeight = max(scanlineHeight, 1.0);\n let cellPos = fract(vec2f(screenPos.x / cellWidth, screenPos.y / cellHeight));\n\n // Horizontal offset every other row for staggered pattern\n var adjusted = cellPos;\n if (fract(screenPos.y / (cellHeight * 2.0)) > 0.5) {\n adjusted.x = fract(adjusted.x + 0.5);\n }\n\n // RGB slots\n var mask = vec3f(0.0);\n if (adjusted.x < 0.333) {\n mask = vec3f(1.0, 0.1, 0.1);\n } else if (adjusted.x < 0.666) {\n mask = vec3f(0.1, 1.0, 0.1);\n } else {\n mask = vec3f(0.1, 0.1, 1.0);\n }\n\n // Vertical gaps between scanlines\n mask *= smoothstep(0.0, 0.15, cellPos.y) * smoothstep(1.0, 0.85, cellPos.y);\n\n return mask;\n}\n\n// Procedural shadow mask (delta pattern)\nfn proceduralShadowMask(screenPos: vec2f, scale: f32) -> vec3f {\n let cellSize = 2.0 * scale;\n let cell = floor(screenPos / cellSize);\n let cellPos = fract(screenPos / cellSize);\n\n // Offset pattern based on row\n let rowOffset = select(0.0, 0.5, fract(cell.y * 0.5) > 0.25);\n let adjustedX = fract(cellPos.x + rowOffset);\n\n // Circular phosphors\n let dist = length(cellPos - 0.5) * 2.0;\n let circle = 1.0 - smoothstep(0.6, 0.8, dist);\n\n // RGB color based on position\n let phase = fract((cell.x + rowOffset) / 3.0);\n var mask = vec3f(0.1);\n if (phase < 0.333) {\n mask = vec3f(1.0, 0.1, 0.1) * circle;\n } else if (phase < 0.666) {\n mask = vec3f(0.1, 1.0, 0.1) * circle;\n } else {\n mask = vec3f(0.1, 0.1, 1.0) * circle;\n }\n\n return max(mask, vec3f(0.1));\n}\n\n// ============================================================================\n// Vignette\n// ============================================================================\n\nfn applyVignette(color: vec3f, uv: vec2f, intensity: f32, size: f32) -> vec3f {\n if (intensity <= 0.0) {\n return color;\n }\n\n // Distance from center\n let centered = uv - 0.5;\n let dist = length(centered);\n\n // Smooth vignette falloff\n let vignette = 1.0 - smoothstep(size * 0.5, size, dist);\n let darkening = mix(1.0 - intensity, 1.0, vignette);\n\n return color * darkening;\n}\n\n// ============================================================================\n// Horizontal Blur (CRT beam softness)\n// ============================================================================\n\n// 5-tap Gaussian horizontal blur\nfn sampleWithHorizontalBlur(uv: vec2f, blurSize: f32, texelSize: vec2f) -> vec3f {\n if (blurSize <= 0.0) {\n return textureSampleLevel(inputTexture, inputSampler, uv, 0.0).rgb;\n }\n\n // Gaussian weights for 5 taps (sigma ~= 0.84 for nice falloff)\n // Weights: 0.0625, 0.25, 0.375, 0.25, 0.0625 (sum = 1.0)\n let w0 = 0.0625;\n let w1 = 0.25;\n let w2 = 0.375;\n\n let offset = blurSize * texelSize.x;\n\n let c0 = textureSampleLevel(inputTexture, inputSampler, uv + vec2f(-2.0 * offset, 0.0), 0.0).rgb;\n let c1 = textureSampleLevel(inputTexture, inputSampler, uv + vec2f(-1.0 * offset, 0.0), 0.0).rgb;\n let c2 = textureSampleLevel(inputTexture, inputSampler, uv, 0.0).rgb;\n let c3 = textureSampleLevel(inputTexture, inputSampler, uv + vec2f(1.0 * offset, 0.0), 0.0).rgb;\n let c4 = textureSampleLevel(inputTexture, inputSampler, uv + vec2f(2.0 * offset, 0.0), 0.0).rgb;\n\n return c0 * w0 + c1 * w1 + c2 * w2 + c3 * w1 + c4 * w0;\n}\n\n// 5-tap Gaussian horizontal blur with RGB convergence\nfn sampleWithBlurAndConvergence(uv: vec2f, convergence: vec3f, blurSize: f32, texelSize: vec2f) -> vec3f {\n if (blurSize <= 0.0) {\n // No blur, just convergence\n let offsetR = vec2f(convergence.r * texelSize.x, 0.0);\n let offsetG = vec2f(convergence.g * texelSize.x, 0.0);\n let offsetB = vec2f(convergence.b * texelSize.x, 0.0);\n let r = textureSampleLevel(inputTexture, inputSampler, uv + offsetR, 0.0).r;\n let g = textureSampleLevel(inputTexture, inputSampler, uv + offsetG, 0.0).g;\n let b = textureSampleLevel(inputTexture, inputSampler, uv + offsetB, 0.0).b;\n return vec3f(r, g, b);\n }\n\n // Gaussian weights\n let w0 = 0.0625;\n let w1 = 0.25;\n let w2 = 0.375;\n let offset = blurSize * texelSize.x;\n\n // Sample each channel with convergence offset + blur\n var r = 0.0;\n var g = 0.0;\n var b = 0.0;\n\n for (var i = -2; i <= 2; i++) {\n let weight = select(select(w1, w2, i == 0), w0, abs(i) == 2);\n let blurOffset = f32(i) * offset;\n\n r += textureSampleLevel(inputTexture, inputSampler, uv + vec2f(convergence.r * texelSize.x + blurOffset, 0.0), 0.0).r * weight;\n g += textureSampleLevel(inputTexture, inputSampler, uv + vec2f(convergence.g * texelSize.x + blurOffset, 0.0), 0.0).g * weight;\n b += textureSampleLevel(inputTexture, inputSampler, uv + vec2f(convergence.b * texelSize.x + blurOffset, 0.0), 0.0).b * weight;\n }\n\n return vec3f(r, g, b);\n}\n\n// ============================================================================\n// Main Shader\n// ============================================================================\n\n@vertex\nfn vertexMain(@builtin(vertex_index) vertexIndex: u32) -> VertexOutput {\n var output: VertexOutput;\n\n // Full-screen triangle\n let x = f32(vertexIndex & 1u) * 4.0 - 1.0;\n let y = f32(vertexIndex >> 1u) * 4.0 - 1.0;\n output.position = vec4f(x, y, 0.0, 1.0);\n output.uv = vec2f((x + 1.0) * 0.5, (1.0 - y) * 0.5);\n\n return output;\n}\n\n@fragment\nfn fragmentMain(input: VertexOutput) -> @location(0) vec4f {\n // Use fragment coordinates (input.position) for pixel-perfect effects\n let pixelPos = input.position.xy;\n var uv = input.uv;\n\n // Check if CRT effects are enabled\n let crtEnabled = uniforms.crtEnabled > 0.5;\n\n // If CRT disabled and upscale disabled, just sample directly with nearest\n // (avoids interpolation artifacts when input matches canvas size)\n if (!crtEnabled && uniforms.upscaleEnabled < 0.5) {\n return textureSampleLevel(inputTexture, nearestSampler, uv, 0.0);\n }\n\n // Apply geometry distortion (only when CRT enabled)\n var distortedUV = uv;\n var cornerMask = 1.0;\n\n if (crtEnabled && uniforms.curvature > 0.0) {\n // Apply zoom first (scales UV inward to enlarge image, compensating for curvature shrinkage)\n var zoomedUV = uv;\n if (uniforms.zoom > 1.0) {\n let centered = uv - 0.5;\n zoomedUV = centered / uniforms.zoom + 0.5;\n }\n distortedUV = applyBarrelDistortion(zoomedUV, uniforms.curvature);\n cornerMask = getCornerMask(distortedUV, uniforms.cornerRadius);\n\n // Clamp to edges for mirror-repeat effect\n // If outside bounds, return black (corner area)\n if (distortedUV.x < 0.0 || distortedUV.x > 1.0 ||\n distortedUV.y < 0.0 || distortedUV.y > 1.0) {\n return vec4f(0.0, 0.0, 0.0, 1.0);\n }\n }\n\n // Calculate texel size for blur and convergence\n let texelSize = 1.0 / uniforms.inputSize;\n\n // Sample color with optional blur and RGB convergence\n var color: vec3f;\n if (crtEnabled) {\n let hasConvergence = any(uniforms.convergence != vec3f(0.0));\n if (hasConvergence) {\n // Blur + convergence combined\n color = sampleWithBlurAndConvergence(distortedUV, uniforms.convergence, uniforms.blurSize, texelSize);\n } else {\n // Just blur (or no blur if blurSize <= 0)\n color = sampleWithHorizontalBlur(distortedUV, uniforms.blurSize, texelSize);\n }\n } else {\n // Upscale-only mode\n // Check if actual upscaling occurred (input larger than canvas)\n let hasUpscaling = uniforms.inputSize.x > uniforms.canvasSize.x + 0.5 ||\n uniforms.inputSize.y > uniforms.canvasSize.y + 0.5;\n\n if (hasUpscaling) {\n // Actual upscaling: use linear for smooth downsampling to canvas\n color = textureSampleLevel(inputTexture, inputSampler, distortedUV, 0.0).rgb;\n } else {\n // No upscaling (1:1 mapping): use nearest to avoid interpolation artifacts\n // This prevents checkerboard patterns from floating-point UV precision issues\n color = textureSampleLevel(inputTexture, nearestSampler, distortedUV, 0.0).rgb;\n }\n }\n\n // Apply CRT effects only when enabled\n if (crtEnabled) {\n // Pixel-perfect scanlines using fragment coordinates\n // scanlineHeight = exact pixels per scanline (e.g. 3 = repeats every 3 pixels)\n let scanlinePos = pixelPos.y / max(uniforms.scanlineHeight, 1.0);\n\n // Apply Gaussian scanlines\n color = applyScanlines(\n color,\n scanlinePos,\n uniforms.scanlineIntensity,\n uniforms.scanlineWidth,\n uniforms.scanlineBrightBoost\n );\n\n // Apply pixel-perfect phosphor mask using fragment coordinates\n if (uniforms.maskType > 0.5 && uniforms.maskIntensity > 0.0) {\n var mask: vec3f;\n let maskPos = pixelPos / uniforms.maskScale;\n if (uniforms.maskType < 1.5) {\n // Aperture grille (type 1)\n mask = proceduralApertureGrille(maskPos.x, 1.0);\n } else if (uniforms.maskType < 2.5) {\n // Slot mask (type 2) - height matches scanline height\n mask = proceduralSlotMask(maskPos, 1.0, uniforms.scanlineHeight / uniforms.maskScale);\n } else {\n // Shadow mask (type 3)\n mask = proceduralShadowMask(maskPos, 1.0);\n }\n\n // Apply mask with pre-calculated brightness compensation\n let maskedColor = color * mask;\n color = mix(color, maskedColor, uniforms.maskIntensity);\n color *= uniforms.maskCompensation;\n }\n\n // Apply vignette\n color = applyVignette(color, uv, uniforms.vignetteIntensity, uniforms.vignetteSize);\n\n // Apply corner mask (darken rounded corners)\n color *= cornerMask;\n }\n\n return vec4f(color, 1.0);\n}\n","import { BasePass } from \"./BasePass.js\"\nimport { Pipeline } from \"../../Pipeline.js\"\n\nimport crtWGSL from \"../shaders/crt.wgsl\"\n\n/**\n * CRTPass - CRT monitor simulation effect\n *\n * Applies retro CRT effects: screen curvature, scanlines, RGB convergence,\n * phosphor mask, and vignette. Optionally upscales the input for a pixelated look.\n *\n * Input: SDR image from PostProcessPass (rendered to intermediate texture)\n * Output: Final image with CRT effects to canvas\n */\nclass CRTPass extends BasePass {\n constructor(engine = null) {\n super('CRT', engine)\n\n this.pipeline = null\n this.inputTexture = null\n\n // Upscaled texture (for pixelated look)\n this.upscaledTexture = null\n this.upscaledWidth = 0\n this.upscaledHeight = 0\n\n // Samplers\n this.linearSampler = null\n this.nearestSampler = null\n\n // Phosphor mask texture (procedural placeholder)\n this.phosphorMaskTexture = null\n\n // Canvas dimensions\n this.canvasWidth = 0\n this.canvasHeight = 0\n\n // Render size (before upscale)\n this.renderWidth = 0\n this.renderHeight = 0\n }\n\n // Settings getters\n get crtSettings() { return this.engine?.settings?.crt ?? {} }\n get crtEnabled() { return this.crtSettings.enabled ?? false }\n get upscaleEnabled() { return this.crtSettings.upscaleEnabled ?? false }\n get upscaleTarget() { return this.crtSettings.upscaleTarget ?? 4 }\n get maxTextureSize() { return this.crtSettings.maxTextureSize ?? 4096 }\n\n // Geometry\n get curvature() { return this.crtSettings.curvature ?? 0.03 }\n get cornerRadius() { return this.crtSettings.cornerRadius ?? 0.03 }\n get zoom() { return this.crtSettings.zoom ?? 1.0 }\n\n // Scanlines\n get scanlineIntensity() { return this.crtSettings.scanlineIntensity ?? 0.25 }\n get scanlineWidth() { return this.crtSettings.scanlineWidth ?? 0.5 }\n get scanlineBrightBoost() { return this.crtSettings.scanlineBrightBoost ?? 1.0 }\n get scanlineHeight() { return this.crtSettings.scanlineHeight ?? 3 } // pixels per scanline\n\n // Convergence\n get convergence() { return this.crtSettings.convergence ?? [0.5, 0.0, -0.5] }\n\n // Phosphor mask\n get maskType() {\n const type = this.crtSettings.maskType ?? 'aperture'\n switch (type) {\n case 'none': return 0\n case 'aperture': return 1\n case 'slot': return 2\n case 'shadow': return 3\n default: return 1\n }\n }\n get maskIntensity() { return this.crtSettings.maskIntensity ?? 0.15 }\n get maskScale() { return this.crtSettings.maskScale ?? 1.0 }\n\n // Vignette\n get vignetteIntensity() { return this.crtSettings.vignetteIntensity ?? 0.15 }\n get vignetteSize() { return this.crtSettings.vignetteSize ?? 0.4 }\n\n // Blur\n get blurSize() { return this.crtSettings.blurSize ?? 0.5 }\n\n /**\n * Calculate brightness compensation for phosphor mask\n * Pre-computed on CPU to avoid per-pixel calculation in shader\n * @param {number} maskType - 0=none, 1=aperture, 2=slot, 3=shadow\n * @param {number} intensity - mask intensity 0-1\n * @returns {number} compensation multiplier\n */\n _calculateMaskCompensation(maskType, intensity) {\n if (maskType < 0.5 || intensity <= 0) {\n return 1.0\n }\n\n // Darkening factors tuned per mask type\n let darkening\n let useLinearOnly = false\n\n if (maskType < 1.5) {\n // Aperture grille\n darkening = 0.25\n } else if (maskType < 2.5) {\n // Slot mask\n darkening = 0.27\n } else {\n // Shadow mask: linear formula works perfectly, don't blend to exp\n darkening = 0.82\n useLinearOnly = true\n }\n\n // Linear formula: 1 / (1 - intensity * darkening)\n const linearComp = 1.0 / Math.max(1.0 - intensity * darkening, 0.1)\n\n // Shadow uses linear only (works well at all intensities)\n if (useLinearOnly) {\n return linearComp\n }\n\n // Aperture/Slot: blend to exp at high intensities to avoid over-brightening\n const expComp = Math.exp(intensity * darkening)\n const t = Math.max(0, Math.min(1, (intensity - 0.4) / 0.2))\n const blendFactor = t * t * (3 - 2 * t) // smoothstep\n\n return linearComp * (1 - blendFactor) + expComp * blendFactor\n }\n\n /**\n * Set the input texture (from PostProcessPass intermediate output)\n * @param {Object} texture - Input texture object\n */\n setInputTexture(texture) {\n if (this.inputTexture !== texture) {\n this.inputTexture = texture\n this._needsRebuild = true\n this._blitPipeline = null // Invalidate blit pipeline (has bind group with old texture)\n }\n }\n\n /**\n * Set the render size (before upscaling)\n * @param {number} width - Render width\n * @param {number} height - Render height\n */\n setRenderSize(width, height) {\n if (this.renderWidth !== width || this.renderHeight !== height) {\n this.renderWidth = width\n this.renderHeight = height\n this._needsUpscaleRebuild = true\n }\n }\n\n /**\n * Calculate the upscaled texture size\n * @returns {{width: number, height: number, scale: number}}\n */\n _calculateUpscaledSize() {\n const renderW = this.renderWidth || this.canvasWidth\n const renderH = this.renderHeight || this.canvasHeight\n const maxSize = this.maxTextureSize\n\n // Find the largest integer scale that fits within limits\n // This avoids non-integer ratios that cause moiré/checkerboard patterns\n let scale = this.upscaleTarget\n while (scale > 1 && (renderW * scale > maxSize || renderH * scale > maxSize)) {\n scale--\n }\n\n // Also limit to 2x canvas size (no benefit beyond display resolution)\n const maxCanvasScale = 2.0\n while (scale > 1 && (renderW * scale > this.canvasWidth * maxCanvasScale ||\n renderH * scale > this.canvasHeight * maxCanvasScale)) {\n scale--\n }\n\n // Ensure at least 1x\n scale = Math.max(scale, 1)\n\n const targetW = renderW * scale\n const targetH = renderH * scale\n\n return { width: targetW, height: targetH, scale }\n }\n\n /**\n * Check if actual upscaling is needed\n * Returns true only if the upscaled texture would be larger than the input\n */\n _needsUpscaling() {\n if (!this.upscaleEnabled) return false\n const { scale } = this._calculateUpscaledSize()\n return scale > 1\n }\n\n async _init() {\n const { device } = this.engine\n\n // Create samplers\n this.linearSampler = device.createSampler({\n label: 'CRT Linear Sampler',\n minFilter: 'linear',\n magFilter: 'linear',\n addressModeU: 'mirror-repeat',\n addressModeV: 'mirror-repeat',\n })\n\n this.nearestSampler = device.createSampler({\n label: 'CRT Nearest Sampler',\n minFilter: 'nearest',\n magFilter: 'nearest',\n addressModeU: 'mirror-repeat',\n addressModeV: 'mirror-repeat',\n })\n\n // Create dummy phosphor mask texture (1x1 white - will be replaced)\n await this._createPhosphorMaskTexture()\n }\n\n /**\n * Create phosphor mask texture\n * This is a simple procedural texture for the aperture grille pattern\n */\n async _createPhosphorMaskTexture() {\n const { device } = this.engine\n\n // Create a 6x2 texture for aperture grille pattern\n // Pattern: R G B R G B (repeated vertically)\n const size = 6\n const data = new Uint8Array(size * 2 * 4)\n\n // Aperture grille pattern\n for (let y = 0; y < 2; y++) {\n for (let x = 0; x < size; x++) {\n const idx = (y * size + x) * 4\n const phase = x % 3\n\n // RGB stripes with some bleed\n if (phase === 0) {\n data[idx] = 255 // R\n data[idx + 1] = 50 // G\n data[idx + 2] = 50 // B\n } else if (phase === 1) {\n data[idx] = 50 // R\n data[idx + 1] = 255 // G\n data[idx + 2] = 50 // B\n } else {\n data[idx] = 50 // R\n data[idx + 1] = 50 // G\n data[idx + 2] = 255 // B\n }\n data[idx + 3] = 255 // A\n }\n }\n\n const texture = device.createTexture({\n label: 'Phosphor Mask',\n size: [size, 2, 1],\n format: 'rgba8unorm',\n usage: GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_DST,\n })\n\n device.queue.writeTexture(\n { texture },\n data,\n { bytesPerRow: size * 4 },\n { width: size, height: 2 }\n )\n\n const sampler = device.createSampler({\n label: 'Phosphor Mask Sampler',\n minFilter: 'nearest',\n magFilter: 'nearest',\n addressModeU: 'repeat',\n addressModeV: 'repeat',\n })\n\n this.phosphorMaskTexture = {\n texture,\n view: texture.createView(),\n sampler\n }\n }\n\n /**\n * Create or resize the upscaled texture\n */\n async _createUpscaledTexture() {\n const { device } = this.engine\n\n const { width, height, scale } = this._calculateUpscaledSize()\n\n // Skip if size hasn't changed\n if (this.upscaledWidth === width && this.upscaledHeight === height) {\n return\n }\n\n // Destroy old texture\n if (this.upscaledTexture?.texture) {\n this.upscaledTexture.texture.destroy()\n }\n\n // Create new upscaled texture\n const texture = device.createTexture({\n label: 'CRT Upscaled Texture',\n size: [width, height, 1],\n format: 'rgba8unorm',\n usage: GPUTextureUsage.TEXTURE_BINDING |\n GPUTextureUsage.RENDER_ATTACHMENT |\n GPUTextureUsage.COPY_DST,\n })\n\n this.upscaledTexture = {\n texture,\n view: texture.createView(),\n width,\n height,\n scale,\n format: 'rgba8unorm',\n }\n\n this.upscaledWidth = width\n this.upscaledHeight = height\n\n console.log(`CRTPass: Created upscaled texture ${width}x${height} (${scale.toFixed(1)}x)`)\n\n this._needsRebuild = true\n this._blitPipeline = null // Blit pipeline render target changed\n }\n\n /**\n * Build or rebuild the CRT pipeline\n */\n async _buildPipeline() {\n if (!this.inputTexture && !this.upscaledTexture) {\n return\n }\n\n const { device } = this.engine\n\n // Check if actual upscaling is needed (scale > 1)\n const needsUpscaling = this._needsUpscaling()\n\n // Determine which texture to use as input\n // When scale = 1, use input directly to avoid unnecessary copy and potential precision issues\n const effectiveInput = (this.crtEnabled || this.upscaleEnabled) && needsUpscaling && this.upscaledTexture\n ? this.upscaledTexture\n : this.inputTexture\n\n if (!effectiveInput) return\n\n // Create bind group layout\n const bindGroupLayout = device.createBindGroupLayout({\n label: 'CRT Bind Group Layout',\n entries: [\n { binding: 0, visibility: GPUShaderStage.FRAGMENT, buffer: { type: 'uniform' } },\n { binding: 1, visibility: GPUShaderStage.FRAGMENT, texture: { sampleType: 'float' } },\n { binding: 2, visibility: GPUShaderStage.FRAGMENT, sampler: { type: 'filtering' } },\n { binding: 3, visibility: GPUShaderStage.FRAGMENT, sampler: { type: 'filtering' } },\n { binding: 4, visibility: GPUShaderStage.FRAGMENT, texture: { sampleType: 'float' } },\n { binding: 5, visibility: GPUShaderStage.FRAGMENT, sampler: { type: 'filtering' } },\n ]\n })\n\n // Create uniform buffer (must match shader struct alignment)\n // Total: 18 floats = 72 bytes, padded to 80 for alignment\n const uniformBuffer = device.createBuffer({\n label: 'CRT Uniforms',\n size: 128, // Padded for alignment\n usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST,\n })\n\n // Create bind group\n const bindGroup = device.createBindGroup({\n label: 'CRT Bind Group',\n layout: bindGroupLayout,\n entries: [\n { binding: 0, resource: { buffer: uniformBuffer } },\n { binding: 1, resource: effectiveInput.view },\n { binding: 2, resource: this.linearSampler },\n { binding: 3, resource: this.nearestSampler },\n { binding: 4, resource: this.phosphorMaskTexture.view },\n { binding: 5, resource: this.phosphorMaskTexture.sampler },\n ]\n })\n\n // Create pipeline layout\n const pipelineLayout = device.createPipelineLayout({\n label: 'CRT Pipeline Layout',\n bindGroupLayouts: [bindGroupLayout]\n })\n\n // Create shader module\n const shaderModule = device.createShaderModule({\n label: 'CRT Shader',\n code: crtWGSL,\n })\n\n // Create render pipeline\n const pipeline = device.createRenderPipeline({\n label: 'CRT Pipeline',\n layout: pipelineLayout,\n vertex: {\n module: shaderModule,\n entryPoint: 'vertexMain',\n },\n fragment: {\n module: shaderModule,\n entryPoint: 'fragmentMain',\n targets: [{\n format: navigator.gpu.getPreferredCanvasFormat(),\n }],\n },\n primitive: {\n topology: 'triangle-list',\n },\n })\n\n this.pipeline = {\n pipeline,\n bindGroup,\n uniformBuffer,\n }\n\n // Track which textures the pipeline was built with\n this._pipelineInputTexture = this.inputTexture\n // Track effective upscaled texture (null when scale = 1)\n this._pipelineUpscaledTexture = needsUpscaling ? this.upscaledTexture : null\n\n this._needsRebuild = false\n }\n\n /**\n * Upscale the input texture using nearest-neighbor filtering\n */\n _upscaleInput() {\n if (!this.inputTexture || !this.upscaledTexture) return\n\n const { device } = this.engine\n\n // Create a simple blit pipeline for nearest-neighbor upscaling\n // We use copyTextureToTexture if sizes match, otherwise render with nearest sampling\n\n const commandEncoder = device.createCommandEncoder({ label: 'CRT Upscale' })\n\n // If input and upscale sizes are the same, just copy\n if (this.inputTexture.width === this.upscaledTexture.width &&\n this.inputTexture.height === this.upscaledTexture.height) {\n commandEncoder.copyTextureToTexture(\n { texture: this.inputTexture.texture },\n { texture: this.upscaledTexture.texture },\n [this.inputTexture.width, this.inputTexture.height]\n )\n } else {\n // Render with nearest sampling for upscale\n // Check if blit pipeline needs recreation (input texture changed)\n if (!this._blitPipeline || this._blitInputTexture !== this.inputTexture) {\n this._createBlitPipeline()\n }\n\n if (this._blitPipeline) {\n const renderPass = commandEncoder.beginRenderPass({\n colorAttachments: [{\n view: this.upscaledTexture.view,\n loadOp: 'clear',\n storeOp: 'store',\n clearValue: { r: 0, g: 0, b: 0, a: 1 },\n }]\n })\n\n renderPass.setPipeline(this._blitPipeline.pipeline)\n renderPass.setBindGroup(0, this._blitPipeline.bindGroup)\n renderPass.draw(3, 1, 0, 0)\n renderPass.end()\n }\n }\n\n device.queue.submit([commandEncoder.finish()])\n }\n\n /**\n * Create a simple nearest-neighbor blit pipeline\n */\n _createBlitPipeline() {\n if (!this.inputTexture) return\n\n const { device } = this.engine\n\n // Track which texture this pipeline was created for\n this._blitInputTexture = this.inputTexture\n\n const blitShader = `\n struct VertexOutput {\n @builtin(position) position: vec4f,\n @location(0) uv: vec2f,\n }\n\n @group(0) @binding(0) var inputTexture: texture_2d<f32>;\n @group(0) @binding(1) var inputSampler: sampler;\n\n @vertex\n fn vertexMain(@builtin(vertex_index) idx: u32) -> VertexOutput {\n var output: VertexOutput;\n let x = f32(idx & 1u) * 4.0 - 1.0;\n let y = f32(idx >> 1u) * 4.0 - 1.0;\n output.position = vec4f(x, y, 0.0, 1.0);\n output.uv = vec2f((x + 1.0) * 0.5, (1.0 - y) * 0.5);\n return output;\n }\n\n @fragment\n fn fragmentMain(input: VertexOutput) -> @location(0) vec4f {\n return textureSample(inputTexture, inputSampler, input.uv);\n }\n `\n\n const shaderModule = device.createShaderModule({\n label: 'Blit Shader',\n code: blitShader,\n })\n\n const bindGroupLayout = device.createBindGroupLayout({\n label: 'Blit Bind Group Layout',\n entries: [\n { binding: 0, visibility: GPUShaderStage.FRAGMENT, texture: { sampleType: 'float' } },\n { binding: 1, visibility: GPUShaderStage.FRAGMENT, sampler: { type: 'filtering' } },\n ]\n })\n\n const bindGroup = device.createBindGroup({\n label: 'Blit Bind Group',\n layout: bindGroupLayout,\n entries: [\n { binding: 0, resource: this.inputTexture.view },\n { binding: 1, resource: this.nearestSampler },\n ]\n })\n\n const pipeline = device.createRenderPipeline({\n label: 'Blit Pipeline',\n layout: device.createPipelineLayout({ bindGroupLayouts: [bindGroupLayout] }),\n vertex: {\n module: shaderModule,\n entryPoint: 'vertexMain',\n },\n fragment: {\n module: shaderModule,\n entryPoint: 'fragmentMain',\n targets: [{ format: 'rgba8unorm' }],\n },\n primitive: { topology: 'triangle-list' },\n })\n\n this._blitPipeline = { pipeline, bindGroup }\n }\n\n async _execute(context) {\n const { device, canvas } = this.engine\n\n // Check if actual upscaling is needed (scale > 1)\n const needsUpscaling = this._needsUpscaling()\n\n // Track which texture we'll actually use\n const effectiveUpscaledTexture = needsUpscaling ? this.upscaledTexture : null\n\n // Check if textures have changed (invalidates pipelines)\n if (this.pipeline && (\n this._pipelineInputTexture !== this.inputTexture ||\n this._pipelineUpscaledTexture !== effectiveUpscaledTexture\n )) {\n this._needsRebuild = true\n this._blitPipeline = null // Also invalidate blit pipeline\n }\n\n // Skip if nothing to do\n if (!this.crtEnabled && !this.upscaleEnabled) {\n // Passthrough - but we need an input texture\n if (!this.inputTexture) return\n\n // Rebuild pipeline if needed for passthrough\n if (this._needsRebuild || !this.pipeline) {\n await this._buildPipeline()\n }\n } else {\n // CRT or upscale enabled\n // Only create/use upscaled texture if actual upscaling is needed\n if (needsUpscaling) {\n if (this._needsUpscaleRebuild) {\n await this._createUpscaledTexture()\n this._needsUpscaleRebuild = false\n }\n\n // Upscale the input (blit pipeline is rebuilt inside if needed)\n if (this.inputTexture && this.upscaledTexture) {\n this._upscaleInput()\n }\n }\n\n // Rebuild CRT pipeline if needed\n if (this._needsRebuild || !this.pipeline) {\n await this._buildPipeline()\n }\n }\n\n if (!this.pipeline) return\n\n // Update uniforms\n const uniformData = new Float32Array(32) // 128 bytes / 4\n\n // Canvas size (vec2f)\n uniformData[0] = this.canvasWidth\n uniformData[1] = this.canvasHeight\n\n // Input size (vec2f) - use upscaled only if actually upscaling\n const inputW = needsUpscaling && this.upscaledTexture ? this.upscaledTexture.width : (this.inputTexture?.width || this.canvasWidth)\n const inputH = needsUpscaling && this.upscaledTexture ? this.upscaledTexture.height : (this.inputTexture?.height || this.canvasHeight)\n uniformData[2] = inputW\n uniformData[3] = inputH\n\n // Render size (vec2f) - determines scanline count when scanlineCount=0\n const renderW = this.renderWidth || this.canvasWidth\n const renderH = this.renderHeight || this.canvasHeight\n uniformData[4] = renderW\n uniformData[5] = renderH\n\n // Debug: log dimensions on resize to verify pixel-perfect scanlines\n if (!this._loggedDimensions) {\n const renderScale = renderW > 0 ? (renderW / this.canvasWidth).toFixed(2) : '?'\n const upscaleInfo = needsUpscaling ? `upscale=${this.upscaledTexture?.scale || '?'}x` : 'no-upscale (direct)'\n console.log(`CRT: canvas=${this.canvasWidth}x${this.canvasHeight}, render=${renderW}x${renderH} (scale ${renderScale}), input=${inputW}x${inputH}, ${upscaleInfo}`)\n console.log(`CRT: Scanlines use fragment coords (${this.canvasHeight}px), should repeat every ${this.scanlineHeight}px = ${Math.floor(this.canvasHeight / this.scanlineHeight)} scanlines`)\n this._loggedDimensions = true\n }\n\n // Geometry (4 floats: curvature, cornerRadius, zoom, pad)\n uniformData[6] = this.curvature\n uniformData[7] = this.cornerRadius\n uniformData[8] = this.zoom\n uniformData[9] = 0 // _padGeom\n\n // Scanlines (4 floats)\n uniformData[10] = this.scanlineIntensity\n uniformData[11] = this.scanlineWidth\n uniformData[12] = this.scanlineBrightBoost\n uniformData[13] = this.scanlineHeight // pixels per scanline (e.g. 3)\n\n // Padding for vec3f alignment (convergence needs 16-byte alignment)\n uniformData[14] = 0\n uniformData[15] = 0\n\n // Convergence (vec3f at offset 64, aligned to 16 bytes)\n const conv = this.convergence\n uniformData[16] = conv[0]\n uniformData[17] = conv[1]\n uniformData[18] = conv[2]\n uniformData[19] = 0 // _pad2\n\n // Phosphor mask (3 floats)\n uniformData[20] = this.maskType\n uniformData[21] = this.maskIntensity\n uniformData[22] = this.maskScale\n\n // Pre-calculate mask brightness compensation (avoid per-pixel calculation)\n const maskCompensation = this._calculateMaskCompensation(this.maskType, this.maskIntensity)\n uniformData[23] = maskCompensation\n\n // Vignette (2 floats)\n uniformData[24] = this.vignetteIntensity\n uniformData[25] = this.vignetteSize\n\n // Blur (1 float)\n uniformData[26] = this.blurSize\n\n // Flags (2 floats)\n uniformData[27] = this.crtEnabled ? 1.0 : 0.0\n uniformData[28] = this.upscaleEnabled ? 1.0 : 0.0\n\n device.queue.writeBuffer(this.pipeline.uniformBuffer, 0, uniformData)\n\n // Render to canvas\n const commandEncoder = device.createCommandEncoder({ label: 'CRT Render' })\n\n const canvasTexture = this.engine.context.getCurrentTexture()\n const renderPass = commandEncoder.beginRenderPass({\n colorAttachments: [{\n view: canvasTexture.createView(),\n loadOp: 'clear',\n storeOp: 'store',\n clearValue: { r: 0, g: 0, b: 0, a: 1 },\n }]\n })\n\n renderPass.setPipeline(this.pipeline.pipeline)\n renderPass.setBindGroup(0, this.pipeline.bindGroup)\n renderPass.draw(3, 1, 0, 0)\n renderPass.end()\n\n device.queue.submit([commandEncoder.finish()])\n }\n\n async _resize(width, height) {\n this.canvasWidth = width\n this.canvasHeight = height\n this._needsUpscaleRebuild = true\n this._needsRebuild = true\n this._loggedDimensions = false // Re-log dimensions on resize\n }\n\n _destroy() {\n if (this.pipeline?.uniformBuffer) {\n this.pipeline.uniformBuffer.destroy()\n }\n if (this.upscaledTexture?.texture) {\n this.upscaledTexture.texture.destroy()\n }\n if (this.phosphorMaskTexture?.texture) {\n this.phosphorMaskTexture.texture.destroy()\n }\n this.pipeline = null\n this._blitPipeline = null\n }\n}\n\nexport { CRTPass }\n","import { BasePass } from \"./BasePass.js\"\nimport { Texture } from \"../../Texture.js\"\nimport { GBufferPass } from \"./GBufferPass.js\"\nimport { LightingPass } from \"./LightingPass.js\"\nimport { mat4, vec3 } from \"../../math.js\"\n\n/**\n * AmbientCapturePass - Captures 6 directional ambient light samples for sky-aware GI\n *\n * Renders simplified views in 6 directions (up, down, left, right, front, back)\n * to capture sky visibility and distant lighting. This provides ambient lighting\n * that responds to sky visibility (blue tint from sky when outdoors, darker when\n * under a roof).\n *\n * Features:\n * - 64x64 resolution per direction (configurable)\n * - Staggered updates: 2 faces per frame (full cycle in 3 frames)\n * - Direct lights + emissive + skybox only (no IBL on geometry)\n * - Aggressive distance culling (25m default)\n * - Output: 6 average colors applied in RenderPost based on surface normal\n *\n * Settings (from engine.settings.ambientCapture):\n * - enabled: boolean - Enable/disable ambient capture\n * - maxDistance: number - Distance culling (default 25m)\n * - intensity: number - Output intensity multiplier\n * - resolution: number - Capture resolution (default 64)\n */\nclass AmbientCapturePass extends BasePass {\n constructor(engine = null) {\n super('AmbientCapture', engine)\n\n // Capture settings\n this.captureSize = 64\n this.maxDistance = 25\n this.currentFaceIndex = 0 // cycles 0-5, update 2 per frame\n\n // 6 face directions (calculated each frame from camera)\n // Order: up, down, left, right, front, back\n this.directions = [\n [0, 1, 0],\n [0, -1, 0],\n [-1, 0, 0],\n [1, 0, 0],\n [0, 0, 1],\n [0, 0, -1]\n ]\n\n // Output: 6 average colors (vec4 each)\n this.faceColors = new Float32Array(6 * 4)\n this.faceColorsBuffer = null // GPU storage buffer\n\n // Internal passes\n this.gbufferPass = null\n this.lightingPass = null\n\n // Render targets (reused for each face)\n this.captureTexture = null\n this.dummyAO = null\n\n // Compute pipeline for reduction\n this.reducePipeline = null\n this.reduceBindGroupLayout = null\n this.reduceBindGroups = [] // One per face\n\n // Camera matrices (reused)\n this.faceView = mat4.create()\n this.faceProj = mat4.create()\n\n // Textures pending destruction\n this._pendingDestroyRing = [[], [], []]\n this._pendingDestroyIndex = 0\n }\n\n async _init() {\n const { device } = this.engine\n\n // Get settings\n this.captureSize = this.settings?.ambientCapture?.resolution ?? 64\n this.maxDistance = this.settings?.ambientCapture?.maxDistance ?? 25\n\n await this._createResources()\n await this._createComputePipeline()\n }\n\n async _createResources() {\n const { device } = this.engine\n\n // Create capture render target\n this.captureTexture = await Texture.renderTarget(this.engine, 'rgba16float', this.captureSize, this.captureSize)\n this.captureTexture.label = 'ambientCaptureRT'\n\n // Create internal GBuffer pass\n this.gbufferPass = new GBufferPass(this.engine)\n await this.gbufferPass.initialize()\n await this.gbufferPass.resize(this.captureSize, this.captureSize)\n\n // Create internal Lighting pass\n this.lightingPass = new LightingPass(this.engine)\n await this.lightingPass.initialize()\n await this.lightingPass.resize(this.captureSize, this.captureSize)\n this.lightingPass.exposureOverride = 1.0\n\n // Disable IBL on geometry for ambient capture (skybox still renders as background)\n this.lightingPass.ambientCaptureMode = true\n\n // Create dummy AO texture (white = no occlusion)\n const dummyAOTexture = device.createTexture({\n label: 'ambientCaptureAO',\n size: [this.captureSize, this.captureSize],\n format: 'r8unorm',\n usage: GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.COPY_DST\n })\n const whiteData = new Uint8Array(this.captureSize * this.captureSize).fill(255)\n device.queue.writeTexture(\n { texture: dummyAOTexture },\n whiteData,\n { bytesPerRow: this.captureSize },\n { width: this.captureSize, height: this.captureSize }\n )\n this.dummyAO = {\n texture: dummyAOTexture,\n view: dummyAOTexture.createView()\n }\n\n // Wire up passes\n await this.lightingPass.setGBuffer(this.gbufferPass.getGBuffer())\n this.lightingPass.setAOTexture(this.dummyAO)\n\n // Create output buffer for 6 face colors\n this.faceColorsBuffer = device.createBuffer({\n label: 'ambientCaptureFaceColors',\n size: 6 * 4 * 4, // 6 vec4f\n usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC | GPUBufferUsage.COPY_DST\n })\n\n // Initialize with distinct colors per direction for debugging\n // up=cyan, down=brown, left=red, right=green, front=blue, back=yellow\n const initColors = new Float32Array(6 * 4)\n // Up: sky blue\n initColors[0] = 0.4; initColors[1] = 0.6; initColors[2] = 1.0; initColors[3] = 1.0\n // Down: brown/ground\n initColors[4] = 0.3; initColors[5] = 0.2; initColors[6] = 0.1; initColors[7] = 1.0\n // Left (-X): dark\n initColors[8] = 0.2; initColors[9] = 0.2; initColors[10] = 0.2; initColors[11] = 1.0\n // Right (+X): dark\n initColors[12] = 0.2; initColors[13] = 0.2; initColors[14] = 0.2; initColors[15] = 1.0\n // Front (+Z): dark\n initColors[16] = 0.2; initColors[17] = 0.2; initColors[18] = 0.2; initColors[19] = 1.0\n // Back (-Z): dark\n initColors[20] = 0.2; initColors[21] = 0.2; initColors[22] = 0.2; initColors[23] = 1.0\n device.queue.writeBuffer(this.faceColorsBuffer, 0, initColors)\n }\n\n async _createComputePipeline() {\n const { device } = this.engine\n\n // Bind group layout for reduction compute\n this.reduceBindGroupLayout = device.createBindGroupLayout({\n label: 'ambientReduceBindGroupLayout',\n entries: [\n { binding: 0, visibility: GPUShaderStage.COMPUTE, texture: { sampleType: 'float' } },\n { binding: 1, visibility: GPUShaderStage.COMPUTE, buffer: { type: 'storage' } },\n { binding: 2, visibility: GPUShaderStage.COMPUTE, buffer: { type: 'uniform' } }\n ]\n })\n\n // Create compute shader for reduction with temporal smoothing\n const shaderCode = `\n // Reduce a texture to a single average color using parallel reduction\n // Includes temporal smoothing to blend with previous frame's value\n // Workgroup: 8x8 threads, each samples region of the texture\n // Uses shared memory for efficient reduction\n\n struct ReduceParams {\n faceIndex: u32,\n textureSize: u32,\n blendFactor: f32, // 0 = keep old, 1 = use new\n emissiveBoost: f32,\n }\n\n @group(0) @binding(0) var inputTexture: texture_2d<f32>;\n @group(0) @binding(1) var<storage, read_write> outputColors: array<vec4f>;\n @group(0) @binding(2) var<uniform> params: ReduceParams;\n\n var<workgroup> sharedColors: array<vec4f, 64>;\n\n @compute @workgroup_size(8, 8, 1)\n fn main(\n @builtin(local_invocation_id) localId: vec3u,\n @builtin(local_invocation_index) localIndex: u32\n ) {\n let faceIndex = params.faceIndex;\n let textureSize = params.textureSize;\n let tilesPerThread = textureSize / 8u;\n\n // Each thread samples a region and accumulates\n var sum = vec4f(0.0);\n let baseX = localId.x * tilesPerThread;\n let baseY = localId.y * tilesPerThread;\n\n for (var dy = 0u; dy < tilesPerThread; dy++) {\n for (var dx = 0u; dx < tilesPerThread; dx++) {\n let coord = vec2i(i32(baseX + dx), i32(baseY + dy));\n var sample = textureLoad(inputTexture, coord, 0);\n\n // Clamp max luminance to prevent sun/bright HDR from dominating\n // This preserves color ratios while limiting brightness\n var lum = max(sample.r, max(sample.g, sample.b));\n let maxLum = 2.0; // Cap brightness (sun can be 100+)\n if (lum > maxLum) {\n sample = sample * (maxLum / lum);\n lum = maxLum;\n }\n\n // Gentle boost for emissive after clamping (logarithmic)\n if (lum > 0.5 && params.emissiveBoost > 0.0) {\n let boost = 1.0 + log2(lum + 1.0) * params.emissiveBoost;\n sample = sample * boost;\n }\n\n sum += sample;\n }\n }\n\n // Average this thread's samples\n let pixelCount = f32(tilesPerThread * tilesPerThread);\n sharedColors[localIndex] = sum / pixelCount;\n\n workgroupBarrier();\n\n // Parallel reduction: 64 -> 32 -> 16 -> 8 -> 4 -> 2 -> 1\n if (localIndex < 32u) {\n sharedColors[localIndex] += sharedColors[localIndex + 32u];\n }\n workgroupBarrier();\n\n if (localIndex < 16u) {\n sharedColors[localIndex] += sharedColors[localIndex + 16u];\n }\n workgroupBarrier();\n\n if (localIndex < 8u) {\n sharedColors[localIndex] += sharedColors[localIndex + 8u];\n }\n workgroupBarrier();\n\n if (localIndex < 4u) {\n sharedColors[localIndex] += sharedColors[localIndex + 4u];\n }\n workgroupBarrier();\n\n if (localIndex < 2u) {\n sharedColors[localIndex] += sharedColors[localIndex + 2u];\n }\n workgroupBarrier();\n\n // Final reduction with temporal smoothing\n if (localIndex == 0u) {\n let finalSum = sharedColors[0] + sharedColors[1];\n let newColor = finalSum / 64.0;\n\n // Blend with previous value for temporal smoothing\n let oldColor = outputColors[faceIndex];\n let blendFactor = params.blendFactor;\n outputColors[faceIndex] = mix(oldColor, newColor, blendFactor);\n }\n }\n `\n\n const shaderModule = device.createShaderModule({\n label: 'ambientReduceShader',\n code: shaderCode\n })\n\n this.reducePipeline = await device.createComputePipelineAsync({\n label: 'ambientReducePipeline',\n layout: device.createPipelineLayout({\n bindGroupLayouts: [this.reduceBindGroupLayout]\n }),\n compute: {\n module: shaderModule,\n entryPoint: 'main'\n }\n })\n\n // Create uniform buffers for each face (stores faceIndex, textureSize, blendFactor, emissiveBoost)\n this.reduceUniformBuffers = []\n this.reduceUniformData = []\n for (let i = 0; i < 6; i++) {\n const buffer = device.createBuffer({\n label: `ambientReduceParams_${i}`,\n size: 16, // u32 + u32 + f32 + f32\n usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST\n })\n // Initialize with default values\n const data = new ArrayBuffer(16)\n const view = new DataView(data)\n view.setUint32(0, i, true) // faceIndex\n view.setUint32(4, this.captureSize, true) // textureSize\n view.setFloat32(8, 1.0, true) // blendFactor (1.0 = instant, for first frame)\n view.setFloat32(12, 2.0, true) // emissiveBoost\n device.queue.writeBuffer(buffer, 0, data)\n this.reduceUniformBuffers.push(buffer)\n this.reduceUniformData.push(data)\n }\n\n // Smoothing time in seconds\n this.smoothingTime = 1.0\n this.lastUpdateTime = performance.now() / 1000\n\n // Create bind groups for each face\n this._createReduceBindGroups()\n }\n\n _createReduceBindGroups() {\n const { device } = this.engine\n\n this.reduceBindGroups = []\n const lightingOutput = this.lightingPass.getOutputTexture()\n\n for (let i = 0; i < 6; i++) {\n const bindGroup = device.createBindGroup({\n label: `ambientReduceBindGroup_${i}`,\n layout: this.reduceBindGroupLayout,\n entries: [\n { binding: 0, resource: lightingOutput.view },\n { binding: 1, resource: { buffer: this.faceColorsBuffer } },\n { binding: 2, resource: { buffer: this.reduceUniformBuffers[i] } }\n ]\n })\n this.reduceBindGroups.push(bindGroup)\n }\n }\n\n /**\n * Set dependencies from main render pipeline\n */\n setDependencies(options) {\n const { environmentMap, encoding, shadowPass, noise, noiseSize } = options\n\n if (environmentMap) {\n this.lightingPass.setEnvironmentMap(environmentMap, encoding ?? 0)\n }\n if (shadowPass) {\n this.lightingPass.setShadowPass(shadowPass)\n }\n if (noise) {\n this.gbufferPass.setNoise(noise, noiseSize, false)\n this.lightingPass.setNoise(noise, noiseSize, false)\n }\n }\n\n /**\n * Calculate 6 world-space directions for ambient capture\n * Uses fixed world axes so shader can apply using world-space normals\n */\n _calculateDirections(camera) {\n // 6 directions in world space: up, down, left (-X), right (+X), front (+Z), back (-Z)\n // These match the shader's normal-direction mapping\n this.directions = [\n [0, 1, 0], // up (+Y)\n [0, -1, 0], // down (-Y)\n [-1, 0, 0], // left (-X)\n [1, 0, 0], // right (+X)\n [0, 0, 1], // front (+Z)\n [0, 0, -1] // back (-Z)\n ]\n }\n\n /**\n * Create a camera looking in a specific direction\n */\n _createFaceCamera(camera, direction, faceIndex) {\n const position = camera.position\n\n // Create look-at view matrix\n // Target = position + direction\n const target = [\n position[0] + direction[0],\n position[1] + direction[1],\n position[2] + direction[2]\n ]\n\n // Up vector: for up/down faces, use world +Z; otherwise use world up\n let up\n if (faceIndex === 0) {\n // Looking up (+Y): use +Z as up\n up = [0, 0, 1]\n } else if (faceIndex === 1) {\n // Looking down (-Y): use +Z as up\n up = [0, 0, 1]\n } else {\n // Horizontal directions: use world up (+Y)\n up = [0, 1, 0]\n }\n\n mat4.lookAt(this.faceView, position, target, up)\n\n // 90 degree FOV perspective projection\n const fov = Math.PI / 2 // 90 degrees\n const aspect = 1.0\n const near = 0.1\n const far = this.maxDistance\n mat4.perspective(this.faceProj, fov, aspect, near, far)\n\n // Create inverse matrices\n const iView = mat4.create()\n const iProj = mat4.create()\n const viewProj = mat4.create()\n const iViewProj = mat4.create()\n mat4.invert(iView, this.faceView)\n mat4.invert(iProj, this.faceProj)\n mat4.multiply(viewProj, this.faceProj, this.faceView)\n mat4.invert(iViewProj, viewProj)\n\n return {\n view: this.faceView,\n proj: this.faceProj,\n iView,\n iProj,\n iViewProj,\n viewProj,\n position,\n near,\n far,\n aspect,\n jitterEnabled: false,\n jitterOffset: [0, 0],\n screenSize: [this.captureSize, this.captureSize],\n forward: direction,\n updateMatrix: () => {},\n updateView: () => {}\n }\n }\n\n /**\n * Execute ambient capture pass\n */\n async _execute(context) {\n const { device, stats } = this.engine\n const { camera, meshes, lights, mainLight, dt = 0.016 } = context\n\n // Process deferred texture destruction\n this._pendingDestroyIndex = (this._pendingDestroyIndex + 1) % 3\n const toDestroy = this._pendingDestroyRing[this._pendingDestroyIndex]\n for (const tex of toDestroy) {\n tex.destroy()\n }\n this._pendingDestroyRing[this._pendingDestroyIndex] = []\n\n // Check if enabled\n if (!this.settings?.ambientCapture?.enabled) {\n return\n }\n\n // Update settings\n this.maxDistance = this.settings?.ambientCapture?.maxDistance ?? 25\n this.smoothingTime = this.settings?.ambientCapture?.smoothingTime ?? 1.0\n\n // Calculate directions based on camera orientation\n this._calculateDirections(camera)\n\n // Copy lights to internal lighting pass\n this.lightingPass.lights = lights\n\n // Determine which 2 faces to update this frame\n const facesToUpdate = [\n this.currentFaceIndex,\n (this.currentFaceIndex + 1) % 6\n ]\n\n // Render and reduce each face with temporal smoothing\n for (const faceIndex of facesToUpdate) {\n await this._renderFace(context, faceIndex)\n this._reduceFace(faceIndex, dt)\n }\n\n // Advance to next pair of faces\n this.currentFaceIndex = (this.currentFaceIndex + 2) % 6\n }\n\n async _renderFace(context, faceIndex) {\n const { camera, meshes, lights, mainLight, dt } = context\n\n const direction = this.directions[faceIndex]\n const faceCamera = this._createFaceCamera(camera, direction, faceIndex)\n\n // Execute GBuffer pass\n await this.gbufferPass.execute({\n camera: faceCamera,\n meshes,\n dt,\n // Custom culling for ambient capture\n cullingOverride: {\n maxDistance: this.maxDistance,\n frustum: true,\n hiZ: false\n }\n })\n\n // Execute Lighting pass\n await this.lightingPass.execute({\n camera: faceCamera,\n meshes,\n dt,\n lights,\n mainLight\n })\n }\n\n _reduceFace(faceIndex, dt) {\n const { device } = this.engine\n\n // Calculate blend factor for temporal smoothing\n // blend = 1 - exp(-dt / smoothingTime) gives exponential smoothing\n // With smoothingTime=1s, after 1s we've blended ~63% of the way to target\n const blendFactor = 1.0 - Math.exp(-dt / this.smoothingTime)\n\n // Get emissive boost from settings\n const emissiveBoost = this.settings?.ambientCapture?.emissiveBoost ?? 2.0\n\n // Update uniform buffer with blend factor and emissive boost\n const data = this.reduceUniformData[faceIndex]\n const view = new DataView(data)\n view.setFloat32(8, blendFactor, true) // blendFactor\n view.setFloat32(12, emissiveBoost, true) // emissiveBoost\n device.queue.writeBuffer(this.reduceUniformBuffers[faceIndex], 0, data)\n\n const commandEncoder = device.createCommandEncoder({\n label: `ambientReduce_face${faceIndex}`\n })\n\n const computePass = commandEncoder.beginComputePass({\n label: `ambientReducePass_face${faceIndex}`\n })\n\n computePass.setPipeline(this.reducePipeline)\n computePass.setBindGroup(0, this.reduceBindGroups[faceIndex])\n computePass.dispatchWorkgroups(1) // Single workgroup handles entire 64x64 texture\n computePass.end()\n\n device.queue.submit([commandEncoder.finish()])\n }\n\n /**\n * Get the face colors buffer for RenderPost\n */\n getFaceColorsBuffer() {\n return this.faceColorsBuffer\n }\n\n /**\n * Get the 6 directions (for RenderPost to know orientation)\n */\n getDirections() {\n return this.directions\n }\n\n async _resize(width, height) {\n // Ambient capture doesn't resize with screen - fixed resolution\n }\n\n _destroy() {\n if (this.gbufferPass) {\n this.gbufferPass.destroy()\n this.gbufferPass = null\n }\n if (this.lightingPass) {\n this.lightingPass.destroy()\n this.lightingPass = null\n }\n if (this.dummyAO?.texture) {\n this.dummyAO.texture.destroy()\n this.dummyAO = null\n }\n if (this.faceColorsBuffer) {\n this.faceColorsBuffer.destroy()\n this.faceColorsBuffer = null\n }\n for (const buffer of this.reduceUniformBuffers || []) {\n buffer.destroy()\n }\n this.reduceUniformBuffers = []\n for (const slot of this._pendingDestroyRing) {\n for (const tex of slot) {\n tex.destroy()\n }\n }\n this._pendingDestroyRing = [[], [], []]\n }\n}\n\nexport { AmbientCapturePass }\n","import { Texture } from \"../Texture.js\"\nimport { mat4 } from \"../math.js\"\n\n/**\n * HistoryBufferManager - Manages A/B double-buffered textures for temporal effects\n *\n * Provides previous frame data for:\n * - SSR (Screen Space Reflections)\n * - SSGI (Screen Space Global Illumination)\n * - Temporal anti-aliasing\n * - Motion blur (future)\n */\nclass HistoryBufferManager {\n constructor(engine) {\n this.engine = engine\n this.frameIndex = 0 // Toggles 0/1 for A/B switching\n this.initialized = false\n this.width = 0\n this.height = 0\n\n // Double-buffered textures (A/B swap)\n this.colorHistory = [null, null] // HDR color after lighting (rgba16float)\n this.depthHistory = [null, null] // Linear depth (r32float)\n this.normalHistory = [null, null] // World normals (rgba16float)\n\n // Single buffer - written each frame, read next frame\n this.velocityBuffer = null // Motion vectors (rg16float)\n\n // Camera matrices history for reprojection\n this.prevView = mat4.create()\n this.prevProj = mat4.create()\n this.prevViewProj = mat4.create()\n this.prevInvViewProj = mat4.create()\n this.prevCameraPosition = [0, 0, 0]\n\n // Track if this is the first frame (no valid history)\n this.hasValidHistory = false\n\n // Textures pending destruction (wait for GPU to finish using them)\n this._pendingDestroyRing = [[], [], []]\n this._pendingDestroyIndex = 0\n }\n\n /**\n * Initialize or resize all history buffers\n * @param {number} width - Buffer width\n * @param {number} height - Buffer height\n */\n async initialize(width, height) {\n const { device } = this.engine\n\n // Skip if already initialized at this size\n if (this.initialized && this.width === width && this.height === height) {\n return\n }\n\n // Queue old buffers for deferred destruction\n this._queueBuffersForDestruction()\n\n this.width = width\n this.height = height\n\n // Create A/B color history buffers (HDR)\n for (let i = 0; i < 2; i++) {\n this.colorHistory[i] = await Texture.renderTarget(this.engine, 'rgba16float', width, height)\n this.colorHistory[i].label = `colorHistory${i}`\n }\n\n // Create A/B depth history buffers (linear depth as r32float)\n for (let i = 0; i < 2; i++) {\n const depthTex = device.createTexture({\n label: `depthHistory${i}`,\n size: [width, height],\n format: 'r32float',\n usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_DST,\n })\n this.depthHistory[i] = {\n texture: depthTex,\n view: depthTex.createView(),\n width,\n height,\n format: 'r32float'\n }\n }\n\n // Create A/B normal history buffers\n for (let i = 0; i < 2; i++) {\n this.normalHistory[i] = await Texture.renderTarget(this.engine, 'rgba16float', width, height)\n this.normalHistory[i].label = `normalHistory${i}`\n }\n\n // Create velocity buffer (motion vectors in pixels)\n const velocityTex = device.createTexture({\n label: 'velocityBuffer',\n size: [width, height],\n format: 'rg16float',\n usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.TEXTURE_BINDING,\n })\n this.velocityBuffer = {\n texture: velocityTex,\n view: velocityTex.createView(),\n width,\n height,\n format: 'rg16float'\n }\n\n this.initialized = true\n this.hasValidHistory = false // Reset - need one frame to build history\n }\n\n /**\n * Get current frame buffers (write targets)\n * @returns {Object} Current frame buffer references\n */\n getCurrent() {\n return {\n color: this.colorHistory[this.frameIndex],\n depth: this.depthHistory[this.frameIndex],\n normal: this.normalHistory[this.frameIndex],\n velocity: this.velocityBuffer,\n }\n }\n\n /**\n * Get previous frame buffers (read sources)\n * @returns {Object} Previous frame buffer and camera data\n */\n getPrevious() {\n const prevIdx = 1 - this.frameIndex\n return {\n color: this.colorHistory[prevIdx],\n depth: this.depthHistory[prevIdx],\n normal: this.normalHistory[prevIdx],\n velocity: this.velocityBuffer,\n view: this.prevView,\n proj: this.prevProj,\n viewProj: this.prevViewProj,\n invViewProj: this.prevInvViewProj,\n cameraPosition: this.prevCameraPosition,\n hasValidHistory: this.hasValidHistory,\n }\n }\n\n /**\n * Get velocity buffer for GBuffer pass to write motion vectors\n * @returns {Object} Velocity buffer texture\n */\n getVelocityBuffer() {\n return this.velocityBuffer\n }\n\n /**\n * Copy lighting output to current color history\n * Called after lighting pass, before SSR/SSGI\n * @param {GPUCommandEncoder} commandEncoder - Active command encoder\n * @param {Texture} lightingOutput - HDR output from lighting pass\n */\n copyLightingToHistory(commandEncoder, lightingOutput) {\n if (!this.initialized || !lightingOutput) return\n\n // Skip copy if source size doesn't match history size (resize in progress)\n const srcWidth = lightingOutput.texture.width\n const srcHeight = lightingOutput.texture.height\n if (srcWidth !== this.width || srcHeight !== this.height) {\n return\n }\n\n const current = this.colorHistory[this.frameIndex]\n commandEncoder.copyTextureToTexture(\n { texture: lightingOutput.texture },\n { texture: current.texture },\n { width: this.width, height: this.height }\n )\n }\n\n /**\n * Copy GBuffer depth to current depth history\n * Note: Not currently used - particles sample GBuffer depth directly\n * @param {GPUCommandEncoder} commandEncoder - Active command encoder\n * @param {Object} depthTexture - Depth texture from GBuffer\n */\n copyDepthToHistory(commandEncoder, depthTexture) {\n // Not implemented - particles use GBuffer depth directly via texture_depth_2d\n // If needed in future, would require render pass to convert depth32float to r32float\n }\n\n /**\n * Copy GBuffer normals to current normal history\n * @param {GPUCommandEncoder} commandEncoder - Active command encoder\n * @param {Texture} gbufferNormal - Normal from GBuffer\n */\n copyNormalToHistory(commandEncoder, normalTexture) {\n if (!this.initialized || !normalTexture) return\n\n // Skip copy if source size doesn't match history size (resize in progress)\n const srcWidth = normalTexture.texture.width\n const srcHeight = normalTexture.texture.height\n if (srcWidth !== this.width || srcHeight !== this.height) {\n return\n }\n\n const current = this.normalHistory[this.frameIndex]\n commandEncoder.copyTextureToTexture(\n { texture: normalTexture.texture },\n { texture: current.texture },\n { width: this.width, height: this.height }\n )\n }\n\n /**\n * Swap buffers and save camera matrices\n * Call at end of frame, after all rendering\n * @param {Camera} camera - Current frame camera\n */\n swap(camera) {\n if (!this.initialized) return\n\n // Process deferred texture destruction (3 frames delayed)\n this._pendingDestroyIndex = (this._pendingDestroyIndex + 1) % 3\n const toDestroy = this._pendingDestroyRing[this._pendingDestroyIndex]\n for (const tex of toDestroy) {\n tex.destroy()\n }\n this._pendingDestroyRing[this._pendingDestroyIndex] = []\n\n // Save current camera matrices as \"previous\" for next frame\n mat4.copy(this.prevView, camera.view)\n mat4.copy(this.prevProj, camera.proj)\n mat4.copy(this.prevViewProj, camera.viewProj)\n mat4.copy(this.prevInvViewProj, camera.iViewProj)\n this.prevCameraPosition[0] = camera.position[0]\n this.prevCameraPosition[1] = camera.position[1]\n this.prevCameraPosition[2] = camera.position[2]\n\n // Swap buffer index for next frame\n this.frameIndex = 1 - this.frameIndex\n\n // After first swap, we have valid history\n this.hasValidHistory = true\n }\n\n /**\n * Check if history is valid (at least one frame rendered)\n * @returns {boolean} True if history buffers contain valid data\n */\n isHistoryValid() {\n return this.hasValidHistory\n }\n\n /**\n * Get current frame index (0 or 1)\n * @returns {number} Current frame index\n */\n getFrameIndex() {\n return this.frameIndex\n }\n\n /**\n * Resize all buffers\n * @param {number} width - New width\n * @param {number} height - New height\n */\n async resize(width, height) {\n await this.initialize(width, height)\n }\n\n /**\n * Queue old buffers for deferred destruction (GPU may still be using them)\n */\n _queueBuffersForDestruction() {\n const slot = this._pendingDestroyRing[this._pendingDestroyIndex]\n for (let i = 0; i < 2; i++) {\n if (this.colorHistory[i]?.texture) {\n slot.push(this.colorHistory[i].texture)\n this.colorHistory[i] = null\n }\n if (this.depthHistory[i]?.texture) {\n slot.push(this.depthHistory[i].texture)\n this.depthHistory[i] = null\n }\n if (this.normalHistory[i]?.texture) {\n slot.push(this.normalHistory[i].texture)\n this.normalHistory[i] = null\n }\n }\n if (this.velocityBuffer?.texture) {\n slot.push(this.velocityBuffer.texture)\n this.velocityBuffer = null\n }\n }\n\n /**\n * Destroy all buffer resources immediately\n */\n _destroyBuffers() {\n for (let i = 0; i < 2; i++) {\n if (this.colorHistory[i]?.texture) {\n this.colorHistory[i].texture.destroy()\n this.colorHistory[i] = null\n }\n if (this.depthHistory[i]?.texture) {\n this.depthHistory[i].texture.destroy()\n this.depthHistory[i] = null\n }\n if (this.normalHistory[i]?.texture) {\n this.normalHistory[i].texture.destroy()\n this.normalHistory[i] = null\n }\n }\n if (this.velocityBuffer?.texture) {\n this.velocityBuffer.texture.destroy()\n this.velocityBuffer = null\n }\n }\n\n /**\n * Clean up all resources\n */\n destroy() {\n this._destroyBuffers()\n // Clean up any pending textures in ring buffer\n for (const slot of this._pendingDestroyRing) {\n for (const tex of slot) {\n tex.destroy()\n }\n }\n this._pendingDestroyRing = [[], [], []]\n this.initialized = false\n this.hasValidHistory = false\n }\n}\n\nexport { HistoryBufferManager }\n","import { Frustum } from \"../utils/Frustum.js\"\nimport { transformBoundingSphere } from \"../utils/BoundingSphere.js\"\n\n/**\n * CullingSystem - Manages visibility culling for entities\n *\n * Performs cone-based frustum culling, distance filtering,\n * and HiZ occlusion culling with configurable limits per pass type.\n */\nclass CullingSystem {\n constructor(engine = null) {\n // Reference to engine for settings access\n this.engine = engine\n\n // Frustum for culling\n this.frustum = new Frustum()\n\n // HiZ pass reference for occlusion culling\n this.hizPass = null\n\n // Current camera data for HiZ testing\n this._viewProj = null\n this._near = 0.05\n this._far = 1000\n this._cameraPos = null\n\n // Stats for occlusion culling\n this._occlusionStats = {\n tested: 0,\n culled: 0\n }\n\n // Cached visible entity lists per pass\n this._visibleCache = {\n shadow: null,\n reflection: null,\n planarReflection: null,\n main: null\n }\n\n // Frame counter for cache invalidation\n this._frameId = 0\n this._cacheFrameId = -1\n }\n\n /**\n * Set the HiZ pass for occlusion culling\n * @param {HiZPass} hizPass - The HiZ pass instance\n */\n setHiZPass(hizPass) {\n this.hizPass = hizPass\n }\n\n // Culling config is now a getter that reads from engine.settings.culling\n get config() {\n return this.engine.settings.culling\n }\n\n /**\n * Update frustum from camera\n * @param {Camera} camera - Camera object\n * @param {number} screenWidth - Screen width in pixels\n * @param {number} screenHeight - Screen height in pixels\n */\n updateFrustum(camera, screenWidth, screenHeight) {\n // Camera uses: view, proj, fov (degrees), aspect, near, far, position, direction\n const fovRadians = camera.fov * (Math.PI / 180)\n this.frustum.update(\n camera.view,\n camera.proj,\n camera.position,\n camera.direction,\n fovRadians,\n camera.aspect,\n camera.near,\n camera.far,\n screenWidth,\n screenHeight\n )\n\n // Store camera data for HiZ testing\n // Copy position to avoid issues with mutable references\n this._viewProj = camera.viewProj\n this._near = camera.near\n this._far = camera.far\n this._cameraPos = [camera.position[0], camera.position[1], camera.position[2]]\n\n // Reset occlusion stats\n this._occlusionStats.tested = 0\n this._occlusionStats.culled = 0\n\n this._frameId++\n }\n\n /**\n * Set culling configuration for a pass type\n * @param {string} passType - 'shadow', 'reflection', or 'main'\n * @param {Object} config - { maxDistance, maxSkinned }\n */\n setConfig(passType, config) {\n const cullingConfig = this.engine?.settings?.culling\n if (cullingConfig && cullingConfig[passType]) {\n Object.assign(cullingConfig[passType], config)\n }\n }\n\n /**\n * Cull entities for a specific pass type\n *\n * @param {EntityManager} entityManager - Entity manager\n * @param {AssetManager} assetManager - Asset manager\n * @param {string} passType - 'shadow', 'reflection', or 'main'\n * @returns {{ visible: Array, skinnedCount: number }}\n */\n cull(entityManager, assetManager, passType = 'main') {\n const config = this.config[passType] || this.config.main\n const visible = []\n let skinnedCount = 0\n let skippedNoVisible = 0\n let skippedNoModel = 0\n\n entityManager.forEach((id, entity) => {\n // Skip if not visible\n if (!entity._visible) {\n skippedNoVisible++\n return\n }\n\n // Skip if no model\n if (!entity.model) {\n skippedNoModel++\n return\n }\n\n // Get bounding sphere from asset and transform by entity matrix\n const asset = assetManager.get(entity.model)\n let bsphere\n\n if (asset?.bsphere) {\n // Transform asset's bsphere by entity's current matrix\n // Note: For skinned models, bsphere is pre-computed as combined sphere of all submeshes\n bsphere = transformBoundingSphere(asset.bsphere, entity._matrix)\n // Cache it on entity for other uses\n entity._bsphere = bsphere\n } else if (entity._bsphere && entity._bsphere.radius > 0) {\n // Use existing bsphere if available\n bsphere = entity._bsphere\n } else {\n // No bsphere available, include by default\n visible.push({ id, entity, distance: 0 })\n return\n }\n\n // Check if culling is enabled\n const globalCullingEnabled = this.engine?.settings?.culling?.frustumEnabled !== false\n const passFrustumEnabled = config.frustum !== false\n\n // For planar reflection, mirror the bounding sphere across the ground level\n // This ensures we cull based on where the object appears in the reflection\n let cullBsphere = bsphere\n if (passType === 'planarReflection') {\n const groundLevel = this.engine?.settings?.planarReflection?.groundLevel ?? 0\n // Mirror Y position: mirroredY = 2 * groundLevel - originalY\n cullBsphere = {\n center: [\n bsphere.center[0],\n 2 * groundLevel - bsphere.center[1],\n bsphere.center[2]\n ],\n radius: bsphere.radius\n }\n }\n\n // Distance test (always apply when global culling is enabled)\n const distance = this.frustum.getDistance(cullBsphere)\n if (globalCullingEnabled && distance - cullBsphere.radius > config.maxDistance) {\n return // Too far\n }\n\n // Pixel size test (always apply when global culling is enabled)\n if (globalCullingEnabled && config.minPixelSize > 0) {\n const projectedSize = this.frustum.getProjectedSize(cullBsphere, distance)\n if (projectedSize < config.minPixelSize) {\n return // Too small to see\n }\n }\n\n // Frustum test (only when both global AND per-pass frustum culling is enabled)\n if (globalCullingEnabled && passFrustumEnabled && !this.frustum.testSpherePlanes(cullBsphere)) {\n return // Outside frustum\n }\n\n // HiZ occlusion culling for entities\n if (passType === 'main' && this.hizPass && this._viewProj && this._cameraPos) {\n const occlusionEnabled = this.engine?.settings?.occlusionCulling?.enabled\n if (occlusionEnabled) {\n this._occlusionStats.tested++\n if (this.hizPass.testSphereOcclusion(bsphere, this._viewProj, this._near, this._far, this._cameraPos)) {\n this._occlusionStats.culled++\n return // Occluded by previous frame's geometry\n }\n }\n }\n\n // Check skinned limit (asset already fetched above)\n const isSkinned = asset?.hasSkin === true\n\n if (isSkinned) {\n if (skinnedCount >= config.maxSkinned) {\n return // Too many skinned already\n }\n skinnedCount++\n }\n\n visible.push({\n id,\n entity,\n distance,\n isSkinned\n })\n })\n\n // Sort by distance for front-to-back rendering (reduces overdraw)\n visible.sort((a, b) => a.distance - b.distance)\n\n return { visible, skinnedCount }\n }\n\n /**\n * Group visible entities by model for instancing\n *\n * @param {Array} visibleEntities - Array from cull()\n * @returns {Map<string, Array>} Map of modelId -> entities\n */\n groupByModel(visibleEntities) {\n const groups = new Map()\n\n for (const item of visibleEntities) {\n const modelId = item.entity.model\n if (!groups.has(modelId)) {\n groups.set(modelId, [])\n }\n groups.get(modelId).push(item)\n }\n\n return groups\n }\n\n /**\n * Group visible entities by model and animation for skinned meshes\n * Entities with same animation can potentially share animation state\n *\n * @param {Array} visibleEntities - Array from cull()\n * @param {number} phaseQuantization - Quantize phase to this step (default 0.05 = 20 groups per animation)\n * @returns {Map<string, Array>} Map of \"modelId|animation|quantizedPhase\" -> entities\n */\n groupByModelAndAnimation(visibleEntities, phaseQuantization = 0.05) {\n const groups = new Map()\n\n for (const item of visibleEntities) {\n const entity = item.entity\n let key = entity.model\n\n if (item.isSkinned && entity.animation) {\n const quantizedPhase = Math.floor(entity.phase / phaseQuantization) * phaseQuantization\n key = `${entity.model}|${entity.animation}|${quantizedPhase.toFixed(2)}`\n }\n\n if (!groups.has(key)) {\n groups.set(key, [])\n }\n groups.get(key).push(item)\n }\n\n return groups\n }\n\n /**\n * Get statistics about culling\n */\n getStats(entityManager, assetManager) {\n const total = entityManager.count\n const { visible } = this.cull(entityManager, assetManager, 'main')\n const culled = total - visible.length\n\n return {\n total,\n visible: visible.length,\n culled,\n cullPercent: total > 0 ? ((culled / total) * 100).toFixed(1) : 0\n }\n }\n\n /**\n * Get occlusion culling statistics\n */\n getOcclusionStats() {\n return {\n tested: this._occlusionStats.tested,\n culled: this._occlusionStats.culled,\n cullPercent: this._occlusionStats.tested > 0\n ? ((this._occlusionStats.culled / this._occlusionStats.tested) * 100).toFixed(1)\n : 0\n }\n }\n}\n\nexport { CullingSystem }\n","import { mat4 } from \"../math.js\"\n\n/**\n * InstanceManager - Manages instance batches for efficient rendering\n *\n * Groups entities by ModelID for instanced draw calls.\n * Handles buffer pools for instance data (model matrix + bsphere + sprite data).\n *\n * Instance data format (112 bytes per instance):\n * - mat4x4f: model matrix (64 bytes)\n * - vec4f: position.xyz + boundingRadius (16 bytes)\n * - vec4f: uvOffset.xy + uvScale.xy (16 bytes) - for sprite sheets\n * - vec4f: color.rgba (16 bytes) - for sprite tinting\n */\nclass InstanceManager {\n constructor(engine = null) {\n // Reference to engine for settings access\n this.engine = engine\n\n // Buffer pools: size -> array of available buffers\n this._bufferPool = new Map()\n\n // Active batches: modelId -> batch info\n this._batches = new Map()\n\n // Instance data stride (28 floats = 112 bytes)\n this.INSTANCE_STRIDE = 28\n\n // Default pool size\n this.DEFAULT_POOL_SIZE = 1000\n }\n\n /**\n * Get or create a buffer from the pool\n * @param {number} capacity - Number of instances the buffer should hold\n * @returns {Object} Buffer info\n */\n _getBuffer(capacity) {\n const { device } = this.engine\n\n // Round up to nearest pool size\n const poolSize = Math.max(this.DEFAULT_POOL_SIZE, Math.pow(2, Math.ceil(Math.log2(capacity))))\n\n // Check pool\n if (!this._bufferPool.has(poolSize)) {\n this._bufferPool.set(poolSize, [])\n }\n\n const pool = this._bufferPool.get(poolSize)\n if (pool.length > 0) {\n return pool.pop()\n }\n\n // Create new buffer\n const byteSize = poolSize * this.INSTANCE_STRIDE * 4 // 4 bytes per float\n const gpuBuffer = device.createBuffer({\n size: byteSize,\n usage: GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_DST,\n label: `Instance buffer (${poolSize})`\n })\n\n return {\n gpuBuffer,\n cpuData: new Float32Array(poolSize * this.INSTANCE_STRIDE),\n capacity: poolSize,\n count: 0\n }\n }\n\n /**\n * Return a buffer to the pool\n * @param {Object} bufferInfo - Buffer info to return\n */\n _releaseBuffer(bufferInfo) {\n const pool = this._bufferPool.get(bufferInfo.capacity)\n if (pool) {\n bufferInfo.count = 0\n pool.push(bufferInfo)\n }\n }\n\n /**\n * Build instance batches from visible entities\n *\n * @param {Map<string, Array>} groups - Groups from CullingSystem.groupByModel()\n * @param {AssetManager} assetManager - Asset manager for mesh lookup\n * @returns {Map<string, Object>} Batches ready for rendering\n */\n buildBatches(groups, assetManager) {\n const { device } = this.engine\n\n // Release old batches back to pool\n for (const [modelId, batch] of this._batches) {\n if (batch.buffer) {\n this._releaseBuffer(batch.buffer)\n }\n }\n this._batches.clear()\n\n // Build new batches\n for (const [modelId, entities] of groups) {\n // Get asset\n const asset = assetManager.get(modelId)\n if (!asset?.ready) continue\n\n // Get or create buffer\n const buffer = this._getBuffer(entities.length)\n\n // Fill instance data\n let offset = 0\n for (const item of entities) {\n const entity = item.entity\n\n // Copy model matrix (16 floats)\n buffer.cpuData.set(entity._matrix, offset)\n\n // Copy position + radius (4 floats)\n buffer.cpuData[offset + 16] = entity._bsphere.center[0]\n buffer.cpuData[offset + 17] = entity._bsphere.center[1]\n buffer.cpuData[offset + 18] = entity._bsphere.center[2]\n buffer.cpuData[offset + 19] = entity._bsphere.radius\n\n // Copy UV transform (4 floats): offset.xy, scale.xy\n // Default: no transform (offset 0,0, scale 1,1)\n const uvTransform = entity._uvTransform || [0, 0, 1, 1]\n buffer.cpuData[offset + 20] = uvTransform[0]\n buffer.cpuData[offset + 21] = uvTransform[1]\n buffer.cpuData[offset + 22] = uvTransform[2]\n buffer.cpuData[offset + 23] = uvTransform[3]\n\n // Copy color tint (4 floats): r, g, b, a\n // Default: white (no tint)\n const color = entity.color || [1, 1, 1, 1]\n buffer.cpuData[offset + 24] = color[0]\n buffer.cpuData[offset + 25] = color[1]\n buffer.cpuData[offset + 26] = color[2]\n buffer.cpuData[offset + 27] = color[3]\n\n offset += this.INSTANCE_STRIDE\n }\n\n buffer.count = entities.length\n\n // Upload to GPU\n device.queue.writeBuffer(\n buffer.gpuBuffer,\n 0,\n buffer.cpuData,\n 0,\n entities.length * this.INSTANCE_STRIDE\n )\n\n // Store batch\n this._batches.set(modelId, {\n modelId,\n mesh: asset.mesh,\n geometry: asset.geometry,\n material: asset.material,\n skin: asset.skin,\n hasSkin: asset.hasSkin,\n buffer,\n instanceCount: entities.length,\n entities\n })\n }\n\n return this._batches\n }\n\n /**\n * Build instance batches for skinned meshes grouped by animation\n *\n * @param {Map<string, Array>} groups - Groups from CullingSystem.groupByModelAndAnimation()\n * @param {AssetManager} assetManager - Asset manager\n * @returns {Map<string, Object>} Batches with animation info\n */\n buildSkinnedBatches(groups, assetManager) {\n const { device } = this.engine\n const batches = new Map()\n\n for (const [key, entities] of groups) {\n // Parse key: \"modelId|animation|phase\"\n const parts = key.split('|')\n const modelId = parts[0] + (parts.length > 1 ? '|' + parts[1].split('|')[0] : '')\n\n // For skinned meshes, we need to check the base modelId\n const baseModelId = key.includes('|') ? key.split('|').slice(0, 2).join('|') : key\n\n // Try to find the asset\n let asset = null\n for (const [assetKey, assetValue] of Object.entries(assetManager.assets)) {\n if (assetKey.includes('|') && assetKey.startsWith(key.split('|')[0])) {\n if (assetValue.ready) {\n asset = assetValue\n break\n }\n }\n }\n\n if (!asset?.ready) continue\n\n // Get or create buffer\n const buffer = this._getBuffer(entities.length)\n\n // Fill instance data\n let offset = 0\n for (const item of entities) {\n const entity = item.entity\n\n buffer.cpuData.set(entity._matrix, offset)\n buffer.cpuData[offset + 16] = entity._bsphere.center[0]\n buffer.cpuData[offset + 17] = entity._bsphere.center[1]\n buffer.cpuData[offset + 18] = entity._bsphere.center[2]\n buffer.cpuData[offset + 19] = entity._bsphere.radius\n\n // Copy UV transform (4 floats): offset.xy, scale.xy\n const uvTransform = entity._uvTransform || [0, 0, 1, 1]\n buffer.cpuData[offset + 20] = uvTransform[0]\n buffer.cpuData[offset + 21] = uvTransform[1]\n buffer.cpuData[offset + 22] = uvTransform[2]\n buffer.cpuData[offset + 23] = uvTransform[3]\n\n // Copy color tint (4 floats): r, g, b, a\n const color = entity.color || [1, 1, 1, 1]\n buffer.cpuData[offset + 24] = color[0]\n buffer.cpuData[offset + 25] = color[1]\n buffer.cpuData[offset + 26] = color[2]\n buffer.cpuData[offset + 27] = color[3]\n\n offset += this.INSTANCE_STRIDE\n }\n\n buffer.count = entities.length\n\n // Upload to GPU\n device.queue.writeBuffer(\n buffer.gpuBuffer,\n 0,\n buffer.cpuData,\n 0,\n entities.length * this.INSTANCE_STRIDE\n )\n\n // Extract animation info from key\n const animation = parts.length > 2 ? parts[1] : null\n const phase = parts.length > 3 ? parseFloat(parts[2]) : 0\n\n batches.set(key, {\n modelId: baseModelId,\n mesh: asset.mesh,\n geometry: asset.geometry,\n material: asset.material,\n skin: asset.skin,\n hasSkin: asset.hasSkin,\n animation,\n phase,\n buffer,\n instanceCount: entities.length,\n entities\n })\n }\n\n return batches\n }\n\n /**\n * Get current batches\n */\n getBatches() {\n return this._batches\n }\n\n /**\n * Get instance buffer layout for pipeline creation\n */\n static getBufferLayout() {\n return {\n arrayStride: 112, // 28 floats * 4 bytes\n stepMode: 'instance',\n attributes: [\n { format: \"float32x4\", offset: 0, shaderLocation: 6 }, // matrix column 0\n { format: \"float32x4\", offset: 16, shaderLocation: 7 }, // matrix column 1\n { format: \"float32x4\", offset: 32, shaderLocation: 8 }, // matrix column 2\n { format: \"float32x4\", offset: 48, shaderLocation: 9 }, // matrix column 3\n { format: \"float32x4\", offset: 64, shaderLocation: 10 }, // position + radius\n { format: \"float32x4\", offset: 80, shaderLocation: 11 }, // uvTransform (offset.xy, scale.xy)\n { format: \"float32x4\", offset: 96, shaderLocation: 12 }, // color (r, g, b, a)\n ]\n }\n }\n\n /**\n * Clear all batches and return buffers to pool\n */\n clear() {\n for (const [modelId, batch] of this._batches) {\n if (batch.buffer) {\n this._releaseBuffer(batch.buffer)\n }\n }\n this._batches.clear()\n }\n\n /**\n * Destroy all buffers\n */\n destroy() {\n this.clear()\n for (const [size, pool] of this._bufferPool) {\n for (const buffer of pool) {\n buffer.gpuBuffer.destroy()\n }\n }\n this._bufferPool.clear()\n }\n\n /**\n * Get statistics about buffer usage\n */\n getStats() {\n let pooledBuffers = 0\n let pooledCapacity = 0\n for (const [size, pool] of this._bufferPool) {\n pooledBuffers += pool.length\n pooledCapacity += pool.length * size\n }\n\n let activeBuffers = this._batches.size\n let activeInstances = 0\n for (const [modelId, batch] of this._batches) {\n activeInstances += batch.instanceCount\n }\n\n return {\n pooledBuffers,\n pooledCapacity,\n activeBuffers,\n activeInstances\n }\n }\n}\n\nexport { InstanceManager }\n","import { Texture } from \"../Texture.js\"\nimport { Material } from \"../Material.js\"\nimport { Mesh } from \"../Mesh.js\"\nimport { Geometry } from \"../Geometry.js\"\n\n/**\n * SpriteSystem - Manages sprite entities for billboard rendering\n *\n * Handles:\n * - Parsing sprite definitions (texture path, frames per row)\n * - Loading and caching sprite textures\n * - Computing UV transforms for sprite sheet frames\n * - Animating sprites based on frame property\n * - Creating sprite materials and meshes\n */\nclass SpriteSystem {\n constructor(engine) {\n this.engine = engine\n\n // Cache for loaded sprite textures: textureUrl -> Texture\n this._textureCache = new Map()\n\n // Cache for sprite materials: key -> Material\n this._materialCache = new Map()\n\n // Cache for sprite geometries: pivot -> Geometry\n this._geometryCache = new Map()\n\n // Active sprite entity tracking for animation\n this._spriteEntities = new Map() // entityId -> spriteData\n }\n\n /**\n * Parse a sprite definition string\n * Format: \"texture.png\" or \"texture.png|8\" (texture|framesPerRow)\n * @param {string} spriteString - Sprite definition\n * @returns {{ url: string, framesPerRow: number }}\n */\n parseSprite(spriteString) {\n if (!spriteString || typeof spriteString !== 'string') {\n return null\n }\n\n const parts = spriteString.split('|')\n return {\n url: parts[0],\n framesPerRow: parts.length > 1 ? parseInt(parts[1], 10) : 1\n }\n }\n\n /**\n * Parse a frame animation string\n * Format: integer frame OR \"startFrame|endFrame|fps\"\n * @param {number|string} frame - Frame definition\n * @returns {{ currentFrame: number, startFrame: number, endFrame: number, fps: number, isAnimated: boolean }}\n */\n parseFrame(frame) {\n // Check for animated format first: \"startFrame|endFrame|fps\"\n if (typeof frame === 'string' && frame.includes('|')) {\n const parts = frame.split('|')\n const start = parseInt(parts[0], 10) || 0\n const end = parseInt(parts[1], 10) || start\n const fps = parseFloat(parts[2]) || 30\n return {\n currentFrame: start,\n startFrame: start,\n endFrame: end,\n fps,\n isAnimated: true\n }\n }\n\n // Single frame (number or numeric string)\n if (typeof frame === 'number') {\n return {\n currentFrame: frame,\n startFrame: frame,\n endFrame: frame,\n fps: 0,\n isAnimated: false\n }\n }\n\n if (typeof frame === 'string' && !isNaN(parseInt(frame, 10))) {\n const f = parseInt(frame, 10)\n return {\n currentFrame: f,\n startFrame: f,\n endFrame: f,\n fps: 0,\n isAnimated: false\n }\n }\n\n // Default: frame 0\n return {\n currentFrame: 0,\n startFrame: 0,\n endFrame: 0,\n fps: 0,\n isAnimated: false\n }\n }\n\n /**\n * Compute UV offset and scale for a frame in a sprite sheet\n * @param {number} frame - Frame index (0-based)\n * @param {number} framesPerRow - Number of frames per row in the sheet\n * @param {number} [totalFrames] - Optional total frames (for non-square sheets)\n * @returns {{ offset: [number, number], scale: [number, number] }}\n */\n computeFrameUV(frame, framesPerRow, totalFrames = null) {\n if (framesPerRow <= 1) {\n // Single frame - full texture\n return {\n offset: [0, 0],\n scale: [1, 1]\n }\n }\n\n const col = frame % framesPerRow\n const row = Math.floor(frame / framesPerRow)\n\n // Calculate number of rows based on totalFrames or assume single row\n // For sprite sheets like 256x32 with 8 frames, there's only 1 row\n // so scaleY should be 1 (full height), not 1/8\n const numRows = totalFrames ? Math.ceil(totalFrames / framesPerRow) : (row + 1)\n const scaleX = 1.0 / framesPerRow\n const scaleY = 1.0 / Math.max(1, numRows)\n\n // X offset is straightforward: column * frame width\n const xOffset = col * scaleX\n\n // Y offset with flipY: top of image is at v=1, bottom at v=0\n // For single-row sheets, yOffset is 0 and scaleY is 1\n // For multi-row sheets, calculate based on row\n const yOffset = row * scaleY\n\n return {\n offset: [xOffset, yOffset],\n scale: [scaleX, scaleY]\n }\n }\n\n /**\n * Get or load a sprite texture\n * @param {string} url - Texture URL\n * @returns {Promise<Texture>}\n */\n async loadTexture(url) {\n if (this._textureCache.has(url)) {\n return this._textureCache.get(url)\n }\n\n const texture = await Texture.fromImage(this.engine, url, {\n srgb: true,\n generateMips: true,\n flipY: true\n })\n\n this._textureCache.set(url, texture)\n return texture\n }\n\n /**\n * Get or create a material for a sprite type\n * @param {string} textureUrl - Texture URL\n * @param {number} roughness - Roughness value (0-1)\n * @param {string} pivot - Pivot mode\n * @returns {Material}\n */\n async getSpriteMaterial(textureUrl, roughness = 0.7, pivot = 'center') {\n const key = `sprite:${textureUrl}:${pivot}:r${roughness.toFixed(2)}`\n\n if (this._materialCache.has(key)) {\n return this._materialCache.get(key)\n }\n\n // Load sprite texture\n const albedoTexture = await this.loadTexture(textureUrl)\n\n // Create default textures for other slots\n const normalTexture = await Texture.fromRGBA(this.engine, 0.5, 0.5, 1.0, 1.0) // Flat normal\n const aoTexture = await Texture.fromRGBA(this.engine, 1.0, 1.0, 1.0, 1.0) // No AO\n const rmTexture = await Texture.fromRGBA(this.engine, 0.0, roughness, 0.0, 1.0) // Roughness, no metallic\n const emissionTexture = await Texture.fromRGBA(this.engine, 0.0, 0.0, 0.0, 1.0) // No emission\n\n const material = new Material(\n [albedoTexture, normalTexture, aoTexture, rmTexture, emissionTexture],\n {\n billboardMode: this._pivotToMode(pivot),\n spriteRoughness: roughness\n },\n key,\n this.engine\n )\n\n // Enable alpha hash for cutout transparency\n material.alphaHash = true\n material.alphaHashScale = 1.0\n\n this._materialCache.set(key, material)\n return material\n }\n\n /**\n * Get or create billboard quad geometry for a pivot mode\n * @param {string} pivot - Pivot mode: 'center', 'bottom', 'horizontal'\n * @returns {Geometry}\n */\n getGeometry(pivot = 'center') {\n if (this._geometryCache.has(pivot)) {\n return this._geometryCache.get(pivot)\n }\n\n const geometry = Geometry.billboardQuad(this.engine, pivot)\n this._geometryCache.set(pivot, geometry)\n return geometry\n }\n\n /**\n * Convert pivot string to billboard mode number\n * @param {string} pivot - Pivot mode\n * @returns {number}\n */\n _pivotToMode(pivot) {\n switch (pivot) {\n case 'center': return 1\n case 'bottom': return 2\n case 'horizontal': return 3\n default: return 0\n }\n }\n\n /**\n * Register a sprite entity for animation tracking\n * @param {string} entityId - Entity ID\n * @param {Object} entity - Entity object\n */\n registerEntity(entityId, entity) {\n if (!entity.sprite) return\n\n const spriteInfo = this.parseSprite(entity.sprite)\n if (!spriteInfo) return\n\n const frameInfo = this.parseFrame(entity.frame || 0)\n\n this._spriteEntities.set(entityId, {\n entity,\n spriteInfo,\n frameInfo,\n animTime: 0\n })\n }\n\n /**\n * Unregister a sprite entity\n * @param {string} entityId - Entity ID\n */\n unregisterEntity(entityId) {\n this._spriteEntities.delete(entityId)\n }\n\n /**\n * Update sprite animations\n * @param {number} dt - Delta time in seconds\n */\n update(dt) {\n for (const [entityId, data] of this._spriteEntities) {\n const { entity, spriteInfo, frameInfo } = data\n\n if (!frameInfo.isAnimated) continue\n\n // Advance animation time\n data.animTime += dt * frameInfo.fps\n\n // Calculate current frame (loop by default)\n const frameCount = frameInfo.endFrame - frameInfo.startFrame + 1\n const frameOffset = Math.floor(data.animTime) % frameCount\n frameInfo.currentFrame = frameInfo.startFrame + frameOffset\n\n // Update entity's UV transform\n const uv = this.computeFrameUV(frameInfo.currentFrame, spriteInfo.framesPerRow)\n entity._uvTransform = [uv.offset[0], uv.offset[1], uv.scale[0], uv.scale[1]]\n }\n }\n\n /**\n * Get instance data for a sprite entity\n * @param {Object} entity - Entity with sprite properties\n * @returns {{ uvTransform: [number, number, number, number], color: [number, number, number, number] }}\n */\n getSpriteInstanceData(entity) {\n // If entity has pre-computed _uvTransform (from animation), use it\n if (entity._uvTransform) {\n return {\n uvTransform: entity._uvTransform,\n color: entity.color || [1, 1, 1, 1]\n }\n }\n\n // Otherwise compute from sprite properties\n const spriteInfo = this.parseSprite(entity.sprite)\n if (!spriteInfo) {\n return {\n uvTransform: [0, 0, 1, 1],\n color: entity.color || [1, 1, 1, 1]\n }\n }\n\n const frameInfo = this.parseFrame(entity.frame || 0)\n const uv = this.computeFrameUV(frameInfo.currentFrame, spriteInfo.framesPerRow)\n\n return {\n uvTransform: [uv.offset[0], uv.offset[1], uv.scale[0], uv.scale[1]],\n color: entity.color || [1, 1, 1, 1]\n }\n }\n\n /**\n * Create a complete sprite asset (geometry + material + mesh)\n * @param {string} spriteString - Sprite definition\n * @param {string} pivot - Pivot mode\n * @param {number} roughness - Roughness value\n * @returns {Promise<{ geometry: Geometry, material: Material, mesh: Mesh }>}\n */\n async createSpriteAsset(spriteString, pivot = 'center', roughness = 0.7) {\n const spriteInfo = this.parseSprite(spriteString)\n if (!spriteInfo) return null\n\n const geometry = this.getGeometry(pivot)\n const material = await this.getSpriteMaterial(spriteInfo.url, roughness, pivot)\n const mesh = new Mesh(this.engine, geometry, material)\n\n return { geometry, material, mesh, spriteInfo }\n }\n\n /**\n * Destroy and clean up resources\n */\n destroy() {\n this._textureCache.clear()\n this._materialCache.clear()\n this._geometryCache.clear()\n this._spriteEntities.clear()\n }\n}\n\nexport { SpriteSystem }\n","/**\n * ParticleEmitter - Configuration class for particle spawning behavior\n *\n * Defines how particles are spawned, their initial properties, physics behavior,\n * and rendering options. Used by ParticleSystem to create and manage particles.\n */\n\nlet _emitterUID = 1\n\nclass ParticleEmitter {\n constructor(config = {}) {\n this.uid = _emitterUID++\n this.name = config.name || `emitter_${this.uid}`\n\n // Apply behavior preset FIRST so user config can override\n const behavior = config.behavior || 'default'\n this.behavior = behavior\n\n // Get preset defaults (empty for 'default')\n const presetDefaults = this._getPresetDefaults(behavior)\n\n // Merge: defaults -> preset -> user config\n const merged = { ...presetDefaults, ...config }\n\n // === Spawning Configuration ===\n // Emitter position in world space\n this.position = merged.position || [0, 0, 0]\n\n // Spawn volume: 'point' | 'box' | 'sphere'\n this.volume = merged.volume || 'point'\n\n // Size of spawn volume (for box: [x,y,z], for sphere: [radius])\n this.volumeSize = merged.volumeSize || [1, 1, 1]\n\n // Particles spawned per second (0 = manual spawn only)\n this.spawnRate = merged.spawnRate ?? 10\n\n // Initial burst of particles on activation\n this.spawnBurst = merged.spawnBurst ?? 0\n\n // Maximum particles in this emitter's pool\n this.maxParticles = merged.maxParticles ?? 1000\n\n // === Particle Initial Properties ===\n // Lifetime range [min, max] in seconds\n this.lifetime = merged.lifetime || [1.0, 2.0]\n\n // Initial speed range [min, max]\n this.speed = merged.speed || [1.0, 3.0]\n\n // Emission direction (normalized) - particles shoot this way\n this.direction = merged.direction || [0, 1, 0]\n\n // Cone spread (0 = tight beam, 1 = hemisphere)\n this.spread = merged.spread ?? 0.5\n\n // Size over lifetime [start, end]\n this.size = merged.size || [0.5, 0.1]\n\n // Particle color tint [r, g, b, a]\n this.color = merged.color || [1, 1, 1, 1]\n\n // Fade timing in seconds\n this.fadeIn = merged.fadeIn ?? 0.1\n this.fadeOut = merged.fadeOut ?? 0.3\n\n // === Physics ===\n // Gravity acceleration [x, y, z]\n this.gravity = merged.gravity || [0, -9.8, 0]\n\n // Air resistance (0 = none, 1 = high drag)\n this.drag = merged.drag ?? 0.1\n\n // Random turbulence strength\n this.turbulence = merged.turbulence ?? 0.0\n\n // === Rendering ===\n // Texture URL for particles\n this.texture = merged.texture || null\n\n // Frames per row for sprite sheets\n this.framesPerRow = merged.framesPerRow ?? 1\n\n // Total frames in sprite sheet (optional, for animation)\n this.totalFrames = merged.totalFrames ?? null\n\n // Animation FPS (0 = no animation)\n this.animationFPS = merged.animationFPS ?? 0\n\n // Blend mode: 'additive' | 'alpha'\n this.blendMode = merged.blendMode || 'additive'\n\n // Depth offset to prevent z-fighting\n this.zOffset = merged.zOffset ?? 0.01\n\n // Soft particle depth fade distance (meters)\n this.softness = merged.softness ?? 0.25\n\n // Rotation speed in radians per second\n this.rotationSpeed = merged.rotationSpeed ?? 0.5\n\n // Whether particles receive basic lighting\n this.lit = merged.lit ?? false\n\n // Emissive multiplier (1.0 = normal, >1 = brighter for fire/sparks)\n this.emissive = merged.emissive ?? 1.0\n\n // === Runtime State ===\n this.enabled = true\n this.spawnAccumulator = 0 // Fractional particle spawn buildup\n this.totalSpawned = 0\n this.aliveCount = 0\n\n // GPU buffer references (set by ParticleSystem)\n this.particleBuffer = null\n this.indirectBuffer = null\n }\n\n /**\n * Get behavior preset defaults\n * @param {string} behavior - Preset name\n * @returns {Object} Default values for the preset\n */\n _getPresetDefaults(behavior) {\n const presets = {\n smoke: {\n lifetime: [2.0, 4.0],\n speed: [0.5, 1.5],\n direction: [0, 1, 0],\n spread: 0.3,\n size: [0.3, 2.0],\n color: [0.5, 0.5, 0.5, 0.6],\n fadeIn: 0.2,\n fadeOut: 1.0,\n gravity: [0, 0.5, 0],\n drag: 0.3,\n turbulence: 0.3,\n blendMode: 'alpha',\n rotationSpeed: 0.3,\n softness: 0.25\n },\n fire: {\n lifetime: [0.3, 0.8],\n speed: [2.0, 4.0],\n direction: [0, 1, 0],\n spread: 0.2,\n size: [0.5, 0.1],\n color: [1.0, 0.6, 0.2, 1.0],\n fadeIn: 0.05,\n fadeOut: 0.3,\n gravity: [0, 2.0, 0],\n drag: 0.1,\n turbulence: 0.5,\n blendMode: 'additive',\n emissive: 3.0, // Bright fire\n lit: false // Fire is self-illuminating\n },\n sparks: {\n lifetime: [0.5, 1.5],\n speed: [5.0, 10.0],\n direction: [0, 1, 0],\n spread: 0.8,\n size: [0.1, 0.05],\n color: [1.0, 0.8, 0.3, 1.0],\n fadeIn: 0.0,\n fadeOut: 0.5,\n gravity: [0, -9.8, 0],\n drag: 0.05,\n turbulence: 0.1,\n blendMode: 'additive',\n emissive: 5.0, // Very bright sparks\n lit: false // Sparks are self-illuminating\n },\n fog: {\n lifetime: [5.0, 10.0],\n speed: [0.1, 0.3],\n direction: [0, 0, 0],\n spread: 1.0,\n size: [2.0, 3.0],\n color: [0.8, 0.8, 0.9, 0.3],\n fadeIn: 1.0,\n fadeOut: 2.0,\n gravity: [0, 0, 0],\n drag: 0.5,\n turbulence: 0.1,\n blendMode: 'alpha',\n volume: 'box'\n }\n }\n return presets[behavior] || {}\n }\n\n /**\n * Get a random value within a [min, max] range\n * @param {number[]} range - [min, max]\n * @param {number} seed - Random seed (0-1)\n * @returns {number}\n */\n static randomInRange(range, seed) {\n return range[0] + (range[1] - range[0]) * seed\n }\n\n /**\n * Get a random point within the spawn volume\n * @param {number[]} seeds - [seed1, seed2, seed3] random values (0-1)\n * @returns {number[]} - [x, y, z] position\n */\n getSpawnPosition(seeds) {\n const [s1, s2, s3] = seeds\n const [px, py, pz] = this.position\n\n switch (this.volume) {\n case 'point':\n return [px, py, pz]\n\n case 'box': {\n const [sx, sy, sz] = this.volumeSize\n return [\n px + (s1 - 0.5) * sx,\n py + (s2 - 0.5) * sy,\n pz + (s3 - 0.5) * sz\n ]\n }\n\n case 'sphere': {\n // Uniform distribution within sphere\n const radius = this.volumeSize[0] * Math.cbrt(s1)\n const theta = s2 * 2 * Math.PI\n const phi = Math.acos(2 * s3 - 1)\n return [\n px + radius * Math.sin(phi) * Math.cos(theta),\n py + radius * Math.cos(phi),\n pz + radius * Math.sin(phi) * Math.sin(theta)\n ]\n }\n\n default:\n return [px, py, pz]\n }\n }\n\n /**\n * Get a random emission direction with spread\n * @param {number[]} seeds - [seed1, seed2] random values (0-1)\n * @returns {number[]} - [x, y, z] normalized direction\n */\n getEmissionDirection(seeds) {\n const [s1, s2] = seeds\n const [dx, dy, dz] = this.direction\n\n if (this.spread <= 0) {\n // No spread - use exact direction\n return [dx, dy, dz]\n }\n\n // Create cone around direction\n // Map spread to cone half-angle (0=0°, 1=90°)\n const halfAngle = this.spread * Math.PI * 0.5\n const theta = s1 * 2 * Math.PI\n const cosAngle = Math.cos(halfAngle * s2)\n const sinAngle = Math.sqrt(1 - cosAngle * cosAngle)\n\n // Random direction in cone around +Y\n let rx = sinAngle * Math.cos(theta)\n let ry = cosAngle\n let rz = sinAngle * Math.sin(theta)\n\n // Rotate from +Y to emission direction\n // Use simplified rotation (assumes direction is normalized)\n const len = Math.sqrt(dx * dx + dy * dy + dz * dz)\n if (len < 0.001) return [rx, ry, rz] // No direction\n\n const ndx = dx / len\n const ndy = dy / len\n const ndz = dz / len\n\n // If direction is close to +Y, return as-is\n if (ndy > 0.999) return [rx, ry, rz]\n if (ndy < -0.999) return [rx, -ry, rz] // Flip for -Y\n\n // Build rotation matrix from +Y to direction\n const ax = -ndz, az = ndx // Cross product Y × dir (simplified)\n const alen = Math.sqrt(ax * ax + az * az)\n const nax = ax / alen, naz = az / alen\n const c = ndy, s = Math.sqrt(1 - c * c)\n\n // Rodrigues' rotation formula (simplified for rotation around axis in XZ plane)\n const outX = rx * (c + nax * nax * (1 - c)) + ry * (-naz * s) + rz * (nax * naz * (1 - c))\n const outY = rx * (naz * s) + ry * c + rz * (-nax * s)\n const outZ = rx * (naz * nax * (1 - c)) + ry * (nax * s) + rz * (c + naz * naz * (1 - c))\n\n return [outX, outY, outZ]\n }\n\n /**\n * Clone this emitter with optional overrides\n * @param {Object} overrides - Properties to override\n * @returns {ParticleEmitter}\n */\n clone(overrides = {}) {\n const config = {\n ...this.toJSON(),\n ...overrides,\n _presetApplied: true // Don't re-apply preset\n }\n return new ParticleEmitter(config)\n }\n\n /**\n * Serialize emitter configuration to JSON\n * @returns {Object}\n */\n toJSON() {\n return {\n name: this.name,\n position: [...this.position],\n volume: this.volume,\n volumeSize: [...this.volumeSize],\n spawnRate: this.spawnRate,\n spawnBurst: this.spawnBurst,\n maxParticles: this.maxParticles,\n lifetime: [...this.lifetime],\n speed: [...this.speed],\n direction: [...this.direction],\n spread: this.spread,\n size: [...this.size],\n color: [...this.color],\n fadeIn: this.fadeIn,\n fadeOut: this.fadeOut,\n gravity: [...this.gravity],\n drag: this.drag,\n turbulence: this.turbulence,\n texture: this.texture,\n framesPerRow: this.framesPerRow,\n totalFrames: this.totalFrames,\n animationFPS: this.animationFPS,\n blendMode: this.blendMode,\n zOffset: this.zOffset,\n softness: this.softness,\n rotationSpeed: this.rotationSpeed,\n lit: this.lit,\n emissive: this.emissive,\n behavior: this.behavior,\n enabled: this.enabled\n }\n }\n\n /**\n * Create emitter from JSON\n * @param {Object} json - Serialized configuration\n * @returns {ParticleEmitter}\n */\n static fromJSON(json) {\n return new ParticleEmitter({ ...json, _presetApplied: true })\n }\n}\n\nexport { ParticleEmitter }\n","import { ParticleEmitter } from \"./ParticleEmitter.js\"\nimport { Texture } from \"../Texture.js\"\n\n/**\n * ParticleSystem - Main system managing particle emitters and GPU resources\n *\n * Handles:\n * - Emitter registry and lifecycle\n * - GPU buffer allocation with global budget\n * - Texture caching for particle sprites\n * - Compute shader dispatch for particle simulation\n * - Data preparation for particle rendering\n */\n\n// Particle struct size in bytes (must match WGSL)\n// position (vec3f) + lifetime (f32) + velocity (vec3f) + maxLifetime (f32) +\n// color (vec4f) + size (vec2f) + rotation (f32) + flags (u32) +\n// lighting (vec3f) + lightingPad (f32) = 80 bytes\nconst PARTICLE_STRIDE = 80\n\n// Spawn request struct size: position (vec3f) + velocity (vec3f) + lifetime (f32) +\n// maxLifetime (f32) + color (vec4f) + startSize (f32) + endSize (f32) +\n// seed (f32) + flags (u32) = 64 bytes\nconst SPAWN_REQUEST_STRIDE = 64\n\nclass ParticleSystem {\n constructor(engine) {\n this.engine = engine\n this.device = engine.device\n\n // Global particle budget\n this.globalMaxParticles = 50000\n this.globalAliveCount = 0\n\n // Emitter registry: uid -> ParticleEmitter\n this._emitters = new Map()\n\n // Active emitter list (for iteration)\n this._activeEmitters = []\n\n // Texture cache: url -> Texture\n this._textureCache = new Map()\n\n // Default particle texture (white circle)\n this._defaultTexture = null\n\n // GPU resources (created on first use)\n this._particleBuffer = null // Storage buffer for all particles\n this._spawnBuffer = null // Buffer for spawn requests\n this._emitterBuffer = null // Buffer for emitter uniforms\n this._counterBuffer = null // Atomic counters\n this._readbackBuffer = null // For reading counter values\n\n // Compute pipeline (created by ParticlePass)\n this.simulatePipeline = null\n this.spawnPipeline = null\n\n // Time tracking\n this._time = 0\n this._lastSpawnTime = 0\n\n // Spawn queue: accumulated spawn requests to send to GPU\n this._spawnQueue = []\n this._maxSpawnPerFrame = 1000 // Limit spawns per frame\n\n // Emitter uniforms buffer data\n this._emitterData = new Float32Array(32) // Per-emitter uniforms\n\n this._initialized = false\n }\n\n /**\n * Initialize GPU resources\n */\n async init() {\n if (this._initialized) return\n\n // Create particle storage buffer\n this._particleBuffer = this.device.createBuffer({\n size: this.globalMaxParticles * PARTICLE_STRIDE,\n usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST,\n label: 'ParticleSystem particles'\n })\n\n // Create spawn request buffer (sized for max spawns per frame)\n this._spawnBuffer = this.device.createBuffer({\n size: this._maxSpawnPerFrame * SPAWN_REQUEST_STRIDE,\n usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST,\n label: 'ParticleSystem spawn requests'\n })\n\n // Create counter buffer: [aliveCount, nextFreeIndex, spawnCount, frameCount]\n this._counterBuffer = this.device.createBuffer({\n size: 16,\n usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST | GPUBufferUsage.COPY_SRC,\n label: 'ParticleSystem counters'\n })\n\n // Readback buffer for counter values\n this._readbackBuffer = this.device.createBuffer({\n size: 16,\n usage: GPUBufferUsage.COPY_DST | GPUBufferUsage.MAP_READ,\n label: 'ParticleSystem counter readback'\n })\n\n // Create emitter uniforms buffer\n this._emitterBuffer = this.device.createBuffer({\n size: 256, // Enough for emitter parameters\n usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST,\n label: 'ParticleSystem emitter uniforms'\n })\n\n // Initialize counters\n const counterData = new Uint32Array([0, 0, 0, 0]) // [aliveCount, nextFreeIndex, spawnCount, frameCount]\n this.device.queue.writeBuffer(this._counterBuffer, 0, counterData)\n\n // Create default texture (white circle with soft edges)\n this._defaultTexture = await this._createDefaultTexture()\n\n this._initialized = true\n }\n\n /**\n * Create a default particle texture (soft white circle)\n */\n async _createDefaultTexture() {\n const size = 64\n const data = new Uint8Array(size * size * 4)\n\n const center = size / 2\n const maxRadius = size / 2\n\n for (let y = 0; y < size; y++) {\n for (let x = 0; x < size; x++) {\n const dx = x - center + 0.5\n const dy = y - center + 0.5\n const dist = Math.sqrt(dx * dx + dy * dy)\n\n // Soft circular falloff\n const alpha = Math.max(0, 1 - dist / maxRadius)\n const softAlpha = alpha * alpha * (3 - 2 * alpha) // Smoothstep\n\n const i = (y * size + x) * 4\n data[i + 0] = 255 // R\n data[i + 1] = 255 // G\n data[i + 2] = 255 // B\n data[i + 3] = Math.floor(softAlpha * 255) // A\n }\n }\n\n return Texture.fromRawData(this.engine, data, size, size, {\n srgb: true,\n generateMips: true\n })\n }\n\n /**\n * Load or get cached particle texture\n * @param {string} url - Texture URL\n * @returns {Promise<Texture>}\n */\n async loadTexture(url) {\n if (!url) return this._defaultTexture\n\n if (this._textureCache.has(url)) {\n return this._textureCache.get(url)\n }\n\n const texture = await Texture.fromImage(this.engine, url, {\n srgb: true,\n generateMips: true,\n flipY: true\n })\n\n this._textureCache.set(url, texture)\n return texture\n }\n\n /**\n * Register a new particle emitter\n * @param {ParticleEmitter|Object} config - Emitter or configuration\n * @returns {ParticleEmitter}\n */\n addEmitter(config) {\n const emitter = config instanceof ParticleEmitter\n ? config\n : new ParticleEmitter(config)\n\n this._emitters.set(emitter.uid, emitter)\n this._activeEmitters.push(emitter)\n\n // Queue initial burst if specified\n if (emitter.spawnBurst > 0) {\n this._queueSpawn(emitter, emitter.spawnBurst)\n }\n\n return emitter\n }\n\n /**\n * Remove an emitter\n * @param {number|ParticleEmitter} emitterOrId - Emitter or UID\n */\n removeEmitter(emitterOrId) {\n const uid = typeof emitterOrId === 'number' ? emitterOrId : emitterOrId.uid\n const emitter = this._emitters.get(uid)\n if (!emitter) return\n\n this._emitters.delete(uid)\n const idx = this._activeEmitters.indexOf(emitter)\n if (idx >= 0) this._activeEmitters.splice(idx, 1)\n }\n\n /**\n * Get emitter by UID\n * @param {number} uid - Emitter UID\n * @returns {ParticleEmitter|null}\n */\n getEmitter(uid) {\n return this._emitters.get(uid) || null\n }\n\n /**\n * Queue particles for spawning\n * @param {ParticleEmitter} emitter - Emitter to spawn from\n * @param {number} count - Number of particles to spawn\n */\n _queueSpawn(emitter, count) {\n if (!emitter.enabled) return\n\n // Respect global budget\n const available = this.globalMaxParticles - this.globalAliveCount\n const toSpawn = Math.min(count, available, emitter.maxParticles - emitter.aliveCount)\n\n if (toSpawn <= 0) return\n\n for (let i = 0; i < toSpawn; i++) {\n // Generate random seeds\n const seed1 = Math.random()\n const seed2 = Math.random()\n const seed3 = Math.random()\n const seed4 = Math.random()\n const seed5 = Math.random()\n\n // Calculate spawn position and velocity\n const position = emitter.getSpawnPosition([seed1, seed2, seed3])\n const direction = emitter.getEmissionDirection([seed4, seed5])\n const speed = ParticleEmitter.randomInRange(emitter.speed, Math.random())\n const velocity = [\n direction[0] * speed,\n direction[1] * speed,\n direction[2] * speed\n ]\n\n // Calculate lifetime\n const lifetime = ParticleEmitter.randomInRange(emitter.lifetime, Math.random())\n\n // Random rotation: -PI to +PI (sign determines spin direction)\n const rotation = (Math.random() - 0.5) * Math.PI * 2\n\n // Flags: bit 0 = alive, bit 1 = additive, bits 8-15 = emitter index\n const emitterIndex = this._activeEmitters.indexOf(emitter)\n const flags = 1 | (emitter.blendMode === 'additive' ? 2 : 0) | ((emitterIndex & 0xFF) << 8)\n\n this._spawnQueue.push({\n emitter,\n position,\n velocity,\n lifetime,\n maxLifetime: lifetime,\n color: [...emitter.color],\n startSize: emitter.size[0],\n endSize: emitter.size[1],\n rotation,\n flags\n })\n }\n }\n\n /**\n * Update particle system\n * @param {number} dt - Delta time in seconds\n */\n update(dt) {\n this._time += dt\n\n // Estimate particle deaths based on average lifetime\n // This prevents globalAliveCount from growing forever\n this._estimateDeaths(dt)\n\n // Process spawn rates for each emitter\n for (const emitter of this._activeEmitters) {\n if (!emitter.enabled || emitter.spawnRate <= 0) continue\n\n // Accumulate spawn time\n emitter.spawnAccumulator += dt * emitter.spawnRate\n const toSpawn = Math.floor(emitter.spawnAccumulator)\n\n if (toSpawn > 0) {\n emitter.spawnAccumulator -= toSpawn\n this._queueSpawn(emitter, toSpawn)\n }\n }\n }\n\n /**\n * Estimate particle deaths based on spawn history and average lifetime\n * @param {number} dt - Delta time\n */\n _estimateDeaths(dt) {\n // Simple estimation: assume deaths occur at roughly the spawn rate\n // after accounting for average particle lifetime\n // This is approximate but prevents the counter from growing forever\n\n let totalSpawnRate = 0\n let totalAvgLifetime = 0\n let activeCount = 0\n\n for (const emitter of this._activeEmitters) {\n if (emitter.enabled && emitter.spawnRate > 0) {\n totalSpawnRate += emitter.spawnRate\n const avgLifetime = (emitter.lifetime[0] + emitter.lifetime[1]) / 2\n totalAvgLifetime += avgLifetime\n activeCount++\n }\n }\n\n if (activeCount > 0) {\n const avgLifetime = totalAvgLifetime / activeCount\n // At steady state: deaths per second ≈ spawn rate\n // Apply deaths proportional to dt\n const estimatedDeaths = totalSpawnRate * dt\n this.globalAliveCount = Math.max(0, this.globalAliveCount - estimatedDeaths)\n }\n }\n\n /**\n * Write spawn requests to GPU and execute spawn compute pass\n * @param {GPUCommandEncoder} commandEncoder\n */\n executeSpawn(commandEncoder) {\n if (this._spawnQueue.length === 0) return\n\n // Limit spawns per frame\n const toProcess = Math.min(this._spawnQueue.length, this._maxSpawnPerFrame)\n const spawnData = new Float32Array(toProcess * (SPAWN_REQUEST_STRIDE / 4))\n\n for (let i = 0; i < toProcess; i++) {\n const spawn = this._spawnQueue[i]\n const offset = i * 16 // 16 floats per spawn\n\n // position (vec3f) + lifetime (f32)\n spawnData[offset + 0] = spawn.position[0]\n spawnData[offset + 1] = spawn.position[1]\n spawnData[offset + 2] = spawn.position[2]\n spawnData[offset + 3] = spawn.lifetime\n\n // velocity (vec3f) + maxLifetime (f32)\n spawnData[offset + 4] = spawn.velocity[0]\n spawnData[offset + 5] = spawn.velocity[1]\n spawnData[offset + 6] = spawn.velocity[2]\n spawnData[offset + 7] = spawn.maxLifetime\n\n // color (vec4f)\n spawnData[offset + 8] = spawn.color[0]\n spawnData[offset + 9] = spawn.color[1]\n spawnData[offset + 10] = spawn.color[2]\n spawnData[offset + 11] = spawn.color[3]\n\n // startSize + endSize + rotation + flags\n spawnData[offset + 12] = spawn.startSize\n spawnData[offset + 13] = spawn.endSize\n spawnData[offset + 14] = spawn.rotation\n // flags needs to be written as uint32\n }\n\n // Write flags separately as uint32\n const flagsView = new Uint32Array(spawnData.buffer)\n for (let i = 0; i < toProcess; i++) {\n flagsView[i * 16 + 15] = this._spawnQueue[i].flags\n }\n\n // Upload spawn data\n this.device.queue.writeBuffer(this._spawnBuffer, 0, spawnData)\n\n // Update spawn count in counter buffer\n const counterUpdate = new Uint32Array([0, 0, toProcess, 0])\n this.device.queue.writeBuffer(this._counterBuffer, 8, counterUpdate.subarray(2, 3))\n\n // Clear processed spawns\n this._spawnQueue.splice(0, toProcess)\n\n // Update alive count estimate\n this.globalAliveCount += toProcess\n for (const emitter of this._activeEmitters) {\n emitter.totalSpawned += toProcess\n }\n }\n\n /**\n * Prepare emitter uniforms for compute/render\n * @param {ParticleEmitter} emitter\n * @returns {Float32Array}\n */\n getEmitterUniforms(emitter) {\n const data = this._emitterData\n\n // Gravity (vec3f) + dt (f32)\n data[0] = emitter.gravity[0]\n data[1] = emitter.gravity[1]\n data[2] = emitter.gravity[2]\n data[3] = 0 // dt will be set by pass\n\n // drag + turbulence + fadeIn + fadeOut\n data[4] = emitter.drag\n data[5] = emitter.turbulence\n data[6] = emitter.fadeIn\n data[7] = emitter.fadeOut\n\n // startSize + endSize + time + maxParticles\n data[8] = emitter.size[0]\n data[9] = emitter.size[1]\n data[10] = this._time\n data[11] = emitter.maxParticles\n\n // Color (vec4f)\n data[12] = emitter.color[0]\n data[13] = emitter.color[1]\n data[14] = emitter.color[2]\n data[15] = emitter.color[3]\n\n // Softness + zOffset + blendMode (as float) + lit\n data[16] = emitter.softness\n data[17] = emitter.zOffset\n data[18] = emitter.blendMode === 'additive' ? 1.0 : 0.0\n data[19] = emitter.lit ? 1.0 : 0.0\n\n return data\n }\n\n /**\n * Get particle buffer for rendering\n * @returns {GPUBuffer}\n */\n getParticleBuffer() {\n return this._particleBuffer\n }\n\n /**\n * Get counter buffer\n * @returns {GPUBuffer}\n */\n getCounterBuffer() {\n return this._counterBuffer\n }\n\n /**\n * Get spawn buffer\n * @returns {GPUBuffer}\n */\n getSpawnBuffer() {\n return this._spawnBuffer\n }\n\n /**\n * Get all active emitters\n * @returns {ParticleEmitter[]}\n */\n getActiveEmitters() {\n return this._activeEmitters.filter(e => e.enabled)\n }\n\n /**\n * Get total particle count across all emitters\n * @returns {number}\n */\n getTotalParticleCount() {\n return this.globalAliveCount\n }\n\n /**\n * Get default particle texture\n * @returns {Texture}\n */\n getDefaultTexture() {\n return this._defaultTexture\n }\n\n /**\n * Reset all particles (clear all emitters)\n */\n reset() {\n // Clear counters\n const counterData = new Uint32Array([0, 0, 0, 0])\n this.device.queue.writeBuffer(this._counterBuffer, 0, counterData)\n\n this.globalAliveCount = 0\n this._spawnQueue = []\n\n for (const emitter of this._activeEmitters) {\n emitter.aliveCount = 0\n emitter.spawnAccumulator = 0\n }\n }\n\n /**\n * Spawn a burst of particles at a specific position\n * @param {Object} config - Burst configuration\n * @param {number[]} config.position - [x, y, z] world position\n * @param {number} config.count - Number of particles\n * @param {Object} [config.overrides] - Emitter property overrides\n * @returns {ParticleEmitter} Temporary emitter used for the burst\n */\n burst(config) {\n const { position, count, overrides = {} } = config\n\n // Create temporary emitter for this burst\n const emitter = new ParticleEmitter({\n position,\n spawnRate: 0, // No continuous spawning\n spawnBurst: count,\n ...overrides\n })\n\n // Add emitter (burst will be queued automatically)\n this.addEmitter(emitter)\n\n return emitter\n }\n\n /**\n * Destroy and clean up resources\n */\n destroy() {\n if (this._particleBuffer) {\n this._particleBuffer.destroy()\n this._particleBuffer = null\n }\n if (this._spawnBuffer) {\n this._spawnBuffer.destroy()\n this._spawnBuffer = null\n }\n if (this._counterBuffer) {\n this._counterBuffer.destroy()\n this._counterBuffer = null\n }\n if (this._readbackBuffer) {\n this._readbackBuffer.destroy()\n this._readbackBuffer = null\n }\n if (this._emitterBuffer) {\n this._emitterBuffer.destroy()\n this._emitterBuffer = null\n }\n\n this._textureCache.clear()\n this._emitters.clear()\n this._activeEmitters = []\n this._spawnQueue = []\n this._initialized = false\n }\n}\n\nexport { ParticleSystem, PARTICLE_STRIDE, SPAWN_REQUEST_STRIDE }\n","import { ShadowPass } from \"./passes/ShadowPass.js\"\nimport { ReflectionPass } from \"./passes/ReflectionPass.js\"\nimport { PlanarReflectionPass } from \"./passes/PlanarReflectionPass.js\"\nimport { GBufferPass } from \"./passes/GBufferPass.js\"\nimport { HiZPass } from \"./passes/HiZPass.js\"\nimport { AOPass } from \"./passes/AOPass.js\"\nimport { LightingPass } from \"./passes/LightingPass.js\"\nimport { SSGITilePass } from \"./passes/SSGITilePass.js\"\nimport { SSGIPass } from \"./passes/SSGIPass.js\"\nimport { RenderPostPass } from \"./passes/RenderPostPass.js\"\nimport { BloomPass } from \"./passes/BloomPass.js\"\nimport { TransparentPass } from \"./passes/TransparentPass.js\"\nimport { ParticlePass } from \"./passes/ParticlePass.js\"\nimport { FogPass } from \"./passes/FogPass.js\"\nimport { VolumetricFogPass } from \"./passes/VolumetricFogPass.js\"\nimport { PostProcessPass } from \"./passes/PostProcessPass.js\"\nimport { CRTPass } from \"./passes/CRTPass.js\"\nimport { AmbientCapturePass } from \"./passes/AmbientCapturePass.js\"\nimport { HistoryBufferManager } from \"./HistoryBufferManager.js\"\nimport { CullingSystem } from \"../core/CullingSystem.js\"\nimport { InstanceManager } from \"../core/InstanceManager.js\"\nimport { SpriteSystem } from \"../core/SpriteSystem.js\"\nimport { ParticleSystem } from \"../core/ParticleSystem.js\"\nimport { transformBoundingSphere, calculateShadowBoundingSphere, sphereInCascade } from \"../utils/BoundingSphere.js\"\nimport { vec3, mat4 } from \"../math.js\"\nimport { Texture } from \"../Texture.js\"\nimport { Geometry } from \"../Geometry.js\"\n\n/**\n * RenderGraph - Orchestrates the multi-pass rendering pipeline\n *\n * Manages pass execution order, resource dependencies, and integrates\n * with the entity/asset system for data-oriented rendering.\n *\n * Pipeline order:\n * 1. Shadow Pass (CSM + spotlight)\n * 2. Reflection Pass (octahedral probes)\n * 3. Planar Reflection Pass (mirrored camera for water/floors)\n * 4. GBuffer Pass (geometry -> albedo, normal, ARM, emission, velocity, depth)\n * 4b. HiZ Pass (hierarchical-Z for occlusion culling)\n * 5. AO Pass (SSAO)\n * 6. Lighting Pass (deferred lighting)\n * 7. Bloom Pass (HDR bright extraction + blur - moved before SSGI)\n * 8. SSGITile Pass (compute - tile light accumulation)\n * 9. SSGI Pass (screen-space global illumination)\n * 9b. Ambient Capture Pass (6-directional sky-aware ambient)\n * 10. RenderPost Pass (combine SSGI/Planar/Ambient with lighting)\n * 11. Transparent Pass (forward rendering for alpha-blended)\n * 12. Bloom Pass (applied to transparent highlights)\n * 13. PostProcess Pass (bloom composite + tone mapping -> canvas)\n */\nclass RenderGraph {\n constructor(engine = null) {\n // Reference to engine for settings access\n this.engine = engine\n\n // Passes (in execution order)\n this.passes = {\n shadow: null, // Pass 1: Shadow maps\n reflection: null, // Pass 2: Reflection probes\n planarReflection: null, // Pass 3: Planar reflection (mirrored camera)\n gbuffer: null, // Pass 4: GBuffer generation\n hiz: null, // Pass 4b: HiZ reduction (for next frame's occlusion culling)\n ao: null, // Pass 5: SSAO\n lighting: null, // Pass 6: Deferred lighting\n bloom: null, // Pass 7: HDR bloom/glare (moved before SSGI)\n ssgiTile: null, // Pass 8: SSGI tile accumulation (compute)\n ssgi: null, // Pass 9: Screen-space global illumination\n ambientCapture: null, // Pass 9b: 6-directional ambient capture for sky-aware GI\n renderPost: null, // Pass 10: Combine SSGI/Planar with lighting\n transparent: null, // Pass 11: Forward transparent objects\n particles: null, // Pass 12: GPU particle rendering\n postProcess: null, // Pass 13: Tone mapping + bloom composite\n crt: null, // Pass 14: CRT effect (optional)\n }\n\n // History buffer manager for temporal effects\n this.historyManager = null\n\n // Support systems (pass engine reference)\n this.cullingSystem = new CullingSystem(engine)\n this.instanceManager = new InstanceManager(engine)\n this.spriteSystem = new SpriteSystem(engine)\n this.particleSystem = new ParticleSystem(engine)\n\n // Environment map\n this.environmentMap = null\n\n // Noise texture for dithering/jittering (can be blue noise or bayer)\n this.noiseTexture = null\n this.noiseSize = 64 // Will be updated when texture loads\n this.noiseAnimated = true // Whether to animate noise offset each frame\n\n // Effect scaling for expensive passes (bloom, AO, SSGI, planar reflection)\n // When autoScale.enabledForEffects is true and height > maxHeight, effects render at reduced resolution\n this.effectWidth = 0\n this.effectHeight = 0\n this.effectScale = 1.0\n\n // Cache for cloned skins per phase group: \"modelId|animation|phase\" -> { skin, mesh }\n this._skinnedPhaseCache = new Map()\n\n // Cache for individual skins per entity: entityId -> { skin, mesh, geometry }\n this._individualSkinCache = new Map()\n\n // Debug/stats\n this.stats = {\n passTimings: {},\n visibleEntities: 0,\n culledEntities: 0,\n drawCalls: 0,\n triangles: 0\n }\n\n // Last render context for probe capture\n this._lastRenderContext = null\n\n // Probe-specific passes (256x256 for probe face capture)\n this.probePasses = {\n gbuffer: null,\n lighting: null\n }\n }\n\n // Convenience getter for individualRenderDistance from settings\n get individualRenderDistance() {\n return this.engine?.settings?.skinning?.individualRenderDistance ?? 20.0\n }\n\n /**\n * Create and initialize the render graph\n * @param {Engine} engine - Engine instance for settings access\n * @param {Texture} environmentMap - HDR environment map for IBL\n * @param {number} encoding - 0 = equirectangular, 1 = octahedral\n * @returns {Promise<RenderGraph>}\n */\n static async create(engine, environmentMap, encoding = 0) {\n const graph = new RenderGraph(engine)\n await graph.initialize(environmentMap, encoding)\n return graph\n }\n\n /**\n * Initialize all passes\n * @param {Texture} environmentMap - HDR environment map\n * @param {number} encoding - 0 = equirectangular, 1 = octahedral\n */\n async initialize(environmentMap, encoding = 0) {\n const timings = []\n const startTotal = performance.now()\n\n this.environmentMap = environmentMap\n this.environmentEncoding = encoding\n\n // Load noise texture based on settings\n let start = performance.now()\n await this._loadNoiseTexture()\n timings.push({ name: 'loadNoiseTexture', time: performance.now() - start })\n\n // Create passes (pass engine reference)\n this.passes.shadow = new ShadowPass(this.engine)\n this.passes.reflection = new ReflectionPass(this.engine)\n this.passes.planarReflection = new PlanarReflectionPass(this.engine)\n this.passes.gbuffer = new GBufferPass(this.engine)\n this.passes.hiz = new HiZPass(this.engine)\n this.passes.ao = new AOPass(this.engine)\n this.passes.lighting = new LightingPass(this.engine)\n this.passes.bloom = new BloomPass(this.engine)\n this.passes.ssgiTile = new SSGITilePass(this.engine)\n this.passes.ssgi = new SSGIPass(this.engine)\n this.passes.ambientCapture = new AmbientCapturePass(this.engine)\n this.passes.renderPost = new RenderPostPass(this.engine)\n this.passes.transparent = new TransparentPass(this.engine)\n this.passes.particles = new ParticlePass(this.engine)\n this.passes.fog = new FogPass(this.engine)\n this.passes.volumetricFog = new VolumetricFogPass(this.engine)\n this.passes.postProcess = new PostProcessPass(this.engine)\n this.passes.crt = new CRTPass(this.engine)\n\n // Create history buffer manager for temporal effects\n const { canvas } = this.engine\n this.historyManager = new HistoryBufferManager(this.engine)\n start = performance.now()\n await this.historyManager.initialize(canvas.width, canvas.height)\n timings.push({ name: 'init:historyManager', time: performance.now() - start })\n\n // Initialize passes\n start = performance.now()\n await this.passes.shadow.initialize()\n timings.push({ name: 'init:shadow', time: performance.now() - start })\n\n start = performance.now()\n await this.passes.reflection.initialize()\n timings.push({ name: 'init:reflection', time: performance.now() - start })\n\n start = performance.now()\n await this.passes.planarReflection.initialize()\n timings.push({ name: 'init:planarReflection', time: performance.now() - start })\n\n start = performance.now()\n await this.passes.gbuffer.initialize()\n timings.push({ name: 'init:gbuffer', time: performance.now() - start })\n\n start = performance.now()\n await this.passes.hiz.initialize()\n timings.push({ name: 'init:hiz', time: performance.now() - start })\n\n start = performance.now()\n await this.passes.ao.initialize()\n timings.push({ name: 'init:ao', time: performance.now() - start })\n\n start = performance.now()\n await this.passes.lighting.initialize()\n timings.push({ name: 'init:lighting', time: performance.now() - start })\n\n start = performance.now()\n await this.passes.bloom.initialize()\n timings.push({ name: 'init:bloom', time: performance.now() - start })\n\n start = performance.now()\n await this.passes.ssgiTile.initialize()\n timings.push({ name: 'init:ssgiTile', time: performance.now() - start })\n\n start = performance.now()\n await this.passes.ssgi.initialize()\n timings.push({ name: 'init:ssgi', time: performance.now() - start })\n\n start = performance.now()\n await this.passes.renderPost.initialize()\n timings.push({ name: 'init:renderPost', time: performance.now() - start })\n\n start = performance.now()\n await this.passes.ambientCapture.initialize()\n timings.push({ name: 'init:ambientCapture', time: performance.now() - start })\n\n start = performance.now()\n await this.passes.transparent.initialize()\n timings.push({ name: 'init:transparent', time: performance.now() - start })\n\n start = performance.now()\n await this.passes.particles.initialize()\n timings.push({ name: 'init:particles', time: performance.now() - start })\n\n start = performance.now()\n await this.passes.fog.initialize()\n timings.push({ name: 'init:fog', time: performance.now() - start })\n\n start = performance.now()\n await this.passes.volumetricFog.initialize()\n timings.push({ name: 'init:volumetricFog', time: performance.now() - start })\n\n start = performance.now()\n await this.passes.postProcess.initialize()\n timings.push({ name: 'init:postProcess', time: performance.now() - start })\n\n start = performance.now()\n await this.passes.crt.initialize()\n timings.push({ name: 'init:crt', time: performance.now() - start })\n\n // Wire up dependencies\n start = performance.now()\n this.passes.reflection.setFallbackEnvironment(environmentMap, this.environmentEncoding)\n this.passes.lighting.setEnvironmentMap(environmentMap, this.environmentEncoding)\n await this.passes.lighting.setGBuffer(this.passes.gbuffer.getGBuffer())\n this.passes.lighting.setShadowPass(this.passes.shadow)\n this.passes.lighting.setNoise(this.noiseTexture, this.noiseSize, this.noiseAnimated)\n timings.push({ name: 'wire:lighting', time: performance.now() - start })\n\n // Wire up planar reflection pass (shares shadows, environment with main)\n start = performance.now()\n this.passes.planarReflection.setDependencies({\n environmentMap,\n encoding: this.environmentEncoding,\n shadowPass: this.passes.shadow,\n lightingPass: this.passes.lighting,\n noise: this.noiseTexture,\n noiseSize: this.noiseSize\n })\n this.passes.planarReflection.setParticleSystem(this.particleSystem)\n timings.push({ name: 'wire:planarReflection', time: performance.now() - start })\n\n // Wire up ambient capture pass (shares shadows, environment with main)\n start = performance.now()\n this.passes.ambientCapture.setDependencies({\n environmentMap,\n encoding: this.environmentEncoding,\n shadowPass: this.passes.shadow,\n noise: this.noiseTexture,\n noiseSize: this.noiseSize\n })\n // Wire ambient capture output to RenderPost\n this.passes.renderPost.setAmbientCaptureBuffer(this.passes.ambientCapture.getFaceColorsBuffer())\n timings.push({ name: 'wire:ambientCapture', time: performance.now() - start })\n\n // Initialize probe-specific passes at 256x256 (for probe face capture)\n start = performance.now()\n await this._initProbePasses()\n timings.push({ name: 'init:probePasses', time: performance.now() - start })\n\n // Set up probe capture to use the renderer\n const probeCapture = this.passes.reflection.getProbeCapture()\n if (probeCapture) {\n probeCapture.setSceneRenderCallback(this._renderSceneForProbe.bind(this))\n }\n\n // Set up GBuffer pass with noise texture for alpha hashing\n start = performance.now()\n this.passes.gbuffer.setNoise(this.noiseTexture, this.noiseSize, this.noiseAnimated)\n timings.push({ name: 'wire:gbuffer', time: performance.now() - start })\n\n // Wire up HiZ pass with GBuffer depth and CullingSystem\n start = performance.now()\n this.passes.hiz.setDepthTexture(this.passes.gbuffer.getGBuffer()?.depth)\n this.cullingSystem.setHiZPass(this.passes.hiz)\n // Also wire HiZ to passes that use it for occlusion culling\n this.passes.gbuffer.setHiZPass(this.passes.hiz)\n this.passes.lighting.setHiZPass(this.passes.hiz)\n this.passes.transparent.setHiZPass(this.passes.hiz)\n this.passes.shadow.setHiZPass(this.passes.hiz)\n timings.push({ name: 'wire:hiz', time: performance.now() - start })\n\n // Set up Shadow pass with noise texture for alpha hashing\n start = performance.now()\n this.passes.shadow.setNoise(this.noiseTexture, this.noiseSize, this.noiseAnimated)\n timings.push({ name: 'wire:shadow', time: performance.now() - start })\n\n // Set up AO pass\n start = performance.now()\n await this.passes.ao.setGBuffer(this.passes.gbuffer.getGBuffer())\n this.passes.ao.setNoise(this.noiseTexture, this.noiseSize, this.noiseAnimated)\n timings.push({ name: 'wire:ao', time: performance.now() - start })\n\n // Pass AO texture to lighting\n this.passes.lighting.setAOTexture(this.passes.ao.getOutputTexture())\n\n // Set up RenderPost pass with blue noise\n this.passes.renderPost.setNoise(this.noiseTexture, this.noiseSize, this.noiseAnimated)\n\n // SSGI passes are wired dynamically per frame (prev HDR, emissive, propagate buffer)\n\n // Wire up transparent pass (forward rendering for alpha-blended materials)\n this.passes.transparent.setGBuffer(this.passes.gbuffer.getGBuffer())\n this.passes.transparent.setShadowPass(this.passes.shadow)\n this.passes.transparent.setEnvironmentMap(environmentMap, this.environmentEncoding)\n this.passes.transparent.setNoise(this.noiseTexture, this.noiseSize, this.noiseAnimated)\n\n // Wire up particle pass (GPU particle system rendering)\n this.passes.particles.setParticleSystem(this.particleSystem)\n this.passes.particles.setGBuffer(this.passes.gbuffer.getGBuffer())\n this.passes.particles.setShadowPass(this.passes.shadow)\n this.passes.particles.setEnvironmentMap(environmentMap, this.environmentEncoding)\n this.passes.particles.setLightingPass(this.passes.lighting)\n\n // Wire up volumetric fog pass\n this.passes.volumetricFog.setGBuffer(this.passes.gbuffer.getGBuffer())\n this.passes.volumetricFog.setShadowPass(this.passes.shadow)\n this.passes.volumetricFog.setLightingPass(this.passes.lighting)\n this.passes.volumetricFog.setHiZPass(this.passes.hiz)\n\n this.passes.postProcess.setInputTexture(this.passes.lighting.getOutputTexture())\n this.passes.postProcess.setNoise(this.noiseTexture, this.noiseSize, this.noiseAnimated)\n\n // Wire up GUI canvas for overlay rendering\n if (this.engine?.guiCanvas) {\n this.passes.postProcess.setGuiCanvas(this.engine.guiCanvas)\n }\n\n // Invalidate occlusion culling after initialization to ensure warmup starts fresh\n // This prevents stale depth data from causing incorrect culling on first frames\n this.invalidateOcclusionCulling()\n }\n\n /**\n * Render a frame using the new entity/asset system\n *\n * @param {Object} context\n * @param {EntityManager} context.entityManager - Entity manager\n * @param {AssetManager} context.assetManager - Asset manager\n * @param {Camera} context.camera - Current camera\n * @param {Object} context.meshes - Legacy meshes (optional, for hybrid rendering)\n * @param {number} context.dt - Delta time\n */\n async renderEntities(context) {\n // Skip main render while probe capture is in progress\n // The main render modifies shared mesh instance counts which corrupts probe data\n if (this._isCapturingProbe) {\n return\n }\n\n const { entityManager, assetManager, camera, meshes, dt = 0 } = context\n const { canvas, stats } = this.engine\n\n // Register sprite entities for animation tracking (before update)\n entityManager.forEach((id, entity) => {\n if (entity.sprite && !entity._spriteRegistered) {\n this.spriteSystem.registerEntity(id, entity)\n entity._spriteRegistered = true\n }\n })\n\n // Update sprite animations\n this.spriteSystem.update(dt)\n\n // Process entities with particle emitters\n const particleEntities = entityManager.getParticles()\n for (const { id, entity } of particleEntities) {\n // Register emitter if not already registered\n if (entity.particles && !entity._emitterUID) {\n const emitterConfig = typeof entity.particles === 'object'\n ? { ...entity.particles, position: entity.position }\n : { position: entity.position }\n const emitter = this.particleSystem.addEmitter(emitterConfig)\n entity._emitterUID = emitter.uid\n }\n // Update emitter position from entity position\n if (entity._emitterUID) {\n const emitter = this.particleSystem.getEmitter(entity._emitterUID)\n if (emitter) {\n emitter.position = [...entity.position]\n }\n }\n }\n\n // Ensure camera matrices are up to date\n camera.aspect = canvas.width / canvas.height\n camera.screenSize[0] = canvas.width\n camera.screenSize[1] = canvas.height\n camera.jitterEnabled = false // Disable for shadow pass first\n camera.updateMatrix()\n camera.updateView()\n\n // Update frustum with screen dimensions for pixel size culling\n this.cullingSystem.updateFrustum(camera, canvas.width, canvas.height)\n\n // Prepare HiZ for occlusion tests (check camera movement, invalidate if needed)\n const hizPass = this.passes.hiz\n if (hizPass) {\n hizPass.prepareForOcclusionTests(camera)\n }\n\n // Cull entities\n const { visible, skinnedCount } = this.cullingSystem.cull(\n entityManager,\n assetManager,\n 'main'\n )\n\n this.stats.visibleEntities = visible.length\n this.stats.culledEntities = entityManager.count - visible.length\n\n // Group by model for instancing\n const groups = this.cullingSystem.groupByModel(visible)\n\n // Build instance batches\n const batches = this.instanceManager.buildBatches(groups, assetManager)\n\n // For shadow pass, we need entities within shadow range, not just camera-visible ones\n // Apply pixel size culling and distance culling based on cascade coverage\n // NEW: Use shadow bounding spheres for frustum/occlusion culling (only for main light)\n const allEntities = []\n const shadowConfig = this.cullingSystem.config.shadow\n\n // Get max shadow distance from culling.shadow.maxDistance setting\n const maxShadowDistance = shadowConfig?.maxDistance ?? 100\n\n // Check if main light is enabled - shadow bounding sphere culling only applies to main light\n const mainLight = this.engine?.settings?.mainLight\n const mainLightEnabled = mainLight?.enabled !== false\n\n // Get light direction for shadow bounding sphere calculation (only used when main light enabled)\n const lightDir = vec3.fromValues(\n mainLight?.direction?.[0] ?? -1,\n mainLight?.direction?.[1] ?? 1,\n mainLight?.direction?.[2] ?? -0.5\n )\n vec3.normalize(lightDir, lightDir)\n\n // Ground level for shadow projection (default 0, or from planarReflection settings)\n const groundLevel = this.engine?.settings?.planarReflection?.groundLevel ?? 0\n\n // Shadow culling settings - only apply shadow bounding sphere culling when main light is enabled\n // Spotlights have their own frustum culling in ShadowPass\n const shadowCullingEnabled = mainLightEnabled && shadowConfig?.frustum !== false\n const shadowHiZEnabled = mainLightEnabled && shadowConfig?.hiZ !== false && this.passes.hiz\n\n // Track shadow culling stats\n let shadowFrustumCulled = 0\n let shadowHiZCulled = 0\n let shadowDistanceCulled = 0\n let shadowPixelCulled = 0\n\n // Collect sprite-only entities (entities with .sprite but no .model)\n const spriteOnlyEntities = []\n entityManager.forEach((id, entity) => {\n if (!entity._visible) return\n if (entity.sprite && !entity.model) {\n // Calculate bounding sphere for sprite entity based on scale\n const scale = entity.scale || [1, 1, 1]\n const radius = Math.max(scale[0], scale[1]) * 0.5\n entity._bsphere = {\n center: [...entity.position],\n radius: radius\n }\n spriteOnlyEntities.push({ id, entity })\n }\n })\n\n entityManager.forEach((id, entity) => {\n // Skip invisible entities (same check as main cull)\n if (!entity._visible) return\n\n if (entity.model) {\n // Update bsphere from asset for shadow culling\n // Note: For skinned models, bsphere is pre-computed as combined sphere of all submeshes\n const asset = assetManager.get(entity.model)\n if (asset?.bsphere) {\n entity._bsphere = transformBoundingSphere(asset.bsphere, entity._matrix)\n }\n\n if (entity._bsphere) {\n // Calculate shadow bounding sphere only when main light is enabled\n // For spotlights, we use the object's regular bsphere (spotlight culling is in ShadowPass)\n if (mainLightEnabled) {\n entity._shadowBsphere = calculateShadowBoundingSphere(\n entity._bsphere,\n lightDir,\n groundLevel\n )\n } else {\n // When main light is off, use regular bsphere for distance/pixel culling\n entity._shadowBsphere = entity._bsphere\n }\n\n // Use shadow bounding sphere for distance culling\n // This ensures objects whose shadows are visible are included\n const distance = this.cullingSystem.frustum.getDistance(entity._shadowBsphere)\n if (distance - entity._shadowBsphere.radius > maxShadowDistance) {\n shadowDistanceCulled++\n return // Shadow too far to be visible\n }\n\n // Skip if projected size is too small (shadow won't be visible)\n // Use shadow bounding sphere for pixel size calculation\n if (shadowConfig.minPixelSize > 0) {\n const projectedSize = this.cullingSystem.frustum.getProjectedSize(entity._shadowBsphere, distance)\n if (projectedSize < shadowConfig.minPixelSize) {\n shadowPixelCulled++\n return // Shadow too small to see\n }\n }\n\n // Frustum cull using shadow bounding sphere (only for main light)\n // Spotlights have their own frustum culling in ShadowPass._buildFilteredInstances\n if (shadowCullingEnabled) {\n if (!this.cullingSystem.frustum.testSpherePlanes(entity._shadowBsphere)) {\n shadowFrustumCulled++\n return // Shadow not in camera frustum\n }\n }\n\n // HiZ occlusion cull using shadow bounding sphere (only for main light)\n // Spotlights have their own distance/frustum culling in ShadowPass\n if (shadowHiZEnabled && this.cullingSystem.frustum.hiZValid) {\n const occluded = this.passes.hiz.testSphereOcclusion(\n entity._shadowBsphere,\n this.cullingSystem.frustum.viewProj\n )\n if (occluded) {\n shadowHiZCulled++\n return // Shadow occluded by depth buffer\n }\n }\n }\n\n allEntities.push({ id, entity })\n }\n })\n\n // Store shadow culling stats\n stats.shadowFrustumCulled = shadowFrustumCulled\n stats.shadowHiZCulled = shadowHiZCulled\n stats.shadowDistanceCulled = shadowDistanceCulled\n stats.shadowPixelCulled = shadowPixelCulled\n\n const allGroups = this.cullingSystem.groupByModel(allEntities)\n\n // Process lights BEFORE shadow pass so shadow can use processed light data\n // Pass camera for frustum culling and distance ordering of point lights\n const rawLights = entityManager.getLights()\n this.passes.lighting.updateLightsFromEntities(rawLights, camera)\n\n // Update meshes for shadow pass (includes entities within shadow range, even if outside main frustum)\n this._updateMeshInstancesFromEntities(allGroups, assetManager, meshes, true, camera, 0, null)\n\n // Execute shadow pass FIRST with shadow-culled data\n const passContext = {\n camera,\n meshes,\n dt,\n lights: this.passes.lighting.lights, // Use processed lights with lightType\n mainLight: this.engine?.settings?.mainLight // Main directional light settings\n }\n\n // Pass 1: Shadow (uses shadow-culled entities - can include off-screen objects)\n // Always execute - ShadowPass internally skips cascades when main light is off,\n // but still renders spotlight shadows\n await this.passes.shadow.execute(passContext)\n\n // Pass 2: Reflection (updates active probes based on camera position)\n await this.passes.reflection.execute(passContext)\n\n // Pass 2b: Planar Reflection (render scene from mirrored camera)\n // Only execute when enabled - skipped entirely when off\n if (this.passes.planarReflection && this.engine?.settings?.planarReflection?.enabled) {\n // Cull entities specifically for planar reflection (distance, skinned limit, pixel size)\n const { visible: planarVisible } = this.cullingSystem.cull(\n entityManager,\n assetManager,\n 'planarReflection'\n )\n const planarGroups = this.cullingSystem.groupByModel(planarVisible)\n\n // Filter out horizontal sprites from planar reflection (they're flat on ground, shouldn't reflect)\n const planarSpriteEntities = spriteOnlyEntities.filter(item => {\n const pivot = item.entity.pivot || item.entity.sprite?.pivot\n return pivot !== 'horizontal'\n })\n\n // Update meshes with planar reflection culled entities (including sprites)\n this._updateMeshInstancesFromEntities(planarGroups, assetManager, meshes, true, camera, dt, entityManager, planarSpriteEntities)\n\n // Set distance fade for planar reflection (prevents object popping at maxDistance)\n const planarCulling = this.engine?.settings?.culling?.planarReflection\n const planarFadeMaxDist = planarCulling?.maxDistance ?? 50\n const planarFadeStart = planarCulling?.fadeStart ?? 0.7\n if (this.passes.planarReflection.gbufferPass) {\n this.passes.planarReflection.gbufferPass.distanceFadeEnd = planarFadeMaxDist\n this.passes.planarReflection.gbufferPass.distanceFadeStart = planarFadeMaxDist * planarFadeStart\n }\n\n await this.passes.planarReflection.execute(passContext)\n } else {\n // Zero stats when disabled\n stats.planarDrawCalls = 0\n stats.planarTriangles = 0\n }\n\n // NOW update meshes for main render - overwrites shadow data with main-culled instances\n // Pass sprite-only entities for sprite rendering\n this._updateMeshInstancesFromEntities(groups, assetManager, meshes, true, camera, dt, entityManager, spriteOnlyEntities)\n\n // Enable TAA jitter for main render (after shadow, before GBuffer)\n // Updates projection matrix with sub-pixel offset for temporal anti-aliasing\n camera.jitterEnabled = this.engine?.settings?.rendering?.jitter ?? true\n camera.updateView() // Recompute proj with jitter\n\n // Set distance fade for main render (prevents object popping at maxDistance)\n const mainCulling = this.engine?.settings?.culling?.main\n const mainMaxDist = mainCulling?.maxDistance ?? 1000\n const mainFadeStart = mainCulling?.fadeStart ?? 0.9\n this.passes.gbuffer.distanceFadeEnd = mainMaxDist\n this.passes.gbuffer.distanceFadeStart = mainMaxDist * mainFadeStart\n\n // Pass 3: GBuffer (uses main-culled entities, outputs velocity for motion vectors)\n passContext.historyManager = this.historyManager\n await this.passes.gbuffer.execute(passContext)\n\n // Pass 3b: HiZ reduction (for next frame's occlusion culling)\n // Must run after GBuffer to have depth data, before next frame's culling\n if (this.passes.hiz) {\n this.passes.hiz.setDepthTexture(this.passes.gbuffer.getGBuffer()?.depth)\n await this.passes.hiz.execute(passContext)\n }\n\n // Pass 4: AO (screen-space ambient occlusion)\n await this.passes.ao.execute(passContext)\n\n // Pass 5: Lighting\n await this.passes.lighting.execute(passContext)\n\n // Copy lighting and normal to history buffers for temporal effects\n const { device } = this.engine\n const commandEncoder = device.createCommandEncoder({ label: 'historyCommandEncoder' })\n this.historyManager.copyLightingToHistory(commandEncoder, this.passes.lighting.getOutputTexture())\n this.historyManager.copyNormalToHistory(commandEncoder, this.passes.gbuffer.getGBuffer()?.normal)\n device.queue.submit([commandEncoder.finish()])\n\n const gbuffer = this.passes.gbuffer.getGBuffer()\n const lightingOutput = this.passes.lighting.getOutputTexture()\n\n // Pass 7: SSGITile (compute shader - accumulate + propagate light between tiles)\n const ssgiEnabled = this.engine?.settings?.ssgi?.enabled\n const prevData = this.historyManager.getPrevious()\n if (this.passes.ssgiTile && ssgiEnabled && prevData.hasValidHistory) {\n // Use previous frame HDR and emissive for tile accumulation\n this.passes.ssgiTile.setPrevHDRTexture(prevData.color)\n this.passes.ssgiTile.setEmissiveTexture(gbuffer.emission)\n await this.passes.ssgiTile.execute(passContext)\n }\n\n // Pass 8: SSGI (screen-space global illumination - sample from propagated tiles)\n if (this.passes.ssgi && ssgiEnabled && prevData.hasValidHistory) {\n // Pass propagate buffer to SSGI for sampling\n const tileInfo = this.passes.ssgiTile.getTileInfo()\n this.passes.ssgi.setPropagateBuffer(\n this.passes.ssgiTile.getPropagateBuffer(),\n tileInfo.tileCountX,\n tileInfo.tileCountY\n )\n this.passes.ssgi.setGBuffer(gbuffer)\n await this.passes.ssgi.execute({\n camera,\n gbuffer,\n })\n }\n\n // Pass 9b: Ambient Capture (6-directional sky-aware ambient)\n // Captures sky visibility in 6 directions for ambient lighting\n if (this.passes.ambientCapture && this.engine?.settings?.ambientCapture?.enabled) {\n await this.passes.ambientCapture.execute(passContext)\n }\n\n // Pass 10: RenderPost (combine SSGI/PlanarReflection/AmbientCapture with lighting)\n let hdrSource = lightingOutput\n if (this.passes.renderPost) {\n const planarEnabled = this.engine?.settings?.planarReflection?.enabled\n const ambientCaptureEnabled = this.engine?.settings?.ambientCapture?.enabled\n await this.passes.renderPost.execute({\n lightingOutput,\n gbuffer,\n camera,\n ssgi: this.passes.ssgi?.getSSGITexture(),\n // Pass null when disabled - renderPost uses black placeholder\n planarReflection: planarEnabled ? this.passes.planarReflection?.getReflectionTexture() : null,\n })\n\n // Use RenderPost output if any screen-space effect is enabled\n if (ssgiEnabled || planarEnabled || ambientCaptureEnabled) {\n hdrSource = this.passes.renderPost.getOutputTexture()\n }\n }\n\n // Pass 11: Transparent (forward rendering for alpha-blended materials)\n if (this.passes.transparent) {\n this.passes.transparent.setOutputTexture(hdrSource)\n // Set distance fade for transparent pass (same as main render)\n const transparentCulling = this.engine?.settings?.culling?.main\n const transparentMaxDist = transparentCulling?.maxDistance ?? 1000\n const transparentFadeStart = transparentCulling?.fadeStart ?? 0.9\n this.passes.transparent.distanceFadeEnd = transparentMaxDist\n this.passes.transparent.distanceFadeStart = transparentMaxDist * transparentFadeStart\n await this.passes.transparent.execute(passContext)\n }\n\n // Pass 12: Fog (distance-based fog with height fade)\n // Applied BEFORE particles - scene fog uses scene depth\n // Particles will apply their own fog based on particle position\n const fogEnabled = this.engine?.settings?.environment?.fog?.enabled\n if (this.passes.fog && fogEnabled) {\n this.passes.fog.setInputTexture(hdrSource)\n this.passes.fog.setGBuffer(gbuffer)\n await this.passes.fog.execute(passContext)\n const fogOutput = this.passes.fog.getOutputTexture()\n if (fogOutput && fogOutput !== hdrSource) {\n hdrSource = fogOutput\n }\n }\n\n // Pass 12b: Particles (GPU particle system)\n // Rendered AFTER simple fog, BEFORE volumetric fog\n // Particles apply their own fog based on particle world position\n if (this.passes.particles && this.particleSystem.getActiveEmitters().length > 0) {\n this.passes.particles.setOutputTexture(hdrSource)\n await this.passes.particles.execute(passContext)\n }\n\n // Pass 13: Volumetric Fog (light scattering through particles)\n // Applied last - additive light scattering on top of everything\n const volumetricFogEnabled = this.engine?.settings?.volumetricFog?.enabled\n if (this.passes.volumetricFog && volumetricFogEnabled) {\n this.passes.volumetricFog.setInputTexture(hdrSource)\n this.passes.volumetricFog.setGBuffer(gbuffer)\n await this.passes.volumetricFog.execute(passContext)\n const volFogOutput = this.passes.volumetricFog.getOutputTexture()\n if (volFogOutput && volFogOutput !== hdrSource) {\n hdrSource = volFogOutput\n }\n }\n\n // Pass 14: Bloom (HDR bright extraction + blur)\n // Runs after transparent so glass/water highlights contribute to bloom\n const bloomEnabled = this.engine?.settings?.bloom?.enabled\n if (this.passes.bloom && bloomEnabled) {\n this.passes.bloom.setInputTexture(hdrSource)\n await this.passes.bloom.execute(passContext)\n this.passes.postProcess.setBloomTexture(this.passes.bloom.getOutputTexture())\n } else {\n this.passes.postProcess.setBloomTexture(null)\n }\n\n // Pass 13: PostProcess (bloom composite + tone mapping)\n // When CRT is enabled, outputs to intermediate texture instead of canvas\n this.passes.postProcess.setInputTexture(hdrSource)\n await this.passes.postProcess.execute(passContext)\n\n // Pass 14: CRT effect (optional - outputs to canvas)\n const crtEnabled = this.engine?.settings?.crt?.enabled\n const crtUpscaleEnabled = this.engine?.settings?.crt?.upscaleEnabled\n if (crtEnabled || crtUpscaleEnabled) {\n // Wire CRT pass to receive PostProcess intermediate output\n const postProcessOutput = this.passes.postProcess.getOutputTexture()\n if (postProcessOutput) {\n this.passes.crt.setInputTexture(postProcessOutput)\n this.passes.crt.setRenderSize(\n this.passes.gbuffer.getGBuffer()?.depth?.width || canvas.width,\n this.passes.gbuffer.getGBuffer()?.depth?.height || canvas.height\n )\n await this.passes.crt.execute(passContext)\n }\n }\n\n // Swap history buffers and save camera matrices for next frame\n this.historyManager.swap(camera)\n\n // Store render context for probe capture (entityManager/assetManager for building fresh batches)\n this._lastRenderContext = {\n meshes,\n entityManager,\n assetManager\n }\n\n // Update stats\n this.stats.drawCalls = stats.drawCalls\n this.stats.triangles = stats.triangles\n }\n\n /**\n * Update legacy mesh instances from entity transforms\n * This bridges the entity system with existing mesh rendering\n *\n * @param {Map} groups - Entity groups by model ID\n * @param {AssetManager} assetManager - Asset manager\n * @param {Object} meshes - Meshes dictionary\n * @param {boolean} resetAll - Reset all mesh instance counts\n * @param {Camera} camera - Camera for proximity calculation (null = no individual skins)\n * @param {number} dt - Delta time for animation updates\n * @param {EntityManager} entityManager - Entity manager for animation state\n */\n _updateMeshInstancesFromEntities(groups, assetManager, meshes, resetAll = false, camera = null, dt = 0, entityManager = null, spriteOnlyEntities = []) {\n if (!meshes) return\n\n const { device } = this.engine\n\n // Reset ALL mesh instance counts first if requested (for frustum-culled passes)\n // Skip meshes marked as static (manually placed, not entity-managed)\n if (resetAll) {\n for (const name in meshes) {\n const mesh = meshes[name]\n if (mesh.geometry && !mesh.static) {\n mesh.geometry.instanceCount = 0\n mesh.geometry._instanceDataDirty = true\n }\n }\n }\n\n // Track which meshes we've updated\n const updatedMeshes = new Set()\n\n // Get camera position for proximity check\n const cameraPos = camera ? [camera.position[0], camera.position[1], camera.position[2]] : null\n const individualDistSq = this.individualRenderDistance * this.individualRenderDistance\n\n // Collect sprite entities and group by material key for batching\n const spriteGroups = new Map() // materialKey -> { entities, spriteInfo }\n\n // Separate entities by type and proximity\n const nonSkinnedGroups = new Map()\n const skinnedIndividualEntities = [] // Close entities needing individual skins\n const skinnedInstancedGroups = new Map() // Far entities for phase-grouped instancing\n\n for (const [modelId, entities] of groups) {\n // Check for sprite entities (entities with .sprite property but no model asset)\n // These are handled separately with billboard geometry\n for (const item of entities) {\n const entity = item.entity\n if (entity.sprite) {\n // Parse sprite and compute UV transform\n const spriteInfo = this.spriteSystem.parseSprite(entity.sprite)\n if (spriteInfo) {\n // Compute UV transform from frame (uses animated _uvTransform if available)\n const instanceData = this.spriteSystem.getSpriteInstanceData(entity)\n entity._uvTransform = instanceData.uvTransform\n\n // Group sprites by material key for batching\n const pivot = entity.pivot || 'center'\n const roughness = entity.roughness ?? 0.7\n const materialKey = `sprite:${spriteInfo.url}:${pivot}:r${roughness.toFixed(2)}`\n\n if (!spriteGroups.has(materialKey)) {\n spriteGroups.set(materialKey, {\n entities: [],\n spriteInfo,\n pivot,\n roughness\n })\n }\n spriteGroups.get(materialKey).entities.push(item)\n }\n }\n }\n\n const asset = assetManager.get(modelId)\n\n // Handle parent GLTF paths (expand to all submeshes)\n // If entity.model is \"model.glb\" instead of \"model.glb|meshName\", expand to all meshes\n if (asset?.meshNames && !asset.mesh) {\n // This is a parent GLTF asset - expand to all submeshes\n // Each submesh will share the same entity transform/animation/phase\n for (const meshName of asset.meshNames) {\n const submeshId = assetManager.createModelId(modelId, meshName)\n const submeshAsset = assetManager.get(submeshId)\n if (!submeshAsset?.mesh) continue\n\n // Add this submesh to the appropriate group\n if (submeshAsset.hasSkin && submeshAsset.skin) {\n // Skinned submesh - process each entity for this submesh\n // Expanded submeshes ALWAYS use phase-grouped instancing (even when close)\n // Individual rendering doesn't support multi-submesh expansion\n for (const item of entities) {\n const entity = item.entity\n\n const animation = entity.animation || 'default'\n const phase = entity.phase || 0\n const quantizedPhase = Math.floor(phase / 0.05) * 0.05\n const key = `${submeshId}|${animation}|${quantizedPhase.toFixed(2)}`\n\n if (!skinnedInstancedGroups.has(key)) {\n skinnedInstancedGroups.set(key, {\n modelId: submeshId, animation, phase: quantizedPhase, asset: submeshAsset, entities: []\n })\n }\n skinnedInstancedGroups.get(key).entities.push(item)\n }\n } else {\n // Non-skinned submesh - add to non-skinned groups\n if (!nonSkinnedGroups.has(submeshId)) {\n nonSkinnedGroups.set(submeshId, { asset: submeshAsset, entities: [] })\n }\n for (const item of entities) {\n nonSkinnedGroups.get(submeshId).entities.push(item)\n }\n }\n }\n continue // Skip normal processing, we've handled expansion\n }\n\n if (!asset?.mesh) continue\n\n if (asset.hasSkin && asset.skin) {\n // For skinned meshes, check proximity for each entity\n for (const item of entities) {\n const entity = item.entity\n const entityId = item.id\n\n // Calculate distance to camera\n let useIndividual = false\n if (cameraPos && entity._bsphere) {\n const dx = entity._bsphere.center[0] - cameraPos[0]\n const dy = entity._bsphere.center[1] - cameraPos[1]\n const dz = entity._bsphere.center[2] - cameraPos[2]\n const distSq = dx * dx + dy * dy + dz * dz\n useIndividual = distSq < individualDistSq\n }\n\n // Check if individual mesh is ready and pipeline is stable\n // If not, keep in instanced group to avoid flash during transition\n let addToIndividualList = false\n if (useIndividual) {\n const cached = this._individualSkinCache.get(entityId)\n if (cached?.mesh && this.passes.gbuffer) {\n // Only switch if pipeline is stable\n if (!this.passes.gbuffer.isPipelineStable(cached.mesh)) {\n addToIndividualList = true // Create/warm pipeline\n useIndividual = false // But keep in instanced until ready\n }\n } else if (!cached) {\n // No cache yet - will be created, but keep in instanced for this frame\n addToIndividualList = true // Create the mesh/pipeline\n useIndividual = false // Keep in instanced for this frame\n }\n }\n\n if (useIndividual || addToIndividualList) {\n skinnedIndividualEntities.push({ id: entityId, entity, asset, modelId })\n }\n if (!useIndividual) {\n // Group by animation and phase for instancing\n const animation = entity.animation || 'default'\n const phase = entity.phase || 0\n const quantizedPhase = Math.floor(phase / 0.05) * 0.05\n const key = `${modelId}|${animation}|${quantizedPhase.toFixed(2)}`\n\n if (!skinnedInstancedGroups.has(key)) {\n skinnedInstancedGroups.set(key, {\n modelId, animation, phase: quantizedPhase, asset, entities: []\n })\n }\n skinnedInstancedGroups.get(key).entities.push(item)\n }\n }\n } else {\n nonSkinnedGroups.set(modelId, { asset, entities })\n }\n }\n\n // Process non-skinned meshes (simple path)\n for (const [modelId, { asset, entities }] of nonSkinnedGroups) {\n const mesh = asset.mesh\n const geometry = mesh.geometry\n\n let meshName = null\n for (const name in meshes) {\n if (meshes[name] === mesh || meshes[name].geometry === geometry) {\n meshName = name\n break\n }\n }\n\n if (!meshName) {\n // Use sanitized modelId as base name, but ensure uniqueness\n let baseName = modelId.replace(/[^a-zA-Z0-9]/g, '_')\n meshName = baseName\n // If name already exists with a DIFFERENT mesh, make it unique\n let counter = 1\n while (meshes[meshName] && meshes[meshName] !== mesh && meshes[meshName].geometry !== geometry) {\n meshName = `${baseName}_${counter++}`\n }\n meshes[meshName] = mesh\n }\n\n geometry.instanceCount = 0\n\n for (const item of entities) {\n const entity = item.entity\n const idx = geometry.instanceCount\n\n if (idx >= geometry.maxInstances) {\n geometry.growInstanceBuffer(entities.length)\n }\n\n geometry.instanceCount++\n const base = idx * 28\n geometry.instanceData.set(entity._matrix, base)\n geometry.instanceData[base + 16] = entity._bsphere.center[0]\n geometry.instanceData[base + 17] = entity._bsphere.center[1]\n geometry.instanceData[base + 18] = entity._bsphere.center[2]\n // Negative radius signals shader to skip pixel/position rounding\n geometry.instanceData[base + 19] = entity.noRounding\n ? -Math.max(entity._bsphere.radius, 1)\n : entity._bsphere.radius\n\n // uvTransform: [offsetX, offsetY, scaleX, scaleY] - default full texture\n const uvTransform = entity._uvTransform || [0, 0, 1, 1]\n geometry.instanceData[base + 20] = uvTransform[0]\n geometry.instanceData[base + 21] = uvTransform[1]\n geometry.instanceData[base + 22] = uvTransform[2]\n geometry.instanceData[base + 23] = uvTransform[3]\n\n // color: [r, g, b, a] - default white\n const color = entity.color || [1, 1, 1, 1]\n geometry.instanceData[base + 24] = color[0]\n geometry.instanceData[base + 25] = color[1]\n geometry.instanceData[base + 26] = color[2]\n geometry.instanceData[base + 27] = color[3]\n }\n\n geometry._instanceDataDirty = true\n updatedMeshes.add(meshName)\n }\n\n // Process individual skinned entities (close to camera, with blending support)\n // Animation time can be scaled by settings.animation.speed (default 1.0)\n const animationSpeed = this.engine?.settings?.animation?.speed ?? 1.0\n const globalTime = (performance.now() / 1000) * animationSpeed\n\n for (const { id: entityId, entity, asset, modelId } of skinnedIndividualEntities) {\n const entityAnimation = entity.animation || 'default'\n const entityPhase = entity.phase || 0\n\n // Get or create individual skin for this entity\n let cached = this._individualSkinCache.get(entityId)\n\n if (!cached) {\n // Create individual skin with local transforms for blending\n const individualSkin = asset.skin.cloneForIndividual()\n\n // Create geometry wrapper for single instance\n const originalGeom = asset.mesh.geometry\n const geomUid = `individual_${entityId}_${Date.now()}`\n\n const individualGeometry = {\n uid: geomUid,\n vertexBuffer: originalGeom.vertexBuffer,\n indexBuffer: originalGeom.indexBuffer,\n vertexBufferLayout: originalGeom.vertexBufferLayout,\n instanceBufferLayout: originalGeom.instanceBufferLayout,\n vertexCount: originalGeom.vertexCount,\n indexArray: originalGeom.indexArray,\n attributes: originalGeom.attributes,\n maxInstances: 1,\n instanceCount: 1,\n instanceData: new Float32Array(28), // 28 floats: matrix(16) + posRadius(4) + uvTransform(4) + color(4)\n instanceBuffer: device.createBuffer({\n size: 112, // 28 floats * 4 bytes\n usage: GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_DST,\n }),\n _instanceDataDirty: true,\n writeInstanceBuffer() {\n device.queue.writeBuffer(this.instanceBuffer, 0, this.instanceData)\n },\n update() {\n if (this._instanceDataDirty) {\n this.writeInstanceBuffer()\n this._instanceDataDirty = false\n }\n }\n }\n\n const individualMesh = {\n geometry: individualGeometry,\n material: asset.mesh.material,\n skin: individualSkin,\n hasSkin: true,\n uid: `individual_${entityId}`,\n // Use asset's combined bsphere for culling (all skinned submeshes share one sphere)\n combinedBsphere: asset.bsphere || null\n }\n\n cached = {\n skin: individualSkin,\n mesh: individualMesh,\n geometry: individualGeometry,\n lastAnimation: entityAnimation,\n // Blending state\n blendStartTime: 0,\n blendFromAnim: null,\n blendFromPhaseOffset: 0\n }\n this._individualSkinCache.set(entityId, cached)\n\n // Initialize animation\n individualSkin.currentAnimation = entityAnimation\n }\n\n const { skin: individualSkin, mesh: individualMesh, geometry: individualGeometry } = cached\n\n // Get animation info\n const anim = individualSkin.animations[entityAnimation]\n if (!anim) continue\n\n // Calculate time EXACTLY the same way as far mode\n // This ensures no glitch when switching between modes\n const phaseOffset = entityPhase * anim.duration\n const baseTime = globalTime + phaseOffset\n\n // Check if animation changed - start blend\n if (dt > 0 && cached.lastAnimation !== entityAnimation) {\n // Animation changed! Start blending\n cached.blendFromAnim = cached.lastAnimation\n cached.blendFromPhaseOffset = entityPhase * (individualSkin.animations[cached.lastAnimation]?.duration || anim.duration)\n cached.blendStartTime = globalTime\n cached.lastAnimation = entityAnimation\n individualSkin.currentAnimation = entityAnimation\n individualSkin.isBlending = true\n individualSkin.blendDuration = 0.3\n }\n\n // Handle blending with global time (not dt-based)\n if (individualSkin.isBlending && cached.blendFromAnim) {\n const blendElapsed = globalTime - cached.blendStartTime\n const blendWeight = Math.min(blendElapsed / individualSkin.blendDuration, 1.0)\n\n if (blendWeight >= 1.0) {\n // Blend complete\n individualSkin.isBlending = false\n cached.blendFromAnim = null\n individualSkin.time = baseTime\n individualSkin.update(0) // Apply current animation only\n } else {\n // Blending - manually apply both animations\n const fromAnim = individualSkin.animations[cached.blendFromAnim]\n const fromTime = globalTime + cached.blendFromPhaseOffset\n\n // Set up blend state\n individualSkin.blendFromAnimation = cached.blendFromAnim\n individualSkin.blendFromTime = fromTime\n individualSkin.blendWeight = blendWeight\n individualSkin.time = baseTime\n\n // Update applies blended animation\n individualSkin.update(0)\n }\n } else {\n // No blending - just set time and update\n individualSkin.time = baseTime\n individualSkin.update(0)\n }\n\n // Update instance data\n individualGeometry.instanceCount = 1\n individualGeometry.instanceData.set(entity._matrix, 0)\n individualGeometry.instanceData[16] = entity._bsphere.center[0]\n individualGeometry.instanceData[17] = entity._bsphere.center[1]\n individualGeometry.instanceData[18] = entity._bsphere.center[2]\n individualGeometry.instanceData[19] = entity._bsphere.radius\n // uvTransform: default full texture\n const uvTransform = entity._uvTransform || [0, 0, 1, 1]\n individualGeometry.instanceData[20] = uvTransform[0]\n individualGeometry.instanceData[21] = uvTransform[1]\n individualGeometry.instanceData[22] = uvTransform[2]\n individualGeometry.instanceData[23] = uvTransform[3]\n // color: default white\n const color = entity.color || [1, 1, 1, 1]\n individualGeometry.instanceData[24] = color[0]\n individualGeometry.instanceData[25] = color[1]\n individualGeometry.instanceData[26] = color[2]\n individualGeometry.instanceData[27] = color[3]\n individualGeometry._instanceDataDirty = true\n\n // Register in meshes dict\n const meshName = `individual_${entityId}`\n meshes[meshName] = individualMesh\n updatedMeshes.add(meshName)\n }\n\n // Process instanced skinned entities (far from camera, phase-grouped)\n for (const [key, group] of skinnedInstancedGroups) {\n const { modelId, animation, phase, asset, entities } = group\n\n // Get or create cloned skin and geometry wrapper for this phase group\n let cached = this._skinnedPhaseCache.get(key)\n if (!cached) {\n const clonedSkin = asset.skin.clone()\n clonedSkin.currentAnimation = animation\n\n const originalGeom = asset.mesh.geometry\n const phaseGeomUid = `phase_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`\n\n const phaseGeometry = {\n uid: phaseGeomUid,\n vertexBuffer: originalGeom.vertexBuffer,\n indexBuffer: originalGeom.indexBuffer,\n vertexBufferLayout: originalGeom.vertexBufferLayout,\n instanceBufferLayout: originalGeom.instanceBufferLayout,\n vertexCount: originalGeom.vertexCount,\n indexArray: originalGeom.indexArray,\n attributes: originalGeom.attributes,\n maxInstances: 64,\n instanceCount: 0,\n instanceData: new Float32Array(28 * 64), // 28 floats per instance\n instanceBuffer: device.createBuffer({\n size: 112 * 64, // 112 bytes per instance\n usage: GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_DST,\n }),\n _instanceDataDirty: true,\n growInstanceBuffer(minCapacity) {\n let newMax = this.maxInstances * 2\n while (newMax < minCapacity) newMax *= 2\n const newBuffer = device.createBuffer({\n size: 112 * newMax, // 112 bytes per instance\n usage: GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_DST,\n })\n const newData = new Float32Array(28 * newMax) // 28 floats per instance\n newData.set(this.instanceData)\n this.instanceBuffer.destroy()\n this.instanceBuffer = newBuffer\n this.instanceData = newData\n this.maxInstances = newMax\n this._instanceDataDirty = true\n },\n writeInstanceBuffer() {\n device.queue.writeBuffer(this.instanceBuffer, 0, this.instanceData)\n },\n update() {\n if (this._instanceDataDirty) {\n this.writeInstanceBuffer()\n this._instanceDataDirty = false\n }\n }\n }\n\n const phaseMesh = {\n geometry: phaseGeometry,\n material: asset.mesh.material,\n skin: clonedSkin,\n hasSkin: true,\n uid: asset.mesh.uid + '_phase_' + key.replace(/[^a-zA-Z0-9]/g, '_'),\n // Use asset's combined bsphere for culling (all skinned submeshes share one sphere)\n combinedBsphere: asset.bsphere || null\n }\n\n cached = { skin: clonedSkin, mesh: phaseMesh, geometry: phaseGeometry }\n this._skinnedPhaseCache.set(key, cached)\n }\n\n const { skin: clonedSkin, mesh: phaseMesh, geometry: phaseGeometry } = cached\n\n const meshName = `skinned_${key.replace(/[^a-zA-Z0-9]/g, '_')}`\n meshes[meshName] = phaseMesh\n\n const anim = clonedSkin.animations[animation]\n if (!anim) continue\n\n const phaseOffset = phase * anim.duration\n clonedSkin.currentAnimation = animation\n clonedSkin.updateAtTime(globalTime + phaseOffset)\n\n phaseGeometry.instanceCount = 0\n\n for (const item of entities) {\n const entity = item.entity\n const idx = phaseGeometry.instanceCount\n\n if (idx >= phaseGeometry.maxInstances) {\n phaseGeometry.growInstanceBuffer(entities.length)\n }\n\n phaseGeometry.instanceCount++\n const base = idx * 28\n phaseGeometry.instanceData.set(entity._matrix, base)\n phaseGeometry.instanceData[base + 16] = entity._bsphere.center[0]\n phaseGeometry.instanceData[base + 17] = entity._bsphere.center[1]\n phaseGeometry.instanceData[base + 18] = entity._bsphere.center[2]\n phaseGeometry.instanceData[base + 19] = entity._bsphere.radius\n // uvTransform: default full texture\n const uvTransform = entity._uvTransform || [0, 0, 1, 1]\n phaseGeometry.instanceData[base + 20] = uvTransform[0]\n phaseGeometry.instanceData[base + 21] = uvTransform[1]\n phaseGeometry.instanceData[base + 22] = uvTransform[2]\n phaseGeometry.instanceData[base + 23] = uvTransform[3]\n // color: default white\n const color = entity.color || [1, 1, 1, 1]\n phaseGeometry.instanceData[base + 24] = color[0]\n phaseGeometry.instanceData[base + 25] = color[1]\n phaseGeometry.instanceData[base + 26] = color[2]\n phaseGeometry.instanceData[base + 27] = color[3]\n }\n\n phaseGeometry._instanceDataDirty = true\n updatedMeshes.add(meshName)\n }\n\n // Add sprite-only entities (entities with .sprite but no .model) to spriteGroups\n for (const item of spriteOnlyEntities) {\n const entity = item.entity\n const spriteInfo = this.spriteSystem.parseSprite(entity.sprite)\n if (!spriteInfo) continue\n\n // Compute UV transform for sprite-only entities\n const instanceData = this.spriteSystem.getSpriteInstanceData(entity)\n entity._uvTransform = instanceData.uvTransform\n\n // Group by material key (same format as model+sprite entities)\n const pivot = entity.pivot || 'center'\n const roughness = entity.roughness ?? 0.7\n const materialKey = `sprite:${spriteInfo.url}:${pivot}:r${roughness.toFixed(2)}`\n\n if (!spriteGroups.has(materialKey)) {\n spriteGroups.set(materialKey, {\n entities: [],\n spriteInfo,\n pivot,\n roughness\n })\n }\n spriteGroups.get(materialKey).entities.push(item)\n }\n\n // Process sprite entities (billboard quads)\n // This is done synchronously - sprite assets are cached after first load\n for (const [materialKey, group] of spriteGroups) {\n const { entities, spriteInfo, pivot, roughness } = group\n\n // Get or create sprite mesh (async on first call, cached thereafter)\n // Note: We use a simple approach - if the asset isn't loaded yet, skip this frame\n const meshName = `sprite_${materialKey.replace(/[^a-zA-Z0-9]/g, '_')}`\n\n let spriteMesh = meshes[meshName]\n if (!spriteMesh) {\n // Check if material is ready\n const material = this.spriteSystem._materialCache.get(materialKey)\n\n if (!material) {\n // Material not loaded yet - trigger async load and skip this frame\n this.spriteSystem.getSpriteMaterial(spriteInfo.url, roughness, pivot)\n continue\n }\n\n // Create a NEW geometry for each sprite batch (not shared)\n // This is needed because each batch has its own instance data\n const geometry = Geometry.billboardQuad(this.engine, pivot)\n\n // Create mesh with sprite geometry and material\n spriteMesh = {\n geometry: geometry,\n material: material,\n hasSkin: false,\n uid: meshName\n }\n meshes[meshName] = spriteMesh\n }\n\n const geometry = spriteMesh.geometry\n\n // Reset instance count for this frame\n geometry.instanceCount = 0\n\n // Ensure geometry has instance buffer large enough\n if (entities.length > geometry.maxInstances) {\n geometry.growInstanceBuffer(entities.length)\n }\n\n // Add sprite instances\n for (const item of entities) {\n const entity = item.entity\n const idx = geometry.instanceCount\n\n geometry.instanceCount++\n const base = idx * 28\n geometry.instanceData.set(entity._matrix, base)\n\n // Bounding sphere (use scale for radius approximation)\n const center = entity.position || [0, 0, 0]\n const radius = Math.max(entity.scale?.[0] || 1, entity.scale?.[1] || 1) * 0.5\n geometry.instanceData[base + 16] = center[0]\n geometry.instanceData[base + 17] = center[1]\n geometry.instanceData[base + 18] = center[2]\n geometry.instanceData[base + 19] = entity.noRounding ? -radius : radius\n\n // uvTransform from sprite frame\n const uvTransform = entity._uvTransform || [0, 0, 1, 1]\n geometry.instanceData[base + 20] = uvTransform[0]\n geometry.instanceData[base + 21] = uvTransform[1]\n geometry.instanceData[base + 22] = uvTransform[2]\n geometry.instanceData[base + 23] = uvTransform[3]\n\n // color tint\n const color = entity.color || [1, 1, 1, 1]\n geometry.instanceData[base + 24] = color[0]\n geometry.instanceData[base + 25] = color[1]\n geometry.instanceData[base + 26] = color[2]\n geometry.instanceData[base + 27] = color[3]\n }\n\n geometry._instanceDataDirty = true\n updatedMeshes.add(meshName)\n }\n }\n\n /**\n * Render a frame using the legacy mesh system (backward compatibility)\n *\n * @param {Object} meshes - Dictionary of meshes\n * @param {Camera} camera - Current camera\n * @param {number} dt - Delta time\n */\n async render(meshes, camera, dt = 0) {\n const { stats } = this.engine\n\n stats.drawCalls = 0\n stats.triangles = 0\n\n const passContext = {\n camera,\n meshes,\n dt\n }\n\n // Pass 4: GBuffer\n await this.passes.gbuffer.execute(passContext)\n\n // Pass 6: Lighting\n await this.passes.lighting.execute(passContext)\n\n // Pass 7: PostProcess\n await this.passes.postProcess.execute(passContext)\n\n this.stats.drawCalls = stats.drawCalls\n this.stats.triangles = stats.triangles\n }\n\n /**\n * Handle window resize\n * @param {number} width - Canvas width (full device pixels)\n * @param {number} height - Canvas height (full device pixels)\n * @param {number} renderScale - Scale for internal rendering (1.0 = full resolution)\n */\n async resize(width, height, renderScale = 1.0) {\n const timings = []\n const startTotal = performance.now()\n\n // Store full canvas dimensions (for CRT pixel-perfect output)\n this.canvasWidth = width\n this.canvasHeight = height\n\n // Calculate internal render dimensions (scaled for performance)\n const renderWidth = Math.max(1, Math.round(width * renderScale))\n const renderHeight = Math.max(1, Math.round(height * renderScale))\n\n // Store render dimensions\n this.renderWidth = renderWidth\n this.renderHeight = renderHeight\n this.renderScale = renderScale\n\n // Calculate effect scale for expensive passes\n // When autoScale.enabled is false but enabledForEffects is true and height > maxHeight,\n // expensive effects (bloom, AO, SSGI, planar reflection) render at reduced resolution\n const autoScale = this.engine?.settings?.rendering?.autoScale\n let effectScale = 1.0\n\n if (autoScale && !autoScale.enabled && autoScale.enabledForEffects) {\n if (renderHeight > (autoScale.maxHeight ?? 1536)) {\n effectScale = autoScale.scaleFactor ?? 0.5\n if (!this._effectScaleWarned) {\n console.log(`Effect auto-scale: Reducing effect resolution by ${effectScale} (height: ${renderHeight}px > ${autoScale.maxHeight}px)`)\n this._effectScaleWarned = true\n }\n } else if (this._effectScaleWarned) {\n console.log(`Effect auto-scale: Restoring full effect resolution (height: ${renderHeight}px <= ${autoScale.maxHeight}px)`)\n this._effectScaleWarned = false\n }\n }\n\n // Passes that render at full canvas resolution (for pixel-perfect output)\n const fullResPasses = new Set(['crt'])\n\n // Expensive passes that should be scaled down at high resolutions\n // Note: ssgiTile and ssgi must be at the same resolution (they share tile grid)\n // Note: planarReflection combines effectScale with its own resolution setting\n const effectScaledPasses = new Set(['bloom', 'ao', 'planarReflection'])\n\n // Calculate scaled dimensions for expensive effects (relative to render dimensions)\n const effectWidth = Math.max(1, Math.floor(renderWidth * effectScale))\n const effectHeight = Math.max(1, Math.floor(renderHeight * effectScale))\n\n // Store effect dimensions for use in rendering\n this.effectWidth = effectWidth\n this.effectHeight = effectHeight\n this.effectScale = effectScale\n\n // Resize all passes\n for (const passName in this.passes) {\n if (this.passes[passName]) {\n const start = performance.now()\n let w, h\n if (fullResPasses.has(passName)) {\n // CRT and similar passes render at full canvas resolution\n w = width\n h = height\n } else if (effectScaledPasses.has(passName) && effectScale < 1.0) {\n // Expensive effects use effect-scaled dimensions\n w = effectWidth\n h = effectHeight\n } else {\n // All other passes use render-scaled dimensions\n w = renderWidth\n h = renderHeight\n }\n await this.passes[passName].resize(w, h)\n timings.push({ name: `pass:${passName}`, time: performance.now() - start })\n }\n }\n\n // Resize history buffer manager (uses render dimensions)\n if (this.historyManager) {\n const start = performance.now()\n await this.historyManager.resize(renderWidth, renderHeight)\n timings.push({ name: 'historyManager', time: performance.now() - start })\n }\n\n // Rewire dependencies after resize\n let start = performance.now()\n await this.passes.lighting.setGBuffer(this.passes.gbuffer.getGBuffer())\n timings.push({ name: 'rewire:lighting.setGBuffer', time: performance.now() - start })\n\n start = performance.now()\n this.passes.lighting.setShadowPass(this.passes.shadow)\n timings.push({ name: 'rewire:lighting.setShadowPass', time: performance.now() - start })\n\n start = performance.now()\n this.passes.gbuffer.setNoise(this.noiseTexture, this.noiseSize, this.noiseAnimated)\n timings.push({ name: 'rewire:gbuffer.setNoise', time: performance.now() - start })\n\n // Rewire HiZ pass with new GBuffer depth and all passes that use it\n start = performance.now()\n if (this.passes.hiz) {\n this.passes.hiz.setDepthTexture(this.passes.gbuffer.getGBuffer()?.depth)\n this.passes.gbuffer.setHiZPass(this.passes.hiz)\n this.passes.lighting.setHiZPass(this.passes.hiz)\n this.passes.transparent.setHiZPass(this.passes.hiz)\n this.passes.shadow.setHiZPass(this.passes.hiz)\n if (this.passes.volumetricFog) {\n this.passes.volumetricFog.setHiZPass(this.passes.hiz)\n }\n }\n timings.push({ name: 'rewire:hiz', time: performance.now() - start })\n\n // Rewire volumetric fog pass\n if (this.passes.volumetricFog) {\n start = performance.now()\n this.passes.volumetricFog.setGBuffer(this.passes.gbuffer.getGBuffer())\n this.passes.volumetricFog.setShadowPass(this.passes.shadow)\n this.passes.volumetricFog.setLightingPass(this.passes.lighting)\n timings.push({ name: 'rewire:volumetricFog', time: performance.now() - start })\n }\n\n start = performance.now()\n this.passes.shadow.setNoise(this.noiseTexture, this.noiseSize, this.noiseAnimated)\n timings.push({ name: 'rewire:shadow.setNoise', time: performance.now() - start })\n\n start = performance.now()\n await this.passes.ao.setGBuffer(this.passes.gbuffer.getGBuffer())\n timings.push({ name: 'rewire:ao.setGBuffer', time: performance.now() - start })\n\n start = performance.now()\n this.passes.lighting.setAOTexture(this.passes.ao.getOutputTexture())\n timings.push({ name: 'rewire:lighting.setAOTexture', time: performance.now() - start })\n\n start = performance.now()\n this.passes.postProcess.setInputTexture(this.passes.lighting.getOutputTexture())\n timings.push({ name: 'rewire:postProcess.setInputTexture', time: performance.now() - start })\n\n start = performance.now()\n this.passes.postProcess.setNoise(this.noiseTexture, this.noiseSize, this.noiseAnimated)\n timings.push({ name: 'rewire:postProcess.setNoise', time: performance.now() - start })\n\n // Rewire transparent pass\n start = performance.now()\n this.passes.transparent.setGBuffer(this.passes.gbuffer.getGBuffer())\n this.passes.transparent.setShadowPass(this.passes.shadow)\n this.passes.transparent.setNoise(this.noiseTexture, this.noiseSize, this.noiseAnimated)\n timings.push({ name: 'rewire:transparent', time: performance.now() - start })\n\n // Rewire particle pass\n start = performance.now()\n this.passes.particles.setGBuffer(this.passes.gbuffer.getGBuffer())\n timings.push({ name: 'rewire:particles', time: performance.now() - start })\n\n // SSGI passes are wired dynamically per frame (no static rewire needed)\n\n }\n\n /**\n * Update environment map\n * @param {Texture} environmentMap - New environment map\n * @param {number} encoding - 0 = equirectangular (default), 1 = octahedral\n */\n setEnvironmentMap(environmentMap, encoding = 0) {\n this.environmentMap = environmentMap\n this.environmentEncoding = encoding\n this.passes.lighting.setEnvironmentMap(environmentMap, encoding)\n if (this.passes.reflection) {\n this.passes.reflection.setFallbackEnvironment(environmentMap, encoding)\n }\n if (this.passes.transparent) {\n this.passes.transparent.setEnvironmentMap(environmentMap, encoding)\n }\n if (this.passes.ambientCapture) {\n this.passes.ambientCapture.setDependencies({\n environmentMap,\n encoding\n })\n }\n }\n\n /**\n * Load reflection probes for a world\n * @param {string} worldId - World identifier\n */\n async loadWorldProbes(worldId) {\n if (this.passes.reflection) {\n await this.passes.reflection.loadWorldProbes(worldId)\n }\n }\n\n /**\n * Load a specific reflection probe\n * @param {string} url - URL to probe HDR image\n * @param {vec3} position - World position\n * @param {string} worldId - World identifier\n */\n async loadProbe(url, position, worldId = 'default') {\n if (this.passes.reflection) {\n return await this.passes.reflection.loadProbe(url, position, worldId)\n }\n return null\n }\n\n /**\n * Request a probe capture at position\n * @param {vec3} position - Capture position\n * @param {string} worldId - World identifier\n */\n requestProbeCapture(position, worldId = 'default') {\n if (this.passes.reflection) {\n this.passes.reflection.requestCapture(position, worldId)\n }\n }\n\n /**\n * Get the reflection probe manager\n */\n getProbeManager() {\n return this.passes.reflection?.getProbeManager()\n }\n\n /**\n * Capture a probe at position and optionally save/use it\n * @param {vec3} position - Capture position\n * @param {Object} options - { save: bool, filename: string, format: 'hdr'|'jpg', useAsEnvironment: bool, saveDebug: bool, saveFaces: bool }\n */\n async captureProbe(position, options = {}) {\n const {\n save = true,\n filename = 'probe', // Base filename without extension\n format = 'jpg', // 'hdr' or 'jpg' (jpg = RGB + exp pair)\n useAsEnvironment = false,\n saveDebug = false, // Save tone-mapped PNG for preview\n saveFaces = false\n } = options\n const probeCapture = this.passes.reflection?.getProbeCapture()\n\n if (!probeCapture) {\n console.error('RenderGraph: ProbeCapture not initialized')\n return\n }\n\n // CRITICAL: Pause main render loop during probe capture\n // The main render modifies mesh.geometry.instanceCount on shared mesh objects\n // which corrupts the probe capture data\n this._isCapturingProbe = true\n\n try {\n // Clear probe pass pipeline caches to ensure fresh creation\n // This fixes issues where pipelines from previous captures may be stale\n if (this.probePasses.gbuffer) {\n this.probePasses.gbuffer.pipelines.clear()\n this.probePasses.gbuffer.skinnedPipelines.clear()\n }\n\n // Clear probe meshes dictionary to avoid stale entries\n this._probeMeshes = {}\n\n await probeCapture.capture(position)\n\n // Save cube faces for debugging (before octahedral conversion)\n if (saveFaces) {\n await probeCapture.saveCubeFaces('face')\n }\n\n if (save) {\n if (format === 'hdr') {\n // Save as Radiance HDR file\n await probeCapture.saveAsHDR(`${filename}.hdr`)\n } else {\n // Save as JPG pair (RGB + exponent)\n await probeCapture.saveAsJPG(filename)\n }\n }\n\n if (saveDebug) {\n // Save tone-mapped PNG for preview/debugging\n await probeCapture.saveAsDebugPNG(`${filename}_debug.png`)\n }\n\n if (useAsEnvironment) {\n const envTex = await probeCapture.getAsEnvironmentTexture()\n if (envTex) {\n this.passes.lighting.setEnvironmentMap(envTex, 1) // 1 = octahedral encoding\n console.log('RenderGraph: Using captured probe as environment (octahedral, RGBE)')\n }\n }\n\n return probeCapture.getProbeTexture()\n } finally {\n // Always reset the flag, even if capture fails\n this._isCapturingProbe = false\n }\n }\n\n /**\n * Convert an equirectangular HDR environment map to octahedral format\n * and save as RGBI JPG pair for efficient storage\n *\n * @param {Object} options - Conversion options\n * @param {string} options.url - URL to HDR file (uses current environment if not provided)\n * @param {string} options.filename - Base filename without extension (default: 'environment')\n * @param {boolean} options.useAsEnvironment - Set converted map as active environment (default: true)\n * @param {boolean} options.saveDebug - Also save tone-mapped PNG for preview (default: false)\n * @returns {Promise<Object>} The converted texture\n */\n async convertEquirectToOctahedral(options = {}) {\n const {\n url = null,\n filename = 'environment',\n useAsEnvironment = true,\n saveDebug = false\n } = options\n\n const probeCapture = this.passes.reflection?.getProbeCapture()\n\n if (!probeCapture) {\n console.error('RenderGraph: ProbeCapture not initialized')\n return null\n }\n\n // Load HDR file if URL provided, otherwise use current environment\n let sourceEnvMap = this.environmentMap\n if (url) {\n console.log(`RenderGraph: Loading HDR from ${url}`)\n sourceEnvMap = await Texture.fromImage(this.engine, url)\n }\n\n if (!sourceEnvMap) {\n console.error('RenderGraph: No environment map available')\n return null\n }\n\n console.log('RenderGraph: Converting equirectangular to octahedral format...')\n\n // Convert equirectangular to octahedral\n await probeCapture.convertEquirectToOctahedral(sourceEnvMap)\n\n // Save as RGBI JPG pair\n await probeCapture.saveAsJPG(filename)\n\n if (saveDebug) {\n // Save tone-mapped PNG for preview\n await probeCapture.saveAsDebugPNG(`${filename}_debug.png`)\n }\n\n // Optionally set as environment\n if (useAsEnvironment) {\n const envTex = await probeCapture.getAsEnvironmentTexture()\n if (envTex) {\n this.passes.lighting.setEnvironmentMap(envTex, 1) // 1 = octahedral encoding\n console.log('RenderGraph: Using converted environment (octahedral, RGBE)')\n }\n }\n\n console.log(`RenderGraph: Saved octahedral environment as ${filename}.jpg + ${filename}.int.jpg`)\n return probeCapture.getProbeTexture()\n }\n\n /**\n * Initialize probe-specific passes at fixed 256x256 size\n * These are used for probe face capture (smaller than main passes)\n */\n async _initProbePasses() {\n const { device } = this.engine\n const probeSize = 1024\n\n // Create probe GBuffer pass\n this.probePasses.gbuffer = new GBufferPass(this.engine)\n await this.probePasses.gbuffer.initialize()\n await this.probePasses.gbuffer.resize(probeSize, probeSize)\n\n // Create probe Lighting pass\n this.probePasses.lighting = new LightingPass(this.engine)\n await this.probePasses.lighting.initialize()\n await this.probePasses.lighting.resize(probeSize, probeSize)\n // Use exposure = 1.0 for probe capture (raw HDR values, no display exposure)\n this.probePasses.lighting.exposureOverride = 1.0\n\n // Create a simple white AO texture for probes (skip AO computation)\n const dummyAOTexture = device.createTexture({\n label: 'probeAO',\n size: [probeSize, probeSize],\n format: 'r8unorm',\n usage: GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.COPY_DST\n })\n // Fill with white (AO = 1.0 = no occlusion)\n const whiteData = new Uint8Array(probeSize * probeSize).fill(255)\n device.queue.writeTexture(\n { texture: dummyAOTexture },\n whiteData,\n { bytesPerRow: probeSize },\n { width: probeSize, height: probeSize }\n )\n this.probePasses.dummyAO = {\n texture: dummyAOTexture,\n view: dummyAOTexture.createView()\n }\n\n // Wire up probe passes\n await this.probePasses.lighting.setGBuffer(this.probePasses.gbuffer.getGBuffer())\n this.probePasses.lighting.setEnvironmentMap(this.environmentMap, this.environmentEncoding)\n this.probePasses.lighting.setShadowPass(this.passes.shadow) // Share shadow pass\n this.probePasses.lighting.setNoise(this.noiseTexture, this.noiseSize, this.noiseAnimated)\n this.probePasses.lighting.setAOTexture(this.probePasses.dummyAO)\n }\n\n /**\n * Render scene for a probe face\n * Called by ProbeCapture for each of the 6 cube faces\n *\n * @param {mat4} viewMatrix - View matrix for this face\n * @param {mat4} projMatrix - Projection matrix (90° FOV)\n * @param {Object} colorTarget - Target texture object with .texture and .view\n * @param {Object} depthTarget - Depth texture object with .texture and .view\n * @param {number} faceIndex - Which face (0-5) for debugging\n * @param {vec3} position - Capture position\n */\n async _renderSceneForProbe(viewMatrix, projMatrix, colorTarget, depthTarget, faceIndex, position) {\n if (!this._lastRenderContext) {\n console.warn('RenderGraph: No render context available for probe capture')\n return\n }\n\n if (!this.probePasses.gbuffer || !this.probePasses.lighting) {\n console.warn('RenderGraph: Probe passes not initialized')\n return\n }\n\n const { device } = this.engine\n const { entityManager, assetManager } = this._lastRenderContext\n\n // CRITICAL: Use a SEPARATE meshes dictionary for probe capture\n // The main render loop (via requestAnimationFrame) can overwrite instance counts\n // in the shared meshes dictionary while probe capture is running\n if (!this._probeMeshes) {\n this._probeMeshes = {}\n }\n const meshes = this._probeMeshes\n\n // Build entity list for probe capture (ALL entities, no frustum culling)\n const probeEntities = []\n entityManager.forEach((id, entity) => {\n if (entity.model) {\n probeEntities.push({ id, entity, distance: 0 })\n }\n })\n const probeGroups = this.cullingSystem.groupByModel(probeEntities)\n\n // Update meshes dictionary with ALL entities for probe rendering\n this._updateMeshInstancesFromEntities(probeGroups, assetManager, meshes, true, null, 0, null)\n\n // Ensure all geometry buffers are written to GPU before rendering\n for (const name in meshes) {\n const mesh = meshes[name]\n if (mesh?.geometry?.update) {\n mesh.geometry.update()\n }\n }\n\n // Wait for GPU to complete buffer writes before rendering each face\n await device.queue.onSubmittedWorkDone()\n\n // Create inverse matrices for lighting calculations\n const iView = mat4.create()\n const iProj = mat4.create()\n const iViewProj = mat4.create()\n const viewProj = mat4.create()\n mat4.invert(iView, viewMatrix)\n mat4.invert(iProj, projMatrix)\n mat4.multiply(viewProj, projMatrix, viewMatrix)\n mat4.invert(iViewProj, viewProj)\n\n // Create a temporary camera-like object with the probe matrices\n // No jitter for probe capture - we want clean, stable environment maps\n const probeCamera = {\n view: viewMatrix,\n proj: projMatrix,\n iView,\n iProj,\n iViewProj,\n position: position || [0, 0, 0],\n near: 0.1,\n far: 10000,\n aspect: 1.0,\n jitterEnabled: false,\n jitterOffset: [0, 0],\n updateMatrix: () => {},\n updateView: () => {}\n }\n\n // Execute probe GBuffer pass (uses meshes with ALL entity instances)\n await this.probePasses.gbuffer.execute({\n camera: probeCamera,\n meshes,\n dt: 0\n })\n\n // Copy light data and environment from main lighting pass to probe pass\n this.probePasses.lighting.lights = this.passes.lighting.lights\n // Ensure probe lighting has current environment map with correct encoding\n if (this.environmentMap) {\n this.probePasses.lighting.setEnvironmentMap(this.environmentMap, this.environmentEncoding)\n }\n\n // Execute probe Lighting pass\n await this.probePasses.lighting.execute({\n camera: probeCamera,\n meshes,\n dt: 0,\n lights: this.passes.lighting.lights,\n mainLight: this.engine?.settings?.mainLight\n })\n\n // Copy lighting output to the probe face texture\n const lightingOutput = this.probePasses.lighting.getOutputTexture()\n const copySize = colorTarget.texture.width // Get size from target texture\n\n if (lightingOutput && colorTarget.texture) {\n const commandEncoder = device.createCommandEncoder()\n commandEncoder.copyTextureToTexture(\n { texture: lightingOutput.texture },\n { texture: colorTarget.texture },\n { width: copySize, height: copySize }\n )\n device.queue.submit([commandEncoder.finish()])\n }\n\n // Copy GBuffer depth to face depth texture (for skybox depth testing)\n const gbufferDepth = this.probePasses.gbuffer.getGBuffer()?.depth\n if (gbufferDepth && depthTarget.texture) {\n const commandEncoder = device.createCommandEncoder()\n commandEncoder.copyTextureToTexture(\n { texture: gbufferDepth.texture },\n { texture: depthTarget.texture },\n { width: copySize, height: copySize }\n )\n device.queue.submit([commandEncoder.finish()])\n }\n }\n\n /**\n * Get culling system for external configuration\n */\n getCullingSystem() {\n return this.cullingSystem\n }\n\n /**\n * Get instance manager for stats\n */\n getInstanceManager() {\n return this.instanceManager\n }\n\n /**\n * Get sprite system for external access\n */\n getSpriteSystem() {\n return this.spriteSystem\n }\n\n /**\n * Get particle system for external access\n */\n getParticleSystem() {\n return this.particleSystem\n }\n\n /**\n * Get render stats\n */\n getStats() {\n return {\n ...this.stats,\n instance: this.instanceManager.getStats(),\n culling: this.cullingSystem.getStats(),\n occlusion: this.cullingSystem.getOcclusionStats()\n }\n }\n\n /**\n * Enable/disable a specific pass\n * @param {string} passName - Name of pass ('gbuffer', 'lighting', 'postProcess')\n * @param {boolean} enabled - Whether to enable\n */\n setPassEnabled(passName, enabled) {\n if (this.passes[passName]) {\n this.passes[passName].enabled = enabled\n }\n }\n\n /**\n * Get a specific pass for configuration\n * @param {string} passName - Name of pass\n * @returns {BasePass} The pass instance\n */\n getPass(passName) {\n return this.passes[passName]\n }\n\n /**\n * Invalidate occlusion culling data and reset warmup period.\n * Call this after scene loading or major camera changes to prevent\n * incorrect occlusion culling with stale depth buffer data.\n */\n invalidateOcclusionCulling() {\n if (this.passes.hiz) {\n this.passes.hiz.invalidate()\n }\n }\n\n /**\n * Load noise texture based on settings\n * Supports: 'bluenoise' (loaded from file), 'bayer8' (generated 8x8 ordered dither)\n */\n async _loadNoiseTexture() {\n const noiseSettings = this.engine?.settings?.noise || { type: 'bluenoise', animated: true }\n this.noiseAnimated = noiseSettings.animated !== false\n\n if (noiseSettings.type === 'bayer8') {\n // Create 8x8 Bayer ordered dither pattern\n this.noiseTexture = this._createBayerTexture()\n this.noiseSize = 8\n console.log('RenderGraph: Using Bayer 8x8 dither pattern')\n } else {\n // Default: load blue noise\n try {\n this.noiseTexture = await Texture.fromImage(this.engine, '/bluenoise.png', {\n flipY: false,\n srgb: false, // Linear data\n generateMips: false,\n addressMode: 'repeat' // Tile across screen\n })\n if (this.noiseTexture.width) {\n this.noiseSize = this.noiseTexture.width\n }\n } catch (e) {\n console.warn('RenderGraph: Failed to load blue noise texture:', e)\n }\n }\n }\n\n /**\n * Create an 8x8 Bayer ordered dither texture\n * @returns {Object} Texture object with texture and view properties\n */\n _createBayerTexture() {\n const { device } = this.engine\n\n // Bayer 8x8 matrix (values 0-63, we normalize to 0-1)\n const bayer8x8 = [\n 0, 32, 8, 40, 2, 34, 10, 42,\n 48, 16, 56, 24, 50, 18, 58, 26,\n 12, 44, 4, 36, 14, 46, 6, 38,\n 60, 28, 52, 20, 62, 30, 54, 22,\n 3, 35, 11, 43, 1, 33, 9, 41,\n 51, 19, 59, 27, 49, 17, 57, 25,\n 15, 47, 7, 39, 13, 45, 5, 37,\n 63, 31, 55, 23, 61, 29, 53, 21\n ]\n\n // Create RGBA texture data (normalized to 0-255)\n const data = new Uint8Array(8 * 8 * 4)\n for (let i = 0; i < 64; i++) {\n const value = Math.round((bayer8x8[i] / 63) * 255)\n data[i * 4 + 0] = value // R\n data[i * 4 + 1] = value // G\n data[i * 4 + 2] = value // B\n data[i * 4 + 3] = 255 // A\n }\n\n // Create GPU texture\n const texture = device.createTexture({\n label: 'Bayer 8x8 Dither',\n size: [8, 8, 1],\n format: 'rgba8unorm',\n usage: GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_DST\n })\n\n device.queue.writeTexture(\n { texture },\n data,\n { bytesPerRow: 8 * 4 },\n { width: 8, height: 8 }\n )\n\n // Create sampler with repeat addressing for tiling\n const sampler = device.createSampler({\n label: 'Bayer 8x8 Sampler',\n addressModeU: 'repeat',\n addressModeV: 'repeat',\n magFilter: 'nearest',\n minFilter: 'nearest',\n })\n\n return {\n texture,\n view: texture.createView(),\n sampler,\n width: 8,\n height: 8\n }\n }\n\n /**\n * Reload noise texture and update all passes that use it\n * Called when noise settings change at runtime\n */\n async reloadNoiseTexture() {\n // Destroy old texture if it exists\n if (this.noiseTexture?.texture) {\n this.noiseTexture.texture.destroy()\n }\n\n // Load new noise texture based on current settings\n await this._loadNoiseTexture()\n\n // Update all passes that use noise\n if (this.passes.gbuffer) {\n this.passes.gbuffer.setNoise(this.noiseTexture, this.noiseSize, this.noiseAnimated)\n }\n if (this.passes.shadow) {\n this.passes.shadow.setNoise(this.noiseTexture, this.noiseSize, this.noiseAnimated)\n }\n if (this.passes.lighting) {\n this.passes.lighting.setNoise(this.noiseTexture, this.noiseSize, this.noiseAnimated)\n }\n if (this.passes.ao) {\n this.passes.ao.setNoise(this.noiseTexture, this.noiseSize, this.noiseAnimated)\n }\n if (this.passes.transparent) {\n this.passes.transparent.setNoise(this.noiseTexture, this.noiseSize, this.noiseAnimated)\n }\n if (this.passes.postProcess) {\n this.passes.postProcess.setNoise(this.noiseTexture, this.noiseSize, this.noiseAnimated)\n }\n if (this.passes.renderPost) {\n this.passes.renderPost.setNoise(this.noiseTexture, this.noiseSize, this.noiseAnimated)\n }\n\n console.log(`RenderGraph: Reloaded noise texture (${this.engine?.settings?.noise?.type || 'bluenoise'})`)\n }\n\n /**\n * Destroy all resources\n */\n destroy() {\n for (const passName in this.passes) {\n if (this.passes[passName]) {\n this.passes[passName].destroy()\n }\n }\n if (this.historyManager) {\n this.historyManager.destroy()\n }\n this.instanceManager.destroy()\n this.spriteSystem.destroy()\n this.particleSystem.destroy()\n }\n}\n\nexport { RenderGraph }\n","/**\n * Skin class for skeletal animation\n * Manages joint hierarchy, animations, and joint matrices texture for GPU skinning\n *\n * Supports animation blending for smooth transitions between animations.\n */\nclass Skin {\n engine = null\n\n constructor(engine = null, options = {}) {\n this.engine = engine\n this.joints = [] // Array of joint nodes\n this.inverseBindMatrices = [] // Inverse bind matrices for each joint\n this.jointMatrices = [] // Final joint matrices (worldMatrix * inverseBindMatrix)\n this.jointData = null // Float32Array for texture upload\n this.jointTexture = null // GPU texture storing joint matrices\n this.animations = {} // Named animations { name: Animation }\n this.currentAnimation = null // Currently playing animation name\n this.time = 0 // Current animation time\n this.speed = 1.0 // Playback speed multiplier\n this.loop = true // Whether to loop animation\n this.rootNode = null // Root node of skeleton hierarchy\n\n // Animation blending state\n this.blendFromAnimation = null // Previous animation name (during blend)\n this.blendFromTime = 0 // Time in previous animation\n this.blendWeight = 1.0 // Blend weight (0 = previous, 1 = current)\n this.blendDuration = 0.3 // Default blend duration in seconds\n this.blendElapsed = 0 // Elapsed time in current blend\n this.isBlending = false // Whether currently blending\n\n // Per-skin local transforms (for individual skins that don't share joint state)\n this.localTransforms = null // Array of { position, rotation, scale } per joint\n this.worldMatrices = null // Array of mat4 for world transforms\n this.useLocalTransforms = false // Whether to use per-skin transforms instead of shared joints\n\n // Previous frame joint matrices for motion vectors\n this.prevJointData = null // Float32Array for previous frame\n this.prevJointTexture = null // GPU texture storing previous joint matrices\n this.prevJointTextureView = null // View for binding\n }\n\n /**\n * Initialize the skin with joints and inverse bind matrices\n * @param {Array} joints - Array of joint nodes\n * @param {Float32Array} inverseBindMatrixData - Flat array of inverse bind matrices\n * @param {Object} rootNode - Root node of the skeleton\n */\n init(joints, inverseBindMatrixData, rootNode) {\n const { device } = this.engine\n\n this.joints = joints\n this.rootNode = rootNode\n this.inverseBindMatrices = []\n this.jointMatrices = []\n\n // Allocate joint data array (16 floats per joint for mat4)\n this.jointData = new Float32Array(joints.length * 16)\n\n // Parse inverse bind matrices and create views for joint matrices\n for (let i = 0; i < joints.length; i++) {\n // Create view into inverse bind matrix data\n const ibm = new Float32Array(\n inverseBindMatrixData.buffer,\n inverseBindMatrixData.byteOffset + i * 16 * 4,\n 16\n )\n this.inverseBindMatrices.push(ibm)\n\n // Create view into joint data for this joint's matrix\n const jointMatrix = new Float32Array(this.jointData.buffer, i * 16 * 4, 16)\n mat4.identity(jointMatrix)\n this.jointMatrices.push(jointMatrix)\n\n // Link joint to this skin\n joints[i].skin = this\n }\n\n // Create GPU texture for joint matrices (4 pixels wide x numJoints high, RGBA32F)\n this.jointTexture = device.createTexture({\n size: [4, joints.length, 1],\n format: 'rgba32float',\n usage: GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_DST,\n })\n\n this.jointTextureView = this.jointTexture.createView()\n\n // Create previous frame joint texture for motion vectors\n this.prevJointData = new Float32Array(joints.length * 16)\n this.prevJointTexture = device.createTexture({\n size: [4, joints.length, 1],\n format: 'rgba32float',\n usage: GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_DST,\n })\n this.prevJointTextureView = this.prevJointTexture.createView()\n\n // Initialize prevJointData with identity matrices\n for (let i = 0; i < joints.length; i++) {\n const offset = i * 16\n // Identity matrix column-major\n this.prevJointData[offset + 0] = 1; this.prevJointData[offset + 1] = 0; this.prevJointData[offset + 2] = 0; this.prevJointData[offset + 3] = 0\n this.prevJointData[offset + 4] = 0; this.prevJointData[offset + 5] = 1; this.prevJointData[offset + 6] = 0; this.prevJointData[offset + 7] = 0\n this.prevJointData[offset + 8] = 0; this.prevJointData[offset + 9] = 0; this.prevJointData[offset + 10] = 1; this.prevJointData[offset + 11] = 0\n this.prevJointData[offset + 12] = 0; this.prevJointData[offset + 13] = 0; this.prevJointData[offset + 14] = 0; this.prevJointData[offset + 15] = 1\n }\n\n // Create sampler for joint texture (nearest filtering, no interpolation)\n this.jointSampler = device.createSampler({\n magFilter: 'nearest',\n minFilter: 'nearest',\n })\n }\n\n /**\n * Add an animation to this skin\n * @param {string} name - Animation name\n * @param {Object} animation - Animation data { duration, channels }\n */\n addAnimation(name, animation) {\n this.animations[name] = animation\n if (!this.currentAnimation) {\n this.currentAnimation = name\n }\n }\n\n /**\n * Play a named animation with optional blending from current animation\n * @param {string} name - Animation name\n * @param {boolean} loop - Whether to loop\n * @param {number} phase - Starting phase (0-1)\n * @param {number} blendTime - Blend duration in seconds (0 = instant switch)\n */\n play(name, loop = true, phase = 0.0, blendTime = 0) {\n if (!this.animations[name]) return\n\n const anim = this.animations[name]\n phase = Math.max(Math.min(phase, 1.0), 0.0)\n\n // If we have a current animation and blend time > 0, start blending\n if (blendTime > 0 && this.currentAnimation && this.currentAnimation !== name) {\n this.blendFromAnimation = this.currentAnimation\n this.blendFromTime = this.time\n this.blendDuration = blendTime\n this.blendElapsed = 0\n this.blendWeight = 0 // Start fully on previous animation\n this.isBlending = true\n } else {\n this.isBlending = false\n this.blendFromAnimation = null\n }\n\n this.currentAnimation = name\n this.loop = loop\n this.time = phase * anim.duration\n }\n\n /**\n * Transition to a new animation with blending\n * @param {string} name - Target animation name\n * @param {number} blendTime - Blend duration (default 0.3s)\n */\n blendTo(name, blendTime = 0.3) {\n this.play(name, this.loop, 0, blendTime)\n }\n\n /**\n * Update animation and joint matrices\n * @param {number} dt - Delta time in seconds\n */\n update(dt) {\n const { device } = this.engine\n\n // Copy current joint data to previous BEFORE updating (for motion vectors)\n if (this.prevJointData && this.jointData) {\n this.prevJointData.set(this.jointData)\n }\n\n // Update animation time\n this.time += dt * this.speed\n\n // Update blend progress (only if dt > 0, otherwise assume externally managed)\n if (this.isBlending && dt > 0) {\n this.blendElapsed += dt\n this.blendWeight = Math.min(this.blendElapsed / this.blendDuration, 1.0)\n\n // Also advance the \"from\" animation time\n this.blendFromTime += dt * this.speed\n\n // Blend complete\n if (this.blendWeight >= 1.0) {\n this.isBlending = false\n this.blendFromAnimation = null\n this.blendWeight = 1.0\n }\n }\n\n // Apply animations (with blending if active)\n if (this.useLocalTransforms) {\n this._applyAnimationToLocalTransforms(dt)\n } else {\n this._applyAnimationToSharedJoints()\n }\n\n // Update world matrices\n if (this.useLocalTransforms) {\n this._updateWorldMatricesFromLocal()\n } else if (this.rootNode) {\n this.rootNode.updateMatrix()\n }\n\n // Calculate final joint matrices: jointMatrix = worldMatrix * inverseBindMatrix\n for (let i = 0; i < this.joints.length; i++) {\n const worldMat = this.useLocalTransforms ? this.worldMatrices[i] : this.joints[i].world\n const dst = this.jointMatrices[i]\n mat4.multiply(dst, worldMat, this.inverseBindMatrices[i])\n }\n\n // Upload previous joint matrices to GPU (for motion vectors)\n if (this.prevJointTexture && this.prevJointData) {\n device.queue.writeTexture(\n { texture: this.prevJointTexture },\n this.prevJointData,\n { bytesPerRow: 4 * 4 * 4, rowsPerImage: this.joints.length },\n [4, this.joints.length, 1]\n )\n }\n\n // Upload current joint matrices to GPU\n device.queue.writeTexture(\n { texture: this.jointTexture },\n this.jointData,\n { bytesPerRow: 4 * 4 * 4, rowsPerImage: this.joints.length },\n [4, this.joints.length, 1]\n )\n }\n\n /**\n * Apply animation to shared joint objects (original behavior)\n */\n _applyAnimationToSharedJoints() {\n if (!this.currentAnimation || !this.animations[this.currentAnimation]) return\n\n const currentAnim = this.animations[this.currentAnimation]\n\n // Handle looping for current animation\n let currentTime = this.time\n if (this.loop && currentAnim.duration > 0) {\n currentTime = currentTime % currentAnim.duration\n } else {\n currentTime = Math.min(currentTime, currentAnim.duration)\n }\n\n if (this.isBlending && this.blendFromAnimation && this.animations[this.blendFromAnimation]) {\n // Blending between two animations\n const fromAnim = this.animations[this.blendFromAnimation]\n\n // Handle looping for from animation\n let fromTime = this.blendFromTime\n if (this.loop && fromAnim.duration > 0) {\n fromTime = fromTime % fromAnim.duration\n } else {\n fromTime = Math.min(fromTime, fromAnim.duration)\n }\n\n // Apply blended animation\n this._applyBlendedAnimation(fromAnim, fromTime, currentAnim, currentTime, this.blendWeight)\n } else {\n // Single animation\n this._applyAnimation(currentAnim, currentTime)\n }\n }\n\n /**\n * Apply animation with blending to local transforms (for individual skins)\n */\n _applyAnimationToLocalTransforms(dt) {\n if (!this.localTransforms) return\n if (!this.currentAnimation || !this.animations[this.currentAnimation]) return\n\n const currentAnim = this.animations[this.currentAnimation]\n\n // Handle looping for current animation\n let currentTime = this.time\n if (this.loop && currentAnim.duration > 0) {\n currentTime = currentTime % currentAnim.duration\n } else {\n currentTime = Math.min(currentTime, currentAnim.duration)\n }\n\n if (this.isBlending && this.blendFromAnimation && this.animations[this.blendFromAnimation]) {\n const fromAnim = this.animations[this.blendFromAnimation]\n\n let fromTime = this.blendFromTime\n if (this.loop && fromAnim.duration > 0) {\n fromTime = fromTime % fromAnim.duration\n } else {\n fromTime = Math.min(fromTime, fromAnim.duration)\n }\n\n this._applyBlendedAnimationToLocal(fromAnim, fromTime, currentAnim, currentTime, this.blendWeight)\n } else {\n this._applyAnimationToLocal(currentAnim, currentTime)\n }\n }\n\n /**\n * Apply blended animation between two animations to shared joints\n */\n _applyBlendedAnimation(fromAnim, fromTime, toAnim, toTime, weight) {\n // Temporary storage for blended values\n const tempPos = vec3.create()\n const tempRot = quat.create()\n const tempScale = vec3.fromValues(1, 1, 1)\n\n // First apply \"from\" animation to get base pose\n this._applyAnimation(fromAnim, fromTime)\n\n // Store the \"from\" pose for each joint\n const fromPoses = this.joints.map(joint => ({\n position: vec3.clone(joint.position),\n rotation: quat.clone(joint.rotation),\n scale: vec3.clone(joint.scale)\n }))\n\n // Apply \"to\" animation\n this._applyAnimation(toAnim, toTime)\n\n // Blend between stored \"from\" pose and current \"to\" pose\n for (let i = 0; i < this.joints.length; i++) {\n const joint = this.joints[i]\n const fromPose = fromPoses[i]\n\n // Lerp position\n vec3.lerp(joint.position, fromPose.position, joint.position, weight)\n\n // Slerp rotation\n quat.slerp(joint.rotation, fromPose.rotation, joint.rotation, weight)\n\n // Lerp scale\n vec3.lerp(joint.scale, fromPose.scale, joint.scale, weight)\n }\n }\n\n /**\n * Apply animation keyframes to joints\n * @param {Object} anim - Animation data\n * @param {number} time - Current time\n */\n _applyAnimation(anim, time) {\n for (const channel of anim.channels) {\n const joint = channel.target\n const sampler = channel.sampler\n\n // Find keyframes\n const times = sampler.input\n const values = sampler.output\n\n // Find the two keyframes to interpolate between\n let prevIndex = 0\n let nextIndex = 0\n\n for (let i = 0; i < times.length - 1; i++) {\n if (time >= times[i] && time < times[i + 1]) {\n prevIndex = i\n nextIndex = i + 1\n break\n }\n if (i === times.length - 2) {\n prevIndex = i + 1\n nextIndex = i + 1\n }\n }\n\n // Calculate interpolation factor\n let t = 0\n if (nextIndex !== prevIndex) {\n t = (time - times[prevIndex]) / (times[nextIndex] - times[prevIndex])\n }\n\n // Apply based on path type\n const path = channel.path\n const numComponents = path === 'rotation' ? 4 : 3\n\n if (path === 'translation') {\n const prev = [\n values[prevIndex * 3],\n values[prevIndex * 3 + 1],\n values[prevIndex * 3 + 2]\n ]\n const next = [\n values[nextIndex * 3],\n values[nextIndex * 3 + 1],\n values[nextIndex * 3 + 2]\n ]\n vec3.lerp(joint.position, prev, next, t)\n } else if (path === 'rotation') {\n const prev = quat.fromValues(\n values[prevIndex * 4],\n values[prevIndex * 4 + 1],\n values[prevIndex * 4 + 2],\n values[prevIndex * 4 + 3]\n )\n const next = quat.fromValues(\n values[nextIndex * 4],\n values[nextIndex * 4 + 1],\n values[nextIndex * 4 + 2],\n values[nextIndex * 4 + 3]\n )\n quat.slerp(joint.rotation, prev, next, t)\n } else if (path === 'scale') {\n const prev = [\n values[prevIndex * 3],\n values[prevIndex * 3 + 1],\n values[prevIndex * 3 + 2]\n ]\n const next = [\n values[nextIndex * 3],\n values[nextIndex * 3 + 1],\n values[nextIndex * 3 + 2]\n ]\n vec3.lerp(joint.scale, prev, next, t)\n }\n }\n }\n\n /**\n * Initialize local transforms for individual skin (enables per-skin animation state)\n * Call this to make the skin independent from the shared joint hierarchy\n */\n initLocalTransforms() {\n this.useLocalTransforms = true\n this.localTransforms = []\n this.worldMatrices = []\n\n // Create local transform storage for each joint\n for (let i = 0; i < this.joints.length; i++) {\n const joint = this.joints[i]\n this.localTransforms.push({\n position: vec3.clone(joint.position),\n rotation: quat.clone(joint.rotation),\n scale: vec3.clone(joint.scale)\n })\n this.worldMatrices.push(mat4.create())\n }\n }\n\n /**\n * Apply animation to local transforms (for individual skins)\n */\n _applyAnimationToLocal(anim, time) {\n for (const channel of anim.channels) {\n const joint = channel.target\n const jointIndex = this.joints.indexOf(joint)\n if (jointIndex === -1) continue\n\n const localTrans = this.localTransforms[jointIndex]\n const sampler = channel.sampler\n const times = sampler.input\n const values = sampler.output\n\n // Find keyframes\n let prevIndex = 0\n let nextIndex = 0\n for (let i = 0; i < times.length - 1; i++) {\n if (time >= times[i] && time < times[i + 1]) {\n prevIndex = i\n nextIndex = i + 1\n break\n }\n if (i === times.length - 2) {\n prevIndex = i + 1\n nextIndex = i + 1\n }\n }\n\n let t = 0\n if (nextIndex !== prevIndex) {\n t = (time - times[prevIndex]) / (times[nextIndex] - times[prevIndex])\n }\n\n const path = channel.path\n if (path === 'translation') {\n const prev = [values[prevIndex * 3], values[prevIndex * 3 + 1], values[prevIndex * 3 + 2]]\n const next = [values[nextIndex * 3], values[nextIndex * 3 + 1], values[nextIndex * 3 + 2]]\n vec3.lerp(localTrans.position, prev, next, t)\n } else if (path === 'rotation') {\n const prev = quat.fromValues(values[prevIndex * 4], values[prevIndex * 4 + 1], values[prevIndex * 4 + 2], values[prevIndex * 4 + 3])\n const next = quat.fromValues(values[nextIndex * 4], values[nextIndex * 4 + 1], values[nextIndex * 4 + 2], values[nextIndex * 4 + 3])\n quat.slerp(localTrans.rotation, prev, next, t)\n } else if (path === 'scale') {\n const prev = [values[prevIndex * 3], values[prevIndex * 3 + 1], values[prevIndex * 3 + 2]]\n const next = [values[nextIndex * 3], values[nextIndex * 3 + 1], values[nextIndex * 3 + 2]]\n vec3.lerp(localTrans.scale, prev, next, t)\n }\n }\n }\n\n /**\n * Apply blended animation to local transforms\n */\n _applyBlendedAnimationToLocal(fromAnim, fromTime, toAnim, toTime, weight) {\n // First apply \"from\" animation\n this._applyAnimationToLocal(fromAnim, fromTime)\n\n // Store from pose\n const fromPoses = this.localTransforms.map(lt => ({\n position: vec3.clone(lt.position),\n rotation: quat.clone(lt.rotation),\n scale: vec3.clone(lt.scale)\n }))\n\n // Apply \"to\" animation\n this._applyAnimationToLocal(toAnim, toTime)\n\n // Blend\n for (let i = 0; i < this.localTransforms.length; i++) {\n const lt = this.localTransforms[i]\n const from = fromPoses[i]\n\n vec3.lerp(lt.position, from.position, lt.position, weight)\n quat.slerp(lt.rotation, from.rotation, lt.rotation, weight)\n vec3.lerp(lt.scale, from.scale, lt.scale, weight)\n }\n }\n\n /**\n * Update world matrices from local transforms (replicates joint hierarchy traversal)\n */\n _updateWorldMatricesFromLocal() {\n // Build parent-child mapping from joints\n const parentIndices = this.joints.map(joint => {\n if (joint.parent) {\n return this.joints.indexOf(joint.parent)\n }\n return -1\n })\n\n // Process joints in order (assuming they're topologically sorted - parents before children)\n for (let i = 0; i < this.joints.length; i++) {\n const lt = this.localTransforms[i]\n const worldMat = this.worldMatrices[i]\n const parentIndex = parentIndices[i]\n\n // Build local matrix\n const localMat = mat4.create()\n mat4.fromRotationTranslationScale(localMat, lt.rotation, lt.position, lt.scale)\n\n // Combine with parent\n if (parentIndex >= 0) {\n mat4.multiply(worldMat, this.worldMatrices[parentIndex], localMat)\n } else {\n mat4.copy(worldMat, localMat)\n }\n }\n }\n\n /**\n * Get the number of joints\n */\n get numJoints() {\n return this.joints.length\n }\n\n /**\n * Get available animation names\n */\n getAnimationNames() {\n return Object.keys(this.animations)\n }\n\n /**\n * Clone this skin with its own joint texture but sharing animation data\n * This allows multiple instances to play at different phases independently\n * @param {boolean} individual - If true, create local transforms for independent animation/blending\n * @returns {Skin} A new Skin instance\n */\n clone(individual = false) {\n const { device } = this.engine\n\n const clonedSkin = new Skin(this.engine)\n\n // Share references to joints hierarchy and animations (read-only during playback)\n clonedSkin.joints = this.joints\n clonedSkin.inverseBindMatrices = this.inverseBindMatrices\n clonedSkin.animations = this.animations\n clonedSkin.rootNode = this.rootNode\n clonedSkin.speed = this.speed\n clonedSkin.loop = this.loop\n clonedSkin.currentAnimation = this.currentAnimation\n clonedSkin.blendDuration = this.blendDuration\n\n // Mark as externally managed - GBufferPass should skip calling update()\n clonedSkin.externallyManaged = true\n\n // Create own joint matrices array (these get modified during update)\n clonedSkin.jointMatrices = []\n clonedSkin.jointData = new Float32Array(this.joints.length * 16)\n\n for (let i = 0; i < this.joints.length; i++) {\n const jointMatrix = new Float32Array(clonedSkin.jointData.buffer, i * 16 * 4, 16)\n mat4.identity(jointMatrix)\n clonedSkin.jointMatrices.push(jointMatrix)\n }\n\n // Create own GPU texture for joint matrices\n clonedSkin.jointTexture = device.createTexture({\n size: [4, this.joints.length, 1],\n format: 'rgba32float',\n usage: GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_DST,\n })\n\n clonedSkin.jointTextureView = clonedSkin.jointTexture.createView()\n\n // Create previous frame joint texture for motion vectors\n clonedSkin.prevJointData = new Float32Array(this.joints.length * 16)\n clonedSkin.prevJointTexture = device.createTexture({\n size: [4, this.joints.length, 1],\n format: 'rgba32float',\n usage: GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_DST,\n })\n clonedSkin.prevJointTextureView = clonedSkin.prevJointTexture.createView()\n\n // Create own sampler\n clonedSkin.jointSampler = device.createSampler({\n magFilter: 'nearest',\n minFilter: 'nearest',\n })\n\n clonedSkin.time = this.time\n\n // For individual skins, initialize local transforms for independent animation/blending\n if (individual) {\n clonedSkin.initLocalTransforms()\n }\n\n return clonedSkin\n }\n\n /**\n * Create an individual clone with its own animation state and blending support\n * Use this for entities that need smooth animation transitions (close to camera)\n * @returns {Skin} A new independent Skin instance\n */\n cloneForIndividual() {\n return this.clone(true)\n }\n\n /**\n * Update animation at a specific time (absolute, not delta)\n * Used for phase-offset playback where multiple skins share the same animation\n * @param {number} absoluteTime - Absolute time in the animation\n */\n updateAtTime(absoluteTime) {\n const { device } = this.engine\n\n // Copy current joint data to previous BEFORE updating (for motion vectors)\n if (this.prevJointData && this.jointData) {\n this.prevJointData.set(this.jointData)\n }\n\n // Apply current animation if any\n if (this.currentAnimation && this.animations[this.currentAnimation]) {\n const anim = this.animations[this.currentAnimation]\n\n // Handle looping\n let t = absoluteTime\n if (this.loop && anim.duration > 0) {\n t = t % anim.duration\n } else {\n t = Math.min(t, anim.duration)\n }\n\n // Apply animation to joints\n this._applyAnimation(anim, t)\n }\n\n // Update world matrices starting from root\n if (this.rootNode) {\n this.rootNode.updateMatrix()\n }\n\n // Calculate final joint matrices: jointMatrix = worldMatrix * inverseBindMatrix\n for (let i = 0; i < this.joints.length; i++) {\n const joint = this.joints[i]\n const dst = this.jointMatrices[i]\n\n // dst = joint.world * inverseBindMatrix\n mat4.multiply(dst, joint.world, this.inverseBindMatrices[i])\n }\n\n // Upload previous joint matrices to GPU (for motion vectors)\n if (this.prevJointTexture && this.prevJointData) {\n device.queue.writeTexture(\n { texture: this.prevJointTexture },\n this.prevJointData,\n { bytesPerRow: 4 * 4 * 4, rowsPerImage: this.joints.length },\n [4, this.joints.length, 1]\n )\n }\n\n // Upload current joint matrices to GPU\n device.queue.writeTexture(\n { texture: this.jointTexture },\n this.jointData,\n { bytesPerRow: 4 * 4 * 4, rowsPerImage: this.joints.length },\n [4, this.joints.length, 1]\n )\n }\n}\n\n/**\n * Joint node for skeletal animation\n * Represents a bone in the skeleton hierarchy\n */\nclass Joint {\n constructor(name = 'joint') {\n this.name = name\n this.position = vec3.create()\n this.rotation = quat.create()\n this.scale = vec3.fromValues(1, 1, 1)\n this.children = []\n this.parent = null\n this.skin = null\n\n // Matrices\n this.matrix = mat4.create() // Local transform\n this.world = mat4.create() // World transform (accumulated from parents)\n\n // Original pose (bind pose)\n this.bindPosition = vec3.create()\n this.bindRotation = quat.create()\n this.bindScale = vec3.fromValues(1, 1, 1)\n }\n\n /**\n * Set the local transform from a matrix\n */\n setMatrix(m) {\n mat4.getTranslation(this.position, m)\n mat4.getRotation(this.rotation, m)\n mat4.getScaling(this.scale, m)\n }\n\n /**\n * Save current pose as bind pose\n */\n saveBindPose() {\n vec3.copy(this.bindPosition, this.position)\n quat.copy(this.bindRotation, this.rotation)\n vec3.copy(this.bindScale, this.scale)\n }\n\n /**\n * Reset to bind pose\n */\n resetToBindPose() {\n vec3.copy(this.position, this.bindPosition)\n quat.copy(this.rotation, this.bindRotation)\n vec3.copy(this.scale, this.bindScale)\n }\n\n /**\n * Add a child joint\n */\n addChild(child) {\n child.parent = this\n this.children.push(child)\n }\n\n /**\n * Update local and world matrices\n * @param {mat4} parentWorld - Parent's world matrix (optional)\n */\n updateMatrix(parentWorld = null) {\n // Build local matrix from TRS\n mat4.fromRotationTranslationScale(this.matrix, this.rotation, this.position, this.scale)\n\n // Combine with parent world matrix\n if (parentWorld) {\n mat4.multiply(this.world, parentWorld, this.matrix)\n } else {\n mat4.copy(this.world, this.matrix)\n }\n\n // Update children\n for (const child of this.children) {\n child.updateMatrix(this.world)\n }\n }\n}\n\nexport { Skin, Joint }\n","import { parse } from '@loaders.gl/core';\nimport { GLTFLoader } from '@loaders.gl/gltf';\nimport { Geometry } from \"./Geometry.js\"\nimport { Texture } from \"./Texture.js\"\nimport { Material } from \"./Material.js\"\nimport { Mesh } from \"./Mesh.js\"\nimport { Skin, Joint } from \"./Skin.js\"\n\n/**\n * Expand triangles by moving vertices away from triangle centroids\n * This creates small overlaps to eliminate gaps between adjacent triangles\n * @param {Object} attributes - Mesh attributes (position, normal, uv, indices, etc.)\n * @param {number} expansion - Expansion distance in model units (e.g., 0.025 meters)\n * @returns {Object} New attributes with expanded triangles (vertices duplicated per triangle)\n */\nfunction expandTriangles(attributes, expansion) {\n const positions = attributes.position\n const normals = attributes.normal\n const tangents = attributes.tangent\n const uvs = attributes.uv\n const indices = attributes.indices\n const weights = attributes.weights\n const joints = attributes.joints\n\n if (!positions || !indices) return attributes\n\n const triCount = indices.length / 3\n\n // Create new arrays - each triangle gets its own 3 vertices\n const newPositions = new Float32Array(triCount * 3 * 3)\n const newNormals = normals ? new Float32Array(triCount * 3 * 3) : null\n const newTangents = tangents ? new Float32Array(triCount * 3 * 4) : null\n const newUvs = uvs ? new Float32Array(triCount * 3 * 2) : null\n const newWeights = weights ? new Float32Array(triCount * 3 * 4) : null\n const newJoints = joints ? new Uint16Array(triCount * 3 * 4) : null\n const newIndices = new Uint32Array(triCount * 3)\n\n for (let t = 0; t < triCount; t++) {\n const i0 = indices[t * 3 + 0]\n const i1 = indices[t * 3 + 1]\n const i2 = indices[t * 3 + 2]\n\n // Get original positions\n const p0 = [positions[i0 * 3], positions[i0 * 3 + 1], positions[i0 * 3 + 2]]\n const p1 = [positions[i1 * 3], positions[i1 * 3 + 1], positions[i1 * 3 + 2]]\n const p2 = [positions[i2 * 3], positions[i2 * 3 + 1], positions[i2 * 3 + 2]]\n\n // Calculate centroid\n const cx = (p0[0] + p1[0] + p2[0]) / 3\n const cy = (p0[1] + p1[1] + p2[1]) / 3\n const cz = (p0[2] + p1[2] + p2[2]) / 3\n\n // Expand each vertex away from centroid\n for (let v = 0; v < 3; v++) {\n const origIdx = [i0, i1, i2][v]\n const p = [p0, p1, p2][v]\n const newIdx = t * 3 + v\n\n // Direction from centroid to vertex\n let dx = p[0] - cx\n let dy = p[1] - cy\n let dz = p[2] - cz\n const len = Math.sqrt(dx * dx + dy * dy + dz * dz)\n\n if (len > 0.0001) {\n dx /= len\n dy /= len\n dz /= len\n }\n\n // Expanded position\n newPositions[newIdx * 3 + 0] = p[0] + dx * expansion\n newPositions[newIdx * 3 + 1] = p[1] + dy * expansion\n newPositions[newIdx * 3 + 2] = p[2] + dz * expansion\n\n // Copy normals\n if (newNormals) {\n newNormals[newIdx * 3 + 0] = normals[origIdx * 3 + 0]\n newNormals[newIdx * 3 + 1] = normals[origIdx * 3 + 1]\n newNormals[newIdx * 3 + 2] = normals[origIdx * 3 + 2]\n }\n\n // Copy tangents (vec4: xyz = tangent direction, w = handedness)\n if (newTangents) {\n newTangents[newIdx * 4 + 0] = tangents[origIdx * 4 + 0]\n newTangents[newIdx * 4 + 1] = tangents[origIdx * 4 + 1]\n newTangents[newIdx * 4 + 2] = tangents[origIdx * 4 + 2]\n newTangents[newIdx * 4 + 3] = tangents[origIdx * 4 + 3]\n }\n\n // Copy UVs\n if (newUvs) {\n newUvs[newIdx * 2 + 0] = uvs[origIdx * 2 + 0]\n newUvs[newIdx * 2 + 1] = uvs[origIdx * 2 + 1]\n }\n\n // Copy weights\n if (newWeights) {\n newWeights[newIdx * 4 + 0] = weights[origIdx * 4 + 0]\n newWeights[newIdx * 4 + 1] = weights[origIdx * 4 + 1]\n newWeights[newIdx * 4 + 2] = weights[origIdx * 4 + 2]\n newWeights[newIdx * 4 + 3] = weights[origIdx * 4 + 3]\n }\n\n // Copy joints\n if (newJoints) {\n newJoints[newIdx * 4 + 0] = joints[origIdx * 4 + 0]\n newJoints[newIdx * 4 + 1] = joints[origIdx * 4 + 1]\n newJoints[newIdx * 4 + 2] = joints[origIdx * 4 + 2]\n newJoints[newIdx * 4 + 3] = joints[origIdx * 4 + 3]\n }\n\n // New indices are sequential\n newIndices[newIdx] = newIdx\n }\n }\n\n return {\n position: newPositions,\n normal: newNormals,\n tangent: newTangents,\n uv: newUvs,\n indices: newIndices,\n weights: newWeights,\n joints: newJoints\n }\n}\n\nasync function loadGltfData(engine, url, options = {}) {\n options = {\n scale: 1.0,\n rotation: [0, 0, 0],\n expandTriangles: 0, // Expansion distance in model units (0 = disabled)\n ...options\n }\n const gltf = await parse(fetch(url), GLTFLoader)\n //console.log('loaded gltf '+url, gltf)\n // Transform GLTF into desired format\n const meshes = {};\n const nodes = [];\n const skins = [];\n const animations = [];\n\n function getTexture(texture_id) {\n let tex = gltf.json.textures[texture_id]\n let filter = 'linear'\n let wrapS = 'mirror-repeat' // Default to mirror-repeat to avoid black borders\n let wrapT = 'mirror-repeat'\n if (tex.sampler !== undefined) {\n const samplerData = gltf.json.samplers[tex.sampler]\n // Filter mode\n if (samplerData.magFilter === 9728) filter = 'nearest'\n else if (samplerData.magFilter === 9729) filter = 'linear'\n // Wrap modes (GLTF: 33071=CLAMP, 33648=MIRROR, 10497=REPEAT)\n // Default to mirror-repeat unless explicitly set to repeat\n if (samplerData.wrapS === 10497) wrapS = 'repeat'\n else if (samplerData.wrapS === 33648) wrapS = 'mirror-repeat'\n else wrapS = 'mirror-repeat' // CLAMP or undefined -> mirror\n if (samplerData.wrapT === 10497) wrapT = 'repeat'\n else if (samplerData.wrapT === 33648) wrapT = 'mirror-repeat'\n else wrapT = 'mirror-repeat' // CLAMP or undefined -> mirror\n }\n let image = gltf.images[tex.source]\n // Get image URI from GLTF JSON (for sourceUrl tracking)\n let imageUri = gltf.json.images?.[tex.source]?.uri || null\n return {image: image, filter: filter, wrapS: wrapS, wrapT: wrapT, uri: imageUri}\n }\n\n function getTextures(property) {\n let textures = {}\n for (const [key, value] of Object.entries(property)) {\n if (key.includes('Texture') && value.index !== undefined) {\n property[key] = getTexture(value.index)\n }\n }\n return property\n }\n\n function getMaterial(material_id) {\n if (material_id === undefined) {\n return {\n pbrMetallicRoughness: {\n baseColorFactor: [1, 1, 1, 1],\n metallicFactor: 0,\n roughnessFactor: 1\n }\n }\n }\n let mat = gltf.json.materials[material_id]\n mat = getTextures(mat)\n if (mat.pbrMetallicRoughness) {\n mat.pbrMetallicRoughness = getTextures(mat.pbrMetallicRoughness)\n }\n return mat\n }\n\n function getAccessor(accessor_id, name) {\n if (accessor_id == undefined) return false\n let acc = gltf.json.accessors[accessor_id]\n let bufferView = gltf.json.bufferViews[acc.bufferView]\n let buffer = gltf.buffers[bufferView.buffer]\n\n // Get number of components based on type\n let numComponents = 0\n switch(acc.type) {\n case 'SCALAR': numComponents = 1; break;\n case 'VEC2': numComponents = 2; break;\n case 'VEC3': numComponents = 3; break;\n case 'VEC4': numComponents = 4; break;\n case 'MAT2': numComponents = 4; break;\n case 'MAT3': numComponents = 9; break;\n case 'MAT4': numComponents = 16; break;\n }\n\n // Create typed array based on componentType\n let byteOffset = (bufferView.byteOffset || 0) + (acc.byteOffset || 0) + (buffer.byteOffset || 0)\n let ab = buffer.arrayBuffer\n switch(acc.componentType) {\n case 5120: // BYTE\n acc.data = new Int8Array(ab, byteOffset, acc.count * numComponents)\n break\n case 5121: // UNSIGNED_BYTE\n acc.data = new Uint8Array(ab, byteOffset, acc.count * numComponents)\n break\n case 5122: // SHORT\n acc.data = new Int16Array(ab, byteOffset, acc.count * numComponents)\n break\n case 5123: // UNSIGNED_SHORT\n acc.data = new Uint16Array(ab, byteOffset, acc.count * numComponents)\n break\n case 5125: // UNSIGNED_INT\n acc.data = new Uint32Array(ab, byteOffset, acc.count * numComponents)\n break\n case 5126: // FLOAT\n acc.data = new Float32Array(ab, byteOffset, acc.count * numComponents)\n break\n }\n return acc.data\n }\n\n // Parse all nodes first\n if (gltf.json.nodes) {\n for (let i = 0; i < gltf.json.nodes.length; i++) {\n const nodeData = gltf.json.nodes[i]\n const joint = new Joint(nodeData.name || `node_${i}`)\n\n // Set transform\n if (nodeData.matrix) {\n joint.setMatrix(new Float32Array(nodeData.matrix))\n } else {\n if (nodeData.translation) {\n vec3.set(joint.position, nodeData.translation[0], nodeData.translation[1], nodeData.translation[2])\n }\n if (nodeData.rotation) {\n quat.set(joint.rotation, nodeData.rotation[0], nodeData.rotation[1], nodeData.rotation[2], nodeData.rotation[3])\n }\n if (nodeData.scale) {\n vec3.set(joint.scale, nodeData.scale[0], nodeData.scale[1], nodeData.scale[2])\n }\n }\n\n joint.saveBindPose()\n joint.nodeIndex = i\n nodes.push(joint)\n }\n\n // Build parent-child relationships\n for (let i = 0; i < gltf.json.nodes.length; i++) {\n const nodeData = gltf.json.nodes[i]\n if (nodeData.children) {\n for (const childIndex of nodeData.children) {\n nodes[i].addChild(nodes[childIndex])\n }\n }\n }\n }\n\n // Parse skins\n if (gltf.json.skins) {\n for (const skinData of gltf.json.skins) {\n const skin = new Skin(engine)\n\n // Get joint nodes\n const jointNodes = skinData.joints.map(jointIndex => nodes[jointIndex])\n\n // Get inverse bind matrices\n const inverseBindMatrices = getAccessor(skinData.inverseBindMatrices, 'inverseBindMatrices')\n\n // Find root node (skeleton)\n let rootNode = skinData.skeleton !== undefined ? nodes[skinData.skeleton] : jointNodes[0]\n // Find the topmost parent\n while (rootNode.parent) {\n rootNode = rootNode.parent\n }\n\n skin.init(jointNodes, inverseBindMatrices, rootNode)\n skins.push(skin)\n }\n }\n\n // Parse animations\n if (gltf.json.animations) {\n for (const animData of gltf.json.animations) {\n const animation = {\n name: animData.name || 'default',\n duration: 0,\n channels: []\n }\n\n // Parse samplers\n const samplers = animData.samplers.map(samplerData => {\n const input = getAccessor(samplerData.input, 'animation_input') // times\n const output = getAccessor(samplerData.output, 'animation_output') // values\n\n // Update animation duration\n if (input && input.length > 0) {\n animation.duration = Math.max(animation.duration, input[input.length - 1])\n }\n\n return {\n input: input,\n output: output,\n interpolation: samplerData.interpolation || 'LINEAR'\n }\n })\n\n // Parse channels\n for (const channelData of animData.channels) {\n const targetNode = nodes[channelData.target.node]\n const sampler = samplers[channelData.sampler]\n\n animation.channels.push({\n target: targetNode,\n path: channelData.target.path,\n sampler: sampler\n })\n }\n\n animations.push(animation)\n\n // Add animation to relevant skins\n for (const skin of skins) {\n skin.addAnimation(animation.name, animation)\n }\n }\n }\n\n // Parse meshes\n for (let mi = 0; mi < gltf.json.meshes.length; mi++) {\n const mesh = gltf.json.meshes[mi]\n\n // Find which node uses this mesh\n let meshNodeIndex = null\n let skinIndex = null\n if (gltf.json.nodes) {\n for (let ni = 0; ni < gltf.json.nodes.length; ni++) {\n if (gltf.json.nodes[ni].mesh === mi) {\n meshNodeIndex = ni\n skinIndex = gltf.json.nodes[ni].skin\n break\n }\n }\n }\n\n // Handle all primitives in the mesh (some GLTF files have multiple primitives per mesh)\n for (let pi = 0; pi < mesh.primitives.length; pi++) {\n const primitive = mesh.primitives[pi]\n const attributes = primitive.attributes;\n\n // Generate unique name: meshName_primitiveIndex or mesh_meshIndex_primitiveIndex\n const baseName = mesh.name || `mesh_${mi}`\n const meshName = mesh.primitives.length > 1 ? `${baseName}_${pi}` : baseName\n\n // Collect mesh attributes\n let attrs = {\n position: getAccessor(attributes.POSITION, 'position'),\n normal: getAccessor(attributes.NORMAL, 'normal'),\n tangent: getAccessor(attributes.TANGENT, 'tangent'),\n uv: getAccessor(attributes.TEXCOORD_0, 'uv'),\n indices: getAccessor(primitive.indices, 'indices'),\n weights: getAccessor(attributes.WEIGHTS_0, 'weights'),\n joints: getAccessor(attributes.JOINTS_0, 'joints')\n }\n\n // Apply triangle expansion if requested (helps eliminate gaps between triangles)\n if (options.expandTriangles > 0) {\n attrs = expandTriangles(attrs, options.expandTriangles)\n }\n\n meshes[meshName] = {\n attributes: attrs,\n material: getMaterial(primitive.material),\n scale: options.scale,\n rotation: options.rotation,\n skinIndex: skinIndex,\n nodeIndex: meshNodeIndex\n };\n }\n }\n\n return { meshes, nodes, skins, animations }\n}\n\nasync function loadGltf(engine, url, options = {}) {\n options = {\n flipY: false,\n ...options\n }\n const data = await loadGltfData(engine, url, options)\n const { meshes: mdata, skins, animations, nodes } = data\n const meshes = {}\n\n for (const name in mdata) {\n const mesh = mdata[name]\n const geometry = new Geometry(engine, mesh.attributes)\n const pbr = mesh.material.pbrMetallicRoughness || {}\n\n // Albedo/Base Color texture\n let albedo\n if (pbr.baseColorTexture) {\n const tex = pbr.baseColorTexture\n albedo = await Texture.fromImage(engine, tex.image, {\n srgb: true,\n flipY: options.flipY,\n addressModeU: tex.wrapS,\n addressModeV: tex.wrapT\n })\n if (tex.uri) albedo.sourceUrl = tex.uri\n } else {\n // Use baseColorFactor if available, otherwise white\n const bcf = pbr.baseColorFactor || [1, 1, 1, 1]\n albedo = await Texture.fromRGBA(engine, bcf[0], bcf[1], bcf[2], bcf[3])\n }\n\n // Normal map\n let normal\n if (mesh.material.normalTexture) {\n const tex = mesh.material.normalTexture\n normal = await Texture.fromImage(engine, tex.image, {\n srgb: false,\n flipY: options.flipY,\n addressModeU: tex.wrapS,\n addressModeV: tex.wrapT\n })\n if (tex.uri) normal.sourceUrl = tex.uri\n } else {\n normal = await Texture.fromColor(engine, \"#8080FF\")\n }\n\n // Metallic/Roughness texture\n // Format: R=ambient occlusion (if packed), G=roughness, B=metallic\n let rm\n if (pbr.metallicRoughnessTexture) {\n const tex = pbr.metallicRoughnessTexture\n rm = await Texture.fromImage(engine, tex.image, {\n srgb: false,\n flipY: options.flipY,\n addressModeU: tex.wrapS,\n addressModeV: tex.wrapT\n })\n if (tex.uri) rm.sourceUrl = tex.uri\n } else {\n // Use material factors or defaults\n // Default: roughness 0.9, metallic 0.0\n const roughness = pbr.roughnessFactor !== undefined ? pbr.roughnessFactor : 0.9\n const metallic = pbr.metallicFactor !== undefined ? pbr.metallicFactor : 0.0\n // R=1 (no AO in this channel), G=roughness, B=metallic\n rm = await Texture.fromRGBA(engine, 1.0, roughness, metallic, 1.0)\n }\n\n // Ambient Occlusion texture\n let ambient\n if (mesh.material.occlusionTexture) {\n const tex = mesh.material.occlusionTexture\n ambient = await Texture.fromImage(engine, tex.image, {\n srgb: false,\n flipY: options.flipY,\n addressModeU: tex.wrapS,\n addressModeV: tex.wrapT\n })\n if (tex.uri) ambient.sourceUrl = tex.uri\n } else {\n // Default: no occlusion (white = full light)\n ambient = await Texture.fromColor(engine, \"#FFFFFF\")\n }\n\n // Emission texture\n let emission\n if (mesh.material.emissiveTexture) {\n const tex = mesh.material.emissiveTexture\n emission = await Texture.fromImage(engine, tex.image, {\n srgb: true,\n flipY: options.flipY,\n addressModeU: tex.wrapS,\n addressModeV: tex.wrapT\n })\n if (tex.uri) emission.sourceUrl = tex.uri\n } else {\n // Use emissiveFactor if available, otherwise black (no emission)\n const ef = mesh.material.emissiveFactor || [0, 0, 0]\n emission = await Texture.fromRGBA(engine, ef[0], ef[1], ef[2], 1.0)\n }\n\n // Get material name from GLTF or generate from index\n const materialName = mesh.material.name || null\n const material = new Material([albedo, normal, ambient, rm, emission], {}, materialName)\n\n // Check GLTF alphaMode for transparency handling\n // \"MASK\" = alpha cutout (use alpha hashing for deferred)\n // \"BLEND\" = true alpha blending (requires forward transparent pass)\n // \"OPAQUE\" = fully opaque (default)\n const alphaMode = mesh.material.alphaMode || 'OPAQUE'\n if (alphaMode === 'MASK') {\n // Alpha cutout: use alpha hashing in deferred renderer\n material.alphaHash = true\n // GLTF alphaCutoff defaults to 0.5, use as scale factor\n const alphaCutoff = mesh.material.alphaCutoff ?? 0.5\n material.alphaHashScale = 1.0 / Math.max(alphaCutoff, 0.01)\n } else if (alphaMode === 'BLEND') {\n // True transparency: render in forward transparent pass\n material.transparent = true\n // Get base opacity from baseColorFactor alpha if available\n const bcf = pbr.baseColorFactor || [1, 1, 1, 1]\n material.opacity = bcf[3]\n }\n\n // Handle KHR_materials_transmission extension (for glass-like materials)\n if (mesh.material.extensions?.KHR_materials_transmission) {\n material.transparent = true\n const transmission = mesh.material.extensions.KHR_materials_transmission\n material.opacity = 1.0 - (transmission.transmissionFactor ?? 0)\n }\n\n // Handle double-sided materials\n if (mesh.material.doubleSided) {\n material.doubleSided = true\n }\n\n // Create mesh with name from GLTF (name is the key from mesh data)\n const nmesh = new Mesh(geometry, material, name)\n\n // Attach skin if this mesh has one\n if (mesh.skinIndex !== undefined && mesh.skinIndex !== null && skins[mesh.skinIndex]) {\n nmesh.skin = skins[mesh.skinIndex]\n nmesh.hasSkin = true\n }\n\n // Preserve node index for scene placement\n nmesh.nodeIndex = mesh.nodeIndex\n\n meshes[name] = nmesh\n }\n\n return { meshes, skins, animations, nodes }\n}\n\n// Simpler version that returns just meshes for backward compatibility\nasync function loadGltfMeshes(engine, url, options = {}) {\n const result = await loadGltf(engine, url, options)\n return result.meshes\n}\n\nexport { loadGltf, loadGltfMeshes, loadGltfData }\n","import { mat4, vec3, quat } from \"../math.js\"\n\n/**\n * EntityManager - Data-oriented entity storage and management\n *\n * Entities are plain objects with:\n * - position: [x, y, z] (Float64)\n * - rotation: [x, y, z, w] (Quaternion)\n * - scale: [x, y, z]\n * - model: \"path/to/model.glb|meshName\" (ModelID)\n * - animation: \"animationName\" (optional)\n * - phase: 0.0-1.0 (animation phase, optional)\n * - light: { enabled, position, direction, color, geom, animation } (optional)\n *\n * Sprite properties (for billboard rendering):\n * - sprite: \"texture.png|8\" (texture path | framesPerRow, optional)\n * - pivot: 'center' | 'bottom' | 'horizontal' (billboard mode)\n * - frame: number or \"start|end|fps\" (animation frame)\n * - roughness: 0-1 (material roughness, default 0.7)\n * - color: [r, g, b, a] (tint color, default [1,1,1,1])\n *\n * Particle properties (for GPU particle emitters):\n * - particles: ParticleEmitter config object or emitter UID\n * - _emitterUID: internal - UID of registered particle emitter\n *\n * Runtime fields (calculated):\n * - _bsphere: { center: [x,y,z], radius: r }\n * - _matrix: mat4\n * - _uvTransform: [offsetX, offsetY, scaleX, scaleY] (for sprite sheets)\n * - _animState: { currentAnim, time, blendFrom, blendFromTime, blendWeight, blendDuration, isBlending }\n */\nclass EntityManager {\n constructor() {\n // Main entity storage: id -> entity\n this.entities = {}\n\n // Indices for fast lookup\n this._byModel = {} // modelId -> Set<entityId>\n this._byAnimation = {} // modelId|animation -> Set<entityId>\n this._lights = new Set() // entityIds with lights\n this._particles = new Set() // entityIds with particle emitters\n\n // ID generation\n this._nextId = 1\n\n // Dirty tracking for batch updates\n this._dirtyEntities = new Set()\n this._dirtyLights = new Set()\n }\n\n /**\n * Generate a unique entity ID\n */\n _generateId() {\n return `e${this._nextId++}`\n }\n\n /**\n * Create a new entity\n * @param {Object} data - Entity data\n * @returns {string} Entity ID\n */\n create(data = {}) {\n const id = data.id || this._generateId()\n\n const entity = {\n position: data.position || [0, 0, 0],\n rotation: data.rotation || [0, 0, 0, 1], // identity quaternion\n scale: data.scale || [1, 1, 1],\n model: data.model || null,\n animation: data.animation || null,\n phase: data.phase || 0,\n light: data.light || null,\n noRounding: data.noRounding || false, // Exempt from pixel/position rounding\n\n // Sprite properties (for billboard rendering)\n sprite: data.sprite || null, // \"texture.png|8\" (texture|framesPerRow)\n pivot: data.pivot || 'center', // 'center', 'bottom', 'horizontal'\n frame: data.frame ?? 0, // integer or \"start|end|fps\"\n roughness: data.roughness ?? 0.7, // Material roughness (0-1)\n color: data.color || null, // [r, g, b, a] tint color (null = white)\n\n // Particle properties (for GPU particle emitters)\n particles: data.particles || null, // ParticleEmitter config or emitter UID\n _emitterUID: null, // Internal: UID of registered emitter\n\n // Runtime fields\n _bsphere: { center: [0, 0, 0], radius: 0 }, // radius 0 = not initialized\n _matrix: mat4.create(),\n _uvTransform: null, // [offsetX, offsetY, scaleX, scaleY] for sprites\n _visible: true,\n _dirty: true,\n\n // Animation state for blending (used by individual skins)\n _animState: {\n currentAnim: data.animation || null,\n time: (data.phase || 0) * 1.0, // Will be scaled by animation duration\n blendFrom: null,\n blendFromTime: 0,\n blendWeight: 1.0,\n blendDuration: 0.3,\n isBlending: false\n }\n }\n\n this.entities[id] = entity\n\n // Update indices\n if (entity.model) {\n this._addToModelIndex(id, entity.model)\n if (entity.animation) {\n this._addToAnimationIndex(id, entity.model, entity.animation)\n }\n }\n\n if (entity.light && entity.light.enabled) {\n this._lights.add(id)\n }\n\n if (entity.particles) {\n this._particles.add(id)\n }\n\n this._dirtyEntities.add(id)\n this._updateEntityMatrix(id)\n\n return id\n }\n\n /**\n * Get entity by ID\n */\n get(id) {\n return this.entities[id]\n }\n\n /**\n * Update entity properties\n */\n update(id, data) {\n const entity = this.entities[id]\n if (!entity) return false\n\n const oldModel = entity.model\n const oldAnimation = entity.animation\n\n // Update transform\n if (data.position) entity.position = data.position\n if (data.rotation) entity.rotation = data.rotation\n if (data.scale) entity.scale = data.scale\n\n // Update model reference\n if (data.model !== undefined && data.model !== oldModel) {\n if (oldModel) {\n this._removeFromModelIndex(id, oldModel)\n if (oldAnimation) {\n this._removeFromAnimationIndex(id, oldModel, oldAnimation)\n }\n }\n entity.model = data.model\n if (data.model) {\n this._addToModelIndex(id, data.model)\n }\n }\n\n // Update animation\n if (data.animation !== undefined && data.animation !== oldAnimation) {\n const model = data.model !== undefined ? data.model : entity.model\n if (oldAnimation && oldModel) {\n this._removeFromAnimationIndex(id, oldModel, oldAnimation)\n }\n entity.animation = data.animation\n if (data.animation && model) {\n this._addToAnimationIndex(id, model, data.animation)\n }\n }\n\n if (data.phase !== undefined) entity.phase = data.phase\n\n // Update light\n if (data.light !== undefined) {\n entity.light = data.light\n if (data.light && data.light.enabled) {\n this._lights.add(id)\n this._dirtyLights.add(id)\n } else {\n this._lights.delete(id)\n }\n }\n\n entity._dirty = true\n this._dirtyEntities.add(id)\n this._updateEntityMatrix(id)\n\n return true\n }\n\n /**\n * Delete entity\n */\n delete(id) {\n const entity = this.entities[id]\n if (!entity) return false\n\n // Remove from indices\n if (entity.model) {\n this._removeFromModelIndex(id, entity.model)\n if (entity.animation) {\n this._removeFromAnimationIndex(id, entity.model, entity.animation)\n }\n }\n\n this._lights.delete(id)\n this._particles.delete(id)\n this._dirtyEntities.delete(id)\n this._dirtyLights.delete(id)\n\n delete this.entities[id]\n return true\n }\n\n /**\n * Update entity's model matrix from transform\n */\n _updateEntityMatrix(id) {\n const entity = this.entities[id]\n if (!entity) return\n\n mat4.identity(entity._matrix)\n mat4.translate(entity._matrix, entity._matrix, entity.position)\n\n // Apply rotation from quaternion\n const rotMat = mat4.create()\n mat4.fromQuat(rotMat, entity.rotation)\n mat4.multiply(entity._matrix, entity._matrix, rotMat)\n\n mat4.scale(entity._matrix, entity._matrix, entity.scale)\n }\n\n /**\n * Update entity's bounding sphere (called when asset is loaded)\n */\n updateBoundingSphere(id, baseBsphere) {\n const entity = this.entities[id]\n if (!entity || !baseBsphere) return\n\n // Transform bsphere center by entity matrix\n const center = vec3.create()\n vec3.transformMat4(center, baseBsphere.center, entity._matrix)\n\n // Scale radius by max scale component\n const maxScale = Math.max(\n Math.abs(entity.scale[0]),\n Math.abs(entity.scale[1]),\n Math.abs(entity.scale[2])\n )\n\n entity._bsphere = {\n center: [center[0], center[1], center[2]],\n radius: baseBsphere.radius * maxScale\n }\n }\n\n /**\n * Get all entities using a specific model\n */\n getByModel(modelId) {\n const ids = this._byModel[modelId]\n if (!ids) return []\n return Array.from(ids).map(id => ({ id, entity: this.entities[id] }))\n }\n\n /**\n * Get all entities with a specific model and animation\n */\n getByModelAndAnimation(modelId, animation) {\n const key = `${modelId}|${animation}`\n const ids = this._byAnimation[key]\n if (!ids) return []\n return Array.from(ids).map(id => ({ id, entity: this.entities[id] }))\n }\n\n /**\n * Get all unique model IDs currently in use\n */\n getActiveModels() {\n return Object.keys(this._byModel)\n }\n\n /**\n * Get all entities with lights\n */\n getLights() {\n return Array.from(this._lights).map(id => ({ id, entity: this.entities[id] }))\n }\n\n /**\n * Get all entities with particle emitters\n */\n getParticles() {\n return Array.from(this._particles).map(id => ({ id, entity: this.entities[id] }))\n }\n\n /**\n * Get dirty entities and clear dirty flags\n */\n consumeDirtyEntities() {\n const dirty = Array.from(this._dirtyEntities)\n this._dirtyEntities.clear()\n for (const id of dirty) {\n const entity = this.entities[id]\n if (entity) entity._dirty = false\n }\n return dirty\n }\n\n /**\n * Get dirty lights and clear dirty flags\n */\n consumeDirtyLights() {\n const dirty = Array.from(this._dirtyLights)\n this._dirtyLights.clear()\n return dirty\n }\n\n /**\n * Set visibility for entity\n */\n setVisible(id, visible) {\n const entity = this.entities[id]\n if (entity) {\n entity._visible = visible\n }\n }\n\n /**\n * Get all visible entities\n */\n getVisible() {\n const result = []\n for (const id in this.entities) {\n if (this.entities[id]._visible) {\n result.push({ id, entity: this.entities[id] })\n }\n }\n return result\n }\n\n /**\n * Iterate over all entities\n */\n forEach(callback) {\n for (const id in this.entities) {\n callback(id, this.entities[id])\n }\n }\n\n /**\n * Get entity count\n */\n get count() {\n return Object.keys(this.entities).length\n }\n\n // Private index management methods\n\n _addToModelIndex(id, modelId) {\n if (!this._byModel[modelId]) {\n this._byModel[modelId] = new Set()\n }\n this._byModel[modelId].add(id)\n }\n\n _removeFromModelIndex(id, modelId) {\n if (this._byModel[modelId]) {\n this._byModel[modelId].delete(id)\n if (this._byModel[modelId].size === 0) {\n delete this._byModel[modelId]\n }\n }\n }\n\n _addToAnimationIndex(id, modelId, animation) {\n const key = `${modelId}|${animation}`\n if (!this._byAnimation[key]) {\n this._byAnimation[key] = new Set()\n }\n this._byAnimation[key].add(id)\n }\n\n _removeFromAnimationIndex(id, modelId, animation) {\n const key = `${modelId}|${animation}`\n if (this._byAnimation[key]) {\n this._byAnimation[key].delete(id)\n if (this._byAnimation[key].size === 0) {\n delete this._byAnimation[key]\n }\n }\n }\n\n /**\n * Set animation for entity with optional blending\n * @param {string} id - Entity ID\n * @param {string} animation - Animation name\n * @param {number} blendTime - Blend duration (0 = instant switch)\n */\n setAnimation(id, animation, blendTime = 0.3) {\n const entity = this.entities[id]\n if (!entity) return false\n\n const animState = entity._animState\n\n // If same animation, do nothing\n if (animState.currentAnim === animation) return true\n\n // Start blend if we have a current animation and blend time > 0\n if (blendTime > 0 && animState.currentAnim) {\n animState.blendFrom = animState.currentAnim\n animState.blendFromTime = animState.time\n animState.blendWeight = 0\n animState.blendDuration = blendTime\n animState.isBlending = true\n } else {\n animState.isBlending = false\n animState.blendFrom = null\n }\n\n animState.currentAnim = animation\n animState.time = 0\n\n // Also update the legacy animation field for compatibility\n const oldAnimation = entity.animation\n if (oldAnimation !== animation) {\n if (oldAnimation && entity.model) {\n this._removeFromAnimationIndex(id, entity.model, oldAnimation)\n }\n entity.animation = animation\n if (animation && entity.model) {\n this._addToAnimationIndex(id, entity.model, animation)\n }\n }\n\n return true\n }\n\n /**\n * Update animation time for entity\n * @param {string} id - Entity ID\n * @param {number} dt - Delta time\n */\n updateAnimationTime(id, dt) {\n const entity = this.entities[id]\n if (!entity) return\n\n const animState = entity._animState\n animState.time += dt\n\n if (animState.isBlending) {\n animState.blendFromTime += dt\n animState.blendWeight = Math.min(\n animState.blendWeight + dt / animState.blendDuration,\n 1.0\n )\n\n if (animState.blendWeight >= 1.0) {\n animState.isBlending = false\n animState.blendFrom = null\n animState.blendWeight = 1.0\n }\n }\n }\n\n /**\n * Check if entity is currently blending animations\n */\n isBlending(id) {\n const entity = this.entities[id]\n return entity?._animState?.isBlending || false\n }\n\n /**\n * Get animation state for entity\n */\n getAnimationState(id) {\n const entity = this.entities[id]\n return entity?._animState || null\n }\n\n /**\n * Clear all entities\n */\n clear() {\n this.entities = {}\n this._byModel = {}\n this._byAnimation = {}\n this._lights.clear()\n this._particles.clear()\n this._dirtyEntities.clear()\n this._dirtyLights.clear()\n }\n\n /**\n * Serialize entities for saving\n */\n serialize() {\n const data = {}\n for (const id in this.entities) {\n const e = this.entities[id]\n data[id] = {\n position: [...e.position],\n rotation: [...e.rotation],\n scale: [...e.scale],\n model: e.model,\n animation: e.animation,\n phase: e.phase,\n light: e.light ? { ...e.light } : null,\n // Sprite properties\n sprite: e.sprite,\n pivot: e.pivot,\n frame: e.frame,\n roughness: e.roughness,\n color: e.color ? [...e.color] : null,\n // Particle properties (serialize config, not runtime UID)\n particles: e.particles\n }\n }\n return data\n }\n\n /**\n * Deserialize entities from saved data\n */\n deserialize(data) {\n this.clear()\n for (const id in data) {\n this.create({ ...data[id], id })\n }\n }\n}\n\nexport { EntityManager }\n","import { loadGltfData, loadGltf } from \"../gltf.js\"\nimport { Geometry } from \"../Geometry.js\"\nimport { Material } from \"../Material.js\"\nimport { Mesh } from \"../Mesh.js\"\nimport { Texture } from \"../Texture.js\"\nimport { calculateBoundingSphere } from \"../utils/BoundingSphere.js\"\n\n/**\n * AssetManager - Lazy loading and caching of assets\n *\n * Asset keys:\n * - \"path/to/model.glb\" - Raw GLTF data (gltf object, ready state)\n * - \"path/to/model.glb|meshName\" - Processed mesh (geometry, material, skin, bsphere)\n *\n * Assets structure:\n * {\n * \"models/fox.glb\": { gltf: {...}, meshNames: [...], ready: true, loading: false }\n * \"models/fox.glb|fox1\": { geometry, material, skin, bsphere, ready: true }\n * }\n */\nclass AssetManager {\n constructor(engine = null) {\n this.engine = engine\n\n // Asset storage\n this.assets = {}\n\n // Loading promises for deduplication\n this._loadingPromises = {}\n\n // Callbacks for when assets become ready\n this._readyCallbacks = {}\n }\n\n /**\n * Parse a ModelID into path and mesh name\n * @param {string} modelId - Format: \"path/to/model.glb|meshName\"\n * @returns {{ path: string, meshName: string|null }}\n */\n parseModelId(modelId) {\n const parts = modelId.split(\"|\")\n return {\n path: parts[0],\n meshName: parts[1] || null\n }\n }\n\n /**\n * Create a ModelID from path and mesh name\n */\n createModelId(path, meshName) {\n return `${path}|${meshName}`\n }\n\n /**\n * Check if an asset exists and is ready\n */\n isReady(assetKey) {\n return this.assets[assetKey]?.ready === true\n }\n\n /**\n * Check if an asset is currently loading\n */\n isLoading(assetKey) {\n return this.assets[assetKey]?.loading === true || this._loadingPromises[assetKey] !== undefined\n }\n\n /**\n * Get an asset if ready, otherwise return null\n */\n get(assetKey) {\n const asset = this.assets[assetKey]\n if (asset?.ready) {\n return asset\n }\n return null\n }\n\n /**\n * Get or load a GLTF file\n * @param {string} path - Path to the GLTF file\n * @param {Object} options - Loading options\n * @returns {Promise<Object>} The loaded GLTF asset\n */\n async loadGltfFile(path, options = {}) {\n // Check if already loaded\n if (this.assets[path]?.ready) {\n return this.assets[path]\n }\n\n // Check if already loading (deduplicate)\n if (this._loadingPromises[path]) {\n return this._loadingPromises[path]\n }\n\n // Mark as loading\n this.assets[path] = { ready: false, loading: true }\n\n // Create loading promise\n this._loadingPromises[path] = (async () => {\n try {\n const result = await loadGltf(this.engine, path, options)\n\n // Store the full result\n const meshNames = Object.keys(result.meshes)\n\n // For skinned models, compute a combined bounding sphere for ALL meshes\n // This prevents individual submeshes from being culled independently\n // Include all meshes (skinned and rigid parts) in the combined sphere\n let combinedBsphere = null\n const hasAnySkin = Object.values(result.meshes).some(m => m.hasSkin)\n\n if (hasAnySkin) {\n // Collect all vertex positions from ALL meshes (not just skinned ones)\n // This ensures rigid parts attached to skinned models share the same bounds\n const allPositions = []\n for (const mesh of Object.values(result.meshes)) {\n if (mesh.geometry?.attributes?.position) {\n const positions = mesh.geometry.attributes.position\n for (let i = 0; i < positions.length; i += 3) {\n allPositions.push(positions[i], positions[i + 1], positions[i + 2])\n }\n }\n }\n\n if (allPositions.length > 0) {\n combinedBsphere = calculateBoundingSphere(new Float32Array(allPositions))\n }\n }\n\n // Store the parent GLTF asset with combined bsphere for entities using parent path\n this.assets[path] = {\n gltf: result,\n meshes: result.meshes,\n skins: result.skins,\n animations: result.animations,\n nodes: result.nodes,\n meshNames: meshNames,\n bsphere: combinedBsphere, // Combined bsphere for parent path entities\n hasSkin: hasAnySkin, // Flag for skinned model detection\n ready: true,\n loading: false\n }\n\n // Auto-register individual meshes\n for (const meshName of meshNames) {\n const mesh = result.meshes[meshName]\n // Use combined bsphere for ALL meshes when model has any skinning\n // This ensures all submeshes are culled together as a unit\n const bsphere = (hasAnySkin && combinedBsphere) ? combinedBsphere : null\n await this._registerMesh(path, meshName, mesh, bsphere)\n }\n\n // Trigger ready callbacks\n this._triggerReady(path)\n\n return this.assets[path]\n } catch (error) {\n console.error(`Failed to load GLTF: ${path}`, error)\n this.assets[path] = { ready: false, loading: false, error: error.message }\n throw error\n } finally {\n delete this._loadingPromises[path]\n }\n })()\n\n return this._loadingPromises[path]\n }\n\n /**\n * Register a mesh asset (internal)\n * @param {string} path - GLTF file path\n * @param {string} meshName - Mesh name\n * @param {Object} mesh - Mesh object\n * @param {Object|null} overrideBsphere - Optional bounding sphere (for skinned mesh combined sphere)\n */\n async _registerMesh(path, meshName, mesh, overrideBsphere = null) {\n const modelId = this.createModelId(path, meshName)\n\n // Use override bsphere if provided, otherwise calculate from geometry\n const bsphere = overrideBsphere || calculateBoundingSphere(mesh.geometry.attributes.position)\n\n this.assets[modelId] = {\n mesh: mesh,\n geometry: mesh.geometry,\n material: mesh.material,\n skin: mesh.skin || null,\n hasSkin: mesh.hasSkin || false,\n bsphere: bsphere,\n ready: true,\n loading: false\n }\n\n // Trigger ready callbacks\n this._triggerReady(modelId)\n }\n\n /**\n * Get or load a specific mesh from a GLTF file\n * @param {string} modelId - Format: \"path/to/model.glb|meshName\"\n * @param {Object} options - Loading options\n * @returns {Promise<Object>} The mesh asset\n */\n async loadMesh(modelId, options = {}) {\n const { path, meshName } = this.parseModelId(modelId)\n\n // If mesh is already loaded, return it\n if (this.assets[modelId]?.ready) {\n return this.assets[modelId]\n }\n\n // Load the GLTF file first (will auto-register meshes)\n await this.loadGltfFile(path, options)\n\n // Now get the mesh\n const meshAsset = this.assets[modelId]\n if (!meshAsset) {\n throw new Error(`Mesh \"${meshName}\" not found in \"${path}\"`)\n }\n\n return meshAsset\n }\n\n /**\n * Preload multiple assets\n * @param {string[]} assetKeys - List of asset keys to preload\n * @param {Object} options - Loading options\n * @returns {Promise<void>}\n */\n async preload(assetKeys, options = {}) {\n const promises = assetKeys.map(key => {\n const { path, meshName } = this.parseModelId(key)\n if (meshName) {\n return this.loadMesh(key, options)\n } else {\n return this.loadGltfFile(key, options)\n }\n })\n\n await Promise.all(promises)\n }\n\n /**\n * Register a callback for when an asset becomes ready\n */\n onReady(assetKey, callback) {\n // If already ready, call immediately\n if (this.assets[assetKey]?.ready) {\n callback(this.assets[assetKey])\n return\n }\n\n // Register callback\n if (!this._readyCallbacks[assetKey]) {\n this._readyCallbacks[assetKey] = []\n }\n this._readyCallbacks[assetKey].push(callback)\n }\n\n /**\n * Trigger ready callbacks\n */\n _triggerReady(assetKey) {\n const callbacks = this._readyCallbacks[assetKey]\n if (callbacks) {\n for (const callback of callbacks) {\n callback(this.assets[assetKey])\n }\n delete this._readyCallbacks[assetKey]\n }\n }\n\n /**\n * Get all loaded mesh names for a GLTF file\n */\n getMeshNames(path) {\n const asset = this.assets[path]\n return asset?.meshNames || []\n }\n\n /**\n * Get all unique GLTF paths currently loaded\n */\n getLoadedPaths() {\n return Object.keys(this.assets).filter(key => !key.includes(\"|\") && this.assets[key].ready)\n }\n\n /**\n * Get all ModelIDs currently loaded\n */\n getLoadedModelIds() {\n return Object.keys(this.assets).filter(key => key.includes(\"|\") && this.assets[key].ready)\n }\n\n /**\n * Get bounding sphere for a model\n */\n getBoundingSphere(modelId) {\n const asset = this.assets[modelId]\n return asset?.bsphere || null\n }\n\n /**\n * Create a clone of a mesh (shares geometry/material, but separate instance buffers)\n */\n cloneMesh(modelId) {\n const asset = this.assets[modelId]\n if (!asset?.ready) {\n return null\n }\n\n // Create a new Mesh with the same geometry and material\n const clone = new Mesh(asset.geometry, asset.material)\n if (asset.skin) {\n clone.skin = asset.skin\n clone.hasSkin = true\n }\n return clone\n }\n\n /**\n * Unload an asset to free memory\n */\n unload(assetKey) {\n const asset = this.assets[assetKey]\n if (!asset) return\n\n // If this is a GLTF file, also unload all its meshes\n if (asset.meshNames) {\n for (const meshName of asset.meshNames) {\n const modelId = this.createModelId(assetKey, meshName)\n delete this.assets[modelId]\n }\n }\n\n delete this.assets[assetKey]\n }\n\n /**\n * Clear all assets\n */\n clear() {\n this.assets = {}\n this._loadingPromises = {}\n this._readyCallbacks = {}\n }\n\n /**\n * Get loading status for all assets\n */\n getStatus() {\n const ready = []\n const loading = []\n const failed = []\n\n for (const key in this.assets) {\n const asset = this.assets[key]\n if (asset.ready) {\n ready.push(key)\n } else if (asset.loading) {\n loading.push(key)\n } else if (asset.error) {\n failed.push({ key, error: asset.error })\n }\n }\n\n return { ready, loading, failed }\n }\n\n /**\n * Register a manually created mesh (for procedural geometry)\n */\n registerMesh(modelId, mesh, bsphere = null) {\n if (!bsphere) {\n bsphere = calculateBoundingSphere(mesh.geometry.attributes.position)\n }\n\n this.assets[modelId] = {\n mesh: mesh,\n geometry: mesh.geometry,\n material: mesh.material,\n skin: mesh.skin || null,\n hasSkin: mesh.hasSkin || false,\n bsphere: bsphere,\n ready: true,\n loading: false\n }\n\n this._triggerReady(modelId)\n }\n}\n\nexport { AssetManager }\n","import GUI from 'lil-gui'\n\n/**\n * DebugUI - Stats display and settings GUI for the renderer\n *\n * Created lazily on first debug mode activation.\n * Shows stats panel and collapsible settings folders.\n */\nclass DebugUI {\n constructor(engine) {\n this.engine = engine\n this.gui = null\n this.statsFolder = null\n this.initialized = false\n\n // Stats display elements\n this._statsController = null\n this._statsText = ''\n\n // Stats update throttling (5 updates per second)\n this._lastStatsUpdate = 0\n this._statsUpdateInterval = 200 // ms\n\n // Folder references for updating\n this.folders = {}\n }\n\n /**\n * Initialize the debug UI (called lazily on first debug mode)\n */\n init() {\n if (this.initialized) return\n\n this.gui = new GUI({ title: 'Debug Panel' })\n this.gui.close() // Start collapsed\n\n // Add CSS for stats styling\n this._addStyles()\n\n // Create stats folder at the top\n this._createStatsFolder()\n\n // Create settings folders\n this._createRenderingFolder()\n this._createCameraFolder()\n this._createEnvironmentFolder()\n this._createLightingFolder()\n this._createMainLightFolder()\n this._createShadowFolder()\n this._createPlanarReflectionFolder()\n this._createAOFolder()\n this._createAmbientCaptureFolder()\n this._createSSGIFolder()\n this._createVolumetricFogFolder()\n this._createBloomFolder()\n this._createTonemapFolder()\n this._createDitheringFolder()\n this._createCRTFolder()\n this._createDebugFolder()\n\n // Close all folders by default\n for (const folder of Object.values(this.folders)) {\n folder.close()\n }\n\n this.initialized = true\n }\n\n /**\n * Add custom CSS for the debug UI\n */\n _addStyles() {\n if (document.getElementById('debug-ui-styles')) return\n\n const style = document.createElement('style')\n style.id = 'debug-ui-styles'\n style.textContent = `\n .lil-gui .stats-display {\n font-family: monospace;\n font-size: 11px;\n line-height: 1.4;\n white-space: pre;\n padding: 4px 8px;\n background: rgba(0, 0, 0, 0.3);\n border-radius: 3px;\n color: #8f8;\n }\n .lil-gui .stats-title {\n display: flex;\n align-items: center;\n gap: 8px;\n }\n .lil-gui .stats-title::before {\n content: '📊';\n }\n `\n document.head.appendChild(style)\n }\n\n /**\n * Create stats folder with live updating display\n */\n _createStatsFolder() {\n this.statsFolder = this.gui.addFolder('Stats')\n this.statsFolder.$title.classList.add('stats-title')\n\n // Create a custom stats display element\n const statsDiv = document.createElement('div')\n statsDiv.className = 'stats-display'\n statsDiv.textContent = 'Loading...'\n this._statsElement = statsDiv\n\n // Add to folder\n this.statsFolder.$children.appendChild(statsDiv)\n\n // Keep stats folder open by default\n this.statsFolder.open()\n }\n\n /**\n * Update stats display (throttled to 5 updates/sec, only when panel is open)\n */\n updateStats() {\n if (!this._statsElement || !this.engine.stats) return\n\n // Only update if GUI panel is open\n if (this.gui?._closed) return\n\n // Throttle updates to 5 per second\n const now = performance.now()\n if (now - this._lastStatsUpdate < this._statsUpdateInterval) return\n this._lastStatsUpdate = now\n\n const s = this.engine.stats\n const fmt = this._fmt.bind(this)\n\n // Basic timing stats\n const fps = s.avg_fps?.toFixed(0) || '0'\n const ms = s.avg_dt?.toFixed(1) || '0'\n const renderMs = s.avg_dt_render?.toFixed(2) || '0'\n\n // Draw calls breakdown\n const dc = s.totalDC || s.drawCalls || 0\n const dcParts = []\n if (s.shadowDC) dcParts.push(`sh:${fmt(s.shadowDC)}`)\n if (s.planarDC) dcParts.push(`pl:${fmt(s.planarDC)}`)\n if (s.transparentDC) dcParts.push(`tr:${fmt(s.transparentDC)}`)\n const dcBreakdown = dcParts.length > 0 ? ` (${dcParts.join(' ')})` : ''\n\n // Triangles breakdown\n const tri = s.totalTri || s.triangles || 0\n const triStr = this._formatNumber(tri)\n const triParts = []\n if (s.shadowTri) triParts.push(`sh:${this._formatNumber(s.shadowTri)}`)\n if (s.planarTri) triParts.push(`pl:${this._formatNumber(s.planarTri)}`)\n if (s.transparentTri) triParts.push(`tr:${this._formatNumber(s.transparentTri)}`)\n const triBreakdown = triParts.length > 0 ? ` (${triParts.join(' ')})` : ''\n\n // Get renderer stats\n const renderer = this.engine.renderer\n const lightingStats = renderer?.getPass?.('lighting')?.stats || {}\n const gbufferStats = renderer?.getPass?.('gbuffer')?.legacyCullingStats || {}\n const occStats = renderer?.cullingSystem?.getOcclusionStats?.() || {}\n const rendererStats = renderer?.stats || {}\n\n // Entity culling stats\n const visibleEntities = rendererStats.visibleEntities || 0\n const occCulled = occStats.culled || 0\n\n // Mesh culling stats\n const meshRendered = gbufferStats.rendered || 0\n const meshTotal = gbufferStats.total || 0\n const meshOccCulled = gbufferStats.culledByOcclusion || 0\n const meshSkipped = gbufferStats.skippedNoBsphere || 0\n\n // Shadow entity culling\n const shEntCull = []\n if (s.shadowFrustumCulled) shEntCull.push(`fr:${fmt(s.shadowFrustumCulled)}`)\n if (s.shadowDistanceCulled) shEntCull.push(`dist:${fmt(s.shadowDistanceCulled)}`)\n if (s.shadowHiZCulled) shEntCull.push(`hiz:${fmt(s.shadowHiZCulled)}`)\n if (s.shadowPixelCulled) shEntCull.push(`px:${fmt(s.shadowPixelCulled)}`)\n const shEntStr = shEntCull.length > 0 ? ` ${shEntCull.join(' ')}` : ''\n\n // Shadow mesh culling\n const shMeshCull = []\n if (s.shadowMeshFrustumCulled) shMeshCull.push(`fr:${fmt(s.shadowMeshFrustumCulled)}`)\n if (s.shadowMeshDistanceCulled) shMeshCull.push(`dist:${fmt(s.shadowMeshDistanceCulled)}`)\n if (s.shadowMeshOcclusionCulled) shMeshCull.push(`occ:${fmt(s.shadowMeshOcclusionCulled)}`)\n const shMeshStr = shMeshCull.length > 0 ? ` ${shMeshCull.join(' ')}` : ''\n\n // Lights stats\n const visibleLights = lightingStats.visibleLights || 0\n const lightOccCulled = lightingStats.culledByOcclusion || 0\n\n this._statsElement.textContent =\n `FPS: ${fps} (${ms}ms) render: ${renderMs}ms\\n` +\n `DC: ${fmt(dc)}${dcBreakdown}\\n` +\n `Tri: ${triStr}${triBreakdown}\\n` +\n `Entities: ${fmt(visibleEntities)} (occ:${fmt(occCulled)})\\n` +\n `Meshes: ${fmt(meshRendered)}/${fmt(meshTotal)} (occ:${fmt(meshOccCulled)} skip:${fmt(meshSkipped)})\\n` +\n `Shadow ent:${shEntStr || ' -'} mesh:${shMeshStr || ' -'}\\n` +\n `Lights: ${fmt(visibleLights)} (occ:${fmt(lightOccCulled)})`\n }\n\n /**\n * Format number with thin space as thousand separator\n */\n _fmt(n) {\n if (n === undefined || n === null) return '0'\n return n.toString().replace(/\\B(?=(\\d{3})+(?!\\d))/g, '\\u2009')\n }\n\n /**\n * Format large numbers with K/M suffix\n */\n _formatNumber(n) {\n if (n > 1000000) return (n / 1000000).toFixed(1) + 'M'\n if (n > 1000) return (n / 1000).toFixed(0) + 'K'\n return n.toString()\n }\n\n _createRenderingFolder() {\n const s = this.engine.settings\n const folder = this.gui.addFolder('Rendering')\n this.folders.rendering = folder\n\n folder.add(s.rendering, 'renderScale', 0.25, 2.0, 0.25).name('Render Scale')\n .onChange(() => this.engine.needsResize = true)\n folder.add(s.rendering.autoScale, 'enabled').name('Auto-Scale (4K)')\n .onChange(() => this.engine.needsResize = true)\n folder.add(s.rendering.autoScale, 'maxHeight', 720, 2160, 1).name('Max Height')\n folder.add(s.rendering.autoScale, 'scaleFactor', 0.25, 1.0, 0.05).name('Scale Factor')\n folder.add(s.rendering, 'fxaa').name('FXAA')\n folder.add(s.rendering, 'jitter').name('TAA Jitter')\n if (s.rendering.jitterAmount !== undefined) {\n folder.add(s.rendering, 'jitterAmount', 0, 1, 0.01).name('Jitter Amount')\n }\n if (s.rendering.jitterFadeDistance !== undefined) {\n folder.add(s.rendering, 'jitterFadeDistance', 5, 100, 1).name('Jitter Fade Dist')\n }\n folder.add(s.rendering, 'debug').name('Debug Mode')\n .onChange((value) => {\n // Immediately hide panel when debug mode is turned off\n if (!value && this.gui) {\n this.gui.domElement.style.display = 'none'\n }\n })\n\n // Culling subfolder\n if (s.culling) {\n const cullFolder = folder.addFolder('Culling')\n cullFolder.add(s.culling, 'frustumEnabled').name('Frustum Culling')\n\n if (s.occlusionCulling) {\n cullFolder.add(s.occlusionCulling, 'enabled').name('Occlusion Culling')\n cullFolder.add(s.occlusionCulling, 'threshold', 0.1, 2.0, 0.1).name('Occlusion Threshold')\n }\n\n // Planar reflection culling sub-folder\n if (s.culling.planarReflection) {\n const prFolder = cullFolder.addFolder('Planar Reflection')\n prFolder.add(s.culling.planarReflection, 'frustum').name('Frustum Culling')\n prFolder.add(s.culling.planarReflection, 'maxDistance', 10, 200, 10).name('Max Distance')\n prFolder.add(s.culling.planarReflection, 'maxSkinned', 0, 100, 1).name('Max Skinned')\n prFolder.add(s.culling.planarReflection, 'minPixelSize', 0, 16, 1).name('Min Pixel Size')\n prFolder.close()\n }\n cullFolder.close()\n }\n\n // Noise subfolder\n if (s.noise) {\n const noiseFolder = folder.addFolder('Noise')\n noiseFolder.add(s.noise, 'type', ['bluenoise', 'bayer8']).name('Type')\n .onChange(() => {\n // Reload noise texture when type changes\n if (this.engine.renderer?.renderGraph) {\n this.engine.renderer.renderGraph.reloadNoiseTexture()\n }\n })\n noiseFolder.add(s.noise, 'animated').name('Animated')\n noiseFolder.close()\n }\n }\n\n _createAOFolder() {\n const s = this.engine.settings\n if (!s.ao) return\n\n const folder = this.gui.addFolder('Ambient Occlusion')\n this.folders.ao = folder\n\n folder.add(s.ao, 'enabled').name('Enabled')\n folder.add(s.ao, 'intensity', 0, 2, 0.1).name('Intensity')\n folder.add(s.ao, 'radius', 8, 128, 1).name('Radius')\n folder.add(s.ao, 'fadeDistance', 10, 100, 1).name('Fade Distance')\n folder.add(s.ao, 'bias', 0, 0.05, 0.001).name('Bias')\n folder.add(s.ao, 'level', 0, 1, 0.05).name('Level')\n }\n\n _createShadowFolder() {\n const s = this.engine.settings\n if (!s.shadow) return\n\n const folder = this.gui.addFolder('Shadows')\n this.folders.shadow = folder\n\n folder.add(s.shadow, 'strength', 0, 1, 0.05).name('Strength')\n if (s.shadow.spotMaxDistance !== undefined) {\n folder.add(s.shadow, 'spotMaxDistance', 10, 200, 5).name('Spot Max Distance')\n }\n if (s.shadow.spotFadeStart !== undefined) {\n folder.add(s.shadow, 'spotFadeStart', 10, 150, 5).name('Spot Fade Start')\n }\n folder.add(s.shadow, 'bias', 0, 0.01, 0.0001).name('Bias')\n folder.add(s.shadow, 'normalBias', 0, 0.05, 0.001).name('Normal Bias')\n folder.add(s.shadow, 'surfaceBias', 0, 0.1, 0.001).name('Surface Bias')\n }\n\n _createMainLightFolder() {\n const s = this.engine.settings\n if (!s.mainLight) return\n\n const folder = this.gui.addFolder('Main Light')\n this.folders.mainLight = folder\n\n folder.add(s.mainLight, 'enabled').name('Enabled')\n folder.add(s.mainLight, 'intensity', 0, 2, 0.05).name('Intensity')\n\n // Color picker\n folder.addColor({ color: this._rgbToHex(s.mainLight.color) }, 'color')\n .name('Color')\n .onChange((hex) => {\n const rgb = this._hexToRgb(hex)\n s.mainLight.color = [rgb.r, rgb.g, rgb.b]\n })\n\n // Direction controls\n const dirProxy = {\n x: s.mainLight.direction[0],\n y: s.mainLight.direction[1],\n z: s.mainLight.direction[2]\n }\n const updateDir = () => {\n s.mainLight.direction = [dirProxy.x, dirProxy.y, dirProxy.z]\n }\n folder.add(dirProxy, 'x', -1, 1, 0.05).name('Direction X').onChange(updateDir)\n folder.add(dirProxy, 'y', -1, 1, 0.05).name('Direction Y').onChange(updateDir)\n folder.add(dirProxy, 'z', -1, 1, 0.05).name('Direction Z').onChange(updateDir)\n }\n\n _createEnvironmentFolder() {\n const s = this.engine.settings\n if (!s.environment) return\n\n const folder = this.gui.addFolder('Environment')\n this.folders.environment = folder\n\n folder.add(s.environment, 'exposure', 0.1, 4, 0.1).name('Exposure')\n folder.add(s.environment, 'diffuse', 0, 10, 0.1).name('Diffuse IBL')\n folder.add(s.environment, 'specular', 0, 10, 0.1).name('Specular IBL')\n\n // Fog subfolder\n if (s.environment.fog) {\n const fogFolder = folder.addFolder('Fog')\n fogFolder.add(s.environment.fog, 'enabled').name('Enabled')\n fogFolder.addColor({ color: this._rgbToHex(s.environment.fog.color) }, 'color')\n .name('Color')\n .onChange((hex) => {\n const rgb = this._hexToRgb(hex)\n s.environment.fog.color[0] = rgb.r\n s.environment.fog.color[1] = rgb.g\n s.environment.fog.color[2] = rgb.b\n })\n fogFolder.add(s.environment.fog.distances, '0', 0, 50, 1).name('Near Distance')\n fogFolder.add(s.environment.fog.distances, '1', 0, 200, 5).name('Mid Distance')\n fogFolder.add(s.environment.fog.distances, '2', 50, 500, 10).name('Far Distance')\n fogFolder.add(s.environment.fog.alpha, '0', 0, 1, 0.01).name('Near Alpha')\n fogFolder.add(s.environment.fog.alpha, '1', 0, 1, 0.01).name('Mid Alpha')\n fogFolder.add(s.environment.fog.alpha, '2', 0, 1, 0.01).name('Far Alpha')\n fogFolder.add(s.environment.fog.heightFade, '0', -100, 100, 1).name('Bottom Y')\n fogFolder.add(s.environment.fog.heightFade, '1', -50, 200, 5).name('Top Y')\n fogFolder.add(s.environment.fog, 'brightResist', 0, 1, 0.05).name('Bright Resist')\n // Initialize debug if not present\n if (s.environment.fog.debug === undefined) s.environment.fog.debug = 0\n fogFolder.add(s.environment.fog, 'debug', 0, 10, 1).name('Debug Mode')\n }\n }\n\n _createLightingFolder() {\n const s = this.engine.settings\n if (!s.lighting) return\n\n const folder = this.gui.addFolder('Lighting System')\n this.folders.lighting = folder\n\n folder.add(s.lighting, 'cullingEnabled').name('Light Culling')\n folder.add(s.lighting, 'maxDistance', 50, 500, 10).name('Max Distance')\n if (s.lighting.specularBoost !== undefined) {\n folder.add(s.lighting, 'specularBoost', 0, 2, 0.05).name('Specular Boost')\n }\n if (s.lighting.specularBoostRoughnessCutoff !== undefined) {\n folder.add(s.lighting, 'specularBoostRoughnessCutoff', 0.1, 1.0, 0.05).name('Boost Roughness Cutoff')\n }\n }\n\n _createSSGIFolder() {\n const s = this.engine.settings\n if (!s.ssgi) return\n\n const folder = this.gui.addFolder('SSGI (Indirect Light)')\n this.folders.ssgi = folder\n\n folder.add(s.ssgi, 'enabled').name('Enabled')\n folder.add(s.ssgi, 'intensity', 0.0, 5.0, 0.1).name('Intensity')\n folder.add(s.ssgi, 'emissiveBoost', 0.0, 50.0, 1.0).name('Emissive Boost')\n folder.add(s.ssgi, 'maxBrightness', 1.0, 50.0, 1.0).name('Max Brightness')\n folder.add(s.ssgi, 'sampleRadius', 0.5, 4.0, 0.5).name('Sample Radius')\n folder.add(s.ssgi, 'saturateLevel', 0.1, 2.0, 0.1).name('Saturate Level')\n }\n\n _createVolumetricFogFolder() {\n const s = this.engine.settings\n if (!s.volumetricFog) return\n\n // Initialize defaults for missing properties\n const vf = s.volumetricFog\n if (vf.density === undefined && vf.densityMultiplier === undefined) vf.density = 0.5\n if (vf.scatterStrength === undefined) vf.scatterStrength = 1.0\n if (!vf.heightRange) vf.heightRange = [-5, 20]\n if (vf.resolution === undefined) vf.resolution = 0.25\n if (vf.maxSamples === undefined) vf.maxSamples = 32\n if (vf.blurRadius === undefined) vf.blurRadius = 4\n if (vf.noiseStrength === undefined) vf.noiseStrength = 1.0\n if (vf.noiseScale === undefined) vf.noiseScale = 0.25\n if (vf.noiseAnimated === undefined) vf.noiseAnimated = true\n if (vf.shadowsEnabled === undefined) vf.shadowsEnabled = true\n if (vf.mainLightScatter === undefined) vf.mainLightScatter = 1.0\n if (vf.mainLightScatterDark === undefined) vf.mainLightScatterDark = 3.0\n if (vf.mainLightSaturation === undefined) vf.mainLightSaturation = 1.0\n if (vf.brightnessThreshold === undefined) vf.brightnessThreshold = 1.0\n if (vf.minVisibility === undefined) vf.minVisibility = 0.15\n if (vf.skyBrightness === undefined) vf.skyBrightness = 5.0\n if (vf.debug === undefined) vf.debug = 0\n\n const folder = this.gui.addFolder('Volumetric Fog')\n this.folders.volumetricFog = folder\n\n folder.add(vf, 'enabled').name('Enabled')\n\n // Density - use whichever property exists\n if (vf.density !== undefined) {\n folder.add(vf, 'density', 0.0, 2.0, 0.05).name('Density')\n } else if (vf.densityMultiplier !== undefined) {\n folder.add(vf, 'densityMultiplier', 0.0, 2.0, 0.05).name('Density')\n }\n\n folder.add(vf, 'scatterStrength', 0.0, 10.0, 0.1).name('Scatter (Lights)')\n folder.add(vf, 'mainLightScatter', 0.0, 5.0, 0.1).name('Sun Scatter (Light)')\n folder.add(vf, 'mainLightScatterDark', 0.0, 10.0, 0.1).name('Sun Scatter (Dark)')\n folder.add(vf, 'mainLightSaturation', 0.0, 1.0, 0.01).name('Sun Saturation')\n folder.add(vf, 'brightnessThreshold', 0.1, 5.0, 0.1).name('Bright Threshold')\n folder.add(vf, 'minVisibility', 0.0, 1.0, 0.05).name('Min Visibility')\n folder.add(vf, 'skyBrightness', 0.0, 10.0, 0.5).name('Sky Brightness')\n folder.add(vf.heightRange, '0', -50, 50, 1).name('Height Bottom')\n folder.add(vf.heightRange, '1', -10, 100, 1).name('Height Top')\n\n // Quality sub-folder\n const qualityFolder = folder.addFolder('Quality')\n qualityFolder.add(vf, 'resolution', 0.125, 0.5, 0.125).name('Resolution')\n qualityFolder.add(vf, 'maxSamples', 16, 128, 8).name('Max Samples')\n qualityFolder.add(vf, 'blurRadius', 0, 8, 1).name('Blur Radius')\n qualityFolder.close()\n\n // Noise sub-folder\n const noiseFolder = folder.addFolder('Noise')\n noiseFolder.add(vf, 'noiseStrength', 0.0, 1.0, 0.1).name('Strength')\n noiseFolder.add(vf, 'noiseScale', 0.05, 1.0, 0.05).name('Scale (Detail)')\n noiseFolder.add(vf, 'noiseAnimated').name('Animated')\n noiseFolder.close()\n\n // Shadows & Debug\n folder.add(vf, 'shadowsEnabled').name('Shadows')\n folder.add(vf, 'debug', 0, 12, 1).name('Debug Mode')\n }\n\n _createBloomFolder() {\n const s = this.engine.settings\n if (!s.bloom) return\n\n const folder = this.gui.addFolder('Bloom')\n this.folders.bloom = folder\n\n folder.add(s.bloom, 'enabled').name('Enabled')\n folder.add(s.bloom, 'intensity', 0.0, 1.0, 0.01).name('Intensity')\n folder.add(s.bloom, 'threshold', 0.0, 2.0, 0.01).name('Threshold')\n folder.add(s.bloom, 'softThreshold', 0.0, 1.0, 0.1).name('Soft Threshold')\n folder.add(s.bloom, 'radius', 8, 128, 4).name('Blur Radius')\n folder.add(s.bloom, 'emissiveBoost', 0.0, 20.0, 0.1).name('Emissive Boost')\n folder.add(s.bloom, 'maxBrightness', 1.0, 10.0, 0.5).name('Max Brightness')\n if (s.bloom.scale !== undefined) {\n folder.add(s.bloom, 'scale', 0.25, 1.0, 0.25).name('Resolution Scale')\n }\n }\n\n _createTonemapFolder() {\n const s = this.engine.settings\n if (!s.rendering) return\n\n // Ensure tonemapMode exists\n if (s.rendering.tonemapMode === undefined) s.rendering.tonemapMode = 0\n\n const folder = this.gui.addFolder('Tone Mapping')\n this.folders.tonemap = folder\n\n folder.add(s.rendering, 'tonemapMode', { 'ACES': 0, 'Reinhard': 1, 'None (Linear)': 2 }).name('Mode')\n }\n\n _createPlanarReflectionFolder() {\n const s = this.engine.settings\n if (!s.planarReflection) return\n\n const folder = this.gui.addFolder('Planar Reflection')\n this.folders.planarReflection = folder\n\n folder.add(s.planarReflection, 'enabled').name('Enabled')\n folder.add(s.planarReflection, 'groundLevel', -10, 10, 0.1).name('Ground Level')\n folder.add(s.planarReflection, 'resolution', 0.25, 1.0, 0.25).name('Resolution')\n folder.add(s.planarReflection, 'roughnessCutoff', 0.0, 1.0, 0.05).name('Roughness Cutoff')\n folder.add(s.planarReflection, 'normalPerturbation', 0.0, 2.0, 0.1).name('Normal Perturbation')\n folder.add(s.planarReflection, 'distanceFade', 0.1, 10.0, 0.1).name('Distance Fade')\n }\n\n _createAmbientCaptureFolder() {\n const s = this.engine.settings\n if (!s.ambientCapture) return\n\n const folder = this.gui.addFolder('Probe GI (Ambient Capture)')\n this.folders.ambientCapture = folder\n\n folder.add(s.ambientCapture, 'enabled').name('Enabled')\n folder.add(s.ambientCapture, 'intensity', 0.0, 2.0, 0.05).name('Intensity')\n folder.add(s.ambientCapture, 'maxDistance', 5, 100, 5).name('Max Distance')\n folder.add(s.ambientCapture, 'emissiveBoost', 0.0, 10.0, 0.1).name('Emissive Boost')\n folder.add(s.ambientCapture, 'saturateLevel', 0.0, 2.0, 0.05).name('Saturate Level')\n }\n\n _createDitheringFolder() {\n const s = this.engine.settings\n if (!s.dithering) return\n\n const folder = this.gui.addFolder('Dithering')\n this.folders.dithering = folder\n\n folder.add(s.dithering, 'enabled').name('Enabled')\n folder.add(s.dithering, 'colorLevels', 4, 256, 1).name('Color Levels')\n }\n\n _createCRTFolder() {\n const s = this.engine.settings\n if (!s.crt) return\n\n const folder = this.gui.addFolder('CRT Effect')\n this.folders.crt = folder\n\n folder.add(s.crt, 'enabled').name('CRT Enabled')\n folder.add(s.crt, 'upscaleEnabled').name('Upscale Only')\n folder.add(s.crt, 'upscaleTarget', 1, 8, 1).name('Upscale Target')\n\n // Geometry sub-folder\n const geomFolder = folder.addFolder('Geometry')\n geomFolder.add(s.crt, 'curvature', 0, 0.25, 0.005).name('Curvature')\n geomFolder.add(s.crt, 'cornerRadius', 0, 0.2, 0.005).name('Corner Radius')\n geomFolder.add(s.crt, 'zoom', 1.0, 1.25, 0.005).name('Zoom')\n geomFolder.close()\n\n // Scanlines sub-folder\n const scanFolder = folder.addFolder('Scanlines')\n scanFolder.add(s.crt, 'scanlineIntensity', 0, 1, 0.05).name('Intensity')\n scanFolder.add(s.crt, 'scanlineWidth', 0, 1, 0.05).name('Width')\n scanFolder.add(s.crt, 'scanlineBrightBoost', 0, 2, 0.05).name('Bright Boost')\n scanFolder.add(s.crt, 'scanlineHeight', 1, 10, 1).name('Height (px)')\n scanFolder.close()\n\n // Convergence sub-folder\n const convFolder = folder.addFolder('RGB Convergence')\n convFolder.add(s.crt.convergence, '0', -3, 3, 0.1).name('Red X Offset')\n convFolder.add(s.crt.convergence, '1', -3, 3, 0.1).name('Green X Offset')\n convFolder.add(s.crt.convergence, '2', -3, 3, 0.1).name('Blue X Offset')\n convFolder.close()\n\n // Phosphor mask sub-folder\n const maskFolder = folder.addFolder('Phosphor Mask')\n maskFolder.add(s.crt, 'maskType', ['none', 'aperture', 'slot', 'shadow']).name('Type')\n maskFolder.add(s.crt, 'maskIntensity', 0, 1, 0.05).name('Intensity')\n maskFolder.close()\n\n // Vignette sub-folder\n const vigFolder = folder.addFolder('Vignette')\n vigFolder.add(s.crt, 'vignetteIntensity', 0, 1, 0.05).name('Intensity')\n vigFolder.add(s.crt, 'vignetteSize', 0.1, 1, 0.05).name('Size')\n vigFolder.close()\n\n // Blur (top level, not in subfolder)\n folder.add(s.crt, 'blurSize', 0, 8, 0.1).name('H-Blur (px)')\n }\n\n _createCameraFolder() {\n const s = this.engine.settings\n if (!s.camera) return\n\n const folder = this.gui.addFolder('Camera')\n this.folders.camera = folder\n\n folder.add(s.camera, 'fov', 30, 120, 1).name('FOV').onChange(() => {\n if (this.engine.camera) this.engine.camera.fov = s.camera.fov\n })\n folder.add(s.camera, 'near', 0.01, 1, 0.01).name('Near Plane').onChange(() => {\n if (this.engine.camera) this.engine.camera.near = s.camera.near\n })\n folder.add(s.camera, 'far', 100, 10000, 100).name('Far Plane').onChange(() => {\n if (this.engine.camera) this.engine.camera.far = s.camera.far\n })\n }\n\n _createDebugFolder() {\n const folder = this.gui.addFolder('Debug Options')\n this.folders.debug = folder\n\n // Add engine-level debug options\n if (!this.engine._debugSettings) {\n this.engine._debugSettings = {\n showLights: false,\n lightCrossSize: 10\n }\n }\n\n folder.add(this.engine._debugSettings, 'showLights').name('Show Lights')\n folder.add(this.engine._debugSettings, 'lightCrossSize', 5, 30, 1).name('Cross Size')\n }\n\n /**\n * Show or hide the debug UI\n */\n setVisible(visible) {\n if (visible && !this.initialized) {\n this.init()\n }\n\n if (this.gui) {\n this.gui.domElement.style.display = visible ? '' : 'none'\n }\n }\n\n /**\n * Check if debug mode is on and update visibility\n */\n update() {\n const debugMode = this.engine.settings?.rendering?.debug ?? false\n\n if (debugMode && !this.initialized) {\n this.init()\n }\n\n if (this.gui) {\n this.gui.domElement.style.display = debugMode ? '' : 'none'\n }\n\n if (debugMode && this.initialized) {\n this.updateStats()\n }\n }\n\n /**\n * Destroy the debug UI\n */\n destroy() {\n if (this.gui) {\n this.gui.destroy()\n this.gui = null\n }\n this.initialized = false\n }\n\n // Utility functions\n _rgbToHex(rgb) {\n const r = Math.round((rgb[0] || 0) * 255)\n const g = Math.round((rgb[1] || 0) * 255)\n const b = Math.round((rgb[2] || 0) * 255)\n return `#${r.toString(16).padStart(2, '0')}${g.toString(16).padStart(2, '0')}${b.toString(16).padStart(2, '0')}`\n }\n\n _hexToRgb(hex) {\n const result = /^#?([a-f\\d]{2})([a-f\\d]{2})([a-f\\d]{2})$/i.exec(hex)\n return result ? {\n r: parseInt(result[1], 16) / 255,\n g: parseInt(result[2], 16) / 255,\n b: parseInt(result[3], 16) / 255\n } : { r: 1, g: 1, b: 1 }\n }\n}\n\nexport { DebugUI }\n","/**\n * Raycaster - Async ray intersection testing\n *\n * Performs ray-geometry intersection tests without blocking rendering.\n * Uses a Web Worker for heavy triangle intersection calculations.\n *\n * Usage:\n * const raycaster = new Raycaster(engine)\n * await raycaster.initialize()\n *\n * raycaster.cast(origin, direction, maxDistance, (result) => {\n * if (result.hit) {\n * console.log('Hit:', result.entity, 'at distance:', result.distance)\n * }\n * })\n */\n\nimport { vec3, mat4 } from \"../math.js\"\n\nclass Raycaster {\n constructor(engine) {\n this.engine = engine\n this.worker = null\n this._pendingCallbacks = new Map()\n this._nextRequestId = 0\n this._initialized = false\n }\n\n async initialize() {\n // Create worker from inline code (avoids separate file issues with bundlers)\n const workerCode = this._getWorkerCode()\n const blob = new Blob([workerCode], { type: 'application/javascript' })\n const workerUrl = URL.createObjectURL(blob)\n\n this.worker = new Worker(workerUrl)\n this.worker.onmessage = this._handleWorkerMessage.bind(this)\n this.worker.onerror = (e) => console.error('Raycaster worker error:', e)\n\n this._initialized = true\n URL.revokeObjectURL(workerUrl)\n }\n\n /**\n * Cast a ray and get the closest intersection\n * @param {Array|vec3} origin - Ray start point [x, y, z]\n * @param {Array|vec3} direction - Ray direction (will be normalized)\n * @param {number} maxDistance - Maximum ray length\n * @param {Function} callback - Called with result: { hit, distance, point, normal, entity, mesh, triangleIndex }\n * @param {Object} options - Optional settings\n * @param {Array} options.entities - Specific entities to test (default: all scene entities)\n * @param {Array} options.meshes - Specific meshes to test (default: all scene meshes)\n * @param {boolean} options.backfaces - Test backfaces (default: false)\n * @param {Array} options.exclude - Entities/meshes to exclude\n */\n cast(origin, direction, maxDistance, callback, options = {}) {\n if (!this._initialized) {\n console.warn('Raycaster not initialized')\n callback({ hit: false, error: 'not initialized' })\n return\n }\n\n const ray = {\n origin: Array.from(origin),\n direction: this._normalize(Array.from(direction)),\n maxDistance\n }\n\n // Collect candidates from scene\n const candidates = this._collectCandidates(ray, options)\n\n if (candidates.length === 0) {\n callback({ hit: false })\n return\n }\n\n // Send to worker for triangle intersection\n const requestId = this._nextRequestId++\n this._pendingCallbacks.set(requestId, { callback, candidates })\n\n this.worker.postMessage({\n type: 'raycast',\n requestId,\n ray,\n debug: options.debug ?? false,\n candidates: candidates.map(c => ({\n id: c.id,\n vertices: c.vertices,\n indices: c.indices,\n matrix: c.matrix,\n backfaces: options.backfaces ?? false\n }))\n })\n }\n\n /**\n * Cast a ray upward from a position to check for sky visibility\n * Useful for determining if camera is under cover\n * @param {Array|vec3} position - Position to test from\n * @param {number} maxDistance - How far to check (default: 100)\n * @param {Function} callback - Called with { hitSky: boolean, distance?: number, entity?: object }\n */\n castToSky(position, maxDistance, callback) {\n this.cast(\n position,\n [0, 1, 0], // Straight up\n maxDistance ?? 100,\n (result) => {\n callback({\n hitSky: !result.hit,\n distance: result.distance,\n entity: result.entity,\n mesh: result.mesh\n })\n }\n )\n }\n\n /**\n * Cast a ray from screen coordinates (mouse picking)\n * @param {number} screenX - Screen X coordinate\n * @param {number} screenY - Screen Y coordinate\n * @param {Object} camera - Camera with projection/view matrices\n * @param {Function} callback - Called with intersection result\n * @param {Object} options - Cast options\n */\n castFromScreen(screenX, screenY, camera, callback, options = {}) {\n const { width, height } = this.engine.canvas\n\n // Convert screen to NDC\n const ndcX = (screenX / width) * 2 - 1\n const ndcY = 1 - (screenY / height) * 2 // Flip Y\n\n // Unproject to world space\n const invViewProj = mat4.create()\n mat4.multiply(invViewProj, camera.proj, camera.view)\n mat4.invert(invViewProj, invViewProj)\n\n // Near and far points\n const nearPoint = this._unproject([ndcX, ndcY, 0], invViewProj)\n const farPoint = this._unproject([ndcX, ndcY, 1], invViewProj)\n\n // Ray direction\n const direction = [\n farPoint[0] - nearPoint[0],\n farPoint[1] - nearPoint[1],\n farPoint[2] - nearPoint[2]\n ]\n\n const maxDistance = options.maxDistance ?? camera.far ?? 1000\n\n this.cast(nearPoint, direction, maxDistance, callback, options)\n }\n\n /**\n * Collect candidate geometries that pass bounding sphere test\n */\n _collectCandidates(ray, options) {\n const candidates = []\n const exclude = new Set(options.exclude ?? [])\n const debug = options.debug\n\n // Test entities - entities reference models via string ID, geometry is in asset\n const entities = options.entities ?? this._getAllEntities()\n const assetManager = this.engine.assetManager\n\n for (const entity of entities) {\n if (exclude.has(entity)) continue\n if (!entity.model) continue\n\n // Get geometry from asset manager\n const asset = assetManager?.get(entity.model)\n if (!asset?.geometry) continue\n\n // Entity has world-space bsphere in _bsphere\n const bsphere = this._getEntityBoundingSphere(entity)\n if (!bsphere) continue\n\n if (this._raySphereIntersect(ray, bsphere)) {\n const geometryData = this._extractGeometry(asset.geometry)\n if (geometryData) {\n const matrix = entity._matrix ?? mat4.create()\n candidates.push({\n id: entity.id ?? entity.name ?? `entity_${candidates.length}`,\n type: 'entity',\n entity,\n asset,\n vertices: geometryData.vertices,\n indices: geometryData.indices,\n matrix: Array.from(matrix),\n bsphereDistance: this._raySphereDistance(ray, bsphere)\n })\n }\n }\n }\n\n // Test standalone meshes\n const meshes = options.meshes ?? this._getAllMeshes()\n let debugStats = debug ? { total: 0, noGeom: 0, noBsphere: 0, noData: 0, sphereMiss: 0, candidates: 0 } : null\n\n for (const [name, mesh] of Object.entries(meshes)) {\n if (exclude.has(mesh)) continue\n if (!mesh.geometry) {\n if (debug) debugStats.noGeom++\n continue\n }\n\n const bsphere = this._getMeshBoundingSphere(mesh)\n if (!bsphere) {\n if (debug) debugStats.noBsphere++\n continue\n }\n\n const geometryData = this._extractGeometry(mesh.geometry)\n if (!geometryData) {\n if (debug) debugStats.noData++\n continue\n }\n\n if (debug) debugStats.total++\n\n // For instanced meshes, test each instance\n // Static meshes keep their instanceCount; dynamic meshes may have it reset mid-frame\n let instanceCount = mesh.geometry.instanceCount ?? 0\n\n // For meshes with instanceCount=0, still test if they have instance data\n // (transparent/static meshes may have valid transforms even when instanceCount is 0)\n if (instanceCount === 0) {\n if (mesh.geometry.instanceData) {\n // Use maxInstances for static meshes (instance data persists)\n // For dynamic meshes, test at least 1 instance\n instanceCount = mesh.static ? (mesh.geometry.maxInstances ?? 1) : 1\n } else {\n // Non-instanced mesh - test with identity matrix\n instanceCount = 1\n }\n }\n\n for (let i = 0; i < instanceCount; i++) {\n const matrix = this._getInstanceMatrix(mesh.geometry, i)\n const instanceBsphere = this._transformBoundingSphere(bsphere, matrix)\n\n if (this._raySphereIntersect(ray, instanceBsphere)) {\n if (debug) debugStats.candidates++\n candidates.push({\n id: `${name}_${i}`,\n type: 'mesh',\n mesh,\n meshName: name,\n instanceIndex: i,\n vertices: geometryData.vertices,\n indices: geometryData.indices,\n matrix: Array.from(matrix),\n bsphereDistance: this._raySphereDistance(ray, instanceBsphere)\n })\n } else {\n if (debug) debugStats.sphereMiss++\n }\n }\n }\n\n if (debug && debugStats) {\n console.log(`Raycaster: meshes=${debugStats.total}, sphereHit=${debugStats.candidates}, sphereMiss=${debugStats.sphereMiss}`)\n // Show which candidates passed bounding sphere test\n if (candidates.length > 0 && candidates.length < 50) {\n const candInfo = candidates.map(c => {\n const m = c.matrix\n const pos = [m[12], m[13], m[14]] // Translation from matrix\n return `${c.id}@[${pos.map(v=>v.toFixed(1)).join(',')}]`\n }).join(', ')\n console.log(`Candidates: ${candInfo}`)\n }\n }\n\n // Sort by bounding sphere distance (closest first) for early termination\n candidates.sort((a, b) => a.bsphereDistance - b.bsphereDistance)\n\n return candidates\n }\n\n _getAllEntities() {\n // Get entities from engine's entity manager\n // engine.entities is a plain object { id: entity }\n const entities = this.engine.entities\n if (!entities) return []\n return Object.values(entities)\n }\n\n _getAllMeshes() {\n // Get meshes from engine\n return this.engine.meshes ?? {}\n }\n\n _getEntityBoundingSphere(entity) {\n // Entities have pre-calculated _bsphere in world space\n if (entity._bsphere && entity._bsphere.radius > 0) {\n return {\n center: Array.from(entity._bsphere.center),\n radius: entity._bsphere.radius\n }\n }\n\n // Fallback to mesh geometry bounding sphere\n const geometry = entity.mesh?.geometry\n if (!geometry) return null\n\n const localBsphere = geometry.getBoundingSphere?.()\n if (!localBsphere || localBsphere.radius <= 0) return null\n\n // Transform by entity matrix\n const matrix = entity._matrix ?? entity.matrix ?? mat4.create()\n return this._transformBoundingSphere(localBsphere, matrix)\n }\n\n _getMeshBoundingSphere(mesh) {\n const geometry = mesh.geometry\n if (!geometry) return null\n\n return geometry.getBoundingSphere?.() ?? null\n }\n\n _transformBoundingSphere(bsphere, matrix) {\n // Transform center\n const center = vec3.create()\n vec3.transformMat4(center, bsphere.center, matrix)\n\n // Scale radius by max scale factor\n const scaleX = Math.sqrt(matrix[0]*matrix[0] + matrix[1]*matrix[1] + matrix[2]*matrix[2])\n const scaleY = Math.sqrt(matrix[4]*matrix[4] + matrix[5]*matrix[5] + matrix[6]*matrix[6])\n const scaleZ = Math.sqrt(matrix[8]*matrix[8] + matrix[9]*matrix[9] + matrix[10]*matrix[10])\n const maxScale = Math.max(scaleX, scaleY, scaleZ)\n\n return {\n center: Array.from(center),\n radius: bsphere.radius * maxScale\n }\n }\n\n _getInstanceMatrix(geometry, instanceIndex) {\n // Always try to read from instanceData - transforms are stored there even for single instances\n if (!geometry.instanceData) {\n return mat4.create()\n }\n\n const stride = 28 // floats per instance (matrix + posRadius + uvTransform + color)\n const offset = instanceIndex * stride\n\n // Check if we have data at this offset\n if (offset + 16 > geometry.instanceData.length) {\n return mat4.create()\n }\n\n const matrix = mat4.create()\n\n // Copy 16 floats for matrix\n for (let i = 0; i < 16; i++) {\n matrix[i] = geometry.instanceData[offset + i]\n }\n\n return matrix\n }\n\n _extractGeometry(geometry) {\n // Get vertex positions from CPU arrays if available\n if (!geometry.vertexArray || !geometry.indexArray) {\n return null\n }\n\n // Extract positions (assuming stride of 20 floats: pos(3) + uv(2) + normal(3) + color(4) + weights(4) + joints(4))\n const stride = 20 // floats per vertex\n const vertexCount = geometry.vertexArray.length / stride\n const vertices = new Float32Array(vertexCount * 3)\n\n for (let i = 0; i < vertexCount; i++) {\n vertices[i * 3] = geometry.vertexArray[i * stride]\n vertices[i * 3 + 1] = geometry.vertexArray[i * stride + 1]\n vertices[i * 3 + 2] = geometry.vertexArray[i * stride + 2]\n }\n\n return {\n vertices,\n indices: geometry.indexArray\n }\n }\n\n /**\n * Ray-sphere intersection test\n * Returns true if ray intersects sphere within maxDistance\n * Handles case where ray origin is inside the sphere\n */\n _raySphereIntersect(ray, sphere) {\n // Vector from sphere center to ray origin\n const oc = [\n ray.origin[0] - sphere.center[0],\n ray.origin[1] - sphere.center[1],\n ray.origin[2] - sphere.center[2]\n ]\n\n // Check if we're inside the sphere\n const distToCenter = Math.sqrt(oc[0]*oc[0] + oc[1]*oc[1] + oc[2]*oc[2])\n if (distToCenter < sphere.radius) {\n // Inside sphere - ray will definitely exit through it\n // Just check if exit point is within maxDistance\n // Exit distance is approximately radius - distToCenter (simplified)\n return true\n }\n\n const a = this._dot(ray.direction, ray.direction)\n const b = 2.0 * this._dot(oc, ray.direction)\n const c = this._dot(oc, oc) - sphere.radius * sphere.radius\n const discriminant = b * b - 4 * a * c\n\n if (discriminant < 0) return false\n\n const sqrtDisc = Math.sqrt(discriminant)\n const t1 = (-b - sqrtDisc) / (2.0 * a)\n const t2 = (-b + sqrtDisc) / (2.0 * a)\n\n // Check if either intersection is within valid range [0, maxDistance]\n if (t1 >= 0 && t1 <= ray.maxDistance) return true\n if (t2 >= 0 && t2 <= ray.maxDistance) return true\n\n return false\n }\n\n /**\n * Get distance to sphere along ray (for sorting)\n */\n _raySphereDistance(ray, sphere) {\n const oc = [\n ray.origin[0] - sphere.center[0],\n ray.origin[1] - sphere.center[1],\n ray.origin[2] - sphere.center[2]\n ]\n\n const a = this._dot(ray.direction, ray.direction)\n const b = 2.0 * this._dot(oc, ray.direction)\n const c = this._dot(oc, oc) - sphere.radius * sphere.radius\n const discriminant = b * b - 4 * a * c\n\n if (discriminant < 0) return Infinity\n\n const t = (-b - Math.sqrt(discriminant)) / (2.0 * a)\n return Math.max(0, t)\n }\n\n _handleWorkerMessage(event) {\n const { type, requestId, result } = event.data\n\n if (type === 'raycastResult') {\n const pending = this._pendingCallbacks.get(requestId)\n if (pending) {\n this._pendingCallbacks.delete(requestId)\n\n // Enrich result with original entity/mesh references\n if (result.hit && pending.candidates) {\n const candidate = pending.candidates.find(c => c.id === result.candidateId)\n if (candidate) {\n result.entity = candidate.entity\n result.mesh = candidate.mesh\n result.meshName = candidate.meshName\n result.instanceIndex = candidate.instanceIndex\n }\n }\n\n pending.callback(result)\n }\n }\n }\n\n _normalize(v) {\n const len = Math.sqrt(v[0]*v[0] + v[1]*v[1] + v[2]*v[2])\n if (len === 0) return [0, 0, 1]\n return [v[0]/len, v[1]/len, v[2]/len]\n }\n\n _dot(a, b) {\n return a[0]*b[0] + a[1]*b[1] + a[2]*b[2]\n }\n\n /**\n * Get perpendicular distance from a point to a ray\n */\n _pointToRayDistance(point, ray) {\n // Vector from ray origin to point\n const op = [\n point[0] - ray.origin[0],\n point[1] - ray.origin[1],\n point[2] - ray.origin[2]\n ]\n\n // Project onto ray direction\n const t = this._dot(op, ray.direction)\n\n // Closest point on ray\n const closest = [\n ray.origin[0] + ray.direction[0] * t,\n ray.origin[1] + ray.direction[1] * t,\n ray.origin[2] + ray.direction[2] * t\n ]\n\n // Distance from point to closest point on ray\n const dx = point[0] - closest[0]\n const dy = point[1] - closest[1]\n const dz = point[2] - closest[2]\n\n return Math.sqrt(dx*dx + dy*dy + dz*dz)\n }\n\n _unproject(ndc, invViewProj) {\n const x = ndc[0]\n const y = ndc[1]\n const z = ndc[2]\n\n // Multiply by inverse view-projection\n const w = invViewProj[3]*x + invViewProj[7]*y + invViewProj[11]*z + invViewProj[15]\n\n return [\n (invViewProj[0]*x + invViewProj[4]*y + invViewProj[8]*z + invViewProj[12]) / w,\n (invViewProj[1]*x + invViewProj[5]*y + invViewProj[9]*z + invViewProj[13]) / w,\n (invViewProj[2]*x + invViewProj[6]*y + invViewProj[10]*z + invViewProj[14]) / w\n ]\n }\n\n /**\n * Generate Web Worker code as string\n */\n _getWorkerCode() {\n return `\n// Raycaster Web Worker\n// Performs triangle intersection tests off the main thread\n\nself.onmessage = function(event) {\n const { type, requestId, ray, candidates, debug } = event.data\n\n if (type === 'raycast') {\n const result = raycastTriangles(ray, candidates, debug)\n self.postMessage({ type: 'raycastResult', requestId, result })\n }\n}\n\nfunction raycastTriangles(ray, candidates, debug) {\n let closestHit = null\n let closestDistance = ray.maxDistance\n let debugInfo = debug ? { totalTris: 0, testedCandidates: 0, scales: [] } : null\n\n for (const candidate of candidates) {\n if (debug) debugInfo.testedCandidates++\n const result = testCandidate(ray, candidate, closestDistance, debug ? debugInfo : null)\n\n if (result && result.distance < closestDistance) {\n closestDistance = result.distance\n closestHit = {\n hit: true,\n distance: result.distance,\n point: result.point,\n normal: result.normal,\n triangleIndex: result.triangleIndex,\n candidateId: candidate.id,\n localT: result.localT,\n scale: result.scale\n }\n }\n }\n\n if (debug) {\n let msg = 'Worker: candidates=' + debugInfo.testedCandidates + ', triangles=' + debugInfo.totalTris\n if (closestHit) {\n msg += ', hit=' + closestHit.distance.toFixed(2) + ' (localT=' + closestHit.localT.toFixed(2) + ', scale=' + closestHit.scale.toFixed(2) + ')'\n } else {\n msg += ', hit=none'\n }\n console.log(msg)\n }\n\n return closestHit ?? { hit: false }\n}\n\nfunction testCandidate(ray, candidate, maxDistance, debugInfo) {\n const { vertices, indices, matrix, backfaces } = candidate\n\n // Compute inverse matrix for transforming ray to local space\n const invMatrix = invertMatrix4(matrix)\n\n // Transform ray to local space\n const localOrigin = transformPoint(ray.origin, invMatrix)\n const localDir = transformDirection(ray.direction, invMatrix)\n\n // Calculate the scale factor of the transformation (for correct distance)\n const dirScale = Math.sqrt(localDir[0]*localDir[0] + localDir[1]*localDir[1] + localDir[2]*localDir[2])\n const localDirNorm = [localDir[0]/dirScale, localDir[1]/dirScale, localDir[2]/dirScale]\n\n let closestHit = null\n let closestT = maxDistance\n\n // Test each triangle\n const triangleCount = indices.length / 3\n if (debugInfo) debugInfo.totalTris += triangleCount\n for (let i = 0; i < triangleCount; i++) {\n const i0 = indices[i * 3]\n const i1 = indices[i * 3 + 1]\n const i2 = indices[i * 3 + 2]\n\n const v0 = [vertices[i0 * 3], vertices[i0 * 3 + 1], vertices[i0 * 3 + 2]]\n const v1 = [vertices[i1 * 3], vertices[i1 * 3 + 1], vertices[i1 * 3 + 2]]\n const v2 = [vertices[i2 * 3], vertices[i2 * 3 + 1], vertices[i2 * 3 + 2]]\n\n const hit = rayTriangleIntersect(localOrigin, localDirNorm, v0, v1, v2, backfaces)\n\n if (hit && hit.t > 0) {\n // Transform hit point and normal back to world space\n const worldPoint = transformPoint(hit.point, matrix)\n\n // Calculate world-space distance (local t may be wrong due to matrix scale)\n const worldDist = Math.sqrt(\n (worldPoint[0] - ray.origin[0]) ** 2 +\n (worldPoint[1] - ray.origin[1]) ** 2 +\n (worldPoint[2] - ray.origin[2]) ** 2\n )\n\n if (worldDist < closestT) {\n closestT = worldDist\n const worldNormal = transformDirection(hit.normal, matrix)\n\n closestHit = {\n distance: worldDist,\n point: worldPoint,\n normal: normalize(worldNormal),\n triangleIndex: i,\n localT: hit.t,\n scale: dirScale\n }\n }\n }\n }\n\n return closestHit\n}\n\n// Möller–Trumbore intersection algorithm\nfunction rayTriangleIntersect(origin, dir, v0, v1, v2, backfaces) {\n const EPSILON = 0.0000001\n\n const edge1 = sub(v1, v0)\n const edge2 = sub(v2, v0)\n const h = cross(dir, edge2)\n const a = dot(edge1, h)\n\n // Check if ray is parallel to triangle\n if (a > -EPSILON && a < EPSILON) return null\n\n // Check backface\n if (!backfaces && a < 0) return null\n\n const f = 1.0 / a\n const s = sub(origin, v0)\n const u = f * dot(s, h)\n\n if (u < 0.0 || u > 1.0) return null\n\n const q = cross(s, edge1)\n const v = f * dot(dir, q)\n\n if (v < 0.0 || u + v > 1.0) return null\n\n const t = f * dot(edge2, q)\n\n if (t > EPSILON) {\n const point = [\n origin[0] + dir[0] * t,\n origin[1] + dir[1] * t,\n origin[2] + dir[2] * t\n ]\n const normal = normalize(cross(edge1, edge2))\n return { t, point, normal, u, v }\n }\n\n return null\n}\n\n// Matrix and vector utilities\nfunction invertMatrix4(m) {\n const inv = new Array(16)\n\n inv[0] = m[5]*m[10]*m[15] - m[5]*m[11]*m[14] - m[9]*m[6]*m[15] + m[9]*m[7]*m[14] + m[13]*m[6]*m[11] - m[13]*m[7]*m[10]\n inv[4] = -m[4]*m[10]*m[15] + m[4]*m[11]*m[14] + m[8]*m[6]*m[15] - m[8]*m[7]*m[14] - m[12]*m[6]*m[11] + m[12]*m[7]*m[10]\n inv[8] = m[4]*m[9]*m[15] - m[4]*m[11]*m[13] - m[8]*m[5]*m[15] + m[8]*m[7]*m[13] + m[12]*m[5]*m[11] - m[12]*m[7]*m[9]\n inv[12] = -m[4]*m[9]*m[14] + m[4]*m[10]*m[13] + m[8]*m[5]*m[14] - m[8]*m[6]*m[13] - m[12]*m[5]*m[10] + m[12]*m[6]*m[9]\n inv[1] = -m[1]*m[10]*m[15] + m[1]*m[11]*m[14] + m[9]*m[2]*m[15] - m[9]*m[3]*m[14] - m[13]*m[2]*m[11] + m[13]*m[3]*m[10]\n inv[5] = m[0]*m[10]*m[15] - m[0]*m[11]*m[14] - m[8]*m[2]*m[15] + m[8]*m[3]*m[14] + m[12]*m[2]*m[11] - m[12]*m[3]*m[10]\n inv[9] = -m[0]*m[9]*m[15] + m[0]*m[11]*m[13] + m[8]*m[1]*m[15] - m[8]*m[3]*m[13] - m[12]*m[1]*m[11] + m[12]*m[3]*m[9]\n inv[13] = m[0]*m[9]*m[14] - m[0]*m[10]*m[13] - m[8]*m[1]*m[14] + m[8]*m[2]*m[13] + m[12]*m[1]*m[10] - m[12]*m[2]*m[9]\n inv[2] = m[1]*m[6]*m[15] - m[1]*m[7]*m[14] - m[5]*m[2]*m[15] + m[5]*m[3]*m[14] + m[13]*m[2]*m[7] - m[13]*m[3]*m[6]\n inv[6] = -m[0]*m[6]*m[15] + m[0]*m[7]*m[14] + m[4]*m[2]*m[15] - m[4]*m[3]*m[14] - m[12]*m[2]*m[7] + m[12]*m[3]*m[6]\n inv[10] = m[0]*m[5]*m[15] - m[0]*m[7]*m[13] - m[4]*m[1]*m[15] + m[4]*m[3]*m[13] + m[12]*m[1]*m[7] - m[12]*m[3]*m[5]\n inv[14] = -m[0]*m[5]*m[14] + m[0]*m[6]*m[13] + m[4]*m[1]*m[14] - m[4]*m[2]*m[13] - m[12]*m[1]*m[6] + m[12]*m[2]*m[5]\n inv[3] = -m[1]*m[6]*m[11] + m[1]*m[7]*m[10] + m[5]*m[2]*m[11] - m[5]*m[3]*m[10] - m[9]*m[2]*m[7] + m[9]*m[3]*m[6]\n inv[7] = m[0]*m[6]*m[11] - m[0]*m[7]*m[10] - m[4]*m[2]*m[11] + m[4]*m[3]*m[10] + m[8]*m[2]*m[7] - m[8]*m[3]*m[6]\n inv[11] = -m[0]*m[5]*m[11] + m[0]*m[7]*m[9] + m[4]*m[1]*m[11] - m[4]*m[3]*m[9] - m[8]*m[1]*m[7] + m[8]*m[3]*m[5]\n inv[15] = m[0]*m[5]*m[10] - m[0]*m[6]*m[9] - m[4]*m[1]*m[10] + m[4]*m[2]*m[9] + m[8]*m[1]*m[6] - m[8]*m[2]*m[5]\n\n let det = m[0]*inv[0] + m[1]*inv[4] + m[2]*inv[8] + m[3]*inv[12]\n if (det === 0) return m // Return original if singular\n\n det = 1.0 / det\n for (let i = 0; i < 16; i++) inv[i] *= det\n\n return inv\n}\n\nfunction transformPoint(p, m) {\n const w = m[3]*p[0] + m[7]*p[1] + m[11]*p[2] + m[15]\n return [\n (m[0]*p[0] + m[4]*p[1] + m[8]*p[2] + m[12]) / w,\n (m[1]*p[0] + m[5]*p[1] + m[9]*p[2] + m[13]) / w,\n (m[2]*p[0] + m[6]*p[1] + m[10]*p[2] + m[14]) / w\n ]\n}\n\nfunction transformDirection(d, m) {\n return [\n m[0]*d[0] + m[4]*d[1] + m[8]*d[2],\n m[1]*d[0] + m[5]*d[1] + m[9]*d[2],\n m[2]*d[0] + m[6]*d[1] + m[10]*d[2]\n ]\n}\n\nfunction normalize(v) {\n const len = Math.sqrt(v[0]*v[0] + v[1]*v[1] + v[2]*v[2])\n if (len === 0) return [0, 0, 1]\n return [v[0]/len, v[1]/len, v[2]/len]\n}\n\nfunction dot(a, b) {\n return a[0]*b[0] + a[1]*b[1] + a[2]*b[2]\n}\n\nfunction cross(a, b) {\n return [\n a[1]*b[2] - a[2]*b[1],\n a[2]*b[0] - a[0]*b[2],\n a[0]*b[1] - a[1]*b[0]\n ]\n}\n\nfunction sub(a, b) {\n return [a[0]-b[0], a[1]-b[1], a[2]-b[2]]\n}\n`\n }\n\n destroy() {\n if (this.worker) {\n this.worker.terminate()\n this.worker = null\n }\n this._pendingCallbacks.clear()\n this._initialized = false\n }\n}\n\nexport { Raycaster }\n","import \"./math.js\"\nimport { Texture } from \"./Texture.js\"\nimport { Geometry } from \"./Geometry.js\";\nimport { Material } from \"./Material.js\";\nimport { Mesh } from \"./Mesh.js\";\nimport { Camera } from \"./Camera.js\"\nimport { RenderGraph } from \"./rendering/RenderGraph.js\"\nimport { loadGltf } from \"./gltf.js\"\nimport { EntityManager } from \"./core/EntityManager.js\"\nimport { AssetManager } from \"./core/AssetManager.js\"\nimport { CullingSystem } from \"./core/CullingSystem.js\"\nimport { InstanceManager } from \"./core/InstanceManager.js\"\nimport { ParticleSystem } from \"./core/ParticleSystem.js\"\nimport { ParticleEmitter } from \"./core/ParticleEmitter.js\"\nimport { DebugUI } from \"./DebugUI.js\"\nimport { Raycaster } from \"./utils/Raycaster.js\"\n\n\n// Display a failure message and stop rendering\nfunction fail(engine, msg, data) {\n if (engine?.canvas) {\n engine.canvas.style.display = \"none\"\n }\n console.error(msg, data)\n\n if (typeof document !== 'undefined') {\n let ecanvas = document.createElement(\"canvas\")\n document.body.appendChild(ecanvas)\n if (ecanvas) {\n ecanvas.width = window.innerWidth\n ecanvas.height = window.innerHeight\n let ctx = ecanvas.getContext(\"2d\")\n ctx.clearRect(0, 0, ecanvas.width, ecanvas.height)\n ctx.fillStyle = \"rgba(255, 128, 128, 0.5)\"\n ctx.fillRect(0, 0, ecanvas.width, ecanvas.height)\n ctx.fillStyle = \"#ffffff\"\n ctx.textAlign = \"center\"\n ctx.textBaseline = \"middle\"\n ctx.font = \"64px Arial\"\n ctx.fillText(\"😒\", ecanvas.width / 2, ecanvas.height / 2 - 72)\n ctx.font = \"bold 20px Arial\"\n ctx.fillText(msg, ecanvas.width / 2, ecanvas.height / 2)\n if (data) {\n ctx.font = \"10px Arial\"\n ctx.fillText(data, ecanvas.width / 2, ecanvas.height / 2 + 20)\n }\n } else {\n alert(msg, data)\n }\n }\n if (engine) {\n engine.rendering = false\n }\n}\n\n\n// Default settings for the entire engine - consolidated from all subsystems\nconst DEFAULT_SETTINGS = {\n // Engine/Runtime settings\n engine: {\n debugMode: false, // F10 toggle: false=character controller, true=fly camera\n mouseSmoothing: 0.2, // Lower = more smoothing\n mouseIdleThreshold: 0.1, // Seconds before stopping mouse callbacks\n },\n\n // Camera defaults\n camera: {\n fov: 70, // Field of view in degrees\n near: 0.05, // Near plane\n far: 5000, // Far plane\n },\n\n // Rendering options\n rendering: {\n debug: false,\n nearestFiltering: false, // Use linear filtering by default\n mipBias: 0, // MIP map bias\n fxaa: false, // Fast approximate anti-aliasing\n renderScale: 1, // Render resolution multiplier (1.5-2.0 for supersampling AA)\n autoScale: {\n enabled: true, // Auto-reduce renderScale for high resolutions\n enabledForEffects: true,// Auto scale effects at high resolutions (when main autoScale disabled)\n maxHeight: 1536, // Height threshold (above this, scale is reduced)\n scaleFactor: 0.5, // Factor to apply when above threshold\n },\n jitter: false, // TAA-like sub-pixel jitter\n jitterAmount: 0.37, // Jitter amplitude in pixels\n jitterFadeDistance: 25.0, // Distance at which jitter fades to 0\n pixelRounding: 0, // Pixel grid size for vertex snapping (0=off, 1=every pixel, 2=every 2px, etc.)\n pixelExpansion: 0, // Sub-pixel expansion to convert gaps to overlaps (0=off, 0.05=default)\n positionRounding: 0, // Round view-space position to this precision (0 = disabled, simulates fixed-point)\n alphaHash: false, // Enable alpha hashing/dithering for cutout transparency (global default)\n alphaHashScale: 1.0, // Scale factor for alpha hash threshold (higher = more opaque)\n luminanceToAlpha: false, // Derive alpha from color luminance (for old game assets where black=transparent)\n tonemapMode: 0, // 0=ACES, 1=Reinhard, 2=None (linear clamp)\n },\n\n // Noise settings for dithering, jittering, etc.\n noise: {\n type: 'bluenoise', // 'bluenoise', 'bayer8' (8x8 ordered dither)\n animated: false, // Animate noise offset each frame (temporal variation)\n },\n\n // Dithering settings (PS1-style color quantization)\n dithering: {\n enabled: false, // Enable/disable dithering\n colorLevels: 32, // Color levels per channel (32 = 5-bit like PS1, 256 = 8-bit, 16 = 4-bit)\n },\n\n // Bloom/Glare settings (HDR glow effect)\n bloom: {\n enabled: true, // Enable/disable bloom\n intensity: 0.12, // Overall bloom intensity\n threshold: 0.98, // Brightness threshold (pixels below this contribute exponentially less)\n softThreshold: 0.5, // Soft knee for threshold (0 = hard, 1 = very soft)\n radius: 64, // Blur radius in pixels (scaled by renderScale)\n emissiveBoost: 6.0, // Extra boost for emissive pixels\n maxBrightness: 6.0, // Clamp input brightness (prevents specular halos)\n scale: 0.5, // Resolution scale (0.5 = half res for performance, 1.0 = full)\n },\n\n // Environment/IBL settings\n environment: {\n texture: \"alps_field.jpg\", // .jpg = octahedral RGBM pair, .hdr = equirectangular\n //texture: \"alps_field_8k.hdr\",\n diffuse: 3.0,\n specular: 1.0,\n emissionFactor: [1.0, 1.0, 1.0, 1.0],\n ambientColor: [0.7, 0.75, 0.9, 0.1],\n exposure: 1.6,\n fog: {\n enabled: true,\n color: [100/255.0, 135/255.0, 170/255.0],\n distances: [0, 15, 50],\n alpha: [0.0, 0.5, 0.9],\n heightFade: [-2, 185], // [bottomY, topY] - full fog at bottomY, zero at topY\n brightResist: 0.0, // How much bright/emissive colors resist fog (0-1)\n debug: 0,\n }\n },\n\n // Main directional light\n mainLight: {\n enabled: true,\n intensity: 1,\n color: [1.0, 0.78, 0.47], // Mid-morning / mid-afternoon (solar elevation ~20°–40°, less red) - soft warm white\n direction: [-0.4, 0.45, 0.35],\n },\n\n // Shadow settings\n shadow: {\n mapSize: 2048,\n cascadeCount: 3,\n cascadeSizes: [10, 25, 125], // Half-widths in meters\n maxSpotShadows: 16,\n spotTileSize: 512,\n spotAtlasSize: 2048,\n spotMaxDistance: 60, // No spot shadow beyond this distance\n spotFadeStart: 55, // Spot shadow fade out starts here\n bias: 0.0005,\n normalBias: 0.015, // ~2-3 texels for shadow acne\n surfaceBias: 0, // Scale shadow projection larger (0.01 = 1% larger)\n strength: 1.0,\n //frustum: false,\n //hiZ: false,\n },\n\n // Ambient Occlusion settings\n ao: {\n enabled: true, // Enable/disable AO\n intensity: 1.6, // Overall AO strength\n radius: 64.0, // Sample radius in pixels\n fadeDistance: 40.0, // Distance at which AO fades to 0\n bias: 0.005, // Depth bias to avoid self-occlusion\n sampleCount: 16, // Number of samples\n level: 1, // AO level multiplier\n },\n\n // Lighting pass settings\n lighting: {\n maxLights: 768,\n tileSize: 16, // Tile size for tiled deferred\n maxLightsPerTile: 256,\n maxDistance: 250, // Max distance for point lights from camera\n cullingEnabled: true,\n directSpecularMultiplier: 3.0, // Multiplier for direct light specular highlights\n specularBoost: 64.0, // Extra specular from 3 fake lights (0 = disabled)\n specularBoostRoughnessCutoff: 0.70, // Only boost materials with roughness < this\n },\n\n // Culling configuration per pass type\n culling: {\n frustumEnabled: true, // Enable frustum culling\n shadow: {\n frustum: true, // Enable frustum culling using shadow bounding spheres\n hiZ: true, // Enable HiZ occlusion culling using shadow bounding spheres\n cascadeFilter: true, // Enable per-cascade instance filtering\n maxDistance: 250,\n maxSkinned: 32,\n minPixelSize: 1,\n fadeStart: 0.8, // Distance fade starts at 80% of maxDistance\n },\n reflection: {\n frustum: true,\n maxDistance: 50,\n maxSkinned: 0,\n minPixelSize: 4,\n fadeStart: 0.8,\n },\n planarReflection: {\n frustum: true,\n maxDistance: 40,\n maxSkinned: 32,\n minPixelSize: 4,\n fadeStart: 0.7, // Earlier fade for reflections (70%)\n },\n main: {\n frustum: true,\n maxDistance: 250,\n maxSkinned: 500,\n minPixelSize: 2,\n fadeStart: 0.8, // Distance fade starts at 90% of maxDistance\n },\n },\n\n // HiZ Occlusion Culling - uses previous frame's depth buffer\n occlusionCulling: {\n enabled: true, // Enable HiZ occlusion culling\n threshold: 0.7, // Depth threshold multiplier (0.5 = 50% of maxZ, 1.0 = 100%)\n positionThreshold: 1.0, // Camera movement (units) before invalidation\n rotationThreshold: 0.1, // Camera rotation (radians, ~1 deg) before invalidation\n maxTileSpan: 16, // Max tiles a bounding sphere can span for occlusion test\n },\n\n // Skinned mesh rendering\n skinning: {\n individualRenderDistance: 20.0, // Proximity threshold for individual rendering\n },\n\n // Screen Space Global Illumination (tile-based light propagation)\n ssgi: {\n enabled: true,\n intensity: 1.0, // GI intensity multiplier\n emissiveBoost: 2.0, // Boost factor for emissive surfaces\n maxBrightness: 4.0, // Clamp luminance (excludes specular highlights)\n sampleRadius: 3, // Vogel disk sample radius in tiles\n saturateLevel: 0.5, // Logarithmic saturation level for indirect light\n },\n\n // Volumetric Fog (light scattering through particles)\n volumetricFog: {\n enabled: false, // Disabled by default (performance impact)\n resolution: 0.125, // 1/4 render resolution for ray marching\n maxSamples: 32, // Ray march samples (8-32)\n blurRadius: 8.0, // Gaussian blur radius\n densityMultiplier: 1.0, // Multiplies base fog density\n scatterStrength: 0.35, // Light scattering intensity\n mainLightScatter: 1.4, // Main directional light scattering boost\n mainLightScatterDark: 5.0, // Main directional light scattering boost\n mainLightSaturation: 0.15, // Main light color saturation in fog\n maxFogOpacity: 0.3, // Maximum fog opacity (0-1)\n heightRange: [-2, 8], // [bottom, top] Y bounds for fog (low ground fog)\n windDirection: [1, 0, 0.2], // Wind direction for fog animation\n windSpeed: 0.5, // Wind speed multiplier\n noiseScale: 0.9, // 3D noise frequency (higher = finer detail)\n noiseStrength: 0.8, // Noise intensity (0 = uniform, 1 = full variation)\n noiseOctaves: 6, // Noise detail layers\n noiseEnabled: true, // Enable 3D noise (disable for debug)\n lightingEnabled: true, // Light fog from scene lights\n shadowsEnabled: true, // Apply shadows to fog\n brightnessThreshold: 0.8, // Scene luminance where fog starts fading (like bloom)\n minVisibility: 0.15, // Minimum fog visibility over bright surfaces (0-1)\n skyBrightness: 1.2, // Virtual brightness for sky pixels (depth at far plane)\n //debugSkyCheck: true\n },\n\n // Planar Reflections (alternative to SSR for water/floor)\n planarReflection: {\n enabled: true, // Disabled by default (use SSR instead)\n groundLevel: 0.1, // Y coordinate of reflection plane (real-time adjustable)\n resolution: 1, // Resolution multiplier (0.5 = half res)\n roughnessCutoff: 0.4, // Only reflect on surfaces with roughness < this\n normalPerturbation: 0.25, // Amount of normal-based distortion (for water)\n blurSamples: 4, // Blur samples based on roughness\n intensity: 1.0, // Reflection brightness (0.9 = 90% for realism)\n distanceFade: 0.5, // Distance from ground for full reflection (meters)\n },\n\n // Ambient Capture (6-directional sky-aware GI)\n ambientCapture: {\n enabled: true, // Enable 6-directional ambient capture\n intensity: 1.0, // Output intensity multiplier (subtle effect)\n maxDistance: 50, // Distance fade & culling in meters\n resolution: 64, // Capture resolution per face (default 64)\n emissiveBoost: 10.0, // Boost for emissive surfaces in capture\n smoothingTime: 0.3, // Temporal smoothing duration in seconds\n saturateLevel: 0.2, // Logarithmic saturation level (0 = disabled)\n },\n\n // Temporal accumulation settings\n temporal: {\n blendFactor: 0.5, // Default history blend (conservative)\n motionScale: 10.0, // Motion rejection sensitivity\n depthThreshold: 0.1, // Depth rejection threshold\n normalThreshold: 0.9, // Normal rejection threshold (dot product)\n },\n\n // Performance auto-tuning\n performance: {\n autoDisable: true, // Auto-disable SSR/SSGI on low FPS\n fpsThreshold: 60, // FPS threshold for auto-disable\n disableDelay: 3.0, // Seconds below threshold before disabling\n },\n\n // CRT effect (retro monitor simulation)\n crt: {\n enabled: false, // Enable CRT effect (geometry, scanlines, etc.)\n upscaleEnabled: false, // Enable upscaling (pixelated look) even when CRT disabled\n upscaleTarget: 4, // Target upscale multiplier (4x render resolution)\n maxTextureSize: 4096, // Max upscaled texture dimension\n\n // Geometry distortion\n curvature: 0.14, // Screen curvature amount (0-0.15)\n cornerRadius: 0.055, // Rounded corner radius (0-0.1)\n zoom: 1.06, // Zoom to compensate for curvature shrinkage\n\n // Scanlines (electron beam simulation - Gaussian profile)\n scanlineIntensity: 0.4, // Scanline effect strength (0-1)\n scanlineWidth: 0.0, // Beam width (0=thin/center only, 1=no gap)\n scanlineBrightBoost: 0.8, // Bright pixels widen beam to fill gaps (0-1)\n scanlineHeight: 5, // Scanline height in canvas pixels\n\n // RGB convergence error (color channel misalignment)\n convergence: [0.79, 0.0, -0.77], // RGB X offset in source pixels\n\n // Phosphor mask\n maskType: 'aperture', // 'aperture', 'slot', 'shadow', 'none'\n maskIntensity: 0.25, // Mask strength (0-1)\n maskScale: 1.0, // Mask size multiplier\n\n // Vignette (edge darkening)\n vignetteIntensity: 0.54, // Edge darkening strength (0-1)\n vignetteSize: 0.85, // Vignette size (larger = more visible)\n\n // Horizontal blur (beam softness)\n blurSize: 0.79, // Horizontal blur in pixels (0-2)\n },\n}\n\n// Function to create WebGPU context\nasync function createWebGPUContext(engine, canvasId) {\n try {\n const canvas = document.getElementById(canvasId)\n if (!canvas) throw new Error(`Canvas with id ${canvasId} not found`)\n engine.canvas = canvas\n\n // Detailed WebGPU availability check\n console.log(\"WebGPU check:\", {\n hasNavigatorGpu: !!navigator.gpu,\n isSecureContext: window.isSecureContext,\n protocol: window.location.protocol,\n hostname: window.location.hostname\n })\n\n if (!navigator.gpu) {\n if (!window.isSecureContext) {\n throw new Error(\"WebGPU requires HTTPS or localhost (secure context)\")\n }\n throw new Error(\"WebGPU not supported on this browser.\")\n }\n\n // Try high-performance adapter first, fall back to any adapter\n let adapter = await navigator.gpu.requestAdapter({\n powerPreference: 'high-performance',\n })\n if (!adapter) {\n console.warn(\"High-performance adapter not found, trying default...\")\n adapter = await navigator.gpu.requestAdapter()\n }\n if (!adapter) throw new Error(\"No appropriate GPUAdapter found.\")\n\n // Log adapter info for debugging\n const adapterInfo = await adapter.requestAdapterInfo?.() || {}\n console.log(\"WebGPU Adapter:\", adapterInfo.vendor, adapterInfo.device, adapterInfo.description)\n console.log(\"Adapter limits:\", {\n maxColorAttachmentBytesPerSample: adapter.limits.maxColorAttachmentBytesPerSample,\n maxStorageBufferBindingSize: adapter.limits.maxStorageBufferBindingSize,\n maxBufferSize: adapter.limits.maxBufferSize\n })\n\n const canTimestamp = adapter.features.has('timestamp-query');\n const requiredFeatures = []\n if (canTimestamp) {\n requiredFeatures.push('timestamp-query')\n engine.canTimestamp = true\n } else {\n engine.canTimestamp = false\n }\n\n // Request higher limits for GBuffer with multiple render targets\n // Default is 32 bytes, but we need 36+ for albedo + normal + ARM + emission + velocity\n const requiredLimits = {}\n const adapterLimits = adapter.limits\n if (adapterLimits.maxColorAttachmentBytesPerSample >= 64) {\n requiredLimits.maxColorAttachmentBytesPerSample = 64\n } else if (adapterLimits.maxColorAttachmentBytesPerSample >= 48) {\n requiredLimits.maxColorAttachmentBytesPerSample = 48\n }\n\n // Try to create device with requested features/limits, fall back if it fails\n let device\n try {\n device = await adapter.requestDevice({\n requiredFeatures: requiredFeatures,\n requiredLimits: requiredLimits\n })\n } catch (deviceError) {\n console.warn(\"Device creation failed with custom limits, trying defaults...\", deviceError)\n // Try without custom limits\n try {\n device = await adapter.requestDevice({\n requiredFeatures: requiredFeatures\n })\n } catch (deviceError2) {\n console.warn(\"Device creation failed with features, trying minimal...\", deviceError2)\n // Try with no features at all\n device = await adapter.requestDevice()\n engine.canTimestamp = false\n }\n }\n\n if (!device) throw new Error(\"Failed to create GPU device\")\n\n const context = canvas.getContext(\"webgpu\")\n if (!context) throw new Error(\"Failed to get WebGPU context from canvas\")\n\n const canvasFormat = navigator.gpu.getPreferredCanvasFormat()\n\n engine.adapter = adapter\n engine.device = device\n engine.context = context\n engine.canvasFormat = canvasFormat\n engine.rendering = true\n\n function configureContext() {\n // Use exact device pixel size if available (from ResizeObserver)\n // This ensures pixel-perfect rendering for CRT effects\n let pixelWidth, pixelHeight\n if (engine._devicePixelSize) {\n pixelWidth = engine._devicePixelSize.width\n pixelHeight = engine._devicePixelSize.height\n } else {\n // Fallback to clientWidth * devicePixelRatio\n const devicePixelRatio = window.devicePixelRatio || 1\n pixelWidth = Math.round(canvas.clientWidth * devicePixelRatio)\n pixelHeight = Math.round(canvas.clientHeight * devicePixelRatio)\n }\n\n // Canvas is ALWAYS at full device pixel resolution for pixel-perfect CRT\n // Render scale only affects internal render passes, not the final canvas\n canvas.width = pixelWidth\n canvas.height = pixelHeight\n\n // Store device pixel size for CRT pass\n engine._canvasPixelSize = { width: pixelWidth, height: pixelHeight }\n\n context.configure({\n device: device,\n format: canvasFormat,\n alphaMode: \"opaque\",\n })\n }\n\n configureContext()\n engine.configureContext = configureContext\n\n // Make available globally for debugging\n if (typeof window !== 'undefined') {\n window.engine = engine\n }\n } catch (error) {\n console.error(\"WebGPU initialization failed:\", error)\n // Provide more specific error message\n let errorTitle = \"WebGPU Error\"\n let errorDetail = error.message\n if (error.message.includes(\"not supported\")) {\n errorTitle = \"WebGPU Not Available\"\n errorDetail = \"Check if WebGPU is enabled in browser flags\"\n } else if (error.message.includes(\"Adapter\")) {\n errorTitle = \"GPU Not Found\"\n errorDetail = \"No compatible GPU adapter found\"\n } else if (error.message.includes(\"device\")) {\n errorTitle = \"Device Creation Failed\"\n errorDetail = error.message + \" - Try updating GPU drivers\"\n }\n fail(engine, errorTitle, errorDetail)\n }\n return engine\n}\n\n/**\n * Deep merge source into target (mutates target)\n * Arrays are replaced, not merged\n */\nfunction deepMerge(target, source) {\n if (!source) return target\n for (const key in source) {\n if (source[key] && typeof source[key] === 'object' && !Array.isArray(source[key])) {\n if (!target[key] || typeof target[key] !== 'object') {\n target[key] = {}\n }\n deepMerge(target[key], source[key])\n } else {\n target[key] = source[key]\n }\n }\n return target\n}\n\nclass Engine {\n\n constructor(settings = {}) {\n this.lastTime = performance.now()\n // Deep clone DEFAULT_SETTINGS and merge with provided settings\n this.settings = deepMerge(\n JSON.parse(JSON.stringify(DEFAULT_SETTINGS)),\n settings\n )\n\n // GPU state properties (populated by createWebGPUContext)\n this.device = null\n this.context = null\n this.canvas = null\n this.canvasFormat = null\n this.canTimestamp = false\n this.configureContext = null\n\n // Runtime state\n this.rendering = true\n this._renderInProgress = false // Prevents frame pileup when GPU is slow\n this.renderTextures = []\n this.renderScale = 1\n this.options = {\n debug: false,\n nearestFiltering: false,\n mipBias: 0,\n }\n this.stats = {\n fps: 0,\n ms: 0,\n drawCalls: 0,\n triangles: 0,\n }\n\n // Debug UI (lazy initialization - created on first debug mode)\n this.debugUI = new DebugUI(this)\n\n this.init()\n }\n\n // Convenience getter/setter for debugMode (used frequently)\n get debugMode() { return this.settings.engine.debugMode }\n set debugMode(value) { this.settings.engine.debugMode = value }\n\n async init() {\n try {\n await createWebGPUContext(this, \"webgpu-canvas\")\n if (!this.rendering) return\n\n // Legacy mesh storage (for backward compatibility)\n this.meshes = {}\n\n // New data-oriented systems\n this.entityManager = new EntityManager()\n this.assetManager = new AssetManager(this)\n\n // Expose for convenience\n this.entities = this.entityManager.entities\n this.assets = this.assetManager.assets\n\n await this._create()\n await this.create()\n await this._after_create()\n\n this._lastTime = performance.now()\n this.time = 0.0\n this.frame = 0\n this.stats.avg_dt = 17\n this.stats.avg_fps = 60\n this.stats.avg_dt_render = 0.1\n\n requestAnimationFrame(() => this._frame())\n\n // Use ResizeObserver with devicePixelContentBoxSize for pixel-perfect sizing\n this._devicePixelSize = null\n try {\n const resizeObserver = new ResizeObserver((entries) => {\n for (const entry of entries) {\n // Prefer devicePixelContentBoxSize for exact device pixels\n if (entry.devicePixelContentBoxSize) {\n const size = entry.devicePixelContentBoxSize[0]\n this._devicePixelSize = {\n width: size.inlineSize,\n height: size.blockSize\n }\n } else if (entry.contentBoxSize) {\n // Fallback to contentBoxSize * devicePixelRatio\n const size = entry.contentBoxSize[0]\n const dpr = window.devicePixelRatio || 1\n this._devicePixelSize = {\n width: Math.round(size.inlineSize * dpr),\n height: Math.round(size.blockSize * dpr)\n }\n }\n this.needsResize = true\n }\n })\n resizeObserver.observe(this.canvas, { box: 'device-pixel-content-box' })\n } catch (e) {\n // Fallback if device-pixel-content-box not supported\n console.log('ResizeObserver device-pixel-content-box not supported, falling back to window resize')\n window.addEventListener(\"resize\", () => {\n this.needsResize = true\n })\n }\n\n setInterval(() => {\n if (this.needsResize && !this._resizing) {\n this.needsResize = false\n this._resize()\n }\n }, 100)\n this._resize()\n } catch (error) {\n fail(this, \"Error\", error.message)\n console.error(error)\n }\n }\n\n _frame() {\n // Skip if previous frame is still rendering (prevents GPU command queue backup)\n if (this._renderInProgress) {\n requestAnimationFrame(() => this._frame())\n return\n }\n\n let t1 = performance.now()\n let dt = t1 - this._lastTime\n this._lastTime = t1\n if (this.rendering && dt > 0 && dt < 100 && !this._resizing) {\n this.stats.dt = dt\n this.stats.fps = 1000 / dt\n this.stats.avg_dt = this.stats.avg_dt * 0.98 + this.stats.dt * 0.02\n this.stats.avg_fps = this.stats.avg_fps * 0.98 + this.stats.fps * 0.02\n let dtt = dt / 1000.0\n this.time += dtt\n this._update(dtt)\n this.update(dtt)\n let t2 = performance.now()\n\n // Mark render in progress to prevent frame pileup\n this._renderInProgress = true\n this._render().finally(() => {\n this._renderInProgress = false\n })\n\n let t3 = performance.now()\n let dtr = t3 - t2\n this.stats.dt_render = dtr\n this.stats.avg_dt_render = this.stats.avg_dt_render * 0.98 + dtr * 0.02\n\n // Calculate totals including all passes\n const shadowDC = this.stats.shadowDrawCalls || 0\n const shadowTri = this.stats.shadowTriangles || 0\n const planarDC = this.stats.planarDrawCalls || 0\n const planarTri = this.stats.planarTriangles || 0\n const transparentDC = this.stats.transparentDrawCalls || 0\n const transparentTri = this.stats.transparentTriangles || 0\n const totalDC = this.stats.drawCalls + shadowDC + planarDC + transparentDC\n const totalTri = this.stats.triangles + shadowTri + planarTri + transparentTri\n this.stats.shadowDC = shadowDC\n this.stats.shadowTri = shadowTri\n this.stats.planarDC = planarDC\n this.stats.planarTri = planarTri\n this.stats.transparentDC = transparentDC\n this.stats.transparentTri = transparentTri\n this.stats.totalDC = totalDC\n this.stats.totalTri = totalTri\n this.frame++\n\n // Update debug UI (checks debug mode internally, lazy init)\n if (this.debugUI) {\n this.debugUI.update()\n }\n }\n requestAnimationFrame(() => this._frame())\n }\n\n async _render() {\n // Pass delta time to renderer for animation updates\n const dt = this.stats.dt ? this.stats.dt / 1000.0 : 0.016\n\n await this.renderer.renderEntities({\n entityManager: this.entityManager,\n assetManager: this.assetManager,\n camera: this.camera,\n meshes: this.meshes,\n dt\n })\n }\n\n async loadGltf(url, options = {}) {\n const result = await loadGltf(this, url, options)\n // Handle both old format (just meshes) and new format (with skins, animations)\n const meshes = result.meshes || result\n for (const [name, mesh] of Object.entries(meshes)) {\n this.meshes[name] = mesh\n }\n // Store skins and animations for access\n if (result.skins) {\n this.skins = this.skins || []\n this.skins.push(...result.skins)\n }\n if (result.animations) {\n this.animations = this.animations || []\n this.animations.push(...result.animations)\n }\n return result\n }\n\n /**\n * Load a GLTF file and register with asset manager (new data-oriented API)\n * @param {string} url - Path to the GLTF file\n * @param {Object} options - Loading options\n * @returns {Promise<Object>} Asset manager entry\n */\n async loadAsset(url, options = {}) {\n const result = await this.assetManager.loadGltfFile(url, options)\n\n // Also store in legacy meshes for backward compatibility\n if (result.meshes) {\n for (const [name, mesh] of Object.entries(result.meshes)) {\n this.meshes[name] = mesh\n // Ensure at least one instance exists for rendering\n if (mesh.geometry.instanceCount === 0) {\n // Use geometry bounding sphere for correct shadow culling\n const localBsphere = mesh.geometry.getBoundingSphere?.()\n const center = localBsphere?.center || [0, 0, 0]\n const radius = localBsphere?.radius || 1\n mesh.addInstance(center, radius)\n mesh.updateInstance(0, mat4.create())\n }\n }\n }\n\n return result\n }\n\n /**\n * Load a GLTF/GLB file and render meshes directly in the scene at their original positions.\n * Unlike loadAsset, this doesn't hide meshes - they render immediately with their transforms.\n * Handles Blender's Z-up coordinate system by respecting the full node hierarchy.\n *\n * @param {string} url - Path to the GLTF/GLB file\n * @param {Object} options - Loading options\n * @param {Array} options.position - Optional position offset [x, y, z]\n * @param {Array} options.rotation - Optional rotation offset [x, y, z] in radians\n * @param {number} options.scale - Optional uniform scale multiplier\n * @param {boolean} options.doubleSided - Optional: force all materials to be double-sided\n * @returns {Promise<Object>} Object containing { meshes, nodes, skins, animations }\n */\n async loadScene(url, options = {}) {\n const result = await loadGltf(this, url, options)\n const { meshes, nodes } = result\n\n // Apply scene-wide doubleSided option if specified\n if (options.doubleSided) {\n for (const mesh of Object.values(meshes)) {\n if (mesh.material) {\n mesh.material.doubleSided = true\n }\n }\n }\n\n // Update node world matrices from their hierarchy\n // This handles Blender's Z-up to Y-up rotation in parent nodes\n for (const node of nodes) {\n if (!node.parent) {\n // Root nodes - start the hierarchy update\n node.updateMatrix(null)\n }\n }\n\n // Optional root transform from options\n const rootTransform = mat4.create()\n if (options.position || options.rotation || options.scale) {\n const pos = options.position || [0, 0, 0]\n const rot = options.rotation || [0, 0, 0]\n const scl = options.scale || 1\n\n const rotQuat = quat.create()\n quat.fromEuler(rotQuat, rot[0] * 180 / Math.PI, rot[1] * 180 / Math.PI, rot[2] * 180 / Math.PI)\n\n mat4.fromRotationTranslationScale(\n rootTransform,\n rotQuat,\n pos,\n [scl, scl, scl]\n )\n }\n\n // For skinned models with multiple submeshes, compute a combined bounding sphere\n // This ensures all submeshes are culled together as a unit (especially for shadows)\n let combinedBsphere = null\n const hasAnySkin = Object.values(meshes).some(m => m.hasSkin)\n\n if (hasAnySkin) {\n // Collect all vertex positions from ALL meshes\n const allPositions = []\n for (const mesh of Object.values(meshes)) {\n const positions = mesh.geometry?.attributes?.position\n if (positions) {\n for (let i = 0; i < positions.length; i += 3) {\n allPositions.push(positions[i], positions[i + 1], positions[i + 2])\n }\n }\n }\n\n if (allPositions.length > 0) {\n // Calculate combined bounding sphere\n const { calculateBoundingSphere } = await import('./utils/BoundingSphere.js')\n combinedBsphere = calculateBoundingSphere(new Float32Array(allPositions))\n }\n }\n\n // For each mesh, find its node and compute world transform\n for (const [name, mesh] of Object.entries(meshes)) {\n // Find the node that references this mesh by nodeIndex\n let meshNode = null\n if (mesh.nodeIndex !== null && mesh.nodeIndex !== undefined) {\n meshNode = nodes[mesh.nodeIndex]\n }\n\n // Compute final world matrix\n const worldMatrix = mat4.create()\n if (meshNode) {\n mat4.copy(worldMatrix, meshNode.world)\n }\n\n // Apply optional root transform\n if (options.position || options.rotation || options.scale) {\n mat4.multiply(worldMatrix, rootTransform, worldMatrix)\n }\n\n // Compute world bounding sphere from geometry bsphere + world transform\n // For skinned models, use the combined bsphere so all submeshes are culled together\n const localBsphere = (hasAnySkin && combinedBsphere) ? combinedBsphere : mesh.geometry.getBoundingSphere?.()\n let worldCenter = [0, 0, 0]\n let worldRadius = 1\n\n if (localBsphere && localBsphere.radius > 0) {\n // Transform local bsphere center by world matrix\n const c = localBsphere.center\n worldCenter = [\n worldMatrix[0] * c[0] + worldMatrix[4] * c[1] + worldMatrix[8] * c[2] + worldMatrix[12],\n worldMatrix[1] * c[0] + worldMatrix[5] * c[1] + worldMatrix[9] * c[2] + worldMatrix[13],\n worldMatrix[2] * c[0] + worldMatrix[6] * c[1] + worldMatrix[10] * c[2] + worldMatrix[14]\n ]\n // Scale radius by the largest axis scale in the transform\n const scaleX = Math.sqrt(worldMatrix[0]**2 + worldMatrix[1]**2 + worldMatrix[2]**2)\n const scaleY = Math.sqrt(worldMatrix[4]**2 + worldMatrix[5]**2 + worldMatrix[6]**2)\n const scaleZ = Math.sqrt(worldMatrix[8]**2 + worldMatrix[9]**2 + worldMatrix[10]**2)\n worldRadius = localBsphere.radius * Math.max(scaleX, scaleY, scaleZ)\n }\n\n // Store combined bsphere on mesh for shadow pass culling\n if (hasAnySkin && combinedBsphere) {\n mesh.combinedBsphere = combinedBsphere\n }\n\n // Add instance with world bounding sphere\n mesh.addInstance(worldCenter, worldRadius)\n mesh.updateInstance(0, worldMatrix)\n\n // Mark as static so instance count doesn't get reset by entity system\n mesh.static = true\n\n // Update geometry buffers\n if (mesh.geometry?.update) {\n mesh.geometry.update()\n }\n\n // Register mesh for rendering\n this.meshes[name] = mesh\n }\n\n // Store skins and animations\n if (result.skins) {\n this.skins = this.skins || []\n this.skins.push(...result.skins)\n }\n if (result.animations) {\n this.animations = this.animations || []\n this.animations.push(...result.animations)\n }\n\n return result\n }\n\n /**\n * Create a new entity (new data-oriented API)\n * @param {Object} data - Entity data\n * @returns {string} Entity ID\n */\n createEntity(data = {}) {\n const entityId = this.entityManager.create(data)\n\n // If entity has a model, ensure asset is loaded\n if (data.model) {\n const { path, meshName } = this.assetManager.parseModelId(data.model)\n\n // Check if asset is ready, if so update bounding sphere\n const meshAsset = this.assetManager.get(data.model)\n if (meshAsset) {\n this.entityManager.updateBoundingSphere(entityId, meshAsset.bsphere)\n } else {\n // Register callback to update bsphere when asset loads\n this.assetManager.onReady(data.model, (asset) => {\n this.entityManager.updateBoundingSphere(entityId, asset.bsphere)\n })\n }\n }\n\n return entityId\n }\n\n /**\n * Update an entity\n * @param {string} id - Entity ID\n * @param {Object} data - Properties to update\n */\n updateEntity(id, data) {\n const result = this.entityManager.update(id, data)\n\n // If model changed, update bounding sphere\n if (data.model) {\n const meshAsset = this.assetManager.get(data.model)\n if (meshAsset) {\n this.entityManager.updateBoundingSphere(id, meshAsset.bsphere)\n }\n }\n\n return result\n }\n\n /**\n * Delete an entity\n * @param {string} id - Entity ID\n */\n deleteEntity(id) {\n return this.entityManager.delete(id)\n }\n\n /**\n * Get entity by ID\n * @param {string} id - Entity ID\n * @returns {Object|null} Entity or null if not found\n */\n getEntity(id) {\n return this.entityManager.get(id)\n }\n\n /**\n * Invalidate occlusion culling data and reset warmup period.\n * Call this after scene loading or major camera teleportation to prevent\n * incorrect occlusion culling with stale depth buffer data.\n */\n invalidateOcclusionCulling() {\n if (this.renderer) {\n this.renderer.invalidateOcclusionCulling()\n }\n }\n\n async _create() {\n let camera = new Camera(this) // Pass engine reference\n camera.updateMatrix()\n camera.updateView()\n this.camera = camera\n\n // Create hidden GUI canvas for 2D overlay (UI, debugging)\n this.guiCanvas = document.createElement('canvas')\n this.guiCanvas.style.display = 'none'\n this.guiCtx = this.guiCanvas.getContext('2d')\n // Initial size will be set by _resize()\n\n // Load environment based on file extension\n // .jpg = octahedral RGBM pair, .hdr = equirectangular\n const envTexture = this.settings.environment.texture\n if (envTexture.toLowerCase().endsWith('.jpg') || envTexture.toLowerCase().endsWith('.jpeg')) {\n // Load octahedral RGBM JPG pair\n this.environment = await Texture.fromJPGPair(this, envTexture)\n this.environmentEncoding = 1 // octahedral\n } else {\n // Load equirectangular HDR\n this.environment = await Texture.fromImage(this, envTexture)\n this.environmentEncoding = 0 // equirectangular\n }\n this._setupInput()\n }\n\n async create() {\n }\n\n async _after_create() {\n this.renderer = await RenderGraph.create(this, this.environment, this.environmentEncoding)\n\n // Initialize raycaster for async ray intersection tests\n this.raycaster = new Raycaster(this)\n await this.raycaster.initialize()\n }\n\n _update(dt) {\n // Process input every frame\n this._updateInput();\n const ctx = this.guiCtx;\n const w = this.guiCanvas.width;\n const h = this.guiCanvas.height;\n ctx.clearRect(0, 0, w, h);\n\n // Debug: render light positions (uses engine's _debugSettings from DebugUI)\n if (this._debugSettings?.showLights) {\n this.debugRenderLights();\n }\n\n/*\n // Debug: visualize HiZ occlusion buffer\n const hizPass = this.renderer?.getPass('hiz');\n if (hizPass) {\n const info = hizPass.getTileInfo();\n const data = hizPass.getHiZData();\n\n // Log sample depth values once per second\n if (data && info.hizDataReady && Math.floor(this.time) !== this._lastHizLog) {\n this._lastHizLog = Math.floor(this.time);\n const centerIdx = Math.floor(info.tileCountY / 2) * info.tileCountX + Math.floor(info.tileCountX / 2);\n console.log(`HiZ center tile: depth=${data[centerIdx].toFixed(6)}, near=${this.camera.near}, far=${this.camera.far}`);\n }\n if (data && info.hizDataReady) {\n const tileW = w / info.tileCountX;\n const tileH = h / info.tileCountY;\n\n // Get camera near/far for depth linearization\n const near = this.camera?.near ?? 0.05;\n const far = this.camera?.far ?? 5000;\n\n ctx.font = '12px monospace';\n ctx.textAlign = 'center';\n ctx.textBaseline = 'middle';\n\n for (let y = 0; y < info.tileCountY; y++) {\n for (let x = 0; x < info.tileCountX; x++) {\n const idx = y * info.tileCountX + x;\n const maxZ = data[idx];\n\n // Linear depth: depth 0 = near, depth 1 = far\n // z = near + depth * (far - near)\n const linearZ = near + maxZ * (far - near);\n const dist = linearZ.toFixed(1);\n const label = `${dist}m`;\n\n ctx.fillStyle = 'rgba(0,0,0,0.2)';\n ctx.fillText(label, x * tileW + tileW / 2 + 1, y * tileH + tileH / 2 + 1);\n\n // White if gap (far/sky), red if geometry\n ctx.fillStyle = maxZ >= 0.999 ? 'rgba(255,255,255,0.2)' : 'rgba(255,32,32,0.2)';\n ctx.fillText(label, x * tileW + tileW / 2, y * tileH + tileH / 2);\n }\n }\n }\n }\n*/\n }\n\n update(dt) {\n }\n\n async _resize() {\n // Wait for any in-progress render to complete before resizing\n if (this._renderInProgress) {\n await new Promise(resolve => {\n const checkRender = () => {\n if (!this._renderInProgress) {\n resolve()\n } else {\n setTimeout(checkRender, 5)\n }\n }\n checkRender()\n })\n }\n\n this._resizing = true\n let t1 = performance.now()\n const { canvas, configureContext } = this\n\n // Calculate effective render scale with auto-scaling\n const autoScale = this.settings?.rendering?.autoScale\n const configuredScale = this.settings?.rendering?.renderScale ?? 1.0\n let effectiveScale = configuredScale\n\n if (autoScale?.enabled) {\n const devicePixelRatio = window.devicePixelRatio || 1\n const nativeHeight = canvas.clientHeight * devicePixelRatio\n\n if (nativeHeight > autoScale.maxHeight) {\n // Apply scale reduction for high-res displays\n effectiveScale = configuredScale * (autoScale.scaleFactor ?? 0.5)\n if (!this._autoScaleWarned) {\n console.log(`Auto-scale: Reducing render scale from ${configuredScale} to ${effectiveScale.toFixed(2)} (native height: ${nativeHeight}px > ${autoScale.maxHeight}px)`)\n this._autoScaleWarned = true\n }\n } else {\n // Restore configured scale for lower resolutions\n if (this._autoScaleWarned) {\n console.log(`Auto-scale: Restoring render scale to ${configuredScale} (native height: ${nativeHeight}px <= ${autoScale.maxHeight}px)`)\n this._autoScaleWarned = false\n }\n }\n }\n\n // Update the effective render scale\n this.renderScale = effectiveScale\n\n configureContext()\n\n // Resize GUI canvas to match render size\n if (this.guiCanvas) {\n this.guiCanvas.width = canvas.width\n this.guiCanvas.height = canvas.height\n this.guiCtx.clearRect(0, 0, canvas.width, canvas.height)\n }\n\n // Pass render scale to RenderGraph - internal passes use scaled dimensions\n // CRT pass will still output at full canvas resolution\n await this.renderer.resize(canvas.width, canvas.height, this.renderScale)\n this.resize()\n\n // Small delay before allowing renders to ensure all GPU resources are ready\n await new Promise(resolve => setTimeout(resolve, 16))\n this._resizing = false\n }\n\n async resize() {\n }\n\n _setupInput() {\n this.keys = {};\n\n // Mouse/touch movement state\n this._inputDeltaX = 0; // Accumulated raw input delta\n this._inputDeltaY = 0;\n this._smoothedX = 0; // Smoothed output\n this._smoothedY = 0;\n this._mouseSmoothing = this.settings.engine.mouseSmoothing;\n\n // Idle detection for normal mode\n this._mouseIdleTime = 0;\n this._mouseIdleThreshold = this.settings.engine.mouseIdleThreshold;\n this._mouseMovedThisFrame = false;\n\n // Pointer lock state\n this._pointerLocked = false;\n this._mouseOnCanvas = false;\n\n // Touch tracking\n this._lastTouchX = 0;\n this._lastTouchY = 0;\n\n // Keyboard events\n window.addEventListener('keydown', (e) => {\n this.keys[e.key.toLowerCase()] = true;\n\n // F10 toggles debug mode (both fly camera and debug panel)\n if (e.key === 'F10') {\n this.debugMode = !this.debugMode;\n this.settings.rendering.debug = this.debugMode;\n console.log(`Debug mode: ${this.debugMode ? 'ON' : 'OFF'}`);\n\n // Exit pointer lock when entering debug mode\n if (this.debugMode && document.pointerLockElement) {\n document.exitPointerLock();\n }\n e.preventDefault();\n }\n });\n window.addEventListener('keyup', (e) => { this.keys[e.key.toLowerCase()] = false; });\n window.addEventListener('blur', (e) => {\n this.keys = {}\n })\n // Mouse events\n window.addEventListener('mousedown', (e) => {\n this.keys['lmb'] = true;\n this._mouseOnCanvas = e.target === this.canvas;\n\n // In normal mode, click requests pointer lock for character controller\n if (!this.debugMode && e.button === 0 && this._mouseOnCanvas) {\n this.canvas.requestPointerLock();\n }\n });\n window.addEventListener('mouseup', (e) => {\n this.keys['lmb'] = false;\n this._mouseOnCanvas = false;\n });\n\n // Pointer lock change handler\n document.addEventListener('pointerlockchange', () => {\n this._pointerLocked = document.pointerLockElement !== null;\n });\n\n window.addEventListener('mousemove', (e) => {\n // In debug mode: only track when LMB pressed on canvas\n // In normal mode: always track (for character controller) when pointer locked\n if (this.debugMode) {\n if (this.keys['lmb'] && this._mouseOnCanvas) {\n this._inputDeltaX += e.movementX;\n this._inputDeltaY += e.movementY;\n }\n } else {\n // Normal mode: only when pointer is locked (clicked on canvas)\n if (this._pointerLocked) {\n this._inputDeltaX += e.movementX;\n this._inputDeltaY += e.movementY;\n this._mouseMovedThisFrame = true;\n }\n }\n });\n\n // Touch events - simulate LMB + mouse movement\n window.addEventListener('touchstart', (e) => {\n this.keys['lmb'] = true;\n if (e.touches.length > 0) {\n this._lastTouchX = e.touches[0].clientX;\n this._lastTouchY = e.touches[0].clientY;\n }\n e.preventDefault();\n }, { passive: false });\n\n window.addEventListener('touchend', (e) => {\n if (e.touches.length === 0) {\n this.keys['lmb'] = false;\n }\n });\n\n window.addEventListener('touchcancel', (e) => {\n this.keys['lmb'] = false;\n });\n\n window.addEventListener('touchmove', (e) => {\n if (e.touches.length > 0) {\n const touch = e.touches[0];\n const dx = touch.clientX - this._lastTouchX;\n const dy = touch.clientY - this._lastTouchY;\n this._inputDeltaX += dx;\n this._inputDeltaY += dy;\n this._lastTouchX = touch.clientX;\n this._lastTouchY = touch.clientY;\n this._mouseMovedThisFrame = true;\n }\n e.preventDefault();\n }, { passive: false });\n\n // Escape exits pointer lock in normal mode\n document.addEventListener('keydown', (e) => {\n if (e.key === 'Escape' && this._pointerLocked) {\n document.exitPointerLock();\n }\n });\n }\n\n /**\n * Called every frame to process smoothed input\n * Call this from your _update() method\n */\n _updateInput() {\n const dt = this.stats.dt ? this.stats.dt / 1000.0 : 0.016;\n let camera = this.camera;\n\n if (this.debugMode) {\n let moveSpeed = 0.1;\n if (this.keys[\"shift\"]) moveSpeed *= 5;\n if (this.keys[\" \"]) moveSpeed *= 0.1;\n\n // Debug mode: fly camera with WASD\n // Camera movement\n if (this.keys[\"w\"]) {\n camera.position[0] += camera.direction[0] * moveSpeed;\n camera.position[1] += camera.direction[1] * moveSpeed;\n camera.position[2] += camera.direction[2] * moveSpeed;\n }\n if (this.keys[\"s\"]) {\n camera.position[0] -= camera.direction[0] * moveSpeed;\n camera.position[1] -= camera.direction[1] * moveSpeed;\n camera.position[2] -= camera.direction[2] * moveSpeed;\n }\n if (this.keys[\"a\"]) {\n camera.position[0] -= camera.right[0] * moveSpeed;\n camera.position[1] -= camera.right[1] * moveSpeed;\n camera.position[2] -= camera.right[2] * moveSpeed;\n }\n if (this.keys[\"d\"]) {\n camera.position[0] += camera.right[0] * moveSpeed;\n camera.position[1] += camera.right[1] * moveSpeed;\n camera.position[2] += camera.right[2] * moveSpeed;\n }\n if (this.keys[\"space\"] || this.keys[\"e\"]) {\n camera.position[1] += moveSpeed;\n }\n if (this.keys[\"c\"] || this.keys[\"q\"]) {\n camera.position[1] -= moveSpeed;\n }\n\n if (this.keys[\"arrowleft\"]) camera.yaw += 0.02;\n if (this.keys[\"arrowright\"]) camera.yaw -= 0.02;\n if (this.keys[\"arrowup\"]) camera.pitch += 0.02;\n if (this.keys[\"arrowdown\"]) camera.pitch -= 0.02;\n\n // Debug mode: original behavior - only when LMB pressed\n if (this.keys['lmb']) {\n // Apply smoothing\n const dx = this._inputDeltaX - this._smoothedX;\n const dy = this._inputDeltaY - this._smoothedY;\n this._smoothedX += dx * this._mouseSmoothing;\n this._smoothedY += dy * this._mouseSmoothing;\n\n // Call handler with smoothed movement\n this.onMouseMove(this._smoothedX, this._smoothedY);\n\n // Reset accumulated input (it's been consumed)\n this._inputDeltaX = 0;\n this._inputDeltaY = 0;\n } else {\n // Decay smoothed values when not pressing\n this._smoothedX *= 0.8;\n this._smoothedY *= 0.8;\n this._inputDeltaX = 0;\n this._inputDeltaY = 0;\n\n // Still call onMouseMove with decaying values for smooth stop\n if (Math.abs(this._smoothedX) > 0.01 || Math.abs(this._smoothedY) > 0.01) {\n this.onMouseMove(this._smoothedX, this._smoothedY);\n }\n }\n } else {\n // Normal mode: always smooth, call onMouseMove, stop when idle\n\n // Check if mouse moved this frame\n if (this._mouseMovedThisFrame) {\n this._mouseIdleTime = 0;\n this._mouseMovedThisFrame = false;\n } else {\n this._mouseIdleTime += dt;\n }\n\n // Apply smoothing to accumulated input\n const dx = this._inputDeltaX - this._smoothedX;\n const dy = this._inputDeltaY - this._smoothedY;\n this._smoothedX += dx * this._mouseSmoothing;\n this._smoothedY += dy * this._mouseSmoothing;\n\n // Reset accumulated input\n this._inputDeltaX = 0;\n this._inputDeltaY = 0;\n\n // Check if there's significant movement\n const hasMovement = Math.abs(this._smoothedX) > 0.01 || Math.abs(this._smoothedY) > 0.01;\n\n // Only call onMouseMove if there's movement and not idle for too long\n if (hasMovement && this._mouseIdleTime < this._mouseIdleThreshold) {\n this.onMouseMove(this._smoothedX, this._smoothedY);\n }\n\n // Decay smoothed values when idle\n if (this._mouseIdleTime >= this._mouseIdleThreshold) {\n this._smoothedX *= 0.8;\n this._smoothedY *= 0.8;\n }\n }\n }\n\n /**\n * Debug render: draw crosses at all light positions\n * Green = visible (in front of camera), Red = not visible (behind camera or culled)\n */\n debugRenderLights() {\n const ctx = this.guiCtx;\n const w = this.guiCanvas.width;\n const h = this.guiCanvas.height;\n const crossSize = this._debugSettings?.lightCrossSize || 10;\n\n // Get camera viewProj matrix (already computed by camera)\n const viewProj = this.camera.viewProj;\n if (!viewProj) return;\n\n // Iterate through all entities with lights\n for (const entityId in this.entityManager.entities) {\n const entity = this.entityManager.entities[entityId];\n if (!entity.light?.enabled) continue;\n\n // Get world position of light\n const lightPos = [\n entity.position[0] + (entity.light.position?.[0] || 0),\n entity.position[1] + (entity.light.position?.[1] || 0),\n entity.position[2] + (entity.light.position?.[2] || 0)\n ];\n\n // Transform to clip space\n const clipPos = vec4.fromValues(lightPos[0], lightPos[1], lightPos[2], 1.0);\n vec4.transformMat4(clipPos, clipPos, viewProj);\n\n // Check if behind camera (w <= 0 means behind)\n const isBehindCamera = clipPos[3] <= 0;\n\n // Perspective divide to get NDC\n let ndcX, ndcY;\n if (!isBehindCamera) {\n ndcX = clipPos[0] / clipPos[3];\n ndcY = clipPos[1] / clipPos[3];\n } else {\n // For lights behind camera, project them to edge of screen\n ndcX = clipPos[0] < 0 ? -2 : 2;\n ndcY = clipPos[1] < 0 ? -2 : 2;\n }\n\n // Convert NDC (-1 to 1) to screen coordinates\n const screenX = (ndcX + 1) * 0.5 * w;\n const screenY = (1 - ndcY) * 0.5 * h; // Y is inverted\n\n // Check if on screen\n const isOnScreen = !isBehindCamera &&\n screenX >= 0 && screenX <= w &&\n screenY >= 0 && screenY <= h;\n\n // Color: green if visible, red if not\n ctx.strokeStyle = isOnScreen ? 'rgba(0, 255, 0, 0.9)' : 'rgba(255, 0, 0, 0.7)';\n ctx.lineWidth = 2;\n\n // Clamp to screen bounds for drawing\n const drawX = Math.max(crossSize, Math.min(w - crossSize, screenX));\n const drawY = Math.max(crossSize, Math.min(h - crossSize, screenY));\n\n // Draw cross\n ctx.beginPath();\n ctx.moveTo(drawX - crossSize, drawY);\n ctx.lineTo(drawX + crossSize, drawY);\n ctx.moveTo(drawX, drawY - crossSize);\n ctx.lineTo(drawX, drawY + crossSize);\n ctx.stroke();\n\n // Draw light type indicator\n const lightType = entity.light.lightType || 0;\n if (lightType === 2) {\n // Spotlight: draw a small cone indicator\n ctx.beginPath();\n ctx.arc(drawX, drawY, crossSize * 0.5, 0, Math.PI * 2);\n ctx.stroke();\n } else if (lightType === 1) {\n // Point light: draw small circle\n ctx.beginPath();\n ctx.arc(drawX, drawY, crossSize * 0.3, 0, Math.PI * 2);\n ctx.stroke();\n }\n }\n }\n\n onMouseMove(dx, dy) {\n }\n}\n\nexport {\n Engine,\n fail,\n Texture,\n Material,\n Geometry,\n Mesh,\n Camera,\n EntityManager,\n AssetManager,\n CullingSystem,\n InstanceManager,\n RenderGraph,\n ParticleSystem,\n ParticleEmitter,\n DebugUI,\n Raycaster,\n}\n"],"names":["mat4","vec3","vec4","quat","glMatrix","module","generateMips","_UID","vertexCount","vec2","cameraFrustum","data","makeShaderDataDefinitions","makeStructuredView","noiseTexture","geometryWGSL","lightCullingWGSL","lightingWGSL","particleSimulateWGSL","particleRenderWGSL","TILE_SIZE","hizReduceWGSL","aoWGSL","commandEncoder","ssgiAccumulateWGSL","ssgiPropagateWGSL","ssgiWGSL","renderPostWGSL","bloomExtractWGSL","bloomBlurWGSL","lightingCommonWGSL","volumetricRaymarchWGSL","volumetricBlurWGSL","volumetricCompositeWGSL","postProcessingWGSL","crtWGSL","individualSkin","individualGeometry","individualMesh","clonedSkin","phaseGeometry","phaseMesh","geometry","start","gltf","parse","GLTFLoader","calculateBoundingSphere"],"mappings":"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"}