use std::fmt;
use std::ops::{Add, Mul, Sub};
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Copy, PartialEq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct Vector3<T> {
pub x: T,
pub y: T,
pub z: T,
}
impl<T> Vector3<T> {
pub const fn new(x: T, y: T, z: T) -> Self {
Self { x, y, z }
}
}
impl Vector3<f64> {
pub const fn zero() -> Self {
Self {
x: 0.0,
y: 0.0,
z: 0.0,
}
}
pub const fn unit_x() -> Self {
Self {
x: 1.0,
y: 0.0,
z: 0.0,
}
}
pub const fn unit_y() -> Self {
Self {
x: 0.0,
y: 1.0,
z: 0.0,
}
}
pub const fn unit_z() -> Self {
Self {
x: 0.0,
y: 0.0,
z: 1.0,
}
}
#[inline]
pub fn dot(&self, other: &Self) -> f64 {
self.x * other.x + self.y * other.y + self.z * other.z
}
#[inline]
pub fn cross(&self, other: &Self) -> Self {
Self {
x: self.y * other.z - self.z * other.y,
y: self.z * other.x - self.x * other.z,
z: self.x * other.y - self.y * other.x,
}
}
#[inline]
pub fn magnitude(&self) -> f64 {
(self.x * self.x + self.y * self.y + self.z * self.z).sqrt()
}
#[inline]
pub fn magnitude_squared(&self) -> f64 {
self.x * self.x + self.y * self.y + self.z * self.z
}
#[inline]
pub fn normalize(&self) -> Self {
let mag = self.magnitude();
if mag > 0.0 {
Self {
x: self.x / mag,
y: self.y / mag,
z: self.z / mag,
}
} else {
*self
}
}
#[inline]
pub fn is_normalized(&self) -> bool {
(self.magnitude_squared() - 1.0).abs() < 1e-10
}
#[inline]
pub fn angle_between(&self, other: &Self) -> f64 {
let dot = self.dot(other);
let mags = self.magnitude() * other.magnitude();
if mags > 0.0 {
(dot / mags).clamp(-1.0, 1.0).acos()
} else {
0.0
}
}
#[inline]
pub fn project(&self, other: &Self) -> Self {
let other_mag_sq = other.magnitude_squared();
if other_mag_sq > 0.0 {
*other * (self.dot(other) / other_mag_sq)
} else {
Self::zero()
}
}
}
impl Add for Vector3<f64> {
type Output = Self;
fn add(self, other: Self) -> Self {
Self {
x: self.x + other.x,
y: self.y + other.y,
z: self.z + other.z,
}
}
}
impl Sub for Vector3<f64> {
type Output = Self;
fn sub(self, other: Self) -> Self {
Self {
x: self.x - other.x,
y: self.y - other.y,
z: self.z - other.z,
}
}
}
impl Mul<f64> for Vector3<f64> {
type Output = Self;
fn mul(self, scalar: f64) -> Self {
Self {
x: self.x * scalar,
y: self.y * scalar,
z: self.z * scalar,
}
}
}
impl fmt::Display for Vector3<f64> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "({:.6}, {:.6}, {:.6})", self.x, self.y, self.z)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_cross_product() {
let v1 = Vector3::new(1.0, 0.0, 0.0);
let v2 = Vector3::new(0.0, 1.0, 0.0);
let result = v1.cross(&v2);
assert!((result.x - 0.0).abs() < 1e-10);
assert!((result.y - 0.0).abs() < 1e-10);
assert!((result.z - 1.0).abs() < 1e-10);
}
#[test]
fn test_normalize() {
let v = Vector3::new(3.0, 4.0, 0.0);
let normalized = v.normalize();
assert!((normalized.magnitude() - 1.0).abs() < 1e-10);
}
#[test]
fn test_zero() {
let v = Vector3::zero();
assert_eq!(v.x, 0.0);
assert_eq!(v.y, 0.0);
assert_eq!(v.z, 0.0);
}
#[test]
fn test_unit_vectors() {
let ux = Vector3::unit_x();
let uy = Vector3::unit_y();
let uz = Vector3::unit_z();
assert_eq!(ux.x, 1.0);
assert_eq!(ux.y, 0.0);
assert_eq!(ux.z, 0.0);
assert_eq!(uy.x, 0.0);
assert_eq!(uy.y, 1.0);
assert_eq!(uy.z, 0.0);
assert_eq!(uz.x, 0.0);
assert_eq!(uz.y, 0.0);
assert_eq!(uz.z, 1.0);
assert!(ux.is_normalized());
assert!(uy.is_normalized());
assert!(uz.is_normalized());
}
#[test]
fn test_magnitude_squared() {
let v = Vector3::new(3.0, 4.0, 0.0);
assert_eq!(v.magnitude_squared(), 25.0);
assert!((v.magnitude() - 5.0).abs() < 1e-10);
}
#[test]
fn test_is_normalized() {
let v1 = Vector3::new(1.0, 0.0, 0.0);
assert!(v1.is_normalized());
let v2 = Vector3::new(3.0, 4.0, 0.0);
assert!(!v2.is_normalized());
let v3 = v2.normalize();
assert!(v3.is_normalized());
}
#[test]
fn test_angle_between() {
let v1 = Vector3::new(1.0, 0.0, 0.0);
let v2 = Vector3::new(0.0, 1.0, 0.0);
let angle = v1.angle_between(&v2);
assert!((angle - std::f64::consts::FRAC_PI_2).abs() < 1e-10);
let v3 = Vector3::new(2.0, 0.0, 0.0);
let angle2 = v1.angle_between(&v3);
assert!(angle2.abs() < 1e-10);
let v4 = Vector3::new(-1.0, 0.0, 0.0);
let angle3 = v1.angle_between(&v4);
assert!((angle3 - std::f64::consts::PI).abs() < 1e-10);
}
#[test]
fn test_project() {
let v1 = Vector3::new(3.0, 4.0, 0.0);
let v2 = Vector3::new(1.0, 0.0, 0.0);
let proj = v1.project(&v2);
assert!((proj.x - 3.0).abs() < 1e-10);
assert!(proj.y.abs() < 1e-10);
assert!(proj.z.abs() < 1e-10);
let v3 = Vector3::new(1.0, 1.0, 0.0);
let proj2 = v1.project(&v3);
assert!((proj2.x - 3.5).abs() < 1e-10);
assert!((proj2.y - 3.5).abs() < 1e-10);
assert!(proj2.z.abs() < 1e-10);
}
}