cherry_rs/core/math/
vec3.rs

1/// A 3D vector
2use serde::{Deserialize, Serialize};
3
4use crate::core::{Float, EPSILON, PI};
5
6const TOL: Float = (1 as Float) * EPSILON;
7
8#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
9#[serde(into = "[Float; 3]")]
10pub struct Vec3 {
11    e: [Float; 3],
12}
13
14/// Required to serialize Vec3 directly into an array instead of a JSON Object.
15impl From<Vec3> for [Float; 3] {
16    fn from(val: Vec3) -> Self {
17        val.e
18    }
19}
20
21impl Vec3 {
22    pub fn new(e0: Float, e1: Float, e2: Float) -> Self {
23        Self { e: [e0, e1, e2] }
24    }
25
26    pub fn x(&self) -> Float {
27        self.e[0]
28    }
29
30    pub fn y(&self) -> Float {
31        self.e[1]
32    }
33
34    pub fn z(&self) -> Float {
35        self.e[2]
36    }
37
38    pub fn set_x(&mut self, x: Float) {
39        self.e[0] = x;
40    }
41
42    pub fn set_y(&mut self, y: Float) {
43        self.e[1] = y;
44    }
45
46    pub fn set_z(&mut self, z: Float) {
47        self.e[2] = z;
48    }
49
50    pub fn k(&self) -> Float {
51        self.e[0]
52    }
53
54    pub fn l(&self) -> Float {
55        self.e[1]
56    }
57
58    pub fn m(&self) -> Float {
59        self.e[2]
60    }
61
62    pub fn length(&self) -> Float {
63        self.length_squared().sqrt()
64    }
65
66    pub fn length_squared(&self) -> Float {
67        self.e.iter().map(|e| e * e).sum()
68    }
69
70    pub fn normalize(&self) -> Self {
71        let length = self.length();
72        Self::new(self.e[0] / length, self.e[1] / length, self.e[2] / length)
73    }
74
75    pub fn is_unit(&self) -> bool {
76        (self.length_squared() - 1.0).abs() / Float::max(1.0, self.length_squared()) < TOL
77    }
78
79    pub fn dot(&self, rhs: Self) -> Float {
80        self.e[0] * rhs.e[0] + self.e[1] * rhs.e[1] + self.e[2] * rhs.e[2]
81    }
82
83    /// Create a square grid of vectors that sample a circle.
84    ///
85    /// # Arguments
86    /// - `radius` - The radius of the circle.
87    /// - `z` - The z-coordinate of the circle.
88    /// - `spacing` - The spacing of the grid. For example, a spacing of 1.0
89    ///   will sample the circle at every pair of integer coordinates, while a
90    ///   scale of 0.5 will sample the circle at every pair of half-integer
91    ///   coordinates.
92    /// - radial_offset_x: Offset the radial position of the vectors by this
93    ///   amount in x
94    /// - radial_offset_y: Offset the radial position of the vectors by this
95    ///   amount in y
96    pub fn sq_grid_in_circ(
97        radius: Float,
98        spacing: Float,
99        z: Float,
100        radial_offset_x: Float,
101        radial_offset_y: Float,
102    ) -> Vec<Self> {
103        // Upper bound is established by the Gauss Circle Problem.
104        let r_over_s = radius / spacing;
105        let num_samples = (PI * r_over_s * r_over_s + 9 as Float * r_over_s).ceil() as usize;
106
107        // Bounding box search.
108        let mut samples = Vec::with_capacity(num_samples);
109        let mut x = -radius;
110        while x <= radius {
111            let mut y = -radius;
112            while y <= radius {
113                if x * x + y * y <= radius * radius {
114                    samples.push(Self::new(x + radial_offset_x, y + radial_offset_y, z));
115                }
116                y += spacing;
117            }
118            x += spacing;
119        }
120
121        samples
122    }
123
124    /// Create a fan of uniformly spaced vectors with endpoints in a given
125    /// z-plane.
126    ///
127    /// The vectors have endpoints at an angle theta with respect to the x-axis
128    /// and extend from distances (-r + radial_offset) to (r +
129    /// radial_offset) from the point (0, 0, z).
130    ///
131    /// # Arguments
132    /// - n: Number of vectors to create
133    /// - r: Radial span of vector endpoints from [-r, r]
134    /// - theta: Angle of vectors with respect to x
135    /// - z: z-coordinate of endpoints
136    /// - radial_offset_x: Offset the radial position of the vectors by this
137    ///   amount in x
138    /// - radial_offset_y: Offset the radial position of the vectors by this
139    ///   amount in y
140    pub fn fan(
141        n: usize,
142        r: Float,
143        theta: Float,
144        z: Float,
145        radial_offset_x: Float,
146        radial_offset_y: Float,
147    ) -> Vec<Self> {
148        let mut vecs = Vec::with_capacity(n);
149        let step = 2.0 * r / (n - 1) as Float;
150        for i in 0..n {
151            let x = (-r + i as Float * step) * theta.cos() + radial_offset_x;
152            let y = (-r + i as Float * step) * theta.sin() + radial_offset_y;
153            vecs.push(Vec3::new(x, y, z));
154        }
155        vecs
156    }
157}
158
159impl PartialEq for Vec3 {
160    fn eq(&self, rhs: &Self) -> bool {
161        (self.e[0] - rhs.e[0]) * (self.e[0] - rhs.e[0])
162            + (self.e[1] - rhs.e[1]) * (self.e[1] - rhs.e[1])
163            + (self.e[2] - rhs.e[2]) * (self.e[2] - rhs.e[2])
164            < TOL * TOL
165    }
166}
167
168impl std::ops::Add<Vec3> for Vec3 {
169    type Output = Self;
170
171    fn add(self, rhs: Self) -> Self {
172        Self::new(
173            self.e[0] + rhs.e[0],
174            self.e[1] + rhs.e[1],
175            self.e[2] + rhs.e[2],
176        )
177    }
178}
179
180impl std::ops::AddAssign<Vec3> for Vec3 {
181    fn add_assign(&mut self, rhs: Self) {
182        self.e[0] += rhs.e[0];
183        self.e[1] += rhs.e[1];
184        self.e[2] += rhs.e[2];
185    }
186}
187
188impl std::ops::Neg for Vec3 {
189    type Output = Self;
190
191    fn neg(self) -> Self {
192        Self::new(-self.e[0], -self.e[1], -self.e[2])
193    }
194}
195
196impl std::ops::Sub<Vec3> for Vec3 {
197    type Output = Self;
198
199    fn sub(self, rhs: Self) -> Self {
200        Self::new(
201            self.e[0] - rhs.e[0],
202            self.e[1] - rhs.e[1],
203            self.e[2] - rhs.e[2],
204        )
205    }
206}
207
208impl std::ops::Mul<Float> for Vec3 {
209    type Output = Self;
210
211    fn mul(self, rhs: Float) -> Self {
212        Self::new(self.e[0] * rhs, self.e[1] * rhs, self.e[2] * rhs)
213    }
214}
215
216#[cfg(test)]
217mod test {
218    use super::*;
219
220    #[test]
221    fn test_sample_circle_sq_grid_unit_circle() {
222        let samples = Vec3::sq_grid_in_circ(1.0, 1.0, 0.0, 0.0, 0.0);
223        assert_eq!(samples.len(), 5);
224    }
225
226    #[test]
227    fn test_sample_circle_sq_grid_radius_2_scale_2() {
228        let samples = Vec3::sq_grid_in_circ(2.0, 2.0, 0.0, 0.0, 0.0);
229        assert_eq!(samples.len(), 5);
230    }
231
232    #[test]
233    fn test_sample_circle_sq_grid_radius_2_scale_1() {
234        let samples = Vec3::sq_grid_in_circ(2.0, 1.0, 0.0, 0.0, 0.0);
235        assert_eq!(samples.len(), 13);
236    }
237}