Skip to main content

ling_graphics/
window.rs

1use crate::camera::Camera3D;
2use crate::color::Color;
3use crate::renderer::{Renderer, SoftwareRenderer};
4use crate::scene::{Scene, Transform as SceneTransform};
5use crate::renderer::FrameBuffer;
6use crate::viewport::Viewport;
7
8/// Terminal-based "window" that renders frames via `SoftwareRenderer`.
9///
10/// Renders a 3-D scene to an RGBA framebuffer then presents it either as
11/// coloured ANSI art in the terminal (`present_terminal`) or drives a full
12/// animation loop (`run`).
13pub struct Window {
14    pub viewport: Viewport,
15    renderer: SoftwareRenderer,
16    term_cols: usize,
17    term_rows: usize,
18}
19
20impl Window {
21    pub fn new(viewport: Viewport) -> Self {
22        Self {
23            viewport,
24            renderer: SoftwareRenderer::new(),
25            term_cols: 100,
26            term_rows: 40,
27        }
28    }
29
30    pub fn with_terminal_dimensions(mut self, cols: usize, rows: usize) -> Self {
31        self.term_cols = cols.max(10);
32        self.term_rows = rows.max(10);
33        self
34    }
35
36    /// Render the scene into the internal framebuffer and return a clone.
37    pub fn render_scene(&mut self, scene: &Scene, camera: &Camera3D) -> FrameBuffer {
38        self.renderer.begin_frame(
39            self.viewport.width,
40            self.viewport.height,
41            self.viewport.clear_color,
42        );
43
44        for &root in scene.roots() {
45            self.render_node_recursive(scene, root, camera, &scene.node(root).transform, 1.0);
46        }
47
48        self.renderer.end_frame().clone()
49    }
50
51    fn render_node_recursive(
52        &mut self,
53        scene: &Scene,
54        node_id: usize,
55        camera: &Camera3D,
56        _parent_transform: &SceneTransform,
57        _dummy_depth: f32,
58    ) {
59        let node = scene.node(node_id);
60        if node.visible {
61            if let (Some(mesh), Some(material)) = (node.mesh.as_ref(), node.material.as_ref()) {
62                let global = scene.global_transform(node_id);
63                let _ = global;
64                self.renderer.draw_mesh(mesh, &node.transform, material, camera);
65            }
66            // Collect children first to avoid borrow conflict
67            let children: Vec<usize> = node.children.clone();
68            let transform = node.transform.clone();
69            for child in children {
70                self.render_node_recursive(scene, child, camera, &transform, _dummy_depth);
71            }
72        }
73    }
74
75    /// Present the last rendered frame to the terminal as ANSI art.
76    pub fn present_terminal(&mut self) {
77        // Clone the framebuffer to release the mutable borrow on self.renderer
78        // before calling present_framebuffer_ascii which needs &self.
79        let fb = self.renderer.end_frame().clone();
80        self.present_framebuffer_ascii(&fb);
81    }
82
83    fn present_framebuffer_ascii(&self, fb: &FrameBuffer) {
84        let mut out = String::new();
85        out.push_str("\x1b[2J\x1b[H");
86
87        let w = fb.width as usize;
88        let h = fb.height as usize;
89        let step_x = (w / self.term_cols).max(1);
90        let step_y = (h / self.term_rows).max(1);
91
92        // Map luminance to ASCII density ramp
93        let chars = b" .:-=+*#%@";
94
95        for y in (0..h).step_by(step_y) {
96            for x in (0..w).step_by(step_x) {
97                let i = (y * w + x) * 4;
98                let r = fb.pixels[i]     as f32 / 255.0;
99                let g = fb.pixels[i + 1] as f32 / 255.0;
100                let b = fb.pixels[i + 2] as f32 / 255.0;
101                let lum = 0.2126 * r + 0.7152 * g + 0.0722 * b;
102                let idx = ((lum.clamp(0.0, 1.0)) * ((chars.len() - 1) as f32)) as usize;
103                out.push(chars[idx] as char);
104            }
105            out.push('\n');
106        }
107
108        print!("{}", out);
109    }
110
111    /// Drive the render / tick / present loop for `max_frames` frames.
112    pub fn run(
113        &mut self,
114        mut scene: Scene,
115        mut camera: Camera3D,
116        mut tick: impl FnMut(u64, &mut Scene, &mut Camera3D),
117        max_frames: u64,
118    ) {
119        for frame in 0..max_frames {
120            tick(frame, &mut scene, &mut camera);
121
122            // Render → clone the framebuffer → present.
123            // Cloning releases the mutable borrow of self.renderer so that
124            // present_framebuffer_ascii can borrow self immutably.
125            let fb = self.render_scene(&scene, &camera);
126            self.present_framebuffer_ascii(&fb);
127        }
128    }
129}