use he3d_math::Vec3;
use he3d_math::scalar::sqrt;
use crate::body::Body;
use crate::contact::{ConstraintPoint, ContactConstraint};
use crate::storage::{Slot, constraint_bodies, constraint_bodies_ref};
const NORMAL_AXIS: usize = 0;
const TANGENT_AXIS_0: usize = 1;
const TANGENT_AXIS_1: usize = 2;
pub(crate) const DEFAULT_VELOCITY_ITERATIONS: u32 = 12;
const BAUMGARTE: f32 = 0.2;
const PENETRATION_SLOP: f32 = 0.005;
const RESTITUTION_THRESHOLD: f32 = 1.0;
const POSITION_ITERATIONS: u32 = 3;
const INFINITE_EFFECTIVE_MASS: f32 = 1e-8;
const MINIMUM_WARM_START_IMPULSE_SQUARED: f32 = 1e-8;
#[derive(Clone, Copy, Debug)]
pub(crate) struct SolverSettings {
pub(crate) velocity_iterations: u32,
}
impl Default for SolverSettings {
fn default() -> Self {
Self {
velocity_iterations: DEFAULT_VELOCITY_ITERATIONS,
}
}
}
pub(crate) fn is_solvable(first: &Body, second: &Body) -> bool {
first.motion.is_pushable() || second.motion.is_pushable()
}
pub(crate) fn prepare(slots: &[Slot], constraints: &mut [ContactConstraint], delta_time: f32) {
let inverse_delta = 1.0 / delta_time;
for constraint in constraints.iter_mut() {
let Some((first, second)) =
constraint_bodies_ref(slots, constraint.a as usize, constraint.b as usize)
else {
continue;
};
let normal = constraint.normal;
let tangents = constraint.tangents;
let inverse_mass_sum = first.mass.inv_mass + second.map_or(0.0, |body| body.mass.inv_mass);
for point in constraint.points_mut() {
let arm_first = point.anchor_a;
let arm_second = point.anchor_b;
let mut reciprocal_masses = [0.0; 3];
for (index, axis) in [normal, tangents[0], tangents[1]].into_iter().enumerate() {
let (term, response) = effective_mass_and_response(
first,
second,
arm_first,
arm_second,
axis,
inverse_mass_sum,
);
point.angular_response[0][index] = response[0];
point.angular_response[1][index] = response[1];
reciprocal_masses[index] = reciprocal_or_zero(term);
}
point.normal_mass = reciprocal_masses[0];
point.tangent_mass = [reciprocal_masses[1], reciprocal_masses[2]];
let approach = relative_velocity(first, second, arm_first, arm_second, normal);
let mut bias = 0.0;
if approach < -RESTITUTION_THRESHOLD {
let restitution = second.map_or(0.0, |body| body.restitution);
let restitution = first.restitution.max(restitution);
if restitution > 0.0 {
bias = -restitution * approach;
}
}
point.bias = bias;
point.position_bias =
BAUMGARTE * inverse_delta * (point.depth - PENETRATION_SLOP).max(0.0);
}
}
}
pub(crate) fn warm_start(slots: &mut [Slot], constraints: &[ContactConstraint]) {
for constraint in constraints {
let normal = constraint.normal;
let tangents = constraint.tangents;
let Some(mut pair) =
ContactPair::resolve(slots, constraint.a as usize, constraint.b as usize)
else {
continue;
};
for point in constraint.points() {
let mut impulse = normal * point.normal_impulse;
for (index, tangent) in tangents.into_iter().enumerate() {
impulse += tangent * point.tangent_impulse[index];
}
if impulse.length_squared() <= MINIMUM_WARM_START_IMPULSE_SQUARED {
continue;
}
pair.apply_impulse(point, NORMAL_AXIS, normal, point.normal_impulse);
pair.apply_impulse(point, TANGENT_AXIS_0, tangents[0], point.tangent_impulse[0]);
pair.apply_impulse(point, TANGENT_AXIS_1, tangents[1], point.tangent_impulse[1]);
}
}
}
pub(crate) fn solve_velocities(
slots: &mut [Slot],
constraints: &mut [ContactConstraint],
settings: SolverSettings,
) {
for _ in 0..settings.velocity_iterations {
for constraint in constraints.iter_mut() {
solve_constraint_velocity(slots, constraint);
}
}
}
fn solve_constraint_velocity(slots: &mut [Slot], constraint: &mut ContactConstraint) {
let normal = constraint.normal;
let tangents = constraint.tangents;
let friction = constraint.friction;
let Some(mut pair) = ContactPair::resolve(slots, constraint.a as usize, constraint.b as usize)
else {
return;
};
if constraint.point_count == TWO_POINT_MANIFOLD {
solve_friction(&mut pair, constraint.points_mut(), tangents, friction);
solve_normal_block(&mut pair, constraint, normal);
} else {
for point in constraint.points_mut() {
solve_normal(&mut pair, point, normal);
solve_friction_point(&mut pair, point, tangents, friction);
}
}
}
fn solve_friction(
pair: &mut ContactPair,
points: &mut [ConstraintPoint],
tangents: [Vec3; 2],
friction: f32,
) {
for point in points {
solve_friction_point(pair, point, tangents, friction);
}
}
fn solve_friction_point(
pair: &mut ContactPair,
point: &mut ConstraintPoint,
tangents: [Vec3; 2],
friction: f32,
) {
for (index, tangent) in tangents.into_iter().enumerate() {
let relative = pair.relative_velocity(point.anchor_a, point.anchor_b, tangent);
let lambda = -point.tangent_mass[index] * relative;
let limit = friction * point.normal_impulse;
let previous = point.tangent_impulse[index];
point.tangent_impulse[index] = (previous + lambda).clamp(-limit, limit);
let delta = point.tangent_impulse[index] - previous;
pair.apply_impulse(point, TANGENT_AXIS_0 + index, tangent, delta);
}
}
const TWO_POINT_MANIFOLD: u8 = 2;
const BLOCK_SINGULAR_RATIO: f32 = 1e-6;
fn solve_normal(pair: &mut ContactPair, point: &mut ConstraintPoint, normal: Vec3) {
let relative = pair.relative_velocity(point.anchor_a, point.anchor_b, normal);
let lambda = -point.normal_mass * (relative - point.bias);
let previous = point.normal_impulse;
point.normal_impulse = (previous + lambda).max(0.0);
let delta = point.normal_impulse - previous;
pair.apply_impulse(point, NORMAL_AXIS, normal, delta);
}
fn solve_normals_individually(
pair: &mut ContactPair,
points: &mut [ConstraintPoint],
normal: Vec3,
) {
for point in points {
solve_normal(pair, point, normal);
}
}
fn solve_normal_block(pair: &mut ContactPair, constraint: &mut ContactConstraint, normal: Vec3) {
let first = constraint.points[0];
let second = constraint.points[1];
let inverse_mass_sum = pair.inverse_mass_sum();
let block = ContactBlock {
first: reciprocal_or_zero(first.normal_mass),
coupling: inverse_mass_sum
+ second.angular_response[0][NORMAL_AXIS].dot(first.anchor_a.cross(normal))
+ second.angular_response[1][NORMAL_AXIS].dot(first.anchor_b.cross(normal)),
second: reciprocal_or_zero(second.normal_mass),
};
let velocity = (
pair.relative_velocity(first.anchor_a, first.anchor_b, normal) - first.bias,
pair.relative_velocity(second.anchor_a, second.anchor_b, normal) - second.bias,
);
let previous = (first.normal_impulse, second.normal_impulse);
let Some((next_first, next_second)) = block.solve(previous, velocity) else {
solve_normals_individually(pair, &mut constraint.points[..2], normal);
return;
};
constraint.points[0].normal_impulse = next_first;
constraint.points[1].normal_impulse = next_second;
pair.apply_impulse(&first, NORMAL_AXIS, normal, next_first - previous.0);
pair.apply_impulse(&second, NORMAL_AXIS, normal, next_second - previous.1);
}
#[derive(Clone, Copy, Debug)]
struct ContactBlock {
first: f32,
coupling: f32,
second: f32,
}
impl ContactBlock {
fn solve(self, previous: (f32, f32), velocity: (f32, f32)) -> Option<(f32, f32)> {
let determinant = self.first * self.second - self.coupling * self.coupling;
let threshold = self.first * self.second * BLOCK_SINGULAR_RATIO;
if determinant <= threshold || determinant.is_nan() {
return None;
}
let (previous_first, previous_second) = previous;
let (velocity_first, velocity_second) = velocity;
let inverse = 1.0 / determinant;
let mut next_first = previous_first
- inverse * (self.second * velocity_first - self.coupling * velocity_second);
let mut next_second = previous_second
- inverse * (self.first * velocity_second - self.coupling * velocity_first);
if next_first < 0.0 && next_second < 0.0 {
next_first = 0.0;
next_second = 0.0;
} else if next_first < 0.0 {
next_first = 0.0;
next_second = (previous_second
+ (self.coupling * previous_first - velocity_second) / self.second)
.max(0.0);
} else if next_second < 0.0 {
next_second = 0.0;
next_first = (previous_first
+ (self.coupling * previous_second - velocity_first) / self.first)
.max(0.0);
}
Some((next_first, next_second))
}
}
pub(crate) fn solve_positions(slots: &mut [Slot], constraints: &mut [ContactConstraint]) {
for _ in 0..POSITION_ITERATIONS {
for constraint in constraints.iter_mut() {
let normal = constraint.normal;
let Some(mut pair) =
ContactPair::resolve(slots, constraint.a as usize, constraint.b as usize)
else {
continue;
};
for point in constraint.points_mut() {
let relative = pair.pseudo_relative_velocity(point.anchor_a, point.anchor_b, normal);
let lambda = -point.normal_mass * (relative - point.position_bias);
let previous = point.position_impulse;
point.position_impulse = (previous + lambda).max(0.0);
let delta = point.position_impulse - previous;
pair.apply_pseudo_impulse(point, normal, delta);
}
}
}
}
struct ContactPair<'a> {
first: &'a mut Body,
second: Option<&'a mut Body>,
}
impl<'a> ContactPair<'a> {
fn resolve(slots: &'a mut [Slot], first: usize, second: usize) -> Option<Self> {
let (first, second) = constraint_bodies(slots, first, second)?;
Some(Self { first, second })
}
fn relative_velocity(&self, arm_first: Vec3, arm_second: Vec3, axis: Vec3) -> f32 {
relative_velocity(
self.first,
self.second.as_deref(),
arm_first,
arm_second,
axis,
)
}
fn inverse_mass_sum(&self) -> f32 {
self.first.mass.inv_mass
+ self
.second
.as_deref()
.map_or(0.0, |body| body.mass.inv_mass)
}
fn pseudo_relative_velocity(&self, arm_first: Vec3, arm_second: Vec3, axis: Vec3) -> f32 {
let first_share = self.first.pseudo_point_velocity(arm_first).dot(axis);
let second_share = self
.second
.as_deref()
.map_or(0.0, |body| body.pseudo_point_velocity(arm_second).dot(axis));
second_share - first_share
}
fn apply_impulse(&mut self, point: &ConstraintPoint, axis: usize, direction: Vec3, delta: f32) {
let impulse = direction * delta;
self.first.linear_velocity -= impulse * self.first.mass.inv_mass;
self.first.angular_velocity -= point.angular_response[0][axis] * delta;
if let Some(second) = self.second.as_deref_mut() {
second.linear_velocity += impulse * second.mass.inv_mass;
second.angular_velocity += point.angular_response[1][axis] * delta;
}
}
fn apply_pseudo_impulse(&mut self, point: &ConstraintPoint, normal: Vec3, delta: f32) {
let impulse = normal * delta;
self.first.pseudo_linear_velocity -= impulse * self.first.mass.inv_mass;
self.first.pseudo_angular_velocity -= point.angular_response[0][NORMAL_AXIS] * delta;
if let Some(second) = self.second.as_deref_mut() {
second.pseudo_linear_velocity += impulse * second.mass.inv_mass;
second.pseudo_angular_velocity += point.angular_response[1][NORMAL_AXIS] * delta;
}
}
}
fn relative_velocity(
first: &Body,
second: Option<&Body>,
arm_first: Vec3,
arm_second: Vec3,
axis: Vec3,
) -> f32 {
let first_share = first.point_velocity(arm_first).dot(axis);
let second_share = second.map_or(0.0, |body| body.point_velocity(arm_second).dot(axis));
second_share - first_share
}
fn effective_mass_and_response(
first: &Body,
second: Option<&Body>,
arm_first: Vec3,
arm_second: Vec3,
axis: Vec3,
inverse_mass_sum: f32,
) -> (f32, [Vec3; 2]) {
let cross_first = arm_first.cross(axis);
let response_first = first.apply_inv_inertia(cross_first);
let mut response = [response_first, Vec3::ZERO];
let mut second_term = 0.0;
if let Some(body) = second {
let cross_second = arm_second.cross(axis);
response[1] = body.apply_inv_inertia(cross_second);
second_term = cross_second.dot(response[1]);
}
(
inverse_mass_sum + cross_first.dot(response_first) + second_term,
response,
)
}
fn reciprocal_or_zero(value: f32) -> f32 {
if value.is_finite() && value > INFINITE_EFFECTIVE_MASS {
1.0 / value
} else {
0.0
}
}
pub(crate) fn combine_friction(first: f32, second: f32) -> f32 {
sqrt(first.max(0.0) * second.max(0.0))
}
#[cfg(test)]
mod tests {
use super::{ContactBlock, combine_friction, is_solvable, reciprocal_or_zero};
use crate::body::{Body, BodyDesc};
use crate::shape::Shape;
fn after(block: ContactBlock, impulse: (f32, f32), velocity: (f32, f32)) -> (f32, f32) {
(
velocity.0 + block.first * impulse.0 + block.coupling * impulse.1,
velocity.1 + block.coupling * impulse.0 + block.second * impulse.1,
)
}
fn edge_block() -> ContactBlock {
ContactBlock {
first: 2.5,
coupling: -0.5,
second: 2.5,
}
}
#[test]
fn a_block_leaves_both_points_either_touching_or_separating() {
let block = edge_block();
for velocity in [(-1.0, -0.3), (-0.2, -1.0), (-1.0, -1.0), (-5.0, 0.0)] {
let impulse = block
.solve((0.0, 0.0), velocity)
.expect("a two-point edge block is invertible");
assert!(impulse.0 >= 0.0 && impulse.1 >= 0.0, "impulse {impulse:?}");
let settled = after(block, impulse, velocity);
assert!(
settled.0.abs() < 1e-5 && settled.1.abs() < 1e-5,
"velocity {velocity:?} settled to {settled:?}"
);
}
}
#[test]
fn points_that_are_already_separating_receive_nothing() {
let block = edge_block();
let impulse = block
.solve((0.0, 0.0), (1.0, 0.5))
.expect("a two-point edge block is invertible");
assert!(
impulse.0.abs() < 1e-9 && impulse.1.abs() < 1e-9,
"got {impulse:?}"
);
}
#[test]
fn a_point_the_block_would_pull_leaves_it_and_the_other_settles_alone() {
let block = edge_block();
let velocity = (1.0, -1.0);
let impulse = block
.solve((0.0, 0.0), velocity)
.expect("a two-point edge block is invertible");
assert!(impulse.0.abs() < 1e-9, "the pulled point must take none");
assert!((impulse.1 - 0.4).abs() < 1e-5, "got {impulse:?}");
let settled = after(block, impulse, velocity);
assert!(
settled.0 >= 0.0,
"a point taking no impulse must not be pulled"
);
assert!(settled.1.abs() < 1e-5, "got {settled:?}");
}
#[test]
fn a_settled_block_asks_for_no_more_impulse() {
let block = edge_block();
let impulse = block
.solve((0.0, 0.0), (-1.0, -0.3))
.expect("a two-point edge block is invertible");
let settled = after(block, impulse, (-1.0, -0.3));
let again = block
.solve(impulse, settled)
.expect("a two-point edge block is invertible");
assert!(
(again.0 - impulse.0).abs() < 1e-5 && (again.1 - impulse.1).abs() < 1e-5,
"{impulse:?} then {again:?}"
);
}
#[test]
fn a_block_whose_points_cannot_be_told_apart_is_turned_down() {
let degenerate = ContactBlock {
first: 2.5,
coupling: 2.5,
second: 2.5,
};
assert!(degenerate.solve((0.0, 0.0), (-1.0, 0.0)).is_none());
let immovable = ContactBlock {
first: 0.0,
coupling: 0.0,
second: 0.0,
};
assert!(immovable.solve((0.0, 0.0), (-1.0, 0.0)).is_none());
}
fn body(motion: fn(Shape) -> BodyDesc) -> Body {
Body::from_desc(&motion(Shape::sphere(0.5)))
}
#[test]
fn two_immovable_bodies_are_not_solvable() {
let stationary = body(BodyDesc::stationary);
let kinematic = body(BodyDesc::kinematic);
let dynamic = Body::from_desc(&BodyDesc::dynamic(Shape::sphere(0.5), 1.0));
assert!(!is_solvable(&stationary, &kinematic));
assert!(is_solvable(&stationary, &dynamic));
assert!(is_solvable(&dynamic, &kinematic));
}
#[test]
fn friction_combines_through_a_geometric_mean() {
assert!((combine_friction(0.0, 0.9) - 0.0).abs() < 1e-6);
assert!((combine_friction(0.25, 1.0) - 0.5).abs() < 1e-6);
assert!((combine_friction(-1.0, 1.0) - 0.0).abs() < 1e-6);
}
#[test]
fn a_degenerate_effective_mass_yields_no_impulse() {
assert!(reciprocal_or_zero(0.0).abs() < 1e-9);
assert!(reciprocal_or_zero(-1.0).abs() < 1e-9);
assert!(reciprocal_or_zero(f32::NAN).abs() < 1e-9);
assert!((reciprocal_or_zero(2.0) - 0.5).abs() < 1e-6);
}
}