use std::fmt;
use std::ops::{Add, AddAssign, Mul, MulAssign, Neg, Sub, SubAssign};
use std::str::FromStr;
use crate::utilities::math_utilities;
use crate::utilities::span_helpers::InvariantF64;
use crate::utilities::{FormatError, SpanStringTokenizer};
#[derive(Clone, Copy, Debug, PartialEq, Default)]
pub struct Vector3D {
pub x: f64,
pub y: f64,
pub z: f64,
}
impl Vector3D {
#[inline]
pub const fn new(x: f64, y: f64, z: f64) -> Self {
Self { x, y, z }
}
pub fn parse(s: &str) -> Result<Self, FormatError> {
SpanStringTokenizer::with_message(s, "Invalid Vector.").scope(|t| {
Ok(Vector3D::new(
t.read_double()?,
t.read_double()?,
t.read_double()?,
))
})
}
#[inline]
pub fn dot(vector1: Vector3D, vector2: Vector3D) -> f64 {
(vector1.x * vector2.x) + (vector1.y * vector2.y) + (vector1.z * vector2.z)
}
#[inline]
#[allow(clippy::should_implement_trait)] pub fn add(left: Vector3D, right: Vector3D) -> Vector3D {
Vector3D::new(left.x + right.x, left.y + right.y, left.z + right.z)
}
#[inline]
pub fn substract(left: Vector3D, right: Vector3D) -> Vector3D {
Vector3D::new(left.x - right.x, left.y - right.y, left.z - right.z)
}
#[inline]
pub fn multiply(left: Vector3D, right: Vector3D) -> Vector3D {
Vector3D::new(left.x * right.x, left.y * right.y, left.z * right.z)
}
#[inline]
pub fn multiply_scalar(left: Vector3D, right: f64) -> Vector3D {
Vector3D::new(left.x * right, left.y * right, left.z * right)
}
#[inline]
pub fn divide(left: Vector3D, right: Vector3D) -> Vector3D {
Vector3D::new(left.x / right.x, left.y / right.y, left.z / right.z)
}
#[inline]
pub fn divide_scalar(left: Vector3D, right: f64) -> Vector3D {
Vector3D::new(left.x / right, left.y / right, left.z / right)
}
#[inline]
pub fn abs(&self) -> Vector3D {
Vector3D::new(self.x.abs(), self.y.abs(), self.z.abs())
}
#[inline]
pub fn clamp(value: Vector3D, min: Vector3D, max: Vector3D) -> Vector3D {
Vector3D::min(Vector3D::max(value, min), max)
}
#[inline]
pub fn max(left: Vector3D, right: Vector3D) -> Vector3D {
Vector3D::new(
math_utilities::max(left.x, right.x),
math_utilities::max(left.y, right.y),
math_utilities::max(left.z, right.z),
)
}
#[inline]
pub fn min(left: Vector3D, right: Vector3D) -> Vector3D {
Vector3D::new(
math_utilities::min(left.x, right.x),
math_utilities::min(left.y, right.y),
math_utilities::min(left.z, right.z),
)
}
#[inline]
pub fn length(&self) -> f64 {
Vector3D::dot(*self, *self).sqrt()
}
#[inline]
pub fn normalize(value: Vector3D) -> Vector3D {
Vector3D::divide_scalar(value, value.length())
}
#[inline]
pub fn distance_squared(value1: Vector3D, value2: Vector3D) -> f64 {
let difference = Vector3D::substract(value1, value2);
Vector3D::dot(difference, difference)
}
#[inline]
pub fn distance(value1: Vector3D, value2: Vector3D) -> f64 {
Vector3D::distance_squared(value1, value2).sqrt()
}
#[inline]
pub const fn deconstruct(&self) -> (f64, f64, f64) {
(self.x, self.y, self.z)
}
}
impl From<Vector3D> for (f64, f64, f64) {
#[inline]
fn from(v: Vector3D) -> Self {
(v.x, v.y, v.z)
}
}
impl Add for Vector3D {
type Output = Vector3D;
#[inline]
fn add(self, right: Vector3D) -> Vector3D {
Vector3D::add(self, right)
}
}
impl Sub for Vector3D {
type Output = Vector3D;
#[inline]
fn sub(self, right: Vector3D) -> Vector3D {
Vector3D::substract(self, right)
}
}
impl Neg for Vector3D {
type Output = Vector3D;
#[inline]
fn neg(self) -> Vector3D {
Vector3D::new(-self.x, -self.y, -self.z)
}
}
impl Mul<f64> for Vector3D {
type Output = Vector3D;
#[inline]
fn mul(self, right: f64) -> Vector3D {
Vector3D::multiply_scalar(self, right)
}
}
impl AddAssign for Vector3D {
#[inline]
fn add_assign(&mut self, right: Vector3D) {
*self = *self + right;
}
}
impl SubAssign for Vector3D {
#[inline]
fn sub_assign(&mut self, right: Vector3D) {
*self = *self - right;
}
}
impl MulAssign<f64> for Vector3D {
#[inline]
fn mul_assign(&mut self, right: f64) {
*self = *self * right;
}
}
impl FromStr for Vector3D {
type Err = FormatError;
#[inline]
fn from_str(s: &str) -> Result<Self, FormatError> {
Vector3D::parse(s)
}
}
impl fmt::Display for Vector3D {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"Vector3D {{ X = {}, Y = {}, Z = {} }}",
InvariantF64(self.x),
InvariantF64(self.y),
InvariantF64(self.z)
)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn parse_and_display() {
assert_eq!(
Vector3D::parse("1, 2.5 -3").unwrap(),
Vector3D::new(1.0, 2.5, -3.0)
);
assert_eq!(
Vector3D::parse("1,2").unwrap_err().message(),
"Invalid Vector."
);
assert_eq!(
Vector3D::new(1.0, 2.5, -3.0).to_string(),
"Vector3D { X = 1, Y = 2.5, Z = -3 }"
);
}
#[test]
fn arithmetic() {
let a = Vector3D::new(1.0, 2.0, 3.0);
let b = Vector3D::new(4.0, 5.0, 6.0);
assert_eq!(a + b, Vector3D::new(5.0, 7.0, 9.0));
assert_eq!(b - a, Vector3D::new(3.0, 3.0, 3.0));
assert_eq!(-a, Vector3D::new(-1.0, -2.0, -3.0));
assert_eq!(a * 2.0, Vector3D::new(2.0, 4.0, 6.0));
assert_eq!(Vector3D::dot(a, b), 32.0);
assert_eq!(Vector3D::multiply(a, b), Vector3D::new(4.0, 10.0, 18.0));
assert_eq!(Vector3D::divide(b, a), Vector3D::new(4.0, 2.5, 2.0));
assert_eq!(Vector3D::new(2.0, 3.0, 6.0).length(), 7.0);
assert_eq!(
Vector3D::normalize(Vector3D::new(0.0, 0.0, 5.0)),
Vector3D::new(0.0, 0.0, 1.0)
);
assert_eq!(Vector3D::distance(a, Vector3D::new(1.0, 2.0, 5.0)), 2.0);
assert_eq!(
Vector3D::clamp(
Vector3D::new(-1.0, 0.5, 9.0),
Vector3D::default(),
Vector3D::new(1.0, 1.0, 1.0)
),
Vector3D::new(0.0, 0.5, 1.0)
);
assert_eq!(Vector3D::new(-1.0, 2.0, -3.0).abs(), a);
}
}