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
64        self.placed_items = layout_snapshot.placed_items.clone();
65        self.cde.restore(&layout_snapshot.cde_snapshot);
66
67        debug_assert!(assertions::layout_qt_matches_fresh_qt(self));
68        debug_assert!(assertions::snapshot_matches_layout(self, layout_snapshot))
69    }
70
71    /// Places an item in the layout at a specific position by applying a transformation.
72    /// Returns the unique key for the placed item.
73    pub fn place_item(&mut self, item: &Item, d_transformation: DTransformation) -> PItemKey {
74        let pk = self
75            .placed_items
76            .insert(PlacedItem::new(item, d_transformation));
77        let pi = &self.placed_items[pk];
78        let hazard = Hazard::new((pk, pi).into(), pi.shape.clone(), true);
79
80        self.cde.register_hazard(hazard);
81
82        debug_assert!(assertions::layout_qt_matches_fresh_qt(self));
83
84        pk
85    }
86
87    /// Removes an item from the layout by its unique key and returns the removed [`PlacedItem`].
88    pub fn remove_item(&mut self, pk: PItemKey) -> PlacedItem {
89        let pi = self
90            .placed_items
91            .remove(pk)
92            .expect("key is not valid anymore");
93
94        // update the collision detection engine
95        self.cde.deregister_hazard_by_entity((pk, &pi).into());
96
97        debug_assert!(assertions::layout_qt_matches_fresh_qt(self));
98
99        pi
100    }
101
102    /// True if no items are placed
103    pub fn is_empty(&self) -> bool {
104        self.placed_items.is_empty()
105    }
106
107    /// The current density of the layout defined as the ratio of the area of the items placed to the area of the container.
108    /// Uses the original shapes of items and container to calculate the area.
109    pub fn density(&self, instance: &impl Instance) -> f32 {
110        self.placed_item_area(instance) / self.container.area()
111    }
112
113    /// The sum of the areas of the items placed in the layout (using the original shapes of the items).
114    pub fn placed_item_area(&self, instance: &impl Instance) -> f32 {
115        self.placed_items
116            .iter()
117            .map(|(_, pi)| instance.item(pi.item_id))
118            .map(|item| item.area())
119            .sum::<f32>()
120    }
121
122    /// Returns the collision detection engine for this layout
123    pub fn cde(&self) -> &CDEngine {
124        &self.cde
125    }
126
127    /// Returns true if all the items are placed without colliding
128    pub fn is_feasible(&self) -> bool {
129        self.placed_items.iter().all(|(pk, pi)| {
130            let hkey = self
131                .cde
132                .haz_key_from_pi_key(pk)
133                .expect("all placed items should be registered in the CDE");
134            !self.cde.detect_poly_collision(&pi.shape, &hkey)
135        })
136    }
137}
138
139/// Immutable and compact representation of a [`Layout`].
140/// Can be used to restore a [`Layout`] back to a previous state.
141#[derive(Clone, Debug)]
142pub struct LayoutSnapshot {
143    /// A copy of the container used in the layout
144    pub container: Container,
145    /// A copy of the placed items in the layout
146    pub placed_items: SlotMap<PItemKey, PlacedItem>,
147    /// Snapshot of the collision detection engine
148    pub cde_snapshot: CDESnapshot,
149}
150
151impl LayoutSnapshot {
152    /// Equivalent to [`Layout::density`]
153    pub fn density(&self, instance: &impl Instance) -> f32 {
154        self.placed_item_area(instance) / self.container.area()
155    }
156
157    /// Equivalent to [`Layout::placed_item_area`]
158    pub fn placed_item_area(&self, instance: &impl Instance) -> f32 {
159        self.placed_items
160            .iter()
161            .map(|(_, pi)| instance.item(pi.item_id))
162            .map(|item| item.area())
163            .sum::<f32>()
164    }
165}