use super::{CommonData, Mask, Object};
use crate::{object::contact::Contact, world::aabb::Aabb};
use alloc::{boxed::Box, sync::Arc, vec::Vec};
use bvh_arena::VolumeHandle;
use delegate::delegate;
use nalgebra_glm::is_null;
use parry::{
math::{Isometry, Real, Translation, Vector},
query::ShapeCastHit,
shape::Shape,
};
pub struct KinematicBody<P = ()> {
common: CommonData<P>,
layer: Mask,
mask: Mask,
weight: Real,
bounce: bool,
pub velocity: Vector<Real>,
next_isometry: Isometry<Real>,
#[allow(clippy::vec_box)]
contacts: Vec<Box<Contact<P>>>,
}
impl<P> KinematicBody<P> {
pub fn new(
shape: Arc<dyn Shape>,
isometry: Isometry<Real>,
payload: P,
layer: Mask,
mask: Mask,
weight: Real,
bounce: bool,
) -> Self {
Self {
common: CommonData::new(shape, isometry, payload),
layer,
mask,
weight,
bounce,
velocity: Vector::zeros(),
next_isometry: isometry,
contacts: Vec::new(),
}
}
}
impl<P> Object for KinematicBody<P> {
type Payload = P;
delegate! {
to self.common {
#[inline] fn set_handle(&mut self, handle: VolumeHandle);
#[inline] fn unset_handle(&mut self);
#[inline] fn handle(&self) -> Option<VolumeHandle>;
#[inline] fn shape(&self) -> &dyn Shape;
#[inline] fn isometry(&self) -> &Isometry<Real>;
#[inline] fn payload(&self) -> &P;
#[inline] fn payload_mut(&mut self) -> &mut P;
}
}
#[inline]
fn aabb(&self) -> Aabb {
Aabb::new(
self.common
.shape
.compute_swept_aabb(&self.common.isometry, &self.next_isometry),
self.layer,
self.mask,
)
}
#[inline]
fn layer(&self) -> Mask {
self.layer
}
#[inline]
fn mask(&self) -> Mask {
self.mask
}
#[inline]
fn velocity(&self) -> Vector<Real> {
self.velocity
}
#[inline]
fn as_kinematic(&self) -> Option<&KinematicBody<Self::Payload>> {
Some(self)
}
}
impl<P> KinematicBody<P> {
pub fn pre_update(&mut self, delta_time: Real) {
self.common.isometry = self.next_isometry;
let translation = Translation::from(self.velocity * delta_time);
self.next_isometry.append_translation_mut(&translation);
self.contacts.clear();
}
#[inline]
pub fn weight(&self) -> Real {
self.weight
}
#[inline]
pub fn next_isometry(&self) -> &Isometry<Real> {
&self.next_isometry
}
#[inline]
pub fn add_contact(&mut self, hit: ShapeCastHit, other_weight: Option<Real>, payload: P) {
let weight_ratio = if let Some(w) = other_weight {
1.0 - (self.weight / (self.weight + w))
} else {
1.0
};
self.contacts
.push(Box::new(Contact::new(hit, weight_ratio, payload)));
}
pub fn apply_contacts(&mut self, delta_time: Real, epsilon: Real) {
self.contacts.sort_by(|a, b| a.order(b, epsilon));
let mut offset = Vector::<Real>::zeros();
for contact in self.contacts.iter() {
let hit = contact.hit();
let normal = hit.normal1.into_inner();
let ratio = contact.weight_ratio();
offset -= normal * (hit.time_of_impact * ratio);
let dot = normal.dot(&self.velocity);
let push_back = normal * (dot * ratio);
if dot > 0.0 {
self.velocity -= push_back;
} else if self.bounce {
self.velocity += push_back;
}
}
if !is_null(&offset, epsilon) {
let translation = Translation::from(offset * delta_time);
self.next_isometry.append_translation_mut(&translation);
}
}
}