Skip to main content

orbit_viewer/
orbit_viewer.rs

1//! Exemple : visualiseur orbital affichant un cube animé, son repère XYZ,
2//! sa trajectoire et un petit graphe de télémétrie (altitude simulée).
3//!
4//! Lancer avec : `cargo run --example orbit_viewer --release`
5//!
6//! Écrit pour eframe/egui 0.35 : `App::ui(&mut self, ui: &mut egui::Ui, ...)`
7//! remplace l'ancien `App::update(&mut self, ctx: &egui::Context, ...)`, et
8//! les panels s'affichent maintenant via `.show(ui, ...)` plutôt que
9//! `.show(ctx, ...)`. `SidePanel` a été remplacé par `Panel` (unifié avec
10//! `TopBottomPanel`).
11
12use 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    // Paramètres configurables en direct dans le panneau
24    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        // 1. Simulation physique / télémétrie
50        if !self.paused {
51            let dt = ctx.input(|i| i.stable_dt).min(0.1); // Pas de temps fluide
52            self.time += dt * self.orbit_speed;
53
54            // Orbite circulaire avec oscillation verticale (altitude)
55            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            // Attachement d'attitude (Roll, Pitch, Yaw dynamiques)
62            self.state.rotation = Rotation3D::new(
63                (self.time * 2.0).sin() * 0.2, // Léger roulis
64                (self.time * 1.5).cos() * 0.3, // Tangage
65                self.time * 0.5,               // Lacet continu
66            );
67
68            // Mise à jour des historiques
69            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        // 2. Panneau de contrôle latéral (IHM)
78        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                // Contrôles de simulation
87                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                // Telemetry Data Box
100                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        // 3. Zone de rendu 3D
119        egui::CentralPanel::default().show(ui, |ui| {
120            // Calcul préalable du rectangle du graphe pour éviter tout conflit de borrow avec `ui`
121            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                // Rendu du maillage fil de fer du cube
129                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                // Repère d'axes XYZ sur l'objet
137                r.draw_axes(self.state.position, self.state.rotation, 1.2);
138
139                // Trajectoire historique
140                r.draw_trail(self.trail.as_slice(), egui::Color32::GOLD);
141
142                // Overlay Graphe 2D de télémétrie (Altitude)
143                r.draw_chart(
144                    chart_rect,
145                    &self.altitude_history,
146                    -1.0, // Min Y
147                    1.0,  // Max Y
148                    egui::Color32::GREEN,
149                );
150            });
151        });
152
153        // Demande le rafraîchissement continu de l'affichage
154        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}