jagua_rs/geometry/fail_fast/
piers.rs1use itertools::{Itertools, izip};
2use ndarray::Array;
3use ordered_float::NotNan;
4use rand_distr::num_traits::FloatConst;
5
6use crate::geometry::Transformation;
7use crate::geometry::geo_traits::{CollidesWith, DistanceTo, Transformable};
8use crate::geometry::primitives::Circle;
9use crate::geometry::primitives::Edge;
10use crate::geometry::primitives::Point;
11use crate::geometry::primitives::Rect;
12use crate::geometry::primitives::SPolygon;
13
14use anyhow::{Result, bail};
15
16static RAYS_PER_ANGLE: usize = if cfg!(debug_assertions) { 10 } else { 200 };
17static N_ANGLES: usize = if cfg!(debug_assertions) { 4 } else { 90 };
18static N_POINTS_PER_DIMENSION: usize = if cfg!(debug_assertions) { 10 } else { 100 };
19static CLIPPING_TRIM: f32 = 0.999;
20static ACTION_RADIUS_RATIO: f32 = 0.10;
21
22pub fn generate_piers(shape: &SPolygon, n: usize, poles: &[Circle]) -> Result<Vec<Edge>> {
26 if n == 0 {
27 return Ok(vec![]);
28 }
29
30 let bbox = shape.bbox;
32 let expanded_bbox = bbox.clone().inflate_to_square();
33 let centroid = shape.centroid();
34 let base_ray = Edge::try_new(
36 Point(centroid.0, centroid.1 - 2.0 * expanded_bbox.height()),
37 Point(centroid.0, centroid.1 + 2.0 * expanded_bbox.height()),
38 )
39 .unwrap();
40
41 let transformations = generate_ray_transformations(expanded_bbox, RAYS_PER_ANGLE, N_ANGLES);
42
43 let rays = transformations
45 .into_iter()
46 .map(|t| base_ray.transform_clone(&t))
47 .collect_vec();
48
49 let clipped_rays = rays.iter().flat_map(|l| clip(shape, l)).collect_vec();
51 let grid_of_unrepresented_points =
52 generate_unrepresented_point_grid(expanded_bbox, shape, poles, N_POINTS_PER_DIMENSION);
53
54 let mut selected_piers = Vec::new();
55
56 let radius_of_ray_influence = ACTION_RADIUS_RATIO * expanded_bbox.width();
57 let forfeit_distance = f32::sqrt(bbox.width().powi(2) * bbox.height().powi(2));
58
59 for _ in 0..n {
60 let min_distance_selected_rays = min_distances_to_rays(
61 &grid_of_unrepresented_points,
62 &selected_piers,
63 forfeit_distance,
64 );
65 let min_distance_poles =
66 min_distances_to_poles(&grid_of_unrepresented_points, poles, forfeit_distance);
67
68 let loss_values = clipped_rays
69 .iter()
70 .map(|new_ray| {
71 loss_function(
72 new_ray,
73 &grid_of_unrepresented_points,
74 &min_distance_selected_rays,
75 &min_distance_poles,
76 radius_of_ray_influence,
77 )
78 })
79 .map(|x| NotNan::new(x).unwrap())
80 .collect_vec();
81
82 let min_loss_ray = clipped_rays
83 .iter()
84 .enumerate()
85 .min_by_key(|(i, _)| loss_values[*i])
86 .map(|(_i, ray)| ray);
87
88 match min_loss_ray {
89 None => bail!("no ray found"),
90 Some(ray) => selected_piers.push(*ray),
91 }
92 }
93 Ok(selected_piers)
94}
95
96#[allow(clippy::cast_precision_loss)]
97fn generate_ray_transformations(
98 bbox: Rect,
99 rays_per_angle: usize,
100 n_angles: usize,
101) -> Vec<Transformation> {
102 let dx = bbox.width() / rays_per_angle as f32;
104 let translations = (0..rays_per_angle)
105 .map(|i| bbox.x_min + dx * i as f32)
106 .map(|x| Transformation::from_translation((x, 0.0)))
107 .collect_vec();
108
109 let angles = Array::linspace(0.0, f32::PI(), n_angles + 1).to_vec();
110 let angles_slice = &angles[0..n_angles]; angles_slice
114 .iter()
115 .flat_map(|angle| {
116 translations
117 .iter()
118 .cloned()
119 .map(move |translation| translation.rotate(*angle))
120 })
121 .collect_vec()
122}
123
124fn clip(shape: &SPolygon, ray: &Edge) -> Vec<Edge> {
126 assert!(!shape.collides_with(&ray.start) && !shape.collides_with(&ray.end));
128
129 let intersections = shape
131 .edge_iter()
132 .filter_map(|edge| edge.collides_at(ray))
133 .sorted_by_key(|p| NotNan::new(ray.start.distance_to(p)).unwrap())
134 .collect_vec();
135
136 intersections
138 .chunks(2)
139 .filter_map(|pair| {
140 if pair.len() == 1 {
141 return None;
142 }
143 let start = pair[0];
144 let end = pair[1];
145 if start == end {
146 None
147 } else {
148 Some(Edge::try_new(start, end).unwrap().scale(CLIPPING_TRIM))
149 }
150 })
151 .collect_vec()
152}
153
154fn generate_unrepresented_point_grid(
155 bbox: Rect,
156 shape: &SPolygon,
157 poles: &[Circle],
158 n_points_per_dimension: usize,
159) -> Vec<Point> {
160 let x_range = Array::linspace(bbox.x_min, bbox.x_max, n_points_per_dimension);
161 let y_range = Array::linspace(bbox.y_min, bbox.y_max, n_points_per_dimension);
162
163 x_range
164 .iter()
165 .flat_map(|x| {
166 y_range
167 .iter()
168 .map(move |y| Point::from((*x, *y))) .filter(|p| shape.collides_with(p)) .filter(|p| poles.iter().all(|c| !c.collides_with(p)))
171 })
172 .collect_vec()
173}
174
175fn loss_function(
176 new_ray: &Edge,
177 point_grid: &[Point],
178 min_distance_to_rays: &[f32],
179 min_distance_to_poles: &[f32],
180 radius_of_ray_influence: f32,
181) -> f32 {
182 izip!(
188 point_grid.iter(),
189 min_distance_to_rays.iter(),
190 min_distance_to_poles.iter()
191 )
192 .map(|(p, min_distance_to_existing_ray, min_distance_to_pole)| {
193 let distance_to_new_ray = new_ray.distance_to(p);
194
195 let min_distance_to_ray = f32::min(*min_distance_to_existing_ray, distance_to_new_ray);
196
197 if min_distance_to_ray < radius_of_ray_influence {
198 f32::min(*min_distance_to_pole, min_distance_to_ray)
199 } else {
200 *min_distance_to_pole
201 }
202 })
203 .map(|d| d.powi(2))
204 .sum()
205}
206
207fn min_distances_to_rays(points: &[Point], rays: &[Edge], forfeit_distance: f32) -> Vec<f32> {
208 points
209 .iter()
210 .map(|p| {
211 rays.iter()
212 .map(|r| r.distance_to(p))
213 .fold(forfeit_distance, f32::min)
214 })
215 .collect_vec()
216}
217
218fn min_distances_to_poles(points: &[Point], poles: &[Circle], forfeit_distance: f32) -> Vec<f32> {
219 points
220 .iter()
221 .map(|p| {
222 poles
223 .iter()
224 .map(|c| c.distance_to(p))
225 .fold(forfeit_distance, f32::min)
226 })
227 .collect_vec()
228}