use either::Either;
use crate::object;
use crate::math::*;
use super::{Constraint, Impulses, PostImpulses};
use super::contact::{CONTACT_DISTANCE, RESTING_VELOCITY};
#[cfg(feature = "derive_serdes")]
use serde::{Deserialize, Serialize};
#[cfg_attr(feature = "derive_serdes", derive(Deserialize, Serialize))]
#[derive(Clone, Debug, PartialEq)]
pub struct Distance {
pub interval : geometry::Interval <NonNegative <f64>>
}
impl Distance {
#[inline]
pub const fn new (interval : geometry::Interval <NonNegative <f64>>) -> Self {
Distance { interval }
}
}
impl Constraint for Distance {
fn constrain_velocity (&self,
mut object_a : Either <&object::Static, &mut object::Dynamic>,
object_b : &mut object::Dynamic,
pseudovelocity_a : Option <Vector3 <f64>>,
pseudovelocity_b : Vector3 <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 position_a = match &object_a {
Either::Left (object_a) => object_a.position,
Either::Right (object_a) => object_a.position
};
let position_b = object_b.position;
let a_to_b = position_b.0 - position_a.0;
let distance = a_to_b.norm();
let a_to_b_normalized = a_to_b / *distance;
let velocity_effective_relative = velocity_effective_b - velocity_effective_a;
let velocity_normal_component = velocity_effective_relative.dot (a_to_b_normalized);
let impulses = {
let (impulse_normal_a, impulse_normal_b) = if
*distance >= (*self.interval.max() - CONTACT_DISTANCE)
&& velocity_normal_component > RESTING_VELOCITY * RESTING_VELOCITY
{
let dvelocity = a_to_b_normalized.dot (velocity_effective_relative);
match &mut object_a {
Either::Right (object_a) => {
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 impulse_normal_a = a_to_b_normalized * dvelocity * mass_relative_a;
let impulse_normal_b = -a_to_b_normalized * dvelocity * mass_relative_b;
(impulse_normal_a, impulse_normal_b)
}
Either::Left (_) => {
let impulse_normal = -a_to_b_normalized * dvelocity;
object_b.derivatives.velocity += impulse_normal;
(Vector3::zero(), impulse_normal)
}
}
} else if distance <= self.interval.min()
&& velocity_normal_component < RESTING_VELOCITY * RESTING_VELOCITY
{
unimplemented!("TODO")
} else {
return None
};
Impulses { impulse_normal_a, impulse_normal_b, .. Impulses::default() }
};
match &mut object_a {
Either::Right (obj_a) => obj_a.derivatives.velocity += impulses.impulse_a(),
Either::Left (_) => {}
}
object_b.derivatives.velocity += impulses.impulse_b();
Some (impulses)
}
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 position_a = match &object_a {
Either::Left (object_a) => object_a.position,
Either::Right (object_a) => object_a.position
};
let position_b = object_b.position;
let a_to_b = position_b.0 - position_a.0;
let distance = a_to_b.norm();
if distance > self.interval.max() {
let excess = *distance - *self.interval.max();
let a_to_b_normalized = a_to_b / *distance;
let (pseudo_impulse_a, pseudo_impulse_b) = match &mut object_a {
Either::Left (_) => {
object::lerp (object_b, *pseudovelocity_b, t_reverse);
let pseudo_impulse_b =
-(CONTACT_DISTANCE.mul_add (0.5, excess) * a_to_b_normalized) / t_forward;
*pseudovelocity_b += pseudo_impulse_b;
object::lerp (object_b, *pseudovelocity_b, t_forward);
(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 =
(CONTACT_DISTANCE.mul_add (0.5, excess) * a_to_b_normalized) / t_forward;
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);
(pseudo_impulse_a, pseudo_impulse_b)
}
};
#[expect(clippy::suboptimal_flops)]
if cfg!(debug_assertions) {
let position_a = match &object_a {
Either::Left (object_a) => object_a.position,
Either::Right (object_a) => object_a.position
};
let position_b = object_b.position;
let distance = *(position_b.0 - position_a.0).norm();
log::trace!(distance; "position corrected distance @ T==1.0");
debug_assert!(distance >= 0.0);
approx::assert_abs_diff_eq!(
distance, *self.interval.max() - 0.5 * CONTACT_DISTANCE,
epsilon=0.000_000_001);
}
impulses = Some (PostImpulses { pseudo_impulse_a, pseudo_impulse_b });
} else if distance < self.interval.min() {
unimplemented!("TODO")
}
impulses
}
}