use crate::{HasPosition, LineSideInfo, Point2, SpadeNum};
use num_traits::{zero, Float};
#[derive(Copy, Clone, PartialOrd, Ord, PartialEq, Eq, Debug, Hash)]
pub struct PointProjection<S> {
factor: S,
length_2: S,
}
#[derive(Copy, Clone, PartialOrd, Ord, PartialEq, Eq, Debug, Hash)]
pub enum InsertionError {
TooSmall,
TooLarge,
NAN,
}
impl core::fmt::Display for InsertionError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
<Self as core::fmt::Debug>::fmt(self, f)
}
}
#[cfg(feature = "std")]
impl std::error::Error for InsertionError {}
pub const MIN_ALLOWED_VALUE: f64 = 1.793662034335766e-43; pub const MAX_ALLOWED_VALUE: f64 = 3.2138760885179806e60; pub fn validate_coordinate<S: SpadeNum>(value: S) -> Result<(), InsertionError> {
let as_f64: f64 = value.into();
if as_f64.is_nan() {
Err(InsertionError::NAN)
} else if as_f64.abs() < MIN_ALLOWED_VALUE && as_f64 != 0.0 {
Err(InsertionError::TooSmall)
} else if as_f64.abs() > MAX_ALLOWED_VALUE {
Err(InsertionError::TooLarge)
} else {
Ok(())
}
}
pub fn validate_vertex<V: HasPosition>(vertex: &V) -> Result<(), InsertionError> {
let position = vertex.position();
validate_coordinate(position.x)?;
validate_coordinate(position.y)?;
Ok(())
}
pub fn mitigate_underflow(position: Point2<f64>) -> Point2<f64> {
Point2::new(
mitigate_underflow_for_coordinate(position.x),
mitigate_underflow_for_coordinate(position.y),
)
}
fn mitigate_underflow_for_coordinate<S: SpadeNum>(coordinate: S) -> S {
if coordinate != S::zero() && coordinate.abs().into() < MIN_ALLOWED_VALUE {
S::zero()
} else {
coordinate
}
}
impl<S: SpadeNum> PointProjection<S> {
fn new(factor: S, length_2: S) -> Self {
Self { factor, length_2 }
}
pub fn is_before_edge(&self) -> bool {
self.factor < S::zero()
}
pub fn is_behind_edge(&self) -> bool {
self.factor > self.length_2
}
pub fn is_on_edge(&self) -> bool {
!self.is_before_edge() && !self.is_behind_edge()
}
pub fn reversed(&self) -> Self {
Self {
factor: self.length_2 - self.factor,
length_2: self.length_2,
}
}
}
impl<S: SpadeNum + Float> PointProjection<S> {
pub fn relative_position(&self) -> S {
if self.length_2 >= zero() {
self.factor / self.length_2
} else {
let l = -self.length_2;
let f = -self.factor;
(l - f) / l
}
}
}
pub fn nearest_point<S>(p1: Point2<S>, p2: Point2<S>, query_point: Point2<S>) -> Point2<S>
where
S: SpadeNum + Float,
{
let dir = p2.sub(p1);
let s = project_point(p1, p2, query_point);
if s.is_on_edge() {
let relative_position = s.relative_position();
p1.add(dir.mul(relative_position))
} else if s.is_before_edge() {
p1
} else {
p2
}
}
pub fn project_point<S>(p1: Point2<S>, p2: Point2<S>, query_point: Point2<S>) -> PointProjection<S>
where
S: SpadeNum,
{
let dir = p2.sub(p1);
PointProjection::new(query_point.sub(p1).dot(dir), dir.length2())
}
pub fn distance_2<S>(p1: Point2<S>, p2: Point2<S>, query_point: Point2<S>) -> S
where
S: SpadeNum + Float,
{
let nn = nearest_point(p1, p2, query_point);
query_point.sub(nn).length2()
}
fn to_robust_coord<S: SpadeNum>(point: Point2<S>) -> robust::Coord<S> {
robust::Coord {
x: point.x,
y: point.y,
}
}
pub fn contained_in_circumference<S>(
v1: Point2<S>,
v2: Point2<S>,
v3: Point2<S>,
p: Point2<S>,
) -> bool
where
S: SpadeNum,
{
let v1 = to_robust_coord(v1);
let v2 = to_robust_coord(v2);
let v3 = to_robust_coord(v3);
let p = to_robust_coord(p);
robust::incircle(v3, v2, v1, p) < 0.0
}
pub fn is_ordered_ccw<S>(p1: Point2<S>, p2: Point2<S>, query_point: Point2<S>) -> bool
where
S: SpadeNum,
{
let query = side_query(p1, p2, query_point);
query.is_on_left_side_or_on_line()
}
pub fn side_query<S>(p1: Point2<S>, p2: Point2<S>, query_point: Point2<S>) -> LineSideInfo
where
S: SpadeNum,
{
let p1 = to_robust_coord(p1);
let p2 = to_robust_coord(p2);
let query_point = to_robust_coord(query_point);
let result = robust::orient2d(p1, p2, query_point);
LineSideInfo::from_determinant(result)
}
fn side_query_inaccurate<S>(from: Point2<S>, to: Point2<S>, query_point: Point2<S>) -> LineSideInfo
where
S: SpadeNum,
{
let q = query_point;
let determinant = (to.x - from.x) * (q.y - from.y) - (to.y - from.y) * (q.x - from.x);
LineSideInfo::from_determinant(determinant.into())
}
pub(crate) fn intersects_edge_non_collinear<S>(
from0: Point2<S>,
to0: Point2<S>,
from1: Point2<S>,
to1: Point2<S>,
) -> bool
where
S: SpadeNum,
{
let other_from = side_query(from0, to0, from1);
let other_to = side_query(from0, to0, to1);
let self_from = side_query(from1, to1, from0);
let self_to = side_query(from1, to1, to0);
assert!(
![&other_from, &other_to, &self_from, &self_to]
.iter()
.all(|q| q.is_on_line()),
"intersects_edge_non_collinear: Given edge is collinear."
);
other_from != other_to && self_from != self_to
}
pub fn distance_2_triangle<S>(vertices: [Point2<S>; 3], query_point: Point2<S>) -> S
where
S: SpadeNum + Float,
{
for i in 0..3 {
let from = vertices[i];
let to = vertices[(i + 1) % 3];
if side_query_inaccurate(from, to, query_point).is_on_right_side() {
return distance_2(from, to, query_point);
}
}
S::zero()
}
pub fn circumcenter<S>(positions: [Point2<S>; 3]) -> (Point2<S>, S)
where
S: SpadeNum + Float,
{
let [v0, v1, v2] = positions;
let b = v1.sub(v0);
let c = v2.sub(v0);
let one = S::one();
let two = one + one;
let d = two * (b.x * c.y - c.x * b.y);
let len_b = b.dot(b);
let len_c = c.dot(c);
let d_inv: S = one / d;
let x = (len_b * c.y - len_c * b.y) * d_inv;
let y = (-len_b * c.x + len_c * b.x) * d_inv;
let result = Point2::new(x, y);
(result.add(v0), x * x + y * y)
}
pub fn triangle_area<S>(positions: [Point2<S>; 3]) -> S
where
S: SpadeNum,
{
let [v0, v1, v2] = positions;
let b = v1.sub(v0);
let c = v2.sub(v0);
(b.x * c.y - b.y * c.x).abs() * 0.5.into()
}
#[cfg(test)]
mod test {
use super::{mitigate_underflow_for_coordinate, validate_coordinate};
use crate::{InsertionError, Point2};
use approx::assert_relative_eq;
#[test]
fn test_point_projection() {
use super::project_point;
let from = Point2::new(1.0f64, 1.0);
let to = Point2::new(4.0, 5.0);
let normal = Point2::new(4.0, -3.0);
let projection = project_point(from, to, from);
let reversed = projection.reversed();
assert!(!projection.is_before_edge());
assert!(!projection.is_behind_edge());
assert!(!reversed.is_before_edge());
assert!(!reversed.is_behind_edge());
assert!(projection.is_on_edge());
assert!(reversed.is_on_edge());
assert_eq!(projection.relative_position(), 0.0);
assert_eq!(reversed.relative_position(), 1.0);
assert_eq!(projection, reversed.reversed());
let mid_point = Point2::new(2.5 + normal.x, 3.0 + normal.y);
let projection = project_point(from, to, mid_point);
assert!(projection.is_on_edge());
assert_eq!(projection.relative_position(), 0.5);
assert_eq!(projection.reversed().relative_position(), 0.5);
let fifth = Point2::new(0.8 * from.x + 0.2 * to.x, 0.8 * from.y + 0.2 * to.y);
let fifth = Point2::new(fifth.x + normal.x, fifth.y + normal.y);
let projection = project_point(from, to, fifth);
assert!(projection.is_on_edge());
assert_relative_eq!(projection.relative_position(), 0.2);
assert_relative_eq!(projection.reversed().relative_position(), 0.8);
let behind_point = Point2::new(0.0, 0.0);
let projection = project_point(from, to, behind_point);
let reversed = projection.reversed();
assert!(projection.is_before_edge());
assert!(reversed.is_behind_edge());
assert!(!projection.is_on_edge());
assert!(!reversed.is_on_edge());
}
#[test]
fn test_validate_coordinate() {
use super::{validate_coordinate, InsertionError::*};
assert_eq!(validate_coordinate(f64::NAN), Err(NAN));
let max_value = super::MAX_ALLOWED_VALUE;
assert_eq!(validate_coordinate(f64::INFINITY), Err(TooLarge));
assert_eq!(validate_coordinate(f64::NEG_INFINITY), Err(TooLarge));
assert_eq!(validate_coordinate(max_value * 2.0), Err(TooLarge));
let min_value = super::MIN_ALLOWED_VALUE;
assert_eq!(validate_coordinate(min_value / 2.0), Err(TooSmall));
let tiny_float = f32::MIN_POSITIVE;
assert_eq!(validate_coordinate(tiny_float), Ok(()));
let big_float = f32::MAX;
assert_eq!(validate_coordinate(big_float), Ok(()));
assert_eq!(validate_coordinate(min_value), Ok(()));
assert_eq!(validate_coordinate(0.0), Ok(()));
}
#[test]
fn test_mitigate_underflow() {
use float_next_after::NextAfter;
for number_under_test in [
0.0.next_after(f64::NEG_INFINITY),
0.0.next_after(f64::INFINITY),
super::MIN_ALLOWED_VALUE.next_after(f64::NEG_INFINITY),
(-super::MIN_ALLOWED_VALUE).next_after(f64::INFINITY),
] {
assert!(validate_coordinate(number_under_test).is_err());
let mitigated = mitigate_underflow_for_coordinate(number_under_test);
assert_ne!(mitigated, number_under_test);
assert_eq!(mitigated, 0.0);
}
assert_eq!(
validate_coordinate(mitigate_underflow_for_coordinate(f64::NAN)),
Err(InsertionError::NAN),
);
assert_eq!(
validate_coordinate(mitigate_underflow_for_coordinate(f64::INFINITY)),
Err(InsertionError::TooLarge),
);
}
#[test]
fn check_min_value() {
let mut expected = 1.0f64;
for _ in 0..142 {
expected *= 0.5;
}
assert_eq!(super::MIN_ALLOWED_VALUE, expected);
}
#[test]
fn check_max_value() {
let mut expected = 1.0f64;
for _ in 0..201 {
expected *= 2.0;
}
assert_eq!(super::MAX_ALLOWED_VALUE, expected);
}
#[test]
fn test_edge_distance() {
use super::distance_2;
let p1 = Point2::new(0.0, 0.0);
let p2 = Point2::new(1.0, 1.0);
assert_relative_eq!(distance_2(p1, p2, Point2::new(1.0, 0.0)), 0.5);
assert_relative_eq!(distance_2(p1, p2, Point2::new(0.0, 1.)), 0.5);
assert_relative_eq!(distance_2(p1, p2, Point2::new(-1.0, -1.0)), 2.0);
assert_relative_eq!(distance_2(p1, p2, Point2::new(2.0, 2.0)), 2.0);
}
#[test]
fn test_edge_side() {
use super::side_query;
let p1 = Point2::new(0.0, 0.0);
let p2 = Point2::new(1.0, 1.0);
assert!(side_query(p1, p2, Point2::new(1.0, 0.0)).is_on_right_side());
assert!(side_query(p1, p2, Point2::new(0.0, 1.0)).is_on_left_side());
assert!(side_query(p1, p2, Point2::new(0.5, 0.5)).is_on_line());
}
#[test]
fn test_intersects_middle() {
use super::intersects_edge_non_collinear;
let (f0, t0) = (Point2::new(0., 0.), Point2::new(5., 5.0));
let (f1, t1) = (Point2::new(-1.5, 1.), Point2::new(1.0, -1.5));
let (f2, t2) = (Point2::new(0.5, 4.), Point2::new(0.5, -4.));
assert!(!intersects_edge_non_collinear(f0, t0, f1, t1));
assert!(!intersects_edge_non_collinear(f1, t1, f0, t0));
assert!(intersects_edge_non_collinear(f0, t0, f2, t2));
assert!(intersects_edge_non_collinear(f2, t2, f0, t0));
assert!(intersects_edge_non_collinear(f1, t1, f2, t2));
assert!(intersects_edge_non_collinear(f2, t2, f1, t1));
}
#[test]
fn test_intersects_end_points() {
use super::intersects_edge_non_collinear;
let (f1, t1) = (Point2::new(0.33f64, 0.33f64), Point2::new(1.0, 0.0));
let (f2, t2) = (Point2::new(0.33, -1.0), Point2::new(0.33, 1.0));
assert!(intersects_edge_non_collinear(f1, t1, f2, t2));
assert!(intersects_edge_non_collinear(f2, t2, f1, t1));
let (f3, t3) = (Point2::new(0.0, -1.0), Point2::new(2.0, 1.0));
assert!(intersects_edge_non_collinear(f1, t1, f3, t3));
assert!(intersects_edge_non_collinear(f3, t3, f1, t1));
let (f4, t4) = (Point2::new(0.33, 0.33), Point2::new(0.0, 2.0));
assert!(intersects_edge_non_collinear(f1, t1, f4, t4));
assert!(intersects_edge_non_collinear(f4, t4, f1, t1));
}
#[test]
#[should_panic]
fn test_collinear_fail() {
use super::intersects_edge_non_collinear;
let (f1, t1) = (Point2::new(1.0, 2.0), Point2::new(3.0, 3.0));
let (f2, t2) = (Point2::new(-1.0, 1.0), Point2::new(-3.0, 0.0));
intersects_edge_non_collinear(f1, t1, f2, t2);
}
#[test]
fn test_triangle_distance() {
use super::distance_2_triangle;
let v1 = Point2::new(0., 0.);
let v2 = Point2::new(1., 0.);
let v3 = Point2::new(0., 1.);
let t = [v1, v2, v3];
assert_eq!(distance_2_triangle(t, Point2::new(0.25, 0.25)), 0.);
assert_relative_eq!(distance_2_triangle(t, Point2::new(-1., -1.)), 2.);
assert_relative_eq!(distance_2_triangle(t, Point2::new(0., -1.)), 1.);
assert_relative_eq!(distance_2_triangle(t, Point2::new(-1., 0.)), 1.);
assert_relative_eq!(distance_2_triangle(t, Point2::new(1., 1.)), 0.5);
assert_relative_eq!(distance_2_triangle(t, Point2::new(0.5, 0.5)), 0.0);
assert!(distance_2_triangle(t, Point2::new(0.6, 0.6)) > 0.001);
}
#[test]
fn test_contained_in_circumference() {
use super::contained_in_circumference;
let (a1, a2, a3) = (3f64, 2f64, 1f64);
let offset = Point2::new(0.5, 0.7);
let v1 = Point2::new(a1.sin(), a1.cos()).mul(2.).add(offset);
let v2 = Point2::new(a2.sin(), a2.cos()).mul(2.).add(offset);
let v3 = Point2::new(a3.sin(), a3.cos()).mul(2.).add(offset);
assert!(super::side_query(v1, v2, v3).is_on_left_side());
assert!(contained_in_circumference(v1, v2, v3, offset));
let shrunk = v1.sub(offset).mul(0.9).add(offset);
assert!(contained_in_circumference(v1, v2, v3, shrunk));
let expanded = v1.sub(offset).mul(1.1).add(offset);
assert!(!contained_in_circumference(v1, v2, v3, expanded));
assert!(!contained_in_circumference(
v1,
v2,
v3,
Point2::new(2.0 + offset.x, 2.0 + offset.y)
));
assert!(contained_in_circumference(
Point2::new(0f64, 0f64),
Point2::new(0f64, -1f64),
Point2::new(1f64, 0f64),
Point2::new(0f64, -0.5f64)
));
}
}