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