TR
games/satisfying-bounce / src / Scene / SpiralScene.php · commit 8bdd6e4
<?phpnamespace App\Scene;use App\Audio\Scale;use App\Render\Gfx;use JumPHP\Renderer\Renderer;/** * A spiral of dots that turns: the arms seem to flow outwards (or inwards) while the dots only circle, their size ripples along the arms and the * colours run round. Each arm that goes past the top or the bottom plays a note of a whole-tone scale (dreamy, no resolution). One turn * every 4 seconds, so the film loops with no seam. */final class SpiralScene extends VideoScene{    public const SECONDS = 24.0;    protected const LOOPS = true;    public const PERIOD = 4.0;    public const ARMS = 8;    private const DOTS = 24;    private int $lastTick = -1;    private float $glowTop = 0.0;    protected function melodyNotes(): string    {        return 'C4';    }    protected function headingText(): string    {        return 'WATCH THE CENTRE';    }    protected function hookText(): string    {        return 'ARE THE ARMS MOVING OUT?';    }    protected function captionText(): string    {        return $this->t('TURNS {n}', ['n' => (int)floor($this->time / self::PERIOD)]);    }    protected function build(): void    {        $this->lastTick = -1;        $this->glowTop = 0.0;    }    protected function tick(float $dt): void    {        // an arm is at the top (or the bottom) every PERIOD / (2 ARMS) seconds        $n = (int)floor($this->time / self::PERIOD * self::ARMS * 2);        if ($n > $this->lastTick) {            $this->lastTick = $n;            $this->glowTop = 1.0;            $this->play(Scale::freq(Scale::WHOLE_TONE, 2 + [0, 3, 1, 4, 2, 5, 3, 6][$n % 8] + ($n % 2) * 6, 60), 'bell', 0.9, 0.22, 0.0);            $this->pulse = 0.8;            $this->tint = Gfx::hsv($n * 0.05);        }        $this->glowTop = max(0.0, $this->glowTop - $dt * 3);    }    protected function drawWorld(Renderer $r): void    {        $cx = 135.0;        $cy = 262.0;        $turn = $this->time / self::PERIOD;        for ($arm = 0; $arm < self::ARMS; $arm++) {            for ($j = 0; $j < self::DOTS; $j++) {                $u = ($j + 1) / self::DOTS;                              // 0..1 along the arm                $rad = 8 + 128 * $u ** 0.9;                $a = $turn * 2 * M_PI + $arm * 2 * M_PI / self::ARMS + $u * 4.4;                $size = 1.1 + 4.6 * $u * (0.55 + 0.45 * sin(2 * M_PI * (3 * $u - 2 * $turn)));                $hue = $arm / self::ARMS + $u * 0.35 + $turn * 0.25;                $x = $cx + cos($a) * $rad;                $y = $cy + sin($a) * $rad;                Gfx::disc($r, $x, $y, $size, Gfx::hsv($hue, 0.55, 0.65 + 0.35 * $u));            }        }        Gfx::glow($r, $cx, $cy, 5 + 4 * $this->glowTop, [255, 255, 255], 0.9);        Gfx::disc($r, $cx, $cy, 3.4, [255, 255, 255]);    }}