@cazala/party 0.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +23 -0
- package/README.md +598 -0
- package/dist/engine.d.ts +113 -0
- package/dist/engine.d.ts.map +1 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +9628 -0
- package/dist/index.js.map +1 -0
- package/dist/interfaces.d.ts +223 -0
- package/dist/interfaces.d.ts.map +1 -0
- package/dist/module.d.ts +259 -0
- package/dist/module.d.ts.map +1 -0
- package/dist/modules/forces/behavior.d.ts +78 -0
- package/dist/modules/forces/behavior.d.ts.map +1 -0
- package/dist/modules/forces/boundary.d.ts +58 -0
- package/dist/modules/forces/boundary.d.ts.map +1 -0
- package/dist/modules/forces/collisions.d.ts +34 -0
- package/dist/modules/forces/collisions.d.ts.map +1 -0
- package/dist/modules/forces/environment.d.ts +62 -0
- package/dist/modules/forces/environment.d.ts.map +1 -0
- package/dist/modules/forces/fluids.d.ts +73 -0
- package/dist/modules/forces/fluids.d.ts.map +1 -0
- package/dist/modules/forces/grab.d.ts +56 -0
- package/dist/modules/forces/grab.d.ts.map +1 -0
- package/dist/modules/forces/interaction.d.ts +59 -0
- package/dist/modules/forces/interaction.d.ts.map +1 -0
- package/dist/modules/forces/joints.d.ts +87 -0
- package/dist/modules/forces/joints.d.ts.map +1 -0
- package/dist/modules/forces/sensors.d.ts +81 -0
- package/dist/modules/forces/sensors.d.ts.map +1 -0
- package/dist/modules/index.d.ts +13 -0
- package/dist/modules/index.d.ts.map +1 -0
- package/dist/modules/render/lines.d.ts +55 -0
- package/dist/modules/render/lines.d.ts.map +1 -0
- package/dist/modules/render/particles.d.ts +53 -0
- package/dist/modules/render/particles.d.ts.map +1 -0
- package/dist/modules/render/trails.d.ts +36 -0
- package/dist/modules/render/trails.d.ts.map +1 -0
- package/dist/oscillators.d.ts +66 -0
- package/dist/oscillators.d.ts.map +1 -0
- package/dist/particle.d.ts +19 -0
- package/dist/particle.d.ts.map +1 -0
- package/dist/runtimes/cpu/engine.d.ts +60 -0
- package/dist/runtimes/cpu/engine.d.ts.map +1 -0
- package/dist/runtimes/cpu/spatial-grid.d.ts +56 -0
- package/dist/runtimes/cpu/spatial-grid.d.ts.map +1 -0
- package/dist/runtimes/webgpu/builders/program.d.ts +56 -0
- package/dist/runtimes/webgpu/builders/program.d.ts.map +1 -0
- package/dist/runtimes/webgpu/builders/render-pass.d.ts +26 -0
- package/dist/runtimes/webgpu/builders/render-pass.d.ts.map +1 -0
- package/dist/runtimes/webgpu/engine.d.ts +73 -0
- package/dist/runtimes/webgpu/engine.d.ts.map +1 -0
- package/dist/runtimes/webgpu/gpu-resources.d.ts +141 -0
- package/dist/runtimes/webgpu/gpu-resources.d.ts.map +1 -0
- package/dist/runtimes/webgpu/module-registry.d.ts +53 -0
- package/dist/runtimes/webgpu/module-registry.d.ts.map +1 -0
- package/dist/runtimes/webgpu/particle-store.d.ts +43 -0
- package/dist/runtimes/webgpu/particle-store.d.ts.map +1 -0
- package/dist/runtimes/webgpu/render-pipeline.d.ts +49 -0
- package/dist/runtimes/webgpu/render-pipeline.d.ts.map +1 -0
- package/dist/runtimes/webgpu/shaders.d.ts +8 -0
- package/dist/runtimes/webgpu/shaders.d.ts.map +1 -0
- package/dist/runtimes/webgpu/simulation-pipeline.d.ts +22 -0
- package/dist/runtimes/webgpu/simulation-pipeline.d.ts.map +1 -0
- package/dist/runtimes/webgpu/spacial-grid.d.ts +27 -0
- package/dist/runtimes/webgpu/spacial-grid.d.ts.map +1 -0
- package/dist/spawner.d.ts +40 -0
- package/dist/spawner.d.ts.map +1 -0
- package/dist/vector.d.ts +61 -0
- package/dist/vector.d.ts.map +1 -0
- package/dist/view.d.ts +43 -0
- package/dist/view.d.ts.map +1 -0
- package/package.json +67 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 cazala
|
|
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.
|
|
22
|
+
|
|
23
|
+
|
package/README.md
ADDED
|
@@ -0,0 +1,598 @@
|
|
|
1
|
+
# @cazala/party
|
|
2
|
+
|
|
3
|
+
A high-performance TypeScript particle physics engine with dual runtime support (WebGPU compute + CPU fallback), modular architecture, and real-time parameter oscillation.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- **Dual Runtime Architecture**: Automatic WebGPU/CPU runtime selection with seamless fallback
|
|
8
|
+
- **GPU Compute Performance**: WebGPU shaders for parallel particle processing at scale
|
|
9
|
+
- **Modular Force System**: Pluggable physics modules with four-phase lifecycle
|
|
10
|
+
- **Spatial Grid Optimization**: Efficient O(1) neighbor queries for collision detection
|
|
11
|
+
- **Real-time Oscillators**: Animate any module parameter with configurable frequency and bounds
|
|
12
|
+
- **Advanced Rendering**: Trails, particle instancing, line rendering with multiple color modes
|
|
13
|
+
- **Session Management**: Export/import complete simulation configurations
|
|
14
|
+
- **Cross-platform**: Works in all modern browsers with automatic feature detection
|
|
15
|
+
|
|
16
|
+
## Installation
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
npm install @cazala/party
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
> **Development Note**: This package is part of a pnpm workspace. For development, clone the full repository and use `npm run setup` from the root.
|
|
23
|
+
|
|
24
|
+
## Quick Start
|
|
25
|
+
|
|
26
|
+
```typescript
|
|
27
|
+
import {
|
|
28
|
+
Engine,
|
|
29
|
+
// Force modules
|
|
30
|
+
Environment,
|
|
31
|
+
Boundary,
|
|
32
|
+
Collisions,
|
|
33
|
+
Behavior,
|
|
34
|
+
Fluids,
|
|
35
|
+
// Render modules
|
|
36
|
+
Particles,
|
|
37
|
+
Trails,
|
|
38
|
+
} from "@cazala/party";
|
|
39
|
+
|
|
40
|
+
const canvas = document.querySelector("canvas")!;
|
|
41
|
+
|
|
42
|
+
const forces = [
|
|
43
|
+
new Environment({
|
|
44
|
+
gravityStrength: 600,
|
|
45
|
+
gravityDirection: "down",
|
|
46
|
+
inertia: 0.05,
|
|
47
|
+
friction: 0.01,
|
|
48
|
+
}),
|
|
49
|
+
new Boundary({
|
|
50
|
+
mode: "bounce",
|
|
51
|
+
restitution: 0.9,
|
|
52
|
+
friction: 0.1,
|
|
53
|
+
}),
|
|
54
|
+
new Collisions({ restitution: 0.85 }),
|
|
55
|
+
new Behavior({
|
|
56
|
+
cohesion: 1.5,
|
|
57
|
+
alignment: 1.2,
|
|
58
|
+
separation: 12,
|
|
59
|
+
viewRadius: 100,
|
|
60
|
+
}),
|
|
61
|
+
new Fluids({
|
|
62
|
+
influenceRadius: 80,
|
|
63
|
+
pressureMultiplier: 25,
|
|
64
|
+
viscosity: 0.8,
|
|
65
|
+
}),
|
|
66
|
+
];
|
|
67
|
+
|
|
68
|
+
const render = [
|
|
69
|
+
new Trails({ trailDecay: 10, trailDiffuse: 4 }),
|
|
70
|
+
new Particles({ colorType: 2, hue: 0.55 }),
|
|
71
|
+
];
|
|
72
|
+
|
|
73
|
+
const engine = new Engine({
|
|
74
|
+
canvas,
|
|
75
|
+
forces,
|
|
76
|
+
render,
|
|
77
|
+
runtime: "auto", // Auto-selects WebGPU when available
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
await engine.initialize();
|
|
81
|
+
|
|
82
|
+
// Add particles
|
|
83
|
+
for (let i = 0; i < 100; i++) {
|
|
84
|
+
engine.addParticle({
|
|
85
|
+
x: Math.random() * canvas.width,
|
|
86
|
+
y: Math.random() * canvas.height,
|
|
87
|
+
vx: (Math.random() - 0.5) * 4,
|
|
88
|
+
vy: (Math.random() - 0.5) * 4,
|
|
89
|
+
mass: 1 + Math.random() * 2,
|
|
90
|
+
size: 3 + Math.random() * 7,
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
engine.play();
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
## Core Concepts
|
|
98
|
+
|
|
99
|
+
### Engine
|
|
100
|
+
|
|
101
|
+
The `Engine` class provides a unified API that automatically selects the best runtime:
|
|
102
|
+
|
|
103
|
+
```typescript
|
|
104
|
+
const engine = new Engine({
|
|
105
|
+
canvas: HTMLCanvasElement,
|
|
106
|
+
forces: Module[], // Force modules
|
|
107
|
+
render: Module[], // Render modules
|
|
108
|
+
runtime: "auto", // "auto" | "webgpu" | "cpu"
|
|
109
|
+
|
|
110
|
+
// Optional configuration
|
|
111
|
+
constrainIterations: 50, // Constraint solver iterations
|
|
112
|
+
cellSize: 32, // Spatial grid cell size
|
|
113
|
+
maxNeighbors: 128, // Max neighbors per particle
|
|
114
|
+
maxParticles: 10000, // Particle capacity
|
|
115
|
+
clearColor: "#000000", // Background color
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
// Lifecycle
|
|
119
|
+
await engine.initialize();
|
|
120
|
+
engine.play();
|
|
121
|
+
engine.pause();
|
|
122
|
+
engine.stop();
|
|
123
|
+
engine.destroy();
|
|
124
|
+
|
|
125
|
+
// State
|
|
126
|
+
const isPlaying = engine.isPlaying();
|
|
127
|
+
const fps = engine.getFPS();
|
|
128
|
+
const count = engine.getCount();
|
|
129
|
+
|
|
130
|
+
// Particles
|
|
131
|
+
engine.addParticle({ x, y, vx, vy, mass, size });
|
|
132
|
+
engine.setParticles([...particles]);
|
|
133
|
+
const particles = await engine.getParticles();
|
|
134
|
+
engine.clear();
|
|
135
|
+
|
|
136
|
+
// View
|
|
137
|
+
engine.setSize(width, height);
|
|
138
|
+
engine.setCamera(x, y);
|
|
139
|
+
engine.setZoom(scale);
|
|
140
|
+
|
|
141
|
+
// Configuration
|
|
142
|
+
const config = engine.export();
|
|
143
|
+
engine.import(config);
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
### Runtime Selection
|
|
147
|
+
|
|
148
|
+
- **"auto"**: Tries WebGPU first, falls back to CPU if unavailable
|
|
149
|
+
- **"webgpu"**: GPU compute with WGSL shaders (Chrome 113+, Edge 113+)
|
|
150
|
+
- **"cpu"**: JavaScript simulation with Canvas2D rendering (universal compatibility)
|
|
151
|
+
|
|
152
|
+
```typescript
|
|
153
|
+
// Check which runtime is active
|
|
154
|
+
const runtime = engine.getActualRuntime(); // "webgpu" | "cpu"
|
|
155
|
+
|
|
156
|
+
// Test module support
|
|
157
|
+
const isSupported = engine.isSupported(module);
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
### Particles
|
|
161
|
+
|
|
162
|
+
Particles are simple data structures with physics properties:
|
|
163
|
+
|
|
164
|
+
```typescript
|
|
165
|
+
const particle = {
|
|
166
|
+
x: 100, y: 100, // Position
|
|
167
|
+
vx: 1, vy: -2, // Velocity
|
|
168
|
+
mass: 2.5, // Mass (negative = pinned)
|
|
169
|
+
size: 8, // Visual size
|
|
170
|
+
color: 0xff6b35, // Color (24-bit RGB)
|
|
171
|
+
};
|
|
172
|
+
|
|
173
|
+
// Bulk operations (preferred for performance)
|
|
174
|
+
engine.setParticles(particles);
|
|
175
|
+
const allParticles = await engine.getParticles();
|
|
176
|
+
|
|
177
|
+
// Individual operations
|
|
178
|
+
engine.addParticle(particle);
|
|
179
|
+
const singleParticle = await engine.getParticle(index);
|
|
180
|
+
|
|
181
|
+
// Pin/unpin helpers
|
|
182
|
+
engine.pinParticles([0, 1, 2]);
|
|
183
|
+
engine.unpinParticles([0, 1, 2]);
|
|
184
|
+
engine.unpinAll();
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
### Modules
|
|
188
|
+
|
|
189
|
+
Modules are pluggable components that contribute to simulation or rendering:
|
|
190
|
+
|
|
191
|
+
```typescript
|
|
192
|
+
// Force modules affect particle physics
|
|
193
|
+
const forces = [
|
|
194
|
+
new Environment({ gravityStrength: 1000 }),
|
|
195
|
+
new Boundary({ mode: "bounce" }),
|
|
196
|
+
new Collisions({ restitution: 0.8 }),
|
|
197
|
+
];
|
|
198
|
+
|
|
199
|
+
// Render modules draw visual effects
|
|
200
|
+
const render = [
|
|
201
|
+
new Particles({ colorType: 2, hue: 0.5 }),
|
|
202
|
+
new Trails({ trailDecay: 10 }),
|
|
203
|
+
];
|
|
204
|
+
|
|
205
|
+
// Module control
|
|
206
|
+
const module = engine.getModule("environment");
|
|
207
|
+
module.setEnabled(false);
|
|
208
|
+
const isEnabled = module.isEnabled();
|
|
209
|
+
|
|
210
|
+
// Read/write module inputs
|
|
211
|
+
const inputs = module.read();
|
|
212
|
+
module.write({ gravityStrength: 500 });
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
## Available Modules
|
|
216
|
+
|
|
217
|
+
### Force Modules
|
|
218
|
+
|
|
219
|
+
#### Environment
|
|
220
|
+
Global physics: gravity, inertia, friction, damping
|
|
221
|
+
|
|
222
|
+
```typescript
|
|
223
|
+
new Environment({
|
|
224
|
+
gravityStrength: 600, // Gravity magnitude
|
|
225
|
+
gravityDirection: "down", // "up"|"down"|"left"|"right"|"inwards"|"outwards"|"custom"
|
|
226
|
+
gravityAngle: Math.PI / 4, // Custom angle (when direction = "custom")
|
|
227
|
+
inertia: 0.05, // Momentum preservation (0-1)
|
|
228
|
+
friction: 0.01, // Velocity damping (0-1)
|
|
229
|
+
damping: 0.02, // Direct velocity reduction (0-1)
|
|
230
|
+
})
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
#### Boundary
|
|
234
|
+
Boundary interactions and containment
|
|
235
|
+
|
|
236
|
+
```typescript
|
|
237
|
+
new Boundary({
|
|
238
|
+
mode: "bounce", // "bounce"|"warp"|"kill"|"none"
|
|
239
|
+
restitution: 0.9, // Bounce energy retention (0-1)
|
|
240
|
+
friction: 0.1, // Tangential friction (0-1)
|
|
241
|
+
repelDistance: 50, // Distance to start repel force
|
|
242
|
+
repelStrength: 0.5, // Repel force magnitude
|
|
243
|
+
})
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
#### Collisions
|
|
247
|
+
Particle-particle collision detection and response
|
|
248
|
+
|
|
249
|
+
```typescript
|
|
250
|
+
new Collisions({
|
|
251
|
+
restitution: 0.8, // Collision elasticity (0-1)
|
|
252
|
+
})
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
#### Behavior
|
|
256
|
+
Flocking behaviors (boids-style steering)
|
|
257
|
+
|
|
258
|
+
```typescript
|
|
259
|
+
new Behavior({
|
|
260
|
+
cohesion: 1.5, // Attraction to group center
|
|
261
|
+
alignment: 1.2, // Velocity matching
|
|
262
|
+
repulsion: 2.0, // Separation force
|
|
263
|
+
separation: 12, // Personal space radius
|
|
264
|
+
viewRadius: 100, // Neighbor detection radius
|
|
265
|
+
viewAngle: Math.PI, // Field of view (radians)
|
|
266
|
+
wander: 20, // Random exploration
|
|
267
|
+
chase: 0.5, // Pursue lighter particles
|
|
268
|
+
avoid: 0.3, // Flee heavier particles
|
|
269
|
+
})
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
#### Fluids
|
|
273
|
+
Smoothed Particle Hydrodynamics (SPH) fluid simulation
|
|
274
|
+
|
|
275
|
+
```typescript
|
|
276
|
+
new Fluids({
|
|
277
|
+
influenceRadius: 80, // Particle interaction radius
|
|
278
|
+
targetDensity: 1.0, // Rest density
|
|
279
|
+
pressureMultiplier: 25, // Pressure force strength
|
|
280
|
+
viscosity: 0.8, // Internal friction
|
|
281
|
+
nearPressureMultiplier: 40, // Near-field pressure
|
|
282
|
+
nearThreshold: 18, // Near-field distance
|
|
283
|
+
enableNearPressure: true, // Enable near-field forces
|
|
284
|
+
maxAcceleration: 60, // Force clamping for stability
|
|
285
|
+
})
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
#### Sensors
|
|
289
|
+
Trail-following and color-based steering
|
|
290
|
+
|
|
291
|
+
```typescript
|
|
292
|
+
new Sensors({
|
|
293
|
+
sensorDistance: 30, // Sensor projection distance
|
|
294
|
+
sensorAngle: Math.PI / 6, // Sensor angle offset (30°)
|
|
295
|
+
sensorRadius: 3, // Sensor detection radius
|
|
296
|
+
sensorThreshold: 0.15, // Minimum detection threshold
|
|
297
|
+
sensorStrength: 800, // Steering force magnitude
|
|
298
|
+
followBehavior: "any", // "any"|"same"|"different"|"none"
|
|
299
|
+
fleeBehavior: "none", // "any"|"same"|"different"|"none"
|
|
300
|
+
colorSimilarityThreshold: 0.5, // Color matching threshold
|
|
301
|
+
fleeAngle: Math.PI / 2, // Flee direction offset (90°)
|
|
302
|
+
})
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
#### Interaction
|
|
306
|
+
User-controlled attraction and repulsion
|
|
307
|
+
|
|
308
|
+
```typescript
|
|
309
|
+
const interaction = new Interaction({
|
|
310
|
+
mode: "attract", // "attract"|"repel"
|
|
311
|
+
strength: 12000, // Force magnitude
|
|
312
|
+
radius: 300, // Interaction radius
|
|
313
|
+
active: false, // Initially inactive
|
|
314
|
+
});
|
|
315
|
+
|
|
316
|
+
// Control interaction
|
|
317
|
+
interaction.setPosition(mouseX, mouseY);
|
|
318
|
+
interaction.setActive(true);
|
|
319
|
+
interaction.setMode("repel");
|
|
320
|
+
```
|
|
321
|
+
|
|
322
|
+
#### Joints
|
|
323
|
+
Distance constraints between particles
|
|
324
|
+
|
|
325
|
+
```typescript
|
|
326
|
+
const joints = new Joints({
|
|
327
|
+
momentum: 0.7, // Momentum preservation (0-1)
|
|
328
|
+
restitution: 0.9, // Joint elasticity
|
|
329
|
+
separation: 0.5, // Separation force strength
|
|
330
|
+
steps: 2, // Constraint iterations
|
|
331
|
+
friction: 0.02, // Joint friction
|
|
332
|
+
enableParticleCollisions: false, // Particle-joint collisions
|
|
333
|
+
enableJointCollisions: false, // Joint-joint collisions
|
|
334
|
+
});
|
|
335
|
+
|
|
336
|
+
// Manage joints
|
|
337
|
+
joints.setJoints([
|
|
338
|
+
{ aIndex: 0, bIndex: 1, restLength: 50 },
|
|
339
|
+
{ aIndex: 1, bIndex: 2, restLength: 75 },
|
|
340
|
+
]);
|
|
341
|
+
joints.add({ aIndex: 2, bIndex: 3, restLength: 100 });
|
|
342
|
+
joints.remove(0, 1);
|
|
343
|
+
joints.removeAll();
|
|
344
|
+
```
|
|
345
|
+
|
|
346
|
+
#### Grab
|
|
347
|
+
Single-particle mouse/touch dragging
|
|
348
|
+
|
|
349
|
+
```typescript
|
|
350
|
+
const grab = new Grab();
|
|
351
|
+
|
|
352
|
+
// Grab particle
|
|
353
|
+
grab.grabParticle(particleIndex, { x: mouseX, y: mouseY });
|
|
354
|
+
|
|
355
|
+
// Update position
|
|
356
|
+
grab.updatePosition(newX, newY);
|
|
357
|
+
|
|
358
|
+
// Release
|
|
359
|
+
grab.releaseParticle();
|
|
360
|
+
|
|
361
|
+
// Check state
|
|
362
|
+
const isGrabbing = grab.isGrabbing();
|
|
363
|
+
```
|
|
364
|
+
|
|
365
|
+
### Render Modules
|
|
366
|
+
|
|
367
|
+
#### Particles
|
|
368
|
+
Instanced particle rendering with multiple color modes
|
|
369
|
+
|
|
370
|
+
```typescript
|
|
371
|
+
new Particles({
|
|
372
|
+
colorType: 2, // 0=Default, 1=Custom, 2=Hue
|
|
373
|
+
customColorR: 1.0, // Custom color red (0-1)
|
|
374
|
+
customColorG: 0.4, // Custom color green (0-1)
|
|
375
|
+
customColorB: 0.2, // Custom color blue (0-1)
|
|
376
|
+
hue: 0.55, // Hue value (0-1) when colorType=2
|
|
377
|
+
})
|
|
378
|
+
|
|
379
|
+
// Pinned particles render as rings
|
|
380
|
+
// Particle size and color come from particle data
|
|
381
|
+
```
|
|
382
|
+
|
|
383
|
+
#### Trails
|
|
384
|
+
Decay and diffusion effects
|
|
385
|
+
|
|
386
|
+
```typescript
|
|
387
|
+
new Trails({
|
|
388
|
+
trailDecay: 10, // Fade speed (higher = faster fade)
|
|
389
|
+
trailDiffuse: 4, // Blur amount (0-12 typical)
|
|
390
|
+
})
|
|
391
|
+
```
|
|
392
|
+
|
|
393
|
+
#### Lines
|
|
394
|
+
Line rendering between particle pairs
|
|
395
|
+
|
|
396
|
+
```typescript
|
|
397
|
+
const lines = new Lines({
|
|
398
|
+
lineWidth: 2.0, // Line thickness
|
|
399
|
+
lineColorR: -1, // Line color (-1 = use particle color)
|
|
400
|
+
lineColorG: -1,
|
|
401
|
+
lineColorB: -1,
|
|
402
|
+
});
|
|
403
|
+
|
|
404
|
+
// Manage lines
|
|
405
|
+
lines.setLines([
|
|
406
|
+
{ aIndex: 0, bIndex: 1 },
|
|
407
|
+
{ aIndex: 1, bIndex: 2 },
|
|
408
|
+
]);
|
|
409
|
+
lines.add({ aIndex: 2, bIndex: 3 });
|
|
410
|
+
lines.remove(0, 1);
|
|
411
|
+
lines.setLineColor("#ff0000"); // Or null for particle colors
|
|
412
|
+
```
|
|
413
|
+
|
|
414
|
+
## Oscillators
|
|
415
|
+
|
|
416
|
+
Oscillators animate module parameters over time with smooth interpolation:
|
|
417
|
+
|
|
418
|
+
```typescript
|
|
419
|
+
// Add oscillator to animate boundary restitution
|
|
420
|
+
engine.addOscillator({
|
|
421
|
+
moduleName: "boundary",
|
|
422
|
+
inputName: "restitution",
|
|
423
|
+
min: 0.4, // Minimum value
|
|
424
|
+
max: 0.95, // Maximum value
|
|
425
|
+
speedHz: 0.2, // Frequency (cycles per second)
|
|
426
|
+
});
|
|
427
|
+
|
|
428
|
+
// Update oscillator parameters
|
|
429
|
+
engine.updateOscillatorSpeed("boundary", "restitution", 0.5);
|
|
430
|
+
engine.updateOscillatorBounds("boundary", "restitution", 0.2, 0.8);
|
|
431
|
+
|
|
432
|
+
// Remove oscillators
|
|
433
|
+
engine.removeOscillator("boundary", "restitution");
|
|
434
|
+
engine.clearModuleOscillators("boundary");
|
|
435
|
+
engine.clearOscillators();
|
|
436
|
+
```
|
|
437
|
+
|
|
438
|
+
## Configuration Management
|
|
439
|
+
|
|
440
|
+
Export and import complete simulation states:
|
|
441
|
+
|
|
442
|
+
```typescript
|
|
443
|
+
// Export current configuration
|
|
444
|
+
const config = engine.export();
|
|
445
|
+
|
|
446
|
+
// Configuration format
|
|
447
|
+
const config = {
|
|
448
|
+
environment: {
|
|
449
|
+
enabled: true,
|
|
450
|
+
gravityStrength: 600,
|
|
451
|
+
gravityDirection: "down",
|
|
452
|
+
// ... all module inputs
|
|
453
|
+
},
|
|
454
|
+
boundary: {
|
|
455
|
+
enabled: true,
|
|
456
|
+
mode: "bounce",
|
|
457
|
+
restitution: 0.9,
|
|
458
|
+
// ... all module inputs
|
|
459
|
+
},
|
|
460
|
+
// ... all modules
|
|
461
|
+
};
|
|
462
|
+
|
|
463
|
+
// Import configuration
|
|
464
|
+
engine.import(config);
|
|
465
|
+
|
|
466
|
+
// Partial import (only specified modules)
|
|
467
|
+
engine.import({
|
|
468
|
+
environment: { gravityStrength: 1000 },
|
|
469
|
+
collisions: { restitution: 0.5 },
|
|
470
|
+
});
|
|
471
|
+
```
|
|
472
|
+
|
|
473
|
+
## Performance Optimization
|
|
474
|
+
|
|
475
|
+
### Spatial Grid
|
|
476
|
+
|
|
477
|
+
The engine uses spatial partitioning for efficient neighbor queries:
|
|
478
|
+
|
|
479
|
+
```typescript
|
|
480
|
+
engine.setCellSize(32); // Smaller = more precise, larger = faster
|
|
481
|
+
engine.setMaxNeighbors(128); // Higher = more accurate, slower
|
|
482
|
+
```
|
|
483
|
+
|
|
484
|
+
**Cell Size Guidelines:**
|
|
485
|
+
- Dense simulations: 16-32
|
|
486
|
+
- Sparse simulations: 64-128
|
|
487
|
+
- Rule of thumb: 2-4x average particle size
|
|
488
|
+
|
|
489
|
+
### Constraint Iterations
|
|
490
|
+
|
|
491
|
+
Control physics solver accuracy vs performance:
|
|
492
|
+
|
|
493
|
+
```typescript
|
|
494
|
+
engine.setConstrainIterations(50); // Higher = more stable, slower
|
|
495
|
+
```
|
|
496
|
+
|
|
497
|
+
**Typical Values:**
|
|
498
|
+
- CPU: 5-10 iterations
|
|
499
|
+
- WebGPU: 20-100 iterations (GPU can handle more)
|
|
500
|
+
|
|
501
|
+
### WebGPU Configuration
|
|
502
|
+
|
|
503
|
+
```typescript
|
|
504
|
+
const engine = new Engine({
|
|
505
|
+
runtime: "webgpu",
|
|
506
|
+
workgroupSize: 64, // 32, 64, 128, or 256
|
|
507
|
+
maxParticles: 10000, // Pre-allocate GPU buffers
|
|
508
|
+
});
|
|
509
|
+
```
|
|
510
|
+
|
|
511
|
+
## Advanced Usage
|
|
512
|
+
|
|
513
|
+
### Custom Modules
|
|
514
|
+
|
|
515
|
+
Create custom force modules by extending the Module class:
|
|
516
|
+
|
|
517
|
+
```typescript
|
|
518
|
+
import { Module, ModuleRole, DataType } from "@cazala/party";
|
|
519
|
+
|
|
520
|
+
type WindInputs = { strength: number; dirX: number; dirY: number };
|
|
521
|
+
|
|
522
|
+
export class Wind extends Module<"wind", WindInputs> {
|
|
523
|
+
readonly name = "wind" as const;
|
|
524
|
+
readonly role = ModuleRole.Force;
|
|
525
|
+
readonly inputs = {
|
|
526
|
+
strength: DataType.NUMBER,
|
|
527
|
+
dirX: DataType.NUMBER,
|
|
528
|
+
dirY: DataType.NUMBER,
|
|
529
|
+
} as const;
|
|
530
|
+
|
|
531
|
+
constructor() {
|
|
532
|
+
super();
|
|
533
|
+
this.write({ strength: 100, dirX: 1, dirY: 0 });
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
// WebGPU implementation
|
|
537
|
+
webgpu() {
|
|
538
|
+
return {
|
|
539
|
+
apply: ({ particleVar, getUniform }) => `{
|
|
540
|
+
let d = vec2<f32>(${getUniform("dirX")}, ${getUniform("dirY")});
|
|
541
|
+
if (length(d) > 0.0) {
|
|
542
|
+
${particleVar}.acceleration += normalize(d) * ${getUniform("strength")};
|
|
543
|
+
}
|
|
544
|
+
}`,
|
|
545
|
+
};
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
// CPU implementation
|
|
549
|
+
cpu() {
|
|
550
|
+
return {
|
|
551
|
+
apply: ({ particle, input }) => {
|
|
552
|
+
const len = Math.hypot(input.dirX, input.dirY) || 1;
|
|
553
|
+
particle.acceleration.x += (input.dirX / len) * input.strength;
|
|
554
|
+
particle.acceleration.y += (input.dirY / len) * input.strength;
|
|
555
|
+
},
|
|
556
|
+
};
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
```
|
|
560
|
+
|
|
561
|
+
### Error Handling
|
|
562
|
+
|
|
563
|
+
```typescript
|
|
564
|
+
try {
|
|
565
|
+
await engine.initialize();
|
|
566
|
+
} catch (error) {
|
|
567
|
+
if (error.message.includes("WebGPU")) {
|
|
568
|
+
console.log("WebGPU not supported, falling back to CPU");
|
|
569
|
+
// Engine automatically falls back when runtime: "auto"
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
// Check module support
|
|
574
|
+
if (!engine.isSupported(customModule)) {
|
|
575
|
+
console.warn("Custom module not supported in current runtime");
|
|
576
|
+
}
|
|
577
|
+
```
|
|
578
|
+
|
|
579
|
+
## Browser Support
|
|
580
|
+
|
|
581
|
+
- **WebGPU**: Chrome 113+, Edge 113+, Firefox Nightly (experimental)
|
|
582
|
+
- **CPU Fallback**: All modern browsers with Canvas2D support
|
|
583
|
+
- **Feature Detection**: Automatic runtime selection with graceful fallback
|
|
584
|
+
|
|
585
|
+
## TypeScript Support
|
|
586
|
+
|
|
587
|
+
Full TypeScript support with comprehensive type definitions:
|
|
588
|
+
|
|
589
|
+
```typescript
|
|
590
|
+
import type { IEngine, IParticle, Module } from "@cazala/party";
|
|
591
|
+
|
|
592
|
+
const engine: IEngine = new Engine({ /* ... */ });
|
|
593
|
+
const particle: IParticle = { x: 0, y: 0, vx: 1, vy: 1, mass: 1, size: 5 };
|
|
594
|
+
```
|
|
595
|
+
|
|
596
|
+
## License
|
|
597
|
+
|
|
598
|
+
MIT License - see [LICENSE](../../LICENSE) file for details.
|