Raytracer docs 🌐 EN

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.

SFML Plugin .so ICoreAccess Édition interactive

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 :

  1. le cœur charge les plugins depuis ./plugins ;
  2. le plugin d'interface est récupéré par son type USER_INTERFACE ;
  3. create(ICoreAccess&) est appelé ;
  4. l'interface utilise ICoreAccess pour déclencher les actions de scène et de rendu ;
  5. à 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 :

ÉtapeRô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é.

PanneauRôle
HierarchyPanelListe arborescente caméra / lumières / primitives, et gestion de la sélection.
ObjectPanelInspecteur / éditeur des propriétés de l'objet sélectionné.
MaterialPanelInspecteur focalisé sur le matériau de la primitive sélectionnée.
RendererPanelAffichage 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 (Phong ou PBR) via un SegmentedControl ;
  • 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.

ComposantRôle
ButtonBouton cliquable, déclenche un callback onClick.
CheckboxCase à cocher booléenne.
ColorPickerSélecteur de couleur (couleur de matériau, couleur de lumière).
DropdownMenu déroulant de sélection (ex. matériau).
SliderCurseur pour une valeur flottante (propriétés de matériau).
SegmentedControlSélecteur segmenté (ex. modèle Phong / PBR).
TextFieldChamp de saisie de texte / valeur.
VectorFieldSaisie d'un vecteur à trois composantes (position, rotation, échelle).
SeparatorSéparateur visuel entre les sections.
Système de menuBarre de menus et items de menu (components/menu/).

La plupart des widgets suivent le même patron commun :

  1. ils stockent leur géométrie dans des formes / textes SFML ;
  2. ils calculent l'état hovered dans update(...) ;
  3. ils mutent leur état dans handleEvent(...) ;
  4. ils exposent des callbacks (onClick, onChange, etc.) ;
  5. 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êtreRôle
ExploratorWindowExplorateur de fichiers/dossiers pour l'ouverture ou l'enregistrement d'une scène (modes OPEN / SAVE).
LoadWindowFenêtre de décision fusion / import entre la scène courante et la scène chargée.
JoinClusterWindowSaisie des paramètres de connexion au cluster (IP et port).
MarketWindowMarché 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).

TypeUsage
INFOInformation neutre.
SUCCESSConfirmation d'une action réussie.
WARNINGAvertissement non bloquant.
ERRORErreur (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 :

OutilRaccourciAction
Déplacer (Move)GTranslation via les flèches et plans du gizmo.
Tourner (Rotate)RRotation via les anneaux du gizmo.
Redimensionner (Scale)SMise à 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.

PrimitiveSommets
Triangleses 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 :

CouleurSignification
Bleusommet au repos.
Blancle sommet sous le curseur.
Jaunele 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 .obj n'est jamais modifié. Les éditions sont stockées comme surcharges en espace-objet dans une liste vertex_overrides de 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

ToucheContexteAction
GViewport, objet sélectionnéOutil gizmo Déplacer.
RViewport, objet sélectionnéOutil gizmo Tourner.
SViewport, objet sélectionnéOutil gizmo Redimensionner.
TViewport, objet sélectionnéBascule le repère World / Local.
TabPrimitive éditable sélectionnéeEntre / sort du mode édition de sommets.
Double-clicPrimitive éditable (viewport)Entre en mode édition de sommets.
ÉchapMode éditionSort du mode édition de sommets.
X / Y / ZPendant un glissement de sommetVerrouille 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.