Thanks to visit codestin.com
Credit goes to github.com

Skip to content

sugarcraft/honey-bounce

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

72 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

honey-bounce

HoneyBounce

CI codecov Packagist Version License PHP

PHP port of charmbracelet/harmonica β€” damped-spring physics + Newtonian projectile simulation for animation. Pure math; no terminal dependency.

composer require sugarcraft/honey-bounce

Spring

Damped harmonic oscillator (Ryan-Juckett's algorithm). Choose dampingRatio: < 1 oscillates, = 1 is critical (no overshoot, fastest convergence), > 1 is over-damped.

use SugarCraft\Bounce\Spring;

$spring = new Spring(
    deltaTime:        Spring::fps(60),  // 1/60 of a second
    angularFrequency: 6.0,              // rad/sec
    dampingRatio:     1.0,             // critical
);
$pos = 0.0;
$vel = 0.0;
$target = 100.0;

for ($frame = 0; $frame < 60; $frame++) {
    [$pos, $vel] = $spring->update($pos, $vel, $target);
    echo sprintf("frame %2d  pos=%.2f  vel=%.2f\n", $frame, $pos, $vel);
}

Spring::fps(int $n) returns 1.0 / $n for the deltaTime β€” pair with the same $n per-second simulation cadence.

Reduced motion

When the REDUCE_MOTION=1 environment variable is set or the terminal signals prefers-reduced-motion, Spring::update() snaps to $target instantly and returns [<target>, 0.0]. This satisfies the WCAG 2.1 reduced-motion guideline and matches the behaviour of SugarCraft\Palette\Probe::reducedMotion().

// With REDUCE_MOTION=1 the spring skips animation entirely:
putenv('REDUCE_MOTION=1');
[$pos, $vel] = $spring->update(0.0, 0.0, 100.0);  // returns [100.0, 0.0]

Spring presets

Spring::fromPreset(SpringPreset $preset, ?float $deltaTime = null) constructs a spring from a named preset at 60 fps (override the frame time as needed). Five presets are available, translated from UIKit's canonical values:

Preset Feel Tension Friction Mass
Gentle soft, slow overshoot 100 10 1
Wobbly bouncy oscillation 180 12 1
Stiff snappy snap 500 20 1
Slow heavy, lazy settle 50 6 1
Molasses barely moves 30 4 1
use SugarCraft\Bounce\{Spring, SpringPreset};

$spring = Spring::fromPreset(SpringPreset::Wobbly);
// With custom frame rate
$spring60 = Spring::fromPreset(SpringPreset::Stiff, 1.0 / 60.0);
$spring30 = Spring::fromPreset(SpringPreset::Gentle, 1.0 / 30.0);

SpringConfig

SpringConfig accepts physical parameters (tension / friction / mass) and derives the angularFrequency and dampingRatio consumed by Spring:

angularFrequency = sqrt(tension / mass)
dampingRatio     = friction / (2 * sqrt(tension * mass))
use SugarCraft\Bounce\{SpringConfig, Spring};

$config = new SpringConfig(tension: 180.0, friction: 12.0, mass: 1.0);
$spring = $config->springAt60Fps();  // or ->spring($deltaTime)

Both SpringConfig::spring() and SpringConfig::springAt60Fps() return a pre-wired Spring instance ready to drive update() calls.

Projectile

Newtonian-physics simulator for arcs / bouncing balls / particle effects.

use SugarCraft\Bounce\{Point, Projectile, Vector};

$p = Projectile::new(
    deltaTime:    Spring::fps(60),
    position:     Point::zero(),
    velocity:     new Vector(5.0, -10.0),
    acceleration: Projectile::gravity(),  // (0, 9.81) β€” Y-down
);
for ($i = 0; $i < 60; $i++) {
    $p = $p->update();
    echo sprintf("t=%2d  pos=(%.1f, %.1f)\n", $i, $p->position->x, $p->position->y);
}

Gravity constants: Projectile::GRAVITY (9.81) and Projectile::TERMINAL_GRAVITY (53.0). Helper factories Projectile::gravity() and Projectile::terminalGravity() return Y-axis Vector instances ready to drop into the constructor.

SugarCraft\Bounce\Gravity exposes the same vectors as static accessors at the package level β€” Gravity::standard(), Gravity::terminal(), Gravity::standardYDown(), Gravity::terminalYDown() β€” so call sites translating from harmonica's package-level Gravity / TerminalGravity constants read uniformly.

Damping-ratio regimes

The dampingRatio argument to Spring picks one of three classical behaviours:

  • Under-damped (ΞΆ < 1) β€” oscillates around the target, amplitudes decaying each cycle. Picks for "bouncy" feel.
  • Critically-damped (ΞΆ = 1) β€” fastest convergence with no overshoot. The default for "snap to value" animations.
  • Over-damped (ΞΆ > 1) β€” converges without overshoot but slower than critical. Picks for slow, weighty motion.

Negative damping ratios are clamped to 0 (a pure oscillator with no decay would never settle).

Coordinate systems

Both Vector and Point are 3D (x, y, z) β€” the constructor's $z defaults to 0.0 so existing 2D call sites still compile unchanged. Use the third dimension when porting demos that need a Z axis (parallax / depth-shaded particle systems).

The Y-axis convention is Y-up by default to match upstream harmonica: Gravity::standard() returns (0, -9.81, 0) so increasing Y means "up the screen". Terminal renderers usually grow downward β€” flip to Gravity::standardYDown() (or its Projectile::gravityYDown() alias) when you want gravity to pull toward the bottom of the grid without manually negating every coordinate.

Projectile::update() returns a new Projectile instance each call (immutable-with-pattern); upstream Projectile.Update() returns the new Point and mutates the receiver in place. Read the new position from result->position rather than $p->position().

SpringChain

Sequence multiple springs so that one spring's settle triggers the next. Useful for staggered animations where each stage must complete before the next begins.

use SugarCraft\Bounce\{SpringChain, Spring, SpringPreset};

$chain = (new SpringChain([]))
    ->withStage(Spring::fromPreset(SpringPreset::Gentle), 0.0, 0.0, 50.0)
    ->withStage(Spring::fromPreset(SpringPreset::Wobbly), 0.0, 0.0, 100.0)
    ->withStage(Spring::fromPreset(SpringPreset::Stiff),  0.0, 0.0, 75.0);

while (!$chain->isComplete()) {
    [$positions, $complete] = $chain->tick();
    // $positions reflects settled stages + the currently animating stage
}

Each tick() call advances only the active stage. When that stage reaches its target (position and velocity both within 0.001 of target), the chain activates the next stage. isComplete() returns true when all stages have settled.

Easing

SugarCraft\Bounce\Easing\Easing provides named easing curves via its ease(float $t): float method β€” apply to any normalized time value in [0.0, 1.0]:

use SugarCraft\Bounce\Easing\Easing;

$ease = Easing::ElasticOut;
for ($f = 0; $f <= 60; $f++) {
    $t = $f / 60.0;
    echo sprintf("frame %2d  t=%.3f  eased=%.3f\n", $f, $t, $ease->ease($t));
}

CubicBezier

CubicBezier implements the CSS cubic-bezier() easing algorithm (Newton-Raphson root-finding with binary-search fallback) for monotonic interpolation. Construct via static factory methods covering all 24 CSS standard easings:

use SugarCraft\Bounce\Easing\CubicBezier;

// CSS named easings
$ease      = CubicBezier::ease();       // 0.25, 0.10, 0.25, 1.00
$easeIn   = CubicBezier::easeIn();      // 0.42, 0.00, 1.00, 1.00
$easeOut  = CubicBezier::easeOut();    // 0.00, 0.00, 0.58, 1.00
$easeInOut = CubicBezier::easeInOut(); // 0.42, 0.00, 0.58, 1.00
$linear   = CubicBezier::linear();      // 0.00, 0.00, 1.00, 1.00

// Sine
$easeInSine      = CubicBezier::easeInSine();
$easeOutSine     = CubicBezier::easeOutSine();
$easeInOutSine   = CubicBezier::easeInOutSine();

// Quadratic
$easeInQuad      = CubicBezier::easeInQuad();
$easeOutQuad     = CubicBezier::easeOutQuad();
$easeInOutQuad   = CubicBezier::easeInOutQuad();

// Cubic
$easeInCubic     = CubicBezier::easeInCubic();
$easeOutCubic    = CubicBezier::easeOutCubic();
$easeInOutCubic  = CubicBezier::easeInOutCubic();

// Quartic / Quintic / Exponential / Circular
$easeInQuart     = CubicBezier::easeInQuart();
$easeOutQuart    = CubicBezier::easeOutQuart();
$easeInOutQuart  = CubicBezier::easeInOutQuart();

$easeInQuint     = CubicBezier::easeInQuint();
$easeOutQuint    = CubicBezier::easeOutQuint();
$easeInOutQuint  = CubicBezier::easeInOutQuint();

$easeInExpo      = CubicBezier::easeInExpo();
$easeOutExpo     = CubicBezier::easeOutExpo();
$easeInOutExpo   = CubicBezier::easeInOutExpo();

$easeInCirc      = CubicBezier::easeInCirc();
$easeOutCirc     = CubicBezier::easeOutCirc();
$easeInOutCirc  = CubicBezier::easeInOutCirc();

for ($f = 0; $f <= 60; $f++) {
    $t = $f / 60.0;
    echo sprintf("frame %2d  eased=%.4f\n", $f, $easeInOutCubic->evaluate($t));
}

CubicBezier::evaluate(float $t): float maps [0, 1] β†’ [0, 1] using the Newton-Raphson algorithm from the W3C CSS Easing spec.

Public API

  • Spring β€” __construct($dt, $Ο‰, $ΞΆ) / update($pos, $vel, $target) / fps(int) / fromPreset(SpringPreset, ?float). update() short-circuits to [$target, 0.0] when Probe::reducedMotion() is true.
  • SpringChain β€” __construct($stages) / build($stages) / withStage(Spring, $pos, $vel, $target) / tick(): (list<float>, bool) / currentPositions(): list<float> / isComplete(): bool / activeStage(): int.
  • SpringCollection β€” add($id, Spring, ...) / remove($id) / tick(): array<string,float> / get($id): float / has($id): bool / all(): array<string,float> / setTarget($id, $target) / getTarget($id): float.
  • SpringPreset β€” Gentle / Wobbly / Stiff / Slow / Molasses. resolve() returns a SpringConfig.
  • SpringConfig β€” __construct(tension, friction, mass) / spring(float $dt) / springAt60Fps().
  • Projectile β€” Projectile::new(...) / update() / position() / velocity() / acceleration() / gravity() / terminalGravity() / gravityYDown() / terminalGravityYDown() / GRAVITY / TERMINAL_GRAVITY.
  • Gravity β€” package-level static accessors mirroring harmonica's Gravity / TerminalGravity constants: standard(), terminal(), standardYDown(), terminalYDown().
  • Vector β€” immutable 3D vector with add / sub / scale / length / dot / cross / Vector::zero().
  • Point β€” immutable 3D point with add(Vector) / distance / Point::zero().
  • Easing β€” enum with ease(float $t): float. Cases: Linear, QuadraticIn/Out/InOut, CubicIn/Out/InOut, ElasticIn/Out/InOut, BounceIn/Out/InOut, BackIn/Out/InOut.
  • CubicBezier β€” evaluate(float $t): float. Static factories for all 24 CSS named easings (ease, easeIn, easeOut, easeInOut, easeIn/OutSine/Quad/Cubic/Quart/Quint/Expo/Circ, linear).

Test

cd honey-bounce && composer install && vendor/bin/phpunit

Demos

Projectile motion

projectile

Spring physics

spring

About

🐝 PHP port of ⚑️ harmonica β€” damped spring physics, easing curves, Newtonian projectile simulation for terminal animation.

Topics

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages