orbit_viewer/
orbit_viewer.rs1use drew_sim::egui;
13use drew_sim::{Engine3D, Mesh3D, Rotation3D, TelemetryState, TrailBuffer};
14
15struct OrbitViewerApp {
16 engine: Engine3D,
17 mesh: Mesh3D,
18 state: TelemetryState,
19 trail: TrailBuffer,
20 altitude_history: Vec<f32>,
21 time: f32,
22
23 orbit_speed: f32,
25 orbit_radius: f32,
26 paused: bool,
27}
28
29impl Default for OrbitViewerApp {
30 fn default() -> Self {
31 Self {
32 engine: Engine3D::default(),
33 mesh: Mesh3D::unit_cube(1.2),
34 state: TelemetryState::default(),
35 trail: TrailBuffer::new(200),
36 altitude_history: Vec::with_capacity(200),
37 time: 0.0,
38 orbit_speed: 1.0,
39 orbit_radius: 2.0,
40 paused: false,
41 }
42 }
43}
44
45impl eframe::App for OrbitViewerApp {
46 fn ui(&mut self, ui: &mut egui::Ui, _frame: &mut eframe::Frame) {
47 let ctx = ui.ctx().clone();
48
49 if !self.paused {
51 let dt = ctx.input(|i| i.stable_dt).min(0.1); self.time += dt * self.orbit_speed;
53
54 self.state.position = [
56 self.time.cos() * self.orbit_radius,
57 (self.time * 1.5).sin() * 0.6,
58 self.time.sin() * self.orbit_radius,
59 ];
60
61 self.state.rotation = Rotation3D::new(
63 (self.time * 2.0).sin() * 0.2, (self.time * 1.5).cos() * 0.3, self.time * 0.5, );
67
68 self.trail.push(self.state.position);
70
71 self.altitude_history.push(self.state.position[1]);
72 if self.altitude_history.len() > 200 {
73 self.altitude_history.remove(0);
74 }
75 }
76
77 egui::Panel::left("control_panel")
79 .resizable(true)
80 .default_size(240.0)
81 .show(ui, |ui| {
82 ui.heading("🛰️ Orbit Viewer");
83 ui.small("Station de sol & Télémétrie");
84 ui.separator();
85
86 ui.label("Contrôles de l'orbite :");
88 ui.checkbox(&mut self.paused, "Pause simulation");
89 ui.add(egui::Slider::new(&mut self.orbit_speed, 0.1..=3.0).text("Vitesse"));
90 ui.add(egui::Slider::new(&mut self.orbit_radius, 0.5..=4.0).text("Rayon"));
91
92 if ui.button("🗑️ Effacer la trajectoire").clicked() {
93 self.trail.clear();
94 self.altitude_history.clear();
95 }
96
97 ui.separator();
98
99 ui.label("Données en direct :");
101 egui::Frame::group(ui.style()).show(ui, |ui| {
102 ui.monospace(format!("Pos X: {:+.2} m", self.state.position[0]));
103 ui.monospace(format!("Alt Y: {:+.2} m", self.state.position[1]));
104 ui.monospace(format!("Pos Z: {:+.2} m", self.state.position[2]));
105 ui.separator();
106 ui.monospace(format!("Pitch: {:+.2} rad", self.state.rotation.pitch));
107 ui.monospace(format!("Yaw: {:+.2} rad", self.state.rotation.yaw));
108 });
109
110 ui.separator();
111 ui.collapsing("🖱️ Contrôles Caméra", |ui| {
112 ui.small("• Clic Gauche + Glisser : Orbite");
113 ui.small("• Molette : Zoom");
114 ui.small("• Clic Droit / Molette : Panoramique");
115 });
116 });
117
118 egui::CentralPanel::default().show(ui, |ui| {
120 let avail_rect = ui.available_rect_before_wrap();
122 let chart_rect = egui::Rect::from_min_size(
123 egui::pos2(avail_rect.max.x - 230.0, avail_rect.min.y + 15.0),
124 egui::vec2(215.0, 90.0),
125 );
126
127 self.engine.render(ui, |r| {
128 r.draw_mesh(
130 &self.mesh,
131 self.state.position,
132 self.state.rotation,
133 egui::Color32::from_rgb(100, 200, 255),
134 );
135
136 r.draw_axes(self.state.position, self.state.rotation, 1.2);
138
139 r.draw_trail(self.trail.as_slice(), egui::Color32::GOLD);
141
142 r.draw_chart(
144 chart_rect,
145 &self.altitude_history,
146 -1.0, 1.0, egui::Color32::GREEN,
149 );
150 });
151 });
152
153 ctx.request_repaint();
155 }
156}
157
158fn main() -> eframe::Result<()> {
159 let options = eframe::NativeOptions {
160 viewport: egui::ViewportBuilder::default()
161 .with_inner_size([1100.0, 680.0])
162 .with_title("drew-sim Orbit Viewer Example"),
163 ..Default::default()
164 };
165
166 eframe::run_native(
167 "drew-sim — orbit viewer",
168 options,
169 Box::new(|_cc| Ok(Box::new(OrbitViewerApp::default()))),
170 )
171}