@codexo/exojs 0.7.12 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +737 -0
- package/dist/esm/core/Application.d.ts +3 -1
- package/dist/esm/core/Application.js +7 -6
- package/dist/esm/core/Application.js.map +1 -1
- package/dist/esm/core/Scene.d.ts +30 -0
- package/dist/esm/core/Scene.js +56 -0
- package/dist/esm/core/Scene.js.map +1 -1
- package/dist/esm/core/SceneManager.js +2 -2
- package/dist/esm/core/SceneManager.js.map +1 -1
- package/dist/esm/debug/DebugOverlay.js +2 -2
- package/dist/esm/debug/DebugOverlay.js.map +1 -1
- package/dist/esm/debug/PointerStackLayer.js +1 -1
- package/dist/esm/debug/PointerStackLayer.js.map +1 -1
- package/dist/esm/index.js +32 -10
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/input/ArcadeStickGamepadMapping.js +18 -19
- package/dist/esm/input/ArcadeStickGamepadMapping.js.map +1 -1
- package/dist/esm/input/Gamepad.d.ts +164 -62
- package/dist/esm/input/Gamepad.js +290 -134
- package/dist/esm/input/Gamepad.js.map +1 -1
- package/dist/esm/input/GamepadAxis.d.ts +120 -0
- package/dist/esm/input/GamepadAxis.js +106 -0
- package/dist/esm/input/GamepadAxis.js.map +1 -0
- package/dist/esm/input/GamepadButton.d.ts +110 -0
- package/dist/esm/input/GamepadButton.js +99 -0
- package/dist/esm/input/GamepadButton.js.map +1 -0
- package/dist/esm/input/GamepadDefinitions.js +4 -0
- package/dist/esm/input/GamepadDefinitions.js.map +1 -1
- package/dist/esm/input/GamepadMapping.d.ts +28 -24
- package/dist/esm/input/GamepadMapping.js +33 -16
- package/dist/esm/input/GamepadMapping.js.map +1 -1
- package/dist/esm/input/GamepadPromptLayouts.d.ts +10 -8
- package/dist/esm/input/GamepadPromptLayouts.js +21 -20
- package/dist/esm/input/GamepadPromptLayouts.js.map +1 -1
- package/dist/esm/input/GenericDualAnalogGamepadMapping.d.ts +6 -3
- package/dist/esm/input/GenericDualAnalogGamepadMapping.js +55 -46
- package/dist/esm/input/GenericDualAnalogGamepadMapping.js.map +1 -1
- package/dist/esm/input/InputBinding.d.ts +74 -0
- package/dist/esm/input/InputBinding.js +100 -0
- package/dist/esm/input/InputBinding.js.map +1 -0
- package/dist/esm/input/InputManager.d.ts +79 -33
- package/dist/esm/input/InputManager.js +229 -104
- package/dist/esm/input/InputManager.js.map +1 -1
- package/dist/esm/input/InteractionManager.d.ts +1 -1
- package/dist/esm/input/InteractionManager.js +13 -13
- package/dist/esm/input/InteractionManager.js.map +1 -1
- package/dist/esm/input/JoyConLeftGamepadMapping.d.ts +14 -9
- package/dist/esm/input/JoyConLeftGamepadMapping.js +39 -9
- package/dist/esm/input/JoyConLeftGamepadMapping.js.map +1 -1
- package/dist/esm/input/JoyConRightGamepadMapping.d.ts +14 -9
- package/dist/esm/input/JoyConRightGamepadMapping.js +35 -9
- package/dist/esm/input/JoyConRightGamepadMapping.js.map +1 -1
- package/dist/esm/input/Pointer.d.ts +84 -71
- package/dist/esm/input/Pointer.js +71 -71
- package/dist/esm/input/Pointer.js.map +1 -1
- package/dist/esm/input/SteamDeckGamepadMapping.d.ts +18 -0
- package/dist/esm/input/SteamDeckGamepadMapping.js +76 -0
- package/dist/esm/input/SteamDeckGamepadMapping.js.map +1 -0
- package/dist/esm/input/index.d.ts +7 -4
- package/dist/esm/input/types.d.ts +0 -76
- package/dist/esm/input/types.js +1 -80
- package/dist/esm/input/types.js.map +1 -1
- package/dist/esm/particles/ParticleSystem.d.ts +180 -83
- package/dist/esm/particles/ParticleSystem.js +446 -133
- package/dist/esm/particles/ParticleSystem.js.map +1 -1
- package/dist/esm/particles/distributions/BoxArea.d.ts +17 -0
- package/dist/esm/particles/distributions/BoxArea.js +48 -0
- package/dist/esm/particles/distributions/BoxArea.js.map +1 -0
- package/dist/esm/particles/distributions/CircleArea.d.ts +19 -0
- package/dist/esm/particles/distributions/CircleArea.js +33 -0
- package/dist/esm/particles/distributions/CircleArea.js.map +1 -0
- package/dist/esm/particles/distributions/ConeDirection.d.ts +28 -0
- package/dist/esm/particles/distributions/ConeDirection.js +44 -0
- package/dist/esm/particles/distributions/ConeDirection.js.map +1 -0
- package/dist/esm/particles/distributions/Constant.d.ts +17 -0
- package/dist/esm/particles/distributions/Constant.js +35 -0
- package/dist/esm/particles/distributions/Constant.js.map +1 -0
- package/dist/esm/particles/distributions/Curve.d.ts +30 -0
- package/dist/esm/particles/distributions/Curve.js +53 -0
- package/dist/esm/particles/distributions/Curve.js.map +1 -0
- package/dist/esm/particles/distributions/Distribution.d.ts +45 -0
- package/dist/esm/particles/distributions/Gradient.d.ts +40 -0
- package/dist/esm/particles/distributions/Gradient.js +72 -0
- package/dist/esm/particles/distributions/Gradient.js.map +1 -0
- package/dist/esm/particles/distributions/LineSegment.d.ts +15 -0
- package/dist/esm/particles/distributions/LineSegment.js +27 -0
- package/dist/esm/particles/distributions/LineSegment.js.map +1 -0
- package/dist/esm/particles/distributions/Range.d.ts +12 -0
- package/dist/esm/particles/distributions/Range.js +19 -0
- package/dist/esm/particles/distributions/Range.js.map +1 -0
- package/dist/esm/particles/distributions/VectorRange.d.ts +20 -0
- package/dist/esm/particles/distributions/VectorRange.js +31 -0
- package/dist/esm/particles/distributions/VectorRange.js.map +1 -0
- package/dist/esm/particles/distributions/index.d.ts +12 -0
- package/dist/esm/particles/gpu/ParticleGpuState.d.ts +57 -0
- package/dist/esm/particles/gpu/ParticleGpuState.js +508 -0
- package/dist/esm/particles/gpu/ParticleGpuState.js.map +1 -0
- package/dist/esm/particles/index.d.ts +2 -10
- package/dist/esm/particles/modules/AlphaFadeOverLifetime.d.ts +24 -0
- package/dist/esm/particles/modules/AlphaFadeOverLifetime.js +60 -0
- package/dist/esm/particles/modules/AlphaFadeOverLifetime.js.map +1 -0
- package/dist/esm/particles/modules/ApplyForce.d.ts +20 -0
- package/dist/esm/particles/modules/ApplyForce.js +48 -0
- package/dist/esm/particles/modules/ApplyForce.js.map +1 -0
- package/dist/esm/particles/modules/AttractToPoint.d.ts +27 -0
- package/dist/esm/particles/modules/AttractToPoint.js +73 -0
- package/dist/esm/particles/modules/AttractToPoint.js.map +1 -0
- package/dist/esm/particles/modules/BurstSpawn.d.ts +53 -0
- package/dist/esm/particles/modules/BurstSpawn.js +94 -0
- package/dist/esm/particles/modules/BurstSpawn.js.map +1 -0
- package/dist/esm/particles/modules/ColorOverLifetime.d.ts +22 -0
- package/dist/esm/particles/modules/ColorOverLifetime.js +65 -0
- package/dist/esm/particles/modules/ColorOverLifetime.js.map +1 -0
- package/dist/esm/particles/modules/ColorOverSpeed.d.ts +27 -0
- package/dist/esm/particles/modules/ColorOverSpeed.js +86 -0
- package/dist/esm/particles/modules/ColorOverSpeed.js.map +1 -0
- package/dist/esm/particles/modules/DeathModule.d.ts +24 -0
- package/dist/esm/particles/modules/DeathModule.js +25 -0
- package/dist/esm/particles/modules/DeathModule.js.map +1 -0
- package/dist/esm/particles/modules/Drag.d.ts +20 -0
- package/dist/esm/particles/modules/Drag.js +45 -0
- package/dist/esm/particles/modules/Drag.js.map +1 -0
- package/dist/esm/particles/modules/OrbitalForce.d.ts +28 -0
- package/dist/esm/particles/modules/OrbitalForce.js +65 -0
- package/dist/esm/particles/modules/OrbitalForce.js.map +1 -0
- package/dist/esm/particles/modules/RateSpawn.d.ts +41 -0
- package/dist/esm/particles/modules/RateSpawn.js +76 -0
- package/dist/esm/particles/modules/RateSpawn.js.map +1 -0
- package/dist/esm/particles/modules/RepelFromPoint.d.ts +24 -0
- package/dist/esm/particles/modules/RepelFromPoint.js +76 -0
- package/dist/esm/particles/modules/RepelFromPoint.js.map +1 -0
- package/dist/esm/particles/modules/RotateOverLifetime.d.ts +20 -0
- package/dist/esm/particles/modules/RotateOverLifetime.js +43 -0
- package/dist/esm/particles/modules/RotateOverLifetime.js.map +1 -0
- package/dist/esm/particles/modules/ScaleOverLifetime.d.ts +26 -0
- package/dist/esm/particles/modules/ScaleOverLifetime.js +59 -0
- package/dist/esm/particles/modules/ScaleOverLifetime.js.map +1 -0
- package/dist/esm/particles/modules/SpawnModule.d.ts +30 -0
- package/dist/esm/particles/modules/SpawnModule.js +31 -0
- package/dist/esm/particles/modules/SpawnModule.js.map +1 -0
- package/dist/esm/particles/modules/SpawnOnDeath.d.ts +24 -0
- package/dist/esm/particles/modules/SpawnOnDeath.js +47 -0
- package/dist/esm/particles/modules/SpawnOnDeath.js.map +1 -0
- package/dist/esm/particles/modules/Turbulence.d.ts +30 -0
- package/dist/esm/particles/modules/Turbulence.js +122 -0
- package/dist/esm/particles/modules/Turbulence.js.map +1 -0
- package/dist/esm/particles/modules/UpdateModule.d.ts +95 -0
- package/dist/esm/particles/modules/UpdateModule.js +66 -0
- package/dist/esm/particles/modules/UpdateModule.js.map +1 -0
- package/dist/esm/particles/modules/VelocityOverLifetime.d.ts +30 -0
- package/dist/esm/particles/modules/VelocityOverLifetime.js +84 -0
- package/dist/esm/particles/modules/VelocityOverLifetime.js.map +1 -0
- package/dist/esm/particles/modules/WgslContribution.d.ts +81 -0
- package/dist/esm/particles/modules/WgslContribution.js +34 -0
- package/dist/esm/particles/modules/WgslContribution.js.map +1 -0
- package/dist/esm/particles/modules/index.d.ts +22 -0
- package/dist/esm/rendering/webgl2/WebGl2ParticleRenderer.d.ts +9 -14
- package/dist/esm/rendering/webgl2/WebGl2ParticleRenderer.js +90 -61
- package/dist/esm/rendering/webgl2/WebGl2ParticleRenderer.js.map +1 -1
- package/dist/esm/rendering/webgl2/glsl/particle.vert.js +1 -1
- package/dist/esm/rendering/webgpu/WebGpuParticleRenderer.d.ts +9 -0
- package/dist/esm/rendering/webgpu/WebGpuParticleRenderer.js +107 -23
- package/dist/esm/rendering/webgpu/WebGpuParticleRenderer.js.map +1 -1
- package/dist/esm/rendering/webgpu/compute/WebGpuComputePipeline.d.ts +52 -0
- package/dist/esm/rendering/webgpu/compute/WebGpuStorageBuffer.d.ts +29 -0
- package/dist/esm/rendering/webgpu/compute/index.d.ts +3 -0
- package/dist/esm/resources/CacheFirstStrategy.d.ts +7 -4
- package/dist/esm/resources/CacheFirstStrategy.js +11 -8
- package/dist/esm/resources/CacheFirstStrategy.js.map +1 -1
- package/dist/esm/resources/CacheStrategy.d.ts +14 -6
- package/dist/esm/resources/Loader.d.ts +8 -3
- package/dist/esm/resources/Loader.js +19 -37
- package/dist/esm/resources/Loader.js.map +1 -1
- package/dist/esm/resources/NetworkOnlyStrategy.d.ts +3 -0
- package/dist/esm/resources/NetworkOnlyStrategy.js +8 -3
- package/dist/esm/resources/NetworkOnlyStrategy.js.map +1 -1
- package/dist/esm/resources/factories/ImageFactory.d.ts +2 -2
- package/dist/esm/resources/factories/ImageFactory.js.map +1 -1
- package/dist/esm/resources/factories/TextureFactory.d.ts +2 -2
- package/dist/esm/resources/factories/TextureFactory.js.map +1 -1
- package/dist/esm/resources/factories/VttFactory.d.ts +3 -3
- package/dist/esm/resources/factories/VttFactory.js +83 -6
- package/dist/esm/resources/factories/VttFactory.js.map +1 -1
- package/dist/exo.esm.js +4028 -1518
- package/dist/exo.esm.js.map +1 -1
- package/package.json +2 -1
- package/dist/esm/input/GamepadChannels.d.ts +0 -47
- package/dist/esm/input/GamepadChannels.js +0 -53
- package/dist/esm/input/GamepadChannels.js.map +0 -1
- package/dist/esm/input/GamepadControl.d.ts +0 -33
- package/dist/esm/input/GamepadControl.js +0 -42
- package/dist/esm/input/GamepadControl.js.map +0 -1
- package/dist/esm/input/Input.d.ts +0 -52
- package/dist/esm/input/Input.js +0 -90
- package/dist/esm/input/Input.js.map +0 -1
- package/dist/esm/particles/Particle.d.ts +0 -77
- package/dist/esm/particles/Particle.js +0 -143
- package/dist/esm/particles/Particle.js.map +0 -1
- package/dist/esm/particles/ParticleProperties.d.ts +0 -29
- package/dist/esm/particles/affectors/ColorAffector.d.ts +0 -30
- package/dist/esm/particles/affectors/ColorAffector.js +0 -55
- package/dist/esm/particles/affectors/ColorAffector.js.map +0 -1
- package/dist/esm/particles/affectors/ForceAffector.d.ts +0 -24
- package/dist/esm/particles/affectors/ForceAffector.js +0 -39
- package/dist/esm/particles/affectors/ForceAffector.js.map +0 -1
- package/dist/esm/particles/affectors/ParticleAffector.d.ts +0 -19
- package/dist/esm/particles/affectors/ScaleAffector.d.ts +0 -23
- package/dist/esm/particles/affectors/ScaleAffector.js +0 -38
- package/dist/esm/particles/affectors/ScaleAffector.js.map +0 -1
- package/dist/esm/particles/affectors/TorqueAffector.d.ts +0 -23
- package/dist/esm/particles/affectors/TorqueAffector.js +0 -37
- package/dist/esm/particles/affectors/TorqueAffector.js.map +0 -1
- package/dist/esm/particles/emitters/ParticleEmitter.d.ts +0 -19
- package/dist/esm/particles/emitters/ParticleOptions.d.ts +0 -62
- package/dist/esm/particles/emitters/ParticleOptions.js +0 -120
- package/dist/esm/particles/emitters/ParticleOptions.js.map +0 -1
- package/dist/esm/particles/emitters/UniversalEmitter.d.ts +0 -40
- package/dist/esm/particles/emitters/UniversalEmitter.js +0 -68
- package/dist/esm/particles/emitters/UniversalEmitter.js.map +0 -1
|
@@ -1,35 +1,195 @@
|
|
|
1
|
-
import { Particle } from './Particle.js';
|
|
2
1
|
import { Rectangle } from '../math/Rectangle.js';
|
|
3
2
|
import { Drawable } from '../rendering/Drawable.js';
|
|
3
|
+
import { Texture } from '../rendering/texture/Texture.js';
|
|
4
|
+
import { Spritesheet } from '../rendering/sprite/Spritesheet.js';
|
|
5
|
+
import { ParticleGpuState } from './gpu/ParticleGpuState.js';
|
|
4
6
|
|
|
7
|
+
/// <reference types="@webgpu/types" />
|
|
8
|
+
const defaultCapacity = 4096;
|
|
5
9
|
/**
|
|
6
|
-
*
|
|
7
|
-
* {@link
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
|
|
12
|
-
|
|
10
|
+
* Lazily-initialised 1×1 opaque-white texture used as the default sprite
|
|
11
|
+
* when a {@link ParticleSystem} is constructed without one. Particles
|
|
12
|
+
* render as solid color quads (the per-particle `color` channel times
|
|
13
|
+
* white-with-alpha-1). Shared across systems to avoid wasted texture
|
|
14
|
+
* allocations.
|
|
15
|
+
*/
|
|
16
|
+
let defaultWhiteTexture = null;
|
|
17
|
+
const getDefaultWhiteTexture = () => {
|
|
18
|
+
if (defaultWhiteTexture === null) {
|
|
19
|
+
const canvas = document.createElement('canvas');
|
|
20
|
+
canvas.width = 1;
|
|
21
|
+
canvas.height = 1;
|
|
22
|
+
const ctx = canvas.getContext('2d');
|
|
23
|
+
if (ctx !== null) {
|
|
24
|
+
ctx.fillStyle = '#ffffff';
|
|
25
|
+
ctx.fillRect(0, 0, 1, 1);
|
|
26
|
+
}
|
|
27
|
+
defaultWhiteTexture = new Texture(canvas);
|
|
28
|
+
}
|
|
29
|
+
return defaultWhiteTexture;
|
|
30
|
+
};
|
|
31
|
+
/**
|
|
32
|
+
* The central coordinator of the particle pipeline. `ParticleSystem` is a
|
|
33
|
+
* {@link Drawable} that owns:
|
|
34
|
+
*
|
|
35
|
+
* - **SoA particle storage** — one typed array per attribute (position,
|
|
36
|
+
* velocity, scale, rotation, color, lifetime, ...), sized to a fixed
|
|
37
|
+
* capacity at construction. User code reads/writes via
|
|
38
|
+
* `system.posX[slot]`, `system.velX[slot]`, etc.
|
|
39
|
+
* - **Spawn modules** — write new particles into freshly allocated slots.
|
|
40
|
+
* - **Update modules** — mutate the live range each frame (forces, color
|
|
41
|
+
* blends, scale curves, drag, ...). Built-in modules ship both CPU and
|
|
42
|
+
* WGSL implementations; custom modules can opt into GPU acceleration by
|
|
43
|
+
* implementing `wgsl()`.
|
|
44
|
+
* - **Death modules** — fire once per dying particle, before its slot is
|
|
45
|
+
* recycled (sub-emitters, event hooks).
|
|
46
|
+
*
|
|
47
|
+
* **Auto-routing CPU vs GPU:** at first {@link update}, the system checks:
|
|
48
|
+
* if a `WebGpuBackend` was supplied AND every registered update module has
|
|
49
|
+
* `wgsl()`, the GPU path engages — a composite compute pipeline runs
|
|
50
|
+
* integration plus all module bodies in one dispatch and writes directly
|
|
51
|
+
* into the renderer's instance buffer (no CPU readback). Otherwise the CPU
|
|
52
|
+
* path runs the existing per-module `apply()` loops.
|
|
13
53
|
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
54
|
+
* **Per-frame order in {@link update} (CPU mode):**
|
|
55
|
+
* 1. Run every spawn module.
|
|
56
|
+
* 2. Integrate position from velocity, rotation from rotationSpeed, advance `elapsed`.
|
|
57
|
+
* 3. Run every update module on the live range.
|
|
58
|
+
* 4. Compact: scan `[0, liveCount)` forward, fire death modules on expired
|
|
59
|
+
* slots, copy survivors down. `liveCount` shrinks to the survivor count.
|
|
60
|
+
*
|
|
61
|
+
* **Per-frame order in {@link update} (GPU mode):**
|
|
62
|
+
* 1. Run every spawn module (CPU writes initial values into the spawn slot).
|
|
63
|
+
* 2. Detect expiries on CPU (via `elapsed >= lifetime`); fire death modules;
|
|
64
|
+
* set `lifetime[slot] = -1` sentinel + clear `alive[slot]` so the GPU
|
|
65
|
+
* shader skips them. **No compaction** — slots are recycled on next spawn.
|
|
66
|
+
* 3. Dispatch the composite compute pipeline. Integration + update modules
|
|
67
|
+
* + pack-instances run in one pass; the instance buffer is written
|
|
68
|
+
* directly. CPU SoA stays as-is for spawn writes.
|
|
69
|
+
*
|
|
70
|
+
* **Coordinate space:** particle positions are LOCAL to the system. The
|
|
71
|
+
* system's `getGlobalTransform()` is applied on top during rendering — both
|
|
72
|
+
* the WebGL2 and WebGPU shaders multiply `projection * translation * rotated`.
|
|
73
|
+
* Setting world-space positions on individual particles double-translates.
|
|
74
|
+
* Position the system itself via `system.setPosition(...)` and emit relative
|
|
75
|
+
* to `(0, 0)`.
|
|
76
|
+
*
|
|
77
|
+
* @example
|
|
78
|
+
* // Backend-agnostic — runs CPU on WebGL2, GPU on WebGPU automatically.
|
|
79
|
+
* const system = new ParticleSystem(loader.get(Texture, 'spark'), {
|
|
80
|
+
* capacity: 8192,
|
|
81
|
+
* backend: app.backend,
|
|
82
|
+
* });
|
|
83
|
+
*
|
|
84
|
+
* system.addSpawnModule(new RateSpawn({ rate: new Constant(60), ... }));
|
|
85
|
+
* system.addUpdateModule(new ApplyForce(0, 980)); // gravity, GPU-eligible
|
|
86
|
+
* system.addUpdateModule(new ColorOverLifetime(fireGradient));
|
|
87
|
+
* scene.addChild(system);
|
|
18
88
|
*/
|
|
19
89
|
class ParticleSystem extends Drawable {
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
90
|
+
/** Maximum particle count this system will store. Fixed at construction. */
|
|
91
|
+
capacity;
|
|
92
|
+
posX;
|
|
93
|
+
posY;
|
|
94
|
+
velX;
|
|
95
|
+
velY;
|
|
96
|
+
scaleX;
|
|
97
|
+
scaleY;
|
|
98
|
+
rotations;
|
|
99
|
+
rotationSpeeds;
|
|
100
|
+
color; // packed 0xAABBGGRR
|
|
101
|
+
elapsed; // seconds since spawn
|
|
102
|
+
lifetime; // total seconds before expiry; -1 sentinel for dead in GPU mode
|
|
103
|
+
textureIndex;
|
|
104
|
+
/**
|
|
105
|
+
* Number of currently live particles. In CPU mode this is exact: slots
|
|
106
|
+
* `[0, liveCount)` are all alive after each `update()`. In GPU mode
|
|
107
|
+
* this is a high-water mark — slots `[0, liveCount)` may contain dead
|
|
108
|
+
* holes (filled in by future spawns); use {@link aliveCount} for the
|
|
109
|
+
* actual alive count.
|
|
110
|
+
*/
|
|
111
|
+
liveCount = 0;
|
|
112
|
+
/**
|
|
113
|
+
* Per-slot alive flag (1 = alive, 0 = dead). Maintained in both CPU
|
|
114
|
+
* and GPU mode. Custom modules iterating the live range should check
|
|
115
|
+
* this to skip dead slots in GPU mode.
|
|
116
|
+
*/
|
|
117
|
+
alive;
|
|
118
|
+
_spawnModules = [];
|
|
119
|
+
_updateModules = [];
|
|
120
|
+
_deathModules = [];
|
|
121
|
+
_backend = null;
|
|
122
|
+
_device = null;
|
|
123
|
+
_gpuState = null;
|
|
124
|
+
_gpuMode = false;
|
|
125
|
+
_compiled = false;
|
|
126
|
+
_spawnHint = 0; // round-robin pointer for first-dead lookup in GPU mode
|
|
127
|
+
/**
|
|
128
|
+
* In GPU mode, slots whose CPU SoA values need re-uploading to the GPU
|
|
129
|
+
* (newly spawned, or just-expired with lifetime sentinel). Cleared
|
|
130
|
+
* after each compute dispatch. CPU never overwrites integrated GPU
|
|
131
|
+
* state — only dirty slots flow CPU → GPU.
|
|
132
|
+
*/
|
|
133
|
+
_gpuDirtySlots = new Set();
|
|
24
134
|
_texture;
|
|
135
|
+
_frames = [];
|
|
25
136
|
_textureFrame = new Rectangle();
|
|
26
137
|
_vertices = new Float32Array(4);
|
|
27
138
|
_texCoords = new Uint32Array(4);
|
|
28
139
|
_updateTexCoords = true;
|
|
29
140
|
_updateVertices = true;
|
|
30
|
-
constructor(
|
|
141
|
+
constructor(arg1, arg2, arg3) {
|
|
31
142
|
super();
|
|
32
|
-
|
|
143
|
+
// Disambiguate the four valid call shapes via instanceof checks.
|
|
144
|
+
// The TS overloads above already prevent illegal combinations like
|
|
145
|
+
// `(texture, sheet)` or `(sheet, frames)` at compile time; this
|
|
146
|
+
// narrowing only sorts out the legal ones.
|
|
147
|
+
let texture = null;
|
|
148
|
+
let frames = null;
|
|
149
|
+
let options = {};
|
|
150
|
+
if (arg1 instanceof Texture) {
|
|
151
|
+
texture = arg1;
|
|
152
|
+
if (Array.isArray(arg2)) {
|
|
153
|
+
frames = arg2;
|
|
154
|
+
options = arg3 ?? {};
|
|
155
|
+
}
|
|
156
|
+
else {
|
|
157
|
+
options = arg2 ?? {};
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
else if (arg1 instanceof Spritesheet) {
|
|
161
|
+
texture = arg1.texture;
|
|
162
|
+
frames = [...arg1.frames.values()];
|
|
163
|
+
options = arg2 ?? {};
|
|
164
|
+
}
|
|
165
|
+
else {
|
|
166
|
+
options = arg1 ?? {};
|
|
167
|
+
}
|
|
168
|
+
const capacity = options.capacity ?? defaultCapacity;
|
|
169
|
+
if (capacity <= 0 || !Number.isInteger(capacity)) {
|
|
170
|
+
throw new Error(`ParticleSystem capacity must be a positive integer (got ${capacity}).`);
|
|
171
|
+
}
|
|
172
|
+
this.capacity = capacity;
|
|
173
|
+
this.posX = new Float32Array(capacity);
|
|
174
|
+
this.posY = new Float32Array(capacity);
|
|
175
|
+
this.velX = new Float32Array(capacity);
|
|
176
|
+
this.velY = new Float32Array(capacity);
|
|
177
|
+
this.scaleX = new Float32Array(capacity);
|
|
178
|
+
this.scaleY = new Float32Array(capacity);
|
|
179
|
+
this.rotations = new Float32Array(capacity);
|
|
180
|
+
this.rotationSpeeds = new Float32Array(capacity);
|
|
181
|
+
this.color = new Uint32Array(capacity);
|
|
182
|
+
this.elapsed = new Float32Array(capacity);
|
|
183
|
+
this.lifetime = new Float32Array(capacity);
|
|
184
|
+
this.textureIndex = new Uint16Array(capacity);
|
|
185
|
+
this.alive = new Uint8Array(capacity);
|
|
186
|
+
this._device = options.device ?? null;
|
|
187
|
+
this._texture = texture ?? getDefaultWhiteTexture();
|
|
188
|
+
if (frames !== null) {
|
|
189
|
+
for (const frame of frames) {
|
|
190
|
+
this._frames.push(frame.clone());
|
|
191
|
+
}
|
|
192
|
+
}
|
|
33
193
|
this.resetTextureFrame();
|
|
34
194
|
}
|
|
35
195
|
get texture() {
|
|
@@ -45,11 +205,17 @@ class ParticleSystem extends Drawable {
|
|
|
45
205
|
this.setTextureFrame(frame);
|
|
46
206
|
}
|
|
47
207
|
/**
|
|
48
|
-
*
|
|
49
|
-
*
|
|
50
|
-
*
|
|
51
|
-
* sprite relative to its world position.
|
|
208
|
+
* Atlas frames declared on this system, or empty when the texture is
|
|
209
|
+
* used as a single frame. Each particle's `textureIndex[i]` selects
|
|
210
|
+
* an entry from this list; out-of-range indices are clamped to 0.
|
|
52
211
|
*/
|
|
212
|
+
get frames() {
|
|
213
|
+
return this._frames;
|
|
214
|
+
}
|
|
215
|
+
/** `true` when the system declares more than one atlas frame. */
|
|
216
|
+
get hasAtlas() {
|
|
217
|
+
return this._frames.length > 1;
|
|
218
|
+
}
|
|
53
219
|
get vertices() {
|
|
54
220
|
if (this._updateVertices) {
|
|
55
221
|
const { x, y, width, height } = this._textureFrame;
|
|
@@ -63,12 +229,6 @@ class ParticleSystem extends Drawable {
|
|
|
63
229
|
}
|
|
64
230
|
return this._vertices;
|
|
65
231
|
}
|
|
66
|
-
/**
|
|
67
|
-
* Packed UV coordinates for the current {@link textureFrame} as four
|
|
68
|
-
* `Uint32` values, each encoding a `(u, v)` pair in the upper/lower 16
|
|
69
|
-
* bits (normalised to 0–65535). Vertex order respects
|
|
70
|
-
* {@link Texture.flipY}. Recomputed lazily on texture or frame changes.
|
|
71
|
-
*/
|
|
72
232
|
get texCoords() {
|
|
73
233
|
if (this._updateTexCoords) {
|
|
74
234
|
const { width, height } = this._texture;
|
|
@@ -93,27 +253,32 @@ class ParticleSystem extends Drawable {
|
|
|
93
253
|
}
|
|
94
254
|
return this._texCoords;
|
|
95
255
|
}
|
|
96
|
-
|
|
97
|
-
|
|
256
|
+
/** `true` when the system is running on the GPU compute pipeline. */
|
|
257
|
+
get gpuMode() {
|
|
258
|
+
return this._gpuMode;
|
|
98
259
|
}
|
|
99
|
-
|
|
100
|
-
|
|
260
|
+
/** GPU-side state, or `null` in CPU mode. */
|
|
261
|
+
get gpuState() {
|
|
262
|
+
return this._gpuState;
|
|
101
263
|
}
|
|
102
|
-
|
|
103
|
-
|
|
264
|
+
/** Actual count of live particles (slots with `alive[i] === 1`). May differ from `liveCount` in GPU mode. */
|
|
265
|
+
get aliveCount() {
|
|
266
|
+
let count = 0;
|
|
267
|
+
for (let i = 0; i < this.liveCount; i++) {
|
|
268
|
+
if (this.alive[i])
|
|
269
|
+
count++;
|
|
270
|
+
}
|
|
271
|
+
return count;
|
|
104
272
|
}
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
273
|
+
get spawnModules() {
|
|
274
|
+
return this._spawnModules;
|
|
275
|
+
}
|
|
276
|
+
get updateModules() {
|
|
277
|
+
return this._updateModules;
|
|
278
|
+
}
|
|
279
|
+
get deathModules() {
|
|
280
|
+
return this._deathModules;
|
|
112
281
|
}
|
|
113
|
-
/**
|
|
114
|
-
* Replaces the particle sprite texture and resets the texture frame to
|
|
115
|
-
* cover the full new texture. No-ops if `texture` is the same instance.
|
|
116
|
-
*/
|
|
117
282
|
setTexture(texture) {
|
|
118
283
|
if (this._texture !== texture) {
|
|
119
284
|
this._texture = texture;
|
|
@@ -121,11 +286,6 @@ class ParticleSystem extends Drawable {
|
|
|
121
286
|
}
|
|
122
287
|
return this;
|
|
123
288
|
}
|
|
124
|
-
/**
|
|
125
|
-
* Sets the sub-rectangle of the texture used as the particle sprite,
|
|
126
|
-
* invalidating cached vertices and UV coordinates and updating the system's
|
|
127
|
-
* local bounds to match the frame dimensions.
|
|
128
|
-
*/
|
|
129
289
|
setTextureFrame(frame) {
|
|
130
290
|
this._textureFrame.copy(frame);
|
|
131
291
|
this._updateTexCoords = true;
|
|
@@ -134,121 +294,274 @@ class ParticleSystem extends Drawable {
|
|
|
134
294
|
this._invalidateBoundsCascade();
|
|
135
295
|
return this;
|
|
136
296
|
}
|
|
137
|
-
/** Resets the texture frame to the full dimensions of the current texture. */
|
|
138
297
|
resetTextureFrame() {
|
|
139
298
|
return this.setTextureFrame(Rectangle.temp.set(0, 0, this._texture.width, this._texture.height));
|
|
140
299
|
}
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
this._emitters.push(emitter);
|
|
300
|
+
addSpawnModule(mod) {
|
|
301
|
+
this._spawnModules.push(mod);
|
|
144
302
|
return this;
|
|
145
303
|
}
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
emitter.destroy();
|
|
304
|
+
addUpdateModule(mod) {
|
|
305
|
+
if (this._compiled) {
|
|
306
|
+
throw new Error('Cannot add update modules after the system has been compiled (first update). Register all modules before the first update().');
|
|
150
307
|
}
|
|
151
|
-
this.
|
|
308
|
+
this._updateModules.push(mod);
|
|
152
309
|
return this;
|
|
153
310
|
}
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
this._affectors.push(affector);
|
|
311
|
+
addDeathModule(mod) {
|
|
312
|
+
this._deathModules.push(mod);
|
|
157
313
|
return this;
|
|
158
314
|
}
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
}
|
|
164
|
-
this._affectors.length = 0;
|
|
315
|
+
clearSpawnModules() {
|
|
316
|
+
for (const mod of this._spawnModules)
|
|
317
|
+
mod.destroy();
|
|
318
|
+
this._spawnModules.length = 0;
|
|
165
319
|
return this;
|
|
166
320
|
}
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
*/
|
|
173
|
-
requestParticle() {
|
|
174
|
-
return this._graveyard.pop() || new Particle();
|
|
321
|
+
clearUpdateModules() {
|
|
322
|
+
for (const mod of this._updateModules)
|
|
323
|
+
mod.destroy();
|
|
324
|
+
this._updateModules.length = 0;
|
|
325
|
+
return this;
|
|
175
326
|
}
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
327
|
+
clearDeathModules() {
|
|
328
|
+
for (const mod of this._deathModules)
|
|
329
|
+
mod.destroy();
|
|
330
|
+
this._deathModules.length = 0;
|
|
179
331
|
return this;
|
|
180
332
|
}
|
|
181
333
|
/**
|
|
182
|
-
*
|
|
183
|
-
*
|
|
184
|
-
*
|
|
185
|
-
*
|
|
334
|
+
* Allocates a particle slot and returns its index. Returns `-1` when
|
|
335
|
+
* the system is at {@link capacity}.
|
|
336
|
+
*
|
|
337
|
+
* **CPU mode:** slots are dense in `[0, liveCount)`. `spawn()` returns
|
|
338
|
+
* the next sequential slot; `liveCount++`.
|
|
339
|
+
*
|
|
340
|
+
* **GPU mode:** slots may have dead holes. `spawn()` finds the first
|
|
341
|
+
* `alive[i] === 0` slot via a round-robin hint pointer (amortised O(1),
|
|
342
|
+
* worst case O(capacity) on full systems).
|
|
186
343
|
*/
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
344
|
+
spawn() {
|
|
345
|
+
if (this._gpuMode) {
|
|
346
|
+
return this._spawnGpu();
|
|
347
|
+
}
|
|
348
|
+
return this._spawnCpu();
|
|
349
|
+
}
|
|
350
|
+
/** Resets the system to zero live particles without destroying it. */
|
|
351
|
+
clearParticles() {
|
|
352
|
+
this.liveCount = 0;
|
|
353
|
+
this._spawnHint = 0;
|
|
354
|
+
this.alive.fill(0);
|
|
355
|
+
this.lifetime.fill(0);
|
|
356
|
+
this.elapsed.fill(0);
|
|
192
357
|
return this;
|
|
193
358
|
}
|
|
194
359
|
/**
|
|
195
|
-
*
|
|
196
|
-
*
|
|
360
|
+
* Engine-side render hook. Captures the active backend on each call so
|
|
361
|
+
* the next `update()` can compile a GPU pipeline if the backend turned
|
|
362
|
+
* out to be `WebGpuBackend`. Re-captures and rebuilds when the backend
|
|
363
|
+
* reference changes (e.g. after device-loss recovery).
|
|
197
364
|
*/
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
365
|
+
render(backend) {
|
|
366
|
+
if (this._backend !== backend) {
|
|
367
|
+
this._backend = backend;
|
|
368
|
+
if (this._gpuState !== null) {
|
|
369
|
+
this._gpuState.destroy();
|
|
370
|
+
this._gpuState = null;
|
|
371
|
+
}
|
|
372
|
+
this._gpuMode = false;
|
|
373
|
+
this._compiled = false;
|
|
201
374
|
}
|
|
202
|
-
|
|
203
|
-
|
|
375
|
+
return super.render(backend);
|
|
376
|
+
}
|
|
377
|
+
/** Per-frame entry point. Routes to CPU or GPU pipeline based on auto-detection at first call. */
|
|
378
|
+
update(delta) {
|
|
379
|
+
if (!this._compiled) {
|
|
380
|
+
this._compile();
|
|
381
|
+
}
|
|
382
|
+
const dt = delta.seconds;
|
|
383
|
+
// 1. Spawn (CPU writes SoA in both modes).
|
|
384
|
+
for (let i = 0; i < this._spawnModules.length; i++) {
|
|
385
|
+
this._spawnModules[i].apply(this, dt);
|
|
386
|
+
}
|
|
387
|
+
if (this._gpuMode) {
|
|
388
|
+
this._updateGpu(dt);
|
|
389
|
+
}
|
|
390
|
+
else {
|
|
391
|
+
this._updateCpu(dt);
|
|
204
392
|
}
|
|
205
|
-
this._particles.length = 0;
|
|
206
|
-
this._graveyard.length = 0;
|
|
207
393
|
return this;
|
|
208
394
|
}
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
const
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
395
|
+
destroy() {
|
|
396
|
+
super.destroy();
|
|
397
|
+
this.clearSpawnModules();
|
|
398
|
+
this.clearUpdateModules();
|
|
399
|
+
this.clearDeathModules();
|
|
400
|
+
if (this._gpuState !== null) {
|
|
401
|
+
this._gpuState.destroy();
|
|
402
|
+
this._gpuState = null;
|
|
403
|
+
}
|
|
404
|
+
for (const frame of this._frames) {
|
|
405
|
+
frame.destroy();
|
|
406
|
+
}
|
|
407
|
+
this._frames.length = 0;
|
|
408
|
+
this._gpuMode = false;
|
|
409
|
+
this._compiled = false;
|
|
410
|
+
this.liveCount = 0;
|
|
411
|
+
this.alive.fill(0);
|
|
412
|
+
this._textureFrame.destroy();
|
|
413
|
+
}
|
|
414
|
+
_compile() {
|
|
415
|
+
this._compiled = true;
|
|
416
|
+
// Duck-typed `instanceof WebGpuBackend` — avoids importing the
|
|
417
|
+
// backend class (which registers a renderer for ParticleSystem
|
|
418
|
+
// and would create a circular dependency). WebGl2Backend has no
|
|
419
|
+
// `device` field, so this naturally falls back to CPU mode.
|
|
420
|
+
const backendDevice = this._backend?.device ?? null;
|
|
421
|
+
const device = this._device ?? backendDevice;
|
|
422
|
+
if (device === null) {
|
|
423
|
+
return;
|
|
424
|
+
}
|
|
425
|
+
const allEligible = this._updateModules.every((m) => typeof m.wgsl === 'function');
|
|
426
|
+
if (!allEligible) {
|
|
427
|
+
return;
|
|
428
|
+
}
|
|
429
|
+
this._gpuState = new ParticleGpuState(device, this.capacity, this._updateModules, this._frames, this._texture);
|
|
430
|
+
this._gpuMode = true;
|
|
431
|
+
// Mark every currently-alive slot dirty so the initial upload
|
|
432
|
+
// matches CPU state; subsequent frames only push deltas.
|
|
433
|
+
for (let i = 0; i < this.liveCount; i++) {
|
|
434
|
+
if (this.alive[i])
|
|
435
|
+
this._gpuDirtySlots.add(i);
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
_spawnCpu() {
|
|
439
|
+
if (this.liveCount >= this.capacity) {
|
|
440
|
+
return -1;
|
|
441
|
+
}
|
|
442
|
+
const slot = this.liveCount++;
|
|
443
|
+
this.alive[slot] = 1;
|
|
444
|
+
this.elapsed[slot] = 0;
|
|
445
|
+
return slot;
|
|
446
|
+
}
|
|
447
|
+
_spawnGpu() {
|
|
448
|
+
const capacity = this.capacity;
|
|
449
|
+
const alive = this.alive;
|
|
450
|
+
const start = this._spawnHint;
|
|
451
|
+
// Search forward from hint, then wrap.
|
|
452
|
+
for (let i = start; i < capacity; i++) {
|
|
453
|
+
if (alive[i] === 0) {
|
|
454
|
+
alive[i] = 1;
|
|
455
|
+
this.elapsed[i] = 0;
|
|
456
|
+
this._spawnHint = i + 1 === capacity ? 0 : i + 1;
|
|
457
|
+
if (i >= this.liveCount)
|
|
458
|
+
this.liveCount = i + 1;
|
|
459
|
+
this._gpuDirtySlots.add(i);
|
|
460
|
+
return i;
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
for (let i = 0; i < start; i++) {
|
|
464
|
+
if (alive[i] === 0) {
|
|
465
|
+
alive[i] = 1;
|
|
466
|
+
this.elapsed[i] = 0;
|
|
467
|
+
this._spawnHint = i + 1;
|
|
468
|
+
if (i >= this.liveCount)
|
|
469
|
+
this.liveCount = i + 1;
|
|
470
|
+
this._gpuDirtySlots.add(i);
|
|
471
|
+
return i;
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
return -1;
|
|
475
|
+
}
|
|
476
|
+
_updateCpu(dt) {
|
|
477
|
+
const { posX, posY, velX, velY, rotations, rotationSpeeds, elapsed } = this;
|
|
478
|
+
const liveCount = this.liveCount;
|
|
479
|
+
for (let i = 0; i < liveCount; i++) {
|
|
480
|
+
posX[i] += velX[i] * dt;
|
|
481
|
+
posY[i] += velY[i] * dt;
|
|
482
|
+
rotations[i] += rotationSpeeds[i] * dt;
|
|
483
|
+
elapsed[i] += dt;
|
|
484
|
+
}
|
|
485
|
+
for (let i = 0; i < this._updateModules.length; i++) {
|
|
486
|
+
this._updateModules[i].apply(this, dt);
|
|
487
|
+
}
|
|
488
|
+
// Compact: forward pass, fire death modules on expired, copy survivors down.
|
|
489
|
+
const lifetime = this.lifetime;
|
|
490
|
+
const alive = this.alive;
|
|
491
|
+
const deathModules = this._deathModules;
|
|
492
|
+
let writeIndex = 0;
|
|
493
|
+
for (let readIndex = 0; readIndex < this.liveCount; readIndex++) {
|
|
494
|
+
if (elapsed[readIndex] >= lifetime[readIndex]) {
|
|
495
|
+
for (let m = 0; m < deathModules.length; m++) {
|
|
496
|
+
deathModules[m].onDeath(this, readIndex);
|
|
497
|
+
}
|
|
498
|
+
alive[readIndex] = 0;
|
|
231
499
|
continue;
|
|
232
500
|
}
|
|
233
|
-
if (
|
|
234
|
-
|
|
235
|
-
|
|
501
|
+
if (writeIndex !== readIndex) {
|
|
502
|
+
this._copySlot(readIndex, writeIndex);
|
|
503
|
+
alive[writeIndex] = 1;
|
|
236
504
|
}
|
|
237
|
-
|
|
238
|
-
|
|
505
|
+
writeIndex++;
|
|
506
|
+
}
|
|
507
|
+
for (let i = writeIndex; i < this.liveCount; i++) {
|
|
508
|
+
alive[i] = 0;
|
|
509
|
+
}
|
|
510
|
+
this.liveCount = writeIndex;
|
|
511
|
+
}
|
|
512
|
+
_updateGpu(dt) {
|
|
513
|
+
// CPU advances its own copy of `elapsed` for expire detection only.
|
|
514
|
+
// GPU's `timing[idx].x` is advanced independently inside the compute
|
|
515
|
+
// shader; the two are never synced after spawn. They tick at the
|
|
516
|
+
// same rate (both add `dt` per frame) so they stay equivalent in
|
|
517
|
+
// practice (modulo numerical drift).
|
|
518
|
+
const elapsed = this.elapsed;
|
|
519
|
+
const lifetime = this.lifetime;
|
|
520
|
+
const alive = this.alive;
|
|
521
|
+
const deathModules = this._deathModules;
|
|
522
|
+
const liveCount = this.liveCount;
|
|
523
|
+
for (let i = 0; i < liveCount; i++) {
|
|
524
|
+
if (alive[i] === 0)
|
|
525
|
+
continue;
|
|
526
|
+
elapsed[i] += dt;
|
|
527
|
+
if (elapsed[i] >= lifetime[i]) {
|
|
528
|
+
for (let m = 0; m < deathModules.length; m++) {
|
|
529
|
+
deathModules[m].onDeath(this, i);
|
|
530
|
+
}
|
|
531
|
+
alive[i] = 0;
|
|
532
|
+
lifetime[i] = -1; // sentinel — GPU shader skips
|
|
533
|
+
this._gpuDirtySlots.add(i); // upload the sentinel so GPU sees the death
|
|
239
534
|
}
|
|
240
535
|
}
|
|
241
|
-
|
|
242
|
-
|
|
536
|
+
// Trim trailing dead slots.
|
|
537
|
+
let newLiveCount = this.liveCount;
|
|
538
|
+
while (newLiveCount > 0 && alive[newLiveCount - 1] === 0) {
|
|
539
|
+
newLiveCount--;
|
|
243
540
|
}
|
|
244
|
-
|
|
541
|
+
this.liveCount = newLiveCount;
|
|
542
|
+
// Push dirty slots (new spawns + just-expired) to GPU. CPU is NOT
|
|
543
|
+
// the source of truth for integrated position/velocity/etc. after
|
|
544
|
+
// spawn — uploading the full live range every frame would wipe
|
|
545
|
+
// out GPU's integrated state.
|
|
546
|
+
if (this._gpuDirtySlots.size > 0) {
|
|
547
|
+
this._gpuState.uploadDirty(this, this._gpuDirtySlots);
|
|
548
|
+
this._gpuDirtySlots.clear();
|
|
549
|
+
}
|
|
550
|
+
this._gpuState.dispatch(this, dt);
|
|
245
551
|
}
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
this.
|
|
249
|
-
this.
|
|
250
|
-
this.
|
|
251
|
-
this.
|
|
552
|
+
_copySlot(from, to) {
|
|
553
|
+
this.posX[to] = this.posX[from];
|
|
554
|
+
this.posY[to] = this.posY[from];
|
|
555
|
+
this.velX[to] = this.velX[from];
|
|
556
|
+
this.velY[to] = this.velY[from];
|
|
557
|
+
this.scaleX[to] = this.scaleX[from];
|
|
558
|
+
this.scaleY[to] = this.scaleY[from];
|
|
559
|
+
this.rotations[to] = this.rotations[from];
|
|
560
|
+
this.rotationSpeeds[to] = this.rotationSpeeds[from];
|
|
561
|
+
this.color[to] = this.color[from];
|
|
562
|
+
this.elapsed[to] = this.elapsed[from];
|
|
563
|
+
this.lifetime[to] = this.lifetime[from];
|
|
564
|
+
this.textureIndex[to] = this.textureIndex[from];
|
|
252
565
|
}
|
|
253
566
|
}
|
|
254
567
|
|