1use crate::collision_detection::hazard_index::HazardIndex;
2use crate::collision_detection::hazards::HazKey;
3use crate::collision_detection::hazards::Hazard;
4use crate::collision_detection::hazards::HazardGeometry;
5use crate::collision_detection::hazards::collector::HazardCollector;
6use crate::collision_detection::hazards::filter::HazardFilter;
7use crate::collision_detection::hazards::{BasicHazardEntity, HazardEntity};
8use crate::collision_detection::narrow_phase::{self, EdgeChains};
9use crate::geometry::Transformation;
10use crate::geometry::fail_fast::{SPSurrogate, SPSurrogateConfig};
11use crate::geometry::geo_enums::{GeoPosition, GeoRelation};
12use crate::geometry::geo_traits::{CollidesWith, Transformable};
13use crate::geometry::primitives::{Circle, Rect, SPolygon};
14use crate::util::assertions;
15use itertools::Itertools;
16use serde::{Deserialize, Serialize};
17use slotmap::{SecondaryMap, SlotMap};
18
19#[derive(Clone, Debug)]
23pub struct CDEngine<E = BasicHazardEntity> {
24 bbox: Rect,
26 pub(crate) hazards_map: SlotMap<HazKey, Hazard<E>>,
28 pub(crate) config: CDEConfig,
30 hkey_exterior: HazKey,
32 pub(crate) index: HazardIndex,
34 base_keys: SecondaryMap<HazKey, ()>,
36}
37
38impl<E: HazardEntity> CDEngine<E> {
39 #[must_use]
42 pub fn new(bbox: Rect, static_hazards: Vec<Hazard<E>>, config: CDEConfig) -> Self {
43 let mut hazards_map = SlotMap::with_key();
44 for haz in static_hazards {
45 hazards_map.insert(haz);
46 }
47 let base_keys = hazards_map.keys().map(|k| (k, ())).collect();
48
49 let mut exteriors = hazards_map
50 .iter()
51 .filter(|(_, h)| h.entity().scope() == GeoPosition::Exterior)
52 .map(|(hkey, _)| hkey);
53 let hkey_exterior = exteriors
54 .next()
55 .expect("No exterior hazard registered in the CDE");
56 assert!(
57 exteriors.next().is_none(),
58 "More than one exterior hazard registered in the CDE"
59 );
60
61 let mut index = HazardIndex::default();
62 for (hkey, haz) in &hazards_map {
63 if hkey != hkey_exterior {
64 index.insert(hkey, haz.shape().bbox);
65 }
66 }
67
68 let cde = CDEngine {
69 bbox,
70 hazards_map,
71 config,
72 hkey_exterior,
73 index,
74 base_keys,
75 };
76 debug_assert!(assertions::index_matches_hazards(&cde));
77 cde
78 }
79
80 pub fn register_hazard(&mut self, hazard: Hazard<E>) {
82 debug_assert!(
83 !self
84 .hazards_map
85 .values()
86 .any(|h| h.entity() == hazard.entity()),
87 "Hazard with an identical entity already registered"
88 );
89 debug_assert!(
90 hazard.entity().scope() == GeoPosition::Interior,
91 "The exterior hazard can only be registered at construction"
92 );
93 let bbox = hazard.shape().bbox;
94 let hkey = self.hazards_map.insert(hazard);
95 self.index.insert(hkey, bbox);
96 debug_assert!(assertions::index_matches_hazards(self));
97 }
98
99 pub fn deregister_hazard_by_entity(&mut self, hazard_entity: E) -> Hazard<E> {
101 let hkey = self
102 .hazards_map
103 .iter()
104 .find(|(_, h)| h.entity() == hazard_entity)
105 .map(|(hkey, _)| hkey)
106 .expect("Cannot deregister hazard that is not registered");
107 self.deregister_hazard_by_key(hkey)
108 }
109
110 pub fn deregister_hazard_by_key(&mut self, hkey: HazKey) -> Hazard<E> {
112 debug_assert!(
113 !self.base_keys.contains_key(hkey),
114 "Hazards the CDE was created with cannot be deregistered"
115 );
116 let hazard = self
117 .hazards_map
118 .remove(hkey)
119 .expect("Cannot deregister hazard that is not registered");
120 self.index.remove(hkey, hazard.shape().bbox);
121 debug_assert!(assertions::index_matches_hazards(self));
122
123 hazard
124 }
125
126 #[must_use]
129 pub fn save(&self) -> CDESnapshot<E> {
130 let dynamic_hazards = self
131 .hazards_map
132 .iter()
133 .filter(|(hkey, _)| !self.base_keys.contains_key(*hkey))
134 .map(|(_, h)| h.clone())
135 .collect_vec();
136 CDESnapshot { dynamic_hazards }
137 }
138
139 pub fn restore(&mut self, snapshot: &CDESnapshot<E>) {
142 let mut hazards_to_remove = self
146 .hazards_map
147 .iter()
148 .filter(|(hkey, _)| !self.base_keys.contains_key(*hkey))
149 .map(|(hkey, h)| (hkey, h.entity()))
150 .collect_vec();
151 let mut hazards_to_add = vec![];
152
153 for hazard in &snapshot.dynamic_hazards {
154 let present = hazards_to_remove
155 .iter()
156 .position(|(_, h)| *h == hazard.entity());
157 if let Some(idx) = present {
158 hazards_to_remove.swap_remove(idx);
160 } else {
161 hazards_to_add.push(hazard.clone());
163 }
164 }
165
166 for (hkey, _) in hazards_to_remove {
168 self.deregister_hazard_by_key(hkey);
169 }
170
171 for hazard in hazards_to_add {
173 self.register_hazard(hazard);
174 }
175
176 debug_assert_eq!(
177 self.hazards_map.len() - self.base_keys.len(),
178 snapshot.dynamic_hazards.len()
179 );
180 }
181
182 pub fn hazards(&self) -> impl Iterator<Item = &Hazard<E>> {
184 self.hazards_map.values()
185 }
186
187 pub fn detect_poly_collision(&self, shape: &SPolygon, filter: &impl HazardFilter<E>) -> bool {
192 if self.bbox.relation_to(shape.bbox) != GeoRelation::Surrounding {
193 return true;
195 }
196 let exterior = self.hazards_map[self.hkey_exterior].entity();
197 if !filter.is_irrelevant(self.hkey_exterior, &exterior)
198 && self.collides_with_exterior(shape)
199 {
200 return true;
201 }
202 self.index.any_overlapping(shape.bbox, |hkey| {
203 let haz = &self.hazards_map[hkey];
204 !filter.is_irrelevant(hkey, &haz.entity()) && Self::poly_collides_with(shape, haz)
205 })
206 }
207
208 pub fn detect_surrogate_collision(
216 &self,
217 base_surrogate: &SPSurrogate,
218 transform: &Transformation,
219 filter: &impl HazardFilter<E>,
220 ) -> bool {
221 let exterior = self.hazards_map[self.hkey_exterior].entity();
222 let check_exterior = !filter.is_irrelevant(self.hkey_exterior, &exterior);
223 for pole in base_surrogate.ff_poles() {
224 let pole = pole.transform_clone(transform);
225 if check_exterior && self.pole_collides_with_exterior(&pole) {
226 return true;
227 }
228 let hit = self.index.any_overlapping(pole.bbox(), |hkey| {
229 let haz = &self.hazards_map[hkey];
230 !filter.is_irrelevant(hkey, &haz.entity()) && Self::pole_collides_with(&pole, haz)
231 });
232 if hit {
233 return true;
234 }
235 }
236 false
237 }
238
239 fn poly_collides_with(shape: &SPolygon, haz: &Hazard<E>) -> bool {
241 let r = narrow_phase::overlap(shape.bbox, haz.shape().bbox)
242 .expect("the index reports overlapping bounding boxes");
243 narrow_phase::edges_intersect(shape, r, haz)
244 || Self::detect_containment_collision(shape, haz)
245 }
246
247 fn pole_collides_with(pole: &Circle, haz: &Hazard<E>) -> bool {
249 narrow_phase::circle_touches_edges(pole, haz)
252 || (haz.shape().bbox.relation_to(pole.bbox()) == GeoRelation::Surrounding
253 && narrow_phase::poly_contains(haz.shape(), haz, pole.center))
254 }
255
256 #[must_use]
262 fn detect_containment_collision(shape: &SPolygon, haz: &Hazard<E>) -> bool {
263 let (haz_shape, haz_entity) = (haz.shape(), haz.entity());
264 let haz_to_shape_bbox_relation = haz_shape.bbox.almost_relation_to(shape.bbox);
268
269 let contained = match haz_to_shape_bbox_relation {
272 GeoRelation::Surrounding => {
273 narrow_phase::poly_contains(haz_shape, haz, shape.poi.center)
274 }
275 GeoRelation::Enclosed => shape.collides_with(&haz_shape.poi.center),
276 GeoRelation::Disjoint | GeoRelation::Intersecting => false,
277 };
278
279 match (haz_entity.scope(), contained) {
281 (GeoPosition::Interior, true) | (GeoPosition::Exterior, false) => true,
282 (GeoPosition::Interior, false) | (GeoPosition::Exterior, true) => false,
283 }
284 }
285
286 pub fn collect_poly_collisions(
292 &self,
293 shape: &SPolygon,
294 filter: &impl HazardFilter<E>,
295 collector: &mut impl HazardCollector<Entity = E>,
296 ) {
297 let _ = self.collect_poly_collisions_until(shape, filter, collector, |_| false);
298 }
299
300 #[must_use]
306 pub fn collect_poly_collisions_until<H, C, F>(
307 &self,
308 shape: &SPolygon,
309 filter: &H,
310 collector: &mut C,
311 mut stop_after_collision: F,
312 ) -> bool
313 where
314 H: HazardFilter<E>,
315 C: HazardCollector<Entity = E>,
316 F: FnMut(E) -> bool,
317 {
318 let exterior = self.hazards_map[self.hkey_exterior].entity();
319 if !collector.contains_key(self.hkey_exterior)
320 && !filter.is_irrelevant(self.hkey_exterior, &exterior)
321 && (self.bbox.relation_to(shape.bbox) != GeoRelation::Surrounding
322 || self.collides_with_exterior(shape))
323 {
324 collector.insert(self.hkey_exterior, exterior);
325 if stop_after_collision(exterior) {
326 return true;
327 }
328 }
329
330 self.index.any_overlapping(shape.bbox, |hkey| {
332 if collector.contains_key(hkey) {
333 return false;
334 }
335 let haz = &self.hazards_map[hkey];
336 if filter.is_irrelevant(hkey, &haz.entity()) {
337 return false;
338 }
339 if Self::poly_collides_with(shape, haz) {
340 collector.insert(hkey, haz.entity());
341 return stop_after_collision(haz.entity());
342 }
343 false
344 })
345 }
346
347 pub fn collect_surrogate_collisions(
352 &self,
353 shape: &SPolygon,
354 filter: &impl HazardFilter<E>,
355 collector: &mut impl HazardCollector<Entity = E>,
356 ) {
357 let _ = self.collect_surrogate_collisions_until(shape, filter, collector, |_| false);
358 }
359
360 #[must_use]
369 pub fn collect_surrogate_collisions_until<H, C, F>(
370 &self,
371 shape: &SPolygon,
372 filter: &H,
373 collector: &mut C,
374 mut stop_after_collision: F,
375 ) -> bool
376 where
377 H: HazardFilter<E>,
378 C: HazardCollector<Entity = E>,
379 F: FnMut(E) -> bool,
380 {
381 let Some(surrogate) = &shape.surrogate else {
382 return false;
383 };
384 let poles = surrogate.ff_poles();
385 let exterior = self.hazards_map[self.hkey_exterior].entity();
386 for pole in poles {
387 if !collector.contains_key(self.hkey_exterior)
388 && !filter.is_irrelevant(self.hkey_exterior, &exterior)
389 && self.pole_collides_with_exterior(pole)
390 {
391 collector.insert(self.hkey_exterior, exterior);
392 if stop_after_collision(exterior) {
393 return true;
394 }
395 }
396 }
397
398 let Some(union) = poles.iter().map(Circle::bbox).reduce(Rect::bounding_rect) else {
400 return false;
401 };
402 self.index
403 .any_overlapping_each(union, &poles.iter().map(Circle::bbox), |i, hkey| {
404 if collector.contains_key(hkey) {
405 return false;
406 }
407 let haz = &self.hazards_map[hkey];
408 if filter.is_irrelevant(hkey, &haz.entity()) {
409 return false;
410 }
411 if Self::pole_collides_with(&poles[i], haz) {
412 collector.insert(hkey, haz.entity());
413 return stop_after_collision(haz.entity());
414 }
415 false
416 })
417 }
418
419 fn exterior(&self) -> (&Hazard<E>, &EdgeChains, bool) {
421 let haz = &self.hazards_map[self.hkey_exterior];
422 match haz.geometry() {
423 HazardGeometry::Exterior { chains, is_rect } => (haz, chains, *is_rect),
424 HazardGeometry::Interior(_) => unreachable!("the exterior has exterior geometry"),
425 }
426 }
427
428 fn collides_with_exterior(&self, shape: &SPolygon) -> bool {
430 let (haz, chains, is_rect) = self.exterior();
431 let ext_bbox = haz.shape().bbox;
432 if is_rect && strictly_inside(shape.bbox, ext_bbox) {
433 return false;
434 }
435 let Some(r) = narrow_phase::overlap(shape.bbox, ext_bbox) else {
436 return true;
437 };
438 narrow_phase::edges_intersect(shape, r, haz)
439 || match ext_bbox.almost_relation_to(shape.bbox) {
440 GeoRelation::Surrounding => !chains.contains(shape.poi.center),
442 _ => true,
443 }
444 }
445
446 fn pole_collides_with_exterior(&self, pole: &Circle) -> bool {
448 let (haz, chains, is_rect) = self.exterior();
449 if is_rect && strictly_inside(pole.bbox(), haz.shape().bbox) {
450 return false;
451 }
452 narrow_phase::circle_touches_edges(pole, haz) || !chains.contains(pole.center)
453 }
454
455 #[must_use]
457 pub fn bbox(&self) -> Rect {
458 self.bbox
459 }
460
461 #[must_use]
463 pub fn haz_key(&self, entity: &E) -> Option<HazKey> {
464 self.hazards_map
465 .iter()
466 .find(|(_, hazard)| hazard.entity() == *entity)
467 .map(|(key, _)| key)
468 }
469
470 #[must_use]
472 pub fn hazards_map(&self) -> &SlotMap<HazKey, Hazard<E>> {
473 &self.hazards_map
474 }
475
476 #[inline]
478 #[must_use]
479 pub fn hazard(&self, key: HazKey) -> Option<&Hazard<E>> {
480 self.hazards_map.get(key)
481 }
482
483 #[must_use]
485 pub fn config(&self) -> CDEConfig {
486 self.config
487 }
488}
489
490fn strictly_inside(inner: Rect, outer: Rect) -> bool {
492 outer.x_min < inner.x_min
493 && inner.x_max < outer.x_max
494 && outer.y_min < inner.y_min
495 && inner.y_max < outer.y_max
496}
497
498#[derive(Serialize, Deserialize, Clone, Copy, Debug, PartialEq)]
500pub struct CDEConfig {
501 pub item_surrogate_config: SPSurrogateConfig,
503}
504
505#[derive(Clone, Debug)]
508pub struct CDESnapshot<E = BasicHazardEntity> {
509 pub(crate) dynamic_hazards: Vec<Hazard<E>>,
510}
511
512impl<E> CDESnapshot<E> {
513 #[must_use]
515 pub fn dynamic_hazards(&self) -> &[Hazard<E>] {
516 &self.dynamic_hazards
517 }
518}