Skip to main content

oxiphysics_geometry/geodesic_geometry/
types.rs

1//! Auto-generated module
2//!
3//! 🤖 Generated with [SplitRS](https://github.com/cool-japan/splitrs)
4
5use super::functions::*;
6/// Status of a vertex in the FMM algorithm.
7#[derive(Debug, Clone, Copy, PartialEq)]
8pub(super) enum FmmStatus {
9    /// Vertex has not been reached yet.
10    Far,
11    /// Vertex is in the narrow band (tentative distance).
12    Trial,
13    /// Vertex distance is finalized.
14    Known,
15}
16/// Parameters for the heat method.
17#[derive(Debug, Clone)]
18pub struct HeatMethodParams {
19    /// Time step factor: `t = factor * mean_edge_length^2`.
20    pub time_factor: f64,
21}
22/// A geodesic Voronoi cell on a mesh surface.
23#[derive(Debug, Clone)]
24pub struct GeodesicVoronoiCell {
25    /// The source vertex (site) of this cell.
26    pub site: usize,
27    /// Indices of vertices belonging to this cell.
28    pub vertices: Vec<usize>,
29    /// Total area of the cell (sum of Voronoi areas of its vertices).
30    pub area: f64,
31}
32/// Result of a discrete exponential map computation.
33#[derive(Debug, Clone)]
34pub struct ExpMapResult {
35    /// The target vertex index on the mesh (nearest to the mapped point).
36    pub vertex: usize,
37    /// The 3D position of the mapped point on the surface.
38    pub position: [f64; 3],
39    /// The face that contains the mapped point.
40    pub face: usize,
41    /// Barycentric coordinates within the face.
42    pub bary: [f64; 3],
43}
44/// Result of a discrete logarithmic map.
45#[derive(Debug, Clone)]
46pub struct LogMapResult {
47    /// 2D coordinates in the tangent plane at the source vertex.
48    pub coords: [f64; 2],
49    /// Geodesic distance from source to target.
50    pub distance: f64,
51    /// Angle in the tangent plane (radians).
52    pub angle: f64,
53}
54/// A node in the priority queue for distance computations.
55#[derive(Debug, Clone)]
56pub(super) struct DistNode {
57    /// Vertex index.
58    pub(super) vertex: usize,
59    /// Distance from source.
60    pub(super) dist: f64,
61}
62/// Triangle mesh for geodesic geometry computations.
63///
64/// Stores vertex positions and triangle connectivity. All algorithms in this
65/// module operate on this structure.
66#[derive(Debug, Clone)]
67pub struct GeodesicMesh {
68    /// Vertex positions in 3-space.
69    pub vertices: Vec<[f64; 3]>,
70    /// Triangular faces as index triples `[v0, v1, v2]`.
71    pub faces: Vec<[usize; 3]>,
72}
73impl GeodesicMesh {
74    /// Create a new geodesic mesh.
75    pub fn new(vertices: Vec<[f64; 3]>, faces: Vec<[usize; 3]>) -> Self {
76        Self { vertices, faces }
77    }
78    /// Number of vertices.
79    pub fn num_vertices(&self) -> usize {
80        self.vertices.len()
81    }
82    /// Number of faces.
83    pub fn num_faces(&self) -> usize {
84        self.faces.len()
85    }
86    /// Build adjacency: for each vertex, the list of neighbouring vertices.
87    pub fn vertex_adjacency(&self) -> Vec<Vec<usize>> {
88        let n = self.num_vertices();
89        let mut adj: Vec<Vec<usize>> = vec![Vec::new(); n];
90        for f in &self.faces {
91            for i in 0..3 {
92                let a = f[i];
93                let b = f[(i + 1) % 3];
94                if !adj[a].contains(&b) {
95                    adj[a].push(b);
96                }
97                if !adj[b].contains(&a) {
98                    adj[b].push(a);
99                }
100            }
101        }
102        adj
103    }
104    /// Build face adjacency: for each vertex, which faces contain it.
105    pub fn vertex_to_faces(&self) -> Vec<Vec<usize>> {
106        let n = self.num_vertices();
107        let mut v2f: Vec<Vec<usize>> = vec![Vec::new(); n];
108        for (fi, f) in self.faces.iter().enumerate() {
109            for &vi in f {
110                v2f[vi].push(fi);
111            }
112        }
113        v2f
114    }
115    /// Compute the face normal of a triangle (unnormalized).
116    pub fn face_normal(&self, fi: usize) -> [f64; 3] {
117        let f = self.faces[fi];
118        let e1 = sub3(self.vertices[f[1]], self.vertices[f[0]]);
119        let e2 = sub3(self.vertices[f[2]], self.vertices[f[0]]);
120        cross3(e1, e2)
121    }
122    /// Compute area-weighted vertex normal.
123    pub fn vertex_normal(&self, vi: usize) -> [f64; 3] {
124        let v2f = self.vertex_to_faces();
125        let mut n = [0.0; 3];
126        for &fi in &v2f[vi] {
127            let fn_ = self.face_normal(fi);
128            n = add3(n, fn_);
129        }
130        normalize3(n)
131    }
132    /// Compute face area.
133    pub fn face_area(&self, fi: usize) -> f64 {
134        0.5 * len3(self.face_normal(fi))
135    }
136    /// Total surface area.
137    pub fn total_area(&self) -> f64 {
138        (0..self.num_faces()).map(|fi| self.face_area(fi)).sum()
139    }
140    /// Build a local tangent frame at a vertex: returns `(tangent, bitangent, normal)`.
141    pub fn tangent_frame(&self, vi: usize) -> ([f64; 3], [f64; 3], [f64; 3]) {
142        let n = self.vertex_normal(vi);
143        let adj = self.vertex_adjacency();
144        let t = if !adj[vi].is_empty() {
145            let edge = sub3(self.vertices[adj[vi][0]], self.vertices[vi]);
146            let proj = sub3(edge, scale3(n, dot3(edge, n)));
147            normalize3(proj)
148        } else {
149            let candidate = if n[0].abs() < 0.9 {
150                [1.0, 0.0, 0.0]
151            } else {
152                [0.0, 1.0, 0.0]
153            };
154            let proj = sub3(candidate, scale3(n, dot3(candidate, n)));
155            normalize3(proj)
156        };
157        let b = cross3(n, t);
158        (t, b, n)
159    }
160    /// Cotangent weight for edge (i, j). Returns the sum of cot(alpha) + cot(beta)
161    /// where alpha, beta are the angles opposite to edge ij in the two adjacent triangles.
162    pub fn cotangent_weight(&self, i: usize, j: usize) -> f64 {
163        let mut cot_sum = 0.0;
164        for f in &self.faces {
165            let mut found = false;
166            let mut opposite = 0;
167            for k in 0..3 {
168                let a = f[k];
169                let b = f[(k + 1) % 3];
170                let c = f[(k + 2) % 3];
171                if (a == i && b == j) || (a == j && b == i) {
172                    found = true;
173                    opposite = c;
174                    break;
175                }
176            }
177            if found {
178                let pi = self.vertices[i];
179                let pj = self.vertices[j];
180                let po = self.vertices[opposite];
181                let ei = sub3(pi, po);
182                let ej = sub3(pj, po);
183                let cos_a = dot3(ei, ej) / (len3(ei) * len3(ej) + 1e-30);
184                let sin_a = len3(cross3(ei, ej)) / (len3(ei) * len3(ej) + 1e-30);
185                if sin_a.abs() > 1e-15 {
186                    cot_sum += cos_a / sin_a;
187                }
188            }
189        }
190        cot_sum
191    }
192    /// Voronoi area at vertex `vi` (mixed area formulation).
193    pub fn voronoi_area(&self, vi: usize) -> f64 {
194        let v2f = self.vertex_to_faces();
195        let mut area = 0.0;
196        for &fi in &v2f[vi] {
197            let f = self.faces[fi];
198            let local_idx = f.iter().position(|&v| v == vi).expect("element must exist");
199            let a = self.vertices[f[local_idx]];
200            let b = self.vertices[f[(local_idx + 1) % 3]];
201            let c = self.vertices[f[(local_idx + 2) % 3]];
202            let angle_a = angle_at_vertex(b, a, c);
203            let angle_b = angle_at_vertex(a, b, c);
204            let angle_c = angle_at_vertex(a, c, b);
205            if angle_a > std::f64::consts::FRAC_PI_2 {
206                area += self.face_area(fi) * 0.5;
207            } else if angle_b > std::f64::consts::FRAC_PI_2 || angle_c > std::f64::consts::FRAC_PI_2
208            {
209                area += self.face_area(fi) * 0.25;
210            } else {
211                let ab2 = dot3(sub3(b, a), sub3(b, a));
212                let ac2 = dot3(sub3(c, a), sub3(c, a));
213                let cot_b = angle_b.cos() / (angle_b.sin() + 1e-30);
214                let cot_c = angle_c.cos() / (angle_c.sin() + 1e-30);
215                area += 0.125 * (cot_b * ac2 + cot_c * ab2);
216            }
217        }
218        area.max(1e-30)
219    }
220}