#[allow(dead_code)]
#[derive(Debug, Clone)]
pub struct ShrinkwrapConfig {
pub offset: f32,
pub snap_normals: bool,
pub max_search_dist: Option<f32>,
}
#[allow(dead_code)]
#[derive(Debug, Clone)]
pub struct ShrinkwrapResult {
pub vertices: Vec<[f32; 3]>,
pub displacements: Vec<f32>,
}
#[allow(dead_code)]
pub fn default_shrinkwrap_config() -> ShrinkwrapConfig {
ShrinkwrapConfig {
offset: 0.0,
snap_normals: false,
max_search_dist: None,
}
}
#[allow(dead_code)]
pub fn shrinkwrap_mesh(
src_verts: &[[f32; 3]],
tgt_verts: &[[f32; 3]],
tgt_faces: &[[u32; 3]],
cfg: &ShrinkwrapConfig,
) -> ShrinkwrapResult {
let mut vertices = Vec::with_capacity(src_verts.len());
let mut displacements = Vec::with_capacity(src_verts.len());
for &sv in src_verts {
let projected = shrinkwrap_single_vertex(sv, tgt_verts, tgt_faces);
let disp = dist3(sv, projected);
let final_pos = if cfg.offset.abs() > f32::EPSILON {
let normal = approx_normal_toward(projected, sv, tgt_verts, tgt_faces);
[
projected[0] + normal[0] * cfg.offset,
projected[1] + normal[1] * cfg.offset,
projected[2] + normal[2] * cfg.offset,
]
} else {
projected
};
vertices.push(final_pos);
displacements.push(disp);
}
ShrinkwrapResult {
vertices,
displacements,
}
}
#[allow(dead_code)]
pub fn shrinkwrap_single_vertex(
v: [f32; 3],
tgt_verts: &[[f32; 3]],
tgt_faces: &[[u32; 3]],
) -> [f32; 3] {
let mut best_pt = v;
let mut best_d2 = f32::MAX;
for face in tgt_faces {
let a = tgt_verts[face[0] as usize];
let b = tgt_verts[face[1] as usize];
let c = tgt_verts[face[2] as usize];
let pt = nearest_point_on_triangle(v, a, b, c);
let d2 = dist2_3(v, pt);
if d2 < best_d2 {
best_d2 = d2;
best_pt = pt;
}
}
best_pt
}
#[allow(dead_code)]
pub fn nearest_point_on_triangle(
p: [f32; 3],
a: [f32; 3],
b: [f32; 3],
c: [f32; 3],
) -> [f32; 3] {
let ab = sub3(b, a);
let ac = sub3(c, a);
let ap = sub3(p, a);
let d1 = dot3(ab, ap);
let d2 = dot3(ac, ap);
if d1 <= 0.0 && d2 <= 0.0 {
return a;
}
let bp = sub3(p, b);
let d3 = dot3(ab, bp);
let d4 = dot3(ac, bp);
if d3 >= 0.0 && d4 <= d3 {
return b;
}
let vc = d1 * d4 - d3 * d2;
if vc <= 0.0 && d1 >= 0.0 && d3 <= 0.0 {
let v = d1 / (d1 - d3);
return [a[0] + ab[0] * v, a[1] + ab[1] * v, a[2] + ab[2] * v];
}
let cp = sub3(p, c);
let d5 = dot3(ab, cp);
let d6 = dot3(ac, cp);
if d6 >= 0.0 && d5 <= d6 {
return c;
}
let vb = d5 * d2 - d1 * d6;
if vb <= 0.0 && d2 >= 0.0 && d6 <= 0.0 {
let w = d2 / (d2 - d6);
return [a[0] + ac[0] * w, a[1] + ac[1] * w, a[2] + ac[2] * w];
}
let va = d3 * d6 - d5 * d4;
if va <= 0.0 && (d4 - d3) >= 0.0 && (d5 - d6) >= 0.0 {
let w = (d4 - d3) / ((d4 - d3) + (d5 - d6));
let bc = sub3(c, b);
return [b[0] + bc[0] * w, b[1] + bc[1] * w, b[2] + bc[2] * w];
}
let denom = 1.0 / (va + vb + vc);
let v = vb * denom;
let w = vc * denom;
[
a[0] + ab[0] * v + ac[0] * w,
a[1] + ab[1] * v + ac[1] * w,
a[2] + ab[2] * v + ac[2] * w,
]
}
#[allow(dead_code)]
pub fn shrinkwrap_vertex_count(result: &ShrinkwrapResult) -> usize {
result.vertices.len()
}
#[allow(dead_code)]
pub fn shrinkwrap_max_displacement(result: &ShrinkwrapResult) -> f32 {
result
.displacements
.iter()
.cloned()
.fold(0.0_f32, f32::max)
}
#[allow(dead_code)]
pub fn shrinkwrap_average_displacement(result: &ShrinkwrapResult) -> f32 {
if result.displacements.is_empty() {
return 0.0;
}
let sum: f32 = result.displacements.iter().sum();
sum / result.displacements.len() as f32
}
#[allow(dead_code)]
pub fn shrinkwrap_apply_offset(result: &mut ShrinkwrapResult, offset: f32) {
for v in &mut result.vertices {
v[1] += offset;
}
}
fn sub3(a: [f32; 3], b: [f32; 3]) -> [f32; 3] {
[a[0] - b[0], a[1] - b[1], a[2] - b[2]]
}
fn dot3(a: [f32; 3], b: [f32; 3]) -> f32 {
a[0] * b[0] + a[1] * b[1] + a[2] * b[2]
}
fn cross3(a: [f32; 3], b: [f32; 3]) -> [f32; 3] {
[
a[1] * b[2] - a[2] * b[1],
a[2] * b[0] - a[0] * b[2],
a[0] * b[1] - a[1] * b[0],
]
}
fn normalize3(v: [f32; 3]) -> [f32; 3] {
let len = (v[0] * v[0] + v[1] * v[1] + v[2] * v[2]).sqrt();
if len < f32::EPSILON {
return [0.0, 1.0, 0.0];
}
[v[0] / len, v[1] / len, v[2] / len]
}
fn dist2_3(a: [f32; 3], b: [f32; 3]) -> f32 {
let d = sub3(a, b);
dot3(d, d)
}
fn dist3(a: [f32; 3], b: [f32; 3]) -> f32 {
dist2_3(a, b).sqrt()
}
fn approx_normal_toward(
pt: [f32; 3],
src: [f32; 3],
tgt_verts: &[[f32; 3]],
tgt_faces: &[[u32; 3]],
) -> [f32; 3] {
let mut best_d2 = f32::MAX;
let mut best_n = [0.0_f32, 1.0, 0.0];
for face in tgt_faces {
let a = tgt_verts[face[0] as usize];
let b = tgt_verts[face[1] as usize];
let c = tgt_verts[face[2] as usize];
let centroid = [
(a[0] + b[0] + c[0]) / 3.0,
(a[1] + b[1] + c[1]) / 3.0,
(a[2] + b[2] + c[2]) / 3.0,
];
let d2 = dist2_3(pt, centroid);
if d2 < best_d2 {
best_d2 = d2;
let ab = sub3(b, a);
let ac = sub3(c, a);
let n = normalize3(cross3(ab, ac));
let to_src = sub3(src, pt);
best_n = if dot3(n, to_src) >= 0.0 { n } else { [-n[0], -n[1], -n[2]] };
}
}
best_n
}
#[cfg(test)]
mod tests {
use super::*;
fn flat_triangle() -> (Vec<[f32; 3]>, Vec<[u32; 3]>) {
let verts = vec![[0.0, 0.0, 0.0], [2.0, 0.0, 0.0], [0.0, 0.0, 2.0]];
let faces = vec![[0u32, 1, 2]];
(verts, faces)
}
#[test]
fn test_nearest_point_on_triangle_inside() {
let a = [0.0, 0.0, 0.0];
let b = [2.0, 0.0, 0.0];
let c = [0.0, 0.0, 2.0];
let p = [0.5, 5.0, 0.5];
let closest = nearest_point_on_triangle(p, a, b, c);
assert!((closest[0] - 0.5).abs() < 1e-5, "x");
assert!((closest[1]).abs() < 1e-5, "y");
assert!((closest[2] - 0.5).abs() < 1e-5, "z");
}
#[test]
fn test_nearest_point_on_triangle_vertex() {
let a = [0.0, 0.0, 0.0];
let b = [1.0, 0.0, 0.0];
let c = [0.0, 1.0, 0.0];
let p = [-1.0, -1.0, 0.0];
let closest = nearest_point_on_triangle(p, a, b, c);
assert!((closest[0]).abs() < 1e-5);
assert!((closest[1]).abs() < 1e-5);
}
#[test]
fn test_shrinkwrap_single_vertex_above_plane() {
let (tgt_verts, tgt_faces) = flat_triangle();
let v = [0.5, 3.0, 0.5];
let projected = shrinkwrap_single_vertex(v, &tgt_verts, &tgt_faces);
assert!((projected[1]).abs() < 1e-5, "should land on y=0");
assert!((projected[0] - 0.5).abs() < 1e-5);
assert!((projected[2] - 0.5).abs() < 1e-5);
}
#[test]
fn test_shrinkwrap_mesh_result_length() {
let (tgt_verts, tgt_faces) = flat_triangle();
let src_verts = vec![[0.3, 2.0, 0.3], [0.1, 1.0, 0.1]];
let cfg = default_shrinkwrap_config();
let result = shrinkwrap_mesh(&src_verts, &tgt_verts, &tgt_faces, &cfg);
assert_eq!(shrinkwrap_vertex_count(&result), 2);
assert_eq!(result.displacements.len(), 2);
}
#[test]
fn test_shrinkwrap_max_displacement_positive() {
let (tgt_verts, tgt_faces) = flat_triangle();
let src_verts = vec![[0.3, 5.0, 0.3]];
let cfg = default_shrinkwrap_config();
let result = shrinkwrap_mesh(&src_verts, &tgt_verts, &tgt_faces, &cfg);
let max_d = shrinkwrap_max_displacement(&result);
assert!(max_d > 4.0, "should have moved ~5 units");
}
#[test]
fn test_shrinkwrap_average_displacement_single() {
let (tgt_verts, tgt_faces) = flat_triangle();
let src_verts = vec![[0.3, 4.0, 0.3]];
let cfg = default_shrinkwrap_config();
let result = shrinkwrap_mesh(&src_verts, &tgt_verts, &tgt_faces, &cfg);
let avg = shrinkwrap_average_displacement(&result);
let max = shrinkwrap_max_displacement(&result);
assert!((avg - max).abs() < 1e-5, "single vertex: avg == max");
}
#[test]
fn test_shrinkwrap_apply_offset() {
let (tgt_verts, tgt_faces) = flat_triangle();
let src_verts = vec![[0.3, 2.0, 0.3]];
let cfg = default_shrinkwrap_config();
let mut result = shrinkwrap_mesh(&src_verts, &tgt_verts, &tgt_faces, &cfg);
let y_before = result.vertices[0][1];
shrinkwrap_apply_offset(&mut result, 0.5);
assert!((result.vertices[0][1] - (y_before + 0.5)).abs() < 1e-5);
}
#[test]
fn test_shrinkwrap_average_displacement_empty() {
let result = ShrinkwrapResult {
vertices: vec![],
displacements: vec![],
};
assert_eq!(shrinkwrap_average_displacement(&result), 0.0);
}
#[test]
fn test_shrinkwrap_with_offset_config() {
let (tgt_verts, tgt_faces) = flat_triangle();
let src_verts = vec![[0.3, 3.0, 0.3]];
let mut cfg = default_shrinkwrap_config();
cfg.offset = 1.0;
let result = shrinkwrap_mesh(&src_verts, &tgt_verts, &tgt_faces, &cfg);
assert!(result.vertices[0][1] > 0.5, "offset should lift vertex");
}
}