obvhs/cwbvh/
traverse_macro.rs

1/// Traverse a CwBvh with custom node and primitive intersections.
2/// I really didn't want to use a macro but it seems like everything else using closures/yielding is slower given
3/// both generic node and primitive traversal.
4///
5/// # Parameters
6/// - `$cwbvh`: `&CwBvh` The CwBvh to be traversed.
7/// - `$node`: `&CwBvhNode` The current node in the BVH that is being traversed.
8/// - `$state`: `Traversal` Mutable traversal state.
9/// - `$node_intersection`: An expression that is executed for each node intersection during traversal.
10///   It should test for intersection against the current `node`, making use of `state.oct_inv4` u32.
11///   It should return a u32 `hitmask` of the node children hitmask corresponding to which nodes were intersected.
12/// - `$primitive_intersection`: A code block that is executed for each primitive intersection.
13///   It should read the current `state.primitive_id` u32. This is the index into the primitive indices for the
14///   current primitive to be tested. Optionally use `break` to halt traversal.
15///
16/// # Example: Closest hit ray traversal
17/// ```
18/// use obvhs::{
19///     cwbvh::{builder::build_cwbvh_from_tris, node::CwBvhNode},
20///     ray::{Ray, RayHit},
21///     test_util::geometry::{icosphere, PLANE},
22///     triangle::Triangle,
23///     BvhBuildParams,
24///     traverse,
25/// };
26/// use glam::*;
27/// use std::time::Duration;
28///
29/// let mut tris: Vec<Triangle> = Vec::new();
30/// tris.extend(icosphere(1));
31/// tris.extend(PLANE);
32///
33/// let ray = Ray::new_inf(vec3a(0.1, 0.1, 4.0), vec3a(0.0, 0.0, -1.0));
34///
35/// let bvh = build_cwbvh_from_tris(&tris, BvhBuildParams::medium_build(), &mut Duration::default());
36/// let mut hit = RayHit::none();
37/// let mut traverse_ray = ray.clone();
38/// let mut state = bvh.new_traversal(ray.direction);
39/// let mut node;
40/// traverse!(bvh, node, state,
41///     // Node intersection:
42///     node.intersect_ray(&traverse_ray, state.oct_inv4),
43///     // Primitive intersection:
44///     {
45///         let t = tris[bvh.primitive_indices[state.primitive_id as usize] as usize].intersect(&traverse_ray);
46///         if t < traverse_ray.tmax {
47///             hit.primitive_id = state.primitive_id;
48///             hit.t = t;
49///             traverse_ray.tmax = t;
50///         }
51///     }
52/// );
53///
54/// let did_hit = hit.t < ray.tmax;
55/// assert!(did_hit);
56/// assert!(bvh.primitive_indices[hit.primitive_id as usize] == 62);
57/// ```
58#[macro_export]
59macro_rules! traverse {
60    ($cwbvh:expr, $node:expr, $state:expr, $node_intersection:expr, $primitive_intersection:expr) => {{
61        use $crate::faststack::FastStack;
62        loop {
63            // While the primitive group is not empty
64            while $state.primitive_group.y != 0 {
65                let local_primitive_index = $crate::cwbvh::firstbithigh($state.primitive_group.y);
66
67                // Remove primitive from current_group
68                $state.primitive_group.y &= !(1u32 << local_primitive_index);
69
70                $state.primitive_id = $state.primitive_group.x + local_primitive_index;
71                $primitive_intersection
72            }
73            $state.primitive_group = UVec2::ZERO;
74
75            // If there's remaining nodes in the current group to check
76            if $state.current_group.y & 0xff000000 != 0 {
77                let hits_imask = $state.current_group.y;
78
79                let child_index_offset = $crate::cwbvh::firstbithigh(hits_imask);
80                let child_index_base = $state.current_group.x;
81
82                // Remove node from current_group
83                $state.current_group.y &= !(1u32 << child_index_offset);
84
85                // If the node group is not yet empty, push it on the stack
86                if $state.current_group.y & 0xff000000 != 0 {
87                    $state.stack.push($state.current_group);
88                }
89
90                let slot_index = (child_index_offset - 24) ^ ($state.oct_inv4 & 0xff);
91                let relative_index = (hits_imask & !(0xffffffffu32 << slot_index)).count_ones();
92
93                let child_node_index = child_index_base + relative_index;
94
95                $node = &$cwbvh.nodes[child_node_index as usize];
96
97                $state.hitmask = $node_intersection;
98
99                $state.current_group.x = $node.child_base_idx;
100                $state.primitive_group.x = $node.primitive_base_idx;
101
102                $state.current_group.y = (&$state.hitmask & 0xff000000u32) | ($node.imask as u32);
103                $state.primitive_group.y = &$state.hitmask & 0x00ffffffu32;
104            } else {
105                // Below is only needed when using triangle postponing, which would only be helpful on the
106                // GPU (it helps reduce thread divergence). Also, this isn't compatible with traversal yielding.
107                // $state.primitive_group = $state.current_group;
108                $state.current_group = UVec2::ZERO;
109            }
110
111            // If there's no remaining nodes in the current group to check, pop it off the stack.
112            if $state.primitive_group.y == 0 && ($state.current_group.y & 0xff000000) == 0 {
113                // If the stack is empty, end traversal.
114                if $state.stack.is_empty() {
115                    #[allow(unused)]
116                    {
117                        $state.current_group.y = 0;
118                    }
119                    break;
120                }
121
122                $state.current_group = $state.stack.pop_fast();
123            }
124        }
125    }};
126}