common/
vec3.rs

1use std::ops::{
2    Add, AddAssign, Div, DivAssign, Index, IndexMut, Mul, MulAssign, Neg, Sub,
3};
4
5/// A 3D vector with `f64` components.
6#[derive(Debug, Clone, Copy, PartialEq)]
7pub struct Vec3 {
8    /// Components stored as `[x, y, z]`.
9    pub e: [f64; 3],
10}
11
12/// Type alias for a 3D point.
13pub type Point3 = Vec3;
14/// Type alias for an RGB color (components in \[0, 1\]).
15pub type Color = Vec3;
16
17// --- Constructors and Accessors ---
18
19impl Vec3 {
20    /// Creates a new `Vec3` from three `f64` components.
21    pub fn new(e0: f64, e1: f64, e2: f64) -> Self {
22        Vec3 { e: [e0, e1, e2] }
23    }
24
25    /// Returns the x component.
26    pub fn x(self) -> f64 { self.e[0] }
27    /// Returns the y component.
28    pub fn y(self) -> f64 { self.e[1] }
29    /// Returns the z component.
30    pub fn z(self) -> f64 { self.e[2] }
31
32    /// Returns the squared length (dot product with itself).
33    pub fn length_squared(self) -> f64 {
34        self.e[0] * self.e[0] + self.e[1] * self.e[1] + self.e[2] * self.e[2]
35    }
36
37    /// Returns the Euclidean length.
38    pub fn length(self) -> f64 {
39        self.length_squared().sqrt()
40    }
41
42    /// Returns `true` if all components are very close to zero (zero-vector check).
43    pub fn near_zero(self) -> bool {
44        const S: f64 = 1e-8;
45        self.e[0].abs() < S && self.e[1].abs() < S && self.e[2].abs() < S
46    }
47}
48
49impl Default for Vec3 {
50    fn default() -> Self {
51        Vec3 { e: [0.0, 0.0, 0.0] }
52    }
53}
54
55// --- Output ---
56
57impl std::fmt::Display for Vec3 {
58    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
59        write!(f, "{} {} {}", self.e[0], self.e[1], self.e[2])
60    }
61}
62
63// --- Operator Overloads ---
64
65impl Neg for Vec3 {
66    type Output = Vec3;
67    fn neg(self) -> Vec3 {
68        Vec3::new(-self.e[0], -self.e[1], -self.e[2])
69    }
70}
71
72impl Index<usize> for Vec3 {
73    type Output = f64;
74    fn index(&self, i: usize) -> &f64 { &self.e[i] }
75}
76
77impl IndexMut<usize> for Vec3 {
78    fn index_mut(&mut self, i: usize) -> &mut f64 { &mut self.e[i] }
79}
80
81impl AddAssign for Vec3 {
82    fn add_assign(&mut self, v: Vec3) {
83        self.e[0] += v.e[0];
84        self.e[1] += v.e[1];
85        self.e[2] += v.e[2];
86    }
87}
88
89impl MulAssign<f64> for Vec3 {
90    fn mul_assign(&mut self, t: f64) {
91        self.e[0] *= t;
92        self.e[1] *= t;
93        self.e[2] *= t;
94    }
95}
96
97impl DivAssign<f64> for Vec3 {
98    fn div_assign(&mut self, t: f64) {
99        *self *= 1.0 / t;
100    }
101}
102
103impl Add for Vec3 {
104    type Output = Vec3;
105    fn add(self, v: Vec3) -> Vec3 {
106        Vec3::new(self.e[0] + v.e[0], self.e[1] + v.e[1], self.e[2] + v.e[2])
107    }
108}
109
110impl Sub for Vec3 {
111    type Output = Vec3;
112    fn sub(self, v: Vec3) -> Vec3 {
113        Vec3::new(self.e[0] - v.e[0], self.e[1] - v.e[1], self.e[2] - v.e[2])
114    }
115}
116
117impl Mul for Vec3 {
118    type Output = Vec3;
119    fn mul(self, v: Vec3) -> Vec3 {
120        Vec3::new(self.e[0] * v.e[0], self.e[1] * v.e[1], self.e[2] * v.e[2])
121    }
122}
123
124impl Mul<f64> for Vec3 {
125    type Output = Vec3;
126    fn mul(self, t: f64) -> Vec3 {
127        Vec3::new(self.e[0] * t, self.e[1] * t, self.e[2] * t)
128    }
129}
130
131impl Mul<Vec3> for f64 {
132    type Output = Vec3;
133    fn mul(self, v: Vec3) -> Vec3 { v * self }
134}
135
136impl Div<f64> for Vec3 {
137    type Output = Vec3;
138    fn div(self, t: f64) -> Vec3 { self * (1.0 / t) }
139}
140
141// --- Utility Functions ---
142
143/// Computes the dot product of two vectors.
144pub fn dot(u: Vec3, v: Vec3) -> f64 {
145    u.e[0] * v.e[0] + u.e[1] * v.e[1] + u.e[2] * v.e[2]
146}
147
148/// Computes the cross product of two vectors.
149pub fn cross(u: Vec3, v: Vec3) -> Vec3 {
150    Vec3::new(
151        u.e[1] * v.e[2] - u.e[2] * v.e[1],
152        u.e[2] * v.e[0] - u.e[0] * v.e[2],
153        u.e[0] * v.e[1] - u.e[1] * v.e[0],
154    )
155}
156
157/// Returns the unit vector in the same direction as `v`.
158pub fn unit_vector(v: Vec3) -> Vec3 {
159    v / v.length()
160}
161
162/// Computes the reflection vector (v: incident direction, n: unit surface normal).
163///
164/// **r** = **v** - 2(**v**·**n**)**n**
165pub fn reflect(v: Vec3, n: Vec3) -> Vec3 {
166    v - 2.0 * dot(v, n) * n
167}
168
169/// Computes the refraction vector (Snell's law).
170///
171/// `uv`: unit incident direction, `n`: unit normal pointing outward from the incident medium, `etai_over_etat`: η/η'.
172pub fn refract(uv: Vec3, n: Vec3, etai_over_etat: f64) -> Vec3 {
173    let cos_theta = dot(-uv, n).min(1.0);
174    let r_out_parallel = etai_over_etat * (uv + cos_theta * n);
175    let r_out_perp = -(1.0 - r_out_parallel.length_squared()).sqrt() * n;
176    r_out_parallel + r_out_perp
177}
178
179/// Returns a random point **inside** the unit sphere (rejection method).
180pub fn random_in_unit_sphere() -> Vec3 {
181    use crate::utils::random_double_range;
182    loop {
183        let p = Vec3::new(
184            random_double_range(-1.0, 1.0),
185            random_double_range(-1.0, 1.0),
186            random_double_range(-1.0, 1.0),
187        );
188        if p.length_squared() < 1.0 {
189            return p;
190        }
191    }
192}
193
194/// Returns a random point **on** the unit sphere surface (true Lambertian reflection).
195pub fn random_unit_vector() -> Vec3 {
196    unit_vector(random_in_unit_sphere())
197}
198
199/// Returns a random point inside the unit **disk** (z=0 plane, rejection method).
200pub fn random_in_unit_disk() -> Vec3 {
201    use crate::utils::random_double_range;
202    loop {
203        let p = Vec3::new(
204            random_double_range(-1.0, 1.0),
205            random_double_range(-1.0, 1.0),
206            0.0,
207        );
208        if p.length_squared() < 1.0 {
209            return p;
210        }
211    }
212}
213
214/// Returns a random point in the hemisphere around the normal (uniform hemisphere sampling).
215pub fn random_in_hemisphere(normal: Vec3) -> Vec3 {
216    let in_unit_sphere = random_in_unit_sphere();
217    if dot(in_unit_sphere, normal) > 0.0 {
218        in_unit_sphere
219    } else {
220        -in_unit_sphere
221    }
222}
223
224// --- Color Utilities ---
225
226/// Averages `pixel_color` over `samples_per_pixel` samples and converts to an "R G B" string.
227pub fn write_color(pixel_color: Color, samples_per_pixel: i32) -> String {
228    let scale = 1.0 / samples_per_pixel as f64;
229    let r = (pixel_color.x() * scale).clamp(0.0, 0.999);
230    let g = (pixel_color.y() * scale).clamp(0.0, 0.999);
231    let b = (pixel_color.z() * scale).clamp(0.0, 0.999);
232    let ir = (256.0 * r) as i32;
233    let ig = (256.0 * g) as i32;
234    let ib = (256.0 * b) as i32;
235    format!("{} {} {}", ir, ig, ib)
236}
237
238/// Averages `pixel_color` over `samples_per_pixel` samples, applies gamma=2 correction (square root), and converts to an "R G B" string.
239pub fn write_color_gamma(pixel_color: Color, samples_per_pixel: i32) -> String {
240    let scale = 1.0 / samples_per_pixel as f64;
241    let r = (pixel_color.x() * scale).sqrt().clamp(0.0, 0.999);
242    let g = (pixel_color.y() * scale).sqrt().clamp(0.0, 0.999);
243    let b = (pixel_color.z() * scale).sqrt().clamp(0.0, 0.999);
244    let ir = (256.0 * r) as i32;
245    let ig = (256.0 * g) as i32;
246    let ib = (256.0 * b) as i32;
247    format!("{} {} {}", ir, ig, ib)
248}