pub mod material;
pub mod solver;
pub use material::ContactMaterial;
pub use solver::{contact_forces, contact_forces_implicit, find_contacts, find_ground_contacts};
use crate::collision::Collision;
use crate::math::{SpatialVec, Vec3};
pub fn compute_contact_force(
collision: &Collision,
material: &ContactMaterial,
velocity_i: &Vec3,
velocity_j: &Vec3,
) -> SpatialVec {
let depth = collision.penetration_depth;
if depth <= 0.0 {
return SpatialVec::zero();
}
let normal = collision.contact_normal;
let rel_vel = velocity_j - velocity_i;
let normal_vel = rel_vel.dot(&normal);
let k = material.stiffness;
let c = material.damping;
let p = 1.0;
let force_magnitude = k * depth.powf(p) - c * normal_vel;
let force_magnitude = force_magnitude.max(0.0);
let force = normal * force_magnitude;
let tangent_vel = rel_vel - normal * normal_vel;
let tangent_speed = tangent_vel.norm();
let friction_force = if tangent_speed > 1e-10 {
let tangent_dir = tangent_vel / tangent_speed;
let friction_magnitude = (material.friction * force_magnitude).min(c * tangent_speed);
-tangent_dir * friction_magnitude
} else {
Vec3::zeros()
};
let total_force = force + friction_force;
SpatialVec::new(Vec3::zeros(), total_force)
}
pub fn compute_contact_force_implicit(
collision: &Collision,
material: &ContactMaterial,
velocity_i: &Vec3,
velocity_j: &Vec3,
mass_i: f64,
mass_j: f64,
dt: f64,
) -> SpatialVec {
let depth = collision.penetration_depth;
if depth <= 0.0 {
return SpatialVec::zero();
}
let normal = collision.contact_normal;
let rel_vel = velocity_j - velocity_i;
let normal_vel = rel_vel.dot(&normal);
let k = material.stiffness;
let c = material.damping;
let m_eff = if !mass_i.is_finite() && !mass_j.is_finite() {
return SpatialVec::zero();
} else if !mass_i.is_finite() {
mass_j
} else if !mass_j.is_finite() {
mass_i
} else if mass_i + mass_j > 0.0 {
(mass_i * mass_j) / (mass_i + mass_j)
} else {
return SpatialVec::zero();
};
if m_eff <= 0.0 || !m_eff.is_finite() {
return SpatialVec::zero();
}
let denom = m_eff + dt * c + dt * dt * k;
let force_magnitude = m_eff * (k * depth - (c + dt * k) * normal_vel) / denom;
let force_magnitude = force_magnitude.max(0.0);
let force = normal * force_magnitude;
let tangent_vel = rel_vel - normal * normal_vel;
let tangent_speed = tangent_vel.norm();
let friction_force = if tangent_speed > 1e-10 {
let tangent_dir = tangent_vel / tangent_speed;
let friction_magnitude = (material.friction * force_magnitude).min(c * tangent_speed);
-tangent_dir * friction_magnitude
} else {
Vec3::zeros()
};
SpatialVec::new(Vec3::zeros(), force + friction_force)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_contact_force_zero_depth() {
let collision = Collision {
body_i: 0,
body_j: 1,
contact_point: Vec3::zeros(),
contact_normal: Vec3::z(),
penetration_depth: 0.0,
};
let material = ContactMaterial::default();
let force = compute_contact_force(&collision, &material, &Vec3::zeros(), &Vec3::zeros());
assert!(force.linear.norm() < 1e-10);
}
#[test]
fn test_contact_force_penetration() {
let collision = Collision {
body_i: 0,
body_j: 1,
contact_point: Vec3::zeros(),
contact_normal: Vec3::z(),
penetration_depth: 0.1,
};
let material = ContactMaterial {
stiffness: 1000.0,
..Default::default()
};
let force = compute_contact_force(&collision, &material, &Vec3::zeros(), &Vec3::zeros());
assert!(force.linear.norm() > 0.0);
assert!(force.linear.dot(&Vec3::z()) > 0.0);
}
}