use crate::dynamics::IntegrationParameters;
use crate::dynamics::solver::solver_body::SolverBodies;
use crate::geometry::ColliderHandle;
use crate::math::{AngVector, AngularInertia, DIM, Real, Vector};
use crate::utils::{AngularInertiaOps, ComponentMul, CrossProduct, DotProduct, OrthonormalBasis};
#[cfg(not(feature = "std"))]
#[allow(unused_imports)]
use simba::scalar::{ComplexField as _, RealField as _};
#[derive(Copy, Clone, Debug)]
pub(crate) struct SoftContactSource {
pub collider1: ColliderHandle,
pub collider2: ColliderHandle,
pub manifold: u32,
pub point: u32,
pub slot: u32,
}
#[derive(Copy, Clone, Debug)]
pub(crate) struct SoftContactElement {
pub particles: [u32; CONTACT_ANCHORS],
pub weights: [Real; CONTACT_ANCHORS],
pub im_particles: [Real; CONTACT_ANCHORS],
pub frozen_pos: [Vector; CONTACT_ANCHORS],
}
pub(crate) const CONTACT_ANCHORS: usize = DIM + 1;
#[derive(Copy, Clone, Debug)]
pub(crate) struct FemContactSide {
pub first_slot: u32,
pub num_particles: u32,
pub start: u32,
pub gains: [Real; DIM],
pub covers_other: bool,
}
impl FemContactSide {
#[inline]
fn response<'a>(&self, pool: &'a [Vector], which: usize) -> &'a [Vector] {
let n = self.num_particles as usize;
let start = self.start as usize + which * n;
&pool[start..start + n]
}
}
#[derive(Copy, Clone, Debug)]
pub(crate) struct SoftContact {
pub source: SoftContactSource,
pub support_body: u32,
#[cfg_attr(not(feature = "fem"), allow(dead_code))]
pub support_particle: [u32; CONTACT_ANCHORS],
pub particles: [u32; CONTACT_ANCHORS],
pub weights: [Real; CONTACT_ANCHORS],
pub im_particles: [Real; CONTACT_ANCHORS],
pub frozen_pos: [Vector; CONTACT_ANCHORS],
pub body: u32,
pub element: Option<SoftContactElement>,
pub other_body: u32,
pub body_im: Vector,
pub body_ii: AngularInertia,
pub body_local_point: Vector,
pub body_arm: Vector,
pub surface_point0: Vector,
pub body_point0: Vector,
pub dir: Vector,
pub tangents: [Vector; DIM - 1],
pub dist0: Real,
pub friction: Real,
pub soft_other: bool,
pub fem: [Option<FemContactSide>; 2],
pub erp_inv_dt: Real,
pub cfm_factor: Real,
pub max_bias: Real,
pub torque_dir: AngVector,
pub ii_torque_dir: AngVector,
pub r_normal: Real,
pub rhs_normal: Real,
pub cfm_normal: Real,
pub impulse_normal: Real,
pub impulse_normal_acc: Real,
pub torque_tangent: [AngVector; DIM - 1],
pub ii_torque_tangent: [AngVector; DIM - 1],
pub r_tangent: [Real; DIM - 1],
pub rhs_tangent: [Real; DIM - 1],
pub impulse_tangent: [Real; DIM - 1],
pub impulse_tangent_acc: [Real; DIM - 1],
}
impl SoftContact {
#[inline]
fn surface_point(&self, bodies: &SolverBodies) -> Vector {
let mut p = Vector::ZERO;
for k in 0..CONTACT_ANCHORS {
let id = self.particles[k];
let x = if id == u32::MAX {
self.frozen_pos[k]
} else {
bodies.get_pose(id).translation
};
p += x * self.weights[k];
}
p
}
#[inline]
fn body_point(&self, bodies: &SolverBodies) -> Vector {
if let Some(e) = &self.element {
let mut p = Vector::ZERO;
for k in 0..CONTACT_ANCHORS {
let id = e.particles[k];
let x = if id == u32::MAX {
e.frozen_pos[k]
} else {
bodies.get_pose(id).translation
};
p += x * e.weights[k];
}
p
} else if self.body == u32::MAX {
self.body_point0
} else {
bodies.get_pose(self.body).pose() * self.body_local_point
}
}
pub fn update(&mut self, bodies: &SolverBodies, params: &IntegrationParameters, wo_bias: bool) {
let inv_dt = params.inv_dt();
let ps = self.surface_point(bodies);
let po = self.body_point(bodies);
let delta = (ps - self.surface_point0) - (po - self.body_point0);
let dist = self.dist0 + delta.gdot(self.dir);
if self.body != u32::MAX {
let pose = bodies.get_pose(self.body);
self.body_im = pose.im;
self.body_ii = pose.ii;
}
fn freeze_levers<const N: usize>(weights: &[Real; N], im: &mut [Real; N]) {
let mut w2 = 0.0;
let mut im_max: Real = 0.0;
for k in 0..N {
w2 += weights[k] * weights[k] * im[k];
im_max = im_max.max(im[k]);
}
if im_max > 0.0 && w2 < 0.04 * im_max {
*im = [0.0; N];
}
}
let (support_fem, other_fem) = self.fem_sides();
if !support_fem {
freeze_levers(&self.weights, &mut self.im_particles);
}
if let (Some(e), false) = (&mut self.element, other_fem) {
freeze_levers(&e.weights, &mut e.im_particles);
}
let mut w_particles = 0.0;
if !support_fem {
for k in 0..CONTACT_ANCHORS {
w_particles += self.weights[k] * self.weights[k] * self.im_particles[k];
}
}
if let (Some(e), false) = (&self.element, other_fem) {
for k in 0..CONTACT_ANCHORS {
w_particles += e.weights[k] * e.weights[k] * e.im_particles[k];
}
}
let fem_gain = |which: usize| -> Real {
self.fem
.iter()
.flatten()
.map(|side| side.gains[which])
.sum()
};
let body_terms = !other_fem || self.element.is_some();
self.torque_dir = self.body_arm.gcross(-self.dir);
self.ii_torque_dir = self.body_ii.transform_vector(self.torque_dir);
let mut r_normal = w_particles + fem_gain(0);
if body_terms {
r_normal += self.dir.gdot(self.body_im.component_mul(&self.dir))
+ self.ii_torque_dir.gdot(self.torque_dir);
}
self.r_normal = crate::utils::inv(r_normal);
for j in 0..DIM - 1 {
let t = self.tangents[j];
self.torque_tangent[j] = self.body_arm.gcross(-t);
self.ii_torque_tangent[j] = self.body_ii.transform_vector(self.torque_tangent[j]);
let mut r_tangent = w_particles + fem_gain(1 + j);
if body_terms {
r_tangent += t.gdot(self.body_im.component_mul(&t))
+ self.ii_torque_tangent[j].gdot(self.torque_tangent[j]);
}
self.r_tangent[j] = crate::utils::inv(r_tangent);
}
let rhs_wo_bias = dist.max(0.0) * inv_dt;
if wo_bias {
self.rhs_normal = rhs_wo_bias;
self.cfm_normal = 1.0;
for j in 0..DIM - 1 {
self.rhs_tangent[j] = 0.0;
}
} else {
let max_corrective_velocity = params.max_corrective_velocity().min(self.max_bias);
let bias = (dist * self.erp_inv_dt).clamp(-max_corrective_velocity, 0.0);
self.rhs_normal = rhs_wo_bias + bias;
self.cfm_normal = if dist <= 0.0 { self.cfm_factor } else { 1.0 };
for j in 0..DIM - 1 {
self.rhs_tangent[j] = delta.gdot(self.tangents[j]) * inv_dt;
}
self.impulse_normal_acc += self.impulse_normal;
self.impulse_normal *= params.warmstart_coefficient;
for j in 0..DIM - 1 {
self.impulse_tangent_acc[j] += self.impulse_tangent[j];
self.impulse_tangent[j] *= params.warmstart_coefficient;
}
}
}
#[inline]
fn fem_sides(&self) -> (bool, bool) {
let support = self.fem[0].is_some();
let other = self.fem[1].is_some() || self.fem[0].is_some_and(|s| s.covers_other);
(support, other)
}
#[inline]
pub(crate) fn apply(
&self,
bodies: &mut SolverBodies,
pool: &[Vector],
which: usize,
dir: Vector,
ii_torque: AngVector,
lambda: Real,
) {
let (support_fem, other_fem) = self.fem_sides();
for side in self.fem.iter().flatten() {
let u = side.response(pool, which);
let first = side.first_slot as usize;
for (i, ui) in u.iter().enumerate() {
bodies.vels[first + i].linear += *ui * lambda;
}
}
if !support_fem {
for k in 0..CONTACT_ANCHORS {
let id = self.particles[k];
if id != u32::MAX && (id as usize) < bodies.len() {
bodies.vels[id as usize].linear +=
dir * (self.weights[k] * self.im_particles[k] * lambda);
}
}
}
if let Some(e) = &self.element {
if !other_fem {
for k in 0..CONTACT_ANCHORS {
let id = e.particles[k];
if id != u32::MAX && (id as usize) < bodies.len() {
bodies.vels[id as usize].linear -=
dir * (e.weights[k] * e.im_particles[k] * lambda);
}
}
}
} else if !other_fem && self.body != u32::MAX && (self.body as usize) < bodies.len() {
let v = &mut bodies.vels[self.body as usize];
v.linear -= dir.component_mul(&self.body_im) * lambda;
v.angular += ii_torque * lambda;
}
}
#[inline]
fn relative_velocity(&self, bodies: &SolverBodies, dir: Vector, torque: AngVector) -> Real {
let mut vs = Vector::ZERO;
for k in 0..CONTACT_ANCHORS {
let id = self.particles[k];
if id != u32::MAX {
vs += bodies.get_vel(id).linear * self.weights[k];
}
}
let mut dvel = dir.gdot(vs);
if let Some(e) = &self.element {
let mut vo = Vector::ZERO;
for k in 0..CONTACT_ANCHORS {
let id = e.particles[k];
if id != u32::MAX {
vo += bodies.get_vel(id).linear * e.weights[k];
}
}
dvel -= dir.gdot(vo);
} else if self.body != u32::MAX {
let vo = bodies.get_vel(self.body);
dvel += -dir.gdot(vo.linear) + torque.gdot(vo.angular);
}
dvel
}
pub fn warmstart(&self, bodies: &mut SolverBodies, pool: &[Vector]) {
self.apply(
bodies,
pool,
0,
self.dir,
self.ii_torque_dir,
self.impulse_normal,
);
for j in 0..DIM - 1 {
self.apply(
bodies,
pool,
1 + j,
self.tangents[j],
self.ii_torque_tangent[j],
self.impulse_tangent[j],
);
}
}
pub fn solve(&mut self, bodies: &mut SolverBodies, pool: &[Vector]) {
let dvel = self.relative_velocity(bodies, self.dir, self.torque_dir) + self.rhs_normal;
let new_impulse = (self.cfm_normal * (self.impulse_normal - self.r_normal * dvel)).max(0.0);
let delta = new_impulse - self.impulse_normal;
self.impulse_normal = new_impulse;
self.apply(bodies, pool, 0, self.dir, self.ii_torque_dir, delta);
let limit = self.friction * self.impulse_normal;
let mut new_tangent = self.impulse_tangent;
for j in 0..DIM - 1 {
let dvel = self.relative_velocity(bodies, self.tangents[j], self.torque_tangent[j])
+ self.rhs_tangent[j];
new_tangent[j] -= self.r_tangent[j] * dvel;
}
#[cfg(feature = "dim2")]
{
new_tangent[0] = new_tangent[0].clamp(-limit, limit);
}
#[cfg(feature = "dim3")]
{
let norm = (new_tangent[0] * new_tangent[0] + new_tangent[1] * new_tangent[1]).sqrt();
if norm > limit {
let scale = if norm > 0.0 { limit / norm } else { 0.0 };
new_tangent[0] *= scale;
new_tangent[1] *= scale;
}
}
for j in 0..DIM - 1 {
let delta = new_tangent[j] - self.impulse_tangent[j];
self.impulse_tangent[j] = new_tangent[j];
self.apply(
bodies,
pool,
1 + j,
self.tangents[j],
self.ii_torque_tangent[j],
delta,
);
}
}
pub fn tangent_basis(dir: Vector) -> [Vector; DIM - 1] {
dir.orthonormal_basis()
}
}