@woosh/meep-engine 3.52.0 → 3.54.0

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 (515) hide show
  1. package/package.json +1 -1
  2. package/src/core/math/noise/worley_noise_2d.d.ts +14 -0
  3. package/src/core/math/noise/worley_noise_2d.d.ts.map +1 -0
  4. package/src/core/math/noise/worley_noise_2d.js +74 -0
  5. package/src/engine/ecs/animation/Animation.d.ts +4 -25
  6. package/src/engine/ecs/animation/Animation.d.ts.map +1 -1
  7. package/src/engine/ecs/animation/Animation.js +9 -41
  8. package/src/engine/ecs/ik/IKGoalSystem.d.ts +4 -5
  9. package/src/engine/ecs/ik/IKGoalSystem.d.ts.map +1 -1
  10. package/src/engine/ecs/ik/IKGoalSystem.js +4 -6
  11. package/src/engine/graphics/ecs/mesh/assetTypeByPath.js +1 -1
  12. package/src/engine/graphics/texture/atlas/CachingTextureAtlas.d.ts +6 -1
  13. package/src/engine/graphics/texture/atlas/CachingTextureAtlas.d.ts.map +1 -1
  14. package/src/engine/graphics/texture/atlas/CachingTextureAtlas.js +363 -325
  15. package/src/engine/graphics/texture/atlas/ManagedTextureAtlas.d.ts +93 -14
  16. package/src/engine/graphics/texture/atlas/ManagedTextureAtlas.d.ts.map +1 -1
  17. package/src/engine/graphics/texture/atlas/ManagedTextureAtlas.js +322 -183
  18. package/src/engine/graphics/texture/atlas/ReferencedTextureAtlas.d.ts +9 -0
  19. package/src/engine/graphics/texture/atlas/ReferencedTextureAtlas.d.ts.map +1 -1
  20. package/src/engine/graphics/texture/atlas/ReferencedTextureAtlas.js +110 -83
  21. package/src/engine/graphics/texture/atlas/SpriteAtlas.d.ts +42 -0
  22. package/src/engine/graphics/texture/atlas/SpriteAtlas.d.ts.map +1 -0
  23. package/src/engine/{graphics3/decal/DecalAtlas.js → graphics/texture/atlas/SpriteAtlas.js} +202 -194
  24. package/src/engine/graphics/texture/atlas/SpriteAtlasFill.d.ts +12 -0
  25. package/src/engine/graphics/texture/atlas/SpriteAtlasFill.d.ts.map +1 -0
  26. package/src/engine/graphics/texture/atlas/SpriteAtlasFill.js +30 -0
  27. package/src/engine/graphics/texture/atlas/TextureAtlas.d.ts +7 -0
  28. package/src/engine/graphics/texture/atlas/TextureAtlas.d.ts.map +1 -1
  29. package/src/engine/graphics/texture/atlas/TextureAtlas.js +11 -1
  30. package/src/engine/graphics/texture/atlas/sprite_atlas_border.d.ts +52 -0
  31. package/src/engine/graphics/texture/atlas/sprite_atlas_border.d.ts.map +1 -0
  32. package/src/engine/{graphics3/decal/decal_atlas_border.js → graphics/texture/atlas/sprite_atlas_border.js} +69 -69
  33. package/src/engine/graphics3/AnimationGraphSystem.d.ts +9 -22
  34. package/src/engine/graphics3/AnimationGraphSystem.d.ts.map +1 -1
  35. package/src/engine/graphics3/AnimationGraphSystem.js +58 -203
  36. package/src/engine/graphics3/AnimationSystem.d.ts +7 -18
  37. package/src/engine/graphics3/AnimationSystem.d.ts.map +1 -1
  38. package/src/engine/graphics3/AnimationSystem.js +62 -243
  39. package/src/engine/graphics3/DecalSystem.d.ts.map +1 -1
  40. package/src/engine/graphics3/DecalSystem.js +447 -450
  41. package/src/engine/graphics3/FoliageWindSystem.d.ts +67 -0
  42. package/src/engine/graphics3/FoliageWindSystem.d.ts.map +1 -0
  43. package/src/engine/graphics3/FoliageWindSystem.js +257 -0
  44. package/src/engine/graphics3/GPUFluidSystem.d.ts +7 -0
  45. package/src/engine/graphics3/GPUFluidSystem.d.ts.map +1 -1
  46. package/src/engine/graphics3/GPUFluidSystem.js +39 -1
  47. package/src/engine/graphics3/GPUParticleEmitterSystem.d.ts +9 -23
  48. package/src/engine/graphics3/GPUParticleEmitterSystem.d.ts.map +1 -1
  49. package/src/engine/graphics3/GPUParticleEmitterSystem.js +904 -949
  50. package/src/engine/graphics3/animation/AnimationClips.d.ts +107 -13
  51. package/src/engine/graphics3/animation/AnimationClips.d.ts.map +1 -1
  52. package/src/engine/graphics3/animation/AnimationClips.js +207 -10
  53. package/src/engine/graphics3/animation/AnimationClipsSink.d.ts +89 -0
  54. package/src/engine/graphics3/animation/AnimationClipsSink.d.ts.map +1 -0
  55. package/src/engine/graphics3/animation/AnimationClipsSink.js +205 -0
  56. package/src/engine/graphics3/animation/FoliageWindRigs.d.ts +43 -0
  57. package/src/engine/graphics3/animation/FoliageWindRigs.d.ts.map +1 -0
  58. package/src/engine/graphics3/animation/FoliageWindRigs.js +69 -0
  59. package/src/engine/graphics3/animation/write_clip_playback.d.ts +17 -11
  60. package/src/engine/graphics3/animation/write_clip_playback.d.ts.map +1 -1
  61. package/src/engine/graphics3/animation/write_clip_playback.js +36 -27
  62. package/src/engine/graphics3/decal/GPUDecalRenderer.d.ts.map +1 -1
  63. package/src/engine/graphics3/decal/GPUDecalRenderer.js +8 -6
  64. package/src/engine/graphics3/decal/shader_decal_composite.d.ts.map +1 -1
  65. package/src/engine/graphics3/decal/shader_decal_composite.js +11 -5
  66. package/src/engine/graphics3/pose/collect_entity_playbacks.d.ts +7 -7
  67. package/src/engine/graphics3/pose/collect_entity_playbacks.d.ts.map +1 -1
  68. package/src/engine/graphics3/pose/collect_entity_playbacks.js +14 -15
  69. package/src/engine/graphics3/prefab/Prefab.d.ts +6 -0
  70. package/src/engine/graphics3/prefab/Prefab.d.ts.map +1 -1
  71. package/src/engine/graphics3/prefab/Prefab.js +169 -162
  72. package/src/engine/graphics3/prefab/prefab_compile.d.ts.map +1 -1
  73. package/src/engine/graphics3/prefab/prefab_compile.js +369 -334
  74. package/src/engine/graphics3/prefab/prefab_instantiate.d.ts.map +1 -1
  75. package/src/engine/graphics3/prefab/prefab_instantiate.js +43 -2
  76. package/src/engine/graphics3/terrain/chunk_terrain_material_surface.js +2 -2
  77. package/src/engine/physics/ecs/ContactProbeSystem.d.ts +2 -3
  78. package/src/engine/physics/ecs/ContactProbeSystem.d.ts.map +1 -1
  79. package/src/engine/physics/ecs/ContactProbeSystem.js +2 -4
  80. package/src/engine/physics/fluid/ecs/FluidObstacleSystem.d.ts +4 -4
  81. package/src/engine/sound/simulation/PROBE_PLACEMENT.md +4 -4
  82. package/src/engine/sound/simulation/README.md +1 -1
  83. package/src/engine/sound/simulation/probe/bakeProbeField.d.ts +22 -8
  84. package/src/engine/sound/simulation/probe/bakeProbeField.d.ts.map +1 -1
  85. package/src/engine/sound/simulation/probe/bakeProbeField.js +60 -15
  86. package/src/engine/sound/simulation/probe/bakeProbeReflectors.d.ts.map +1 -1
  87. package/src/engine/sound/simulation/probe/bakeProbeReflectors.js +19 -23
  88. package/src/engine/sound/simulation/probe/probe_sdf_grid.d.ts +63 -17
  89. package/src/engine/sound/simulation/probe/probe_sdf_grid.d.ts.map +1 -1
  90. package/src/engine/sound/simulation/probe/probe_sdf_grid.js +67 -17
  91. package/src/generation/foliage/FOLIAGE_DESIGN.md +319 -0
  92. package/src/generation/foliage/FOLIAGE_PLAN.md +198 -0
  93. package/src/generation/foliage/animation/build_tree_wind_rig.d.ts +20 -0
  94. package/src/generation/foliage/animation/build_tree_wind_rig.d.ts.map +1 -0
  95. package/src/generation/foliage/animation/build_tree_wind_rig.js +108 -0
  96. package/src/generation/foliage/asset/TreeAsset.d.ts +86 -0
  97. package/src/generation/foliage/asset/TreeAsset.d.ts.map +1 -0
  98. package/src/generation/foliage/asset/TreeAsset.js +111 -0
  99. package/src/generation/foliage/asset/TreeLODAsset.d.ts +115 -0
  100. package/src/generation/foliage/asset/TreeLODAsset.d.ts.map +1 -0
  101. package/src/generation/foliage/asset/TreeLODAsset.js +346 -0
  102. package/src/generation/foliage/asset/generate_tree.d.ts +15 -0
  103. package/src/generation/foliage/asset/generate_tree.d.ts.map +1 -0
  104. package/src/generation/foliage/asset/generate_tree.js +93 -0
  105. package/src/generation/foliage/asset/place_tree.d.ts +82 -0
  106. package/src/generation/foliage/asset/place_tree.d.ts.map +1 -0
  107. package/src/generation/foliage/asset/place_tree.js +194 -0
  108. package/src/generation/foliage/config/BarkMaterialConfig.d.ts +121 -0
  109. package/src/generation/foliage/config/BarkMaterialConfig.d.ts.map +1 -0
  110. package/src/generation/foliage/config/BarkMaterialConfig.js +250 -0
  111. package/src/generation/foliage/config/BranchLevelConfig.d.ts +288 -0
  112. package/src/generation/foliage/config/BranchLevelConfig.d.ts.map +1 -0
  113. package/src/generation/foliage/config/BranchLevelConfig.js +536 -0
  114. package/src/generation/foliage/config/CrownConfig.d.ts +100 -0
  115. package/src/generation/foliage/config/CrownConfig.d.ts.map +1 -0
  116. package/src/generation/foliage/config/CrownConfig.js +186 -0
  117. package/src/generation/foliage/config/CrownEnvelopeType.d.ts +16 -0
  118. package/src/generation/foliage/config/CrownEnvelopeType.d.ts.map +1 -0
  119. package/src/generation/foliage/config/CrownEnvelopeType.js +17 -0
  120. package/src/generation/foliage/config/FoliageBarkType.d.ts +11 -0
  121. package/src/generation/foliage/config/FoliageBarkType.d.ts.map +1 -0
  122. package/src/generation/foliage/config/FoliageBarkType.js +16 -0
  123. package/src/generation/foliage/config/FoliageCardType.d.ts +12 -0
  124. package/src/generation/foliage/config/FoliageCardType.d.ts.map +1 -0
  125. package/src/generation/foliage/config/FoliageCardType.js +18 -0
  126. package/src/generation/foliage/config/FoliageConfig.d.ts +151 -0
  127. package/src/generation/foliage/config/FoliageConfig.d.ts.map +1 -0
  128. package/src/generation/foliage/config/FoliageConfig.js +283 -0
  129. package/src/generation/foliage/config/FoliageMaterialConfig.d.ts +143 -0
  130. package/src/generation/foliage/config/FoliageMaterialConfig.d.ts.map +1 -0
  131. package/src/generation/foliage/config/FoliageMaterialConfig.js +279 -0
  132. package/src/generation/foliage/config/PhyllotaxisMode.d.ts +13 -0
  133. package/src/generation/foliage/config/PhyllotaxisMode.d.ts.map +1 -0
  134. package/src/generation/foliage/config/PhyllotaxisMode.js +20 -0
  135. package/src/generation/foliage/config/TreeLODConfig.d.ts +87 -0
  136. package/src/generation/foliage/config/TreeLODConfig.d.ts.map +1 -0
  137. package/src/generation/foliage/config/TreeLODConfig.js +211 -0
  138. package/src/generation/foliage/config/TreePreset.d.ts +16 -0
  139. package/src/generation/foliage/config/TreePreset.d.ts.map +1 -0
  140. package/src/generation/foliage/config/TreePreset.js +17 -0
  141. package/src/generation/foliage/config/TreeSpeciesConfig.d.ts +109 -0
  142. package/src/generation/foliage/config/TreeSpeciesConfig.d.ts.map +1 -0
  143. package/src/generation/foliage/config/TreeSpeciesConfig.js +400 -0
  144. package/src/generation/foliage/config/TreeWindConfig.d.ts +84 -0
  145. package/src/generation/foliage/config/TreeWindConfig.d.ts.map +1 -0
  146. package/src/generation/foliage/config/TreeWindConfig.js +157 -0
  147. package/src/generation/foliage/index.d.ts +39 -0
  148. package/src/generation/foliage/index.d.ts.map +1 -0
  149. package/src/generation/foliage/index.js +44 -0
  150. package/src/generation/foliage/material/make_bark_material.d.ts +37 -0
  151. package/src/generation/foliage/material/make_bark_material.d.ts.map +1 -0
  152. package/src/generation/foliage/material/make_bark_material.js +730 -0
  153. package/src/generation/foliage/material/make_foliage_material.d.ts +40 -0
  154. package/src/generation/foliage/material/make_foliage_material.d.ts.map +1 -0
  155. package/src/generation/foliage/material/make_foliage_material.js +605 -0
  156. package/src/generation/foliage/mesh/branch_collar_project.d.ts +42 -0
  157. package/src/generation/foliage/mesh/branch_collar_project.d.ts.map +1 -0
  158. package/src/generation/foliage/mesh/branch_collar_project.js +205 -0
  159. package/src/generation/foliage/mesh/branch_ring_triangulate.d.ts +35 -0
  160. package/src/generation/foliage/mesh/branch_ring_triangulate.d.ts.map +1 -0
  161. package/src/generation/foliage/mesh/branch_ring_triangulate.js +72 -0
  162. package/src/generation/foliage/mesh/foliage_collect_attachments.d.ts +95 -0
  163. package/src/generation/foliage/mesh/foliage_collect_attachments.d.ts.map +1 -0
  164. package/src/generation/foliage/mesh/foliage_collect_attachments.js +569 -0
  165. package/src/generation/foliage/mesh/tree_mesh_apply_canopy_ao.d.ts +19 -0
  166. package/src/generation/foliage/mesh/tree_mesh_apply_canopy_ao.d.ts.map +1 -0
  167. package/src/generation/foliage/mesh/tree_mesh_apply_canopy_ao.js +150 -0
  168. package/src/generation/foliage/mesh/tree_mesh_build_bark.d.ts +55 -0
  169. package/src/generation/foliage/mesh/tree_mesh_build_bark.d.ts.map +1 -0
  170. package/src/generation/foliage/mesh/tree_mesh_build_bark.js +1049 -0
  171. package/src/generation/foliage/mesh/tree_mesh_build_foliage.d.ts +22 -0
  172. package/src/generation/foliage/mesh/tree_mesh_build_foliage.d.ts.map +1 -0
  173. package/src/generation/foliage/mesh/tree_mesh_build_foliage.js +454 -0
  174. package/src/generation/foliage/presets/make_tree_species_preset.d.ts +13 -0
  175. package/src/generation/foliage/presets/make_tree_species_preset.d.ts.map +1 -0
  176. package/src/generation/foliage/presets/make_tree_species_preset.js +870 -0
  177. package/src/generation/foliage/skeleton/TreeSkeleton.d.ts +443 -0
  178. package/src/generation/foliage/skeleton/TreeSkeleton.d.ts.map +1 -0
  179. package/src/generation/foliage/skeleton/TreeSkeleton.js +1100 -0
  180. package/src/generation/foliage/skeleton/crown_envelope_evaluate.d.ts +27 -0
  181. package/src/generation/foliage/skeleton/crown_envelope_evaluate.d.ts.map +1 -0
  182. package/src/generation/foliage/skeleton/crown_envelope_evaluate.js +121 -0
  183. package/src/generation/foliage/skeleton/spine_frame_double_reflection.d.ts +32 -0
  184. package/src/generation/foliage/skeleton/spine_frame_double_reflection.d.ts.map +1 -0
  185. package/src/generation/foliage/skeleton/spine_frame_double_reflection.js +87 -0
  186. package/src/generation/foliage/skeleton/tree_skeleton_assign_skin_joints.d.ts +33 -0
  187. package/src/generation/foliage/skeleton/tree_skeleton_assign_skin_joints.d.ts.map +1 -0
  188. package/src/generation/foliage/skeleton/tree_skeleton_assign_skin_joints.js +205 -0
  189. package/src/generation/foliage/skeleton/tree_skeleton_compute_pipe_radii.d.ts +17 -0
  190. package/src/generation/foliage/skeleton/tree_skeleton_compute_pipe_radii.d.ts.map +1 -0
  191. package/src/generation/foliage/skeleton/tree_skeleton_compute_pipe_radii.js +206 -0
  192. package/src/generation/foliage/skeleton/tree_skeleton_grow.d.ts +18 -0
  193. package/src/generation/foliage/skeleton/tree_skeleton_grow.d.ts.map +1 -0
  194. package/src/generation/foliage/skeleton/tree_skeleton_grow.js +1871 -0
  195. package/src/shade/playground/additive_blending/index.html +197 -0
  196. package/src/shade/playground/additive_blending/main.d.ts +2 -0
  197. package/src/shade/playground/additive_blending/main.d.ts.map +1 -0
  198. package/src/shade/playground/additive_blending/main.js +529 -0
  199. package/src/shade/playground/foliage_wind/README.md +18 -0
  200. package/src/shade/playground/foliage_wind/foliage_world.d.ts +133 -0
  201. package/src/shade/playground/foliage_wind/foliage_world.d.ts.map +1 -0
  202. package/src/shade/playground/foliage_wind/foliage_world.js +602 -0
  203. package/src/shade/playground/foliage_wind/index.html +260 -0
  204. package/src/shade/playground/foliage_wind/main.d.ts +2 -0
  205. package/src/shade/playground/foliage_wind/main.d.ts.map +1 -0
  206. package/src/shade/playground/foliage_wind/main.js +275 -0
  207. package/src/shade/renderer/Renderer.d.ts.map +1 -1
  208. package/src/shade/renderer/Renderer.js +45 -16
  209. package/src/shade/renderer/animation/ADDITIVE_BLENDING_PLAN.md +145 -0
  210. package/src/shade/renderer/animation/ANIMATION_DATABASE_SPEC.d.ts +9 -1
  211. package/src/shade/renderer/animation/ANIMATION_DATABASE_SPEC.d.ts.map +1 -1
  212. package/src/shade/renderer/animation/ANIMATION_DATABASE_SPEC.js +53 -1
  213. package/src/shade/renderer/animation/ANIMATION_FOLIAGE_JOINT_STRUCT.d.ts +8 -0
  214. package/src/shade/renderer/animation/ANIMATION_FOLIAGE_JOINT_STRUCT.d.ts.map +1 -0
  215. package/src/shade/renderer/animation/ANIMATION_FOLIAGE_JOINT_STRUCT.js +71 -0
  216. package/src/shade/renderer/animation/ANIMATION_FOLIAGE_RIG_STRUCT.d.ts +11 -0
  217. package/src/shade/renderer/animation/ANIMATION_FOLIAGE_RIG_STRUCT.d.ts.map +1 -0
  218. package/src/shade/renderer/animation/ANIMATION_FOLIAGE_RIG_STRUCT.js +32 -0
  219. package/src/shade/renderer/animation/GPUAnimationManager.d.ts +30 -3
  220. package/src/shade/renderer/animation/GPUAnimationManager.d.ts.map +1 -1
  221. package/src/shade/renderer/animation/GPUAnimationManager.js +532 -79
  222. package/src/shade/renderer/animation/POSE_ACCUMULATOR_LAYOUT.d.ts +19 -12
  223. package/src/shade/renderer/animation/POSE_ACCUMULATOR_LAYOUT.d.ts.map +1 -1
  224. package/src/shade/renderer/animation/POSE_ACCUMULATOR_LAYOUT.js +20 -13
  225. package/src/shade/renderer/animation/ShadeAnimationClip.d.ts +35 -8
  226. package/src/shade/renderer/animation/ShadeAnimationClip.d.ts.map +1 -1
  227. package/src/shade/renderer/animation/ShadeAnimationClip.js +433 -2
  228. package/src/shade/renderer/animation/chunk_pose_accumulator.d.ts.map +1 -1
  229. package/src/shade/renderer/animation/chunk_pose_accumulator.js +40 -32
  230. package/src/shade/renderer/animation/foliage/FoliageWindJoint.d.ts +97 -0
  231. package/src/shade/renderer/animation/foliage/FoliageWindJoint.d.ts.map +1 -0
  232. package/src/shade/renderer/animation/foliage/FoliageWindJoint.js +161 -0
  233. package/src/shade/renderer/animation/foliage/FoliageWindRig.d.ts +65 -0
  234. package/src/shade/renderer/animation/foliage/FoliageWindRig.d.ts.map +1 -0
  235. package/src/shade/renderer/animation/foliage/FoliageWindRig.js +89 -0
  236. package/src/shade/renderer/animation/foliage/chunk_foliage_wind_step.d.ts +19 -0
  237. package/src/shade/renderer/animation/foliage/chunk_foliage_wind_step.d.ts.map +1 -0
  238. package/src/shade/renderer/animation/foliage/chunk_foliage_wind_step.js +143 -0
  239. package/src/shade/renderer/animation/foliage/shader_foliage_wind_apply.d.ts +14 -0
  240. package/src/shade/renderer/animation/foliage/shader_foliage_wind_apply.d.ts.map +1 -0
  241. package/src/shade/renderer/animation/foliage/shader_foliage_wind_apply.js +116 -0
  242. package/src/shade/renderer/animation/pose/pose_evaluate_local.d.ts.map +1 -1
  243. package/src/shade/renderer/animation/pose/pose_evaluate_local.js +39 -29
  244. package/src/shade/renderer/animation/shader_animation_pose_resolve.js +37 -37
  245. package/src/shade/renderer/animation/skinning/GPUMeshSkinningContext.js +2 -2
  246. package/src/shade/renderer/extension/RenderExtension.d.ts +7 -0
  247. package/src/shade/renderer/extension/RenderExtension.d.ts.map +1 -1
  248. package/src/shade/renderer/extension/RenderExtension.js +9 -0
  249. package/src/shade/renderer/extension/RenderExtensionRegistry.d.ts +6 -0
  250. package/src/shade/renderer/extension/RenderExtensionRegistry.d.ts.map +1 -1
  251. package/src/shade/renderer/extension/RenderExtensionRegistry.js +15 -0
  252. package/src/shade/renderer/fluid/GPUFluidCascade.d.ts +10 -3
  253. package/src/shade/renderer/fluid/GPUFluidCascade.d.ts.map +1 -1
  254. package/src/shade/renderer/fluid/GPUFluidCascade.js +15 -0
  255. package/src/shade/renderer/fluid/GPUFluidCascadeStack.d.ts +8 -1
  256. package/src/shade/renderer/fluid/GPUFluidCascadeStack.d.ts.map +1 -1
  257. package/src/shade/renderer/fluid/GPUFluidCascadeStack.js +11 -0
  258. package/src/shade/renderer/fluid/coupling/chunk_fluid_cascade_parent.d.ts.map +1 -1
  259. package/src/shade/renderer/fluid/coupling/chunk_fluid_cascade_parent.js +51 -48
  260. package/src/shade/renderer/fluid/forcing/GPUFluidForcing.d.ts +6 -6
  261. package/src/shade/renderer/fluid/mac/FluidMacPasses.d.ts +6 -0
  262. package/src/shade/renderer/fluid/mac/FluidMacPasses.d.ts.map +1 -1
  263. package/src/shade/renderer/fluid/mac/FluidMacPasses.js +16 -0
  264. package/src/shade/renderer/fluid/mac/chunk_fluid_mac_seams.d.ts.map +1 -1
  265. package/src/shade/renderer/fluid/mac/chunk_fluid_mac_seams.js +15 -3
  266. package/src/shade/renderer/fluid/mac/chunk_v3_mac_lattice_corners.d.ts.map +1 -1
  267. package/src/shade/renderer/fluid/mac/chunk_v3_mac_lattice_corners.js +102 -96
  268. package/src/shade/renderer/fluid/mac/shader_fluid_mac_advect_correct.js +12 -12
  269. package/src/shade/renderer/fluid/mac/shader_fluid_mac_advect_predict.js +12 -12
  270. package/src/shade/renderer/fluid/pressure/GPUFluidPressureSolve.d.ts +8 -1
  271. package/src/shade/renderer/fluid/pressure/GPUFluidPressureSolve.d.ts.map +1 -1
  272. package/src/shade/renderer/fluid/pressure/GPUFluidPressureSolve.js +16 -0
  273. package/src/shade/renderer/fluid/publish/FluidWindPublish.d.ts +7 -0
  274. package/src/shade/renderer/fluid/publish/FluidWindPublish.d.ts.map +1 -1
  275. package/src/shade/renderer/fluid/publish/FluidWindPublish.js +10 -0
  276. package/src/shade/renderer/fluid/query/GPUFluidQueries.d.ts +1 -1
  277. package/src/shade/renderer/geometry/bvh/GPUBLASRefitBatch.js +2 -2
  278. package/src/shade/renderer/geometry/sdf/GPUGeometrySDFManager.js +1 -1
  279. package/src/shade/renderer/hiz/HierarchicalZBuffer.d.ts +3 -3
  280. package/src/shade/renderer/hiz/HierarchicalZBuffer.d.ts.map +1 -1
  281. package/src/shade/renderer/hiz/HierarchicalZBuffer.js +350 -294
  282. package/src/shade/renderer/hiz/generate_downsampler_code.d.ts +0 -2
  283. package/src/shade/renderer/hiz/generate_downsampler_code.d.ts.map +1 -1
  284. package/src/shade/renderer/hiz/generate_downsampler_code.js +99 -100
  285. package/src/shade/renderer/hiz/hzb_pick_alignment.d.ts +15 -0
  286. package/src/shade/renderer/hiz/hzb_pick_alignment.d.ts.map +1 -0
  287. package/src/shade/renderer/hiz/hzb_pick_alignment.js +148 -0
  288. package/src/shade/renderer/hiz/hzb_plan_reduction_steps.d.ts +28 -0
  289. package/src/shade/renderer/hiz/hzb_plan_reduction_steps.d.ts.map +1 -0
  290. package/src/shade/renderer/hiz/hzb_plan_reduction_steps.js +58 -0
  291. package/src/shade/renderer/hiz/shader_hzb_reduce_any.d.ts +5 -5
  292. package/src/shade/renderer/hiz/shader_hzb_reduce_any.d.ts.map +1 -1
  293. package/src/shade/renderer/hiz/shader_hzb_reduce_any.js +57 -67
  294. package/src/shade/renderer/hiz/shader_hzb_reduce_mip0_clip.d.ts +3 -3
  295. package/src/shade/renderer/hiz/shader_hzb_reduce_mip0_clip.d.ts.map +1 -1
  296. package/src/shade/renderer/hiz/shader_hzb_reduce_mip0_clip.js +111 -112
  297. package/src/shade/renderer/hiz/shader_hzb_spd.d.ts +5 -0
  298. package/src/shade/renderer/hiz/shader_hzb_spd.d.ts.map +1 -0
  299. package/src/shade/renderer/hiz/shader_hzb_spd.js +160 -0
  300. package/src/shade/renderer/light/GPULightCollection.d.ts +8 -0
  301. package/src/shade/renderer/light/GPULightCollection.d.ts.map +1 -1
  302. package/src/shade/renderer/light/GPULightCollection.js +67 -0
  303. package/src/shade/renderer/light/environment/FAR_FIELD_IRRADIANCE_SH_RESOURCE.d.ts +7 -0
  304. package/src/shade/renderer/light/environment/FAR_FIELD_IRRADIANCE_SH_RESOURCE.d.ts.map +1 -0
  305. package/src/shade/renderer/light/environment/FAR_FIELD_IRRADIANCE_SH_RESOURCE.js +13 -0
  306. package/src/shade/renderer/light/environment/shader_project_far_field_irradiance_sh.d.ts +10 -0
  307. package/src/shade/renderer/light/environment/shader_project_far_field_irradiance_sh.d.ts.map +1 -0
  308. package/src/shade/renderer/light/environment/shader_project_far_field_irradiance_sh.js +72 -0
  309. package/src/shade/renderer/light/world_cluster/WORLD_LIGHT_CLUSTER.d.ts +39 -0
  310. package/src/shade/renderer/light/world_cluster/WORLD_LIGHT_CLUSTER.d.ts.map +1 -0
  311. package/src/shade/renderer/light/world_cluster/WORLD_LIGHT_CLUSTER.js +90 -0
  312. package/src/shade/renderer/light/world_cluster/chunk_world_light_cluster_lookup.d.ts +12 -0
  313. package/src/shade/renderer/light/world_cluster/chunk_world_light_cluster_lookup.d.ts.map +1 -0
  314. package/src/shade/renderer/light/world_cluster/chunk_world_light_cluster_lookup.js +56 -0
  315. package/src/shade/renderer/light/world_cluster/graph_build_world_light_clusters.d.ts +31 -0
  316. package/src/shade/renderer/light/world_cluster/graph_build_world_light_clusters.d.ts.map +1 -0
  317. package/src/shade/renderer/light/world_cluster/graph_build_world_light_clusters.js +196 -0
  318. package/src/shade/renderer/light/world_cluster/shader_world_cluster_assign_lights.d.ts +10 -0
  319. package/src/shade/renderer/light/world_cluster/shader_world_cluster_assign_lights.d.ts.map +1 -0
  320. package/src/shade/renderer/light/world_cluster/shader_world_cluster_assign_lights.js +163 -0
  321. package/src/shade/renderer/light/world_cluster/shader_world_cluster_cull_lights.d.ts +4 -0
  322. package/src/shade/renderer/light/world_cluster/shader_world_cluster_cull_lights.d.ts.map +1 -0
  323. package/src/shade/renderer/light/world_cluster/shader_world_cluster_cull_lights.js +137 -0
  324. package/src/shade/renderer/loader/SceneBundle.d.ts +7 -0
  325. package/src/shade/renderer/loader/SceneBundle.d.ts.map +1 -1
  326. package/src/shade/renderer/loader/SceneBundle.js +119 -111
  327. package/src/shade/renderer/loader/gltf/tiny-gltf.d.ts +1 -1
  328. package/src/shade/renderer/material/standard/fragment_gbuffer.d.ts +4 -0
  329. package/src/shade/renderer/material/standard/fragment_gbuffer.d.ts.map +1 -1
  330. package/src/shade/renderer/material/standard/fragment_gbuffer.js +6 -5
  331. package/src/shade/renderer/object_property/chunk_write_object_property_node3d.d.ts +8 -7
  332. package/src/shade/renderer/object_property/chunk_write_object_property_node3d.d.ts.map +1 -1
  333. package/src/shade/renderer/object_property/chunk_write_object_property_node3d.js +9 -8
  334. package/src/shade/renderer/particles/GPUParticleSystem.d.ts.map +1 -1
  335. package/src/shade/renderer/particles/GPUParticleSystem.js +4 -1
  336. package/src/shade/renderer/particles/graph_particles_avboit.d.ts +4 -3
  337. package/src/shade/renderer/particles/graph_particles_avboit.d.ts.map +1 -1
  338. package/src/shade/renderer/particles/graph_particles_avboit.js +28 -11
  339. package/src/shade/renderer/particles/shaders/chunk_particle_lighting.d.ts +9 -13
  340. package/src/shade/renderer/particles/shaders/chunk_particle_lighting.d.ts.map +1 -1
  341. package/src/shade/renderer/particles/shaders/chunk_particle_lighting.js +25 -20
  342. package/src/shade/renderer/particles/shaders/shader_particle_avboit_draw.d.ts.map +1 -1
  343. package/src/shade/renderer/particles/shaders/shader_particle_avboit_draw.js +22 -34
  344. package/src/shade/renderer/postprocess/nss/NSS.d.ts +1 -1
  345. package/src/shade/renderer/postprocess/ssr/resolve/ssr_resolve_shader.d.ts.map +1 -1
  346. package/src/shade/renderer/postprocess/ssr/resolve/ssr_resolve_shader.js +9 -7
  347. package/src/shade/renderer/rasterize/forward/chunk_forward_shade_standard_fragment.d.ts.map +1 -1
  348. package/src/shade/renderer/rasterize/forward/chunk_forward_shade_standard_fragment.js +15 -7
  349. package/src/shade/renderer/rasterize/native/avboit/avboit_draw_fragment_body.d.ts.map +1 -1
  350. package/src/shade/renderer/rasterize/native/avboit/avboit_draw_fragment_body.js +6 -4
  351. package/src/shade/renderer/rasterize/native/oit/oit_resolve_fragment_body.js +3 -3
  352. package/src/shade/renderer/rasterize/native/oit/shader_oit_generate_moments.d.ts.map +1 -1
  353. package/src/shade/renderer/rasterize/native/oit/shader_oit_generate_moments.js +8 -5
  354. package/src/shade/renderer/rasterize/native/viz/viz_rasterization_alpha_tested_pass_descriptor.d.ts.map +1 -1
  355. package/src/shade/renderer/rasterize/native/viz/viz_rasterization_alpha_tested_pass_descriptor.js +4 -3
  356. package/src/shade/renderer/shader/chunk/depth/chunk_query_depth_from_screen_space_bb.d.ts.map +1 -1
  357. package/src/shade/renderer/shader/chunk/depth/chunk_query_depth_from_screen_space_bb.js +62 -62
  358. package/src/shade/renderer/shader/chunk/random/lowbias/chunk_hash_lowbias32_vec2u.d.ts +6 -0
  359. package/src/shade/renderer/shader/chunk/random/lowbias/chunk_hash_lowbias32_vec2u.d.ts.map +1 -0
  360. package/src/shade/renderer/shader/chunk/random/lowbias/chunk_hash_lowbias32_vec2u.js +19 -0
  361. package/src/shade/renderer/shader/chunk/screen_space/chunk_ffx_sssr_get_mip_resolution.d.ts.map +1 -1
  362. package/src/shade/renderer/shader/chunk/screen_space/chunk_ffx_sssr_get_mip_resolution.js +16 -10
  363. package/src/shade/renderer/shader/chunk/screen_space/chunk_ffx_sssr_hierarchical_raymarch.d.ts.map +1 -1
  364. package/src/shade/renderer/shader/chunk/screen_space/chunk_ffx_sssr_hierarchical_raymarch.js +118 -117
  365. package/src/shade/renderer/shadow/map/GPUSceneShadowmapContext.js +1 -1
  366. package/src/shade/renderer/texture/SAMPLER_ANISOTROPIC_CLAMP.d.ts +9 -0
  367. package/src/shade/renderer/texture/SAMPLER_ANISOTROPIC_CLAMP.d.ts.map +1 -0
  368. package/src/shade/renderer/texture/SAMPLER_ANISOTROPIC_CLAMP.js +17 -0
  369. package/src/shade/renderer/texture/SAMPLER_LINEAR_UPSCALE.d.ts +8 -0
  370. package/src/shade/renderer/texture/SAMPLER_LINEAR_UPSCALE.d.ts.map +1 -1
  371. package/src/shade/renderer/texture/SAMPLER_LINEAR_UPSCALE.js +28 -20
  372. package/src/shade/renderer/texture/SAMPLER_TRILINEAR_CLAMP.d.ts +8 -0
  373. package/src/shade/renderer/texture/SAMPLER_TRILINEAR_CLAMP.d.ts.map +1 -0
  374. package/src/shade/renderer/texture/SAMPLER_TRILINEAR_CLAMP.js +15 -0
  375. package/src/shade/renderer/texture/atlas/GPUTileAtlas3D.d.ts +41 -7
  376. package/src/shade/renderer/texture/atlas/GPUTileAtlas3D.d.ts.map +1 -1
  377. package/src/shade/renderer/texture/atlas/GPUTileAtlas3D.js +162 -58
  378. package/src/shade/renderer/view/chunk_view_mip_bias.d.ts +21 -0
  379. package/src/shade/renderer/view/chunk_view_mip_bias.d.ts.map +1 -0
  380. package/src/shade/renderer/view/chunk_view_mip_bias.js +44 -0
  381. package/src/shade/renderer/volumetrics/GPUSceneVolumetrics.js +1 -1
  382. package/src/shade/renderer/volumetrics/GPUViewVolumetrics.d.ts +10 -12
  383. package/src/shade/renderer/volumetrics/GPUViewVolumetrics.d.ts.map +1 -1
  384. package/src/shade/renderer/volumetrics/GPUViewVolumetrics.js +583 -606
  385. package/src/shade/renderer/volumetrics/chunk_volumetrics_froxel_sample.d.ts +24 -0
  386. package/src/shade/renderer/volumetrics/chunk_volumetrics_froxel_sample.d.ts.map +1 -0
  387. package/src/shade/renderer/volumetrics/gi/chunk_volumetrics_gi_sample.d.ts.map +1 -1
  388. package/src/shade/renderer/volumetrics/graph_build_volumetrics.d.ts +1 -1
  389. package/src/shade/renderer/volumetrics/graph_build_volumetrics.js +640 -640
  390. package/src/shade/renderer/volumetrics/irradiance_clipmap/GPUIrradianceClipmap.d.ts +113 -0
  391. package/src/shade/renderer/volumetrics/irradiance_clipmap/GPUIrradianceClipmap.d.ts.map +1 -0
  392. package/src/shade/renderer/volumetrics/irradiance_clipmap/GPUIrradianceClipmap.js +484 -0
  393. package/src/shade/renderer/volumetrics/irradiance_clipmap/IRRADIANCE_CLIPMAP.d.ts +50 -0
  394. package/src/shade/renderer/volumetrics/irradiance_clipmap/IRRADIANCE_CLIPMAP.d.ts.map +1 -0
  395. package/src/shade/renderer/volumetrics/irradiance_clipmap/IRRADIANCE_CLIPMAP.js +60 -0
  396. package/src/shade/renderer/volumetrics/irradiance_clipmap/IRRADIANCE_CLIPMAP_STRUCT.d.ts +25 -0
  397. package/src/shade/renderer/volumetrics/irradiance_clipmap/IRRADIANCE_CLIPMAP_STRUCT.d.ts.map +1 -0
  398. package/src/shade/renderer/volumetrics/irradiance_clipmap/IRRADIANCE_CLIPMAP_STRUCT.js +47 -0
  399. package/src/shade/renderer/volumetrics/irradiance_clipmap/IrradianceCascadePlacement.d.ts +74 -0
  400. package/src/shade/renderer/volumetrics/irradiance_clipmap/IrradianceCascadePlacement.d.ts.map +1 -0
  401. package/src/shade/renderer/volumetrics/irradiance_clipmap/IrradianceCascadePlacement.js +173 -0
  402. package/src/shade/renderer/volumetrics/irradiance_clipmap/chunk_irradiance_clipmap_gi_sample.d.ts +26 -0
  403. package/src/shade/renderer/volumetrics/irradiance_clipmap/chunk_irradiance_clipmap_gi_sample.d.ts.map +1 -0
  404. package/src/shade/renderer/volumetrics/irradiance_clipmap/chunk_irradiance_clipmap_gi_sample.js +83 -0
  405. package/src/shade/renderer/volumetrics/irradiance_clipmap/chunk_irradiance_clipmap_local_light.d.ts +31 -0
  406. package/src/shade/renderer/volumetrics/irradiance_clipmap/chunk_irradiance_clipmap_local_light.d.ts.map +1 -0
  407. package/src/shade/renderer/volumetrics/irradiance_clipmap/chunk_irradiance_clipmap_local_light.js +113 -0
  408. package/src/shade/renderer/volumetrics/irradiance_clipmap/chunk_irradiance_clipmap_sample.d.ts +121 -0
  409. package/src/shade/renderer/volumetrics/irradiance_clipmap/chunk_irradiance_clipmap_sample.d.ts.map +1 -0
  410. package/src/shade/renderer/volumetrics/irradiance_clipmap/chunk_irradiance_clipmap_sample.js +356 -0
  411. package/src/shade/renderer/volumetrics/irradiance_clipmap/shader_irradiance_clipmap_build.d.ts +16 -0
  412. package/src/shade/renderer/volumetrics/irradiance_clipmap/shader_irradiance_clipmap_build.d.ts.map +1 -0
  413. package/src/shade/renderer/volumetrics/irradiance_clipmap/shader_irradiance_clipmap_build.js +191 -0
  414. package/src/shade/renderer/volumetrics/shader_volumetrics_classify_froxels.d.ts +16 -0
  415. package/src/shade/renderer/volumetrics/shader_volumetrics_classify_froxels.d.ts.map +1 -0
  416. package/src/shade/renderer/volumetrics/shader_volumetrics_lighting_regimes.d.ts +23 -0
  417. package/src/shade/renderer/volumetrics/shader_volumetrics_lighting_regimes.d.ts.map +1 -0
  418. package/src/shade/renderer/wind/GPUSceneWind.d.ts +1 -1
  419. package/src/shade/renderer/wind/GPUSceneWind.js +1 -1
  420. package/src/shade/wgsl/emulator/WGSLJavaScriptCompiler.js +1 -1
  421. package/src/shade/wgsl/emulator/WGSLLanguageCore.d.ts.map +1 -1
  422. package/src/shade/wgsl/emulator/WGSLLanguageCore.js +105 -26
  423. package/src/engine/graphics/composit/CompositingStages.d.ts +0 -6
  424. package/src/engine/graphics/composit/CompositingStages.d.ts.map +0 -1
  425. package/src/engine/graphics/composit/CompositingStages.js +0 -8
  426. package/src/engine/graphics/geometry/buffered/computeBufferAttributeHash.d.ts +0 -7
  427. package/src/engine/graphics/geometry/buffered/computeBufferAttributeHash.d.ts.map +0 -1
  428. package/src/engine/graphics/geometry/buffered/computeBufferAttributeHash.js +0 -24
  429. package/src/engine/graphics/geometry/buffered/computeGeometryBoundingSphereMiniball.d.ts +0 -8
  430. package/src/engine/graphics/geometry/buffered/computeGeometryBoundingSphereMiniball.d.ts.map +0 -1
  431. package/src/engine/graphics/geometry/buffered/computeGeometryBoundingSphereMiniball.js +0 -17
  432. package/src/engine/graphics/geometry/buffered/computeGeometryEquality.d.ts +0 -8
  433. package/src/engine/graphics/geometry/buffered/computeGeometryEquality.d.ts.map +0 -1
  434. package/src/engine/graphics/geometry/buffered/computeGeometryEquality.js +0 -60
  435. package/src/engine/graphics/geometry/buffered/computeGeometryHash.d.ts +0 -7
  436. package/src/engine/graphics/geometry/buffered/computeGeometryHash.d.ts.map +0 -1
  437. package/src/engine/graphics/geometry/buffered/computeGeometryHash.js +0 -26
  438. package/src/engine/graphics/geometry/buffered/compute_buffer_geometry_byte_size.d.ts +0 -7
  439. package/src/engine/graphics/geometry/buffered/compute_buffer_geometry_byte_size.d.ts.map +0 -1
  440. package/src/engine/graphics/geometry/buffered/compute_buffer_geometry_byte_size.js +0 -23
  441. package/src/engine/graphics/geometry/instancing/geometry_copy.d.ts +0 -7
  442. package/src/engine/graphics/geometry/instancing/geometry_copy.d.ts.map +0 -1
  443. package/src/engine/graphics/geometry/instancing/geometry_copy.js +0 -15
  444. package/src/engine/graphics/geometry/instancing/rewriteMaterial.d.ts +0 -2
  445. package/src/engine/graphics/geometry/instancing/rewriteMaterial.d.ts.map +0 -1
  446. package/src/engine/graphics/geometry/instancing/rewriteMaterial.js +0 -57
  447. package/src/engine/graphics/geometry/ribbon/Quad.d.ts +0 -96
  448. package/src/engine/graphics/geometry/ribbon/Quad.d.ts.map +0 -1
  449. package/src/engine/graphics/geometry/ribbon/Quad.js +0 -213
  450. package/src/engine/graphics/geometry/ribbon/copyAttributeV3.d.ts +0 -9
  451. package/src/engine/graphics/geometry/ribbon/copyAttributeV3.d.ts.map +0 -1
  452. package/src/engine/graphics/geometry/ribbon/copyAttributeV3.js +0 -12
  453. package/src/engine/graphics/geometry/ribbon/copyAttributeValue.d.ts +0 -10
  454. package/src/engine/graphics/geometry/ribbon/copyAttributeValue.d.ts.map +0 -1
  455. package/src/engine/graphics/geometry/ribbon/copyAttributeValue.js +0 -17
  456. package/src/engine/graphics/geometry/ribbon/equalAttributeV3.d.ts +0 -10
  457. package/src/engine/graphics/geometry/ribbon/equalAttributeV3.d.ts.map +0 -1
  458. package/src/engine/graphics/geometry/ribbon/equalAttributeV3.js +0 -13
  459. package/src/engine/graphics/geometry/ribbon/equalAttributeValue.d.ts +0 -11
  460. package/src/engine/graphics/geometry/ribbon/equalAttributeValue.d.ts.map +0 -1
  461. package/src/engine/graphics/geometry/ribbon/equalAttributeValue.js +0 -24
  462. package/src/engine/graphics/geometry/ribbon/rotateRibbon.d.ts +0 -6
  463. package/src/engine/graphics/geometry/ribbon/rotateRibbon.d.ts.map +0 -1
  464. package/src/engine/graphics/geometry/ribbon/rotateRibbon.js +0 -37
  465. package/src/engine/graphics/geometry/ribbon/updateTipPosition.d.ts +0 -9
  466. package/src/engine/graphics/geometry/ribbon/updateTipPosition.d.ts.map +0 -1
  467. package/src/engine/graphics/geometry/ribbon/updateTipPosition.js +0 -52
  468. package/src/engine/graphics/geometry/skining/computeSkinnedMeshVertices.d.ts +0 -8
  469. package/src/engine/graphics/geometry/skining/computeSkinnedMeshVertices.d.ts.map +0 -1
  470. package/src/engine/graphics/geometry/skining/computeSkinnedMeshVertices.js +0 -54
  471. package/src/engine/graphics/render/RenderPassType.d.ts +0 -6
  472. package/src/engine/graphics/render/RenderPassType.d.ts.map +0 -1
  473. package/src/engine/graphics/render/RenderPassType.js +0 -8
  474. package/src/engine/graphics/render/layers/RenderLayer.d.ts +0 -78
  475. package/src/engine/graphics/render/layers/RenderLayer.d.ts.map +0 -1
  476. package/src/engine/graphics/render/layers/RenderLayer.js +0 -95
  477. package/src/engine/graphics/render/layers/RenderLayerManager.d.ts +0 -72
  478. package/src/engine/graphics/render/layers/RenderLayerManager.d.ts.map +0 -1
  479. package/src/engine/graphics/render/layers/RenderLayerManager.js +0 -160
  480. package/src/engine/graphics/render/layers/RenderLayerManagerState.d.ts +0 -18
  481. package/src/engine/graphics/render/layers/RenderLayerManagerState.d.ts.map +0 -1
  482. package/src/engine/graphics/render/layers/RenderLayerManagerState.js +0 -48
  483. package/src/engine/graphics/render/layers/RenderLayerState.d.ts +0 -18
  484. package/src/engine/graphics/render/layers/RenderLayerState.d.ts.map +0 -1
  485. package/src/engine/graphics/render/layers/RenderLayerState.js +0 -27
  486. package/src/engine/graphics/render/layers/RenderLayerUtils.d.ts +0 -8
  487. package/src/engine/graphics/render/layers/RenderLayerUtils.d.ts.map +0 -1
  488. package/src/engine/graphics/render/layers/RenderLayerUtils.js +0 -103
  489. package/src/engine/graphics/render/make_bvh_visibility_builder.d.ts +0 -10
  490. package/src/engine/graphics/render/make_bvh_visibility_builder.d.ts.map +0 -1
  491. package/src/engine/graphics/render/make_bvh_visibility_builder.js +0 -64
  492. package/src/engine/graphics/render/view/CameraViewFlags.d.ts +0 -7
  493. package/src/engine/graphics/render/view/CameraViewFlags.d.ts.map +0 -1
  494. package/src/engine/graphics/render/view/CameraViewFlags.js +0 -9
  495. package/src/engine/graphics/render/visibility/IncrementalDeltaSet.d.ts +0 -143
  496. package/src/engine/graphics/render/visibility/IncrementalDeltaSet.d.ts.map +0 -1
  497. package/src/engine/graphics/render/visibility/IncrementalDeltaSet.js +0 -328
  498. package/src/engine/graphics/render/visibility/VisibilityComputer.d.ts +0 -39
  499. package/src/engine/graphics/render/visibility/VisibilityComputer.d.ts.map +0 -1
  500. package/src/engine/graphics/render/visibility/VisibilityComputer.js +0 -80
  501. package/src/engine/graphics/render/visibility/VisibilityFilter.d.ts +0 -29
  502. package/src/engine/graphics/render/visibility/VisibilityFilter.d.ts.map +0 -1
  503. package/src/engine/graphics/render/visibility/VisibilityFilter.js +0 -37
  504. package/src/engine/graphics3/animation/write_graph_playback.d.ts +0 -24
  505. package/src/engine/graphics3/animation/write_graph_playback.d.ts.map +0 -1
  506. package/src/engine/graphics3/animation/write_graph_playback.js +0 -48
  507. package/src/engine/graphics3/decal/DecalAtlasFill.js +0 -32
  508. package/src/shade/renderer/volumetrics/gi/chunk_volumetrics_froxel_centre_ws.js +0 -49
  509. package/src/shade/renderer/volumetrics/gi/chunk_volumetrics_gi.js +0 -50
  510. package/src/shade/renderer/volumetrics/gi/chunk_volumetrics_gi_sample.js +0 -105
  511. package/src/shade/renderer/volumetrics/gi/graph_build_froxel_gi.js +0 -80
  512. package/src/shade/renderer/volumetrics/gi/shader_volumetrics_gi.js +0 -51
  513. package/src/shade/renderer/volumetrics/gi/volumetrics_gi_resources.js +0 -39
  514. package/src/shade/renderer/volumetrics/graph_build_froxel_irradiance.js +0 -121
  515. package/src/shade/renderer/volumetrics/shader_volumetrics_build_irradiance.js +0 -237
@@ -1,949 +1,904 @@
1
- import { assert } from "../../core/assert.js";
2
- import { warn_limited } from "../../shade/util/warn_limited.js";
3
- import { FramePhase } from "../../shade/renderer/extension/FramePhase.js";
4
- import { RenderExtension } from "../../shade/renderer/extension/RenderExtension.js";
5
- import { ParticleEmitter } from "../../shade/renderer/particles/runtime/ParticleEmitter.js";
6
- import { GPUTextureContext } from "../../shade/renderer/texture/GPUTextureContext.js";
7
- import { System } from "../ecs/System.js";
8
- import Name from "../ecs/name/Name.js";
9
- import { Transform64 } from "../ecs/transform/Transform64.js";
10
- import { TRANSFORM64_EVENT_CHANGE } from "../ecs/transform/TRANSFORM64_EVENT_CHANGE.js";
11
- import { row_of_entity } from "../../shade/renderer/scene/rows/GPUSceneRows.js";
12
- import { GameAssetType } from "../asset/GameAssetType.js";
13
- import { ImageRGBADataLoader } from "../asset/loaders/image/ImageRGBADataLoader.js";
14
- import { ParticleEffect } from "../graphics/ecs/particles/ParticleEffect.js";
15
- import { TextureAtlas } from "../graphics/texture/atlas/TextureAtlas.js";
16
- import { Sampler2D } from "../graphics/texture/sampler/Sampler2D.js";
17
- import { sampler2d_ensure_uint8_RGBA } from "../graphics/texture/sampler/sampler2d_ensure_uint8_RGBA.js";
18
-
19
- /**
20
- * Bursts {@link GPUParticleEmitterSystem#burst} will hold before it starts dropping them. Reached
21
- * only when frames have stopped arriving — see the call site.
22
- *
23
- * @type {number}
24
- */
25
- const BURST_QUEUE_LIMIT = 256;
26
-
27
- /**
28
- * @typedef {object} ParticleAtlasTexture
29
- * @property {number} references Number of entities using this URL.
30
- * @property {import("../graphics/texture/atlas/AtlasPatch.js").AtlasPatch|null} patch Null while loading or after failure.
31
- */
32
-
33
- /**
34
- * @typedef {object} ParticleEmitterEntry
35
- * @property {number} entity
36
- * @property {ParticleEffect} effect the entity's component — what to emit
37
- * @property {Transform64} transform the entity's transform — where to emit it
38
- * @property {ParticleEmitter|null} emitter the emitter, once the effect has a program to run
39
- * @property {import("../../shade/renderer/scene/GPUSceneContext.js").GPUSceneContext|null} context
40
- * the scene context the emitter is registered with, at the entity's row; null until a frame has
41
- * registered it, or after a device restart took that context away
42
- * @property {function():void} on_transform_changed
43
- * @property {string|null} url last observed {@link ParticleEffect#texture}
44
- * @property {ParticleAtlasTexture|null} texture shared image record, or null for an untextured effect
45
- */
46
-
47
- /**
48
- * Draws the game's GPU particle effects: one emitter per entity carrying
49
- * {@link ../graphics/ecs/particles/ParticleEffect.js ParticleEffect} and
50
- * {@link ../ecs/transform/Transform64.js Transform64}.
51
- *
52
- * ```js
53
- * entityManager.addSystem(new GPUParticleEmitterSystem(graphics, scene, assetManager));
54
- *
55
- * new Entity()
56
- * .add(new Transform64())
57
- * .add(ParticleEffect.from({ ...create_particle_effect({ layout, init, update }), spawn_rate: 400 }))
58
- * .build(dataset);
59
- * ```
60
- *
61
- * **The entity is the emitter.** The component says what it emits and the transform says where, the
62
- * same division every other placed thing in this engine uses — a `Decal`, an `SGMesh`, a `Light`.
63
- * Which means an emitter is spawned, moved, parented, attached to a bone, saved and destroyed by
64
- * exactly the machinery that does those things for everything else, and a particle effect stops
65
- * being a special case that needs its own scene-graph call.
66
- *
67
- * ## What this system does with the pair
68
- *
69
- * It builds a {@link ParticleEmitter} per entity and owns it: the emitter is registered with the
70
- * scene's particle system at the entity's row when the component has a compiled effect, so it
71
- * stands where the entity stands, carries whatever the component currently says, and is retired
72
- * when the entity goes. The emitter is not hidden — {@link emitter_of} hands it out — but it is
73
- * not the interface either: write to the component and the next frame carries it.
74
- *
75
- * Changes are found by **comparison** rather than announced. Once a frame each entity's
76
- * fields are compared against what was last written to its emitter, and the emitter is marked dirty only
77
- * where they differ; `EmitterRegistry` then restages exactly the rows that moved. A component nobody
78
- * touched costs a run of integer comparisons and no upload, so there is no change signal to fire and
79
- * none to forget. What that buys is that gameplay can write `effect.spawn_rate = 0` anywhere,
80
- * including from code that has never heard of this system.
81
- *
82
- * ## It owns the scene's sprite atlas
83
- *
84
- * Every {@link ParticleEffect#texture} is loaded through the supplied `AssetManager` and packed into
85
- * one {@link TextureAtlas} shared by the scene, one patch per distinct URL however many entities
86
- * name it, reference-counted so the last entity to let go is what unpacks it. A `FrameStart`
87
- * extension publishes the pixels and every current patch region before the renderer records the
88
- * particle passes — every repack moves UVs that nothing else changed, so the regions are rewritten
89
- * whole rather than when a URL changes. Pending and failed images sample a transparent texel;
90
- * effects with no image sample a white one.
91
- *
92
- * An emitter in this scene that this system did not build — one registered with the particle
93
- * system directly — gets the white texel. The atlas is the scene's and that emitter has nothing in
94
- * it, so the alternative is not "its own texture" but a UV rectangle from somebody else's image.
95
- *
96
- * ## What it is not
97
- *
98
- * It does not simulate anything and does not record a particle pass. `GPUParticleSystem` does both,
99
- * inside the renderer, driven from the emitter table these rows are in — see
100
- * `shade/renderer/particles/DESIGN.md`. This system's whole contribution is membership, placement,
101
- * the fields, and the sprite sheet.
102
- */
103
- export class GPUParticleEmitterSystem extends System {
104
- dependencies = [ParticleEffect, Transform64];
105
-
106
- /**
107
- * CPU atlas, exposed for inspection. Owned by this system.
108
- * @type {TextureAtlas}
109
- */
110
- atlas = new TextureAtlas(64);
111
-
112
- /** @type {import("./GraphicsEngine.js").GraphicsEngine} */
113
- #graphics;
114
- /** @type {import("../../shade/renderer/scene/Scene.js").Scene} */
115
- #scene;
116
- /** @type {import("../asset/AssetManager.js").AssetManager} */
117
- #assets;
118
- /** @type {Map<number, ParticleEmitterEntry>} */
119
- #entries = new Map();
120
- /**
121
- * The same entries by the emitter they own, so that the emitters this system did not build can
122
- * be told apart from the ones it did without a linear search per emitter per frame.
123
- * @type {Map<ParticleEmitter, ParticleEmitterEntry>}
124
- */
125
- #by_emitter = new Map();
126
- /** @type {Map<string, ParticleAtlasTexture>} */
127
- #textures = new Map();
128
- /**
129
- * Bursts asked for through {@link burst} and not yet handed to the particle system. Flat pairs
130
- * of `entity, count`, drained every frame.
131
- * @type {number[]}
132
- */
133
- #bursts = [];
134
- /** @type {import("../graphics/texture/atlas/AtlasPatch.js").AtlasPatch} */
135
- #white;
136
- /** @type {import("../graphics/texture/atlas/AtlasPatch.js").AtlasPatch} */
137
- #transparent;
138
- /** @type {GPUParticleAtlasExtension|null} */
139
- #extension = null;
140
- /** @type {GPUTextureContext|null} */
141
- #texture = null;
142
- /** @type {GPUTextureView|null} */
143
- #view = null;
144
- /** @type {GPUDevice|null} */
145
- #device = null;
146
- /** @type {number} */
147
- #uploaded_version = -1;
148
- /** @type {import("../../shade/renderer/particles/GPUParticleSystem.js").GPUParticleSystem|null} */
149
- #particles = null;
150
- /** @type {GPUTextureView|null} */
151
- #previous_atlas = null;
152
- /** @type {Float32Array} */
153
- #region = new Float32Array(4);
154
-
155
- /**
156
- * @param {import("./GraphicsEngine.js").GraphicsEngine} graphics
157
- * @param {import("../../shade/renderer/scene/Scene.js").Scene} scene scene the emitters are put
158
- * in, and whose particle atlas this system owns
159
- * @param {import("../asset/AssetManager.js").AssetManager} assets
160
- */
161
- constructor(graphics, scene, assets) {
162
- super();
163
-
164
- assert.defined(graphics, "graphics");
165
- assert.equal(scene.isScene, true, "scene.isScene !== true");
166
- assert.defined(assets, "assets");
167
-
168
- this.#graphics = graphics;
169
- this.#scene = scene;
170
- this.#assets = assets;
171
-
172
- this.#create_defaults();
173
- }
174
-
175
- /**
176
- * The scene the emitters are put in. Received rather than created: meshes and lights come from
177
- * systems of their own, and all of them have to be writing into one scene for any of it to be
178
- * visible together.
179
- *
180
- * @returns {import("../../shade/renderer/scene/Scene.js").Scene}
181
- */
182
- get scene() {
183
- return this.#scene;
184
- }
185
-
186
- /**
187
- * The emitters this system currently holds, in no particular order. Read-only — they follow
188
- * the entities.
189
- *
190
- * @returns {ParticleEmitter[]}
191
- */
192
- get emitters() {
193
- return Array.from(this.#by_emitter.keys());
194
- }
195
-
196
- /**
197
- * The emitter built for an entity.
198
- *
199
- * Handed out for what an entity reference cannot express — asking the particle system about
200
- * it — and for nothing routine: the component is how an effect is changed, and an emitter
201
- * written to behind the component's back is overwritten by the next comparison.
202
- *
203
- * @param {number} entity
204
- * @returns {ParticleEmitter|null} `null` for an entity this system has not linked, or one whose
205
- * component has no compiled effect yet
206
- */
207
- emitter_of(entity) {
208
- const entry = this.#entries.get(entity);
209
-
210
- if (entry === undefined) {
211
- return null;
212
- }
213
-
214
- return entry.emitter;
215
- }
216
-
217
- /**
218
- * Ask an entity's emitter for `count` particles on top of its rate, on the next frame.
219
- *
220
- * The one-shot half of the API, and the only thing here the CPU asks for rather than describes:
221
- * an impact, a footfall, a muzzle flash. It goes through the same GPU path as continuous
222
- * emission — the tick pass folds the burst into the emitter's accumulator, and the pool budget
223
- * and the culling apply — so it is a request rather than a guarantee, and an entity whose
224
- * emitter is not live by the time the frame starts is silently skipped.
225
- *
226
- * @param {number} entity
227
- * @param {number} count
228
- */
229
- burst(entity, count) {
230
- assert.isNonNegativeInteger(count, "count");
231
-
232
- if (count === 0) {
233
- return;
234
- }
235
-
236
- if (this.#bursts.length >= BURST_QUEUE_LIMIT * 2) {
237
- /*
238
- The queue is drained by a frame, so it only grows without bound when frames have stopped
239
- arriving — a scene nobody draws, or a device that never came up — and gameplay carries on
240
- asking. Dropping is right either way: nothing is going to spawn these, and a burst is a
241
- request about the moment it was made.
242
-
243
- The text is the key `warn_limited` counts on, so it carries no entity: one message per
244
- limit, not one per caller that happens to be unlucky.
245
- */
246
- warn_limited(
247
- `GPUParticleEmitterSystem: ${BURST_QUEUE_LIMIT} bursts are queued and no frame has `
248
- + "drained them; dropping the rest"
249
- );
250
-
251
- return;
252
- }
253
-
254
- this.#bursts.push(entity, count);
255
- }
256
-
257
- /**
258
- * @param {import("../ecs/EntityManager.js").EntityManager} entityManager
259
- * @returns {Promise<void>}
260
- */
261
- async startup(entityManager) {
262
- this.entityManager = entityManager;
263
-
264
- // the scene's rows are this dataset's entities; the first system to place one says so
265
- const dataset = entityManager.dataset;
266
- const scene = this.#scene;
267
-
268
- assert.ok(
269
- scene.dataset === null || scene.dataset === undefined || scene.dataset === dataset,
270
- 'the scene is bound to another dataset'
271
- );
272
-
273
- scene.dataset = dataset;
274
-
275
- if (!this.#assets.hasLoaderForType(GameAssetType.Image)) {
276
- await this.#assets.registerLoader(GameAssetType.Image, new ImageRGBADataLoader());
277
- }
278
-
279
- this.#extension = new GPUParticleAtlasExtension(this);
280
-
281
- this.#graphics.add_extension(this.#extension);
282
- }
283
-
284
- /** @returns {Promise<void>} */
285
- async shutdown() {
286
- if (this.#extension !== null) {
287
- this.#graphics.remove_extension(this.#extension);
288
- this.#extension = null;
289
- }
290
-
291
- for (const entity of Array.from(this.#entries.keys())) {
292
- this.#drop(entity);
293
- }
294
-
295
- this.#detach();
296
-
297
- this.#texture?.destroy();
298
- this.#texture = null;
299
- this.#view = null;
300
- this.#device = null;
301
- this.#uploaded_version = -1;
302
- this.#bursts.length = 0;
303
-
304
- // An in-flight load checks its record's identity here before adding a patch.
305
- this.#textures.clear();
306
-
307
- this.atlas.reset();
308
- this.#create_defaults();
309
- }
310
-
311
- /**
312
- * @param {ParticleEffect} effect
313
- * @param {Transform64} transform
314
- * @param {number} entity
315
- */
316
- link(effect, transform, entity) {
317
- /** @type {ParticleEmitterEntry} */
318
- const entry = {
319
- entity,
320
- effect,
321
- transform,
322
- emitter: null,
323
- context: null,
324
- on_transform_changed: null,
325
- url: null,
326
- texture: null
327
- };
328
-
329
- this.#entries.set(entity, entry);
330
-
331
- /*
332
- The emitter is placed the moment its transform moves rather than on a poll. A Transform64
333
- has no signals of its own, so the announcement comes from the dataset: whoever wrote the
334
- transform sends TRANSFORM64_EVENT_CHANGE and this wakes on it. Coarser than watching
335
- position, rotation and scale separately — a scale write wakes a placement that reads the
336
- whole transform anyway — and that is the trade the type makes everywhere.
337
- */
338
- entry.on_transform_changed = () => this.#place(entry);
339
-
340
- this.entityManager.dataset.addEntityEventListener(
341
- entity, TRANSFORM64_EVENT_CHANGE, entry.on_transform_changed
342
- );
343
-
344
- // Everything else — building the emitter, acquiring the texture — is what a frame does
345
- // anyway, and doing it here as well would be a second copy of it that has to stay in step.
346
- this.#sync(entry);
347
-
348
- // Registered now when there is a context to register with, so an entity spawned mid-level
349
- // stands where it should from its first frame.
350
- this.#register_all();
351
- }
352
-
353
- /**
354
- * @param {ParticleEffect} effect
355
- * @param {Transform64} transform
356
- * @param {number} entity
357
- */
358
- unlink(effect, transform, entity) {
359
- this.#drop(entity);
360
- }
361
-
362
- /**
363
- * Bring every emitter in line with its entity: build emitters for effects that have arrived,
364
- * retire emitters for effects that have gone, copy changed fields onto the ones that stay, and
365
- * follow the texture URLs.
366
- *
367
- * @param {number} [time_delta_seconds]
368
- * @returns {void}
369
- */
370
- update(time_delta_seconds) {
371
- for (const entry of this.#entries.values()) {
372
- this.#sync(entry);
373
- }
374
-
375
- /*
376
- Registered here as well as at FrameStart, so an emitter whose effect arrived during a
377
- simulation tick has its row written by the next frame's build rather than the one after:
378
- a frame builds the scene context before FrameStart runs, so a row acquired at FrameStart
379
- is not written until the frame after.
380
- */
381
- this.#register_all();
382
-
383
- this.atlas.update();
384
- }
385
-
386
- /**
387
- * The particle system a scene context's frames run, or null while there is no renderer.
388
- *
389
- * @param {import("../../shade/renderer/scene/GPUSceneContext.js").GPUSceneContext} context
390
- * @returns {import("../../shade/renderer/particles/GPUParticleSystem.js").GPUParticleSystem|null}
391
- */
392
- #particles_of(context) {
393
- const renderer = this.#graphics.renderer;
394
-
395
- if (renderer === null || renderer === undefined) {
396
- return null;
397
- }
398
-
399
- return renderer.particles(context);
400
- }
401
-
402
- /**
403
- * {@link #register} against whatever context and particle system the scene has right now, if
404
- * it has any.
405
- */
406
- #register_all() {
407
- const context = this.#graphics.scene_context(this.#scene);
408
-
409
- if (context === null) {
410
- return;
411
- }
412
-
413
- const particles = this.#particles_of(context);
414
-
415
- if (particles !== null) {
416
- this.#register(particles, context);
417
- }
418
- }
419
-
420
- /**
421
- * Publish the atlas at FrameStart — after the scene context has established transform rows, and
422
- * before the renderer records the particle passes that read the emitter table.
423
- *
424
- * {@link update} is called from here as well as by the entity manager, because a frame must not
425
- * depend on a simulation tick having preceded it: an editor draws on demand, and a paused game
426
- * still renders. It is idempotent and costs a comparison per emitter when nothing has changed,
427
- * which is what makes running it twice a frame the cheap way to be right rather than a saving
428
- * worth chasing.
429
- *
430
- * @param {import("../../shade/renderer/extension/FrameContext.js").FrameContext} frame
431
- * @returns {void}
432
- */
433
- prepare(frame) {
434
- if (frame.view.scene.scene !== this.#scene) {
435
- return;
436
- }
437
-
438
- this.update();
439
-
440
- const renderer = this.#graphics.renderer;
441
-
442
- /*
443
- The renderer's GPU particle feature is this system's to turn on, and it is turned on every
444
- frame rather than once, because a device restart builds a renderer that has never heard of
445
- it. The consequence is worth stating: while this system is in the profile there is no way to
446
- switch GPU particles off through the renderer, because this would switch them back on at the
447
- next FrameStart. Switching them off means taking the system out — or, for one effect,
448
- `ParticleEffect#emitting`.
449
- */
450
- renderer.feature_particles_enabled = true;
451
-
452
- const particles = renderer.particles(frame.view.scene);
453
-
454
- if (particles !== this.#particles) {
455
- this.#detach();
456
- this.#particles = particles;
457
- this.#previous_atlas = particles.atlas;
458
- }
459
-
460
- this.#register(particles, frame.view.scene);
461
-
462
- this.#upload(renderer.graphics.device);
463
-
464
- particles.atlas = this.#view;
465
-
466
- this.#publish_regions(particles);
467
- this.#drain_bursts(particles);
468
- }
469
-
470
- /**
471
- * Register every emitter not yet registered with this context, at its entity's row.
472
- *
473
- * From {@link update} and {@link prepare} rather than at link, because a row needs a scene
474
- * context and the context needs a device, and neither is a system's to wait for: a level loads
475
- * before the device is up, and a device restart replaces the context under every entry. An
476
- * entry registered with a context that is no longer this one is registered again with the one
477
- * that is. Idempotent, and a comparison per entry when nothing has changed.
478
- *
479
- * @param {import("../../shade/renderer/particles/GPUParticleSystem.js").GPUParticleSystem} particles
480
- * @param {import("../../shade/renderer/scene/GPUSceneContext.js").GPUSceneContext} scene_context
481
- */
482
- #register(particles, scene_context) {
483
- const rows = scene_context.rows;
484
-
485
- for (const entry of this.#entries.values()) {
486
- if (entry.emitter === null || entry.context === scene_context) {
487
- continue;
488
- }
489
-
490
- rows.acquire(entry.entity);
491
-
492
- particles.add_emitter(entry.emitter, row_of_entity(entry.entity));
493
-
494
- entry.context = scene_context;
495
- }
496
- }
497
-
498
- /**
499
- * Point every emitter of this scene at its patch in the atlas.
500
- *
501
- * Every region, every frame: growth also moves the UVs of images that did not change, and there
502
- * is no signal for that which an emitter could subscribe to.
503
- *
504
- * @param {import("../../shade/renderer/particles/GPUParticleSystem.js").GPUParticleSystem} particles
505
- */
506
- #publish_regions(particles) {
507
- const registry = particles.registry;
508
- const emitters = registry.emitters;
509
- const region = this.#region;
510
-
511
- for (let i = 0; i < emitters.length; i++) {
512
- const emitter = emitters[i];
513
- const entry = this.#by_emitter.get(emitter);
514
-
515
- // Not ours: it has nothing in this atlas, so it gets the one patch that is true for
516
- // everybody — a white texel, which draws it as a flat quad of its own colour.
517
- const patch = entry === undefined || entry.texture === null
518
- ? this.#white
519
- : entry.texture.patch ?? this.#transparent;
520
-
521
- const uv = patch.uv;
522
-
523
- if (patch === this.#white || patch === this.#transparent) {
524
- // Sample the texel centre everywhere, avoiding interpolation into the gutter.
525
- region[0] = uv.position.x + uv.size.x * 0.5;
526
- region[1] = uv.position.y + uv.size.y * 0.5;
527
- region[2] = 0;
528
- region[3] = 0;
529
- } else {
530
- region[0] = uv.position.x;
531
- region[1] = uv.position.y;
532
- region[2] = uv.size.x;
533
- region[3] = uv.size.y;
534
- }
535
-
536
- registry.set_atlas_region(emitter, region);
537
- }
538
- }
539
-
540
- /**
541
- * Hand this frame's bursts to the particle system, now that membership has been swept and every
542
- * emitter that can have a row has one.
543
- *
544
- * The queue is emptied whether or not each burst could be delivered. A burst is a request about
545
- * *this* moment — a footfall, an impact — and holding one for an emitter that is not live yet
546
- * would deliver it seconds late, at whatever unrelated moment the emitter finally arrived.
547
- *
548
- * @param {import("../../shade/renderer/particles/GPUParticleSystem.js").GPUParticleSystem} particles
549
- */
550
- #drain_bursts(particles) {
551
- const bursts = this.#bursts;
552
-
553
- for (let i = 0; i < bursts.length; i += 2) {
554
- const entry = this.#entries.get(bursts[i]);
555
-
556
- if (entry === undefined || entry.emitter === null) {
557
- continue;
558
- }
559
-
560
- if (particles.registry.context(entry.emitter) === undefined) {
561
- continue;
562
- }
563
-
564
- particles.spawn(entry.emitter, bursts[i + 1]);
565
- }
566
-
567
- bursts.length = 0;
568
- }
569
-
570
- /**
571
- * One entity's emitter, brought in line with its component.
572
- *
573
- * @param {ParticleEmitterEntry} entry
574
- */
575
- #sync(entry) {
576
- const effect = entry.effect;
577
-
578
- /*
579
- Ahead of everything the effect gates, and deliberately: an image takes a fetch and a decode
580
- to arrive, and an entity that names one has said what it wants to draw with whether or not
581
- its program has finished compiling. Waiting would mean a first frame of transparent
582
- particles for every effect that arrives with its texture.
583
- */
584
- if (entry.url !== effect.texture) {
585
- this.#release(entry.url);
586
-
587
- entry.url = effect.texture;
588
- entry.texture = entry.url === null ? null : this.#acquire(entry.url);
589
- }
590
-
591
- if (effect.layout === null || effect.program === null) {
592
- // No effect (yet, or any more). An entity whose effect is still compiling is ordinary,
593
- // and so is one whose effect was cleared — both mean "emits nothing", and the emitter
594
- // is retired rather than keeping the last program it happened to have.
595
- this.#detach_emitter(entry);
596
-
597
- return;
598
- }
599
-
600
- if (entry.emitter === null) {
601
- const emitter = new ParticleEmitter();
602
-
603
- /*
604
- The emitter takes the entity's name when it has one, exactly as `ShadedGeometrySystem`
605
- does for a primitive. Nothing draws differently for it: what it buys is that a walk over
606
- the registry is readable in a debugger.
607
- */
608
- const name = this.entityManager.dataset.getComponent(entry.entity, Name);
609
-
610
- emitter.name = name === undefined || name === null
611
- ? `particles/entity ${entry.entity}`
612
- : name.getValue();
613
-
614
- entry.emitter = emitter;
615
-
616
- apply_particle_effect(emitter, effect);
617
-
618
- // registered at the entity's row by the next frame — see #register
619
- this.#by_emitter.set(emitter, entry);
620
- } else if (apply_particle_effect(entry.emitter, effect)) {
621
- // The emitter's own change counter, which is what EmitterRegistry restages a row from.
622
- entry.emitter.needsUpdate = true;
623
- }
624
- }
625
-
626
- /**
627
- * The entity moved: its row is rewritten from its transform by the context that holds it.
628
- *
629
- * @param {ParticleEmitterEntry} entry
630
- */
631
- #place(entry) {
632
- const context = entry.context;
633
-
634
- if (context === null || context !== this.#graphics.scene_context(this.#scene)) {
635
- return;
636
- }
637
-
638
- context.rows.mark_world(entry.entity);
639
- }
640
-
641
- /**
642
- * Retire an entity's emitter and let go of its row, leaving the entry linked.
643
- *
644
- * @param {ParticleEmitterEntry} entry
645
- */
646
- #detach_emitter(entry) {
647
- const emitter = entry.emitter;
648
-
649
- if (emitter === null) {
650
- return;
651
- }
652
-
653
- const context = entry.context;
654
-
655
- // A context that has been replaced took its rows and its particle system with it; only the
656
- // one that is running is handed anything back.
657
- if (context !== null && context === this.#graphics.scene_context(this.#scene)) {
658
- const particles = this.#particles_of(context);
659
-
660
- if (particles !== null && particles.registry.context(emitter) !== undefined) {
661
- particles.remove_emitter(emitter);
662
- }
663
-
664
- context.rows.release(entry.entity);
665
- }
666
-
667
- entry.context = null;
668
-
669
- this.#by_emitter.delete(emitter);
670
-
671
- entry.emitter = null;
672
- }
673
-
674
- /**
675
- * @param {number} entity
676
- */
677
- #drop(entity) {
678
- const entry = this.#entries.get(entity);
679
-
680
- if (entry === undefined) {
681
- return;
682
- }
683
-
684
- if (entry.on_transform_changed !== null) {
685
- this.entityManager.dataset.removeEntityEventListener(
686
- entity, TRANSFORM64_EVENT_CHANGE, entry.on_transform_changed
687
- );
688
-
689
- entry.on_transform_changed = null;
690
- }
691
-
692
- this.#detach_emitter(entry);
693
- this.#release(entry.url);
694
-
695
- entry.url = null;
696
- entry.texture = null;
697
-
698
- this.#entries.delete(entity);
699
- }
700
-
701
- /** @returns {void} */
702
- #create_defaults() {
703
- const white = Sampler2D.uint8(4, 1, 1);
704
-
705
- white.data.fill(255);
706
-
707
- this.#white = this.atlas.add(white);
708
- this.#transparent = this.atlas.add(Sampler2D.uint8(4, 1, 1));
709
- }
710
-
711
- /**
712
- * @param {string} url
713
- * @returns {ParticleAtlasTexture}
714
- */
715
- #acquire(url) {
716
- let texture = this.#textures.get(url);
717
-
718
- if (texture !== undefined) {
719
- texture.references++;
720
-
721
- return texture;
722
- }
723
-
724
- texture = { references: 1, patch: null };
725
-
726
- this.#textures.set(url, texture);
727
-
728
- this.#assets.promise(url, GameAssetType.Image).then(asset => {
729
- // Removed, changed URL, or shut down while loading: the completion owns nothing now.
730
- if (this.#textures.get(url) !== texture) {
731
- return;
732
- }
733
-
734
- texture.patch = this.atlas.add(sampler2d_ensure_uint8_RGBA(asset.create()));
735
- }).catch(error => {
736
- if (this.#textures.get(url) === texture) {
737
- console.warn(`GPU particle texture '${url}' could not be loaded`, error);
738
- }
739
- });
740
-
741
- return texture;
742
- }
743
-
744
- /**
745
- * @param {string|null} url
746
- * @returns {void}
747
- */
748
- #release(url) {
749
- if (url === null) {
750
- return;
751
- }
752
-
753
- const texture = this.#textures.get(url);
754
-
755
- if (texture === undefined || --texture.references !== 0) {
756
- return;
757
- }
758
-
759
- this.#textures.delete(url);
760
-
761
- if (texture.patch !== null) {
762
- this.atlas.remove(texture.patch);
763
- }
764
- }
765
-
766
- /**
767
- * @param {GPUDevice} device
768
- * @returns {void}
769
- */
770
- #upload(device) {
771
- const source = this.atlas.sampler;
772
-
773
- if (this.#device !== device || this.#texture === null) {
774
- this.#texture?.destroy();
775
-
776
- this.#device = device;
777
- this.#texture = new GPUTextureContext(device);
778
-
779
- const descriptor = this.#texture.descriptor;
780
-
781
- descriptor.label = "particles/sprite atlas";
782
- descriptor.format = "rgba8unorm";
783
- descriptor.usage = GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_DST;
784
-
785
- this.#uploaded_version = -1;
786
- }
787
-
788
- const texture = this.#texture;
789
-
790
- if (texture.width !== source.width || texture.height !== source.height) {
791
- texture.resize(source.width, source.height);
792
-
793
- this.#uploaded_version = -1;
794
- }
795
-
796
- if (this.#uploaded_version !== source.version) {
797
- device.queue.writeTexture({ texture: texture.gpu_texture }, source.data,
798
- { bytesPerRow: source.width * 4 }, [source.width, source.height]);
799
-
800
- texture.incrementVersion();
801
-
802
- this.#uploaded_version = source.version;
803
- }
804
-
805
- this.#view = texture.obtainView();
806
- }
807
-
808
- /** @returns {void} */
809
- #detach() {
810
- if (this.#particles !== null && this.#particles.atlas === this.#view) {
811
- this.#particles.atlas = this.#previous_atlas;
812
-
813
- for (const emitter of this.#particles.emitters) {
814
- this.#particles.registry.set_atlas_region(emitter, [0, 0, 1, 1]);
815
- }
816
- }
817
-
818
- this.#particles = null;
819
- this.#previous_atlas = null;
820
- }
821
- }
822
-
823
- /**
824
- * Copy a component onto the emitter built for it, reporting whether anything actually moved.
825
- *
826
- * Comparing while assigning rather than in a pass of its own is what makes change detection cost
827
- * nothing extra: the fields have to be read to be written, and the only addition is an `||` per
828
- * field. What it answers is whether the emitter's GPU row has to be restaged, and restaging one that
829
- * has not changed is an upload per emitter per frame.
830
- *
831
- * `spawn_rate` comes from {@link ParticleEffect#effective_spawn_rate}, so an effect switched off
832
- * with `emitting = false` reaches the GPU as a rate of zero and keeps its authored rate to come back
833
- * to. Layout and program are shared by reference, never copied — two entities of one effect belong
834
- * on one program id in the heap.
835
- *
836
- * Every field is compared by name, and that is load-bearing rather than style. Through a shared
837
- * `assign(target, field, value)` the one `target[field]` access saw all thirteen names, went
838
- * megamorphic, and read numbers back boxed, while the call boxed the value it was handed: 228 B and
839
- * ~120 ns per emitter per call, against nothing and ~9 ns written out like this
840
- * (`GPUParticleEmitterSystem.allocation.spec.js`). This runs for every live emitter twice a frame.
841
- *
842
- * @param {ParticleEmitter} emitter
843
- * @param {ParticleEffect} effect
844
- * @returns {boolean} whether the emitter differs from what it held before
845
- */
846
- export function apply_particle_effect(emitter, effect) {
847
- let changed = false;
848
-
849
- if (emitter.layout !== effect.layout) {
850
- emitter.layout = effect.layout;
851
- changed = true;
852
- }
853
-
854
- if (emitter.program !== effect.program) {
855
- emitter.program = effect.program;
856
- changed = true;
857
- }
858
-
859
- if (emitter.texture !== effect.texture) {
860
- emitter.texture = effect.texture;
861
- changed = true;
862
- }
863
-
864
- if (emitter.seed !== effect.seed) {
865
- emitter.seed = effect.seed;
866
- changed = true;
867
- }
868
-
869
- const spawn_rate = effect.effective_spawn_rate;
870
-
871
- if (emitter.spawn_rate !== spawn_rate) {
872
- emitter.spawn_rate = spawn_rate;
873
- changed = true;
874
- }
875
-
876
- if (emitter.prewarm !== effect.prewarm) {
877
- emitter.prewarm = effect.prewarm;
878
- changed = true;
879
- }
880
-
881
- if (emitter.flags !== effect.flags) {
882
- emitter.flags = effect.flags;
883
- changed = true;
884
- }
885
-
886
- if (emitter.render_position !== effect.render_position) {
887
- emitter.render_position = effect.render_position;
888
- changed = true;
889
- }
890
-
891
- if (emitter.render_size !== effect.render_size) {
892
- emitter.render_size = effect.render_size;
893
- changed = true;
894
- }
895
-
896
- if (emitter.render_color !== effect.render_color) {
897
- emitter.render_color = effect.render_color;
898
- changed = true;
899
- }
900
-
901
- if (emitter.render_orientation !== effect.render_orientation) {
902
- emitter.render_orientation = effect.render_orientation;
903
- changed = true;
904
- }
905
-
906
- if (emitter.render_frame !== effect.render_frame) {
907
- emitter.render_frame = effect.render_frame;
908
- changed = true;
909
- }
910
-
911
- if (emitter.render_velocity !== effect.render_velocity) {
912
- emitter.render_velocity = effect.render_velocity;
913
- changed = true;
914
- }
915
-
916
- const flipbook = emitter.flipbook;
917
-
918
- if (flipbook[0] !== effect.flipbook[0] || flipbook[1] !== effect.flipbook[1]) {
919
- flipbook.set(effect.flipbook);
920
-
921
- changed = true;
922
- }
923
-
924
- return changed;
925
- }
926
-
927
- class GPUParticleAtlasExtension extends RenderExtension {
928
- /** @type {string} */
929
- name = "gpu particle atlas";
930
- /** @type {FramePhase} */
931
- phase = FramePhase.FrameStart;
932
- /** @type {GPUParticleEmitterSystem} */
933
- #system;
934
-
935
- /** @param {GPUParticleEmitterSystem} system */
936
- constructor(system) {
937
- super();
938
-
939
- this.#system = system;
940
- }
941
-
942
- /**
943
- * @param {import("../../shade/renderer/extension/FrameContext.js").FrameContext} frame
944
- * @returns {void}
945
- */
946
- record(frame) {
947
- this.#system.prepare(frame);
948
- }
949
- }
1
+ import { assert } from "../../core/assert.js";
2
+ import { warn_limited } from "../../shade/util/warn_limited.js";
3
+ import { FramePhase } from "../../shade/renderer/extension/FramePhase.js";
4
+ import { RenderExtension } from "../../shade/renderer/extension/RenderExtension.js";
5
+ import { ParticleEmitter } from "../../shade/renderer/particles/runtime/ParticleEmitter.js";
6
+ import { GPUTextureContext } from "../../shade/renderer/texture/GPUTextureContext.js";
7
+ import { System } from "../ecs/System.js";
8
+ import Name from "../ecs/name/Name.js";
9
+ import { Transform64 } from "../ecs/transform/Transform64.js";
10
+ import { TRANSFORM64_EVENT_CHANGE } from "../ecs/transform/TRANSFORM64_EVENT_CHANGE.js";
11
+ import { row_of_entity } from "../../shade/renderer/scene/rows/GPUSceneRows.js";
12
+ import { GameAssetType } from "../asset/GameAssetType.js";
13
+ import { ImageRGBADataLoader } from "../asset/loaders/image/ImageRGBADataLoader.js";
14
+ import { ParticleEffect } from "../graphics/ecs/particles/ParticleEffect.js";
15
+ import { ManagedAtlas } from "../graphics/texture/atlas/ManagedTextureAtlas.js";
16
+ import { SpriteAtlas } from "../graphics/texture/atlas/SpriteAtlas.js";
17
+ import { SpriteAtlasFill } from "../graphics/texture/atlas/SpriteAtlasFill.js";
18
+ import { TextureFilterType } from "../../shade/renderer/texture/TextureFilterType.js";
19
+ import { Sampler2D } from "../graphics/texture/sampler/Sampler2D.js";
20
+
21
+ /**
22
+ * Bursts {@link GPUParticleEmitterSystem#burst} will hold before it starts dropping them. Reached
23
+ * only when frames have stopped arriving — see the call site.
24
+ *
25
+ * @type {number}
26
+ */
27
+ const BURST_QUEUE_LIMIT = 256;
28
+
29
+ /**
30
+ * @typedef {object} ParticleEmitterEntry
31
+ * @property {number} entity
32
+ * @property {ParticleEffect} effect the entity's component — what to emit
33
+ * @property {Transform64} transform the entity's transform — where to emit it
34
+ * @property {ParticleEmitter|null} emitter the emitter, once the effect has a program to run
35
+ * @property {import("../../shade/renderer/scene/GPUSceneContext.js").GPUSceneContext|null} context
36
+ * the scene context the emitter is registered with, at the entity's row; null until a frame has
37
+ * registered it, or after a device restart took that context away
38
+ * @property {function():void} on_transform_changed
39
+ * @property {string|null} url last observed {@link ParticleEffect#texture}
40
+ * @property {import("../graphics/texture/atlas/ManagedTextureAtlas.js").ManagedAtlasReference|null} texture shared image reference, or null for an untextured effect
41
+ */
42
+
43
+ /**
44
+ * Draws the game's GPU particle effects: one emitter per entity carrying
45
+ * {@link ../graphics/ecs/particles/ParticleEffect.js ParticleEffect} and
46
+ * {@link ../ecs/transform/Transform64.js Transform64}.
47
+ *
48
+ * ```js
49
+ * entityManager.addSystem(new GPUParticleEmitterSystem(graphics, scene, assetManager));
50
+ *
51
+ * new Entity()
52
+ * .add(new Transform64())
53
+ * .add(ParticleEffect.from({ ...create_particle_effect({ layout, init, update }), spawn_rate: 400 }))
54
+ * .build(dataset);
55
+ * ```
56
+ *
57
+ * **The entity is the emitter.** The component says what it emits and the transform says where, the
58
+ * same division every other placed thing in this engine uses — a `Decal`, an `SGMesh`, a `Light`.
59
+ * Which means an emitter is spawned, moved, parented, attached to a bone, saved and destroyed by
60
+ * exactly the machinery that does those things for everything else, and a particle effect stops
61
+ * being a special case that needs its own scene-graph call.
62
+ *
63
+ * ## What this system does with the pair
64
+ *
65
+ * It builds a {@link ParticleEmitter} per entity and owns it: the emitter is registered with the
66
+ * scene's particle system at the entity's row when the component has a compiled effect, so it
67
+ * stands where the entity stands, carries whatever the component currently says, and is retired
68
+ * when the entity goes. The emitter is not hidden — {@link emitter_of} hands it out — but it is
69
+ * not the interface either: write to the component and the next frame carries it.
70
+ *
71
+ * Changes are found by **comparison** rather than announced. Once a frame each entity's
72
+ * fields are compared against what was last written to its emitter, and the emitter is marked dirty only
73
+ * where they differ; `EmitterRegistry` then restages exactly the rows that moved. A component nobody
74
+ * touched costs a run of integer comparisons and no upload, so there is no change signal to fire and
75
+ * none to forget. What that buys is that gameplay can write `effect.spawn_rate = 0` anywhere,
76
+ * including from code that has never heard of this system.
77
+ *
78
+ * ## It owns the scene's sprite atlas
79
+ *
80
+ * Every {@link ParticleEffect#texture} is loaded through the supplied `AssetManager` and packed into
81
+ * one {@link SpriteAtlas} shared by the scene, one patch per distinct URL however many entities
82
+ * name it, reference-counted through {@link ManagedAtlas} so unreferenced sprites remain in the
83
+ * atlas graveyard and are only reaped when incoming sprites need the space. A `FrameStart`
84
+ * extension publishes the pixels and every current patch region before the renderer records the
85
+ * particle passes — every repack moves UVs that nothing else changed, so the regions are rewritten
86
+ * whole rather than when a URL changes. Pending and failed images sample a transparent texel;
87
+ * effects with no image sample a white one.
88
+ *
89
+ * An emitter in this scene that this system did not build — one registered with the particle
90
+ * system directly — gets the white texel. The atlas is the scene's and that emitter has nothing in
91
+ * it, so the alternative is not "its own texture" but a UV rectangle from somebody else's image.
92
+ *
93
+ * ## What it is not
94
+ *
95
+ * It does not simulate anything and does not record a particle pass. `GPUParticleSystem` does both,
96
+ * inside the renderer, driven from the emitter table these rows are in — see
97
+ * `shade/renderer/particles/DESIGN.md`. This system's whole contribution is membership, placement,
98
+ * the fields, and the sprite sheet.
99
+ */
100
+ export class GPUParticleEmitterSystem extends System {
101
+ dependencies = [ParticleEffect, Transform64];
102
+
103
+ /**
104
+ * CPU atlas, exposed for inspection. Owned by this system.
105
+ * @type {SpriteAtlas}
106
+ */
107
+ atlas = new SpriteAtlas(SpriteAtlasFill.EdgeExtend, 64);
108
+
109
+ /** @type {import("./GraphicsEngine.js").GraphicsEngine} */
110
+ #graphics;
111
+ /** @type {import("../../shade/renderer/scene/Scene.js").Scene} */
112
+ #scene;
113
+ /** @type {import("../asset/AssetManager.js").AssetManager} */
114
+ #assets;
115
+ /** @type {Map<number, ParticleEmitterEntry>} */
116
+ #entries = new Map();
117
+ /**
118
+ * The same entries by the emitter they own, so that the emitters this system did not build can
119
+ * be told apart from the ones it did without a linear search per emitter per frame.
120
+ * @type {Map<ParticleEmitter, ParticleEmitterEntry>}
121
+ */
122
+ #by_emitter = new Map();
123
+ /** @type {ManagedAtlas} */
124
+ #managed_atlas;
125
+ /**
126
+ * Bursts asked for through {@link burst} and not yet handed to the particle system. Flat pairs
127
+ * of `entity, count`, drained every frame.
128
+ * @type {number[]}
129
+ */
130
+ #bursts = [];
131
+ /** @type {import("../graphics/texture/atlas/AtlasPatch.js").AtlasPatch} */
132
+ #white;
133
+ /** @type {import("../graphics/texture/atlas/AtlasPatch.js").AtlasPatch} */
134
+ #transparent;
135
+ /** @type {GPUParticleAtlasExtension|null} */
136
+ #extension = null;
137
+ /** @type {GPUTextureContext|null} */
138
+ #texture = null;
139
+ /** @type {GPUTextureView|null} */
140
+ #view = null;
141
+ /** @type {GPUDevice|null} */
142
+ #device = null;
143
+ /** @type {number} */
144
+ #uploaded_version = -1;
145
+ /** @type {import("../../shade/renderer/particles/GPUParticleSystem.js").GPUParticleSystem|null} */
146
+ #particles = null;
147
+ /** @type {GPUTextureView|null} */
148
+ #previous_atlas = null;
149
+ /** @type {Float32Array} */
150
+ #region = new Float32Array(4);
151
+
152
+ /**
153
+ * @param {import("./GraphicsEngine.js").GraphicsEngine} graphics
154
+ * @param {import("../../shade/renderer/scene/Scene.js").Scene} scene scene the emitters are put
155
+ * in, and whose particle atlas this system owns
156
+ * @param {import("../asset/AssetManager.js").AssetManager} assets
157
+ */
158
+ constructor(graphics, scene, assets) {
159
+ super();
160
+
161
+ assert.defined(graphics, "graphics");
162
+ assert.equal(scene.isScene, true, "scene.isScene !== true");
163
+ assert.defined(assets, "assets");
164
+
165
+ this.#graphics = graphics;
166
+ this.#scene = scene;
167
+ this.#assets = assets;
168
+ this.#managed_atlas = new ManagedAtlas({
169
+ assetManager: this.#assets,
170
+ atlas: this.atlas
171
+ });
172
+
173
+ this.#create_defaults();
174
+ }
175
+
176
+ /**
177
+ * The scene the emitters are put in. Received rather than created: meshes and lights come from
178
+ * systems of their own, and all of them have to be writing into one scene for any of it to be
179
+ * visible together.
180
+ *
181
+ * @returns {import("../../shade/renderer/scene/Scene.js").Scene}
182
+ */
183
+ get scene() {
184
+ return this.#scene;
185
+ }
186
+
187
+ /**
188
+ * The emitters this system currently holds, in no particular order. Read-only — they follow
189
+ * the entities.
190
+ *
191
+ * @returns {ParticleEmitter[]}
192
+ */
193
+ get emitters() {
194
+ return Array.from(this.#by_emitter.keys());
195
+ }
196
+
197
+ /**
198
+ * The emitter built for an entity.
199
+ *
200
+ * Handed out for what an entity reference cannot express — asking the particle system about
201
+ * it — and for nothing routine: the component is how an effect is changed, and an emitter
202
+ * written to behind the component's back is overwritten by the next comparison.
203
+ *
204
+ * @param {number} entity
205
+ * @returns {ParticleEmitter|null} `null` for an entity this system has not linked, or one whose
206
+ * component has no compiled effect yet
207
+ */
208
+ emitter_of(entity) {
209
+ const entry = this.#entries.get(entity);
210
+
211
+ if (entry === undefined) {
212
+ return null;
213
+ }
214
+
215
+ return entry.emitter;
216
+ }
217
+
218
+ /**
219
+ * Ask an entity's emitter for `count` particles on top of its rate, on the next frame.
220
+ *
221
+ * The one-shot half of the API, and the only thing here the CPU asks for rather than describes:
222
+ * an impact, a footfall, a muzzle flash. It goes through the same GPU path as continuous
223
+ * emission — the tick pass folds the burst into the emitter's accumulator, and the pool budget
224
+ * and the culling apply — so it is a request rather than a guarantee, and an entity whose
225
+ * emitter is not live by the time the frame starts is silently skipped.
226
+ *
227
+ * @param {number} entity
228
+ * @param {number} count
229
+ */
230
+ burst(entity, count) {
231
+ assert.isNonNegativeInteger(count, "count");
232
+
233
+ if (count === 0) {
234
+ return;
235
+ }
236
+
237
+ if (this.#bursts.length >= BURST_QUEUE_LIMIT * 2) {
238
+ /*
239
+ The queue is drained by a frame, so it only grows without bound when frames have stopped
240
+ arriving — a scene nobody draws, or a device that never came up — and gameplay carries on
241
+ asking. Dropping is right either way: nothing is going to spawn these, and a burst is a
242
+ request about the moment it was made.
243
+
244
+ The text is the key `warn_limited` counts on, so it carries no entity: one message per
245
+ limit, not one per caller that happens to be unlucky.
246
+ */
247
+ warn_limited(
248
+ `GPUParticleEmitterSystem: ${BURST_QUEUE_LIMIT} bursts are queued and no frame has `
249
+ + "drained them; dropping the rest"
250
+ );
251
+
252
+ return;
253
+ }
254
+
255
+ this.#bursts.push(entity, count);
256
+ }
257
+
258
+ /**
259
+ * @param {import("../ecs/EntityManager.js").EntityManager} entityManager
260
+ * @returns {Promise<void>}
261
+ */
262
+ async startup(entityManager) {
263
+ this.entityManager = entityManager;
264
+
265
+ // the scene's rows are this dataset's entities; the first system to place one says so
266
+ const dataset = entityManager.dataset;
267
+ const scene = this.#scene;
268
+
269
+ assert.ok(
270
+ scene.dataset === null || scene.dataset === undefined || scene.dataset === dataset,
271
+ 'the scene is bound to another dataset'
272
+ );
273
+
274
+ scene.dataset = dataset;
275
+
276
+ if (!this.#assets.hasLoaderForType(GameAssetType.Image)) {
277
+ await this.#assets.registerLoader(GameAssetType.Image, new ImageRGBADataLoader());
278
+ }
279
+
280
+ this.#extension = new GPUParticleAtlasExtension(this);
281
+
282
+ this.#graphics.add_extension(this.#extension);
283
+ }
284
+
285
+ /** @returns {Promise<void>} */
286
+ async shutdown() {
287
+ if (this.#extension !== null) {
288
+ this.#graphics.remove_extension(this.#extension);
289
+ this.#extension = null;
290
+ }
291
+
292
+ for (const entity of Array.from(this.#entries.keys())) {
293
+ this.#drop(entity);
294
+ }
295
+
296
+ this.#detach();
297
+
298
+ this.#texture?.destroy();
299
+ this.#texture = null;
300
+ this.#view = null;
301
+ this.#device = null;
302
+ this.#uploaded_version = -1;
303
+ this.#bursts.length = 0;
304
+
305
+ this.#managed_atlas.reset();
306
+ this.#create_defaults();
307
+ }
308
+
309
+ /**
310
+ * @param {ParticleEffect} effect
311
+ * @param {Transform64} transform
312
+ * @param {number} entity
313
+ */
314
+ link(effect, transform, entity) {
315
+ /** @type {ParticleEmitterEntry} */
316
+ const entry = {
317
+ entity,
318
+ effect,
319
+ transform,
320
+ emitter: null,
321
+ context: null,
322
+ on_transform_changed: null,
323
+ url: null,
324
+ texture: null
325
+ };
326
+
327
+ this.#entries.set(entity, entry);
328
+
329
+ /*
330
+ The emitter is placed the moment its transform moves rather than on a poll. A Transform64
331
+ has no signals of its own, so the announcement comes from the dataset: whoever wrote the
332
+ transform sends TRANSFORM64_EVENT_CHANGE and this wakes on it. Coarser than watching
333
+ position, rotation and scale separately — a scale write wakes a placement that reads the
334
+ whole transform anyway — and that is the trade the type makes everywhere.
335
+ */
336
+ entry.on_transform_changed = () => this.#place(entry);
337
+
338
+ this.entityManager.dataset.addEntityEventListener(
339
+ entity, TRANSFORM64_EVENT_CHANGE, entry.on_transform_changed
340
+ );
341
+
342
+ // Everything else — building the emitter, acquiring the texture — is what a frame does
343
+ // anyway, and doing it here as well would be a second copy of it that has to stay in step.
344
+ this.#sync(entry);
345
+
346
+ // Registered now when there is a context to register with, so an entity spawned mid-level
347
+ // stands where it should from its first frame.
348
+ this.#register_all();
349
+ }
350
+
351
+ /**
352
+ * @param {ParticleEffect} effect
353
+ * @param {Transform64} transform
354
+ * @param {number} entity
355
+ */
356
+ unlink(effect, transform, entity) {
357
+ this.#drop(entity);
358
+ }
359
+
360
+ /**
361
+ * Bring every emitter in line with its entity: build emitters for effects that have arrived,
362
+ * retire emitters for effects that have gone, copy changed fields onto the ones that stay, and
363
+ * follow the texture URLs.
364
+ *
365
+ * @param {number} [time_delta_seconds]
366
+ * @returns {void}
367
+ */
368
+ update(time_delta_seconds) {
369
+ for (const entry of this.#entries.values()) {
370
+ this.#sync(entry);
371
+ }
372
+
373
+ /*
374
+ Registered here as well as at FrameStart, so an emitter whose effect arrived during a
375
+ simulation tick has its row written by the next frame's build rather than the one after:
376
+ a frame builds the scene context before FrameStart runs, so a row acquired at FrameStart
377
+ is not written until the frame after.
378
+ */
379
+ this.#register_all();
380
+
381
+ this.#managed_atlas.update();
382
+ }
383
+
384
+ /**
385
+ * The particle system a scene context's frames run, or null while there is no renderer.
386
+ *
387
+ * @param {import("../../shade/renderer/scene/GPUSceneContext.js").GPUSceneContext} context
388
+ * @returns {import("../../shade/renderer/particles/GPUParticleSystem.js").GPUParticleSystem|null}
389
+ */
390
+ #particles_of(context) {
391
+ const renderer = this.#graphics.renderer;
392
+
393
+ if (renderer === null || renderer === undefined) {
394
+ return null;
395
+ }
396
+
397
+ return renderer.particles(context);
398
+ }
399
+
400
+ /**
401
+ * {@link #register} against whatever context and particle system the scene has right now, if
402
+ * it has any.
403
+ */
404
+ #register_all() {
405
+ const context = this.#graphics.scene_context(this.#scene);
406
+
407
+ if (context === null) {
408
+ return;
409
+ }
410
+
411
+ const particles = this.#particles_of(context);
412
+
413
+ if (particles !== null) {
414
+ this.#register(particles, context);
415
+ }
416
+ }
417
+
418
+ /**
419
+ * Publish the atlas at FrameStart — after the scene context has established transform rows, and
420
+ * before the renderer records the particle passes that read the emitter table.
421
+ *
422
+ * {@link update} is called from here as well as by the entity manager, because a frame must not
423
+ * depend on a simulation tick having preceded it: an editor draws on demand, and a paused game
424
+ * still renders. It is idempotent and costs a comparison per emitter when nothing has changed,
425
+ * which is what makes running it twice a frame the cheap way to be right rather than a saving
426
+ * worth chasing.
427
+ *
428
+ * @param {import("../../shade/renderer/extension/FrameContext.js").FrameContext} frame
429
+ * @returns {void}
430
+ */
431
+ prepare(frame) {
432
+ if (frame.view.scene.scene !== this.#scene) {
433
+ return;
434
+ }
435
+
436
+ this.update();
437
+
438
+ const renderer = this.#graphics.renderer;
439
+
440
+ /*
441
+ The renderer's GPU particle feature is this system's to turn on, and it is turned on every
442
+ frame rather than once, because a device restart builds a renderer that has never heard of
443
+ it. The consequence is worth stating: while this system is in the profile there is no way to
444
+ switch GPU particles off through the renderer, because this would switch them back on at the
445
+ next FrameStart. Switching them off means taking the system out — or, for one effect,
446
+ `ParticleEffect#emitting`.
447
+ */
448
+ renderer.feature_particles_enabled = true;
449
+
450
+ const particles = renderer.particles(frame.view.scene);
451
+
452
+ if (particles !== this.#particles) {
453
+ this.#detach();
454
+ this.#particles = particles;
455
+ this.#previous_atlas = particles.atlas;
456
+ }
457
+
458
+ this.#register(particles, frame.view.scene);
459
+
460
+ this.#upload(renderer.graphics);
461
+
462
+ particles.atlas = this.#view;
463
+
464
+ this.#publish_regions(particles);
465
+ this.#drain_bursts(particles);
466
+ }
467
+
468
+ /**
469
+ * Register every emitter not yet registered with this context, at its entity's row.
470
+ *
471
+ * From {@link update} and {@link prepare} rather than at link, because a row needs a scene
472
+ * context and the context needs a device, and neither is a system's to wait for: a level loads
473
+ * before the device is up, and a device restart replaces the context under every entry. An
474
+ * entry registered with a context that is no longer this one is registered again with the one
475
+ * that is. Idempotent, and a comparison per entry when nothing has changed.
476
+ *
477
+ * @param {import("../../shade/renderer/particles/GPUParticleSystem.js").GPUParticleSystem} particles
478
+ * @param {import("../../shade/renderer/scene/GPUSceneContext.js").GPUSceneContext} scene_context
479
+ */
480
+ #register(particles, scene_context) {
481
+ const rows = scene_context.rows;
482
+
483
+ for (const entry of this.#entries.values()) {
484
+ if (entry.emitter === null || entry.context === scene_context) {
485
+ continue;
486
+ }
487
+
488
+ rows.acquire(entry.entity);
489
+
490
+ particles.add_emitter(entry.emitter, row_of_entity(entry.entity));
491
+
492
+ entry.context = scene_context;
493
+ }
494
+ }
495
+
496
+ /**
497
+ * Point every emitter of this scene at its patch in the atlas.
498
+ *
499
+ * Every region, every frame: growth also moves the UVs of images that did not change, and there
500
+ * is no signal for that which an emitter could subscribe to.
501
+ *
502
+ * @param {import("../../shade/renderer/particles/GPUParticleSystem.js").GPUParticleSystem} particles
503
+ */
504
+ #publish_regions(particles) {
505
+ const registry = particles.registry;
506
+ const emitters = registry.emitters;
507
+ const region = this.#region;
508
+
509
+ for (let i = 0; i < emitters.length; i++) {
510
+ const emitter = emitters[i];
511
+ const entry = this.#by_emitter.get(emitter);
512
+
513
+ // Not ours: it has nothing in this atlas, so it gets the one patch that is true for
514
+ // everybody — a white texel, which draws it as a flat quad of its own colour.
515
+ const patch = entry === undefined || entry.texture === null
516
+ ? this.#white
517
+ : entry.texture.getValue() ?? this.#transparent;
518
+
519
+ const uv = patch.uv;
520
+
521
+ if (patch === this.#white || patch === this.#transparent) {
522
+ // Sample the texel centre everywhere, avoiding interpolation into the gutter.
523
+ region[0] = uv.position.x + uv.size.x * 0.5;
524
+ region[1] = uv.position.y + uv.size.y * 0.5;
525
+ region[2] = 0;
526
+ region[3] = 0;
527
+ } else {
528
+ region[0] = uv.position.x;
529
+ region[1] = uv.position.y;
530
+ region[2] = uv.size.x;
531
+ region[3] = uv.size.y;
532
+ }
533
+
534
+ registry.set_atlas_region(emitter, region);
535
+ }
536
+ }
537
+
538
+ /**
539
+ * Hand this frame's bursts to the particle system, now that membership has been swept and every
540
+ * emitter that can have a row has one.
541
+ *
542
+ * The queue is emptied whether or not each burst could be delivered. A burst is a request about
543
+ * *this* moment — a footfall, an impact — and holding one for an emitter that is not live yet
544
+ * would deliver it seconds late, at whatever unrelated moment the emitter finally arrived.
545
+ *
546
+ * @param {import("../../shade/renderer/particles/GPUParticleSystem.js").GPUParticleSystem} particles
547
+ */
548
+ #drain_bursts(particles) {
549
+ const bursts = this.#bursts;
550
+
551
+ for (let i = 0; i < bursts.length; i += 2) {
552
+ const entry = this.#entries.get(bursts[i]);
553
+
554
+ if (entry === undefined || entry.emitter === null) {
555
+ continue;
556
+ }
557
+
558
+ if (particles.registry.context(entry.emitter) === undefined) {
559
+ continue;
560
+ }
561
+
562
+ particles.spawn(entry.emitter, bursts[i + 1]);
563
+ }
564
+
565
+ bursts.length = 0;
566
+ }
567
+
568
+ /**
569
+ * One entity's emitter, brought in line with its component.
570
+ *
571
+ * @param {ParticleEmitterEntry} entry
572
+ */
573
+ #sync(entry) {
574
+ const effect = entry.effect;
575
+
576
+ /*
577
+ Ahead of everything the effect gates, and deliberately: an image takes a fetch and a decode
578
+ to arrive, and an entity that names one has said what it wants to draw with whether or not
579
+ its program has finished compiling. Waiting would mean a first frame of transparent
580
+ particles for every effect that arrives with its texture.
581
+ */
582
+ if (entry.url !== effect.texture) {
583
+ entry.texture?.release();
584
+
585
+ entry.url = effect.texture;
586
+ entry.texture = entry.url === null ? null : this.#managed_atlas.acquire(entry.url);
587
+ }
588
+
589
+ if (effect.layout === null || effect.program === null) {
590
+ // No effect (yet, or any more). An entity whose effect is still compiling is ordinary,
591
+ // and so is one whose effect was cleared — both mean "emits nothing", and the emitter
592
+ // is retired rather than keeping the last program it happened to have.
593
+ this.#detach_emitter(entry);
594
+
595
+ return;
596
+ }
597
+
598
+ if (entry.emitter === null) {
599
+ const emitter = new ParticleEmitter();
600
+
601
+ /*
602
+ The emitter takes the entity's name when it has one, exactly as `ShadedGeometrySystem`
603
+ does for a primitive. Nothing draws differently for it: what it buys is that a walk over
604
+ the registry is readable in a debugger.
605
+ */
606
+ const name = this.entityManager.dataset.getComponent(entry.entity, Name);
607
+
608
+ emitter.name = name === undefined || name === null
609
+ ? `particles/entity ${entry.entity}`
610
+ : name.getValue();
611
+
612
+ entry.emitter = emitter;
613
+
614
+ apply_particle_effect(emitter, effect);
615
+
616
+ // registered at the entity's row by the next frame — see #register
617
+ this.#by_emitter.set(emitter, entry);
618
+ } else if (apply_particle_effect(entry.emitter, effect)) {
619
+ // The emitter's own change counter, which is what EmitterRegistry restages a row from.
620
+ entry.emitter.needsUpdate = true;
621
+ }
622
+ }
623
+
624
+ /**
625
+ * The entity moved: its row is rewritten from its transform by the context that holds it.
626
+ *
627
+ * @param {ParticleEmitterEntry} entry
628
+ */
629
+ #place(entry) {
630
+ const context = entry.context;
631
+
632
+ if (context === null || context !== this.#graphics.scene_context(this.#scene)) {
633
+ return;
634
+ }
635
+
636
+ context.rows.mark_world(entry.entity);
637
+ }
638
+
639
+ /**
640
+ * Retire an entity's emitter and let go of its row, leaving the entry linked.
641
+ *
642
+ * @param {ParticleEmitterEntry} entry
643
+ */
644
+ #detach_emitter(entry) {
645
+ const emitter = entry.emitter;
646
+
647
+ if (emitter === null) {
648
+ return;
649
+ }
650
+
651
+ const context = entry.context;
652
+
653
+ // A context that has been replaced took its rows and its particle system with it; only the
654
+ // one that is running is handed anything back.
655
+ if (context !== null && context === this.#graphics.scene_context(this.#scene)) {
656
+ const particles = this.#particles_of(context);
657
+
658
+ if (particles !== null && particles.registry.context(emitter) !== undefined) {
659
+ particles.remove_emitter(emitter);
660
+ }
661
+
662
+ context.rows.release(entry.entity);
663
+ }
664
+
665
+ entry.context = null;
666
+
667
+ this.#by_emitter.delete(emitter);
668
+
669
+ entry.emitter = null;
670
+ }
671
+
672
+ /**
673
+ * @param {number} entity
674
+ */
675
+ #drop(entity) {
676
+ const entry = this.#entries.get(entity);
677
+
678
+ if (entry === undefined) {
679
+ return;
680
+ }
681
+
682
+ if (entry.on_transform_changed !== null) {
683
+ this.entityManager.dataset.removeEntityEventListener(
684
+ entity, TRANSFORM64_EVENT_CHANGE, entry.on_transform_changed
685
+ );
686
+
687
+ entry.on_transform_changed = null;
688
+ }
689
+
690
+ this.#detach_emitter(entry);
691
+ entry.texture?.release();
692
+
693
+ entry.url = null;
694
+ entry.texture = null;
695
+
696
+ this.#entries.delete(entity);
697
+ }
698
+
699
+ /** @returns {void} */
700
+ #create_defaults() {
701
+ const white = Sampler2D.uint8(4, 1, 1);
702
+
703
+ white.data.fill(255);
704
+
705
+ this.#white = this.atlas.add(white);
706
+ this.#transparent = this.atlas.add(Sampler2D.uint8(4, 1, 1));
707
+ }
708
+
709
+ /**
710
+ * @param {import("../../shade/renderer/GPUDeviceContext.js").GPUDeviceContext} graphics
711
+ * @returns {void}
712
+ */
713
+ #upload(graphics) {
714
+ const device = graphics.device;
715
+ const source = this.atlas.sampler;
716
+
717
+ if (this.#device !== device || this.#texture === null) {
718
+ this.#texture?.destroy();
719
+
720
+ this.#device = device;
721
+ this.#texture = new GPUTextureContext(device);
722
+
723
+ const descriptor = this.#texture.descriptor;
724
+
725
+ descriptor.label = "particles/sprite atlas";
726
+ descriptor.format = "rgba8unorm";
727
+ descriptor.usage = GPUTextureUsage.TEXTURE_BINDING
728
+ | GPUTextureUsage.COPY_DST
729
+ | GPUTextureUsage.RENDER_ATTACHMENT;
730
+
731
+ this.#uploaded_version = -1;
732
+ }
733
+
734
+ const texture = this.#texture;
735
+
736
+ if (texture.width !== source.width || texture.height !== source.height) {
737
+ texture.descriptor.mipLevelCount = 1 + Math.floor(Math.log2(Math.max(source.width, source.height)));
738
+ texture.resize(source.width, source.height);
739
+
740
+ this.#uploaded_version = -1;
741
+ }
742
+
743
+ if (this.#uploaded_version !== source.version) {
744
+ device.queue.writeTexture({ texture: texture.gpu_texture }, source.data,
745
+ { bytesPerRow: source.width * 4 }, [source.width, source.height]);
746
+
747
+ if (texture.descriptor.mipLevelCount > 1) {
748
+ graphics.textures.mipmaps.generateMipmap(
749
+ texture.gpu_texture,
750
+ texture.descriptor,
751
+ TextureFilterType.Linear
752
+ );
753
+ }
754
+
755
+ texture.incrementVersion();
756
+
757
+ this.#uploaded_version = source.version;
758
+ }
759
+
760
+ this.#view = texture.obtainView();
761
+ }
762
+
763
+ /** @returns {void} */
764
+ #detach() {
765
+ if (this.#particles !== null && this.#particles.atlas === this.#view) {
766
+ this.#particles.atlas = this.#previous_atlas;
767
+
768
+ for (const emitter of this.#particles.emitters) {
769
+ this.#particles.registry.set_atlas_region(emitter, [0, 0, 1, 1]);
770
+ }
771
+ }
772
+
773
+ this.#particles = null;
774
+ this.#previous_atlas = null;
775
+ }
776
+ }
777
+
778
+ /**
779
+ * Copy a component onto the emitter built for it, reporting whether anything actually moved.
780
+ *
781
+ * Comparing while assigning rather than in a pass of its own is what makes change detection cost
782
+ * nothing extra: the fields have to be read to be written, and the only addition is an `||` per
783
+ * field. What it answers is whether the emitter's GPU row has to be restaged, and restaging one that
784
+ * has not changed is an upload per emitter per frame.
785
+ *
786
+ * `spawn_rate` comes from {@link ParticleEffect#effective_spawn_rate}, so an effect switched off
787
+ * with `emitting = false` reaches the GPU as a rate of zero and keeps its authored rate to come back
788
+ * to. Layout and program are shared by reference, never copied — two entities of one effect belong
789
+ * on one program id in the heap.
790
+ *
791
+ * Every field is compared by name, and that is load-bearing rather than style. Through a shared
792
+ * `assign(target, field, value)` the one `target[field]` access saw all thirteen names, went
793
+ * megamorphic, and read numbers back boxed, while the call boxed the value it was handed: 228 B and
794
+ * ~120 ns per emitter per call, against nothing and ~9 ns written out like this
795
+ * (`GPUParticleEmitterSystem.allocation.spec.js`). This runs for every live emitter twice a frame.
796
+ *
797
+ * @param {ParticleEmitter} emitter
798
+ * @param {ParticleEffect} effect
799
+ * @returns {boolean} whether the emitter differs from what it held before
800
+ */
801
+ export function apply_particle_effect(emitter, effect) {
802
+ let changed = false;
803
+
804
+ if (emitter.layout !== effect.layout) {
805
+ emitter.layout = effect.layout;
806
+ changed = true;
807
+ }
808
+
809
+ if (emitter.program !== effect.program) {
810
+ emitter.program = effect.program;
811
+ changed = true;
812
+ }
813
+
814
+ if (emitter.texture !== effect.texture) {
815
+ emitter.texture = effect.texture;
816
+ changed = true;
817
+ }
818
+
819
+ if (emitter.seed !== effect.seed) {
820
+ emitter.seed = effect.seed;
821
+ changed = true;
822
+ }
823
+
824
+ const spawn_rate = effect.effective_spawn_rate;
825
+
826
+ if (emitter.spawn_rate !== spawn_rate) {
827
+ emitter.spawn_rate = spawn_rate;
828
+ changed = true;
829
+ }
830
+
831
+ if (emitter.prewarm !== effect.prewarm) {
832
+ emitter.prewarm = effect.prewarm;
833
+ changed = true;
834
+ }
835
+
836
+ if (emitter.flags !== effect.flags) {
837
+ emitter.flags = effect.flags;
838
+ changed = true;
839
+ }
840
+
841
+ if (emitter.render_position !== effect.render_position) {
842
+ emitter.render_position = effect.render_position;
843
+ changed = true;
844
+ }
845
+
846
+ if (emitter.render_size !== effect.render_size) {
847
+ emitter.render_size = effect.render_size;
848
+ changed = true;
849
+ }
850
+
851
+ if (emitter.render_color !== effect.render_color) {
852
+ emitter.render_color = effect.render_color;
853
+ changed = true;
854
+ }
855
+
856
+ if (emitter.render_orientation !== effect.render_orientation) {
857
+ emitter.render_orientation = effect.render_orientation;
858
+ changed = true;
859
+ }
860
+
861
+ if (emitter.render_frame !== effect.render_frame) {
862
+ emitter.render_frame = effect.render_frame;
863
+ changed = true;
864
+ }
865
+
866
+ if (emitter.render_velocity !== effect.render_velocity) {
867
+ emitter.render_velocity = effect.render_velocity;
868
+ changed = true;
869
+ }
870
+
871
+ const flipbook = emitter.flipbook;
872
+
873
+ if (flipbook[0] !== effect.flipbook[0] || flipbook[1] !== effect.flipbook[1]) {
874
+ flipbook.set(effect.flipbook);
875
+
876
+ changed = true;
877
+ }
878
+
879
+ return changed;
880
+ }
881
+
882
+ class GPUParticleAtlasExtension extends RenderExtension {
883
+ /** @type {string} */
884
+ name = "gpu particle atlas";
885
+ /** @type {FramePhase} */
886
+ phase = FramePhase.FrameStart;
887
+ /** @type {GPUParticleEmitterSystem} */
888
+ #system;
889
+
890
+ /** @param {GPUParticleEmitterSystem} system */
891
+ constructor(system) {
892
+ super();
893
+
894
+ this.#system = system;
895
+ }
896
+
897
+ /**
898
+ * @param {import("../../shade/renderer/extension/FrameContext.js").FrameContext} frame
899
+ * @returns {void}
900
+ */
901
+ record(frame) {
902
+ this.#system.prepare(frame);
903
+ }
904
+ }