collider/core/dur_hitbox/
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
15mod solvers;
16
17use std::f64;
18use geom::*;
19use geom::shape::PlacedBounds;
20use core;
21
22// DurHitbox (and DurHbVel) is almost identical to Hitbox (and HbVel), except
23// it uses a `duration` (amount of time until invalidation of the hitbox)
24// rather than an `end_time` (time of the invalidation of the hitbox). This
25// new struct is meant to make that distinction clear.
26
27#[derive(Clone)]
28pub struct DurHbVel {
29    pub value: Vec2,
30    pub resize: Vec2,
31    pub duration: f64,
32}
33
34impl DurHbVel {
35    pub fn still() -> DurHbVel {
36        DurHbVel {
37            value: Vec2::zero(),
38            resize: Vec2::zero(),
39            duration: f64::INFINITY,
40        }
41    }
42
43    fn is_still(&self) -> bool {
44        self.value == Vec2::zero() && self.resize == Vec2::zero()
45    }
46
47    fn negate(&self) -> DurHbVel {
48        DurHbVel {
49            value: -self.value,
50            resize: -self.resize,
51            duration: self.duration,
52        }
53    }
54}
55
56impl PlacedBounds for DurHbVel {
57    fn bounds_center(&self) -> &Vec2 { &self.value }
58    fn bounds_dims(&self) -> &Vec2 { &self.resize }
59}
60
61#[derive(Clone)]
62pub struct DurHitbox {
63    pub value: PlacedShape,
64    pub vel: DurHbVel,
65}
66
67impl DurHitbox {
68    pub fn new(value: PlacedShape) -> DurHitbox {
69        DurHitbox { value, vel: DurHbVel::still() }
70    }
71
72    pub fn advanced_shape(&self, time: f64) -> PlacedShape {
73        assert!(time < core::HIGH_TIME, "requires time < {}", core::HIGH_TIME);
74        self.value.advance(self.vel.value, self.vel.resize, time)
75    }
76
77    pub fn bounding_box(&self) -> PlacedShape {
78        self.bounding_box_for(self.vel.duration)
79    }
80
81    pub fn bounding_box_for(&self, duration: f64) -> PlacedShape {
82        if self.vel.is_still() {
83            self.value.as_rect()
84        } else {
85            let end_value = self.advanced_shape(duration);
86            self.value.bounding_box(&end_value)
87        }
88    }
89
90    pub fn collide_time(&self, other: &DurHitbox) -> f64 {
91        solvers::collide_time(self, other)
92    }
93
94    pub fn separate_time(&self, other: &DurHitbox, padding: f64) -> f64 {
95        solvers::separate_time(self, other, padding)
96    }
97}
98
99#[cfg(test)]
100mod tests {
101    use geom::*;
102    use core::dur_hitbox::DurHitbox;
103    use std::f64;
104
105    #[test]
106    fn test_rect_rect_collision() {
107        let mut a = DurHitbox::new(PlacedShape::new(v2(-11.0, 0.0), Shape::rect(v2(2.0, 2.0))));
108        a.vel.value = v2(2.0, 0.0);
109        a.vel.duration = 100.0;
110        let mut b = DurHitbox::new(PlacedShape::new(v2(12.0, 2.0), Shape::rect(v2(2.0, 4.0))));
111        b.vel.value = v2(-0.5, 0.0);
112        b.vel.resize = v2(1.0, 0.0);
113        b.vel.duration = 100.0;
114        assert_eq!(a.collide_time(&b), 7.0);
115        assert_eq!(b.collide_time(&a), 7.0);
116        assert_eq!(a.separate_time(&b, 0.1), 0.0);
117    }
118
119    #[test]
120    fn test_circle_circle_collision() {
121        let sqrt2 = (2.0f64).sqrt();
122        let mut a = DurHitbox::new(PlacedShape::new(v2(-0.1*sqrt2, 0.0), Shape::circle(2.0)));
123        a.vel.value = v2(0.1, 0.0);
124        a.vel.duration = 100.0;
125        let mut b = DurHitbox::new(PlacedShape::new(v2(3.0*sqrt2, 0.0), Shape::circle(2.0 + sqrt2*0.1)));
126        b.vel.value = v2(-2.0, 1.0);
127        b.vel.resize = v2(-0.1, -0.1);
128        b.vel.duration = 100.0;
129        assert!((a.collide_time(&b) - sqrt2).abs() < 1e-7);
130        assert_eq!(a.separate_time(&b, 0.1), 0.0);
131    }
132
133    #[test]
134    fn test_rect_circle_collision() {
135        let mut a = DurHitbox::new(PlacedShape::new(v2(-11.0, 0.0), Shape::circle(2.0)));
136        a.vel.value = v2(2.0, 0.0);
137        a.vel.duration = 100.0;
138        let mut b = DurHitbox::new(PlacedShape::new(v2(12.0, 2.0), Shape::rect(v2(2.0, 4.0))));
139        b.vel.value = v2(-1.0, 0.0);
140        b.vel.duration = 100.0;
141        assert_eq!(a.collide_time(&b), 7.0);
142        assert_eq!(b.collide_time(&a), 7.0);
143        assert_eq!(a.separate_time(&b, 0.1), 0.0);
144    }
145
146    #[test]
147    fn test_rect_circle_angled_collision() {
148        let mut a = DurHitbox::new(PlacedShape::new(v2(0., 0.), Shape::square(2.)));
149        a.vel.duration = 100.0;
150        let mut b = DurHitbox::new(PlacedShape::new(v2(5., 5.), Shape::circle(2.)));
151        b.vel.value = v2(-1., -1.);
152        b.vel.duration = 100.0;
153        let collide_time = a.collide_time(&b);
154        let expected_time = 4. - 1. / 2f64.sqrt();
155        assert_eq!(collide_time, expected_time);
156    }
157
158    #[test]
159    fn test_rect_rect_separation() {
160        let mut a = DurHitbox::new(PlacedShape::new(v2(0.0, 0.0), Shape::rect(v2(6.0, 4.0))));
161        a.vel.value = v2(1.0, 1.0);
162        a.vel.duration = 100.0;
163        let mut b = DurHitbox::new(PlacedShape::new(v2(1.0, 0.0), Shape::rect(v2(4.0, 4.0))));
164        b.vel.value = v2(0.5, 0.0);
165        b.vel.duration = 100.0;
166        assert_eq!(a.separate_time(&b, 0.1), 4.1);
167        assert_eq!(b.separate_time(&a, 0.1), 4.1);
168        assert_eq!(a.collide_time(&b), 0.0);
169    }
170
171    #[test]
172    fn test_circle_circle_separation() {
173        let sqrt2 = (2.0f64).sqrt();
174        let mut a = DurHitbox::new(PlacedShape::new(v2(2.0, 5.0), Shape::circle(2.0)));
175        a.vel.duration = 100.0;
176        let mut b = DurHitbox::new(PlacedShape::new(v2(3.0, 4.0), Shape::circle(1.8)));
177        b.vel.value = v2(-1.0, 1.0);
178        b.vel.duration = 100.0;
179        assert_eq!(a.separate_time(&b, 0.1), 1.0 + sqrt2);
180        assert_eq!(b.separate_time(&a, 0.1), 1.0 + sqrt2);
181        assert_eq!(a.collide_time(&b), 0.0);
182    }
183
184    #[test]
185    fn test_rect_circle_separation() {
186        let sqrt2 = (2.0f64).sqrt();
187        let mut a = DurHitbox::new(PlacedShape::new(v2(4.0, 2.0), Shape::rect(v2(4.0, 6.0))));
188        a.vel.duration = 100.0;
189        let mut b = DurHitbox::new(PlacedShape::new(v2(3.0, 4.0), Shape::circle(3.8)));
190        b.vel.value = v2(-1.0, 1.0);
191        b.vel.duration = 100.0;
192        assert_eq!(a.separate_time(&b, 0.1), 1.0 + sqrt2);
193        assert_eq!(b.separate_time(&a, 0.1), 1.0 + sqrt2);
194        assert_eq!(a.collide_time(&b), 0.0);
195    }
196
197    #[test]
198    fn test_rect_circle_angled_separation() {
199        let mut a = DurHitbox::new(PlacedShape::new(v2(0., 0.), Shape::square(2.)));
200        a.vel.duration = 100.0;
201        let mut b = DurHitbox::new(PlacedShape::new(v2(-1., 1.), Shape::circle(2.)));
202        b.vel.value = v2(1., -1.);
203        b.vel.duration = 100.0;
204        let separate_time = a.separate_time(&b, 0.1);
205        let expected_time = 2. + 1.1 / 2f64.sqrt();
206        assert_eq!(separate_time, expected_time);
207    }
208
209    #[test]
210    fn test_no_collision() {
211        let mut a = DurHitbox::new(PlacedShape::new(v2(-11.0, 0.0), Shape::rect(v2(2.0, 2.0))));
212        a.vel.value = v2(2.0, 0.0);
213        a.vel.duration = 100.0;
214        let mut b = DurHitbox::new(PlacedShape::new(v2(12.0, 2.0), Shape::rect(v2(2.0, 4.0))));
215        b.vel.value = v2(-1.0, 1.0);
216        b.vel.duration = 100.0;
217        assert_eq!(a.collide_time(&b), f64::INFINITY);
218        assert_eq!(a.separate_time(&b, 0.1), 0.0);
219
220        b.value.shape == Shape::circle(2.0);
221        b.vel.resize == Vec2::zero();
222        assert_eq!(a.collide_time(&b), f64::INFINITY);
223        assert_eq!(a.separate_time(&b, 0.1), 0.0);
224
225        a.value.shape == Shape::circle(2.0);
226        a.vel.resize == Vec2::zero();
227        assert_eq!(a.collide_time(&b), f64::INFINITY);
228        assert_eq!(a.separate_time(&b, 0.1), 0.0);
229    }
230
231    #[test]
232    fn test_no_separation() {
233        let mut a = DurHitbox::new(PlacedShape::new(v2(5.0, 1.0), Shape::rect(v2(2.0, 2.0))));
234        a.vel.value = v2(2.0, 1.0);
235        a.vel.duration = 100.0;
236        let mut b = DurHitbox::new(PlacedShape::new(v2(5.0, 1.0), Shape::rect(v2(2.0, 4.0))));
237        b.vel.value = v2(2.0, 1.0);
238        b.vel.duration = 100.0;
239        assert_eq!(a.separate_time(&b, 0.1), f64::INFINITY);
240        assert_eq!(a.collide_time(&b), 0.0);
241
242        b.value.shape == Shape::circle(2.0);
243        b.vel.resize == Vec2::zero();
244        assert_eq!(a.separate_time(&b, 0.1), f64::INFINITY);
245        assert_eq!(a.collide_time(&b), 0.0);
246
247        a.value.shape == Shape::circle(2.0);
248        a.vel.resize == Vec2::zero();
249        assert_eq!(a.separate_time(&b, 0.1), f64::INFINITY);
250        assert_eq!(a.collide_time(&b), 0.0);
251    }
252
253    #[test]
254    fn test_low_duration() {
255        let sqrt2 = (2.0f64).sqrt();
256        let mut a = DurHitbox::new(PlacedShape::new(v2(0.0, 0.0), Shape::circle(2.0)));
257        a.vel.duration = 4.0 - sqrt2 + 0.01;
258        let mut b = DurHitbox::new(PlacedShape::new(v2(4.0, 4.0), Shape::circle(2.0)));
259        b.vel.value = v2(-1.0, -1.0);
260        b.vel.duration = 4.0 - sqrt2 + 0.01;
261        assert_eq!(a.collide_time(&b), 4.0 - sqrt2);
262        a.vel.duration -= 0.02;
263        assert_eq!(a.collide_time(&b), f64::INFINITY);
264        b.vel.duration -= 0.02;
265        assert_eq!(a.collide_time(&b), f64::INFINITY);
266    }
267}