use approx::{ulps_eq, ulps_ne};
use compare_variables::compare_variables;
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
use crate::{
CentroidData, Rotation2, Transformation,
primitive::{Primitive, PrimitiveIntersections},
};
use bounding_box::{BoundingBox, ToBoundingBox};
use std::f64::consts::{FRAC_PI_2, PI, TAU};
#[doc = ""]
#[cfg_attr(
feature = "doc-images",
doc = "![Arc segment example][example_arc_segment]"
)]
#[cfg_attr(
feature = "doc-images",
embed_doc_image::embed_doc_image("example_arc_segment", "docs/img/example_arc_segment.svg")
)]
#[cfg_attr(
not(feature = "doc-images"),
doc = "**Doc images not enabled**. Compile docs with
`cargo doc --features 'doc-images'` and Rust version >= 1.54."
)]
#[derive(Clone, Debug, PartialEq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "serde", serde(deny_unknown_fields))]
pub struct ArcSegment {
center: [f64; 2],
radius: f64,
start_angle: f64,
offset_angle: f64,
}
impl ArcSegment {
pub fn center(&self) -> [f64; 2] {
return self.center;
}
pub fn radius(&self) -> f64 {
return self.radius;
}
pub fn start_angle(&self) -> f64 {
return self.start_angle;
}
pub fn offset_angle(&self) -> f64 {
return self.offset_angle;
}
pub fn stop_angle(&self) -> f64 {
return self.start_angle() + self.offset_angle();
}
pub fn is_positive(&self) -> bool {
return self.offset_angle().is_sign_positive();
}
pub fn circle(center: [f64; 2], radius: f64) -> crate::error::Result<Self> {
return Self::from_center_radius_start_offset_angle(center, radius, 0.0, TAU, 0.0, 0);
}
pub fn from_center_radius_start_offset_angle(
center: [f64; 2],
radius: f64,
start_angle: f64,
offset_angle: f64,
epsilon: f64,
max_ulps: u32,
) -> crate::error::Result<Self> {
compare_variables!(0.0 < radius)?;
if approx::ulps_eq!(offset_angle, 0.0, epsilon = epsilon, max_ulps = max_ulps) {
let mut c = center;
c.translate([radius * start_angle.cos(), radius * start_angle.sin()]);
return Err(crate::error::ErrorType::PointsIdentical { start: c, stop: c }.into());
}
let start_angle = start_angle.rem_euclid(TAU);
let offset_angle = if offset_angle.abs() == TAU {
TAU * offset_angle.signum()
} else {
offset_angle % TAU
};
return Ok(ArcSegment {
center,
radius,
start_angle,
offset_angle,
});
}
pub fn from_center_radius_start_stop_angle(
center: [f64; 2],
radius: f64,
start_angle: f64,
stop_angle: f64,
epsilon: f64,
max_ulps: u32,
) -> crate::error::Result<Self> {
let offset_angle = stop_angle - start_angle;
return Self::from_center_radius_start_offset_angle(
center,
radius,
start_angle,
offset_angle,
epsilon,
max_ulps,
);
}
pub fn from_start_middle_stop(
start: [f64; 2],
middle: [f64; 2],
stop: [f64; 2],
epsilon: f64,
max_ulps: u32,
) -> crate::error::Result<Self> {
let center = three_point_arc_center(start, middle, stop)?;
let is_positive = three_point_arc_is_positive(start, middle, stop);
let start_angle = ((start[1] - center[1]).atan2(start[0] - center[0])).rem_euclid(TAU);
let stop_angle = ((stop[1] - center[1]).atan2(stop[0] - center[0])).rem_euclid(TAU);
let offset_angle = calculate_offset_angle(start_angle, stop_angle, is_positive);
let radius = ((start[0] - center[0]).powi(2) + (start[1] - center[1]).powi(2)).sqrt();
return ArcSegment::from_center_radius_start_offset_angle(
center,
radius,
start_angle,
offset_angle,
epsilon,
max_ulps,
);
}
pub fn from_start_center_angle(
start: [f64; 2],
center: [f64; 2],
offset_angle: f64,
epsilon: f64,
max_ulps: u32,
) -> crate::error::Result<Self> {
let start_angle = (start[1] - center[1]).atan2(start[0] - center[0]);
let radius = ((start[0] - center[0]).powi(2) + (start[1] - center[1]).powi(2)).sqrt();
return ArcSegment::from_center_radius_start_offset_angle(
center,
radius,
start_angle,
offset_angle,
epsilon,
max_ulps,
);
}
#[doc = ""]
#[cfg_attr(feature = "doc-images", doc = "![Four possible arcs][four_arcs]")]
#[cfg_attr(
feature = "doc-images",
embed_doc_image::embed_doc_image("four_arcs", "docs/img/example_four_arcs.svg")
)]
#[cfg_attr(
not(feature = "doc-images"),
doc = "**Doc images not enabled**. Compile docs with `cargo doc --features 'doc-images'` and Rust version >= 1.54."
)]
pub fn from_start_stop_radius(
start: [f64; 2],
stop: [f64; 2],
radius: f64,
positive: bool,
large_arc: bool,
epsilon: f64,
max_ulps: u32,
) -> crate::error::Result<Self> {
fn compute_offset_angle(
start: [f64; 2],
stop: [f64; 2],
center: [f64; 2],
positive: bool,
) -> (f64, f64) {
let start_angle = (start[1] - center[1]).atan2(start[0] - center[0]);
let stop_angle = (stop[1] - center[1]).atan2(stop[0] - center[0]);
let mut delta = stop_angle - start_angle;
if positive {
if delta < 0.0 {
delta += TAU;
}
} else {
if delta > 0.0 {
delta -= TAU;
}
}
(start_angle, delta)
}
compare_variables!(radius > 0.0)?;
let (x1, y1) = (start[0], start[1]);
let (x2, y2) = (stop[0], stop[1]);
let dx = x2 - x1;
let dy = y2 - y1;
let distance_start_stop = (dx * dx + dy * dy).sqrt();
if approx::ulps_eq!(
distance_start_stop,
0.0,
epsilon = epsilon,
max_ulps = max_ulps
) {
return Err(crate::error::ErrorType::PointsIdentical { start, stop }.into());
}
let mut radius = radius;
if 0.5 * distance_start_stop > radius {
radius = 0.5 * distance_start_stop
}
let mx = (x1 + x2) * 0.5;
let my = (y1 + y2) * 0.5;
let half_d = distance_start_stop * 0.5;
let h = (radius * radius - half_d * half_d).sqrt();
let inv_d = 1.0 / distance_start_stop;
let px = -dy * inv_d;
let py = dx * inv_d;
let c1 = [mx + px * h, my + py * h];
let c2 = [mx - px * h, my - py * h];
let (start_angle_1, offset_angle_1) = compute_offset_angle(start, stop, c1, positive);
let (start_angle_2, offset_angle_2) = compute_offset_angle(start, stop, c2, positive);
let is_large1 = offset_angle_1.abs() > std::f64::consts::PI;
let is_large2 = offset_angle_2.abs() > std::f64::consts::PI;
let (center, start_angle, offset_angle) =
match (is_large1 == large_arc, is_large2 == large_arc) {
(true, false) => (c1, start_angle_1, offset_angle_1),
(false, true) => (c2, start_angle_2, offset_angle_2),
(true, true) => {
(c1, start_angle_1, offset_angle_1)
}
(false, false) => {
let target = if large_arc {
std::f64::consts::PI * 1.5
} else {
std::f64::consts::PI * 0.5
};
let err1 = (offset_angle_1.abs() - target).abs();
let err2 = (offset_angle_2.abs() - target).abs();
if err1 < err2 {
(c1, start_angle_1, offset_angle_1)
} else {
(c2, start_angle_2, offset_angle_2)
}
}
};
Self::from_center_radius_start_offset_angle(
center,
radius,
start_angle,
offset_angle,
epsilon,
max_ulps,
)
}
pub fn from_start_stop_center(
start: [f64; 2],
stop: [f64; 2],
center: [f64; 2],
positive: bool,
large_arc: bool,
epsilon: f64,
max_ulps: u32,
) -> crate::error::Result<Self> {
let radius_start_center =
((start[0] - center[0]).powi(2) + (start[1] - center[1]).powi(2)).sqrt();
let radius_stop_center =
((stop[0] - center[0]).powi(2) + (stop[1] - center[1]).powi(2)).sqrt();
if approx::ulps_ne!(
radius_start_center,
radius_stop_center,
epsilon = epsilon,
max_ulps = max_ulps
) {
compare_variables!(radius_start_center == radius_stop_center)?;
}
return ArcSegment::from_start_stop_radius(
start,
stop,
radius_start_center,
positive,
large_arc,
epsilon,
max_ulps,
);
}
pub fn fillet(
start: [f64; 2],
corner: [f64; 2],
stop: [f64; 2],
radius: f64,
epsilon: f64,
max_ulps: u32,
) -> crate::error::Result<Self> {
if ulps_eq!(start, corner, epsilon = epsilon, max_ulps = max_ulps)
|| ulps_eq!(corner, stop, epsilon = epsilon, max_ulps = max_ulps)
{
return Err(crate::error::ErrorType::Collinear([start, corner, stop]).into());
}
compare_variables!(radius > 0.0)?;
let start_angle = (corner[1] - start[1]).atan2(corner[0] - start[0]);
let stop_angle = (corner[1] - stop[1]).atan2(corner[0] - stop[0]);
let pos_offset_angle: f64;
let neg_offset_angle: f64;
if stop_angle < start_angle {
pos_offset_angle = stop_angle + TAU - start_angle;
} else {
pos_offset_angle = stop_angle - start_angle;
}
if stop_angle > start_angle {
neg_offset_angle = start_angle + TAU - stop_angle;
} else {
neg_offset_angle = start_angle - stop_angle;
}
let mut offset_angle: f64;
let dir: f64;
if pos_offset_angle.abs() < PI {
offset_angle = pos_offset_angle;
dir = 0.5;
} else {
offset_angle = neg_offset_angle;
dir = -0.5;
}
offset_angle = offset_angle % TAU;
let mut segment_len = radius / (offset_angle / 2.0).tan().abs();
let delta_pc = ((corner[0] - start[0]).powi(2) + (corner[1] - start[1]).powi(2)).sqrt();
let delta_cn = ((corner[0] - stop[0]).powi(2) + (corner[1] - stop[1]).powi(2)).sqrt();
let min_dist: f64;
if delta_pc > delta_cn {
min_dist = delta_cn;
} else {
min_dist = delta_pc;
}
let radius = if segment_len > min_dist {
segment_len = min_dist;
segment_len * (offset_angle / 2.0).tan()
} else {
radius
};
let delta_ci = (radius.powi(2) + segment_len.powi(2)).sqrt();
let xp = corner[0] - (corner[0] - start[0]) * segment_len / delta_pc;
let yp = corner[1] - (corner[1] - start[1]) * segment_len / delta_pc;
let start_fillet = [xp, yp];
let xn = corner[0] - (corner[0] - stop[0]) * segment_len / delta_cn;
let yn = corner[1] - (corner[1] - stop[1]) * segment_len / delta_cn;
let stop_fillet = [xn, yn];
let delta_x = corner[0] * 2.0 - xp - xn;
let delta_y = corner[1] * 2.0 - yp - yn;
let delta_cc = (delta_x.powi(2) + delta_y.powi(2)).sqrt();
let xc = corner[0] - delta_x * delta_ci / delta_cc;
let yc = corner[1] - delta_y * delta_ci / delta_cc;
let xm = (start_angle + offset_angle * dir).cos() * radius + xc;
let ym = (start_angle + offset_angle * dir).sin() * radius + yc;
let middle_fillet = [xm, ym];
return ArcSegment::from_start_middle_stop(
start_fillet,
middle_fillet,
stop_fillet,
epsilon,
max_ulps,
);
}
pub fn is_circle(&self) -> bool {
return self.offset_angle().abs() >= TAU;
}
pub fn covers_angle(&self, angle: f64) -> bool {
let angle = angle % TAU;
let start_angle = self.start_angle();
let offset_angle = self.offset_angle();
let stop_angle = start_angle + offset_angle;
if offset_angle > 0.0 {
return (angle >= start_angle && angle <= stop_angle)
|| (angle + TAU >= start_angle && angle + TAU <= stop_angle)
|| (angle - TAU >= start_angle && angle - TAU <= stop_angle);
} else {
return (angle >= stop_angle && angle <= start_angle)
|| (angle + TAU >= stop_angle && angle + TAU <= start_angle)
|| (angle - TAU >= stop_angle && angle - TAU <= start_angle);
}
}
pub fn start(&self) -> [f64; 2] {
let mut c = self.center.clone();
c.translate([
self.radius * self.start_angle().cos(),
self.radius * self.start_angle().sin(),
]);
return c;
}
pub fn stop(&self) -> [f64; 2] {
let mut c = self.center.clone();
c.translate([
self.radius * self.stop_angle().cos(),
self.radius * self.stop_angle().sin(),
]);
return c;
}
pub fn number_points(&self) -> usize {
return 3;
}
pub fn length(&self) -> f64 {
return (self.radius() * self.offset_angle()).abs();
}
pub fn centroid(&self) -> [f64; 2] {
return CentroidData::from(self).into();
}
pub fn reverse(&mut self) {
self.start_angle = self.stop_angle();
self.offset_angle = -self.offset_angle;
}
pub fn segment_point(&self, normalized: f64) -> [f64; 2] {
let normalized = normalized.clamp(0.0, 1.0);
let angle = self.start_angle() + normalized * self.offset_angle;
return [
self.center[0] + self.radius * angle.cos(),
self.center[1] + self.radius * angle.sin(),
];
}
pub fn polygonize<'a>(
&'a self,
polygonizer: super::SegmentPolygonizer,
) -> super::PolygonPointsIterator<'a> {
let radius = self.radius();
let num_segs = match polygonizer {
super::SegmentPolygonizer::InnerSegments(segs) => segs,
super::SegmentPolygonizer::MaximumSegmentLength(max_len) => {
if max_len <= 0.0 {
0
} else {
let max_angle = 2.0 * (0.5 * max_len / radius).clamp(-1.0, 1.0).asin();
(self.offset_angle().abs() / max_angle).ceil() as usize
}
}
super::SegmentPolygonizer::MaximumAngle(max_angle) => {
if max_angle <= 0.0 {
0
} else {
(self.offset_angle().abs() / max_angle).ceil() as usize
}
}
};
return super::PolygonPointsIterator {
index: 0,
num_segs,
segment: super::SegmentRef::ArcSegment(self),
};
}
pub fn points<'a>(&'a self) -> super::PolygonPointsIterator<'a> {
return self.polygonize(super::SegmentPolygonizer::InnerSegments(1));
}
pub fn invert(&mut self) {
self.start_angle = self.stop_angle();
self.offset_angle = -self.offset_angle;
}
pub fn same_circle(&self, other: &ArcSegment, epsilon: f64, max_ulps: u32) -> bool {
ulps_eq!(
self.center,
other.center,
epsilon = epsilon,
max_ulps = max_ulps
) && ulps_eq!(
self.radius,
other.radius,
epsilon = epsilon,
max_ulps = max_ulps
)
}
pub fn is_tangent(
&self,
line_segment: &super::LineSegment,
epsilon: f64,
max_ulps: u32,
) -> bool {
return line_segment.is_tangent(self, epsilon, max_ulps);
}
#[doc = ""]
#[cfg_attr(feature = "doc-images", doc = "![Touching and dividing][touching]")]
#[cfg_attr(
feature = "doc-images",
embed_doc_image::embed_doc_image("touching", "docs/img/touching.svg")
)]
#[cfg_attr(
not(feature = "doc-images"),
doc = "**Doc images not enabled**. Compile docs with `cargo doc --features 'doc-images'` and Rust version >= 1.54."
)]
pub fn touches_segment<'a, T: Into<super::SegmentRef<'a>>>(
&self,
other: T,
epsilon: f64,
max_ulps: u32,
) -> bool {
return self
.touches_and_intersections(other.into(), epsilon, max_ulps)
.0;
}
pub(crate) fn touches_and_intersections(
&self,
other: super::SegmentRef<'_>,
epsilon: f64,
max_ulps: u32,
) -> (bool, PrimitiveIntersections) {
fn ep_as(s: &super::ArcSegment, pt: [f64; 2], epsilon: f64, max_ulps: u32) -> bool {
return ulps_eq!(pt, s.start(), epsilon = epsilon, max_ulps = max_ulps)
|| ulps_eq!(pt, s.stop(), epsilon = epsilon, max_ulps = max_ulps);
}
match other {
super::SegmentRef::LineSegment(line_segment) => {
return line_segment.touches_and_intersections(self.into(), epsilon, max_ulps);
}
super::SegmentRef::ArcSegment(arc_segment) => {
if std::ptr::eq(self, arc_segment) {
return (true, PrimitiveIntersections::Zero);
}
let intersections = self.intersections_arc_segment(arc_segment, epsilon, max_ulps);
let touches = match intersections {
PrimitiveIntersections::Zero => false,
PrimitiveIntersections::One(i) => {
if ep_as(self, i, epsilon, max_ulps)
|| ep_as(arc_segment, i, epsilon, max_ulps)
{
true
} else {
let a = [i[0] - self.center[0], i[1] - self.center[1]];
let b = [i[0] - arc_segment.center[0], i[1] - arc_segment.center[1]];
let cross = a[0] * b[1] - a[1] * b[0];
ulps_eq!(cross, 0.0, epsilon = epsilon, max_ulps = max_ulps)
}
}
PrimitiveIntersections::Two([i1, i2]) => {
if self.same_circle(&arc_segment, epsilon, max_ulps) {
true
} else {
(ep_as(self, i1, epsilon, max_ulps)
|| ep_as(arc_segment, i1, epsilon, max_ulps))
&& (ep_as(self, i2, epsilon, max_ulps)
|| ep_as(arc_segment, i2, epsilon, max_ulps))
}
}
};
return (touches, intersections);
}
}
}
pub(crate) fn intersections_line_circle(
&self,
a: f64,
b: f64,
c: f64,
epsilon: f64,
max_ulps: u32,
) -> PrimitiveIntersections {
let denom = a * a + b * b;
let x0 = -a * c / denom;
let y0 = -b * c / denom;
let arg = self.radius().powi(2) - c.powi(2) / denom;
let mut intersections = PrimitiveIntersections::Zero;
if arg < 0.0 {
} else if ulps_eq!(arg, 0.0, epsilon = epsilon, max_ulps = max_ulps) {
let mut pt = self.center();
pt.translate([x0, y0]);
if self.covers_point(pt, epsilon, max_ulps) {
intersections.push(pt);
}
} else {
let m = (arg / denom).sqrt();
let xa = x0 + b * m;
let ya = y0 - a * m;
let xb = x0 - b * m;
let yb = y0 + a * m;
let mut pta = self.center();
pta.translate([xa, ya]);
let mut ptb = self.center();
ptb.translate([xb, yb]);
let angle_a = ya.atan2(xa);
let angle_b = yb.atan2(xb);
if self.covers_angle(angle_a) {
intersections.push(pta);
}
if self.covers_angle(angle_b) {
intersections.push(ptb);
}
}
return intersections;
}
pub(crate) fn split_y_monotonic(&self, epsilon: f64, max_ulps: u32) -> [Option<ArcSegment>; 3] {
let start_angle = if self.is_circle() {
-FRAC_PI_2
} else {
self.start_angle()
};
let mut angles = [Some(start_angle), None, None, None];
let mut free_slot = 1;
if self.is_positive() {
if self.start_angle() > FRAC_PI_2 {
if self.covers_angle(3.0 * FRAC_PI_2) {
angles[free_slot] = Some(3.0 * FRAC_PI_2);
free_slot += 1;
}
if self.covers_angle(FRAC_PI_2) {
angles[free_slot] = Some(FRAC_PI_2);
free_slot += 1;
}
} else {
if self.covers_angle(FRAC_PI_2) {
angles[free_slot] = Some(FRAC_PI_2);
free_slot += 1;
}
if self.covers_angle(3.0 * FRAC_PI_2) {
angles[free_slot] = Some(3.0 * FRAC_PI_2);
free_slot += 1;
}
}
} else {
if self.start_angle() < FRAC_PI_2 {
if self.covers_angle(3.0 * FRAC_PI_2) {
angles[free_slot] = Some(3.0 * FRAC_PI_2);
free_slot += 1;
}
if self.covers_angle(FRAC_PI_2) {
angles[free_slot] = Some(FRAC_PI_2);
free_slot += 1;
}
} else {
if self.covers_angle(FRAC_PI_2) {
angles[free_slot] = Some(FRAC_PI_2);
free_slot += 1;
}
if self.covers_angle(3.0 * FRAC_PI_2) {
angles[free_slot] = Some(3.0 * FRAC_PI_2);
free_slot += 1;
}
}
}
if ulps_ne!(
self.start_angle().rem_euclid(TAU),
self.stop_angle().rem_euclid(TAU),
epsilon = epsilon,
max_ulps = max_ulps
) {
angles[free_slot] = Some(self.stop_angle());
}
let mut arcs = [None, None, None];
free_slot = 0;
for w in angles.windows(2) {
let start_angle = match w[0] {
Some(a) => a,
None => break,
};
let stop_angle = match w[1] {
Some(a) => a,
None => break,
};
if let Ok(arc) = ArcSegment::from_center_radius_start_stop_angle(
self.center(),
self.radius(),
start_angle,
stop_angle,
epsilon,
max_ulps,
) {
arcs[free_slot] = Some(arc);
free_slot += 1;
}
}
return arcs;
}
}
impl crate::primitive::private::Sealed for ArcSegment {}
impl Primitive for ArcSegment {
fn covers_point(&self, point: [f64; 2], epsilon: f64, max_ulps: u32) -> bool {
if ulps_eq!(self.start(), point, epsilon = epsilon, max_ulps = max_ulps) {
return true;
}
if ulps_eq!(self.stop(), point, epsilon = epsilon, max_ulps = max_ulps) {
return true;
}
if !BoundingBox::from(self).approx_covers_point(point, epsilon, max_ulps) {
return false;
}
let shifted_pt = [point[0] - self.center()[0], point[1] - self.center()[1]];
if ulps_eq!(
self.radius().powi(2),
shifted_pt[0].powi(2) + shifted_pt[1].powi(2),
epsilon = epsilon,
max_ulps = max_ulps
) {
let angle = shifted_pt[1].atan2(shifted_pt[0]);
return self.covers_angle(angle);
} else {
return false;
}
}
fn covers_arc_segment(&self, arc_segment: &Self, epsilon: f64, max_ulps: u32) -> bool {
if std::ptr::eq(self, arc_segment) {
return true;
}
if self.is_circle() {
return ulps_eq!(
self.center(),
arc_segment.center(),
epsilon = epsilon,
max_ulps = max_ulps
) && ulps_eq!(
self.radius(),
arc_segment.radius(),
epsilon = epsilon,
max_ulps = max_ulps
);
}
match self.intersections_primitive(arc_segment, epsilon, max_ulps) {
PrimitiveIntersections::Zero => false,
PrimitiveIntersections::One(_) => false,
PrimitiveIntersections::Two([pt1, pt2]) => {
let start = arc_segment.start();
let stop = arc_segment.stop();
if ulps_ne!(start, pt1, epsilon = epsilon, max_ulps = max_ulps)
&& ulps_ne!(stop, pt1, epsilon = epsilon, max_ulps = max_ulps)
{
false
} else if ulps_ne!(start, pt2, epsilon = epsilon, max_ulps = max_ulps)
&& ulps_ne!(stop, pt2, epsilon = epsilon, max_ulps = max_ulps)
{
false
} else {
self.covers_angle(arc_segment.start_angle() + 0.5 * arc_segment.offset_angle())
}
}
}
}
fn covers_line_segment(
&self,
_line_segment: &crate::segment::LineSegment,
_epsilon: f64,
_max_ulps: u32,
) -> bool {
return false;
}
fn covers_line(&self, _line: &crate::line::Line, _epsilon: f64, _max_ulps: u32) -> bool {
return false;
}
fn intersections_line(
&self,
line: &crate::line::Line,
epsilon: f64,
max_ulps: u32,
) -> PrimitiveIntersections {
line.intersections_arc_segment(self, epsilon, max_ulps)
}
fn intersections_line_segment(
&self,
line_segment: &crate::segment::LineSegment,
epsilon: f64,
max_ulps: u32,
) -> PrimitiveIntersections {
line_segment.intersections_arc_segment(self, epsilon, max_ulps)
}
fn intersections_arc_segment(
&self,
arc_segment: &ArcSegment,
epsilon: f64,
max_ulps: u32,
) -> PrimitiveIntersections {
if std::ptr::eq(self, arc_segment) {
return PrimitiveIntersections::Zero;
}
let radius_arc1 = self.radius();
let radius_arc2 = arc_segment.radius();
let center_arc1 = self.center();
let center_arc2 = arc_segment.center();
let x2 = center_arc2[0] - center_arc1[0];
let y2 = center_arc2[1] - center_arc1[1];
let a = -2.0 * x2;
let b = -2.0 * y2;
let c = x2.powi(2) + y2.powi(2) + (radius_arc1).powi(2) - (radius_arc2).powi(2);
if approx::ulps_eq!(c, 0.0, epsilon = epsilon, max_ulps = max_ulps) {
let mut intersections = PrimitiveIntersections::Zero;
if self.covers_angle(arc_segment.start_angle()) {
intersections.push(arc_segment.start());
}
if self.covers_angle(arc_segment.stop_angle()) {
intersections.push(arc_segment.stop());
}
if intersections.len() == 2 {
return intersections;
}
if arc_segment.covers_angle(self.start_angle()) {
intersections.push(self.start());
}
if intersections.len() == 2 {
return intersections;
}
if arc_segment.covers_angle(self.stop_angle()) {
intersections.push(self.stop());
}
return intersections;
} else {
let mut intersections = PrimitiveIntersections::Zero;
for i in self.intersections_line_circle(a, b, c, epsilon, max_ulps) {
if arc_segment.covers_point(i, epsilon, max_ulps) {
intersections.push(i);
}
}
return intersections;
}
}
fn intersections_primitive<T: Primitive>(
&self,
other: &T,
epsilon: f64,
max_ulps: u32,
) -> PrimitiveIntersections {
other.intersections_arc_segment(self, epsilon, max_ulps)
}
}
impl Transformation for ArcSegment {
fn translate(&mut self, shift: [f64; 2]) {
self.center.translate(shift);
}
fn rotate(&mut self, center: [f64; 2], angle: f64) {
let t = Rotation2::new(angle);
let pt = [self.center[0] - center[0], self.center[1] - center[1]];
self.center = t * pt;
self.center.translate([center[0], center[1]]);
self.start_angle = (self.start_angle + angle).rem_euclid(TAU);
}
fn scale(&mut self, factor: f64) {
self.center.scale(factor);
self.radius *= factor;
}
fn line_reflection(&mut self, start: [f64; 2], stop: [f64; 2]) -> () {
if start[0] == stop[0] {
self.center = [-self.center[0] + 2.0 * start[0], self.center[1]];
} else if start[1] == stop[1] {
self.center = [self.center[0], -self.center[1] + 2.0 * start[1]];
} else {
let m = (stop[1] - start[1]) / (stop[0] - start[0]);
let c = (stop[0] * start[1] - start[0] * stop[1]) / (stop[0] - start[0]);
let d = (self.center[0] + (self.center[1] - c) * m) / (1.0 + m.powi(2));
self.center = [
2.0 * d - self.center[0],
2.0 * d * m - self.center[1] + 2.0 * c,
];
};
self.offset_angle = -self.offset_angle;
let line_angle = (stop[1] - start[1]).atan2(stop[0] - start[0]);
let diff = line_angle - self.start_angle;
self.start_angle = (self.start_angle + 2.0 * diff).rem_euclid(TAU);
}
}
impl ToBoundingBox for ArcSegment {
fn bounding_box(&self) -> BoundingBox {
let c = self.center();
let mut xmin_pt = c.clone();
xmin_pt.translate([-self.radius(), 0.0]);
let mut xmax_pt = c.clone();
xmax_pt.translate([self.radius(), 0.0]);
let mut ymin_pt = c.clone();
ymin_pt.translate([0.0, -self.radius()]);
let mut ymax_pt = c.clone();
ymax_pt.translate([0.0, self.radius()]);
let start = self.start();
let stop = self.stop();
let xmin = if self.covers_angle(PI) {
xmin_pt[0]
} else {
if start[0] < stop[0] {
start[0]
} else {
stop[0]
}
};
let xmax = if self.covers_angle(0.0) {
xmax_pt[0]
} else {
if start[0] > stop[0] {
start[0]
} else {
stop[0]
}
};
let ymin = if self.covers_angle(-FRAC_PI_2) {
ymin_pt[1]
} else {
if start[1] < stop[1] {
start[1]
} else {
stop[1]
}
};
let ymax = if self.covers_angle(FRAC_PI_2) {
ymax_pt[1]
} else {
if start[1] > stop[1] {
start[1]
} else {
stop[1]
}
};
return BoundingBox::new(xmin, xmax, ymin, ymax);
}
}
impl From<&ArcSegment> for CentroidData {
fn from(value: &ArcSegment) -> Self {
let inv_3 = 1.0 / 3.0;
let center = value.center();
let angle = value.offset_angle();
let radius = value.radius();
let x = (value.start()[0] + value.stop()[0]) * inv_3;
let y = (value.start()[1] + value.stop()[1]) * inv_3;
let area = 0.5 * (value.start()[0] * value.stop()[1] - value.stop()[0] * value.start()[1]);
let data_shape_1 = CentroidData { area, x, y };
let half_angle = 0.5 * angle;
let area = half_angle * radius.powi(2);
let centroid_transformed = [2.0 / 3.0 * radius * half_angle.sin() / half_angle, 0.0];
let middle_angle = value.start_angle() + 0.5 * value.offset_angle();
let t = Rotation2::new(middle_angle);
let mut centroid_movec_back = t * centroid_transformed;
centroid_movec_back.translate([center[0], center[1]]);
let data_shape_2 = Self {
area,
x: centroid_movec_back[0],
y: centroid_movec_back[1],
};
let x = (value.start()[0] + value.stop()[0] + center[0]) * inv_3;
let y = (value.start()[1] + value.stop()[1] + center[1]) * inv_3;
let area = 0.5
* ((value.stop()[0] - value.start()[0]) * (center[1] - value.start()[1])
- (center[0] - value.start()[0]) * (value.stop()[1] - value.start()[1]));
let data_shape_3 = Self { area, x, y };
return data_shape_1.union(&data_shape_2).subtract(&data_shape_3);
}
}
pub fn three_point_arc_center(
start: [f64; 2],
middle: [f64; 2],
stop: [f64; 2],
) -> crate::error::Result<[f64; 2]> {
fn determinant(m: [f64; 9]) -> f64 {
return m[0] * (m[4] * m[8] - m[5] * m[7]) - m[1] * (m[3] * m[8] - m[5] * m[6])
+ m[2] * (m[3] * m[7] - m[4] * m[6]);
}
if geometry_predicates::orient2d(start.into(), middle.into(), stop.into()) == 0.0 {
return Err(crate::error::ErrorType::Collinear([start, middle, stop]).into());
}
let start_sq = start[0].powi(2) + start[1].powi(2);
let middle_sq = middle[0].powi(2) + middle[1].powi(2);
let stop_sq = stop[0].powi(2) + stop[1].powi(2);
let m11_det = determinant([
start[0], start[1], 1.0, middle[0], middle[1], 1.0, stop[0], stop[1], 1.0,
]);
let m12_det = determinant([
start_sq, start[1], 1.0, middle_sq, middle[1], 1.0, stop_sq, stop[1], 1.0,
]);
let m13_det = determinant([
start_sq, start[0], 1.0, middle_sq, middle[0], 1.0, stop_sq, stop[0], 1.0,
]);
let x0 = 0.5 * m12_det / m11_det;
let y0 = -0.5 * m13_det / m11_det;
return Ok([x0, y0]);
}
pub fn three_point_arc_is_positive(start: [f64; 2], middle: [f64; 2], stop: [f64; 2]) -> bool {
let z = (stop[0] - start[0]) * (middle[1] - start[1])
- (stop[1] - start[1]) * (middle[0] - start[0]);
return z < 0.0;
}
pub fn calculate_offset_angle(start_angle: f64, stop_angle: f64, positive: bool) -> f64 {
let offset_angle: f64;
if positive {
if start_angle < stop_angle {
offset_angle = stop_angle - start_angle;
} else {
offset_angle = TAU - (start_angle - stop_angle);
}
} else {
if start_angle > stop_angle {
offset_angle = stop_angle - start_angle;
} else {
offset_angle = (stop_angle - start_angle) - TAU;
}
}
return offset_angle;
}