333 lines
9.9 KiB
Rust
333 lines
9.9 KiB
Rust
//! What Ely feels from the sessions' marks, and her pose at each moment of it, moving as the films move her.
|
|
|
|
mod curves;
|
|
#[cfg(test)]
|
|
mod tests;
|
|
|
|
use std::f32::consts::PI;
|
|
|
|
use self::curves::{
|
|
SHUT, between, blink, early, floating, fold, hops, opening, overshoot, popping, shaking,
|
|
smooth, tap, twinkling, wiggle,
|
|
};
|
|
use crate::sessions::orb::Look;
|
|
|
|
/// What the sessions ask of her, least pressing first.
|
|
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
|
|
pub enum Mood {
|
|
Calm,
|
|
Done,
|
|
Working,
|
|
Error,
|
|
Waiting,
|
|
}
|
|
|
|
/// The most pressing of the marks' looks; calm with none.
|
|
pub fn mood(looks: impl IntoIterator<Item = Look>) -> Mood {
|
|
looks
|
|
.into_iter()
|
|
.map(|look| match look {
|
|
Look::Waiting => Mood::Waiting,
|
|
Look::Error => Mood::Error,
|
|
Look::Working(_) => Mood::Working,
|
|
Look::Done => Mood::Done,
|
|
Look::Idle => Mood::Calm,
|
|
})
|
|
.max()
|
|
.unwrap_or(Mood::Calm)
|
|
}
|
|
|
|
/// Seconds she cheers a program that finished, and calm lasts before she dozes.
|
|
const CHEER: f64 = 4.0;
|
|
const DOZE: f64 = 300.0;
|
|
/// Seconds a press keeps her wink.
|
|
const PETTED: f32 = 1.5;
|
|
/// Seconds she takes to rise from behind the edge, from as deep as she is tall.
|
|
const RISE: f32 = 0.6;
|
|
const DEPTH: f32 = 280.0;
|
|
/// Seconds she takes to ease from one act into the next.
|
|
pub const EASE: f32 = 0.3;
|
|
|
|
/// What she is doing; a change eases her from her last pose.
|
|
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
|
pub enum Act {
|
|
Calm,
|
|
Doze,
|
|
Cheer,
|
|
Work,
|
|
Wait,
|
|
Fret,
|
|
Wink,
|
|
}
|
|
|
|
/// Her eyes: open, smiling arcs, sleeping lines, or a wink of the left.
|
|
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
|
pub enum Eyes {
|
|
Open,
|
|
Arcs,
|
|
Lines,
|
|
Wink,
|
|
}
|
|
|
|
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
|
pub enum Mouth {
|
|
Smile,
|
|
Grin,
|
|
Gasp,
|
|
Flat,
|
|
Frown,
|
|
}
|
|
|
|
/// A mark beside her head.
|
|
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
|
pub enum Mark {
|
|
Bang,
|
|
Z1,
|
|
Z2,
|
|
Heart,
|
|
SparkleLeft,
|
|
SparkleRight,
|
|
Drop,
|
|
}
|
|
|
|
/// How a mark moves from its place: by `at` units, grown by `scale`, turned `turn` degrees, at `alpha`.
|
|
#[derive(Clone, Copy, Debug)]
|
|
pub struct Motion {
|
|
pub at: (f32, f32),
|
|
pub scale: f32,
|
|
pub turn: f32,
|
|
pub alpha: f32,
|
|
}
|
|
|
|
/// Her pose at one moment, in the icon's units and degrees: lift and bob go up, marks and hands move as the icon's y does, down.
|
|
#[derive(Clone, Debug)]
|
|
pub struct Pose {
|
|
pub act: Act,
|
|
/// All of her up, and aside.
|
|
pub lift: f32,
|
|
pub shake: f32,
|
|
/// Her head up with her breath, and turned.
|
|
pub bob: f32,
|
|
pub tilt: f32,
|
|
pub sway: f32,
|
|
pub ahoge: f32,
|
|
pub look: (f32, f32),
|
|
/// Each eye's height, left then right, and both eyes' size.
|
|
pub open: [f32; 2],
|
|
pub wide: f32,
|
|
pub eyes: Eyes,
|
|
pub mouth: Mouth,
|
|
/// The mouth's size, which pops as it changes.
|
|
pub pop: f32,
|
|
/// How much her worried brows show.
|
|
pub brows: f32,
|
|
pub flush: f32,
|
|
/// Each hand pressed down, left then right.
|
|
pub hands: [f32; 2],
|
|
pub marks: Vec<(Mark, Motion)>,
|
|
}
|
|
|
|
/// Her pose `held` seconds into `mood`, `shown` seconds after she came, and `petted` seconds after a press.
|
|
pub fn pose(mood: Mood, held: f64, shown: f64, petted: Option<f64>) -> Pose {
|
|
let mut pose = match mood {
|
|
Mood::Waiting => waiting(held),
|
|
Mood::Error => fretting(held),
|
|
Mood::Working => working(held),
|
|
Mood::Done if held < CHEER => cheering(held),
|
|
Mood::Calm | Mood::Done if held >= DOZE => dozing(held - DOZE),
|
|
Mood::Calm => calm(held),
|
|
Mood::Done => calm(held - CHEER),
|
|
};
|
|
if shown < RISE as f64 {
|
|
pose.lift -= DEPTH * (1.0 - shown as f32 / RISE).powi(3);
|
|
}
|
|
match petted.map(early) {
|
|
Some(since) if since < PETTED => winking(&mut pose, since),
|
|
Some(since) => pose.open[0] = pose.open[0].min(opening(since - PETTED)),
|
|
None => {}
|
|
}
|
|
pose
|
|
}
|
|
|
|
/// `to`, `since` seconds after a change of act or mood, or a press, eased from `from`, her pose then: her place, her moves and her brows blend, the marks `to` lacks fade, and the shapes are `to`'s.
|
|
pub fn blend(from: &Pose, to: Pose, since: f32) -> Pose {
|
|
let k = smooth((since / EASE).clamp(0.0, 1.0));
|
|
let mix = |a: f32, b: f32| a + (b - a) * k;
|
|
let fading: Vec<(Mark, Motion)> = (from.marks.iter())
|
|
.filter(|(mark, _)| to.marks.iter().all(|(kept, _)| kept != mark))
|
|
.map(|&(mark, motion)| {
|
|
let alpha = motion.alpha * (1.0 - k);
|
|
(mark, Motion { alpha, ..motion })
|
|
})
|
|
.collect();
|
|
let mut pose = Pose {
|
|
lift: mix(from.lift, to.lift),
|
|
shake: mix(from.shake, to.shake),
|
|
bob: mix(from.bob, to.bob),
|
|
tilt: mix(from.tilt, to.tilt),
|
|
sway: mix(from.sway, to.sway),
|
|
ahoge: mix(from.ahoge, to.ahoge),
|
|
look: (mix(from.look.0, to.look.0), mix(from.look.1, to.look.1)),
|
|
wide: mix(from.wide, to.wide),
|
|
brows: mix(from.brows, to.brows),
|
|
flush: mix(from.flush, to.flush),
|
|
hands: [
|
|
mix(from.hands[0], to.hands[0]),
|
|
mix(from.hands[1], to.hands[1]),
|
|
],
|
|
..to
|
|
};
|
|
pose.marks.extend(fading);
|
|
pose
|
|
}
|
|
|
|
/// Life under every act, `t` seconds into it: a bob of `amp` units every `period` seconds, swaying locks, a breathing ahoge, and blinks, her eyes opening and her mouth popping as the act begins.
|
|
fn idle(act: Act, t: f64, period: f32, amp: f32) -> Pose {
|
|
let phase = 2.0 * PI * fold(t, period) / period;
|
|
Pose {
|
|
act,
|
|
lift: 0.0,
|
|
shake: 0.0,
|
|
bob: phase.sin() * amp,
|
|
tilt: 0.0,
|
|
sway: (PI * fold(t, 2.0 * period) / period + 1.1).sin() * 2.2,
|
|
ahoge: (phase + 0.6).sin() * 4.0,
|
|
look: (0.0, 0.0),
|
|
open: [blink(t).min(opening(early(t))); 2],
|
|
wide: 1.0,
|
|
eyes: Eyes::Open,
|
|
mouth: Mouth::Smile,
|
|
pop: popping(early(t)),
|
|
brows: 0.0,
|
|
flush: 0.0,
|
|
hands: [0.0; 2],
|
|
marks: Vec::new(),
|
|
}
|
|
}
|
|
|
|
/// Calm: she glances left, then right, her head after her eyes, and her ahoge wiggles.
|
|
fn calm(t: f64) -> Pose {
|
|
let mut pose = idle(Act::Calm, t, 2.6, 4.0);
|
|
let at = fold(t, 20.0);
|
|
let look = 6.0 * (between(at, 16.0, 17.2) - between(at, 9.0, 10.2));
|
|
pose.look = (look, 0.0);
|
|
pose.tilt = look * 0.5;
|
|
pose.ahoge += wiggle(at - 12.4);
|
|
pose
|
|
}
|
|
|
|
/// Dozing: her eyes fall shut, her head and ahoge droop, and zs float up.
|
|
fn dozing(t: f64) -> Pose {
|
|
let mut pose = idle(Act::Doze, t, 4.2, 3.0);
|
|
let settle = smooth((early(t) / 0.8).min(1.0));
|
|
if t < 0.6 {
|
|
pose.open = [1.0 - (1.0 - SHUT) * smooth(early(t) / 0.6); 2];
|
|
} else {
|
|
pose.eyes = Eyes::Lines;
|
|
pose.open = [1.0; 2];
|
|
}
|
|
pose.mouth = Mouth::Flat;
|
|
pose.tilt = settle * 5.0 + (2.0 * PI * fold(t, 8.4) / 8.4).sin() * 1.5;
|
|
pose.ahoge += settle * 14.0;
|
|
pose.marks = vec![(Mark::Z1, floating(t, 0.0)), (Mark::Z2, floating(t, 0.5))];
|
|
pose
|
|
}
|
|
|
|
/// Cheering: smiling eyes and mouth, a hop, and twinkling sparkles.
|
|
fn cheering(t: f64) -> Pose {
|
|
let mut pose = idle(Act::Cheer, t, 2.2, 4.0);
|
|
let t = early(t);
|
|
pose.eyes = Eyes::Arcs;
|
|
pose.mouth = Mouth::Grin;
|
|
pose.flush = 0.6;
|
|
pose.lift = hops(t, 1.0);
|
|
pose.ahoge += wiggle(t - 0.1);
|
|
pose.marks = vec![
|
|
(Mark::SparkleLeft, twinkling(t, 0.0)),
|
|
(Mark::SparkleRight, twinkling(t, 0.45)),
|
|
];
|
|
pose
|
|
}
|
|
|
|
/// Working: her eyes run along the terminal's lines, and her fingers tap by turns.
|
|
fn working(t: f64) -> Pose {
|
|
let mut pose = idle(Act::Work, t, 2.0, 2.5);
|
|
let at = fold(t, 2.25);
|
|
let scan = if at < 2.0 {
|
|
-5.0 + 10.0 * smooth(at / 2.0)
|
|
} else {
|
|
5.0 - 10.0 * smooth((at - 2.0) / 0.25)
|
|
};
|
|
pose.look = (scan, 5.0);
|
|
pose.mouth = Mouth::Flat;
|
|
pose.tilt = -2.0;
|
|
let beat = fold(t, 3.0);
|
|
if beat < 1.8 {
|
|
let n = (beat / 0.3) as usize;
|
|
pose.hands[n % 2] = tap(beat - n as f32 * 0.3);
|
|
}
|
|
pose
|
|
}
|
|
|
|
/// Waiting on you: wide eyes, a gasp, a mark that rocks, and two hops every 2.6 s.
|
|
fn waiting(t: f64) -> Pose {
|
|
let mut pose = idle(Act::Wait, t, 1.6, 3.0);
|
|
let beat = 2.0 * PI * fold(t, 0.8) / 0.8;
|
|
pose.wide = 1.0 + 0.12 * overshoot((early(t) / 0.12).min(1.0)) + 0.02 * beat.sin();
|
|
pose.mouth = Mouth::Gasp;
|
|
pose.look = (0.0, -2.0);
|
|
let at = fold(t, 2.6);
|
|
pose.lift = hops(at, 2.0);
|
|
pose.ahoge += wiggle(at);
|
|
let bang = Motion {
|
|
at: (0.0, 0.0),
|
|
scale: popping(early(t)),
|
|
turn: beat.sin() * 8.0,
|
|
alpha: 1.0,
|
|
};
|
|
pose.marks = vec![(Mark::Bang, bang)];
|
|
pose
|
|
}
|
|
|
|
/// An error: brows up, a frown, a shake, a smaller one every 4.5 s, and a drop that runs down.
|
|
fn fretting(t: f64) -> Pose {
|
|
let mut pose = idle(Act::Fret, t, 2.4, 1.5);
|
|
pose.brows = 1.0;
|
|
pose.mouth = Mouth::Frown;
|
|
pose.look = (0.0, 3.0);
|
|
pose.tilt = -3.0;
|
|
pose.shake = shaking(early(t), 10.0) + shaking(fold(t, 4.5) - 3.9, 4.0);
|
|
let f = fold(t, 2.0) / 2.0;
|
|
let drop = Motion {
|
|
at: (0.0, 18.0 * smooth(f)),
|
|
scale: 1.0,
|
|
turn: 0.0,
|
|
alpha: (f * PI).sin(),
|
|
};
|
|
pose.marks = vec![(Mark::Drop, drop)];
|
|
pose
|
|
}
|
|
|
|
/// A press, `since` seconds ago: a wink, a grin, a blush, a small hop, a wiggle and a heart that rises; an eye she slept with opens.
|
|
fn winking(pose: &mut Pose, since: f32) {
|
|
let woke = pose.eyes != Eyes::Open;
|
|
pose.act = Act::Wink;
|
|
pose.eyes = Eyes::Wink;
|
|
pose.open = [
|
|
opening(since),
|
|
if woke { opening(since) } else { pose.open[1] },
|
|
];
|
|
pose.mouth = Mouth::Grin;
|
|
pose.pop = popping(since);
|
|
pose.flush = 1.0;
|
|
pose.lift += hops(since, 1.0) * 0.6;
|
|
pose.ahoge += wiggle(since);
|
|
let heart = Motion {
|
|
at: (0.0, -26.0 * since / PETTED),
|
|
scale: popping(since),
|
|
turn: 0.0,
|
|
alpha: ((PETTED - since) / 0.4).min(1.0),
|
|
};
|
|
pose.marks.push((Mark::Heart, heart));
|
|
}
|