use nalgebra::Vector3;
use std::ops::{Add, Mul, Neg, Sub};
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Copy, PartialEq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(
feature = "serde",
serde(bound(
serialize = "T: Serialize + Clone + PartialEq + std::fmt::Debug + 'static",
deserialize = "T: Deserialize<'de> + Clone + PartialEq + std::fmt::Debug + 'static"
))
)]
#[repr(transparent)]
pub struct XYZ<T>(pub(crate) Vector3<T>);
impl<T> XYZ<T> {
#[inline]
pub const fn new(x: T, y: T, z: T) -> Self {
Self(Vector3::new(x, y, z))
}
#[inline]
pub const fn from_vec3(vec: Vector3<T>) -> Self {
Self(vec)
}
#[inline]
pub const fn as_vec3(&self) -> &Vector3<T> {
&self.0
}
#[inline]
pub fn into_vec3(self) -> Vector3<T> {
self.0
}
}
impl<T: Copy> XYZ<T> {
#[inline]
pub fn x(&self) -> T {
self.0[0]
}
#[inline]
pub fn y(&self) -> T {
self.0[1]
}
#[inline]
pub fn z(&self) -> T {
self.0[2]
}
#[inline]
pub fn components(&self) -> (T, T, T) {
(self.x(), self.y(), self.z())
}
}
impl XYZ<f64> {
pub const ZERO: Self = Self::new(0.0, 0.0, 0.0);
#[inline]
pub fn magnitude(&self) -> f64 {
self.0.magnitude()
}
#[inline]
pub fn magnitude_squared(&self) -> f64 {
self.0.magnitude_squared()
}
#[inline]
pub fn dot(&self, other: &Self) -> f64 {
self.0.dot(&other.0)
}
#[inline]
pub fn cross(&self, other: &Self) -> Self {
Self(self.0.cross(&other.0))
}
#[inline]
pub fn try_normalize(&self) -> Option<Self> {
let mag = self.magnitude();
if mag.abs() < f64::EPSILON {
None
} else {
Some(Self(self.0 / mag))
}
}
#[inline]
pub fn normalize_unchecked(&self) -> Self {
Self(self.0.normalize())
}
#[inline]
pub fn scale(&self, factor: f64) -> Self {
Self(self.0 * factor)
}
#[inline]
pub fn add(&self, other: &Self) -> Self {
Self(self.0 + other.0)
}
#[inline]
pub fn sub(&self, other: &Self) -> Self {
Self(self.0 - other.0)
}
#[inline]
pub fn neg(&self) -> Self {
Self(-self.0)
}
}
impl Add for XYZ<f64> {
type Output = Self;
#[inline]
fn add(self, other: Self) -> Self::Output {
Self(self.0 + other.0)
}
}
impl Sub for XYZ<f64> {
type Output = Self;
#[inline]
fn sub(self, other: Self) -> Self::Output {
Self(self.0 - other.0)
}
}
impl Neg for XYZ<f64> {
type Output = Self;
#[inline]
fn neg(self) -> Self::Output {
Self(-self.0)
}
}
impl Mul<f64> for XYZ<f64> {
type Output = Self;
#[inline]
fn mul(self, scalar: f64) -> Self::Output {
Self(self.0 * scalar)
}
}
impl Mul<XYZ<f64>> for f64 {
type Output = XYZ<f64>;
#[inline]
fn mul(self, xyz: XYZ<f64>) -> Self::Output {
XYZ(xyz.0 * self)
}
}
use qtty::{Quantity, Unit};
impl<U: Unit> XYZ<Quantity<U>> {
pub const ZERO: Self = Self::new(
Quantity::<U>::new(0.0),
Quantity::<U>::new(0.0),
Quantity::<U>::new(0.0),
);
#[inline]
pub fn magnitude(&self) -> Quantity<U> {
let mag = Vector3::new(self.0[0].value(), self.0[1].value(), self.0[2].value()).magnitude();
Quantity::new(mag)
}
#[inline]
pub fn magnitude_squared(&self) -> f64 {
let x = self.0[0].value();
let y = self.0[1].value();
let z = self.0[2].value();
x * x + y * y + z * z
}
#[inline]
pub fn to_raw(&self) -> XYZ<f64> {
XYZ::new(self.0[0].value(), self.0[1].value(), self.0[2].value())
}
#[inline]
pub fn from_raw(raw: XYZ<f64>) -> Self {
Self::new(
Quantity::new(raw.x()),
Quantity::new(raw.y()),
Quantity::new(raw.z()),
)
}
#[inline]
pub fn add(&self, other: &Self) -> Self {
Self(self.0 + other.0)
}
#[inline]
pub fn sub(&self, other: &Self) -> Self {
Self(self.0 - other.0)
}
}
impl<U: Unit> Add for XYZ<Quantity<U>> {
type Output = Self;
#[inline]
fn add(self, other: Self) -> Self::Output {
Self(self.0 + other.0)
}
}
impl<U: Unit> Sub for XYZ<Quantity<U>> {
type Output = Self;
#[inline]
fn sub(self, other: Self) -> Self::Output {
Self(self.0 - other.0)
}
}
impl<U: Unit> Neg for XYZ<Quantity<U>> {
type Output = Self;
#[inline]
fn neg(self) -> Self::Output {
Self::new(-self.0[0], -self.0[1], -self.0[2])
}
}
impl<T: Default> Default for XYZ<T> {
fn default() -> Self {
Self(Vector3::new(T::default(), T::default(), T::default()))
}
}
impl<T: std::fmt::Display + Copy> std::fmt::Display for XYZ<T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "(")?;
std::fmt::Display::fmt(&self.x(), f)?;
write!(f, ", ")?;
std::fmt::Display::fmt(&self.y(), f)?;
write!(f, ", ")?;
std::fmt::Display::fmt(&self.z(), f)?;
write!(f, ")")
}
}
impl<T: std::fmt::LowerExp + Copy> std::fmt::LowerExp for XYZ<T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "(")?;
std::fmt::LowerExp::fmt(&self.x(), f)?;
write!(f, ", ")?;
std::fmt::LowerExp::fmt(&self.y(), f)?;
write!(f, ", ")?;
std::fmt::LowerExp::fmt(&self.z(), f)?;
write!(f, ")")
}
}
impl<T: std::fmt::UpperExp + Copy> std::fmt::UpperExp for XYZ<T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "(")?;
std::fmt::UpperExp::fmt(&self.x(), f)?;
write!(f, ", ")?;
std::fmt::UpperExp::fmt(&self.y(), f)?;
write!(f, ", ")?;
std::fmt::UpperExp::fmt(&self.z(), f)?;
write!(f, ")")
}
}
#[cfg(test)]
mod tests {
use super::*;
use qtty::{Meter, Quantity, M};
#[test]
fn test_xyz_basic_ops() {
let a = XYZ::new(1.0, 2.0, 3.0);
let b = XYZ::new(4.0, 5.0, 6.0);
let sum = a + b;
assert!((sum.x() - 5.0).abs() < f64::EPSILON);
assert!((sum.y() - 7.0).abs() < f64::EPSILON);
assert!((sum.z() - 9.0).abs() < f64::EPSILON);
let diff = b - a;
assert!((diff.x() - 3.0).abs() < f64::EPSILON);
assert!((diff.y() - 3.0).abs() < f64::EPSILON);
assert!((diff.z() - 3.0).abs() < f64::EPSILON);
let scaled = a.scale(2.0);
assert!((scaled.x() - 2.0).abs() < f64::EPSILON);
assert!((scaled.y() - 4.0).abs() < f64::EPSILON);
assert!((scaled.z() - 6.0).abs() < f64::EPSILON);
}
#[test]
fn test_xyz_magnitude_and_normalize() {
let v = XYZ::new(3.0, 4.0, 0.0);
assert!((v.magnitude() - 5.0).abs() < f64::EPSILON);
let n = v.try_normalize().unwrap();
assert!((n.magnitude() - 1.0).abs() < f64::EPSILON);
assert!((n.x() - 0.6).abs() < f64::EPSILON);
assert!((n.y() - 0.8).abs() < f64::EPSILON);
}
#[test]
fn test_xyz_dot_and_cross() {
let a = XYZ::new(1.0, 0.0, 0.0);
let b = XYZ::new(0.0, 1.0, 0.0);
assert!(a.dot(&b).abs() < f64::EPSILON);
let c = a.cross(&b);
assert!((c.x()).abs() < f64::EPSILON);
assert!((c.y()).abs() < f64::EPSILON);
assert!((c.z() - 1.0).abs() < f64::EPSILON);
}
#[test]
fn test_xyz_zero_normalize() {
let zero = XYZ::<f64>::ZERO;
assert!(zero.try_normalize().is_none());
}
#[test]
fn test_xyz_quantity_ops() {
let a = XYZ::new(3.0 * M, 4.0 * M, 0.0 * M);
let b = XYZ::new(1.0 * M, 2.0 * M, 3.0 * M);
let sum = a + b;
assert!((sum.x().value() - 4.0).abs() < f64::EPSILON);
assert!((sum.y().value() - 6.0).abs() < f64::EPSILON);
let diff = a - b;
assert!((diff.x().value() - 2.0).abs() < f64::EPSILON);
assert!((diff.y().value() - 2.0).abs() < f64::EPSILON);
let mag = a.magnitude();
assert!((mag.value() - 5.0).abs() < f64::EPSILON);
let mag_sq = a.magnitude_squared();
assert!((mag_sq - 25.0).abs() < f64::EPSILON);
let raw = a.to_raw();
assert!((raw.x() - 3.0).abs() < f64::EPSILON);
let back = XYZ::<Quantity<Meter>>::from_raw(raw);
assert!((back.y().value() - 4.0).abs() < f64::EPSILON);
let neg = -b;
assert!((neg.z().value() + 3.0).abs() < f64::EPSILON);
}
#[test]
fn test_xyz_vec3_roundtrip() {
let vec3: nalgebra::Vector3<f64> = nalgebra::Vector3::new(1.0, 2.0, 3.0);
let xyz: XYZ<f64> = XYZ::from_vec3(vec3);
assert!((xyz.x() - 1.0).abs() < f64::EPSILON);
let as_vec3 = xyz.as_vec3();
assert!((as_vec3[2] - 3.0).abs() < f64::EPSILON);
let into_vec3 = xyz.into_vec3();
assert!((into_vec3[1] - 2.0).abs() < f64::EPSILON);
}
#[test]
fn test_xyz_f64_helpers_and_traits() {
let a = XYZ::new(1.0, -2.0, 3.5);
let b = XYZ::new(-4.0, 5.0, 6.0);
let mag_sq = a.magnitude_squared();
assert!((mag_sq - (1.0 + 4.0 + 12.25)).abs() < f64::EPSILON);
let sum = XYZ::<f64>::add(&a, &b);
assert!((sum.x() + 3.0).abs() < f64::EPSILON);
assert!((sum.y() - 3.0).abs() < f64::EPSILON);
assert!((sum.z() - 9.5).abs() < f64::EPSILON);
let diff = XYZ::<f64>::sub(&a, &b);
assert!((diff.x() - 5.0).abs() < f64::EPSILON);
assert!((diff.y() + 7.0).abs() < f64::EPSILON);
assert!((diff.z() - -2.5).abs() < f64::EPSILON);
let (x, y, z) = a.components();
assert!((x - 1.0).abs() < f64::EPSILON);
assert!((y + 2.0).abs() < f64::EPSILON);
assert!((z - 3.5).abs() < f64::EPSILON);
let default_xyz: XYZ<f64> = Default::default();
assert!((default_xyz.x()).abs() < f64::EPSILON);
assert!((default_xyz.y()).abs() < f64::EPSILON);
assert!((default_xyz.z()).abs() < f64::EPSILON);
let display = a.to_string();
assert_eq!(display, "(1, -2, 3.5)");
let display_prec = format!("{:.2}", a);
assert_eq!(display_prec, "(1.00, -2.00, 3.50)");
let display_exp = format!("{:.2e}", a);
assert_eq!(display_exp, "(1.00e0, -2.00e0, 3.50e0)");
let neg = -a;
assert!((neg.x() + 1.0).abs() < f64::EPSILON);
assert!((neg.y() - 2.0).abs() < f64::EPSILON);
assert!((neg.z() + 3.5).abs() < f64::EPSILON);
let scaled = a * 2.0;
assert!((scaled.x() - 2.0).abs() < f64::EPSILON);
let scaled2 = 2.0 * a;
assert!((scaled2.y() + 4.0).abs() < f64::EPSILON);
}
#[test]
fn test_xyz_quantity_helpers() {
let a = XYZ::new(1.0 * M, 2.0 * M, 3.0 * M);
let b = XYZ::new(-1.0 * M, 0.5 * M, 4.0 * M);
let sum = XYZ::<Quantity<Meter>>::add(&a, &b);
assert!((sum.x().value()).abs() < f64::EPSILON);
assert!((sum.y().value() - 2.5).abs() < f64::EPSILON);
let diff = XYZ::<Quantity<Meter>>::sub(&a, &b);
assert!((diff.x().value() - 2.0).abs() < f64::EPSILON);
assert!((diff.z().value() - -1.0).abs() < f64::EPSILON);
}
}