#![allow(dead_code)]
use crate::constants::CIRCLE_TOLERANCE;
use crate::point::point;
use crate::utils::diff_of_prod;
use crate::{circle::Circle, point::Point};
#[derive(Debug, PartialEq)]
pub enum CircleCircleConfig {
NoIntersection(),
NoncocircularOnePoint(Point), NoncocircularTwoPoints(Point, Point), SameCircles(),
}
const ZERO: f64 = 0f64;
pub fn int_circle_circle(circle0: Circle, circle1: Circle) -> CircleCircleConfig {
debug_assert!(circle0.r.is_finite());
debug_assert!(circle1.r.is_finite());
let u = circle1.c - circle0.c;
let usqr_len = u.dot(u);
let r0 = circle0.r;
let r1 = circle1.r;
let r0_m_r1 = r0 - r1;
if usqr_len < CIRCLE_TOLERANCE * CIRCLE_TOLERANCE && r0_m_r1.abs() < CIRCLE_TOLERANCE {
return CircleCircleConfig::SameCircles();
}
let r0_m_r1_sqr = r0_m_r1 * r0_m_r1;
if usqr_len < r0_m_r1_sqr {
return CircleCircleConfig::NoIntersection();
}
let r0_p_r1 = r0 + r1;
let r0_p_r1_sqr = r0_p_r1 * r0_p_r1;
if usqr_len > r0_p_r1_sqr {
return CircleCircleConfig::NoIntersection();
}
if usqr_len < r0_p_r1_sqr {
if r0_m_r1_sqr < usqr_len {
let s = 0.5 * (diff_of_prod(r0, r0, r1, r1) / usqr_len + 1.0);
let mut discr = diff_of_prod(r0 / usqr_len, r0, s, s);
if discr < ZERO {
discr = ZERO;
}
let t = discr.sqrt();
let v = point(u.y, -u.x);
let tmp = circle0.c + u * s;
let p0 = tmp - v * t;
let p1 = tmp + v * t;
if !p0.x.is_finite() || !p0.y.is_finite() || !p1.x.is_finite() || !p1.y.is_finite() {
return CircleCircleConfig::NoIntersection();
}
if t > 0f64 {
CircleCircleConfig::NoncocircularTwoPoints(p0, p1)
} else {
CircleCircleConfig::NoncocircularOnePoint(p0)
}
} else {
let p0 = circle0.c + u * (r0 / r0_m_r1);
if !p0.x.is_finite() || !p0.y.is_finite() {
return CircleCircleConfig::NoIntersection();
}
CircleCircleConfig::NoncocircularOnePoint(p0)
}
} else {
let p0 = circle0.c + u * (r0 / r0_p_r1);
if !p0.x.is_finite() || !p0.y.is_finite() {
return CircleCircleConfig::NoIntersection();
}
CircleCircleConfig::NoncocircularOnePoint(p0)
}
}
#[cfg(test)]
mod tests_circle {
use super::*;
use crate::circle::circle;
fn ff(circle0: Circle, circle1: Circle) -> CircleCircleConfig {
int_circle_circle(circle0, circle1)
}
#[test]
fn test_same_circles_01() {
let circle0 = circle(point(100.0, -100.0), 1.0);
let circle1 = circle(point(100.0, -100.0), 1.0);
assert_eq!(ff(circle0, circle1), CircleCircleConfig::SameCircles());
}
#[test]
fn test_same_non_intersection_01() {
let circle0 = circle(point(1000.0, -1000.0), 1.01);
let circle1 = circle(point(1000.0, -1000.0), 1.0);
assert_eq!(ff(circle0, circle1), CircleCircleConfig::NoIntersection());
}
#[test]
fn test_same_non_intersection_02() {
let circle0 = circle(point(1000.0, -1000.0), 1.0);
let circle1 = circle(point(1002.0, -1002.0), 1.0);
assert_eq!(ff(circle0, circle1), CircleCircleConfig::NoIntersection());
}
#[test]
fn test_noncircular_two_points() {
let eps = f64::EPSILON * 10.0;
let circle0 = circle(point(10.0, -10.0), 1.0);
let circle1 = circle(point(10.0, -12.0 + eps), 1.0);
let point0 = point(10.000000042146848, -11.0);
let point1 = point(9.999999957853152, -11.0);
let res = ff(circle0, circle1);
assert_eq!(
res,
CircleCircleConfig::NoncocircularTwoPoints(point0, point1)
);
}
#[test]
fn test_noncircular_one_point_01() {
let eps = f64::EPSILON * 2.0;
let circle0 = circle(point(10.0, -10.0), 1.0);
let circle1 = circle(point(10.0, -12.0 + eps), 1.0);
let point0 = point(10.0, -11.0);
let res = ff(circle0, circle1);
assert_eq!(res, CircleCircleConfig::NoncocircularOnePoint(point0));
}
#[test]
fn test_noncircular_one_point_02() {
let circle0 = circle(point(10.0, -10.0), 1.0);
let circle1 = circle(point(10.0, -10.5), 0.5);
let point0 = point(10.0, -11.0);
let res = ff(circle0, circle1);
assert_eq!(res, CircleCircleConfig::NoncocircularOnePoint(point0));
}
#[test]
fn test_noncircular_two_points_1() {
let eps = f64::EPSILON * 5.0;
let circle0 = circle(point(10.0, -10.0), 1.0);
let circle1 = circle(point(10.0, -10.5 - eps), 0.5);
let point0 = point(10.000000059604645, -10.999999999999998);
let point1 = point(9.999999940395355, -10.999999999999998);
let res = ff(circle0, circle1);
assert_eq!(
res,
CircleCircleConfig::NoncocircularTwoPoints(point0, point1)
);
}
#[test]
fn test_noncircular_one_point_03() {
let eps = f64::EPSILON * 2.0;
let circle0 = circle(point(1000.0, -1000.0), 100.0);
let circle1 = circle(point(1000.0, -1200.0 + eps), 100.0);
let point0 = point(1000.0, -1100.0);
let res = ff(circle0, circle1);
assert_eq!(res, CircleCircleConfig::NoncocircularOnePoint(point0));
}
#[test]
fn test_tolerance_boundary_within_tolerance() {
let tolerance = 1e-10;
let circle0 = circle(point(0.0, 0.0), 1.0);
let circle1 = circle(point(tolerance * 0.5, 0.0), 1.0); let res = ff(circle0, circle1);
assert_eq!(res, CircleCircleConfig::SameCircles());
}
#[test]
fn test_tolerance_boundary_outside_tolerance() {
let tolerance = 1e-10;
let circle0 = circle(point(0.0, 0.0), 1.0);
let circle1 = circle(point(tolerance * 2.0, 0.0), 1.0); let res = ff(circle0, circle1);
match res {
CircleCircleConfig::NoncocircularTwoPoints(_, _) => {
}
_ => panic!("Expected two intersection points for circles beyond tolerance"),
}
}
#[test]
fn test_radius_tolerance_boundary() {
let tolerance = 1e-10;
let circle0 = circle(point(0.0, 0.0), 1.0);
let circle1 = circle(point(0.0, 0.0), 1.0 + tolerance * 0.5); let res = ff(circle0, circle1);
assert_eq!(res, CircleCircleConfig::SameCircles());
}
#[test]
fn test_one_inside_other_no_intersection() {
let circle0 = circle(point(0.0, 0.0), 10.0);
let circle1 = circle(point(0.0, 0.0), 5.0);
let res = ff(circle0, circle1);
assert_eq!(res, CircleCircleConfig::NoIntersection());
}
#[test]
fn test_external_tangent() {
let circle0 = circle(point(0.0, 0.0), 1.0);
let circle1 = circle(point(2.0, 0.0), 1.0); let res = ff(circle0, circle1);
match res {
CircleCircleConfig::NoncocircularOnePoint(_) => {
}
_ => panic!("Expected one point for external tangent circles"),
}
}
#[test]
fn test_internal_tangent() {
let circle0 = circle(point(0.0, 0.0), 2.0);
let circle1 = circle(point(1.0, 0.0), 1.0); let res = ff(circle0, circle1);
match res {
CircleCircleConfig::NoncocircularOnePoint(_) => {
}
_ => panic!("Expected one point for internal tangent circles"),
}
}
#[test]
fn test_circles_with_zero_radius_same_center() {
let circle0 = circle(point(0.0, 0.0), 0.0);
let circle1 = circle(point(0.0, 0.0), 0.0);
let res = ff(circle0, circle1);
assert_eq!(res, CircleCircleConfig::SameCircles());
}
#[test]
fn test_circles_with_zero_radius_different_center() {
let circle0 = circle(point(0.0, 0.0), 0.0);
let circle1 = circle(point(1.0, 1.0), 0.0);
let res = ff(circle0, circle1);
assert_eq!(res, CircleCircleConfig::NoIntersection());
}
}
#[cfg(test)]
mod tests_circle_old {
use super::*;
use crate::{circle::circle, utils::perturbed_ulps_as_int};
fn ff(circle0: Circle, circle1: Circle) -> CircleCircleConfig {
int_circle_circle(circle0, circle1)
}
#[test]
fn test_same_circles01() {
let circle0 = circle(point(0.0, 0.0), 1.0);
let circle1 = circle(point(0.0, 0.0), 1.0);
assert_eq!(ff(circle0, circle1), CircleCircleConfig::SameCircles());
}
#[test]
fn test_same_circles02() {
let circle0 = circle(point(0.0, 0.0), 0.0);
let circle1 = circle(point(0.0, 0.0), 0.0);
assert_eq!(ff(circle0, circle1), CircleCircleConfig::SameCircles());
}
#[test]
fn test_same_circles03() {
let circle0 = circle(point(0.0, 0.0), f64::MAX);
let circle1 = circle(point(0.0, 0.0), f64::MAX);
assert_eq!(ff(circle0, circle1), CircleCircleConfig::SameCircles());
}
#[test]
fn test_same_circles04() {
let circle0 = circle(point(f64::MAX, f64::MAX), f64::MAX);
let circle1 = circle(point(f64::MAX, f64::MAX), f64::MAX);
assert_eq!(ff(circle0, circle1), CircleCircleConfig::SameCircles());
}
#[test]
fn test_donot_intersect01() {
let r = perturbed_ulps_as_int(1.0, -2);
let circle0 = circle(point(-1.0, 0.0), r);
let circle1 = circle(point(1.0, 0.0), 1.0);
assert_eq!(ff(circle0, circle1), CircleCircleConfig::NoIntersection());
}
#[test]
fn test_donot_intersect02() {
let x = perturbed_ulps_as_int(1.0, 2);
let circle0 = circle(point(-1.0, 0.0), 1.0);
let circle1 = circle(point(x, 0.0), 1.0);
assert_eq!(ff(circle0, circle1), CircleCircleConfig::NoIntersection());
}
#[test]
fn test_tangent01() {
let x = perturbed_ulps_as_int(1.0, 1);
let circle0 = circle(point(-1.0, 0.0), 1.0);
let circle1 = circle(point(x, 0.0), 1.0);
assert_eq!(
ff(circle0, circle1),
CircleCircleConfig::NoncocircularOnePoint(point(0.0, 0.0))
);
}
#[test]
fn test_tangent02() {
let circle0 = circle(point(1.0, 0.0), 1.0);
let circle1 = circle(point(1.0 + 1e-9, 0.0), 1.0); let res = int_circle_circle(circle0, circle1);
match res {
CircleCircleConfig::NoncocircularTwoPoints(p0, p1) => {
assert!((p0.x - 1.0).abs() < 1e-7);
assert!((p0.y - 1.0).abs() < 1e-7);
assert!((p1.x - 1.0).abs() < 1e-7);
assert!((p1.y + 1.0).abs() < 1e-7);
}
_ => panic!("Expected two intersection points"),
}
}
#[test]
fn test_tangent03() {
let _0 = perturbed_ulps_as_int(0.0, 1);
let _1 = perturbed_ulps_as_int(1.0, -1);
let circle0 = circle(point(0.0, 0.0), 1.0);
let circle1 = circle(point(_0, 0.0), 1.0);
let res = ff(circle0, circle1);
assert_eq!(res, CircleCircleConfig::SameCircles());
}
#[test]
fn test_tangent04() {
let circle0 = circle(point(0.0, 0.0), 1.0);
let circle1 = circle(point(0.0, 0.0), 1.0 - 1e-9); let res = ff(circle0, circle1);
assert_eq!(res, CircleCircleConfig::NoIntersection());
}
#[test]
fn test_tangent05() {
let circle0 = circle(point(1.0, 0.0), 1.0);
let circle1 = circle(point(1.0 + 5e-10, 0.0), 1.0 - 5e-10); let res = ff(circle0, circle1);
match res {
CircleCircleConfig::NoncocircularOnePoint(p) => {
assert!((p.x - 2.0).abs() < 1e-8);
assert!((p.y - 0.0).abs() < 1e-8);
}
_ => panic!("Expected tangent point, got {:?}", res),
}
}
#[test]
fn test_tangent06() {
let circle0 = circle(point(1.0, 0.0), 1.0);
let circle1 = circle(point(1.0 + 1e-9, 0.0), 1.0 - 1e-9); let res = ff(circle0, circle1);
match res {
CircleCircleConfig::NoncocircularTwoPoints(_, _) => {
}
_ => panic!("Expected two intersection points, got {:?}", res),
}
}
#[test]
fn test_no_intersection2() {
let c0 = circle(point(0.5, 0.0), 0.5);
let c1 = circle(point(-1.0, 0.0), 1.0);
let res = ff(c0, c1);
assert_eq!(
res,
CircleCircleConfig::NoncocircularOnePoint(point(0.0, 0.0))
);
}
use crate::svg::svg;
#[test]
fn test_intersection_issue_01() {
let mut svg = svg(150.0, 200.0);
let c0 = circle(point(100.0, 130.0), 20.0);
let c1 = circle(point(75.0, 40.0), 85.0);
svg.circle(&c0, "red");
svg.circle(&c1, "blue");
let p0 = point(113.87064429562277, 115.59148769566033);
let p1 = point(80.68522962987866, 124.80965843614482);
svg.circle(&circle(p0, 1.0), "red");
svg.write();
let res = ff(c0, c1);
assert_eq!(res, CircleCircleConfig::NoncocircularTwoPoints(p0, p1));
}
#[test]
fn test_external_tangent_exact() {
let circle0 = circle(point(0.0, 0.0), 1.0);
let circle1 = circle(point(2.0, 0.0), 1.0); let res = ff(circle0, circle1);
match res {
CircleCircleConfig::NoncocircularOnePoint(p) => {
assert!((p.x - 1.0).abs() < 1e-10);
assert!((p.y - 0.0).abs() < 1e-10);
}
_ => panic!("Expected external tangent point, got {:?}", res),
}
}
#[test]
fn test_discriminant_zero_path() {
let circle0 = circle(point(0.0, 0.0), 1.0);
let circle1 = circle(point(2.0, 0.0), 1.0); let res = ff(circle0, circle1);
match res {
CircleCircleConfig::NoncocircularOnePoint(p) => {
assert!((p.x - 1.0).abs() < 1e-10);
assert!((p.y - 0.0).abs() < 1e-10);
}
_ => panic!("Expected NoncocircularOnePoint from discriminant zero path, got {:?}", res),
}
}
#[test]
fn test_bounds_check_non_finite_intersection_points() {
let circle0 = circle(point(0.0, 0.0), 1.0);
let circle1 = circle(point(1e100, 0.0), 1e100);
let res = ff(circle0, circle1);
match res {
CircleCircleConfig::NoIntersection() => {
}
CircleCircleConfig::NoncocircularOnePoint(p) => {
assert!(p.x.is_finite() && p.y.is_finite(), "Point coordinates should be finite");
}
CircleCircleConfig::NoncocircularTwoPoints(p0, p1) => {
assert!(p0.x.is_finite() && p0.y.is_finite(), "p0 should be finite");
assert!(p1.x.is_finite() && p1.y.is_finite(), "p1 should be finite");
}
_ => {}
}
}
#[test]
fn test_bounds_check_tangent_non_finite_point() {
let circle0 = circle(point(0.0, 0.0), 1.0);
let circle1 = circle(point(2.0, 0.0), 1e-100); let res = ff(circle0, circle1);
match res {
CircleCircleConfig::NoIntersection() => {
}
CircleCircleConfig::NoncocircularOnePoint(p) => {
assert!(p.x.is_finite() && p.y.is_finite(), "Tangent point should be finite");
}
_ => {}
}
}
}