From 2a2fd53c4f3c4e54b89a9861a21f2d4aaa357435 Mon Sep 17 00:00:00 2001 From: Sergey Lapin Date: Fri, 3 Apr 2026 20:06:37 +0300 Subject: [PATCH] Camera and Light components --- src/features/editScene/CMakeLists.txt | 14 ++ src/features/editScene/EditorApp.cpp | 24 +++ src/features/editScene/EditorApp.hpp | 4 + src/features/editScene/components/Camera.hpp | 37 ++++ .../editScene/components/CameraModule.cpp | 53 ++++++ src/features/editScene/components/Light.hpp | 48 +++++ .../editScene/components/LightModule.cpp | 43 +++++ .../editScene/systems/CameraSystem.cpp | 118 ++++++++++++ .../editScene/systems/CameraSystem.hpp | 40 ++++ .../editScene/systems/EditorUISystem.cpp | 156 +++++++--------- .../editScene/systems/EditorUISystem.hpp | 1 + .../editScene/systems/LightSystem.cpp | 123 ++++++++++++ .../editScene/systems/LightSystem.hpp | 37 ++++ .../editScene/systems/SceneSerializer.cpp | 176 ++++++++++++++++++ .../editScene/systems/SceneSerializer.hpp | 4 + src/features/editScene/ui/CameraEditor.cpp | 88 +++++++++ src/features/editScene/ui/CameraEditor.hpp | 27 +++ .../editScene/ui/ComponentRegistration.cpp | 21 +++ .../editScene/ui/ComponentRegistration.hpp | 79 ++++++++ .../editScene/ui/ComponentRegistry.hpp | 72 +++++-- src/features/editScene/ui/LightEditor.cpp | 136 ++++++++++++++ src/features/editScene/ui/LightEditor.hpp | 20 ++ 22 files changed, 1218 insertions(+), 103 deletions(-) create mode 100644 src/features/editScene/components/Camera.hpp create mode 100644 src/features/editScene/components/CameraModule.cpp create mode 100644 src/features/editScene/components/Light.hpp create mode 100644 src/features/editScene/components/LightModule.cpp create mode 100644 src/features/editScene/systems/CameraSystem.cpp create mode 100644 src/features/editScene/systems/CameraSystem.hpp create mode 100644 src/features/editScene/systems/LightSystem.cpp create mode 100644 src/features/editScene/systems/LightSystem.hpp create mode 100644 src/features/editScene/ui/CameraEditor.cpp create mode 100644 src/features/editScene/ui/CameraEditor.hpp create mode 100644 src/features/editScene/ui/ComponentRegistration.cpp create mode 100644 src/features/editScene/ui/ComponentRegistration.hpp create mode 100644 src/features/editScene/ui/LightEditor.cpp create mode 100644 src/features/editScene/ui/LightEditor.hpp diff --git a/src/features/editScene/CMakeLists.txt b/src/features/editScene/CMakeLists.txt index 13a4571..b8b2221 100644 --- a/src/features/editScene/CMakeLists.txt +++ b/src/features/editScene/CMakeLists.txt @@ -14,10 +14,17 @@ set(EDITSCENE_SOURCES systems/EditorUISystem.cpp systems/SceneSerializer.cpp systems/PhysicsSystem.cpp + systems/LightSystem.cpp + systems/CameraSystem.cpp ui/TransformEditor.cpp ui/RenderableEditor.cpp ui/PhysicsColliderEditor.cpp ui/RigidBodyEditor.cpp + ui/LightEditor.cpp + ui/CameraEditor.cpp + ui/ComponentRegistration.cpp + components/LightModule.cpp + components/CameraModule.cpp camera/EditorCamera.cpp gizmo/Gizmo.cpp physics/physics.cpp @@ -31,15 +38,22 @@ set(EDITSCENE_HEADERS components/Relationship.hpp components/PhysicsCollider.hpp components/RigidBody.hpp + components/Light.hpp + components/Camera.hpp systems/EditorUISystem.hpp systems/SceneSerializer.hpp systems/PhysicsSystem.hpp + systems/LightSystem.hpp + systems/CameraSystem.hpp ui/ComponentEditor.hpp ui/ComponentRegistry.hpp + ui/ComponentRegistration.hpp ui/TransformEditor.hpp ui/RenderableEditor.hpp ui/PhysicsColliderEditor.hpp ui/RigidBodyEditor.hpp + ui/LightEditor.hpp + ui/CameraEditor.hpp camera/EditorCamera.hpp gizmo/Gizmo.hpp physics/physics.h diff --git a/src/features/editScene/EditorApp.cpp b/src/features/editScene/EditorApp.cpp index 3262e9f..9aef223 100644 --- a/src/features/editScene/EditorApp.cpp +++ b/src/features/editScene/EditorApp.cpp @@ -2,6 +2,8 @@ #include "EditorApp.hpp" #include "systems/EditorUISystem.hpp" #include "systems/PhysicsSystem.hpp" +#include "systems/LightSystem.hpp" +#include "systems/CameraSystem.hpp" #include "camera/EditorCamera.hpp" #include "components/EntityName.hpp" #include "components/Transform.hpp" @@ -9,6 +11,8 @@ #include "components/EditorMarker.hpp" #include "components/PhysicsCollider.hpp" #include "components/RigidBody.hpp" +#include "components/Light.hpp" +#include "components/Camera.hpp" #include #include @@ -111,6 +115,12 @@ void EditorApp::setup() m_physicsSystem = std::make_unique(m_world, m_sceneMgr); m_physicsSystem->initialize(); m_uiSystem->setPhysicsSystem(m_physicsSystem.get()); + + // Setup light system + m_lightSystem = std::make_unique(m_world, m_sceneMgr); + + // Setup camera system + m_cameraSystem = std::make_unique(m_world, m_sceneMgr); // Add default entities to UI cache for (auto &e : m_defaultEntities) { @@ -143,6 +153,10 @@ void EditorApp::setupECS() // Register physics components m_world.component(); m_world.component(); + + // Register light and camera components + m_world.component(); + m_world.component(); } void EditorApp::createDefaultEntities() @@ -270,6 +284,16 @@ bool EditorApp::frameRenderingQueued(const Ogre::FrameEvent &evt) if (m_physicsSystem) { m_physicsSystem->update(evt.timeSinceLastFrame); } + + // Update lights + if (m_lightSystem) { + m_lightSystem->update(); + } + + // Update cameras + if (m_cameraSystem) { + m_cameraSystem->update(); + } // Don't call base class - it crashes when iterating input listeners return true; diff --git a/src/features/editScene/EditorApp.hpp b/src/features/editScene/EditorApp.hpp index 3f057e9..17f01e6 100644 --- a/src/features/editScene/EditorApp.hpp +++ b/src/features/editScene/EditorApp.hpp @@ -14,6 +14,8 @@ class EditorUISystem; class EditorCamera; class EditorPhysicsSystem; +class EditorLightSystem; +class EditorCameraSystem; /** * RenderTargetListener for ImGui frame management @@ -80,6 +82,8 @@ private: std::unique_ptr m_camera; std::unique_ptr m_imguiListener; std::unique_ptr m_physicsSystem; + std::unique_ptr m_lightSystem; + std::unique_ptr m_cameraSystem; // State uint16_t m_currentModifiers; diff --git a/src/features/editScene/components/Camera.hpp b/src/features/editScene/components/Camera.hpp new file mode 100644 index 0000000..4fd7407 --- /dev/null +++ b/src/features/editScene/components/Camera.hpp @@ -0,0 +1,37 @@ +#ifndef EDITSCENE_CAMERA_HPP +#define EDITSCENE_CAMERA_HPP +#pragma once + +#include + +/** + * Camera component - attaches an Ogre::Camera to the entity's SceneNode + */ +struct CameraComponent { + // Camera properties + float fovY = 45.0f; // Vertical field of view in degrees + float nearClip = 0.1f; // Near clip distance + float farClip = 1000.0f; // Far clip distance + float aspectRatio = 16.0f / 9.0f; // Aspect ratio (width/height) + + // For orthographic camera + bool orthographic = false; + float orthoWidth = 10.0f; // Width for orthographic view + float orthoHeight = 10.0f; // Height for orthographic view + + // The Ogre camera object (created by CameraSystem) + Ogre::Camera* camera = nullptr; + + // For preview (optional RTT - created on demand) + Ogre::RenderTarget* previewTarget = nullptr; + Ogre::TexturePtr previewTexture; + bool showPreview = false; + int previewWidth = 400; + int previewHeight = 300; + + void markDirty() { needsRebuild = true; } + bool needsRebuild = true; + bool needsPreviewUpdate = false; +}; + +#endif // EDITSCENE_CAMERA_HPP diff --git a/src/features/editScene/components/CameraModule.cpp b/src/features/editScene/components/CameraModule.cpp new file mode 100644 index 0000000..cdfa083 --- /dev/null +++ b/src/features/editScene/components/CameraModule.cpp @@ -0,0 +1,53 @@ +#include "Camera.hpp" +#include "Transform.hpp" +#include "../ui/ComponentRegistration.hpp" +#include "../ui/CameraEditor.hpp" +#include "../systems/CameraSystem.hpp" + +// Register Camera component +REGISTER_COMPONENT("Camera", CameraComponent, CameraEditor) +{ + registry.registerComponent( + "Camera", + std::make_unique(sceneMgr), + // Adder + [sceneMgr](flecs::entity e) { + if (!e.has()) { + // Camera requires Transform + if (!e.has()) { + // Auto-add transform if missing + TransformComponent transform; + transform.node = sceneMgr->getRootSceneNode()->createChildSceneNode(); + e.set(transform); + } + e.set({}); + } + }, + // Remover + [sceneMgr](flecs::entity e) { + if (e.has()) { + auto& camera = e.get_mut(); + // Clean up Ogre camera and preview resources + if (camera.camera) { + // Clean up preview + if (camera.previewTarget) { + camera.previewTarget->removeAllListeners(); + } + if (camera.previewTexture) { + Ogre::TextureManager::getSingleton().remove( + camera.previewTexture->getName()); + } + + // Detach and destroy camera + Ogre::SceneNode* parent = camera.camera->getParentSceneNode(); + if (parent) { + parent->detachObject(camera.camera); + } + sceneMgr->destroyCamera(camera.camera); + camera.camera = nullptr; + } + e.remove(); + } + } + ); +} diff --git a/src/features/editScene/components/Light.hpp b/src/features/editScene/components/Light.hpp new file mode 100644 index 0000000..e578fc1 --- /dev/null +++ b/src/features/editScene/components/Light.hpp @@ -0,0 +1,48 @@ +#ifndef EDITSCENE_LIGHT_HPP +#define EDITSCENE_LIGHT_HPP +#pragma once + +#include + +/** + * Light component - attaches an Ogre::Light to the entity's SceneNode + */ +struct LightComponent { + enum class LightType { + Point, // Omnidirectional light + Directional, // Parallel rays (sun/moon) + Spotlight // Cone-shaped light + }; + + LightType lightType = LightType::Point; + + // Common properties + Ogre::ColourValue diffuseColor{0.8f, 0.8f, 0.8f}; + Ogre::ColourValue specularColor{0.5f, 0.5f, 0.5f}; + float intensity = 1.0f; + + // Attenuation (for Point and Spot) + float range = 100.0f; + float constantAttenuation = 1.0f; + float linearAttenuation = 0.0f; + float quadraticAttenuation = 0.0f; + + // Spotlight specific + float spotlightInnerAngle = 30.0f; // Degrees + float spotlightOuterAngle = 45.0f; // Degrees + float spotlightFalloff = 1.0f; + + // Direction (for Directional and Spot, relative to node orientation) + Ogre::Vector3 direction{0, -1, 0}; + + // Cast shadows + bool castShadows = true; + + // The Ogre light object (created by LightSystem) + Ogre::Light* light = nullptr; + + void markDirty() { needsRebuild = true; } + bool needsRebuild = true; +}; + +#endif // EDITSCENE_LIGHT_HPP diff --git a/src/features/editScene/components/LightModule.cpp b/src/features/editScene/components/LightModule.cpp new file mode 100644 index 0000000..e0e7633 --- /dev/null +++ b/src/features/editScene/components/LightModule.cpp @@ -0,0 +1,43 @@ +#include "Light.hpp" +#include "Transform.hpp" +#include "../ui/ComponentRegistration.hpp" +#include "../ui/LightEditor.hpp" +#include "../systems/LightSystem.hpp" + +// Register Light component +REGISTER_COMPONENT("Light", LightComponent, LightEditor) +{ + registry.registerComponent( + "Light", + std::make_unique(), + // Adder + [sceneMgr](flecs::entity e) { + if (!e.has()) { + // Light requires Transform + if (!e.has()) { + // Auto-add transform if missing + TransformComponent transform; + transform.node = sceneMgr->getRootSceneNode()->createChildSceneNode(); + e.set(transform); + } + e.set({}); + } + }, + // Remover + [sceneMgr](flecs::entity e) { + if (e.has()) { + auto& light = e.get_mut(); + // Clean up Ogre light + if (light.light) { + Ogre::SceneNode* parent = light.light->getParentSceneNode(); + if (parent) { + parent->detachObject(light.light); + } + sceneMgr->destroyLight(light.light); + light.light = nullptr; + } + e.remove(); + } + } + ); +} diff --git a/src/features/editScene/systems/CameraSystem.cpp b/src/features/editScene/systems/CameraSystem.cpp new file mode 100644 index 0000000..b786d2e --- /dev/null +++ b/src/features/editScene/systems/CameraSystem.cpp @@ -0,0 +1,118 @@ +#include "CameraSystem.hpp" +#include "../components/Camera.hpp" +#include "../components/Transform.hpp" +#include + +EditorCameraSystem::EditorCameraSystem(flecs::world& world, Ogre::SceneManager* sceneMgr) + : m_world(world) + , m_sceneMgr(sceneMgr) + , m_cameraQuery(world.query()) +{ +} + +EditorCameraSystem::~EditorCameraSystem() = default; + +void EditorCameraSystem::update() +{ + m_cameraQuery.each([&](flecs::entity entity, CameraComponent& camera, TransformComponent& transform) { + if (camera.needsRebuild || !camera.camera) { + updateCamera(entity, camera, transform); + } + + // Update preview if needed + if (camera.showPreview && camera.needsPreviewUpdate && camera.previewTarget) { + camera.previewTarget->update(); + camera.needsPreviewUpdate = false; + } + }); +} + +void EditorCameraSystem::processAllCameras() +{ + m_cameraQuery.each([&](flecs::entity entity, CameraComponent& camera, TransformComponent& transform) { + updateCamera(entity, camera, transform); + }); +} + +Ogre::Camera* EditorCameraSystem::getActiveCamera() const +{ + Ogre::Camera* result = nullptr; + m_cameraQuery.each([&](flecs::entity entity, CameraComponent& camera, TransformComponent& transform) { + if (!result && camera.camera) { + result = camera.camera; + } + }); + return result; +} + +void EditorCameraSystem::updateCamera(flecs::entity entity, CameraComponent& camera, + TransformComponent& transform) +{ + // Remove existing camera if any + if (camera.camera) { + removeCamera(camera); + } + + // Need a valid scene node to attach the camera + if (!transform.node) { + Ogre::LogManager::getSingleton().logMessage( + "CameraSystem: Cannot create camera - entity has no SceneNode"); + return; + } + + // Create unique name for the camera + std::string cameraName = "Camera_" + std::to_string(entity.id()); + + // Create the Ogre camera + camera.camera = m_sceneMgr->createCamera(cameraName); + + // Attach to the entity's scene node + transform.node->attachObject(camera.camera); + + // Set projection type + if (camera.orthographic) { + camera.camera->setProjectionType(Ogre::PT_ORTHOGRAPHIC); + camera.camera->setOrthoWindow(camera.orthoWidth, camera.orthoHeight); + } else { + camera.camera->setProjectionType(Ogre::PT_PERSPECTIVE); + camera.camera->setFOVy(Ogre::Degree(camera.fovY)); + } + + // Set clip distances + camera.camera->setNearClipDistance(camera.nearClip); + camera.camera->setFarClipDistance(camera.farClip); + + // Set aspect ratio + camera.camera->setAspectRatio(camera.aspectRatio); + + camera.needsRebuild = false; + + Ogre::LogManager::getSingleton().logMessage( + "CameraSystem: Created " + + std::string(camera.orthographic ? "orthographic" : "perspective") + + " camera '" + cameraName + "'"); +} + +void EditorCameraSystem::removeCamera(CameraComponent& camera) +{ + if (camera.camera) { + // Clean up preview resources + if (camera.previewTarget) { + camera.previewTarget->removeAllListeners(); + camera.previewTarget = nullptr; + } + if (camera.previewTexture) { + Ogre::TextureManager::getSingleton().remove(camera.previewTexture->getName()); + camera.previewTexture.reset(); + } + + // Detach from parent node first + Ogre::SceneNode* parent = camera.camera->getParentSceneNode(); + if (parent) { + parent->detachObject(camera.camera); + } + + m_sceneMgr->destroyCamera(camera.camera); + camera.camera = nullptr; + } +} diff --git a/src/features/editScene/systems/CameraSystem.hpp b/src/features/editScene/systems/CameraSystem.hpp new file mode 100644 index 0000000..24507af --- /dev/null +++ b/src/features/editScene/systems/CameraSystem.hpp @@ -0,0 +1,40 @@ +#ifndef EDITSCENE_CAMERASYSTEM_HPP +#define EDITSCENE_CAMERASYSTEM_HPP +#pragma once + +#include +#include + +/** + * Camera system - manages Ogre::Camera objects for entities with CameraComponent + */ +class EditorCameraSystem { +public: + EditorCameraSystem(flecs::world& world, Ogre::SceneManager* sceneMgr); + ~EditorCameraSystem(); + + // Update cameras (process dirty components) + void update(); + + // Process all cameras immediately (e.g., after scene load) + void processAllCameras(); + + // Get the active editor camera (first camera found, or nullptr) + Ogre::Camera* getActiveCamera() const; + +private: + // Create/update a camera from component + void updateCamera(flecs::entity entity, class CameraComponent& camera, + class TransformComponent& transform); + + // Remove a camera + void removeCamera(CameraComponent& camera); + + flecs::world& m_world; + Ogre::SceneManager* m_sceneMgr; + + // Query for entities with Camera and Transform + flecs::query m_cameraQuery; +}; + +#endif // EDITSCENE_CAMERASYSTEM_HPP diff --git a/src/features/editScene/systems/EditorUISystem.cpp b/src/features/editScene/systems/EditorUISystem.cpp index 7a14d3f..22f6a70 100644 --- a/src/features/editScene/systems/EditorUISystem.cpp +++ b/src/features/editScene/systems/EditorUISystem.cpp @@ -5,10 +5,13 @@ #include "../components/EditorMarker.hpp" #include "../components/PhysicsCollider.hpp" #include "../components/RigidBody.hpp" +#include "../components/Light.hpp" +#include "../components/Camera.hpp" #include "../ui/TransformEditor.hpp" #include "../ui/RenderableEditor.hpp" #include "../ui/PhysicsColliderEditor.hpp" #include "../ui/RigidBodyEditor.hpp" +#include "../ui/ComponentRegistration.hpp" #include "PhysicsSystem.hpp" #include #include @@ -143,6 +146,15 @@ void EditorUISystem::registerComponentEditors() e.remove(); } }); + + // Register modular components (Light, Camera, etc.) + registerModularComponents(); +} + +void EditorUISystem::registerModularComponents() +{ + // This calls all component modules registered via REGISTER_COMPONENT macro + ComponentRegistration::registerAll(m_componentRegistry, m_sceneMgr, m_physicsSystem); } void EditorUISystem::update() @@ -341,6 +353,10 @@ void EditorUISystem::renderEntityNode(flecs::entity entity, int depth) indicators += " [T]"; if (entity.has()) indicators += " [R]"; + if (entity.has()) + indicators += " [L]"; + if (entity.has()) + indicators += " [Cam]"; if (entity.has()) indicators += " [RB]"; if (entity.has()) @@ -453,35 +469,53 @@ void EditorUISystem::renderComponentList(flecs::entity entity) // Render component editors ImGui::BeginChild("Components", ImVec2(0, 0), true); + // Dynamically render all components the entity has + int componentCount = 0; + + // Render Transform first if present (it's the base component) if (entity.has()) { auto &transform = entity.get_mut(); - m_componentRegistry.render(entity, - transform); + m_componentRegistry.render(entity, transform); + componentCount++; } + // Render Renderable if (entity.has()) { auto &renderable = entity.get_mut(); - m_componentRegistry.render(entity, - renderable); + m_componentRegistry.render(entity, renderable); + componentCount++; } + // Render Light if present + if (entity.has()) { + auto &light = entity.get_mut(); + m_componentRegistry.render(entity, light); + componentCount++; + } + + // Render Camera if present + if (entity.has()) { + auto &camera = entity.get_mut(); + m_componentRegistry.render(entity, camera); + componentCount++; + } + + // Render RigidBody if (entity.has()) { auto &rigidBody = entity.get_mut(); - m_componentRegistry.render(entity, - rigidBody); + m_componentRegistry.render(entity, rigidBody); + componentCount++; } + // Render PhysicsCollider if (entity.has()) { auto &collider = entity.get_mut(); - m_componentRegistry.render(entity, - collider); + m_componentRegistry.render(entity, collider); + componentCount++; } // Show message if no components - if (!entity.has() && - !entity.has() && - !entity.has() && - !entity.has()) { + if (componentCount == 0) { ImGui::TextDisabled("No components"); ImGui::Text("Click 'Add Component' to add components"); } @@ -495,42 +529,19 @@ void EditorUISystem::renderAddComponentMenu(flecs::entity entity) ImGui::Text("Add Component:"); ImGui::Separator(); - bool hasTransform = entity.has(); - bool hasRenderable = entity.has(); - bool hasRigidBody = entity.has(); - bool hasCollider = entity.has(); - - if (!hasTransform) { - if (ImGui::MenuItem("Transform")) { - m_componentRegistry - .addComponent( - entity); + // Dynamically list all registered components that the entity doesn't have + bool hasAny = false; + m_componentRegistry.forEach([&](const std::type_index& type, const ComponentRegistry::ComponentInfo& info) { + if (!m_componentRegistry.entityHasComponent(entity, type)) { + hasAny = true; + if (ImGui::MenuItem(info.name)) { + m_componentRegistry.addComponentByType(entity, type); + } } - } + }); - if (!hasRenderable) { - if (ImGui::MenuItem("Renderable")) { - m_componentRegistry - .addComponent( - entity); - } - } - - ImGui::Separator(); - ImGui::Text("Physics:"); - - if (!hasRigidBody) { - if (ImGui::MenuItem("Rigid Body")) { - m_componentRegistry - .addComponent(entity); - } - } - - if (!hasCollider) { - if (ImGui::MenuItem("Physics Collider")) { - m_componentRegistry - .addComponent(entity); - } + if (!hasAny) { + ImGui::TextDisabled("All components added"); } ImGui::EndPopup(); @@ -539,51 +550,26 @@ void EditorUISystem::renderAddComponentMenu(flecs::entity entity) void EditorUISystem::renderRemoveComponentMenu(flecs::entity entity) { - { - if (ImGui::BeginPopup("RemoveComponentMenu")) { - ImGui::Text("Remove Component:"); - ImGui::Separator(); + if (ImGui::BeginPopup("RemoveComponentMenu")) { + ImGui::Text("Remove Component:"); + ImGui::Separator(); - bool hasAny = false; - - if (entity.has()) { + // Dynamically list all registered components that the entity has + bool hasAny = false; + m_componentRegistry.forEach([&](const std::type_index& type, const ComponentRegistry::ComponentInfo& info) { + if (m_componentRegistry.entityHasComponent(entity, type)) { hasAny = true; - if (ImGui::MenuItem("Transform")) { - m_componentRegistry.removeComponent< - TransformComponent>(entity); + if (ImGui::MenuItem(info.name)) { + m_componentRegistry.removeComponentByType(entity, type); } } + }); - if (entity.has()) { - hasAny = true; - if (ImGui::MenuItem("Renderable")) { - m_componentRegistry.removeComponent< - RenderableComponent>(entity); - } - } - - if (entity.has()) { - hasAny = true; - if (ImGui::MenuItem("Rigid Body")) { - m_componentRegistry.removeComponent< - RigidBodyComponent>(entity); - } - } - - if (entity.has()) { - hasAny = true; - if (ImGui::MenuItem("Physics Collider")) { - m_componentRegistry.removeComponent< - PhysicsColliderComponent>(entity); - } - } - - if (!hasAny) { - ImGui::TextDisabled("No components to remove"); - } - - ImGui::EndPopup(); + if (!hasAny) { + ImGui::TextDisabled("No components to remove"); } + + ImGui::EndPopup(); } } diff --git a/src/features/editScene/systems/EditorUISystem.hpp b/src/features/editScene/systems/EditorUISystem.hpp index 9fba0e1..6aedc86 100644 --- a/src/features/editScene/systems/EditorUISystem.hpp +++ b/src/features/editScene/systems/EditorUISystem.hpp @@ -113,6 +113,7 @@ private: // Helper functions void registerComponentEditors(); + void registerModularComponents(); flecs::entity findEntityParent(flecs::entity entity); std::vector getEntityChildren(flecs::entity entity); bool isDescendantOf(flecs::entity potentialChild, diff --git a/src/features/editScene/systems/LightSystem.cpp b/src/features/editScene/systems/LightSystem.cpp new file mode 100644 index 0000000..b9529ff --- /dev/null +++ b/src/features/editScene/systems/LightSystem.cpp @@ -0,0 +1,123 @@ +#include "LightSystem.hpp" +#include "../components/Light.hpp" +#include "../components/Transform.hpp" +#include + +EditorLightSystem::EditorLightSystem(flecs::world& world, Ogre::SceneManager* sceneMgr) + : m_world(world) + , m_sceneMgr(sceneMgr) + , m_lightQuery(world.query()) +{ +} + +EditorLightSystem::~EditorLightSystem() = default; + +void EditorLightSystem::update() +{ + m_lightQuery.each([&](flecs::entity entity, LightComponent& light, TransformComponent& transform) { + if (light.needsRebuild || !light.light) { + updateLight(entity, light, transform); + } + }); +} + +void EditorLightSystem::processAllLights() +{ + m_lightQuery.each([&](flecs::entity entity, LightComponent& light, TransformComponent& transform) { + updateLight(entity, light, transform); + }); +} + +void EditorLightSystem::updateLight(flecs::entity entity, LightComponent& light, + TransformComponent& transform) +{ + // Remove existing light if any + if (light.light) { + removeLight(light); + } + + // Need a valid scene node to attach the light + if (!transform.node) { + Ogre::LogManager::getSingleton().logMessage( + "LightSystem: Cannot create light - entity has no SceneNode"); + return; + } + + // Create unique name for the light + std::string lightName = "Light_" + std::to_string(entity.id()); + + // Create the Ogre light + light.light = m_sceneMgr->createLight(lightName); + + // Attach to the entity's scene node + transform.node->attachObject(light.light); + + // Set light type + switch (light.lightType) { + case LightComponent::LightType::Point: + light.light->setType(Ogre::Light::LT_POINT); + break; + case LightComponent::LightType::Directional: + light.light->setType(Ogre::Light::LT_DIRECTIONAL); + break; + case LightComponent::LightType::Spotlight: + light.light->setType(Ogre::Light::LT_SPOTLIGHT); + break; + } + + // Set colors (apply intensity) + light.light->setDiffuseColour(light.diffuseColor * light.intensity); + light.light->setSpecularColour(light.specularColor * light.intensity); + + // Set attenuation for point/spot lights + if (light.lightType == LightComponent::LightType::Point || + light.lightType == LightComponent::LightType::Spotlight) { + light.light->setAttenuation(light.range, + light.constantAttenuation, + light.linearAttenuation, + light.quadraticAttenuation); + } + + // Set direction for directional/spot lights by rotating the node + if (light.lightType == LightComponent::LightType::Directional || + light.lightType == LightComponent::LightType::Spotlight) { + // Convert direction to orientation - the light's default direction is typically (0,0,-1) or (0,-1,0) + // We need to orient the node to point in the desired direction + Ogre::Vector3 defaultDir(0, -1, 0); // Ogre lights typically point down by default + Ogre::Quaternion rot = defaultDir.getRotationTo(light.direction); + transform.node->setOrientation(rot); + } + + // Set spotlight parameters + if (light.lightType == LightComponent::LightType::Spotlight) { + light.light->setSpotlightRange( + Ogre::Degree(light.spotlightInnerAngle), + Ogre::Degree(light.spotlightOuterAngle), + light.spotlightFalloff); + } + + // Set shadow casting + light.light->setCastShadows(light.castShadows); + + light.needsRebuild = false; + + Ogre::LogManager::getSingleton().logMessage( + "LightSystem: Created " + + std::string(light.lightType == LightComponent::LightType::Point ? "point" : + light.lightType == LightComponent::LightType::Directional ? "directional" : "spot") + + " light '" + lightName + "'"); +} + +void EditorLightSystem::removeLight(LightComponent& light) +{ + if (light.light) { + // Detach from parent node first + Ogre::SceneNode* parent = light.light->getParentSceneNode(); + if (parent) { + parent->detachObject(light.light); + } + + m_sceneMgr->destroyLight(light.light); + light.light = nullptr; + } +} diff --git a/src/features/editScene/systems/LightSystem.hpp b/src/features/editScene/systems/LightSystem.hpp new file mode 100644 index 0000000..a326ada --- /dev/null +++ b/src/features/editScene/systems/LightSystem.hpp @@ -0,0 +1,37 @@ +#ifndef EDITSCENE_LIGHTSYSTEM_HPP +#define EDITSCENE_LIGHTSYSTEM_HPP +#pragma once + +#include +#include + +/** + * Light system - manages Ogre::Light objects for entities with LightComponent + */ +class EditorLightSystem { +public: + EditorLightSystem(flecs::world& world, Ogre::SceneManager* sceneMgr); + ~EditorLightSystem(); + + // Update lights (process dirty components) + void update(); + + // Process all lights immediately (e.g., after scene load) + void processAllLights(); + +private: + // Create/update a light from component + void updateLight(flecs::entity entity, class LightComponent& light, + class TransformComponent& transform); + + // Remove a light + void removeLight(LightComponent& light); + + flecs::world& m_world; + Ogre::SceneManager* m_sceneMgr; + + // Query for entities with Light and Transform + flecs::query m_lightQuery; +}; + +#endif // EDITSCENE_LIGHTSYSTEM_HPP diff --git a/src/features/editScene/systems/SceneSerializer.cpp b/src/features/editScene/systems/SceneSerializer.cpp index 6352431..759de39 100644 --- a/src/features/editScene/systems/SceneSerializer.cpp +++ b/src/features/editScene/systems/SceneSerializer.cpp @@ -5,6 +5,8 @@ #include "../components/EditorMarker.hpp" #include "../components/RigidBody.hpp" #include "../components/PhysicsCollider.hpp" +#include "../components/Light.hpp" +#include "../components/Camera.hpp" #include "EditorUISystem.hpp" #include #include @@ -123,6 +125,14 @@ nlohmann::json SceneSerializer::serializeEntity(flecs::entity entity) json["collider"] = serializeCollider(entity); } + if (entity.has()) { + json["light"] = serializeLight(entity); + } + + if (entity.has()) { + json["camera"] = serializeCamera(entity); + } + // Serialize children json["children"] = nlohmann::json::array(); entity.children([&](flecs::entity child) { @@ -174,6 +184,14 @@ void SceneSerializer::deserializeEntity(const nlohmann::json& json, flecs::entit deserializeCollider(entity, json["collider"]); } + if (json.contains("light")) { + deserializeLight(entity, json["light"]); + } + + if (json.contains("camera")) { + deserializeCamera(entity, json["camera"]); + } + // Add to UI system if provided if (uiSystem) { uiSystem->addEntity(entity); @@ -313,6 +331,80 @@ nlohmann::json SceneSerializer::serializeCollider(flecs::entity entity) return json; } +nlohmann::json SceneSerializer::serializeLight(flecs::entity entity) +{ + auto& light = entity.get(); + nlohmann::json json; + + // Serialize light type + switch (light.lightType) { + case LightComponent::LightType::Point: + json["lightType"] = "point"; + break; + case LightComponent::LightType::Directional: + json["lightType"] = "directional"; + break; + case LightComponent::LightType::Spotlight: + json["lightType"] = "spotlight"; + break; + } + + // Colors + json["diffuseColor"] = { + {"r", light.diffuseColor.r}, + {"g", light.diffuseColor.g}, + {"b", light.diffuseColor.b}, + {"a", light.diffuseColor.a} + }; + + json["specularColor"] = { + {"r", light.specularColor.r}, + {"g", light.specularColor.g}, + {"b", light.specularColor.b}, + {"a", light.specularColor.a} + }; + + json["intensity"] = light.intensity; + + // Attenuation + json["range"] = light.range; + json["constantAttenuation"] = light.constantAttenuation; + json["linearAttenuation"] = light.linearAttenuation; + json["quadraticAttenuation"] = light.quadraticAttenuation; + + // Spotlight settings + json["spotlightInnerAngle"] = light.spotlightInnerAngle; + json["spotlightOuterAngle"] = light.spotlightOuterAngle; + json["spotlightFalloff"] = light.spotlightFalloff; + + // Direction + json["direction"] = { + {"x", light.direction.x}, + {"y", light.direction.y}, + {"z", light.direction.z} + }; + + json["castShadows"] = light.castShadows; + + return json; +} + +nlohmann::json SceneSerializer::serializeCamera(flecs::entity entity) +{ + auto& camera = entity.get(); + nlohmann::json json; + + json["fovY"] = camera.fovY; + json["nearClip"] = camera.nearClip; + json["farClip"] = camera.farClip; + json["aspectRatio"] = camera.aspectRatio; + json["orthographic"] = camera.orthographic; + json["orthoWidth"] = camera.orthoWidth; + json["orthoHeight"] = camera.orthoHeight; + + return json; +} + void SceneSerializer::deserializeTransform(flecs::entity entity, const nlohmann::json& json, flecs::entity parentEntity) { TransformComponent transform; @@ -486,3 +578,87 @@ void SceneSerializer::deserializeCollider(flecs::entity entity, const nlohmann:: entity.set(collider); } + +void SceneSerializer::deserializeLight(flecs::entity entity, const nlohmann::json& json) +{ + LightComponent light; + + // Deserialize light type + std::string lightType = json.value("lightType", "point"); + if (lightType == "point") { + light.lightType = LightComponent::LightType::Point; + } else if (lightType == "directional") { + light.lightType = LightComponent::LightType::Directional; + } else if (lightType == "spotlight") { + light.lightType = LightComponent::LightType::Spotlight; + } + + // Deserialize colors + if (json.contains("diffuseColor")) { + auto& col = json["diffuseColor"]; + light.diffuseColor = Ogre::ColourValue( + col.value("r", 0.8f), + col.value("g", 0.8f), + col.value("b", 0.8f), + col.value("a", 1.0f) + ); + } + + if (json.contains("specularColor")) { + auto& col = json["specularColor"]; + light.specularColor = Ogre::ColourValue( + col.value("r", 0.5f), + col.value("g", 0.5f), + col.value("b", 0.5f), + col.value("a", 1.0f) + ); + } + + light.intensity = json.value("intensity", 1.0f); + + // Attenuation + light.range = json.value("range", 100.0f); + light.constantAttenuation = json.value("constantAttenuation", 1.0f); + light.linearAttenuation = json.value("linearAttenuation", 0.0f); + light.quadraticAttenuation = json.value("quadraticAttenuation", 0.0f); + + // Spotlight settings + light.spotlightInnerAngle = json.value("spotlightInnerAngle", 30.0f); + light.spotlightOuterAngle = json.value("spotlightOuterAngle", 45.0f); + light.spotlightFalloff = json.value("spotlightFalloff", 1.0f); + + // Direction + if (json.contains("direction")) { + auto& dir = json["direction"]; + light.direction = Ogre::Vector3( + dir.value("x", 0.0f), + dir.value("y", -1.0f), + dir.value("z", 0.0f) + ); + } + + light.castShadows = json.value("castShadows", true); + + // Mark as dirty so light system will create the light + light.needsRebuild = true; + + entity.set(light); +} + +void SceneSerializer::deserializeCamera(flecs::entity entity, const nlohmann::json& json) +{ + CameraComponent camera; + + camera.fovY = json.value("fovY", 45.0f); + camera.nearClip = json.value("nearClip", 0.1f); + camera.farClip = json.value("farClip", 1000.0f); + camera.aspectRatio = json.value("aspectRatio", 16.0f / 9.0f); + camera.orthographic = json.value("orthographic", false); + camera.orthoWidth = json.value("orthoWidth", 10.0f); + camera.orthoHeight = json.value("orthoHeight", 10.0f); + + // Mark as dirty so camera system will create the camera + camera.needsRebuild = true; + + entity.set(camera); +} diff --git a/src/features/editScene/systems/SceneSerializer.hpp b/src/features/editScene/systems/SceneSerializer.hpp index e309e46..03fd5fd 100644 --- a/src/features/editScene/systems/SceneSerializer.hpp +++ b/src/features/editScene/systems/SceneSerializer.hpp @@ -44,6 +44,8 @@ private: nlohmann::json serializeEntityName(flecs::entity entity); nlohmann::json serializeRigidBody(flecs::entity entity); nlohmann::json serializeCollider(flecs::entity entity); + nlohmann::json serializeLight(flecs::entity entity); + nlohmann::json serializeCamera(flecs::entity entity); // Component deserialization void deserializeTransform(flecs::entity entity, const nlohmann::json& json, flecs::entity parentEntity); @@ -51,6 +53,8 @@ private: void deserializeEntityName(flecs::entity entity, const nlohmann::json& json); void deserializeRigidBody(flecs::entity entity, const nlohmann::json& json); void deserializeCollider(flecs::entity entity, const nlohmann::json& json); + void deserializeLight(flecs::entity entity, const nlohmann::json& json); + void deserializeCamera(flecs::entity entity, const nlohmann::json& json); flecs::world& m_world; Ogre::SceneManager* m_sceneMgr; diff --git a/src/features/editScene/ui/CameraEditor.cpp b/src/features/editScene/ui/CameraEditor.cpp new file mode 100644 index 0000000..63a8afd --- /dev/null +++ b/src/features/editScene/ui/CameraEditor.cpp @@ -0,0 +1,88 @@ +#include "CameraEditor.hpp" +#include +#include +#include +#include +#include + +CameraEditor::CameraEditor(Ogre::SceneManager* sceneMgr) + : m_sceneMgr(sceneMgr) +{ +} + +bool CameraEditor::renderComponent(flecs::entity entity, CameraComponent &camera) +{ + bool modified = false; + + if (ImGui::CollapsingHeader("Camera", ImGuiTreeNodeFlags_DefaultOpen)) { + ImGui::Indent(); + + // Projection type + bool ortho = camera.orthographic; + if (ImGui::Checkbox("Orthographic", &camera.orthographic)) { + camera.markDirty(); + modified = true; + } + + if (camera.orthographic) { + // Orthographic settings + if (ImGui::DragFloat("Ortho Width", &camera.orthoWidth, 0.1f, 0.1f, 1000.0f)) { + camera.markDirty(); + modified = true; + } + if (ImGui::DragFloat("Ortho Height", &camera.orthoHeight, 0.1f, 0.1f, 1000.0f)) { + camera.markDirty(); + modified = true; + } + } else { + // Perspective settings + if (ImGui::SliderFloat("FOV Y", &camera.fovY, 10.0f, 120.0f, "%.1f deg")) { + camera.markDirty(); + modified = true; + } + } + + // Clip distances + ImGui::Separator(); + if (ImGui::DragFloat("Near Clip", &camera.nearClip, 0.01f, 0.001f, 10.0f)) { + camera.markDirty(); + modified = true; + } + if (ImGui::DragFloat("Far Clip", &camera.farClip, 1.0f, 10.0f, 10000.0f)) { + camera.markDirty(); + modified = true; + } + + // Aspect ratio (auto or manual) + ImGui::Separator(); + static bool autoAspect = true; + ImGui::Checkbox("Auto Aspect Ratio", &autoAspect); + if (!autoAspect) { + if (ImGui::DragFloat("Aspect Ratio", &camera.aspectRatio, 0.01f, 0.1f, 10.0f)) { + camera.markDirty(); + modified = true; + } + } + + // Preview section - simplified for now + ImGui::Separator(); + if (camera.camera) { + ImGui::Text("Camera Preview: (Right-click viewport to use)"); + if (ImGui::Button("Use This Camera")) { + // This would switch the main viewport to use this camera + // Implementation depends on viewport management + ImGui::OpenPopup("Camera Switch"); + } + if (ImGui::BeginPopup("Camera Switch")) { + ImGui::Text("Camera switch not yet implemented"); + ImGui::EndPopup(); + } + } else { + ImGui::TextDisabled("Camera not yet created"); + } + + ImGui::Unindent(); + } + + return modified; +} diff --git a/src/features/editScene/ui/CameraEditor.hpp b/src/features/editScene/ui/CameraEditor.hpp new file mode 100644 index 0000000..401c2a3 --- /dev/null +++ b/src/features/editScene/ui/CameraEditor.hpp @@ -0,0 +1,27 @@ +#ifndef EDITSCENE_CAMERAEDITOR_HPP +#define EDITSCENE_CAMERAEDITOR_HPP +#pragma once + +#include "ComponentEditor.hpp" +#include "../components/Camera.hpp" +#include + +/** + * Editor for CameraComponent with preview functionality + */ +class CameraEditor : public ComponentEditor { +public: + CameraEditor(Ogre::SceneManager* sceneMgr); + bool renderComponent(flecs::entity entity, CameraComponent &camera) override; + const char *getName() const override { return "Camera"; } + +private: + void updatePreviewTexture(CameraComponent& camera); + void renderPreview(CameraComponent& camera); + + Ogre::SceneManager* m_sceneMgr; + // Track last rendered frame to avoid flickering + unsigned long m_lastFrame = 0; +}; + +#endif // EDITSCENE_CAMERAEDITOR_HPP diff --git a/src/features/editScene/ui/ComponentRegistration.cpp b/src/features/editScene/ui/ComponentRegistration.cpp new file mode 100644 index 0000000..35891b9 --- /dev/null +++ b/src/features/editScene/ui/ComponentRegistration.cpp @@ -0,0 +1,21 @@ +#include "ComponentRegistration.hpp" + +std::vector& ComponentRegistration::getRegistrants() +{ + static std::vector registrants; + return registrants; +} + +void ComponentRegistration::registerComponent(RegistrationFunc func) +{ + getRegistrants().push_back(func); +} + +void ComponentRegistration::registerAll(ComponentRegistry& registry, + Ogre::SceneManager* sceneMgr, + EditorPhysicsSystem* physicsSystem) +{ + for (auto& func : getRegistrants()) { + func(registry, sceneMgr, physicsSystem); + } +} diff --git a/src/features/editScene/ui/ComponentRegistration.hpp b/src/features/editScene/ui/ComponentRegistration.hpp new file mode 100644 index 0000000..2dcd6f9 --- /dev/null +++ b/src/features/editScene/ui/ComponentRegistration.hpp @@ -0,0 +1,79 @@ +#ifndef EDITSCENE_COMPONENTREGISTRATION_HPP +#define EDITSCENE_COMPONENTREGISTRATION_HPP +#pragma once + +#include "ComponentRegistry.hpp" +#include +#include +#include + +// Forward declarations +class EditorPhysicsSystem; + +/** + * Helper class to modularize component registration + * Each component module registers itself using static initialization + */ +class ComponentRegistration { +public: + using RegistrationFunc = std::function; + + /** + * Register a component module + * Call this from a .cpp file to auto-register the component + */ + static void registerComponent(RegistrationFunc func); + + /** + * Register all components with the registry + * Call this once during editor initialization + */ + static void registerAll(ComponentRegistry& registry, + Ogre::SceneManager* sceneMgr, + EditorPhysicsSystem* physicsSystem); + +private: + static std::vector& getRegistrants(); +}; + +/** + * Macro to simplify component registration + * Usage in a .cpp file: + * + * REGISTER_COMPONENT("My Component", MyComponent, MyComponentEditor) + * { + * // Adder + * registry.registerComponent( + * name, std::make_unique(...), + * [](flecs::entity e) { ... }, + * [](flecs::entity e) { ... }); + * } + */ +#define REGISTER_COMPONENT(nameStr, ComponentType, EditorType) \ + static void register_##ComponentType(ComponentRegistry& registry, \ + Ogre::SceneManager* sceneMgr, \ + EditorPhysicsSystem* physics); \ + struct ComponentType##_registrar { \ + ComponentType##_registrar() { \ + ComponentRegistration::registerComponent(register_##ComponentType); \ + } \ + } static ComponentType##_instance; \ + static void register_##ComponentType(ComponentRegistry& registry, \ + Ogre::SceneManager* sceneMgr, \ + EditorPhysicsSystem* physics) + +/** + * Helper for standard SceneNode-attached components + */ +template +struct SceneNodeComponentTraits { + // Override these for your component type + static const char* getName() { return "Component"; } + static void onAdd(T& comp, Ogre::SceneNode* node) {} + static void onRemove(T& comp, Ogre::SceneNode* node) {} + static void onUpdate(T& comp) {} +}; + +#endif // EDITSCENE_COMPONENTREGISTRATION_HPP diff --git a/src/features/editScene/ui/ComponentRegistry.hpp b/src/features/editScene/ui/ComponentRegistry.hpp index ef5349e..a6692a2 100644 --- a/src/features/editScene/ui/ComponentRegistry.hpp +++ b/src/features/editScene/ui/ComponentRegistry.hpp @@ -5,6 +5,8 @@ #include #include #include +#include +#include #include "ComponentEditor.hpp" /** @@ -17,6 +19,11 @@ using ComponentAdder = std::function; */ using ComponentRemover = std::function; +/** + * Function type for checking if an entity has a component + */ +using ComponentChecker = std::function; + /** * Registry for component editors and their add/remove functions */ @@ -27,6 +34,7 @@ public: std::unique_ptr editor; ComponentAdder adder; ComponentRemover remover; + ComponentChecker checker; // Checks if entity has this component }; ComponentRegistry() = default; @@ -53,6 +61,7 @@ public: info.editor = std::move(editor); info.adder = adder; info.remover = remover; + info.checker = [](flecs::entity e) { return e.has(); }; m_components[std::type_index(typeid(T))] = std::move(info); } @@ -104,15 +113,37 @@ public: } /** - * Get all registered component types + * Check if entity has a specific component type */ - std::vector getRegisteredTypes() const + bool entityHasComponent(flecs::entity entity, const std::type_index &type) const { - std::vector types; - for (const auto &pair : m_components) { - types.push_back(pair.first); + auto it = m_components.find(type); + if (it != m_components.end() && it->second.checker) { + return it->second.checker(entity); + } + return false; + } + + /** + * Add a component by type_index + */ + void addComponentByType(flecs::entity entity, const std::type_index &type) + { + auto it = m_components.find(type); + if (it != m_components.end() && it->second.adder) { + it->second.adder(entity); + } + } + + /** + * Remove a component by type_index + */ + void removeComponentByType(flecs::entity entity, const std::type_index &type) + { + auto it = m_components.find(type); + if (it != m_components.end() && it->second.remover) { + it->second.remover(entity); } - return types; } /** @@ -128,27 +159,32 @@ public: } /** - * Check if entity has a specific component type + * Get all registered component types */ - bool entityHasComponent(flecs::entity entity, - const std::type_index &type) const + std::vector getRegisteredTypes() const { - // This is a simplified check - in practice you'd need to - // store flecs component IDs and check those - return false; + std::vector types; + for (const auto &pair : m_components) { + types.push_back(pair.first); + } + return types; } /** - * Render add component menu for all registered components - * Returns true if a component was added + * Iterate over all registered components */ - bool renderAddComponentMenu(flecs::entity entity); + template + void forEach(Func&& func) const + { + for (const auto& pair : m_components) { + func(pair.first, pair.second); + } + } /** - * Render remove component menu for components the entity has - * Returns true if a component was removed + * Get the number of registered components */ - bool renderRemoveComponentMenu(flecs::entity entity); + size_t getComponentCount() const { return m_components.size(); } private: std::unordered_map m_components; diff --git a/src/features/editScene/ui/LightEditor.cpp b/src/features/editScene/ui/LightEditor.cpp new file mode 100644 index 0000000..b2e7e3c --- /dev/null +++ b/src/features/editScene/ui/LightEditor.cpp @@ -0,0 +1,136 @@ +#include "LightEditor.hpp" +#include + +void LightEditor::renderColorEdit(const char* label, Ogre::ColourValue& color) +{ + float col[4] = { color.r, color.g, color.b, color.a }; + if (ImGui::ColorEdit4(label, col)) { + color.r = col[0]; + color.g = col[1]; + color.b = col[2]; + color.a = col[3]; + } +} + +bool LightEditor::renderComponent(flecs::entity entity, LightComponent &light) +{ + bool modified = false; + + if (ImGui::CollapsingHeader("Light", ImGuiTreeNodeFlags_DefaultOpen)) { + ImGui::Indent(); + + // Light type selector + const char* lightTypes[] = { "Point", "Directional", "Spotlight" }; + int currentType = static_cast(light.lightType); + if (ImGui::Combo("Type", ¤tType, lightTypes, IM_ARRAYSIZE(lightTypes))) { + light.lightType = static_cast(currentType); + light.markDirty(); + modified = true; + } + + // Colors + ImGui::Separator(); + ImGui::Text("Colors"); + + Ogre::ColourValue diffuse = light.diffuseColor; + renderColorEdit("Diffuse", light.diffuseColor); + if (light.diffuseColor != diffuse) { + light.markDirty(); + modified = true; + } + + Ogre::ColourValue specular = light.specularColor; + renderColorEdit("Specular", light.specularColor); + if (light.specularColor != specular) { + light.markDirty(); + modified = true; + } + + // Intensity + ImGui::Separator(); + float intensity = light.intensity; + if (ImGui::DragFloat("Intensity", &light.intensity, 0.1f, 0.0f, 10.0f)) { + light.markDirty(); + modified = true; + } + + // Cast shadows + bool castShadows = light.castShadows; + if (ImGui::Checkbox("Cast Shadows", &light.castShadows)) { + light.markDirty(); + modified = true; + } + + // Type-specific properties + ImGui::Separator(); + + if (light.lightType == LightComponent::LightType::Point || + light.lightType == LightComponent::LightType::Spotlight) { + ImGui::Text("Attenuation"); + + if (ImGui::DragFloat("Range", &light.range, 1.0f, 0.1f, 10000.0f)) { + light.markDirty(); + modified = true; + } + + if (ImGui::DragFloat("Constant", &light.constantAttenuation, 0.1f, 0.0f, 1.0f)) { + light.markDirty(); + modified = true; + } + + if (ImGui::DragFloat("Linear", &light.linearAttenuation, 0.001f, 0.0f, 1.0f, "%.4f")) { + light.markDirty(); + modified = true; + } + + if (ImGui::DragFloat("Quadratic", &light.quadraticAttenuation, 0.0001f, 0.0f, 1.0f, "%.6f")) { + light.markDirty(); + modified = true; + } + } + + if (light.lightType == LightComponent::LightType::Directional || + light.lightType == LightComponent::LightType::Spotlight) { + ImGui::Separator(); + ImGui::Text("Direction"); + float dir[3] = { light.direction.x, light.direction.y, light.direction.z }; + if (ImGui::DragFloat3("Direction", dir, 0.1f)) { + light.direction = Ogre::Vector3(dir[0], dir[1], dir[2]).normalisedCopy(); + light.markDirty(); + modified = true; + } + } + + if (light.lightType == LightComponent::LightType::Spotlight) { + ImGui::Separator(); + ImGui::Text("Spotlight Settings"); + + if (ImGui::SliderFloat("Inner Angle", &light.spotlightInnerAngle, 0.0f, 90.0f)) { + // Ensure inner <= outer + if (light.spotlightInnerAngle > light.spotlightOuterAngle) { + light.spotlightOuterAngle = light.spotlightInnerAngle; + } + light.markDirty(); + modified = true; + } + + if (ImGui::SliderFloat("Outer Angle", &light.spotlightOuterAngle, 0.0f, 90.0f)) { + // Ensure outer >= inner + if (light.spotlightOuterAngle < light.spotlightInnerAngle) { + light.spotlightInnerAngle = light.spotlightOuterAngle; + } + light.markDirty(); + modified = true; + } + + if (ImGui::DragFloat("Falloff", &light.spotlightFalloff, 0.1f, 0.0f, 10.0f)) { + light.markDirty(); + modified = true; + } + } + + ImGui::Unindent(); + } + + return modified; +} diff --git a/src/features/editScene/ui/LightEditor.hpp b/src/features/editScene/ui/LightEditor.hpp new file mode 100644 index 0000000..7fd3b11 --- /dev/null +++ b/src/features/editScene/ui/LightEditor.hpp @@ -0,0 +1,20 @@ +#ifndef EDITSCENE_LIGHTEDITOR_HPP +#define EDITSCENE_LIGHTEDITOR_HPP +#pragma once + +#include "ComponentEditor.hpp" +#include "../components/Light.hpp" + +/** + * Editor for LightComponent + */ +class LightEditor : public ComponentEditor { +public: + bool renderComponent(flecs::entity entity, LightComponent &light) override; + const char *getName() const override { return "Light"; } + +private: + void renderColorEdit(const char* label, Ogre::ColourValue& color); +}; + +#endif // EDITSCENE_LIGHTEDITOR_HPP