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;
13#[must_use]
17pub fn snapshot_matches_layout(layout: &Layout, layout_snapshot: &LayoutSnapshot) -> bool {
18 if layout.container.id != layout_snapshot.container.id {
19 return false;
20 }
21 for placed_item in layout_snapshot.placed_items.values() {
22 if !layout
23 .placed_items
24 .values()
25 .any(|pi| pi.item_id == placed_item.item_id && pi.d_transf == placed_item.d_transf)
26 {
27 return false;
28 }
29 }
30 true
31}
32
33#[must_use]
34pub fn collision_hazards_sorted_correctly(hazards: &[QTHazard]) -> bool {
35 let mut partial_hazard_detected = false;
36 for hazard in hazards {
37 match hazard.presence {
38 QTHazPresence::Partial(_) => {
39 partial_hazard_detected = true;
40 }
41 QTHazPresence::Entire => {
42 if partial_hazard_detected {
43 return false;
44 }
45 }
46 QTHazPresence::None => {
47 panic!("None hazard should never be collision hazard vec");
48 }
49 }
50 }
51 true
52}
53
54#[must_use]
55pub fn qt_contains_no_dangling_hazards(cde: &CDEngine) -> bool {
56 if let Some(children) = &cde.quadtree.children {
57 for child in children.as_ref() {
58 if !qt_node_contains_no_dangling_hazards(child, &cde.quadtree) {
59 return false;
60 }
61 }
62 }
63 true
64}
65
66fn qt_node_contains_no_dangling_hazards(node: &QTNode, parent: &QTNode) -> bool {
67 let parent_h_entities = parent
68 .hazards
69 .iter()
70 .map(|h| &h.entity)
71 .unique()
72 .collect_vec();
73
74 let dangling_hazards = node
75 .hazards
76 .iter()
77 .any(|h| !parent_h_entities.contains(&&h.entity));
78 if dangling_hazards {
79 println!("Node contains dangling hazard");
80 return false;
81 }
82
83 if let Some(children) = &node.children {
84 for child in children.as_ref() {
85 if !qt_node_contains_no_dangling_hazards(child, node) {
86 return false;
87 }
88 }
89 }
90
91 true
92}
93
94#[must_use]
95pub fn layout_qt_matches_fresh_qt(layout: &Layout) -> bool {
96 let container = &layout.container;
100 let mut fresh_cde = container.base_cde.as_ref().clone();
101 for (pk, pi) in &layout.placed_items {
102 let hazard = Hazard::new((pk, pi).into(), pi.shape.clone(), true);
103 fresh_cde.register_hazard(hazard);
104 }
105
106 qt_nodes_match(Some(&layout.cde().quadtree), Some(&fresh_cde.quadtree))
107 && hazards_match(layout.cde().hazards(), fresh_cde.hazards())
108}
109
110fn qt_nodes_match(qn1: Option<&QTNode>, qn2: Option<&QTNode>) -> bool {
111 let hashable = |h: &QTHazard| {
112 let p_sk = match h.presence {
113 QTHazPresence::None => 0,
114 QTHazPresence::Partial(_) => 1,
115 QTHazPresence::Entire => 2,
116 };
117 (h.entity, p_sk)
118 };
119 match (qn1, qn2) {
120 (Some(qn1), Some(qn2)) => {
121 let hv1 = &qn1.hazards;
123 let hv2 = &qn2.hazards;
124
125 let active_haz_1 = hv1
127 .iter()
128 .map(hashable)
129 .collect::<HashSet<(HazardEntity, u8)>>();
130
131 let active_haz_2 = hv2
132 .iter()
133 .map(hashable)
134 .collect::<HashSet<(HazardEntity, u8)>>();
135
136 let active_in_1_but_not_2 = active_haz_1
137 .difference(&active_haz_2)
138 .collect::<HashSet<_>>();
139 let active_in_2_but_not_1 = active_haz_2
140 .difference(&active_haz_1)
141 .collect::<HashSet<_>>();
142
143 if !(active_in_1_but_not_2.is_empty() && active_in_2_but_not_1.is_empty()) {
144 let from_1 = **active_in_1_but_not_2.iter().next().unwrap();
145 let from_2 = **active_in_2_but_not_1.iter().next().unwrap();
146 println!("{}", from_1 == from_2);
147 error!(
148 "Active hazards don't match {active_in_1_but_not_2:?} vs {active_in_2_but_not_1:?}"
149 );
150 return false;
151 }
152 }
153 (Some(qn1), None) => {
154 if qn1.hazards.iter().next().is_some() {
155 error!("qn1 contains active hazards while other qn2 does not exist");
156 return false;
157 }
158 }
159 (None, Some(qn2)) => {
160 if qn2.hazards.iter().next().is_some() {
161 error!("qn2 contains active hazards while other qn1 does not exist");
162 return false;
163 }
164 }
165 (None, None) => panic!("Both nodes are none"),
166 }
167
168 match (
170 qn1.map_or(&None, |qn| &qn.children),
171 qn2.map_or(&None, |qn| &qn.children),
172 ) {
173 (None, None) => true,
174 (Some(c1), None) => {
175 let qn1_has_partial_hazards = qn1.is_some_and(|qn| {
176 qn.hazards
177 .iter()
178 .any(|h| matches!(h.presence, QTHazPresence::Partial(_)))
179 });
180 if qn1_has_partial_hazards {
181 for child in c1.as_ref() {
182 if !qt_nodes_match(Some(child), None) {
183 return false;
184 }
185 }
186 }
187 true
188 }
189 (None, Some(c2)) => {
190 let qn2_has_partial_hazards = qn2.is_some_and(|qn| {
191 qn.hazards
192 .iter()
193 .any(|h| matches!(h.presence, QTHazPresence::Partial(_)))
194 });
195 if qn2_has_partial_hazards {
196 for child in c2.as_ref() {
197 if !qt_nodes_match(None, Some(child)) {
198 return false;
199 }
200 }
201 }
202 true
203 }
204 (Some(c1), Some(c2)) => {
205 for (child1, child2) in c1.as_ref().iter().zip(c2.as_ref().iter()) {
206 if !qt_nodes_match(Some(child1), Some(child2)) {
207 return false;
208 }
209 }
210 true
211 }
212 }
213}
214
215fn hazards_match<'a>(
216 chv1: impl Iterator<Item = &'a Hazard>,
217 chv2: impl Iterator<Item = &'a Hazard>,
218) -> bool {
219 let chv1_active_hazards = chv1.map(|h| h.entity).collect::<HashSet<_>>();
220
221 let chv2_active_hazards = chv2.map(|h| h.entity).collect::<HashSet<_>>();
222
223 if chv1_active_hazards != chv2_active_hazards {
224 println!("Hazard vecs don't match");
225 return false;
226 }
227 true
228}
229
230#[must_use]
232pub fn quadrants_have_valid_layout(quadrants: &[Rect; 4]) -> bool {
233 let layout = Rect::QUADRANT_NEIGHBOR_LAYOUT;
234 for (idx, q) in quadrants.iter().enumerate() {
235 let [n_0, n_1] = layout[idx];
237 let q_corners = q.corners();
238 let n_0_corners = quadrants[n_0].corners();
239 let n_1_corners = quadrants[n_1].corners();
240
241 assert_eq!(
242 2,
243 n_0_corners
244 .iter()
245 .filter(|c| q_corners.iter().any(|qc| &qc == c))
246 .count()
247 );
248 assert_eq!(
249 2,
250 n_1_corners
251 .iter()
252 .filter(|c| q_corners.iter().any(|qc| &qc == c))
253 .count()
254 );
255 }
256 true
257}
258
259pub fn print_layout(layout: &Layout) {
261 println!(
262 "let mut layout = Layout::new(0, instance.container({}).clone());",
263 layout.container.id
264 );
265 println!();
266
267 for pi in layout.placed_items.values() {
268 let transformation_str = {
269 let t_decomp = &pi.d_transf;
270 let (tr, (tx, ty)) = (t_decomp.rotation(), t_decomp.translation());
271 format!("&DTransformation::new({tr:.6},({tx:.6},{ty:.6}))")
272 };
273
274 println!(
275 "layout.place_item(instance.item({}), {});",
276 pi.item_id, transformation_str
277 );
278 }
279}