Skip to main content

euv_engine/raytracing/
impl.rs

1use super::*;
2
3/// Implements factory constructors and accessors for [`Ray`] and
4/// [`Occluder`].
5impl Ray {
6    /// Creates a new ray starting at `origin` pointing in `direction`.
7    ///
8    /// `t_min` and `t_max` default to [`RAYTRACE_DEFAULT_T_MIN`] and
9    /// [`RAYTRACE_DEFAULT_T_MAX`]. `depth` defaults to 0.
10    ///
11    /// # Arguments
12    ///
13    /// - `Vector3D` - The ray origin.
14    /// - `Vector3D` - The unit direction.
15    ///
16    /// # Returns
17    ///
18    /// - `Ray` - The new ray.
19    pub fn new(origin: Vector3D, direction: Vector3D) -> Ray {
20        Ray {
21            origin,
22            direction,
23            t_min: RAYTRACE_DEFAULT_T_MIN,
24            t_max: RAYTRACE_DEFAULT_T_MAX,
25            depth: 0,
26        }
27    }
28
29    /// Computes the world-space point at distance `t` along this ray.
30    ///
31    /// # Arguments
32    ///
33    /// - `f64` - The ray parameter.
34    ///
35    /// # Returns
36    ///
37    /// - `Vector3D` - `origin + direction * t`.
38    pub fn at(&self, t: f64) -> Vector3D {
39        self.get_origin() + self.get_direction().scaled(t)
40    }
41
42    /// Returns a clone of this ray with `depth` replaced by `depth`.
43    ///
44    /// # Arguments
45    ///
46    /// - `u32` - The new recursion depth.
47    ///
48    /// # Returns
49    ///
50    /// - `Ray` - The cloned ray with updated depth.
51    pub fn with_depth(&self, depth: u32) -> Ray {
52        Ray {
53            origin: self.get_origin(),
54            direction: self.get_direction(),
55            t_min: self.get_t_min(),
56            t_max: self.get_t_max(),
57            depth,
58        }
59    }
60
61    /// Intersects this ray with the triangle `v0`, `v1`, `v2` using the
62    /// Moller-Trumbore algorithm, keeping only hits inside this ray's
63    /// `t_min`..=`t_max` range.
64    ///
65    /// The test is two-sided: a hit from either face counts, and the
66    /// returned normal always faces against the ray direction.
67    ///
68    /// # Arguments
69    ///
70    /// - `Vector3D` - The first triangle vertex.
71    /// - `Vector3D` - The second triangle vertex.
72    /// - `Vector3D` - The third triangle vertex.
73    ///
74    /// # Returns
75    ///
76    /// - `Option<(f64, Vector3D)>` - The hit distance along the ray and the
77    ///   unit normal oriented against the ray direction, or `None` on miss.
78    pub fn intersect_triangle(
79        &self,
80        v0: Vector3D,
81        v1: Vector3D,
82        v2: Vector3D,
83    ) -> Option<(f64, Vector3D)> {
84        let (t, normal): (f64, Vector3D) =
85            intersect_triangle(self.get_origin(), self.get_direction(), v0, v1, v2)?;
86        if t < self.get_t_min() || t > self.get_t_max() {
87            return None;
88        }
89        Some((t, normal))
90    }
91}
92
93/// Implements factory constructors for [`Occluder`].
94impl Occluder {
95    /// Creates a spherical occluder centered at `center` with `radius`.
96    ///
97    /// # Arguments
98    ///
99    /// - `Vector3D` - The sphere center.
100    /// - `f64` - The sphere radius.
101    /// - `Material` - The surface material.
102    ///
103    /// # Returns
104    ///
105    /// - `Occluder` - The new sphere occluder.
106    pub fn sphere(center: Vector3D, radius: f64, material: Material) -> Occluder {
107        Occluder {
108            kind: OccluderKind::Sphere,
109            center,
110            extent: Vector3D::new(radius, radius, radius),
111            vertices: [center; 3],
112            material,
113        }
114    }
115
116    /// Creates an axis-aligned bounding-box occluder from `min` to `max`.
117    ///
118    /// # Arguments
119    ///
120    /// - `Vector3D` - The AABB minimum corner.
121    /// - `Vector3D` - The AABB maximum corner.
122    /// - `Material` - The surface material.
123    ///
124    /// # Returns
125    ///
126    /// - `Occluder` - The new AABB occluder.
127    pub fn aabb(min: Vector3D, max: Vector3D, material: Material) -> Occluder {
128        Occluder {
129            kind: OccluderKind::Aabb,
130            center: min,
131            extent: max,
132            vertices: [min; 3],
133            material,
134        }
135    }
136
137    /// Creates a triangular occluder from its three vertices.
138    ///
139    /// The vertex order fixes the geometric winding; because the
140    /// intersection test is two-sided the winding does not affect which
141    /// faces produce hits, only the raw cross-product normal before it is
142    /// oriented against the incoming ray.
143    ///
144    /// # Arguments
145    ///
146    /// - `Vector3D` - The first triangle vertex.
147    /// - `Vector3D` - The second triangle vertex.
148    /// - `Vector3D` - The third triangle vertex.
149    /// - `Material` - The surface material.
150    ///
151    /// # Returns
152    ///
153    /// - `Occluder` - The new triangle occluder.
154    pub fn triangle(v0: Vector3D, v1: Vector3D, v2: Vector3D, material: Material) -> Occluder {
155        Occluder {
156            kind: OccluderKind::Triangle,
157            center: Vector3D::zero(),
158            extent: Vector3D::zero(),
159            vertices: [v0, v1, v2],
160            material,
161        }
162    }
163
164    /// Returns a list of `(center, radius)` sphere tuples approximating
165    /// this occluder, suitable for [`soft_shadow_factor`].
166    ///
167    /// For sphere occluders this returns `(center, radius)`. For AABB
168    /// occluders the bounding sphere is computed conservatively from the
169    /// AABB extents.
170    ///
171    /// # Returns
172    ///
173    /// - `Vec<(Vector3D, f64)>` - One bounding sphere per occluder.
174    pub fn occluder_points(&self) -> Vec<(Vector3D, f64)> {
175        collect_occluder_points(std::slice::from_ref(self))
176    }
177}
178
179/// Implements the constructor and zero-allocation tracing entry points for
180/// [`RayTraceScene`].
181impl RayTraceScene {
182    /// Creates a new scene taking ownership of `occluders` and precomputing
183    /// the `(center, radius)` shadow bounding spheres used by
184    /// [`soft_shadow_factor`].
185    ///
186    /// # Arguments
187    ///
188    /// - `Vec<Occluder>` - All occluding surfaces in the scene.
189    ///
190    /// # Returns
191    ///
192    /// - `RayTraceScene` - The new scene with precomputed shadow data.
193    pub fn new(occluders: Vec<Occluder>) -> RayTraceScene {
194        let shadow_points: Vec<(Vector3D, f64)> = collect_occluder_points(&occluders);
195        RayTraceScene {
196            occluders,
197            shadow_points,
198        }
199    }
200
201    /// Iteratively traces a ray through the scene and returns the final
202    /// shaded color, using the [`RAYTRACE_DEFAULT_MAX_BOUNCES`] constant as
203    /// the bounce limit.
204    ///
205    /// Performs no heap allocation per ray or per bounce: the shadow
206    /// bounding spheres precomputed at construction are reused, and no
207    /// [`Material`] is cloned. Use [`RayTraceScene::trace_with_bounces`] to
208    /// override the bounce limit.
209    ///
210    /// # Arguments
211    ///
212    /// - `Ray` - The ray to trace.
213    /// - `&LightingUniforms` - Lighting parameters used during shading.
214    ///
215    /// # Returns
216    ///
217    /// - `Vector3D` - The final traced color.
218    pub fn trace(&self, ray: Ray, lights: &LightingUniforms) -> Vector3D {
219        self.trace_with_bounces(ray, lights, RAYTRACE_DEFAULT_MAX_BOUNCES)
220    }
221
222    /// Iteratively traces a ray through the scene with an explicit bounce
223    /// limit and returns the final shaded color.
224    ///
225    /// On a miss the ambient color scaled by the accumulated specular
226    /// throughput is added. On a hit the surface material is evaluated with
227    /// [`LightingUniforms::shade`] and, when the hit material has a
228    /// non-zero specular component, the trace continues with a reflected
229    /// ray up to `max_bounces` times (incrementing the ray's `depth` field
230    /// per bounce).
231    ///
232    /// # Arguments
233    ///
234    /// - `Ray` - The ray to trace.
235    /// - `&LightingUniforms` - Lighting parameters used during shading.
236    /// - `u32` - The maximum number of bounces allowed for this ray.
237    ///
238    /// # Returns
239    ///
240    /// - `Vector3D` - The final traced color.
241    pub fn trace_with_bounces(
242        &self,
243        ray: Ray,
244        lights: &LightingUniforms,
245        max_bounces: u32,
246    ) -> Vector3D {
247        trace_bounces(
248            ray,
249            self.get_occluders(),
250            self.get_shadow_points(),
251            lights,
252            max_bounces,
253        )
254    }
255
256    /// Finds the closest intersection between a ray and the scene
257    /// occluders.
258    ///
259    /// The winning occluder's [`Material`] is cloned exactly once, when the
260    /// returned [`Hit`] is constructed; losing candidates are never cloned.
261    ///
262    /// # Arguments
263    ///
264    /// - `&Ray` - The ray to test.
265    ///
266    /// # Returns
267    ///
268    /// - `Option<Hit>` - The closest hit, or `None` if the ray misses.
269    pub fn closest_hit(&self, ray: &Ray) -> Option<Hit> {
270        let occluders: &[Occluder] = self.get_occluders();
271        closest_hit_indexed(ray, occluders).map(
272            |(index, t, position, normal): (usize, f64, Vector3D, Vector3D)| Hit {
273                t,
274                position,
275                normal,
276                material: occluders[index].get_material().clone(),
277            },
278        )
279    }
280}