aboutsummaryrefslogtreecommitdiff
path: root/src/geometry
diff options
context:
space:
mode:
Diffstat (limited to 'src/geometry')
-rw-r--r--src/geometry/broad_phase_multi_sap.rs150
-rw-r--r--src/geometry/capsule.rs86
-rw-r--r--src/geometry/collider.rs449
-rw-r--r--src/geometry/collider_set.rs5
-rw-r--r--src/geometry/contact.rs66
-rw-r--r--src/geometry/contact_generator/ball_ball_contact_generator.rs9
-rw-r--r--src/geometry/contact_generator/ball_convex_contact_generator.rs23
-rw-r--r--src/geometry/contact_generator/capsule_capsule_contact_generator.rs39
-rw-r--r--src/geometry/contact_generator/contact_dispatcher.rs106
-rw-r--r--src/geometry/contact_generator/contact_generator.rs30
-rw-r--r--src/geometry/contact_generator/contact_generator_workspace.rs104
-rw-r--r--src/geometry/contact_generator/cuboid_capsule_contact_generator.rs11
-rw-r--r--src/geometry/contact_generator/cuboid_cuboid_contact_generator.rs4
-rw-r--r--src/geometry/contact_generator/cuboid_triangle_contact_generator.rs8
-rw-r--r--src/geometry/contact_generator/heightfield_shape_contact_generator.rs128
-rw-r--r--src/geometry/contact_generator/mod.rs18
-rw-r--r--src/geometry/contact_generator/pfm_pfm_contact_generator.rs144
-rw-r--r--src/geometry/contact_generator/polygon_polygon_contact_generator.rs35
-rw-r--r--src/geometry/contact_generator/serializable_workspace_tag.rs9
-rw-r--r--src/geometry/contact_generator/trimesh_shape_contact_generator.rs42
-rw-r--r--src/geometry/interaction_graph.rs1
-rw-r--r--src/geometry/interaction_groups.rs60
-rw-r--r--src/geometry/mod.rs33
-rw-r--r--src/geometry/narrow_phase.rs227
-rw-r--r--src/geometry/polygon.rs2
-rw-r--r--src/geometry/polygonal_feature_map.rs132
-rw-r--r--src/geometry/polyhedron_feature3d.rs169
-rw-r--r--src/geometry/proximity.rs12
-rw-r--r--src/geometry/proximity_detector/ball_convex_proximity_detector.rs20
-rw-r--r--src/geometry/proximity_detector/cuboid_cuboid_proximity_detector.rs4
-rw-r--r--src/geometry/proximity_detector/cuboid_triangle_proximity_detector.rs8
-rw-r--r--src/geometry/proximity_detector/polygon_polygon_proximity_detector.rs29
-rw-r--r--src/geometry/proximity_detector/proximity_detector.rs8
-rw-r--r--src/geometry/proximity_detector/proximity_dispatcher.rs40
-rw-r--r--src/geometry/proximity_detector/trimesh_shape_proximity_detector.rs11
-rw-r--r--src/geometry/round_cylinder.rs107
-rw-r--r--src/geometry/sat.rs105
-rw-r--r--src/geometry/shape.rs390
-rw-r--r--src/geometry/trimesh.rs45
-rw-r--r--src/geometry/user_callbacks.rs57
-rw-r--r--src/geometry/wquadtree.rs33
41 files changed, 2247 insertions, 712 deletions
diff --git a/src/geometry/broad_phase_multi_sap.rs b/src/geometry/broad_phase_multi_sap.rs
index 8e69d24..4cea113 100644
--- a/src/geometry/broad_phase_multi_sap.rs
+++ b/src/geometry/broad_phase_multi_sap.rs
@@ -1,13 +1,10 @@
+use crate::data::hashmap::HashMap;
use crate::data::pubsub::Subscription;
use crate::dynamics::RigidBodySet;
use crate::geometry::{ColliderHandle, ColliderSet, RemovedCollider};
use crate::math::{Point, Vector, DIM};
-#[cfg(feature = "enhanced-determinism")]
-use crate::utils::FxHashMap32 as HashMap;
use bit_vec::BitVec;
use ncollide::bounding_volume::{BoundingVolume, AABB};
-#[cfg(not(feature = "enhanced-determinism"))]
-use rustc_hash::FxHashMap as HashMap;
use std::cmp::Ordering;
use std::ops::{Index, IndexMut};
@@ -135,6 +132,7 @@ impl Endpoint {
}
#[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))]
+#[derive(Clone)]
struct SAPAxis {
min_bound: f32,
max_bound: f32,
@@ -240,13 +238,14 @@ impl SAPAxis {
}
}
- fn delete_out_of_bounds_proxies(&self, existing_proxies: &mut BitVec) -> bool {
- let mut deleted_any = false;
+ fn delete_out_of_bounds_proxies(&self, existing_proxies: &mut BitVec) -> usize {
+ let mut deleted = 0;
for endpoint in &self.endpoints {
if endpoint.value < self.min_bound {
- if endpoint.is_end() {
- existing_proxies.set(endpoint.proxy() as usize, false);
- deleted_any = true;
+ let proxy_idx = endpoint.proxy() as usize;
+ if endpoint.is_end() && existing_proxies[proxy_idx] {
+ existing_proxies.set(proxy_idx, false);
+ deleted += 1;
}
} else {
break;
@@ -255,16 +254,17 @@ impl SAPAxis {
for endpoint in self.endpoints.iter().rev() {
if endpoint.value > self.max_bound {
- if endpoint.is_start() {
+ let proxy_idx = endpoint.proxy() as usize;
+ if endpoint.is_start() && existing_proxies[proxy_idx] {
existing_proxies.set(endpoint.proxy() as usize, false);
- deleted_any = true;
+ deleted += 1;
}
} else {
break;
}
}
- deleted_any
+ deleted
}
fn delete_out_of_bounds_endpoints(&mut self, existing_proxies: &BitVec) {
@@ -334,27 +334,58 @@ impl SAPAxis {
}
#[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))]
+#[derive(Clone)]
struct SAPRegion {
- axii: [SAPAxis; DIM],
+ axes: [SAPAxis; DIM],
existing_proxies: BitVec,
#[cfg_attr(feature = "serde-serialize", serde(skip))]
to_insert: Vec<usize>, // Workspace
- need_update: bool,
+ update_count: u8,
+ proxy_count: usize,
}
impl SAPRegion {
pub fn new(bounds: AABB<f32>) -> Self {
- let axii = [
+ let axes = [
SAPAxis::new(bounds.mins.x, bounds.maxs.x),
SAPAxis::new(bounds.mins.y, bounds.maxs.y),
#[cfg(feature = "dim3")]
SAPAxis::new(bounds.mins.z, bounds.maxs.z),
];
SAPRegion {
- axii,
+ axes,
existing_proxies: BitVec::new(),
to_insert: Vec::new(),
- need_update: false,
+ update_count: 0,
+ proxy_count: 0,
+ }
+ }
+
+ pub fn recycle(bounds: AABB<f32>, mut old: Self) -> Self {
+ // Correct the bounds
+ for (axis, &bound) in old.axes.iter_mut().zip(bounds.mins.iter()) {
+ axis.min_bound = bound;
+ }
+ for (axis, &bound) in old.axes.iter_mut().zip(bounds.maxs.iter()) {
+ axis.max_bound = bound;
+ }
+
+ old.update_count = 0;
+
+ // The rest of the fields should be "empty"
+ assert_eq!(old.proxy_count, 0);
+ assert!(old.to_insert.is_empty());
+ debug_assert!(!old.existing_proxies.any());
+ assert!(old.axes.iter().all(|ax| ax.endpoints.len() == 2));
+
+ old
+ }
+
+ pub fn recycle_or_new(bounds: AABB<f32>, pool: &mut Vec<Self>) -> Self {
+ if let Some(old) = pool.pop() {
+ Self::recycle(bounds, old)
+ } else {
+ Self::new(bounds)
}
}
@@ -362,7 +393,7 @@ impl SAPRegion {
// We keep the proxy_id as argument for uniformity with the "preupdate"
// method. However we don't actually need it because the deletion will be
// handled transparently during the next update.
- self.need_update = true;
+ self.update_count = 1;
}
pub fn preupdate_proxy(&mut self, proxy_id: usize) -> bool {
@@ -374,51 +405,64 @@ impl SAPRegion {
if !self.existing_proxies[proxy_id] {
self.to_insert.push(proxy_id);
self.existing_proxies.set(proxy_id, true);
+ self.proxy_count += 1;
false
} else {
- self.need_update = true;
+ // Here we need a second update if all proxies exit this region. In this case, we need
+ // to delete the final proxy, but the region may not have AABBs overlapping it, so it
+ // wouldn't get an update otherwise.
+ self.update_count = 2;
true
}
}
pub fn update(&mut self, proxies: &Proxies, reporting: &mut HashMap<(u32, u32), bool>) {
- if self.need_update {
+ if self.update_count > 0 {
// Update endpoints.
- let mut deleted_any = false;
+ let mut deleted = 0;
+
for dim in 0..DIM {
- self.axii[dim].update_endpoints(dim, proxies, reporting);
- deleted_any = self.axii[dim]
- .delete_out_of_bounds_proxies(&mut self.existing_proxies)
- || deleted_any;
+ self.axes[dim].update_endpoints(dim, proxies, reporting);
+ deleted += self.axes[dim].delete_out_of_bounds_proxies(&mut self.existing_proxies);
}
- if deleted_any {
+ if deleted > 0 {
+ self.proxy_count -= deleted;
for dim in 0..DIM {
- self.axii[dim].delete_out_of_bounds_endpoints(&self.existing_proxies);
+ self.axes[dim].delete_out_of_bounds_endpoints(&self.existing_proxies);
}
}
- self.need_update = false;
+ self.update_count -= 1;
}
if !self.to_insert.is_empty() {
// Insert new proxies.
for dim in 1..DIM {
- self.axii[dim].batch_insert(dim, &self.to_insert, proxies, None);
+ self.axes[dim].batch_insert(dim, &self.to_insert, proxies, None);
}
- self.axii[0].batch_insert(0, &self.to_insert, proxies, Some(reporting));
+ self.axes[0].batch_insert(0, &self.to_insert, proxies, Some(reporting));
self.to_insert.clear();
+
+ // In the rare event that all proxies leave this region in the next step, we need an
+ // update to remove them.
+ self.update_count = 1;
}
}
}
/// A broad-phase based on multiple Sweep-and-Prune instances running of disjoint region of the 3D world.
#[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))]
+#[derive(Clone)]
pub struct BroadPhase {
proxies: Proxies,
regions: HashMap<Point<i32>, SAPRegion>,
removed_colliders: Option<Subscription<RemovedCollider>>,
deleted_any: bool,
+ #[cfg_attr(feature = "serde-serialize", serde(skip))]
+ region_pool: Vec<SAPRegion>, // To avoid repeated allocations.
+ #[cfg_attr(feature = "serde-serialize", serde(skip))]
+ regions_to_remove: Vec<Point<i32>>, // Workspace
// We could think serializing this workspace is useless.
// It turns out is is important to serialize at least its capacity
// and restore this capacity when deserializing the hashmap.
@@ -433,14 +477,15 @@ pub struct BroadPhase {
#[cfg_attr(
feature = "serde-serialize",
serde(
- serialize_with = "crate::utils::serialize_hashmap_capacity",
- deserialize_with = "crate::utils::deserialize_hashmap_capacity"
+ serialize_with = "crate::data::hashmap::serialize_hashmap_capacity",
+ deserialize_with = "crate::data::hashmap::deserialize_hashmap_capacity"
)
)]
reporting: HashMap<(u32, u32), bool>, // Workspace
}
#[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))]
+#[derive(Clone)]
pub(crate) struct BroadPhaseProxy {
handle: ColliderHandle,
aabb: AABB<f32>,
@@ -448,6 +493,7 @@ pub(crate) struct BroadPhaseProxy {
}
#[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))]
+#[derive(Clone)]
struct Proxies {
elements: Vec<BroadPhaseProxy>,
first_free: u32,
@@ -509,6 +555,8 @@ impl BroadPhase {
removed_colliders: None,
proxies: Proxies::new(),
regions: HashMap::default(),
+ region_pool: Vec::new(),
+ regions_to_remove: Vec::new(),
reporting: HashMap::default(),
deleted_any: false,
}
@@ -609,15 +657,17 @@ impl BroadPhase {
let start = point_key(aabb.mins);
let end = point_key(aabb.maxs);
+ let regions = &mut self.regions;
+ let pool = &mut self.region_pool;
+
#[cfg(feature = "dim2")]
for i in start.x..=end.x {
for j in start.y..=end.y {
let region_key = Point::new(i, j);
let region_bounds = region_aabb(region_key);
- let region = self
- .regions
+ let region = regions
.entry(region_key)
- .or_insert_with(|| SAPRegion::new(region_bounds));
+ .or_insert_with(|| SAPRegion::recycle_or_new(region_bounds, pool));
let _ = region.preupdate_proxy(proxy_id);
}
}
@@ -628,10 +678,9 @@ impl BroadPhase {
for k in start.z..=end.z {
let region_key = Point::new(i, j, k);
let region_bounds = region_aabb(region_key);
- let region = self
- .regions
+ let region = regions
.entry(region_key)
- .or_insert_with(|| SAPRegion::new(region_bounds));
+ .or_insert_with(|| SAPRegion::recycle_or_new(region_bounds, pool));
let _ = region.preupdate_proxy(proxy_id);
}
}
@@ -640,11 +689,26 @@ impl BroadPhase {
}
}
+ fn update_regions(&mut self) {
+ for (point, region) in &mut self.regions {
+ region.update(&self.proxies, &mut self.reporting);
+ if region.proxy_count == 0 {
+ self.regions_to_remove.push(*point);
+ }
+ }
+
+ // Remove all the empty regions and store them in the region pool
+ let regions = &mut self.regions;
+ self.region_pool.extend(
+ self.regions_to_remove
+ .drain(..)
+ .map(|p| regions.remove(&p).unwrap()),
+ );
+ }
+
pub(crate) fn complete_removals(&mut self) {
if self.deleted_any {
- for (_, region) in &mut self.regions {
- region.update(&self.proxies, &mut self.reporting);
- }
+ self.update_regions();
// NOTE: we don't care about reporting pairs.
self.reporting.clear();
@@ -656,9 +720,7 @@ impl BroadPhase {
// println!("num regions: {}", self.regions.len());
self.reporting.clear();
- for (_, region) in &mut self.regions {
- region.update(&self.proxies, &mut self.reporting)
- }
+ self.update_regions();
// Convert reports to broad phase events.
// let t = instant::now();
diff --git a/src/geometry/capsule.rs b/src/geometry/capsule.rs
index 0d754af..54736cc 100644
--- a/src/geometry/capsule.rs
+++ b/src/geometry/capsule.rs
@@ -1,18 +1,16 @@
-use crate::geometry::AABB;
+use crate::geometry::{Ray, RayIntersection, AABB};
use crate::math::{Isometry, Point, Rotation, Vector};
use approx::AbsDiffEq;
use na::Unit;
-use ncollide::query::{PointProjection, PointQuery};
-use ncollide::shape::{FeatureId, Segment};
+use ncollide::query::{algorithms::VoronoiSimplex, PointProjection, PointQuery, RayCast};
+use ncollide::shape::{FeatureId, Segment, SupportMap};
#[derive(Copy, Clone, Debug)]
#[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))]
-/// A capsule shape defined as a segment with a radius.
+/// A capsule shape defined as a round segment.
pub struct Capsule {
- /// The first endpoint of the capsule.
- pub a: Point<f32>,
- /// The second enpdoint of the capsule.
- pub b: Point<f32>,
+ /// The axis and endpoint of the capsule.
+ pub segment: Segment<f32>,
/// The radius of the capsule.
pub radius: f32,
}
@@ -39,13 +37,14 @@ impl Capsule {
/// Creates a new capsule defined as the segment between `a` and `b` and with the given `radius`.
pub fn new(a: Point<f32>, b: Point<f32>, radius: f32) -> Self {
- Self { a, b, radius }
+ let segment = Segment::new(a, b);
+ Self { segment, radius }
}
/// The axis-aligned bounding box of this capsule.
pub fn aabb(&self, pos: &Isometry<f32>) -> AABB {
- let a = pos * self.a;
- let b = pos * self.b;
+ let a = pos * self.segment.a;
+ let b = pos * self.segment.b;
let mins = a.coords.inf(&b.coords) - Vector::repeat(self.radius);
let maxs = a.coords.sup(&b.coords) + Vector::repeat(self.radius);
AABB::new(mins.into(), maxs.into())
@@ -53,7 +52,7 @@ impl Capsule {
/// The height of this capsule.
pub fn height(&self) -> f32 {
- (self.b - self.a).norm()
+ (self.segment.b - self.segment.a).norm()
}
/// The half-height of this capsule.
@@ -63,17 +62,17 @@ impl Capsule {
/// The center of this capsule.
pub fn center(&self) -> Point<f32> {
- na::center(&self.a, &self.b)
+ na::center(&self.segment.a, &self.segment.b)
}
/// Creates a new capsule equal to `self` with all its endpoints transformed by `pos`.
pub fn transform_by(&self, pos: &Isometry<f32>) -> Self {
- Self::new(pos * self.a, pos * self.b, self.radius)
+ Self::new(pos * self.segment.a, pos * self.segment.b, self.radius)
}
/// The rotation `r` such that `r * Y` is collinear with `b - a`.
pub fn rotation_wrt_y(&self) -> Rotation<f32> {
- let mut dir = self.b - self.a;
+ let mut dir = self.segment.b - self.segment.a;
if dir.y < 0.0 {
dir = -dir;
}
@@ -96,24 +95,49 @@ impl Capsule {
}
}
-// impl SupportMap<f32> for Capsule {
-// fn local_support_point(&self, dir: &Vector) -> Point {
-// let dir = Unit::try_new(dir, 0.0).unwrap_or(Vector::y_axis());
-// self.local_support_point_toward(&dir)
-// }
-//
-// fn local_support_point_toward(&self, dir: &Unit<Vector>) -> Point {
-// if dir.dot(&self.a.coords) > dir.dot(&self.b.coords) {
-// self.a + **dir * self.radius
-// } else {
-// self.b + **dir * self.radius
-// }
-// }
-// }
+impl SupportMap<f32> for Capsule {
+ fn local_support_point(&self, dir: &Vector<f32>) -> Point<f32> {
+ let dir = Unit::try_new(*dir, 0.0).unwrap_or(Vector::y_axis());
+ self.local_support_point_toward(&dir)
+ }
+
+ fn local_support_point_toward(&self, dir: &Unit<Vector<f32>>) -> Point<f32> {
+ if dir.dot(&self.segment.a.coords) > dir.dot(&self.segment.b.coords) {
+ self.segment.a + **dir * self.radius
+ } else {
+ self.segment.b + **dir * self.radius
+ }
+ }
+}
+
+impl RayCast<f32> for Capsule {
+ fn toi_and_normal_with_ray(
+ &self,
+ m: &Isometry<f32>,
+ ray: &Ray,
+ max_toi: f32,
+ solid: bool,
+ ) -> Option<RayIntersection> {
+ let ls_ray = ray.inverse_transform_by(m);
+
+ ncollide::query::ray_intersection_with_support_map_with_params(
+ &Isometry::identity(),
+ self,
+ &mut VoronoiSimplex::new(),
+ &ls_ray,
+ max_toi,
+ solid,
+ )
+ .map(|mut res| {
+ res.normal = m * res.normal;
+ res
+ })
+ }
+}
// TODO: this code has been extracted from ncollide and added here
// so we can modify it to fit with our new definition of capsule.
-// Wa should find a way to avoid this code duplication.
+// We should find a way to avoid this code duplication.
impl PointQuery<f32> for Capsule {
#[inline]
fn project_point(
@@ -122,7 +146,7 @@ impl PointQuery<f32> for Capsule {
pt: &Point<f32>,
solid: bool,
) -> PointProjection<f32> {
- let seg = Segment::new(self.a, self.b);
+ let seg = Segment::new(self.segment.a, self.segment.b);
let proj = seg.project_point(m, pt, solid);
let dproj = *pt - proj.point;
diff --git a/src/geometry/collider.rs b/src/geometry/collider.rs
index 7c293b6..c2adc59 100644
--- a/src/geometry/collider.rs
+++ b/src/geometry/collider.rs
@@ -1,154 +1,199 @@
use crate::dynamics::{MassProperties, RigidBodyHandle, RigidBodySet};
use crate::geometry::{
- Ball, Capsule, ColliderGraphIndex, Contact, Cuboid, HeightField, InteractionGraph, Polygon,
- Proximity, Ray, RayIntersection, Triangle, Trimesh,
+ Ball, Capsule, ColliderGraphIndex, Contact, Cuboid, HeightField, InteractionGraph,
+ InteractionGroups, Proximity, Segment, Shape, ShapeType, Triangle, Trimesh,
};
+#[cfg(feature = "dim3")]
+use crate::geometry::{Cone, Cylinder, RoundCylinder};
use crate::math::{AngVector, Isometry, Point, Rotation, Vector};
use na::Point3;
-use ncollide::bounding_volume::{HasBoundingVolume, AABB};
-use ncollide::query::RayCast;
-use num::Zero;
+use ncollide::bounding_volume::AABB;
+use std::ops::Deref;
+use std::sync::Arc;
+/// The shape of a collider.
#[derive(Clone)]
-#[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))]
-/// An enum grouping all the possible shape of a collider.
-pub enum Shape {
- /// A ball shape.
- Ball(Ball),
- /// A convex polygon shape.
- Polygon(Polygon),
- /// A cuboid shape.
- Cuboid(Cuboid),
- /// A capsule shape.
- Capsule(Capsule),
- /// A triangle shape.
- Triangle(Triangle),
- /// A triangle mesh shape.
- Trimesh(Trimesh),
- /// A heightfield shape.
- HeightField(HeightField),
+pub struct ColliderShape(pub Arc<dyn Shape>);
+
+impl Deref for ColliderShape {
+ type Target = dyn Shape;
+ fn deref(&self) -> &dyn Shape {
+ &*self.0
+ }
}
-impl Shape {
- /// Gets a reference to the underlying ball shape, if `self` is one.
- pub fn as_ball(&self) -> Option<&Ball> {
- match self {
- Shape::Ball(b) => Some(b),
- _ => None,
- }
+impl ColliderShape {
+ /// Initialize a ball shape defined by its radius.
+ pub fn ball(radius: f32) -> Self {
+ ColliderShape(Arc::new(Ball::new(radius)))
}
- /// Gets a reference to the underlying polygon shape, if `self` is one.
- pub fn as_polygon(&self) -> Option<&Polygon> {
- match self {
- Shape::Polygon(p) => Some(p),
- _ => None,
- }
+ /// Initialize a cylindrical shape defined by its half-height
+ /// (along along the y axis) and its radius.
+ #[cfg(feature = "dim3")]
+ pub fn cylinder(half_height: f32, radius: f32) -> Self {
+ ColliderShape(Arc::new(Cylinder::new(half_height, radius)))
}
- /// Gets a reference to the underlying cuboid shape, if `self` is one.
- pub fn as_cuboid(&self) -> Option<&Cuboid> {
- match self {
- Shape::Cuboid(c) => Some(c),
- _ => None,
- }
+ /// Initialize a rounded cylindrical shape defined by its half-height
+ /// (along along the y axis), its radius, and its roundedness (the
+ /// radius of the sphere used for dilating the cylinder).
+ #[cfg(feature = "dim3")]
+ pub fn round_cylinder(half_height: f32, radius: f32, border_radius: f32) -> Self {
+ ColliderShape(Arc::new(RoundCylinder::new(
+ half_height,
+ radius,
+ border_radius,
+ )))
}
- /// Gets a reference to the underlying capsule shape, if `self` is one.
- pub fn as_capsule(&self) -> Option<&Capsule> {
- match self {
- Shape::Capsule(c) => Some(c),
- _ => None,
- }
+ /// Initialize a cone shape defined by its half-height
+ /// (along along the y axis) and its basis radius.
+ #[cfg(feature = "dim3")]
+ pub fn cone(half_height: f32, radius: f32) -> Self {
+ ColliderShape(Arc::new(Cone::new(half_height, radius)))
}
- /// Gets a reference to the underlying triangle mesh shape, if `self` is one.
- pub fn as_trimesh(&self) -> Option<&Trimesh> {
- match self {
- Shape::Trimesh(c) => Some(c),
- _ => None,
- }
+ /// Initialize a cuboid shape defined by its half-extents.
+ pub fn cuboid(half_extents: Vector<f32>) -> Self {
+ ColliderShape(Arc::new(Cuboid::new(half_extents)))
}
- /// Gets a reference to the underlying heightfield shape, if `self` is one.
- pub fn as_heightfield(&self) -> Option<&HeightField> {
- match self {
- Shape::HeightField(h) => Some(h),
- _ => None,
- }
+ /// Initialize a capsule shape from its endpoints and radius.
+ pub fn capsule(a: Point<f32>, b: Point<f32>, radius: f32) -> Self {
+ ColliderShape(Arc::new(Capsule::new(a, b, radius)))
}
- /// Gets a reference to the underlying triangle shape, if `self` is one.
- pub fn as_triangle(&self) -> Option<&Triangle> {
- match self {
- Shape::Triangle(c) => Some(c),
-