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),
|
|
]
|
|
}
|