Librairie de rendu 3D immédiat pour applications [eframe]/[egui] (0.35+), pensée
pour la visualisation de télémétrie temps réel : drones, robots, capteurs
d'orientation.
[dependencies]
drew-sim = "0.4"La feature stl (chargement de fichiers .stl) est activée par défaut.
Pour s'en passer :
drew-sim = { version = "0.4", default-features = false }| Action | Effet |
|---|---|
| Clic gauche + glisser | Orbite la caméra |
| Molette | Zoom |
| Clic droit ou clic molette + glisser | Panoramique |
Ces contrôles sont gérés automatiquement par Engine3D::render : rien à
faire côté application. En contrepartie, rien n'est affiché à l'écran
(pas de widget de caméra visible) : tout se fait à la souris.
engine.camera.reset() remet la caméra dans sa position initiale.
- Repère main droite, angles en radians, positifs dans le sens trigonométrique (règle de la main droite autour de l'axe).
rolltourne autour de Z,pitchautour de X,yawautour de Y, appliqués dans cet ordre :R = Ry(yaw) * Rx(pitch) * Rz(roll).- Y est l'axe vertical de la scène ; Z (axe de roulis) est l'« avant » du véhicule.
- Les angles renvoyés par
compose,inverse,normalizedetfrom_matrixsont normalisés :pitchdans [-π/2, π/2],rolletyawdans [-π, π]. Le triplet peut donc différer de celui d'entrée tout en représentant la même rotation ; comparer l'effet sur des points plutôt que les angles. - Les quaternions (
Quat) suivent la même convention :Quat::from_euler(r)équivaut àRy(yaw) * Rx(pitch) * Rz(roll). - La projection est orthographique avec une orbite horizontale uniquement (pas de perspective, pas d'inclinaison de caméra).
| Fonction | Rôle |
|---|---|
Engine3D::default() |
Caméra à orbit=0.5, zoom=1.0, grille par défaut |
engine.render(ui, |r| { ... }) |
Alloue la zone de dessin, applique la souris à la caméra, dessine la grille de sol, puis appelle la closure avec un Render3D prêt à l'emploi |
engine.camera |
Accès direct à l'OrbitCamera (ex : forcer un angle au démarrage) |
engine.camera.reset() |
Remet orbite, zoom et panoramique à leurs valeurs par défaut |
engine.grid |
GridStyle : couleurs et espacement de la grille au sol |
GridStyle::default().with_step_px(30.0).with_half_lines(20) |
Personnalise la grille (with_line_color, with_ground_color, with_background_color, with_step_px, with_half_lines). GridStyle est #[non_exhaustive] : on part de default() |
| Fonction | Rôle |
|---|---|
r.world_to_screen(pos) |
Projette un point 3D vers l'écran (utile pour dessiner autre chose que les primitives ci-dessous) |
r.draw_mesh(&mesh, position, rotation, color) |
Maillage en fil de fer (sommets projetés une fois, arêtes partagées dessinées une fois) |
r.draw_mesh_with_stroke(&mesh, position, rotation, stroke) |
Idem, avec un egui::Stroke complet (épaisseur de trait) |
r.draw_axes(position, rotation, length) |
Repère XYZ (Rouge = X, Vert = Y, Bleu = Z) |
r.draw_trail(&points, color) |
Polyligne reliant un historique de positions |
r.draw_chart(rect, &values, min, max, color) |
Graphe 2D (courbe de valeurs), indépendant de la caméra 3D, pour afficher un capteur en overlay |
| Fonction | Rôle |
|---|---|
Mesh3D::from_stl_file(path) |
Charge un .stl (nécessite la feature stl) |
Mesh3D::unit_cube(size) |
Cube généré sans dépendance externe, pratique pour tester rapidement |
Mesh3D::from_raw(vertices, triangles) |
Construit un maillage à la main, avec validation des indices |
mesh.triangle_count() |
Nombre de triangles |
mesh.edges() |
Arêtes uniques (calculées une fois, puis en cache) |
mesh.invalidate_edges() |
À appeler après avoir modifié mesh.triangles à la main |
| Fonction | Rôle |
|---|---|
TelemetryState { position, rotation } |
Contrat agnostique de la source (capteur réel, simulation, rejeu de log) |
TrailBuffer::new(capacity) |
Historique des capacity dernières positions (évite une fuite mémoire en session longue ; push amorti O(1)) |
trail.push(pos) / trail.as_slice() / trail.clear() |
Alimenter et lire le buffer |
trail.len() / trail.is_empty() / trail.capacity() |
Taille courante et capacité |
| Fonction | Rôle |
|---|---|
add(a, b) / sub(a, b) |
Addition / soustraction de deux Vec3 |
scale(a, s) |
Mise à l'échelle d'un Vec3 par un scalaire |
dot(a, b) |
Produit scalaire |
cross(a, b) |
Produit vectoriel |
length(a) |
Norme du vecteur |
normalize(a) |
Normalise le vecteur (renvoie a inchangé si trop proche de zéro) |
mat3_mul(a, b) / mat3_mul_vec(&m, v) / mat3_transpose(m) |
Produit de matrices 3x3, matrice x vecteur, transposée |
| Fonction | Rôle |
|---|---|
Rotation3D::new(roll, pitch, yaw) |
Angles en radians (const fn) |
Rotation3D::IDENTITY |
Rotation nulle |
rotation.rotate_point(p) |
Applique la rotation à un point |
rotation.rotate_points(&pts) / rotate_points_into(&pts, &mut out) |
Rotation d'un lot de points, matrice calculée une seule fois (la seconde réutilise un buffer) |
rotation.compose(other) |
Applique self puis other (produit de matrices exact). Près du gimbal lock (pitch ≈ ±90°), le résultat reste géométriquement correct mais roll et yaw peuvent être redistribués (yaw figé à 0) |
rotation.inverse() |
Rotation inverse |
rotation.slerp(other, t) |
Interpolation sphérique par le chemin le plus court (rejeu de logs) |
rotation.normalized() |
Même rotation, angles ramenés dans la plage normalisée |
rotation.to_matrix() / Rotation3D::from_matrix(m) |
Conversion angles ↔ matrice 3x3 |
Format natif des IMU et autopilotes (PX4, ArduPilot, BNO055…), sans gimbal lock.
| Fonction | Rôle |
|---|---|
Quat::IDENTITY / Quat::new(w, x, y, z) |
Rotation nulle / construction à partir des composantes |
Quat::from_axis_angle(axis, angle) |
Rotation autour d'un axe (l'axe est normalisé) |
Quat::from_euler(r) / q.to_euler() |
Conversion depuis / vers Rotation3D (aussi via From) |
q.compose(other) |
Applique q puis other, même sémantique que Rotation3D::compose |
a * b |
Produit de Hamilton : applique b d'abord, puis a (comme un produit de matrices) |
q.inverse() / q.conjugate() |
Inverse / conjugué |
q.normalize() |
Renormalise (à faire régulièrement si l'on intègre des vitesses angulaires, pour éviter la dérive) |
q.slerp(other, t) |
Interpolation sphérique, chemin le plus court |
q.rotate_point(p) / q.to_matrix() |
Application à un point / matrice 3x3 |
Depuis egui/eframe 0.35, App::update(ctx, frame) a été remplacé par
App::ui(ui, frame) : on reçoit directement un &mut egui::Ui plutôt qu'un
&egui::Context, et les panels se dessinent avec .show(ui, ...) au lieu de
.show(ctx, ...). Pour accéder au Context (ex : request_repaint), on
passe par ui.ctx().
use drew_sim::{Engine3D, Mesh3D, TelemetryState};
use drew_sim::egui;
struct App {
engine: Engine3D,
mesh: Mesh3D,
state: TelemetryState,
}
impl eframe::App for App {
fn ui(&mut self, ui: &mut egui::Ui, _frame: &mut eframe::Frame) {
egui::CentralPanel::default().show(ui, |ui| {
self.engine.render(ui, |r| {
r.draw_mesh(&self.mesh, self.state.position, self.state.rotation, egui::Color32::WHITE);
r.draw_axes(self.state.position, self.state.rotation, 1.5);
});
});
}
}
fn main() -> eframe::Result<()> {
eframe::run_native(
"drew-sim minimal",
eframe::NativeOptions::default(),
Box::new(|_cc| {
Ok(Box::new(App {
engine: Engine3D::default(),
mesh: Mesh3D::unit_cube(1.5),
state: TelemetryState::default(),
}))
}),
)
}use drew_sim::{Quat, Rotation3D, TelemetryState};
// Quaternion (w, x, y, z) fourni par le capteur, normalisé par précaution.
let q = Quat::new(0.9239, 0.0, 0.3827, 0.0).normalize();
let state = TelemetryState::new([0.0, 1.0, 0.0], q.to_euler());Les composantes (w, x, y, z) d'un capteur dépendent de son propre repère
(NED, ENU…) : il faut les exprimer dans le repère de drew-sim (voir
Conventions) avant de les convertir.
Un exemple plus riche (cube animé, panneau de contrôle, trail, graphe d'altitude) est fourni :
cargo run --example orbit_viewer --releaseRotation3D::rotate_pointetcomposeprennentself(typeCopy) au lieu de&self: les appelsr.rotate_point(p)sont inchangés, seuls les appels écritsRotation3D::rotate_point(&r, p)doivent perdre le&.Rotation3D::composerenvoie des angles normalisés (voir Conventions).Mesh3Da désormais un champ privé (cache d'arêtes) : on ne peut plus l'écrireMesh3D { vertices, triangles }hors de la crate. UtiliserMesh3D::from_raw,unit_cubeoufrom_stl_file.GridStyleetMeshErrorsont#[non_exhaustive]: partir deGridStyle::default()puiswith_*, et prévoir un bras_ =>dans lesmatchsurMeshError.
- Rendu avec faces pleines / z-buffer simple (actuellement fil de fer)
- Caméra en perspective avec inclinaison (actuellement orthographique, orbite horizontale)
- Import d'autres formats de maillage (OBJ)
- HUD optionnel affichant les contrôles souris et l'état de la caméra
- Feature optionnelle
glam(conversionsQuat/Vec3) - CI (fmt, clippy, tests, doc) et MSRV déclarée
GPL-2.0-or-later, voir LICENSE.
Copyright (C) 2026 Jorge Andre Castro