use nalgebra_glm::{Vec3, vec3};
use serde::{Deserialize, Serialize};
use crate::ecs::world::Entity;
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct Lattice {
pub base_points: Vec<Vec3>,
pub displacements: Vec<Vec3>,
pub dimensions: [usize; 3],
pub bounds_min: Vec3,
pub bounds_max: Vec3,
pub falloff: f32,
pub version: u32,
}
impl Lattice {
pub fn new(bounds_min: Vec3, bounds_max: Vec3, dimensions: [usize; 3]) -> Self {
let [nx, ny, nz] = dimensions;
let point_count = nx * ny * nz;
let mut base_points = Vec::with_capacity(point_count);
let size = bounds_max - bounds_min;
for z in 0..nz {
for y in 0..ny {
for x in 0..nx {
let u = if nx > 1 {
x as f32 / (nx - 1) as f32
} else {
0.5
};
let v = if ny > 1 {
y as f32 / (ny - 1) as f32
} else {
0.5
};
let w = if nz > 1 {
z as f32 / (nz - 1) as f32
} else {
0.5
};
let position = bounds_min + vec3(u * size.x, v * size.y, w * size.z);
base_points.push(position);
}
}
}
let displacements = vec![Vec3::zeros(); point_count];
Self {
base_points,
displacements,
dimensions,
bounds_min,
bounds_max,
falloff: 0.0,
version: 0,
}
}
pub fn with_falloff(mut self, falloff: f32) -> Self {
self.falloff = falloff;
self
}
pub fn get_index(&self, x: usize, y: usize, z: usize) -> usize {
let [nx, ny, _nz] = self.dimensions;
z * (nx * ny) + y * nx + x
}
pub fn set_displacement(&mut self, x: usize, y: usize, z: usize, displacement: Vec3) {
let index = self.get_index(x, y, z);
if index < self.displacements.len() {
self.displacements[index] = displacement;
self.version = self.version.wrapping_add(1);
}
}
pub fn get_displacement(&self, x: usize, y: usize, z: usize) -> Vec3 {
let index = self.get_index(x, y, z);
self.displacements
.get(index)
.copied()
.unwrap_or(Vec3::zeros())
}
pub fn get_point(&self, x: usize, y: usize, z: usize) -> Vec3 {
let index = self.get_index(x, y, z);
if index < self.base_points.len() {
self.base_points[index] + self.displacements[index]
} else {
Vec3::zeros()
}
}
pub fn world_to_uvw(&self, world_pos: Vec3) -> Vec3 {
let size = self.bounds_max - self.bounds_min;
let relative = world_pos - self.bounds_min;
vec3(
if size.x > 0.0 {
relative.x / size.x
} else {
0.5
},
if size.y > 0.0 {
relative.y / size.y
} else {
0.5
},
if size.z > 0.0 {
relative.z / size.z
} else {
0.5
},
)
}
pub fn sample(&self, world_pos: Vec3) -> Vec3 {
let uvw = self.world_to_uvw(world_pos);
let inside = uvw.x >= 0.0
&& uvw.x <= 1.0
&& uvw.y >= 0.0
&& uvw.y <= 1.0
&& uvw.z >= 0.0
&& uvw.z <= 1.0;
let blend = if inside {
1.0
} else if self.falloff > 0.0 {
let clamped = vec3(
uvw.x.clamp(0.0, 1.0),
uvw.y.clamp(0.0, 1.0),
uvw.z.clamp(0.0, 1.0),
);
let distance = nalgebra_glm::length(&(uvw - clamped));
let size = self.bounds_max - self.bounds_min;
let avg_size = (size.x + size.y + size.z) / 3.0;
let normalized_distance = distance * avg_size;
(1.0 - normalized_distance / self.falloff).max(0.0)
} else {
0.0
};
if blend <= 0.0 {
return Vec3::zeros();
}
let [nx, ny, nz] = self.dimensions;
let fx = uvw.x.clamp(0.0, 1.0) * (nx - 1) as f32;
let fy = uvw.y.clamp(0.0, 1.0) * (ny - 1) as f32;
let fz = uvw.z.clamp(0.0, 1.0) * (nz - 1) as f32;
let x0 = (fx.floor() as usize).min(nx - 2);
let y0 = (fy.floor() as usize).min(ny - 2);
let z0 = (fz.floor() as usize).min(nz - 2);
let x1 = x0 + 1;
let y1 = y0 + 1;
let z1 = z0 + 1;
let tx = fx - x0 as f32;
let ty = fy - y0 as f32;
let tz = fz - z0 as f32;
let d000 = self.get_displacement(x0, y0, z0);
let d100 = self.get_displacement(x1, y0, z0);
let d010 = self.get_displacement(x0, y1, z0);
let d110 = self.get_displacement(x1, y1, z0);
let d001 = self.get_displacement(x0, y0, z1);
let d101 = self.get_displacement(x1, y0, z1);
let d011 = self.get_displacement(x0, y1, z1);
let d111 = self.get_displacement(x1, y1, z1);
let d00 = nalgebra_glm::lerp(&d000, &d100, tx);
let d10 = nalgebra_glm::lerp(&d010, &d110, tx);
let d01 = nalgebra_glm::lerp(&d001, &d101, tx);
let d11 = nalgebra_glm::lerp(&d011, &d111, tx);
let d0 = nalgebra_glm::lerp(&d00, &d10, ty);
let d1 = nalgebra_glm::lerp(&d01, &d11, ty);
let displacement = nalgebra_glm::lerp(&d0, &d1, tz);
displacement * blend
}
pub fn reset_displacements(&mut self) {
for displacement in &mut self.displacements {
*displacement = Vec3::zeros();
}
self.version = self.version.wrapping_add(1);
}
pub fn point_count(&self) -> usize {
self.base_points.len()
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LatticeInfluenced {
pub lattice_entity: Entity,
pub last_lattice_version: u32,
pub last_position: Vec3,
}
impl Default for LatticeInfluenced {
fn default() -> Self {
Self {
lattice_entity: Entity::default(),
last_lattice_version: u32::MAX,
last_position: vec3(f32::MAX, f32::MAX, f32::MAX),
}
}
}
impl LatticeInfluenced {
pub fn new(lattice_entity: Entity) -> Self {
Self {
lattice_entity,
last_lattice_version: u32::MAX,
last_position: vec3(f32::MAX, f32::MAX, f32::MAX),
}
}
pub fn needs_update(&self, lattice_version: u32, current_position: Vec3) -> bool {
self.last_lattice_version != lattice_version
|| (self.last_position - current_position).norm() > 0.0001
}
pub fn mark_updated(&mut self, lattice_version: u32, position: Vec3) {
self.last_lattice_version = lattice_version;
self.last_position = position;
}
}