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