You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
QtMeshEditor currently has exactly one hard-coded directional light in every scene. The author of src/Manager.cpp:200 left an honest TODO right next to it:
//TODO: Add the hability of the user adding/removing lightsmSceneMgr->setAmbientLight(Ogre::ColourValue(0.3f, 0.3f, 0.3f));
Ogre::Light* light = mSceneMgr->createLight();
light->setType(Ogre::Light::LT_DIRECTIONAL);
light->setDiffuseColour(1.0f, 1.0f, 1.0f);
light->setSpecularColour(.8f, .8f, .8f);
Ogre::SceneNode* lightSceneNode = mSceneMgr->getRootSceneNode()->createChildSceneNode();
lightSceneNode->attachObject(light);
lightSceneNode->setDirection(1, -1, 1);
This epic closes that TODO and turns lighting into a first-class feature: users can add, edit, move, duplicate, group, enable/disable, and delete lights of multiple types; tune colour, intensity, range, falloff, spot cones, and (where applicable) shadows; preview with a light gizmo in the viewport; pick from preset rigs (three-point, studio, sunset); import/export lights as part of the scene; and reach every operation through GUI, CLI, and MCP — matching the conventions established by the recent UV (#458) and HDR (#466) epics.
This epic is complementary to the HDR/IBL epic (#466). HDR provides image-based ambient and reflections; this epic provides direct/local lights. Together they cover the full Phong-and-PBR lighting picture.
Why
The TODO is years old and visible in the code. It limits every screenshot, export preview, and material authoring session to one fixed angle.
Material authoring is unrealistic with one light. Real PBR validation needs at least a key + fill + back (three-point) rig. Specular highlights, rim light, falloff, and shadow shape only emerge with multiple lights at different angles and intensities.
Indie devs ship game assets. Their target engines (Unity, Unreal, Godot, Bevy) all use the standard point/spot/directional/area light vocabulary. QtMeshEditor must speak the same language so authored lighting "looks the same" when the asset lands in the target engine.
Scene export is already half-done.MeshImporterExporter::sceneExporter() handles multi-entity scenes; lights are the missing piece for round-tripping a fully-lit preview to a glTF scene.
Architecture
A new singleton, LightManager (src/LightManager.{h,cpp}), mirroring the pattern of Manager, SelectionSet, and the new singletons added by the HDR (#466) and AI (#397) epics:
Owns the list of user-created lights as (name, Ogre::Light*, Ogre::SceneNode*) triples.
Lights live under named SceneNodes under the scene root so they integrate cleanly with the existing Manager::sceneNodeCreated / SelectionSet / TransformOperator pipelines — selecting a light works exactly like selecting any other scene node.
Distinguishes user-created lights from internal lights (e.g. the eventual material preview light, the existing hard-coded default) via a Ogre::MovableObject::getUserAny() tag, so the right inspector and outliner only show user lights.
Emits lightCreated, lightChanged, lightDeleted consumed by the QML inspector, the viewport gizmo overlays, and the MCP server.
Persists per-scene state into the project file (extends the existing scene export path) and into .material/.scene-adjacent metadata.
Manager::CreateEmptyScene keeps creating a single default key light during early Slices so the editor stays usable; once Slice E (presets/rigs) lands, the default becomes the "Three-point studio" rig and the hard-coded line goes away.
Selection and transforms reuse the existing infrastructure:
Selecting a light's scene node selects it in SelectionSet.
TransformOperator moves/rotates the light just like any other node (W/E shortcuts work on lights for translate/rotate; scale is no-op for non-area lights).
Undo/redo via UndoManager — new CreateLightCommand, DeleteLightCommand, EditLightPropertyCommand (mergeable for slider drags, same pattern as TranslateCommand).
Child Issues
Slices A–D are the minimum viable multi-light editor; E–H bring it to parity with other 3D tools; I is power-user stretch.
Sentry breadcrumbs scene.light.* for every light action.
No regression in non-lit / unlit materials (wireframe, ambient-only).
CLAUDE.md updated with a "Scene Lighting" section under Architecture.
Dependencies & related issues
Epic: HDR — HDR lighting, IBL, and tone mapping #466 (HDR/IBL) — provides ambient + image-based reflections. This epic provides direct/local lights. They render together correctly: IBL fills ambient/specular ambient, dynamic lights add direct contribution. No ordering dependency, but Slice E's "sunset" rig should match the bundled sunset_outdoor HDRI for a coherent default.
Manager::CreateEmptyScene (src/Manager.cpp:195) — the existing single-light setup is replaced in Slice A.
MaterialPreviewRenderer keeps its own internal preview light untouched; not affected by this epic.
Out of scope
Light baking / lightmap generation. Out of scope. The procedural texture pipeline (ogre-procedural's TextureLightBaker) is unrelated and not exposed here.
GI / global illumination beyond IBL ambient. Real GI (path tracing, light propagation volumes, etc.) is a much larger initiative.
Custom light shader code. Use Ogre's stock light contribution and the existing RTSS PBR + Phong paths.
Volumetric / atmospheric scattering. Future epic.
Real-time light cookies (projected textures) — see Slice I stretch.
Notes for implementers
Lights are scene nodes. Reuse selection/transform/undo infrastructure — do not invent a parallel system.
Internal lights (preview-renderer, RTSS internal) must stay isolated from user lights via a getUserAny() tag so they don't pollute the inspector or scene export.
Property edits during slider drag should merge into a single undo command (mirror TranslateCommand's mergeWith), not push one command per value change.
Light gizmos must use the same Qt::Tool / overlay conventions as MeshInfoOverlay and ViewCube to avoid Ogre direct-render artifacts.
glTF scene export already handles entities. Extending it to KHR_lights_punctual is the canonical path for Slice G — don't invent a private format.
All shadow controls in Slice F must keep the no-shadow path identical in performance to today's behaviour, so users who don't want shadows pay nothing.
Every slice ships as its own PR with screenshots, following the slice-A→slice-H cadence used in Phase 5 and the UV/HDR epics.
Overview
QtMeshEditor currently has exactly one hard-coded directional light in every scene. The author of
src/Manager.cpp:200left an honest TODO right next to it:This epic closes that TODO and turns lighting into a first-class feature: users can add, edit, move, duplicate, group, enable/disable, and delete lights of multiple types; tune colour, intensity, range, falloff, spot cones, and (where applicable) shadows; preview with a light gizmo in the viewport; pick from preset rigs (three-point, studio, sunset); import/export lights as part of the scene; and reach every operation through GUI, CLI, and MCP — matching the conventions established by the recent UV (#458) and HDR (#466) epics.
This epic is complementary to the HDR/IBL epic (#466). HDR provides image-based ambient and reflections; this epic provides direct/local lights. Together they cover the full Phong-and-PBR lighting picture.
Why
MeshImporterExporter::sceneExporter()handles multi-entity scenes; lights are the missing piece for round-tripping a fully-lit preview to a glTF scene.Architecture
A new singleton,
LightManager(src/LightManager.{h,cpp}), mirroring the pattern ofManager,SelectionSet, and the new singletons added by the HDR (#466) and AI (#397) epics:(name, Ogre::Light*, Ogre::SceneNode*)triples.SceneNodes under the scene root so they integrate cleanly with the existingManager::sceneNodeCreated/SelectionSet/TransformOperatorpipelines — selecting a light works exactly like selecting any other scene node.Ogre::MovableObject::getUserAny()tag, so the right inspector and outliner only show user lights.lightCreated,lightChanged,lightDeletedconsumed by the QML inspector, the viewport gizmo overlays, and the MCP server..material/.scene-adjacent metadata.Manager::CreateEmptyScenekeeps creating a single default key light during early Slices so the editor stays usable; once Slice E (presets/rigs) lands, the default becomes the "Three-point studio" rig and the hard-coded line goes away.Selection and transforms reuse the existing infrastructure:
SelectionSet.TransformOperatormoves/rotates the light just like any other node (W/Eshortcuts work on lights for translate/rotate; scale is no-op for non-area lights).UndoManager— newCreateLightCommand,DeleteLightCommand,EditLightPropertyCommand(mergeable for slider drags, same pattern asTranslateCommand).Child Issues
Slices A–D are the minimum viable multi-light editor; E–H bring it to parity with other 3D tools; I is power-user stretch.
LightManagerfoundation + replace hard-coded lightAcceptance Criteria (epic-level)
TransformOperatorgizmos.qtmesh light add|list|remove|editandqtmesh light --apply-rig three_pointwork.create_light,delete_light,list_lights,set_light_property,apply_light_rig.scene.light.*for every light action.CLAUDE.mdupdated with a "Scene Lighting" section under Architecture.Dependencies & related issues
sunset_outdoorHDRI for a coherent default.Manager::CreateEmptyScene(src/Manager.cpp:195) — the existing single-light setup is replaced in Slice A.MaterialPreviewRendererkeeps its own internal preview light untouched; not affected by this epic.Out of scope
ogre-procedural'sTextureLightBaker) is unrelated and not exposed here.Notes for implementers
getUserAny()tag so they don't pollute the inspector or scene export.TranslateCommand'smergeWith), not push one command per value change.Qt::Tool/ overlay conventions asMeshInfoOverlayandViewCubeto avoid Ogre direct-render artifacts.