@hology/core 0.0.241 → 0.0.243
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.
- package/dist/effects/vfx/gpu-sprite-runtime.js +1 -1
- package/dist/effects/vfx/gpu-sprite-scheduler-runtime.js +1 -1
- package/dist/effects/vfx/gpu-sprite-world-runtime.js +1 -1
- package/dist/effects/vfx/initializsers.d.ts +7 -1
- package/dist/effects/vfx/initializsers.js +1 -1
- package/dist/effects/vfx/vfx-materializer.js +1 -1
- package/dist/gameplay/services/physics/abstract-physics-system.d.ts +4 -0
- package/dist/gameplay/services/physics/physics-system.d.ts +4 -8
- package/dist/gameplay/services/physics/physics-system.js +1 -1
- package/dist/scene/asset-resource-loader.js +1 -1
- package/dist/scene/materializer.js +1 -1
- package/dist/scene/storage/storage.d.ts +7 -0
- package/dist/scene/storage/storage.js +1 -1
- package/dist/secondary-motion/runtime.js +1 -1
- package/dist/test/asset-resource-loader-converted-fbx.test.d.ts +2 -0
- package/dist/test/asset-resource-loader-converted-fbx.test.js +4 -0
- package/dist/test/gpu-sprite-compiler.test.js +1 -1
- package/dist/test/materializer-dynamic-transform.test.d.ts +2 -0
- package/dist/test/materializer-dynamic-transform.test.js +4 -0
- package/dist/test/vfx-world-space-emitter-rotation.test.js +1 -1
- package/package.json +2 -2
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -1,4 +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 E(e){return`${e.vertexMain}\n${e.fragmentMain}\n${e.vertexDefinitions}\n${e.fragmentDefinitions}`}function C(e){return!0===e.needsWorldPosition||E(e).includes("input.worldPosition")}function T(e){return!0===e.needsWorldNormal||E(e).includes("input.worldNormal")}function P(e){return!0===e.requiresRendererShaderHeader||/\brenderer[A-Z][A-Za-z0-9_]*\s*\(/.test(E(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.hasLocalChildTransforms=t.some(e=>null!=e.parentEmitter&&!0!==e.localSpace&&!0===t[e.parentEmitter]?.localSpace),this.hasEmitterCommandTransforms=this.hasLocalTrailTransforms||this.hasLocalChildTransforms,this.emitterCommandFloats=this.hasEmitterCommandTransforms?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 P(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.hasEmitterCommandTransforms)){const t=this.emitters[e];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.hasEmitterCommandTransforms&&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}),E=i.createBuffer({label:"vfx-gpu-frame",size:176,usage:GPUBufferUsage.UNIFORM|GPUBufferUsage.COPY_DST}),C=i.createBuffer({label:"vfx-gpu-indirect",size:16*this.renderClasses.length,usage:GPUBufferUsage.STORAGE|GPUBufferUsage.INDIRECT|GPUBufferUsage.COPY_DST|GPUBufferUsage.COPY_SRC}),T=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 O=new Uint32Array(4*this.renderClasses.length);for(let e=0;e<this.renderClasses.length;e++){const t=this.renderClasses[e];O[4*e]=null!=t.trail?4*(t.trail.historyLength-1)*6:null==t.meshVertices?6:t.meshVertices.length/8,O[4*e+2]=0,O[4*e+3]=this.renderClassAliveOffsets[e]}i.queue.writeBuffer(C,0,O);const R=i.createShaderModule({label:"vfx-gpu-simulate-wgsl",code:getGpuSpriteComputeWgsl(this.pageSize,u,p,d,v,this.hasEmitterCommandTransforms,m,this.optionalVortexOffset,this.optionalDisperseOffset,this.hasWorldCollision,this.hasCollisionEventState,this.optionalCollisionEventOffset,this.maximumChildLevel,this.hasLocalChildTransforms)}),L=[E];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:R,entryPoint:"clearMain"}}),k=i.createComputePipeline({label:"vfx-gpu-apply-emitter-commands",layout:W,compute:{module:R,entryPoint:"applyEmitterCommandsMain"}}),Y=i.createComputePipeline({label:"vfx-gpu-simulate",layout:W,compute:{module:R,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:C}},{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&&P(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 C=p||u?6:4+(d?1:0)+(m?1:0),T=null;if(null!=s&&Object.keys(s.uniforms).length>0){g.push({binding:C++,visibility:GPUShaderStage.VERTEX|GPUShaderStage.FRAGMENT,buffer:{type:"uniform"}});const e=U(s.uniforms);T=i.createBuffer({label:"vfx-gpu-material-uniforms",size:e.byteLength,usage:GPUBufferUsage.UNIFORM|GPUBufferUsage.COPY_DST}),i.queue.writeBuffer(T,0,e)}const _=(null!=s?Object.values(s.textures):u?[]:[n.texture]).map(e=>t.getExternalTexture(e)),O=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<_.length;e++)g.push({binding:C++,visibility:GPUShaderStage.FRAGMENT,sampler:{type:"filtering"}}),g.push({binding:C++,visibility:GPUShaderStage.FRAGMENT,texture:{sampleType:"float"}});for(const[,e]of O)g.push({binding:C++,visibility:GPUShaderStage.FRAGMENT,sampler:{type:"sceneDepth"===e?"non-filtering":"filtering"}}),g.push({binding:C++,visibility:GPUShaderStage.FRAGMENT,texture:{sampleType:"float"}});const R=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?[R]:[R,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:E}},{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}})),C=p||u?6:4+(d?1:0)+(m?1:0),null!=T&&N.push({binding:C++,resource:{buffer:T}});for(const e of _)N.push({binding:C++,resource:e.sampler}),N.push({binding:C++,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,R,L,N,O.map(([,e])=>e),y,T,s,W)});return new _(r,a,o,E,C,T,w,b,s,S,x,j,k,Y,X,L,Q,N)}}class _{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:O(t.blendSrc),dstFactor:O(t.blendDst),operation:R(t.blendEquation)},alpha:{srcFactor:O(t.blendSrcAlpha??t.blendSrc),dstFactor:O(t.blendDstAlpha??t.blendDst),operation:R(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 O(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 R(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=P(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=C(e),y=T(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=P(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=C(e),h=T(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 childEmissionEnd = -1.0;\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 if (childEmissionEnd >= 0.0) { emissionTime = min(emissionTime, max(0.0, childEmissionEnd - 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 // Preserve the advancing age after render death. Child emitters read their\n // parent particle directly; freezing this value below particleLife made a\n // dead parent appear alive forever and froze all child behavior curves.\n particle.positionAge = vec4(particle.positionAge.xyz, age);\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,m=!1){const f=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>>;":f?"@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 parentEventOccurred = parentGeneration > 0u;\n let particlesPerParent = max(1u, u32(emitter.radialMetadata.w));\n simulationLocalSlot = localSlot % particlesPerParent;\n childOrigin = parent.positionAge.xyz;\n ${m?"let parentEmitter = emitters[u32(emitter.radialMetadata.z) - 1u];\n if (parentEmitter.trail.z > 0.0 && emitter.trail.z < 0.0) {\n let parentTransform = mat4x4<f32>(\n parentEmitter.trailTransform0, parentEmitter.trailTransform1,\n parentEmitter.trailTransform2, parentEmitter.trailTransform3\n );\n childOrigin = (parentTransform * vec4(childOrigin, 1.0)).xyz;\n }":""}\n ${u?`if (u32(emitter.attractionMetadata.z) == 2u) {\n let collisionEvent = optionalStates[${p}u + owner.padding];\n childEventReady = parentEventOccurred && collisionEvent.x >= 0.0 && u32(collisionEvent.y) == parentGeneration;\n emitterTime = max(0.0, parent.positionAge.w - collisionEvent.x);\n childEmissionEnd = max(0.0, parent.velocityLife.w - collisionEvent.x);\n } else {\n childEventReady = parentEventOccurred;\n emitterTime = parent.positionAge.w;\n childEmissionEnd = parent.velocityLife.w;\n }`:"childEventReady = parentEventOccurred;\n emitterTime = parent.positionAge.w;\n childEmissionEnd = parent.velocityLife.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=P(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=C(e),h=T(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";/*
|
|
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 m}from"../../shader-nodes/depth.js";import{sceneMapUniformName as d}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"},[m]:{type:"vec2",expression:"frame.viewportNearFar.xy"},[p]:{type:"float",expression:"frame.viewportNearFar.z"},[u]:{type:"float",expression:"frame.viewportNearFar.w"}},sceneTextures:{...r.sceneTextures,[d]:{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 E(e){return`${e.vertexMain}\n${e.fragmentMain}\n${e.vertexDefinitions}\n${e.fragmentDefinitions}`}function C(e){return!0===e.needsWorldPosition||E(e).includes("input.worldPosition")}function T(e){return!0===e.needsWorldNormal||E(e).includes("input.worldNormal")}function P(e){return!0===e.requiresRendererShaderHeader||/\brenderer[A-Z][A-Za-z0-9_]*\s*\(/.test(E(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.hasLocalChildTransforms=t.some(e=>null!=e.parentEmitter&&!0!==e.localSpace&&!0===t[e.parentEmitter]?.localSpace),this.hasEmitterCommandTransforms=this.hasLocalTrailTransforms||this.hasLocalChildTransforms||t.some(e=>!0!==e.localSpace&&null==e.parentEmitter),this.emitterCommandFloats=this.hasEmitterCommandTransforms?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 P(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 m=this.renderClasses.some(e=>null!=e.meshVertices),d=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=m||f||d,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(null==this.emitters[e])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.hasEmitterCommandTransforms)){const t=this.emitters[e];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.hasEmitterCommandTransforms&&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,m=this.hasDisperseState,d=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}),E=i.createBuffer({label:"vfx-gpu-frame",size:176,usage:GPUBufferUsage.UNIFORM|GPUBufferUsage.COPY_DST}),C=i.createBuffer({label:"vfx-gpu-indirect",size:16*this.renderClasses.length,usage:GPUBufferUsage.STORAGE|GPUBufferUsage.INDIRECT|GPUBufferUsage.COPY_DST|GPUBufferUsage.COPY_SRC}),T=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 O=new Uint32Array(4*this.renderClasses.length);for(let e=0;e<this.renderClasses.length;e++){const t=this.renderClasses[e];O[4*e]=null!=t.trail?4*(t.trail.historyLength-1)*6:null==t.meshVertices?6:t.meshVertices.length/8,O[4*e+2]=0,O[4*e+3]=this.renderClassAliveOffsets[e]}i.queue.writeBuffer(C,0,O);const R=i.createShaderModule({label:"vfx-gpu-simulate-wgsl",code:getGpuSpriteComputeWgsl(this.pageSize,u,p,m,v,this.hasEmitterCommandTransforms,d,this.optionalVortexOffset,this.optionalDisperseOffset,this.hasWorldCollision,this.hasCollisionEventState,this.optionalCollisionEventOffset,this.maximumChildLevel,this.hasLocalChildTransforms)}),L=[E];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:R,entryPoint:"clearMain"}}),k=i.createComputePipeline({label:"vfx-gpu-apply-emitter-commands",layout:W,compute:{module:R,entryPoint:"applyEmitterCommandsMain"}}),Y=i.createComputePipeline({label:"vfx-gpu-simulate",layout:W,compute:{module:R,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:C}},{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&&P(s),u=null!=n.meshVertices,p=null!=n.trail,m=!u&&!0===n.localSpace,d=!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)")}(m,d,null!=n.stretchScale):G(s,m,d,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 C=p||u?6:4+(m?1:0)+(d?1:0),T=null;if(null!=s&&Object.keys(s.uniforms).length>0){g.push({binding:C++,visibility:GPUShaderStage.VERTEX|GPUShaderStage.FRAGMENT,buffer:{type:"uniform"}});const e=U(s.uniforms);T=i.createBuffer({label:"vfx-gpu-material-uniforms",size:e.byteLength,usage:GPUBufferUsage.UNIFORM|GPUBufferUsage.COPY_DST}),i.queue.writeBuffer(T,0,e)}const _=(null!=s?Object.values(s.textures):u?[]:[n.texture]).map(e=>t.getExternalTexture(e)),O=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"}})):(m&&g.push({binding:4,visibility:GPUShaderStage.VERTEX,buffer:{type:"read-only-storage"}}),d&&g.push({binding:m?5:4,visibility:GPUShaderStage.VERTEX,buffer:{type:"read-only-storage"}}));for(let e=0;e<_.length;e++)g.push({binding:C++,visibility:GPUShaderStage.FRAGMENT,sampler:{type:"filtering"}}),g.push({binding:C++,visibility:GPUShaderStage.FRAGMENT,texture:{sampleType:"float"}});for(const[,e]of O)g.push({binding:C++,visibility:GPUShaderStage.FRAGMENT,sampler:{type:"sceneDepth"===e?"non-filtering":"filtering"}}),g.push({binding:C++,visibility:GPUShaderStage.FRAGMENT,texture:{sampleType:"float"}});const R=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?[R]:[R,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:A: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:E}},{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}})):(m&&N.push({binding:4,resource:{buffer:S}}),d&&N.push({binding:m?5:4,resource:{buffer:x}})),C=p||u?6:4+(m?1:0)+(d?1:0),null!=T&&N.push({binding:C++,resource:{buffer:T}});for(const e of _)N.push({binding:C++,resource:e.sampler}),N.push({binding:C++,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 z(B,R,L,N,O.map(([,e])=>e),y,T,s,W)});return new _(r,a,o,E,C,T,w,b,s,S,x,j,k,Y,X,L,Q,N)}}class _{constructor(e,t,i,r,n,a,o,s,l,c,u,p,m,d,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=m,this.simulatePipeline=d,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 z{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 A={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:O(t.blendSrc),dstFactor:O(t.blendDst),operation:R(t.blendEquation)},alpha:{srcFactor:O(t.blendSrcAlpha??t.blendSrc),dstFactor:O(t.blendDstAlpha??t.blendDst),operation:R(t.blendEquationAlpha??t.blendEquation)}};case e.AdditiveBlending:return A;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 O(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 R(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=P(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"),m=e.varyings.map((e,t)=>` @location(${t}) ${e.name}: vec4<f32>,`).join("\n"),d=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=C(e),y=T(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${m}\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${d}\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=P(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,m=p+1;let d=m+1;const f=C(e),h=T(e),v=f?` @location(${d++}) worldPosition: vec3<f32>,`:"",g=h?` @location(${d++}) 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(${m}) @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 worldSpawnDirection(emitter: Emitter, direction: vec3<f32>) -> vec3<f32> {\n if (emitter.trail.z >= 0.0) { return direction; }\n let rotation = worldSpawnRotation(emitter);\n return direction + 2.0 * cross(rotation.xyz, cross(rotation.xyz, direction) + rotation.w * direction);\n}\n\nfn worldSpawnRotation(emitter: Emitter) -> vec4<f32> {\n if (emitter.trail.z >= 0.0) { return vec4(0.0, 0.0, 0.0, 1.0); }\n let x = normalize(emitter.trailTransform0.xyz);\n let y = normalize(emitter.trailTransform1.xyz);\n let z = normalize(emitter.trailTransform2.xyz);\n let trace = x.x + y.y + z.z;\n var result: vec4<f32>;\n if (trace > 0.0) {\n let s = sqrt(trace + 1.0) * 2.0;\n result = vec4((y.z - z.y) / s, (z.x - x.z) / s, (x.y - y.x) / s, 0.25 * s);\n } else if (x.x > y.y && x.x > z.z) {\n let s = sqrt(1.0 + x.x - y.y - z.z) * 2.0;\n result = vec4(0.25 * s, (x.y + y.x) / s, (x.z + z.x) / s, (y.z - z.y) / s);\n } else if (y.y > z.z) {\n let s = sqrt(1.0 + y.y - x.x - z.z) * 2.0;\n result = vec4((x.y + y.x) / s, 0.25 * s, (y.z + z.y) / s, (z.x - x.z) / s);\n } else {\n let s = sqrt(1.0 + z.z - x.x - y.y) * 2.0;\n result = vec4((x.z + z.x) / s, (y.z + z.y) / s, 0.25 * s, (x.y - y.x) / s);\n }\n return normalize(result);\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 childEmissionEnd = -1.0;\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 if (childEmissionEnd >= 0.0) { emissionTime = min(emissionTime, max(0.0, childEmissionEnd - 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 // Preserve the advancing age after render death. Child emitters read their\n // parent particle directly; freezing this value below particleLife made a\n // dead parent appear alive forever and froze all child behavior curves.\n particle.positionAge = vec4(particle.positionAge.xyz, age);\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 zoneOffset = worldSpawnDirection(emitter, zoneOffset);\n let initialPosition = childOrigin + emitter.positionLife.xyz + zoneOffset;\n let rangedVelocity = mix(emitter.velocityMinDamping.xyz, emitter.velocityMaxRadius.xyz, random3(particleSeed + 31u));\n let localInitialVelocity = select(rangedVelocity, radialVelocity(emitter, particleSeed), emitter.radialMetadata.y > 0.5);\n let initialVelocity = worldSpawnDirection(emitter, localInitialVelocity);\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,m=0,d=!1){const f=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>>;":f?"@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__",m>0?"if (u32(emitter.attractionMetadata.w) != u32(frame.padding2)) { return; }":"").replace("__ALLOCATION_COUNT__",m>0?"select(u32(emitter.allocation.y), u32(emitter.radialMetadata.w), emitter.radialMetadata.z > 0.5)":"u32(emitter.allocation.y)").replace("__CHILD_CONTEXT__",m>0?`if (emitter.radialMetadata.z > 0.5) {\n let parent = particles[owner.padding];\n parentGeneration = u32(parent.scaleAlphaEmitterGeneration.w);\n let parentEventOccurred = parentGeneration > 0u;\n let particlesPerParent = max(1u, u32(emitter.radialMetadata.w));\n simulationLocalSlot = localSlot % particlesPerParent;\n childOrigin = parent.positionAge.xyz;\n ${d?"let parentEmitter = emitters[u32(emitter.radialMetadata.z) - 1u];\n if (parentEmitter.trail.z > 0.0 && emitter.trail.z < 0.0) {\n let parentTransform = mat4x4<f32>(\n parentEmitter.trailTransform0, parentEmitter.trailTransform1,\n parentEmitter.trailTransform2, parentEmitter.trailTransform3\n );\n childOrigin = (parentTransform * vec4(childOrigin, 1.0)).xyz;\n }":""}\n ${u?`if (u32(emitter.attractionMetadata.z) == 2u) {\n let collisionEvent = optionalStates[${p}u + owner.padding];\n childEventReady = parentEventOccurred && collisionEvent.x >= 0.0 && u32(collisionEvent.y) == parentGeneration;\n emitterTime = max(0.0, parent.positionAge.w - collisionEvent.x);\n childEmissionEnd = max(0.0, parent.velocityLife.w - collisionEvent.x);\n } else {\n childEventReady = parentEventOccurred;\n emitterTime = parent.positionAge.w;\n childEmissionEnd = parent.velocityLife.w;\n }`:"childEventReady = parentEventOccurred;\n emitterTime = parent.positionAge.w;\n childEmissionEnd = parent.velocityLife.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 = localInitialVelocity; }\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] = quaternionMultiply(\n worldSpawnRotation(emitter), quaternionFromEulerXYZ(initialRotationEuler));":"").replace("__MESH_TRANSFORM_UPDATE__",t?"if (emitterDelta > 0.0) {\n let meshStateOffset = particleIndex * 3u;\n let previousRotationEuler = optionalStates[meshStateOffset + 2u].xyz;\n let rotationDelta = emitter.rotationStep.xyz * frame.padding1;\n let rotationEuler = previousRotationEuler + rotationDelta;\n optionalStates[meshStateOffset + 2u] = vec4(rotationEuler, 0.0);\n optionalStates[meshStateOffset] = quaternionMultiply(\n optionalStates[meshStateOffset], quaternionFromEulerXYZ(rotationDelta));\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=P(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"),m=e.varyings.length;let d=m+1;const f=C(e),h=T(e),v=f?` @location(${d++}) worldPosition: vec3<f32>,`:"",g=h?` @location(${d++}) 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(${m}) @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
2
|
* Copyright (©) 2026 Hology Interactive AB. All rights reserved.
|
|
3
3
|
* See the LICENSE.md file for details.
|
|
4
4
|
*/
|