use crate::{
base_math::{
angle, angle_is_within_sweep, bulge_from_angle, delta_angle, dist_squared,
line_seg_closest_point, midpoint, min_max, point_on_circle, point_within_arc_sweep,
},
PlineVertex, Real, Vector2, AABB,
};
pub fn seg_arc_radius_and_center<T>(v1: PlineVertex<T>, v2: PlineVertex<T>) -> (T, Vector2<T>)
where
T: Real,
{
debug_assert!(!v1.bulge_is_zero(), "v1 to v2 must be an arc");
debug_assert!(!v1.pos().fuzzy_eq(v2.pos()), "v1 must not be on top of v2");
let abs_bulge = v1.bulge.abs();
let chord_v = v2.pos() - v1.pos();
let chord_len = chord_v.length();
let radius = chord_len * (abs_bulge * abs_bulge + T::one()) / (T::four() * abs_bulge);
let s = abs_bulge * chord_len / T::two();
let m = radius - s;
let mut offs_x = -m * chord_v.y / chord_len;
let mut offs_y = m * chord_v.x / chord_len;
if v1.bulge_is_neg() {
offs_x = -offs_x;
offs_y = -offs_y;
}
let center = Vector2::new(
v1.x + chord_v.x / T::two() + offs_x,
v1.y + chord_v.y / T::two() + offs_y,
);
(radius, center)
}
#[derive(Debug, Copy, Clone)]
pub struct SplitResult<T = f64>
where
T: Real,
{
pub updated_start: PlineVertex<T>,
pub split_vertex: PlineVertex<T>,
}
pub fn seg_split_at_point<T>(
v1: PlineVertex<T>,
v2: PlineVertex<T>,
point_on_seg: Vector2<T>,
pos_equal_eps: T,
) -> SplitResult<T>
where
T: Real,
{
if v1.bulge_is_zero() {
let updated_start = v1;
let split_vertex = PlineVertex::new(point_on_seg.x, point_on_seg.y, T::zero());
return SplitResult {
updated_start,
split_vertex,
};
}
if v1.pos().fuzzy_eq_eps(v2.pos(), pos_equal_eps)
|| v1.pos().fuzzy_eq_eps(point_on_seg, pos_equal_eps)
{
let updated_start = PlineVertex::new(point_on_seg.x, point_on_seg.y, T::zero());
let split_vertex = PlineVertex::new(point_on_seg.x, point_on_seg.y, v1.bulge);
return SplitResult {
updated_start,
split_vertex,
};
}
if v2.pos().fuzzy_eq_eps(point_on_seg, pos_equal_eps) {
let updated_start = v1;
let split_vertex = PlineVertex::new(v2.x, v2.y, T::zero());
return SplitResult {
updated_start,
split_vertex,
};
}
let (_, arc_center) = seg_arc_radius_and_center(v1, v2);
let point_pos_angle = angle(arc_center, point_on_seg);
let arc_start_angle = angle(arc_center, v1.pos());
let theta1 = delta_angle(arc_start_angle, point_pos_angle);
let bulge1 = bulge_from_angle(theta1);
let arc_end_angle = angle(arc_center, v2.pos());
let theta2 = delta_angle(point_pos_angle, arc_end_angle);
let bulge2 = bulge_from_angle(theta2);
let updated_start = PlineVertex::new(v1.x, v1.y, bulge1);
let split_vertex = PlineVertex::new(point_on_seg.x, point_on_seg.y, bulge2);
SplitResult {
updated_start,
split_vertex,
}
}
pub fn seg_tangent_vector<T>(
v1: PlineVertex<T>,
v2: PlineVertex<T>,
point_on_seg: Vector2<T>,
) -> Vector2<T>
where
T: Real,
{
if v1.bulge_is_zero() {
return v2.pos() - v1.pos();
}
let (_, arc_center) = seg_arc_radius_and_center(v1, v2);
if v1.bulge_is_pos() {
return Vector2::new(
-(point_on_seg.y - arc_center.y),
point_on_seg.x - arc_center.x,
);
}
Vector2::new(
point_on_seg.y - arc_center.y,
-(point_on_seg.x - arc_center.x),
)
}
pub fn seg_closest_point<T>(v1: PlineVertex<T>, v2: PlineVertex<T>, point: Vector2<T>) -> Vector2<T>
where
T: Real,
{
if v1.bulge_is_zero() {
return line_seg_closest_point(v1.pos(), v2.pos(), point);
}
let (arc_radius, arc_center) = seg_arc_radius_and_center(v1, v2);
if point.fuzzy_eq(arc_center) {
return v1.pos();
}
if point_within_arc_sweep(arc_center, v1.pos(), v2.pos(), v1.bulge_is_neg(), point) {
let v_to_point = (point - arc_center).normalize();
return v_to_point.scale(arc_radius) + arc_center;
}
let dist1 = dist_squared(v1.pos(), point);
let dist2 = dist_squared(v2.pos(), point);
if dist1 < dist2 {
return v1.pos();
}
v2.pos()
}
pub fn seg_fast_approx_bounding_box<T>(v1: PlineVertex<T>, v2: PlineVertex<T>) -> AABB<T>
where
T: Real,
{
if v1.bulge_is_zero() {
let (min_x, max_x) = min_max(v1.x, v2.x);
let (min_y, max_y) = min_max(v1.y, v2.y);
return AABB::new(min_x, min_y, max_x, max_y);
}
let b = v1.bulge;
let offs_x = b * (v2.y - v1.y) / T::two();
let offs_y = -b * (v2.x - v1.x) / T::two();
let (pt_x_min, pt_x_max) = min_max(v1.x + offs_x, v2.x + offs_x);
let (pt_y_min, pt_y_max) = min_max(v1.y + offs_y, v2.y + offs_y);
let (end_point_x_min, end_point_x_max) = min_max(v1.x, v2.x);
let (end_point_y_min, end_point_y_max) = min_max(v1.y, v2.y);
let min_x = num_traits::real::Real::min(end_point_x_min, pt_x_min);
let min_y = num_traits::real::Real::min(end_point_y_min, pt_y_min);
let max_x = num_traits::real::Real::max(end_point_x_max, pt_x_max);
let max_y = num_traits::real::Real::max(end_point_y_max, pt_y_max);
AABB::new(min_x, min_y, max_x, max_y)
}
pub(crate) fn arc_seg_bounding_box<T>(v1: PlineVertex<T>, v2: PlineVertex<T>) -> AABB<T>
where
T: Real,
{
debug_assert!(!v1.bulge_is_zero(), "expected arc");
let (arc_radius, arc_center) = seg_arc_radius_and_center(v1, v2);
let start_angle = angle(arc_center, v1.pos());
let end_angle = angle(arc_center, v2.pos());
let sweep_angle = delta_angle(start_angle, end_angle);
let crosses_angle = |angle| angle_is_within_sweep(angle, start_angle, sweep_angle);
let min_x = if crosses_angle(T::pi()) {
arc_center.x - arc_radius
} else {
num_traits::real::Real::min(v1.x, v2.x)
};
let min_y = if crosses_angle(T::from(1.5).unwrap() * T::pi()) {
arc_center.y - arc_radius
} else {
num_traits::real::Real::min(v1.y, v2.y)
};
let max_x = if crosses_angle(T::zero()) {
arc_center.x + arc_radius
} else {
num_traits::real::Real::max(v1.x, v2.x)
};
let max_y = if crosses_angle(T::from(0.5).unwrap() * T::pi()) {
arc_center.y + arc_radius
} else {
num_traits::real::Real::max(v1.y, v2.y)
};
AABB::new(min_x, min_y, max_x, max_y)
}
pub fn seg_bounding_box<T>(v1: PlineVertex<T>, v2: PlineVertex<T>) -> AABB<T>
where
T: Real,
{
if v1.bulge_is_zero() {
let (min_x, max_x) = min_max(v1.x, v2.x);
let (min_y, max_y) = min_max(v1.y, v2.y);
AABB::new(min_x, min_y, max_x, max_y)
} else {
arc_seg_bounding_box(v1, v2)
}
}
pub fn seg_length<T>(v1: PlineVertex<T>, v2: PlineVertex<T>) -> T
where
T: Real,
{
if v1.fuzzy_eq(v2) {
return T::zero();
}
if v1.bulge_is_zero() {
return dist_squared(v1.pos(), v2.pos()).sqrt();
}
let (arc_radius, arc_center) = seg_arc_radius_and_center(v1, v2);
let start_angle = angle(arc_center, v1.pos());
let end_angle = angle(arc_center, v2.pos());
arc_radius * delta_angle(start_angle, end_angle).abs()
}
pub fn seg_midpoint<T>(v1: PlineVertex<T>, v2: PlineVertex<T>) -> Vector2<T>
where
T: Real,
{
if v1.bulge_is_zero() {
return midpoint(v1.pos(), v2.pos());
}
let (arc_radius, arc_center) = seg_arc_radius_and_center(v1, v2);
let angle1 = angle(arc_center, v1.pos());
let angle2 = angle(arc_center, v2.pos());
let angle_offset = delta_angle(angle1, angle2).abs() / T::two();
let mid_angle = if v1.bulge_is_pos() {
angle1 + angle_offset
} else {
angle1 - angle_offset
};
point_on_circle(arc_radius, arc_center, mid_angle)
}