Proper scene editor implementation

This commit is contained in:
2026-03-31 02:48:01 +03:00
parent 9c2adbb698
commit db15c6a48a
43 changed files with 4602 additions and 1 deletions

View File

@@ -1,3 +1,4 @@
project(features)
# ...
add_subdirectory(characters)
add_subdirectory(sceneEditor)
add_subdirectory(editScene)

View File

@@ -0,0 +1,62 @@
project(editScene)
set(CMAKE_CXX_STANDARD 17)
find_package(OGRE REQUIRED COMPONENTS Bites Overlay CONFIG)
find_package(flecs REQUIRED CONFIG)
find_package(SDL2 REQUIRED)
# Collect all source files
set(EDITSCENE_SOURCES
main.cpp
EditorApp.cpp
systems/EditorUISystem.cpp
ui/TransformEditor.cpp
ui/RenderableEditor.cpp
camera/EditorCamera.cpp
gizmo/Gizmo.cpp
)
set(EDITSCENE_HEADERS
EditorApp.hpp
components/Transform.hpp
components/Renderable.hpp
components/EntityName.hpp
components/Relationship.hpp
systems/EditorUISystem.hpp
ui/ComponentEditor.hpp
ui/ComponentRegistry.hpp
ui/TransformEditor.hpp
ui/RenderableEditor.hpp
camera/EditorCamera.hpp
gizmo/Gizmo.hpp
)
add_executable(editSceneEditor ${EDITSCENE_SOURCES} ${EDITSCENE_HEADERS})
target_link_libraries(editSceneEditor
OgreMain
OgreBites
OgreOverlay
flecs::flecs_static
)
target_include_directories(editSceneEditor PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}
)
# Copy local resources (materials, etc.)
if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/resources")
file(COPY "${CMAKE_CURRENT_SOURCE_DIR}/resources"
DESTINATION "${CMAKE_CURRENT_BINARY_DIR}")
endif()
# Copy resources from main build
add_custom_command(TARGET editSceneEditor POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy_directory
"${CMAKE_BINARY_DIR}/resources"
"${CMAKE_CURRENT_BINARY_DIR}/resources"
COMMAND ${CMAKE_COMMAND} -E copy
"${CMAKE_CURRENT_SOURCE_DIR}/resources.cfg"
"${CMAKE_CURRENT_BINARY_DIR}/resources.cfg"
COMMENT "Copying resources to editSceneEditor build directory"
)

View File

@@ -0,0 +1,372 @@
#include <iostream>
#include "EditorApp.hpp"
#include "systems/EditorUISystem.hpp"
#include "camera/EditorCamera.hpp"
#include "components/EntityName.hpp"
#include "components/Transform.hpp"
#include "components/Renderable.hpp"
#include "components/EditorMarker.hpp"
#include <OgreRTShaderSystem.h>
#include <imgui.h>
//=============================================================================
// ImGuiRenderListener Implementation
//=============================================================================
ImGuiRenderListener::ImGuiRenderListener(Ogre::ImGuiOverlay *imguiOverlay,
EditorUISystem *uiSystem)
: m_imguiOverlay(imguiOverlay)
, m_uiSystem(uiSystem)
{
}
void ImGuiRenderListener::preViewportUpdate(
const Ogre::RenderTargetViewportEvent &evt)
{
(void)evt;
// Start ImGui frame before viewport renders
Ogre::ImGuiOverlay::NewFrame();
// Render editor UI
if (m_uiSystem) {
m_uiSystem->update();
}
}
void ImGuiRenderListener::postViewportUpdate(
const Ogre::RenderTargetViewportEvent &evt)
{
(void)evt;
// End ImGui frame after viewport renders
ImGui::EndFrame();
}
//=============================================================================
// EditorApp Implementation
//=============================================================================
EditorApp::EditorApp()
: OgreBites::ApplicationContext("EditSceneEditor")
, m_sceneMgr(nullptr)
, m_overlaySystem(nullptr)
, m_imguiOverlay(nullptr)
, m_currentModifiers(0)
{
}
EditorApp::~EditorApp()
{
// Singletons are managed by OGRE, don't delete them
}
void EditorApp::setup()
{
// Base setup
OgreBites::ApplicationContext::setup();
// Get root and create scene manager
Ogre::Root *root = getRoot();
m_sceneMgr = root->createSceneManager();
m_sceneMgr->setAmbientLight(Ogre::ColourValue(0.3f, 0.3f, 0.3f));
// Add scene manager to RTShader generator
Ogre::RTShader::ShaderGenerator *shadergen =
Ogre::RTShader::ShaderGenerator::getSingletonPtr();
if (shadergen) {
shadergen->addSceneManager(m_sceneMgr);
}
// Setup overlay system - get from ApplicationContext
m_overlaySystem = getOverlaySystem();
OgreAssert(m_overlaySystem, "OverlaySystem not available");
m_sceneMgr->addRenderQueueListener(m_overlaySystem);
// Setup ImGui overlay
m_imguiOverlay = initialiseImGui();
if (m_imguiOverlay) {
m_imguiOverlay->setZOrder(300);
m_imguiOverlay->show();
ImGui::StyleColorsDark();
}
// Setup camera
m_camera =
std::make_unique<EditorCamera>(m_sceneMgr, getRenderWindow());
// Setup ECS and scene
setupECS();
setupLights();
createGrid();
createAxes();
createDefaultEntities();
// Setup UI system
m_uiSystem = std::make_unique<EditorUISystem>(m_world, m_sceneMgr);
// Add default entities to UI cache
for (auto &e : m_defaultEntities) {
m_uiSystem->addEntity(e);
}
// Create and register ImGui render listener
m_imguiListener = std::make_unique<ImGuiRenderListener>(
m_imguiOverlay, m_uiSystem.get());
getRenderWindow()->addListener(m_imguiListener.get());
// Register input listeners
addInputListener(this);
addInputListener(getImGuiInputListener());
}
void EditorApp::setupECS()
{
// Register components
m_world.component<EntityNameComponent>();
m_world.component<TransformComponent>();
m_world.component<RenderableComponent>();
m_world.component<EditorMarkerComponent>();
}
void EditorApp::createDefaultEntities()
{
// Create root entity
flecs::entity root = m_world.entity("Root");
root.set<EntityNameComponent>(EntityNameComponent("Root"));
root.add<EditorMarkerComponent>();
// Create child using flecs::ChildOf relationship
flecs::entity child1 = m_world.entity("Child1");
child1.set<EntityNameComponent>(EntityNameComponent("Child 1"));
child1.add<EditorMarkerComponent>();
child1.child_of(root);
// Create grandchild using flecs::ChildOf relationship
flecs::entity grandchild = m_world.entity("Grandchild");
grandchild.set<EntityNameComponent>(EntityNameComponent("Grandchild"));
grandchild.add<EditorMarkerComponent>();
grandchild.child_of(child1);
}
void EditorApp::setupLights()
{
// Directional light
Ogre::Light *dirLight = m_sceneMgr->createLight("DirectionalLight");
dirLight->setType(Ogre::Light::LT_DIRECTIONAL);
dirLight->setDiffuseColour(Ogre::ColourValue(1.0f, 1.0f, 1.0f));
dirLight->setSpecularColour(Ogre::ColourValue(0.5f, 0.5f, 0.5f));
Ogre::SceneNode *dirNode =
m_sceneMgr->getRootSceneNode()->createChildSceneNode();
dirNode->attachObject(dirLight);
dirNode->setDirection(Ogre::Vector3(1, -1, 0), Ogre::Node::TS_WORLD);
// Fill light
Ogre::Light *fillLight = m_sceneMgr->createLight("FillLight");
fillLight->setType(Ogre::Light::LT_DIRECTIONAL);
fillLight->setDiffuseColour(Ogre::ColourValue(0.4f, 0.4f, 0.4f));
Ogre::SceneNode *fillNode =
m_sceneMgr->getRootSceneNode()->createChildSceneNode();
fillNode->attachObject(fillLight);
fillNode->setDirection(Ogre::Vector3(-0.5f, -1, 0.5f),
Ogre::Node::TS_WORLD);
}
void EditorApp::createGrid()
{
try {
Ogre::ManualObject *grid =
m_sceneMgr->createManualObject("Grid");
// Use Ogre/AxisGizmo which properly supports vertex colors
grid->begin("Ogre/AxisGizmo",
Ogre::RenderOperation::OT_LINE_LIST);
// Draw grid lines - grey color set once
float size = 10.0f;
int divisions = 20;
float step = size * 2.0f / divisions;
for (int i = 0; i <= divisions; ++i) {
float pos = -size + i * step;
// Lines along X
grid->position(pos, 0, -size);
grid->position(pos, 0, size);
// Lines along Z
grid->position(-size, 0, pos);
grid->position(size, 0, pos);
}
grid->end();
Ogre::SceneNode *gridNode =
m_sceneMgr->getRootSceneNode()->createChildSceneNode(
"GridNode");
gridNode->attachObject(grid);
} catch (const std::exception &e) {
Ogre::LogManager::getSingleton().logMessage(
"Grid creation failed: " + Ogre::String(e.what()));
}
}
void EditorApp::createAxes()
{
try {
// X axis (red)
Ogre::ManualObject *axisX =
m_sceneMgr->createManualObject("AxisX");
axisX->begin("Ogre/AxisGizmo",
Ogre::RenderOperation::OT_LINE_LIST);
axisX->colour(1.0f, 0.0f, 0.0f);
axisX->position(0, 0, 0);
axisX->position(2, 0, 0);
axisX->end();
// Y axis (green)
Ogre::ManualObject *axisY =
m_sceneMgr->createManualObject("AxisY");
axisY->begin("Ogre/AxisGizmo",
Ogre::RenderOperation::OT_LINE_LIST);
axisY->colour(0.0f, 1.0f, 0.0f);
axisY->position(0, 0, 0);
axisY->position(0, 2, 0);
axisY->end();
// Z axis (blue)
Ogre::ManualObject *axisZ =
m_sceneMgr->createManualObject("AxisZ");
axisZ->begin("Ogre/AxisGizmo",
Ogre::RenderOperation::OT_LINE_LIST);
axisZ->colour(0.0f, 0.0f, 1.0f);
axisZ->position(0, 0, 0);
axisZ->position(0, 0, 2);
axisZ->end();
// Create axis node
Ogre::SceneNode *axisNode =
m_sceneMgr->getRootSceneNode()->createChildSceneNode(
"AxisNode");
axisNode->attachObject(axisX);
axisNode->attachObject(axisY);
axisNode->attachObject(axisZ);
} catch (const std::exception &e) {
Ogre::LogManager::getSingleton().logMessage(
"Axis creation failed: " + Ogre::String(e.what()));
}
}
bool EditorApp::frameRenderingQueued(const Ogre::FrameEvent &evt)
{
// Update camera
if (m_camera) {
m_camera->update(evt.timeSinceLastFrame);
}
// Don't call base class - it crashes when iterating input listeners
return true;
}
bool EditorApp::mouseMoved(const OgreBites::MouseMotionEvent &evt)
{
// Skip if ImGui wants to capture mouse (for gizmo, we still want to process even if over UI)
// But we need to update hover state
if (m_camera && m_uiSystem) {
Ogre::Ray mouseRay = m_camera->getMouseRay(evt.x, evt.y);
Ogre::Vector2 delta(evt.xrel, evt.yrel);
// Try gizmo first - gizmo handles its own visibility checks
if (m_uiSystem->onMouseMoved(mouseRay, delta)) {
return true;
}
}
// Only pass to camera if ImGui doesn't want the mouse
ImGuiIO &io = ImGui::GetIO();
if (!io.WantCaptureMouse && m_camera) {
m_camera->handleMouseMove(evt);
}
return true;
}
bool EditorApp::mousePressed(const OgreBites::MouseButtonEvent &evt)
{
// Get mouse ray for gizmo interaction FIRST (before ImGui check)
// This allows clicking on 3D gizmo even when mouse is over empty UI areas
if (m_camera && m_uiSystem) {
Ogre::Ray mouseRay = m_camera->getMouseRay(evt.x, evt.y);
std::cout << "click: " << mouseRay.getOrigin() << " "
<< mouseRay.getDirection() << std::endl;
// Try gizmo first - if it handles the event, don't pass to camera or UI
if (m_uiSystem->onMousePressed(mouseRay)) {
return true;
}
}
// Then check if ImGui wants the mouse
ImGuiIO &io = ImGui::GetIO();
if (io.WantCaptureMouse) {
return false; // Let ImGui handle it
}
if (m_camera) {
m_camera->handleMousePress(evt);
}
return true;
}
bool EditorApp::mouseReleased(const OgreBites::MouseButtonEvent &evt)
{
// Handle gizmo mouse release (always process to end dragging)
if (m_uiSystem) {
if (m_uiSystem->onMouseReleased()) {
return true;
}
}
// Only pass to camera if ImGui doesn't want the mouse
ImGuiIO &io = ImGui::GetIO();
if (!io.WantCaptureMouse && m_camera) {
m_camera->handleMouseRelease(evt);
}
return true;
}
bool EditorApp::keyPressed(const OgreBites::KeyboardEvent &evt)
{
m_currentModifiers = evt.keysym.mod;
// Delete key to delete selected entity
if (evt.keysym.sym == 127) { // Delete key
// Handled in UI
}
// F key to focus camera on selected entity
if (evt.keysym.sym == 'f' || evt.keysym.sym == 'F') {
if (m_camera && m_uiSystem &&
m_uiSystem->getSelectedEntity().is_alive()) {
auto entity = m_uiSystem->getSelectedEntity();
if (entity.has<TransformComponent>()) {
auto &transform =
entity.get<TransformComponent>();
m_camera->focusOn(transform.position);
}
}
}
return true;
}
bool EditorApp::keyReleased(const OgreBites::KeyboardEvent &evt)
{
m_currentModifiers = evt.keysym.mod;
return true;
}
flecs::entity EditorApp::getSelectedEntity() const
{
if (m_uiSystem) {
return m_uiSystem->getSelectedEntity();
}
return flecs::entity::null();
}

View File

@@ -0,0 +1,86 @@
#ifndef EDITSCENE_EDITORAPP_HPP
#define EDITSCENE_EDITORAPP_HPP
#pragma once
#include <Ogre.h>
#include <OgreApplicationContext.h>
#include <OgreInput.h>
#include <OgreOverlaySystem.h>
#include <OgreImGuiOverlay.h>
#include <OgreRenderTargetListener.h>
#include <flecs.h>
#include <memory>
// Forward declarations
class EditorUISystem;
class EditorCamera;
/**
* RenderTargetListener for ImGui frame management
* Handles NewFrame() in preViewportUpdate and EndFrame() in postViewportUpdate
*/
class ImGuiRenderListener : public Ogre::RenderTargetListener {
public:
ImGuiRenderListener(Ogre::ImGuiOverlay *imguiOverlay,
EditorUISystem *uiSystem);
void preViewportUpdate(const Ogre::RenderTargetViewportEvent &evt) override;
void postViewportUpdate(const Ogre::RenderTargetViewportEvent &evt) override;
private:
Ogre::ImGuiOverlay *m_imguiOverlay;
EditorUISystem *m_uiSystem;
};
/**
* Main application class for the scene editor
*/
class EditorApp : public OgreBites::ApplicationContext,
public OgreBites::InputListener {
public:
EditorApp();
virtual ~EditorApp();
// OgreBites::ApplicationContext overrides
void setup() override;
bool frameRenderingQueued(const Ogre::FrameEvent &evt) override;
// OgreBites::InputListener overrides
bool mouseMoved(const OgreBites::MouseMotionEvent &evt) override;
bool mousePressed(const OgreBites::MouseButtonEvent &evt) override;
bool mouseReleased(const OgreBites::MouseButtonEvent &evt) override;
bool keyPressed(const OgreBites::KeyboardEvent &evt) override;
bool keyReleased(const OgreBites::KeyboardEvent &evt) override;
// Scene setup
void setupLights();
void createGrid();
void createAxes();
// ECS setup
void setupECS();
void createDefaultEntities();
// Getters
flecs::entity getSelectedEntity() const;
Ogre::SceneManager *getSceneManager() const { return m_sceneMgr; }
private:
// Ogre objects
Ogre::SceneManager *m_sceneMgr;
Ogre::OverlaySystem *m_overlaySystem;
Ogre::ImGuiOverlay *m_imguiOverlay;
// ECS
flecs::world m_world;
std::vector<flecs::entity> m_defaultEntities;
// Editor systems
std::unique_ptr<EditorUISystem> m_uiSystem;
std::unique_ptr<EditorCamera> m_camera;
std::unique_ptr<ImGuiRenderListener> m_imguiListener;
// State
uint16_t m_currentModifiers;
};
#endif // EDITSCENE_EDITORAPP_HPP

View File

@@ -0,0 +1,155 @@
#include "EditorCamera.hpp"
#include <OgreViewport.h>
EditorCamera::EditorCamera(Ogre::SceneManager *sceneMgr,
Ogre::RenderWindow *window)
: m_sceneMgr(sceneMgr)
, m_camera(nullptr)
, m_cameraNode(nullptr)
, m_targetNode(nullptr)
, m_position(0, 5, 15)
, m_target(0, 0, 0)
, m_distance(15.0f)
, m_yaw(0.0f)
, m_pitch(-20.0f)
, m_rotating(false)
, m_panning(false)
, m_zooming(false)
, m_lastMouseX(0)
, m_lastMouseY(0)
{
// Create camera
m_camera = sceneMgr->createCamera("EditorCamera");
m_camera->setNearClipDistance(0.1f);
m_camera->setFarClipDistance(1000.0f);
m_camera->setAutoAspectRatio(true);
// Create camera node
m_cameraNode = sceneMgr->getRootSceneNode()->createChildSceneNode(
"EditorCameraNode");
m_cameraNode->attachObject(m_camera);
// Create target node
m_targetNode = sceneMgr->getRootSceneNode()->createChildSceneNode(
"EditorCameraTarget");
// Setup viewport
Ogre::Viewport *vp = window->addViewport(m_camera);
vp->setBackgroundColour(Ogre::ColourValue(0.1f, 0.1f, 0.1f));
updateCameraPosition();
}
EditorCamera::~EditorCamera() = default;
void EditorCamera::update(float deltaTime)
{
(void)deltaTime;
updateCameraPosition();
}
void EditorCamera::handleMouseMove(const OgreBites::MouseMotionEvent &evt)
{
int dx = evt.x - m_lastMouseX;
int dy = evt.y - m_lastMouseY;
m_lastMouseX = evt.x;
m_lastMouseY = evt.y;
if (m_rotating) {
m_yaw -= dx * ROTATION_SPEED;
m_pitch -= dy * ROTATION_SPEED;
// Clamp pitch
if (m_pitch > 89.0f)
m_pitch = 89.0f;
if (m_pitch < -89.0f)
m_pitch = -89.0f;
}
if (m_panning) {
// Get right and up vectors from camera's orientation
Ogre::Quaternion orientation = m_camera->getDerivedOrientation();
Ogre::Vector3 right = orientation * Ogre::Vector3::UNIT_X;
Ogre::Vector3 up = orientation * Ogre::Vector3::UNIT_Y;
Ogre::Vector3 pan = right * (-dx * PAN_SPEED * m_distance) +
up * (dy * PAN_SPEED * m_distance);
m_target += pan;
}
}
void EditorCamera::handleMousePress(const OgreBites::MouseButtonEvent &evt)
{
m_lastMouseX = evt.x;
m_lastMouseY = evt.y;
if (evt.button == OgreBites::BUTTON_RIGHT) {
m_rotating = true;
} else if (evt.button == OgreBites::BUTTON_MIDDLE) {
m_panning = true;
} else if (evt.button == OgreBites::BUTTON_LEFT) {
// Left click is for selection, handled by app
}
}
void EditorCamera::handleMouseRelease(const OgreBites::MouseButtonEvent &evt)
{
if (evt.button == OgreBites::BUTTON_RIGHT) {
m_rotating = false;
} else if (evt.button == OgreBites::BUTTON_MIDDLE) {
m_panning = false;
}
}
void EditorCamera::handleKeyboard(const OgreBites::KeyboardEvent &evt)
{
// Camera keyboard controls can be added here
// For now, mouse controls are sufficient
(void)evt;
}
void EditorCamera::focusOn(const Ogre::Vector3 &point)
{
m_target = point;
updateCameraPosition();
}
void EditorCamera::setPosition(const Ogre::Vector3 &pos)
{
m_target = pos;
updateCameraPosition();
}
Ogre::Ray EditorCamera::getMouseRay(float screenX, float screenY) const
{
// Convert pixel coordinates to normalized viewport coordinates (0-1)
Ogre::Viewport *viewport = m_camera->getViewport();
if (!viewport)
return Ogre::Ray();
float normX = screenX / viewport->getActualWidth();
float normY = screenY / viewport->getActualHeight();
return m_camera->getCameraToViewportRay(normX, normY);
}
void EditorCamera::updateCameraPosition()
{
// Calculate camera position based on spherical coordinates
float yawRad = Ogre::Degree(m_yaw).valueRadians();
float pitchRad = Ogre::Degree(m_pitch).valueRadians();
Ogre::Vector3 offset;
offset.x = m_distance * Ogre::Math::Cos(pitchRad) *
Ogre::Math::Sin(yawRad);
offset.y = m_distance * Ogre::Math::Sin(pitchRad);
offset.z = m_distance * Ogre::Math::Cos(pitchRad) *
Ogre::Math::Cos(yawRad);
m_position = m_target + offset;
m_cameraNode->setPosition(m_position);
m_cameraNode->lookAt(m_target, Ogre::Node::TS_WORLD);
m_targetNode->setPosition(m_target);
}

View File

@@ -0,0 +1,83 @@
#ifndef EDITSCENE_EDITORCAMERA_HPP
#define EDITSCENE_EDITORCAMERA_HPP
#pragma once
#include <Ogre.h>
#include <OgreCamera.h>
#include <OgreSceneManager.h>
#include <OgreRenderWindow.h>
#include <OgreInput.h>
/**
* Simple editor camera controller
* Supports orbit and fly modes
*/
class EditorCamera {
public:
EditorCamera(Ogre::SceneManager *sceneMgr,
Ogre::RenderWindow *window);
~EditorCamera();
/**
* Update camera movement
*/
void update(float deltaTime);
/**
* Handle input events
*/
void handleMouseMove(const OgreBites::MouseMotionEvent &evt);
void handleMousePress(const OgreBites::MouseButtonEvent &evt);
void handleMouseRelease(const OgreBites::MouseButtonEvent &evt);
void handleKeyboard(const OgreBites::KeyboardEvent &evt);
/**
* Get the camera
*/
Ogre::Camera *getCamera() const { return m_camera; }
/**
* Focus camera on a point
*/
void focusOn(const Ogre::Vector3 &point);
/**
* Set camera position
*/
void setPosition(const Ogre::Vector3 &pos);
/**
* Get ray from mouse position
*/
Ogre::Ray getMouseRay(float screenX, float screenY) const;
private:
void updateCameraPosition();
Ogre::SceneManager *m_sceneMgr;
Ogre::Camera *m_camera;
Ogre::SceneNode *m_cameraNode;
Ogre::SceneNode *m_targetNode;
// Camera state
Ogre::Vector3 m_position;
Ogre::Vector3 m_target;
float m_distance;
float m_yaw;
float m_pitch;
// Input state
bool m_rotating;
bool m_panning;
bool m_zooming;
int m_lastMouseX;
int m_lastMouseY;
// Movement speeds
static constexpr float ROTATION_SPEED = 0.3f;
static constexpr float PAN_SPEED = 0.01f;
static constexpr float ZOOM_SPEED = 0.5f;
static constexpr float MIN_DISTANCE = 1.0f;
static constexpr float MAX_DISTANCE = 1000.0f;
};
#endif // EDITSCENE_EDITORCAMERA_HPP

View File

@@ -0,0 +1,13 @@
#ifndef EDITSCENE_EDITORMARKER_HPP
#define EDITSCENE_EDITORMARKER_HPP
#pragma once
/**
* Marker component for entities created in the editor
* Used to filter out flecs internal entities (components, systems, etc.)
*/
struct EditorMarkerComponent {
// Empty marker component
};
#endif // EDITSCENE_EDITORMARKER_HPP

View File

@@ -0,0 +1,21 @@
#ifndef EDITSCENE_ENTITYNAME_HPP
#define EDITSCENE_ENTITYNAME_HPP
#pragma once
#include <Ogre.h>
/**
* Name component for Flecs entities
* Used for display in the hierarchy window
*/
struct EntityNameComponent {
Ogre::String name;
EntityNameComponent() = default;
explicit EntityNameComponent(const Ogre::String &n)
: name(n)
{
}
};
#endif // EDITSCENE_ENTITYNAME_HPP

View File

@@ -0,0 +1,21 @@
#ifndef EDITSCENE_RELATIONSHIP_HPP
#define EDITSCENE_RELATIONSHIP_HPP
#pragma once
#include <flecs.h>
/**
* Parent-child relationship component
* Tracks the parent entity for hierarchy
*/
struct ParentComponent {
flecs::entity parent;
};
/**
* Component to mark an entity as modified (for UI updates)
*/
struct ModifiedComponent {
bool modified = true;
};
#endif // EDITSCENE_RELATIONSHIP_HPP

View File

@@ -0,0 +1,23 @@
#ifndef EDITSCENE_RENDERABLE_HPP
#define EDITSCENE_RENDERABLE_HPP
#pragma once
#include <Ogre.h>
/**
* Renderable component for Flecs entities
* Links a mesh to an entity
*/
struct RenderableComponent {
Ogre::Entity *entity = nullptr;
Ogre::String meshName;
bool visible = true;
RenderableComponent() = default;
explicit RenderableComponent(const Ogre::String &mesh)
: meshName(mesh)
{
}
};
#endif // EDITSCENE_RENDERABLE_HPP

View File

@@ -0,0 +1,41 @@
#ifndef EDITSCENE_TRANSFORM_HPP
#define EDITSCENE_TRANSFORM_HPP
#pragma once
#include <Ogre.h>
/**
* Transform component for Flecs entities
* Links a Flecs entity to an Ogre SceneNode
*/
struct TransformComponent {
Ogre::SceneNode *node = nullptr;
Ogre::Vector3 position = Ogre::Vector3::ZERO;
Ogre::Quaternion rotation = Ogre::Quaternion::IDENTITY;
Ogre::Vector3 scale = Ogre::Vector3::UNIT_SCALE;
/**
* Apply component values to the scene node
*/
void applyToNode() const
{
if (node) {
node->setPosition(position);
node->setOrientation(rotation);
node->setScale(scale);
}
}
/**
* Update component values from the scene node
*/
void updateFromNode()
{
if (node) {
position = node->getPosition();
rotation = node->getOrientation();
scale = node->getScale();
}
}
};
#endif // EDITSCENE_TRANSFORM_HPP

View File

@@ -0,0 +1,304 @@
#include "Gizmo.hpp"
#include "../components/Transform.hpp"
#include <cmath>
// Simple colors for axes - not using vertex colors since RTSS has issues
static const float COLOR_RED[3] = {1.0f, 0.0f, 0.0f};
static const float COLOR_GREEN[3] = {0.0f, 1.0f, 0.0f};
static const float COLOR_BLUE[3] = {0.0f, 0.0f, 1.0f};
static const float COLOR_YELLOW[3] = {1.0f, 1.0f, 0.0f};
Gizmo::Gizmo(Ogre::SceneManager *sceneMgr)
: m_sceneMgr(sceneMgr)
, m_gizmoNode(nullptr)
, m_axisX(nullptr)
, m_axisY(nullptr)
, m_axisZ(nullptr)
, m_mode(Mode::Translate)
, m_selectedAxis(Axis::None)
, m_hoveredAxis(Axis::None)
, m_size(1.0f)
, m_axisLength(2.0f)
, m_isDragging(false)
, m_dragStartT(0.0f)
{
m_gizmoNode = m_sceneMgr->getRootSceneNode()->createChildSceneNode("GizmoNode");
m_axisX = m_sceneMgr->createManualObject("GizmoAxisX");
m_axisY = m_sceneMgr->createManualObject("GizmoAxisY");
m_axisZ = m_sceneMgr->createManualObject("GizmoAxisZ");
m_gizmoNode->attachObject(m_axisX);
m_gizmoNode->attachObject(m_axisY);
m_gizmoNode->attachObject(m_axisZ);
// Set render queue to overlay so gizmo draws on top of everything
m_axisX->setRenderQueueGroup(Ogre::RENDER_QUEUE_OVERLAY);
m_axisY->setRenderQueueGroup(Ogre::RENDER_QUEUE_OVERLAY);
m_axisZ->setRenderQueueGroup(Ogre::RENDER_QUEUE_OVERLAY);
m_gizmoNode->setVisible(false);
}
Gizmo::~Gizmo()
{
if (m_axisX) m_sceneMgr->destroyManualObject(m_axisX);
if (m_axisY) m_sceneMgr->destroyManualObject(m_axisY);
if (m_axisZ) m_sceneMgr->destroyManualObject(m_axisZ);
if (m_gizmoNode) m_sceneMgr->destroySceneNode(m_gizmoNode);
}
void Gizmo::attachTo(flecs::entity entity)
{
m_attachedEntity = entity;
if (entity.is_alive() && entity.has<TransformComponent>()) {
createGizmoGeometry();
update();
m_gizmoNode->setVisible(true);
} else {
m_gizmoNode->setVisible(false);
}
}
void Gizmo::detach()
{
m_attachedEntity = flecs::entity::null();
m_gizmoNode->setVisible(false);
m_selectedAxis = Axis::None;
m_hoveredAxis = Axis::None;
m_isDragging = false;
}
void Gizmo::update()
{
if (!m_attachedEntity.is_alive() || !m_attachedEntity.has<TransformComponent>()) {
m_gizmoNode->setVisible(false);
return;
}
auto &transform = m_attachedEntity.get<TransformComponent>();
if (transform.node) {
m_gizmoNode->setPosition(transform.position);
m_gizmoNode->setOrientation(transform.rotation);
m_gizmoNode->setVisible(true);
}
}
void Gizmo::setMode(Mode mode)
{
if (m_mode != mode) {
m_mode = mode;
createGizmoGeometry();
}
}
void Gizmo::createGizmoGeometry()
{
float len = m_axisLength * m_size;
float arrowSize = 0.3f * m_size;
// X Axis - Red (or Yellow if selected/hovered)
bool xSelected = (m_selectedAxis == Axis::X || m_hoveredAxis == Axis::X);
m_axisX->clear();
m_axisX->begin("Ogre/AxisGizmo", Ogre::RenderOperation::OT_LINE_LIST);
if (xSelected) {
m_axisX->colour(1.0f, 1.0f, 0.0f); // Yellow
} else {
m_axisX->colour(1.0f, 0.0f, 0.0f); // Red
}
// Main line
m_axisX->position(0, 0, 0);
m_axisX->position(len, 0, 0);
// Arrow head lines
m_axisX->position(len, 0, 0);
m_axisX->position(len - arrowSize, arrowSize, 0);
m_axisX->position(len, 0, 0);
m_axisX->position(len - arrowSize, -arrowSize, 0);
m_axisX->end();
// Y Axis - Green
bool ySelected = (m_selectedAxis == Axis::Y || m_hoveredAxis == Axis::Y);
m_axisY->clear();
m_axisY->begin("Ogre/AxisGizmo", Ogre::RenderOperation::OT_LINE_LIST);
if (ySelected) {
m_axisY->colour(1.0f, 1.0f, 0.0f); // Yellow
} else {
m_axisY->colour(0.0f, 1.0f, 0.0f); // Green
}
// Main line
m_axisY->position(0, 0, 0);
m_axisY->position(0, len, 0);
// Arrow head lines
m_axisY->position(0, len, 0);
m_axisY->position(arrowSize, len - arrowSize, 0);
m_axisY->position(0, len, 0);
m_axisY->position(-arrowSize, len - arrowSize, 0);
m_axisY->end();
// Z Axis - Blue
bool zSelected = (m_selectedAxis == Axis::Z || m_hoveredAxis == Axis::Z);
m_axisZ->clear();
m_axisZ->begin("Ogre/AxisGizmo", Ogre::RenderOperation::OT_LINE_LIST);
if (zSelected) {
m_axisZ->colour(1.0f, 1.0f, 0.0f); // Yellow
} else {
m_axisZ->colour(0.0f, 0.0f, 1.0f); // Blue
}
// Main line
m_axisZ->position(0, 0, 0);
m_axisZ->position(0, 0, len);
// Arrow head lines
m_axisZ->position(0, 0, len);
m_axisZ->position(arrowSize, 0, len - arrowSize);
m_axisZ->position(0, 0, len);
m_axisZ->position(-arrowSize, 0, len - arrowSize);
m_axisZ->end();
}
// Project ray onto axis line and return the parameter t along the axis
static bool projectRayOntoAxis(const Ogre::Ray& ray, const Ogre::Vector3& axisOrigin,
const Ogre::Vector3& axisDir, float& outT)
{
Ogre::Vector3 rayOrigin = ray.getOrigin();
Ogre::Vector3 rayDir = ray.getDirection();
Ogre::Vector3 w0 = rayOrigin - axisOrigin;
float a = rayDir.dotProduct(rayDir);
float b = rayDir.dotProduct(axisDir);
float c = axisDir.dotProduct(axisDir);
float d = rayDir.dotProduct(w0);
float e = axisDir.dotProduct(w0);
float denom = a * c - b * b;
if (std::abs(denom) < 0.0001f) {
return false;
}
outT = (a * e - b * d) / denom;
return true;
}
bool Gizmo::onMousePressed(const Ogre::Ray &mouseRay)
{
if (!m_axisX->isVisible()) return false;
m_selectedAxis = hitTest(mouseRay);
if (m_selectedAxis != Axis::None && m_attachedEntity.is_alive()
&& m_attachedEntity.has<TransformComponent>()) {
m_isDragging = true;
auto &transform = m_attachedEntity.get_mut<TransformComponent>();
m_dragStartPosition = transform.position;
switch (m_selectedAxis) {
case Axis::X: m_dragAxisDir = m_gizmoNode->getOrientation() * Ogre::Vector3::UNIT_X; break;
case Axis::Y: m_dragAxisDir = m_gizmoNode->getOrientation() * Ogre::Vector3::UNIT_Y; break;
case Axis::Z: m_dragAxisDir = m_gizmoNode->getOrientation() * Ogre::Vector3::UNIT_Z; break;
default: m_dragAxisDir = Ogre::Vector3::UNIT_X; break;
}
projectRayOntoAxis(mouseRay, m_dragStartPosition, m_dragAxisDir, m_dragStartT);
return true;
}
return false;
}
bool Gizmo::onMouseMoved(const Ogre::Ray &mouseRay, const Ogre::Vector2 &mouseDelta)
{
if (m_isDragging && m_selectedAxis != Axis::None
&& m_attachedEntity.is_alive()
&& m_attachedEntity.has<TransformComponent>()) {
auto &transform = m_attachedEntity.get_mut<TransformComponent>();
float currentT;
if (!projectRayOntoAxis(mouseRay, m_dragStartPosition, m_dragAxisDir, currentT)) {
return true;
}
float deltaT = currentT - m_dragStartT;
transform.position = m_dragStartPosition + m_dragAxisDir * deltaT;
transform.applyToNode();
m_gizmoNode->setPosition(transform.position);
return true;
} else if (m_axisX->isVisible()) {
Axis prevHover = m_hoveredAxis;
m_hoveredAxis = hitTest(mouseRay);
if (prevHover != m_hoveredAxis) {
// Could update visualization here
}
}
return false;
}
bool Gizmo::onMouseReleased()
{
if (m_isDragging) {
m_isDragging = false;
m_selectedAxis = Axis::None;
return true;
}
return false;
}
Gizmo::Axis Gizmo::hitTest(const Ogre::Ray &mouseRay)
{
if (!m_gizmoNode || !m_axisX->isVisible())
return Axis::None;
Ogre::Vector3 gizmoPos = m_gizmoNode->getPosition();
float len = m_axisLength * m_size;
float threshold = 0.5f * m_size;
float bestDist = 1000000.0f;
Axis bestAxis = Axis::None;
for (int i = 0; i < 3; ++i) {
Ogre::Vector3 axisDir;
if (i == 0) axisDir = m_gizmoNode->getOrientation() * Ogre::Vector3::UNIT_X;
else if (i == 1) axisDir = m_gizmoNode->getOrientation() * Ogre::Vector3::UNIT_Y;
else axisDir = m_gizmoNode->getOrientation() * Ogre::Vector3::UNIT_Z;
for (int j = 0; j <= 8; ++j) {
float t = len * j / 8.0f;
Ogre::Vector3 pointOnAxis = gizmoPos + axisDir * t;
Ogre::Vector3 L = pointOnAxis - mouseRay.getOrigin();
float tca = L.dotProduct(mouseRay.getDirection());
if (tca < 0.01f) continue;
float d2 = L.dotProduct(L) - tca * tca;
if (d2 <= threshold * threshold) {
if (tca < bestDist) {
bestDist = tca;
bestAxis = static_cast<Axis>(i + 1);
}
}
}
}
return bestAxis;
}
void Gizmo::setVisible(bool visible)
{
if (m_gizmoNode) {
m_gizmoNode->setVisible(visible);
}
}
bool Gizmo::isVisible() const
{
return m_gizmoNode && m_axisX && m_axisX->isVisible();
}

View File

@@ -0,0 +1,107 @@
#ifndef EDITSCENE_GIZMO_HPP
#define EDITSCENE_GIZMO_HPP
#pragma once
#include <Ogre.h>
#include <OgreManualObject.h>
#include <flecs.h>
/**
* 3D Gizmo for editing entity transforms
* Shows X/Y/Z axes and allows mouse interaction for translation
*/
class Gizmo {
public:
enum class Mode {
Translate,
Rotate,
Scale
};
enum class Axis {
None,
X,
Y,
Z
};
Gizmo(Ogre::SceneManager *sceneMgr);
~Gizmo();
/**
* Attach to an entity's transform
*/
void attachTo(flecs::entity entity);
/**
* Detach from current entity
*/
void detach();
/**
* Update gizmo position/rotation to match attached entity
*/
void update();
/**
* Check if gizmo is attached to an entity
*/
bool isAttached() const { return m_attachedEntity.is_alive(); }
/**
* Get attached entity
*/
flecs::entity getAttachedEntity() const { return m_attachedEntity; }
/**
* Set gizmo mode (translate/rotate/scale)
*/
void setMode(Mode mode);
Mode getMode() const { return m_mode; }
/**
* Handle mouse input for gizmo interaction
* Returns true if gizmo handled the input
*/
bool onMousePressed(const Ogre::Ray &mouseRay);
bool onMouseMoved(const Ogre::Ray &mouseRay, const Ogre::Vector2 &mouseDelta);
bool onMouseReleased();
/**
* Show/hide gizmo
*/
void setVisible(bool visible);
bool isVisible() const;
/**
* Set gizmo size/scale
*/
void setSize(float size) { m_size = size; createGizmoGeometry(); }
float getSize() const { return m_size; }
private:
void createGizmoGeometry();
Axis hitTest(const Ogre::Ray &mouseRay);
Ogre::SceneManager *m_sceneMgr;
Ogre::SceneNode *m_gizmoNode;
Ogre::ManualObject *m_axisX;
Ogre::ManualObject *m_axisY;
Ogre::ManualObject *m_axisZ;
flecs::entity m_attachedEntity;
Mode m_mode;
Axis m_selectedAxis;
Axis m_hoveredAxis;
float m_size;
float m_axisLength;
bool m_isDragging;
// Drag state - stored at drag start and reused during drag
Ogre::Vector3 m_dragStartPosition;
Ogre::Vector3 m_dragAxisDir;
float m_dragStartT;
};
#endif // EDITSCENE_GIZMO_HPP

View File

@@ -0,0 +1,20 @@
#include <iostream>
#include "EditorApp.hpp"
int main(int argc, char *argv[])
{
(void)argc;
(void)argv;
try {
EditorApp app;
app.initApp();
app.getRoot()->startRendering();
app.closeApp();
} catch (const std::exception &e) {
std::cerr << "Error: " << e.what() << std::endl;
return 1;
}
return 0;
}

View File

@@ -0,0 +1,17 @@
# Ogre Resources Configuration for EditScene Editor
[General]
FileSystem=resources
FileSystem=resources/materials
FileSystem=resources/meshes
FileSystem=resources/textures
FileSystem=resources/buildings
FileSystem=resources/vehicles
[Popular]
FileSystem=resources/materials/programs
FileSystem=resources/materials/scripts
FileSystem=resources/materials/textures
[Essential]
Zip=/usr/share/OGRE-14/media/packs/SdkTrays.zip

View File

@@ -0,0 +1,55 @@
material GizmoRed
{
technique
{
pass
{
diffuse 1 0 0
ambient 1 0 0
specular 0 0 0 0
lighting on
}
}
}
material GizmoGreen
{
technique
{
pass
{
diffuse 0 1 0
ambient 0 1 0
specular 0 0 0 0
lighting on
}
}
}
material GizmoBlue
{
technique
{
pass
{
diffuse 0 0 1
ambient 0 0 1
specular 0 0 0 0
lighting on
}
}
}
material GizmoYellow
{
technique
{
pass
{
diffuse 1 1 0
ambient 1 1 0
specular 0 0 0 0
lighting on
}
}
}

View File

@@ -0,0 +1,637 @@
#include "EditorUISystem.hpp"
#include "../components/EntityName.hpp"
#include "../components/Transform.hpp"
#include "../components/Renderable.hpp"
#include "../components/EditorMarker.hpp"
#include "../ui/TransformEditor.hpp"
#include "../ui/RenderableEditor.hpp"
#include <imgui.h>
#include <algorithm>
EditorUISystem::EditorUISystem(flecs::world &world,
Ogre::SceneManager *sceneMgr)
: m_world(world)
, m_sceneMgr(sceneMgr)
, m_selectedEntity(flecs::entity::null())
, m_nameQuery(world.query<EntityNameComponent>())
{
registerComponentEditors();
m_gizmo = std::make_unique<Gizmo>(m_sceneMgr);
}
EditorUISystem::~EditorUISystem() = default;
bool EditorUISystem::onMousePressed(const Ogre::Ray &mouseRay)
{
if (m_gizmo) {
return m_gizmo->onMousePressed(mouseRay);
}
return false;
}
bool EditorUISystem::onMouseMoved(const Ogre::Ray &mouseRay, const Ogre::Vector2 &mouseDelta)
{
if (m_gizmo) {
return m_gizmo->onMouseMoved(mouseRay, mouseDelta);
}
return false;
}
bool EditorUISystem::onMouseReleased()
{
if (m_gizmo) {
return m_gizmo->onMouseReleased();
}
return false;
}
void EditorUISystem::registerComponentEditors()
{
// Register Transform component
auto transformEditor = std::make_unique<TransformEditor>();
m_componentRegistry.registerComponent<TransformComponent>(
"Transform", std::move(transformEditor),
// Adder
[this](flecs::entity e) {
if (!e.has<TransformComponent>()) {
TransformComponent transform;
transform.node =
m_sceneMgr->getRootSceneNode()
->createChildSceneNode();
transform.position = Ogre::Vector3::ZERO;
transform.rotation = Ogre::Quaternion::IDENTITY;
transform.scale = Ogre::Vector3::UNIT_SCALE;
e.set<TransformComponent>(transform);
}
},
// Remover
[this](flecs::entity e) {
if (e.has<TransformComponent>()) {
auto &transform =
e.get_mut<TransformComponent>();
if (transform.node) {
m_sceneMgr->destroySceneNode(
transform.node);
transform.node = nullptr;
}
e.remove<TransformComponent>();
}
});
// Register Renderable component
auto renderableEditor = std::make_unique<RenderableEditor>(m_sceneMgr);
m_componentRegistry.registerComponent<RenderableComponent>(
"Renderable", std::move(renderableEditor),
// Adder
[this](flecs::entity e) {
if (!e.has<RenderableComponent>()) {
e.set<RenderableComponent>({});
}
},
// Remover
[this](flecs::entity e) {
if (e.has<RenderableComponent>()) {
auto &renderable =
e.get_mut<RenderableComponent>();
if (renderable.entity) {
m_sceneMgr->destroyEntity(
renderable.entity);
renderable.entity = nullptr;
}
e.remove<RenderableComponent>();
}
});
}
void EditorUISystem::update()
{
// Render UI windows
// Note: NewFrame() is called by ImGuiRenderListener::preViewportUpdate
renderHierarchyWindow();
renderPropertyWindow();
// Update gizmo position to match selected entity
if (m_gizmo && m_gizmo->isAttached()) {
m_gizmo->update();
}
}
void EditorUISystem::setSelectedEntity(flecs::entity entity)
{
m_selectedEntity = entity;
// Update gizmo attachment
if (m_gizmo) {
if (entity.is_alive() && entity.has<TransformComponent>()) {
m_gizmo->attachTo(entity);
} else {
m_gizmo->detach();
}
}
}
void EditorUISystem::renderHierarchyWindow()
{
ImVec2 viewportSize = ImGui::GetMainViewport()->Size;
// Left window - fixed to left side
ImGui::SetNextWindowPos(ImVec2(0, 0), ImGuiCond_Always);
ImGui::SetNextWindowSize(ImVec2(LEFT_PANEL_WIDTH, viewportSize.y),
ImGuiCond_Always);
ImGuiWindowFlags windowFlags =
ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoMove |
ImGuiWindowFlags_NoResize | ImGuiWindowFlags_MenuBar;
if (ImGui::Begin("Entity Hierarchy", nullptr, windowFlags)) {
// Menu bar
if (ImGui::BeginMenuBar()) {
if (ImGui::BeginMenu("Entity")) {
if (ImGui::MenuItem("New Entity", "Ctrl+N")) {
createNewEntity();
}
if (ImGui::MenuItem("New Child Entity")) {
if (m_selectedEntity.is_alive()) {
createChildEntity(
m_selectedEntity);
}
}
ImGui::Separator();
if (ImGui::MenuItem(
"Duplicate", "Ctrl+D", false,
m_selectedEntity.is_alive())) {
duplicateEntity(m_selectedEntity);
}
ImGui::Separator();
if (ImGui::MenuItem(
"Delete", "Delete", false,
m_selectedEntity.is_alive())) {
deleteEntity(m_selectedEntity);
}
ImGui::EndMenu();
}
ImGui::EndMenuBar();
}
// Create Entity button
if (ImGui::Button("+ New Entity", ImVec2(120, 0))) {
createNewEntity();
}
ImGui::SameLine();
if (ImGui::Button("+ New Child", ImVec2(120, 0))) {
if (m_selectedEntity.is_alive()) {
createChildEntity(m_selectedEntity);
}
}
ImGui::Separator();
// Root entities query - entities without a parent
std::vector<flecs::entity> rootEntities;
m_world.query_builder<>()
.with<EditorMarkerComponent>()
.build()
.each([&](flecs::entity entity) {
// Check if entity has no parent (flecs::ChildOf)
bool isRoot = true;
entity.children([&](flecs::entity child) {
// Just checking if we can iterate children
});
// Alternative: use parent() function
if (entity.parent().is_valid() && entity.parent() != 0) {
isRoot = false;
}
if (isRoot) {
rootEntities.push_back(entity);
}
});
// Render tree for each root entity
for (auto &entity : rootEntities) {
renderEntityNode(entity, 0);
}
// If no entities, show message
if (rootEntities.empty()) {
ImGui::TextDisabled("No entities in scene");
}
}
ImGui::End();
}
void EditorUISystem::renderEntityNode(flecs::entity entity, int depth)
{
if (!entity.is_alive())
return;
// Get entity name
std::string name = "Unnamed";
if (entity.has<EntityNameComponent>()) {
name = entity.get<EntityNameComponent>().name;
}
// Collect children using flecs::ChildOf relationship
std::vector<flecs::entity> children;
entity.children([&](flecs::entity child) {
children.push_back(child);
});
bool hasChildren = !children.empty();
// Tree node flags
ImGuiTreeNodeFlags flags = ImGuiTreeNodeFlags_OpenOnArrow |
ImGuiTreeNodeFlags_OpenOnDoubleClick |
ImGuiTreeNodeFlags_SpanAvailWidth;
// Leaf nodes (no children) have bullet style
if (!hasChildren) {
flags |= ImGuiTreeNodeFlags_Leaf | ImGuiTreeNodeFlags_NoTreePushOnOpen;
}
// Highlight selected
if (m_selectedEntity == entity) {
flags |= ImGuiTreeNodeFlags_Selected;
}
// Create label with optional component indicators
char label[256];
std::string indicators;
if (entity.has<TransformComponent>())
indicators += " [T]";
if (entity.has<RenderableComponent>())
indicators += " [R]";
snprintf(label, sizeof(label), "%s%s##%llu", name.c_str(),
indicators.c_str(), (unsigned long long)entity.id());
// Draw tree node
bool isOpen = ImGui::TreeNodeEx(label, flags);
// Handle selection on click
if (ImGui::IsItemClicked()) {
setSelectedEntity(entity);
}
// Context menu
if (ImGui::BeginPopupContextItem()) {
renderEntityContextMenu(entity);
ImGui::EndPopup();
}
// Render children recursively
if (isOpen && hasChildren) {
for (auto &child : children) {
renderEntityNode(child, depth + 1);
}
ImGui::TreePop();
}
}
void EditorUISystem::renderEntityContextMenu(flecs::entity entity)
{
if (ImGui::MenuItem("New Child Entity")) {
createChildEntity(entity);
}
ImGui::Separator();
if (ImGui::MenuItem("Duplicate")) {
duplicateEntity(entity);
}
ImGui::Separator();
if (ImGui::MenuItem("Delete")) {
deleteEntity(entity);
}
}
void EditorUISystem::renderPropertyWindow()
{
ImVec2 viewportSize = ImGui::GetMainViewport()->Size;
// Right window - fixed to right side
ImGui::SetNextWindowPos(ImVec2(viewportSize.x - RIGHT_PANEL_WIDTH, 0),
ImGuiCond_Always);
ImGui::SetNextWindowSize(ImVec2(RIGHT_PANEL_WIDTH, viewportSize.y),
ImGuiCond_Always);
ImGuiWindowFlags windowFlags =
ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoMove |
ImGuiWindowFlags_NoResize | ImGuiWindowFlags_MenuBar;
if (ImGui::Begin("Property Editor", nullptr, windowFlags)) {
if (m_selectedEntity.is_alive()) {
// Entity name header
if (m_selectedEntity.has<EntityNameComponent>()) {
auto &nameComp =
m_selectedEntity
.get_mut<EntityNameComponent>();
char nameBuffer[256];
strcpy(nameBuffer, nameComp.name.c_str());
if (ImGui::InputText("Name", nameBuffer,
sizeof(nameBuffer))) {
nameComp.name = nameBuffer;
}
}
// Entity ID
ImGui::TextDisabled(
"ID: %llu",
(unsigned long long)m_selectedEntity.id());
ImGui::Separator();
// Component operations
renderComponentList(m_selectedEntity);
} else {
ImGui::TextDisabled("No entity selected");
ImGui::Text("Select an entity from the hierarchy");
}
}
ImGui::End();
}
void EditorUISystem::renderComponentList(flecs::entity entity)
{
// Component operations menu bar
if (ImGui::Button("Add Component")) {
ImGui::OpenPopup("AddComponentMenu");
}
renderAddComponentMenu(entity);
ImGui::SameLine();
if (ImGui::Button("Remove Component")) {
ImGui::OpenPopup("RemoveComponentMenu");
}
renderRemoveComponentMenu(entity);
ImGui::Separator();
// Render component editors
ImGui::BeginChild("Components", ImVec2(0, 0), true);
if (entity.has<TransformComponent>()) {
auto &transform = entity.get_mut<TransformComponent>();
m_componentRegistry.render<TransformComponent>(entity,
transform);
}
if (entity.has<RenderableComponent>()) {
auto &renderable = entity.get_mut<RenderableComponent>();
m_componentRegistry.render<RenderableComponent>(entity,
renderable);
}
// Show message if no components
if (!entity.has<TransformComponent>() &&
!entity.has<RenderableComponent>()) {
ImGui::TextDisabled("No components");
ImGui::Text("Click 'Add Component' to add components");
}
ImGui::EndChild();
}
void EditorUISystem::renderAddComponentMenu(flecs::entity entity)
{
if (ImGui::BeginPopup("AddComponentMenu")) {
ImGui::Text("Add Component:");
ImGui::Separator();
bool hasTransform = entity.has<TransformComponent>();
bool hasRenderable = entity.has<RenderableComponent>();
if (!hasTransform) {
if (ImGui::MenuItem("Transform")) {
m_componentRegistry
.addComponent<TransformComponent>(
entity);
}
}
if (!hasRenderable) {
if (ImGui::MenuItem("Renderable")) {
m_componentRegistry
.addComponent<RenderableComponent>(
entity);
}
}
if (hasTransform && hasRenderable) {
ImGui::TextDisabled("All components added");
}
ImGui::EndPopup();
}
}
void EditorUISystem::renderRemoveComponentMenu(flecs::entity entity)
{
{
if (ImGui::BeginPopup("RemoveComponentMenu")) {
ImGui::Text("Remove Component:");
ImGui::Separator();
bool hasAny = false;
if (entity.has<TransformComponent>()) {
hasAny = true;
if (ImGui::MenuItem("Transform")) {
m_componentRegistry.removeComponent<
TransformComponent>(entity);
}
}
if (entity.has<RenderableComponent>()) {
hasAny = true;
if (ImGui::MenuItem("Renderable")) {
m_componentRegistry.removeComponent<
RenderableComponent>(entity);
}
}
if (!hasAny) {
ImGui::TextDisabled("No components to remove");
}
ImGui::EndPopup();
}
}
}
void EditorUISystem::createNewEntity()
{
flecs::entity entity = m_world.entity();
entity.set<EntityNameComponent>(EntityNameComponent("New Entity"));
entity.add<EditorMarkerComponent>();
// Add transform by default
TransformComponent transform;
transform.node = m_sceneMgr->getRootSceneNode()->createChildSceneNode();
transform.position = Ogre::Vector3::ZERO;
transform.rotation = Ogre::Quaternion::IDENTITY;
transform.scale = Ogre::Vector3::UNIT_SCALE;
entity.set<TransformComponent>(transform);
setSelectedEntity(entity);
}
void EditorUISystem::createChildEntity(flecs::entity parent)
{
if (!parent.is_alive() || !parent.has<TransformComponent>())
return;
// Create entity as child of parent using flecs::ChildOf
flecs::entity entity = m_world.entity();
entity.child_of(parent);
entity.set<EntityNameComponent>(EntityNameComponent("Child Entity"));
entity.add<EditorMarkerComponent>();
// Create transform with parent as scene node parent
TransformComponent transform;
auto &parentTransform = parent.get_mut<TransformComponent>();
transform.node = parentTransform.node->createChildSceneNode();
transform.position = Ogre::Vector3::ZERO;
transform.rotation = Ogre::Quaternion::IDENTITY;
transform.scale = Ogre::Vector3::UNIT_SCALE;
entity.set<TransformComponent>(transform);
setSelectedEntity(entity);
}
void EditorUISystem::deleteEntity(flecs::entity entity)
{
if (!entity.is_alive())
return;
// First delete all children recursively
auto children = getEntityChildren(entity);
for (auto &child : children) {
deleteEntity(child);
}
// Clean up transform node
if (entity.has<TransformComponent>()) {
auto &transform = entity.get_mut<TransformComponent>();
if (transform.node) {
m_sceneMgr->destroySceneNode(transform.node);
}
}
// Clean up renderable
if (entity.has<RenderableComponent>()) {
auto &renderable = entity.get_mut<RenderableComponent>();
if (renderable.entity) {
m_sceneMgr->destroyEntity(renderable.entity);
}
}
if (m_selectedEntity == entity) {
setSelectedEntity(flecs::entity::null());
}
// Remove from cached list
auto it = std::find(m_allEntities.begin(), m_allEntities.end(), entity);
if (it != m_allEntities.end()) {
m_allEntities.erase(it);
}
entity.destruct();
}
void EditorUISystem::duplicateEntity(flecs::entity entity)
{
if (!entity.is_alive())
return;
flecs::entity newEntity = m_world.entity();
// Copy name
if (entity.has<EntityNameComponent>()) {
auto &name = entity.get<EntityNameComponent>().name;
newEntity.set<EntityNameComponent>(
EntityNameComponent(name + " (Copy)"));
}
// Copy parent relationship using flecs::ChildOf
flecs::entity parent = entity.parent();
if (parent.is_valid() && parent != 0) {
newEntity.child_of(parent);
}
// Copy transform
if (entity.has<TransformComponent>()) {
auto &oldTransform = entity.get<TransformComponent>();
TransformComponent newTransform;
// Find parent node
Ogre::SceneNode *parentNode = m_sceneMgr->getRootSceneNode();
flecs::entity parent = entity.parent();
if (parent.is_valid() && parent != 0 &&
parent.has<TransformComponent>()) {
parentNode = parent.get<TransformComponent>().node;
}
newTransform.node = parentNode->createChildSceneNode();
newTransform.position =
oldTransform.position +
Ogre::Vector3(1, 0, 0); // Offset slightly
newTransform.rotation = oldTransform.rotation;
newTransform.scale = oldTransform.scale;
newTransform.applyToNode();
newEntity.set<TransformComponent>(newTransform);
}
// Copy renderable (but not the mesh instance)
if (entity.has<RenderableComponent>()) {
auto &oldRenderable = entity.get<RenderableComponent>();
RenderableComponent newRenderable;
newRenderable.meshName = oldRenderable.meshName;
newRenderable.visible = oldRenderable.visible;
// Entity will be created when mesh is loaded
newEntity.set<RenderableComponent>(newRenderable);
}
setSelectedEntity(newEntity);
}
flecs::entity EditorUISystem::findEntityParent(flecs::entity entity)
{
if (!entity.is_alive())
return flecs::entity::null();
// Use flecs parent() function
flecs::entity parent = entity.parent();
if (parent.is_valid() && parent != 0) {
return parent;
}
return flecs::entity::null();
}
std::vector<flecs::entity>
EditorUISystem::getEntityChildren(flecs::entity parent)
{
std::vector<flecs::entity> children;
if (!parent.is_alive())
return children;
// Use flecs::ChildOf relationship to get children
parent.children([&](flecs::entity child) {
children.push_back(child);
});
return children;
}
bool EditorUISystem::isDescendantOf(flecs::entity potentialChild,
flecs::entity potentialParent)
{
if (!potentialChild.is_alive() || !potentialParent.is_alive())
return false;
flecs::entity current = findEntityParent(potentialChild);
while (current.is_alive()) {
if (current == potentialParent)
return true;
current = findEntityParent(current);
}
return false;
}

View File

@@ -0,0 +1,103 @@
#ifndef EDITSCENE_EDITORUISYSTEM_HPP
#define EDITSCENE_EDITORUISYSTEM_HPP
#pragma once
#include <flecs.h>
#include <Ogre.h>
#include <memory>
#include "../ui/ComponentRegistry.hpp"
#include "../components/EntityName.hpp"
#include "../gizmo/Gizmo.hpp"
/**
* Main UI system for the scene editor
* Handles rendering of:
* - Left window: Entity hierarchy
* - Right window: Property editor
* - 3D Gizmo for transform editing
*/
class EditorUISystem {
public:
EditorUISystem(flecs::world &world, Ogre::SceneManager *sceneMgr);
~EditorUISystem();
/**
* Update and render all UI windows
* Call this once per frame
*/
void update();
/**
* Set the currently selected entity
*/
void setSelectedEntity(flecs::entity entity);
/**
* Add existing entity to cache (for entities created before UI)
*/
void addEntity(flecs::entity entity) { m_allEntities.push_back(entity); }
/**
* Get the currently selected entity
*/
flecs::entity getSelectedEntity() const
{
return m_selectedEntity;
}
/**
* Handle mouse events for gizmo interaction
* Returns true if gizmo handled the event
*/
bool onMousePressed(const Ogre::Ray &mouseRay);
bool onMouseMoved(const Ogre::Ray &mouseRay, const Ogre::Vector2 &mouseDelta);
bool onMouseReleased();
/**
* Get the gizmo for external interaction
*/
Gizmo *getGizmo() const { return m_gizmo.get(); }
private:
// Window rendering functions
void renderHierarchyWindow();
void renderPropertyWindow();
// Entity operations
void renderEntityNode(flecs::entity entity, int depth);
void renderEntityContextMenu(flecs::entity entity);
void createNewEntity();
void createChildEntity(flecs::entity parent);
void deleteEntity(flecs::entity entity);
void duplicateEntity(flecs::entity entity);
// Component operations
void renderComponentList(flecs::entity entity);
void renderAddComponentMenu(flecs::entity entity);
void renderRemoveComponentMenu(flecs::entity entity);
// Helper functions
void registerComponentEditors();
flecs::entity findEntityParent(flecs::entity entity);
std::vector<flecs::entity> getEntityChildren(flecs::entity entity);
bool isDescendantOf(flecs::entity potentialChild,
flecs::entity potentialParent);
// Core references
flecs::world m_world;
Ogre::SceneManager *m_sceneMgr;
// State
flecs::entity m_selectedEntity;
ComponentRegistry m_componentRegistry;
std::vector<flecs::entity> m_allEntities;
std::unique_ptr<Gizmo> m_gizmo;
// Queries
flecs::query<EntityNameComponent> m_nameQuery;
// Constants
static constexpr float LEFT_PANEL_WIDTH = 300.0f;
static constexpr float RIGHT_PANEL_WIDTH = 350.0f;
};
#endif // EDITSCENE_EDITORUISYSTEM_HPP

View File

@@ -0,0 +1,50 @@
#ifndef EDITSCENE_COMPONENTEDITOR_HPP
#define EDITSCENE_COMPONENTEDITOR_HPP
#pragma once
#include <imgui.h>
#include <flecs.h>
/**
* Base interface for component editors
*/
class IComponentEditor {
public:
virtual ~IComponentEditor() = default;
/**
* Render the editor UI
* @param entity The flecs entity being edited
* @param component Pointer to the component data
* @return true if the component was modified
*/
virtual bool render(flecs::entity entity, void *component) = 0;
/**
* Get the display name for this component type
*/
virtual const char *getName() const = 0;
};
/**
* Template base class for component editors
* @tparam T The component type to edit
*/
template<typename T>
class ComponentEditor : public IComponentEditor {
public:
bool render(flecs::entity entity, void *component) override
{
return renderComponent(entity, *static_cast<T *>(component));
}
protected:
/**
* Implement this method to render the editor for your component
* @param entity The flecs entity being edited
* @param component Reference to the component data
* @return true if the component was modified
*/
virtual bool renderComponent(flecs::entity entity, T &component) = 0;
};
#endif // EDITSCENE_COMPONENTEDITOR_HPP

View File

@@ -0,0 +1,157 @@
#ifndef EDITSCENE_COMPONENTREGISTRY_HPP
#define EDITSCENE_COMPONENTREGISTRY_HPP
#pragma once
#include <typeindex>
#include <unordered_map>
#include <memory>
#include <functional>
#include "ComponentEditor.hpp"
/**
* Function type for adding a component to an entity
*/
using ComponentAdder = std::function<void(flecs::entity)>;
/**
* Function type for removing a component from an entity
*/
using ComponentRemover = std::function<void(flecs::entity)>;
/**
* Registry for component editors and their add/remove functions
*/
class ComponentRegistry {
public:
struct ComponentInfo {
const char *name;
std::unique_ptr<IComponentEditor> editor;
ComponentAdder adder;
ComponentRemover remover;
};
ComponentRegistry() = default;
~ComponentRegistry() = default;
// Delete copy
ComponentRegistry(const ComponentRegistry &) = delete;
ComponentRegistry &operator=(const ComponentRegistry &) = delete;
// Allow move
ComponentRegistry(ComponentRegistry &&) = default;
ComponentRegistry &operator=(ComponentRegistry &&) = default;
/**
* Register a component type with its editor and add/remove functions
*/
template<typename T>
void registerComponent(const char *name,
std::unique_ptr<ComponentEditor<T>> editor,
ComponentAdder adder, ComponentRemover remover)
{
ComponentInfo info;
info.name = name;
info.editor = std::move(editor);
info.adder = adder;
info.remover = remover;
m_components[std::type_index(typeid(T))] = std::move(info);
}
/**
* Check if a component type is registered
*/
template<typename T>
bool hasComponent() const
{
return m_components.find(std::type_index(typeid(T))) !=
m_components.end();
}
/**
* Render a component editor
*/
template<typename T>
bool render(flecs::entity entity, T &component)
{
auto it = m_components.find(std::type_index(typeid(T)));
if (it != m_components.end() && it->second.editor) {
return it->second.editor->render(entity, &component);
}
return false;
}
/**
* Add a component to an entity
*/
template<typename T>
void addComponent(flecs::entity entity)
{
auto it = m_components.find(std::type_index(typeid(T)));
if (it != m_components.end() && it->second.adder) {
it->second.adder(entity);
}
}
/**
* Remove a component from an entity
*/
template<typename T>
void removeComponent(flecs::entity entity)
{
auto it = m_components.find(std::type_index(typeid(T)));
if (it != m_components.end() && it->second.remover) {
it->second.remover(entity);
}
}
/**
* Get all registered component types
*/
std::vector<std::type_index> getRegisteredTypes() const
{
std::vector<std::type_index> types;
for (const auto &pair : m_components) {
types.push_back(pair.first);
}
return types;
}
/**
* Get component name for a type
*/
const char *getComponentName(const std::type_index &type) const
{
auto it = m_components.find(type);
if (it != m_components.end()) {
return it->second.name;
}
return "Unknown";
}
/**
* Check if entity has a specific component type
*/
bool entityHasComponent(flecs::entity entity,
const std::type_index &type) const
{
// This is a simplified check - in practice you'd need to
// store flecs component IDs and check those
return false;
}
/**
* Render add component menu for all registered components
* Returns true if a component was added
*/
bool renderAddComponentMenu(flecs::entity entity);
/**
* Render remove component menu for components the entity has
* Returns true if a component was removed
*/
bool renderRemoveComponentMenu(flecs::entity entity);
private:
std::unordered_map<std::type_index, ComponentInfo> m_components;
};
#endif // EDITSCENE_COMPONENTREGISTRY_HPP

View File

@@ -0,0 +1,233 @@
#include "RenderableEditor.hpp"
#include <imgui.h>
#include "../components/Transform.hpp"
#include <OgreResourceGroupManager.h>
#include <algorithm>
char RenderableEditor::s_meshNameBuffer[256] = { 0 };
char RenderableEditor::s_searchBuffer[256] = { 0 };
bool RenderableEditor::s_showMeshBrowser = false;
RenderableEditor::RenderableEditor(Ogre::SceneManager *sceneMgr)
: m_sceneMgr(sceneMgr)
{
}
void RenderableEditor::scanMeshFiles()
{
if (m_meshesScanned)
return;
m_meshFiles.clear();
// Get all resource groups
Ogre::ResourceGroupManager &rgm = Ogre::ResourceGroupManager::getSingleton();
Ogre::StringVector groups = rgm.getResourceGroups();
for (const auto &group : groups) {
// Get file info for this group
Ogre::FileInfoListPtr fileList = rgm.findResourceFileInfo(group, "*");
if (fileList) {
for (const auto &fileInfo : *fileList) {
const std::string &filename = fileInfo.filename;
// Check for supported extensions
if (filename.size() > 5) {
std::string ext = filename.substr(filename.find_last_of('.') + 1);
// Convert to lowercase
std::transform(ext.begin(), ext.end(), ext.begin(), ::tolower);
if (ext == "mesh" || ext == "glb" || ext == "gltf") {
m_meshFiles.push_back(filename);
}
}
}
}
}
// Sort and remove duplicates
std::sort(m_meshFiles.begin(), m_meshFiles.end());
m_meshFiles.erase(std::unique(m_meshFiles.begin(), m_meshFiles.end()),
m_meshFiles.end());
m_meshesScanned = true;
}
bool RenderableEditor::matchesSearch(const std::string &meshName,
const std::string &search)
{
if (search.empty())
return true;
std::string lowerMesh = meshName;
std::string lowerSearch = search;
std::transform(lowerMesh.begin(), lowerMesh.end(), lowerMesh.begin(),
::tolower);
std::transform(lowerSearch.begin(), lowerSearch.end(), lowerSearch.begin(),
::tolower);
return lowerMesh.find(lowerSearch) != std::string::npos;
}
void RenderableEditor::loadMesh(RenderableComponent &renderable,
flecs::entity entity)
{
if (renderable.meshName.empty() || !m_sceneMgr)
return;
try {
// Destroy old entity if exists
if (renderable.entity) {
m_sceneMgr->destroyEntity(renderable.entity);
renderable.entity = nullptr;
}
// Create new entity
renderable.entity = m_sceneMgr->createEntity(renderable.meshName);
// Attach to transform node if available
if (entity.has<TransformComponent>()) {
auto &transform = entity.get_mut<TransformComponent>();
if (transform.node && renderable.entity) {
transform.node->attachObject(renderable.entity);
}
}
} catch (const std::exception &e) {
// Error will be displayed in UI
}
}
bool RenderableEditor::renderComponent(flecs::entity entity,
RenderableComponent &renderable)
{
bool modified = false;
if (ImGui::CollapsingHeader("Renderable",
ImGuiTreeNodeFlags_DefaultOpen)) {
ImGui::Indent();
// Mesh name input
strcpy(s_meshNameBuffer, renderable.meshName.c_str());
if (ImGui::InputText("Mesh Name", s_meshNameBuffer,
sizeof(s_meshNameBuffer))) {
renderable.meshName = s_meshNameBuffer;
}
ImGui::SameLine();
if (ImGui::Button("Browse...")) {
scanMeshFiles();
s_showMeshBrowser = true;
strcpy(s_searchBuffer, "");
}
// Mesh Browser Popup
if (s_showMeshBrowser) {
ImGui::OpenPopup("Mesh Browser");
}
if (ImGui::BeginPopupModal("Mesh Browser", &s_showMeshBrowser,
ImGuiWindowFlags_AlwaysAutoResize)) {
ImGui::Text("Search:");
ImGui::SameLine();
ImGui::InputText("##search", s_searchBuffer,
sizeof(s_searchBuffer));
ImGui::Separator();
// Show filtered mesh list
ImGui::BeginChild("MeshList", ImVec2(400, 300), true);
std::string searchStr(s_searchBuffer);
for (const auto &meshName : m_meshFiles) {
if (!matchesSearch(meshName, searchStr))
continue;
// Highlight current selection
bool isCurrent = (renderable.meshName == meshName);
if (isCurrent) {
ImGui::PushStyleColor(
ImGuiCol_Text,
ImVec4(0.2f, 0.8f, 0.2f, 1.0f));
}
if (ImGui::Selectable(meshName.c_str(), isCurrent)) {
renderable.meshName = meshName;
strcpy(s_meshNameBuffer, meshName.c_str());
}
if (isCurrent) {
ImGui::PopStyleColor();
}
}
if (m_meshFiles.empty()) {
ImGui::TextDisabled("No mesh files found");
}
ImGui::EndChild();
ImGui::Separator();
// Show count
int visibleCount = 0;
for (const auto &meshName : m_meshFiles) {
if (matchesSearch(meshName, searchStr))
visibleCount++;
}
ImGui::Text("Showing %d of %zu meshes", visibleCount,
m_meshFiles.size());
// Buttons
if (ImGui::Button("Load", ImVec2(120, 0))) {
loadMesh(renderable, entity);
modified = true;
s_showMeshBrowser = false;
ImGui::CloseCurrentPopup();
}
ImGui::SameLine();
if (ImGui::Button("Cancel", ImVec2(120, 0))) {
s_showMeshBrowser = false;
ImGui::CloseCurrentPopup();
}
ImGui::SameLine();
if (ImGui::Button("Refresh", ImVec2(120, 0))) {
m_meshesScanned = false;
scanMeshFiles();
}
ImGui::EndPopup();
}
// Load mesh button
if (ImGui::Button("Load Mesh")) {
loadMesh(renderable, entity);
modified = true;
}
// Visibility toggle
if (renderable.entity) {
bool visible = renderable.entity->getVisible();
if (ImGui::Checkbox("Visible", &visible)) {
renderable.entity->setVisible(visible);
renderable.visible = visible;
modified = true;
}
}
// Entity info
if (renderable.entity) {
ImGui::Text("Entity: %p", renderable.entity);
} else {
ImGui::TextDisabled("No entity loaded");
}
ImGui::Unindent();
}
return modified;
}

View File

@@ -0,0 +1,36 @@
#ifndef EDITSCENE_RENDERABLEEDITOR_HPP
#define EDITSCENE_RENDERABLEEDITOR_HPP
#pragma once
#include "ComponentEditor.hpp"
#include "../components/Renderable.hpp"
#include <OgreSceneManager.h>
#include <vector>
#include <string>
/**
* Editor for RenderableComponent
*/
class RenderableEditor : public ComponentEditor<RenderableComponent> {
public:
explicit RenderableEditor(Ogre::SceneManager *sceneMgr);
const char *getName() const override { return "Renderable"; }
protected:
bool renderComponent(flecs::entity entity,
RenderableComponent &renderable) override;
private:
void scanMeshFiles();
void loadMesh(RenderableComponent &renderable, flecs::entity entity);
bool matchesSearch(const std::string &meshName, const std::string &search);
Ogre::SceneManager *m_sceneMgr;
std::vector<std::string> m_meshFiles;
bool m_meshesScanned = false;
static char s_meshNameBuffer[256];
static char s_searchBuffer[256];
static bool s_showMeshBrowser;
};
#endif // EDITSCENE_RENDERABLEEDITOR_HPP

View File

@@ -0,0 +1,69 @@
#include "TransformEditor.hpp"
#include <imgui.h>
bool TransformEditor::renderComponent(flecs::entity entity,
TransformComponent &transform)
{
bool modified = false;
if (ImGui::CollapsingHeader("Transform",
ImGuiTreeNodeFlags_DefaultOpen)) {
ImGui::Indent();
// Position
float pos[3] = { transform.position.x, transform.position.y,
transform.position.z };
if (ImGui::DragFloat3("Position", pos, 0.1f)) {
transform.position =
Ogre::Vector3(pos[0], pos[1], pos[2]);
modified = true;
}
// Rotation (using Ogre's getYaw/Pitch/Roll)
float yaw = Ogre::Radian(transform.rotation.getYaw()).valueDegrees();
float pitch = Ogre::Radian(transform.rotation.getPitch()).valueDegrees();
float roll = Ogre::Radian(transform.rotation.getRoll()).valueDegrees();
float rot[3] = { yaw, pitch, roll };
if (ImGui::DragFloat3("Rotation (Y/P/R)", rot, 0.5f)) {
Ogre::Quaternion q1(Ogre::Degree(rot[0]), Ogre::Vector3::UNIT_Y);
Ogre::Quaternion q2(Ogre::Degree(rot[1]), Ogre::Vector3::UNIT_X);
Ogre::Quaternion q3(Ogre::Degree(rot[2]), Ogre::Vector3::UNIT_Z);
transform.rotation = q1 * q2 * q3;
modified = true;
}
// Scale
float scale[3] = { transform.scale.x, transform.scale.y,
transform.scale.z };
if (ImGui::DragFloat3("Scale", scale, 0.01f)) {
transform.scale =
Ogre::Vector3(scale[0], scale[1], scale[2]);
modified = true;
}
// Reset buttons
if (ImGui::Button("Reset Position")) {
transform.position = Ogre::Vector3::ZERO;
modified = true;
}
ImGui::SameLine();
if (ImGui::Button("Reset Rotation")) {
transform.rotation = Ogre::Quaternion::IDENTITY;
modified = true;
}
ImGui::SameLine();
if (ImGui::Button("Reset Scale")) {
transform.scale = Ogre::Vector3::UNIT_SCALE;
modified = true;
}
// Apply changes to scene node
if (modified && transform.node) {
transform.applyToNode();
}
ImGui::Unindent();
}
return modified;
}

View File

@@ -0,0 +1,19 @@
#ifndef EDITSCENE_TRANSFORMEDITOR_HPP
#define EDITSCENE_TRANSFORMEDITOR_HPP
#pragma once
#include "ComponentEditor.hpp"
#include "../components/Transform.hpp"
/**
* Editor for TransformComponent
*/
class TransformEditor : public ComponentEditor<TransformComponent> {
public:
const char *getName() const override { return "Transform"; }
protected:
bool renderComponent(flecs::entity entity,
TransformComponent &transform) override;
};
#endif // EDITSCENE_TRANSFORMEDITOR_HPP

View File

@@ -0,0 +1,35 @@
project(sceneEditor)
find_package(OGRE REQUIRED COMPONENTS Bites Paging Terrain CONFIG)
find_package(ZLIB)
find_package(SDL2)
find_package(assimp REQUIRED CONFIG)
find_package(OgreProcedural REQUIRED CONFIG)
find_package(pugixml REQUIRED CONFIG)
find_package(flecs REQUIRED CONFIG)
find_package(Tracy REQUIRED CONFIG)
add_executable(editorFeatureDemo main.cpp EditorApp.cpp systems/EditorUISystem.cpp camera/EditorCamera.cpp)
target_link_libraries(editorFeatureDemo OgreMain OgreBites
flecs::flecs_static
)
file(COPY "${CMAKE_CURRENT_SOURCE_DIR}/resources.cfg"
DESTINATION "${CMAKE_CURRENT_BINARY_DIR}")
add_custom_command(TARGET editorFeatureDemo POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy_directory
"${CMAKE_BINARY_DIR}/resources"
"${CMAKE_CURRENT_BINARY_DIR}/resources"
# COMMAND ${CMAKE_COMMAND} -E copy
# "${CMAKE_CURRENT_SOURCE_DIR}/resources/main/jaiqua.mesh"
# "${CMAKE_CURRENT_BINARY_DIR}/resources/main/jaiqua.mesh"
# COMMAND ${CMAKE_COMMAND} -E copy
# "${CMAKE_CURRENT_SOURCE_DIR}/resources/main/jaiqua.skeleton"
# "${CMAKE_CURRENT_BINARY_DIR}/resources/main/jaiqua.skeleton"
# COMMAND ${CMAKE_COMMAND} -E copy
# "${CMAKE_CURRENT_SOURCE_DIR}/resources/main/jaiqua.material"
# "${CMAKE_CURRENT_BINARY_DIR}/resources/main/jaiqua.material"
# COMMAND ${CMAKE_COMMAND} -E copy
# "${CMAKE_CURRENT_SOURCE_DIR}/resources/main/blue_jaiqua.jpg"
# "${CMAKE_CURRENT_BINARY_DIR}/resources/main/blue_jaiqua.jpg"
DEPENDS "${CMAKE_BINARY_DIR}/resources"
COMMENT "Copying generated resources from root build dir to local build dir"
)

View File

@@ -0,0 +1,451 @@
#include "EditorApp.hpp"
#include "systems/EditorUISystem.hpp"
#include "camera/EditorCamera.hpp"
#include "components/Transform.hpp"
#include "components/Renderable.hpp"
#include "components/EditorComponents.hpp"
#include "components/EulerUtils.hpp"
EditorApp::EditorApp()
: OgreBites::ApplicationContext("OgreEditor")
, mSceneMgr(nullptr)
, mOverlaySystem(nullptr)
, mImGuiOverlay(nullptr)
, mIsDraggingCursor(false)
, mCurrentModifiers(0)
, mGridVisible(true)
, mAxesVisible(true)
, m3DCursorPosition(Ogre::Vector3::ZERO)
{
}
EditorApp::~EditorApp()
{
if (mImGuiOverlay) {
delete mImGuiOverlay;
mImGuiOverlay = nullptr;
}
if (mOverlaySystem) {
delete mOverlaySystem;
mOverlaySystem = nullptr;
}
}
void EditorApp::setup()
{
// Call base class setup first
OgreBites::ApplicationContext::setup();
// Get root and setup scene
Ogre::Root *root = getRoot();
if (!root) {
OGRE_EXCEPT(Ogre::Exception::ERR_RT_ASSERTION_FAILED,
"Failed to get Ogre::Root", "EditorApp::setup");
return;
}
mSceneMgr = root->createSceneManager();
if (!mSceneMgr) {
OGRE_EXCEPT(Ogre::Exception::ERR_RT_ASSERTION_FAILED,
"Failed to create SceneManager",
"EditorApp::setup");
return;
}
// Setup overlay system
mOverlaySystem = new Ogre::OverlaySystem();
mSceneMgr->addRenderQueueListener(mOverlaySystem);
// Setup render window
Ogre::RenderWindow *window = getRenderWindow();
if (!window) {
OGRE_EXCEPT(Ogre::Exception::ERR_RT_ASSERTION_FAILED,
"Failed to get RenderWindow", "EditorApp::setup");
return;
}
// Setup ImGui overlay
setupImGui();
setupFlecs();
setupScene();
// Setup editor camera
mEditorCamera = std::make_unique<EditorCamera>(mSceneMgr, window);
// Setup UI system
mUISystem = std::make_unique<EditorUISystem>(mWorld, mSceneMgr,
m3DCursorPosition);
// Set the cursor moved callback
mUISystem->setOnCursorMoved(
[this](const Ogre::Vector3 &pos) { m3DCursorPosition = pos; });
// Register input listeners
addInputListener(this);
// Add ImGui input listener - this is a method of ApplicationContext
OgreBites::InputListener *imguiListener = getImGuiInputListener();
if (imguiListener) {
addInputListener(imguiListener);
}
}
void EditorApp::setupFlecs()
{
// Configure flecs
mWorld.set_entity_range(1, 10000);
// Register components using the helper function
registerComponents(mWorld);
// Create default entities
flecs::entity rootEntity = mWorld.entity();
rootEntity.set<EntityNameComponent>({ "Root" });
// Create a test entity with mesh
flecs::entity testEntity = mWorld.entity();
testEntity.set<EntityNameComponent>({ "Test Cube" });
// Create transform with scene node
TransformComponent transform;
transform.node = mSceneMgr->getRootSceneNode()->createChildSceneNode();
transform.position = Ogre::Vector3(0, 0, 0);
transform.applyToNode();
testEntity.set<TransformComponent>(transform);
// Try to load a mesh
try {
RenderableComponent renderable("cube.mesh");
renderable.entity = mSceneMgr->createEntity("cube.mesh");
testEntity.set<RenderableComponent>(renderable);
// Attach to node
if (testEntity.has<TransformComponent>()) {
auto &testTransform =
testEntity.get_mut<TransformComponent>();
if (testTransform.node && renderable.entity) {
testTransform.node->attachObject(
renderable.entity);
}
}
} catch (const std::exception &e) {
Ogre::LogManager::getSingleton().logMessage(
"Failed to load cube.mesh: " + std::string(e.what()));
}
// Optional: Add a system for auto-updating transforms
mWorld.system<TransformComponent>().each(
[](flecs::entity e, TransformComponent &transform) {
// Auto-sync transforms if needed
// transform.updateFromNode();
});
}
void EditorApp::setupScene()
{
// Setup ambient light
mSceneMgr->setAmbientLight(Ogre::ColourValue(0.3f, 0.3f, 0.3f));
// Create directional light with scene node
Ogre::Light *directionalLight = mSceneMgr->createLight("MainLight");
directionalLight->setType(Ogre::Light::LT_DIRECTIONAL);
directionalLight->setDiffuseColour(Ogre::ColourValue(1.0f, 1.0f, 1.0f));
directionalLight->setSpecularColour(
Ogre::ColourValue(0.5f, 0.5f, 0.5f));
// Create scene node for directional light and set direction
Ogre::SceneNode *lightNode =
mSceneMgr->getRootSceneNode()->createChildSceneNode();
lightNode->attachObject(directionalLight);
lightNode->setDirection(Ogre::Vector3(1, -1, 0), Ogre::Node::TS_WORLD);
// Create a fill light from below
Ogre::Light *fillLight = mSceneMgr->createLight("FillLight");
fillLight->setType(Ogre::Light::LT_DIRECTIONAL);
fillLight->setDiffuseColour(Ogre::ColourValue(0.4f, 0.4f, 0.4f));
fillLight->setSpecularColour(Ogre::ColourValue(0.2f, 0.2f, 0.2f));
Ogre::SceneNode *fillLightNode =
mSceneMgr->getRootSceneNode()->createChildSceneNode();
fillLightNode->attachObject(fillLight);
fillLightNode->setDirection(Ogre::Vector3(-0.5f, -1.0f, 0.5f),
Ogre::Node::TS_WORLD);
// Optional: Add a grid helper with named node for toggling
try {
Ogre::Plane plane(Ogre::Vector3::UNIT_Y, -1);
Ogre::MeshManager::getSingleton().createPlane(
"grid_plane",
Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME,
plane, 20, 20, 20, 20, true, 1, 5, 5,
Ogre::Vector3::UNIT_Z);
Ogre::Entity *gridEntity =
mSceneMgr->createEntity("grid_plane");
// Try to set a grid material if available
if (Ogre::MaterialManager::getSingleton().resourceExists(
"GridMaterial")) {
gridEntity->setMaterialName("GridMaterial");
}
// Create node with a name for easy access
Ogre::SceneNode *gridNode =
mSceneMgr->getRootSceneNode()->createChildSceneNode(
"GridNode");
gridNode->attachObject(gridEntity);
gridNode->setPosition(0, -1, 0);
Ogre::LogManager::getSingleton().logMessage(
"Grid created successfully");
} catch (const std::exception &e) {
Ogre::LogManager::getSingleton().logMessage(
"Grid plane creation failed: " + std::string(e.what()));
}
// Add a simple axis helper
createAxisHelper();
}
void EditorApp::createAxisHelper()
{
try {
// X-axis (red)
Ogre::ManualObject *axisX =
mSceneMgr->createManualObject("AxisX");
axisX->begin("BaseWhiteNoLighting",
Ogre::RenderOperation::OT_LINE_LIST);
axisX->colour(Ogre::ColourValue(1.0f, 0.0f, 0.0f));
axisX->position(0, 0, 0);
axisX->position(2, 0, 0);
axisX->end();
// Y-axis (green)
Ogre::ManualObject *axisY =
mSceneMgr->createManualObject("AxisY");
axisY->begin("BaseWhiteNoLighting",
Ogre::RenderOperation::OT_LINE_LIST);
axisY->colour(Ogre::ColourValue(0.0f, 1.0f, 0.0f));
axisY->position(0, 0, 0);
axisY->position(0, 2, 0);
axisY->end();
// Z-axis (blue)
Ogre::ManualObject *axisZ =
mSceneMgr->createManualObject("AxisZ");
axisZ->begin("BaseWhiteNoLighting",
Ogre::RenderOperation::OT_LINE_LIST);
axisZ->colour(Ogre::ColourValue(0.0f, 0.0f, 1.0f));
axisZ->position(0, 0, 0);
axisZ->position(0, 0, 2);
axisZ->end();
// Create named node for easy access
Ogre::SceneNode *axisNode =
mSceneMgr->getRootSceneNode()->createChildSceneNode(
"AxisNode");
axisNode->attachObject(axisX);
axisNode->attachObject(axisY);
axisNode->attachObject(axisZ);
} catch (const std::exception &e) {
Ogre::LogManager::getSingleton().logMessage(
"Axis helper creation failed: " +
std::string(e.what()));
}
}
void EditorApp::setupImGui()
{
// Create ImGui overlay using Ogre's built-in integration
mImGuiOverlay = new Ogre::ImGuiOverlay();
// Show the overlay - this enables it in the overlay system
mImGuiOverlay->show();
// Setup ImGui style
ImGui::StyleColorsDark();
// Configure ImGui IO (standard features only)
ImGuiIO &io = ImGui::GetIO();
io.ConfigWindowsMoveFromTitleBarOnly = true;
Ogre::LogManager::getSingleton().logMessage(
"ImGui overlay initialized");
}
bool EditorApp::frameRenderingQueued(const Ogre::FrameEvent &evt)
{
// Update camera
if (mEditorCamera) {
mEditorCamera->update(evt.timeSinceLastFrame);
}
// Start ImGui frame
if (mImGuiOverlay) {
mImGuiOverlay->NewFrame();
}
// Update UI system (this will render all ImGui windows)
if (mUISystem) {
mUISystem->update();
}
return OgreBites::ApplicationContext::frameRenderingQueued(evt);
}
bool EditorApp::mouseMoved(const OgreBites::MouseMotionEvent &evt)
{
if (mEditorCamera) {
mEditorCamera->handleMouseMove(evt);
}
return true;
}
bool EditorApp::mousePressed(const OgreBites::MouseButtonEvent &evt)
{
if (mEditorCamera) {
mEditorCamera->handleMousePress(evt);
// Check for Ctrl key using current modifier state
if (evt.button == OgreBites::BUTTON_LEFT && isCtrlPressed()) {
Ogre::Viewport *vp =
mEditorCamera->getCamera()->getViewport();
if (vp) {
float screenX = static_cast<float>(evt.x) /
static_cast<float>(
vp->getActualWidth());
float screenY = static_cast<float>(evt.y) /
static_cast<float>(
vp->getActualHeight());
Ogre::Vector3 cursorPos =
mEditorCamera->getMouseRay(screenX,
screenY);
m3DCursorPosition = cursorPos;
}
}
}
return true;
}
bool EditorApp::mouseReleased(const OgreBites::MouseButtonEvent &evt)
{
if (mEditorCamera) {
mEditorCamera->handleMouseRelease(evt);
}
return true;
}
bool EditorApp::keyPressed(const OgreBites::KeyboardEvent &evt)
{
if (mEditorCamera) {
mEditorCamera->handleKeyboard(evt);
}
// Update current modifiers from the event
mCurrentModifiers = evt.keysym.mod;
// Handle delete key
if (evt.keysym.sym == OgreBites::SDLK_DELETE) {
if (mUISystem && mSelectedEntity) {
// Clean up scene node
if (mSelectedEntity.has<TransformComponent>()) {
auto &transform =
mSelectedEntity
.get_mut<TransformComponent>();
if (transform.node) {
mSceneMgr->destroySceneNode(
transform.node);
transform.node = nullptr;
}
}
// Clean up renderable
if (mSelectedEntity.has<RenderableComponent>()) {
auto &renderable =
mSelectedEntity
.get_mut<RenderableComponent>();
if (renderable.entity) {
mSceneMgr->destroyEntity(
renderable.entity);
renderable.entity = nullptr;
}
}
mSelectedEntity.destruct();
mUISystem->setSelectedEntity(flecs::entity::null());
}
}
// Handle F5 for reloading resources
if (evt.keysym.sym == OgreBites::SDLK_F5) {
reloadResources();
}
// Handle F3 for showing/hiding grid
if (evt.keysym.sym == OgreBites::SDLK_F3) {
toggleGrid();
}
// Handle F4 for showing/hiding axes
if (evt.keysym.sym == OgreBites::SDLK_F4) {
toggleAxes();
}
return true;
}
bool EditorApp::keyReleased(const OgreBites::KeyboardEvent &evt)
{
// Update current modifiers from the event
mCurrentModifiers = evt.keysym.mod;
return true;
}
void EditorApp::toggleGrid()
{
// Use getChild and cast to SceneNode
Ogre::Node *node = mSceneMgr->getRootSceneNode()->getChild("GridNode");
if (node) {
Ogre::SceneNode *gridNode =
static_cast<Ogre::SceneNode *>(node);
mGridVisible = !mGridVisible;
gridNode->setVisible(mGridVisible);
Ogre::LogManager::getSingleton().logMessage(
"Grid visibility: " +
std::string(mGridVisible ? "ON" : "OFF"));
} else {
Ogre::LogManager::getSingleton().logMessage(
"Grid node not found");
}
}
void EditorApp::toggleAxes()
{
// Use getChild and cast to SceneNode
Ogre::Node *node = mSceneMgr->getRootSceneNode()->getChild("AxisNode");
if (node) {
Ogre::SceneNode *axisNode =
static_cast<Ogre::SceneNode *>(node);
mAxesVisible = !mAxesVisible;
axisNode->setVisible(mAxesVisible);
Ogre::LogManager::getSingleton().logMessage(
"Axes visibility: " +
std::string(mAxesVisible ? "ON" : "OFF"));
} else {
Ogre::LogManager::getSingleton().logMessage(
"Axis node not found");
}
}
void EditorApp::reloadResources()
{
// Reload all materials
Ogre::LogManager::getSingleton().logMessage("Reloading materials...");
Ogre::MaterialManager::getSingleton().reloadAll();
Ogre::LogManager::getSingleton().logMessage("Resources reloaded");
}

View File

@@ -0,0 +1,73 @@
#pragma once
#include <Ogre.h>
#include <OgreApplicationContext.h>
#include <OgreInput.h>
#include <OgreOverlaySystem.h>
#include <OgreImGuiOverlay.h>
#include <flecs.h>
#include <memory>
// Forward declarations
class EditorUISystem;
class EditorCamera;
class EditorApp : public OgreBites::ApplicationContext,
public OgreBites::InputListener
{
public:
EditorApp();
virtual ~EditorApp();
void setup() override;
bool frameRenderingQueued(const Ogre::FrameEvent& evt) override;
// Ogre 14.5 input handling
bool mouseMoved(const OgreBites::MouseMotionEvent& evt) override;
bool mousePressed(const OgreBites::MouseButtonEvent& evt) override;
bool mouseReleased(const OgreBites::MouseButtonEvent& evt) override;
bool keyPressed(const OgreBites::KeyboardEvent& evt) override;
bool keyReleased(const OgreBites::KeyboardEvent& evt) override;
// Setup methods
void setupScene();
void setupImGui();
void setupFlecs();
void createAxisHelper();
// Helper methods
void toggleGrid();
void toggleAxes();
void reloadResources();
// Getters
flecs::entity getSelectedEntity() const { return mSelectedEntity; }
Ogre::SceneManager* getSceneManager() const { return mSceneMgr; }
// Helper to check modifier keys using current state
bool isCtrlPressed() const { return (mCurrentModifiers & OgreBites::KMOD_CTRL) != 0; }
bool isShiftPressed() const { return (mCurrentModifiers & OgreBites::KMOD_SHIFT) != 0; }
bool isAltPressed() const { return (mCurrentModifiers & OgreBites::KMOD_ALT) != 0; }
private:
// Ogre objects
Ogre::SceneManager* mSceneMgr;
Ogre::OverlaySystem* mOverlaySystem;
Ogre::ImGuiOverlay* mImGuiOverlay;
// ECS
flecs::world mWorld;
flecs::entity mSelectedEntity;
// Editor state
Ogre::Vector3 m3DCursorPosition;
bool mIsDraggingCursor;
bool mGridVisible;
bool mAxesVisible;
// Modifier key tracking
uint16_t mCurrentModifiers;
// Editor systems
std::unique_ptr<EditorUISystem> mUISystem;
std::unique_ptr<EditorCamera> mEditorCamera;
};

View File

@@ -0,0 +1,259 @@
#include "EditorCamera.hpp"
#include <OgreRay.h>
#include <OgrePlane.h>
#include <OgreMath.h>
#include <algorithm>
#include <cmath>
EditorCamera::EditorCamera(Ogre::SceneManager *sceneMgr,
Ogre::RenderWindow *window)
: mSceneMgr(sceneMgr)
, mWindow(window)
, mYaw(0.0f)
, mPitch(Ogre::Math::DegreesToRadians(
45.0f)) // Convert 45 degrees to radians
, mDistance(15.0f)
, mTargetPosition(0, 0, 0)
, mCursorPosition(0, 0, 0)
, mActive(true)
, mMiddleMouseDown(false)
, mRightMouseDown(false)
, mLastMouseX(0)
, mLastMouseY(0)
, mMoveSpeed(10.0f)
, mRotateSpeed(0.005f)
, mZoomSpeed(0.5f)
{
// Create camera
mCamera = mSceneMgr->createCamera("EditorCamera");
mCamera->setNearClipDistance(0.1f);
mCamera->setFarClipDistance(1000.0f);
mCamera->setAutoAspectRatio(true);
// Create camera node and attach camera
mCameraNode = mSceneMgr->getRootSceneNode()->createChildSceneNode();
mCameraNode->attachObject(mCamera);
// Create target node for looking at
mTargetNode = mSceneMgr->getRootSceneNode()->createChildSceneNode();
mTargetNode->setPosition(mTargetPosition);
// Set viewport
Ogre::Viewport *vp = mWindow->addViewport(mCamera);
vp->setBackgroundColour(Ogre::ColourValue(0.1f, 0.1f, 0.1f));
mCamera->setAspectRatio(Ogre::Real(vp->getActualWidth()) /
Ogre::Real(vp->getActualHeight()));
// Initialize camera position
updateCameraTransform();
}
EditorCamera::~EditorCamera()
{
if (mSceneMgr) {
if (mCamera) {
mCameraNode->detachObject(mCamera);
mSceneMgr->destroyCamera(mCamera);
}
if (mCameraNode) {
mSceneMgr->destroySceneNode(mCameraNode);
}
if (mTargetNode) {
mSceneMgr->destroySceneNode(mTargetNode);
}
}
}
void EditorCamera::update(float deltaTime)
{
if (!mActive)
return;
// Camera transform is updated in real-time through input handlers
}
void EditorCamera::handleMouseMove(const OgreBites::MouseMotionEvent &evt)
{
if (!mActive)
return;
int deltaX = evt.x - mLastMouseX;
int deltaY = evt.y - mLastMouseY;
if (mMiddleMouseDown) {
// Pan - move target position
Ogre::Vector3 right =
mCameraNode->getOrientation() * Ogre::Vector3::UNIT_X;
Ogre::Vector3 up =
mCameraNode->getOrientation() * Ogre::Vector3::UNIT_Y;
float moveScale = mMoveSpeed * 0.01f;
Ogre::Vector3 panDelta = (right * static_cast<float>(-deltaX) +
up * static_cast<float>(deltaY)) *
moveScale;
mTargetPosition += panDelta;
mTargetNode->setPosition(mTargetPosition);
updateCameraTransform();
} else if (mRightMouseDown) {
// Orbit - rotate around target
// Convert delta to radians (delta is in pixels, mRotateSpeed is in radians per pixel)
orbit(static_cast<float>(deltaX) * mRotateSpeed,
static_cast<float>(deltaY) * mRotateSpeed);
}
mLastMouseX = evt.x;
mLastMouseY = evt.y;
}
void EditorCamera::handleMousePress(const OgreBites::MouseButtonEvent &evt)
{
if (evt.button == OgreBites::BUTTON_MIDDLE) {
mMiddleMouseDown = true;
} else if (evt.button == OgreBites::BUTTON_RIGHT) {
mRightMouseDown = true;
}
mLastMouseX = evt.x;
mLastMouseY = evt.y;
}
void EditorCamera::handleMouseRelease(const OgreBites::MouseButtonEvent &evt)
{
if (evt.button == OgreBites::BUTTON_MIDDLE) {
mMiddleMouseDown = false;
} else if (evt.button == OgreBites::BUTTON_RIGHT) {
mRightMouseDown = false;
}
}
void EditorCamera::handleKeyboard(const OgreBites::KeyboardEvent &evt)
{
if (!mActive)
return;
float moveDelta = mMoveSpeed * 0.016f; // Assume 60fps
Ogre::Vector3 forward =
mCameraNode->getOrientation() * Ogre::Vector3::NEGATIVE_UNIT_Z;
Ogre::Vector3 right =
mCameraNode->getOrientation() * Ogre::Vector3::UNIT_X;
forward.y = 0; // Keep movement horizontal
right.y = 0;
forward.normalise();
right.normalise();
Ogre::Vector3 moveDeltaVec = Ogre::Vector3::ZERO;
// Use character constants instead of SDLK_ constants
if (evt.keysym.sym == 'w') {
moveDeltaVec += forward * moveDelta;
} else if (evt.keysym.sym == 's') {
moveDeltaVec -= forward * moveDelta;
} else if (evt.keysym.sym == 'a') {
moveDeltaVec -= right * moveDelta;
} else if (evt.keysym.sym == 'd') {
moveDeltaVec += right * moveDelta;
} else if (evt.keysym.sym == 'q') {
moveDeltaVec.y += moveDelta;
} else if (evt.keysym.sym == 'e') {
moveDeltaVec.y -= moveDelta;
}
if (moveDeltaVec != Ogre::Vector3::ZERO) {
mTargetPosition += moveDeltaVec;
mTargetNode->setPosition(mTargetPosition);
updateCameraTransform();
}
}
void EditorCamera::setPosition(const Ogre::Vector3 &pos)
{
// Calculate distance and angles from target
Ogre::Vector3 offset = pos - mTargetPosition;
mDistance = offset.length();
// Calculate yaw and pitch from the offset
mYaw = std::atan2(offset.z, offset.x);
mPitch = std::asin(offset.y / mDistance);
updateCameraTransform();
}
void EditorCamera::setTarget(const Ogre::Vector3 &target)
{
mTargetPosition = target;
mTargetNode->setPosition(mTargetPosition);
updateCameraTransform();
}
void EditorCamera::orbit(float deltaYaw, float deltaPitch)
{
mYaw += deltaYaw;
mPitch += deltaPitch;
// Clamp pitch to avoid gimbal lock (-89 to 89 degrees)
// Convert degree limits to radians using Ogre::Math::DegreesToRadians
float maxPitch = Ogre::Math::DegreesToRadians(89.0f);
float minPitch = Ogre::Math::DegreesToRadians(-89.0f);
mPitch = std::clamp(mPitch, minPitch, maxPitch);
updateCameraTransform();
}
void EditorCamera::pan(const Ogre::Vector3 &delta)
{
mTargetPosition += delta;
mTargetNode->setPosition(mTargetPosition);
updateCameraTransform();
}
void EditorCamera::zoom(float delta)
{
mDistance -= delta;
mDistance = std::clamp(mDistance, 1.0f, 500.0f);
updateCameraTransform();
}
void EditorCamera::updateCameraTransform()
{
// Calculate camera position based on spherical coordinates
float cosPitch = std::cos(mPitch);
float sinPitch = std::sin(mPitch);
float cosYaw = std::cos(mYaw);
float sinYaw = std::sin(mYaw);
Ogre::Vector3 offset;
offset.x = mDistance * cosPitch * cosYaw;
offset.y = mDistance * sinPitch;
offset.z = mDistance * cosPitch * sinYaw;
// Set camera node position
Ogre::Vector3 cameraPosition = mTargetPosition + offset;
mCameraNode->setPosition(cameraPosition);
// Make camera look at target
mCameraNode->lookAt(mTargetPosition, Ogre::Node::TS_WORLD);
}
Ogre::Vector3 EditorCamera::getMouseRay(float screenX, float screenY)
{
if (!mCamera)
return Ogre::Vector3::ZERO;
// Create ray from camera through mouse position
Ogre::Ray ray = mCamera->getCameraToViewportRay(screenX, screenY);
// Create a plane at Y=0 for ground intersection
Ogre::Plane groundPlane(Ogre::Vector3::UNIT_Y, 0);
// Calculate intersection with ground plane
std::pair<bool, Ogre::Real> intersection = ray.intersects(groundPlane);
if (intersection.first) {
// Return the intersection point
return ray.getPoint(intersection.second);
}
// If no intersection, return a point at a default distance along the ray
return ray.getPoint(100.0f);
}

View File

@@ -0,0 +1,67 @@
#ifndef EDITORCAMERA_HPP
#define EDITORCAMERA_HPP
#pragma once
#include <Ogre.h>
#include <OgreInput.h>
#include <OgreCamera.h>
#include <OgreSceneNode.h>
#include <OgreSceneManager.h>
#include <OgreRenderWindow.h>
#include <memory>
#include <functional>
class EditorCamera {
public:
EditorCamera(Ogre::SceneManager* sceneMgr, Ogre::RenderWindow* window);
~EditorCamera();
void update(float deltaTime);
void setActive(bool active);
Ogre::Camera* getCamera() const { return mCamera; }
Ogre::SceneNode* getCameraNode() const { return mCameraNode; }
void handleMouseMove(const OgreBites::MouseMotionEvent& evt);
void handleMousePress(const OgreBites::MouseButtonEvent& evt);
void handleMouseRelease(const OgreBites::MouseButtonEvent& evt);
void handleKeyboard(const OgreBites::KeyboardEvent& evt);
Ogre::Vector3 getMouseRay(float screenX, float screenY);
Ogre::Vector3 getCursorPosition() const { return mCursorPosition; }
void setCursorPosition(const Ogre::Vector3& pos) { mCursorPosition = pos; }
// Camera control methods
void setPosition(const Ogre::Vector3& pos);
void setTarget(const Ogre::Vector3& target);
void orbit(float deltaYaw, float deltaPitch);
void pan(const Ogre::Vector3& delta);
void zoom(float delta);
private:
Ogre::SceneManager* mSceneMgr;
Ogre::Camera* mCamera;
Ogre::SceneNode* mCameraNode;
Ogre::SceneNode* mTargetNode; // Node for target position
Ogre::RenderWindow* mWindow;
// Camera parameters
float mYaw;
float mPitch;
float mDistance;
Ogre::Vector3 mTargetPosition;
Ogre::Vector3 mCursorPosition;
// Input state
bool mActive;
bool mMiddleMouseDown;
bool mRightMouseDown;
int mLastMouseX;
int mLastMouseY;
// Control speeds
float mMoveSpeed;
float mRotateSpeed;
float mZoomSpeed;
void updateCameraTransform();
};
#endif // EDITORCAMERA_HPP

View File

@@ -0,0 +1,4 @@
#ifndef EDITORCOMPONENTS_HPP
#define EDITORCOMPONENTS_HPP
#endif // EDITORCOMPONENTS_HPP

View File

@@ -0,0 +1,102 @@
#ifndef EULERUTILS_HPP
#define EULERUTILS_HPP
#pragma once
#include <Ogre.h>
#include <cmath>
#include <algorithm>
namespace EulerUtils {
// Convert Euler angles (in degrees) to quaternion
// Order: Pitch (X), Yaw (Y), Roll (Z)
inline Ogre::Quaternion fromEulerDegrees(const Ogre::Vector3& degrees) {
Ogre::Radian pitch(Ogre::Degree(degrees.x));
Ogre::Radian yaw(Ogre::Degree(degrees.y));
Ogre::Radian roll(Ogre::Degree(degrees.z));
// Create quaternions for each axis
Ogre::Quaternion qPitch(pitch, Ogre::Vector3::UNIT_X);
Ogre::Quaternion qYaw(yaw, Ogre::Vector3::UNIT_Y);
Ogre::Quaternion qRoll(roll, Ogre::Vector3::UNIT_Z);
// Combine in ZYX order (roll, yaw, pitch)
return qYaw * qPitch * qRoll;
}
// Convert quaternion to Euler angles (in degrees)
// Returns Vector3 (pitch, yaw, roll) in degrees
inline Ogre::Vector3 toEulerDegrees(const Ogre::Quaternion& quat) {
// Extract rotation matrix from quaternion
Ogre::Matrix3 rotMat;
quat.ToRotationMatrix(rotMat);
// Extract Euler angles from rotation matrix
// Using XYZ (pitch, yaw, roll) order
Ogre::Radian pitch, yaw, roll;
rotMat.ToEulerAnglesXYZ(pitch, yaw, roll);
return Ogre::Vector3(
pitch.valueDegrees(),
yaw.valueDegrees(),
roll.valueDegrees()
);
}
// Alternative implementation using quaternion math (more stable)
inline Ogre::Vector3 toEulerDegreesStable(const Ogre::Quaternion& quat) {
// Extract quaternion components
float w = quat.w;
float x = quat.x;
float y = quat.y;
float z = quat.z;
// Pitch (X-axis rotation)
float sinr_cosp = 2.0f * (w * x + y * z);
float cosr_cosp = 1.0f - 2.0f * (x * x + y * y);
float pitch = std::atan2(sinr_cosp, cosr_cosp);
// Yaw (Y-axis rotation)
float sinp = 2.0f * (w * y - z * x);
float yaw;
if (std::abs(sinp) >= 1.0f) {
yaw = std::copysign(Ogre::Math::HALF_PI, sinp);
} else {
yaw = std::asin(sinp);
}
// Roll (Z-axis rotation)
float siny_cosp = 2.0f * (w * z + x * y);
float cosy_cosp = 1.0f - 2.0f * (y * y + z * z);
float roll = std::atan2(siny_cosp, cosy_cosp);
return Ogre::Vector3(
Ogre::Radian(pitch).valueDegrees(),
Ogre::Radian(yaw).valueDegrees(),
Ogre::Radian(roll).valueDegrees()
);
}
// Apply rotation to a vector using quaternion
inline Ogre::Vector3 rotateVector(const Ogre::Quaternion& quat, const Ogre::Vector3& vec) {
return quat * vec;
}
// Clamp Euler angles to reasonable ranges
inline Ogre::Vector3 clampEulerDegrees(const Ogre::Vector3& euler, float minDeg = -180.0f, float maxDeg = 180.0f) {
return Ogre::Vector3(
std::clamp(euler.x, minDeg, maxDeg),
std::clamp(euler.y, minDeg, maxDeg),
std::clamp(euler.z, minDeg, maxDeg)
);
}
// Normalize Euler angles to [-180, 180] range
inline Ogre::Vector3 normalizeEulerDegrees(const Ogre::Vector3& euler) {
return Ogre::Vector3(
std::fmod(euler.x + 180.0f, 360.0f) - 180.0f,
std::fmod(euler.y + 180.0f, 360.0f) - 180.0f,
std::fmod(euler.z + 180.0f, 360.0f) - 180.0f
);
}
}
#endif // EULERUTILS_HPP

View File

@@ -0,0 +1,4 @@
#ifndef RENDERABLE_HPP
#define RENDERABLE_HPP
#endif // RENDERABLE_HPP

View File

@@ -0,0 +1,77 @@
#pragma once
#include <Ogre.h>
#include <flecs.h>
#include <string>
#include "EulerUtils.hpp"
// Component definitions
struct TransformComponent {
Ogre::SceneNode* node;
Ogre::Vector3 position;
Ogre::Quaternion orientation;
Ogre::Vector3 scale;
TransformComponent() : node(nullptr), position(Ogre::Vector3::ZERO),
orientation(Ogre::Quaternion::IDENTITY),
scale(Ogre::Vector3::UNIT_SCALE) {}
void applyToNode() {
if (node) {
node->setPosition(position);
node->setOrientation(orientation);
node->setScale(scale);
}
}
void updateFromNode() {
if (node) {
position = node->getPosition();
orientation = node->getOrientation();
scale = node->getScale();
}
}
void setEulerDegrees(const Ogre::Vector3& degrees) {
orientation = EulerUtils::fromEulerDegrees(degrees);
applyToNode();
}
Ogre::Vector3 getEulerDegrees() const {
return EulerUtils::toEulerDegrees(orientation);
}
void rotateEulerDegrees(const Ogre::Vector3& deltaDegrees) {
Ogre::Vector3 current = getEulerDegrees();
Ogre::Vector3 newEuler = current + deltaDegrees;
newEuler = EulerUtils::normalizeEulerDegrees(newEuler);
setEulerDegrees(newEuler);
}
};
struct RenderableComponent {
Ogre::Entity* entity;
std::string meshName;
RenderableComponent() : entity(nullptr) {}
RenderableComponent(const std::string& mesh) : meshName(mesh), entity(nullptr) {}
};
struct EditorTag {
bool isSelected;
EditorTag() : isSelected(false) {}
};
struct EntityNameComponent {
std::string name;
EntityNameComponent() : name("Entity") {}
EntityNameComponent(const std::string& n) : name(n) {}
};
// Register components with flecs 4.1.4
inline void registerComponents(flecs::world& world) {
// Simple component registration - this is all that's needed
world.component<TransformComponent>();
world.component<RenderableComponent>();
world.component<EditorTag>();
world.component<EntityNameComponent>();
}

View File

@@ -0,0 +1,17 @@
#include <iostream>
#include "EditorApp.hpp"
int main(int argc, char *argv[])
{
try {
EditorApp app;
app.initApp();
app.getRoot()->startRendering();
app.closeApp();
} catch (const std::exception &e) {
std::cerr << "Error: " << e.what() << std::endl;
return 1;
}
return 0;
}

View File

@@ -0,0 +1,82 @@
# Ogre Core Resources
[OgreInternal]
#FileSystem=./Media/Main
FileSystem=resources/main
FileSystem=resources/shaderlib
#FileSystem=./Media/RTShaderLib
FileSystem=resources/terrain
# Resources required by OgreBites::Trays
[Essential]
#Zip=./Media/packs/SdkTrays.zip
#Zip=./Media/packs/profiler.zip
## this line will end up in the [Essential] group
#FileSystem=./Media/thumbnails
# Common sample resources needed by many of the samples.
# Rarely used resources should be separately loaded by the
# samples which require them.
[General]
FileSystem=skybox
FileSystem=resources/buildings
FileSystem=resources/buildings/parts/pier
FileSystem=resources/buildings/parts/furniture
FileSystem=resources/vehicles
FileSystem=resources/debug
FileSystem=resources/fonts
# PBR media must come before the scripts that reference it
#FileSystem=./Media/PBR
#FileSystem=./Media/PBR/filament
#FileSystem=./Media/materials/programs/GLSL
#FileSystem=./Media/materials/programs/GLSL120
#FileSystem=./Media/materials/programs/GLSL150
#FileSystem=./Media/materials/programs/GLSL400
#FileSystem=./Media/materials/programs/GLSLES
#FileSystem=./Media/materials/programs/SPIRV
#FileSystem=./Media/materials/programs/Cg
#FileSystem=./Media/materials/programs/HLSL
#FileSystem=./Media/materials/programs/HLSL_Cg
#FileSystem=./Media/materials/scripts
#FileSystem=./Media/materials/textures
#FileSystem=./Media/materials/textures/terrain
#FileSystem=./Media/models
#FileSystem=./Media/particle
#FileSystem=./Media/DeferredShadingMedia
#FileSystem=./Media/DeferredShadingMedia/DeferredShading/post
#FileSystem=./Media/PCZAppMedia
#FileSystem=./Media/materials/scripts/SSAO
#FileSystem=./Media/materials/textures/SSAO
#FileSystem=./Media/volumeTerrain
#FileSystem=./Media/CSMShadows
#Zip=./Media/packs/cubemap.zip
#Zip=./Media/packs/cubemapsJS.zip
#Zip=./Media/packs/dragon.zip
#Zip=./Media/packs/fresneldemo.zip
#Zip=./Media/packs/ogredance.zip
#Zip=./Media/packs/Sinbad.zip
#Zip=./Media/packs/skybox.zip
#Zip=./Media/volumeTerrain/volumeTerrainBig.zip
#Zip=./Media/packs/DamagedHelmet.zip
#Zip=./Media/packs/filament_shaders.zip
#[BSPWorld]
#Zip=./Media/packs/oa_rpg3dm2.pk3
#Zip=./Media/packs/ogretestmap.zip
# Materials for visual tests
#[Tests]
#FileSystem=/media/slapin/library/ogre/ogre-sdk/Tests/Media
[LuaScripts]
FileSystem=lua-scripts
#[Characters]
#FileSystem=./characters
[Audio]
FileSystem=./audio/gui
[Water]
FileSystem=water

View File

@@ -0,0 +1,260 @@
#include "EditorUISystem.hpp"
#include "../ui/ComponentEditorRegistry.hpp"
#include "../ui/TransformEditor.hpp"
#include "../ui/RenderableEditor.hpp"
#include <imgui.h>
#include <algorithm>
EditorUISystem::EditorUISystem(flecs::world &world,
Ogre::SceneManager *sceneMgr,
Ogre::Vector3 &cursorPos)
: m_world(world)
, m_sceneMgr(sceneMgr)
, m_cursorPosition(cursorPos)
, m_nameQuery(world.query<EntityNameComponent>())
, m_editorRegistry()
{
registerEditors();
refreshEntityList();
}
EditorUISystem::~EditorUISystem()
{
}
void EditorUISystem::registerEditors()
{
// Register transform editor - using the template version
auto transformEditor =
std::make_unique<TransformEditor>(m_cursorPosition, [this]() {
if (m_onCursorMoved) {
m_onCursorMoved(m_cursorPosition);
}
});
m_editorRegistry.registerEditor<TransformComponent>(
std::move(transformEditor));
// Register renderable editor - using the template version
auto renderableEditor = std::make_unique<RenderableEditor>(m_sceneMgr);
m_editorRegistry.registerEditor<RenderableComponent>(
std::move(renderableEditor));
}
void EditorUISystem::setOnCursorMoved(
std::function<void(const Ogre::Vector3 &)> callback)
{
m_onCursorMoved = callback;
}
void EditorUISystem::update()
{
renderHierarchyWindow();
renderPropertyWindow();
}
void EditorUISystem::renderHierarchyWindow()
{
ImGui::Begin("Scene Hierarchy", nullptr, ImGuiWindowFlags_NoCollapse);
if (ImGui::Button("Create Entity")) {
flecs::entity entity = m_world.entity();
entity.set<EntityNameComponent>({ "New Entity" });
// Create transform component with scene node
TransformComponent transform;
transform.node =
m_sceneMgr->getRootSceneNode()->createChildSceneNode();
transform.position = Ogre::Vector3::ZERO;
entity.set<TransformComponent>(transform);
refreshEntityList();
}
ImGui::Separator();
static float lastRefresh = 0;
if (ImGui::GetTime() - lastRefresh > 1.0f) {
refreshEntityList();
lastRefresh = ImGui::GetTime();
}
// Display entities using query
m_nameQuery.each([this](flecs::entity e, EntityNameComponent &name) {
if (!e.is_alive())
return;
std::string displayName =
name.name.empty() ?
("Entity " + std::to_string(e.id())) :
name.name;
ImGuiTreeNodeFlags flags = ImGuiTreeNodeFlags_Leaf |
ImGuiTreeNodeFlags_NoTreePushOnOpen;
if (m_selectedEntity == e) {
flags |= ImGuiTreeNodeFlags_Selected;
}
ImGui::TreeNodeEx(displayName.c_str(), flags);
if (ImGui::IsItemClicked()) {
setSelectedEntity(e);
}
if (ImGui::BeginPopupContextItem()) {
renderEntityContextMenu(e);
ImGui::EndPopup();
}
// Component indicators
if (e.has<TransformComponent>()) {
ImGui::SameLine();
ImGui::TextDisabled("[Transform]");
}
if (e.has<RenderableComponent>()) {
ImGui::SameLine();
ImGui::TextDisabled("[Mesh]");
}
});
ImGui::End();
}
void EditorUISystem::renderEntityContextMenu(flecs::entity entity)
{
if (ImGui::MenuItem("Delete")) {
// Clean up scene node
if (entity.has<TransformComponent>()) {
auto &transform = entity.get_mut<TransformComponent>();
if (transform.node) {
m_sceneMgr->destroySceneNode(transform.node);
transform.node = nullptr;
}
}
// Clean up renderable
if (entity.has<RenderableComponent>()) {
auto &renderable =
entity.get_mut<RenderableComponent>();
if (renderable.entity) {
m_sceneMgr->destroyEntity(renderable.entity);
renderable.entity = nullptr;
}
}
if (m_selectedEntity == entity) {
setSelectedEntity(flecs::entity::null());
}
entity.destruct();
refreshEntityList();
}
ImGui::Separator();
if (ImGui::MenuItem("Add Transform Component")) {
if (!entity.has<TransformComponent>()) {
TransformComponent transform;
transform.node = m_sceneMgr->getRootSceneNode()
->createChildSceneNode();
transform.position = Ogre::Vector3::ZERO;
entity.set<TransformComponent>(transform);
}
}
if (ImGui::MenuItem("Add Mesh Component")) {
if (!entity.has<RenderableComponent>()) {
entity.set<RenderableComponent>({});
}
}
}
void EditorUISystem::renderPropertyWindow()
{
ImGui::Begin("Properties", nullptr, ImGuiWindowFlags_NoCollapse);
if (m_selectedEntity && m_selectedEntity.is_alive()) {
// Entity name editing
if (m_selectedEntity.has<EntityNameComponent>()) {
auto &name =
m_selectedEntity.get_mut<EntityNameComponent>();
static char nameBuffer[256];
strcpy(nameBuffer, name.name.c_str());
if (ImGui::InputText("Entity Name", nameBuffer,
sizeof(nameBuffer))) {
name.name = nameBuffer;
}
}
ImGui::Separator();
render3DCursorControls();
ImGui::Separator();
renderComponentEditors(m_selectedEntity);
} else {
ImGui::Text("No entity selected");
render3DCursorControls();
}
ImGui::End();
}
void EditorUISystem::render3DCursorControls()
{
if (ImGui::CollapsingHeader("3D Cursor",
ImGuiTreeNodeFlags_DefaultOpen)) {
ImGui::Indent();
float cursorPos[3] = { m_cursorPosition.x, m_cursorPosition.y,
m_cursorPosition.z };
if (ImGui::DragFloat3("Cursor Position", cursorPos, 0.1f)) {
m_cursorPosition = Ogre::Vector3(
cursorPos[0], cursorPos[1], cursorPos[2]);
if (m_onCursorMoved) {
m_onCursorMoved(m_cursorPosition);
}
}
ImGui::Text("Tip: Ctrl+Click in scene view to move cursor");
if (ImGui::Button("Reset Cursor")) {
m_cursorPosition = Ogre::Vector3::ZERO;
if (m_onCursorMoved) {
m_onCursorMoved(m_cursorPosition);
}
}
ImGui::Unindent();
}
}
void EditorUISystem::renderComponentEditors(flecs::entity entity)
{
// Render Transform component if present
if (entity.has<TransformComponent>()) {
auto &transform = entity.get_mut<TransformComponent>();
m_editorRegistry.renderComponent<TransformComponent>(entity,
transform);
}
// Render Renderable component if present
if (entity.has<RenderableComponent>()) {
auto &renderable = entity.get_mut<RenderableComponent>();
m_editorRegistry.renderComponent<RenderableComponent>(
entity, renderable);
}
}
void EditorUISystem::setSelectedEntity(flecs::entity entity)
{
m_selectedEntity = entity;
}
void EditorUISystem::refreshEntityList()
{
m_entityList.clear();
m_nameQuery.each([this](flecs::entity e, EntityNameComponent &name) {
m_entityList.push_back(e);
});
}

View File

@@ -0,0 +1,44 @@
#ifndef EDITORUISYSTEM_HPP
#define EDITORUISYSTEM_HPP
#pragma once
#include <flecs.h>
#include <imgui.h>
#include <vector>
#include <memory>
#include <functional>
#include "../ui/ComponentEditorRegistry.hpp"
#include "../components/Transform.hpp"
#include "../components/Renderable.hpp"
#include "../components/EditorComponents.hpp"
class EditorUISystem {
public:
EditorUISystem(flecs::world& world, Ogre::SceneManager* sceneMgr, Ogre::Vector3& cursorPos);
~EditorUISystem();
void update();
void setSelectedEntity(flecs::entity entity);
void setOnCursorMoved(std::function<void(const Ogre::Vector3&)> callback);
private:
void renderHierarchyWindow();
void renderPropertyWindow();
void renderEntityContextMenu(flecs::entity entity);
void renderComponentEditors(flecs::entity entity);
void render3DCursorControls();
void refreshEntityList();
void registerEditors();
flecs::world& m_world;
Ogre::SceneManager* m_sceneMgr;
Ogre::Vector3& m_cursorPosition;
flecs::entity m_selectedEntity;
ComponentEditorRegistry m_editorRegistry;
std::vector<flecs::entity> m_entityList;
std::function<void(const Ogre::Vector3&)> m_onCursorMoved;
// flecs 4.1.4 query for entities with name component
flecs::query<EntityNameComponent> m_nameQuery;
};
#endif // EDITORUISYSTEM_HPP

View File

@@ -0,0 +1,4 @@
#ifndef RENDERSYSTEM_HPP
#define RENDERSYSTEM_HPP
#endif // RENDERSYSTEM_HPP

View File

@@ -0,0 +1,70 @@
#ifndef COMPONENTEDITOR_HPP
#define COMPONENTEDITOR_HPP
#pragma once
#include <imgui.h>
#include <flecs.h>
#include <typeindex>
#include <unordered_map>
#include <functional>
#include <memory>
#include <string>
class IComponentEditor {
public:
virtual ~IComponentEditor() = default;
virtual bool render(flecs::entity entity, void* component) = 0;
virtual std::type_index getType() const = 0;
};
template<typename T>
class ComponentEditor : public IComponentEditor {
public:
bool render(flecs::entity entity, void* component) override {
return renderComponent(entity, *static_cast<T*>(component));
}
std::type_index getType() const override {
return std::type_index(typeid(T));
}
protected:
virtual bool renderComponent(flecs::entity entity, T& component) = 0;
};
class ComponentEditorRegistry {
public:
template<typename T>
void registerEditor(std::unique_ptr<ComponentEditor<T>> editor) {
m_editors[std::type_index(typeid(T))] = std::move(editor);
}
bool renderComponent(flecs::entity entity, flecs::type_t componentType, void* component) {
// In flecs 4.1.4, we need to get the type ID properly
// For now, we'll use a simpler approach - store editors by component ID
auto it = m_editors_by_id.find(componentType);
if (it != m_editors_by_id.end()) {
return it->second->render(entity, component);
}
return false;
}
// Alternative: Register by type directly
template<typename T>
void registerEditor(std::unique_ptr<ComponentEditor<T>> editor) {
m_editors_by_type[typeid(T)] = std::move(editor);
}
template<typename T>
bool renderComponent(flecs::entity entity, T& component) {
auto it = m_editors_by_type.find(typeid(T));
if (it != m_editors_by_type.end()) {
return it->second->render(entity, &component);
}
return false;
}
private:
std::unordered_map<std::type_index, std::unique_ptr<IComponentEditor>> m_editors_by_type;
std::unordered_map<flecs::type_t, std::unique_ptr<IComponentEditor>> m_editors_by_id;
};
#endif // COMPONENTEDITOR_HPP

View File

@@ -0,0 +1,196 @@
#ifndef COMPONENTEDITORREGISTRY_HPP
#define COMPONENTEDITORREGISTRY_HPP
#pragma once
#include <imgui.h>
#include <flecs.h>
#include <typeindex>
#include <unordered_map>
#include <functional>
#include <memory>
#include <string>
#include <cstring>
// Forward declarations
class IComponentEditor;
/**
* Base interface for component editors
*/
class IComponentEditor {
public:
virtual ~IComponentEditor() = default;
/**
* Render the editor UI
* @param entity The flecs entity being edited
* @param component Pointer to the component data
* @return true if the component was modified
*/
virtual bool render(flecs::entity entity, void* component) = 0;
/**
* Get the type of component this editor handles
* @return type_index of the component
*/
virtual std::type_index getType() const = 0;
};
/**
* Template base class for component editors
* @tparam T The component type to edit
*/
template<typename T>
class ComponentEditor : public IComponentEditor {
public:
bool render(flecs::entity entity, void* component) override {
return renderComponent(entity, *static_cast<T*>(component));
}
std::type_index getType() const override {
return std::type_index(typeid(T));
}
protected:
/**
* Implement this method to render the editor for your component
* @param entity The flecs entity being edited
* @param component Reference to the component data
* @return true if the component was modified
*/
virtual bool renderComponent(flecs::entity entity, T& component) = 0;
};
/**
* Registry for component editors
* Manages the registration and rendering of editors for different component types
*/
class ComponentEditorRegistry {
public:
ComponentEditorRegistry() = default;
~ComponentEditorRegistry() = default;
// Delete copy constructor and assignment
ComponentEditorRegistry(const ComponentEditorRegistry&) = delete;
ComponentEditorRegistry& operator=(const ComponentEditorRegistry&) = delete;
// Allow move operations
ComponentEditorRegistry(ComponentEditorRegistry&&) = default;
ComponentEditorRegistry& operator=(ComponentEditorRegistry&&) = default;
/**
* Register an editor for a specific component type using type_index
* @param typeIndex The type_index of the component
* @param editor The editor instance
*/
void registerEditor(std::type_index typeIndex, std::unique_ptr<IComponentEditor> editor) {
m_editors_by_type[typeIndex] = std::move(editor);
}
/**
* Register an editor for a component type using template
* @tparam T The component type
* @param editor The editor instance
*/
template<typename T>
void registerEditor(std::unique_ptr<ComponentEditor<T>> editor) {
m_editors_by_type[std::type_index(typeid(T))] = std::move(editor);
}
/**
* Render a component editor for the given component
* @tparam T The component type
* @param entity The flecs entity
* @param component Reference to the component data
* @return true if the component was modified
*/
template<typename T>
bool renderComponent(flecs::entity entity, T& component) {
auto it = m_editors_by_type.find(std::type_index(typeid(T)));
if (it != m_editors_by_type.end()) {
return it->second->render(entity, &component);
}
// If no specific editor, show a default editor
return renderDefaultEditor(entity, component);
}
/**
* Check if a component type has a registered editor
* @tparam T The component type
* @return true if an editor is registered
*/
template<typename T>
bool hasEditor() const {
return m_editors_by_type.find(std::type_index(typeid(T))) != m_editors_by_type.end();
}
private:
/**
* Default editor for components without custom editors
* Displays raw component data as text
*/
template<typename T>
bool renderDefaultEditor(flecs::entity entity, T& component) {
if (ImGui::CollapsingHeader("Component (No Editor)", ImGuiTreeNodeFlags_DefaultOpen)) {
ImGui::Indent();
// Get component name from type info
const char* typeName = typeid(T).name();
// Simple demangling for common compilers
#ifdef _MSC_VER
// MSVC: Remove "struct " or "class " prefix
std::string name = typeName;
if (name.find("struct ") == 0) name = name.substr(7);
if (name.find("class ") == 0) name = name.substr(6);
ImGui::Text("Type: %s", name.c_str());
#else
ImGui::Text("Type: %s", typeName);
#endif
ImGui::Text("Size: %zu bytes", sizeof(T));
ImGui::Text("Address: %p", &component);
// Show raw bytes option
if (ImGui::TreeNode("Show Raw Data")) {
unsigned char* bytes = reinterpret_cast<unsigned char*>(&component);
for (size_t i = 0; i < sizeof(T); ++i) {
ImGui::Text("%02X ", bytes[i]);
if ((i + 1) % 16 == 0 && i + 1 < sizeof(T)) {
ImGui::NewLine();
} else if ((i + 1) % 8 == 0) {
ImGui::SameLine();
ImGui::Text(" ");
} else {
ImGui::SameLine();
}
}
ImGui::TreePop();
}
ImGui::Unindent();
}
return false;
}
// Store editors by C++ type
std::unordered_map<std::type_index, std::unique_ptr<IComponentEditor>> m_editors_by_type;
};
/**
* Helper macro to create a simple component editor
* Use this for quick creation of editors for simple components
*/
#define CREATE_SIMPLE_EDITOR(ComponentType, ...) \
class SimpleEditor_##ComponentType : public ComponentEditor<ComponentType> { \
protected: \
bool renderComponent(flecs::entity entity, ComponentType& comp) override { \
bool modified = false; \
__VA_ARGS__ \
return modified; \
} \
}; \
auto register_##ComponentType = []() { \
return std::make_unique<SimpleEditor_##ComponentType>(); \
}
#endif // COMPONENTEDITORREGISTRY_HPP

View File

@@ -0,0 +1,4 @@
#ifndef PROPERTYEDITOR_HPP
#define PROPERTYEDITOR_HPP
#endif // PROPERTYEDITOR_HPP

View File

@@ -0,0 +1,81 @@
#ifndef RENDERABLEEDITOR_HPP
#define RENDERABLEEDITOR_HPP
#pragma once
#include "ComponentEditorRegistry.hpp"
#include "../components/Transform.hpp"
#include <imgui.h>
#include <Ogre.h>
#include <string>
#include <memory>
class RenderableEditor : public ComponentEditor<RenderableComponent> {
public:
RenderableEditor(Ogre::SceneManager* sceneMgr) : m_sceneMgr(sceneMgr) {}
protected:
bool renderComponent(flecs::entity entity, RenderableComponent& renderable) override {
bool modified = false;
if (ImGui::CollapsingHeader("Renderable", ImGuiTreeNodeFlags_DefaultOpen)) {
ImGui::Indent();
// Mesh selection
static char meshName[128] = "";
ImGui::InputText("Mesh Name", meshName, sizeof(meshName));
if (ImGui::Button("Load Mesh")) {
if (strlen(meshName) > 0) {
if (renderable.entity) {
m_sceneMgr->destroyEntity(renderable.entity);
}
renderable.meshName = meshName;
try {
renderable.entity = m_sceneMgr->createEntity(meshName);
// Attach to scene node if transform exists
if (entity.has<TransformComponent>()) {
auto& transform = entity.get_mut<TransformComponent>();
if (transform.node) {
transform.node->attachObject(renderable.entity);
}
}
modified = true;
} catch (const std::exception& e) {
Ogre::LogManager::getSingleton().logMessage(
"Failed to load mesh: " + std::string(e.what())
);
}
}
}
// Show current mesh info
if (!renderable.meshName.empty()) {
ImGui::Text("Current Mesh: %s", renderable.meshName.c_str());
}
// Detach button
if (renderable.entity && ImGui::Button("Detach Mesh")) {
if (entity.has<TransformComponent>()) {
auto& transform = entity.get_mut<TransformComponent>();
if (transform.node) {
transform.node->detachObject(renderable.entity);
}
}
m_sceneMgr->destroyEntity(renderable.entity);
renderable.entity = nullptr;
renderable.meshName.clear();
modified = true;
}
ImGui::Unindent();
}
return modified;
}
private:
Ogre::SceneManager* m_sceneMgr;
};
#endif // RENDERABLEEDITOR_HPP

View File

@@ -0,0 +1,86 @@
#ifndef TRANSFORMEDITOR_HPP
#define TRANSFORMEDITOR_HPP
#pragma once
#include "ComponentEditorRegistry.hpp"
#include "../components/Transform.hpp"
#include "../components/EulerUtils.hpp"
#include <imgui.h>
#include <Ogre.h>
#include <functional>
#include <memory>
class TransformEditor : public ComponentEditor<TransformComponent> {
public:
TransformEditor(Ogre::Vector3& cursorPos, std::function<void()> onCursorSet)
: m_cursorPosition(cursorPos), m_onCursorSet(onCursorSet) {}
protected:
bool renderComponent(flecs::entity entity, TransformComponent& transform) override {
bool modified = false;
if (ImGui::CollapsingHeader("Transform", ImGuiTreeNodeFlags_DefaultOpen)) {
ImGui::Indent();
// Position editing
float pos[3] = {transform.position.x, transform.position.y, transform.position.z};
if (ImGui::DragFloat3("Position", pos, 0.1f)) {
transform.position = Ogre::Vector3(pos[0], pos[1], pos[2]);
transform.applyToNode();
modified = true;
}
// Rotation editing using Euler angles
Ogre::Vector3 euler = EulerUtils::toEulerDegrees(transform.orientation);
float rot[3] = {euler.x, euler.y, euler.z};
if (ImGui::DragFloat3("Rotation (deg)", rot, 1.0f, -180.0f, 180.0f)) {
transform.orientation = EulerUtils::fromEulerDegrees(Ogre::Vector3(rot[0], rot[1], rot[2]));
transform.applyToNode();
modified = true;
}
// Scale editing
float scale[3] = {transform.scale.x, transform.scale.y, transform.scale.z};
if (ImGui::DragFloat3("Scale", scale, 0.1f, 0.01f, 100.0f)) {
transform.scale = Ogre::Vector3(scale[0], scale[1], scale[2]);
transform.applyToNode();
modified = true;
}
ImGui::Separator();
// Reset button
if (ImGui::Button("Reset Transform")) {
transform.position = Ogre::Vector3::ZERO;
transform.orientation = Ogre::Quaternion::IDENTITY;
transform.scale = Ogre::Vector3::UNIT_SCALE;
transform.applyToNode();
modified = true;
}
ImGui::SameLine();
// 3D Cursor controls
if (ImGui::Button("Set Cursor from Position")) {
m_cursorPosition = transform.position;
if (m_onCursorSet) m_onCursorSet();
}
ImGui::SameLine();
if (ImGui::Button("Set Position from Cursor")) {
transform.position = m_cursorPosition;
transform.applyToNode();
modified = true;
}
ImGui::Unindent();
}
return modified;
}
private:
Ogre::Vector3& m_cursorPosition;
std::function<void()> m_onCursorSet;
};
#endif // TRANSFORMEDITOR_HPP