Skip to main content

drew_sim/
render.rs

1//! Contexte de rendu : projection 3D -> 2D et primitives de dessin.
2
3use crate::math::{Rotation3D, Vec3};
4use crate::mesh::Mesh3D;
5use eframe::egui;
6
7/// Contexte transmis lors du callback de dessin d'une frame.
8///
9/// Regroupe tout ce qu'il faut pour projeter un point 3D vers l'écran et
10/// dessiner par-dessus (maillages, repères, trajectoires, graphes).
11pub struct Render3D<'a> {
12    pub painter: &'a egui::Painter,
13    pub center: egui::Pos2,
14    pub ground_y: f32,
15    pub camera_orbit: f32,
16    pub zoom: f32,
17}
18
19impl<'a> Render3D<'a> {
20    /// Projection d'un point du monde 3D vers un point écran 2D.
21    ///
22    /// Projection orthographique simplifiée avec rotation orbitale sur
23    /// l'axe horizontal ; suffisante pour de la télémétrie temps réel.
24    pub fn world_to_screen(&self, pos: Vec3) -> egui::Pos2 {
25        let base_scale = 50.0 * self.zoom;
26        let (sin_o, cos_o) = self.camera_orbit.sin_cos();
27        let rot_x = pos[0] * cos_o - pos[2] * sin_o;
28
29        egui::pos2(
30            self.center.x + (rot_x * base_scale),
31            self.ground_y - (pos[1] * base_scale),
32        )
33    }
34
35    /// Dessine un maillage en fil de fer à la position/orientation données.
36    pub fn draw_mesh(&self, mesh: &Mesh3D, position: Vec3, rotation: Rotation3D, color: egui::Color32) {
37        self.draw_mesh_with_stroke(mesh, position, rotation, egui::Stroke::new(1.0, color));
38    }
39
40    /// Variante de `draw_mesh` avec un `Stroke` complet (épaisseur incluse).
41    pub fn draw_mesh_with_stroke(
42        &self,
43        mesh: &Mesh3D,
44        position: Vec3,
45        rotation: Rotation3D,
46        stroke: egui::Stroke,
47    ) {
48        for tri in &mesh.triangles {
49            let screen: Vec<egui::Pos2> = tri
50                .iter()
51                .map(|&idx| {
52                    let r = rotation.rotate_point(mesh.vertices[idx]);
53                    let world = [r[0] + position[0], r[1] + position[1], r[2] + position[2]];
54                    self.world_to_screen(world)
55                })
56                .collect();
57
58            self.painter.line_segment([screen[0], screen[1]], stroke);
59            self.painter.line_segment([screen[1], screen[2]], stroke);
60            self.painter.line_segment([screen[2], screen[0]], stroke);
61        }
62    }
63
64    /// Dessine le repère d'axes XYZ (Rouge=X, Vert=Y, Bleu=Z) à une position.
65    pub fn draw_axes(&self, position: Vec3, rotation: Rotation3D, length: f32) {
66        let origin = position;
67        let tip = |axis: Vec3| {
68            let r = rotation.rotate_point(axis);
69            self.world_to_screen([origin[0] + r[0], origin[1] + r[1], origin[2] + r[2]])
70        };
71
72        let p_origin = self.world_to_screen(origin);
73        let p_x = tip([length, 0.0, 0.0]);
74        let p_y = tip([0.0, length, 0.0]);
75        let p_z = tip([0.0, 0.0, length]);
76
77        self.painter.line_segment([p_origin, p_x], egui::Stroke::new(2.5, egui::Color32::RED));
78        self.painter.line_segment([p_origin, p_y], egui::Stroke::new(2.5, egui::Color32::GREEN));
79        self.painter.line_segment([p_origin, p_z], egui::Stroke::new(2.5, egui::Color32::BLUE));
80    }
81
82    /// Dessine l'historique d'une trajectoire (polyligne reliant `points`).
83    pub fn draw_trail(&self, points: &[Vec3], color: egui::Color32) {
84        if points.len() < 2 {
85            return;
86        }
87        let stroke = egui::Stroke::new(1.5, color);
88        for w in points.windows(2) {
89            let p1 = self.world_to_screen(w[0]);
90            let p2 = self.world_to_screen(w[1]);
91            self.painter.line_segment([p1, p2], stroke);
92        }
93    }
94
95    /// Dessine un graphique 2D (courbe de valeurs) dans un rectangle donné
96    /// de l'écran, indépendamment de la caméra 3D.
97    pub fn draw_chart(&self, rect: egui::Rect, values: &[f32], min_val: f32, max_val: f32, color: egui::Color32) {
98        if values.len() < 2 || (max_val - min_val).abs() < f32::EPSILON {
99            return;
100        }
101
102        self.painter.rect_filled(rect, 4.0, egui::Color32::from_black_alpha(150));
103        self.painter.rect_stroke(
104            rect,
105            4.0,
106            egui::Stroke::new(1.0, egui::Color32::GRAY),
107            egui::StrokeKind::Middle,
108        );
109
110        let (width, height, len) = (rect.width(), rect.height(), values.len());
111        let stroke = egui::Stroke::new(1.5, color);
112
113        let point_at = |i: usize| {
114            let x_norm = i as f32 / (len - 1) as f32;
115            let y_norm = ((values[i] - min_val) / (max_val - min_val)).clamp(0.0, 1.0);
116            egui::pos2(rect.min.x + x_norm * width, rect.max.y - y_norm * height)
117        };
118
119        for i in 0..len - 1 {
120            self.painter.line_segment([point_at(i), point_at(i + 1)], stroke);
121        }
122    }
123}