retrofire_core/
geom.rs

1//! Basic geometric primitives.
2
3use alloc::vec::Vec;
4use core::fmt::Debug;
5
6use crate::math::{
7    Affine, Lerp, Linear, Mat4x4, Parametric, Point2, Point3, Vec2, Vec3,
8    Vector, mat::RealToReal, space::Real, vec2, vec3,
9};
10use crate::render::Model;
11
12pub use mesh::Mesh;
13
14pub mod mesh;
15
16/// Vertex with a position and arbitrary other attributes.
17#[derive(Copy, Clone, Debug, Eq, PartialEq)]
18pub struct Vertex<P, A> {
19    pub pos: P,
20    pub attrib: A,
21}
22
23/// Two-dimensional vertex type.
24pub type Vertex2<A, B = Model> = Vertex<Point2<B>, A>;
25
26/// Three-dimensional vertex type.
27pub type Vertex3<A, B = Model> = Vertex<Point3<B>, A>;
28
29/// Triangle, defined by three vertices.
30#[derive(Copy, Clone, Debug, Eq, PartialEq)]
31#[repr(transparent)]
32pub struct Tri<V>(pub [V; 3]);
33
34/// Plane, defined by the four parameters of the plane equation.
35#[derive(Copy, Clone, Debug, Eq, PartialEq)]
36#[repr(transparent)]
37pub struct Plane<V>(pub(crate) V);
38
39/// Plane embedded in 3D space, splitting the space into two half-spaces.
40pub type Plane3<B = ()> = Plane<Vector<[f32; 4], Real<3, B>>>;
41
42/// A ray, or a half line, composed of an initial point and a direction vector.
43#[derive(Copy, Clone, Debug, Eq, PartialEq)]
44pub struct Ray<T: Affine>(pub T, pub T::Diff);
45
46/// A curve composed of a chain of line segments.
47///
48/// The polyline is represented as a list of points, or vertices, with each
49/// pair of consecutive vertices sharing an edge.
50#[derive(Clone, Debug, Eq, PartialEq)]
51pub struct Polyline<T>(pub Vec<T>);
52
53/// A closed curve composed of a chain of line segments.
54///
55/// The polygon is represented as a list of points, or vertices, with each pair
56/// of consecutive vertices, as well as the first and last vertex, sharing an edge.
57#[derive(Clone, Debug, Eq, PartialEq)]
58pub struct Polygon<T>(pub Vec<T>);
59
60/// A line segment between two vertices.
61#[derive(Copy, Clone, Debug, Eq, PartialEq)]
62pub struct Edge<T>(pub T, pub T);
63
64/// A surface normal in 3D.
65// TODO Use distinct type rather than alias
66pub type Normal3 = Vec3;
67/// A surface normal in 2D.
68pub type Normal2 = Vec2;
69
70/// Polygon winding order.
71///
72/// The triangle *ABC* below has clockwise winding, while
73/// the triangle *DEF* has counter-clockwise winding.
74///
75/// ```text
76///     B            F
77///    / \          / \
78///   /   \        /   \
79///  /     \      /     \
80/// A-------C    D-------E
81///    Cw           Ccw
82/// ```
83#[derive(Copy, Clone, Debug, Default, Eq, PartialEq)]
84pub enum Winding {
85    /// Clockwise winding.
86    Cw,
87    /// Counter-clockwise winding.
88    #[default]
89    Ccw,
90}
91
92/// Creates a `Vertex` with the give position and attribute values.
93#[inline]
94pub const fn vertex<P, A>(pos: P, attrib: A) -> Vertex<P, A> {
95    Vertex { pos, attrib }
96}
97
98/// Creates a `Tri` with the given vertices.
99#[inline]
100pub const fn tri<V>(a: V, b: V, c: V) -> Tri<V> {
101    Tri([a, b, c])
102}
103
104//
105// Inherent impls
106//
107
108impl<V> Tri<V> {
109    /// Given a triangle ABC, returns the edges [AB, BC, CA].
110    ///
111    /// # Examples
112    /// ```
113    /// use retrofire_core::geom::{Tri, Edge};
114    /// use retrofire_core::math::{Point2, pt2};
115    ///
116    /// let pts: [Point2; _] = [pt2(-1.0, 0.0), pt2(2.0, 0.0), pt2(1.0, 2.0)];
117    /// let tri = Tri(pts);
118    ///
119    /// let [e0, e1, e2] = tri.edges();
120    /// assert_eq!(e0, Edge(&pts[0], &pts[1]));
121    /// assert_eq!(e1, Edge(&pts[1], &pts[2]));
122    /// assert_eq!(e2, Edge(&pts[2], &pts[0]));
123    ///
124    /// ```
125    #[inline]
126    pub fn edges(&self) -> [Edge<&V>; 3] {
127        let [a, b, c] = &self.0;
128        [Edge(a, b), Edge(b, c), Edge(c, a)]
129    }
130}
131
132impl<P: Affine, A> Tri<Vertex<P, A>> {
133    /// Given a triangle ABC, returns the vectors [AB, AC].
134    #[inline]
135    pub fn tangents(&self) -> [P::Diff; 2] {
136        let [a, b, c] = &self.0;
137        [b.pos.sub(&a.pos), c.pos.sub(&a.pos)]
138    }
139}
140
141impl<A, B> Tri<Vertex2<A, B>> {
142    /// Returns the winding order of `self`.
143    ///
144    /// # Examples
145    /// ```
146    /// use retrofire_core::geom::{Tri, vertex, Winding};
147    /// use retrofire_core::math::pt2;
148    ///
149    /// let mut tri = Tri([
150    ///     vertex(pt2::<_, ()>(0.0, 0.0), ()),
151    ///     vertex(pt2(0.0, 3.0), ()),
152    ///     vertex(pt2(4.0, 0.0), ()),
153    /// ]);
154    /// assert_eq!(tri.winding(), Winding::Cw);
155    ///
156    /// tri.0.swap(1, 2);
157    /// assert_eq!(tri.winding(), Winding::Ccw);
158    /// ```
159    pub fn winding(&self) -> Winding {
160        let [t, u] = self.tangents();
161        if t.perp_dot(u) < 0.0 {
162            Winding::Cw
163        } else {
164            Winding::Ccw
165        }
166    }
167
168    /// Returns the signed area of `self`.
169    ///
170    /// The area is positive *iff* `self` is wound counter-clockwise.
171    ///
172    /// # Examples
173    /// ```
174    /// use retrofire_core::geom::{Tri, vertex};
175    /// use retrofire_core::math::pt2;
176    ///
177    /// let tri = Tri([
178    ///     vertex(pt2::<_, ()>(0.0, 0.0), ()),
179    ///     vertex(pt2(0.0, 3.0), ()),
180    ///     vertex(pt2(4.0, 0.0), ()),
181    /// ]);
182    /// assert_eq!(tri.signed_area(), -6.0);
183    /// ```
184    pub fn signed_area(&self) -> f32 {
185        let [t, u] = self.tangents();
186        t.perp_dot(u) / 2.0
187    }
188
189    /// Returns the (positive) area of `self`.
190    ///
191    /// # Examples
192    /// ```
193    /// use retrofire_core::geom::{vertex, Tri};
194    /// use retrofire_core::math::pt2;
195    ///
196    /// let tri = Tri([
197    ///     vertex(pt2::<_, ()>(0.0, 0.0), ()),
198    ///     vertex(pt2(0.0, 3.0), ()),
199    ///     vertex(pt2(4.0, 0.0), ()),
200    /// ]);
201    /// assert_eq!(tri.area(), 6.0);
202    /// ```
203    pub fn area(&self) -> f32 {
204        self.signed_area().abs()
205    }
206}
207
208impl<A, B> Tri<Vertex3<A, B>> {
209    /// Returns the normal vector of `self`.
210    ///
211    /// The result is normalized to unit length.
212    ///
213    /// # Examples
214    /// ```
215    /// use core::f32::consts::SQRT_2;
216    /// use retrofire_core::geom::{Tri, vertex};
217    /// use retrofire_core::math::{pt3, vec3};
218    ///
219    /// // Triangle lying in a 45° angle
220    /// let tri = Tri([
221    ///     vertex(pt3::<_, ()>(0.0, 0.0, 0.0), ()),
222    ///     vertex(pt3(0.0, 3.0, 3.0), ()),
223    ///     vertex(pt3(4.0, 0.0,0.0), ()),
224    /// ]);
225    /// assert_eq!(tri.normal(), vec3(0.0, SQRT_2 / 2.0, -SQRT_2 / 2.0));
226    /// ```
227    pub fn normal(&self) -> Normal3 {
228        let [t, u] = self.tangents();
229        // TODO normal with basis
230        t.cross(&u).normalize().to()
231    }
232
233    /// Returns the plane that `self` lies on.
234    ///
235    /// # Examples
236    /// ```
237    /// use retrofire_core::geom::{Tri, Plane3, vertex};
238    /// use retrofire_core::math::{pt3, Vec3};
239    ///
240    /// let tri = Tri([
241    ///     vertex(pt3::<f32, ()>(0.0, 0.0, 2.0), ()),
242    ///     vertex(pt3(1.0, 0.0, 2.0), ()),
243    ///     vertex(pt3(0.0, 1.0, 2.0), ())
244    /// ]);
245    /// assert_eq!(tri.plane().normal(), Vec3::Z);
246    /// assert_eq!(tri.plane().offset(), 2.0);
247    /// ```
248    pub fn plane(&self) -> Plane3<B> {
249        let [a, b, c] = self.0.each_ref().map(|v| v.pos);
250        Plane::from_points(a, b, c)
251    }
252
253    /// Returns the winding order of `self`, as projected to the XY plane.
254    // TODO is this 3D version meaningful/useful enough?
255    pub fn winding(&self) -> Winding {
256        // TODO better way to xyz->xy...
257        let [u, v] = self.tangents();
258        let ([ux, uy, _], [vx, vy, _]) = (u.0, v.0);
259        let z = vec2::<_, ()>(ux, uy).perp_dot(vec2(vx, vy));
260        if z < 0.0 { Winding::Cw } else { Winding::Ccw }
261    }
262
263    /// Returns the area of `self`.
264    ///
265    /// # Examples
266    /// ```
267    /// use retrofire_core::geom::{tri, vertex};
268    /// use retrofire_core::math::pt3;
269    ///
270    /// let tri = tri(
271    ///     vertex(pt3::<_, ()>(0.0, 0.0, 0.0), ()),
272    ///     vertex(pt3(4.0, 0.0, 0.0), ()),
273    ///     vertex(pt3(0.0, 3.0, 0.0), ()),
274    /// );
275    /// assert_eq!(tri.area(), 6.0);
276    /// ```
277    #[cfg(feature = "fp")]
278    pub fn area(&self) -> f32 {
279        let [t, u] = self.tangents();
280        t.cross(&u).len() / 2.0
281    }
282}
283
284impl<B> Plane3<B> {
285    /// The x = 0 coordinate plane.
286    pub const YZ: Self = Self::new(1.0, 0.0, 0.0, 0.0);
287
288    /// The y = 0 coordinate plane.
289    pub const XZ: Self = Self::new(0.0, 1.0, 0.0, 0.0);
290
291    /// The z = 0 coordinate plane.
292    pub const XY: Self = Self::new(0.0, 0.0, 1.0, 0.0);
293
294    /// Creates a new plane with the given coefficients.
295    ///
296    // TODO not normalized because const
297    // The coefficients are normalized to
298    //
299    // (a', b', c', d') = (a, b, c, d) / |(a, b, c)|.
300    ///
301    /// The returned plane satisfies the plane equation
302    ///
303    /// *ax* + *by* + *cz* = *d*,
304    ///
305    /// or equivalently
306    ///
307    /// *ax* + *by* + *cz* - *d* = 0.
308    ///
309    /// Note the sign of the *d* coefficient.
310    ///
311    /// The coefficients (a, b, c) make up a vector normal to the plane,
312    /// and d is proportional to the plane's distance to the origin.
313    /// If (a, b, c) is a unit vector, then d is exactly the offset of the
314    /// plane from the origin in the direction of the normal.
315    ///
316    /// # Examples
317    /// ```
318    /// use retrofire_core::{geom::Plane3, math::Vec3};
319    ///
320    /// let p = <Plane3>::new(1.0, 0.0, 0.0, -2.0);
321    /// assert_eq!(p.normal(), Vec3::X);
322    /// assert_eq!(p.offset(), -2.0);
323    ///
324    /// ```
325    #[inline]
326    pub const fn new(a: f32, b: f32, c: f32, d: f32) -> Self {
327        Self(Vector::new([a, b, c, -d]))
328    }
329
330    /// Creates a plane given three points on the plane.
331    ///
332    /// # Panics
333    /// If the points are collinear or nearly so.
334    ///
335    /// # Examples
336    /// ```
337    /// use retrofire_core::{geom::Plane3, math::{pt3, vec3}};
338    ///
339    /// let p = <Plane3>::from_points(
340    ///     pt3(0.0, 0.0, 2.0),
341    ///     pt3(1.0, 0.0, 2.0),
342    ///     pt3(0.0, 1.0, 2.0),
343    /// );
344    /// assert_eq!(p.normal(), vec3(0.0, 0.0, 1.0));
345    /// assert_eq!(p.offset(), 2.0);
346    ///
347    /// ```
348    pub fn from_points(a: Point3<B>, b: Point3<B>, c: Point3<B>) -> Self {
349        let n = (b - a).cross(&(c - a)).to();
350        Self::from_point_and_normal(a, n)
351    }
352
353    /// Creates a plane given a point on the plane and a normal.
354    ///
355    /// `n` does not have to be normalized.
356    ///
357    /// # Panics
358    /// If `n` is non-finite or nearly zero-length.
359    ///
360    /// # Examples
361    /// ```
362    /// use retrofire_core::{geom::Plane3, math::{Vec3, pt3, vec3}};
363    ///
364    /// let p = <Plane3>::from_point_and_normal(pt3(1.0, 2.0, 3.0), Vec3::Z);
365    /// assert_eq!(p.normal(), Vec3::Z);
366    /// assert_eq!(p.offset(), 3.0);
367    ///
368    /// ```
369    pub fn from_point_and_normal(pt: Point3<B>, n: Normal3) -> Self {
370        let n = n.normalize();
371        // For example, if pt = (0, 1, 0) and n = (0, 1, 0), d has to be 1
372        // to satisfy the plane equation n_x + n_y + n_z = d
373        let d = pt.to_vec().dot(&n.to());
374        Plane::new(n.x(), n.y(), n.z(), d)
375    }
376
377    /// Returns the normal vector of `self`.
378    ///
379    /// The normal returned is unit length.
380    ///
381    /// # Examples
382    /// ```
383    /// use retrofire_core::{geom::Plane3, math::Vec3};
384    ///
385    /// assert_eq!(<Plane3>::XY.normal(), Vec3::Z);
386    /// assert_eq!(<Plane3>::YZ.normal(), Vec3::X);
387    #[inline]
388    pub fn normal(&self) -> Normal3 {
389        self.abc().normalize().to()
390    }
391
392    /// Returns the signed distance of `self` from the origin.
393    ///
394    /// This distance is negative if the origin is [*outside*][Self::is_inside]
395    /// the plane and positive if the origin is *inside* the plane.
396    ///
397    /// # Examples
398    /// ```
399    /// use retrofire_core::{geom::Plane3, math::{Vec3, pt3}};
400    ///
401    /// assert_eq!(<Plane3>::new(0.0, 1.0, 0.0, 3.0).offset(), 3.0);
402    /// assert_eq!(<Plane3>::new(0.0, 2.0, 0.0, 6.0).offset(), 3.0);
403    /// assert_eq!(<Plane3>::new(0.0, -1.0, 0.0, -3.0).offset(), -3.0);
404    /// ```
405    #[inline]
406    pub fn offset(&self) -> f32 {
407        // plane dist from origin is origin dist from plane, negated
408        -self.signed_dist(Point3::origin())
409    }
410
411    /// Returns the perpendicular projection of a point on `self`.
412    ///
413    /// In other words, returns *P'*, the point on the plane closest to *P*.
414    ///
415    /// ```text
416    ///          ^        P
417    ///         /         ·
418    ///        /          ·
419    ///       / · · · · · P'
420    ///      /           ·
421    ///     /           ·
422    ///    O------------------>
423    /// ```
424    ///
425    /// # Examples
426    /// ```
427    /// use retrofire_core::geom::Plane3;
428    /// use retrofire_core::math::{Point3, pt3};
429    ///
430    /// let pt: Point3 = pt3(1.0, 2.0, -3.0);
431    ///
432    /// assert_eq!(<Plane3>::XZ.project(pt), pt3(1.0, 0.0, -3.0));
433    /// assert_eq!(<Plane3>::XY.project(pt), pt3(1.0, 2.0, 0.0));
434    ///
435    /// assert_eq!(<Plane3>::new(0.0, 0.0, 1.0, 2.0).project(pt), pt3(1.0, 2.0, 2.0));
436    /// assert_eq!(<Plane3>::new(0.0, 0.0, 2.0, 2.0).project(pt), pt3(1.0, 2.0, 1.0));
437    /// ```
438    pub fn project(&self, pt: Point3<B>) -> Point3<B> {
439        // t = -(plane dot orig) / (plane dot dir)
440        // In this case dir is parallel to plane normal
441
442        let dir = self.abc().to();
443
444        // TODO add to_homog()/to_real() methods
445        let pt_hom = [pt.x(), pt.y(), pt.z(), 1.0].into();
446
447        // Use homogeneous pt to get self · pt = ax + by + cz + d
448        // Could also just add d manually to ax + by + cz
449        let plane_dot_orig = self.0.dot(&pt_hom);
450
451        // Vector, so w = 0, so dir_hom · dir_hom = dir · dir
452        let plane_dot_dir = dir.len_sqr(); // = dir · dir
453
454        let t = -plane_dot_orig / plane_dot_dir;
455
456        pt + t * dir
457    }
458
459    /// Returns the signed distance of a point to `self`.
460    ///
461    /// # Examples
462    /// ```
463    /// use retrofire_core::geom::Plane3;
464    /// use retrofire_core::math::{Point3, pt3, Vec3};
465    ///
466    /// let pt: Point3 = pt3(1.0, 2.0, -3.0);
467    ///
468    /// assert_eq!(<Plane3>::XZ.signed_dist(pt), 2.0);
469    /// assert_eq!(<Plane3>::XY.signed_dist(pt), -3.0);
470    ///
471    /// let p = <Plane3>::new(-1.0, 0.0, 0.0, 2.0);
472    /// assert_eq!(p.signed_dist(pt), -3.0);
473    /// ```
474    #[inline]
475    pub fn signed_dist(&self, pt: Point3<B>) -> f32 {
476        use crate::math::float::*;
477        let len_sqr = self.abc().len_sqr();
478        // TODO use to_homog once committed
479        let pt = [pt.x(), pt.y(), pt.z(), 1.0].into();
480        self.0.dot(&pt) * f32::recip_sqrt(len_sqr)
481    }
482
483    /// Returns whether a point is in the half-space that the normal of `self`
484    /// points away from.
485    ///
486    /// # Examples
487    /// ```
488    /// use retrofire_core::geom::Plane3;
489    /// use retrofire_core::math::{Point3, pt3};
490    ///
491    /// let pt: Point3 = pt3(1.0, 2.0, -3.0);
492    ///
493    /// assert!(!<Plane3>::XZ.is_inside(pt));
494    /// assert!(<Plane3>::XY.is_inside(pt));
495    /// ```
496    // TODO "plane.is_inside(point)" reads wrong
497    #[cfg(feature = "fp")]
498    #[inline]
499    pub fn is_inside(&self, pt: Point3<B>) -> bool {
500        self.signed_dist(pt) <= 0.0
501    }
502
503    /// Returns an orthonormal affine basis on `self`.
504    ///
505    /// The y-axis of the basis is the normal vector; the x- and z-axes are
506    /// two arbitrary orthogonal unit vectors tangent to the plane. The origin
507    /// point is the point on the plane closest to the origin.
508    ///
509    /// # Examples
510    /// ```
511    /// use retrofire_core::assert_approx_eq;
512    /// use retrofire_core::geom::Plane3;
513    /// use retrofire_core::math::{Point3, pt3, vec3, Apply};
514    ///
515    /// let p = <Plane3>::from_point_and_normal(pt3(0.0,1.0,0.0), vec3(0.0,1.0,1.0));
516    /// let m = p.basis::<()>();
517    ///
518    /// assert_approx_eq!(m.apply(&Point3::origin()), pt3(0.0, 0.5, 0.5));
519    /// ```
520    pub fn basis<F>(&self) -> Mat4x4<RealToReal<3, F, B>> {
521        let up = self.abc();
522
523        let right: Vec3<B> =
524            if up.x().abs() < up.y().abs() && up.x().abs() < up.z().abs() {
525                Vec3::X
526            } else {
527                Vec3::Z
528            };
529        let fwd = right.cross(&up).normalize();
530        let right = up.normalize().cross(&fwd);
531
532        let origin = self.offset() * up;
533
534        Mat4x4::from_affine(right, up, fwd, origin.to_pt())
535    }
536
537    /// Helper that returns the plane normal non-normalized.
538    fn abc(&self) -> Vec3<B> {
539        let [a, b, c, _] = self.0.0;
540        vec3(a, b, c)
541    }
542}
543
544impl<T> Polyline<T> {
545    /// Creates a new polyline from an iterator of vertex points.
546    pub fn new(verts: impl IntoIterator<Item = T>) -> Self {
547        Self(verts.into_iter().collect())
548    }
549
550    /// Returns an iterator over the line segments of `self`.
551    ///
552    /// # Examples
553    /// ```
554    /// use retrofire_core::geom::{Polyline, Edge};
555    /// use retrofire_core::math::{pt2, Point2};
556    ///
557    /// let pts: [Point2; _] = [pt2(0.0, 0.0), pt2(1.0, 1.0), pt2(2.0, 1.0)];
558    ///
559    /// let pline = Polyline::new(pts);
560    /// let mut edges = pline.edges();
561    ///
562    /// assert_eq!(edges.next(), Some(Edge(&pts[0], &pts[1])));
563    /// assert_eq!(edges.next(), Some(Edge(&pts[1], &pts[2])));
564    /// assert_eq!(edges.next(), None);
565    /// ```
566    pub fn edges(&self) -> impl Iterator<Item = Edge<&T>> + '_ {
567        self.0.windows(2).map(|e| Edge(&e[0], &e[1]))
568    }
569}
570
571impl<T> Polygon<T> {
572    pub fn new(verts: impl IntoIterator<Item = T>) -> Self {
573        Self(verts.into_iter().collect())
574    }
575
576    /// Returns an iterator over the edges of `self`.
577    ///
578    /// Given a polygon ABC...XYZ, returns the edges AB, BC, ..., XY, YZ, ZA.
579    /// If `self` has zero or one vertices, returns an empty iterator.
580    ///
581    /// # Examples
582    /// ```
583    /// use retrofire_core::geom::{Polygon, Edge};
584    /// use retrofire_core::math::{Point2, pt2};
585    ///
586    /// let pts: [Point2; _] = [pt2(0.0, 0.0), pt2(1.0, 1.0), pt2(2.0, 1.0)];
587    ///
588    /// let poly = Polygon::new(pts);
589    /// let mut edges = poly.edges();
590    ///
591    /// assert_eq!(edges.next(), Some(Edge(&pts[0], &pts[1])));
592    /// assert_eq!(edges.next(), Some(Edge(&pts[1], &pts[2])));
593    /// assert_eq!(edges.next(), Some(Edge(&pts[2], &pts[0])));
594    /// assert_eq!(edges.next(), None);
595    /// ```
596    pub fn edges(&self) -> impl Iterator<Item = Edge<&T>> + '_ {
597        let last_first = if let [f, .., l] = &self.0[..] {
598            Some(Edge(l, f))
599        } else {
600            None
601        };
602        self.0
603            .windows(2)
604            .map(|e| Edge(&e[0], &e[1]))
605            .chain(last_first)
606    }
607}
608
609//
610// Local trait impls
611//
612
613impl<T> Parametric<T> for Ray<T>
614where
615    T: Affine<Diff: Linear<Scalar = f32>>,
616{
617    fn eval(&self, t: f32) -> T {
618        self.0.add(&self.1.mul(t))
619    }
620}
621
622impl<T: Lerp + Clone> Parametric<T> for Polyline<T> {
623    /// Returns the point on `self` at *t*.
624    ///
625    /// If the number of vertices in `self` is *n* > 1, the vertex at index
626    /// *k* < *n* corresponds to `t` = *k* / (*n* - 1). Intermediate values
627    /// of *t* are linearly interpolated between the two closest vertices.
628    /// Values *t* < 0 and *t* > 1 are clamped to 0 and 1 respectively.
629    /// A polyline with a single vertex returns the value of that vertex
630    /// for any value of *t*.
631    ///
632    /// # Panics
633    /// If `self` has no vertices.
634    ///
635    /// # Examples
636    /// ```
637    /// use retrofire_core::geom::{Polyline, Edge};
638    /// use retrofire_core::math::{pt2, Point2, Parametric};
639    ///
640    /// let pl = Polyline::<Point2>(
641    ///     vec![pt2(0.0, 0.0), pt2(1.0, 2.0), pt2(2.0, 1.0)]);
642    ///
643    /// assert_eq!(pl.eval(0.0), pl.0[0]);
644    /// assert_eq!(pl.eval(0.5), pl.0[1]);
645    /// assert_eq!(pl.eval(1.0), pl.0[2]);
646    ///
647    /// // Values not corresponding to a vertex are interpolated:
648    /// assert_eq!(pl.eval(0.25), pt2(0.5, 1.0));
649    /// assert_eq!(pl.eval(0.75), pt2(1.5, 1.5));
650    ///
651    /// // Values of t outside 0.0..=1.0 are clamped:
652    /// assert_eq!(pl.eval(-1.23), pl.eval(0.0));
653    /// assert_eq!(pl.eval(7.68), pl.eval(1.0));
654    /// ```
655    fn eval(&self, t: f32) -> T {
656        let pts = &self.0;
657        assert!(!pts.is_empty(), "cannot eval an empty polyline");
658
659        let max = pts.len() - 1;
660        let i = t.clamp(0.0, 1.0) * max as f32;
661        let t_rem = i % 1.0;
662        let i = i as usize;
663
664        if i == max {
665            pts[i].clone()
666        } else {
667            pts[i].lerp(&pts[i + 1], t_rem)
668        }
669    }
670}
671
672impl<P: Lerp, A: Lerp> Lerp for Vertex<P, A> {
673    fn lerp(&self, other: &Self, t: f32) -> Self {
674        vertex(
675            self.pos.lerp(&other.pos, t),
676            // TODO Normals shouldn't be lerped
677            self.attrib.lerp(&other.attrib, t),
678        )
679    }
680}
681
682#[cfg(test)]
683mod tests {
684    use crate::assert_approx_eq;
685    use crate::math::*;
686    use alloc::vec;
687
688    use super::*;
689
690    type Pt<const N: usize> = Point<[f32; N], Real<N>>;
691
692    fn tri<const N: usize>(
693        a: Pt<N>,
694        b: Pt<N>,
695        c: Pt<N>,
696    ) -> Tri<Vertex<Pt<N>, ()>> {
697        Tri([a, b, c].map(|p| vertex(p, ())))
698    }
699
700    #[test]
701    fn triangle_winding_2_cw() {
702        let tri = tri(pt2(-1.0, 0.0), pt2(0.0, 1.0), pt2(1.0, -1.0));
703        assert_eq!(tri.winding(), Winding::Cw);
704    }
705    #[test]
706    fn triangle_winding_2_ccw() {
707        let tri = tri(pt2(-2.0, 0.0), pt2(1.0, 0.0), pt2(0.0, 1.0));
708        assert_eq!(tri.winding(), Winding::Ccw);
709    }
710    #[test]
711    fn triangle_winding_3_cw() {
712        let tri =
713            tri(pt3(-1.0, 0.0, 0.0), pt3(0.0, 1.0, 1.0), pt3(1.0, -1.0, 0.0));
714        assert_eq!(tri.winding(), Winding::Cw);
715    }
716    #[test]
717    fn triangle_winding_3_ccw() {
718        let tri =
719            tri(pt3(-1.0, 0.0, 0.0), pt3(1.0, 0.0, 0.0), pt3(0.0, 1.0, -1.0));
720        assert_eq!(tri.winding(), Winding::Ccw);
721    }
722
723    #[test]
724    fn triangle_area_2() {
725        let tri = tri(pt2(-1.0, 0.0), pt2(2.0, 0.0), pt2(2.0, 1.0));
726        assert_eq!(tri.area(), 1.5);
727    }
728    #[cfg(feature = "fp")]
729    #[test]
730    fn triangle_area_3() {
731        // base = 3, height = 2
732        let tri = tri(
733            pt3(-1.0, 0.0, -1.0),
734            pt3(2.0, 0.0, -1.0),
735            pt3(0.0, 0.0, 1.0),
736        );
737        assert_approx_eq!(tri.area(), 3.0);
738    }
739
740    #[test]
741    fn triangle_plane() {
742        let tri = tri(
743            pt3(-1.0, -2.0, -1.0),
744            pt3(2.0, -2.0, -1.0),
745            pt3(0.0, -2.0, 1.0),
746        );
747        assert_approx_eq!(tri.plane().0, Plane3::new(0.0, -1.0, 0.0, 2.0).0);
748    }
749
750    #[test]
751    fn plane_from_points() {
752        let p = <Plane3>::from_points(
753            pt3(1.0, 0.0, 0.0),
754            pt3(0.0, 1.0, 0.0),
755            pt3(0.0, 0.0, 1.0),
756        );
757
758        assert_approx_eq!(p.normal(), vec3(1.0, 1.0, 1.0).normalize());
759        assert_approx_eq!(p.offset(), f32::sqrt(1.0 / 3.0));
760    }
761    #[test]
762    #[should_panic]
763    fn plane_from_collinear_points_panics() {
764        <Plane3>::from_points(
765            pt3(1.0, 2.0, 3.0),
766            pt3(-2.0, -4.0, -6.0),
767            pt3(0.5, 1.0, 1.5),
768        );
769    }
770    #[test]
771    #[should_panic]
772    fn plane_from_zero_normal_panics() {
773        <Plane3>::from_point_and_normal(
774            pt3(1.0, 2.0, 3.0),
775            vec3(0.0, 0.0, 0.0),
776        );
777    }
778    #[test]
779    fn plane_from_point_and_normal() {
780        let p = <Plane3>::from_point_and_normal(
781            pt3(1.0, 2.0, -3.0),
782            vec3(0.0, 0.0, 12.3),
783        );
784        assert_approx_eq!(p.normal(), vec3(0.0, 0.0, 1.0));
785        assert_approx_eq!(p.offset(), -3.0);
786    }
787    #[cfg(feature = "fp")]
788    #[test]
789    fn plane_is_point_inside_xz() {
790        let p = <Plane3>::from_point_and_normal(pt3(1.0, 2.0, 3.0), Vec3::Y);
791
792        // Inside
793        assert!(p.is_inside(pt3(0.0, 0.0, 0.0)));
794        // Coincident=inside
795        assert!(p.is_inside(pt3(0.0, 2.0, 0.0)));
796        assert!(p.is_inside(pt3(1.0, 2.0, 3.0)));
797        // Outside
798        assert!(!p.is_inside(pt3(0.0, 3.0, 0.0)));
799        assert!(!p.is_inside(pt3(1.0, 3.0, 3.0)));
800    }
801    #[cfg(feature = "fp")]
802    #[test]
803    fn plane_is_point_inside_neg_xz() {
804        let p = <Plane3>::from_point_and_normal(pt3(1.0, 2.0, 3.0), -Vec3::Y);
805
806        // Outside
807        assert!(!p.is_inside(pt3(0.0, 0.0, 0.0)));
808        // Coincident=inside
809        assert!(p.is_inside(pt3(0.0, 2.0, 0.0)));
810        assert!(p.is_inside(pt3(1.0, 2.0, 3.0)));
811        // Inside
812        assert!(p.is_inside(pt3(0.0, 3.0, 0.0)));
813        assert!(p.is_inside(pt3(1.0, 3.0, 3.0)));
814    }
815    #[cfg(feature = "fp")]
816    #[test]
817    fn plane_is_point_inside_diagonal() {
818        let p = <Plane3>::from_point_and_normal(pt3(0.0, 1.0, 0.0), splat(1.0));
819
820        // Inside
821        assert!(p.is_inside(pt3(0.0, 0.0, 0.0)));
822        assert!(p.is_inside(pt3(-1.0, 1.0, -1.0)));
823        // Coincident=inside
824        assert!(p.is_inside(pt3(0.0, 1.0, 0.0)));
825        // Outside
826        assert!(!p.is_inside(pt3(0.0, 2.0, 0.0)));
827        assert!(!p.is_inside(pt3(1.0, 1.0, 1.0)));
828        assert!(!p.is_inside(pt3(1.0, 0.0, 1.0)));
829    }
830
831    #[test]
832    fn plane_project_point() {
833        let p = <Plane3>::from_point_and_normal(pt3(0.0, 2.0, 0.0), Vec3::Y);
834
835        // Outside
836        assert_approx_eq!(p.project(pt3(5.0, 10.0, -3.0)), pt3(5.0, 2.0, -3.0));
837        // Coincident
838        assert_approx_eq!(p.project(pt3(5.0, 2.0, -3.0)), pt3(5.0, 2.0, -3.0));
839        // Inside
840        assert_approx_eq!(
841            p.project(pt3(5.0, -10.0, -3.0)),
842            pt3(5.0, 2.0, -3.0)
843        );
844    }
845
846    #[test]
847    fn polyline_eval_f32() {
848        let pl = Polyline(vec![0.0, 1.0, -0.5]);
849
850        assert_eq!(pl.eval(-5.0), 0.0);
851        assert_eq!(pl.eval(0.00), 0.0);
852        assert_eq!(pl.eval(0.25), 0.5);
853        assert_eq!(pl.eval(0.50), 1.0);
854        assert_eq!(pl.eval(0.75), 0.25);
855        assert_eq!(pl.eval(1.00), -0.5);
856        assert_eq!(pl.eval(5.00), -0.5);
857    }
858
859    #[test]
860    #[should_panic]
861    fn empty_polyline_eval() {
862        Polyline::<f32>(vec![]).eval(0.5);
863    }
864
865    #[test]
866    fn singleton_polyline_eval() {
867        let pl = Polyline(vec![3.14]);
868        assert_eq!(pl.eval(0.0), 3.14);
869        assert_eq!(pl.eval(1.0), 3.14);
870    }
871}