@forgeax/engine-render 0.1.23 → 0.1.24

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