@cazala/party 0.4.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 +24 -2
- package/dist/index.js +94 -1
- package/dist/index.js.map +1 -1
- package/dist/spawner.d.ts +3 -1
- package/dist/spawner.d.ts.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -12,7 +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
|
|
15
|
+
- **Spawner Utility**: Generate particle shapes, including text and images
|
|
16
16
|
|
|
17
17
|
## Installation
|
|
18
18
|
|
|
@@ -192,7 +192,7 @@ engine.unpinAll();
|
|
|
192
192
|
|
|
193
193
|
### Spawner
|
|
194
194
|
|
|
195
|
-
Generate particle arrays from common shapes (including text) using `Spawner`:
|
|
195
|
+
Generate particle arrays from common shapes (including text and images) using `Spawner`:
|
|
196
196
|
|
|
197
197
|
```typescript
|
|
198
198
|
import { Spawner } from "@cazala/party";
|
|
@@ -220,6 +220,28 @@ Notes:
|
|
|
220
220
|
- `size` controls particle radius; `textSize` is the font size used to rasterize text.
|
|
221
221
|
- Playground font options: `sans-serif`, `serif`, `monospace`.
|
|
222
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
|
+
|
|
223
245
|
### Modules
|
|
224
246
|
|
|
225
247
|
Modules are pluggable components that contribute to simulation or rendering:
|
package/dist/index.js
CHANGED
|
@@ -5473,7 +5473,7 @@ function calculateVelocity(position, center, cfg) {
|
|
|
5473
5473
|
}
|
|
5474
5474
|
class Spawner {
|
|
5475
5475
|
initParticles(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, } = 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;
|
|
5477
5477
|
const particles = [];
|
|
5478
5478
|
if (count <= 0)
|
|
5479
5479
|
return particles;
|
|
@@ -5587,6 +5587,99 @@ class Spawner {
|
|
|
5587
5587
|
}
|
|
5588
5588
|
return particles;
|
|
5589
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
|
+
}
|
|
5590
5683
|
if (shape === "grid") {
|
|
5591
5684
|
const cols = Math.ceil(Math.sqrt(count));
|
|
5592
5685
|
const rows = Math.ceil(count / cols);
|