Skip to main content

jagua_rs/entities/
container.rs

1use std::sync::Arc;
2
3use itertools::Itertools;
4
5use crate::collision_detection::hazards::BasicHazardEntity;
6use crate::collision_detection::hazards::Hazard;
7use crate::collision_detection::{CDEConfig, CDEngine};
8use crate::geometry::OriginalShape;
9use crate::geometry::fail_fast::SPSurrogateConfig;
10use crate::geometry::primitives::SPolygon;
11
12use anyhow::{Result, ensure};
13
14/// A container in which [`Item`](crate::entities::Item)'s can be placed.
15#[derive(Clone, Debug)]
16pub struct Container {
17    /// Original contour of the container as defined in the input
18    pub(crate) outer_orig: Arc<OriginalShape>,
19    /// Contour of the container to be used for collision detection
20    pub(crate) outer_cd: Arc<SPolygon>,
21    /// Zones of different qualities in the container, stored per quality.
22    pub(crate) quality_zones: [Option<InferiorQualityZone>; N_QUALITIES],
23    /// The initial state of the `CDEngine` for this container. (equivalent to an empty layout using this container)
24    pub(crate) base_cde: Arc<CDEngine>,
25}
26
27impl Container {
28    pub fn new(
29        original_outer: OriginalShape,
30        quality_zones: Vec<InferiorQualityZone>,
31        cde_config: CDEConfig,
32    ) -> Result<Self> {
33        let outer = Arc::new(original_outer.convert_to_internal()?);
34        let outer_orig = Arc::new(original_outer);
35        ensure!(
36            quality_zones.len() == quality_zones.iter().map(|qz| qz.quality).unique().count(),
37            "Quality zones must have unique qualities"
38        );
39        ensure!(
40            quality_zones
41                .iter()
42                .map(|qz| qz.quality)
43                .all(|q| q < N_QUALITIES),
44            "All quality zones must be below N_QUALITIES: {N_QUALITIES}"
45        );
46        let quality_zones = {
47            let mut qz = <[_; N_QUALITIES]>::default();
48            for q in quality_zones {
49                let quality = q.quality;
50                qz[quality] = Some(q);
51            }
52            qz
53        };
54
55        let base_cde = {
56            let mut hazards = vec![Hazard::new(BasicHazardEntity::Exterior, outer.clone())];
57            let qz_hazards = quality_zones
58                .iter()
59                .flatten()
60                .flat_map(InferiorQualityZone::to_hazards);
61            hazards.extend(qz_hazards);
62            let base_cde = CDEngine::new(outer.bbox.inflate_to_square(), hazards, cde_config);
63            Arc::new(base_cde)
64        };
65
66        Ok(Self {
67            outer_cd: outer,
68            outer_orig,
69            quality_zones,
70            base_cde,
71        })
72    }
73
74    /// Gross area of the original outer contour, without subtracting holes or quality zones.
75    #[must_use]
76    pub fn area(&self) -> f32 {
77        self.outer_orig.area()
78    }
79
80    /// Original contour of the container as defined in the input
81    #[must_use]
82    pub fn outer_orig(&self) -> &Arc<OriginalShape> {
83        &self.outer_orig
84    }
85
86    /// Contour of the container to be used for collision detection
87    #[must_use]
88    pub fn outer_cd(&self) -> &Arc<SPolygon> {
89        &self.outer_cd
90    }
91
92    /// Zones of different qualities in the container, stored per quality.
93    #[must_use]
94    pub fn quality_zones(&self) -> &[Option<InferiorQualityZone>; N_QUALITIES] {
95        &self.quality_zones
96    }
97
98    /// The initial state of the `CDEngine` for this container. (equivalent to an empty layout using this container)
99    #[must_use]
100    pub fn base_cde(&self) -> &Arc<CDEngine> {
101        &self.base_cde
102    }
103}
104
105/// Maximum number of qualities that can be used for quality zones in a container.
106pub const N_QUALITIES: usize = 10;
107
108/// Represents a zone of inferior quality in the [`Container`]
109#[derive(Clone, Debug)]
110pub struct InferiorQualityZone {
111    /// Quality of this zone. Higher qualities are superior. A zone with quality 0 is treated as a hole.
112    pub(crate) quality: usize,
113    /// Contours of this quality-zone as defined in the input file
114    pub(crate) shapes_orig: Vec<Arc<OriginalShape>>,
115    /// Contours of this quality-zone to be used for collision detection
116    pub(crate) shapes_cd: Vec<Arc<SPolygon>>,
117}
118
119impl InferiorQualityZone {
120    /// Converts the shapes for collision detection and generates their surrogates,
121    /// so they can be quantified as collision targets.
122    /// Returns an error for invalid geometry or a quality outside `0..N_QUALITIES`.
123    pub fn new(
124        quality: usize,
125        original_shapes: Vec<OriginalShape>,
126        surrogate_config: SPSurrogateConfig,
127    ) -> Result<Self> {
128        ensure!(
129            quality < N_QUALITIES,
130            "Quality must be in range of N_QUALITIES"
131        );
132        let shapes: Result<Vec<Arc<SPolygon>>> = original_shapes
133            .iter()
134            .map(|orig| {
135                let mut shape = orig.convert_to_internal()?;
136                shape.generate_surrogate(surrogate_config)?;
137                Ok(Arc::new(shape))
138            })
139            .collect();
140
141        let original_shapes = original_shapes.into_iter().map(Arc::new).collect_vec();
142
143        Ok(Self {
144            quality,
145            shapes_cd: shapes?,
146            shapes_orig: original_shapes,
147        })
148    }
149
150    /// Returns a zone with only the shapes whose collision contour satisfies `f`, in their original order.
151    /// Kept shapes are shared, not converted again. Hazard indices refer to the new zone's order.
152    #[must_use]
153    pub fn filtered(&self, mut f: impl FnMut(&SPolygon) -> bool) -> Self {
154        let (shapes_orig, shapes_cd) = self
155            .shapes_orig
156            .iter()
157            .zip(&self.shapes_cd)
158            .filter(|(_, shape)| f(shape))
159            .map(|(orig, cd)| (orig.clone(), cd.clone()))
160            .unzip();
161        Self {
162            quality: self.quality,
163            shapes_orig,
164            shapes_cd,
165        }
166    }
167
168    /// Returns the set of hazards induced by this zone.
169    pub fn to_hazards(&self) -> impl Iterator<Item = Hazard> {
170        self.shapes_cd.iter().enumerate().map(|(idx, shape)| {
171            let entity = match self.quality {
172                0 => BasicHazardEntity::Hole { idx },
173                _ => BasicHazardEntity::InferiorQualityZone {
174                    quality: self.quality,
175                    idx,
176                },
177            };
178            Hazard::new(entity, shape.clone())
179        })
180    }
181
182    #[must_use]
183    pub fn area(&self) -> f32 {
184        self.shapes_orig.iter().map(|shape| shape.area()).sum()
185    }
186
187    /// Quality of this zone. Higher qualities are superior. A zone with quality 0 is treated as a hole.
188    #[must_use]
189    pub fn quality(&self) -> usize {
190        self.quality
191    }
192
193    /// Contours of this quality-zone as defined in the input file
194    #[must_use]
195    pub fn shapes_orig(&self) -> &[Arc<OriginalShape>] {
196        &self.shapes_orig
197    }
198
199    /// Contours of this quality-zone to be used for collision detection
200    #[must_use]
201    pub fn shapes_cd(&self) -> &[Arc<SPolygon>] {
202        &self.shapes_cd
203    }
204}