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))]
#[repr(transparent)]
pub struct XYZ<T>(pub(crate) [T; 3]);
impl<T> XYZ<T> {
#[inline]
pub const fn new(x: T, y: T, z: T) -> Self {
Self([x, y, z])
}
#[inline]
pub const fn from_array(arr: [T; 3]) -> Self {
Self(arr)
}
#[inline]
pub const fn as_array(&self) -> &[T; 3] {
&self.0
}
#[inline]
pub fn into_array(self) -> [T; 3] {
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 {
let [x, y, z] = self.0;
(x * x + y * y + z * z).sqrt()
}
#[inline]
pub fn magnitude_squared(&self) -> f64 {
let [x, y, z] = self.0;
x * x + y * y + z * z
}
#[inline]
pub fn dot(&self, other: &Self) -> f64 {
self.0[0] * other.0[0] + self.0[1] * other.0[1] + self.0[2] * other.0[2]
}
#[inline]
pub fn cross(&self, other: &Self) -> Self {
let [ax, ay, az] = self.0;
let [bx, by, bz] = other.0;
Self([ay * bz - az * by, az * bx - ax * bz, ax * by - ay * bx])
}
#[inline]
pub fn try_normalize(&self) -> Option<Self> {
let mag = self.magnitude();
if mag.abs() < f64::EPSILON {
None
} else {
Some(Self([self.0[0] / mag, self.0[1] / mag, self.0[2] / mag]))
}
}
#[inline]
pub fn normalize_unchecked(&self) -> Self {
let mag = self.magnitude();
Self([self.0[0] / mag, self.0[1] / mag, self.0[2] / mag])
}
#[inline]
pub fn scale(&self, factor: f64) -> Self {
Self([self.0[0] * factor, self.0[1] * factor, self.0[2] * factor])
}
#[inline]
pub fn add(&self, other: &Self) -> Self {
Self([
self.0[0] + other.0[0],
self.0[1] + other.0[1],
self.0[2] + other.0[2],
])
}
#[inline]
pub fn sub(&self, other: &Self) -> Self {
Self([
self.0[0] - other.0[0],
self.0[1] - other.0[1],
self.0[2] - other.0[2],
])
}
#[inline]
pub fn neg(&self) -> Self {
Self([-self.0[0], -self.0[1], -self.0[2]])
}
}
impl Add for XYZ<f64> {
type Output = Self;
#[inline]
fn add(self, other: Self) -> Self::Output {
Self([
self.0[0] + other.0[0],
self.0[1] + other.0[1],
self.0[2] + other.0[2],
])
}
}
forward_ref_binop! { impl Add, add for XYZ<f64>, XYZ<f64> }
impl Sub for XYZ<f64> {
type Output = Self;
#[inline]
fn sub(self, other: Self) -> Self::Output {
Self([
self.0[0] - other.0[0],
self.0[1] - other.0[1],
self.0[2] - other.0[2],
])
}
}
forward_ref_binop! { impl Sub, sub for XYZ<f64>, XYZ<f64> }
impl Neg for XYZ<f64> {
type Output = Self;
#[inline]
fn neg(self) -> Self::Output {
Self([-self.0[0], -self.0[1], -self.0[2]])
}
}
forward_ref_unop! { impl Neg, neg for XYZ<f64> }
impl Mul<f64> for XYZ<f64> {
type Output = Self;
#[inline]
fn mul(self, scalar: f64) -> Self::Output {
Self([self.0[0] * scalar, self.0[1] * scalar, self.0[2] * scalar])
}
}
forward_ref_binop! { impl Mul, mul for XYZ<f64>, f64 }
impl Mul<XYZ<f64>> for f64 {
type Output = XYZ<f64>;
#[inline]
fn mul(self, xyz: XYZ<f64>) -> Self::Output {
XYZ([xyz.0[0] * self, xyz.0[1] * self, xyz.0[2] * self])
}
}
forward_ref_binop! { impl Mul, mul for f64, XYZ<f64> }
use qtty::{Quantity, Unit, UnitMul};
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 x = self.0[0].value();
let y = self.0[1].value();
let z = self.0[2].value();
Quantity::new((x * x + y * y + z * z).sqrt())
}
#[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[0] + other.0[0],
self.0[1] + other.0[1],
self.0[2] + other.0[2],
])
}
#[inline]
pub fn sub(&self, other: &Self) -> Self {
Self([
self.0[0] - other.0[0],
self.0[1] - other.0[1],
self.0[2] - other.0[2],
])
}
}
impl<U: Unit + UnitMul<U>> XYZ<Quantity<U>> {
#[inline]
pub fn magnitude_squared(&self) -> Quantity<<U as UnitMul<U>>::Output> {
let x = self.0[0].value();
let y = self.0[1].value();
let z = self.0[2].value();
Quantity::new(x * x + y * y + z * z)
}
}
impl<U: Unit> Add for XYZ<Quantity<U>> {
type Output = Self;
#[inline]
fn add(self, other: Self) -> Self::Output {
Self([
self.0[0] + other.0[0],
self.0[1] + other.0[1],
self.0[2] + other.0[2],
])
}
}
forward_ref_binop! { impl[U: Unit] Add, add for XYZ<Quantity<U>>, XYZ<Quantity<U>> }
impl<U: Unit> Sub for XYZ<Quantity<U>> {
type Output = Self;
#[inline]
fn sub(self, other: Self) -> Self::Output {
Self([
self.0[0] - other.0[0],
self.0[1] - other.0[1],
self.0[2] - other.0[2],
])
}
}
forward_ref_binop! { impl[U: Unit] Sub, sub for XYZ<Quantity<U>>, XYZ<Quantity<U>> }
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])
}
}
forward_ref_unop! { impl[U: Unit] Neg, neg for XYZ<Quantity<U>> }
impl<T: Default> Default for XYZ<T> {
fn default() -> Self {
Self([T::default(), T::default(), T::default()])
}
}
impl_quantity_fmt_triplet! {
impl[T] for XYZ<T>
where { T: Copy, },
fmt_each: { T, },
|this, f, FmtOne| {
write!(f, "(")?;
FmtOne::fmt(&this.x(), f)?;
write!(f, ", ")?;
FmtOne::fmt(&this.y(), f)?;
write!(f, ", ")?;
FmtOne::fmt(&this.z(), f)?;
write!(f, ")")
}
}
#[cfg(test)]
mod tests {
use super::*;
use qtty::units::Meter;
use qtty::{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.value() - 25.0).abs() < f64::EPSILON);
let mag_sq_typed: qtty::Quantity<qtty::Prod<Meter, Meter>> = a.magnitude_squared();
assert!((mag_sq_typed.value() - 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_array_roundtrip() {
let arr: [f64; 3] = [1.0, 2.0, 3.0];
let xyz: XYZ<f64> = XYZ::from_array(arr);
assert!((xyz.x() - 1.0).abs() < f64::EPSILON);
let as_arr = xyz.as_array();
assert!((as_arr[2] - 3.0).abs() < f64::EPSILON);
let into_arr = xyz.into_array();
assert!((into_arr[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);
}
}