pub const trait LerpUtil: Sized { fn lerp(self, from: Self, to: Self) -> Self; fn lerp_inv(self, from: Self, to: Self) -> Option; } const impl LerpUtil for f32 { /// linear interpolation /// from * (1.0 - self) + to * self fn lerp(self, from: Self, to: Self) -> Self { from + (to - from) * self } /// inverse of lerp, and `None` where `from` and `to` are the same point: /// every input lerps to it, so there is no one answer to come back to. fn lerp_inv(self, from: Self, to: Self) -> Option { match to == from { true => None, false => Some((self - from) / (to - from)), } } } macro_rules! impl_op { ($T:ident $op:ident $fn:ident $opa:ident $fna:ident; $($field:ident)*) => { #[allow(non_snake_case)] mod ${concat($T, _op_, $fn, _impl)} { use super::*; #[allow(unused_imports)] use std::ops::*; const impl $op for $T { type Output = Self; fn $fn(self, rhs: Self) -> Self::Output { Self { $($field: self.$field.$fn(rhs.$field),)* } } } const impl $opa for $T { fn $fna(&mut self, rhs: Self) { *self = self.$fn(rhs); } } const impl $op for $T { type Output = Self; fn $fn(self, rhs: f32) -> Self::Output { Self { $($field: self.$field.$fn(rhs),)* } } } const impl $op<$T> for f32 { type Output = $T; fn $fn(self, rhs: $T) -> Self::Output { $T { $($field: self.$fn(rhs.$field),)* } } } const impl $opa for $T { fn $fna(&mut self, rhs: f32) { *self = self.$fn(rhs); } } } }; ($T:ident $op:ident $fn:ident; $($field:ident)*) => { impl_op!($T $op $fn ${concat($op,Assign)} ${concat($fn,_assign)}; $($field)*); }; (impl $op:ident for $T:ident: $fn:ident $($field:ident)*) => { impl_op!($T $op $fn ${concat($op,Assign)} ${concat($fn,_assign)}; $($field)*); }; } pub(crate) use impl_op;