use glam::Vec2;
#[cfg(feature = "tracing")]
use tracing::instrument;
use crate::instance::EdgeSide;
pub(crate) const EPSILON: f32 = 1e-4;
pub(crate) trait Vec2Helper {
fn side(self, edge: (Vec2, Vec2)) -> EdgeSide;
fn mirror(self, edge: (Vec2, Vec2)) -> Vec2;
fn on_segment(self, segment: (Vec2, Vec2)) -> bool;
fn in_bounding_box(self, segment: (Vec2, Vec2)) -> bool;
}
impl Vec2Helper for Vec2 {
#[cfg_attr(feature = "tracing", instrument(skip_all))]
#[inline(always)]
fn side(self, line: (Vec2, Vec2)) -> EdgeSide {
let local_point = self - line.0;
let local_line = line.1 - line.0;
match local_line.perp_dot(local_point) {
x if x.abs() < EPSILON => EdgeSide::Edge,
x if x > 0.0 => EdgeSide::Left,
_ => EdgeSide::Right,
}
}
#[cfg_attr(feature = "tracing", instrument(skip_all))]
#[inline(always)]
fn mirror(self, line: (Vec2, Vec2)) -> Vec2 {
let local_point = self - line.0;
let local_line = line.1 - line.0;
let local_reflect_point = 2.0 * local_point.project_onto(local_line) - local_point;
line.0 + local_reflect_point
}
#[cfg_attr(feature = "tracing", instrument(skip_all))]
#[inline(always)]
fn on_segment(self, segment: (Vec2, Vec2)) -> bool {
self.in_bounding_box(segment) && (self.side(segment) == EdgeSide::Edge)
}
#[cfg_attr(feature = "tracing", instrument(skip_all))]
#[inline(always)]
fn in_bounding_box(self, segment: (Vec2, Vec2)) -> bool {
const EPSILON: f32 = 1.0e-6;
((min(segment.0.x, segment.1.x) - EPSILON)..=(max(segment.0.x, segment.1.x) + EPSILON))
.contains(&self.x)
&& ((min(segment.0.y, segment.1.y) - EPSILON)
..=(max(segment.0.y, segment.1.y) + EPSILON))
.contains(&self.y)
}
}
#[inline(always)]
pub(crate) fn min(x: f32, y: f32) -> f32 {
if x < y {
x
} else {
y
}
}
#[inline(always)]
pub(crate) fn max(x: f32, y: f32) -> f32 {
if x > y {
x
} else {
y
}
}
#[cfg_attr(feature = "tracing", instrument(skip_all))]
#[inline(always)]
pub(crate) fn heuristic(root: Vec2, goal: Vec2, interval: (Vec2, Vec2)) -> f32 {
let goal = if root.side(interval) == goal.side(interval) {
goal.mirror(interval)
} else {
goal
};
if root == interval.0 || root == interval.1 {
root.distance(goal)
} else {
match intersection_time((root, goal), interval) {
x if x < 0.0 => root.distance(interval.0) + interval.0.distance(goal),
x if x > 1.0 => root.distance(interval.1) + interval.1.distance(goal),
_ => root.distance(goal),
}
}
}
#[cfg_attr(feature = "tracing", instrument(skip_all))]
#[inline(always)]
pub(crate) fn turning_point(root: Vec2, goal: Vec2, interval: (Vec2, Vec2)) -> Option<Vec2> {
let goal = if root.side(interval) == goal.side(interval) {
goal.mirror(interval)
} else {
goal
};
if root == interval.0 {
None
} else if goal.side((root, interval.0)) == EdgeSide::Right {
Some(interval.0)
} else if goal.side((root, interval.1)) == EdgeSide::Left {
Some(interval.1)
} else {
None
}
}
#[cfg_attr(feature = "tracing", instrument(skip_all))]
#[inline(always)]
pub(crate) fn intersection_time(line: (Vec2, Vec2), segment: (Vec2, Vec2)) -> f32 {
let local_line = line.0 - line.1;
(line.0 - segment.0).perp_dot(local_line) / (local_line).perp_dot(segment.0 - segment.1)
}
#[cfg_attr(feature = "tracing", instrument(skip_all))]
#[inline(always)]
pub(crate) fn line_intersect_segment(line: (Vec2, Vec2), segment: (Vec2, Vec2)) -> Option<Vec2> {
let intersection_time = intersection_time(line, segment);
if !(-EPSILON..=(1.0 + EPSILON)).contains(&intersection_time) || intersection_time.is_nan() {
None
} else {
Some(segment.0 + intersection_time * (segment.1 - segment.0))
}
}
#[cfg(test)]
mod tests {
use glam::{vec2, Vec2};
use crate::{helpers::Vec2Helper, instance::EdgeSide};
use super::{heuristic, line_intersect_segment};
#[test]
fn test_on_side() {
assert_eq!(
Vec2Helper::side(
Vec2::new(0.0, 0.5),
(Vec2::new(0.0, 0.0), Vec2::new(1.0, 0.0))
),
EdgeSide::Left
);
assert_eq!(
Vec2Helper::side(
Vec2::new(0.0, -0.5),
(Vec2::new(0.0, 0.0), Vec2::new(1.0, 0.0))
),
EdgeSide::Right
);
assert_eq!(
Vec2Helper::side(
Vec2::new(1.0, 0.0),
(Vec2::new(0.0, 0.0), Vec2::new(1.0, 1.0))
),
EdgeSide::Right
);
assert_eq!(
Vec2Helper::side(
Vec2::new(0.0, 1.0),
(Vec2::new(0.0, 0.0), Vec2::new(1.0, 1.0))
),
EdgeSide::Left
);
assert_eq!(
Vec2Helper::side(
Vec2::new(2.0, 2.0),
(Vec2::new(0.0, 0.0), Vec2::new(1.0, 1.0))
),
EdgeSide::Edge
);
assert_eq!(
Vec2Helper::side(
Vec2::new(5.585231282, 5.3880110045),
(Vec2::new(9.56, 7.42), Vec2::new(1.54, 3.32))
),
EdgeSide::Edge
);
assert_ne!(
Vec2Helper::side(
Vec2::new(1.8266357, 1.2239377),
(Vec2::new(1.775, 1.275), Vec2::new(1.775, 1.175))
),
EdgeSide::Edge
);
}
#[test]
fn test_mirror() {
assert_eq!(
Vec2Helper::mirror(
Vec2::new(1.0, 0.0),
(Vec2::new(0.0, 0.0), Vec2::new(0.0, 1.0))
),
Vec2::new(-1.0, 0.0)
);
assert_eq!(
Vec2Helper::mirror(
Vec2::new(-1.0, 0.0),
(Vec2::new(0.0, 0.0), Vec2::new(0.0, 1.0))
),
Vec2::new(1.0, 0.0)
);
}
#[test]
fn test_heuristic() {
assert_eq!(
heuristic(
Vec2::new(0.0, 0.0),
Vec2::new(1.0, 1.0),
(Vec2::new(1.0, 0.0), Vec2::new(0.0, 1.0))
),
2.0_f32.sqrt()
);
assert_eq!(
heuristic(
Vec2::new(0.0, 0.0),
Vec2::new(2.0, -1.0),
(Vec2::new(1.0, 0.0), Vec2::new(0.0, 1.0))
),
1.0 + 2.0_f32.sqrt()
);
assert_eq!(
heuristic(
Vec2::new(0.0, 0.0),
Vec2::new(-1.0, 2.0),
(Vec2::new(1.0, 0.0), Vec2::new(0.0, 1.0))
),
1.0 + 2.0_f32.sqrt()
);
assert_eq!(
heuristic(
Vec2::new(0.0, 0.0),
Vec2::new(1.0, -1.0),
(Vec2::new(1.0, 0.0), Vec2::new(0.0, 1.0))
),
2.0
);
}
#[test]
fn test_line_intersect() {
assert_eq!(
line_intersect_segment(
(Vec2::new(0.0, 0.5), Vec2::new(0.5, 0.5)),
(Vec2::new(1.0, 0.0), Vec2::new(1.0, 1.0))
),
Some(Vec2::new(1.0, 0.5))
);
assert_eq!(
line_intersect_segment(
(Vec2::new(0.0, 0.0), Vec2::new(0.5, 0.8)),
(Vec2::new(1.0, 0.0), Vec2::new(1.0, 0.2))
),
None
);
assert_eq!(
line_intersect_segment(
(Vec2::new(0.0, 0.8), Vec2::new(0.5, 0.0)),
(Vec2::new(1.0, 0.0), Vec2::new(1.0, 0.2))
),
None
);
}
#[test]
fn test_line_intersect_colinear() {
assert_eq!(
line_intersect_segment(
(Vec2::new(0.0, 0.5), Vec2::new(0.5, 0.5)),
(Vec2::new(-1.0, 0.5), Vec2::new(1.0, 0.5))
),
None
);
}
#[test]
fn test_on_segment() {
let segment = (vec2(0.0, 0.0), vec2(-9.0, 0.0));
let p = vec2(-0.30399954, 5.9604645e-8);
assert!(p.on_segment(segment));
}
}