use crate::{
geometry::{Quaternion, Vector3},
time::{TimePoint, Timestamp},
};
use alloc::string::String;
use approx::AbsDiffEq;
use core::{cmp::Ordering, ops::Mul, time::Duration};
pub use error::TransformError;
pub use traits::{Localized, Transformable};
mod error;
mod traits;
#[derive(Debug, Clone)]
pub struct Transform<T = Timestamp>
where
T: TimePoint,
{
pub translation: Vector3,
pub rotation: Quaternion,
pub timestamp: T,
pub parent: String,
pub child: String,
}
impl<T> Transform<T>
where
T: TimePoint,
{
pub fn interpolate(
from: &Transform<T>,
to: &Transform<T>,
timestamp: T,
) -> Result<Transform<T>, TransformError> {
if from.timestamp > to.timestamp || timestamp < from.timestamp || timestamp > to.timestamp {
return Err(TransformError::TimestampMismatch(
to.timestamp.as_seconds()?,
from.timestamp.as_seconds()?,
));
}
if from.child != to.child || from.parent != to.parent {
return Err(TransformError::IncompatibleFrames);
}
let range = to.timestamp.duration_since(from.timestamp)?;
if range.is_zero() {
return Ok(from.clone());
}
let diff = timestamp.duration_since(from.timestamp)?;
let ratio = diff.as_secs_f64() / range.as_secs_f64();
Ok(Transform {
translation: (1.0 - ratio) * from.translation + ratio * to.translation,
rotation: from.rotation.slerp(to.rotation, ratio),
timestamp,
child: from.child.clone(),
parent: from.parent.clone(),
})
}
#[must_use = "Returns a new transform"]
pub fn identity() -> Self {
Transform {
translation: Vector3 {
x: 0.0,
y: 0.0,
z: 0.0,
},
rotation: Quaternion {
w: 1.0,
x: 0.0,
y: 0.0,
z: 0.0,
},
timestamp: T::static_timestamp(),
parent: String::new(),
child: String::new(),
}
}
pub fn inverse(&self) -> Result<Self, TransformError> {
let q = self.rotation.normalize()?;
let inverse_rotation = q.conjugate();
let inverse_translation = -1.0 * (inverse_rotation.rotate_vector(self.translation));
Ok(Transform {
translation: inverse_translation,
rotation: inverse_rotation,
timestamp: self.timestamp,
parent: self.child.clone(),
child: self.parent.clone(),
})
}
}
impl<T> Mul for Transform<T>
where
T: TimePoint,
{
type Output = Result<Transform<T>, TransformError>;
#[inline]
fn mul(
self,
rhs: Transform<T>,
) -> Self::Output {
let is_self_static = self.timestamp.is_static();
let is_rhs_static = rhs.timestamp.is_static();
let duration = if !is_self_static && !is_rhs_static {
let d = match self.timestamp.cmp(&rhs.timestamp) {
Ordering::Equal => Ok(Duration::from_secs(0)),
Ordering::Greater => self.timestamp.duration_since(rhs.timestamp),
Ordering::Less => rhs.timestamp.duration_since(self.timestamp),
}?;
if d.as_secs_f64() > 2.0 * f64::EPSILON {
return Err(TransformError::TimestampMismatch(
self.timestamp.as_seconds()?,
rhs.timestamp.as_seconds()?,
));
}
d
} else {
Duration::from_secs(0)
};
if self.child == rhs.child {
return Err(TransformError::SameFrameMultiplication);
}
if self.child != rhs.parent && self.parent != rhs.child {
return Err(TransformError::IncompatibleFrames);
}
let r = self.rotation * rhs.rotation;
let t = self.rotation.rotate_vector(rhs.translation) + self.translation;
Ok(Transform {
translation: t,
rotation: r,
timestamp: if is_self_static {
rhs.timestamp
} else if is_rhs_static {
self.timestamp
} else {
self.timestamp.checked_add(duration.div_f64(2.0))?
},
parent: self.parent,
child: rhs.child,
})
}
}
impl<T> PartialEq for Transform<T>
where
T: TimePoint,
{
fn eq(
&self,
other: &Self,
) -> bool {
self.translation
.abs_diff_eq(&other.translation, f64::EPSILON)
&& self.rotation.abs_diff_eq(&other.rotation, f64::EPSILON)
&& self.timestamp == other.timestamp
&& self.parent == other.parent
&& self.child == other.child
}
}
impl<T> Eq for Transform<T> where T: TimePoint {}
#[cfg(test)]
mod tests;