Skip to main content

polyscope_structures/surface_mesh/
geometry.rs

1//! Geometry computation methods for surface meshes.
2//!
3//! This module contains methods for computing derived mesh data from raw vertices and faces:
4//! - Triangulation (fan triangulation for arbitrary polygons)
5//! - Face and vertex normals
6//! - Corner normals for shading
7//! - Edge extraction and classification for wireframe rendering
8//! - Tangent basis computation for intrinsic vectors
9
10use glam::Vec3;
11use std::collections::HashSet;
12
13use super::{ShadeStyle, SurfaceMesh};
14
15impl SurfaceMesh {
16    // === Computation methods ===
17
18    /// Recomputes all derived data (triangulation, normals, edges).
19    pub(super) fn recompute(&mut self) {
20        if !self.needs_recompute {
21            return;
22        }
23
24        self.compute_triangulation();
25        self.compute_face_normals();
26        self.compute_vertex_normals();
27        self.compute_corner_normals();
28        self.compute_edges();
29        self.compute_edge_is_real();
30
31        self.needs_recompute = false;
32    }
33
34    /// Computes triangulation using fan triangulation.
35    ///
36    /// For a polygon with vertices [v0, v1, v2, v3, ...], creates triangles:
37    /// [v0, v1, v2], [v0, v2, v3], [v0, v3, v4], ...
38    fn compute_triangulation(&mut self) {
39        self.triangulation.clear();
40        self.face_to_tri_range.clear();
41
42        for face in &self.faces {
43            let start_tri = self.triangulation.len();
44
45            if face.len() >= 3 {
46                let v0 = face[0];
47                // Fan triangulation: create (n-2) triangles for n-gon
48                for i in 1..(face.len() - 1) {
49                    self.triangulation.push([v0, face[i], face[i + 1]]);
50                }
51            }
52
53            let end_tri = self.triangulation.len();
54            self.face_to_tri_range.push(start_tri..end_tri);
55        }
56    }
57
58    /// Computes face normals using cross product of first two edges.
59    fn compute_face_normals(&mut self) {
60        self.face_normals.clear();
61        self.face_normals.reserve(self.faces.len());
62
63        for face in &self.faces {
64            if face.len() >= 3 {
65                let v0 = self.vertices[face[0] as usize];
66                let v1 = self.vertices[face[1] as usize];
67                let v2 = self.vertices[face[2] as usize];
68
69                let e1 = v1 - v0;
70                let e2 = v2 - v0;
71                let normal = e1.cross(e2).normalize_or_zero();
72                self.face_normals.push(normal);
73            } else {
74                self.face_normals.push(Vec3::ZERO);
75            }
76        }
77    }
78
79    /// Computes vertex normals as area-weighted average of incident face normals.
80    fn compute_vertex_normals(&mut self) {
81        self.vertex_normals.clear();
82        self.vertex_normals.resize(self.vertices.len(), Vec3::ZERO);
83
84        for (face_idx, face) in self.faces.iter().enumerate() {
85            if face.len() < 3 {
86                continue;
87            }
88
89            let face_normal = self.face_normals[face_idx];
90
91            // Compute face area using triangulation
92            let v0 = self.vertices[face[0] as usize];
93            let mut area = 0.0;
94            for i in 1..(face.len() - 1) {
95                let v1 = self.vertices[face[i] as usize];
96                let v2 = self.vertices[face[i + 1] as usize];
97                let e1 = v1 - v0;
98                let e2 = v2 - v0;
99                area += e1.cross(e2).length() * 0.5;
100            }
101
102            // Add weighted normal to each vertex of this face
103            let weighted_normal = face_normal * area;
104            for &vi in face {
105                self.vertex_normals[vi as usize] += weighted_normal;
106            }
107        }
108
109        // Normalize all vertex normals
110        for normal in &mut self.vertex_normals {
111            *normal = normal.normalize_or_zero();
112        }
113    }
114
115    /// Computes corner normals (per-corner of each triangle).
116    pub(super) fn compute_corner_normals(&mut self) {
117        self.corner_normals.clear();
118        self.corner_normals.reserve(self.triangulation.len() * 3);
119
120        for (face_idx, range) in self.face_to_tri_range.iter().enumerate() {
121            let face_normal = self.face_normals[face_idx];
122
123            for tri_idx in range.clone() {
124                let tri = self.triangulation[tri_idx];
125                for vi in tri {
126                    // For tri-flat, we use face normals; for smooth, we use vertex normals
127                    // Store both options - the shader will choose based on shade_style
128                    match self.shade_style {
129                        ShadeStyle::Smooth => {
130                            self.corner_normals.push(self.vertex_normals[vi as usize]);
131                        }
132                        ShadeStyle::Flat | ShadeStyle::TriFlat => {
133                            self.corner_normals.push(face_normal);
134                        }
135                    }
136                }
137            }
138        }
139    }
140
141    /// Computes `edge_is_real` flags for wireframe rendering.
142    /// Marks which edges in the triangulation are real polygon edges vs internal.
143    fn compute_edge_is_real(&mut self) {
144        self.edge_is_real.clear();
145        self.edge_is_real.reserve(self.triangulation.len() * 3);
146
147        for range in &self.face_to_tri_range {
148            let num_tris = range.end - range.start;
149
150            for (j, _tri_idx) in range.clone().enumerate() {
151                // For fan triangulation from v0:
152                // Triangle j has vertices [v0, v_{j+1}, v_{j+2}]
153                // Edge 0 (v0 -> v_{j+1}): real only if j == 0
154                // Edge 1 (v_{j+1} -> v_{j+2}): always real (it's a polygon edge)
155                // Edge 2 (v_{j+2} -> v0): real only if j == num_tris - 1
156
157                let edge0_real = if j == 0 { 1.0 } else { 0.0 };
158                let edge1_real = 1.0; // middle edge always real
159                let edge2_real = if j == num_tris - 1 { 1.0 } else { 0.0 };
160
161                // Each triangle corner gets the edge_is_real for all three edges
162                // This matches C++ Polyscope's approach
163                let edge_real = Vec3::new(edge0_real, edge1_real, edge2_real);
164                self.edge_is_real.push(edge_real);
165                self.edge_is_real.push(edge_real);
166                self.edge_is_real.push(edge_real);
167            }
168        }
169    }
170
171    /// Computes unique edges as sorted pairs.
172    fn compute_edges(&mut self) {
173        let mut edge_set: HashSet<(u32, u32)> = HashSet::new();
174
175        for face in &self.faces {
176            let n = face.len();
177            for i in 0..n {
178                let v0 = face[i];
179                let v1 = face[(i + 1) % n];
180                // Store as sorted pair to avoid duplicates
181                let edge = if v0 < v1 { (v0, v1) } else { (v1, v0) };
182                edge_set.insert(edge);
183            }
184        }
185
186        self.edges = edge_set.into_iter().collect();
187        self.edges.sort_unstable(); // Sort for deterministic ordering
188    }
189
190    /// Compute default per-face tangent basis from first edge direction.
191    #[must_use]
192    pub fn compute_face_tangent_basis(&self) -> (Vec<Vec3>, Vec<Vec3>) {
193        let mut basis_x = Vec::with_capacity(self.faces.len());
194        let mut basis_y = Vec::with_capacity(self.faces.len());
195
196        for (face_idx, face) in self.faces.iter().enumerate() {
197            if face.len() >= 3 {
198                let v0 = self.vertices[face[0] as usize];
199                let v1 = self.vertices[face[1] as usize];
200                let normal = self.face_normals[face_idx];
201
202                let bx = (v1 - v0).normalize_or_zero();
203                let by = normal.cross(bx).normalize_or_zero();
204                basis_x.push(bx);
205                basis_y.push(by);
206            } else {
207                basis_x.push(Vec3::X);
208                basis_y.push(Vec3::Y);
209            }
210        }
211
212        (basis_x, basis_y)
213    }
214
215    /// Compute default per-vertex tangent basis from area-weighted face bases.
216    #[must_use]
217    pub fn compute_vertex_tangent_basis(&self) -> (Vec<Vec3>, Vec<Vec3>) {
218        let (face_bx, _face_by) = self.compute_face_tangent_basis();
219
220        let mut vert_bx = vec![Vec3::ZERO; self.vertices.len()];
221
222        for (face_idx, face) in self.faces.iter().enumerate() {
223            if face.len() < 3 {
224                continue;
225            }
226
227            // Compute face area
228            let v0 = self.vertices[face[0] as usize];
229            let mut area = 0.0f32;
230            for i in 1..(face.len() - 1) {
231                let v1 = self.vertices[face[i] as usize];
232                let v2 = self.vertices[face[i + 1] as usize];
233                area += (v1 - v0).cross(v2 - v0).length() * 0.5;
234            }
235
236            let weighted_bx = face_bx[face_idx] * area;
237            for &vi in face {
238                vert_bx[vi as usize] += weighted_bx;
239            }
240        }
241
242        // Orthonormalize against vertex normals
243        let mut basis_x = Vec::with_capacity(self.vertices.len());
244        let mut basis_y = Vec::with_capacity(self.vertices.len());
245
246        for (i, normal) in self.vertex_normals.iter().enumerate() {
247            let mut bx = vert_bx[i];
248            // Project out normal component and normalize
249            bx -= *normal * normal.dot(bx);
250            bx = bx.normalize_or_zero();
251
252            // If degenerate, pick an arbitrary tangent
253            if bx.length_squared() < 1e-6 {
254                bx = if normal.x.abs() < 0.9 {
255                    Vec3::X
256                } else {
257                    Vec3::Y
258                };
259                bx -= *normal * normal.dot(bx);
260                bx = bx.normalize_or_zero();
261            }
262
263            let by = normal.cross(bx).normalize_or_zero();
264            basis_x.push(bx);
265            basis_y.push(by);
266        }
267
268        (basis_x, basis_y)
269    }
270}