#![allow(clippy::needless_range_loop, clippy::ptr_arg)]
#[allow(unused_imports)]
use super::functions::*;
#[allow(dead_code)]
#[derive(Clone, Debug)]
pub struct ImplicitSpring {
pub i: usize,
pub j: usize,
pub rest_length: f64,
pub stiffness: f64,
pub damping: f64,
}
impl ImplicitSpring {
pub fn new(i: usize, j: usize, rest_length: f64, stiffness: f64, damping: f64) -> Self {
ImplicitSpring {
i,
j,
rest_length,
stiffness,
damping,
}
}
pub fn potential_energy(&self, particles: &[ImplicitParticle]) -> f64 {
let pi = &particles[self.i].position;
let pj = &particles[self.j].position;
let diff = v3_sub(*pi, *pj);
let len = v3_norm(diff);
let stretch = len - self.rest_length;
0.5 * self.stiffness * stretch * stretch
}
pub fn force_on_i(&self, particles: &[ImplicitParticle]) -> [f64; 3] {
let pi = &particles[self.i].position;
let pj = &particles[self.j].position;
let vi = &particles[self.i].velocity;
let vj = &particles[self.j].velocity;
let diff = v3_sub(*pi, *pj);
let len = v3_norm(diff);
if len < 1e-15 {
return [0.0; 3];
}
let dir = v3_scale(diff, 1.0 / len);
let stretch = len - self.rest_length;
let rel_vel = v3_dot(v3_sub(*vi, *vj), dir);
let force_mag = -self.stiffness * stretch - self.damping * rel_vel;
v3_scale(dir, force_mag)
}
}
#[allow(dead_code)]
pub struct ProjectiveConstraint {
pub kind: ProjectiveConstraintKind,
}
impl ProjectiveConstraint {
pub fn spring(i: usize, j: usize, rest_length: f64, weight: f64) -> Self {
ProjectiveConstraint {
kind: ProjectiveConstraintKind::Spring {
i,
j,
rest_length,
weight,
},
}
}
pub fn anchor(i: usize, target: [f64; 3], weight: f64) -> Self {
ProjectiveConstraint {
kind: ProjectiveConstraintKind::Anchor { i, target, weight },
}
}
pub fn local_step(&self, positions: &[[f64; 3]]) -> Vec<[f64; 3]> {
match &self.kind {
ProjectiveConstraintKind::Spring {
i, j, rest_length, ..
} => {
let pi = positions[*i];
let pj = positions[*j];
let diff = v3_sub(pi, pj);
let len = v3_norm(diff);
if len < 1e-15 {
return vec![pi, pj];
}
let dir = v3_scale(diff, 1.0 / len);
let half = v3_scale(dir, 0.5 * rest_length);
let mid = v3_scale(v3_add(pi, pj), 0.5);
vec![v3_add(mid, half), v3_sub(mid, half)]
}
ProjectiveConstraintKind::Anchor { i: _, target, .. } => vec![*target],
}
}
}
#[allow(dead_code)]
pub struct LineSearch {
pub c1: f64,
pub max_iter: usize,
pub rho: f64,
}
impl LineSearch {
pub fn backtrack<F>(&self, f0: f64, grad: &[f64], f: F) -> f64
where
F: Fn(f64) -> f64,
{
let mut alpha = 1.0;
let derphi0 = -vec_dot(grad, grad);
for _ in 0..self.max_iter {
let f_new = f(alpha);
if f_new <= f0 + self.c1 * alpha * derphi0 {
return alpha;
}
alpha *= self.rho;
}
alpha
}
}
#[allow(dead_code)]
pub struct FilterLineSearch {
pub tau_max: f64,
pub c1: f64,
pub rho: f64,
pub max_iter: usize,
}
impl FilterLineSearch {
pub fn ccd_step_fraction(
&self,
_x: &[[f64; 3]],
_d: &[[f64; 3]],
distances: &[f64],
distance_gradients: &[f64],
) -> f64 {
let mut alpha = self.tau_max;
for (&dist, &grad) in distances.iter().zip(distance_gradients.iter()) {
if grad < 0.0 {
let t = -0.9 * dist / grad;
if t < alpha {
alpha = t;
}
}
}
alpha.max(1e-10)
}
pub fn search<F>(&self, alpha_ccd: f64, f0: f64, grad: &[f64], f: F) -> f64
where
F: Fn(f64) -> f64,
{
let mut alpha = alpha_ccd.min(1.0);
let derphi0 = -vec_dot(grad, grad);
for _ in 0..self.max_iter {
let f_new = f(alpha);
if f_new <= f0 + self.c1 * alpha * derphi0 {
return alpha;
}
alpha *= self.rho;
}
alpha
}
}
#[allow(dead_code)]
pub struct FastShapeMatchingConstraint {
pub rest_positions: Vec<[f64; 3]>,
pub masses: Vec<f64>,
pub weight: f64,
}
impl FastShapeMatchingConstraint {
pub fn new(rest_positions: Vec<[f64; 3]>, masses: Vec<f64>, weight: f64) -> Self {
FastShapeMatchingConstraint {
rest_positions,
masses,
weight,
}
}
pub fn center_of_mass(positions: &[[f64; 3]], masses: &[f64]) -> [f64; 3] {
let total_mass: f64 = masses.iter().sum();
if total_mass < 1e-15 {
return [0.0; 3];
}
let mut com = [0.0f64; 3];
for (p, &m) in positions.iter().zip(masses.iter()) {
com = v3_add(com, v3_scale(*p, m));
}
v3_scale(com, 1.0 / total_mass)
}
fn covariance_matrix(
positions: &[[f64; 3]],
rest_positions: &[[f64; 3]],
masses: &[f64],
com_cur: [f64; 3],
com_rest: [f64; 3],
) -> [[f64; 3]; 3] {
let mut apq = [[0.0f64; 3]; 3];
for ((p, r), &m) in positions
.iter()
.zip(rest_positions.iter())
.zip(masses.iter())
{
let qi = v3_sub(*p, com_cur);
let ri = v3_sub(*r, com_rest);
for row in 0..3 {
for col in 0..3 {
apq[row][col] += m * qi[row] * ri[col];
}
}
}
apq
}
fn polar_decomp_rotation(a: [[f64; 3]; 3]) -> [[f64; 3]; 3] {
let id = [[1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0]];
let det0 = mat3_det(a);
if det0.abs() < 1e-10 {
return id;
}
let mut r = a;
for _ in 0..20 {
let det = mat3_det(r);
if det.abs() < 1e-15 {
return id;
}
let inv_t = mat3_transpose(mat3_inv_scale(r, det));
let mut converged = true;
for i in 0..3 {
for j in 0..3 {
let new_val = 0.5 * (r[i][j] + inv_t[i][j]);
if (new_val - r[i][j]).abs() > 1e-10 {
converged = false;
}
r[i][j] = new_val;
}
}
if converged {
break;
}
}
r
}
pub fn goal_positions(&self, positions: &[[f64; 3]]) -> Vec<[f64; 3]> {
let com_cur = Self::center_of_mass(positions, &self.masses);
let com_rest = Self::center_of_mass(&self.rest_positions, &self.masses);
let apq = Self::covariance_matrix(
positions,
&self.rest_positions,
&self.masses,
com_cur,
com_rest,
);
let r = Self::polar_decomp_rotation(apq);
positions
.iter()
.zip(self.rest_positions.iter())
.map(|(_p, r_rest)| {
let rel = v3_sub(*r_rest, com_rest);
let rotated = mat3_mul_v3(r, rel);
v3_add(com_cur, rotated)
})
.collect()
}
}
#[allow(dead_code)]
pub struct PcgResult {
pub x: Vec<f64>,
pub residual_norm: f64,
pub iterations: usize,
pub converged: bool,
}
#[allow(dead_code)]
pub struct BackwardEulerIntegrator {
pub max_newton_iter: usize,
pub newton_tol: f64,
pub pcg: PcgSolver,
}
impl BackwardEulerIntegrator {
pub fn new(max_newton_iter: usize, newton_tol: f64) -> Self {
BackwardEulerIntegrator {
max_newton_iter,
newton_tol,
pcg: PcgSolver::new(PcgParams {
max_iter: 500,
tolerance: 1e-8,
}),
}
}
pub fn step(
&self,
particles: &mut Vec<ImplicitParticle>,
springs: &[ImplicitSpring],
gravity: [f64; 3],
dt: f64,
) -> usize {
let n = particles.len();
let dof = 3 * n;
let x_prev: Vec<[f64; 3]> = particles.iter().map(|p| p.position).collect();
let v_prev: Vec<[f64; 3]> = particles.iter().map(|p| p.velocity).collect();
let mut x_star: Vec<[f64; 3]> = x_prev
.iter()
.zip(v_prev.iter())
.map(|(x, v)| v3_add(*x, v3_scale(*v, dt)))
.collect();
for i in 0..n {
particles[i].position = x_star[i];
}
let mut iter_count = 0;
for _iter in 0..self.max_newton_iter {
iter_count += 1;
let mut residual = vec![0.0f64; dof];
for i in 0..n {
if particles[i].is_static() {
continue;
}
let m = particles[i].mass;
let v_new = v3_scale(v3_sub(particles[i].position, x_prev[i]), 1.0 / dt);
let inertia_force = v3_scale(v3_sub(v_new, v_prev[i]), m / dt);
let fg = v3_scale(gravity, m);
let mut fs = [0.0f64; 3];
for s in springs {
if s.i == i {
let f = s.force_on_i(particles);
fs = v3_add(fs, f);
} else if s.j == i {
let f = s.force_on_i(particles);
fs = v3_sub(fs, f);
}
}
let net = v3_add(fg, fs);
let g = v3_sub(inertia_force, net);
residual[3 * i] = g[0];
residual[3 * i + 1] = g[1];
residual[3 * i + 2] = g[2];
}
let res_norm = vec_norm(&residual);
if res_norm < self.newton_tol {
break;
}
let mut diag = vec![0.0f64; dof];
for i in 0..n {
if particles[i].is_static() {
diag[3 * i] = 1.0;
diag[3 * i + 1] = 1.0;
diag[3 * i + 2] = 1.0;
} else {
let m = particles[i].mass;
diag[3 * i] = m / (dt * dt);
diag[3 * i + 1] = m / (dt * dt);
diag[3 * i + 2] = m / (dt * dt);
}
}
for s in springs {
let k = s.stiffness;
for idx in [s.i, s.j] {
if !particles[idx].is_static() {
diag[3 * idx] += k;
diag[3 * idx + 1] += k;
diag[3 * idx + 2] += k;
}
}
}
let neg_res: Vec<f64> = residual.iter().map(|r| -r).collect();
let delta_x: Vec<f64> = neg_res
.iter()
.zip(diag.iter())
.map(|(r, d)| r / d.max(1e-15))
.collect();
for i in 0..n {
if particles[i].is_static() {
continue;
}
x_star[i] = v3_add(
x_star[i],
[delta_x[3 * i], delta_x[3 * i + 1], delta_x[3 * i + 2]],
);
particles[i].position = x_star[i];
}
}
for i in 0..n {
if !particles[i].is_static() {
particles[i].velocity =
v3_scale(v3_sub(particles[i].position, x_prev[i]), 1.0 / dt);
}
}
iter_count
}
}
#[allow(dead_code)]
pub struct NewtonRaphsonSolver {
pub max_iter: usize,
pub tolerance: f64,
pub pcg_params: PcgParams,
pub line_search: LineSearch,
}
impl NewtonRaphsonSolver {
pub fn new(max_iter: usize, tolerance: f64) -> Self {
NewtonRaphsonSolver {
max_iter,
tolerance,
pcg_params: PcgParams::default(),
line_search: LineSearch::default(),
}
}
pub fn minimise(
&self,
particles: &mut Vec<ImplicitParticle>,
springs: &[ImplicitSpring],
inertia_target: &[[f64; 3]],
dt: f64,
) -> usize {
let n = particles.len();
let mut iter_count = 0;
for _iter in 0..self.max_iter {
iter_count += 1;
let mut grad = vec![0.0f64; 3 * n];
for i in 0..n {
if particles[i].is_static() {
continue;
}
let m = particles[i].mass;
let diff = v3_sub(particles[i].position, inertia_target[i]);
grad[3 * i] += m / (dt * dt) * diff[0];
grad[3 * i + 1] += m / (dt * dt) * diff[1];
grad[3 * i + 2] += m / (dt * dt) * diff[2];
}
for s in springs {
if particles[s.i].is_static() && particles[s.j].is_static() {
continue;
}
let pi = particles[s.i].position;
let pj = particles[s.j].position;
let diff = v3_sub(pi, pj);
let len = v3_norm(diff);
if len < 1e-15 {
continue;
}
let dir = v3_scale(diff, 1.0 / len);
let stretch = len - s.rest_length;
let fg = v3_scale(dir, s.stiffness * stretch);
if !particles[s.i].is_static() {
grad[3 * s.i] += fg[0];
grad[3 * s.i + 1] += fg[1];
grad[3 * s.i + 2] += fg[2];
}
if !particles[s.j].is_static() {
grad[3 * s.j] -= fg[0];
grad[3 * s.j + 1] -= fg[1];
grad[3 * s.j + 2] -= fg[2];
}
}
let grad_norm = vec_norm(&grad);
if grad_norm < self.tolerance {
break;
}
let mut diag = vec![1.0f64; 3 * n];
for i in 0..n {
if particles[i].is_static() {
continue;
}
let base = particles[i].mass / (dt * dt);
diag[3 * i] = base;
diag[3 * i + 1] = base;
diag[3 * i + 2] = base;
}
for s in springs {
let k = s.stiffness;
for idx in [s.i, s.j] {
if !particles[idx].is_static() {
diag[3 * idx] += k;
diag[3 * idx + 1] += k;
diag[3 * idx + 2] += k;
}
}
}
let direction: Vec<f64> = grad
.iter()
.zip(diag.iter())
.map(|(g, d)| -g / d.max(1e-15))
.collect();
let current_energy = self.total_energy(particles, springs, inertia_target, dt);
let derphi0 = vec_dot(&grad, &direction);
let c1 = self.line_search.c1;
let rho = self.line_search.rho;
let mut alpha = 1.0_f64;
for _ in 0..self.line_search.max_iter {
let mut test_particles = particles.clone();
for i in 0..n {
if test_particles[i].is_static() {
continue;
}
let dp = [
direction[3 * i] * alpha,
direction[3 * i + 1] * alpha,
direction[3 * i + 2] * alpha,
];
test_particles[i].position = v3_add(test_particles[i].position, dp);
}
let f_new = self.total_energy(&test_particles, springs, inertia_target, dt);
if f_new <= current_energy + c1 * alpha * derphi0 {
break;
}
alpha *= rho;
}
for i in 0..n {
if particles[i].is_static() {
continue;
}
let dp = [
direction[3 * i] * alpha,
direction[3 * i + 1] * alpha,
direction[3 * i + 2] * alpha,
];
particles[i].position = v3_add(particles[i].position, dp);
}
}
iter_count
}
fn total_energy(
&self,
particles: &[ImplicitParticle],
springs: &[ImplicitSpring],
inertia_target: &[[f64; 3]],
dt: f64,
) -> f64 {
let mut e = 0.0;
for (i, p) in particles.iter().enumerate() {
if p.is_static() {
continue;
}
let diff = v3_sub(p.position, inertia_target[i]);
e += 0.5 * p.mass / (dt * dt) * v3_dot(diff, diff);
}
for s in springs {
e += s.potential_energy(particles);
}
e
}
}
#[allow(dead_code)]
#[derive(Clone, Debug)]
pub enum ProjectiveConstraintKind {
Spring {
i: usize,
j: usize,
rest_length: f64,
weight: f64,
},
Anchor {
i: usize,
target: [f64; 3],
weight: f64,
},
}
#[allow(dead_code)]
pub struct CsrMatrix {
pub nrows: usize,
pub ncols: usize,
pub row_ptr: Vec<usize>,
pub col_idx: Vec<usize>,
pub values: Vec<f64>,
}
impl CsrMatrix {
pub fn from_coo(
nrows: usize,
ncols: usize,
rows: &[usize],
cols: &[usize],
vals: &[f64],
) -> Self {
assert_eq!(rows.len(), cols.len());
assert_eq!(rows.len(), vals.len());
let mut row_count = vec![0usize; nrows];
for &r in rows {
row_count[r] += 1;
}
let mut row_ptr = vec![0usize; nrows + 1];
for i in 0..nrows {
row_ptr[i + 1] = row_ptr[i] + row_count[i];
}
let nnz = row_ptr[nrows];
let mut col_idx = vec![0usize; nnz];
let mut values = vec![0.0f64; nnz];
let mut cursor = row_ptr.clone();
for k in 0..rows.len() {
let r = rows[k];
let pos = cursor[r];
col_idx[pos] = cols[k];
values[pos] = vals[k];
cursor[r] += 1;
}
let mut m = CsrMatrix {
nrows,
ncols,
row_ptr,
col_idx,
values,
};
m.sort_and_sum_duplicates();
m
}
fn sort_and_sum_duplicates(&mut self) {
let nrows = self.nrows;
let mut new_col: Vec<usize> = Vec::new();
let mut new_val: Vec<f64> = Vec::new();
let mut new_ptr = vec![0usize; nrows + 1];
for r in 0..nrows {
let start = self.row_ptr[r];
let end = self.row_ptr[r + 1];
let mut pairs: Vec<(usize, f64)> = self.col_idx[start..end]
.iter()
.zip(self.values[start..end].iter())
.map(|(&c, &v)| (c, v))
.collect();
pairs.sort_by_key(|p| p.0);
let mut j = 0;
while j < pairs.len() {
let c = pairs[j].0;
let mut v = pairs[j].1;
let mut k = j + 1;
while k < pairs.len() && pairs[k].0 == c {
v += pairs[k].1;
k += 1;
}
new_col.push(c);
new_val.push(v);
j = k;
}
new_ptr[r + 1] = new_col.len();
}
self.row_ptr = new_ptr;
self.col_idx = new_col;
self.values = new_val;
}
pub fn matvec(&self, x: &[f64]) -> Vec<f64> {
assert_eq!(x.len(), self.ncols);
let mut y = vec![0.0f64; self.nrows];
for r in 0..self.nrows {
let start = self.row_ptr[r];
let end = self.row_ptr[r + 1];
for k in start..end {
y[r] += self.values[k] * x[self.col_idx[k]];
}
}
y
}
pub fn diagonal(&self) -> Vec<f64> {
let mut d = vec![0.0f64; self.nrows];
for r in 0..self.nrows {
let start = self.row_ptr[r];
let end = self.row_ptr[r + 1];
for k in start..end {
if self.col_idx[k] == r {
d[r] = self.values[k];
}
}
}
d
}
pub fn jacobi_preconditioner(&self) -> Vec<f64> {
self.diagonal()
.iter()
.map(|&d| if d.abs() > 1e-15 { 1.0 / d } else { 1.0 })
.collect()
}
}
#[allow(dead_code)]
#[derive(Clone, Debug)]
pub struct ImplicitParticle {
pub position: [f64; 3],
pub velocity: [f64; 3],
pub mass: f64,
pub external_force: [f64; 3],
}
impl ImplicitParticle {
pub fn new(position: [f64; 3], mass: f64) -> Self {
ImplicitParticle {
position,
velocity: [0.0; 3],
mass,
external_force: [0.0; 3],
}
}
pub fn new_static(position: [f64; 3]) -> Self {
ImplicitParticle {
position,
velocity: [0.0; 3],
mass: 0.0,
external_force: [0.0; 3],
}
}
pub fn is_static(&self) -> bool {
self.mass < 1e-15
}
}
#[allow(dead_code)]
pub struct PcgSolver {
pub params: PcgParams,
}
impl PcgSolver {
pub fn new(params: PcgParams) -> Self {
PcgSolver { params }
}
pub fn solve(&self, a: &CsrMatrix, b: &[f64], x0: Option<&[f64]>) -> PcgResult {
let n = b.len();
assert_eq!(a.nrows, n);
assert_eq!(a.ncols, n);
let m_inv = a.jacobi_preconditioner();
let mut x: Vec<f64> = match x0 {
Some(x0) => x0.to_vec(),
None => vec![0.0f64; n],
};
let ax = a.matvec(&x);
let mut r: Vec<f64> = b.iter().zip(ax.iter()).map(|(bi, axi)| bi - axi).collect();
let mut z: Vec<f64> = r.iter().zip(m_inv.iter()).map(|(ri, mi)| ri * mi).collect();
let mut p = z.clone();
let mut rz = vec_dot(&r, &z);
let b_norm = vec_norm(b);
let tol = if b_norm > 1e-15 {
self.params.tolerance * b_norm
} else {
self.params.tolerance
};
for iter in 0..self.params.max_iter {
let ap = a.matvec(&p);
let pap = vec_dot(&p, &ap);
if pap.abs() < 1e-30 {
break;
}
let alpha = rz / pap;
for i in 0..n {
x[i] += alpha * p[i];
}
for i in 0..n {
r[i] -= alpha * ap[i];
}
let res_norm = vec_norm(&r);
if res_norm < tol {
return PcgResult {
x,
residual_norm: res_norm,
iterations: iter + 1,
converged: true,
};
}
for i in 0..n {
z[i] = r[i] * m_inv[i];
}
let rz_new = vec_dot(&r, &z);
let beta = rz_new / rz.max(1e-30);
rz = rz_new;
for i in 0..n {
p[i] = z[i] + beta * p[i];
}
}
let res_norm = vec_norm(&r);
PcgResult {
x,
residual_norm: res_norm,
iterations: self.params.max_iter,
converged: false,
}
}
}
#[allow(dead_code)]
pub struct ProjectiveDynamicsSolver {
pub n_particles: usize,
pub masses: Vec<f64>,
pub constraints: Vec<ProjectiveConstraint>,
pub dt: f64,
pub n_iterations: usize,
}
impl ProjectiveDynamicsSolver {
pub fn new(n_particles: usize, masses: Vec<f64>, dt: f64, n_iterations: usize) -> Self {
ProjectiveDynamicsSolver {
n_particles,
masses,
constraints: Vec::new(),
dt,
n_iterations,
}
}
pub fn add_constraint(&mut self, c: ProjectiveConstraint) {
self.constraints.push(c);
}
pub fn inertia_target(
&self,
positions: &[[f64; 3]],
velocities: &[[f64; 3]],
gravity: [f64; 3],
) -> Vec<[f64; 3]> {
let dt = self.dt;
positions
.iter()
.zip(velocities.iter())
.zip(self.masses.iter())
.map(|((q, v), &m)| {
let inertia = v3_add(*q, v3_scale(*v, dt));
if m > 1e-15 {
v3_add(inertia, v3_scale(gravity, dt * dt))
} else {
*q
}
})
.collect()
}
pub fn step(
&self,
positions: &mut Vec<[f64; 3]>,
velocities: &mut Vec<[f64; 3]>,
gravity: [f64; 3],
) {
let n = self.n_particles;
let dt = self.dt;
let y = self.inertia_target(positions, velocities, gravity);
let mut q = y.clone();
for _iter in 0..self.n_iterations {
let mut rhs: Vec<[f64; 3]> = (0..n)
.map(|i| v3_scale(y[i], self.masses[i] / (dt * dt)))
.collect();
for c in &self.constraints {
let proj = c.local_step(&q);
match &c.kind {
ProjectiveConstraintKind::Spring { i, j, weight, .. } => {
let w_dt2 = weight / (dt * dt);
rhs[*i] = v3_add(rhs[*i], v3_scale(proj[0], w_dt2));
rhs[*j] = v3_add(rhs[*j], v3_scale(proj[1], w_dt2));
}
ProjectiveConstraintKind::Anchor { i, weight, .. } => {
let w_dt2 = weight / (dt * dt);
rhs[*i] = v3_add(rhs[*i], v3_scale(proj[0], w_dt2));
}
}
}
for i in 0..n {
let mut diag = self.masses[i] / (dt * dt);
for c in &self.constraints {
match &c.kind {
ProjectiveConstraintKind::Spring {
i: ci,
j: cj,
weight,
..
} => {
if *ci == i || *cj == i {
diag += weight / (dt * dt);
}
}
ProjectiveConstraintKind::Anchor { i: ci, weight, .. } => {
if *ci == i {
diag += weight / (dt * dt);
}
}
}
}
if diag > 1e-15 {
q[i] = v3_scale(rhs[i], 1.0 / diag);
}
}
}
for i in 0..n {
if self.masses[i] > 1e-15 {
velocities[i] = v3_scale(v3_sub(q[i], positions[i]), 1.0 / dt);
positions[i] = q[i];
}
}
}
}
#[allow(dead_code)]
pub struct IpcBarrierParams {
pub d_hat: f64,
pub kappa: f64,
}
impl IpcBarrierParams {
pub fn barrier(&self, d: f64) -> f64 {
if d <= 0.0 || d >= self.d_hat {
return 0.0;
}
let x = d / self.d_hat;
-(d - self.d_hat).powi(2) * x.ln()
}
pub fn barrier_gradient(&self, d: f64) -> f64 {
if d <= 0.0 || d >= self.d_hat {
return 0.0;
}
let dh = self.d_hat;
-2.0 * (d - dh) * (d / dh).ln() - (d - dh).powi(2) / d
}
pub fn barrier_hessian(&self, d: f64) -> f64 {
if d <= 0.0 || d >= self.d_hat {
return 0.0;
}
let dh = self.d_hat;
-2.0 * (d / dh).ln() - 2.0 * (d - dh) / d - 2.0 * (d - dh) / d + (d - dh).powi(2) / (d * d)
}
pub fn total_barrier_energy(&self, distances: &[f64]) -> f64 {
self.kappa * distances.iter().map(|&d| self.barrier(d)).sum::<f64>()
}
}
#[allow(dead_code)]
pub struct PcgParams {
pub max_iter: usize,
pub tolerance: f64,
}
#[allow(dead_code)]
pub struct MatrixFreeHessian {
pub springs: Vec<ImplicitSpring>,
pub masses: Vec<f64>,
pub dt: f64,
}
impl MatrixFreeHessian {
pub fn new(springs: Vec<ImplicitSpring>, masses: Vec<f64>, dt: f64) -> Self {
MatrixFreeHessian {
springs,
masses,
dt,
}
}
pub fn apply(&self, positions: &[[f64; 3]], v: &[f64]) -> Vec<f64> {
let n = positions.len();
let mut result = vec![0.0f64; 3 * n];
for i in 0..n {
let s = self.masses[i] / (self.dt * self.dt);
result[3 * i] += s * v[3 * i];
result[3 * i + 1] += s * v[3 * i + 1];
result[3 * i + 2] += s * v[3 * i + 2];
}
for s in &self.springs {
let pi = positions[s.i];
let pj = positions[s.j];
let diff = v3_sub(pi, pj);
let len = v3_norm(diff);
if len < 1e-15 {
continue;
}
let d = v3_scale(diff, 1.0 / len);
let ratio = s.rest_length / len;
let vi = [v[3 * s.i], v[3 * s.i + 1], v[3 * s.i + 2]];
let vj = [v[3 * s.j], v[3 * s.j + 1], v[3 * s.j + 2]];
let dv = v3_sub(vi, vj);
let ddot = v3_dot(d, dv);
let ki: [f64; 3] = [
s.stiffness * (d[0] * ddot + (1.0 - ratio) * (dv[0] - d[0] * ddot)),
s.stiffness * (d[1] * ddot + (1.0 - ratio) * (dv[1] - d[1] * ddot)),
s.stiffness * (d[2] * ddot + (1.0 - ratio) * (dv[2] - d[2] * ddot)),
];
result[3 * s.i] += ki[0];
result[3 * s.i + 1] += ki[1];
result[3 * s.i + 2] += ki[2];
result[3 * s.j] -= ki[0];
result[3 * s.j + 1] -= ki[1];
result[3 * s.j + 2] -= ki[2];
}
result
}
}
#[allow(dead_code)]
pub struct IpcContactSolver {
pub params: IpcBarrierParams,
pub filter: FilterLineSearch,
}
impl IpcContactSolver {
pub fn new(params: IpcBarrierParams, filter: FilterLineSearch) -> Self {
IpcContactSolver { params, filter }
}
pub fn plane_distances(
&self,
positions: &[[f64; 3]],
plane_normal: [f64; 3],
plane_offset: f64,
) -> Vec<f64> {
positions
.iter()
.map(|p| v3_dot(*p, plane_normal) - plane_offset)
.collect()
}
pub fn barrier_gradient_flat(
&self,
positions: &[[f64; 3]],
plane_normal: [f64; 3],
plane_offset: f64,
) -> Vec<f64> {
let n = positions.len();
let mut grad = vec![0.0f64; 3 * n];
for (i, p) in positions.iter().enumerate() {
let d = v3_dot(*p, plane_normal) - plane_offset;
let db = self.params.barrier_gradient(d);
let scale = self.params.kappa * db;
grad[3 * i] += scale * plane_normal[0];
grad[3 * i + 1] += scale * plane_normal[1];
grad[3 * i + 2] += scale * plane_normal[2];
}
grad
}
}