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