Skip to main content

manifold_rust/
subdivision.rs

1// Copyright 2026 Lars Brubaker
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//      http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15// Phase 15: Subdivision — ported from C++ subdivision.cpp (811 lines)
16//
17// Implements the full subdivision system with:
18// - Partition class with cached triangulations
19// - Triangle and quad subdivision
20// - Barycentric interpolation for vertices and properties
21// - Edge-division-based refinement
22
23#[path = "subdivision_partition.rs"]
24mod subdivision_partition;
25use subdivision_partition::{BaryIndices, Partition, lerp_vec4, next3};
26
27use crate::impl_mesh::ManifoldImpl;
28use crate::linalg::{BVec4, IVec3, IVec4, Mat3, Mat3x4, Vec3, Vec4};
29use crate::types::{next_halfedge, Barycentric, Halfedge, TmpEdge, TriRef};
30
31// ---------------------------------------------------------------------------
32// CreateTmpEdges — port of C++ inline CreateTmpEdges()
33// ---------------------------------------------------------------------------
34
35fn create_tmp_edges(halfedge: &[Halfedge]) -> Vec<TmpEdge> {
36    let mut edges: Vec<TmpEdge> = Vec::with_capacity(halfedge.len());
37    for (idx, half) in halfedge.iter().enumerate() {
38        if half.is_forward() {
39            edges.push(TmpEdge::new(half.start_vert, half.end_vert, idx as i32));
40        }
41    }
42    debug_assert!(
43        edges.len() == halfedge.len() / 2,
44        "Not oriented! edges={} halfedges={}",
45        edges.len(),
46        halfedge.len()
47    );
48    edges
49}
50
51// ---------------------------------------------------------------------------
52// ManifoldImpl methods for subdivision
53// ---------------------------------------------------------------------------
54
55impl ManifoldImpl {
56    /// Port of C++ Manifold::Impl::GetNeighbor(int tri)
57    pub fn get_neighbor(&self, tri: i32) -> i32 {
58        let mut neighbor: i32 = -1;
59        for i in 0..3 {
60            if self.is_marked_inside_quad((3 * tri + i) as usize) {
61                neighbor = if neighbor == -1 { i } else { -2 };
62            }
63        }
64        neighbor
65    }
66
67    /// Port of C++ Manifold::Impl::GetHalfedges(int tri)
68    pub fn get_halfedges_quad(&self, tri: i32) -> IVec4 {
69        let mut halfedges = IVec4::new(-1, -1, -1, -1);
70        for i in 0..3 {
71            halfedges[i] = 3 * tri + i as i32;
72        }
73        let neighbor = self.get_neighbor(tri);
74        if neighbor >= 0 {
75            // quad
76            let pair = self.halfedge[(3 * tri + neighbor) as usize].paired_halfedge;
77            if pair / 3 < tri {
78                return IVec4::new(-1, -1, -1, -1); // only process lower tri index
79            }
80            halfedges[2] = next_halfedge(halfedges[neighbor as usize]);
81            halfedges[3] = next_halfedge(halfedges[2]);
82            halfedges[0] = next_halfedge(pair);
83            halfedges[1] = next_halfedge(halfedges[0]);
84        }
85        halfedges
86    }
87
88    /// Port of C++ Manifold::Impl::GetIndices(int halfedge)
89    fn get_indices(&self, halfedge: i32) -> BaryIndices {
90        let mut tri = halfedge / 3;
91        let mut idx = halfedge % 3;
92        let neighbor = self.get_neighbor(tri);
93        if idx == neighbor {
94            return BaryIndices { tri: -1, start4: -1, end4: -1 };
95        }
96
97        if neighbor < 0 {
98            // tri
99            BaryIndices { tri, start4: idx, end4: next3(idx) }
100        } else {
101            // quad
102            let pair = self.halfedge[(3 * tri + neighbor) as usize].paired_halfedge;
103            if pair / 3 < tri {
104                tri = pair / 3;
105                idx = if next3(neighbor) == idx { 0 } else { 1 };
106            } else {
107                idx = if next3(neighbor) == idx { 2 } else { 3 };
108            }
109            BaryIndices { tri, start4: idx, end4: (idx + 1) % 4 }
110        }
111    }
112
113    /// Port of C++ Manifold::Impl::FillRetainedVerts()
114    fn fill_retained_verts(&self, vert_bary: &mut [Barycentric]) {
115        let num_tri = self.halfedge.len() / 3;
116        for tri in 0..num_tri {
117            for i in 0..3 {
118                let indices = self.get_indices((3 * tri + i) as i32);
119                if indices.start4 < 0 {
120                    continue; // skip quad interiors
121                }
122                let mut uvw = Vec4::splat(0.0);
123                uvw[indices.start4 as usize] = 1.0;
124                vert_bary[self.halfedge[3 * tri + i].start_vert as usize] = Barycentric {
125                    tri: indices.tri,
126                    uvw,
127                };
128            }
129        }
130    }
131
132    /// Port of C++ Manifold::Impl::Subdivide()
133    /// edgeDivisions: takes (edge_vec, tangent0, tangent1) → number of new vertices
134    pub fn subdivide(
135        &mut self,
136        edge_divisions: &dyn Fn(Vec3, Vec4, Vec4) -> i32,
137        keep_interior: bool,
138    ) -> Vec<Barycentric> {
139        let edges = create_tmp_edges(&self.halfedge);
140        let num_vert = self.num_vert();
141        let num_edge = edges.len();
142        let num_tri = self.num_tri();
143
144        // Build half2edge mapping
145        let mut half2edge = vec![0i32; 2 * num_edge];
146        for (edge, tmp) in edges.iter().enumerate() {
147            let idx = tmp.halfedge_idx as usize;
148            half2edge[idx] = edge as i32;
149            half2edge[self.halfedge[idx].paired_halfedge as usize] = edge as i32;
150        }
151
152        // Get face halfedges for each triangle
153        let face_halfedges: Vec<IVec4> = (0..num_tri)
154            .map(|tri| self.get_halfedges_quad(tri as i32))
155            .collect();
156
157        // Compute edge divisions
158        let mut edge_added = vec![0i32; num_edge];
159        for i in 0..num_edge {
160            let edge = &edges[i];
161            let h_idx = edge.halfedge_idx as usize;
162            if self.is_marked_inside_quad(h_idx) {
163                edge_added[i] = 0;
164                continue;
165            }
166            let vec = self.vert_pos[edge.first as usize] - self.vert_pos[edge.second as usize];
167            let tangent0 = if self.halfedge_tangent.is_empty() {
168                Vec4::splat(0.0)
169            } else {
170                self.halfedge_tangent[h_idx]
171            };
172            let tangent1 = if self.halfedge_tangent.is_empty() {
173                Vec4::splat(0.0)
174            } else {
175                self.halfedge_tangent[self.halfedge[h_idx].paired_halfedge as usize]
176            };
177            edge_added[i] = edge_divisions(vec, tangent0, tangent1);
178        }
179
180        // Optional: add extra divisions to short edges for interior thickness
181        if keep_interior {
182            let orig_edge_added = edge_added.clone();
183            for i in 0..num_edge {
184                let edge = &edges[i];
185                let h_idx = edge.halfedge_idx as usize;
186                if self.is_marked_inside_quad(h_idx) {
187                    continue;
188                }
189
190                let this_added = orig_edge_added[i];
191                let added_fn = |mut h: i32| -> i32 {
192                    let mut longest = 0;
193                    let mut total = 0;
194                    for _ in 0..3 {
195                        let added = orig_edge_added[half2edge[h as usize] as usize];
196                        longest = longest.max(added);
197                        total += added;
198                        h = next_halfedge(h);
199                        if self.is_marked_inside_quad(h as usize) {
200                            longest = 0;
201                            total = 1;
202                            break;
203                        }
204                    }
205                    let min_extra = (longest as f64 * 0.2) as i32 + 1;
206                    let extra = 2 * longest + min_extra - total;
207                    if longest == 0 {
208                        return 0;
209                    }
210                    if extra > 0 {
211                        (extra * (longest - this_added)) / longest
212                    } else {
213                        0
214                    }
215                };
216
217                let a1 = added_fn(h_idx as i32);
218                let a2 = added_fn(self.halfedge[h_idx].paired_halfedge);
219                edge_added[i] = orig_edge_added[i] + a1.max(a2);
220            }
221        }
222
223        // Compute edge offsets (exclusive scan)
224        let mut edge_offset = vec![0i32; num_edge];
225        let mut acc = num_vert as i32;
226        for i in 0..num_edge {
227            edge_offset[i] = acc;
228            acc += edge_added[i];
229        }
230
231        // Allocate vert_bary
232        let total_edge_verts = acc - num_vert as i32;
233        let mut vert_bary = vec![
234            Barycentric {
235                tri: 0,
236                uvw: Vec4::splat(0.0)
237            };
238            acc as usize
239        ];
240        self.fill_retained_verts(&mut vert_bary);
241
242        // Fill edge vertex barycentric coords
243        for i in 0..num_edge {
244            let n = edge_added[i];
245            let offset = edge_offset[i];
246            let indices = self.get_indices(edges[i].halfedge_idx);
247            if indices.tri < 0 {
248                continue; // inside quad
249            }
250            let frac = 1.0 / (n as f64 + 1.0);
251            for j in 0..n {
252                let mut uvw = Vec4::splat(0.0);
253                uvw[indices.end4 as usize] = (j + 1) as f64 * frac;
254                uvw[indices.start4 as usize] = 1.0 - uvw[indices.end4 as usize];
255                vert_bary[(offset + j) as usize] = Barycentric { tri: indices.tri, uvw };
256            }
257        }
258
259        // Generate partitions for each triangle
260        let sub_tris: Vec<Partition> = (0..num_tri)
261            .map(|tri| {
262                let halfedges = face_halfedges[tri];
263                let mut divisions = IVec4::default();
264                for i in 0..4 {
265                    if halfedges[i] >= 0 {
266                        divisions[i] = edge_added[half2edge[halfedges[i] as usize] as usize] + 1;
267                    }
268                }
269                Partition::get_partition(divisions)
270            })
271            .collect();
272
273        // Compute triangle offsets (exclusive scan)
274        let mut tri_offset = vec![0i32; num_tri];
275        {
276            let mut acc = 0i32;
277            for tri in 0..num_tri {
278                tri_offset[tri] = acc;
279                acc += sub_tris[tri].tri_vert.len() as i32;
280            }
281        }
282
283        // Compute interior vertex offsets (exclusive scan)
284        let mut interior_offset = vec![0i32; num_tri];
285        {
286            let mut acc = vert_bary.len() as i32;
287            for tri in 0..num_tri {
288                interior_offset[tri] = acc;
289                acc += sub_tris[tri].num_interior();
290            }
291        }
292
293        // Allocate output arrays
294        let total_new_tris = if num_tri > 0 {
295            tri_offset[num_tri - 1] + sub_tris[num_tri - 1].tri_vert.len() as i32
296        } else {
297            0
298        };
299        let total_new_verts = if num_tri > 0 {
300            interior_offset[num_tri - 1] + sub_tris[num_tri - 1].num_interior()
301        } else {
302            vert_bary.len() as i32
303        };
304
305        let mut tri_verts = vec![IVec3::default(); total_new_tris as usize];
306        vert_bary.resize(
307            total_new_verts as usize,
308            Barycentric { tri: 0, uvw: Vec4::splat(0.0) },
309        );
310        let mut tri_ref_out = vec![TriRef::default(); total_new_tris as usize];
311        let mut face_normal_out = vec![Vec3::splat(0.0); total_new_tris as usize];
312
313        // Build new triangles
314        for tri in 0..num_tri {
315            let halfedges = face_halfedges[tri];
316            if halfedges[0] < 0 {
317                continue;
318            }
319
320            let mut tri3 = IVec4::default();
321            let mut edge_offs = IVec4::default();
322            let mut edge_fwd = BVec4::splat(false);
323            for i in 0..4 {
324                if halfedges[i] < 0 {
325                    tri3[i] = -1;
326                    continue;
327                }
328                let he = &self.halfedge[halfedges[i] as usize];
329                tri3[i] = he.start_vert;
330                edge_offs[i] = edge_offset[half2edge[halfedges[i] as usize] as usize];
331                edge_fwd[i] = he.is_forward();
332            }
333
334            let new_tris = sub_tris[tri].reindex(
335                tri3,
336                edge_offs,
337                edge_fwd,
338                interior_offset[tri],
339            );
340
341            let start = tri_offset[tri] as usize;
342            for (j, t) in new_tris.iter().enumerate() {
343                tri_verts[start + j] = *t;
344                tri_ref_out[start + j] = self.mesh_relation.tri_ref[tri];
345                face_normal_out[start + j] = self.face_normal[tri];
346            }
347
348            // Map interior barycentric coordinates
349            let idx = sub_tris[tri].idx;
350            let v_idx = if halfedges[3] >= 0 || idx[1] == next3(idx[0]) {
351                idx
352            } else {
353                IVec4::new(idx[2], idx[0], idx[1], idx[3])
354            };
355            let mut r_idx = IVec4::default();
356            for i in 0..4 {
357                r_idx[v_idx[i] as usize] = i as i32;
358            }
359
360            let sub_bary = &sub_tris[tri].vert_bary;
361            let int_off = sub_tris[tri].interior_offset() as usize;
362            for (j, bary) in sub_bary[int_off..].iter().enumerate() {
363                vert_bary[interior_offset[tri] as usize + j] = Barycentric {
364                    tri: tri as i32,
365                    uvw: Vec4::new(
366                        bary[r_idx[0] as usize],
367                        bary[r_idx[1] as usize],
368                        bary[r_idx[2] as usize],
369                        bary[r_idx[3] as usize],
370                    ),
371                };
372            }
373        }
374
375        self.mesh_relation.tri_ref = tri_ref_out;
376        self.face_normal = face_normal_out;
377
378        // Compute new vertex positions
379        let mut new_vert_pos = vec![Vec3::splat(0.0); vert_bary.len()];
380        for (vert, bary) in vert_bary.iter().enumerate() {
381            let halfedges = face_halfedges[bary.tri as usize];
382            if halfedges[3] < 0 {
383                // triangle
384                let tri_pos = Mat3::from_cols(
385                    self.vert_pos[self.halfedge[halfedges[0] as usize].start_vert as usize],
386                    self.vert_pos[self.halfedge[halfedges[1] as usize].start_vert as usize],
387                    self.vert_pos[self.halfedge[halfedges[2] as usize].start_vert as usize],
388                );
389                new_vert_pos[vert] = tri_pos * bary.uvw.xyz();
390            } else {
391                // quad
392                let quad_pos = Mat3x4::from_cols(
393                    self.vert_pos[self.halfedge[halfedges[0] as usize].start_vert as usize],
394                    self.vert_pos[self.halfedge[halfedges[1] as usize].start_vert as usize],
395                    self.vert_pos[self.halfedge[halfedges[2] as usize].start_vert as usize],
396                    self.vert_pos[self.halfedge[halfedges[3] as usize].start_vert as usize],
397                );
398                new_vert_pos[vert] = quad_pos * bary.uvw;
399            }
400        }
401        self.vert_pos = new_vert_pos;
402
403        // Handle properties
404        if self.num_prop > 0 {
405            let num_prop_vert = self.num_prop_vert();
406            let added_verts = self.num_vert() - num_vert;
407            let prop_offset = num_prop_vert as i32 - num_vert as i32;
408            let num_prop = self.num_prop as usize;
409
410            // Allocate new property array
411            let mut prop =
412                vec![0.0f64; num_prop * (num_prop_vert + added_verts + total_edge_verts as usize)];
413
414            // Copy retained prop verts
415            for (i, &v) in self.properties.iter().enumerate() {
416                prop[i] = v;
417            }
418
419            // Copy interior prop verts and forward edge prop verts
420            for i in 0..added_verts {
421                let vert = num_prop_vert + i;
422                let bary = &vert_bary[num_vert + i];
423                let halfedges = face_halfedges[bary.tri as usize];
424
425                for p in 0..num_prop {
426                    if halfedges[3] < 0 {
427                        // triangle
428                        let mut tri_prop = Vec3::splat(0.0);
429                        for k in 0..3 {
430                            tri_prop[k] = self.properties
431                                [self.halfedge[3 * bary.tri as usize + k].prop_vert as usize
432                                    * num_prop
433                                    + p];
434                        }
435                        prop[vert * num_prop + p] =
436                            tri_prop.x * bary.uvw.x + tri_prop.y * bary.uvw.y + tri_prop.z * bary.uvw.z;
437                    } else {
438                        // quad
439                        let mut quad_prop = Vec4::splat(0.0);
440                        for k in 0..4 {
441                            quad_prop[k] = self.properties
442                                [self.halfedge[halfedges[k] as usize].prop_vert as usize
443                                    * num_prop
444                                    + p];
445                        }
446                        prop[vert * num_prop + p] = quad_prop.x * bary.uvw.x
447                            + quad_prop.y * bary.uvw.y
448                            + quad_prop.z * bary.uvw.z
449                            + quad_prop.w * bary.uvw.w;
450                    }
451                }
452            }
453
454            // Copy backward edge prop verts
455            for i in 0..num_edge {
456                let n = edge_added[i];
457                let offset = edge_offset[i] as usize + prop_offset as usize + added_verts;
458                let frac = 1.0 / (n as f64 + 1.0);
459                let halfedge_idx =
460                    self.halfedge[edges[i].halfedge_idx as usize].paired_halfedge as usize;
461                let prop0 = self.halfedge[halfedge_idx].prop_vert as usize;
462                let prop1 =
463                    self.halfedge[next_halfedge(halfedge_idx as i32) as usize].prop_vert as usize;
464                for j in 0..n as usize {
465                    for p in 0..num_prop {
466                        let t = (j + 1) as f64 * frac;
467                        prop[(offset + j) * num_prop + p] = self.properties[prop0 * num_prop + p]
468                            + (self.properties[prop1 * num_prop + p]
469                                - self.properties[prop0 * num_prop + p])
470                                * t;
471                    }
472                }
473            }
474
475            // Build property triangles
476            let mut tri_prop_out = vec![IVec3::default(); total_new_tris as usize];
477            for tri in 0..num_tri {
478                let halfedges = face_halfedges[tri];
479                if halfedges[0] < 0 {
480                    continue;
481                }
482
483                let mut tri3 = IVec4::default();
484                let mut edge_offs = IVec4::default();
485                let mut edge_fwd = BVec4::splat(true);
486                for i in 0..4 {
487                    if halfedges[i] < 0 {
488                        tri3[i] = -1;
489                        continue;
490                    }
491                    let he = &self.halfedge[halfedges[i] as usize];
492                    tri3[i] = he.prop_vert;
493                    edge_offs[i] = edge_offset[half2edge[halfedges[i] as usize] as usize];
494                    if !he.is_forward() {
495                        let paired = he.paired_halfedge;
496                        if self.halfedge[paired as usize].prop_vert
497                            != self.halfedge[next_halfedge(halfedges[i]) as usize].prop_vert
498                            || self.halfedge[next_halfedge(paired) as usize].prop_vert
499                                != he.prop_vert
500                        {
501                            // edge doesn't match, point to backward edge propverts
502                            edge_offs[i] += added_verts as i32;
503                        } else {
504                            edge_fwd[i] = false;
505                        }
506                    }
507                }
508
509                // Add prop_offset to edge offsets
510                let prop_edge_offs = IVec4::new(
511                    edge_offs[0] + prop_offset,
512                    edge_offs[1] + prop_offset,
513                    edge_offs[2] + prop_offset,
514                    edge_offs[3] + prop_offset,
515                );
516
517                let new_tris = sub_tris[tri].reindex(
518                    tri3,
519                    prop_edge_offs,
520                    edge_fwd,
521                    interior_offset[tri] + prop_offset,
522                );
523
524                let start = tri_offset[tri] as usize;
525                for (j, t) in new_tris.iter().enumerate() {
526                    tri_prop_out[start + j] = *t;
527                }
528            }
529
530            self.properties = prop;
531            self.create_halfedges(&tri_prop_out, &tri_verts);
532        } else {
533            self.create_halfedges(&tri_verts, &[]);
534        }
535
536        vert_bary
537    }
538}
539
540/// Simple midpoint subdivision: each triangle is split into 4 by inserting
541/// edge midpoints. This is a convenience wrapper that uses uniform n=2 divisions.
542pub fn subdivide_impl(mesh: &ManifoldImpl, levels: usize) -> ManifoldImpl {
543    if levels == 0 || mesh.is_empty() {
544        return mesh.clone();
545    }
546
547    let mut current = mesh.clone();
548    for _ in 0..levels {
549        current.subdivide(&|_vec, _t0, _t1| 1, false);
550        current.calculate_bbox();
551        current.set_epsilon(-1.0, false);
552    }
553
554    current
555}
556
557#[cfg(test)]
558#[path = "subdivision_tests.rs"]
559mod tests;