Files
ELYTerminal/src/pet/look.rs
T

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));
}