Skip to main content

manifold_rust/
types_meshgl.rs

1// types_meshgl.rs — MeshGLP: the GL-style interchange mesh representation.
2//
3// Ported from include/manifold/manifold.h (MeshGLP) and src/manifold.cpp
4// (MeshGL::Merge, MeshGL::UpdateNormals). Extracted from types.rs, which
5// re-exports MeshGLP / MeshGL / MeshGL64 so external paths
6// (`crate::types::MeshGL`, ...) are unchanged. Meshes enter and leave the
7// library in this form; impl_mesh.rs / manifold_meshgl.rs convert between it
8// and the internal halfedge representation.
9
10use crate::linalg::Vec3;
11use crate::types::Box;
12
13/// Precision parameter of a [`MeshGLP`] (`f32` or `f64`). Mirrors the
14/// `Precision` template parameter of the C++ `MeshGLP`: conversions to and
15/// from the kernel's internal `f64` happen through this trait, so the f32
16/// instantiation narrows exactly where the C++ float instantiation does and
17/// the f64 instantiation is lossless end to end.
18pub trait MeshPrecision: Copy + Default {
19    /// True for `f32`. The exported tolerance is floored at
20    /// `f32::EPSILON * bbox.scale()` only for single precision, matching the
21    /// `std::is_same<Precision, float>` checks in the C++ template.
22    const IS_SINGLE: bool;
23    fn to_f64(self) -> f64;
24    fn from_f64(v: f64) -> Self;
25}
26
27impl MeshPrecision for f32 {
28    const IS_SINGLE: bool = true;
29    fn to_f64(self) -> f64 { self as f64 }
30    fn from_f64(v: f64) -> Self { v as f32 }
31}
32
33impl MeshPrecision for f64 {
34    const IS_SINGLE: bool = false;
35    fn to_f64(self) -> f64 { self }
36    fn from_f64(v: f64) -> Self { v }
37}
38
39/// Index parameter of a [`MeshGLP`] (`u32` or `u64`), mirroring the `I`
40/// template parameter of the C++ `MeshGLP`. The kernel itself indexes with
41/// 32 bits for both instantiations (as the C++ does — its import casts every
42/// index to `uint32_t`), so `u64` indices wider than 32 bits truncate on
43/// import exactly like the C++ `static_cast`.
44pub trait MeshIndex: Copy + Default {
45    fn to_u64(self) -> u64;
46    fn from_usize(v: usize) -> Self;
47    /// Conversion from the kernel's `int` indices on export. Matches C++
48    /// integral conversion: bit-pattern for u32, sign-extension for u64.
49    fn from_i32(v: i32) -> Self;
50}
51
52impl MeshIndex for u32 {
53    fn to_u64(self) -> u64 { self as u64 }
54    fn from_usize(v: usize) -> Self { v as u32 }
55    fn from_i32(v: i32) -> Self { v as u32 }
56}
57
58impl MeshIndex for u64 {
59    fn to_u64(self) -> u64 { self }
60    fn from_usize(v: usize) -> Self { v as u64 }
61    fn from_i32(v: i32) -> Self { v as i64 as u64 }
62}
63
64/// GL-style mesh representation. Generic over precision (f32/f64) and index type (u32/u64).
65#[derive(Clone, Debug, Default)]
66pub struct MeshGLP<P: Copy + Default, I: Copy + Default = u32> {
67    /// Number of properties per vertex, always >= 3.
68    pub num_prop: I,
69    /// Flat interleaved vertex properties: [x, y, z, ...] × num_verts.
70    pub vert_properties: Vec<P>,
71    /// Triangle vertex indices, 3 per triangle (CCW from outside).
72    pub tri_verts: Vec<I>,
73    /// Optional: merge-from vertex indices.
74    pub merge_from_vert: Vec<I>,
75    /// Optional: merge-to vertex indices.
76    pub merge_to_vert: Vec<I>,
77    /// Optional: run start indices into triVerts.
78    pub run_index: Vec<I>,
79    /// Optional: original mesh ID per run.
80    pub run_original_id: Vec<u32>,
81    /// Optional: 3×4 column-major transform per run (12 elements each).
82    pub run_transform: Vec<P>,
83    /// Optional: source face ID per triangle.
84    pub face_id: Vec<I>,
85    /// Optional: halfedge tangent vectors (4 per halfedge).
86    pub halfedge_tangent: Vec<P>,
87    /// Optional: per-run flags; 1 = backside (normals need flipping).
88    pub run_flags: Vec<u8>,
89    /// Tolerance for mesh simplification.
90    pub tolerance: P,
91}
92
93impl<P: MeshPrecision, I: MeshIndex> MeshGLP<P, I> {
94    pub fn num_vert(&self) -> usize {
95        if self.num_prop.to_u64() == 0 {
96            0
97        } else {
98            self.vert_properties.len() / self.num_prop.to_u64() as usize
99        }
100    }
101
102    pub fn num_tri(&self) -> usize {
103        self.tri_verts.len() / 3
104    }
105
106    pub fn get_vert_pos(&self, v: usize) -> [P; 3] {
107        let offset = v * self.num_prop.to_u64() as usize;
108        [self.vert_properties[offset], self.vert_properties[offset + 1], self.vert_properties[offset + 2]]
109    }
110
111    pub fn get_tri_verts(&self, t: usize) -> [I; 3] {
112        let offset = 3 * t;
113        [self.tri_verts[offset], self.tri_verts[offset + 1], self.tri_verts[offset + 2]]
114    }
115
116    pub fn get_tangent(&self, h: usize) -> [P; 4] {
117        let offset = 4 * h;
118        [
119            self.halfedge_tangent[offset],
120            self.halfedge_tangent[offset + 1],
121            self.halfedge_tangent[offset + 2],
122            self.halfedge_tangent[offset + 3],
123        ]
124    }
125}
126
127impl MeshGLP<f32, u32> {
128    /// Merges coincident vertices based on position within tolerance.
129    /// Uses BVH collision detection to find open edges, then groups
130    /// coincident vertices via union-find. Returns true if new merges
131    /// were found, false if the mesh was already fully merged.
132    pub fn merge(&mut self) -> bool {
133        use crate::collider::Collider;
134        use crate::disjoint_sets::DisjointSets;
135        use crate::sort::morton_code;
136        use std::collections::BTreeSet;
137
138        let num_vert = self.num_vert();
139        let num_tri = self.num_tri();
140
141        // Build initial merge map from existing merge vectors
142        let mut merge_map: Vec<usize> = (0..num_vert).collect();
143        for i in 0..self.merge_from_vert.len() {
144            merge_map[self.merge_from_vert[i] as usize] = self.merge_to_vert[i] as usize;
145        }
146
147        // Find open (non-manifold) edges
148        let next = [1usize, 2, 0];
149        let mut open_edges: BTreeSet<(usize, usize)> = BTreeSet::new();
150        for tri in 0..num_tri {
151            for i in 0..3 {
152                let a = merge_map[self.tri_verts[3 * tri + next[i]] as usize];
153                let b = merge_map[self.tri_verts[3 * tri + i] as usize];
154                let edge = (a, b);
155                // Look for the reverse edge
156                let rev = (b, a);
157                if open_edges.contains(&rev) {
158                    open_edges.remove(&rev);
159                } else {
160                    open_edges.insert(edge);
161                }
162            }
163        }
164
165        if open_edges.is_empty() {
166            return false;
167        }
168
169        // Collect unique open vertices — only the START vertex of each open
170        // halfedge, matching C++ which stores (start,end) and takes edge.first=start.
171        // Our BTreeSet stores (end,start) so we take edge.1 (= b = start vertex).
172        let open_verts: Vec<usize> = {
173            let mut vset = std::collections::BTreeSet::new();
174            for (_a, b) in &open_edges {
175                vset.insert(*b);
176            }
177            vset.into_iter().collect()
178        };
179        let num_open = open_verts.len();
180
181        // Compute bounding box
182        let mut bbox = Box::default();
183        for v in 0..num_vert {
184            let pos = self.get_vert_pos(v);
185            let p = Vec3::new(pos[0] as f64, pos[1] as f64, pos[2] as f64);
186            bbox.union_point(p);
187        }
188
189        let tolerance = f64::max(
190            self.tolerance as f64,
191            f32::EPSILON as f64 * bbox.scale(),
192        );
193
194        // Build BVH boxes and morton codes for open vertices
195        let mut vert_box: Vec<Box> = Vec::with_capacity(num_open);
196        let mut vert_morton: Vec<u32> = Vec::with_capacity(num_open);
197        for &v in &open_verts {
198            let pos = self.get_vert_pos(v);
199            let center = Vec3::new(pos[0] as f64, pos[1] as f64, pos[2] as f64);
200            let half_tol = tolerance / 2.0;
201            let bx = Box::from_points(
202                center - Vec3::new(half_tol, half_tol, half_tol),
203                center + Vec3::new(half_tol, half_tol, half_tol),
204            );
205            vert_box.push(bx);
206            vert_morton.push(morton_code(center, &bbox));
207        }
208
209        // Sort by morton code
210        let mut order: Vec<usize> = (0..num_open).collect();
211        order.sort_by_key(|&i| vert_morton[i]);
212
213        let sorted_box: Vec<Box> = order.iter().map(|&i| vert_box[i]).collect();
214        let sorted_morton: Vec<u32> = order.iter().map(|&i| vert_morton[i]).collect();
215        let sorted_verts: Vec<usize> = order.iter().map(|&i| open_verts[i]).collect();
216
217        // Build collider and find coincident vertex pairs
218        let collider = Collider::new(sorted_box.clone(), sorted_morton);
219        let uf = DisjointSets::new(num_vert as u32);
220
221        collider.collisions_with_boxes(&sorted_box, false, |a, b| {
222            uf.unite(sorted_verts[a] as u32, sorted_verts[b] as u32);
223        });
224
225        // Also merge from existing merge vectors
226        for i in 0..self.merge_from_vert.len() {
227            uf.unite(self.merge_from_vert[i], self.merge_to_vert[i]);
228        }
229
230        // Rebuild merge vectors
231        self.merge_from_vert.clear();
232        self.merge_to_vert.clear();
233        for v in 0..num_vert {
234            let merge_to = uf.find(v as u32) as usize;
235            if merge_to != v {
236                self.merge_from_vert.push(v as u32);
237                self.merge_to_vert.push(merge_to as u32);
238            }
239        }
240
241        true
242    }
243
244    /// True if triangle run `run` is on the backside (e.g. from a subtraction).
245    /// run_flags is a bitmask (#1718): bit 0 = backside. Informational only —
246    /// the framework already orients stored normals on the standard flow.
247    pub fn backside(&self, run: usize) -> bool {
248        run < self.run_flags.len() && (self.run_flags[run] & 1) != 0
249    }
250
251    /// True if the first three extra-property channels (slots 3, 4, 5) of run
252    /// `run` carry world-frame vertex normals (set by `CalculateNormals(0)`,
253    /// round-tripped via run_flags bit 1, #1718). Consumers should treat the
254    /// slot as normals and skip re-applying run_transform to it.
255    pub fn has_normals(&self, run: usize) -> bool {
256        run < self.run_flags.len() && (self.run_flags[run] & 2) != 0
257    }
258
259    /// Applies run transforms to normals stored at `normal_idx` in each vertex's properties,
260    /// then clears run_transform and run_flags. Matches C++ MeshGL::UpdateNormals(normalIdx).
261    ///
262    /// The normal transform is the inverse-transpose of the 3×3 rotation part of the run
263    /// transform. For backside runs (run_flags bit 0 set), normals are additionally negated.
264    pub fn update_normals(&mut self, normal_idx: usize) {
265        if normal_idx < 3 || normal_idx + 3 > self.num_prop as usize {
266            return;
267        }
268        let num_vert = self.num_vert();
269        let num_run = self.run_original_id.len();
270        let np = self.num_prop as usize;
271        let mut vert_updated = vec![false; num_vert];
272
273        for run in 0..num_run {
274            // Build the 3x3 normal transform from the column-major 3x4 run transform
275            let offset = 12 * run;
276            let has_transform = offset + 12 <= self.run_transform.len();
277
278            // Extract mat3 (upper-left 3x3 of the 3x4 transform)
279            let (m00, m01, m02,
280                 m10, m11, m12,
281                 m20, m21, m22) = if has_transform {
282                let t = &self.run_transform[offset..offset + 12];
283                // Column-major: col0=[t0,t1,t2], col1=[t3,t4,t5], col2=[t6,t7,t8]
284                (t[0] as f64, t[3] as f64, t[6] as f64,
285                 t[1] as f64, t[4] as f64, t[7] as f64,
286                 t[2] as f64, t[5] as f64, t[8] as f64)
287            } else {
288                (1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0)
289            };
290
291            // Normal transform = inverse(transpose(M)) = (M^T)^{-1}
292            // For a rotation matrix R: (R^T)^{-1} = R itself.
293            // For a general transform with scale s: det = s^3, inv_trans = M / s^2.
294            // We compute full adjugate/determinant to match C++ la::inverse(la::transpose(M)).
295            let det = m00*(m11*m22 - m12*m21) - m01*(m10*m22 - m12*m20) + m02*(m10*m21 - m11*m20);
296            let (n00, n01, n02, n10, n11, n12, n20, n21, n22) = if det.abs() < 1e-30 {
297                (1.0f64, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0)
298            } else {
299                let inv = 1.0 / det;
300                // Adjugate of transpose(M) = transpose of adjugate(M)
301                let a00 = (m11*m22 - m12*m21) * inv;
302                let a01 = (m02*m21 - m01*m22) * inv;
303                let a02 = (m01*m12 - m02*m11) * inv;
304                let a10 = (m12*m20 - m10*m22) * inv;
305                let a11 = (m00*m22 - m02*m20) * inv;
306                let a12 = (m02*m10 - m00*m12) * inv;
307                let a20 = (m10*m21 - m11*m20) * inv;
308                let a21 = (m01*m20 - m00*m21) * inv;
309                let a22 = (m00*m11 - m01*m10) * inv;
310                (a00, a01, a02, a10, a11, a12, a20, a21, a22)
311            };
312
313            let sign = if self.backside(run) { -1.0f64 } else { 1.0 };
314
315            // Determine run's vertex range
316            let start = if run < self.run_index.len() { self.run_index[run] as usize } else { 0 };
317            let end = if run + 1 < self.run_index.len() { self.run_index[run + 1] as usize } else { self.tri_verts.len() };
318
319            for idx in (start..end).step_by(1) {
320                let vert = self.tri_verts[idx] as usize;
321                if vert >= num_vert || vert_updated[vert] { continue; }
322                vert_updated[vert] = true;
323                let prop_start = vert * np + normal_idx;
324                let nx = self.vert_properties[prop_start] as f64;
325                let ny = self.vert_properties[prop_start + 1] as f64;
326                let nz = self.vert_properties[prop_start + 2] as f64;
327                // Apply normal transform
328                let tx = n00*nx + n01*ny + n02*nz;
329                let ty = n10*nx + n11*ny + n12*nz;
330                let tz = n20*nx + n21*ny + n22*nz;
331                // SafeNormalize
332                let len = (tx*tx + ty*ty + tz*tz).sqrt();
333                let (tx, ty, tz) = if len > 0.0 {
334                    (sign * tx / len, sign * ty / len, sign * tz / len)
335                } else {
336                    (0.0, 0.0, 0.0)
337                };
338                self.vert_properties[prop_start] = tx as f32;
339                self.vert_properties[prop_start + 1] = ty as f32;
340                self.vert_properties[prop_start + 2] = tz as f32;
341            }
342        }
343        self.run_transform.clear();
344        self.run_flags.clear();
345    }
346}
347
348/// Single-precision mesh (standard for graphics).
349pub type MeshGL = MeshGLP<f32, u32>;
350
351/// Double-precision, 64-bit index mesh (for huge meshes).
352pub type MeshGL64 = MeshGLP<f64, u64>;