@cazala/party 0.1.0-next.46.59ce407 → 0.1.0-next.48.389a3a7

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 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
15
16
 
16
17
  ## Installation
17
18
 
@@ -189,6 +190,36 @@ 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) 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
+
192
223
  ### Modules
193
224
 
194
225
  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, } = options;
5476
5477
  const particles = [];
5477
5478
  if (count <= 0)
5478
5479
  return particles;
@@ -5499,6 +5500,93 @@ 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
+ }
5502
5590
  if (shape === "grid") {
5503
5591
  const cols = Math.ceil(Math.sqrt(count));
5504
5592
  const rows = Math.ceil(count / cols);
@@ -10392,5 +10480,5 @@ class Particles extends Module {
10392
10480
  }
10393
10481
  }
10394
10482
 
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 };
10483
+ 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
10484
  //# sourceMappingURL=index.js.map