1use std::collections::{BinaryHeap, HashMap};
6
7use super::types::{
8 DijkEntry, GeoMesh, GeoVoronoiCell, HeatGeodesicResult, IntrinsicDelaunay, IsolineSegment,
9};
10
11#[inline]
12pub(super) fn sub3(a: [f64; 3], b: [f64; 3]) -> [f64; 3] {
13 [a[0] - b[0], a[1] - b[1], a[2] - b[2]]
14}
15#[inline]
16pub(super) fn add3(a: [f64; 3], b: [f64; 3]) -> [f64; 3] {
17 [a[0] + b[0], a[1] + b[1], a[2] + b[2]]
18}
19#[inline]
20pub(super) fn scale3(a: [f64; 3], s: f64) -> [f64; 3] {
21 [a[0] * s, a[1] * s, a[2] * s]
22}
23#[inline]
24pub(super) fn dot3(a: [f64; 3], b: [f64; 3]) -> f64 {
25 a[0] * b[0] + a[1] * b[1] + a[2] * b[2]
26}
27#[inline]
28pub(super) fn cross3(a: [f64; 3], b: [f64; 3]) -> [f64; 3] {
29 [
30 a[1] * b[2] - a[2] * b[1],
31 a[2] * b[0] - a[0] * b[2],
32 a[0] * b[1] - a[1] * b[0],
33 ]
34}
35#[inline]
36pub(super) fn len3(a: [f64; 3]) -> f64 {
37 dot3(a, a).sqrt()
38}
39#[inline]
40pub(super) fn dist3(a: [f64; 3], b: [f64; 3]) -> f64 {
41 len3(sub3(a, b))
42}
43#[inline]
44pub(super) fn normalize3(a: [f64; 3]) -> [f64; 3] {
45 let l = len3(a);
46 if l < f64::EPSILON {
47 [0.0, 0.0, 0.0]
48 } else {
49 [a[0] / l, a[1] / l, a[2] / l]
50 }
51}
52pub fn dijkstra_geodesic(mesh: &GeoMesh, source: usize) -> Vec<f64> {
57 let n = mesh.n_vertices();
58 let mut dist = vec![f64::INFINITY; n];
59 dist[source] = 0.0;
60 let adj = mesh.build_adjacency();
61 let mut heap: BinaryHeap<DijkEntry> = BinaryHeap::new();
62 heap.push(DijkEntry {
63 dist: 0.0,
64 vertex: source,
65 });
66 while let Some(DijkEntry { dist: d, vertex: u }) = heap.pop() {
67 if d > dist[u] {
68 continue;
69 }
70 for &(v, w) in &adj[u] {
71 let alt = dist[u] + w;
72 if alt < dist[v] {
73 dist[v] = alt;
74 heap.push(DijkEntry {
75 dist: alt,
76 vertex: v,
77 });
78 }
79 }
80 }
81 dist
82}
83pub fn dijkstra_geodesic_multi_source(mesh: &GeoMesh, sources: &[usize]) -> Vec<f64> {
87 let n = mesh.n_vertices();
88 let mut dist = vec![f64::INFINITY; n];
89 let adj = mesh.build_adjacency();
90 let mut heap: BinaryHeap<DijkEntry> = BinaryHeap::new();
91 for &s in sources {
92 dist[s] = 0.0;
93 heap.push(DijkEntry {
94 dist: 0.0,
95 vertex: s,
96 });
97 }
98 while let Some(DijkEntry { dist: d, vertex: u }) = heap.pop() {
99 if d > dist[u] {
100 continue;
101 }
102 for &(v, w) in &adj[u] {
103 let alt = dist[u] + w;
104 if alt < dist[v] {
105 dist[v] = alt;
106 heap.push(DijkEntry {
107 dist: alt,
108 vertex: v,
109 });
110 }
111 }
112 }
113 dist
114}
115pub fn dijkstra_with_predecessors(mesh: &GeoMesh, source: usize) -> (Vec<f64>, Vec<usize>) {
121 let n = mesh.n_vertices();
122 let mut dist = vec![f64::INFINITY; n];
123 let mut pred = vec![usize::MAX; n];
124 dist[source] = 0.0;
125 let adj = mesh.build_adjacency();
126 let mut heap: BinaryHeap<DijkEntry> = BinaryHeap::new();
127 heap.push(DijkEntry {
128 dist: 0.0,
129 vertex: source,
130 });
131 while let Some(DijkEntry { dist: d, vertex: u }) = heap.pop() {
132 if d > dist[u] {
133 continue;
134 }
135 for &(v, w) in &adj[u] {
136 let alt = dist[u] + w;
137 if alt < dist[v] {
138 dist[v] = alt;
139 pred[v] = u;
140 heap.push(DijkEntry {
141 dist: alt,
142 vertex: v,
143 });
144 }
145 }
146 }
147 (dist, pred)
148}
149pub fn extract_geodesic_path(source: usize, target: usize, pred: &[usize]) -> Option<Vec<usize>> {
154 if pred[target] == usize::MAX && target != source {
155 return None;
156 }
157 let mut path = Vec::new();
158 let mut cur = target;
159 let max_steps = pred.len() + 1;
160 let mut steps = 0;
161 while cur != source {
162 path.push(cur);
163 let p = pred[cur];
164 if p == usize::MAX {
165 return None;
166 }
167 cur = p;
168 steps += 1;
169 if steps > max_steps {
170 return None;
171 }
172 }
173 path.push(source);
174 path.reverse();
175 Some(path)
176}
177pub fn path_to_polyline(mesh: &GeoMesh, path: &[usize]) -> Vec<[f64; 3]> {
179 path.iter().map(|&v| mesh.vertices[v]).collect()
180}
181pub fn polyline_length(polyline: &[[f64; 3]]) -> f64 {
183 polyline.windows(2).map(|w| dist3(w[0], w[1])).sum()
184}
185pub fn heat_geodesic(
206 mesh: &GeoMesh,
207 source: usize,
208 t: f64,
209 n_diffusion_steps: usize,
210) -> HeatGeodesicResult {
211 let n = mesh.n_vertices();
212 if n == 0 {
213 return HeatGeodesicResult {
214 distances: Vec::new(),
215 gradient: Vec::new(),
216 };
217 }
218 let mut heat = vec![0.0_f64; n];
219 heat[source] = 1.0;
220 let adj = mesh.build_adjacency();
221 let mean_edge_len_sq = {
222 let mut total = 0.0_f64;
223 let mut count = 0usize;
224 for face in &mesh.faces {
225 for k in 0..3 {
226 let a = face[k];
227 let b = face[(k + 1) % 3];
228 let d = dist3(mesh.vertices[a], mesh.vertices[b]);
229 total += d * d;
230 count += 1;
231 }
232 }
233 if count == 0 {
234 1.0
235 } else {
236 total / count as f64
237 }
238 };
239 let eff_t = if t <= 0.0 { mean_edge_len_sq } else { t };
240 let dt = eff_t / n_diffusion_steps.max(1) as f64;
241 for _ in 0..n_diffusion_steps {
242 let old = heat.clone();
243 for v in 0..n {
244 if adj[v].is_empty() {
245 continue;
246 }
247 let laplacian: f64 =
248 adj[v].iter().map(|&(u, _)| old[u] - old[v]).sum::<f64>() / adj[v].len() as f64;
249 heat[v] = old[v] + dt * laplacian;
250 }
251 }
252 let nf = mesh.faces.len();
253 let mut gradient = vec![[0.0_f64; 3]; nf];
254 for (fi, face) in mesh.faces.iter().enumerate() {
255 let [i0, i1, i2] = *face;
256 let p0 = mesh.vertices[i0];
257 let p1 = mesh.vertices[i1];
258 let p2 = mesh.vertices[i2];
259 let u0 = heat[i0];
260 let u1 = heat[i1];
261 let u2 = heat[i2];
262 let ab = sub3(p1, p0);
263 let ac = sub3(p2, p0);
264 let normal = cross3(ab, ac);
265 let area2 = len3(normal);
266 if area2 < 1e-15 {
267 continue;
268 }
269 let area = area2 * 0.5;
270 let grad = [
271 (u0 * (p2[0] - p1[0]) + u1 * (p0[0] - p2[0]) + u2 * (p1[0] - p0[0])) / (2.0 * area),
272 (u0 * (p2[1] - p1[1]) + u1 * (p0[1] - p2[1]) + u2 * (p1[1] - p0[1])) / (2.0 * area),
273 (u0 * (p2[2] - p1[2]) + u1 * (p0[2] - p2[2]) + u2 * (p1[2] - p0[2])) / (2.0 * area),
274 ];
275 gradient[fi] = grad;
276 }
277 let mut normalised_grad = vec![[0.0_f64; 3]; nf];
278 for fi in 0..nf {
279 let g = gradient[fi];
280 let l = len3(g);
281 if l > 1e-15 {
282 normalised_grad[fi] = scale3(g, -1.0 / l);
283 }
284 }
285 let mut div = vec![0.0_f64; n];
286 for (fi, face) in mesh.faces.iter().enumerate() {
287 let [i0, i1, i2] = *face;
288 let p0 = mesh.vertices[i0];
289 let p1 = mesh.vertices[i1];
290 let p2 = mesh.vertices[i2];
291 let x = normalised_grad[fi];
292 let edges = [
293 (i0, i1, i2, p0, p1, p2),
294 (i1, i2, i0, p1, p2, p0),
295 (i2, i0, i1, p2, p0, p1),
296 ];
297 for (vi, vj, _vk, pi, pj, pk) in edges {
298 let eki = sub3(pi, pk);
299 let ekj = sub3(pj, pk);
300 let cos_k = dot3(eki, ekj);
301 let sin_k = len3(cross3(eki, ekj));
302 let cot_k = if sin_k > 1e-15 { cos_k / sin_k } else { 0.0 };
303 let eij = sub3(pj, pi);
304 div[vi] += cot_k * dot3(x, eij);
305 let _ = vj;
306 }
307 }
308 let mut phi = vec![0.0_f64; n];
309 let mut visited = vec![false; n];
310 let mut queue = std::collections::VecDeque::new();
311 queue.push_back(source);
312 visited[source] = true;
313 phi[source] = 0.0;
314 while let Some(u) = queue.pop_front() {
315 for &(v, w) in &adj[u] {
316 if !visited[v] {
317 phi[v] = phi[u] + w.abs();
318 visited[v] = true;
319 queue.push_back(v);
320 }
321 }
322 }
323 let min_phi = phi.iter().cloned().fold(f64::INFINITY, f64::min);
324 let min_phi = if min_phi.is_finite() { min_phi } else { 0.0 };
325 let distances: Vec<f64> = phi.iter().map(|&p| (p - min_phi).max(0.0)).collect();
326 HeatGeodesicResult {
327 distances,
328 gradient: normalised_grad,
329 }
330}
331pub fn extract_isolines(mesh: &GeoMesh, distances: &[f64], level: f64) -> Vec<IsolineSegment> {
336 let mut segments = Vec::new();
337 for face in &mesh.faces {
338 let [i0, i1, i2] = *face;
339 let p0 = mesh.vertices[i0];
340 let p1 = mesh.vertices[i1];
341 let p2 = mesh.vertices[i2];
342 let d0 = distances[i0] - level;
343 let d1 = distances[i1] - level;
344 let d2 = distances[i2] - level;
345 let edges = [(d0, d1, p0, p1), (d1, d2, p1, p2), (d2, d0, p2, p0)];
346 let mut crossings = Vec::new();
347 for (da, db, pa, pb) in edges {
348 if (da < 0.0 && db >= 0.0) || (da >= 0.0 && db < 0.0) {
349 let t = da / (da - db);
350 let pt = [
351 pa[0] + t * (pb[0] - pa[0]),
352 pa[1] + t * (pb[1] - pa[1]),
353 pa[2] + t * (pb[2] - pa[2]),
354 ];
355 crossings.push(pt);
356 }
357 }
358 if crossings.len() == 2 {
359 segments.push(IsolineSegment {
360 start: crossings[0],
361 end: crossings[1],
362 });
363 }
364 }
365 segments
366}
367pub fn geodesic_voronoi_regions(mesh: &GeoMesh, sources: &[usize]) -> Vec<usize> {
375 let n = mesh.n_vertices();
376 let mut dist = vec![f64::INFINITY; n];
377 let mut region = vec![0usize; n];
378 let adj = mesh.build_adjacency();
379 let mut heap: BinaryHeap<DijkEntry> = BinaryHeap::new();
380 for (si, &s) in sources.iter().enumerate() {
381 if s < n {
382 dist[s] = 0.0;
383 region[s] = si;
384 heap.push(DijkEntry {
385 dist: 0.0,
386 vertex: s,
387 });
388 }
389 }
390 let mut region_heap: HashMap<usize, usize> = HashMap::new();
391 for (si, &s) in sources.iter().enumerate() {
392 if s < n {
393 region_heap.insert(s, si);
394 }
395 }
396 while let Some(DijkEntry { dist: d, vertex: u }) = heap.pop() {
397 if d > dist[u] {
398 continue;
399 }
400 let r = region[u];
401 for &(v, w) in &adj[u] {
402 let alt = dist[u] + w;
403 if alt < dist[v] {
404 dist[v] = alt;
405 region[v] = r;
406 heap.push(DijkEntry {
407 dist: alt,
408 vertex: v,
409 });
410 }
411 }
412 }
413 region
414}
415pub fn mean_geodesic_distance(mesh: &GeoMesh) -> Vec<f64> {
420 let n = mesh.n_vertices();
421 (0..n)
422 .map(|v| {
423 let d = dijkstra_geodesic(mesh, v);
424 let finite: Vec<f64> = d.iter().cloned().filter(|x| x.is_finite()).collect();
425 if finite.is_empty() {
426 0.0
427 } else {
428 finite.iter().sum::<f64>() / finite.len() as f64
429 }
430 })
431 .collect()
432}
433pub fn geodesic_diameter(mesh: &GeoMesh) -> (f64, usize, usize) {
441 if mesh.n_vertices() == 0 {
442 return (0.0, 0, 0);
443 }
444 let d0 = dijkstra_geodesic(mesh, 0);
445 let u = d0
446 .iter()
447 .enumerate()
448 .filter(|(_, d)| d.is_finite())
449 .max_by(|a, b| a.1.partial_cmp(b.1).unwrap_or(std::cmp::Ordering::Equal))
450 .map(|(i, _)| i)
451 .unwrap_or(0);
452 let du = dijkstra_geodesic(mesh, u);
453 let (v, diameter) = du
454 .iter()
455 .enumerate()
456 .filter(|(_, d)| d.is_finite())
457 .max_by(|a, b| a.1.partial_cmp(b.1).unwrap_or(std::cmp::Ordering::Equal))
458 .map(|(i, &d)| (i, d))
459 .unwrap_or((0, 0.0));
460 (diameter, u, v)
461}
462pub fn geodesic_centroid(mesh: &GeoMesh) -> (usize, f64) {
467 let n = mesh.n_vertices();
468 if n == 0 {
469 return (0, 0.0);
470 }
471 let mut best_vertex = 0;
472 let mut best_sum = f64::INFINITY;
473 for v in 0..n {
474 let d = dijkstra_geodesic(mesh, v);
475 let sum: f64 = d.iter().filter(|x| x.is_finite()).map(|x| x * x).sum();
476 if sum < best_sum {
477 best_sum = sum;
478 best_vertex = v;
479 }
480 }
481 (best_vertex, best_sum)
482}
483pub fn geodesic_eccentricity(mesh: &GeoMesh) -> Vec<f64> {
486 (0..mesh.n_vertices())
487 .map(|v| {
488 let d = dijkstra_geodesic(mesh, v);
489 d.iter()
490 .cloned()
491 .filter(|x| x.is_finite())
492 .fold(0.0_f64, f64::max)
493 })
494 .collect()
495}
496pub fn all_pairs_geodesic(mesh: &GeoMesh) -> Vec<f64> {
501 let n = mesh.n_vertices();
502 let mut d = vec![f64::INFINITY; n * n];
503 for i in 0..n {
504 d[i * n + i] = 0.0;
505 }
506 for face in &mesh.faces {
507 let [a, b, c] = *face;
508 let lab = dist3(mesh.vertices[a], mesh.vertices[b]);
509 let lbc = dist3(mesh.vertices[b], mesh.vertices[c]);
510 let lca = dist3(mesh.vertices[c], mesh.vertices[a]);
511 for (u, v, w) in [
512 (a, b, lab),
513 (b, c, lbc),
514 (c, a, lca),
515 (b, a, lab),
516 (c, b, lbc),
517 (a, c, lca),
518 ] {
519 if w < d[u * n + v] {
520 d[u * n + v] = w;
521 }
522 }
523 }
524 for k in 0..n {
525 for i in 0..n {
526 if d[i * n + k].is_infinite() {
527 continue;
528 }
529 for j in 0..n {
530 let via = d[i * n + k] + d[k * n + j];
531 if via < d[i * n + j] {
532 d[i * n + j] = via;
533 }
534 }
535 }
536 }
537 d
538}
539pub fn smooth_geodesic_path(path: &[[f64; 3]], iterations: usize) -> Vec<[f64; 3]> {
546 if path.len() < 3 {
547 return path.to_vec();
548 }
549 let mut pts = path.to_vec();
550 for _ in 0..iterations {
551 let prev = pts.clone();
552 for i in 1..pts.len() - 1 {
553 pts[i] = scale3(add3(prev[i - 1], prev[i + 1]), 0.5);
554 }
555 }
556 pts
557}
558pub(super) fn is_locally_delaunay(
564 len_ab: f64,
565 len_ac: f64,
566 len_bc: f64,
567 len_ad: f64,
568 len_bd: f64,
569) -> bool {
570 let cos_c = (len_ac * len_ac + len_bc * len_bc - len_ab * len_ab) / (2.0 * len_ac * len_bc);
571 let cos_d = (len_ad * len_ad + len_bd * len_bd - len_ab * len_ab) / (2.0 * len_ad * len_bd);
572 cos_c + cos_d >= -1e-12
573}
574pub fn intrinsic_delaunay(mesh: &GeoMesh) -> IntrinsicDelaunay {
582 let nf = mesh.faces.len();
583 let nv = mesh.vertices.len();
584 let mut faces = mesh.faces.clone();
585 let mut edge_len: Vec<[f64; 3]> = faces
586 .iter()
587 .map(|&[a, b, c]| {
588 [
589 dist3(mesh.vertices[b], mesh.vertices[c]),
590 dist3(mesh.vertices[a], mesh.vertices[c]),
591 dist3(mesh.vertices[a], mesh.vertices[b]),
592 ]
593 })
594 .collect();
595 let build_adj = |faces: &[[usize; 3]]| -> Vec<[(usize, usize); 3]> {
596 use std::collections::HashMap;
597 let mut edge_map: HashMap<(usize, usize), (usize, usize)> = HashMap::new();
598 let mut adj = vec![(usize::MAX, 0); nf * 3];
599 for (fi, face) in faces.iter().enumerate() {
600 for k in 0..3 {
601 let va = face[(k + 1) % 3];
602 let vb = face[(k + 2) % 3];
603 let key = (va.min(vb), va.max(vb));
604 if let Some(&(fj, kj)) = edge_map.get(&key) {
605 adj[fi * 3 + k] = (fj, kj);
606 adj[fj * 3 + kj] = (fi, k);
607 } else {
608 edge_map.insert(key, (fi, k));
609 }
610 }
611 }
612 let result = vec![(usize::MAX, 0usize); nf];
613 let _ = result;
614 let mut out: Vec<[(usize, usize); 3]> = vec![(usize::MAX, 0); nf]
615 .into_iter()
616 .map(|_| [(usize::MAX, 0); 3])
617 .collect();
618 for fi in 0..nf {
619 for k in 0..3 {
620 out[fi][k] = adj[fi * 3 + k];
621 }
622 }
623 out
624 };
625 let max_iter = nf * nf + nf * 4;
626 for _iter in 0..max_iter {
627 let adj = build_adj(&faces);
628 let mut flipped = false;
629 'outer: for fi in 0..nf {
630 for k in 0..3 {
631 let (fj, kj) = adj[fi][k];
632 if fj == usize::MAX {
633 continue;
634 }
635 let va = faces[fi][(k + 1) % 3];
636 let vb = faces[fi][(k + 2) % 3];
637 let vc = faces[fi][k];
638 let vd = faces[fj][kj];
639 let len_ab = edge_len[fi][k];
640 let len_bc = edge_len[fi][(k + 1) % 3];
641 let len_ca = edge_len[fi][(k + 2) % 3];
642 let len_da = edge_len[fj][(kj + 1) % 3];
643 let len_db = edge_len[fj][(kj + 2) % 3];
644 let _ = (va, vb, vc, vd, len_bc, len_ca, len_da, len_db);
645 if !is_locally_delaunay(
646 len_ab,
647 dist3(mesh.vertices[va.min(nv - 1)], mesh.vertices[vc.min(nv - 1)]),
648 dist3(mesh.vertices[vb.min(nv - 1)], mesh.vertices[vc.min(nv - 1)]),
649 dist3(mesh.vertices[va.min(nv - 1)], mesh.vertices[vd.min(nv - 1)]),
650 dist3(mesh.vertices[vb.min(nv - 1)], mesh.vertices[vd.min(nv - 1)]),
651 ) {
652 faces[fi] = [vc, vd, vb];
653 faces[fj] = [vd, vc, va];
654 let new_cd =
655 dist3(mesh.vertices[vc.min(nv - 1)], mesh.vertices[vd.min(nv - 1)]);
656 let new_db =
657 dist3(mesh.vertices[vd.min(nv - 1)], mesh.vertices[vb.min(nv - 1)]);
658 let new_bc =
659 dist3(mesh.vertices[vb.min(nv - 1)], mesh.vertices[vc.min(nv - 1)]);
660 let new_ca =
661 dist3(mesh.vertices[vc.min(nv - 1)], mesh.vertices[va.min(nv - 1)]);
662 let new_ad =
663 dist3(mesh.vertices[va.min(nv - 1)], mesh.vertices[vd.min(nv - 1)]);
664 edge_len[fi] = [new_db, new_bc, new_cd];
665 edge_len[fj] = [new_ca, new_ad, new_cd];
666 flipped = true;
667 break 'outer;
668 }
669 }
670 }
671 if !flipped {
672 break;
673 }
674 }
675 IntrinsicDelaunay {
676 edge_lengths: edge_len,
677 faces,
678 vertices: mesh.vertices.clone(),
679 }
680}
681pub fn angular_defect(mesh: &GeoMesh) -> Vec<f64> {
692 use std::f64::consts::TAU;
693 let n = mesh.n_vertices();
694 let mut defect = vec![TAU; n];
695 for &[a, b, c] in &mesh.faces {
696 let pa = mesh.vertices[a];
697 let pb = mesh.vertices[b];
698 let pc = mesh.vertices[c];
699 let angle_at = |o: [f64; 3], p: [f64; 3], q: [f64; 3]| -> f64 {
700 let d1 = normalize3(sub3(p, o));
701 let d2 = normalize3(sub3(q, o));
702 let cos_theta = dot3(d1, d2).clamp(-1.0, 1.0);
703 cos_theta.acos()
704 };
705 defect[a] -= angle_at(pa, pb, pc);
706 defect[b] -= angle_at(pb, pa, pc);
707 defect[c] -= angle_at(pc, pa, pb);
708 }
709 let mut edge_count: HashMap<(usize, usize), usize> = HashMap::new();
710 for &[a, b, c] in &mesh.faces {
711 for &(u, v) in &[(a, b), (b, c), (c, a)] {
712 *edge_count.entry((u.min(v), u.max(v))).or_insert(0) += 1;
713 }
714 }
715 let mut is_boundary = vec![false; n];
716 for (&(u, v), &cnt) in &edge_count {
717 if cnt == 1 {
718 is_boundary[u] = true;
719 is_boundary[v] = true;
720 }
721 }
722 for v in 0..n {
723 if is_boundary[v] {
724 defect[v] -= std::f64::consts::PI;
725 }
726 }
727 defect
728}
729pub fn total_gaussian_curvature(mesh: &GeoMesh) -> f64 {
734 angular_defect(mesh).iter().sum()
735}
736pub fn parallel_transport(mesh: &GeoMesh, path: &[usize], initial_vector: [f64; 3]) -> [f64; 3] {
749 if path.len() < 2 {
750 return initial_vector;
751 }
752 let mut v = initial_vector;
753 for i in 0..path.len() - 1 {
754 let p_curr = mesh.vertices[path[i]];
755 let p_next = mesh.vertices[path[i + 1]];
756 let edge = normalize3(sub3(p_next, p_curr));
757 let normal = vertex_normal_approx(mesh, path[i]);
758 let v_tangent = sub3(v, scale3(normal, dot3(v, normal)));
759 let v_tangent_len = len3(v_tangent);
760 if v_tangent_len < 1e-14 {
761 v = v_tangent;
762 continue;
763 }
764 let v_tangent_n = scale3(v_tangent, 1.0 / v_tangent_len);
765 let edge_tangent = sub3(edge, scale3(normal, dot3(edge, normal)));
766 let edge_tangent_len = len3(edge_tangent);
767 if edge_tangent_len < 1e-14 {
768 continue;
769 }
770 let edge_tangent_n = scale3(edge_tangent, 1.0 / edge_tangent_len);
771 let cos_a = dot3(v_tangent_n, edge_tangent_n).clamp(-1.0, 1.0);
772 let cross = cross3(v_tangent_n, edge_tangent_n);
773 let sin_a = dot3(cross, normal);
774 let _angle = sin_a.atan2(cos_a);
775 let v_along = scale3(edge, dot3(v, edge));
776 let v_perp = sub3(v, v_along);
777 let next_normal = vertex_normal_approx(mesh, path[i + 1]);
778 let v_perp_new = sub3(v_perp, scale3(next_normal, dot3(v_perp, next_normal)));
779 let perp_len = len3(v_perp);
780 let perp_new_len = len3(v_perp_new);
781 let v_perp_scaled = if perp_new_len > 1e-14 {
782 scale3(v_perp_new, perp_len / perp_new_len)
783 } else {
784 v_perp_new
785 };
786 let next_edge = if i + 1 < path.len() - 1 {
787 normalize3(sub3(mesh.vertices[path[i + 2]], p_next))
788 } else {
789 edge
790 };
791 v = add3(v_perp_scaled, scale3(next_edge, len3(v_along)));
792 }
793 v
794}
795pub(super) fn vertex_normal_approx(mesh: &GeoMesh, v: usize) -> [f64; 3] {
797 let mut acc = [0.0f64; 3];
798 let mut count = 0usize;
799 for &[a, b, c] in &mesh.faces {
800 if a == v || b == v || c == v {
801 acc = add3(
802 acc,
803 mesh.face_normal_raw(mesh.faces.iter().position(|&f| f == [a, b, c]).unwrap_or(0)),
804 );
805 count += 1;
806 }
807 }
808 if count == 0 {
809 return [0.0, 1.0, 0.0];
810 }
811 normalize3(acc)
812}
813pub fn varadhan_distances(mesh: &GeoMesh, source: usize, t: f64) -> Vec<f64> {
821 let n = mesh.n_vertices();
822 if n == 0 {
823 return Vec::new();
824 }
825 let eff_t = if t <= 0.0 { 1e-3 } else { t };
826 let result = heat_geodesic(mesh, source, eff_t, 5);
827 let max_heat = result.distances.iter().cloned().fold(0.0f64, f64::max);
828 let adj = mesh.build_adjacency();
829 let mut heat = vec![0.0_f64; n];
830 heat[source] = 1.0;
831 let dt = eff_t / 5.0;
832 for _ in 0..5 {
833 let old = heat.clone();
834 for v in 0..n {
835 if adj[v].is_empty() {
836 continue;
837 }
838 let lap: f64 =
839 adj[v].iter().map(|&(u, _)| old[u] - old[v]).sum::<f64>() / adj[v].len() as f64;
840 heat[v] = (old[v] + dt * lap).max(1e-300);
841 }
842 }
843 heat[source] = heat[source].max(1e-300);
844 let h0 = heat[source];
845 let distances: Vec<f64> = heat
846 .iter()
847 .map(|&h| {
848 let ratio = (h / h0).max(1e-300);
849 (-4.0 * eff_t * ratio.ln()).max(0.0).sqrt()
850 })
851 .collect();
852 let _ = (max_heat, result);
853 distances
854}
855pub fn geodesic_voronoi_cells(mesh: &GeoMesh, sources: &[usize]) -> Vec<GeoVoronoiCell> {
860 if sources.is_empty() {
861 return Vec::new();
862 }
863 let regions = geodesic_voronoi_regions(mesh, sources);
864 let ns = sources.len();
865 let mut cells: Vec<GeoVoronoiCell> = sources
866 .iter()
867 .enumerate()
868 .map(|(si, &sv)| GeoVoronoiCell {
869 source_idx: si,
870 source_vertex: sv,
871 vertices: Vec::new(),
872 area: 0.0,
873 })
874 .collect();
875 for (v, &r) in regions.iter().enumerate() {
876 if r < ns {
877 cells[r].vertices.push(v);
878 }
879 }
880 for fi in 0..mesh.n_faces() {
881 let area = mesh.face_area(fi);
882 let [a, b, c] = mesh.faces[fi];
883 for &v in &[a, b, c] {
884 let r = regions[v];
885 if r < ns {
886 cells[r].area += area / 3.0;
887 }
888 }
889 }
890 cells
891}
892pub fn cotangent_weight(lab: f64, lac: f64, lbc: f64) -> f64 {
897 let cos_a = (lab * lab + lac * lac - lbc * lbc) / (2.0 * lab * lac + 1e-300);
898 let sin2_a = (1.0 - cos_a * cos_a).max(0.0);
899 let sin_a = sin2_a.sqrt();
900 if sin_a < 1e-14 { 0.0 } else { cos_a / sin_a }
901}
902pub fn build_cotangent_laplacian(mesh: &GeoMesh) -> Vec<Vec<(usize, f64)>> {
907 let n = mesh.n_vertices();
908 let mut laplacian: Vec<Vec<(usize, f64)>> = vec![Vec::new(); n];
909 for &[i, j, k] in &mesh.faces {
910 let pi = mesh.vertices[i];
911 let pj = mesh.vertices[j];
912 let pk = mesh.vertices[k];
913 let lij = dist3(pi, pj);
914 let lik = dist3(pi, pk);
915 let ljk = dist3(pj, pk);
916 let cot_k = cotangent_weight(ljk, lik, lij);
917 let cot_j = cotangent_weight(lij, ljk, lik);
918 let cot_i = cotangent_weight(lik, lij, ljk);
919 laplacian[i].push((j, cot_k * 0.5));
920 laplacian[j].push((i, cot_k * 0.5));
921 laplacian[i].push((k, cot_j * 0.5));
922 laplacian[k].push((i, cot_j * 0.5));
923 laplacian[j].push((k, cot_i * 0.5));
924 laplacian[k].push((j, cot_i * 0.5));
925 }
926 laplacian
927}
928pub fn dijkstra_intrinsic(id: &IntrinsicDelaunay, source: usize) -> Vec<f64> {
933 let n = id.vertices.len();
934 let mut dist = vec![f64::INFINITY; n];
935 dist[source] = 0.0;
936 let mut adj: Vec<Vec<(usize, f64)>> = vec![Vec::new(); n];
937 for (fi, &[a, b, c]) in id.faces.iter().enumerate() {
938 let lab = id.edge_lengths[fi][2];
939 let lac = id.edge_lengths[fi][1];
940 let lbc = id.edge_lengths[fi][0];
941 adj[a].push((b, lab));
942 adj[b].push((a, lab));
943 adj[a].push((c, lac));
944 adj[c].push((a, lac));
945 adj[b].push((c, lbc));
946 adj[c].push((b, lbc));
947 }
948 let mut heap: BinaryHeap<DijkEntry> = BinaryHeap::new();
949 heap.push(DijkEntry {
950 dist: 0.0,
951 vertex: source,
952 });
953 while let Some(DijkEntry { dist: d, vertex: u }) = heap.pop() {
954 if d > dist[u] {
955 continue;
956 }
957 for &(v, w) in &adj[u] {
958 let alt = dist[u] + w;
959 if alt < dist[v] {
960 dist[v] = alt;
961 heap.push(DijkEntry {
962 dist: alt,
963 vertex: v,
964 });
965 }
966 }
967 }
968 dist
969}