@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
@@ -9,6 +9,20 @@ const MAP_MODE = {
9
9
  READ: 0x0001,
10
10
  };
11
11
  const WORKGROUP_SIZE = 64;
12
+ /** Effect flag bits for the extended compute uniform. */
13
+ export const GPU_PARTICLE_EFFECT_WIND = 0x01;
14
+ export const GPU_PARTICLE_EFFECT_TURBULENCE = 0x02;
15
+ export const GPU_PARTICLE_EFFECT_PLANES = 0x04;
16
+ export const GPU_PARTICLE_EFFECT_HEIGHTFIELD = 0x08;
17
+ export const GPU_PARTICLE_EFFECT_SUB_EMITTERS = 0x10;
18
+ export const GPU_PARTICLE_EFFECT_LIFE_CURVES = 0x20;
19
+ export const GPU_PARTICLE_EFFECT_LIGHTING = 0x40;
20
+ export const GPU_PARTICLE_EFFECT_TRAILS = 0x80;
21
+ export const GPU_PARTICLE_EFFECT_SIZE_CURVES = 0x100;
22
+ export const GPU_PARTICLE_MAX_PLANES = 3;
23
+ export const GPU_PARTICLE_MAX_SUB_EMITTERS = 2;
24
+ export const GPU_PARTICLE_CURVE_STOPS = 16;
25
+ export const GPU_PARTICLE_UNIFORM_BYTE_LENGTH = 272;
12
26
  export function detectGPUParticleBackend(globalScope = globalThis) {
13
27
  const gpu = readWebGPU(globalScope);
14
28
  if (gpu) {
@@ -66,12 +80,15 @@ export class UnsupportedGPUParticleBackend {
66
80
  }
67
81
  export class WebGPUParticleBackend {
68
82
  capabilities = { supported: true, backend: "webgpu" };
83
+ supportsEffects = true;
69
84
  gpu;
70
85
  shaderSource;
86
+ effectsShaderSource;
71
87
  adapter = null;
72
88
  device = null;
73
89
  updatePipeline = null;
74
90
  spawnPipeline = null;
91
+ effectsPipeline = null;
75
92
  constructor(options = {}) {
76
93
  const gpu = options.gpu ?? readWebGPU(globalThis);
77
94
  if (!gpu) {
@@ -79,9 +96,10 @@ export class WebGPUParticleBackend {
79
96
  }
80
97
  this.gpu = gpu;
81
98
  this.shaderSource = options.shaderSource ?? createDefaultParticleComputeShader();
99
+ this.effectsShaderSource = options.effectsShaderSource ?? createEffectsParticleComputeShader();
82
100
  }
83
101
  async initialize() {
84
- if (this.device && this.updatePipeline && this.spawnPipeline) {
102
+ if (this.device && this.updatePipeline && this.spawnPipeline && this.effectsPipeline) {
85
103
  return;
86
104
  }
87
105
  this.adapter = await this.gpu.requestAdapter();
@@ -109,6 +127,18 @@ export class WebGPUParticleBackend {
109
127
  entryPoint: "spawn_main",
110
128
  },
111
129
  });
130
+ const effectsModule = this.device.createShaderModule({
131
+ label: "aura3d-particles-effects",
132
+ code: this.effectsShaderSource,
133
+ });
134
+ this.effectsPipeline = this.device.createComputePipeline({
135
+ label: "aura3d-particles-effects",
136
+ layout: "auto",
137
+ compute: {
138
+ module: effectsModule,
139
+ entryPoint: "main",
140
+ },
141
+ });
112
142
  }
113
143
  async spawn(input) {
114
144
  validateSpawnInput(input);
@@ -189,6 +219,9 @@ export class WebGPUParticleBackend {
189
219
  }
190
220
  async update(input) {
191
221
  validateUpdateInput(input);
222
+ if (input.effects) {
223
+ return this.updateWithEffects(input, input.effects);
224
+ }
192
225
  if (input.count === 0) {
193
226
  return {
194
227
  backend: "webgpu",
@@ -258,9 +291,127 @@ export class WebGPUParticleBackend {
258
291
  velocityReadback.destroy();
259
292
  }
260
293
  }
294
+ async updateWithEffects(input, effects) {
295
+ const plan = encodeGPUParticleEffects(effects, input.deltaTime, input.count);
296
+ if (input.count === 0) {
297
+ return {
298
+ backend: "webgpu",
299
+ count: 0,
300
+ workgroups: 0,
301
+ positions: input.positions.slice(),
302
+ velocities: input.velocities.slice(),
303
+ };
304
+ }
305
+ await this.initialize();
306
+ const device = this.requireDevice();
307
+ const pipeline = this.requireEffectsPipeline();
308
+ const count = input.count;
309
+ const byteLength = count * 4 * Float32Array.BYTES_PER_ELEMENT;
310
+ const positionBuffer = createStorageBuffer(device, "aura3d-particles-fx-positions", byteLength);
311
+ const velocityBuffer = createStorageBuffer(device, "aura3d-particles-fx-velocities", byteLength);
312
+ const accelerationBuffer = createStorageBuffer(device, "aura3d-particles-fx-accelerations", byteLength);
313
+ const paramsBuffer = device.createBuffer({
314
+ label: "aura3d-particles-fx-params",
315
+ size: GPU_PARTICLE_UNIFORM_BYTE_LENGTH,
316
+ usage: BUFFER_USAGE.UNIFORM | BUFFER_USAGE.COPY_DST,
317
+ });
318
+ const fieldsBuffer = createStorageBuffer(device, "aura3d-particles-fx-fields", Math.max(plan.fieldData.byteLength, 16));
319
+ const spawnByteLength = Math.max(count * Uint32Array.BYTES_PER_ELEMENT, 4);
320
+ const spawnBuffer = createStorageBuffer(device, "aura3d-particles-fx-spawn-requests", spawnByteLength);
321
+ const trailByteLength = Math.max(count * plan.trailDepth * 4 * Float32Array.BYTES_PER_ELEMENT, 16);
322
+ const trailBuffer = createStorageBuffer(device, "aura3d-particles-fx-trail", trailByteLength);
323
+ const attributeByteLength = Math.max(count * 2 * 4 * Float32Array.BYTES_PER_ELEMENT, 16);
324
+ const attributeBuffer = createStorageBuffer(device, "aura3d-particles-fx-attributes", attributeByteLength);
325
+ const baseAttributeBuffer = createStorageBuffer(device, "aura3d-particles-fx-base-attributes", attributeByteLength);
326
+ const positionReadback = createReadbackBuffer(device, "aura3d-particles-fx-positions-readback", byteLength);
327
+ const velocityReadback = createReadbackBuffer(device, "aura3d-particles-fx-velocities-readback", byteLength);
328
+ const spawnReadback = createReadbackBuffer(device, "aura3d-particles-fx-spawn-readback", spawnByteLength);
329
+ const trailReadback = createReadbackBuffer(device, "aura3d-particles-fx-trail-readback", trailByteLength);
330
+ const attributeReadback = createReadbackBuffer(device, "aura3d-particles-fx-attributes-readback", attributeByteLength);
331
+ try {
332
+ device.queue.writeBuffer(positionBuffer, 0, viewParticleSlice(input.positions, count));
333
+ device.queue.writeBuffer(velocityBuffer, 0, viewParticleSlice(input.velocities, count));
334
+ device.queue.writeBuffer(accelerationBuffer, 0, viewParticleSlice(input.accelerations ?? new Float32Array(count * 4), count));
335
+ device.queue.writeBuffer(paramsBuffer, 0, plan.uniform);
336
+ device.queue.writeBuffer(fieldsBuffer, 0, plan.fieldData);
337
+ device.queue.writeBuffer(spawnBuffer, 0, new Uint32Array(count));
338
+ device.queue.writeBuffer(trailBuffer, 0, snapshotTrailHistory(input.trailHistory, input.positions, count, plan.trailDepth));
339
+ device.queue.writeBuffer(attributeBuffer, 0, new Float32Array(Math.max(count * 8, 4)));
340
+ device.queue.writeBuffer(baseAttributeBuffer, 0, createBaseAttributeSnapshot(input.baseAttributes, count));
341
+ const bindGroup = device.createBindGroup({
342
+ label: "aura3d-particles-fx-bind-group",
343
+ layout: pipeline.getBindGroupLayout(0),
344
+ entries: [
345
+ { binding: 0, resource: { buffer: positionBuffer } },
346
+ { binding: 1, resource: { buffer: velocityBuffer } },
347
+ { binding: 2, resource: { buffer: accelerationBuffer } },
348
+ { binding: 3, resource: { buffer: paramsBuffer } },
349
+ { binding: 4, resource: { buffer: fieldsBuffer } },
350
+ { binding: 5, resource: { buffer: spawnBuffer } },
351
+ { binding: 6, resource: { buffer: trailBuffer } },
352
+ { binding: 7, resource: { buffer: attributeBuffer } },
353
+ { binding: 8, resource: { buffer: baseAttributeBuffer } },
354
+ ],
355
+ });
356
+ const commandEncoder = device.createCommandEncoder({ label: "aura3d-particles-fx-encoder" });
357
+ const computePass = commandEncoder.beginComputePass({ label: "aura3d-particles-fx-pass" });
358
+ computePass.setPipeline(pipeline);
359
+ computePass.setBindGroup(0, bindGroup);
360
+ const workgroups = Math.ceil(count / WORKGROUP_SIZE);
361
+ computePass.dispatchWorkgroups(workgroups);
362
+ computePass.end();
363
+ commandEncoder.copyBufferToBuffer(positionBuffer, 0, positionReadback, 0, byteLength);
364
+ commandEncoder.copyBufferToBuffer(velocityBuffer, 0, velocityReadback, 0, byteLength);
365
+ commandEncoder.copyBufferToBuffer(spawnBuffer, 0, spawnReadback, 0, spawnByteLength);
366
+ commandEncoder.copyBufferToBuffer(trailBuffer, 0, trailReadback, 0, trailByteLength);
367
+ commandEncoder.copyBufferToBuffer(attributeBuffer, 0, attributeReadback, 0, attributeByteLength);
368
+ device.queue.submit([commandEncoder.finish()]);
369
+ const [positions, velocities, spawnRequests, trailPositions, attributes] = await Promise.all([
370
+ readFloat32Buffer(positionReadback, byteLength),
371
+ readFloat32Buffer(velocityReadback, byteLength),
372
+ readUint32Buffer(spawnReadback, spawnByteLength),
373
+ readFloat32Buffer(trailReadback, trailByteLength),
374
+ readFloat32Buffer(attributeReadback, attributeByteLength),
375
+ ]);
376
+ const result = {
377
+ backend: "webgpu",
378
+ count,
379
+ workgroups,
380
+ positions,
381
+ velocities,
382
+ };
383
+ if (plan.captureSpawnRequests) {
384
+ result.spawnRequests = spawnRequests.slice(0, count);
385
+ }
386
+ if (plan.captureAttributes) {
387
+ result.attributes = attributes.slice(0, count * 8);
388
+ }
389
+ if (plan.captureTrails) {
390
+ result.trailPositions = trailPositions.slice(0, count * plan.trailDepth * 4);
391
+ }
392
+ return result;
393
+ }
394
+ finally {
395
+ positionBuffer.destroy();
396
+ velocityBuffer.destroy();
397
+ accelerationBuffer.destroy();
398
+ paramsBuffer.destroy();
399
+ fieldsBuffer.destroy();
400
+ spawnBuffer.destroy();
401
+ trailBuffer.destroy();
402
+ attributeBuffer.destroy();
403
+ baseAttributeBuffer.destroy();
404
+ positionReadback.destroy();
405
+ velocityReadback.destroy();
406
+ spawnReadback.destroy();
407
+ trailReadback.destroy();
408
+ attributeReadback.destroy();
409
+ }
410
+ }
261
411
  dispose() {
262
412
  this.updatePipeline = null;
263
413
  this.spawnPipeline = null;
414
+ this.effectsPipeline = null;
264
415
  this.adapter = null;
265
416
  this.device?.destroy?.();
266
417
  this.device = null;
@@ -283,6 +434,12 @@ export class WebGPUParticleBackend {
283
434
  }
284
435
  return this.spawnPipeline;
285
436
  }
437
+ requireEffectsPipeline() {
438
+ if (!this.effectsPipeline) {
439
+ throw new Error("WebGPU particle effects compute pipeline was not initialized.");
440
+ }
441
+ return this.effectsPipeline;
442
+ }
286
443
  }
287
444
  function readWebGPU(globalScope) {
288
445
  return globalScope.navigator?.gpu;
@@ -356,6 +513,232 @@ async function readFloat32Buffer(buffer, byteLength) {
356
513
  buffer.unmap();
357
514
  return new Float32Array(copy);
358
515
  }
516
+ async function readUint32Buffer(buffer, byteLength) {
517
+ await buffer.mapAsync(MAP_MODE.READ);
518
+ const mapped = buffer.getMappedRange();
519
+ const copy = mapped.slice(0, byteLength);
520
+ buffer.unmap();
521
+ return new Uint32Array(copy);
522
+ }
523
+ /**
524
+ * Pure encoder for the extended compute uniform. The 272-byte layout is:
525
+ * header (deltaTime f32 @0, count u32 @4, time f32 @8, flags u32 @12),
526
+ * then 16 vec4 slots: wind, windGust, windTurb, fxCounts, 3x(plane, planeMisc),
527
+ * sub0, sub1, height0, height1, light0, light1. Unit-tested byte-for-byte, and
528
+ * mirrored by the fake devices in tests plus the WGSL kernel below.
529
+ */
530
+ export function encodeGPUParticleEffects(effects, deltaTime, count) {
531
+ const planes = effects.planes ?? [];
532
+ const subEmitters = effects.subEmitters ?? [];
533
+ if (planes.length > GPU_PARTICLE_MAX_PLANES) {
534
+ throw new RangeError(`GPU particle effects support at most ${GPU_PARTICLE_MAX_PLANES} analytic planes.`);
535
+ }
536
+ if (subEmitters.length > GPU_PARTICLE_MAX_SUB_EMITTERS) {
537
+ throw new RangeError(`GPU particle effects support at most ${GPU_PARTICLE_MAX_SUB_EMITTERS} sub-emitters.`);
538
+ }
539
+ let flags = 0;
540
+ if (effects.wind)
541
+ flags |= GPU_PARTICLE_EFFECT_WIND;
542
+ if (effects.turbulence)
543
+ flags |= GPU_PARTICLE_EFFECT_TURBULENCE;
544
+ if (planes.length > 0)
545
+ flags |= GPU_PARTICLE_EFFECT_PLANES;
546
+ if (effects.heightfield)
547
+ flags |= GPU_PARTICLE_EFFECT_HEIGHTFIELD;
548
+ if (subEmitters.length > 0)
549
+ flags |= GPU_PARTICLE_EFFECT_SUB_EMITTERS;
550
+ if (effects.lifeCurves) {
551
+ if (effects.lifeCurves.colors)
552
+ flags |= GPU_PARTICLE_EFFECT_LIFE_CURVES;
553
+ if (effects.lifeCurves.sizes)
554
+ flags |= GPU_PARTICLE_EFFECT_SIZE_CURVES;
555
+ }
556
+ if (effects.lighting)
557
+ flags |= GPU_PARTICLE_EFFECT_LIGHTING;
558
+ const trailDepth = effects.trailPointsPerParticle ?? 1;
559
+ if (!Number.isInteger(trailDepth) || trailDepth < 1 || trailDepth > 8) {
560
+ throw new RangeError("GPU particle trailPointsPerParticle must be an integer in [1, 8].");
561
+ }
562
+ const captureTrails = effects.trailPointsPerParticle !== undefined;
563
+ if (captureTrails)
564
+ flags |= GPU_PARTICLE_EFFECT_TRAILS;
565
+ const turbulence = effects.turbulence;
566
+ if (turbulence) {
567
+ if (turbulence.lutResolution !== 8) {
568
+ throw new RangeError("GPU particle turbulence requires an 8^3 curl-noise LUT (TURBULENCE_LUT_RESOLUTION).");
569
+ }
570
+ if (turbulence.lut.length < 8 ** 3 * 4) {
571
+ throw new RangeError("GPU particle turbulence LUT must hold 8^3 vec4 entries.");
572
+ }
573
+ }
574
+ const curves = effects.lifeCurves;
575
+ if (curves) {
576
+ if (curves.stops !== GPU_PARTICLE_CURVE_STOPS) {
577
+ throw new RangeError(`GPU particle life curves require exactly ${GPU_PARTICLE_CURVE_STOPS} stops.`);
578
+ }
579
+ if (!curves.colors && !curves.sizes) {
580
+ throw new RangeError("GPU particle life curves need at least a color gradient or a size curve.");
581
+ }
582
+ if (curves.colors && curves.colors.length < curves.stops * 4) {
583
+ throw new RangeError("GPU particle color gradient buffer is smaller than the declared stop count.");
584
+ }
585
+ if (curves.sizes && curves.sizes.length < curves.stops) {
586
+ throw new RangeError("GPU particle size curve buffer is smaller than the declared stop count.");
587
+ }
588
+ }
589
+ const heightfield = effects.heightfield;
590
+ if (heightfield) {
591
+ if (!Number.isInteger(heightfield.columns) || heightfield.columns < 2 ||
592
+ !Number.isInteger(heightfield.rows) || heightfield.rows < 2) {
593
+ throw new RangeError("GPU particle heightfield columns and rows must be integers >= 2.");
594
+ }
595
+ if (!Number.isFinite(heightfield.cellSize) || heightfield.cellSize <= 0) {
596
+ throw new RangeError("GPU particle heightfield cellSize must be a finite positive number.");
597
+ }
598
+ if (heightfield.heights.length < heightfield.columns * heightfield.rows) {
599
+ throw new RangeError("GPU particle heightfield heights are smaller than columns*rows.");
600
+ }
601
+ }
602
+ const buffer = new ArrayBuffer(GPU_PARTICLE_UNIFORM_BYTE_LENGTH);
603
+ const view = new DataView(buffer);
604
+ const setF32 = (offset, value) => view.setFloat32(offset, value, true);
605
+ const setU32 = (offset, value) => view.setUint32(offset, value, true);
606
+ setF32(0, deltaTime);
607
+ setU32(4, count);
608
+ setF32(8, effects.time ?? 0);
609
+ setU32(12, flags);
610
+ const wind = effects.wind;
611
+ setF32(16, (wind?.direction.x ?? 0) * (wind?.strength ?? 0));
612
+ setF32(20, (wind?.direction.y ?? 0) * (wind?.strength ?? 0));
613
+ setF32(24, (wind?.direction.z ?? 0) * (wind?.strength ?? 0));
614
+ setF32(28, wind?.gustAmplitude ?? 0);
615
+ setF32(32, wind?.gustDirection.x ?? 0);
616
+ setF32(36, wind?.gustDirection.y ?? 0);
617
+ setF32(40, wind?.gustDirection.z ?? 0);
618
+ setF32(44, wind?.gustFrequency ?? 0);
619
+ setF32(48, wind?.gustSpeed ?? 0);
620
+ setF32(52, turbulence?.strength ?? 0);
621
+ setF32(56, turbulence?.scale ?? 0);
622
+ setF32(60, turbulence?.flowSpeed ?? 0);
623
+ setF32(64, trailDepth);
624
+ setF32(68, planes.length);
625
+ setF32(72, heightfield ? 1 : 0);
626
+ setF32(76, subEmitters.length);
627
+ for (let index = 0; index < GPU_PARTICLE_MAX_PLANES; index += 1) {
628
+ const plane = planes[index];
629
+ const base = 80 + index * 32;
630
+ setF32(base, plane?.normal.x ?? 0);
631
+ setF32(base + 4, plane?.normal.y ?? 1);
632
+ setF32(base + 8, plane?.normal.z ?? 0);
633
+ setF32(base + 12, plane?.constant ?? 0);
634
+ setF32(base + 16, plane?.restitution ?? 0.5);
635
+ setF32(base + 20, plane?.killOnContact ? 1 : 0);
636
+ setF32(base + 24, 0);
637
+ setF32(base + 28, 0);
638
+ }
639
+ const sub0 = subEmitters[0];
640
+ const sub1 = subEmitters[1];
641
+ setF32(176, sub0?.triggerAge ?? 2);
642
+ setF32(180, sub0?.chance ?? 0);
643
+ setF32(184, sub0?.childCount ?? 0);
644
+ setF32(188, sub1?.triggerAge ?? 2);
645
+ setF32(192, sub1?.chance ?? 0);
646
+ setF32(196, sub1?.childCount ?? 0);
647
+ const lighting = effects.lighting;
648
+ setF32(200, lighting ? 1 : 0);
649
+ setF32(204, lighting?.diffuseStrength ?? 0);
650
+ setF32(208, heightfield?.originX ?? 0);
651
+ setF32(212, heightfield?.originZ ?? 0);
652
+ setF32(216, heightfield?.cellSize ?? 1);
653
+ setF32(220, heightfield?.columns ?? 0);
654
+ setF32(224, heightfield?.rows ?? 0);
655
+ setF32(228, heightfield?.restitution ?? 0.4);
656
+ setF32(232, heightfield?.killOnContact ? 1 : 0);
657
+ setF32(236, lighting?.ambient[0] ?? 0);
658
+ setF32(240, lighting?.ambient[1] ?? 0);
659
+ setF32(244, lighting?.ambient[2] ?? 0);
660
+ setF32(248, lighting?.keyDirection.x ?? 0);
661
+ setF32(252, lighting?.keyDirection.y ?? 1);
662
+ setF32(256, lighting?.keyDirection.z ?? 0);
663
+ setF32(260, effects.seed ?? 0);
664
+ setF32(264, curves ? 1 : 0);
665
+ setF32(268, 0);
666
+ const lutWords = turbulence ? 8 ** 3 * 4 : 4;
667
+ const heightWords = heightfield ? heightfield.columns * heightfield.rows : 1;
668
+ const fieldData = new Float32Array(lutWords + heightWords + 32 * 4);
669
+ if (turbulence) {
670
+ fieldData.set(turbulence.lut.slice(0, 8 ** 3 * 4), 0);
671
+ }
672
+ if (heightfield) {
673
+ fieldData.set(heightfield.heights.slice(0, heightfield.columns * heightfield.rows), lutWords);
674
+ }
675
+ if (curves) {
676
+ const curveBase = lutWords + heightWords;
677
+ for (let stop = 0; stop < GPU_PARTICLE_CURVE_STOPS; stop += 1) {
678
+ fieldData[curveBase + stop * 4] = curves.colors?.[stop * 4] ?? 0;
679
+ fieldData[curveBase + stop * 4 + 1] = curves.colors?.[stop * 4 + 1] ?? 0;
680
+ fieldData[curveBase + stop * 4 + 2] = curves.colors?.[stop * 4 + 2] ?? 0;
681
+ fieldData[curveBase + stop * 4 + 3] = curves.colors?.[stop * 4 + 3] ?? 1;
682
+ fieldData[curveBase + (16 + stop) * 4] = curves.sizes?.[stop] ?? 0;
683
+ }
684
+ }
685
+ return {
686
+ uniform: buffer,
687
+ flags,
688
+ trailDepth,
689
+ captureSpawnRequests: subEmitters.length > 0,
690
+ captureAttributes: Boolean(curves?.colors || curves?.sizes || lighting),
691
+ captureTrails,
692
+ fieldData,
693
+ };
694
+ }
695
+ /**
696
+ * Snapshot helper for the base-attributes buffer. Missing lanes default to
697
+ * opaque white / size 1 so lighting without a color module stays sane.
698
+ */
699
+ export function createBaseAttributeSnapshot(baseAttributes, count) {
700
+ const snapshot = new Float32Array(Math.max(count * 8, 4));
701
+ if (!baseAttributes) {
702
+ for (let index = 0; index < count; index += 1) {
703
+ const offset = index * 8;
704
+ snapshot[offset] = 1;
705
+ snapshot[offset + 1] = 1;
706
+ snapshot[offset + 2] = 1;
707
+ snapshot[offset + 3] = 1;
708
+ snapshot[offset + 4] = 1;
709
+ }
710
+ return snapshot;
711
+ }
712
+ snapshot.set(baseAttributes.slice(0, count * 8));
713
+ return snapshot;
714
+ }
715
+ /**
716
+ * Seed the GPU trail ring from the caller's previous result so history
717
+ * accumulates across calls. A missing or short history starts fresh: slot 0
718
+ * takes the live position/age and older slots carry +Infinity age so
719
+ * decoders drop them until real history exists.
720
+ */
721
+ export function snapshotTrailHistory(trailHistory, positions, count, depth) {
722
+ const required = count * depth * 4;
723
+ if (trailHistory && trailHistory.length >= required) {
724
+ return trailHistory.slice(0, Math.max(required, 4));
725
+ }
726
+ return createTrailRingInit(positions, count, depth);
727
+ }
728
+ export function createTrailRingInit(positions, count, depth) {
729
+ const ring = new Float32Array(Math.max(count * depth * 4, 4));
730
+ for (let index = 0; index < count; index += 1) {
731
+ const source = index * 4;
732
+ for (let slot = 0; slot < depth; slot += 1) {
733
+ const target = (index * depth + slot) * 4;
734
+ ring[target] = positions[source] ?? 0;
735
+ ring[target + 1] = positions[source + 1] ?? 0;
736
+ ring[target + 2] = positions[source + 2] ?? 0;
737
+ ring[target + 3] = slot === 0 ? (positions[source + 3] ?? 0) : Number.POSITIVE_INFINITY;
738
+ }
739
+ }
740
+ return ring;
741
+ }
359
742
  function createDefaultParticleComputeShader() {
360
743
  return `
361
744
  struct SimParams {
@@ -382,6 +765,314 @@ fn main(@builtin(global_invocation_id) globalId: vec3<u32>) {
382
765
  positions[index].w = positions[index].w + params.deltaTime;
383
766
  }
384
767
 
768
+ @compute @workgroup_size(${WORKGROUP_SIZE})
769
+ fn spawn_main(@builtin(global_invocation_id) globalId: vec3<u32>) {
770
+ let index = globalId.x;
771
+ if (index >= params.count) {
772
+ return;
773
+ }
774
+
775
+ positions[index] = positions[index];
776
+ velocities[index] = velocities[index];
777
+ }
778
+ `;
779
+ }
780
+ /**
781
+ * Extended A4 compute kernel. Header offsets mirror encodeGPUParticleEffects:
782
+ * deltaTime f32 @0, count u32 @4, time f32 @8, flags u32 @12, then 16 vec4
783
+ * slots (wind, windGust, windTurb, fxCounts, 3x plane pairs, sub0, sub1,
784
+ * height0, height1, light0, light1). Kill signaling: positions w = -1.
785
+ */
786
+ export function createEffectsParticleComputeShader() {
787
+ return `
788
+ struct FxParams {
789
+ deltaTime: f32,
790
+ count: u32,
791
+ time: f32,
792
+ flags: u32,
793
+ wind: vec4<f32>,
794
+ windGust: vec4<f32>,
795
+ windTurb: vec4<f32>,
796
+ fxCounts: vec4<f32>,
797
+ plane0: vec4<f32>,
798
+ plane0Misc: vec4<f32>,
799
+ plane1: vec4<f32>,
800
+ plane1Misc: vec4<f32>,
801
+ plane2: vec4<f32>,
802
+ plane2Misc: vec4<f32>,
803
+ sub0: vec4<f32>,
804
+ sub1: vec4<f32>,
805
+ height0: vec4<f32>,
806
+ height1: vec4<f32>,
807
+ light0: vec4<f32>,
808
+ light1: vec4<f32>,
809
+ };
810
+
811
+ @group(0) @binding(0) var<storage, read_write> positions: array<vec4<f32>>;
812
+ @group(0) @binding(1) var<storage, read_write> velocities: array<vec4<f32>>;
813
+ @group(0) @binding(2) var<storage, read> accelerations: array<vec4<f32>>;
814
+ @group(0) @binding(3) var<uniform> params: FxParams;
815
+ @group(0) @binding(4) var<storage, read> fieldData: array<f32>;
816
+ @group(0) @binding(5) var<storage, read_write> spawnRequests: array<u32>;
817
+ @group(0) @binding(6) var<storage, read_write> trailRing: array<vec4<f32>>;
818
+ @group(0) @binding(7) var<storage, read_write> attributes: array<vec4<f32>>;
819
+ @group(0) @binding(8) var<storage, read> baseAttributes: array<vec4<f32>>;
820
+
821
+ fn aura_wrap8(value: i32) -> u32 {
822
+ return u32(((value % 8) + 8) % 8);
823
+ }
824
+
825
+ fn aura_heights_base() -> u32 {
826
+ if ((params.flags & 2u) != 0u) {
827
+ return 2048u;
828
+ }
829
+ return 4u;
830
+ }
831
+
832
+ fn aura_curves_base() -> u32 {
833
+ var base = aura_heights_base();
834
+ if ((params.flags & 8u) != 0u) {
835
+ base = base + u32(params.height0.w) * u32(params.height1.x);
836
+ } else {
837
+ base = base + 1u;
838
+ }
839
+ return base;
840
+ }
841
+
842
+ fn aura_lut_at(ix: u32, iy: u32, iz: u32) -> vec4<f32> {
843
+ let o = (ix + iy * 8u + iz * 64u) * 4u;
844
+ return vec4<f32>(fieldData[o], fieldData[o + 1u], fieldData[o + 2u], fieldData[o + 3u]);
845
+ }
846
+
847
+ fn aura_height_raw(index: u32) -> f32 {
848
+ return fieldData[aura_heights_base() + index];
849
+ }
850
+
851
+ fn aura_curve(index: u32) -> vec4<f32> {
852
+ let o = aura_curves_base() + index * 4u;
853
+ return vec4<f32>(fieldData[o], fieldData[o + 1u], fieldData[o + 2u], fieldData[o + 3u]);
854
+ }
855
+
856
+ fn aura_sample_turb(p: vec3<f32>) -> vec3<f32> {
857
+ let g = fract(p) * 8.0 - vec3<f32>(0.5);
858
+ let b = floor(g);
859
+ let f = g - b;
860
+ let x0 = aura_wrap8(i32(b.x));
861
+ let y0 = aura_wrap8(i32(b.y));
862
+ let z0 = aura_wrap8(i32(b.z));
863
+ let x1 = aura_wrap8(i32(b.x) + 1);
864
+ let y1 = aura_wrap8(i32(b.y) + 1);
865
+ let z1 = aura_wrap8(i32(b.z) + 1);
866
+ let c000 = aura_lut_at(x0, y0, z0).xyz;
867
+ let c100 = aura_lut_at(x1, y0, z0).xyz;
868
+ let c010 = aura_lut_at(x0, y1, z0).xyz;
869
+ let c110 = aura_lut_at(x1, y1, z0).xyz;
870
+ let c001 = aura_lut_at(x0, y0, z1).xyz;
871
+ let c101 = aura_lut_at(x1, y0, z1).xyz;
872
+ let c011 = aura_lut_at(x0, y1, z1).xyz;
873
+ let c111 = aura_lut_at(x1, y1, z1).xyz;
874
+ return mix(
875
+ mix(mix(c000, c100, f.x), mix(c010, c110, f.x), f.y),
876
+ mix(mix(c001, c101, f.x), mix(c011, c111, f.x), f.y),
877
+ f.z);
878
+ }
879
+
880
+ fn aura_height_at(x: f32, z: f32) -> f32 {
881
+ let cols = params.height0.w;
882
+ let rows = params.height1.x;
883
+ let fx = clamp((x - params.height0.x) / params.height0.z, 0.0, cols - 1.001);
884
+ let fz = clamp((z - params.height0.y) / params.height0.z, 0.0, rows - 1.001);
885
+ let bx = floor(fx);
886
+ let bz = floor(fz);
887
+ let x0 = u32(bx);
888
+ let z0 = u32(bz);
889
+ let tx = fx - bx;
890
+ let tz = fz - bz;
891
+ let stride = u32(cols);
892
+ let h00 = aura_height_raw(z0 * stride + x0);
893
+ let h10 = aura_height_raw(z0 * stride + x0 + 1u);
894
+ let h01 = aura_height_raw((z0 + 1u) * stride + x0);
895
+ let h11 = aura_height_raw((z0 + 1u) * stride + x0 + 1u);
896
+ return h00 * (1.0 - tx) * (1.0 - tz) + h10 * tx * (1.0 - tz) + h01 * (1.0 - tx) * tz + h11 * tx * tz;
897
+ }
898
+
899
+ fn aura_sample_color(t: f32) -> vec4<f32> {
900
+ let x = clamp(t, 0.0, 1.0) * 15.0;
901
+ let i = u32(floor(x));
902
+ let f = x - floor(x);
903
+ return mix(aura_curve(i), aura_curve(min(i + 1u, 15u)), f);
904
+ }
905
+
906
+ fn aura_sample_size(t: f32) -> f32 {
907
+ let x = clamp(t, 0.0, 1.0) * 15.0;
908
+ let i = u32(floor(x));
909
+ let f = x - floor(x);
910
+ return mix(aura_curve(16u + i).x, aura_curve(16u + min(i + 1u, 15u)).x, f);
911
+ }
912
+
913
+ fn aura_apply_light(col: vec3<f32>, vel: vec3<f32>) -> vec3<f32> {
914
+ let speed = length(vel);
915
+ var n = vec3<f32>(0.0, 1.0, 0.0);
916
+ if (speed > 0.0001) {
917
+ n = vel / speed;
918
+ }
919
+ let key = vec3<f32>(params.light0.z, params.light0.w, params.light1.x);
920
+ let facing = max(dot(n, key / length(key)), 0.0);
921
+ let ambient = vec3<f32>(params.height1.w, params.light0.x, params.light0.y);
922
+ return col * (ambient + params.sub1.w * facing);
923
+ }
924
+
925
+ fn aura_hash(index: u32, seed: f32) -> f32 {
926
+ return fract(sin(f32(index) * 12.9898 + seed * 78.233) * 43758.5453);
927
+ }
928
+
929
+ @compute @workgroup_size(${WORKGROUP_SIZE})
930
+ fn main(@builtin(global_invocation_id) globalId: vec3<u32>) {
931
+ let index = globalId.x;
932
+ if (index >= params.count) {
933
+ return;
934
+ }
935
+ let flags = params.flags;
936
+ let dt = params.deltaTime;
937
+ var pos = positions[index].xyz;
938
+ var age = positions[index].w;
939
+ var vel = velocities[index].xyz;
940
+ let lifetime = max(velocities[index].w, 0.000001);
941
+ var dead = false;
942
+
943
+ if ((flags & 1u) != 0u) {
944
+ let along = dot(pos, params.windGust.xyz);
945
+ let gust = 1.0 + params.wind.w * sin(along * params.windGust.w + params.time * params.windTurb.x);
946
+ vel = vel + params.wind.xyz * gust * dt;
947
+ }
948
+
949
+ if ((flags & 2u) != 0u) {
950
+ let samplePos = pos * params.windTurb.z + vec3<f32>(params.time * params.windTurb.w, 0.0, 0.0);
951
+ vel = vel + aura_sample_turb(samplePos) * params.windTurb.y * dt;
952
+ }
953
+
954
+ vel = vel + accelerations[index].xyz * dt;
955
+ pos = pos + vel * dt;
956
+
957
+ if ((flags & 4u) != 0u) {
958
+ let planeCount = u32(params.fxCounts.y);
959
+ if (planeCount >= 1u) {
960
+ let d = dot(params.plane0.xyz, pos) + params.plane0.w;
961
+ if (d < 0.0) {
962
+ if (params.plane0Misc.y > 0.5) {
963
+ dead = true;
964
+ } else {
965
+ pos = pos - params.plane0.xyz * d;
966
+ let vn = dot(vel, params.plane0.xyz);
967
+ if (vn < 0.0) {
968
+ vel = vel + params.plane0.xyz * (-(1.0 + params.plane0Misc.x) * vn);
969
+ }
970
+ }
971
+ }
972
+ }
973
+ if (!dead && planeCount >= 2u) {
974
+ let d = dot(params.plane1.xyz, pos) + params.plane1.w;
975
+ if (d < 0.0) {
976
+ if (params.plane1Misc.y > 0.5) {
977
+ dead = true;
978
+ } else {
979
+ pos = pos - params.plane1.xyz * d;
980
+ let vn = dot(vel, params.plane1.xyz);
981
+ if (vn < 0.0) {
982
+ vel = vel + params.plane1.xyz * (-(1.0 + params.plane1Misc.x) * vn);
983
+ }
984
+ }
985
+ }
986
+ }
987
+ if (!dead && planeCount >= 3u) {
988
+ let d = dot(params.plane2.xyz, pos) + params.plane2.w;
989
+ if (d < 0.0) {
990
+ if (params.plane2Misc.y > 0.5) {
991
+ dead = true;
992
+ } else {
993
+ pos = pos - params.plane2.xyz * d;
994
+ let vn = dot(vel, params.plane2.xyz);
995
+ if (vn < 0.0) {
996
+ vel = vel + params.plane2.xyz * (-(1.0 + params.plane2Misc.x) * vn);
997
+ }
998
+ }
999
+ }
1000
+ }
1001
+ }
1002
+
1003
+ if (!dead && (flags & 8u) != 0u) {
1004
+ let ground = aura_height_at(pos.x, pos.z);
1005
+ if (pos.y < ground) {
1006
+ if (params.height1.z > 0.5) {
1007
+ dead = true;
1008
+ } else {
1009
+ let e = params.height0.z;
1010
+ let dhdx = (aura_height_at(pos.x + e, pos.z) - aura_height_at(pos.x - e, pos.z)) / (2.0 * e);
1011
+ let dhdz = (aura_height_at(pos.x, pos.z + e) - aura_height_at(pos.x, pos.z - e)) / (2.0 * e);
1012
+ let n = vec3<f32>(-dhdx, 1.0, -dhdz) / length(vec3<f32>(-dhdx, 1.0, -dhdz));
1013
+ pos.y = ground;
1014
+ let vn = dot(vel, n);
1015
+ if (vn < 0.0) {
1016
+ vel = vel + n * (-(1.0 + params.height1.y) * vn);
1017
+ }
1018
+ }
1019
+ }
1020
+ }
1021
+
1022
+ if (!dead) {
1023
+ age = age + dt;
1024
+ }
1025
+
1026
+ if (!dead && (flags & 16u) != 0u) {
1027
+ let prevAge = age - dt;
1028
+ let nPrev = prevAge / lifetime;
1029
+ let nNew = age / lifetime;
1030
+ let subCount = u32(params.fxCounts.w);
1031
+ if (subCount >= 1u && nPrev < params.sub0.x && nNew >= params.sub0.x
1032
+ && aura_hash(index, params.light1.y) < params.sub0.y) {
1033
+ spawnRequests[index] = u32(params.sub0.z);
1034
+ }
1035
+ if (subCount >= 2u && nPrev < params.sub0.w && nNew >= params.sub0.w
1036
+ && aura_hash(index, params.light1.y + 1.7) < params.sub1.x) {
1037
+ spawnRequests[index] = spawnRequests[index] + (u32(params.sub1.y) << 16u);
1038
+ }
1039
+ }
1040
+
1041
+ if ((flags & 32u) != 0u || (flags & 64u) != 0u || (flags & 256u) != 0u) {
1042
+ let t = clamp(age / lifetime, 0.0, 1.0);
1043
+ var col = baseAttributes[index * 2u];
1044
+ var size = baseAttributes[index * 2u + 1u].x;
1045
+ if ((flags & 32u) != 0u) {
1046
+ col = aura_sample_color(t);
1047
+ }
1048
+ if ((flags & 256u) != 0u) {
1049
+ size = aura_sample_size(t);
1050
+ }
1051
+ if ((flags & 64u) != 0u) {
1052
+ col = vec4<f32>(aura_apply_light(col.xyz, vel), col.w);
1053
+ }
1054
+ attributes[index * 2u] = col;
1055
+ attributes[index * 2u + 1u] = vec4<f32>(size, 0.0, 1.0, 0.0);
1056
+ }
1057
+
1058
+ if ((flags & 128u) != 0u) {
1059
+ let k = u32(params.fxCounts.x);
1060
+ let base = index * k;
1061
+ var s = k - 1u;
1062
+ while (s >= 1u) {
1063
+ trailRing[base + s] = trailRing[base + s - 1u] + vec4<f32>(0.0, 0.0, 0.0, dt);
1064
+ s = s - 1u;
1065
+ }
1066
+ trailRing[base] = vec4<f32>(pos, age);
1067
+ }
1068
+
1069
+ if (dead) {
1070
+ age = -1.0;
1071
+ }
1072
+ positions[index] = vec4<f32>(pos, age);
1073
+ velocities[index] = vec4<f32>(vel, lifetime);
1074
+ }
1075
+
385
1076
  @compute @workgroup_size(${WORKGROUP_SIZE})
386
1077
  fn spawn_main(@builtin(global_invocation_id) globalId: vec3<u32>) {
387
1078
  let index = globalId.x;