Skip to main content

jagua_rs/util/
assertions.rs

1use crate::collision_detection::CDEngine;
2use crate::collision_detection::hazards::Hazard;
3use crate::collision_detection::hazards::{BasicHazardEntity, HazardEntity};
4use crate::entities::Layout;
5use crate::entities::LayoutSnapshot;
6use crate::geometry::fail_fast::SPSurrogate;
7use crate::geometry::geo_enums::GeoPosition;
8use itertools::Itertools;
9use std::collections::HashSet;
10//Various checks to verify correctness of the state of the system
11//Used in debug_assertion!() blocks
12
13#[must_use]
14pub fn snapshot_matches_layout(layout: &Layout, layout_snapshot: &LayoutSnapshot) -> bool {
15    if !std::sync::Arc::ptr_eq(
16        &layout.container.base_cde,
17        &layout_snapshot.container.base_cde,
18    ) {
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.idx == placed_item.item.idx && pi.d_transf == placed_item.d_transf)
26        {
27            return false;
28        }
29    }
30    true
31}
32
33/// Checks that the engine's index holds exactly the bounding boxes of all hazards except the
34/// exterior, sorted by `x_min`, and that its chunk bounds are the largest `x_max` of each chunk.
35#[must_use]
36pub fn index_matches_hazards<E: HazardEntity>(cde: &CDEngine<E>) -> bool {
37    let indexed = cde.index.iter().collect_vec();
38    let sorted = indexed.is_sorted_by(|(_, a), (_, b)| a.x_min <= b.x_min);
39    let chunk_bounds = indexed
40        .chunks(64)
41        .map(|chunk| {
42            chunk
43                .iter()
44                .map(|(_, bbox)| bbox.x_max)
45                .fold(f32::NEG_INFINITY, f32::max)
46        })
47        .eq(cde.index.chunk_x_max().iter().copied());
48    let interior = cde
49        .hazards_map
50        .iter()
51        .filter(|(_, h)| h.entity().scope() == GeoPosition::Interior)
52        .map(|(k, h)| (k, h.shape().bbox))
53        .sorted_by_key(|(k, _)| *k)
54        .collect_vec();
55    let by_key = indexed.into_iter().sorted_by_key(|(k, _)| *k).collect_vec();
56    sorted && chunk_bounds && by_key == interior
57}
58
59/// Checks that the layout's engine holds the container's hazards and those of its placed items.
60#[must_use]
61pub fn layout_cde_matches_fresh(layout: &Layout) -> bool {
62    let hazards = layout
63        .placed_items
64        .iter()
65        .map(|(pk, pi)| Hazard::new(BasicHazardEntity::from((pk, pi)), pi.shape.clone()));
66    cde_matches_fresh(layout.cde(), &layout.container.base_cde, hazards)
67}
68
69/// Checks that `cde` matches a fresh copy of `base` with `dynamic_hazards` registered:
70/// the same hazards, and an index that matches them.
71#[must_use]
72pub fn cde_matches_fresh<E: HazardEntity>(
73    cde: &CDEngine<E>,
74    base: &CDEngine<E>,
75    dynamic_hazards: impl IntoIterator<Item = Hazard<E>>,
76) -> bool {
77    let mut fresh_cde = base.clone();
78    for hazard in dynamic_hazards {
79        fresh_cde.register_hazard(hazard);
80    }
81    index_matches_hazards(cde) && hazards_match(cde.hazards(), fresh_cde.hazards())
82}
83
84fn hazards_match<'a, E: HazardEntity + 'a>(
85    chv1: impl Iterator<Item = &'a Hazard<E>>,
86    chv2: impl Iterator<Item = &'a Hazard<E>>,
87) -> bool {
88    let chv1_active_hazards = chv1.map(Hazard::entity).collect::<HashSet<_>>();
89
90    let chv2_active_hazards = chv2.map(Hazard::entity).collect::<HashSet<_>>();
91
92    if chv1_active_hazards != chv2_active_hazards {
93        println!("Hazard vecs don't match");
94        return false;
95    }
96    true
97}
98
99///Prints code to rebuild a layout. Intended for debugging purposes.
100pub fn print_layout(layout: &Layout) {
101    println!("let mut layout = Layout::new(container.clone());");
102    println!();
103
104    for pi in layout.placed_items.values() {
105        let transformation_str = {
106            let t_decomp = &pi.d_transf;
107            let (tr, (tx, ty)) = (t_decomp.rotation(), t_decomp.translation());
108            format!(
109                "&DTransformation::new({tr:.6},({tx:.6},{ty:.6})).with_reflection({})",
110                t_decomp.reflected
111            )
112        };
113
114        println!(
115            "layout.place_item(instance.item({}), {});",
116            pi.item.idx, transformation_str
117        );
118    }
119}
120
121/// Checks a transformed buffer's hull indices against those recomputed from its reference.
122#[must_use]
123pub fn hull_indices_match_reference(
124    surrogate: &SPSurrogate,
125    reference: &SPSurrogate,
126    reflected: bool,
127    n_vertices: usize,
128) -> bool {
129    let ref_indices = &reference.convex_hull_indices;
130    let expected: Vec<usize> = if reflected {
131        ref_indices
132            .iter()
133            .rev()
134            .map(|&i| n_vertices - 1 - i)
135            .collect()
136    } else {
137        ref_indices.clone()
138    };
139    surrogate.convex_hull_indices == expected
140}