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
62/// Implements factory constructors for [`Occluder`].
63impl Occluder {
64 /// Creates a spherical occluder centered at `center` with `radius`.
65 ///
66 /// # Arguments
67 ///
68 /// - `Vector3D` - The sphere center.
69 /// - `f64` - The sphere radius.
70 /// - `Material` - The surface material.
71 ///
72 /// # Returns
73 ///
74 /// - `Occluder` - The new sphere occluder.
75 pub fn sphere(center: Vector3D, radius: f64, material: Material) -> Occluder {
76 Occluder {
77 kind: OccluderKind::Sphere,
78 center,
79 extent: Vector3D::new(radius, radius, radius),
80 material,
81 }
82 }
83
84 /// Creates an axis-aligned bounding-box occluder from `min` to `max`.
85 ///
86 /// # Arguments
87 ///
88 /// - `Vector3D` - The AABB minimum corner.
89 /// - `Vector3D` - The AABB maximum corner.
90 /// - `Material` - The surface material.
91 ///
92 /// # Returns
93 ///
94 /// - `Occluder` - The new AABB occluder.
95 pub fn aabb(min: Vector3D, max: Vector3D, material: Material) -> Occluder {
96 Occluder {
97 kind: OccluderKind::Aabb,
98 center: min,
99 extent: max,
100 material,
101 }
102 }
103
104 /// Returns a list of `(center, radius)` sphere tuples approximating
105 /// this occluder, suitable for [`soft_shadow_factor`].
106 ///
107 /// For sphere occluders this returns `(center, radius)`. For AABB
108 /// occluders the bounding sphere is computed conservatively from the
109 /// AABB extents.
110 ///
111 /// # Returns
112 ///
113 /// - `Vec<(Vector3D, f64)>` - One bounding sphere per occluder.
114 pub fn occluder_points(&self) -> Vec<(Vector3D, f64)> {
115 collect_occluder_points(std::slice::from_ref(self))
116 }
117}
118
119/// Implements the constructor and zero-allocation tracing entry points for
120/// [`RayTraceScene`].
121impl RayTraceScene {
122 /// Creates a new scene taking ownership of `occluders` and precomputing
123 /// the `(center, radius)` shadow bounding spheres used by
124 /// [`soft_shadow_factor`].
125 ///
126 /// # Arguments
127 ///
128 /// - `Vec<Occluder>` - All occluding surfaces in the scene.
129 ///
130 /// # Returns
131 ///
132 /// - `RayTraceScene` - The new scene with precomputed shadow data.
133 pub fn new(occluders: Vec<Occluder>) -> RayTraceScene {
134 let shadow_points: Vec<(Vector3D, f64)> = collect_occluder_points(&occluders);
135 RayTraceScene {
136 occluders,
137 shadow_points,
138 }
139 }
140
141 /// Iteratively traces a ray through the scene and returns the final
142 /// shaded color, using the [`RAYTRACE_DEFAULT_MAX_BOUNCES`] constant as
143 /// the bounce limit.
144 ///
145 /// Performs no heap allocation per ray or per bounce: the shadow
146 /// bounding spheres precomputed at construction are reused, and no
147 /// [`Material`] is cloned. Use [`RayTraceScene::trace_with_bounces`] to
148 /// override the bounce limit.
149 ///
150 /// # Arguments
151 ///
152 /// - `Ray` - The ray to trace.
153 /// - `&LightingUniforms` - Lighting parameters used during shading.
154 ///
155 /// # Returns
156 ///
157 /// - `Vector3D` - The final traced color.
158 pub fn trace(&self, ray: Ray, lights: &LightingUniforms) -> Vector3D {
159 self.trace_with_bounces(ray, lights, RAYTRACE_DEFAULT_MAX_BOUNCES)
160 }
161
162 /// Iteratively traces a ray through the scene with an explicit bounce
163 /// limit and returns the final shaded color.
164 ///
165 /// On a miss the ambient color scaled by the accumulated specular
166 /// throughput is added. On a hit the surface material is evaluated with
167 /// [`LightingUniforms::shade`] and, when the hit material has a
168 /// non-zero specular component, the trace continues with a reflected
169 /// ray up to `max_bounces` times (incrementing the ray's `depth` field
170 /// per bounce).
171 ///
172 /// # Arguments
173 ///
174 /// - `Ray` - The ray to trace.
175 /// - `&LightingUniforms` - Lighting parameters used during shading.
176 /// - `u32` - The maximum number of bounces allowed for this ray.
177 ///
178 /// # Returns
179 ///
180 /// - `Vector3D` - The final traced color.
181 pub fn trace_with_bounces(
182 &self,
183 ray: Ray,
184 lights: &LightingUniforms,
185 max_bounces: u32,
186 ) -> Vector3D {
187 trace_bounces(
188 ray,
189 self.get_occluders(),
190 self.get_shadow_points(),
191 lights,
192 max_bounces,
193 )
194 }
195
196 /// Finds the closest intersection between a ray and the scene
197 /// occluders.
198 ///
199 /// The winning occluder's [`Material`] is cloned exactly once, when the
200 /// returned [`Hit`] is constructed; losing candidates are never cloned.
201 ///
202 /// # Arguments
203 ///
204 /// - `&Ray` - The ray to test.
205 ///
206 /// # Returns
207 ///
208 /// - `Option<Hit>` - The closest hit, or `None` if the ray misses.
209 pub fn closest_hit(&self, ray: &Ray) -> Option<Hit> {
210 let occluders: &[Occluder] = self.get_occluders();
211 closest_hit_indexed(ray, occluders).map(
212 |(index, t, position, normal): (usize, f64, Vector3D, Vector3D)| Hit {
213 t,
214 position,
215 normal,
216 material: occluders[index].get_material().clone(),
217 },
218 )
219 }
220}