mirage-engine 0.2.0

Mirage, an immediate-mode 3D engine for simple games on desktop and the browser
Documentation
use core::mem::discriminant;

use crate::math::{Quat, Vec3};
use crate::mesh::Local;

/// What one clip moves: one track per joint and path it moves, and nothing
/// for the joints and paths it leaves at rest.
#[derive(Clone, Debug, Default, PartialEq)]
pub(crate) struct Animation {
    tracks: Vec<Track>,
}

impl Animation {
    /// The clip that moves `tracks`.
    pub(crate) fn new(tracks: Vec<Track>) -> Self {
        Self { tracks }
    }

    /// The tracks, one per joint and path.
    pub(crate) fn tracks(&self) -> &[Track] {
        &self.tracks
    }

    /// Memory the clip's keys hold.
    pub(crate) fn bytes(&self) -> usize {
        self.tracks().iter().map(Track::bytes).sum()
    }

    /// The span the clip's keys lie in, which a pose of it is read along.
    pub(crate) fn timeline(&self) -> Timeline {
        self.tracks()
            .iter()
            .filter_map(|track| track.moves.span())
            .reduce(Timeline::joined)
            .unwrap_or_default()
    }

    /// Every time any track of the clip holds a key, in ascending order,
    /// with no time repeated.
    ///
    /// Sampling the clip at all of them reaches every pose it holds between
    /// its first key and its last.
    pub(crate) fn key_times(&self) -> Vec<f32> {
        let mut times: Vec<f32> = self
            .tracks()
            .iter()
            .flat_map(|track| track.moves.times())
            .collect();
        times.sort_by(f32::total_cmp);
        times.dedup();

        times
    }
}

/// The span one clip's keys lie in: where the first of them lies along the
/// source's own timeline, and where the last does.
///
/// One run of a clip is one span of it, so a pose of it is read at a
/// fraction of this and a machine counts its cycles in these.
#[derive(Clone, Copy, Debug, Default, PartialEq)]
pub(crate) struct Timeline {
    start: f32,
    end: f32,
}

impl Timeline {
    /// How long the clip runs for, in seconds; nothing at all where it
    /// holds one key or none.
    pub(crate) fn span(self) -> f32 {
        self.end - self.start
    }

    /// The time `fraction` of the way along it lies at, in seconds.
    pub(crate) fn at(self, fraction: f32) -> f32 {
        self.start + self.span() * fraction
    }

    /// The span holding both of these.
    fn joined(self, other: Self) -> Self {
        Self {
            start: self.start.min(other.start),
            end: self.end.max(other.end),
        }
    }
}

/// One path of one joint through a clip, with the keys along it.
#[derive(Clone, Debug, PartialEq)]
pub(crate) struct Track {
    pub(crate) joint: u32,
    pub(crate) moves: Moves,
}

impl Track {
    /// Whether both tracks move the same path of the same joint, which one
    /// of them alone is then read for.
    pub(crate) fn same_path(&self, other: &Self) -> bool {
        self.joint == other.joint && discriminant(&self.moves) == discriminant(&other.moves)
    }

    /// Whether both tracks hold the same keys, whatever else names them.
    pub(crate) fn same_curve(&self, other: &Self) -> bool {
        self.moves == other.moves
    }

    /// Memory this track's keys hold.
    fn bytes(&self) -> usize {
        self.moves.bytes()
    }
}

/// What a track moves, with the keys it moves along.
#[derive(Clone, Debug, PartialEq)]
pub(crate) enum Moves {
    Position(Keys<Vec3>),
    Turn(Keys<Quat>),
    Scale(Keys<Vec3>),
}

impl Moves {
    /// `local` with the path this track moves at the value it holds `at`
    /// seconds along the clip; `local` as it is where the track holds no key
    /// at all.
    ///
    /// A time before the first key reads the first key and one past the last
    /// reads the last, so a clip holds at both of its ends.
    pub(crate) fn moved(&self, local: Local, at: f32) -> Local {
        match self {
            Self::Position(keys) => Local {
                position: keys.at(at).unwrap_or(local.position),
                ..local
            },
            Self::Turn(keys) => Local {
                turn: keys.at(at).unwrap_or(local.turn),
                ..local
            },
            Self::Scale(keys) => Local {
                scale: keys.at(at).unwrap_or(local.scale),
                ..local
            },
        }
    }

    /// The span these keys lie in, absent where the track holds no key.
    fn span(&self) -> Option<Timeline> {
        match self {
            Self::Position(keys) | Self::Scale(keys) => keys.span(),
            Self::Turn(keys) => keys.span(),
        }
    }

    /// The time each of these keys lies at, in the order they are held.
    fn times(&self) -> Vec<f32> {
        match self {
            Self::Position(keys) | Self::Scale(keys) => keys.times(),
            Self::Turn(keys) => keys.times(),
        }
    }

    /// Memory these keys hold.
    fn bytes(&self) -> usize {
        match self {
            Self::Position(keys) | Self::Scale(keys) => keys.bytes(),
            Self::Turn(keys) => keys.bytes(),
        }
    }
}

/// The keys of one track: the time each of them lies at and the value
/// there, and how a time between two of them reads.
///
/// A time is seconds along the source's own timeline, and the times need not
/// step by one amount, so a read finds the keys it lies between.
/// [`Cubic`](Keys::Cubic) holds three values per key: the curve into the
/// key, the key's own value, and the curve out of it.
#[derive(Clone, Debug, PartialEq)]
pub(crate) enum Keys<T> {
    Step(Vec<(f32, T)>),
    Linear(Vec<(f32, T)>),
    Cubic(Vec<(f32, [T; 3])>),
}

impl<T> Keys<T> {
    /// Memory these keys hold.
    fn bytes(&self) -> usize {
        match self {
            Self::Step(keys) | Self::Linear(keys) => size_of_val(keys.as_slice()),
            Self::Cubic(keys) => size_of_val(keys.as_slice()),
        }
    }

    /// The span from the first of these keys to the last, absent where
    /// there is no key; the keys lie in the order they are held.
    fn span(&self) -> Option<Timeline> {
        let (first, last) = match self {
            Self::Step(keys) | Self::Linear(keys) => (keys.first()?.0, keys.last()?.0),
            Self::Cubic(keys) => (keys.first()?.0, keys.last()?.0),
        };

        Some(Timeline {
            start: first,
            end: last,
        })
    }

    /// The time each key lies at, in the order they are held.
    fn times(&self) -> Vec<f32> {
        match self {
            Self::Step(keys) | Self::Linear(keys) => keys.iter().map(|&(at, _)| at).collect(),
            Self::Cubic(keys) => keys.iter().map(|&(at, _)| at).collect(),
        }
    }
}

impl<T: Mixed> Keys<T> {
    /// The value these keys hold `at` seconds along the clip, absent where
    /// they hold no key at all.
    ///
    /// The first key reads before the first of them and the last past the
    /// last of them. Between two keys, a [`Step`](Keys::Step) channel holds
    /// the earlier value until the later key, a [`Linear`](Keys::Linear) one
    /// mixes the two, and a [`Cubic`](Keys::Cubic) one takes the curve the
    /// pair's own tangents span. Where two keys lie at one time, the later
    /// of the two is read.
    fn at(&self, at: f32) -> Option<T> {
        match self {
            Self::Step(keys) => Some(keys.get(Between::of(keys, at).earlier)?.1),
            Self::Linear(keys) => {
                let between = Between::of(keys, at);
                let (_, earlier) = *keys.get(between.earlier)?;
                let (_, later) = *keys.get(between.later)?;

                Some(earlier.mixed(later, between.amount))
            }
            Self::Cubic(keys) => {
                let between = Between::of(keys, at);
                let (earlier_at, [_, earlier, out]) = *keys.get(between.earlier)?;
                let (later_at, [into, later, _]) = *keys.get(between.later)?;
                let span = later_at - earlier_at;

                Some(T::combined([
                    (earlier, hermite(between.amount, Curve::Earlier)),
                    (out, hermite(between.amount, Curve::OutOf) * span),
                    (later, hermite(between.amount, Curve::Later)),
                    (into, hermite(between.amount, Curve::Into) * span),
                ]))
            }
        }
    }
}

/// Where a time lies among a track's keys: the key before it, the key after
/// it, and how far of the way from one to the other it is.
///
/// A time at either end of the track names that end key twice, so a read of
/// it returns the end key's own value whichever channel reads it.
struct Between {
    earlier: usize,
    later: usize,
    amount: f32,
}

impl Between {
    /// Where `at` lies among `keys`, which are held in the order they lie
    /// in.
    fn of<T>(keys: &[(f32, T)], at: f32) -> Self {
        let after = keys.partition_point(|&(time, _)| time <= at);
        if after == 0 {
            return Self {
                earlier: 0,
                later: 0,
                amount: 0.0,
            };
        }
        let earlier = after - 1;
        let Some(&(later_at, _)) = keys.get(after) else {
            return Self {
                earlier,
                later: earlier,
                amount: 0.0,
            };
        };
        let earlier_at = keys[earlier].0;
        let span = later_at - earlier_at;

        Self {
            earlier,
            later: after,
            // Two keys at one time leave no span to read across, and the
            // later of the two is the one kept.
            amount: match span > 0.0 {
                true => (at - earlier_at) / span,
                false => 1.0,
            },
        }
    }
}

/// Which of the four curves of a [`Cubic`](Keys::Cubic) interval one value
/// is scaled by.
enum Curve {
    Earlier,
    OutOf,
    Later,
    Into,
}

/// What one of those curves scales its value by `amount` of the way across
/// an interval, as glTF states them.
fn hermite(amount: f32, curve: Curve) -> f32 {
    let squared = amount * amount;
    let cubed = squared * amount;
    match curve {
        Curve::Earlier => 2.0 * cubed - 3.0 * squared + 1.0,
        Curve::OutOf => cubed - 2.0 * squared + amount,
        Curve::Later => 3.0 * squared - 2.0 * cubed,
        Curve::Into => cubed - squared,
    }
}

/// A value a track holds at its keys, and how a time between two of them
/// reads.
///
/// A position and a size mix straight; a turn takes the shorter way round
/// and comes back at unit length.
pub(crate) trait Mixed: Copy {
    /// This value `amount` of the way to `other`.
    fn mixed(self, other: Self, amount: f32) -> Self;

    /// These values, each scaled by its own amount and added.
    fn combined(weighted: [(Self, f32); 4]) -> Self;
}

impl Mixed for Vec3 {
    fn mixed(self, other: Self, amount: f32) -> Self {
        self.lerp(other, amount)
    }

    fn combined(weighted: [(Self, f32); 4]) -> Self {
        weighted
            .into_iter()
            .map(|(value, amount)| value * amount)
            .sum()
    }
}

impl Mixed for Quat {
    fn mixed(self, other: Self, amount: f32) -> Self {
        self.lerp(other, amount)
    }

    /// A curve takes the turns as the source states them, never the shorter
    /// way round, so these are added lane by lane and the sum taken back to
    /// unit length.
    fn combined(weighted: [(Self, f32); 4]) -> Self {
        let summed = weighted.into_iter().fold(
            Quat::from_xyzw(0.0, 0.0, 0.0, 0.0),
            |sum, (turn, amount)| sum + turn * amount,
        );

        normalized(summed)
    }
}

/// `turn` at unit length, and no turn at all where it has no length.
fn normalized(turn: Quat) -> Quat {
    match turn.length_squared() > 0.0 {
        true => turn.normalize(),
        false => Quat::IDENTITY,
    }
}