jagua_rs/util/
assertions.rs1use crate::collision_detection::CDEngine;
2use crate::collision_detection::hazards::Hazard;
3use crate::collision_detection::hazards::HazardEntity;
4use crate::collision_detection::quadtree::QTHazPresence;
5use crate::collision_detection::quadtree::QTHazard;
6use crate::collision_detection::quadtree::QTNode;
7use crate::entities::Layout;
8use crate::entities::LayoutSnapshot;
9use crate::geometry::primitives::Rect;
10use itertools::Itertools;
11use log::error;
12use std::collections::HashSet;
13pub fn layouts_match(layout: &Layout, layout_snapshot: &LayoutSnapshot) -> bool {
17 if layout.container.id != layout_snapshot.container.id {
18 return false;
19 }
20 for placed_item in layout_snapshot.placed_items.values() {
21 if !layout
22 .placed_items()
23 .values()
24 .any(|pi| pi.item_id == placed_item.item_id && pi.d_transf == placed_item.d_transf)
25 {
26 return false;
27 }
28 }
29 true
31}
32
33pub fn collision_hazards_sorted_correctly(hazards: &[QTHazard]) -> bool {
34 let mut partial_hazard_detected = false;
35 for hazard in hazards.iter() {
36 match hazard.presence {
37 QTHazPresence::Partial(_) => {
38 partial_hazard_detected = true;
39 }
40 QTHazPresence::Entire => {
41 if partial_hazard_detected {
42 return false;
43 }
44 }
45 QTHazPresence::None => {
46 panic!("None hazard should never be collision hazard vec");
47 }
48 };
49 }
50 true
51}
52
53pub fn qt_node_contains_no_deactivated_hazards<'a>(
54 node: &'a QTNode,
55 mut stacktrace: Vec<&'a QTNode>,
56) -> (bool, Vec<&'a QTNode>) {
57 stacktrace.push(node);
58 let deactivated_hazard = node.hazards.all_hazards().iter().find(|h| !h.active);
59 if deactivated_hazard.is_some() {
60 println!("Deactivated hazard found");
61 dbg!(&stacktrace);
62 return (false, stacktrace);
63 }
64
65 if let Some(children) = &node.children {
66 for child in children.as_ref() {
67 let result = qt_node_contains_no_deactivated_hazards(child, stacktrace);
68 stacktrace = result.1;
69 let contains_no_deactivated_hazards = result.0;
70 if !contains_no_deactivated_hazards {
71 return (false, stacktrace);
72 }
73 }
74 }
75
76 stacktrace.pop();
77 (true, stacktrace)
78}
79
80pub fn qt_contains_no_dangling_hazards(cde: &CDEngine) -> bool {
81 if let Some(children) = &cde.quadtree().children {
82 for child in children.as_ref() {
83 if !qt_node_contains_no_dangling_hazards(child, cde.quadtree()) {
84 return false;
85 }
86 }
87 }
88 true
89}
90
91fn qt_node_contains_no_dangling_hazards(node: &QTNode, parent: &QTNode) -> bool {
92 let parent_h_entities = parent
93 .hazards
94 .all_hazards()
95 .iter()
96 .map(|h| &h.entity)
97 .unique()
98 .collect_vec();
99
100 let dangling_hazards = node
101 .hazards
102 .all_hazards()
103 .iter()
104 .any(|h| !parent_h_entities.contains(&&h.entity));
105 if dangling_hazards {
106 println!("Node contains dangling hazard");
107 return false;
108 }
109
110 if let Some(children) = &node.children {
111 for child in children.as_ref() {
112 if !qt_node_contains_no_dangling_hazards(child, node) {
113 return false;
114 }
115 }
116 }
117
118 true
119}
120
121pub fn qt_hz_entity_activation_consistent(cde: &CDEngine) -> bool {
122 for (active, hz_entity) in cde
123 .quadtree()
124 .hazards
125 .all_hazards()
126 .iter()
127 .map(|h| (h.active, &h.entity))
128 .unique()
129 {
130 if !hz_entity_same_everywhere(cde.quadtree(), hz_entity, active) {
131 return false;
132 }
133 }
134 true
135}
136
137pub fn hz_entity_same_everywhere(qt_node: &QTNode, hz_entity: &HazardEntity, active: bool) -> bool {
138 if let Some(h) = qt_node
139 .hazards
140 .all_hazards()
141 .iter()
142 .find(|h| &h.entity == hz_entity)
143 {
144 if h.active != active {
145 println!("Hazard entity activation inconsistent");
146 return false;
147 }
148 }
149 if let Some(children) = &qt_node.children {
150 for child in children.as_ref() {
151 if !hz_entity_same_everywhere(child, hz_entity, active) {
152 return false;
153 }
154 }
155 }
156
157 true
158}
159
160pub fn layout_qt_matches_fresh_qt(layout: &Layout) -> bool {
161 let container = &layout.container;
165 let mut fresh_cde = container.base_cde.as_ref().clone();
166 for (pk, pi) in layout.placed_items().iter() {
167 let hazard = Hazard::new((pk, pi).into(), pi.shape.clone());
168 fresh_cde.register_hazard(hazard);
169 }
170
171 qt_nodes_match(Some(layout.cde().quadtree()), Some(fresh_cde.quadtree()))
172 && hazards_match(layout.cde().dynamic_hazards(), fresh_cde.dynamic_hazards())
173}
174
175fn qt_nodes_match(qn1: Option<&QTNode>, qn2: Option<&QTNode>) -> bool {
176 let hashable = |h: &QTHazard| {
177 let p_sk = match h.presence {
178 QTHazPresence::None => 0,
179 QTHazPresence::Partial(_) => 1,
180 QTHazPresence::Entire => 2,
181 };
182 (h.entity, h.active, p_sk)
183 };
184 match (qn1, qn2) {
185 (Some(qn1), Some(qn2)) => {
186 let hv1 = &qn1.hazards;
188 let hv2 = &qn2.hazards;
189
190 let active_haz_1 = hv1
192 .active_hazards()
193 .iter()
194 .map(hashable)
195 .collect::<HashSet<(HazardEntity, bool, u8)>>();
196
197 let active_haz_2 = hv2
198 .active_hazards()
199 .iter()
200 .map(hashable)
201 .collect::<HashSet<(HazardEntity, bool, u8)>>();
202
203 let active_in_1_but_not_2 = active_haz_1
204 .difference(&active_haz_2)
205 .collect::<HashSet<_>>();
206 let active_in_2_but_not_1 = active_haz_2
207 .difference(&active_haz_1)
208 .collect::<HashSet<_>>();
209
210 if !(active_in_1_but_not_2.is_empty() && active_in_2_but_not_1.is_empty()) {
211 let from_1 = **active_in_1_but_not_2.iter().next().unwrap();
212 let from_2 = **active_in_2_but_not_1.iter().next().unwrap();
213 println!("{}", from_1 == from_2);
214 error!(
215 "Active hazards don't match {active_in_1_but_not_2:?} vs {active_in_2_but_not_1:?}"
216 );
217 return false;
218 }
219 }
220 (Some(qn1), None) => {
221 if qn1.hazards.active_hazards().iter().next().is_some() {
222 error!("qn1 contains active hazards while other qn2 does not exist");
223 return false;
224 }
225 }
226 (None, Some(qn2)) => {
227 if qn2.hazards.active_hazards().iter().next().is_some() {
228 error!("qn2 contains active hazards while other qn1 does not exist");
229 return false;
230 }
231 }
232 (None, None) => panic!("Both nodes are none"),
233 }
234
235 match (
237 qn1.map_or(&None, |qn| &qn.children),
238 qn2.map_or(&None, |qn| &qn.children),
239 ) {
240 (None, None) => true,
241 (Some(c1), None) => {
242 let qn1_has_partial_hazards = qn1.is_some_and(|qn| {
243 qn.hazards
244 .active_hazards()
245 .iter()
246 .any(|h| matches!(h.presence, QTHazPresence::Partial(_)))
247 });
248 if qn1_has_partial_hazards {
249 for child in c1.as_ref() {
250 if !qt_nodes_match(Some(child), None) {
251 return false;
252 }
253 }
254 }
255 true
256 }
257 (None, Some(c2)) => {
258 let qn2_has_partial_hazards = qn2.is_some_and(|qn| {
259 qn.hazards
260 .active_hazards()
261 .iter()
262 .any(|h| matches!(h.presence, QTHazPresence::Partial(_)))
263 });
264 if qn2_has_partial_hazards {
265 for child in c2.as_ref() {
266 if !qt_nodes_match(None, Some(child)) {
267 return false;
268 }
269 }
270 }
271 true
272 }
273 (Some(c1), Some(c2)) => {
274 for (child1, child2) in c1.as_ref().iter().zip(c2.as_ref().iter()) {
275 if !qt_nodes_match(Some(child1), Some(child2)) {
276 return false;
277 }
278 }
279 true
280 }
281 }
282}
283
284fn hazards_match(chv1: &[Hazard], chv2: &[Hazard]) -> bool {
285 let chv1_active_hazards = chv1
286 .iter()
287 .filter(|h| h.active)
288 .map(|h| &h.entity)
289 .collect::<HashSet<_>>();
290
291 let chv2_active_hazards = chv2
292 .iter()
293 .filter(|h| h.active)
294 .map(|h| &h.entity)
295 .collect::<HashSet<_>>();
296
297 if chv1_active_hazards != chv2_active_hazards {
298 println!("Hazard vecs don't match");
299 return false;
300 }
301 true
302}
303
304pub fn quadrants_have_valid_layout(quadrants: &[Rect; 4]) -> bool {
306 let layout = Rect::QUADRANT_NEIGHBOR_LAYOUT;
307 for (idx, q) in quadrants.iter().enumerate() {
308 let [n_0, n_1] = layout[idx];
310 let q_corners = q.corners();
311 let n_0_corners = quadrants[n_0].corners();
312 let n_1_corners = quadrants[n_1].corners();
313
314 assert_eq!(
315 2,
316 n_0_corners
317 .iter()
318 .filter(|c| q_corners.iter().any(|qc| &qc == c))
319 .count()
320 );
321 assert_eq!(
322 2,
323 n_1_corners
324 .iter()
325 .filter(|c| q_corners.iter().any(|qc| &qc == c))
326 .count()
327 );
328 }
329 true
330}
331
332pub fn print_layout(layout: &Layout) {
334 println!(
335 "let mut layout = Layout::new(0, instance.container({}).clone());",
336 layout.container.id
337 );
338 println!();
339
340 for pi in layout.placed_items().values() {
341 let transformation_str = {
342 let t_decomp = &pi.d_transf;
343 let (tr, (tx, ty)) = (t_decomp.rotation(), t_decomp.translation());
344 format!("&DTransformation::new({tr:.6},({tx:.6},{ty:.6}))")
345 };
346
347 println!(
348 "layout.place_item(instance.item({}), {});",
349 pi.item_id, transformation_str
350 );
351 }
352}