jagua_rs/entities/
layout.rs

1use crate::collision_detection::hazards::Hazard;
2use crate::collision_detection::{CDESnapshot, CDEngine};
3use crate::entities::Item;
4use crate::entities::{Container, Instance};
5use crate::entities::{PItemKey, PlacedItem};
6use crate::geometry::DTransformation;
7use crate::util::assertions;
8use slotmap::SlotMap;
9
10/// A [`Layout`] is a dynamic representation of items that have been placed in a container at specific positions.
11/// Items can be placed and removed. The container can be swapped. Snapshots can be taken and restored to.
12/// Each layout maintains a [`CDEngine`], which can be used to check for collisions before placing items.
13#[derive(Clone)]
14pub struct Layout {
15    /// The container used for this layout
16    pub container: Container,
17    /// All the items that have been placed in this layout, indexed by a unique key
18    pub placed_items: SlotMap<PItemKey, PlacedItem>,
19    /// The collision detection engine for this layout
20    cde: CDEngine,
21}
22
23impl Layout {
24    #[must_use]
25    pub fn new(container: Container) -> Self {
26        let cde = container.base_cde.as_ref().clone();
27        Layout {
28            container,
29            placed_items: SlotMap::with_key(),
30            cde,
31        }
32    }
33
34    #[must_use]
35    pub fn from_snapshot(ls: &LayoutSnapshot) -> Self {
36        let mut layout = Layout::new(ls.container.clone());
37        layout.restore(ls);
38        layout
39    }
40
41    /// Replaces the current container with a new one, rebuilding the collision detection engine accordingly.
42    pub fn swap_container(&mut self, container: Container) {
43        let cde_snapshot = self.cde.save();
44        // rebuild the CDE
45        self.container = container;
46        self.cde = self.container.base_cde.as_ref().clone();
47        for hazard in cde_snapshot.dynamic_hazards {
48            // re-register all dynamic hazards from the previous CDE snapshot
49            self.cde.register_hazard(hazard);
50        }
51    }
52
53    /// Saves the current state of the layout to be potentially restored to later.
54    #[must_use]
55    pub fn save(&self) -> LayoutSnapshot {
56        LayoutSnapshot {
57            container: self.container.clone(),
58            placed_items: self.placed_items.clone(),
59            cde_snapshot: self.cde.save(),
60        }
61    }
62
63    /// Restores the layout to a previous state using a snapshot.
64    pub fn restore(&mut self, layout_snapshot: &LayoutSnapshot) {
65        assert_eq!(self.container.id, layout_snapshot.container.id);
66
67        self.placed_items.clone_from(&layout_snapshot.placed_items);
68        self.cde.restore(&layout_snapshot.cde_snapshot);
69
70        debug_assert!(assertions::layout_qt_matches_fresh_qt(self));
71        debug_assert!(assertions::snapshot_matches_layout(self, layout_snapshot));
72    }
73
74    /// Places an item in the layout at a specific position by applying a transformation.
75    /// Returns the unique key for the placed item.
76    pub fn place_item(&mut self, item: &Item, d_transformation: DTransformation) -> PItemKey {
77        let pk = self
78            .placed_items
79            .insert(PlacedItem::new(item, d_transformation));
80        let pi = &self.placed_items[pk];
81        let hazard = Hazard::new((pk, pi).into(), pi.shape.clone(), true);
82
83        self.cde.register_hazard(hazard);
84
85        debug_assert!(assertions::layout_qt_matches_fresh_qt(self));
86
87        pk
88    }
89
90    /// Removes an item from the layout by its unique key and returns the removed [`PlacedItem`].
91    pub fn remove_item(&mut self, pk: PItemKey) -> PlacedItem {
92        let pi = self
93            .placed_items
94            .remove(pk)
95            .expect("key is not valid anymore");
96
97        // update the collision detection engine
98        self.cde.deregister_hazard_by_entity((pk, &pi).into());
99
100        debug_assert!(assertions::layout_qt_matches_fresh_qt(self));
101
102        pi
103    }
104
105    /// True if no items are placed
106    #[must_use]
107    pub fn is_empty(&self) -> bool {
108        self.placed_items.is_empty()
109    }
110
111    /// The current density of the layout defined as the ratio of the area of the items placed to the area of the container.
112    /// Uses the original shapes of items and container to calculate the area.
113    pub fn density(&self, instance: &impl Instance) -> f32 {
114        self.placed_item_area(instance) / self.container.area()
115    }
116
117    /// The sum of the areas of the items placed in the layout (using the original shapes of the items).
118    pub fn placed_item_area(&self, instance: &impl Instance) -> f32 {
119        self.placed_items
120            .iter()
121            .map(|(_, pi)| instance.item(pi.item_id))
122            .map(Item::area)
123            .sum::<f32>()
124    }
125
126    /// Returns the collision detection engine for this layout
127    #[must_use]
128    pub fn cde(&self) -> &CDEngine {
129        &self.cde
130    }
131
132    /// Returns true if all the items are placed without colliding
133    #[must_use]
134    pub fn is_feasible(&self) -> bool {
135        self.placed_items.iter().all(|(pk, pi)| {
136            let hkey = self
137                .cde
138                .haz_key_from_pi_key(pk)
139                .expect("all placed items should be registered in the CDE");
140            !self.cde.detect_poly_collision(&pi.shape, &hkey)
141        })
142    }
143}
144
145/// Immutable and compact representation of a [`Layout`].
146/// Can be used to restore a [`Layout`] back to a previous state.
147#[derive(Clone, Debug)]
148pub struct LayoutSnapshot {
149    /// A copy of the container used in the layout
150    pub container: Container,
151    /// A copy of the placed items in the layout
152    pub placed_items: SlotMap<PItemKey, PlacedItem>,
153    /// Snapshot of the collision detection engine
154    pub cde_snapshot: CDESnapshot,
155}
156
157impl LayoutSnapshot {
158    /// Equivalent to [`Layout::density`]
159    pub fn density(&self, instance: &impl Instance) -> f32 {
160        self.placed_item_area(instance) / self.container.area()
161    }
162
163    /// Equivalent to [`Layout::placed_item_area`]
164    pub fn placed_item_area(&self, instance: &impl Instance) -> f32 {
165        self.placed_items
166            .iter()
167            .map(|(_, pi)| instance.item(pi.item_id))
168            .map(Item::area)
169            .sum::<f32>()
170    }
171}