use super::{Point, PointLocation};
use crate::{Error, Orientation, PolygonScalar, TotalOrd};
use claim::debug_assert_ok;
use num_traits::*;
use rand::distributions::uniform::SampleUniform;
use rand::Rng;
#[derive(Debug, Clone)]
pub struct Triangle<T>([Point<T, 2>; 3]);
impl<T> Triangle<T>
where
T: PolygonScalar,
{
pub fn new(pts: [Point<T, 2>; 3]) -> Result<Triangle<T>, Error> {
let triangle = Triangle(pts);
triangle.validate()?;
Ok(triangle)
}
pub fn new_ccw(mut pts: [Point<T, 2>; 3]) -> Triangle<T> {
match Point::orient(&pts[0], &pts[1], &pts[2]) {
Orientation::CounterClockWise => Triangle(pts),
Orientation::ClockWise => {
pts.swap(0, 2);
Triangle(pts)
}
Orientation::CoLinear => panic!("Cannot orient colinear points."),
}
}
pub fn validate(&self) -> Result<(), Error> {
self.view().validate()
}
pub fn locate(&self, pt: &Point<T, 2>) -> PointLocation {
self.view().locate(pt)
}
pub fn view(&'_ self) -> TriangleView<'_, T> {
TriangleView([&self.0[0], &self.0[1], &self.0[2]])
}
}
pub struct TriangleView<'a, T>([&'a Point<T, 2>; 3]);
impl<'a, T> TriangleView<'a, T>
where
T: PolygonScalar,
{
pub fn new(pts: [&'a Point<T, 2>; 3]) -> Result<TriangleView<'a, T>, Error> {
let triangle = TriangleView(pts);
triangle.validate()?;
Ok(triangle)
}
pub fn new_ccw(pts: [&'a Point<T, 2>; 3]) -> TriangleView<'a, T> {
match Point::orient(pts[0], pts[1], pts[2]) {
Orientation::CounterClockWise => TriangleView(pts),
Orientation::ClockWise => TriangleView([pts[2], pts[1], pts[0]]),
Orientation::CoLinear => panic!("Cannot orient colinear points."),
}
}
pub fn new_unchecked(pts: [&'a Point<T, 2>; 3]) -> TriangleView<'a, T> {
TriangleView(pts)
}
pub fn validate(&self) -> Result<(), Error> {
if self.orientation() != Orientation::CounterClockWise {
Err(Error::ClockWiseViolation)
} else {
Ok(())
}
}
pub fn orientation(&self) -> Orientation {
let arr = &self.0;
Point::orient(arr[0], arr[1], arr[2])
}
pub fn locate(&self, pt: &Point<T, 2>) -> PointLocation {
use Orientation::*;
debug_assert_ok!(self.validate());
let [a, b, c] = self.0;
let ab = Point::orient(a, b, pt);
let bc = Point::orient(b, c, pt);
let ca = Point::orient(c, a, pt);
if ab == ClockWise || bc == ClockWise || ca == ClockWise {
PointLocation::Outside
} else if ab == CoLinear || bc == CoLinear || ca == CoLinear {
PointLocation::OnBoundary
} else {
PointLocation::Inside
}
}
pub fn signed_area<F>(&self) -> F
where
T: PolygonScalar + Into<F>,
F: NumOps<F, F> + FromPrimitive + Clone,
{
self.signed_area_2x::<F>() / F::from_usize(2).unwrap()
}
pub fn signed_area_2x<F>(&self) -> F
where
T: PolygonScalar + Into<F>,
F: NumOps<F, F> + Clone,
{
let [a, b, c] = self.0;
let ax = a.x_coord().clone().into();
let ay = a.y_coord().clone().into();
let bx = b.x_coord().clone().into();
let by = b.y_coord().clone().into();
let cx = c.x_coord().clone().into();
let cy = c.y_coord().clone().into();
ax.clone() * by.clone() - bx.clone() * ay.clone() + bx * cy.clone() - cx.clone() * by + cx * ay
- ax * cy
}
pub fn bounding_box(&self) -> (Point<T, 2>, Point<T, 2>) {
let min_x = self.0[0]
.x_coord()
.total_min(self.0[1].x_coord())
.total_min(self.0[2].x_coord())
.clone();
let max_x = self.0[0]
.x_coord()
.total_max(self.0[1].x_coord())
.total_max(self.0[2].x_coord())
.clone();
let min_y = self.0[0]
.y_coord()
.total_min(self.0[1].y_coord())
.total_min(self.0[2].y_coord())
.clone();
let max_y = self.0[0]
.y_coord()
.total_max(self.0[1].y_coord())
.total_max(self.0[2].y_coord())
.clone();
(Point::new([min_x, min_y]), Point::new([max_x, max_y]))
}
pub fn rejection_sampling<R>(&self, rng: &mut R) -> Point<T, 2>
where
R: Rng + ?Sized,
T: SampleUniform,
{
let (min, max) = self.bounding_box();
loop {
let [min_x, min_y] = min.array.clone();
let [max_x, max_y] = max.array.clone();
let x = rng.gen_range(min_x..=max_x);
let y = rng.gen_range(min_y..=max_y);
let pt = Point::new([x, y]);
if self.locate(&pt) != PointLocation::Outside {
return pt;
}
}
}
}