1use super::*;
2
3fn vector3d_min(a: Vector3D, b: Vector3D) -> Vector3D {
13 Vector3D::new(
14 a.get_x().min(b.get_x()),
15 a.get_y().min(b.get_y()),
16 a.get_z().min(b.get_z()),
17 )
18}
19
20fn vector3d_max(a: Vector3D, b: Vector3D) -> Vector3D {
30 Vector3D::new(
31 a.get_x().max(b.get_x()),
32 a.get_y().max(b.get_y()),
33 a.get_z().max(b.get_z()),
34 )
35}
36
37fn is_finite_vector(value: Vector3D) -> bool {
52 value.get_x().is_finite() && value.get_y().is_finite() && value.get_z().is_finite()
53}
54
55fn slab_interval(
71 origin_component: f64,
72 dir_component: f64,
73 lower: f64,
74 upper: f64,
75) -> Option<(f64, f64)> {
76 if dir_component.abs() < RAYTRACE_TRIANGLE_EPSILON {
77 if origin_component < lower || origin_component > upper {
78 return None;
79 }
80 return Some((f64::NEG_INFINITY, f64::INFINITY));
81 }
82 let inverse: f64 = 1.0 / dir_component;
83 let t0: f64 = (lower - origin_component) * inverse;
84 let t1: f64 = (upper - origin_component) * inverse;
85 Some((t0.min(t1), t0.max(t1)))
86}
87
88fn aabb_entry_parameter(ray: &Ray, lower: Vector3D, upper: Vector3D) -> Option<f64> {
107 let origin: Vector3D = ray.get_origin();
108 let dir: Vector3D = ray.get_direction();
109 if !is_finite_vector(origin)
110 || !is_finite_vector(dir)
111 || !is_finite_vector(lower)
112 || !is_finite_vector(upper)
113 {
114 return None;
115 }
116 let x: Option<(f64, f64)> =
117 slab_interval(origin.get_x(), dir.get_x(), lower.get_x(), upper.get_x());
118 let y: Option<(f64, f64)> =
119 slab_interval(origin.get_y(), dir.get_y(), lower.get_y(), upper.get_y());
120 let z: Option<(f64, f64)> =
121 slab_interval(origin.get_z(), dir.get_z(), lower.get_z(), upper.get_z());
122 let (x0, x1): (f64, f64) = x?;
123 let (y0, y1): (f64, f64) = y?;
124 let (z0, z1): (f64, f64) = z?;
125 let t_enter: f64 = x0.max(y0).max(z0);
126 let t_exit: f64 = x1.min(y1).min(z1);
127 if t_enter > t_exit {
128 return None;
129 }
130 let clipped: f64 = t_enter.max(ray.get_t_min());
131 if clipped > t_exit.min(ray.get_t_max()) {
132 return None;
133 }
134 Some(clipped)
135}
136
137pub fn intersect_triangle(ray: &Ray, vertices: [Vector3D; 3]) -> Option<(f64, Vector3D)> {
161 let [v0, v1, v2]: [Vector3D; 3] = vertices;
162 let origin: Vector3D = ray.get_origin();
163 let dir: Vector3D = ray.get_direction();
164 let edge1: Vector3D = v1 - v0;
165 let edge2: Vector3D = v2 - v0;
166 let pvec: Vector3D = dir.cross(edge2);
167 let det: f64 = edge1.dot(pvec);
168 if det.abs() < RAYTRACE_TRIANGLE_EPSILON {
169 return None;
170 }
171 let inverse_det: f64 = 1.0 / det;
172 let tvec: Vector3D = origin - v0;
173 let u: f64 = tvec.dot(pvec) * inverse_det;
174 if !(0.0..=1.0).contains(&u) {
175 return None;
176 }
177 let qvec: Vector3D = tvec.cross(edge1);
178 let v: f64 = dir.dot(qvec) * inverse_det;
179 let v_usable: bool = v.is_finite() && (0.0..=1.0).contains(&v);
183 let edge_usable: bool = (u + v).is_finite() && (u + v <= 1.0);
184 if !v_usable || !edge_usable {
185 return None;
186 }
187 let t: f64 = edge2.dot(qvec) * inverse_det;
188 if !t.is_finite() {
189 return None;
190 }
191 let geometric: Vector3D = edge1.cross(edge2).normalized();
192 let normal: Vector3D = if geometric.dot(dir) > 0.0 {
193 -geometric
194 } else {
195 geometric
196 };
197 Some((t, normal))
198}
199
200fn occluder_aabb_extents(occluder: &Occluder) -> (Vector3D, Vector3D) {
215 let (mn, mx): (Vector3D, Vector3D) = match occluder.get_kind() {
216 OccluderKind::Aabb => (occluder.get_center(), occluder.get_extent()),
217 OccluderKind::Sphere => {
218 let center: Vector3D = occluder.get_center();
219 let radius: f64 = occluder.get_extent().get_x();
220 let r: Vector3D = Vector3D::new(radius, radius, radius);
221 (center - r, center + r)
222 }
223 OccluderKind::Triangle => {
224 let [v0, v1, v2]: [Vector3D; 3] = occluder.get_vertices();
225 let lo: Vector3D = vector3d_min(v0, vector3d_min(v1, v2));
226 let hi: Vector3D = vector3d_max(v0, vector3d_max(v1, v2));
227 (lo, hi)
228 }
229 };
230 (vector3d_min(mn, mx), vector3d_max(mn, mx))
231}
232
233pub(crate) fn collect_occluder_points(occluders: &[Occluder]) -> Vec<(Vector3D, f64)> {
247 let mut out: Vec<(Vector3D, f64)> = Vec::new();
248 for occ in occluders.iter() {
249 let (mn, mx): (Vector3D, Vector3D) = occluder_aabb_extents(occ);
250 let cx: f64 = (mn.get_x() + mx.get_x()) * 0.5;
251 let cy: f64 = (mn.get_y() + mx.get_y()) * 0.5;
252 let cz: f64 = (mn.get_z() + mx.get_z()) * 0.5;
253 let ex: f64 = (mx.get_x() - mn.get_x()) * 0.5;
254 let ey: f64 = (mx.get_y() - mn.get_y()) * 0.5;
255 let ez: f64 = (mx.get_z() - mn.get_z()) * 0.5;
256 let r: f64 = (ex * ex + ey * ey + ez * ez).sqrt();
257 out.push((Vector3D::new(cx, cy, cz), r));
258 }
259 out
260}
261
262fn aabb_surface_area(lower: Vector3D, upper: Vector3D) -> f64 {
274 let dx: f64 = (upper.get_x() - lower.get_x()).max(0.0);
275 let dy: f64 = (upper.get_y() - lower.get_y()).max(0.0);
276 let dz: f64 = (upper.get_z() - lower.get_z()).max(0.0);
277 2.0 * (dx * dy + dy * dz + dz * dx)
278}
279
280fn axis_component(value: Vector3D, axis: usize) -> f64 {
291 match axis {
292 0 => value.get_x(),
293 1 => value.get_y(),
294 _ => value.get_z(),
295 }
296}
297
298fn intersect_sphere_in_range(ray: &Ray, center: Vector3D, radius: f64) -> Option<(f64, Vector3D)> {
320 if !radius.is_finite() || radius <= 0.0 {
324 return None;
325 }
326 let origin: Vector3D = ray.get_origin();
327 let dir: Vector3D = ray.get_direction();
328 if !is_finite_vector(origin) || !is_finite_vector(dir) || !is_finite_vector(center) {
329 return None;
330 }
331 let a: f64 = dir.dot(dir);
332 if !a.is_finite() || a <= 0.0 {
336 return None;
337 }
338 let t_min: f64 = ray.get_t_min();
339 let t_max: f64 = ray.get_t_max();
340 let oc: Vector3D = origin - center;
341 let half_b: f64 = oc.dot(dir);
342 let c: f64 = oc.dot(oc) - radius * radius;
343 let discriminant: f64 = half_b * half_b - a * c;
344 let discriminant_usable: bool = discriminant.is_finite() && discriminant >= 0.0;
347 if !discriminant_usable {
348 return None;
349 }
350 let root: f64 = discriminant.sqrt();
351 let near: f64 = (-half_b - root) / a;
352 let far: f64 = (-half_b + root) / a;
353 let t: f64 = if near >= t_min && near <= t_max {
354 near
355 } else if far >= t_min && far <= t_max {
356 far
357 } else {
358 return None;
359 };
360 let normal: Vector3D = (origin + dir.scaled(t) - center).normalized();
361 Some((t, normal))
362}
363
364fn intersect_aabb_in_range(ray: &Ray, lower: Vector3D, upper: Vector3D) -> Option<(f64, Vector3D)> {
385 let origin: Vector3D = ray.get_origin();
386 let dir: Vector3D = ray.get_direction();
387 let t_min: f64 = ray.get_t_min();
388 let t_max: f64 = ray.get_t_max();
389 if !is_finite_vector(origin) || !is_finite_vector(dir) {
390 return None;
391 }
392 if !is_finite_vector(lower) || !is_finite_vector(upper) {
393 return None;
394 }
395 let lo: Vector3D = vector3d_min(lower, upper);
396 let hi: Vector3D = vector3d_max(lower, upper);
397 if lo.get_x() >= hi.get_x() || lo.get_y() >= hi.get_y() || lo.get_z() >= hi.get_z() {
401 return None;
402 }
403 let origins: [f64; 3] = [origin.get_x(), origin.get_y(), origin.get_z()];
404 let dirs: [f64; 3] = [dir.get_x(), dir.get_y(), dir.get_z()];
405 let los: [f64; 3] = [lo.get_x(), lo.get_y(), lo.get_z()];
406 let his: [f64; 3] = [hi.get_x(), hi.get_y(), hi.get_z()];
407 let mut t_enter: f64 = f64::NEG_INFINITY;
408 let mut t_exit: f64 = f64::INFINITY;
409 let mut enter_axis: usize = 0;
410 let mut exit_axis: usize = 0;
411 for axis in 0..3 {
412 let d: f64 = dirs[axis];
413 let o: f64 = origins[axis];
414 if d.abs() < RAYTRACE_TRIANGLE_EPSILON {
415 if o < los[axis] || o > his[axis] {
416 return None;
417 }
418 continue;
419 }
420 let inverse: f64 = 1.0 / d;
421 let plane_a: f64 = (los[axis] - o) * inverse;
422 let plane_b: f64 = (his[axis] - o) * inverse;
423 let (near, far): (f64, f64) = if plane_a <= plane_b {
429 (plane_a, plane_b)
430 } else {
431 (plane_b, plane_a)
432 };
433 if near > t_enter {
434 t_enter = near;
435 enter_axis = axis;
436 }
437 if far < t_exit {
438 t_exit = far;
439 exit_axis = axis;
440 }
441 }
442 if t_enter > t_exit {
443 return None;
444 }
445 let inside: bool = t_enter <= 0.0 && t_exit >= 0.0;
453 let t: f64 = if inside { t_exit } else { t_enter };
454 if !t.is_finite() {
455 return None;
456 }
457 if t < t_min {
458 return None;
459 }
460 if t > t_max {
461 return None;
462 }
463 let axis: usize = if inside { exit_axis } else { enter_axis };
464 let travelling_positive: bool = dirs[axis] > 0.0;
471 let sign: f64 = if inside {
472 if travelling_positive { 1.0 } else { -1.0 }
473 } else if travelling_positive {
474 -1.0
475 } else {
476 1.0
477 };
478 let normal: Vector3D = match axis {
479 0 => Vector3D::new(sign, 0.0, 0.0),
480 1 => Vector3D::new(0.0, sign, 0.0),
481 _ => Vector3D::new(0.0, 0.0, sign),
482 };
483 Some((t, normal))
484}
485
486fn intersect_occluder_in_range(occluder: &Occluder, ray: &Ray) -> Option<(f64, Vector3D)> {
504 let t_min: f64 = ray.get_t_min();
505 let t_max: f64 = ray.get_t_max();
506 match occluder.get_kind() {
507 OccluderKind::Sphere => {
508 intersect_sphere_in_range(ray, occluder.get_center(), occluder.get_extent().get_x())
509 }
510 OccluderKind::Aabb => {
511 let (lower, upper): (Vector3D, Vector3D) = occluder_aabb_extents(occluder);
512 intersect_aabb_in_range(ray, lower, upper)
513 }
514 OccluderKind::Triangle => match intersect_triangle(ray, occluder.get_vertices()) {
515 Some((t, normal)) if t.is_finite() && t >= t_min && t <= t_max => Some((t, normal)),
516 _ => None,
517 },
518 }
519}
520
521fn bounds_of_range(indices: &[usize], build: &BvhBuild, range: BvhRange) -> (Vector3D, Vector3D) {
533 let mut lower: Vector3D = Vector3D::new(f64::INFINITY, f64::INFINITY, f64::INFINITY);
534 let mut upper: Vector3D =
535 Vector3D::new(f64::NEG_INFINITY, f64::NEG_INFINITY, f64::NEG_INFINITY);
536 for slot in 0..range.count {
537 let primitive: usize = indices[range.start + slot];
538 lower = vector3d_min(lower, build.lower[primitive]);
539 upper = vector3d_max(upper, build.upper[primitive]);
540 }
541 (lower, upper)
542}
543
544fn longest_centroid_axis(indices: &[usize], build: &BvhBuild, range: BvhRange) -> SahSplit {
560 let centroids: &[Vector3D] = &build.centroids;
561 let mut best_axis: usize = 0;
562 let mut best_span: f64 = -1.0;
563 let mut best_mid: f64 = 0.0;
564 for axis in 0..3 {
565 let mut min: f64 = f64::INFINITY;
566 let mut max: f64 = f64::NEG_INFINITY;
567 for slot in 0..range.count {
568 let value: f64 = axis_component(centroids[indices[range.start + slot]], axis);
569 min = min.min(value);
570 max = max.max(value);
571 }
572 let span: f64 = max - min;
573 if span > best_span {
574 best_span = span;
575 best_axis = axis;
576 best_mid = (min + max) * 0.5;
577 }
578 }
579 SahSplit {
580 axis: best_axis,
581 boundary: best_mid,
582 }
583}
584
585fn partition_range(
605 indices: &mut [usize],
606 build: &BvhBuild,
607 range: BvhRange,
608 split: SahSplit,
609) -> usize {
610 let centroids: &[Vector3D] = &build.centroids;
611 let mut left: usize = 0;
612 let mut right: usize = range.count;
613 while left < right {
614 while left < right
615 && axis_component(centroids[indices[range.start + left]], split.axis) < split.boundary
616 {
617 left += 1;
618 }
619 while right > left
620 && axis_component(centroids[indices[range.start + right - 1]], split.axis)
621 >= split.boundary
622 {
623 right -= 1;
624 }
625 if left >= right {
626 break;
627 }
628 indices.swap(range.start + left, range.start + right - 1);
629 left += 1;
630 right -= 1;
631 }
632 left
633}
634
635fn choose_sah_split(
656 indices: &[usize],
657 build: &BvhBuild,
658 range: BvhRange,
659 parent_area: f64,
660) -> Option<SahSplit> {
661 let count: usize = range.count;
662 if count < 2 || !parent_area.is_finite() || parent_area <= 0.0 {
663 return None;
664 }
665 let centroids: &[Vector3D] = &build.centroids;
666 let mut best: Option<(usize, f64, f64)> = None;
667 for axis in 0..3 {
668 let mut centroid_min: f64 = f64::INFINITY;
669 let mut centroid_max: f64 = f64::NEG_INFINITY;
670 for slot in 0..count {
671 let value: f64 = axis_component(centroids[indices[range.start + slot]], axis);
672 centroid_min = centroid_min.min(value);
673 centroid_max = centroid_max.max(value);
674 }
675 let span: f64 = centroid_max - centroid_min;
676 if !span.is_finite() || span <= RAYTRACE_BVH_AXIS_EPSILON {
677 continue;
678 }
679 let scale: f64 = RAYTRACE_BVH_BINS as f64 / span;
680 let mut bin_lower: [Vector3D; RAYTRACE_BVH_BINS] = [Vector3D::zero(); RAYTRACE_BVH_BINS];
681 let mut bin_upper: [Vector3D; RAYTRACE_BVH_BINS] = [Vector3D::zero(); RAYTRACE_BVH_BINS];
682 let mut bin_count: [usize; RAYTRACE_BVH_BINS] = [0; RAYTRACE_BVH_BINS];
683 for slot in 0..count {
684 let primitive: usize = indices[range.start + slot];
685 let scaled: f64 = (axis_component(centroids[primitive], axis) - centroid_min) * scale;
686 let mut bin: usize = scaled as usize;
687 if bin >= RAYTRACE_BVH_BINS {
688 bin = RAYTRACE_BVH_BINS - 1;
689 }
690 if bin_count[bin] == 0 {
691 bin_lower[bin] = build.lower[primitive];
692 bin_upper[bin] = build.upper[primitive];
693 } else {
694 bin_lower[bin] = vector3d_min(bin_lower[bin], build.lower[primitive]);
695 bin_upper[bin] = vector3d_max(bin_upper[bin], build.upper[primitive]);
696 }
697 bin_count[bin] += 1;
698 }
699 let mut left_area: [f64; RAYTRACE_BVH_BINS] = [0.0; RAYTRACE_BVH_BINS];
700 let mut left_count: [usize; RAYTRACE_BVH_BINS] = [0; RAYTRACE_BVH_BINS];
701 let mut running_lower: Vector3D =
702 Vector3D::new(f64::INFINITY, f64::INFINITY, f64::INFINITY);
703 let mut running_upper: Vector3D =
704 Vector3D::new(f64::NEG_INFINITY, f64::NEG_INFINITY, f64::NEG_INFINITY);
705 let mut running_count: usize = 0;
706 for bin in 0..RAYTRACE_BVH_BINS {
707 if bin_count[bin] > 0 {
708 running_lower = vector3d_min(running_lower, bin_lower[bin]);
709 running_upper = vector3d_max(running_upper, bin_upper[bin]);
710 running_count += bin_count[bin];
711 }
712 left_area[bin] = aabb_surface_area(running_lower, running_upper);
713 left_count[bin] = running_count;
714 }
715 let mut right_lower: Vector3D = Vector3D::new(f64::INFINITY, f64::INFINITY, f64::INFINITY);
716 let mut right_upper: Vector3D =
717 Vector3D::new(f64::NEG_INFINITY, f64::NEG_INFINITY, f64::NEG_INFINITY);
718 let mut right_count: usize = 0;
719 for bin in (1..RAYTRACE_BVH_BINS).rev() {
720 if bin_count[bin] > 0 {
721 right_lower = vector3d_min(right_lower, bin_lower[bin]);
722 right_upper = vector3d_max(right_upper, bin_upper[bin]);
723 right_count += bin_count[bin];
724 }
725 let left_total: usize = left_count[bin - 1];
726 if left_total == 0 || right_count == 0 {
727 continue;
728 }
729 let cost: f64 = left_area[bin - 1] * left_total as f64
730 + aabb_surface_area(right_lower, right_upper) * right_count as f64;
731 if matches!(best, Some((_, _, incumbent)) if cost >= incumbent) {
732 continue;
733 }
734 best = Some((axis, centroid_min + bin as f64 / scale, cost));
735 }
736 }
737 let (axis, boundary, cost): (usize, f64, f64) = best?;
738 let incumbent: f64 = count as f64 * parent_area;
739 if !cost.is_finite() || cost >= incumbent {
740 return None;
741 }
742 Some(SahSplit { axis, boundary })
743}
744
745fn build_bvh_subtree(context: &mut BvhBuildContext, subtree: BvhSubtree) {
766 let root: usize = context.nodes.len();
767 let bounds: (Vector3D, Vector3D) = subtree.bounds;
768 let range: BvhRange = subtree.range;
769 let too_small_to_split: bool = range.count <= RAYTRACE_BVH_LEAF_SIZE || range.count < 2;
770 if too_small_to_split || subtree.depth >= RAYTRACE_BVH_MAX_DEPTH {
771 context.nodes.push(RayTraceNode {
772 lower: bounds.0,
773 upper: bounds.1,
774 start: range.start,
775 count: range.count,
776 escape: root + 1,
777 });
778 return;
779 }
780 let build: &BvhBuild = context.build;
781 let split: SahSplit = match choose_sah_split(
782 context.indices,
783 build,
784 range,
785 aabb_surface_area(bounds.0, bounds.1),
786 ) {
787 Some(chosen) => chosen,
788 None => longest_centroid_axis(context.indices, build, range),
789 };
790 let partitioned: usize = partition_range(context.indices, build, range, split);
791 let left_count: usize = if partitioned == 0 || partitioned == range.count {
792 range.count / 2
793 } else {
794 partitioned
795 };
796 context.nodes.push(RayTraceNode {
797 lower: bounds.0,
798 upper: bounds.1,
799 start: range.start,
800 count: 0,
801 escape: root + 1,
802 });
803 let left_range: BvhRange = BvhRange {
804 start: range.start,
805 count: left_count,
806 };
807 let left_bounds: (Vector3D, Vector3D) = bounds_of_range(context.indices, build, left_range);
808 build_bvh_subtree(
809 context,
810 BvhSubtree {
811 bounds: left_bounds,
812 range: left_range,
813 depth: subtree.depth + 1,
814 },
815 );
816 let right_range: BvhRange = BvhRange {
817 start: range.start + left_count,
818 count: range.count - left_count,
819 };
820 let right_bounds: (Vector3D, Vector3D) = bounds_of_range(context.indices, build, right_range);
821 build_bvh_subtree(
822 context,
823 BvhSubtree {
824 bounds: right_bounds,
825 range: right_range,
826 depth: subtree.depth + 1,
827 },
828 );
829 context.nodes[root].escape = context.nodes.len();
830}
831
832pub(crate) fn build_bvh(occluders: &[Occluder]) -> (Vec<RayTraceNode>, Vec<usize>) {
848 let count: usize = occluders.len();
849 let mut nodes: Vec<RayTraceNode> = Vec::new();
850 let mut indices: Vec<usize> = Vec::with_capacity(count);
851 if count == 0 {
852 return (nodes, indices);
853 }
854 let mut lower: Vec<Vector3D> = Vec::with_capacity(count);
855 let mut upper: Vec<Vector3D> = Vec::with_capacity(count);
856 let mut centroids: Vec<Vector3D> = Vec::with_capacity(count);
857 for (index, occluder) in occluders.iter().enumerate() {
858 let (lo, hi): (Vector3D, Vector3D) = occluder_aabb_extents(occluder);
859 let midpoint: Vector3D = (lo + hi).scaled(0.5);
860 let centroid: Vector3D = if is_finite_vector(midpoint) {
861 midpoint
862 } else {
863 Vector3D::zero()
864 };
865 lower.push(lo);
866 upper.push(hi);
867 centroids.push(centroid);
868 indices.push(index);
869 }
870 let build: BvhBuild = BvhBuild {
871 lower,
872 upper,
873 centroids,
874 };
875 let root_range: BvhRange = BvhRange { start: 0, count };
876 let root_bounds: (Vector3D, Vector3D) = bounds_of_range(&indices, &build, root_range);
877 nodes.reserve(count.saturating_mul(2));
878 let mut context: BvhBuildContext = BvhBuildContext {
879 nodes: &mut nodes,
880 indices: &mut indices,
881 build: &build,
882 };
883 build_bvh_subtree(
884 &mut context,
885 BvhSubtree {
886 bounds: root_bounds,
887 range: root_range,
888 depth: 0,
889 },
890 );
891 (nodes, indices)
892}
893
894fn ray_is_traceable(ray: &Ray) -> bool {
908 let t_min: f64 = ray.get_t_min();
909 let t_max: f64 = ray.get_t_max();
910 if !t_min.is_finite() || !t_max.is_finite() || t_min > t_max {
911 return false;
912 }
913 is_finite_vector(ray.get_origin()) && is_finite_vector(ray.get_direction())
914}
915
916fn traceable_direction_of(ray: &Ray) -> Option<Vector3D> {
934 if !ray_is_traceable(ray) {
935 return None;
936 }
937 let dir: Vector3D = ray.get_direction();
938 let squared_length: f64 = dir.dot(dir);
939 if !squared_length.is_finite() || squared_length <= 0.0 {
940 return None;
941 }
942 Some(dir)
943}
944
945fn linear_closest_hit(
962 ray: &Ray,
963 occluders: &[Occluder],
964) -> Option<(usize, f64, Vector3D, Vector3D)> {
965 let origin: Vector3D = ray.get_origin();
966 let dir: Vector3D = ray.get_direction();
967 let mut best: Option<(usize, f64, Vector3D, Vector3D)> = None;
968 for (index, occluder) in occluders.iter().enumerate() {
969 let hit: Option<(f64, Vector3D)> = intersect_occluder_in_range(occluder, ray);
970 let Some((t, normal)) = hit else {
971 continue;
972 };
973 let nearer: bool = match &best {
974 None => true,
975 Some((best_index, best_t, _, _)) => {
976 t < *best_t || (t == *best_t && index < *best_index)
977 }
978 };
979 if nearer {
980 best = Some((index, t, origin + dir.scaled(t), normal));
981 }
982 }
983 best
984}
985
986fn linear_occluded(ray: &Ray, occluders: &[Occluder]) -> bool {
997 occluders
998 .iter()
999 .any(|occluder: &Occluder| intersect_occluder_in_range(occluder, ray).is_some())
1000}
1001
1002pub(crate) fn closest_hit_indexed(
1023 ray: &Ray,
1024 scene: &RayTraceScene,
1025) -> Option<(usize, f64, Vector3D, Vector3D)> {
1026 traceable_direction_of(ray)?;
1031 let nodes: &[RayTraceNode] = scene.get_nodes();
1032 if nodes.is_empty() {
1033 return None;
1034 }
1035 let occluders: &[Occluder] = scene.get_occluders();
1036 if occluders.len() <= RAYTRACE_LINEAR_MAX_PRIMITIVES {
1037 return linear_closest_hit(ray, occluders);
1038 }
1039 let indices: &[usize] = scene.get_indices();
1040 let origin: Vector3D = ray.get_origin();
1041 let t_min: f64 = ray.get_t_min();
1042 let mut best_t: f64 = ray.get_t_max();
1043 let mut best_index: usize = 0;
1044 let mut best_normal: Vector3D = Vector3D::zero();
1045 let mut found: bool = false;
1046 let mut stack: [usize; RAYTRACE_BVH_STACK_SIZE] = [0usize; RAYTRACE_BVH_STACK_SIZE];
1047 let root: &RayTraceNode = &nodes[0];
1048 let dir: Vector3D = ray.get_direction();
1049 let _root_entry: f64 = aabb_entry_parameter(ray, root.get_lower(), root.get_upper())?;
1053 stack[0] = 0usize;
1054 let mut depth: usize = 1;
1055 while depth > 0 {
1056 depth -= 1;
1057 let node_index: usize = stack[depth];
1058 let node: RayTraceNode = nodes[node_index];
1059 if node.get_count() == 0 {
1060 let left_index: usize = node_index + 1;
1061 let right_index: usize = nodes[left_index].get_escape();
1062 let left: &RayTraceNode = &nodes[left_index];
1063 let right: &RayTraceNode = &nodes[right_index];
1064 let probe: Ray = ray.with_range(t_min, best_t);
1065 let left_entry: Option<f64> =
1066 aabb_entry_parameter(&probe, left.get_lower(), left.get_upper());
1067 let right_entry: Option<f64> =
1068 aabb_entry_parameter(&probe, right.get_lower(), right.get_upper());
1069 match (left_entry, right_entry) {
1070 (None, None) => {}
1071 (Some(_), None) => {
1072 stack[depth] = left_index;
1073 depth += 1;
1074 }
1075 (None, Some(_)) => {
1076 stack[depth] = right_index;
1077 depth += 1;
1078 }
1079 (Some(left_t), Some(right_t)) => {
1080 let near_first: usize = if left_t <= right_t {
1081 left_index
1082 } else {
1083 right_index
1084 };
1085 stack[depth] = if left_t <= right_t {
1086 right_index
1087 } else {
1088 left_index
1089 };
1090 depth += 1;
1091 stack[depth] = near_first;
1092 depth += 1;
1093 }
1094 }
1095 continue;
1096 }
1097 let start: usize = node.get_start();
1098 let mut probe: Ray = ray.with_range(t_min, best_t);
1099 for slot in 0..node.get_count() {
1100 let primitive: usize = indices[start + slot];
1101 let hit: Option<(f64, Vector3D)> =
1102 intersect_occluder_in_range(&occluders[primitive], &probe);
1103 if let Some((t, normal)) = hit {
1104 let nearer: bool = t < best_t;
1105 let stable_tie: bool = t == best_t && (!found || primitive < best_index);
1106 if nearer || stable_tie {
1107 best_t = t;
1108 best_index = primitive;
1109 best_normal = normal;
1110 found = true;
1111 probe = ray.with_range(t_min, best_t);
1112 }
1113 }
1114 }
1115 }
1116 if !found {
1117 return None;
1118 }
1119 Some((best_index, best_t, origin + dir.scaled(best_t), best_normal))
1120}
1121
1122pub(crate) fn occluded_indexed(ray: &Ray, scene: &RayTraceScene) -> bool {
1137 if traceable_direction_of(ray).is_none() {
1138 return false;
1139 }
1140 let occluders: &[Occluder] = scene.get_occluders();
1141 if occluders.len() <= RAYTRACE_LINEAR_MAX_PRIMITIVES {
1142 return linear_occluded(ray, occluders);
1143 }
1144 let nodes: &[RayTraceNode] = scene.get_nodes();
1145 if nodes.is_empty() {
1146 return false;
1147 }
1148 let indices: &[usize] = scene.get_indices();
1149 let mut stack: [usize; RAYTRACE_BVH_STACK_SIZE] = [0usize; RAYTRACE_BVH_STACK_SIZE];
1150 let root: &RayTraceNode = &nodes[0];
1151 if aabb_entry_parameter(ray, root.get_lower(), root.get_upper()).is_none() {
1152 return false;
1153 }
1154 stack[0] = 0usize;
1155 let mut depth: usize = 1;
1156 while depth > 0 {
1157 depth -= 1;
1158 let node_index: usize = stack[depth];
1159 let node: RayTraceNode = nodes[node_index];
1160 if node.get_count() == 0 {
1161 let left_index: usize = node_index + 1;
1162 let right_index: usize = nodes[left_index].get_escape();
1163 for child_index in [left_index, right_index] {
1164 let child: &RayTraceNode = &nodes[child_index];
1165 let entry: Option<f64> =
1166 aabb_entry_parameter(ray, child.get_lower(), child.get_upper());
1167 if entry.is_some() {
1168 stack[depth] = child_index;
1169 depth += 1;
1170 }
1171 }
1172 continue;
1173 }
1174 let start: usize = node.get_start();
1175 for slot in 0..node.get_count() {
1176 let primitive: usize = indices[start + slot];
1177 let blocked: bool = intersect_occluder_in_range(&occluders[primitive], ray).is_some();
1178 if blocked {
1179 return true;
1180 }
1181 }
1182 }
1183 false
1184}
1185
1186fn bounce_ray(ray: &Ray, position: Vector3D, normal: Vector3D) -> Ray {
1200 let dir: Vector3D = ray.get_direction();
1201 let dot: f64 = dir.dot(normal);
1202 let reflected_dir: Vector3D = dir - normal.scaled(2.0 * dot);
1203 Ray {
1204 origin: position,
1205 direction: reflected_dir,
1206 t_min: RAYTRACE_DEFAULT_T_MIN,
1207 t_max: RAYTRACE_DEFAULT_T_MAX,
1208 depth: ray.get_depth() + 1,
1209 }
1210}
1211
1212pub(crate) fn shadow_visibility<'a>(
1235 scene: &'a RayTraceScene,
1236 position: Vector3D,
1237 normal: Vector3D,
1238) -> impl FnMut(&Light, Vector3D, f64) -> f64 + 'a {
1239 let origin: Vector3D = position + normal.scaled(RAYTRACE_SHADOW_SELF_OFFSET);
1240 move |light: &Light, light_direction: Vector3D, max_distance: f64| {
1241 let facing: f64 = normal.dot(light_direction);
1242 if facing <= 0.0 {
1243 return 1.0;
1244 }
1245 let length: f64 = light_direction.magnitude();
1246 let distance_usable: bool =
1247 length.is_finite() && length > 0.0 && max_distance.is_finite() && max_distance > 0.0;
1248 if !distance_usable {
1249 return 1.0;
1250 }
1251 let limit: f64 = match light.get_kind() {
1252 LightType::Directional => max_distance,
1253 LightType::Point | LightType::Spot => max_distance - RAYTRACE_SHADOW_LIGHT_EPSILON,
1254 };
1255 let limit: f64 = if limit > RAYTRACE_SHADOW_SELF_OFFSET {
1256 limit
1257 } else {
1258 return 1.0;
1259 };
1260 let dir: Vector3D = light_direction.scaled(1.0 / length);
1261 let ray: Ray = Ray::new(origin, dir).with_range(RAYTRACE_SHADOW_SELF_OFFSET, limit);
1262 let blocked: bool = occluded_indexed(&ray, scene);
1263 if blocked { 0.0 } else { 1.0 }
1264 }
1265}
1266
1267pub(crate) fn trace_bounces(
1293 ray: Ray,
1294 scene: &RayTraceScene,
1295 lights: &LightingUniforms,
1296 max_bounces: u32,
1297) -> Vector3D {
1298 let ambient: Vector3D = lights.get_ambient();
1299 let mut color: Vector3D = Vector3D::zero();
1300 let mut throughput: f64 = 1.0;
1301 let mut current: Ray = ray;
1302 loop {
1303 let (index, _t, position, normal): (usize, f64, Vector3D, Vector3D) =
1304 match closest_hit_indexed(¤t, scene) {
1305 None => {
1306 color += ambient.scaled(throughput);
1307 break;
1308 }
1309 Some(hit) => hit,
1310 };
1311 let material: &Material = scene.get_occluders()[index].get_material();
1312 let shaded: Vector3D = lights.shade_with_visibility(
1313 position,
1314 normal,
1315 material,
1316 shadow_visibility(scene, position, normal),
1317 );
1318 color += shaded.scaled(throughput);
1319 let spec: f64 = material.get_specular();
1320 if current.get_depth() >= max_bounces || spec <= EPSILON {
1321 break;
1322 }
1323 throughput *= spec;
1324 current = bounce_ray(¤t, position, normal);
1325 }
1326 color
1327}