Camera and Light components

This commit is contained in:
2026-04-03 20:06:37 +03:00
parent d4061386ec
commit 2a2fd53c4f
22 changed files with 1218 additions and 103 deletions

View File

@@ -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

View File

@@ -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 <OgreRTShaderSystem.h>
#include <imgui.h>
@@ -111,6 +115,12 @@ void EditorApp::setup()
m_physicsSystem = std::make_unique<EditorPhysicsSystem>(m_world, m_sceneMgr);
m_physicsSystem->initialize();
m_uiSystem->setPhysicsSystem(m_physicsSystem.get());
// Setup light system
m_lightSystem = std::make_unique<EditorLightSystem>(m_world, m_sceneMgr);
// Setup camera system
m_cameraSystem = std::make_unique<EditorCameraSystem>(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<PhysicsColliderComponent>();
m_world.component<RigidBodyComponent>();
// Register light and camera components
m_world.component<LightComponent>();
m_world.component<CameraComponent>();
}
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;

View File

@@ -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<EditorCamera> m_camera;
std::unique_ptr<ImGuiRenderListener> m_imguiListener;
std::unique_ptr<EditorPhysicsSystem> m_physicsSystem;
std::unique_ptr<EditorLightSystem> m_lightSystem;
std::unique_ptr<EditorCameraSystem> m_cameraSystem;
// State
uint16_t m_currentModifiers;

View File

@@ -0,0 +1,37 @@
#ifndef EDITSCENE_CAMERA_HPP
#define EDITSCENE_CAMERA_HPP
#pragma once
#include <Ogre.h>
/**
* 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

View File

@@ -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<CameraComponent>(
"Camera",
std::make_unique<CameraEditor>(sceneMgr),
// Adder
[sceneMgr](flecs::entity e) {
if (!e.has<CameraComponent>()) {
// Camera requires Transform
if (!e.has<TransformComponent>()) {
// Auto-add transform if missing
TransformComponent transform;
transform.node = sceneMgr->getRootSceneNode()->createChildSceneNode();
e.set<TransformComponent>(transform);
}
e.set<CameraComponent>({});
}
},
// Remover
[sceneMgr](flecs::entity e) {
if (e.has<CameraComponent>()) {
auto& camera = e.get_mut<CameraComponent>();
// 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<CameraComponent>();
}
}
);
}

View File

@@ -0,0 +1,48 @@
#ifndef EDITSCENE_LIGHT_HPP
#define EDITSCENE_LIGHT_HPP
#pragma once
#include <Ogre.h>
/**
* 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

View File

@@ -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<LightComponent>(
"Light",
std::make_unique<LightEditor>(),
// Adder
[sceneMgr](flecs::entity e) {
if (!e.has<LightComponent>()) {
// Light requires Transform
if (!e.has<TransformComponent>()) {
// Auto-add transform if missing
TransformComponent transform;
transform.node = sceneMgr->getRootSceneNode()->createChildSceneNode();
e.set<TransformComponent>(transform);
}
e.set<LightComponent>({});
}
},
// Remover
[sceneMgr](flecs::entity e) {
if (e.has<LightComponent>()) {
auto& light = e.get_mut<LightComponent>();
// 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<LightComponent>();
}
}
);
}

View File

@@ -0,0 +1,118 @@
#include "CameraSystem.hpp"
#include "../components/Camera.hpp"
#include "../components/Transform.hpp"
#include <OgreLogManager.h>
EditorCameraSystem::EditorCameraSystem(flecs::world& world, Ogre::SceneManager* sceneMgr)
: m_world(world)
, m_sceneMgr(sceneMgr)
, m_cameraQuery(world.query<CameraComponent, TransformComponent>())
{
}
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;
}
}

View File

@@ -0,0 +1,40 @@
#ifndef EDITSCENE_CAMERASYSTEM_HPP
#define EDITSCENE_CAMERASYSTEM_HPP
#pragma once
#include <flecs.h>
#include <Ogre.h>
/**
* 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<class CameraComponent, class TransformComponent> m_cameraQuery;
};
#endif // EDITSCENE_CAMERASYSTEM_HPP

View File

@@ -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 <imgui.h>
#include <algorithm>
@@ -143,6 +146,15 @@ void EditorUISystem::registerComponentEditors()
e.remove<PhysicsColliderComponent>();
}
});
// 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<RenderableComponent>())
indicators += " [R]";
if (entity.has<LightComponent>())
indicators += " [L]";
if (entity.has<CameraComponent>())
indicators += " [Cam]";
if (entity.has<RigidBodyComponent>())
indicators += " [RB]";
if (entity.has<PhysicsColliderComponent>())
@@ -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<TransformComponent>()) {
auto &transform = entity.get_mut<TransformComponent>();
m_componentRegistry.render<TransformComponent>(entity,
transform);
m_componentRegistry.render<TransformComponent>(entity, transform);
componentCount++;
}
// Render Renderable
if (entity.has<RenderableComponent>()) {
auto &renderable = entity.get_mut<RenderableComponent>();
m_componentRegistry.render<RenderableComponent>(entity,
renderable);
m_componentRegistry.render<RenderableComponent>(entity, renderable);
componentCount++;
}
// Render Light if present
if (entity.has<LightComponent>()) {
auto &light = entity.get_mut<LightComponent>();
m_componentRegistry.render<LightComponent>(entity, light);
componentCount++;
}
// Render Camera if present
if (entity.has<CameraComponent>()) {
auto &camera = entity.get_mut<CameraComponent>();
m_componentRegistry.render<CameraComponent>(entity, camera);
componentCount++;
}
// Render RigidBody
if (entity.has<RigidBodyComponent>()) {
auto &rigidBody = entity.get_mut<RigidBodyComponent>();
m_componentRegistry.render<RigidBodyComponent>(entity,
rigidBody);
m_componentRegistry.render<RigidBodyComponent>(entity, rigidBody);
componentCount++;
}
// Render PhysicsCollider
if (entity.has<PhysicsColliderComponent>()) {
auto &collider = entity.get_mut<PhysicsColliderComponent>();
m_componentRegistry.render<PhysicsColliderComponent>(entity,
collider);
m_componentRegistry.render<PhysicsColliderComponent>(entity, collider);
componentCount++;
}
// Show message if no components
if (!entity.has<TransformComponent>() &&
!entity.has<RenderableComponent>() &&
!entity.has<RigidBodyComponent>() &&
!entity.has<PhysicsColliderComponent>()) {
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<TransformComponent>();
bool hasRenderable = entity.has<RenderableComponent>();
bool hasRigidBody = entity.has<RigidBodyComponent>();
bool hasCollider = entity.has<PhysicsColliderComponent>();
if (!hasTransform) {
if (ImGui::MenuItem("Transform")) {
m_componentRegistry
.addComponent<TransformComponent>(
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<RenderableComponent>(
entity);
}
}
ImGui::Separator();
ImGui::Text("Physics:");
if (!hasRigidBody) {
if (ImGui::MenuItem("Rigid Body")) {
m_componentRegistry
.addComponent<RigidBodyComponent>(entity);
}
}
if (!hasCollider) {
if (ImGui::MenuItem("Physics Collider")) {
m_componentRegistry
.addComponent<PhysicsColliderComponent>(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<TransformComponent>()) {
// 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<RenderableComponent>()) {
hasAny = true;
if (ImGui::MenuItem("Renderable")) {
m_componentRegistry.removeComponent<
RenderableComponent>(entity);
}
}
if (entity.has<RigidBodyComponent>()) {
hasAny = true;
if (ImGui::MenuItem("Rigid Body")) {
m_componentRegistry.removeComponent<
RigidBodyComponent>(entity);
}
}
if (entity.has<PhysicsColliderComponent>()) {
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();
}
}

View File

@@ -113,6 +113,7 @@ private:
// Helper functions
void registerComponentEditors();
void registerModularComponents();
flecs::entity findEntityParent(flecs::entity entity);
std::vector<flecs::entity> getEntityChildren(flecs::entity entity);
bool isDescendantOf(flecs::entity potentialChild,

View File

@@ -0,0 +1,123 @@
#include "LightSystem.hpp"
#include "../components/Light.hpp"
#include "../components/Transform.hpp"
#include <OgreLogManager.h>
EditorLightSystem::EditorLightSystem(flecs::world& world, Ogre::SceneManager* sceneMgr)
: m_world(world)
, m_sceneMgr(sceneMgr)
, m_lightQuery(world.query<LightComponent, TransformComponent>())
{
}
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;
}
}

View File

@@ -0,0 +1,37 @@
#ifndef EDITSCENE_LIGHTSYSTEM_HPP
#define EDITSCENE_LIGHTSYSTEM_HPP
#pragma once
#include <flecs.h>
#include <Ogre.h>
/**
* 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<class LightComponent, class TransformComponent> m_lightQuery;
};
#endif // EDITSCENE_LIGHTSYSTEM_HPP

View File

@@ -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 <fstream>
#include <iostream>
@@ -123,6 +125,14 @@ nlohmann::json SceneSerializer::serializeEntity(flecs::entity entity)
json["collider"] = serializeCollider(entity);
}
if (entity.has<LightComponent>()) {
json["light"] = serializeLight(entity);
}
if (entity.has<CameraComponent>()) {
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<LightComponent>();
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<CameraComponent>();
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<PhysicsColliderComponent>(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<LightComponent>(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<CameraComponent>(camera);
}

View File

@@ -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;

View File

@@ -0,0 +1,88 @@
#include "CameraEditor.hpp"
#include <imgui.h>
#include <OgreTextureManager.h>
#include <OgreRenderTexture.h>
#include <OgreViewport.h>
#include <OgreLogManager.h>
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;
}

View File

@@ -0,0 +1,27 @@
#ifndef EDITSCENE_CAMERAEDITOR_HPP
#define EDITSCENE_CAMERAEDITOR_HPP
#pragma once
#include "ComponentEditor.hpp"
#include "../components/Camera.hpp"
#include <OgreSceneManager.h>
/**
* Editor for CameraComponent with preview functionality
*/
class CameraEditor : public ComponentEditor<CameraComponent> {
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

View File

@@ -0,0 +1,21 @@
#include "ComponentRegistration.hpp"
std::vector<ComponentRegistration::RegistrationFunc>& ComponentRegistration::getRegistrants()
{
static std::vector<RegistrationFunc> 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);
}
}

View File

@@ -0,0 +1,79 @@
#ifndef EDITSCENE_COMPONENTREGISTRATION_HPP
#define EDITSCENE_COMPONENTREGISTRATION_HPP
#pragma once
#include "ComponentRegistry.hpp"
#include <OgreSceneManager.h>
#include <flecs.h>
#include <functional>
// 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<void(ComponentRegistry&,
Ogre::SceneManager*,
EditorPhysicsSystem*)>;
/**
* 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<RegistrationFunc>& getRegistrants();
};
/**
* Macro to simplify component registration
* Usage in a .cpp file:
*
* REGISTER_COMPONENT("My Component", MyComponent, MyComponentEditor)
* {
* // Adder
* registry.registerComponent<MyComponent>(
* name, std::make_unique<MyComponentEditor>(...),
* [](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<typename T>
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

View File

@@ -5,6 +5,8 @@
#include <unordered_map>
#include <memory>
#include <functional>
#include <vector>
#include <flecs.h>
#include "ComponentEditor.hpp"
/**
@@ -17,6 +19,11 @@ using ComponentAdder = std::function<void(flecs::entity)>;
*/
using ComponentRemover = std::function<void(flecs::entity)>;
/**
* Function type for checking if an entity has a component
*/
using ComponentChecker = std::function<bool(flecs::entity)>;
/**
* Registry for component editors and their add/remove functions
*/
@@ -27,6 +34,7 @@ public:
std::unique_ptr<IComponentEditor> 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<T>(); };
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<std::type_index> getRegisteredTypes() const
bool entityHasComponent(flecs::entity entity, const std::type_index &type) const
{
std::vector<std::type_index> 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<std::type_index> getRegisteredTypes() const
{
// This is a simplified check - in practice you'd need to
// store flecs component IDs and check those
return false;
std::vector<std::type_index> 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<typename Func>
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<std::type_index, ComponentInfo> m_components;

View File

@@ -0,0 +1,136 @@
#include "LightEditor.hpp"
#include <imgui.h>
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<int>(light.lightType);
if (ImGui::Combo("Type", &currentType, lightTypes, IM_ARRAYSIZE(lightTypes))) {
light.lightType = static_cast<LightComponent::LightType>(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;
}

View File

@@ -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<LightComponent> {
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