Skip to main content

manifold_rust/
sort.rs

1// sort.rs — Phase 5: SortGeometry, Morton codes, vertex/face sorting
2//
3// Ports src/sort.cpp from the Manifold C++ library.
4// The Collider is stubbed (Phase 10 will implement it fully).
5
6use crate::linalg::Vec3;
7use crate::types::{Box as BBox, Halfedge};
8use crate::impl_mesh::ManifoldImpl;
9
10// -----------------------------------------------------------------------
11// Morton code (30-bit, 10 bits per axis)
12// -----------------------------------------------------------------------
13
14const K_NO_CODE: u32 = 0xFFFF_FFFFu32;
15
16/// Spread the low 10 bits of v into bits 0,3,6,9,...,27 (every 3rd bit).
17/// This is the inverse of the interleaving needed for a 3D Morton code.
18#[inline]
19fn spread_bits3(mut v: u32) -> u32 {
20    v = 0xFF0000FFu32 & v.wrapping_mul(0x00010001u32);
21    v = 0x0F00F00Fu32 & v.wrapping_mul(0x00000101u32);
22    v = 0xC30C30C3u32 & v.wrapping_mul(0x00000011u32);
23    v = 0x49249249u32 & v.wrapping_mul(0x00000005u32);
24    v
25}
26
27/// Compute a 30-bit Morton code for a position within the given bounding box.
28/// Returns K_NO_CODE for NaN positions (unreferenced vertices).
29pub fn morton_code(position: Vec3, bbox: &BBox) -> u32 {
30    if position.x.is_nan() {
31        return K_NO_CODE;
32    }
33    morton_code_impl(position, bbox)
34}
35
36fn morton_code_impl(position: Vec3, bbox: &BBox) -> u32 {
37    let range = bbox.max - bbox.min;
38    let xyz = (position - bbox.min) / range;
39    let x_f = (1024.0 * xyz.x).min(1023.0).max(0.0);
40    let y_f = (1024.0 * xyz.y).min(1023.0).max(0.0);
41    let z_f = (1024.0 * xyz.z).min(1023.0).max(0.0);
42    let x = spread_bits3(x_f as u32);
43    let y = spread_bits3(y_f as u32);
44    let z = spread_bits3(z_f as u32);
45    x * 4 + y * 2 + z
46}
47
48// -----------------------------------------------------------------------
49// SortVerts
50// -----------------------------------------------------------------------
51
52/// Sorts vertices by their Morton code and removes NaN-flagged vertices.
53/// Updates all halfedge vertex references accordingly.
54pub fn sort_verts(mesh: &mut ManifoldImpl) {
55    let num_vert = mesh.vert_pos.len();
56    let bbox = mesh.bbox;
57
58    // Compute Morton code for each vertex
59    let vert_morton: Vec<u32> = mesh.vert_pos.iter()
60        .map(|&p| morton_code(p, &bbox))
61        .collect();
62
63    // Build sorted index array
64    let mut vert_new2old: Vec<i32> = (0..num_vert as i32).collect();
65    vert_new2old.sort_by(|&a, &b| vert_morton[a as usize].cmp(&vert_morton[b as usize]));
66
67    // Find how many survive (NaN verts get K_NO_CODE, sort to end)
68    let new_num_vert = vert_new2old.partition_point(|&v| vert_morton[v as usize] < K_NO_CODE);
69    let vert_new2old_trimmed = &vert_new2old[..new_num_vert];
70
71    reindex_verts(mesh, vert_new2old_trimmed, num_vert);
72
73    // Permute vert positions (only surviving verts)
74    let old_pos = mesh.vert_pos.clone();
75    mesh.vert_pos.resize(new_num_vert, Vec3::new(0.0, 0.0, 0.0));
76    for (new_idx, &old_idx) in vert_new2old_trimmed.iter().enumerate() {
77        mesh.vert_pos[new_idx] = old_pos[old_idx as usize];
78    }
79
80    // Permute vert normals if present
81    if mesh.vert_normal.len() == num_vert {
82        let old_n = mesh.vert_normal.clone();
83        mesh.vert_normal.resize(new_num_vert, Vec3::new(0.0, 0.0, 0.0));
84        for (new_idx, &old_idx) in vert_new2old_trimmed.iter().enumerate() {
85            mesh.vert_normal[new_idx] = old_n[old_idx as usize];
86        }
87    }
88}
89
90/// Updates halfedge start/end vert indices from old→new index mapping.
91/// `vert_new2old[new] = old` — we invert to get `vert_old2new[old] = new`.
92pub fn reindex_verts(mesh: &mut ManifoldImpl, vert_new2old: &[i32], old_num_vert: usize) {
93    let mut vert_old2new = vec![-1i32; old_num_vert];
94    for (new_idx, &old_idx) in vert_new2old.iter().enumerate() {
95        vert_old2new[old_idx as usize] = new_idx as i32;
96    }
97    let has_prop = mesh.num_prop > 0;
98    for edge in mesh.halfedge.iter_mut() {
99        if edge.start_vert < 0 {
100            continue;
101        }
102        edge.start_vert = vert_old2new[edge.start_vert as usize];
103        edge.end_vert = vert_old2new[edge.end_vert as usize];
104        if !has_prop {
105            edge.prop_vert = edge.start_vert;
106        }
107    }
108}
109
110// -----------------------------------------------------------------------
111// GetFaceBoxMorton
112// -----------------------------------------------------------------------
113
114/// Computes per-face bounding boxes and Morton codes.
115/// Faces with removed halfedges (pairedHalfedge < 0) get K_NO_CODE.
116pub fn get_face_box_morton(mesh: &ManifoldImpl) -> (Vec<BBox>, Vec<u32>) {
117    let num_tri = mesh.num_tri();
118    let bbox = mesh.bbox;
119    let mut face_box = vec![BBox::default(); num_tri];
120    let mut face_morton = vec![0u32; num_tri];
121
122    for face in 0..num_tri {
123        if mesh.halfedge[3 * face].paired_halfedge < 0 {
124            face_morton[face] = K_NO_CODE;
125            continue;
126        }
127        let mut center = Vec3::new(0.0, 0.0, 0.0);
128        for i in 0..3 {
129            let pos = mesh.vert_pos[mesh.halfedge[3 * face + i].start_vert as usize];
130            center = center + pos;
131            face_box[face].union_point(pos);
132        }
133        center = center / 3.0;
134        face_morton[face] = morton_code_impl(center, &bbox);
135    }
136
137    (face_box, face_morton)
138}
139
140// -----------------------------------------------------------------------
141// SortFaces / GatherFaces
142// -----------------------------------------------------------------------
143
144/// Sorts faces by Morton code, removing faces flagged for removal (K_NO_CODE).
145/// Updates `face_box` and `face_morton` in-place.
146pub fn sort_faces(mesh: &mut ManifoldImpl, face_box: &mut Vec<BBox>, face_morton: &mut Vec<u32>) {
147    let num_tri = face_box.len();
148    let mut face_new2old: Vec<usize> = (0..num_tri).collect();
149
150    // Stable sort by Morton code (removed tris get K_NO_CODE → sorted last)
151    face_new2old.sort_by(|&a, &b| face_morton[a].cmp(&face_morton[b]));
152
153    // Trim removed faces
154    let new_num_tri = face_new2old.partition_point(|&f| face_morton[f] < K_NO_CODE);
155    face_new2old.truncate(new_num_tri);
156
157    // Permute face_morton and face_box to match new order
158    let old_morton = face_morton.clone();
159    let old_box = face_box.clone();
160    face_morton.resize(new_num_tri, 0);
161    face_box.resize(new_num_tri, BBox::default());
162    for (new_f, &old_f) in face_new2old.iter().enumerate() {
163        face_morton[new_f] = old_morton[old_f];
164        face_box[new_f] = old_box[old_f];
165    }
166
167    gather_faces(mesh, &face_new2old);
168}
169
170/// Reorders halfedges (and related arrays) according to face_new2old permutation.
171pub fn gather_faces(mesh: &mut ManifoldImpl, face_new2old: &[usize]) {
172    let num_tri = face_new2old.len();
173    let old_num_tri = mesh.num_tri();
174
175    // Permute tri_ref if present
176    if mesh.mesh_relation.tri_ref.len() == old_num_tri {
177        let old_tri_ref = mesh.mesh_relation.tri_ref.clone();
178        mesh.mesh_relation.tri_ref.resize(num_tri, Default::default());
179        for (new_f, &old_f) in face_new2old.iter().enumerate() {
180            mesh.mesh_relation.tri_ref[new_f] = old_tri_ref[old_f];
181        }
182    }
183
184    // Permute face normals if present
185    if mesh.face_normal.len() == old_num_tri {
186        let old_normals = mesh.face_normal.clone();
187        mesh.face_normal.resize(num_tri, Vec3::new(0.0, 0.0, 0.0));
188        for (new_f, &old_f) in face_new2old.iter().enumerate() {
189            mesh.face_normal[new_f] = old_normals[old_f];
190        }
191    }
192
193    // Build faceOld2New for pairedHalfedge remapping
194    let mut face_old2new = vec![-1i32; old_num_tri];
195    for (new_f, &old_f) in face_new2old.iter().enumerate() {
196        face_old2new[old_f] = new_f as i32;
197    }
198
199    // Gather halfedges from old layout into new
200    let old_halfedge = mesh.halfedge.clone();
201    let old_tangent = mesh.halfedge_tangent.clone();
202    let has_tangent = !old_tangent.is_empty();
203
204    mesh.halfedge.resize(3 * num_tri, Halfedge::default());
205    if has_tangent {
206        mesh.halfedge_tangent.resize(3 * num_tri, Default::default());
207    }
208
209    for new_face in 0..num_tri {
210        let old_face = face_new2old[new_face];
211        for i in 0..3 {
212            let old_edge_idx = 3 * old_face + i;
213            let new_edge_idx = 3 * new_face + i;
214            let mut edge = old_halfedge[old_edge_idx];
215            // Remap pairedHalfedge
216            if edge.paired_halfedge >= 0 {
217                let paired_old_face = (edge.paired_halfedge / 3) as usize;
218                let offset = edge.paired_halfedge % 3;
219                edge.paired_halfedge = 3 * face_old2new[paired_old_face] + offset;
220            }
221            mesh.halfedge[new_edge_idx] = edge;
222            if has_tangent {
223                mesh.halfedge_tangent[new_edge_idx] = old_tangent[old_edge_idx];
224            }
225        }
226    }
227}
228
229// -----------------------------------------------------------------------
230// CompactProps
231// -----------------------------------------------------------------------
232
233/// Removes unreferenced property vertices and reindexes propVerts.
234pub fn compact_props(mesh: &mut ManifoldImpl) {
235    if mesh.num_prop == 0 {
236        return;
237    }
238    let num_prop = mesh.num_prop;
239    let num_prop_verts = mesh.properties.len() / num_prop;
240
241    // Mark which prop verts are referenced
242    let mut keep = vec![false; num_prop_verts];
243    for edge in &mesh.halfedge {
244        if edge.prop_vert >= 0 && (edge.prop_vert as usize) < num_prop_verts {
245            keep[edge.prop_vert as usize] = true;
246        }
247    }
248
249    // Build prefix sum for old→new mapping
250    let mut prop_old2new = vec![0i32; num_prop_verts + 1];
251    for i in 0..num_prop_verts {
252        prop_old2new[i + 1] = prop_old2new[i] + if keep[i] { 1 } else { 0 };
253    }
254    let new_num_prop_verts = prop_old2new[num_prop_verts] as usize;
255
256    // Compact properties array
257    let old_prop = mesh.properties.clone();
258    mesh.properties.resize(num_prop * new_num_prop_verts, 0.0);
259    for old_idx in 0..num_prop_verts {
260        if !keep[old_idx] {
261            continue;
262        }
263        let new_idx = prop_old2new[old_idx] as usize;
264        for p in 0..num_prop {
265            mesh.properties[new_idx * num_prop + p] = old_prop[old_idx * num_prop + p];
266        }
267    }
268
269    // Remap propVert indices in halfedges
270    for edge in mesh.halfedge.iter_mut() {
271        if edge.prop_vert >= 0 {
272            edge.prop_vert = prop_old2new[edge.prop_vert as usize];
273        }
274    }
275}
276
277// -----------------------------------------------------------------------
278// SortGeometry — main entry point
279// -----------------------------------------------------------------------
280
281/// Sorts vertices and faces by Morton code, removes flagged-for-deletion
282/// elements, and compacts property arrays. Should be called after
283/// `create_halfedges()` to finalize the mesh topology.
284///
285/// Note: Collider construction is done in Phase 10.
286pub fn sort_geometry(mesh: &mut ManifoldImpl) {
287    if mesh.halfedge.is_empty() {
288        return;
289    }
290    sort_verts(mesh);
291    let (mut face_box, mut face_morton) = get_face_box_morton(mesh);
292    sort_faces(mesh, &mut face_box, &mut face_morton);
293    if mesh.halfedge.is_empty() {
294        return;
295    }
296    compact_props(mesh);
297
298    debug_assert!(
299        mesh.halfedge.len() % 6 == 0,
300        "Not an even number of halfedges after sorting (expected multiple of 6, got {})",
301        mesh.halfedge.len()
302    );
303}
304
305// -----------------------------------------------------------------------
306// Tests
307// -----------------------------------------------------------------------
308
309#[cfg(test)]
310mod tests {
311    use super::*;
312    use crate::linalg::Mat3x4;
313    use crate::impl_mesh::ManifoldImpl;
314
315    #[test]
316    fn test_spread_bits3() {
317        // SpreadBits3(0b1111111111) should interleave into alternating positions
318        // Values verified against C++ constexpr evaluation
319        assert_eq!(spread_bits3(0), 0);
320        assert_eq!(spread_bits3(1), 1);
321        // Each bit of the input lands 3 positions apart in the output
322        assert_eq!(spread_bits3(0b10), 0b1000);
323        assert_eq!(spread_bits3(0b11), 0b1001);
324        assert_eq!(spread_bits3(0b100), 0b1000000);
325    }
326
327    #[test]
328    fn test_morton_code_basic() {
329        let bbox = BBox {
330            min: Vec3::new(0.0, 0.0, 0.0),
331            max: Vec3::new(1.0, 1.0, 1.0),
332        };
333        // Origin → all zeros → code 0
334        let code_origin = morton_code(Vec3::new(0.0, 0.0, 0.0), &bbox);
335        assert_eq!(code_origin, 0);
336
337        // NaN → K_NO_CODE
338        let code_nan = morton_code(Vec3::new(f64::NAN, 0.0, 0.0), &bbox);
339        assert_eq!(code_nan, K_NO_CODE);
340
341        // Center of cube should be a positive code less than K_NO_CODE
342        let code_center = morton_code(Vec3::new(0.5, 0.5, 0.5), &bbox);
343        assert!(code_center > 0 && code_center < K_NO_CODE);
344    }
345
346    #[test]
347    fn test_morton_code_ordering() {
348        // Points closer together should (generally) have closer Morton codes.
349        // More specifically: points sorted by Morton code produce a Z-curve traversal.
350        let bbox = BBox {
351            min: Vec3::new(0.0, 0.0, 0.0),
352            max: Vec3::new(8.0, 8.0, 8.0),
353        };
354        let p0 = morton_code(Vec3::new(0.0, 0.0, 0.0), &bbox);
355        let p1 = morton_code(Vec3::new(1.0, 0.0, 0.0), &bbox);
356        let p2 = morton_code(Vec3::new(2.0, 0.0, 0.0), &bbox);
357        // These should be strictly increasing along x with y=z=0
358        assert!(p0 < p1);
359        assert!(p1 < p2);
360    }
361
362    #[test]
363    fn test_sort_geometry_tetrahedron() {
364        let mut m = ManifoldImpl::tetrahedron(&Mat3x4::identity());
365        // Should have 4 vertices and 12 halfedges (4 triangles) before sort
366        assert_eq!(m.vert_pos.len(), 4);
367        assert_eq!(m.halfedge.len(), 12);
368        sort_geometry(&mut m);
369        // Sort shouldn't remove any valid verts/faces
370        assert_eq!(m.vert_pos.len(), 4);
371        assert_eq!(m.halfedge.len(), 12);
372    }
373
374    #[test]
375    fn test_sort_geometry_cube() {
376        let mut m = ManifoldImpl::cube(&Mat3x4::identity());
377        let vert_count = m.vert_pos.len();
378        let halfedge_count = m.halfedge.len();
379        sort_geometry(&mut m);
380        assert_eq!(m.vert_pos.len(), vert_count);
381        assert_eq!(m.halfedge.len(), halfedge_count);
382        // After sort, paired halfedges should still be valid
383        for (i, edge) in m.halfedge.iter().enumerate() {
384            assert!(edge.paired_halfedge >= 0,
385                "halfedge {} has invalid paired_halfedge {}", i, edge.paired_halfedge);
386            let paired = &m.halfedge[edge.paired_halfedge as usize];
387            assert_eq!(paired.paired_halfedge, i as i32,
388                "halfedge {} paired -> {} but paired doesn't point back", i, edge.paired_halfedge);
389        }
390    }
391
392    #[test]
393    fn test_reindex_verts_identity() {
394        let mut m = ManifoldImpl::tetrahedron(&Mat3x4::identity());
395        let n = m.vert_pos.len();
396        // Identity permutation should not change anything
397        let identity: Vec<i32> = (0..n as i32).collect();
398        let before: Vec<_> = m.halfedge.iter().map(|e| (e.start_vert, e.end_vert)).collect();
399        reindex_verts(&mut m, &identity, n);
400        let after: Vec<_> = m.halfedge.iter().map(|e| (e.start_vert, e.end_vert)).collect();
401        assert_eq!(before, after);
402    }
403
404    #[test]
405    fn test_sort_faces_manifold_preserved() {
406        // After sort_geometry, mesh must still be 2-manifold
407        let mut m = ManifoldImpl::cube(&Mat3x4::identity());
408        sort_geometry(&mut m);
409        assert!(m.is_2_manifold());
410    }
411}