use crate::{AABB, Bounded, Point, VectorMetricSquared};
use num_traits::Float;
#[derive(Debug, PartialEq, Clone, Copy)]
pub struct Triangle<T, const N: usize> {
vertices: [Point<T, N>; 3],
}
impl<T, const N: usize> Triangle<T, N>
where
T: Float + std::iter::Sum,
{
pub fn new(vertices: [Point<T, N>; 3]) -> Self {
Self { vertices }
}
#[inline]
pub fn a(&self) -> Point<T, N> {
self.vertices[0]
}
#[inline]
pub fn a_ref(&self) -> &Point<T, N> {
&self.vertices[0]
}
#[inline]
pub fn b(&self) -> Point<T, N> {
self.vertices[1]
}
#[inline]
pub fn b_ref(&self) -> &Point<T, N> {
&self.vertices[1]
}
#[inline]
pub fn c(&self) -> Point<T, N> {
self.vertices[2]
}
#[inline]
pub fn c_ref(&self) -> &Point<T, N> {
&self.vertices[2]
}
#[inline]
pub fn a_ref_mut(&mut self) -> &mut Point<T, N> {
&mut self.vertices[0]
}
#[inline]
pub fn b_ref_mut(&mut self) -> &mut Point<T, N> {
&mut self.vertices[1]
}
#[inline]
pub fn c_ref_mut(&mut self) -> &mut Point<T, N> {
&mut self.vertices[2]
}
#[inline]
pub fn vertices(&self) -> (Point<T, N>, Point<T, N>, Point<T, N>) {
(self.a(), self.b(), self.c())
}
pub fn centroid(&self) -> Point<T, N> {
let (a, b, c) = self.vertices();
let mut coords = [T::zero(); N];
let three = T::from(3.0).unwrap();
for i in 0..N {
coords[i] = (a.coords_ref()[i] + b.coords_ref()[i] + c.coords_ref()[i]) / three;
}
Point::new(coords)
}
pub fn area(&self) -> T {
let two = T::one() + T::one();
let (a, b, c) = self.vertices();
let (u, v) = (b - a, c - a);
let u_dot_v = u.dot(&v);
let mag_u = u.magnitude_squared();
let mag_v = v.magnitude_squared();
(mag_u * mag_v - u_dot_v * u_dot_v).sqrt() / two
}
}
impl<T, const N: usize> From<(Point<T, N>, Point<T, N>, Point<T, N>)> for Triangle<T, N>
where
T: Float + std::iter::Sum,
{
fn from(value: (Point<T, N>, Point<T, N>, Point<T, N>)) -> Self {
Self {
vertices: [value.0, value.1, value.2],
}
}
}
impl<T, const N: usize> From<[Point<T, N>; 3]> for Triangle<T, N>
where
T: Float + std::iter::Sum,
{
fn from(value: [Point<T, N>; 3]) -> Self {
Self { vertices: value }
}
}
impl<T, const N: usize> Bounded<T, N> for Triangle<T, N>
where
T: Float + std::iter::Sum,
{
fn aabb(&self) -> AABB<T, N> {
let (a, b, c) = self.vertices();
let min_coords = std::array::from_fn(|i| a.coords_ref()[i].min(b.coords_ref()[i]).min(c.coords_ref()[i]));
let max_coords = std::array::from_fn(|i| a.coords_ref()[i].max(b.coords_ref()[i]).max(c.coords_ref()[i]));
AABB::new(Point::new(min_coords), Point::new(max_coords))
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::Bounded;
use approx::assert_relative_eq;
#[test]
fn test_triangle_construction_and_accessors() {
let a = Point::new([0.0, 0.0]);
let b = Point::new([1.0, 0.0]);
let c = Point::new([0.0, 1.0]);
let tri = Triangle::new([a, b, c]);
assert_eq!(tri.a(), a);
assert_eq!(tri.b(), b);
assert_eq!(tri.c(), c);
let from_tuple: Triangle<f64, 2> = Triangle::from((a, b, c));
assert_eq!(from_tuple.a(), a);
assert_eq!(from_tuple.b(), b);
assert_eq!(from_tuple.c(), c);
let from_arr: Triangle<f64, 2> = Triangle::from([a, b, c]);
assert_eq!(from_arr.a(), a);
}
#[test]
fn test_triangle_aabb_2d() {
let tri = Triangle::new([
Point::new([0.0, 0.0]),
Point::new([4.0, 0.0]),
Point::new([0.0, 3.0]),
]);
let aabb = tri.aabb();
assert_eq!(aabb.min_ref().coords_ref()[0], 0.0);
assert_eq!(aabb.min_ref().coords_ref()[1], 0.0);
assert_eq!(aabb.max_ref().coords_ref()[0], 4.0);
assert_eq!(aabb.max_ref().coords_ref()[1], 3.0);
}
#[test]
fn test_triangle_aabb_3d() {
let tri = Triangle::new([
Point::new([0.0, 0.0, 0.0]),
Point::new([2.0, 0.0, 0.0]),
Point::new([0.0, 2.0, 0.0]),
]);
let aabb = tri.aabb();
assert_eq!(aabb.min_ref().coords_ref(), &[0.0, 0.0, 0.0]);
assert_eq!(aabb.max_ref().coords_ref(), &[2.0, 2.0, 0.0]);
}
#[test]
fn test_triangle_centroid_2d() {
let tri = Triangle::new([
Point::new([0.0, 0.0]),
Point::new([2.0, 0.0]),
Point::new([0.0, 2.0]),
]);
let c = tri.centroid();
let two_thirds = 2.0 / 3.0;
assert_relative_eq!(c.coords_ref()[0], two_thirds);
assert_relative_eq!(c.coords_ref()[1], two_thirds);
}
#[test]
fn test_triangle_centroid_3d() {
let tri = Triangle::new([
Point::new([1.0, 0.0, 0.0]),
Point::new([0.0, 1.0, 0.0]),
Point::new([0.0, 0.0, 1.0]),
]);
let c = tri.centroid();
let third = 1.0 / 3.0;
assert_relative_eq!(c.coords_ref()[0], third);
assert_relative_eq!(c.coords_ref()[1], third);
assert_relative_eq!(c.coords_ref()[2], third);
}
#[test]
fn test_triangle_area_2d() {
let tri = Triangle::new([
Point::new([0.0, 0.0]),
Point::new([1.0, 0.0]),
Point::new([0.0, 1.0]),
]);
assert_relative_eq!(tri.area(), 0.5);
}
#[test]
fn test_triangle_area_2d_general() {
let tri = Triangle::new([
Point::new([0.0, 0.0]),
Point::new([4.0, 0.0]),
Point::new([0.0, 3.0]),
]);
assert_relative_eq!(tri.area(), 6.0);
}
#[test]
fn test_triangle_area_3d() {
let tri = Triangle::new([
Point::new([0.0, 0.0, 0.0]),
Point::new([2.0, 0.0, 0.0]),
Point::new([0.0, 2.0, 0.0]),
]);
assert_relative_eq!(tri.area(), 2.0);
}
}