use he3d_math::Vec3;
use crate::body::BodyId;
use crate::static_geometry::StaticMeshId;
pub(crate) const MAX_MANIFOLD_POINTS: usize = 4;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub enum ContactPhase {
Begin,
Persist,
End,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub enum ContactPartner {
Body(BodyId),
Static(StaticMeshId),
}
#[derive(Clone, Copy, Debug, PartialEq)]
#[non_exhaustive]
pub struct Contact {
pub a: BodyId,
pub b: ContactPartner,
pub point: Vec3,
pub normal: Vec3,
pub depth: f32,
pub phase: ContactPhase,
}
impl Contact {
#[must_use]
pub fn involves(&self, id: BodyId) -> bool {
self.a == id || self.b == ContactPartner::Body(id)
}
}
#[derive(Clone, Copy, Debug, Default)]
pub(crate) struct ManifoldPoint {
pub position: Vec3,
pub depth: f32,
pub feature: u32,
}
#[derive(Clone, Copy, Debug)]
pub(crate) struct Manifold {
pub normal: Vec3,
pub points: [ManifoldPoint; MAX_MANIFOLD_POINTS],
pub count: usize,
}
impl Default for Manifold {
fn default() -> Self {
Self {
normal: Vec3::Z,
points: [ManifoldPoint::default(); MAX_MANIFOLD_POINTS],
count: 0,
}
}
}
impl Manifold {
pub(crate) fn empty() -> Self {
Self::default()
}
pub(crate) fn push(&mut self, point: ManifoldPoint) {
if self.count < MAX_MANIFOLD_POINTS {
self.points[self.count] = point;
self.count += 1;
}
}
}
#[derive(Clone, Copy, Debug, Default)]
pub(crate) struct ConstraintPoint {
pub position: Vec3,
pub anchor_a: Vec3,
pub anchor_b: Vec3,
pub depth: f32,
pub feature: u32,
pub normal_impulse: f32,
pub tangent_impulse: [f32; 2],
pub normal_mass: f32,
pub tangent_mass: [f32; 2],
pub angular_response: [[Vec3; 3]; 2],
pub bias: f32,
pub position_bias: f32,
pub position_impulse: f32,
}
#[derive(Clone, Copy, Debug)]
pub(crate) struct ContactConstraint {
pub id_a: BodyId,
pub id_b: ContactPartner,
pub a: u32,
pub b: u32,
pub partner_key: u64,
pub normal: Vec3,
pub tangents: [Vec3; 2],
pub friction: f32,
pub point_count: u8,
pub points: [ConstraintPoint; MAX_MANIFOLD_POINTS],
}
impl Default for ContactConstraint {
fn default() -> Self {
Self {
id_a: BodyId::new(0, 0),
id_b: ContactPartner::Body(BodyId::new(0, 0)),
a: 0,
b: 0,
partner_key: 0,
normal: Vec3::Z,
tangents: [Vec3::X, Vec3::Y],
friction: 0.0,
point_count: 0,
points: [ConstraintPoint::default(); MAX_MANIFOLD_POINTS],
}
}
}
impl ContactConstraint {
pub(crate) fn points(&self) -> &[ConstraintPoint] {
&self.points[..usize::from(self.point_count)]
}
pub(crate) fn points_mut(&mut self) -> &mut [ConstraintPoint] {
let count = usize::from(self.point_count);
&mut self.points[..count]
}
pub(crate) fn sort_key(&self) -> (u32, u32, u64) {
(self.a, self.b, self.partner_key)
}
}
pub(crate) fn tangent_basis(normal: Vec3) -> [Vec3; 2] {
let helper = if normal.x.abs() > normal.y.abs() {
Vec3::new(0.0, 1.0, 0.0)
} else {
Vec3::new(1.0, 0.0, 0.0)
};
let first = normal.cross(helper).normalize_or_zero();
let second = normal.cross(first);
[first, second]
}
#[cfg(test)]
#[allow(clippy::cast_precision_loss)]
mod tests {
use super::{ContactConstraint, Manifold, ManifoldPoint, tangent_basis};
use he3d_math::Vec3;
#[test]
fn a_manifold_stops_at_its_capacity() {
let mut manifold = Manifold::empty();
for index in 0..10 {
manifold.push(ManifoldPoint {
depth: index as f32,
..ManifoldPoint::default()
});
}
assert_eq!(manifold.count, 4);
}
#[test]
fn tangents_are_orthonormal_to_the_normal() {
for normal in [
Vec3::X,
Vec3::Y,
Vec3::Z,
Vec3::new(1.0, 2.0, 3.0).normalize(),
] {
let [t0, t1] = tangent_basis(normal);
assert!(normal.dot(t0).abs() < 1e-5, "t0 not orthogonal");
assert!(normal.dot(t1).abs() < 1e-5, "t1 not orthogonal");
assert!(t0.dot(t1).abs() < 1e-5, "tangents not orthogonal");
assert!((t0.length() - 1.0).abs() < 1e-5);
assert!((t1.length() - 1.0).abs() < 1e-5);
}
}
#[test]
fn an_empty_constraint_has_no_points() {
let constraint = ContactConstraint::default();
assert!(constraint.points().is_empty());
}
}