use crate::{Real, Vector2};
pub fn min_max<T>(v1: T, v2: T) -> (T, T)
where
T: PartialOrd,
{
if v1 < v2 {
(v1, v2)
} else {
(v2, v1)
}
}
pub fn normalize_radians<T>(angle: T) -> T
where
T: Real,
{
if angle >= T::zero() && angle <= T::tau() {
return angle;
}
angle - (angle / T::tau()).floor() * T::tau()
}
pub fn delta_angle<T>(angle1: T, angle2: T) -> T
where
T: Real,
{
let mut diff = normalize_radians(angle2 - angle1);
if diff > T::pi() {
diff = diff - T::tau();
}
diff
}
pub fn angle_is_between_eps<T>(test_angle: T, start_angle: T, end_angle: T, epsilon: T) -> bool
where
T: Real,
{
let end_sweep = normalize_radians(end_angle - start_angle);
let mid_sweep = normalize_radians(test_angle - start_angle);
mid_sweep < end_sweep + epsilon
}
pub fn angle_is_between<T>(test_angle: T, start_angle: T, end_angle: T) -> bool
where
T: Real,
{
angle_is_between_eps(test_angle, start_angle, end_angle, T::fuzzy_epsilon())
}
pub fn angle_is_within_sweep_eps<T>(
test_angle: T,
start_angle: T,
sweep_angle: T,
epsilon: T,
) -> bool
where
T: Real,
{
let end_angle = start_angle + sweep_angle;
if sweep_angle < T::zero() {
return angle_is_between_eps(test_angle, end_angle, start_angle, epsilon);
}
angle_is_between_eps(test_angle, start_angle, end_angle, epsilon)
}
pub fn angle_is_within_sweep<T>(test_angle: T, start_angle: T, sweep_angle: T) -> bool
where
T: Real,
{
angle_is_within_sweep_eps(test_angle, start_angle, sweep_angle, T::fuzzy_epsilon())
}
pub fn quadratic_solutions<T>(a: T, b: T, c: T, discriminant: T) -> (T, T)
where
T: Real,
{
debug_assert!(
(b * b - T::four() * a * c).fuzzy_eq(discriminant),
"discriminant is not valid"
);
let sqrt_discr = discriminant.sqrt();
let denom = T::two() * a;
let sol1 = if b < T::zero() {
(-b + sqrt_discr) / denom
} else {
(-b - sqrt_discr) / denom
};
let sol2 = (c / a) / sol1;
(sol1, sol2)
}
pub fn dist_squared<T>(p0: Vector2<T>, p1: Vector2<T>) -> T
where
T: Real,
{
let d = p0 - p1;
d.dot(d)
}
pub fn angle<T>(p0: Vector2<T>, p1: Vector2<T>) -> T
where
T: Real,
{
T::atan2(p1.y - p0.y, p1.x - p0.x)
}
pub fn midpoint<T>(p0: Vector2<T>, p1: Vector2<T>) -> Vector2<T>
where
T: Real,
{
Vector2::new((p0.x + p1.x) / T::two(), (p0.y + p1.y) / T::two())
}
pub fn point_on_circle<T>(radius: T, center: Vector2<T>, angle: T) -> Vector2<T>
where
T: Real,
{
let (s, c) = angle.sin_cos();
Vector2::new(center.x + radius * c, center.y + radius * s)
}
pub fn point_from_parametric<T>(p0: Vector2<T>, p1: Vector2<T>, t: T) -> Vector2<T>
where
T: Real,
{
p0 + (p1 - p0).scale(t)
}
pub fn parametric_from_point<T>(p0: Vector2<T>, p1: Vector2<T>, point: Vector2<T>) -> T
where
T: Real,
{
if p0.x.fuzzy_eq(p1.x) {
debug_assert!(
point.x.fuzzy_eq(p0.x),
"point does not lie on the line defined by p0 to p1"
);
(point.y - p0.y) / (p1.y - p0.y)
} else {
debug_assert!(
point.fuzzy_eq(p0)
|| ((point.y - p0.y) / (point.x - p0.x)).fuzzy_eq((p1.y - p0.y) / (p1.x - p0.x)),
"point does not lie on the line defined by p0 to p1"
);
(point.x - p0.x) / (p1.x - p0.x)
}
}
pub fn line_seg_closest_point<T>(p0: Vector2<T>, p1: Vector2<T>, point: Vector2<T>) -> Vector2<T>
where
T: Real,
{
let v = p1 - p0;
let w = point - p0;
let c1 = w.dot(v);
if c1 < T::fuzzy_epsilon() {
return p0;
}
let c2 = v.length_squared();
if c2 < c1 + T::fuzzy_epsilon() {
return p1;
}
let b = c1 / c2;
p0 + v.scale(b)
}
fn perp_dot_test_value<T>(p0: Vector2<T>, p1: Vector2<T>, point: Vector2<T>) -> T
where
T: Real,
{
(p1.x - p0.x) * (point.y - p0.y) - (p1.y - p0.y) * (point.x - p0.x)
}
pub fn is_left<T>(p0: Vector2<T>, p1: Vector2<T>, point: Vector2<T>) -> bool
where
T: Real,
{
perp_dot_test_value(p0, p1, point) > T::zero()
}
pub fn is_left_or_equal<T>(p0: Vector2<T>, p1: Vector2<T>, point: Vector2<T>) -> bool
where
T: Real,
{
perp_dot_test_value(p0, p1, point) >= T::zero()
}
pub fn is_left_or_coincident_eps<T>(
p0: Vector2<T>,
p1: Vector2<T>,
point: Vector2<T>,
epsilon: T,
) -> bool
where
T: Real,
{
debug_assert!(epsilon > T::zero());
perp_dot_test_value(p0, p1, point) > -epsilon
}
pub fn is_left_or_coincident<T>(p0: Vector2<T>, p1: Vector2<T>, point: Vector2<T>) -> bool
where
T: Real,
{
is_left_or_coincident_eps(p0, p1, point, T::fuzzy_epsilon())
}
pub fn is_right_or_coincident_eps<T>(
p0: Vector2<T>,
p1: Vector2<T>,
point: Vector2<T>,
epsilon: T,
) -> bool
where
T: Real,
{
debug_assert!(epsilon > T::zero());
perp_dot_test_value(p0, p1, point) < epsilon
}
pub fn is_right_or_coincident<T>(p0: Vector2<T>, p1: Vector2<T>, point: Vector2<T>) -> bool
where
T: Real,
{
is_right_or_coincident_eps(p0, p1, point, T::fuzzy_epsilon())
}
pub fn point_within_arc_sweep<T>(
center: Vector2<T>,
arc_start: Vector2<T>,
arc_end: Vector2<T>,
is_clockwise: bool,
point: Vector2<T>,
) -> bool
where
T: Real,
{
if is_clockwise {
is_right_or_coincident(center, arc_start, point)
&& is_left_or_coincident(center, arc_end, point)
} else {
is_left_or_coincident(center, arc_start, point)
&& is_right_or_coincident(center, arc_end, point)
}
}
#[inline]
pub fn bulge_from_angle<T>(angle: T) -> T
where
T: Real,
{
(angle / T::four()).tan()
}
#[inline]
pub fn angle_from_bulge<T>(bulge: T) -> T
where
T: Real,
{
T::four() * bulge.atan()
}