Files
MobileGL/intro-video/hype/anim.py
T

125 lines
3.5 KiB
Python

"""Easing, palette and per-frame timing helpers shared by the scenes."""
import math
from engine import hexc, mix, with_alpha, scale_rgb
import score as S
# --- palette (sampled from the MobileGlues icon, pushed for contrast) --------------------------------
INK = hexc('#06110a') # near-black green: dark plates
INK2 = hexc('#0d2213') # panels on dark
MOSS = hexc('#1d4a25') # deep forest
LEAF = hexc('#4e9a3e') # icon outline green
GLUE = hexc('#6edb55') # icon highlight lime: the signal colour
NEON = hexc('#8cff62', 2.6) # emissive lime (blooms)
MINT = hexc('#a0e28c') # icon background: light plates
PALE = hexc('#d4f0c9') # pale top face
BONE = hexc('#f1f7ec') # type on dark
CORAL = hexc('#ff6a55') # rare accent: errors / device lost
CORAL_HOT = hexc('#ff6a55', 2.2)
AMBER = hexc('#e7a94f') # speckle tan, tiny accents
CLEAR = (0.0, 0.0, 0.0, 0.0)
def a(c, al):
return with_alpha(c, al)
# --- easing ----------------------------------------------------------------------------------------
def clamp01(x):
return 0.0 if x < 0 else 1.0 if x > 1 else x
def lin(t, t0, t1):
return clamp01((t - t0) / (t1 - t0)) if t1 != t0 else float(t >= t0)
def out_expo(u):
u = clamp01(u)
return 1.0 if u >= 1 else 1 - 2 ** (-10 * u)
def in_expo(u):
u = clamp01(u)
return 0.0 if u <= 0 else 2 ** (10 * u - 10)
def out_cubic(u):
u = clamp01(u)
return 1 - (1 - u) ** 3
def in_cubic(u):
u = clamp01(u)
return u ** 3
def in_out_cubic(u):
u = clamp01(u)
return 4 * u ** 3 if u < 0.5 else 1 - (-2 * u + 2) ** 3 / 2
def out_back(u, k=1.7):
u = clamp01(u)
return 1 + (k + 1) * (u - 1) ** 3 + k * (u - 1) ** 2
def spring(u, freq=3.0, damp=6.0):
"""0 -> 1 with overshoot; u in seconds-ish units (unclamped above)."""
if u <= 0:
return 0.0
return 1 - math.exp(-damp * u) * math.cos(2 * math.pi * freq * u)
def bump(u):
"""0 -> 1 -> 0 over u in [0, 1]."""
u = clamp01(u)
return math.sin(math.pi * u)
def lerp(a_, b, u):
return a_ + (b - a_) * u
def rnd(*k):
"""Deterministic hash -> [0, 1)."""
h = 2166136261
for v in k:
for ch in repr(v):
h = ((h ^ ord(ch)) * 16777619) & 0xffffffff
h ^= h >> 13
h = (h * 1274126177) & 0xffffffff
return (h & 0xffffff) / float(0x1000000)
# --- frame timing ----------------------------------------------------------------------------------
class Frame:
def __init__(self, t, entry_start_bar=0):
self.t = t
self.bar = t / S.BAR
self.beat = t / S.BEAT
self.b0 = entry_start_bar
self.lt = t - entry_start_bar * S.BAR # local seconds in entry
self.lbeat = self.lt / S.BEAT # local beats
self.lbar = self.lt / S.BAR
self.kick = S.pulse(t, 'kick', 0.11)
self.clap = S.pulse(t, 'clap', 0.10)
self.hat = S.pulse(t, 'hat', 0.05)
self.crash = S.pulse(t, 'crash', 0.6)
self.impact = S.pulse(t, 'impact', 0.35)
self.roll = S.pulse(t, 'roll', 0.08)
def at(self, beat):
"""Seconds since local beat `beat` (negative before)."""
return self.lt - beat * S.BEAT
def u(self, b0, b1):
"""Progress through local beats [b0, b1]."""
return clamp01((self.lbeat - b0) / (b1 - b0))
def step(self, div=1.0):
"""Index of the current local beat subdivision (div=2 -> 8ths)."""
return int(math.floor(self.lbeat * div + 1e-6))
def frame_idx(t, fps=60):
return int(math.floor(t * fps + 1e-6))