jagua_rs/util/
assertions.rs

1use 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//Various checks to verify correctness of the state of the system
14//Used in debug_assertion!() blocks
15
16pub 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    //check if every placed item is correctly represented in the quadtree
93
94    //rebuild the quadtree
95    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            //if both nodes exist
118            let hv1 = &qn1.hazards;
119            let hv2 = &qn2.hazards;
120
121            //collect active hazards to hashsets
122            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    //Check children
165    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
226/// Checks if the quadrants follow the layout set in [Rect::QUADRANT_NEIGHBOR_LAYOUT]
227pub 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        //make sure they share two points (an edge) with each neighbor
231        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
254///Prints code to rebuild a layout. Intended for debugging purposes.
255pub 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}