@hology/core 0.0.237 → 0.0.239

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 (90) hide show
  1. package/dist/effects/sequence/sequence-player.js +1 -1
  2. package/dist/effects/vfx/gpu-particle-arena.d.ts +57 -0
  3. package/dist/effects/vfx/gpu-particle-arena.js +4 -0
  4. package/dist/effects/vfx/gpu-sprite-backend.d.ts +33 -0
  5. package/dist/effects/vfx/gpu-sprite-backend.js +4 -0
  6. package/dist/effects/vfx/gpu-sprite-compiler.d.ts +58 -0
  7. package/dist/effects/vfx/gpu-sprite-compiler.js +4 -0
  8. package/dist/effects/vfx/gpu-sprite-render-graph.d.ts +10 -0
  9. package/dist/effects/vfx/gpu-sprite-render-graph.js +4 -0
  10. package/dist/effects/vfx/gpu-sprite-runtime.d.ts +304 -0
  11. package/dist/effects/vfx/gpu-sprite-runtime.js +4 -0
  12. package/dist/effects/vfx/gpu-sprite-scheduler-runtime.d.ts +71 -0
  13. package/dist/effects/vfx/gpu-sprite-scheduler-runtime.js +4 -0
  14. package/dist/effects/vfx/gpu-sprite-template.d.ts +47 -0
  15. package/dist/effects/vfx/gpu-sprite-template.js +4 -0
  16. package/dist/effects/vfx/gpu-sprite-world-runtime.d.ts +61 -0
  17. package/dist/effects/vfx/gpu-sprite-world-runtime.js +4 -0
  18. package/dist/effects/vfx/gpu-vfx-capacity.d.ts +3 -0
  19. package/dist/effects/vfx/gpu-vfx-capacity.js +4 -0
  20. package/dist/effects/vfx/gpu-vfx-scheduler.d.ts +52 -0
  21. package/dist/effects/vfx/gpu-vfx-scheduler.js +4 -0
  22. package/dist/effects/vfx/gpu-vfx-world-service.d.ts +103 -0
  23. package/dist/effects/vfx/gpu-vfx-world-service.js +4 -0
  24. package/dist/effects/vfx/index.d.ts +11 -0
  25. package/dist/effects/vfx/index.js +1 -1
  26. package/dist/effects/vfx/trail-renderer.js +1 -1
  27. package/dist/effects/vfx/vfx-actor.d.ts +19 -2
  28. package/dist/effects/vfx/vfx-actor.js +1 -1
  29. package/dist/effects/vfx/vfx-asset.d.ts +2 -0
  30. package/dist/effects/vfx/vfx-default-sprite-texture.d.ts +3 -0
  31. package/dist/effects/vfx/vfx-default-sprite-texture.js +4 -0
  32. package/dist/effects/vfx/vfx-input-runtime.d.ts +3 -1
  33. package/dist/effects/vfx/vfx-materializer.d.ts +10 -0
  34. package/dist/effects/vfx/vfx-materializer.js +1 -1
  35. package/dist/effects/vfx/vfx-mesh-transform.d.ts +4 -0
  36. package/dist/effects/vfx/vfx-mesh-transform.js +4 -0
  37. package/dist/effects/vfx/vfx-param.d.ts +2 -0
  38. package/dist/effects/vfx/vfx-param.js +1 -1
  39. package/dist/effects/vfx/vfx-renderers.js +1 -1
  40. package/dist/effects/vfx/vfx-runtime.d.ts +35 -0
  41. package/dist/effects/vfx/vfx-runtime.js +4 -0
  42. package/dist/effects/vfx/vfx-service.d.ts +3 -0
  43. package/dist/effects/vfx/vfx-service.js +1 -1
  44. package/dist/gameplay/actors/builtin/components/character/character-animation.d.ts +14 -0
  45. package/dist/gameplay/actors/builtin/components/character/character-animation.js +1 -1
  46. package/dist/gameplay/actors/builtin/components/character/character-movement-like.d.ts +1 -1
  47. package/dist/gameplay/actors/builtin/components/character/character-movement.d.ts +8 -3
  48. package/dist/gameplay/actors/builtin/components/character/character-movement.js +1 -1
  49. package/dist/gameplay/actors/builtin/components/character/root-motion-constraint.d.ts +10 -0
  50. package/dist/gameplay/actors/builtin/components/character/root-motion-constraint.js +1 -1
  51. package/dist/gameplay/ai/behavior-tree/move.d.ts +14 -0
  52. package/dist/gameplay/ai/behavior-tree/move.js +1 -1
  53. package/dist/gameplay/initiate.d.ts +3 -0
  54. package/dist/gameplay/initiate.js +1 -1
  55. package/dist/gameplay/services/physics/physics-system.js +1 -1
  56. package/dist/gameplay/services/render.d.ts +14 -1
  57. package/dist/gameplay/services/render.js +1 -1
  58. package/dist/rendering/webgpu-experimental-backend.d.ts +8 -2
  59. package/dist/rendering/webgpu-experimental-backend.js +1 -1
  60. package/dist/rendering.d.ts +32 -3
  61. package/dist/rendering.js +1 -1
  62. package/dist/scene/asset-resource-loader.js +1 -1
  63. package/dist/scene/materializer.js +1 -1
  64. package/dist/scene/objects/shapes.js +1 -1
  65. package/dist/shader/builtin/decal-standard-shader.d.ts +1 -0
  66. package/dist/shader/builtin/decal-standard-shader.js +1 -1
  67. package/dist/shader/decal-shader.js +1 -1
  68. package/dist/shader-nodes/effects.js +1 -1
  69. package/dist/shader-nodes/material-flags.js +1 -1
  70. package/dist/shader-nodes/particle.d.ts +5 -2
  71. package/dist/shader-nodes/particle.js +1 -1
  72. package/dist/test/asset-resource-loader-skinned-merge.test.d.ts +2 -0
  73. package/dist/test/asset-resource-loader-skinned-merge.test.js +4 -0
  74. package/dist/test/character-root-motion-constraint.test.js +1 -1
  75. package/dist/test/gpu-particle-arena.test.d.ts +2 -0
  76. package/dist/test/gpu-particle-arena.test.js +4 -0
  77. package/dist/test/gpu-sprite-compiler.test.d.ts +2 -0
  78. package/dist/test/gpu-sprite-compiler.test.js +4 -0
  79. package/dist/test/gpu-vfx-scheduler.test.d.ts +2 -0
  80. package/dist/test/gpu-vfx-scheduler.test.js +4 -0
  81. package/dist/test/mesh-clone.test.d.ts +2 -0
  82. package/dist/test/mesh-clone.test.js +4 -0
  83. package/dist/test/sequence-scene-binding.test.js +1 -1
  84. package/dist/test/sequence-vfx.test.js +1 -1
  85. package/dist/test/vfx-mesh-transform.test.d.ts +2 -0
  86. package/dist/test/vfx-mesh-transform.test.js +4 -0
  87. package/dist/utils/mesh.d.ts +7 -1
  88. package/dist/utils/mesh.js +1 -1
  89. package/package.json +17 -5
  90. package/tsconfig.tsbuildinfo +1 -1
@@ -0,0 +1,304 @@
1
+ import * as THREE from 'three';
2
+ import type { RendererCustomPassContext } from '@hology/webgpu-renderer';
3
+ import { type CompiledNodeWebGpuParticle, type NodeShaderMaterial, type WgslExternalBindings } from 'three-shader-graph';
4
+ /** Shader-graph semantic contract used while compiling a GPU sprite material. */
5
+ export declare const gpuSpriteParticleWgslBindings: WgslExternalBindings;
6
+ export type GpuSpriteEmitter = {
7
+ position: THREE.Vector3;
8
+ /** Simulate in effect-local coordinates and transform during rendering. */
9
+ localSpace?: boolean;
10
+ capacity: number;
11
+ rate: {
12
+ type: 'once';
13
+ count: number;
14
+ delay: number;
15
+ } | {
16
+ type: 'continuous';
17
+ count: number;
18
+ interval: number;
19
+ delay: number;
20
+ duration: number;
21
+ };
22
+ life: number;
23
+ /** Minimum sampled lifetime. Omitted for fixed-lifetime emitters. */
24
+ lifeMin?: number;
25
+ positionRadius: number;
26
+ spawnZoneType?: number;
27
+ spawnZoneA?: THREE.Vector4;
28
+ spawnZoneB?: THREE.Vector4;
29
+ velocityMin: THREE.Vector3;
30
+ velocityMax: THREE.Vector3;
31
+ force: THREE.Vector3;
32
+ damping: number;
33
+ mass: number;
34
+ randomDrift: THREE.Vector3;
35
+ randomDriftDelay: number;
36
+ randomDriftEasing: number;
37
+ hasRandomDrift: boolean;
38
+ followParentSpeed: number;
39
+ hasFollowParent: boolean;
40
+ moveToTarget: THREE.Vector3;
41
+ moveToEasing: number;
42
+ hasMoveTo: boolean;
43
+ /** Actual emitter index whose particles own this child emitter's instances. */
44
+ parentEmitter?: number;
45
+ /** Logical particles reserved for each parent-particle child instance. */
46
+ childParticlesPerParent?: number;
47
+ /** 1 = particle birth, 2 = first world collision. */
48
+ childSpawnEvent?: number;
49
+ /** Root emitters are level zero; children are simulated after their parent level. */
50
+ childLevel?: number;
51
+ hasWorldCollision?: boolean;
52
+ collisionBounce?: number;
53
+ collisionFriction?: number;
54
+ collisionLifeLoss?: number;
55
+ /** Diagnostic coloring for screen-space collision inputs and decisions. */
56
+ collisionDebug?: boolean;
57
+ orientAlongVelocity: boolean;
58
+ scaleStart: [number, number];
59
+ scaleEnd: [number, number];
60
+ colorStart: THREE.Color;
61
+ colorEnd: THREE.Color;
62
+ /** Sample colorStart/colorEnd once at birth instead of interpolating by age. */
63
+ randomColor?: boolean;
64
+ alphaStart: number;
65
+ alphaEnd: number;
66
+ rotationMin: number;
67
+ rotationMax: number;
68
+ /** Legacy CPU additive Z rotation applied once per simulation update. */
69
+ spriteRotationStep?: number;
70
+ meshRotation: THREE.Quaternion;
71
+ meshRotationEuler: THREE.Vector3;
72
+ meshRotationStep: THREE.Vector3;
73
+ /** 0 = fixed, 1 = XYZ, 2 = X, 3 = Y, 4 = Z. */
74
+ randomMeshRotationAxis: number;
75
+ radialVelocityDirection: THREE.Vector3;
76
+ radialVelocitySpeed: number;
77
+ radialVelocitySpread: number;
78
+ hasRadialVelocity: boolean;
79
+ attractionTarget: THREE.Vector3;
80
+ attractionForce: number;
81
+ attractionRadius: number;
82
+ hasAttraction: boolean;
83
+ alignRotationAxis: THREE.Vector3;
84
+ alignRotationMode: number;
85
+ alignRotationCenter: THREE.Vector3;
86
+ alignRotationOffset: THREE.Vector3;
87
+ vortexAxis: THREE.Vector3;
88
+ vortexAmount: number;
89
+ hasVortex: boolean;
90
+ disperseDistance: number;
91
+ /** 0 = XYZ, 1 = XY, 2 = XZ, 3 = YZ. */
92
+ dispersePlane: number;
93
+ disperseEasing: number;
94
+ hasDisperse: boolean;
95
+ /** Easing codes for scalar scale, color, opacity, and component scale. */
96
+ curveEasing: THREE.Vector4;
97
+ meshScaleStart: THREE.Vector3;
98
+ meshScaleEnd: THREE.Vector3;
99
+ /** Index into the runtime's render-class table. Defaults to zero. */
100
+ renderClass?: number;
101
+ /** Scheduler-controlled simulation origin. Defaults to zero. */
102
+ startTime?: number;
103
+ /** Inactive slots retain their allocation but do not simulate or draw. */
104
+ active?: boolean;
105
+ /** Internal recycled-slot generation, encoded exactly in the particle state. */
106
+ activationGeneration?: number;
107
+ paused?: boolean;
108
+ pausedTime?: number;
109
+ stopTime?: number;
110
+ /** Maximum particle life remaining after stop; negative preserves natural draining. */
111
+ expireWithin?: number;
112
+ timescale?: number;
113
+ };
114
+ export type GpuSpriteRenderClass = {
115
+ texture: THREE.Texture;
116
+ material?: NodeShaderMaterial;
117
+ lockY?: boolean;
118
+ /** Velocity-proportional sprite length. Undefined selects an ordinary billboard. */
119
+ stretchScale?: number;
120
+ /** Omitted for sprites, which use the built-in six-vertex quad. */
121
+ meshVertices?: Float32Array;
122
+ /** Default source-material tint, stored once per mesh render class. */
123
+ meshColor?: THREE.Color;
124
+ meshOpacity?: number;
125
+ localSpace?: boolean;
126
+ trail?: {
127
+ historyLength: number;
128
+ width: number;
129
+ taper: boolean;
130
+ colorStart: THREE.Color;
131
+ colorEnd: THREE.Color;
132
+ opacityStart: number;
133
+ opacityEnd: number;
134
+ scrollSpeed: number;
135
+ opacityChannel: 'red' | 'alpha' | 'none';
136
+ blendingMode: string;
137
+ /** Camera-facing ribbons store positions only; oriented ribbons also store a side vector per sample. */
138
+ billboard: boolean;
139
+ };
140
+ };
141
+ export type GpuSpriteRuntimeStats = {
142
+ capacity: number;
143
+ aliveParticles: number;
144
+ aliveCountSource: 'gpu-readback' | 'estimate';
145
+ estimatedAliveParticles: number;
146
+ emitterCount: number;
147
+ renderClassCount: number;
148
+ renderDrawCalls: number;
149
+ renderedTriangles: number;
150
+ storageBytes: number;
151
+ aliveIndexCapacity: number;
152
+ aliveIndexBytes: number;
153
+ optionalStateBytes: number;
154
+ computeStorageBufferCount: number;
155
+ aliveByRenderClass: readonly number[];
156
+ activePageCount: number;
157
+ simulatedCapacity: number;
158
+ emitterUploadBytes: number;
159
+ activePageUploadBytes: number;
160
+ particleOwnerUploadBytes: number;
161
+ emitterCommandCount: number;
162
+ emitterCommandUploadBytes: number;
163
+ cumulativeDynamicUploadBytes: number;
164
+ };
165
+ export declare const GPU_SPRITE_EMITTER_BYTES: number;
166
+ export declare const GPU_SPRITE_BASE_COMMAND_BYTES: number;
167
+ export declare const GPU_SPRITE_TRANSFORM_COMMAND_BYTES: number;
168
+ /** WGSL RenderClass: five 16-byte vectors. Exported to guard its CPU packing ABI. */
169
+ export declare const GPU_TRAIL_RENDER_CLASS_BYTES = 80;
170
+ export type GpuSpriteOptionalStateLayout = Readonly<{
171
+ vortexOffset: number;
172
+ disperseOffset: number;
173
+ collisionEventOffset: number;
174
+ trailOffset: number;
175
+ entries: number;
176
+ bytes: number;
177
+ }>;
178
+ /** Layout of the single vec4-addressed optional particle-state storage buffer. */
179
+ export declare function calculateGpuSpriteOptionalStateLayout(options: {
180
+ capacity: number;
181
+ particleTransforms: boolean;
182
+ vortex: boolean;
183
+ disperse: boolean;
184
+ collisionEvents?: boolean;
185
+ trailHistoryEntries: number;
186
+ }): GpuSpriteOptionalStateLayout;
187
+ export declare class GpuSpriteDirtyRange {
188
+ start: number;
189
+ end: number;
190
+ get length(): number;
191
+ mark(index: number): void;
192
+ clear(): void;
193
+ }
194
+ export declare function getCompiledGpuSpriteMaterial(material: NodeShaderMaterial): CompiledNodeWebGpuParticle;
195
+ export declare function getCompiledGpuRenderClassMaterial(renderClass: GpuSpriteRenderClass): CompiledNodeWebGpuParticle | null;
196
+ /** Phase-one GPU sprite runtime: shared storage, fused simulation/compaction, and indirect drawing. */
197
+ export declare class GpuSpriteRuntime {
198
+ readonly emitters: readonly GpuSpriteEmitter[];
199
+ private resource;
200
+ private elapsedTime;
201
+ private deltaTime;
202
+ private pendingSimulationSteps;
203
+ private playing;
204
+ private readonly capacity;
205
+ private readonly emitterData;
206
+ private readonly emitterTransformData;
207
+ private readonly dirtyEmitterTransforms;
208
+ private readonly emitterOffsets;
209
+ private readonly renderClasses;
210
+ private readonly renderClassAliveOffsets;
211
+ private readonly aliveIndexCapacity;
212
+ private readonly sceneTextureReads;
213
+ private readonly writesDepth;
214
+ private actualAliveParticles;
215
+ private aliveByRenderClass;
216
+ private readonly pageSize;
217
+ private readonly activePages;
218
+ private readonly activePageUpload;
219
+ private activeParticleCount;
220
+ private readonly particleOwnerData;
221
+ private readonly dirtyParticleOwnerPages;
222
+ private emitterUploadBytes;
223
+ private activePageUploadBytes;
224
+ private particleOwnerUploadBytes;
225
+ private cumulativeDynamicUploadBytes;
226
+ private readonly pendingEmitterCommands;
227
+ private readonly pendingEmitterCommandBits;
228
+ private readonly pendingEmitterCommandFlags;
229
+ private readonly pendingEmitterCommandIndices;
230
+ private readonly emitterCommandUpload;
231
+ private readonly emitterCommandUploadFloats;
232
+ private readonly emitterCommandUploadBits;
233
+ private readonly emitterCommandFloats;
234
+ private readonly hasLocalTrailTransforms;
235
+ private pendingEmitterCommandCount;
236
+ private emitterCommandCount;
237
+ private emitterCommandUploadBytes;
238
+ private readonly aliveCountReadbackEnabled;
239
+ private readonly trailHistoryEntries;
240
+ private readonly emitterTrailOffsets;
241
+ private readonly hasParticleTransforms;
242
+ private readonly hasVortexState;
243
+ private readonly hasDisperseState;
244
+ private readonly hasCollisionEventState;
245
+ private readonly hasWorldCollision;
246
+ private readonly maximumChildLevel;
247
+ private readonly optionalVortexOffset;
248
+ private readonly optionalDisperseOffset;
249
+ private readonly optionalCollisionEventOffset;
250
+ private readonly optionalTrailOffset;
251
+ private readonly optionalStateEntries;
252
+ constructor(emitters: readonly GpuSpriteEmitter[], textureOrClasses: THREE.Texture | readonly GpuSpriteRenderClass[], material?: NodeShaderMaterial, options?: {
253
+ pageSize?: number;
254
+ capacity?: number;
255
+ aliveCountReadback?: boolean;
256
+ });
257
+ update(deltaTime: number): void;
258
+ setPlaying(playing: boolean): void;
259
+ get hasActiveParticles(): boolean;
260
+ resetTelemetry(): void;
261
+ restart(): void;
262
+ setEmitterPosition(index: number, position: THREE.Vector3): void;
263
+ setEmitterTransform(index: number, transform: THREE.Matrix4): void;
264
+ setEmitterActive(index: number, active: boolean, restart?: boolean): void;
265
+ setEmitterLife(index: number, life: number): void;
266
+ setEmitterVelocity(index: number, velocity: THREE.Vector3): void;
267
+ setEmitterPositionRadius(index: number, radius: number): void;
268
+ setEmitterScale(index: number, scale: number, authoredScale: number, baseStart: readonly [number, number], baseEnd: readonly [number, number]): void;
269
+ setEmitterRotation(index: number, rotationDegrees: THREE.Vector3): void;
270
+ setEmitterRotationStep(index: number, rotationDegrees: THREE.Vector3): void;
271
+ setEmitterBoundValue(index: number, binding: {
272
+ target: string;
273
+ authoredScalar?: number;
274
+ baseScaleStart?: readonly [number, number];
275
+ baseScaleEnd?: readonly [number, number];
276
+ }, value: unknown): boolean;
277
+ queueEmitterPaused(index: number, paused: boolean): void;
278
+ queueEmitterTimescale(index: number, timescale: number): void;
279
+ queueEmitterStop(index: number, expireWithin?: number): void;
280
+ queueEmitterState(index: number, position: THREE.Vector3, active: boolean, restart?: boolean): void;
281
+ setActivePages(pages: Iterable<number>): void;
282
+ mapParticlePages(pages: readonly number[], emitterIndices: readonly number[], emitterCapacities: readonly number[]): void;
283
+ execute(context: RendererCustomPassContext): void;
284
+ executeCompute(context: RendererCustomPassContext): void;
285
+ executeRender(context: RendererCustomPassContext, colorTarget?: string): void;
286
+ getSceneTextureReads(): readonly string[];
287
+ getComputeTextureReads(): readonly string[];
288
+ getStats(): GpuSpriteRuntimeStats;
289
+ dispose(): void;
290
+ private readGpuCounts;
291
+ private writeEmitterData;
292
+ private writeInitialParticleOwners;
293
+ private rebuildActiveParticleSchedule;
294
+ private uploadDirtyParticleOwners;
295
+ private uploadEmitterCommands;
296
+ private writeFrameUniforms;
297
+ private createResource;
298
+ }
299
+ /** Returns the complete render shader used for a compiled particle material. */
300
+ export declare function getGpuSpriteMaterialWgsl(material: CompiledNodeWebGpuParticle, meshOutput?: boolean, localSpace?: boolean): string;
301
+ export declare function getGpuSpriteComputeWgsl(pageSize: number, meshTransforms?: boolean, vortexState?: boolean, disperseState?: boolean, trailState?: boolean, localTrailTransforms?: boolean, randomDrift?: boolean, vortexStateOffset?: number, disperseStateOffset?: number, collision?: boolean, collisionEvents?: boolean, collisionEventOffset?: number, maximumChildLevel?: number): string;
302
+ /** Returns the procedural trail shader used for a compiled custom trail graph. */
303
+ export declare function getGpuTrailMaterialWgsl(material: NodeShaderMaterial, localSpace?: boolean): string;
304
+ //# sourceMappingURL=gpu-sprite-runtime.d.ts.map
@@ -0,0 +1,4 @@
1
+ import*as e from"three";import{buildParticleLightingShaderHeader as t}from"@hology/webgpu-renderer";import{compileNodeWebGpuParticle as i,webGpuRendererBindings as r}from"three-shader-graph";import{particleColorUniformName as n,particleEnergyUniformName as a,particleOpacityUniformName as o,particleTimeUniformName as s,particleVelcoityUniformName as l}from"../../shader-nodes/particle.js";import{depthUniformName as c,farUniformName as u,nearUniformName as p,resolutionUniformName as d}from"../../shader-nodes/depth.js";import{sceneMapUniformName as m}from"../../shader-nodes/scene-sample.js";import{elapsedTimeUniformName as f}from"../../shader-nodes/time.js";const h=(new e.Matrix4).set(1,0,0,0,0,1,0,0,0,0,.5,.5,0,0,0,1),v=new e.Matrix4;export const gpuSpriteParticleWgslBindings={...r,arrays:{...r.arrays},uniformExpressions:{...r.uniformExpressions,[f]:{type:"float",expression:"frame.time"},[n]:{type:"vec3",expression:"gpuParticle.color.rgb"},[a]:{type:"float",expression:"(1.0 - clamp(gpuParticle.positionAge.w / gpuParticle.velocityLife.w, 0.0, 1.0))"},[o]:{type:"float",expression:"gpuParticle.scaleAlphaEmitterGeneration.y"},[s]:{type:"float",expression:"gpuParticle.positionAge.w"},[l]:{type:"vec3",expression:"gpuParticle.velocityLife.xyz"},[d]:{type:"vec2",expression:"frame.viewportNearFar.xy"},[p]:{type:"float",expression:"frame.viewportNearFar.z"},[u]:{type:"float",expression:"frame.viewportNearFar.w"}},sceneTextures:{...r.sceneTextures,[m]:{semantic:"sceneColor",sampleType:"float"},[c]:{semantic:"sceneDepth",sampleType:"depth"}}};const g={...gpuSpriteParticleWgslBindings,uniformExpressions:{...gpuSpriteParticleWgslBindings.uniformExpressions,trailLength:{type:"float",expression:"f32(state.z)"},verticesPerNode:{type:"float",expression:"2.0"},minID:{type:"float",expression:"0.0"},maxID:{type:"float",expression:"f32(activeSegments)"},maxTrailLength:{type:"float",expression:"f32(renderClass.trail.x)"},width:{type:"float",expression:"renderClass.settings.x"},taper:{type:"bool",expression:"renderClass.settings.z > 0.5"}},attributes:{position:{shaderLocation:0,format:"float32x3"},normal:{shaderLocation:1,format:"float32x3"},uv:{shaderLocation:2,format:"float32x2"},color:{shaderLocation:3,format:"float32x4"},nodeID:{shaderLocation:8,format:"float32"},nodeVertexID:{shaderLocation:9,format:"float32"},nodeCenter:{shaderLocation:10,format:"float32x3"},nodeTangent:{shaderLocation:11,format:"float32x3"}}},y=152,x=64;export const GPU_SPRITE_EMITTER_BYTES=608;export const GPU_SPRITE_BASE_COMMAND_BYTES=48;export const GPU_SPRITE_TRANSFORM_COMMAND_BYTES=112;export const GPU_TRAIL_RENDER_CLASS_BYTES=80;export function calculateGpuSpriteOptionalStateLayout(e){const t=Math.max(0,Math.floor(e.capacity)),i=Math.max(0,Math.floor(e.trailHistoryEntries)),r=e.particleTransforms?3*t:0,n=r+(e.vortex?t:0),a=n+(e.disperse?t:0),o=a+(e.collisionEvents?t:0),s=o+i;return{vortexOffset:r,disperseOffset:n,collisionEventOffset:a,trailOffset:o,entries:s,bytes:16*s}}export class GpuSpriteDirtyRange{constructor(){this.start=Number.POSITIVE_INFINITY,this.end=0}get length(){return Math.max(0,this.end-this.start)}mark(e){this.start=Math.min(this.start,e),this.end=Math.max(this.end,e+1)}clear(){this.start=Number.POSITIVE_INFINITY,this.end=0}}const S=new WeakMap,w=new WeakMap;export function getCompiledGpuSpriteMaterial(e){const t=S.get(e);if(t?.version===e.version)return t.compiled;const r=i(e,gpuSpriteParticleWgslBindings);return S.set(e,{version:e.version,compiled:r}),r}function b(e){const t=w.get(e);if(t?.version===e.version)return t.compiled;const r=i(e,g);return w.set(e,{version:e.version,compiled:r}),r}export function getCompiledGpuRenderClassMaterial(e){return null==e.material?null:null==e.trail?getCompiledGpuSpriteMaterial(e.material):b(e.material)}function C(e){return`${e.vertexMain}\n${e.fragmentMain}\n${e.vertexDefinitions}\n${e.fragmentDefinitions}`}function E(e){return!0===e.needsWorldPosition||C(e).includes("input.worldPosition")}function P(e){return!0===e.needsWorldNormal||C(e).includes("input.worldNormal")}function _(e){return!0===e.requiresRendererShaderHeader||/\brenderer[A-Z][A-Za-z0-9_]*\s*\(/.test(C(e))}export class GpuSpriteRuntime{constructor(t,i,r,n={}){this.emitters=t,this.resource=null,this.elapsedTime=0,this.deltaTime=0,this.pendingSimulationSteps=0,this.playing=!0,this.dirtyEmitterTransforms=new GpuSpriteDirtyRange,this.emitterOffsets=[],this.actualAliveParticles=null,this.aliveByRenderClass=[],this.activePages=[],this.activeParticleCount=0,this.emitterUploadBytes=0,this.activePageUploadBytes=0,this.particleOwnerUploadBytes=0,this.cumulativeDynamicUploadBytes=0,this.pendingEmitterCommandCount=0,this.emitterCommandCount=0,this.emitterCommandUploadBytes=0,this.aliveCountReadbackEnabled=!1!==n.aliveCountReadback,this.renderClasses=Array.isArray(i)?i:[{texture:i,material:r}],this.emitterTrailOffsets=new Array(t.length).fill(0);let a=0;for(let e=0;e<t.length;e++){const i=this.renderClasses[t[e].renderClass??0]?.trail;if(null==i)continue;this.emitterTrailOffsets[e]=a;const r=i.billboard?1:2;a+=t[e].capacity*(i.historyLength*r+1)}this.trailHistoryEntries=a,this.hasLocalTrailTransforms=t.some(e=>!0===e.localSpace&&null!=this.renderClasses[e.renderClass??0]?.trail),this.emitterCommandFloats=this.hasLocalTrailTransforms?28:12,this.writesDepth=this.renderClasses.some(e=>!0===getCompiledGpuRenderClassMaterial(e)?.renderState.depthWrite);const o=this.renderClasses.flatMap(e=>null==e.material?[]:Object.values(getCompiledGpuRenderClassMaterial(e).sceneTextures)),s=this.renderClasses.some(e=>{if(null==e.material)return!1;return _(getCompiledGpuRenderClassMaterial(e))});if(this.sceneTextureReads=[...o.includes("sceneColor")?["sceneColorSnapshot"]:[],...o.includes("sceneDepth")?["sceneDepthSnapshot"]:[],...s?["directionalShadow","pointShadow","spotShadow"]:[]],0===this.renderClasses.length)throw new Error("GPU sprite runtime requires at least one render class.");const l=new Array(this.renderClasses.length).fill(0);for(const e of t){const t=e.renderClass??0;if(t<0||t>=this.renderClasses.length)throw new Error(`GPU sprite emitter references invalid render class ${t}.`);l[t]+=e.capacity}const c=new Array(this.renderClasses.length);let u=0;for(let e=0;e<this.renderClasses.length;e++)c[e]=u,u+=l[e];this.renderClassAliveOffsets=c,this.aliveIndexCapacity=u;const p=t.reduce((e,t)=>e+t.capacity,0);if(this.capacity=Math.max(p,Math.floor(n.capacity??p)),this.pageSize=Math.max(1,Math.floor(n.pageSize??Math.max(1,this.capacity))),this.capacity>0&&this.capacity%this.pageSize!==0)throw new Error("GPU sprite runtime capacity must be divisible by its particle page size.");const d=this.renderClasses.some(e=>null!=e.meshVertices),m=this.renderClasses.some(e=>!1===e.trail?.billboard),f=t.some(e=>1!==e.meshScaleStart.x||1!==e.meshScaleStart.y||1!==e.meshScaleStart.z||1!==e.meshScaleEnd.x||1!==e.meshScaleEnd.y||1!==e.meshScaleEnd.z);this.hasParticleTransforms=d||f||m,this.hasVortexState=t.some(e=>e.hasVortex),this.hasDisperseState=t.some(e=>e.hasDisperse),this.hasCollisionEventState=t.some(e=>2===e.childSpawnEvent),this.hasWorldCollision=t.some(e=>e.hasWorldCollision),this.maximumChildLevel=t.reduce((e,t)=>Math.max(e,t.childLevel??0),0);const h=calculateGpuSpriteOptionalStateLayout({capacity:this.capacity,particleTransforms:this.hasParticleTransforms,vortex:this.hasVortexState,disperse:this.hasDisperseState,collisionEvents:this.hasCollisionEventState,trailHistoryEntries:this.trailHistoryEntries});this.optionalVortexOffset=h.vortexOffset,this.optionalDisperseOffset=h.disperseOffset,this.optionalCollisionEventOffset=h.collisionEventOffset,this.optionalTrailOffset=h.trailOffset,this.optionalStateEntries=h.entries;const v=Math.ceil(this.capacity/this.pageSize);this.activePageUpload=new Uint32Array(Math.max(1,this.capacity)),this.particleOwnerData=new Uint32Array(4*Math.max(1,this.capacity)),this.dirtyParticleOwnerPages=new Uint8Array(Math.max(1,v));for(let e=0;e<v;e++)this.activePages.push(e);this.emitterData=new Float32Array(Math.max(1,t.length)*y),this.emitterTransformData=new Float32Array(16*Math.max(1,t.length));const g=(new e.Matrix4).elements;for(let e=0;e<t.length;e++)this.emitterTransformData.set(g,16*e);this.pendingEmitterCommands=new Float32Array(Math.max(1,t.length)*this.emitterCommandFloats),this.pendingEmitterCommandBits=new Uint32Array(this.pendingEmitterCommands.buffer),this.pendingEmitterCommandFlags=new Uint8Array(Math.max(1,t.length)),this.pendingEmitterCommandIndices=new Uint32Array(Math.max(1,t.length)),this.emitterCommandUpload=new ArrayBuffer(Math.max(1,t.length)*this.emitterCommandFloats*4),this.emitterCommandUploadFloats=new Float32Array(this.emitterCommandUpload),this.emitterCommandUploadBits=new Uint32Array(this.emitterCommandUpload),this.writeEmitterData(),this.writeInitialParticleOwners(),this.rebuildActiveParticleSchedule()}update(e){if(!this.playing)return;const t=Math.max(0,Math.min(e,.1));this.deltaTime+=t,this.elapsedTime+=t,t>0&&(this.pendingSimulationSteps+=1)}setPlaying(e){this.playing=e}get hasActiveParticles(){return this.activeParticleCount>0}resetTelemetry(){this.emitterUploadBytes=0,this.activePageUploadBytes=0,this.particleOwnerUploadBytes=0,this.emitterCommandCount=0,this.emitterCommandUploadBytes=0,this.cumulativeDynamicUploadBytes=0}restart(){this.elapsedTime=0,this.deltaTime=0,this.pendingSimulationSteps=0,null!=this.resource&&(this.resource.resetRequested=!0)}setEmitterPosition(e,t){const i=this.emitters[e];null!=i&&(i.position.copy(t),this.writeEmitterData(e),this.resource?.markEmitterDirty(e))}setEmitterTransform(e,t){if(!this.emitters[e]?.localSpace)return;const i=16*e,r=t.elements;let n=!1;for(let e=0;e<16;e++)if(this.emitterTransformData[i+e]!==r[e]){n=!0;break}if(n&&(this.emitterTransformData.set(r,i),this.dirtyEmitterTransforms.mark(e),this.hasLocalTrailTransforms)){const t=this.emitters[e];null!=this.renderClasses[t.renderClass??0]?.trail&&this.queueEmitterState(e,t.position,!1!==t.active)}}setEmitterActive(e,t,i=!1){const r=this.emitters[e];null!=r&&(r.active=t,t&&i&&(r.startTime=this.elapsedTime,r.activationGeneration=(r.activationGeneration??0)%63+1,r.paused=!1,r.pausedTime=0,r.stopTime=-1),this.writeEmitterData(e),this.resource?.markEmitterDirty(e))}setEmitterLife(e,t){const i=this.emitters[e];null!=i&&(i.life=Math.max(1e-4,t),this.writeEmitterData(e),this.resource?.markEmitterDirty(e))}setEmitterVelocity(e,t){const i=this.emitters[e];null!=i&&(i.velocityMin.copy(t),i.velocityMax.copy(t),this.writeEmitterData(e),this.resource?.markEmitterDirty(e))}setEmitterPositionRadius(e,t){const i=this.emitters[e];null!=i&&(i.positionRadius=Math.max(0,t),this.writeEmitterData(e),this.resource?.markEmitterDirty(e))}setEmitterScale(e,t,i,r,n){const a=this.emitters[e];if(null==a||!Number.isFinite(t))return;const o=t/(Math.abs(i)>1e-6?i:1);a.scaleStart=[r[0]*o,r[1]*o],a.scaleEnd=[n[0]*o,n[1]*o],this.writeEmitterData(e),this.resource?.markEmitterDirty(e)}setEmitterRotation(t,i){const r=this.emitters[t];if(null==r)return;const n=i.clone().multiplyScalar(Math.PI/180);null!=this.renderClasses[r.renderClass??0]?.meshVertices?(r.meshRotationEuler.copy(n),r.meshRotation.setFromEuler(new e.Euler(n.x,n.y,n.z))):(r.rotationMin=n.z,r.rotationMax=n.z),this.writeEmitterData(t),this.resource?.markEmitterDirty(t)}setEmitterRotationStep(e,t){const i=this.emitters[e];if(null==i)return;const r=t.clone().multiplyScalar(Math.PI/180*Math.PI/180);null!=this.renderClasses[i.renderClass??0]?.meshVertices?i.meshRotationStep.copy(r):i.spriteRotationStep=r.z,this.writeEmitterData(e),this.resource?.markEmitterDirty(e)}setEmitterBoundValue(t,i,r){const n=this.emitters[t];if(null==n)return!1;const a="number"==typeof r&&Number.isFinite(r)?r:void 0,o=r instanceof e.Vector3?r:void 0,s=r instanceof e.Color?r:void 0,l=i.target;let c=!0;return"initializer:lifetime:duration"===l&&null!=a?n.lifeMin=n.life=Math.max(1e-4,a):"initializer:randomLifetime:minDuration"===l&&null!=a?n.lifeMin=Math.max(1e-4,a):"initializer:randomLifetime:maxDuration"===l&&null!=a?n.life=Math.max(1e-4,a):"initializer:velocity:velocity"===l&&null!=o?(n.velocityMin.copy(o),n.velocityMax.copy(o)):"initializer:randomVelocity:min"===l&&null!=o?n.velocityMin.copy(o):"initializer:randomVelocity:max"===l&&null!=o?n.velocityMax.copy(o):"initializer:randomDirection:speed"===l&&null!=a?(n.velocityMin.setScalar(-a),n.velocityMax.setScalar(a)):"initializer:radialVelocity:speed"===l&&null!=a?n.radialVelocitySpeed=a:"initializer:radialVelocity:direction"===l&&null!=o?n.radialVelocityDirection.copy(o).normalize():"initializer:radialVelocity:spread"===l&&null!=a?n.radialVelocitySpread=e.MathUtils.degToRad(a):"initializer:mass:min"===l&&null!=a?n.mass=Math.max(1e-6,a):l.endsWith(":position")&&null!=o&&null!=n.spawnZoneA?n.spawnZoneA.set(o.x,o.y,o.z,n.spawnZoneA.w):"initializer:positionBox:dimensions"===l&&null!=o?n.spawnZoneB?.set(o.x,o.y,o.z,0):"initializer:positionLine:a"===l&&null!=o?n.spawnZoneA?.set(o.x,o.y,o.z,0):"initializer:positionLine:b"===l&&null!=o?n.spawnZoneB?.set(o.x,o.y,o.z,0):l.endsWith(":radius")&&null!=a?n.positionRadius=Math.max(0,a):"initializer:positionRing:thickness"===l&&null!=a&&null!=n.spawnZoneA?n.spawnZoneA.w=Math.max(a,1e-4):l.endsWith(":angle")&&null!=a&&null!=n.spawnZoneB?n.spawnZoneB.w=e.MathUtils.degToRad(e.MathUtils.clamp(a,0,360)):l.endsWith(":rotation")&&l.startsWith("initializer:position")&&null!=o&&null!=n.spawnZoneB?n.spawnZoneB.set(o.x*Math.PI/180,o.y*Math.PI/180,o.z*Math.PI/180,n.spawnZoneB.w):"initializer:scale:scale"===l&&null!=a&&null!=i.baseScaleStart&&null!=i.baseScaleEnd?this.setEmitterScale(t,a,i.authoredScalar??1,i.baseScaleStart,i.baseScaleEnd):"initializer:randomScale:min"===l&&null!=a?(n.scaleStart=[a,n.scaleStart[1]],n.scaleEnd=[a,n.scaleEnd[1]]):"initializer:randomScale:max"===l&&null!=a?(n.scaleStart=[n.scaleStart[0],a],n.scaleEnd=[n.scaleEnd[0],a]):"initializer:scaleComponents:scale"===l&&null!=o?(n.meshScaleStart.copy(o),n.meshScaleEnd.copy(o)):"initializer:randomColor:a"===l&&null!=s?n.colorStart.copy(s):"initializer:randomColor:b"===l&&null!=s?n.colorEnd.copy(s):"initializer:rotation:rotation"===l&&null!=o?this.setEmitterRotation(t,o):"behaviour:force:force"===l&&null!=o?n.force.copy(o):"behaviour:gravity:gravity"===l&&null!=a?n.force.y=-a:"behaviour:linearDamping:factor"===l&&null!=a?n.damping=a:"behaviour:randomDrift:drift"===l&&null!=o?n.randomDrift.copy(o):"behaviour:randomDrift:delay"===l&&null!=a?n.randomDriftDelay=Math.max(0,a):"behaviour:followParent:speed"===l&&null!=a?n.followParentSpeed=a:"behaviour:disperse:distance"===l&&null!=a?n.disperseDistance=a:"behaviour:scale:a"===l&&null!=a?n.scaleStart=[n.scaleStart[0]*a,n.scaleStart[1]*a]:"behaviour:scale:b"===l&&null!=a?n.scaleEnd=[n.scaleEnd[0]*a,n.scaleEnd[1]*a]:"behaviour:scaleComponents:a"===l&&null!=o?n.meshScaleStart.copy(o):"behaviour:scaleComponents:b"===l&&null!=o?n.meshScaleEnd.copy(o):"behaviour:rotate:rotation"!==l&&"behaviour:rotateAdd:rotation"!==l||null==o?"behaviour:changeColor:a"===l&&null!=s?n.colorStart.copy(s):"behaviour:changeColor:b"===l&&null!=s?n.colorEnd.copy(s):"behaviour:changeOpacity:a"===l&&null!=a?n.alphaStart=a:"behaviour:changeOpacity:b"===l&&null!=a?n.alphaEnd=a:"behaviour:vortex:axis"===l&&null!=o?n.vortexAxis.copy(o):"behaviour:vortex:amount"===l&&null!=a?n.vortexAmount=a:"behaviour:attraction:target"!==l&&"behaviour:repulsion:target"!==l||null==o?"behaviour:attraction:force"!==l&&"behaviour:repulsion:force"!==l||null==a?"behaviour:attraction:radius"!==l&&"behaviour:repulsion:radius"!==l||null==a?"behaviour:moveTo:target"===l&&null!=o?n.moveToTarget.copy(o):c=!1:n.attractionRadius=Math.max(0,a):n.attractionForce=a*(l.includes("repulsion")?-1:1):n.attractionTarget.copy(o):this.setEmitterRotationStep(t,o),!!c&&(this.writeEmitterData(t),this.resource?.markEmitterDirty(t),!0)}queueEmitterPaused(e,t){const i=this.emitters[e];null!=i&&i.paused!==t&&(t?i.pausedTime=Math.max(0,this.elapsedTime-(i.startTime??0))*(i.timescale??1):i.startTime=this.elapsedTime-(i.pausedTime??0)/Math.max(i.timescale??1,1e-4),i.paused=t,this.queueEmitterState(e,i.position,!1!==i.active))}queueEmitterTimescale(e,t){const i=this.emitters[e];if(null==i)return;const r=i.timescale??1,n=!0===i.paused?i.pausedTime??0:Math.max(0,this.elapsedTime-(i.startTime??0))*r;i.timescale=Math.max(1e-4,t),!0===i.paused?i.pausedTime=n:i.startTime=this.elapsedTime-n/i.timescale,this.queueEmitterState(e,i.position,!1!==i.active)}queueEmitterStop(e,t=-1){const i=this.emitters[e];null!=i&&((i.stopTime??-1)<0&&(i.stopTime=!0===i.paused?i.pausedTime??0:Math.max(0,this.elapsedTime-(i.startTime??0))*(i.timescale??1)),i.expireWithin=t<0?-1:Math.max(0,t),this.queueEmitterState(e,i.position,!1!==i.active))}queueEmitterState(e,t,i,r=!1){const n=this.emitters[e];if(null==n)return;n.position.copy(t),n.active=i,i&&r&&(n.startTime=this.elapsedTime,n.activationGeneration=(n.activationGeneration??0)%63+1,n.paused=!1,n.pausedTime=0,n.stopTime=-1,n.expireWithin=-1);const a=e*this.emitterCommandFloats;this.pendingEmitterCommandBits[a]=e,this.pendingEmitterCommandBits[a+1]=i?1:0,this.pendingEmitterCommandBits[a+2]=n.activationGeneration??1,this.pendingEmitterCommands[a+3]=n.expireWithin??-1,this.pendingEmitterCommands[a+4]=t.x,this.pendingEmitterCommands[a+5]=t.y,this.pendingEmitterCommands[a+6]=t.z,this.pendingEmitterCommands[a+7]=n.startTime??0,this.pendingEmitterCommands[a+8]=!0===n.paused?1:0,this.pendingEmitterCommands[a+9]=n.pausedTime??0,this.pendingEmitterCommands[a+10]=n.stopTime??-1,this.pendingEmitterCommands[a+11]=n.timescale??1,this.hasLocalTrailTransforms&&this.pendingEmitterCommands.set(this.emitterTransformData.subarray(16*e,16*e+16),a+12),0===this.pendingEmitterCommandFlags[e]&&(this.pendingEmitterCommandFlags[e]=1,this.pendingEmitterCommandIndices[this.pendingEmitterCommandCount++]=e)}setActivePages(e){const t=Math.ceil(this.capacity/this.pageSize);this.activePages.length=0;for(const i of e){if(!Number.isInteger(i)||i<0||i>=t)throw new Error(`GPU sprite active page ${i} is outside the allocated arena.`);this.activePages.push(i)}this.rebuildActiveParticleSchedule(),null!=this.resource&&(this.resource.activePageUploadRequested=!0)}mapParticlePages(e,t,i){if(t.length!==i.length)throw new Error("GPU sprite particle-owner emitter indices and capacities must have equal lengths.");const r=i.reduce((e,t)=>e+t,0);if(r>e.length*this.pageSize)throw new Error("GPU sprite particle-owner pages do not provide the requested emitter capacity.");let n=0,a=0;const o=e.length*this.pageSize;for(let s=0;s<o;s++){for(;n<i.length&&s>=a+i[n];)a+=i[n],n+=1;const o=e[Math.floor(s/this.pageSize)];if(null==o||o<0||o>=this.dirtyParticleOwnerPages.length)throw new Error(`GPU sprite particle-owner page ${o} is outside the allocated arena.`);const l=4*(o*this.pageSize+s%this.pageSize),c=n<t.length&&s<r;if(this.particleOwnerData[l]=c?t[n]:0,this.particleOwnerData[l+1]=c?s-a:0,this.particleOwnerData[l+2]=c?1:0,c){const r=t[n],c=this.emitters[r];if(null!=c?.parentEmitter&&(c.childParticlesPerParent??0)>0){const r=t.indexOf(c.parentEmitter);if(r>=0){let t=0;for(let e=0;e<r;e++)t+=i[e];const n=s-a,u=t+Math.floor(n/c.childParticlesPerParent),p=e[Math.floor(u/this.pageSize)];this.particleOwnerData[l+3]=p*this.pageSize+u%this.pageSize,this.dirtyParticleOwnerPages[o]=1;continue}}const u=this.renderClasses[this.emitters[r]?.renderClass??0]?.trail?.historyLength??0;this.particleOwnerData[l+3]=u>0?this.optionalTrailOffset+this.emitterTrailOffsets[r]+(s-a)*(u*(!1===this.renderClasses[this.emitters[r]?.renderClass??0]?.trail?.billboard?2:1)+1):0}else this.particleOwnerData[l+3]=0;this.dirtyParticleOwnerPages[o]=1}}execute(e){this.executeCompute(e),this.executeRender(e)}executeCompute(e){if(this.emitterUploadBytes=0,this.activePageUploadBytes=0,this.particleOwnerUploadBytes=0,this.emitterCommandCount=0,this.emitterCommandUploadBytes=0,0===this.capacity||0===this.emitters.length||0===this.activeParticleCount)return this.deltaTime=0,void(this.pendingSimulationSteps=0);const t=this.resource??(this.resource=this.createResource(e));if(t.dirtyEmitters.length>0){const i=t.dirtyEmitters.start*y,r=t.dirtyEmitters.length*y;e.device.queue.writeBuffer(t.emitterBuffer,i*Float32Array.BYTES_PER_ELEMENT,this.emitterData,i,r),this.emitterUploadBytes=r*Float32Array.BYTES_PER_ELEMENT,this.cumulativeDynamicUploadBytes+=this.emitterUploadBytes,t.dirtyEmitters.clear()}if(t.activePageUploadRequested&&(this.activeParticleCount>0&&e.device.queue.writeBuffer(t.activePageBuffer,0,this.activePageUpload,0,this.activeParticleCount),this.activePageUploadBytes=this.activeParticleCount*Uint32Array.BYTES_PER_ELEMENT,this.cumulativeDynamicUploadBytes+=this.activePageUploadBytes,t.activePageUploadRequested=!1),this.uploadDirtyParticleOwners(e,t),this.uploadEmitterCommands(e,t),null!=t.emitterTransformBuffer&&this.dirtyEmitterTransforms.length>0){const i=16*this.dirtyEmitterTransforms.start,r=16*this.dirtyEmitterTransforms.length;e.device.queue.writeBuffer(t.emitterTransformBuffer,4*i,this.emitterTransformData,i,r),this.cumulativeDynamicUploadBytes+=4*r,this.dirtyEmitterTransforms.clear()}this.writeFrameUniforms(e,t.frameData);for(let i=0;i<t.simulationFrameBuffers.length;i++)t.frameData[39]=i,e.device.queue.writeBuffer(t.simulationFrameBuffers[i],0,t.frameData);for(const i of t.renderResources)null!=i.compiledMaterial&&null!=i.materialBuffer&&(i.compiledMaterial.syncLiveUniforms(i.compiledMaterial.uniforms),e.device.queue.writeBuffer(i.materialBuffer,0,U(i.compiledMaterial.uniforms)));const i=e.encoder.beginComputePass({label:"vfx-gpu-sprite-compute-pass"});i.setPipeline(t.clearPipeline),i.setBindGroup(0,t.computeBindGroups[0]);const r=t.getCollisionBindGroup(e);if(null!=r&&i.setBindGroup(1,r),i.dispatchWorkgroups(Math.ceil(this.renderClasses.length/x)),this.emitterCommandCount>0&&(i.setPipeline(t.applyCommandsPipeline),i.dispatchWorkgroups(Math.ceil(this.emitterCommandCount/x))),this.activeParticleCount>0){i.setPipeline(t.simulatePipeline);for(const e of t.computeBindGroups)i.setBindGroup(0,e),i.dispatchWorkgroups(Math.ceil(this.activeParticleCount/x))}i.end(),null==t.readbackBuffer||t.readbackPending||(e.encoder.copyBufferToBuffer(t.indirectBuffer,0,t.readbackBuffer,0,16*this.renderClasses.length),t.readbackPending=!0,setTimeout(()=>{this.readGpuCounts(e.device,t)},0)),t.resetRequested=!1,this.deltaTime=0,this.pendingSimulationSteps=0}executeRender(e,t){if(0===this.capacity||0===this.emitters.length||0===this.activeParticleCount)return;const i=this.resource??(this.resource=this.createResource(e)),r=e.encoder.beginRenderPass({label:"vfx-gpu-sprite-render-pass",colorAttachments:[{view:null==t?e.sceneColorView:e.getColorView(t),loadOp:"load",storeOp:"store"}],depthStencilAttachment:null==e.depthView?void 0:{view:e.depthView,...this.writesDepth?{depthLoadOp:"load",depthStoreOp:"store"}:{depthReadOnly:!0}}});for(let t=0;t<i.renderResources.length;t++){const n=i.renderResources[t];r.setPipeline(n.pipeline),r.setBindGroup(0,n.getBindGroup(e));const a=n.getLightingBindGroup(e);null!=a&&r.setBindGroup(1,a),null!=n.vertexBuffer&&r.setVertexBuffer(0,n.vertexBuffer),r.drawIndirect(i.indirectBuffer,16*t)}r.end()}getSceneTextureReads(){return this.sceneTextureReads}getComputeTextureReads(){return this.hasWorldCollision?["mainDepth","hologyNormalRoughness"]:[]}getStats(){let e=0;for(const t of this.emitters){const i="once"===t.rate.type?t.rate.count:(Math.floor(this.elapsedTime/Math.max(t.rate.interval,1e-4))+1)*t.rate.count,r="once"===t.rate.type?this.elapsedTime<t.life?i:0:Math.min(i,Math.ceil(t.life/Math.max(t.rate.interval,1e-4))*t.rate.count);e+=Math.min(t.capacity,r)}const t=this.optionalStateEntries>0||this.renderClasses.some(e=>null!=e.trail),i=t?Math.max(16,16*this.optionalStateEntries):0,r=this.renderClasses.reduce((e,t,i)=>{const r=null!=t.trail?8*(t.trail.historyLength-1):null==t.meshVertices?2:t.meshVertices.length/24;return e+(this.aliveByRenderClass[i]??0)*r},0),n=16*this.capacity*4+4*Math.max(1,this.aliveIndexCapacity)+4*Math.max(1,this.capacity)+16*Math.max(1,this.capacity)+this.emitterData.byteLength+Math.max(1,this.emitters.length)*this.emitterCommandFloats*4+160+i+16*this.renderClasses.length*(this.aliveCountReadbackEnabled?2:1)+this.renderClasses.reduce((e,t)=>e+(t.meshVertices?.byteLength??0),0)+(this.renderClasses.some(e=>!0===e.localSpace||null!=e.meshVertices)?this.emitterTransformData.byteLength:0);return{capacity:this.capacity,aliveParticles:0===this.activeParticleCount?0:this.actualAliveParticles??e,aliveCountSource:0===this.activeParticleCount||null==this.actualAliveParticles?"estimate":"gpu-readback",estimatedAliveParticles:e,emitterCount:this.emitters.length,renderClassCount:this.renderClasses.length,renderDrawCalls:0===this.activeParticleCount?0:this.renderClasses.length,renderedTriangles:r,storageBytes:n,aliveIndexCapacity:this.aliveIndexCapacity,aliveIndexBytes:4*Math.max(1,this.aliveIndexCapacity),optionalStateBytes:i,computeStorageBufferCount:7+(t?1:0),aliveByRenderClass:this.aliveByRenderClass,activePageCount:this.activePages.length,simulatedCapacity:this.activeParticleCount,emitterUploadBytes:this.emitterUploadBytes,activePageUploadBytes:this.activePageUploadBytes,particleOwnerUploadBytes:this.particleOwnerUploadBytes,emitterCommandCount:this.emitterCommandCount,emitterCommandUploadBytes:this.emitterCommandUploadBytes,cumulativeDynamicUploadBytes:this.cumulativeDynamicUploadBytes}}dispose(){this.resource?.dispose(),this.resource=null}async readGpuCounts(e,t){const i=t.readbackBuffer;if(null!=i)try{if(await e.queue.onSubmittedWorkDone(),t.disposed)return;await i.mapAsync(GPUMapMode.READ);const r=new Uint32Array(i.getMappedRange()),n=Array.from({length:this.renderClasses.length},(e,t)=>r[4*t+1]??0);this.resource!==t||t.disposed||(this.aliveByRenderClass=n,this.actualAliveParticles=n.reduce((e,t)=>e+t,0)),i.unmap()}catch{}finally{t.readbackPending=!1,t.destroyDeferredReadbackBuffer()}}writeEmitterData(e){let t=null==e?0:this.emitterOffsets[e]??0;const i=e??0,r=null==e?this.emitters.length:Math.min(this.emitters.length,e+1);for(let e=i;e<r;e++){const i=this.emitters[e];this.emitterOffsets[e]=t;const r=e*y;this.emitterData.set([i.position.x,i.position.y,i.position.z,i.life,i.velocityMin.x,i.velocityMin.y,i.velocityMin.z,i.damping,i.velocityMax.x,i.velocityMax.y,i.velocityMax.z,i.positionRadius,i.force.x,i.force.y,i.force.z,"once"===i.rate.type?-1:i.rate.interval,i.scaleStart[0],i.scaleStart[1],i.scaleEnd[0],i.scaleEnd[1],i.colorStart.r,i.colorStart.g,i.colorStart.b,i.alphaStart,i.colorEnd.r,i.colorEnd.g,i.colorEnd.b,i.alphaEnd,t,i.capacity,i.rate.count,Math.max(0,Math.min(this.renderClasses.length-1,i.renderClass??0)),i.rotationMin,i.rotationMax,i.startTime??0,!1===i.active?0:i.activationGeneration??1,!0===i.paused?1:0,i.pausedTime??0,i.stopTime??-1,i.timescale??1,i.rate.delay,"continuous"===i.rate.type?i.rate.duration:-1,i.lifeMin??i.life,i.life,i.meshRotation.x,i.meshRotation.y,i.meshRotation.z,i.meshRotation.w,i.meshScaleStart.x,i.meshScaleStart.y,i.meshScaleStart.z,!0===i.randomColor?1:0,i.meshScaleEnd.x,i.meshScaleEnd.y,i.meshScaleEnd.z,i.spawnZoneType??0,i.spawnZoneA?.x??0,i.spawnZoneA?.y??0,i.spawnZoneA?.z??0,i.spawnZoneA?.w??0,i.spawnZoneB?.x??0,i.spawnZoneB?.y??0,i.spawnZoneB?.z??0,i.spawnZoneB?.w??0,i.meshRotationEuler.x,i.meshRotationEuler.y,i.meshRotationEuler.z,i.spriteRotationStep??0,i.meshRotationStep.x,i.meshRotationStep.y,i.meshRotationStep.z,i.randomMeshRotationAxis,i.radialVelocityDirection.x,i.radialVelocityDirection.y,i.radialVelocityDirection.z,i.radialVelocitySpeed,i.radialVelocitySpread,i.hasRadialVelocity?1:0,null==i.parentEmitter?0:i.parentEmitter+1,i.childParticlesPerParent??0,i.attractionTarget.x,i.attractionTarget.y,i.attractionTarget.z,i.attractionForce,i.attractionRadius,i.hasAttraction?1:0,i.childSpawnEvent??0,i.childLevel??0,i.alignRotationAxis.x,i.alignRotationAxis.y,i.alignRotationAxis.z,i.alignRotationMode,i.alignRotationCenter.x,i.alignRotationCenter.y,i.alignRotationCenter.z,0,i.alignRotationOffset.x,i.alignRotationOffset.y,i.alignRotationOffset.z,0,i.vortexAxis.x,i.vortexAxis.y,i.vortexAxis.z,i.vortexAmount,i.hasVortex?1:0,i.hasWorldCollision?1:0,i.collisionBounce??0,i.collisionFriction??0,i.curveEasing.x,i.curveEasing.y,i.curveEasing.z,i.curveEasing.w,i.disperseDistance,i.dispersePlane,i.disperseEasing,i.hasDisperse?1:0,this.optionalTrailOffset+this.emitterTrailOffsets[e],this.renderClasses[i.renderClass??0]?.trail?.historyLength??0,(i.localSpace?1:-1)*(!1===this.renderClasses[i.renderClass??0]?.trail?.billboard?2:1),i.expireWithin??-1,i.mass,i.followParentSpeed,i.hasFollowParent?1:0,i.orientAlongVelocity?1:0,i.randomDrift.x,i.randomDrift.y,i.randomDrift.z,i.randomDriftDelay,i.randomDriftEasing,i.collisionLifeLoss??0,i.collisionDebug?1:0,0,i.moveToTarget.x,i.moveToTarget.y,i.moveToTarget.z,i.hasMoveTo?i.moveToEasing+1:0],r),this.emitterData.set(this.emitterTransformData.subarray(16*e,16*e+16),r+136),t+=i.capacity}}writeInitialParticleOwners(){for(let e=0;e<this.emitters.length;e++){const t=this.emitterOffsets[e],i=this.emitters[e].capacity;for(let r=0;r<i;r++){const i=4*(t+r);this.particleOwnerData[i]=e,this.particleOwnerData[i+1]=r,this.particleOwnerData[i+2]=1;const n=this.emitters[e];if(null!=n.parentEmitter&&(n.childParticlesPerParent??0)>0){this.particleOwnerData[i+3]=this.emitterOffsets[n.parentEmitter]+Math.floor(r/n.childParticlesPerParent);continue}const a=this.renderClasses[this.emitters[e].renderClass??0]?.trail?.historyLength??0;this.particleOwnerData[i+3]=a>0?this.optionalTrailOffset+this.emitterTrailOffsets[e]+r*(a*(!1===this.renderClasses[this.emitters[e]?.renderClass??0]?.trail?.billboard?2:1)+1):0}}}rebuildActiveParticleSchedule(){let e=0;for(const t of this.activePages){const i=t*this.pageSize,r=Math.min(this.capacity,i+this.pageSize);for(let t=i;t<r;t++)0!==this.particleOwnerData[4*t+2]&&(this.activePageUpload[e++]=t)}this.activeParticleCount=e}uploadDirtyParticleOwners(e,t){let i=0;for(;i<this.dirtyParticleOwnerPages.length;){if(0===this.dirtyParticleOwnerPages[i]){i+=1;continue}const r=i;for(;i<this.dirtyParticleOwnerPages.length&&0!==this.dirtyParticleOwnerPages[i];)this.dirtyParticleOwnerPages[i]=0,i+=1;const n=r*this.pageSize*4,a=(i-r)*this.pageSize*4;e.device.queue.writeBuffer(t.particleOwnerBuffer,n*Uint32Array.BYTES_PER_ELEMENT,this.particleOwnerData,n,a),this.particleOwnerUploadBytes+=a*Uint32Array.BYTES_PER_ELEMENT}this.cumulativeDynamicUploadBytes+=this.particleOwnerUploadBytes}uploadEmitterCommands(e,t){const i=this.pendingEmitterCommandCount;for(let e=0;e<i;e++){const t=this.pendingEmitterCommandIndices[e],i=t*this.emitterCommandFloats,r=e*this.emitterCommandFloats;this.emitterCommandUploadBits[r]=this.pendingEmitterCommandBits[i],this.emitterCommandUploadBits[r+1]=this.pendingEmitterCommandBits[i+1],this.emitterCommandUploadBits[r+2]=this.pendingEmitterCommandBits[i+2],this.emitterCommandUploadBits[r+3]=this.pendingEmitterCommandBits[i+3];for(let e=4;e<this.emitterCommandFloats;e++)this.emitterCommandUploadFloats[r+e]=this.pendingEmitterCommands[i+e];this.pendingEmitterCommandFlags[t]=0}this.pendingEmitterCommandCount=0,this.emitterCommandCount=i,this.emitterCommandUploadBytes=i*this.emitterCommandFloats*4,i>0&&(e.device.queue.writeBuffer(t.emitterCommandBuffer,0,this.emitterCommandUpload,0,this.emitterCommandUploadBytes),this.cumulativeDynamicUploadBytes+=this.emitterCommandUploadBytes)}writeFrameUniforms(e,t){const i=e.camera;i.updateMatrixWorld(),t.set(v.multiplyMatrices(h,i.projectionMatrix).elements,0),t.set(i.matrixWorldInverse.elements,16),t[32]=this.elapsedTime,t[33]=this.deltaTime,t[34]=this.capacity,t[35]=!0===this.resource?.resetRequested?1:0,t[36]=this.activeParticleCount,t[37]=this.emitterCommandCount,t[38]=this.pendingSimulationSteps,t[40]=e.width,t[41]=e.height,t[42]=i.near,t[43]=i.far}createResource(t){const i=t.device,r=i.createBuffer({label:"vfx-gpu-particles",size:16*Math.max(1,this.capacity)*4,usage:GPUBufferUsage.STORAGE|GPUBufferUsage.COPY_DST}),n=new Float32Array(16*Math.max(1,this.capacity));for(let e=0;e<this.emitters.length;e++){const t=this.emitterOffsets[e],i=t+this.emitters[e].capacity;for(let r=t;r<i;r++)n[16*r+14]=e}i.queue.writeBuffer(r,0,n);const a=i.createBuffer({label:"vfx-gpu-alive-indices",size:4*Math.max(1,this.aliveIndexCapacity),usage:GPUBufferUsage.STORAGE}),o=i.createBuffer({label:"vfx-gpu-emitters",size:this.emitterData.byteLength,usage:GPUBufferUsage.STORAGE|GPUBufferUsage.COPY_DST}),s=i.createBuffer({label:"vfx-gpu-emitter-commands",size:Math.max(1,this.emitters.length)*this.emitterCommandFloats*4,usage:GPUBufferUsage.STORAGE|GPUBufferUsage.COPY_DST}),l=this.renderClasses.some(e=>null!=e.meshVertices),c=this.emitters.some(e=>1!==e.meshScaleStart.x||1!==e.meshScaleStart.y||1!==e.meshScaleStart.z||1!==e.meshScaleEnd.x||1!==e.meshScaleEnd.y||1!==e.meshScaleEnd.z),u=this.hasParticleTransforms,p=this.hasVortexState,d=this.hasDisperseState,m=this.emitters.some(e=>e.hasRandomDrift),f=this.renderClasses.some(e=>!0===e.localSpace&&null==e.trail),h=this.renderClasses.some(e=>null!=e.trail),v=this.trailHistoryEntries>0,g=l||f,y=this.optionalStateEntries>0||h,x=y?i.createBuffer({label:"vfx-gpu-optional-state",size:Math.max(16,16*this.optionalStateEntries),usage:GPUBufferUsage.STORAGE|GPUBufferUsage.COPY_DST}):null,S=g?i.createBuffer({label:"vfx-gpu-emitter-transforms",size:Math.max(64,this.emitterTransformData.byteLength),usage:GPUBufferUsage.STORAGE|GPUBufferUsage.COPY_DST}):null;null!=S&&i.queue.writeBuffer(S,0,this.emitterTransformData);const w=i.createBuffer({label:"vfx-gpu-active-particle-indices",size:4*Math.max(1,this.capacity),usage:GPUBufferUsage.STORAGE|GPUBufferUsage.COPY_DST}),b=i.createBuffer({label:"vfx-gpu-particle-owners",size:this.particleOwnerData.byteLength,usage:GPUBufferUsage.STORAGE|GPUBufferUsage.COPY_DST}),C=i.createBuffer({label:"vfx-gpu-frame",size:176,usage:GPUBufferUsage.UNIFORM|GPUBufferUsage.COPY_DST}),E=i.createBuffer({label:"vfx-gpu-indirect",size:16*this.renderClasses.length,usage:GPUBufferUsage.STORAGE|GPUBufferUsage.INDIRECT|GPUBufferUsage.COPY_DST|GPUBufferUsage.COPY_SRC}),P=this.aliveCountReadbackEnabled?i.createBuffer({label:"vfx-gpu-indirect-readback",size:16*this.renderClasses.length,usage:GPUBufferUsage.COPY_DST|GPUBufferUsage.MAP_READ}):null;i.queue.writeBuffer(o,0,this.emitterData),this.activeParticleCount>0&&i.queue.writeBuffer(w,0,this.activePageUpload,0,this.activeParticleCount),i.queue.writeBuffer(b,0,this.particleOwnerData),this.dirtyParticleOwnerPages.fill(0);const R=new Uint32Array(4*this.renderClasses.length);for(let e=0;e<this.renderClasses.length;e++){const t=this.renderClasses[e];R[4*e]=null!=t.trail?4*(t.trail.historyLength-1)*6:null==t.meshVertices?6:t.meshVertices.length/8,R[4*e+2]=0,R[4*e+3]=this.renderClassAliveOffsets[e]}i.queue.writeBuffer(E,0,R);const O=i.createShaderModule({label:"vfx-gpu-simulate-wgsl",code:getGpuSpriteComputeWgsl(this.pageSize,u,p,d,v,this.hasLocalTrailTransforms,m,this.optionalVortexOffset,this.optionalDisperseOffset,this.hasWorldCollision,this.hasCollisionEventState,this.optionalCollisionEventOffset,this.maximumChildLevel)}),L=[C];for(let e=1;e<=this.maximumChildLevel;e++)L.push(i.createBuffer({label:`vfx-gpu-frame-child-level-${e}`,size:176,usage:GPUBufferUsage.UNIFORM|GPUBufferUsage.COPY_DST}));const B=i.createBindGroupLayout({label:"vfx-gpu-compute-layout",entries:[{binding:0,visibility:GPUShaderStage.COMPUTE,buffer:{type:"uniform"}},{binding:1,visibility:GPUShaderStage.COMPUTE,buffer:{type:"storage"}},{binding:2,visibility:GPUShaderStage.COMPUTE,buffer:{type:"storage"}},{binding:3,visibility:GPUShaderStage.COMPUTE,buffer:{type:"storage"}},{binding:4,visibility:GPUShaderStage.COMPUTE,buffer:{type:"storage"}},{binding:5,visibility:GPUShaderStage.COMPUTE,buffer:{type:"read-only-storage"}},{binding:6,visibility:GPUShaderStage.COMPUTE,buffer:{type:"read-only-storage"}},{binding:7,visibility:GPUShaderStage.COMPUTE,buffer:{type:"read-only-storage"}},...y?[{binding:8,visibility:GPUShaderStage.COMPUTE,buffer:{type:"storage"}}]:[]]}),N=this.hasWorldCollision?i.createBindGroupLayout({label:"vfx-gpu-collision-layout",entries:[{binding:0,visibility:GPUShaderStage.COMPUTE,texture:{sampleType:"depth"}},{binding:1,visibility:GPUShaderStage.COMPUTE,texture:{sampleType:"float"}}]}):null,W=i.createPipelineLayout({bindGroupLayouts:null==N?[B]:[B,N]}),j=i.createComputePipeline({label:"vfx-gpu-clear",layout:W,compute:{module:O,entryPoint:"clearMain"}}),k=i.createComputePipeline({label:"vfx-gpu-apply-emitter-commands",layout:W,compute:{module:O,entryPoint:"applyEmitterCommandsMain"}}),Y=i.createComputePipeline({label:"vfx-gpu-simulate",layout:W,compute:{module:O,entryPoint:"simulateMain"}}),X=L.map((e,t)=>i.createBindGroup({label:`vfx-gpu-compute-bind-group-${t}`,layout:B,entries:[{binding:0,resource:{buffer:e}},{binding:1,resource:{buffer:o}},{binding:2,resource:{buffer:r}},{binding:3,resource:{buffer:a}},{binding:4,resource:{buffer:E}},{binding:5,resource:{buffer:w}},{binding:6,resource:{buffer:b}},{binding:7,resource:{buffer:s}},...null==x?[]:[{binding:8,resource:{buffer:x}}]]})),Q=this.renderClasses.map((n,o)=>{const s=getCompiledGpuRenderClassMaterial(n),l=null!=s&&_(s),u=null!=n.meshVertices,p=null!=n.trail,d=!u&&!0===n.localSpace,m=!u&&!p&&c,f=p?null==s?(h=!1,F.replace("__EMITTER_TRANSFORM_DECLARATION__",h?"@group(0) @binding(6) var<storage, read> emitterTransforms: array<mat4x4<f32>>;":"").replaceAll("__TRAIL_WORLD_POSITION__",h?"(emitterTransforms[u32(particle.scaleAlphaEmitterGeneration.z)] * vec4":"vec4").replaceAll("vec4(centerLocal, particle)",h?"vec4(centerLocal, 1.0)).xyz":"centerLocal").replaceAll("vec4(beforeLocal, particle)",h?"vec4(beforeLocal, 1.0)).xyz":"beforeLocal").replaceAll("vec4(afterLocal, particle)",h?"vec4(afterLocal, 1.0)).xyz":"afterLocal").replace("__TRAIL_SAMPLER_BINDING__",h?"7":"6").replace("__TRAIL_TEXTURE_BINDING__",h?"8":"7")):$(s,!1):u?null==s?q:V(s):null==s?function(e,t,i){return Z.replace("__EMITTER_TRANSFORM_DECLARATION__",e?"@group(0) @binding(4) var<storage, read> emitterTransforms: array<mat4x4<f32>>;":"").replace("__PARTICLE_TRANSFORM_DECLARATION__",t?`@group(0) @binding(${e?5:4}) var<storage, read> optionalStates: array<vec4<f32>>;`:"").replace("__SPRITE_SAMPLER_BINDING__",String(4+(e?1:0)+(t?1:0))).replace("__SPRITE_TEXTURE_BINDING__",String(5+(e?1:0)+(t?1:0))).replace("__PARTICLE_COMPONENT_SCALE__",t?"optionalStates[aliveIndices[renderClass.data.x + instanceIndex] * 3u + 1u].xy":"vec2<f32>(1.0)").replace("__PARTICLE_WORLD_POSITION__",e?"(emitterTransforms[u32(particle.scaleAlphaEmitterGeneration.z)] * vec4(particle.positionAge.xyz, 1.0)).xyz":"particle.positionAge.xyz").replace("__PARTICLE_WORLD_VELOCITY__",e?"(emitterTransforms[u32(particle.scaleAlphaEmitterGeneration.z)] * vec4(particle.velocityLife.xyz, 0.0)).xyz":"particle.velocityLife.xyz").replace("__SPRITE_CLIP_POSITION__",i?"stretchedSpriteClipPosition(particleWorldPosition, particleWorldVelocity, baseCorner)":"spriteClipPosition(particleWorldPosition, corner)")}(d,m,null!=n.stretchScale):G(s,d,m,null!=n.stretchScale);var h;const v=i.createShaderModule({label:`vfx-gpu-sprite-${o}-wgsl`,code:f}),g=[{binding:0,visibility:GPUShaderStage.VERTEX|GPUShaderStage.FRAGMENT,buffer:{type:"uniform"}},{binding:1,visibility:GPUShaderStage.VERTEX|GPUShaderStage.FRAGMENT,buffer:{type:"read-only-storage"}},{binding:2,visibility:GPUShaderStage.VERTEX|GPUShaderStage.FRAGMENT,buffer:{type:"read-only-storage"}},{binding:3,visibility:GPUShaderStage.VERTEX|GPUShaderStage.FRAGMENT,buffer:{type:"uniform"}}],y=i.createBuffer({label:`vfx-gpu-render-${o}-uniform`,size:p?80:u?32:16,usage:GPUBufferUsage.UNIFORM|GPUBufferUsage.COPY_DST}),w=new ArrayBuffer(p?80:u?32:16);if(new Uint32Array(w,0,4).set([0,u||p?!0===n.localSpace?1:0:!0===n.lockY?1:0,null==n.stretchScale?0:1,0]),u||p||null==n.stretchScale||(new Float32Array(w,12,1)[0]=n.stretchScale),u){const t=n.meshColor??new e.Color(16777215);new Float32Array(w,16,4).set([t.r,t.g,t.b,n.meshOpacity??1])}if(p){const e=n.trail;new Float32Array(w,16,12).set([e.colorStart.r,e.colorStart.g,e.colorStart.b,e.opacityStart,e.colorEnd.r,e.colorEnd.g,e.colorEnd.b,e.opacityEnd,e.width,e.scrollSpeed,e.taper?1:0,"alpha"===e.opacityChannel?1:"none"===e.opacityChannel?2:0]),new Uint32Array(w,64,4).set([e.historyLength,4,e.billboard?1:2,e.billboard?1:0])}i.queue.writeBuffer(y,0,w);let E=p||u?6:4+(d?1:0)+(m?1:0),P=null;if(null!=s&&Object.keys(s.uniforms).length>0){g.push({binding:E++,visibility:GPUShaderStage.VERTEX|GPUShaderStage.FRAGMENT,buffer:{type:"uniform"}});const e=U(s.uniforms);P=i.createBuffer({label:"vfx-gpu-material-uniforms",size:e.byteLength,usage:GPUBufferUsage.UNIFORM|GPUBufferUsage.COPY_DST}),i.queue.writeBuffer(P,0,e)}const T=(null!=s?Object.values(s.textures):u?[]:[n.texture]).map(e=>t.getExternalTexture(e)),R=null==s?[]:Object.entries(s.sceneTextures);p||u?(g.push({binding:4,visibility:GPUShaderStage.VERTEX,buffer:{type:"read-only-storage"}}),g.push({binding:5,visibility:GPUShaderStage.VERTEX,buffer:{type:"read-only-storage"}})):(d&&g.push({binding:4,visibility:GPUShaderStage.VERTEX,buffer:{type:"read-only-storage"}}),m&&g.push({binding:d?5:4,visibility:GPUShaderStage.VERTEX,buffer:{type:"read-only-storage"}}));for(let e=0;e<T.length;e++)g.push({binding:E++,visibility:GPUShaderStage.FRAGMENT,sampler:{type:"filtering"}}),g.push({binding:E++,visibility:GPUShaderStage.FRAGMENT,texture:{sampleType:"float"}});for(const[,e]of R)g.push({binding:E++,visibility:GPUShaderStage.FRAGMENT,sampler:{type:"sceneDepth"===e?"non-filtering":"filtering"}}),g.push({binding:E++,visibility:GPUShaderStage.FRAGMENT,texture:{sampleType:"float"}});const O=i.createBindGroupLayout({label:"vfx-gpu-render-layout",entries:[...g]}),L=l?i.createBindGroupLayout({label:"vfx-gpu-lighting-layout",entries:[{binding:0,visibility:GPUShaderStage.VERTEX|GPUShaderStage.FRAGMENT,buffer:{type:"uniform"}},{binding:1,visibility:GPUShaderStage.FRAGMENT,buffer:{type:"uniform"}},{binding:2,visibility:GPUShaderStage.FRAGMENT,sampler:{type:"comparison"}},{binding:3,visibility:GPUShaderStage.FRAGMENT,texture:{sampleType:"depth",viewDimension:"2d-array"}},{binding:4,visibility:GPUShaderStage.FRAGMENT,sampler:{type:"filtering"}},{binding:5,visibility:GPUShaderStage.FRAGMENT,texture:{sampleType:"float",viewDimension:"2d-array"}},{binding:6,visibility:GPUShaderStage.FRAGMENT,texture:{sampleType:"depth",viewDimension:"2d-array"}}]}):null,B=i.createRenderPipeline({label:`vfx-gpu-sprite-${o}-pipeline`,layout:i.createPipelineLayout({bindGroupLayouts:null==L?[O]:[O,L]}),vertex:{module:v,entryPoint:"vertexMain",buffers:u?[{arrayStride:8*Float32Array.BYTES_PER_ELEMENT,attributes:[{shaderLocation:0,offset:0,format:"float32x3"},{shaderLocation:1,offset:3*Float32Array.BYTES_PER_ELEMENT,format:"float32x3"},{shaderLocation:2,offset:6*Float32Array.BYTES_PER_ELEMENT,format:"float32x2"}]}]:[]},fragment:{module:v,entryPoint:"fragmentMain",targets:[{format:t.colorFormat,blend:null==s?u||p&&"normal"===n.trail.blendingMode?M:z:I(s.renderState)}]},primitive:{topology:"triangle-list",cullMode:u&&null!=s?D(s.renderState):"none",frontFace:"ccw"},depthStencil:null==t.depthView?void 0:{format:"depth32float",depthWriteEnabled:s?.renderState.depthWrite??!1,depthCompare:!1===s?.renderState.depthTest?"always":"less-equal"}}),N=[{binding:0,resource:{buffer:C}},{binding:1,resource:{buffer:r}},{binding:2,resource:{buffer:a}},{binding:3,resource:{buffer:y}}];p?(N.push({binding:4,resource:{buffer:x}}),N.push({binding:5,resource:{buffer:b}})):u?(N.push({binding:4,resource:{buffer:S}}),N.push({binding:5,resource:{buffer:x}})):(d&&N.push({binding:4,resource:{buffer:S}}),m&&N.push({binding:d?5:4,resource:{buffer:x}})),E=p||u?6:4+(d?1:0)+(m?1:0),null!=P&&N.push({binding:E++,resource:{buffer:P}});for(const e of T)N.push({binding:E++,resource:e.sampler}),N.push({binding:E++,resource:e.view});let W=null;return null!=n.meshVertices&&(W=i.createBuffer({label:`vfx-gpu-mesh-${o}-vertices`,size:Math.max(4,n.meshVertices.byteLength),usage:GPUBufferUsage.VERTEX|GPUBufferUsage.COPY_DST}),i.queue.writeBuffer(W,0,n.meshVertices)),new A(B,O,L,N,R.map(([,e])=>e),y,P,s,W)});return new T(r,a,o,C,E,P,w,b,s,S,x,j,k,Y,X,L,Q,N)}}class T{constructor(e,t,i,r,n,a,o,s,l,c,u,p,d,m,f,h,v,g){this.particleBuffer=e,this.aliveIndexBuffer=t,this.emitterBuffer=i,this.frameBuffer=r,this.indirectBuffer=n,this.readbackBuffer=a,this.activePageBuffer=o,this.particleOwnerBuffer=s,this.emitterCommandBuffer=l,this.emitterTransformBuffer=c,this.optionalStateBuffer=u,this.clearPipeline=p,this.applyCommandsPipeline=d,this.simulatePipeline=m,this.computeBindGroups=f,this.simulationFrameBuffers=h,this.renderResources=v,this.collisionLayout=g,this.frameData=new Float32Array(44),this.resetRequested=!0,this.dirtyEmitters=new GpuSpriteDirtyRange,this.activePageUploadRequested=!1,this.readbackPending=!1,this.disposed=!1,this.readbackBufferDestroyed=!1,this.collisionDepthView=null,this.collisionNormalView=null,this.collisionBindGroup=null}getCollisionBindGroup(e){if(null==this.collisionLayout)return null;const t=e.getTextureView("mainDepth"),i=e.getTextureView("hologyNormalRoughness");return null!=this.collisionBindGroup&&t===this.collisionDepthView&&i===this.collisionNormalView||(this.collisionDepthView=t,this.collisionNormalView=i,this.collisionBindGroup=e.device.createBindGroup({label:"vfx-gpu-collision-bind-group",layout:this.collisionLayout,entries:[{binding:0,resource:t},{binding:1,resource:i}]})),this.collisionBindGroup}markEmitterDirty(e){this.dirtyEmitters.mark(e)}dispose(){if(!this.disposed){this.disposed=!0,this.particleBuffer.destroy(),this.aliveIndexBuffer.destroy(),this.emitterBuffer.destroy(),this.frameBuffer.destroy(),this.indirectBuffer.destroy(),this.activePageBuffer.destroy();for(let e=1;e<this.simulationFrameBuffers.length;e++)this.simulationFrameBuffers[e].destroy();this.particleOwnerBuffer.destroy(),this.emitterCommandBuffer.destroy(),this.emitterTransformBuffer?.destroy(),this.optionalStateBuffer?.destroy();for(const e of this.renderResources)e.renderClassBuffer.destroy(),e.materialBuffer?.destroy(),e.vertexBuffer?.destroy();this.destroyDeferredReadbackBuffer()}}destroyDeferredReadbackBuffer(){!this.disposed||this.readbackPending||this.readbackBufferDestroyed||(this.readbackBufferDestroyed=!0,this.readbackBuffer?.destroy())}}class A{constructor(e,t,i,r,n,a,o,s,l){this.pipeline=e,this.bindGroupLayout=t,this.lightingBindGroupLayout=i,this.bindEntries=r,this.sceneTextureSemantics=n,this.renderClassBuffer=a,this.materialBuffer=o,this.compiledMaterial=s,this.vertexBuffer=l,this.bindGroup=null,this.lightingBindGroup=null,this.sceneViews=[],this.lightingResources=[]}getBindGroup(e){const t=this.sceneTextureSemantics.map(t=>e.getTextureView("sceneColor"===t?"sceneColorSnapshot":"sceneDepthSnapshot"));if(null!=this.bindGroup&&t.every((e,t)=>e===this.sceneViews[t]))return this.bindGroup;const i=[...this.bindEntries];let r=0===i.length?0:Math.max(...i.map(e=>e.binding))+1;for(let n=0;n<t.length;n++)i.push({binding:r++,resource:"sceneDepth"===this.sceneTextureSemantics[n]?e.nearestClampSampler:e.linearClampSampler}),i.push({binding:r++,resource:t[n]});return this.sceneViews=t,this.bindGroup=e.device.createBindGroup({label:"vfx-gpu-render-bind-group",layout:this.bindGroupLayout,entries:i}),this.bindGroup}getLightingBindGroup(e){if(null==this.lightingBindGroupLayout)return null;const t=e.getSceneLightingResources(),i=[t.cameraBuffer,t.lightBuffer,t.shadowSampler,t.directionalShadowView,t.linearSampler,t.pointShadowView,t.spotShadowView];return null!=this.lightingBindGroup&&i.every((e,t)=>e===this.lightingResources[t])||(this.lightingResources=i,this.lightingBindGroup=e.device.createBindGroup({label:"vfx-gpu-lighting-bind-group",layout:this.lightingBindGroupLayout,entries:[{binding:0,resource:{buffer:t.cameraBuffer}},{binding:1,resource:{buffer:t.lightBuffer}},{binding:2,resource:t.shadowSampler},{binding:3,resource:t.directionalShadowView},{binding:4,resource:t.linearSampler},{binding:5,resource:t.pointShadowView},{binding:6,resource:t.spotShadowView}]})),this.lightingBindGroup}}const z={color:{srcFactor:"src-alpha",dstFactor:"one",operation:"add"},alpha:{srcFactor:"one",dstFactor:"one",operation:"add"}},M={color:{srcFactor:"src-alpha",dstFactor:"one-minus-src-alpha",operation:"add"},alpha:{srcFactor:"one",dstFactor:"one-minus-src-alpha",operation:"add"}};function I(t){if(t.transparent&&t.blending!==e.NoBlending)switch(t.blending){case e.CustomBlending:return{color:{srcFactor:R(t.blendSrc),dstFactor:R(t.blendDst),operation:O(t.blendEquation)},alpha:{srcFactor:R(t.blendSrcAlpha??t.blendSrc),dstFactor:R(t.blendDstAlpha??t.blendDst),operation:O(t.blendEquationAlpha??t.blendEquation)}};case e.AdditiveBlending:return z;case e.MultiplyBlending:return{color:{srcFactor:"zero",dstFactor:"src",operation:"add"},alpha:{srcFactor:"zero",dstFactor:"one",operation:"add"}};case e.SubtractiveBlending:return{color:{srcFactor:"zero",dstFactor:"one-minus-src",operation:"add"},alpha:{srcFactor:"zero",dstFactor:"one",operation:"add"}};default:return{color:{srcFactor:"src-alpha",dstFactor:"one-minus-src-alpha",operation:"add"},alpha:{srcFactor:"one",dstFactor:"one-minus-src-alpha",operation:"add"}}}}function R(t){switch(t){case e.ZeroFactor:return"zero";case e.OneFactor:return"one";case e.SrcColorFactor:return"src";case e.OneMinusSrcColorFactor:return"one-minus-src";case e.SrcAlphaFactor:return"src-alpha";case e.OneMinusSrcAlphaFactor:return"one-minus-src-alpha";case e.DstColorFactor:return"dst";case e.OneMinusDstColorFactor:return"one-minus-dst";case e.DstAlphaFactor:return"dst-alpha";case e.OneMinusDstAlphaFactor:return"one-minus-dst-alpha";case e.SrcAlphaSaturateFactor:return"src-alpha-saturated";default:return"one"}}function O(t){switch(t){case e.SubtractEquation:return"subtract";case e.ReverseSubtractEquation:return"reverse-subtract";case e.MinEquation:return"min";case e.MaxEquation:return"max";default:return"add"}}function D(t){return t.side===e.DoubleSide?"none":t.side===e.BackSide?"front":"back"}function L(e){let t=0;return{entries:Object.entries(e).map(([e,i])=>{const[r,n]=function(e){switch(e){case"f32":case"i32":case"u32":return[4,4];case"vec2<f32>":return[8,8];case"vec3<f32>":return[16,12];case"vec4<f32>":return[16,16];case"mat3x3<f32>":return[16,48];case"mat4x4<f32>":return[16,64]}}(i.type);t=Math.ceil(t/r)*r;const a={name:e,uniform:i,offset:t};return t+=n,a}),byteLength:Math.max(16,16*Math.ceil(t/16))}}function B(t){return"number"==typeof t?[t]:"boolean"==typeof t?[t?1:0]:Array.isArray(t)?[...t]:t instanceof e.Color?[t.r,t.g,t.b]:"elements"in t?Array.from(t.elements):t instanceof e.Vector2?[t.x,t.y]:t instanceof e.Vector3?[t.x,t.y,t.z]:t instanceof e.Vector4?[t.x,t.y,t.z,t.w]:[]}function U(e){const t=L(e),i=new ArrayBuffer(t.byteLength),r=new DataView(i),n=new Float32Array(i);for(const{uniform:e,offset:i}of t.entries){const t=B(e.value);if("i32"===e.type)r.setInt32(i,t[0]??0,!0);else if("u32"===e.type)r.setUint32(i,t[0]??0,!0);else if("mat3x3<f32>"===e.type)for(let e=0;e<3;e++)n.set(t.slice(3*e,3*e+3),i/4+4*e);else n.set(t,i/4)}return i}function G(e,i,r,n=!1){const a=_(e)?t():"",o=L(e.uniforms).entries,s=o.length>0;let l=4+(i?1:0)+(r?1:0);const c=s?`struct MaterialUniforms {\n${o.map(({name:e,uniform:t})=>` ${e}: ${t.type},`).join("\n")}\n}\n@group(0) @binding(${l++}) var<uniform> materialUniforms: MaterialUniforms;`:"",u=Object.keys(e.textures).map(e=>{const t=e.replace(/[^A-Za-z0-9_]/g,"_");return`@group(0) @binding(${l++}) var ${t}Sampler: sampler;\n@group(0) @binding(${l++}) var ${t}Texture: texture_2d<f32>;`}).join("\n"),p=Object.keys(e.sceneTextures).map(e=>{const t=e.replace(/[^A-Za-z0-9_]/g,"_");return`@group(0) @binding(${l++}) var ${t}Sampler: sampler;\n@group(0) @binding(${l++}) var ${t}Texture: texture_2d<f32>;`}).join("\n"),d=e.varyings.map((e,t)=>` @location(${t}) ${e.name}: vec4<f32>,`).join("\n"),m=e.varyings.map(e=>` output.${e.name} = vec4<f32>(0.0);`).join("\n"),f=e.varyings.flatMap(e=>e.assignments.flatMap(t=>Array.from({length:t.componentCount},(i,r)=>{const n=t.componentOffset+r,a=1===t.componentCount?t.value:`(${t.value})[${r}]`;return` output.${e.name}[${n}] = ${a};`}))).join("\n"),h=e.varyings.length;let v=h+1;const g=E(e),y=P(e),x=g?` @location(${v++}) worldPosition: vec3<f32>,`:"",S=y?` @location(${v++}) worldNormal: vec3<f32>,`:"",w=[null==e.discardExpression?"":`if (${e.discardExpression}) { discard; }`,e.alphaTest>0?`if (graphColor.a < ${e.alphaTest}) { discard; }`:""].filter(Boolean).join("\n ");return`\n${a}\nstruct Frame { projection: mat4x4<f32>, view: mat4x4<f32>, time: f32, delta: f32, capacity: f32, reset: f32, activePageCount: f32, commandCount: f32, padding1: f32, padding2: f32, viewportNearFar: vec4<f32> }\nstruct Particle { positionAge: vec4<f32>, velocityLife: vec4<f32>, color: vec4<f32>, scaleAlphaEmitterGeneration: vec4<f32> }\nstruct MeshParticleTransform { rotation: vec4<f32>, scale: vec4<f32>, rotationEuler: vec4<f32> }\nstruct GraphVertexInput { position: vec3<f32>, normal: vec3<f32>, uv: vec2<f32>, color: vec4<f32> }\nstruct RenderClass { data: vec4<u32> }\nstruct VertexOutput {\n @builtin(position) position: vec4<f32>,\n${d}\n @location(${h}) @interpolate(flat) particleIndex: u32,\n${x}\n${S}\n}\n@group(0) @binding(0) var<uniform> frame: Frame;\n@group(0) @binding(1) var<storage, read> particles: array<Particle>;\n@group(0) @binding(2) var<storage, read> aliveIndices: array<u32>;\n@group(0) @binding(3) var<uniform> renderClass: RenderClass;\n${i?"@group(0) @binding(4) var<storage, read> emitterTransforms: array<mat4x4<f32>>;":""}\n${r?`@group(0) @binding(${i?5:4}) var<storage, read> optionalStates: array<vec4<f32>>;`:""}\n${c}\n${u}\n${p}\n${e.vertexDefinitions}\n${e.fragmentDefinitions}\n\n// Alternative to inverse(viewMatrix)[3].xyz\nfn cameraWorldPositionFromViewMatrix(viewMatrix: mat4x4<f32>) -> vec3<f32> {\n let rotation = mat3x3<f32>(\n viewMatrix[0].xyz,\n viewMatrix[1].xyz,\n viewMatrix[2].xyz\n );\n let translation = viewMatrix[3].xyz;\n return -(transpose(rotation) * translation);\n}\n\nfn spriteClipPosition(worldPosition: vec3<f32>, corner: vec2<f32>) -> vec4<f32> {\n if (renderClass.data.y != 0u) {\n let cameraPosition = cameraWorldPositionFromViewMatrix(frame.view);\n let toCamera = normalize(cameraPosition - worldPosition);\n let right = normalize(cross(vec3<f32>(0.0, 1.0, 0.0), toCamera));\n let billboardPosition = worldPosition + right * corner.x + vec3<f32>(0.0, 1.0, 0.0) * corner.y;\n return frame.projection * frame.view * vec4<f32>(billboardPosition, 1.0);\n }\n let viewPosition = frame.view * vec4<f32>(worldPosition, 1.0);\n return frame.projection * vec4<f32>(viewPosition.xy + corner, viewPosition.zw);\n}\n\nfn stretchedSpriteClipPosition(worldPosition: vec3<f32>, velocity: vec3<f32>, corner: vec2<f32>) -> vec4<f32> {\n let viewPosition = frame.view * vec4<f32>(worldPosition, 1.0);\n let viewVelocity = (frame.view * vec4<f32>(velocity * bitcast<f32>(renderClass.data.w), 0.0)).xyz;\n let speed = length(viewVelocity);\n if (speed < 0.000001) {\n return frame.projection * vec4<f32>(viewPosition.xy + corner, viewPosition.zw);\n }\n let side = normalize(cross(viewPosition.xyz, viewVelocity)) * corner.y;\n let along = viewVelocity * ((corner.x + 0.5) * (1.0 + 1.0 / speed));\n return frame.projection * vec4<f32>(viewPosition.xyz + side - along, viewPosition.w);\n}\n\n@vertex fn vertexMain(@builtin(vertex_index) vertexIndex: u32, @builtin(instance_index) instanceIndex: u32) -> VertexOutput {\n let corners = array<vec2<f32>, 6>(vec2(-0.5,-0.5), vec2(0.5,-0.5), vec2(-0.5,0.5), vec2(-0.5,0.5), vec2(0.5,-0.5), vec2(0.5,0.5));\n let uvs = array<vec2<f32>, 6>(vec2(0,1), vec2(1,1), vec2(0,0), vec2(0,0), vec2(1,1), vec2(1,0));\n let particleIndex = aliveIndices[renderClass.data.x + instanceIndex];\n let gpuParticle = particles[particleIndex];\n let particleWorldPosition = ${i?"(emitterTransforms[u32(gpuParticle.scaleAlphaEmitterGeneration.z)] * vec4(gpuParticle.positionAge.xyz, 1.0)).xyz":"gpuParticle.positionAge.xyz"};\n let input = GraphVertexInput(vec3(corners[vertexIndex], 0.0), vec3(0.0, 0.0, 1.0), uvs[vertexIndex], gpuParticle.color);\n ${e.vertexMain}\n let componentScale = ${r?"optionalStates[particleIndex * 3u + 1u].xy":"vec2<f32>(1.0)"};\n let baseCorner = corners[vertexIndex] * componentScale * gpuParticle.scaleAlphaEmitterGeneration.x;\n let rotation = gpuParticle.color.w;\n let corner = vec2<f32>(\n cos(rotation) * baseCorner.x - sin(rotation) * baseCorner.y,\n sin(rotation) * baseCorner.x + cos(rotation) * baseCorner.y\n );\n var output: VertexOutput;\n let particleWorldVelocity = ${i?"(emitterTransforms[u32(gpuParticle.scaleAlphaEmitterGeneration.z)] * vec4(gpuParticle.velocityLife.xyz, 0.0)).xyz":"gpuParticle.velocityLife.xyz"};\n output.position = ${n?"stretchedSpriteClipPosition(particleWorldPosition, particleWorldVelocity, baseCorner)":"spriteClipPosition(particleWorldPosition, corner)"};\n${m}\n${f}\n output.particleIndex = particleIndex;\n ${g?"output.worldPosition = particleWorldPosition;":""}\n ${y?"output.worldNormal = normalize(cameraWorldPositionFromViewMatrix(frame.view) - particleWorldPosition);":""}\n return output;\n}\n\n@fragment fn fragmentMain(input: VertexOutput) -> @location(0) vec4<f32> {\n let gpuParticle = particles[input.particleIndex];\n ${e.fragmentMain}\n let graphColor = ${e.colorExpression};\n ${w}\n return graphColor;\n}`}function V(e){const i=_(e)?t(!0,"\n let gpuParticle = particles[instanceIndex];\n let particleTransform = loadMeshParticleTransform(instanceIndex);\n let particleScale = particleTransform.scale.xyz * gpuParticle.scaleAlphaEmitterGeneration.x;\n var particleModel = mat4x4<f32>(\n vec4(rotateByQuaternion(vec3(particleScale.x, 0.0, 0.0), particleTransform.rotation), 0.0),\n vec4(rotateByQuaternion(vec3(0.0, particleScale.y, 0.0), particleTransform.rotation), 0.0),\n vec4(rotateByQuaternion(vec3(0.0, 0.0, particleScale.z), particleTransform.rotation), 0.0),\n vec4(gpuParticle.positionAge.xyz, 1.0)\n );\n if (renderClass.data.y != 0u) {\n particleModel = emitterTransforms[u32(gpuParticle.scaleAlphaEmitterGeneration.z)] * particleModel;\n }\n return particleModel;"):"",r=L(e.uniforms).entries;let n=6;const a=r.length>0?`struct MaterialUniforms {\n${r.map(({name:e,uniform:t})=>` ${e}: ${t.type},`).join("\n")}\n}\n@group(0) @binding(${n++}) var<uniform> materialUniforms: MaterialUniforms;`:"",o=Object.keys(e.textures).map(e=>{const t=e.replace(/[^A-Za-z0-9_]/g,"_");return`@group(0) @binding(${n++}) var ${t}Sampler: sampler;\n@group(0) @binding(${n++}) var ${t}Texture: texture_2d<f32>;`}).join("\n"),s=Object.keys(e.sceneTextures).map(e=>{const t=e.replace(/[^A-Za-z0-9_]/g,"_");return`@group(0) @binding(${n++}) var ${t}Sampler: sampler;\n@group(0) @binding(${n++}) var ${t}Texture: texture_2d<f32>;`}).join("\n"),l=e.varyings.map((e,t)=>` @location(${t}) ${e.name}: vec4<f32>,`).join("\n"),c=e.varyings.map(e=>` output.${e.name} = vec4<f32>(0.0);`).join("\n"),u=e.varyings.flatMap(e=>e.assignments.flatMap(t=>Array.from({length:t.componentCount},(i,r)=>{const n=1===t.componentCount?t.value:`(${t.value})[${r}]`;return` output.${e.name}[${t.componentOffset+r}] = ${n};`}))).join("\n"),p=e.varyings.length,d=p+1;let m=d+1;const f=E(e),h=P(e),v=f?` @location(${m++}) worldPosition: vec3<f32>,`:"",g=h?` @location(${m++}) worldNormal: vec3<f32>,`:"",y=[null==e.discardExpression?"":`if (${e.discardExpression}) { discard; }`,e.alphaTest>0?`if (graphColor.a < ${e.alphaTest}) { discard; }`:""].filter(Boolean).join("\n ");return`\n${i}\nstruct Frame { projection: mat4x4<f32>, view: mat4x4<f32>, time: f32, delta: f32, capacity: f32, reset: f32, activePageCount: f32, commandCount: f32, padding1: f32, padding2: f32, viewportNearFar: vec4<f32> }\nstruct Particle { positionAge: vec4<f32>, velocityLife: vec4<f32>, color: vec4<f32>, scaleAlphaEmitterGeneration: vec4<f32> }\nstruct MeshParticleTransform { rotation: vec4<f32>, scale: vec4<f32>, rotationEuler: vec4<f32> }\nstruct GraphVertexInput { position: vec3<f32>, normal: vec3<f32>, uv: vec2<f32>, color: vec4<f32>, instanceIndex: u32 }\nstruct RenderClass { data: vec4<u32>, color: vec4<f32> }\nstruct VertexOutput {\n @builtin(position) position: vec4<f32>,\n${l}\n @location(${p}) @interpolate(flat) particleIndex: u32,\n @location(${d}) @interpolate(flat) instanceIndex: u32,\n${v}\n${g}\n}\n@group(0) @binding(0) var<uniform> frame: Frame;\n@group(0) @binding(1) var<storage, read> particles: array<Particle>;\n@group(0) @binding(2) var<storage, read> aliveIndices: array<u32>;\n@group(0) @binding(3) var<uniform> renderClass: RenderClass;\n@group(0) @binding(4) var<storage, read> emitterTransforms: array<mat4x4<f32>>;\n@group(0) @binding(5) var<storage, read> optionalStates: array<vec4<f32>>;\n${a}\n${o}\n${s}\n${e.vertexDefinitions}\n${e.fragmentDefinitions}\n\nfn rotateByQuaternion(value: vec3<f32>, quaternion: vec4<f32>) -> vec3<f32> {\n return value + 2.0 * cross(quaternion.xyz, cross(quaternion.xyz, value) + quaternion.w * value);\n}\n\nfn loadMeshParticleTransform(particleIndex: u32) -> MeshParticleTransform {\n let offset = particleIndex * 3u;\n return MeshParticleTransform(optionalStates[offset], optionalStates[offset + 1u], optionalStates[offset + 2u]);\n}\n\nfn normalMatrixFromLinearTransform(transform: mat3x3<f32>) -> mat3x3<f32> {\n let cofactor0 = cross(transform[1], transform[2]);\n let determinant = dot(transform[0], cofactor0);\n let safeDeterminant = select(\n select(-0.0000001, 0.0000001, determinant >= 0.0),\n determinant,\n abs(determinant) >= 0.0000001\n );\n return mat3x3<f32>(\n cofactor0,\n cross(transform[2], transform[0]),\n cross(transform[0], transform[1])\n ) * (1.0 / safeDeterminant);\n}\n\n@vertex fn vertexMain(\n @location(0) position: vec3<f32>, @location(1) normal: vec3<f32>, @location(2) uv: vec2<f32>,\n @builtin(instance_index) instanceIndex: u32,\n) -> VertexOutput {\n let particleIndex = aliveIndices[renderClass.data.x + instanceIndex];\n let gpuParticle = particles[particleIndex];\n let meshTransform = loadMeshParticleTransform(particleIndex);\n // External textures use the same renderer cache as ordinary meshes, so\n // authored Three.js UVs pass through unchanged at this ABI boundary.\n let input = GraphVertexInput(position, normal, uv, gpuParticle.color, particleIndex);\n ${e.vertexMain}\n let scaled = position * meshTransform.scale.xyz * gpuParticle.scaleAlphaEmitterGeneration.x;\n let rotated = rotateByQuaternion(scaled, meshTransform.rotation);\n var worldPosition = vec4<f32>(gpuParticle.positionAge.xyz + rotated, 1.0);\n if (renderClass.data.y != 0u) { worldPosition = emitterTransforms[u32(gpuParticle.scaleAlphaEmitterGeneration.z)] * worldPosition; }\n var output: VertexOutput;\n output.position = frame.projection * frame.view * worldPosition;\n${c}\n${u}\n output.particleIndex = particleIndex;\n output.instanceIndex = particleIndex;\n ${f?"output.worldPosition = worldPosition.xyz;":""}\n ${h?"\n var worldNormal = rotateByQuaternion(normal / max(abs(meshTransform.scale.xyz), vec3<f32>(0.00001)), meshTransform.rotation);\n if (renderClass.data.y != 0u) {\n let emitterTransform = emitterTransforms[u32(gpuParticle.scaleAlphaEmitterGeneration.z)];\n let emitterLinearTransform = mat3x3<f32>(\n emitterTransform[0].xyz,\n emitterTransform[1].xyz,\n emitterTransform[2].xyz\n );\n let emitterNormalMatrix = normalMatrixFromLinearTransform(emitterLinearTransform);\n worldNormal = emitterNormalMatrix * worldNormal;\n }\n output.worldNormal = normalize(worldNormal);":""}\n return output;\n}\n\n@fragment fn fragmentMain(input: VertexOutput) -> @location(0) vec4<f32> {\n let gpuParticle = particles[input.particleIndex];\n ${e.fragmentMain}\n let graphColor = ${e.colorExpression};\n ${y}\n return graphColor;\n}`}export function getGpuSpriteMaterialWgsl(e,t=!1,i=!1){return t?V(e):G(e,i,!1)}const N="\nstruct Frame { projection: mat4x4<f32>, view: mat4x4<f32>, time: f32, delta: f32, capacity: f32, reset: f32, activePageCount: f32, commandCount: f32, padding1: f32, padding2: f32, viewportNearFar: vec4<f32> }\nstruct Emitter {\n positionLife: vec4<f32>, velocityMinDamping: vec4<f32>, velocityMaxRadius: vec4<f32>, forceInterval: vec4<f32>,\n scales: vec4<f32>, colorStart: vec4<f32>, colorEnd: vec4<f32>, allocation: vec4<f32>, sprite: vec4<f32>, timing: vec4<f32>, emission: vec4<f32>,\n meshRotation: vec4<f32>, meshScaleStart: vec4<f32>, meshScaleEnd: vec4<f32>,\n spawnZoneA: vec4<f32>, spawnZoneB: vec4<f32>,\n rotationInitialSpriteStep: vec4<f32>, rotationStep: vec4<f32>,\n radialVelocity: vec4<f32>, radialMetadata: vec4<f32>,\n attraction: vec4<f32>, attractionMetadata: vec4<f32>,\n alignRotationAxisMode: vec4<f32>, alignRotationCenter: vec4<f32>, alignRotationOffset: vec4<f32>,\n vortex: vec4<f32>, vortexMetadata: vec4<f32>, curveEasing: vec4<f32>, disperse: vec4<f32>, trail: vec4<f32>,\n dynamics: vec4<f32>, randomDrift: vec4<f32>, randomDriftMetadata: vec4<f32>, moveTo: vec4<f32>,\n trailTransform0: vec4<f32>, trailTransform1: vec4<f32>, trailTransform2: vec4<f32>, trailTransform3: vec4<f32>\n}\n\nstruct Particle { positionAge: vec4<f32>, velocityLife: vec4<f32>, color: vec4<f32>, scaleAlphaEmitterGeneration: vec4<f32> }\nstruct IndirectArgs { vertexCount: u32, instanceCount: atomic<u32>, firstVertex: u32, firstInstance: u32 }\nstruct ParticleOwner { emitterIndex: u32, localSlot: u32, enabled: u32, padding: u32 }\nstruct EmitterCommand {\n header: vec4<u32>, positionStart: vec4<f32>, timing: vec4<f32>,\n __EMITTER_COMMAND_TRANSFORM_FIELDS__\n}\n@group(0) @binding(0) var<uniform> frame: Frame;\n@group(0) @binding(1) var<storage, read_write> emitters: array<Emitter>;\n@group(0) @binding(2) var<storage, read_write> particles: array<Particle>;\n@group(0) @binding(3) var<storage, read_write> aliveIndices: array<u32>;\n@group(0) @binding(4) var<storage, read_write> indirect: array<IndirectArgs>;\n@group(0) @binding(5) var<storage, read> activeParticleIndices: array<u32>;\n@group(0) @binding(6) var<storage, read> particleOwners: array<ParticleOwner>;\n@group(0) @binding(7) var<storage, read> emitterCommands: array<EmitterCommand>;\n__MESH_TRANSFORM_DECLARATION__\n__COLLISION_DECLARATION__\n\nfn hash(value: u32) -> f32 {\n var x = value;\n x = ((x >> 16u) ^ x) * 0x45d9f3bu; x = ((x >> 16u) ^ x) * 0x45d9f3bu; x = (x >> 16u) ^ x;\n return f32(x) / 4294967295.0;\n}\nfn random3(seed: u32) -> vec3<f32> { return vec3(hash(seed), hash(seed + 1013u), hash(seed + 7919u)); }\n\nfn trailWorldPosition(emitter: Emitter, position: vec3<f32>) -> vec3<f32> {\n if (emitter.trail.z < 0.0) { return position; }\n let transform = mat4x4<f32>(\n emitter.trailTransform0, emitter.trailTransform1,\n emitter.trailTransform2, emitter.trailTransform3\n );\n return (transform * vec4(position, 1.0)).xyz;\n}\nfn trailWorldDirection(emitter: Emitter, direction: vec3<f32>) -> vec3<f32> {\n if (emitter.trail.z < 0.0) { return direction; }\n let transform = mat4x4<f32>(\n emitter.trailTransform0, emitter.trailTransform1,\n emitter.trailTransform2, emitter.trailTransform3\n );\n return normalize((transform * vec4(direction, 0.0)).xyz);\n}\n\nfn rotateSpawnPoint(value: vec3<f32>, rotation: vec3<f32>) -> vec3<f32> {\n let cosX = cos(rotation.x); let sinX = sin(rotation.x);\n let afterX = vec3(value.x, value.y * cosX - value.z * sinX, value.y * sinX + value.z * cosX);\n let cosY = cos(rotation.y); let sinY = sin(rotation.y);\n let afterY = vec3(afterX.x * cosY + afterX.z * sinY, afterX.y, -afterX.x * sinY + afterX.z * cosY);\n let cosZ = cos(rotation.z); let sinZ = sin(rotation.z);\n return vec3(afterY.x * cosZ - afterY.y * sinZ, afterY.x * sinZ + afterY.y * cosZ, afterY.z);\n}\n\nfn quaternionFromEulerXYZ(rotation: vec3<f32>) -> vec4<f32> {\n let c = cos(rotation * 0.5);\n let s = sin(rotation * 0.5);\n return vec4(\n s.x * c.y * c.z + c.x * s.y * s.z,\n c.x * s.y * c.z - s.x * c.y * s.z,\n c.x * c.y * s.z + s.x * s.y * c.z,\n c.x * c.y * c.z - s.x * s.y * s.z\n );\n}\n\nfn quaternionMultiply(a: vec4<f32>, b: vec4<f32>) -> vec4<f32> {\n return vec4(\n a.x * b.w + a.w * b.x + a.y * b.z - a.z * b.y,\n a.y * b.w + a.w * b.y + a.z * b.x - a.x * b.z,\n a.z * b.w + a.w * b.z + a.x * b.y - a.y * b.x,\n a.w * b.w - a.x * b.x - a.y * b.y - a.z * b.z\n );\n}\n\nfn quaternionFromUnitVectors(sourceDirection: vec3<f32>, targetDirection: vec3<f32>) -> vec4<f32> {\n var real = dot(sourceDirection, targetDirection) + 1.0;\n var imaginary: vec3<f32>;\n if (real < 0.000001) {\n real = 0.0;\n imaginary = select(\n vec3(-sourceDirection.y, sourceDirection.x, 0.0),\n vec3(0.0, -sourceDirection.z, sourceDirection.y),\n abs(sourceDirection.x) > abs(sourceDirection.z)\n );\n } else {\n imaginary = cross(sourceDirection, targetDirection);\n }\n return normalize(vec4(imaginary, real));\n}\n\nfn eulerXYZFromQuaternion(q: vec4<f32>) -> vec3<f32> {\n return vec3(\n atan2(2.0 * (q.x * q.w - q.y * q.z), q.w * q.w - q.x * q.x - q.y * q.y + q.z * q.z),\n asin(clamp(2.0 * (q.x * q.z + q.y * q.w), -1.0, 1.0)),\n atan2(2.0 * (q.z * q.w - q.x * q.y), q.w * q.w + q.x * q.x - q.y * q.y - q.z * q.z)\n );\n}\n\nfn rotateAroundAxis(value: vec3<f32>, axis: vec3<f32>, angle: f32) -> vec3<f32> {\n let cosine = cos(angle);\n let sine = sin(angle);\n return value * cosine + cross(axis, value) * sine + axis * dot(axis, value) * (1.0 - cosine);\n}\n\nfn applyAxisAngleCpuParity(value: vec3<f32>, axis: vec3<f32>, angle: f32) -> vec3<f32> {\n let halfAngle = angle * 0.5;\n let quaternion = vec4(axis * sin(halfAngle), cos(halfAngle));\n let intermediate = vec4(\n quaternion.w * value.x + quaternion.y * value.z - quaternion.z * value.y,\n quaternion.w * value.y + quaternion.z * value.x - quaternion.x * value.z,\n quaternion.w * value.z + quaternion.x * value.y - quaternion.y * value.x,\n -quaternion.x * value.x - quaternion.y * value.y - quaternion.z * value.z\n );\n return vec3(\n intermediate.x * quaternion.w + intermediate.w * -quaternion.x + intermediate.y * -quaternion.z - intermediate.z * -quaternion.y,\n intermediate.y * quaternion.w + intermediate.w * -quaternion.y + intermediate.z * -quaternion.x - intermediate.x * -quaternion.z,\n intermediate.z * quaternion.w + intermediate.w * -quaternion.z + intermediate.x * -quaternion.y - intermediate.y * -quaternion.x\n );\n}\n\nfn applyEasing(value: f32, easingCode: u32) -> f32 {\n if (easingCode == 1u) { return value * value; }\n if (easingCode == 2u) { return 1.0 - (value - 1.0) * (value - 1.0); }\n if (easingCode == 3u) { return select(0.5 * pow(value * 2.0, 2.0), -0.5 * ((value * 2.0 - 2.0) * (value * 2.0 - 2.0) - 2.0), value >= 0.5); }\n if (easingCode == 4u) { return value * value * value; }\n if (easingCode == 5u) { let shifted = value - 1.0; return shifted * shifted * shifted + 1.0; }\n if (easingCode == 6u) {\n let doubled = value * 2.0;\n let shifted = doubled - 2.0;\n return select(0.5 * doubled * doubled * doubled, 0.5 * (shifted * shifted * shifted + 2.0), value >= 0.5);\n }\n if (easingCode == 7u) { return pow(value, 4.0); }\n if (easingCode == 8u) { let shifted = value - 1.0; let squared = shifted * shifted; return 1.0 - squared * squared; }\n if (easingCode == 9u) {\n let doubled = value * 2.0;\n let shifted = doubled - 2.0;\n let inSquared = doubled * doubled;\n let outSquared = shifted * shifted;\n return select(0.5 * inSquared * inSquared, -0.5 * (outSquared * outSquared - 2.0), value >= 0.5);\n }\n if (easingCode == 10u) { return 1.0 - cos(value * 1.5707963267948966); }\n if (easingCode == 11u) { return sin(value * 1.5707963267948966); }\n if (easingCode == 12u) { return -0.5 * (cos(3.141592653589793 * value) - 1.0); }\n if (easingCode == 13u) { return select(pow(2.0, 10.0 * (value - 1.0)), 0.0, value == 0.0); }\n if (easingCode == 14u) { return select(1.0 - pow(2.0, -10.0 * value), 1.0, value == 1.0); }\n if (easingCode == 15u) {\n if (value == 0.0 || value == 1.0) { return value; }\n return select(0.5 * pow(2.0, 10.0 * (value * 2.0 - 1.0)), 0.5 * (-pow(2.0, -10.0 * (value * 2.0 - 1.0)) + 2.0), value >= 0.5);\n }\n if (easingCode == 16u) { return 1.0 - sqrt(max(0.0, 1.0 - value * value)); }\n if (easingCode == 17u) { let s = 1.70158; return value * value * ((s + 1.0) * value - s); }\n if (easingCode == 18u) { let shifted = value - 1.0; let s = 1.70158; return shifted * shifted * ((s + 1.0) * shifted + s) + 1.0; }\n if (easingCode == 19u) { return sin(3.141592653589793 * value); }\n if (easingCode == 20u) { return pow(sin(3.141592653589793 * value), 2.0); }\n if (easingCode == 21u) { let centered = 2.0 * value - 1.0; let squared = centered * centered; return max(0.0, 1.0 - squared * squared); }\n if (easingCode == 22u) { return exp(-20.0 * pow(value - 0.2, 2.0)) * (1.0 - value); }\n if (easingCode == 23u) { let shifted = value - 1.0; return sqrt(max(0.0, 1.0 - shifted * shifted)); }\n if (easingCode == 24u) {\n let doubled = value * 2.0;\n return select(-0.5 * (sqrt(max(0.0, 1.0 - doubled * doubled)) - 1.0), 0.5 * (sqrt(max(0.0, 1.0 - (doubled - 2.0) * (doubled - 2.0))) + 1.0), value >= 0.5);\n }\n if (easingCode == 25u) {\n let doubled = value * 2.0;\n let s = 1.70158 * 1.525;\n return select(0.5 * doubled * doubled * ((s + 1.0) * doubled - s), 0.5 * ((doubled - 2.0) * (doubled - 2.0) * ((s + 1.0) * (doubled - 2.0) + s) + 2.0), value >= 0.5);\n }\n if (easingCode == 26u) { return select(pow(value * 2.0, 3.0), pow(2.0 * (1.0 - value), 0.5), value >= 0.5); }\n if (easingCode == 27u) { return select(pow(2.0 * value, 1.5), pow(2.0 * (1.0 - value), 6.0), value >= 0.5); }\n return value;\n}\n\nfn radialVelocity(emitter: Emitter, seed: u32) -> vec3<f32> {\n let direction = emitter.radialVelocity.xyz;\n let normal = normalize(select(\n vec3(-direction.y / select(1.0, direction.x, direction.x != 0.0), 1.0, 1.0),\n select(\n vec3(1.0, 0.0, 1.0),\n vec3(1.0, 1.0, 0.0),\n direction.y == 0.0 && direction.z != 0.0\n ),\n direction.x == 0.0\n ));\n let spreadDirection = rotateAroundAxis(\n direction * emitter.radialVelocity.w,\n normal,\n emitter.radialMetadata.x * hash(seed + 127u)\n );\n return rotateAroundAxis(spreadDirection, direction, 6.283185307179586 * hash(seed + 131u));\n}\n\n@compute @workgroup_size(64)\nfn clearMain(@builtin(global_invocation_id) id: vec3<u32>) {\n let renderClass = id.x;\n if (renderClass >= arrayLength(&indirect)) { return; }\n atomicStore(&indirect[renderClass].instanceCount, 0u);\n indirect[renderClass].firstVertex = 0u;\n}\n\n@compute @workgroup_size(64)\nfn applyEmitterCommandsMain(@builtin(global_invocation_id) id: vec3<u32>) {\n if (id.x >= u32(frame.commandCount)) { return; }\n let command = emitterCommands[id.x];\n let emitterIndex = command.header.x;\n if (emitterIndex >= arrayLength(&emitters)) { return; }\n var emitter = emitters[emitterIndex];\n emitter.positionLife = vec4(command.positionStart.xyz, emitter.positionLife.w);\n emitter.sprite = vec4(\n emitter.sprite.xy,\n command.positionStart.w,\n f32(command.header.z) * f32(command.header.y)\n );\n emitter.timing = command.timing;\n emitter.trail.w = bitcast<f32>(command.header.w);\n __APPLY_EMITTER_COMMAND_TRANSFORM__\n emitters[emitterIndex] = emitter;\n}\n\n@compute @workgroup_size(64)\nfn simulateMain(@builtin(global_invocation_id) id: vec3<u32>) {\n if (id.x >= u32(frame.activePageCount)) { return; }\n let particleIndex = activeParticleIndices[id.x];\n if (particleIndex >= u32(frame.capacity)) { return; }\n let owner = particleOwners[particleIndex];\n if (owner.enabled == 0u) { return; }\n var particle = particles[particleIndex];\n let emitterIndex = owner.emitterIndex;\n let emitter = emitters[emitterIndex];\n if (emitter.sprite.w < 0.5) { return; }\n __CHILD_LEVEL_FILTER__\n let allocationOffset = u32(emitter.allocation.x);\n let allocationCount = __ALLOCATION_COUNT__;\n let spawnCount = u32(emitter.allocation.z);\n let renderClass = u32(floor(emitter.allocation.w));\n let continuous = emitter.forceInterval.w >= 0.0;\n let localSlot = owner.localSlot;\n var simulationLocalSlot = localSlot;\n var childOrigin = vec3(0.0);\n var parentGeneration = 0u;\n var childEventReady = true;\n var emitterTime = max(0.0, select((frame.time - emitter.sprite.z) * emitter.timing.w, emitter.timing.y, emitter.timing.x > 0.5));\n __CHILD_CONTEXT__\n let emitterDelta = select(frame.delta * emitter.timing.w, 0.0, emitter.timing.x > 0.5);\n let emissionStarted = childEventReady && emitterTime >= emitter.emission.x;\n let stopAllowsEmission = emitter.timing.z < 0.0 || emitter.timing.z >= emitter.emission.x;\n var emissionTime = max(0.0, emitterTime - emitter.emission.x);\n if (emitter.emission.y >= 0.0) { emissionTime = min(emissionTime, emitter.emission.y); }\n if (emitter.timing.z >= 0.0) { emissionTime = min(emissionTime, max(0.0, emitter.timing.z - emitter.emission.x)); }\n var totalSpawn = spawnCount;\n if (continuous) {\n let generationCount = u32(floor(emissionTime / max(emitter.forceInterval.w, 0.0001))) + 1u;\n totalSpawn = generationCount * spawnCount;\n }\n var alive = false;\n __COLLISION_DEBUG_DECLARATION__\n if (emissionStarted && stopAllowsEmission && totalSpawn > simulationLocalSlot) {\n var sequence = simulationLocalSlot;\n var birthTime = 0.0;\n if (continuous) {\n sequence = localSlot + ((totalSpawn - 1u - localSlot) / allocationCount) * allocationCount;\n birthTime = f32(sequence / spawnCount) * emitter.forceInterval.w;\n }\n let age = max(0.0, emitterTime - emitter.emission.x) - birthTime;\n // Include the logical emitter in the key because a recycled physical\n // page can belong to a different effect slot on its next use. Keep the\n // value within f32's exact integer range because particle state stores it\n // as a float.\n let generationKey = (u32(hash(sequence + emitterIndex * 104729u + u32(emitter.sprite.w) * 13007u + parentGeneration * 8191u) * 16777215.0) & 0x00ffffffu) + 1u;\n let previousGeneration = u32(particle.scaleAlphaEmitterGeneration.w);\n let newGeneration = frame.reset > 0.5 || previousGeneration != generationKey;\n let sampledLife = mix(emitter.emission.z, emitter.emission.w, hash(sequence + emitterIndex * 104729u + 43u));\n var particleLife = select(particle.velocityLife.w, sampledLife, newGeneration);\n if (emitter.timing.z >= 0.0 && emitter.trail.w >= 0.0) {\n let ageAtStop = max(0.0, emitter.timing.z - emitter.emission.x - birthTime);\n particleLife = min(particleLife, ageAtStop + emitter.trail.w);\n }\n if (age >= 0.0 && age < particleLife) {\n if (newGeneration) {\n let particleSeed = sequence + emitterIndex * 104729u + u32(emitter.sprite.w) * 13007u;\n let random = random3(particleSeed);\n // Preserve the legacy CPU SphereZone distribution. Although a cubic\n // radius produces uniform volume density, authored CPU effects use a\n // linear radius and this angle convention, so it is the compatibility\n // contract for existing assets.\n let zoneType = u32(emitter.meshScaleEnd.w);\n let sphereRadius = select(\n hash(particleSeed + 17u) * emitter.velocityMaxRadius.w,\n emitter.velocityMaxRadius.w,\n emitter.spawnZoneA.w > 0.5\n );\n let sphereTheta = 3.141592653589793 * hash(particleSeed + 19u);\n let spherePhi = 6.283185307179586 * hash(particleSeed + 23u);\n let sphereOffset = vec3<f32>(\n sphereRadius * sin(sphereTheta) * cos(spherePhi),\n sphereRadius * sin(spherePhi) * sin(sphereTheta),\n sphereRadius * cos(sphereTheta)\n );\n let boxOffset = (random3(particleSeed + 79u) - vec3(0.5)) * emitter.spawnZoneB.xyz;\n let lineOffset = mix(emitter.spawnZoneA.xyz, emitter.spawnZoneB.xyz, hash(particleSeed + 83u));\n let circleAngle = hash(particleSeed + 89u) * emitter.spawnZoneB.w;\n let circleDistance = select(\n hash(particleSeed + 97u) * emitter.velocityMaxRadius.w,\n emitter.velocityMaxRadius.w - emitter.spawnZoneA.w + hash(particleSeed + 97u) * emitter.spawnZoneA.w,\n emitter.spawnZoneA.w > 0.0\n );\n let circleOffset = rotateSpawnPoint(\n vec3(circleDistance * cos(circleAngle), 0.0, circleDistance * sin(circleAngle)),\n emitter.spawnZoneB.xyz\n );\n var zoneOffset = emitter.spawnZoneA.xyz;\n if (zoneType == 1u) { zoneOffset += sphereOffset; }\n if (zoneType == 2u) { zoneOffset += boxOffset; }\n if (zoneType == 3u) { zoneOffset = lineOffset; }\n if (zoneType == 4u) { zoneOffset = emitter.spawnZoneA.xyz + circleOffset; }\n let initialPosition = childOrigin + emitter.positionLife.xyz + zoneOffset;\n let rangedVelocity = mix(emitter.velocityMinDamping.xyz, emitter.velocityMaxRadius.xyz, random3(particleSeed + 31u));\n let initialVelocity = select(rangedVelocity, radialVelocity(emitter, particleSeed), emitter.radialMetadata.y > 0.5);\n particle.positionAge = vec4(initialPosition, age);\n particle.velocityLife = vec4(initialVelocity, particleLife);\n let initialColor = select(\n emitter.colorStart.rgb,\n mix(emitter.colorStart.rgb, emitter.colorEnd.rgb, hash(particleSeed + 71u)),\n emitter.meshScaleStart.w > 0.5\n );\n particle.color = vec4<f32>(initialColor, mix(emitter.sprite.x, emitter.sprite.y, hash(particleSeed + 67u)));\n particle.scaleAlphaEmitterGeneration = vec4(\n particle.scaleAlphaEmitterGeneration.xyz,\n f32(generationKey)\n );\n __MESH_TRANSFORM_INITIALIZE__\n __VORTEX_INITIALIZE__\n __DISPERSE_INITIALIZE__\n __TRAIL_INITIALIZE__\n __COLLISION_EVENT_INITIALIZE__\n } else {\n // Match the CPU order: authored LinearDamping mutates velocity first,\n // position integrates that velocity, force then changes velocity, and\n // Nebula's built-in damping is applied last.\n let previousPosition = particle.positionAge.xyz;\n var behaviourVelocity = particle.velocityLife.xyz;\n __VORTEX_UPDATE__\n let positionVelocity = behaviourVelocity\n * max(0.0, 1.0 - emitter.velocityMinDamping.w * emitterDelta);\n var acceleration = emitter.forceInterval.xyz;\n __RANDOM_DRIFT_UPDATE__\n let attractionOffset = emitter.attraction.xyz - particle.positionAge.xyz;\n let attractionDistanceSq = dot(attractionOffset, attractionOffset);\n let attractionRadiusSq = emitter.attractionMetadata.x * emitter.attractionMetadata.x;\n if (emitter.attractionMetadata.y > 0.5\n && attractionDistanceSq > 0.000004\n && attractionDistanceSq < attractionRadiusSq) {\n acceleration += normalize(attractionOffset)\n * (1.0 - attractionDistanceSq / attractionRadiusSq)\n * emitter.attraction.w;\n }\n if (abs(emitter.dynamics.x - 1.0) > 0.000001) {\n acceleration /= max(emitter.dynamics.x, 0.000001);\n }\n var velocity = (positionVelocity + acceleration * emitterDelta)\n * pow(0.994, emitterDelta / 0.0167);\n if (emitter.dynamics.z > 0.5) {\n let followOffset = emitter.positionLife.xyz - particle.positionAge.xyz;\n if (emitter.dynamics.y == 0.0) {\n particle.positionAge = vec4(emitter.positionLife.xyz, particle.positionAge.w);\n velocity = vec3(0.0);\n } else if (dot(followOffset, followOffset) > 0.000001) {\n velocity = normalize(followOffset) * emitter.dynamics.y;\n }\n }\n particle.velocityLife = vec4(velocity, particle.velocityLife.w);\n let integratedMovement = select(positionVelocity * emitterDelta, vec3(0.0), emitter.moveTo.w > 0.0);\n particle.positionAge = vec4(particle.positionAge.xyz + integratedMovement, particle.positionAge.w);\n __WORLD_COLLISION_UPDATE__\n }\n let normalizedAge = clamp(age / particle.velocityLife.w, 0.0, 1.0);\n let scaleProgress = applyEasing(normalizedAge, u32(emitter.curveEasing.x));\n let colorProgress = applyEasing(normalizedAge, u32(emitter.curveEasing.y));\n let opacityProgress = applyEasing(normalizedAge, u32(emitter.curveEasing.z));\n let componentScaleProgress = applyEasing(normalizedAge, u32(emitter.curveEasing.w));\n __DISPERSE_UPDATE__\n if (emitter.moveTo.w > 0.0) {\n let moveProgress = applyEasing(normalizedAge, u32(emitter.moveTo.w - 1.0));\n let previousMoveProgress = applyEasing(clamp((age - emitterDelta) / particle.velocityLife.w, 0.0, 1.0), u32(emitter.moveTo.w - 1.0));\n particle.positionAge = vec4(particle.positionAge.xyz + emitter.moveTo.xyz * (moveProgress - previousMoveProgress), particle.positionAge.w);\n }\n __TRAIL_UPDATE__\n let scaleRange = mix(emitter.scales.xy, emitter.scales.zw, scaleProgress);\n particle.scaleAlphaEmitterGeneration = vec4(\n mix(scaleRange.x, scaleRange.y, hash(sequence + emitterIndex * 104729u + 53u)),\n mix(emitter.colorStart.w, emitter.colorEnd.w, opacityProgress),\n f32(emitterIndex),\n particle.scaleAlphaEmitterGeneration.w\n );\n particle.positionAge = vec4(particle.positionAge.xyz, age);\n particle.velocityLife = vec4(particle.velocityLife.xyz, particleLife);\n let animatedColor = mix(emitter.colorStart.rgb, emitter.colorEnd.rgb, colorProgress);\n var rotation = particle.color.w;\n if (emitterDelta > 0.0) { rotation += emitter.rotationInitialSpriteStep.w * frame.padding1; }\n particle.color = vec4(select(animatedColor, particle.color.rgb, emitter.meshScaleStart.w > 0.5), rotation);\n __COLLISION_DEBUG_COLOR__\n __MESH_TRANSFORM_UPDATE__\n alive = true;\n }\n }\n particles[particleIndex] = particle;\n if (alive) {\n let outputIndex = atomicAdd(&indirect[renderClass].instanceCount, 1u);\n aliveIndices[indirect[renderClass].firstInstance + outputIndex] = particleIndex;\n }\n}\n";export function getGpuSpriteComputeWgsl(e,t=!1,i=!1,r=!1,n=!1,a=!1,o=!1,s=0,l=0,c=!1,u=!1,p=0,d=0){const m=t||i||r||n||u;return N.replaceAll("__PARTICLE_PAGE_SIZE__",String(e)).replace("__EMITTER_COMMAND_TRANSFORM_FIELDS__",a?"trailTransform0: vec4<f32>, trailTransform1: vec4<f32>, trailTransform2: vec4<f32>, trailTransform3: vec4<f32>":"").replace("__APPLY_EMITTER_COMMAND_TRANSFORM__",a?"emitter.trailTransform0 = command.trailTransform0;\n emitter.trailTransform1 = command.trailTransform1;\n emitter.trailTransform2 = command.trailTransform2;\n emitter.trailTransform3 = command.trailTransform3;":"").replace("__MESH_TRANSFORM_DECLARATION__",t?"struct MeshParticleTransform { rotation: vec4<f32>, scale: vec4<f32>, rotationEuler: vec4<f32> }\n@group(0) @binding(8) var<storage, read_write> optionalStates: array<vec4<f32>>;":m?"@group(0) @binding(8) var<storage, read_write> optionalStates: array<vec4<f32>>;":"").replace("__COLLISION_DECLARATION__",c?"@group(1) @binding(0) var collisionDepth: texture_depth_2d;\n@group(1) @binding(1) var collisionNormalRoughness: texture_2d<f32>;":"").replace("__CHILD_LEVEL_FILTER__",d>0?"if (u32(emitter.attractionMetadata.w) != u32(frame.padding2)) { return; }":"").replace("__ALLOCATION_COUNT__",d>0?"select(u32(emitter.allocation.y), u32(emitter.radialMetadata.w), emitter.radialMetadata.z > 0.5)":"u32(emitter.allocation.y)").replace("__CHILD_CONTEXT__",d>0?`if (emitter.radialMetadata.z > 0.5) {\n let parent = particles[owner.padding];\n parentGeneration = u32(parent.scaleAlphaEmitterGeneration.w);\n let parentAlive = parentGeneration > 0u && parent.positionAge.w < parent.velocityLife.w;\n let particlesPerParent = max(1u, u32(emitter.radialMetadata.w));\n simulationLocalSlot = localSlot % particlesPerParent;\n childOrigin = parent.positionAge.xyz;\n ${u?`if (u32(emitter.attractionMetadata.z) == 2u) {\n let collisionEvent = optionalStates[${p}u + owner.padding];\n childEventReady = parentAlive && collisionEvent.x >= 0.0 && u32(collisionEvent.y) == parentGeneration;\n emitterTime = max(0.0, parent.positionAge.w - collisionEvent.x);\n } else {\n childEventReady = parentAlive;\n emitterTime = parent.positionAge.w;\n }`:"childEventReady = parentAlive;\n emitterTime = parent.positionAge.w;"}\n }`:"").replace("__COLLISION_EVENT_INITIALIZE__",u?`optionalStates[${p}u + particleIndex] = vec4(-1.0, f32(generationKey), 0.0, 0.0);`:"").replace("__COLLISION_DEBUG_DECLARATION__",c?"var collisionDebugColor = vec3(1.0, 0.0, 1.0);":"").replace("__COLLISION_DEBUG_COLOR__",c?"if (emitter.randomDriftMetadata.z > 0.5) {\n particle.color = vec4(collisionDebugColor, rotation);\n }":"").replace("__WORLD_COLLISION_UPDATE__",c?`if (emitter.vortexMetadata.y > 0.5 && emitterDelta > 0.0) {\n let dimensions = vec2<i32>(textureDimensions(collisionDepth));\n var hit = false;\n var hitPosition = previousPosition;\n var hitViewNormal = vec3(0.0, 1.0, 0.0);\n var previousDifference = -1.0;\n let viewVelocity = (frame.view * vec4(particle.velocityLife.xyz, 0.0)).xyz;\n // Trace the projected motion segment instead of testing only its end.\n // This catches thin and oblique geometry whose screen pixel changes\n // between updates while keeping the work local to collision emitters.\n for (var collisionStep = 0u; collisionStep <= 6u; collisionStep += 1u) {\n let progress = f32(collisionStep) / 6.0;\n let samplePosition = mix(previousPosition, particle.positionAge.xyz, progress);\n let sampleClip = frame.projection * frame.view * vec4(samplePosition, 1.0);\n if (sampleClip.w <= 0.0) { previousDifference = -1.0; continue; }\n let uv = vec2(sampleClip.x / sampleClip.w * 0.5 + 0.5, 0.5 - sampleClip.y / sampleClip.w * 0.5);\n if (any(uv < vec2(0.0)) || any(uv > vec2(1.0))) { previousDifference = -1.0; continue; }\n let pixel = clamp(vec2<i32>(uv * vec2<f32>(dimensions)), vec2<i32>(0), dimensions - vec2<i32>(1));\n let sceneDepth = textureLoad(collisionDepth, pixel, 0);\n if (sceneDepth >= 0.999999) { previousDifference = -1.0; continue; }\n collisionDebugColor = vec3(0.15, 0.4, 1.0);\n let difference = sampleClip.z / sampleClip.w - sceneDepth;\n let thickness = max(0.002, particle.scaleAlphaEmitterGeneration.x / max(abs(sampleClip.w), 0.001));\n let viewNormal = normalize(textureLoad(collisionNormalRoughness, pixel, 0).xyz * 2.0 - vec3(1.0));\n let ndc = vec2(uv.x * 2.0 - 1.0, 1.0 - uv.y * 2.0);\n var surfaceView: vec3<f32>;\n if (abs(frame.projection[2].w) > 0.5) {\n let surfaceViewZ = -frame.projection[3].z / (sceneDepth + frame.projection[2].z);\n surfaceView = vec3(\n ndc.x * -surfaceViewZ / frame.projection[0].x,\n ndc.y * -surfaceViewZ / frame.projection[1].y,\n surfaceViewZ\n );\n } else {\n surfaceView = vec3(\n (ndc.x - frame.projection[3].x) / frame.projection[0].x,\n (ndc.y - frame.projection[3].y) / frame.projection[1].y,\n (sceneDepth - frame.projection[3].z) / frame.projection[2].z\n );\n }\n let particleView = (frame.view * vec4(samplePosition, 1.0)).xyz;\n let normalDistance = dot(particleView - surfaceView, viewNormal);\n let collisionRadius = max(0.01, particle.scaleAlphaEmitterGeneration.x * 0.25);\n let approachingVisibleSurface = dot(viewVelocity, viewNormal) < 0.0\n && normalDistance >= -collisionRadius && normalDistance <= collisionRadius;\n let crossedSurface = previousDifference <= 0.0 && difference >= 0.0;\n let restingOnSurface = difference >= 0.0 && difference <= thickness;\n if (crossedSurface || restingOnSurface || approachingVisibleSurface) {\n hit = true;\n collisionDebugColor = vec3(1.0, 0.8, 0.05);\n hitPosition = samplePosition;\n hitViewNormal = viewNormal;\n break;\n }\n previousDifference = difference;\n }\n if (hit) {\n let worldNormal = normalize(transpose(mat3x3<f32>(frame.view[0].xyz, frame.view[1].xyz, frame.view[2].xyz)) * hitViewNormal);\n if (dot(particle.velocityLife.xyz, worldNormal) < 0.0) {\n collisionDebugColor = vec3(0.1, 1.0, 0.2);\n if (emitter.vortexMetadata.z > 0.0) {\n particle.velocityLife = vec4(reflect(particle.velocityLife.xyz, worldNormal) * emitter.vortexMetadata.z, particle.velocityLife.w);\n } else {\n let tangent = particle.velocityLife.xyz - worldNormal * dot(particle.velocityLife.xyz, worldNormal);\n particle.velocityLife = vec4(tangent * (1.0 - emitter.vortexMetadata.w), particle.velocityLife.w);\n }\n particle.positionAge = vec4(hitPosition + worldNormal * particle.scaleAlphaEmitterGeneration.x * 0.25, particle.positionAge.w);\n if (emitter.randomDriftMetadata.y > 0.0) {\n particle.velocityLife.w = max(age + 0.001, particle.velocityLife.w - emitter.randomDriftMetadata.y);\n }\n ${u?`let eventOffset = ${p}u + particleIndex;\n if (optionalStates[eventOffset].x < 0.0) {\n optionalStates[eventOffset] = vec4(age, f32(generationKey), 0.0, 0.0);\n }\n `:""}\n }\n }\n }`:"").replace("__MESH_TRANSFORM_INITIALIZE__",t?"var initialRotationEuler = emitter.rotationInitialSpriteStep.xyz;\n let alignRotationMode = u32(emitter.alignRotationAxisMode.w);\n if (alignRotationMode > 0u) {\n var alignDirection = initialPosition - emitter.alignRotationCenter.xyz;\n if (alignRotationMode == 2u) { alignDirection = -alignDirection; }\n if (alignRotationMode == 3u) { alignDirection = initialVelocity; }\n if (dot(alignDirection, alignDirection) < 0.0001) { alignDirection = vec3(0.0, 1.0, 0.0); }\n let alignQuaternion = quaternionFromUnitVectors(emitter.alignRotationAxisMode.xyz, normalize(alignDirection));\n let offsetQuaternion = quaternionFromEulerXYZ(emitter.alignRotationOffset.xyz);\n initialRotationEuler = eulerXYZFromQuaternion(quaternionMultiply(alignQuaternion, offsetQuaternion));\n }\n let randomRotationAxis = u32(emitter.rotationStep.w);\n if (randomRotationAxis == 1u) {\n initialRotationEuler = random3(particleSeed + 107u) * 6.283185307179586;\n }\n if (randomRotationAxis == 2u) { initialRotationEuler.x = hash(particleSeed + 107u) * 6.283185307179586; }\n if (randomRotationAxis == 3u) { initialRotationEuler.y = hash(particleSeed + 109u) * 6.283185307179586; }\n if (randomRotationAxis == 4u) { initialRotationEuler.z = hash(particleSeed + 113u) * 6.283185307179586; }\n let meshStateOffset = particleIndex * 3u;\n optionalStates[meshStateOffset + 2u] = vec4(initialRotationEuler, 0.0);\n optionalStates[meshStateOffset] = quaternionFromEulerXYZ(initialRotationEuler);":"").replace("__MESH_TRANSFORM_UPDATE__",t?"if (emitterDelta > 0.0) {\n let meshStateOffset = particleIndex * 3u;\n let rotationEuler = optionalStates[meshStateOffset + 2u].xyz\n + emitter.rotationStep.xyz * frame.padding1;\n optionalStates[meshStateOffset + 2u] = vec4(rotationEuler, 0.0);\n optionalStates[meshStateOffset] = quaternionFromEulerXYZ(rotationEuler);\n }\n if (emitter.dynamics.w > 0.5 && dot(particle.velocityLife.xyz, particle.velocityLife.xyz) > 0.000001) {\n let orientQuaternion = quaternionFromUnitVectors(vec3(0.0, 0.0, 1.0), normalize(particle.velocityLife.xyz));\n let meshStateOffset = particleIndex * 3u;\n optionalStates[meshStateOffset] = orientQuaternion;\n optionalStates[meshStateOffset + 2u] = vec4(eulerXYZFromQuaternion(orientQuaternion), 0.0);\n }\n optionalStates[particleIndex * 3u + 1u] = vec4(mix(emitter.meshScaleStart.xyz, emitter.meshScaleEnd.xyz, componentScaleProgress), 0.0);":"").replace("__RANDOM_DRIFT_UPDATE__",o?"if (any(abs(emitter.randomDrift.xyz) > vec3(0.000001))) {\n let driftCadence = max(emitter.randomDrift.w, max(emitterDelta, 0.000001));\n let driftStep = u32(floor(age / driftCadence));\n let driftRandom = random3(sequence + emitterIndex * 104729u + driftStep * 15485863u + 211u) * 2.0 - vec3(1.0);\n acceleration += driftRandom * emitter.randomDrift.xyz\n * (1.0 - applyEasing(clamp(age / particleLife, 0.0, 1.0), u32(emitter.randomDriftMetadata.x)));\n }":"").replace("__VORTEX_INITIALIZE__",i?`optionalStates[${s}u + particleIndex] = vec4(0.0);`:"").replace("__VORTEX_UPDATE__",i?`if (emitter.vortexMetadata.x > 0.5 && emitterDelta > 0.0) {\n behaviourVelocity -= optionalStates[${s}u + particleIndex].xyz;\n let vortexOffset = particle.positionAge.xyz - emitter.positionLife.xyz;\n let rotatedVortexOffset = applyAxisAngleCpuParity(vortexOffset, emitter.vortex.xyz, emitter.vortex.w * emitterDelta);\n let vortexVelocity = (rotatedVortexOffset - vortexOffset) / emitterDelta;\n behaviourVelocity += vortexVelocity;\n optionalStates[${s}u + particleIndex] = vec4(vortexVelocity, 0.0);\n }`:"").replace("__DISPERSE_INITIALIZE__",r?`var disperseDirection = zoneOffset;\n let disperseLength = length(disperseDirection);\n if (disperseLength > 0.0) { disperseDirection /= disperseLength; }\n let dispersePlane = u32(emitter.disperse.y);\n if (dispersePlane == 1u) { disperseDirection.z = 0.0; }\n if (dispersePlane == 2u) { disperseDirection.y = 0.0; }\n if (dispersePlane == 3u) { disperseDirection.x = 0.0; }\n optionalStates[${l}u + particleIndex] = vec4(disperseDirection * emitter.disperse.x, 0.0);`:"").replace("__DISPERSE_UPDATE__",r?`if (emitter.disperse.w > 0.5) {\n let disperseProgress = applyEasing(normalizedAge, u32(emitter.disperse.z));\n let previousDisperseProgress = optionalStates[${l}u + particleIndex].w;\n particle.positionAge = vec4(\n particle.positionAge.xyz + optionalStates[${l}u + particleIndex].xyz * (disperseProgress - previousDisperseProgress),\n particle.positionAge.w\n );\n optionalStates[${l}u + particleIndex].w = disperseProgress;\n }`:"").replace("__TRAIL_INITIALIZE__",n?"if (u32(emitter.trail.y) > 1u) {\n let trailLength = u32(emitter.trail.y);\n let trailOffset = owner.padding;\n let initialTrailState = vec4(trailOffset, 0u, 1u, u32(floor(age * 60.0)));\n optionalStates[trailOffset] = bitcast<vec4<f32>>(initialTrailState);\n let trailStride = u32(abs(emitter.trail.z));\n optionalStates[trailOffset + 1u] = vec4(trailWorldPosition(emitter, initialPosition), f32(generationKey));\n if (trailStride > 1u) { optionalStates[trailOffset + 2u] = vec4(1.0, 0.0, 0.0, 0.0); }\n }":"").replace("__TRAIL_UPDATE__",n?"let trailLength = u32(emitter.trail.y);\n if (trailLength > 1u) {\n let trailOffset = owner.padding;\n var trailStateValue = bitcast<vec4<u32>>(optionalStates[trailOffset]);\n let trailTick = u32(floor(age * 60.0));\n if (trailTick != trailStateValue.w) {\n trailStateValue.y = (trailStateValue.y + 1u) % trailLength;\n trailStateValue.z = min(trailLength, trailStateValue.z + 1u);\n trailStateValue.w = trailTick;\n }\n let trailStride = u32(abs(emitter.trail.z));\n let trailSampleOffset = trailStateValue.x + 1u + trailStateValue.y * trailStride;\n optionalStates[trailSampleOffset] = vec4(trailWorldPosition(emitter, particle.positionAge.xyz), f32(generationKey));\n if (trailStride > 1u) {\n var movement = vec3(0.0, 0.0, -1.0);\n if (dot(particle.velocityLife.xyz, particle.velocityLife.xyz) > 0.000001) { movement = normalize(particle.velocityLife.xyz); }\n let movementRotation = quaternionFromUnitVectors(movement, vec3(0.0, 0.0, -1.0));\n let particleRotation = optionalStates[particleIndex * 3u];\n let orientation = quaternionMultiply(particleRotation, movementRotation);\n let side = vec3(1.0, 0.0, 0.0) + 2.0 * cross(orientation.xyz, cross(orientation.xyz, vec3(1.0, 0.0, 0.0)) + orientation.w * vec3(1.0, 0.0, 0.0));\n optionalStates[trailSampleOffset + 1u] = vec4(trailWorldDirection(emitter, normalize(side)), 0.0);\n }\n optionalStates[trailOffset] = bitcast<vec4<f32>>(trailStateValue);\n }":"")}const F="\nstruct Frame { projection: mat4x4<f32>, view: mat4x4<f32>, time: f32, delta: f32, capacity: f32, reset: f32, activePageCount: f32, commandCount: f32, padding1: f32, padding2: f32, viewportNearFar: vec4<f32> }\nstruct Particle { positionAge: vec4<f32>, velocityLife: vec4<f32>, color: vec4<f32>, scaleAlphaEmitterGeneration: vec4<f32> }\nstruct ParticleOwner { emitterIndex: u32, localSlot: u32, enabled: u32, padding: u32 }\nstruct RenderClass { data: vec4<u32>, colorStart: vec4<f32>, colorEnd: vec4<f32>, settings: vec4<f32>, trail: vec4<u32> }\nstruct VertexOutput { @builtin(position) position: vec4<f32>, @location(0) uv: vec2<f32>, @location(1) color: vec4<f32> }\n@group(0) @binding(0) var<uniform> frame: Frame;\n@group(0) @binding(1) var<storage, read> particles: array<Particle>;\n@group(0) @binding(2) var<storage, read> aliveIndices: array<u32>;\n@group(0) @binding(3) var<uniform> renderClass: RenderClass;\n@group(0) @binding(4) var<storage, read> optionalStates: array<vec4<f32>>;\n@group(0) @binding(5) var<storage, read> particleOwners: array<ParticleOwner>;\n__EMITTER_TRANSFORM_DECLARATION__\n@group(0) @binding(__TRAIL_SAMPLER_BINDING__) var trailSampler: sampler;\n@group(0) @binding(__TRAIL_TEXTURE_BINDING__) var trailTexture: texture_2d<f32>;\n\nfn cameraWorldPosition() -> vec3<f32> {\n let rotation = mat3x3<f32>(frame.view[0].xyz, frame.view[1].xyz, frame.view[2].xyz);\n return -(transpose(rotation) * frame.view[3].xyz);\n}\nfn historyPosition(state: vec4<u32>, logicalIndex: i32) -> vec3<f32> {\n let count = i32(state.z);\n let index = clamp(logicalIndex, 0, count - 1);\n let oldest = (state.y + renderClass.trail.x - state.z + 1u) % renderClass.trail.x;\n return optionalStates[state.x + 1u + ((oldest + u32(index)) % renderClass.trail.x) * renderClass.trail.z].xyz;\n}\nfn historySide(state: vec4<u32>, logicalIndex: i32) -> vec3<f32> {\n let count = i32(state.z);\n let index = clamp(logicalIndex, 0, count - 1);\n let oldest = (state.y + renderClass.trail.x - state.z + 1u) % renderClass.trail.x;\n return optionalStates[state.x + 2u + ((oldest + u32(index)) % renderClass.trail.x) * renderClass.trail.z].xyz;\n}\nfn catmullRom(p0: vec3<f32>, p1: vec3<f32>, p2: vec3<f32>, p3: vec3<f32>, t: f32) -> vec3<f32> {\n let v0 = (p2 - p0) * 0.5;\n let v1 = (p3 - p1) * 0.5;\n let t2 = t * t;\n return (2.0 * (p1 - p2) + v0 + v1) * t2 * t + (-3.0 * (p1 - p2) - 2.0 * v0 - v1) * t2 + v0 * t + p1;\n}\nfn trailCenter(state: vec4<u32>, renderNode: u32) -> vec3<f32> {\n let subdivision = renderClass.trail.y;\n let control = i32(renderNode / subdivision);\n let t = f32(renderNode % subdivision) / f32(subdivision);\n return catmullRom(\n historyPosition(state, control - 1), historyPosition(state, control),\n historyPosition(state, control + 1), historyPosition(state, control + 2), t\n );\n}\n@vertex fn vertexMain(@builtin(vertex_index) vertexIndex: u32, @builtin(instance_index) instanceIndex: u32) -> VertexOutput {\n let particleIndex = aliveIndices[renderClass.data.x + instanceIndex];\n let particle = particles[particleIndex];\n let stateOffset = particleOwners[particleIndex].padding;\n let state = bitcast<vec4<u32>>(optionalStates[stateOffset]);\n let segment = vertexIndex / 6u;\n let triangleVertex = vertexIndex % 6u;\n let nodeOffsets = array<u32, 6>(0u, 0u, 1u, 1u, 0u, 1u);\n let sides = array<f32, 6>(-1.0, 1.0, -1.0, -1.0, 1.0, 1.0);\n let node = segment + nodeOffsets[triangleVertex];\n let activeSegments = select(0u, (state.z - 1u) * renderClass.trail.y, state.z > 1u);\n var output: VertexOutput;\n if (segment >= activeSegments) {\n output.position = vec4(2.0, 2.0, 2.0, 1.0); output.uv = vec2(0.0); output.color = vec4(0.0); return output;\n }\n let maxNode = max(1u, activeSegments);\n let centerLocal = trailCenter(state, node);\n let beforeLocal = trailCenter(state, select(node - 1u, node, node == 0u));\n let afterLocal = trailCenter(state, min(node + 1u, maxNode));\n if (any(centerLocal != centerLocal) || any(beforeLocal != beforeLocal) || any(afterLocal != afterLocal)\n || any(abs(centerLocal) > vec3(1000000.0)) || any(abs(beforeLocal) > vec3(1000000.0))\n || any(abs(afterLocal) > vec3(1000000.0))) {\n output.position = vec4(2.0, 2.0, 2.0, 1.0); return output;\n }\n var center = __TRAIL_WORLD_POSITION__(centerLocal, particle);\n let before = __TRAIL_WORLD_POSITION__(beforeLocal, particle);\n let after = __TRAIL_WORLD_POSITION__(afterLocal, particle);\n let tangentDelta = after - before;\n var tangent = vec3(1.0, 0.0, 0.0);\n if (dot(tangentDelta, tangentDelta) > 0.000001) { tangent = normalize(tangentDelta); }\n let cameraDelta = cameraWorldPosition() - center;\n var toCamera = vec3(0.0, 0.0, 1.0);\n if (dot(cameraDelta, cameraDelta) > 0.000001) { toCamera = normalize(cameraDelta); }\n var side = cross(tangent, toCamera);\n if (renderClass.trail.w == 0u) {\n let control = i32(node / renderClass.trail.y);\n let sideT = f32(node % renderClass.trail.y) / f32(renderClass.trail.y);\n side = mix(historySide(state, control), historySide(state, control + 1), sideT);\n }\n if (dot(side, side) < 0.000001) { side = vec3(1.0, 0.0, 0.0); } else { side = normalize(side); }\n let progress = f32(node) / f32(maxNode);\n let taper = mix(1.0, progress, renderClass.settings.z);\n let widthScale = select(1.0, particle.scaleAlphaEmitterGeneration.x, renderClass.trail.w != 0u);\n center += side * sides[triangleVertex] * renderClass.settings.x * 0.5 * taper * widthScale;\n if (any(center != center) || any(abs(center) > vec3(1000000.0))) {\n output.position = vec4(2.0, 2.0, 2.0, 1.0); return output;\n }\n output.position = frame.projection * frame.view * vec4(center, 1.0);\n // CPU Trail.trailUV uses tail-to-head fraction on U and head width on V.\n // Texture scrolling is likewise authored along U.\n output.uv = vec2(\n 1.0 - progress - frame.time * renderClass.settings.y,\n select(0.0, 1.0, sides[triangleVertex] > 0.0)\n );\n output.color = mix(renderClass.colorEnd, renderClass.colorStart, progress);\n output.color = vec4(\n output.color.rgb * particle.color.rgb,\n output.color.a * particle.scaleAlphaEmitterGeneration.y\n );\n return output;\n}\n@fragment fn fragmentMain(input: VertexOutput) -> @location(0) vec4<f32> {\n let sample = textureSample(trailTexture, trailSampler, input.uv);\n var textureAlpha = sample.r;\n if (renderClass.settings.w > 0.5) { textureAlpha = sample.a; }\n if (renderClass.settings.w > 1.5) { textureAlpha = 1.0; }\n let alpha = input.color.a * textureAlpha;\n if (alpha < 0.001) { discard; }\n return vec4(sample.rgb * input.color.rgb, alpha);\n}\n";function $(e,i){const r=_(e)?t():"",n=L(e.uniforms).entries;let a=i?7:6;const o=n.length>0?`struct MaterialUniforms {\n${n.map(({name:e,uniform:t})=>` ${e}: ${t.type},`).join("\n")}\n}\n@group(0) @binding(${a++}) var<uniform> materialUniforms: MaterialUniforms;`:"",s=Object.keys(e.textures).map(e=>{const t=e.replace(/[^A-Za-z0-9_]/g,"_");return`@group(0) @binding(${a++}) var ${t}Sampler: sampler;\n@group(0) @binding(${a++}) var ${t}Texture: texture_2d<f32>;`}).join("\n"),l=Object.keys(e.sceneTextures).map(e=>{const t=e.replace(/[^A-Za-z0-9_]/g,"_");return`@group(0) @binding(${a++}) var ${t}Sampler: sampler;\n@group(0) @binding(${a++}) var ${t}Texture: texture_2d<f32>;`}).join("\n"),c=e.varyings.map((e,t)=>` @location(${t}) ${e.name}: vec4<f32>,`).join("\n"),u=e.varyings.map(e=>` output.${e.name} = vec4<f32>(0.0);`).join("\n"),p=e.varyings.flatMap(e=>e.assignments.flatMap(t=>Array.from({length:t.componentCount},(i,r)=>{const n=1===t.componentCount?t.value:`(${t.value})[${r}]`;return` output.${e.name}[${t.componentOffset+r}] = ${n};`}))).join("\n"),d=e.varyings.length;let m=d+1;const f=E(e),h=P(e),v=f?` @location(${m++}) worldPosition: vec3<f32>,`:"",g=h?` @location(${m++}) worldNormal: vec3<f32>,`:"",y=[null==e.discardExpression?"":`if (${e.discardExpression}) { discard; }`,e.alphaTest>0?`if (graphColor.a < ${e.alphaTest}) { discard; }`:""].filter(Boolean).join("\n "),x=`struct GraphVertexInput {\n position: vec3<f32>, normal: vec3<f32>, uv: vec2<f32>, color: vec4<f32>,\n nodeID: f32, nodeVertexID: f32, nodeCenter: vec3<f32>, nodeTangent: vec3<f32>\n}\n\nstruct VertexOutput {\n @builtin(position) position: vec4<f32>,\n${c}\n @location(${d}) @interpolate(flat) particleIndex: u32,\n${v}\n${g}\n}`,S=` let gpuParticle = particle;\n let graphUv = vec2(1.0 - progress, select(0.0, 1.0, sides[triangleVertex] > 0.0));\n let input = GraphVertexInput(\n centerLocal, tangent, graphUv, particle.color,\n f32(node), select(0.0, 1.0, sides[triangleVertex] > 0.0), center, tangent\n );\n ${e.vertexMain}\n${u}\n${p}\n output.particleIndex = particleIndex;\n ${f?"output.worldPosition = center;":""}\n ${h?"output.worldNormal = normalize(cross(tangent, side));":""}`,w=` output.position = vec4(2.0, 2.0, 2.0, 1.0);\n${e.varyings.map(e=>` output.${e.name} = vec4<f32>(0.0);`).join("\n")}\n output.particleIndex = particleIndex;\n ${f?"output.worldPosition = vec3<f32>(0.0);":""}\n ${h?"output.worldNormal = vec3<f32>(0.0, 1.0, 0.0);":""}\n return output;`,b=`@fragment fn fragmentMain(input: VertexOutput) -> @location(0) vec4<f32> {\n let gpuParticle = particles[input.particleIndex];\n ${e.fragmentMain}\n let graphColor = ${e.colorExpression};\n ${y}\n if (any(graphColor != graphColor) || any(abs(graphColor) > vec4<f32>(1000000.0))) { discard; }\n return graphColor;\n}`;return F.replace("struct VertexOutput { @builtin(position) position: vec4<f32>, @location(0) uv: vec2<f32>, @location(1) color: vec4<f32> }",x).replace("@group(0) @binding(__TRAIL_SAMPLER_BINDING__) var trailSampler: sampler;\n@group(0) @binding(__TRAIL_TEXTURE_BINDING__) var trailTexture: texture_2d<f32>;",`${o}\n${s}\n${l}\n${e.vertexDefinitions}\n${e.fragmentDefinitions}`).replace(" output.uv = vec2(\n 1.0 - progress - frame.time * renderClass.settings.y,\n select(0.0, 1.0, sides[triangleVertex] > 0.0)\n );\n output.color = mix(renderClass.colorEnd, renderClass.colorStart, progress);\n output.color = vec4(\n output.color.rgb * particle.color.rgb,\n output.color.a * particle.scaleAlphaEmitterGeneration.y\n );",S).replace(" output.position = vec4(2.0, 2.0, 2.0, 1.0); output.uv = vec2(0.0); output.color = vec4(0.0); return output;",w).replace("@fragment fn fragmentMain(input: VertexOutput) -> @location(0) vec4<f32> {\n let sample = textureSample(trailTexture, trailSampler, input.uv);\n var textureAlpha = sample.r;\n if (renderClass.settings.w > 0.5) { textureAlpha = sample.a; }\n if (renderClass.settings.w > 1.5) { textureAlpha = 1.0; }\n let alpha = input.color.a * textureAlpha;\n if (alpha < 0.001) { discard; }\n return vec4(sample.rgb * input.color.rgb, alpha);\n}",b).replace("__EMITTER_TRANSFORM_DECLARATION__",i?"@group(0) @binding(6) var<storage, read> emitterTransforms: array<mat4x4<f32>>;":"").replaceAll("__TRAIL_WORLD_POSITION__",i?"(emitterTransforms[u32(particle.scaleAlphaEmitterGeneration.z)] * vec4":"vec4").replaceAll("vec4(centerLocal, particle)",i?"vec4(centerLocal, 1.0)).xyz":"centerLocal").replaceAll("vec4(beforeLocal, particle)",i?"vec4(beforeLocal, 1.0)).xyz":"beforeLocal").replaceAll("vec4(afterLocal, particle)",i?"vec4(afterLocal, 1.0)).xyz":"afterLocal").replace("__TRAIL_SAMPLER_BINDING__","0").replace("__TRAIL_TEXTURE_BINDING__","0").replace(/^/,`${r}\n`)}export function getGpuTrailMaterialWgsl(e,t=!1){return $(b(e),t)}const q="\nstruct Frame { projection: mat4x4<f32>, view: mat4x4<f32>, time: f32, delta: f32, capacity: f32, reset: f32, activePageCount: f32, commandCount: f32, padding1: f32, padding2: f32, viewportNearFar: vec4<f32> }\nstruct Particle { positionAge: vec4<f32>, velocityLife: vec4<f32>, color: vec4<f32>, scaleAlphaEmitterGeneration: vec4<f32> }\nstruct RenderClass { data: vec4<u32>, color: vec4<f32> }\nstruct VertexOutput { @builtin(position) position: vec4<f32>, @location(0) color: vec4<f32>, @location(1) normal: vec3<f32>, @location(2) uv: vec2<f32> }\n@group(0) @binding(0) var<uniform> frame: Frame;\n@group(0) @binding(1) var<storage, read> particles: array<Particle>;\n@group(0) @binding(2) var<storage, read> aliveIndices: array<u32>;\n@group(0) @binding(3) var<uniform> renderClass: RenderClass;\n@group(0) @binding(4) var<storage, read> emitterTransforms: array<mat4x4<f32>>;\nstruct MeshParticleTransform { rotation: vec4<f32>, scale: vec4<f32>, rotationEuler: vec4<f32> }\n@group(0) @binding(5) var<storage, read> optionalStates: array<vec4<f32>>;\n\nfn rotateByQuaternion(value: vec3<f32>, quaternion: vec4<f32>) -> vec3<f32> {\n return value + 2.0 * cross(quaternion.xyz, cross(quaternion.xyz, value) + quaternion.w * value);\n}\n\nfn loadMeshParticleTransform(particleIndex: u32) -> MeshParticleTransform {\n let offset = particleIndex * 3u;\n return MeshParticleTransform(optionalStates[offset], optionalStates[offset + 1u], optionalStates[offset + 2u]);\n}\n\n@vertex fn vertexMain(\n @location(0) position: vec3<f32>,\n @location(1) normal: vec3<f32>,\n @location(2) uv: vec2<f32>,\n @builtin(instance_index) instanceIndex: u32,\n) -> VertexOutput {\n let particleIndex = aliveIndices[renderClass.data.x + instanceIndex];\n let particle = particles[particleIndex];\n let meshTransform = loadMeshParticleTransform(particleIndex);\n let scaled = position * meshTransform.scale.xyz * particle.scaleAlphaEmitterGeneration.x;\n let rotated = rotateByQuaternion(scaled, meshTransform.rotation);\n var output: VertexOutput;\n var worldPosition = vec4<f32>(particle.positionAge.xyz + rotated, 1.0);\n if (renderClass.data.y != 0u) {\n worldPosition = emitterTransforms[u32(particle.scaleAlphaEmitterGeneration.z)] * worldPosition;\n }\n output.position = frame.projection * frame.view * worldPosition;\n output.color = vec4<f32>(\n particle.color.rgb * renderClass.color.rgb,\n particle.scaleAlphaEmitterGeneration.y * renderClass.color.a\n );\n output.normal = rotateByQuaternion(normal, meshTransform.rotation);\n output.uv = uv;\n return output;\n}\n\n@fragment fn fragmentMain(input: VertexOutput) -> @location(0) vec4<f32> {\n if (input.color.a < 0.001) { discard; }\n return input.color;\n}\n",Z="\nstruct Frame { projection: mat4x4<f32>, view: mat4x4<f32>, time: f32, delta: f32, capacity: f32, reset: f32, activePageCount: f32, commandCount: f32, padding1: f32, padding2: f32, viewportNearFar: vec4<f32> }\nstruct Particle { positionAge: vec4<f32>, velocityLife: vec4<f32>, color: vec4<f32>, scaleAlphaEmitterGeneration: vec4<f32> }\nstruct MeshParticleTransform { rotation: vec4<f32>, scale: vec4<f32>, rotationEuler: vec4<f32> }\nstruct RenderClass { data: vec4<u32> }\nstruct VertexOutput { @builtin(position) position: vec4<f32>, @location(0) uv: vec2<f32>, @location(1) color: vec4<f32> }\n@group(0) @binding(0) var<uniform> frame: Frame;\n@group(0) @binding(1) var<storage, read> particles: array<Particle>;\n@group(0) @binding(2) var<storage, read> aliveIndices: array<u32>;\n@group(0) @binding(3) var<uniform> renderClass: RenderClass;\n__EMITTER_TRANSFORM_DECLARATION__\n__PARTICLE_TRANSFORM_DECLARATION__\n@group(0) @binding(__SPRITE_SAMPLER_BINDING__) var spriteSampler: sampler;\n@group(0) @binding(__SPRITE_TEXTURE_BINDING__) var spriteTexture: texture_2d<f32>;\n// Alternative to inverse(viewMatrix)[3].xyz\nfn cameraWorldPositionFromViewMatrix(viewMatrix: mat4x4<f32>) -> vec3<f32> {\n let rotation = mat3x3<f32>(\n viewMatrix[0].xyz,\n viewMatrix[1].xyz,\n viewMatrix[2].xyz\n );\n let translation = viewMatrix[3].xyz;\n return -(transpose(rotation) * translation);\n}\nfn spriteClipPosition(worldPosition: vec3<f32>, corner: vec2<f32>) -> vec4<f32> {\n if (renderClass.data.y != 0u) {\n let cameraPosition = cameraWorldPositionFromViewMatrix(frame.view);\n let toCamera = normalize(cameraPosition - worldPosition);\n let right = normalize(cross(vec3<f32>(0.0, 1.0, 0.0), toCamera));\n let billboardPosition = worldPosition + right * corner.x + vec3<f32>(0.0, 1.0, 0.0) * corner.y;\n return frame.projection * frame.view * vec4<f32>(billboardPosition, 1.0);\n }\n let viewPosition = frame.view * vec4<f32>(worldPosition, 1.0);\n return frame.projection * vec4<f32>(viewPosition.xy + corner, viewPosition.zw);\n}\nfn stretchedSpriteClipPosition(worldPosition: vec3<f32>, velocity: vec3<f32>, corner: vec2<f32>) -> vec4<f32> {\n let viewPosition = frame.view * vec4<f32>(worldPosition, 1.0);\n let viewVelocity = (frame.view * vec4<f32>(velocity * bitcast<f32>(renderClass.data.w), 0.0)).xyz;\n let speed = length(viewVelocity);\n if (speed < 0.000001) {\n return frame.projection * vec4<f32>(viewPosition.xy + corner, viewPosition.zw);\n }\n let side = normalize(cross(viewPosition.xyz, viewVelocity)) * corner.y;\n let along = viewVelocity * ((corner.x + 0.5) * (1.0 + 1.0 / speed));\n return frame.projection * vec4<f32>(viewPosition.xyz + side - along, viewPosition.w);\n}\n@vertex fn vertexMain(@builtin(vertex_index) vertexIndex: u32, @builtin(instance_index) instanceIndex: u32) -> VertexOutput {\n // Match Three.PlaneGeometry(1, 1), whose authored corners are +/- 0.5.\n let corners = array<vec2<f32>, 6>(vec2(-0.5,-0.5), vec2(0.5,-0.5), vec2(-0.5,0.5), vec2(-0.5,0.5), vec2(0.5,-0.5), vec2(0.5,0.5));\n let uvs = array<vec2<f32>, 6>(vec2(0,1), vec2(1,1), vec2(0,0), vec2(0,0), vec2(1,1), vec2(1,0));\n let particle = particles[aliveIndices[renderClass.data.x + instanceIndex]];\n let componentScale = __PARTICLE_COMPONENT_SCALE__;\n let baseCorner = corners[vertexIndex] * componentScale * particle.scaleAlphaEmitterGeneration.x;\n let rotation = particle.color.w;\n let corner = vec2<f32>(\n cos(rotation) * baseCorner.x - sin(rotation) * baseCorner.y,\n sin(rotation) * baseCorner.x + cos(rotation) * baseCorner.y\n );\n var output: VertexOutput;\n let particleWorldPosition = __PARTICLE_WORLD_POSITION__;\n let particleWorldVelocity = __PARTICLE_WORLD_VELOCITY__;\n output.position = __SPRITE_CLIP_POSITION__;\n output.uv = uvs[vertexIndex]; output.color = vec4(particle.color.rgb, particle.scaleAlphaEmitterGeneration.y);\n return output;\n}\n@fragment fn fragmentMain(input: VertexOutput) -> @location(0) vec4<f32> {\n let sample = textureSample(spriteTexture, spriteSampler, input.uv);\n let alpha = sample.a * input.color.a;\n if (alpha < 0.001) { discard; }\n return vec4(sample.rgb * input.color.rgb, alpha);\n}\n";/*
2
+ * Copyright (©) 2026 Hology Interactive AB. All rights reserved.
3
+ * See the LICENSE.md file for details.
4
+ */