diff options
| author | Robert Hrusecky <robert.hrusecky@utexas.edu> | 2020-10-29 18:09:41 -0500 |
|---|---|---|
| committer | Robert Hrusecky <robert.hrusecky@utexas.edu> | 2020-10-29 18:09:41 -0500 |
| commit | bcec54ef31d987cf20b493628a20777183a95f65 (patch) | |
| tree | cee40c0467c04f1f02861342e20ce8223ca6d99b /src/geometry | |
| parent | dd8e25bc4756b8bd01d283b5d7e7c5daa9a1af3f (diff) | |
| parent | 4b8242b9c267a9412c88793575db37f79c544ca2 (diff) | |
| download | rapier-bcec54ef31d987cf20b493628a20777183a95f65.tar.gz rapier-bcec54ef31d987cf20b493628a20777183a95f65.tar.bz2 rapier-bcec54ef31d987cf20b493628a20777183a95f65.zip | |
Merge branch 'master' into infinite_fall_memory
Fix merge conflict resulting from "axii" spelling correction
Diffstat (limited to 'src/geometry')
35 files changed, 1858 insertions, 595 deletions
diff --git a/src/geometry/broad_phase_multi_sap.rs b/src/geometry/broad_phase_multi_sap.rs index db83fa3..d8a46ee 100644 --- a/src/geometry/broad_phase_multi_sap.rs +++ b/src/geometry/broad_phase_multi_sap.rs @@ -337,7 +337,7 @@ impl SAPAxis { #[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))] struct SAPRegion { - axii: [SAPAxis; DIM], + axes: [SAPAxis; DIM], existing_proxies: BitVec, #[cfg_attr(feature = "serde-serialize", serde(skip))] to_insert: Vec<usize>, // Workspace @@ -347,14 +347,14 @@ struct SAPRegion { 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(), update_count: 0, @@ -394,14 +394,14 @@ impl SAPRegion { // Update endpoints. let mut deleted = 0; for dim in 0..DIM { - self.axii[dim].update_endpoints(dim, proxies, reporting); - deleted += self.axii[dim].delete_out_of_bounds_proxies(&mut self.existing_proxies); + self.axes[dim].update_endpoints(dim, proxies, reporting); + deleted += self.axes[dim].delete_out_of_bounds_proxies(&mut self.existing_proxies); } 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); } } @@ -411,9 +411,9 @@ impl SAPRegion { 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 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..f53d75a 100644 --- a/src/geometry/collider.rs +++ b/src/geometry/collider.rs @@ -1,145 +1,189 @@ 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), - _ => None, - } + /// Initialize a segment shape from its endpoints. + pub fn segment(a: Point<f32>, b: Point<f32>) -> Self { + ColliderShape(Arc::new(Segment::new(a, b))) + } + + /// Initializes a triangle shape. + pub fn triangle(a: Point<f32>, b: Point<f32>, c: Point<f32>) -> Self { + ColliderShape(Arc::new(Triangle::new(a, b, c))) } - /// Computes the axis-aligned bounding box of this shape. - pub fn compute_aabb(&self, position: &Isometry<f32>) -> AABB<f32> { - match self { - Shape::Ball(ball) => ball.bounding_volume(position), - Shape::Polygon(poly) => poly.aabb(position), - Shape::Capsule(caps) => caps.aabb(position), - Shape::Cuboid(cuboid) => cuboid.bounding_volume(position), - Shape::Triangle(triangle) => triangle.bounding_volume(position), - Shape::Trimesh(trimesh) => trimesh.aabb(position), - Shape::HeightField(heightfield) => heightfield.bounding_volume(position), + /// Initializes a triangle mesh shape defined by its vertex and index buffers. + pub fn trimesh(vertices: Vec<Point<f32>>, indices: Vec<Point3<u32>>) -> Self { + ColliderShape(Arc::new(Trimesh::new(vertices, indices))) + } + + /// Initializes an heightfield shape defined by its set of height and a scale + /// factor along each coordinate axis. + #[cfg(feature = "dim2")] + pub fn heightfield(heights: na::DVector<f32>, scale: Vector<f32>) -> Self { + ColliderShape(Arc::new(HeightField::new(heights, scale))) + } + + /// Initializes an heightfield shape on the x-z plane defined by its set of height and a scale + /// factor along each coordinate axis. + #[cfg(feature = "dim3")] + pub fn heightfield(heights: na::DMatrix<f32>, scale: Vector<f32>) -> Self { + ColliderShape(Arc::new(HeightField::new(heights, scale))) + } +} + +#[cfg(feature = "serde-serialize")] +impl serde::Serialize for ColliderShape { + fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> + where + S: serde::Serializer, + { + use crate::serde::ser::SerializeStruct; + + if let Some(ser) = self.0.as_serialize() { + let typ = self.0.shape_type(); + let mut state = serializer.serialize_struct("ColliderShape", 2)?; + state.serialize_field("tag", &(typ as i32))?; + state.serialize_field("inner", ser)?; + state.end() + } else { + Err(serde::ser::Error::custom( + "Found a non-serializable custom shape.", + )) } } +} - /// Computes the first intersection point between a ray in this collider. - /// - /// Some shapes are not supported yet and will always return `None`. - /// - /// # Parameters - /// - `position`: the position of this shape. - /// - `ray`: the ray to cast. - /// - `max_toi`: the maximum time-of-impact that can be reported by this cast. This effectively - /// limits the length of the ray to `ray.dir.norm() * max_toi`. Use `f32::MAX` for an unbounded ray. - pub fn cast_ray( - &self, - position: &Isometry<f32>, - ray: &Ray, - max_toi: f32, - ) -> Option<RayIntersection> { - match self { - Shape::Ball(ball) => ball.toi_and_normal_with_ray(position, ray, max_toi, true), - Shape::Polygon(_poly) => None, - Shape::Capsule(caps) => { - let pos = position * caps.transform_wrt_y(); - let caps = ncollide::shape::Capsule::new(caps.half_height(), caps.radius); - caps.toi_and_normal_with_ray(&pos, ray, max_toi, true) - } - Shape::Cuboid(cuboid) => cuboid.toi_and_normal_with_ray(position, ray, max_toi, true), - #[cfg(feature = "dim2")] - Shape::Triangle(_) | Shape::Trimesh(_) => { - // This is not implemented yet in 2D. - None +#[cfg(feature = "serde-serialize")] +impl<'de> serde::Deserialize<'de> for ColliderShape { + fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> + where + D: serde::Deserializer<'de>, + { + struct Visitor {}; + impl<'de> serde::de::Visitor<'de> for Visitor { + type Value = ColliderShape; + fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { + write!(formatter, "one shape type tag and the inner shape data") } - #[cfg(feature = "dim3")] - Shape::Triangle(triangle) => { - triangle.toi_and_normal_with_ray(position, ray, max_toi, true) - } - #[cfg(feature = "dim3")] - Shape::Trimesh(trimesh) => { - trimesh.toi_and_normal_with_ray(position, ray, max_toi, true) - } - Shape::HeightField(heightfield) => { - heightfield.toi_and_normal_with_ray(position, ray, max_toi, true) + + fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error> + where + A: serde::de::SeqAccess<'de>, + { + use num::cast::FromPrimitive; + + let tag: i32 = seq + .next_element()? + .ok_or_else(|| serde::de::Error::invalid_length(0, &self))?; + + fn deser<'de, A, S: Shape + serde::Deserialize<'de>>( + seq: &mut A, + ) -> Result<Arc<dyn Shape>, A::Error> + where + A: serde::de::SeqAccess<'de>, + { + let shape: S = seq.next_element()?.ok_or_else(|| { + serde::de::Error::custom("Failed to deserialize builtin shape.") + })?; + Ok(Arc::new(shape) as Arc<dyn Shape>) + } + + let shape = match ShapeType::from_i32(tag) { + Some(ShapeType::Ball) => deser::<A, Ball>(&mut seq)?, + Some(ShapeType::Polygon) => { + unimplemented!() + // let shape: Polygon = seq + // .next_element()? + // .ok_or_else(|| serde::de::Error::invalid_length(0, &self))?; + // Arc::new(shape) as Arc<dyn Shape> + } + Some(ShapeType::Cuboid) => deser::<A, Cuboid>(&mut seq)?, + Some(ShapeType::Capsule) => deser::<A, Capsule>(&mut seq)?, + Some(ShapeType::Triangle) => deser::<A, Triangle>(&mut seq)?, + Some(ShapeType::Segment) => deser::<A, Segment>(&mut seq)?, + Some(ShapeType::Trimesh) => deser::<A, Trimesh>(&mut seq)?, + Some(ShapeType::HeightField) => deser::<A, HeightField>(&mut seq)?, + #[cfg(feature = "dim3")] + Some(ShapeType::Cylinder) => deser::<A, Cylinder>(&mut seq)?, + #[cfg(feature = "dim3")] + Some(ShapeType::Cone) => deser::<A, Cone>(&mut seq)?, + #[cfg(feature = "dim3")] + Some(ShapeType::RoundCylinder) => deser::<A, RoundCylinder>(&mut seq)?, + None => { + return Err(serde::de::Error::custom( + "found invalid shape type to deserialize", + )) + } + }; + + Ok(ColliderShape(shape)) } } + + deserializer.deserialize_struct("ColliderShape", &["tag", "inner"], Visitor {}) } } @@ -148,7 +192,7 @@ impl Shape { /// /// To build a new collider, use the `ColliderBuilder` structure. pub struct Collider { - shape: Shape, + shape: ColliderShape, density: f32, is_sensor: bool, pub(crate) parent: RigidBodyHandle, @@ -159,9 +203,13 @@ pub struct Collider { pub friction: f32, /// The restitution coefficient of this collider. pub restitution: f32, + pub(crate) collision_groups: InteractionGroups, + pub(crate) solver_groups: InteractionGroups, pub(crate) contact_graph_index: ColliderGraphIndex, pub(crate) proximity_graph_index: ColliderGraphIndex, pub(crate) proxy_index: usize, + /// User-defined data associated to this rigid-body. + pub user_data: u128, } impl Clone for Collider { @@ -209,14 +257,24 @@ impl Collider { &self.delta } + /// The collision groups used by this collider. + pub fn collision_groups(&self) -> InteractionGroups { + self.collision_groups + } + + /// The solver groups used by this collider. + pub fn solver_groups(&self) -> InteractionGroups { + self.solver_groups + } + /// The density of this collider. pub fn density(&self) -> f32 { self.density } /// The geometric shape of this collider. - pub fn shape(&self) -> &Shape { - &self.shape + pub fn shape(&self) -> &dyn Shape { + &*self.shape.0 } /// Compute the axis-aligned bounding box of this collider. @@ -232,20 +290,7 @@ impl Collider { /// Compute the local-space mass properties of this collider. pub fn mass_properties(&self) -> MassProperties { - match &self.shape { - Shape::Ball(ball) => MassProperties::from_ball(self.density, ball.radius), - #[cfg(feature = "dim2")] - Shape::Polygon(p) => MassProperties::from_polygon(self.density, p.vertices()), - #[cfg(feature = "dim3")] - Shape::Polygon(_p) => unimplemented!(), - Shape::Cuboid(c) => MassProperties::from_cuboid(self.density, c.half_extents), - Shape::Capsule(caps) => { - MassProperties::from_capsule(self.density, caps.a, caps.b, caps.radius) - } - Shape::Triangle(_) => MassProperties::zero(), - Shape::Trimesh(_) => MassProperties::zero(), - Shape::HeightField(_) => MassProperties::zero(), - } + self.shape.mass_properties(self.density) } } @@ -254,7 +299,7 @@ impl Collider { #[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))] pub struct ColliderBuilder { /// The shape of the collider to be built. - pub shape: Shape, + pub shape: ColliderShape, /// The density of the collider to be built. density: Option<f32>, /// The friction coefficient of the collider to be built. @@ -265,11 +310,17 @@ pub struct ColliderBuilder { pub delta: Isometry<f32>, /// Is this collider a sensor? pub is_sensor: bool, + /// The user-data of the collider being built. + pub user_data: u128, + /// The collision groups for the collider being built. + pub collision_groups: InteractionGroups, + /// The solver groups for the collider being built. + pub solver_groups: InteractionGroups, } impl ColliderBuilder { /// Initialize a new collider builder with the given shape. - pub fn new(shape: Shape) -> Self { + pub fn new(shape: ColliderShape) -> Self { Self { shape, density: None, @@ -277,6 +328,9 @@ impl ColliderBuilder { restitution: 0.0, delta: Isometry::identity(), is_sensor: false, + user_data: 0, + collision_groups: InteractionGroups::all(), + solver_groups: InteractionGroups::all(), } } @@ -288,96 +342,93 @@ impl ColliderBuilder { /// Initialize a new collider builder with a ball shape defined by its radius. pub fn ball(radius: f32) -> Self { - Self::new(Shape::Ball(Ball::new(radius))) + Self::new(ColliderShape::ball(radius)) } - /// Initialize a new collider builder with a cuboid shape defined by its half-extents. - #[cfg(feature = "dim2")] - pub fn cuboid(hx: f32, hy: f32) -> Self { - let cuboid = Cuboid { - half_extents: Vector::new(hx, hy), - }; + /// Initialize a new collider builder with 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 { + Self::new(ColliderShape::cylinder(half_height, radius)) + } - Self::new(Shape::Cuboid(cuboid)) + /// Initialize a new collider builder with 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 { + Self::new(ColliderShape::round_cylinder( + half_height, + radius, + border_radius, + )) |
