#![allow(dead_code)]
use crate::constants::DIVISION_EPSILON;
use crate::prelude::*;
#[derive(Debug, PartialEq)]
pub enum DistLineCircleConfig {
OnePair(f64, f64, Point, Point),
TwoPairs(f64, f64, f64, Point, Point, Point, Point),
}
const ZERO: f64 = 0.0f64;
pub fn dist_line_circle(line: &Line, circle: &Circle) -> DistLineCircleConfig {
let mut parameter: [f64; 2] = [0.0; 2];
let mut closest: [[Point; 2]; 2] = [[point(0.0, 0.0); 2]; 2];
let num_closest_pairs;
let delta = line.origin - circle.c;
let direction = line.dir;
let radius = circle.r;
let dot_dir_dir = direction.dot(direction);
let dot_dir_del = direction.dot(delta);
let dot_perp_dir_del = direction.perp(delta);
let r_sqr = radius * radius;
let test = dot_perp_dir_del * dot_perp_dir_del - r_sqr * dot_dir_dir;
if test >= ZERO {
num_closest_pairs = 1;
if dot_dir_dir.abs() > DIVISION_EPSILON {
parameter[0] = -dot_dir_del / dot_dir_dir;
} else {
parameter[0] = 0.0;
}
closest[0][0] = delta + direction * parameter[0];
closest[0][1] = closest[0][0];
if test > ZERO {
let (closestn, _) = closest[0][1].normalize(false);
closest[0][1] = closestn * radius;
}
} else {
let a0 = delta.dot(delta) - radius * radius;
let a1 = dot_dir_del;
let a2 = dot_dir_dir;
let discr = f64::max(a1 * a1 - a0 * a2, ZERO);
let sqrt_discr = discr.sqrt();
let temp = -dot_dir_del
+ if dot_dir_del > ZERO {
-sqrt_discr
} else {
sqrt_discr
};
num_closest_pairs = 2;
if dot_dir_dir.abs() > DIVISION_EPSILON && temp.abs() > DIVISION_EPSILON {
parameter[0] = temp / dot_dir_dir;
parameter[1] = a0 / temp;
} else {
parameter[0] = 0.0;
parameter[1] = 0.0;
}
if parameter[0] > parameter[1] {
(parameter[1], parameter[0]) = (parameter[0], parameter[1]);
}
closest[0][0] = delta + direction * parameter[0];
closest[0][1] = closest[0][0];
closest[1][0] = delta + direction * parameter[1];
closest[1][1] = closest[1][0];
}
for j in 0..num_closest_pairs {
for i in 0..2 {
closest[j][i] = closest[j][i] + circle.c;
}
}
if num_closest_pairs == 1 {
let dist = (closest[0][0] - closest[0][1]).norm();
DistLineCircleConfig::OnePair(dist, parameter[0], closest[0][0], closest[0][1])
} else {
let dist = 0.0;
DistLineCircleConfig::TwoPairs(
dist,
parameter[0],
parameter[1],
closest[0][0],
closest[0][1],
closest[1][0],
closest[1][1],
)
}
}
#[cfg(test)]
mod test_dist_line_circle {
use crate::circle::circle;
use crate::distance::dist_line_circle::DistLineCircleConfig;
use crate::line::{Line, line};
use crate::point::point;
use crate::segment::segment;
fn rev(line: Line) -> Line {
Line::new(line.origin, -line.dir)
}
#[test]
fn test_circle_touching_line() {
let line = line(point(0.0, 0.0), point(1.0, 0.0));
let circle = circle(point(1.0, 1.0), 1.0);
let res = super::dist_line_circle(&line, &circle);
assert_eq!(
res,
DistLineCircleConfig::OnePair(0.0, 1.0, point(1.0, 0.0), point(1.0, 0.0))
);
let res = super::dist_line_circle(&rev(line), &circle);
assert_eq!(
res,
DistLineCircleConfig::OnePair(0.0, -1.0, point(1.0, 0.0), point(1.0, 0.0))
);
}
#[test]
fn test_circle_not_intersecting_line() {
let eps = f64::EPSILON;
let line = line(point(0.0, 0.0), point(1.0, 0.0));
let circle = circle(point(1.0, 1.0), 1.0 - eps);
let res = super::dist_line_circle(&line, &circle);
assert_eq!(
res,
DistLineCircleConfig::OnePair(eps, 1.0, point(1.0, 0.0), point(1.0, eps))
);
let res = super::dist_line_circle(&rev(line), &circle);
assert_eq!(
res,
DistLineCircleConfig::OnePair(eps, -1.0, point(1.0, 0.0), point(1.0, eps))
);
}
#[test]
fn test_circle_not_intersecting_line_02() {
let seg = segment(point(-3.0, 1.5), point(-1.0, 1.5));
let circle = circle(point(0.0, 0.0), 1.0);
let line = line(seg.a, seg.b - seg.a);
let res = super::dist_line_circle(&line, &circle);
assert_eq!(
res,
DistLineCircleConfig::OnePair(0.5, 1.5, point(0.0, 1.5), point(0.0, 1.0))
);
let res = super::dist_line_circle(&rev(line), &circle);
assert_eq!(
res,
DistLineCircleConfig::OnePair(0.5, -1.5, point(0.0, 1.5), point(0.0, 1.0))
);
}
#[test]
fn test_circle_intersecting_line() {
let eps = f64::EPSILON;
let line = line(point(0.0, 0.0), point(1.0, 0.0));
let circle = circle(point(1.0, 1.0 - eps), 1.0);
let res = super::dist_line_circle(&line, &circle);
assert_eq!(
res,
DistLineCircleConfig::TwoPairs(
0.0,
0.9999999789265757,
1.0000000210734243,
point(0.9999999789265757, 0.0),
point(0.9999999789265757, 0.0),
point(1.0000000210734243, 0.0),
point(1.0000000210734243, 0.0)
)
);
let res = super::dist_line_circle(&rev(line), &circle);
assert_eq!(
res,
DistLineCircleConfig::TwoPairs(
0.0,
-1.0000000210734243,
-0.9999999789265757,
point(1.0000000210734243, 0.0),
point(1.0000000210734243, 0.0),
point(0.9999999789265757, 0.0),
point(0.9999999789265757, 0.0),
)
);
}
#[test]
fn test_circle_intersecting_line_02() {
let (dir, _) = point(0.0, -100.0).normalize(false);
let line = line(point(1.0, 5.0), dir);
let circle = circle(point(0.0, 0.0), 2.0);
let res = super::dist_line_circle(&line, &circle);
assert_eq!(
res,
DistLineCircleConfig::TwoPairs(
0.0,
3.267949192431123,
6.732050807568877,
point(1.0, 1.7320508075688772),
point(1.0, 1.7320508075688772),
point(1.0, -1.7320508075688767),
point(1.0, -1.7320508075688767),
)
);
}
#[test]
fn test_degenerate_line_zero_direction() {
let line = line(point(0.0, 0.0), point(0.0, 0.0));
let circle = circle(point(1.0, 1.0), 1.0);
let res = super::dist_line_circle(&line, &circle);
match res {
DistLineCircleConfig::OnePair(dist, param, p0, p1) => {
assert!(dist.is_finite());
assert_eq!(param, 0.0);
assert!(p0.x.is_finite() && p0.y.is_finite());
assert!(p1.x.is_finite() && p1.y.is_finite());
}
_ => panic!("Expected OnePair for degenerate line"),
}
}
#[test]
fn test_degenerate_line_very_small_direction() {
let line = line(point(0.0, 0.0), point(1e-13, 1e-13));
let circle = circle(point(1.0, 1.0), 1.0);
let res = super::dist_line_circle(&line, &circle);
match res {
DistLineCircleConfig::OnePair(dist, param, p0, p1) => {
assert!(dist.is_finite());
assert!(param.is_finite());
assert!(p0.x.is_finite() && p0.y.is_finite());
assert!(p1.x.is_finite() && p1.y.is_finite());
}
DistLineCircleConfig::TwoPairs(dist, p0, p1, _, _, _, _) => {
assert!(dist.is_finite());
assert!(p0.is_finite());
assert!(p1.is_finite());
}
}
}
#[test]
fn test_tangent_line_small_perpendicular_distance() {
let line = line(point(0.0, 0.0), point(1.0, 1e-13));
let circle = circle(point(0.5, 0.0), 0.5);
let res = super::dist_line_circle(&line, &circle);
match res {
DistLineCircleConfig::OnePair(dist, _, p0, p1) => {
assert!(dist.is_finite());
assert!(dist >= 0.0); assert!(p0.x.is_finite() && p0.y.is_finite());
assert!(p1.x.is_finite() && p1.y.is_finite());
}
DistLineCircleConfig::TwoPairs(dist, p0, p1, _, _, _, _) => {
assert!(dist.is_finite());
assert!(p0.is_finite());
assert!(p1.is_finite());
}
}
}
#[test]
fn test_division_epsilon_guard_effectiveness() {
const DIVISION_EPSILON: f64 = 1e-12;
let small_dir_mag = 1e-13;
let line = line(point(0.0, 0.0), point(small_dir_mag, small_dir_mag));
let circle = circle(point(10.0, 10.0), 1.0);
let res = super::dist_line_circle(&line, &circle);
match res {
DistLineCircleConfig::OnePair(dist, param, p0, p1) => {
assert!(!dist.is_infinite(), "Distance should not be infinite");
assert!(!param.is_infinite(), "Parameter should not be infinite");
assert!(!p0.x.is_infinite() && !p0.y.is_infinite(), "p0 should be finite");
assert!(!p1.x.is_infinite() && !p1.y.is_infinite(), "p1 should be finite");
}
DistLineCircleConfig::TwoPairs(dist, p0, p1, _, _, _, _) => {
assert!(!dist.is_infinite());
assert!(!p0.is_infinite() && !p1.is_infinite());
}
}
}
}