obvhs/splits.rs
1//! Split large triangles into multiple smaller Aabbs.
2
3use glam::Vec3A;
4
5use crate::{aabb::Aabb, triangle::Triangle};
6
7/// Splits large triangles into multiple smaller Aabbs. Fits the new aabbs tightly to the triangle.
8/// Note: This will result in more aabbs than triangles. The indices Vec will have grow with the
9/// added Aabb's with the respective mapping back to the initial list of triangles.
10/// # Arguments
11/// * `avg_half_area` - The average half area of the Triangles
12/// * `largest_half_area` - The largest half area of the Triangles
13/// This is tuned to try to create splits conservatively enough that it generally
14/// wont result in lower traversal performance across a variety of scenes.
15/// (Naive splitting can result in lower traversal performance in some scenes)
16pub fn split_aabbs_preset(
17 aabbs: &mut Vec<Aabb>,
18 indices: &mut Vec<u32>,
19 triangles: &[Triangle],
20 avg_half_area: f32,
21 largest_half_area: f32,
22) {
23 split_aabbs_precise(
24 aabbs,
25 indices,
26 triangles,
27 avg_half_area * 3.0,
28 (avg_half_area * 4.0).max(avg_half_area * 0.9 + largest_half_area * 0.1),
29 1.8,
30 1.6,
31 12,
32 12,
33 );
34}
35
36/// Splits large triangles into multiple smaller Aabbs. Fits the new aabbs tightly to the triangle.
37/// Note: This will result in more aabbs than triangles. The indices Vec will have grow with the
38/// added Aabb's with the respective mapping back to the initial list of triangles.
39/// # Arguments
40/// * `area_thresh_low` - Triangles with aabb half areas below this will not be considered for splitting.
41/// * `area_thresh_high` - If the low split factor condition is not met then area_thresh_high > old_cost
42/// must be met in addition to best_cost * split_factor_high < old_cost in order for the split to occur
43/// * `split_factor_low` - If the resulting smallest aabb half area (best_cost) multiplied by this factor is
44/// lower than the original cost the best split will be used (best_cost * split_factor_low < old_cost)
45/// (area_thresh_high > old_cost && best_cost * split_factor_high < old_cost)
46/// * `max_iterations` - Number of times to evaluate the entire set of aabbs/triangles (including the newly added splits)
47/// * `split_tests` - Number of places try splitting the triangle at.
48#[allow(clippy::too_many_arguments)]
49pub fn split_aabbs_precise(
50 aabbs: &mut Vec<Aabb>,
51 indices: &mut Vec<u32>,
52 triangles: &[Triangle],
53 area_thresh_low: f32,
54 area_thresh_high: f32,
55 split_factor_low: f32,
56 split_factor_high: f32,
57 max_iterations: u32,
58 split_tests: u32,
59) {
60 crate::scope!("split_aabbs_precise");
61
62 let mut candidates = Vec::new();
63
64 for (i, aabb) in aabbs.iter().enumerate() {
65 if aabb.half_area() > area_thresh_low {
66 candidates.push(i)
67 }
68 }
69
70 let mut old_candidates_len = candidates.len();
71 for _ in 0..max_iterations {
72 for i in 0..candidates.len() {
73 let aabb = &mut aabbs[candidates[i]];
74 let index = indices[candidates[i]];
75 let axis: usize = aabb.largest_axis();
76
77 let tri = triangles[index as usize];
78
79 let mut best_cost = f32::MAX;
80 let mut left = *aabb;
81 let mut right = *aabb;
82
83 // TODO optimization: create multiple splits simultaneously
84 for i in 1..split_tests {
85 let n = i as f32 / split_tests as f32;
86 let pos = aabb.min[axis] * n + aabb.max[axis] * (1.0 - n);
87
88 let mut tmp_left = *aabb;
89 let mut tmp_right = *aabb;
90
91 tmp_left.max[axis] = pos;
92 tmp_right.min[axis] = pos;
93 let verts = [tri.v0, tri.v1, tri.v2, tri.v0];
94 let (t_left, t_right) = split_triangle(axis as u32, pos, verts);
95 tmp_left = t_left.intersection(&tmp_left);
96 tmp_right = t_right.intersection(&tmp_right);
97 let area = tmp_left.half_area() + tmp_right.half_area();
98 if area < best_cost {
99 best_cost = area;
100 left = tmp_left;
101 right = tmp_right;
102 }
103 }
104
105 let old_cost = aabb.half_area();
106
107 if (area_thresh_high > old_cost && best_cost * split_factor_high < old_cost)
108 || best_cost * split_factor_low < old_cost
109 {
110 *aabb = left;
111 candidates.push(aabbs.len());
112 aabbs.push(right);
113 indices.push(index);
114 }
115 }
116 if old_candidates_len == candidates.len() {
117 break;
118 } else {
119 candidates.retain(|c| aabbs[*c].half_area() > area_thresh_low);
120 old_candidates_len = candidates.len();
121 }
122 }
123}
124
125/// Based on <https://github.com/embree/embree/blob/be0accfd0b246e2b03355b8ee7710a22c1b49240/kernels/builders/splitter.h#L17C1-L49C6>,
126/// but with the "current bounds" moved out.
127pub fn split_triangle(dim: u32, pos: f32, v: [Vec3A; 4]) -> (Aabb, Aabb) {
128 let mut left = Aabb::INVALID;
129 let mut right = Aabb::INVALID;
130
131 // Clip triangle to left and right box by processing all edges
132 for i in 0..3 {
133 let v0 = v[i];
134 let v1 = v[i + 1];
135 let v0d = v0[dim as usize];
136 let v1d = v1[dim as usize];
137
138 if v0d <= pos {
139 // This point is on left side
140 left.extend(v0);
141 }
142 if v0d >= pos {
143 // This point is on right side
144 right.extend(v0);
145 }
146
147 // The edge crosses the splitting location
148 if (v0d < pos && pos < v1d) || (v1d < pos && pos < v0d) {
149 debug_assert!((v1d - v0d) != 0.0);
150 let inv_length = 1.0 / (v1d - v0d);
151 let c = Vec3A::mul_add(Vec3A::splat((pos - v0d) * inv_length), v1 - v0, v0);
152 left.extend(c);
153 right.extend(c);
154 }
155 }
156
157 (left, right)
158}