Entrar
PT
games/satisfying-bounce / src / Scene / PendulumScene.php · commit 8bdd6e4
<?phpnamespace App\Scene;use App\Audio\Scale;use App\Render\Gfx;use JumPHP\Renderer\Renderer;/** * A row of pendulums of slightly different lengths, started together: they drift apart into travelling waves, a snake, a tangle, and come * back in line exactly after 24 seconds. Pendulum i swings 14 + i times in the cycle, and each one rings its own note of a pentatonic scale * at the right end of every swing: the notes start as one chord, scatter into rhythms and gather again. The film is one cycle: a perfect loop. */final class PendulumScene extends VideoScene{    public const SECONDS = 24.0;    protected const LOOPS = true;    public const PERIOD = 24.0;    public const COUNT = 12;    private const TOP = 108.0;    /** @var array<int, int> pendulum => swings completed (the right end reached) */    private array $swings = [];    /** @var array<int, float> */    private array $flash = [];    protected function melodyNotes(): string    {        return 'C4';    }    protected function headingText(): string    {        return 'PENDULUM WAVE';    }    protected function hookText(): string    {        return 'WAIT FOR THEM TO LINE UP';    }    protected function captionText(): string    {        return $this->t('SWINGS {n}', ['n' => array_sum($this->swings)]);    }    protected function build(): void    {        $this->swings = array_fill(0, self::COUNT, -1);        $this->flash = array_fill(0, self::COUNT, 0.0);    }    /** the angle of pendulum $i at $time (it is at its right end whenever $time * (14 + i) / PERIOD is a whole number) */    public static function angle(int $i, float $time): float    {        return 0.27 * cos(2 * M_PI * (14 + $i) * $time / self::PERIOD);    }    public function swings(int $i): int    {        return $this->swings[$i];    }    protected function tick(float $dt): void    {        for ($i = 0; $i < self::COUNT; $i++) {            $n = (int)floor((14 + $i) * $this->time / self::PERIOD + 1e-9);            if ($n > $this->swings[$i]) {                $this->swings[$i] = $n;                $this->flash[$i] = 1.0;                $this->play(Scale::freq(Scale::MAJOR_PENTA, 2 + $i, 55), 'marimba', 0.8, 0.12, 0.0);                $this->pulse = max($this->pulse, 0.3);                $this->tint = Gfx::hsv($i / self::COUNT);            }            $this->flash[$i] = max(0.0, $this->flash[$i] - $dt * 3.5);        }    }    protected function drawWorld(Renderer $r): void    {        $gap = (270 - 50) / (self::COUNT - 1);        Gfx::bar($r, 18, self::TOP, 252, self::TOP, 4, [120, 126, 190]);        for ($i = 0; $i < self::COUNT; $i++) {            $len = 300.0 / (1 + 0.78 * ($i / (self::COUNT - 1))) ** 2;      // longer ones swing slower            $px = 25 + $i * $gap;            $a = self::angle($i, $this->time);            $x = $px + sin($a) * $len;            $y = self::TOP + cos($a) * $len;            $rgb = Gfx::hsv($i / self::COUNT * 0.85 + 0.02, 0.55, 1.0);            $f = $this->flash[$i];            Gfx::bar($r, $px, self::TOP, $x, $y, 0.9, Gfx::mix(Gfx::BG_BOTTOM, $rgb, 0.75));            Gfx::glow($r, $x, $y, 7 + 3 * $f, $rgb, 0.6 + 0.8 * $f);            Gfx::disc($r, $x, $y, 7 + 1.5 * $f, Gfx::mix($rgb, [255, 255, 255], $f * 0.8));            Gfx::disc($r, $x - 2.2, $y - 2.4, 2, [255, 255, 255], 190);        }    }}