jagua_rs/collision_detection/
cd_engine.rs1use crate::collision_detection::hazards::Hazard;
2use crate::collision_detection::hazards::HazardEntity;
3use crate::collision_detection::hazards::detector::HazardDetector;
4use crate::collision_detection::hazards::filter::HazardFilter;
5use crate::collision_detection::quadtree::{QTHazPresence, QTHazard, QTNode};
6use crate::geometry::Transformation;
7use crate::geometry::fail_fast::{SPSurrogate, SPSurrogateConfig};
8use crate::geometry::geo_enums::{GeoPosition, GeoRelation};
9use crate::geometry::geo_traits::{CollidesWith, Transformable};
10use crate::geometry::primitives::Rect;
11use crate::geometry::primitives::SPolygon;
12use crate::util::assertions;
13use itertools::Itertools;
14use serde::{Deserialize, Serialize};
15
16#[derive(Clone, Debug)]
19pub struct CDEngine {
20 pub quadtree: QTNode,
22 pub static_hazards: Vec<Hazard>,
24 pub dynamic_hazards: Vec<Hazard>,
26 pub config: CDEConfig,
28 pub uncommitted_deregisters: Vec<Hazard>,
30}
31
32impl CDEngine {
33 pub fn new(bbox: Rect, static_hazards: Vec<Hazard>, config: CDEConfig) -> CDEngine {
34 let mut qt_root = QTNode::new(config.quadtree_depth, bbox, config.cd_threshold);
35
36 for haz in static_hazards.iter() {
37 let qt_haz = QTHazard::from_qt_root(qt_root.bbox, haz);
38 qt_root.register_hazard(qt_haz);
39 }
40
41 CDEngine {
42 quadtree: qt_root,
43 static_hazards,
44 dynamic_hazards: vec![],
45 config,
46 uncommitted_deregisters: vec![],
47 }
48 }
49
50 pub fn register_hazard(&mut self, hazard: Hazard) {
52 debug_assert!(
53 !self
54 .dynamic_hazards
55 .iter()
56 .any(|h| h.entity == hazard.entity),
57 "Hazard already registered"
58 );
59 let hazard_in_uncommitted_deregs = self
60 .uncommitted_deregisters
61 .iter()
62 .position(|h| h.entity == hazard.entity);
63
64 let hazard = match hazard_in_uncommitted_deregs {
65 Some(index) => {
66 let unc_hazard = self.uncommitted_deregisters.swap_remove(index);
67 self.quadtree.activate_hazard(unc_hazard.entity);
68 unc_hazard
69 }
70 None => {
71 let qt_haz = QTHazard::from_qt_root(self.bbox(), &hazard);
72 self.quadtree.register_hazard(qt_haz);
73 hazard
74 }
75 };
76 self.dynamic_hazards.push(hazard);
77
78 debug_assert!(assertions::qt_contains_no_dangling_hazards(self));
79 }
80
81 pub fn deregister_hazard(&mut self, hazard_entity: HazardEntity, commit_instant: bool) {
89 let haz_index = self
90 .dynamic_hazards
91 .iter()
92 .position(|h| h.entity == hazard_entity)
93 .expect("Hazard not found");
94
95 let hazard = self.dynamic_hazards.swap_remove(haz_index);
96
97 match commit_instant {
98 true => self.quadtree.deregister_hazard(hazard_entity),
99 false => {
100 self.quadtree.deactivate_hazard(hazard_entity);
101 self.uncommitted_deregisters.push(hazard);
102 }
103 }
104 debug_assert!(assertions::qt_contains_no_dangling_hazards(self));
105 }
106
107 pub fn create_snapshot(&mut self) -> CDESnapshot {
108 self.commit_deregisters();
109 CDESnapshot {
110 dynamic_hazards: self.dynamic_hazards.clone(),
111 }
112 }
113
114 pub fn restore(&mut self, snapshot: &CDESnapshot) {
116 let mut hazards_to_remove = self.dynamic_hazards.iter().map(|h| h.entity).collect_vec();
118 debug_assert!(hazards_to_remove.len() == self.dynamic_hazards.len());
119 let mut hazards_to_add = vec![];
120
121 for hazard in snapshot.dynamic_hazards.iter() {
122 let hazard_already_present = hazards_to_remove.iter().position(|h| h == &hazard.entity);
123 if let Some(idx) = hazard_already_present {
124 hazards_to_remove.swap_remove(idx);
126 } else {
127 hazards_to_add.push(hazard.clone());
129 }
130 }
131
132 for haz_entity in hazards_to_remove.iter() {
134 let haz_index = self
135 .dynamic_hazards
136 .iter()
137 .position(|h| &h.entity == haz_entity)
138 .expect("Hazard not found");
139 self.dynamic_hazards.swap_remove(haz_index);
140 self.quadtree.deregister_hazard(*haz_entity);
141 }
142
143 for unc_haz in self.uncommitted_deregisters.drain(..) {
145 if let Some(pos) = hazards_to_add
146 .iter()
147 .position(|h| h.entity == unc_haz.entity)
148 {
149 self.quadtree.activate_hazard(unc_haz.entity);
151 self.dynamic_hazards.push(unc_haz);
152 hazards_to_add.swap_remove(pos);
153 } else {
154 self.quadtree.deregister_hazard(unc_haz.entity);
156 }
157 }
158
159 for hazard in hazards_to_add {
160 let qt_haz = QTHazard::from_qt_root(self.bbox(), &hazard);
161 self.quadtree.register_hazard(qt_haz);
162 self.dynamic_hazards.push(hazard);
163 }
164
165 debug_assert!(self.dynamic_hazards.len() == snapshot.dynamic_hazards.len());
166 }
167
168 pub fn commit_deregisters(&mut self) {
170 for uncommitted_hazard in self.uncommitted_deregisters.drain(..) {
171 self.quadtree.deregister_hazard(uncommitted_hazard.entity);
172 }
173 }
174
175 pub fn has_uncommitted_deregisters(&self) -> bool {
176 !self.uncommitted_deregisters.is_empty()
177 }
178
179 pub fn all_hazards(&self) -> impl Iterator<Item = &Hazard> {
181 self.static_hazards
182 .iter()
183 .chain(self.dynamic_hazards.iter())
184 }
185
186 pub fn detect_poly_collision(&self, shape: &SPolygon, filter: &impl HazardFilter) -> bool {
191 if self.bbox().relation_to(shape.bbox) != GeoRelation::Surrounding {
192 true
194 } else {
195 let v_qt_root = self.get_virtual_root(shape.bbox);
197
198 for edge in shape.edge_iter() {
200 if v_qt_root.collides(&edge, filter).is_some() {
201 return true;
202 }
203 }
204
205 for qt_hazard in v_qt_root.hazards.active_hazards() {
207 match &qt_hazard.presence {
208 QTHazPresence::None => {}
209 QTHazPresence::Entire => unreachable!(
210 "Entire hazards in the virtual root should have been caught by the edge intersection tests"
211 ),
212 QTHazPresence::Partial(qthaz_partial) => {
213 if !filter.is_irrelevant(&qt_hazard.entity)
214 && self.detect_containment_collision(
215 shape,
216 &qthaz_partial.shape,
217 qt_hazard.entity,
218 )
219 {
220 return true;
222 }
223 }
224 }
225 }
226
227 false
228 }
229 }
230
231 pub fn detect_surrogate_collision(
237 &self,
238 base_surrogate: &SPSurrogate,
239 transform: &Transformation,
240 filter: &impl HazardFilter,
241 ) -> bool {
242 for pole in base_surrogate.ff_poles() {
243 let t_pole = pole.transform_clone(transform);
244 if self.quadtree.collides(&t_pole, filter).is_some() {
245 return true;
246 }
247 }
248 for pier in base_surrogate.ff_piers() {
249 let t_pier = pier.transform_clone(transform);
250 if self.quadtree.collides(&t_pier, filter).is_some() {
251 return true;
252 }
253 }
254 false
255 }
256
257 pub fn detect_containment_collision(
264 &self,
265 shape: &SPolygon,
266 haz_shape: &SPolygon,
267 haz_entity: HazardEntity,
268 ) -> bool {
269 let haz_to_shape_bbox_relation = haz_shape.bbox.almost_relation_to(shape.bbox);
273
274 let contained = match haz_to_shape_bbox_relation {
277 GeoRelation::Surrounding => haz_shape.collides_with(&shape.poi.center),
278 GeoRelation::Enclosed => shape.collides_with(&haz_shape.poi.center),
279 GeoRelation::Disjoint | GeoRelation::Intersecting => false,
280 };
281
282 match (haz_entity.scope(), contained) {
284 (GeoPosition::Interior, true) | (GeoPosition::Exterior, false) => true,
285 (GeoPosition::Interior, false) | (GeoPosition::Exterior, true) => false,
286 }
287 }
288
289 pub fn collect_poly_collisions(&self, shape: &SPolygon, detector: &mut impl HazardDetector) {
294 if self.bbox().relation_to(shape.bbox) != GeoRelation::Surrounding {
295 detector.push(HazardEntity::Exterior)
296 }
297
298 let v_quadtree = self.get_virtual_root(shape.bbox);
300
301 shape
303 .edge_iter()
304 .for_each(|e| v_quadtree.collect_collisions(&e, detector));
305
306 v_quadtree
307 .hazards
308 .active_hazards()
309 .iter()
310 .for_each(|qt_haz| match &qt_haz.presence {
311 QTHazPresence::Entire | QTHazPresence::None => {}
312 QTHazPresence::Partial(qt_par_haz) => {
313 if !detector.contains(&qt_haz.entity)
314 && self.detect_containment_collision(
315 shape,
316 &qt_par_haz.shape,
317 qt_haz.entity,
318 )
319 {
320 detector.push(qt_haz.entity);
321 }
322 }
323 })
324 }
325
326 pub fn collect_surrogate_collisions(
332 &self,
333 base_surrogate: &SPSurrogate,
334 transform: &Transformation,
335 detector: &mut impl HazardDetector,
336 ) {
337 for pole in base_surrogate.ff_poles() {
338 let t_pole = pole.transform_clone(transform);
339 self.quadtree.collect_collisions(&t_pole, detector)
340 }
341 for pier in base_surrogate.ff_piers() {
342 let t_pier = pier.transform_clone(transform);
343 self.quadtree.collect_collisions(&t_pier, detector);
344 }
345 }
346
347 pub fn get_virtual_root(&self, bbox: Rect) -> &QTNode {
350 let mut v_root = &self.quadtree;
351 while let Some(children) = v_root.children.as_ref() {
352 let surrounding_child = children
354 .iter()
355 .find(|child| child.bbox.relation_to(bbox) == GeoRelation::Surrounding);
356 match surrounding_child {
357 Some(child) => v_root = child,
358 None => break,
359 }
360 }
361 v_root
362 }
363
364 pub fn bbox(&self) -> Rect {
365 self.quadtree.bbox
366 }
367}
368
369#[derive(Serialize, Deserialize, Clone, Copy, Debug, PartialEq)]
371pub struct CDEConfig {
372 pub quadtree_depth: u8,
374 pub cd_threshold: u8,
376 pub item_surrogate_config: SPSurrogateConfig,
378}
379
380#[derive(Clone, Debug)]
382pub struct CDESnapshot {
383 dynamic_hazards: Vec<Hazard>,
384}