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