1use robust::{Coord, orient2d};
4use rstar::{
5 AABB, RTree,
6 primitives::{GeomWithData, Line, Rectangle},
7};
8use wowlab_engine_ports::{
9 ConeQuery, RadiusQuery, SpatialActor, SpatialEnvelope, SpatialQuery, SpatialQueryError,
10};
11use wowlab_types::sim::{
12 EnemyIdx, GEOMETRY_EPSILON, Position2, SpatialLayerId, SpatialTransform, StaticObstacle,
13 StaticSpatialScene,
14};
15
16use crate::{SpatialQueryDiagnostics, SpatialQueryObservation};
17
18const POINT_DIMENSIONS: usize = 2;
19const EDGE_POINT_COUNT: usize = 2;
20const QUADRANT_COUNT: i32 = 4;
21
22type ActorEntry = GeomWithData<[f64; POINT_DIMENSIONS], EnemyIdx>;
23type SegmentEntry = GeomWithData<Line<[f64; POINT_DIMENSIONS]>, usize>;
24type PolygonEntry = GeomWithData<Rectangle<[f64; POINT_DIMENSIONS]>, usize>;
25
26#[derive(Clone, Debug)]
27struct PolygonBlocker {
28 exterior: Vec<Position2>,
29 holes: Vec<Box<[Position2]>>,
30}
31
32impl PolygonBlocker {
33 fn from_rings(exterior: &[Position2], holes: &[Vec<Position2>]) -> Self {
34 Self {
35 exterior: exterior.to_vec(),
36 holes: holes
37 .iter()
38 .map(|ring| ring.clone().into_boxed_slice())
39 .collect(),
40 }
41 }
42}
43
44#[derive(Debug)]
45struct LayerIndex {
46 actors: RTree<ActorEntry>,
47 obstacles: RTree<SegmentEntry>,
48 segments: Vec<(Position2, Position2)>,
49 polygon_envelopes: RTree<PolygonEntry>,
50 polygons: Vec<PolygonBlocker>,
51}
52
53impl LayerIndex {
54 fn new() -> Self {
55 Self {
56 actors: RTree::new(),
57 obstacles: RTree::new(),
58 segments: Vec::new(),
59 polygon_envelopes: RTree::new(),
60 polygons: Vec::new(),
61 }
62 }
63}
64
65#[derive(Debug)]
66pub(crate) struct RstarSpatialQuery {
67 layers: Vec<LayerIndex>,
68 indexed_transforms: Vec<Option<SpatialTransform>>,
69}
70
71impl RstarSpatialQuery {
72 pub(crate) fn build(
73 scene: &StaticSpatialScene,
74 actors: &[SpatialActor],
75 ) -> Result<Self, SpatialQueryError> {
76 let mut layers: Vec<_> = scene.layers.iter().map(|_| LayerIndex::new()).collect();
77
78 for obstacle in &scene.obstacles {
79 match obstacle {
80 StaticObstacle::Segment { layer, start, end } => {
81 push_segment(layer_index_mut(&mut layers, *layer)?, *start, *end);
82 }
83 StaticObstacle::Polygon {
84 layer,
85 exterior,
86 holes,
87 } => {
88 let index = layer_index_mut(&mut layers, *layer)?;
89
90 push_ring_segments(index, exterior);
91
92 for hole in holes {
93 push_ring_segments(index, hole);
94 }
95
96 index
97 .polygons
98 .push(PolygonBlocker::from_rings(exterior, holes));
99 }
100 }
101 }
102
103 for layer in &mut layers {
104 let entries = layer
105 .segments
106 .iter()
107 .enumerate()
108 .map(|(id, segment)| {
109 SegmentEntry::new(Line::new(point(segment.0), point(segment.1)), id)
110 })
111 .collect();
112
113 layer.obstacles = RTree::bulk_load(entries);
114 let polygon_entries = layer
115 .polygons
116 .iter()
117 .enumerate()
118 .filter_map(|(id, polygon)| {
119 polygon_envelope(polygon).map(|envelope| PolygonEntry::new(envelope, id))
120 })
121 .collect();
122
123 layer.polygon_envelopes = RTree::bulk_load(polygon_entries);
124 }
125
126 let capacity = actors
127 .iter()
128 .map(|actor| actor.id.as_usize() + 1)
129 .max()
130 .unwrap_or(0);
131 let mut query = Self {
132 layers,
133 indexed_transforms: vec![None; capacity],
134 };
135 let mut actor_entries: Vec<Vec<ActorEntry>> =
136 query.layers.iter().map(|_| Vec::new()).collect();
137
138 for actor in actors {
139 query.validate_transform(actor.transform)?;
140 let indexed_transform = query
141 .indexed_transforms
142 .get_mut(actor.id.as_usize())
143 .ok_or_else(|| SpatialQueryError::missing_actor(actor.id))?;
144
145 if indexed_transform.is_some() {
146 return Err(SpatialQueryError::duplicate_actor(actor.id));
147 }
148
149 *indexed_transform = Some(actor.transform);
150 actor_entries
151 .get_mut(usize::from(actor.transform.layer.0))
152 .ok_or_else(|| SpatialQueryError::missing_layer(actor.transform.layer))?
153 .push(ActorEntry::new(point(actor.transform.position), actor.id));
154 }
155
156 for (layer, entries) in query.layers.iter_mut().zip(actor_entries) {
157 layer.actors = RTree::bulk_load(entries);
158 }
159
160 Ok(query)
161 }
162
163 fn layer(&self, layer: SpatialLayerId) -> Option<&LayerIndex> {
164 self.layers.get(usize::from(layer.0))
165 }
166
167 fn layer_mut(&mut self, layer: SpatialLayerId) -> Option<&mut LayerIndex> {
168 self.layers.get_mut(usize::from(layer.0))
169 }
170
171 #[cfg(test)]
172 fn diagnose_transform_validation(
173 &self,
174 transform: SpatialTransform,
175 ) -> TransformValidationObservation {
176 if !transform.position.x.is_finite()
177 || !transform.position.y.is_finite()
178 || !transform.heading.is_finite()
179 {
180 return TransformValidationObservation::error(SpatialQueryError::non_finite_transform());
181 }
182
183 let Some(index) = self.layer(transform.layer) else {
184 return TransformValidationObservation::error(SpatialQueryError::missing_layer(
185 transform.layer,
186 ));
187 };
188
189 let segment_candidates: Vec<_> = transform_segment_candidates(index, transform.position)
190 .map(|entry| entry.data)
191 .collect();
192 let polygon_candidates: Vec<_> = transform_polygon_candidates(index, transform.position)
193 .map(|entry| entry.data)
194 .collect();
195 let mut segment_blocked = false;
196 let mut segment_exact_checks = 0;
197
198 for id in &segment_candidates {
199 segment_exact_checks += 1;
200 segment_blocked |= index.segments.get(*id).is_some_and(|segment| {
201 point_segment_distance_sq(transform.position, *segment)
202 <= GEOMETRY_EPSILON * GEOMETRY_EPSILON
203 });
204 }
205
206 let mut polygon_blocked = false;
207 let mut polygon_exact_checks = 0;
208
209 for id in &polygon_candidates {
210 polygon_exact_checks += 1;
211 polygon_blocked |= index
212 .polygons
213 .get(*id)
214 .is_some_and(|polygon| point_in_blocker(transform.position, polygon));
215 }
216
217 TransformValidationObservation {
218 result: if segment_blocked || polygon_blocked {
219 Err(SpatialQueryError::blocked_transform())
220 } else {
221 Ok(())
222 },
223 segment_candidates: segment_candidates.len(),
224 polygon_candidates: polygon_candidates.len(),
225 segment_exact_checks,
226 polygon_exact_checks,
227 }
228 }
229}
230
231#[derive(Debug, Eq, PartialEq)]
232#[cfg(test)]
233struct TransformValidationObservation {
234 result: Result<(), SpatialQueryError>,
235 segment_candidates: usize,
236 polygon_candidates: usize,
237 segment_exact_checks: usize,
238 polygon_exact_checks: usize,
239}
240
241#[cfg(test)]
242impl TransformValidationObservation {
243 const fn error(error: SpatialQueryError) -> Self {
244 Self {
245 result: Err(error),
246 segment_candidates: 0,
247 polygon_candidates: 0,
248 segment_exact_checks: 0,
249 polygon_exact_checks: 0,
250 }
251 }
252}
253
254impl SpatialQueryDiagnostics for RstarSpatialQuery {
255 fn diagnose_actors_in_radius(
256 &self,
257 query: RadiusQuery,
258 allowed: Option<&[EnemyIdx]>,
259 ) -> SpatialQueryObservation<Vec<EnemyIdx>> {
260 let RadiusQuery {
261 layer,
262 center,
263 radius,
264 } = query;
265 let envelope = SpatialEnvelope {
266 min: Position2 {
267 x: center.x - radius - GEOMETRY_EPSILON,
268 y: center.y - radius - GEOMETRY_EPSILON,
269 },
270 max: Position2 {
271 x: center.x + radius + GEOMETRY_EPSILON,
272 y: center.y + radius + GEOMETRY_EPSILON,
273 },
274 };
275 let candidates = self.candidates_in_envelope(layer, envelope);
276 let broad_phase_candidates = candidates.len();
277 let result = candidates
278 .into_iter()
279 .filter(|enemy| actor_is_allowed(*enemy, allowed))
280 .filter(|enemy| {
281 self.indexed_transforms
282 .get(enemy.as_usize())
283 .and_then(|transform| *transform)
284 .is_some_and(|transform| {
285 transform.layer == layer
286 && center.distance(transform.position) <= radius + GEOMETRY_EPSILON
287 })
288 })
289 .collect();
290
291 SpatialQueryObservation {
292 result,
293 broad_phase_candidates,
294 }
295 }
296
297 fn diagnose_actors_in_cone(
298 &self,
299 query: ConeQuery,
300 allowed: Option<&[EnemyIdx]>,
301 ) -> SpatialQueryObservation<Vec<EnemyIdx>> {
302 let ConeQuery {
303 layer,
304 apex,
305 heading,
306 half_angle,
307 range,
308 } = query;
309 let candidates =
310 self.candidates_in_envelope(layer, cone_envelope(apex, heading, half_angle, range));
311 let broad_phase_candidates = candidates.len();
312 let result = candidates
313 .into_iter()
314 .filter(|enemy| actor_is_allowed(*enemy, allowed))
315 .filter(|enemy| {
316 self.indexed_transforms
317 .get(enemy.as_usize())
318 .and_then(|transform| *transform)
319 .is_some_and(|transform| {
320 transform.layer == layer
321 && in_cone(apex, heading, half_angle, range, transform.position)
322 })
323 })
324 .collect();
325
326 SpatialQueryObservation {
327 result,
328 broad_phase_candidates,
329 }
330 }
331
332 fn diagnose_segment_contacts(
333 &self,
334 layer: SpatialLayerId,
335 start: Position2,
336 end: Position2,
337 ) -> SpatialQueryObservation<Vec<usize>> {
338 let Some(index) = self.layer(layer) else {
339 return SpatialQueryObservation {
340 result: Vec::new(),
341 broad_phase_candidates: 0,
342 };
343 };
344 let envelope = AABB::from_corners(
345 [
346 start.x.min(end.x) - GEOMETRY_EPSILON,
347 start.y.min(end.y) - GEOMETRY_EPSILON,
348 ],
349 [
350 start.x.max(end.x) + GEOMETRY_EPSILON,
351 start.y.max(end.y) + GEOMETRY_EPSILON,
352 ],
353 );
354 let mut candidates: Vec<_> = index
355 .obstacles
356 .locate_in_envelope_intersecting(envelope)
357 .map(|entry| entry.data)
358 .collect();
359
360 candidates.sort_unstable();
361 candidates.dedup();
362 let broad_phase_candidates = candidates.len();
363
364 candidates.retain(|id| {
365 index
366 .segments
367 .get(*id)
368 .is_some_and(|obstacle| los_contact((start, end), *obstacle))
369 });
370
371 SpatialQueryObservation {
372 result: candidates,
373 broad_phase_candidates,
374 }
375 }
376}
377
378impl SpatialQuery for RstarSpatialQuery {
379 fn candidates_on_layer(&self, layer: SpatialLayerId) -> Vec<EnemyIdx> {
380 let Some(index) = self.layer(layer) else {
381 return Vec::new();
382 };
383 let mut ids: Vec<_> = index.actors.iter().map(|entry| entry.data).collect();
384
385 ids.sort_unstable();
386 ids.dedup();
387
388 ids
389 }
390
391 fn candidates_in_envelope(
392 &self,
393 layer: SpatialLayerId,
394 envelope: SpatialEnvelope,
395 ) -> Vec<EnemyIdx> {
396 let Some(index) = self.layer(layer) else {
397 return Vec::new();
398 };
399 let mut ids: Vec<_> = index
400 .actors
401 .locate_in_envelope_intersecting(AABB::from_corners(
402 point(envelope.min),
403 point(envelope.max),
404 ))
405 .map(|entry| entry.data)
406 .collect();
407
408 ids.sort_unstable();
409 ids.dedup();
410
411 ids
412 }
413
414 fn actors_in_radius(&self, query: RadiusQuery, allowed: Option<&[EnemyIdx]>) -> Vec<EnemyIdx> {
415 self.diagnose_actors_in_radius(query, allowed).result
416 }
417
418 fn actors_in_cone(&self, query: ConeQuery, allowed: Option<&[EnemyIdx]>) -> Vec<EnemyIdx> {
419 self.diagnose_actors_in_cone(query, allowed).result
420 }
421
422 fn actors_by_distance(
423 &self,
424 layer: SpatialLayerId,
425 origin: Position2,
426 allowed: Option<&[EnemyIdx]>,
427 ) -> Vec<EnemyIdx> {
428 let Some(index) = self.layer(layer) else {
429 return Vec::new();
430 };
431 let mut actors: Vec<_> = if let Some(allowed) = allowed {
432 allowed
433 .iter()
434 .filter_map(|actor| {
435 let transform = self
436 .indexed_transforms
437 .get(actor.as_usize())
438 .and_then(|transform| *transform)
439 .filter(|transform| transform.layer == layer)?;
440 let dx = transform.position.x - origin.x;
441 let dy = transform.position.y - origin.y;
442
443 Some((*actor, dx.mul_add(dx, dy * dy)))
444 })
445 .collect()
446 } else {
447 index
448 .actors
449 .nearest_neighbor_iter_with_distance_2(point(origin))
450 .map(|(entry, distance)| (entry.data, distance))
451 .collect()
452 };
453
454 actors.sort_unstable_by(|left, right| {
455 left.1
456 .total_cmp(&right.1)
457 .then_with(|| left.0.cmp(&right.0))
458 });
459
460 actors.into_iter().map(|(actor, _)| actor).collect()
461 }
462
463 fn nearest_actor_matching(
464 &self,
465 layer: SpatialLayerId,
466 origin: Position2,
467 is_eligible: &mut dyn FnMut(EnemyIdx) -> bool,
468 ) -> Option<EnemyIdx> {
469 let index = self.layer(layer)?;
470 let mut nearest = index
471 .actors
472 .nearest_neighbor_iter_with_distance_2(point(origin))
473 .peekable();
474 let mut distance_band = Vec::new();
475
476 while let Some((entry, distance)) = nearest.next() {
477 distance_band.clear();
478 distance_band.push(entry.data);
479
480 while nearest
481 .peek()
482 .is_some_and(|(_, next_distance)| distance.total_cmp(next_distance).is_eq())
483 {
484 if let Some((entry, _)) = nearest.next() {
485 distance_band.push(entry.data);
486 }
487 }
488
489 distance_band.sort_unstable();
490 distance_band.dedup();
491
492 if let Some(actor) = distance_band
493 .iter()
494 .copied()
495 .find(|actor| is_eligible(*actor))
496 {
497 return Some(actor);
498 }
499 }
500
501 None
502 }
503
504 fn segment_visible(&self, layer: SpatialLayerId, start: Position2, end: Position2) -> bool {
505 let Some(index) = self.layer(layer) else {
506 return false;
507 };
508 let envelope = AABB::from_corners(
509 [
510 start.x.min(end.x) - GEOMETRY_EPSILON,
511 start.y.min(end.y) - GEOMETRY_EPSILON,
512 ],
513 [
514 start.x.max(end.x) + GEOMETRY_EPSILON,
515 start.y.max(end.y) + GEOMETRY_EPSILON,
516 ],
517 );
518
519 !index
520 .obstacles
521 .locate_in_envelope_intersecting(envelope)
522 .any(|entry| {
523 index
524 .segments
525 .get(entry.data)
526 .is_some_and(|obstacle| los_contact((start, end), *obstacle))
527 })
528 }
529
530 fn validate_transform(&self, transform: SpatialTransform) -> Result<(), SpatialQueryError> {
531 if !transform.position.x.is_finite()
532 || !transform.position.y.is_finite()
533 || !transform.heading.is_finite()
534 {
535 return Err(SpatialQueryError::non_finite_transform());
536 }
537
538 let index = self
539 .layer(transform.layer)
540 .ok_or_else(|| SpatialQueryError::missing_layer(transform.layer))?;
541
542 if transform_segment_candidates(index, transform.position).any(|entry| {
543 index.segments.get(entry.data).is_some_and(|segment| {
544 point_segment_distance_sq(transform.position, *segment)
545 <= GEOMETRY_EPSILON * GEOMETRY_EPSILON
546 })
547 }) || transform_polygon_candidates(index, transform.position).any(|entry| {
548 index
549 .polygons
550 .get(entry.data)
551 .is_some_and(|polygon| point_in_blocker(transform.position, polygon))
552 }) {
553 return Err(SpatialQueryError::blocked_transform());
554 }
555
556 Ok(())
557 }
558
559 fn insert_actor(&mut self, actor: SpatialActor) -> Result<(), SpatialQueryError> {
560 self.validate_transform(actor.transform)?;
561
562 if self
563 .indexed_transforms
564 .get(actor.id.as_usize())
565 .is_some_and(Option::is_some)
566 {
567 return Err(SpatialQueryError::duplicate_actor(actor.id));
568 }
569
570 if actor.id.as_usize() >= self.indexed_transforms.len() {
571 self.indexed_transforms
572 .resize(actor.id.as_usize() + 1, None);
573 }
574
575 self.layer_mut(actor.transform.layer)
576 .ok_or_else(|| SpatialQueryError::missing_layer(actor.transform.layer))?
577 .actors
578 .insert(ActorEntry::new(point(actor.transform.position), actor.id));
579 let Some(indexed_transform) = self.indexed_transforms.get_mut(actor.id.as_usize()) else {
580 return Err(SpatialQueryError::missing_actor(actor.id));
581 };
582
583 *indexed_transform = Some(actor.transform);
584
585 Ok(())
586 }
587
588 fn remove_actor(&mut self, actor: SpatialActor) -> Result<(), SpatialQueryError> {
589 let indexed = self
590 .indexed_transforms
591 .get(actor.id.as_usize())
592 .and_then(|transform| *transform)
593 .ok_or_else(|| SpatialQueryError::missing_actor(actor.id))?;
594
595 if indexed != actor.transform {
596 return Err(SpatialQueryError::transform_mismatch(actor.id));
597 }
598
599 let removed = self
600 .layer_mut(indexed.layer)
601 .ok_or_else(|| SpatialQueryError::missing_layer(indexed.layer))?
602 .actors
603 .remove(&ActorEntry::new(point(indexed.position), actor.id));
604
605 if removed.is_none() {
606 return Err(SpatialQueryError::missing_actor(actor.id));
607 }
608
609 let Some(indexed_transform) = self.indexed_transforms.get_mut(actor.id.as_usize()) else {
610 return Err(SpatialQueryError::missing_actor(actor.id));
611 };
612
613 *indexed_transform = None;
614
615 Ok(())
616 }
617
618 fn relocate_actor(
619 &mut self,
620 actor: EnemyIdx,
621 from: SpatialTransform,
622 to: SpatialTransform,
623 ) -> Result<(), SpatialQueryError> {
624 self.validate_transform(to)?;
625 let indexed = self
626 .indexed_transforms
627 .get(actor.as_usize())
628 .and_then(|transform| *transform)
629 .ok_or_else(|| SpatialQueryError::missing_actor(actor))?;
630
631 if indexed != from {
632 return Err(SpatialQueryError::transform_mismatch(actor));
633 }
634
635 let from_entry = ActorEntry::new(point(from.position), actor);
636
637 if self
638 .layer_mut(from.layer)
639 .ok_or_else(|| SpatialQueryError::missing_layer(from.layer))?
640 .actors
641 .remove(&from_entry)
642 .is_none()
643 {
644 return Err(SpatialQueryError::missing_actor(actor));
645 }
646
647 self.layer_mut(to.layer)
648 .ok_or_else(|| SpatialQueryError::missing_layer(to.layer))?
649 .actors
650 .insert(ActorEntry::new(point(to.position), actor));
651 let Some(indexed_transform) = self.indexed_transforms.get_mut(actor.as_usize()) else {
652 return Err(SpatialQueryError::missing_actor(actor));
653 };
654
655 *indexed_transform = Some(to);
656
657 Ok(())
658 }
659}
660
661fn actor_is_allowed(actor: EnemyIdx, allowed: Option<&[EnemyIdx]>) -> bool {
662 allowed.is_none_or(|ids| ids.binary_search(&actor).is_ok())
663}
664
665fn layer_index_mut(
666 layers: &mut [LayerIndex],
667 layer: SpatialLayerId,
668) -> Result<&mut LayerIndex, SpatialQueryError> {
669 layers
670 .get_mut(usize::from(layer.0))
671 .ok_or_else(|| SpatialQueryError::missing_layer(layer))
672}
673
674fn push_segment(index: &mut LayerIndex, start: Position2, end: Position2) {
675 index.segments.push((start, end));
676}
677
678fn push_ring_segments(index: &mut LayerIndex, ring: &[Position2]) {
679 for edge in ring.windows(EDGE_POINT_COUNT) {
680 if let [start, end] = edge {
681 push_segment(index, *start, *end);
682 }
683 }
684}
685
686fn point_epsilon_envelope(position: Position2) -> AABB<[f64; POINT_DIMENSIONS]> {
687 AABB::from_corners(
688 [position.x - GEOMETRY_EPSILON, position.y - GEOMETRY_EPSILON],
689 [position.x + GEOMETRY_EPSILON, position.y + GEOMETRY_EPSILON],
690 )
691}
692
693fn transform_segment_candidates(
694 index: &LayerIndex,
695 position: Position2,
696) -> impl Iterator<Item = &SegmentEntry> {
697 index
698 .obstacles
699 .locate_in_envelope_intersecting(point_epsilon_envelope(position))
700}
701
702fn transform_polygon_candidates(
703 index: &LayerIndex,
704 position: Position2,
705) -> impl Iterator<Item = &PolygonEntry> {
706 index
707 .polygon_envelopes
708 .locate_in_envelope_intersecting(point_epsilon_envelope(position))
709}
710
711fn polygon_envelope(polygon: &PolygonBlocker) -> Option<Rectangle<[f64; POINT_DIMENSIONS]>> {
712 let first = polygon.exterior.first()?;
713 let mut min = *first;
714 let mut max = *first;
715
716 for position in polygon.exterior.iter().skip(1) {
717 min.x = min.x.min(position.x);
718 min.y = min.y.min(position.y);
719 max.x = max.x.max(position.x);
720 max.y = max.y.max(position.y);
721 }
722
723 Some(Rectangle::from_corners(point(min), point(max)))
724}
725
726const fn point(position: Position2) -> [f64; POINT_DIMENSIONS] {
727 [position.x, position.y]
728}
729
730const fn coord(position: Position2) -> Coord<f64> {
731 Coord {
732 x: position.x,
733 y: position.y,
734 }
735}
736
737fn exact_intersection(a: (Position2, Position2), b: (Position2, Position2)) -> bool {
738 let o1 = orient2d(coord(a.0), coord(a.1), coord(b.0));
739 let o2 = orient2d(coord(a.0), coord(a.1), coord(b.1));
740 let o3 = orient2d(coord(b.0), coord(b.1), coord(a.0));
741 let o4 = orient2d(coord(b.0), coord(b.1), coord(a.1));
742
743 if opposite_signs(o1, o2) && opposite_signs(o3, o4) {
744 return true;
745 }
746
747 exact_zero(o1) && on_closed_segment(b.0, a.0, a.1)
748 || exact_zero(o2) && on_closed_segment(b.1, a.0, a.1)
749 || exact_zero(o3) && on_closed_segment(a.0, b.0, b.1)
750 || exact_zero(o4) && on_closed_segment(a.1, b.0, b.1)
751}
752
753const fn opposite_signs(left: f64, right: f64) -> bool {
754 (left > 0.0 && right < 0.0) || (left < 0.0 && right > 0.0)
755}
756
757fn exact_zero(value: f64) -> bool {
758 value.total_cmp(&0.0).is_eq() || value.total_cmp(&-0.0).is_eq()
759}
760
761fn on_closed_segment(point: Position2, start: Position2, end: Position2) -> bool {
762 point.x >= start.x.min(end.x)
763 && point.x <= start.x.max(end.x)
764 && point.y >= start.y.min(end.y)
765 && point.y <= start.y.max(end.y)
766}
767
768fn point_segment_distance_sq(point: Position2, segment: (Position2, Position2)) -> f64 {
769 let dx = segment.1.x - segment.0.x;
770 let dy = segment.1.y - segment.0.y;
771 let length_sq = dx.mul_add(dx, dy * dy);
772
773 if exact_zero(length_sq) {
774 let ex = point.x - segment.0.x;
775 let ey = point.y - segment.0.y;
776
777 return ex.mul_add(ex, ey * ey);
778 }
779
780 let t =
781 (((point.x - segment.0.x) * dx + (point.y - segment.0.y) * dy) / length_sq).clamp(0.0, 1.0);
782 let qx = segment.0.x + t * dx;
783 let qy = segment.0.y + t * dy;
784 let ex = point.x - qx;
785 let ey = point.y - qy;
786
787 ex.mul_add(ex, ey * ey)
788}
789
790fn los_contact(a: (Position2, Position2), b: (Position2, Position2)) -> bool {
791 exact_intersection(a, b)
792 || point_segment_distance_sq(a.0, b) <= GEOMETRY_EPSILON * GEOMETRY_EPSILON
793 || point_segment_distance_sq(a.1, b) <= GEOMETRY_EPSILON * GEOMETRY_EPSILON
794 || point_segment_distance_sq(b.0, a) <= GEOMETRY_EPSILON * GEOMETRY_EPSILON
795 || point_segment_distance_sq(b.1, a) <= GEOMETRY_EPSILON * GEOMETRY_EPSILON
796}
797
798fn angle_diff(left: f64, right: f64) -> f64 {
799 let mut delta = (left - right) % std::f64::consts::TAU;
800
801 if delta > std::f64::consts::PI {
802 delta -= std::f64::consts::TAU;
803 }
804
805 if delta < -std::f64::consts::PI {
806 delta += std::f64::consts::TAU;
807 }
808
809 delta.abs()
810}
811
812fn in_cone(apex: Position2, heading: f64, half_angle: f64, range: f64, point: Position2) -> bool {
813 let distance = apex.distance(point);
814
815 if distance > range + GEOMETRY_EPSILON {
816 return false;
817 }
818
819 if distance <= GEOMETRY_EPSILON {
820 return true;
821 }
822
823 let angle = (point.y - apex.y).atan2(point.x - apex.x);
824
825 angle_diff(angle, heading) <= half_angle + GEOMETRY_EPSILON
826}
827
828fn cone_envelope(apex: Position2, heading: f64, half_angle: f64, range: f64) -> SpatialEnvelope {
829 if half_angle >= std::f64::consts::PI - GEOMETRY_EPSILON {
830 return SpatialEnvelope {
831 min: Position2 {
832 x: apex.x - range - GEOMETRY_EPSILON,
833 y: apex.y - range - GEOMETRY_EPSILON,
834 },
835 max: Position2 {
836 x: apex.x + range + GEOMETRY_EPSILON,
837 y: apex.y + range + GEOMETRY_EPSILON,
838 },
839 };
840 }
841
842 let mut min = apex;
843 let mut max = apex;
844 let mut include = |angle: f64| {
845 let position = Position2 {
846 x: apex.x + range * angle.cos(),
847 y: apex.y + range * angle.sin(),
848 };
849
850 min.x = min.x.min(position.x);
851 min.y = min.y.min(position.y);
852 max.x = max.x.max(position.x);
853 max.y = max.y.max(position.y);
854 };
855
856 include(heading - half_angle);
857 include(heading + half_angle);
858
859 for quadrant in 0..QUADRANT_COUNT {
860 let angle = f64::from(quadrant) * std::f64::consts::FRAC_PI_2;
861
862 if angle_diff(angle, heading) <= half_angle + GEOMETRY_EPSILON {
863 include(angle);
864 }
865 }
866
867 SpatialEnvelope {
868 min: Position2 {
869 x: min.x - GEOMETRY_EPSILON,
870 y: min.y - GEOMETRY_EPSILON,
871 },
872 max: Position2 {
873 x: max.x + GEOMETRY_EPSILON,
874 y: max.y + GEOMETRY_EPSILON,
875 },
876 }
877}
878
879fn point_in_blocker(point: Position2, polygon: &PolygonBlocker) -> bool {
880 ring_contains(point, &polygon.exterior)
881 && !polygon
882 .holes
883 .iter()
884 .any(|hole| ring_contains_strict(point, hole))
885}
886
887fn ring_contains(point: Position2, ring: &[Position2]) -> bool {
888 ring.windows(EDGE_POINT_COUNT).any(|edge| {
889 let [start, end] = edge else {
890 return false;
891 };
892
893 point_segment_distance_sq(point, (*start, *end)) <= GEOMETRY_EPSILON * GEOMETRY_EPSILON
894 }) || ring_contains_strict(point, ring)
895}
896
897fn ring_contains_strict(point: Position2, ring: &[Position2]) -> bool {
898 let mut inside = false;
899
900 for edge in ring.windows(EDGE_POINT_COUNT) {
901 let [a, b] = edge else {
902 continue;
903 };
904
905 if (a.y > point.y) != (b.y > point.y) {
906 let crossing_x = (b.x - a.x) * (point.y - a.y) / (b.y - a.y) + a.x;
907
908 if point.x < crossing_x {
909 inside = !inside;
910 }
911 }
912 }
913
914 inside
915}
916
917#[cfg(test)]
918#[path = "../benches/support/mod.rs"]
919mod frozen_oracle;
920
921#[cfg(test)]
922#[path = "rstar/tests.rs"]
923mod tests;