#![allow(dead_code)]
#[derive(Debug, Clone)]
pub struct Lattice {
pub u: usize,
pub v: usize,
pub w: usize,
pub control_points: Vec<[f32; 3]>,
pub min: [f32; 3],
pub max: [f32; 3],
}
impl Lattice {
pub fn new(u: usize, v: usize, w: usize) -> Self {
let u = u.max(2);
let v = v.max(2);
let w = w.max(2);
let mut cp = Vec::with_capacity(u * v * w);
for wi in 0..w {
for vi in 0..v {
for ui in 0..u {
cp.push([
ui as f32 / (u - 1) as f32,
vi as f32 / (v - 1) as f32,
wi as f32 / (w - 1) as f32,
]);
}
}
}
Self {
u,
v,
w,
control_points: cp,
min: [0.0; 3],
max: [1.0; 3],
}
}
pub fn cp_count(&self) -> usize {
self.u * self.v * self.w
}
}
pub fn world_to_lattice_uvw(pos: [f32; 3], min: [f32; 3], max: [f32; 3]) -> [f32; 3] {
[
((pos[0] - min[0]) / (max[0] - min[0]).max(1e-12)).clamp(0.0, 1.0),
((pos[1] - min[1]) / (max[1] - min[1]).max(1e-12)).clamp(0.0, 1.0),
((pos[2] - min[2]) / (max[2] - min[2]).max(1e-12)).clamp(0.0, 1.0),
]
}
pub fn lattice_trilinear(lattice: &Lattice, uvw: [f32; 3]) -> [f32; 3] {
let u_max = (lattice.u - 1) as f32;
let v_max = (lattice.v - 1) as f32;
let w_max = (lattice.w - 1) as f32;
let uf = (uvw[0] * u_max).clamp(0.0, u_max);
let vf = (uvw[1] * v_max).clamp(0.0, v_max);
let wf = (uvw[2] * w_max).clamp(0.0, w_max);
let ui = (uf as usize).min(lattice.u.saturating_sub(2));
let vi = (vf as usize).min(lattice.v.saturating_sub(2));
let wi = (wf as usize).min(lattice.w.saturating_sub(2));
let tu = uf - ui as f32;
let tv = vf - vi as f32;
let tw = wf - wi as f32;
let idx = |u: usize, v: usize, w: usize| w * lattice.v * lattice.u + v * lattice.u + u;
let mut result = [0.0_f32; 3];
for dw in 0..2_usize {
for dv in 0..2_usize {
for du in 0..2_usize {
let weight = (if du == 0 { 1.0 - tu } else { tu })
* (if dv == 0 { 1.0 - tv } else { tv })
* (if dw == 0 { 1.0 - tw } else { tw });
let cp = lattice.control_points[idx(ui + du, vi + dv, wi + dw)];
result[0] += weight * cp[0];
result[1] += weight * cp[1];
result[2] += weight * cp[2];
}
}
}
result
}
pub fn apply_lattice_deform(positions: &mut [[f32; 3]], lattice: &Lattice) {
for p in positions.iter_mut() {
let uvw = world_to_lattice_uvw(*p, lattice.min, lattice.max);
let deformed = lattice_trilinear(lattice, uvw);
for i in 0..3 {
p[i] = lattice.min[i] + deformed[i] * (lattice.max[i] - lattice.min[i]);
}
}
}
pub fn validate_lattice(lattice: &Lattice) -> bool {
lattice.u >= 2
&& lattice.v >= 2
&& lattice.w >= 2
&& lattice.control_points.len() == lattice.u * lattice.v * lattice.w
}
fn bernstein_basis_lat(n: usize, i: usize, t: f32) -> f32 {
binomial_lat(n, i) as f32 * t.powi(i as i32) * (1.0 - t).powi((n - i) as i32)
}
fn binomial_lat(n: usize, k: usize) -> usize {
if k > n {
return 0;
}
let k = k.min(n - k);
let mut r = 1usize;
for i in 0..k {
r = r * (n - i) / (i + 1);
}
r
}
pub struct FfdLattice {
pub divisions: [usize; 3],
pub control_points: Vec<[f32; 3]>,
pub bounds_min: [f32; 3],
pub bounds_max: [f32; 3],
}
pub fn new_ffd_lattice(
nx: usize,
ny: usize,
nz: usize,
min: [f32; 3],
max: [f32; 3],
) -> FfdLattice {
FfdLattice {
divisions: [nx, ny, nz],
control_points: vec![[0.0; 3]; (nx + 1) * (ny + 1) * (nz + 1)],
bounds_min: min,
bounds_max: max,
}
}
pub fn ffd_lattice_size(l: &FfdLattice) -> usize {
l.divisions[0] * l.divisions[1] * l.divisions[2]
}
pub fn ffd_lattice_point_count(l: &FfdLattice) -> usize {
(l.divisions[0] + 1) * (l.divisions[1] + 1) * (l.divisions[2] + 1)
}
pub fn ffd_apply_to_point(l: &FfdLattice, p: [f32; 3]) -> [f32; 3] {
let [nx, ny, nz] = l.divisions;
let [minx, miny, minz] = l.bounds_min;
let [maxx, maxy, maxz] = l.bounds_max;
let s = ((p[0] - minx) / (maxx - minx).max(1e-9)).clamp(0.0, 1.0);
let t = ((p[1] - miny) / (maxy - miny).max(1e-9)).clamp(0.0, 1.0);
let u = ((p[2] - minz) / (maxz - minz).max(1e-9)).clamp(0.0, 1.0);
let mut offset = [0.0f32; 3];
#[allow(clippy::needless_range_loop)]
for i in 0..=nx {
for j in 0..=ny {
for k in 0..=nz {
let w = bernstein_basis_lat(nx, i, s)
* bernstein_basis_lat(ny, j, t)
* bernstein_basis_lat(nz, k, u);
let idx = i * (ny + 1) * (nz + 1) + j * (nz + 1) + k;
let cp = l.control_points[idx];
offset[0] += w * cp[0];
offset[1] += w * cp[1];
offset[2] += w * cp[2];
}
}
}
[p[0] + offset[0], p[1] + offset[1], p[2] + offset[2]]
}
pub fn ffd_set_control_point(l: &mut FfdLattice, i: usize, j: usize, k: usize, offset: [f32; 3]) {
let [_, ny, nz] = l.divisions;
let idx = i * (ny + 1) * (nz + 1) + j * (nz + 1) + k;
if idx < l.control_points.len() {
l.control_points[idx] = offset;
}
}
pub fn ffd_reset(l: &mut FfdLattice) {
for cp in l.control_points.iter_mut() {
*cp = [0.0; 3];
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_lattice_new_dimensions() {
let lat = Lattice::new(2, 3, 4);
assert_eq!(lat.u, 2);
assert_eq!(lat.v, 3);
assert_eq!(lat.w, 4);
}
#[test]
fn test_lattice_cp_count() {
let lat = Lattice::new(2, 2, 2);
assert_eq!(lat.cp_count(), 8);
}
#[test]
fn test_world_to_lattice_uvw_corners() {
let min = [0.0_f32; 3];
let max = [1.0_f32; 3];
let uvw = world_to_lattice_uvw([0.0, 0.0, 0.0], min, max);
for c in uvw {
assert!(c.abs() < 1e-5);
}
}
#[test]
fn test_world_to_lattice_uvw_clamp() {
let min = [0.0_f32; 3];
let max = [1.0_f32; 3];
let uvw = world_to_lattice_uvw([2.0, -1.0, 0.5], min, max);
assert!(uvw[0] <= 1.0);
assert!(uvw[1] >= 0.0);
}
#[test]
fn test_lattice_trilinear_origin() {
let lat = Lattice::new(2, 2, 2);
let out = lattice_trilinear(&lat, [0.0, 0.0, 0.0]);
assert!(out[0].abs() < 1e-5);
assert!(out[1].abs() < 1e-5);
assert!(out[2].abs() < 1e-5);
}
#[test]
fn test_lattice_trilinear_far_corner() {
let lat = Lattice::new(2, 2, 2);
let out = lattice_trilinear(&lat, [1.0, 1.0, 1.0]);
assert!((out[0] - 1.0).abs() < 1e-5);
assert!((out[1] - 1.0).abs() < 1e-5);
assert!((out[2] - 1.0).abs() < 1e-5);
}
#[test]
fn test_validate_lattice_valid() {
let lat = Lattice::new(3, 3, 3);
assert!(validate_lattice(&lat));
}
#[test]
fn test_apply_lattice_deform_identity() {
let lat = Lattice::new(2, 2, 2);
let mut pos = vec![[0.5_f32, 0.5, 0.5]];
apply_lattice_deform(&mut pos, &lat);
for c in pos[0] {
assert!((0.0..=1.0).contains(&c));
}
}
#[test]
fn test_apply_lattice_deform_preserves_count() {
let lat = Lattice::new(2, 2, 2);
let mut pos = vec![[0.1_f32, 0.2, 0.3]; 10];
apply_lattice_deform(&mut pos, &lat);
assert_eq!(pos.len(), 10);
}
}