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