use gizmo_math::{Mat3, Vec3};
#[derive(Debug, Clone, Copy)]
pub struct SoftBodyNode {
pub position: Vec3,
pub velocity: Vec3,
pub mass: f32,
pub is_fixed: bool, }
#[derive(Debug, Clone, Copy)]
pub struct Tetrahedron {
pub node_indices: [u32; 4],
pub rest_volume: f32,
pub inv_rest_matrix: Mat3,
}
impl Tetrahedron {
pub fn calculate_rest_data(p0: Vec3, p1: Vec3, p2: Vec3, p3: Vec3) -> (Mat3, f32) {
let e1 = p1 - p0;
let e2 = p2 - p0;
let e3 = p3 - p0;
let dm = Mat3::from_cols(e1, e2, e3);
let det = dm.determinant();
let volume = (det / 6.0).abs();
let inv_dm = dm.inverse();
(inv_dm, volume)
}
}
#[derive(Debug, Clone)]
pub struct SoftBodyMesh {
pub nodes: Vec<SoftBodyNode>,
pub elements: Vec<Tetrahedron>,
pub youngs_modulus: f32,
pub poissons_ratio: f32,
pub lambda: f32,
pub mu: f32,
pub damping: f32,
}
gizmo_core::impl_component!(SoftBodyMesh);
pub fn resolve_node_collision(
mut position: Vec3,
mut velocity: Vec3,
dt: f32,
rigid_colliders: &[(
gizmo_core::entity::Entity,
gizmo_physics_core::Transform,
gizmo_physics_core::Collider,
)],
) -> (Vec3, Vec3, bool) {
let mut collided = false;
let ray = gizmo_physics_core::raycast::Ray::new(position, velocity.normalize_or_zero());
let dist = velocity.length() * dt;
if dist > 1e-5 {
for (_, col_trans, col) in rigid_colliders {
if let Some((d, n)) = gizmo_physics_core::raycast::Raycast::ray_shape(&ray, &col.shape, col_trans) {
if d <= dist + 0.1 {
let bounce = 0.5;
let friction = 0.8;
let vn = velocity.dot(n);
if vn < 0.0 {
let vt = velocity - n * vn;
velocity = vt * (1.0 - friction) - n * (vn * bounce);
}
position += ray.direction * (d - 0.1).max(0.0);
collided = true;
}
}
}
}
(position, velocity, collided)
}
impl SoftBodyMesh {
pub fn new(youngs_modulus: f32, poissons_ratio: f32) -> Self {
let mu = youngs_modulus / (2.0 * (1.0 + poissons_ratio));
let lambda = (youngs_modulus * poissons_ratio)
/ ((1.0 + poissons_ratio) * (1.0 - 2.0 * poissons_ratio));
Self {
nodes: Vec::new(),
elements: Vec::new(),
youngs_modulus,
poissons_ratio,
lambda,
mu,
damping: 0.99,
}
}
pub fn add_node(&mut self, position: Vec3, mass: f32) -> u32 {
let idx = self.nodes.len() as u32;
self.nodes.push(SoftBodyNode {
position,
velocity: Vec3::ZERO,
mass,
is_fixed: false,
});
idx
}
pub fn add_element(&mut self, i0: u32, i1: u32, i2: u32, i3: u32) {
let p0 = self.nodes[i0 as usize].position;
let p1 = self.nodes[i1 as usize].position;
let p2 = self.nodes[i2 as usize].position;
let p3 = self.nodes[i3 as usize].position;
let (inv_rest_matrix, rest_volume) = Tetrahedron::calculate_rest_data(p0, p1, p2, p3);
self.elements.push(Tetrahedron {
node_indices: [i0, i1, i2, i3],
rest_volume,
inv_rest_matrix,
});
}
pub fn step(
&mut self,
dt: f32,
gravity: Vec3,
rigid_colliders: &[(
gizmo_core::entity::Entity,
gizmo_physics_core::Transform,
gizmo_physics_core::Collider,
)],
) {
let num_nodes = self.nodes.len();
let mut forces: Vec<Vec3> = self.nodes.iter().map(|n| gravity * n.mass).collect();
use rayon::prelude::*;
let positions: Vec<Vec3> = self.nodes.iter().map(|n| n.position).collect();
const MIN_JACOBIAN: f32 = 0.1;
let elastic_forces = self
.elements
.par_iter()
.fold(
|| vec![Vec3::ZERO; num_nodes],
|mut acc_forces, elem| {
let i0 = elem.node_indices[0] as usize;
let i1 = elem.node_indices[1] as usize;
let i2 = elem.node_indices[2] as usize;
let i3 = elem.node_indices[3] as usize;
let x0 = positions[i0];
let x1 = positions[i1];
let x2 = positions[i2];
let x3 = positions[i3];
let ds = Mat3::from_cols(x1 - x0, x2 - x0, x3 - x0);
let f = ds * elem.inv_rest_matrix;
let j = f.determinant();
if j < MIN_JACOBIAN {
return acc_forces;
}
let f_inv_t = f.inverse().transpose();
let ln_j = j.ln();
let p = f_inv_t * (-self.mu) + f * self.mu + f_inv_t * (self.lambda * ln_j);
let h = p * elem.inv_rest_matrix.transpose() * elem.rest_volume;
let f1 = -h.col(0);
let f2 = -h.col(1);
let f3 = -h.col(2);
let f0 = -(f1 + f2 + f3);
acc_forces[i0] += f0;
acc_forces[i1] += f1;
acc_forces[i2] += f2;
acc_forces[i3] += f3;
acc_forces
},
)
.reduce(
|| vec![Vec3::ZERO; num_nodes],
|mut a, b| {
for i in 0..num_nodes {
a[i] += b[i];
}
a
},
);
for i in 0..num_nodes {
forces[i] += elastic_forces[i];
}
for (i, node) in self.nodes.iter_mut().enumerate() {
if node.is_fixed {
continue;
}
let acceleration = forces[i] / node.mass;
node.velocity += acceleration * dt;
node.velocity *= self.damping.powf(dt);
let next_pos = node.position + node.velocity * dt;
let (new_pos, new_vel, collided) =
resolve_node_collision(node.position, node.velocity, dt, rigid_colliders);
if collided {
node.position = new_pos;
node.velocity = new_vel;
} else {
node.position = next_pos;
}
}
}
}