use alloc::vec::Vec;
use core::fmt::Debug;
use crate::math::{
Affine, Lerp, Linear, Mat4x4, Parametric, Point2, Point3, Vec2, Vec3,
Vector, mat::RealToReal, space::Real, vec2, vec3,
};
use crate::render::Model;
pub use mesh::Mesh;
pub mod mesh;
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub struct Vertex<P, A> {
pub pos: P,
pub attrib: A,
}
pub type Vertex2<A, B = Model> = Vertex<Point2<B>, A>;
pub type Vertex3<A, B = Model> = Vertex<Point3<B>, A>;
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
#[repr(transparent)]
pub struct Tri<V>(pub [V; 3]);
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
#[repr(transparent)]
pub struct Plane<V>(pub(crate) V);
pub type Plane3<B = ()> = Plane<Vector<[f32; 4], Real<3, B>>>;
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub struct Ray<T: Affine>(pub T, pub T::Diff);
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct Polyline<T>(pub Vec<T>);
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct Polygon<T>(pub Vec<T>);
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub struct Edge<T>(pub T, pub T);
pub type Normal3 = Vec3;
pub type Normal2 = Vec2;
#[derive(Copy, Clone, Debug, Default, Eq, PartialEq)]
pub enum Winding {
Cw,
#[default]
Ccw,
}
#[inline]
pub const fn vertex<P, A>(pos: P, attrib: A) -> Vertex<P, A> {
Vertex { pos, attrib }
}
#[inline]
pub const fn tri<V>(a: V, b: V, c: V) -> Tri<V> {
Tri([a, b, c])
}
impl<V> Tri<V> {
#[inline]
pub fn edges(&self) -> [Edge<&V>; 3] {
let [a, b, c] = &self.0;
[Edge(a, b), Edge(b, c), Edge(c, a)]
}
}
impl<P: Affine, A> Tri<Vertex<P, A>> {
#[inline]
pub fn tangents(&self) -> [P::Diff; 2] {
let [a, b, c] = &self.0;
[b.pos.sub(&a.pos), c.pos.sub(&a.pos)]
}
}
impl<A, B> Tri<Vertex2<A, B>> {
pub fn winding(&self) -> Winding {
let [t, u] = self.tangents();
if t.perp_dot(u) < 0.0 {
Winding::Cw
} else {
Winding::Ccw
}
}
pub fn signed_area(&self) -> f32 {
let [t, u] = self.tangents();
t.perp_dot(u) / 2.0
}
pub fn area(&self) -> f32 {
self.signed_area().abs()
}
}
impl<A, B> Tri<Vertex3<A, B>> {
pub fn normal(&self) -> Normal3 {
let [t, u] = self.tangents();
t.cross(&u).normalize().to()
}
pub fn plane(&self) -> Plane3<B> {
let [a, b, c] = self.0.each_ref().map(|v| v.pos);
Plane::from_points(a, b, c)
}
pub fn winding(&self) -> Winding {
let [u, v] = self.tangents();
let ([ux, uy, _], [vx, vy, _]) = (u.0, v.0);
let z = vec2::<_, ()>(ux, uy).perp_dot(vec2(vx, vy));
if z < 0.0 { Winding::Cw } else { Winding::Ccw }
}
#[cfg(feature = "fp")]
pub fn area(&self) -> f32 {
let [t, u] = self.tangents();
t.cross(&u).len() / 2.0
}
}
impl<B> Plane3<B> {
pub const YZ: Self = Self::new(1.0, 0.0, 0.0, 0.0);
pub const XZ: Self = Self::new(0.0, 1.0, 0.0, 0.0);
pub const XY: Self = Self::new(0.0, 0.0, 1.0, 0.0);
#[inline]
pub const fn new(a: f32, b: f32, c: f32, d: f32) -> Self {
Self(Vector::new([a, b, c, -d]))
}
pub fn from_points(a: Point3<B>, b: Point3<B>, c: Point3<B>) -> Self {
let n = (b - a).cross(&(c - a)).to();
Self::from_point_and_normal(a, n)
}
pub fn from_point_and_normal(pt: Point3<B>, n: Normal3) -> Self {
let n = n.normalize();
let d = pt.to_vec().dot(&n.to());
Plane::new(n.x(), n.y(), n.z(), d)
}
#[inline]
pub fn normal(&self) -> Normal3 {
self.abc().normalize().to()
}
#[inline]
pub fn offset(&self) -> f32 {
-self.signed_dist(Point3::origin())
}
pub fn project(&self, pt: Point3<B>) -> Point3<B> {
let dir = self.abc().to();
let pt_hom = [pt.x(), pt.y(), pt.z(), 1.0].into();
let plane_dot_orig = self.0.dot(&pt_hom);
let plane_dot_dir = dir.len_sqr();
let t = -plane_dot_orig / plane_dot_dir;
pt + t * dir
}
#[inline]
pub fn signed_dist(&self, pt: Point3<B>) -> f32 {
use crate::math::float::*;
let len_sqr = self.abc().len_sqr();
let pt = [pt.x(), pt.y(), pt.z(), 1.0].into();
self.0.dot(&pt) * f32::recip_sqrt(len_sqr)
}
#[cfg(feature = "fp")]
#[inline]
pub fn is_inside(&self, pt: Point3<B>) -> bool {
self.signed_dist(pt) <= 0.0
}
pub fn basis<F>(&self) -> Mat4x4<RealToReal<3, F, B>> {
let up = self.abc();
let right: Vec3<B> =
if up.x().abs() < up.y().abs() && up.x().abs() < up.z().abs() {
Vec3::X
} else {
Vec3::Z
};
let fwd = right.cross(&up).normalize();
let right = up.normalize().cross(&fwd);
let origin = self.offset() * up;
Mat4x4::from_affine(right, up, fwd, origin.to_pt())
}
fn abc(&self) -> Vec3<B> {
let [a, b, c, _] = self.0.0;
vec3(a, b, c)
}
}
impl<T> Polyline<T> {
pub fn new(verts: impl IntoIterator<Item = T>) -> Self {
Self(verts.into_iter().collect())
}
pub fn edges(&self) -> impl Iterator<Item = Edge<&T>> + '_ {
self.0.windows(2).map(|e| Edge(&e[0], &e[1]))
}
}
impl<T> Polygon<T> {
pub fn new(verts: impl IntoIterator<Item = T>) -> Self {
Self(verts.into_iter().collect())
}
pub fn edges(&self) -> impl Iterator<Item = Edge<&T>> + '_ {
let last_first = if let [f, .., l] = &self.0[..] {
Some(Edge(l, f))
} else {
None
};
self.0
.windows(2)
.map(|e| Edge(&e[0], &e[1]))
.chain(last_first)
}
}
impl<T> Parametric<T> for Ray<T>
where
T: Affine<Diff: Linear<Scalar = f32>>,
{
fn eval(&self, t: f32) -> T {
self.0.add(&self.1.mul(t))
}
}
impl<T: Lerp + Clone> Parametric<T> for Polyline<T> {
fn eval(&self, t: f32) -> T {
let pts = &self.0;
assert!(!pts.is_empty(), "cannot eval an empty polyline");
let max = pts.len() - 1;
let i = t.clamp(0.0, 1.0) * max as f32;
let t_rem = i % 1.0;
let i = i as usize;
if i == max {
pts[i].clone()
} else {
pts[i].lerp(&pts[i + 1], t_rem)
}
}
}
impl<P: Lerp, A: Lerp> Lerp for Vertex<P, A> {
fn lerp(&self, other: &Self, t: f32) -> Self {
vertex(
self.pos.lerp(&other.pos, t),
self.attrib.lerp(&other.attrib, t),
)
}
}
#[cfg(test)]
mod tests {
use crate::assert_approx_eq;
use crate::math::*;
use alloc::vec;
use super::*;
type Pt<const N: usize> = Point<[f32; N], Real<N>>;
fn tri<const N: usize>(
a: Pt<N>,
b: Pt<N>,
c: Pt<N>,
) -> Tri<Vertex<Pt<N>, ()>> {
Tri([a, b, c].map(|p| vertex(p, ())))
}
#[test]
fn triangle_winding_2_cw() {
let tri = tri(pt2(-1.0, 0.0), pt2(0.0, 1.0), pt2(1.0, -1.0));
assert_eq!(tri.winding(), Winding::Cw);
}
#[test]
fn triangle_winding_2_ccw() {
let tri = tri(pt2(-2.0, 0.0), pt2(1.0, 0.0), pt2(0.0, 1.0));
assert_eq!(tri.winding(), Winding::Ccw);
}
#[test]
fn triangle_winding_3_cw() {
let tri =
tri(pt3(-1.0, 0.0, 0.0), pt3(0.0, 1.0, 1.0), pt3(1.0, -1.0, 0.0));
assert_eq!(tri.winding(), Winding::Cw);
}
#[test]
fn triangle_winding_3_ccw() {
let tri =
tri(pt3(-1.0, 0.0, 0.0), pt3(1.0, 0.0, 0.0), pt3(0.0, 1.0, -1.0));
assert_eq!(tri.winding(), Winding::Ccw);
}
#[test]
fn triangle_area_2() {
let tri = tri(pt2(-1.0, 0.0), pt2(2.0, 0.0), pt2(2.0, 1.0));
assert_eq!(tri.area(), 1.5);
}
#[cfg(feature = "fp")]
#[test]
fn triangle_area_3() {
let tri = tri(
pt3(-1.0, 0.0, -1.0),
pt3(2.0, 0.0, -1.0),
pt3(0.0, 0.0, 1.0),
);
assert_approx_eq!(tri.area(), 3.0);
}
#[test]
fn triangle_plane() {
let tri = tri(
pt3(-1.0, -2.0, -1.0),
pt3(2.0, -2.0, -1.0),
pt3(0.0, -2.0, 1.0),
);
assert_approx_eq!(tri.plane().0, Plane3::new(0.0, -1.0, 0.0, 2.0).0);
}
#[test]
fn plane_from_points() {
let p = <Plane3>::from_points(
pt3(1.0, 0.0, 0.0),
pt3(0.0, 1.0, 0.0),
pt3(0.0, 0.0, 1.0),
);
assert_approx_eq!(p.normal(), vec3(1.0, 1.0, 1.0).normalize());
assert_approx_eq!(p.offset(), f32::sqrt(1.0 / 3.0));
}
#[test]
#[should_panic]
fn plane_from_collinear_points_panics() {
<Plane3>::from_points(
pt3(1.0, 2.0, 3.0),
pt3(-2.0, -4.0, -6.0),
pt3(0.5, 1.0, 1.5),
);
}
#[test]
#[should_panic]
fn plane_from_zero_normal_panics() {
<Plane3>::from_point_and_normal(
pt3(1.0, 2.0, 3.0),
vec3(0.0, 0.0, 0.0),
);
}
#[test]
fn plane_from_point_and_normal() {
let p = <Plane3>::from_point_and_normal(
pt3(1.0, 2.0, -3.0),
vec3(0.0, 0.0, 12.3),
);
assert_approx_eq!(p.normal(), vec3(0.0, 0.0, 1.0));
assert_approx_eq!(p.offset(), -3.0);
}
#[cfg(feature = "fp")]
#[test]
fn plane_is_point_inside_xz() {
let p = <Plane3>::from_point_and_normal(pt3(1.0, 2.0, 3.0), Vec3::Y);
assert!(p.is_inside(pt3(0.0, 0.0, 0.0)));
assert!(p.is_inside(pt3(0.0, 2.0, 0.0)));
assert!(p.is_inside(pt3(1.0, 2.0, 3.0)));
assert!(!p.is_inside(pt3(0.0, 3.0, 0.0)));
assert!(!p.is_inside(pt3(1.0, 3.0, 3.0)));
}
#[cfg(feature = "fp")]
#[test]
fn plane_is_point_inside_neg_xz() {
let p = <Plane3>::from_point_and_normal(pt3(1.0, 2.0, 3.0), -Vec3::Y);
assert!(!p.is_inside(pt3(0.0, 0.0, 0.0)));
assert!(p.is_inside(pt3(0.0, 2.0, 0.0)));
assert!(p.is_inside(pt3(1.0, 2.0, 3.0)));
assert!(p.is_inside(pt3(0.0, 3.0, 0.0)));
assert!(p.is_inside(pt3(1.0, 3.0, 3.0)));
}
#[cfg(feature = "fp")]
#[test]
fn plane_is_point_inside_diagonal() {
let p = <Plane3>::from_point_and_normal(pt3(0.0, 1.0, 0.0), splat(1.0));
assert!(p.is_inside(pt3(0.0, 0.0, 0.0)));
assert!(p.is_inside(pt3(-1.0, 1.0, -1.0)));
assert!(p.is_inside(pt3(0.0, 1.0, 0.0)));
assert!(!p.is_inside(pt3(0.0, 2.0, 0.0)));
assert!(!p.is_inside(pt3(1.0, 1.0, 1.0)));
assert!(!p.is_inside(pt3(1.0, 0.0, 1.0)));
}
#[test]
fn plane_project_point() {
let p = <Plane3>::from_point_and_normal(pt3(0.0, 2.0, 0.0), Vec3::Y);
assert_approx_eq!(p.project(pt3(5.0, 10.0, -3.0)), pt3(5.0, 2.0, -3.0));
assert_approx_eq!(p.project(pt3(5.0, 2.0, -3.0)), pt3(5.0, 2.0, -3.0));
assert_approx_eq!(
p.project(pt3(5.0, -10.0, -3.0)),
pt3(5.0, 2.0, -3.0)
);
}
#[test]
fn polyline_eval_f32() {
let pl = Polyline(vec![0.0, 1.0, -0.5]);
assert_eq!(pl.eval(-5.0), 0.0);
assert_eq!(pl.eval(0.00), 0.0);
assert_eq!(pl.eval(0.25), 0.5);
assert_eq!(pl.eval(0.50), 1.0);
assert_eq!(pl.eval(0.75), 0.25);
assert_eq!(pl.eval(1.00), -0.5);
assert_eq!(pl.eval(5.00), -0.5);
}
#[test]
#[should_panic]
fn empty_polyline_eval() {
Polyline::<f32>(vec![]).eval(0.5);
}
#[test]
fn singleton_polyline_eval() {
let pl = Polyline(vec![3.14]);
assert_eq!(pl.eval(0.0), 3.14);
assert_eq!(pl.eval(1.0), 3.14);
}
}