1use crate::error::{DagError, DagResult};
9
10#[derive(Debug, Clone, Copy, PartialEq)]
12pub struct MeshletBounds {
13 pub sphere: [f32; 4],
15 pub aabb_min: [f32; 3],
17 pub aabb_max: [f32; 3],
19 pub cone: [f32; 4],
21}
22
23impl MeshletBounds {
24 #[must_use]
26 pub fn to_flat(&self) -> [f32; 16] {
27 [
28 self.sphere[0], self.sphere[1], self.sphere[2], self.sphere[3],
29 self.aabb_min[0], self.aabb_min[1], self.aabb_min[2], 0.0,
30 self.aabb_max[0], self.aabb_max[1], self.aabb_max[2], 0.0,
31 self.cone[0], self.cone[1], self.cone[2], self.cone[3],
32 ]
33 }
34}
35
36#[must_use]
40pub fn triangle_normal(positions: &[f32], a: u32, b: u32, c: u32) -> Option<[f64; 3]> {
41 let (ao, bo, co) = (a as usize * 3, b as usize * 3, c as usize * 3);
42 let ax = f64::from(positions[ao]);
43 let ay = f64::from(positions[ao + 1]);
44 let az = f64::from(positions[ao + 2]);
45 let ab = [
46 f64::from(positions[bo]) - ax,
47 f64::from(positions[bo + 1]) - ay,
48 f64::from(positions[bo + 2]) - az,
49 ];
50 let ac = [
51 f64::from(positions[co]) - ax,
52 f64::from(positions[co + 1]) - ay,
53 f64::from(positions[co + 2]) - az,
54 ];
55 let cross = [
56 ab[1] * ac[2] - ab[2] * ac[1],
57 ab[2] * ac[0] - ab[0] * ac[2],
58 ab[0] * ac[1] - ab[1] * ac[0],
59 ];
60 let length = hypot3(cross[0], cross[1], cross[2]);
61 let scale = f64::max(hypot2(ab[0], ab[1]), hypot2(ac[0], ac[1]));
62 if !length.is_finite() || length <= scale * scale * 1e-12 {
63 return None;
64 }
65 Some([cross[0] / length, cross[1] / length, cross[2] / length])
66}
67
68pub fn compute_meshlet_bounds(
73 positions: &[f32],
74 vertices: &[u32],
75 triangle_normals: &[[f64; 3]],
76 has_degenerate_triangle: bool,
77) -> DagResult<MeshletBounds> {
78 let mut min = [f64::INFINITY; 3];
79 let mut max = [f64::NEG_INFINITY; 3];
80 for &vertex in vertices {
81 let offset = vertex as usize * 3;
82 for (axis, value) in positions[offset..offset + 3].iter().enumerate() {
83 let value = f64::from(*value);
84 min[axis] = min[axis].min(value);
85 max[axis] = max[axis].max(value);
86 }
87 }
88 let center: [f32; 3] = [
89 ((min[0] + max[0]) * 0.5) as f32,
90 ((min[1] + max[1]) * 0.5) as f32,
91 ((min[2] + max[2]) * 0.5) as f32,
92 ];
93 let mut radius = 0.0f64;
94 for &vertex in vertices {
95 let offset = vertex as usize * 3;
96 let dx = f64::from(positions[offset]) - f64::from(center[0]);
97 let dy = f64::from(positions[offset + 1]) - f64::from(center[1]);
98 let dz = f64::from(positions[offset + 2]) - f64::from(center[2]);
99 radius = radius.max(hypot3(dx, dy, dz));
100 }
101 let conservative_radius = next_float32(radius);
102 if !conservative_radius.is_finite() {
103 return Err(DagError::overflow(
104 "Meshlet sphere cannot be represented by finite float32 bounds.",
105 ));
106 }
107 let cone = compute_normal_cone(triangle_normals, has_degenerate_triangle);
108 Ok(MeshletBounds {
109 sphere: [center[0], center[1], center[2], conservative_radius],
110 aabb_min: [min[0] as f32, min[1] as f32, min[2] as f32],
111 aabb_max: [max[0] as f32, max[1] as f32, max[2] as f32],
112 cone,
113 })
114}
115
116fn adjacent_float32(value: f32, direction: i32) -> f32 {
118 if value == 0.0 {
119 return if direction > 0 { f32::from_bits(1) } else { f32::from_bits(1).copysign(-1.0) };
120 }
121 let bits = value.to_bits();
122 let next = if (value > 0.0) == (direction > 0) { bits + 1 } else { bits - 1 };
123 f32::from_bits(next)
124}
125
126pub(crate) fn next_float32(value: f64) -> f32 {
128 let rounded = value as f32;
129 if !rounded.is_finite() || f64::from(rounded) >= value {
130 return rounded;
131 }
132 adjacent_float32(rounded, 1)
133}
134
135fn previous_float32(value: f64) -> f32 {
137 adjacent_float32(value as f32, -1)
138}
139
140fn compute_normal_cone(normals: &[[f64; 3]], disabled: bool) -> [f32; 4] {
142 if disabled || normals.is_empty() {
143 return [0.0, 0.0, 1.0, -1.0];
144 }
145 let mut sum = [0.0f64; 3];
146 for normal in normals {
147 sum[0] += normal[0];
148 sum[1] += normal[1];
149 sum[2] += normal[2];
150 }
151 let length = hypot3(sum[0], sum[1], sum[2]);
152 if length <= 1e-12 {
153 return [0.0, 0.0, 1.0, -1.0];
154 }
155 let axis: [f32; 3] = [
156 (sum[0] / length) as f32,
157 (sum[1] / length) as f32,
158 (sum[2] / length) as f32,
159 ];
160 let mut cutoff = 1.0f64;
161 for normal in normals {
162 let dot = f64::from(axis[0]) * normal[0]
163 + f64::from(axis[1]) * normal[1]
164 + f64::from(axis[2]) * normal[2];
165 cutoff = cutoff.min(dot);
166 }
167 if cutoff <= 0.0 {
168 return [axis[0], axis[1], axis[2], -1.0];
169 }
170 [axis[0], axis[1], axis[2], previous_float32(cutoff.min(1.0))]
171}
172
173#[inline]
180#[must_use]
181pub fn hypot3(x: f64, y: f64, z: f64) -> f64 {
182 js_hypot(&[x, y, z])
183}
184
185#[inline]
187#[must_use]
188pub fn hypot2(x: f64, y: f64) -> f64 {
189 js_hypot(&[x, y])
190}
191
192#[inline]
194fn js_hypot(args: &[f64]) -> f64 {
195 let mut max = 0.0f64;
196 for &value in args {
197 if value.is_infinite() {
198 return f64::INFINITY;
199 }
200 max = if max < value.abs() { value.abs() } else { max };
201 }
202 if max == 0.0 {
203 return 0.0;
204 }
205 let mut sum = 0.0f64;
206 for &value in args {
207 let scaled = value / max;
208 sum += scaled * scaled;
209 }
210 max * sum.sqrt()
211}
212
213#[cfg(test)]
214mod tests {
215 use super::*;
216
217 #[test]
218 fn adjacent_float32_steps_one_ulp() {
219 let one = 1.0f32;
220 assert_eq!(adjacent_float32(one, 1).to_bits(), one.to_bits() + 1);
221 assert_eq!(adjacent_float32(one, -1).to_bits(), one.to_bits() - 1);
222 assert_eq!(adjacent_float32(0.0, 1), f32::from_bits(1));
224 assert_eq!(adjacent_float32(0.0, -1), -f32::from_bits(1));
225 assert!(adjacent_float32(f32::MAX, 1).is_infinite());
227 }
228
229 #[test]
230 fn next_float32_conservative_rounding() {
231 assert_eq!(next_float32(2.0), 2.0f32);
233 let v = 1.0 + f64::EPSILON; assert!(f64::from(next_float32(v)) >= v);
236 }
237
238 #[test]
239 fn triangle_normal_rejects_collinear_and_repeated() {
240 let positions = [0.0f32, 0.0, 0.0, 1.0, 0.0, 0.0, 2.0, 0.0, 0.0, 0.0, 1.0, 0.0];
241 assert!(triangle_normal(&positions, 0, 1, 2).is_none()); assert!(triangle_normal(&positions, 0, 0, 1).is_none()); let n = triangle_normal(&positions, 0, 1, 3).expect("valid triangle");
244 assert!((hypot3(n[0], n[1], n[2]) - 1.0).abs() < 1e-12);
245 }
246
247 #[test]
248 fn bounds_disable_cone_on_degenerate() {
249 let positions = [0.0f32, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0, 0.0];
250 let bounds = compute_meshlet_bounds(&positions, &[0, 1, 2], &[], true).expect("bounds");
251 assert_eq!(bounds.cone, [0.0, 0.0, 1.0, -1.0]);
252 }
253
254 #[test]
255 fn hypot_matches_reference() {
256 assert_eq!(hypot3(3.0, 4.0, 0.0), 5.0);
257 assert_eq!(hypot2(3.0, 4.0), 5.0);
258 assert_eq!(hypot3(0.0, 0.0, 0.0), 0.0);
259 assert!(hypot3(f64::INFINITY, 1.0, 2.0).is_infinite());
260 }
261}