#![allow(clippy::needless_range_loop)]
use super::math_helpers::{det3x3, edge_matrix_raw, inv3x3, mul3x3, transpose3x3};
#[derive(Debug, Clone)]
#[allow(dead_code)]
pub struct CorotFemNode {
pub position: [f64; 3],
pub velocity: [f64; 3],
pub force: [f64; 3],
pub mass: f64,
}
#[derive(Debug, Clone)]
#[allow(dead_code)]
pub struct CorotFemTet {
pub node_indices: [usize; 4],
pub rest_positions: [[f64; 3]; 4],
pub volume: f64,
pub dm_inv: [[f64; 3]; 3],
}
#[allow(dead_code)]
impl CorotFemTet {
pub fn new(node_indices: [usize; 4], nodes: &[CorotFemNode]) -> Self {
let p: [[f64; 3]; 4] = [
nodes[node_indices[0]].position,
nodes[node_indices[1]].position,
nodes[node_indices[2]].position,
nodes[node_indices[3]].position,
];
let dm = edge_matrix_raw(p[0], p[1], p[2], p[3]);
let det = det3x3(dm);
let volume = det.abs() / 6.0;
let dm_inv = inv3x3(dm);
Self {
node_indices,
rest_positions: p,
volume,
dm_inv,
}
}
pub fn compute_deformation_gradient(&self, nodes: &[CorotFemNode]) -> [[f64; 3]; 3] {
let p0 = nodes[self.node_indices[0]].position;
let p1 = nodes[self.node_indices[1]].position;
let p2 = nodes[self.node_indices[2]].position;
let p3 = nodes[self.node_indices[3]].position;
let ds = edge_matrix_raw(p0, p1, p2, p3);
mul3x3(ds, self.dm_inv)
}
pub fn polar_decompose_rotation(f: [[f64; 3]; 3]) -> [[f64; 3]; 3] {
let mut r = f;
for _ in 0..5 {
let det = det3x3(r);
if det.abs() < 1e-30 {
return [[1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0]];
}
let inv = inv3x3(r);
let inv_t = transpose3x3(inv);
for i in 0..3 {
for j in 0..3 {
r[i][j] = (r[i][j] + inv_t[i][j]) * 0.5;
}
}
}
if det3x3(r) < 0.0 {
for i in 0..3 {
for j in 0..3 {
r[i][j] = -r[i][j];
}
}
}
r
}
#[allow(clippy::too_many_arguments)]
pub fn compute_elastic_forces(
&self,
nodes: &[CorotFemNode],
mu: f64,
lambda: f64,
) -> [[f64; 3]; 4] {
let f = self.compute_deformation_gradient(nodes);
let r = Self::polar_decompose_rotation(f);
let rt = transpose3x3(r);
let rtf = mul3x3(rt, f);
let mut strain = [[0.0f64; 3]; 3];
for i in 0..3 {
for j in 0..3 {
let delta = if i == j { 1.0 } else { 0.0 };
strain[i][j] = rtf[i][j] - delta;
}
}
let trace = strain[0][0] + strain[1][1] + strain[2][2];
let mut stress = [[0.0f64; 3]; 3];
for i in 0..3 {
for j in 0..3 {
let delta = if i == j { 1.0 } else { 0.0 };
stress[i][j] = 2.0 * mu * strain[i][j] + lambda * trace * delta;
}
}
let piola = mul3x3(r, stress);
let dm_inv_t = transpose3x3(self.dm_inv);
let h = mul3x3(piola, dm_inv_t);
let mut forces = [[0.0f64; 3]; 4];
for i in 0..3 {
forces[1][i] = -self.volume * h[i][0];
forces[2][i] = -self.volume * h[i][1];
forces[3][i] = -self.volume * h[i][2];
forces[0][i] = -(forces[1][i] + forces[2][i] + forces[3][i]);
}
forces
}
}
#[derive(Debug, Clone)]
#[allow(dead_code)]
pub struct CorotFemSim {
pub nodes: Vec<CorotFemNode>,
pub tets: Vec<CorotFemTet>,
pub mu: f64,
pub lambda: f64,
pub dt: f64,
}
#[allow(dead_code)]
impl CorotFemSim {
pub fn new(
nodes: Vec<CorotFemNode>,
tets: Vec<CorotFemTet>,
mu: f64,
lambda: f64,
dt: f64,
) -> Self {
Self {
nodes,
tets,
mu,
lambda,
dt,
}
}
pub fn step(&mut self) {
for n in &mut self.nodes {
n.force = [0.0; 3];
}
for tet in &self.tets {
let fs = tet.compute_elastic_forces(&self.nodes, self.mu, self.lambda);
for k in 0..4 {
let idx = tet.node_indices[k];
for d in 0..3 {
self.nodes[idx].force[d] += fs[k][d];
}
}
}
let dt = self.dt;
for n in &mut self.nodes {
let inv_m = 1.0 / n.mass;
for d in 0..3 {
n.velocity[d] += n.force[d] * inv_m * dt;
n.position[d] += n.velocity[d] * dt;
}
}
}
pub fn total_strain_energy(&self) -> f64 {
let mut energy = 0.0;
for tet in &self.tets {
let f = tet.compute_deformation_gradient(&self.nodes);
let r = CorotFemTet::polar_decompose_rotation(f);
let rt = transpose3x3(r);
let rtf = mul3x3(rt, f);
let mut strain = [[0.0f64; 3]; 3];
for i in 0..3 {
for j in 0..3 {
let delta = if i == j { 1.0 } else { 0.0 };
strain[i][j] = rtf[i][j] - delta;
}
}
let trace = strain[0][0] + strain[1][1] + strain[2][2];
let mut frobenius_sq = 0.0;
for i in 0..3 {
for j in 0..3 {
frobenius_sq += strain[i][j] * strain[i][j];
}
}
energy += tet.volume * (self.mu * frobenius_sq + 0.5 * self.lambda * trace * trace);
}
energy
}
}
pub type CorotTet = CorotFemTet;
#[derive(Debug, Clone)]
#[allow(dead_code)]
pub struct NewmarkBetaSim {
pub nodes: Vec<CorotFemNode>,
pub tets: Vec<CorotFemTet>,
pub mu: f64,
pub lambda: f64,
pub dt: f64,
pub beta: f64,
pub gamma: f64,
pub prev_accel: Vec<[f64; 3]>,
pub pinned: Vec<bool>,
pub rayleigh_alpha: f64,
pub rayleigh_beta: f64,
}
#[allow(dead_code)]
impl NewmarkBetaSim {
pub fn new(
nodes: Vec<CorotFemNode>,
tets: Vec<CorotFemTet>,
mu: f64,
lambda: f64,
dt: f64,
) -> Self {
let n = nodes.len();
Self {
nodes,
tets,
mu,
lambda,
dt,
beta: 0.25,
gamma: 0.5,
prev_accel: vec![[0.0; 3]; n],
pinned: vec![false; n],
rayleigh_alpha: 0.0,
rayleigh_beta: 0.0,
}
}
#[allow(clippy::too_many_arguments)]
pub fn with_params(
nodes: Vec<CorotFemNode>,
tets: Vec<CorotFemTet>,
mu: f64,
lambda: f64,
dt: f64,
beta: f64,
gamma: f64,
rayleigh_alpha: f64,
rayleigh_beta: f64,
) -> Self {
let n = nodes.len();
Self {
nodes,
tets,
mu,
lambda,
dt,
beta,
gamma,
prev_accel: vec![[0.0; 3]; n],
pinned: vec![false; n],
rayleigh_alpha,
rayleigh_beta,
}
}
fn accumulate_forces(&mut self) {
let n = self.nodes.len();
for node in &mut self.nodes {
node.force = [0.0; 3];
}
for tet in &self.tets {
let fs = tet.compute_elastic_forces(&self.nodes, self.mu, self.lambda);
for k in 0..4 {
let idx = tet.node_indices[k];
for d in 0..3 {
self.nodes[idx].force[d] += fs[k][d];
}
}
}
if self.rayleigh_alpha.abs() > 1e-30 {
for i in 0..n {
if self.pinned[i] {
continue;
}
let m = self.nodes[i].mass;
let v = self.nodes[i].velocity;
for d in 0..3 {
self.nodes[i].force[d] -= self.rayleigh_alpha * m * v[d];
}
}
}
}
pub fn step(&mut self) {
let dt = self.dt;
let beta = self.beta;
let gamma = self.gamma;
let n = self.nodes.len();
let pred_positions: Vec<[f64; 3]> = (0..n)
.map(|i| {
let mut pos = [0.0f64; 3];
if self.pinned[i] {
return self.nodes[i].position;
}
let v = self.nodes[i].velocity;
let a = self.prev_accel[i];
for d in 0..3 {
pos[d] = self.nodes[i].position[d] + dt * v[d] + dt * dt * (0.5 - beta) * a[d];
}
pos
})
.collect();
for i in 0..n {
if !self.pinned[i] {
self.nodes[i].position = pred_positions[i];
}
}
self.accumulate_forces();
let new_accel: Vec<[f64; 3]> = (0..n)
.map(|i| {
if self.pinned[i] {
return [0.0; 3];
}
let inv_m = 1.0 / self.nodes[i].mass;
let f = self.nodes[i].force;
[f[0] * inv_m, f[1] * inv_m, f[2] * inv_m]
})
.collect();
for i in 0..n {
if self.pinned[i] {
continue;
}
let a_n = self.prev_accel[i];
let a_new = new_accel[i];
for d in 0..3 {
self.nodes[i].velocity[d] += dt * (1.0 - gamma) * a_n[d] + dt * gamma * a_new[d];
self.nodes[i].position[d] += dt * dt * beta * a_new[d];
}
}
self.prev_accel = new_accel;
}
pub fn total_strain_energy(&self) -> f64 {
let mut energy = 0.0;
for tet in &self.tets {
let f = tet.compute_deformation_gradient(&self.nodes);
let r = CorotFemTet::polar_decompose_rotation(f);
let rt = transpose3x3(r);
let rtf = mul3x3(rt, f);
let mut strain = [[0.0f64; 3]; 3];
for i in 0..3 {
for j in 0..3 {
let delta = if i == j { 1.0 } else { 0.0 };
strain[i][j] = rtf[i][j] - delta;
}
}
let trace = strain[0][0] + strain[1][1] + strain[2][2];
let mut frob_sq = 0.0;
for i in 0..3 {
for j in 0..3 {
frob_sq += strain[i][j] * strain[i][j];
}
}
energy += tet.volume * (self.mu * frob_sq + 0.5 * self.lambda * trace * trace);
}
energy
}
pub fn total_kinetic_energy(&self) -> f64 {
let mut ke = 0.0;
for (i, node) in self.nodes.iter().enumerate() {
if self.pinned[i] {
continue;
}
let v = node.velocity;
ke += 0.5 * node.mass * (v[0] * v[0] + v[1] * v[1] + v[2] * v[2]);
}
ke
}
pub fn total_mechanical_energy(&self) -> f64 {
self.total_kinetic_energy() + self.total_strain_energy()
}
pub fn pin_node(&mut self, idx: usize) {
if idx < self.pinned.len() {
self.pinned[idx] = true;
self.nodes[idx].velocity = [0.0; 3];
}
}
pub fn unpin_node(&mut self, idx: usize) {
if idx < self.pinned.len() {
self.pinned[idx] = false;
}
}
pub fn apply_impulse(&mut self, idx: usize, impulse: [f64; 3]) {
if idx < self.nodes.len() && !self.pinned[idx] {
let inv_m = 1.0 / self.nodes[idx].mass;
for d in 0..3 {
self.nodes[idx].velocity[d] += impulse[d] * inv_m;
}
}
}
pub fn active_node_count(&self) -> usize {
self.pinned.iter().filter(|&&p| !p).count()
}
}
#[derive(Debug, Clone)]
#[allow(dead_code)]
#[allow(non_snake_case)]
pub struct CorotationalTet {
pub indices: [usize; 4],
pub Dm: [[f64; 3]; 3],
pub V0: f64,
dm_inv: [[f64; 3]; 3],
}
#[allow(dead_code)]
#[allow(non_snake_case)]
impl CorotationalTet {
pub fn new(indices: [usize; 4], positions: &[[f64; 3]]) -> Self {
let p0 = positions[indices[0]];
let p1 = positions[indices[1]];
let p2 = positions[indices[2]];
let p3 = positions[indices[3]];
let Dm = edge_matrix_raw(p0, p1, p2, p3);
let V0 = det3x3(Dm).abs() / 6.0;
let dm_inv = inv3x3(Dm);
Self {
indices,
Dm,
V0,
dm_inv,
}
}
pub fn compute_deformation_gradient(&self, nodes: &[[f64; 3]]) -> [[f64; 3]; 3] {
let p0 = nodes[self.indices[0]];
let p1 = nodes[self.indices[1]];
let p2 = nodes[self.indices[2]];
let p3 = nodes[self.indices[3]];
let ds = edge_matrix_raw(p0, p1, p2, p3);
mul3x3(ds, self.dm_inv)
}
pub fn polar_decompose(F: &[[f64; 3]; 3]) -> ([[f64; 3]; 3], [[f64; 3]; 3]) {
let mut r = *F;
for _ in 0..10 {
let det = det3x3(r);
if det.abs() < 1e-30 {
let r_id = [[1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0]];
return (r_id, *F);
}
let inv = inv3x3(r);
let inv_t = transpose3x3(inv);
for i in 0..3 {
for j in 0..3 {
r[i][j] = (r[i][j] + inv_t[i][j]) * 0.5;
}
}
}
if det3x3(r) < 0.0 {
for row in r.iter_mut() {
for x in row.iter_mut() {
*x = -*x;
}
}
}
let rt = transpose3x3(r);
let s = mul3x3(rt, *F);
(r, s)
}
#[allow(clippy::too_many_arguments)]
pub fn elastic_forces(&self, nodes: &[[f64; 3]], mu: f64, lambda: f64) -> [[f64; 3]; 4] {
let f = self.compute_deformation_gradient(nodes);
let (r, _s) = Self::polar_decompose(&f);
let rt = transpose3x3(r);
let rtf = mul3x3(rt, f);
let mut strain = [[0.0f64; 3]; 3];
for i in 0..3 {
for j in 0..3 {
let delta = if i == j { 1.0 } else { 0.0 };
strain[i][j] = rtf[i][j] - delta;
}
}
let trace = strain[0][0] + strain[1][1] + strain[2][2];
let mut stress = [[0.0f64; 3]; 3];
for i in 0..3 {
for j in 0..3 {
let delta = if i == j { 1.0 } else { 0.0 };
stress[i][j] = 2.0 * mu * strain[i][j] + lambda * trace * delta;
}
}
let piola = mul3x3(r, stress);
let dm_inv_t = transpose3x3(self.dm_inv);
let h = mul3x3(piola, dm_inv_t);
let mut forces = [[0.0f64; 3]; 4];
for i in 0..3 {
forces[1][i] = -self.V0 * h[i][0];
forces[2][i] = -self.V0 * h[i][1];
forces[3][i] = -self.V0 * h[i][2];
forces[0][i] = -(forces[1][i] + forces[2][i] + forces[3][i]);
}
forces
}
pub fn stiffness_matrix(&self, nodes: &[[f64; 3]], mu: f64, lambda: f64) -> Vec<f64> {
let h = 1e-6;
let dof = 12; let mut k = vec![0.0_f64; dof * dof];
let f0 = self.elastic_forces(nodes, mu, lambda);
for col_node in 0..4 {
for col_dim in 0..3 {
let col = col_node * 3 + col_dim;
let mut nodes_p = nodes.to_vec();
nodes_p[self.indices[col_node]][col_dim] += h;
let f1 = self.elastic_forces(&nodes_p, mu, lambda);
for row_node in 0..4 {
for row_dim in 0..3 {
let row = row_node * 3 + row_dim;
k[row * dof + col] = (f1[row_node][row_dim] - f0[row_node][row_dim]) / h;
}
}
}
}
k
}
}
#[derive(Debug, Clone)]
#[allow(dead_code)]
#[allow(non_snake_case)]
pub struct HighLevelFemBody {
pub positions: Vec<[f64; 3]>,
pub velocities: Vec<[f64; 3]>,
pub masses: Vec<f64>,
pub elements: Vec<CorotationalTet>,
pub E: f64,
pub nu: f64,
mu: f64,
lambda: f64,
}
#[allow(dead_code)]
#[allow(non_snake_case)]
impl HighLevelFemBody {
pub fn new(positions: Vec<[f64; 3]>, masses: Vec<f64>, E: f64, nu: f64) -> Self {
let n = positions.len();
let mu = E / (2.0 * (1.0 + nu));
let lambda = E * nu / ((1.0 + nu) * (1.0 - 2.0 * nu));
Self {
positions,
velocities: vec![[0.0; 3]; n],
masses,
elements: Vec::new(),
E,
nu,
mu,
lambda,
}
}
pub fn add_tet(&mut self, i: usize, j: usize, k: usize, l: usize) {
let tet = CorotationalTet::new([i, j, k, l], &self.positions);
self.elements.push(tet);
}
pub fn step(&mut self, dt: f64) {
let n = self.positions.len();
let mut forces = vec![[0.0f64; 3]; n];
for elem in &self.elements {
let f = elem.elastic_forces(&self.positions, self.mu, self.lambda);
for (k, &node_idx) in elem.indices.iter().enumerate() {
for d in 0..3 {
forces[node_idx][d] += f[k][d];
}
}
}
for i in 0..n {
let inv_m = 1.0 / self.masses[i];
for d in 0..3 {
self.velocities[i][d] += forces[i][d] * inv_m * dt;
self.positions[i][d] += self.velocities[i][d] * dt;
}
}
}
pub fn apply_gravity(&mut self, gravity: [f64; 3], dt: f64) {
for i in 0..self.positions.len() {
for d in 0..3 {
self.velocities[i][d] += gravity[d] * dt;
}
}
}
pub fn total_elastic_energy(&self) -> f64 {
let mut energy = 0.0;
for elem in &self.elements {
let f = elem.compute_deformation_gradient(&self.positions);
let (r, _) = CorotationalTet::polar_decompose(&f);
let rt = transpose3x3(r);
let rtf = mul3x3(rt, f);
let mut strain = [[0.0f64; 3]; 3];
for i in 0..3 {
for j in 0..3 {
let delta = if i == j { 1.0 } else { 0.0 };
strain[i][j] = rtf[i][j] - delta;
}
}
let trace = strain[0][0] + strain[1][1] + strain[2][2];
let mut frob_sq = 0.0;
for row in strain.iter() {
for &x in row.iter() {
frob_sq += x * x;
}
}
energy += elem.V0 * (self.mu * frob_sq + 0.5 * self.lambda * trace * trace);
}
energy
}
}