1use crate::math::{Rotation3D, Vec3};
4use crate::mesh::Mesh3D;
5use eframe::egui;
6
7pub 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 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 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 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 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 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 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}