jagua_rs/entities/
container.rs

1use std::sync::Arc;
2
3use itertools::Itertools;
4
5use crate::collision_detection::hazards::Hazard;
6use crate::collision_detection::hazards::HazardEntity;
7use crate::collision_detection::{CDEConfig, CDEngine};
8use crate::geometry::OriginalShape;
9use crate::geometry::primitives::SPolygon;
10
11use anyhow::{Result, ensure};
12
13/// A container in which [`Item`](crate::entities::Item)'s can be placed.
14#[derive(Clone, Debug)]
15pub struct Container {
16    /// Unique identifier of the container
17    pub id: usize,
18    /// Original contour of the container as defined in the input
19    pub outer_orig: Arc<OriginalShape>,
20    /// Contour of the container to be used for collision detection
21    pub outer_cd: Arc<SPolygon>,
22    /// Zones of different qualities in the container, stored per quality.
23    pub quality_zones: [Option<InferiorQualityZone>; N_QUALITIES],
24    /// The initial state of the `CDEngine` for this container. (equivalent to an empty layout using this container)
25    pub base_cde: Arc<CDEngine>,
26}
27
28impl Container {
29    pub fn new(
30        id: usize,
31        original_outer: OriginalShape,
32        quality_zones: Vec<InferiorQualityZone>,
33        cde_config: CDEConfig,
34    ) -> Result<Self> {
35        let outer = Arc::new(original_outer.convert_to_internal()?);
36        let outer_orig = Arc::new(original_outer);
37        ensure!(
38            quality_zones.len() == quality_zones.iter().map(|qz| qz.quality).unique().count(),
39            "Quality zones must have unique qualities"
40        );
41        ensure!(
42            quality_zones
43                .iter()
44                .map(|qz| qz.quality)
45                .all(|q| q < N_QUALITIES),
46            "All quality zones must be below N_QUALITIES: {N_QUALITIES}"
47        );
48        let quality_zones = {
49            let mut qz = <[_; N_QUALITIES]>::default();
50            for q in quality_zones {
51                let quality = q.quality;
52                qz[quality] = Some(q);
53            }
54            qz
55        };
56
57        let base_cde = {
58            let mut hazards = vec![Hazard::new(HazardEntity::Exterior, outer.clone(), false)];
59            let qz_hazards = quality_zones
60                .iter()
61                .flatten()
62                .flat_map(InferiorQualityZone::to_hazards);
63            hazards.extend(qz_hazards);
64            let base_cde = CDEngine::new(outer.bbox.inflate_to_square(), hazards, cde_config);
65            Arc::new(base_cde)
66        };
67
68        Ok(Self {
69            id,
70            outer_cd: outer,
71            outer_orig,
72            quality_zones,
73            base_cde,
74        })
75    }
76
77    /// The area of the contour of the container, excluding holes
78    pub fn area(&self) -> f32 {
79        self.outer_orig.area()
80            - self.quality_zones[0]
81                .as_ref()
82                .map_or(0.0, InferiorQualityZone::area)
83    }
84}
85
86/// Maximum number of qualities that can be used for quality zones in a container.
87pub const N_QUALITIES: usize = 10;
88
89/// Represents a zone of inferior quality in the [`Container`]
90#[derive(Clone, Debug)]
91pub struct InferiorQualityZone {
92    /// Quality of this zone. Higher qualities are superior. A zone with quality 0 is treated as a hole.
93    pub quality: usize,
94    /// Contours of this quality-zone as defined in the input file
95    pub shapes_orig: Vec<Arc<OriginalShape>>,
96    /// Contours of this quality-zone to be used for collision detection
97    pub shapes_cd: Vec<Arc<SPolygon>>,
98}
99
100impl InferiorQualityZone {
101    pub fn new(quality: usize, original_shapes: Vec<OriginalShape>) -> Result<Self> {
102        assert!(
103            quality < N_QUALITIES,
104            "Quality must be in range of N_QUALITIES"
105        );
106        let shapes: Result<Vec<Arc<SPolygon>>> = original_shapes
107            .iter()
108            .map(|orig| orig.convert_to_internal().map(Arc::new))
109            .collect();
110
111        let original_shapes = original_shapes.into_iter().map(Arc::new).collect_vec();
112
113        Ok(Self {
114            quality,
115            shapes_cd: shapes?,
116            shapes_orig: original_shapes,
117        })
118    }
119
120    /// Returns the set of hazards induced by this zone.
121    pub fn to_hazards(&self) -> impl Iterator<Item = Hazard> {
122        self.shapes_cd.iter().enumerate().map(|(idx, shape)| {
123            let entity = match self.quality {
124                0 => HazardEntity::Hole { idx },
125                _ => HazardEntity::InferiorQualityZone {
126                    quality: self.quality,
127                    idx,
128                },
129            };
130            Hazard::new(entity, shape.clone(), false)
131        })
132    }
133
134    #[must_use]
135    pub fn area(&self) -> f32 {
136        self.shapes_orig.iter().map(|shape| shape.area()).sum()
137    }
138}