1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
#![allow(clippy::suboptimal_flops)] // The code is much more readadble this way

use std::{
    cmp::PartialEq,
    fmt::{Display, Result},
    str::FromStr,
};

/// A point in 3D space, using `f64`s
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Vec3D {
    /// X-coordinate
    pub x: f64,
    /// Y-coordinate
    pub y: f64,
    /// Z-coordinate
    pub z: f64,
}

impl Vec3D {
    impl_vec_single_value_const!(Vec3D, ZERO, 0.0, (x, y, z));
    impl_vec_single_value_const!(Vec3D, ONE, 1.0, (x, y, z));

    impl_vec_core!(Vec3D, f64, (x, y, z));

    /// Return the dot product in combination with another `Vec3D`
    #[must_use]
    pub fn dot(&self, other: Self) -> f64 {
        self.x * other.x + self.y * other.y + self.z * other.z
    }

    /// Returns the dot product in combination with itself
    #[must_use]
    pub fn dot_self(&self) -> f64 {
        self.x.powi(2) + self.y.powi(2) + self.z.powi(2)
    }

    /// The length/magnitude of the `Vec3D`
    #[must_use]
    pub fn magnitude(&self) -> f64 {
        (self.x.powi(2) + self.y.powi(2) + self.z.powi(2)).sqrt()
    }

    /// The cross product of two vectors is a vector perpendicular to both of them. See [a more detailed explanation](https://www.gabrielgambetta.com/computer-graphics-from-scratch/A0-linear-algebra.html#cross-product)
    #[must_use]
    pub fn cross(&self, other: Self) -> Self {
        Self::new(
            self.y * other.z - self.z * other.y,
            self.z * other.x - self.x * other.z,
            self.x * other.y - self.y * other.x,
        )
    }

    /// Generate a normal from the `Vec3D`
    #[must_use]
    pub fn normal(self) -> Self {
        self / self.magnitude()
    }
}

impl FromStr for Vec3D {
    type Err = String;

    fn from_str(s: &str) -> std::prelude::v1::Result<Self, Self::Err> {
        let s = s.replace(' ', "");
        let s = s.strip_prefix("Vec3D").unwrap_or(&s);
        let s = s.strip_prefix('(').unwrap_or(s);
        let s = s.strip_suffix(')').unwrap_or(s);
        let parts: Vec<&str> = s.split(',').collect();

        if parts.len() != 3 {
            return Err(String::from("Incorrect number of arguments, string must be in format x,y,z to be parsed correctly"));
        }

        let mut nums = Vec::new();

        for part in parts {
            nums.push(match part.parse::<f64>() {
                Ok(val) => val,
                Err(_) => {
                    return Err(String::from(
                        "Could not parse part of argument, make sure it's a valid number",
                    ))
                }
            });
        }

        Ok(Self::from((nums[0], nums[1], nums[2])))
    }
}

impl Display for Vec3D {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result {
        write!(f, "Vec3D({}, {}, {})", self.x, self.y, self.z)
    }
}

impl<T: Into<f64>> From<(T, T, T)> for Vec3D {
    fn from(value: (T, T, T)) -> Self {
        Self {
            x: value.0.into(),
            y: value.1.into(),
            z: value.2.into(),
        }
    }
}

impl_vec_add!(Vec3D, (x, y, z));
impl_vec_sub!(Vec3D, (x, y, z));
impl_vec_neg!(Vec3D, 0.0, (x, y, z));
impl_vec_mul!(Vec3D, (x, y, z));
impl_vec_mul_single!(Vec3D, f64, (x, y, z));
impl_vec_div!(Vec3D, (x, y, z));
impl_vec_div_single!(Vec3D, f64, (x, y, z));
impl_vec_rem!(Vec3D, (x, y, z));

impl std::iter::Sum for Vec3D {
    fn sum<I: Iterator<Item = Self>>(iter: I) -> Self {
        let mut sum = Self::ZERO;
        for item in iter {
            sum += item;
        }

        sum
    }
}