@forgeax/engine-render 0.1.23 → 0.1.25

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 (322) hide show
  1. package/README.md +231 -10
  2. package/dist/assembly/bundler-contract.d.ts +9 -0
  3. package/dist/assembly/bundler-contract.d.ts.map +1 -0
  4. package/dist/assembly/device-feature-admission.d.ts +8 -0
  5. package/dist/assembly/device-feature-admission.d.ts.map +1 -1
  6. package/dist/assembly/extended-lighting-fallback.d.ts +13 -0
  7. package/dist/assembly/extended-lighting-fallback.d.ts.map +1 -0
  8. package/dist/assembly/factory.d.ts +7 -243
  9. package/dist/assembly/factory.d.ts.map +1 -1
  10. package/dist/assembly/material/assembly.d.ts +3 -2
  11. package/dist/assembly/material/assembly.d.ts.map +1 -1
  12. package/dist/assembly/material/standard-layer-projection.d.ts +10 -0
  13. package/dist/assembly/material/standard-layer-projection.d.ts.map +1 -0
  14. package/dist/assembly/material/surface-projection.d.ts +24 -0
  15. package/dist/assembly/material/surface-projection.d.ts.map +1 -0
  16. package/dist/assembly/material-shader-policy.d.ts +188 -0
  17. package/dist/assembly/material-shader-policy.d.ts.map +1 -0
  18. package/dist/assembly/material-shader-prewarm.d.ts +21 -0
  19. package/dist/assembly/material-shader-prewarm.d.ts.map +1 -0
  20. package/dist/assembly/mesh-ssbo-grow.d.ts +88 -0
  21. package/dist/assembly/mesh-ssbo-grow.d.ts.map +1 -0
  22. package/dist/assembly/recovery/device-loss-fanout.d.ts +14 -0
  23. package/dist/assembly/recovery/device-loss-fanout.d.ts.map +1 -0
  24. package/dist/assembly/recovery/generation.d.ts +46 -0
  25. package/dist/assembly/recovery/generation.d.ts.map +1 -0
  26. package/dist/assembly/recovery/recovery-attempt.d.ts +63 -0
  27. package/dist/assembly/recovery/recovery-attempt.d.ts.map +1 -0
  28. package/dist/assembly/recovery/renderer-recover.d.ts +115 -0
  29. package/dist/assembly/recovery/renderer-recover.d.ts.map +1 -0
  30. package/dist/assembly/renderer-facade.d.ts +10 -0
  31. package/dist/assembly/renderer-facade.d.ts.map +1 -0
  32. package/dist/assembly/renderer-frame-transaction.d.ts +17 -0
  33. package/dist/assembly/renderer-frame-transaction.d.ts.map +1 -1
  34. package/dist/assembly/renderer-helpers.d.ts +19 -0
  35. package/dist/assembly/renderer-helpers.d.ts.map +1 -0
  36. package/dist/assembly/renderer-lifecycle.d.ts +34 -0
  37. package/dist/assembly/renderer-lifecycle.d.ts.map +1 -1
  38. package/dist/assembly/shader-prewarm-policy.d.ts +15 -0
  39. package/dist/assembly/shader-prewarm-policy.d.ts.map +1 -0
  40. package/dist/assembly/webgpu-ready-contract.d.ts +9 -0
  41. package/dist/assembly/webgpu-ready-contract.d.ts.map +1 -0
  42. package/dist/assembly/webgpu-ready.d.ts +55 -0
  43. package/dist/assembly/webgpu-ready.d.ts.map +1 -0
  44. package/dist/assembly/webgpu-renderer.d.ts +152 -0
  45. package/dist/assembly/webgpu-renderer.d.ts.map +1 -0
  46. package/dist/assembly/webgpu-vertex-layouts.d.ts +89 -0
  47. package/dist/assembly/webgpu-vertex-layouts.d.ts.map +1 -0
  48. package/dist/assets/asset-decoders.d.ts.map +1 -1
  49. package/dist/authoring.mjs +1 -1
  50. package/dist/{chunk-FF7K3FSO.mjs → chunk-ADHHVYLW.mjs} +89 -89
  51. package/dist/chunk-ADHHVYLW.mjs.map +1 -0
  52. package/dist/{chunk-YALPS44A.mjs → chunk-E34VL5VI.mjs} +47 -47
  53. package/dist/chunk-E34VL5VI.mjs.map +1 -0
  54. package/dist/{chunk-4L3P6DT5.mjs → chunk-GE3SDD72.mjs} +37 -5
  55. package/dist/chunk-GE3SDD72.mjs.map +1 -0
  56. package/dist/{chunk-HZUPSYW7.mjs → chunk-HKXTW355.mjs} +105 -15
  57. package/dist/chunk-HKXTW355.mjs.map +1 -0
  58. package/dist/chunk-JSEDTZYS.mjs +413 -0
  59. package/dist/chunk-JSEDTZYS.mjs.map +1 -0
  60. package/dist/{chunk-FBC4R44J.mjs → chunk-KM2NOX2I.mjs} +57 -57
  61. package/dist/chunk-KM2NOX2I.mjs.map +1 -0
  62. package/dist/{chunk-DIYW3M5D.mjs → chunk-N3RT2EUR.mjs} +2288 -2238
  63. package/dist/chunk-N3RT2EUR.mjs.map +1 -0
  64. package/dist/{chunk-ZUWKAZ2I.mjs → chunk-TCLDX7SZ.mjs} +1167 -368
  65. package/dist/chunk-TCLDX7SZ.mjs.map +1 -0
  66. package/dist/{chunk-QODA5UKU.mjs → chunk-ZZ4YQ474.mjs} +2 -2
  67. package/dist/chunk-ZZ4YQ474.mjs.map +1 -0
  68. package/dist/components/instances.d.ts +13 -31
  69. package/dist/components/instances.d.ts.map +1 -1
  70. package/dist/construct-renderer.mjs +20688 -16314
  71. package/dist/construct-renderer.mjs.map +1 -1
  72. package/dist/device/device-scope.d.ts +2 -0
  73. package/dist/device/device-scope.d.ts.map +1 -1
  74. package/dist/device/gpu-residency.d.ts +24 -4
  75. package/dist/device/gpu-residency.d.ts.map +1 -1
  76. package/dist/environment/frame.d.ts +5 -0
  77. package/dist/environment/frame.d.ts.map +1 -1
  78. package/dist/environment/inspection.d.ts +7 -0
  79. package/dist/environment/inspection.d.ts.map +1 -1
  80. package/dist/environment/lifecycle.d.ts +20 -2
  81. package/dist/environment/lifecycle.d.ts.map +1 -1
  82. package/dist/errors/recover.d.ts +58 -2
  83. package/dist/errors/recover.d.ts.map +1 -1
  84. package/dist/errors/render.d.ts +13 -0
  85. package/dist/errors/render.d.ts.map +1 -1
  86. package/dist/extract/contracts.d.ts +899 -0
  87. package/dist/extract/contracts.d.ts.map +1 -0
  88. package/dist/features/host.d.ts +2 -0
  89. package/dist/features/host.d.ts.map +1 -1
  90. package/dist/features/prepared-gpu-work.d.ts +3 -0
  91. package/dist/features/prepared-gpu-work.d.ts.map +1 -1
  92. package/dist/features/prepared-graphics-store.d.ts +2 -0
  93. package/dist/features/prepared-graphics-store.d.ts.map +1 -1
  94. package/dist/gpu-driven/production-raster.d.ts +34 -1
  95. package/dist/gpu-driven/production-raster.d.ts.map +1 -1
  96. package/dist/gpu-scene.d.ts +5 -0
  97. package/dist/gpu-scene.d.ts.map +1 -1
  98. package/dist/ibl/IblPipelineCache.d.ts +7 -6
  99. package/dist/ibl/IblPipelineCache.d.ts.map +1 -1
  100. package/dist/ibl/skylight-bind-group.d.ts +7 -1
  101. package/dist/ibl/skylight-bind-group.d.ts.map +1 -1
  102. package/dist/index.d.ts +2 -1
  103. package/dist/index.d.ts.map +1 -1
  104. package/dist/index.mjs +27 -9
  105. package/dist/index.mjs.map +1 -1
  106. package/dist/instance-buffer-cache.d.ts +7 -1
  107. package/dist/instance-buffer-cache.d.ts.map +1 -1
  108. package/dist/instances.d.ts +101 -0
  109. package/dist/instances.d.ts.map +1 -0
  110. package/dist/internal.mjs +6 -6
  111. package/dist/light-buffer-layout.d.ts +3 -0
  112. package/dist/light-buffer-layout.d.ts.map +1 -1
  113. package/dist/materials.d.ts +96 -11
  114. package/dist/materials.d.ts.map +1 -1
  115. package/dist/mesh-material-bindings.d.ts +56 -0
  116. package/dist/mesh-material-bindings.d.ts.map +1 -1
  117. package/dist/occlusion-proxy.d.ts +4 -0
  118. package/dist/occlusion-proxy.d.ts.map +1 -0
  119. package/dist/pbr-pipeline.d.ts +32 -9
  120. package/dist/pbr-pipeline.d.ts.map +1 -1
  121. package/dist/pipeline-builder.d.ts.map +1 -1
  122. package/dist/pipeline-spec.d.ts.map +1 -1
  123. package/dist/points-lines/prepare.d.ts +3 -0
  124. package/dist/points-lines/prepare.d.ts.map +1 -1
  125. package/dist/points-lines/standard-owner.d.ts +33 -0
  126. package/dist/points-lines/standard-owner.d.ts.map +1 -0
  127. package/dist/record/frame-snapshot.d.ts +18 -2
  128. package/dist/record/frame-snapshot.d.ts.map +1 -1
  129. package/dist/record/frame-targets.d.ts.map +1 -1
  130. package/dist/record/frame.d.ts +14 -2
  131. package/dist/record/frame.d.ts.map +1 -1
  132. package/dist/record/main-pass-geometry.d.ts +36 -4
  133. package/dist/record/main-pass-geometry.d.ts.map +1 -1
  134. package/dist/record/main-pass-material.d.ts +7 -17
  135. package/dist/record/main-pass-material.d.ts.map +1 -1
  136. package/dist/record/main-pass.d.ts.map +1 -1
  137. package/dist/record/mesh-ssbo.d.ts +11 -3
  138. package/dist/record/mesh-ssbo.d.ts.map +1 -1
  139. package/dist/record/recovery-pipeline.d.ts +30 -0
  140. package/dist/record/recovery-pipeline.d.ts.map +1 -0
  141. package/dist/record/render-context.d.ts +28 -5
  142. package/dist/record/render-context.d.ts.map +1 -1
  143. package/dist/record/shadow-pass.d.ts +11 -1
  144. package/dist/record/shadow-pass.d.ts.map +1 -1
  145. package/dist/record/typed-frame-graph.d.ts +1 -1
  146. package/dist/record/typed-frame-graph.d.ts.map +1 -1
  147. package/dist/recovery/render-system-candidate.d.ts +65 -0
  148. package/dist/recovery/render-system-candidate.d.ts.map +1 -0
  149. package/dist/render-contract.d.ts +68 -7
  150. package/dist/render-contract.d.ts.map +1 -1
  151. package/dist/render-system-extract-tail.d.ts +16 -0
  152. package/dist/render-system-extract-tail.d.ts.map +1 -0
  153. package/dist/render-system-extract.d.ts +180 -22
  154. package/dist/render-system-extract.d.ts.map +1 -1
  155. package/dist/render-system.d.ts +131 -4
  156. package/dist/render-system.d.ts.map +1 -1
  157. package/dist/scene/render-scene-types.d.ts +2 -0
  158. package/dist/scene/render-scene-types.d.ts.map +1 -1
  159. package/dist/scene/render-scene.d.ts +31 -1
  160. package/dist/scene/render-scene.d.ts.map +1 -1
  161. package/dist/systems/skin-palette-allocator.d.ts +6 -0
  162. package/dist/systems/skin-palette-allocator.d.ts.map +1 -1
  163. package/dist/temporal/frame-coordinator.d.ts.map +1 -1
  164. package/dist/temporal/index.mjs +6 -6
  165. package/dist/temporal/taa-history-store.d.ts +15 -1
  166. package/dist/temporal/taa-history-store.d.ts.map +1 -1
  167. package/dist/temporal/transaction.d.ts +5 -0
  168. package/dist/temporal/transaction.d.ts.map +1 -1
  169. package/package.json +20 -20
  170. package/src/__tests__/device-feature-admission.unit.test.ts +6 -0
  171. package/src/__tests__/device-loss-fanout.unit.test.ts +107 -0
  172. package/src/__tests__/device-scope-lifecycle.unit.test.ts +21 -0
  173. package/src/__tests__/environment-generation.integration.test.ts +23 -0
  174. package/src/__tests__/expose-renderer-errors.unit.test.ts +34 -0
  175. package/src/__tests__/factory-contract.integration.test.ts +122 -7
  176. package/src/__tests__/fallback-row-stride-surface.unit.test.ts +4 -1
  177. package/src/__tests__/gpu-scene.unit.test.ts +38 -2
  178. package/src/__tests__/ibl/face-uniforms.unit.test.ts +2 -0
  179. package/src/__tests__/ibl-residency-publication.unit.test.ts +113 -0
  180. package/src/__tests__/instances-store.unit.test.ts +46 -0
  181. package/src/__tests__/instances-world-ownership.integration.test.ts +62 -0
  182. package/src/__tests__/material-contract-inventory.unit.test.ts +29 -22
  183. package/src/__tests__/material-cooked-projection.unit.test.ts +19 -1
  184. package/src/__tests__/material-pass-policy.unit.test.ts +69 -0
  185. package/src/__tests__/material-snapshot-mutation.integration.test.ts +69 -0
  186. package/src/__tests__/material-static-dynamic-route.unit.test.ts +29 -0
  187. package/src/__tests__/material-stride-surface.unit.test.ts +7 -3
  188. package/src/__tests__/material-surface-authoring.test-d.ts +62 -0
  189. package/src/__tests__/materials-standard-contract.unit.test.ts +104 -0
  190. package/src/__tests__/materials-transmission-validation.unit.test.ts +24 -0
  191. package/src/__tests__/mesh-buffer-usage-surface.unit.test.ts +1 -5
  192. package/src/__tests__/physical-clearcoat.integration.test.ts +89 -0
  193. package/src/__tests__/prepared-gpu-driven-pbr.integration.test.ts +1 -1
  194. package/src/__tests__/prepared-graphics-pipeline-warmup.unit.test.ts +1 -1
  195. package/src/__tests__/recover-error-policy-owner.unit.test.ts +6 -3
  196. package/src/__tests__/recovery-candidate-prepare.contract.test.ts +140 -0
  197. package/src/__tests__/renderer-factory-material-contract.unit.test.ts +127 -18
  198. package/src/__tests__/renderer-frame-generation-fence.integration.test.ts +47 -0
  199. package/src/__tests__/renderer-generation-assembly.unit.test.ts +77 -0
  200. package/src/__tests__/renderer-generation-failure.unit.test.ts +108 -0
  201. package/src/__tests__/renderer-producer-root-matrix.integration.test.ts +33 -0
  202. package/src/__tests__/renderer-recovery-capability-matrix.unit.test.ts +59 -0
  203. package/src/__tests__/renderer-recovery-contract.unit.test.ts +73 -0
  204. package/src/__tests__/renderer-recovery-lifecycle.unit.test.ts +216 -0
  205. package/src/__tests__/renderer-target-history-recovery.integration.test.ts +80 -0
  206. package/src/__tests__/shader-manifest-fixture.ts +13 -16
  207. package/src/__tests__/shader-stage-surface.unit.test.ts +1 -1
  208. package/src/__tests__/skin-motion-regression.unit.test.ts +33 -0
  209. package/src/__tests__/skinned-shadow-caster.test.ts +30 -2
  210. package/src/__tests__/standard-layer-projection.unit.test.ts +23 -0
  211. package/src/__tests__/surface-adjacent-regressions.integration.test.ts +60 -0
  212. package/src/__tests__/surface-standard-pipeline.browser.test.ts +20 -0
  213. package/src/__tests__/surface-standard-pipeline.dawn.test.ts +20 -0
  214. package/src/__tests__/surface-standard-pipeline.fixture.ts +132 -0
  215. package/src/__tests__/surface-variant-provenance.integration.test.ts +33 -0
  216. package/src/__tests__/transmission-prewarm.unit.test.ts +95 -85
  217. package/src/__tests__/volumetric-fog-budget-recovery.integration.test.ts +1 -1
  218. package/src/__tests__/volumetric-fog-topology.unit.test.ts +3 -1
  219. package/src/assembly/bundler-contract.ts +9 -0
  220. package/src/assembly/device-feature-admission.ts +15 -5
  221. package/src/assembly/extended-lighting-fallback.ts +216 -0
  222. package/src/assembly/factory.ts +83 -10088
  223. package/src/assembly/material/__tests__/standard-layer-plan-projection.integration.test.ts +18 -0
  224. package/src/assembly/material/__tests__/surface-pass-projection.integration.test.ts +62 -0
  225. package/src/assembly/material/assembly.ts +18 -2
  226. package/src/assembly/material/standard-layer-projection.ts +16 -0
  227. package/src/assembly/material/surface-projection.ts +90 -0
  228. package/src/assembly/material-shader-policy.ts +932 -0
  229. package/src/assembly/material-shader-prewarm.ts +88 -0
  230. package/src/assembly/mesh-ssbo-grow.ts +270 -0
  231. package/src/assembly/recovery/device-loss-fanout.ts +89 -0
  232. package/src/assembly/recovery/generation.ts +141 -0
  233. package/src/assembly/recovery/recovery-attempt.ts +800 -0
  234. package/src/assembly/recovery/renderer-recover.ts +906 -0
  235. package/src/assembly/renderer-facade.ts +342 -0
  236. package/src/assembly/renderer-frame-transaction.ts +45 -0
  237. package/src/assembly/renderer-helpers.ts +99 -0
  238. package/src/assembly/renderer-lifecycle.ts +159 -0
  239. package/src/assembly/shader-prewarm-policy.ts +157 -0
  240. package/src/assembly/webgpu-ready-contract.ts +14 -0
  241. package/src/assembly/webgpu-ready.ts +4066 -0
  242. package/src/assembly/webgpu-renderer.ts +4027 -0
  243. package/src/assembly/webgpu-vertex-layouts.ts +132 -0
  244. package/src/assets/asset-decoders.ts +27 -4
  245. package/src/components/camera.ts +1 -1
  246. package/src/components/instances.ts +14 -197
  247. package/src/debug-draw-glue.ts +1 -1
  248. package/src/device/device-scope.ts +35 -6
  249. package/src/device/gpu-residency.ts +194 -26
  250. package/src/environment/frame.ts +6 -0
  251. package/src/environment/inspection.ts +10 -0
  252. package/src/environment/lifecycle.ts +114 -8
  253. package/src/errors/recover.ts +76 -5
  254. package/src/errors/render.ts +17 -0
  255. package/src/extract/contracts.ts +974 -0
  256. package/src/features/host.ts +16 -0
  257. package/src/features/prepared-gpu-work.ts +20 -2
  258. package/src/features/prepared-graphics-store.ts +17 -0
  259. package/src/gpu-driven/production-raster.ts +131 -3
  260. package/src/gpu-scene.ts +87 -31
  261. package/src/ibl/IblPipelineCache.ts +134 -50
  262. package/src/ibl/skylight-bind-group.ts +26 -0
  263. package/src/index.ts +10 -1
  264. package/src/instance-buffer-cache.ts +14 -1
  265. package/src/instances.ts +288 -0
  266. package/src/light-buffer-layout.ts +3 -0
  267. package/src/materials.ts +365 -53
  268. package/src/mesh-material-bindings.ts +59 -0
  269. package/src/occlusion-proxy.ts +64 -0
  270. package/src/pbr-pipeline.ts +139 -15
  271. package/src/pipeline-builder.ts +14 -10
  272. package/src/pipeline-spec.ts +6 -14
  273. package/src/points-lines/__tests__/cache-recovery.unit.test.ts +22 -0
  274. package/src/points-lines/prepare.ts +9 -0
  275. package/src/points-lines/standard-owner.ts +403 -0
  276. package/src/record/__tests__/frame-targets-recovery.unit.test.ts +60 -0
  277. package/src/record/__tests__/main-pass-geometry.unit.test.ts +26 -1
  278. package/src/record/__tests__/mesh-ssbo.unit.test.ts +38 -0
  279. package/src/record/__tests__/recovery-pipeline.unit.test.ts +124 -0
  280. package/src/record/__tests__/sprite-material-abi.unit.test.ts +28 -13
  281. package/src/record/__tests__/standard-pbr-ubo-layout.unit.test.ts +47 -4
  282. package/src/record/__tests__/temporal-payload.unit.test.ts +22 -43
  283. package/src/record/__tests__/typed-frame-graph-plan.unit.test.ts +1 -1
  284. package/src/record/frame-snapshot.ts +34 -2
  285. package/src/record/frame-targets.ts +11 -1
  286. package/src/record/frame.ts +37 -4
  287. package/src/record/helpers.ts +1 -1
  288. package/src/record/main-pass-geometry.ts +727 -63
  289. package/src/record/main-pass-material.ts +316 -209
  290. package/src/record/main-pass-sprite-draws.ts +7 -8
  291. package/src/record/main-pass.ts +94 -6
  292. package/src/record/mesh-ssbo.ts +32 -10
  293. package/src/record/recovery-pipeline.ts +695 -0
  294. package/src/record/render-context.ts +39 -4
  295. package/src/record/shadow-pass.ts +46 -99
  296. package/src/record/typed-frame-graph.ts +45 -1
  297. package/src/record/view-ubo.ts +3 -3
  298. package/src/recovery/render-system-candidate.ts +876 -0
  299. package/src/render-contract.ts +69 -7
  300. package/src/render-system-extract-tail.ts +3039 -0
  301. package/src/render-system-extract.ts +177 -3003
  302. package/src/render-system.ts +906 -662
  303. package/src/scene/render-scene-types.ts +2 -0
  304. package/src/scene/render-scene.ts +703 -37
  305. package/src/systems/skin-palette-allocator.ts +129 -7
  306. package/src/temporal/frame-coordinator.ts +4 -1
  307. package/src/temporal/taa-history-store.ts +46 -2
  308. package/src/temporal/temporal-view.ts +1 -1
  309. package/src/temporal/transaction.ts +9 -0
  310. package/src/tilemap-chunk-extract-system.ts +1 -1
  311. package/src/transmission/__tests__/standard-transmission.dawn.test.ts +19 -12
  312. package/src/transmission/__tests__/surface-compatibility.integration.test.ts +32 -0
  313. package/dist/chunk-4L3P6DT5.mjs.map +0 -1
  314. package/dist/chunk-4NPUASZR.mjs +0 -203
  315. package/dist/chunk-4NPUASZR.mjs.map +0 -1
  316. package/dist/chunk-DIYW3M5D.mjs.map +0 -1
  317. package/dist/chunk-FBC4R44J.mjs.map +0 -1
  318. package/dist/chunk-FF7K3FSO.mjs.map +0 -1
  319. package/dist/chunk-HZUPSYW7.mjs.map +0 -1
  320. package/dist/chunk-QODA5UKU.mjs.map +0 -1
  321. package/dist/chunk-YALPS44A.mjs.map +0 -1
  322. package/dist/chunk-ZUWKAZ2I.mjs.map +0 -1
@@ -1,17 +1,18 @@
1
1
  import { GlyphText, selectActiveCameraIndex, getActiveCamera } from './chunk-FJ6P52EE.mjs';
2
- import { MeshRenderer, MeshFilter, DirectionalLight, validateDirectionalLightData, PointLight, computeInvRangeSquared, SpotLight, degToCos, RectAreaLight, validateRectAreaLightData, directionalShadowQualityFromF32, PointLightShadow, Skylight, LightProbe, validateLightProbeData, SkyboxBackground, Atmosphere, Instances, Layer, Points, Lines, SortKey, resolveMeshMaterialBindings, pointShapeFromU32, PostProcessParams, Fog, resolveVisibility, VolumetricFog, resolveSelectedVolumetricLight, extractVolumetricFog, CubeCamera, cubeCameraUpdateIntentFromF32, ReflectionProbe, reflectionProbeUpdateIntentFromF32, tonemapToU32, MotionBlur, Camera, bloomEnabledFromF32, antialiasFromF32, tonemapFromF32, cameraProjectionFromF32 } from './chunk-FF7K3FSO.mjs';
2
+ import { MeshRenderer, MeshFilter, DirectionalLight, validateDirectionalLightData, PointLight, computeInvRangeSquared, SpotLight, degToCos, RectAreaLight, validateRectAreaLightData, directionalShadowQualityFromF32, PointLightShadow, Skylight, LightProbe, validateLightProbeData, SkyboxBackground, Atmosphere, Instances, Layer, Points, Lines, SortKey, resolveMeshMaterialBindings, pointShapeFromU32, PostProcessParams, CubeCamera, cubeCameraUpdateIntentFromF32, ReflectionProbe, reflectionProbeUpdateIntentFromF32, tonemapToU32, resolveVisibility, Fog, VolumetricFog, resolveSelectedVolumetricLight, extractVolumetricFog, MotionBlur, Camera, bloomEnabledFromF32, antialiasFromF32, tonemapFromF32, cameraProjectionFromF32 } from './chunk-ADHHVYLW.mjs';
3
3
  import { SpriteRegionOverride, SpriteInstances } from './chunk-JDWARUOI.mjs';
4
- import { PROBE_BLEND_RECORD_CAPACITY, GPU_BUFFER_USAGE_MAP_READ, GPU_BUFFER_USAGE_VERTEX, GPU_BUFFER_USAGE_COPY_DST, IES_SLICE_CAPACITY, COOKIE_SLICE_CAPACITY, PROBE_BLEND_RECORD_BYTE_SIZE, GPU_BUFFER_USAGE_QUERY_RESOLVE, GPU_BUFFER_USAGE_COPY_SRC, COOKIE_SLICE_SIZE, selectPasses, isStandardPbrMaterialShader, resolveRenderTargetMaterialSource } from './chunk-HZUPSYW7.mjs';
5
- import { STANDARD_OUTPUT_TRANSFORM_FEATURE_ID, SHADOW_ATLAS_DEFAULT_LAYERS, SkinMaterialMismatchError, MaterialSkinAttrMissingError, ShadowInvalidConfigError, EnvironmentSourceConflictError, FogCardinalityError, AtmosphereInvalidParameterError } from './chunk-QODA5UKU.mjs';
4
+ import { PROBE_BLEND_RECORD_CAPACITY, GPU_BUFFER_USAGE_MAP_READ, GPU_BUFFER_USAGE_VERTEX, GPU_BUFFER_USAGE_COPY_DST, IES_SLICE_CAPACITY, COOKIE_SLICE_CAPACITY, isStandardPbrSkinMaterialShader, selectPasses, resolveRenderTargetMaterialSource, isCanonicalStandardPbrMaterialShader, isStandardPbrMaterialShader, PROBE_BLEND_RECORD_BYTE_SIZE, GPU_BUFFER_USAGE_QUERY_RESOLVE, GPU_BUFFER_USAGE_COPY_SRC, COOKIE_SLICE_SIZE } from './chunk-HKXTW355.mjs';
5
+ import { STANDARD_OUTPUT_TRANSFORM_FEATURE_ID, SHADOW_ATLAS_DEFAULT_LAYERS, SkinMaterialMismatchError, MaterialSkinAttrMissingError, ShadowInvalidConfigError, EnvironmentSourceConflictError, FogCardinalityError, AtmosphereInvalidParameterError } from './chunk-ZZ4YQ474.mjs';
6
6
  import { err, ok, RhiError } from '@forgeax/engine-rhi';
7
7
  import { resolveAssetHandle, walkMaterialPassesOverSharedRefs } from '@forgeax/engine-assets-runtime';
8
8
  import { SpawnLightInvalidBoundsError, routeWorldError, SpriteInstancesMutuallyExclusiveWithInstancesError, SpriteInstancesRequiresSpriteShaderError, SpriteInstancesCountMismatchError, InstanceTransformsStrideMismatchError, readRenderArrayView } from '@forgeax/engine-ecs/projection';
9
- import { PROCEDURAL_FLOATS_PER_VERTEX, deriveVertexLayoutProjection } from '@forgeax/engine-geometry';
10
9
  import { vec3, mat4, frustum, box3 } from '@forgeax/engine-math';
11
10
  import { AssetGuid } from '@forgeax/engine-pack/guid';
12
11
  import { Transform, GlobalTransform, MorphWeights } from '@forgeax/engine-scene';
13
- import { Skin, SkinInstancesCoexistForbiddenError, SkeletonResolveFailedError, JointCountMismatchError, JointEntityDanglingError } from '@forgeax/engine-skinning';
12
+ import { STANDARD_PIPELINE_PARAM_SCHEMA } from '@forgeax/engine-shader';
14
13
  import { R_MIN, err as err$1, ok as ok$1, TextError, toShared, handleSlot, AssetError, ASSET_ERROR_HINTS, materialValuesToLinearRuntime, derive, unpackSlot, materialGuidText } from '@forgeax/engine-types';
14
+ import { PROCEDURAL_FLOATS_PER_VERTEX, deriveVertexLayoutProjection } from '@forgeax/engine-geometry';
15
+ import { Skin, SkinInstancesCoexistForbiddenError, SkeletonResolveFailedError, JointCountMismatchError, JointEntityDanglingError } from '@forgeax/engine-skinning';
15
16
  import { resetFontConcurrency, trackFontConcurrency, layoutGlyphText, conservativeCubeAabb, bakeGlyphMesh } from '@forgeax/engine-graphics-extras';
16
17
 
17
18
  // src/inspection-types.ts
@@ -869,6 +870,10 @@ function validateAtmosphereParameters(parameters) {
869
870
  }
870
871
 
871
872
  // src/environment/frame.ts
873
+ var SKYLIGHT_RECOVERY_FALLBACK = Object.freeze({
874
+ kind: "skylight",
875
+ active: false
876
+ });
872
877
  var EnvironmentSelectionParameterError = class extends Error {
873
878
  code = "environment-selection-invalid";
874
879
  expected;
@@ -1598,2243 +1603,1207 @@ function createCookieProjectionMatrixData(count) {
1598
1603
  return data;
1599
1604
  }
1600
1605
 
1601
- // src/render-system-extract.ts
1602
- function buildRectAreaWorldFrame(input) {
1603
- const center = vec3.create(input.center[0] ?? 0, input.center[1] ?? 0, input.center[2] ?? 0);
1604
- const axisX = vec3.normalize(
1605
- vec3.create(),
1606
- vec3.create(input.axisX[0] ?? 0, input.axisX[1] ?? 0, input.axisX[2] ?? 0)
1607
- );
1608
- const axisY = vec3.normalize(
1609
- vec3.create(),
1610
- vec3.create(input.axisY[0] ?? 0, input.axisY[1] ?? 0, input.axisY[2] ?? 0)
1611
- );
1612
- const normal = vec3.normalize(vec3.create(), vec3.cross(vec3.create(), axisX, axisY));
1613
- normal[0] = normal[0] === 0 ? 0 : normal[0] ?? 0;
1614
- normal[1] = normal[1] === 0 ? 0 : normal[1] ?? 0;
1615
- normal[2] = normal[2] === 0 ? 0 : normal[2] ?? 0;
1616
- return {
1617
- center,
1618
- axisX,
1619
- axisY,
1620
- normal,
1621
- halfWidth: Math.max(0, input.width * 0.5),
1622
- halfHeight: Math.max(0, input.height * 0.5)
1623
- };
1624
- }
1625
- function rectAreaFacesPoint(frame, point) {
1626
- const toPoint = vec3.create(
1627
- (point[0] ?? 0) - (frame.center[0] ?? 0),
1628
- (point[1] ?? 0) - (frame.center[1] ?? 0),
1629
- (point[2] ?? 0) - (frame.center[2] ?? 0)
1630
- );
1631
- return vec3.dot(frame.normal, toPoint) > 0;
1632
- }
1633
- function sortDispatchByQueue(entries) {
1634
- return entries.slice().sort((a, b) => a.queue - b.queue);
1635
- }
1636
- var DEFAULT_FORWARD_PASS = {
1637
- name: "forward",
1638
- program: { module: "forgeax::default-unlit", vertexEntry: "vs_main", fragmentEntry: "fs_main" },
1639
- renderState: { tags: { LightMode: "Forward" }, queue: 2e3 }
1640
- };
1641
- function appendMaterialDispatchEntries(pendingDispatch, passes, entity, materialHandle, renderableIndex, layer, paramSnapshot, passIndexOffset = 0) {
1642
- const matchedPasses = selectPasses(passes, {});
1643
- for (let pIdx = 0; pIdx < matchedPasses.length; pIdx++) {
1644
- const pass = matchedPasses[pIdx];
1645
- if (pass === void 0) continue;
1646
- const passState = pass.renderState ?? {};
1647
- pendingDispatch.push({
1648
- entityIndex: entity,
1649
- materialHandle,
1650
- renderableIndex,
1651
- passIndex: passIndexOffset + pIdx,
1652
- queue: passState.queue ?? 2e3,
1653
- layer,
1654
- tags: passState.tags ?? {},
1655
- renderState: pipelineRenderState(passState),
1656
- // Material module identity is already closed in the cooked program.
1657
- // It must never become a draw-time define map.
1658
- defines: void 0,
1659
- vertexEntry: pass.program.vertexEntry,
1660
- fragmentEntry: pass.program.fragmentEntry,
1661
- materialShaderId: runtimeMaterialShaderId(pass.program.module, pass.name),
1662
- paramSnapshot,
1663
- ...passState.stencilReference !== void 0 && {
1664
- stencilReference: passState.stencilReference
1606
+ // src/render-system-extract-tail.ts
1607
+ function extractFrames(worlds, owner, assets, pipelineState, materialSnapshotCachesByWorld, options = {}) {
1608
+ const cameraOwner = typeof owner === "number" ? owner : owner.cameraOwner;
1609
+ const resourceOwner = typeof owner === "number" ? owner : owner.resourceOwner;
1610
+ const skinPaletteAllocator = pipelineState?.skinPaletteAllocator ?? null;
1611
+ if (skinPaletteAllocator !== null) {
1612
+ skinPaletteAllocator.resetForFrame();
1613
+ }
1614
+ const failedWorlds = /* @__PURE__ */ new Set();
1615
+ const resourceOwnerWorld = worlds[resourceOwner];
1616
+ const resourceOwnerFog = resourceOwnerWorld === void 0 ? null : selectFogFrame(resourceOwnerWorld) ?? null;
1617
+ const succeededFrames = [];
1618
+ const succeededIndices = [];
1619
+ const extractionOrder = Array.from({ length: worlds.length }, (_, wi) => wi).sort((a, b) => {
1620
+ if (a === cameraOwner) return -1;
1621
+ if (b === cameraOwner) return 1;
1622
+ return a - b;
1623
+ });
1624
+ const framesByWorld = /* @__PURE__ */ new Map();
1625
+ for (const wi of extractionOrder) {
1626
+ const world = worlds[wi];
1627
+ if (world === void 0 || failedWorlds.has(world)) continue;
1628
+ try {
1629
+ const isCameraOwner = wi === cameraOwner;
1630
+ const cameraOwnerFrame2 = framesByWorld.get(cameraOwner);
1631
+ const prepared = prepareExtractContext(world, {
1632
+ ...assets !== void 0 ? { assets } : {},
1633
+ ...pipelineState !== void 0 ? { pipelineState } : {},
1634
+ resourceOwnerFog: wi === resourceOwner ? resourceOwnerFog : null,
1635
+ ...materialSnapshotCachesByWorld === void 0 ? {} : {
1636
+ materialSnapshotCache: materialSnapshotCachesByWorld.get(world) ?? (() => {
1637
+ const cache = /* @__PURE__ */ new Map();
1638
+ materialSnapshotCachesByWorld.set(world, cache);
1639
+ return cache;
1640
+ })()
1641
+ },
1642
+ cull: options.cull === "none" ? "none" : isCameraOwner ? "self" : "external",
1643
+ renderables: options.renderables ?? "full",
1644
+ ...options.renderableEntitiesByWorld?.[wi] === void 0 ? {} : { renderableEntities: options.renderableEntitiesByWorld[wi] },
1645
+ retainHidden: options.retainHidden ?? false,
1646
+ ...options.getMaterialShaderArtifact === void 0 ? {} : { getMaterialShaderArtifact: options.getMaterialShaderArtifact },
1647
+ ...options.instanceCollections === void 0 ? {} : { instanceCollections: options.instanceCollections },
1648
+ ...cameraOwnerFrame2 === void 0 ? {} : { cullCameras: cameraOwnerFrame2.cameras }
1649
+ });
1650
+ const frame = extractFrame(world, prepared);
1651
+ framesByWorld.set(wi, frame);
1652
+ } catch (err6) {
1653
+ try {
1654
+ createWorldInternalView(world)._routeError(err6, {
1655
+ severity: Severity.Error,
1656
+ systemName: `RenderSystem.extractFrames(world[${wi}])`
1657
+ });
1658
+ } catch {
1665
1659
  }
1666
- });
1660
+ }
1667
1661
  }
1668
- }
1669
- function selectCameraTargetViews(candidates, options) {
1670
- const display = candidates.find(
1671
- (candidate) => candidate.entityKey === options.displayEntityKey && candidate.target === void 0
1672
- );
1673
- const rejected = [];
1674
- const ordered = candidates.filter((candidate) => candidate.target !== void 0).sort((left, right) => left.worldId - right.worldId || left.entityKey - right.entityKey);
1675
- const auxiliary = [];
1676
- const seenTargets = /* @__PURE__ */ new Set();
1677
- for (const candidate of ordered) {
1678
- const duplicate = candidate.target !== void 0 && seenTargets.has(candidate.target);
1679
- if (candidate.target !== void 0) seenTargets.add(candidate.target);
1680
- if (candidate.entityKey === options.displayEntityKey) {
1681
- rejected.push({ entityKey: candidate.entityKey, reason: "display-target" });
1682
- continue;
1662
+ for (let wi = 0; wi < worlds.length; wi++) {
1663
+ const frame = framesByWorld.get(wi);
1664
+ if (frame === void 0) continue;
1665
+ succeededFrames.push(frame);
1666
+ succeededIndices.push(wi);
1667
+ }
1668
+ const renderables = [];
1669
+ const dispatchEntries = [];
1670
+ const visibilitySnapshots = [];
1671
+ const featureVisibilitySnapshots = [];
1672
+ const hiddenEntityReports = [];
1673
+ for (let fi = 0; fi < succeededFrames.length; fi++) {
1674
+ const f = succeededFrames[fi];
1675
+ const wId = succeededIndices[fi];
1676
+ if (f === void 0 || wId === void 0) continue;
1677
+ const base = renderables.length;
1678
+ const visibilitySnapshot = f.visibilitySnapshots[0];
1679
+ const world = worlds[wId];
1680
+ if (visibilitySnapshot !== void 0) {
1681
+ visibilitySnapshots.push(visibilitySnapshot);
1682
+ if (world !== void 0)
1683
+ featureVisibilitySnapshots.push({ world, snapshot: visibilitySnapshot });
1683
1684
  }
1684
- if (candidate.target === void 0 || duplicate) {
1685
- rejected.push({ entityKey: candidate.entityKey, reason: "duplicate-target" });
1686
- continue;
1685
+ hiddenEntityReports.push(...f.hiddenEntityReports);
1686
+ for (const r of f.renderables) {
1687
+ renderables.push({
1688
+ ...r,
1689
+ worldId: wId,
1690
+ ...r.pointsLines === void 0 ? {} : { pointsLines: { ...r.pointsLines, worldId: wId } }
1691
+ });
1687
1692
  }
1688
- if (auxiliary.length >= Math.max(0, options.budget)) {
1689
- rejected.push({ entityKey: candidate.entityKey, reason: "budget" });
1690
- continue;
1693
+ for (const d of f.dispatch) {
1694
+ dispatchEntries.push({ ...d, renderableIndex: (d.renderableIndex ?? 0) + base });
1691
1695
  }
1692
- auxiliary.push(candidate);
1693
1696
  }
1694
- return {
1695
- display,
1696
- auxiliary: Object.freeze(auxiliary),
1697
- rejected: Object.freeze(rejected)
1698
- };
1699
- }
1700
- var Severity = Object.freeze({ Error: "error", Warning: "warning" });
1701
- function createWorldInternalView(world) {
1702
- return {
1703
- _routeError(error, ctx) {
1704
- routeWorldError(world, error, { systemName: ctx.systemName });
1705
- },
1706
- _getArrayView(entity, component, fieldName) {
1707
- return readRenderArrayView(world, entity, component, fieldName);
1697
+ dispatchEntries.sort((a, b) => (a.queue ?? 0) - (b.queue ?? 0));
1698
+ const point = [];
1699
+ const spot = [];
1700
+ const rect = [];
1701
+ let directional;
1702
+ let directionalCount = 0;
1703
+ let lightViewProj;
1704
+ let splitPlanes;
1705
+ let cascadeCount;
1706
+ let cascadeBlend;
1707
+ let shadowMapSize;
1708
+ let depthBias;
1709
+ let normalBias;
1710
+ let directionalShadowQuality;
1711
+ let directionalShadowError;
1712
+ const pointShadow = [];
1713
+ let directionalCsmConfig;
1714
+ let directionalCsmDirection;
1715
+ for (const f of succeededFrames) {
1716
+ for (const p of f.lights.point) point.push(p);
1717
+ for (const s of f.lights.spot) spot.push(s);
1718
+ for (const r of f.lights.rect) rect.push(r);
1719
+ for (const ps of f.lights.pointShadow) pointShadow.push(ps);
1720
+ if (directional === void 0 && f.lights.directional !== void 0) {
1721
+ directional = f.lights.directional;
1722
+ lightViewProj = f.lights.lightViewProj;
1723
+ splitPlanes = f.lights.splitPlanes;
1724
+ cascadeCount = f.lights.cascadeCount;
1725
+ cascadeBlend = f.lights.cascadeBlend;
1726
+ shadowMapSize = f.lights.shadowMapSize;
1727
+ depthBias = f.lights.depthBias;
1728
+ normalBias = f.lights.normalBias;
1729
+ directionalShadowQuality = f.lights.directionalShadowQuality;
1730
+ directionalShadowError = f.lights.directionalShadowError;
1731
+ directionalCsmConfig = f.lights.directionalCsmConfig;
1732
+ directionalCsmDirection = f.lights.directionalCsmDirection;
1708
1733
  }
1709
- };
1710
- }
1711
- function resolveCameraTarget(world, raw) {
1712
- if (raw === void 0 || raw <= 0) return void 0;
1713
- const resolved = world.sharedRefs.resolve(
1714
- toShared(Math.round(raw))
1715
- );
1716
- return resolved.ok ? resolved.value : void 0;
1717
- }
1718
- var BUILTIN_USER_REGION_TEXTURE_FIELDS = [
1719
- "baseColorTexture",
1720
- "metallicRoughnessTexture",
1721
- "normalTexture",
1722
- "specularTintTexture",
1723
- "emissiveTexture",
1724
- "occlusionTexture",
1725
- "transmissionTexture",
1726
- "thicknessTexture"
1727
- ];
1728
- var BUILTIN_USER_REGION_TEXTURE_FIELD_SET = new Set(BUILTIN_USER_REGION_TEXTURE_FIELDS);
1729
- var BUILTIN_BASE_COLOR_TEXTURE_FIELD_SET = /* @__PURE__ */ new Set(["baseColorTexture"]);
1730
- function materialTextureFields(shaderId, fields) {
1731
- if (fields !== void 0 && fields.size > 0) return fields;
1732
- if (shaderId === "forgeax::default-standard-pbr" || shaderId === "forgeax::default-standard-pbr-skin" || shaderId === "forgeax::pbr-skin") {
1733
- return BUILTIN_USER_REGION_TEXTURE_FIELD_SET;
1734
+ directionalCount += f.lights.directionalCount;
1734
1735
  }
1735
- if (shaderId === "forgeax::default-unlit" || shaderId === "forgeax::sprite" || shaderId === "forgeax::sprite-lit") {
1736
- return BUILTIN_BASE_COLOR_TEXTURE_FIELD_SET;
1736
+ let cameraOwnerFrame;
1737
+ let resourceOwnerFrame;
1738
+ for (let fi = 0; fi < succeededFrames.length; fi++) {
1739
+ if (succeededIndices[fi] === cameraOwner) cameraOwnerFrame = succeededFrames[fi];
1740
+ if (succeededIndices[fi] === resourceOwner) resourceOwnerFrame = succeededFrames[fi];
1737
1741
  }
1738
- return fields;
1739
- }
1740
- function runtimeMaterialShaderId(module, passName) {
1741
- if (passName === "shadow-caster" && (module === "forgeax_material::standard" || module === "forgeax_material::unlit" || module === "forgeax::default-standard-pbr" || module === "forgeax::default-unlit")) {
1742
- return "forgeax::default-shadow-caster";
1742
+ const cameras = cameraOwnerFrame !== void 0 ? [...cameraOwnerFrame.cameras] : [];
1743
+ const auxiliaryCameras = cameraOwnerFrame !== void 0 ? [...cameraOwnerFrame.auxiliaryCameras] : [];
1744
+ const cubeCameras = cameraOwnerFrame !== void 0 ? [...cameraOwnerFrame.cubeCameras] : [];
1745
+ const mergeCam = cameras[0];
1746
+ if (directionalCsmConfig !== void 0 && directionalCsmDirection !== void 0) {
1747
+ const mergeCameraData = mergeCam !== void 0 ? {
1748
+ world: mergeCam.world,
1749
+ fov: mergeCam.fov,
1750
+ aspect: mergeCam.aspect,
1751
+ near: mergeCam.near,
1752
+ far: mergeCam.far,
1753
+ projection: mergeCam.projection,
1754
+ orthoLeft: mergeCam.orthoLeft,
1755
+ orthoRight: mergeCam.orthoRight,
1756
+ orthoBottom: mergeCam.orthoBottom,
1757
+ orthoTop: mergeCam.orthoTop
1758
+ } : void 0;
1759
+ const csm = computeDirectionalCsm(
1760
+ directionalCsmDirection,
1761
+ directionalCsmConfig,
1762
+ mergeCameraData
1763
+ );
1764
+ if (csm !== null) {
1765
+ lightViewProj = csm.lightViewProj;
1766
+ splitPlanes = csm.splitPlanes;
1767
+ cascadeCount = csm.cascadeCount;
1768
+ cascadeBlend = csm.cascadeBlend;
1769
+ shadowMapSize = csm.shadowMapSize;
1770
+ directionalShadowQuality = csm.directionalShadowQuality;
1771
+ }
1743
1772
  }
1744
- switch (module) {
1745
- case "forgeax_material::standard":
1746
- return "forgeax::default-standard-pbr";
1747
- case "forgeax_material::unlit":
1748
- return "forgeax::default-unlit";
1749
- case "forgeax_material::sprite":
1750
- return "forgeax::sprite";
1751
- case "forgeax_material::sprite-lit":
1752
- return "forgeax::sprite-lit";
1753
- default:
1754
- return module;
1773
+ const lights = {
1774
+ directional,
1775
+ directionalCount,
1776
+ point,
1777
+ spot,
1778
+ rect,
1779
+ lightViewProj,
1780
+ splitPlanes,
1781
+ cascadeCount,
1782
+ cascadeBlend,
1783
+ shadowMapSize,
1784
+ depthBias,
1785
+ normalBias,
1786
+ directionalShadowQuality,
1787
+ directionalShadowError,
1788
+ pointShadow,
1789
+ directionalCsmConfig,
1790
+ directionalCsmDirection
1791
+ };
1792
+ const volumetricFog = resourceOwnerFrame?.volumetricFog;
1793
+ const skylight = resourceOwnerFrame?.skylight;
1794
+ const skylightCount = resourceOwnerFrame?.skylightCount ?? 0;
1795
+ const skybox = resourceOwnerFrame?.skybox;
1796
+ const skyboxCount = resourceOwnerFrame?.skyboxCount ?? 0;
1797
+ const fog = resourceOwnerFrame?.fog;
1798
+ const fogFailure = resourceOwnerFrame?.fogFailure;
1799
+ const environment = resourceOwnerFrame?.environment;
1800
+ const lightProbes = succeededFrames.flatMap((frame) => frame.lightProbes ?? []);
1801
+ const postProcessParams = new Map(resourceOwnerFrame?.postProcessParams);
1802
+ const OUTPUT_TRANSFORM_PARAM_KEY = STANDARD_OUTPUT_TRANSFORM_FEATURE_ID;
1803
+ const cameraTonemapParam = cameraOwnerFrame?.postProcessParams.get(OUTPUT_TRANSFORM_PARAM_KEY);
1804
+ if (cameraTonemapParam !== void 0) {
1805
+ postProcessParams.set(OUTPUT_TRANSFORM_PARAM_KEY, cameraTonemapParam);
1806
+ } else {
1807
+ postProcessParams.delete(OUTPUT_TRANSFORM_PARAM_KEY);
1755
1808
  }
1756
- }
1757
- function pipelineRenderState(renderState) {
1758
- if (renderState === void 0) return void 0;
1759
- const {
1760
- cullMode,
1761
- depthCompare,
1762
- depthWriteEnabled,
1763
- blend,
1764
- alphaToCoverageEnabled,
1765
- stencil,
1766
- stencilReadMask,
1767
- stencilWriteMask,
1768
- frontFace
1769
- } = renderState;
1770
- if (cullMode === void 0 && depthCompare === void 0 && depthWriteEnabled === void 0 && blend === void 0 && alphaToCoverageEnabled === void 0 && stencil === void 0 && stencilReadMask === void 0 && stencilWriteMask === void 0 && frontFace === void 0) {
1771
- return void 0;
1809
+ const frustumStats = {
1810
+ culled: succeededFrames.reduce((s, f) => s + f.frustumStats.culled, 0),
1811
+ total: succeededFrames.reduce((s, f) => s + f.frustumStats.total, 0)
1812
+ };
1813
+ const visibilityStats = {
1814
+ explicitlyHidden: succeededFrames.reduce((s, f) => s + f.visibilityStats.explicitlyHidden, 0)
1815
+ };
1816
+ const reflectionProbes = succeededFrames.flatMap((f, index) => {
1817
+ const worldId = succeededIndices[index] ?? 0;
1818
+ return (f.reflectionProbes ?? []).map((probe) => ({ ...probe, worldId }));
1819
+ });
1820
+ if (options.renderables !== "none" && options.renderableEntitiesByWorld === void 0) {
1821
+ options.instanceCollections?.retain(
1822
+ new Set(
1823
+ renderables.flatMap(
1824
+ (row) => row.instances?.collectionId === void 0 ? [] : [row.instances.collectionId]
1825
+ )
1826
+ )
1827
+ );
1828
+ if (skinPaletteAllocator !== null && typeof skinPaletteAllocator.retainSliceKeys === "function") {
1829
+ skinPaletteAllocator.retainSliceKeys(
1830
+ new Set(
1831
+ renderables.flatMap(
1832
+ (row) => row.skin === void 0 ? [] : [`${worlds[row.worldId]?.identity ?? row.worldId}:${row.entityKey}`]
1833
+ )
1834
+ )
1835
+ );
1836
+ }
1772
1837
  }
1773
1838
  return {
1774
- ...cullMode !== void 0 && { cullMode },
1775
- ...depthCompare !== void 0 && { depthCompare },
1776
- ...depthWriteEnabled !== void 0 && { depthWriteEnabled },
1777
- ...blend !== void 0 && { blend },
1778
- ...alphaToCoverageEnabled !== void 0 && { alphaToCoverageEnabled },
1779
- ...stencil !== void 0 && { stencil },
1780
- ...stencilReadMask !== void 0 && { stencilReadMask },
1781
- ...stencilWriteMask !== void 0 && { stencilWriteMask },
1782
- ...frontFace !== void 0 && { frontFace }
1839
+ cameras,
1840
+ auxiliaryCameras,
1841
+ cubeCameras,
1842
+ reflectionProbes,
1843
+ lights,
1844
+ ...volumetricFog === void 0 ? {} : { volumetricFog: { ...volumetricFog, worldId: resourceOwner } },
1845
+ renderables,
1846
+ dispatch: dispatchEntries,
1847
+ skylight,
1848
+ skylightCount,
1849
+ lightProbes,
1850
+ skybox,
1851
+ skyboxCount,
1852
+ fog,
1853
+ ...fogFailure === void 0 ? {} : { fogFailure },
1854
+ environment,
1855
+ environmentReady: resourceOwnerFrame !== void 0 && environment !== void 0,
1856
+ frustumStats,
1857
+ visibilityStats,
1858
+ postProcessParams,
1859
+ visibilitySnapshots,
1860
+ featureVisibilitySnapshots,
1861
+ hiddenEntityReports
1783
1862
  };
1784
1863
  }
1785
- var guidHandleInternByWorld = /* @__PURE__ */ new WeakMap();
1786
- function internSharedRefFromGuid(world, assetsRef, guid, brand) {
1787
- let perWorld = guidHandleInternByWorld.get(world);
1788
- if (perWorld === void 0) {
1789
- perWorld = /* @__PURE__ */ new Map();
1790
- guidHandleInternByWorld.set(world, perWorld);
1791
- }
1792
- const key = `${guid.toLowerCase()}\0${brand}`;
1793
- const cached = perWorld.get(key);
1794
- const payload = assetsRef.lookup(guid);
1795
- if (cached !== void 0 && cached.payload === payload) {
1796
- const cachedHandle = toShared(cached.handle);
1797
- if (world.sharedRefs.resolve(cachedHandle).ok) return cachedHandle;
1798
- }
1799
- if (cached !== void 0) {
1800
- world.sharedRefs.release(toShared(cached.handle));
1801
- perWorld.delete(key);
1802
- }
1803
- if (payload === void 0) return void 0;
1804
- const handle = world.internSharedRef(brand, payload);
1805
- perWorld.set(key, { handle, payload });
1806
- return handle;
1807
- }
1808
- function resolveVideoFieldHandle(value, world, assetsRef) {
1809
- const texture = materialTextureValue(value);
1810
- const textureGuid = assetReferenceText(texture?.texture ?? value);
1811
- if (textureGuid === void 0) return void 0;
1812
- const payload = assetsRef.lookup(textureGuid);
1813
- if (payload === void 0 || payload.kind !== "video") return void 0;
1814
- return internSharedRefFromGuid(world, assetsRef, textureGuid, "VideoAsset");
1815
- }
1816
- function materialTextureValue(value) {
1817
- if (typeof value !== "object" || value === null || Array.isArray(value)) return void 0;
1818
- const textureValue = value;
1819
- return typeof textureValue.texture === "number" || typeof textureValue.texture === "string" || textureValue.texture instanceof Uint8Array ? textureValue : void 0;
1820
- }
1821
- function assetReferenceText(value) {
1822
- if (typeof value === "string") return value;
1823
- if (value instanceof Uint8Array && isAssetGuidBytes(value)) return AssetGuid.format(value);
1824
- return void 0;
1825
- }
1826
- function isAssetGuidBytes(value) {
1827
- return value.byteLength === 16;
1828
- }
1829
- function materialNormalScale(values) {
1830
- for (const [field, value] of Object.entries(values)) {
1831
- if (field !== "normalTexture") continue;
1832
- const scale = materialTextureValue(value)?.normalScale;
1833
- return typeof scale === "number" && Number.isFinite(scale) ? scale : 1;
1864
+ function hasFiniteOrderedLocalAabb(aabb) {
1865
+ if (aabb === void 0 || aabb.length !== 6) return false;
1866
+ const minX = aabb[0];
1867
+ const minY = aabb[1];
1868
+ const minZ = aabb[2];
1869
+ const maxX = aabb[3];
1870
+ const maxY = aabb[4];
1871
+ const maxZ = aabb[5];
1872
+ if (minX === void 0 || minY === void 0 || minZ === void 0 || maxX === void 0 || maxY === void 0 || maxZ === void 0) {
1873
+ return false;
1834
1874
  }
1835
- return 1;
1836
- }
1837
- function materialTextureRef(value) {
1838
- return materialTextureValue(value)?.texture ?? value;
1839
- }
1840
- function collectMaterialTextureCoordinates(values) {
1841
- const out = /* @__PURE__ */ new Map();
1842
- for (const field of [
1843
- "baseColorTexture",
1844
- "metallicRoughnessTexture",
1845
- "normalTexture",
1846
- "specularTintTexture",
1847
- "emissiveTexture",
1848
- "occlusionTexture"
1849
- ]) {
1850
- const coordinates = materialTextureValue(values[field])?.coordinates;
1851
- if (coordinates !== void 0) out.set(field, coordinates);
1875
+ if (!Number.isFinite(minX) || !Number.isFinite(minY) || !Number.isFinite(minZ) || !Number.isFinite(maxX) || !Number.isFinite(maxY) || !Number.isFinite(maxZ)) {
1876
+ return false;
1852
1877
  }
1853
- return out;
1878
+ return minX <= maxX && minY <= maxY && minZ <= maxZ;
1854
1879
  }
1855
- function collectMaterialTextureSources(values, world) {
1856
- const out = /* @__PURE__ */ new Map();
1857
- for (const [field, value] of Object.entries(values)) {
1858
- const reference = materialTextureRef(value);
1859
- if (typeof reference !== "number") continue;
1860
- const resolved = world.sharedRefs.resolve(toShared(reference));
1861
- if (!resolved.ok || resolveRenderTargetMaterialSource(resolved.value) === void 0) continue;
1862
- out.set(field, resolved.value);
1880
+ function morphSnapshotFor(mesh, weights) {
1881
+ const targets = mesh.morphTargets;
1882
+ if (targets === void 0 || targets.length === 0 || weights === void 0) return void 0;
1883
+ if (weights.length !== targets.length) return void 0;
1884
+ const firstPositions = targets[0]?.position;
1885
+ if (firstPositions === void 0 || firstPositions.length === 0 || firstPositions.length % 3 !== 0) {
1886
+ return void 0;
1863
1887
  }
1864
- return out;
1865
- }
1866
- function collectMaterialTextureSamplers(values, resolveSampler) {
1867
- const out = /* @__PURE__ */ new Map();
1868
- for (const [field, value] of Object.entries(values)) {
1869
- const sampler = resolveSampler(materialTextureValue(value)?.sampler);
1870
- if (sampler !== void 0) out.set(field, sampler);
1888
+ const vertexCount = firstPositions.length / 3;
1889
+ if (mesh.vertices.length % vertexCount !== 0) return void 0;
1890
+ for (let targetIndex = 0; targetIndex < targets.length; targetIndex += 1) {
1891
+ const target = targets[targetIndex];
1892
+ if (target?.position?.length !== firstPositions.length) return void 0;
1871
1893
  }
1872
- const legacySampler = resolveSampler(values.sampler);
1873
- if (legacySampler !== void 0 && !out.has("baseColorTexture")) {
1874
- out.set("baseColorTexture", legacySampler);
1894
+ const copied = new Float32Array(weights.length);
1895
+ for (let index = 0; index < weights.length; index += 1) {
1896
+ const weight = weights[index] ?? Number.NaN;
1897
+ if (!Number.isFinite(weight)) return void 0;
1898
+ copied[index] = weight;
1875
1899
  }
1876
- return out;
1900
+ return { weights: copied, targetCount: targets.length };
1877
1901
  }
1878
- function collectUserRegionTextureHandles(pv, shaderId, paramSchema, assetsRef, world, resolveTex, videoOut) {
1879
- const fields = materialTextureFields(
1880
- shaderId,
1881
- paramSchema !== void 0 ? derive(paramSchema).textureFieldNames : shaderId !== void 0 ? assetsRef.materialShaderTextureFieldNames(shaderId) : void 0
1882
- ) ?? BUILTIN_USER_REGION_TEXTURE_FIELDS;
1883
- const out = /* @__PURE__ */ new Map();
1884
- for (const field of fields) {
1885
- const videoHandle = resolveVideoFieldHandle(pv[field], world, assetsRef);
1886
- if (videoHandle !== void 0) {
1887
- videoOut.set(field, videoHandle);
1888
- continue;
1902
+ function extractFrame(world, context) {
1903
+ const {
1904
+ assets,
1905
+ pipelineState,
1906
+ materialSnapshotCache: persistentMaterialSnapshotCache,
1907
+ cull: cullMode,
1908
+ resourceOwnerFog,
1909
+ getMaterialShaderArtifact,
1910
+ instanceCollections,
1911
+ renderableEntities,
1912
+ retainHidden
1913
+ } = context;
1914
+ const fog = resourceOwnerFog === void 0 ? selectFogFrame(world) : resourceOwnerFog ?? void 0;
1915
+ const fogFailure = fogState(world).failure;
1916
+ const visibility = context.visibility.hasAnyHiddenIntent ? context.visibility : void 0;
1917
+ const skinPaletteAllocator = pipelineState?.skinPaletteAllocator ?? null;
1918
+ const directionalLightQuery = world.query({ read: [DirectionalLight] }).unwrap();
1919
+ const worldInternal = createWorldInternalView(world);
1920
+ let volumetricFog = extractVolumeSnapshot(world, assets, worldInternal);
1921
+ const cameraRoles = selectCameraRoles(world);
1922
+ const cameras = [...cameraRoles.display];
1923
+ const auxiliaryCameras = cameraRoles.auxiliary;
1924
+ const cubeCameras = collectCubeCameraSnapshots(world);
1925
+ const reflectionProbes = collectReflectionProbeFacts(world);
1926
+ let directional;
1927
+ let directionalCount = 0;
1928
+ let firstHitCastShadow;
1929
+ let firstHitShadowFields;
1930
+ for (const row of directionalLightQuery) {
1931
+ const l = row.get(DirectionalLight);
1932
+ directionalCount += 1;
1933
+ const intensity = l.intensity;
1934
+ const snapshot = {
1935
+ kind: "directional",
1936
+ entity: row.entity,
1937
+ direction: vec3.create(l.direction[0] ?? 0, l.direction[1] ?? -1, l.direction[2] ?? 0),
1938
+ color: vec3.create(
1939
+ (l.color[0] ?? 1) * intensity,
1940
+ (l.color[1] ?? 1) * intensity,
1941
+ (l.color[2] ?? 1) * intensity
1942
+ ),
1943
+ intensity
1944
+ };
1945
+ if (directional === void 0) {
1946
+ directional = snapshot;
1947
+ firstHitCastShadow = l.castShadow;
1948
+ firstHitShadowFields = {
1949
+ cascadeCount: l.cascadeCount,
1950
+ splitLambda: l.splitLambda,
1951
+ cascadeBlend: l.cascadeBlend,
1952
+ mapSize: l.mapSize,
1953
+ depthBias: l.depthBias,
1954
+ normalBias: l.normalBias,
1955
+ shadowDistance: l.shadowDistance,
1956
+ shadowFilter: l.shadowFilter,
1957
+ shadowAngularRadius: l.shadowAngularRadius,
1958
+ maxPenumbraTexels: l.maxPenumbraTexels
1959
+ };
1889
1960
  }
1890
- const handle = resolveTex(materialTextureRef(pv[field]), "TextureAsset");
1891
- if (handle !== void 0) out.set(field, handle);
1892
1961
  }
1893
- return out;
1894
- }
1895
- var ENGINE_INJECTED_TEXTURE_FIELDS = /* @__PURE__ */ new Set(["emissiveTexture", "occlusionTexture"]);
1896
- function isEngineInjectedTextureField(shaderId, fieldName) {
1897
- return isStandardPbrMaterialShader(shaderId) && ENGINE_INJECTED_TEXTURE_FIELDS.has(fieldName);
1898
- }
1899
- function materialParametersToParamSchema(parameters, shaderId) {
1900
- const engineInjectedTextureFields = isStandardPbrMaterialShader(shaderId) ? ENGINE_INJECTED_TEXTURE_FIELDS : void 0;
1901
- return parameters.flatMap((parameter) => {
1902
- if (parameter.type === "bool") return [];
1903
- if (parameter.type === "texture" || parameter.type === "texture_cube") {
1904
- if (engineInjectedTextureFields?.has(parameter.name) === true) return [];
1905
- return [
1906
- {
1907
- name: parameter.name,
1908
- type: parameter.type === "texture_cube" ? "texture_cube" : "texture2d"
1909
- }
1910
- ];
1962
+ let directionalShadowError;
1963
+ if (directional !== void 0 && firstHitShadowFields !== void 0) {
1964
+ const validation = validateDirectionalLightData({
1965
+ direction: directional.direction,
1966
+ castShadow: firstHitCastShadow,
1967
+ ...firstHitShadowFields
1968
+ });
1969
+ if (!validation.ok) {
1970
+ directionalShadowError = validation.error;
1971
+ worldInternal._routeError(directionalShadowError, {
1972
+ severity: Severity.Error,
1973
+ systemName: "RenderSystem.extract (directional-shadow)"
1974
+ });
1911
1975
  }
1912
- return [
1913
- {
1914
- name: parameter.name,
1915
- type: parameter.type,
1916
- ...parameter.colorSpace === void 0 ? {} : { colorSpace: parameter.colorSpace }
1917
- }
1918
- ];
1919
- });
1920
- }
1921
- function materialColorParameterSchema(parameters, shaderId, assets) {
1922
- if (shaderId === void 0) return parameters;
1923
- const shader = assets.shaderRegistry.findMaterialArtifact(shaderId);
1924
- if (!shader.ok) return parameters;
1925
- const byName = new Map(
1926
- shader.value.paramSchema.map((parameter) => [parameter.name, parameter])
1927
- );
1928
- for (const parameter of parameters) byName.set(parameter.name, parameter);
1929
- return [...byName.values()];
1930
- }
1931
- var MATERIAL_PARENT_CHAIN_LIMIT = 128;
1932
- function isDeepFrozen(value, seen = /* @__PURE__ */ new Set()) {
1933
- if (typeof value !== "object" || value === null) return true;
1934
- if (seen.has(value)) return true;
1935
- if (!Object.isFrozen(value)) return false;
1936
- seen.add(value);
1937
- for (const child of Object.values(value)) {
1938
- if (!isDeepFrozen(child, seen)) return false;
1939
- }
1940
- return true;
1941
- }
1942
- function captureMaterialCacheChain(root, assets) {
1943
- const chain = [];
1944
- const visitedParentGuids = /* @__PURE__ */ new Set();
1945
- let current = root;
1946
- for (let depth = 0; depth < MATERIAL_PARENT_CHAIN_LIMIT; depth += 1) {
1947
- const parentRef = current.parent;
1948
- const parentGuid = parentRef === void 0 ? void 0 : materialGuidText(parentRef);
1949
- chain.push({ asset: current, parentGuid });
1950
- if (parentGuid === void 0) return chain;
1951
- if (parentRef === void 0) return void 0;
1952
- if (visitedParentGuids.has(parentGuid)) return void 0;
1953
- visitedParentGuids.add(parentGuid);
1954
- const parent = assets.lookup(parentRef);
1955
- if (parent?.kind !== "material") return void 0;
1956
- current = parent;
1957
- }
1958
- return void 0;
1959
- }
1960
- function readPersistentMaterialSnapshot(cache, handleRaw, assets) {
1961
- const entry = cache?.get(handleRaw);
1962
- if (entry === void 0) return void 0;
1963
- if (entry.crossFrameSafe && entry.chain !== void 0 && entry.catalogEpoch === assets.catalogEpoch)
1964
- return entry;
1965
- cache?.delete(handleRaw);
1966
- return void 0;
1967
- }
1968
- function readStablePersistentMaterialSnapshot(cache, handleRaw, assets) {
1969
- const entry = cache?.get(handleRaw);
1970
- return entry?.crossFrameSafe === true && entry.chain !== void 0 && entry.catalogEpoch === assets.catalogEpoch ? entry : void 0;
1971
- }
1972
- function storeMaterialSnapshot(cache, handleRaw, snapshot, passes, root, assets) {
1973
- const chain = captureMaterialCacheChain(root, assets);
1974
- const entry = {
1975
- snapshot,
1976
- passes,
1977
- crossFrameSafe: chain?.every(({ asset }) => isDeepFrozen(asset)) === true,
1978
- ...chain === void 0 ? {} : { chain },
1979
- ...chain === void 0 ? {} : { catalogEpoch: assets.catalogEpoch }
1980
- };
1981
- cache.set(handleRaw, entry);
1982
- return entry;
1983
- }
1984
- function resolveMaterialSnapshot(handleRaw, world, assetsRef, materialSnapshotCache, persistentMaterialSnapshotCache) {
1985
- if (handleRaw === 0) return defaultMaterialSnapshot(handleRaw);
1986
- const cached = materialSnapshotCache?.get(handleRaw);
1987
- if (cached !== void 0) return cached.snapshot;
1988
- const stablePersistentCached = readStablePersistentMaterialSnapshot(
1989
- persistentMaterialSnapshotCache,
1990
- handleRaw,
1991
- assetsRef
1992
- );
1993
- if (stablePersistentCached !== void 0) {
1994
- materialSnapshotCache?.set(handleRaw, stablePersistentCached);
1995
- return stablePersistentCached.snapshot;
1996
- }
1997
- const tagged = toShared(handleRaw);
1998
- const res = resolveAssetHandle(world, tagged);
1999
- if (!res.ok) return defaultMaterialSnapshot(handleRaw);
2000
- const asset = res.value;
2001
- if (asset.kind !== "material") return defaultMaterialSnapshot(handleRaw);
2002
- const persistentCached = readPersistentMaterialSnapshot(
2003
- persistentMaterialSnapshotCache,
2004
- handleRaw,
2005
- assetsRef
2006
- );
2007
- if (persistentCached !== void 0) {
2008
- materialSnapshotCache?.set(handleRaw, persistentCached);
2009
- return persistentCached.snapshot;
2010
1976
  }
2011
- const resolvedResult = walkMaterialPassesOverSharedRefs(world, tagged, assetsRef);
2012
- if (!resolvedResult.ok) return defaultMaterialSnapshot(handleRaw);
2013
- const resolved = resolvedResult.value;
2014
- const allPasses = resolved.passes;
2015
- const firstPassShader = allPasses.length > 0 ? runtimeMaterialShaderId(allPasses[0]?.program.module, allPasses[0]?.name) : void 0;
2016
- const pv = materialValuesToLinearRuntime(
2017
- resolved.values,
2018
- materialColorParameterSchema(resolved.parameters ?? [], firstPassShader, assetsRef),
2019
- resolved.colorSpace
2020
- );
2021
- const baseColorPv = pv.baseColor;
2022
- const baseColor = vec3.create(
2023
- baseColorPv?.[0] ?? 1,
2024
- baseColorPv?.[1] ?? 1,
2025
- baseColorPv?.[2] ?? 1
2026
- );
2027
- const metallicPv = typeof pv.metallic === "number" ? pv.metallic : 0;
2028
- const roughnessPv = typeof pv.roughness === "number" ? pv.roughness : 0.5;
2029
- const clearcoatPv = typeof pv.clearcoat === "number" ? pv.clearcoat : 0;
2030
- const clearcoatRoughnessPv = typeof pv.clearcoatRoughness === "number" ? pv.clearcoatRoughness : 0.5;
2031
- const specularTintPv = pv.specularTint;
2032
- const normalScalePv = materialNormalScale(pv);
2033
- const paramSnap = {};
2034
- for (const [k, v] of Object.entries(pv)) {
2035
- if (typeof v === "number") paramSnap[k] = v;
2036
- else if (typeof v === "string") paramSnap[k] = v;
2037
- else if (Array.isArray(v) && v.every((x) => typeof x === "number")) {
2038
- paramSnap[k] = v;
1977
+ const pointSnapshots = [];
1978
+ const pointSnapshotEntities = [];
1979
+ const pointLightQuery = world.query({
1980
+ read: [PointLight],
1981
+ optional: [Transform, GlobalTransform]
1982
+ }).unwrap();
1983
+ for (const row of pointLightQuery) {
1984
+ const p = row.get(PointLight);
1985
+ const hasTransform = row.get(GlobalTransform) !== void 0;
1986
+ const intensity = p.intensity;
1987
+ const range = p.range;
1988
+ const entityId = row.entity;
1989
+ let worldMat;
1990
+ if (hasTransform) {
1991
+ const view = worldInternal._getArrayView(entityId, GlobalTransform, "world");
1992
+ if (view !== void 0) worldMat = new Float32Array(view);
2039
1993
  }
1994
+ const position = worldMat !== void 0 ? mat4.getTranslation(vec3.create(), worldMat) : vec3.create(0, 0, 0);
1995
+ pointSnapshots.push({
1996
+ kind: "point",
1997
+ entity: entityId,
1998
+ position,
1999
+ color: vec3.create(
2000
+ (p.color[0] ?? 1) * intensity,
2001
+ (p.color[1] ?? 1) * intensity,
2002
+ (p.color[2] ?? 1) * intensity
2003
+ ),
2004
+ intensity,
2005
+ invRangeSquared: computeInvRangeSquared(range)
2006
+ });
2007
+ pointSnapshotEntities.push(entityId);
2040
2008
  }
2041
- const materialParamSchema = materialParametersToParamSchema(
2042
- resolved.parameters ?? [],
2043
- firstPassShader
2044
- );
2045
- const resolveTexLike = (value, brand) => {
2046
- if (typeof value === "number") return toShared(value);
2047
- if (typeof value === "string") {
2048
- return internSharedRefFromGuid(world, assetsRef, value, brand);
2049
- }
2050
- return void 0;
2051
- };
2052
- const videoTextureFields = /* @__PURE__ */ new Map();
2053
- const textureHandles = collectUserRegionTextureHandles(
2054
- pv,
2055
- firstPassShader,
2056
- materialParamSchema.length > 0 ? materialParamSchema : void 0,
2057
- assetsRef,
2058
- world,
2059
- resolveTexLike,
2060
- videoTextureFields
2061
- );
2062
- const samplerHandles = collectMaterialTextureSamplers(
2063
- pv,
2064
- (value) => resolveTexLike(materialTextureRef(value), "SamplerAsset")
2065
- );
2066
- const textureSources = collectMaterialTextureSources(pv, world);
2067
- const emissiveTextureHandle = resolveTexLike(
2068
- materialTextureRef(pv.emissiveTexture),
2069
- "TextureAsset"
2070
- );
2071
- const occlusionTextureHandle = resolveTexLike(
2072
- materialTextureRef(pv.occlusionTexture),
2073
- "TextureAsset"
2074
- );
2075
- const textureCoordinates = collectMaterialTextureCoordinates(pv);
2076
- const baseColorTextureHandle = textureHandles.get("baseColorTexture");
2077
- const metallicRoughnessTextureHandle = textureHandles.get("metallicRoughnessTexture");
2078
- const normalTextureHandle = textureHandles.get("normalTexture");
2079
- const emissivePv = pv.emissive;
2080
- const snapshot = {
2081
- baseColor,
2082
- metallic: metallicPv,
2083
- roughness: roughnessPv,
2084
- clearcoat: clearcoatPv,
2085
- clearcoatRoughness: clearcoatRoughnessPv,
2086
- ...specularTintPv !== void 0 && {
2087
- specularTint: [
2088
- specularTintPv[0] ?? 1,
2089
- specularTintPv[1] ?? 1,
2090
- specularTintPv[2] ?? 1
2091
- ]
2092
- },
2093
- normalScale: normalScalePv,
2094
- materialShaderId: firstPassShader,
2095
- materialHandle: handleRaw,
2096
- renderState: pipelineRenderState(allPasses[0]?.renderState),
2097
- paramSnapshot: paramSnap,
2098
- ...materialParamSchema.length > 0 && { materialParamSchema },
2099
- ...textureCoordinates.size > 0 && { textureCoordinates },
2100
- ...textureHandles.size > 0 && { textureHandles },
2101
- ...textureSources.size > 0 && { textureSources },
2102
- ...videoTextureFields.size > 0 && { videoTextureFields },
2103
- ...samplerHandles.size > 0 && { samplerHandles },
2104
- ...baseColorTextureHandle !== void 0 && { baseColorTexture: baseColorTextureHandle },
2105
- ...metallicRoughnessTextureHandle !== void 0 && {
2106
- metallicRoughnessTexture: metallicRoughnessTextureHandle
2107
- },
2108
- ...normalTextureHandle !== void 0 && { normalTexture: normalTextureHandle },
2109
- ...emissivePv !== void 0 && {
2110
- emissive: [emissivePv[0] ?? 0, emissivePv[1] ?? 0, emissivePv[2] ?? 0]
2111
- },
2112
- ...typeof pv.emissiveIntensity === "number" && { emissiveIntensity: pv.emissiveIntensity },
2113
- ...emissiveTextureHandle !== void 0 && { emissiveTexture: emissiveTextureHandle },
2114
- ...occlusionTextureHandle !== void 0 && { occlusionTexture: occlusionTextureHandle },
2115
- ...typeof pv.occlusionStrength === "number" && { occlusionStrength: pv.occlusionStrength },
2116
- // feat-city-glb Bug 5 (per-submesh transparency): derive `transparent`
2117
- // from the first pass's `renderState.blend` presence, identical to the
2118
- // entity-level snapshot builder (extractFrame archetype loop). Without
2119
- // this, per-submesh materials (materials[i>=1], e.g. a glTF BLEND decal
2120
- // submesh on a multi-material mesh) never carry the transparent flag, so
2121
- // the record stage's LDR split + blend routing treats them as opaque and
2122
- // their alpha=0 texels composite as black.
2123
- transparent: allPasses[0]?.renderState?.blend !== void 0
2124
- };
2125
- const stored = materialSnapshotCache !== void 0 ? storeMaterialSnapshot(
2126
- materialSnapshotCache,
2127
- handleRaw,
2128
- snapshot,
2129
- allPasses,
2130
- asset,
2131
- assetsRef
2132
- ) : void 0;
2133
- const cacheEntry = stored ?? (persistentMaterialSnapshotCache !== void 0 ? storeMaterialSnapshot(
2134
- persistentMaterialSnapshotCache,
2135
- handleRaw,
2136
- snapshot,
2137
- allPasses,
2138
- asset,
2139
- assetsRef
2140
- ) : void 0);
2141
- if (cacheEntry?.crossFrameSafe === true) {
2142
- persistentMaterialSnapshotCache?.set(handleRaw, cacheEntry);
2143
- }
2144
- return snapshot;
2145
- }
2146
- function computeFrustumCorners(vp, camNear, camFar, nearZ, farZ, projection) {
2147
- const invVP = mat4.create();
2148
- mat4.invert(invVP, vp);
2149
- const corners = [];
2150
- const span = camFar - camNear;
2151
- const ndcNear = projection === "orthographic" ? (nearZ - camNear) / span : camFar * (nearZ - camNear) / (nearZ * span);
2152
- const ndcFar = projection === "orthographic" ? (farZ - camNear) / span : camFar * (farZ - camNear) / (farZ * span);
2153
- const signs = [-1, 1];
2154
- for (const sx of signs) {
2155
- for (const sy of signs) {
2156
- corners.push(unprojectNDC(invVP, sx, sy, ndcNear));
2157
- }
2158
- }
2159
- for (const sx of signs) {
2160
- for (const sy of signs) {
2161
- corners.push(unprojectNDC(invVP, sx, sy, ndcFar));
2009
+ const spotSnapshots = [];
2010
+ const spotLightQuery = world.query({
2011
+ read: [SpotLight],
2012
+ optional: [Transform, GlobalTransform]
2013
+ }).unwrap();
2014
+ let spotTileNext = 0;
2015
+ const iesSlices = /* @__PURE__ */ new Map();
2016
+ const cookieSlices = /* @__PURE__ */ new Map();
2017
+ const projectorSlices = /* @__PURE__ */ new Map();
2018
+ for (const row of spotLightQuery) {
2019
+ const s = row.get(SpotLight);
2020
+ const hasTransform = row.get(GlobalTransform) !== void 0;
2021
+ const intensity = s.intensity;
2022
+ const range = s.range;
2023
+ const innerConeDeg = s.innerConeDeg;
2024
+ const outerConeDeg = s.outerConeDeg;
2025
+ let worldMat;
2026
+ if (hasTransform) {
2027
+ const entity = row.entity;
2028
+ const view = worldInternal._getArrayView(entity, GlobalTransform, "world");
2029
+ if (view !== void 0) worldMat = new Float32Array(view);
2162
2030
  }
2163
- }
2164
- return corners;
2165
- }
2166
- function unprojectNDC(invVP, ndcX, ndcY, ndcZ) {
2167
- const ndc = vec3.create(ndcX, ndcY, ndcZ);
2168
- const ws = vec3.create();
2169
- mat4.unproject(ws, ndc, invVP);
2170
- return ws;
2171
- }
2172
- function pssmSplit(nearPlane, farPlane, cascadeCount, splitLambda) {
2173
- const EPS = 1e-6;
2174
- if (farPlane <= nearPlane + EPS) {
2175
- throw new ShadowInvalidConfigError("shadowDistance", farPlane, nearPlane + EPS);
2176
- }
2177
- const result = new Float32Array(cascadeCount);
2178
- const m = cascadeCount;
2179
- const n = nearPlane;
2180
- const f = farPlane;
2181
- const ratio = f / n;
2182
- for (let i = 1; i <= m; i++) {
2183
- const t = i / m;
2184
- const logPart = n * ratio ** t;
2185
- const uniformPart = n + t * (f - n);
2186
- result[i - 1] = splitLambda * logPart + (1 - splitLambda) * uniformPart;
2187
- }
2188
- return result;
2189
- }
2190
- var POINT_SHADOW_FACE_LOOK = [
2191
- vec3.create(1, 0, 0),
2192
- vec3.create(-1, 0, 0),
2193
- vec3.create(0, 1, 0),
2194
- vec3.create(0, -1, 0),
2195
- vec3.create(0, 0, 1),
2196
- vec3.create(0, 0, -1)
2197
- ];
2198
- var POINT_SHADOW_FACE_UP = [
2199
- vec3.create(0, -1, 0),
2200
- vec3.create(0, -1, 0),
2201
- vec3.create(0, 0, 1),
2202
- vec3.create(0, 0, -1),
2203
- vec3.create(0, -1, 0),
2204
- vec3.create(0, -1, 0)
2205
- ];
2206
- function buildPointShadowMatrices(lightPos, near, far) {
2207
- const fovY = Math.PI / 2;
2208
- const aspect = 1;
2209
- const proj = mat4.create();
2210
- mat4.perspective(proj, fovY, aspect, near, far);
2211
- const out = [];
2212
- for (let i = 0; i < 6; i++) {
2213
- const look = POINT_SHADOW_FACE_LOOK[i];
2214
- const up = POINT_SHADOW_FACE_UP[i];
2215
- const target = vec3.create(
2216
- (lightPos[0] ?? 0) + (look[0] ?? 0),
2217
- (lightPos[1] ?? 0) + (look[1] ?? 0),
2218
- (lightPos[2] ?? 0) + (look[2] ?? 0)
2219
- );
2220
- const view = mat4.create();
2221
- mat4.lookAt(view, lightPos, target, up);
2222
- const vp = mat4.create();
2223
- mat4.multiply(vp, proj, view);
2224
- out.push(vp);
2225
- }
2226
- return out;
2227
- }
2228
- function computeDirectionalCsm(direction, config, cameraData) {
2229
- const cascadeCount = Math.round(config.cascadeCount);
2230
- const sNear = cameraData?.near ?? 0.1;
2231
- const sFar = config.shadowDistance;
2232
- const splits = pssmSplit(sNear, sFar, cascadeCount, config.splitLambda);
2233
- const lightDirN = vec3.normalize(vec3.create(), direction);
2234
- const lightTarget = vec3.create(lightDirN[0] ?? 0, lightDirN[1] ?? 0, lightDirN[2] ?? 0);
2235
- const lightView = mat4.create();
2236
- mat4.lookAt(lightView, vec3.create(0, 0, 0), lightTarget, vec3.create(0, 1, 0));
2237
- if (cameraData === void 0) return null;
2238
- const camProj = mat4.create();
2239
- if (cameraData.projection === "orthographic") {
2240
- mat4.orthographic(
2241
- camProj,
2242
- cameraData.orthoLeft,
2243
- cameraData.orthoRight,
2244
- cameraData.orthoBottom,
2245
- cameraData.orthoTop,
2246
- cameraData.near,
2247
- cameraData.far
2031
+ const position = worldMat !== void 0 ? mat4.getTranslation(vec3.create(), worldMat) : vec3.create(0, 0, 0);
2032
+ const dir = vec3.create(s.direction[0] ?? 0, s.direction[1] ?? -1, s.direction[2] ?? 0);
2033
+ const dirLen = Math.sqrt(
2034
+ (dir[0] ?? 0) * (dir[0] ?? 0) + (dir[1] ?? 0) * (dir[1] ?? 0) + (dir[2] ?? 0) * (dir[2] ?? 0)
2248
2035
  );
2249
- } else {
2250
- mat4.perspective(camProj, cameraData.fov, cameraData.aspect, cameraData.near, cameraData.far);
2251
- }
2252
- const camView = mat4.create();
2253
- mat4.invert(camView, cameraData.world);
2254
- const cameraVP = mat4.create();
2255
- mat4.multiply(cameraVP, camProj, camView);
2256
- const resultLightViewProjs = [];
2257
- const cascadeFits = [];
2258
- let lightSpaceMaxZFull = -Infinity;
2259
- const fullCorners = computeFrustumCorners(
2260
- cameraVP,
2261
- cameraData.near,
2262
- cameraData.far,
2263
- sNear,
2264
- sFar,
2265
- cameraData.projection
2266
- );
2267
- for (const ws of fullCorners) {
2268
- const ls = vec3.create();
2269
- mat4.transformVec3(ls, lightView, ws);
2270
- if ((ls[2] ?? 0) > lightSpaceMaxZFull) lightSpaceMaxZFull = ls[2] ?? 0;
2271
- }
2272
- for (let cIdx = 0; cIdx < 4; cIdx++) {
2273
- if (cIdx >= cascadeCount) {
2274
- resultLightViewProjs.push(new Float32Array(16));
2275
- continue;
2036
+ const EPSILON = 1e-6;
2037
+ const hasValidDirection = dirLen > EPSILON;
2038
+ if (!hasValidDirection) {
2039
+ worldInternal._routeError(
2040
+ new SpawnLightInvalidBoundsError("SpotLight", "direction", [
2041
+ dir[0] ?? 0,
2042
+ dir[1] ?? 0,
2043
+ dir[2] ?? 0
2044
+ ]),
2045
+ {
2046
+ severity: Severity.Error,
2047
+ systemName: "RenderSystem.extract (spot-direction)"
2048
+ }
2049
+ );
2276
2050
  }
2277
- const cascadeNear = cIdx === 0 ? sNear : splits[cIdx - 1] ?? sFar;
2278
- const cascadeFar = splits[cIdx] ?? sFar;
2279
- const corners = computeFrustumCorners(
2280
- cameraVP,
2281
- cameraData.near,
2282
- cameraData.far,
2283
- cascadeNear,
2284
- cascadeFar,
2285
- cameraData.projection
2286
- );
2287
- const lightMVP = mat4.clone(lightView);
2288
- let minX = Infinity;
2289
- let maxX = -Infinity;
2290
- let minY = Infinity;
2291
- let maxY = -Infinity;
2292
- let minZ = Infinity;
2293
- let maxZ = -Infinity;
2294
- for (const ws of corners) {
2295
- const ls = vec3.create();
2296
- mat4.transformVec3(ls, lightMVP, ws);
2297
- if ((ls[0] ?? 0) < minX) minX = ls[0] ?? 0;
2298
- if ((ls[0] ?? 0) > maxX) maxX = ls[0] ?? 0;
2299
- if ((ls[1] ?? 0) < minY) minY = ls[1] ?? 0;
2300
- if ((ls[1] ?? 0) > maxY) maxY = ls[1] ?? 0;
2301
- if ((ls[2] ?? 0) < minZ) minZ = ls[2] ?? 0;
2302
- if ((ls[2] ?? 0) > maxZ) maxZ = ls[2] ?? 0;
2051
+ const dirN = vec3.create(
2052
+ hasValidDirection ? (dir[0] ?? 0) / dirLen : dir[0] ?? 0,
2053
+ hasValidDirection ? (dir[1] ?? 0) / dirLen : dir[1] ?? 0,
2054
+ hasValidDirection ? (dir[2] ?? 0) / dirLen : dir[2] ?? 0
2055
+ );
2056
+ const castShadow = s.castShadow;
2057
+ const sMapSize = s.mapSize;
2058
+ const sNearPlane = s.nearPlane;
2059
+ const sFarPlane = s.farPlane;
2060
+ const iesProfileAsset = s.iesProfile === void 0 ? void 0 : resolveAssetHandle(world, s.iesProfile);
2061
+ const cookieAsset = s.cookie === void 0 ? void 0 : resolveAssetHandle(world, s.cookie);
2062
+ const hasProjector = Number(s.projector) > 0;
2063
+ let projectorHandle;
2064
+ let projectorAsset;
2065
+ let projectorGuid;
2066
+ if (hasProjector) {
2067
+ const candidate = toShared(Math.round(Number(s.projector)));
2068
+ const resolvedProjector = resolveAssetHandle(world, candidate);
2069
+ if (!resolvedProjector.ok || resolvedProjector.value.kind !== "texture") {
2070
+ worldInternal._routeError(
2071
+ resolvedProjector.ok ? new RhiError({
2072
+ code: "asset-not-registered",
2073
+ expected: "SpotLight.projector resolves to a TextureAsset",
2074
+ hint: "load the authored projector TextureAsset before assigning SpotLight.projector",
2075
+ detail: { assetHandle: Number(candidate) }
2076
+ }) : resolvedProjector.error,
2077
+ { severity: Severity.Error, systemName: "RenderSystem.extract (spot-projector)" }
2078
+ );
2079
+ } else {
2080
+ projectorHandle = candidate;
2081
+ projectorAsset = resolvedProjector.value;
2082
+ projectorGuid = assets?.guidOf(projectorAsset) ?? `handle:${Number(candidate)}`;
2083
+ }
2303
2084
  }
2304
- const nearZ = Math.max(maxZ, lightSpaceMaxZFull);
2305
- const orthoProj = mat4.create();
2306
- mat4.orthographic(orthoProj, minX, maxX, minY, maxY, -nearZ, -minZ);
2307
- cascadeFits.push({
2308
- split: splits[cIdx] ?? sFar,
2309
- minX,
2310
- maxX,
2311
- minY,
2312
- maxY,
2313
- minZ,
2314
- maxZ
2315
- });
2316
- resultLightViewProjs.push(new Float32Array(mat4.multiply(mat4.create(), orthoProj, lightView)));
2317
- }
2318
- const projection = projectDirectionalShadow(
2319
- cascadeFits,
2320
- config.mapSize,
2321
- config.shadowFilter ?? 2,
2322
- config.shadowAngularRadius ?? 465e-5,
2323
- config.maxPenumbraTexels ?? 32
2324
- );
2325
- if (projection === void 0) return null;
2326
- return {
2327
- lightViewProj: resultLightViewProjs,
2328
- splitPlanes: projection.splitPlanes,
2329
- cascadeCount,
2330
- cascadeBlend: config.cascadeBlend,
2331
- shadowMapSize: config.mapSize,
2332
- directionalShadowQuality: projection.directionalShadowQuality
2333
- };
2334
- }
2335
- var ALL_VISIBLE = {
2336
- diagnostics: [],
2337
- hasAnyIntent: false,
2338
- hasAnyHiddenIntent: false,
2339
- get: () => void 0,
2340
- effective: () => "visible"
2341
- };
2342
- function collectCameraSnapshots(world) {
2343
- const worldInternal = createWorldInternalView(world);
2344
- const cameras = [];
2345
- const cameraQuery = world.query({ read: [Camera], optional: [MotionBlur], with: [Transform, GlobalTransform] }).unwrap();
2346
- for (const row of cameraQuery) {
2347
- const cam = row.get(Camera);
2348
- const motionBlur = row.get(MotionBlur);
2349
- const entity = row.entity;
2350
- const view = worldInternal._getArrayView(entity, GlobalTransform, "world");
2351
- if (view === void 0) continue;
2352
- const worldMat = new Float32Array(view);
2353
- const target = resolveCameraTarget(world, cam.target);
2354
- cameras.push({
2355
- entityKey: entity,
2356
- ...target === void 0 ? {} : { target },
2357
- historyVersion: cam.historyVersion,
2358
- position: mat4.getTranslation(vec3.create(), worldMat),
2359
- world: worldMat,
2360
- fov: cam.fov,
2361
- aspect: cam.aspect,
2362
- near: cam.near,
2363
- far: cam.far,
2364
- projection: cameraProjectionFromF32(cam.projection),
2365
- orthoLeft: cam.left,
2366
- orthoRight: cam.right,
2367
- orthoBottom: cam.bottom,
2368
- orthoTop: cam.top,
2369
- tonemap: tonemapFromF32(cam.tonemap),
2370
- exposure: cam.exposure,
2371
- whitePoint: cam.whitePoint,
2372
- antialias: antialiasFromF32(cam.antialias),
2373
- bloom: bloomEnabledFromF32(cam.bloom),
2374
- bloomThreshold: cam.bloomThreshold,
2375
- bloomIntensity: cam.bloomIntensity,
2376
- bloomBlurRadius: cam.bloomBlurRadius,
2377
- clearColor: [
2378
- cam.clearColor[0] ?? 0,
2379
- cam.clearColor[1] ?? 0,
2380
- cam.clearColor[2] ?? 0,
2381
- cam.clearColor[3] ?? 1
2382
- ],
2383
- ...motionBlur === void 0 ? {} : {
2384
- motionBlur: {
2385
- shutterAngle: motionBlur.shutterAngle,
2386
- maxRadiusPixels: motionBlur.maxRadiusPixels,
2387
- sampleCount: motionBlur.sampleCount
2388
- }
2085
+ const iesProfileData = iesProfileAsset?.ok && iesProfileAsset.value.kind === "ies-profile" ? new Uint8Array(iesProfileAsset.value.data) : void 0;
2086
+ const cookieProjection = cookieAsset?.ok && cookieAsset.value.kind === "texture" ? prepareCookieProjection(cookieAsset.value) : void 0;
2087
+ const cookieData = cookieProjection?.data;
2088
+ const projectorProjection = projectorAsset === void 0 ? void 0 : prepareCookieProjection(projectorAsset);
2089
+ const projectorData = projectorProjection?.data;
2090
+ const iesProfileHandle = s.iesProfile === void 0 ? void 0 : handleSlot(s.iesProfile);
2091
+ const cookieHandle = s.cookie === void 0 ? void 0 : handleSlot(s.cookie);
2092
+ let iesProfileSlice = iesProfileHandle === void 0 ? void 0 : iesSlices.get(iesProfileHandle);
2093
+ if (iesProfileSlice === void 0 && iesProfileHandle !== void 0 && iesProfileData !== void 0 && iesSlices.size < IES_SLICE_CAPACITY) {
2094
+ iesProfileSlice = iesSlices.size;
2095
+ iesSlices.set(iesProfileHandle, iesProfileSlice);
2096
+ }
2097
+ let cookieSlice = cookieHandle === void 0 ? void 0 : cookieSlices.get(cookieHandle);
2098
+ if (cookieSlice === void 0 && cookieHandle !== void 0 && cookieData !== void 0 && cookieSlices.size < COOKIE_SLICE_CAPACITY) {
2099
+ cookieSlice = cookieSlices.size;
2100
+ cookieSlices.set(cookieHandle, cookieSlice);
2101
+ }
2102
+ let projectorSlice = projectorHandle === void 0 ? void 0 : projectorSlices.get(projectorHandle) ?? cookieSlices.get(projectorHandle);
2103
+ if (projectorSlice === void 0 && projectorHandle !== void 0 && projectorData !== void 0 && cookieSlices.size + projectorSlices.size < COOKIE_SLICE_CAPACITY) {
2104
+ projectorSlice = cookieSlices.size + projectorSlices.size;
2105
+ projectorSlices.set(projectorHandle, projectorSlice);
2106
+ }
2107
+ const shadowIntensity = s.shadowIntensity;
2108
+ let lightViewProj2;
2109
+ let shadowAtlasTile = -1;
2110
+ if ((castShadow || hasProjector) && hasValidDirection) {
2111
+ const target = vec3.create(
2112
+ (position[0] ?? 0) + (dirN[0] ?? 0),
2113
+ (position[1] ?? 0) + (dirN[1] ?? 0),
2114
+ (position[2] ?? 0) + (dirN[2] ?? 0)
2115
+ );
2116
+ const fov = outerConeDeg * 2 * (Math.PI / 180);
2117
+ const proj = mat4.create();
2118
+ mat4.perspective(proj, fov, 1, sNearPlane, sFarPlane);
2119
+ const view = mat4.create();
2120
+ mat4.lookAt(view, position, target, vec3.create(0, 1, 0));
2121
+ lightViewProj2 = new Float32Array(16);
2122
+ mat4.multiply(lightViewProj2, proj, view);
2123
+ if (castShadow && spotTileNext < 4) {
2124
+ shadowAtlasTile = spotTileNext;
2125
+ spotTileNext += 1;
2126
+ }
2127
+ }
2128
+ spotSnapshots.push({
2129
+ kind: "spot",
2130
+ entity: row.entity,
2131
+ // D-6: position reflects world transform; direction stays sourced
2132
+ // from SpotLight.direction (NOT rotated by the parent).
2133
+ position,
2134
+ direction: dirN,
2135
+ color: vec3.create(
2136
+ (s.color[0] ?? 1) * intensity,
2137
+ (s.color[1] ?? 1) * intensity,
2138
+ (s.color[2] ?? 1) * intensity
2139
+ ),
2140
+ intensity,
2141
+ invRangeSquared: computeInvRangeSquared(range),
2142
+ cosInner: degToCos(innerConeDeg),
2143
+ cosOuter: degToCos(outerConeDeg),
2144
+ ...s.iesProfile === void 0 ? {} : {
2145
+ iesProfileHandle: handleSlot(s.iesProfile),
2146
+ ...iesProfileSlice === void 0 ? {} : { iesProfileSlice }
2147
+ },
2148
+ ...s.cookie === void 0 ? {} : {
2149
+ cookieHandle: handleSlot(s.cookie),
2150
+ ...cookieSlice === void 0 ? {} : { cookieSlice }
2151
+ },
2152
+ ...iesProfileData === void 0 ? {} : { iesProfileData },
2153
+ ...cookieData === void 0 ? {} : { cookieData },
2154
+ ...cookieProjection === void 0 ? {} : { cookieMatrix: cookieProjection.matrix },
2155
+ ...projectorSlice === void 0 ? {} : { projectorSlice },
2156
+ ...projectorData === void 0 ? {} : { projectorData },
2157
+ ...projectorProjection === void 0 ? {} : { projectorMatrix: projectorProjection.matrix },
2158
+ rollDeg: s.rollDeg,
2159
+ // ── shadow fields ──
2160
+ castShadow,
2161
+ lightViewProj: lightViewProj2,
2162
+ mapSize: sMapSize,
2163
+ nearPlane: sNearPlane,
2164
+ farPlane: sFarPlane,
2165
+ shadowAtlasTile,
2166
+ shadowIntensity,
2167
+ depthBias: s.depthBias,
2168
+ normalBias: s.normalBias,
2169
+ pcfKernelSize: s.pcfKernelSize,
2170
+ ...projectorHandle === void 0 || projectorAsset === void 0 || projectorGuid === void 0 ? {} : {
2171
+ projectorHandle,
2172
+ projectorAsset,
2173
+ projectorGuid,
2174
+ projectorGeneration: 1,
2175
+ projectorRevision: 1
2389
2176
  }
2390
2177
  });
2391
2178
  }
2392
- return cameras;
2393
- }
2394
- function collectCubeCameraSnapshots(world) {
2395
- const worldInternal = createWorldInternalView(world);
2396
- const snapshots = [];
2397
- const query = world.query({ read: [CubeCamera], with: [Transform, GlobalTransform] }).unwrap();
2398
- for (const row of query) {
2399
- const camera = row.get(CubeCamera);
2400
- const target = resolveCameraTarget(world, camera.target);
2401
- if (target === void 0) continue;
2402
- const view = worldInternal._getArrayView(row.entity, GlobalTransform, "world");
2403
- if (view === void 0) continue;
2404
- snapshots.push({
2405
- entityKey: row.entity,
2406
- target,
2407
- position: [view[12] ?? 0, view[13] ?? 0, view[14] ?? 0],
2408
- near: camera.near,
2409
- far: camera.far,
2410
- updateIntent: cubeCameraUpdateIntentFromF32(camera.updateIntent),
2411
- requestVersion: camera.requestVersion,
2412
- faceBudget: camera.faceBudget
2413
- });
2414
- }
2415
- return snapshots;
2416
- }
2417
- function collectReflectionProbeFacts(world) {
2418
- const worldInternal = createWorldInternalView(world);
2419
- const facts = [];
2420
- const query = world.query({ read: [ReflectionProbe], with: [Transform, GlobalTransform] }).unwrap();
2421
- for (const row of query) {
2422
- const probe = row.get(ReflectionProbe);
2423
- const view = worldInternal._getArrayView(row.entity, GlobalTransform, "world");
2424
- if (view === void 0) {
2179
+ const rectSnapshots = [];
2180
+ const rectLightQuery = world.query({
2181
+ read: [RectAreaLight],
2182
+ optional: [Transform, GlobalTransform]
2183
+ }).unwrap();
2184
+ for (const row of rectLightQuery) {
2185
+ const light = row.get(RectAreaLight);
2186
+ const validation = validateRectAreaLightData({
2187
+ intensity: light.intensity,
2188
+ color: light.color,
2189
+ width: light.width,
2190
+ height: light.height,
2191
+ range: light.range
2192
+ });
2193
+ if (!validation.ok) {
2194
+ worldInternal._routeError(validation.error, {
2195
+ severity: Severity.Error,
2196
+ systemName: "RenderSystem.extract (rect-area-light)"
2197
+ });
2425
2198
  continue;
2426
2199
  }
2427
- facts.push({
2428
- worldId: 0,
2429
- entityKey: row.entity,
2430
- center: [view[12] ?? 0, view[13] ?? 0, view[14] ?? 0],
2431
- halfExtents: [
2432
- probe.halfExtents[0] ?? 0,
2433
- probe.halfExtents[1] ?? 0,
2434
- probe.halfExtents[2] ?? 0
2435
- ],
2436
- priority: probe.priority,
2437
- intensity: probe.intensity,
2438
- resolution: probe.resolution,
2439
- revision: probe.invalidationVersion,
2440
- updateIntent: reflectionProbeUpdateIntentFromF32(probe.updateIntent),
2441
- invalidationVersion: probe.invalidationVersion
2200
+ const worldView = row.get(GlobalTransform);
2201
+ const worldMat = worldView === void 0 ? void 0 : new Float32Array(worldInternal._getArrayView(row.entity, GlobalTransform, "world") ?? []);
2202
+ const position = worldMat !== void 0 ? mat4.getTranslation(vec3.create(), worldMat) : vec3.create(0, 0, 0);
2203
+ const frame = buildRectAreaWorldFrame({
2204
+ center: position,
2205
+ axisX: [worldMat?.[0] ?? 1, worldMat?.[1] ?? 0, worldMat?.[2] ?? 0],
2206
+ axisY: [worldMat?.[4] ?? 0, worldMat?.[5] ?? 1, worldMat?.[6] ?? 0],
2207
+ width: light.width,
2208
+ height: light.height
2442
2209
  });
2443
- }
2444
- return facts;
2445
- }
2446
- function selectCameraRoles(world) {
2447
- const cameras = collectCameraSnapshots(world);
2448
- const displayEntities = cameras.filter((camera) => camera.target === void 0).map((camera) => camera.entityKey ?? 0);
2449
- const activeCameraIndex = selectActiveCameraIndex(
2450
- displayEntities,
2451
- getActiveCamera(world)?.entity
2452
- );
2453
- const displayEntityKey = displayEntities[activeCameraIndex] ?? displayEntities[0];
2454
- const display = displayEntityKey === void 0 ? [] : cameras.filter((camera) => camera.entityKey === displayEntityKey);
2455
- const candidates = cameras.map((camera) => ({
2456
- worldId: 0,
2457
- entityKey: camera.entityKey ?? 0,
2458
- ...camera.target === void 0 ? {} : { target: camera.target },
2459
- requestVersion: 0,
2460
- update: "continuous"
2461
- }));
2462
- const selected = selectCameraTargetViews(candidates, {
2463
- ...displayEntityKey === void 0 ? {} : { displayEntityKey },
2464
- budget: 1
2465
- });
2466
- const auxiliary = selected.auxiliary.flatMap(
2467
- (candidate) => cameras.filter((camera) => camera.entityKey === candidate.entityKey)
2468
- );
2469
- return { display, auxiliary };
2470
- }
2471
- function tonemapParams(camera) {
2472
- const bytes = new ArrayBuffer(16);
2473
- const floats = new Float32Array(bytes);
2474
- const integers = new Uint32Array(bytes);
2475
- floats[0] = camera.exposure;
2476
- floats[1] = camera.whitePoint;
2477
- integers[2] = tonemapToU32(camera.tonemap);
2478
- return new Uint8Array(bytes);
2479
- }
2480
- function prepareExtractContext(world, options = {}) {
2481
- const renderables = options.renderables ?? "full";
2482
- return {
2483
- assets: options.assets,
2484
- pipelineState: options.pipelineState,
2485
- materialSnapshotCache: options.materialSnapshotCache,
2486
- resourceOwnerFog: options.resourceOwnerFog,
2487
- cull: options.cull ?? "self",
2488
- cullCameras: options.cullCameras,
2489
- renderables,
2490
- visibility: renderables === "none" ? ALL_VISIBLE : resolveVisibility(world),
2491
- getMaterialShaderArtifact: options.getMaterialShaderArtifact
2492
- };
2493
- }
2494
- var fogQueries = /* @__PURE__ */ new WeakMap();
2495
- var fogLkgStates = /* @__PURE__ */ new WeakMap();
2496
- function fogState(world) {
2497
- const existing = fogLkgStates.get(world);
2498
- if (existing !== void 0) return existing;
2499
- const created = { lkg: void 0, failure: void 0 };
2500
- fogLkgStates.set(world, created);
2501
- return created;
2502
- }
2503
- function selectFogFrame(world) {
2504
- const fogCandidates = [];
2505
- let fogQuery = fogQueries.get(world);
2506
- if (fogQuery === void 0) {
2507
- fogQuery = world.query({ read: [Fog] }).unwrap();
2508
- fogQueries.set(world, fogQuery);
2509
- }
2510
- for (const row of fogQuery) {
2511
- const value = row.get(Fog);
2512
- fogCandidates.push({
2513
- entityKey: row.entity,
2514
- color: [value.color[0] ?? 0, value.color[1] ?? 0, value.color[2] ?? 0],
2515
- density: value.density,
2516
- heightFalloff: value.heightFalloff,
2517
- maxOpacity: value.maxOpacity
2210
+ rectSnapshots.push({
2211
+ kind: "rect-area",
2212
+ position,
2213
+ color: vec3.create(
2214
+ (light.color[0] ?? 1) * light.intensity,
2215
+ (light.color[1] ?? 1) * light.intensity,
2216
+ (light.color[2] ?? 1) * light.intensity
2217
+ ),
2218
+ intensity: light.intensity,
2219
+ invRangeSquared: computeInvRangeSquared(light.range),
2220
+ halfWidth: frame.halfWidth,
2221
+ halfHeight: frame.halfHeight,
2222
+ axisX: frame.axisX,
2223
+ axisY: frame.axisY
2518
2224
  });
2519
2225
  }
2520
- const fogSelection = selectEnvironment({
2521
- environments: [],
2522
- fogs: fogCandidates,
2523
- suns: [],
2524
- lane: "direct"
2525
- });
2526
- const state = fogState(world);
2527
- if (fogSelection.ok) {
2528
- state.lkg = fogSelection.value.fog;
2529
- state.failure = void 0;
2530
- return fogSelection.value.fog;
2531
- }
2532
- state.failure = {
2533
- code: fogSelection.error.code,
2534
- expected: fogSelection.error.expected,
2535
- hint: fogSelection.error.hint,
2536
- detail: Object.freeze({ ...fogSelection.error.detail })
2537
- };
2538
- routeWorldError(world, fogSelection.error, {
2539
- systemName: "RenderSystem.extract (fog-selection)"
2540
- });
2541
- if (state.lkg !== void 0) return state.lkg;
2542
- throw fogSelection.error;
2543
- }
2544
- function textureDigest(texture) {
2545
- let hash = 2166136261;
2546
- for (const byte of texture.data) hash = Math.imul(hash ^ byte, 16777619) >>> 0;
2547
- return `fnv1a:${hash.toString(16).padStart(8, "0")}`;
2548
- }
2549
- function extractVolumeSnapshot(world, assets, worldInternal) {
2550
- const query = world.query({ read: [VolumetricFog] }).unwrap();
2551
- const authorings = [];
2552
- let selectedLight;
2553
- let first;
2554
- for (const row of query) {
2555
- const source = row.get(VolumetricFog);
2556
- if (source.light === null) continue;
2557
- const selected = resolveSelectedVolumetricLight(world, source.light);
2558
- if (selected.status === "available") {
2559
- const pairSpot = source.spotLight === null ? void 0 : source.spotLight;
2560
- selectedLight ??= {
2561
- entity: selected.entity,
2562
- kind: selected.kind,
2563
- ...selected.kind === "point" ? { pointLightEntity: selected.entity } : {},
2564
- ...pairSpot === void 0 ? {} : { spotLightEntity: pairSpot }
2565
- };
2566
- }
2567
- const densityHandle = toShared(Math.round(Number(source.density)));
2568
- const resolved = resolveAssetHandle(world, densityHandle);
2569
- if (!resolved.ok || resolved.value.kind !== "texture") {
2570
- worldInternal._routeError(
2571
- resolved.ok ? new RhiError({
2572
- code: "asset-not-registered",
2573
- expected: "VolumetricFog.density resolves to a TextureAsset",
2574
- hint: "load a TextureAsset before assigning it to VolumetricFog.density",
2575
- detail: { assetHandle: Number(densityHandle) }
2576
- }) : resolved.error,
2577
- { severity: Severity.Error, systemName: "RenderSystem.extract (volumetric-density)" }
2578
- );
2579
- return { status: "degraded" };
2226
+ let volumeSpot;
2227
+ const extractedFog = volumetricFog;
2228
+ if (extractedFog !== void 0) {
2229
+ if (extractedFog.spotLightEntity !== void 0) {
2230
+ volumeSpot = spotSnapshots.find((light) => light.entity === extractedFog.spotLightEntity);
2231
+ } else if (extractedFog.lightKind === "spot") {
2232
+ volumeSpot = spotSnapshots.find((light) => light.entity === extractedFog.lightEntity);
2580
2233
  }
2581
- const densityAsset = resolved.value;
2582
- const guid = assets?.guidOf(densityAsset) ?? `handle:${Number(densityHandle)}`;
2583
- const shape = densityAsset.shape;
2584
- const density = shape.viewDimension === "3d" ? {
2585
- guid,
2586
- generation: 1,
2587
- shape: {
2588
- viewDimension: "3d",
2589
- extent: {
2590
- width: shape.extent.width,
2591
- height: shape.extent.height,
2592
- depth: shape.extent.depth
2593
- }
2594
- },
2595
- format: densityAsset.format,
2596
- colorSpace: densityAsset.colorSpace
2597
- } : {
2598
- guid,
2599
- generation: 1,
2600
- shape: {
2601
- viewDimension: shape.viewDimension,
2602
- extent: shape.viewDimension === "2d-array" ? {
2603
- width: shape.extent.width,
2604
- height: shape.extent.height,
2605
- layers: shape.extent.layers
2606
- } : { width: shape.extent.width, height: shape.extent.height }
2607
- },
2608
- format: densityAsset.format,
2609
- colorSpace: densityAsset.colorSpace
2610
- };
2611
- const authoring = {
2612
- light: source.light,
2613
- density,
2614
- bounds: {
2615
- min: [source.boundsMin[0] ?? 0, source.boundsMin[1] ?? 0, source.boundsMin[2] ?? 0],
2616
- max: [source.boundsMax[0] ?? 0, source.boundsMax[1] ?? 0, source.boundsMax[2] ?? 0]
2234
+ }
2235
+ if (volumetricFog !== void 0 && volumetricFog.status === "available" && volumeSpot?.projectorAsset !== void 0 && volumeSpot.projectorHandle !== void 0) {
2236
+ const projectorHandle = volumeSpot.projectorHandle;
2237
+ const projectorAsset = volumeSpot.projectorAsset;
2238
+ volumetricFog = {
2239
+ ...volumetricFog,
2240
+ projector: {
2241
+ guid: volumeSpot.projectorGuid ?? `handle:${Number(volumeSpot.projectorHandle)}`,
2242
+ generation: volumeSpot.projectorGeneration ?? 1,
2243
+ view: "2d",
2244
+ sampler: "linear-clamp-to-edge",
2245
+ projection: "spot-projection",
2246
+ revision: volumeSpot.projectorRevision ?? 1
2617
2247
  },
2618
- extinction: [source.extinction[0] ?? 0, source.extinction[1] ?? 0, source.extinction[2] ?? 0],
2619
- albedo: [source.albedo[0] ?? 0, source.albedo[1] ?? 0, source.albedo[2] ?? 0],
2620
- emission: [source.emission[0] ?? 0, source.emission[1] ?? 0, source.emission[2] ?? 0],
2621
- anisotropy: source.anisotropy,
2622
- maxDistance: source.maxDistance
2248
+ projectorHandle,
2249
+ projectorAsset
2623
2250
  };
2624
- authorings.push(authoring);
2625
- first ??= { densityHandle, densityAsset, guid };
2626
2251
  }
2627
- if (authorings.length === 0) return void 0;
2628
- const extracted = extractVolumetricFog(authorings);
2629
- if (!extracted.ok) {
2630
- worldInternal._routeError(extracted.error, {
2631
- severity: Severity.Error,
2632
- systemName: "RenderSystem.extract (volumetric-fog)"
2633
- });
2634
- return {
2635
- status: "degraded",
2636
- ...first === void 0 ? {} : {
2637
- densityHandle: first.densityHandle,
2638
- densityAsset: first.densityAsset,
2639
- guid: first.guid,
2640
- generation: 1,
2641
- digest: textureDigest(first.densityAsset),
2642
- ...selectedLight === void 0 ? {} : {
2643
- lightEntity: selectedLight.entity,
2644
- lightKind: selectedLight.kind,
2645
- ...selectedLight.pointLightEntity === void 0 ? {} : { pointLightEntity: selectedLight.pointLightEntity },
2646
- ...selectedLight.spotLightEntity === void 0 ? {} : { spotLightEntity: selectedLight.spotLightEntity }
2647
- }
2648
- }
2649
- };
2650
- }
2651
- if (extracted.value.status === "off" || first === void 0) return { status: "off" };
2652
- const selectedLightFields = selectedLight === void 0 ? {} : {
2653
- lightEntity: selectedLight.entity,
2654
- lightKind: selectedLight.kind,
2655
- ...selectedLight.pointLightEntity === void 0 ? {} : { pointLightEntity: selectedLight.pointLightEntity },
2656
- ...selectedLight.spotLightEntity === void 0 ? {} : { spotLightEntity: selectedLight.spotLightEntity }
2657
- };
2658
- return {
2659
- status: "available",
2660
- fog: extracted.value.fog,
2661
- densityHandle: first.densityHandle,
2662
- densityAsset: first.densityAsset,
2663
- guid: first.guid,
2664
- generation: 1,
2665
- digest: textureDigest(first.densityAsset),
2666
- ...selectedLightFields
2667
- };
2668
- }
2669
- function extractFrames(worlds, owner, assets, pipelineState, materialSnapshotCachesByWorld, options = {}) {
2670
- const cameraOwner = typeof owner === "number" ? owner : owner.cameraOwner;
2671
- const resourceOwner = typeof owner === "number" ? owner : owner.resourceOwner;
2672
- const skinPaletteAllocator = pipelineState?.skinPaletteAllocator ?? null;
2673
- if (skinPaletteAllocator !== null) {
2674
- skinPaletteAllocator.resetForFrame();
2675
- }
2676
- const failedWorlds = /* @__PURE__ */ new Set();
2677
- const resourceOwnerWorld = worlds[resourceOwner];
2678
- const resourceOwnerFog = resourceOwnerWorld === void 0 ? null : selectFogFrame(resourceOwnerWorld) ?? null;
2679
- const succeededFrames = [];
2680
- const succeededIndices = [];
2681
- const extractionOrder = Array.from({ length: worlds.length }, (_, wi) => wi).sort((a, b) => {
2682
- if (a === cameraOwner) return -1;
2683
- if (b === cameraOwner) return 1;
2684
- return a - b;
2685
- });
2686
- const framesByWorld = /* @__PURE__ */ new Map();
2687
- for (const wi of extractionOrder) {
2688
- const world = worlds[wi];
2689
- if (world === void 0 || failedWorlds.has(world)) continue;
2690
- try {
2691
- const isCameraOwner = wi === cameraOwner;
2692
- const cameraOwnerFrame2 = framesByWorld.get(cameraOwner);
2693
- const prepared = prepareExtractContext(world, {
2694
- ...assets !== void 0 ? { assets } : {},
2695
- ...pipelineState !== void 0 ? { pipelineState } : {},
2696
- resourceOwnerFog: wi === resourceOwner ? resourceOwnerFog : null,
2697
- ...materialSnapshotCachesByWorld === void 0 ? {} : {
2698
- materialSnapshotCache: materialSnapshotCachesByWorld.get(world) ?? (() => {
2699
- const cache = /* @__PURE__ */ new Map();
2700
- materialSnapshotCachesByWorld.set(world, cache);
2701
- return cache;
2702
- })()
2703
- },
2704
- cull: options.cull === "none" ? "none" : isCameraOwner ? "self" : "external",
2705
- ...options.getMaterialShaderArtifact === void 0 ? {} : { getMaterialShaderArtifact: options.getMaterialShaderArtifact },
2706
- ...cameraOwnerFrame2 === void 0 ? {} : { cullCameras: cameraOwnerFrame2.cameras },
2707
- renderables: options.renderables ?? "full"
2708
- });
2709
- const frame = extractFrame(world, prepared);
2710
- framesByWorld.set(wi, frame);
2711
- } catch (err6) {
2712
- try {
2713
- createWorldInternalView(world)._routeError(err6, {
2714
- severity: Severity.Error,
2715
- systemName: `RenderSystem.extractFrames(world[${wi}])`
2716
- });
2717
- } catch {
2718
- }
2719
- }
2720
- }
2721
- for (let wi = 0; wi < worlds.length; wi++) {
2722
- const frame = framesByWorld.get(wi);
2723
- if (frame === void 0) continue;
2724
- succeededFrames.push(frame);
2725
- succeededIndices.push(wi);
2726
- }
2727
- const renderables = [];
2728
- const dispatchEntries = [];
2729
- const visibilitySnapshots = [];
2730
- const featureVisibilitySnapshots = [];
2731
- const hiddenEntityReports = [];
2732
- for (let fi = 0; fi < succeededFrames.length; fi++) {
2733
- const f = succeededFrames[fi];
2734
- const wId = succeededIndices[fi];
2735
- if (f === void 0 || wId === void 0) continue;
2736
- const base = renderables.length;
2737
- const visibilitySnapshot = f.visibilitySnapshots[0];
2738
- const world = worlds[wId];
2739
- if (visibilitySnapshot !== void 0) {
2740
- visibilitySnapshots.push(visibilitySnapshot);
2741
- if (world !== void 0)
2742
- featureVisibilitySnapshots.push({ world, snapshot: visibilitySnapshot });
2743
- }
2744
- hiddenEntityReports.push(...f.hiddenEntityReports);
2745
- for (const r of f.renderables) {
2746
- renderables.push({
2747
- ...r,
2748
- worldId: wId,
2749
- ...r.pointsLines === void 0 ? {} : { pointsLines: { ...r.pointsLines, worldId: wId } }
2750
- });
2751
- }
2752
- for (const d of f.dispatch) {
2753
- dispatchEntries.push({ ...d, renderableIndex: (d.renderableIndex ?? 0) + base });
2754
- }
2755
- }
2756
- dispatchEntries.sort((a, b) => (a.queue ?? 0) - (b.queue ?? 0));
2757
- const point = [];
2758
- const spot = [];
2759
- const rect = [];
2760
- let directional;
2761
- let directionalCount = 0;
2762
2252
  let lightViewProj;
2763
2253
  let splitPlanes;
2764
2254
  let cascadeCount;
2765
2255
  let cascadeBlend;
2766
2256
  let shadowMapSize;
2767
- let depthBias;
2768
- let normalBias;
2769
2257
  let directionalShadowQuality;
2770
- let directionalShadowError;
2771
- const pointShadow = [];
2772
2258
  let directionalCsmConfig;
2773
2259
  let directionalCsmDirection;
2774
- for (const f of succeededFrames) {
2775
- for (const p of f.lights.point) point.push(p);
2776
- for (const s of f.lights.spot) spot.push(s);
2777
- for (const r of f.lights.rect) rect.push(r);
2778
- for (const ps of f.lights.pointShadow) pointShadow.push(ps);
2779
- if (directional === void 0 && f.lights.directional !== void 0) {
2780
- directional = f.lights.directional;
2781
- lightViewProj = f.lights.lightViewProj;
2782
- splitPlanes = f.lights.splitPlanes;
2783
- cascadeCount = f.lights.cascadeCount;
2784
- cascadeBlend = f.lights.cascadeBlend;
2785
- shadowMapSize = f.lights.shadowMapSize;
2786
- depthBias = f.lights.depthBias;
2787
- normalBias = f.lights.normalBias;
2788
- directionalShadowQuality = f.lights.directionalShadowQuality;
2789
- directionalShadowError = f.lights.directionalShadowError;
2790
- directionalCsmConfig = f.lights.directionalCsmConfig;
2791
- directionalCsmDirection = f.lights.directionalCsmDirection;
2260
+ const cam0 = cameras[0];
2261
+ const cameraData = cam0 !== void 0 ? {
2262
+ world: cam0.world,
2263
+ fov: cam0.fov,
2264
+ aspect: cam0.aspect,
2265
+ near: cam0.near,
2266
+ far: cam0.far,
2267
+ projection: cam0.projection,
2268
+ orthoLeft: cam0.orthoLeft,
2269
+ orthoRight: cam0.orthoRight,
2270
+ orthoBottom: cam0.orthoBottom,
2271
+ orthoTop: cam0.orthoTop
2272
+ } : void 0;
2273
+ if (directional !== void 0 && firstHitCastShadow !== false && directionalShadowError === void 0) {
2274
+ const dirSnapshot = directional;
2275
+ const sf = firstHitShadowFields;
2276
+ if (sf !== void 0) {
2277
+ directionalCsmConfig = {
2278
+ cascadeCount: sf.cascadeCount,
2279
+ splitLambda: sf.splitLambda,
2280
+ cascadeBlend: sf.cascadeBlend,
2281
+ mapSize: sf.mapSize,
2282
+ shadowDistance: sf.shadowDistance,
2283
+ shadowFilter: sf.shadowFilter,
2284
+ shadowAngularRadius: sf.shadowAngularRadius,
2285
+ maxPenumbraTexels: sf.maxPenumbraTexels
2286
+ };
2287
+ directionalCsmDirection = dirSnapshot.direction;
2288
+ const csm = computeDirectionalCsm(dirSnapshot.direction, directionalCsmConfig, cameraData);
2289
+ cascadeCount = Math.round(sf.cascadeCount);
2290
+ cascadeBlend = sf.cascadeBlend;
2291
+ shadowMapSize = sf.mapSize;
2292
+ if (csm !== null) {
2293
+ lightViewProj = csm.lightViewProj;
2294
+ splitPlanes = csm.splitPlanes;
2295
+ directionalShadowQuality = csm.directionalShadowQuality;
2296
+ } else {
2297
+ const sNear = cameraData?.near ?? 0.1;
2298
+ const splits = pssmSplit(sNear, sf.shadowDistance, cascadeCount, sf.splitLambda);
2299
+ const padded = new Float32Array(16);
2300
+ for (let i = 0; i < splits.length; i++) padded[i * 4] = splits[i] ?? 0;
2301
+ splitPlanes = padded;
2302
+ }
2303
+ directionalShadowQuality = directionalShadowQualityFromF32(
2304
+ sf.shadowFilter,
2305
+ sf.shadowAngularRadius,
2306
+ sf.maxPenumbraTexels
2307
+ );
2792
2308
  }
2793
- directionalCount += f.lights.directionalCount;
2794
- }
2795
- let cameraOwnerFrame;
2796
- let resourceOwnerFrame;
2797
- for (let fi = 0; fi < succeededFrames.length; fi++) {
2798
- if (succeededIndices[fi] === cameraOwner) cameraOwnerFrame = succeededFrames[fi];
2799
- if (succeededIndices[fi] === resourceOwner) resourceOwnerFrame = succeededFrames[fi];
2800
2309
  }
2801
- const cameras = cameraOwnerFrame !== void 0 ? [...cameraOwnerFrame.cameras] : [];
2802
- const auxiliaryCameras = cameraOwnerFrame !== void 0 ? [...cameraOwnerFrame.auxiliaryCameras] : [];
2803
- const cubeCameras = cameraOwnerFrame !== void 0 ? [...cameraOwnerFrame.cubeCameras] : [];
2804
- const mergeCam = cameras[0];
2805
- if (directionalCsmConfig !== void 0 && directionalCsmDirection !== void 0) {
2806
- const mergeCameraData = mergeCam !== void 0 ? {
2807
- world: mergeCam.world,
2808
- fov: mergeCam.fov,
2809
- aspect: mergeCam.aspect,
2810
- near: mergeCam.near,
2811
- far: mergeCam.far,
2812
- projection: mergeCam.projection,
2813
- orthoLeft: mergeCam.orthoLeft,
2814
- orthoRight: mergeCam.orthoRight,
2815
- orthoBottom: mergeCam.orthoBottom,
2816
- orthoTop: mergeCam.orthoTop
2817
- } : void 0;
2818
- const csm = computeDirectionalCsm(
2819
- directionalCsmDirection,
2820
- directionalCsmConfig,
2821
- mergeCameraData
2822
- );
2823
- if (csm !== null) {
2824
- lightViewProj = csm.lightViewProj;
2825
- splitPlanes = csm.splitPlanes;
2826
- cascadeCount = csm.cascadeCount;
2827
- cascadeBlend = csm.cascadeBlend;
2828
- shadowMapSize = csm.shadowMapSize;
2829
- directionalShadowQuality = csm.directionalShadowQuality;
2310
+ const paddedSplitPlanes = new Float32Array(16);
2311
+ if (splitPlanes !== void 0) {
2312
+ for (let i = 0; i < splitPlanes.length; i++) {
2313
+ paddedSplitPlanes[i] = splitPlanes[i] ?? 0;
2830
2314
  }
2831
2315
  }
2832
- const lights = {
2833
- directional,
2834
- directionalCount,
2835
- point,
2836
- spot,
2837
- rect,
2838
- lightViewProj,
2839
- splitPlanes,
2840
- cascadeCount,
2841
- cascadeBlend,
2842
- shadowMapSize,
2843
- depthBias,
2844
- normalBias,
2316
+ const pointShadowSnapshots = [];
2317
+ {
2318
+ const pointShadowQuery = world.query({ read: [Transform, GlobalTransform, PointLightShadow], with: [PointLight] }).unwrap();
2319
+ for (const row of pointShadowQuery) {
2320
+ const ps = row.get(PointLightShadow);
2321
+ const wRow = row.get(GlobalTransform).world;
2322
+ if (wRow === void 0) continue;
2323
+ const px = wRow[12] ?? 0;
2324
+ const py = wRow[13] ?? 0;
2325
+ const pz = wRow[14] ?? 0;
2326
+ const lightPos = vec3.create(px, py, pz);
2327
+ const mapSize = ps.mapSize;
2328
+ const nearPlane = ps.nearPlane;
2329
+ const farPlane = ps.farPlane;
2330
+ const layer = pointShadowSnapshots.length < SHADOW_ATLAS_DEFAULT_LAYERS ? pointShadowSnapshots.length : -1;
2331
+ const matrices = buildPointShadowMatrices(lightPos, nearPlane, farPlane);
2332
+ const packed = new Float32Array(96);
2333
+ for (let f = 0; f < 6; f++) {
2334
+ const m = matrices[f];
2335
+ if (m === void 0) continue;
2336
+ for (let k = 0; k < 16; k++) {
2337
+ packed[f * 16 + k] = m[k] ?? 0;
2338
+ }
2339
+ }
2340
+ pointShadowSnapshots.push({
2341
+ entity: row.entity,
2342
+ position: lightPos,
2343
+ mapSize,
2344
+ nearPlane,
2345
+ farPlane,
2346
+ shadowAtlasLayer: layer,
2347
+ shadowMatrices: packed
2348
+ });
2349
+ }
2350
+ }
2351
+ if (pointShadowSnapshots.length > 0) {
2352
+ const shadowByEntity = /* @__PURE__ */ new Map();
2353
+ for (const ps of pointShadowSnapshots) shadowByEntity.set(ps.entity, ps);
2354
+ for (let i = 0; i < pointSnapshots.length; i++) {
2355
+ const entityId = pointSnapshotEntities[i] ?? 0;
2356
+ const ps = shadowByEntity.get(entityId);
2357
+ if (ps !== void 0) {
2358
+ pointSnapshots[i] = {
2359
+ ...pointSnapshots[i],
2360
+ shadowAtlasLayer: ps.shadowAtlasLayer,
2361
+ shadowNear: ps.nearPlane,
2362
+ shadowFar: ps.farPlane
2363
+ };
2364
+ }
2365
+ }
2366
+ }
2367
+ const lights = {
2368
+ directional,
2369
+ directionalCount,
2370
+ point: pointSnapshots,
2371
+ spot: spotSnapshots,
2372
+ rect: rectSnapshots,
2373
+ lightViewProj,
2374
+ splitPlanes: splitPlanes !== void 0 ? paddedSplitPlanes : void 0,
2375
+ cascadeCount,
2376
+ cascadeBlend,
2377
+ shadowMapSize,
2378
+ depthBias: firstHitCastShadow !== false ? firstHitShadowFields?.depthBias : void 0,
2379
+ normalBias: firstHitCastShadow !== false ? firstHitShadowFields?.normalBias : void 0,
2845
2380
  directionalShadowQuality,
2846
2381
  directionalShadowError,
2847
- pointShadow,
2382
+ pointShadow: pointShadowSnapshots,
2383
+ // bug-20260710-editor-cross-world-shadow: raw CSM config + light direction
2384
+ // so the merge layer can recompute matrices against the surfaced camera.
2848
2385
  directionalCsmConfig,
2849
2386
  directionalCsmDirection
2850
2387
  };
2851
- const volumetricFog = resourceOwnerFrame?.volumetricFog;
2852
- const skylight = resourceOwnerFrame?.skylight;
2853
- const skylightCount = resourceOwnerFrame?.skylightCount ?? 0;
2854
- const skybox = resourceOwnerFrame?.skybox;
2855
- const skyboxCount = resourceOwnerFrame?.skyboxCount ?? 0;
2856
- const fog = resourceOwnerFrame?.fog;
2857
- const fogFailure = resourceOwnerFrame?.fogFailure;
2858
- const environment = resourceOwnerFrame?.environment;
2859
- const lightProbes = succeededFrames.flatMap((frame) => frame.lightProbes ?? []);
2860
- const postProcessParams = new Map(resourceOwnerFrame?.postProcessParams);
2861
- const OUTPUT_TRANSFORM_PARAM_KEY = STANDARD_OUTPUT_TRANSFORM_FEATURE_ID;
2862
- const cameraTonemapParam = cameraOwnerFrame?.postProcessParams.get(OUTPUT_TRANSFORM_PARAM_KEY);
2863
- if (cameraTonemapParam !== void 0) {
2864
- postProcessParams.set(OUTPUT_TRANSFORM_PARAM_KEY, cameraTonemapParam);
2865
- } else {
2866
- postProcessParams.delete(OUTPUT_TRANSFORM_PARAM_KEY);
2867
- }
2868
- const frustumStats = {
2869
- culled: succeededFrames.reduce((s, f) => s + f.frustumStats.culled, 0),
2870
- total: succeededFrames.reduce((s, f) => s + f.frustumStats.total, 0)
2871
- };
2872
- const visibilityStats = {
2873
- explicitlyHidden: succeededFrames.reduce((s, f) => s + f.visibilityStats.explicitlyHidden, 0)
2874
- };
2875
- const reflectionProbes = succeededFrames.flatMap((f, index) => {
2876
- const worldId = succeededIndices[index] ?? 0;
2877
- return (f.reflectionProbes ?? []).map((probe) => ({ ...probe, worldId }));
2878
- });
2879
- return {
2880
- cameras,
2881
- auxiliaryCameras,
2882
- cubeCameras,
2883
- reflectionProbes,
2884
- lights,
2885
- ...volumetricFog === void 0 ? {} : { volumetricFog: { ...volumetricFog, worldId: resourceOwner } },
2886
- renderables,
2887
- dispatch: dispatchEntries,
2888
- skylight,
2889
- skylightCount,
2890
- lightProbes,
2891
- skybox,
2892
- skyboxCount,
2893
- fog,
2894
- ...fogFailure === void 0 ? {} : { fogFailure },
2895
- environment,
2896
- environmentReady: resourceOwnerFrame !== void 0 && environment !== void 0,
2897
- frustumStats,
2898
- visibilityStats,
2899
- postProcessParams,
2900
- visibilitySnapshots,
2901
- featureVisibilitySnapshots,
2902
- hiddenEntityReports
2903
- };
2904
- }
2905
- function hasFiniteOrderedLocalAabb(aabb) {
2906
- if (aabb === void 0 || aabb.length !== 6) return false;
2907
- const minX = aabb[0];
2908
- const minY = aabb[1];
2909
- const minZ = aabb[2];
2910
- const maxX = aabb[3];
2911
- const maxY = aabb[4];
2912
- const maxZ = aabb[5];
2913
- if (minX === void 0 || minY === void 0 || minZ === void 0 || maxX === void 0 || maxY === void 0 || maxZ === void 0) {
2914
- return false;
2915
- }
2916
- if (!Number.isFinite(minX) || !Number.isFinite(minY) || !Number.isFinite(minZ) || !Number.isFinite(maxX) || !Number.isFinite(maxY) || !Number.isFinite(maxZ)) {
2917
- return false;
2918
- }
2919
- return minX <= maxX && minY <= maxY && minZ <= maxZ;
2920
- }
2921
- function morphSnapshotFor(mesh, weights) {
2922
- const targets = mesh.morphTargets;
2923
- if (targets === void 0 || targets.length === 0 || weights === void 0) return void 0;
2924
- if (weights.length !== targets.length) return void 0;
2925
- const firstPositions = targets[0]?.position;
2926
- if (firstPositions === void 0 || firstPositions.length === 0 || firstPositions.length % 3 !== 0) {
2927
- return void 0;
2928
- }
2929
- const vertexCount = firstPositions.length / 3;
2930
- if (mesh.vertices.length % vertexCount !== 0) return void 0;
2931
- for (let targetIndex = 0; targetIndex < targets.length; targetIndex += 1) {
2932
- const target = targets[targetIndex];
2933
- if (target?.position?.length !== firstPositions.length) return void 0;
2934
- }
2935
- const copied = new Float32Array(weights.length);
2936
- for (let index = 0; index < weights.length; index += 1) {
2937
- const weight = weights[index] ?? Number.NaN;
2938
- if (!Number.isFinite(weight)) return void 0;
2939
- copied[index] = weight;
2940
- }
2941
- return { weights: copied, targetCount: targets.length };
2942
- }
2943
- function extractFrame(world, context) {
2944
- const {
2945
- assets,
2946
- pipelineState,
2947
- materialSnapshotCache: persistentMaterialSnapshotCache,
2948
- cull: cullMode,
2949
- resourceOwnerFog,
2950
- getMaterialShaderArtifact
2951
- } = context;
2952
- const fog = resourceOwnerFog === void 0 ? selectFogFrame(world) : resourceOwnerFog ?? void 0;
2953
- const fogFailure = fogState(world).failure;
2954
- const visibility = context.visibility.hasAnyHiddenIntent ? context.visibility : void 0;
2955
- const skinPaletteAllocator = pipelineState?.skinPaletteAllocator ?? null;
2956
- const directionalLightQuery = world.query({ read: [DirectionalLight] }).unwrap();
2957
- const worldInternal = createWorldInternalView(world);
2958
- let volumetricFog = extractVolumeSnapshot(world, assets, worldInternal);
2959
- const cameraRoles = selectCameraRoles(world);
2960
- const cameras = [...cameraRoles.display];
2961
- const auxiliaryCameras = cameraRoles.auxiliary;
2962
- const cubeCameras = collectCubeCameraSnapshots(world);
2963
- const reflectionProbes = collectReflectionProbeFacts(world);
2964
- let directional;
2965
- let directionalCount = 0;
2966
- let firstHitCastShadow;
2967
- let firstHitShadowFields;
2968
- for (const row of directionalLightQuery) {
2969
- const l = row.get(DirectionalLight);
2970
- directionalCount += 1;
2971
- const intensity = l.intensity;
2972
- const snapshot = {
2973
- kind: "directional",
2974
- entity: row.entity,
2975
- direction: vec3.create(l.direction[0] ?? 0, l.direction[1] ?? -1, l.direction[2] ?? 0),
2976
- color: vec3.create(
2977
- (l.color[0] ?? 1) * intensity,
2978
- (l.color[1] ?? 1) * intensity,
2979
- (l.color[2] ?? 1) * intensity
2980
- ),
2981
- intensity
2982
- };
2983
- if (directional === void 0) {
2984
- directional = snapshot;
2985
- firstHitCastShadow = l.castShadow;
2986
- firstHitShadowFields = {
2987
- cascadeCount: l.cascadeCount,
2988
- splitLambda: l.splitLambda,
2989
- cascadeBlend: l.cascadeBlend,
2990
- mapSize: l.mapSize,
2991
- depthBias: l.depthBias,
2992
- normalBias: l.normalBias,
2993
- shadowDistance: l.shadowDistance,
2994
- shadowFilter: l.shadowFilter,
2995
- shadowAngularRadius: l.shadowAngularRadius,
2996
- maxPenumbraTexels: l.maxPenumbraTexels
2388
+ const skylightQuery = world.query({ read: [Skylight] }).unwrap();
2389
+ let skylight;
2390
+ let skylightCount = 0;
2391
+ for (const row of skylightQuery) {
2392
+ const s = row.get(Skylight);
2393
+ const equirectRaw = s.equirect;
2394
+ const intensity = s.intensity;
2395
+ const colorR = s.color[0] ?? 1;
2396
+ const colorG = s.color[1] ?? 1;
2397
+ const colorB = s.color[2] ?? 1;
2398
+ const rotation = [
2399
+ s.rotation[0] ?? 0,
2400
+ s.rotation[1] ?? 0,
2401
+ s.rotation[2] ?? 0,
2402
+ s.rotation[3] ?? 1
2403
+ ];
2404
+ skylightCount += 1;
2405
+ if (skylight === void 0) {
2406
+ skylight = {
2407
+ equirectHandle: equirectRaw !== void 0 ? Math.round(equirectRaw) : 0,
2408
+ color: [colorR, colorG, colorB],
2409
+ intensity,
2410
+ rotation,
2411
+ // w19: winning entity handle for the multi-Skylight once-warn (F-8).
2412
+ entityHandle: row.entity
2997
2413
  };
2998
2414
  }
2999
2415
  }
3000
- let directionalShadowError;
3001
- if (directional !== void 0 && firstHitShadowFields !== void 0) {
3002
- const validation = validateDirectionalLightData({
3003
- direction: directional.direction,
3004
- castShadow: firstHitCastShadow,
3005
- ...firstHitShadowFields
2416
+ const lightProbeQuery = world.query({ read: [LightProbe, GlobalTransform] }).unwrap();
2417
+ const lightProbes = [];
2418
+ for (const row of lightProbeQuery) {
2419
+ const probe = row.get(LightProbe);
2420
+ const validation = validateLightProbeData({
2421
+ irradiance: probe.irradiance,
2422
+ radius: probe.radius
3006
2423
  });
3007
2424
  if (!validation.ok) {
3008
- directionalShadowError = validation.error;
3009
- worldInternal._routeError(directionalShadowError, {
2425
+ worldInternal._routeError(validation.error, {
3010
2426
  severity: Severity.Error,
3011
- systemName: "RenderSystem.extract (directional-shadow)"
2427
+ systemName: "RenderSystem.extract (light-probe)"
3012
2428
  });
2429
+ continue;
3013
2430
  }
2431
+ const transform = row.get(GlobalTransform);
2432
+ lightProbes.push({
2433
+ identity: String(row.entity),
2434
+ position: [transform.world[12] ?? 0, transform.world[13] ?? 0, transform.world[14] ?? 0],
2435
+ radius: probe.radius,
2436
+ irradiance: new Float32Array(probe.irradiance),
2437
+ admitted: true
2438
+ });
3014
2439
  }
3015
- const pointSnapshots = [];
3016
- const pointSnapshotEntities = [];
3017
- const pointLightQuery = world.query({
3018
- read: [PointLight],
3019
- optional: [Transform, GlobalTransform]
3020
- }).unwrap();
3021
- for (const row of pointLightQuery) {
3022
- const p = row.get(PointLight);
3023
- const hasTransform = row.get(GlobalTransform) !== void 0;
3024
- const intensity = p.intensity;
3025
- const range = p.range;
3026
- const entityId = row.entity;
3027
- let worldMat;
3028
- if (hasTransform) {
3029
- const view = worldInternal._getArrayView(entityId, GlobalTransform, "world");
3030
- if (view !== void 0) worldMat = new Float32Array(view);
2440
+ const skyboxQuery = world.query({ read: [SkyboxBackground] }).unwrap();
2441
+ let skybox;
2442
+ let skyboxCount = 0;
2443
+ for (const row of skyboxQuery) {
2444
+ const s = row.get(SkyboxBackground);
2445
+ const equirectRaw = s.equirect;
2446
+ const modeRaw = s.mode;
2447
+ const rotation = [
2448
+ s.rotation[0] ?? 0,
2449
+ s.rotation[1] ?? 0,
2450
+ s.rotation[2] ?? 0,
2451
+ s.rotation[3] ?? 1
2452
+ ];
2453
+ skyboxCount += 1;
2454
+ if (skybox === void 0 && equirectRaw !== void 0) {
2455
+ skybox = {
2456
+ equirectHandle: Math.round(equirectRaw),
2457
+ mode: modeRaw,
2458
+ rotation,
2459
+ // w19: winning entity handle for the multi-SkyboxBackground warn (F-8).
2460
+ entityHandle: row.entity
2461
+ };
3031
2462
  }
3032
- const position = worldMat !== void 0 ? mat4.getTranslation(vec3.create(), worldMat) : vec3.create(0, 0, 0);
3033
- pointSnapshots.push({
3034
- kind: "point",
3035
- entity: entityId,
3036
- position,
3037
- color: vec3.create(
3038
- (p.color[0] ?? 1) * intensity,
3039
- (p.color[1] ?? 1) * intensity,
3040
- (p.color[2] ?? 1) * intensity
3041
- ),
3042
- intensity,
3043
- invRangeSquared: computeInvRangeSquared(range)
2463
+ }
2464
+ const environmentCandidates = [];
2465
+ const imageEnvironment = skylight?.equirectHandle !== void 0 && skylight.equirectHandle > 0 ? skylight : skybox?.equirectHandle !== void 0 && skybox.equirectHandle > 0 ? skybox : void 0;
2466
+ if (imageEnvironment !== void 0) {
2467
+ environmentCandidates.push({
2468
+ kind: "image",
2469
+ entityKey: imageEnvironment.entityHandle,
2470
+ sourceKey: `equirect:${imageEnvironment.equirectHandle}`
3044
2471
  });
3045
- pointSnapshotEntities.push(entityId);
3046
2472
  }
3047
- const spotSnapshots = [];
3048
- const spotLightQuery = world.query({
3049
- read: [SpotLight],
3050
- optional: [Transform, GlobalTransform]
3051
- }).unwrap();
3052
- let spotTileNext = 0;
3053
- const iesSlices = /* @__PURE__ */ new Map();
3054
- const cookieSlices = /* @__PURE__ */ new Map();
3055
- const projectorSlices = /* @__PURE__ */ new Map();
3056
- for (const row of spotLightQuery) {
3057
- const s = row.get(SpotLight);
3058
- const hasTransform = row.get(GlobalTransform) !== void 0;
3059
- const intensity = s.intensity;
3060
- const range = s.range;
3061
- const innerConeDeg = s.innerConeDeg;
3062
- const outerConeDeg = s.outerConeDeg;
3063
- let worldMat;
3064
- if (hasTransform) {
3065
- const entity = row.entity;
3066
- const view = worldInternal._getArrayView(entity, GlobalTransform, "world");
3067
- if (view !== void 0) worldMat = new Float32Array(view);
3068
- }
3069
- const position = worldMat !== void 0 ? mat4.getTranslation(vec3.create(), worldMat) : vec3.create(0, 0, 0);
3070
- const dir = vec3.create(s.direction[0] ?? 0, s.direction[1] ?? -1, s.direction[2] ?? 0);
3071
- const dirLen = Math.sqrt(
3072
- (dir[0] ?? 0) * (dir[0] ?? 0) + (dir[1] ?? 0) * (dir[1] ?? 0) + (dir[2] ?? 0) * (dir[2] ?? 0)
3073
- );
3074
- const EPSILON = 1e-6;
3075
- const hasValidDirection = dirLen > EPSILON;
3076
- if (!hasValidDirection) {
3077
- worldInternal._routeError(
3078
- new SpawnLightInvalidBoundsError("SpotLight", "direction", [
3079
- dir[0] ?? 0,
3080
- dir[1] ?? 0,
3081
- dir[2] ?? 0
3082
- ]),
3083
- {
3084
- severity: Severity.Error,
3085
- systemName: "RenderSystem.extract (spot-direction)"
3086
- }
3087
- );
2473
+ const atmosphereQuery = world.query({ read: [Atmosphere] }).unwrap();
2474
+ for (const row of atmosphereQuery) {
2475
+ const value = row.get(Atmosphere);
2476
+ const atmosphere = {
2477
+ turbidity: value.turbidity,
2478
+ rayleigh: value.rayleigh,
2479
+ mieCoefficient: value.mieCoefficient,
2480
+ mieDirectionalG: value.mieDirectionalG,
2481
+ sunAngularRadius: value.sunAngularRadius
2482
+ };
2483
+ environmentCandidates.push({
2484
+ kind: "atmosphere",
2485
+ entityKey: row.entity,
2486
+ sourceKey: `atmosphere:${row.entity}`,
2487
+ atmosphere
2488
+ });
2489
+ }
2490
+ const directionalDirection = directional?.direction;
2491
+ const directionalLength = directionalDirection === void 0 ? 0 : Math.hypot(
2492
+ directionalDirection[0] ?? 0,
2493
+ directionalDirection[1] ?? 0,
2494
+ directionalDirection[2] ?? 0
2495
+ );
2496
+ const sun = directional === void 0 || directionalLength <= 1e-6 ? [] : [
2497
+ {
2498
+ entityKey: 0,
2499
+ direction: [
2500
+ -(directionalDirection?.[0] ?? 0) / directionalLength,
2501
+ -(directionalDirection?.[1] ?? 0) / directionalLength,
2502
+ -(directionalDirection?.[2] ?? 0) / directionalLength
2503
+ ],
2504
+ color: [
2505
+ directional.color[0] ?? 0,
2506
+ directional.color[1] ?? 0,
2507
+ directional.color[2] ?? 0
2508
+ ],
2509
+ intensity: directional.intensity
3088
2510
  }
3089
- const dirN = vec3.create(
3090
- hasValidDirection ? (dir[0] ?? 0) / dirLen : dir[0] ?? 0,
3091
- hasValidDirection ? (dir[1] ?? 0) / dirLen : dir[1] ?? 0,
3092
- hasValidDirection ? (dir[2] ?? 0) / dirLen : dir[2] ?? 0
3093
- );
3094
- const castShadow = s.castShadow;
3095
- const sMapSize = s.mapSize;
3096
- const sNearPlane = s.nearPlane;
3097
- const sFarPlane = s.farPlane;
3098
- const iesProfileAsset = s.iesProfile === void 0 ? void 0 : resolveAssetHandle(world, s.iesProfile);
3099
- const cookieAsset = s.cookie === void 0 ? void 0 : resolveAssetHandle(world, s.cookie);
3100
- const hasProjector = Number(s.projector) > 0;
3101
- let projectorHandle;
3102
- let projectorAsset;
3103
- let projectorGuid;
3104
- if (hasProjector) {
3105
- const candidate = toShared(Math.round(Number(s.projector)));
3106
- const resolvedProjector = resolveAssetHandle(world, candidate);
3107
- if (!resolvedProjector.ok || resolvedProjector.value.kind !== "texture") {
3108
- worldInternal._routeError(
3109
- resolvedProjector.ok ? new RhiError({
3110
- code: "asset-not-registered",
3111
- expected: "SpotLight.projector resolves to a TextureAsset",
3112
- hint: "load the authored projector TextureAsset before assigning SpotLight.projector",
3113
- detail: { assetHandle: Number(candidate) }
3114
- }) : resolvedProjector.error,
3115
- { severity: Severity.Error, systemName: "RenderSystem.extract (spot-projector)" }
2511
+ ];
2512
+ const environmentSelection = selectEnvironment({
2513
+ environments: environmentCandidates,
2514
+ fogs: fog === void 0 ? [] : [fog],
2515
+ suns: sun,
2516
+ lane: "direct"
2517
+ });
2518
+ if (!environmentSelection.ok) {
2519
+ routeWorldError(world, environmentSelection.error, {
2520
+ systemName: "RenderSystem.extract (environment-selection)"
2521
+ });
2522
+ throw environmentSelection.error;
2523
+ }
2524
+ const environment = environmentSelection.value;
2525
+ const frustumPlanes = [];
2526
+ const cullingCameras = cullMode === "external" ? context.cullCameras ?? [] : cameras;
2527
+ if (cullMode !== "none") {
2528
+ for (const cam of cullingCameras) {
2529
+ if (cam.projection === "perspective" && (cam.fov <= 0 || cam.aspect <= 0)) {
2530
+ frustumPlanes.push(new Float32Array(0));
2531
+ continue;
2532
+ }
2533
+ if (cam.near >= cam.far) {
2534
+ frustumPlanes.push(new Float32Array(0));
2535
+ continue;
2536
+ }
2537
+ const proj = mat4.create();
2538
+ if (cam.projection === "orthographic") {
2539
+ mat4.orthographic(
2540
+ proj,
2541
+ cam.orthoLeft,
2542
+ cam.orthoRight,
2543
+ cam.orthoTop,
2544
+ cam.orthoBottom,
2545
+ cam.near,
2546
+ cam.far
3116
2547
  );
3117
2548
  } else {
3118
- projectorHandle = candidate;
3119
- projectorAsset = resolvedProjector.value;
3120
- projectorGuid = assets?.guidOf(projectorAsset) ?? `handle:${Number(candidate)}`;
2549
+ mat4.perspective(proj, cam.fov, cam.aspect, cam.near, cam.far);
3121
2550
  }
3122
- }
3123
- const iesProfileData = iesProfileAsset?.ok && iesProfileAsset.value.kind === "ies-profile" ? new Uint8Array(iesProfileAsset.value.data) : void 0;
3124
- const cookieProjection = cookieAsset?.ok && cookieAsset.value.kind === "texture" ? prepareCookieProjection(cookieAsset.value) : void 0;
3125
- const cookieData = cookieProjection?.data;
3126
- const projectorProjection = projectorAsset === void 0 ? void 0 : prepareCookieProjection(projectorAsset);
3127
- const projectorData = projectorProjection?.data;
3128
- const iesProfileHandle = s.iesProfile === void 0 ? void 0 : handleSlot(s.iesProfile);
3129
- const cookieHandle = s.cookie === void 0 ? void 0 : handleSlot(s.cookie);
3130
- let iesProfileSlice = iesProfileHandle === void 0 ? void 0 : iesSlices.get(iesProfileHandle);
3131
- if (iesProfileSlice === void 0 && iesProfileHandle !== void 0 && iesProfileData !== void 0 && iesSlices.size < IES_SLICE_CAPACITY) {
3132
- iesProfileSlice = iesSlices.size;
3133
- iesSlices.set(iesProfileHandle, iesProfileSlice);
3134
- }
3135
- let cookieSlice = cookieHandle === void 0 ? void 0 : cookieSlices.get(cookieHandle);
3136
- if (cookieSlice === void 0 && cookieHandle !== void 0 && cookieData !== void 0 && cookieSlices.size < COOKIE_SLICE_CAPACITY) {
3137
- cookieSlice = cookieSlices.size;
3138
- cookieSlices.set(cookieHandle, cookieSlice);
3139
- }
3140
- let projectorSlice = projectorHandle === void 0 ? void 0 : projectorSlices.get(projectorHandle) ?? cookieSlices.get(projectorHandle);
3141
- if (projectorSlice === void 0 && projectorHandle !== void 0 && projectorData !== void 0 && cookieSlices.size + projectorSlices.size < COOKIE_SLICE_CAPACITY) {
3142
- projectorSlice = cookieSlices.size + projectorSlices.size;
3143
- projectorSlices.set(projectorHandle, projectorSlice);
3144
- }
3145
- const shadowIntensity = s.shadowIntensity;
3146
- let lightViewProj2;
3147
- let shadowAtlasTile = -1;
3148
- if ((castShadow || hasProjector) && hasValidDirection) {
3149
- const target = vec3.create(
3150
- (position[0] ?? 0) + (dirN[0] ?? 0),
3151
- (position[1] ?? 0) + (dirN[1] ?? 0),
3152
- (position[2] ?? 0) + (dirN[2] ?? 0)
3153
- );
3154
- const fov = outerConeDeg * 2 * (Math.PI / 180);
3155
- const proj = mat4.create();
3156
- mat4.perspective(proj, fov, 1, sNearPlane, sFarPlane);
3157
2551
  const view = mat4.create();
3158
- mat4.lookAt(view, position, target, vec3.create(0, 1, 0));
3159
- lightViewProj2 = new Float32Array(16);
3160
- mat4.multiply(lightViewProj2, proj, view);
3161
- if (castShadow && spotTileNext < 4) {
3162
- shadowAtlasTile = spotTileNext;
3163
- spotTileNext += 1;
3164
- }
3165
- }
3166
- spotSnapshots.push({
3167
- kind: "spot",
3168
- entity: row.entity,
3169
- // D-6: position reflects world transform; direction stays sourced
3170
- // from SpotLight.direction (NOT rotated by the parent).
3171
- position,
3172
- direction: dirN,
3173
- color: vec3.create(
3174
- (s.color[0] ?? 1) * intensity,
3175
- (s.color[1] ?? 1) * intensity,
3176
- (s.color[2] ?? 1) * intensity
3177
- ),
3178
- intensity,
3179
- invRangeSquared: computeInvRangeSquared(range),
3180
- cosInner: degToCos(innerConeDeg),
3181
- cosOuter: degToCos(outerConeDeg),
3182
- ...s.iesProfile === void 0 ? {} : {
3183
- iesProfileHandle: handleSlot(s.iesProfile),
3184
- ...iesProfileSlice === void 0 ? {} : { iesProfileSlice }
3185
- },
3186
- ...s.cookie === void 0 ? {} : {
3187
- cookieHandle: handleSlot(s.cookie),
3188
- ...cookieSlice === void 0 ? {} : { cookieSlice }
3189
- },
3190
- ...iesProfileData === void 0 ? {} : { iesProfileData },
3191
- ...cookieData === void 0 ? {} : { cookieData },
3192
- ...cookieProjection === void 0 ? {} : { cookieMatrix: cookieProjection.matrix },
3193
- ...projectorSlice === void 0 ? {} : { projectorSlice },
3194
- ...projectorData === void 0 ? {} : { projectorData },
3195
- ...projectorProjection === void 0 ? {} : { projectorMatrix: projectorProjection.matrix },
3196
- rollDeg: s.rollDeg,
3197
- // ── shadow fields ──
3198
- castShadow,
3199
- lightViewProj: lightViewProj2,
3200
- mapSize: sMapSize,
3201
- nearPlane: sNearPlane,
3202
- farPlane: sFarPlane,
3203
- shadowAtlasTile,
3204
- shadowIntensity,
3205
- depthBias: s.depthBias,
3206
- normalBias: s.normalBias,
3207
- pcfKernelSize: s.pcfKernelSize,
3208
- ...projectorHandle === void 0 || projectorAsset === void 0 || projectorGuid === void 0 ? {} : {
3209
- projectorHandle,
3210
- projectorAsset,
3211
- projectorGuid,
3212
- projectorGeneration: 1,
3213
- projectorRevision: 1
3214
- }
3215
- });
2552
+ mat4.invert(view, cam.world);
2553
+ const vp = mat4.create();
2554
+ mat4.multiply(vp, proj, view);
2555
+ const f = frustum.create();
2556
+ frustum.fromViewProjection(f, vp);
2557
+ frustumPlanes.push(f);
2558
+ }
3216
2559
  }
3217
- const rectSnapshots = [];
3218
- const rectLightQuery = world.query({
3219
- read: [RectAreaLight],
3220
- optional: [Transform, GlobalTransform]
2560
+ const renderables = [];
2561
+ let frustumCulled = 0;
2562
+ let frustumTotal = 0;
2563
+ const explicitlyHidden = /* @__PURE__ */ new Set();
2564
+ let dispatch = [];
2565
+ const materialSnapshotCache = /* @__PURE__ */ new Map();
2566
+ const meshRendererQuery = context.renderables === "none" ? [] : world.query({
2567
+ read: [MeshRenderer],
2568
+ optional: [
2569
+ Transform,
2570
+ GlobalTransform,
2571
+ MeshFilter,
2572
+ Instances,
2573
+ Skin,
2574
+ Layer,
2575
+ MorphWeights,
2576
+ SpriteRegionOverride,
2577
+ SpriteInstances,
2578
+ Points,
2579
+ Lines,
2580
+ SortKey
2581
+ ]
3221
2582
  }).unwrap();
3222
- for (const row of rectLightQuery) {
3223
- const light = row.get(RectAreaLight);
3224
- const validation = validateRectAreaLightData({
3225
- intensity: light.intensity,
3226
- color: light.color,
3227
- width: light.width,
3228
- height: light.height,
3229
- range: light.range
3230
- });
3231
- if (!validation.ok) {
3232
- worldInternal._routeError(validation.error, {
3233
- severity: Severity.Error,
3234
- systemName: "RenderSystem.extract (rect-area-light)"
3235
- });
2583
+ const resolveArchVersion = (entity) => {
2584
+ const values = [
2585
+ worldInternal._getArrayView(entity, SpriteInstances, "transforms"),
2586
+ worldInternal._getArrayView(entity, SpriteInstances, "regions")
2587
+ ];
2588
+ let hash = 2166136261;
2589
+ for (const value of values) {
2590
+ hash = Math.imul(hash ^ (value?.length ?? 0), 16777619) >>> 0;
2591
+ if (value === void 0) continue;
2592
+ for (let index = 0; index < value.length; index += 1) {
2593
+ hash = Math.imul(hash ^ Math.fround(value[index] ?? 0), 16777619) >>> 0;
2594
+ }
2595
+ }
2596
+ return hash;
2597
+ };
2598
+ const flushPendingDispatch = (pending) => {
2599
+ for (const entry of pending) dispatch.push(entry);
2600
+ };
2601
+ for (const row of meshRendererQuery) {
2602
+ if (context.renderables === "full" && renderableEntities !== void 0 && !renderableEntities.has(row.entity)) {
3236
2603
  continue;
3237
2604
  }
3238
- const worldView = row.get(GlobalTransform);
3239
- const worldMat = worldView === void 0 ? void 0 : new Float32Array(worldInternal._getArrayView(row.entity, GlobalTransform, "world") ?? []);
3240
- const position = worldMat !== void 0 ? mat4.getTranslation(vec3.create(), worldMat) : vec3.create(0, 0, 0);
3241
- const frame = buildRectAreaWorldFrame({
3242
- center: position,
3243
- axisX: [worldMat?.[0] ?? 1, worldMat?.[1] ?? 0, worldMat?.[2] ?? 0],
3244
- axisY: [worldMat?.[4] ?? 0, worldMat?.[5] ?? 1, worldMat?.[6] ?? 0],
3245
- width: light.width,
3246
- height: light.height
3247
- });
3248
- rectSnapshots.push({
3249
- kind: "rect-area",
3250
- position,
3251
- color: vec3.create(
3252
- (light.color[0] ?? 1) * light.intensity,
3253
- (light.color[1] ?? 1) * light.intensity,
3254
- (light.color[2] ?? 1) * light.intensity
3255
- ),
3256
- intensity: light.intensity,
3257
- invRangeSquared: computeInvRangeSquared(light.range),
3258
- halfWidth: frame.halfWidth,
3259
- halfHeight: frame.halfHeight,
3260
- axisX: frame.axisX,
3261
- axisY: frame.axisY
3262
- });
3263
- }
3264
- let volumeSpot;
3265
- const extractedFog = volumetricFog;
3266
- if (extractedFog !== void 0) {
3267
- if (extractedFog.spotLightEntity !== void 0) {
3268
- volumeSpot = spotSnapshots.find((light) => light.entity === extractedFog.spotLightEntity);
3269
- } else if (extractedFog.lightKind === "spot") {
3270
- volumeSpot = spotSnapshots.find((light) => light.entity === extractedFog.lightEntity);
2605
+ const hasTransform = row.has(Transform);
2606
+ const meshFilter = row.get(MeshFilter);
2607
+ const hasInstances = row.has(Instances);
2608
+ const skin = row.get(Skin);
2609
+ const morphWeightsView = worldInternal._getArrayView(row.entity, MorphWeights, "weights");
2610
+ const hasMeshFilter = meshFilter !== void 0;
2611
+ const hasSkin = skin !== void 0;
2612
+ const hasSpriteInstances = row.has(SpriteInstances);
2613
+ const points = row.get(Points);
2614
+ const lines = row.get(Lines);
2615
+ const sortKey = row.get(SortKey)?.value;
2616
+ const pointsLinesComponent = points !== void 0 ? "Points" : lines !== void 0 ? "Lines" : void 0;
2617
+ const isRenderable = hasTransform && hasMeshFilter;
2618
+ const fAssetHandle = meshFilter?.assetHandle;
2619
+ const fLayerValue = row.get(Layer)?.value;
2620
+ const hasSpriteRegionOverride = row.has(SpriteRegionOverride);
2621
+ const skinSkeletonView = skin?.skeleton;
2622
+ let archVersion = 0;
2623
+ if (hasInstances || hasSpriteInstances) {
2624
+ archVersion = resolveArchVersion(row.entity);
3271
2625
  }
3272
- }
3273
- if (volumetricFog !== void 0 && volumetricFog.status === "available" && volumeSpot?.projectorAsset !== void 0 && volumeSpot.projectorHandle !== void 0) {
3274
- const projectorHandle = volumeSpot.projectorHandle;
3275
- const projectorAsset = volumeSpot.projectorAsset;
3276
- volumetricFog = {
3277
- ...volumetricFog,
3278
- projector: {
3279
- guid: volumeSpot.projectorGuid ?? `handle:${Number(volumeSpot.projectorHandle)}`,
3280
- generation: volumeSpot.projectorGeneration ?? 1,
3281
- view: "2d",
3282
- sampler: "linear-clamp-to-edge",
3283
- projection: "spot-projection",
3284
- revision: volumeSpot.projectorRevision ?? 1
3285
- },
3286
- projectorHandle,
3287
- projectorAsset
3288
- };
3289
- }
3290
- let lightViewProj;
3291
- let splitPlanes;
3292
- let cascadeCount;
3293
- let cascadeBlend;
3294
- let shadowMapSize;
3295
- let directionalShadowQuality;
3296
- let directionalCsmConfig;
3297
- let directionalCsmDirection;
3298
- const cam0 = cameras[0];
3299
- const cameraData = cam0 !== void 0 ? {
3300
- world: cam0.world,
3301
- fov: cam0.fov,
3302
- aspect: cam0.aspect,
3303
- near: cam0.near,
3304
- far: cam0.far,
3305
- projection: cam0.projection,
3306
- orthoLeft: cam0.orthoLeft,
3307
- orthoRight: cam0.orthoRight,
3308
- orthoBottom: cam0.orthoBottom,
3309
- orthoTop: cam0.orthoTop
3310
- } : void 0;
3311
- if (directional !== void 0 && firstHitCastShadow !== false && directionalShadowError === void 0) {
3312
- const dirSnapshot = directional;
3313
- const sf = firstHitShadowFields;
3314
- if (sf !== void 0) {
3315
- directionalCsmConfig = {
3316
- cascadeCount: sf.cascadeCount,
3317
- splitLambda: sf.splitLambda,
3318
- cascadeBlend: sf.cascadeBlend,
3319
- mapSize: sf.mapSize,
3320
- shadowDistance: sf.shadowDistance,
3321
- shadowFilter: sf.shadowFilter,
3322
- shadowAngularRadius: sf.shadowAngularRadius,
3323
- maxPenumbraTexels: sf.maxPenumbraTexels
3324
- };
3325
- directionalCsmDirection = dirSnapshot.direction;
3326
- const csm = computeDirectionalCsm(dirSnapshot.direction, directionalCsmConfig, cameraData);
3327
- cascadeCount = Math.round(sf.cascadeCount);
3328
- cascadeBlend = sf.cascadeBlend;
3329
- shadowMapSize = sf.mapSize;
3330
- if (csm !== null) {
3331
- lightViewProj = csm.lightViewProj;
3332
- splitPlanes = csm.splitPlanes;
3333
- directionalShadowQuality = csm.directionalShadowQuality;
3334
- } else {
3335
- const sNear = cameraData?.near ?? 0.1;
3336
- const splits = pssmSplit(sNear, sf.shadowDistance, cascadeCount, sf.splitLambda);
3337
- const padded = new Float32Array(16);
3338
- for (let i = 0; i < splits.length; i++) padded[i * 4] = splits[i] ?? 0;
3339
- splitPlanes = padded;
2626
+ {
2627
+ const entity = row.entity;
2628
+ const authorVisible = !(isRenderable && visibility?.effective(entity) === "hidden");
2629
+ if (!authorVisible) explicitlyHidden.add(entity);
2630
+ if (!authorVisible && !retainHidden) {
2631
+ continue;
3340
2632
  }
3341
- directionalShadowQuality = directionalShadowQualityFromF32(
3342
- sf.shadowFilter,
3343
- sf.shadowAngularRadius,
3344
- sf.maxPenumbraTexels
2633
+ const layerVal = fLayerValue ?? 0;
2634
+ const pendingDispatch = [];
2635
+ const materialsView = worldInternal._getArrayView(entity, MeshRenderer, "materials");
2636
+ const materialCount = materialsView?.length ?? 0;
2637
+ let materialHandles = Array.from(materialsView ?? []);
2638
+ let materialBindingSources = materialHandles.map(
2639
+ () => "renderer-override"
3345
2640
  );
3346
- }
3347
- }
3348
- const paddedSplitPlanes = new Float32Array(16);
3349
- if (splitPlanes !== void 0) {
3350
- for (let i = 0; i < splitPlanes.length; i++) {
3351
- paddedSplitPlanes[i] = splitPlanes[i] ?? 0;
3352
- }
3353
- }
3354
- const pointShadowSnapshots = [];
3355
- {
3356
- const pointShadowQuery = world.query({ read: [Transform, GlobalTransform, PointLightShadow], with: [PointLight] }).unwrap();
3357
- for (const row of pointShadowQuery) {
3358
- const ps = row.get(PointLightShadow);
3359
- const wRow = row.get(GlobalTransform).world;
3360
- if (wRow === void 0) continue;
3361
- const px = wRow[12] ?? 0;
3362
- const py = wRow[13] ?? 0;
3363
- const pz = wRow[14] ?? 0;
3364
- const lightPos = vec3.create(px, py, pz);
3365
- const mapSize = ps.mapSize;
3366
- const nearPlane = ps.nearPlane;
3367
- const farPlane = ps.farPlane;
3368
- const layer = pointShadowSnapshots.length < SHADOW_ATLAS_DEFAULT_LAYERS ? pointShadowSnapshots.length : -1;
3369
- const matrices = buildPointShadowMatrices(lightPos, nearPlane, farPlane);
3370
- const packed = new Float32Array(96);
3371
- for (let f = 0; f < 6; f++) {
3372
- const m = matrices[f];
3373
- if (m === void 0) continue;
3374
- for (let k = 0; k < 16; k++) {
3375
- packed[f * 16 + k] = m[k] ?? 0;
2641
+ let materialBindingDiagnostics = [];
2642
+ let gpuDrivenSubmeshes = [];
2643
+ let gpuDrivenIndexed = false;
2644
+ let gpuDrivenGeometry;
2645
+ const fAssetHandleVal = fAssetHandle;
2646
+ if (fAssetHandleVal !== void 0 && fAssetHandleVal !== 0 && assets !== void 0 && assets !== null) {
2647
+ const meshHandle = toShared(fAssetHandleVal);
2648
+ const meshRes = resolveAssetHandle(world, meshHandle);
2649
+ if (meshRes.ok && meshRes.value.kind === "mesh") {
2650
+ const meshAsset = meshRes.value;
2651
+ const guid = meshRes.value.guid ?? "<no-guid>";
2652
+ gpuDrivenSubmeshes = meshAsset.submeshes;
2653
+ gpuDrivenIndexed = meshAsset.indices !== void 0;
2654
+ const layoutProjection = deriveVertexLayoutProjection(meshAsset.attributes);
2655
+ gpuDrivenGeometry = {
2656
+ identity: `${guid}:${layoutProjection.digest}`,
2657
+ vertexInputs: layoutProjection.attributes.map((attribute) => ({
2658
+ semantic: attribute.key,
2659
+ location: attribute.shaderLocation,
2660
+ format: attribute.format
2661
+ })),
2662
+ topology: meshAsset.submeshes[0]?.topology ?? "triangle-list",
2663
+ indexed: gpuDrivenIndexed
2664
+ };
2665
+ if (!Array.isArray(meshAsset.materialSlots) && world.get(entity, GlyphText).ok) {
2666
+ ensureGlyphMeshMaterialSlots(world, meshHandle);
2667
+ }
2668
+ const resolvedBindings = resolveMeshMaterialBindings(meshAsset, materialsView ?? [], {
2669
+ isValidOverride(handle) {
2670
+ const resolved = resolveAssetHandle(world, toShared(handle));
2671
+ return resolved.ok && resolved.value.kind === "material";
2672
+ },
2673
+ resolveMeshDefault(defaultGuid) {
2674
+ const guidText = AssetGuid.format(defaultGuid);
2675
+ if (assets.lookup(guidText)?.kind !== "material") return void 0;
2676
+ return internSharedRefFromGuid(world, assets, guidText, "MaterialAsset");
2677
+ }
2678
+ });
2679
+ if (!resolvedBindings.ok) {
2680
+ if (resolvedBindings.code === "mesh-material-slots-missing") {
2681
+ worldInternal._routeError(
2682
+ new AssetError({
2683
+ code: "load-failed",
2684
+ expected: "every MeshAsset producer supplies materialSlots[]",
2685
+ hint: `fix the MeshAsset producer; renderer inheritance never guesses slot topology (mesh=${guid}, entity=${entity}, vertices=${meshAsset.vertices.length}, indices=${meshAsset.indices?.length ?? 0})`,
2686
+ detail: {
2687
+ referencedByGuid: guid,
2688
+ referencedByKind: "mesh",
2689
+ subAssetGuid: "<material-slots-missing>",
2690
+ sourceField: { fieldName: "materialSlots" }
2691
+ }
2692
+ }),
2693
+ {
2694
+ severity: Severity.Error,
2695
+ systemName: "RenderSystem.extract (mesh-material-slots-missing)"
2696
+ }
2697
+ );
2698
+ continue;
2699
+ }
2700
+ const materialGuid = AssetGuid.format(resolvedBindings.defaultMaterial);
2701
+ worldInternal._routeError(
2702
+ new AssetError({
2703
+ code: "load-failed",
2704
+ expected: `MeshAsset materialSlots[${resolvedBindings.slotIndex}] default ${materialGuid} is ready and a MaterialAsset`,
2705
+ hint: `loadByGuid(meshGuid) must recursively load the declared default material; mesh=${guid}, slot=${resolvedBindings.slotIndex}, material=${materialGuid}`,
2706
+ detail: {
2707
+ referencedByGuid: guid,
2708
+ referencedByKind: "mesh",
2709
+ subAssetGuid: materialGuid,
2710
+ sourceField: {
2711
+ fieldName: "materialSlots",
2712
+ arrayIndex: resolvedBindings.slotIndex
2713
+ }
2714
+ }
2715
+ }),
2716
+ {
2717
+ severity: Severity.Error,
2718
+ systemName: "RenderSystem.extract (mesh-default-not-ready)"
2719
+ }
2720
+ );
2721
+ continue;
2722
+ }
2723
+ materialHandles = resolvedBindings.bindings.map((binding) => binding.handle);
2724
+ materialBindingSources = resolvedBindings.bindings.map((binding) => binding.source);
2725
+ materialBindingDiagnostics = resolvedBindings.diagnostics.map((diagnostic) => ({
2726
+ ...diagnostic,
2727
+ detail: diagnostic.code === "mesh-renderer-material-override-overflow" ? {
2728
+ expectedCount: meshAsset.materialSlots.length,
2729
+ actualCount: materialCount,
2730
+ meshAssetGuid: guid
2731
+ } : {
2732
+ meshAssetGuid: guid,
2733
+ slotIndex: diagnostic.slotIndex,
2734
+ handle: diagnostic.handle ?? 0
2735
+ }
2736
+ }));
2737
+ for (const diagnostic of resolvedBindings.diagnostics) {
2738
+ worldInternal._routeError(
2739
+ new AssetError({
2740
+ code: diagnostic.code,
2741
+ expected: diagnostic.code === "mesh-renderer-material-override-overflow" ? `materials.length <= materialSlots.length (${meshAsset.materialSlots.length})` : `materials[${diagnostic.slotIndex}] resolves to a live MaterialAsset`,
2742
+ hint: ASSET_ERROR_HINTS[diagnostic.code],
2743
+ detail: diagnostic.code === "mesh-renderer-material-override-overflow" ? {
2744
+ expectedCount: meshAsset.materialSlots.length,
2745
+ actualCount: materialCount,
2746
+ meshAssetGuid: guid
2747
+ } : {
2748
+ meshAssetGuid: guid,
2749
+ slotIndex: diagnostic.slotIndex,
2750
+ handle: diagnostic.handle ?? 0
2751
+ }
2752
+ }),
2753
+ {
2754
+ severity: Severity.Warning,
2755
+ systemName: `RenderSystem.extract (${diagnostic.code})`
2756
+ }
2757
+ );
2758
+ }
3376
2759
  }
3377
2760
  }
3378
- pointShadowSnapshots.push({
3379
- entity: row.entity,
3380
- position: lightPos,
3381
- mapSize,
3382
- nearPlane,
3383
- farPlane,
3384
- shadowAtlasLayer: layer,
3385
- shadowMatrices: packed
3386
- });
3387
- }
3388
- }
3389
- if (pointShadowSnapshots.length > 0) {
3390
- const shadowByEntity = /* @__PURE__ */ new Map();
3391
- for (const ps of pointShadowSnapshots) shadowByEntity.set(ps.entity, ps);
3392
- for (let i = 0; i < pointSnapshots.length; i++) {
3393
- const entityId = pointSnapshotEntities[i] ?? 0;
3394
- const ps = shadowByEntity.get(entityId);
3395
- if (ps !== void 0) {
3396
- pointSnapshots[i] = {
3397
- ...pointSnapshots[i],
3398
- shadowAtlasLayer: ps.shadowAtlasLayer,
3399
- shadowNear: ps.nearPlane,
3400
- shadowFar: ps.farPlane
3401
- };
2761
+ if (materialHandles.length === 0) {
2762
+ materialHandles = [0];
2763
+ materialBindingSources = ["engine-default"];
3402
2764
  }
3403
- }
3404
- }
3405
- const lights = {
3406
- directional,
3407
- directionalCount,
3408
- point: pointSnapshots,
3409
- spot: spotSnapshots,
3410
- rect: rectSnapshots,
3411
- lightViewProj,
3412
- splitPlanes: splitPlanes !== void 0 ? paddedSplitPlanes : void 0,
3413
- cascadeCount,
3414
- cascadeBlend,
3415
- shadowMapSize,
3416
- depthBias: firstHitCastShadow !== false ? firstHitShadowFields?.depthBias : void 0,
3417
- normalBias: firstHitCastShadow !== false ? firstHitShadowFields?.normalBias : void 0,
3418
- directionalShadowQuality,
3419
- directionalShadowError,
3420
- pointShadow: pointShadowSnapshots,
3421
- // bug-20260710-editor-cross-world-shadow: raw CSM config + light direction
3422
- // so the merge layer can recompute matrices against the surfaced camera.
3423
- directionalCsmConfig,
3424
- directionalCsmDirection
3425
- };
3426
- const skylightQuery = world.query({ read: [Skylight] }).unwrap();
3427
- let skylight;
3428
- let skylightCount = 0;
3429
- for (const row of skylightQuery) {
3430
- const s = row.get(Skylight);
3431
- const equirectRaw = s.equirect;
3432
- const intensity = s.intensity;
3433
- const colorR = s.color[0] ?? 1;
3434
- const colorG = s.color[1] ?? 1;
3435
- const colorB = s.color[2] ?? 1;
3436
- const rotation = [
3437
- s.rotation[0] ?? 0,
3438
- s.rotation[1] ?? 0,
3439
- s.rotation[2] ?? 0,
3440
- s.rotation[3] ?? 1
3441
- ];
3442
- skylightCount += 1;
3443
- if (skylight === void 0) {
3444
- skylight = {
3445
- equirectHandle: equirectRaw !== void 0 ? Math.round(equirectRaw) : 0,
3446
- color: [colorR, colorG, colorB],
3447
- intensity,
3448
- rotation,
3449
- // w19: winning entity handle for the multi-Skylight once-warn (F-8).
3450
- entityHandle: row.entity
3451
- };
3452
- }
3453
- }
3454
- const lightProbeQuery = world.query({ read: [LightProbe, GlobalTransform] }).unwrap();
3455
- const lightProbes = [];
3456
- for (const row of lightProbeQuery) {
3457
- const probe = row.get(LightProbe);
3458
- const validation = validateLightProbeData({
3459
- irradiance: probe.irradiance,
3460
- radius: probe.radius
3461
- });
3462
- if (!validation.ok) {
3463
- worldInternal._routeError(validation.error, {
3464
- severity: Severity.Error,
3465
- systemName: "RenderSystem.extract (light-probe)"
3466
- });
3467
- continue;
3468
- }
3469
- const transform = row.get(GlobalTransform);
3470
- lightProbes.push({
3471
- identity: String(row.entity),
3472
- position: [transform.world[12] ?? 0, transform.world[13] ?? 0, transform.world[14] ?? 0],
3473
- radius: probe.radius,
3474
- irradiance: new Float32Array(probe.irradiance),
3475
- admitted: true
3476
- });
3477
- }
3478
- const skyboxQuery = world.query({ read: [SkyboxBackground] }).unwrap();
3479
- let skybox;
3480
- let skyboxCount = 0;
3481
- for (const row of skyboxQuery) {
3482
- const s = row.get(SkyboxBackground);
3483
- const equirectRaw = s.equirect;
3484
- const modeRaw = s.mode;
3485
- const rotation = [
3486
- s.rotation[0] ?? 0,
3487
- s.rotation[1] ?? 0,
3488
- s.rotation[2] ?? 0,
3489
- s.rotation[3] ?? 1
3490
- ];
3491
- skyboxCount += 1;
3492
- if (skybox === void 0 && equirectRaw !== void 0) {
3493
- skybox = {
3494
- equirectHandle: Math.round(equirectRaw),
3495
- mode: modeRaw,
3496
- rotation,
3497
- // w19: winning entity handle for the multi-SkyboxBackground warn (F-8).
3498
- entityHandle: row.entity
3499
- };
3500
- }
3501
- }
3502
- const environmentCandidates = [];
3503
- const imageEnvironment = skylight?.equirectHandle !== void 0 && skylight.equirectHandle > 0 ? skylight : skybox?.equirectHandle !== void 0 && skybox.equirectHandle > 0 ? skybox : void 0;
3504
- if (imageEnvironment !== void 0) {
3505
- environmentCandidates.push({
3506
- kind: "image",
3507
- entityKey: imageEnvironment.entityHandle,
3508
- sourceKey: `equirect:${imageEnvironment.equirectHandle}`
3509
- });
3510
- }
3511
- const atmosphereQuery = world.query({ read: [Atmosphere] }).unwrap();
3512
- for (const row of atmosphereQuery) {
3513
- const value = row.get(Atmosphere);
3514
- const atmosphere = {
3515
- turbidity: value.turbidity,
3516
- rayleigh: value.rayleigh,
3517
- mieCoefficient: value.mieCoefficient,
3518
- mieDirectionalG: value.mieDirectionalG,
3519
- sunAngularRadius: value.sunAngularRadius
3520
- };
3521
- environmentCandidates.push({
3522
- kind: "atmosphere",
3523
- entityKey: row.entity,
3524
- sourceKey: `atmosphere:${row.entity}`,
3525
- atmosphere
3526
- });
3527
- }
3528
- const directionalDirection = directional?.direction;
3529
- const directionalLength = directionalDirection === void 0 ? 0 : Math.hypot(
3530
- directionalDirection[0] ?? 0,
3531
- directionalDirection[1] ?? 0,
3532
- directionalDirection[2] ?? 0
3533
- );
3534
- const sun = directional === void 0 || directionalLength <= 1e-6 ? [] : [
3535
- {
3536
- entityKey: 0,
3537
- direction: [
3538
- -(directionalDirection?.[0] ?? 0) / directionalLength,
3539
- -(directionalDirection?.[1] ?? 0) / directionalLength,
3540
- -(directionalDirection?.[2] ?? 0) / directionalLength
3541
- ],
3542
- color: [
3543
- directional.color[0] ?? 0,
3544
- directional.color[1] ?? 0,
3545
- directional.color[2] ?? 0
3546
- ],
3547
- intensity: directional.intensity
3548
- }
3549
- ];
3550
- const environmentSelection = selectEnvironment({
3551
- environments: environmentCandidates,
3552
- fogs: fog === void 0 ? [] : [fog],
3553
- suns: sun,
3554
- lane: "direct"
3555
- });
3556
- if (!environmentSelection.ok) {
3557
- routeWorldError(world, environmentSelection.error, {
3558
- systemName: "RenderSystem.extract (environment-selection)"
3559
- });
3560
- throw environmentSelection.error;
3561
- }
3562
- const environment = environmentSelection.value;
3563
- const frustumPlanes = [];
3564
- const cullingCameras = cullMode === "external" ? context.cullCameras ?? [] : cameras;
3565
- if (cullMode !== "none") {
3566
- for (const cam of cullingCameras) {
3567
- if (cam.projection === "perspective" && (cam.fov <= 0 || cam.aspect <= 0)) {
3568
- frustumPlanes.push(new Float32Array(0));
3569
- continue;
3570
- }
3571
- if (cam.near >= cam.far) {
3572
- frustumPlanes.push(new Float32Array(0));
3573
- continue;
3574
- }
3575
- const proj = mat4.create();
3576
- if (cam.projection === "orthographic") {
3577
- mat4.orthographic(
3578
- proj,
3579
- cam.orthoLeft,
3580
- cam.orthoRight,
3581
- cam.orthoBottom,
3582
- cam.orthoTop,
3583
- cam.near,
3584
- cam.far
3585
- );
2765
+ const handleRaw = materialHandles[0] ?? 0;
2766
+ const cachedMaterial = handleRaw !== 0 && !hasSpriteRegionOverride && !hasSkin ? materialSnapshotCache.get(handleRaw) : void 0;
2767
+ let materialSnap;
2768
+ if (cachedMaterial !== void 0) {
2769
+ materialSnap = cachedMaterial.snapshot;
2770
+ if (isRenderable) {
2771
+ appendMaterialDispatchEntries(
2772
+ pendingDispatch,
2773
+ cachedMaterial.passes,
2774
+ entity,
2775
+ handleRaw,
2776
+ renderables.length,
2777
+ layerVal,
2778
+ materialSnap.paramSnapshot,
2779
+ 0,
2780
+ materialSnap.materialShaderId
2781
+ );
2782
+ }
2783
+ } else if (handleRaw === 0 || assets === void 0 || assets === null) {
2784
+ materialSnap = defaultMaterialSnapshot(handleRaw);
3586
2785
  } else {
3587
- mat4.perspective(proj, cam.fov, cam.aspect, cam.near, cam.far);
3588
- }
3589
- const view = mat4.create();
3590
- mat4.invert(view, cam.world);
3591
- const vp = mat4.create();
3592
- mat4.multiply(vp, proj, view);
3593
- const f = frustum.create();
3594
- frustum.fromViewProjection(f, vp);
3595
- frustumPlanes.push(f);
3596
- }
3597
- }
3598
- const renderables = [];
3599
- let frustumCulled = 0;
3600
- let frustumTotal = 0;
3601
- const explicitlyHidden = /* @__PURE__ */ new Set();
3602
- let dispatch = [];
3603
- const materialSnapshotCache = /* @__PURE__ */ new Map();
3604
- const meshRendererQuery = context.renderables === "none" ? [] : world.query({
3605
- read: [MeshRenderer],
3606
- optional: [
3607
- Transform,
3608
- GlobalTransform,
3609
- MeshFilter,
3610
- Instances,
3611
- Skin,
3612
- Layer,
3613
- MorphWeights,
3614
- SpriteRegionOverride,
3615
- SpriteInstances,
3616
- Points,
3617
- Lines,
3618
- SortKey
3619
- ]
3620
- }).unwrap();
3621
- const resolveArchVersion = (entity) => {
3622
- const values = [
3623
- worldInternal._getArrayView(entity, Instances, "transforms"),
3624
- worldInternal._getArrayView(entity, SpriteInstances, "transforms"),
3625
- worldInternal._getArrayView(entity, SpriteInstances, "regions")
3626
- ];
3627
- let hash = 2166136261;
3628
- for (const value of values) {
3629
- hash = Math.imul(hash ^ (value?.length ?? 0), 16777619) >>> 0;
3630
- if (value === void 0) continue;
3631
- for (let index = 0; index < value.length; index += 1) {
3632
- hash = Math.imul(hash ^ Math.fround(value[index] ?? 0), 16777619) >>> 0;
3633
- }
3634
- }
3635
- return hash;
3636
- };
3637
- const flushPendingDispatch = (pending) => {
3638
- for (const entry of pending) dispatch.push(entry);
3639
- };
3640
- for (const row of meshRendererQuery) {
3641
- const hasTransform = row.has(Transform);
3642
- const meshFilter = row.get(MeshFilter);
3643
- const hasInstances = row.has(Instances);
3644
- const skin = row.get(Skin);
3645
- const morphWeightsView = worldInternal._getArrayView(row.entity, MorphWeights, "weights");
3646
- const hasMeshFilter = meshFilter !== void 0;
3647
- const hasSkin = skin !== void 0;
3648
- const hasSpriteInstances = row.has(SpriteInstances);
3649
- const points = row.get(Points);
3650
- const lines = row.get(Lines);
3651
- const sortKey = row.get(SortKey)?.value;
3652
- const pointsLinesComponent = points !== void 0 ? "Points" : lines !== void 0 ? "Lines" : void 0;
3653
- const isRenderable = hasTransform && hasMeshFilter;
3654
- const fAssetHandle = meshFilter?.assetHandle;
3655
- const fLayerValue = row.get(Layer)?.value;
3656
- const hasSpriteRegionOverride = row.has(SpriteRegionOverride);
3657
- const skinSkeletonView = skin?.skeleton;
3658
- let archVersion = 0;
3659
- if (hasInstances || hasSpriteInstances) {
3660
- archVersion = resolveArchVersion(row.entity);
3661
- }
3662
- {
3663
- const entity = row.entity;
3664
- if (isRenderable && visibility?.effective(entity) === "hidden") {
3665
- explicitlyHidden.add(entity);
3666
- continue;
3667
- }
3668
- const layerVal = fLayerValue ?? 0;
3669
- const pendingDispatch = [];
3670
- const materialsView = worldInternal._getArrayView(entity, MeshRenderer, "materials");
3671
- const materialCount = materialsView?.length ?? 0;
3672
- let materialHandles = Array.from(materialsView ?? []);
3673
- let materialBindingSources = materialHandles.map(
3674
- () => "renderer-override"
3675
- );
3676
- let materialBindingDiagnostics = [];
3677
- let gpuDrivenSubmeshes = [];
3678
- let gpuDrivenIndexed = false;
3679
- let gpuDrivenGeometry;
3680
- const fAssetHandleVal = fAssetHandle;
3681
- if (fAssetHandleVal !== void 0 && fAssetHandleVal !== 0 && assets !== void 0 && assets !== null) {
3682
- const meshHandle = toShared(fAssetHandleVal);
3683
- const meshRes = resolveAssetHandle(world, meshHandle);
3684
- if (meshRes.ok && meshRes.value.kind === "mesh") {
3685
- const meshAsset = meshRes.value;
3686
- const guid = meshRes.value.guid ?? "<no-guid>";
3687
- gpuDrivenSubmeshes = meshAsset.submeshes;
3688
- gpuDrivenIndexed = meshAsset.indices !== void 0;
3689
- const layoutProjection = deriveVertexLayoutProjection(meshAsset.attributes);
3690
- gpuDrivenGeometry = {
3691
- identity: `${guid}:${layoutProjection.digest}`,
3692
- vertexInputs: layoutProjection.attributes.map((attribute) => ({
3693
- semantic: attribute.key,
3694
- location: attribute.shaderLocation,
3695
- format: attribute.format
3696
- })),
3697
- topology: meshAsset.submeshes[0]?.topology ?? "triangle-list",
3698
- indexed: gpuDrivenIndexed
3699
- };
3700
- if (!Array.isArray(meshAsset.materialSlots) && world.get(entity, GlyphText).ok) {
3701
- ensureGlyphMeshMaterialSlots(world, meshHandle);
3702
- }
3703
- const resolvedBindings = resolveMeshMaterialBindings(meshAsset, materialsView ?? [], {
3704
- isValidOverride(handle) {
3705
- const resolved = resolveAssetHandle(world, toShared(handle));
3706
- return resolved.ok && resolved.value.kind === "material";
3707
- },
3708
- resolveMeshDefault(defaultGuid) {
3709
- const guidText = AssetGuid.format(defaultGuid);
3710
- if (assets.lookup(guidText)?.kind !== "material") return void 0;
3711
- return internSharedRefFromGuid(world, assets, guidText, "MaterialAsset");
3712
- }
3713
- });
3714
- if (!resolvedBindings.ok) {
3715
- if (resolvedBindings.code === "mesh-material-slots-missing") {
3716
- worldInternal._routeError(
3717
- new AssetError({
3718
- code: "load-failed",
3719
- expected: "every MeshAsset producer supplies materialSlots[]",
3720
- hint: `fix the MeshAsset producer; renderer inheritance never guesses slot topology (mesh=${guid}, entity=${entity}, vertices=${meshAsset.vertices.length}, indices=${meshAsset.indices?.length ?? 0})`,
3721
- detail: {
3722
- referencedByGuid: guid,
3723
- referencedByKind: "mesh",
3724
- subAssetGuid: "<material-slots-missing>",
3725
- sourceField: { fieldName: "materialSlots" }
3726
- }
3727
- }),
3728
- {
3729
- severity: Severity.Error,
3730
- systemName: "RenderSystem.extract (mesh-material-slots-missing)"
3731
- }
3732
- );
3733
- continue;
3734
- }
3735
- const materialGuid = AssetGuid.format(resolvedBindings.defaultMaterial);
3736
- worldInternal._routeError(
3737
- new AssetError({
3738
- code: "load-failed",
3739
- expected: `MeshAsset materialSlots[${resolvedBindings.slotIndex}] default ${materialGuid} is ready and a MaterialAsset`,
3740
- hint: `loadByGuid(meshGuid) must recursively load the declared default material; mesh=${guid}, slot=${resolvedBindings.slotIndex}, material=${materialGuid}`,
3741
- detail: {
3742
- referencedByGuid: guid,
3743
- referencedByKind: "mesh",
3744
- subAssetGuid: materialGuid,
3745
- sourceField: {
3746
- fieldName: "materialSlots",
3747
- arrayIndex: resolvedBindings.slotIndex
3748
- }
3749
- }
3750
- }),
3751
- {
3752
- severity: Severity.Error,
3753
- systemName: "RenderSystem.extract (mesh-default-not-ready)"
3754
- }
3755
- );
3756
- continue;
3757
- }
3758
- materialHandles = resolvedBindings.bindings.map((binding) => binding.handle);
3759
- materialBindingSources = resolvedBindings.bindings.map((binding) => binding.source);
3760
- materialBindingDiagnostics = resolvedBindings.diagnostics.map((diagnostic) => ({
3761
- ...diagnostic,
3762
- detail: diagnostic.code === "mesh-renderer-material-override-overflow" ? {
3763
- expectedCount: meshAsset.materialSlots.length,
3764
- actualCount: materialCount,
3765
- meshAssetGuid: guid
3766
- } : {
3767
- meshAssetGuid: guid,
3768
- slotIndex: diagnostic.slotIndex,
3769
- handle: diagnostic.handle ?? 0
3770
- }
3771
- }));
3772
- for (const diagnostic of resolvedBindings.diagnostics) {
3773
- worldInternal._routeError(
3774
- new AssetError({
3775
- code: diagnostic.code,
3776
- expected: diagnostic.code === "mesh-renderer-material-override-overflow" ? `materials.length <= materialSlots.length (${meshAsset.materialSlots.length})` : `materials[${diagnostic.slotIndex}] resolves to a live MaterialAsset`,
3777
- hint: ASSET_ERROR_HINTS[diagnostic.code],
3778
- detail: diagnostic.code === "mesh-renderer-material-override-overflow" ? {
3779
- expectedCount: meshAsset.materialSlots.length,
3780
- actualCount: materialCount,
3781
- meshAssetGuid: guid
3782
- } : {
3783
- meshAssetGuid: guid,
3784
- slotIndex: diagnostic.slotIndex,
3785
- handle: diagnostic.handle ?? 0
3786
- }
3787
- }),
3788
- {
3789
- severity: Severity.Warning,
3790
- systemName: `RenderSystem.extract (${diagnostic.code})`
3791
- }
3792
- );
3793
- }
3794
- }
3795
- }
3796
- if (materialHandles.length === 0) {
3797
- materialHandles = [0];
3798
- materialBindingSources = ["engine-default"];
3799
- }
3800
- const handleRaw = materialHandles[0] ?? 0;
3801
- const cachedMaterial = handleRaw !== 0 && !hasSpriteRegionOverride && !hasSkin ? materialSnapshotCache.get(handleRaw) : void 0;
3802
- let materialSnap;
3803
- if (cachedMaterial !== void 0) {
3804
- materialSnap = cachedMaterial.snapshot;
3805
- if (isRenderable) {
3806
- appendMaterialDispatchEntries(
3807
- pendingDispatch,
3808
- cachedMaterial.passes,
3809
- entity,
3810
- handleRaw,
3811
- renderables.length,
3812
- layerVal,
3813
- materialSnap.paramSnapshot
3814
- );
3815
- }
3816
- } else if (handleRaw === 0 || assets === void 0 || assets === null) {
3817
- materialSnap = defaultMaterialSnapshot(handleRaw);
3818
- } else {
3819
- const tagged = toShared(handleRaw);
3820
- const stablePersistentCached = !hasSpriteRegionOverride && !hasSkin ? readStablePersistentMaterialSnapshot(
3821
- persistentMaterialSnapshotCache,
3822
- handleRaw,
3823
- assets
3824
- ) : void 0;
3825
- if (stablePersistentCached !== void 0) {
3826
- materialSnapshotCache.set(handleRaw, stablePersistentCached);
3827
- materialSnap = stablePersistentCached.snapshot;
3828
- if (isRenderable) {
3829
- appendMaterialDispatchEntries(
3830
- pendingDispatch,
3831
- stablePersistentCached.passes,
3832
- entity,
3833
- handleRaw,
3834
- renderables.length,
3835
- layerVal,
3836
- materialSnap.paramSnapshot
3837
- );
2786
+ const tagged = toShared(handleRaw);
2787
+ const stablePersistentCached = !hasSpriteRegionOverride && !hasSkin ? readStablePersistentMaterialSnapshot(
2788
+ persistentMaterialSnapshotCache,
2789
+ handleRaw,
2790
+ assets
2791
+ ) : void 0;
2792
+ if (stablePersistentCached !== void 0) {
2793
+ materialSnapshotCache.set(handleRaw, stablePersistentCached);
2794
+ materialSnap = stablePersistentCached.snapshot;
2795
+ if (isRenderable) {
2796
+ appendMaterialDispatchEntries(
2797
+ pendingDispatch,
2798
+ stablePersistentCached.passes,
2799
+ entity,
2800
+ handleRaw,
2801
+ renderables.length,
2802
+ layerVal,
2803
+ materialSnap.paramSnapshot,
2804
+ 0,
2805
+ materialSnap.materialShaderId
2806
+ );
3838
2807
  }
3839
2808
  } else {
3840
2809
  const res = resolveAssetHandle(world, tagged);
@@ -3887,7 +2856,7 @@ function extractFrame(world, context) {
3887
2856
  }
3888
2857
  const resolved = resolvedResult.value;
3889
2858
  const allPasses = resolved.passes;
3890
- const firstPassShader = allPasses.length > 0 ? runtimeMaterialShaderId(allPasses[0]?.program.module, allPasses[0]?.name) : void 0;
2859
+ const firstPassShader = runtimeMaterialShaderIdForMaterial(asset, allPasses, assets);
3891
2860
  const pv = materialValuesToLinearRuntime(
3892
2861
  resolved.values,
3893
2862
  materialColorParameterSchema(resolved.parameters ?? [], firstPassShader, assets),
@@ -3901,9 +2870,7 @@ function extractFrame(world, context) {
3901
2870
  );
3902
2871
  const metallicPv = typeof pv.metallic === "number" ? pv.metallic : 0;
3903
2872
  const roughnessPv = typeof pv.roughness === "number" ? pv.roughness : 0.5;
3904
- const clearcoatPv = typeof pv.clearcoat === "number" ? pv.clearcoat : 0;
3905
- const clearcoatRoughnessPv = typeof pv.clearcoatRoughness === "number" ? pv.clearcoatRoughness : 0.5;
3906
- const specularTintPv = pv.specularTint;
2873
+ const specularColorPv = pv.specularColor;
3907
2874
  const normalScalePv = materialNormalScale(pv);
3908
2875
  const paramSnap = {};
3909
2876
  for (const [k, v] of Object.entries(pv)) {
@@ -3919,7 +2886,7 @@ function extractFrame(world, context) {
3919
2886
  );
3920
2887
  {
3921
2888
  const hasSkinSkel = hasSkin && skinSkeletonView !== void 0 && skinSkeletonView !== void 0 && skinSkeletonView !== 0;
3922
- const isPbrSkinMaterial = firstPassShader === "forgeax::pbr-skin";
2889
+ const isPbrSkinMaterial = isStandardPbrSkinMaterialShader(firstPassShader);
3923
2890
  if (hasSkinSkel && !isPbrSkinMaterial) {
3924
2891
  worldInternal._routeError(new SkinMaterialMismatchError(entity, firstPassShader), {
3925
2892
  severity: Severity.Error,
@@ -4068,13 +3035,11 @@ function extractFrame(world, context) {
4068
3035
  baseColor,
4069
3036
  metallic: metallicPv,
4070
3037
  roughness: roughnessPv,
4071
- clearcoat: clearcoatPv,
4072
- clearcoatRoughness: clearcoatRoughnessPv,
4073
- ...specularTintPv !== void 0 && {
4074
- specularTint: [
4075
- specularTintPv[0] ?? 1,
4076
- specularTintPv[1] ?? 1,
4077
- specularTintPv[2] ?? 1
3038
+ ...specularColorPv !== void 0 && {
3039
+ specularColor: [
3040
+ specularColorPv[0] ?? 1,
3041
+ specularColorPv[1] ?? 1,
3042
+ specularColorPv[2] ?? 1
4078
3043
  ]
4079
3044
  },
4080
3045
  normalScale: normalScalePv,
@@ -4131,7 +3096,9 @@ function extractFrame(world, context) {
4131
3096
  handleRaw,
4132
3097
  renderables.length,
4133
3098
  layerVal,
4134
- paramSnap
3099
+ paramSnap,
3100
+ 0,
3101
+ firstPassShader
4135
3102
  );
4136
3103
  }
4137
3104
  }
@@ -4228,7 +3195,10 @@ function extractFrame(world, context) {
4228
3195
  ibms[jIdx] = ibmFlat.subarray(jIdx * 16, jIdx * 16 + 16);
4229
3196
  }
4230
3197
  if (skinPaletteAllocator !== null) {
4231
- const slice = skinPaletteAllocator.allocateSlice(skeleton.jointCount);
3198
+ const slice = typeof skinPaletteAllocator.allocateSliceFor === "function" ? skinPaletteAllocator.allocateSliceFor(
3199
+ `${world.identity}:${entity}`,
3200
+ skeleton.jointCount
3201
+ ) : skinPaletteAllocator.allocateSlice(skeleton.jointCount);
4232
3202
  skinPaletteAllocator.writeJointPalette(slice, ibms, jointWorlds);
4233
3203
  skinSlice = {
4234
3204
  jointCount: slice.jointCount,
@@ -4274,7 +3244,9 @@ function extractFrame(world, context) {
4274
3244
  subHandle,
4275
3245
  renderables.length,
4276
3246
  layerVal,
4277
- materialsArr[mi]?.paramSnapshot
3247
+ materialsArr[mi]?.paramSnapshot,
3248
+ 0,
3249
+ materialsArr[mi]?.materialShaderId
4278
3250
  );
4279
3251
  }
4280
3252
  }
@@ -4372,31 +3344,28 @@ function extractFrame(world, context) {
4372
3344
  };
4373
3345
  let instancesSnap;
4374
3346
  if (hasInstances) {
4375
- const transforms = worldInternal._getArrayView(entity, Instances, "transforms");
4376
- if (transforms === void 0) {
4377
- instancesSnap = {
4378
- transforms: new Float32Array(),
4379
- instanceCount: 0,
4380
- cacheKey: entity,
4381
- archVersion
4382
- };
4383
- } else {
4384
- const actualLength = transforms.length;
4385
- if (actualLength % 16 !== 0) {
4386
- worldInternal._routeError(new InstanceTransformsStrideMismatchError(actualLength), {
3347
+ const transforms = worldInternal._getArrayView(entity, Instances, "transforms") ?? new Float32Array();
3348
+ if (transforms.length % 16 !== 0) {
3349
+ worldInternal._routeError(
3350
+ new InstanceTransformsStrideMismatchError(transforms.length),
3351
+ {
4387
3352
  severity: Severity.Error,
4388
3353
  systemName: "RenderSystem.extract (Instances stride)"
4389
- });
4390
- continue;
4391
- }
4392
- const snapshotCopy = new Float32Array(transforms);
4393
- instancesSnap = {
4394
- transforms: snapshotCopy,
4395
- instanceCount: actualLength / 16,
4396
- cacheKey: entity,
4397
- archVersion
4398
- };
3354
+ }
3355
+ );
3356
+ continue;
4399
3357
  }
3358
+ const projected = instanceCollections?.project(world, entity, transforms);
3359
+ instancesSnap = {
3360
+ transforms: projected?.transforms ?? new Float32Array(transforms),
3361
+ instanceCount: transforms.length / 16,
3362
+ cacheKey: entity,
3363
+ archVersion: projected === void 0 ? fingerprintNumericArray(transforms) : 0,
3364
+ ...projected === void 0 ? {} : {
3365
+ collectionId: projected.collectionId,
3366
+ revision: projected.revision
3367
+ }
3368
+ };
4400
3369
  }
4401
3370
  const baseRenderable = {
4402
3371
  assetHandle: Math.round(fAssetHandle ?? 0),
@@ -4410,7 +3379,9 @@ function extractFrame(world, context) {
4410
3379
  materialBindingDiagnostics,
4411
3380
  worldId: 0,
4412
3381
  entityKey: entity,
3382
+ ...authorVisible ? {} : { authorVisible: false },
4413
3383
  ...skinSlice !== void 0 ? { skin: skinSlice } : {},
3384
+ ...skin?.joints === void 0 ? {} : { skinJointEntities: Array.from(skin.joints, (joint) => Number(joint)) },
4414
3385
  ...morph !== void 0 ? { morph } : {},
4415
3386
  ...instancesSnap !== void 0 ? { instances: instancesSnap } : {},
4416
3387
  ...spriteInstancesSnap !== void 0 ? { spriteInstances: spriteInstancesSnap } : {},
@@ -4486,60 +3457,1139 @@ function extractFrame(world, context) {
4486
3457
  }
4487
3458
  }
4488
3459
  }
4489
- dispatch = sortDispatchByQueue(dispatch);
4490
- const postProcessParams = /* @__PURE__ */ new Map();
4491
- const postProcessParamsQuery = world.query({ with: [PostProcessParams] }).unwrap();
4492
- for (const row of postProcessParamsQuery) {
3460
+ dispatch = sortDispatchByQueue(dispatch);
3461
+ const postProcessParams = /* @__PURE__ */ new Map();
3462
+ const postProcessParamsQuery = world.query({ with: [PostProcessParams] }).unwrap();
3463
+ for (const row of postProcessParamsQuery) {
3464
+ const entity = row.entity;
3465
+ const read = world.get(entity, PostProcessParams);
3466
+ if (!read.ok) continue;
3467
+ postProcessParams.set(read.value.shader, read.value.data);
3468
+ }
3469
+ const tonemapCamera = cameras[0];
3470
+ if (tonemapCamera !== void 0) {
3471
+ postProcessParams.set(STANDARD_OUTPUT_TRANSFORM_FEATURE_ID, tonemapParams(tonemapCamera));
3472
+ }
3473
+ return {
3474
+ cameras,
3475
+ auxiliaryCameras,
3476
+ cubeCameras,
3477
+ reflectionProbes,
3478
+ lights,
3479
+ environment,
3480
+ environmentReady: true,
3481
+ ...volumetricFog === void 0 ? {} : { volumetricFog },
3482
+ renderables,
3483
+ dispatch,
3484
+ skylight,
3485
+ skylightCount,
3486
+ lightProbes,
3487
+ skybox,
3488
+ skyboxCount,
3489
+ fog,
3490
+ ...fogFailure === void 0 ? {} : { fogFailure },
3491
+ frustumStats: { culled: frustumCulled, total: frustumTotal },
3492
+ visibilityStats: { explicitlyHidden: explicitlyHidden.size },
3493
+ postProcessParams,
3494
+ visibilitySnapshots: [context.visibility],
3495
+ featureVisibilitySnapshots: [{ world, snapshot: context.visibility }],
3496
+ hiddenEntityReports: [...explicitlyHidden].map((entity) => ({ world, entity }))
3497
+ };
3498
+ }
3499
+ function defaultMaterialSnapshot(materialHandle = 0) {
3500
+ return {
3501
+ baseColor: vec3.create(0.5, 0.5, 0.5),
3502
+ metallic: 0,
3503
+ roughness: 1,
3504
+ materialHandle
3505
+ };
3506
+ }
3507
+ function identityProjection() {
3508
+ const projection = new Float32Array(16);
3509
+ projection[0] = 1;
3510
+ projection[5] = 1;
3511
+ projection[10] = 1;
3512
+ projection[15] = 1;
3513
+ return projection;
3514
+ }
3515
+
3516
+ // src/render-system-extract.ts
3517
+ function buildRectAreaWorldFrame(input) {
3518
+ const center = vec3.create(input.center[0] ?? 0, input.center[1] ?? 0, input.center[2] ?? 0);
3519
+ const axisX = vec3.normalize(
3520
+ vec3.create(),
3521
+ vec3.create(input.axisX[0] ?? 0, input.axisX[1] ?? 0, input.axisX[2] ?? 0)
3522
+ );
3523
+ const axisY = vec3.normalize(
3524
+ vec3.create(),
3525
+ vec3.create(input.axisY[0] ?? 0, input.axisY[1] ?? 0, input.axisY[2] ?? 0)
3526
+ );
3527
+ const normal = vec3.normalize(vec3.create(), vec3.cross(vec3.create(), axisX, axisY));
3528
+ normal[0] = normal[0] === 0 ? 0 : normal[0] ?? 0;
3529
+ normal[1] = normal[1] === 0 ? 0 : normal[1] ?? 0;
3530
+ normal[2] = normal[2] === 0 ? 0 : normal[2] ?? 0;
3531
+ return {
3532
+ center,
3533
+ axisX,
3534
+ axisY,
3535
+ normal,
3536
+ halfWidth: Math.max(0, input.width * 0.5),
3537
+ halfHeight: Math.max(0, input.height * 0.5)
3538
+ };
3539
+ }
3540
+ function rectAreaFacesPoint(frame, point) {
3541
+ const toPoint = vec3.create(
3542
+ (point[0] ?? 0) - (frame.center[0] ?? 0),
3543
+ (point[1] ?? 0) - (frame.center[1] ?? 0),
3544
+ (point[2] ?? 0) - (frame.center[2] ?? 0)
3545
+ );
3546
+ return vec3.dot(frame.normal, toPoint) > 0;
3547
+ }
3548
+ function sortDispatchByQueue(entries) {
3549
+ return entries.slice().sort((a, b) => a.queue - b.queue);
3550
+ }
3551
+ var DEFAULT_FORWARD_PASS = {
3552
+ name: "forward",
3553
+ program: { module: "forgeax::default-unlit", vertexEntry: "vs_main", fragmentEntry: "fs_main" },
3554
+ renderState: { tags: { LightMode: "Forward" }, queue: 2e3 }
3555
+ };
3556
+ function appendMaterialDispatchEntries(pendingDispatch, passes, entity, materialHandle, renderableIndex, layer, paramSnapshot, passIndexOffset = 0, materialShaderIdOverride) {
3557
+ const matchedPasses = selectPasses(passes, {});
3558
+ for (let pIdx = 0; pIdx < matchedPasses.length; pIdx++) {
3559
+ const pass = matchedPasses[pIdx];
3560
+ if (pass === void 0) continue;
3561
+ const passState = pass.renderState ?? {};
3562
+ const authoredShaderId = runtimeMaterialShaderId(pass.program.module, pass.name);
3563
+ const dispatchShaderId = authoredShaderId === "forgeax::default-shadow-caster" ? authoredShaderId : materialShaderIdOverride ?? authoredShaderId;
3564
+ pendingDispatch.push({
3565
+ entityIndex: entity,
3566
+ materialHandle,
3567
+ renderableIndex,
3568
+ passIndex: passIndexOffset + pIdx,
3569
+ queue: passState.queue ?? 2e3,
3570
+ layer,
3571
+ tags: passState.tags ?? {},
3572
+ renderState: pipelineRenderState(passState),
3573
+ // Material module identity is already closed in the cooked program.
3574
+ // It must never become a draw-time define map.
3575
+ defines: void 0,
3576
+ vertexEntry: pass.program.vertexEntry,
3577
+ fragmentEntry: pass.program.fragmentEntry,
3578
+ // A cooked Standard publication supplies one specialization key for its
3579
+ // forward/deferred artifact, but its ShadowCaster pass still owns the
3580
+ // engine depth-only shader. Keep the pass-local canonical identity so a
3581
+ // specialized Standard source (which only declares fs_main/fs_gbuffer)
3582
+ // is never asked to build an fs_shadow pipeline. Authored non-Standard
3583
+ // ShadowCaster modules retain the publication override and continue to
3584
+ // use their producer-owned source.
3585
+ materialShaderId: dispatchShaderId,
3586
+ paramSnapshot,
3587
+ ...passState.stencilReference !== void 0 && {
3588
+ stencilReference: passState.stencilReference
3589
+ }
3590
+ });
3591
+ }
3592
+ }
3593
+ function selectCameraTargetViews(candidates, options) {
3594
+ const display = candidates.find(
3595
+ (candidate) => candidate.entityKey === options.displayEntityKey && candidate.target === void 0
3596
+ );
3597
+ const rejected = [];
3598
+ const ordered = candidates.filter((candidate) => candidate.target !== void 0).sort((left, right) => left.worldId - right.worldId || left.entityKey - right.entityKey);
3599
+ const auxiliary = [];
3600
+ const seenTargets = /* @__PURE__ */ new Set();
3601
+ for (const candidate of ordered) {
3602
+ const duplicate = candidate.target !== void 0 && seenTargets.has(candidate.target);
3603
+ if (candidate.target !== void 0) seenTargets.add(candidate.target);
3604
+ if (candidate.entityKey === options.displayEntityKey) {
3605
+ rejected.push({ entityKey: candidate.entityKey, reason: "display-target" });
3606
+ continue;
3607
+ }
3608
+ if (candidate.target === void 0 || duplicate) {
3609
+ rejected.push({ entityKey: candidate.entityKey, reason: "duplicate-target" });
3610
+ continue;
3611
+ }
3612
+ if (auxiliary.length >= Math.max(0, options.budget)) {
3613
+ rejected.push({ entityKey: candidate.entityKey, reason: "budget" });
3614
+ continue;
3615
+ }
3616
+ auxiliary.push(candidate);
3617
+ }
3618
+ return {
3619
+ display,
3620
+ auxiliary: Object.freeze(auxiliary),
3621
+ rejected: Object.freeze(rejected)
3622
+ };
3623
+ }
3624
+ var Severity = Object.freeze({ Error: "error", Warning: "warning" });
3625
+ function createWorldInternalView(world) {
3626
+ return {
3627
+ _routeError(error, ctx) {
3628
+ routeWorldError(world, error, { systemName: ctx.systemName });
3629
+ },
3630
+ _getArrayView(entity, component, fieldName) {
3631
+ return readRenderArrayView(world, entity, component, fieldName);
3632
+ }
3633
+ };
3634
+ }
3635
+ function resolveCameraTarget(world, raw) {
3636
+ if (raw === void 0 || raw <= 0) return void 0;
3637
+ const resolved = world.sharedRefs.resolve(
3638
+ toShared(Math.round(raw))
3639
+ );
3640
+ return resolved.ok ? resolved.value : void 0;
3641
+ }
3642
+ var BUILTIN_USER_REGION_TEXTURE_FIELDS = [
3643
+ "baseColorTexture",
3644
+ "metallicRoughnessTexture",
3645
+ "normalTexture",
3646
+ "specularColorTexture",
3647
+ "emissiveTexture",
3648
+ "occlusionTexture",
3649
+ "transmissionTexture",
3650
+ "thicknessTexture"
3651
+ ];
3652
+ var BUILTIN_USER_REGION_TEXTURE_FIELD_SET = new Set(BUILTIN_USER_REGION_TEXTURE_FIELDS);
3653
+ var BUILTIN_BASE_COLOR_TEXTURE_FIELD_SET = /* @__PURE__ */ new Set(["baseColorTexture"]);
3654
+ function materialTextureFields(shaderId, fields) {
3655
+ if (fields !== void 0 && fields.size > 0) return fields;
3656
+ if (shaderId === "forgeax::default-standard-pbr" || shaderId === "forgeax::default-standard-pbr-skin" || shaderId === "forgeax::pbr-skin") {
3657
+ return BUILTIN_USER_REGION_TEXTURE_FIELD_SET;
3658
+ }
3659
+ if (shaderId === "forgeax::default-unlit" || shaderId === "forgeax::sprite" || shaderId === "forgeax::sprite-lit") {
3660
+ return BUILTIN_BASE_COLOR_TEXTURE_FIELD_SET;
3661
+ }
3662
+ return fields;
3663
+ }
3664
+ function runtimeMaterialShaderId(module, passName) {
3665
+ if (passName === "shadow-caster" && (module === "forgeax_material::standard" || module === "forgeax_material::unlit" || module === "forgeax::default-standard-pbr" || module === "forgeax::default-unlit")) {
3666
+ return "forgeax::default-shadow-caster";
3667
+ }
3668
+ switch (module) {
3669
+ case "forgeax_material::standard":
3670
+ return "forgeax::default-standard-pbr";
3671
+ case "forgeax_material::unlit":
3672
+ return "forgeax::default-unlit";
3673
+ case "forgeax_material::sprite":
3674
+ return "forgeax::sprite";
3675
+ case "forgeax_material::sprite-lit":
3676
+ return "forgeax::sprite-lit";
3677
+ default:
3678
+ return module;
3679
+ }
3680
+ }
3681
+ function runtimeMaterialShaderIdForMaterial(material, passes, assets) {
3682
+ const authored = runtimeMaterialShaderId(passes[0]?.program.module, passes[0]?.name);
3683
+ return assets.getMaterialProjectionForPayload(material)?.specializationKey ?? authored;
3684
+ }
3685
+ function pipelineRenderState(renderState) {
3686
+ if (renderState === void 0) return void 0;
3687
+ const {
3688
+ cullMode,
3689
+ depthCompare,
3690
+ depthWriteEnabled,
3691
+ blend,
3692
+ alphaToCoverageEnabled,
3693
+ stencil,
3694
+ stencilReadMask,
3695
+ stencilWriteMask,
3696
+ frontFace
3697
+ } = renderState;
3698
+ if (cullMode === void 0 && depthCompare === void 0 && depthWriteEnabled === void 0 && blend === void 0 && alphaToCoverageEnabled === void 0 && stencil === void 0 && stencilReadMask === void 0 && stencilWriteMask === void 0 && frontFace === void 0) {
3699
+ return void 0;
3700
+ }
3701
+ return {
3702
+ ...cullMode !== void 0 && { cullMode },
3703
+ ...depthCompare !== void 0 && { depthCompare },
3704
+ ...depthWriteEnabled !== void 0 && { depthWriteEnabled },
3705
+ ...blend !== void 0 && { blend },
3706
+ ...alphaToCoverageEnabled !== void 0 && { alphaToCoverageEnabled },
3707
+ ...stencil !== void 0 && { stencil },
3708
+ ...stencilReadMask !== void 0 && { stencilReadMask },
3709
+ ...stencilWriteMask !== void 0 && { stencilWriteMask },
3710
+ ...frontFace !== void 0 && { frontFace }
3711
+ };
3712
+ }
3713
+ var guidHandleInternByWorld = /* @__PURE__ */ new WeakMap();
3714
+ function internSharedRefFromGuid(world, assetsRef, guid, brand) {
3715
+ let perWorld = guidHandleInternByWorld.get(world);
3716
+ if (perWorld === void 0) {
3717
+ perWorld = /* @__PURE__ */ new Map();
3718
+ guidHandleInternByWorld.set(world, perWorld);
3719
+ }
3720
+ const key = `${guid.toLowerCase()}\0${brand}`;
3721
+ const cached = perWorld.get(key);
3722
+ const payload = assetsRef.lookup(guid);
3723
+ if (cached !== void 0 && cached.payload === payload) {
3724
+ const cachedHandle = toShared(cached.handle);
3725
+ if (world.sharedRefs.resolve(cachedHandle).ok) return cachedHandle;
3726
+ }
3727
+ if (cached !== void 0) {
3728
+ world.sharedRefs.release(toShared(cached.handle));
3729
+ perWorld.delete(key);
3730
+ }
3731
+ if (payload === void 0) return void 0;
3732
+ const handle = world.internSharedRef(brand, payload);
3733
+ perWorld.set(key, { handle, payload });
3734
+ return handle;
3735
+ }
3736
+ function resolveVideoFieldHandle(value, world, assetsRef) {
3737
+ const texture = materialTextureValue(value);
3738
+ const textureGuid = assetReferenceText(texture?.texture ?? value);
3739
+ if (textureGuid === void 0) return void 0;
3740
+ const payload = assetsRef.lookup(textureGuid);
3741
+ if (payload === void 0 || payload.kind !== "video") return void 0;
3742
+ return internSharedRefFromGuid(world, assetsRef, textureGuid, "VideoAsset");
3743
+ }
3744
+ function materialTextureValue(value) {
3745
+ if (typeof value !== "object" || value === null || Array.isArray(value)) return void 0;
3746
+ const textureValue = value;
3747
+ return typeof textureValue.texture === "number" || typeof textureValue.texture === "string" || textureValue.texture instanceof Uint8Array ? textureValue : void 0;
3748
+ }
3749
+ function assetReferenceText(value) {
3750
+ if (typeof value === "string") return value;
3751
+ if (value instanceof Uint8Array && isAssetGuidBytes(value)) return AssetGuid.format(value);
3752
+ return void 0;
3753
+ }
3754
+ function isAssetGuidBytes(value) {
3755
+ return value.byteLength === 16;
3756
+ }
3757
+ function materialNormalScale(values) {
3758
+ for (const [field, value] of Object.entries(values)) {
3759
+ if (field !== "normalTexture") continue;
3760
+ const scale = materialTextureValue(value)?.normalScale;
3761
+ return typeof scale === "number" && Number.isFinite(scale) ? scale : 1;
3762
+ }
3763
+ return 1;
3764
+ }
3765
+ function materialTextureRef(value) {
3766
+ return materialTextureValue(value)?.texture ?? value;
3767
+ }
3768
+ function collectMaterialTextureCoordinates(values) {
3769
+ const out = /* @__PURE__ */ new Map();
3770
+ for (const [field, value] of Object.entries(values)) {
3771
+ if (!field.endsWith("Texture")) continue;
3772
+ const coordinates = materialTextureValue(value)?.coordinates;
3773
+ if (coordinates !== void 0) out.set(field, coordinates);
3774
+ }
3775
+ return out;
3776
+ }
3777
+ function collectMaterialTextureSources(values, world) {
3778
+ const out = /* @__PURE__ */ new Map();
3779
+ for (const [field, value] of Object.entries(values)) {
3780
+ const reference = materialTextureRef(value);
3781
+ if (typeof reference !== "number") continue;
3782
+ const resolved = world.sharedRefs.resolve(toShared(reference));
3783
+ if (!resolved.ok || resolveRenderTargetMaterialSource(resolved.value) === void 0) continue;
3784
+ out.set(field, resolved.value);
3785
+ }
3786
+ return out;
3787
+ }
3788
+ function collectMaterialTextureSamplers(values, resolveSampler) {
3789
+ const out = /* @__PURE__ */ new Map();
3790
+ for (const [field, value] of Object.entries(values)) {
3791
+ const sampler = resolveSampler(materialTextureValue(value)?.sampler);
3792
+ if (sampler !== void 0) out.set(field, sampler);
3793
+ }
3794
+ const legacySampler = resolveSampler(values.sampler);
3795
+ if (legacySampler !== void 0 && !out.has("baseColorTexture")) {
3796
+ out.set("baseColorTexture", legacySampler);
3797
+ }
3798
+ return out;
3799
+ }
3800
+ function collectUserRegionTextureHandles(pv, shaderId, paramSchema, assetsRef, world, resolveTex, videoOut) {
3801
+ const fields = materialTextureFields(
3802
+ shaderId,
3803
+ paramSchema !== void 0 ? derive(paramSchema).textureFieldNames : shaderId !== void 0 ? assetsRef.materialShaderTextureFieldNames(shaderId) : void 0
3804
+ ) ?? BUILTIN_USER_REGION_TEXTURE_FIELDS;
3805
+ const out = /* @__PURE__ */ new Map();
3806
+ for (const field of fields) {
3807
+ const videoHandle = resolveVideoFieldHandle(pv[field], world, assetsRef);
3808
+ if (videoHandle !== void 0) {
3809
+ videoOut.set(field, videoHandle);
3810
+ continue;
3811
+ }
3812
+ const handle = resolveTex(materialTextureRef(pv[field]), "TextureAsset");
3813
+ if (handle !== void 0) out.set(field, handle);
3814
+ }
3815
+ return out;
3816
+ }
3817
+ var ENGINE_INJECTED_TEXTURE_FIELDS = /* @__PURE__ */ new Set(["emissiveTexture", "occlusionTexture"]);
3818
+ function isEngineInjectedTextureField(shaderId, fieldName) {
3819
+ return isStandardPbrMaterialShader(shaderId) && ENGINE_INJECTED_TEXTURE_FIELDS.has(fieldName);
3820
+ }
3821
+ function materialParametersToParamSchema(parameters, shaderId) {
3822
+ if (isCanonicalStandardPbrMaterialShader(shaderId)) {
3823
+ return STANDARD_PIPELINE_PARAM_SCHEMA;
3824
+ }
3825
+ const engineInjectedTextureFields = isStandardPbrMaterialShader(shaderId) ? ENGINE_INJECTED_TEXTURE_FIELDS : void 0;
3826
+ return parameters.flatMap((parameter) => {
3827
+ if (parameter.type === "bool") return [];
3828
+ if (parameter.type === "texture" || parameter.type === "texture_cube") {
3829
+ if (engineInjectedTextureFields?.has(parameter.name) === true) return [];
3830
+ return [
3831
+ {
3832
+ name: parameter.name,
3833
+ type: parameter.type === "texture_cube" ? "texture_cube" : "texture2d",
3834
+ default: parameter.default
3835
+ }
3836
+ ];
3837
+ }
3838
+ const numericDefault = typeof parameter.default === "number" ? parameter.default : Array.isArray(parameter.default) && parameter.default.every((entry) => typeof entry === "number") ? parameter.default : void 0;
3839
+ const numericEntry = {
3840
+ name: parameter.name,
3841
+ type: parameter.type,
3842
+ ...parameter.colorSpace === void 0 ? {} : { colorSpace: parameter.colorSpace }
3843
+ };
3844
+ return numericDefault === void 0 ? [numericEntry] : [{ ...numericEntry, default: numericDefault }];
3845
+ });
3846
+ }
3847
+ function materialColorParameterSchema(parameters, shaderId, assets) {
3848
+ if (shaderId === void 0) return parameters;
3849
+ const shader = assets.shaderRegistry.findMaterialArtifact(shaderId);
3850
+ if (!shader.ok) return parameters;
3851
+ const byName = new Map(
3852
+ shader.value.paramSchema.map((parameter) => [parameter.name, parameter])
3853
+ );
3854
+ for (const parameter of parameters) byName.set(parameter.name, parameter);
3855
+ return [...byName.values()];
3856
+ }
3857
+ var MATERIAL_PARENT_CHAIN_LIMIT = 128;
3858
+ function isDeepFrozen(value, seen = /* @__PURE__ */ new Set()) {
3859
+ if (typeof value !== "object" || value === null) return true;
3860
+ if (seen.has(value)) return true;
3861
+ if (!Object.isFrozen(value)) return false;
3862
+ seen.add(value);
3863
+ for (const child of Object.values(value)) {
3864
+ if (!isDeepFrozen(child, seen)) return false;
3865
+ }
3866
+ return true;
3867
+ }
3868
+ function captureMaterialCacheChain(root, assets) {
3869
+ const chain = [];
3870
+ const visitedParentGuids = /* @__PURE__ */ new Set();
3871
+ let current = root;
3872
+ for (let depth = 0; depth < MATERIAL_PARENT_CHAIN_LIMIT; depth += 1) {
3873
+ const parentRef = current.parent;
3874
+ const parentGuid = parentRef === void 0 ? void 0 : materialGuidText(parentRef);
3875
+ chain.push({ asset: current, parentGuid });
3876
+ if (parentGuid === void 0) return chain;
3877
+ if (parentRef === void 0) return void 0;
3878
+ if (visitedParentGuids.has(parentGuid)) return void 0;
3879
+ visitedParentGuids.add(parentGuid);
3880
+ const parent = assets.lookup(parentRef);
3881
+ if (parent?.kind !== "material") return void 0;
3882
+ current = parent;
3883
+ }
3884
+ return void 0;
3885
+ }
3886
+ function readPersistentMaterialSnapshot(cache, handleRaw, assets) {
3887
+ const entry = cache?.get(handleRaw);
3888
+ if (entry === void 0) return void 0;
3889
+ if (entry.crossFrameSafe && entry.chain !== void 0 && entry.catalogEpoch === assets.catalogEpoch)
3890
+ return entry;
3891
+ cache?.delete(handleRaw);
3892
+ return void 0;
3893
+ }
3894
+ function readStablePersistentMaterialSnapshot(cache, handleRaw, assets) {
3895
+ const entry = cache?.get(handleRaw);
3896
+ return entry?.crossFrameSafe === true && entry.chain !== void 0 && entry.catalogEpoch === assets.catalogEpoch ? entry : void 0;
3897
+ }
3898
+ function storeMaterialSnapshot(cache, handleRaw, snapshot, passes, root, assets) {
3899
+ const chain = captureMaterialCacheChain(root, assets);
3900
+ const entry = {
3901
+ snapshot,
3902
+ passes,
3903
+ crossFrameSafe: chain?.every(({ asset }) => isDeepFrozen(asset)) === true,
3904
+ ...chain === void 0 ? {} : { chain },
3905
+ ...chain === void 0 ? {} : { catalogEpoch: assets.catalogEpoch }
3906
+ };
3907
+ cache.set(handleRaw, entry);
3908
+ return entry;
3909
+ }
3910
+ function resolveMaterialSnapshot(handleRaw, world, assetsRef, materialSnapshotCache, persistentMaterialSnapshotCache) {
3911
+ if (handleRaw === 0) return defaultMaterialSnapshot(handleRaw);
3912
+ const cached = materialSnapshotCache?.get(handleRaw);
3913
+ if (cached !== void 0) return cached.snapshot;
3914
+ const stablePersistentCached = readStablePersistentMaterialSnapshot(
3915
+ persistentMaterialSnapshotCache,
3916
+ handleRaw,
3917
+ assetsRef
3918
+ );
3919
+ if (stablePersistentCached !== void 0) {
3920
+ materialSnapshotCache?.set(handleRaw, stablePersistentCached);
3921
+ return stablePersistentCached.snapshot;
3922
+ }
3923
+ const tagged = toShared(handleRaw);
3924
+ const res = resolveAssetHandle(world, tagged);
3925
+ if (!res.ok) return defaultMaterialSnapshot(handleRaw);
3926
+ const asset = res.value;
3927
+ if (asset.kind !== "material") return defaultMaterialSnapshot(handleRaw);
3928
+ const persistentCached = readPersistentMaterialSnapshot(
3929
+ persistentMaterialSnapshotCache,
3930
+ handleRaw,
3931
+ assetsRef
3932
+ );
3933
+ if (persistentCached !== void 0) {
3934
+ materialSnapshotCache?.set(handleRaw, persistentCached);
3935
+ return persistentCached.snapshot;
3936
+ }
3937
+ const resolvedResult = walkMaterialPassesOverSharedRefs(world, tagged, assetsRef);
3938
+ if (!resolvedResult.ok) return defaultMaterialSnapshot(handleRaw);
3939
+ const resolved = resolvedResult.value;
3940
+ const allPasses = resolved.passes;
3941
+ const firstPassShader = runtimeMaterialShaderIdForMaterial(asset, allPasses, assetsRef);
3942
+ const pv = materialValuesToLinearRuntime(
3943
+ resolved.values,
3944
+ materialColorParameterSchema(resolved.parameters ?? [], firstPassShader, assetsRef),
3945
+ resolved.colorSpace
3946
+ );
3947
+ const baseColorPv = pv.baseColor;
3948
+ const baseColor = vec3.create(
3949
+ baseColorPv?.[0] ?? 1,
3950
+ baseColorPv?.[1] ?? 1,
3951
+ baseColorPv?.[2] ?? 1
3952
+ );
3953
+ const metallicPv = typeof pv.metallic === "number" ? pv.metallic : 0;
3954
+ const roughnessPv = typeof pv.roughness === "number" ? pv.roughness : 0.5;
3955
+ const specularColorPv = pv.specularColor;
3956
+ const normalScalePv = materialNormalScale(pv);
3957
+ const paramSnap = {};
3958
+ for (const [k, v] of Object.entries(pv)) {
3959
+ if (typeof v === "number") paramSnap[k] = v;
3960
+ else if (typeof v === "string") paramSnap[k] = v;
3961
+ else if (Array.isArray(v) && v.every((x) => typeof x === "number")) {
3962
+ paramSnap[k] = v;
3963
+ }
3964
+ }
3965
+ const materialParamSchema = materialParametersToParamSchema(
3966
+ resolved.parameters ?? [],
3967
+ firstPassShader
3968
+ );
3969
+ const resolveTexLike = (value, brand) => {
3970
+ if (typeof value === "number") return toShared(value);
3971
+ if (typeof value === "string") {
3972
+ return internSharedRefFromGuid(world, assetsRef, value, brand);
3973
+ }
3974
+ return void 0;
3975
+ };
3976
+ const videoTextureFields = /* @__PURE__ */ new Map();
3977
+ const textureHandles = collectUserRegionTextureHandles(
3978
+ pv,
3979
+ firstPassShader,
3980
+ materialParamSchema.length > 0 ? materialParamSchema : void 0,
3981
+ assetsRef,
3982
+ world,
3983
+ resolveTexLike,
3984
+ videoTextureFields
3985
+ );
3986
+ const samplerHandles = collectMaterialTextureSamplers(
3987
+ pv,
3988
+ (value) => resolveTexLike(materialTextureRef(value), "SamplerAsset")
3989
+ );
3990
+ const textureSources = collectMaterialTextureSources(pv, world);
3991
+ const emissiveTextureHandle = resolveTexLike(
3992
+ materialTextureRef(pv.emissiveTexture),
3993
+ "TextureAsset"
3994
+ );
3995
+ const occlusionTextureHandle = resolveTexLike(
3996
+ materialTextureRef(pv.occlusionTexture),
3997
+ "TextureAsset"
3998
+ );
3999
+ const textureCoordinates = collectMaterialTextureCoordinates(pv);
4000
+ const baseColorTextureHandle = textureHandles.get("baseColorTexture");
4001
+ const metallicRoughnessTextureHandle = textureHandles.get("metallicRoughnessTexture");
4002
+ const normalTextureHandle = textureHandles.get("normalTexture");
4003
+ const emissivePv = pv.emissive;
4004
+ const snapshot = {
4005
+ baseColor,
4006
+ metallic: metallicPv,
4007
+ roughness: roughnessPv,
4008
+ ...specularColorPv !== void 0 && {
4009
+ specularColor: [
4010
+ specularColorPv[0] ?? 1,
4011
+ specularColorPv[1] ?? 1,
4012
+ specularColorPv[2] ?? 1
4013
+ ]
4014
+ },
4015
+ normalScale: normalScalePv,
4016
+ materialShaderId: firstPassShader,
4017
+ materialHandle: handleRaw,
4018
+ renderState: pipelineRenderState(allPasses[0]?.renderState),
4019
+ paramSnapshot: paramSnap,
4020
+ ...materialParamSchema.length > 0 && { materialParamSchema },
4021
+ ...textureCoordinates.size > 0 && { textureCoordinates },
4022
+ ...textureHandles.size > 0 && { textureHandles },
4023
+ ...textureSources.size > 0 && { textureSources },
4024
+ ...videoTextureFields.size > 0 && { videoTextureFields },
4025
+ ...samplerHandles.size > 0 && { samplerHandles },
4026
+ ...baseColorTextureHandle !== void 0 && { baseColorTexture: baseColorTextureHandle },
4027
+ ...metallicRoughnessTextureHandle !== void 0 && {
4028
+ metallicRoughnessTexture: metallicRoughnessTextureHandle
4029
+ },
4030
+ ...normalTextureHandle !== void 0 && { normalTexture: normalTextureHandle },
4031
+ ...emissivePv !== void 0 && {
4032
+ emissive: [emissivePv[0] ?? 0, emissivePv[1] ?? 0, emissivePv[2] ?? 0]
4033
+ },
4034
+ ...typeof pv.emissiveIntensity === "number" && { emissiveIntensity: pv.emissiveIntensity },
4035
+ ...emissiveTextureHandle !== void 0 && { emissiveTexture: emissiveTextureHandle },
4036
+ ...occlusionTextureHandle !== void 0 && { occlusionTexture: occlusionTextureHandle },
4037
+ ...typeof pv.occlusionStrength === "number" && { occlusionStrength: pv.occlusionStrength },
4038
+ // feat-city-glb Bug 5 (per-submesh transparency): derive `transparent`
4039
+ // from the first pass's `renderState.blend` presence, identical to the
4040
+ // entity-level snapshot builder (extractFrame archetype loop). Without
4041
+ // this, per-submesh materials (materials[i>=1], e.g. a glTF BLEND decal
4042
+ // submesh on a multi-material mesh) never carry the transparent flag, so
4043
+ // the record stage's LDR split + blend routing treats them as opaque and
4044
+ // their alpha=0 texels composite as black.
4045
+ transparent: allPasses[0]?.renderState?.blend !== void 0
4046
+ };
4047
+ const stored = materialSnapshotCache !== void 0 ? storeMaterialSnapshot(
4048
+ materialSnapshotCache,
4049
+ handleRaw,
4050
+ snapshot,
4051
+ allPasses,
4052
+ asset,
4053
+ assetsRef
4054
+ ) : void 0;
4055
+ const cacheEntry = stored ?? (persistentMaterialSnapshotCache !== void 0 ? storeMaterialSnapshot(
4056
+ persistentMaterialSnapshotCache,
4057
+ handleRaw,
4058
+ snapshot,
4059
+ allPasses,
4060
+ asset,
4061
+ assetsRef
4062
+ ) : void 0);
4063
+ if (cacheEntry?.crossFrameSafe === true) {
4064
+ persistentMaterialSnapshotCache?.set(handleRaw, cacheEntry);
4065
+ }
4066
+ return snapshot;
4067
+ }
4068
+ function computeFrustumCorners(vp, camNear, camFar, nearZ, farZ, projection) {
4069
+ const invVP = mat4.create();
4070
+ mat4.invert(invVP, vp);
4071
+ const corners = [];
4072
+ const span = camFar - camNear;
4073
+ const ndcNear = projection === "orthographic" ? (nearZ - camNear) / span : camFar * (nearZ - camNear) / (nearZ * span);
4074
+ const ndcFar = projection === "orthographic" ? (farZ - camNear) / span : camFar * (farZ - camNear) / (farZ * span);
4075
+ const signs = [-1, 1];
4076
+ for (const sx of signs) {
4077
+ for (const sy of signs) {
4078
+ corners.push(unprojectNDC(invVP, sx, sy, ndcNear));
4079
+ }
4080
+ }
4081
+ for (const sx of signs) {
4082
+ for (const sy of signs) {
4083
+ corners.push(unprojectNDC(invVP, sx, sy, ndcFar));
4084
+ }
4085
+ }
4086
+ return corners;
4087
+ }
4088
+ function unprojectNDC(invVP, ndcX, ndcY, ndcZ) {
4089
+ const ndc = vec3.create(ndcX, ndcY, ndcZ);
4090
+ const ws = vec3.create();
4091
+ mat4.unproject(ws, ndc, invVP);
4092
+ return ws;
4093
+ }
4094
+ function pssmSplit(nearPlane, farPlane, cascadeCount, splitLambda) {
4095
+ const EPS = 1e-6;
4096
+ if (farPlane <= nearPlane + EPS) {
4097
+ throw new ShadowInvalidConfigError("shadowDistance", farPlane, nearPlane + EPS);
4098
+ }
4099
+ const result = new Float32Array(cascadeCount);
4100
+ const m = cascadeCount;
4101
+ const n = nearPlane;
4102
+ const f = farPlane;
4103
+ const ratio = f / n;
4104
+ for (let i = 1; i <= m; i++) {
4105
+ const t = i / m;
4106
+ const logPart = n * ratio ** t;
4107
+ const uniformPart = n + t * (f - n);
4108
+ result[i - 1] = splitLambda * logPart + (1 - splitLambda) * uniformPart;
4109
+ }
4110
+ return result;
4111
+ }
4112
+ var POINT_SHADOW_FACE_LOOK = [
4113
+ vec3.create(1, 0, 0),
4114
+ vec3.create(-1, 0, 0),
4115
+ vec3.create(0, 1, 0),
4116
+ vec3.create(0, -1, 0),
4117
+ vec3.create(0, 0, 1),
4118
+ vec3.create(0, 0, -1)
4119
+ ];
4120
+ var POINT_SHADOW_FACE_UP = [
4121
+ vec3.create(0, -1, 0),
4122
+ vec3.create(0, -1, 0),
4123
+ vec3.create(0, 0, 1),
4124
+ vec3.create(0, 0, -1),
4125
+ vec3.create(0, -1, 0),
4126
+ vec3.create(0, -1, 0)
4127
+ ];
4128
+ function buildPointShadowMatrices(lightPos, near, far) {
4129
+ const fovY = Math.PI / 2;
4130
+ const aspect = 1;
4131
+ const proj = mat4.create();
4132
+ mat4.perspective(proj, fovY, aspect, near, far);
4133
+ const out = [];
4134
+ for (let i = 0; i < 6; i++) {
4135
+ const look = POINT_SHADOW_FACE_LOOK[i];
4136
+ const up = POINT_SHADOW_FACE_UP[i];
4137
+ const target = vec3.create(
4138
+ (lightPos[0] ?? 0) + (look[0] ?? 0),
4139
+ (lightPos[1] ?? 0) + (look[1] ?? 0),
4140
+ (lightPos[2] ?? 0) + (look[2] ?? 0)
4141
+ );
4142
+ const view = mat4.create();
4143
+ mat4.lookAt(view, lightPos, target, up);
4144
+ const vp = mat4.create();
4145
+ mat4.multiply(vp, proj, view);
4146
+ out.push(vp);
4147
+ }
4148
+ return out;
4149
+ }
4150
+ function computeDirectionalCsm(direction, config, cameraData) {
4151
+ const cascadeCount = Math.round(config.cascadeCount);
4152
+ const sNear = cameraData?.near ?? 0.1;
4153
+ const sFar = config.shadowDistance;
4154
+ const splits = pssmSplit(sNear, sFar, cascadeCount, config.splitLambda);
4155
+ const lightDirN = vec3.normalize(vec3.create(), direction);
4156
+ const lightTarget = vec3.create(lightDirN[0] ?? 0, lightDirN[1] ?? 0, lightDirN[2] ?? 0);
4157
+ const lightView = mat4.create();
4158
+ mat4.lookAt(lightView, vec3.create(0, 0, 0), lightTarget, vec3.create(0, 1, 0));
4159
+ if (cameraData === void 0) return null;
4160
+ const camProj = mat4.create();
4161
+ if (cameraData.projection === "orthographic") {
4162
+ mat4.orthographic(
4163
+ camProj,
4164
+ cameraData.orthoLeft,
4165
+ cameraData.orthoRight,
4166
+ cameraData.orthoTop,
4167
+ cameraData.orthoBottom,
4168
+ cameraData.near,
4169
+ cameraData.far
4170
+ );
4171
+ } else {
4172
+ mat4.perspective(camProj, cameraData.fov, cameraData.aspect, cameraData.near, cameraData.far);
4173
+ }
4174
+ const camView = mat4.create();
4175
+ mat4.invert(camView, cameraData.world);
4176
+ const cameraVP = mat4.create();
4177
+ mat4.multiply(cameraVP, camProj, camView);
4178
+ const resultLightViewProjs = [];
4179
+ const cascadeFits = [];
4180
+ let lightSpaceMaxZFull = -Infinity;
4181
+ const fullCorners = computeFrustumCorners(
4182
+ cameraVP,
4183
+ cameraData.near,
4184
+ cameraData.far,
4185
+ sNear,
4186
+ sFar,
4187
+ cameraData.projection
4188
+ );
4189
+ for (const ws of fullCorners) {
4190
+ const ls = vec3.create();
4191
+ mat4.transformVec3(ls, lightView, ws);
4192
+ if ((ls[2] ?? 0) > lightSpaceMaxZFull) lightSpaceMaxZFull = ls[2] ?? 0;
4193
+ }
4194
+ for (let cIdx = 0; cIdx < 4; cIdx++) {
4195
+ if (cIdx >= cascadeCount) {
4196
+ resultLightViewProjs.push(new Float32Array(16));
4197
+ continue;
4198
+ }
4199
+ const cascadeNear = cIdx === 0 ? sNear : splits[cIdx - 1] ?? sFar;
4200
+ const cascadeFar = splits[cIdx] ?? sFar;
4201
+ const corners = computeFrustumCorners(
4202
+ cameraVP,
4203
+ cameraData.near,
4204
+ cameraData.far,
4205
+ cascadeNear,
4206
+ cascadeFar,
4207
+ cameraData.projection
4208
+ );
4209
+ const lightMVP = mat4.clone(lightView);
4210
+ let minX = Infinity;
4211
+ let maxX = -Infinity;
4212
+ let minY = Infinity;
4213
+ let maxY = -Infinity;
4214
+ let minZ = Infinity;
4215
+ let maxZ = -Infinity;
4216
+ for (const ws of corners) {
4217
+ const ls = vec3.create();
4218
+ mat4.transformVec3(ls, lightMVP, ws);
4219
+ if ((ls[0] ?? 0) < minX) minX = ls[0] ?? 0;
4220
+ if ((ls[0] ?? 0) > maxX) maxX = ls[0] ?? 0;
4221
+ if ((ls[1] ?? 0) < minY) minY = ls[1] ?? 0;
4222
+ if ((ls[1] ?? 0) > maxY) maxY = ls[1] ?? 0;
4223
+ if ((ls[2] ?? 0) < minZ) minZ = ls[2] ?? 0;
4224
+ if ((ls[2] ?? 0) > maxZ) maxZ = ls[2] ?? 0;
4225
+ }
4226
+ const nearZ = Math.max(maxZ, lightSpaceMaxZFull);
4227
+ const orthoProj = mat4.create();
4228
+ mat4.orthographic(orthoProj, minX, maxX, maxY, minY, -nearZ, -minZ);
4229
+ cascadeFits.push({
4230
+ split: splits[cIdx] ?? sFar,
4231
+ minX,
4232
+ maxX,
4233
+ minY,
4234
+ maxY,
4235
+ minZ,
4236
+ maxZ
4237
+ });
4238
+ resultLightViewProjs.push(new Float32Array(mat4.multiply(mat4.create(), orthoProj, lightView)));
4239
+ }
4240
+ const projection = projectDirectionalShadow(
4241
+ cascadeFits,
4242
+ config.mapSize,
4243
+ config.shadowFilter ?? 2,
4244
+ config.shadowAngularRadius ?? 465e-5,
4245
+ config.maxPenumbraTexels ?? 32
4246
+ );
4247
+ if (projection === void 0) return null;
4248
+ return {
4249
+ lightViewProj: resultLightViewProjs,
4250
+ splitPlanes: projection.splitPlanes,
4251
+ cascadeCount,
4252
+ cascadeBlend: config.cascadeBlend,
4253
+ shadowMapSize: config.mapSize,
4254
+ directionalShadowQuality: projection.directionalShadowQuality
4255
+ };
4256
+ }
4257
+ var ALL_VISIBLE = {
4258
+ diagnostics: [],
4259
+ hasAnyIntent: false,
4260
+ hasAnyHiddenIntent: false,
4261
+ get: () => void 0,
4262
+ effective: () => "visible"
4263
+ };
4264
+ function collectCameraSnapshots(world) {
4265
+ const worldInternal = createWorldInternalView(world);
4266
+ const cameras = [];
4267
+ const cameraQuery = world.query({ read: [Camera], optional: [MotionBlur], with: [Transform, GlobalTransform] }).unwrap();
4268
+ for (const row of cameraQuery) {
4269
+ const cam = row.get(Camera);
4270
+ const motionBlur = row.get(MotionBlur);
4493
4271
  const entity = row.entity;
4494
- const read = world.get(entity, PostProcessParams);
4495
- if (!read.ok) continue;
4496
- postProcessParams.set(read.value.shader, read.value.data);
4272
+ const view = worldInternal._getArrayView(entity, GlobalTransform, "world");
4273
+ if (view === void 0) continue;
4274
+ const worldMat = new Float32Array(view);
4275
+ const target = resolveCameraTarget(world, cam.target);
4276
+ cameras.push({
4277
+ entityKey: entity,
4278
+ ...target === void 0 ? {} : { target },
4279
+ historyVersion: cam.historyVersion,
4280
+ position: mat4.getTranslation(vec3.create(), worldMat),
4281
+ world: worldMat,
4282
+ fov: cam.fov,
4283
+ aspect: cam.aspect,
4284
+ near: cam.near,
4285
+ far: cam.far,
4286
+ projection: cameraProjectionFromF32(cam.projection),
4287
+ orthoLeft: cam.left,
4288
+ orthoRight: cam.right,
4289
+ orthoBottom: cam.bottom,
4290
+ orthoTop: cam.top,
4291
+ tonemap: tonemapFromF32(cam.tonemap),
4292
+ exposure: cam.exposure,
4293
+ whitePoint: cam.whitePoint,
4294
+ antialias: antialiasFromF32(cam.antialias),
4295
+ bloom: bloomEnabledFromF32(cam.bloom),
4296
+ bloomThreshold: cam.bloomThreshold,
4297
+ bloomIntensity: cam.bloomIntensity,
4298
+ bloomBlurRadius: cam.bloomBlurRadius,
4299
+ clearColor: [
4300
+ cam.clearColor[0] ?? 0,
4301
+ cam.clearColor[1] ?? 0,
4302
+ cam.clearColor[2] ?? 0,
4303
+ cam.clearColor[3] ?? 1
4304
+ ],
4305
+ ...motionBlur === void 0 ? {} : {
4306
+ motionBlur: {
4307
+ shutterAngle: motionBlur.shutterAngle,
4308
+ maxRadiusPixels: motionBlur.maxRadiusPixels,
4309
+ sampleCount: motionBlur.sampleCount
4310
+ }
4311
+ }
4312
+ });
4497
4313
  }
4498
- const tonemapCamera = cameras[0];
4499
- if (tonemapCamera !== void 0) {
4500
- postProcessParams.set(STANDARD_OUTPUT_TRANSFORM_FEATURE_ID, tonemapParams(tonemapCamera));
4314
+ return cameras;
4315
+ }
4316
+ function collectCubeCameraSnapshots(world) {
4317
+ const worldInternal = createWorldInternalView(world);
4318
+ const snapshots = [];
4319
+ const query = world.query({ read: [CubeCamera], with: [Transform, GlobalTransform] }).unwrap();
4320
+ for (const row of query) {
4321
+ const camera = row.get(CubeCamera);
4322
+ const target = resolveCameraTarget(world, camera.target);
4323
+ if (target === void 0) continue;
4324
+ const view = worldInternal._getArrayView(row.entity, GlobalTransform, "world");
4325
+ if (view === void 0) continue;
4326
+ snapshots.push({
4327
+ entityKey: row.entity,
4328
+ target,
4329
+ position: [view[12] ?? 0, view[13] ?? 0, view[14] ?? 0],
4330
+ near: camera.near,
4331
+ far: camera.far,
4332
+ updateIntent: cubeCameraUpdateIntentFromF32(camera.updateIntent),
4333
+ requestVersion: camera.requestVersion,
4334
+ faceBudget: camera.faceBudget
4335
+ });
4336
+ }
4337
+ return snapshots;
4338
+ }
4339
+ function collectReflectionProbeFacts(world) {
4340
+ const worldInternal = createWorldInternalView(world);
4341
+ const facts = [];
4342
+ const query = world.query({ read: [ReflectionProbe], with: [Transform, GlobalTransform] }).unwrap();
4343
+ for (const row of query) {
4344
+ const probe = row.get(ReflectionProbe);
4345
+ const view = worldInternal._getArrayView(row.entity, GlobalTransform, "world");
4346
+ if (view === void 0) {
4347
+ continue;
4348
+ }
4349
+ facts.push({
4350
+ worldId: 0,
4351
+ entityKey: row.entity,
4352
+ center: [view[12] ?? 0, view[13] ?? 0, view[14] ?? 0],
4353
+ halfExtents: [
4354
+ probe.halfExtents[0] ?? 0,
4355
+ probe.halfExtents[1] ?? 0,
4356
+ probe.halfExtents[2] ?? 0
4357
+ ],
4358
+ priority: probe.priority,
4359
+ intensity: probe.intensity,
4360
+ resolution: probe.resolution,
4361
+ revision: probe.invalidationVersion,
4362
+ updateIntent: reflectionProbeUpdateIntentFromF32(probe.updateIntent),
4363
+ invalidationVersion: probe.invalidationVersion
4364
+ });
4501
4365
  }
4366
+ return facts;
4367
+ }
4368
+ function selectCameraRoles(world) {
4369
+ const cameras = collectCameraSnapshots(world);
4370
+ const displayEntities = cameras.filter((camera) => camera.target === void 0).map((camera) => camera.entityKey ?? 0);
4371
+ const activeCameraIndex = selectActiveCameraIndex(
4372
+ displayEntities,
4373
+ getActiveCamera(world)?.entity
4374
+ );
4375
+ const displayEntityKey = displayEntities[activeCameraIndex] ?? displayEntities[0];
4376
+ const display = displayEntityKey === void 0 ? [] : cameras.filter((camera) => camera.entityKey === displayEntityKey);
4377
+ const candidates = cameras.map((camera) => ({
4378
+ worldId: 0,
4379
+ entityKey: camera.entityKey ?? 0,
4380
+ ...camera.target === void 0 ? {} : { target: camera.target },
4381
+ requestVersion: 0,
4382
+ update: "continuous"
4383
+ }));
4384
+ const selected = selectCameraTargetViews(candidates, {
4385
+ ...displayEntityKey === void 0 ? {} : { displayEntityKey },
4386
+ budget: 1
4387
+ });
4388
+ const auxiliary = selected.auxiliary.flatMap(
4389
+ (candidate) => cameras.filter((camera) => camera.entityKey === candidate.entityKey)
4390
+ );
4391
+ return { display, auxiliary };
4392
+ }
4393
+ function tonemapParams(camera) {
4394
+ const bytes = new ArrayBuffer(16);
4395
+ const floats = new Float32Array(bytes);
4396
+ const integers = new Uint32Array(bytes);
4397
+ floats[0] = camera.exposure;
4398
+ floats[1] = camera.whitePoint;
4399
+ integers[2] = tonemapToU32(camera.tonemap);
4400
+ return new Uint8Array(bytes);
4401
+ }
4402
+ function prepareExtractContext(world, options = {}) {
4403
+ const renderables = options.renderables ?? "full";
4502
4404
  return {
4503
- cameras,
4504
- auxiliaryCameras,
4505
- cubeCameras,
4506
- reflectionProbes,
4507
- lights,
4508
- environment,
4509
- environmentReady: true,
4510
- ...volumetricFog === void 0 ? {} : { volumetricFog },
4405
+ assets: options.assets,
4406
+ pipelineState: options.pipelineState,
4407
+ materialSnapshotCache: options.materialSnapshotCache,
4408
+ resourceOwnerFog: options.resourceOwnerFog,
4409
+ cull: options.cull ?? "self",
4410
+ cullCameras: options.cullCameras,
4511
4411
  renderables,
4512
- dispatch,
4513
- skylight,
4514
- skylightCount,
4515
- lightProbes,
4516
- skybox,
4517
- skyboxCount,
4518
- fog,
4519
- ...fogFailure === void 0 ? {} : { fogFailure },
4520
- frustumStats: { culled: frustumCulled, total: frustumTotal },
4521
- visibilityStats: { explicitlyHidden: explicitlyHidden.size },
4522
- postProcessParams,
4523
- visibilitySnapshots: [context.visibility],
4524
- featureVisibilitySnapshots: [{ world, snapshot: context.visibility }],
4525
- hiddenEntityReports: [...explicitlyHidden].map((entity) => ({ world, entity }))
4412
+ renderableEntities: options.renderableEntities,
4413
+ retainHidden: options.retainHidden ?? false,
4414
+ visibility: renderables === "none" ? ALL_VISIBLE : resolveVisibility(world),
4415
+ getMaterialShaderArtifact: options.getMaterialShaderArtifact,
4416
+ instanceCollections: options.instanceCollections
4526
4417
  };
4527
4418
  }
4528
- function defaultMaterialSnapshot(materialHandle = 0) {
4529
- return {
4530
- baseColor: vec3.create(0.5, 0.5, 0.5),
4531
- metallic: 0,
4532
- roughness: 1,
4533
- materialHandle
4419
+ var fogQueries = /* @__PURE__ */ new WeakMap();
4420
+ var fogLkgStates = /* @__PURE__ */ new WeakMap();
4421
+ function fogState(world) {
4422
+ const existing = fogLkgStates.get(world);
4423
+ if (existing !== void 0) return existing;
4424
+ const created = { lkg: void 0, failure: void 0 };
4425
+ fogLkgStates.set(world, created);
4426
+ return created;
4427
+ }
4428
+ function selectFogFrame(world) {
4429
+ const fogCandidates = [];
4430
+ let fogQuery = fogQueries.get(world);
4431
+ if (fogQuery === void 0) {
4432
+ fogQuery = world.query({ read: [Fog] }).unwrap();
4433
+ fogQueries.set(world, fogQuery);
4434
+ }
4435
+ for (const row of fogQuery) {
4436
+ const value = row.get(Fog);
4437
+ fogCandidates.push({
4438
+ entityKey: row.entity,
4439
+ color: [value.color[0] ?? 0, value.color[1] ?? 0, value.color[2] ?? 0],
4440
+ density: value.density,
4441
+ heightFalloff: value.heightFalloff,
4442
+ maxOpacity: value.maxOpacity
4443
+ });
4444
+ }
4445
+ const fogSelection = selectEnvironment({
4446
+ environments: [],
4447
+ fogs: fogCandidates,
4448
+ suns: [],
4449
+ lane: "direct"
4450
+ });
4451
+ const state = fogState(world);
4452
+ if (fogSelection.ok) {
4453
+ state.lkg = fogSelection.value.fog;
4454
+ state.failure = void 0;
4455
+ return fogSelection.value.fog;
4456
+ }
4457
+ state.failure = {
4458
+ code: fogSelection.error.code,
4459
+ expected: fogSelection.error.expected,
4460
+ hint: fogSelection.error.hint,
4461
+ detail: Object.freeze({ ...fogSelection.error.detail })
4534
4462
  };
4463
+ routeWorldError(world, fogSelection.error, {
4464
+ systemName: "RenderSystem.extract (fog-selection)"
4465
+ });
4466
+ if (state.lkg !== void 0) return state.lkg;
4467
+ throw fogSelection.error;
4535
4468
  }
4536
- function identityProjection() {
4537
- const projection = new Float32Array(16);
4538
- projection[0] = 1;
4539
- projection[5] = 1;
4540
- projection[10] = 1;
4541
- projection[15] = 1;
4542
- return projection;
4469
+ function textureDigest(texture) {
4470
+ let hash = 2166136261;
4471
+ for (const byte of texture.data) hash = Math.imul(hash ^ byte, 16777619) >>> 0;
4472
+ return `fnv1a:${hash.toString(16).padStart(8, "0")}`;
4473
+ }
4474
+ function extractVolumeSnapshot(world, assets, worldInternal) {
4475
+ const query = world.query({ read: [VolumetricFog] }).unwrap();
4476
+ const authorings = [];
4477
+ let selectedLight;
4478
+ let first;
4479
+ for (const row of query) {
4480
+ const source = row.get(VolumetricFog);
4481
+ if (source.light === null) continue;
4482
+ const selected = resolveSelectedVolumetricLight(world, source.light);
4483
+ if (selected.status === "available") {
4484
+ const pairSpot = source.spotLight === null ? void 0 : source.spotLight;
4485
+ selectedLight ??= {
4486
+ entity: selected.entity,
4487
+ kind: selected.kind,
4488
+ ...selected.kind === "point" ? { pointLightEntity: selected.entity } : {},
4489
+ ...pairSpot === void 0 ? {} : { spotLightEntity: pairSpot }
4490
+ };
4491
+ }
4492
+ const densityHandle = toShared(Math.round(Number(source.density)));
4493
+ const resolved = resolveAssetHandle(world, densityHandle);
4494
+ if (!resolved.ok || resolved.value.kind !== "texture") {
4495
+ worldInternal._routeError(
4496
+ resolved.ok ? new RhiError({
4497
+ code: "asset-not-registered",
4498
+ expected: "VolumetricFog.density resolves to a TextureAsset",
4499
+ hint: "load a TextureAsset before assigning it to VolumetricFog.density",
4500
+ detail: { assetHandle: Number(densityHandle) }
4501
+ }) : resolved.error,
4502
+ { severity: Severity.Error, systemName: "RenderSystem.extract (volumetric-density)" }
4503
+ );
4504
+ return { status: "degraded" };
4505
+ }
4506
+ const densityAsset = resolved.value;
4507
+ const guid = assets?.guidOf(densityAsset) ?? `handle:${Number(densityHandle)}`;
4508
+ const shape = densityAsset.shape;
4509
+ const density = shape.viewDimension === "3d" ? {
4510
+ guid,
4511
+ generation: 1,
4512
+ shape: {
4513
+ viewDimension: "3d",
4514
+ extent: {
4515
+ width: shape.extent.width,
4516
+ height: shape.extent.height,
4517
+ depth: shape.extent.depth
4518
+ }
4519
+ },
4520
+ format: densityAsset.format,
4521
+ colorSpace: densityAsset.colorSpace
4522
+ } : {
4523
+ guid,
4524
+ generation: 1,
4525
+ shape: {
4526
+ viewDimension: shape.viewDimension,
4527
+ extent: shape.viewDimension === "2d-array" ? {
4528
+ width: shape.extent.width,
4529
+ height: shape.extent.height,
4530
+ layers: shape.extent.layers
4531
+ } : { width: shape.extent.width, height: shape.extent.height }
4532
+ },
4533
+ format: densityAsset.format,
4534
+ colorSpace: densityAsset.colorSpace
4535
+ };
4536
+ const authoring = {
4537
+ light: source.light,
4538
+ density,
4539
+ bounds: {
4540
+ min: [source.boundsMin[0] ?? 0, source.boundsMin[1] ?? 0, source.boundsMin[2] ?? 0],
4541
+ max: [source.boundsMax[0] ?? 0, source.boundsMax[1] ?? 0, source.boundsMax[2] ?? 0]
4542
+ },
4543
+ extinction: [source.extinction[0] ?? 0, source.extinction[1] ?? 0, source.extinction[2] ?? 0],
4544
+ albedo: [source.albedo[0] ?? 0, source.albedo[1] ?? 0, source.albedo[2] ?? 0],
4545
+ emission: [source.emission[0] ?? 0, source.emission[1] ?? 0, source.emission[2] ?? 0],
4546
+ anisotropy: source.anisotropy,
4547
+ maxDistance: source.maxDistance
4548
+ };
4549
+ authorings.push(authoring);
4550
+ first ??= { densityHandle, densityAsset, guid };
4551
+ }
4552
+ if (authorings.length === 0) return void 0;
4553
+ const extracted = extractVolumetricFog(authorings);
4554
+ if (!extracted.ok) {
4555
+ worldInternal._routeError(extracted.error, {
4556
+ severity: Severity.Error,
4557
+ systemName: "RenderSystem.extract (volumetric-fog)"
4558
+ });
4559
+ return {
4560
+ status: "degraded",
4561
+ ...first === void 0 ? {} : {
4562
+ densityHandle: first.densityHandle,
4563
+ densityAsset: first.densityAsset,
4564
+ guid: first.guid,
4565
+ generation: 1,
4566
+ digest: textureDigest(first.densityAsset),
4567
+ ...selectedLight === void 0 ? {} : {
4568
+ lightEntity: selectedLight.entity,
4569
+ lightKind: selectedLight.kind,
4570
+ ...selectedLight.pointLightEntity === void 0 ? {} : { pointLightEntity: selectedLight.pointLightEntity },
4571
+ ...selectedLight.spotLightEntity === void 0 ? {} : { spotLightEntity: selectedLight.spotLightEntity }
4572
+ }
4573
+ }
4574
+ };
4575
+ }
4576
+ if (extracted.value.status === "off" || first === void 0) return { status: "off" };
4577
+ const selectedLightFields = selectedLight === void 0 ? {} : {
4578
+ lightEntity: selectedLight.entity,
4579
+ lightKind: selectedLight.kind,
4580
+ ...selectedLight.pointLightEntity === void 0 ? {} : { pointLightEntity: selectedLight.pointLightEntity },
4581
+ ...selectedLight.spotLightEntity === void 0 ? {} : { spotLightEntity: selectedLight.spotLightEntity }
4582
+ };
4583
+ return {
4584
+ status: "available",
4585
+ fog: extracted.value.fog,
4586
+ densityHandle: first.densityHandle,
4587
+ densityAsset: first.densityAsset,
4588
+ guid: first.guid,
4589
+ generation: 1,
4590
+ digest: textureDigest(first.densityAsset),
4591
+ ...selectedLightFields
4592
+ };
4543
4593
  }
4544
4594
  var PROBE_R_MIN = R_MIN;
4545
4595
  var PROBE_MAX_CONTRIBUTORS = PROBE_BLEND_RECORD_CAPACITY;
@@ -5080,6 +5130,6 @@ var ProbeBlendSceneProjection = class {
5080
5130
  }
5081
5131
  };
5082
5132
 
5083
- export { InstanceBoundsCache, OcclusionRenderRuntime, PROBE_MAX_CONTRIBUTORS, ProbeBlendSceneProjection, admitProbeContributors, blendLightProbes, buildRectAreaWorldFrame, createCookieProjectionMatrixData, createExtendedLightingState, extractFrames, fingerprintNumericArray, glyphTextLayoutSystem, primitiveKey, primitiveKeyId, projectExtendedLightingInspection, promoteExtendedLightingCandidate, recordExtendedLightingFailure, rectAreaFacesPoint, scaledProbeWeight, setOcclusionRuntimeTestHooks, viewKey, viewKeyId };
5084
- //# sourceMappingURL=chunk-DIYW3M5D.mjs.map
5085
- //# sourceMappingURL=chunk-DIYW3M5D.mjs.map
5133
+ export { InstanceBoundsCache, OcclusionRenderRuntime, PROBE_MAX_CONTRIBUTORS, ProbeBlendSceneProjection, SKYLIGHT_RECOVERY_FALLBACK, admitProbeContributors, blendLightProbes, buildRectAreaWorldFrame, createCookieProjectionMatrixData, createExtendedLightingState, extractFrames, fingerprintNumericArray, glyphTextLayoutSystem, primitiveKey, primitiveKeyId, projectExtendedLightingInspection, promoteExtendedLightingCandidate, recordExtendedLightingFailure, rectAreaFacesPoint, scaledProbeWeight, setOcclusionRuntimeTestHooks, viewKey, viewKeyId };
5134
+ //# sourceMappingURL=chunk-N3RT2EUR.mjs.map
5135
+ //# sourceMappingURL=chunk-N3RT2EUR.mjs.map