jagua_rs/collision_detection/quadtree/qt_hazard.rs
1use crate::collision_detection::hazards::Hazard;
2use crate::collision_detection::hazards::HazardEntity;
3use crate::collision_detection::quadtree::qt_partial_hazard::{QTHazPartial, RelevantEdges};
4use crate::geometry::geo_enums::{GeoPosition, GeoRelation};
5use crate::geometry::geo_traits::CollidesWith;
6use crate::geometry::primitives::Rect;
7use crate::geometry::primitives::SPolygon;
8use crate::util::assertions;
9use std::sync::Arc;
10
11/// Representation of a [`Hazard`] in a [`QTNode`](crate::collision_detection::quadtree::QTNode)
12#[derive(Clone, Debug)]
13pub struct QTHazard {
14 /// The bounding box of the quadtree node
15 pub qt_bbox: Rect,
16 /// Entity inducing the hazard
17 pub entity: HazardEntity,
18 /// How the hazard is present in the node
19 pub presence: QTHazPresence,
20 /// Whether the hazard is active or not
21 pub active: bool,
22}
23
24/// Presence of a [`Hazard`] in a [`QTNode`](crate::collision_detection::quadtree::QTNode)
25#[derive(Clone, Debug)]
26pub enum QTHazPresence {
27 /// The hazard is entirely absent from the node
28 None,
29 /// The hazard is only partially present in the node
30 Partial(QTHazPartial),
31 /// The hazard is present in the entire node.
32 Entire,
33}
34impl QTHazard {
35 /// Converts a [`Hazard`] into a [`QTHazard`], assuming it is for the root of the quadtree.
36 pub fn from_qt_root(qt_root_bbox: Rect, haz: &Hazard) -> Self {
37 Self {
38 qt_bbox: qt_root_bbox,
39 entity: haz.entity,
40 presence: QTHazPresence::Partial(QTHazPartial {
41 shape: haz.shape.clone(),
42 edges: RelevantEdges::All,
43 }),
44 active: haz.active,
45 }
46 }
47
48 /// Returns the resulting QTHazards after constricting to the provided quadrants.
49 /// The quadrants should be ordered according to the [Cartesian system](https://en.wikipedia.org/wiki/Quadrant_(plane_geometry))
50 /// and should all be inside the bounds from which `self` was created.
51 pub fn constrict(&self, quadrants: [Rect; 4]) -> [Self; 4] {
52 debug_assert!(
53 quadrants
54 .iter()
55 .all(|q| self.qt_bbox.relation_to(*q) == GeoRelation::Surrounding)
56 );
57 debug_assert!(assertions::quadrants_have_valid_layout(&quadrants));
58
59 match &self.presence {
60 QTHazPresence::None => unreachable!(),
61 QTHazPresence::Entire => [0, 1, 2, 3].map(|_| self.clone()),
62 QTHazPresence::Partial(partial_haz) => {
63 //If the hazard is partially present, it may produce different hazards for each quadrant
64
65 //check the bbox of the hazard with the bboxes of the quadrants
66 let haz_bbox = partial_haz.shape.bbox;
67
68 //Check if one of the quadrants entirely contains the hazard
69 let enclosed_hazard_quadrant = quadrants
70 .iter()
71 .map(|q| haz_bbox.relation_to(*q))
72 .position(|r| r == GeoRelation::Enclosed);
73
74 if let Some(quad_index) = enclosed_hazard_quadrant {
75 //The hazard is entirely enclosed within one quadrant,
76 //For this quadrant the QTHazard is equivalent to the original hazard, the rest are None
77 [0, 1, 2, 3].map(|i| {
78 let presence = match i {
79 i if i == quad_index => QTHazPresence::Partial(partial_haz.clone()),
80 _ => QTHazPresence::None,
81 };
82 Self {
83 qt_bbox: quadrants[i],
84 entity: self.entity,
85 presence,
86 active: self.active,
87 }
88 })
89 } else {
90 //The hazard is partially active in multiple quadrants, find which ones
91 let arc_shape = &partial_haz.shape;
92
93 //For every quadrant, check which of the edges of the hazard are relevant
94 let mut constricted_hazards = quadrants.map(|q| {
95 compute_edge_collisions_in_quadrant(q, &partial_haz.edges, arc_shape).map(
96 |p_haz| {
97 //The quadrant has collisions with the hazard edges
98 //For these quadrants we can be sure they will be of Partial presence
99 QTHazard {
100 qt_bbox: q,
101 entity: self.entity,
102 presence: QTHazPresence::Partial(p_haz),
103 active: self.active,
104 }
105 },
106 )
107 });
108
109 debug_assert!(constricted_hazards.iter().filter(|h| h.is_some()).count() > 0);
110
111 //At this point, we know which quadrants have collisions with which edges.
112 //What remain are the quadrants without any intersecting edges.
113 //These can either have the hazard entirely present or entirely absent.
114 for i in 0..4 {
115 let quadrant = quadrants[i];
116 if constricted_hazards[i].is_none() {
117 //Presence of Entire and None type are always separated by a node with Partial presence.
118 //If a neighbor is already resolved to Entire or None, this quadrant will have the same presence.
119 let [neighbor_0, neighbor_1] = Rect::QUADRANT_NEIGHBOR_LAYOUT[i];
120 let presence_n0 = constricted_hazards[neighbor_0]
121 .as_ref()
122 .map(|h| &h.presence);
123 let presence_n1 = constricted_hazards[neighbor_1]
124 .as_ref()
125 .map(|h| &h.presence);
126
127 let presence = match (presence_n0, &presence_n1) {
128 (Some(QTHazPresence::None), Some(QTHazPresence::Entire))
129 | (Some(QTHazPresence::Entire), Some(QTHazPresence::None)) => {
130 unreachable!(
131 "one of the neighbors is Entire, the other is None, this quadrant should be Partial"
132 )
133 }
134 (Some(QTHazPresence::Entire), _) => QTHazPresence::Entire,
135 (_, Some(QTHazPresence::Entire)) => QTHazPresence::Entire,
136 (Some(QTHazPresence::None), _) => QTHazPresence::None,
137 (_, Some(QTHazPresence::None)) => QTHazPresence::None,
138 _ => {
139 //Neither of its neighbors is resolved, check its position.
140 let haz_scope = self.entity.scope();
141 //Since partial presence is not possible, checking whether the center of the quadrant collides or not suffices
142 let colliding = arc_shape.collides_with(&quadrant.centroid());
143 match (haz_scope, colliding) {
144 (GeoPosition::Interior, true) => QTHazPresence::Entire,
145 (GeoPosition::Exterior, false) => QTHazPresence::Entire,
146 _ => QTHazPresence::None,
147 }
148 }
149 };
150
151 constricted_hazards[i] = Some(QTHazard {
152 qt_bbox: quadrant,
153 entity: self.entity,
154 presence,
155 active: self.active,
156 });
157 }
158 }
159
160 constricted_hazards
161 .map(|h| h.expect("all constricted hazards should be resolved"))
162 }
163 }
164 }
165 }
166
167 pub fn n_edges(&self) -> usize {
168 match &self.presence {
169 QTHazPresence::None | QTHazPresence::Entire => 0,
170 QTHazPresence::Partial(partial_haz) => partial_haz.n_edges(),
171 }
172 }
173}
174
175fn compute_edge_collisions_in_quadrant(
176 quadrant: Rect,
177 relevant_edges: &RelevantEdges,
178 shape: &Arc<SPolygon>,
179) -> Option<QTHazPartial> {
180 let mut p_haz = None;
181
182 let mut check_edge = |idx| {
183 let edge = shape.edge(idx);
184 if quadrant.collides_with(&edge) {
185 p_haz
186 .get_or_insert(QTHazPartial {
187 shape: shape.clone(),
188 edges: RelevantEdges::Some(vec![]),
189 })
190 .register_edge(idx);
191 }
192 };
193
194 match relevant_edges {
195 RelevantEdges::All => {
196 for i in 0..shape.n_vertices() {
197 check_edge(i);
198 }
199 }
200 RelevantEdges::Some(indices) => {
201 for &i in indices {
202 check_edge(i);
203 }
204 }
205 }
206
207 p_haz
208}