use super::xyz::XYZ;
use crate::frames::ReferenceFrame;
use qtty::{LengthUnit, Quantity, Unit};
use std::marker::PhantomData;
use std::ops::{Add, 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_vec3(vec3: nalgebra::Vector3<Quantity<U>>) -> Self {
Self {
xyz: XYZ::from_vec3(vec3),
_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_vec3(&self) -> &nalgebra::Vector3<Quantity<U>> {
self.xyz.as_vec3()
}
#[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 magnitude_squared(&self) -> f64 {
self.xyz.magnitude_squared()
}
#[inline]
pub fn scale(&self, factor: f64) -> Self {
Self::from_xyz(XYZ::from_raw(self.xyz.to_raw().scale(factor)))
}
#[inline]
pub fn dot(&self, other: &Self) -> f64 {
self.xyz.to_raw().dot(&other.xyz.to_raw())
}
#[inline]
pub fn cross(&self, other: &Self) -> Self {
Self::from_xyz(XYZ::from_raw(self.xyz.to_raw().cross(&other.xyz.to_raw())))
}
#[inline]
pub fn negate(&self) -> Self {
Self::from_xyz(-self.xyz)
}
}
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)
}
}
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)
}
}
impl<F: ReferenceFrame, U: Unit> Neg for Vector<F, U> {
type Output = Self;
#[inline]
fn neg(self) -> Self::Output {
self.negate()
}
}
impl<F: ReferenceFrame, U: Unit> PartialEq for Vector<F, U> {
fn eq(&self, other: &Self) -> bool {
self.xyz == other.xyz
}
}
impl<F: ReferenceFrame, U: Unit> std::fmt::Display for Vector<F, U>
where
Quantity<U>: std::fmt::Display,
{
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "Vector<{}> X: ", F::frame_name())?;
std::fmt::Display::fmt(&self.x(), f)?;
write!(f, ", Y: ")?;
std::fmt::Display::fmt(&self.y(), f)?;
write!(f, ", Z: ")?;
std::fmt::Display::fmt(&self.z(), f)
}
}
impl<F: ReferenceFrame, U: Unit> std::fmt::LowerExp for Vector<F, U>
where
Quantity<U>: std::fmt::LowerExp,
{
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "Vector<{}> X: ", F::frame_name())?;
std::fmt::LowerExp::fmt(&self.x(), f)?;
write!(f, ", Y: ")?;
std::fmt::LowerExp::fmt(&self.y(), f)?;
write!(f, ", Z: ")?;
std::fmt::LowerExp::fmt(&self.z(), f)
}
}
impl<F: ReferenceFrame, U: Unit> std::fmt::UpperExp for Vector<F, U>
where
Quantity<U>: std::fmt::UpperExp,
{
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "Vector<{}> X: ", F::frame_name())?;
std::fmt::UpperExp::fmt(&self.x(), f)?;
write!(f, ", Y: ")?;
std::fmt::UpperExp::fmt(&self.y(), f)?;
write!(f, ", Z: ")?;
std::fmt::UpperExp::fmt(&self.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::{AstronomicalUnit, Day, Kilometer, Meter, Per, Quantity};
#[derive(Debug, Copy, Clone, ReferenceFrame)]
struct TestFrame;
type DispAu = Displacement<TestFrame, AstronomicalUnit>;
type AuPerDay = Per<AstronomicalUnit, Day>;
type VelAuDay = Velocity<TestFrame, AuPerDay>;
#[test]
fn test_vector_add_sub() {
let a = DispAu::new(1.0, 2.0, 3.0);
let b = DispAu::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]
fn test_vector_magnitude() {
let v = DispAu::new(3.0, 4.0, 0.0);
assert!((v.magnitude().value() - 5.0).abs() < 1e-12);
}
#[test]
fn test_displacement_normalize() {
let v = DispAu::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 = DispAu::ZERO;
assert!(zero.normalize().is_none());
}
#[test]
fn test_velocity_add_sub() {
let v1 = VelAuDay::new(
Quantity::<AuPerDay>::new(1.0),
Quantity::<AuPerDay>::new(2.0),
Quantity::<AuPerDay>::new(3.0),
);
let v2 = VelAuDay::new(
Quantity::<AuPerDay>::new(0.5),
Quantity::<AuPerDay>::new(1.0),
Quantity::<AuPerDay>::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 = VelAuDay::new(
Quantity::<AuPerDay>::new(3.0),
Quantity::<AuPerDay>::new(4.0),
Quantity::<AuPerDay>::new(0.0),
);
assert!((v.magnitude().value() - 5.0).abs() < 1e-12);
}
#[test]
fn test_vector_misc_ops() {
let v = DispAu::new(1.0, 2.0, 3.0);
let scaled = v.scale(2.0);
assert_eq!(scaled, DispAu::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(&DispAu::new(0.0, 1.0, 0.0));
assert!((dot - 2.0).abs() < 1e-12);
let cross = v.cross(&DispAu::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 - 14.0).abs() < 1e-12);
let from_vec3 = DispAu::from_vec3(nalgebra::Vector3::new(
Quantity::<AstronomicalUnit>::new(1.0),
Quantity::<AstronomicalUnit>::new(2.0),
Quantity::<AstronomicalUnit>::new(3.0),
));
assert_eq!(from_vec3, 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_vector_display_and_accessors() {
let v = Displacement::<TestFrame, Meter>::new(1.0, 2.0, 3.0);
let vec3 = v.as_vec3();
assert!((vec3[0].value() - 1.0).abs() < 1e-12);
assert!((vec3[1].value() - 2.0).abs() < 1e-12);
assert!((vec3[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_au = DispAu::new(1.0, -2.0, 0.5);
let v_km: Displacement<TestFrame, Kilometer> = v_au.to_unit();
let back: DispAu = v_km.to_unit();
assert!((back.x().value() - v_au.x().value()).abs() < 1e-12);
assert!((back.y().value() - v_au.y().value()).abs() < 1e-12);
assert!((back.z().value() - v_au.z().value()).abs() < 1e-12);
}
#[test]
fn test_velocity_to_unit_roundtrip() {
type KmPerDay = Per<Kilometer, Day>;
let v_au_day = VelAuDay::new(
Quantity::<AuPerDay>::new(0.01),
Quantity::<AuPerDay>::new(-0.02),
Quantity::<AuPerDay>::new(0.03),
);
let v_km_day: Velocity<TestFrame, KmPerDay> = v_au_day.to_unit();
let back: VelAuDay = v_km_day.to_unit();
assert!((back.x().value() - v_au_day.x().value()).abs() < 1e-12);
assert!((back.y().value() - v_au_day.y().value()).abs() < 1e-12);
assert!((back.z().value() - v_au_day.z().value()).abs() < 1e-12);
}
#[test]
fn vector_has_xyz_layout() {
assert_eq!(
core::mem::size_of::<DispAu>(),
core::mem::size_of::<XYZ<Quantity<AstronomicalUnit>>>()
);
assert_eq!(
core::mem::align_of::<DispAu>(),
core::mem::align_of::<XYZ<Quantity<AstronomicalUnit>>>()
);
}
}