@meimu/mutracer 0.1.3 → 0.1.4

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 (118) hide show
  1. package/dist/assets/bvh.worker.js +1 -1
  2. package/dist/mutracer.umd.js +4114 -4015
  3. package/docs/api.md +11 -9
  4. package/docs/threejs.md +30 -4
  5. package/lib/Engine.js +18 -1
  6. package/lib/Engine.js.map +1 -1
  7. package/lib/main.d.ts +1 -1
  8. package/lib/main.js.map +1 -1
  9. package/lib/oidn.d.ts +1 -1
  10. package/lib/oidnBundledWeights.cdn.d.ts +2 -2
  11. package/lib/renderer/MaterialTable.d.ts +1 -0
  12. package/lib/renderer/MaterialTable.js +10 -4
  13. package/lib/renderer/MaterialTable.js.map +1 -1
  14. package/lib/renderer/WGPUBloomPass.js +18 -18
  15. package/lib/renderer/WGPUFullQuadPass.js +6 -6
  16. package/lib/renderer/WGPUJNLMDenoiser.js +69 -69
  17. package/lib/renderer/WGPURayTraceOutput.js +14 -14
  18. package/lib/renderer/WGPURayTracePass.d.ts +2 -0
  19. package/lib/renderer/WGPURayTracePass.js +12 -6
  20. package/lib/renderer/WGPURayTracePass.js.map +1 -1
  21. package/lib/renderer/WGPURenderingPipeline.js +15 -15
  22. package/lib/renderer/WebGLJNLMDenoiser.js +69 -69
  23. package/lib/renderer/WebGLRayTraceOutputPass.js +4 -4
  24. package/lib/renderer/WebGLRayTracePass.d.ts +1 -0
  25. package/lib/renderer/WebGLRayTracePass.js +7 -1
  26. package/lib/renderer/WebGLRayTracePass.js.map +1 -1
  27. package/lib/renderer/envMapCreation.d.ts +5 -1
  28. package/lib/renderer/envMapCreation.js +42 -5
  29. package/lib/renderer/envMapCreation.js.map +1 -1
  30. package/lib/renderer/geometryUpdates.js +1 -1
  31. package/lib/renderer/geometryUpdates.js.map +1 -1
  32. package/lib/renderer/glsl/chunks/cameraRay.glsl.js +12 -12
  33. package/lib/renderer/glsl/chunks/envMap.glsl.js +66 -54
  34. package/lib/renderer/glsl/chunks/envMap.glsl.js.map +1 -1
  35. package/lib/renderer/glsl/chunks/intersect.glsl.js +316 -310
  36. package/lib/renderer/glsl/chunks/intersect.glsl.js.map +1 -1
  37. package/lib/renderer/glsl/chunks/materialBuffer.glsl.js +151 -119
  38. package/lib/renderer/glsl/chunks/materialBuffer.glsl.js.map +1 -1
  39. package/lib/renderer/glsl/chunks/openPbrBsdf.glsl.js +414 -414
  40. package/lib/renderer/glsl/chunks/rand.glsl.js +3 -3
  41. package/lib/renderer/glsl/chunks/random.glsl.js +3 -3
  42. package/lib/renderer/glsl/chunks/rayTraceCore.glsl.js +40 -39
  43. package/lib/renderer/glsl/chunks/rayTraceCore.glsl.js.map +1 -1
  44. package/lib/renderer/glsl/chunks/sampleLight.glsl.js +69 -69
  45. package/lib/renderer/glsl/chunks/sampleMaterial.glsl.js +78 -78
  46. package/lib/renderer/glsl/chunks/sampleShadowCatcher.glsl.js +12 -12
  47. package/lib/renderer/glsl/chunks/sampling.glsl.js +75 -75
  48. package/lib/renderer/glsl/chunks/sobol.glsl.js +61 -61
  49. package/lib/renderer/glsl/chunks/textureLinear.glsl.js +5 -5
  50. package/lib/renderer/glsl/chunks/uvProjection.glsl.js +11 -11
  51. package/lib/renderer/glsl/depthQuery.frag.js +5 -5
  52. package/lib/renderer/glsl/depthQuery.frag.js.map +1 -1
  53. package/lib/renderer/glsl/normalizeRayTraceOutput.frag.js +8 -8
  54. package/lib/renderer/glsl/rayTrace.frag.js +133 -133
  55. package/lib/renderer/glsl/rayTrace.frag.js.map +1 -1
  56. package/lib/renderer/glsl/shadowCatcher.frag.js +9 -9
  57. package/lib/renderer/glsl/shadowCatcher.frag.js.map +1 -1
  58. package/lib/renderer/glsl/webglPipeline.frag.js +52 -52
  59. package/lib/renderer/lightsUtil.js +6 -4
  60. package/lib/renderer/lightsUtil.js.map +1 -1
  61. package/lib/renderer/materialChanges.js +1 -1
  62. package/lib/renderer/materialChanges.js.map +1 -1
  63. package/lib/renderer/materialComparison.generated.js +36 -18
  64. package/lib/renderer/materialComparison.generated.js.map +1 -1
  65. package/lib/renderer/prepareMeshBVH.js +2 -2
  66. package/lib/renderer/prepareMeshBVH.js.map +1 -1
  67. package/lib/renderer/sceneChangeDetector.js +4 -1
  68. package/lib/renderer/sceneChangeDetector.js.map +1 -1
  69. package/lib/renderer/sceneLightChanges.js +3 -2
  70. package/lib/renderer/sceneLightChanges.js.map +1 -1
  71. package/lib/renderer/scratchSampling.js +18 -18
  72. package/lib/renderer/wgsl/chunks/common.wgsl.js +4 -4
  73. package/lib/renderer/wgsl/chunks/envMap.wgsl.d.ts +1 -1
  74. package/lib/renderer/wgsl/chunks/envMap.wgsl.js +39 -31
  75. package/lib/renderer/wgsl/chunks/envMap.wgsl.js.map +1 -1
  76. package/lib/renderer/wgsl/chunks/intersect.wgsl.js +475 -469
  77. package/lib/renderer/wgsl/chunks/intersect.wgsl.js.map +1 -1
  78. package/lib/renderer/wgsl/chunks/intersectionAlpha.wgsl.js +8 -8
  79. package/lib/renderer/wgsl/chunks/materialBuffer.wgsl.js +93 -59
  80. package/lib/renderer/wgsl/chunks/materialBuffer.wgsl.js.map +1 -1
  81. package/lib/renderer/wgsl/chunks/openPbrBsdf.wgsl.js +469 -469
  82. package/lib/renderer/wgsl/chunks/pdm_displacement.wgsl.d.ts +1 -1
  83. package/lib/renderer/wgsl/chunks/pdm_displacement.wgsl.js +117 -120
  84. package/lib/renderer/wgsl/chunks/pdm_displacement.wgsl.js.map +1 -1
  85. package/lib/renderer/wgsl/chunks/rand.wgsl.js +3 -3
  86. package/lib/renderer/wgsl/chunks/rayTraceCore.wgsl.js +25 -22
  87. package/lib/renderer/wgsl/chunks/rayTraceCore.wgsl.js.map +1 -1
  88. package/lib/renderer/wgsl/chunks/sampleLight.wgsl.js +127 -127
  89. package/lib/renderer/wgsl/chunks/sampleMaterial.wgsl.js +136 -136
  90. package/lib/renderer/wgsl/chunks/sampleShadowCatcher.wgsl.js +6 -6
  91. package/lib/renderer/wgsl/chunks/sampling.wgsl.js +73 -73
  92. package/lib/renderer/wgsl/chunks/sobol.wgsl.js +62 -62
  93. package/lib/renderer/wgsl/chunks/uvProjection.wgsl.js +11 -11
  94. package/lib/renderer/wgsl/compose.frag.wgsl.js +105 -105
  95. package/lib/renderer/wgsl/customDenoise.wgsl.js +10 -10
  96. package/lib/renderer/wgsl/depthQuery.wgsl.d.ts +1 -1
  97. package/lib/renderer/wgsl/depthQuery.wgsl.js +5 -5
  98. package/lib/renderer/wgsl/depthQuery.wgsl.js.map +1 -1
  99. package/lib/renderer/wgsl/offlineAdaptive.compute.js +28 -28
  100. package/lib/renderer/wgsl/rayTrace.compute.js +251 -248
  101. package/lib/renderer/wgsl/rayTrace.compute.js.map +1 -1
  102. package/lib/scene/EnvironmentLight.d.ts +9 -3
  103. package/lib/scene/EnvironmentLight.js +9 -2
  104. package/lib/scene/EnvironmentLight.js.map +1 -1
  105. package/lib/scene/Scene.d.ts +4 -0
  106. package/lib/scene/Scene.js.map +1 -1
  107. package/lib/scene/SpotLight.d.ts +2 -2
  108. package/lib/scene/SpotLight.js +1 -1
  109. package/lib/scene/SpotLight.js.map +1 -1
  110. package/lib/scene/Texture.d.ts +17 -6
  111. package/lib/scene/Texture.js +13 -4
  112. package/lib/scene/Texture.js.map +1 -1
  113. package/lib/util/fromTHREE.js +151 -29
  114. package/lib/util/fromTHREE.js.map +1 -1
  115. package/lib/version.d.ts +1 -1
  116. package/lib/version.js +1 -1
  117. package/lib/version.js.map +1 -1
  118. package/package.json +1 -1
@@ -1 +1 @@
1
- {"version":3,"file":"intersect.wgsl.js","sourceRoot":"","sources":["../../../../src/renderer/wgsl/chunks/intersect.wgsl.ts"],"names":[],"mappings":"AAAA,qDAAqD;AACrD,sDAAsD;AACtD,kFAAkF;AAElF,OAAO,EAAE,qBAAqB,EAAE,MAAM,wBAAwB,CAAC;AAC/D,OAAO,EAAE,oBAAoB,EAAE,MAAM,mBAAmB,CAAC;AACzD,OAAO,oBAAoB,MAAM,4BAA4B,CAAC;AAC9D,OAAO,EAAE,2BAA2B,EAAE,MAAM,6BAA6B,CAAC;AAE1E,MAAM,gBAAgB,GAAG,oBAAoB,CAAC;AAE9C,MAAM,iBAAiB,GAAG;IACxB,OAAO,UAAU,CAAC;;;;;;;;;;;;;;;;;;;CAmBnB,CAAC;AACF,CAAC,CAAC;AAEF,SAAS,qBAAqB,CAAC,MAAe;IAC5C,OAAO,SAAS,CAAC;;;;;;;;;;;;;;EAcb,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,OAAO;;;CAG9B,CAAC;AACF,CAAC;AAED,MAAM,yBAAyB,GAAG,UAAU,MAAe,EAAE,uBAAgC;IAC3F,OAAO,UAAU,CAAC;;;;;;;oEAOgD,uBAAuB,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE;oEACrC,uBAAuB,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE;oEACrC,uBAAuB,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE;;;;;;;;;;;;;;;;;EAiBrG,qBAAqB,CAAC,MAAM,CAAC;;;;;;;;;;;;;;;;;EAiB7B,iBAAiB,EAAE;;CAEtB,CAAC;AACF,CAAC,CAAC;AAEF,MAAM,kBAAkB,GAAG,UAAU,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAuGrC,CAAC;AAEF,SAAS,kBAAkB,CACzB,aAAqB,EACrB,eAAwB,EACxB,MAAe,EACf,cAAsB,EACtB,uBAAgC,EAChC,gBAAgB,GAAG,EAAE;IAErB,MAAM,kBAAkB,GAAG,UAAU,CAAC;gCACO,oBAAoB;;;;EAIzD,gBAAgB;;;;;;;;mDAQmC,uBAAuB,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE;mDACrC,uBAAuB,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE;mDACrC,uBAAuB,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE;;;;EAIxF,eAAe;QACf,CAAC,CAAC,yBAAyB,CAAC,MAAM,EAAE,uBAAuB,CAAC;QAC5D,CAAC,CAAC,iBAAiB,EAAE;;EAEnB,aAAa;;;CAGxB,CAAC;IACA,OAAO,UAAU,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;gCAiE2B,oBAAoB;;uBAEhC,oBAAoB;uBACpB,oBAAoB;uBACpB,oBAAoB;uBACpB,oBAAoB;;;;;;;;;;;;;EAa/C,CAAC,cAAc,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAgFhC,kBAAkB;;;;;;;;;;;;;;CAc3B,CAAC,CAAC,CAAC,EAAE;;;;;;;;;;;;;;;;;EAiBF,CAAC,cAAc,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAuCzC,CAAC,CAAC,CAAC,EAAE;EACJ,CAAC,cAAc,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAgFzC,CAAC,CAAC,CAAC,EAAE;EACJ,CAAC,cAAc,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAkDzC,CAAC,CAAC,CAAC,EAAE;;EAEF,kBAAkB;;;;;;;CAO7B,CAAC;AACF,CAAC;AACD,MAAM,CAAC,OAAO,UAAU,gBAAgB,CAAC,EACvC,eAAe,EACf,uBAAuB,GAAG,IAAI,EAC9B,cAAc,GAAG,KAAK,EACtB,cAAc,GAAG,CAAC,EAMnB;IACC,OAAO,QAAQ,CAAC;;EAEhB,eAAe,CAAC,CAAC,CAAC,gBAAgB,CAAC,CAAC,CAAC,EAAE;EACvC,CAAC,cAAc,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,kBAAkB,CAAC,CAAC,CAAC,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;uEA8PmB,uBAAuB,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE;uEACrC,uBAAuB,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE;uEACrC,uBAAuB,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE;;;;;;;;;EAS5G,uBAAuB,CAAC,CAAC,CAAC,UAAU,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;CAyBtC,CAAC,CAAC,CAAC,UAAU,CAAC,EAAE;;;;;;;;;;;;;;;;;2BAiByB,qBAAqB,CAAC,SAAS;;;;;;;;;;iDAUpB,uBAAuB,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE;iDACrC,uBAAuB,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE;iDACrC,uBAAuB,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;sDA4BhC,uBAAuB,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE;sDACrC,uBAAuB,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE;sDACrC,uBAAuB,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;sBAyBvE,qBAAqB,CAAC,SAAS;;;;EAIrD,uBAAuB,CAAC,CAAC,CAAC,UAAU,CAAC;EACrC,cAAc,CAAC,CAAC,CAAC,UAAU,CAAC,8BAA8B,CAAC,CAAC,CAAC,EAAE;;2JAE4F,cAAc,CAAC,CAAC,CAAC,2EAA2E,CAAC,CAAC,CAAC,EAAE;;;;;;;;;;;;;;;;;;;;;EAqB5P,cAAc,CAAC,CAAC,CAAC,UAAU,CAAC;;;;;;;;;;;;;;;;;;;CAmB7B,CAAC,CAAC,CAAC,EAAE;;;CAGL,CAAC,CAAC,CAAC,UAAU,CAAC,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;oCAuL8B,oBAAoB;;;;;;;;;;;;;;uBAcjC,oBAAoB;uBACpB,oBAAoB;uBACpB,oBAAoB;uBACpB,oBAAoB;;;uBAGpB,oBAAoB;uBACpB,oBAAoB;uBACpB,oBAAoB;uBACpB,oBAAoB;;;;;;;;;;;EAWpD,cAAc,CAAC,CAAC,CAAC,UAAU,CAAC,wDAAqE,CAAC,CAAC,CAAC,EAAE;;;;EAItG,uBAAuB,CAAC,CAAC,CAAC,UAAU,CAAC;;;;;CAKtC,CAAC,CAAC,CAAC,UAAU,CAAC,EAAE;;;;;;EAMf,2BAA2B,CAAC,uBAAuB,CAAC;;;;;;;;;;;;;;;;;;;;;;EAsBlD,kBAAkB,CAClB;;;;;CAKD,EACC,eAAe,EACf,KAAK,EACL,cAAc,EACd,uBAAuB,CACxB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAoCC,kBAAkB,CAClB;;;;;CAKC,EACD,eAAe,EACf,KAAK,EACL,cAAc,EACd,uBAAuB,EACvB;;;;CAIC,CACF;;;;;;iEAM0F,oBAAoB;;;;;;;;;;;uBAW7E,oBAAoB;uBACpB,oBAAoB;uBACpB,oBAAoB;uBACpB,oBAAoB;;;uBAGpB,oBAAoB;uBACpB,oBAAoB;uBACpB,oBAAoB;uBACpB,oBAAoB;;;;;;;;;;EAUtD,cAAc,CAAC,CAAC,CAAC,UAAU,CAAC,wDAAqE,CAAC,CAAC,CAAC,EAAE;;;;EAItG,uBAAuB,CAAC,CAAC,CAAC,UAAU,CAAC;;;;;CAKtC,CAAC,CAAC,CAAC,UAAU,CAAC,EAAE;;;;;;;;;;;;;;;;;;;;;EAqBb,kBAAkB;IAClB,SAAS,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;CAyBT,EACD,eAAe,EACf,IAAI,EACJ,cAAc,EACd,uBAAuB,CACxB;;;;;CAKF,CAAC;AACF,CAAC","sourcesContent":["// Portions adapted from ray-tracing-renderer@0.10.15\n// (https://github.com/hoverinc/ray-tracing-renderer).\n// Copyright (c) 2019 HOVER, MIT license; see LICENSES/RayTracingRenderer-MIT.txt.\n\nimport { uvProjectionConstants } from '../../MaterialTable.js';\nimport { INSTANCE_VEC4_STRIDE } from '../../bvhAccel.js';\nimport pdm_displacementWgsl from './pdm_displacement.wgsl.js';\nimport { createIntersectionAlphaWgsl } from './intersectionAlpha.wgsl.js';\n\nconst displacementWgsl = pdm_displacementWgsl;\n\nconst flatIntersectCode = function () {\n return /* wgsl */ `\nlet hit = intersectTriangle(o, d, tri, maxDim, shear, tMax);\nif (hit.t > 0.0) {\n let e1 = tri.p1 - tri.p0;\n let e2 = tri.p2 - tri.p0;\n hitNormal = normalize(cross(e1, e2));\n let sideMaterialIndex = i32(unpackMaterialIndex(bitcast<i32>(insData.z)).x);\n let materialDoubleSided = materialsBuffer[sideMaterialIndex].doubleSided > 0.5;\n if (!doubleSided && !materialDoubleSided && dot(hitNormal, d) > 0.0) {\n hitNormal = vec3f(0.0);\n continue;\n }\n\n tMax = hit.t;\n isHit = true;\n isHitCurrent = true;\n hitPosition = o + tMax * d;\n hitBarycentric = hit.barycentric;\n}\n`;\n};\n\nfunction getPDMDisplacmentCode(anyHit: boolean) {\n return /*wgsl */ `\n let microTriangle = intersectDisplacement(\n o, d, tMax,\n tri.p0, tri.p1, tri.p2,\n n0, n1, n2,\n uv0, uv1, uv2,\n displacementMapIndex,\n getMapSize(displacementMapIndex),\n minH, maxH,\n hScale,\n matData.displacementBias,\n matData.displacementStepSize,\n i32(matData.displacementUvProjection),\n displacementUvProjectionScale,\n ${anyHit ? 'true' : 'false'},\n isRefractionRay\n );\n`;\n}\n\nconst displacementIntersectCode = function (anyHit: boolean, hasAnisotropyDirections: boolean) {\n return /* wgsl */ `\n\nlet hScale = insData.w;\nlet minMaxH = bitcast<i32>(r3.w);\n// Displacement currently uses the first material of the instance.\nlet materialIndex = i32(unpackMaterialIndex(bitcast<i32>(insData.z)).x);\nlet matData = materialsBuffer[materialIndex];\nlet n0 = normalize(attrBuffers[index.x * ATTR_STRIDE + NORMAL_OFF]${hasAnisotropyDirections ? '.xyz' : ''});\nlet n1 = normalize(attrBuffers[index.y * ATTR_STRIDE + NORMAL_OFF]${hasAnisotropyDirections ? '.xyz' : ''});\nlet n2 = normalize(attrBuffers[index.z * ATTR_STRIDE + NORMAL_OFF]${hasAnisotropyDirections ? '.xyz' : ''});\nif (matData.displacementMapIndex >= 0.0 && matData.displacementBias != 0.0) {\n tri.p0 += n0 * matData.displacementBias;\n tri.p1 += n1 * matData.displacementBias;\n tri.p2 += n2 * matData.displacementBias;\n}\nif (abs(hScale) > 0.0 && minMaxH > 0) {\n let minH = f32(minMaxH >> 8) / 255.0;\n let maxH = f32(minMaxH & 0xff) / 255.0;\n let displacementUvProjectionScale = vec2f(matData.displacementUvProjectionScale);\n let displacementMapIndex = i32(matData.displacementMapIndex);\n\n // TODO duplicate reading of normal and uv\n var uv0 = attrBuffers[index.x * ATTR_STRIDE + UV_OFF].xy;\n var uv1 = attrBuffers[index.y * ATTR_STRIDE + UV_OFF].xy;\n var uv2 = attrBuffers[index.z * ATTR_STRIDE + UV_OFF].xy;\n\n ${getPDMDisplacmentCode(anyHit)}\n\n if (microTriangle.t < tMax) {\n hitNormal = microTriangle.normal;\n // TODO support double sided to improve non watertight triangles.\n\n tMax = microTriangle.t;\n // TODO barycentric is for micro triangle\n hitBarycentric = microTriangle.barycentric;\n hitPosition = o + tMax * d;\n\n isHitMicroTriangle = true;\n isHit = true;\n isHitCurrent = true;\n }\n}\nelse {\n ${flatIntersectCode()}\n}\n`;\n};\n\nconst cwbvhTraversalWgsl = /* wgsl */ `\nfn unpackCwbvhBytes(value: u32) -> vec4f {\n return vec4f(\n f32(value & 0xffu),\n f32((value >> 8u) & 0xffu),\n f32((value >> 16u) & 0xffu),\n f32(value >> 24u)\n );\n}\n\n// CWBVH calls these helpers only with nonzero 8/24-bit masks. Every such\n// integer is exactly representable as f32, whose unbiased IEEE-754 exponent\n// gives the highest set bit. This avoids both the full-width firstLeadingBit\n// polyfill on some shader backends and a serial bit-spread/popcount chain.\n// Do not use this conversion for full 32-bit masks: rounding near a power of\n// two can change their highest bit. Zero is also outside this contract.\nfn cwbvhHighestBitIndex8(value: u32) -> u32 {\n return (bitcast<u32>(f32(value)) >> 23u) - 127u;\n}\n\nfn cwbvhHighestBitIndex24(value: u32) -> u32 {\n return (bitcast<u32>(f32(value)) >> 23u) - 127u;\n}\n\nfn intersectCwbvhInterior(\n node: i32,\n bufferOffset: i32,\n n0: vec4f,\n o: vec3f,\n invD: vec3f,\n tMax: f32\n) -> u32 {\n let i = node + bufferOffset;\n let n2 = bvhBuffer[i + 2];\n let n3 = bvhBuffer[i + 3];\n let n4 = bvhBuffer[i + 4];\n let header = bitcast<u32>(n0.w);\n let exponentX = bitcast<i32>((header & 0xffu) << 24u) >> 24;\n let exponentY = bitcast<i32>(((header >> 8u) & 0xffu) << 24u) >> 24;\n let exponentZ = bitcast<i32>(((header >> 16u) & 0xffu) << 24u) >> 24;\n let quantizationScale = vec3f(\n bitcast<f32>(u32(exponentX + 127) << 23u),\n bitcast<f32>(u32(exponentY + 127) << 23u),\n bitcast<f32>(u32(exponentZ + 127) << 23u)\n );\n let padding = depthSlabPadding(invD, o);\n let originTerm = (n0.xyz - o) * invD;\n let adjustedInvD = quantizationScale * invD;\n let minX0 = unpackCwbvhBytes(bitcast<u32>(n2.x));\n let minX1 = unpackCwbvhBytes(bitcast<u32>(n2.y));\n let minY0 = unpackCwbvhBytes(bitcast<u32>(n2.z));\n let minY1 = unpackCwbvhBytes(bitcast<u32>(n2.w));\n let minZ0 = unpackCwbvhBytes(bitcast<u32>(n3.x));\n let minZ1 = unpackCwbvhBytes(bitcast<u32>(n3.y));\n let maxX0 = unpackCwbvhBytes(bitcast<u32>(n3.z));\n let maxX1 = unpackCwbvhBytes(bitcast<u32>(n3.w));\n let maxY0 = unpackCwbvhBytes(bitcast<u32>(n4.x));\n let maxY1 = unpackCwbvhBytes(bitcast<u32>(n4.y));\n let maxZ0 = unpackCwbvhBytes(bitcast<u32>(n4.z));\n let maxZ1 = unpackCwbvhBytes(bitcast<u32>(n4.w));\n\n let minTx0 = minX0 * adjustedInvD.x + originTerm.x;\n let minTy0 = minY0 * adjustedInvD.y + originTerm.y;\n let minTz0 = minZ0 * adjustedInvD.z + originTerm.z;\n let maxTx0 = maxX0 * adjustedInvD.x + originTerm.x;\n let maxTy0 = maxY0 * adjustedInvD.y + originTerm.y;\n let maxTz0 = maxZ0 * adjustedInvD.z + originTerm.z;\n let near0 = max(\n max((min(minTx0, maxTx0) - vec4f(padding.x)), (min(minTy0, maxTy0) - vec4f(padding.y))),\n (min(minTz0, maxTz0) - vec4f(padding.z))\n );\n let far0 = min(\n min((max(minTx0, maxTx0) + vec4f(padding.x)), (max(minTy0, maxTy0) + vec4f(padding.y))),\n (max(minTz0, maxTz0) + vec4f(padding.z))\n );\n\n let minTx1 = minX1 * adjustedInvD.x + originTerm.x;\n let minTy1 = minY1 * adjustedInvD.y + originTerm.y;\n let minTz1 = minZ1 * adjustedInvD.z + originTerm.z;\n let maxTx1 = maxX1 * adjustedInvD.x + originTerm.x;\n let maxTy1 = maxY1 * adjustedInvD.y + originTerm.y;\n let maxTz1 = maxZ1 * adjustedInvD.z + originTerm.z;\n let near1 = max(\n max((min(minTx1, maxTx1) - vec4f(padding.x)), (min(minTy1, maxTy1) - vec4f(padding.y))),\n (min(minTz1, maxTz1) - vec4f(padding.z))\n );\n let far1 = min(\n min((max(minTx1, maxTx1) + vec4f(padding.x)), (max(minTy1, maxTy1) + vec4f(padding.y))),\n (max(minTz1, maxTz1) + vec4f(padding.z))\n );\n\n let hits0 = (near0 <= min(far0, vec4f(tMax))) & (far0 > vec4f(0.0));\n let hits1 = (near1 <= min(far1, vec4f(tMax))) & (far1 > vec4f(0.0));\n return select(0u, 1u, hits0.x)\n | select(0u, 2u, hits0.y)\n | select(0u, 4u, hits0.z)\n | select(0u, 8u, hits0.w)\n | select(0u, 16u, hits1.x)\n | select(0u, 32u, hits1.y)\n | select(0u, 64u, hits1.z)\n | select(0u, 128u, hits1.w);\n}\n\n`;\n\nfunction getBVHTraverseCode(\n handleHitCode: string,\n hasDisplacement: boolean,\n anyHit: boolean,\n blasLayoutMask: number,\n hasAnisotropyDirections: boolean,\n skipTriangleCode = ''\n) {\n const intersectTriangles = /* wgsl */ `\n let insData = instancesBuffer[insIdx * ${INSTANCE_VEC4_STRIDE}];\n let vertexOffset = vec3i(bitcast<i32>(insData.y));\n for (var k = 0; k < primitiveCount; k++) {\n let triangleIndex = primitiveOffset + k;\n ${skipTriangleCode}\n if (bounces > 0 && triangleIndex == currentTriangleIndex) {\n continue;\n }\n \n let r3 = bvhBuffer[triangleIndex];\n let index = bitcast<vec3i>(r3.xyz) + vertexOffset;\n var tri = Triangle(\n attrBuffers[index.x * ATTR_STRIDE + POSITION_OFF]${hasAnisotropyDirections ? '.xyz' : ''},\n attrBuffers[index.y * ATTR_STRIDE + POSITION_OFF]${hasAnisotropyDirections ? '.xyz' : ''},\n attrBuffers[index.z * ATTR_STRIDE + POSITION_OFF]${hasAnisotropyDirections ? '.xyz' : ''}\n );\n var isHitCurrent = false;\n\n ${hasDisplacement\n ? displacementIntersectCode(anyHit, hasAnisotropyDirections)\n : flatIntersectCode()}\n if (isHitCurrent) {\n ${handleHitCode}\n }\n }\n`;\n return /* wgsl */ `\nvar d = ray.d;\nvar o = ray.o;\nvar tlasStack: array<i32, TLAS_STACK_SIZE>;\nvar tlasTop = -1;\nvar invD = traversalInverse(d);\nvar isHitMicroTriangle = false;\nvar isHit = false;\nvar hitPosition: vec3f;\nvar hitNormal: vec3f;\nvar hitBarycentric: vec3f;\n\n\nif (intersectBox(o, bvhBuffer[0].xyz, bvhBuffer[1].xyz, invD, tMax) > 0.0) {\n tlasTop = 0;\n tlasStack[0] = 0;\n}\nwhile (tlasTop >= 0) {\n let tlasNode = tlasStack[tlasTop];\n tlasTop--;\n o = ray.o;\n d = ray.d;\n invD = traversalInverse(d);\n\n let tlasR1 = bvhBuffer[tlasNode];\n let tlasPointerOrPrimitiveCount = bitcast<i32>(tlasR1.w);\n if (tlasPointerOrPrimitiveCount >= 0) {\n let left = tlasNode + 2;\n let right = tlasPointerOrPrimitiveCount;\n let d0 = intersectBox(\n o,\n bvhBuffer[left].xyz,\n bvhBuffer[left + 1].xyz,\n invD,\n tMax\n );\n let d1 = intersectBox(\n o,\n bvhBuffer[right].xyz,\n bvhBuffer[right + 1].xyz,\n invD,\n tMax\n );\n\n if (d0 > d1) {\n if (d0 > 0.0) { tlasTop++; tlasStack[tlasTop] = left; }\n if (d1 > 0.0) { tlasTop++; tlasStack[tlasTop] = right; }\n }\n else {\n if (d1 > 0.0) { tlasTop++; tlasStack[tlasTop] = right; }\n if (d0 > 0.0) { tlasTop++; tlasStack[tlasTop] = left; }\n }\n }\n else {\n let tlasPrimitiveCount = -tlasPointerOrPrimitiveCount;\n // The previous unified LIFO stack visited leaf instances in reverse order.\n // Preserve that order for alpha-tested any-hit rays and deterministic RNG.\n for (\n var instanceOffset = tlasPrimitiveCount - 1;\n instanceOffset >= 0;\n instanceOffset--\n ) {\n let instanceRecord = bvhBuffer[tlasNode + 2 + instanceOffset];\n let insIdx = bitcast<i32>(instanceRecord.x);\n let blasRoot = bitcast<i32>(instanceRecord.y);\n let insData = instancesBuffer[insIdx * ${INSTANCE_VEC4_STRIDE}];\n let w2l = mat4x4<f32>(\n instancesBuffer[insIdx * ${INSTANCE_VEC4_STRIDE} + 1],\n instancesBuffer[insIdx * ${INSTANCE_VEC4_STRIDE} + 2],\n instancesBuffer[insIdx * ${INSTANCE_VEC4_STRIDE} + 3],\n instancesBuffer[insIdx * ${INSTANCE_VEC4_STRIDE} + 4]\n );\n o = (w2l * vec4f(ray.o, 1.0)).xyz;\n d = (w2l * vec4f(ray.d, 0.0)).xyz;\n invD = traversalInverse(d);\n maxDim = maxDimension(abs(d));\n shear = getShear(d, maxDim);\n\n let encodedBlasOffset = bitcast<u32>(insData.x);\n let blasLayout = encodedBlasOffset >> 30u;\n let blasOffset = i32(encodedBlasOffset & 0x3fffffffu);\n let bufferOff = rtUniform.blasOffset;\n\n ${(blasLayoutMask & 8) !== 0 ? /* wgsl */ `\n if (blasLayout == 3u) {\n // TinyBVH's official CWBVH traversal keeps compact node groups on the\n // stack. The upper 8 bits identify intersected interior slots and the\n // lower 8 bits preserve the node's interior-slot mask for popcount\n // addressing. Triangle leaves are merged into a separate 24-bit mask.\n var cwbvhStack: array<vec2u, BLAS_STACK_SIZE>;\n var cwbvhTop = -1;\n var cwbvhVisits = 0u;\n var nodeGroupBase = 0u;\n // A synthetic slot-7 hit selects relative node zero for the root.\n var nodeGroupMask = 0x80000000u;\n let octant = select(0u, 4u, d.x < 0.0)\n | select(0u, 2u, d.y < 0.0)\n | select(0u, 1u, d.z < 0.0);\n let octantInverse = 7u - octant;\n\n // Keep a statically visible trip bound. DXC can otherwise miscompile\n // the dynamic stack-top loop into a non-terminating shader on D3D12.\n // Validated scene rays stay far below this conservative safety cap.\n while (cwbvhVisits < 4096u) {\n cwbvhVisits++;\n \n let internalMask = nodeGroupMask & 0xffu;\n let childBitIndex =\n cwbvhHighestBitIndex8(nodeGroupMask >> 24u) + 24u;\n nodeGroupMask &= ~(1u << childBitIndex);\n if ((nodeGroupMask & 0xff000000u) != 0u) {\n cwbvhTop++;\n \n cwbvhStack[cwbvhTop] = vec2u(nodeGroupBase, nodeGroupMask);\n }\n\n let slot = (childBitIndex - 24u) ^ octantInverse;\n let precedingMask = (1u << slot) - 1u;\n let relativeIndex = countOneBits(internalMask & precedingMask);\n let childNodeIndex = nodeGroupBase + relativeIndex;\n let node = blasOffset + i32(childNodeIndex * 5u);\n let compressed0 = bvhBuffer[node + bufferOff];\n let slotHits = intersectCwbvhInterior(\n node, bufferOff, compressed0, o, invD, tMax\n );\n // Empty child hits need neither metadata nor leaf processing. Clear\n // the current group so the common tail restores pending siblings.\n nodeGroupMask = 0u;\n if (slotHits != 0u) {\n let compressed1 = bvhBuffer[node + bufferOff + 1];\n let nodeInternalMask = bitcast<u32>(compressed0.w) >> 24u;\n var hitMask = 0u;\n var remainingSlotHits = slotHits;\n while (remainingSlotHits != 0u) {\n let childSlot = cwbvhHighestBitIndex8(remainingSlotHits);\n remainingSlotHits &= ~(1u << childSlot);\n let metaWord = select(\n bitcast<u32>(compressed1.z),\n bitcast<u32>(compressed1.w),\n childSlot >= 4u\n );\n let childMeta =\n (metaWord >> ((childSlot & 3u) * 8u)) & 0xffu;\n let localOffset = childMeta & 0x1fu;\n let bitIndex = select(\n localOffset,\n localOffset ^ octantInverse,\n localOffset >= 24u\n );\n let childBits = (childMeta >> 5u) & 0x7u;\n hitMask |= childBits << bitIndex;\n }\n\n nodeGroupBase = bitcast<u32>(compressed1.x);\n nodeGroupMask = (hitMask & 0xff000000u) | nodeInternalMask;\n let triangleBase = bitcast<u32>(compressed1.y);\n var triangleMask = hitMask & 0x00ffffffu;\n while (triangleMask != 0u) {\n let triangleBitIndex = cwbvhHighestBitIndex24(triangleMask);\n triangleMask &= ~(1u << triangleBitIndex);\n let primitiveCount = 1;\n let primitiveOffset = bufferOff + blasOffset\n + i32(triangleBase + triangleBitIndex);\n ${intersectTriangles}\n }\n }\n\n if ((nodeGroupMask & 0xff000000u) != 0u) { continue; }\n if (cwbvhTop >= 0) {\n let restoredGroup = cwbvhStack[cwbvhTop];\n nodeGroupBase = restoredGroup.x;\n nodeGroupMask = restoredGroup.y;\n cwbvhTop--;\n }\n else { break; }\n }\n }\n ` : ''}\n\n var blasStack: array<i32, BLAS_STACK_SIZE>;\n var blasTop = -1;\n if (blasLayout != 3u) {\n blasTop = 0;\n \n blasStack[0] = blasRoot;\n }\n\n while (blasTop >= 0) {\n \n let node = blasStack[blasTop];\n blasTop--;\n var primitiveCount = 0;\n var primitiveOffset = 0;\n\n ${(blasLayoutMask & 4) !== 0 ? /* wgsl */ `\n if (blasLayout == 2u) {\n let i = node + bufferOff;\n let gpu0 = bvhBuffer[i];\n let gpu1 = bvhBuffer[i + 1];\n let gpu2 = bvhBuffer[i + 2];\n let gpu3 = bvhBuffer[i + 3];\n primitiveCount = i32(bitcast<u32>(gpu2.w));\n if (primitiveCount > 0) {\n primitiveOffset = bufferOff + blasOffset\n + i32(bitcast<u32>(gpu3.w));\n }\n else {\n let left = blasOffset + i32(bitcast<u32>(gpu0.w) * 4u);\n let right = blasOffset + i32(bitcast<u32>(gpu1.w) * 4u);\n let leftDistance = intersectBox(\n o, gpu0.xyz, gpu1.xyz, invD, tMax\n );\n let rightDistance = intersectBox(\n o, gpu2.xyz, gpu3.xyz, invD, tMax\n );\n if (leftDistance > rightDistance) {\n if (leftDistance > 0.0) {\n blasTop++; blasStack[blasTop] = left;\n }\n if (rightDistance > 0.0) {\n blasTop++; blasStack[blasTop] = right;\n }\n }\n else {\n if (rightDistance > 0.0) {\n blasTop++; blasStack[blasTop] = right;\n }\n if (leftDistance > 0.0) {\n blasTop++; blasStack[blasTop] = left;\n }\n }\n }\n }\n ` : ''}\n ${(blasLayoutMask & 2) !== 0 ? /* wgsl */ `\n if (blasLayout == 1u) {\n let nodeInfo = bitcast<u32>(node);\n if ((nodeInfo & 0x80000000u) != 0u) {\n primitiveCount = i32((nodeInfo >> 23u) & 0xffu);\n primitiveOffset = bufferOff + blasOffset\n + i32(nodeInfo & 0x007fffffu);\n }\n else {\n let i = node + bufferOff;\n let wide0 = bvhBuffer[i];\n let wide1 = bvhBuffer[i + 1];\n let wide2 = bvhBuffer[i + 2];\n let wide3 = bvhBuffer[i + 3];\n let nodeMin = wide0.xyz;\n let nodeExtent = vec3f(wide0.w, wide1.x, wide1.y);\n let childInfos = array<u32, 4>(\n bitcast<u32>(wide1.z), bitcast<u32>(wide1.w),\n bitcast<u32>(wide2.x), bitcast<u32>(wide2.y)\n );\n let childMinX = unpack4x8unorm(bitcast<u32>(wide2.z));\n let childMinY = unpack4x8unorm(bitcast<u32>(wide2.w));\n let childMinZ = unpack4x8unorm(bitcast<u32>(wide3.x));\n let childMaxX = unpack4x8unorm(bitcast<u32>(wide3.y));\n let childMaxY = unpack4x8unorm(bitcast<u32>(wide3.z));\n let childMaxZ = unpack4x8unorm(bitcast<u32>(wide3.w));\n let tx0 = (nodeMin.x + childMinX * nodeExtent.x - o.x) * invD.x;\n let tx1 = (nodeMin.x + childMaxX * nodeExtent.x - o.x) * invD.x;\n let ty0 = (nodeMin.y + childMinY * nodeExtent.y - o.y) * invD.y;\n let ty1 = (nodeMin.y + childMaxY * nodeExtent.y - o.y) * invD.y;\n let tz0 = (nodeMin.z + childMinZ * nodeExtent.z - o.z) * invD.z;\n let tz1 = (nodeMin.z + childMaxZ * nodeExtent.z - o.z) * invD.z;\n let padding = depthSlabPadding(invD, o);\n let tNear = max(\n max((min(tx0, tx1) - vec4f(padding.x)), (min(ty0, ty1) - vec4f(padding.y))),\n (min(tz0, tz1) - vec4f(padding.z))\n );\n let tFar = min(\n min((max(tx0, tx1) + vec4f(padding.x)), (max(ty0, ty1) + vec4f(padding.y))),\n (max(tz0, tz1) + vec4f(padding.z))\n );\n var distance = select(tFar, tNear, tNear > vec4f(0.0));\n distance = select(\n distance,\n vec4f(-1.0),\n tNear > min(tFar, vec4f(tMax))\n );\n var childDistances = array<f32, 4>(\n distance.x,\n distance.y,\n distance.z,\n distance.w\n );\n\n // Push far-to-near so that the closest child is popped first.\n for (var order = 0; order < 4; order++) {\n var farthestDistance = -1.0;\n var farthestChild = -1;\n for (var child = 0; child < 4; child++) {\n if (\n childInfos[child] != 0u &&\n childDistances[child] > farthestDistance\n ) {\n farthestDistance = childDistances[child];\n farthestChild = child;\n }\n }\n if (farthestChild >= 0 && farthestDistance > 0.0) {\n let childInfo = childInfos[farthestChild];\n var childAddress = bitcast<i32>(childInfo);\n if ((childInfo & 0x80000000u) == 0u) {\n childAddress += blasOffset;\n }\n blasTop++;\n blasStack[blasTop] = childAddress;\n childDistances[farthestChild] = -1.0;\n }\n }\n }\n }\n ` : ''}\n ${(blasLayoutMask & 1) !== 0 ? /* wgsl */ `\n if (blasLayout == 0u) {\n let i = node + bufferOff;\n let r1 = bvhBuffer[i];\n let pointerToRightOrNumPrims = bitcast<i32>(r1.w);\n\n if (pointerToRightOrNumPrims >= 0) {\n let left = node + 2;\n let right = pointerToRightOrNumPrims + blasOffset;\n let d0 = intersectBox(\n o,\n bvhBuffer[left + bufferOff].xyz,\n bvhBuffer[left + 1 + bufferOff].xyz,\n invD,\n tMax\n );\n let d1 = intersectBox(\n o,\n bvhBuffer[right + bufferOff].xyz,\n bvhBuffer[right + 1 + bufferOff].xyz,\n invD,\n tMax\n );\n\n if (d0 > d1) {\n if (d0 > 0.0) {\n blasTop++; blasStack[blasTop] = left;\n \n }\n if (d1 > 0.0) {\n blasTop++; blasStack[blasTop] = right;\n \n }\n }\n else {\n if (d1 > 0.0) {\n blasTop++; blasStack[blasTop] = right;\n \n }\n if (d0 > 0.0) {\n blasTop++; blasStack[blasTop] = left;\n \n }\n }\n }\n else {\n primitiveCount = -pointerToRightOrNumPrims;\n primitiveOffset = i + 2;\n }\n }\n ` : ''}\n if (primitiveCount > 0) {\n ${intersectTriangles}\n }\n }\n }\n }\n}\n\n`;\n}\nexport default function getIntersectCode({\n hasDisplacement,\n hasAnisotropyDirections = true,\n hasCoatNormals = false,\n blasLayoutMask = 1\n}: {\n hasDisplacement: boolean;\n hasAnisotropyDirections?: boolean;\n hasCoatNormals?: boolean;\n blasLayoutMask?: number;\n}) {\n return /*wgsl*/ `\n\n${hasDisplacement ? displacementWgsl : ''}\n${(blasLayoutMask & 8) !== 0 ? cwbvhTraversalWgsl : ''}\n\nstruct Triangle {\n p0: vec3f,\n p1: vec3f,\n p2: vec3f,\n};\n\nfn intersectSphere(rad: f32, pos: vec3f, r: Ray) -> f32 {\n let op: vec3f = pos - r.o;\n let eps: f32 = 0.001;\n let b: f32 = dot(op, r.d);\n var det: f32 = b * b - dot(op, op) + rad * rad;\n if (det < 0.0) {\n return INF;\n }\n\n det = sqrt(det);\n let t1: f32 = b - det;\n if (t1 > eps) {\n return t1;\n }\n\n let t2: f32 = b + det;\n if (t2 > eps) {\n return t2;\n }\n\n return INF;\n}\n\nfn intersectRect(pos: vec3f, u: vec3f, v: vec3f, n: vec3f, r: Ray) -> f32 {\n let dt: f32 = dot(r.d, n);\n let t: f32 = (dot(n, pos) - dot(n, r.o)) / dt;\n\n if (t > EPS) {\n let p: vec3f = r.o + r.d * t;\n let vi: vec3f = p - pos;\n let a1: f32 = dot(u / dot(u, u), vi);\n if (a1 >= 0.0 && a1 <= 1.0) {\n let a2: f32 = dot(v / dot(v, v), vi);\n if (a2 >= 0.0 && a2 <= 1.0) {\n return t;\n }\n }\n }\n\n return INF;\n}\n\nfn sphereSolidAnglePdf(origin: vec3f, center: vec3f, radius: f32) -> f32 {\n let centerDistance = distance(origin, center);\n if (centerDistance <= radius) { return INV4PI; }\n let sinThetaMaxSquared = min(1.0, radius * radius / (centerDistance * centerDistance));\n let cosThetaMax = sqrt(max(0.0, 1.0 - sinThetaMaxSquared));\n return 1.0 / max(TWOPI * (1.0 - cosThetaMax), 1e-8);\n}\n\nfn intersectLight(r: Ray, light: Light, lightSample: ptr<function, LightSampleRec>) -> f32 {\n // Analytic continuation hits use the same conditional light density as NEE.\n // Initialize every field because this record is reused across the light loop.\n var d: f32 = INF;\n (*lightSample).direction = r.d;\n (*lightSample).normal = -r.d;\n (*lightSample).dist = INF;\n (*lightSample).pdf = 0.0;\n (*lightSample).emission = vec3f(0.0);\n (*lightSample).isDelta = false;\n var u = light.u;\n var v = light.v;\n var lightType = i32(light.litType);\n\n if (lightType == RECT_LIGHT) {\n // Adjust the position from center to start corner\n var pos = light.position - u * 0.5 - v * 0.5;\n var normal = normalize(cross(u, v));\n if (dot(normal, r.d) > 0.0) { // Hide backfacing quad light\n return INF;\n }\n\n d = intersectRect(pos, u, v, normal, r);\n if (d < 0.0) {\n d = INF;\n }\n else {\n var cosTheta: f32 = dot(-r.d, normal);\n let spread = light.radiusOrSpread;\n if (cosTheta >= cos(spread * PI / 2.0)) {\n (*lightSample).direction = r.d;\n (*lightSample).normal = normal;\n (*lightSample).dist = d;\n (*lightSample).pdf = (d * d) / (light.area * cosTheta);\n (*lightSample).emission = light.emission;\n (*lightSample).isDelta = false;\n }\n else {\n d = INF;\n }\n }\n }\n\n else if (lightType == SPHERE_LIGHT) {\n if (light.radiusOrSpread <= 0.0) {\n let toPoint = light.position - r.o;\n let projection = dot(toPoint, r.d);\n let perpendicular = toPoint - projection * r.d;\n if (projection <= EPS || dot(perpendicular, perpendicular) > 1e-8) {\n return INF;\n }\n d = projection;\n (*lightSample).direction = r.d;\n (*lightSample).normal = -r.d;\n (*lightSample).dist = d;\n (*lightSample).pdf = 1.0;\n (*lightSample).isDelta = true;\n }\n else {\n d = intersectSphere(light.radiusOrSpread, light.position, r);\n (*lightSample).isDelta = false;\n if (d > 0.0 && d != INF) {\n (*lightSample).direction = r.d;\n (*lightSample).dist = d;\n (*lightSample).normal = normalize(\n r.o + r.d * d - light.position\n );\n (*lightSample).pdf = sphereSolidAnglePdf(\n r.o,\n light.position,\n light.radiusOrSpread\n );\n }\n }\n if (d < 0.0) {\n d = INF;\n }\n else {\n (*lightSample).direction = r.d;\n (*lightSample).dist = d;\n (*lightSample).emission = select(\n light.emission,\n light.emission / max(d * d, 1e-8),\n light.radiusOrSpread <= 0.0\n );\n }\n }\n\n else if (lightType == SPOT_LIGHT) {\n if (light.radiusOrSpread <= 0.0) {\n let toPoint = light.position - r.o;\n let projection = dot(toPoint, r.d);\n let perpendicular = toPoint - projection * r.d;\n if (projection <= EPS || dot(perpendicular, perpendicular) > 1e-8) {\n return INF;\n }\n d = projection;\n (*lightSample).direction = r.d;\n (*lightSample).normal = -r.d;\n (*lightSample).dist = d;\n (*lightSample).pdf = 1.0;\n (*lightSample).isDelta = true;\n }\n else {\n d = intersectSphere(light.radiusOrSpread, light.position, r);\n (*lightSample).isDelta = false;\n }\n if (d != INF) {\n let hitPt = r.o + d * r.d;\n let multiplier = evaluateSpotEmissionMultiplier(light, normalize(r.o - hitPt));\n if (multiplier <= 0.0) { return INF; }\n if (light.radiusOrSpread > 0.0) {\n (*lightSample).direction = r.d;\n (*lightSample).dist = d;\n (*lightSample).normal = normalize(\n r.o + r.d * d - light.position\n );\n (*lightSample).pdf = sphereSolidAnglePdf(r.o, light.position, light.radiusOrSpread);\n }\n (*lightSample).emission = light.emission * multiplier * select(\n 1.0,\n 1.0 / max(d * d, 1e-8),\n light.radiusOrSpread <= 0.0\n );\n }\n }\n\n else if (lightType != RECT_LIGHT && lightType != SPHERE_LIGHT && lightType != SPOT_LIGHT) {\n // Unsupported light\n d = INF;\n }\n\n return d;\n}\n\nfn randomPickMaterial(m0: vec3i, m1: vec3i, m2: vec3i, bary: vec3f) -> i32 {\n let m00 = unpackMaterialIndex(m0.x);\n let m01 = unpackMaterialIndex(m0.y);\n let m02 = unpackMaterialIndex(m0.z);\n let m10 = unpackMaterialIndex(m1.x);\n let m11 = unpackMaterialIndex(m1.y);\n let m12 = unpackMaterialIndex(m1.z);\n let m20 = unpackMaterialIndex(m2.x);\n let m21 = unpackMaterialIndex(m2.y);\n let m22 = unpackMaterialIndex(m2.z);\n var w00 = m00.y * bary.x;\n var w01 = m01.y * bary.x;\n var w02 = m02.y * bary.x;\n var w10 = m10.y * bary.y;\n var w11 = m11.y * bary.y;\n var w12 = m12.y * bary.y;\n var w20 = m20.y * bary.z;\n var w21 = m21.y * bary.z;\n var w22 = m22.y * bary.z;\n\n var wTotal = w00 + w01 + w02 + w10 + w11 + w12 + w20 + w21 + w22;\n // Accumulate weights\n w00 = w00 / wTotal;\n w01 = w01 / wTotal + w00;\n w02 = w02 / wTotal + w01;\n w10 = w10 / wTotal + w02;\n w11 = w11 / wTotal + w10;\n w12 = w12 / wTotal + w11;\n w20 = w20 / wTotal + w12;\n w21 = w21 / wTotal + w20;\n w22 = w22 / wTotal + w21;\n\n let rand = hashedSample(0u);\n var mIdx: f32;\n if (rand < w00) {\n mIdx = m00.x;\n } else if (rand < w01) {\n mIdx = m01.x;\n } else if (rand < w02) {\n mIdx = m02.x;\n } else if (rand < w10) {\n mIdx = m10.x;\n } else if (rand < w11) {\n mIdx = m11.x;\n } else if (rand < w12) {\n mIdx = m12.x;\n } else if (rand < w20) {\n mIdx = m20.x;\n } else if (rand < w21) {\n mIdx = m21.x;\n } else {\n mIdx = m22.x;\n }\n return i32(mIdx);\n}\n\nfn resolveSurfaceMaterialIndex(\n fallbackMaterialIndex: i32,\n index: vec3i,\n bary: vec3f\n) -> i32 {\n let m0 = vec3i(attrBuffers[index.x * ATTR_STRIDE + MATERIALINDEX_OFF]${hasAnisotropyDirections ? '.xyz' : ''});\n let m1 = vec3i(attrBuffers[index.y * ATTR_STRIDE + MATERIALINDEX_OFF]${hasAnisotropyDirections ? '.xyz' : ''});\n let m2 = vec3i(attrBuffers[index.z * ATTR_STRIDE + MATERIALINDEX_OFF]${hasAnisotropyDirections ? '.xyz' : ''});\n if (m0.x > 0 || m1.x > 0 || m2.x > 0) {\n return randomPickMaterial(m0, m1, m2, bary);\n }\n return fallbackMaterialIndex;\n}\n\n\n\n${hasAnisotropyDirections ? /* wgsl */ `\nfn anisotropy_axes(normal: vec3f, dp1: vec3f, dp2: vec3f, duv1: vec2f, duv2: vec2f, input: vec4f) -> mat2x3f {\n let determinant = duv1.x * duv2.y - duv1.y * duv2.x;\n var t = input.xyz;\n var b = vec3f(0.0);\n // Internal W=2 marks an authored XYZ tangent whose sign is derived from UVs.\n var sign = select(input.w, 0.0, abs(input.w) > 1.5);\n if (abs(determinant) > 1e-12) {\n let dt = (dp1 * duv2.y - dp2 * duv1.y) / determinant;\n let db = (dp2 * duv1.x - dp1 * duv2.x) / determinant;\n if (dot(t, t) < 1e-8) { t = dt; }\n if (sign == 0.0) { sign = select(1.0, -1.0, dot(cross(normal, dt), db) < 0.0); }\n }\n t -= normal * dot(normal, t);\n if (dot(t, t) < 1e-8) {\n let signZ = select(-1.0, 1.0, normal.z >= 0.0);\n let factor = -1.0 / (signZ + normal.z);\n let xy = normal.x * normal.y * factor;\n t = vec3f(1.0 + signZ * normal.x * normal.x * factor, signZ * xy, -signZ * normal.x);\n b = vec3f(xy, signZ + normal.y * normal.y * factor, -normal.y);\n }\n else { t = normalize(t); b = normalize(cross(normal, t)); }\n return mat2x3f(t, b * select(1.0, -1.0, sign < 0.0));\n}\n\n` : /* wgsl */ ``}\nfn fetchAttributes(\n si: ptr<function, SurfaceInteraction>,\n tri: Triangle,\n bary: vec3f,\n index: vec3i,\n isHitMicroTriangle: bool,\n resolvedMaterialIndex: i32\n) {\n (*si).hit = true;\n // Get projected uv\n let material = materialsBuffer[(*si).materialIndex];\n let uvProjection = i32(material.uvProjection);\n\n let i0 = index.x;\n let i1 = index.y;\n let i2 = index.z;\n let isSphericalProj = uvProjection == ${uvProjectionConstants.spherical};\n\n var normal: vec3f = (*si).normal;\n var normalForSphericalProj: vec3f;\n var n0 = vec3f(0.0);\n var n1 = vec3f(0.0);\n var n2 = vec3f(0.0);\n // Otherwise use the normal from the microtriangle\n // The exception is spherical projection, we need the original normal to project the uv. Or the case like earth has wrong normal.\n if (!isHitMicroTriangle || isSphericalProj) {\n n0 = attrBuffers[i0 * ATTR_STRIDE + NORMAL_OFF]${hasAnisotropyDirections ? '.xyz' : ''};\n n1 = attrBuffers[i1 * ATTR_STRIDE + NORMAL_OFF]${hasAnisotropyDirections ? '.xyz' : ''};\n n2 = attrBuffers[i2 * ATTR_STRIDE + NORMAL_OFF]${hasAnisotropyDirections ? '.xyz' : ''};\n normalForSphericalProj = normalize(bary.x * n0 + bary.y * n1 + bary.z * n2);\n if (!isHitMicroTriangle) {\n normal = normalForSphericalProj;\n }\n }\n\n var uv: vec2f;\n var uv0: vec2f;\n var uv1: vec2f;\n var uv2: vec2f;\n var secondaryUv = vec2f(0.0);\n if (USE_UV) {\n uv0 = attrBuffers[i0 * ATTR_STRIDE + UV_OFF].xy;\n uv1 = attrBuffers[i1 * ATTR_STRIDE + UV_OFF].xy;\n uv2 = attrBuffers[i2 * ATTR_STRIDE + UV_OFF].xy;\n uv = bary.x * uv0 + bary.y * uv1 + bary.z * uv2;\n if (USE_UV1) {\n let secondaryUv0 = attrBuffers[i0 * ATTR_STRIDE + UV1_OFF].xy;\n let secondaryUv1 = attrBuffers[i1 * ATTR_STRIDE + UV1_OFF].xy;\n let secondaryUv2 = attrBuffers[i2 * ATTR_STRIDE + UV1_OFF].xy;\n secondaryUv = bary.x * secondaryUv0\n + bary.y * secondaryUv1\n + bary.z * secondaryUv2;\n }\n }\n\n if (USE_TANGENT) {\n let t0 = attrBuffers[i0 * ATTR_STRIDE + TANGENT_OFF]${hasAnisotropyDirections ? '.xyz' : ''};\n let t1 = attrBuffers[i1 * ATTR_STRIDE + TANGENT_OFF]${hasAnisotropyDirections ? '.xyz' : ''};\n let t2 = attrBuffers[i2 * ATTR_STRIDE + TANGENT_OFF]${hasAnisotropyDirections ? '.xyz' : ''};\n (*si).tangent = normalize(bary.x * t0 + bary.y * t1 + bary.z * t2);\n }\n\n if (resolvedMaterialIndex >= 0) {\n (*si).materialIndex = resolvedMaterialIndex;\n }\n else {\n (*si).materialIndex = resolveSurfaceMaterialIndex(\n (*si).materialIndex,\n index,\n bary\n );\n }\n\n if (uvProjection > 0) {\n // NOTE: position and normal are not transformed to world space yet.\n uv = projectUV(uvProjection, (*si).position, select(normal, normalForSphericalProj, isSphericalProj));\n secondaryUv = uv;\n }\n\n uv = uv * material.uvScale;\n secondaryUv = secondaryUv * material.uvScale;\n\n // Force repeating UVs for triplanar mapping\n if (uvProjection == ${uvProjectionConstants.triplanar}) {\n uv = fract(uv);\n }\n\n${hasAnisotropyDirections ? /* wgsl */ `\n${hasCoatNormals ? /* wgsl */ ` (*si).coatNormal = normal;` : ''}\n let directionMaterial = materialsBuffer[(*si).materialIndex];\n if (directionMaterial.anisotropyMapIndex >= 0.0 || directionMaterial.specularAnisotropyRotation != 0.0 || directionMaterial.coatAnisotropyRotation != 0.0${hasCoatNormals ? \" || (USE_COAT_NORMAL_MAPS && directionMaterial.coatNormalMapIndex >= 0.0)\" : \"\"}) {\n let dp1 = tri.p0 - tri.p2;\n let dp2 = tri.p1 - tri.p2;\n var inputTangent = vec4f(0.0);\n if (USE_TANGENT) {\n inputTangent = bary.x * attrBuffers[i0 * ATTR_STRIDE + TANGENT_OFF]\n + bary.y * attrBuffers[i1 * ATTR_STRIDE + TANGENT_OFF]\n + bary.z * attrBuffers[i2 * ATTR_STRIDE + TANGENT_OFF];\n }\n let geometryAxes = anisotropy_axes(normal, dp1, dp2, uv0-uv2, uv1-uv2, inputTangent);\n (*si).geometryTangent = geometryAxes[0];\n (*si).geometryBitangent = geometryAxes[1];\n var directionAxes = geometryAxes;\n if (USE_UV1 && directionMaterial.anisotropyMapIndex >= 0.0 && mapInfo[i32(directionMaterial.anisotropyMapIndex) + NUM_MAPS * 2].x > 0.5) {\n let a = attrBuffers[i0 * ATTR_STRIDE + UV1_OFF].xy;\n let b = attrBuffers[i1 * ATTR_STRIDE + UV1_OFF].xy;\n let c = attrBuffers[i2 * ATTR_STRIDE + UV1_OFF].xy;\n directionAxes = anisotropy_axes(normal, dp1, dp2, a-c, b-c, select(inputTangent, vec4f(0.0), inputTangent.w == 0.0));\n }\n (*si).directionTangent = directionAxes[0];\n (*si).directionBitangent = directionAxes[1];\n${hasCoatNormals ? /* wgsl */ `\n if (USE_COAT_NORMAL_MAPS && directionMaterial.coatNormalMapIndex >= 0.0) {\n let coatIndex = i32(directionMaterial.coatNormalMapIndex);\n var coatAxes = geometryAxes;\n if (USE_UV1 && mapInfo[coatIndex + NUM_MAPS * 2].x > 0.5) {\n let a = attrBuffers[i0 * ATTR_STRIDE + UV1_OFF].xy;\n let b = attrBuffers[i1 * ATTR_STRIDE + UV1_OFF].xy;\n let c = attrBuffers[i2 * ATTR_STRIDE + UV1_OFF].xy;\n coatAxes = anisotropy_axes(normal, dp1, dp2, a-c, b-c,\n select(inputTangent, vec4f(0.0), inputTangent.w == 0.0));\n }\n // Procedural projection overrides both UV sets. UV0 has already applied\n // triplanar wrapping, so use it for either texture channel in that case.\n let decoded = sampleMap(coatIndex, getMapUvSet(coatIndex, uv,\n select(secondaryUv, uv, uvProjection > 0))).rgb * 2.0 - 1.0;\n let mapped = coatAxes[0] * decoded.x * directionMaterial.coatNormalScale.x\n + coatAxes[1] * decoded.y * directionMaterial.coatNormalScale.y + normal * decoded.z;\n if (dot(mapped, mapped) > 1e-12) { (*si).coatNormal = normalize(mapped); }\n }\n` : ''}\n }\n\n` : /* wgsl */ ``}\n (*si).normal = normal;\n (*si).vertexNormal = normal;\n (*si).shadowOffset = vec3f(0.0);\n if (!isHitMicroTriangle) {\n // Smooth-limit geometry offset for direct-light shadow rays. This follows\n // the Cycles shadow terminator construction while keeping the result in\n // local space until the instance transform is available.\n let geometricNormal = normalize(cross(tri.p1 - tri.p0, tri.p2 - tri.p0));\n let smoothNormal = bary.x * n0 + bary.y * n1 + bary.z * n2;\n let a = dot(n2 - n0, tri.p0 - tri.p2);\n let b = dot(n2 - n1, tri.p1 - tri.p2);\n let c = dot(n1 - n0, tri.p1 - tri.p0);\n var h = a * bary.x * (bary.x - 1.0)\n + (a + b + c) * bary.x * bary.y\n + b * bary.y * (bary.y - 1.0);\n if (dot(smoothNormal, geometricNormal) > 0.0) {\n var h0 = max(max(dot(tri.p1 - tri.p0, n0), dot(tri.p2 - tri.p0, n0)), 0.0);\n var h1 = max(max(dot(tri.p0 - tri.p1, n1), dot(tri.p2 - tri.p1, n1)), 0.0);\n var h2 = max(max(dot(tri.p0 - tri.p2, n2), dot(tri.p1 - tri.p2, n2)), 0.0);\n h0 = max(dot(tri.p0 - (*si).position, n0) + h0, 0.0);\n h1 = max(dot(tri.p1 - (*si).position, n1) + h1, 0.0);\n h2 = max(dot(tri.p2 - (*si).position, n2) + h2, 0.0);\n h = max(min(min(h0, h1), h2), h * 0.5);\n }\n else {\n var h0 = max(max(dot(tri.p0 - tri.p1, n0), dot(tri.p0 - tri.p2, n0)), 0.0);\n var h1 = max(max(dot(tri.p1 - tri.p0, n1), dot(tri.p1 - tri.p2, n1)), 0.0);\n var h2 = max(max(dot(tri.p2 - tri.p0, n2), dot(tri.p2 - tri.p1, n2)), 0.0);\n h0 = max(dot((*si).position - tri.p0, n0) + h0, 0.0);\n h1 = max(dot((*si).position - tri.p1, n1) + h1, 0.0);\n h2 = max(dot((*si).position - tri.p2, n2) + h2, 0.0);\n h = min(-min(min(h0, h1), h2), h * 0.5);\n }\n (*si).shadowOffset = smoothNormal * h;\n }\n (*si).uv = uv;\n (*si).uv1 = secondaryUv;\n if (USE_NORMAL_MAPS) {\n let dp1 = tri.p0 - tri.p2;\n let dp2 = tri.p1 - tri.p2;\n let duv1 = uv0 - uv2;\n let duv2 = uv1 - uv2;\n (*si).normal = getMatNormal(\n (*si).materialIndex, uv, normal, dp1, dp2, duv1, duv2\n );\n }\n}\n\nstruct TriangleIntersect {\n t: f32,\n barycentric: vec3f,\n};\n\n\n// Triangle-ray intersection\n// Faster than the classic Möller–Trumbore intersection algorithm\n// http://www.pbr-book.org/3ed-2018/Shapes/Triangle_Meshes.html#TriangleIntersection\nfn intersectTriangle(o: vec3f, d: vec3f, tri: Triangle, maxDim: i32, shear: vec3f, tMax: f32) -> TriangleIntersect {\n var ti: TriangleIntersect;\n\n // translate vertices based on ray origin\n var p0t = tri.p0 - o;\n var p1t = tri.p1 - o;\n var p2t = tri.p2 - o;\n\n // permute components of triangle vertices\n if (maxDim == 0) {\n p0t = p0t.yzx;\n p1t = p1t.yzx;\n p2t = p2t.yzx;\n } else if (maxDim == 1) {\n p0t = p0t.zxy;\n p1t = p1t.zxy;\n p2t = p2t.zxy;\n }\n\n // apply shear transformation to translated vertex positions\n // TODO swizzled assignment is not supported in wgsl\n p0t = vec3f(p0t.xy + shear.xy * p0t.z, p0t.z);\n p1t = vec3f(p1t.xy + shear.xy * p1t.z, p1t.z);\n p2t = vec3f(p2t.xy + shear.xy * p2t.z, p2t.z);\n\n // compute edge function coefficients\n var e = vec3f(\n p1t.x * p2t.y - p1t.y * p2t.x,\n p2t.x * p0t.y - p2t.y * p0t.x,\n p0t.x * p1t.y - p0t.y * p1t.x\n );\n\n if (DEPTH_QUERY_MODE) {\n // Exact grid rays can land on shared edges. Roundoff in the two edge\n // products must not turn the edge into a crack between both triangles.\n let edgeScale = abs(vec3f(p1t.x * p2t.y, p2t.x * p0t.y, p0t.x * p1t.y))\n + abs(vec3f(p1t.y * p2t.x, p2t.y * p0t.x, p0t.y * p1t.x));\n e = select(e, vec3f(0.0), abs(e) <= edgeScale * 1e-6);\n }\n\n // check if intersection is inside triangle\n if (any(e < vec3f(0)) && any(e > vec3f(0))) {\n return ti;\n }\n\n let det = e.x + e.y + e.z;\n if (DEPTH_QUERY_MODE && det == 0.0) { return ti; }\n\n p0t.z *= shear.z;\n p1t.z *= shear.z;\n p2t.z *= shear.z;\n let tScaled = (e.x * p0t.z + e.y * p1t.z + e.z * p2t.z);\n\n // check if closer intersection already exists\n if (abs(tScaled) > abs(tMax * det)) {\n return ti;\n }\n\n let invDet = 1. / det;\n ti.t = tScaled * invDet;\n ti.barycentric = e * invDet;\n\n return ti;\n}\n\n// Pinhole depth queries can lie exactly on a grid vertex or a flat slab.\n// Specialize conservative slabs for that entry point; beauty retains its\n// original traversal arithmetic and random streams.\noverride DEPTH_QUERY_MODE: bool = false;\n\nfn traversalInverse(d: vec3f) -> vec3f {\n if (!DEPTH_QUERY_MODE) { return 1.0 / d; }\n return 1.0 / select(d, select(vec3f(1e-20), vec3f(-1e-20), d < vec3f(0.0)), abs(d) < vec3f(1e-20));\n}\n\nfn depthSlabPadding(invD: vec3f, o: vec3f) -> vec3f {\n if (!DEPTH_QUERY_MODE) { return vec3f(0.0); }\n return abs(invD) * max(vec3f(1.0), abs(o)) * 1e-6;\n}\n\n// Branchless ray/box intersection\n// https://tavianator.com/fast-branchless-raybounding-box-intersections/\nfn intersectBox(o: vec3f, bmin: vec3f, bmax: vec3f, invD: vec3f, tMax: f32) -> f32 {\n let tBot = (bmin - o) * invD;\n let tTop = (bmax - o) * invD;\n let padding = depthSlabPadding(invD, o);\n let tNear = min(tBot, tTop) - padding;\n let tFar = max(tBot, tTop) + padding;\n let t0 = max(tNear.x, max(tNear.y, tNear.z));\n let t1 = min(tFar.x, min(tFar.y, tFar.z));\n return select(select(t1, t0, t0 > 0.0), -1.0, (t0 > t1 || t0 > tMax));\n}\n\nfn maxDimension(v: vec3f) -> i32 {\n return select(select(2, 1, v.y > v.z), select(2, 0, v.x > v.z), v.x > v.y);\n}\n\nfn getShear(d: vec3f, maxDim: i32) -> vec3f {\n // Permute space so that the z dimension is the one where the absolute value of the ray's direction is largest.\n // Then create a shear transformation that aligns ray direction with the +z axis\n if (maxDim == 0) {\n return vec3f(-d.y, -d.z, 1.0) / d.x;\n } else if (maxDim == 1) {\n return vec3f(-d.z, -d.x, 1.0) / d.y;\n } else {\n return vec3f(-d.x, -d.y, 1.0) / d.z;\n }\n}\n\nfn completeSurfaceInteraction(\n si: ptr<function, SurfaceInteraction>,\n hitTri: Triangle,\n hitBarycentric: vec3f,\n hitIndex: vec3i,\n isHitMicroTriangle: bool,\n hitInsIdx: i32,\n hitNormal: vec3f,\n hitPosition: vec3f,\n resolvedMaterialIndex: i32\n) {\n (*si).instanceIndex = hitInsIdx;\n (*si).barycentrics = hitBarycentric.xy;\n (*si).normal = hitNormal;\n (*si).position = hitPosition;\n (*si).materialIndex = i32(unpackMaterialIndex(\n bitcast<i32>(instancesBuffer[hitInsIdx * ${INSTANCE_VEC4_STRIDE}].z)\n ).x);\n // NOTE: Must be called before transforming normal/position to world space.\n // UV projection depends on the local position.\n fetchAttributes(\n si,\n hitTri,\n hitBarycentric,\n hitIndex,\n isHitMicroTriangle,\n resolvedMaterialIndex\n );\n\n let l2w = mat4x4<f32>(\n instancesBuffer[hitInsIdx * ${INSTANCE_VEC4_STRIDE} + 5],\n instancesBuffer[hitInsIdx * ${INSTANCE_VEC4_STRIDE} + 6],\n instancesBuffer[hitInsIdx * ${INSTANCE_VEC4_STRIDE} + 7],\n instancesBuffer[hitInsIdx * ${INSTANCE_VEC4_STRIDE} + 8]\n );\n let w2l = mat4x4<f32>(\n instancesBuffer[hitInsIdx * ${INSTANCE_VEC4_STRIDE} + 1],\n instancesBuffer[hitInsIdx * ${INSTANCE_VEC4_STRIDE} + 2],\n instancesBuffer[hitInsIdx * ${INSTANCE_VEC4_STRIDE} + 3],\n instancesBuffer[hitInsIdx * ${INSTANCE_VEC4_STRIDE} + 4]\n );\n let normalTransform = transpose(mat3x3<f32>(\n w2l[0].xyz,\n w2l[1].xyz,\n w2l[2].xyz\n ));\n\n (*si).faceNormal = normalize(normalTransform * hitNormal);\n (*si).vertexNormal = normalize(normalTransform * (*si).vertexNormal);\n (*si).normal = normalize(normalTransform * (*si).normal);\n${hasCoatNormals ? /* wgsl */ ` (*si).coatNormal = normalize(normalTransform * (*si).coatNormal);` : ''}\n if (USE_TANGENT && dot((*si).tangent, (*si).tangent) > 0.0) {\n (*si).tangent = normalize((l2w * vec4f((*si).tangent, 0.0)).xyz);\n }\n${hasAnisotropyDirections ? /* wgsl */ `\n (*si).directionTangent = (l2w * vec4f((*si).directionTangent, 0.0)).xyz;\n (*si).directionBitangent = (l2w * vec4f((*si).directionBitangent, 0.0)).xyz;\n (*si).geometryTangent = (l2w * vec4f((*si).geometryTangent, 0.0)).xyz;\n (*si).geometryBitangent = (l2w * vec4f((*si).geometryBitangent, 0.0)).xyz;\n` : /* wgsl */ ``}\n (*si).shadowOffset = (l2w * vec4f((*si).shadowOffset, 0.0)).xyz;\n (*si).position = (l2w * vec4((*si).position, 1.0)).xyz;\n}\n\n\n${createIntersectionAlphaWgsl(hasAnisotropyDirections)}\n\n\n\n// Traverse BVH, find closest triangle intersection, and return surface information\nfn intersectScene(ray: Ray, si: ptr<function, SurfaceInteraction>, bounces: i32, doubleSided: bool, isRefractionRay: bool) {\n\n (*si).hit = false;\n\n if (rtUniform.isEmpty > 0) {\n return;\n }\n\n var tMax = RAY_MAX_DISTANCE;\n var maxDim: i32;\n var shear: vec3f;\n\n var hitIndex: vec3i;\n var hitTri: Triangle;\n var hitInsIdx: i32;\n var currentTriangleIndex: i32 = (*si).triangleIndex;\n\n ${getBVHTraverseCode(\n `\n(*si).triangleIndex = triangleIndex;\nhitTri = tri;\nhitIndex = index;\nhitInsIdx = insIdx;\n `,\n hasDisplacement,\n false,\n blasLayoutMask,\n hasAnisotropyDirections\n )}\n\n if (isHit) {\n completeSurfaceInteraction(\n si,\n hitTri,\n hitBarycentric,\n hitIndex,\n isHitMicroTriangle,\n hitInsIdx,\n hitNormal,\n hitPosition,\n -1\n );\n }\n}\n\n// Local intersection helper for shader bevel.\nfn intersectSceneBevel(ray: Ray, si: ptr<function, SurfaceInteraction>, instanceIndex: i32, maxDistance: f32) {\n (*si).hit = false;\n\n if (rtUniform.isEmpty > 0) {\n return;\n }\n\n let bounces = 0;\n let doubleSided = true;\n let isRefractionRay = false;\n var tMax = maxDistance;\n var maxDim: i32;\n var shear: vec3f;\n var hitIndex: vec3i;\n var hitTri: Triangle;\n var hitInsIdx: i32;\n var currentTriangleIndex: i32 = -1;\n\n ${getBVHTraverseCode(\n `\n(*si).triangleIndex = triangleIndex;\nhitTri = tri;\nhitIndex = index;\nhitInsIdx = insIdx;\n `,\n hasDisplacement,\n false,\n blasLayoutMask,\n hasAnisotropyDirections,\n `\n if (insIdx != instanceIndex) {\n continue;\n }\n `\n )}\n\n if (isHit) {\n (*si).instanceIndex = hitInsIdx;\n (*si).normal = hitNormal;\n (*si).position = hitPosition;\n (*si).materialIndex = i32(unpackMaterialIndex(bitcast<i32>(instancesBuffer[hitInsIdx * ${INSTANCE_VEC4_STRIDE}].z)).x);\n fetchAttributes(\n si,\n hitTri,\n hitBarycentric,\n hitIndex,\n isHitMicroTriangle,\n -1\n );\n\n let l2w = mat4x4<f32>(\n instancesBuffer[hitInsIdx * ${INSTANCE_VEC4_STRIDE} + 5],\n instancesBuffer[hitInsIdx * ${INSTANCE_VEC4_STRIDE} + 6],\n instancesBuffer[hitInsIdx * ${INSTANCE_VEC4_STRIDE} + 7],\n instancesBuffer[hitInsIdx * ${INSTANCE_VEC4_STRIDE} + 8]\n );\n let w2l = mat4x4<f32>(\n instancesBuffer[hitInsIdx * ${INSTANCE_VEC4_STRIDE} + 1],\n instancesBuffer[hitInsIdx * ${INSTANCE_VEC4_STRIDE} + 2],\n instancesBuffer[hitInsIdx * ${INSTANCE_VEC4_STRIDE} + 3],\n instancesBuffer[hitInsIdx * ${INSTANCE_VEC4_STRIDE} + 4]\n );\n let normalTransform = transpose(mat3x3<f32>(\n w2l[0].xyz,\n w2l[1].xyz,\n w2l[2].xyz\n ));\n (*si).faceNormal = normalize(normalTransform * hitNormal);\n (*si).vertexNormal = normalize(normalTransform * (*si).vertexNormal);\n (*si).normal = normalize(normalTransform * (*si).normal);\n${hasCoatNormals ? /* wgsl */ ` (*si).coatNormal = normalize(normalTransform * (*si).coatNormal);` : ''}\n if (USE_TANGENT && dot((*si).tangent, (*si).tangent) > 0.0) {\n (*si).tangent = normalize((l2w * vec4f((*si).tangent, 0.0)).xyz);\n }\n${hasAnisotropyDirections ? /* wgsl */ `\n (*si).directionTangent = (l2w * vec4f((*si).directionTangent, 0.0)).xyz;\n (*si).directionBitangent = (l2w * vec4f((*si).directionBitangent, 0.0)).xyz;\n (*si).geometryTangent = (l2w * vec4f((*si).geometryTangent, 0.0)).xyz;\n (*si).geometryBitangent = (l2w * vec4f((*si).geometryBitangent, 0.0)).xyz;\n` : /* wgsl */ ``}\n (*si).shadowOffset = (l2w * vec4f((*si).shadowOffset, 0.0)).xyz;\n (*si).position = (l2w * vec4((*si).position, 1.0)).xyz;\n }\n}\n\nfn intersectSceneShadow(ray: Ray, lightDistance: f32, currTriangleIndex: i32) -> bool {\n if (rtUniform.isEmpty > 0) {\n return false;\n }\n\n let doubleSided = true;\n let shadowRayTMax = min(max(lightDistance, 0.0), RAY_MAX_DISTANCE);\n var tMax = shadowRayTMax;\n var maxDim: i32;\n var shear: vec3f;\n\n let bounces = 1;\n var isRefractionRay = false;\n var currentTriangleIndex: i32 = currTriangleIndex;\n\n ${getBVHTraverseCode(\n /*wgsl */ `\n// Traversal is already clipped to the sampled light. Affine instance\n// transforms preserve the ray parameter because local directions are not\n// renormalized, so no local-to-world hit transform or distance() is needed.\nvar isOccluded = true;\n\nif (isOccluded) {\n if (HAS_ALPHA) {\n let materialIndex = i32(unpackMaterialIndex(bitcast<i32>(insData.z)).x);\n if (!intersectionAlphaPasses(\n materialIndex,\n index,\n hitBarycentric,\n hitPosition,\n hitNormal\n )) {\n // Continue looking for an opaque hit, but never traverse beyond the light.\n tMax = shadowRayTMax;\n isOccluded = false;\n }\n }\n}\nif (isOccluded) {\n return true;\n}\n `,\n hasDisplacement,\n true,\n blasLayoutMask,\n hasAnisotropyDirections\n )}\n\n return false;\n}\n\n`;\n}\n"]}
1
+ {"version":3,"file":"intersect.wgsl.js","sourceRoot":"","sources":["../../../../src/renderer/wgsl/chunks/intersect.wgsl.ts"],"names":[],"mappings":"AAAA,qDAAqD;AACrD,sDAAsD;AACtD,kFAAkF;AAElF,OAAO,EAAE,qBAAqB,EAAE,MAAM,wBAAwB,CAAC;AAC/D,OAAO,EAAE,oBAAoB,EAAE,MAAM,mBAAmB,CAAC;AACzD,OAAO,oBAAoB,MAAM,4BAA4B,CAAC;AAC9D,OAAO,EAAE,2BAA2B,EAAE,MAAM,6BAA6B,CAAC;AAE1E,MAAM,gBAAgB,GAAG,oBAAoB,CAAC;AAE9C,MAAM,iBAAiB,GAAG;IACxB,OAAO,UAAU,CAAC;;;;;;;;;;;;;;;;;;;CAmBnB,CAAC;AACF,CAAC,CAAC;AAEF,SAAS,qBAAqB,CAAC,MAAe;IAC5C,OAAO,SAAS,CAAC;;;;;;;;;;;;;;EAcb,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,OAAO;;;CAG9B,CAAC;AACF,CAAC;AAED,MAAM,yBAAyB,GAAG,UAAU,MAAe,EAAE,uBAAgC;IAC3F,OAAO,UAAU,CAAC;;;;;;;oEAOgD,uBAAuB,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE;oEACrC,uBAAuB,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE;oEACrC,uBAAuB,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE;;;;;;;;;;;;;;;;;EAiBrG,qBAAqB,CAAC,MAAM,CAAC;;;;;;;;;;;;;;;;;EAiB7B,iBAAiB,EAAE;;CAEtB,CAAC;AACF,CAAC,CAAC;AAEF,MAAM,kBAAkB,GAAG,UAAU,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAuGrC,CAAC;AAEF,SAAS,kBAAkB,CACzB,aAAqB,EACrB,eAAwB,EACxB,MAAe,EACf,cAAsB,EACtB,uBAAgC,EAChC,gBAAgB,GAAG,EAAE;IAErB,MAAM,kBAAkB,GAAG,UAAU,CAAC;gCACO,oBAAoB;;;;EAIzD,gBAAgB;;;;;;;;mDAQmC,uBAAuB,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE;mDACrC,uBAAuB,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE;mDACrC,uBAAuB,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE;;;;EAIxF,eAAe;QACf,CAAC,CAAC,yBAAyB,CAAC,MAAM,EAAE,uBAAuB,CAAC;QAC5D,CAAC,CAAC,iBAAiB,EAAE;;EAEnB,aAAa;;;CAGxB,CAAC;IACA,OAAO,UAAU,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;gCAiE2B,oBAAoB;;uBAEhC,oBAAoB;uBACpB,oBAAoB;uBACpB,oBAAoB;uBACpB,oBAAoB;;;;;;;;;;;;;EAa/C,CAAC,cAAc,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAgFhC,kBAAkB;;;;;;;;;;;;;;CAc3B,CAAC,CAAC,CAAC,EAAE;;;;;;;;;;;;;;;;;EAiBF,CAAC,cAAc,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAuCzC,CAAC,CAAC,CAAC,EAAE;EACJ,CAAC,cAAc,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAgFzC,CAAC,CAAC,CAAC,EAAE;EACJ,CAAC,cAAc,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAkDzC,CAAC,CAAC,CAAC,EAAE;;EAEF,kBAAkB;;;;;;;CAO7B,CAAC;AACF,CAAC;AACD,MAAM,CAAC,OAAO,UAAU,gBAAgB,CAAC,EACvC,eAAe,EACf,uBAAuB,GAAG,IAAI,EAC9B,cAAc,GAAG,KAAK,EACtB,cAAc,GAAG,CAAC,EAMnB;IACC,OAAO,QAAQ,CAAC;;EAEhB,eAAe,CAAC,CAAC,CAAC,gBAAgB,CAAC,CAAC,CAAC,EAAE;EACvC,CAAC,cAAc,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,kBAAkB,CAAC,CAAC,CAAC,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;uEA8PmB,uBAAuB,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE;uEACrC,uBAAuB,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE;uEACrC,uBAAuB,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE;;;;;;;;;EAS5G,uBAAuB,CAAC,CAAC,CAAC,UAAU,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;CAyBtC,CAAC,CAAC,CAAC,UAAU,CAAC,EAAE;;;;;;;;;;;;;;;;;;;;;;;2BAuByB,qBAAqB,CAAC,SAAS;;;;;;;;;;iDAUpB,uBAAuB,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE;iDACrC,uBAAuB,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE;iDACrC,uBAAuB,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;sDA4BhC,uBAAuB,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE;sDACrC,uBAAuB,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE;sDACrC,uBAAuB,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;sBAyBvE,qBAAqB,CAAC,SAAS;;;;EAIrD,uBAAuB,CAAC,CAAC,CAAC,UAAU,CAAC;EACrC,cAAc,CAAC,CAAC,CAAC,UAAU,CAAC,8BAA8B,CAAC,CAAC,CAAC,EAAE;;2JAE4F,cAAc,CAAC,CAAC,CAAC,2EAA2E,CAAC,CAAC,CAAC,EAAE;;;;;;;;;;;;;;;;;;;;;EAqB5P,cAAc,CAAC,CAAC,CAAC,UAAU,CAAC;;;;;;;;;;;;;;;;;;;CAmB7B,CAAC,CAAC,CAAC,EAAE;;;CAGL,CAAC,CAAC,CAAC,UAAU,CAAC,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;oCAuL8B,oBAAoB;;;;;;;;;;;;;;uBAcjC,oBAAoB;uBACpB,oBAAoB;uBACpB,oBAAoB;uBACpB,oBAAoB;;;uBAGpB,oBAAoB;uBACpB,oBAAoB;uBACpB,oBAAoB;uBACpB,oBAAoB;;;;;;;;;;;EAWpD,cAAc,CAAC,CAAC,CAAC,UAAU,CAAC,wDAAqE,CAAC,CAAC,CAAC,EAAE;;;;EAItG,uBAAuB,CAAC,CAAC,CAAC,UAAU,CAAC;;;;;CAKtC,CAAC,CAAC,CAAC,UAAU,CAAC,EAAE;;;;;;EAMf,2BAA2B,CAAC,uBAAuB,CAAC;;;;;;;;;;;;;;;;;;;;;;EAsBlD,kBAAkB,CAClB;;;;;CAKD,EACC,eAAe,EACf,KAAK,EACL,cAAc,EACd,uBAAuB,CACxB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAoCC,kBAAkB,CAClB;;;;;CAKC,EACD,eAAe,EACf,KAAK,EACL,cAAc,EACd,uBAAuB,EACvB;;;;CAIC,CACF;;;;;;iEAM0F,oBAAoB;;;;;;;;;;;uBAW7E,oBAAoB;uBACpB,oBAAoB;uBACpB,oBAAoB;uBACpB,oBAAoB;;;uBAGpB,oBAAoB;uBACpB,oBAAoB;uBACpB,oBAAoB;uBACpB,oBAAoB;;;;;;;;;;EAUtD,cAAc,CAAC,CAAC,CAAC,UAAU,CAAC,wDAAqE,CAAC,CAAC,CAAC,EAAE;;;;EAItG,uBAAuB,CAAC,CAAC,CAAC,UAAU,CAAC;;;;;CAKtC,CAAC,CAAC,CAAC,UAAU,CAAC,EAAE;;;;;;;;;;;;;;;;;;;;;EAqBb,kBAAkB;IAClB,SAAS,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;CAyBT,EACD,eAAe,EACf,IAAI,EACJ,cAAc,EACd,uBAAuB,CACxB;;;;;CAKF,CAAC;AACF,CAAC","sourcesContent":["// Portions adapted from ray-tracing-renderer@0.10.15\n// (https://github.com/hoverinc/ray-tracing-renderer).\n// Copyright (c) 2019 HOVER, MIT license; see LICENSES/RayTracingRenderer-MIT.txt.\n\nimport { uvProjectionConstants } from '../../MaterialTable.js';\nimport { INSTANCE_VEC4_STRIDE } from '../../bvhAccel.js';\nimport pdm_displacementWgsl from './pdm_displacement.wgsl.js';\nimport { createIntersectionAlphaWgsl } from './intersectionAlpha.wgsl.js';\n\nconst displacementWgsl = pdm_displacementWgsl;\n\nconst flatIntersectCode = function () {\n return /* wgsl */ `\nlet hit = intersectTriangle(o, d, tri, maxDim, shear, tMax);\nif (hit.t > 0.0) {\n let e1 = tri.p1 - tri.p0;\n let e2 = tri.p2 - tri.p0;\n hitNormal = normalize(cross(e1, e2));\n let sideMaterialIndex = i32(unpackMaterialIndex(bitcast<i32>(insData.z)).x);\n let materialDoubleSided = materialsBuffer[sideMaterialIndex].doubleSided > 0.5;\n if (!doubleSided && !materialDoubleSided && dot(hitNormal, d) > 0.0) {\n hitNormal = vec3f(0.0);\n continue;\n }\n\n tMax = hit.t;\n isHit = true;\n isHitCurrent = true;\n hitPosition = o + tMax * d;\n hitBarycentric = hit.barycentric;\n}\n`;\n};\n\nfunction getPDMDisplacmentCode(anyHit: boolean) {\n return /*wgsl */ `\n let microTriangle = intersectDisplacement(\n o, d, tMax,\n tri.p0, tri.p1, tri.p2,\n n0, n1, n2,\n uv0, uv1, uv2,\n displacementMapIndex,\n getMapSize(displacementMapIndex),\n minH, maxH,\n hScale,\n matData.displacementBias,\n matData.displacementStepSize,\n i32(matData.displacementUvProjection),\n displacementUvProjectionScale,\n ${anyHit ? 'true' : 'false'},\n isRefractionRay\n );\n`;\n}\n\nconst displacementIntersectCode = function (anyHit: boolean, hasAnisotropyDirections: boolean) {\n return /* wgsl */ `\n\nlet hScale = insData.w;\nlet minMaxH = bitcast<i32>(r3.w);\n// Displacement currently uses the first material of the instance.\nlet materialIndex = i32(unpackMaterialIndex(bitcast<i32>(insData.z)).x);\nlet matData = materialsBuffer[materialIndex];\nlet n0 = normalize(attrBuffers[index.x * ATTR_STRIDE + NORMAL_OFF]${hasAnisotropyDirections ? '.xyz' : ''});\nlet n1 = normalize(attrBuffers[index.y * ATTR_STRIDE + NORMAL_OFF]${hasAnisotropyDirections ? '.xyz' : ''});\nlet n2 = normalize(attrBuffers[index.z * ATTR_STRIDE + NORMAL_OFF]${hasAnisotropyDirections ? '.xyz' : ''});\nif (matData.displacementMapIndex >= 0.0 && matData.displacementBias != 0.0) {\n tri.p0 += n0 * matData.displacementBias;\n tri.p1 += n1 * matData.displacementBias;\n tri.p2 += n2 * matData.displacementBias;\n}\nif (abs(hScale) > 0.0 && minMaxH > 0) {\n let minH = f32(minMaxH >> 8) / 255.0;\n let maxH = f32(minMaxH & 0xff) / 255.0;\n let displacementUvProjectionScale = vec2f(matData.displacementUvProjectionScale);\n let displacementMapIndex = i32(matData.displacementMapIndex);\n\n // TODO duplicate reading of normal and uv\n var uv0 = attrBuffers[index.x * ATTR_STRIDE + UV_OFF].xy;\n var uv1 = attrBuffers[index.y * ATTR_STRIDE + UV_OFF].xy;\n var uv2 = attrBuffers[index.z * ATTR_STRIDE + UV_OFF].xy;\n\n ${getPDMDisplacmentCode(anyHit)}\n\n if (microTriangle.t < tMax) {\n hitNormal = microTriangle.normal;\n // TODO support double sided to improve non watertight triangles.\n\n tMax = microTriangle.t;\n // TODO barycentric is for micro triangle\n hitBarycentric = microTriangle.barycentric;\n hitPosition = o + tMax * d;\n\n isHitMicroTriangle = true;\n isHit = true;\n isHitCurrent = true;\n }\n}\nelse {\n ${flatIntersectCode()}\n}\n`;\n};\n\nconst cwbvhTraversalWgsl = /* wgsl */ `\nfn unpackCwbvhBytes(value: u32) -> vec4f {\n return vec4f(\n f32(value & 0xffu),\n f32((value >> 8u) & 0xffu),\n f32((value >> 16u) & 0xffu),\n f32(value >> 24u)\n );\n}\n\n// CWBVH calls these helpers only with nonzero 8/24-bit masks. Every such\n// integer is exactly representable as f32, whose unbiased IEEE-754 exponent\n// gives the highest set bit. This avoids both the full-width firstLeadingBit\n// polyfill on some shader backends and a serial bit-spread/popcount chain.\n// Do not use this conversion for full 32-bit masks: rounding near a power of\n// two can change their highest bit. Zero is also outside this contract.\nfn cwbvhHighestBitIndex8(value: u32) -> u32 {\n return (bitcast<u32>(f32(value)) >> 23u) - 127u;\n}\n\nfn cwbvhHighestBitIndex24(value: u32) -> u32 {\n return (bitcast<u32>(f32(value)) >> 23u) - 127u;\n}\n\nfn intersectCwbvhInterior(\n node: i32,\n bufferOffset: i32,\n n0: vec4f,\n o: vec3f,\n invD: vec3f,\n tMax: f32\n) -> u32 {\n let i = node + bufferOffset;\n let n2 = bvhBuffer[i + 2];\n let n3 = bvhBuffer[i + 3];\n let n4 = bvhBuffer[i + 4];\n let header = bitcast<u32>(n0.w);\n let exponentX = bitcast<i32>((header & 0xffu) << 24u) >> 24;\n let exponentY = bitcast<i32>(((header >> 8u) & 0xffu) << 24u) >> 24;\n let exponentZ = bitcast<i32>(((header >> 16u) & 0xffu) << 24u) >> 24;\n let quantizationScale = vec3f(\n bitcast<f32>(u32(exponentX + 127) << 23u),\n bitcast<f32>(u32(exponentY + 127) << 23u),\n bitcast<f32>(u32(exponentZ + 127) << 23u)\n );\n let padding = depthSlabPadding(invD, o);\n let originTerm = (n0.xyz - o) * invD;\n let adjustedInvD = quantizationScale * invD;\n let minX0 = unpackCwbvhBytes(bitcast<u32>(n2.x));\n let minX1 = unpackCwbvhBytes(bitcast<u32>(n2.y));\n let minY0 = unpackCwbvhBytes(bitcast<u32>(n2.z));\n let minY1 = unpackCwbvhBytes(bitcast<u32>(n2.w));\n let minZ0 = unpackCwbvhBytes(bitcast<u32>(n3.x));\n let minZ1 = unpackCwbvhBytes(bitcast<u32>(n3.y));\n let maxX0 = unpackCwbvhBytes(bitcast<u32>(n3.z));\n let maxX1 = unpackCwbvhBytes(bitcast<u32>(n3.w));\n let maxY0 = unpackCwbvhBytes(bitcast<u32>(n4.x));\n let maxY1 = unpackCwbvhBytes(bitcast<u32>(n4.y));\n let maxZ0 = unpackCwbvhBytes(bitcast<u32>(n4.z));\n let maxZ1 = unpackCwbvhBytes(bitcast<u32>(n4.w));\n\n let minTx0 = minX0 * adjustedInvD.x + originTerm.x;\n let minTy0 = minY0 * adjustedInvD.y + originTerm.y;\n let minTz0 = minZ0 * adjustedInvD.z + originTerm.z;\n let maxTx0 = maxX0 * adjustedInvD.x + originTerm.x;\n let maxTy0 = maxY0 * adjustedInvD.y + originTerm.y;\n let maxTz0 = maxZ0 * adjustedInvD.z + originTerm.z;\n let near0 = max(\n max((min(minTx0, maxTx0) - vec4f(padding.x)), (min(minTy0, maxTy0) - vec4f(padding.y))),\n (min(minTz0, maxTz0) - vec4f(padding.z))\n );\n let far0 = min(\n min((max(minTx0, maxTx0) + vec4f(padding.x)), (max(minTy0, maxTy0) + vec4f(padding.y))),\n (max(minTz0, maxTz0) + vec4f(padding.z))\n );\n\n let minTx1 = minX1 * adjustedInvD.x + originTerm.x;\n let minTy1 = minY1 * adjustedInvD.y + originTerm.y;\n let minTz1 = minZ1 * adjustedInvD.z + originTerm.z;\n let maxTx1 = maxX1 * adjustedInvD.x + originTerm.x;\n let maxTy1 = maxY1 * adjustedInvD.y + originTerm.y;\n let maxTz1 = maxZ1 * adjustedInvD.z + originTerm.z;\n let near1 = max(\n max((min(minTx1, maxTx1) - vec4f(padding.x)), (min(minTy1, maxTy1) - vec4f(padding.y))),\n (min(minTz1, maxTz1) - vec4f(padding.z))\n );\n let far1 = min(\n min((max(minTx1, maxTx1) + vec4f(padding.x)), (max(minTy1, maxTy1) + vec4f(padding.y))),\n (max(minTz1, maxTz1) + vec4f(padding.z))\n );\n\n let hits0 = (near0 <= min(far0, vec4f(tMax))) & (far0 > vec4f(0.0));\n let hits1 = (near1 <= min(far1, vec4f(tMax))) & (far1 > vec4f(0.0));\n return select(0u, 1u, hits0.x)\n | select(0u, 2u, hits0.y)\n | select(0u, 4u, hits0.z)\n | select(0u, 8u, hits0.w)\n | select(0u, 16u, hits1.x)\n | select(0u, 32u, hits1.y)\n | select(0u, 64u, hits1.z)\n | select(0u, 128u, hits1.w);\n}\n\n`;\n\nfunction getBVHTraverseCode(\n handleHitCode: string,\n hasDisplacement: boolean,\n anyHit: boolean,\n blasLayoutMask: number,\n hasAnisotropyDirections: boolean,\n skipTriangleCode = ''\n) {\n const intersectTriangles = /* wgsl */ `\n let insData = instancesBuffer[insIdx * ${INSTANCE_VEC4_STRIDE}];\n let vertexOffset = vec3i(bitcast<i32>(insData.y));\n for (var k = 0; k < primitiveCount; k++) {\n let triangleIndex = primitiveOffset + k;\n ${skipTriangleCode}\n if (bounces > 0 && triangleIndex == currentTriangleIndex) {\n continue;\n }\n \n let r3 = bvhBuffer[triangleIndex];\n let index = bitcast<vec3i>(r3.xyz) + vertexOffset;\n var tri = Triangle(\n attrBuffers[index.x * ATTR_STRIDE + POSITION_OFF]${hasAnisotropyDirections ? '.xyz' : ''},\n attrBuffers[index.y * ATTR_STRIDE + POSITION_OFF]${hasAnisotropyDirections ? '.xyz' : ''},\n attrBuffers[index.z * ATTR_STRIDE + POSITION_OFF]${hasAnisotropyDirections ? '.xyz' : ''}\n );\n var isHitCurrent = false;\n\n ${hasDisplacement\n ? displacementIntersectCode(anyHit, hasAnisotropyDirections)\n : flatIntersectCode()}\n if (isHitCurrent) {\n ${handleHitCode}\n }\n }\n`;\n return /* wgsl */ `\nvar d = ray.d;\nvar o = ray.o;\nvar tlasStack: array<i32, TLAS_STACK_SIZE>;\nvar tlasTop = -1;\nvar invD = traversalInverse(d);\nvar isHitMicroTriangle = false;\nvar isHit = false;\nvar hitPosition: vec3f;\nvar hitNormal: vec3f;\nvar hitBarycentric: vec3f;\n\n\nif (intersectBox(o, bvhBuffer[0].xyz, bvhBuffer[1].xyz, invD, tMax) > 0.0) {\n tlasTop = 0;\n tlasStack[0] = 0;\n}\nwhile (tlasTop >= 0) {\n let tlasNode = tlasStack[tlasTop];\n tlasTop--;\n o = ray.o;\n d = ray.d;\n invD = traversalInverse(d);\n\n let tlasR1 = bvhBuffer[tlasNode];\n let tlasPointerOrPrimitiveCount = bitcast<i32>(tlasR1.w);\n if (tlasPointerOrPrimitiveCount >= 0) {\n let left = tlasNode + 2;\n let right = tlasPointerOrPrimitiveCount;\n let d0 = intersectBox(\n o,\n bvhBuffer[left].xyz,\n bvhBuffer[left + 1].xyz,\n invD,\n tMax\n );\n let d1 = intersectBox(\n o,\n bvhBuffer[right].xyz,\n bvhBuffer[right + 1].xyz,\n invD,\n tMax\n );\n\n if (d0 > d1) {\n if (d0 > 0.0) { tlasTop++; tlasStack[tlasTop] = left; }\n if (d1 > 0.0) { tlasTop++; tlasStack[tlasTop] = right; }\n }\n else {\n if (d1 > 0.0) { tlasTop++; tlasStack[tlasTop] = right; }\n if (d0 > 0.0) { tlasTop++; tlasStack[tlasTop] = left; }\n }\n }\n else {\n let tlasPrimitiveCount = -tlasPointerOrPrimitiveCount;\n // The previous unified LIFO stack visited leaf instances in reverse order.\n // Preserve that order for alpha-tested any-hit rays and deterministic RNG.\n for (\n var instanceOffset = tlasPrimitiveCount - 1;\n instanceOffset >= 0;\n instanceOffset--\n ) {\n let instanceRecord = bvhBuffer[tlasNode + 2 + instanceOffset];\n let insIdx = bitcast<i32>(instanceRecord.x);\n let blasRoot = bitcast<i32>(instanceRecord.y);\n let insData = instancesBuffer[insIdx * ${INSTANCE_VEC4_STRIDE}];\n let w2l = mat4x4<f32>(\n instancesBuffer[insIdx * ${INSTANCE_VEC4_STRIDE} + 1],\n instancesBuffer[insIdx * ${INSTANCE_VEC4_STRIDE} + 2],\n instancesBuffer[insIdx * ${INSTANCE_VEC4_STRIDE} + 3],\n instancesBuffer[insIdx * ${INSTANCE_VEC4_STRIDE} + 4]\n );\n o = (w2l * vec4f(ray.o, 1.0)).xyz;\n d = (w2l * vec4f(ray.d, 0.0)).xyz;\n invD = traversalInverse(d);\n maxDim = maxDimension(abs(d));\n shear = getShear(d, maxDim);\n\n let encodedBlasOffset = bitcast<u32>(insData.x);\n let blasLayout = encodedBlasOffset >> 30u;\n let blasOffset = i32(encodedBlasOffset & 0x3fffffffu);\n let bufferOff = rtUniform.blasOffset;\n\n ${(blasLayoutMask & 8) !== 0 ? /* wgsl */ `\n if (blasLayout == 3u) {\n // TinyBVH's official CWBVH traversal keeps compact node groups on the\n // stack. The upper 8 bits identify intersected interior slots and the\n // lower 8 bits preserve the node's interior-slot mask for popcount\n // addressing. Triangle leaves are merged into a separate 24-bit mask.\n var cwbvhStack: array<vec2u, BLAS_STACK_SIZE>;\n var cwbvhTop = -1;\n var cwbvhVisits = 0u;\n var nodeGroupBase = 0u;\n // A synthetic slot-7 hit selects relative node zero for the root.\n var nodeGroupMask = 0x80000000u;\n let octant = select(0u, 4u, d.x < 0.0)\n | select(0u, 2u, d.y < 0.0)\n | select(0u, 1u, d.z < 0.0);\n let octantInverse = 7u - octant;\n\n // Keep a statically visible trip bound. DXC can otherwise miscompile\n // the dynamic stack-top loop into a non-terminating shader on D3D12.\n // Validated scene rays stay far below this conservative safety cap.\n while (cwbvhVisits < 4096u) {\n cwbvhVisits++;\n \n let internalMask = nodeGroupMask & 0xffu;\n let childBitIndex =\n cwbvhHighestBitIndex8(nodeGroupMask >> 24u) + 24u;\n nodeGroupMask &= ~(1u << childBitIndex);\n if ((nodeGroupMask & 0xff000000u) != 0u) {\n cwbvhTop++;\n \n cwbvhStack[cwbvhTop] = vec2u(nodeGroupBase, nodeGroupMask);\n }\n\n let slot = (childBitIndex - 24u) ^ octantInverse;\n let precedingMask = (1u << slot) - 1u;\n let relativeIndex = countOneBits(internalMask & precedingMask);\n let childNodeIndex = nodeGroupBase + relativeIndex;\n let node = blasOffset + i32(childNodeIndex * 5u);\n let compressed0 = bvhBuffer[node + bufferOff];\n let slotHits = intersectCwbvhInterior(\n node, bufferOff, compressed0, o, invD, tMax\n );\n // Empty child hits need neither metadata nor leaf processing. Clear\n // the current group so the common tail restores pending siblings.\n nodeGroupMask = 0u;\n if (slotHits != 0u) {\n let compressed1 = bvhBuffer[node + bufferOff + 1];\n let nodeInternalMask = bitcast<u32>(compressed0.w) >> 24u;\n var hitMask = 0u;\n var remainingSlotHits = slotHits;\n while (remainingSlotHits != 0u) {\n let childSlot = cwbvhHighestBitIndex8(remainingSlotHits);\n remainingSlotHits &= ~(1u << childSlot);\n let metaWord = select(\n bitcast<u32>(compressed1.z),\n bitcast<u32>(compressed1.w),\n childSlot >= 4u\n );\n let childMeta =\n (metaWord >> ((childSlot & 3u) * 8u)) & 0xffu;\n let localOffset = childMeta & 0x1fu;\n let bitIndex = select(\n localOffset,\n localOffset ^ octantInverse,\n localOffset >= 24u\n );\n let childBits = (childMeta >> 5u) & 0x7u;\n hitMask |= childBits << bitIndex;\n }\n\n nodeGroupBase = bitcast<u32>(compressed1.x);\n nodeGroupMask = (hitMask & 0xff000000u) | nodeInternalMask;\n let triangleBase = bitcast<u32>(compressed1.y);\n var triangleMask = hitMask & 0x00ffffffu;\n while (triangleMask != 0u) {\n let triangleBitIndex = cwbvhHighestBitIndex24(triangleMask);\n triangleMask &= ~(1u << triangleBitIndex);\n let primitiveCount = 1;\n let primitiveOffset = bufferOff + blasOffset\n + i32(triangleBase + triangleBitIndex);\n ${intersectTriangles}\n }\n }\n\n if ((nodeGroupMask & 0xff000000u) != 0u) { continue; }\n if (cwbvhTop >= 0) {\n let restoredGroup = cwbvhStack[cwbvhTop];\n nodeGroupBase = restoredGroup.x;\n nodeGroupMask = restoredGroup.y;\n cwbvhTop--;\n }\n else { break; }\n }\n }\n ` : ''}\n\n var blasStack: array<i32, BLAS_STACK_SIZE>;\n var blasTop = -1;\n if (blasLayout != 3u) {\n blasTop = 0;\n \n blasStack[0] = blasRoot;\n }\n\n while (blasTop >= 0) {\n \n let node = blasStack[blasTop];\n blasTop--;\n var primitiveCount = 0;\n var primitiveOffset = 0;\n\n ${(blasLayoutMask & 4) !== 0 ? /* wgsl */ `\n if (blasLayout == 2u) {\n let i = node + bufferOff;\n let gpu0 = bvhBuffer[i];\n let gpu1 = bvhBuffer[i + 1];\n let gpu2 = bvhBuffer[i + 2];\n let gpu3 = bvhBuffer[i + 3];\n primitiveCount = i32(bitcast<u32>(gpu2.w));\n if (primitiveCount > 0) {\n primitiveOffset = bufferOff + blasOffset\n + i32(bitcast<u32>(gpu3.w));\n }\n else {\n let left = blasOffset + i32(bitcast<u32>(gpu0.w) * 4u);\n let right = blasOffset + i32(bitcast<u32>(gpu1.w) * 4u);\n let leftDistance = intersectBox(\n o, gpu0.xyz, gpu1.xyz, invD, tMax\n );\n let rightDistance = intersectBox(\n o, gpu2.xyz, gpu3.xyz, invD, tMax\n );\n if (leftDistance > rightDistance) {\n if (leftDistance > 0.0) {\n blasTop++; blasStack[blasTop] = left;\n }\n if (rightDistance > 0.0) {\n blasTop++; blasStack[blasTop] = right;\n }\n }\n else {\n if (rightDistance > 0.0) {\n blasTop++; blasStack[blasTop] = right;\n }\n if (leftDistance > 0.0) {\n blasTop++; blasStack[blasTop] = left;\n }\n }\n }\n }\n ` : ''}\n ${(blasLayoutMask & 2) !== 0 ? /* wgsl */ `\n if (blasLayout == 1u) {\n let nodeInfo = bitcast<u32>(node);\n if ((nodeInfo & 0x80000000u) != 0u) {\n primitiveCount = i32((nodeInfo >> 23u) & 0xffu);\n primitiveOffset = bufferOff + blasOffset\n + i32(nodeInfo & 0x007fffffu);\n }\n else {\n let i = node + bufferOff;\n let wide0 = bvhBuffer[i];\n let wide1 = bvhBuffer[i + 1];\n let wide2 = bvhBuffer[i + 2];\n let wide3 = bvhBuffer[i + 3];\n let nodeMin = wide0.xyz;\n let nodeExtent = vec3f(wide0.w, wide1.x, wide1.y);\n let childInfos = array<u32, 4>(\n bitcast<u32>(wide1.z), bitcast<u32>(wide1.w),\n bitcast<u32>(wide2.x), bitcast<u32>(wide2.y)\n );\n let childMinX = unpack4x8unorm(bitcast<u32>(wide2.z));\n let childMinY = unpack4x8unorm(bitcast<u32>(wide2.w));\n let childMinZ = unpack4x8unorm(bitcast<u32>(wide3.x));\n let childMaxX = unpack4x8unorm(bitcast<u32>(wide3.y));\n let childMaxY = unpack4x8unorm(bitcast<u32>(wide3.z));\n let childMaxZ = unpack4x8unorm(bitcast<u32>(wide3.w));\n let tx0 = (nodeMin.x + childMinX * nodeExtent.x - o.x) * invD.x;\n let tx1 = (nodeMin.x + childMaxX * nodeExtent.x - o.x) * invD.x;\n let ty0 = (nodeMin.y + childMinY * nodeExtent.y - o.y) * invD.y;\n let ty1 = (nodeMin.y + childMaxY * nodeExtent.y - o.y) * invD.y;\n let tz0 = (nodeMin.z + childMinZ * nodeExtent.z - o.z) * invD.z;\n let tz1 = (nodeMin.z + childMaxZ * nodeExtent.z - o.z) * invD.z;\n let padding = depthSlabPadding(invD, o);\n let tNear = max(\n max((min(tx0, tx1) - vec4f(padding.x)), (min(ty0, ty1) - vec4f(padding.y))),\n (min(tz0, tz1) - vec4f(padding.z))\n );\n let tFar = min(\n min((max(tx0, tx1) + vec4f(padding.x)), (max(ty0, ty1) + vec4f(padding.y))),\n (max(tz0, tz1) + vec4f(padding.z))\n );\n var distance = select(tFar, tNear, tNear > vec4f(0.0));\n distance = select(\n distance,\n vec4f(-1.0),\n tNear > min(tFar, vec4f(tMax))\n );\n var childDistances = array<f32, 4>(\n distance.x,\n distance.y,\n distance.z,\n distance.w\n );\n\n // Push far-to-near so that the closest child is popped first.\n for (var order = 0; order < 4; order++) {\n var farthestDistance = -1.0;\n var farthestChild = -1;\n for (var child = 0; child < 4; child++) {\n if (\n childInfos[child] != 0u &&\n childDistances[child] > farthestDistance\n ) {\n farthestDistance = childDistances[child];\n farthestChild = child;\n }\n }\n if (farthestChild >= 0 && farthestDistance > 0.0) {\n let childInfo = childInfos[farthestChild];\n var childAddress = bitcast<i32>(childInfo);\n if ((childInfo & 0x80000000u) == 0u) {\n childAddress += blasOffset;\n }\n blasTop++;\n blasStack[blasTop] = childAddress;\n childDistances[farthestChild] = -1.0;\n }\n }\n }\n }\n ` : ''}\n ${(blasLayoutMask & 1) !== 0 ? /* wgsl */ `\n if (blasLayout == 0u) {\n let i = node + bufferOff;\n let r1 = bvhBuffer[i];\n let pointerToRightOrNumPrims = bitcast<i32>(r1.w);\n\n if (pointerToRightOrNumPrims >= 0) {\n let left = node + 2;\n let right = pointerToRightOrNumPrims + blasOffset;\n let d0 = intersectBox(\n o,\n bvhBuffer[left + bufferOff].xyz,\n bvhBuffer[left + 1 + bufferOff].xyz,\n invD,\n tMax\n );\n let d1 = intersectBox(\n o,\n bvhBuffer[right + bufferOff].xyz,\n bvhBuffer[right + 1 + bufferOff].xyz,\n invD,\n tMax\n );\n\n if (d0 > d1) {\n if (d0 > 0.0) {\n blasTop++; blasStack[blasTop] = left;\n \n }\n if (d1 > 0.0) {\n blasTop++; blasStack[blasTop] = right;\n \n }\n }\n else {\n if (d1 > 0.0) {\n blasTop++; blasStack[blasTop] = right;\n \n }\n if (d0 > 0.0) {\n blasTop++; blasStack[blasTop] = left;\n \n }\n }\n }\n else {\n primitiveCount = -pointerToRightOrNumPrims;\n primitiveOffset = i + 2;\n }\n }\n ` : ''}\n if (primitiveCount > 0) {\n ${intersectTriangles}\n }\n }\n }\n }\n}\n\n`;\n}\nexport default function getIntersectCode({\n hasDisplacement,\n hasAnisotropyDirections = true,\n hasCoatNormals = false,\n blasLayoutMask = 1\n}: {\n hasDisplacement: boolean;\n hasAnisotropyDirections?: boolean;\n hasCoatNormals?: boolean;\n blasLayoutMask?: number;\n}) {\n return /*wgsl*/ `\n\n${hasDisplacement ? displacementWgsl : ''}\n${(blasLayoutMask & 8) !== 0 ? cwbvhTraversalWgsl : ''}\n\nstruct Triangle {\n p0: vec3f,\n p1: vec3f,\n p2: vec3f,\n};\n\nfn intersectSphere(rad: f32, pos: vec3f, r: Ray) -> f32 {\n let op: vec3f = pos - r.o;\n let eps: f32 = 0.001;\n let b: f32 = dot(op, r.d);\n var det: f32 = b * b - dot(op, op) + rad * rad;\n if (det < 0.0) {\n return INF;\n }\n\n det = sqrt(det);\n let t1: f32 = b - det;\n if (t1 > eps) {\n return t1;\n }\n\n let t2: f32 = b + det;\n if (t2 > eps) {\n return t2;\n }\n\n return INF;\n}\n\nfn intersectRect(pos: vec3f, u: vec3f, v: vec3f, n: vec3f, r: Ray) -> f32 {\n let dt: f32 = dot(r.d, n);\n let t: f32 = (dot(n, pos) - dot(n, r.o)) / dt;\n\n if (t > EPS) {\n let p: vec3f = r.o + r.d * t;\n let vi: vec3f = p - pos;\n let a1: f32 = dot(u / dot(u, u), vi);\n if (a1 >= 0.0 && a1 <= 1.0) {\n let a2: f32 = dot(v / dot(v, v), vi);\n if (a2 >= 0.0 && a2 <= 1.0) {\n return t;\n }\n }\n }\n\n return INF;\n}\n\nfn sphereSolidAnglePdf(origin: vec3f, center: vec3f, radius: f32) -> f32 {\n let centerDistance = distance(origin, center);\n if (centerDistance <= radius) { return INV4PI; }\n let sinThetaMaxSquared = min(1.0, radius * radius / (centerDistance * centerDistance));\n let cosThetaMax = sqrt(max(0.0, 1.0 - sinThetaMaxSquared));\n return 1.0 / max(TWOPI * (1.0 - cosThetaMax), 1e-8);\n}\n\nfn intersectLight(r: Ray, light: Light, lightSample: ptr<function, LightSampleRec>) -> f32 {\n // Analytic continuation hits use the same conditional light density as NEE.\n // Initialize every field because this record is reused across the light loop.\n var d: f32 = INF;\n (*lightSample).direction = r.d;\n (*lightSample).normal = -r.d;\n (*lightSample).dist = INF;\n (*lightSample).pdf = 0.0;\n (*lightSample).emission = vec3f(0.0);\n (*lightSample).isDelta = false;\n var u = light.u;\n var v = light.v;\n var lightType = i32(light.litType);\n\n if (lightType == RECT_LIGHT) {\n // Adjust the position from center to start corner\n var pos = light.position - u * 0.5 - v * 0.5;\n var normal = normalize(cross(u, v));\n if (dot(normal, r.d) > 0.0) { // Hide backfacing quad light\n return INF;\n }\n\n d = intersectRect(pos, u, v, normal, r);\n if (d < 0.0) {\n d = INF;\n }\n else {\n var cosTheta: f32 = dot(-r.d, normal);\n let spread = light.radiusOrSpread;\n if (cosTheta >= cos(spread * PI / 2.0)) {\n (*lightSample).direction = r.d;\n (*lightSample).normal = normal;\n (*lightSample).dist = d;\n (*lightSample).pdf = (d * d) / (light.area * cosTheta);\n (*lightSample).emission = light.emission;\n (*lightSample).isDelta = false;\n }\n else {\n d = INF;\n }\n }\n }\n\n else if (lightType == SPHERE_LIGHT) {\n if (light.radiusOrSpread <= 0.0) {\n let toPoint = light.position - r.o;\n let projection = dot(toPoint, r.d);\n let perpendicular = toPoint - projection * r.d;\n if (projection <= EPS || dot(perpendicular, perpendicular) > 1e-8) {\n return INF;\n }\n d = projection;\n (*lightSample).direction = r.d;\n (*lightSample).normal = -r.d;\n (*lightSample).dist = d;\n (*lightSample).pdf = 1.0;\n (*lightSample).isDelta = true;\n }\n else {\n d = intersectSphere(light.radiusOrSpread, light.position, r);\n (*lightSample).isDelta = false;\n if (d > 0.0 && d != INF) {\n (*lightSample).direction = r.d;\n (*lightSample).dist = d;\n (*lightSample).normal = normalize(\n r.o + r.d * d - light.position\n );\n (*lightSample).pdf = sphereSolidAnglePdf(\n r.o,\n light.position,\n light.radiusOrSpread\n );\n }\n }\n if (d < 0.0) {\n d = INF;\n }\n else {\n (*lightSample).direction = r.d;\n (*lightSample).dist = d;\n (*lightSample).emission = select(\n light.emission,\n light.emission / max(d * d, 1e-8),\n light.radiusOrSpread <= 0.0\n );\n }\n }\n\n else if (lightType == SPOT_LIGHT) {\n if (light.radiusOrSpread <= 0.0) {\n let toPoint = light.position - r.o;\n let projection = dot(toPoint, r.d);\n let perpendicular = toPoint - projection * r.d;\n if (projection <= EPS || dot(perpendicular, perpendicular) > 1e-8) {\n return INF;\n }\n d = projection;\n (*lightSample).direction = r.d;\n (*lightSample).normal = -r.d;\n (*lightSample).dist = d;\n (*lightSample).pdf = 1.0;\n (*lightSample).isDelta = true;\n }\n else {\n d = intersectSphere(light.radiusOrSpread, light.position, r);\n (*lightSample).isDelta = false;\n }\n if (d != INF) {\n let hitPt = r.o + d * r.d;\n let multiplier = evaluateSpotEmissionMultiplier(light, normalize(r.o - hitPt));\n if (multiplier <= 0.0) { return INF; }\n if (light.radiusOrSpread > 0.0) {\n (*lightSample).direction = r.d;\n (*lightSample).dist = d;\n (*lightSample).normal = normalize(\n r.o + r.d * d - light.position\n );\n (*lightSample).pdf = sphereSolidAnglePdf(r.o, light.position, light.radiusOrSpread);\n }\n (*lightSample).emission = light.emission * multiplier * select(\n 1.0,\n 1.0 / max(d * d, 1e-8),\n light.radiusOrSpread <= 0.0\n );\n }\n }\n\n else if (lightType != RECT_LIGHT && lightType != SPHERE_LIGHT && lightType != SPOT_LIGHT) {\n // Unsupported light\n d = INF;\n }\n\n return d;\n}\n\nfn randomPickMaterial(m0: vec3i, m1: vec3i, m2: vec3i, bary: vec3f) -> i32 {\n let m00 = unpackMaterialIndex(m0.x);\n let m01 = unpackMaterialIndex(m0.y);\n let m02 = unpackMaterialIndex(m0.z);\n let m10 = unpackMaterialIndex(m1.x);\n let m11 = unpackMaterialIndex(m1.y);\n let m12 = unpackMaterialIndex(m1.z);\n let m20 = unpackMaterialIndex(m2.x);\n let m21 = unpackMaterialIndex(m2.y);\n let m22 = unpackMaterialIndex(m2.z);\n var w00 = m00.y * bary.x;\n var w01 = m01.y * bary.x;\n var w02 = m02.y * bary.x;\n var w10 = m10.y * bary.y;\n var w11 = m11.y * bary.y;\n var w12 = m12.y * bary.y;\n var w20 = m20.y * bary.z;\n var w21 = m21.y * bary.z;\n var w22 = m22.y * bary.z;\n\n var wTotal = w00 + w01 + w02 + w10 + w11 + w12 + w20 + w21 + w22;\n // Accumulate weights\n w00 = w00 / wTotal;\n w01 = w01 / wTotal + w00;\n w02 = w02 / wTotal + w01;\n w10 = w10 / wTotal + w02;\n w11 = w11 / wTotal + w10;\n w12 = w12 / wTotal + w11;\n w20 = w20 / wTotal + w12;\n w21 = w21 / wTotal + w20;\n w22 = w22 / wTotal + w21;\n\n let rand = hashedSample(0u);\n var mIdx: f32;\n if (rand < w00) {\n mIdx = m00.x;\n } else if (rand < w01) {\n mIdx = m01.x;\n } else if (rand < w02) {\n mIdx = m02.x;\n } else if (rand < w10) {\n mIdx = m10.x;\n } else if (rand < w11) {\n mIdx = m11.x;\n } else if (rand < w12) {\n mIdx = m12.x;\n } else if (rand < w20) {\n mIdx = m20.x;\n } else if (rand < w21) {\n mIdx = m21.x;\n } else {\n mIdx = m22.x;\n }\n return i32(mIdx);\n}\n\nfn resolveSurfaceMaterialIndex(\n fallbackMaterialIndex: i32,\n index: vec3i,\n bary: vec3f\n) -> i32 {\n let m0 = vec3i(attrBuffers[index.x * ATTR_STRIDE + MATERIALINDEX_OFF]${hasAnisotropyDirections ? '.xyz' : ''});\n let m1 = vec3i(attrBuffers[index.y * ATTR_STRIDE + MATERIALINDEX_OFF]${hasAnisotropyDirections ? '.xyz' : ''});\n let m2 = vec3i(attrBuffers[index.z * ATTR_STRIDE + MATERIALINDEX_OFF]${hasAnisotropyDirections ? '.xyz' : ''});\n if (m0.x > 0 || m1.x > 0 || m2.x > 0) {\n return randomPickMaterial(m0, m1, m2, bary);\n }\n return fallbackMaterialIndex;\n}\n\n\n\n${hasAnisotropyDirections ? /* wgsl */ `\nfn anisotropy_axes(normal: vec3f, dp1: vec3f, dp2: vec3f, duv1: vec2f, duv2: vec2f, input: vec4f) -> mat2x3f {\n let determinant = duv1.x * duv2.y - duv1.y * duv2.x;\n var t = input.xyz;\n var b = vec3f(0.0);\n // Internal W=2 marks an authored XYZ tangent whose sign is derived from UVs.\n var sign = select(input.w, 0.0, abs(input.w) > 1.5);\n if (abs(determinant) > 1e-12) {\n let dt = (dp1 * duv2.y - dp2 * duv1.y) / determinant;\n let db = (dp2 * duv1.x - dp1 * duv2.x) / determinant;\n if (dot(t, t) < 1e-8) { t = dt; }\n if (sign == 0.0) { sign = select(1.0, -1.0, dot(cross(normal, dt), db) < 0.0); }\n }\n t -= normal * dot(normal, t);\n if (dot(t, t) < 1e-8) {\n let signZ = select(-1.0, 1.0, normal.z >= 0.0);\n let factor = -1.0 / (signZ + normal.z);\n let xy = normal.x * normal.y * factor;\n t = vec3f(1.0 + signZ * normal.x * normal.x * factor, signZ * xy, -signZ * normal.x);\n b = vec3f(xy, signZ + normal.y * normal.y * factor, -normal.y);\n }\n else { t = normalize(t); b = normalize(cross(normal, t)); }\n return mat2x3f(t, b * select(1.0, -1.0, sign < 0.0));\n}\n\n` : /* wgsl */ ``}\nfn fetchAttributes(\n si: ptr<function, SurfaceInteraction>,\n tri: Triangle,\n bary: vec3f,\n index: vec3i,\n isHitMicroTriangle: bool,\n resolvedMaterialIndex: i32\n) {\n (*si).hit = true;\n // Get projected uv\n let material = materialsBuffer[(*si).materialIndex];\n let uvProjection = i32(material.uvProjection);\n\n let i0 = index.x;\n let i1 = index.y;\n let i2 = index.z;\n (*si).vertexColor = vec3f(1.0);\n if (USE_VERTEX_COLOR) {\n (*si).vertexColor = bary.x * attrBuffers[i0 * ATTR_STRIDE + COLOR_OFF].xyz\n + bary.y * attrBuffers[i1 * ATTR_STRIDE + COLOR_OFF].xyz\n + bary.z * attrBuffers[i2 * ATTR_STRIDE + COLOR_OFF].xyz;\n }\n let isSphericalProj = uvProjection == ${uvProjectionConstants.spherical};\n\n var normal: vec3f = (*si).normal;\n var normalForSphericalProj: vec3f;\n var n0 = vec3f(0.0);\n var n1 = vec3f(0.0);\n var n2 = vec3f(0.0);\n // Otherwise use the normal from the microtriangle\n // The exception is spherical projection, we need the original normal to project the uv. Or the case like earth has wrong normal.\n if (!isHitMicroTriangle || isSphericalProj) {\n n0 = attrBuffers[i0 * ATTR_STRIDE + NORMAL_OFF]${hasAnisotropyDirections ? '.xyz' : ''};\n n1 = attrBuffers[i1 * ATTR_STRIDE + NORMAL_OFF]${hasAnisotropyDirections ? '.xyz' : ''};\n n2 = attrBuffers[i2 * ATTR_STRIDE + NORMAL_OFF]${hasAnisotropyDirections ? '.xyz' : ''};\n normalForSphericalProj = normalize(bary.x * n0 + bary.y * n1 + bary.z * n2);\n if (!isHitMicroTriangle) {\n normal = normalForSphericalProj;\n }\n }\n\n var uv: vec2f;\n var uv0: vec2f;\n var uv1: vec2f;\n var uv2: vec2f;\n var secondaryUv = vec2f(0.0);\n if (USE_UV) {\n uv0 = attrBuffers[i0 * ATTR_STRIDE + UV_OFF].xy;\n uv1 = attrBuffers[i1 * ATTR_STRIDE + UV_OFF].xy;\n uv2 = attrBuffers[i2 * ATTR_STRIDE + UV_OFF].xy;\n uv = bary.x * uv0 + bary.y * uv1 + bary.z * uv2;\n if (USE_UV1) {\n let secondaryUv0 = attrBuffers[i0 * ATTR_STRIDE + UV1_OFF].xy;\n let secondaryUv1 = attrBuffers[i1 * ATTR_STRIDE + UV1_OFF].xy;\n let secondaryUv2 = attrBuffers[i2 * ATTR_STRIDE + UV1_OFF].xy;\n secondaryUv = bary.x * secondaryUv0\n + bary.y * secondaryUv1\n + bary.z * secondaryUv2;\n }\n }\n\n if (USE_TANGENT) {\n let t0 = attrBuffers[i0 * ATTR_STRIDE + TANGENT_OFF]${hasAnisotropyDirections ? '.xyz' : ''};\n let t1 = attrBuffers[i1 * ATTR_STRIDE + TANGENT_OFF]${hasAnisotropyDirections ? '.xyz' : ''};\n let t2 = attrBuffers[i2 * ATTR_STRIDE + TANGENT_OFF]${hasAnisotropyDirections ? '.xyz' : ''};\n (*si).tangent = normalize(bary.x * t0 + bary.y * t1 + bary.z * t2);\n }\n\n if (resolvedMaterialIndex >= 0) {\n (*si).materialIndex = resolvedMaterialIndex;\n }\n else {\n (*si).materialIndex = resolveSurfaceMaterialIndex(\n (*si).materialIndex,\n index,\n bary\n );\n }\n\n if (uvProjection > 0) {\n // NOTE: position and normal are not transformed to world space yet.\n uv = projectUV(uvProjection, (*si).position, select(normal, normalForSphericalProj, isSphericalProj));\n secondaryUv = uv;\n }\n\n uv = uv * material.uvScale;\n secondaryUv = secondaryUv * material.uvScale;\n\n // Force repeating UVs for triplanar mapping\n if (uvProjection == ${uvProjectionConstants.triplanar}) {\n uv = fract(uv);\n }\n\n${hasAnisotropyDirections ? /* wgsl */ `\n${hasCoatNormals ? /* wgsl */ ` (*si).coatNormal = normal;` : ''}\n let directionMaterial = materialsBuffer[(*si).materialIndex];\n if (directionMaterial.anisotropyMapIndex >= 0.0 || directionMaterial.specularAnisotropyRotation != 0.0 || directionMaterial.coatAnisotropyRotation != 0.0${hasCoatNormals ? \" || (USE_COAT_NORMAL_MAPS && directionMaterial.coatNormalMapIndex >= 0.0)\" : \"\"}) {\n let dp1 = tri.p0 - tri.p2;\n let dp2 = tri.p1 - tri.p2;\n var inputTangent = vec4f(0.0);\n if (USE_TANGENT) {\n inputTangent = bary.x * attrBuffers[i0 * ATTR_STRIDE + TANGENT_OFF]\n + bary.y * attrBuffers[i1 * ATTR_STRIDE + TANGENT_OFF]\n + bary.z * attrBuffers[i2 * ATTR_STRIDE + TANGENT_OFF];\n }\n let geometryAxes = anisotropy_axes(normal, dp1, dp2, uv0-uv2, uv1-uv2, inputTangent);\n (*si).geometryTangent = geometryAxes[0];\n (*si).geometryBitangent = geometryAxes[1];\n var directionAxes = geometryAxes;\n if (USE_UV1 && directionMaterial.anisotropyMapIndex >= 0.0 && mapInfo[i32(directionMaterial.anisotropyMapIndex) + NUM_MAPS * 2].x > 0.5) {\n let a = attrBuffers[i0 * ATTR_STRIDE + UV1_OFF].xy;\n let b = attrBuffers[i1 * ATTR_STRIDE + UV1_OFF].xy;\n let c = attrBuffers[i2 * ATTR_STRIDE + UV1_OFF].xy;\n directionAxes = anisotropy_axes(normal, dp1, dp2, a-c, b-c, select(inputTangent, vec4f(0.0), inputTangent.w == 0.0));\n }\n (*si).directionTangent = directionAxes[0];\n (*si).directionBitangent = directionAxes[1];\n${hasCoatNormals ? /* wgsl */ `\n if (USE_COAT_NORMAL_MAPS && directionMaterial.coatNormalMapIndex >= 0.0) {\n let coatIndex = i32(directionMaterial.coatNormalMapIndex);\n var coatAxes = geometryAxes;\n if (USE_UV1 && mapInfo[coatIndex + NUM_MAPS * 2].x > 0.5) {\n let a = attrBuffers[i0 * ATTR_STRIDE + UV1_OFF].xy;\n let b = attrBuffers[i1 * ATTR_STRIDE + UV1_OFF].xy;\n let c = attrBuffers[i2 * ATTR_STRIDE + UV1_OFF].xy;\n coatAxes = anisotropy_axes(normal, dp1, dp2, a-c, b-c,\n select(inputTangent, vec4f(0.0), inputTangent.w == 0.0));\n }\n // Procedural projection overrides both UV sets. UV0 has already applied\n // triplanar wrapping, so use it for either texture channel in that case.\n let decoded = sampleMap(coatIndex, getMapUvSet(coatIndex, uv,\n select(secondaryUv, uv, uvProjection > 0))).rgb * 2.0 - 1.0;\n let mapped = coatAxes[0] * decoded.x * directionMaterial.coatNormalScale.x\n + coatAxes[1] * decoded.y * directionMaterial.coatNormalScale.y + normal * decoded.z;\n if (dot(mapped, mapped) > 1e-12) { (*si).coatNormal = normalize(mapped); }\n }\n` : ''}\n }\n\n` : /* wgsl */ ``}\n (*si).normal = normal;\n (*si).vertexNormal = normal;\n (*si).shadowOffset = vec3f(0.0);\n if (!isHitMicroTriangle) {\n // Smooth-limit geometry offset for direct-light shadow rays. This follows\n // the Cycles shadow terminator construction while keeping the result in\n // local space until the instance transform is available.\n let geometricNormal = normalize(cross(tri.p1 - tri.p0, tri.p2 - tri.p0));\n let smoothNormal = bary.x * n0 + bary.y * n1 + bary.z * n2;\n let a = dot(n2 - n0, tri.p0 - tri.p2);\n let b = dot(n2 - n1, tri.p1 - tri.p2);\n let c = dot(n1 - n0, tri.p1 - tri.p0);\n var h = a * bary.x * (bary.x - 1.0)\n + (a + b + c) * bary.x * bary.y\n + b * bary.y * (bary.y - 1.0);\n if (dot(smoothNormal, geometricNormal) > 0.0) {\n var h0 = max(max(dot(tri.p1 - tri.p0, n0), dot(tri.p2 - tri.p0, n0)), 0.0);\n var h1 = max(max(dot(tri.p0 - tri.p1, n1), dot(tri.p2 - tri.p1, n1)), 0.0);\n var h2 = max(max(dot(tri.p0 - tri.p2, n2), dot(tri.p1 - tri.p2, n2)), 0.0);\n h0 = max(dot(tri.p0 - (*si).position, n0) + h0, 0.0);\n h1 = max(dot(tri.p1 - (*si).position, n1) + h1, 0.0);\n h2 = max(dot(tri.p2 - (*si).position, n2) + h2, 0.0);\n h = max(min(min(h0, h1), h2), h * 0.5);\n }\n else {\n var h0 = max(max(dot(tri.p0 - tri.p1, n0), dot(tri.p0 - tri.p2, n0)), 0.0);\n var h1 = max(max(dot(tri.p1 - tri.p0, n1), dot(tri.p1 - tri.p2, n1)), 0.0);\n var h2 = max(max(dot(tri.p2 - tri.p0, n2), dot(tri.p2 - tri.p1, n2)), 0.0);\n h0 = max(dot((*si).position - tri.p0, n0) + h0, 0.0);\n h1 = max(dot((*si).position - tri.p1, n1) + h1, 0.0);\n h2 = max(dot((*si).position - tri.p2, n2) + h2, 0.0);\n h = min(-min(min(h0, h1), h2), h * 0.5);\n }\n (*si).shadowOffset = smoothNormal * h;\n }\n (*si).uv = uv;\n (*si).uv1 = secondaryUv;\n if (USE_NORMAL_MAPS) {\n let dp1 = tri.p0 - tri.p2;\n let dp2 = tri.p1 - tri.p2;\n let duv1 = uv0 - uv2;\n let duv2 = uv1 - uv2;\n (*si).normal = getMatNormal(\n (*si).materialIndex, uv, normal, dp1, dp2, duv1, duv2\n );\n }\n}\n\nstruct TriangleIntersect {\n t: f32,\n barycentric: vec3f,\n};\n\n\n// Triangle-ray intersection\n// Faster than the classic Möller–Trumbore intersection algorithm\n// http://www.pbr-book.org/3ed-2018/Shapes/Triangle_Meshes.html#TriangleIntersection\nfn intersectTriangle(o: vec3f, d: vec3f, tri: Triangle, maxDim: i32, shear: vec3f, tMax: f32) -> TriangleIntersect {\n var ti: TriangleIntersect;\n\n // translate vertices based on ray origin\n var p0t = tri.p0 - o;\n var p1t = tri.p1 - o;\n var p2t = tri.p2 - o;\n\n // permute components of triangle vertices\n if (maxDim == 0) {\n p0t = p0t.yzx;\n p1t = p1t.yzx;\n p2t = p2t.yzx;\n } else if (maxDim == 1) {\n p0t = p0t.zxy;\n p1t = p1t.zxy;\n p2t = p2t.zxy;\n }\n\n // apply shear transformation to translated vertex positions\n // TODO swizzled assignment is not supported in wgsl\n p0t = vec3f(p0t.xy + shear.xy * p0t.z, p0t.z);\n p1t = vec3f(p1t.xy + shear.xy * p1t.z, p1t.z);\n p2t = vec3f(p2t.xy + shear.xy * p2t.z, p2t.z);\n\n // compute edge function coefficients\n var e = vec3f(\n p1t.x * p2t.y - p1t.y * p2t.x,\n p2t.x * p0t.y - p2t.y * p0t.x,\n p0t.x * p1t.y - p0t.y * p1t.x\n );\n\n if (DEPTH_QUERY_MODE) {\n // Exact grid rays can land on shared edges. Roundoff in the two edge\n // products must not turn the edge into a crack between both triangles.\n let edgeScale = abs(vec3f(p1t.x * p2t.y, p2t.x * p0t.y, p0t.x * p1t.y))\n + abs(vec3f(p1t.y * p2t.x, p2t.y * p0t.x, p0t.y * p1t.x));\n e = select(e, vec3f(0.0), abs(e) <= edgeScale * 1e-6);\n }\n\n // check if intersection is inside triangle\n if (any(e < vec3f(0)) && any(e > vec3f(0))) {\n return ti;\n }\n\n let det = e.x + e.y + e.z;\n if (DEPTH_QUERY_MODE && det == 0.0) { return ti; }\n\n p0t.z *= shear.z;\n p1t.z *= shear.z;\n p2t.z *= shear.z;\n let tScaled = (e.x * p0t.z + e.y * p1t.z + e.z * p2t.z);\n\n // check if closer intersection already exists\n if (abs(tScaled) > abs(tMax * det)) {\n return ti;\n }\n\n let invDet = 1. / det;\n ti.t = tScaled * invDet;\n ti.barycentric = e * invDet;\n\n return ti;\n}\n\n// Pinhole depth queries can lie exactly on a grid vertex or a flat slab.\n// Specialize conservative slabs for that entry point; beauty retains its\n// original traversal arithmetic and random streams.\noverride DEPTH_QUERY_MODE: bool = false;\n\nfn traversalInverse(d: vec3f) -> vec3f {\n if (!DEPTH_QUERY_MODE) { return 1.0 / d; }\n return 1.0 / select(d, select(vec3f(1e-20), vec3f(-1e-20), d < vec3f(0.0)), abs(d) < vec3f(1e-20));\n}\n\nfn depthSlabPadding(invD: vec3f, o: vec3f) -> vec3f {\n if (!DEPTH_QUERY_MODE) { return vec3f(0.0); }\n return abs(invD) * max(vec3f(1.0), abs(o)) * 1e-6;\n}\n\n// Branchless ray/box intersection\n// https://tavianator.com/fast-branchless-raybounding-box-intersections/\nfn intersectBox(o: vec3f, bmin: vec3f, bmax: vec3f, invD: vec3f, tMax: f32) -> f32 {\n let tBot = (bmin - o) * invD;\n let tTop = (bmax - o) * invD;\n let padding = depthSlabPadding(invD, o);\n let tNear = min(tBot, tTop) - padding;\n let tFar = max(tBot, tTop) + padding;\n let t0 = max(tNear.x, max(tNear.y, tNear.z));\n let t1 = min(tFar.x, min(tFar.y, tFar.z));\n return select(select(t1, t0, t0 > 0.0), -1.0, (t0 > t1 || t0 > tMax));\n}\n\nfn maxDimension(v: vec3f) -> i32 {\n return select(select(2, 1, v.y > v.z), select(2, 0, v.x > v.z), v.x > v.y);\n}\n\nfn getShear(d: vec3f, maxDim: i32) -> vec3f {\n // Permute space so that the z dimension is the one where the absolute value of the ray's direction is largest.\n // Then create a shear transformation that aligns ray direction with the +z axis\n if (maxDim == 0) {\n return vec3f(-d.y, -d.z, 1.0) / d.x;\n } else if (maxDim == 1) {\n return vec3f(-d.z, -d.x, 1.0) / d.y;\n } else {\n return vec3f(-d.x, -d.y, 1.0) / d.z;\n }\n}\n\nfn completeSurfaceInteraction(\n si: ptr<function, SurfaceInteraction>,\n hitTri: Triangle,\n hitBarycentric: vec3f,\n hitIndex: vec3i,\n isHitMicroTriangle: bool,\n hitInsIdx: i32,\n hitNormal: vec3f,\n hitPosition: vec3f,\n resolvedMaterialIndex: i32\n) {\n (*si).instanceIndex = hitInsIdx;\n (*si).barycentrics = hitBarycentric.xy;\n (*si).normal = hitNormal;\n (*si).position = hitPosition;\n (*si).materialIndex = i32(unpackMaterialIndex(\n bitcast<i32>(instancesBuffer[hitInsIdx * ${INSTANCE_VEC4_STRIDE}].z)\n ).x);\n // NOTE: Must be called before transforming normal/position to world space.\n // UV projection depends on the local position.\n fetchAttributes(\n si,\n hitTri,\n hitBarycentric,\n hitIndex,\n isHitMicroTriangle,\n resolvedMaterialIndex\n );\n\n let l2w = mat4x4<f32>(\n instancesBuffer[hitInsIdx * ${INSTANCE_VEC4_STRIDE} + 5],\n instancesBuffer[hitInsIdx * ${INSTANCE_VEC4_STRIDE} + 6],\n instancesBuffer[hitInsIdx * ${INSTANCE_VEC4_STRIDE} + 7],\n instancesBuffer[hitInsIdx * ${INSTANCE_VEC4_STRIDE} + 8]\n );\n let w2l = mat4x4<f32>(\n instancesBuffer[hitInsIdx * ${INSTANCE_VEC4_STRIDE} + 1],\n instancesBuffer[hitInsIdx * ${INSTANCE_VEC4_STRIDE} + 2],\n instancesBuffer[hitInsIdx * ${INSTANCE_VEC4_STRIDE} + 3],\n instancesBuffer[hitInsIdx * ${INSTANCE_VEC4_STRIDE} + 4]\n );\n let normalTransform = transpose(mat3x3<f32>(\n w2l[0].xyz,\n w2l[1].xyz,\n w2l[2].xyz\n ));\n\n (*si).faceNormal = normalize(normalTransform * hitNormal);\n (*si).vertexNormal = normalize(normalTransform * (*si).vertexNormal);\n (*si).normal = normalize(normalTransform * (*si).normal);\n${hasCoatNormals ? /* wgsl */ ` (*si).coatNormal = normalize(normalTransform * (*si).coatNormal);` : ''}\n if (USE_TANGENT && dot((*si).tangent, (*si).tangent) > 0.0) {\n (*si).tangent = normalize((l2w * vec4f((*si).tangent, 0.0)).xyz);\n }\n${hasAnisotropyDirections ? /* wgsl */ `\n (*si).directionTangent = (l2w * vec4f((*si).directionTangent, 0.0)).xyz;\n (*si).directionBitangent = (l2w * vec4f((*si).directionBitangent, 0.0)).xyz;\n (*si).geometryTangent = (l2w * vec4f((*si).geometryTangent, 0.0)).xyz;\n (*si).geometryBitangent = (l2w * vec4f((*si).geometryBitangent, 0.0)).xyz;\n` : /* wgsl */ ``}\n (*si).shadowOffset = (l2w * vec4f((*si).shadowOffset, 0.0)).xyz;\n (*si).position = (l2w * vec4((*si).position, 1.0)).xyz;\n}\n\n\n${createIntersectionAlphaWgsl(hasAnisotropyDirections)}\n\n\n\n// Traverse BVH, find closest triangle intersection, and return surface information\nfn intersectScene(ray: Ray, si: ptr<function, SurfaceInteraction>, bounces: i32, doubleSided: bool, isRefractionRay: bool) {\n\n (*si).hit = false;\n\n if (rtUniform.isEmpty > 0) {\n return;\n }\n\n var tMax = RAY_MAX_DISTANCE;\n var maxDim: i32;\n var shear: vec3f;\n\n var hitIndex: vec3i;\n var hitTri: Triangle;\n var hitInsIdx: i32;\n var currentTriangleIndex: i32 = (*si).triangleIndex;\n\n ${getBVHTraverseCode(\n `\n(*si).triangleIndex = triangleIndex;\nhitTri = tri;\nhitIndex = index;\nhitInsIdx = insIdx;\n `,\n hasDisplacement,\n false,\n blasLayoutMask,\n hasAnisotropyDirections\n )}\n\n if (isHit) {\n completeSurfaceInteraction(\n si,\n hitTri,\n hitBarycentric,\n hitIndex,\n isHitMicroTriangle,\n hitInsIdx,\n hitNormal,\n hitPosition,\n -1\n );\n }\n}\n\n// Local intersection helper for shader bevel.\nfn intersectSceneBevel(ray: Ray, si: ptr<function, SurfaceInteraction>, instanceIndex: i32, maxDistance: f32) {\n (*si).hit = false;\n\n if (rtUniform.isEmpty > 0) {\n return;\n }\n\n let bounces = 0;\n let doubleSided = true;\n let isRefractionRay = false;\n var tMax = maxDistance;\n var maxDim: i32;\n var shear: vec3f;\n var hitIndex: vec3i;\n var hitTri: Triangle;\n var hitInsIdx: i32;\n var currentTriangleIndex: i32 = -1;\n\n ${getBVHTraverseCode(\n `\n(*si).triangleIndex = triangleIndex;\nhitTri = tri;\nhitIndex = index;\nhitInsIdx = insIdx;\n `,\n hasDisplacement,\n false,\n blasLayoutMask,\n hasAnisotropyDirections,\n `\n if (insIdx != instanceIndex) {\n continue;\n }\n `\n )}\n\n if (isHit) {\n (*si).instanceIndex = hitInsIdx;\n (*si).normal = hitNormal;\n (*si).position = hitPosition;\n (*si).materialIndex = i32(unpackMaterialIndex(bitcast<i32>(instancesBuffer[hitInsIdx * ${INSTANCE_VEC4_STRIDE}].z)).x);\n fetchAttributes(\n si,\n hitTri,\n hitBarycentric,\n hitIndex,\n isHitMicroTriangle,\n -1\n );\n\n let l2w = mat4x4<f32>(\n instancesBuffer[hitInsIdx * ${INSTANCE_VEC4_STRIDE} + 5],\n instancesBuffer[hitInsIdx * ${INSTANCE_VEC4_STRIDE} + 6],\n instancesBuffer[hitInsIdx * ${INSTANCE_VEC4_STRIDE} + 7],\n instancesBuffer[hitInsIdx * ${INSTANCE_VEC4_STRIDE} + 8]\n );\n let w2l = mat4x4<f32>(\n instancesBuffer[hitInsIdx * ${INSTANCE_VEC4_STRIDE} + 1],\n instancesBuffer[hitInsIdx * ${INSTANCE_VEC4_STRIDE} + 2],\n instancesBuffer[hitInsIdx * ${INSTANCE_VEC4_STRIDE} + 3],\n instancesBuffer[hitInsIdx * ${INSTANCE_VEC4_STRIDE} + 4]\n );\n let normalTransform = transpose(mat3x3<f32>(\n w2l[0].xyz,\n w2l[1].xyz,\n w2l[2].xyz\n ));\n (*si).faceNormal = normalize(normalTransform * hitNormal);\n (*si).vertexNormal = normalize(normalTransform * (*si).vertexNormal);\n (*si).normal = normalize(normalTransform * (*si).normal);\n${hasCoatNormals ? /* wgsl */ ` (*si).coatNormal = normalize(normalTransform * (*si).coatNormal);` : ''}\n if (USE_TANGENT && dot((*si).tangent, (*si).tangent) > 0.0) {\n (*si).tangent = normalize((l2w * vec4f((*si).tangent, 0.0)).xyz);\n }\n${hasAnisotropyDirections ? /* wgsl */ `\n (*si).directionTangent = (l2w * vec4f((*si).directionTangent, 0.0)).xyz;\n (*si).directionBitangent = (l2w * vec4f((*si).directionBitangent, 0.0)).xyz;\n (*si).geometryTangent = (l2w * vec4f((*si).geometryTangent, 0.0)).xyz;\n (*si).geometryBitangent = (l2w * vec4f((*si).geometryBitangent, 0.0)).xyz;\n` : /* wgsl */ ``}\n (*si).shadowOffset = (l2w * vec4f((*si).shadowOffset, 0.0)).xyz;\n (*si).position = (l2w * vec4((*si).position, 1.0)).xyz;\n }\n}\n\nfn intersectSceneShadow(ray: Ray, lightDistance: f32, currTriangleIndex: i32) -> bool {\n if (rtUniform.isEmpty > 0) {\n return false;\n }\n\n let doubleSided = true;\n let shadowRayTMax = min(max(lightDistance, 0.0), RAY_MAX_DISTANCE);\n var tMax = shadowRayTMax;\n var maxDim: i32;\n var shear: vec3f;\n\n let bounces = 1;\n var isRefractionRay = false;\n var currentTriangleIndex: i32 = currTriangleIndex;\n\n ${getBVHTraverseCode(\n /*wgsl */ `\n// Traversal is already clipped to the sampled light. Affine instance\n// transforms preserve the ray parameter because local directions are not\n// renormalized, so no local-to-world hit transform or distance() is needed.\nvar isOccluded = true;\n\nif (isOccluded) {\n if (HAS_ALPHA) {\n let materialIndex = i32(unpackMaterialIndex(bitcast<i32>(insData.z)).x);\n if (!intersectionAlphaPasses(\n materialIndex,\n index,\n hitBarycentric,\n hitPosition,\n hitNormal\n )) {\n // Continue looking for an opaque hit, but never traverse beyond the light.\n tMax = shadowRayTMax;\n isOccluded = false;\n }\n }\n}\nif (isOccluded) {\n return true;\n}\n `,\n hasDisplacement,\n true,\n blasLayoutMask,\n hasAnisotropyDirections\n )}\n\n return false;\n}\n\n`;\n}\n"]}
@@ -8,20 +8,20 @@ import { materialAlphaModeConstants } from '../../../scene/OpenPBRMaterial.js';
8
8
  */
9
9
  export function createIntersectionAlphaWgsl(hasAnisotropyDirections = true) {
10
10
  return /* wgsl */ `
11
- fn nr(
12
- pl: i32,
11
+ fn nG(
12
+ pt: i32,
13
13
  index: vec3i,
14
14
  aB: vec3f,
15
- eU: vec3f,
16
- eC: vec3f
15
+ eY: vec3f,
16
+ eD: vec3f
17
17
  ) -> bool {
18
- var materialIndex = pl;
18
+ var materialIndex = pt;
19
19
  let m0 = vec3i(attrBuffers[index.x * ATTR_STRIDE + MATERIALINDEX_OFF]${hasAnisotropyDirections ? '.xyz' : ''});
20
20
  let m1 = vec3i(attrBuffers[index.y * ATTR_STRIDE + MATERIALINDEX_OFF]${hasAnisotropyDirections ? '.xyz' : ''});
21
21
  let m2 = vec3i(attrBuffers[index.z * ATTR_STRIDE + MATERIALINDEX_OFF]${hasAnisotropyDirections ? '.xyz' : ''});
22
22
 
23
23
  if (m0.x > 0 || m1.x > 0 || m2.x > 0) {
24
- materialIndex = fj(m0, m1, m2, aB);
24
+ materialIndex = fm(m0, m1, m2, aB);
25
25
  }
26
26
 
27
27
  let ma = materialsBuffer[materialIndex];
@@ -37,7 +37,7 @@ let uv2 = attrBuffers[index.z * ATTR_STRIDE + UV_OFF].xy;
37
37
  var uv = aB.x * uv0 +
38
38
  aB.y * uv1 + aB.z * uv2;
39
39
  if (uvProjection > 0) {
40
- uv = gC(uvProjection, eU, eC);
40
+ uv = gH(uvProjection, eY, eD);
41
41
  }
42
42
  uv *= ma.uvScale;
43
43
  if (uvProjection == ${uvProjectionConstants.triplanar}) {
@@ -57,7 +57,7 @@ let uv2 = attrBuffers[index.z * ATTR_STRIDE + UV_OFF].xy;
57
57
  var uv = aB.x * uv0 +
58
58
  aB.y * uv1 + aB.z * uv2;
59
59
  if (uvProjection > 0) {
60
- uv = gC(uvProjection, eU, eC);
60
+ uv = gH(uvProjection, eY, eD);
61
61
  }
62
62
  uv *= ma.uvScale;
63
63
  if (uvProjection == ${uvProjectionConstants.triplanar}) {
@@ -20,7 +20,7 @@ else if ((*ma).alphaMode == ${materialAlphaModeConstants.mask}) {
20
20
  : '(*ma).alpha = 1.0;';
21
21
  return /* wgsl */ `
22
22
 
23
- ${hasAnisotropyDirections ? /* wgsl */ `fn fb(roughness: f32, strength: f32) -> vec2f {
23
+ ${hasAnisotropyDirections ? /* wgsl */ `fn fe(roughness: f32, strength: f32) -> vec2f {
24
24
  if (strength == 0.0) { return vec2f(roughness, 0.0); }
25
25
  let u = roughness * roughness;
26
26
  let a = u + (1.0 - u) * strength * strength;
@@ -29,43 +29,55 @@ return vec2f(pow((a*a + u*u) * 0.5, 0.25), clamp(1.0 - u/a, 0.0, 1.0));
29
29
  }
30
30
  ` : /* wgsl */ ``}
31
31
 
32
- fn oB(c: vec3f) -> vec3f {
32
+ fn oK(c: vec3f) -> vec3f {
33
33
  return mix(pow(c * 0.9478672986 + 0.0521327014, vec3f(2.4)), c * 0.0773993808, vec3f(c < vec3f(0.04045)));
34
34
  }
35
35
 
36
36
  fn decodeMapColor(idx: i32, c: vec3f) -> vec3f {
37
37
  if (mapInfo[idx + NUM_MAPS * 2].y > 0.5) { return c; }
38
- return oB(c);
38
+ return oK(c);
39
39
  }
40
40
 
41
41
  fn ae(val: i32) -> vec2f {
42
42
  return vec2f(f32(val >> 8), f32(val & 0xff) / 255.0);
43
43
  }
44
44
 
45
- fn oa(idx: i32) -> vec2f {
45
+ fn kJ(idx: i32) -> vec2f {
46
46
  return mapInfo[idx].xy;
47
47
  }
48
48
 
49
- fn pj(idx: i32) -> i32 {
49
+ fn lk(idx: i32) -> i32 {
50
50
  return i32(floor(mapInfo[idx].z));
51
51
  }
52
52
 
53
- fn nZ(idx: i32) -> vec2f {
53
+ fn jP(idx: i32) -> vec2f {
54
54
  return vec2f(fract(mapInfo[idx].z), mapInfo[idx].w);
55
55
  }
56
56
 
57
- fn sg(idx: i32, uv: vec2f) -> vec2f {
57
+ fn fS(value: f32, mode: f32) -> f32 {
58
+ if (mode < 0.5) { return fract(value); }
59
+ if (mode < 1.5) { return clamp(value, 0.0, 1.0); }
60
+ return 1.0 - abs(1.0 - (value - 2.0 * floor(value * 0.5)));
61
+ }
62
+
63
+ fn kv(idx: i32, uv: vec2f) -> vec2f {
58
64
  let transform = mapInfo[idx + NUM_MAPS];
59
- let qz = fract(uv * transform.xy + transform.zw);
60
- let jg = 1.0 / vec2f(textureDimensions(map).xy);
61
- let dZ = nZ(idx);
62
- let kg = oa(idx) + jg;
63
- let oE = dZ - 0.5 * jg;
64
- let lr = dZ + kg - jg;
65
+ let iK = mapInfo[idx + NUM_MAPS * 2].zw;
66
+ let value = uv * transform.xy + transform.zw;
67
+ return vec2f(fS(value.x, iK.x), fS(value.y, iK.y));
68
+ }
69
+
70
+ fn sq(idx: i32, uv: vec2f) -> vec2f {
71
+ let qH = kv(idx, uv);
72
+ let jk = 1.0 / vec2f(textureDimensions(map).xy);
73
+ let dZ = jP(idx);
74
+ let kn = kJ(idx) + jk;
75
+ let oN = dZ - 0.5 * jk;
76
+ let lE = dZ + kn - jk;
65
77
  return clamp(
66
- oE + qz * kg,
78
+ oN + qH * kn,
67
79
  dZ,
68
- lr
80
+ lE
69
81
  );
70
82
  }
71
83
 
@@ -74,11 +86,32 @@ return select(uv, uv1, mapInfo[idx + NUM_MAPS * 2].x > 0.5);
74
86
  }
75
87
 
76
88
  fn sampleMap(idx: i32, uv: vec2f) -> vec4f {
77
- return textureSampleLevel(map, mapSampler, sg(idx, uv), pj(idx), 0.0);
89
+ let size = vec2i(round(kJ(idx) * vec2f(textureDimensions(map).xy) + vec2f(1.0)));
90
+ let eA = kv(idx, uv) * vec2f(size) - vec2f(0.5);
91
+ let iK = mapInfo[idx + NUM_MAPS * 2].zw;
92
+
93
+
94
+ if ((size.x > 1 && iK.x < 0.5 && (eA.x < 0.0 || eA.x > f32(size.x - 1))) ||
95
+ (size.y > 1 && iK.y < 0.5 && (eA.y < 0.0 || eA.y > f32(size.y - 1)))) {
96
+ let base = vec2i(floor(eA));
97
+ let weight = fract(eA);
98
+ let origin = vec2i(round(jP(idx) * vec2f(textureDimensions(map).xy) - vec2f(0.5)));
99
+ return mix(
100
+ mix(eU(idx, base, size, origin, iK), eU(idx, base + vec2i(1, 0), size, origin, iK), weight.x),
101
+ mix(eU(idx, base + vec2i(0, 1), size, origin, iK), eU(idx, base + vec2i(1, 1), size, origin, iK), weight.x), weight.y);
102
+ }
103
+ return textureSampleLevel(map, mapSampler, sq(idx, uv), lk(idx), 0.0);
104
+ }
105
+
106
+ fn eU(idx: i32, jm: vec2i, size: vec2i, origin: vec2i, iK: vec2f) -> vec4f {
107
+ var p = clamp(jm, vec2i(0), size - vec2i(1));
108
+ if (iK.x < 0.5) { p.x = ((jm.x % size.x) + size.x) % size.x; }
109
+ if (iK.y < 0.5) { p.y = ((jm.y % size.y) + size.y) % size.y; }
110
+ return textureLoad(map, origin + p, lk(idx), 0);
78
111
  }
79
112
 
80
113
 
81
- fn lE(materialIndex: i32, uv: vec2f, uv1: vec2f) -> vec3f {
114
+ fn lO(materialIndex: i32, uv: vec2f, uv1: vec2f) -> vec3f {
82
115
  let matData = materialsBuffer[materialIndex];
83
116
  var emission = matData.emission;
84
117
  if (USE_EMISSIVE_MAPS) {
@@ -104,24 +137,25 @@ return emission;
104
137
 
105
138
  fn aK(roughness: f32, anisotropy: f32) -> vec2f {
106
139
  let alpha = roughness * roughness;
107
- let oX = ROUGHNESS_MIN * ROUGHNESS_MIN;
108
- if (anisotropy == 0.0) { return vec2f(max(oX, alpha)); }
109
- let dg = 1.0 - anisotropy;
110
- let rZ = alpha * sqrt(2.0 / (1.0 + dg * dg));
111
- return max(vec2f(oX), vec2f(rZ, dg * rZ));
140
+ let pg = ROUGHNESS_MIN * ROUGHNESS_MIN;
141
+ if (anisotropy == 0.0) { return vec2f(max(pg, alpha)); }
142
+ let di = 1.0 - anisotropy;
143
+ let sh = alpha * sqrt(2.0 / (1.0 + di * di));
144
+ return max(vec2f(pg), vec2f(sh, di * sh));
112
145
  }
113
146
 
114
- fn kA(instanceIndex: i32) -> vec3f {
147
+ fn kI(instanceIndex: i32) -> vec3f {
115
148
  if (instanceIndex < 0) { return vec3f(1.0); }
116
149
  return instancesBuffer[instanceIndex * ${INSTANCE_VEC4_STRIDE} + 9].xyz;
117
150
  }
118
151
 
119
- fn eT(
152
+ fn eX(
120
153
  ma: ptr<function, J>,
121
154
  materialIndex: i32,
122
155
  uv: vec2f,
123
156
  uv1: vec2f,
124
- instanceIndex: i32
157
+ instanceIndex: i32,
158
+ vertexColor: vec3f
125
159
  ) {
126
160
  let matData = materialsBuffer[materialIndex];
127
161
 
@@ -130,7 +164,7 @@ let matData = materialsBuffer[materialIndex];
130
164
  (*ma).specularRoughness = matData.roughness;
131
165
  (*ma).baseMetalness = matData.metallic;
132
166
  (*ma).specularAnisotropy = clamp(matData.anisotropy, 0.0, 1.0);
133
- (*ma).emission = lE(materialIndex, uv, uv1);
167
+ (*ma).emission = lO(materialIndex, uv, uv1);
134
168
  (*ma).inside = false;
135
169
  (*ma).specularIor = matData.ior;
136
170
  (*ma).transmissionWeight = matData.transmission;
@@ -169,19 +203,19 @@ if (USE_DIFFUSE_MAPS) {
169
203
  let diffuseMapIndex = i32(matData.diffuseMapIndex);
170
204
  if (diffuseMapIndex >= 0) {
171
205
 
172
- let nQ = sampleMap(
206
+ let ob = sampleMap(
173
207
  diffuseMapIndex, getMapUvSet(diffuseMapIndex, uv, uv1)
174
208
  );
175
209
 
176
210
 
177
- (*ma).baseColor *= decodeMapColor(diffuseMapIndex, nQ.rgb);
211
+ (*ma).baseColor *= decodeMapColor(diffuseMapIndex, ob.rgb);
178
212
 
179
213
 
180
214
  if (matData.mapAlphaUsage == ${mapAlphaUsageConstants.transmission}) {
181
- (*ma).transmissionWeight = 1.0 - nQ.a;
215
+ (*ma).transmissionWeight = 1.0 - ob.a;
182
216
  }
183
217
  else if (matData.mapAlphaUsage == ${mapAlphaUsageConstants.alpha}) {
184
- (*ma).alpha *= nQ.a;
218
+ (*ma).alpha *= ob.a;
185
219
  }
186
220
  }
187
221
  }
@@ -196,11 +230,11 @@ if (USE_METALLIC_ROUGHNESS_MAPS) {
196
230
 
197
231
  let mrtMapIndex = i32(matData.mrtMapIndex);
198
232
  if (mrtMapIndex >= 0) {
199
- let fK = sampleMap(
233
+ let fN = sampleMap(
200
234
  mrtMapIndex, getMapUvSet(mrtMapIndex, uv, uv1)
201
235
  );
202
- (*ma).baseMetalness *= fK.b;
203
- (*ma).specularRoughness *= fK.g;
236
+ (*ma).baseMetalness *= fN.b;
237
+ (*ma).specularRoughness *= fN.g;
204
238
  }
205
239
  }
206
240
 
@@ -232,16 +266,16 @@ ${hasAnisotropyDirections ? /* wgsl */ `
232
266
  (*ma).coatAnisotropyRotation = matData.coatAnisotropyRotation;
233
267
  if (USE_ANISOTROPY_MAPS && matData.anisotropyMapIndex >= 0.0) {
234
268
  let index = i32(matData.anisotropyMapIndex);
235
- let iX = sampleMap(index, getMapUvSet(index, uv, uv1));
236
- (*ma).specularAnisotropy *= iX.b;
237
- let direction = iX.rg * 2.0 - 1.0;
269
+ let eA = sampleMap(index, getMapUvSet(index, uv, uv1));
270
+ (*ma).specularAnisotropy *= eA.b;
271
+ let direction = eA.rg * 2.0 - 1.0;
238
272
  if (dot(direction, direction) > 1e-4) { (*ma).anisotropyDirection = normalize(direction); }
239
273
  (*ma).anisotropyDirectionEnabled = true;
240
274
  }
241
275
  if (matData.specularAnisotropyModel > 0.5 && (*ma).specularAnisotropy > 0.0) {
242
- let sv = fb(clamp((*ma).specularRoughness, 0.0, 1.0), (*ma).specularAnisotropy);
243
- (*ma).specularRoughness = sv.x;
244
- (*ma).specularAnisotropy = sv.y;
276
+ let sF = fe(clamp((*ma).specularRoughness, 0.0, 1.0), (*ma).specularAnisotropy);
277
+ (*ma).specularRoughness = sF.x;
278
+ (*ma).specularAnisotropy = sF.y;
245
279
  }
246
280
  if ((*ma).anisotropyDirectionEnabled) {
247
281
  let theta = matData.specularAnisotropyRotation;
@@ -253,14 +287,14 @@ let d = (*ma).anisotropyDirection;
253
287
 
254
288
 
255
289
  if ((*ma).coatWeight > 0.0 && (*ma).coatRoughness > 0.0) {
256
- let hI = (*ma).specularRoughness * (*ma).specularRoughness;
257
- let hJ = (*ma).coatRoughness * (*ma).coatRoughness;
290
+ let hM = (*ma).specularRoughness * (*ma).specularRoughness;
291
+ let hN = (*ma).coatRoughness * (*ma).coatRoughness;
258
292
  (*ma).specularRoughness = mix((*ma).specularRoughness,
259
- pow(min(1.0, hI * hI + 2.0 * hJ * hJ), 0.25), (*ma).coatWeight);
293
+ pow(min(1.0, hM * hM + 2.0 * hN * hN), 0.25), (*ma).coatWeight);
260
294
  }
261
- let instanceColor = kA(instanceIndex);
262
- (*ma).baseColor *= instanceColor;
263
- if ((*ma).materialType == UNLIT) { (*ma).emission *= instanceColor; }
295
+ let color = kI(instanceIndex) * vertexColor;
296
+ (*ma).baseColor *= color;
297
+ if ((*ma).materialType == UNLIT) { (*ma).emission *= color; }
264
298
 
265
299
  (*ma).specularRoughness = clamp((*ma).specularRoughness, ROUGHNESS_MIN, 1.0);
266
300
  (*ma).baseMetalness = clamp((*ma).baseMetalness, 0.0, 1.0);
@@ -269,39 +303,39 @@ let specularAlpha = aK((*ma).specularRoughness, (*ma).specularAnisotropy);
269
303
  (*ma).alphaY = specularAlpha.y;
270
304
  }
271
305
 
272
- fn ob(materialIndex: i32, uv: vec2f, normal: vec3f, dp1: vec3f, dp2: vec3f, nS: vec2f, nT: vec2f) -> vec3f {
306
+ fn om(materialIndex: i32, uv: vec2f, normal: vec3f, dp1: vec3f, dp2: vec3f, od: vec2f, oe: vec2f) -> vec3f {
273
307
  let matData = materialsBuffer[materialIndex];
274
308
  let normalMapIndex = i32(matData.normalMapIndex);
275
309
  if (normalMapIndex >= 0) {
276
310
 
277
311
 
278
312
 
279
- let qO = cross(dp2, normal);
280
- let qM = cross(normal, dp1);
281
- var dpdu = qO * nS.x + qM * nT.x;
282
- var se = qO * nS.y + qM * nT.y;
283
- let dS = max(dot(dpdu, dpdu), dot(se, se));
284
- if (dS > 1e-12) {
285
- let sn = inverseSqrt(dS);
286
- dpdu *= sn;
287
- se *= sn;
313
+ let qW = cross(dp2, normal);
314
+ let qV = cross(normal, dp1);
315
+ var dpdu = qW * od.x + qV * oe.x;
316
+ var sn = qW * od.y + qV * oe.y;
317
+ let dT = max(dot(dpdu, dpdu), dot(sn, sn));
318
+ if (dT > 1e-12) {
319
+ let sz = inverseSqrt(dT);
320
+ dpdu *= sz;
321
+ sn *= sz;
288
322
  }
289
323
  else {
290
324
 
291
325
 
292
- let nf = select(
326
+ let nt = select(
293
327
  vec3f(0.0, 1.0, 0.0),
294
328
  vec3f(0.0, 0.0, 1.0),
295
329
  abs(normal.z) < 0.999
296
330
  );
297
- dpdu = normalize(cross(nf, normal));
298
- se = cross(normal, dpdu);
331
+ dpdu = normalize(cross(nt, normal));
332
+ sn = cross(normal, dpdu);
299
333
  }
300
334
 
301
335
  var n = 2.0 * sampleMap(normalMapIndex, uv).rgb - 1.0;
302
336
  n = normalize(vec3(n.xy * matData.normalMapScale, n.z));
303
337
 
304
- let tbn = mat3x3(dpdu, se, normal);
338
+ let tbn = mat3x3(dpdu, sn, normal);
305
339
 
306
340
  return normalize(tbn * n);
307
341
  } else {