@cazala/party 0.3.0 → 0.5.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/README.md +53 -0
- package/dist/index.js +183 -2
- package/dist/index.js.map +1 -1
- package/dist/spawner.d.ts +20 -2
- package/dist/spawner.d.ts.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -12,6 +12,7 @@ A high-performance TypeScript particle physics engine with dual runtime support
|
|
|
12
12
|
- **Advanced Rendering**: Trails, particle instancing, line rendering with multiple color modes
|
|
13
13
|
- **Export/Import Presets**: Export/import module settings (inputs + enabled state)
|
|
14
14
|
- **Cross-platform**: Works in all modern browsers with automatic feature detection
|
|
15
|
+
- **Spawner Utility**: Generate particle shapes, including text and images
|
|
15
16
|
|
|
16
17
|
## Installation
|
|
17
18
|
|
|
@@ -189,6 +190,58 @@ engine.unpinParticles([0, 1, 2]);
|
|
|
189
190
|
engine.unpinAll();
|
|
190
191
|
```
|
|
191
192
|
|
|
193
|
+
### Spawner
|
|
194
|
+
|
|
195
|
+
Generate particle arrays from common shapes (including text and images) using `Spawner`:
|
|
196
|
+
|
|
197
|
+
```typescript
|
|
198
|
+
import { Spawner } from "@cazala/party";
|
|
199
|
+
|
|
200
|
+
const spawner = new Spawner();
|
|
201
|
+
const particles = spawner.initParticles({
|
|
202
|
+
count: 5000,
|
|
203
|
+
shape: "text",
|
|
204
|
+
center: { x: 0, y: 0 },
|
|
205
|
+
position: { x: 0, y: 0 },
|
|
206
|
+
align: { horizontal: "center", vertical: "center" },
|
|
207
|
+
text: "Party",
|
|
208
|
+
font: "sans-serif",
|
|
209
|
+
textSize: 80,
|
|
210
|
+
size: 3,
|
|
211
|
+
mass: 1,
|
|
212
|
+
colors: ["#ffffff"],
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
engine.setParticles(particles);
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
Notes:
|
|
219
|
+
|
|
220
|
+
- `size` controls particle radius; `textSize` is the font size used to rasterize text.
|
|
221
|
+
- Playground font options: `sans-serif`, `serif`, `monospace`.
|
|
222
|
+
|
|
223
|
+
Image example:
|
|
224
|
+
|
|
225
|
+
```typescript
|
|
226
|
+
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
|
|
227
|
+
const imageParticles = spawner.initParticles({
|
|
228
|
+
count: 12000,
|
|
229
|
+
shape: "image",
|
|
230
|
+
center: { x: 0, y: 0 },
|
|
231
|
+
position: { x: 0, y: 0 },
|
|
232
|
+
align: { horizontal: "center", vertical: "center" },
|
|
233
|
+
imageData,
|
|
234
|
+
imageSize: 400, // scales to this max dimension
|
|
235
|
+
size: 3,
|
|
236
|
+
mass: 1,
|
|
237
|
+
});
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
Notes:
|
|
241
|
+
|
|
242
|
+
- `imageData` must be provided synchronously (no URL fetching inside the spawner).
|
|
243
|
+
- Fully transparent pixels are skipped; particle colors come from image pixels.
|
|
244
|
+
|
|
192
245
|
### Modules
|
|
193
246
|
|
|
194
247
|
Modules are pluggable components that contribute to simulation or rendering:
|
package/dist/index.js
CHANGED
|
@@ -5431,6 +5431,7 @@ class Engine {
|
|
|
5431
5431
|
}
|
|
5432
5432
|
}
|
|
5433
5433
|
|
|
5434
|
+
const TEXT_SPAWNER_FONTS = ["sans-serif", "serif", "monospace"];
|
|
5434
5435
|
function calculateVelocity(position, center, cfg) {
|
|
5435
5436
|
if (!cfg || cfg.speed === 0)
|
|
5436
5437
|
return { vx: 0, vy: 0 };
|
|
@@ -5472,7 +5473,7 @@ function calculateVelocity(position, center, cfg) {
|
|
|
5472
5473
|
}
|
|
5473
5474
|
class Spawner {
|
|
5474
5475
|
initParticles(options) {
|
|
5475
|
-
const { count, shape, center, spacing = 25, radius = 100, innerRadius = 50, squareSize = 200, cornerRadius = 0, size = 5, mass = 1, bounds, velocity, colors, } = options;
|
|
5476
|
+
const { count, shape, center, spacing = 25, radius = 100, innerRadius = 50, squareSize = 200, cornerRadius = 0, size = 5, mass = 1, bounds, velocity, colors, text = "Party", font = "sans-serif", textSize = 64, position, align, imageData, imageSize, } = options;
|
|
5476
5477
|
const particles = [];
|
|
5477
5478
|
if (count <= 0)
|
|
5478
5479
|
return particles;
|
|
@@ -5499,6 +5500,186 @@ class Spawner {
|
|
|
5499
5500
|
return { r: 1, g: 1, b: 1, a: 1 };
|
|
5500
5501
|
return toColor(colors[Math.floor(Math.random() * colors.length)]);
|
|
5501
5502
|
};
|
|
5503
|
+
if (shape === "text") {
|
|
5504
|
+
const textValue = text.trim();
|
|
5505
|
+
if (!textValue)
|
|
5506
|
+
return particles;
|
|
5507
|
+
const createCanvas = () => {
|
|
5508
|
+
if (typeof OffscreenCanvas !== "undefined") {
|
|
5509
|
+
return new OffscreenCanvas(1, 1);
|
|
5510
|
+
}
|
|
5511
|
+
if (typeof document !== "undefined") {
|
|
5512
|
+
return document.createElement("canvas");
|
|
5513
|
+
}
|
|
5514
|
+
return null;
|
|
5515
|
+
};
|
|
5516
|
+
const canvas = createCanvas();
|
|
5517
|
+
if (!canvas)
|
|
5518
|
+
return particles;
|
|
5519
|
+
const ctx = canvas.getContext("2d");
|
|
5520
|
+
if (!ctx)
|
|
5521
|
+
return particles;
|
|
5522
|
+
const fontSize = Math.max(1, Math.floor(textSize));
|
|
5523
|
+
const fontSpec = `${fontSize}px ${font}`;
|
|
5524
|
+
ctx.font = fontSpec;
|
|
5525
|
+
ctx.textBaseline = "alphabetic";
|
|
5526
|
+
ctx.textAlign = "left";
|
|
5527
|
+
const metrics = ctx.measureText(textValue);
|
|
5528
|
+
const ascent = metrics.actualBoundingBoxAscent || fontSize;
|
|
5529
|
+
const descent = metrics.actualBoundingBoxDescent || fontSize * 0.25;
|
|
5530
|
+
const left = metrics.actualBoundingBoxLeft || 0;
|
|
5531
|
+
const right = metrics.actualBoundingBoxRight || metrics.width;
|
|
5532
|
+
const textWidth = Math.max(1, Math.ceil(left + right));
|
|
5533
|
+
const textHeight = Math.max(1, Math.ceil(ascent + descent));
|
|
5534
|
+
const padding = Math.ceil(fontSize * 0.25);
|
|
5535
|
+
canvas.width = textWidth + padding * 2;
|
|
5536
|
+
canvas.height = textHeight + padding * 2;
|
|
5537
|
+
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
|
5538
|
+
ctx.font = fontSpec;
|
|
5539
|
+
ctx.textBaseline = "alphabetic";
|
|
5540
|
+
ctx.textAlign = "left";
|
|
5541
|
+
ctx.fillStyle = "#ffffff";
|
|
5542
|
+
ctx.fillText(textValue, padding + left, padding + ascent);
|
|
5543
|
+
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
|
|
5544
|
+
const data = imageData.data;
|
|
5545
|
+
const sampleStep = Math.max(1, Math.round(size));
|
|
5546
|
+
const points = [];
|
|
5547
|
+
for (let y = 0; y < canvas.height; y += sampleStep) {
|
|
5548
|
+
for (let x = 0; x < canvas.width; x += sampleStep) {
|
|
5549
|
+
const idx = (y * canvas.width + x) * 4 + 3;
|
|
5550
|
+
if (data[idx] > 0) {
|
|
5551
|
+
points.push({ x, y });
|
|
5552
|
+
}
|
|
5553
|
+
}
|
|
5554
|
+
}
|
|
5555
|
+
const maxCount = Math.min(count, points.length);
|
|
5556
|
+
if (maxCount <= 0)
|
|
5557
|
+
return particles;
|
|
5558
|
+
const textPosition = position ?? center;
|
|
5559
|
+
const horizontal = align?.horizontal ?? "center";
|
|
5560
|
+
const vertical = align?.vertical ?? "center";
|
|
5561
|
+
const originX = horizontal === "left"
|
|
5562
|
+
? textPosition.x
|
|
5563
|
+
: horizontal === "right"
|
|
5564
|
+
? textPosition.x - textWidth
|
|
5565
|
+
: textPosition.x - textWidth / 2;
|
|
5566
|
+
const originY = vertical === "top"
|
|
5567
|
+
? textPosition.y
|
|
5568
|
+
: vertical === "bottom"
|
|
5569
|
+
? textPosition.y - textHeight
|
|
5570
|
+
: textPosition.y - textHeight / 2;
|
|
5571
|
+
const stride = points.length / maxCount;
|
|
5572
|
+
for (let i = 0; i < maxCount; i++) {
|
|
5573
|
+
const idx = Math.floor(i * stride);
|
|
5574
|
+
const point = points[idx];
|
|
5575
|
+
if (!point)
|
|
5576
|
+
continue;
|
|
5577
|
+
const x = originX + (point.x - padding);
|
|
5578
|
+
const y = originY + (point.y - padding);
|
|
5579
|
+
const { vx, vy } = calculateVelocity({ x, y }, textPosition, velocity);
|
|
5580
|
+
particles.push({
|
|
5581
|
+
position: { x, y },
|
|
5582
|
+
velocity: { x: vx, y: vy },
|
|
5583
|
+
size,
|
|
5584
|
+
mass,
|
|
5585
|
+
color: getColor(),
|
|
5586
|
+
});
|
|
5587
|
+
}
|
|
5588
|
+
return particles;
|
|
5589
|
+
}
|
|
5590
|
+
if (shape === "image") {
|
|
5591
|
+
if (!imageData)
|
|
5592
|
+
return particles;
|
|
5593
|
+
const width = Math.floor(imageData.width);
|
|
5594
|
+
const height = Math.floor(imageData.height);
|
|
5595
|
+
if (!Number.isFinite(width) ||
|
|
5596
|
+
!Number.isFinite(height) ||
|
|
5597
|
+
width <= 0 ||
|
|
5598
|
+
height <= 0)
|
|
5599
|
+
return particles;
|
|
5600
|
+
const data = imageData.data;
|
|
5601
|
+
const sampleStepTarget = Math.max(1, Math.round(size));
|
|
5602
|
+
const targetSize = typeof imageSize === "number" && Number.isFinite(imageSize) && imageSize > 0
|
|
5603
|
+
? imageSize
|
|
5604
|
+
: Math.max(width, height);
|
|
5605
|
+
const scale = Math.max(0.0001, targetSize / Math.max(width, height));
|
|
5606
|
+
const sampleStepSource = Math.max(1, Math.round(sampleStepTarget / scale));
|
|
5607
|
+
const points = [];
|
|
5608
|
+
for (let y = 0; y < height; y += sampleStepSource) {
|
|
5609
|
+
for (let x = 0; x < width; x += sampleStepSource) {
|
|
5610
|
+
const idx = (y * width + x) * 4;
|
|
5611
|
+
const alpha = data[idx + 3];
|
|
5612
|
+
if (alpha === 0)
|
|
5613
|
+
continue;
|
|
5614
|
+
points.push({
|
|
5615
|
+
x: x * scale,
|
|
5616
|
+
y: y * scale,
|
|
5617
|
+
color: {
|
|
5618
|
+
r: data[idx] / 255,
|
|
5619
|
+
g: data[idx + 1] / 255,
|
|
5620
|
+
b: data[idx + 2] / 255,
|
|
5621
|
+
a: alpha / 255,
|
|
5622
|
+
},
|
|
5623
|
+
});
|
|
5624
|
+
}
|
|
5625
|
+
}
|
|
5626
|
+
const maxCount = Math.max(0, Math.floor(count));
|
|
5627
|
+
if (maxCount <= 0 || points.length === 0)
|
|
5628
|
+
return particles;
|
|
5629
|
+
const imagePosition = position ?? center;
|
|
5630
|
+
const horizontal = align?.horizontal ?? "center";
|
|
5631
|
+
const vertical = align?.vertical ?? "center";
|
|
5632
|
+
const scaledWidth = width * scale;
|
|
5633
|
+
const scaledHeight = height * scale;
|
|
5634
|
+
const originX = horizontal === "left"
|
|
5635
|
+
? imagePosition.x
|
|
5636
|
+
: horizontal === "right"
|
|
5637
|
+
? imagePosition.x - scaledWidth
|
|
5638
|
+
: imagePosition.x - scaledWidth / 2;
|
|
5639
|
+
const originY = vertical === "top"
|
|
5640
|
+
? imagePosition.y
|
|
5641
|
+
: vertical === "bottom"
|
|
5642
|
+
? imagePosition.y - scaledHeight
|
|
5643
|
+
: imagePosition.y - scaledHeight / 2;
|
|
5644
|
+
const baseCount = Math.min(maxCount, points.length);
|
|
5645
|
+
const stride = points.length / baseCount;
|
|
5646
|
+
for (let i = 0; i < baseCount; i++) {
|
|
5647
|
+
const idx = Math.floor(i * stride);
|
|
5648
|
+
const point = points[idx];
|
|
5649
|
+
if (!point)
|
|
5650
|
+
continue;
|
|
5651
|
+
const x = originX + point.x;
|
|
5652
|
+
const y = originY + point.y;
|
|
5653
|
+
const { vx, vy } = calculateVelocity({ x, y }, imagePosition, velocity);
|
|
5654
|
+
particles.push({
|
|
5655
|
+
position: { x, y },
|
|
5656
|
+
velocity: { x: vx, y: vy },
|
|
5657
|
+
size,
|
|
5658
|
+
mass,
|
|
5659
|
+
color: point.color,
|
|
5660
|
+
});
|
|
5661
|
+
}
|
|
5662
|
+
const extraCount = maxCount - baseCount;
|
|
5663
|
+
if (extraCount > 0) {
|
|
5664
|
+
const jitter = sampleStepTarget * 0.4;
|
|
5665
|
+
for (let i = 0; i < extraCount; i++) {
|
|
5666
|
+
const point = points[Math.floor(Math.random() * points.length)];
|
|
5667
|
+
if (!point)
|
|
5668
|
+
continue;
|
|
5669
|
+
const x = originX + point.x + (Math.random() - 0.5) * jitter;
|
|
5670
|
+
const y = originY + point.y + (Math.random() - 0.5) * jitter;
|
|
5671
|
+
const { vx, vy } = calculateVelocity({ x, y }, imagePosition, velocity);
|
|
5672
|
+
particles.push({
|
|
5673
|
+
position: { x, y },
|
|
5674
|
+
velocity: { x: vx, y: vy },
|
|
5675
|
+
size,
|
|
5676
|
+
mass,
|
|
5677
|
+
color: point.color,
|
|
5678
|
+
});
|
|
5679
|
+
}
|
|
5680
|
+
}
|
|
5681
|
+
return particles;
|
|
5682
|
+
}
|
|
5502
5683
|
if (shape === "grid") {
|
|
5503
5684
|
const cols = Math.ceil(Math.sqrt(count));
|
|
5504
5685
|
const rows = Math.ceil(count / cols);
|
|
@@ -10392,5 +10573,5 @@ class Particles extends Module {
|
|
|
10392
10573
|
}
|
|
10393
10574
|
}
|
|
10394
10575
|
|
|
10395
|
-
export { AbstractEngine, Behavior, Boundary, CanvasComposition, Collisions, DEFAULT_BEHAVIOR_ALIGNMENT, DEFAULT_BEHAVIOR_AVOID, DEFAULT_BEHAVIOR_CHASE, DEFAULT_BEHAVIOR_COHESION, DEFAULT_BEHAVIOR_REPULSION, DEFAULT_BEHAVIOR_SEPARATION, DEFAULT_BEHAVIOR_VIEW_ANGLE, DEFAULT_BEHAVIOR_VIEW_RADIUS, DEFAULT_BEHAVIOR_WANDER, DEFAULT_BOUNDARY_FRICTION, DEFAULT_BOUNDARY_MODE, DEFAULT_BOUNDARY_REPEL_DISTANCE, DEFAULT_BOUNDARY_REPEL_STRENGTH, DEFAULT_BOUNDARY_RESTITUTION, DEFAULT_COLLISIONS_RESTITUTION, DEFAULT_ENVIRONMENT_DAMPING, DEFAULT_ENVIRONMENT_FRICTION, DEFAULT_ENVIRONMENT_GRAVITY_ANGLE, DEFAULT_ENVIRONMENT_GRAVITY_DIRECTION, DEFAULT_ENVIRONMENT_GRAVITY_STRENGTH, DEFAULT_ENVIRONMENT_INERTIA, DEFAULT_FLUIDS_ENABLE_NEAR_PRESSURE, DEFAULT_FLUIDS_INFLUENCE_RADIUS, DEFAULT_FLUIDS_MAX_ACCELERATION, DEFAULT_FLUIDS_NEAR_PRESSURE_MULTIPLIER, DEFAULT_FLUIDS_NEAR_THRESHOLD, DEFAULT_FLUIDS_PRESSURE_MULTIPLIER, DEFAULT_FLUIDS_TARGET_DENSITY, DEFAULT_FLUIDS_VISCOSITY, DEFAULT_GRAB_GRABBED_INDEX, DEFAULT_GRAB_POSITION_X, DEFAULT_GRAB_POSITION_Y, DEFAULT_INTERACTION_MODE, DEFAULT_INTERACTION_RADIUS, DEFAULT_INTERACTION_STRENGTH, DEFAULT_JOINTS_ENABLE_JOINT_COLLISIONS, DEFAULT_JOINTS_ENABLE_PARTICLE_COLLISIONS, DEFAULT_JOINTS_FRICTION, DEFAULT_JOINTS_MOMENTUM, DEFAULT_JOINTS_RESTITUTION, DEFAULT_JOINTS_SEPARATION, DEFAULT_JOINTS_STEPS, DEFAULT_PICFLIP_DENSITY, DEFAULT_PICFLIP_FLIP_RATIO, DEFAULT_PICFLIP_INFLUENCE_RADIUS, DEFAULT_PICFLIP_INTERNAL_MAX_ACCELERATION, DEFAULT_PICFLIP_PRESSURE, DEFAULT_PICFLIP_PRESSURE_MULTIPLIER, DEFAULT_PICFLIP_RADIUS, DEFAULT_PICFLIP_TARGET_DENSITY, DEFAULT_SENSORS_COLOR_SIMILARITY_THRESHOLD, DEFAULT_SENSORS_FLEE_ANGLE, DEFAULT_SENSORS_FLEE_BEHAVIOR, DEFAULT_SENSORS_FOLLOW_BEHAVIOR, DEFAULT_SENSORS_SENSOR_ANGLE, DEFAULT_SENSORS_SENSOR_DISTANCE, DEFAULT_SENSORS_SENSOR_RADIUS, DEFAULT_SENSORS_SENSOR_STRENGTH, DEFAULT_SENSORS_SENSOR_THRESHOLD, DEFAULT_TRAILS_TRAIL_DECAY, DEFAULT_TRAILS_TRAIL_DIFFUSE, DataType, Engine, Environment, Fluids, FluidsMethod, Grab, Interaction, Joints, Lines, Module, ModuleRole, OscillatorManager, Particles, ParticlesColorType, RenderPassKind, Sensors, Spawner, Trails, Vector, degToRad, radToDeg };
|
|
10576
|
+
export { AbstractEngine, Behavior, Boundary, CanvasComposition, Collisions, DEFAULT_BEHAVIOR_ALIGNMENT, DEFAULT_BEHAVIOR_AVOID, DEFAULT_BEHAVIOR_CHASE, DEFAULT_BEHAVIOR_COHESION, DEFAULT_BEHAVIOR_REPULSION, DEFAULT_BEHAVIOR_SEPARATION, DEFAULT_BEHAVIOR_VIEW_ANGLE, DEFAULT_BEHAVIOR_VIEW_RADIUS, DEFAULT_BEHAVIOR_WANDER, DEFAULT_BOUNDARY_FRICTION, DEFAULT_BOUNDARY_MODE, DEFAULT_BOUNDARY_REPEL_DISTANCE, DEFAULT_BOUNDARY_REPEL_STRENGTH, DEFAULT_BOUNDARY_RESTITUTION, DEFAULT_COLLISIONS_RESTITUTION, DEFAULT_ENVIRONMENT_DAMPING, DEFAULT_ENVIRONMENT_FRICTION, DEFAULT_ENVIRONMENT_GRAVITY_ANGLE, DEFAULT_ENVIRONMENT_GRAVITY_DIRECTION, DEFAULT_ENVIRONMENT_GRAVITY_STRENGTH, DEFAULT_ENVIRONMENT_INERTIA, DEFAULT_FLUIDS_ENABLE_NEAR_PRESSURE, DEFAULT_FLUIDS_INFLUENCE_RADIUS, DEFAULT_FLUIDS_MAX_ACCELERATION, DEFAULT_FLUIDS_NEAR_PRESSURE_MULTIPLIER, DEFAULT_FLUIDS_NEAR_THRESHOLD, DEFAULT_FLUIDS_PRESSURE_MULTIPLIER, DEFAULT_FLUIDS_TARGET_DENSITY, DEFAULT_FLUIDS_VISCOSITY, DEFAULT_GRAB_GRABBED_INDEX, DEFAULT_GRAB_POSITION_X, DEFAULT_GRAB_POSITION_Y, DEFAULT_INTERACTION_MODE, DEFAULT_INTERACTION_RADIUS, DEFAULT_INTERACTION_STRENGTH, DEFAULT_JOINTS_ENABLE_JOINT_COLLISIONS, DEFAULT_JOINTS_ENABLE_PARTICLE_COLLISIONS, DEFAULT_JOINTS_FRICTION, DEFAULT_JOINTS_MOMENTUM, DEFAULT_JOINTS_RESTITUTION, DEFAULT_JOINTS_SEPARATION, DEFAULT_JOINTS_STEPS, DEFAULT_PICFLIP_DENSITY, DEFAULT_PICFLIP_FLIP_RATIO, DEFAULT_PICFLIP_INFLUENCE_RADIUS, DEFAULT_PICFLIP_INTERNAL_MAX_ACCELERATION, DEFAULT_PICFLIP_PRESSURE, DEFAULT_PICFLIP_PRESSURE_MULTIPLIER, DEFAULT_PICFLIP_RADIUS, DEFAULT_PICFLIP_TARGET_DENSITY, DEFAULT_SENSORS_COLOR_SIMILARITY_THRESHOLD, DEFAULT_SENSORS_FLEE_ANGLE, DEFAULT_SENSORS_FLEE_BEHAVIOR, DEFAULT_SENSORS_FOLLOW_BEHAVIOR, DEFAULT_SENSORS_SENSOR_ANGLE, DEFAULT_SENSORS_SENSOR_DISTANCE, DEFAULT_SENSORS_SENSOR_RADIUS, DEFAULT_SENSORS_SENSOR_STRENGTH, DEFAULT_SENSORS_SENSOR_THRESHOLD, DEFAULT_TRAILS_TRAIL_DECAY, DEFAULT_TRAILS_TRAIL_DIFFUSE, DataType, Engine, Environment, Fluids, FluidsMethod, Grab, Interaction, Joints, Lines, Module, ModuleRole, OscillatorManager, Particles, ParticlesColorType, RenderPassKind, Sensors, Spawner, TEXT_SPAWNER_FONTS, Trails, Vector, degToRad, radToDeg };
|
|
10396
10577
|
//# sourceMappingURL=index.js.map
|