#[inline]
pub fn point_on_side(x: f32, y: f32, px: f32, py: f32, dx: f32, dy: f32) -> bool {
let cross = (x - px) * dy - (y - py) * dx;
cross <= 0.0 }
#[inline]
pub fn point_on_line_side(x: f32, y: f32, x1: f32, y1: f32, x2: f32, y2: f32) -> bool {
point_on_side(x, y, x1, y1, x2 - x1, y2 - y1)
}
pub fn point_to_segment_dist(px: f32, py: f32, x1: f32, y1: f32, x2: f32, y2: f32) -> f32 {
let dx = x2 - x1;
let dy = y2 - y1;
let len_sq = dx * dx + dy * dy;
if len_sq < 1e-10 {
return ((px - x1).powi(2) + (py - y1).powi(2)).sqrt();
}
let t = ((px - x1) * dx + (py - y1) * dy) / len_sq;
let t = t.clamp(0.0, 1.0);
let closest_x = x1 + t * dx;
let closest_y = y1 + t * dy;
((px - closest_x).powi(2) + (py - closest_y).powi(2)).sqrt()
}
#[allow(clippy::too_many_arguments)]
pub fn line_intersection(
x1: f32,
y1: f32,
x2: f32,
y2: f32,
x3: f32,
y3: f32,
x4: f32,
y4: f32,
) -> Option<f32> {
let dx1 = x2 - x1;
let dy1 = y2 - y1;
let dx2 = x4 - x3;
let dy2 = y4 - y3;
let denom = dx1 * dy2 - dy1 * dx2;
if denom.abs() < 1e-10 {
return None; }
let t = ((x3 - x1) * dy2 - (y3 - y1) * dx2) / denom;
let s = ((x3 - x1) * dy1 - (y3 - y1) * dx1) / denom;
if (0.0..=1.0).contains(&t) && (0.0..=1.0).contains(&s) {
Some(t)
} else {
None
}
}
#[inline]
pub fn point_to_angle(x1: f32, y1: f32, x2: f32, y2: f32) -> f32 {
(y2 - y1).atan2(x2 - x1)
}
#[inline]
pub fn normalize_angle(angle: f32) -> f32 {
angle.rem_euclid(std::f32::consts::TAU)
}
#[inline]
pub fn angle_diff(a: f32, b: f32) -> f32 {
let mut diff = b - a;
while diff > std::f32::consts::PI {
diff -= std::f32::consts::TAU;
}
while diff < -std::f32::consts::PI {
diff += std::f32::consts::TAU;
}
diff
}
#[inline]
pub fn dist(x1: f32, y1: f32, x2: f32, y2: f32) -> f32 {
((x2 - x1).powi(2) + (y2 - y1).powi(2)).sqrt()
}
#[inline]
pub fn circle_intersects_segment(
cx: f32,
cy: f32,
r: f32,
x1: f32,
y1: f32,
x2: f32,
y2: f32,
) -> bool {
point_to_segment_dist(cx, cy, x1, y1, x2, y2) < r
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn point_on_side_basic() {
assert!(point_on_side(1.0, 1.0, 0.0, 0.0, 1.0, 0.0));
assert!(!point_on_side(1.0, -1.0, 0.0, 0.0, 1.0, 0.0));
}
#[test]
fn line_intersection_basic() {
let t = line_intersection(0.0, 0.0, 1.0, 1.0, 0.0, 1.0, 1.0, 0.0);
assert!(t.is_some());
let t = t.unwrap();
assert!((t - 0.5).abs() < 1e-5);
}
#[test]
fn parallel_lines_no_intersection() {
assert!(line_intersection(0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 1.0, 1.0).is_none());
}
#[test]
fn point_to_angle_right() {
let a = point_to_angle(0.0, 0.0, 1.0, 0.0);
assert!(a.abs() < 1e-5);
}
#[test]
fn distance_basic() {
assert!((dist(0.0, 0.0, 3.0, 4.0) - 5.0).abs() < 1e-5);
}
#[test]
fn distance_same_point() {
assert!((dist(5.0, 3.0, 5.0, 3.0)).abs() < 1e-5);
}
#[test]
fn distance_negative_coords() {
assert!((dist(-3.0, -4.0, 0.0, 0.0) - 5.0).abs() < 1e-5);
}
#[test]
fn point_on_line_side_basic() {
assert!(point_on_line_side(5.0, 1.0, 0.0, 0.0, 10.0, 0.0));
assert!(!point_on_line_side(5.0, -1.0, 0.0, 0.0, 10.0, 0.0));
}
#[test]
fn point_on_line_side_on_line() {
assert!(point_on_line_side(5.0, 0.0, 0.0, 0.0, 10.0, 0.0));
}
#[test]
fn point_to_segment_dist_perpendicular() {
let d = point_to_segment_dist(5.0, 3.0, 0.0, 0.0, 10.0, 0.0);
assert!((d - 3.0).abs() < 1e-5);
}
#[test]
fn point_to_segment_dist_endpoint() {
let d = point_to_segment_dist(15.0, 0.0, 0.0, 0.0, 10.0, 0.0);
assert!((d - 5.0).abs() < 1e-5);
}
#[test]
fn point_to_segment_dist_degenerate() {
let d = point_to_segment_dist(3.0, 4.0, 0.0, 0.0, 0.0, 0.0);
assert!((d - 5.0).abs() < 1e-5);
}
#[test]
fn point_to_segment_dist_start_endpoint() {
let d = point_to_segment_dist(-3.0, 0.0, 0.0, 0.0, 10.0, 0.0);
assert!((d - 3.0).abs() < 1e-5);
}
#[test]
fn line_intersection_at_endpoints() {
let t = line_intersection(0.0, 0.0, 1.0, 0.0, 1.0, 0.0, 1.0, 1.0);
assert!(t.is_some());
assert!((t.unwrap() - 1.0).abs() < 1e-5);
}
#[test]
fn line_intersection_no_overlap() {
let t = line_intersection(0.0, 0.0, 1.0, 0.0, 2.0, -1.0, 2.0, 1.0);
assert!(t.is_none());
}
#[test]
fn normalize_angle_positive() {
let a = normalize_angle(0.5);
assert!((a - 0.5).abs() < 1e-5);
}
#[test]
fn normalize_angle_negative() {
let a = normalize_angle(-0.5);
let expected = std::f32::consts::TAU - 0.5;
assert!((a - expected).abs() < 1e-5);
}
#[test]
fn normalize_angle_over_tau() {
let a = normalize_angle(std::f32::consts::TAU + 1.0);
assert!((a - 1.0).abs() < 1e-4);
}
#[test]
fn angle_diff_same_angle() {
assert!(angle_diff(1.0, 1.0).abs() < 1e-5);
}
#[test]
fn angle_diff_opposite() {
let d = angle_diff(0.0, std::f32::consts::PI);
assert!((d - std::f32::consts::PI).abs() < 1e-5);
}
#[test]
fn angle_diff_wrap_around() {
let d = angle_diff(std::f32::consts::TAU - 0.1, 0.1);
assert!((d - 0.2).abs() < 1e-4);
}
#[test]
fn angle_diff_negative_wrap() {
let d = angle_diff(0.1, std::f32::consts::TAU - 0.1);
assert!((d + 0.2).abs() < 1e-4);
}
#[test]
fn point_to_angle_up() {
let a = point_to_angle(0.0, 0.0, 0.0, 1.0);
assert!((a - std::f32::consts::FRAC_PI_2).abs() < 1e-5);
}
#[test]
fn point_to_angle_left() {
let a = point_to_angle(0.0, 0.0, -1.0, 0.0);
assert!((a - std::f32::consts::PI).abs() < 1e-5);
}
#[test]
fn circle_intersects_segment_hit() {
assert!(circle_intersects_segment(
5.0, 2.0, 3.0, 0.0, 0.0, 10.0, 0.0
));
}
#[test]
fn circle_intersects_segment_miss() {
assert!(!circle_intersects_segment(
5.0, 5.0, 3.0, 0.0, 0.0, 10.0, 0.0
));
}
#[test]
fn circle_intersects_segment_tangent() {
assert!(!circle_intersects_segment(
5.0, 3.0, 3.0, 0.0, 0.0, 10.0, 0.0
));
}
#[test]
fn circle_intersects_segment_at_endpoint() {
assert!(circle_intersects_segment(
11.0, 0.0, 2.0, 0.0, 0.0, 10.0, 0.0
));
}
}