1use crate::error::DagResult;
10use crate::meshlet_builder::{build_meshlets, MeshletBuildResult};
11use crate::simplify::cluster_simplify;
12use crate::types::{
13 IndexedGeometry, OUTPUT_TRIANGLE_BUDGET_DEFAULT,
14};
15use crate::validation::{budget, validate_input};
16
17#[derive(Debug, Clone)]
19#[derive(Default)]
20pub struct DagOptions {
21 pub levels: Option<u32>,
23 pub max_triangles: Option<u32>,
25 pub max_vertices: Option<u32>,
27 pub output_triangle_budget: Option<u64>,
29}
30
31
32#[derive(Debug, Clone)]
34pub struct DagLevel {
35 pub level: u32,
37 pub error: f64,
39 pub positions: Vec<f32>,
41 pub indices: Vec<u32>,
43 pub meshlet_count: usize,
45 pub max_vertices: u32,
47 pub max_triangles: u32,
49 pub descriptors: Vec<u32>,
51 pub vertex_remap: Vec<u32>,
53 pub local_triangle_indices: Vec<u32>,
55 pub bounds: Vec<f32>,
57 pub source_triangles: Vec<u32>,
59 pub cluster_source_spans: Vec<u32>,
61}
62
63#[derive(Debug, Clone)]
65pub struct MeshletDag {
66 pub levels: Vec<DagLevel>,
68 pub parents_by_level: Vec<Vec<u32>>,
70}
71
72pub fn build_meshlet_dag(geometry: &IndexedGeometry, options: &DagOptions) -> DagResult<MeshletDag> {
77 let level_count = (options.levels.unwrap_or(4)).clamp(1, 8);
78 let output_triangle_budget = options.output_triangle_budget.unwrap_or(OUTPUT_TRIANGLE_BUDGET_DEFAULT);
79 let max_triangles = Some(options.max_triangles.unwrap_or(64));
82 let input = validate_input(geometry, options.max_vertices, max_triangles)?;
83
84 let base = build_meshlets(geometry, options.max_vertices, max_triangles)?;
86 let identity_source: Vec<u32> = (0..geometry.triangle_count() as u32).collect();
87 let base_spans = base.cluster_output_spans();
88 let mut levels = vec![DagLevel {
89 level: 0,
90 error: 0.0,
91 positions: input.geometry.positions.clone(),
92 indices: input.geometry.indices.clone(),
93 meshlet_count: base.meshlet_count,
94 max_vertices: input.max_vertices,
95 max_triangles: input.max_triangles,
96 descriptors: base.descriptors.clone(),
97 vertex_remap: base.vertex_remap.clone(),
98 local_triangle_indices: base.local_triangle_indices.clone(),
99 bounds: base.bounds.clone(),
100 source_triangles: identity_source.clone(),
101 cluster_source_spans: base_spans.clone(),
102 }];
103
104 let mut current_positions: Vec<f32> = input.geometry.positions.clone();
105 let mut current_indices: Vec<u32> = input.geometry.indices.clone();
106 let mut current_error = 0.0f64;
107 let mut current_cluster_spans = base_spans;
108 let mut parents_by_level: Vec<Vec<u32>> = Vec::new();
109
110 for level in 1..level_count {
111 let quantized = cluster_simplify(¤t_positions, ¤t_indices, 2.0)?;
112 if quantized.indices.len() / 3 >= current_indices.len() / 3 {
113 break; }
115 budget(
116 (quantized.indices.len() / 3) as u64,
117 output_triangle_budget,
118 "dag level triangles",
119 )?;
120 current_error = if current_error == 0.0 {
121 quantized.max_displacement
122 } else {
123 current_error + quantized.max_displacement
124 };
125 let built: MeshletBuildResult = build_meshlets(
126 &IndexedGeometry {
127 positions: quantized.positions.clone(),
128 indices: quantized.indices.clone(),
129 },
130 options.max_vertices,
131 max_triangles,
132 )?;
133 let coarse_spans = built.cluster_output_spans();
134 levels.push(DagLevel {
135 level,
136 error: current_error,
137 positions: quantized.positions.clone(),
138 indices: quantized.indices.clone(),
139 meshlet_count: built.meshlet_count,
140 max_vertices: input.max_vertices,
141 max_triangles: input.max_triangles,
142 descriptors: built.descriptors.clone(),
143 vertex_remap: built.vertex_remap.clone(),
144 local_triangle_indices: built.local_triangle_indices.clone(),
145 bounds: built.bounds.clone(),
146 source_triangles: quantized.source_triangles.clone(),
147 cluster_source_spans: coarse_spans.clone(),
148 });
149
150 let parents =
153 assign_parents(¤t_cluster_spans, &coarse_spans, &quantized.source_triangles);
154 parents_by_level.push(parents);
155
156 current_positions = quantized.positions;
159 current_indices = quantized.indices;
160 current_cluster_spans = coarse_spans;
161 }
162
163 Ok(MeshletDag { levels, parents_by_level })
164}
165
166fn assign_parents(fine_spans: &[u32], coarse_spans: &[u32], source_triangles: &[u32]) -> Vec<u32> {
171 let fine_count = fine_spans.len() / 2;
172 let coarse_count = coarse_spans.len() / 2;
173 let mut votes: Vec<std::collections::HashMap<u32, u64>> = vec![std::collections::HashMap::new(); fine_count];
174
175 for p in 0..coarse_count {
176 let start = coarse_spans[p * 2] as usize;
177 let end = coarse_spans[p * 2 + 1] as usize;
178 for &src in &source_triangles[start..end] {
179 if let Some(fine_idx) = binary_search_span(fine_spans, src) {
180 *votes[fine_idx].entry(p as u32).or_insert(0) += 1;
181 }
182 }
183 }
184 let mut parent_of_fine = vec![u32::MAX; fine_count];
185 for (f, per_coarse) in votes.iter().enumerate() {
186 let mut best = -1i64;
187 let mut best_votes = -1i64;
188 for (&coarse, &v) in per_coarse {
190 if (v as i64) > best_votes || ((v as i64) == best_votes && (coarse as i64) < best) {
191 best = coarse as i64;
192 best_votes = v as i64;
193 }
194 }
195 if best >= 0 {
196 parent_of_fine[f] = best as u32;
197 }
198 }
199 parent_of_fine
200}
201
202pub(crate) fn binary_search_span(spans: &[u32], src: u32) -> Option<usize> {
204 if spans.is_empty() {
205 return None;
206 }
207 let mut lo = 0usize;
208 let mut hi = spans.len() / 2 - 1;
209 while lo < hi {
210 let mid = (lo + hi) / 2;
211 if spans[mid * 2 + 1] <= src {
212 lo = mid + 1;
213 } else {
214 hi = mid;
215 }
216 }
217 (src >= spans[lo * 2] && src < spans[lo * 2 + 1]).then_some(lo)
218}
219
220#[cfg(test)]
221mod tests {
222 use super::*;
223
224 fn sphere_geometry(segments: usize, rings: usize) -> IndexedGeometry {
225 let (positions, indices) = crate::simplify::test_support::test_sphere(segments, rings);
226 IndexedGeometry { positions, indices }
227 }
228
229 #[test]
230 fn monotone_levels() {
231 let dag = build_meshlet_dag(&sphere_geometry(24, 12), &DagOptions { levels: Some(4), ..Default::default() })
232 .expect("dag");
233 assert!(dag.levels.len() >= 2);
234 assert_eq!(dag.levels[0].level, 0);
235 assert_eq!(dag.levels[0].error, 0.0);
236 for i in 1..dag.levels.len() {
237 let (prev, cur) = (&dag.levels[i - 1], &dag.levels[i]);
238 assert!(cur.indices.len() < prev.indices.len(), "triangles must shrink");
239 assert!(cur.error > prev.error, "error must grow");
240 assert!(cur.meshlet_count < prev.meshlet_count, "cluster count must shrink");
241 }
242 }
243
244 #[test]
245 fn level0_matches_direct_build() {
246 let g = sphere_geometry(16, 8);
247 let dag = build_meshlet_dag(&g, &DagOptions { levels: Some(3), ..Default::default() }).expect("dag");
248 let direct = build_meshlets(&g, None, Some(64)).expect("build");
250 assert_eq!(dag.levels[0].meshlet_count, direct.meshlet_count);
251 assert_eq!(dag.levels[0].descriptors, direct.descriptors);
252 }
253
254 #[test]
255 fn parents_are_surjective_single_parent() {
256 let dag = build_meshlet_dag(&sphere_geometry(24, 12), &DagOptions { levels: Some(4), ..Default::default() })
257 .expect("dag");
258 for (k, parents) in dag.parents_by_level.iter().enumerate() {
259 let fine = dag.levels[k].meshlet_count;
260 let coarse = dag.levels[k + 1].meshlet_count;
261 assert_eq!(parents.len(), fine);
262 let mut covered = vec![false; coarse];
263 for &parent in parents {
264 assert!(parent < coarse as u32, "parent out of range");
265 covered[parent as usize] = true;
266 }
267 assert!(covered.iter().all(|&c| c), "every coarse cluster must have children");
268 }
269 }
270
271 #[test]
272 fn empty_mesh_produces_single_level() {
273 let dag = build_meshlet_dag(&IndexedGeometry { positions: vec![], indices: vec![] }, &DagOptions::default())
274 .expect("empty dag");
275 assert_eq!(dag.levels.len(), 1);
276 assert_eq!(dag.levels[0].meshlet_count, 0);
277 assert!(dag.parents_by_level.is_empty());
278 }
279
280 #[test]
281 fn degenerate_only_mesh_stays_at_level0() {
282 let g = IndexedGeometry {
285 positions: vec![0.0f32, 0.0, 0.0, 1.0, 0.0, 0.0, 2.0, 0.0, 0.0, 3.0, 0.0, 0.0],
286 indices: vec![0, 1, 2, 1, 2, 3],
287 };
288 let dag = build_meshlet_dag(&g, &DagOptions::default()).expect("dag");
289 for level in &dag.levels {
290 assert_eq!(level.descriptors.len() / 4, level.meshlet_count);
291 }
292 }
293
294 #[test]
295 fn level_clamped_to_eight() {
296 let dag = build_meshlet_dag(&sphere_geometry(64, 32), &DagOptions { levels: Some(99), ..Default::default() })
297 .expect("dag");
298 assert!(dag.levels.len() <= 8);
299 }
300
301 #[test]
302 fn binary_search_span_basics() {
303 let spans = [0, 3, 3, 5, 5, 9];
304 assert_eq!(binary_search_span(&spans, 0), Some(0));
305 assert_eq!(binary_search_span(&spans, 2), Some(0));
306 assert_eq!(binary_search_span(&spans, 3), Some(1));
307 assert_eq!(binary_search_span(&spans, 4), Some(1));
308 assert_eq!(binary_search_span(&spans, 8), Some(2));
309 assert_eq!(binary_search_span(&spans, 9), None);
310 assert_eq!(binary_search_span(&[], 0), None);
311 }
312}