psychopath/sub_crates/rmath/src/xform.rs

306 lines
7.9 KiB
Rust

#![allow(dead_code)]
use std::ops::{Add, Mul};
use crate::point::Point;
use crate::sealed::Sealed;
use crate::wide4::Float4;
/// A forward affine transform.
///
/// Use this for working with transforms that still need to be
/// manipulated or composed with other transforms, or for storing
/// transforms more compactly.
///
/// Note: slightly counter-intuitively, even though this can perform
/// forward (but not inverse) transforms on points and vectors, it is
/// capable of *inverse* (but not forward) transforms on surface normals.
/// This is because forward transforms on surface normals require the
/// inverse transform matrix.
///
/// Convert to an [`XformFull`] for a larger-format type capable of
/// efficiently performing both forward and inverse transforms on all
/// types, but which is effectively "frozen" in terms of further
/// manipulation of the transform itself.
#[derive(Debug, Copy, Clone, PartialEq)]
#[repr(C)]
pub struct Xform {
/// Rotation/scale/shear matrix.
pub m: [Float4; 3],
/// Translation.
pub t: Float4,
}
impl Xform {
/// Creates a new affine transform with the specified values:
///
/// ```text
/// a d g j
/// b e h k
/// c f i l
/// ```
///
/// Where j, k, and l are the xyz translation component.
#[inline]
#[allow(clippy::many_single_char_names)]
#[allow(clippy::too_many_arguments)]
pub fn new(
a: f32,
b: f32,
c: f32,
d: f32,
e: f32,
f: f32,
g: f32,
h: f32,
i: f32,
j: f32,
k: f32,
l: f32,
) -> Self {
Self {
m: [
Float4::new(a, b, c, 0.0),
Float4::new(d, e, f, 0.0),
Float4::new(g, h, i, 0.0),
],
t: Float4::new(j, k, l, 0.0),
}
}
/// Creates a new identity transform.
#[inline]
pub fn identity() -> Self {
Self {
m: [
Float4::new(1.0, 0.0, 0.0, 0.0),
Float4::new(0.0, 1.0, 0.0, 0.0),
Float4::new(0.0, 0.0, 1.0, 0.0),
],
t: Float4::splat(0.0),
}
}
#[inline]
pub fn from_location(loc: Point) -> Xform {
Self {
m: [
Float4::new(1.0, 0.0, 0.0, 0.0),
Float4::new(0.0, 1.0, 0.0, 0.0),
Float4::new(0.0, 0.0, 1.0, 0.0),
],
t: loc.0,
}
}
/// Returns whether the matrices are approximately equal to each other.
/// Each corresponding element in the matrices cannot have a relative
/// error exceeding epsilon.
pub(crate) fn aprx_eq(&self, other: Xform, max_ulps: u32) -> bool {
let mut eq = true;
eq &= Float4::aprx_eq(self.m[0], other.m[0], max_ulps);
eq &= Float4::aprx_eq(self.m[1], other.m[1], max_ulps);
eq &= Float4::aprx_eq(self.m[2], other.m[2], max_ulps);
eq &= Float4::aprx_eq(self.t, other.t, max_ulps);
eq
}
/// Compute the "full" version of the transform.
#[inline]
pub fn to_full(&self) -> Option<XformFull> {
if let Some(inv_m) = Float4::invert_3x3(&self.m) {
Some(XformFull {
fwd: *self,
inv_m: inv_m,
})
} else {
None
}
}
/// Faster but less precise version of `to_full()`.
#[inline]
pub fn to_full_fast(&self) -> Option<XformFull> {
if let Some(inv_m) = Float4::invert_3x3_fast(&self.m) {
Some(XformFull {
fwd: *self,
inv_m: inv_m,
})
} else {
None
}
}
/// Composes two transforms together.
///
/// The resulting transform is the same as doing `self` and then
/// `rhs` in sequence.
#[inline]
pub fn compose(&self, rhs: &Self) -> Self {
let (m, t) = Float4::affine_mul_affine(&self.m, self.t, &rhs.m, rhs.t);
Self { m: m, t: t }
}
/// Composes two transforms together.
///
/// Faster but less precise version.
#[inline]
pub fn compose_fast(&self, rhs: &Self) -> Self {
let (m, t) = Float4::affine_mul_affine_fast(&self.m, self.t, &rhs.m, rhs.t);
Self { m: m, t: t }
}
}
impl Default for Xform {
fn default() -> Self {
Self::identity()
}
}
/// Multiply a matrix by a f32
impl Mul<f32> for Xform {
type Output = Self;
#[inline]
fn mul(self, rhs: f32) -> Self {
Self {
m: [self.m[0] * rhs, self.m[1] * rhs, self.m[2] * rhs],
t: self.t * rhs,
}
}
}
/// Add two matrices together
impl Add for Xform {
type Output = Self;
#[inline]
fn add(self, rhs: Self) -> Self {
Self {
m: [
self.m[0] + rhs.m[0],
self.m[1] + rhs.m[1],
self.m[2] + rhs.m[2],
],
t: self.t + rhs.t,
}
}
}
impl AsXform for Xform {
#[inline(always)]
fn as_xform(&self) -> &Xform {
self
}
}
impl Sealed for Xform {}
//-------------------------------------------------------------
/// A combined forward/inverse affine transform.
///
/// Unlike [`Xform`], this can perform both forward and inverse
/// transforms on all types. However, it also takes up more space and
/// is effectively "frozen" in terms of further manipulation. Prefer
/// [`Xform`] when manipulating or composing transforms, and also
/// when storing transforms if space is a consideration.
///
/// Note: only the 3x3 part of the transform is stored inverted. This
/// is because it's both trivial and more numerically stable to reuse
/// the forward translation vector to do inverse transforms, as
/// `(point - fwd.t) * inv_m`.
#[derive(Debug, Copy, Clone)]
#[repr(C)]
pub struct XformFull {
/// Forward transform.
pub fwd: Xform,
/// Inverse rotation/scale/shear matrix.
pub inv_m: [Float4; 3],
}
impl XformFull {
pub fn identity() -> Self {
Self {
fwd: Xform {
m: [
Float4::new(1.0, 0.0, 0.0, 0.0),
Float4::new(0.0, 1.0, 0.0, 0.0),
Float4::new(0.0, 0.0, 1.0, 0.0),
],
t: Float4::splat(0.0),
},
inv_m: [
Float4::new(1.0, 0.0, 0.0, 0.0),
Float4::new(0.0, 1.0, 0.0, 0.0),
Float4::new(0.0, 0.0, 1.0, 0.0),
],
}
}
}
impl AsXform for XformFull {
#[inline(always)]
fn as_xform(&self) -> &Xform {
&self.fwd
}
}
impl Sealed for XformFull {}
//-------------------------------------------------------------
pub trait AsXform: Sealed {
fn as_xform(&self) -> &Xform;
}
//-------------------------------------------------------------
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn equality() {
let a = Xform::identity();
let b = Xform::identity();
let c = Xform::new(1.1, 0.0, 0.0, 0.0, 0.0, 1.1, 0.0, 0.0, 0.0, 0.0, 1.1, 0.0);
assert_eq!(a, b);
assert!(a != c);
}
#[test]
fn approximate_equality() {
let a = Xform::identity();
let b = Xform::new(
1.000001, 0.0, 0.0, 0.0, 1.000001, 0.0, 0.0, 0.0, 1.000001, 0.0, 0.0, 0.0,
);
let c = Xform::new(
1.000003, 0.0, 0.0, 0.0, 1.000003, 0.0, 0.0, 0.0, 1.000003, 0.0, 0.0, 0.0,
);
assert!(a.aprx_eq(b, 10));
assert!(!a.aprx_eq(b, 6));
assert!(a.aprx_eq(c, 27));
assert!(!a.aprx_eq(c, 23));
}
#[test]
fn compose() {
let a = Xform::new(1.0, 3.0, 9.0, 2.0, 6.0, 2.0, 2.0, 7.0, 11.0, 1.5, 8.0, 12.0);
let b = Xform::new(
1.0, 2.0, 3.0, 5.0, 6.0, 7.0, 9.0, 10.0, 11.0, 13.0, 14.0, 15.0,
);
let c = Xform::new(
97.0, 110.0, 123.0, 50.0, 60.0, 70.0, 136.0, 156.0, 176.0, 162.5, 185.0, 207.5,
);
assert_eq!(a.compose(&b), c);
assert_eq!(a.compose_fast(&b), c);
}
}