Skip to main content

geometry_dag/
dag.rs

1//! 簇级几何 DAG:逐层聚类简化 + 重建簇 + 父子投票(fine→coarse 单射)。
2//!
3//! 与 TS 权威实现(`packages/deep-engine/src/geometry/meshletDag.ts` 的
4//! `buildMeshletDag`)逐位对拍。语义要点:
5//! - level 0 = 原始网格直接簇划分;
6//! - 每层对上一层的输出做 `factor=2` 聚类简化,三角形数不再下降即收束;
7//! - 误差场逐层累加(f64);父子归属按源三角形覆盖投票,平票取最小父索引。
8
9use 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/// DAG 构建选项(与 TS `MeshletDagOptions` 对应)。
18#[derive(Debug, Clone)]
19#[derive(Default)]
20pub struct DagOptions {
21    /// 层级数(含原始层),钳制到 `1..=8`;缺省 4。
22    pub levels: Option<u32>,
23    /// 簇最大三角形数,透传簇划分;缺省 64。
24    pub max_triangles: Option<u32>,
25    /// 簇最大顶点数;缺省 64。
26    pub max_vertices: Option<u32>,
27    /// 输出三角总量预算,防失控;缺省 8,000,000。
28    pub output_triangle_budget: Option<u64>,
29}
30
31
32/// 单层 DAG(与 TS `MeshletDagLevel` 同构)。
33#[derive(Debug, Clone)]
34pub struct DagLevel {
35    /// 层号,0 = 原始。
36    pub level: u32,
37    /// 该层误差:顶点相对源位置的最大位移累计(世界单位)。
38    pub error: f64,
39    /// 层网格顶点位置(紧凑 XYZ)。
40    pub positions: Vec<f32>,
41    /// 层网格索引。
42    pub indices: Vec<u32>,
43    /// 簇数。
44    pub meshlet_count: usize,
45    /// 构建参数:簇顶点上限。
46    pub max_vertices: u32,
47    /// 构建参数:簇三角形上限。
48    pub max_triangles: u32,
49    /// 簇描述符(`[vertexOffset, vertexCount, triangleOffset, triangleCount]` × n)。
50    pub descriptors: Vec<u32>,
51    /// 拼接全局顶点表。
52    pub vertex_remap: Vec<u32>,
53    /// 打包局部三角形。
54    pub local_triangle_indices: Vec<u32>,
55    /// 包围体(16 f32 × n)。
56    pub bounds: Vec<f32>,
57    /// 输出三角形 i 的代表源三角形(level 0 序)。
58    pub source_triangles: Vec<u32>,
59    /// 簇 i 的源三角形段 `[start,end)`(输入序连续)。
60    pub cluster_source_spans: Vec<u32>,
61}
62
63/// 完整 DAG(与 TS `MeshletDag` 同构)。
64#[derive(Debug, Clone)]
65pub struct MeshletDag {
66    /// 各层(level 0 起升序)。
67    pub levels: Vec<DagLevel>,
68    /// `parents_by_level[k][c]` = level `k` 簇 `c` 在 level `k+1` 的父簇索引;每细簇恰一父。
69    pub parents_by_level: Vec<Vec<u32>>,
70}
71
72/// 构建簇级几何 DAG。
73///
74/// # Errors
75/// 输入非法或单层三角形超出 `output_triangle_budget` 时返回错误。
76pub 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    // TS `buildMeshletDag` 语义:DAG 层的簇三角形缺省是 64(MeshletDagOptions 注释),
80    // 与 buildMeshlets 直接调用的缺省 126 不同;透传前在此落定缺省值。
81    let max_triangles = Some(options.max_triangles.unwrap_or(64));
82    let input = validate_input(geometry, options.max_vertices, max_triangles)?;
83
84    // 原始层:直接簇划分(与生产簇划分完全一致)。
85    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(&current_positions, &current_indices, 2.0)?;
112        if quantized.indices.len() / 3 >= current_indices.len() / 3 {
113            break; // 不再下降即收束
114        }
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        // 父子回填:level k(细,=上一轮 current* 状态)的每个簇,按其输入三角形被本层
151        // (level k+1)哪些簇覆盖投票,取占比最大者为父(fine→coarse 单射,平票取最小索引)。
152        let parents =
153            assign_parents(&current_cluster_spans, &coarse_spans, &quantized.source_triangles);
154        parents_by_level.push(parents);
155
156        // 下一轮的"当前层"状态:move 接管 quantized(每层一次 clone 付出在 level 快照上,
157        // 换取借用在 levels 增长下的清晰生命周期)。
158        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
166/// 细层簇 → 粗层簇投票。`fine_spans` 为细层簇的输入三角形段,`coarse_spans` 为粗层簇
167/// 的输入三角形段,`source_triangles` 为粗层输出三角形 → 细层输入三角形索引。
168///
169/// 无任何覆盖(全部三角形被去重丢弃)的细簇父索引为 [`u32::MAX`](对应 TS 的 -1 哨兵)。
170fn 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        // 与 TS 一致:严格大于才替换,平票取最小父索引。
189        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
202/// 段表二分:返回 `src` 落入的簇索引(段为输入序连续区间);空表或未命中返回 `None`。
203pub(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        // DAG 层簇参数缺省(64/64),与直接 build_meshlets 的显式 64 对齐。
249        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        // 全共线三角形:level 0 可分簇,但简化无法减少(退化剔除后为 0 → 0 >= 3 不成立,
283        // 实际 0 < 3 会下降一层;空层簇数 0,父子表为空)。验证不 panic 且结构自洽。
284        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}