use super::{Point3, UnitQuaternion, Vector3};
use eunomia::RealField;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(
feature = "serde",
serde(bound(deserialize = "T: serde::Deserialize<'de> + Copy + Default"))
)]
#[repr(transparent)]
pub struct Translation3<T> {
pub vector: Vector3<T>,
}
impl<T> Translation3<T> {
#[inline(always)]
pub const fn new(x: T, y: T, z: T) -> Self {
Self {
vector: Vector3::from_array([x, y, z]),
}
}
#[inline(always)]
pub const fn from_vector(vector: Vector3<T>) -> Self {
Self { vector }
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(
feature = "serde",
serde(bound(deserialize = "T: serde::Deserialize<'de> + Copy + Default"))
)]
#[repr(C)]
pub struct Isometry3<T> {
pub rotation: UnitQuaternion<T>,
pub translation: Vector3<T>,
}
impl<T: RealField> Isometry3<T> {
#[inline]
pub fn identity() -> Self {
Self {
rotation: UnitQuaternion::identity(),
translation: Vector3::zeros(),
}
}
#[inline]
pub fn from_parts(translation: Translation3<T>, rotation: UnitQuaternion<T>) -> Self {
Self {
rotation,
translation: translation.vector,
}
}
#[inline]
pub fn transform_point(self, p: Point3<T>) -> Point3<T> {
Point3::from_coords(self.rotation.transform_vector(p.coords) + self.translation)
}
#[inline]
pub fn transform_vector(self, v: Vector3<T>) -> Vector3<T> {
self.rotation.transform_vector(v)
}
#[inline]
pub fn inverse(self) -> Self {
let inv_rot = self.rotation.inverse();
Self {
rotation: inv_rot,
translation: -inv_rot.transform_vector(self.translation),
}
}
}
impl<T: RealField> Default for Isometry3<T> {
#[inline]
fn default() -> Self {
Self::identity()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::geometry::{Unit, Vector};
#[test]
fn transform_point_rotates_then_translates() {
let z = Unit::new_normalize(Vector::from_array([0.0_f64, 0.0, 1.0]));
let rot = UnitQuaternion::from_axis_angle(z, core::f64::consts::FRAC_PI_2);
let iso = Isometry3::from_parts(Translation3::new(10.0, 0.0, 0.0), rot);
let p = iso.transform_point(Point3::from_array([1.0, 0.0, 0.0]));
assert!((p.coords.data[0] - 10.0).abs() < 1e-12);
assert!((p.coords.data[1] - 1.0).abs() < 1e-12);
assert!(p.coords.data[2].abs() < 1e-12);
}
#[test]
fn inverse_round_trips() {
let axis = Unit::new_normalize(Vector::from_array([1.0_f64, 1.0, 1.0]));
let iso = Isometry3::from_parts(
Translation3::new(3.0, -2.0, 5.0),
UnitQuaternion::from_axis_angle(axis, 0.9),
);
let p = Point3::from_array([2.0_f64, -1.0, 4.0]);
let round = iso.inverse().transform_point(iso.transform_point(p));
for k in 0..3 {
assert!((round.coords.data[k] - p.coords.data[k]).abs() < 1e-10);
}
}
}