jagua_rs/io/
import.rs

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;
7use crate::geometry::primitives::Rect;
8use crate::geometry::primitives::SPolygon;
9use crate::geometry::shape_modification::{ShapeModifyConfig, ShapeModifyMode};
10use crate::geometry::{DTransformation, Transformation};
11use crate::io::ext_repr::{ExtContainer, ExtItem, ExtSPolygon, ExtShape};
12use anyhow::{Result, bail};
13use itertools::Itertools;
14use log::warn;
15
16/// Converts external representations of items and containers into internal ones.
17#[derive(Clone, Debug, Copy)]
18pub struct Importer {
19    pub shape_modify_config: ShapeModifyConfig,
20    pub cde_config: CDEConfig,
21}
22
23impl Importer {
24    /// Creates a new instance with the given configuration.
25    ///
26    /// * `cde_config` - Configuration for the CDE (Collision Detection Engine).
27    /// * `poly_simpl_tolerance` - See [`ShapeModifyConfig::simplify_tolerance`].
28    /// * `min_item_separation` - Optional minimum separation distance between items and any other hazard. If enabled, every hazard is inflated/deflated by half this value. See [`ShapeModifyConfig::offset`].
29    pub fn new(
30        cde_config: CDEConfig,
31        poly_simpl_tolerance: Option<f32>,
32        min_item_separation: Option<f32>,
33    ) -> Importer {
34        Importer {
35            shape_modify_config: ShapeModifyConfig {
36                offset: min_item_separation.map(|f| f / 2.0),
37                simplify_tolerance: poly_simpl_tolerance,
38            },
39            cde_config,
40        }
41    }
42
43    pub fn import_item(&self, ext_item: &ExtItem) -> Result<Item> {
44        let original_shape = {
45            let shape = match &ext_item.shape {
46                ExtShape::Rectangle {
47                    x_min,
48                    y_min,
49                    width,
50                    height,
51                } => {
52                    let rect = Rect::try_new(*x_min, *y_min, x_min + width, y_min + height)?;
53                    SPolygon::from(rect)
54                }
55                ExtShape::SimplePolygon(esp) => import_simple_polygon(esp)?,
56                ExtShape::Polygon(ep) => {
57                    warn!("No native support for polygons yet, ignoring the holes");
58                    import_simple_polygon(&ep.outer)?
59                }
60                ExtShape::MultiPolygon(_) => {
61                    bail!("No support for multipolygons yet")
62                }
63            };
64            OriginalShape {
65                pre_transform: centering_transformation(&shape),
66                shape,
67                modify_mode: ShapeModifyMode::Inflate,
68                modify_config: self.shape_modify_config,
69            }
70        };
71
72        let base_quality = ext_item.min_quality;
73
74        let allowed_orientations = match ext_item.allowed_orientations.as_ref() {
75            Some(a_o) => {
76                if a_o.is_empty() || (a_o.len() == 1 && a_o[0] == 0.0) {
77                    RotationRange::None
78                } else {
79                    RotationRange::Discrete(a_o.iter().map(|angle| angle.to_radians()).collect())
80                }
81            }
82            None => RotationRange::Continuous,
83        };
84
85        Item::new(
86            ext_item.id as usize,
87            original_shape,
88            allowed_orientations,
89            base_quality,
90            self.cde_config.item_surrogate_config,
91        )
92    }
93
94    pub fn import_container(&self, ext_cont: &ExtContainer) -> Result<Container> {
95        assert!(
96            ext_cont.zones.iter().all(|zone| zone.quality < N_QUALITIES),
97            "All quality zones must have lower quality than N_QUALITIES, set N_QUALITIES to a higher value if required"
98        );
99
100        let original_outer = {
101            let outer = match &ext_cont.shape {
102                ExtShape::Rectangle {
103                    x_min,
104                    y_min,
105                    width,
106                    height,
107                } => Rect::try_new(*x_min, *y_min, x_min + width, y_min + height)?.into(),
108                ExtShape::SimplePolygon(esp) => import_simple_polygon(esp)?,
109                ExtShape::Polygon(ep) => import_simple_polygon(&ep.outer)?,
110                ExtShape::MultiPolygon(_) => {
111                    bail!("No support for multipolygon shapes yet")
112                }
113            };
114            OriginalShape {
115                shape: outer,
116                pre_transform: DTransformation::empty(),
117                modify_mode: ShapeModifyMode::Deflate,
118                modify_config: self.shape_modify_config,
119            }
120        };
121
122        let holes = match &ext_cont.shape {
123            ExtShape::SimplePolygon(_) | ExtShape::Rectangle { .. } => vec![],
124            ExtShape::Polygon(jp) => {
125                let json_holes = &jp.inner;
126                json_holes
127                    .iter()
128                    .map(import_simple_polygon)
129                    .collect::<Result<Vec<SPolygon>>>()?
130            }
131            ExtShape::MultiPolygon(_) => {
132                unimplemented!("No support for multipolygon shapes yet")
133            }
134        };
135
136        let mut shapes_inferior_qzones = (0..N_QUALITIES)
137            .map(|q| {
138                ext_cont
139                    .zones
140                    .iter()
141                    .filter(|zone| zone.quality == q)
142                    .map(|zone| match &zone.shape {
143                        ExtShape::Rectangle {
144                            x_min,
145                            y_min,
146                            width,
147                            height,
148                        } => Rect::try_new(*x_min, *y_min, x_min + width, y_min + height)
149                            .map(|r| r.into()),
150                        ExtShape::SimplePolygon(esp) => import_simple_polygon(esp),
151                        ExtShape::Polygon(_) => {
152                            unimplemented!("No support for polygon to simplepolygon conversion yet")
153                        }
154                        ExtShape::MultiPolygon(_) => {
155                            unimplemented!("No support for multipolygon shapes yet")
156                        }
157                    })
158                    .collect::<Result<Vec<SPolygon>>>()
159            })
160            .collect::<Result<Vec<Vec<SPolygon>>>>()?;
161
162        //merge the container holes with quality == 0
163        shapes_inferior_qzones[0].extend(holes);
164
165        //convert the shapes to inferior quality zones
166        let quality_zones = shapes_inferior_qzones
167            .into_iter()
168            .enumerate()
169            .map(|(q, zone_shapes)| {
170                let original_shapes = zone_shapes
171                    .into_iter()
172                    .map(|s| OriginalShape {
173                        shape: s,
174                        pre_transform: DTransformation::empty(),
175                        modify_mode: ShapeModifyMode::Inflate,
176                        modify_config: self.shape_modify_config,
177                    })
178                    .collect_vec();
179                InferiorQualityZone::new(q, original_shapes)
180            })
181            .collect::<Result<Vec<InferiorQualityZone>>>()?;
182
183        Container::new(
184            ext_cont.id as usize,
185            original_outer,
186            quality_zones,
187            self.cde_config,
188        )
189    }
190}
191
192pub fn import_simple_polygon(sp: &ExtSPolygon) -> Result<SPolygon> {
193    let mut points = sp.0.iter().map(|(x, y)| Point(*x, *y)).collect_vec();
194    //Strip the last vertex if it is the same as the first one
195    if points.len() > 1 && points[0] == points[points.len() - 1] {
196        points.pop();
197    }
198    //Remove duplicates that are consecutive (e.g. [1, 2, 2, 3] -> [1, 2, 3])
199    points.dedup();
200    //Bail if there are any non-consecutive duplicates.
201    if points.len() != points.iter().unique().count() {
202        bail!("Simple polygon has non-consecutive duplicate vertices");
203    }
204    SPolygon::new(points)
205}
206
207/// Returns a transformation that translates the shape's centroid to the origin.
208pub fn centering_transformation(shape: &SPolygon) -> DTransformation {
209    let Point(cx, cy) = shape.centroid();
210    DTransformation::new(0.0, (-cx, -cy))
211}
212
213/// Converts an external transformation (applicable to the original shapes) to an internal transformation (used within `jagua-rs`).
214///
215/// * `ext_transf` - The external transformation.
216/// * `pre_transf` - The transformation that was applied to the original shape to derive the internal representation.
217pub fn ext_to_int_transformation(
218    ext_transf: &DTransformation,
219    pre_transf: &DTransformation,
220) -> DTransformation {
221    //1. undo pre-transform
222    //2. do the absolute transformation
223
224    Transformation::empty()
225        .transform(&pre_transf.compose().inverse())
226        .transform_from_decomposed(ext_transf)
227        .decompose()
228}