use super::xyz::XYZ;
use crate::frames::ReferenceFrame;
use qtty::dimensionless::Ratio;
use qtty::length::LengthUnit;
use qtty::{Quantity, Unit, UnitMul};
use std::marker::PhantomData;
use std::ops::{Add, Div, Neg, Sub};
#[repr(transparent)]
#[derive(Debug, Clone, Copy)]
pub struct Vector<F: ReferenceFrame, U: Unit> {
pub(in crate::cartesian) xyz: XYZ<Quantity<U>>,
_frame: PhantomData<F>,
}
impl<F: ReferenceFrame, U: Unit> Vector<F, U> {
#[inline]
pub fn new<T: Into<Quantity<U>>>(x: T, y: T, z: T) -> Self {
Self {
xyz: XYZ::new(x.into(), y.into(), z.into()),
_frame: PhantomData,
}
}
#[inline]
pub fn from_array(arr: [Quantity<U>; 3]) -> Self {
Self {
xyz: XYZ::from_array(arr),
_frame: PhantomData,
}
}
#[inline]
pub(crate) fn from_xyz(xyz: XYZ<Quantity<U>>) -> Self {
Self {
xyz,
_frame: PhantomData,
}
}
pub const ZERO: Self = Self {
xyz: XYZ::new(
Quantity::<U>::new(0.0),
Quantity::<U>::new(0.0),
Quantity::<U>::new(0.0),
),
_frame: PhantomData,
};
}
impl<F: ReferenceFrame, U: Unit> Vector<F, U> {
#[inline]
pub fn x(&self) -> Quantity<U> {
self.xyz.x()
}
#[inline]
pub fn y(&self) -> Quantity<U> {
self.xyz.y()
}
#[inline]
pub fn z(&self) -> Quantity<U> {
self.xyz.z()
}
#[inline]
pub fn as_array(&self) -> &[Quantity<U>; 3] {
self.xyz.as_array()
}
#[inline]
pub fn to_unit<U2: Unit<Dim = U::Dim>>(&self) -> Vector<F, U2> {
Vector::<F, U2>::new(
self.x().to::<U2>(),
self.y().to::<U2>(),
self.z().to::<U2>(),
)
}
#[inline]
pub fn reinterpret_frame<F2: ReferenceFrame>(self) -> Vector<F2, U> {
Vector::new(self.x(), self.y(), self.z())
}
}
impl<F: ReferenceFrame, U: Unit> Vector<F, U> {
#[inline]
pub fn magnitude(&self) -> Quantity<U> {
self.xyz.magnitude()
}
#[inline]
pub fn scale(&self, factor: f64) -> Self {
Self::from_xyz(XYZ::from_raw(self.xyz.to_raw().scale(factor)))
}
#[inline]
pub fn negate(&self) -> Self {
Self::from_xyz(-self.xyz)
}
}
impl<F: ReferenceFrame, U: Unit + UnitMul<U>> Vector<F, U> {
#[inline]
pub fn magnitude_squared(&self) -> Quantity<<U as UnitMul<U>>::Output> {
self.xyz.magnitude_squared()
}
#[inline]
pub fn dot(&self, other: &Self) -> Quantity<<U as UnitMul<U>>::Output> {
let s = self.xyz.to_raw().dot(&other.xyz.to_raw());
Quantity::new(s)
}
#[inline]
pub fn cross(&self, other: &Self) -> Vector<F, <U as UnitMul<U>>::Output> {
let raw = self.xyz.to_raw().cross(&other.xyz.to_raw());
Vector::<F, <U as UnitMul<U>>::Output>::new(
Quantity::new(raw.x()),
Quantity::new(raw.y()),
Quantity::new(raw.z()),
)
}
}
impl<F: ReferenceFrame, U: LengthUnit> Vector<F, U> {
#[inline]
pub fn normalize(&self) -> Option<super::Direction<F>> {
self.xyz
.to_raw()
.try_normalize()
.map(super::Direction::from_xyz_unchecked)
}
#[inline]
pub fn normalize_unchecked(&self) -> super::Direction<F> {
super::Direction::from_xyz_unchecked(self.xyz.to_raw().normalize_unchecked())
}
}
impl<F: ReferenceFrame, U: Unit> Add for Vector<F, U> {
type Output = Self;
#[inline]
fn add(self, other: Self) -> Self::Output {
Self::from_xyz(self.xyz + other.xyz)
}
}
forward_ref_binop! { impl[F: ReferenceFrame, U: Unit] Add, add for Vector<F, U>, Vector<F, U> }
impl<F: ReferenceFrame, U: Unit> Sub for Vector<F, U> {
type Output = Self;
#[inline]
fn sub(self, other: Self) -> Self::Output {
Self::from_xyz(self.xyz - other.xyz)
}
}
forward_ref_binop! { impl[F: ReferenceFrame, U: Unit] Sub, sub for Vector<F, U>, Vector<F, U> }
impl<F: ReferenceFrame, U: Unit> Neg for Vector<F, U> {
type Output = Self;
#[inline]
fn neg(self) -> Self::Output {
self.negate()
}
}
forward_ref_unop! { impl[F: ReferenceFrame, U: Unit] Neg, neg for Vector<F, U> }
impl<F: ReferenceFrame, U: Unit> Div<Quantity<U>> for Vector<F, U> {
type Output = Vector<F, Ratio>;
#[inline]
fn div(self, rhs: Quantity<U>) -> Vector<F, Ratio> {
let c = rhs.value();
Vector::<F, Ratio>::new(
Quantity::new(self.x().value() / c),
Quantity::new(self.y().value() / c),
Quantity::new(self.z().value() / c),
)
}
}
forward_ref_binop! { impl[F: ReferenceFrame, U: Unit] Div, div for Vector<F, U>, Quantity<U> }
impl<F: ReferenceFrame, U: Unit> PartialEq for Vector<F, U> {
fn eq(&self, other: &Self) -> bool {
self.xyz == other.xyz
}
}
impl_quantity_fmt_triplet! {
impl[F, U] for Vector<F, U>
where {
F: ReferenceFrame,
U: Unit,
},
fmt_each: { Quantity<U>, },
|this, f, FmtOne| {
write!(f, "Vector<{}> X: ", F::frame_name())?;
FmtOne::fmt(&this.x(), f)?;
write!(f, ", Y: ")?;
FmtOne::fmt(&this.y(), f)?;
write!(f, ", Z: ")?;
FmtOne::fmt(&this.z(), f)
}
}
pub type Displacement<F, U> = Vector<F, U>;
pub type Velocity<F, U> = Vector<F, U>;
#[cfg(test)]
mod tests {
use super::*;
use crate::DeriveReferenceFrame as ReferenceFrame;
use qtty::units::{Kilometer, Meter, Second};
use qtty::{Per, Quantity};
#[derive(Debug, Copy, Clone, ReferenceFrame)]
struct TestFrame;
type DispM = Displacement<TestFrame, Meter>;
type MPerS = Per<Meter, Second>;
type VelMPerS = Velocity<TestFrame, MPerS>;
#[test]
fn test_vector_add_sub() {
let a = DispM::new(1.0, 2.0, 3.0);
let b = DispM::new(4.0, 5.0, 6.0);
let sum = a + b;
assert!((sum.x().value() - 5.0).abs() < 1e-12);
assert!((sum.y().value() - 7.0).abs() < 1e-12);
assert!((sum.z().value() - 9.0).abs() < 1e-12);
let diff = b - a;
assert!((diff.x().value() - 3.0).abs() < 1e-12);
assert!((diff.y().value() - 3.0).abs() < 1e-12);
assert!((diff.z().value() - 3.0).abs() < 1e-12);
}
#[test]
#[allow(clippy::op_ref)]
fn ref_ops_uniformity_smoke() {
let a = DispM::new(1.0, 2.0, 3.0);
let b = DispM::new(4.0, 5.0, 6.0);
let expected = DispM::new(5.0, 7.0, 9.0);
let by_value = a + b;
let lhs_ref = &a + b;
let rhs_ref = a + &b;
let both_ref = &a + &b;
let check = |got: DispM| {
assert!((got.x().value() - expected.x().value()).abs() < 1e-12);
assert!((got.y().value() - expected.y().value()).abs() < 1e-12);
assert!((got.z().value() - expected.z().value()).abs() < 1e-12);
};
check(by_value);
check(lhs_ref);
check(rhs_ref);
check(both_ref);
let n_value = -a;
let n_ref = -&a;
assert!((n_value.x().value() - n_ref.x().value()).abs() < 1e-12);
assert!((n_value.y().value() - n_ref.y().value()).abs() < 1e-12);
assert!((n_value.z().value() - n_ref.z().value()).abs() < 1e-12);
}
#[test]
fn test_vector_magnitude() {
let v = DispM::new(3.0, 4.0, 0.0);
assert!((v.magnitude().value() - 5.0).abs() < 1e-12);
}
#[test]
fn test_displacement_normalize() {
let v = DispM::new(3.0, 4.0, 0.0);
let dir = v.normalize().expect("non-zero displacement");
let norm = (dir.x() * dir.x() + dir.y() * dir.y() + dir.z() * dir.z()).sqrt();
assert!((norm - 1.0).abs() < 1e-12);
}
#[test]
fn test_zero_displacement_normalize() {
let zero = DispM::ZERO;
assert!(zero.normalize().is_none());
}
#[test]
fn test_velocity_add_sub() {
let v1 = VelMPerS::new(
Quantity::<MPerS>::new(1.0),
Quantity::<MPerS>::new(2.0),
Quantity::<MPerS>::new(3.0),
);
let v2 = VelMPerS::new(
Quantity::<MPerS>::new(0.5),
Quantity::<MPerS>::new(1.0),
Quantity::<MPerS>::new(1.5),
);
let sum = v1 + v2;
assert!((sum.x().value() - 1.5).abs() < 1e-12);
assert!((sum.y().value() - 3.0).abs() < 1e-12);
assert!((sum.z().value() - 4.5).abs() < 1e-12);
let diff = v1 - v2;
assert!((diff.x().value() - 0.5).abs() < 1e-12);
assert!((diff.y().value() - 1.0).abs() < 1e-12);
assert!((diff.z().value() - 1.5).abs() < 1e-12);
}
#[test]
fn test_velocity_magnitude() {
let v = VelMPerS::new(
Quantity::<MPerS>::new(3.0),
Quantity::<MPerS>::new(4.0),
Quantity::<MPerS>::new(0.0),
);
assert!((v.magnitude().value() - 5.0).abs() < 1e-12);
}
#[test]
fn test_vector_misc_ops() {
let v = DispM::new(1.0, 2.0, 3.0);
let scaled = v.scale(2.0);
assert_eq!(scaled, DispM::new(2.0, 4.0, 6.0));
let neg = v.negate();
assert!((neg.x().value() + 1.0).abs() < 1e-12);
assert!((neg.y().value() + 2.0).abs() < 1e-12);
let dot = v.dot(&DispM::new(0.0, 1.0, 0.0));
assert!((dot.value() - 2.0).abs() < 1e-12);
let cross = v.cross(&DispM::new(0.0, 1.0, 0.0));
assert!((cross.x().value() + 3.0).abs() < 1e-12);
assert!(cross.y().value().abs() < 1e-12);
assert!((cross.z().value() - 1.0).abs() < 1e-12);
let magnitude_sq = v.magnitude_squared();
assert!((magnitude_sq.value() - 14.0).abs() < 1e-12);
let from_arr = DispM::from_array([
Quantity::<Meter>::new(1.0),
Quantity::<Meter>::new(2.0),
Quantity::<Meter>::new(3.0),
]);
assert_eq!(from_arr, v);
let dir = v.normalize_unchecked();
let norm = (dir.x() * dir.x() + dir.y() * dir.y() + dir.z() * dir.z()).sqrt();
assert!((norm - 1.0).abs() < 1e-12);
let neg_op = -v;
assert_eq!(neg_op, neg);
}
#[test]
fn test_typed_dot_cross_magnitude_squared() {
use qtty::Prod;
let v = Displacement::<TestFrame, Meter>::new(1.0, 0.0, 0.0);
let w = Displacement::<TestFrame, Meter>::new(1.0, 0.0, 0.0);
let d: Quantity<Prod<Meter, Meter>> = v.dot(&w);
assert!((d.value() - 1.0).abs() < 1e-12);
let m2: Quantity<Prod<Meter, Meter>> = v.magnitude_squared();
assert!((m2.value() - 1.0).abs() < 1e-12);
let m: Quantity<Meter> = m2.sqrt();
assert!((m.value() - 1.0).abs() < 1e-12);
let a = Displacement::<TestFrame, Meter>::new(1.0, 0.0, 0.0);
let b = Displacement::<TestFrame, Meter>::new(0.0, 1.0, 0.0);
let c: Vector<TestFrame, Prod<Meter, Meter>> = a.cross(&b);
assert!((c.x().value()).abs() < 1e-12);
assert!((c.y().value()).abs() < 1e-12);
assert!((c.z().value() - 1.0).abs() < 1e-12);
}
#[test]
fn test_vector_display_and_accessors() {
let v = Displacement::<TestFrame, Meter>::new(1.0, 2.0, 3.0);
let arr = v.as_array();
assert!((arr[0].value() - 1.0).abs() < 1e-12);
assert!((arr[1].value() - 2.0).abs() < 1e-12);
assert!((arr[2].value() - 3.0).abs() < 1e-12);
let text = v.to_string();
assert!(text.contains("Vector<TestFrame>"));
let text_prec = format!("{:.1}", v);
let expected_x_prec = format!("{:.1}", v.x());
assert!(text_prec.contains(&format!("X: {expected_x_prec}")));
let text_exp = format!("{:.2e}", v);
let expected_y_exp = format!("{:.2e}", v.y());
assert!(text_exp.contains(&format!("Y: {expected_y_exp}")));
}
#[test]
fn test_vector_to_unit_roundtrip() {
let v_m = DispM::new(1.0, -2.0, 0.5);
let v_km: Displacement<TestFrame, Kilometer> = v_m.to_unit();
let back: DispM = v_km.to_unit();
assert!((back.x().value() - v_m.x().value()).abs() < 1e-12);
assert!((back.y().value() - v_m.y().value()).abs() < 1e-12);
assert!((back.z().value() - v_m.z().value()).abs() < 1e-12);
}
#[test]
fn test_velocity_to_unit_roundtrip() {
type KmPerS = Per<Kilometer, Second>;
let v_m_s = VelMPerS::new(
Quantity::<MPerS>::new(0.01),
Quantity::<MPerS>::new(-0.02),
Quantity::<MPerS>::new(0.03),
);
let v_km_s: Velocity<TestFrame, KmPerS> = v_m_s.to_unit();
let back: VelMPerS = v_km_s.to_unit();
assert!((back.x().value() - v_m_s.x().value()).abs() < 1e-12);
assert!((back.y().value() - v_m_s.y().value()).abs() < 1e-12);
assert!((back.z().value() - v_m_s.z().value()).abs() < 1e-12);
}
#[test]
fn vector_has_xyz_layout() {
assert_eq!(
core::mem::size_of::<DispM>(),
core::mem::size_of::<XYZ<Quantity<Meter>>>()
);
assert_eq!(
core::mem::align_of::<DispM>(),
core::mem::align_of::<XYZ<Quantity<Meter>>>()
);
}
}