use crate::approxeq::ApproxEq;
use crate::trig::Trig;
use crate::{Rotation3D, Transform3D, UnknownUnit, Vector3D};
use core::{fmt, hash};
#[cfg(feature = "bytemuck")]
use bytemuck::{Pod, Zeroable};
use num_traits::real::Real;
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[repr(C)]
pub struct RigidTransform3D<T, Src, Dst> {
pub rotation: Rotation3D<T, Src, Dst>,
pub translation: Vector3D<T, Dst>,
}
impl<T, Src, Dst> RigidTransform3D<T, Src, Dst> {
#[inline]
pub const fn new(rotation: Rotation3D<T, Src, Dst>, translation: Vector3D<T, Dst>) -> Self {
Self {
rotation,
translation,
}
}
}
impl<T: Copy, Src, Dst> RigidTransform3D<T, Src, Dst> {
pub fn cast_unit<Src2, Dst2>(&self) -> RigidTransform3D<T, Src2, Dst2> {
RigidTransform3D {
rotation: self.rotation.cast_unit(),
translation: self.translation.cast_unit(),
}
}
}
impl<T: Real + ApproxEq<T>, Src, Dst> RigidTransform3D<T, Src, Dst> {
#[inline]
pub fn identity() -> Self {
Self {
rotation: Rotation3D::identity(),
translation: Vector3D::zero(),
}
}
#[inline]
pub fn new_from_reversed(
translation: Vector3D<T, Src>,
rotation: Rotation3D<T, Src, Dst>,
) -> Self {
let translation = rotation.transform_vector3d(translation);
Self {
rotation,
translation,
}
}
#[inline]
pub fn from_rotation(rotation: Rotation3D<T, Src, Dst>) -> Self {
Self {
rotation,
translation: Vector3D::zero(),
}
}
#[inline]
pub fn from_translation(translation: Vector3D<T, Dst>) -> Self {
Self {
translation,
rotation: Rotation3D::identity(),
}
}
#[inline]
pub fn decompose_reversed(&self) -> (Vector3D<T, Src>, Rotation3D<T, Src, Dst>) {
let translation = self.rotation.inverse().transform_vector3d(self.translation);
(translation, self.rotation)
}
#[inline]
pub fn then<Dst2>(
&self,
other: &RigidTransform3D<T, Dst, Dst2>,
) -> RigidTransform3D<T, Src, Dst2> {
let t_prime = other.rotation.transform_vector3d(self.translation);
let r_prime = self.rotation.then(&other.rotation);
let t_prime2 = t_prime + other.translation;
RigidTransform3D {
rotation: r_prime,
translation: t_prime2,
}
}
#[inline]
pub fn inverse(&self) -> RigidTransform3D<T, Dst, Src> {
RigidTransform3D::new_from_reversed(-self.translation, self.rotation.inverse())
}
pub fn to_transform(&self) -> Transform3D<T, Src, Dst>
where
T: Trig,
{
self.rotation
.to_transform()
.then(&self.translation.to_transform())
}
#[inline]
pub fn to_untyped(&self) -> RigidTransform3D<T, UnknownUnit, UnknownUnit> {
RigidTransform3D {
rotation: self.rotation.to_untyped(),
translation: self.translation.to_untyped(),
}
}
#[inline]
pub fn from_untyped(transform: &RigidTransform3D<T, UnknownUnit, UnknownUnit>) -> Self {
RigidTransform3D {
rotation: Rotation3D::from_untyped(&transform.rotation),
translation: Vector3D::from_untyped(transform.translation),
}
}
}
impl<T: fmt::Debug, Src, Dst> fmt::Debug for RigidTransform3D<T, Src, Dst> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("RigidTransform3D")
.field("rotation", &self.rotation)
.field("translation", &self.translation)
.finish()
}
}
impl<T: PartialEq, Src, Dst> PartialEq for RigidTransform3D<T, Src, Dst> {
fn eq(&self, other: &Self) -> bool {
self.rotation == other.rotation && self.translation == other.translation
}
}
impl<T: Eq, Src, Dst> Eq for RigidTransform3D<T, Src, Dst> {}
impl<T: hash::Hash, Src, Dst> hash::Hash for RigidTransform3D<T, Src, Dst> {
fn hash<H: hash::Hasher>(&self, state: &mut H) {
self.rotation.hash(state);
self.translation.hash(state);
}
}
impl<T: Copy, Src, Dst> Copy for RigidTransform3D<T, Src, Dst> {}
impl<T: Clone, Src, Dst> Clone for RigidTransform3D<T, Src, Dst> {
fn clone(&self) -> Self {
RigidTransform3D {
rotation: self.rotation.clone(),
translation: self.translation.clone(),
}
}
}
#[cfg(feature = "arbitrary")]
impl<'a, T, Src, Dst> arbitrary::Arbitrary<'a> for RigidTransform3D<T, Src, Dst>
where
T: arbitrary::Arbitrary<'a>,
{
fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
Ok(RigidTransform3D {
rotation: arbitrary::Arbitrary::arbitrary(u)?,
translation: arbitrary::Arbitrary::arbitrary(u)?,
})
}
}
#[cfg(feature = "bytemuck")]
unsafe impl<T: Zeroable, Src, Dst> Zeroable for RigidTransform3D<T, Src, Dst> {}
#[cfg(feature = "bytemuck")]
unsafe impl<T: Pod, Src: 'static, Dst: 'static> Pod for RigidTransform3D<T, Src, Dst> {}
impl<T: Real + ApproxEq<T>, Src, Dst> From<Rotation3D<T, Src, Dst>>
for RigidTransform3D<T, Src, Dst>
{
fn from(rot: Rotation3D<T, Src, Dst>) -> Self {
Self::from_rotation(rot)
}
}
impl<T: Real + ApproxEq<T>, Src, Dst> From<Vector3D<T, Dst>> for RigidTransform3D<T, Src, Dst> {
fn from(t: Vector3D<T, Dst>) -> Self {
Self::from_translation(t)
}
}
#[cfg(test)]
mod test {
use super::RigidTransform3D;
use crate::default::{Rotation3D, Transform3D, Vector3D};
#[test]
fn test_rigid_construction() {
let translation = Vector3D::new(12.1, 17.8, -5.5);
let rotation = Rotation3D::unit_quaternion(0.5, -7.8, 2.2, 4.3);
let rigid = RigidTransform3D::new(rotation, translation);
assert!(rigid
.to_transform()
.approx_eq(&rotation.to_transform().then(&translation.to_transform())));
let rigid = RigidTransform3D::new_from_reversed(translation, rotation);
assert!(rigid
.to_transform()
.approx_eq(&translation.to_transform().then(&rotation.to_transform())));
}
#[test]
fn test_rigid_decomposition() {
let translation = Vector3D::new(12.1, 17.8, -5.5);
let rotation = Rotation3D::unit_quaternion(0.5, -7.8, 2.2, 4.3);
let rigid = RigidTransform3D::new(rotation, translation);
let (t2, r2) = rigid.decompose_reversed();
assert!(rigid
.to_transform()
.approx_eq(&t2.to_transform().then(&r2.to_transform())));
}
#[test]
fn test_rigid_inverse() {
let translation = Vector3D::new(12.1, 17.8, -5.5);
let rotation = Rotation3D::unit_quaternion(0.5, -7.8, 2.2, 4.3);
let rigid = RigidTransform3D::new(rotation, translation);
let inverse = rigid.inverse();
assert!(rigid
.then(&inverse)
.to_transform()
.approx_eq(&Transform3D::identity()));
assert!(inverse
.to_transform()
.approx_eq(&rigid.to_transform().inverse().unwrap()));
}
#[test]
fn test_rigid_multiply() {
let translation = Vector3D::new(12.1, 17.8, -5.5);
let rotation = Rotation3D::unit_quaternion(0.5, -7.8, 2.2, 4.3);
let translation2 = Vector3D::new(9.3, -3.9, 1.1);
let rotation2 = Rotation3D::unit_quaternion(0.1, 0.2, 0.3, -0.4);
let rigid = RigidTransform3D::new(rotation, translation);
let rigid2 = RigidTransform3D::new(rotation2, translation2);
assert!(rigid
.then(&rigid2)
.to_transform()
.approx_eq(&rigid.to_transform().then(&rigid2.to_transform())));
assert!(rigid2
.then(&rigid)
.to_transform()
.approx_eq(&rigid2.to_transform().then(&rigid.to_transform())));
}
}