mod normal_part;
mod tangent_part;
pub use normal_part::ContactNormalPart;
pub use tangent_part::ContactTangentPart;
use core::cmp::Ordering;
use crate::{
collision::contact_types::ContactId,
dynamics::solver::{BodyQueryItem, ContactSoftnessCoefficients},
prelude::*,
};
#[cfg(feature = "serialize")]
use bevy::reflect::{ReflectDeserialize, ReflectSerialize};
use bevy::{
ecs::entity::{Entity, EntityMapper, MapEntities},
reflect::Reflect,
utils::default,
};
use super::solver_body::{SolverBody, SolverBodyInertia};
#[derive(Clone, Debug, PartialEq, Reflect)]
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serialize", reflect(Serialize, Deserialize))]
#[reflect(Debug, PartialEq)]
pub struct ContactConstraintPoint {
pub normal_part: ContactNormalPart,
pub tangent_part: Option<ContactTangentPart>,
pub anchor1: Vector,
pub anchor2: Vector,
pub normal_speed: Scalar,
pub initial_separation: Scalar,
}
#[derive(Clone, Debug, PartialEq, Reflect)]
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serialize", reflect(Serialize, Deserialize))]
#[reflect(Debug, PartialEq)]
pub struct ContactConstraint {
pub body1: Entity,
pub body2: Entity,
pub relative_dominance: i16,
pub friction: Scalar,
pub restitution: Scalar,
#[cfg(feature = "2d")]
pub tangent_speed: Scalar,
#[cfg(feature = "3d")]
pub tangent_velocity: Vector,
pub normal: Vector,
#[cfg(feature = "3d")]
pub tangent1: Vector,
pub points: Vec<ContactConstraintPoint>,
pub contact_id: ContactId,
pub manifold_index: usize,
}
impl ContactConstraint {
pub(super) fn generate(
body1_entity: Entity,
body2_entity: Entity,
body1: BodyQueryItem,
body2: BodyQueryItem,
contact_id: ContactId,
manifold: &ContactManifold,
manifold_index: usize,
warm_start_enabled: bool,
softness: &ContactSoftnessCoefficients,
) -> Self {
let inertia1 = body1.inertia.unwrap_or(&SolverBodyInertia::DUMMY);
let inertia2 = body2.inertia.unwrap_or(&SolverBodyInertia::DUMMY);
let relative_dominance = inertia1.dominance() - inertia2.dominance();
let (inv_mass1, i1, inv_mass2, i2) = match relative_dominance.cmp(&0) {
Ordering::Equal => (
inertia1.effective_inv_mass(),
inertia1.effective_inv_angular_inertia(),
inertia2.effective_inv_mass(),
inertia2.effective_inv_angular_inertia(),
),
Ordering::Greater => (
Vector::ZERO,
SymmetricTensor::ZERO,
inertia2.effective_inv_mass(),
inertia2.effective_inv_angular_inertia(),
),
Ordering::Less => (
inertia1.effective_inv_mass(),
inertia1.effective_inv_angular_inertia(),
Vector::ZERO,
SymmetricTensor::ZERO,
),
};
let softness = if relative_dominance != 0 {
softness.non_dynamic
} else {
softness.dynamic
};
let effective_inverse_mass_sum = inv_mass1 + inv_mass2;
let tangents = compute_tangent_directions(
manifold.normal,
body1.linear_velocity.0,
body2.linear_velocity.0,
);
let mut points = Vec::with_capacity(manifold.points.len());
for point in manifold.points.iter() {
let anchor1 = point.anchor1;
let anchor2 = point.anchor2;
let point = ContactConstraintPoint {
normal_part: ContactNormalPart::generate(
effective_inverse_mass_sum,
&i1,
&i2,
anchor1,
anchor2,
manifold.normal,
warm_start_enabled.then_some(point.warm_start_normal_impulse),
softness,
),
tangent_part: (manifold.friction > 0.0).then_some(ContactTangentPart::generate(
effective_inverse_mass_sum,
&i1,
&i2,
anchor1,
anchor2,
tangents,
warm_start_enabled.then_some(point.warm_start_tangent_impulse),
)),
anchor1,
anchor2,
normal_speed: point.normal_speed,
initial_separation: -point.penetration - (anchor2 - anchor1).dot(manifold.normal),
};
points.push(point);
}
ContactConstraint {
body1: body1_entity,
body2: body2_entity,
relative_dominance,
friction: manifold.friction,
restitution: manifold.restitution,
#[cfg(feature = "2d")]
tangent_speed: manifold.tangent_speed,
#[cfg(feature = "3d")]
tangent_velocity: manifold.tangent_velocity,
normal: manifold.normal,
#[cfg(feature = "3d")]
tangent1: tangents[0],
points,
contact_id,
manifold_index,
}
}
pub fn warm_start(
&self,
body1: &mut SolverBody,
body2: &mut SolverBody,
inertia1: &SolverBodyInertia,
inertia2: &SolverBodyInertia,
warm_start_coefficient: Scalar,
) {
let inv_mass1 = inertia1.effective_inv_mass();
let inv_mass2 = inertia2.effective_inv_mass();
let inv_angular_inertia1 = inertia1.effective_inv_angular_inertia();
let inv_angular_inertia2 = inertia2.effective_inv_angular_inertia();
let tangent_directions = self.tangent_directions();
for point in self.points.iter() {
let r1 = point.anchor1;
let r2 = point.anchor2;
let tangent_impulse = point
.tangent_part
.as_ref()
.map_or(default(), |part| part.impulse);
#[cfg(feature = "2d")]
let p = warm_start_coefficient
* (point.normal_part.impulse * self.normal
+ tangent_impulse * tangent_directions[0]);
#[cfg(feature = "3d")]
let p = warm_start_coefficient
* (point.normal_part.impulse * self.normal
+ tangent_impulse.x * tangent_directions[0]
+ tangent_impulse.y * tangent_directions[1]);
body1.linear_velocity -= p * inv_mass1;
body1.angular_velocity -= inv_angular_inertia1 * cross(r1, p);
body2.linear_velocity += p * inv_mass2;
body2.angular_velocity += inv_angular_inertia2 * cross(r2, p);
}
}
pub fn solve(
&mut self,
body1: &mut SolverBody,
body2: &mut SolverBody,
inertia1: &SolverBodyInertia,
inertia2: &SolverBodyInertia,
delta_secs: Scalar,
use_bias: bool,
max_overlap_solve_speed: Scalar,
) {
let inv_mass1 = inertia1.effective_inv_mass();
let inv_mass2 = inertia2.effective_inv_mass();
let inv_angular_inertia1 = inertia1.effective_inv_angular_inertia();
let inv_angular_inertia2 = inertia2.effective_inv_angular_inertia();
let delta_translation = body2.delta_position - body1.delta_position;
for point in self.points.iter_mut() {
let r1 = body1.delta_rotation * point.anchor1;
let r2 = body2.delta_rotation * point.anchor2;
let delta_separation = delta_translation + (r2 - r1);
let separation = delta_separation.dot(self.normal) + point.initial_separation;
let r1 = point.anchor1;
let r2 = point.anchor2;
let relative_velocity = body2.velocity_at_point(r2) - body1.velocity_at_point(r1);
let impulse_magnitude = point.normal_part.solve_impulse(
separation,
relative_velocity,
self.normal,
use_bias,
max_overlap_solve_speed,
delta_secs,
);
let impulse = impulse_magnitude * self.normal;
body1.linear_velocity -= impulse * inv_mass1;
body1.angular_velocity -= inv_angular_inertia1 * cross(r1, impulse);
body2.linear_velocity += impulse * inv_mass2;
body2.angular_velocity += inv_angular_inertia2 * cross(r2, impulse);
}
let tangent_directions = self.tangent_directions();
for point in self.points.iter_mut() {
let Some(ref mut friction_part) = point.tangent_part else {
continue;
};
let r1 = point.anchor1;
let r2 = point.anchor2;
let relative_velocity = body2.velocity_at_point(r2) - body1.velocity_at_point(r1);
let impulse = friction_part.solve_impulse(
tangent_directions,
relative_velocity,
#[cfg(feature = "2d")]
self.tangent_speed,
#[cfg(feature = "3d")]
self.tangent_velocity,
self.friction,
point.normal_part.impulse,
);
body1.linear_velocity -= impulse * inv_mass1;
body1.angular_velocity -= inv_angular_inertia1 * cross(r1, impulse);
body2.linear_velocity += impulse * inv_mass2;
body2.angular_velocity += inv_angular_inertia2 * cross(r2, impulse);
}
}
pub fn apply_restitution(
&mut self,
body1: &mut SolverBody,
body2: &mut SolverBody,
inertia1: &SolverBodyInertia,
inertia2: &SolverBodyInertia,
threshold: Scalar,
) {
let inv_mass1 = inertia1.effective_inv_mass();
let inv_mass2 = inertia2.effective_inv_mass();
let inv_angular_inertia1 = inertia1.effective_inv_angular_inertia();
let inv_angular_inertia2 = inertia2.effective_inv_angular_inertia();
for point in self.points.iter_mut() {
if point.normal_speed > -threshold || point.normal_part.total_impulse == 0.0 {
continue;
}
let r1 = point.anchor1;
let r2 = point.anchor2;
let relative_velocity = body2.velocity_at_point(r2) - body1.velocity_at_point(r1);
let normal_speed = relative_velocity.dot(self.normal);
let mut impulse = -point.normal_part.effective_mass
* (normal_speed + self.restitution * point.normal_speed);
let new_impulse = (point.normal_part.impulse + impulse).max(0.0);
impulse = new_impulse - point.normal_part.impulse;
point.normal_part.impulse = new_impulse;
point.normal_part.total_impulse += impulse;
let impulse = impulse * self.normal;
body1.linear_velocity -= impulse * inv_mass1;
body1.angular_velocity -= inv_angular_inertia1 * cross(r1, impulse);
body2.linear_velocity += impulse * inv_mass2;
body2.angular_velocity += inv_angular_inertia2 * cross(r2, impulse);
}
}
#[inline(always)]
pub fn tangent_directions(&self) -> [Vector; DIM - 1] {
#[cfg(feature = "2d")]
{
[Vector::new(self.normal.y, -self.normal.x)]
}
#[cfg(feature = "3d")]
{
[self.tangent1, self.tangent1.cross(self.normal)]
}
}
}
#[allow(unused_variables)]
#[inline(always)]
fn compute_tangent_directions(
normal: Vector,
velocity1: Vector,
velocity2: Vector,
) -> [Vector; DIM - 1] {
#[cfg(feature = "2d")]
{
[Vector::new(normal.y, -normal.x)]
}
#[cfg(feature = "3d")]
{
let force_direction = -normal;
let relative_velocity = velocity1 - velocity2;
let tangent_velocity =
relative_velocity - force_direction * force_direction.dot(relative_velocity);
let tangent = tangent_velocity
.try_normalize()
.unwrap_or(force_direction.any_orthonormal_vector());
let bitangent = force_direction.cross(tangent);
[tangent, bitangent]
}
}
impl MapEntities for ContactConstraint {
fn map_entities<M: EntityMapper>(&mut self, entity_mapper: &mut M) {
self.body1 = entity_mapper.get_mapped(self.body1);
self.body2 = entity_mapper.get_mapped(self.body2);
}
}