Skip to main content

ling/gfx/
depth.rs

1// src/gfx/depth.rs — deferred depth-sorted draw queue (painter's algorithm).
2//
3// All 3-D draw calls (`วาดสามเหลี่ยม3มิติ`, `วาดเส้น3มิติ`) push a `DrawCall`
4// into this queue instead of rasterising immediately.  When `แสดงผล` / `present`
5// is called, the queue is sorted back-to-front by the depth tag and then
6// flushed into the pixel buffer.
7//
8// Painter's algorithm is exact for convex non-intersecting geometry and
9// produces plausible results for the Sierpiński fractal + tesseract wireframe.
10
11// `raster` is wasm-safe (pure CPU); the software-framebuffer flush runs on web too.
12use crate::gfx::raster;
13
14/// Tagged draw call stored in the queue.
15#[derive(Debug, Clone)]
16pub struct DrawCall {
17    /// Camera-space z of the face/edge centroid — larger = further away.
18    pub depth: f32,
19    /// Pre-lit 0x00RRGGBB colour.
20    pub color: u32,
21    pub kind:  DrawKind,
22}
23
24#[derive(Debug, Clone)]
25pub enum DrawKind {
26    Triangle { x0:f32, y0:f32, x1:f32, y1:f32, x2:f32, y2:f32 },
27    /// Gouraud-interpolated + per-pixel posterised triangle (smooth cel).
28    TriangleG { x0:f32, y0:f32, c0:u32, x1:f32, y1:f32, c1:u32, x2:f32, y2:f32, c2:u32, bands:u32 },
29    Line     { x0:f32, y0:f32, x1:f32, y1:f32 },
30}
31
32/// Deferred depth-sorted draw queue.
33#[derive(Default, Debug)]
34pub struct DepthQueue {
35    calls: Vec<DrawCall>,
36}
37
38impl DepthQueue {
39    /// Queue a filled triangle.
40    pub fn push_triangle(
41        &mut self, depth: f32, color: u32,
42        x0:f32, y0:f32, x1:f32, y1:f32, x2:f32, y2:f32,
43    ) {
44        self.calls.push(DrawCall {
45            depth, color,
46            kind: DrawKind::Triangle { x0, y0, x1, y1, x2, y2 },
47        });
48    }
49
50    /// Queue a Gouraud + posterised triangle (smooth cel).
51    #[allow(clippy::too_many_arguments)]
52    pub fn push_triangle_g(
53        &mut self, depth: f32,
54        x0:f32, y0:f32, c0:u32, x1:f32, y1:f32, c1:u32, x2:f32, y2:f32, c2:u32, bands:u32,
55    ) {
56        self.calls.push(DrawCall {
57            depth, color: c0,
58            kind: DrawKind::TriangleG { x0,y0,c0, x1,y1,c1, x2,y2,c2, bands },
59        });
60    }
61
62    /// Queue a line segment.
63    pub fn push_line(
64        &mut self, depth: f32, color: u32,
65        x0:f32, y0:f32, x1:f32, y1:f32,
66    ) {
67        self.calls.push(DrawCall {
68            depth, color,
69            kind: DrawKind::Line { x0, y0, x1, y1 },
70        });
71    }
72
73    /// Sort back-to-front and rasterise everything into `buf`.
74    /// Consumes `self` — call site does `mem::take` to avoid borrow conflict.
75    pub fn flush(mut self, buf: &mut Vec<u32>, width: usize, height: usize) {
76        // Sort largest depth first (furthest → painted first, nearest on top)
77        self.calls.sort_unstable_by(|a, b| {
78            b.depth.partial_cmp(&a.depth).unwrap_or(std::cmp::Ordering::Equal)
79        });
80        for call in &self.calls {
81            match call.kind {
82                DrawKind::Triangle { x0, y0, x1, y1, x2, y2 } =>
83                    raster::fill_triangle(buf, width, height, call.color,
84                                         x0, y0, x1, y1, x2, y2),
85                DrawKind::TriangleG { x0,y0,c0, x1,y1,c1, x2,y2,c2, bands } =>
86                    raster::fill_triangle_gouraud(buf, width, height,
87                                         x0,y0,c0, x1,y1,c1, x2,y2,c2, bands),
88                DrawKind::Line { x0, y0, x1, y1 } =>
89                    raster::draw_line(buf, width, height, call.color,
90                                      x0, y0, x1, y1),
91            }
92        }
93        // `self` dropped here — no need to clear explicitly
94    }
95
96    pub fn is_empty(&self) -> bool { self.calls.is_empty() }
97
98    /// Consume the queue and send all draw calls to the WebGL backend.
99    /// Only compiled for wasm32 targets.
100    #[cfg(target_arch = "wasm32")]
101    pub fn flush_to_webgl(mut self, fill_r: f32, fill_g: f32, fill_b: f32, width: usize, height: usize) {
102        // Sort back-to-front (painter's algorithm) — same as the native path.
103        self.calls.sort_unstable_by(|a, b| {
104            b.depth.partial_cmp(&a.depth).unwrap_or(std::cmp::Ordering::Equal)
105        });
106        for call in &self.calls {
107            match call.kind {
108                DrawKind::Triangle { x0, y0, x1, y1, x2, y2 } =>
109                    crate::gfx::webgl::push_triangle(call.color, x0, y0, x1, y1, x2, y2, call.depth),
110                DrawKind::TriangleG { x0,y0,c0, x1,y1,c1, x2,y2,c2, bands:_ } => {
111                    // WebGL path: approximate with the averaged vertex colour.
112                    let avg = {
113                        let r=((c0>>16&0xFF)+(c1>>16&0xFF)+(c2>>16&0xFF))/3;
114                        let g=((c0>>8&0xFF)+(c1>>8&0xFF)+(c2>>8&0xFF))/3;
115                        let b=((c0&0xFF)+(c1&0xFF)+(c2&0xFF))/3;
116                        (r<<16)|(g<<8)|b
117                    };
118                    crate::gfx::webgl::push_triangle(avg, x0, y0, x1, y1, x2, y2, call.depth);
119                }
120                DrawKind::Line { x0, y0, x1, y1 } =>
121                    crate::gfx::webgl::push_line(call.color, x0, y0, x1, y1, call.depth),
122            }
123        }
124        crate::gfx::webgl::flush(fill_r, fill_g, fill_b, width, height);
125    }
126}