use glam_det::nums::num_traits::*;
use glam_det::nums::{f32x4, u32x4};
use glam_det::{Isometry3, Point3, Point3x4, Vec3, Vec3x4};
pub use phys_geom::shape::Capsule;
use crate::geometric_tools::distance_squared_point_to_segment;
use crate::traits::{
ArrayGetter, BaseShapeWide, ContainsPoint, ContainsResult, ConvexShape, CreateShapeWide,
Expansion, MinkowskiSupport, MinkowskiSupportResult, MinkowskiSupportResultWide,
MinkowskiSupportWide, SignedDistanceToPoint,
};
use crate::ShapeContainer;
pub trait CapsuleExt {
fn point_a(&self) -> Point3;
fn point_b(&self) -> Point3;
}
impl CapsuleExt for Capsule {
fn point_a(&self) -> Point3 {
Point3::new(0.0, self.half_height(), 0.0)
}
fn point_b(&self) -> Point3 {
Point3::new(0.0, -self.half_height(), 0.0)
}
}
impl MinkowskiSupport for Capsule {
fn support_point(&self, direction: Vec3, transform: &Isometry3) -> MinkowskiSupportResult {
let direction = transform.inverse().transform_vector3(direction);
let result = Point3::new(0.0, self.half_height() * direction.y.signumf(), 0.0)
+ direction.normalize() * self.radius();
MinkowskiSupportResult {
point: transform.transform_point3(result),
point_index: 0,
}
}
}
impl ContainsPoint for Capsule {
fn contains_point_with_threshold(&self, local_point: Point3, threshold: f32) -> ContainsResult {
let radius = self.radius();
let distance_squared = distance_squared_point_to_segment(
local_point,
self.point_a(),
self.point_b(),
self.height(),
);
let radius_squared = radius * radius;
if (distance_squared - radius_squared).absf() < threshold {
ContainsResult::Surface
} else if distance_squared < radius_squared {
ContainsResult::Inside
} else {
ContainsResult::Outside
}
}
}
impl SignedDistanceToPoint for Capsule {
fn signed_distance_to_point(&self, local_point: Point3) -> f32 {
let distance_squared = distance_squared_point_to_segment(
local_point,
self.point_a(),
self.point_b(),
self.height(),
);
let distance = distance_squared.sqrtf();
distance - self.radius()
}
}
impl ConvexShape for Capsule {}
impl Expansion for Capsule {
#[inline]
fn max_radius_and_max_angular_expansion(&self) -> (f32, f32) {
(self.radius() + self.half_height(), self.half_height())
}
}
#[derive(Debug)]
pub struct CapsuleWide {
pub half_height: f32x4,
pub radius: f32x4,
}
impl Default for CapsuleWide {
fn default() -> Self {
Self {
half_height: f32x4::ZERO,
radius: f32x4::ZERO,
}
}
}
impl MinkowskiSupportWide for CapsuleWide {
fn support_point_local(
&self,
local_direction: Vec3x4,
_: Option<&ShapeContainer>,
) -> MinkowskiSupportResultWide {
let point = Point3x4::new(
f32x4::ZERO,
self.half_height * local_direction.y.signumf(),
f32x4::ZERO,
) + local_direction.normalize() * self.radius;
MinkowskiSupportResultWide {
point,
point_index: u32x4::ZERO,
}
}
}
macro_rules! impl_capsule_wide {
($($num:tt),*) => {
$(
impl CreateShapeWide<$num> for CapsuleWide {
fn create<'a>(iter: impl Iterator<Item=&'a Self::TShape> + Clone) -> Self where Self::TShape: 'a {
Self {
half_height: f32x4::from(ArrayGetter::<$num>::get_array4_from_iter(iter.clone().map(#[inline]|capsule| {
capsule.half_height()
}), 0.0)),
radius: f32x4::from(ArrayGetter::<$num>::get_array4_from_iter(iter.clone().map(#[inline]|capsule| {
capsule.radius()
}), 0.0)),
}
}
}
)*
}
}
impl_capsule_wide!(1, 2, 3, 4);
impl BaseShapeWide for CapsuleWide {
type TShape = Capsule;
}
#[cfg(test)]
mod tests {
use approx_det::assert_relative_eq;
use wasm_bindgen_test::*;
use super::*;
use crate::Shape;
#[cfg(test)]
mod signed_distance_tests {
use approx_det::assert_relative_eq;
use super::*;
#[test]
#[wasm_bindgen_test]
fn test_outer() {
let _ = env_logger::builder().is_test(true).try_init();
let container = ShapeContainer::default();
let capsule = Shape::Capsule(Capsule::new(1.0, 1.0)).into_shape_ref(&container);
assert_relative_eq!(
capsule.signed_distance_to_point(Point3::new(0.0, 3.0, 0.0)),
1.0
);
assert_relative_eq!(
capsule.signed_distance_to_point(Point3::new(2.0, 0.0, 0.0)),
1.0
);
assert_relative_eq!(
capsule.signed_distance_to_point(Point3::new(-2.0, 0.0, 0.0)),
1.0
);
assert_relative_eq!(
capsule.signed_distance_to_point(Point3::new(0.0, -3.0, 0.0)),
1.0
);
}
#[test]
#[wasm_bindgen_test]
fn test_inner() {
let _ = env_logger::builder().is_test(true).try_init();
let container = ShapeContainer::default();
let capsule = Shape::Capsule(Capsule::new(1.0, 1.0)).into_shape_ref(&container);
assert_relative_eq!(
capsule.signed_distance_to_point(Point3::new(0.0, 0.0, 0.0)),
-1.0
);
assert_relative_eq!(
capsule.signed_distance_to_point(Point3::new(0.0, 1.0, 0.0)),
-1.0
);
assert_relative_eq!(
capsule.signed_distance_to_point(Point3::new(0.0, -1.0, 0.0)),
-1.0
);
assert_relative_eq!(
capsule.signed_distance_to_point(Point3::new(0.0, 1.5, 0.0)),
-0.5
);
assert_relative_eq!(
capsule.signed_distance_to_point(Point3::new(0.0, -1.5, 0.0)),
-0.5
);
assert_relative_eq!(
capsule.signed_distance_to_point(Point3::new(0.5, 0.0, 0.0)),
-0.5
);
}
#[test]
#[wasm_bindgen_test]
fn test_surface() {
let _ = env_logger::builder().is_test(true).try_init();
let container = ShapeContainer::default();
let capsule = Shape::Capsule(Capsule::new(1.0, 1.0)).into_shape_ref(&container);
assert_relative_eq!(
capsule.signed_distance_to_point(Point3::new(0.0, 2.0, 0.0)),
0.0
);
assert_relative_eq!(
capsule.signed_distance_to_point(Point3::new(0.0, -2.0, 0.0)),
0.0
);
assert_relative_eq!(
capsule.signed_distance_to_point(Point3::new(1.0, 0.0, 0.0)),
0.0
);
}
}
#[test]
#[wasm_bindgen_test]
fn test_contains_point() {
let _ = env_logger::builder().is_test(true).try_init();
let container = ShapeContainer::default();
let capsule = Shape::Capsule(Capsule::new(1.0, 1.0)).into_shape_ref(&container);
assert_eq!(
capsule.contains_point(Point3::new(0.0, 0.0, 0.0)),
ContainsResult::Inside
);
assert_eq!(
capsule.contains_point(Point3::new(0.0, 0.5, 0.0)),
ContainsResult::Inside
);
assert_eq!(
capsule.contains_point(Point3::new(0.0, -0.5, 0.0)),
ContainsResult::Inside
);
assert_eq!(
capsule.contains_point(Point3::new(0.0, 1.0, 0.0)),
ContainsResult::Inside
);
assert_eq!(
capsule.contains_point(Point3::new(0.0, -1.0, 0.0)),
ContainsResult::Inside
);
assert_eq!(
capsule.contains_point(Point3::new(0.0, 1.5, 0.0)),
ContainsResult::Inside
);
assert_eq!(
capsule.contains_point(Point3::new(0.0, -1.5, 0.0)),
ContainsResult::Inside
);
assert_eq!(
capsule.contains_point(Point3::new(0.0, 2.0, 0.0)),
ContainsResult::Surface
);
assert_eq!(
capsule.contains_point(Point3::new(0.0, -2.0, 0.0)),
ContainsResult::Surface
);
assert_eq!(
capsule.contains_point(Point3::new(0.0, 2.1, 0.0)),
ContainsResult::Outside
);
assert_eq!(
capsule.contains_point(Point3::new(0.0, -2.1, 0.0)),
ContainsResult::Outside
);
assert_eq!(
capsule.contains_point(Point3::new(0.0, 2.0, 0.1)),
ContainsResult::Outside
);
assert_eq!(
capsule.contains_point(Point3::new(0.0, -2.0, 0.1)),
ContainsResult::Outside
);
assert_eq!(
capsule.contains_point(Point3::new(0.0, 2.0, -0.1)),
ContainsResult::Outside
);
assert_eq!(
capsule.contains_point(Point3::new(0.0, -2.0, -0.1)),
ContainsResult::Outside
);
assert_eq!(
capsule.contains_point(Point3::new(0.0, 2.1, 0.1)),
ContainsResult::Outside
);
assert_eq!(
capsule.contains_point(Point3::new(0.0, -2.1, 0.1)),
ContainsResult::Outside
);
assert_eq!(
capsule.contains_point(Point3::new(1.0, 0.0, 0.0)),
ContainsResult::Surface
);
assert_eq!(
capsule.contains_point(Point3::new(-1.0, 0.0, 0.0)),
ContainsResult::Surface
);
assert_eq!(
capsule.contains_point(Point3::new(1.0, 0.1, 0.0)),
ContainsResult::Surface
);
assert_eq!(
capsule.contains_point(Point3::new(1.0, 0.0, 0.1)),
ContainsResult::Outside
);
}
#[test]
#[wasm_bindgen_test]
fn test_compute_expand() {
let _ = env_logger::builder().is_test(true).try_init();
let capsule = Capsule::new(1.0, 1.0);
let (max_radius, max_angular_expansion) = capsule.max_radius_and_max_angular_expansion();
assert_relative_eq!(max_radius, 2.0f32);
assert_relative_eq!(max_angular_expansion, 1.0f32);
}
}