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 pub fn new(bbox: Rect, static_hazards: Vec<Hazard>, config: CDEConfig) -> CDEngine {
35 let mut quadtree = QTNode::new(config.quadtree_depth, bbox, config.cd_threshold);
36 let mut hazards_map = SlotMap::with_key();
37
38 for haz in static_hazards.into_iter() {
39 let hkey = hazards_map.insert(haz);
40 let qt_haz = QTHazard::from_root(quadtree.bbox, &hazards_map[hkey], hkey);
41 quadtree.register_hazard(qt_haz, &hazards_map);
42 }
43
44 let hkey_exterior = hazards_map
45 .iter()
46 .find(|(_, h)| matches!(h.entity, HazardEntity::Exterior))
47 .map(|(hkey, _)| hkey)
48 .expect("No exterior hazard registered in the CDE");
49
50 CDEngine {
51 quadtree,
52 hazards_map,
53 config,
54 hkey_exterior,
55 }
56 }
57
58 pub fn register_hazard(&mut self, hazard: Hazard) {
60 debug_assert!(
61 !self.hazards_map.values().any(|h| h.entity == hazard.entity),
62 "Hazard with an identical entity already registered"
63 );
64 let hkey = self.hazards_map.insert(hazard);
65 let qt_hazard = QTHazard::from_root(self.bbox(), &self.hazards_map[hkey], hkey);
66 self.quadtree.register_hazard(qt_hazard, &self.hazards_map);
67
68 debug_assert!(assertions::qt_contains_no_dangling_hazards(self));
69 }
70
71 pub fn deregister_hazard_by_entity(&mut self, hazard_entity: HazardEntity) -> Hazard {
73 let hkey = self
74 .hazards_map
75 .iter()
76 .find(|(_, h)| h.entity == hazard_entity)
77 .map(|(hkey, _)| hkey)
78 .expect("Cannot deregister hazard that is not registered");
79
80 self.quadtree.deregister_hazard(hkey);
81 let hazard = self.hazards_map.remove(hkey).unwrap();
82 debug_assert!(assertions::qt_contains_no_dangling_hazards(self));
83
84 hazard
85 }
86
87 pub fn deregister_hazard_by_key(&mut self, hkey: HazKey) -> Hazard {
88 let hazard = self
89 .hazards_map
90 .remove(hkey)
91 .expect("Cannot deregister hazard that is not registered");
92 self.quadtree.deregister_hazard(hkey);
93 debug_assert!(assertions::qt_contains_no_dangling_hazards(self));
94
95 hazard
96 }
97
98 pub fn save(&self) -> CDESnapshot {
99 let dynamic_hazards = self
100 .hazards_map
101 .values()
102 .filter(|h| h.dynamic)
103 .cloned()
104 .collect_vec();
105 CDESnapshot { dynamic_hazards }
106 }
107
108 pub fn restore(&mut self, snapshot: &CDESnapshot) {
110 let mut hazards_to_remove = self
115 .hazards_map
116 .iter()
117 .filter(|(_, h)| h.dynamic)
118 .map(|(hkey, h)| (hkey, h.entity))
119 .collect_vec();
120 let mut hazards_to_add = vec![];
121
122 for hazard in snapshot.dynamic_hazards.iter() {
123 let present = hazards_to_remove
124 .iter()
125 .position(|(_, h)| h == &hazard.entity);
126 if let Some(idx) = present {
127 hazards_to_remove.swap_remove(idx);
129 } else {
130 hazards_to_add.push(hazard.clone());
132 }
133 }
134
135 for (hkey, _) in hazards_to_remove {
137 self.deregister_hazard_by_key(hkey);
138 }
139
140 for hazard in hazards_to_add {
142 self.register_hazard(hazard);
143 }
144
145 debug_assert!(
146 self.hazards_map.values().filter(|h| h.dynamic).count()
147 == snapshot.dynamic_hazards.len()
148 );
149 }
150
151 pub fn hazards(&self) -> impl Iterator<Item = &Hazard> {
153 self.hazards_map.values()
154 }
155
156 pub fn detect_poly_collision(&self, shape: &SPolygon, filter: &impl HazardFilter) -> bool {
161 if self.bbox().relation_to(shape.bbox) != GeoRelation::Surrounding {
162 true
164 } else {
165 let v_qt_root = self.get_virtual_root(shape.bbox);
167
168 for edge in shape.edge_iter() {
170 if v_qt_root.collides(&edge, filter).is_some() {
171 return true;
172 }
173 }
174
175 for qt_hazard in v_qt_root.hazards.iter() {
177 match &qt_hazard.presence {
178 QTHazPresence::None => {}
179 QTHazPresence::Entire => unreachable!(
180 "Entire hazards in the virtual root should have been caught by the edge intersection tests"
181 ),
182 QTHazPresence::Partial(_) => {
183 if !filter.is_irrelevant(qt_hazard.hkey) {
184 let haz_shape = &self.hazards_map[qt_hazard.hkey].shape;
185 if self.detect_containment_collision(shape, haz_shape, qt_hazard.entity)
186 {
187 return true;
189 }
190 }
191 }
192 }
193 }
194
195 false
196 }
197 }
198
199 pub fn detect_surrogate_collision(
205 &self,
206 base_surrogate: &SPSurrogate,
207 transform: &Transformation,
208 filter: &impl HazardFilter,
209 ) -> bool {
210 for pole in base_surrogate.ff_poles() {
211 let t_pole = pole.transform_clone(transform);
212 if self.quadtree.collides(&t_pole, filter).is_some() {
213 return true;
214 }
215 }
216 for pier in base_surrogate.ff_piers() {
217 let t_pier = pier.transform_clone(transform);
218 if self.quadtree.collides(&t_pier, filter).is_some() {
219 return true;
220 }
221 }
222 false
223 }
224
225 pub fn detect_containment_collision(
232 &self,
233 shape: &SPolygon,
234 haz_shape: &SPolygon,
235 haz_entity: HazardEntity,
236 ) -> bool {
237 let haz_to_shape_bbox_relation = haz_shape.bbox.almost_relation_to(shape.bbox);
241
242 let contained = match haz_to_shape_bbox_relation {
245 GeoRelation::Surrounding => haz_shape.collides_with(&shape.poi.center),
246 GeoRelation::Enclosed => shape.collides_with(&haz_shape.poi.center),
247 GeoRelation::Disjoint | GeoRelation::Intersecting => false,
248 };
249
250 match (haz_entity.scope(), contained) {
252 (GeoPosition::Interior, true) | (GeoPosition::Exterior, false) => true,
253 (GeoPosition::Interior, false) | (GeoPosition::Exterior, true) => false,
254 }
255 }
256
257 pub fn collect_poly_collisions(&self, shape: &SPolygon, collector: &mut impl HazardCollector) {
262 if self.bbox().relation_to(shape.bbox) != GeoRelation::Surrounding {
263 collector.insert(self.hkey_exterior, HazardEntity::Exterior);
264 }
265
266 let v_quadtree = self.get_virtual_root(shape.bbox);
268
269 shape
271 .edge_iter()
272 .for_each(|e| v_quadtree.collect_collisions(&e, collector));
273
274 for qt_haz in v_quadtree.hazards.iter() {
276 match &qt_haz.presence {
277 QTHazPresence::None | QTHazPresence::Entire => {}
279 QTHazPresence::Partial(_) => {
280 if !collector.contains_key(qt_haz.hkey) {
281 let h_shape = &self.hazards_map[qt_haz.hkey].shape;
282 if self.detect_containment_collision(shape, h_shape, qt_haz.entity) {
283 collector.insert(qt_haz.hkey, qt_haz.entity);
284 }
285 }
286 }
287 }
288 }
289 }
290
291 pub fn collect_surrogate_collisions(
297 &self,
298 base_surrogate: &SPSurrogate,
299 transform: &Transformation,
300 collector: &mut impl HazardCollector,
301 ) {
302 for pole in base_surrogate.ff_poles() {
303 let t_pole = pole.transform_clone(transform);
304 self.quadtree.collect_collisions(&t_pole, collector)
305 }
306 for pier in base_surrogate.ff_piers() {
307 let t_pier = pier.transform_clone(transform);
308 self.quadtree.collect_collisions(&t_pier, collector);
309 }
310 }
311
312 pub fn get_virtual_root(&self, bbox: Rect) -> &QTNode {
315 let mut v_root = &self.quadtree;
316 while let Some(children) = v_root.children.as_ref() {
317 let surrounding_child = children
319 .iter()
320 .find(|child| child.bbox.relation_to(bbox) == GeoRelation::Surrounding);
321 match surrounding_child {
322 Some(child) => v_root = child,
323 None => break,
324 }
325 }
326 v_root
327 }
328
329 pub fn bbox(&self) -> Rect {
330 self.quadtree.bbox
331 }
332
333 pub fn haz_key_from_pi_key(&self, pik: PItemKey) -> Option<HazKey> {
334 self.hazards_map
335 .iter()
336 .find(|(_, hazard)| match hazard.entity {
337 HazardEntity::PlacedItem { pk, .. } => pik == pk,
338 _ => false,
339 })
340 .map(|(key, _)| key)
341 }
342}
343
344#[derive(Serialize, Deserialize, Clone, Copy, Debug, PartialEq)]
346pub struct CDEConfig {
347 pub quadtree_depth: u8,
349 pub cd_threshold: u8,
351 pub item_surrogate_config: SPSurrogateConfig,
353}
354
355#[derive(Clone, Debug)]
357pub struct CDESnapshot {
358 pub dynamic_hazards: Vec<Hazard>,
359}