Skip to main content

manifold_rust/
smoothing_tangents.rs

1// Smoothing tangent methods — extracted from smoothing.rs
2// Contains: vert_halfedge, sharpen_edges, sharpen_tangent, linearize_flat_tangents,
3//           distribute_tangents, create_tangents_from_normals, create_tangents
4
5use std::collections::BTreeMap;
6
7use crate::impl_mesh::ManifoldImpl;
8use crate::linalg::{cross, dot, normalize, Vec3, Vec4};
9use crate::math;
10use crate::types::{next_halfedge, prev_halfedge, radians, K_PI, K_TWO_PI, Smoothness};
11
12use super::{vec3_from_vec4, safe_normalize, angle_between, circular_tangent, equal_normals, wrap, collect_vertex_cycle};
13
14impl ManifoldImpl {
15    pub fn vert_halfedge(&self) -> Vec<i32> {
16        let mut vert_halfedge = vec![-1; self.num_vert()];
17        for (idx, edge) in self.halfedge.iter().enumerate() {
18            let start = edge.start_vert as usize;
19            if vert_halfedge[start] < 0 {
20                vert_halfedge[start] = idx as i32;
21            }
22        }
23        vert_halfedge
24    }
25
26    pub fn sharpen_edges(&self, min_sharp_angle: f64, min_smoothness: f64) -> Vec<Smoothness> {
27        let mut out = Vec::new();
28        // Clamp to avoid float-noise false positives (matches C++ kMinSharpAngle)
29        let min_radians = radians(min_sharp_angle.max(1e-4));
30        for e in 0..self.halfedge.len() {
31            if !self.halfedge[e].is_forward() {
32                continue;
33            }
34            let pair = self.halfedge[e].paired_halfedge as usize;
35            let d = dot(self.face_normal[e / 3], self.face_normal[pair / 3]).clamp(-1.0, 1.0);
36            let dihedral = math::acos(d);
37            if dihedral > min_radians {
38                out.push(Smoothness { halfedge: e, smoothness: min_smoothness });
39                out.push(Smoothness { halfedge: pair, smoothness: min_smoothness });
40            }
41        }
42        out
43    }
44
45    pub fn sharpen_tangent(&mut self, halfedge: usize, smoothness: f64) {
46        self.halfedge_tangent[halfedge] = Vec4::new(
47            smoothness * self.halfedge_tangent[halfedge].x,
48            smoothness * self.halfedge_tangent[halfedge].y,
49            smoothness * self.halfedge_tangent[halfedge].z,
50            if smoothness == 0.0 { 0.0 } else { self.halfedge_tangent[halfedge].w },
51        );
52    }
53
54    pub fn linearize_flat_tangents(&mut self) {
55        for halfedge in 0..self.halfedge_tangent.len() {
56            if !self.halfedge[halfedge].is_forward() {
57                continue;
58            }
59            let pair = self.halfedge[halfedge].paired_halfedge as usize;
60            let tangent = self.halfedge_tangent[halfedge];
61            let other = self.halfedge_tangent[pair];
62            let flat = [tangent.w == 0.0, other.w == 0.0];
63            if !flat[0] && !flat[1] {
64                continue;
65            }
66            let edge_vec = self.vert_pos[self.halfedge[halfedge].end_vert as usize]
67                - self.vert_pos[self.halfedge[halfedge].start_vert as usize];
68
69            if flat[0] && flat[1] {
70                self.halfedge_tangent[halfedge] = Vec4::new(edge_vec.x / 3.0, edge_vec.y / 3.0, edge_vec.z / 3.0, 1.0);
71                self.halfedge_tangent[pair] = Vec4::new(-edge_vec.x / 3.0, -edge_vec.y / 3.0, -edge_vec.z / 3.0, 1.0);
72            } else if flat[0] {
73                let other_v = vec3_from_vec4(other);
74                let v = (edge_vec + other_v) / 2.0;
75                self.halfedge_tangent[halfedge] = Vec4::new(v.x, v.y, v.z, 1.0);
76            } else {
77                let tan_v = vec3_from_vec4(tangent);
78                let v = (-edge_vec + tan_v) / 2.0;
79                self.halfedge_tangent[pair] = Vec4::new(v.x, v.y, v.z, 1.0);
80            }
81        }
82    }
83
84    pub fn distribute_tangents(&mut self, fixed_halfedges: &[bool]) {
85        for halfedge in 0..fixed_halfedges.len() {
86            // Per #1671: skip non-fixed and inside-quad seeds outright (the old
87            // code re-seeded inside-quad halfedges to their neighbor).
88            if !fixed_halfedges[halfedge] || self.is_marked_inside_quad(halfedge) {
89                continue;
90            }
91
92            let start = halfedge;
93
94            let mut normal = Vec3::new(0.0, 0.0, 0.0);
95            let mut current_angle = Vec::new();
96            let mut desired_angle = Vec::new();
97
98            let approx_normal = self.vert_normal[self.halfedge[start].start_vert as usize];
99            let center = self.vert_pos[self.halfedge[start].start_vert as usize];
100            let mut last_edge_vec =
101                safe_normalize(self.vert_pos[self.halfedge[start].end_vert as usize] - center);
102            let first_tangent = safe_normalize(vec3_from_vec4(self.halfedge_tangent[start]));
103            let mut last_tangent = first_tangent;
104            let mut current = start;
105            let mut guard = 0usize;
106
107            loop {
108                guard += 1;
109                if guard > self.halfedge.len() + 1 {
110                    break;
111                }
112                current = crate::impl_mesh::next_halfedge(self.halfedge[current].paired_halfedge) as usize;
113                if self.is_marked_inside_quad(current) {
114                    if current == start {
115                        break;
116                    }
117                    continue;
118                }
119                let this_edge_vec =
120                    safe_normalize(self.vert_pos[self.halfedge[current].end_vert as usize] - center);
121                let this_tangent = safe_normalize(vec3_from_vec4(self.halfedge_tangent[current]));
122                normal = normal + cross(this_tangent, last_tangent);
123
124                let cumulative = angle_between(this_edge_vec, last_edge_vec)
125                    + desired_angle.last().copied().unwrap_or(0.0);
126                desired_angle.push(cumulative);
127
128                if current == start {
129                    current_angle.push(K_TWO_PI);
130                } else {
131                    let mut angle = angle_between(this_tangent, first_tangent);
132                    if dot(approx_normal, cross(this_tangent, first_tangent)) < 0.0 {
133                        angle = K_TWO_PI - angle;
134                    }
135                    current_angle.push(angle);
136                }
137
138                last_edge_vec = this_edge_vec;
139                last_tangent = this_tangent;
140                if fixed_halfedges[current] {
141                    break;
142                }
143            }
144
145            if current_angle.len() == 1 || dot(normal, normal) == 0.0 {
146                continue;
147            }
148
149            let scale = current_angle.last().copied().unwrap_or(K_TWO_PI)
150                / desired_angle.last().copied().unwrap_or(K_TWO_PI);
151            let mut offset = 0.0;
152            if current == start {
153                for i in 0..current_angle.len() {
154                    offset += wrap(current_angle[i] - scale * desired_angle[i]);
155                }
156                offset /= current_angle.len() as f64;
157            }
158
159            current = start;
160            let axis = safe_normalize(normal);
161            let mut i = 0usize;
162            let mut guard = 0usize;
163            loop {
164                guard += 1;
165                if guard > self.halfedge.len() + 1 {
166                    break;
167                }
168                current = crate::impl_mesh::next_halfedge(self.halfedge[current].paired_halfedge) as usize;
169                // Per #1671: stop before processing a *different* fixed halfedge
170                // (the terminating fixed edge is no longer rotated here).
171                if current != start && fixed_halfedges[current] {
172                    break;
173                }
174                if self.is_marked_inside_quad(current) {
175                    if current == start {
176                        break;
177                    }
178                    continue;
179                }
180                desired_angle[i] *= scale;
181                let last_angle = if i > 0 { desired_angle[i - 1] } else { 0.0 };
182                if desired_angle[i] - last_angle > K_PI {
183                    desired_angle[i] = last_angle + K_PI;
184                } else if i + 1 < desired_angle.len() && scale * desired_angle[i + 1] - desired_angle[i] > K_PI {
185                    desired_angle[i] = scale * desired_angle[i + 1] - K_PI;
186                }
187
188                let angle = current_angle[i] - desired_angle[i] - offset;
189                let tangent = vec3_from_vec4(self.halfedge_tangent[current]);
190                let q = crate::linalg::rotation_quat_axis_angle(axis, angle);
191                let rotated = crate::linalg::qrot(q, tangent);
192                self.halfedge_tangent[current] = Vec4::new(
193                    rotated.x,
194                    rotated.y,
195                    rotated.z,
196                    self.halfedge_tangent[current].w,
197                );
198                i += 1;
199                if fixed_halfedges[current] {
200                    break;
201                }
202            }
203        }
204    }
205
206    pub fn create_tangents_from_normals(&mut self, normal_idx: usize) {
207        if self.is_empty() {
208            return;
209        }
210        // special flags for tangent.w (matches C++ kInsideQuad/kMissingNormal)
211        const K_INSIDE_QUAD: f64 = -1.0;
212        const K_MISSING_NORMAL: f64 = -3.0;
213
214        let num_vert = self.num_vert();
215        let num_halfedge = self.halfedge.len();
216        let mut tangent = vec![Vec4::new(0.0, 0.0, 0.0, 0.0); num_halfedge];
217        let mut fixed_halfedge = vec![false; num_halfedge];
218        let vert_halfedge = self.vert_halfedge();
219
220        for &e in vert_halfedge.iter().take(num_vert) {
221            if e < 0 {
222                continue;
223            }
224            let e = e as usize;
225            let cycle = collect_vertex_cycle(self, e);
226            let mut face_edges = [-1isize, -1isize];
227            let mut start_halfedge: isize = -1;
228            let mut last_normal = Vec3::new(0.0, 0.0, 0.0);
229
230            for idx in 0..cycle.len() {
231                let halfedge = cycle[idx];
232                let next_he = cycle[(idx + 1) % cycle.len()];
233                let here_normal = self.get_normal(halfedge, normal_idx);
234                let next_normal = self.get_normal(next_he, normal_idx);
235                // Per #1671: a halfedge is flat when its normal matches the face
236                // normal within the EqualNormals tolerance (was kPrecision-squared).
237                let here_is_flat = equal_normals(here_normal, self.face_normal[halfedge / 3]);
238                let next_is_flat = equal_normals(next_normal, self.face_normal[next_he / 3]);
239
240                // Start with flag clear
241                tangent[halfedge].w = 1.0;
242
243                if here_is_flat != next_is_flat {
244                    // Record halfedges bordering a single flat face
245                    if face_edges[0] == -1 {
246                        face_edges[0] = halfedge as isize;
247                    } else if face_edges[1] == -1 {
248                        face_edges[1] = halfedge as isize;
249                    } else {
250                        face_edges[0] = -2;
251                    }
252                }
253
254                let here_zero = here_normal == Vec3::new(0.0, 0.0, 0.0);
255                let next_zero = next_normal == Vec3::new(0.0, 0.0, 0.0);
256
257                if here_zero || next_zero {
258                    if !here_zero {
259                        // next missing — record the last good normal
260                        last_normal = here_normal;
261                    } else if !next_zero {
262                        // here missing, next present — record start of missing segment
263                        if start_halfedge < 0 {
264                            start_halfedge = halfedge as isize;
265                        }
266                    } else {
267                        // both missing
268                        if start_halfedge < 0 {
269                            start_halfedge = -2;
270                        }
271                    }
272                    tangent[halfedge] = Vec4::new(last_normal.x, last_normal.y, last_normal.z, K_MISSING_NORMAL);
273                }
274
275                if self.is_inside_quad(halfedge) {
276                    tangent[halfedge] = Vec4::new(last_normal.x, last_normal.y, last_normal.z, K_INSIDE_QUAD);
277                }
278
279                if tangent[halfedge].w < 0.0 {
280                    continue;
281                }
282
283                let different_normals = !equal_normals(next_normal, here_normal);
284                if different_normals {
285                    fixed_halfedge[halfedge] = true;
286                    face_edges[0] = -2; // override flat face logic when multiple normals present
287                }
288
289                tangent[halfedge] = if different_normals {
290                    let edge_vec = self.vert_pos[self.halfedge[halfedge].end_vert as usize]
291                        - self.vert_pos[self.halfedge[halfedge].start_vert as usize];
292                    let dir = cross(here_normal, next_normal);
293                    let signed_dir = if dot(dir, edge_vec) < 0.0 { -dir } else { dir };
294                    circular_tangent(signed_dir, edge_vec)
295                } else {
296                    self.tangent_from_normal(here_normal, halfedge)
297                };
298            }
299
300            // All normals missing: use vertex pseudonormal
301            let last_zero = last_normal == Vec3::new(0.0, 0.0, 0.0);
302            if start_halfedge != -1 && last_zero {
303                let vert = self.halfedge[e].start_vert as usize;
304                let normal = self.vert_normal[vert];
305                for &halfedge in &cycle {
306                    if tangent[halfedge].w != K_INSIDE_QUAD {
307                        tangent[halfedge] = self.tangent_from_normal(normal, halfedge);
308                    }
309                }
310                continue;
311            }
312
313            // Some normals missing: orbit backwards from start_halfedge to fill in
314            if start_halfedge >= 0 {
315                let start = start_halfedge as usize;
316                // prevNormal = GetNormal(NextHalfedge(paired(start)), normalIdx)
317                let paired_start = self.halfedge[start].paired_halfedge as usize;
318                let next_of_paired = next_halfedge(paired_start as i32) as usize;
319                let mut prev_norm = self.get_normal(next_of_paired, normal_idx);
320
321                let mut current = start;
322                loop {
323                    if tangent[current].w == K_MISSING_NORMAL {
324                        let stored = Vec3::new(tangent[current].x, tangent[current].y, tangent[current].z);
325                        let next_norm = if stored == Vec3::new(0.0, 0.0, 0.0) {
326                            last_normal
327                        } else {
328                            stored
329                        };
330
331                        tangent[current] = if equal_normals(prev_norm, next_norm) {
332                            self.tangent_from_normal(prev_norm, current)
333                        } else {
334                            let dir = cross(prev_norm, next_norm);
335                            let edge_vec = self.vert_pos[self.halfedge[current].end_vert as usize]
336                                - self.vert_pos[self.halfedge[current].start_vert as usize];
337                            let signed_dir = if dot(dir, edge_vec) < 0.0 { -dir } else { dir };
338                            circular_tangent(signed_dir, edge_vec)
339                        };
340                    }
341
342                    let current_normal = self.get_normal(current, normal_idx);
343                    if current_normal != Vec3::new(0.0, 0.0, 0.0) {
344                        prev_norm = current_normal;
345                    }
346                    // advance backward: paired(PrevHalfedge(current))
347                    let prev_he = prev_halfedge(current as i32) as usize;
348                    current = self.halfedge[prev_he].paired_halfedge as usize;
349                    if current == start {
350                        break;
351                    }
352                }
353            }
354
355            if face_edges[0] >= 0 && face_edges[1] >= 0 {
356                let f0 = face_edges[0] as usize;
357                let f1 = face_edges[1] as usize;
358                let edge0 = self.vert_pos[self.halfedge[f0].end_vert as usize]
359                    - self.vert_pos[self.halfedge[f0].start_vert as usize];
360                let edge1 = self.vert_pos[self.halfedge[f1].end_vert as usize]
361                    - self.vert_pos[self.halfedge[f1].start_vert as usize];
362                let new_tangent = normalize(edge0) - normalize(edge1);
363                tangent[f0] = circular_tangent(new_tangent, edge0);
364                tangent[f1] = circular_tangent(-new_tangent, edge1);
365                // Fix these tangents to keep them aligned to the edges
366                fixed_halfedge[f0] = true;
367                fixed_halfedge[f1] = true;
368            }
369        }
370
371        self.halfedge_tangent = tangent;
372        self.distribute_tangents(&fixed_halfedge);
373    }
374
375    /// Returns true if halfedge tangents form a valid quad/triangle arrangement.
376    /// Checks that kInsideQuad (-1.0) markers are consistent: paired halfedges
377    /// must agree, and marked halfedges cannot be adjacent within a triangle.
378    pub fn valid_tangents(&self) -> bool {
379        if self.halfedge_tangent.len() != self.halfedge.len() {
380            return true; // no tangents means nothing to validate
381        }
382        let num_halfedge = self.halfedge.len();
383        for edge_idx in 0..num_halfedge {
384            let in_quad = self.is_marked_inside_quad(edge_idx);
385            let pair = self.halfedge[edge_idx].paired_halfedge as usize;
386            if in_quad != self.is_marked_inside_quad(pair) {
387                return false;
388            }
389            if !in_quad {
390                continue;
391            }
392            // A kInsideQuad halfedge cannot have adjacent kInsideQuad halfedges
393            let next_e = next_halfedge(edge_idx as i32) as usize;
394            let prev_e = prev_halfedge(edge_idx as i32) as usize;
395            let pair_next = next_halfedge(pair as i32) as usize;
396            let pair_prev = prev_halfedge(pair as i32) as usize;
397            if self.is_marked_inside_quad(next_e)
398                || self.is_marked_inside_quad(prev_e)
399                || self.is_marked_inside_quad(pair_next)
400                || self.is_marked_inside_quad(pair_prev)
401            {
402                return false;
403            }
404        }
405        true
406    }
407
408    pub fn create_tangents(&mut self, mut sharpened_edges: Vec<Smoothness>) {
409        if self.is_empty() {
410            return;
411        }
412        let num_halfedge = self.halfedge.len();
413        let vert_halfedge = self.vert_halfedge();
414        let tri_is_flat_face = self.flat_faces();
415        let vert_flat_face = self.vert_flat_face(&tri_is_flat_face);
416        let mut vert_normal = self.vert_normal.clone();
417        for v in 0..self.num_vert() {
418            if vert_flat_face[v] >= 0 {
419                vert_normal[v] = self.face_normal[vert_flat_face[v] as usize];
420            }
421        }
422
423        let mut tangent = vec![Vec4::new(0.0, 0.0, 0.0, 0.0); num_halfedge];
424        let mut fixed_halfedge = vec![false; num_halfedge];
425        for (edge_idx, tan) in tangent.iter_mut().enumerate() {
426            *tan = if self.is_inside_quad(edge_idx) {
427                Vec4::new(0.0, 0.0, 0.0, -1.0)
428            } else {
429                self.tangent_from_normal(vert_normal[self.halfedge[edge_idx].start_vert as usize], edge_idx)
430            };
431        }
432        self.halfedge_tangent = tangent;
433
434        for tri in 0..self.num_tri() {
435            if !tri_is_flat_face[tri] {
436                continue;
437            }
438            for j in 0..3 {
439                let tri2 = self.halfedge[3 * tri + j].paired_halfedge as usize / 3;
440                if !tri_is_flat_face[tri2]
441                    || !self.mesh_relation.tri_ref[tri].same_face(&self.mesh_relation.tri_ref[tri2])
442                {
443                    sharpened_edges.push(Smoothness { halfedge: 3 * tri + j, smoothness: 0.0 });
444                }
445            }
446        }
447
448        type Pair = (Smoothness, Smoothness);
449        let mut edges: BTreeMap<usize, Pair> = BTreeMap::new();
450        for edge in sharpened_edges {
451            if edge.smoothness >= 1.0 {
452                continue;
453            }
454            let forward = self.halfedge[edge.halfedge].is_forward();
455            let pair = self.halfedge[edge.halfedge].paired_halfedge as usize;
456            let idx = if forward { edge.halfedge } else { pair };
457            edges.entry(idx)
458                .and_modify(|existing| {
459                    let e = if forward { &mut existing.0 } else { &mut existing.1 };
460                    e.smoothness = e.smoothness.min(edge.smoothness);
461                })
462                .or_insert_with(|| {
463                    let mut pair_entry = (edge, Smoothness { halfedge: pair, smoothness: 1.0 });
464                    if !forward {
465                        pair_entry = (pair_entry.1, pair_entry.0);
466                    }
467                    pair_entry
468                });
469        }
470
471        let mut vert_tangents: BTreeMap<usize, Vec<Pair>> = BTreeMap::new();
472        for edge in edges.values() {
473            vert_tangents
474                .entry(self.halfedge[edge.0.halfedge].start_vert as usize)
475                .or_default()
476                .push(*edge);
477            vert_tangents
478                .entry(self.halfedge[edge.1.halfedge].start_vert as usize)
479                .or_default()
480                .push((edge.1, edge.0));
481        }
482
483        for v in 0..self.num_vert() {
484            let Some(vert) = vert_tangents.get(&v) else {
485                if vert_halfedge[v] >= 0 {
486                    fixed_halfedge[vert_halfedge[v] as usize] = true;
487                }
488                continue;
489            };
490
491            if vert.len() == 1 {
492                continue;
493            }
494            if vert.len() == 2 {
495                let first = vert[0].0.halfedge;
496                let second = vert[1].0.halfedge;
497                fixed_halfedge[first] = true;
498                fixed_halfedge[second] = true;
499                let new_tangent =
500                    normalize(vec3_from_vec4(self.halfedge_tangent[first]) - vec3_from_vec4(self.halfedge_tangent[second]));
501                let pos = self.vert_pos[self.halfedge[first].start_vert as usize];
502                self.halfedge_tangent[first] =
503                    circular_tangent(new_tangent, self.vert_pos[self.halfedge[first].end_vert as usize] - pos);
504                self.halfedge_tangent[second] =
505                    circular_tangent(-new_tangent, self.vert_pos[self.halfedge[second].end_vert as usize] - pos);
506
507                let mut smoothness = (vert[0].1.smoothness + vert[1].0.smoothness) / 2.0;
508                for current in collect_vertex_cycle(self, first) {
509                    if current == second {
510                        smoothness = (vert[1].1.smoothness + vert[0].0.smoothness) / 2.0;
511                    } else if current != first && !self.is_marked_inside_quad(current) {
512                        self.sharpen_tangent(current, smoothness);
513                    }
514                }
515            } else {
516                let mut smoothness = 0.0;
517                let mut denom = 0.0;
518                for pair in vert {
519                    smoothness += pair.0.smoothness + pair.1.smoothness;
520                    denom += if pair.0.smoothness == 0.0 { 0.0 } else { 1.0 };
521                    denom += if pair.1.smoothness == 0.0 { 0.0 } else { 1.0 };
522                }
523                if denom > 0.0 {
524                    smoothness /= denom;
525                }
526
527                for current in collect_vertex_cycle(self, vert[0].0.halfedge) {
528                    if !self.is_marked_inside_quad(current) {
529                        let pair = self.halfedge[current].paired_halfedge as usize;
530                        let s = if tri_is_flat_face[current / 3] || tri_is_flat_face[pair / 3] {
531                            0.0
532                        } else {
533                            smoothness
534                        };
535                        self.sharpen_tangent(current, s);
536                    }
537                }
538            }
539        }
540
541        self.linearize_flat_tangents();
542        self.distribute_tangents(&fixed_halfedge);
543    }
544}