use crate::{
geometry::{Quaternion, Vector3},
time::{TimePoint, Timestamp},
};
use alloc::string::String;
use approx::{AbsDiffEq, RelativeEq};
use core::ops::Mul;
pub use error::TransformError;
pub use traits::{Localized, Transformable};
mod error;
mod traits;
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
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 const UNIT_NORM_TOLERANCE: f64 = 1e-6;
pub fn validate(&self) -> Result<(), TransformError> {
let t = self.translation;
let q = self.rotation;
let finite = t.x.is_finite()
&& t.y.is_finite()
&& t.z.is_finite()
&& q.w.is_finite()
&& q.x.is_finite()
&& q.y.is_finite()
&& q.z.is_finite();
if !finite {
return Err(TransformError::NonFiniteValues);
}
let norm = q.norm();
if (norm - 1.0).abs() > Self::UNIT_NORM_TOLERANCE {
return Err(TransformError::NonUnitRotation(norm));
}
Ok(())
}
pub fn interpolate(
from: &Transform<T>,
to: &Transform<T>,
timestamp: T,
) -> Result<Transform<T>, TransformError> {
if from.timestamp > to.timestamp {
return Err(TransformError::TimestampMismatch(
from.timestamp.as_seconds_lossy(),
to.timestamp.as_seconds_lossy(),
));
}
if timestamp < from.timestamp || timestamp > to.timestamp {
return Err(TransformError::TimestampOutOfRange(
timestamp.as_seconds_lossy(),
from.timestamp.as_seconds_lossy(),
to.timestamp.as_seconds_lossy(),
));
}
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]
pub fn identity() -> Self {
Transform {
translation: Vector3::zero(),
rotation: Quaternion::identity(),
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();
if !is_self_static && !is_rhs_static && self.timestamp != rhs.timestamp {
return Err(TransformError::TimestampMismatch(
self.timestamp.as_seconds_lossy(),
rhs.timestamp.as_seconds_lossy(),
));
}
if self.child == rhs.child {
return Err(TransformError::SameFrameMultiplication);
}
if self.child != rhs.parent {
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 {
self.timestamp
},
parent: self.parent,
child: rhs.child,
})
}
}
impl<T> AbsDiffEq for Transform<T>
where
T: TimePoint,
{
type Epsilon = f64;
fn default_epsilon() -> Self::Epsilon {
f64::EPSILON
}
fn abs_diff_eq(
&self,
other: &Self,
epsilon: Self::Epsilon,
) -> bool {
self.translation.abs_diff_eq(&other.translation, epsilon)
&& self.rotation.abs_diff_eq(&other.rotation, epsilon)
&& self.timestamp == other.timestamp
&& self.parent == other.parent
&& self.child == other.child
}
}
impl<T> RelativeEq for Transform<T>
where
T: TimePoint,
{
fn default_max_relative() -> Self::Epsilon {
f64::EPSILON
}
fn relative_eq(
&self,
other: &Self,
epsilon: Self::Epsilon,
max_relative: Self::Epsilon,
) -> bool {
self.translation
.relative_eq(&other.translation, epsilon, max_relative)
&& self
.rotation
.relative_eq(&other.rotation, epsilon, max_relative)
&& self.timestamp == other.timestamp
&& self.parent == other.parent
&& self.child == other.child
}
}
#[cfg(test)]
mod tests;