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}