use axiolid_core::Point2;
use axiolid_exact::{Arith, Dyadic};
use axiolid_guarantees::Sign;
use super::point::{dy, orient, same_point, sgn, sign, Circle, Pred, XPoint};
#[allow(clippy::large_enum_variant)]
#[derive(Debug, Clone)]
pub(crate) enum Carrier {
Segment,
Arc {
circle: Circle,
turn: Sign,
bulge: Dyadic,
},
}
#[derive(Debug, Clone, Copy)]
pub(crate) struct Bounds {
x0: f64,
y0: f64,
x1: f64,
y1: f64,
}
impl Bounds {
fn around(points: &[Point2], grow: f64) -> Self {
let mut b = Self {
x0: f64::INFINITY,
y0: f64::INFINITY,
x1: f64::NEG_INFINITY,
y1: f64::NEG_INFINITY,
};
for p in points {
b.x0 = b.x0.min(p.x);
b.y0 = b.y0.min(p.y);
b.x1 = b.x1.max(p.x);
b.y1 = b.y1.max(p.y);
}
let scale = b.x0.abs().max(b.x1.abs()).max(b.y0.abs()).max(b.y1.abs()) + grow.abs();
let pad = grow.abs() + 1e-9 * scale + f64::MIN_POSITIVE;
Self {
x0: b.x0 - pad,
y0: b.y0 - pad,
x1: b.x1 + pad,
y1: b.y1 + pad,
}
}
pub(crate) fn hull(boxes: impl Iterator<Item = Self>) -> Option<Self> {
boxes.reduce(|a, b| Self {
x0: a.x0.min(b.x0),
y0: a.y0.min(b.y0),
x1: a.x1.max(b.x1),
y1: a.y1.max(b.y1),
})
}
pub(crate) fn overlaps(&self, other: &Self) -> bool {
self.x0 <= other.x1 && other.x0 <= self.x1 && self.y0 <= other.y1 && other.y0 <= self.y1
}
pub(crate) fn has_nan(&self) -> bool {
self.x0.is_nan() || self.y0.is_nan() || self.x1.is_nan() || self.y1.is_nan()
}
pub(crate) fn centre(&self) -> (f64, f64) {
(0.5 * self.x0 + 0.5 * self.x1, 0.5 * self.y0 + 0.5 * self.y1)
}
pub(crate) fn wider_than_tall(&self) -> bool {
self.x1 - self.x0 >= self.y1 - self.y0
}
pub(crate) fn may_hold(&self, x: (f64, f64), y: (f64, f64)) -> bool {
self.x0 <= x.1 && x.0 <= self.x1 && self.y0 <= y.1 && y.0 <= self.y1
}
}
#[derive(Debug, Clone)]
pub(crate) struct Edge {
pub(crate) p0: XPoint,
pub(crate) p1: XPoint,
p0f: Point2,
d: (Dyadic, Dyadic),
pub(crate) carrier: Carrier,
pub(crate) bounds: Bounds,
}
fn half(value: &Dyadic) -> Dyadic {
value.mul(&dy(0.5))
}
fn edge_bounds(from: Point2, to: Point2, bulge: f64) -> Bounds {
let chord = ((to.x - from.x).powi(2) + (to.y - from.y).powi(2)).sqrt();
Bounds::around(&[from, to], bulge.abs() * chord / 2.0)
}
impl Edge {
pub(crate) fn new(from: Point2, to: Point2, bulge: f64) -> Self {
let (p0x, p0y) = (dy(from.x), dy(from.y));
let d = (dy(to.x).sub(&p0x), dy(to.y).sub(&p0y));
let carrier = if bulge == 0.0 {
Carrier::Segment
} else {
let b = dy(bulge);
let k = b.mul(&dy(4.0));
let one_minus = dy(1.0).sub(&b.square());
let mx = half(&p0x.add(&dy(to.x)));
let my = half(&p0y.add(&dy(to.y)));
let kcx = k.mul(&mx).sub(&one_minus.mul(&d.1));
let kcy = k.mul(&my).add(&one_minus.mul(&d.0));
let chord2 = d.0.square().add(&d.1.square());
let one_plus = dy(1.0).add(&b.square());
let circle = Circle {
alpha: k.square(),
bx: dy(-2.0).mul(&k).mul(&kcx),
by: dy(-2.0).mul(&k).mul(&kcy),
gamma: kcx
.square()
.add(&kcy.square())
.sub(&chord2.mul(&one_plus.square())),
};
Carrier::Arc {
circle,
turn: sgn(&b),
bulge: b,
}
};
Self {
p0: XPoint::from_f64(from),
p1: XPoint::from_f64(to),
p0f: from,
d,
carrier,
bounds: edge_bounds(from, to, bulge),
}
}
pub(crate) fn is_arc(&self) -> bool {
matches!(self.carrier, Carrier::Arc { .. })
}
pub(crate) fn point_at(&self, u: &Dyadic) -> XPoint {
let (p0x, p0y) = (dy(self.p0f.x), dy(self.p0f.y));
match &self.carrier {
Carrier::Segment => XPoint::rational(
p0x.add(&u.mul(&self.d.0)),
p0y.add(&u.mul(&self.d.1)),
dy(1.0),
),
Carrier::Arc { bulge: b, .. } => {
let s = b.mul(&dy(1.0).sub(u));
let s2 = s.square();
let w = b.mul(&dy(1.0).add(&s2).square());
let g = b.sub(&s).mul(&dy(1.0).add(&b.mul(&s)));
let one_minus = dy(1.0).sub(&s2);
let two_s = dy(2.0).mul(&s);
let vx = one_minus.mul(&self.d.0).add(&two_s.mul(&self.d.1));
let vy = one_minus.mul(&self.d.1).sub(&two_s.mul(&self.d.0));
XPoint::rational(
p0x.mul(&w).add(&g.mul(&vx)),
p0y.mul(&w).add(&g.mul(&vy)),
w,
)
}
}
}
pub(crate) fn approx_at(&self, u: f64) -> Point2 {
let p1 = self.p1.approx();
let (dx, dy) = (p1.x - self.p0f.x, p1.y - self.p0f.y);
match &self.carrier {
Carrier::Segment => Point2::new(self.p0f.x + u * dx, self.p0f.y + u * dy),
Carrier::Arc { bulge, .. } => {
let b = bulge.to_f64();
let s = b * (1.0 - u);
let w = b * (1.0 + s * s) * (1.0 + s * s);
let f = (b - s) * (1.0 + b * s) / w;
let one_minus = 1.0 - s * s;
Point2::new(
self.p0f.x + f * (one_minus * dx + 2.0 * s * dy),
self.p0f.y + f * (one_minus * dy - 2.0 * s * dx),
)
}
}
}
pub(crate) fn holds(&self, x: &XPoint) -> bool {
match &self.carrier {
Carrier::Segment => {
sign(Pred::Dot(&self.p0, x, &self.d)) != Sign::Negative
&& sign(Pred::Dot(x, &self.p1, &self.d)) != Sign::Negative
}
Carrier::Arc { turn, .. } => {
same_point(x, &self.p0)
|| same_point(x, &self.p1)
|| orient(&self.p0, &self.p1, x) == turn.flip()
}
}
}
pub(crate) fn same_segment(&self, other: &Self) -> Option<bool> {
if self.is_arc() || other.is_arc() {
return None;
}
let (a, b) = (self.p0f, self.p1.approx());
let (c, d) = (other.p0f, other.p1.approx());
if a == c && b == d {
Some(true)
} else if a == d && b == c {
Some(false)
} else {
None
}
}
pub(crate) fn contains(&self, x: &XPoint) -> bool {
let on_carrier = match &self.carrier {
Carrier::Segment => orient(&self.p0, &self.p1, x) == Sign::Zero,
Carrier::Arc { circle, .. } => sign(Pred::OnCircle(x, circle)) == Sign::Zero,
};
on_carrier && self.holds(x)
}
pub(crate) fn order(&self, a: &XPoint, b: &XPoint) -> Sign {
if same_point(a, b) {
return Sign::Zero;
}
match &self.carrier {
Carrier::Segment => sign(Pred::Dot(a, b, &self.d)).flip(),
Carrier::Arc { turn, .. } => {
if same_point(a, &self.p0) || same_point(b, &self.p1) {
return Sign::Negative;
}
if same_point(b, &self.p0) || same_point(a, &self.p1) {
return Sign::Positive;
}
let o = orient(&self.p0, a, b);
if o == *turn {
Sign::Negative
} else {
Sign::Positive
}
}
}
}
}
pub(crate) fn line_meets_circle(
q: (&Dyadic, &Dyadic, &Dyadic),
e: &(Dyadic, Dyadic),
circle: &Circle,
) -> (Vec<XPoint>, bool) {
let (qx, qy, qw) = q;
let e2 = e.0.square().add(&e.1.square());
let qw2 = qw.square();
let a = circle.alpha.mul(&e2).mul(&qw2);
let qe = qx.mul(&e.0).add(&qy.mul(&e.1));
let b = circle
.alpha
.mul(&qe)
.mul(qw)
.add(&half(&circle.bx.mul(&e.0).add(&circle.by.mul(&e.1))).mul(&qw2));
let c = circle
.alpha
.mul(&qx.square().add(&qy.square()))
.add(&circle.bx.mul(qx).add(&circle.by.mul(qy)).mul(qw))
.add(&circle.gamma.mul(&qw2));
let disc = b.square().sub(&a.mul(&c));
let base = circle.alpha.mul(&e2).mul(qw);
let xa = qx.mul(&base).sub(&e.0.mul(&b));
let ya = qy.mul(&base).sub(&e.1.mul(&b));
match sgn(&disc) {
Sign::Negative => (Vec::new(), false),
Sign::Zero => (vec![XPoint::rational(xa, ya, a)], true),
_ => {
let minus = XPoint::new(
xa.clone(),
e.0.neg(),
ya.clone(),
e.1.neg(),
a.clone(),
disc.clone(),
);
let plus = XPoint::new(xa, e.0.clone(), ya, e.1.clone(), a, disc);
(vec![minus, plus], false)
}
}
}
fn orient_f64(a: Point2, b: Point2, c: Point2) -> Option<Sign> {
let left = (b.x - a.x) * (c.y - a.y);
let right = (b.y - a.y) * (c.x - a.x);
let det = left - right;
let magnitude = left.abs() + right.abs();
if !(magnitude >= 1e-280 && magnitude.is_finite()) {
return None;
}
let bound = 3.330_669_073_875_472e-16 * magnitude;
if det > bound {
Some(Sign::Positive)
} else if -det > bound {
Some(Sign::Negative)
} else {
None
}
}
fn apart_f64(p: Point2, b: Point2, c: Point2) -> bool {
let first = (b.x - p.x) * (c.x - p.x);
let second = (b.y - p.y) * (c.y - p.y);
let magnitude = first.abs() + second.abs();
magnitude >= 1e-280
&& magnitude.is_finite()
&& -(first + second) > 3.330_669_073_875_472e-16 * magnitude
}
fn segments_quick(first: &Edge, second: &Edge) -> Option<Vec<XPoint>> {
let (a, b) = (first.p0f, first.p1.approx());
let (c, d) = (second.p0f, second.p1.approx());
if (a == c && b == d) || (a == d && b == c) {
return Some(vec![first.p0.clone(), first.p1.clone()]);
}
let sides = [
orient_f64(a, b, c),
orient_f64(a, b, d),
orient_f64(c, d, a),
orient_f64(c, d, b),
];
let one_side = |p: Option<Sign>, q: Option<Sign>| p.is_some() && p == q;
if one_side(sides[0], sides[1]) || one_side(sides[2], sides[3]) {
return Some(Vec::new());
}
if sides.iter().all(Option::is_some) {
return None;
}
for (p, own) in [(a, &first.p0), (b, &first.p1)] {
let far = if p == c {
d
} else if p == d {
c
} else {
continue;
};
if orient_f64(a, b, far).is_some() || apart_f64(p, if p == a { b } else { a }, far) {
return Some(vec![own.clone()]);
}
}
None
}
pub(crate) fn crossings(first: &Edge, second: &Edge) -> Vec<XPoint> {
let mut out: Vec<XPoint> = Vec::new();
let mut push = |x: XPoint| {
if first.holds(&x) && second.holds(&x) && !out.iter().any(|y| same_point(y, &x)) {
out.push(x);
}
};
let endpoints_on_each_other = |push: &mut dyn FnMut(XPoint)| {
for p in [&first.p0, &first.p1] {
if second.contains(p) {
push(p.clone());
}
}
for p in [&second.p0, &second.p1] {
if first.contains(p) {
push(p.clone());
}
}
};
match (&first.carrier, &second.carrier) {
(Carrier::Segment, Carrier::Segment) => {
if let Some(quick) = segments_quick(first, second) {
return quick;
}
let (d, e) = (&first.d, &second.d);
let den = d.0.mul(&e.1).sub(&d.1.mul(&e.0));
if sgn(&den) == Sign::Zero {
endpoints_on_each_other(&mut push);
} else {
let (p0x, p0y) = (dy(first.p0f.x), dy(first.p0f.y));
let (q0x, q0y) = (dy(second.p0f.x), dy(second.p0f.y));
let (rx, ry) = (q0x.sub(&p0x), q0y.sub(&p0y));
let num = rx.mul(&e.1).sub(&ry.mul(&e.0));
push(XPoint::rational(
p0x.mul(&den).add(&num.mul(&d.0)),
p0y.mul(&den).add(&num.mul(&d.1)),
den,
));
}
}
(Carrier::Segment, Carrier::Arc { circle, .. })
| (Carrier::Arc { circle, .. }, Carrier::Segment) => {
let seg = if first.is_arc() { second } else { first };
let (px, py) = (dy(seg.p0f.x), dy(seg.p0f.y));
let (points, _) = line_meets_circle((&px, &py, &dy(1.0)), &seg.d, circle);
for x in points {
push(x);
}
endpoints_on_each_other(&mut push);
}
(Carrier::Arc { circle: c1, .. }, Carrier::Arc { circle: c2, .. }) => {
if c1.same_as(c2) {
endpoints_on_each_other(&mut push);
} else {
let u = c2.alpha.mul(&c1.bx).sub(&c1.alpha.mul(&c2.bx));
let v = c2.alpha.mul(&c1.by).sub(&c1.alpha.mul(&c2.by));
let g = c2.alpha.mul(&c1.gamma).sub(&c1.alpha.mul(&c2.gamma));
if sgn(&u) != Sign::Zero || sgn(&v) != Sign::Zero {
let n = u.square().add(&v.square());
let (qx, qy) = (u.mul(&g).neg(), v.mul(&g).neg());
let (points, _) = line_meets_circle((&qx, &qy, &n), &(v.neg(), u), c1);
for x in points {
push(x);
}
}
endpoints_on_each_other(&mut push);
}
}
}
out
}