Skip to main content

embedded_3dgfx/
primitive.rs

1use embedded_graphics_core::pixelcolor::Rgb565;
2use nalgebra::Point2;
3
4#[derive(Debug, Clone)]
5pub enum DrawPrimitive {
6    ColoredPoint(Point2<i32>, Rgb565),
7    Line([Point2<i32>; 2], Rgb565),
8    ColoredTriangle([Point2<i32>; 3], Rgb565),
9    ColoredTriangleWithDepth {
10        points: [Point2<i32>; 3],
11        depths: [f32; 3],
12        color: Rgb565,
13    },
14    TranslucentTriangleWithDepth {
15        points: [Point2<i32>; 3],
16        depths: [f32; 3],
17        color: Rgb565,
18        alpha: u8,
19    },
20    /// Screen-door dithered stipple triangle (zero-cost, order-independent transparency).
21    ScreenDoorTriangleWithDepth {
22        points: [Point2<i32>; 3],
23        depths: [f32; 3],
24        color: Rgb565,
25        alpha: u8,
26    },
27    #[cfg(feature = "lighting")]
28    GouraudTriangle {
29        points: [Point2<i32>; 3],
30        colors: [Rgb565; 3],
31    },
32    #[cfg(feature = "lighting")]
33    GouraudTriangleWithDepth {
34        points: [Point2<i32>; 3],
35        depths: [f32; 3],
36        colors: [Rgb565; 3],
37    },
38    #[cfg(feature = "textured")]
39    TexturedTriangle {
40        points: [Point2<i32>; 3],
41        uvs: [[f32; 2]; 3],
42        texture_id: u32,
43    },
44    #[cfg(feature = "textured")]
45    TexturedTriangleWithDepth {
46        points: [Point2<i32>; 3],
47        depths: [f32; 3],
48        ws: [f32; 3],
49        uvs: [[f32; 2]; 3],
50        texture_id: u32,
51    },
52    #[cfg(feature = "textured")]
53    TexturedGouraudTriangleWithDepth {
54        points: [Point2<i32>; 3],
55        depths: [f32; 3],
56        ws: [f32; 3],
57        uvs: [[f32; 2]; 3],
58        colors: [Rgb565; 3],
59        texture_id: u32,
60    },
61    /// Perspective-correct textured triangle with baked lightmap.
62    ///
63    /// The final pixel colour is:
64    /// `clamp(surface.sample(su,sv) × lightmap.sample(lu,lv) + dynamic_tint)`
65    /// where `×` is per-channel normalised multiply.
66    /// Set `lightmap_id = u32::MAX` to fall back to full-bright surface colour.
67    /// Set `dynamic_tint = Rgb565::new(0,0,0)` for no dynamic lighting.
68    #[cfg(feature = "textured")]
69    LightmappedTriangle {
70        points: [Point2<i32>; 3],
71        depths: [f32; 3],
72        ws: [f32; 3],
73        surface_uvs: [[f32; 2]; 3],
74        lm_uvs: [[f32; 2]; 3],
75        texture_id: u32,
76        lightmap_id: u32,
77        /// Per-face brightness multiplier in 0..=255 (255 = no darkening).
78        brightness: u8,
79        /// Additive RGB565 tint from runtime point lights.
80        dynamic_tint: Rgb565,
81    },
82}
83
84impl DrawPrimitive {
85    /// Calculate screen-space axis-aligned bounding box (min_x, min_y, max_x, max_y)
86    pub fn bounds(&self) -> (i32, i32, i32, i32) {
87        match self {
88            DrawPrimitive::ColoredPoint(p, _) => (p.x, p.y, p.x, p.y),
89            DrawPrimitive::Line([a, b], _) => {
90                (a.x.min(b.x), a.y.min(b.y), a.x.max(b.x), a.y.max(b.y))
91            }
92            DrawPrimitive::ColoredTriangle(points, _)
93            | DrawPrimitive::ColoredTriangleWithDepth { points, .. }
94            | DrawPrimitive::TranslucentTriangleWithDepth { points, .. }
95            | DrawPrimitive::ScreenDoorTriangleWithDepth { points, .. } => {
96                let min_x = points.iter().map(|p| p.x).min().unwrap_or(0);
97                let min_y = points.iter().map(|p| p.y).min().unwrap_or(0);
98                let max_x = points.iter().map(|p| p.x).max().unwrap_or(0);
99                let max_y = points.iter().map(|p| p.y).max().unwrap_or(0);
100                (min_x, min_y, max_x, max_y)
101            }
102            #[cfg(feature = "lighting")]
103            DrawPrimitive::GouraudTriangle { points, .. }
104            | DrawPrimitive::GouraudTriangleWithDepth { points, .. } => {
105                let min_x = points.iter().map(|p| p.x).min().unwrap_or(0);
106                let min_y = points.iter().map(|p| p.y).min().unwrap_or(0);
107                let max_x = points.iter().map(|p| p.x).max().unwrap_or(0);
108                let max_y = points.iter().map(|p| p.y).max().unwrap_or(0);
109                (min_x, min_y, max_x, max_y)
110            }
111            #[cfg(feature = "textured")]
112            DrawPrimitive::TexturedTriangle { points, .. }
113            | DrawPrimitive::TexturedTriangleWithDepth { points, .. }
114            | DrawPrimitive::TexturedGouraudTriangleWithDepth { points, .. }
115            | DrawPrimitive::LightmappedTriangle { points, .. } => {
116                let min_x = points.iter().map(|p| p.x).min().unwrap_or(0);
117                let min_y = points.iter().map(|p| p.y).min().unwrap_or(0);
118                let max_x = points.iter().map(|p| p.x).max().unwrap_or(0);
119                let max_y = points.iter().map(|p| p.y).max().unwrap_or(0);
120                (min_x, min_y, max_x, max_y)
121            }
122        }
123    }
124}
125
126#[cfg(test)]
127mod tests {
128    use super::*;
129    use embedded_graphics_core::pixelcolor::RgbColor;
130
131    fn pts() -> [Point2<i32>; 3] {
132        [Point2::new(-2, 0), Point2::new(3, -4), Point2::new(1, 5)]
133    }
134
135    #[test]
136    fn primitive_bounds_cover_every_family() {
137        let p = pts();
138        assert_eq!(
139            DrawPrimitive::ColoredPoint(p[0], Rgb565::RED).bounds(),
140            (-2, 0, -2, 0)
141        );
142        assert_eq!(
143            DrawPrimitive::Line([p[0], p[2]], Rgb565::RED).bounds(),
144            (-2, 0, 1, 5)
145        );
146        assert_eq!(
147            DrawPrimitive::ColoredTriangle(p, Rgb565::RED).bounds(),
148            (-2, -4, 3, 5)
149        );
150        assert_eq!(
151            DrawPrimitive::ColoredTriangleWithDepth {
152                points: p,
153                depths: [1.0; 3],
154                color: Rgb565::RED,
155            }
156            .bounds(),
157            (-2, -4, 3, 5)
158        );
159        assert_eq!(
160            DrawPrimitive::TranslucentTriangleWithDepth {
161                points: p,
162                depths: [1.0; 3],
163                color: Rgb565::RED,
164                alpha: 128,
165            }
166            .bounds(),
167            (-2, -4, 3, 5)
168        );
169        assert_eq!(
170            DrawPrimitive::ScreenDoorTriangleWithDepth {
171                points: p,
172                depths: [1.0; 3],
173                color: Rgb565::RED,
174                alpha: 128,
175            }
176            .bounds(),
177            (-2, -4, 3, 5)
178        );
179
180        #[cfg(feature = "lighting")]
181        {
182            assert_eq!(
183                DrawPrimitive::GouraudTriangle {
184                    points: p,
185                    colors: [Rgb565::RED; 3],
186                }
187                .bounds(),
188                (-2, -4, 3, 5)
189            );
190            assert_eq!(
191                DrawPrimitive::GouraudTriangleWithDepth {
192                    points: p,
193                    depths: [1.0; 3],
194                    colors: [Rgb565::RED; 3],
195                }
196                .bounds(),
197                (-2, -4, 3, 5)
198            );
199        }
200
201        #[cfg(feature = "textured")]
202        {
203            let uvs = [[0.0; 2]; 3];
204            assert_eq!(
205                DrawPrimitive::TexturedTriangle {
206                    points: p,
207                    uvs,
208                    texture_id: 0,
209                }
210                .bounds(),
211                (-2, -4, 3, 5)
212            );
213            assert_eq!(
214                DrawPrimitive::TexturedTriangleWithDepth {
215                    points: p,
216                    depths: [1.0; 3],
217                    ws: [1.0; 3],
218                    uvs,
219                    texture_id: 0,
220                }
221                .bounds(),
222                (-2, -4, 3, 5)
223            );
224            assert_eq!(
225                DrawPrimitive::TexturedGouraudTriangleWithDepth {
226                    points: p,
227                    depths: [1.0; 3],
228                    ws: [1.0; 3],
229                    uvs,
230                    colors: [Rgb565::RED; 3],
231                    texture_id: 0,
232                }
233                .bounds(),
234                (-2, -4, 3, 5)
235            );
236            assert_eq!(
237                DrawPrimitive::LightmappedTriangle {
238                    points: p,
239                    depths: [1.0; 3],
240                    ws: [1.0; 3],
241                    surface_uvs: uvs,
242                    lm_uvs: uvs,
243                    texture_id: 0,
244                    lightmap_id: 0,
245                    brightness: 255,
246                    dynamic_tint: Rgb565::BLACK,
247                }
248                .bounds(),
249                (-2, -4, 3, 5)
250            );
251        }
252    }
253}