jagua_rs/entities/
layout.rs1use crate::collision_detection::hazards::Hazard;
2use crate::collision_detection::hazards::filter::QualityZoneFilter;
3use crate::collision_detection::{CDESnapshot, CDEngine};
4use crate::entities::Container;
5use crate::entities::Item;
6use crate::entities::{PItemKey, PlacedItem};
7use crate::geometry::DTransformation;
8use crate::util::assertions;
9use itertools::Itertools;
10use slotmap::SlotMap;
11use std::sync::Arc;
12
13#[derive(Clone)]
17pub struct Layout {
18 pub(crate) container: Container,
20 pub(crate) placed_items: SlotMap<PItemKey, PlacedItem>,
22 cde: CDEngine,
24}
25
26impl Layout {
27 #[must_use]
28 pub fn new(container: Container) -> Self {
29 let cde = container.base_cde.as_ref().clone();
30 Layout {
31 container,
32 placed_items: SlotMap::with_key(),
33 cde,
34 }
35 }
36
37 #[must_use]
38 pub fn from_snapshot(ls: &LayoutSnapshot) -> Self {
39 let mut layout = Layout::new(ls.container.clone());
40 layout
41 .restore(ls)
42 .expect("snapshot shares the container's static base");
43 layout
44 }
45
46 pub fn swap_container(&mut self, container: Container) {
48 let cde_snapshot = self.cde.save();
49 self.container = container;
51 self.cde = self.container.base_cde.as_ref().clone();
52 for hazard in cde_snapshot.dynamic_hazards {
53 self.cde.register_hazard(hazard);
55 }
56 }
57
58 #[must_use]
60 pub fn save(&self) -> LayoutSnapshot {
61 LayoutSnapshot {
62 container: self.container.clone(),
63 placed_items: self.placed_items.clone(),
64 cde_snapshot: self.cde.save(),
65 }
66 }
67
68 pub fn restore(&mut self, layout_snapshot: &LayoutSnapshot) -> Result<(), ContainerMismatch> {
76 if !Arc::ptr_eq(
77 &self.container.base_cde,
78 &layout_snapshot.container.base_cde,
79 ) {
80 return Err(ContainerMismatch);
81 }
82 self.container.clone_from(&layout_snapshot.container);
83
84 self.placed_items.clone_from(&layout_snapshot.placed_items);
85 self.cde.restore(&layout_snapshot.cde_snapshot);
86
87 debug_assert!(assertions::layout_cde_matches_fresh(self));
88 debug_assert!(assertions::snapshot_matches_layout(self, layout_snapshot));
89 Ok(())
90 }
91
92 pub fn place_item(&mut self, item: &Arc<Item>, d_transformation: DTransformation) -> PItemKey {
96 let pk = self
97 .placed_items
98 .insert(PlacedItem::new(item, d_transformation));
99 let pi = &self.placed_items[pk];
100 let hazard = Hazard::new((pk, pi).into(), pi.shape.clone());
101
102 self.cde.register_hazard(hazard);
103
104 debug_assert!(assertions::layout_cde_matches_fresh(self));
105
106 pk
107 }
108
109 pub fn remove_item(&mut self, pk: PItemKey) -> PlacedItem {
111 let pi = self
112 .placed_items
113 .remove(pk)
114 .expect("key is not valid anymore");
115
116 self.cde.deregister_hazard_by_entity((pk, &pi).into());
118
119 debug_assert!(assertions::layout_cde_matches_fresh(self));
120
121 pi
122 }
123
124 #[must_use]
126 pub fn is_empty(&self) -> bool {
127 self.placed_items.is_empty()
128 }
129
130 #[must_use]
133 pub fn density(&self) -> f32 {
134 self.placed_item_area() / self.container.area()
135 }
136
137 #[must_use]
139 pub fn placed_item_area(&self) -> f32 {
140 self.placed_items
141 .values()
142 .map(|pi| pi.item.area())
143 .sum::<f32>()
144 }
145
146 pub fn items(&self) -> impl Iterator<Item = &Item> {
148 self.placed_items
149 .values()
150 .map(|pi| pi.item.as_ref())
151 .unique_by(|item| item.idx)
152 }
153
154 #[must_use]
156 pub fn cde(&self) -> &CDEngine {
157 &self.cde
158 }
159
160 #[must_use]
163 pub fn is_collision_free(&self) -> bool {
164 self.placed_items.iter().all(|(pk, pi)| {
165 let hkey = self
166 .cde
167 .haz_key(&(pk, pi).into())
168 .expect("all placed items should be registered in the CDE");
169 match pi.item.min_quality {
170 Some(min_quality) => {
171 let filter = (QualityZoneFilter { min_quality }, hkey);
172 !self.cde.detect_poly_collision(&pi.shape, &filter)
173 }
174 None => !self.cde.detect_poly_collision(&pi.shape, &hkey),
175 }
176 })
177 }
178
179 #[must_use]
181 pub fn container(&self) -> &Container {
182 &self.container
183 }
184
185 #[must_use]
187 pub fn placed_items(&self) -> &SlotMap<PItemKey, PlacedItem> {
188 &self.placed_items
189 }
190}
191
192#[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)]
194#[error("layout and snapshot have different static collision bases")]
195pub struct ContainerMismatch;
196
197#[derive(Clone, Debug)]
200pub struct LayoutSnapshot {
201 pub(crate) container: Container,
203 pub(crate) placed_items: SlotMap<PItemKey, PlacedItem>,
205 pub(crate) cde_snapshot: CDESnapshot,
207}
208
209impl LayoutSnapshot {
210 #[must_use]
212 pub fn container(&self) -> &Container {
213 &self.container
214 }
215
216 #[must_use]
218 pub fn placed_items(&self) -> &SlotMap<PItemKey, PlacedItem> {
219 &self.placed_items
220 }
221
222 #[must_use]
224 pub fn density(&self) -> f32 {
225 self.placed_item_area() / self.container.area()
226 }
227
228 #[must_use]
230 pub fn placed_item_area(&self) -> f32 {
231 self.placed_items
232 .values()
233 .map(|pi| pi.item.area())
234 .sum::<f32>()
235 }
236}