#![allow(dead_code)]
use std::f32::consts::PI;
#[derive(Debug, Clone)]
pub struct LatticeCage {
pub divisions: [usize; 3],
pub control_points: Vec<[f32; 3]>,
pub origin: [f32; 3],
pub size: [f32; 3],
}
#[derive(Debug, Clone)]
pub struct LatticeCageResult {
pub positions: Vec<[f32; 3]>,
pub moved_count: usize,
}
pub fn new_lattice_cage(origin: [f32; 3], size: [f32; 3], divisions: [usize; 3]) -> LatticeCage {
let (l, m, n) = (divisions[0] + 1, divisions[1] + 1, divisions[2] + 1);
let total = l * m * n;
let mut control_points = Vec::with_capacity(total);
for li in 0..l {
for mi in 0..m {
for ni in 0..n {
let x = origin[0] + size[0] * li as f32 / divisions[0].max(1) as f32;
let y = origin[1] + size[1] * mi as f32 / divisions[1].max(1) as f32;
let z = origin[2] + size[2] * ni as f32 / divisions[2].max(1) as f32;
control_points.push([x, y, z]);
}
}
}
LatticeCage {
divisions,
control_points,
origin,
size,
}
}
pub fn bernstein(i: usize, n: usize, t: f32) -> f32 {
binomial(n, i) as f32 * t.powi(i as i32) * (1.0 - t).powi((n - i) as i32)
}
fn binomial(n: usize, k: usize) -> u64 {
if k > n {
return 0;
}
let k = k.min(n - k);
let mut result = 1u64;
for i in 0..k {
result = result * (n - i) as u64 / (i + 1) as u64;
}
result
}
pub fn world_to_lattice_params(cage: &LatticeCage, point: [f32; 3]) -> [f32; 3] {
[
(point[0] - cage.origin[0]) / cage.size[0].max(1e-8),
(point[1] - cage.origin[1]) / cage.size[1].max(1e-8),
(point[2] - cage.origin[2]) / cage.size[2].max(1e-8),
]
}
fn cp_index(cage: &LatticeCage, l: usize, m: usize, n: usize) -> usize {
let m_pts = cage.divisions[1] + 1;
let n_pts = cage.divisions[2] + 1;
l * m_pts * n_pts + m * n_pts + n
}
pub fn evaluate_ffd(cage: &LatticeCage, stu: [f32; 3]) -> [f32; 3] {
let l = cage.divisions[0];
let m = cage.divisions[1];
let n = cage.divisions[2];
let [s, t, u] = stu;
let mut result = [0.0f32; 3];
for li in 0..=l {
let bl = bernstein(li, l, s);
for mi in 0..=m {
let bm = bernstein(mi, m, t);
for ni in 0..=n {
let bn = bernstein(ni, n, u);
let idx = cp_index(cage, li, mi, ni);
let cp = cage.control_points[idx];
let w = bl * bm * bn;
result[0] += w * cp[0];
result[1] += w * cp[1];
result[2] += w * cp[2];
}
}
}
result
}
pub fn apply_lattice_deform(cage: &LatticeCage, positions: &[[f32; 3]]) -> LatticeCageResult {
let mut out = Vec::with_capacity(positions.len());
let mut moved_count = 0usize;
for &pos in positions {
let stu = world_to_lattice_params(cage, pos);
if stu.iter().all(|&v| (0.0..=1.0).contains(&v)) {
let new_pos = evaluate_ffd(cage, stu);
let d = (0..3)
.map(|i| (new_pos[i] - pos[i]).abs())
.fold(0.0f32, f32::max);
if d > 1e-7 {
moved_count += 1;
}
out.push(new_pos);
} else {
out.push(pos);
}
}
LatticeCageResult {
positions: out,
moved_count,
}
}
pub fn move_control_point(cage: &mut LatticeCage, index: usize, delta: [f32; 3]) {
if let Some(cp) = cage.control_points.get_mut(index) {
cp[0] += delta[0];
cp[1] += delta[1];
cp[2] += delta[2];
}
}
pub fn control_point_count(cage: &LatticeCage) -> usize {
cage.control_points.len()
}
pub fn twist_lattice(cage: &mut LatticeCage, angle_per_unit_y: f32) {
let origin_y = cage.origin[1];
let size_y = cage.size[1];
for cp in cage.control_points.iter_mut() {
let t = (cp[1] - origin_y) / size_y.max(1e-8);
let angle = angle_per_unit_y * t;
let cos_a = angle.cos();
let sin_a = angle.sin();
let x = cp[0] - cage.origin[0];
let z = cp[2] - cage.origin[2];
cp[0] = cage.origin[0] + cos_a * x - sin_a * z;
cp[2] = cage.origin[2] + sin_a * x + cos_a * z;
}
}
pub fn reset_lattice(cage: &mut LatticeCage) {
*cage = new_lattice_cage(cage.origin, cage.size, cage.divisions);
}
pub fn validate_lattice(cage: &LatticeCage) -> bool {
let expected = (cage.divisions[0] + 1) * (cage.divisions[1] + 1) * (cage.divisions[2] + 1);
cage.control_points.len() == expected
}
pub fn lattice_deformed_bounds(cage: &LatticeCage) -> ([f32; 3], [f32; 3]) {
let mut mn = [f32::MAX; 3];
let mut mx = [f32::MIN; 3];
for cp in &cage.control_points {
for i in 0..3 {
mn[i] = mn[i].min(cp[i]);
mx[i] = mx[i].max(cp[i]);
}
}
(mn, mx)
}
pub fn lattice_volume(cage: &LatticeCage) -> f32 {
cage.size[0] * cage.size[1] * cage.size[2]
}
pub fn bend_lattice(cage: &mut LatticeCage, bend_angle_rad: f32) {
let _ = PI; let origin_x = cage.origin[0];
let size_x = cage.size[0];
for cp in cage.control_points.iter_mut() {
let t = (cp[0] - origin_x) / size_x.max(1e-8);
let angle = bend_angle_rad * t;
let radius = size_x / bend_angle_rad.abs().max(1e-8);
cp[1] += radius * (1.0 - angle.cos());
cp[0] = origin_x + radius * angle.sin() + (cp[0] - origin_x) * (1.0 - t);
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_new_cage_point_count() {
let cage = new_lattice_cage([0.0; 3], [1.0; 3], [2, 2, 2]);
assert_eq!(cage.control_points.len(), 3 * 3 * 3);
}
#[test]
fn test_validate_lattice() {
let cage = new_lattice_cage([0.0; 3], [1.0; 3], [1, 1, 1]);
assert!(validate_lattice(&cage));
}
#[test]
fn test_bernstein_endpoints() {
assert!((bernstein(0, 2, 0.0) - 1.0).abs() < 1e-6);
assert!((bernstein(2, 2, 1.0) - 1.0).abs() < 1e-6);
}
#[test]
fn test_evaluate_ffd_at_origin() {
let cage = new_lattice_cage([0.0; 3], [2.0; 3], [1, 1, 1]);
let result = evaluate_ffd(&cage, [0.0, 0.0, 0.0]);
assert!(result[0].abs() < 1e-5 && result[1].abs() < 1e-5 && result[2].abs() < 1e-5);
}
#[test]
fn test_apply_deform_count() {
let cage = new_lattice_cage([0.0; 3], [1.0; 3], [1, 1, 1]);
let pts = vec![[0.5, 0.5, 0.5], [0.2, 0.3, 0.4]];
let res = apply_lattice_deform(&cage, &pts);
assert_eq!(res.positions.len(), 2);
}
#[test]
fn test_move_control_point() {
let mut cage = new_lattice_cage([0.0; 3], [1.0; 3], [1, 1, 1]);
let before = cage.control_points[0];
move_control_point(&mut cage, 0, [0.1, 0.2, 0.3]);
let after = cage.control_points[0];
assert!((after[0] - before[0] - 0.1).abs() < 1e-6);
}
#[test]
fn test_reset_lattice() {
let mut cage = new_lattice_cage([0.0; 3], [1.0; 3], [1, 1, 1]);
move_control_point(&mut cage, 0, [5.0, 5.0, 5.0]);
reset_lattice(&mut cage);
assert!(validate_lattice(&cage));
let cp = cage.control_points[0];
assert!(cp[0].abs() < 1e-5 && cp[1].abs() < 1e-5 && cp[2].abs() < 1e-5);
}
#[test]
fn test_control_point_count() {
let cage = new_lattice_cage([0.0; 3], [1.0; 3], [3, 3, 3]);
assert_eq!(control_point_count(&cage), 4 * 4 * 4);
}
#[test]
fn test_lattice_volume() {
let cage = new_lattice_cage([0.0; 3], [2.0, 3.0, 4.0], [1, 1, 1]);
assert!((lattice_volume(&cage) - 24.0).abs() < 1e-5);
}
#[test]
fn test_world_to_lattice_params_center() {
let cage = new_lattice_cage([0.0; 3], [2.0; 3], [1, 1, 1]);
let stu = world_to_lattice_params(&cage, [1.0, 1.0, 1.0]);
for &v in &stu {
assert!((v - 0.5).abs() < 1e-5);
}
}
}