mirror of
https://github.com/storytold/deckcraft.git
synced 2026-10-29 21:16:57 -06:00
Every transition and Effect Option was built in PowerPoint (COM), exported as video and compared frame by frame with DeckCraft at the same progress (mean abs diff over 150 variants 35.7 -> 15.0, 0-255). - anim: transition_frame(kind, option, t, aspect) with measured curves (smoothstep fades, ease-in-out moves, Cover/Uncover/Pan curves), directions and option meanings, soft edges as per-corner opacity, 3-D effects as perspective-projected cards, Dynamic Content moving slide contents over cross-fading backgrounds (Source::OldBack/NewBack/OldContent/NewContent); layer_mesh subdivides perspective quads so textures stay straight. - New options: Page Curl Single, Glitter Hexagons, Shred In/Out, Zoom and Rotate; PowerPoint-style Effect Options labels. - render: composite_transition (CPU twin of the show), render_slide_part, render_transition; deckcraft-cli render-transition FILE --slide N --t T. - show: per-vertex opacity meshes, slide parts rendered once per transition. - pptx: PowerPoint's elements and defaults (p:strips diagonal wipes, p14:warp and p:newsflash for Zoom, p:zoom for Shape In/Out, glitter pattern, shred rectangle/dir, prstTrans invX per preset, pageCurlSingle, defaults for randomBar/doors/window/pan/zoom); PowerPoint reopens DeckCraft's files with the same effects. - Tests: measured curves, probes of which slide shows where at t, option coverage, per-frame triangle budget, PowerPoint XML and defaults round trip. Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
1170 lines
46 KiB
Rust
1170 lines
46 KiB
Rust
//! The transition effects, as measured on PowerPoint (see [`crate::transition`]).
|
|
//!
|
|
//! `t` is linear progress (0..1) through the transition's duration; each effect applies its own
|
|
//! curve. Directions follow the file format's option: for Push, Wipe, Cover, Uncover, Pan, Vortex,
|
|
//! Cube… `dir` is where things move (`l`: toward the left); for Glitter it is the side the effect
|
|
//! starts from; Ripple's corner is where the wave heads.
|
|
|
|
use std::f64::consts::PI;
|
|
|
|
use crate::easing::ease;
|
|
use crate::transition::{FULL, Layer, R, Source};
|
|
use crate::transition_geom::*;
|
|
|
|
use Source::{Black, New, NewBack, NewContent, Old, OldBack, OldContent, White};
|
|
|
|
const RANDOM_KINDS: &[&str] = &[
|
|
"fade",
|
|
"push",
|
|
"wipe",
|
|
"split",
|
|
"reveal",
|
|
"randomBar",
|
|
"shape",
|
|
"uncover",
|
|
"cover",
|
|
"fallOver",
|
|
"drape",
|
|
"curtains",
|
|
"wind",
|
|
"prestige",
|
|
"fracture",
|
|
"crush",
|
|
"peelOff",
|
|
"pageCurlDouble",
|
|
"airplane",
|
|
"origami",
|
|
"dissolve",
|
|
"checker",
|
|
"blinds",
|
|
"clock",
|
|
"ripple",
|
|
"honeycomb",
|
|
"glitter",
|
|
"vortex",
|
|
"shred",
|
|
"switch",
|
|
"flip",
|
|
"gallery",
|
|
"cube",
|
|
"doors",
|
|
"box",
|
|
"comb",
|
|
"zoom",
|
|
"pan",
|
|
"ferris",
|
|
"conveyor",
|
|
"rotate",
|
|
"window",
|
|
"orbit",
|
|
"flythrough",
|
|
];
|
|
|
|
pub(crate) fn layers(kind: &str, o: &str, t: f64, a: f64, depth: u32) -> Vec<Layer> {
|
|
match kind {
|
|
"none" => vec![Layer::full(New, 1.0)],
|
|
"cut" => {
|
|
if o == "throughBlack" && t < 0.5 {
|
|
vec![Layer::full(Black, 1.0)]
|
|
} else {
|
|
vec![Layer::full(New, 1.0)]
|
|
}
|
|
}
|
|
"fade" if o == "throughBlack" => through_black(t, 0.5, 0.5),
|
|
"fade" | "morph" => vec![Layer::full(Old, 1.0), Layer::full(New, ss(t))],
|
|
"push" => {
|
|
let (dx, dy) = dir(o, (-1.0, 0.0));
|
|
let s = q2(t);
|
|
vec![moved(Old, dx * s, dy * s, 1.0), moved(New, dx * (s - 1.0), dy * (s - 1.0), 1.0)]
|
|
}
|
|
"uncover" => {
|
|
let (dx, dy) = dir(o, (-1.0, 0.0));
|
|
// Diagonal: the old slide accelerates off in the first third.
|
|
let s = if o.len() == 2 { ein(win(t, 0.05, 0.35)) } else { pow_inout(t, 3.0) };
|
|
vec![Layer::full(New, 1.0), moved(Old, dx * s, dy * s, 1.0)]
|
|
}
|
|
"cover" => {
|
|
let (dx, dy) = dir(o, (-1.0, 0.0));
|
|
// Diagonal: the new slide shoots in and settles slowly.
|
|
let s = if o.len() == 2 { 1.0 - (1.0 - win(t, 0.07, 0.95)).powi(3) } else { pow_inout(t, 4.0) };
|
|
vec![Layer::full(Old, 1.0), moved(New, dx * (s - 1.0), dy * (s - 1.0), 1.0)]
|
|
}
|
|
"wipe" => wipe(o, t),
|
|
"split" => split(o, t),
|
|
"reveal" => reveal(o, t),
|
|
"randomBar" => random_bars(o == "vert", t),
|
|
"shape" => shape(o, t, a),
|
|
"flash" => flash(t),
|
|
"dissolve" => dissolve(t),
|
|
"checker" => checker(o == "vert", t, a),
|
|
"blinds" => blinds(o == "vert", t, a),
|
|
"clock" => clock(o, t, a),
|
|
"ripple" => ripple(o, t, a),
|
|
"comb" => comb(o == "vert", t),
|
|
"zoom" => zoom(o, t, a),
|
|
"pan" => pan(o, t),
|
|
"fallOver" => fall_over(o == "r", t, a),
|
|
"drape" => drape(o == "r", t, a),
|
|
"curtains" => curtains(t, a),
|
|
"wind" => wind(o == "r", t, a),
|
|
"prestige" => prestige(t, a),
|
|
"fracture" => fracture(t, a),
|
|
"crush" => crush(t, a),
|
|
"peelOff" => peel_off(o == "r", t),
|
|
"pageCurlDouble" => page_curl(o, t),
|
|
"airplane" => airplane(o == "r", t, a),
|
|
"origami" => origami(o == "r", t, a),
|
|
"honeycomb" => honeycomb(t, a),
|
|
"glitter" => glitter(o, t, a),
|
|
"vortex" => vortex(o, t, a),
|
|
"shred" => shred(o, t, a),
|
|
"switch" => switch(o == "r", t, a),
|
|
"flip" => flip(o == "r", t, a),
|
|
"gallery" => gallery(o == "r", t, a),
|
|
"conveyor" => conveyor(o == "r", t, a),
|
|
"cube" | "box" | "rotate" | "orbit" => prism(kind, o, t, a),
|
|
"doors" | "window" => doors(kind == "window", o == "horz", t, a),
|
|
"ferris" => ferris(o == "r", t, a),
|
|
"flythrough" => flythrough(o, t),
|
|
"random" if depth == 0 => {
|
|
let h = o.bytes().fold(0x51ED_u64, |h, b| hash64(h ^ b as u64));
|
|
let k = RANDOM_KINDS.get((h % RANDOM_KINDS.len() as u64) as usize).copied().unwrap_or("fade");
|
|
let opt = deckcraft_model::anim::TRANSITIONS.iter().find(|x| x.0 == k).and_then(|x| x.4.first().copied()).unwrap_or("");
|
|
layers(k, opt, t, a, depth + 1)
|
|
}
|
|
_ => vec![Layer::full(Old, 1.0), Layer::full(New, ss(t))],
|
|
}
|
|
}
|
|
|
|
/// Old fades to black over [0, `out`], new fades in from black over [`inn`, 1] (smoothstep).
|
|
fn through_black(t: f64, out: f64, inn: f64) -> Vec<Layer> {
|
|
let mut v = vec![Layer::full(Black, 1.0)];
|
|
if t < out {
|
|
v.push(Layer::full(Old, 1.0 - ss(win(t, 0.0, out))));
|
|
}
|
|
if t > inn {
|
|
v.push(Layer::full(New, ss(win(t, inn, 1.0))));
|
|
}
|
|
v
|
|
}
|
|
|
|
// ---------- Subtle ----------
|
|
|
|
/// Width of the soft edge of Wipe and Split (slide units along the motion).
|
|
const WIPE_SOFT: f64 = 0.8;
|
|
|
|
/// Where the middle of the soft edge is at `t`: from fully before the slide to fully past it.
|
|
fn wipe_pos(t: f64) -> f64 {
|
|
-WIPE_SOFT / 2.0 + (1.0 + WIPE_SOFT) * 1.05 * t
|
|
}
|
|
|
|
fn wipe(o: &str, t: f64) -> Vec<Layer> {
|
|
let c = wipe_pos(t);
|
|
// Position along the motion, 0 where the new slide starts showing.
|
|
let p: Box<dyn Fn(f64, f64) -> f64> = match o {
|
|
"l" => Box::new(|x, _| 1.0 - x),
|
|
"u" => Box::new(|_, y| 1.0 - y),
|
|
"d" => Box::new(|_, y| y),
|
|
"lu" => Box::new(|x, y| (2.0 - x - y) / 2.0),
|
|
"ru" => Box::new(|x, y| (1.0 + x - y) / 2.0),
|
|
"ld" => Box::new(|x, y| (1.0 - x + y) / 2.0),
|
|
"rd" => Box::new(|x, y| (x + y) / 2.0),
|
|
_ => Box::new(|x, _| x),
|
|
};
|
|
let diag = o.len() == 2;
|
|
let (n, m) = if diag {
|
|
(32, 18)
|
|
} else if matches!(o, "u" | "d") {
|
|
(1, 36)
|
|
} else {
|
|
(48, 1)
|
|
};
|
|
let mut v = vec![Layer::full(Old, 1.0)];
|
|
v.extend(field(New, n, m, &|x, y| edge(c, p(x, y), WIPE_SOFT)));
|
|
v
|
|
}
|
|
|
|
fn split(o: &str, t: f64) -> Vec<Layer> {
|
|
let horz = o.starts_with("horz");
|
|
let inward = o.ends_with("In");
|
|
let c = wipe_pos(t);
|
|
let p = move |x: f64, y: f64| {
|
|
let d = if horz { (y - 0.5).abs() * 2.0 } else { (x - 0.5).abs() * 2.0 };
|
|
if inward { 1.0 - d } else { d }
|
|
};
|
|
let (n, m) = if horz { (1, 36) } else { (48, 1) };
|
|
let mut v = vec![Layer::full(Old, 1.0)];
|
|
v.extend(field(New, n, m, &|x, y| edge(c, p(x, y), WIPE_SOFT)));
|
|
v
|
|
}
|
|
|
|
fn reveal(o: &str, t: f64) -> Vec<Layer> {
|
|
let dx = if o.ends_with("Right") { 1.0 } else { -1.0 };
|
|
if o.starts_with("black") {
|
|
// Old slides away fading to black, a pause on black, new slides in from black.
|
|
let mut v = vec![Layer::full(Black, 1.0)];
|
|
if t < 0.4 {
|
|
let u = win(t, 0.0, 0.38);
|
|
v.push(moved(Old, dx * 0.12 * ein(u), 0.0, 1.0 - ss(u)));
|
|
}
|
|
if t > 0.55 {
|
|
let u = win(t, 0.55, 1.0);
|
|
v.push(moved(New, -dx * 0.12 * (1.0 - eout(u)), 0.0, ss(u)));
|
|
}
|
|
return v;
|
|
}
|
|
// Smoothly: the backgrounds cross-fade while the old content slides off fading and the new
|
|
// content follows.
|
|
let mut v = vec![Layer::full(OldBack, 1.0), Layer::full(NewBack, ss(t))];
|
|
if t < 0.5 {
|
|
let u = win(t, 0.0, 0.45);
|
|
v.push(moved(OldContent, dx * 0.12 * ein(u), 0.0, 1.0 - ss(u)));
|
|
}
|
|
if t > 0.45 {
|
|
let u = win(t, 0.47, 0.95);
|
|
v.push(moved(NewContent, -dx * 0.12 * (1.0 - eout(u)), 0.0, ss(u)));
|
|
}
|
|
v
|
|
}
|
|
|
|
fn random_bars(vert: bool, t: f64) -> Vec<Layer> {
|
|
// Thin bars, each cross-fading quickly at its own random moment.
|
|
let n = 160usize;
|
|
let w = 0.2;
|
|
let mut v = vec![Layer::full(Old, 1.0)];
|
|
for i in 0..n {
|
|
let start = rnd(i as u64 * 7 + 17) * (1.0 - w);
|
|
let k = win(t, start, start + w);
|
|
if k <= 0.0 {
|
|
continue;
|
|
}
|
|
let (a, b) = (i as f64 / n as f64, (i + 1) as f64 / n as f64);
|
|
let r: R = if vert { [a, 0.0, b, 1.0] } else { [0.0, a, 1.0, b] };
|
|
v.push(Layer::inplace(New, r, k));
|
|
}
|
|
v
|
|
}
|
|
|
|
fn shape(o: &str, t: f64, a: f64) -> Vec<Layer> {
|
|
match o {
|
|
// Shape In: the old slide shrinks into the centre over the first half.
|
|
"in" => {
|
|
let s = 1.0 - win(t, 0.0, 0.5);
|
|
let mut v = vec![Layer::full(New, 1.0)];
|
|
if s > 0.0 {
|
|
v.extend(soft_rect(Old, FULL, scale_rect(FULL, s), 0.012, 1.0));
|
|
}
|
|
v
|
|
}
|
|
// Shape Out: the new slide grows out of the centre over the second half.
|
|
"out" => {
|
|
let s = win(t, 0.5, 1.0);
|
|
let mut v = vec![Layer::full(Old, 1.0)];
|
|
if s > 0.0 {
|
|
v.extend(soft_rect(New, FULL, scale_rect(FULL, s), 0.012, 1.0));
|
|
}
|
|
v
|
|
}
|
|
_ => {
|
|
let soft = 0.18;
|
|
let c = -soft / 2.0 + (1.0 + soft) * t;
|
|
let f: Box<dyn Fn(f64, f64) -> f64> = match o {
|
|
"diamond" => Box::new(move |x, y| edge(c, (x - 0.5).abs() + (y - 0.5).abs(), soft)),
|
|
"plus" => Box::new(move |x, y| edge(c, (x - 0.5).abs().min((y - 0.5).abs()) * 2.0, soft)),
|
|
_ => {
|
|
let rmax = ((0.5 * a).powi(2) + 0.25).sqrt();
|
|
Box::new(move |x, y| edge(c, (((x - 0.5) * a).powi(2) + (y - 0.5).powi(2)).sqrt() / rmax, soft))
|
|
}
|
|
};
|
|
let mut v = vec![Layer::full(Old, 1.0)];
|
|
v.extend(field(New, 48, 27, &*f));
|
|
v
|
|
}
|
|
}
|
|
}
|
|
|
|
fn flash(t: f64) -> Vec<Layer> {
|
|
// A flash bulb: the old slide burns out to white fast, the white holds, then fades off the new
|
|
// slide.
|
|
let white = if t < 0.1 {
|
|
t / 0.1
|
|
} else if t < 0.47 {
|
|
1.0
|
|
} else {
|
|
1.0 - win(t, 0.47, 0.88)
|
|
};
|
|
let base = if t < 0.2 { Old } else { New };
|
|
vec![Layer::full(base, 1.0), Layer::full(White, white)]
|
|
}
|
|
|
|
// ---------- Exciting: patterns ----------
|
|
|
|
fn dissolve(t: f64) -> Vec<Layer> {
|
|
let (n, m) = (48usize, 27usize);
|
|
let mut v = vec![Layer::full(Old, 1.0)];
|
|
let cell = |c: usize, r: usize| [c as f64 / n as f64, r as f64 / m as f64, (c + 1) as f64 / n as f64, (r + 1) as f64 / m as f64];
|
|
for r in 0..m {
|
|
for c in 0..n {
|
|
if t > rnd((r * n + c) as u64 + 991) * 0.97 {
|
|
v.push(Layer::inplace(New, cell(c, r), 1.0));
|
|
}
|
|
}
|
|
}
|
|
v
|
|
}
|
|
|
|
/// One tile flipping in 3-D about its own vertical (or horizontal) axis: old face for the first
|
|
/// half of `k`, new face for the second.
|
|
fn flip_tile(cam: &Cam, cell: R, k: f64, vertical_axis: bool, out: &mut Vec<Layer>) {
|
|
if k <= 0.0 {
|
|
out.push(Layer::inplace(Old, cell, 1.0));
|
|
return;
|
|
}
|
|
if k >= 1.0 {
|
|
out.push(Layer::inplace(New, cell, 1.0));
|
|
return;
|
|
}
|
|
let ang = 180.0 * k;
|
|
let cc = center(cell);
|
|
let c3 = cam.unit(cc[0], cc[1]);
|
|
let (src, deg) = if k < 0.5 { (Old, ang) } else { (New, ang - 180.0) };
|
|
let pts = cam.rect(cell).map(|p| if vertical_axis { p.yaw(c3, deg) } else { p.pitch(c3, deg) });
|
|
let sh = facing_shade(pts, 0.6);
|
|
out.extend(card(cam, src, cell, pts, 1.0, sh, true));
|
|
}
|
|
|
|
fn checker(vert: bool, t: f64, a: f64) -> Vec<Layer> {
|
|
let (n, m) = (10usize, 8usize);
|
|
let cam = Cam { a, d: 3.0 };
|
|
let mut v = vec![Layer::full(Black, 1.0)];
|
|
for r in 0..m {
|
|
for c in 0..n {
|
|
let cell = [c as f64 / n as f64, r as f64 / m as f64, (c + 1) as f64 / n as f64, (r + 1) as f64 / m as f64];
|
|
let across = if vert { r as f64 / m as f64 } else { c as f64 / n as f64 };
|
|
let start = across * 0.55 + rnd((r * n + c) as u64 + 5) * 0.12;
|
|
let k = q2(win(t, start, start + 0.3));
|
|
flip_tile(&cam, cell, k, !vert, &mut v);
|
|
}
|
|
}
|
|
v
|
|
}
|
|
|
|
fn blinds(vert: bool, t: f64, a: f64) -> Vec<Layer> {
|
|
let n = 12usize;
|
|
let cam = Cam { a, d: 3.0 };
|
|
let mut v = vec![Layer::full(Black, 1.0)];
|
|
for i in 0..n {
|
|
let (s, e) = (i as f64 / n as f64, (i + 1) as f64 / n as f64);
|
|
let cell = if vert { [s, 0.0, e, 1.0] } else { [0.0, s, 1.0, e] };
|
|
let start = 0.1 + 0.3 * i as f64 / n as f64;
|
|
let k = q2(win(t, start, start + 0.4));
|
|
flip_tile(&cam, cell, k, vert, &mut v);
|
|
}
|
|
v
|
|
}
|
|
|
|
fn clock(o: &str, t: f64, a: f64) -> Vec<Layer> {
|
|
// The hand sweeps at a constant speed with a soft leading edge.
|
|
let soft = 0.12;
|
|
let c = -soft / 2.0 + (1.0 + soft) * t;
|
|
let f = move |ang: f64| -> f64 {
|
|
let p = match o {
|
|
"counterClockwise" => (2.0 * PI - ang) / (2.0 * PI),
|
|
"wedge" => {
|
|
let d = if ang > PI { 2.0 * PI - ang } else { ang };
|
|
d / PI
|
|
}
|
|
_ => ang / (2.0 * PI),
|
|
};
|
|
edge(c, p, soft)
|
|
};
|
|
let mut v = vec![Layer::full(Old, 1.0)];
|
|
v.extend(fan(New, a, 240, &f));
|
|
v
|
|
}
|
|
|
|
fn ripple(o: &str, t: f64, a: f64) -> Vec<Layer> {
|
|
// A soft disc of the new slide spreads from the centre, or from the corner opposite to where
|
|
// the wave heads.
|
|
let origin = match o {
|
|
"lu" => [1.0, 1.0],
|
|
"ru" => [0.0, 1.0],
|
|
"ld" => [1.0, 0.0],
|
|
"rd" => [0.0, 0.0],
|
|
_ => [0.5, 0.5],
|
|
};
|
|
let far = [[0.0, 0.0], [1.0, 0.0], [0.0, 1.0], [1.0, 1.0]]
|
|
.iter()
|
|
.map(|p: &[f64; 2]| (((p[0] - origin[0]) * a).powi(2) + (p[1] - origin[1]).powi(2)).sqrt())
|
|
.fold(0.0, f64::max);
|
|
let soft = 0.16;
|
|
let c = -soft / 2.0 + (1.0 + soft) * t;
|
|
let mut v = vec![Layer::full(Old, 1.0)];
|
|
let f = |x: f64, y: f64| {
|
|
let d = (((x - origin[0]) * a).powi(2) + (y - origin[1]).powi(2)).sqrt() / far.max(1e-6);
|
|
edge(c, d, soft)
|
|
};
|
|
v.extend(field(New, 48, 27, &f));
|
|
// Rings of shade travelling inside the wave.
|
|
let rings = |x: f64, y: f64| {
|
|
let d = (((x - origin[0]) * a).powi(2) + (y - origin[1]).powi(2)).sqrt() / far.max(1e-6);
|
|
let inside = edge(c, d, soft);
|
|
let w = ((d - c) * 30.0).sin().abs();
|
|
0.18 * inside * (1.0 - w) * (1.0 - t)
|
|
};
|
|
v.extend(field(Black, 48, 27, &rings));
|
|
v
|
|
}
|
|
|
|
fn comb(vert: bool, t: f64) -> Vec<Layer> {
|
|
// Bands of the old slide slide off to alternate sides, one after another from the last band,
|
|
// uncovering the new slide.
|
|
let n = if vert { 10usize } else { 7usize };
|
|
let mut v = vec![Layer::full(New, 1.0)];
|
|
for i in 0..n {
|
|
let (a, b) = (i as f64 / n as f64, (i + 1) as f64 / n as f64);
|
|
let d = if (n - 1 - i) % 2 == 0 { 1.0 } else { -1.0 };
|
|
let start = 0.06 + 0.05 * (n - 1 - i) as f64 * 7.0 / n as f64;
|
|
let s = ein(win(t, start, start + 0.55));
|
|
if s >= 1.0 {
|
|
continue;
|
|
}
|
|
let band = if vert { [a, 0.0, b, 1.0] } else { [0.0, a, 1.0, b] };
|
|
let (ox, oy) = if vert { (0.0, d * s) } else { (d * s, 0.0) };
|
|
v.push(Layer::rect(Old, band, offset(band, ox, oy), 1.0));
|
|
}
|
|
v
|
|
}
|
|
|
|
fn zoom(o: &str, t: f64, a: f64) -> Vec<Layer> {
|
|
match o {
|
|
// Zoom and Rotate: the new slide spins in from the centre, a full clockwise turn, fading
|
|
// in as it grows.
|
|
"rotate" => {
|
|
let u = win(t, 0.1, 0.98);
|
|
let s = 0.1 + 0.9 * u;
|
|
let ang = 360.0 * (1.0 - u);
|
|
let r = scale_rect(FULL, s);
|
|
let q = rot_quad(crate::transition::corners(r), [0.5, 0.5], ang, a);
|
|
vec![Layer::full(Old, 1.0), Layer::quad(New, FULL, q, 0.35 + 0.65 * ss(win(t, 0.0, 0.45)))]
|
|
}
|
|
// Zoom Out: the new slide comes down from large underneath while the old one shrinks away
|
|
// fading.
|
|
"out" => {
|
|
let s = eout(win(t, 0.0, 0.8));
|
|
vec![
|
|
scaled(New, 1.9 - 0.9 * s, 0.5, 0.5, 0.0, 0.0, 1.0),
|
|
scaled(Old, 1.0 - 0.5 * eout(t), 0.5, 0.5, 0.0, 0.0, 1.0 - ss(win(t, 0.0, 0.95))),
|
|
]
|
|
}
|
|
// Zoom In: the old slide grows toward the viewer fading, the new one grows into place
|
|
// from small.
|
|
_ => vec![
|
|
Layer::full(Black, 1.0),
|
|
scaled(Old, 1.0 + 0.3 * ss(t), 0.5, 0.5, 0.0, 0.0, 1.0 - ss(win(t, 0.0, 0.9))),
|
|
scaled(New, 0.5 + 0.5 * ss(win(t, 0.15, 0.95)), 0.5, 0.5, 0.0, 0.0, ss(win(t, 0.0, 0.85))),
|
|
],
|
|
}
|
|
}
|
|
|
|
fn fall_over(right: bool, t: f64, a: f64) -> Vec<Layer> {
|
|
// The old slide tips forward about its bottom edge and falls out of view.
|
|
let cam = Cam { a, d: 2.2 };
|
|
let u = ein(win(t, 0.0, 0.88));
|
|
let ang = 92.0 * u;
|
|
let pivot = v3(0.0, 0.5, 0.0);
|
|
let side = if right { 1.0 } else { -1.0 };
|
|
let pts = cam.rect(FULL).map(|p| p.pitch(pivot, ang).roll(pivot, side * 6.0 * u));
|
|
let mut v = vec![Layer::full(New, 1.0)];
|
|
if u < 1.0 {
|
|
v.extend(card(&cam, Old, FULL, pts, 1.0, facing_shade(pts, 0.4), true));
|
|
}
|
|
v
|
|
}
|
|
|
|
fn drape(right: bool, t: f64, a: f64) -> Vec<Layer> {
|
|
// The new slide drops from its top edge like cloth over the old one.
|
|
let cam = Cam { a, d: 2.2 };
|
|
let u = q2(win(t, 0.25, 0.62));
|
|
let wob = 4.0 * (1.0 - win(t, 0.6, 1.0)) * (win(t, 0.6, 1.0) * PI * 2.0).sin();
|
|
let ang = 90.0 * (1.0 - u) + wob * u;
|
|
let pivot = v3(0.0, -0.5, 0.0);
|
|
let side = if right { -1.0 } else { 1.0 };
|
|
let pts = cam.rect(FULL).map(|p| p.pitch(pivot, -ang).roll(pivot, side * 3.0 * (1.0 - u)));
|
|
let mut v = vec![Layer::full(Old, 1.0)];
|
|
if u > 0.0 {
|
|
v.extend(card(&cam, New, FULL, pts, 1.0, facing_shade(pts, 0.5), true));
|
|
}
|
|
v
|
|
}
|
|
|
|
fn curtains(t: f64, a: f64) -> Vec<Layer> {
|
|
// The old slide parts in the middle into two curtains drawn to the sides, then lifted away.
|
|
let open = q2(win(t, 0.0, 0.35));
|
|
let lift = ein(win(t, 0.62, 0.9));
|
|
let mut v = vec![Layer::full(New, 1.0)];
|
|
let w_top = 0.5 - 0.22 * open;
|
|
let w_bot = 0.5 - 0.12 * open;
|
|
let up = 0.15 * lift;
|
|
let sw = 0.75 * lift;
|
|
let left = [[-sw, -up], [w_top - sw, -up], [w_bot - 0.6 * sw, 1.0 - up], [-0.6 * sw, 1.0 - up]];
|
|
let right = [[1.0 - w_top + sw, -up], [1.0 + sw, -up], [1.0 + 0.6 * sw, 1.0 - up], [1.0 - w_bot + 0.6 * sw, 1.0 - up]];
|
|
v.push(Layer::quad(Old, [0.0, 0.0, 0.5, 1.0], left, 1.0));
|
|
v.push(Layer::quad(Old, [0.5, 0.0, 1.0, 1.0], right, 1.0));
|
|
let _ = a;
|
|
v
|
|
}
|
|
|
|
fn wind(right: bool, t: f64, a: f64) -> Vec<Layer> {
|
|
// The old slide blows away like a sheet, lifting and receding toward one side.
|
|
let cam = Cam { a, d: 2.2 };
|
|
let u = ein(win(t, 0.12, 0.8));
|
|
let side = if right { 1.0 } else { -1.0 };
|
|
let pivot = v3(0.0, 0.0, 0.0);
|
|
let pts = cam.rect(FULL).map(|p| {
|
|
let w = p.pitch(v3(0.0, -0.5, 0.0), -25.0 * u).yaw(pivot, -side * 20.0 * u);
|
|
v3(w.x + side * 1.6 * u * a / 2.0, w.y - 0.8 * u, w.z + 0.5 * u)
|
|
});
|
|
let mut v = vec![Layer::full(New, 1.0)];
|
|
if u < 1.0 {
|
|
v.extend(card(&cam, Old, FULL, pts, 1.0, 0.0, true));
|
|
}
|
|
v
|
|
}
|
|
|
|
fn prestige(t: f64, a: f64) -> Vec<Layer> {
|
|
// The old slide lifts at its bottom edge and flies off upward.
|
|
let cam = Cam { a, d: 2.2 };
|
|
let lift = q2(win(t, 0.4, 0.62));
|
|
let fly = ein(win(t, 0.5, 0.85));
|
|
let pivot = v3(0.0, -0.5, 0.0);
|
|
let pts = cam.rect(FULL).map(|p| {
|
|
let w = p.pitch(pivot, -40.0 * lift).roll(pivot, -12.0 * fly);
|
|
v3(w.x - 0.4 * fly * a / 2.0, w.y - 1.3 * fly, w.z - 0.2 * fly)
|
|
});
|
|
let mut v = vec![Layer::full(New, 1.0)];
|
|
if fly < 1.0 {
|
|
v.extend(card(&cam, Old, FULL, pts, 1.0, facing_shade(pts, 0.3), true));
|
|
}
|
|
v
|
|
}
|
|
|
|
fn fracture(t: f64, a: f64) -> Vec<Layer> {
|
|
// The old slide cracks into long shards radiating from its centre; the shards part a little,
|
|
// then fall away, the top ones first.
|
|
let mut v = vec![Layer::full(New, 1.0)];
|
|
let crack = ss(win(t, 0.06, 0.2));
|
|
if crack <= 0.0 {
|
|
v.push(Layer::full(Old, 1.0));
|
|
return v;
|
|
}
|
|
let n = 36usize;
|
|
let rings = [0.0, 0.3, 0.6, 1.0];
|
|
let c = [0.5, 0.5];
|
|
for i in 0..n {
|
|
let a0 = 2.0 * PI * (i as f64 + 0.35 * rnd(i as u64)) / n as f64;
|
|
let a1 = 2.0 * PI * (i as f64 + 1.0 + 0.35 * rnd((i + 1) as u64 % n as u64)) / n as f64;
|
|
for r in 0..3 {
|
|
let (r0, r1) = (rings[r], if r == 2 { 1.6 } else { rings[r + 1] });
|
|
let p = |ang: f64, rr: f64| -> [f64; 2] {
|
|
let b = boundary(ang, a);
|
|
[c[0] + (b[0] - c[0]) * rr, c[1] + (b[1] - c[1]) * rr]
|
|
};
|
|
let pts = [p(a0, r0), p(a1, r0), p(a1, r1), p(a0, r1)];
|
|
let id = (i * 3 + r) as u64;
|
|
let mid = [(pts[0][0] + pts[1][0] + pts[2][0] + pts[3][0]) / 4.0, (pts[0][1] + pts[1][1] + pts[2][1] + pts[3][1]) / 4.0];
|
|
let start = 0.3 + 0.28 * mid[1].clamp(0.0, 1.0) + 0.1 * rnd(id + 300);
|
|
let k = win(t, start, start + 0.35);
|
|
let gap = 0.015 * crack;
|
|
let out = [(mid[0] - c[0]) * gap * 4.0, (mid[1] - c[1]) * gap * 4.0 + 1.4 * k * k];
|
|
let ang = (rnd(id + 7) - 0.5) * 50.0 * k;
|
|
let q = pts.map(|p| {
|
|
let p = [mid[0] + (p[0] - mid[0]) * (1.0 - 0.06 * crack) + out[0], mid[1] + (p[1] - mid[1]) * (1.0 - 0.06 * crack) + out[1]];
|
|
rot(p, [mid[0] + out[0], mid[1] + out[1]], ang, a)
|
|
});
|
|
let uv = pts.map(|p| [p[0].clamp(0.0, 1.0), p[1].clamp(0.0, 1.0)]);
|
|
if k < 1.0 {
|
|
let l = Layer { source: Old, src: FULL, quad: q, uv, alpha: 1.0, fade: [1.0; 4] };
|
|
v.push(l);
|
|
v.push(l.shade(0.1 * crack * rnd(id + 9) + 0.2 * k));
|
|
}
|
|
}
|
|
}
|
|
v
|
|
}
|
|
|
|
fn crush(t: f64, a: f64) -> Vec<Layer> {
|
|
// The old slide crumples into a ball that drops away.
|
|
let mut v = vec![Layer::full(New, 1.0)];
|
|
let k = q2(win(t, 0.08, 0.55));
|
|
let drop = ein(win(t, 0.65, 1.0));
|
|
let size = 1.0 - 0.45 * k;
|
|
let cx = 0.5 - 0.1 * k + 0.05 * drop;
|
|
let cy = 0.5 + 0.05 * k + 0.8 * drop;
|
|
let n = 40usize;
|
|
let c = [0.5, 0.5];
|
|
for i in 0..n {
|
|
let a0 = 2.0 * PI * i as f64 / n as f64;
|
|
let a1 = 2.0 * PI * (i + 1) as f64 / n as f64;
|
|
let j = |ang: f64, idx: usize| -> [f64; 2] {
|
|
let b = boundary(ang, a);
|
|
// Corners pull in toward a round ball, with crumpled jags.
|
|
let round = {
|
|
let (dx, dy) = ((b[0] - 0.5) * a, b[1] - 0.5);
|
|
let len = (dx * dx + dy * dy).sqrt().max(1e-9);
|
|
let r = 0.45;
|
|
[0.5 + dx / len * r / a, 0.5 + dy / len * r]
|
|
};
|
|
let jag = 1.0 - 0.18 * k * rnd(idx as u64 % n as u64 + 77);
|
|
let p = [b[0] + (round[0] - b[0]) * k, b[1] + (round[1] - b[1]) * k];
|
|
[cx + (p[0] - c[0]) * size * jag, cy + (p[1] - c[1]) * size * jag]
|
|
};
|
|
let (p0, p1) = (j(a0, i), j(a1, i + 1));
|
|
let (u0, u1) = (boundary(a0, a), boundary(a1, a));
|
|
let l = Layer::tri_uv(Old, [[cx, cy], p0, p1], [c, u0, u1], [1.0; 3], 1.0);
|
|
v.push(l);
|
|
v.push(l.shade(0.35 * k * rnd(i as u64 + 13)));
|
|
}
|
|
v
|
|
}
|
|
|
|
fn peel_off(right: bool, t: f64) -> Vec<Layer> {
|
|
// The old slide peels from a bottom corner toward the opposite top corner; the peeled flap
|
|
// shows the page's back folded over.
|
|
let u = ss(win(t, 0.2, 0.95));
|
|
// Fold line perpendicular to the diagonal, sweeping from the corner.
|
|
let (cx, cy) = if right { (0.0, 1.0) } else { (1.0, 1.0) };
|
|
let nd: [f64; 2] = if right { [1.0, -1.0] } else { [-1.0, -1.0] };
|
|
let len = (nd[0] * nd[0] + nd[1] * nd[1]).sqrt();
|
|
let n = [nd[0] / len, nd[1] / len];
|
|
let span = 2f64.sqrt();
|
|
let d0 = n[0] * cx + n[1] * cy;
|
|
let fold = d0 + span * 1.25 * u;
|
|
let mut v = vec![Layer::full(New, 1.0)];
|
|
let full = rect_poly(FULL);
|
|
// Kept part: beyond the fold line from the corner.
|
|
let keep = clip(&full, [-n[0], -n[1]], -fold);
|
|
v.extend(poly_layers(Old, &keep, 1.0, &|p| p));
|
|
// Peeled part, reflected over the fold line.
|
|
let peeled = clip(&full, n, fold);
|
|
let refl = |p: [f64; 2]| {
|
|
let d = n[0] * p[0] + n[1] * p[1] - fold;
|
|
[p[0] - 2.0 * d * n[0], p[1] - 2.0 * d * n[1]]
|
|
};
|
|
let flap = poly_layers(Old, &peeled, 1.0, &refl);
|
|
for l in flap {
|
|
v.push(l);
|
|
v.push(l.shade(0.25));
|
|
}
|
|
v
|
|
}
|
|
|
|
fn page_curl(o: &str, t: f64) -> Vec<Layer> {
|
|
let single = o.starts_with("single");
|
|
let right = o.ends_with('r') || o.ends_with('R');
|
|
let mut v = vec![Layer::full(New, 1.0)];
|
|
// A roll travelling across the old slide: x of the roll's front edge.
|
|
let roll = |area: R, k: f64, toward_left: bool, v: &mut Vec<Layer>| {
|
|
let w = area[2] - area[0];
|
|
let r = 0.06;
|
|
if toward_left {
|
|
let e = area[2] - (w + r) * k;
|
|
let keep = [area[0], area[1], e.max(area[0]), area[3]];
|
|
if keep[2] > keep[0] {
|
|
v.push(Layer::inplace(Old, keep, 1.0));
|
|
}
|
|
// The roll: a band squeezing the rolled-up part.
|
|
let band = [e.max(area[0]), area[1], (e + r).min(area[2]).max(area[0]), area[3]];
|
|
if band[2] > band[0] && k < 1.0 {
|
|
let src = [band[0], area[1], area[2], area[3]];
|
|
let l = Layer::rect(Old, src, band, 1.0);
|
|
v.push(l);
|
|
v.push(l.shade(0.3).faded([0.2, 1.0, 1.0, 0.2]));
|
|
}
|
|
} else {
|
|
let e = area[0] + (w + r) * k;
|
|
let keep = [e.min(area[2]), area[1], area[2], area[3]];
|
|
if keep[2] > keep[0] {
|
|
v.push(Layer::inplace(Old, keep, 1.0));
|
|
}
|
|
let band = [(e - r).max(area[0]).min(area[2]), area[1], e.min(area[2]), area[3]];
|
|
if band[2] > band[0] && k < 1.0 {
|
|
let src = [area[0], area[1], band[2], area[3]];
|
|
let l = Layer::rect(Old, src, band, 1.0);
|
|
v.push(l);
|
|
v.push(l.shade(0.3).faded([1.0, 0.2, 0.2, 1.0]));
|
|
}
|
|
}
|
|
};
|
|
if single {
|
|
let k = q2(win(t, 0.25, 0.85));
|
|
roll(FULL, k, !right, &mut v);
|
|
} else {
|
|
// Double: the half on the far side rolls first, then the near half.
|
|
let k1 = q2(win(t, 0.1, 0.6));
|
|
let k2 = q2(win(t, 0.45, 0.85));
|
|
if right {
|
|
roll([0.0, 0.0, 0.5, 1.0], k1, false, &mut v);
|
|
roll([0.5, 0.0, 1.0, 1.0], k2, false, &mut v);
|
|
} else {
|
|
roll([0.5, 0.0, 1.0, 1.0], k1, true, &mut v);
|
|
roll([0.0, 0.0, 0.5, 1.0], k2, true, &mut v);
|
|
}
|
|
}
|
|
v
|
|
}
|
|
|
|
fn airplane(right: bool, t: f64, a: f64) -> Vec<Layer> {
|
|
// The old slide folds into a big paper dart (nose to the right for "l") that hangs low, then
|
|
// flies off up and away.
|
|
let fold = q2(win(t, 0.0, 0.3));
|
|
let fly = ein(win(t, 0.82, 0.95));
|
|
let mut v = vec![Layer::full(New, 1.0)];
|
|
if fly >= 1.0 {
|
|
return v;
|
|
}
|
|
let mx = |p: [f64; 2]| if right { [1.0 - p[0], p[1]] } else { p };
|
|
// The dart in its hanging pose ("l"), then flown off.
|
|
let drift = 0.04 * win(t, 0.3, 0.82);
|
|
let pose = |p: [f64; 2]| -> [f64; 2] {
|
|
let p = [p[0] + drift, p[1] + drift];
|
|
let c = [0.75, 0.65];
|
|
let k = 1.0 - 0.85 * fly;
|
|
[c[0] + (p[0] - c[0]) * k - 0.4 * fly, c[1] + (p[1] - c[1]) * k - 1.0 * fly]
|
|
};
|
|
let dart = [[0.42, 0.28], [1.02, 0.66], [1.0, 0.72], [0.46, 1.08]];
|
|
let flat = [[0.0, 0.0], [1.0, 0.0], [1.0, 1.0], [0.0, 1.0]];
|
|
let pts: Vec<[f64; 2]> = (0..4)
|
|
.map(|i| {
|
|
let (f, d) = (flat[i], dart[i]);
|
|
mx(pose([f[0] + (d[0] - f[0]) * fold, f[1] + (d[1] - f[1]) * fold]))
|
|
})
|
|
.collect();
|
|
let uv: Vec<[f64; 2]> = flat.iter().map(|p| mx(*p)).collect();
|
|
if let (Some(p0), Some(p1), Some(p2), Some(p3)) = (pts.first(), pts.get(1), pts.get(2), pts.get(3)) {
|
|
let l1 = Layer::tri_uv(Old, [*p0, *p1, *p2], [uv[0], uv[1], uv[2]], [1.0; 3], 1.0);
|
|
let l2 = Layer::tri_uv(Old, [*p0, *p2, *p3], [uv[0], uv[2], uv[3]], [1.0; 3], 1.0);
|
|
v.push(l1);
|
|
v.push(l1.shade(0.1 * fold));
|
|
v.push(l2);
|
|
v.push(l2.shade(0.3 * fold));
|
|
}
|
|
let _ = a;
|
|
v
|
|
}
|
|
|
|
fn origami(right: bool, t: f64, a: f64) -> Vec<Layer> {
|
|
// The old slide folds into a bird that flies off.
|
|
let fold = q2(win(t, 0.0, 0.2));
|
|
let fly = ein(win(t, 0.2, 0.85));
|
|
let side = if right { 1.0 } else { -1.0 };
|
|
let mut v = vec![Layer::full(New, 1.0)];
|
|
if win(t, 0.2, 0.92) >= 1.0 {
|
|
return v;
|
|
}
|
|
let size = 1.0 - 0.35 * fold;
|
|
let cx = 0.47 + side * 0.65 * fly;
|
|
let cy = 0.42 + 0.05 * fly;
|
|
let flap = (t * 18.0).sin() * 0.08 * fold;
|
|
let body = |p: [f64; 2]| [cx + (p[0] - 0.5) * size, cy + (p[1] - 0.5) * size];
|
|
let shape = [
|
|
([0.0, 0.0], [-0.45 * side, -0.25 - flap]),
|
|
([1.0, 0.0], [0.15 * side, -0.4 + flap]),
|
|
([1.0, 1.0], [0.45 * side, 0.05]),
|
|
([0.0, 1.0], [-0.1 * side, 0.2]),
|
|
];
|
|
let pts: Vec<[f64; 2]> =
|
|
shape.iter().map(|(flat, bird)| body([flat[0] + (0.5 + bird[0] - flat[0]) * fold, flat[1] + (0.5 + bird[1] - flat[1]) * fold])).collect();
|
|
let uv: Vec<[f64; 2]> = shape.iter().map(|(flat, _)| *flat).collect();
|
|
if let (Some(p0), Some(p1), Some(p2), Some(p3)) = (pts.first(), pts.get(1), pts.get(2), pts.get(3)) {
|
|
let l1 = Layer::tri_uv(Old, [*p0, *p1, *p2], [uv[0], uv[1], uv[2]], [1.0; 3], 1.0);
|
|
let l2 = Layer::tri_uv(Old, [*p0, *p2, *p3], [uv[0], uv[2], uv[3]], [1.0; 3], 1.0);
|
|
v.push(l1);
|
|
v.push(l1.shade(0.15 * fold));
|
|
v.push(l2);
|
|
v.push(l2.shade(0.3 * fold));
|
|
}
|
|
let _ = a;
|
|
v
|
|
}
|
|
|
|
fn honeycomb(t: f64, a: f64) -> Vec<Layer> {
|
|
// The old slide splits into hexagonal cells along dark seams; the cells fly at the viewer and
|
|
// out of sight, leaving black; cells of the new slide pop in one by one and close the seams.
|
|
let cols = 11usize;
|
|
let w = 1.0 / cols as f64;
|
|
let rh = w * a * 0.5;
|
|
let rows = (1.0 / (rh * 1.5)).ceil() as usize + 2;
|
|
let mut v = vec![Layer::full(Black, 1.0)];
|
|
let seam = ss(win(t, 0.0, 0.12)) * (1.0 - ss(win(t, 0.85, 1.0)));
|
|
let hex = |cx: f64, cy: f64, s: f64| -> Vec<[f64; 2]> {
|
|
(0..6)
|
|
.map(|k| {
|
|
let ang = PI / 3.0 * k as f64 + PI / 6.0;
|
|
[cx + ang.cos() * s * w * 0.577, cy + ang.sin() * s * rh * 0.577 / 0.5 * 0.5]
|
|
})
|
|
.collect()
|
|
};
|
|
for r in 0..rows {
|
|
for c in 0..=cols {
|
|
let cx = (c as f64 + if r % 2 == 1 { 0.5 } else { 0.0 }) * w;
|
|
let cy = r as f64 * rh * 0.866;
|
|
let id = (r * 64 + c) as u64;
|
|
let src_hex = hex(cx, cy, 1.0);
|
|
let out_start = 0.1 + 0.2 * rnd(id + 1);
|
|
let out_k = ein(win(t, out_start, out_start + 0.12));
|
|
let in_start = 0.38 + 0.42 * rnd(id + 3);
|
|
let in_k = eout(win(t, in_start, in_start + 0.1));
|
|
let gap = 1.0 - 0.1 * seam;
|
|
let mut cell = |src: Source, s: f64, alpha: f64| {
|
|
let dst = hex(cx, cy, s);
|
|
for k in 0..6 {
|
|
let (p0, p1) = (dst[k], dst[(k + 1) % 6]);
|
|
let (u0, u1) = (src_hex[k], src_hex[(k + 1) % 6]);
|
|
v.push(Layer::tri_uv(src, [[cx, cy], p0, p1], [[cx, cy], u0, u1], [1.0; 3], alpha));
|
|
}
|
|
};
|
|
if t < 0.5 && out_k < 1.0 {
|
|
cell(Old, gap * (1.0 + 3.0 * out_k), 1.0 - out_k * out_k);
|
|
}
|
|
if in_k > 0.0 {
|
|
cell(New, gap * (0.3 + 0.7 * in_k), 1.0);
|
|
}
|
|
}
|
|
}
|
|
v
|
|
}
|
|
|
|
fn glitter(o: &str, t: f64, a: f64) -> Vec<Layer> {
|
|
// Tiles flip from the old slide to the new in a sparkling sweep starting on the option's side.
|
|
let hexes = o.starts_with("hex");
|
|
let d = o.trim_start_matches("hex").to_ascii_lowercase();
|
|
let (n, m) = if hexes { (28usize, 16usize) } else { (40usize, 22usize) };
|
|
let cam = Cam { a, d: 3.0 };
|
|
let mut v = vec![Layer::full(Old, 1.0)];
|
|
for r in 0..m {
|
|
for c in 0..n {
|
|
let cell = [c as f64 / n as f64, r as f64 / m as f64, (c + 1) as f64 / n as f64, (r + 1) as f64 / m as f64];
|
|
let cc = center(cell);
|
|
// Distance from the side the sweep starts on ("r": from the right).
|
|
let grad = match d.as_str() {
|
|
"r" => 1.0 - cc[0],
|
|
"u" => cc[1],
|
|
"d" => 1.0 - cc[1],
|
|
_ => cc[0],
|
|
};
|
|
let start = 0.75 * grad * 0.85 + rnd((r * n + c) as u64 + 77) * 0.12;
|
|
let k = win(t, start, start + 0.25);
|
|
if k <= 0.0 {
|
|
continue;
|
|
}
|
|
v.push(Layer::inplace(Black, cell, 1.0));
|
|
flip_tile(&cam, cell, k, true, &mut v);
|
|
}
|
|
}
|
|
v
|
|
}
|
|
|
|
fn vortex(o: &str, t: f64, a: f64) -> Vec<Layer> {
|
|
// The old slide breaks into fine particles that whirl off toward the option's side, the
|
|
// trailing side first; the new slide's particles whirl in and settle, the leading side first.
|
|
let (dx, dy) = dir(o, (-1.0, 0.0));
|
|
let (n, m) = (48usize, 27usize);
|
|
let mut v = vec![Layer::full(Black, 1.0)];
|
|
for r in 0..m {
|
|
for c in 0..n {
|
|
let id = (r * n + c) as u64;
|
|
let cell = [c as f64 / n as f64, r as f64 / m as f64, (c + 1) as f64 / n as f64, (r + 1) as f64 / m as f64];
|
|
let cc = center(cell);
|
|
// 1 on the side things move toward, 0 on the other.
|
|
let lead = (0.5 + (cc[0] - 0.5) * dx + (cc[1] - 0.5) * dy).clamp(0.0, 1.0);
|
|
let go = 0.4 * lead + 0.08 * rnd(id + 31);
|
|
let ko = win(t, go, go + 0.35);
|
|
let end = 0.62 + 0.3 * (1.0 - lead) + 0.06 * rnd(id + 41);
|
|
let kn = 1.0 - win(t, end - 0.35, end);
|
|
let mut piece = |src: Source, k: f64| {
|
|
let swirl = 2.0 * PI * (rnd(id + 5) - 0.5) * k;
|
|
let off = [dx * k * 0.8 + swirl.sin() * 0.15 * k, dy * k * 0.8 - swirl.cos().abs() * 0.12 * k];
|
|
let s = 0.8 - 0.5 * k;
|
|
let r2 = offset(scale_rect(cell, s), off[0], off[1]);
|
|
let q = rot_quad(crate::transition::corners(r2), center(r2), rnd(id) * 360.0 * k, a);
|
|
v.push(Layer::quad(src, cell, q, 1.0 - k * k));
|
|
};
|
|
if kn < 1.0 {
|
|
piece(New, kn);
|
|
}
|
|
if ko > 0.0 && ko < 1.0 && kn >= 1.0 {
|
|
piece(Old, ko);
|
|
} else if ko <= 0.0 {
|
|
v.push(Layer::inplace(Old, cell, 1.0));
|
|
}
|
|
}
|
|
}
|
|
v
|
|
}
|
|
|
|
fn shred(o: &str, t: f64, a: f64) -> Vec<Layer> {
|
|
let particles = o.starts_with("particles") || o.starts_with("rect");
|
|
let out = o.ends_with("Out");
|
|
let cam = Cam { a, d: 2.5 };
|
|
let mut v = vec![Layer::full(Black, 1.0)];
|
|
// First half: the old slide comes apart and recedes; second half: the new one assembles.
|
|
let (src, k) = if t < 0.55 { (Old, q2(win(t, 0.05, 0.55))) } else { (New, 1.0 - q2(win(t, 0.55, 0.95))) };
|
|
if particles {
|
|
let (n, m) = (64usize, 36usize);
|
|
for r in 0..m {
|
|
for c in 0..n {
|
|
let id = (r * n + c) as u64;
|
|
let cell = [c as f64 / n as f64, r as f64 / m as f64, (c + 1) as f64 / n as f64, (r + 1) as f64 / m as f64];
|
|
let cc = center(cell);
|
|
let z = if out { -0.6 } else { 1.5 } * k * (0.5 + rnd(id));
|
|
let jitter = [(rnd(id + 1) - 0.5) * 0.4 * k, (rnd(id + 2) - 0.5) * 0.4 * k];
|
|
let p3 = cam.unit(cc[0] + jitter[0], cc[1] + jitter[1]);
|
|
let pc = cam.p(v3(p3.x, p3.y, z));
|
|
let s = 0.8 * (1.0 - 0.3 * k) * cam.d / (cam.d + z).max(0.1);
|
|
let r2 = offset(scale_rect(cell, s), pc[0] - cc[0], pc[1] - cc[1]);
|
|
let l = Layer::rect(src, cell, r2, 1.0 - 0.3 * k * rnd(id + 5));
|
|
v.push(l);
|
|
v.push(l.shade(0.4 * k));
|
|
}
|
|
}
|
|
} else {
|
|
let n = 30usize;
|
|
for i in 0..n {
|
|
let id = i as u64;
|
|
let (x0, x1) = (i as f64 / n as f64, (i + 1) as f64 / n as f64);
|
|
let cell = [x0, 0.0, x1, 1.0];
|
|
let z = if out { -0.3 } else { 1.0 } * k * k * (0.6 + rnd(id + 11));
|
|
let tilt = (rnd(id + 12) - 0.5) * 30.0 * k;
|
|
let drop = (rnd(id + 13) - 0.3) * 0.25 * k;
|
|
let inset = 0.004 * k;
|
|
let w3 = cam.rect([x0 + inset, 0.0, x1 - inset, 1.0]);
|
|
let c3 = cam.unit((x0 + x1) / 2.0, 0.5);
|
|
let pts = w3.map(|p| {
|
|
let q = p.roll(c3, tilt * 0.2).yaw(c3, tilt);
|
|
v3(q.x, q.y + drop, q.z + z)
|
|
});
|
|
v.extend(card(&cam, src, cell, pts, 1.0, 0.3 * k, true));
|
|
}
|
|
}
|
|
v
|
|
}
|
|
|
|
fn switch(right: bool, t: f64, a: f64) -> Vec<Layer> {
|
|
// Switching cards: the old slide is pulled out to one side ("l": left) and slipped behind
|
|
// while the new one, pulled out to the other side from behind it, comes to the front. Both
|
|
// turn a little away at their outer edges.
|
|
let cam = Cam { a, d: 2.5 };
|
|
let side = if right { -1.0 } else { 1.0 };
|
|
let u = (t + ss(t)) / 2.0;
|
|
let k = (u * PI).sin();
|
|
let shift = 0.5 * k * a;
|
|
let old = cam.rect(FULL).map(|p| {
|
|
let q = p.yaw(v3(0.0, 0.0, 0.0), side * 14.0 * k);
|
|
v3(q.x - side * shift, q.y, q.z + 0.12 * k)
|
|
});
|
|
let new = cam.rect(FULL).map(|p| {
|
|
let q = p.yaw(v3(0.0, 0.0, 0.0), -side * 14.0 * k);
|
|
v3(q.x + side * shift, q.y, q.z + 0.12 * k)
|
|
});
|
|
let mut v = vec![Layer::full(Black, 1.0)];
|
|
let o = card(&cam, Old, FULL, old, 1.0, facing_shade(old, 0.3), true);
|
|
let n = card(&cam, New, FULL, new, 1.0, facing_shade(new, 0.3), true);
|
|
if u < 0.5 {
|
|
v.extend(n);
|
|
v.extend(o);
|
|
} else {
|
|
v.extend(o);
|
|
v.extend(n);
|
|
}
|
|
v
|
|
}
|
|
|
|
fn flip(right: bool, t: f64, a: f64) -> Vec<Layer> {
|
|
// The slide turns over like a card: the old face, then the new one on its back.
|
|
let cam = Cam { a, d: 2.5 };
|
|
let s = q2(t);
|
|
let side = if right { 1.0 } else { -1.0 };
|
|
let deg = 180.0 * s * side;
|
|
let back = 0.6 * (s * PI).sin();
|
|
let c = v3(0.0, 0.0, 0.0);
|
|
let pts = cam.rect(FULL).map(|p| {
|
|
let q = p.yaw(c, deg);
|
|
v3(q.x, q.y, q.z + back)
|
|
});
|
|
let mut v = vec![Layer::full(Black, 1.0)];
|
|
if s < 0.5 {
|
|
v.extend(card(&cam, Old, FULL, pts, 1.0, facing_shade(pts, 0.5), true));
|
|
} else {
|
|
// The back face: the new slide, mirrored so it reads right way round.
|
|
let m = [pts[1], pts[0], pts[3], pts[2]];
|
|
v.extend(card(&cam, New, FULL, m, 1.0, facing_shade(m, 0.5), true));
|
|
}
|
|
v
|
|
}
|
|
|
|
fn gallery(right: bool, t: f64, a: f64) -> Vec<Layer> {
|
|
// The slides hang on a wall: it turns away and recedes, the slides slide along it, then it
|
|
// turns back. A faint reflection shows below.
|
|
let cam = Cam { a, d: 2.5 };
|
|
let side = if right { 1.0 } else { -1.0 };
|
|
let k = ss(win(t, 0.0, 0.35)) * (1.0 - ss(win(t, 0.7, 1.0)));
|
|
let s = q2(win(t, 0.3, 0.8));
|
|
let gap = 0.15;
|
|
let back = 0.7 * k;
|
|
let yaw = -side * 22.0 * k;
|
|
let mut v = vec![Layer::full(Black, 1.0)];
|
|
for (src, shift) in [(Old, side * s), (New, side * (s - 1.0))] {
|
|
let x0 = shift * (a + gap);
|
|
let pts = cam.rect(FULL).map(|p| v3(p.x + x0, p.y, p.z + back)).map(|p| p.yaw(v3(0.0, 0.0, back), yaw));
|
|
// Reflection: the slide flipped below its bottom edge, faint.
|
|
let refl = [pts[3], pts[2], v3(pts[2].x, pts[2].y + 0.25, pts[2].z), v3(pts[3].x, pts[3].y + 0.25, pts[3].z)];
|
|
let rq = cam.quad(refl);
|
|
v.push(Layer {
|
|
source: src,
|
|
src: [0.0, 0.75, 1.0, 1.0],
|
|
quad: rq,
|
|
uv: [[0.0, 1.0], [1.0, 1.0], [1.0, 0.75], [0.0, 0.75]],
|
|
alpha: 0.3 * k,
|
|
fade: [1.0, 1.0, 0.0, 0.0],
|
|
});
|
|
v.extend(card(&cam, src, FULL, pts, 1.0, 0.0, true));
|
|
}
|
|
v
|
|
}
|
|
|
|
fn conveyor(right: bool, t: f64, a: f64) -> Vec<Layer> {
|
|
// The contents ride a belt: it turns away, the contents slide along, it turns back; the
|
|
// backgrounds cross-fade underneath.
|
|
let cam = Cam { a, d: 2.5 };
|
|
let side = if right { 1.0 } else { -1.0 };
|
|
let k = ss(win(t, 0.0, 0.35)) * (1.0 - ss(win(t, 0.65, 1.0)));
|
|
let s = q2(win(t, 0.33, 0.75));
|
|
let back = 0.45 * k;
|
|
let yaw = -side * 18.0 * k;
|
|
let mut v = vec![Layer::full(OldBack, 1.0), Layer::full(NewBack, ss(t))];
|
|
for (src, shift) in [(OldContent, side * s), (NewContent, side * (s - 1.0))] {
|
|
let x0 = shift * a * 1.05;
|
|
let pts = cam.rect(FULL).map(|p| v3(p.x + x0, p.y, p.z + back)).map(|p| p.yaw(v3(0.0, 0.0, back), yaw));
|
|
v.extend(card(&cam, src, FULL, pts, 1.0, 0.0, true));
|
|
}
|
|
v
|
|
}
|
|
|
|
fn prism(kind: &str, o: &str, t: f64, a: f64) -> Vec<Layer> {
|
|
// Cube: the slides are two faces of a cube turning; Box: two inside walls of a box; Rotate and
|
|
// Orbit: the same with the contents only, over cross-fading backgrounds.
|
|
let (dx, dy) = dir(o, (-1.0, 0.0));
|
|
let vertical = dy != 0.0 && dx == 0.0;
|
|
let content = matches!(kind, "rotate" | "orbit");
|
|
let inverted = matches!(kind, "box" | "orbit");
|
|
let cam = Cam { a, d: 2.5 };
|
|
let s = q2(t);
|
|
let half = if vertical { 0.5 } else { a / 2.0 };
|
|
// Faces on a cube around (0, 0, half) (outside), or the inside of a box around (0, 0, -half).
|
|
let c = v3(0.0, 0.0, if inverted { -half } else { half });
|
|
// Positive turns the right (or top) side away.
|
|
let sign = if vertical { -dy } else { dx };
|
|
let deg = 90.0 * s * sign * if inverted { -1.0 } else { 1.0 };
|
|
let turn = |p: V3, base: f64| -> V3 { if vertical { p.pitch(c, base + deg) } else { p.yaw(c, base + deg) } };
|
|
let side_base = -90.0 * sign * if inverted { -1.0 } else { 1.0 };
|
|
let (os, ns) = if content { (OldContent, NewContent) } else { (Old, New) };
|
|
let old = cam.rect(FULL).map(|p| turn(p, 0.0));
|
|
let new = cam.rect(FULL).map(|p| turn(p, side_base));
|
|
// Keep the nearest edge on the screen plane, as PowerPoint's camera does.
|
|
let near = old.iter().chain(new.iter()).map(|p| p.z).fold(f64::INFINITY, f64::min);
|
|
let lift = if inverted { 0.0 } else { -near.min(0.0) - near.max(0.0) };
|
|
// The box is seen from further back mid-turn.
|
|
let back = if inverted { 0.5 } else { 0.15 } * (s * PI).sin();
|
|
let push = |q: [V3; 4]| q.map(|p| v3(p.x, p.y, p.z + lift + back));
|
|
let (old, new) = (push(old), push(new));
|
|
let mut v = if content { vec![Layer::full(OldBack, 1.0), Layer::full(NewBack, ss(t))] } else { vec![Layer::full(Black, 1.0)] };
|
|
v.extend(card(&cam, os, FULL, old, 1.0, if content { 0.0 } else { facing_shade(old, 0.6) }, false));
|
|
v.extend(card(&cam, ns, FULL, new, 1.0, if content { 0.0 } else { facing_shade(new, 0.6) }, false));
|
|
v
|
|
}
|
|
|
|
fn doors(window: bool, horz: bool, t: f64, a: f64) -> Vec<Layer> {
|
|
// The old slide parts in two halves that swing open toward the viewer and darken; the new one
|
|
// comes forward from the middle, fading in over them. Window does it with the contents only,
|
|
// over cross-fading backgrounds.
|
|
let cam = Cam { a, d: 2.2 };
|
|
let open = ein(win(t, 0.05, 0.68));
|
|
let come = q2(win(t, 0.0, 0.72));
|
|
let (os, ns) = if window { (OldContent, NewContent) } else { (Old, New) };
|
|
let mut v = if window { vec![Layer::full(OldBack, 1.0), Layer::full(NewBack, ss(t))] } else { vec![Layer::full(Black, 1.0)] };
|
|
let halves: [(R, f64); 2] =
|
|
if horz { [([0.0, 0.0, 1.0, 0.5], -1.0), ([0.0, 0.5, 1.0, 1.0], 1.0)] } else { [([0.0, 0.0, 0.5, 1.0], -1.0), ([0.5, 0.0, 1.0, 1.0], 1.0)] };
|
|
if open < 1.0 {
|
|
for (r, sgn) in halves {
|
|
let hinge = if horz { cam.unit(0.5, if sgn < 0.0 { 0.0 } else { 1.0 }) } else { cam.unit(if sgn < 0.0 { 0.0 } else { 1.0 }, 0.5) };
|
|
let ang = 30.0 * open;
|
|
let slide = 0.55 * open;
|
|
let pts = cam.rect(r).map(|p| {
|
|
let q = if horz { p.pitch(hinge, sgn * ang) } else { p.yaw(hinge, -sgn * ang) };
|
|
if horz { v3(q.x, q.y + sgn * slide, q.z) } else { v3(q.x + sgn * slide * a, q.y, q.z) }
|
|
});
|
|
let dark = if window { 0.0 } else { 0.7 * open };
|
|
let alpha = if window { 1.0 - ss(win(t, 0.2, 0.6)) } else { 1.0 };
|
|
v.extend(card(&cam, os, r, pts, alpha, dark, true));
|
|
}
|
|
}
|
|
let sc = 0.5 + 0.5 * come;
|
|
let fade_in = if window { ss(t) } else { ss(win(t, 0.05, 0.62)) };
|
|
v.push(scaled(ns, sc, 0.5, 0.5, 0.0, 0.0, fade_in));
|
|
v
|
|
}
|
|
|
|
fn ferris(right: bool, t: f64, a: f64) -> Vec<Layer> {
|
|
// The contents ride a big wheel turning about a hub far off the lower corner: the old ones
|
|
// drop away down and sideways, the new ones come down round from the top, over cross-fading
|
|
// backgrounds.
|
|
let side = if right { -1.0 } else { 1.0 };
|
|
let mut v = vec![Layer::full(OldBack, 1.0), Layer::full(NewBack, ss(t))];
|
|
let out = ein(win(t, 0.0, 0.5));
|
|
if out < 1.0 {
|
|
let (dx, dy) = (-side * 0.45 * out.powf(1.5), 0.9 * out);
|
|
let q = rot_quad(crate::transition::corners(offset(FULL, dx, dy)), [0.5 + dx, 0.5 + dy], side * -15.0 * out, a);
|
|
v.push(Layer::quad(OldContent, FULL, q, 1.0));
|
|
}
|
|
let inn = q2(win(t, 0.5, 1.0));
|
|
if inn > 0.0 {
|
|
let k = 1.0 - inn;
|
|
let (dx, dy) = (-side * 0.5 * k.powf(1.3), -0.75 * k);
|
|
let r = offset(scale_rect(FULL, 1.0 - 0.25 * k), dx, dy);
|
|
let q = rot_quad(crate::transition::corners(r), center(r), side * -18.0 * k, a);
|
|
v.push(Layer::quad(NewContent, FULL, q, 1.0));
|
|
}
|
|
v
|
|
}
|
|
|
|
fn pan(o: &str, t: f64) -> Vec<Layer> {
|
|
// The contents slide along together while the backgrounds cross-fade.
|
|
let (dx, dy) = dir(o, (-1.0, 0.0));
|
|
// Quick start, long glide.
|
|
let s = ease(t, 0.2, 0.8, 0.0);
|
|
vec![
|
|
Layer::full(OldBack, 1.0),
|
|
Layer::full(NewBack, s),
|
|
moved(OldContent, dx * s, dy * s, 1.0),
|
|
moved(NewContent, dx * (s - 1.0), dy * (s - 1.0), 1.0),
|
|
]
|
|
}
|
|
|
|
fn flythrough(o: &str, t: f64) -> Vec<Layer> {
|
|
// The contents fly past the viewer (in) or recede (out) while the backgrounds cross-fade.
|
|
let bounce = o.ends_with("Bounce");
|
|
let out = o.starts_with("out");
|
|
let s = q2(t);
|
|
let b = if bounce { 0.08 * (win(t, 0.6, 1.0) * PI).sin() } else { 0.0 };
|
|
let old_a = 1.0 - ss(win(t, 0.0, 0.6));
|
|
let new_a = ss(win(t, 0.3, 0.95));
|
|
let (os, ns) = if out { (1.0 - 0.5 * s, 1.8 - 0.8 * s - b) } else { (1.0 + 1.5 * s, 0.4 + 0.6 * s + b) };
|
|
vec![
|
|
Layer::full(OldBack, 1.0),
|
|
Layer::full(NewBack, ss(t)),
|
|
scaled(NewContent, ns, 0.5, 0.5, 0.0, 0.0, new_a),
|
|
scaled(OldContent, os, 0.5, 0.5, 0.0, 0.0, old_a),
|
|
]
|
|
}
|