use derive_more::From;
use either::Either;
#[cfg(feature = "derive_serdes")]
use serde::{Deserialize, Serialize};
use crate::collision::Proximity;
use crate::object;
use crate::math::*;
use super::{Constraint, Impulses, PostImpulses};
pub const CONTACT_DISTANCE : f64 = 0.005; pub const RESTING_VELOCITY : f64 = (1.0/120.0) * CONTACT_DISTANCE;
#[cfg_attr(feature = "derive_serdes", derive(Deserialize, Serialize))]
#[derive(Clone, Debug, PartialEq, From)]
pub struct Contact {
pub normal : Unit3 <f64>,
pub distance : f64
}
impl Contact {
#[inline]
pub const fn new (normal : Unit3 <f64>, distance : f64) -> Self {
Contact { normal, distance }
}
pub(crate) fn constrain_velocity_with_restitution (&self,
mut object_a : Either <&object::Static, &mut object::Dynamic>,
object_b : &mut object::Dynamic,
pseudovelocity_a : Option <Vector3 <f64>>,
pseudovelocity_b : Vector3 <f64>,
restitution : Normalized <f64>
) -> Option <Impulses> {
let velocity_effective_a = match &object_a {
Either::Left (_) => {
debug_assert!(pseudovelocity_a.is_none());
Vector3::zero()
}
Either::Right (object_a) => object_a.derivatives.velocity +
pseudovelocity_a.unwrap_or (Vector3::zero())
};
let velocity_effective_b = object_b.derivatives.velocity + pseudovelocity_b;
let velocity_effective_relative = velocity_effective_a - velocity_effective_b;
let velocity_normal_component = velocity_effective_relative.dot (*self.normal);
let mut impulses = None;
let mut mass_relative_a = 0.0;
let mut mass_relative_b = 0.0;
if velocity_normal_component < 0.0 {
let velocity_normal = velocity_normal_component * *self.normal;
let impulse_normal = velocity_normal + *restitution * velocity_normal;
let impulse_normal_magnitude2 = impulse_normal.magnitude_squared();
if impulse_normal_magnitude2 < RESTING_VELOCITY * RESTING_VELOCITY {
return None
}
let (impulse_normal_a, impulse_normal_b) = match &mut object_a {
Either::Right (object_a) => {
let mass_combined = object_a.mass + object_b.mass;
mass_relative_a = object_a.mass.mass() * mass_combined.mass_reciprocal();
mass_relative_b = object_b.mass.mass() * mass_combined.mass_reciprocal();
let impulse_normal_a = -mass_relative_b * impulse_normal;
let impulse_normal_b = mass_relative_a * impulse_normal;
object_a.derivatives.velocity += impulse_normal_a;
object_b.derivatives.velocity += impulse_normal_b;
(impulse_normal_a, impulse_normal_b)
}
Either::Left (_) => {
object_b.derivatives.velocity += impulse_normal;
(Vector3::zero(), impulse_normal)
}
};
let (impulse_tangent_a, impulse_tangent_b) = {
let velocity_tangent = velocity_effective_relative - velocity_normal;
let velocity_tangent_magnitude2 = velocity_tangent.magnitude_squared();
if velocity_tangent_magnitude2 > 0.0 {
let friction_a = match &object_a {
Either::Left (x) => x.material.friction,
Either::Right (x) => x.material.friction
};
let friction_coefficient = friction_a * object_b.material.friction;
let impulse_tangent_potential_magnitude2 =
*friction_coefficient * impulse_normal_magnitude2;
let impulse_tangent = if impulse_tangent_potential_magnitude2 <
velocity_tangent_magnitude2
{
(impulse_tangent_potential_magnitude2.sqrt()
/ velocity_tangent_magnitude2.sqrt()) * velocity_tangent
} else {
velocity_tangent
};
match object_a {
Either::Right (object_a) => {
let impulse_tangent_a = -mass_relative_b * impulse_tangent;
let impulse_tangent_b = mass_relative_a * impulse_tangent;
object_a.derivatives.velocity += impulse_tangent_a;
object_b.derivatives.velocity += impulse_tangent_b;
(impulse_tangent_a, impulse_tangent_b)
}
Either::Left (_) => {
object_b.derivatives.velocity += impulse_tangent;
(Vector3::zero(), impulse_tangent)
}
}
} else {
(Vector3::zero(), Vector3::zero())
}
};
impulses = Some (Impulses {
impulse_normal_a, impulse_normal_b,
impulse_tangent_a, impulse_tangent_b
});
}
impulses
}
}
impl Constraint for Contact {
fn constrain_velocity (&self,
object_a : Either <&object::Static, &mut object::Dynamic>,
object_b : &mut object::Dynamic,
pseudovelocity_a : Option <Vector3 <f64>>,
pseudovelocity_b : Vector3 <f64>
) -> Option <Impulses> {
self.constrain_velocity_with_restitution (
object_a, object_b, pseudovelocity_a, pseudovelocity_b, Normalized::zero())
}
fn constrain_position (&mut self,
t_reverse : f64,
mut object_a : Either <&object::Static, &mut object::Dynamic>,
object_b : &mut object::Dynamic,
pseudovelocity_a : Option <&mut Vector3 <f64>>,
pseudovelocity_b : &mut Vector3 <f64>
) -> Option <PostImpulses> {
let t_forward = -t_reverse;
let mut impulses = None;
let mut proximity = match &object_a {
Either::Left (object_a) => Proximity::query (*object_a, object_b),
Either::Right (object_a) => Proximity::query (*object_a, object_b)
};
log::trace!(distance=proximity.distance; "post impulse contact @ T==1.0");
if cfg!(debug_assertions) {
approx::assert_abs_diff_eq!(proximity.normal.cross (proximity.half_axis),
Vector3::zero());
}
let normal = proximity.normal;
if proximity.distance < 0.0 {
let a_to_b = -proximity.distance * *proximity.normal;
let pseudo_impulse = (a_to_b + 0.5 * CONTACT_DISTANCE * *proximity.normal)
/ t_forward;
let (pseudo_impulse_a, pseudo_impulse_b) = match &mut object_a {
Either::Left (object_a) => {
object::lerp (object_b, *pseudovelocity_b, t_reverse);
let pseudo_impulse_b = -pseudo_impulse;
*pseudovelocity_b += pseudo_impulse_b;
object::lerp (object_b, *pseudovelocity_b, t_forward);
proximity = Proximity::query (*object_a, object_b);
(Vector3::zero(), pseudo_impulse_b)
}
Either::Right (object_a) => {
let pseudovelocity_a = pseudovelocity_a.unwrap();
object::lerp (*object_a, *pseudovelocity_a, t_reverse);
object::lerp (object_b, *pseudovelocity_b, t_reverse);
let mass_combined = object_a.mass + object_b.mass;
let mass_relative_a = object_a.mass.mass() * mass_combined.mass_reciprocal();
let mass_relative_b = object_b.mass.mass() * mass_combined.mass_reciprocal();
let pseudo_impulse_a = mass_relative_b * pseudo_impulse;
let pseudo_impulse_b = -mass_relative_a * pseudo_impulse;
*pseudovelocity_a += pseudo_impulse_a;
*pseudovelocity_b += pseudo_impulse_b;
object::lerp (*object_a, *pseudovelocity_a, t_forward);
object::lerp (object_b, *pseudovelocity_b, t_forward);
proximity = Proximity::query (*object_a, object_b);
(pseudo_impulse_a, pseudo_impulse_b)
}
};
log::trace!(distance=proximity.distance; "position corrected distance @ T==1.0");
debug_assert!(proximity.distance >= 0.0);
if cfg!(debug_assertions) {
approx::assert_relative_eq!(*proximity.normal, *normal, epsilon=0.000_000_1);
approx::assert_abs_diff_eq!(
proximity.distance, 0.5 * CONTACT_DISTANCE, epsilon=0.000_000_001);
}
impulses = Some (PostImpulses { pseudo_impulse_a, pseudo_impulse_b });
}
*self = Contact { normal: proximity.normal, distance: proximity.distance };
impulses
}
}
impl Eq for Contact { }
#[expect(clippy::non_canonical_partial_ord_impl)]
impl PartialOrd for Contact {
fn partial_cmp (&self, _rhs : &Self) -> Option <std::cmp::Ordering> {
unreachable!()
}
}
impl Ord for Contact {
fn cmp (&self, _other : &Self) -> std::cmp::Ordering {
unreachable!()
}
}
impl TryFrom <Proximity> for Contact {
type Error = ();
fn try_from (Proximity { distance, normal, .. } : Proximity)
-> Result <Self, Self::Error>
{
if distance >= 0.0 && distance < CONTACT_DISTANCE {
Ok (Contact { normal, distance })
} else {
Err (())
}
}
}