#![doc = include_str!("../readme.md")]
use std::fmt::Formatter;
use std::marker::PhantomData;
use std::ops::{Add, Mul, Sub};
use glam::{Mat4, Quat};
pub trait Smoothed {
type Attribute: Copy;
fn drive(target: Self::Attribute, current: Self::Attribute, percent: f32) -> Self::Attribute;
}
#[derive(Copy, Clone)]
pub struct TransformComponent<T: Smoothed> {
pub retention: f32,
pub current: T::Attribute,
pub target: T::Attribute,
_unused: PhantomData<T>,
}
impl<T: Smoothed> std::fmt::Debug for TransformComponent<T> where
T::Attribute: std::fmt::Debug {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
f.debug_struct(
"TransformComponent",
)
.field("retention", &self.retention)
.field("current", &self.current)
.field("target", &self.target)
.finish()
}
}
impl<T: Smoothed> TransformComponent<T> {
pub fn drive(&mut self, delta_time: f32) -> T::Attribute {
let percent = 1.0 - self.retention.powf(delta_time);
let new_current = T::drive(self.target, self.current, percent);
self.current = new_current;
new_current
}
pub fn hard_set(&mut self, target: T::Attribute) {
self.target = target;
self.current = target;
}
pub fn new(retention: f32, initial: T::Attribute) -> Self {
Self {
retention,
current: initial,
target: initial,
_unused: PhantomData,
}
}
pub fn begin<F: FnOnce(T::Attribute) -> Mat4>(&mut self, f: F) -> First<T, F> {
First {
component: self,
f,
}
}
}
impl<T> TransformComponent<Translate<T>>
where T: Add<T, Output=T> + Mul<f32, Output=T> + Sub<T, Output=T> + Copy
{
pub fn new_translate(initial_state: T) -> Self {
Self::new(0.01, initial_state)
}
pub fn new_zoom(initial_state: T) -> Self {
Self::new(0.03, initial_state)
}
pub fn new_angle(initial_state: T) -> Self {
Self::new(0.04, initial_state)
}
}
impl TransformComponent<Rotate> {
pub fn new_rotate(initial_state: Quat) -> Self {
Self::new(0.04, initial_state)
}
}
pub struct Translate<T>(PhantomData<T>);
impl<T> Smoothed for Translate<T>
where T: Add<T, Output = T> + Mul<f32, Output = T> + Sub<T, Output = T> + Copy {
type Attribute = T;
fn drive(target: T, current: T, percent: f32) -> T {
current + (target - current) * percent
}
}
pub struct Rotate;
impl Smoothed for Rotate {
type Attribute = Quat;
fn drive(target: Quat, current: Quat, percent: f32) -> Quat {
current.slerp(target, percent).normalize()
}
}
pub struct First<'a, T: Smoothed, F: FnOnce(T::Attribute) -> Mat4> {
component: &'a mut TransformComponent<T>,
f: F,
}
pub struct Composition<'a, T: Smoothed, F: FnOnce(T::Attribute) -> Mat4, I: Scaffold + 'a> {
component: &'a mut TransformComponent<T>,
f: F,
inner: I,
}
pub trait Scaffold: Sized {
fn drive(self, time: f32) -> Mat4;
#[inline(always)]
fn and_then<'a, T: Smoothed, F: FnOnce(T::Attribute) -> Mat4>(self, next: &'a mut TransformComponent<T>, f: F) -> Composition<'a, T, F, Self>
where Self: 'a {
Composition {
component: next,
f,
inner: self,
}
}
}
impl<'a, T: Smoothed, F: FnOnce(T::Attribute) -> Mat4> Scaffold for First<'a, T, F> {
#[inline(always)]
fn drive(self, time: f32) -> Mat4 {
let attrib = self.component.drive(time);
(self.f)(attrib)
}
}
impl<'a, T, F, I> Scaffold for Composition<'a, T, F, I>
where T: Smoothed,
F: FnOnce(T::Attribute) -> Mat4,
I: Scaffold + 'a {
#[inline(always)]
fn drive(self, time: f32) -> Mat4 {
let inner = self.inner.drive(time);
let attrib = self.component.drive(time);
(self.f)(attrib) * inner
}
}
#[cfg(test)]
mod test {
use glam::Vec3;
use super::*;
#[test]
fn this_works() {
let mut zoom = TransformComponent::new_zoom(2.0);
let mut rotate = TransformComponent::new_rotate(Quat::IDENTITY);
let mut translate = TransformComponent::new_translate(Vec3::ONE);
let mut angle = TransformComponent::new_angle(0.4);
zoom.target = 4.0;
rotate.target *= Quat::from_rotation_x(0.4);
translate.target += Vec3::ONE;
angle.target /= 3.0;
let delta_time = 0.03;
let transform_matrix = zoom.begin(|zoom| Mat4::from_translation(Vec3::splat(zoom)))
.and_then(&mut rotate, |quat| Mat4::from_quat(quat))
.and_then(&mut translate, |by| Mat4::from_translation(by))
.and_then(&mut angle, |angle| Mat4::from_rotation_y(angle))
.drive(delta_time);
let inv = transform_matrix.inverse();
assert!(inv.is_finite());
}
}