use super::generated::types::{Flector, Line, Motor, Plane, Point};
use crate::scalar::Float;
use crate::specialized::euclidean::dim3::Vector as EuclideanVector;
impl<T: Float> Point<T> {
#[inline]
pub fn from_cartesian(x: T, y: T, z: T) -> Self {
Self::new(x, y, z, T::one())
}
#[inline]
pub fn ideal(dx: T, dy: T, dz: T) -> Self {
Self::new(dx, dy, dz, T::zero())
}
#[inline]
pub fn origin() -> Self {
Self::from_cartesian(T::zero(), T::zero(), T::zero())
}
#[inline]
pub fn cartesian_x(&self) -> T {
self.x() / self.w()
}
#[inline]
pub fn cartesian_y(&self) -> T {
self.y() / self.w()
}
#[inline]
pub fn cartesian_z(&self) -> T {
self.z() / self.w()
}
#[inline]
pub fn is_ideal(&self, epsilon: T) -> bool {
self.w().abs() < epsilon
}
#[inline]
pub fn is_finite(&self, epsilon: T) -> bool {
self.w().abs() >= epsilon
}
pub fn unitize(&self) -> Option<Self> {
if self.w().abs() < T::epsilon() {
None
} else {
Some(Self::new(
self.x() / self.w(),
self.y() / self.w(),
self.z() / self.w(),
T::one(),
))
}
}
#[inline]
pub fn to_cartesian(&self) -> Option<(T, T, T)> {
if self.w().abs() < T::epsilon() {
None
} else {
Some((
self.x() / self.w(),
self.y() / self.w(),
self.z() / self.w(),
))
}
}
#[inline]
pub fn attitude(&self) -> T {
self.w()
}
#[inline]
pub fn bulk_norm_squared(&self) -> T {
self.x() * self.x() + self.y() * self.y() + self.z() * self.z()
}
#[inline]
pub fn bulk_norm(&self) -> T {
self.bulk_norm_squared().sqrt()
}
#[inline]
pub fn weight_norm(&self) -> T {
self.w().abs()
}
#[inline]
pub fn geometric_norm(&self) -> T {
let weight = self.weight_norm();
if weight < T::epsilon() {
T::zero()
} else {
self.bulk_norm() / weight
}
}
pub fn distance(&self, other: &Point<T>) -> T {
self.distance_squared(other).sqrt()
}
pub fn distance_squared(&self, other: &Point<T>) -> T {
let dx = self.x() / self.w() - other.x() / other.w();
let dy = self.y() / self.w() - other.y() / other.w();
let dz = self.z() / self.w() - other.z() / other.w();
dx * dx + dy * dy + dz * dz
}
pub fn midpoint(&self, other: &Point<T>) -> Point<T> {
let two = T::TWO;
Point::new(
(self.x() * other.w() + other.x() * self.w()) / two,
(self.y() * other.w() + other.y() * self.w()) / two,
(self.z() * other.w() + other.z() * self.w()) / two,
self.w() * other.w(),
)
}
#[inline]
pub fn dot(&self, other: &Point<T>) -> T {
self.x() * other.x() + self.y() * other.y() + self.z() * other.z()
}
#[inline]
pub fn left_contract_line(&self, line: &Line<T>) -> Plane<T> {
crate::ops::Wedge::wedge(self, line)
}
}
impl<T: Float> Line<T> {
pub fn from_point_and_direction(point: &Point<T>, direction: &EuclideanVector<T>) -> Self {
let ideal = Point::ideal(direction.x(), direction.y(), direction.z());
crate::ops::Wedge::wedge(point, &ideal)
}
#[inline]
pub fn from_plucker(direction: &EuclideanVector<T>, moment: &EuclideanVector<T>) -> Self {
Self::new_unchecked(
moment.z(), -moment.y(), direction.x(), moment.x(), direction.y(), direction.z(), )
}
#[inline]
pub fn x_axis() -> Self {
Self::new_unchecked(
T::zero(), T::zero(), T::one(), T::zero(), T::zero(), T::zero(), )
}
#[inline]
pub fn y_axis() -> Self {
Self::new_unchecked(
T::zero(), T::zero(), T::zero(), T::zero(), T::one(), T::zero(), )
}
#[inline]
pub fn z_axis() -> Self {
Self::new_unchecked(
T::zero(), T::zero(), T::zero(), T::zero(), T::zero(), T::one(), )
}
#[inline]
pub fn direction(&self) -> EuclideanVector<T> {
EuclideanVector::new(self.dir_x(), self.dir_y(), self.dir_z())
}
#[inline]
pub fn moment(&self) -> EuclideanVector<T> {
EuclideanVector::new(self.moment_x(), -self.moment_y(), self.moment_z())
}
#[inline]
pub fn weight_norm(&self) -> T {
self.direction().norm()
}
#[inline]
pub fn bulk_norm(&self) -> T {
self.moment().norm()
}
pub fn unitized(&self) -> Self {
let wn = self.weight_norm();
if wn < T::epsilon() {
return *self;
}
Self::new_unchecked(
self.moment_z() / wn,
self.moment_y() / wn,
self.dir_x() / wn,
self.moment_x() / wn,
self.dir_y() / wn,
self.dir_z() / wn,
)
}
#[inline]
pub fn plucker_residual(&self) -> T {
let d = self.direction();
let m = self.moment();
d.x() * m.x() + d.y() * m.y() + d.z() * m.z()
}
#[inline]
pub fn satisfies_plucker_condition(&self, tolerance: T) -> bool {
self.plucker_residual().abs() < tolerance
}
pub fn through_origin(&self) -> bool {
self.moment().norm() < T::epsilon()
}
pub fn is_parallel(&self, other: &Line<T>) -> bool {
self.direction().cross(other.direction()).norm() < T::epsilon()
}
#[inline]
pub fn dot(&self, other: &Line<T>) -> T {
self.moment_x() * other.moment_x()
+ self.moment_y() * other.moment_y()
+ self.moment_z() * other.moment_z()
+ self.dir_x() * other.dir_x()
+ self.dir_y() * other.dir_y()
+ self.dir_z() * other.dir_z()
}
#[inline]
pub fn geometric_norm(&self) -> T {
let weight = self.weight_norm();
if weight < T::epsilon() {
T::zero()
} else {
self.bulk_norm() / weight
}
}
pub fn angle(&self, other: &Line<T>) -> T {
let d1 = self.direction().normalized();
let d2 = other.direction().normalized();
let cos_angle = d1.dot(d2).abs().min(T::one());
cos_angle.acos()
}
pub fn distance(&self, other: &Line<T>) -> T {
let d1 = self.direction();
let d2 = other.direction();
let m1 = self.moment();
let m2 = other.moment();
let cross = d1.cross(d2);
let cross_norm = cross.norm();
if cross_norm < T::epsilon() {
let l1_unit = self.unitized();
let l2_unit = other.unitized();
(l1_unit.moment() - l2_unit.moment()).norm()
} else {
let reciprocal = d1.dot(m2) + d2.dot(m1);
reciprocal.abs() / cross_norm
}
}
#[inline]
pub fn is_zero(&self, epsilon: T) -> bool {
self.weight_norm_squared() < epsilon * epsilon
}
#[inline]
pub fn weight_norm_squared(&self) -> T {
self.moment_x() * self.moment_x()
+ self.moment_y() * self.moment_y()
+ self.moment_z() * self.moment_z()
}
#[inline]
pub fn plucker_inner(&self, other: &Line<T>) -> T {
self.moment_x() * other.dir_x()
+ self.moment_y() * other.dir_y()
+ self.moment_z() * other.dir_z()
+ other.moment_x() * self.dir_x()
+ other.moment_y() * self.dir_y()
+ other.moment_z() * self.dir_z()
}
pub fn distance_to_point(&self, p: &Point<T>) -> T {
let d1 = self.moment_x();
let d2 = self.moment_y();
let d3 = self.moment_z();
let m1 = self.dir_x();
let m2 = self.dir_y();
let m3 = self.dir_z();
let px = p.x();
let py = p.y();
let pz = p.z();
let pw = p.w();
let cx = d2 * pz - d3 * py;
let cy = d3 * px - d1 * pz;
let cz = d1 * py - d2 * px;
let nx = cx + m1 * pw;
let ny = cy + m2 * pw;
let nz = cz + m3 * pw;
let dir_norm = self.weight_norm();
if dir_norm < T::epsilon() {
return T::zero();
}
(nx * nx + ny * ny + nz * nz).sqrt() / (dir_norm * pw.abs())
}
pub fn closest_point(&self, p: &Point<T>) -> Point<T> {
let d = self.direction();
let m = self.moment();
let d_sq = d.x() * d.x() + d.y() * d.y() + d.z() * d.z();
if d_sq < T::epsilon() {
return Point::origin();
}
let line_pt_x = (d.y() * m.z() - d.z() * m.y()) / d_sq;
let line_pt_y = (d.z() * m.x() - d.x() * m.z()) / d_sq;
let line_pt_z = (d.x() * m.y() - d.y() * m.x()) / d_sq;
let px = p.x() - line_pt_x;
let py = p.y() - line_pt_y;
let pz = p.z() - line_pt_z;
let t = (px * d.x() + py * d.y() + pz * d.z()) / d_sq;
Point::from_cartesian(
line_pt_x + t * d.x(),
line_pt_y + t * d.y(),
line_pt_z + t * d.z(),
)
}
}
impl<T: Float> Plane<T> {
pub fn from_normal_and_distance(cart_nx: T, cart_ny: T, cart_nz: T, distance: T) -> Self {
Self::new(distance, cart_nz, -cart_ny, cart_nx)
}
#[inline]
pub fn xy() -> Self {
Self::new(T::zero(), T::one(), T::zero(), T::zero())
}
#[inline]
pub fn xz() -> Self {
Self::new(T::zero(), T::zero(), -T::one(), T::zero())
}
#[inline]
pub fn yz() -> Self {
Self::new(T::zero(), T::zero(), T::zero(), T::one())
}
#[inline]
pub fn normal(&self) -> EuclideanVector<T> {
EuclideanVector::new(self.nx(), -self.ny(), self.nz())
}
#[inline]
pub fn distance_from_origin(&self) -> T {
self.dist()
}
#[inline]
pub fn weight_norm(&self) -> T {
self.normal().norm()
}
#[inline]
pub fn bulk_norm(&self) -> T {
self.dist().abs()
}
pub fn unitized(&self) -> Self {
let wn = self.weight_norm();
if wn < T::epsilon() {
return *self;
}
Self::new(
self.dist() / wn,
self.nz() / wn,
self.ny() / wn,
self.nx() / wn,
)
}
#[inline]
pub fn dot(&self, other: &Plane<T>) -> T {
self.nx() * other.nx() + self.ny() * other.ny() + self.nz() * other.nz()
}
#[inline]
pub fn attitude(&self) -> T {
self.dist()
}
#[inline]
pub fn geometric_norm(&self) -> T {
let weight = self.weight_norm();
if weight < T::epsilon() {
T::zero()
} else {
self.bulk_norm() / weight
}
}
pub fn angle(&self, other: &Plane<T>) -> T {
let n1 = self.normal().normalized();
let n2 = other.normal().normalized();
let cos_angle = n1.dot(n2).abs().min(T::one());
cos_angle.acos()
}
pub fn angle_to_line(&self, line: &Line<T>) -> T {
let n = self.normal().normalized();
let d = line.direction().normalized();
let sin_angle = n.dot(d).abs().min(T::one());
sin_angle.asin()
}
pub fn signed_distance(&self, point: &Point<T>) -> T {
let p = self.unitized();
(p.nx() * point.x() + p.ny() * point.y() + p.nz() * point.z() + p.dist() * point.w())
/ point.w()
}
pub fn project_point(&self, point: &Point<T>) -> Point<T> {
let dist = self.signed_distance(point);
let n = self.normal().normalized();
Point::new(
point.x() - dist * n.x() * point.w(),
point.y() - dist * n.y() * point.w(),
point.z() - dist * n.z() * point.w(),
point.w(),
)
}
pub fn project_line(&self, line: &Line<T>) -> Line<T> {
let n = self.normal();
let d = line.direction();
let dot = n.dot(d);
let n_norm_sq = n.dot(n);
let proj_d = if n_norm_sq < T::epsilon() {
d
} else {
EuclideanVector::new(
d.x() - dot * n.x() / n_norm_sq,
d.y() - dot * n.y() / n_norm_sq,
d.z() - dot * n.z() / n_norm_sq,
)
};
let m = line.moment();
let d_norm_sq = d.dot(d);
let closest_point = if d_norm_sq < T::epsilon() {
Point::origin()
} else {
let cross = d.cross(m);
Point::new(
cross.x() / d_norm_sq,
cross.y() / d_norm_sq,
cross.z() / d_norm_sq,
T::one(),
)
};
let proj_point = self.project_point(&closest_point);
Line::from_point_and_direction(&proj_point, &proj_d)
}
}
impl<T: Float> Motor<T> {
#[inline]
pub fn identity() -> Self {
Self::new_unchecked(
T::zero(),
T::zero(),
T::zero(),
T::zero(),
T::zero(),
T::zero(),
T::zero(),
T::one(), )
}
pub fn from_translation(dx: T, dy: T, dz: T) -> Self {
let half = T::one() / T::TWO;
Self::new_unchecked(
T::zero(), dz * half, -dy * half, T::zero(), dx * half, T::zero(), T::zero(), T::one(), )
}
pub fn from_rotation_x(angle: T) -> Self {
let half = angle / T::TWO;
Self::new_unchecked(
T::zero(), T::zero(), T::zero(), -half.sin(), T::zero(), T::zero(), T::zero(), half.cos(), )
}
pub fn from_rotation_y(angle: T) -> Self {
let half = angle / T::TWO;
Self::new_unchecked(
T::zero(), T::zero(), T::zero(), T::zero(), T::zero(), -half.sin(), T::zero(), half.cos(), )
}
pub fn from_rotation_z(angle: T) -> Self {
let half = angle / T::TWO;
Self::new_unchecked(
T::zero(), T::zero(), T::zero(), T::zero(), T::zero(), T::zero(), -half.sin(), half.cos(), )
}
pub fn from_axis_angle(axis: &EuclideanVector<T>, angle: T) -> Self {
let half = angle / T::TWO;
let (sin_half, cos_half) = (half.sin(), half.cos());
let axis_norm = axis.normalized();
Self::new_unchecked(
T::zero(), T::zero(), T::zero(), -sin_half * axis_norm.x(), T::zero(), -sin_half * axis_norm.y(), -sin_half * axis_norm.z(), cos_half, )
}
pub fn from_line(line: &Line<T>, angle: T, distance: T) -> Self {
let line_unit = line.unitized();
let half_angle = angle / T::TWO;
let half_dist = distance / T::TWO;
let (sin_a, cos_a) = (half_angle.sin(), half_angle.cos());
let d = line_unit.direction();
let m = line_unit.moment();
Self::new_unchecked(
T::zero(), sin_a * m.z() + half_dist * cos_a * d.z(), -(sin_a * m.y() + half_dist * cos_a * d.y()), -sin_a * d.x(), sin_a * m.x() + half_dist * cos_a * d.x(), -sin_a * d.y(), -sin_a * d.z(), cos_a, )
}
pub fn unitized(&self) -> Self {
use crate::norm::DegenerateNormed;
let bn = self.bulk_norm();
if bn < T::epsilon() {
return *self;
}
Self::new_unchecked(
self.s() / bn,
self.tz() / bn,
self.ty() / bn,
self.tx() / bn,
self.rx() / bn,
self.ry() / bn,
self.rz() / bn,
self.ps() / bn,
)
}
#[inline]
pub fn is_unitized(&self, tolerance: T) -> bool {
use crate::norm::DegenerateNormed;
(self.bulk_norm_squared() - T::one()).abs() < tolerance
}
#[inline]
pub fn geometric_constraint_residual(&self) -> T {
self.s() * self.ps() + self.rx() * self.tx() + self.ty() * self.ry() + self.tz() * self.rz()
}
#[inline]
pub fn satisfies_geometric_constraint(&self, tolerance: T) -> bool {
self.geometric_constraint_residual().abs() < tolerance
}
pub fn rotation_angle(&self) -> T {
let cos_half = self.s().min(T::one()).max(-T::one());
cos_half.acos() * T::TWO
}
pub fn translation(&self) -> EuclideanVector<T> {
EuclideanVector::new(T::TWO * self.tz(), -T::TWO * self.ty(), T::TWO * self.rx())
}
}
impl<T: Float> Flector<T> {
pub fn from_plane(plane: &Plane<T>) -> Self {
let p = plane.unitized();
Self::new_unchecked(
T::zero(),
T::zero(),
T::zero(),
T::zero(),
p.dist(),
p.nz(),
p.ny(),
p.nx(),
)
}
pub fn from_plane_through_origin(cart_nx: T, cart_ny: T, cart_nz: T) -> Self {
let norm = (cart_nx * cart_nx + cart_ny * cart_ny + cart_nz * cart_nz).sqrt();
Self::new_unchecked(
T::zero(),
T::zero(),
T::zero(),
T::zero(),
T::zero(), cart_nz / norm, -cart_ny / norm, cart_nx / norm, )
}
#[inline]
pub fn reflect_xy() -> Self {
Self::from_plane(&Plane::xy())
}
#[inline]
pub fn reflect_xz() -> Self {
Self::from_plane(&Plane::xz())
}
#[inline]
pub fn reflect_yz() -> Self {
Self::from_plane(&Plane::yz())
}
#[inline]
pub fn point_part(&self) -> Point<T> {
Point::new(self.px(), self.py(), self.pz(), self.pw())
}
#[inline]
pub fn plane_part(&self) -> Plane<T> {
Plane::new(self.dist(), self.nz(), self.ny(), self.nx())
}
#[inline]
pub fn is_pure_reflection(&self) -> bool {
let pt = self.point_part();
pt.bulk_norm() < T::epsilon() && pt.weight_norm() < T::epsilon()
}
pub fn unitized(&self) -> Self {
use crate::norm::DegenerateNormed;
let bn = self.bulk_norm();
if bn < T::epsilon() {
return *self;
}
Self::new_unchecked(
self.px() / bn,
self.py() / bn,
self.pz() / bn,
self.pw() / bn,
self.dist() / bn,
self.nz() / bn,
self.ny() / bn,
self.nx() / bn,
)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::ops::Transform;
use crate::test_utils::RELATIVE_EQ_EPS;
use approx::relative_eq;
#[test]
fn motor_identity_preserves_point() {
let p = Point::<f64>::from_cartesian(3.0, 4.0, 5.0);
let m = Motor::<f64>::identity();
eprintln!(
"Identity motor: s={}, bx={}, by={}, bz={}, tx={}, ty={}, tz={}, ps={}",
m.s(),
m.rx(),
m.ty(),
m.tz(),
m.tx(),
m.ry(),
m.rz(),
m.ps()
);
eprintln!("Input point: ({}, {}, {}, {})", p.x(), p.y(), p.z(), p.w());
let result = m.transform(&p);
eprintln!(
"Output point: ({}, {}, {}, {})",
result.x(),
result.y(),
result.z(),
result.w()
);
assert!(relative_eq!(
result.x(),
p.x(),
epsilon = RELATIVE_EQ_EPS,
max_relative = RELATIVE_EQ_EPS
));
assert!(relative_eq!(
result.y(),
p.y(),
epsilon = RELATIVE_EQ_EPS,
max_relative = RELATIVE_EQ_EPS
));
assert!(relative_eq!(
result.z(),
p.z(),
epsilon = RELATIVE_EQ_EPS,
max_relative = RELATIVE_EQ_EPS
));
assert!(relative_eq!(
result.w(),
p.w(),
epsilon = RELATIVE_EQ_EPS,
max_relative = RELATIVE_EQ_EPS
));
}
#[test]
fn motor_translation_x() {
let origin = Point::<f64>::origin();
let t = Motor::<f64>::from_translation(2.0, 0.0, 0.0);
eprintln!(
"Motor T: s={}, bx={}, by={}, bz={}, tx={}, ty={}, tz={}, ps={}",
t.s(),
t.rx(),
t.ty(),
t.tz(),
t.tx(),
t.ry(),
t.rz(),
t.ps()
);
eprintln!(
"Origin: ({}, {}, {}, {})",
origin.x(),
origin.y(),
origin.z(),
origin.w()
);
let result = t.transform(&origin);
eprintln!(
"Translated: ({}, {}, {}, {})",
result.x(),
result.y(),
result.z(),
result.w()
);
assert!(relative_eq!(
result.x(),
2.0,
epsilon = RELATIVE_EQ_EPS,
max_relative = RELATIVE_EQ_EPS
));
assert!(relative_eq!(
result.y(),
0.0,
epsilon = RELATIVE_EQ_EPS,
max_relative = RELATIVE_EQ_EPS
));
assert!(relative_eq!(
result.z(),
0.0,
epsilon = RELATIVE_EQ_EPS,
max_relative = RELATIVE_EQ_EPS
));
assert!(relative_eq!(
result.w(),
1.0,
epsilon = RELATIVE_EQ_EPS,
max_relative = RELATIVE_EQ_EPS
));
}
#[test]
fn motor_translation_y() {
let origin = Point::<f64>::origin();
let t = Motor::<f64>::from_translation(0.0, -19.26, 0.0);
eprintln!(
"Motor T: s={}, bx={}, by={}, bz={}, tx={}, ty={}, tz={}, ps={}",
t.s(),
t.rx(),
t.ty(),
t.tz(),
t.tx(),
t.ry(),
t.rz(),
t.ps()
);
eprintln!(
"Origin: ({}, {}, {}, {})",
origin.x(),
origin.y(),
origin.z(),
origin.w()
);
let result = t.transform(&origin);
eprintln!(
"Translated: ({}, {}, {}, {})",
result.x(),
result.y(),
result.z(),
result.w()
);
assert!(relative_eq!(
result.x(),
0.0,
epsilon = RELATIVE_EQ_EPS,
max_relative = RELATIVE_EQ_EPS
));
assert!(relative_eq!(
result.y(),
-19.26,
epsilon = RELATIVE_EQ_EPS,
max_relative = RELATIVE_EQ_EPS
));
assert!(relative_eq!(
result.z(),
0.0,
epsilon = RELATIVE_EQ_EPS,
max_relative = RELATIVE_EQ_EPS
));
}
#[test]
fn motor_translation_z() {
let origin = Point::<f64>::origin();
let t = Motor::<f64>::from_translation(0.0, 0.0, 5.0);
eprintln!(
"Motor T: s={}, bx={}, by={}, bz={}, tx={}, ty={}, tz={}, ps={}",
t.s(),
t.rx(),
t.ty(),
t.tz(),
t.tx(),
t.ry(),
t.rz(),
t.ps()
);
let result = t.transform(&origin);
eprintln!(
"Translated: ({}, {}, {}, {})",
result.x(),
result.y(),
result.z(),
result.w()
);
assert!(relative_eq!(
result.x(),
0.0,
epsilon = RELATIVE_EQ_EPS,
max_relative = RELATIVE_EQ_EPS
));
assert!(relative_eq!(
result.y(),
0.0,
epsilon = RELATIVE_EQ_EPS,
max_relative = RELATIVE_EQ_EPS
));
assert!(relative_eq!(
result.z(),
5.0,
epsilon = RELATIVE_EQ_EPS,
max_relative = RELATIVE_EQ_EPS
));
}
#[test]
fn flector_reflect_xy_plane() {
let f = Flector::<f64>::reflect_xy();
let p = Point::<f64>::from_cartesian(1.0, 2.0, 3.0);
eprintln!(
"Flector: px={}, py={}, pz={}, pw={}, nx={}, ny={}, nz={}, dist={}",
f.px(),
f.py(),
f.pz(),
f.pw(),
f.nx(),
f.ny(),
f.nz(),
f.dist()
);
eprintln!("Input point: ({}, {}, {}, {})", p.x(), p.y(), p.z(), p.w());
let result = f.transform(&p);
eprintln!(
"Output point: ({}, {}, {}, {})",
result.x(),
result.y(),
result.z(),
result.w()
);
assert!(relative_eq!(
result.cartesian_x(),
1.0,
epsilon = RELATIVE_EQ_EPS,
max_relative = RELATIVE_EQ_EPS
));
assert!(relative_eq!(
result.cartesian_y(),
2.0,
epsilon = RELATIVE_EQ_EPS,
max_relative = RELATIVE_EQ_EPS
));
assert!(relative_eq!(
result.cartesian_z(),
-3.0,
epsilon = RELATIVE_EQ_EPS,
max_relative = RELATIVE_EQ_EPS
));
}
#[test]
fn flector_reflect_yz_plane() {
let f = Flector::<f64>::reflect_yz();
let p = Point::<f64>::from_cartesian(1.0, 2.0, 3.0);
let result = f.transform(&p);
assert!(relative_eq!(
result.cartesian_x(),
-1.0,
epsilon = RELATIVE_EQ_EPS,
max_relative = RELATIVE_EQ_EPS
));
assert!(relative_eq!(
result.cartesian_y(),
2.0,
epsilon = RELATIVE_EQ_EPS,
max_relative = RELATIVE_EQ_EPS
));
assert!(relative_eq!(
result.cartesian_z(),
3.0,
epsilon = RELATIVE_EQ_EPS,
max_relative = RELATIVE_EQ_EPS
));
}
#[test]
fn flector_reflect_xz_plane() {
let f = Flector::<f64>::reflect_xz();
let p = Point::<f64>::from_cartesian(1.0, 2.0, 3.0);
let result = f.transform(&p);
assert!(relative_eq!(
result.cartesian_x(),
1.0,
epsilon = RELATIVE_EQ_EPS,
max_relative = RELATIVE_EQ_EPS
));
assert!(relative_eq!(
result.cartesian_y(),
-2.0,
epsilon = RELATIVE_EQ_EPS,
max_relative = RELATIVE_EQ_EPS
));
assert!(relative_eq!(
result.cartesian_z(),
3.0,
epsilon = RELATIVE_EQ_EPS,
max_relative = RELATIVE_EQ_EPS
));
}
}