1use crate::{
4 euclid::{Box2D, Point2D, Vector2D},
5 legacy::{
6 geometry::{anchor::Anchor, feature_index::IndexedSubfeature},
7 geometry_tile_data::{GeometryCoordinate, GeometryCoordinates},
8 glyph::Shaping,
9 grid_index::Circle,
10 shaping::{Padding, PositionedIcon},
11 style_types::SymbolPlacementType,
12 util::math::{convert_point_f64, convert_point_i16, deg2radf, rotate, MinMax},
13 ScreenSpace, TileSpace,
14 },
15};
16
17#[derive(Clone)]
19pub struct CollisionFeature {
20 pub boxes: Vec<CollisionBox>,
21 pub indexed_feature: IndexedSubfeature,
22 pub along_line: bool,
23}
24
25impl CollisionFeature {
26 pub fn new(
28 line: &GeometryCoordinates,
29 anchor: &Anchor,
30 top: f64,
31 bottom: f64,
32 left: f64,
33 right: f64,
34 collision_padding: Option<Padding>,
35 box_scale: f64,
36 padding: f64,
37 placement: SymbolPlacementType,
38 indexed_feature: IndexedSubfeature,
39 overscaling: f64,
40 rotate_: f64,
41 ) -> Self {
42 let mut self_ = Self {
43 boxes: vec![],
44 indexed_feature,
45 along_line: placement != SymbolPlacementType::Point,
46 };
47
48 if top == 0. && bottom == 0. && left == 0. && right == 0. {
49 return self_;
50 }
51
52 let mut y1 = top * box_scale - padding;
53 let mut y2 = bottom * box_scale + padding;
54 let mut x1 = left * box_scale - padding;
55 let mut x2 = right * box_scale + padding;
56
57 if let Some(collision_padding) = collision_padding {
58 x1 -= collision_padding.left * box_scale;
59 y1 -= collision_padding.top * box_scale;
60 x2 += collision_padding.right * box_scale;
61 y2 += collision_padding.bottom * box_scale;
62 }
63
64 if self_.along_line {
65 let mut height = y2 - y1;
66 let length = x2 - x1;
67
68 if height <= 0.0 {
69 return self_;
70 }
71
72 height = 10.0 * box_scale.max(height);
73
74 let anchor_point = convert_point_i16(&anchor.point);
75 self_.bboxify_label(
76 line,
77 &anchor_point,
78 anchor.segment.unwrap_or(0),
79 length,
80 height,
81 overscaling,
82 );
83 } else if rotate_ != 0. {
84 let rotate_radians = deg2radf(rotate_);
87
88 let tl = rotate(&Vector2D::<_, TileSpace>::new(x1, y1), rotate_radians);
89 let tr = rotate(&Vector2D::<_, TileSpace>::new(x2, y1), rotate_radians);
90 let bl = rotate(&Vector2D::<_, TileSpace>::new(x1, y2), rotate_radians);
91 let br = rotate(&Vector2D::<_, TileSpace>::new(x2, y2), rotate_radians);
92
93 let x_min = [tl.x, tr.x, bl.x, br.x].min_value();
97 let x_max = [tl.x, tr.x, bl.x, br.x].max_value();
98 let y_min = [tl.y, tr.y, bl.y, br.y].min_value();
99 let y_max = [tl.y, tr.y, bl.y, br.y].max_value();
100
101 self_.boxes.push(CollisionBox {
102 anchor: anchor.point,
103 x1: x_min,
104 y1: y_min,
105 x2: x_max,
106 y2: y_max,
107 signed_distance_from_anchor: 0.0,
108 });
109 } else {
110 self_.boxes.push(CollisionBox {
111 anchor: anchor.point,
112 x1,
113 y1,
114 x2,
115 y2,
116 signed_distance_from_anchor: 0.0,
117 });
118 }
119 self_
120 }
121
122 pub fn new_from_text(
125 line: &GeometryCoordinates,
126 anchor: &Anchor,
127 shaped_text: Shaping,
128 box_scale: f64,
129 padding: f64,
130 placement: SymbolPlacementType,
131 indexed_feature: IndexedSubfeature,
132 overscaling: f64,
133 rotate: f64,
134 ) -> Self {
135 Self::new(
136 line,
137 anchor,
138 shaped_text.top,
139 shaped_text.bottom,
140 shaped_text.left,
141 shaped_text.right,
142 None,
143 box_scale,
144 padding,
145 placement,
146 indexed_feature,
147 overscaling,
148 rotate,
149 )
150 }
151
152 pub fn new_from_icon(
162 line: &GeometryCoordinates,
163 anchor: &Anchor,
164 shaped_icon: &Option<PositionedIcon>,
165 box_scale: f64,
166 padding: f64,
167 indexed_feature: IndexedSubfeature,
168 rotate: f64,
169 ) -> Self {
170 Self::new(
171 line,
172 anchor,
173 if let Some(shaped_icon) = &shaped_icon {
174 shaped_icon.top
175 } else {
176 0.
177 },
178 if let Some(shaped_icon) = &shaped_icon {
179 shaped_icon.bottom
180 } else {
181 0.
182 },
183 if let Some(shaped_icon) = &shaped_icon {
184 shaped_icon.left
185 } else {
186 0.
187 },
188 if let Some(shaped_icon) = &shaped_icon {
189 shaped_icon.right
190 } else {
191 0.
192 },
193 shaped_icon
194 .as_ref()
195 .map(|shaped_icon| shaped_icon.collision_padding),
196 box_scale,
197 padding,
198 SymbolPlacementType::Point,
199 indexed_feature,
200 1.,
201 rotate,
202 )
203 }
204
205 fn bboxify_label(
207 &mut self,
208 line: &GeometryCoordinates,
209 anchor_point: &GeometryCoordinate,
210 segment: usize,
211 label_length: f64,
212 box_size: f64,
213 overscaling: f64,
214 ) {
215 let step = box_size / 2.;
216 let n_boxes = ((label_length / step).floor() as i32).max(1);
217
218 let overscaling_padding_factor = 1. + 0.4 * overscaling.log2();
226 let n_pitch_padding_boxes =
227 ((n_boxes as f64 * overscaling_padding_factor / 2.).floor()) as i32;
228
229 let first_box_offset = -box_size / 2.;
232
233 let mut p = anchor_point;
234 let mut index = segment + 1;
235 let mut anchor_distance = first_box_offset;
236 let label_start_distance = -label_length / 2.;
237 let padding_start_distance = label_start_distance - label_length / 8.;
238
239 loop {
241 if index == 0 {
242 if anchor_distance > label_start_distance {
243 return;
246 } else {
247 index = 0;
250 break;
251 }
252 }
253
254 index -= 1;
255 anchor_distance -= convert_point_f64(&line[index]).distance_to(convert_point_f64(p));
256 p = &line[index];
257
258 if !(anchor_distance > padding_start_distance) {
259 break;
260 }
261 }
262
263 let mut segment_length =
264 convert_point_f64(&line[index]).distance_to(convert_point_f64(&line[index + 1]));
265
266 for i in -n_pitch_padding_boxes..n_boxes + n_pitch_padding_boxes {
267 let box_offset = i as f64 * step;
269 let mut box_distance_to_anchor = label_start_distance + box_offset;
270
271 if box_offset < 0. {
273 box_distance_to_anchor += box_offset;
274 }
275 if box_offset > label_length {
276 box_distance_to_anchor += box_offset - label_length;
277 }
278
279 if box_distance_to_anchor < anchor_distance {
280 continue;
283 }
284
285 while anchor_distance + segment_length < box_distance_to_anchor {
287 anchor_distance += segment_length;
288 index += 1;
289
290 if index + 1 >= line.len() {
292 return;
293 }
294
295 segment_length = convert_point_f64(&line[index])
296 .distance_to(convert_point_f64(&line[index + 1]));
297 }
298
299 let segment_box_distance = box_distance_to_anchor - anchor_distance;
301
302 let p0 = line[index];
303 let p1 = line[index + 1];
304
305 let box_anchor = Point2D::new(
306 p0.x as f64 + segment_box_distance / segment_length * (p1.x - p0.x) as f64,
307 p0.y as f64 + segment_box_distance / segment_length * (p1.y - p0.y) as f64,
308 );
309
310 let padded_anchor_distance = if (box_distance_to_anchor - first_box_offset).abs() < step
315 {
316 0.0
317 } else {
318 (box_distance_to_anchor - first_box_offset) * 0.8
319 };
320
321 self.boxes.push(CollisionBox {
322 anchor: box_anchor,
323 x1: -box_size / 2.,
324 y1: -box_size / 2.,
325 x2: box_size / 2.,
326 y2: box_size / 2.,
327 signed_distance_from_anchor: padded_anchor_distance,
328 });
329 }
330 }
331}
332
333#[derive(Default, Clone, Copy, Debug)]
335pub struct CollisionBox {
336 pub anchor: Point2D<f64, TileSpace>,
338
339 pub x1: f64,
345 pub y1: f64,
346 pub x2: f64,
347 pub y2: f64,
348
349 pub signed_distance_from_anchor: f64,
350}
351
352#[derive(Clone, Copy, Debug)]
354pub enum ProjectedCollisionBox {
355 Circle(Circle<f64>),
356 Box(Box2D<f64, ScreenSpace>),
357}
358
359impl Default for ProjectedCollisionBox {
360 fn default() -> Self {
362 Self::Box(Box2D::zero())
363 }
364}
365
366impl ProjectedCollisionBox {
367 pub fn box_(&self) -> &Box2D<f64, ScreenSpace> {
369 match self {
370 ProjectedCollisionBox::Circle(_) => panic!("not a box"),
371 ProjectedCollisionBox::Box(box_) => box_,
372 }
373 }
374
375 pub fn circle(&self) -> &Circle<f64> {
377 match self {
378 ProjectedCollisionBox::Circle(circle) => circle,
379 ProjectedCollisionBox::Box(_) => panic!("not a circle"),
380 }
381 }
382
383 pub fn is_box(&self) -> bool {
385 match self {
386 ProjectedCollisionBox::Circle(_) => false,
387 ProjectedCollisionBox::Box(_) => true,
388 }
389 }
390
391 pub fn is_circle(&self) -> bool {
393 match self {
394 ProjectedCollisionBox::Circle(_) => true,
395 ProjectedCollisionBox::Box(_) => false,
396 }
397 }
398}