use nalgebra::Vector2;
use crate::geometry::Circle;
pub fn external_homothetic_center(c1: &Circle, c2: &Circle) -> Option<Vector2<f64>> {
let dr = c2.radius - c1.radius;
if dr.abs() < f64::EPSILON * 1e4 {
return None;
}
let result = (c2.radius * c1.center - c1.radius * c2.center) / dr;
Some(result)
}
pub fn internal_homothetic_center(c1: &Circle, c2: &Circle) -> Vector2<f64> {
let sum_r = c2.radius + c1.radius;
if sum_r.abs() < f64::EPSILON {
return (c1.center + c2.center) / 2.0;
}
(c2.radius * c1.center + c1.radius * c2.center) / sum_r
}
pub fn external_triple(
c1: &Circle,
c2: &Circle,
c3: &Circle,
) -> Option<(Vector2<f64>, Vector2<f64>, Vector2<f64>)> {
let s12 = external_homothetic_center(c1, c2)?;
let s23 = external_homothetic_center(c2, c3)?;
let s31 = external_homothetic_center(c3, c1)?;
Some((s12, s23, s31))
}
pub fn internal_triple(
c1: &Circle,
c2: &Circle,
c3: &Circle,
) -> (Vector2<f64>, Vector2<f64>, Vector2<f64>) {
let s12 = internal_homothetic_center(c1, c2);
let s23 = internal_homothetic_center(c2, c3);
let s31 = internal_homothetic_center(c3, c1);
(s12, s23, s31)
}
pub fn monge_collinear_area(
c1: &Circle,
c2: &Circle,
c3: &Circle,
) -> Result<f64, &'static str> {
let (s12, s23, s31) = external_triple(c1, c2, c3)
.ok_or("Equal radii — external centers at infinity")?;
Ok(triangle_area(&s12, &s23, &s31))
}
pub fn triangle_area(p: &Vector2<f64>, q: &Vector2<f64>, r: &Vector2<f64>) -> f64 {
0.5 * ((q.x - p.x) * (r.y - p.y) - (q.y - p.y) * (r.x - p.x)).abs()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_external_homothetic_basic() {
let c1 = Circle::new(Vector2::new(0.0, 0.0), 1.0);
let c2 = Circle::new(Vector2::new(4.0, 0.0), 2.0);
let s = external_homothetic_center(&c1, &c2).unwrap();
assert!((s.x - (-4.0)).abs() < 1e-10);
assert!((s.y).abs() < 1e-10);
}
#[test]
fn test_internal_homothetic_basic() {
let c1 = Circle::new(Vector2::new(0.0, 0.0), 1.0);
let c2 = Circle::new(Vector2::new(4.0, 0.0), 2.0);
let s = internal_homothetic_center(&c1, &c2);
assert!((s.x - 4.0 / 3.0).abs() < 1e-10);
assert!((s.y).abs() < 1e-10);
}
#[test]
fn test_external_equal_radii() {
let c1 = Circle::new(Vector2::new(0.0, 0.0), 1.0);
let c2 = Circle::new(Vector2::new(4.0, 0.0), 1.0);
assert!(external_homothetic_center(&c1, &c2).is_none());
}
#[test]
fn test_monge_theorem() {
let c1 = Circle::new(Vector2::new(0.0, 0.0), 1.0);
let c2 = Circle::new(Vector2::new(4.0, 0.0), 2.0);
let c3 = Circle::new(Vector2::new(2.0, 3.0), 1.5);
let area = monge_collinear_area(&c1, &c2, &c3).unwrap();
assert!(
area < 1e-10,
"Monge centers should be collinear, area = {}",
area
);
}
#[test]
fn test_internal_center_collinear_with_circle_centers() {
let c1 = Circle::new(Vector2::new(0.0, 0.0), 1.0);
let c2 = Circle::new(Vector2::new(4.0, 0.0), 2.0);
let s = internal_homothetic_center(&c1, &c2);
let area = triangle_area(&c1.center, &c2.center, &s);
assert!(
area < 1e-10,
"Internal center should be collinear with circle centers, area = {}",
area
);
}
#[test]
fn test_equal_radii_edge_case() {
let c1 = Circle::new(Vector2::new(0.0, 0.0), 2.0);
let c2 = Circle::new(Vector2::new(4.0, 0.0), 2.0);
assert!(external_homothetic_center(&c1, &c2).is_none());
let s = internal_homothetic_center(&c1, &c2);
assert!((s.x - 2.0).abs() < 1e-10);
let area = triangle_area(&c1.center, &c2.center, &s);
assert!(area < 1e-10);
}
}