use glam_det::Point3;
use strum_macros::EnumCount as EnumCountMacro;
use super::ConvexHull;
use crate::ray::{Raycast, RaycastHitResult};
use crate::shapes::container::{ComplexShapeId as ConvexHullId, ComplexShapeId};
use crate::shapes::{Capsule, Cuboid, Cylinder, InfinitePlane, ShapeContainer, Sphere};
use crate::traits::{ContainsPoint, ContainsResult, Expansion, SignedDistanceToPoint};
use crate::{Aabb3, ComputeAabb3, ComputeVolume, Ray};
#[repr(C)]
#[derive(Debug, Clone, Copy, PartialEq, EnumCountMacro, strum_macros::AsRefStr)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum Shape {
Cuboid(Cuboid),
Sphere(Sphere),
Capsule(Capsule),
Cylinder(Cylinder),
ConvexHull(ConvexHullId),
InfinitePlane(InfinitePlane),
Custom {
plugin_id: u64,
complex_shape_id: ComplexShapeId,
},
}
macro_rules! impl_shape_from_types {
($($type:ident),*) => {
$(
impl From<$type> for Shape {
#[inline]
fn from(shape: $type) -> Self {
Self::$type(shape)
}
}
)*
};
}
impl_shape_from_types!(Cuboid, Sphere, Capsule, Cylinder, InfinitePlane);
#[derive(Clone, Copy)]
pub struct ShapeRef<'a> {
pub value: Shape,
pub container: &'a ShapeContainer,
}
impl<'a> ShapeRef<'a> {
#[must_use]
pub fn compute_aabb(&self) -> Aabb3 {
match &self.value {
Shape::Cuboid(s) => s.compute_aabb(),
Shape::Sphere(s) => s.compute_aabb(),
Shape::Capsule(s) => s.compute_aabb(),
Shape::InfinitePlane(s) => s.compute_aabb(),
Shape::Cylinder(s) => s.compute_aabb(),
Shape::ConvexHull(id) => self
.container
.get_shape_set_by_id(ConvexHull::PLUGIN_ID)
.compute_aabb(*id),
Shape::Custom {
plugin_id,
complex_shape_id,
} => self
.container
.get_shape_set_by_id(*plugin_id)
.compute_aabb(*complex_shape_id),
}
}
#[must_use]
pub fn max_radius_and_max_angular_expansion(&self) -> (f32, f32) {
match &self.value {
Shape::Cuboid(s) => s.max_radius_and_max_angular_expansion(),
Shape::Sphere(s) => s.max_radius_and_max_angular_expansion(),
Shape::Capsule(s) => s.max_radius_and_max_angular_expansion(),
Shape::InfinitePlane(s) => s.max_radius_and_max_angular_expansion(),
Shape::Cylinder(s) => s.max_radius_and_max_angular_expansion(),
Shape::ConvexHull(id) => self
.container
.get_shape_set_by_id(ConvexHull::PLUGIN_ID)
.max_radius_and_max_angular_expansion(*id),
Shape::Custom {
plugin_id,
complex_shape_id,
} => self
.container
.get_shape_set_by_id(*plugin_id)
.max_radius_and_max_angular_expansion(*complex_shape_id),
}
}
#[inline]
#[must_use]
pub fn raycast(
&self,
local_ray: Ray,
max_distance: f32,
discard_inside_hit: bool,
) -> Option<RaycastHitResult> {
match &self.value {
Shape::Cuboid(s) => s.raycast(local_ray, max_distance, discard_inside_hit),
Shape::Sphere(s) => s.raycast(local_ray, max_distance, discard_inside_hit),
Shape::Capsule(s) => s.raycast(local_ray, max_distance, discard_inside_hit),
Shape::InfinitePlane(s) => s.raycast(local_ray, max_distance, discard_inside_hit),
Shape::Cylinder(s) => s.raycast(local_ray, max_distance, discard_inside_hit),
Shape::ConvexHull(id) => self
.container
.get_shape_set_by_id(ConvexHull::PLUGIN_ID)
.raycast(*id, local_ray, max_distance, discard_inside_hit),
Shape::Custom {
plugin_id,
complex_shape_id,
} => self.container.get_shape_set_by_id(*plugin_id).raycast(
*complex_shape_id,
local_ray,
max_distance,
discard_inside_hit,
),
}
}
#[inline]
#[must_use]
pub fn contains_point(&self, local_point: Point3) -> ContainsResult {
self.contains_point_with_threshold(local_point, f32::EPSILON)
}
#[inline]
#[must_use]
pub fn contains_point_with_threshold(
&self,
local_point: Point3,
threshold: f32,
) -> ContainsResult {
match &self.value {
Shape::Cuboid(s) => s.contains_point_with_threshold(local_point, threshold),
Shape::Sphere(s) => s.contains_point_with_threshold(local_point, threshold),
Shape::Capsule(s) => s.contains_point_with_threshold(local_point, threshold),
Shape::InfinitePlane(s) => s.contains_point_with_threshold(local_point, threshold),
Shape::Cylinder(s) => s.contains_point_with_threshold(local_point, threshold),
Shape::ConvexHull(id) => {
self.container
.get_shape_set_by_id(ConvexHull::PLUGIN_ID)
.contains_point_with_threshold(*id, local_point, threshold)
}
Shape::Custom {
plugin_id,
complex_shape_id,
} => self
.container
.get_shape_set_by_id(*plugin_id)
.contains_point_with_threshold(*complex_shape_id, local_point, threshold),
}
}
#[inline]
#[must_use]
pub fn signed_distance_to_point(&self, local_point: Point3) -> f32 {
match &self.value {
Shape::Cuboid(s) => s.signed_distance_to_point(local_point),
Shape::Sphere(s) => s.signed_distance_to_point(local_point),
Shape::Capsule(s) => s.signed_distance_to_point(local_point),
Shape::InfinitePlane(s) => s.signed_distance_to_point(local_point),
Shape::Cylinder(s) => s.signed_distance_to_point(local_point),
Shape::ConvexHull(_id) => {
todo!("issue #1433");
}
Shape::Custom {
plugin_id,
complex_shape_id,
} => self
.container
.get_shape_set_by_id(*plugin_id)
.signed_distance_to_point(*complex_shape_id, local_point),
}
}
}
impl Shape {
#[inline]
#[must_use]
pub fn new_cuboid(x: f32, y: f32, z: f32) -> Self {
Self::Cuboid(Cuboid::new_xyz(x, y, z))
}
#[inline]
#[must_use]
pub fn new_sphere(radius: f32) -> Self {
Self::Sphere(Sphere::new(radius))
}
#[inline]
#[must_use]
pub fn new_capsule(half_height: f32, radius: f32) -> Self {
Self::Capsule(Capsule::new(half_height, radius))
}
#[inline]
#[must_use]
pub fn new_cylinder(half_height: f32, radius: f32) -> Self {
Self::Cylinder(Cylinder::new(half_height, radius))
}
#[inline]
#[must_use]
pub fn new_infinite_plane() -> Self {
Self::InfinitePlane(InfinitePlane::default())
}
#[inline]
#[must_use]
pub fn new_convex_hull(convex_hull_id: ConvexHullId) -> Self {
Self::ConvexHull(convex_hull_id)
}
}
impl Shape {
#[inline]
#[must_use]
pub fn into_shape_ref(self, container: &ShapeContainer) -> ShapeRef<'_> {
ShapeRef {
value: self,
container,
}
}
}
impl ComputeVolume for Shape {
#[inline]
fn compute_volume(&self) -> f32 {
match self {
Self::InfinitePlane(_) => 0.0,
Self::Cuboid(shape) => shape.compute_volume(),
Self::Sphere(shape) => shape.compute_volume(),
Self::Cylinder(shape) => shape.compute_volume(),
Self::Capsule(shape) => shape.compute_volume(),
_ => {
panic!("Not supported on complex shape. use ShapeRef::volume instead");
}
}
}
}
impl<'a> ComputeVolume for ShapeRef<'a> {
#[inline]
fn compute_volume(&self) -> f32 {
match self.value {
Shape::InfinitePlane(_) => 0.0,
Shape::Cuboid(shape) => shape.compute_volume(),
Shape::Sphere(shape) => shape.compute_volume(),
Shape::Cylinder(shape) => shape.compute_volume(),
Shape::Capsule(shape) => shape.compute_volume(),
Shape::ConvexHull(id) => self
.container
.get_shape_set_by_id(ConvexHull::PLUGIN_ID)
.compute_volume(id),
Shape::Custom {
plugin_id,
complex_shape_id,
} => self
.container
.get_shape_set_by_id(plugin_id)
.compute_volume(complex_shape_id),
}
}
}
#[cfg(test)]
mod tests {
use std::f32::consts::PI;
use approx_det::assert_relative_eq;
use glam_det::*;
use wasm_bindgen_test::{wasm_bindgen_test, wasm_bindgen_test_configure};
use crate::ray::RaycastHitResult;
use crate::{
Capsule, ComputeVolume, ConvexHull, Cuboid, CuboidExt, Cylinder, InfinitePlane, Ray, Shape,
ShapeContainer, Sphere,
};
wasm_bindgen_test_configure!(run_in_browser);
#[test]
#[wasm_bindgen_test]
fn test_expansion() {
let _ = env_logger::builder().is_test(true).try_init();
let mut container = ShapeContainer::default();
let cuboid = Shape::Cuboid(crate::Cuboid::new_xyz(1.0, 2.0, 3.0));
let cuboid_ref = cuboid.into_shape_ref(&container);
let _ = cuboid_ref.max_radius_and_max_angular_expansion();
let sphere = Shape::Sphere(Sphere::new(1.0));
let sphere_ref = sphere.into_shape_ref(&container);
let _ = sphere_ref.max_radius_and_max_angular_expansion();
let capsule = Shape::Capsule(crate::Capsule::new(1.0, 2.0));
let capsule_ref = capsule.into_shape_ref(&container);
let _ = capsule_ref.max_radius_and_max_angular_expansion();
let cylinder = Shape::Cylinder(crate::Cylinder::new(1.0, 2.0));
let cylinder_ref = cylinder.into_shape_ref(&container);
let _ = cylinder_ref.max_radius_and_max_angular_expansion();
let convex_hull = Shape::ConvexHull(container.add(ConvexHull::new_unchecked(&[
Point3::new(0.0, 0.0, 0.0),
Point3::new(1.0, 0.0, 0.0),
Point3::new(0.0, 1.0, 0.0),
Point3::new(1.0, 1.0, 1.0),
])));
let convex_hull_ref = convex_hull.into_shape_ref(&container);
let _ = convex_hull_ref.max_radius_and_max_angular_expansion();
let infinite_plane = Shape::InfinitePlane(crate::InfinitePlane::default());
let infinite_plane_ref = infinite_plane.into_shape_ref(&container);
let _ = infinite_plane_ref.max_radius_and_max_angular_expansion();
}
#[test]
#[wasm_bindgen_test]
fn test_shape_volume() {
let _ = env_logger::builder().is_test(true).try_init();
let shape: Shape = Shape::Cuboid(Cuboid::new(vec3(1.0, 2.0, 3.0)));
assert_relative_eq!(shape.compute_volume(), 6.0);
let shape: Shape = Shape::Cylinder(Cylinder::new(1.0, 2.0));
assert_relative_eq!(shape.compute_volume(), 8.0 * PI);
let shape: Shape = Shape::Capsule(Capsule::new(1.0, 2.0));
assert_relative_eq!(shape.compute_volume(), 32.0 * PI / 3.0 + 8.0 * PI);
let shape: Shape = Shape::Sphere(Sphere::new(1.0));
assert_relative_eq!(shape.compute_volume(), 4.188_790_3);
let shape: Shape = Shape::InfinitePlane(InfinitePlane::default());
assert_relative_eq!(shape.compute_volume(), 0.0);
}
#[test]
fn test_shape_new() {
let shape = Shape::new_cuboid(1.0, 2.0, 3.0);
#[allow(clippy::float_cmp)]
if let Shape::Cuboid(cuboid) = shape {
assert_eq!(cuboid.length(), [1.0, 2.0, 3.0]);
} else {
panic!("Expected a cuboid shape");
}
let shape = Shape::new_sphere(1.0);
if let Shape::Sphere(sphere) = shape {
assert_relative_eq!(sphere.radius(), 1.0, epsilon = f32::EPSILON);
} else {
panic!("Expected a sphere shape");
}
let shape = Shape::new_capsule(2.0, 1.0);
if let Shape::Capsule(capsule) = shape {
assert_relative_eq!(capsule.radius(), 1.0, epsilon = f32::EPSILON);
assert_relative_eq!(capsule.half_height(), 2.0, epsilon = f32::EPSILON);
} else {
panic!("Expected a capsule shape");
}
let shape = Shape::new_cylinder(2.0, 1.0);
if let Shape::Cylinder(cylinder) = shape {
assert_relative_eq!(cylinder.radius(), 1.0, epsilon = f32::EPSILON);
assert_relative_eq!(cylinder.half_height(), 2.0, epsilon = f32::EPSILON);
} else {
panic!("Expected a cylinder shape");
}
let shape = Shape::new_infinite_plane();
if let Shape::InfinitePlane(_) = shape {
} else {
panic!("Expected an infinite plane shape");
}
let mut container = ShapeContainer::default();
let convex_hull_id = container.add(ConvexHull::new_unchecked(&[
Point3::new(0.0, 0.0, 0.0),
Point3::new(1.0, 0.0, 0.0),
Point3::new(0.0, 1.0, 0.0),
Point3::new(1.0, 1.0, 1.0),
]));
let shape = Shape::new_convex_hull(convex_hull_id);
if let Shape::ConvexHull(id) = shape {
assert_eq!(id, convex_hull_id);
} else {
panic!("Expected a convex hull shape");
}
}
#[test]
#[wasm_bindgen_test]
fn test_convex_hull_raycast() {
let _ = env_logger::builder().is_test(true).try_init();
let cuboid = Cuboid::new(Vec3::ONE * 2.0);
let vertice_indexs = 0..8_usize;
let vertices: Vec<Point3> = vertice_indexs
.into_iter()
.map(|i| cuboid.get_vertex(i))
.collect();
let convex_hull = ConvexHull::new_unchecked(&vertices);
let mut shape_batches = ShapeContainer::default();
let id = shape_batches.add(convex_hull);
assert!(shape_batches.get::<ConvexHull>(id).is_some());
let shape = Shape::ConvexHull(id);
let shape_ref = shape.into_shape_ref(&shape_batches);
let ray = Ray::new_with_vec3([0.0, 0.0, 1.0], [0.0, 0.0, 1.0]);
assert_eq!(shape_ref.raycast(ray, 10.0, true), None);
assert_eq!(
shape_ref.raycast(ray, 10.0, false),
Some(RaycastHitResult {
distance: 0.0,
normal: -ray.direction,
})
);
}
}