cubeforge 0.4.10 → 0.4.11
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/dist/index.d.ts +28 -1
- package/dist/index.js +113 -3
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -531,8 +531,35 @@ interface ParticleEmitterProps {
|
|
|
531
531
|
* - `'screen'` — lightens, softer than additive
|
|
532
532
|
*/
|
|
533
533
|
blendMode?: 'normal' | 'additive' | 'multiply' | 'screen';
|
|
534
|
+
/**
|
|
535
|
+
* Formation mode: particles lerp toward fixed target positions instead of
|
|
536
|
+
* being emitted with velocity. Enables logo reveals, constellations, shape morphing.
|
|
537
|
+
* - `'standard'` (default) — normal emit/gravity/lifetime behaviour
|
|
538
|
+
* - `'formation'` — particles seek `formationPoints`; no gravity, no expiry
|
|
539
|
+
*/
|
|
540
|
+
mode?: 'standard' | 'formation';
|
|
541
|
+
/**
|
|
542
|
+
* Target positions for formation mode. One particle is spawned per point.
|
|
543
|
+
* Change this array to morph the formation — particles smoothly lerp to new targets.
|
|
544
|
+
*/
|
|
545
|
+
formationPoints?: {
|
|
546
|
+
x: number;
|
|
547
|
+
y: number;
|
|
548
|
+
}[];
|
|
549
|
+
/**
|
|
550
|
+
* How strongly particles seek their target each frame in formation mode.
|
|
551
|
+
* Exponential lerp factor (0–1). Default 0.055 (~5.5% per frame at 60 fps).
|
|
552
|
+
*/
|
|
553
|
+
seekStrength?: number;
|
|
554
|
+
/**
|
|
555
|
+
* Smoothly transition all particles to this color.
|
|
556
|
+
* Works in both standard and formation modes.
|
|
557
|
+
*/
|
|
558
|
+
targetColor?: string;
|
|
559
|
+
/** Duration of the global color transition in seconds. Default 0.5. */
|
|
560
|
+
colorTransitionDuration?: number;
|
|
534
561
|
}
|
|
535
|
-
declare function ParticleEmitter({ active, preset, rate, speed, spread, angle, particleLife, particleSize, color, gravity, maxParticles, burstCount, emitShape, emitRadius, emitWidth, emitHeight, textureSrc, enableRotation, rotationSpeedRange, sizeOverLife, attractors, colorOverLife, blendMode, }: ParticleEmitterProps): null;
|
|
562
|
+
declare function ParticleEmitter({ active, preset, rate, speed, spread, angle, particleLife, particleSize, color, gravity, maxParticles, burstCount, emitShape, emitRadius, emitWidth, emitHeight, textureSrc, enableRotation, rotationSpeedRange, sizeOverLife, attractors, colorOverLife, blendMode, mode, formationPoints, seekStrength, targetColor, colorTransitionDuration, }: ParticleEmitterProps): null;
|
|
536
563
|
|
|
537
564
|
interface VirtualJoystickProps {
|
|
538
565
|
/** Diameter of the joystick base in pixels (default 120) */
|
package/dist/index.js
CHANGED
|
@@ -3653,15 +3653,91 @@ var RenderSystem = class {
|
|
|
3653
3653
|
for (const id of world.query("Transform", "ParticlePool")) {
|
|
3654
3654
|
const t = world.getComponent(id, "Transform");
|
|
3655
3655
|
const pool = world.getComponent(id, "ParticlePool");
|
|
3656
|
+
if (pool.targetColor && pool._colorTransitionFrom !== void 0) {
|
|
3657
|
+
pool._colorTransitionElapsed = (pool._colorTransitionElapsed ?? 0) + dt;
|
|
3658
|
+
const dur = pool.colorTransitionDuration ?? 0.5;
|
|
3659
|
+
const ct = Math.min((pool._colorTransitionElapsed ?? 0) / dur, 1);
|
|
3660
|
+
const ease = ct * ct * (3 - 2 * ct);
|
|
3661
|
+
const [cr0, cg0, cb0] = parseCSSColor(pool._colorTransitionFrom);
|
|
3662
|
+
const [cr1, cg1, cb1] = parseCSSColor(pool.targetColor);
|
|
3663
|
+
const ri = Math.round(cr0 + (cr1 - cr0) * ease);
|
|
3664
|
+
const gi = Math.round(cg0 + (cg1 - cg0) * ease);
|
|
3665
|
+
const bi = Math.round(cb0 + (cb1 - cb0) * ease);
|
|
3666
|
+
pool.color = `rgb(${ri},${gi},${bi})`;
|
|
3667
|
+
if (ct >= 1) {
|
|
3668
|
+
pool._colorTransitionFrom = void 0;
|
|
3669
|
+
pool._colorTransitionElapsed = void 0;
|
|
3670
|
+
}
|
|
3671
|
+
}
|
|
3672
|
+
const isFormation = pool.mode === "formation";
|
|
3656
3673
|
pool.particles = pool.particles.filter((p) => {
|
|
3674
|
+
if (isFormation) {
|
|
3675
|
+
if (pool.attractors) {
|
|
3676
|
+
for (const attr of pool.attractors) {
|
|
3677
|
+
const adx = attr.x - p.x;
|
|
3678
|
+
const ady = attr.y - p.y;
|
|
3679
|
+
const dist = Math.sqrt(adx * adx + ady * ady);
|
|
3680
|
+
if (dist < attr.radius && dist > 0) {
|
|
3681
|
+
const force = attr.strength * (1 - dist / attr.radius);
|
|
3682
|
+
p.vx += adx / dist * force * dt;
|
|
3683
|
+
p.vy += ady / dist * force * dt;
|
|
3684
|
+
}
|
|
3685
|
+
}
|
|
3686
|
+
}
|
|
3687
|
+
p.vx *= 0.82;
|
|
3688
|
+
p.vy *= 0.82;
|
|
3689
|
+
p.x += p.vx * dt;
|
|
3690
|
+
p.y += p.vy * dt;
|
|
3691
|
+
if (p.targetX !== void 0 && p.targetY !== void 0) {
|
|
3692
|
+
const seek2 = pool.seekStrength ?? 0.055;
|
|
3693
|
+
p.x += (p.targetX - p.x) * seek2;
|
|
3694
|
+
p.y += (p.targetY - p.y) * seek2;
|
|
3695
|
+
}
|
|
3696
|
+
return true;
|
|
3697
|
+
}
|
|
3657
3698
|
p.life -= dt;
|
|
3699
|
+
if (pool.attractors) {
|
|
3700
|
+
for (const attr of pool.attractors) {
|
|
3701
|
+
const adx = attr.x - p.x;
|
|
3702
|
+
const ady = attr.y - p.y;
|
|
3703
|
+
const dist = Math.sqrt(adx * adx + ady * ady);
|
|
3704
|
+
if (dist < attr.radius && dist > 0) {
|
|
3705
|
+
const force = attr.strength * (1 - dist / attr.radius);
|
|
3706
|
+
p.vx += adx / dist * force * dt;
|
|
3707
|
+
p.vy += ady / dist * force * dt;
|
|
3708
|
+
}
|
|
3709
|
+
}
|
|
3710
|
+
}
|
|
3658
3711
|
p.x += p.vx * dt;
|
|
3659
3712
|
p.y += p.vy * dt;
|
|
3660
3713
|
p.vy += p.gravity * dt;
|
|
3661
3714
|
if (p.rotationSpeed !== void 0) p.rotation = (p.rotation ?? 0) + p.rotationSpeed * dt;
|
|
3662
3715
|
return p.life > 0;
|
|
3663
3716
|
});
|
|
3664
|
-
if (
|
|
3717
|
+
if (isFormation) {
|
|
3718
|
+
const fp = pool.formationPoints ?? [];
|
|
3719
|
+
while (pool.particles.length < fp.length) {
|
|
3720
|
+
const idx = pool.particles.length;
|
|
3721
|
+
const startSize = pool.sizeOverLife?.start ?? pool.particleSize;
|
|
3722
|
+
const endSize = pool.sizeOverLife?.end ?? pool.particleSize;
|
|
3723
|
+
pool.particles.push({
|
|
3724
|
+
x: t.x + (world.rng() - 0.5) * 20,
|
|
3725
|
+
y: t.y + (world.rng() - 0.5) * 20,
|
|
3726
|
+
vx: 0,
|
|
3727
|
+
vy: 0,
|
|
3728
|
+
life: 1,
|
|
3729
|
+
maxLife: 1,
|
|
3730
|
+
size: startSize,
|
|
3731
|
+
startSize,
|
|
3732
|
+
endSize,
|
|
3733
|
+
color: pool.color,
|
|
3734
|
+
gravity: 0,
|
|
3735
|
+
rotation: 0,
|
|
3736
|
+
targetX: fp[idx].x,
|
|
3737
|
+
targetY: fp[idx].y
|
|
3738
|
+
});
|
|
3739
|
+
}
|
|
3740
|
+
} else if (pool.active && pool.particles.length < pool.maxParticles) {
|
|
3665
3741
|
let spawnCount;
|
|
3666
3742
|
if (pool.burstCount != null && pool.burstCount > 0) {
|
|
3667
3743
|
spawnCount = pool.burstCount;
|
|
@@ -15556,7 +15632,12 @@ function ParticleEmitter({
|
|
|
15556
15632
|
sizeOverLife,
|
|
15557
15633
|
attractors,
|
|
15558
15634
|
colorOverLife,
|
|
15559
|
-
blendMode
|
|
15635
|
+
blendMode,
|
|
15636
|
+
mode,
|
|
15637
|
+
formationPoints,
|
|
15638
|
+
seekStrength,
|
|
15639
|
+
targetColor,
|
|
15640
|
+
colorTransitionDuration
|
|
15560
15641
|
}) {
|
|
15561
15642
|
const presetConfig = preset ? PARTICLE_PRESETS[preset] : {};
|
|
15562
15643
|
const resolvedRate = rate ?? presetConfig.rate ?? 20;
|
|
@@ -15596,7 +15677,11 @@ function ParticleEmitter({
|
|
|
15596
15677
|
sizeOverLife,
|
|
15597
15678
|
attractors,
|
|
15598
15679
|
colorOverLife,
|
|
15599
|
-
blendMode
|
|
15680
|
+
blendMode,
|
|
15681
|
+
mode,
|
|
15682
|
+
formationPoints,
|
|
15683
|
+
seekStrength,
|
|
15684
|
+
colorTransitionDuration
|
|
15600
15685
|
});
|
|
15601
15686
|
return () => engine.ecs.removeComponent(entityId, "ParticlePool");
|
|
15602
15687
|
}, []);
|
|
@@ -15605,6 +15690,31 @@ function ParticleEmitter({
|
|
|
15605
15690
|
if (!pool) return;
|
|
15606
15691
|
pool.active = active;
|
|
15607
15692
|
}, [active, engine, entityId]);
|
|
15693
|
+
useEffect28(() => {
|
|
15694
|
+
const pool = engine.ecs.getComponent(entityId, "ParticlePool");
|
|
15695
|
+
if (!pool) return;
|
|
15696
|
+
pool.attractors = attractors;
|
|
15697
|
+
}, [attractors, engine, entityId]);
|
|
15698
|
+
useEffect28(() => {
|
|
15699
|
+
const pool = engine.ecs.getComponent(entityId, "ParticlePool");
|
|
15700
|
+
if (!pool || pool.mode !== "formation" || !formationPoints) return;
|
|
15701
|
+
pool.formationPoints = formationPoints;
|
|
15702
|
+
const shuffled = [...formationPoints].sort(() => Math.random() - 0.5);
|
|
15703
|
+
pool.particles.forEach((p, i) => {
|
|
15704
|
+
if (shuffled[i]) {
|
|
15705
|
+
p.targetX = shuffled[i].x;
|
|
15706
|
+
p.targetY = shuffled[i].y;
|
|
15707
|
+
}
|
|
15708
|
+
});
|
|
15709
|
+
}, [formationPoints, engine, entityId]);
|
|
15710
|
+
useEffect28(() => {
|
|
15711
|
+
const pool = engine.ecs.getComponent(entityId, "ParticlePool");
|
|
15712
|
+
if (!pool || !targetColor) return;
|
|
15713
|
+
pool._colorTransitionFrom = pool.color;
|
|
15714
|
+
pool._colorTransitionElapsed = 0;
|
|
15715
|
+
pool.targetColor = targetColor;
|
|
15716
|
+
pool.colorTransitionDuration = colorTransitionDuration;
|
|
15717
|
+
}, [targetColor, colorTransitionDuration, engine, entityId]);
|
|
15608
15718
|
return null;
|
|
15609
15719
|
}
|
|
15610
15720
|
|