@aura3d/rendering 2.0.3 → 3.0.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 (292) hide show
  1. package/dist/AtmosphereWetness.d.ts +61 -0
  2. package/dist/AtmosphereWetness.d.ts.map +1 -0
  3. package/dist/AtmosphereWetness.js +99 -0
  4. package/dist/AtmosphereWetness.js.map +1 -0
  5. package/dist/ClusteredForwardLighting.d.ts +25 -1
  6. package/dist/ClusteredForwardLighting.d.ts.map +1 -1
  7. package/dist/ClusteredForwardLighting.js +80 -3
  8. package/dist/ClusteredForwardLighting.js.map +1 -1
  9. package/dist/DayNightSky.d.ts +63 -0
  10. package/dist/DayNightSky.d.ts.map +1 -0
  11. package/dist/DayNightSky.js +156 -0
  12. package/dist/DayNightSky.js.map +1 -0
  13. package/dist/EnvironmentPresetPack.d.ts +30 -0
  14. package/dist/EnvironmentPresetPack.d.ts.map +1 -0
  15. package/dist/EnvironmentPresetPack.js +49 -0
  16. package/dist/EnvironmentPresetPack.js.map +1 -0
  17. package/dist/ForwardPass.d.ts +67 -0
  18. package/dist/ForwardPass.d.ts.map +1 -1
  19. package/dist/ForwardPass.js +196 -2
  20. package/dist/ForwardPass.js.map +1 -1
  21. package/dist/InstancedPBRMaterial.d.ts +1 -0
  22. package/dist/InstancedPBRMaterial.d.ts.map +1 -1
  23. package/dist/InstancedPBRMaterial.js +3 -0
  24. package/dist/InstancedPBRMaterial.js.map +1 -1
  25. package/dist/InstancingDiagnostics.d.ts +69 -0
  26. package/dist/InstancingDiagnostics.d.ts.map +1 -0
  27. package/dist/InstancingDiagnostics.js +98 -0
  28. package/dist/InstancingDiagnostics.js.map +1 -0
  29. package/dist/LightingRig.d.ts +19 -1
  30. package/dist/LightingRig.d.ts.map +1 -1
  31. package/dist/LightingRig.js +61 -1
  32. package/dist/LightingRig.js.map +1 -1
  33. package/dist/MeshConsolidation.d.ts +12 -0
  34. package/dist/MeshConsolidation.d.ts.map +1 -1
  35. package/dist/MeshConsolidation.js +28 -0
  36. package/dist/MeshConsolidation.js.map +1 -1
  37. package/dist/MorphTargetPlan.d.ts +29 -2
  38. package/dist/MorphTargetPlan.d.ts.map +1 -1
  39. package/dist/MorphTargetPlan.js +23 -4
  40. package/dist/MorphTargetPlan.js.map +1 -1
  41. package/dist/NormalMappedPBRMaterial.d.ts.map +1 -1
  42. package/dist/NormalMappedPBRMaterial.js +22 -2
  43. package/dist/NormalMappedPBRMaterial.js.map +1 -1
  44. package/dist/OceanSurface.d.ts +75 -0
  45. package/dist/OceanSurface.d.ts.map +1 -1
  46. package/dist/OceanSurface.js +211 -0
  47. package/dist/OceanSurface.js.map +1 -1
  48. package/dist/PBRMaterial.d.ts +1 -0
  49. package/dist/PBRMaterial.d.ts.map +1 -1
  50. package/dist/PBRMaterial.js +23 -2
  51. package/dist/PBRMaterial.js.map +1 -1
  52. package/dist/PbrReference.d.ts +48 -0
  53. package/dist/PbrReference.d.ts.map +1 -1
  54. package/dist/PbrReference.js +126 -0
  55. package/dist/PbrReference.js.map +1 -1
  56. package/dist/PlanarReflection.d.ts +221 -0
  57. package/dist/PlanarReflection.d.ts.map +1 -0
  58. package/dist/PlanarReflection.js +541 -0
  59. package/dist/PlanarReflection.js.map +1 -0
  60. package/dist/PostProcessPass.d.ts +13 -0
  61. package/dist/PostProcessPass.d.ts.map +1 -1
  62. package/dist/PostProcessPass.js +15 -4
  63. package/dist/PostProcessPass.js.map +1 -1
  64. package/dist/ReflectionSurfaces.d.ts +14 -0
  65. package/dist/ReflectionSurfaces.d.ts.map +1 -1
  66. package/dist/ReflectionSurfaces.js +93 -0
  67. package/dist/ReflectionSurfaces.js.map +1 -1
  68. package/dist/RenderDevice.d.ts +87 -0
  69. package/dist/RenderDevice.d.ts.map +1 -1
  70. package/dist/RenderDevice.js +85 -0
  71. package/dist/RenderDevice.js.map +1 -1
  72. package/dist/Renderer.js +8 -5
  73. package/dist/Renderer.js.map +1 -1
  74. package/dist/RendererPostprocessPlan.d.ts +48 -0
  75. package/dist/RendererPostprocessPlan.d.ts.map +1 -1
  76. package/dist/RendererPostprocessPlan.js +101 -11
  77. package/dist/RendererPostprocessPlan.js.map +1 -1
  78. package/dist/RendererVisualPipelineReport.d.ts.map +1 -1
  79. package/dist/RendererVisualPipelineReport.js +3 -1
  80. package/dist/RendererVisualPipelineReport.js.map +1 -1
  81. package/dist/Sampler.d.ts +29 -0
  82. package/dist/Sampler.d.ts.map +1 -1
  83. package/dist/Sampler.js +47 -0
  84. package/dist/Sampler.js.map +1 -1
  85. package/dist/SceneOptimization.d.ts +1 -0
  86. package/dist/SceneOptimization.d.ts.map +1 -1
  87. package/dist/SceneOptimization.js +4 -2
  88. package/dist/SceneOptimization.js.map +1 -1
  89. package/dist/SdfText.d.ts +250 -0
  90. package/dist/SdfText.d.ts.map +1 -0
  91. package/dist/SdfText.js +551 -0
  92. package/dist/SdfText.js.map +1 -0
  93. package/dist/ShaderChunks.d.ts.map +1 -1
  94. package/dist/ShaderChunks.js +30 -12
  95. package/dist/ShaderChunks.js.map +1 -1
  96. package/dist/ShaderLibrary.d.ts.map +1 -1
  97. package/dist/ShaderLibrary.js +478 -34
  98. package/dist/ShaderLibrary.js.map +1 -1
  99. package/dist/ShaderLibraryCore.d.ts.map +1 -1
  100. package/dist/ShaderLibraryCore.js +70 -12
  101. package/dist/ShaderLibraryCore.js.map +1 -1
  102. package/dist/ShadowMap.d.ts +24 -0
  103. package/dist/ShadowMap.d.ts.map +1 -1
  104. package/dist/ShadowMap.js +43 -0
  105. package/dist/ShadowMap.js.map +1 -1
  106. package/dist/SkinnedLitMaterial.d.ts.map +1 -1
  107. package/dist/SkinnedLitMaterial.js +2 -0
  108. package/dist/SkinnedLitMaterial.js.map +1 -1
  109. package/dist/SkinningBounds.d.ts +10 -0
  110. package/dist/SkinningBounds.d.ts.map +1 -1
  111. package/dist/SkinningBounds.js +27 -0
  112. package/dist/SkinningBounds.js.map +1 -1
  113. package/dist/SpriteFlipbook.d.ts +60 -0
  114. package/dist/SpriteFlipbook.d.ts.map +1 -0
  115. package/dist/SpriteFlipbook.js +128 -0
  116. package/dist/SpriteFlipbook.js.map +1 -0
  117. package/dist/TerrainTiles.d.ts +120 -0
  118. package/dist/TerrainTiles.d.ts.map +1 -0
  119. package/dist/TerrainTiles.js +221 -0
  120. package/dist/TerrainTiles.js.map +1 -0
  121. package/dist/TexturedPBRMaterial.d.ts +1 -0
  122. package/dist/TexturedPBRMaterial.d.ts.map +1 -1
  123. package/dist/TexturedPBRMaterial.js +23 -2
  124. package/dist/TexturedPBRMaterial.js.map +1 -1
  125. package/dist/TexturedUnlitMaterial.d.ts.map +1 -1
  126. package/dist/TexturedUnlitMaterial.js +20 -2
  127. package/dist/TexturedUnlitMaterial.js.map +1 -1
  128. package/dist/UnlitMaterial.d.ts.map +1 -1
  129. package/dist/UnlitMaterial.js +20 -2
  130. package/dist/UnlitMaterial.js.map +1 -1
  131. package/dist/VertexFormat.d.ts +6 -0
  132. package/dist/VertexFormat.d.ts.map +1 -1
  133. package/dist/VertexFormat.js +12 -0
  134. package/dist/VertexFormat.js.map +1 -1
  135. package/dist/VolumetricFog.d.ts +55 -0
  136. package/dist/VolumetricFog.d.ts.map +1 -0
  137. package/dist/VolumetricFog.js +91 -0
  138. package/dist/VolumetricFog.js.map +1 -0
  139. package/dist/WaterSurface.d.ts +77 -0
  140. package/dist/WaterSurface.d.ts.map +1 -0
  141. package/dist/WaterSurface.js +133 -0
  142. package/dist/WaterSurface.js.map +1 -0
  143. package/dist/WebGL2Device.d.ts +58 -0
  144. package/dist/WebGL2Device.d.ts.map +1 -1
  145. package/dist/WebGL2Device.js +536 -17
  146. package/dist/WebGL2Device.js.map +1 -1
  147. package/dist/WebGPUDevice.d.ts +50 -0
  148. package/dist/WebGPUDevice.d.ts.map +1 -1
  149. package/dist/WebGPUDevice.js +504 -10
  150. package/dist/WebGPUDevice.js.map +1 -1
  151. package/dist/WebGPUSkinningLimits.d.ts +40 -0
  152. package/dist/WebGPUSkinningLimits.d.ts.map +1 -1
  153. package/dist/WebGPUSkinningLimits.js +32 -0
  154. package/dist/WebGPUSkinningLimits.js.map +1 -1
  155. package/dist/effects/CollisionModule.d.ts +9 -0
  156. package/dist/effects/CollisionModule.d.ts.map +1 -1
  157. package/dist/effects/CollisionModule.js +10 -0
  158. package/dist/effects/CollisionModule.js.map +1 -1
  159. package/dist/effects/ColorModule.d.ts +7 -0
  160. package/dist/effects/ColorModule.d.ts.map +1 -1
  161. package/dist/effects/ColorModule.js +23 -0
  162. package/dist/effects/ColorModule.js.map +1 -1
  163. package/dist/effects/ForceModule.d.ts +35 -0
  164. package/dist/effects/ForceModule.d.ts.map +1 -1
  165. package/dist/effects/ForceModule.js +49 -0
  166. package/dist/effects/ForceModule.js.map +1 -1
  167. package/dist/effects/GPUParticleBackend.d.ts +186 -0
  168. package/dist/effects/GPUParticleBackend.d.ts.map +1 -1
  169. package/dist/effects/GPUParticleBackend.js +692 -1
  170. package/dist/effects/GPUParticleBackend.js.map +1 -1
  171. package/dist/effects/HeightfieldModule.d.ts +58 -0
  172. package/dist/effects/HeightfieldModule.d.ts.map +1 -0
  173. package/dist/effects/HeightfieldModule.js +129 -0
  174. package/dist/effects/HeightfieldModule.js.map +1 -0
  175. package/dist/effects/LightingModule.d.ts +34 -0
  176. package/dist/effects/LightingModule.d.ts.map +1 -0
  177. package/dist/effects/LightingModule.js +54 -0
  178. package/dist/effects/LightingModule.js.map +1 -0
  179. package/dist/effects/ParticleDiagnostics.d.ts +8 -0
  180. package/dist/effects/ParticleDiagnostics.d.ts.map +1 -1
  181. package/dist/effects/ParticleDiagnostics.js +3 -0
  182. package/dist/effects/ParticleDiagnostics.js.map +1 -1
  183. package/dist/effects/ParticleModule.d.ts +8 -0
  184. package/dist/effects/ParticleModule.d.ts.map +1 -1
  185. package/dist/effects/ParticleModule.js.map +1 -1
  186. package/dist/effects/ParticleRenderer.d.ts +20 -0
  187. package/dist/effects/ParticleRenderer.d.ts.map +1 -1
  188. package/dist/effects/ParticleRenderer.js +57 -7
  189. package/dist/effects/ParticleRenderer.js.map +1 -1
  190. package/dist/effects/ParticleSystem.d.ts +45 -1
  191. package/dist/effects/ParticleSystem.d.ts.map +1 -1
  192. package/dist/effects/ParticleSystem.js +373 -4
  193. package/dist/effects/ParticleSystem.js.map +1 -1
  194. package/dist/effects/SizeModule.d.ts +4 -0
  195. package/dist/effects/SizeModule.d.ts.map +1 -1
  196. package/dist/effects/SizeModule.js +16 -0
  197. package/dist/effects/SizeModule.js.map +1 -1
  198. package/dist/effects/SubEmitterModule.d.ts +61 -0
  199. package/dist/effects/SubEmitterModule.d.ts.map +1 -0
  200. package/dist/effects/SubEmitterModule.js +119 -0
  201. package/dist/effects/SubEmitterModule.js.map +1 -0
  202. package/dist/effects/TrailModule.d.ts +38 -0
  203. package/dist/effects/TrailModule.d.ts.map +1 -1
  204. package/dist/effects/TrailModule.js +95 -2
  205. package/dist/effects/TrailModule.js.map +1 -1
  206. package/dist/effects/TurbulenceModule.d.ts +43 -0
  207. package/dist/effects/TurbulenceModule.d.ts.map +1 -0
  208. package/dist/effects/TurbulenceModule.js +130 -0
  209. package/dist/effects/TurbulenceModule.js.map +1 -0
  210. package/dist/effects/index.d.ts +4 -0
  211. package/dist/effects/index.d.ts.map +1 -1
  212. package/dist/effects/index.js +4 -0
  213. package/dist/effects/index.js.map +1 -1
  214. package/dist/index.d.ts +57 -19
  215. package/dist/index.d.ts.map +1 -1
  216. package/dist/index.js +31 -10
  217. package/dist/index.js.map +1 -1
  218. package/dist/materials/MaterialExtensions.js +3 -3
  219. package/dist/materials/MaterialExtensions.js.map +1 -1
  220. package/dist/postprocess/NativeBloomPyramid.d.ts +28 -0
  221. package/dist/postprocess/NativeBloomPyramid.d.ts.map +1 -0
  222. package/dist/postprocess/NativeBloomPyramid.js +54 -0
  223. package/dist/postprocess/NativeBloomPyramid.js.map +1 -0
  224. package/dist/postprocess/NativeLdrEffectLuts.d.ts +10 -1
  225. package/dist/postprocess/NativeLdrEffectLuts.d.ts.map +1 -1
  226. package/dist/postprocess/NativeLdrEffectLuts.js +19 -2
  227. package/dist/postprocess/NativeLdrEffectLuts.js.map +1 -1
  228. package/dist/postprocess/index.d.ts +2 -0
  229. package/dist/postprocess/index.d.ts.map +1 -1
  230. package/dist/postprocess/index.js +1 -0
  231. package/dist/postprocess/index.js.map +1 -1
  232. package/dist/production-runtime/PBRHDRPipeline.d.ts +22 -0
  233. package/dist/production-runtime/PBRHDRPipeline.d.ts.map +1 -1
  234. package/dist/production-runtime/PBRHDRPipeline.js +125 -0
  235. package/dist/production-runtime/PBRHDRPipeline.js.map +1 -1
  236. package/dist/production-runtime/ProductionWebGPURenderer.d.ts +52 -0
  237. package/dist/production-runtime/ProductionWebGPURenderer.d.ts.map +1 -1
  238. package/dist/production-runtime/ProductionWebGPURenderer.js +101 -0
  239. package/dist/production-runtime/ProductionWebGPURenderer.js.map +1 -1
  240. package/dist/production-runtime/index.d.ts +5 -4
  241. package/dist/production-runtime/index.d.ts.map +1 -1
  242. package/dist/production-runtime/index.js +3 -2
  243. package/dist/production-runtime/index.js.map +1 -1
  244. package/dist/production-runtime/passes/DepthPrepass.d.ts +26 -1
  245. package/dist/production-runtime/passes/DepthPrepass.d.ts.map +1 -1
  246. package/dist/production-runtime/passes/DepthPrepass.js +49 -2
  247. package/dist/production-runtime/passes/DepthPrepass.js.map +1 -1
  248. package/dist/production-runtime/passes/FramegraphTopology.d.ts +13 -0
  249. package/dist/production-runtime/passes/FramegraphTopology.d.ts.map +1 -0
  250. package/dist/production-runtime/passes/FramegraphTopology.js +59 -0
  251. package/dist/production-runtime/passes/FramegraphTopology.js.map +1 -0
  252. package/dist/production-runtime/passes/OpaquePass.d.ts +24 -1
  253. package/dist/production-runtime/passes/OpaquePass.d.ts.map +1 -1
  254. package/dist/production-runtime/passes/OpaquePass.js +41 -2
  255. package/dist/production-runtime/passes/OpaquePass.js.map +1 -1
  256. package/dist/production-runtime/passes/ShadowPass.d.ts +24 -1
  257. package/dist/production-runtime/passes/ShadowPass.d.ts.map +1 -1
  258. package/dist/production-runtime/passes/ShadowPass.js +43 -2
  259. package/dist/production-runtime/passes/ShadowPass.js.map +1 -1
  260. package/dist/production-runtime/passes/SkyboxPass.d.ts +21 -1
  261. package/dist/production-runtime/passes/SkyboxPass.d.ts.map +1 -1
  262. package/dist/production-runtime/passes/SkyboxPass.js +39 -2
  263. package/dist/production-runtime/passes/SkyboxPass.js.map +1 -1
  264. package/dist/production-runtime/passes/ToneMappingPass.d.ts +29 -1
  265. package/dist/production-runtime/passes/ToneMappingPass.d.ts.map +1 -1
  266. package/dist/production-runtime/passes/ToneMappingPass.js +53 -2
  267. package/dist/production-runtime/passes/ToneMappingPass.js.map +1 -1
  268. package/dist/production-runtime/passes/TransparentPass.d.ts +24 -1
  269. package/dist/production-runtime/passes/TransparentPass.d.ts.map +1 -1
  270. package/dist/production-runtime/passes/TransparentPass.js +42 -2
  271. package/dist/production-runtime/passes/TransparentPass.js.map +1 -1
  272. package/dist/shadows/CascadeHysteresis.d.ts +67 -0
  273. package/dist/shadows/CascadeHysteresis.d.ts.map +1 -0
  274. package/dist/shadows/CascadeHysteresis.js +104 -0
  275. package/dist/shadows/CascadeHysteresis.js.map +1 -0
  276. package/dist/shadows/ContactShadows.d.ts +52 -0
  277. package/dist/shadows/ContactShadows.d.ts.map +1 -1
  278. package/dist/shadows/ContactShadows.js +122 -0
  279. package/dist/shadows/ContactShadows.js.map +1 -1
  280. package/dist/shadows/SpotShadowMaps.d.ts +77 -0
  281. package/dist/shadows/SpotShadowMaps.d.ts.map +1 -0
  282. package/dist/shadows/SpotShadowMaps.js +106 -0
  283. package/dist/shadows/SpotShadowMaps.js.map +1 -0
  284. package/dist/webgpu/WebGPUPostProcess.d.ts +2 -0
  285. package/dist/webgpu/WebGPUPostProcess.d.ts.map +1 -1
  286. package/dist/webgpu/WebGPUPostProcess.js +1 -0
  287. package/dist/webgpu/WebGPUPostProcess.js.map +1 -1
  288. package/dist/webgpu/WebGPUPostShaders.d.ts +78 -0
  289. package/dist/webgpu/WebGPUPostShaders.d.ts.map +1 -0
  290. package/dist/webgpu/WebGPUPostShaders.js +262 -0
  291. package/dist/webgpu/WebGPUPostShaders.js.map +1 -0
  292. package/package.json +3 -3
@@ -1,3 +1,24 @@
1
+ /**
2
+ * Soft-particle depth fade: 0 when the particle is behind the scene surface,
3
+ * ramping to 1 one fadeDistance in front of it. Pure and shared so the probe
4
+ * can show the exact on/off delta the renderer applies.
5
+ */
6
+ export function computeSoftParticleFade(sceneDepth, particleDepth, fadeDistance) {
7
+ if (!Number.isFinite(sceneDepth) || !Number.isFinite(particleDepth) || !Number.isFinite(fadeDistance)) {
8
+ throw new RangeError("Soft-particle fade inputs must be finite numbers.");
9
+ }
10
+ if (fadeDistance <= 0) {
11
+ throw new RangeError("Soft-particle fadeDistance must be positive.");
12
+ }
13
+ const t = (sceneDepth - particleDepth) / fadeDistance;
14
+ return Math.min(1, Math.max(0, t));
15
+ }
16
+ /** WGSL snippet consumers can drop into their own particle render shaders. */
17
+ export const SOFT_PARTICLE_WGSL = `
18
+ fn auraSoftParticleFade(sceneDepth: f32, particleDepth: f32, fadeDistance: f32) -> f32 {
19
+ return clamp((sceneDepth - particleDepth) / max(fadeDistance, 1e-6), 0.0, 1.0);
20
+ }
21
+ `;
1
22
  export class ParticleRenderer {
2
23
  lastBatch = {
3
24
  sprites: [],
@@ -6,15 +27,36 @@ export class ParticleRenderer {
6
27
  bounds: null,
7
28
  };
8
29
  buildBatch(particles, options = {}) {
30
+ const soft = options.softParticles;
31
+ if (soft?.enabled) {
32
+ validateSoftParticles(soft);
33
+ }
9
34
  const sprites = particles
10
35
  .filter((particle) => particle.alive)
11
- .map((particle) => ({
12
- id: particle.id,
13
- position: { ...particle.position },
14
- color: { ...particle.color },
15
- size: particle.size,
16
- rotation: particle.rotation,
17
- }));
36
+ .map((particle) => {
37
+ let fade = 1;
38
+ let alpha = particle.color.a;
39
+ if (soft?.enabled) {
40
+ const sceneDepth = soft.sceneDepthAt(particle.position);
41
+ const particleDepth = soft.particleDepthAt(particle.position);
42
+ if (!Number.isFinite(particleDepth)) {
43
+ throw new RangeError("ParticleRenderer softParticles particleDepthAt must return a finite number.");
44
+ }
45
+ // Non-finite scene depth = the view ray hits no occluder: no attenuation.
46
+ fade = Number.isFinite(sceneDepth)
47
+ ? computeSoftParticleFade(sceneDepth, particleDepth, soft.fadeDistance)
48
+ : 1;
49
+ alpha = particle.color.a * fade;
50
+ }
51
+ return {
52
+ id: particle.id,
53
+ position: { ...particle.position },
54
+ color: { ...particle.color, a: alpha },
55
+ size: particle.size,
56
+ rotation: particle.rotation,
57
+ fade,
58
+ };
59
+ });
18
60
  sortSprites(sprites, options);
19
61
  this.lastBatch = {
20
62
  sprites,
@@ -81,6 +123,14 @@ function computeBounds(sprites) {
81
123
  }
82
124
  return { min, max };
83
125
  }
126
+ function validateSoftParticles(soft) {
127
+ if (!Number.isFinite(soft.fadeDistance) || soft.fadeDistance <= 0) {
128
+ throw new RangeError("ParticleRenderer softParticles fadeDistance must be a finite positive number.");
129
+ }
130
+ if (typeof soft.sceneDepthAt !== "function" || typeof soft.particleDepthAt !== "function") {
131
+ throw new RangeError("ParticleRenderer softParticles requires sceneDepthAt and particleDepthAt functions.");
132
+ }
133
+ }
84
134
  function distanceSquared(a, b) {
85
135
  const x = a.x - b.x;
86
136
  const y = a.y - b.y;
@@ -1 +1 @@
1
- {"version":3,"file":"ParticleRenderer.js","sourceRoot":"","sources":["../../../../../packages/rendering/src/effects/ParticleRenderer.ts"],"names":[],"mappings":"AAkCA,MAAM,OAAO,gBAAgB;IACnB,SAAS,GAAwB;QACvC,OAAO,EAAE,EAAE;QACX,SAAS,EAAE,CAAC;QACZ,aAAa,EAAE,CAAC;QAChB,MAAM,EAAE,IAAI;KACb,CAAC;IAEF,UAAU,CAAC,SAA8B,EAAE,UAAiC,EAAE;QAC5E,MAAM,OAAO,GAAG,SAAS;aACtB,MAAM,CAAC,CAAC,QAAQ,EAAE,EAAE,CAAC,QAAQ,CAAC,KAAK,CAAC;aACpC,GAAG,CAAC,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC;YAClB,EAAE,EAAE,QAAQ,CAAC,EAAE;YACf,QAAQ,EAAE,EAAE,GAAG,QAAQ,CAAC,QAAQ,EAAE;YAClC,KAAK,EAAE,EAAE,GAAG,QAAQ,CAAC,KAAK,EAAE;YAC5B,IAAI,EAAE,QAAQ,CAAC,IAAI;YACnB,QAAQ,EAAE,QAAQ,CAAC,QAAQ;SAC5B,CAAC,CAAC,CAAC;QACN,WAAW,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;QAE9B,IAAI,CAAC,SAAS,GAAG;YACf,OAAO;YACP,SAAS,EAAE,OAAO,CAAC,MAAM;YACzB,aAAa,EAAE,OAAO,CAAC,MAAM,GAAG,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,GAAG,YAAY,CAAC,iBAAiB;YAChF,MAAM,EAAE,aAAa,CAAC,OAAO,CAAC;SAC/B,CAAC;QAEF,OAAO,IAAI,CAAC,SAAS,CAAC;IACxB,CAAC;IAED,MAAM,CAAC,MAAsB,EAAE,MAA0B,EAAE,UAAiC,EAAE;QAC5F,MAAM,KAAK,GAAG,IAAI,CAAC,UAAU,CAAC,MAAM,CAAC,SAAS,EAAE,OAAO,CAAC,CAAC;QACzD,MAAM,CAAC,aAAa,CAAC,KAAK,CAAC,CAAC;QAC5B,MAAM,CAAC,kBAAkB,CAAC,KAAK,CAAC,aAAa,CAAC,CAAC;QAC/C,OAAO,KAAK,CAAC;IACf,CAAC;IAED,YAAY;QACV,OAAO;YACL,OAAO,EAAE,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;gBAC/C,GAAG,MAAM;gBACT,QAAQ,EAAE,EAAE,GAAG,MAAM,CAAC,QAAQ,EAAE;gBAChC,KAAK,EAAE,EAAE,GAAG,MAAM,CAAC,KAAK,EAAE;aAC3B,CAAC,CAAC;YACH,SAAS,EAAE,IAAI,CAAC,SAAS,CAAC,SAAS;YACnC,aAAa,EAAE,IAAI,CAAC,SAAS,CAAC,aAAa;YAC3C,MAAM,EAAE,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC,CAAC;gBAC9B,GAAG,EAAE,EAAE,GAAG,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,GAAG,EAAE;gBACrC,GAAG,EAAE,EAAE,GAAG,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,GAAG,EAAE;aACtC,CAAC,CAAC,CAAC,IAAI;SACT,CAAC;IACJ,CAAC;CACF;AAED,SAAS,WAAW,CAAC,OAAyB,EAAE,OAA8B;IAC5E,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,IAAI,MAAM,CAAC;IACpC,IAAI,IAAI,KAAK,MAAM;QAAE,OAAO;IAC5B,MAAM,MAAM,GAAG,OAAO,CAAC,cAAc,CAAC;IACtC,IAAI,CAAC,MAAM,IAAI,CAAC,cAAc,CAAC,MAAM,CAAC,EAAE,CAAC;QACvC,MAAM,IAAI,UAAU,CAAC,+EAA+E,CAAC,CAAC;IACxG,CAAC;IACD,MAAM,SAAS,GAAG,IAAI,KAAK,eAAe,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IACpD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE;QACpB,MAAM,aAAa,GAAG,eAAe,CAAC,CAAC,CAAC,QAAQ,EAAE,MAAM,CAAC,GAAG,eAAe,CAAC,CAAC,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;QAChG,IAAI,IAAI,CAAC,GAAG,CAAC,aAAa,CAAC,GAAG,IAAI;YAAE,OAAO,aAAa,GAAG,SAAS,CAAC;QACrE,OAAO,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,EAAE,CAAC;IACrB,CAAC,CAAC,CAAC;AACL,CAAC;AAED,SAAS,aAAa,CAAC,OAAkC;IACvD,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC;IACtC,MAAM,GAAG,GAAG,EAAE,CAAC,EAAE,MAAM,CAAC,iBAAiB,EAAE,CAAC,EAAE,MAAM,CAAC,iBAAiB,EAAE,CAAC,EAAE,MAAM,CAAC,iBAAiB,EAAE,CAAC;IACtG,MAAM,GAAG,GAAG,EAAE,CAAC,EAAE,MAAM,CAAC,iBAAiB,EAAE,CAAC,EAAE,MAAM,CAAC,iBAAiB,EAAE,CAAC,EAAE,MAAM,CAAC,iBAAiB,EAAE,CAAC;IACtG,KAAK,MAAM,MAAM,IAAI,OAAO,EAAE,CAAC;QAC7B,IAAI,CAAC,cAAc,CAAC,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,MAAM,CAAC,IAAI,GAAG,CAAC,EAAE,CAAC;YACzF,MAAM,IAAI,UAAU,CAAC,kFAAkF,CAAC,CAAC;QAC3G,CAAC;QACD,MAAM,MAAM,GAAG,MAAM,CAAC,IAAI,GAAG,GAAG,CAAC;QACjC,GAAG,CAAC,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,EAAE,MAAM,CAAC,QAAQ,CAAC,CAAC,GAAG,MAAM,CAAC,CAAC;QACpD,GAAG,CAAC,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,EAAE,MAAM,CAAC,QAAQ,CAAC,CAAC,GAAG,MAAM,CAAC,CAAC;QACpD,GAAG,CAAC,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,EAAE,MAAM,CAAC,QAAQ,CAAC,CAAC,GAAG,MAAM,CAAC,CAAC;QACpD,GAAG,CAAC,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,EAAE,MAAM,CAAC,QAAQ,CAAC,CAAC,GAAG,MAAM,CAAC,CAAC;QACpD,GAAG,CAAC,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,EAAE,MAAM,CAAC,QAAQ,CAAC,CAAC,GAAG,MAAM,CAAC,CAAC;QACpD,GAAG,CAAC,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,EAAE,MAAM,CAAC,QAAQ,CAAC,CAAC,GAAG,MAAM,CAAC,CAAC;IACtD,CAAC;IACD,OAAO,EAAE,GAAG,EAAE,GAAG,EAAE,CAAC;AACtB,CAAC;AAED,SAAS,eAAe,CAAC,CAAc,EAAE,CAAc;IACrD,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;IACpB,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;IACpB,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;IACpB,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;AAC/B,CAAC;AAED,SAAS,cAAc,CAAC,KAAkB;IACxC,OAAO,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;AAC1F,CAAC"}
1
+ {"version":3,"file":"ParticleRenderer.js","sourceRoot":"","sources":["../../../../../packages/rendering/src/effects/ParticleRenderer.ts"],"names":[],"mappings":"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"}
@@ -1,6 +1,6 @@
1
1
  import { type Particle } from "./Particle.js";
2
2
  import { ParticleEmitter } from "./ParticleEmitter.js";
3
- import type { GPUParticleBackend } from "./GPUParticleBackend.js";
3
+ import { type GPUParticleBackend, type GPUParticleEffectsInput } from "./GPUParticleBackend.js";
4
4
  import { type ParticleModule } from "./ParticleModule.js";
5
5
  export interface ParticleSystemStats {
6
6
  liveCount: number;
@@ -12,7 +12,25 @@ export interface ParticleSystemStats {
12
12
  uploadedBytes: number;
13
13
  gpuSpawns: number;
14
14
  gpuUpdates: number;
15
+ /** True once any spawn request (emitter or sub-emitter) exceeded capacity. Never a silent clamp. */
16
+ overBudget: boolean;
15
17
  }
18
+ export interface CollectedGPUParticleEffects {
19
+ readonly effects: GPUParticleEffectsInput;
20
+ /**
21
+ * Modules whose per-frame hooks the compute kernel implements. The system
22
+ * skips their CPU update/afterIntegrate hooks on the GPU path (zero CPU
23
+ * per-particle work); every other module keeps its CPU hooks on both paths.
24
+ */
25
+ readonly gpuCovered: ReadonlySet<ParticleModule>;
26
+ }
27
+ /**
28
+ * Aggregate GPU-capable modules into one compute effects descriptor.
29
+ * Returns undefined when no module has a GPU implementation (legacy path).
30
+ * Overflow policy: the first N instances of a capped effect (3 planes,
31
+ * 2 sub-emitters) run on GPU; extras keep their exact CPU hooks.
32
+ */
33
+ export declare function collectGPUParticleEffects(modules: readonly ParticleModule[]): CollectedGPUParticleEffects | undefined;
16
34
  export interface ParticleSystemOptions {
17
35
  maxParticles?: number;
18
36
  emitters?: readonly ParticleEmitter[];
@@ -30,6 +48,11 @@ export declare class ParticleSystem {
30
48
  private stats;
31
49
  private gpuBackend?;
32
50
  private preferGPU;
51
+ private lastSpawnRequests;
52
+ private lastIntegratedParticles;
53
+ private lastEffectsCovered;
54
+ /** GPU trail ring rows by particle id, fed back so history accumulates across calls. */
55
+ private trailRingById;
33
56
  constructor(options?: ParticleSystemOptions);
34
57
  addEmitter(emitter: ParticleEmitter): this;
35
58
  addModule(module: ParticleModule): this;
@@ -45,6 +68,27 @@ export declare class ParticleSystem {
45
68
  private initializeSpawnedOnGPU;
46
69
  private integrate;
47
70
  private integrateOnGPU;
71
+ /**
72
+ * Assemble the previous trail ring in live-particle order from per-id rows
73
+ * so compute history survives spawn/death reordering. Unknown ids start
74
+ * fresh (head = live position/age, older slots expired).
75
+ */
76
+ private assembleTrailHistory;
77
+ private storeTrailRings;
78
+ /** CPU path: turn queued sub-emitter requests into live child particles. */
79
+ private drainSubEmitterSpawns;
80
+ /**
81
+ * GPU path: covered sub-emitters report per-particle spawn requests from
82
+ * the compute kernel; uncovered ones queue CPU requests as usual. Children
83
+ * initialize through the GPU spawn pipeline when the backend offers one.
84
+ */
85
+ private drainSubEmitterSpawnsOnGPU;
86
+ /**
87
+ * Decode one covered sub-emitter slot from the packed kernel flags
88
+ * (low 16 bits = slot 0, high 16 bits = slot 1).
89
+ */
90
+ private requestsForSubEmitter;
91
+ private applyGPUTrailHistories;
48
92
  private compactDeadParticles;
49
93
  private createContext;
50
94
  }
@@ -1 +1 @@
1
- {"version":3,"file":"ParticleSystem.d.ts","sourceRoot":"","sources":["../../../../../packages/rendering/src/effects/ParticleSystem.ts"],"names":[],"mappings":"AAAA,OAAO,EAIL,KAAK,QAAQ,EACd,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AACvD,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AAClE,OAAO,EAAwB,KAAK,cAAc,EAA8B,MAAM,qBAAqB,CAAC;AAE5G,MAAM,WAAW,mBAAmB;IAClC,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,EAAE,MAAM,CAAC;IACrB,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC;IACrB,aAAa,EAAE,MAAM,CAAC;IACtB,aAAa,EAAE,MAAM,CAAC;IACtB,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,qBAAqB;IACpC,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,EAAE,SAAS,eAAe,EAAE,CAAC;IACtC,OAAO,CAAC,EAAE,SAAS,cAAc,EAAE,CAAC;IACpC,UAAU,CAAC,EAAE,kBAAkB,CAAC;IAChC,SAAS,CAAC,EAAE,OAAO,CAAC;CACrB;AAED,qBAAa,cAAc;IACzB,QAAQ,CAAC,SAAS,EAAE,QAAQ,EAAE,CAAM;IACpC,QAAQ,CAAC,QAAQ,EAAE,eAAe,EAAE,CAAM;IAC1C,QAAQ,CAAC,OAAO,EAAE,cAAc,EAAE,CAAM;IACxC,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAE9B,WAAW,SAAK;IAChB,MAAM,UAAS;IAEf,OAAO,CAAC,KAAK,CAAsB;IACnC,OAAO,CAAC,UAAU,CAAC,CAAqB;IACxC,OAAO,CAAC,SAAS,CAAU;gBAEf,OAAO,GAAE,qBAA0B;IAuB/C,UAAU,CAAC,OAAO,EAAE,eAAe,GAAG,IAAI;IAK1C,SAAS,CAAC,MAAM,EAAE,cAAc,GAAG,IAAI;IAKvC,aAAa,CAAC,OAAO,EAAE,kBAAkB,GAAG,SAAS,EAAE,SAAS,UAAwB,GAAG,IAAI;IAM/F,KAAK,IAAI,IAAI;IAMb,OAAO,IAAI,IAAI;IAOf,MAAM,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI;IAgBzB,WAAW,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,kBAAkB,GAAG,OAAO,CAAC,IAAI,CAAC;IAiB1E,UAAU,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,KAAK,GAAG,KAAK,CAAC;IAU3D,QAAQ,IAAI,mBAAmB;IAI/B,kBAAkB,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI;IASvC,OAAO,CAAC,KAAK;YAmBC,sBAAsB;IAmDpC,OAAO,CAAC,SAAS;YAuBH,cAAc;IA8D5B,OAAO,CAAC,oBAAoB;IAkB5B,OAAO,CAAC,aAAa;CAQtB"}
1
+ {"version":3,"file":"ParticleSystem.d.ts","sourceRoot":"","sources":["../../../../../packages/rendering/src/effects/ParticleSystem.ts"],"names":[],"mappings":"AAAA,OAAO,EAIL,KAAK,QAAQ,EACd,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AACvD,OAAO,EAEL,KAAK,kBAAkB,EACvB,KAAK,uBAAuB,EAG7B,MAAM,yBAAyB,CAAC;AACjC,OAAO,EAAwB,KAAK,cAAc,EAA8B,MAAM,qBAAqB,CAAC;AAW5G,MAAM,WAAW,mBAAmB;IAClC,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,EAAE,MAAM,CAAC;IACrB,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC;IACrB,aAAa,EAAE,MAAM,CAAC;IACtB,aAAa,EAAE,MAAM,CAAC;IACtB,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,oGAAoG;IACpG,UAAU,EAAE,OAAO,CAAC;CACrB;AAED,MAAM,WAAW,2BAA2B;IAC1C,QAAQ,CAAC,OAAO,EAAE,uBAAuB,CAAC;IAC1C;;;;OAIG;IACH,QAAQ,CAAC,UAAU,EAAE,WAAW,CAAC,cAAc,CAAC,CAAC;CAClD;AAED;;;;;GAKG;AACH,wBAAgB,yBAAyB,CACvC,OAAO,EAAE,SAAS,cAAc,EAAE,GACjC,2BAA2B,GAAG,SAAS,CA8GzC;AAED,MAAM,WAAW,qBAAqB;IACpC,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,EAAE,SAAS,eAAe,EAAE,CAAC;IACtC,OAAO,CAAC,EAAE,SAAS,cAAc,EAAE,CAAC;IACpC,UAAU,CAAC,EAAE,kBAAkB,CAAC;IAChC,SAAS,CAAC,EAAE,OAAO,CAAC;CACrB;AAED,qBAAa,cAAc;IACzB,QAAQ,CAAC,SAAS,EAAE,QAAQ,EAAE,CAAM;IACpC,QAAQ,CAAC,QAAQ,EAAE,eAAe,EAAE,CAAM;IAC1C,QAAQ,CAAC,OAAO,EAAE,cAAc,EAAE,CAAM;IACxC,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAE9B,WAAW,SAAK;IAChB,MAAM,UAAS;IAEf,OAAO,CAAC,KAAK,CAAsB;IACnC,OAAO,CAAC,UAAU,CAAC,CAAqB;IACxC,OAAO,CAAC,SAAS,CAAU;IAC3B,OAAO,CAAC,iBAAiB,CAAgB;IACzC,OAAO,CAAC,uBAAuB,CAAkB;IACjD,OAAO,CAAC,kBAAkB,CAA0C;IACpE,wFAAwF;IACxF,OAAO,CAAC,aAAa,CAAmC;gBAE5C,OAAO,GAAE,qBAA0B;IAwB/C,UAAU,CAAC,OAAO,EAAE,eAAe,GAAG,IAAI;IAK1C,SAAS,CAAC,MAAM,EAAE,cAAc,GAAG,IAAI;IAKvC,aAAa,CAAC,OAAO,EAAE,kBAAkB,GAAG,SAAS,EAAE,SAAS,UAAwB,GAAG,IAAI;IAM/F,KAAK,IAAI,IAAI;IAUb,OAAO,IAAI,IAAI;IAOf,MAAM,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI;IAiBzB,WAAW,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,kBAAkB,GAAG,OAAO,CAAC,IAAI,CAAC;IAkB1E,UAAU,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,KAAK,GAAG,KAAK,CAAC;IAU3D,QAAQ,IAAI,mBAAmB;IAI/B,kBAAkB,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI;IASvC,OAAO,CAAC,KAAK;YAuBC,sBAAsB;IAmDpC,OAAO,CAAC,SAAS;YAuBH,cAAc;IAmI5B;;;;OAIG;IACH,OAAO,CAAC,oBAAoB;IAyB5B,OAAO,CAAC,eAAe;IA0BvB,4EAA4E;IAC5E,OAAO,CAAC,qBAAqB;IAoB7B;;;;OAIG;YACW,0BAA0B;IAqCxC;;;OAGG;IACH,OAAO,CAAC,qBAAqB;IA0B7B,OAAO,CAAC,sBAAsB;IAoB9B,OAAO,CAAC,oBAAoB;IAkB5B,OAAO,CAAC,aAAa;CAQtB"}
@@ -1,6 +1,139 @@
1
1
  import { addScaledVector3, normalizedParticleAge, setVector3, } from "./Particle.js";
2
2
  import { ParticleEmitter } from "./ParticleEmitter.js";
3
+ import { GPU_PARTICLE_CURVE_STOPS, } from "./GPUParticleBackend.js";
3
4
  import { applyParticleModules } from "./ParticleModule.js";
5
+ import { CollisionModule } from "./CollisionModule.js";
6
+ import { encodeColorGradientLUT, ColorModule } from "./ColorModule.js";
7
+ import { encodeSizeCurveLUT, SizeModule } from "./SizeModule.js";
8
+ import { WindModule } from "./ForceModule.js";
9
+ import { TurbulenceModule } from "./TurbulenceModule.js";
10
+ import { HeightfieldModule } from "./HeightfieldModule.js";
11
+ import { LightingModule } from "./LightingModule.js";
12
+ import { SubEmitterModule } from "./SubEmitterModule.js";
13
+ import { decodeTrailRingBuffer, TrailModule } from "./TrailModule.js";
14
+ /**
15
+ * Aggregate GPU-capable modules into one compute effects descriptor.
16
+ * Returns undefined when no module has a GPU implementation (legacy path).
17
+ * Overflow policy: the first N instances of a capped effect (3 planes,
18
+ * 2 sub-emitters) run on GPU; extras keep their exact CPU hooks.
19
+ */
20
+ export function collectGPUParticleEffects(modules) {
21
+ const covered = new Set();
22
+ let wind;
23
+ let turbulence;
24
+ let heightfield;
25
+ let lighting;
26
+ let lifeCurves;
27
+ let trailPointsPerParticle;
28
+ const planes = [];
29
+ const subEmitters = [];
30
+ let colorModule;
31
+ let sizeModule;
32
+ for (const module of modules) {
33
+ if (module instanceof WindModule && !wind) {
34
+ const encoded = module.toGPUWind();
35
+ wind = {
36
+ direction: encoded.direction,
37
+ strength: encoded.strength,
38
+ gustAmplitude: encoded.gustAmplitude,
39
+ gustDirection: encoded.gustDirection,
40
+ gustFrequency: encoded.gustFrequency,
41
+ gustSpeed: encoded.gustSpeed,
42
+ };
43
+ covered.add(module);
44
+ }
45
+ else if (module instanceof TurbulenceModule && !turbulence) {
46
+ const encoded = module.toGPUTurbulence();
47
+ turbulence = {
48
+ strength: encoded.strength,
49
+ scale: encoded.scale,
50
+ flowSpeed: encoded.flowSpeed,
51
+ lut: module.getLUT(),
52
+ lutResolution: encoded.lutResolution,
53
+ };
54
+ covered.add(module);
55
+ }
56
+ else if (module instanceof CollisionModule && planes.length < 3) {
57
+ const encoded = module.toGPUPlane();
58
+ planes.push({
59
+ normal: encoded.normal,
60
+ constant: encoded.constant,
61
+ restitution: encoded.restitution,
62
+ killOnContact: encoded.killOnContact,
63
+ });
64
+ covered.add(module);
65
+ }
66
+ else if (module instanceof HeightfieldModule && !heightfield) {
67
+ const encoded = module.toGPUHeightfield();
68
+ heightfield = {
69
+ originX: encoded.originX,
70
+ originZ: encoded.originZ,
71
+ cellSize: encoded.cellSize,
72
+ columns: encoded.columns,
73
+ rows: encoded.rows,
74
+ heights: module.sampler.heights,
75
+ restitution: encoded.restitution,
76
+ killOnContact: encoded.killOnContact,
77
+ };
78
+ covered.add(module);
79
+ }
80
+ else if (module instanceof ColorModule && !colorModule) {
81
+ colorModule = module;
82
+ }
83
+ else if (module instanceof SizeModule && !sizeModule) {
84
+ sizeModule = module;
85
+ }
86
+ else if (module instanceof LightingModule && !lighting) {
87
+ const encoded = module.toGPULighting();
88
+ lighting = {
89
+ ambient: encoded.ambient,
90
+ keyDirection: encoded.keyDirection,
91
+ diffuseStrength: encoded.diffuseStrength,
92
+ };
93
+ covered.add(module);
94
+ }
95
+ else if (module instanceof SubEmitterModule && subEmitters.length < 2) {
96
+ const encoded = module.toGPUSubEmitter();
97
+ subEmitters.push({
98
+ triggerAge: encoded.triggerAge,
99
+ chance: encoded.chance,
100
+ childCount: encoded.childCount,
101
+ });
102
+ covered.add(module);
103
+ }
104
+ else if (module instanceof TrailModule && trailPointsPerParticle === undefined) {
105
+ trailPointsPerParticle = Math.min(module.maxPoints, 8);
106
+ covered.add(module);
107
+ }
108
+ }
109
+ if (colorModule || sizeModule) {
110
+ lifeCurves = {
111
+ stops: GPU_PARTICLE_CURVE_STOPS,
112
+ ...(colorModule ? { colors: encodeColorGradientLUT(colorModule.gradient, GPU_PARTICLE_CURVE_STOPS) } : {}),
113
+ ...(sizeModule ? { sizes: encodeSizeCurveLUT(sizeModule.curve, GPU_PARTICLE_CURVE_STOPS) } : {}),
114
+ };
115
+ if (colorModule)
116
+ covered.add(colorModule);
117
+ if (sizeModule)
118
+ covered.add(sizeModule);
119
+ }
120
+ if (covered.size === 0) {
121
+ return undefined;
122
+ }
123
+ return {
124
+ effects: {
125
+ wind,
126
+ turbulence,
127
+ planes,
128
+ heightfield,
129
+ subEmitters,
130
+ lifeCurves,
131
+ lighting,
132
+ trailPointsPerParticle,
133
+ },
134
+ gpuCovered: covered,
135
+ };
136
+ }
4
137
  export class ParticleSystem {
5
138
  particles = [];
6
139
  emitters = [];
@@ -11,6 +144,11 @@ export class ParticleSystem {
11
144
  stats;
12
145
  gpuBackend;
13
146
  preferGPU;
147
+ lastSpawnRequests = [];
148
+ lastIntegratedParticles = [];
149
+ lastEffectsCovered;
150
+ /** GPU trail ring rows by particle id, fed back so history accumulates across calls. */
151
+ trailRingById = new Map();
14
152
  constructor(options = {}) {
15
153
  this.maxParticles = options.maxParticles ?? 1000;
16
154
  if (!Number.isInteger(this.maxParticles) || this.maxParticles <= 0) {
@@ -30,6 +168,7 @@ export class ParticleSystem {
30
168
  uploadedBytes: 0,
31
169
  gpuSpawns: 0,
32
170
  gpuUpdates: 0,
171
+ overBudget: false,
33
172
  };
34
173
  }
35
174
  addEmitter(emitter) {
@@ -49,6 +188,10 @@ export class ParticleSystem {
49
188
  this.stats.killedCount += this.particles.length;
50
189
  this.particles.length = 0;
51
190
  this.stats.liveCount = 0;
191
+ this.trailRingById.clear();
192
+ this.lastSpawnRequests = [];
193
+ this.lastIntegratedParticles = [];
194
+ this.lastEffectsCovered = undefined;
52
195
  }
53
196
  dispose() {
54
197
  this.clear();
@@ -65,6 +208,7 @@ export class ParticleSystem {
65
208
  }
66
209
  this.spawn(deltaTime);
67
210
  this.integrate(deltaTime);
211
+ this.drainSubEmitterSpawns();
68
212
  this.compactDeadParticles();
69
213
  this.elapsedTime += deltaTime;
70
214
  this.stats.liveCount = this.particles.length;
@@ -79,6 +223,7 @@ export class ParticleSystem {
79
223
  const spawnedParticles = this.spawn(deltaTime);
80
224
  await this.initializeSpawnedOnGPU(spawnedParticles, backend);
81
225
  await this.integrateOnGPU(deltaTime, backend);
226
+ await this.drainSubEmitterSpawnsOnGPU(backend);
82
227
  this.compactDeadParticles();
83
228
  this.elapsedTime += deltaTime;
84
229
  this.stats.liveCount = this.particles.length;
@@ -106,7 +251,11 @@ export class ParticleSystem {
106
251
  for (const emitter of this.emitters) {
107
252
  const capacity = this.maxParticles - this.particles.length;
108
253
  const result = emitter.emit(deltaTime, capacity);
109
- this.stats.droppedCount += Math.max(0, result.requested - result.emitted);
254
+ const dropped = Math.max(0, result.requested - result.emitted);
255
+ this.stats.droppedCount += dropped;
256
+ if (dropped > 0) {
257
+ this.stats.overBudget = true;
258
+ }
110
259
  this.stats.spawnedCount += result.emitted;
111
260
  for (const particle of result.particles) {
112
261
  const context = this.createContext(deltaTime, particle);
@@ -185,14 +334,26 @@ export class ParticleSystem {
185
334
  if (liveParticles.length === 0) {
186
335
  return;
187
336
  }
337
+ const collected = collectGPUParticleEffects(this.modules);
338
+ const useEffects = collected !== undefined && backend.supportsEffects === true;
339
+ const covered = useEffects ? collected.gpuCovered : undefined;
188
340
  for (const particle of liveParticles) {
189
341
  const context = this.createContext(deltaTime, particle);
190
- applyParticleModules(this.modules, "update", particle, context);
342
+ for (const module of this.modules) {
343
+ if (covered?.has(module)) {
344
+ continue;
345
+ }
346
+ module.update?.(particle, context);
347
+ }
191
348
  setVector3(particle.previousPosition, particle.position);
192
349
  }
193
350
  const positions = new Float32Array(liveParticles.length * 4);
194
351
  const velocities = new Float32Array(liveParticles.length * 4);
195
352
  const accelerations = new Float32Array(liveParticles.length * 4);
353
+ const needsBaseAttributes = Boolean(useEffects && (collected.effects.lifeCurves || collected.effects.lighting));
354
+ const baseAttributes = needsBaseAttributes ? new Float32Array(liveParticles.length * 8) : undefined;
355
+ const trailDepth = useEffects ? (collected.effects.trailPointsPerParticle ?? 0) : 0;
356
+ const trailHistory = trailDepth > 0 ? this.assembleTrailHistory(liveParticles, trailDepth) : undefined;
196
357
  for (let index = 0; index < liveParticles.length; index += 1) {
197
358
  const particle = liveParticles[index];
198
359
  const offset = index * 4;
@@ -203,9 +364,19 @@ export class ParticleSystem {
203
364
  velocities[offset] = particle.velocity.x;
204
365
  velocities[offset + 1] = particle.velocity.y;
205
366
  velocities[offset + 2] = particle.velocity.z;
367
+ // velocities.w carries per-particle lifetime for the effects kernel.
368
+ velocities[offset + 3] = useEffects ? particle.lifetime : 0;
206
369
  accelerations[offset] = particle.acceleration.x;
207
370
  accelerations[offset + 1] = particle.acceleration.y;
208
371
  accelerations[offset + 2] = particle.acceleration.z;
372
+ if (baseAttributes) {
373
+ const attributeOffset = index * 8;
374
+ baseAttributes[attributeOffset] = particle.color.r;
375
+ baseAttributes[attributeOffset + 1] = particle.color.g;
376
+ baseAttributes[attributeOffset + 2] = particle.color.b;
377
+ baseAttributes[attributeOffset + 3] = particle.color.a;
378
+ baseAttributes[attributeOffset + 4] = particle.size;
379
+ }
209
380
  }
210
381
  const result = await backend.update({
211
382
  positions,
@@ -213,26 +384,224 @@ export class ParticleSystem {
213
384
  accelerations,
214
385
  deltaTime,
215
386
  count: liveParticles.length,
387
+ ...(useEffects
388
+ ? {
389
+ effects: {
390
+ ...collected.effects,
391
+ time: this.elapsedTime,
392
+ },
393
+ baseAttributes,
394
+ trailHistory,
395
+ }
396
+ : {}),
216
397
  });
398
+ if (useEffects) {
399
+ this.lastSpawnRequests = result.spawnRequests ? Array.from(result.spawnRequests) : [];
400
+ this.lastIntegratedParticles = liveParticles;
401
+ this.lastEffectsCovered = covered;
402
+ }
403
+ else {
404
+ this.lastSpawnRequests = [];
405
+ this.lastIntegratedParticles = [];
406
+ this.lastEffectsCovered = undefined;
407
+ }
217
408
  for (let index = 0; index < liveParticles.length; index += 1) {
218
409
  const particle = liveParticles[index];
219
410
  const offset = index * 4;
220
411
  particle.position.x = result.positions[offset];
221
412
  particle.position.y = result.positions[offset + 1];
222
413
  particle.position.z = result.positions[offset + 2];
223
- particle.age = result.positions[offset + 3];
414
+ const rawAge = result.positions[offset + 3];
415
+ if (useEffects && (!Number.isFinite(rawAge) || rawAge < 0)) {
416
+ // Compute-side kill (analytic plane or heightfield in kill mode).
417
+ particle.alive = false;
418
+ particle.age = particle.lifetime;
419
+ continue;
420
+ }
421
+ particle.age = rawAge;
224
422
  particle.velocity.x = result.velocities[offset];
225
423
  particle.velocity.y = result.velocities[offset + 1];
226
424
  particle.velocity.z = result.velocities[offset + 2];
425
+ if (result.attributes) {
426
+ const attributeOffset = index * 8;
427
+ particle.color.r = result.attributes[attributeOffset] ?? particle.color.r;
428
+ particle.color.g = result.attributes[attributeOffset + 1] ?? particle.color.g;
429
+ particle.color.b = result.attributes[attributeOffset + 2] ?? particle.color.b;
430
+ particle.color.a = result.attributes[attributeOffset + 3] ?? particle.color.a;
431
+ particle.size = result.attributes[attributeOffset + 4] ?? particle.size;
432
+ }
227
433
  particle.rotation += particle.angularVelocity * deltaTime;
228
- applyParticleModules(this.modules, "afterIntegrate", particle, this.createContext(deltaTime, particle));
434
+ const context = this.createContext(deltaTime, particle);
435
+ for (const module of this.modules) {
436
+ if (covered?.has(module)) {
437
+ continue;
438
+ }
439
+ module.afterIntegrate?.(particle, context);
440
+ }
229
441
  if (particle.age >= particle.lifetime) {
230
442
  particle.alive = false;
231
443
  }
232
444
  }
445
+ if (useEffects && result.trailPositions) {
446
+ this.applyGPUTrailHistories(liveParticles, collected.effects, result.trailPositions);
447
+ this.storeTrailRings(liveParticles, collected.effects, result.trailPositions);
448
+ }
233
449
  this.stats.gpuUpdates += 1;
234
450
  this.recordBufferUpload(positions.byteLength + velocities.byteLength + accelerations.byteLength + 16);
235
451
  }
452
+ /**
453
+ * Assemble the previous trail ring in live-particle order from per-id rows
454
+ * so compute history survives spawn/death reordering. Unknown ids start
455
+ * fresh (head = live position/age, older slots expired).
456
+ */
457
+ assembleTrailHistory(liveParticles, depth) {
458
+ const history = new Float32Array(liveParticles.length * depth * 4);
459
+ for (let index = 0; index < liveParticles.length; index += 1) {
460
+ const particle = liveParticles[index];
461
+ const target = index * depth * 4;
462
+ const row = this.trailRingById.get(particle.id);
463
+ if (row && row.length >= depth * 4) {
464
+ history.set(row.subarray(0, depth * 4), target);
465
+ continue;
466
+ }
467
+ history[target] = particle.position.x;
468
+ history[target + 1] = particle.position.y;
469
+ history[target + 2] = particle.position.z;
470
+ history[target + 3] = particle.age;
471
+ for (let slot = 1; slot < depth; slot += 1) {
472
+ const slotOffset = target + slot * 4;
473
+ history[slotOffset] = particle.position.x;
474
+ history[slotOffset + 1] = particle.position.y;
475
+ history[slotOffset + 2] = particle.position.z;
476
+ history[slotOffset + 3] = Number.POSITIVE_INFINITY;
477
+ }
478
+ }
479
+ return history;
480
+ }
481
+ storeTrailRings(liveParticles, effects, trailPositions) {
482
+ const depth = effects.trailPointsPerParticle ?? 1;
483
+ for (let index = 0; index < liveParticles.length; index += 1) {
484
+ const particle = liveParticles[index];
485
+ if (!particle) {
486
+ continue;
487
+ }
488
+ this.trailRingById.set(particle.id, trailPositions.slice(index * depth * 4, (index + 1) * depth * 4));
489
+ }
490
+ if (this.trailRingById.size > this.particles.length * 2 + 16) {
491
+ const liveIds = new Set(this.particles.map((particle) => particle.id));
492
+ for (const id of this.trailRingById.keys()) {
493
+ if (!liveIds.has(id)) {
494
+ this.trailRingById.delete(id);
495
+ }
496
+ }
497
+ }
498
+ }
499
+ /** CPU path: turn queued sub-emitter requests into live child particles. */
500
+ drainSubEmitterSpawns() {
501
+ for (const module of this.modules) {
502
+ if (!(module instanceof SubEmitterModule)) {
503
+ continue;
504
+ }
505
+ const capacity = this.maxParticles - this.particles.length;
506
+ const { children, dropped } = module.drainPendingSpawns(capacity);
507
+ this.stats.droppedCount += dropped;
508
+ if (dropped > 0) {
509
+ this.stats.overBudget = true;
510
+ }
511
+ for (const child of children) {
512
+ const context = this.createContext(0, child);
513
+ applyParticleModules(this.modules, "onSpawn", child, context);
514
+ this.particles.push(child);
515
+ this.stats.spawnedCount += 1;
516
+ }
517
+ }
518
+ }
519
+ /**
520
+ * GPU path: covered sub-emitters report per-particle spawn requests from
521
+ * the compute kernel; uncovered ones queue CPU requests as usual. Children
522
+ * initialize through the GPU spawn pipeline when the backend offers one.
523
+ */
524
+ async drainSubEmitterSpawnsOnGPU(backend) {
525
+ const spawned = [];
526
+ let slot = 0;
527
+ for (const module of this.modules) {
528
+ if (!(module instanceof SubEmitterModule)) {
529
+ continue;
530
+ }
531
+ const capacity = this.maxParticles - this.particles.length - spawned.length;
532
+ if (this.lastEffectsCovered?.has(module) ?? false) {
533
+ const { children, dropped } = module.createChildren(this.requestsForSubEmitter(slot), capacity);
534
+ slot += 1;
535
+ this.stats.droppedCount += dropped;
536
+ if (dropped > 0) {
537
+ this.stats.overBudget = true;
538
+ }
539
+ spawned.push(...children);
540
+ }
541
+ else {
542
+ const { children, dropped } = module.drainPendingSpawns(capacity);
543
+ this.stats.droppedCount += dropped;
544
+ if (dropped > 0) {
545
+ this.stats.overBudget = true;
546
+ }
547
+ spawned.push(...children);
548
+ }
549
+ }
550
+ if (spawned.length === 0) {
551
+ return;
552
+ }
553
+ for (const child of spawned) {
554
+ const context = this.createContext(0, child);
555
+ applyParticleModules(this.modules, "onSpawn", child, context);
556
+ this.particles.push(child);
557
+ this.stats.spawnedCount += 1;
558
+ }
559
+ await this.initializeSpawnedOnGPU(spawned, backend);
560
+ }
561
+ /**
562
+ * Decode one covered sub-emitter slot from the packed kernel flags
563
+ * (low 16 bits = slot 0, high 16 bits = slot 1).
564
+ */
565
+ requestsForSubEmitter(slot) {
566
+ const requests = [];
567
+ if (this.lastSpawnRequests.length === 0) {
568
+ return requests;
569
+ }
570
+ for (let index = 0; index < this.lastSpawnRequests.length; index += 1) {
571
+ const flag = this.lastSpawnRequests[index] ?? 0;
572
+ const count = slot === 0 ? flag & 0xffff : flag >>> 16;
573
+ if (count <= 0) {
574
+ continue;
575
+ }
576
+ // Death triggers intentionally reference particles that just died; the
577
+ // kernel only records requests for triggers crossed while alive.
578
+ const particle = this.lastIntegratedParticles[index];
579
+ if (!particle) {
580
+ continue;
581
+ }
582
+ requests.push({
583
+ position: { ...particle.position },
584
+ velocity: { ...particle.velocity },
585
+ count,
586
+ });
587
+ }
588
+ return requests;
589
+ }
590
+ applyGPUTrailHistories(liveParticles, effects, trailPositions) {
591
+ const trailModule = this.modules.find((module) => module instanceof TrailModule);
592
+ if (!(trailModule instanceof TrailModule)) {
593
+ return;
594
+ }
595
+ const depth = effects.trailPointsPerParticle ?? 1;
596
+ const histories = decodeTrailRingBuffer(trailPositions, liveParticles.length, depth, trailModule.lifetime);
597
+ for (let index = 0; index < liveParticles.length; index += 1) {
598
+ const particle = liveParticles[index];
599
+ if (!particle || !particle.alive) {
600
+ continue;
601
+ }
602
+ particle.userData.trail = histories[index] ?? [];
603
+ }
604
+ }
236
605
  compactDeadParticles() {
237
606
  let write = 0;
238
607
  let killed = 0;