retrofire_core/render/
scene.rs

1use crate::geom::{Mesh, vertex};
2use crate::math::{
3    Mat4x4, Point3,
4    mat::{Apply, RealToProj},
5    pt3, splat,
6};
7
8use super::{
9    Model, ModelToWorld,
10    clip::{ClipVert, Status, view_frustum},
11};
12
13#[derive(Clone, Debug)]
14pub struct Obj<A> {
15    pub geom: Mesh<A>,
16    pub bbox: BBox<Model>,
17    pub tf: Mat4x4<ModelToWorld>,
18}
19
20// TODO Decide whether upper bound is inclusive or exclusive
21// TODO Needs to be more generic to work with clip points
22#[derive(Copy, Clone, Debug, PartialEq)]
23pub struct BBox<B: Default>(pub Point3<B>, pub Point3<B>);
24
25impl<A> Obj<A> {
26    pub fn new(geom: Mesh<A>) -> Self {
27        Self::with_transform(geom, Mat4x4::identity())
28    }
29    pub fn with_transform(geom: Mesh<A>, tf: Mat4x4<ModelToWorld>) -> Self {
30        let bbox = BBox::of(&geom);
31        Self { geom, bbox, tf }
32    }
33}
34
35impl<B: Default> BBox<B> {
36    pub fn of<A>(mesh: &Mesh<A, B>) -> Self {
37        mesh.verts.iter().map(|v| &v.pos).collect()
38    }
39
40    /// If needed, enlarges `self` so that a point is just contained.
41    pub fn extend(&mut self, pt: &Point3<B>) {
42        let BBox(low, upp) = self;
43        *low = low.zip_map(*pt, f32::min);
44        *upp = upp.zip_map(*pt, f32::max);
45    }
46
47    pub fn is_empty(&self) -> bool {
48        let BBox(low, upp) = self;
49        (0..3).any(|i| low[i] >= upp[i])
50    }
51
52    /// Returns whether a point is within the bounds of `self`.
53    pub fn contains(&self, pt: &Point3<B>) -> bool {
54        let BBox(low, upp) = self;
55        (0..3).all(|i| low[i] <= pt[i] && pt[i] <= upp[i])
56    }
57
58    #[rustfmt::skip]
59    pub fn verts(&self) -> [Point3<B>; 8] {
60        let [x0, y0, z0] = self.0.0;
61        let [x1, y1, z1] = self.1.0;
62        [
63            pt3(x0, y0, z0), pt3(x0, y0, z1), pt3(x0, y1, z0), pt3(x0, y1, z1),
64            pt3(x1, y0, z0), pt3(x1, y0, z1), pt3(x1, y1, z0), pt3(x1, y1, z1),
65        ]
66    }
67
68    /// Returns whether `self` intersects the view frustum.
69    ///
70    /// Given a real-to-projection transform, tests this bounding box against
71    /// the view frustum and returns whether the box (and thus any bounded
72    /// geometry) is fully hidden, fully visible, or potentially partially
73    /// visible.
74    ///
75    /// If this method returns `Hidden`, the box is definitely outside the
76    /// frustum and any bounded geometry does not have to be drawn. If it
77    /// returns `Visible`, it is fully inside the frustum, and contained
78    /// geometry needs no clipping or culling.  If the return value is
79    /// `Clipped`, the box and the geometry are *potentially* visible and
80    /// more fine-grained culling is required.
81    pub fn visibility(&self, tf: &Mat4x4<RealToProj<B>>) -> Status {
82        view_frustum::status(
83            &self
84                .verts()
85                .map(|p| ClipVert::new(vertex(tf.apply(&p), ()))),
86        )
87    }
88}
89
90impl<A> Default for Obj<A> {
91    /// Returns an empty `Obj`.
92    fn default() -> Self {
93        Self {
94            geom: Default::default(),
95            bbox: Default::default(),
96            tf: Default::default(),
97        }
98    }
99}
100
101impl<B: Default> Default for BBox<B> {
102    /// Returns an empty `BBox`.
103    fn default() -> Self {
104        BBox(
105            splat(f32::INFINITY).to_pt(),
106            splat(f32::NEG_INFINITY).to_pt(),
107        )
108    }
109}
110
111impl<'a, B: Default> Extend<&'a Point3<B>> for BBox<B> {
112    fn extend<I: IntoIterator<Item = &'a Point3<B>>>(&mut self, it: I) {
113        it.into_iter().for_each(|pt| self.extend(pt));
114    }
115}
116
117impl<'a, B: Default> FromIterator<&'a Point3<B>> for BBox<B> {
118    fn from_iter<I: IntoIterator<Item = &'a Point3<B>>>(it: I) -> Self {
119        let mut bbox = BBox::default();
120        it.into_iter().for_each(|pt| bbox.extend(pt));
121        bbox
122    }
123}
124
125#[cfg(test)]
126mod tests {
127    use crate::math::pt3;
128
129    use super::*;
130
131    #[test]
132    fn bbox_default() {
133        assert!(BBox::<()>::default().is_empty());
134        assert!(!BBox::<()>::default().contains(&Point3::origin()));
135    }
136
137    #[test]
138    fn bbox_extend() {
139        let mut bbox = BBox::<()>(pt3(-1.0, -2.0, -3.0), pt3(5.0, 3.0, 2.0));
140
141        bbox.extend(&pt3(1.0, 1.0, 1.0));
142        assert_eq!(bbox, BBox(pt3(-1.0, -2.0, -3.0), pt3(5.0, 3.0, 2.0)));
143
144        bbox.extend(&pt3(-2.0, 3.0, 3.0));
145        assert_eq!(bbox, BBox(pt3(-2.0, -2.0, -3.0), pt3(5.0, 3.0, 3.0)));
146    }
147
148    #[test]
149    fn bbox_is_empty() {
150        assert!(
151            BBox::<()>(pt3(-1.0, 0.0, -1.0), pt3(1.0, 0.0, 1.0)).is_empty()
152        );
153        assert!(
154            BBox::<()>(pt3(-1.0, -1.0, 1.0), pt3(1.0, 1.0, -1.0)).is_empty()
155        );
156        assert!(
157            !BBox::<()>(pt3(-1.0, -1.0, -1.0), pt3(1.0, 1.0, 1.0)).is_empty()
158        );
159        assert!(
160            !BBox::<()>(pt3(-1.0, 10.0, -1.0), pt3(1.0, f32::INFINITY, 1.0))
161                .is_empty()
162        );
163    }
164
165    #[test]
166    fn bbox_contains() {
167        assert!(
168            !BBox::<()>(pt3(-1.0, 0.0, -1.0), pt3(1.0, 0.0, 1.0))
169                .contains(&pt3(0.0, 1.0, 0.0))
170        );
171        assert!(
172            BBox::<()>(pt3(-1.0, 0.0, -1.0), pt3(1.0, 0.0, 1.0))
173                .contains(&pt3(0.0, 0.0, 0.0))
174        );
175        assert!(
176            BBox::<()>(pt3(-1.0, -1.0, -1.0), pt3(1.0, 1.0, 1.0))
177                .contains(&pt3(-1.0, 0.0, 0.0))
178        );
179        assert!(
180            BBox::<()>(pt3(-1.0, -1.0, -1.0), pt3(1.0, 1.0, 1.0))
181                .contains(&pt3(0.0, 0.0, 0.0))
182        );
183    }
184}