1use super::*;
2
3fn vector3d_min(a: Vector3D, b: Vector3D) -> Vector3D {
14 Vector3D::new(
15 a.get_x().min(b.get_x()),
16 a.get_y().min(b.get_y()),
17 a.get_z().min(b.get_z()),
18 )
19}
20
21fn vector3d_max(a: Vector3D, b: Vector3D) -> Vector3D {
32 Vector3D::new(
33 a.get_x().max(b.get_x()),
34 a.get_y().max(b.get_y()),
35 a.get_z().max(b.get_z()),
36 )
37}
38
39fn is_finite_vector(value: Vector3D) -> bool {
54 value.get_x().is_finite() && value.get_y().is_finite() && value.get_z().is_finite()
55}
56
57fn slab_interval(
76 origin_component: f64,
77 dir_component: f64,
78 lower: f64,
79 upper: f64,
80) -> Option<(f64, f64)> {
81 if dir_component.abs() < RAYTRACE_TRIANGLE_EPSILON {
82 if origin_component < lower || origin_component > upper {
83 return None;
84 }
85 return Some((f64::NEG_INFINITY, f64::INFINITY));
86 }
87 let inverse: f64 = 1.0 / dir_component;
88 let t0: f64 = (lower - origin_component) * inverse;
89 let t1: f64 = (upper - origin_component) * inverse;
90 Some((t0.min(t1), t0.max(t1)))
91}
92
93fn aabb_entry_parameter(ray: &Ray, lower: Vector3D, upper: Vector3D) -> Option<f64> {
113 let origin: Vector3D = ray.get_origin();
114 let dir: Vector3D = ray.get_direction();
115 if !is_finite_vector(origin)
116 || !is_finite_vector(dir)
117 || !is_finite_vector(lower)
118 || !is_finite_vector(upper)
119 {
120 return None;
121 }
122 let x: Option<(f64, f64)> =
123 slab_interval(origin.get_x(), dir.get_x(), lower.get_x(), upper.get_x());
124 let y: Option<(f64, f64)> =
125 slab_interval(origin.get_y(), dir.get_y(), lower.get_y(), upper.get_y());
126 let z: Option<(f64, f64)> =
127 slab_interval(origin.get_z(), dir.get_z(), lower.get_z(), upper.get_z());
128 let (x0, x1): (f64, f64) = x?;
129 let (y0, y1): (f64, f64) = y?;
130 let (z0, z1): (f64, f64) = z?;
131 let t_enter: f64 = x0.max(y0).max(z0);
132 let t_exit: f64 = x1.min(y1).min(z1);
133 if t_enter > t_exit {
134 return None;
135 }
136 let clipped: f64 = t_enter.max(ray.get_t_min());
137 if clipped > t_exit.min(ray.get_t_max()) {
138 return None;
139 }
140 Some(clipped)
141}
142
143pub fn intersect_triangle(ray: &Ray, vertices: [Vector3D; 3]) -> Option<(f64, Vector3D)> {
167 let [v0, v1, v2]: [Vector3D; 3] = vertices;
168 let origin: Vector3D = ray.get_origin();
169 let dir: Vector3D = ray.get_direction();
170 let edge1: Vector3D = v1 - v0;
171 let edge2: Vector3D = v2 - v0;
172 let pvec: Vector3D = dir.cross(edge2);
173 let det: f64 = edge1.dot(pvec);
174 if det.abs() < RAYTRACE_TRIANGLE_EPSILON {
175 return None;
176 }
177 let inverse_det: f64 = 1.0 / det;
178 let tvec: Vector3D = origin - v0;
179 let u: f64 = tvec.dot(pvec) * inverse_det;
180 if !(0.0..=1.0).contains(&u) {
181 return None;
182 }
183 let qvec: Vector3D = tvec.cross(edge1);
184 let v: f64 = dir.dot(qvec) * inverse_det;
185 let v_usable: bool = v.is_finite() && (0.0..=1.0).contains(&v);
189 let edge_usable: bool = (u + v).is_finite() && (u + v <= 1.0);
190 if !v_usable || !edge_usable {
191 return None;
192 }
193 let t: f64 = edge2.dot(qvec) * inverse_det;
194 if !t.is_finite() {
195 return None;
196 }
197 let geometric: Vector3D = edge1.cross(edge2).normalized();
198 let normal: Vector3D = if geometric.dot(dir) > 0.0 {
199 -geometric
200 } else {
201 geometric
202 };
203 Some((t, normal))
204}
205
206fn occluder_aabb_extents(occluder: &Occluder) -> (Vector3D, Vector3D) {
221 let (mn, mx): (Vector3D, Vector3D) = match occluder.get_kind() {
222 OccluderKind::Aabb => (occluder.get_center(), occluder.get_extent()),
223 OccluderKind::Sphere => {
224 let center: Vector3D = occluder.get_center();
225 let radius: f64 = occluder.get_extent().get_x();
226 let r: Vector3D = Vector3D::new(radius, radius, radius);
227 (center - r, center + r)
228 }
229 OccluderKind::Triangle => {
230 let [v0, v1, v2]: [Vector3D; 3] = occluder.get_vertices();
231 let lo: Vector3D = vector3d_min(v0, vector3d_min(v1, v2));
232 let hi: Vector3D = vector3d_max(v0, vector3d_max(v1, v2));
233 (lo, hi)
234 }
235 };
236 (vector3d_min(mn, mx), vector3d_max(mn, mx))
237}
238
239pub(crate) fn collect_occluder_points(occluders: &[Occluder]) -> Vec<(Vector3D, f64)> {
253 let mut out: Vec<(Vector3D, f64)> = Vec::new();
254 for occ in occluders.iter() {
255 let (mn, mx): (Vector3D, Vector3D) = occluder_aabb_extents(occ);
256 let cx: f64 = (mn.get_x() + mx.get_x()) * 0.5;
257 let cy: f64 = (mn.get_y() + mx.get_y()) * 0.5;
258 let cz: f64 = (mn.get_z() + mx.get_z()) * 0.5;
259 let ex: f64 = (mx.get_x() - mn.get_x()) * 0.5;
260 let ey: f64 = (mx.get_y() - mn.get_y()) * 0.5;
261 let ez: f64 = (mx.get_z() - mn.get_z()) * 0.5;
262 let r: f64 = (ex * ex + ey * ey + ez * ez).sqrt();
263 out.push((Vector3D::new(cx, cy, cz), r));
264 }
265 out
266}
267
268fn aabb_surface_area(lower: Vector3D, upper: Vector3D) -> f64 {
281 let dx: f64 = (upper.get_x() - lower.get_x()).max(0.0);
282 let dy: f64 = (upper.get_y() - lower.get_y()).max(0.0);
283 let dz: f64 = (upper.get_z() - lower.get_z()).max(0.0);
284 2.0 * (dx * dy + dy * dz + dz * dx)
285}
286
287fn axis_component(value: Vector3D, axis: usize) -> f64 {
298 match axis {
299 0 => value.get_x(),
300 1 => value.get_y(),
301 _ => value.get_z(),
302 }
303}
304
305fn intersect_sphere_in_range(ray: &Ray, center: Vector3D, radius: f64) -> Option<(f64, Vector3D)> {
327 if !radius.is_finite() || radius <= 0.0 {
331 return None;
332 }
333 let origin: Vector3D = ray.get_origin();
334 let dir: Vector3D = ray.get_direction();
335 if !is_finite_vector(origin) || !is_finite_vector(dir) || !is_finite_vector(center) {
336 return None;
337 }
338 let a: f64 = dir.dot(dir);
339 if !a.is_finite() || a <= 0.0 {
343 return None;
344 }
345 let t_min: f64 = ray.get_t_min();
346 let t_max: f64 = ray.get_t_max();
347 let oc: Vector3D = origin - center;
348 let half_b: f64 = oc.dot(dir);
349 let c: f64 = oc.dot(oc) - radius * radius;
350 let discriminant: f64 = half_b * half_b - a * c;
351 let discriminant_usable: bool = discriminant.is_finite() && discriminant >= 0.0;
354 if !discriminant_usable {
355 return None;
356 }
357 let root: f64 = discriminant.sqrt();
358 let near: f64 = (-half_b - root) / a;
359 let far: f64 = (-half_b + root) / a;
360 let t: f64 = if near >= t_min && near <= t_max {
361 near
362 } else if far >= t_min && far <= t_max {
363 far
364 } else {
365 return None;
366 };
367 let normal: Vector3D = (origin + dir.scaled(t) - center).normalized();
368 Some((t, normal))
369}
370
371fn intersect_aabb_in_range(ray: &Ray, lower: Vector3D, upper: Vector3D) -> Option<(f64, Vector3D)> {
393 let origin: Vector3D = ray.get_origin();
394 let dir: Vector3D = ray.get_direction();
395 let t_min: f64 = ray.get_t_min();
396 let t_max: f64 = ray.get_t_max();
397 if !is_finite_vector(origin) || !is_finite_vector(dir) {
398 return None;
399 }
400 if !is_finite_vector(lower) || !is_finite_vector(upper) {
401 return None;
402 }
403 let lo: Vector3D = vector3d_min(lower, upper);
404 let hi: Vector3D = vector3d_max(lower, upper);
405 if lo.get_x() >= hi.get_x() || lo.get_y() >= hi.get_y() || lo.get_z() >= hi.get_z() {
409 return None;
410 }
411 let origins: [f64; 3] = [origin.get_x(), origin.get_y(), origin.get_z()];
412 let dirs: [f64; 3] = [dir.get_x(), dir.get_y(), dir.get_z()];
413 let los: [f64; 3] = [lo.get_x(), lo.get_y(), lo.get_z()];
414 let his: [f64; 3] = [hi.get_x(), hi.get_y(), hi.get_z()];
415 let mut t_enter: f64 = f64::NEG_INFINITY;
416 let mut t_exit: f64 = f64::INFINITY;
417 let mut enter_axis: usize = 0;
418 let mut exit_axis: usize = 0;
419 for axis in 0..3 {
420 let d: f64 = dirs[axis];
421 let o: f64 = origins[axis];
422 if d.abs() < RAYTRACE_TRIANGLE_EPSILON {
423 if o < los[axis] || o > his[axis] {
424 return None;
425 }
426 continue;
427 }
428 let inverse: f64 = 1.0 / d;
429 let plane_a: f64 = (los[axis] - o) * inverse;
430 let plane_b: f64 = (his[axis] - o) * inverse;
431 let (near, far): (f64, f64) = if plane_a <= plane_b {
437 (plane_a, plane_b)
438 } else {
439 (plane_b, plane_a)
440 };
441 if near > t_enter {
442 t_enter = near;
443 enter_axis = axis;
444 }
445 if far < t_exit {
446 t_exit = far;
447 exit_axis = axis;
448 }
449 }
450 if t_enter > t_exit {
451 return None;
452 }
453 let inside: bool = t_enter <= 0.0 && t_exit >= 0.0;
461 let t: f64 = if inside { t_exit } else { t_enter };
462 if !t.is_finite() {
463 return None;
464 }
465 if t < t_min {
466 return None;
467 }
468 if t > t_max {
469 return None;
470 }
471 let axis: usize = if inside { exit_axis } else { enter_axis };
472 let travelling_positive: bool = dirs[axis] > 0.0;
479 let sign: f64 = if inside {
480 if travelling_positive { 1.0 } else { -1.0 }
481 } else if travelling_positive {
482 -1.0
483 } else {
484 1.0
485 };
486 let normal: Vector3D = match axis {
487 0 => Vector3D::new(sign, 0.0, 0.0),
488 1 => Vector3D::new(0.0, sign, 0.0),
489 _ => Vector3D::new(0.0, 0.0, sign),
490 };
491 Some((t, normal))
492}
493
494fn intersect_occluder_in_range(occluder: &Occluder, ray: &Ray) -> Option<(f64, Vector3D)> {
512 let t_min: f64 = ray.get_t_min();
513 let t_max: f64 = ray.get_t_max();
514 match occluder.get_kind() {
515 OccluderKind::Sphere => {
516 intersect_sphere_in_range(ray, occluder.get_center(), occluder.get_extent().get_x())
517 }
518 OccluderKind::Aabb => {
519 let (lower, upper): (Vector3D, Vector3D) = occluder_aabb_extents(occluder);
520 intersect_aabb_in_range(ray, lower, upper)
521 }
522 OccluderKind::Triangle => match intersect_triangle(ray, occluder.get_vertices()) {
523 Some((t, normal)) if t.is_finite() && t >= t_min && t <= t_max => Some((t, normal)),
524 _ => None,
525 },
526 }
527}
528
529fn bounds_of_range(indices: &[usize], build: &BvhBuild, range: BvhRange) -> (Vector3D, Vector3D) {
541 let mut lower: Vector3D = Vector3D::new(f64::INFINITY, f64::INFINITY, f64::INFINITY);
542 let mut upper: Vector3D =
543 Vector3D::new(f64::NEG_INFINITY, f64::NEG_INFINITY, f64::NEG_INFINITY);
544 for slot in 0..range.count {
545 let primitive: usize = indices[range.start + slot];
546 lower = vector3d_min(lower, build.lower[primitive]);
547 upper = vector3d_max(upper, build.upper[primitive]);
548 }
549 (lower, upper)
550}
551
552fn longest_centroid_axis(indices: &[usize], build: &BvhBuild, range: BvhRange) -> SahSplit {
568 let centroids: &[Vector3D] = &build.centroids;
569 let mut best_axis: usize = 0;
570 let mut best_span: f64 = -1.0;
571 let mut best_mid: f64 = 0.0;
572 for axis in 0..3 {
573 let mut min: f64 = f64::INFINITY;
574 let mut max: f64 = f64::NEG_INFINITY;
575 for slot in 0..range.count {
576 let value: f64 = axis_component(centroids[indices[range.start + slot]], axis);
577 min = min.min(value);
578 max = max.max(value);
579 }
580 let span: f64 = max - min;
581 if span > best_span {
582 best_span = span;
583 best_axis = axis;
584 best_mid = (min + max) * 0.5;
585 }
586 }
587 SahSplit {
588 axis: best_axis,
589 boundary: best_mid,
590 }
591}
592
593fn partition_range(
613 indices: &mut [usize],
614 build: &BvhBuild,
615 range: BvhRange,
616 split: SahSplit,
617) -> usize {
618 let centroids: &[Vector3D] = &build.centroids;
619 let mut left: usize = 0;
620 let mut right: usize = range.count;
621 while left < right {
622 while left < right
623 && axis_component(centroids[indices[range.start + left]], split.axis) < split.boundary
624 {
625 left += 1;
626 }
627 while right > left
628 && axis_component(centroids[indices[range.start + right - 1]], split.axis)
629 >= split.boundary
630 {
631 right -= 1;
632 }
633 if left >= right {
634 break;
635 }
636 indices.swap(range.start + left, range.start + right - 1);
637 left += 1;
638 right -= 1;
639 }
640 left
641}
642
643fn choose_sah_split(
664 indices: &[usize],
665 build: &BvhBuild,
666 range: BvhRange,
667 parent_area: f64,
668) -> Option<SahSplit> {
669 let count: usize = range.count;
670 if count < 2 || !parent_area.is_finite() || parent_area <= 0.0 {
671 return None;
672 }
673 let centroids: &[Vector3D] = &build.centroids;
674 let mut best: Option<(usize, f64, f64)> = None;
675 for axis in 0..3 {
676 let mut centroid_min: f64 = f64::INFINITY;
677 let mut centroid_max: f64 = f64::NEG_INFINITY;
678 for slot in 0..count {
679 let value: f64 = axis_component(centroids[indices[range.start + slot]], axis);
680 centroid_min = centroid_min.min(value);
681 centroid_max = centroid_max.max(value);
682 }
683 let span: f64 = centroid_max - centroid_min;
684 if !span.is_finite() || span <= RAYTRACE_BVH_AXIS_EPSILON {
685 continue;
686 }
687 let scale: f64 = RAYTRACE_BVH_BINS as f64 / span;
688 let mut bin_lower: [Vector3D; RAYTRACE_BVH_BINS] = [Vector3D::zero(); RAYTRACE_BVH_BINS];
689 let mut bin_upper: [Vector3D; RAYTRACE_BVH_BINS] = [Vector3D::zero(); RAYTRACE_BVH_BINS];
690 let mut bin_count: [usize; RAYTRACE_BVH_BINS] = [0; RAYTRACE_BVH_BINS];
691 for slot in 0..count {
692 let primitive: usize = indices[range.start + slot];
693 let scaled: f64 = (axis_component(centroids[primitive], axis) - centroid_min) * scale;
694 let mut bin: usize = scaled as usize;
695 if bin >= RAYTRACE_BVH_BINS {
696 bin = RAYTRACE_BVH_BINS - 1;
697 }
698 if bin_count[bin] == 0 {
699 bin_lower[bin] = build.lower[primitive];
700 bin_upper[bin] = build.upper[primitive];
701 } else {
702 bin_lower[bin] = vector3d_min(bin_lower[bin], build.lower[primitive]);
703 bin_upper[bin] = vector3d_max(bin_upper[bin], build.upper[primitive]);
704 }
705 bin_count[bin] += 1;
706 }
707 let mut left_area: [f64; RAYTRACE_BVH_BINS] = [0.0; RAYTRACE_BVH_BINS];
708 let mut left_count: [usize; RAYTRACE_BVH_BINS] = [0; RAYTRACE_BVH_BINS];
709 let mut running_lower: Vector3D =
710 Vector3D::new(f64::INFINITY, f64::INFINITY, f64::INFINITY);
711 let mut running_upper: Vector3D =
712 Vector3D::new(f64::NEG_INFINITY, f64::NEG_INFINITY, f64::NEG_INFINITY);
713 let mut running_count: usize = 0;
714 for bin in 0..RAYTRACE_BVH_BINS {
715 if bin_count[bin] > 0 {
716 running_lower = vector3d_min(running_lower, bin_lower[bin]);
717 running_upper = vector3d_max(running_upper, bin_upper[bin]);
718 running_count += bin_count[bin];
719 }
720 left_area[bin] = aabb_surface_area(running_lower, running_upper);
721 left_count[bin] = running_count;
722 }
723 let mut right_lower: Vector3D = Vector3D::new(f64::INFINITY, f64::INFINITY, f64::INFINITY);
724 let mut right_upper: Vector3D =
725 Vector3D::new(f64::NEG_INFINITY, f64::NEG_INFINITY, f64::NEG_INFINITY);
726 let mut right_count: usize = 0;
727 for bin in (1..RAYTRACE_BVH_BINS).rev() {
728 if bin_count[bin] > 0 {
729 right_lower = vector3d_min(right_lower, bin_lower[bin]);
730 right_upper = vector3d_max(right_upper, bin_upper[bin]);
731 right_count += bin_count[bin];
732 }
733 let left_total: usize = left_count[bin - 1];
734 if left_total == 0 || right_count == 0 {
735 continue;
736 }
737 let cost: f64 = left_area[bin - 1] * left_total as f64
738 + aabb_surface_area(right_lower, right_upper) * right_count as f64;
739 if matches!(best, Some((_, _, incumbent)) if cost >= incumbent) {
740 continue;
741 }
742 best = Some((axis, centroid_min + bin as f64 / scale, cost));
743 }
744 }
745 let (axis, boundary, cost): (usize, f64, f64) = best?;
746 let incumbent: f64 = count as f64 * parent_area;
747 if !cost.is_finite() || cost >= incumbent {
748 return None;
749 }
750 Some(SahSplit { axis, boundary })
751}
752
753fn build_bvh_subtree(context: &mut BvhBuildContext, subtree: BvhSubtree) {
774 let root: usize = context.nodes.len();
775 let bounds: (Vector3D, Vector3D) = subtree.bounds;
776 let range: BvhRange = subtree.range;
777 let too_small_to_split: bool = range.count <= RAYTRACE_BVH_LEAF_SIZE || range.count < 2;
778 if too_small_to_split || subtree.depth >= RAYTRACE_BVH_MAX_DEPTH {
779 context.nodes.push(RayTraceNode {
780 lower: bounds.0,
781 upper: bounds.1,
782 start: range.start,
783 count: range.count,
784 escape: root + 1,
785 });
786 return;
787 }
788 let build: &BvhBuild = context.build;
789 let split: SahSplit = match choose_sah_split(
790 context.indices,
791 build,
792 range,
793 aabb_surface_area(bounds.0, bounds.1),
794 ) {
795 Some(chosen) => chosen,
796 None => longest_centroid_axis(context.indices, build, range),
797 };
798 let partitioned: usize = partition_range(context.indices, build, range, split);
799 let left_count: usize = if partitioned == 0 || partitioned == range.count {
800 range.count / 2
801 } else {
802 partitioned
803 };
804 context.nodes.push(RayTraceNode {
805 lower: bounds.0,
806 upper: bounds.1,
807 start: range.start,
808 count: 0,
809 escape: root + 1,
810 });
811 let left_range: BvhRange = BvhRange {
812 start: range.start,
813 count: left_count,
814 };
815 let left_bounds: (Vector3D, Vector3D) = bounds_of_range(context.indices, build, left_range);
816 build_bvh_subtree(
817 context,
818 BvhSubtree {
819 bounds: left_bounds,
820 range: left_range,
821 depth: subtree.depth + 1,
822 },
823 );
824 let right_range: BvhRange = BvhRange {
825 start: range.start + left_count,
826 count: range.count - left_count,
827 };
828 let right_bounds: (Vector3D, Vector3D) = bounds_of_range(context.indices, build, right_range);
829 build_bvh_subtree(
830 context,
831 BvhSubtree {
832 bounds: right_bounds,
833 range: right_range,
834 depth: subtree.depth + 1,
835 },
836 );
837 context.nodes[root].escape = context.nodes.len();
838}
839
840pub(crate) fn build_bvh(occluders: &[Occluder]) -> (Vec<RayTraceNode>, Vec<usize>) {
856 let count: usize = occluders.len();
857 let mut nodes: Vec<RayTraceNode> = Vec::new();
858 let mut indices: Vec<usize> = Vec::with_capacity(count);
859 if count == 0 {
860 return (nodes, indices);
861 }
862 let mut lower: Vec<Vector3D> = Vec::with_capacity(count);
863 let mut upper: Vec<Vector3D> = Vec::with_capacity(count);
864 let mut centroids: Vec<Vector3D> = Vec::with_capacity(count);
865 for (index, occluder) in occluders.iter().enumerate() {
866 let (lo, hi): (Vector3D, Vector3D) = occluder_aabb_extents(occluder);
867 let midpoint: Vector3D = (lo + hi).scaled(0.5);
868 let centroid: Vector3D = if is_finite_vector(midpoint) {
869 midpoint
870 } else {
871 Vector3D::zero()
872 };
873 lower.push(lo);
874 upper.push(hi);
875 centroids.push(centroid);
876 indices.push(index);
877 }
878 let build: BvhBuild = BvhBuild {
879 lower,
880 upper,
881 centroids,
882 };
883 let root_range: BvhRange = BvhRange { start: 0, count };
884 let root_bounds: (Vector3D, Vector3D) = bounds_of_range(&indices, &build, root_range);
885 nodes.reserve(count.saturating_mul(2));
886 let mut context: BvhBuildContext = BvhBuildContext {
887 nodes: &mut nodes,
888 indices: &mut indices,
889 build: &build,
890 };
891 build_bvh_subtree(
892 &mut context,
893 BvhSubtree {
894 bounds: root_bounds,
895 range: root_range,
896 depth: 0,
897 },
898 );
899 (nodes, indices)
900}
901
902fn ray_is_traceable(ray: &Ray) -> bool {
916 let t_min: f64 = ray.get_t_min();
917 let t_max: f64 = ray.get_t_max();
918 if !t_min.is_finite() || !t_max.is_finite() || t_min > t_max {
919 return false;
920 }
921 is_finite_vector(ray.get_origin()) && is_finite_vector(ray.get_direction())
922}
923
924fn traceable_direction_of(ray: &Ray) -> Option<Vector3D> {
942 if !ray_is_traceable(ray) {
943 return None;
944 }
945 let dir: Vector3D = ray.get_direction();
946 let squared_length: f64 = dir.dot(dir);
947 if !squared_length.is_finite() || squared_length <= 0.0 {
948 return None;
949 }
950 Some(dir)
951}
952
953fn linear_closest_hit(
970 ray: &Ray,
971 occluders: &[Occluder],
972) -> Option<(usize, f64, Vector3D, Vector3D)> {
973 let origin: Vector3D = ray.get_origin();
974 let dir: Vector3D = ray.get_direction();
975 let mut best: Option<(usize, f64, Vector3D, Vector3D)> = None;
976 for (index, occluder) in occluders.iter().enumerate() {
977 let hit: Option<(f64, Vector3D)> = intersect_occluder_in_range(occluder, ray);
978 let Some((t, normal)) = hit else {
979 continue;
980 };
981 let nearer: bool = match &best {
982 None => true,
983 Some((best_index, best_t, _, _)) => {
984 t < *best_t || (t == *best_t && index < *best_index)
985 }
986 };
987 if nearer {
988 best = Some((index, t, origin + dir.scaled(t), normal));
989 }
990 }
991 best
992}
993
994fn linear_occluded(ray: &Ray, occluders: &[Occluder]) -> bool {
1005 occluders
1006 .iter()
1007 .any(|occluder: &Occluder| intersect_occluder_in_range(occluder, ray).is_some())
1008}
1009
1010pub(crate) fn closest_hit_indexed(
1031 ray: &Ray,
1032 scene: &RayTraceScene,
1033) -> Option<(usize, f64, Vector3D, Vector3D)> {
1034 traceable_direction_of(ray)?;
1039 let nodes: &[RayTraceNode] = scene.get_nodes();
1040 if nodes.is_empty() {
1041 return None;
1042 }
1043 let occluders: &[Occluder] = scene.get_occluders();
1044 if occluders.len() <= RAYTRACE_LINEAR_MAX_PRIMITIVES {
1045 return linear_closest_hit(ray, occluders);
1046 }
1047 let indices: &[usize] = scene.get_indices();
1048 let origin: Vector3D = ray.get_origin();
1049 let t_min: f64 = ray.get_t_min();
1050 let mut best_t: f64 = ray.get_t_max();
1051 let mut best_index: usize = 0;
1052 let mut best_normal: Vector3D = Vector3D::zero();
1053 let mut found: bool = false;
1054 let mut stack: [usize; RAYTRACE_BVH_STACK_SIZE] = [0usize; RAYTRACE_BVH_STACK_SIZE];
1055 let root: &RayTraceNode = &nodes[0];
1056 let dir: Vector3D = ray.get_direction();
1057 let _root_entry: f64 = aabb_entry_parameter(ray, root.get_lower(), root.get_upper())?;
1061 stack[0] = 0usize;
1062 let mut depth: usize = 1;
1063 while depth > 0 {
1064 depth -= 1;
1065 let node_index: usize = stack[depth];
1066 let node: RayTraceNode = nodes[node_index];
1067 if node.get_count() == 0 {
1068 let left_index: usize = node_index + 1;
1069 let right_index: usize = nodes[left_index].get_escape();
1070 let left: &RayTraceNode = &nodes[left_index];
1071 let right: &RayTraceNode = &nodes[right_index];
1072 let probe: Ray = ray.with_range(t_min, best_t);
1073 let left_entry: Option<f64> =
1074 aabb_entry_parameter(&probe, left.get_lower(), left.get_upper());
1075 let right_entry: Option<f64> =
1076 aabb_entry_parameter(&probe, right.get_lower(), right.get_upper());
1077 match (left_entry, right_entry) {
1078 (None, None) => {}
1079 (Some(_), None) => {
1080 stack[depth] = left_index;
1081 depth += 1;
1082 }
1083 (None, Some(_)) => {
1084 stack[depth] = right_index;
1085 depth += 1;
1086 }
1087 (Some(left_t), Some(right_t)) => {
1088 let near_first: usize = if left_t <= right_t {
1089 left_index
1090 } else {
1091 right_index
1092 };
1093 stack[depth] = if left_t <= right_t {
1094 right_index
1095 } else {
1096 left_index
1097 };
1098 depth += 1;
1099 stack[depth] = near_first;
1100 depth += 1;
1101 }
1102 }
1103 continue;
1104 }
1105 let start: usize = node.get_start();
1106 let mut probe: Ray = ray.with_range(t_min, best_t);
1107 for slot in 0..node.get_count() {
1108 let primitive: usize = indices[start + slot];
1109 let hit: Option<(f64, Vector3D)> =
1110 intersect_occluder_in_range(&occluders[primitive], &probe);
1111 if let Some((t, normal)) = hit {
1112 let nearer: bool = t < best_t;
1113 let stable_tie: bool = t == best_t && (!found || primitive < best_index);
1114 if nearer || stable_tie {
1115 best_t = t;
1116 best_index = primitive;
1117 best_normal = normal;
1118 found = true;
1119 probe = ray.with_range(t_min, best_t);
1120 }
1121 }
1122 }
1123 }
1124 if !found {
1125 return None;
1126 }
1127 Some((best_index, best_t, origin + dir.scaled(best_t), best_normal))
1128}
1129
1130pub(crate) fn occluded_indexed(ray: &Ray, scene: &RayTraceScene) -> bool {
1145 if traceable_direction_of(ray).is_none() {
1146 return false;
1147 }
1148 let occluders: &[Occluder] = scene.get_occluders();
1149 if occluders.len() <= RAYTRACE_LINEAR_MAX_PRIMITIVES {
1150 return linear_occluded(ray, occluders);
1151 }
1152 let nodes: &[RayTraceNode] = scene.get_nodes();
1153 if nodes.is_empty() {
1154 return false;
1155 }
1156 let indices: &[usize] = scene.get_indices();
1157 let mut stack: [usize; RAYTRACE_BVH_STACK_SIZE] = [0usize; RAYTRACE_BVH_STACK_SIZE];
1158 let root: &RayTraceNode = &nodes[0];
1159 if aabb_entry_parameter(ray, root.get_lower(), root.get_upper()).is_none() {
1160 return false;
1161 }
1162 stack[0] = 0usize;
1163 let mut depth: usize = 1;
1164 while depth > 0 {
1165 depth -= 1;
1166 let node_index: usize = stack[depth];
1167 let node: RayTraceNode = nodes[node_index];
1168 if node.get_count() == 0 {
1169 let left_index: usize = node_index + 1;
1170 let right_index: usize = nodes[left_index].get_escape();
1171 for child_index in [left_index, right_index] {
1172 let child: &RayTraceNode = &nodes[child_index];
1173 let entry: Option<f64> =
1174 aabb_entry_parameter(ray, child.get_lower(), child.get_upper());
1175 if entry.is_some() {
1176 stack[depth] = child_index;
1177 depth += 1;
1178 }
1179 }
1180 continue;
1181 }
1182 let start: usize = node.get_start();
1183 for slot in 0..node.get_count() {
1184 let primitive: usize = indices[start + slot];
1185 let blocked: bool = intersect_occluder_in_range(&occluders[primitive], ray).is_some();
1186 if blocked {
1187 return true;
1188 }
1189 }
1190 }
1191 false
1192}
1193
1194fn bounce_ray(ray: &Ray, position: Vector3D, normal: Vector3D) -> Ray {
1209 let dir: Vector3D = ray.get_direction();
1210 let dot: f64 = dir.dot(normal);
1211 let reflected_dir: Vector3D = dir - normal.scaled(2.0 * dot);
1212 Ray {
1213 origin: position,
1214 direction: reflected_dir,
1215 t_min: RAYTRACE_DEFAULT_T_MIN,
1216 t_max: RAYTRACE_DEFAULT_T_MAX,
1217 depth: ray.get_depth() + 1,
1218 }
1219}
1220
1221pub(crate) fn shadow_visibility<'a>(
1245 scene: &'a RayTraceScene,
1246 position: Vector3D,
1247 normal: Vector3D,
1248) -> impl FnMut(&Light, Vector3D, f64) -> f64 + 'a {
1249 let origin: Vector3D = position + normal.scaled(RAYTRACE_SHADOW_SELF_OFFSET);
1250 move |light: &Light, light_direction: Vector3D, max_distance: f64| {
1251 let facing: f64 = normal.dot(light_direction);
1252 if facing <= 0.0 {
1253 return 1.0;
1254 }
1255 let length: f64 = light_direction.magnitude();
1256 let distance_usable: bool =
1257 length.is_finite() && length > 0.0 && max_distance.is_finite() && max_distance > 0.0;
1258 if !distance_usable {
1259 return 1.0;
1260 }
1261 let limit: f64 = match light.get_kind() {
1262 LightType::Directional => max_distance,
1263 LightType::Point | LightType::Spot => max_distance - RAYTRACE_SHADOW_LIGHT_EPSILON,
1264 };
1265 let limit: f64 = if limit > RAYTRACE_SHADOW_SELF_OFFSET {
1266 limit
1267 } else {
1268 return 1.0;
1269 };
1270 let dir: Vector3D = light_direction.scaled(1.0 / length);
1271 let ray: Ray = Ray::new(origin, dir).with_range(RAYTRACE_SHADOW_SELF_OFFSET, limit);
1272 let blocked: bool = occluded_indexed(&ray, scene);
1273 if blocked { 0.0 } else { 1.0 }
1274 }
1275}
1276
1277pub(crate) fn trace_bounces(
1303 ray: Ray,
1304 scene: &RayTraceScene,
1305 lights: &LightingUniforms,
1306 max_bounces: u32,
1307) -> Vector3D {
1308 let ambient: Vector3D = lights.get_ambient();
1309 let mut color: Vector3D = Vector3D::zero();
1310 let mut throughput: f64 = 1.0;
1311 let mut current: Ray = ray;
1312 loop {
1313 let (index, _t, position, normal): (usize, f64, Vector3D, Vector3D) =
1314 match closest_hit_indexed(¤t, scene) {
1315 None => {
1316 color += ambient.scaled(throughput);
1317 break;
1318 }
1319 Some(hit) => hit,
1320 };
1321 let material: &Material = scene.get_occluders()[index].get_material();
1322 let shaded: Vector3D = lights.shade_with_visibility(
1323 position,
1324 normal,
1325 material,
1326 shadow_visibility(scene, position, normal),
1327 );
1328 color += shaded.scaled(throughput);
1329 let spec: f64 = material.get_specular();
1330 if current.get_depth() >= max_bounces || spec <= EPSILON {
1331 break;
1332 }
1333 throughput *= spec;
1334 current = bounce_ray(¤t, position, normal);
1335 }
1336 color
1337}