@codexo/exojs-particles 0.12.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/LICENSE +21 -0
- package/README.md +129 -0
- package/dist/esm/ParticleSystem.d.ts +212 -0
- package/dist/esm/ParticleSystem.js +570 -0
- package/dist/esm/ParticleSystem.js.map +1 -0
- package/dist/esm/distributions/BoxArea.d.ts +17 -0
- package/dist/esm/distributions/BoxArea.js +48 -0
- package/dist/esm/distributions/BoxArea.js.map +1 -0
- package/dist/esm/distributions/CircleArea.d.ts +19 -0
- package/dist/esm/distributions/CircleArea.js +33 -0
- package/dist/esm/distributions/CircleArea.js.map +1 -0
- package/dist/esm/distributions/ColorGradient.d.ts +40 -0
- package/dist/esm/distributions/ColorGradient.js +72 -0
- package/dist/esm/distributions/ColorGradient.js.map +1 -0
- package/dist/esm/distributions/ConeDirection.d.ts +28 -0
- package/dist/esm/distributions/ConeDirection.js +44 -0
- package/dist/esm/distributions/ConeDirection.js.map +1 -0
- package/dist/esm/distributions/Constant.d.ts +17 -0
- package/dist/esm/distributions/Constant.js +35 -0
- package/dist/esm/distributions/Constant.js.map +1 -0
- package/dist/esm/distributions/Curve.d.ts +30 -0
- package/dist/esm/distributions/Curve.js +53 -0
- package/dist/esm/distributions/Curve.js.map +1 -0
- package/dist/esm/distributions/Distribution.d.ts +45 -0
- package/dist/esm/distributions/LineSegment.d.ts +15 -0
- package/dist/esm/distributions/LineSegment.js +27 -0
- package/dist/esm/distributions/LineSegment.js.map +1 -0
- package/dist/esm/distributions/Range.d.ts +12 -0
- package/dist/esm/distributions/Range.js +19 -0
- package/dist/esm/distributions/Range.js.map +1 -0
- package/dist/esm/distributions/VectorRange.d.ts +20 -0
- package/dist/esm/distributions/VectorRange.js +31 -0
- package/dist/esm/distributions/VectorRange.js.map +1 -0
- package/dist/esm/distributions/index.d.ts +12 -0
- package/dist/esm/gpu/ParticleGpuState.d.ts +57 -0
- package/dist/esm/gpu/ParticleGpuState.js +535 -0
- package/dist/esm/gpu/ParticleGpuState.js.map +1 -0
- package/dist/esm/index.d.ts +1 -0
- package/dist/esm/index.js +32 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/modules/AlphaFadeOverLifetime.d.ts +24 -0
- package/dist/esm/modules/AlphaFadeOverLifetime.js +64 -0
- package/dist/esm/modules/AlphaFadeOverLifetime.js.map +1 -0
- package/dist/esm/modules/ApplyForce.d.ts +20 -0
- package/dist/esm/modules/ApplyForce.js +48 -0
- package/dist/esm/modules/ApplyForce.js.map +1 -0
- package/dist/esm/modules/AttractToPoint.d.ts +27 -0
- package/dist/esm/modules/AttractToPoint.js +73 -0
- package/dist/esm/modules/AttractToPoint.js.map +1 -0
- package/dist/esm/modules/BurstSpawn.d.ts +53 -0
- package/dist/esm/modules/BurstSpawn.js +93 -0
- package/dist/esm/modules/BurstSpawn.js.map +1 -0
- package/dist/esm/modules/ColorOverLifetime.d.ts +22 -0
- package/dist/esm/modules/ColorOverLifetime.js +65 -0
- package/dist/esm/modules/ColorOverLifetime.js.map +1 -0
- package/dist/esm/modules/ColorOverSpeed.d.ts +27 -0
- package/dist/esm/modules/ColorOverSpeed.js +86 -0
- package/dist/esm/modules/ColorOverSpeed.js.map +1 -0
- package/dist/esm/modules/DeathModule.d.ts +24 -0
- package/dist/esm/modules/DeathModule.js +25 -0
- package/dist/esm/modules/DeathModule.js.map +1 -0
- package/dist/esm/modules/Drag.d.ts +20 -0
- package/dist/esm/modules/Drag.js +43 -0
- package/dist/esm/modules/Drag.js.map +1 -0
- package/dist/esm/modules/OrbitalForce.d.ts +28 -0
- package/dist/esm/modules/OrbitalForce.js +65 -0
- package/dist/esm/modules/OrbitalForce.js.map +1 -0
- package/dist/esm/modules/RateSpawn.d.ts +41 -0
- package/dist/esm/modules/RateSpawn.js +75 -0
- package/dist/esm/modules/RateSpawn.js.map +1 -0
- package/dist/esm/modules/RepelFromPoint.d.ts +24 -0
- package/dist/esm/modules/RepelFromPoint.js +76 -0
- package/dist/esm/modules/RepelFromPoint.js.map +1 -0
- package/dist/esm/modules/RotateOverLifetime.d.ts +20 -0
- package/dist/esm/modules/RotateOverLifetime.js +41 -0
- package/dist/esm/modules/RotateOverLifetime.js.map +1 -0
- package/dist/esm/modules/ScaleOverLifetime.d.ts +26 -0
- package/dist/esm/modules/ScaleOverLifetime.js +59 -0
- package/dist/esm/modules/ScaleOverLifetime.js.map +1 -0
- package/dist/esm/modules/SpawnModule.d.ts +30 -0
- package/dist/esm/modules/SpawnModule.js +31 -0
- package/dist/esm/modules/SpawnModule.js.map +1 -0
- package/dist/esm/modules/SpawnOnDeath.d.ts +24 -0
- package/dist/esm/modules/SpawnOnDeath.js +47 -0
- package/dist/esm/modules/SpawnOnDeath.js.map +1 -0
- package/dist/esm/modules/Turbulence.d.ts +30 -0
- package/dist/esm/modules/Turbulence.js +122 -0
- package/dist/esm/modules/Turbulence.js.map +1 -0
- package/dist/esm/modules/UpdateModule.d.ts +95 -0
- package/dist/esm/modules/UpdateModule.js +66 -0
- package/dist/esm/modules/UpdateModule.js.map +1 -0
- package/dist/esm/modules/VelocityOverLifetime.d.ts +30 -0
- package/dist/esm/modules/VelocityOverLifetime.js +84 -0
- package/dist/esm/modules/VelocityOverLifetime.js.map +1 -0
- package/dist/esm/modules/WgslContribution.d.ts +81 -0
- package/dist/esm/modules/WgslContribution.js +34 -0
- package/dist/esm/modules/WgslContribution.js.map +1 -0
- package/dist/esm/modules/index.d.ts +22 -0
- package/dist/esm/particlesBuildInfo.d.ts +15 -0
- package/dist/esm/particlesBuildInfo.js +8 -0
- package/dist/esm/particlesBuildInfo.js.map +1 -0
- package/dist/esm/particlesExtension.d.ts +24 -0
- package/dist/esm/particlesExtension.js +49 -0
- package/dist/esm/particlesExtension.js.map +1 -0
- package/dist/esm/public.d.ts +9 -0
- package/dist/esm/register.d.ts +1 -0
- package/dist/esm/register.js +43 -0
- package/dist/esm/register.js.map +1 -0
- package/dist/esm/renderers/WebGl2ParticleRenderer.d.ts +45 -0
- package/dist/esm/renderers/WebGl2ParticleRenderer.js +325 -0
- package/dist/esm/renderers/WebGl2ParticleRenderer.js.map +1 -0
- package/dist/esm/renderers/WebGpuParticleRenderer.d.ts +48 -0
- package/dist/esm/renderers/WebGpuParticleRenderer.js +553 -0
- package/dist/esm/renderers/WebGpuParticleRenderer.js.map +1 -0
- package/dist/esm/renderers/glsl/particle.frag.js +4 -0
- package/dist/esm/renderers/glsl/particle.frag.js.map +1 -0
- package/dist/esm/renderers/glsl/particle.vert.js +4 -0
- package/dist/esm/renderers/glsl/particle.vert.js.map +1 -0
- package/package.json +55 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Codexo
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
# @codexo/exojs-particles
|
|
2
|
+
|
|
3
|
+
Official ExoJS extension for GPU-accelerated particle systems.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```sh
|
|
8
|
+
npm install @codexo/exojs @codexo/exojs-particles
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
This package requires `@codexo/exojs` as a peer dependency. Both must be the same version.
|
|
12
|
+
|
|
13
|
+
## Core compatibility
|
|
14
|
+
|
|
15
|
+
| `@codexo/exojs-particles` | `@codexo/exojs` |
|
|
16
|
+
|---|---|
|
|
17
|
+
| 0.12.x | 0.12.x |
|
|
18
|
+
|
|
19
|
+
## Usage — side-effect-free root entry
|
|
20
|
+
|
|
21
|
+
Import `ParticleSystem` and supply it explicitly to your Application via `extensions`:
|
|
22
|
+
|
|
23
|
+
```ts
|
|
24
|
+
import { Application } from '@codexo/exojs';
|
|
25
|
+
import { ParticleSystem, particlesExtension } from '@codexo/exojs-particles';
|
|
26
|
+
|
|
27
|
+
const app = new Application({
|
|
28
|
+
extensions: [particlesExtension],
|
|
29
|
+
});
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
Importing from the root entry (`@codexo/exojs-particles`) does **not** register the extension globally. You control exactly which Applications receive the Particles extension.
|
|
33
|
+
|
|
34
|
+
## Extension descriptor
|
|
35
|
+
|
|
36
|
+
`particlesExtension` is the default descriptor. Use it when you want the default renderer batch size:
|
|
37
|
+
|
|
38
|
+
```ts
|
|
39
|
+
import { particlesExtension } from '@codexo/exojs-particles';
|
|
40
|
+
|
|
41
|
+
const app = new Application({ extensions: [particlesExtension] });
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## Custom batch size via `createParticlesExtension`
|
|
45
|
+
|
|
46
|
+
```ts
|
|
47
|
+
import { createParticlesExtension } from '@codexo/exojs-particles';
|
|
48
|
+
|
|
49
|
+
const app = new Application({
|
|
50
|
+
extensions: [createParticlesExtension({ batchSize: 8192 })],
|
|
51
|
+
});
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## `ApplicationOptions.extensions`
|
|
55
|
+
|
|
56
|
+
Pass any combination of descriptors:
|
|
57
|
+
|
|
58
|
+
```ts
|
|
59
|
+
const app = new Application({
|
|
60
|
+
extensions: [particlesExtension],
|
|
61
|
+
});
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
## `/register` convenience entry
|
|
65
|
+
|
|
66
|
+
Importing `/register` registers the default `particlesExtension` descriptor in the global `ExtensionRegistry`. Subsequently created Applications that use global defaults will automatically receive the Particles extension. This is the only side-effectful entry in this package.
|
|
67
|
+
|
|
68
|
+
```ts
|
|
69
|
+
// Side effect: registers particlesExtension in the global ExtensionRegistry.
|
|
70
|
+
import '@codexo/exojs-particles/register';
|
|
71
|
+
|
|
72
|
+
// All named exports are re-exported from /register:
|
|
73
|
+
import { ParticleSystem, particlesExtension } from '@codexo/exojs-particles/register';
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
**Note:** `/register` does not use automatic discovery. It explicitly calls `ExtensionRegistry.register(particlesExtension)` at module evaluation time.
|
|
77
|
+
|
|
78
|
+
## Minimal working example
|
|
79
|
+
|
|
80
|
+
```ts
|
|
81
|
+
import { Application, Scene, Texture } from '@codexo/exojs';
|
|
82
|
+
import {
|
|
83
|
+
ParticleSystem,
|
|
84
|
+
particlesExtension,
|
|
85
|
+
RateSpawn,
|
|
86
|
+
Constant,
|
|
87
|
+
} from '@codexo/exojs-particles';
|
|
88
|
+
|
|
89
|
+
const app = new Application({ extensions: [particlesExtension] });
|
|
90
|
+
document.body.append(app.canvas);
|
|
91
|
+
|
|
92
|
+
class DemoScene extends Scene {
|
|
93
|
+
system!: ParticleSystem;
|
|
94
|
+
|
|
95
|
+
override async load(loader) {
|
|
96
|
+
await loader.load(Texture, { particle: '/particle.png' });
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
override create(loader) {
|
|
100
|
+
this.system = new ParticleSystem(loader.get(Texture, 'particle'), {
|
|
101
|
+
capacity: 1024,
|
|
102
|
+
});
|
|
103
|
+
this.system.addSpawnModule(
|
|
104
|
+
new RateSpawn({ rate: new Constant(120), lifetime: new Constant(2) }),
|
|
105
|
+
);
|
|
106
|
+
this.addChild(this.system);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
app.scenes.start(DemoScene);
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
## WebGL2 and WebGPU support
|
|
114
|
+
|
|
115
|
+
- **WebGL2**: full instanced-draw renderer
|
|
116
|
+
- **WebGPU**: compute-shader GPU simulation when a WebGPU device is available; falls back to CPU simulation otherwise
|
|
117
|
+
|
|
118
|
+
## Destruction and ownership
|
|
119
|
+
|
|
120
|
+
`ParticleSystem` owns its GPU resources. Call `system.destroy()` when you are done with it. The `Application` or parent scene does not automatically destroy nested systems.
|
|
121
|
+
|
|
122
|
+
## Links
|
|
123
|
+
|
|
124
|
+
- [Official ExoJS Particles guide](https://exojs.dev/guides/extensions/particles)
|
|
125
|
+
- [API reference](https://exojs.dev/api/exojs-particles)
|
|
126
|
+
|
|
127
|
+
## License
|
|
128
|
+
|
|
129
|
+
MIT
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
import type { Time } from '@codexo/exojs';
|
|
2
|
+
import { Rectangle } from '@codexo/exojs';
|
|
3
|
+
import { Drawable } from '@codexo/exojs';
|
|
4
|
+
import type { RenderPlanBuilder } from '@codexo/exojs/rendering';
|
|
5
|
+
import { Spritesheet } from '@codexo/exojs';
|
|
6
|
+
import { Texture } from '@codexo/exojs';
|
|
7
|
+
import { ParticleGpuState } from '#gpu/ParticleGpuState';
|
|
8
|
+
import type { DeathModule } from '#modules/DeathModule';
|
|
9
|
+
import type { SpawnModule } from '#modules/SpawnModule';
|
|
10
|
+
import type { UpdateModule } from '#modules/UpdateModule';
|
|
11
|
+
/**
|
|
12
|
+
* Options for {@link ParticleSystem}'s constructor — orthogonal config
|
|
13
|
+
* that's independent of the texture source. Texture / frames / spritesheet
|
|
14
|
+
* live in positional arguments to enforce mutual exclusivity at the type
|
|
15
|
+
* level (you can't pass both a texture and a spritesheet by accident).
|
|
16
|
+
*/
|
|
17
|
+
export interface ParticleSystemOptions {
|
|
18
|
+
/** Maximum particle count. Fixed at construction. Default 4096. */
|
|
19
|
+
capacity?: number;
|
|
20
|
+
/**
|
|
21
|
+
* Direct GPU device. Lets advanced consumers wire a `GPUDevice` owned
|
|
22
|
+
* outside an `Application` (or a mock device in tests). When omitted,
|
|
23
|
+
* the backend reference is captured automatically on the first
|
|
24
|
+
* {@link ParticleSystem.render} call — `WebGpuBackend` ⇒ GPU mode,
|
|
25
|
+
* anything else (incl. WebGL2) ⇒ CPU mode.
|
|
26
|
+
*/
|
|
27
|
+
device?: GPUDevice;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* The central coordinator of the particle pipeline. `ParticleSystem` is a
|
|
31
|
+
* {@link Drawable} that owns:
|
|
32
|
+
*
|
|
33
|
+
* - **SoA particle storage** — one typed array per attribute (position,
|
|
34
|
+
* velocity, scale, rotation, color, lifetime, ...), sized to a fixed
|
|
35
|
+
* capacity at construction. User code reads/writes via
|
|
36
|
+
* `system.posX[slot]`, `system.velX[slot]`, etc.
|
|
37
|
+
* - **Spawn modules** — write new particles into freshly allocated slots.
|
|
38
|
+
* - **Update modules** — mutate the live range each frame (forces, color
|
|
39
|
+
* blends, scale curves, drag, ...). Built-in modules ship both CPU and
|
|
40
|
+
* WGSL implementations; custom modules can opt into GPU acceleration by
|
|
41
|
+
* implementing `wgsl()`.
|
|
42
|
+
* - **Death modules** — fire once per dying particle, before its slot is
|
|
43
|
+
* recycled (sub-emitters, event hooks).
|
|
44
|
+
*
|
|
45
|
+
* **Auto-routing CPU vs GPU:** at first {@link update}, the system checks:
|
|
46
|
+
* if a `WebGpuBackend` was supplied AND every registered update module has
|
|
47
|
+
* `wgsl()`, the GPU path engages — a composite compute pipeline runs
|
|
48
|
+
* integration plus all module bodies in one dispatch and writes directly
|
|
49
|
+
* into the renderer's instance buffer (no CPU readback). Otherwise the CPU
|
|
50
|
+
* path runs the existing per-module `apply()` loops.
|
|
51
|
+
*
|
|
52
|
+
* **Per-frame order in {@link update} (CPU mode):**
|
|
53
|
+
* 1. Run every spawn module.
|
|
54
|
+
* 2. Integrate position from velocity, rotation from rotationSpeed, advance `elapsed`.
|
|
55
|
+
* 3. Run every update module on the live range.
|
|
56
|
+
* 4. Compact: scan `[0, liveCount)` forward, fire death modules on expired
|
|
57
|
+
* slots, copy survivors down. `liveCount` shrinks to the survivor count.
|
|
58
|
+
*
|
|
59
|
+
* **Per-frame order in {@link update} (GPU mode):**
|
|
60
|
+
* 1. Run every spawn module (CPU writes initial values into the spawn slot).
|
|
61
|
+
* 2. Detect expiries on CPU (via `elapsed >= lifetime`); fire death modules;
|
|
62
|
+
* set `lifetime[slot] = -1` sentinel + clear `alive[slot]` so the GPU
|
|
63
|
+
* shader skips them. **No compaction** — slots are recycled on next spawn.
|
|
64
|
+
* 3. Dispatch the composite compute pipeline. Integration + update modules
|
|
65
|
+
* + pack-instances run in one pass; the instance buffer is written
|
|
66
|
+
* directly. CPU SoA stays as-is for spawn writes.
|
|
67
|
+
*
|
|
68
|
+
* **Coordinate space:** particle positions are LOCAL to the system. The
|
|
69
|
+
* system's `getGlobalTransform()` is applied on top during rendering — both
|
|
70
|
+
* the WebGL2 and WebGPU shaders multiply `projection * translation * rotated`.
|
|
71
|
+
* Setting world-space positions on individual particles double-translates.
|
|
72
|
+
* Position the system itself via `system.setPosition(...)` and emit relative
|
|
73
|
+
* to `(0, 0)`.
|
|
74
|
+
*
|
|
75
|
+
* @example
|
|
76
|
+
* // Backend-agnostic — runs CPU on WebGL2, GPU on WebGPU automatically.
|
|
77
|
+
* const system = new ParticleSystem(loader.get(Texture, 'spark'), {
|
|
78
|
+
* capacity: 8192,
|
|
79
|
+
* backend: app.backend,
|
|
80
|
+
* });
|
|
81
|
+
*
|
|
82
|
+
* system.addSpawnModule(new RateSpawn({ rate: new Constant(60), ... }));
|
|
83
|
+
* system.addUpdateModule(new ApplyForce(0, 980)); // gravity, GPU-eligible
|
|
84
|
+
* system.addUpdateModule(new ColorOverLifetime(fireGradient));
|
|
85
|
+
* scene.addChild(system);
|
|
86
|
+
*/
|
|
87
|
+
export declare class ParticleSystem extends Drawable {
|
|
88
|
+
/** Maximum particle count this system will store. Fixed at construction. */
|
|
89
|
+
readonly capacity: number;
|
|
90
|
+
readonly posX: Float32Array;
|
|
91
|
+
readonly posY: Float32Array;
|
|
92
|
+
readonly velX: Float32Array;
|
|
93
|
+
readonly velY: Float32Array;
|
|
94
|
+
readonly scaleX: Float32Array;
|
|
95
|
+
readonly scaleY: Float32Array;
|
|
96
|
+
readonly rotations: Float32Array;
|
|
97
|
+
readonly rotationSpeeds: Float32Array;
|
|
98
|
+
readonly color: Uint32Array;
|
|
99
|
+
readonly elapsed: Float32Array;
|
|
100
|
+
readonly lifetime: Float32Array;
|
|
101
|
+
readonly textureIndex: Uint16Array;
|
|
102
|
+
/**
|
|
103
|
+
* Number of currently live particles. In CPU mode this is exact: slots
|
|
104
|
+
* `[0, liveCount)` are all alive after each `update()`. In GPU mode
|
|
105
|
+
* this is a high-water mark — slots `[0, liveCount)` may contain dead
|
|
106
|
+
* holes (filled in by future spawns); use {@link aliveCount} for the
|
|
107
|
+
* actual alive count.
|
|
108
|
+
*/
|
|
109
|
+
liveCount: number;
|
|
110
|
+
/**
|
|
111
|
+
* Per-slot alive flag (1 = alive, 0 = dead). Maintained in both CPU
|
|
112
|
+
* and GPU mode. Custom modules iterating the live range should check
|
|
113
|
+
* this to skip dead slots in GPU mode.
|
|
114
|
+
*/
|
|
115
|
+
readonly alive: Uint8Array;
|
|
116
|
+
private readonly _spawnModules;
|
|
117
|
+
private readonly _updateModules;
|
|
118
|
+
private readonly _deathModules;
|
|
119
|
+
private _backend;
|
|
120
|
+
private readonly _device;
|
|
121
|
+
private _gpuState;
|
|
122
|
+
private _gpuMode;
|
|
123
|
+
private _compiled;
|
|
124
|
+
private _spawnHint;
|
|
125
|
+
/**
|
|
126
|
+
* In GPU mode, slots whose CPU SoA values need re-uploading to the GPU
|
|
127
|
+
* (newly spawned, or just-expired with lifetime sentinel). Cleared
|
|
128
|
+
* after each compute dispatch. CPU never overwrites integrated GPU
|
|
129
|
+
* state — only dirty slots flow CPU → GPU.
|
|
130
|
+
*/
|
|
131
|
+
private readonly _gpuDirtySlots;
|
|
132
|
+
private _texture;
|
|
133
|
+
private readonly _frames;
|
|
134
|
+
private readonly _textureFrame;
|
|
135
|
+
private readonly _vertices;
|
|
136
|
+
private readonly _texCoords;
|
|
137
|
+
private _updateTexCoords;
|
|
138
|
+
private _updateVertices;
|
|
139
|
+
/** No texture — particles render as solid-color quads on a 1×1 white default. */
|
|
140
|
+
constructor(options?: ParticleSystemOptions);
|
|
141
|
+
/** Single texture, no atlas — every particle uses the full texture as one frame. */
|
|
142
|
+
constructor(texture: Texture, options?: ParticleSystemOptions);
|
|
143
|
+
/** Multi-frame atlas — each particle's `textureIndex` selects a frame. */
|
|
144
|
+
constructor(texture: Texture, frames: readonly Rectangle[], options?: ParticleSystemOptions);
|
|
145
|
+
/** Spritesheet shorthand — texture + frames pulled from the sheet. */
|
|
146
|
+
constructor(spritesheet: Spritesheet, options?: ParticleSystemOptions);
|
|
147
|
+
get texture(): Texture;
|
|
148
|
+
set texture(texture: Texture);
|
|
149
|
+
get textureFrame(): Rectangle;
|
|
150
|
+
set textureFrame(frame: Rectangle);
|
|
151
|
+
/**
|
|
152
|
+
* Atlas frames declared on this system, or empty when the texture is
|
|
153
|
+
* used as a single frame. Each particle's `textureIndex[i]` selects
|
|
154
|
+
* an entry from this list; out-of-range indices are clamped to 0.
|
|
155
|
+
*/
|
|
156
|
+
get frames(): readonly Rectangle[];
|
|
157
|
+
/** `true` when the system declares more than one atlas frame. */
|
|
158
|
+
get hasAtlas(): boolean;
|
|
159
|
+
get vertices(): Float32Array;
|
|
160
|
+
get texCoords(): Uint32Array;
|
|
161
|
+
/** `true` when the system is running on the GPU compute pipeline. */
|
|
162
|
+
get gpuMode(): boolean;
|
|
163
|
+
/** GPU-side state, or `null` in CPU mode. */
|
|
164
|
+
get gpuState(): ParticleGpuState | null;
|
|
165
|
+
/** Actual count of live particles (slots with `alive[i] === 1`). May differ from `liveCount` in GPU mode. */
|
|
166
|
+
get aliveCount(): number;
|
|
167
|
+
get spawnModules(): readonly SpawnModule[];
|
|
168
|
+
get updateModules(): readonly UpdateModule[];
|
|
169
|
+
get deathModules(): readonly DeathModule[];
|
|
170
|
+
setTexture(texture: Texture): this;
|
|
171
|
+
setTextureFrame(frame: Rectangle): this;
|
|
172
|
+
resetTextureFrame(): this;
|
|
173
|
+
addSpawnModule(mod: SpawnModule): this;
|
|
174
|
+
addUpdateModule(mod: UpdateModule): this;
|
|
175
|
+
addDeathModule(mod: DeathModule): this;
|
|
176
|
+
clearSpawnModules(): this;
|
|
177
|
+
clearUpdateModules(): this;
|
|
178
|
+
clearDeathModules(): this;
|
|
179
|
+
/**
|
|
180
|
+
* Allocates a particle slot and returns its index. Returns `-1` when
|
|
181
|
+
* the system is at {@link capacity}.
|
|
182
|
+
*
|
|
183
|
+
* **CPU mode:** slots are dense in `[0, liveCount)`. `spawn()` returns
|
|
184
|
+
* the next sequential slot; `liveCount++`.
|
|
185
|
+
*
|
|
186
|
+
* **GPU mode:** slots may have dead holes. `spawn()` finds the first
|
|
187
|
+
* `alive[i] === 0` slot via a round-robin hint pointer (amortised O(1),
|
|
188
|
+
* worst case O(capacity) on full systems).
|
|
189
|
+
*/
|
|
190
|
+
spawn(): number;
|
|
191
|
+
/** Resets the system to zero live particles without destroying it. */
|
|
192
|
+
clearParticles(): this;
|
|
193
|
+
/**
|
|
194
|
+
* @internal
|
|
195
|
+
*
|
|
196
|
+
* Collect-hook: captures the active backend before this node is emitted so
|
|
197
|
+
* the next `update()` can compile a GPU pipeline if the backend turned out
|
|
198
|
+
* to be `WebGpuBackend`. Re-captures and rebuilds when the backend
|
|
199
|
+
* reference changes (e.g. after device-loss recovery).
|
|
200
|
+
*/
|
|
201
|
+
/** @internal */
|
|
202
|
+
_collect(builder: RenderPlanBuilder, seq?: number): void;
|
|
203
|
+
/** Per-frame entry point. Routes to CPU or GPU pipeline based on auto-detection at first call. */
|
|
204
|
+
update(delta: Time): this;
|
|
205
|
+
destroy(): void;
|
|
206
|
+
private _compile;
|
|
207
|
+
private _spawnCpu;
|
|
208
|
+
private _spawnGpu;
|
|
209
|
+
private _updateCpu;
|
|
210
|
+
private _updateGpu;
|
|
211
|
+
private _copySlot;
|
|
212
|
+
}
|