use std::cmp::Ordering;
use crate::data::Vector;
use crate::PolygonScalar;
#[derive(PartialEq, Eq, PartialOrd, Ord, Debug, Copy, Clone)]
pub enum Orientation {
CounterClockWise,
ClockWise,
CoLinear,
}
use Orientation::*;
#[derive(PartialEq, Eq, PartialOrd, Ord, Debug, Copy, Clone)]
pub enum SoS {
CounterClockWise,
ClockWise,
}
impl Orientation {
pub fn new<T>(p1: &[T; 2], p2: &[T; 2], p3: &[T; 2]) -> Orientation
where
T: PolygonScalar,
{
match T::cmp_slope(p1, p2, p3) {
Ordering::Less => Orientation::ClockWise,
Ordering::Equal => Orientation::CoLinear,
Ordering::Greater => Orientation::CounterClockWise,
}
}
pub fn along_vector<T>(p1: &[T; 2], vector: &Vector<T, 2>, p2: &[T; 2]) -> Orientation
where
T: PolygonScalar,
{
match T::cmp_vector_slope(&vector.0, p1, p2) {
Ordering::Less => Orientation::ClockWise,
Ordering::Equal => Orientation::CoLinear,
Ordering::Greater => Orientation::CounterClockWise,
}
}
pub fn along_perp_vector<T>(p1: &[T; 2], vector: &Vector<T, 2>, p2: &[T; 2]) -> Orientation
where
T: PolygonScalar,
{
match T::cmp_perp_vector_slope(&vector.0, p1, p2) {
Ordering::Less => Orientation::ClockWise,
Ordering::Equal => Orientation::CoLinear,
Ordering::Greater => Orientation::CounterClockWise,
}
}
pub fn is_colinear(self) -> bool {
matches!(self, Orientation::CoLinear)
}
pub fn is_ccw(self) -> bool {
matches!(self, Orientation::CounterClockWise)
}
pub fn is_cw(self) -> bool {
matches!(self, Orientation::ClockWise)
}
#[must_use]
pub fn then(self, other: Orientation) -> Orientation {
match self {
Orientation::CoLinear => other,
_ => self,
}
}
pub fn break_ties(self, a: u32, b: u32, c: u32) -> SoS {
match self {
CounterClockWise => SoS::CounterClockWise,
ClockWise => SoS::ClockWise,
CoLinear => SoS::new(a, b, c),
}
}
pub fn sos(self, other: SoS) -> SoS {
match self {
CounterClockWise => SoS::CounterClockWise,
ClockWise => SoS::ClockWise,
CoLinear => other,
}
}
#[must_use]
pub fn reverse(self) -> Orientation {
match self {
Orientation::CounterClockWise => Orientation::ClockWise,
Orientation::ClockWise => Orientation::CounterClockWise,
Orientation::CoLinear => Orientation::CoLinear,
}
}
pub fn ccw_cmp_around_with<T>(
vector: &Vector<T, 2>,
p1: &[T; 2],
p2: &[T; 2],
p3: &[T; 2],
) -> Ordering
where
T: PolygonScalar,
{
let aq = Orientation::along_vector(p1, vector, p2);
let ar = Orientation::along_vector(p1, vector, p3);
let on_zero = |d: &[T; 2]| match Orientation::along_perp_vector(p1, vector, d) {
CounterClockWise => false,
ClockWise => true,
CoLinear => true,
};
let cmp = || match Orientation::new(p1, p2, p3) {
CounterClockWise => Ordering::Less,
ClockWise => Ordering::Greater,
CoLinear => Ordering::Equal,
};
match (aq, ar) {
(CounterClockWise, ClockWise) => Ordering::Less,
(ClockWise, CounterClockWise) => Ordering::Greater,
(CoLinear, ClockWise) => Ordering::Less,
(ClockWise, CoLinear) => Ordering::Greater,
(CounterClockWise, CounterClockWise) => cmp(),
(ClockWise, ClockWise) => cmp(),
(CounterClockWise, CoLinear) => {
if on_zero(p3) {
Ordering::Greater } else {
Ordering::Less }
}
(CoLinear, CounterClockWise) => {
if on_zero(p2) {
Ordering::Less
} else {
Ordering::Greater
}
}
(CoLinear, CoLinear) => match (on_zero(p2), on_zero(p3)) {
(true, true) => Ordering::Equal,
(false, false) => Ordering::Equal,
(true, false) => Ordering::Less,
(false, true) => Ordering::Greater,
},
}
}
}
impl SoS {
pub fn new(a: u32, b: u32, c: u32) -> SoS {
assert_ne!(a, b);
assert_ne!(b, c);
assert_ne!(c, a);
let ab = a < b;
let ac = a < c;
let cb = c < b;
if ab ^ ac ^ cb {
SoS::ClockWise
} else {
SoS::CounterClockWise
}
}
pub fn orient(self) -> Orientation {
match self {
SoS::CounterClockWise => Orientation::CounterClockWise,
SoS::ClockWise => Orientation::ClockWise,
}
}
#[must_use]
pub fn reverse(self) -> SoS {
match self {
SoS::CounterClockWise => SoS::ClockWise,
SoS::ClockWise => SoS::CounterClockWise,
}
}
}
#[cfg(test)]
#[cfg(not(tarpaulin_include))]
mod tests {
use super::*;
use crate::data::Point;
use num::BigInt;
use proptest::prelude::*;
use test_strategy::proptest;
#[test]
fn orientation_limit_1() {
PolygonScalar::cmp_slope(
&[i8::MAX, i8::MAX],
&[i8::MIN, i8::MIN],
&[i8::MIN, i8::MIN],
);
}
#[test]
fn cmp_slope_1() {
assert_eq!(
PolygonScalar::cmp_slope(&[0i8, 0], &[1, 1], &[2, 2],),
Ordering::Equal
);
}
#[test]
fn cmp_slope_2() {
assert_eq!(
Orientation::new(&[0i8, 0], &[0, 1], &[2, 2],),
Orientation::ClockWise
);
}
#[test]
fn orientation_limit_2() {
let options = &[i8::MIN, i8::MAX, 0, -10, 10];
for [a, b, c, d, e, f] in crate::utils::permutations([options; 6]) {
PolygonScalar::cmp_slope(&[a, b], &[c, d], &[e, f]);
}
}
#[test]
fn cmp_around_1() {
use num_bigint::*;
let pt1 = [BigInt::from(0), BigInt::from(0)];
let pt2 = [BigInt::from(-1), BigInt::from(1)];
let vector = Vector([BigInt::from(1), BigInt::from(0)]);
assert_eq!(
Orientation::ccw_cmp_around_with(&vector, &pt1, &pt2, &pt1),
Ordering::Greater
);
}
#[test]
fn sos_unit1() {
assert_eq!(SoS::new(0, 1, 2), SoS::CounterClockWise)
}
#[test]
#[should_panic]
fn sos_unit2() {
SoS::new(0, 0, 1);
}
#[test]
fn sos_unit3() {
assert_eq!(SoS::new(99, 0, 1), SoS::CounterClockWise);
}
#[proptest]
fn sos_eq_prop(a: u8, b: u8, c: u8) {
if a != b && b != c && c != a {
let (a, b, c) = (a as u32, b as u32, c as u32);
let one = &BigInt::from(1);
let big_a = BigInt::from(a);
let big_b = BigInt::from(b);
let big_c = BigInt::from(c);
let p = Point::new([big_a, one << a]);
let q = Point::new([big_b, one << b]);
let r = Point::new([big_c, one << c]);
prop_assert_eq!(SoS::new(a, b, c).orient(), Orientation::new(&p, &q, &r));
}
}
#[proptest]
fn sos_rev_prop(a: u32, b: u32, c: u32) {
if a != b && b != c && c != a {
prop_assert_eq!(SoS::new(a, b, c), SoS::new(c, b, a).reverse());
prop_assert_eq!(SoS::new(a, b, c), SoS::new(a, c, b).reverse());
prop_assert_eq!(SoS::new(a, b, c), SoS::new(b, a, c).reverse());
prop_assert_eq!(SoS::new(a, b, c), SoS::new(b, c, a));
}
}
}