mod normal_part;
mod tangent_part;
pub use normal_part::ContactNormalPart;
pub use tangent_part::ContactTangentPart;
use crate::{dynamics::solver::softness_parameters::SoftnessCoefficients, prelude::*};
use bevy::{
ecs::entity::{Entity, EntityMapper, MapEntities},
reflect::Reflect,
utils::default,
};
#[derive(Clone, Debug, PartialEq, Reflect)]
pub struct ContactConstraintPoint {
pub normal_part: ContactNormalPart,
pub tangent_part: Option<ContactTangentPart>,
pub max_normal_impulse: Scalar,
pub local_anchor1: Vector,
pub local_anchor2: Vector,
pub anchor1: Vector,
pub anchor2: Vector,
pub normal_speed: Scalar,
pub initial_separation: Scalar,
}
#[derive(Clone, Debug, PartialEq, Reflect)]
pub struct ContactConstraint {
pub entity1: Entity,
pub entity2: Entity,
pub collider_entity1: Entity,
pub collider_entity2: Entity,
pub friction: Friction,
pub restitution: Restitution,
pub normal: Vector,
pub points: Vec<ContactConstraintPoint>,
pub manifold_index: usize,
}
impl ContactConstraint {
#[allow(clippy::too_many_arguments)]
pub fn generate(
manifold_id: usize,
manifold: &ContactManifold,
body1: &RigidBodyQueryReadOnlyItem,
body2: &RigidBodyQueryReadOnlyItem,
collider_entity1: Entity,
collider_entity2: Entity,
collider_transform1: Option<ColliderTransform>,
collider_transform2: Option<ColliderTransform>,
collision_margin: impl Into<CollisionMargin>,
speculative_margin: impl Into<SpeculativeMargin>,
friction: Friction,
restitution: Restitution,
softness: SoftnessCoefficients,
warm_start: bool,
delta_secs: Scalar,
) -> Self {
let collision_margin: Scalar = collision_margin.into().0;
let speculative_margin: Scalar = speculative_margin.into().0;
let local_normal1 = collider_transform1.map_or(manifold.normal1, |transform| {
transform.rotation * manifold.normal1
});
let normal = *body1.rotation * local_normal1;
let inverse_mass_sum = body1.inv_mass() + body2.inv_mass();
let i1 = body1.effective_world_inv_inertia();
let i2 = body2.effective_world_inv_inertia();
let mut constraint = Self {
entity1: body1.entity,
entity2: body2.entity,
collider_entity1,
collider_entity2,
friction,
restitution,
normal,
points: Vec::with_capacity(manifold.contacts.len()),
manifold_index: manifold_id,
};
let tangents =
constraint.tangent_directions(body1.linear_velocity.0, body2.linear_velocity.0);
for mut contact in manifold.contacts.iter().copied() {
if let Some(transform) = collider_transform1 {
contact.point1 = transform.rotation * contact.point1 + transform.translation;
}
if let Some(transform) = collider_transform2 {
contact.point2 = transform.rotation * contact.point2 + transform.translation;
}
contact.penetration += collision_margin;
let effective_distance = -contact.penetration;
let local_anchor1 = contact.point1 - body1.center_of_mass.0;
let local_anchor2 = contact.point2 - body2.center_of_mass.0;
let r1 = *body1.rotation * local_anchor1;
let r2 = *body2.rotation * local_anchor2;
let relative_velocity = body2.velocity_at_point(r2) - body1.velocity_at_point(r1);
let keep_contact = effective_distance < speculative_margin || {
let delta_distance = relative_velocity.dot(normal) * delta_secs;
effective_distance + delta_distance < speculative_margin
};
if !keep_contact {
continue;
}
let point = ContactConstraintPoint {
normal_part: ContactNormalPart::generate(
inverse_mass_sum,
i1,
i2,
r1,
r2,
normal,
warm_start.then_some(contact.normal_impulse),
softness,
),
tangent_part: (friction.dynamic_coefficient > 0.0).then_some(
ContactTangentPart::generate(
inverse_mass_sum,
i1,
i2,
r1,
r2,
tangents,
warm_start.then_some(contact.tangent_impulse),
),
),
max_normal_impulse: 0.0,
local_anchor1,
local_anchor2,
anchor1: r1,
anchor2: r2,
normal_speed: normal.dot(relative_velocity),
initial_separation: -contact.penetration - (r2 - r1).dot(normal),
};
constraint.points.push(point);
}
constraint
}
pub fn warm_start(
&self,
body1: &mut RigidBodyQueryItem,
body2: &mut RigidBodyQueryItem,
normal: Vector,
tangent_directions: [Vector; DIM - 1],
warm_start_coefficient: Scalar,
) {
let inv_mass1 = body1.effective_inv_mass();
let inv_mass2 = body2.effective_inv_mass();
let inv_inertia1 = body1.effective_world_inv_inertia();
let inv_inertia2 = body2.effective_world_inv_inertia();
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 * normal + tangent_impulse * tangent_directions[0]);
#[cfg(feature = "3d")]
let p = warm_start_coefficient
* (point.normal_part.impulse * normal
+ tangent_impulse.x * tangent_directions[0]
+ tangent_impulse.y * tangent_directions[1]);
if body1.rb.is_dynamic() && body1.dominance() <= body2.dominance() {
body1.linear_velocity.0 -= p * inv_mass1;
body1.angular_velocity.0 -= inv_inertia1 * cross(r1, p);
}
if body2.rb.is_dynamic() && body2.dominance() <= body1.dominance() {
body2.linear_velocity.0 += p * inv_mass2;
body2.angular_velocity.0 += inv_inertia2 * cross(r2, p);
}
}
}
pub fn solve(
&mut self,
body1: &mut RigidBodyQueryItem,
body2: &mut RigidBodyQueryItem,
delta_secs: Scalar,
use_bias: bool,
max_overlap_solve_speed: Scalar,
) {
let inv_mass1 = body1.effective_inv_mass();
let inv_mass2 = body2.effective_inv_mass();
let inv_inertia1 = body1.effective_world_inv_inertia();
let inv_inertia2 = body2.effective_world_inv_inertia();
let delta_translation = body2.accumulated_translation.0 - body1.accumulated_translation.0;
for point in self.points.iter_mut() {
let r1 = *body1.rotation * point.local_anchor1;
let r2 = *body2.rotation * point.local_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,
);
point.max_normal_impulse = impulse_magnitude.max(point.max_normal_impulse);
if impulse_magnitude == 0.0 {
continue;
}
let impulse = impulse_magnitude * self.normal;
if body1.rb.is_dynamic() && body1.dominance() <= body2.dominance() {
body1.linear_velocity.0 -= impulse * inv_mass1;
body1.angular_velocity.0 -= inv_inertia1 * cross(r1, impulse);
}
if body2.rb.is_dynamic() && body2.dominance() <= body1.dominance() {
body2.linear_velocity.0 += impulse * inv_mass2;
body2.angular_velocity.0 += inv_inertia2 * cross(r2, impulse);
}
}
let friction = self.friction;
let tangent_directions =
self.tangent_directions(body1.linear_velocity.0, body2.linear_velocity.0);
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,
friction,
point.normal_part.impulse,
);
if body1.rb.is_dynamic() && body1.dominance() <= body2.dominance() {
body1.linear_velocity.0 -= impulse * inv_mass1;
body1.angular_velocity.0 -= inv_inertia1 * cross(r1, impulse);
}
if body2.rb.is_dynamic() && body2.dominance() <= body1.dominance() {
body2.linear_velocity.0 += impulse * inv_mass2;
body2.angular_velocity.0 += inv_inertia2 * cross(r2, impulse);
}
}
}
pub fn apply_restitution(
&mut self,
body1: &mut RigidBodyQueryItem,
body2: &mut RigidBodyQueryItem,
threshold: Scalar,
) {
for point in self.points.iter_mut() {
if point.normal_speed > -threshold || point.max_normal_impulse == 0.0 {
continue;
}
let r1 = point.anchor1;
let r2 = point.anchor2;
let inv_mass1 = body1.effective_inv_mass();
let inv_mass2 = body2.effective_inv_mass();
let inv_inertia1 = body1.effective_world_inv_inertia();
let inv_inertia2 = body2.effective_world_inv_inertia();
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.coefficient * 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.max_normal_impulse = impulse.max(point.max_normal_impulse);
let impulse = impulse * self.normal;
if body1.rb.is_dynamic() && body1.dominance() <= body2.dominance() {
body1.linear_velocity.0 -= impulse * inv_mass1;
body1.angular_velocity.0 -= inv_inertia1 * cross(r1, impulse);
}
if body2.rb.is_dynamic() && body2.dominance() <= body1.dominance() {
body2.linear_velocity.0 += impulse * inv_mass2;
body2.angular_velocity.0 += inv_inertia2 * cross(r2, impulse);
}
}
}
#[allow(unused_variables)]
pub fn tangent_directions(&self, velocity1: Vector, velocity2: Vector) -> [Vector; DIM - 1] {
#[cfg(feature = "2d")]
{
[Vector::new(self.normal.y, -self.normal.x)]
}
#[cfg(feature = "3d")]
{
let force_direction = -self.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.entity1 = entity_mapper.map_entity(self.entity1);
self.entity2 = entity_mapper.map_entity(self.entity2);
}
}