diff --git a/src/features/editScene/EditorApp.cpp b/src/features/editScene/EditorApp.cpp index a5658e5..8817306 100644 --- a/src/features/editScene/EditorApp.cpp +++ b/src/features/editScene/EditorApp.cpp @@ -80,7 +80,8 @@ void ImGuiRenderListener::preViewportUpdate( } // Render startup menu in game mode (inside ImGui frame scope) - if (m_editorApp && m_editorApp->getGameMode() == EditorApp::GameMode::Game && + if (m_editorApp && + m_editorApp->getGameMode() == EditorApp::GameMode::Game && m_editorApp->getGamePlayState() == EditorApp::GamePlayState::Menu) { StartupMenuSystem *sms = m_editorApp->getStartupMenuSystem(); if (sms) @@ -209,7 +210,8 @@ void EditorApp::setup() m_uiSystem = std::make_unique( m_world, m_sceneMgr, getRenderWindow()); if (m_uiSystem) - m_uiSystem->setEditorUIEnabled(m_gameMode == GameMode::Editor); + m_uiSystem->setEditorUIEnabled(m_gameMode == + GameMode::Editor); // Setup physics system m_physicsSystem = std::make_unique( @@ -238,13 +240,13 @@ void EditorApp::setup() // Setup ProceduralTexture system m_proceduralTextureSystem = std::make_unique(m_world, - m_sceneMgr); + m_sceneMgr); m_proceduralTextureSystem->initialize(); // Setup ProceduralMaterial system m_proceduralMaterialSystem = std::make_unique(m_world, - m_sceneMgr); + m_sceneMgr); m_proceduralMaterialSystem->initialize(); // Setup ProceduralMesh system @@ -263,8 +265,8 @@ void EditorApp::setup() m_animationTreeSystem->initialize(); // Setup Character physics system - m_characterSystem = std::make_unique( - m_world, m_sceneMgr); + m_characterSystem = + std::make_unique(m_world, m_sceneMgr); m_characterSystem->initialize(); // Setup CellGrid system @@ -278,9 +280,8 @@ void EditorApp::setup() m_roomLayoutSystem->initialize(); // Setup game systems - m_startupMenuSystem = - std::make_unique(m_world, m_sceneMgr, - this); + m_startupMenuSystem = std::make_unique( + m_world, m_sceneMgr, this); m_playerControllerSystem = std::make_unique( m_world, m_sceneMgr, this); @@ -317,7 +318,8 @@ void EditorApp::setup() // Create and register ImGui render listener m_imguiListener = std::make_unique( - m_imguiOverlay, m_uiSystem.get(), getRenderWindow(), this); + m_imguiOverlay, m_uiSystem.get(), getRenderWindow(), + this); getRenderWindow()->addListener(m_imguiListener.get()); // Register input listeners @@ -509,10 +511,13 @@ void EditorApp::createGrid() grid->end(); - Ogre::SceneNode *gridNode = + m_gridNode = m_sceneMgr->getRootSceneNode()->createChildSceneNode( "GridNode"); - gridNode->attachObject(grid); + m_gridNode->attachObject(grid); + + // Set initial visibility based on game mode + m_gridNode->setVisible(m_gameMode == GameMode::Editor); } catch (const std::exception &e) { Ogre::LogManager::getSingleton().logMessage( "Grid creation failed: " + Ogre::String(e.what())); @@ -553,12 +558,15 @@ void EditorApp::createAxes() axisZ->end(); // Create axis node - Ogre::SceneNode *axisNode = + m_axisNode = m_sceneMgr->getRootSceneNode()->createChildSceneNode( "AxisNode"); - axisNode->attachObject(axisX); - axisNode->attachObject(axisY); - axisNode->attachObject(axisZ); + m_axisNode->attachObject(axisX); + m_axisNode->attachObject(axisY); + m_axisNode->attachObject(axisZ); + + // Set initial visibility based on game mode + m_axisNode->setVisible(m_gameMode == GameMode::Editor); } catch (const std::exception &e) { Ogre::LogManager::getSingleton().logMessage( "Axis creation failed: " + Ogre::String(e.what())); diff --git a/src/features/editScene/EditorApp.hpp b/src/features/editScene/EditorApp.hpp index 0858e6a..4a76c74 100644 --- a/src/features/editScene/EditorApp.hpp +++ b/src/features/editScene/EditorApp.hpp @@ -123,14 +123,23 @@ public: // Game mode management void setGameMode(GameMode mode); - GameMode getGameMode() const { return m_gameMode; } - GamePlayState getGamePlayState() const { return m_gamePlayState; } + GameMode getGameMode() const + { + return m_gameMode; + } + GamePlayState getGamePlayState() const + { + return m_gamePlayState; + } void setGamePlayState(GamePlayState state); void startNewGame(const Ogre::String &scenePath); void clearScene(); // Input access - GameInputState &getGameInputState() { return m_gameInput; } + GameInputState &getGameInputState() + { + return m_gameInput; + } // Getters flecs::entity getSelectedEntity() const; @@ -142,7 +151,10 @@ public: { return &m_world; } - EditorCamera *getEditorCamera() const { return m_camera.get(); } + EditorCamera *getEditorCamera() const + { + return m_camera.get(); + } AnimationTreeSystem *getAnimationTreeSystem() const { return m_animationTreeSystem.get(); @@ -155,7 +167,10 @@ public: { return m_startupMenuSystem.get(); } - Ogre::ImGuiOverlay *getImGuiOverlay() const { return m_imguiOverlay; } + Ogre::ImGuiOverlay *getImGuiOverlay() const + { + return m_imguiOverlay; + } private: // Ogre objects @@ -195,6 +210,10 @@ private: GamePlayState m_gamePlayState = GamePlayState::Menu; GameInputState m_gameInput; bool m_setupComplete = false; + + // Editor visualization nodes + Ogre::SceneNode *m_gridNode = nullptr; + Ogre::SceneNode *m_axisNode = nullptr; }; #endif // EDITSCENE_EDITORAPP_HPP diff --git a/src/features/editScene/components/ProceduralMaterial.hpp b/src/features/editScene/components/ProceduralMaterial.hpp index 490cae5..d0563e5 100644 --- a/src/features/editScene/components/ProceduralMaterial.hpp +++ b/src/features/editScene/components/ProceduralMaterial.hpp @@ -3,6 +3,7 @@ #include #include #include +#include "ProceduralTexture.hpp" /** * @brief Component for creating procedural Ogre materials @@ -11,32 +12,47 @@ * The material can be referenced by name for use with meshes/entities. */ struct ProceduralMaterialComponent { - // Material name for Ogre resource - std::string materialName; - - // Reference to entity with ProceduralTextureComponent (for diffuse) - flecs::entity diffuseTextureEntity; - - // Whether the material needs regeneration - bool dirty = true; - - // Whether the material has been created - bool created = false; - - // Pointer to the created Ogre material - Ogre::MaterialPtr ogreMaterial; - - // Track texture version for automatic rebuild when texture changes - unsigned int textureVersion = 0; - - // Material properties - float ambient[3] = {0.2f, 0.2f, 0.2f}; // Ambient color (RGB 0-1) - float diffuse[3] = {1.0f, 1.0f, 1.0f}; // Diffuse color multiplier (RGB 0-1) - float specular[3] = {0.0f, 0.0f, 0.0f}; // Specular color (RGB 0-1) - float shininess = 32.0f; // Specular shininess - float roughness = 0.5f; // Roughness (0-1, for PBR-style) - - void markDirty() { - dirty = true; - } + // Unique identifier for persistent referencing across scene loads + std::string materialId; + + // Material name for Ogre resource + std::string materialName; + + // Persistent reference to texture by ID + std::string diffuseTextureId; + + // Runtime reference to entity with ProceduralTextureComponent (for diffuse) + flecs::entity diffuseTextureEntity = flecs::entity::null(); + + // Whether the material needs regeneration + bool dirty = true; + + // Whether the material has been created + bool created = false; + + // Pointer to the created Ogre material + Ogre::MaterialPtr ogreMaterial; + + // Track texture version for automatic rebuild when texture changes + unsigned int textureVersion = 0; + + // Material properties + float ambient[3] = { 0.2f, 0.2f, 0.2f }; // Ambient color (RGB 0-1) + float diffuse[3] = { 1.0f, 1.0f, + 1.0f }; // Diffuse color multiplier (RGB 0-1) + float specular[3] = { 0.0f, 0.0f, 0.0f }; // Specular color (RGB 0-1) + float shininess = 32.0f; // Specular shininess + float roughness = 0.5f; // Roughness (0-1, for PBR-style) + + void markDirty() + { + dirty = true; + } + + // Helper to check if texture reference is valid + bool hasValidTexture() const + { + return diffuseTextureEntity.is_alive() && + diffuseTextureEntity.has(); + } }; diff --git a/src/features/editScene/components/ProceduralTexture.hpp b/src/features/editScene/components/ProceduralTexture.hpp index b64b6fc..25e9a1e 100644 --- a/src/features/editScene/components/ProceduralTexture.hpp +++ b/src/features/editScene/components/ProceduralTexture.hpp @@ -10,13 +10,26 @@ * @brief Information about a named rectangle in the texture atlas */ struct TextureRectInfo { - std::string name; - float u1, v1; // Top-left UV (0-1 range) - float u2, v2; // Bottom-right UV (0-1 range) - - TextureRectInfo() : u1(0), v1(0), u2(1), v2(1) {} - TextureRectInfo(const std::string& n, float left, float top, float right, float bottom) - : name(n), u1(left), v1(top), u2(right), v2(bottom) {} + std::string name; + float u1, v1; // Top-left UV (0-1 range) + float u2, v2; // Bottom-right UV (0-1 range) + + TextureRectInfo() + : u1(0) + , v1(0) + , u2(1) + , v2(1) + { + } + TextureRectInfo(const std::string &n, float left, float top, + float right, float bottom) + : name(n) + , u1(left) + , v1(top) + , u2(right) + , v2(bottom) + { + } }; /** @@ -27,129 +40,144 @@ struct TextureRectInfo { * Also supports named rectangles for texture atlas/UV mapping. */ struct ProceduralTextureComponent { - // Texture name for Ogre resource - std::string textureName; - - // Grid dimensions (default 10x10) - static constexpr int GRID_SIZE = 10; - static constexpr int RECT_COUNT = GRID_SIZE * GRID_SIZE; - - // Colors for each rectangle (stored as float4: r, g, b, a) - std::array colors; - - // Named rectangles for texture atlas (name -> rect info) - std::map namedRects; - - // Texture size (default 512x512) - int textureSize = 512; - - // UV margin for texture mapping (default 0.01, range 0.01-0.025) - // Used to prevent color bleeding by adding padding around UV coordinates - float uvMargin = 0.01f; - - // Whether the texture needs regeneration - bool dirty = true; - - // Whether the texture has been generated - bool generated = false; - - // Version counter - increments each time texture is regenerated - // Used by dependent systems to detect texture changes - unsigned int version = 0; - - // Pointer to the generated Ogre texture - Ogre::TexturePtr ogreTexture; - - ProceduralTextureComponent() { - // Initialize with default colors (checkerboard pattern) - for (int i = 0; i < RECT_COUNT; ++i) { - int row = i / GRID_SIZE; - int col = i % GRID_SIZE; - bool isWhite = (row + col) % 2 == 0; - - colors[i * 4 + 0] = isWhite ? 1.0f : 0.0f; // R - colors[i * 4 + 1] = isWhite ? 1.0f : 0.0f; // G - colors[i * 4 + 2] = isWhite ? 1.0f : 0.0f; // B - colors[i * 4 + 3] = 1.0f; // A - } - } - - // Get color for a specific rectangle - Ogre::ColourValue getColor(int index) const { - if (index < 0 || index >= RECT_COUNT) return Ogre::ColourValue::White; - return Ogre::ColourValue( - colors[index * 4 + 0], - colors[index * 4 + 1], - colors[index * 4 + 2], - colors[index * 4 + 3] - ); - } - - // Set color for a specific rectangle - void setColor(int index, const Ogre::ColourValue& color) { - if (index < 0 || index >= RECT_COUNT) return; - colors[index * 4 + 0] = color.r; - colors[index * 4 + 1] = color.g; - colors[index * 4 + 2] = color.b; - colors[index * 4 + 3] = color.a; - dirty = true; - } - - // Calculate UV coordinates for a rectangle index - void getRectUVs(int index, float& u1, float& v1, float& u2, float& v2) const { - int row = index / GRID_SIZE; - int col = index % GRID_SIZE; - float cellSize = 1.0f / GRID_SIZE; - - u1 = col * cellSize; - v1 = row * cellSize; - u2 = (col + 1) * cellSize; - v2 = (row + 1) * cellSize; - } - - // Add a named rectangle - bool addNamedRect(const std::string& name, int rectIndex) { - if (name.empty() || rectIndex < 0 || rectIndex >= RECT_COUNT) return false; - - float u1, v1, u2, v2; - getRectUVs(rectIndex, u1, v1, u2, v2); - - namedRects[name] = TextureRectInfo(name, u1, v1, u2, v2); - dirty = true; // Mark texture as dirty since rect atlas changed - return true; - } - - // Remove a named rectangle - bool removeNamedRect(const std::string& name) { - auto it = namedRects.find(name); - if (it != namedRects.end()) { - namedRects.erase(it); - dirty = true; // Mark texture as dirty since rect atlas changed - return true; - } - return false; - } - - // Get named rectangle info - const TextureRectInfo* getNamedRect(const std::string& name) const { - auto it = namedRects.find(name); - if (it != namedRects.end()) { - return &it->second; - } - return nullptr; - } - - // Check if a name exists - bool hasNamedRect(const std::string& name) const { - return namedRects.find(name) != namedRects.end(); - } - - // Get all named rectangles - const std::map& getAllNamedRects() const { - return namedRects; - } - - void markDirty() { - dirty = true; - } + // Unique identifier for persistent referencing across scene loads + std::string textureId; + + // Texture name for Ogre resource + std::string textureName; + + // Grid dimensions (default 10x10) + static constexpr int GRID_SIZE = 10; + static constexpr int RECT_COUNT = GRID_SIZE * GRID_SIZE; + + // Colors for each rectangle (stored as float4: r, g, b, a) + std::array colors; + + // Named rectangles for texture atlas (name -> rect info) + std::map namedRects; + + // Texture size (default 512x512) + int textureSize = 512; + + // UV margin for texture mapping (default 0.01, range 0.01-0.025) + // Used to prevent color bleeding by adding padding around UV coordinates + float uvMargin = 0.01f; + + // Whether the texture needs regeneration + bool dirty = true; + + // Whether the texture has been generated + bool generated = false; + + // Version counter - increments each time texture is regenerated + // Used by dependent systems to detect texture changes + unsigned int version = 0; + + // Pointer to the generated Ogre texture + Ogre::TexturePtr ogreTexture; + + ProceduralTextureComponent() + { + // Initialize with default colors (checkerboard pattern) + for (int i = 0; i < RECT_COUNT; ++i) { + int row = i / GRID_SIZE; + int col = i % GRID_SIZE; + bool isWhite = (row + col) % 2 == 0; + + colors[i * 4 + 0] = isWhite ? 1.0f : 0.0f; // R + colors[i * 4 + 1] = isWhite ? 1.0f : 0.0f; // G + colors[i * 4 + 2] = isWhite ? 1.0f : 0.0f; // B + colors[i * 4 + 3] = 1.0f; // A + } + } + + // Get color for a specific rectangle + Ogre::ColourValue getColor(int index) const + { + if (index < 0 || index >= RECT_COUNT) + return Ogre::ColourValue::White; + return Ogre::ColourValue(colors[index * 4 + 0], + colors[index * 4 + 1], + colors[index * 4 + 2], + colors[index * 4 + 3]); + } + + // Set color for a specific rectangle + void setColor(int index, const Ogre::ColourValue &color) + { + if (index < 0 || index >= RECT_COUNT) + return; + colors[index * 4 + 0] = color.r; + colors[index * 4 + 1] = color.g; + colors[index * 4 + 2] = color.b; + colors[index * 4 + 3] = color.a; + dirty = true; + } + + // Calculate UV coordinates for a rectangle index + void getRectUVs(int index, float &u1, float &v1, float &u2, + float &v2) const + { + int row = index / GRID_SIZE; + int col = index % GRID_SIZE; + float cellSize = 1.0f / GRID_SIZE; + + u1 = col * cellSize; + v1 = row * cellSize; + u2 = (col + 1) * cellSize; + v2 = (row + 1) * cellSize; + } + + // Add a named rectangle + bool addNamedRect(const std::string &name, int rectIndex) + { + if (name.empty() || rectIndex < 0 || rectIndex >= RECT_COUNT) + return false; + + float u1, v1, u2, v2; + getRectUVs(rectIndex, u1, v1, u2, v2); + + namedRects[name] = TextureRectInfo(name, u1, v1, u2, v2); + dirty = true; // Mark texture as dirty since rect atlas changed + return true; + } + + // Remove a named rectangle + bool removeNamedRect(const std::string &name) + { + auto it = namedRects.find(name); + if (it != namedRects.end()) { + namedRects.erase(it); + dirty = true; // Mark texture as dirty since rect atlas changed + return true; + } + return false; + } + + // Get named rectangle info + const TextureRectInfo *getNamedRect(const std::string &name) const + { + auto it = namedRects.find(name); + if (it != namedRects.end()) { + return &it->second; + } + return nullptr; + } + + // Check if a name exists + bool hasNamedRect(const std::string &name) const + { + return namedRects.find(name) != namedRects.end(); + } + + // Get all named rectangles + const std::map &getAllNamedRects() const + { + return namedRects; + } + + void markDirty() + { + dirty = true; + } }; diff --git a/src/features/editScene/systems/SceneSerializer.cpp b/src/features/editScene/systems/SceneSerializer.cpp index fc9182b..845c4c7 100644 --- a/src/features/editScene/systems/SceneSerializer.cpp +++ b/src/features/editScene/systems/SceneSerializer.cpp @@ -27,13 +27,14 @@ #include #include -SceneSerializer::SceneSerializer(flecs::world& world, Ogre::SceneManager* sceneMgr) +SceneSerializer::SceneSerializer(flecs::world &world, + Ogre::SceneManager *sceneMgr) : m_world(world) , m_sceneMgr(sceneMgr) { } -bool SceneSerializer::saveToFile(const std::string& filepath) +bool SceneSerializer::saveToFile(const std::string &filepath) { try { nlohmann::json scene; @@ -48,14 +49,16 @@ bool SceneSerializer::saveToFile(const std::string& filepath) // 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)); + 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; + m_lastError = + "Failed to open file for writing: " + filepath; return false; } @@ -63,19 +66,21 @@ bool SceneSerializer::saveToFile(const std::string& filepath) file.close(); return true; - } catch (const std::exception& e) { + } catch (const std::exception &e) { m_lastError = std::string("Save error: ") + e.what(); return false; } } -bool SceneSerializer::loadFromFile(const std::string& filepath, EditorUISystem* uiSystem) +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; + m_lastError = + "Failed to open file for reading: " + filepath; return false; } @@ -90,7 +95,8 @@ bool SceneSerializer::loadFromFile(const std::string& filepath, EditorUISystem* if (scene.contains("version")) { std::string version = scene["version"]; if (version != "1.0") { - m_lastError = "Unsupported scene version: " + version; + m_lastError = + "Unsupported scene version: " + version; return false; } } @@ -99,17 +105,23 @@ bool SceneSerializer::loadFromFile(const std::string& filepath, EditorUISystem* m_entityMap.clear(); // Load entities - if (scene.contains("entities") && scene["entities"].is_array()) { + if (scene.contains("entities") && + scene["entities"].is_array()) { Ogre::LogManager::getSingleton().logMessage( - "SceneSerializer: Loading " + Ogre::StringConverter::toString(scene["entities"].size()) + " entities"); - for (const auto& entityJson : scene["entities"]) { - deserializeEntity(entityJson, flecs::entity::null(), uiSystem); + "SceneSerializer: Loading " + + Ogre::StringConverter::toString( + scene["entities"].size()) + + " entities"); + for (const auto &entityJson : scene["entities"]) { + deserializeEntity(entityJson, + flecs::entity::null(), + uiSystem); } } m_entityMap.clear(); return true; - } catch (const std::exception& e) { + } catch (const std::exception &e) { m_lastError = std::string("Load error: ") + e.what(); return false; } @@ -118,7 +130,7 @@ bool SceneSerializer::loadFromFile(const std::string& filepath, EditorUISystem* nlohmann::json SceneSerializer::serializeEntity(flecs::entity entity) { nlohmann::json json; - + // Store entity ID for parent/child relationships json["id"] = entity.id(); @@ -164,7 +176,8 @@ nlohmann::json SceneSerializer::serializeEntity(flecs::entity entity) } if (entity.has()) { - json["staticGeometryMember"] = serializeStaticGeometryMember(entity); + json["staticGeometryMember"] = + serializeStaticGeometryMember(entity); } if (entity.has()) { @@ -172,7 +185,8 @@ nlohmann::json SceneSerializer::serializeEntity(flecs::entity entity) } if (entity.has()) { - json["proceduralMaterial"] = serializeProceduralMaterial(entity); + json["proceduralMaterial"] = + serializeProceduralMaterial(entity); } if (entity.has()) { @@ -183,26 +197,26 @@ nlohmann::json SceneSerializer::serializeEntity(flecs::entity entity) json["triangleBuffer"] = serializeTriangleBuffer(entity); } - if (entity.has()) { - json["character"] = serializeCharacter(entity); - } + if (entity.has()) { + json["character"] = serializeCharacter(entity); + } - if (entity.has()) { - json["characterSlots"] = serializeCharacterSlots(entity); - } + if (entity.has()) { + json["characterSlots"] = serializeCharacterSlots(entity); + } - if (entity.has()) { - json["animationTree"] = serializeAnimationTree(entity); - } + if (entity.has()) { + json["animationTree"] = serializeAnimationTree(entity); + } - if (entity.has()) { - json["startupMenu"] = serializeStartupMenu(entity); - } + if (entity.has()) { + json["startupMenu"] = serializeStartupMenu(entity); + } + + if (entity.has()) { + json["playerController"] = serializePlayerController(entity); + } - if (entity.has()) { - json["playerController"] = serializePlayerController(entity); - } - // CellGrid/Town components if (entity.has()) { json["cellGrid"] = serializeCellGrid(entity); @@ -241,7 +255,9 @@ nlohmann::json SceneSerializer::serializeEntity(flecs::entity entity) return json; } -void SceneSerializer::deserializeEntity(const nlohmann::json& json, flecs::entity parent, EditorUISystem* uiSystem) +void SceneSerializer::deserializeEntity(const nlohmann::json &json, + flecs::entity parent, + EditorUISystem *uiSystem) { // Create new entity flecs::entity entity = m_world.entity(); @@ -290,12 +306,16 @@ void SceneSerializer::deserializeEntity(const nlohmann::json& json, flecs::entit } if (json.contains("lodSettings")) { - Ogre::LogManager::getSingleton().logMessage("SceneSerializer: Found lodSettings for entity " + Ogre::StringConverter::toString(entity.id())); + Ogre::LogManager::getSingleton().logMessage( + "SceneSerializer: Found lodSettings for entity " + + Ogre::StringConverter::toString(entity.id())); deserializeLodSettings(entity, json["lodSettings"]); } if (json.contains("lod")) { - Ogre::LogManager::getSingleton().logMessage("SceneSerializer: Found lod for entity " + Ogre::StringConverter::toString(entity.id())); + Ogre::LogManager::getSingleton().logMessage( + "SceneSerializer: Found lod for entity " + + Ogre::StringConverter::toString(entity.id())); deserializeLod(entity, json["lod"]); } @@ -304,7 +324,8 @@ void SceneSerializer::deserializeEntity(const nlohmann::json& json, flecs::entit } if (json.contains("staticGeometryMember")) { - deserializeStaticGeometryMember(entity, json["staticGeometryMember"]); + deserializeStaticGeometryMember(entity, + json["staticGeometryMember"]); } if (json.contains("proceduralTexture")) { @@ -312,37 +333,38 @@ void SceneSerializer::deserializeEntity(const nlohmann::json& json, flecs::entit } if (json.contains("proceduralMaterial")) { - deserializeProceduralMaterial(entity, json["proceduralMaterial"]); + deserializeProceduralMaterial(entity, + json["proceduralMaterial"]); } if (json.contains("primitive")) { deserializePrimitive(entity, json["primitive"]); } - if (json.contains("character")) { - deserializeCharacter(entity, json["character"]); - } + if (json.contains("character")) { + deserializeCharacter(entity, json["character"]); + } - if (json.contains("characterSlots")) { - deserializeCharacterSlots(entity, json["characterSlots"]); - } + if (json.contains("characterSlots")) { + deserializeCharacterSlots(entity, json["characterSlots"]); + } - if (json.contains("animationTree")) { - deserializeAnimationTree(entity, json["animationTree"]); - } + if (json.contains("animationTree")) { + deserializeAnimationTree(entity, json["animationTree"]); + } - if (json.contains("startupMenu")) { - deserializeStartupMenu(entity, json["startupMenu"]); - } + if (json.contains("startupMenu")) { + deserializeStartupMenu(entity, json["startupMenu"]); + } - if (json.contains("playerController")) { - deserializePlayerController(entity, json["playerController"]); - } + if (json.contains("playerController")) { + deserializePlayerController(entity, json["playerController"]); + } if (json.contains("triangleBuffer")) { deserializeTriangleBuffer(entity, json["triangleBuffer"]); } - + // CellGrid/Town components if (json.contains("cellGrid")) { deserializeCellGrid(entity, json["cellGrid"]); @@ -376,7 +398,7 @@ void SceneSerializer::deserializeEntity(const nlohmann::json& json, flecs::entit // Deserialize children if (json.contains("children") && json["children"].is_array()) { - for (const auto& childJson : json["children"]) { + for (const auto &childJson : json["children"]) { deserializeEntity(childJson, entity, uiSystem); } } @@ -384,34 +406,28 @@ void SceneSerializer::deserializeEntity(const nlohmann::json& json, flecs::entit nlohmann::json SceneSerializer::serializeTransform(flecs::entity entity) { - auto& transform = entity.get(); + auto &transform = entity.get(); 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} - }; + + 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(); + auto &renderable = entity.get(); nlohmann::json json; json["meshName"] = renderable.meshName; json["visible"] = renderable.visible; @@ -420,7 +436,7 @@ nlohmann::json SceneSerializer::serializeRenderable(flecs::entity entity) nlohmann::json SceneSerializer::serializeEntityName(flecs::entity entity) { - auto& name = entity.get(); + auto &name = entity.get(); nlohmann::json json; json["name"] = name.name; return json; @@ -428,150 +444,138 @@ nlohmann::json SceneSerializer::serializeEntityName(flecs::entity entity) nlohmann::json SceneSerializer::serializeRigidBody(flecs::entity entity) { - auto& rb = entity.get(); + auto &rb = entity.get(); nlohmann::json json; - + // Serialize body type as string switch (rb.bodyType) { - case RigidBodyComponent::BodyType::Static: - json["bodyType"] = "static"; - break; - case RigidBodyComponent::BodyType::Dynamic: - json["bodyType"] = "dynamic"; - break; - case RigidBodyComponent::BodyType::Kinematic: - json["bodyType"] = "kinematic"; - break; + case RigidBodyComponent::BodyType::Static: + json["bodyType"] = "static"; + break; + case RigidBodyComponent::BodyType::Dynamic: + json["bodyType"] = "dynamic"; + break; + case RigidBodyComponent::BodyType::Kinematic: + json["bodyType"] = "kinematic"; + break; } - + json["mass"] = rb.mass; json["friction"] = rb.friction; json["restitution"] = rb.restitution; json["isSensor"] = rb.isSensor; json["enabled"] = rb.enabled; - + return json; } nlohmann::json SceneSerializer::serializeCollider(flecs::entity entity) { - auto& collider = entity.get(); + auto &collider = entity.get(); nlohmann::json json; - + // Serialize shape type as string switch (collider.shapeType) { - case PhysicsColliderComponent::ShapeType::Box: - json["shapeType"] = "box"; - break; - case PhysicsColliderComponent::ShapeType::Sphere: - json["shapeType"] = "sphere"; - break; - case PhysicsColliderComponent::ShapeType::Capsule: - json["shapeType"] = "capsule"; - break; - case PhysicsColliderComponent::ShapeType::Cylinder: - json["shapeType"] = "cylinder"; - break; - case PhysicsColliderComponent::ShapeType::Mesh: - json["shapeType"] = "mesh"; - break; - case PhysicsColliderComponent::ShapeType::ConvexHull: - json["shapeType"] = "convexHull"; - break; + case PhysicsColliderComponent::ShapeType::Box: + json["shapeType"] = "box"; + break; + case PhysicsColliderComponent::ShapeType::Sphere: + json["shapeType"] = "sphere"; + break; + case PhysicsColliderComponent::ShapeType::Capsule: + json["shapeType"] = "capsule"; + break; + case PhysicsColliderComponent::ShapeType::Cylinder: + json["shapeType"] = "cylinder"; + break; + case PhysicsColliderComponent::ShapeType::Mesh: + json["shapeType"] = "mesh"; + break; + case PhysicsColliderComponent::ShapeType::ConvexHull: + json["shapeType"] = "convexHull"; + break; } - + // Serialize shape parameters - json["parameters"] = { - {"x", collider.parameters.x}, - {"y", collider.parameters.y}, - {"z", collider.parameters.z} - }; - + json["parameters"] = { { "x", collider.parameters.x }, + { "y", collider.parameters.y }, + { "z", collider.parameters.z } }; + json["radius"] = collider.radius; json["halfHeight"] = collider.halfHeight; json["meshName"] = collider.meshName; - + // Serialize offset - json["offset"] = { - {"x", collider.offset.x}, - {"y", collider.offset.y}, - {"z", collider.offset.z} - }; - + json["offset"] = { { "x", collider.offset.x }, + { "y", collider.offset.y }, + { "z", collider.offset.z } }; + // Serialize rotation offset - json["rotationOffset"] = { - {"w", collider.rotationOffset.w}, - {"x", collider.rotationOffset.x}, - {"y", collider.rotationOffset.y}, - {"z", collider.rotationOffset.z} - }; - + json["rotationOffset"] = { { "w", collider.rotationOffset.w }, + { "x", collider.rotationOffset.x }, + { "y", collider.rotationOffset.y }, + { "z", collider.rotationOffset.z } }; + return json; } nlohmann::json SceneSerializer::serializeLight(flecs::entity entity) { - auto& light = entity.get(); + auto &light = entity.get(); nlohmann::json json; - + // Serialize light type switch (light.lightType) { - case LightComponent::LightType::Point: - json["lightType"] = "point"; - break; - case LightComponent::LightType::Directional: - json["lightType"] = "directional"; - break; - case LightComponent::LightType::Spotlight: - json["lightType"] = "spotlight"; - break; + case LightComponent::LightType::Point: + json["lightType"] = "point"; + break; + case LightComponent::LightType::Directional: + json["lightType"] = "directional"; + break; + case LightComponent::LightType::Spotlight: + json["lightType"] = "spotlight"; + break; } - + // Colors - json["diffuseColor"] = { - {"r", light.diffuseColor.r}, - {"g", light.diffuseColor.g}, - {"b", light.diffuseColor.b}, - {"a", light.diffuseColor.a} - }; - - json["specularColor"] = { - {"r", light.specularColor.r}, - {"g", light.specularColor.g}, - {"b", light.specularColor.b}, - {"a", light.specularColor.a} - }; - + json["diffuseColor"] = { { "r", light.diffuseColor.r }, + { "g", light.diffuseColor.g }, + { "b", light.diffuseColor.b }, + { "a", light.diffuseColor.a } }; + + json["specularColor"] = { { "r", light.specularColor.r }, + { "g", light.specularColor.g }, + { "b", light.specularColor.b }, + { "a", light.specularColor.a } }; + json["intensity"] = light.intensity; - + // Attenuation json["range"] = light.range; json["constantAttenuation"] = light.constantAttenuation; json["linearAttenuation"] = light.linearAttenuation; json["quadraticAttenuation"] = light.quadraticAttenuation; - + // Spotlight settings json["spotlightInnerAngle"] = light.spotlightInnerAngle; json["spotlightOuterAngle"] = light.spotlightOuterAngle; json["spotlightFalloff"] = light.spotlightFalloff; - + // Direction - json["direction"] = { - {"x", light.direction.x}, - {"y", light.direction.y}, - {"z", light.direction.z} - }; - + json["direction"] = { { "x", light.direction.x }, + { "y", light.direction.y }, + { "z", light.direction.z } }; + json["castShadows"] = light.castShadows; - + return json; } nlohmann::json SceneSerializer::serializeCamera(flecs::entity entity) { - auto& camera = entity.get(); + auto &camera = entity.get(); nlohmann::json json; - + json["fovY"] = camera.fovY; json["nearClip"] = camera.nearClip; json["farClip"] = camera.farClip; @@ -579,101 +583,107 @@ nlohmann::json SceneSerializer::serializeCamera(flecs::entity entity) json["orthographic"] = camera.orthographic; json["orthoWidth"] = camera.orthoWidth; json["orthoHeight"] = camera.orthoHeight; - + return json; } -void SceneSerializer::deserializeTransform(flecs::entity entity, const nlohmann::json& json, flecs::entity parentEntity) +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) - ); + 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) - ); + 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) - ); + 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()) { + if (parentEntity.is_valid() && parentEntity != 0 && + parentEntity.has()) { // Child of parent entity's node - auto& parentTransform = parentEntity.get(); + auto &parentTransform = parentEntity.get(); if (parentTransform.node) { - transform.node = parentTransform.node->createChildSceneNode(); + transform.node = + parentTransform.node->createChildSceneNode(); } else { - transform.node = m_sceneMgr->getRootSceneNode()->createChildSceneNode(); + transform.node = m_sceneMgr->getRootSceneNode() + ->createChildSceneNode(); } } else { // Root level - transform.node = m_sceneMgr->getRootSceneNode()->createChildSceneNode(); + transform.node = + m_sceneMgr->getRootSceneNode()->createChildSceneNode(); } transform.applyToNode(); entity.set(transform); } -void SceneSerializer::deserializeRenderable(flecs::entity entity, const nlohmann::json& json) +void SceneSerializer::deserializeRenderable(flecs::entity entity, + const nlohmann::json &json) { RenderableComponent renderable; renderable.meshName = json.value("meshName", ""); renderable.visible = json.value("visible", true); renderable.entity = nullptr; - + // Load the mesh immediately if mesh name is provided if (!renderable.meshName.empty() && entity.has()) { - auto& transform = entity.get_mut(); + auto &transform = entity.get_mut(); if (transform.node) { try { - renderable.entity = m_sceneMgr->createEntity(renderable.meshName); + renderable.entity = m_sceneMgr->createEntity( + renderable.meshName); transform.node->attachObject(renderable.entity); - renderable.entity->setVisible(renderable.visible); - } catch (const Ogre::Exception& e) { + renderable.entity->setVisible( + renderable.visible); + } catch (const Ogre::Exception &e) { // Mesh not found - entity will be created but without mesh // User can load mesh manually later Ogre::LogManager::getSingleton().logMessage( - "Warning: Failed to load mesh '" + renderable.meshName + + "Warning: Failed to load mesh '" + + renderable.meshName + "' for entity: " + e.getDescription()); } } } - + entity.set(renderable); } -void SceneSerializer::deserializeEntityName(flecs::entity entity, const nlohmann::json& json) +void SceneSerializer::deserializeEntityName(flecs::entity entity, + const nlohmann::json &json) { std::string name = json.value("name", "Entity"); entity.set(EntityNameComponent(name)); } -void SceneSerializer::deserializeRigidBody(flecs::entity entity, const nlohmann::json& json) +void SceneSerializer::deserializeRigidBody(flecs::entity entity, + const nlohmann::json &json) { RigidBodyComponent rb; - + // Deserialize body type std::string bodyType = json.value("bodyType", "static"); if (bodyType == "static") { @@ -683,85 +693,84 @@ void SceneSerializer::deserializeRigidBody(flecs::entity entity, const nlohmann: } else if (bodyType == "kinematic") { rb.bodyType = RigidBodyComponent::BodyType::Kinematic; } - + rb.mass = json.value("mass", 1.0f); rb.friction = json.value("friction", 0.5f); rb.restitution = json.value("restitution", 0.0f); rb.isSensor = json.value("isSensor", false); rb.enabled = json.value("enabled", true); - + // Mark as dirty so physics system will create the body rb.bodyDirty = true; rb.bodyCreated = false; - + entity.set(rb); } -void SceneSerializer::deserializeCollider(flecs::entity entity, const nlohmann::json& json) +void SceneSerializer::deserializeCollider(flecs::entity entity, + const nlohmann::json &json) { PhysicsColliderComponent collider; - + // Deserialize shape type std::string shapeType = json.value("shapeType", "box"); if (shapeType == "box") { collider.shapeType = PhysicsColliderComponent::ShapeType::Box; } else if (shapeType == "sphere") { - collider.shapeType = PhysicsColliderComponent::ShapeType::Sphere; + collider.shapeType = + PhysicsColliderComponent::ShapeType::Sphere; } else if (shapeType == "capsule") { - collider.shapeType = PhysicsColliderComponent::ShapeType::Capsule; + collider.shapeType = + PhysicsColliderComponent::ShapeType::Capsule; } else if (shapeType == "cylinder") { - collider.shapeType = PhysicsColliderComponent::ShapeType::Cylinder; + collider.shapeType = + PhysicsColliderComponent::ShapeType::Cylinder; } else if (shapeType == "mesh") { collider.shapeType = PhysicsColliderComponent::ShapeType::Mesh; } else if (shapeType == "convexHull") { - collider.shapeType = PhysicsColliderComponent::ShapeType::ConvexHull; + collider.shapeType = + PhysicsColliderComponent::ShapeType::ConvexHull; } - + // Deserialize shape parameters if (json.contains("parameters")) { - auto& params = json["parameters"]; - collider.parameters = Ogre::Vector3( - params.value("x", 0.5f), - params.value("y", 0.5f), - params.value("z", 0.5f) - ); + auto ¶ms = json["parameters"]; + collider.parameters = Ogre::Vector3(params.value("x", 0.5f), + params.value("y", 0.5f), + params.value("z", 0.5f)); } - + collider.radius = json.value("radius", 0.5f); collider.halfHeight = json.value("halfHeight", 1.0f); collider.meshName = json.value("meshName", ""); - + // Deserialize offset if (json.contains("offset")) { - auto& off = json["offset"]; - collider.offset = Ogre::Vector3( - off.value("x", 0.0f), - off.value("y", 0.0f), - off.value("z", 0.0f) - ); + auto &off = json["offset"]; + collider.offset = Ogre::Vector3(off.value("x", 0.0f), + off.value("y", 0.0f), + off.value("z", 0.0f)); } - + // Deserialize rotation offset if (json.contains("rotationOffset")) { - auto& rot = json["rotationOffset"]; + auto &rot = json["rotationOffset"]; collider.rotationOffset = Ogre::Quaternion( - rot.value("w", 1.0f), - rot.value("x", 0.0f), - rot.value("y", 0.0f), - rot.value("z", 0.0f) - ); + rot.value("w", 1.0f), rot.value("x", 0.0f), + rot.value("y", 0.0f), rot.value("z", 0.0f)); } - + // Mark as dirty so physics system will create the shape collider.shapeDirty = true; - + entity.set(collider); } -void SceneSerializer::deserializeLight(flecs::entity entity, const nlohmann::json& json) +void SceneSerializer::deserializeLight(flecs::entity entity, + const nlohmann::json &json) { LightComponent light; - + // Deserialize light type std::string lightType = json.value("lightType", "point"); if (lightType == "point") { @@ -771,63 +780,58 @@ void SceneSerializer::deserializeLight(flecs::entity entity, const nlohmann::jso } else if (lightType == "spotlight") { light.lightType = LightComponent::LightType::Spotlight; } - + // Deserialize colors if (json.contains("diffuseColor")) { - auto& col = json["diffuseColor"]; - light.diffuseColor = Ogre::ColourValue( - col.value("r", 0.8f), - col.value("g", 0.8f), - col.value("b", 0.8f), - col.value("a", 1.0f) - ); + auto &col = json["diffuseColor"]; + light.diffuseColor = Ogre::ColourValue(col.value("r", 0.8f), + col.value("g", 0.8f), + col.value("b", 0.8f), + col.value("a", 1.0f)); } - + if (json.contains("specularColor")) { - auto& col = json["specularColor"]; - light.specularColor = Ogre::ColourValue( - col.value("r", 0.5f), - col.value("g", 0.5f), - col.value("b", 0.5f), - col.value("a", 1.0f) - ); + auto &col = json["specularColor"]; + light.specularColor = Ogre::ColourValue(col.value("r", 0.5f), + col.value("g", 0.5f), + col.value("b", 0.5f), + col.value("a", 1.0f)); } - + light.intensity = json.value("intensity", 1.0f); - + // Attenuation light.range = json.value("range", 100.0f); light.constantAttenuation = json.value("constantAttenuation", 1.0f); light.linearAttenuation = json.value("linearAttenuation", 0.0f); light.quadraticAttenuation = json.value("quadraticAttenuation", 0.0f); - + // Spotlight settings light.spotlightInnerAngle = json.value("spotlightInnerAngle", 30.0f); light.spotlightOuterAngle = json.value("spotlightOuterAngle", 45.0f); light.spotlightFalloff = json.value("spotlightFalloff", 1.0f); - + // Direction if (json.contains("direction")) { - auto& dir = json["direction"]; - light.direction = Ogre::Vector3( - dir.value("x", 0.0f), - dir.value("y", -1.0f), - dir.value("z", 0.0f) - ); + auto &dir = json["direction"]; + light.direction = Ogre::Vector3(dir.value("x", 0.0f), + dir.value("y", -1.0f), + dir.value("z", 0.0f)); } - + light.castShadows = json.value("castShadows", true); - + // Mark as dirty so light system will create the light light.needsRebuild = true; - + entity.set(light); } -void SceneSerializer::deserializeCamera(flecs::entity entity, const nlohmann::json& json) +void SceneSerializer::deserializeCamera(flecs::entity entity, + const nlohmann::json &json) { CameraComponent camera; - + camera.fovY = json.value("fovY", 45.0f); camera.nearClip = json.value("nearClip", 0.1f); camera.farClip = json.value("farClip", 1000.0f); @@ -835,60 +839,59 @@ void SceneSerializer::deserializeCamera(flecs::entity entity, const nlohmann::js camera.orthographic = json.value("orthographic", false); camera.orthoWidth = json.value("orthoWidth", 10.0f); camera.orthoHeight = json.value("orthoHeight", 10.0f); - + // Mark as dirty so camera system will create the camera camera.needsRebuild = true; - + entity.set(camera); } - nlohmann::json SceneSerializer::serializeLodSettings(flecs::entity entity) { - auto& settings = entity.get(); + auto &settings = entity.get(); nlohmann::json json; - + // Serialize settings ID json["settingsId"] = settings.settingsId; - + // Serialize strategy switch (settings.strategy) { - case LodSettingsComponent::Strategy::Distance: - json["strategy"] = "distance"; - break; - case LodSettingsComponent::Strategy::PixelCount: - json["strategy"] = "pixelCount"; - break; - case LodSettingsComponent::Strategy::EdgePixelCount: - json["strategy"] = "edgePixelCount"; - break; + case LodSettingsComponent::Strategy::Distance: + json["strategy"] = "distance"; + break; + case LodSettingsComponent::Strategy::PixelCount: + json["strategy"] = "pixelCount"; + break; + case LodSettingsComponent::Strategy::EdgePixelCount: + json["strategy"] = "edgePixelCount"; + break; } - + // Serialize LOD levels json["lodLevels"] = nlohmann::json::array(); - for (const auto& level : settings.lodLevels) { + for (const auto &level : settings.lodLevels) { nlohmann::json levelJson; levelJson["distance"] = level.distance; - + switch (level.reductionMethod) { - case LodLevelConfig::ReductionMethod::Proportional: - levelJson["reductionMethod"] = "proportional"; - break; - case LodLevelConfig::ReductionMethod::Constant: - levelJson["reductionMethod"] = "constant"; - break; - case LodLevelConfig::ReductionMethod::CollapseCost: - levelJson["reductionMethod"] = "collapseCost"; - break; + case LodLevelConfig::ReductionMethod::Proportional: + levelJson["reductionMethod"] = "proportional"; + break; + case LodLevelConfig::ReductionMethod::Constant: + levelJson["reductionMethod"] = "constant"; + break; + case LodLevelConfig::ReductionMethod::CollapseCost: + levelJson["reductionMethod"] = "collapseCost"; + break; } - + levelJson["reductionValue"] = level.reductionValue; levelJson["useManualMesh"] = level.useManualMesh; levelJson["manualMeshName"] = level.manualMeshName; - + json["lodLevels"].push_back(levelJson); } - + // Serialize advanced options json["useCompression"] = settings.useCompression; json["useVertexNormals"] = settings.useVertexNormals; @@ -897,39 +900,43 @@ nlohmann::json SceneSerializer::serializeLodSettings(flecs::entity entity) json["useBackgroundQueue"] = settings.useBackgroundQueue; json["outsideWeight"] = settings.outsideWeight; json["outsideWalkAngle"] = settings.outsideWalkAngle; - + return json; } nlohmann::json SceneSerializer::serializeLod(flecs::entity entity) { - auto& lod = entity.get(); + auto &lod = entity.get(); nlohmann::json json; - + // Serialize settings ID (persistent across scene loads) json["settingsId"] = lod.settingsId; - + json["distanceMultiplier"] = lod.distanceMultiplier; - + return json; } -void SceneSerializer::deserializeLodSettings(flecs::entity entity, const nlohmann::json& json) +void SceneSerializer::deserializeLodSettings(flecs::entity entity, + const nlohmann::json &json) { LodSettingsComponent settings; - + // Deserialize settings ID settings.settingsId = json.value("settingsId", ""); Ogre::LogManager::getSingleton().logMessage( - "Deserialize LodSettings: loaded settingsId=" + settings.settingsId); - + "Deserialize LodSettings: loaded settingsId=" + + settings.settingsId); + // Auto-generate ID if empty if (settings.settingsId.empty()) { - settings.settingsId = "lod_settings_" + std::to_string(entity.id()); + settings.settingsId = + "lod_settings_" + std::to_string(entity.id()); Ogre::LogManager::getSingleton().logMessage( - "Deserialize LodSettings: auto-generated settingsId=" + settings.settingsId); + "Deserialize LodSettings: auto-generated settingsId=" + + settings.settingsId); } - + // Deserialize strategy std::string strategy = json.value("strategy", "distance"); if (strategy == "distance") { @@ -937,63 +944,76 @@ void SceneSerializer::deserializeLodSettings(flecs::entity entity, const nlohman } else if (strategy == "pixelCount") { settings.strategy = LodSettingsComponent::Strategy::PixelCount; } else if (strategy == "edgePixelCount") { - settings.strategy = LodSettingsComponent::Strategy::EdgePixelCount; + settings.strategy = + LodSettingsComponent::Strategy::EdgePixelCount; } - + // Deserialize LOD levels settings.lodLevels.clear(); if (json.contains("lodLevels") && json["lodLevels"].is_array()) { - for (const auto& levelJson : json["lodLevels"]) { + for (const auto &levelJson : json["lodLevels"]) { LodLevelConfig level; level.distance = levelJson.value("distance", 100.0f); - - std::string method = levelJson.value("reductionMethod", "proportional"); + + std::string method = levelJson.value("reductionMethod", + "proportional"); if (method == "proportional") { - level.reductionMethod = LodLevelConfig::ReductionMethod::Proportional; + level.reductionMethod = LodLevelConfig:: + ReductionMethod::Proportional; } else if (method == "constant") { - level.reductionMethod = LodLevelConfig::ReductionMethod::Constant; + level.reductionMethod = + LodLevelConfig::ReductionMethod::Constant; } else if (method == "collapseCost") { - level.reductionMethod = LodLevelConfig::ReductionMethod::CollapseCost; + level.reductionMethod = LodLevelConfig:: + ReductionMethod::CollapseCost; } - - level.reductionValue = levelJson.value("reductionValue", 0.5f); - level.useManualMesh = levelJson.value("useManualMesh", false); - level.manualMeshName = levelJson.value("manualMeshName", ""); - + + level.reductionValue = + levelJson.value("reductionValue", 0.5f); + level.useManualMesh = + levelJson.value("useManualMesh", false); + level.manualMeshName = + levelJson.value("manualMeshName", ""); + settings.lodLevels.push_back(level); } } - + // Deserialize advanced options settings.useCompression = json.value("useCompression", true); settings.useVertexNormals = json.value("useVertexNormals", true); - settings.preventPunchingHoles = json.value("preventPunchingHoles", false); - settings.preventBreakingLines = json.value("preventBreakingLines", false); + settings.preventPunchingHoles = + json.value("preventPunchingHoles", false); + settings.preventBreakingLines = + json.value("preventBreakingLines", false); settings.useBackgroundQueue = json.value("useBackgroundQueue", false); settings.outsideWeight = json.value("outsideWeight", 0.0f); settings.outsideWalkAngle = json.value("outsideWalkAngle", 0.0f); - + // Mark as dirty so LOD system will apply settings settings.markDirty(); - + entity.set(settings); } -void SceneSerializer::deserializeLod(flecs::entity entity, const nlohmann::json& json) +void SceneSerializer::deserializeLod(flecs::entity entity, + const nlohmann::json &json) { LodComponent lod; - + // Deserialize settings ID lod.settingsId = json.value("settingsId", ""); Ogre::LogManager::getSingleton().logMessage( "Deserialize LOD: settingsId=" + lod.settingsId); - + // Try to find the settings entity by ID if (!lod.settingsId.empty()) { - m_world.query().each([&](flecs::entity e, LodSettingsComponent& settings) { + m_world.query().each([&](flecs::entity e, + LodSettingsComponent + &settings) { Ogre::LogManager::getSingleton().logMessage( - "Deserialize LOD: checking settings entity " + - Ogre::StringConverter::toString(e.id()) + + "Deserialize LOD: checking settings entity " + + Ogre::StringConverter::toString(e.id()) + " with settingsId=" + settings.settingsId); if (settings.settingsId == lod.settingsId) { Ogre::LogManager::getSingleton().logMessage( @@ -1002,53 +1022,55 @@ void SceneSerializer::deserializeLod(flecs::entity entity, const nlohmann::json& } }); } - + lod.distanceMultiplier = json.value("distanceMultiplier", 1.0f); lod.lodApplied = false; lod.dirty = true; - + entity.set(lod); - + Ogre::LogManager::getSingleton().logMessage( - "Deserialize LOD: Final settingsEntity=" + + "Deserialize LOD: Final settingsEntity=" + Ogre::StringConverter::toString(lod.settingsEntity.id()) + ", hasValid=" + (lod.hasValidSettings() ? "true" : "false")); } nlohmann::json SceneSerializer::serializeStaticGeometry(flecs::entity entity) { - auto& region = entity.get(); + auto ®ion = entity.get(); nlohmann::json json; - + json["regionId"] = region.regionId; json["regionName"] = region.regionName; json["renderingDistance"] = region.renderingDistance; json["castShadows"] = region.castShadows; json["regionDimensions"] = region.regionDimensions; json["visibilityFlags"] = region.visibilityFlags; - + return json; } -nlohmann::json SceneSerializer::serializeStaticGeometryMember(flecs::entity entity) +nlohmann::json +SceneSerializer::serializeStaticGeometryMember(flecs::entity entity) { - auto& member = entity.get(); + auto &member = entity.get(); nlohmann::json json; - + json["regionId"] = member.regionId; json["meshName"] = member.meshName; json["materialName"] = member.materialName; json["lodSettingsId"] = member.lodSettingsId; json["castShadows"] = member.castShadows; json["visible"] = member.visible; - + return json; } -void SceneSerializer::deserializeStaticGeometry(flecs::entity entity, const nlohmann::json& json) +void SceneSerializer::deserializeStaticGeometry(flecs::entity entity, + const nlohmann::json &json) { StaticGeometryComponent region; - + region.regionId = json.value("regionId", ""); if (region.regionId.empty()) { // Generate new ID if missing @@ -1057,21 +1079,22 @@ void SceneSerializer::deserializeStaticGeometry(flecs::entity entity, const nloh static std::uniform_int_distribution<> dis(1000, 9999); region.regionId = "region_" + std::to_string(dis(gen)); } - + region.regionName = json.value("regionName", ""); region.renderingDistance = json.value("renderingDistance", 0.0f); region.castShadows = json.value("castShadows", true); region.regionDimensions = json.value("regionDimensions", 1000.0f); region.visibilityFlags = json.value("visibilityFlags", 0xFFFFFFFF); region.dirty = true; - + entity.set(region); } -void SceneSerializer::deserializeStaticGeometryMember(flecs::entity entity, const nlohmann::json& json) +void SceneSerializer::deserializeStaticGeometryMember( + flecs::entity entity, const nlohmann::json &json) { StaticGeometryMemberComponent member; - + member.regionId = json.value("regionId", ""); member.meshName = json.value("meshName", ""); member.materialName = json.value("materialName", ""); @@ -1079,19 +1102,20 @@ void SceneSerializer::deserializeStaticGeometryMember(flecs::entity entity, cons member.castShadows = json.value("castShadows", true); member.visible = json.value("visible", true); member.dirty = true; - + entity.set(member); } nlohmann::json SceneSerializer::serializeProceduralTexture(flecs::entity entity) { - auto& texture = entity.get(); + auto &texture = entity.get(); nlohmann::json json; - + + json["textureId"] = texture.textureId; json["textureName"] = texture.textureName; json["textureSize"] = texture.textureSize; json["uvMargin"] = texture.uvMargin; - + // Serialize colors array nlohmann::json colorsJson = nlohmann::json::array(); for (int i = 0; i < ProceduralTextureComponent::RECT_COUNT; ++i) { @@ -1103,11 +1127,11 @@ nlohmann::json SceneSerializer::serializeProceduralTexture(flecs::entity entity) colorsJson.push_back(color); } json["colors"] = colorsJson; - + // Serialize named rectangles nlohmann::json namedRectsJson = nlohmann::json::array(); - for (const auto& pair : texture.namedRects) { - const auto& rect = pair.second; + for (const auto &pair : texture.namedRects) { + const auto &rect = pair.second; nlohmann::json rectJson; rectJson["name"] = rect.name; rectJson["u1"] = rect.u1; @@ -1117,23 +1141,31 @@ nlohmann::json SceneSerializer::serializeProceduralTexture(flecs::entity entity) namedRectsJson.push_back(rectJson); } json["namedRects"] = namedRectsJson; - + return json; } -void SceneSerializer::deserializeProceduralTexture(flecs::entity entity, const nlohmann::json& json) +void SceneSerializer::deserializeProceduralTexture(flecs::entity entity, + const nlohmann::json &json) { ProceduralTextureComponent texture; - + + texture.textureId = json.value("textureId", ""); texture.textureName = json.value("textureName", ""); texture.textureSize = json.value("textureSize", 512); texture.uvMargin = json.value("uvMargin", 0.01f); - + + // Auto-generate texture ID if empty + if (texture.textureId.empty()) { + texture.textureId = "texture_" + std::to_string(entity.id()); + } + // Deserialize colors array if (json.contains("colors") && json["colors"].is_array()) { int i = 0; - for (const auto& colorJson : json["colors"]) { - if (i >= ProceduralTextureComponent::RECT_COUNT) break; + for (const auto &colorJson : json["colors"]) { + if (i >= ProceduralTextureComponent::RECT_COUNT) + break; texture.colors[i * 4 + 0] = colorJson.value("r", 1.0f); texture.colors[i * 4 + 1] = colorJson.value("g", 1.0f); texture.colors[i * 4 + 2] = colorJson.value("b", 1.0f); @@ -1141,10 +1173,10 @@ void SceneSerializer::deserializeProceduralTexture(flecs::entity entity, const n ++i; } } - + // Deserialize named rectangles if (json.contains("namedRects") && json["namedRects"].is_array()) { - for (const auto& rectJson : json["namedRects"]) { + for (const auto &rectJson : json["namedRects"]) { std::string name = rectJson.value("name", ""); if (!name.empty()) { TextureRectInfo rect; @@ -1157,116 +1189,151 @@ void SceneSerializer::deserializeProceduralTexture(flecs::entity entity, const n } } } - + texture.dirty = true; - + entity.set(texture); } -nlohmann::json SceneSerializer::serializeProceduralMaterial(flecs::entity entity) +nlohmann::json +SceneSerializer::serializeProceduralMaterial(flecs::entity entity) { - auto& material = entity.get(); + auto &material = entity.get_mut(); nlohmann::json json; - + + json["materialId"] = material.materialId; json["materialName"] = material.materialName; - json["diffuseTextureEntity"] = material.diffuseTextureEntity.id(); - - json["ambient"] = {{"r", material.ambient[0]}, {"g", material.ambient[1]}, {"b", material.ambient[2]}}; - json["diffuse"] = {{"r", material.diffuse[0]}, {"g", material.diffuse[1]}, {"b", material.diffuse[2]}}; - json["specular"] = {{"r", material.specular[0]}, {"g", material.specular[1]}, {"b", material.specular[2]}}; + + // Ensure diffuseTextureId is populated from texture entity if empty + std::string textureIdToSave = material.diffuseTextureId; + if (textureIdToSave.empty() && + material.diffuseTextureEntity.is_alive() && + material.diffuseTextureEntity.has()) { + const auto &texture = + material.diffuseTextureEntity + .get(); + textureIdToSave = texture.textureId; + // Update the component for future saves + material.diffuseTextureId = textureIdToSave; + } + + json["diffuseTextureId"] = textureIdToSave; + + json["ambient"] = { { "r", material.ambient[0] }, + { "g", material.ambient[1] }, + { "b", material.ambient[2] } }; + json["diffuse"] = { { "r", material.diffuse[0] }, + { "g", material.diffuse[1] }, + { "b", material.diffuse[2] } }; + json["specular"] = { { "r", material.specular[0] }, + { "g", material.specular[1] }, + { "b", material.specular[2] } }; json["shininess"] = material.shininess; json["roughness"] = material.roughness; - + return json; } -void SceneSerializer::deserializeProceduralMaterial(flecs::entity entity, const nlohmann::json& json) +void SceneSerializer::deserializeProceduralMaterial(flecs::entity entity, + const nlohmann::json &json) { ProceduralMaterialComponent material; - + + material.materialId = json.value("materialId", ""); material.materialName = json.value("materialName", ""); - - // Store the entity ID for now - resolution will happen at runtime - if (json.contains("diffuseTextureEntity")) { - uint64_t textureEntityId = json["diffuseTextureEntity"]; - if (textureEntityId != 0) { - // Will resolve to actual entity in update loop - material.diffuseTextureEntity = m_world.entity(textureEntityId); - } + material.diffuseTextureId = json.value("diffuseTextureId", ""); + + // Auto-generate material ID if empty + if (material.materialId.empty()) { + material.materialId = "material_" + std::to_string(entity.id()); } - + + // Resolve texture reference by ID (like LOD system does) + if (!material.diffuseTextureId.empty()) { + m_world.query().each( + [&](flecs::entity e, + ProceduralTextureComponent &texture) { + if (texture.textureId == + material.diffuseTextureId) { + material.diffuseTextureEntity = e; + } + }); + } + if (json.contains("ambient")) { material.ambient[0] = json["ambient"].value("r", 0.2f); material.ambient[1] = json["ambient"].value("g", 0.2f); material.ambient[2] = json["ambient"].value("b", 0.2f); } - + if (json.contains("diffuse")) { material.diffuse[0] = json["diffuse"].value("r", 1.0f); material.diffuse[1] = json["diffuse"].value("g", 1.0f); material.diffuse[2] = json["diffuse"].value("b", 1.0f); } - + if (json.contains("specular")) { material.specular[0] = json["specular"].value("r", 0.0f); material.specular[1] = json["specular"].value("g", 0.0f); material.specular[2] = json["specular"].value("b", 0.0f); } - + material.shininess = json.value("shininess", 32.0f); material.roughness = json.value("roughness", 0.5f); - + material.dirty = true; - + entity.set(material); } nlohmann::json SceneSerializer::serializePrimitive(flecs::entity entity) { - auto& prim = entity.get(); + auto &prim = entity.get(); nlohmann::json json; - + json["type"] = static_cast(prim.type); - + // Box parameters json["boxSizeX"] = prim.boxSizeX; json["boxSizeY"] = prim.boxSizeY; json["boxSizeZ"] = prim.boxSizeZ; - + // Plane parameters json["planeSizeX"] = prim.planeSizeX; json["planeSizeY"] = prim.planeSizeY; json["planeSegmentsX"] = prim.planeSegmentsX; json["planeSegmentsY"] = prim.planeSegmentsY; - + return json; } -void SceneSerializer::deserializePrimitive(flecs::entity entity, const nlohmann::json& json) +void SceneSerializer::deserializePrimitive(flecs::entity entity, + const nlohmann::json &json) { PrimitiveComponent prim; - - prim.type = static_cast(json.value("type", 0)); - + + prim.type = + static_cast(json.value("type", 0)); + prim.boxSizeX = json.value("boxSizeX", 1.0f); prim.boxSizeY = json.value("boxSizeY", 1.0f); prim.boxSizeZ = json.value("boxSizeZ", 1.0f); - + prim.planeSizeX = json.value("planeSizeX", 1.0f); prim.planeSizeY = json.value("planeSizeY", 1.0f); prim.planeSegmentsX = json.value("planeSegmentsX", 1); prim.planeSegmentsY = json.value("planeSegmentsY", 1); - + prim.dirty = true; - + entity.set(prim); } nlohmann::json SceneSerializer::serializeTriangleBuffer(flecs::entity entity) { - auto& tb = entity.get(); + auto &tb = entity.get(); nlohmann::json json; - + // Only serialize configuration, not the buffer contents json["meshName"] = tb.meshName; json["textureEntity"] = tb.textureEntity.id(); @@ -1274,59 +1341,87 @@ nlohmann::json SceneSerializer::serializeTriangleBuffer(flecs::entity entity) json["materialEntity"] = tb.materialEntity.id(); json["useStaticGeometry"] = tb.useStaticGeometry; json["staticGeometryEntity"] = tb.staticGeometryEntity.id(); - + return json; } -void SceneSerializer::deserializeTriangleBuffer(flecs::entity entity, const nlohmann::json& json) +void SceneSerializer::deserializeTriangleBuffer(flecs::entity entity, + const nlohmann::json &json) { TriangleBufferComponent tb; - + tb.meshName = json.value("meshName", ""); tb.textureRectName = json.value("textureRectName", ""); tb.useStaticGeometry = json.value("useStaticGeometry", false); - - // Store entity IDs for runtime resolution + + // Resolve entity references using the entity map if (json.contains("textureEntity")) { uint64_t id = json["textureEntity"]; - if (id != 0) tb.textureEntity = m_world.entity(id); + if (id != 0) { + // Look up in entity map to find the corresponding new entity + auto it = m_entityMap.find(id); + if (it != m_entityMap.end()) { + tb.textureEntity = it->second; + } else { + // Entity not found in map (might not exist or not loaded yet) + // Create a reference that will be invalid but can be resolved later + tb.textureEntity = m_world.entity(id); + } + } } - + if (json.contains("materialEntity")) { uint64_t id = json["materialEntity"]; - if (id != 0) tb.materialEntity = m_world.entity(id); + if (id != 0) { + // Look up in entity map to find the corresponding new entity + auto it = m_entityMap.find(id); + if (it != m_entityMap.end()) { + tb.materialEntity = it->second; + } else { + // Entity not found in map (might not exist or not loaded yet) + // Create a reference that will be invalid but can be resolved later + tb.materialEntity = m_world.entity(id); + } + } } - + if (json.contains("staticGeometryEntity")) { uint64_t id = json["staticGeometryEntity"]; - if (id != 0) tb.staticGeometryEntity = m_world.entity(id); + if (id != 0) { + // Look up in entity map to find the corresponding new entity + auto it = m_entityMap.find(id); + if (it != m_entityMap.end()) { + tb.staticGeometryEntity = it->second; + } else { + // Entity not found in map (might not exist or not loaded yet) + // Create a reference that will be invalid but can be resolved later + tb.staticGeometryEntity = m_world.entity(id); + } + } } - + tb.dirty = true; - + entity.set(tb); } - nlohmann::json SceneSerializer::serializeCharacter(flecs::entity entity) { - auto& cc = entity.get(); + auto &cc = entity.get(); nlohmann::json json; json["enabled"] = cc.enabled; json["radius"] = cc.radius; json["height"] = cc.height; - json["offset"] = { - cc.offset.x, cc.offset.y, cc.offset.z - }; - json["linearVelocity"] = { - cc.linearVelocity.x, cc.linearVelocity.y, cc.linearVelocity.z - }; + json["offset"] = { cc.offset.x, cc.offset.y, cc.offset.z }; + json["linearVelocity"] = { cc.linearVelocity.x, cc.linearVelocity.y, + cc.linearVelocity.z }; return json; } -void SceneSerializer::deserializeCharacter(flecs::entity entity, const nlohmann::json& json) +void SceneSerializer::deserializeCharacter(flecs::entity entity, + const nlohmann::json &json) { CharacterComponent cc; cc.enabled = json.value("enabled", true); @@ -1334,18 +1429,17 @@ void SceneSerializer::deserializeCharacter(flecs::entity entity, const nlohmann: cc.height = json.value("height", 1.8f); if (json.contains("offset") && json["offset"].is_array() && json["offset"].size() >= 3) { - cc.offset = Ogre::Vector3( - json["offset"][0].get(), - json["offset"][1].get(), - json["offset"][2].get()); + cc.offset = Ogre::Vector3(json["offset"][0].get(), + json["offset"][1].get(), + json["offset"][2].get()); } if (json.contains("linearVelocity") && json["linearVelocity"].is_array() && json["linearVelocity"].size() >= 3) { - cc.linearVelocity = Ogre::Vector3( - json["linearVelocity"][0].get(), - json["linearVelocity"][1].get(), - json["linearVelocity"][2].get()); + cc.linearVelocity = + Ogre::Vector3(json["linearVelocity"][0].get(), + json["linearVelocity"][1].get(), + json["linearVelocity"][2].get()); } cc.dirty = true; entity.set(cc); @@ -1353,33 +1447,33 @@ void SceneSerializer::deserializeCharacter(flecs::entity entity, const nlohmann: nlohmann::json SceneSerializer::serializeCharacterSlots(flecs::entity entity) { - auto& cs = entity.get(); + auto &cs = entity.get(); nlohmann::json json; json["age"] = cs.age; json["sex"] = cs.sex; json["slots"] = nlohmann::json::object(); - for (const auto& pair : cs.slots) + for (const auto &pair : cs.slots) json["slots"][pair.first] = pair.second; return json; } -void SceneSerializer::deserializeCharacterSlots(flecs::entity entity, const nlohmann::json& json) +void SceneSerializer::deserializeCharacterSlots(flecs::entity entity, + const nlohmann::json &json) { CharacterSlotsComponent cs; cs.age = json.value("age", "adult"); cs.sex = json.value("sex", "male"); if (json.contains("slots") && json["slots"].is_object()) { - for (auto& [slot, mesh] : json["slots"].items()) + for (auto &[slot, mesh] : json["slots"].items()) cs.slots[slot] = mesh.get(); } cs.dirty = true; entity.set(cs); } -static nlohmann::json serializeAnimationTreeNode( - const AnimationTreeNode &node) +static nlohmann::json serializeAnimationTreeNode(const AnimationTreeNode &node) { nlohmann::json json; json["type"] = node.type; @@ -1440,7 +1534,7 @@ nlohmann::json SceneSerializer::serializeAnimationTree(flecs::entity entity) } void SceneSerializer::deserializeAnimationTree(flecs::entity entity, - const nlohmann::json &json) + const nlohmann::json &json) { AnimationTreeComponent at; at.enabled = json.value("enabled", true); @@ -1451,23 +1545,22 @@ void SceneSerializer::deserializeAnimationTree(flecs::entity entity, entity.set(at); } - // ============================================================================ // CellGrid/Town Component Serialization // ============================================================================ nlohmann::json SceneSerializer::serializeCellGrid(flecs::entity entity) { - auto& grid = entity.get(); + auto &grid = entity.get(); nlohmann::json json; - + json["width"] = grid.width; json["height"] = grid.height; json["depth"] = grid.depth; json["cellSize"] = grid.cellSize; json["cellHeight"] = grid.cellHeight; json["generationScript"] = grid.generationScript; - + // Serialize texture rectangles json["floorRectName"] = grid.floorRectName; json["ceilingRectName"] = grid.ceilingRectName; @@ -1480,22 +1573,20 @@ nlohmann::json SceneSerializer::serializeCellGrid(flecs::entity entity) json["roofTopRectName"] = grid.roofTopRectName; json["roofSideRectName"] = grid.roofSideRectName; json["friction"] = grid.friction; - + // Serialize cells nlohmann::json cellsJson = nlohmann::json::array(); - for (const auto& cell : grid.cells) { - cellsJson.push_back({ - {"x", cell.x}, - {"y", cell.y}, - {"z", cell.z}, - {"flags", cell.flags} - }); + for (const auto &cell : grid.cells) { + cellsJson.push_back({ { "x", cell.x }, + { "y", cell.y }, + { "z", cell.z }, + { "flags", cell.flags } }); } json["cells"] = cellsJson; - + // Serialize furniture cells nlohmann::json furnitureJson = nlohmann::json::array(); - for (const auto& furn : grid.furnitureCells) { + for (const auto &furn : grid.furnitureCells) { nlohmann::json furnJson; furnJson["x"] = furn.x; furnJson["y"] = furn.y; @@ -1506,63 +1597,63 @@ nlohmann::json SceneSerializer::serializeCellGrid(flecs::entity entity) furnitureJson.push_back(furnJson); } json["furnitureCells"] = furnitureJson; - + return json; } nlohmann::json SceneSerializer::serializeTown(flecs::entity entity) { - auto& town = entity.get(); + auto &town = entity.get(); nlohmann::json json; - + json["townName"] = town.townName; json["materialName"] = town.materialName; json["proceduralMaterialEntityId"] = town.proceduralMaterialEntityId; json["textureRectName"] = town.textureRectName; - + // Serialize color rects nlohmann::json colorRectsJson = nlohmann::json::object(); - for (const auto& pair : town.colorRects) { + for (const auto &pair : town.colorRects) { colorRectsJson[pair.first] = { - {"left", pair.second.left}, - {"top", pair.second.top}, - {"right", pair.second.right}, - {"bottom", pair.second.bottom}, - {"color", { - {"r", pair.second.color.r}, - {"g", pair.second.color.g}, - {"b", pair.second.color.b}, - {"a", pair.second.color.a} - }} + { "left", pair.second.left }, + { "top", pair.second.top }, + { "right", pair.second.right }, + { "bottom", pair.second.bottom }, + { "color", + { { "r", pair.second.color.r }, + { "g", pair.second.color.g }, + { "b", pair.second.color.b }, + { "a", pair.second.color.a } } } }; } json["colorRects"] = colorRectsJson; - + return json; } nlohmann::json SceneSerializer::serializeDistrict(flecs::entity entity) { - auto& district = entity.get(); + auto &district = entity.get(); nlohmann::json json; - + json["radius"] = district.radius; json["elevation"] = district.elevation; json["height"] = district.height; json["isPlaza"] = district.isPlaza; json["lotTemplateNames"] = district.lotTemplateNames; - json["proceduralMaterialEntityId"] = district.proceduralMaterialEntityId; + json["proceduralMaterialEntityId"] = + district.proceduralMaterialEntityId; json["textureRectName"] = district.textureRectName; json["friction"] = district.friction; - + return json; } nlohmann::json SceneSerializer::serializeLot(flecs::entity entity) { - auto& lot = entity.get(); + auto &lot = entity.get(); nlohmann::json json; - + json["width"] = lot.width; json["depth"] = lot.depth; json["elevation"] = lot.elevation; @@ -1573,19 +1664,19 @@ nlohmann::json SceneSerializer::serializeLot(flecs::entity entity) json["proceduralMaterialEntityId"] = lot.proceduralMaterialEntityId; json["textureRectName"] = lot.textureRectName; json["friction"] = lot.friction; - + return json; } nlohmann::json SceneSerializer::serializeRoom(flecs::entity entity) { - auto& room = entity.get_mut(); - + auto &room = entity.get_mut(); + // Ensure room has a persistent ID room.ensurePersistentId(entity.id()); - + nlohmann::json json; - + json["persistentId"] = room.persistentId; json["minX"] = room.minX; json["minY"] = room.minY; @@ -1602,29 +1693,29 @@ nlohmann::json SceneSerializer::serializeRoom(flecs::entity entity) json["fillRoomWithFurniture"] = room.fillRoomWithFurniture; json["furnitureSeed"] = room.furnitureSeed; json["furnitureYOffset"] = room.furnitureYOffset; - + // Serialize exits (bool array for Z-, Z+, X-, X+) nlohmann::json exits = nlohmann::json::array(); for (int i = 0; i < 4; i++) { exits.push_back(room.exits[i]); } json["exits"] = exits; - + // Serialize connected room persistent IDs (not entity IDs, as they change on save/load) nlohmann::json connections = nlohmann::json::array(); - for (const std::string& id : room.connectedRoomIds) { + for (const std::string &id : room.connectedRoomIds) { connections.push_back(id); } json["connectedRoomIds"] = connections; - + return json; } nlohmann::json SceneSerializer::serializeRoof(flecs::entity entity) { - auto& roof = entity.get(); + auto &roof = entity.get(); nlohmann::json json; - + json["type"] = static_cast(roof.type); json["posX"] = roof.posX; json["posY"] = roof.posY; @@ -1636,15 +1727,15 @@ nlohmann::json SceneSerializer::serializeRoof(flecs::entity entity) json["offsetZ"] = roof.offsetZ; json["baseHeight"] = roof.baseHeight; json["maxHeight"] = roof.maxHeight; - + return json; } nlohmann::json SceneSerializer::serializeFurnitureTemplate(flecs::entity entity) { - auto& furn = entity.get(); + auto &furn = entity.get(); nlohmann::json json; - + json["templateName"] = furn.templateName; json["tags"] = furn.tags; json["meshName"] = furn.meshName; @@ -1656,21 +1747,22 @@ nlohmann::json SceneSerializer::serializeFurnitureTemplate(flecs::entity entity) json["offsetY"] = furn.offsetY; json["offsetZ"] = furn.offsetZ; json["weight"] = furn.weight; - + return json; } -void SceneSerializer::deserializeCellGrid(flecs::entity entity, const nlohmann::json& json) +void SceneSerializer::deserializeCellGrid(flecs::entity entity, + const nlohmann::json &json) { CellGridComponent grid; - + grid.width = json.value("width", 10); grid.height = json.value("height", 1); grid.depth = json.value("depth", 10); grid.cellSize = json.value("cellSize", 4.0f); grid.cellHeight = json.value("cellHeight", 4.0f); grid.generationScript = json.value("generationScript", ""); - + // Deserialize texture rectangles grid.floorRectName = json.value("floorRectName", ""); grid.ceilingRectName = json.value("ceilingRectName", ""); @@ -1683,10 +1775,10 @@ void SceneSerializer::deserializeCellGrid(flecs::entity entity, const nlohmann:: grid.roofTopRectName = json.value("roofTopRectName", ""); grid.roofSideRectName = json.value("roofSideRectName", ""); grid.friction = json.value("friction", 0.5f); - + // Deserialize cells if (json.contains("cells") && json["cells"].is_array()) { - for (const auto& cellJson : json["cells"]) { + for (const auto &cellJson : json["cells"]) { Cell cell; cell.x = cellJson.value("x", 0); cell.y = cellJson.value("y", 0); @@ -1695,18 +1787,21 @@ void SceneSerializer::deserializeCellGrid(flecs::entity entity, const nlohmann:: grid.cells.push_back(cell); } } - + // Deserialize furniture cells - if (json.contains("furnitureCells") && json["furnitureCells"].is_array()) { - for (const auto& furnJson : json["furnitureCells"]) { + if (json.contains("furnitureCells") && + json["furnitureCells"].is_array()) { + for (const auto &furnJson : json["furnitureCells"]) { FurnitureCell furn; furn.x = furnJson.value("x", 0); furn.y = furnJson.value("y", 0); furn.z = furnJson.value("z", 0); - furn.furnitureType = furnJson.value("furnitureType", ""); + furn.furnitureType = + furnJson.value("furnitureType", ""); furn.rotation = furnJson.value("rotation", 0); - if (furnJson.contains("tags") && furnJson["tags"].is_array()) { - for (const auto& tag : furnJson["tags"]) { + if (furnJson.contains("tags") && + furnJson["tags"].is_array()) { + for (const auto &tag : furnJson["tags"]) { if (tag.is_string()) { furn.tags.push_back(tag); } @@ -1715,43 +1810,63 @@ void SceneSerializer::deserializeCellGrid(flecs::entity entity, const nlohmann:: grid.furnitureCells.push_back(furn); } } - + grid.dirty = true; entity.set(grid); } -void SceneSerializer::deserializeTown(flecs::entity entity, const nlohmann::json& json) +void SceneSerializer::deserializeTown(flecs::entity entity, + const nlohmann::json &json) { TownComponent town; - + town.townName = json.value("townName", "New Town"); town.materialName = json.value("materialName", ""); - town.proceduralMaterialEntityId = json.value("proceduralMaterialEntityId", ""); + town.proceduralMaterialEntityId = + json.value("proceduralMaterialEntityId", ""); town.textureRectName = json.value("textureRectName", ""); - + // Resolve material entity reference if (!town.proceduralMaterialEntityId.empty()) { try { - uint64_t matId = std::stoull(town.proceduralMaterialEntityId); - flecs::entity matEntity(m_world, matId); - if (matEntity.is_alive() && matEntity.has()) { - town.proceduralMaterialEntity = matEntity; + uint64_t matId = + std::stoull(town.proceduralMaterialEntityId); + // Look up in entity map to find the corresponding new entity + auto it = m_entityMap.find(matId); + if (it != m_entityMap.end()) { + flecs::entity matEntity = it->second; + if (matEntity.is_alive() && + matEntity.has< + ProceduralMaterialComponent>()) { + town.proceduralMaterialEntity = + matEntity; + } + } else { + // Entity not found in map (might not exist or not loaded yet) + // Create a reference that will be invalid but can be resolved later + flecs::entity matEntity(m_world, matId); + if (matEntity.is_alive() && + matEntity.has< + ProceduralMaterialComponent>()) { + town.proceduralMaterialEntity = + matEntity; + } } } catch (...) { // Invalid ID format, ignore } } - + // Deserialize color rects if (json.contains("colorRects") && json["colorRects"].is_object()) { - for (auto& [name, rectJson] : json["colorRects"].items()) { + for (auto &[name, rectJson] : json["colorRects"].items()) { TownComponent::ColorRect rect; rect.left = rectJson.value("left", 0.0f); rect.top = rectJson.value("top", 0.0f); rect.right = rectJson.value("right", 1.0f); rect.bottom = rectJson.value("bottom", 1.0f); if (rectJson.contains("color")) { - auto& c = rectJson["color"]; + auto &c = rectJson["color"]; rect.color.r = c.value("r", 1.0f); rect.color.g = c.value("g", 1.0f); rect.color.b = c.value("b", 1.0f); @@ -1760,53 +1875,75 @@ void SceneSerializer::deserializeTown(flecs::entity entity, const nlohmann::json town.colorRects[name] = rect; } } - + town.dirty = true; town.materialDirty = true; entity.set(town); } -void SceneSerializer::deserializeDistrict(flecs::entity entity, const nlohmann::json& json) +void SceneSerializer::deserializeDistrict(flecs::entity entity, + const nlohmann::json &json) { DistrictComponent district; - + district.radius = json.value("radius", 50.0f); district.elevation = json.value("elevation", 0.0f); district.height = json.value("height", 0.2f); district.isPlaza = json.value("isPlaza", false); - district.proceduralMaterialEntityId = json.value("proceduralMaterialEntityId", ""); + district.proceduralMaterialEntityId = + json.value("proceduralMaterialEntityId", ""); district.textureRectName = json.value("textureRectName", ""); district.friction = json.value("friction", 0.5f); - + // Resolve material entity reference if (!district.proceduralMaterialEntityId.empty()) { try { - uint64_t matId = std::stoull(district.proceduralMaterialEntityId); - flecs::entity matEntity(m_world, matId); - if (matEntity.is_alive() && matEntity.has()) { - district.proceduralMaterialEntity = matEntity; + uint64_t matId = std::stoull( + district.proceduralMaterialEntityId); + // Look up in entity map to find the corresponding new entity + auto it = m_entityMap.find(matId); + if (it != m_entityMap.end()) { + flecs::entity matEntity = it->second; + if (matEntity.is_alive() && + matEntity.has< + ProceduralMaterialComponent>()) { + district.proceduralMaterialEntity = + matEntity; + } + } else { + // Entity not found in map (might not exist or not loaded yet) + // Create a reference that will be invalid but can be resolved later + flecs::entity matEntity(m_world, matId); + if (matEntity.is_alive() && + matEntity.has< + ProceduralMaterialComponent>()) { + district.proceduralMaterialEntity = + matEntity; + } } } catch (...) { // Invalid ID format, ignore } } - - if (json.contains("lotTemplateNames") && json["lotTemplateNames"].is_array()) { - for (const auto& name : json["lotTemplateNames"]) { + + if (json.contains("lotTemplateNames") && + json["lotTemplateNames"].is_array()) { + for (const auto &name : json["lotTemplateNames"]) { if (name.is_string()) { district.lotTemplateNames.push_back(name); } } } - + district.dirty = true; entity.set(district); } -void SceneSerializer::deserializeLot(flecs::entity entity, const nlohmann::json& json) +void SceneSerializer::deserializeLot(flecs::entity entity, + const nlohmann::json &json) { LotComponent lot; - + lot.width = json.value("width", 10); lot.depth = json.value("depth", 10); lot.elevation = json.value("elevation", 0.0f); @@ -1814,37 +1951,57 @@ void SceneSerializer::deserializeLot(flecs::entity entity, const nlohmann::json& lot.offsetX = json.value("offsetX", 0.0f); lot.offsetZ = json.value("offsetZ", 0.0f); lot.templateName = json.value("templateName", ""); - lot.proceduralMaterialEntityId = json.value("proceduralMaterialEntityId", ""); + lot.proceduralMaterialEntityId = + json.value("proceduralMaterialEntityId", ""); lot.textureRectName = json.value("textureRectName", ""); lot.friction = json.value("friction", 0.5f); - + // Resolve material entity reference if (!lot.proceduralMaterialEntityId.empty()) { try { - uint64_t matId = std::stoull(lot.proceduralMaterialEntityId); - flecs::entity matEntity(m_world, matId); - if (matEntity.is_alive() && matEntity.has()) { - lot.proceduralMaterialEntity = matEntity; + uint64_t matId = + std::stoull(lot.proceduralMaterialEntityId); + // Look up in entity map to find the corresponding new entity + auto it = m_entityMap.find(matId); + if (it != m_entityMap.end()) { + flecs::entity matEntity = it->second; + if (matEntity.is_alive() && + matEntity.has< + ProceduralMaterialComponent>()) { + lot.proceduralMaterialEntity = + matEntity; + } + } else { + // Entity not found in map (might not exist or not loaded yet) + // Create a reference that will be invalid but can be resolved later + flecs::entity matEntity(m_world, matId); + if (matEntity.is_alive() && + matEntity.has< + ProceduralMaterialComponent>()) { + lot.proceduralMaterialEntity = + matEntity; + } } } catch (...) { // Invalid ID format, ignore } } - + lot.dirty = true; entity.set(lot); } -void SceneSerializer::deserializeRoom(flecs::entity entity, const nlohmann::json& json) +void SceneSerializer::deserializeRoom(flecs::entity entity, + const nlohmann::json &json) { RoomComponent room; - + // Load persistent ID (auto-generate if not present for backward compatibility) room.persistentId = json.value("persistentId", ""); if (room.persistentId.empty()) { room.ensurePersistentId(entity.id()); } - + room.minX = json.value("minX", 0); room.minY = json.value("minY", 0); room.minZ = json.value("minZ", 0); @@ -1859,47 +2016,51 @@ void SceneSerializer::deserializeRoom(flecs::entity entity, const nlohmann::json room.fillRoomWithFurniture = json.value("fillRoomWithFurniture", false); room.furnitureSeed = json.value("furnitureSeed", 42u); room.furnitureYOffset = json.value("furnitureYOffset", 0.05f); - + // Load exits (bool array for Z-, Z+, X-, X+) if (json.contains("exits") && json["exits"].is_array()) { int i = 0; - for (const auto& exitVal : json["exits"]) { - if (i >= 4) break; + for (const auto &exitVal : json["exits"]) { + if (i >= 4) + break; if (exitVal.is_boolean()) { room.exits[i] = exitVal.get(); } i++; } } - + if (json.contains("tags") && json["tags"].is_array()) { - for (const auto& tag : json["tags"]) { + for (const auto &tag : json["tags"]) { if (tag.is_string()) { room.tags.push_back(tag); } } } - + // Load connected rooms using persistent IDs - if (json.contains("connectedRoomIds") && json["connectedRoomIds"].is_array()) { + if (json.contains("connectedRoomIds") && + json["connectedRoomIds"].is_array()) { // New format: persistent IDs - for (const auto& id : json["connectedRoomIds"]) { + for (const auto &id : json["connectedRoomIds"]) { if (id.is_string()) { - room.connectedRoomIds.push_back(id.get()); + room.connectedRoomIds.push_back( + id.get()); } } } // Backward compatibility: old format used entity IDs which are invalid after reload // We skip loading these as they would be garbage - + room.markDirty(); entity.set(room); } -void SceneSerializer::deserializeRoof(flecs::entity entity, const nlohmann::json& json) +void SceneSerializer::deserializeRoof(flecs::entity entity, + const nlohmann::json &json) { RoofComponent roof; - + roof.type = static_cast(json.value("type", 0)); roof.posX = json.value("posX", 0); roof.posY = json.value("posY", 0); @@ -1911,14 +2072,15 @@ void SceneSerializer::deserializeRoof(flecs::entity entity, const nlohmann::json roof.offsetZ = json.value("offsetZ", 0.0f); roof.baseHeight = json.value("baseHeight", 0.5f); roof.maxHeight = json.value("maxHeight", 0.5f); - + entity.set(roof); } -void SceneSerializer::deserializeFurnitureTemplate(flecs::entity entity, const nlohmann::json& json) +void SceneSerializer::deserializeFurnitureTemplate(flecs::entity entity, + const nlohmann::json &json) { FurnitureTemplateComponent furn; - + furn.templateName = json.value("templateName", ""); furn.meshName = json.value("meshName", ""); furn.materialName = json.value("materialName", ""); @@ -1929,23 +2091,23 @@ void SceneSerializer::deserializeFurnitureTemplate(flecs::entity entity, const n furn.offsetY = json.value("offsetY", 0.0f); furn.offsetZ = json.value("offsetZ", 0.0f); furn.weight = json.value("weight", 1.0f); - + if (json.contains("tags") && json["tags"].is_array()) { - for (const auto& tag : json["tags"]) { + for (const auto &tag : json["tags"]) { if (tag.is_string()) { furn.tags.push_back(tag); } } } - + entity.set(furn); } nlohmann::json SceneSerializer::serializeClearArea(flecs::entity entity) { - auto& clearArea = entity.get(); + auto &clearArea = entity.get(); nlohmann::json json; - + json["minX"] = clearArea.minX; json["minY"] = clearArea.minY; json["minZ"] = clearArea.minZ; @@ -1956,14 +2118,15 @@ nlohmann::json SceneSerializer::serializeClearArea(flecs::entity entity) json["clearFurniture"] = clearArea.clearFurniture; json["clearRoofs"] = clearArea.clearRoofs; json["clearRooms"] = clearArea.clearRooms; - + return json; } -void SceneSerializer::deserializeClearArea(flecs::entity entity, const nlohmann::json& json) +void SceneSerializer::deserializeClearArea(flecs::entity entity, + const nlohmann::json &json) { ClearAreaComponent clearArea; - + clearArea.minX = json.value("minX", 0); clearArea.minY = json.value("minY", 0); clearArea.minZ = json.value("minZ", 0); @@ -1974,12 +2137,11 @@ void SceneSerializer::deserializeClearArea(flecs::entity entity, const nlohmann: clearArea.clearFurniture = json.value("clearFurniture", true); clearArea.clearRoofs = json.value("clearRoofs", false); clearArea.clearRooms = json.value("clearRooms", false); - + clearArea.markDirty(); entity.set(clearArea); } - nlohmann::json SceneSerializer::serializeStartupMenu(flecs::entity entity) { auto &sm = entity.get(); @@ -2028,15 +2190,15 @@ void SceneSerializer::deserializePlayerController(flecs::entity entity, { PlayerControllerComponent pc; pc.cameraMode = json.value("cameraMode", pc.cameraMode); - pc.targetCharacterName = json.value("targetCharacterName", - pc.targetCharacterName); + pc.targetCharacterName = + json.value("targetCharacterName", pc.targetCharacterName); pc.fpsBoneName = json.value("fpsBoneName", pc.fpsBoneName); pc.tpsDistance = json.value("tpsDistance", pc.tpsDistance); pc.tpsHeight = json.value("tpsHeight", pc.tpsHeight); - pc.mouseSensitivity = json.value("mouseSensitivity", - pc.mouseSensitivity); - pc.locomotionStateMachine = json.value("locomotionStateMachine", - pc.locomotionStateMachine); + pc.mouseSensitivity = + json.value("mouseSensitivity", pc.mouseSensitivity); + pc.locomotionStateMachine = + json.value("locomotionStateMachine", pc.locomotionStateMachine); pc.idleState = json.value("idleState", pc.idleState); pc.walkState = json.value("walkState", pc.walkState); pc.runState = json.value("runState", pc.runState); diff --git a/src/features/editScene/ui/ProceduralMaterialEditor.cpp b/src/features/editScene/ui/ProceduralMaterialEditor.cpp index df54e91..2b10dd9 100644 --- a/src/features/editScene/ui/ProceduralMaterialEditor.cpp +++ b/src/features/editScene/ui/ProceduralMaterialEditor.cpp @@ -2,170 +2,207 @@ #include "../components/ProceduralTexture.hpp" #include -ProceduralMaterialEditor::ProceduralMaterialEditor(Ogre::SceneManager* sceneMgr) +ProceduralMaterialEditor::ProceduralMaterialEditor(Ogre::SceneManager *sceneMgr) : m_sceneMgr(sceneMgr) { } -void ProceduralMaterialEditor::renderTextureSelector(flecs::entity entity, ProceduralMaterialComponent &material) +void ProceduralMaterialEditor::renderTextureSelector( + flecs::entity entity, ProceduralMaterialComponent &material) { // Get the world from the entity flecs::world world = entity.world(); - + // Collect all entities with ProceduralTextureComponent std::vector textureEntities; std::vector textureNames; std::vector displayNames; - + int currentIndex = -1; int noneIndex = 0; - + // Add "None" option displayNames.push_back("None"); textureEntities.push_back(flecs::entity::null()); textureNames.push_back(""); - - world.query().each([&](flecs::entity e, ProceduralTextureComponent& texture) { - textureEntities.push_back(e); - - std::string name = texture.textureName.empty() ? - "Texture " + std::to_string(e.id()) : texture.textureName; - textureNames.push_back(name); - - // Build display name - std::string display = name; - if (texture.generated) { - display += " (generated)"; - } else { - display += " (pending)"; - } - displayNames.push_back(display); - - if (material.diffuseTextureEntity == e) { - currentIndex = (int)textureEntities.size() - 1; - } - }); - + + world.query().each( + [&](flecs::entity e, ProceduralTextureComponent &texture) { + textureEntities.push_back(e); + + std::string name = + texture.textureName.empty() ? + "Texture " + std::to_string(e.id()) : + texture.textureName; + textureNames.push_back(name); + + // Build display name + std::string display = name; + if (texture.generated) { + display += " (generated)"; + } else { + display += " (pending)"; + } + displayNames.push_back(display); + + if (material.diffuseTextureEntity == e) { + currentIndex = (int)textureEntities.size() - 1; + } + }); + // Default to none if not found if (currentIndex == -1) { currentIndex = noneIndex; } - + // Build combo string std::string comboItems; for (size_t i = 0; i < displayNames.size(); ++i) { - if (i > 0) comboItems += '\0'; + if (i > 0) + comboItems += '\0'; comboItems += displayNames[i]; } comboItems += '\0'; - + int newIndex = currentIndex; if (ImGui::Combo("Diffuse Texture", &newIndex, comboItems.c_str())) { if (newIndex >= 0 && newIndex < (int)textureEntities.size()) { if (newIndex == noneIndex) { - material.diffuseTextureEntity = flecs::entity::null(); + material.diffuseTextureEntity = + flecs::entity::null(); + material.diffuseTextureId = ""; } else { - material.diffuseTextureEntity = textureEntities[newIndex]; + material.diffuseTextureEntity = + textureEntities[newIndex]; + // Also store the persistent texture ID + if (material.diffuseTextureEntity.is_alive() && + material.diffuseTextureEntity + .has()) { + const auto &texture = + material.diffuseTextureEntity.get< + ProceduralTextureComponent>(); + material.diffuseTextureId = + texture.textureId; + } else { + material.diffuseTextureId = ""; + } } material.markDirty(); } } - + // Show warning if no textures available if (textureEntities.size() <= 1) { - ImGui::TextColored(ImVec4(1, 0.5f, 0, 1), "Create a Procedural Texture first!"); + ImGui::TextColored(ImVec4(1, 0.5f, 0, 1), + "Create a Procedural Texture first!"); } } -bool ProceduralMaterialEditor::renderComponent(flecs::entity entity, ProceduralMaterialComponent &material) +bool ProceduralMaterialEditor::renderComponent( + flecs::entity entity, ProceduralMaterialComponent &material) { bool modified = false; - - if (ImGui::CollapsingHeader("Procedural Material", ImGuiTreeNodeFlags_DefaultOpen)) { + + if (ImGui::CollapsingHeader("Procedural Material", + ImGuiTreeNodeFlags_DefaultOpen)) { ImGui::Indent(); - + // Material info if (material.created) { - ImGui::TextColored(ImVec4(0, 1, 0, 1), "Status: Created"); - ImGui::Text("Material: %s", material.materialName.c_str()); + ImGui::TextColored(ImVec4(0, 1, 0, 1), + "Status: Created"); + ImGui::Text("Material: %s", + material.materialName.c_str()); } else if (material.dirty) { - ImGui::TextColored(ImVec4(1, 1, 0, 1), "Status: Needs Creation"); + ImGui::TextColored(ImVec4(1, 1, 0, 1), + "Status: Needs Creation"); } else { - ImGui::TextColored(ImVec4(0.5f, 0.5f, 0.5f, 1), "Status: Not Created"); + ImGui::TextColored(ImVec4(0.5f, 0.5f, 0.5f, 1), + "Status: Not Created"); } - + ImGui::Separator(); - + // Material name char nameBuf[256]; - strncpy(nameBuf, material.materialName.c_str(), sizeof(nameBuf) - 1); + strncpy(nameBuf, material.materialName.c_str(), + sizeof(nameBuf) - 1); nameBuf[sizeof(nameBuf) - 1] = '\0'; - - if (ImGui::InputText("Material Name", nameBuf, sizeof(nameBuf))) { + + if (ImGui::InputText("Material Name", nameBuf, + sizeof(nameBuf))) { material.materialName = nameBuf; material.markDirty(); modified = true; } ImGui::TextDisabled("(Leave empty for auto-generated name)"); - + ImGui::Separator(); - + // Diffuse texture selector renderTextureSelector(entity, material); - + ImGui::Separator(); - + // Material colors ImGui::Text("Material Properties:"); - + if (ImGui::ColorEdit3("Ambient", material.ambient)) { material.markDirty(); modified = true; } - + if (ImGui::ColorEdit3("Diffuse Multiplier", material.diffuse)) { material.markDirty(); modified = true; } - + if (ImGui::ColorEdit3("Specular", material.specular)) { material.markDirty(); modified = true; } - - if (ImGui::SliderFloat("Shininess", &material.shininess, 1.0f, 128.0f)) { + + if (ImGui::SliderFloat("Shininess", &material.shininess, 1.0f, + 128.0f)) { material.markDirty(); modified = true; } - - if (ImGui::SliderFloat("Roughness", &material.roughness, 0.0f, 1.0f)) { + + if (ImGui::SliderFloat("Roughness", &material.roughness, 0.0f, + 1.0f)) { material.markDirty(); modified = true; } - + ImGui::Separator(); - + // Recreate button if (ImGui::Button("Recreate Material")) { material.markDirty(); modified = true; } - + ImGui::SameLine(); - + // Reset to defaults if (ImGui::Button("Reset to Defaults")) { - material.ambient[0] = 0.2f; material.ambient[1] = 0.2f; material.ambient[2] = 0.2f; - material.diffuse[0] = 1.0f; material.diffuse[1] = 1.0f; material.diffuse[2] = 1.0f; - material.specular[0] = 0.0f; material.specular[1] = 0.0f; material.specular[2] = 0.0f; + material.ambient[0] = 0.2f; + material.ambient[1] = 0.2f; + material.ambient[2] = 0.2f; + material.diffuse[0] = 1.0f; + material.diffuse[1] = 1.0f; + material.diffuse[2] = 1.0f; + material.specular[0] = 0.0f; + material.specular[1] = 0.0f; + material.specular[2] = 0.0f; material.shininess = 32.0f; material.roughness = 0.5f; material.markDirty(); modified = true; } - + ImGui::Unindent(); } - + return modified; }