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        mesh.collider = crate::collider::Collider::default();
289        return;
290    }
291    sort_verts(mesh);
292    let (mut face_box, mut face_morton) = get_face_box_morton(mesh);
293    sort_faces(mesh, &mut face_box, &mut face_morton);
294    if mesh.halfedge.is_empty() {
295        mesh.collider = crate::collider::Collider::default();
296        return;
297    }
298    // Cache the face BVH on the mesh (C++ builds collider_ here in
299    // SortGeometry); query sites reuse it instead of rebuilding per query.
300    mesh.collider = crate::collider::Collider::new(face_box, face_morton);
301    compact_props(mesh);
302
303    debug_assert!(
304        mesh.halfedge.len() % 6 == 0,
305        "Not an even number of halfedges after sorting (expected multiple of 6, got {})",
306        mesh.halfedge.len()
307    );
308}
309
310// -----------------------------------------------------------------------
311// Tests
312// -----------------------------------------------------------------------
313
314#[cfg(test)]
315mod tests {
316    use super::*;
317    use crate::linalg::Mat3x4;
318    use crate::impl_mesh::ManifoldImpl;
319
320    #[test]
321    fn test_spread_bits3() {
322        // SpreadBits3(0b1111111111) should interleave into alternating positions
323        // Values verified against C++ constexpr evaluation
324        assert_eq!(spread_bits3(0), 0);
325        assert_eq!(spread_bits3(1), 1);
326        // Each bit of the input lands 3 positions apart in the output
327        assert_eq!(spread_bits3(0b10), 0b1000);
328        assert_eq!(spread_bits3(0b11), 0b1001);
329        assert_eq!(spread_bits3(0b100), 0b1000000);
330    }
331
332    #[test]
333    fn test_morton_code_basic() {
334        let bbox = BBox {
335            min: Vec3::new(0.0, 0.0, 0.0),
336            max: Vec3::new(1.0, 1.0, 1.0),
337        };
338        // Origin → all zeros → code 0
339        let code_origin = morton_code(Vec3::new(0.0, 0.0, 0.0), &bbox);
340        assert_eq!(code_origin, 0);
341
342        // NaN → K_NO_CODE
343        let code_nan = morton_code(Vec3::new(f64::NAN, 0.0, 0.0), &bbox);
344        assert_eq!(code_nan, K_NO_CODE);
345
346        // Center of cube should be a positive code less than K_NO_CODE
347        let code_center = morton_code(Vec3::new(0.5, 0.5, 0.5), &bbox);
348        assert!(code_center > 0 && code_center < K_NO_CODE);
349    }
350
351    #[test]
352    fn test_morton_code_ordering() {
353        // Points closer together should (generally) have closer Morton codes.
354        // More specifically: points sorted by Morton code produce a Z-curve traversal.
355        let bbox = BBox {
356            min: Vec3::new(0.0, 0.0, 0.0),
357            max: Vec3::new(8.0, 8.0, 8.0),
358        };
359        let p0 = morton_code(Vec3::new(0.0, 0.0, 0.0), &bbox);
360        let p1 = morton_code(Vec3::new(1.0, 0.0, 0.0), &bbox);
361        let p2 = morton_code(Vec3::new(2.0, 0.0, 0.0), &bbox);
362        // These should be strictly increasing along x with y=z=0
363        assert!(p0 < p1);
364        assert!(p1 < p2);
365    }
366
367    #[test]
368    fn test_sort_geometry_tetrahedron() {
369        let mut m = ManifoldImpl::tetrahedron(&Mat3x4::identity());
370        // Should have 4 vertices and 12 halfedges (4 triangles) before sort
371        assert_eq!(m.vert_pos.len(), 4);
372        assert_eq!(m.halfedge.len(), 12);
373        sort_geometry(&mut m);
374        // Sort shouldn't remove any valid verts/faces
375        assert_eq!(m.vert_pos.len(), 4);
376        assert_eq!(m.halfedge.len(), 12);
377    }
378
379    #[test]
380    fn test_sort_geometry_cube() {
381        let mut m = ManifoldImpl::cube(&Mat3x4::identity());
382        let vert_count = m.vert_pos.len();
383        let halfedge_count = m.halfedge.len();
384        sort_geometry(&mut m);
385        assert_eq!(m.vert_pos.len(), vert_count);
386        assert_eq!(m.halfedge.len(), halfedge_count);
387        // After sort, paired halfedges should still be valid
388        for (i, edge) in m.halfedge.iter().enumerate() {
389            assert!(edge.paired_halfedge >= 0,
390                "halfedge {} has invalid paired_halfedge {}", i, edge.paired_halfedge);
391            let paired = &m.halfedge[edge.paired_halfedge as usize];
392            assert_eq!(paired.paired_halfedge, i as i32,
393                "halfedge {} paired -> {} but paired doesn't point back", i, edge.paired_halfedge);
394        }
395    }
396
397    #[test]
398    fn test_reindex_verts_identity() {
399        let mut m = ManifoldImpl::tetrahedron(&Mat3x4::identity());
400        let n = m.vert_pos.len();
401        // Identity permutation should not change anything
402        let identity: Vec<i32> = (0..n as i32).collect();
403        let before: Vec<_> = m.halfedge.iter().map(|e| (e.start_vert, e.end_vert)).collect();
404        reindex_verts(&mut m, &identity, n);
405        let after: Vec<_> = m.halfedge.iter().map(|e| (e.start_vert, e.end_vert)).collect();
406        assert_eq!(before, after);
407    }
408
409    #[test]
410    fn test_sort_faces_manifold_preserved() {
411        // After sort_geometry, mesh must still be 2-manifold
412        let mut m = ManifoldImpl::cube(&Mat3x4::identity());
413        sort_geometry(&mut m);
414        assert!(m.is_2_manifold());
415    }
416}