Accueil / Interface graphique
Interface graphique
L'interface interactive du Raytracer est un plugin chargé à l'exécution
(user_interface.so). Elle affiche la scène, les panneaux d'édition et le viewport de
rendu, et permet de manipuler les objets à la souris — jusqu'à l'édition individuelle des sommets
d'un maillage.
Vue d'ensemble
L'interface graphique est implémentée comme un plugin à l'exécution
(user_interface.so) chargé par le chargeur de plugins du cœur. Le point d'entrée est la
classe UserInterface (qui implémente IUserInterface), et son type de plugin
est PluginType::USER_INTERFACE. Le code source vit dans
srcs/plugins/user_interface/.
Le cycle d'intégration à l'exécution est le suivant :
- le cœur charge les plugins depuis
./plugins; - le plugin d'interface est récupéré par son type
USER_INTERFACE; create(ICoreAccess&)est appelé ;- l'interface utilise
ICoreAccesspour déclencher les actions de scène et de rendu ; - à la fermeture,
destroy()est appelé et l'instance du plugin est déchargée.
UserInterface crée et possède la boucle d'événements de la fenêtre SFML,
construit les menus, les panneaux latéraux et les widgets du viewport de rendu, transmet les actions
de l'utilisateur à ICoreAccess, synchronise la sélection du viewport avec celle des
panneaux, et affiche les notifications (toasts) et fenêtres auxiliaires. Elle prend en charge :
- les actions de cycle de vie de scène (nouveau / ouvrir / enregistrer / enregistrer sous / importer) ;
- la création d'objets depuis le menu (primitives / lumières) ;
- la sélection et l'édition de propriétés via les panneaux ;
- l'affichage de l'état du rendu et les contrôles de la vue de rendu ;
- les actions de connexion / démarrage de cluster via des fenêtres dédiées ;
- les notifications toast en cas de succès ou d'erreur.
L'interface dépend de SFML (graphics, window,
system, network). Si le plugin renderer du viewport est absent, l'interface
le signale par un toast et continue de fonctionner.
Le contrat Component
Tous les widgets dérivent d'une abstraction commune, Component, et partagent le même
cycle de vie :
| Étape | Rôle |
|---|---|
setFont(...) | Attribue la police utilisée pour le texte. |
layout(...) | Calcule la géométrie (pour les widgets qui possèdent leur position/taille). |
update(mouse) | Calcule l'état hovered (survol) selon la position de la souris. |
handleEvent(event, mouse) | Mute l'état en réaction aux événements et déclenche les callbacks. |
draw(window) | Dessine le widget dans la fenêtre. |
La structure de base expose les champs d'état enabled, hovered et
visible. Les helpers de projection et de picking du viewport (dont
projectToPixel(...), pickViewportLight(...) et le filtre de sliders de
matériau isMaterialFloatSlider(name, model)) sont regroupés dans
ViewportHelper.
Panneaux
Les panneaux (dans srcs/plugins/user_interface/panels/) forment la colonne d'inspection
latérale. Chacun se reconstruit dynamiquement selon l'objet sélectionné.
| Panneau | Rôle |
|---|---|
HierarchyPanel | Liste arborescente caméra / lumières / primitives, et gestion de la sélection. |
ObjectPanel | Inspecteur / éditeur des propriétés de l'objet sélectionné. |
MaterialPanel | Inspecteur focalisé sur le matériau de la primitive sélectionnée. |
RendererPanel | Affichage de la texture de rendu et contrôles de l'onglet de rendu. |
CameraPanel | Édition de la résolution, de la position et de la rotation de la caméra. |
HierarchyPanel
Affiche la scène sous forme de liste arborescente (caméra, lumières, primitives) et gère la
sélection. Il prend en charge la multi-sélection, la sélection de la caméra et la
synchronisation de la sélection avec le picking du viewport. Un callback de demande de masquage
(setOnItemHideRequest) permet de cacher un objet, et le panneau gère son propre
défilement et sa barre de défilement interne.
ObjectPanel
Inspecteur et éditeur des propriétés de l'objet sélectionné. Sa méthode clé
rebuild(const ISceneObject *currentObject) reconfigure le panneau dynamiquement selon le
type d'objet à l'exécution. Il expose :
- les transformations communes — position, rotation et échelle (scale) ;
- pour les primitives — un menu déroulant de matériau et les propriétés flottantes converties en sliders ;
- pour les lumières — un sélecteur de couleur et un champ d'intensité.
C'est aussi l'ancrage de l'édition de sommets : le bouton Convert to Mesh
(convertir en maillage), le navigateur de sommets < Point N / total >,
le champ Vertex et les boutons - / + de la ligne
Size y sont présentés (voir Édition de sommets).
MaterialPanel
Inspecteur focalisé sur le matériau des primitives sélectionnées. Sa méthode
rebuild(const ISceneObject *currentObject) construit les contrôles à partir de l'état
courant du matériau. Il propose :
- un sélecteur de modèle (
PhongouPBR) via unSegmentedControl; - l'édition de la couleur de base via un
ColorPicker; - des sliders flottants dynamiques, filtrés par modèle de matériau grâce à
ViewportHelper::isMaterialFloatSlider(...).
RendererPanel
Affiche la texture de sortie du rendu et les contrôles de l'onglet de rendu supérieur : texte de
commentaire / statut du rendu, bouton de fermeture du rendu, et utilitaires de correspondance
pixel-espace (getViewportPixel(...)). Il maintient l'échelle et les bornes du viewport
synchronisées avec les dimensions du rendu courant.
CameraPanel
Permet d'éditer la caméra active : la résolution (largeur et hauteur), la
position et la rotation, via des champs de texte et des champs
vectoriels. Sa méthode rebuild(ICamera *camera) renseigne les champs depuis la caméra
courante.
Composants réutilisables
Les widgets réutilisables sont définis dans
srcs/plugins/user_interface/components/. Ils sont assemblés par les panneaux et les
fenêtres.
| Composant | Rôle |
|---|---|
Button | Bouton cliquable, déclenche un callback onClick. |
Checkbox | Case à cocher booléenne. |
ColorPicker | Sélecteur de couleur (couleur de matériau, couleur de lumière). |
Dropdown | Menu déroulant de sélection (ex. matériau). |
Slider | Curseur pour une valeur flottante (propriétés de matériau). |
SegmentedControl | Sélecteur segmenté (ex. modèle Phong / PBR). |
TextField | Champ de saisie de texte / valeur. |
VectorField | Saisie d'un vecteur à trois composantes (position, rotation, échelle). |
Separator | Séparateur visuel entre les sections. |
| Système de menu | Barre de menus et items de menu (components/menu/). |
La plupart des widgets suivent le même patron commun :
- ils stockent leur géométrie dans des formes / textes SFML ;
- ils calculent l'état
hovereddansupdate(...); - ils mutent leur état dans
handleEvent(...); - ils exposent des callbacks (
onClick,onChange, etc.) ; - ils renvoient une indication de curseur via
getCursor().
Ce patron uniforme (setFont / layout / update /
handleEvent / draw + callbacks) rend l'ajout d'un nouveau widget très
mécanique : il suffit d'implémenter le contrat Component.
Fenêtres auxiliaires
Les fenêtres pop-up sont implémentées dans
srcs/plugins/user_interface/windows/. Elles dérivent de la classe abstraite
Window, base des sous-fenêtres SFML en thread séparé. Chaque fenêtre dérivée implémente
handleEvent(...), drawUi() et updateUi() ; loop()
crée la fenêtre et exécute le cycle poll / update / draw, tandis que destroy() arrête le
drapeau d'exécution et joint (ou détache) le thread proprement.
| Fenêtre | Rôle |
|---|---|
ExploratorWindow | Explorateur de fichiers/dossiers pour l'ouverture ou l'enregistrement d'une scène (modes OPEN / SAVE). |
LoadWindow | Fenêtre de décision fusion / import entre la scène courante et la scène chargée. |
JoinClusterWindow | Saisie des paramètres de connexion au cluster (IP et port). |
MarketWindow | Marché de matériaux (onglets Local et Online) pour parcourir et ajouter des matériaux. |
- ExploratorWindow : listage de répertoire avec navigation vers le parent
(
..), filtrage des fichiers cachés, champ de nom de fichier en mode enregistrement, et bouton de confirmation (Open / Save). - LoadWindow : présente des groupes de cases à cocher appariés pour les champs de la caméra et les coefficients de lumière d'environnement ; l'utilisateur choisit, propriété par propriété, si la valeur vient de la scène courante ou de la scène chargée, puis les valeurs retenues sont écrites dans la scène active.
- JoinClusterWindow : champs IP et port (validation numérique) ; le
callback
windowCallback(std::string ip, size_t port)est déclenché à la validation par Entrée ou au clic sur JOIN. - MarketWindow : parcours d'une bibliothèque de matériaux avec recherche, sous deux onglets — matériaux locaux et matériaux en ligne — et boutons d'ajout à la scène.
Notifications toast
Les toasts sont des notifications transitoires et non bloquantes empilées en bas à droite
de l'interface (toast/Toast.hpp, toast/ToastManager.hpp).
| Type | Usage |
|---|---|
| INFO | Information neutre. |
| SUCCESS | Confirmation d'une action réussie. |
| WARNING | Avertissement non bloquant. |
| ERROR | Erreur (ex. plugin renderer manquant). |
Le gestionnaire expose push(title, content, type) pour créer une entrée,
update() pour appliquer le fondu (apparition / disparition) et retirer les éléments
expirés, et draw(window) pour rendre la pile de toasts animés.
Gizmos de transformation
Quand un objet unique est sélectionné dans le viewport, un gizmo permet de le transformer directement à la souris. Trois outils sont disponibles :
| Outil | Raccourci | Action |
|---|---|---|
| Déplacer (Move) | G | Translation via les flèches et plans du gizmo. |
| Tourner (Rotate) | R | Rotation via les anneaux du gizmo. |
| Redimensionner (Scale) | S | Mise à l'échelle via les poignées du gizmo. |
La touche T bascule le repère du gizmo entre World (monde) et Local. Ces raccourcis n'agissent que lorsque le viewport a le focus, qu'un objet est sélectionné, que l'on n'est pas en mode édition de sommets et que le mode déplacement (vol libre) est désactivé — afin que G / R / S n'entrent pas en conflit avec les touches de vol ZQSD.
Le choix de l'outil est aussi accessible dans l'ObjectPanel (callbacks
onGizmoModeChanged / onGizmoSpaceChanged), et un badge d'état
(« lecture » du gizmo, repère World/Local) est dessiné dans le viewport.
Édition de sommets
L'édition de sommets permet de déplacer les sommets individuels d'une primitive éditable directement dans le viewport, en glissant des poignées à l'écran ou en tapant des coordonnées. C'est le pendant interactif de la transformation d'objet (position / rotation / échelle) exposée dans l'ObjectPanel.
Ce qui est éditable
Une primitive est éditable lorsqu'elle implémente IEditablePrimitive en plus de
IPrimitive. L'interface découvre cette capacité à l'exécution avec
dynamic_cast<IEditablePrimitive *>(primitive) — le même patron de capacité
runtime que le renderer utilise pour la sélection — de sorte que le contrat de plugin
IPrimitive reste intact.
| Primitive | Sommets |
|---|---|
| Triangle | ses 3 coins. |
| Mesh (maillage) | les sommets uniques (soudés) du .obj chargé ; un sommet partagé déplace toutes les faces incidentes. |
Les primitives analytiques (cube, sphère, plan, cylindre, cône, tore, tanglecube, fractale) sont définies par des paramètres plutôt que par des sommets et ne sont pas éditables sommet par sommet : déplacez-les / redimensionnez-les avec les champs de transformation de l'ObjectPanel.
Convert to Mesh
Pour éditer une primitive analytique sommet par sommet, sélectionnez-la et pressez
Convert to Mesh dans l'ObjectPanel. Sa surface est triangulée en un
maillage (cuit dans generated_meshes/<name>_<n>.obj), la primitive est
remplacée par ce maillage, et le maillage est sélectionné pour entrer immédiatement en mode édition.
Comme il s'agit d'un maillage normal adossé à un fichier, il s'enregistre et se recharge, et accepte
les vertex_overrides comme tout autre maillage.
- Les primitives infinies (un plan) ne peuvent pas être converties.
- Un cube est cuit exactement en ses 8 coins / 12 triangles (faces planes) et reste donc une boîte éditable propre.
- Le reste utilise un échantillonnage de surface générique : les formes lisses / convexes (sphère, cône, cylindre) ressortent proprement, tandis que les formes non convexes (tore, tanglecube) sont approximées.
Entrer / sortir du mode édition
- Entrer : sélectionnez une seule primitive éditable, puis pressez Tab — ou double-cliquez dessus dans le viewport.
- Sortir : pressez de nouveau Tab ou Échap. Sélectionner un autre objet (dans la hiérarchie, ou en supprimant celui édité) quitte aussi le mode édition.
- En mode édition, un bandeau « Edit Mode — <nom> » s'affiche au-dessus du viewport et un toast confirme la transition.
Le picking d'objet classique dans le viewport est désactivé en mode édition, afin qu'un clic ne sélectionne jamais un autre objet par accident. Le clic droit continue d'orbiter la caméra ; les touches de vol WASD/ZQSD restent disponibles sauf pendant un glissement de sommet actif (pour que les touches de verrouillage d'axe ne déplacent pas aussi la caméra).
Poignées, survol et picking
Chaque sommet est projeté à l'écran et dessiné comme un petit cercle :
| Couleur | Signification |
|---|---|
| Bleu | sommet au repos. |
| Blanc | le sommet sous le curseur. |
| Jaune | le sommet sélectionné. |
Le clic sélectionne le sommet dont la poignée est la plus proche du curseur, dans un petit rayon en pixels ; en cas d'égalité (poignées superposées), le sommet le plus proche de la caméra l'emporte. Les sommets derrière la caméra ou hors écran sont ignorés. Pour les maillages très denses, le nombre de poignées dessinées est plafonné (le picking les considère toutes).
Alternativement, utilisez le navigateur < Point N / total > de l'ObjectPanel (juste
au-dessus de la ligne Size) : les flèches parcourent les sommets un par un sans viser les
poignées à l'écran. Le pas entre en mode édition si nécessaire et met en évidence le sommet courant en
rouge vif sur la forme.
Glisser un sommet
Glissez une poignée pour déplacer son sommet. Par défaut, le sommet glisse dans le plan qui passe par sa position de départ et est parallèle à l'écran (normale = direction de la caméra) — le comportement intuitif « déplace-le là où je pointe ».
- Verrouillage d'axe : maintenez X, Y ou Z pendant le glissement pour contraindre le déplacement à cet axe du monde.
- Clavier : les coordonnées monde du sommet sélectionné apparaissent dans un champ Vertex de l'ObjectPanel ; taper une valeur déplace le sommet et reste synchronisé avec le glissement.
- Grossir / rétrécir (boutons - / +) : les boutons - / + de la ligne Size éloignent / rapprochent le sommet sélectionné du centroïde de la forme, pour pousser ou tirer un seul point. Sans sommet sélectionné, ces mêmes boutons redimensionnent l'objet entier.
Après chaque édition, le BVH de la scène est marqué « sale » et le viewport re-trace, de sorte que le changement est visible immédiatement.
Spécificités des maillages
- Déplacer un sommet partagé met à jour toutes les faces incidentes.
- Les normales des faces affectées sont recalculées, et les normales de sommet lisses (interpolées) sont rafraîchies comme la moyenne des normales des faces incidentes.
- Le BVH local du maillage est reconstruit au relâchement du sommet
(
onGeometryChanged). Pour les petits maillages il est aussi reconstruit en direct pendant le glissement ; pour les grands maillages (> ~4000 triangles), la surface rendue est légèrement en retard pendant le glissement et se cale sur la forme finale au relâchement, ce qui garde le glissement réactif.
Sauvegarde des éditions (sérialisation)
- Triangle — ses trois coins font déjà partie du JSON de scène, donc les positions éditées sont enregistrées et rechargées telles quelles.
- Mesh — le fichier
.objn'est jamais modifié. Les éditions sont stockées comme surcharges en espace-objet dans une listevertex_overridesde l'objet maillage, ré-appliquées après le chargement du.obj.
{
"name": "Cube",
"type": "mesh",
"file": "tests/obj/cube.obj",
"position": [26.0, 10.0, 15.0],
"rotation": [0.0, 0.0, 20.0],
"scale": [14.0, 14.0, 14.0],
"material": "Blue",
"vertex_overrides": [
{ "index": 0, "position": [1.0, -1.0, 0.857] }
]
}
Comme les surcharges sont en espace-objet, elles survivent aux changements ultérieurs de transformation
du maillage et font un aller-retour propre : charger → éditer → enregistrer → recharger
restitue la forme éditée. Voir tests/configs/vertex_edit.json pour une scène (un Triangle
et un petit cube maillé) prête à l'essai.
Limitations
- L'édition change uniquement la géométrie ; elle n'ajoute ni ne retire de sommets/faces.
- Les normales lisses recalculées aux sommets édités sont des moyennes géométriques, donc les arêtes
franches définies dans le
.obj(vn) sont localement adoucies là où vous éditez. - Le plan de déplacement est parallèle à la vue ; il n'y a pas de glissement séparé en profondeur (utilisez le champ Vertex ou un verrouillage d'axe pour un placement précis).
Raccourcis clavier
| Touche | Contexte | Action |
|---|---|---|
| G | Viewport, objet sélectionné | Outil gizmo Déplacer. |
| R | Viewport, objet sélectionné | Outil gizmo Tourner. |
| S | Viewport, objet sélectionné | Outil gizmo Redimensionner. |
| T | Viewport, objet sélectionné | Bascule le repère World / Local. |
| Tab | Primitive éditable sélectionnée | Entre / sort du mode édition de sommets. |
| Double-clic | Primitive éditable (viewport) | Entre en mode édition de sommets. |
| Échap | Mode édition | Sort du mode édition de sommets. |
| X / Y / Z | Pendant un glissement de sommet | Verrouille le déplacement sur l'axe X / Y / Z du monde. |
| - / + | Ligne Size de l'ObjectPanel | Éloigne / rapproche le sommet du centroïde (ou redimensionne l'objet si aucun sommet n'est sélectionné). |
Les touches de déplacement libre de la caméra (type WASD/ZQSD, avec la molette pour la vitesse) restent actives dans le viewport hors mode édition ; c'est pourquoi les raccourcis de gizmo sont désactivés quand le mode déplacement est actif.