use crate::{Point};
use num_traits::Float;
use std::ops::{Add, AddAssign, Mul, Sub, SubAssign};
#[cfg(feature = "serde")]
use serde::{Deserialize, Deserializer, Serialize, Serializer};
pub trait VectorMetricSquared<T> {
fn magnitude_squared(&self) -> T;
fn dot(&self, other: &Self) -> T;
}
pub trait EuclideanVector<T>: VectorMetricSquared<T>
where
Self: Sized,
{
fn magnitude(&self) -> T;
fn normalize(&self) -> Option<Self>;
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Vector<T, const N: usize> {
coords: [T; N],
}
#[cfg(feature = "serde")]
impl<T: Serialize, const N: usize> Serialize for Vector<T, N> {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
self.coords.as_slice().serialize(serializer)
}
}
#[cfg(feature = "serde")]
impl<'de, T: Deserialize<'de>, const N: usize> Deserialize<'de> for Vector<T, N> {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let coords_vec = Vec::<T>::deserialize(deserializer)?;
let coords: [T; N] = coords_vec.try_into().map_err(|_| {
serde::de::Error::custom(format!("Vector dimension mismatch: expected {}", N))
})?;
Ok(Self { coords })
}
}
pub type Vector2D<T> = Vector<T, 2>;
pub type Vector3D<T> = Vector<T, 3>;
impl<T, const N: usize> Vector<T, N>
where
T: Float,
{
#[inline]
pub fn new(coords: [T; N]) -> Self {
Self { coords }
}
#[inline]
pub fn coords(&self) -> [T; N]
where
T: Copy,
{
self.coords
}
#[inline]
pub fn coords_ref(&self) -> &[T; N] {
&self.coords
}
#[inline]
pub fn coords_ref_mut(&mut self) -> &mut [T; N] {
&mut self.coords
}
#[inline]
pub fn set_coords(&mut self, coords: [T; N]) {
self.coords = coords;
}
}
impl<T, const N: usize> From<(&Point<T, N>, &Point<T, N>)> for Vector<T, N>
where
T: Float,
{
#[inline]
fn from((initial, final_point): (&Point<T, N>, &Point<T, N>)) -> Self {
let coords = std::array::from_fn(|i| final_point.coords_ref()[i] - initial.coords_ref()[i]);
Vector::new(coords)
}
}
impl<T, const N: usize> From<&Point<T, N>> for Vector<T, N>
where
T: Float,
{
#[inline]
fn from(final_point: &Point<T, N>) -> Self {
Vector::new(*final_point.coords_ref())
}
}
impl<T: Float> From<(T, T)> for Vector2D<T> {
#[inline]
fn from(value: (T, T)) -> Self {
let (x, y) = value;
Self { coords: [x, y] }
}
}
impl<T: Float> From<(T, T, T)> for Vector3D<T> {
#[inline]
fn from(value: (T, T, T)) -> Self {
let (x, y, z) = value;
Self { coords: [x, y, z] }
}
}
impl<T, const N: usize> Add for Vector<T, N>
where
T: Float,
{
type Output = Vector<T, N>;
#[inline]
fn add(self, rhs: Self) -> Self::Output {
let coords = std::array::from_fn(|i| self.coords_ref()[i] + rhs.coords_ref()[i]);
Self { coords }
}
}
impl<T, const N: usize> Sub for Vector<T, N>
where
T: Float,
{
type Output = Vector<T, N>;
#[inline]
fn sub(self, rhs: Self) -> Self::Output {
let coords = std::array::from_fn(|i| self.coords_ref()[i] - rhs.coords_ref()[i]);
Self { coords }
}
}
impl<T, const N: usize> Mul<T> for Vector<T, N>
where
T: Float,
{
type Output = Vector<T, N>;
#[inline]
fn mul(self, scalar: T) -> Self::Output {
let coords = std::array::from_fn(|i| self.coords_ref()[i] * scalar);
Self { coords }
}
}
impl<T, const N: usize> AddAssign for Vector<T, N>
where
T: Float,
{
#[inline]
fn add_assign(&mut self, rhs: Self) {
let coords = std::array::from_fn(|i| self.coords_ref()[i] + rhs.coords_ref()[i]);
self.set_coords(coords);
}
}
impl<T, const N: usize> SubAssign for Vector<T, N>
where
T: Float,
{
#[inline]
fn sub_assign(&mut self, rhs: Self) {
let coords = std::array::from_fn(|i| self.coords_ref()[i] - rhs.coords_ref()[i]);
self.set_coords(coords);
}
}
impl<const N: usize> Mul<Vector<f32, N>> for f32 {
type Output = Vector<f32, N>;
#[inline]
fn mul(self, vector: Vector<f32, N>) -> Vector<f32, N> {
vector * self
}
}
impl<const N: usize> Mul<Vector<f64, N>> for f64 {
type Output = Vector<f64, N>;
#[inline]
fn mul(self, vector: Vector<f64, N>) -> Vector<f64, N> {
vector * self
}
}
impl<T, const N: usize> VectorMetricSquared<T> for Vector<T, N>
where
T: Float + std::iter::Sum,
{
#[inline]
fn magnitude_squared(&self) -> T {
self.coords_ref().iter().map(|coord| *coord * *coord).sum()
}
#[inline]
fn dot(&self, other: &Self) -> T {
self.coords_ref()
.iter()
.zip(other.coords_ref().iter())
.map(|(a, b)| *a * *b)
.sum()
}
}
impl<T, const N: usize> EuclideanVector<T> for Vector<T, N>
where
T: Float + std::iter::Sum,
{
#[inline]
fn magnitude(&self) -> T {
self.magnitude_squared().sqrt()
}
#[inline]
fn normalize(&self) -> Option<Self> {
let mag = self.magnitude();
if mag <= T::epsilon() * T::from(10.0).unwrap_or(T::one()) {
None
} else {
Some(*self * (T::one() / mag))
}
}
}
impl<T> Vector<T, 2>
where
T: Float,
{
#[inline]
pub fn cross(&self, other: &Self) -> Vector<T, 3> {
let [x1, y1] = *self.coords_ref();
let [x2, y2] = *other.coords_ref();
Vector::new([T::zero(), T::zero(), x1 * y2 - y1 * x2])
}
}
impl<T> Vector<T, 3>
where
T: Float,
{
#[inline]
pub fn cross(&self, other: &Self) -> Self {
let [x1, y1, z1] = *self.coords_ref();
let [x2, y2, z2] = *other.coords_ref();
Self {
coords: [y1 * z2 - z1 * y2, z1 * x2 - x1 * z2, x1 * y2 - y1 * x2],
}
}
}
#[cfg(test)]
mod vectors_tests {
use super::*;
use approx::assert_relative_eq;
#[test]
fn test_construction_and_conversions() {
let v_gen: Vector<f64, 3> = Vector::new([1.0, 2.0, 3.0]);
assert_eq!(v_gen.coords_ref(), &[1.0, 2.0, 3.0]);
let p1 = Point::new([1.0, 2.0, 3.0]);
let p2 = Point::new([4.0, 6.0, 8.0]);
let v_from_pts: Vector<f64, 3> = Vector::from((&p1, &p2));
assert_eq!(v_from_pts.coords_ref(), &[3.0, 4.0, 5.0]);
let v2d: Vector2D<f32> = Vector2D::from((1.0, 2.0));
assert_eq!(v2d.coords_ref(), &[1.0, 2.0]);
let v3d: Vector3D<f32> = Vector3D::from((1.0, 2.0, 3.0));
assert_eq!(v3d.coords_ref(), &[1.0, 2.0, 3.0]);
let alias: Vector3D<f64> = Vector::new([1.0, 2.0, 3.0]);
assert_eq!(alias.coords_ref(), &[1.0, 2.0, 3.0]);
}
#[test]
fn test_arithmetic_operations() {
let v1 = Vector::new([1.0, 2.0, 3.0]);
let v2 = Vector::new([4.0, 5.0, 6.0]);
let sum = v1 + v2;
assert_relative_eq!(sum.coords_ref()[0], 5.0);
assert_relative_eq!(sum.coords_ref()[1], 7.0);
assert_relative_eq!(sum.coords_ref()[2], 9.0);
let diff = v2 - v1;
assert_relative_eq!(diff.coords_ref()[0], 3.0);
assert_relative_eq!(diff.coords_ref()[1], 3.0);
assert_relative_eq!(diff.coords_ref()[2], 3.0);
let scaled_r = v1 * 2.0;
assert_eq!(scaled_r.coords_ref(), &[2.0, 4.0, 6.0]);
let scaled_l_f32: Vector<f32, 2> = 3.0_f32 * Vector::new([1.0, 2.0]);
assert_eq!(scaled_l_f32.coords_ref(), &[3.0, 6.0]);
let scaled_l_f64: Vector<f64, 2> = 3.0_f64 * Vector::new([1.0, 2.0]);
assert_eq!(scaled_l_f64.coords_ref(), &[3.0, 6.0]);
let mut v = Vector::new([1.0, 2.0]);
v += Vector::new([3.0, 4.0]);
assert_eq!(v.coords_ref(), &[4.0, 6.0]);
v -= Vector::new([1.0, 2.0]);
assert_eq!(v.coords_ref(), &[3.0, 4.0]);
}
#[test]
fn test_vector_properties() {
let v = Vector::new([3.0_f64, 4.0, 0.0]);
assert_relative_eq!(v.magnitude_squared(), 25.0);
assert_relative_eq!(v.magnitude(), 5.0);
let v1 = Vector::new([1.0, 2.0, 3.0]);
let v2 = Vector::new([4.0, 5.0, 6.0]);
assert_relative_eq!(v1.dot(&v2), 32.0);
assert_relative_eq!(v1.dot(&v1), v1.magnitude_squared());
let normalized = v.normalize().unwrap();
assert_relative_eq!(normalized.magnitude(), 1.0);
assert_relative_eq!(normalized.coords_ref()[0] / v.coords_ref()[0], 0.2);
let zero_vec: Vector<f64, 3> = Vector::new([0.0, 0.0, 0.0]);
assert!(zero_vec.normalize().is_none());
}
#[test]
fn test_cross_product() {
let v1 = Vector3D::from((1.0, 0.0, 0.0));
let v2 = Vector3D::from((0.0, 1.0, 0.0));
let cross = v1.cross(&v2);
assert_relative_eq!(cross.coords_ref()[0], 0.0);
assert_relative_eq!(cross.coords_ref()[1], 0.0);
assert_relative_eq!(cross.coords_ref()[2], 1.0);
let v = Vector3D::from((3.0, -2.0, 5.0));
let self_cross = v.cross(&v);
assert!(self_cross.coords_ref().iter().all(|&c| c.abs() < 1e-10));
let v = Vector3D::from((2.0, 3.0, 4.0));
let w = Vector3D::from((5.0, 6.0, 7.0));
let cross_vw = v.cross(&w);
assert_relative_eq!(cross_vw.dot(&v), 0.0, epsilon = 1e-10);
assert_relative_eq!(cross_vw.dot(&w), 0.0, epsilon = 1e-10);
let v_f64: Vector3D<f64> = Vector3D::from((1.0, 0.0, 0.0));
let w_f64 = Vector3D::from((0.0, 1.0, 0.0));
let cross_f64 = v_f64.cross(&w_f64);
assert_eq!(cross_f64.coords_ref(), &[0.0, 0.0, 1.0]);
}
#[test]
fn test_traits_and_types() {
let v1 = Vector::new([1.0, 2.0]);
let v2 = v1;
assert_eq!(v1.coords_ref(), v2.coords_ref());
println!("{:?}", v1);
let v_f32: Vector<f32, 2> = Vector::new([1.5, 2.5]);
let _ = v_f32 * 2.0_f32;
let v_f64: Vector<f64, 3> = Vector::new([1.0, 2.0, 3.0]);
let _sum = v_f64 + Vector::new([4.0, 5.0, 6.0]);
}
}
#[cfg(all(test, feature = "serde"))]
mod serde_tests {
use super::*;
use serde_json;
#[test]
fn test_vector_serialization_roundtrip() {
let v = Vector::new([1.0, 2.0, 3.0]);
let json = serde_json::to_string(&v).unwrap();
let w: Vector<f64, 3> = serde_json::from_str(&json).unwrap();
assert_eq!(v, w);
}
#[test]
fn test_vector2d_serialization_roundtrip() {
let v = Vector2D::from((1.0, 2.0));
let json = serde_json::to_string(&v).unwrap();
let w: Vector2D<f64> = serde_json::from_str(&json).unwrap();
assert_eq!(v, w);
}
#[test]
fn test_vector3d_serialization_roundtrip() {
let v = Vector3D::from((1.0, 2.0, 3.0));
let json = serde_json::to_string(&v).unwrap();
let w: Vector3D<f64> = serde_json::from_str(&json).unwrap();
assert_eq!(v, w);
}
}