1use crate::collision_detection::CDEConfig;
2use crate::entities::Item;
3use crate::entities::{Container, InferiorQualityZone, N_QUALITIES};
4use crate::geometry::OriginalShape;
5use crate::geometry::geo_enums::RotationRange;
6use crate::geometry::primitives::{Point, Rect, SPolygon};
7use crate::geometry::shape_modification::{ShapeModifyConfig, ShapeModifyMode};
8use crate::geometry::{DTransformation, Transformation};
9use crate::io::ext_repr::{ExtContainer, ExtItem, ExtSPolygon, ExtShape};
10use anyhow::{Result, bail};
11use float_cmp::approx_eq;
12use itertools::Itertools;
13use log::{debug, warn};
14
15#[derive(Clone, Debug, Copy)]
17pub struct Importer {
18 pub shape_modify_config: ShapeModifyConfig,
19 pub cde_config: CDEConfig,
20}
21
22impl Importer {
23 #[must_use]
30 pub fn new(
31 cde_config: CDEConfig,
32 simplify_tolerance: Option<f32>,
33 min_item_separation: Option<f32>,
34 narrow_concavity_cutoff: Option<(f32, f32)>,
35 ) -> Importer {
36 Importer {
37 shape_modify_config: ShapeModifyConfig {
38 offset: min_item_separation.map(|f| f / 2.0),
39 simplify_tolerance,
40 narrow_concavity_cutoff,
41 },
42 cde_config,
43 }
44 }
45
46 pub fn import_item(&self, ext_item: &ExtItem) -> Result<Item> {
47 debug!("[IMPORT] starting item {:?}", ext_item.id);
48
49 let original_shape = {
50 let shape = match &ext_item.shape {
51 ExtShape::Rectangle {
52 x_min,
53 y_min,
54 width,
55 height,
56 } => {
57 let rect = Rect::try_new(*x_min, *y_min, x_min + width, y_min + height)?;
58 SPolygon::from(rect)
59 }
60 ExtShape::SimplePolygon(esp) => import_simple_polygon(esp)?,
61 ExtShape::Polygon(ep) => {
62 warn!("No native support for polygons yet, ignoring the holes");
63 import_simple_polygon(&ep.outer)?
64 }
65 ExtShape::MultiPolygon(_) => {
66 bail!("No support for multipolygons yet")
67 }
68 };
69 OriginalShape {
70 pre_transform: centering_transformation(&shape),
71 shape,
72 modify_mode: ShapeModifyMode::Inflate,
73 modify_config: self.shape_modify_config,
74 }
75 };
76
77 let base_quality = ext_item.min_quality;
78
79 let allowed_orientations = match ext_item.allowed_orientations.as_ref() {
80 Some(a_o) => {
81 if a_o.is_empty() || (a_o.len() == 1 && a_o[0] == 0.0) {
82 RotationRange::None
83 } else {
84 RotationRange::Discrete(a_o.iter().map(|angle| angle.to_radians()).collect())
85 }
86 }
87 None => RotationRange::Continuous,
88 };
89
90 Item::new(
91 usize::try_from(ext_item.id).unwrap(),
92 original_shape,
93 allowed_orientations,
94 base_quality,
95 self.cde_config.item_surrogate_config,
96 )
97 }
98
99 pub fn import_container(&self, ext_cont: &ExtContainer) -> Result<Container> {
100 assert!(
101 ext_cont.zones.iter().all(|zone| zone.quality < N_QUALITIES),
102 "All quality zones must have lower quality than N_QUALITIES, set N_QUALITIES to a higher value if required"
103 );
104
105 let original_outer = {
106 let outer = match &ext_cont.shape {
107 ExtShape::Rectangle {
108 x_min,
109 y_min,
110 width,
111 height,
112 } => Rect::try_new(*x_min, *y_min, x_min + width, y_min + height)?.into(),
113 ExtShape::SimplePolygon(esp) => import_simple_polygon(esp)?,
114 ExtShape::Polygon(ep) => import_simple_polygon(&ep.outer)?,
115 ExtShape::MultiPolygon(_) => {
116 bail!("No support for multipolygon shapes yet")
117 }
118 };
119 OriginalShape {
120 shape: outer,
121 pre_transform: DTransformation::empty(),
122 modify_mode: ShapeModifyMode::Deflate,
123 modify_config: self.shape_modify_config,
124 }
125 };
126
127 let holes = match &ext_cont.shape {
128 ExtShape::SimplePolygon(_) | ExtShape::Rectangle { .. } => vec![],
129 ExtShape::Polygon(jp) => {
130 let json_holes = &jp.inner;
131 json_holes
132 .iter()
133 .map(import_simple_polygon)
134 .collect::<Result<Vec<SPolygon>>>()?
135 }
136 ExtShape::MultiPolygon(_) => {
137 unimplemented!("No support for multipolygon shapes yet")
138 }
139 };
140
141 let mut shapes_inferior_qzones = (0..N_QUALITIES)
142 .map(|q| {
143 ext_cont
144 .zones
145 .iter()
146 .filter(|zone| zone.quality == q)
147 .map(|zone| match &zone.shape {
148 ExtShape::Rectangle {
149 x_min,
150 y_min,
151 width,
152 height,
153 } => Rect::try_new(*x_min, *y_min, x_min + width, y_min + height)
154 .map(Into::into),
155 ExtShape::SimplePolygon(esp) => import_simple_polygon(esp),
156 ExtShape::Polygon(_) => {
157 unimplemented!("No support for polygon to simplepolygon conversion yet")
158 }
159 ExtShape::MultiPolygon(_) => {
160 unimplemented!("No support for multipolygon shapes yet")
161 }
162 })
163 .collect::<Result<Vec<SPolygon>>>()
164 })
165 .collect::<Result<Vec<Vec<SPolygon>>>>()?;
166
167 shapes_inferior_qzones[0].extend(holes);
169
170 let quality_zones = shapes_inferior_qzones
172 .into_iter()
173 .enumerate()
174 .map(|(q, zone_shapes)| {
175 let original_shapes = zone_shapes
176 .into_iter()
177 .map(|s| OriginalShape {
178 shape: s,
179 pre_transform: DTransformation::empty(),
180 modify_mode: ShapeModifyMode::Inflate,
181 modify_config: self.shape_modify_config,
182 })
183 .collect_vec();
184 InferiorQualityZone::new(q, original_shapes)
185 })
186 .collect::<Result<Vec<InferiorQualityZone>>>()?;
187
188 Container::new(
189 usize::try_from(ext_cont.id).unwrap(),
190 original_outer,
191 quality_zones,
192 self.cde_config,
193 )
194 }
195}
196
197pub fn import_simple_polygon(sp: &ExtSPolygon) -> Result<SPolygon> {
198 let mut points = sp.0.iter().map(|(x, y)| Point(*x, *y)).collect_vec();
199 if points.len() > 1 && points[0] == points[points.len() - 1] {
201 points.pop();
202 }
203 eliminate_degenerate_vertices(&mut points);
205 if points.len() != points.iter().unique().count() {
207 bail!("Simple polygon has non-consecutive duplicate vertices");
208 }
209 SPolygon::new(points)
210}
211
212#[must_use]
214pub fn centering_transformation(shape: &SPolygon) -> DTransformation {
215 let Point(cx, cy) = shape.centroid();
216 DTransformation::new(0.0, (-cx, -cy))
217}
218
219#[must_use]
224pub fn ext_to_int_transformation(
225 ext_transf: &DTransformation,
226 pre_transf: &DTransformation,
227) -> DTransformation {
228 Transformation::empty()
232 .transform(&pre_transf.compose().inverse())
233 .transform_from_decomposed(ext_transf)
234 .decompose()
235}
236
237pub fn eliminate_degenerate_vertices(points: &mut Vec<Point>) {
238 let mut indices_to_remove = vec![];
239 let n_points = points.len();
240 for i in 0..n_points {
241 let j = (i + 1) % n_points;
242 let p_i = points[i];
243 let p_j = points[j];
244 if approx_eq!(f32, p_i.0, p_j.0) && approx_eq!(f32, p_i.1, p_j.1) {
245 indices_to_remove.push(i);
247 }
248 }
249 indices_to_remove.sort_unstable_by(|a, b| b.cmp(a));
251 for index in indices_to_remove {
252 if index < points.len() {
253 let j = (index + 1) % points.len();
254 debug!(
255 "[IMPORT] degenerate vertex eliminated (idx: {}, {:?}, {:?})",
256 index, points[index], points[j]
257 );
258 points.remove(index);
259 }
260 }
261}