Documents' helpers are compiled into the kernels they feed, so a document calling a VM-only helper (Motion's prog/pulse/spring_step, the native ease(@name, u)) could not share it with a kernel; the P(DOOM) plates carried their own copies for that reason. - std/shared.splash (documents, shaders, kernels): prog(t, a, b) (0 at a, 1 at b, held outside; b below a runs from 1 down to 0, a = b steps), remap(x, a, b, c, d), pulse(t, t0, half_life), spring_step(t, freq, damping). The kernel prelude's remap and std.anim's pulse (an exp form of the same curve) are deleted; anim.pulse_sum uses the shared one. - The kernel parser reads `@name` and lowers ease(@ease_x, u) to std.ease's x(u) (@linear: clamp; @ease_in/@ease_out/@ease_in_out: quad), Motion's formulas; an ease(…) without an @name is an ordinary call (a kernel's own `ease`). Checked: cargo test --release -p makepad-script-compute --features vm (all, the AI-style corpus with its own ease functions included); every P(DOOM) plate rendered through the commercial change on top. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
119 lines
4.7 KiB
Text
119 lines
4.7 KiB
Text
// Animation evaluated per element (AK1): tweens, keyframe tracks, springs,
|
|
// pulses and event steps over time `t`. Tracks live in buffers: `times`
|
|
// (ascending), `values`, n keys.
|
|
use std.search.*
|
|
use std.ease as ease
|
|
|
|
// Progress of t through [t0, t1], 0..1 (0 before, 1 after).
|
|
fn tween(t, t0, t1) { if t1 > t0 { clamp((t - t0) / (t1 - t0), 0.0, 1.0) } else { if t >= t1 { 1.0 } else { 0.0 } } }
|
|
|
|
// A value between a and b over [t0, t1] (apply an ease to `tween` for
|
|
// curves: `mix(a, b, ease.out_cubic(tween(t, t0, t1)))`).
|
|
fn lerp_over(t, t0, t1, a, b) { mix(a, b, tween(t, t0, t1)) }
|
|
|
|
// A keyframe track: linear between keys, held before the first and after
|
|
// the last.
|
|
fn keys(times, values, n, t) {
|
|
let k = last_le(times, n, t)
|
|
if k < 0 { values[0] }
|
|
elif k >= n - 1 { values[n - 1] }
|
|
else { mix(values[k], values[k + 1], tween(t, times[k], times[k + 1])) }
|
|
}
|
|
|
|
// A Motion key track (motion eval.rs sample_track) in f64: `times` (f64,
|
|
// ascending), `values` (f64, `width` per key; this reads component
|
|
// `comp`), per key an ease id (`eases`, i32: ease.apply_d's ids) and four
|
|
// ease params (`eparams`, f64); key k's ease shapes the segment ending at
|
|
// key k. The first key's value before it, the last one's after.
|
|
fn track_d(times, values, width, comp, eases, eparams, n, t) {
|
|
let tt = f64(t)
|
|
if !(tt > times[0]) { values[comp] }
|
|
else {
|
|
let next = last_le(times, n, tt) + 1
|
|
if next >= n { values[(n - 1) * width + comp] }
|
|
else {
|
|
let a = next - 1
|
|
let span = times[next] - times[a]
|
|
let u = ease.apply_d(eases[next], (tt - times[a]) / max(span, f64(0.000000000001)), span, eparams[next * 4], eparams[next * 4 + 1], eparams[next * 4 + 2], eparams[next * 4 + 3])
|
|
values[a * width + comp] + (values[next * width + comp] - values[a * width + comp]) * u
|
|
}
|
|
}
|
|
}
|
|
|
|
// A precomputed history (render-kernels' `precompute`, AK5): record i of
|
|
// step s at words (s * count + i) * stride; word `word` of element i at time
|
|
// t, linear between steps and held at the ends.
|
|
fn history_at(table, count, stride, word, i, t0, dt, steps, t) {
|
|
let last = steps - 1
|
|
let x = clamp((t - t0) / dt, 0.0, float(last))
|
|
let k = min(int(floor(x)), max(last - 1, 0))
|
|
let a = table[(k * count + i) * stride + word]
|
|
if last == 0 { a } else {
|
|
let b = table[((k + 1) * count + i) * stride + word]
|
|
a + (b - a) * (x - float(k))
|
|
}
|
|
}
|
|
|
|
// The index of the last event at or before t (-1: none yet), and how long
|
|
// ago it was.
|
|
fn step_index(times, n, t) { last_le(times, n, t) }
|
|
fn since_last(times, n, t) {
|
|
let k = last_le(times, n, t)
|
|
if k < 0 { 1000000000.0 } else { t - times[k] }
|
|
}
|
|
|
|
// A value that jumps to values[k] at each event and settles there with a
|
|
// spring from the previous value (frequency `freq` Hz, damping ratio `z`).
|
|
fn stepped(times, values, n, t, freq, z) {
|
|
let k = last_le(times, n, t)
|
|
if k < 0 { values[0] }
|
|
else {
|
|
let prev = values[max(k - 1, 0)]
|
|
mix(prev, values[k], spring_to(t - times[k], freq, z))
|
|
}
|
|
}
|
|
|
|
// Step response of a spring (0 -> 1) after `age` seconds: `freq` Hz,
|
|
// damping ratio `z` (under 1 overshoots).
|
|
fn spring_to(age, freq, z) {
|
|
if age <= 0.0 { 0.0 }
|
|
else {
|
|
let w = TAU * freq
|
|
let zz = clamp(z, 0.0, 0.999)
|
|
let wd = w * sqrt(1.0 - zz * zz)
|
|
1.0 - exp(-zz * w * age) * (cos(wd * age) + zz * w / wd * sin(wd * age))
|
|
}
|
|
}
|
|
|
|
// (A decaying pulse from one event is the shared stdlib's `pulse(t, t0, half_life)`.)
|
|
|
|
// Sum of the pulses of the (at most 32) latest events at or before t.
|
|
fn pulse_sum(times, n, t, half_life) {
|
|
let k = last_le(times, n, t)
|
|
let s = 0.0
|
|
for j in 0..32 {
|
|
let e = k - j
|
|
if e >= 0 { s = s + pulse(t, times[e], half_life) }
|
|
}
|
|
s
|
|
}
|
|
|
|
// Exponential follow toward a target: the fraction covered after `dt`
|
|
// with lag `lag` seconds (63% after one lag).
|
|
fn follow(dt, lag) { if lag > 0.0 { 1.0 - exp(-dt / lag) } else { 1.0 } }
|
|
|
|
// Distance travelled from rest under constant acceleration `a`, capped at
|
|
// speed `vmax` (for launches and settles).
|
|
fn accelerate(t, a, vmax) {
|
|
let t1 = if a > 0.0 { vmax / a } else { 0.0 }
|
|
if t <= 0.0 { 0.0 } elif t < t1 { 0.5 * a * t * t } else { 0.5 * a * t1 * t1 + vmax * (t - t1) }
|
|
}
|
|
|
|
// Birth-clock emitters (AK10): particles born at `rate` per second, each
|
|
// living `life` seconds. A kernel runs `births_max(life, rate)` elements;
|
|
// element e is birth `births_first(t, life, rate) + e`, born at
|
|
// `birth_time(index, rate)` (dead when born after t). Thin with
|
|
// `hash01(index, k) < keep` for a rate that varies.
|
|
fn births_first(t, life, rate) { int(floor(max(t - life, 0.0) * rate)) }
|
|
fn births_max(life, rate) { int(ceil(life * rate)) + 1 }
|
|
fn birth_time(index, rate) { float(index) / rate }
|