jagua_rs/entities/
layout.rs

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