use std::collections::HashSet;
use crate::ids::VertexId;
use crate::linalg::{
SparseSystem, build_cotan_laplacian, build_vertex_index, conjugate_gradient,
regularize_diagonal,
};
use crate::storage::MeshStorage;
pub fn harmonic_map(
mesh: &MeshStorage,
correspondences: &[(VertexId, [f64; 3])],
) -> Option<Vec<[f64; 3]>> {
let n = mesh.vertex_count();
if n == 0 || correspondences.is_empty() {
return None;
}
let v_idx = build_vertex_index(mesh);
let pinned_set: HashSet<usize> = correspondences
.iter()
.filter_map(|(v, _)| v_idx.get(v))
.copied()
.collect();
let _free: Vec<usize> = (0..n).filter(|i| !pinned_set.contains(i)).collect();
let laplacian = build_cotan_laplacian(mesh, &v_idx);
let mut a = laplacian.finish();
regularize_diagonal(&mut a, 1e-8);
let mut rhs_x = vec![0.0; n];
let mut rhs_y = vec![0.0; n];
let mut rhs_z = vec![0.0; n];
for &(v, pos) in correspondences {
if let Some(&idx) = v_idx.get(&v) {
rhs_x[idx] = pos[0];
rhs_y[idx] = pos[1];
rhs_z[idx] = pos[2];
}
}
let sol_x = conjugate_gradient(&a, &rhs_x, n * 100, 1e-6)?;
let sol_y = conjugate_gradient(&a, &rhs_y, n * 100, 1e-6)?;
let sol_z = conjugate_gradient(&a, &rhs_z, n * 100, 1e-6)?;
let mapped: Vec<[f64; 3]> = sol_x
.iter()
.zip(sol_y.iter())
.zip(sol_z.iter())
.map(|((&x, &y), &z)| [x, y, z])
.collect();
Some(mapped)
}
pub fn apply_mobius_transform(
uv: &[[f64; 2]],
a: [f64; 2],
b: [f64; 2],
c: [f64; 2],
d: [f64; 2],
) -> Vec<[f64; 2]> {
uv.iter()
.map(|&[u, v]| {
let z = [u, v];
let num = complex_mul_add(a, z, b); let den = complex_mul_add(c, z, d); complex_div(num, den)
})
.collect()
}
pub fn mobius_to_center(target: [f64; 2]) -> ([f64; 2], [f64; 2], [f64; 2], [f64; 2]) {
let a = target; let a_conj = [a[0], -a[1]];
(
[1.0, 0.0],
[-a[0], -a[1]],
[-a_conj[0], -a_conj[1]],
[1.0, 0.0],
)
}
pub fn compute_vertex_scale_factors(
mesh: &MeshStorage,
target_curvature: &[f64],
) -> Option<Vec<f64>> {
let n = mesh.vertex_count();
if n == 0 || target_curvature.len() != n {
return None;
}
let v_idx = build_vertex_index(mesh);
let laplacian = build_cotan_laplacian(mesh, &v_idx);
let lap = laplacian.finish();
let mut sys = SparseSystem::new(n);
sys.add_diag(0, 1.0);
for row in 1..n {
if let Some(row_view) = lap.outer_view(row) {
for (col, &val) in row_view.iter() {
sys.add(row, col, val);
}
}
}
let mut a = sys.finish();
regularize_diagonal(&mut a, 0.1);
let current_k: Vec<f64> = mesh
.vertex_ids()
.map(|v| crate::geometry::gaussian_curvature(mesh, v).unwrap_or(0.0))
.collect();
let mut rhs = vec![0.0; n];
for i in 1..n {
rhs[i] = target_curvature[i] - current_k[i];
}
let u = conjugate_gradient(&a, &rhs, n * 500, 1e-4)?;
Some(u)
}
fn complex_mul(a: [f64; 2], b: [f64; 2]) -> [f64; 2] {
[a[0] * b[0] - a[1] * b[1], a[0] * b[1] + a[1] * b[0]]
}
fn complex_mul_add(a: [f64; 2], b: [f64; 2], c: [f64; 2]) -> [f64; 2] {
let prod = complex_mul(a, b);
[prod[0] + c[0], prod[1] + c[1]]
}
fn complex_div(a: [f64; 2], b: [f64; 2]) -> [f64; 2] {
let denom = b[0] * b[0] + b[1] * b[1];
if denom < 1e-14 {
return [0.0, 0.0];
}
[
(a[0] * b[0] + a[1] * b[1]) / denom,
(a[1] * b[0] - a[0] * b[1]) / denom,
]
}
#[cfg(test)]
mod tests {
use super::*;
use crate::test_util::build_icosphere;
#[test]
fn test_mobius_identity() {
let uv = vec![[0.5, 0.3], [-0.2, 0.8], [0.0, 0.0]];
let result = apply_mobius_transform(&uv, [1.0, 0.0], [0.0, 0.0], [0.0, 0.0], [1.0, 0.0]);
for (i, &[u, v]) in uv.iter().enumerate() {
assert!((result[i][0] - u).abs() < 1e-12);
assert!((result[i][1] - v).abs() < 1e-12);
}
}
#[test]
fn test_mobius_translation() {
let uv = vec![[0.0, 0.0], [1.0, 2.0]];
let result = apply_mobius_transform(&uv, [1.0, 0.0], [1.0, 0.0], [0.0, 0.0], [1.0, 0.0]);
assert!((result[0][0] - 1.0).abs() < 1e-12);
assert!((result[0][1] - 0.0).abs() < 1e-12);
assert!((result[1][0] - 2.0).abs() < 1e-12);
assert!((result[1][1] - 2.0).abs() < 1e-12);
}
#[test]
fn test_mobius_to_center_maps_origin() {
let target = [0.5, 0.3];
let coeffs = mobius_to_center(target);
let origin_uv =
apply_mobius_transform(&[[0.0, 0.0]], coeffs.0, coeffs.1, coeffs.2, coeffs.3);
assert!((origin_uv[0][0] + target[0]).abs() < 1e-12);
assert!((origin_uv[0][1] + target[1]).abs() < 1e-12);
}
#[test]
fn test_scale_factors_sphere() {
let mesh = build_icosphere(2);
let n = mesh.vertex_count();
let target_k: Vec<f64> = (0..n).map(|_| 0.0).collect();
let result = compute_vertex_scale_factors(&mesh, &target_k);
assert!(result.is_some(), "Scale factor computation should succeed");
let u = result.unwrap();
assert_eq!(u.len(), n);
assert!(
u.iter().all(|x| x.is_finite()),
"All scale factors must be finite"
);
}
}