scene save implemented

This commit is contained in:
2026-03-31 12:57:59 +03:00
parent db15c6a48a
commit 3ebb41647e
8 changed files with 471 additions and 32 deletions

View File

@@ -3,6 +3,7 @@ set(CMAKE_CXX_STANDARD 17)
find_package(OGRE REQUIRED COMPONENTS Bites Overlay CONFIG)
find_package(flecs REQUIRED CONFIG)
find_package(nlohmann_json REQUIRED)
find_package(SDL2 REQUIRED)
# Collect all source files
@@ -10,6 +11,7 @@ set(EDITSCENE_SOURCES
main.cpp
EditorApp.cpp
systems/EditorUISystem.cpp
systems/SceneSerializer.cpp
ui/TransformEditor.cpp
ui/RenderableEditor.cpp
camera/EditorCamera.cpp
@@ -23,6 +25,7 @@ set(EDITSCENE_HEADERS
components/EntityName.hpp
components/Relationship.hpp
systems/EditorUISystem.hpp
systems/SceneSerializer.hpp
ui/ComponentEditor.hpp
ui/ComponentRegistry.hpp
ui/TransformEditor.hpp
@@ -38,6 +41,7 @@ target_link_libraries(editSceneEditor
OgreBites
OgreOverlay
flecs::flecs_static
nlohmann_json::nlohmann_json
)
target_include_directories(editSceneEditor PRIVATE

View File

@@ -64,7 +64,7 @@ void EditorApp::setup()
// Base setup
OgreBites::ApplicationContext::setup();
// Get root and create scene manager
// Create scene manager
Ogre::Root *root = getRoot();
m_sceneMgr = root->createSceneManager();
m_sceneMgr->setAmbientLight(Ogre::ColourValue(0.3f, 0.3f, 0.3f));
@@ -129,22 +129,7 @@ void EditorApp::setupECS()
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);
// Start with empty scene - user creates entities as needed
}
void EditorApp::setupLights()

View File

@@ -79,8 +79,9 @@ void Gizmo::update()
auto &transform = m_attachedEntity.get<TransformComponent>();
if (transform.node) {
m_gizmoNode->setPosition(transform.position);
m_gizmoNode->setOrientation(transform.rotation);
// Use derived (world) position and orientation
m_gizmoNode->setPosition(transform.node->_getDerivedPosition());
m_gizmoNode->setOrientation(transform.node->_getDerivedOrientation());
m_gizmoNode->setVisible(true);
}
}
@@ -192,8 +193,11 @@ bool Gizmo::onMousePressed(const Ogre::Ray &mouseRay)
m_isDragging = true;
auto &transform = m_attachedEntity.get_mut<TransformComponent>();
m_dragStartPosition = transform.position;
// Use derived (world) position for dragging
m_dragStartPosition = transform.node->_getDerivedPosition();
// Get axis direction in world space from gizmo orientation
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;
@@ -224,10 +228,20 @@ bool Gizmo::onMouseMoved(const Ogre::Ray &mouseRay, const Ogre::Vector2 &mouseDe
float deltaT = currentT - m_dragStartT;
transform.position = m_dragStartPosition + m_dragAxisDir * deltaT;
// Calculate new world position
Ogre::Vector3 newWorldPos = m_dragStartPosition + m_dragAxisDir * deltaT;
// Convert to local position if node has a parent
if (transform.node->getParent()) {
transform.position = transform.node->getParent()->convertWorldToLocalPosition(newWorldPos);
} else {
transform.position = newWorldPos;
}
transform.applyToNode();
m_gizmoNode->setPosition(transform.position);
// Update gizmo to follow (use derived position)
m_gizmoNode->setPosition(transform.node->_getDerivedPosition());
return true;
} else if (m_axisX->isVisible()) {

View File

@@ -3,6 +3,7 @@
[General]
FileSystem=resources
FileSystem=resources/materials
FileSystem=resources/materials/scripts
FileSystem=resources/meshes
FileSystem=resources/textures
FileSystem=resources/buildings
@@ -10,7 +11,6 @@ FileSystem=resources/vehicles
[Popular]
FileSystem=resources/materials/programs
FileSystem=resources/materials/scripts
FileSystem=resources/materials/textures
[Essential]

View File

@@ -17,6 +17,7 @@ EditorUISystem::EditorUISystem(flecs::world &world,
{
registerComponentEditors();
m_gizmo = std::make_unique<Gizmo>(m_sceneMgr);
m_serializer = std::make_unique<SceneSerializer>(m_world, m_sceneMgr);
}
EditorUISystem::~EditorUISystem() = default;
@@ -146,6 +147,18 @@ void EditorUISystem::renderHierarchyWindow()
if (ImGui::Begin("Entity Hierarchy", nullptr, windowFlags)) {
// Menu bar
if (ImGui::BeginMenuBar()) {
// File menu
if (ImGui::BeginMenu("File")) {
if (ImGui::MenuItem("Save Scene", "Ctrl+S")) {
saveScene("scene.json");
}
if (ImGui::MenuItem("Load Scene", "Ctrl+O")) {
loadScene("scene.json");
}
ImGui::EndMenu();
}
// Entity menu
if (ImGui::BeginMenu("Entity")) {
if (ImGui::MenuItem("New Entity", "Ctrl+N")) {
createNewEntity();
@@ -170,6 +183,17 @@ void EditorUISystem::renderHierarchyWindow()
}
ImGui::EndMenu();
}
// Settings menu
if (ImGui::BeginMenu("Settings")) {
if (ImGui::Checkbox("Parent SceneNodes", &m_parentSceneNodes)) {
// Toggle applied immediately to new entities
}
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("When enabled, child entities' SceneNodes are parented to their parent's SceneNode, inheriting transforms. When disabled, SceneNodes are created at root level.");
}
ImGui::EndMenu();
}
ImGui::EndMenuBar();
}
@@ -484,11 +508,21 @@ void EditorUISystem::createChildEntity(flecs::entity parent)
entity.set<EntityNameComponent>(EntityNameComponent("Child Entity"));
entity.add<EditorMarkerComponent>();
// Create transform with parent as scene node parent
// Create transform
TransformComponent transform;
auto &parentTransform = parent.get_mut<TransformComponent>();
transform.node = parentTransform.node->createChildSceneNode();
transform.position = Ogre::Vector3::ZERO;
// SceneNode parenting depends on setting
if (m_parentSceneNodes) {
// Child SceneNode inherits parent's transform
transform.node = parentTransform.node->createChildSceneNode();
transform.position = Ogre::Vector3::ZERO;
} else {
// Child SceneNode at root level, position relative to parent
transform.node = m_sceneMgr->getRootSceneNode()->createChildSceneNode();
transform.position = parentTransform.position;
}
transform.rotation = Ogre::Quaternion::IDENTITY;
transform.scale = Ogre::Vector3::UNIT_SCALE;
entity.set<TransformComponent>(transform);
@@ -561,18 +595,26 @@ void EditorUISystem::duplicateEntity(flecs::entity entity)
auto &oldTransform = entity.get<TransformComponent>();
TransformComponent newTransform;
// Find parent node
Ogre::SceneNode *parentNode = m_sceneMgr->getRootSceneNode();
// Find parent entity and node
flecs::entity parent = entity.parent();
Ogre::SceneNode *parentNode = m_sceneMgr->getRootSceneNode();
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
// SceneNode parenting depends on setting
if (m_parentSceneNodes && parent.is_valid() && parent != 0) {
// Create as child of parent's SceneNode
newTransform.node = parentNode->createChildSceneNode();
newTransform.position = oldTransform.position + Ogre::Vector3(1, 0, 0);
} else {
// Create at root level
newTransform.node = m_sceneMgr->getRootSceneNode()->createChildSceneNode();
newTransform.position = oldTransform.position + Ogre::Vector3(1, 0, 0);
}
newTransform.rotation = oldTransform.rotation;
newTransform.scale = oldTransform.scale;
newTransform.applyToNode();
@@ -635,3 +677,29 @@ bool EditorUISystem::isDescendantOf(flecs::entity potentialChild,
}
return false;
}
void EditorUISystem::saveScene(const std::string& filepath)
{
if (!m_serializer) return;
if (m_serializer->saveToFile(filepath)) {
Ogre::LogManager::getSingleton().logMessage(
"Scene saved to: " + filepath);
} else {
Ogre::LogManager::getSingleton().logMessage(
"Failed to save scene: " + m_serializer->getLastError());
}
}
void EditorUISystem::loadScene(const std::string& filepath)
{
if (!m_serializer) return;
if (m_serializer->loadFromFile(filepath, this)) {
Ogre::LogManager::getSingleton().logMessage(
"Scene loaded from: " + filepath);
} else {
Ogre::LogManager::getSingleton().logMessage(
"Failed to load scene: " + m_serializer->getLastError());
}
}

View File

@@ -4,9 +4,11 @@
#include <flecs.h>
#include <Ogre.h>
#include <memory>
#include <string>
#include "../ui/ComponentRegistry.hpp"
#include "../components/EntityName.hpp"
#include "../gizmo/Gizmo.hpp"
#include "SceneSerializer.hpp"
/**
* Main UI system for the scene editor
@@ -57,7 +59,25 @@ public:
*/
Gizmo *getGizmo() const { return m_gizmo.get(); }
/**
* Get/set SceneNode parenting mode
*/
bool getParentSceneNodes() const { return m_parentSceneNodes; }
void setParentSceneNodes(bool value) { m_parentSceneNodes = value; }
/**
* Save scene to file
*/
void saveScene(const std::string& filepath);
/**
* Load scene from file
*/
void loadScene(const std::string& filepath);
private:
// File menu
void renderFileMenu();
// Window rendering functions
void renderHierarchyWindow();
void renderPropertyWindow();
@@ -91,6 +111,10 @@ private:
ComponentRegistry m_componentRegistry;
std::vector<flecs::entity> m_allEntities;
std::unique_ptr<Gizmo> m_gizmo;
std::unique_ptr<SceneSerializer> m_serializer;
// Settings
bool m_parentSceneNodes = true; // Whether child entities inherit parent's SceneNode
// Queries
flecs::query<EntityNameComponent> m_nameQuery;

View File

@@ -0,0 +1,285 @@
#include "SceneSerializer.hpp"
#include "../components/Transform.hpp"
#include "../components/Renderable.hpp"
#include "../components/EntityName.hpp"
#include "../components/EditorMarker.hpp"
#include "EditorUISystem.hpp"
#include <fstream>
#include <iostream>
SceneSerializer::SceneSerializer(flecs::world& world, Ogre::SceneManager* sceneMgr)
: m_world(world)
, m_sceneMgr(sceneMgr)
{
}
bool SceneSerializer::saveToFile(const std::string& filepath)
{
try {
nlohmann::json scene;
scene["version"] = "1.0";
scene["entities"] = nlohmann::json::array();
// Collect all entities with EditorMarkerComponent
m_world.query_builder<>()
.with<EditorMarkerComponent>()
.build()
.each([&](flecs::entity entity) {
// Only save root entities (children will be saved recursively)
flecs::entity parent = entity.parent();
if (!parent.is_valid() || parent == 0) {
scene["entities"].push_back(serializeEntity(entity));
}
});
// Write to file
std::ofstream file(filepath);
if (!file.is_open()) {
m_lastError = "Failed to open file for writing: " + filepath;
return false;
}
file << scene.dump(4); // Pretty print with 4-space indent
file.close();
return true;
} catch (const std::exception& e) {
m_lastError = std::string("Save error: ") + e.what();
return false;
}
}
bool SceneSerializer::loadFromFile(const std::string& filepath, EditorUISystem* uiSystem)
{
try {
// Read from file
std::ifstream file(filepath);
if (!file.is_open()) {
m_lastError = "Failed to open file for reading: " + filepath;
return false;
}
nlohmann::json scene;
file >> scene;
file.close();
// Clear existing entities (optional - could be made configurable)
// For now, we'll just add to the existing scene
// Validate version
if (scene.contains("version")) {
std::string version = scene["version"];
if (version != "1.0") {
m_lastError = "Unsupported scene version: " + version;
return false;
}
}
// Clear entity map for new load
m_entityMap.clear();
// Load entities
if (scene.contains("entities") && scene["entities"].is_array()) {
for (const auto& entityJson : scene["entities"]) {
deserializeEntity(entityJson, flecs::entity::null(), uiSystem);
}
}
m_entityMap.clear();
return true;
} catch (const std::exception& e) {
m_lastError = std::string("Load error: ") + e.what();
return false;
}
}
nlohmann::json SceneSerializer::serializeEntity(flecs::entity entity)
{
nlohmann::json json;
// Store entity ID for parent/child relationships
json["id"] = entity.id();
// Serialize components
if (entity.has<EntityNameComponent>()) {
json["name"] = serializeEntityName(entity);
}
if (entity.has<TransformComponent>()) {
json["transform"] = serializeTransform(entity);
}
if (entity.has<RenderableComponent>()) {
json["renderable"] = serializeRenderable(entity);
}
// Serialize children
json["children"] = nlohmann::json::array();
entity.children([&](flecs::entity child) {
if (child.has<EditorMarkerComponent>()) {
json["children"].push_back(serializeEntity(child));
}
});
return json;
}
void SceneSerializer::deserializeEntity(const nlohmann::json& json, flecs::entity parent, EditorUISystem* uiSystem)
{
// Create new entity
flecs::entity entity = m_world.entity();
entity.add<EditorMarkerComponent>();
// Store in map for potential future reference
if (json.contains("id")) {
uint64_t id = json["id"];
m_entityMap[id] = entity;
}
// Set parent relationship
if (parent.is_valid() && parent != 0) {
entity.child_of(parent);
}
// Deserialize components
if (json.contains("name")) {
deserializeEntityName(entity, json["name"]);
} else {
entity.set<EntityNameComponent>(EntityNameComponent("Entity"));
}
if (json.contains("transform")) {
deserializeTransform(entity, json["transform"], parent);
}
if (json.contains("renderable")) {
deserializeRenderable(entity, json["renderable"]);
}
// Add to UI system if provided
if (uiSystem) {
uiSystem->addEntity(entity);
}
// Deserialize children
if (json.contains("children") && json["children"].is_array()) {
for (const auto& childJson : json["children"]) {
deserializeEntity(childJson, entity, uiSystem);
}
}
}
nlohmann::json SceneSerializer::serializeTransform(flecs::entity entity)
{
auto& transform = entity.get<TransformComponent>();
nlohmann::json json;
json["position"] = {
{"x", transform.position.x},
{"y", transform.position.y},
{"z", transform.position.z}
};
json["rotation"] = {
{"w", transform.rotation.w},
{"x", transform.rotation.x},
{"y", transform.rotation.y},
{"z", transform.rotation.z}
};
json["scale"] = {
{"x", transform.scale.x},
{"y", transform.scale.y},
{"z", transform.scale.z}
};
return json;
}
nlohmann::json SceneSerializer::serializeRenderable(flecs::entity entity)
{
auto& renderable = entity.get<RenderableComponent>();
nlohmann::json json;
json["meshName"] = renderable.meshName;
json["visible"] = renderable.visible;
return json;
}
nlohmann::json SceneSerializer::serializeEntityName(flecs::entity entity)
{
auto& name = entity.get<EntityNameComponent>();
nlohmann::json json;
json["name"] = name.name;
return json;
}
void SceneSerializer::deserializeTransform(flecs::entity entity, const nlohmann::json& json, flecs::entity parentEntity)
{
TransformComponent transform;
// Read position
if (json.contains("position")) {
auto& pos = json["position"];
transform.position = Ogre::Vector3(
pos.value("x", 0.0f),
pos.value("y", 0.0f),
pos.value("z", 0.0f)
);
}
// Read rotation
if (json.contains("rotation")) {
auto& rot = json["rotation"];
transform.rotation = Ogre::Quaternion(
rot.value("w", 1.0f),
rot.value("x", 0.0f),
rot.value("y", 0.0f),
rot.value("z", 0.0f)
);
}
// Read scale
if (json.contains("scale")) {
auto& scl = json["scale"];
transform.scale = Ogre::Vector3(
scl.value("x", 1.0f),
scl.value("y", 1.0f),
scl.value("z", 1.0f)
);
}
// Create scene node
if (parentEntity.is_valid() && parentEntity != 0 && parentEntity.has<TransformComponent>()) {
// Child of parent entity's node
auto& parentTransform = parentEntity.get<TransformComponent>();
if (parentTransform.node) {
transform.node = parentTransform.node->createChildSceneNode();
} else {
transform.node = m_sceneMgr->getRootSceneNode()->createChildSceneNode();
}
} else {
// Root level
transform.node = m_sceneMgr->getRootSceneNode()->createChildSceneNode();
}
transform.applyToNode();
entity.set<TransformComponent>(transform);
}
void SceneSerializer::deserializeRenderable(flecs::entity entity, const nlohmann::json& json)
{
RenderableComponent renderable;
renderable.meshName = json.value("meshName", "");
renderable.visible = json.value("visible", true);
// Don't create the Ogre::Entity here - it will be created when mesh is loaded
renderable.entity = nullptr;
entity.set<RenderableComponent>(renderable);
}
void SceneSerializer::deserializeEntityName(flecs::entity entity, const nlohmann::json& json)
{
std::string name = json.value("name", "Entity");
entity.set<EntityNameComponent>(EntityNameComponent(name));
}

View File

@@ -0,0 +1,59 @@
#ifndef EDITSCENE_SCENESERIALIZER_HPP
#define EDITSCENE_SCENESERIALIZER_HPP
#pragma once
#include <flecs.h>
#include <Ogre.h>
#include <nlohmann/json.hpp>
#include <string>
#include <vector>
// Forward declarations
class EditorUISystem;
/**
* Scene serializer for saving/loading scenes in JSON format
*/
class SceneSerializer {
public:
SceneSerializer(flecs::world& world, Ogre::SceneManager* sceneMgr);
/**
* Save scene to JSON file
*/
bool saveToFile(const std::string& filepath);
/**
* Load scene from JSON file
*/
bool loadFromFile(const std::string& filepath, EditorUISystem* uiSystem = nullptr);
/**
* Get last error message
*/
const std::string& getLastError() const { return m_lastError; }
private:
// Serialization helpers
nlohmann::json serializeEntity(flecs::entity entity);
void deserializeEntity(const nlohmann::json& json, flecs::entity parent, EditorUISystem* uiSystem);
// Component serialization
nlohmann::json serializeTransform(flecs::entity entity);
nlohmann::json serializeRenderable(flecs::entity entity);
nlohmann::json serializeEntityName(flecs::entity entity);
// Component deserialization
void deserializeTransform(flecs::entity entity, const nlohmann::json& json, flecs::entity parentEntity);
void deserializeRenderable(flecs::entity entity, const nlohmann::json& json);
void deserializeEntityName(flecs::entity entity, const nlohmann::json& json);
flecs::world& m_world;
Ogre::SceneManager* m_sceneMgr;
std::string m_lastError;
// Track entity ID mapping for parent/child relationships
std::unordered_map<uint64_t, flecs::entity> m_entityMap;
};
#endif // EDITSCENE_SCENESERIALIZER_HPP