jagua_rs/collision_detection/
cd_engine.rs1use crate::collision_detection::hazards::HazKey;
2use crate::collision_detection::hazards::Hazard;
3use crate::collision_detection::hazards::HazardEntity;
4use crate::collision_detection::hazards::collector::HazardCollector;
5use crate::collision_detection::hazards::filter::HazardFilter;
6use crate::collision_detection::quadtree::{QTHazPresence, QTHazard, QTNode};
7use crate::entities::PItemKey;
8use crate::geometry::Transformation;
9use crate::geometry::fail_fast::{SPSurrogate, SPSurrogateConfig};
10use crate::geometry::geo_enums::{GeoPosition, GeoRelation};
11use crate::geometry::geo_traits::{CollidesWith, Transformable};
12use crate::geometry::primitives::Rect;
13use crate::geometry::primitives::SPolygon;
14use crate::util::assertions;
15use itertools::Itertools;
16use serde::{Deserialize, Serialize};
17use slotmap::SlotMap;
18
19#[derive(Clone, Debug)]
22pub struct CDEngine {
23 pub quadtree: QTNode,
25 pub hazards_map: SlotMap<HazKey, Hazard>,
27 pub config: CDEConfig,
29 hkey_exterior: HazKey,
31}
32
33impl CDEngine {
34 #[must_use]
35 pub fn new(bbox: Rect, static_hazards: Vec<Hazard>, config: CDEConfig) -> CDEngine {
36 let mut quadtree = QTNode::new(config.quadtree_depth, bbox, config.cd_threshold);
37 let mut hazards_map = SlotMap::with_key();
38
39 for haz in static_hazards {
40 let hkey = hazards_map.insert(haz);
41 let qt_haz = QTHazard::from_root(quadtree.bbox, &hazards_map[hkey], hkey);
42 quadtree.register_hazard(qt_haz, &hazards_map);
43 }
44
45 let hkey_exterior = hazards_map
46 .iter()
47 .find(|(_, h)| matches!(h.entity, HazardEntity::Exterior))
48 .map(|(hkey, _)| hkey)
49 .expect("No exterior hazard registered in the CDE");
50
51 CDEngine {
52 quadtree,
53 hazards_map,
54 config,
55 hkey_exterior,
56 }
57 }
58
59 pub fn register_hazard(&mut self, hazard: Hazard) {
61 debug_assert!(
62 !self.hazards_map.values().any(|h| h.entity == hazard.entity),
63 "Hazard with an identical entity already registered"
64 );
65 let hkey = self.hazards_map.insert(hazard);
66 let qt_hazard = QTHazard::from_root(self.bbox(), &self.hazards_map[hkey], hkey);
67 self.quadtree.register_hazard(qt_hazard, &self.hazards_map);
68
69 debug_assert!(assertions::qt_contains_no_dangling_hazards(self));
70 }
71
72 pub fn deregister_hazard_by_entity(&mut self, hazard_entity: HazardEntity) -> Hazard {
74 let hkey = self
75 .hazards_map
76 .iter()
77 .find(|(_, h)| h.entity == hazard_entity)
78 .map(|(hkey, _)| hkey)
79 .expect("Cannot deregister hazard that is not registered");
80
81 self.quadtree.deregister_hazard(hkey);
82 let hazard = self.hazards_map.remove(hkey).unwrap();
83 debug_assert!(assertions::qt_contains_no_dangling_hazards(self));
84
85 hazard
86 }
87
88 pub fn deregister_hazard_by_key(&mut self, hkey: HazKey) -> Hazard {
89 let hazard = self
90 .hazards_map
91 .remove(hkey)
92 .expect("Cannot deregister hazard that is not registered");
93 self.quadtree.deregister_hazard(hkey);
94 debug_assert!(assertions::qt_contains_no_dangling_hazards(self));
95
96 hazard
97 }
98
99 #[must_use]
100 pub fn save(&self) -> CDESnapshot {
101 let dynamic_hazards = self
102 .hazards_map
103 .values()
104 .filter(|h| h.dynamic)
105 .cloned()
106 .collect_vec();
107 CDESnapshot { dynamic_hazards }
108 }
109
110 pub fn restore(&mut self, snapshot: &CDESnapshot) {
112 let mut hazards_to_remove = self
117 .hazards_map
118 .iter()
119 .filter(|(_, h)| h.dynamic)
120 .map(|(hkey, h)| (hkey, h.entity))
121 .collect_vec();
122 let mut hazards_to_add = vec![];
123
124 for hazard in &snapshot.dynamic_hazards {
125 let present = hazards_to_remove
126 .iter()
127 .position(|(_, h)| h == &hazard.entity);
128 if let Some(idx) = present {
129 hazards_to_remove.swap_remove(idx);
131 } else {
132 hazards_to_add.push(hazard.clone());
134 }
135 }
136
137 for (hkey, _) in hazards_to_remove {
139 self.deregister_hazard_by_key(hkey);
140 }
141
142 for hazard in hazards_to_add {
144 self.register_hazard(hazard);
145 }
146
147 debug_assert!(
148 self.hazards_map.values().filter(|h| h.dynamic).count()
149 == snapshot.dynamic_hazards.len()
150 );
151 }
152
153 pub fn hazards(&self) -> impl Iterator<Item = &Hazard> {
155 self.hazards_map.values()
156 }
157
158 pub fn detect_poly_collision(&self, shape: &SPolygon, filter: &impl HazardFilter) -> bool {
163 if self.bbox().relation_to(shape.bbox) == GeoRelation::Surrounding {
164 let v_qt_root = self.get_virtual_root(shape.bbox);
166
167 for edge in shape.edge_iter() {
169 if v_qt_root.collides(&edge, filter).is_some() {
170 return true;
171 }
172 }
173
174 for qt_hazard in v_qt_root.hazards.iter() {
176 match &qt_hazard.presence {
177 QTHazPresence::None => {}
178 QTHazPresence::Entire => unreachable!(
179 "Entire hazards in the virtual root should have been caught by the edge intersection tests"
180 ),
181 QTHazPresence::Partial(_) => {
182 if !filter.is_irrelevant(qt_hazard.hkey) {
183 let haz_shape = &self.hazards_map[qt_hazard.hkey].shape;
184 if self.detect_containment_collision(shape, haz_shape, qt_hazard.entity)
185 {
186 return true;
188 }
189 }
190 }
191 }
192 }
193
194 false
195 } else {
196 true
198 }
199 }
200
201 pub fn detect_surrogate_collision(
207 &self,
208 base_surrogate: &SPSurrogate,
209 transform: &Transformation,
210 filter: &impl HazardFilter,
211 ) -> bool {
212 for pole in base_surrogate.ff_poles() {
213 let t_pole = pole.transform_clone(transform);
214 if self.quadtree.collides(&t_pole, filter).is_some() {
215 return true;
216 }
217 }
218 for pier in base_surrogate.ff_piers() {
219 let t_pier = pier.transform_clone(transform);
220 if self.quadtree.collides(&t_pier, filter).is_some() {
221 return true;
222 }
223 }
224 false
225 }
226
227 #[must_use]
234 pub fn detect_containment_collision(
235 &self,
236 shape: &SPolygon,
237 haz_shape: &SPolygon,
238 haz_entity: HazardEntity,
239 ) -> bool {
240 let haz_to_shape_bbox_relation = haz_shape.bbox.almost_relation_to(shape.bbox);
244
245 let contained = match haz_to_shape_bbox_relation {
248 GeoRelation::Surrounding => haz_shape.collides_with(&shape.poi.center),
249 GeoRelation::Enclosed => shape.collides_with(&haz_shape.poi.center),
250 GeoRelation::Disjoint | GeoRelation::Intersecting => false,
251 };
252
253 match (haz_entity.scope(), contained) {
255 (GeoPosition::Interior, true) | (GeoPosition::Exterior, false) => true,
256 (GeoPosition::Interior, false) | (GeoPosition::Exterior, true) => false,
257 }
258 }
259
260 pub fn collect_poly_collisions(&self, shape: &SPolygon, collector: &mut impl HazardCollector) {
265 if self.bbox().relation_to(shape.bbox) != GeoRelation::Surrounding {
266 collector.insert(self.hkey_exterior, HazardEntity::Exterior);
267 }
268
269 let v_quadtree = self.get_virtual_root(shape.bbox);
271
272 shape
274 .edge_iter()
275 .for_each(|e| v_quadtree.collect_collisions(&e, collector));
276
277 for qt_haz in v_quadtree.hazards.iter() {
279 match &qt_haz.presence {
280 QTHazPresence::None | QTHazPresence::Entire => {}
282 QTHazPresence::Partial(_) => {
283 if !collector.contains_key(qt_haz.hkey) {
284 let h_shape = &self.hazards_map[qt_haz.hkey].shape;
285 if self.detect_containment_collision(shape, h_shape, qt_haz.entity) {
286 collector.insert(qt_haz.hkey, qt_haz.entity);
287 }
288 }
289 }
290 }
291 }
292 }
293
294 pub fn collect_surrogate_collisions(
300 &self,
301 base_surrogate: &SPSurrogate,
302 transform: &Transformation,
303 collector: &mut impl HazardCollector,
304 ) {
305 for pole in base_surrogate.ff_poles() {
306 let t_pole = pole.transform_clone(transform);
307 self.quadtree.collect_collisions(&t_pole, collector);
308 }
309 for pier in base_surrogate.ff_piers() {
310 let t_pier = pier.transform_clone(transform);
311 self.quadtree.collect_collisions(&t_pier, collector);
312 }
313 }
314
315 #[must_use]
318 pub fn get_virtual_root(&self, bbox: Rect) -> &QTNode {
319 let mut v_root = &self.quadtree;
320 while let Some(children) = v_root.children.as_ref() {
321 let surrounding_child = children
323 .iter()
324 .find(|child| child.bbox.relation_to(bbox) == GeoRelation::Surrounding);
325 match surrounding_child {
326 Some(child) => v_root = child,
327 None => break,
328 }
329 }
330 v_root
331 }
332
333 #[must_use]
334 pub fn bbox(&self) -> Rect {
335 self.quadtree.bbox
336 }
337
338 #[must_use]
339 pub fn haz_key_from_pi_key(&self, pik: PItemKey) -> Option<HazKey> {
340 self.hazards_map
341 .iter()
342 .find(|(_, hazard)| match hazard.entity {
343 HazardEntity::PlacedItem { pk, .. } => pik == pk,
344 _ => false,
345 })
346 .map(|(key, _)| key)
347 }
348}
349
350#[derive(Serialize, Deserialize, Clone, Copy, Debug, PartialEq)]
352pub struct CDEConfig {
353 pub quadtree_depth: u8,
355 pub cd_threshold: u8,
357 pub item_surrogate_config: SPSurrogateConfig,
359}
360
361#[derive(Clone, Debug)]
363pub struct CDESnapshot {
364 pub dynamic_hazards: Vec<Hazard>,
365}