aboutsummaryrefslogtreecommitdiff
path: root/src/geometry/capsule.rs
diff options
context:
space:
mode:
authorSébastien Crozet <developer@crozet.re>2020-08-25 22:10:25 +0200
committerSébastien Crozet <developer@crozet.re>2020-08-25 22:10:25 +0200
commit754a48b7ff6d8c58b1ee08651e60112900b60455 (patch)
tree7d777a6c003f1f5d8f8d24f533f35a95a88957fe /src/geometry/capsule.rs
downloadrapier-754a48b7ff6d8c58b1ee08651e60112900b60455.tar.gz
rapier-754a48b7ff6d8c58b1ee08651e60112900b60455.tar.bz2
rapier-754a48b7ff6d8c58b1ee08651e60112900b60455.zip
First public release of Rapier.v0.1.0
Diffstat (limited to 'src/geometry/capsule.rs')
-rw-r--r--src/geometry/capsule.rs168
1 files changed, 168 insertions, 0 deletions
diff --git a/src/geometry/capsule.rs b/src/geometry/capsule.rs
new file mode 100644
index 0000000..0d754af
--- /dev/null
+++ b/src/geometry/capsule.rs
@@ -0,0 +1,168 @@
+use crate::geometry::AABB;
+use crate::math::{Isometry, Point, Rotation, Vector};
+use approx::AbsDiffEq;
+use na::Unit;
+use ncollide::query::{PointProjection, PointQuery};
+use ncollide::shape::{FeatureId, Segment};
+
+#[derive(Copy, Clone, Debug)]
+#[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))]
+/// A capsule shape defined as a segment with a radius.
+pub struct Capsule {
+ /// The first endpoint of the capsule.
+ pub a: Point<f32>,
+ /// The second enpdoint of the capsule.
+ pub b: Point<f32>,
+ /// The radius of the capsule.
+ pub radius: f32,
+}
+
+impl Capsule {
+ /// Creates a new capsule aligned with the `x` axis and with the given half-height an radius.
+ pub fn new_x(half_height: f32, radius: f32) -> Self {
+ let b = Point::from(Vector::x() * half_height);
+ Self::new(-b, b, radius)
+ }
+
+ /// Creates a new capsule aligned with the `y` axis and with the given half-height an radius.
+ pub fn new_y(half_height: f32, radius: f32) -> Self {
+ let b = Point::from(Vector::y() * half_height);
+ Self::new(-b, b, radius)
+ }
+
+ /// Creates a new capsule aligned with the `z` axis and with the given half-height an radius.
+ #[cfg(feature = "dim3")]
+ pub fn new_z(half_height: f32, radius: f32) -> Self {
+ let b = Point::from(Vector::z() * half_height);
+ Self::new(-b, b, radius)
+ }
+
+ /// 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 }
+ }
+
+ /// 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 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())
+ }
+
+ /// The height of this capsule.
+ pub fn height(&self) -> f32 {
+ (self.b - self.a).norm()
+ }
+
+ /// The half-height of this capsule.
+ pub fn half_height(&self) -> f32 {
+ self.height() / 2.0
+ }
+
+ /// The center of this capsule.
+ pub fn center(&self) -> Point<f32> {
+ na::center(&self.a, &self.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)
+ }
+
+ /// 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;
+ if dir.y < 0.0 {
+ dir = -dir;
+ }
+
+ #[cfg(feature = "dim2")]
+ {
+ Rotation::rotation_between(&Vector::y(), &dir)
+ }
+
+ #[cfg(feature = "dim3")]
+ {
+ Rotation::rotation_between(&Vector::y(), &dir).unwrap_or(Rotation::identity())
+ }
+ }
+
+ /// The transform `t` such that `t * Y` is collinear with `b - a` and such that `t * origin = (b + a) / 2.0`.
+ pub fn transform_wrt_y(&self) -> Isometry<f32> {
+ let rot = self.rotation_wrt_y();
+ Isometry::from_parts(self.center().coords.into(), rot)
+ }
+}
+
+// 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
+// }
+// }
+// }
+
+// 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.
+impl PointQuery<f32> for Capsule {
+ #[inline]
+ fn project_point(
+ &self,
+ m: &Isometry<f32>,
+ pt: &Point<f32>,
+ solid: bool,
+ ) -> PointProjection<f32> {
+ let seg = Segment::new(self.a, self.b);
+ let proj = seg.project_point(m, pt, solid);
+ let dproj = *pt - proj.point;
+
+ if let Some((dir, dist)) = Unit::try_new_and_get(dproj, f32::default_epsilon()) {
+ let inside = dist <= self.radius;
+ if solid && inside {
+ return PointProjection::new(true, *pt);
+ } else {
+ return PointProjection::new(inside, proj.point + dir.into_inner() * self.radius);
+ }
+ } else if solid {
+ return PointProjection::new(true, *pt);
+ }
+
+ #[cfg(feature = "dim2")]
+ if let Some(dir) = seg.normal() {
+ let dir = m * *dir;
+ PointProjection::new(true, proj.point + dir * self.radius)
+ } else {
+ // The segment has no normal, likely because it degenerates to a point.
+ PointProjection::new(true, proj.point + Vector::ith(1, self.radius))
+ }
+
+ #[cfg(feature = "dim3")]
+ if let Some(dir) = seg.direction() {
+ use crate::utils::WBasis;
+ let dir = m * dir.orthonormal_basis()[0];
+ PointProjection::new(true, proj.point + dir * self.radius)
+ } else {
+ // The segment has no normal, likely because it degenerates to a point.
+ PointProjection::new(true, proj.point + Vector::ith(1, self.radius))
+ }
+ }
+
+ #[inline]
+ fn project_point_with_feature(
+ &self,
+ m: &Isometry<f32>,
+ pt: &Point<f32>,
+ ) -> (PointProjection<f32>, FeatureId) {
+ (self.project_point(m, pt, false), FeatureId::Face(0))
+ }
+}