collider/geom/shape/
mod.rs

1// Copyright 2016-2018 Matthew D. Michelotti
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//   http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use std::cmp::Ordering;
16
17use geom::{Vec2, DirVec2, v2, Card, CardMask};
18use core::{Hitbox, HbVel};
19use float::n64;
20
21mod normals;
22#[cfg(test)] mod tests;
23
24/// Enumeration of kinds of shapes used by Collider.
25#[derive(PartialEq, Eq, Copy, Clone, Debug, Hash)]
26pub enum ShapeKind {
27    /// Circle.  Requires width and height to match.
28    Circle,
29    /// Axis-aligned rectangle.
30    Rect
31}
32
33/// Represents a shape, without any position.
34///
35/// Each shape has a `width` and `height`, which are allowed to be negative.
36#[derive(PartialEq, Copy, Clone, Debug)]
37pub struct Shape {
38    kind: ShapeKind,
39    dims: Vec2
40}
41
42impl Shape {
43    /// Constructs a new shape with the given `kind` and `dims` (width and height dimensions).
44    ///
45    /// Dimensions must be non-negative.
46    /// If `kind` is `Circle`, then the width and height must match.
47    pub fn new(kind: ShapeKind, dims: Vec2) -> Shape {
48        assert!(dims.x >= 0.0 && dims.y >= 0.0, "dims must be non-negative");
49        Shape::with_any_dims(kind, dims)
50    }
51
52    // allows negative dims
53    fn with_any_dims(kind: ShapeKind, dims: Vec2) -> Shape {
54        if kind == ShapeKind::Circle {
55            assert_eq!(dims.x, dims.y, "circle width must equal height");
56        }
57        Shape { kind: kind, dims: dims }
58    }
59
60    /// Constructs a new circle shape, using `diam` as the width and height.
61    #[inline]
62    pub fn circle(diam: f64) -> Shape {
63        Shape::new(ShapeKind::Circle, v2(diam, diam))
64    }
65
66    /// Constructs a new axis-aligned rectangle shape with the given `dims` (width and height dimensions).
67    #[inline]
68    pub fn rect(dims: Vec2) -> Shape {
69        Shape::new(ShapeKind::Rect, dims)
70    }
71
72    /// Constructs a new axis-aligned square shape with the given `width`.
73    #[inline]
74    pub fn square(width: f64) -> Shape {
75        Shape::new(ShapeKind::Rect, v2(width, width))
76    }
77
78    /// Returns the kind of shape.
79    #[inline]
80    pub fn kind(&self) -> ShapeKind {
81        self.kind
82    }
83
84    /// Returns the dims of the shape.
85    #[inline]
86    pub fn dims(&self) -> Vec2 {
87        self.dims
88    }
89
90    /// Shorthand for `PlacedShape::new(pos, self)`.
91    #[inline]
92    pub fn place(self, pos: Vec2) -> PlacedShape {
93        PlacedShape::new(pos, self)
94    }
95
96    pub(crate) fn advance(&self, resize_vel: Vec2, elapsed: f64) -> Shape {
97        Shape::with_any_dims(self.kind, self.dims + resize_vel * elapsed)
98    }
99}
100
101/// Represents a shape with a position.
102#[derive(PartialEq, Copy, Clone, Debug)]
103pub struct PlacedShape {
104    /// The position of the center of the shape.
105    pub pos: Vec2,
106    /// The shape.
107    pub shape: Shape
108}
109
110impl PlacedShape {
111    /// Constructs a new `PlacedShape` with the given `pos` and `shape`.
112    #[inline]
113    pub fn new(pos: Vec2, shape: Shape) -> PlacedShape {
114        PlacedShape { pos: pos, shape: shape }
115    }
116
117    /// Shorthand for `self.shape.kind()`
118    #[inline]
119    pub fn kind(&self) -> ShapeKind {
120        self.shape.kind()
121    }
122
123    /// Shorthand for `self.shape.dims()`
124    #[inline]
125    pub fn dims(&self) -> Vec2 {
126        self.shape.dims()
127    }
128
129    /// Returns the lowest x coordinate of the `PlacedShape`.
130    pub fn min_x(&self) -> f64 { self.bounds_left() }
131
132    /// Returns the lowest y coordinate of the `PlacedShape`.
133    pub fn min_y(&self) -> f64 { self.bounds_bottom() }
134
135    /// Returns the highest x coordinate of the `PlacedShape`.
136    pub fn max_x(&self) -> f64 { self.bounds_right() }
137
138    /// Returns the highest y coordinate of the `PlacedShape`.
139    pub fn max_y(&self) -> f64 { self.bounds_top() }
140
141    /// Returns `true` if the two shapes overlap, subject to negligible numerical error.
142    pub fn overlaps(&self, other: &PlacedShape) -> bool {
143        self.normal_from(other).len() >= 0.0
144    }
145
146    /// Returns a normal vector that points in the direction from `other` to `self`.
147    ///
148    /// The length of this vector is the minimum distance that `self` would need to
149    /// be moved along this direction so that it is no longer overlapping `other`.
150    /// If the shapes are not overlappingt to begin with, then the length of this vector
151    /// is negative, and describes the minimum distance that `self` would need to
152    /// be moved so that it is just overlapping `other`.
153    ///
154    /// (As a minor caveat,
155    /// when computing the normal between two `Rect` shapes,
156    /// the direction will always be axis-aligned.)
157    pub fn normal_from(&self, other: &PlacedShape) -> DirVec2 {
158        match (self.kind(), other.kind()) {
159            (ShapeKind::Rect, ShapeKind::Rect) => normals::rect_rect_normal(self, other),
160            (ShapeKind::Rect, ShapeKind::Circle) => normals::rect_circle_normal(self, other),
161            (ShapeKind::Circle, ShapeKind::Rect) => normals::rect_circle_normal(other, self).flip(),
162            (ShapeKind::Circle, ShapeKind::Circle) => normals::circle_circle_normal(self, other),
163        }
164    }
165
166    /// Returns a normal vector like `normal_from`, but only certain normal directions are permitted.
167    ///
168    /// A normal vector with a cardinal component that is not present in the `mask`
169    /// will not be returned, and the next-in-line normal vector will be used instead.
170    /// This function panics if `mask` is empty, or if both shapes are circles and
171    /// `mask` is anything but full.
172    pub fn masked_normal_from(&self, other: &PlacedShape, mask: CardMask) -> DirVec2 {
173        match (self.kind(), other.kind()) {
174            (ShapeKind::Rect, ShapeKind::Rect) => normals::masked_rect_rect_normal(self, other, mask),
175            (ShapeKind::Rect, ShapeKind::Circle) => normals::masked_rect_circle_normal(self, other, mask),
176            (ShapeKind::Circle, ShapeKind::Rect) => normals::masked_rect_circle_normal(other, self, mask.flip()).flip(),
177            (ShapeKind::Circle, ShapeKind::Circle) => normals::masked_circle_circle_normal(self, other, mask),
178        }
179    }
180
181    /// Returns the point of contact between two shapes.
182    ///
183    /// If the shapes are not overlapping, returns the nearest point between the shapes.
184    pub fn contact_point(&self, other: &PlacedShape) -> Vec2 {
185        match (self.kind(), other.kind()) {
186            (ShapeKind::Rect, ShapeKind::Rect) => normals::rect_rect_contact(self, other),
187            (ShapeKind::Circle, _) => normals::circle_any_contact(self, other),
188            (ShapeKind::Rect, ShapeKind::Circle) => normals::circle_any_contact(other, self),
189        }
190    }
191
192    /// Shorthand for `Hitbox::new(self, HbVel::moving(vel))`.
193    #[inline]
194    pub fn moving(self, vel: Vec2) -> Hitbox {
195        Hitbox::new(self, HbVel::moving(vel))
196    }
197
198    /// Shorthand for `Hitbox::new(self, HbVel::moving_until(vel, end_time))`.
199    #[inline]
200    pub fn moving_until(self, vel: Vec2, end_time: f64) -> Hitbox {
201        Hitbox::new(self, HbVel::moving_until(vel, end_time))
202    }
203
204    /// Shorthand for `Hitbox::new(self, HbVel::still())`.
205    #[inline]
206    pub fn still(self) -> Hitbox {
207        Hitbox::new(self, HbVel::still())
208    }
209
210    /// Shorthand for `Hitbox::new(self, HbVel::still_until(end_time))`.
211    #[inline]
212    pub fn still_until(self, end_time: f64) -> Hitbox {
213        Hitbox::new(self, HbVel::still_until(end_time))
214    }
215
216    pub(crate) fn sector(&self, point: Vec2) -> Sector {
217        let x = interval_sector(self.min_x(), self.max_x(), point.x);
218        let y = interval_sector(self.min_y(), self.max_y(), point.y);
219        Sector::new(x, y)
220    }
221
222    pub(crate) fn as_rect(&self) -> PlacedShape {
223        PlacedShape::new(self.pos, Shape::rect(self.shape.dims()))
224    }
225
226    pub(crate) fn bounding_box(&self, other: &PlacedShape) -> PlacedShape {
227        let right = self.max_x().max(other.max_x());
228        let top = self.max_y().max(other.max_y());
229        let left = self.min_x().min(other.min_x());
230        let bottom = self.min_y().min(other.min_y());
231
232        let shape = Shape::rect(v2(right - left, top - bottom));
233        let pos = v2(left + shape.dims().x * 0.5, bottom + shape.dims().y * 0.5);
234        PlacedShape::new(pos, shape)
235    }
236
237    pub(crate) fn advance(&self, vel: Vec2, resize_vel: Vec2, elapsed: f64) -> PlacedShape {
238        PlacedShape::new(self.pos + vel * elapsed, self.shape.advance(resize_vel, elapsed))
239    }
240}
241
242pub(crate) trait PlacedBounds {
243    fn bounds_center(&self) -> &Vec2;
244    fn bounds_dims(&self) -> &Vec2;
245
246    fn bounds_bottom(&self) -> f64 { self.bounds_center().y - self.bounds_dims().y * 0.5 }
247    fn bounds_left(&self) -> f64 { self.bounds_center().x - self.bounds_dims().x * 0.5 }
248    fn bounds_top(&self) -> f64 { self.bounds_center().y + self.bounds_dims().y * 0.5 }
249    fn bounds_right(&self) -> f64 { self.bounds_center().x + self.bounds_dims().x * 0.5 }
250
251    fn edge(&self, card: Card) -> f64 {
252        match card {
253            Card::MinusY => -self.bounds_bottom(),
254            Card::MinusX => -self.bounds_left(),
255            Card::PlusY => self.bounds_top(),
256            Card::PlusX => self.bounds_right(),
257        }
258    }
259
260    fn max_edge(&self) -> f64 {
261        Card::values().iter()
262                      .map(|&card| self.edge(card).abs())
263                      .max_by_key(|&edge| n64(edge))
264                      .unwrap()
265    }
266
267    fn card_overlap(&self, src: &Self, card: Card) -> f64 {
268        src.edge(card) + self.edge(card.flip())
269    }
270
271    fn corner(&self, sector: Sector) -> Vec2 {
272        let x = match sector.x {
273            Ordering::Less => self.bounds_left(),
274            Ordering::Greater => self.bounds_right(),
275            Ordering::Equal => panic!("expected corner sector")
276        };
277        let y = match sector.y {
278            Ordering::Less => self.bounds_bottom(),
279            Ordering::Greater => self.bounds_top(),
280            Ordering::Equal => panic!("expected corner sector")
281        };
282        v2(x, y)
283    }
284}
285
286impl PlacedBounds for PlacedShape {
287    fn bounds_center(&self) -> &Vec2 { &self.pos }
288    fn bounds_dims(&self) -> &Vec2 { &self.shape.dims }
289}
290
291fn interval_sector(left: f64, right: f64, val: f64) -> Ordering {
292    if val < left {
293        Ordering::Less
294    } else if val > right {
295        Ordering::Greater
296    } else {
297        Ordering::Equal
298    }
299}
300
301#[derive(PartialEq, Eq, Copy, Clone)]
302pub(crate) struct Sector {
303    x: Ordering,
304    y: Ordering
305}
306
307impl Sector {
308    pub fn new(x: Ordering, y: Ordering) -> Sector {
309        Sector { x: x, y: y }
310    }
311
312    pub fn is_corner(&self) -> bool {
313        self.x != Ordering::Equal && self.y != Ordering::Equal
314    }
315
316    pub fn corner_cards(&self) -> Option<(Card, Card)> {
317        if self.is_corner() {
318            Some((
319                if self.x == Ordering::Greater { Card::PlusX } else { Card::MinusX },
320                if self.y == Ordering::Greater { Card::PlusY } else { Card::MinusY },
321            ))
322        } else {
323            None
324        }
325    }
326}