diff --git a/src/features/editScene/CMakeLists.txt b/src/features/editScene/CMakeLists.txt index 5dc2638..ef95662 100644 --- a/src/features/editScene/CMakeLists.txt +++ b/src/features/editScene/CMakeLists.txt @@ -25,6 +25,7 @@ set(EDITSCENE_SOURCES systems/CellGridSystem.cpp systems/RoomLayoutSystem.cpp systems/FurnitureLibrary.cpp + systems/CharacterSlotSystem.cpp ui/TransformEditor.cpp ui/RenderableEditor.cpp ui/PhysicsColliderEditor.cpp @@ -39,6 +40,7 @@ set(EDITSCENE_SOURCES ui/ProceduralMaterialEditor.cpp ui/PrimitiveEditor.cpp ui/TriangleBufferEditor.cpp + ui/CharacterSlotsEditor.cpp ui/CellGridEditor.cpp ui/LotEditor.cpp ui/DistrictEditor.cpp @@ -58,6 +60,7 @@ set(EDITSCENE_SOURCES components/ProceduralMaterialModule.cpp components/PrimitiveModule.cpp components/TriangleBufferModule.cpp + components/CharacterSlotsModule.cpp components/CellGridModule.cpp components/CellGridEditorsModule.cpp components/CellGrid.cpp @@ -84,6 +87,7 @@ set(EDITSCENE_HEADERS components/ProceduralMaterial.hpp components/Primitive.hpp components/TriangleBuffer.hpp + components/CharacterSlots.hpp components/CellGrid.hpp systems/EditorUISystem.hpp systems/CellGridSystem.hpp @@ -91,6 +95,7 @@ set(EDITSCENE_HEADERS systems/FurnitureLibrary.hpp systems/ProceduralMaterialSystem.hpp systems/ProceduralMeshSystem.hpp + systems/CharacterSlotSystem.hpp systems/ProceduralTextureSystem.hpp systems/StaticGeometrySystem.hpp systems/SceneSerializer.hpp @@ -115,6 +120,7 @@ set(EDITSCENE_HEADERS ui/ProceduralMaterialEditor.hpp ui/PrimitiveEditor.hpp ui/TriangleBufferEditor.hpp + ui/CharacterSlotsEditor.hpp ui/CellGridEditor.hpp ui/LotEditor.hpp ui/DistrictEditor.hpp diff --git a/src/features/editScene/EditorApp.cpp b/src/features/editScene/EditorApp.cpp index 5528005..804f990 100644 --- a/src/features/editScene/EditorApp.cpp +++ b/src/features/editScene/EditorApp.cpp @@ -9,6 +9,7 @@ #include "systems/ProceduralTextureSystem.hpp" #include "systems/ProceduralMaterialSystem.hpp" #include "systems/ProceduralMeshSystem.hpp" +#include "systems/CharacterSlotSystem.hpp" #include "systems/CellGridSystem.hpp" #include "systems/RoomLayoutSystem.hpp" #include "camera/EditorCamera.hpp" @@ -29,6 +30,7 @@ #include "components/ProceduralMaterial.hpp" #include "components/Primitive.hpp" #include "components/TriangleBuffer.hpp" +#include "components/CharacterSlots.hpp" #include "components/CellGrid.hpp" #include "components/CellGridModule.hpp" #include @@ -119,6 +121,7 @@ EditorApp::~EditorApp() } // Release all systems + m_characterSlotSystem.reset(); m_proceduralMeshSystem.reset(); m_proceduralMaterialSystem.reset(); m_proceduralTextureSystem.reset(); @@ -223,6 +226,11 @@ void EditorApp::setup() m_world, m_sceneMgr); m_proceduralMeshSystem->initialize(); + // Setup CharacterSlot system + m_characterSlotSystem = std::make_unique( + m_world, m_sceneMgr); + m_characterSlotSystem->initialize(); + // Setup CellGrid system m_cellGridSystem = std::make_unique(m_world, m_sceneMgr); @@ -289,6 +297,9 @@ void EditorApp::setupECS() m_world.component(); m_world.component(); + // Register CharacterSlots component + m_world.component(); + // Register CellGrid/Town components CellGridModule::registerComponents(m_world); } @@ -449,6 +460,11 @@ bool EditorApp::frameRenderingQueued(const Ogre::FrameEvent &evt) m_proceduralMaterialSystem->update(); } + // Update CharacterSlot system + if (m_characterSlotSystem) { + m_characterSlotSystem->update(); + } + // Update ProceduralMesh system if (m_proceduralMeshSystem) { m_proceduralMeshSystem->update(); diff --git a/src/features/editScene/EditorApp.hpp b/src/features/editScene/EditorApp.hpp index 5e2663c..bfe854c 100644 --- a/src/features/editScene/EditorApp.hpp +++ b/src/features/editScene/EditorApp.hpp @@ -21,6 +21,7 @@ class StaticGeometrySystem; class ProceduralTextureSystem; class ProceduralMaterialSystem; class ProceduralMeshSystem; +class CharacterSlotSystem; class CellGridSystem; class RoomLayoutSystem; @@ -116,6 +117,7 @@ private: std::unique_ptr m_proceduralTextureSystem; std::unique_ptr m_proceduralMaterialSystem; std::unique_ptr m_proceduralMeshSystem; + std::unique_ptr m_characterSlotSystem; std::unique_ptr m_cellGridSystem; std::unique_ptr m_roomLayoutSystem; diff --git a/src/features/editScene/components/CharacterSlots.hpp b/src/features/editScene/components/CharacterSlots.hpp new file mode 100644 index 0000000..914b9be --- /dev/null +++ b/src/features/editScene/components/CharacterSlots.hpp @@ -0,0 +1,20 @@ +#ifndef EDITSCENE_CHARACTERSLOTS_HPP +#define EDITSCENE_CHARACTERSLOTS_HPP +#pragma once +#include +#include + +/** + * Multi-slot mesh component for character parts sharing a skeleton. + * The "face" slot (or first available slot) serves as the master skeleton. + */ +struct CharacterSlotsComponent { + Ogre::String age = "adult"; + Ogre::String sex = "male"; + std::unordered_map slots; + bool dirty = true; + + CharacterSlotsComponent() = default; +}; + +#endif // EDITSCENE_CHARACTERSLOTS_HPP diff --git a/src/features/editScene/components/CharacterSlotsModule.cpp b/src/features/editScene/components/CharacterSlotsModule.cpp new file mode 100644 index 0000000..999bc13 --- /dev/null +++ b/src/features/editScene/components/CharacterSlotsModule.cpp @@ -0,0 +1,35 @@ +#include "CharacterSlots.hpp" +#include "../ui/ComponentRegistration.hpp" +#include "../ui/CharacterSlotsEditor.hpp" +#include "../systems/CharacterSlotSystem.hpp" +#include "Transform.hpp" + +REGISTER_COMPONENT_GROUP("Character Slots", "Rendering", + CharacterSlotsComponent, CharacterSlotsEditor) +{ + CharacterSlotSystem::loadCatalog(); + + registry.registerComponent( + "Character Slots", + "Rendering", + std::make_unique(sceneMgr), + /* Adder */ + [sceneMgr](flecs::entity e) { + if (!e.has()) { + TransformComponent transform; + transform.node = + sceneMgr->getRootSceneNode() + ->createChildSceneNode(); + e.set(transform); + } + CharacterSlotsComponent cs; + cs.dirty = true; + e.set(cs); + }, + /* Remover */ + [sceneMgr](flecs::entity e) { + (void)sceneMgr; + e.remove(); + } + ); +} diff --git a/src/features/editScene/systems/CharacterSlotSystem.cpp b/src/features/editScene/systems/CharacterSlotSystem.cpp new file mode 100644 index 0000000..2863b88 --- /dev/null +++ b/src/features/editScene/systems/CharacterSlotSystem.cpp @@ -0,0 +1,273 @@ +#include "CharacterSlotSystem.hpp" +#include "../components/Transform.hpp" +#include +#include +#include +#include +#include +#include + +bool CharacterSlotSystem::s_catalogLoaded = false; +nlohmann::json CharacterSlotSystem::s_bodyParts = nlohmann::json::object(); +std::set CharacterSlotSystem::s_meshNames; + +CharacterSlotSystem::CharacterSlotSystem(flecs::world &world, + Ogre::SceneManager *sceneMgr) + : m_world(world) + , m_sceneMgr(sceneMgr) +{ + m_world.observer("CharacterSlotsCleanup") + .event(flecs::OnRemove) + .each([this](flecs::entity e, CharacterSlotsComponent &) { + destroyCharacterParts(e); + }); +} + +CharacterSlotSystem::~CharacterSlotSystem() +{ + std::vector toRemove; + for (auto &pair : m_entities) + toRemove.push_back(pair.first); + for (auto id : toRemove) + destroyCharacterParts(m_world.entity(id)); +} + +void CharacterSlotSystem::initialize() +{ + if (m_initialized) + return; + loadCatalog(); + m_initialized = true; +} + +void CharacterSlotSystem::loadCatalog() +{ + if (s_catalogLoaded) + return; + + s_bodyParts = nlohmann::json::object(); + s_meshNames.clear(); + + Ogre::ResourceGroupManager &rgm = + Ogre::ResourceGroupManager::getSingleton(); + Ogre::StringVector groups = rgm.getResourceGroups(); + std::vector partNames; + + for (const auto &group : groups) { + Ogre::StringVectorPtr names = + rgm.findResourceNames(group, "body_part_*.json"); + if (names) + partNames.insert(partNames.end(), names->begin(), + names->end()); + } + + for (const auto &name : partNames) { + Ogre::String group = rgm.findGroupContainingResource(name); + Ogre::DataStreamPtr stream = rgm.openResource(name, group); + Ogre::String jsonStr = stream->getAsString(); + try { + nlohmann::json jdata = nlohmann::json::parse(jsonStr); + if (!jdata.contains("age") || !jdata.contains("sex") || + !jdata.contains("slot") || !jdata.contains("mesh")) + continue; + + Ogre::String age = jdata["age"].get(); + Ogre::String sex = jdata["sex"].get(); + Ogre::String slot = jdata["slot"].get(); + Ogre::String mesh = jdata["mesh"].get(); + + if (!s_bodyParts.contains(age)) + s_bodyParts[age] = nlohmann::json::object(); + if (!s_bodyParts[age].contains(sex)) + s_bodyParts[age][sex] = nlohmann::json::object(); + if (!s_bodyParts[age][sex].contains(slot)) + s_bodyParts[age][sex][slot] = + nlohmann::json::array(); + s_bodyParts[age][sex][slot].push_back(mesh); + s_meshNames.insert(mesh); + + /* Preload mesh into Characters group */ + try { + Ogre::MeshManager::getSingleton().load(mesh, + "Characters"); + } catch (...) { + } + } catch (...) { + continue; + } + } + + s_catalogLoaded = true; +} + +bool CharacterSlotSystem::isCatalogLoaded() +{ + return s_catalogLoaded; +} + +std::vector CharacterSlotSystem::getAges() +{ + std::vector ages; + if (!s_catalogLoaded) + return ages; + for (auto &el : s_bodyParts.items()) + ages.push_back(el.key()); + return ages; +} + +std::vector CharacterSlotSystem::getSexes( + const Ogre::String &age) +{ + std::vector sexes; + if (!s_catalogLoaded || !s_bodyParts.contains(age)) + return sexes; + for (auto &el : s_bodyParts[age].items()) + sexes.push_back(el.key()); + return sexes; +} + +std::vector CharacterSlotSystem::getSlots( + const Ogre::String &age, const Ogre::String &sex) +{ + std::vector slots; + if (!s_catalogLoaded || !s_bodyParts.contains(age) || + !s_bodyParts[age].contains(sex)) + return slots; + for (auto &el : s_bodyParts[age][sex].items()) + slots.push_back(el.key()); + return slots; +} + +std::vector CharacterSlotSystem::getMeshes( + const Ogre::String &age, const Ogre::String &sex, + const Ogre::String &slot) +{ + std::vector meshes; + if (!s_catalogLoaded || !s_bodyParts.contains(age) || + !s_bodyParts[age].contains(sex) || + !s_bodyParts[age][sex].contains(slot)) + return meshes; + for (auto &m : s_bodyParts[age][sex][slot]) + meshes.push_back(m.get()); + return meshes; +} + +void CharacterSlotSystem::update() +{ + if (!m_initialized) + return; + + m_world.query().each( + [this](flecs::entity e, CharacterSlotsComponent &cs) { + if (cs.dirty) { + std::cout << "CharacterSlotSystem: building entity " + << e.id() << std::endl; + buildCharacter(e, cs); + cs.dirty = false; + } + }); +} + +void CharacterSlotSystem::buildCharacter(flecs::entity e, + CharacterSlotsComponent &cs) +{ + std::cout << "CharacterSlotSystem::buildCharacter: entity=" << e.id() + << " age=" << cs.age << " sex=" << cs.sex + << " slots=" << cs.slots.size() << std::endl; + + destroyCharacterParts(e); + + if (!e.has()) { + std::cout << " no TransformComponent" << std::endl; + return; + } + + auto &transform = e.get_mut(); + if (!transform.node) { + std::cout << " transform.node is null" << std::endl; + return; + } + + /* Determine master slot (face preferred, else first non-empty) */ + Ogre::String masterSlot; + if (cs.slots.find("face") != cs.slots.end() && + !cs.slots.at("face").empty()) { + masterSlot = "face"; + } else { + for (const auto &pair : cs.slots) { + if (!pair.second.empty()) { + masterSlot = pair.first; + break; + } + } + } + + if (masterSlot.empty()) { + std::cout << " masterSlot empty" << std::endl; + return; + } + + std::cout << " masterSlot=" << masterSlot + << " mesh=" << cs.slots.at(masterSlot) << std::endl; + + Ogre::Entity *masterEnt = nullptr; + try { + masterEnt = m_sceneMgr->createEntity(cs.slots.at(masterSlot)); + transform.node->attachObject(masterEnt); + m_entities[e.id()].parts[masterSlot] = masterEnt; + std::cout << " master loaded: " << masterEnt->getName() + << std::endl; + } catch (const Ogre::Exception &ex) { + std::cout << " FAILED to load master mesh: " + << ex.getDescription() << std::endl; + Ogre::LogManager::getSingleton().logMessage( + "CharacterSlotSystem: Failed to load master mesh '" + + cs.slots.at(masterSlot) + "': " + ex.getDescription()); + return; + } + + for (const auto &pair : cs.slots) { + const Ogre::String &slot = pair.first; + const Ogre::String &mesh = pair.second; + + if (slot == masterSlot || mesh.empty()) + continue; + + try { + Ogre::Entity *partEnt = m_sceneMgr->createEntity(mesh); + partEnt->shareSkeletonInstanceWith(masterEnt); + transform.node->attachObject(partEnt); + m_entities[e.id()].parts[slot] = partEnt; + std::cout << " part loaded: " << slot << "=" + << partEnt->getName() << std::endl; + } catch (const Ogre::Exception &ex) { + std::cout << " FAILED to load part " << slot + << ": " << ex.getDescription() << std::endl; + Ogre::LogManager::getSingleton().logMessage( + "CharacterSlotSystem: Failed to load part '" + + slot + "' mesh '" + mesh + + "': " + ex.getDescription()); + } + } +} + +void CharacterSlotSystem::destroyCharacterParts(flecs::entity e) +{ + auto it = m_entities.find(e.id()); + if (it == m_entities.end()) + return; + + for (auto &pair : it->second.parts) { + Ogre::Entity *ent = pair.second; + if (ent) { + try { + ent->detachFromParent(); + m_sceneMgr->destroyEntity(ent); + } catch (...) { + } + } + } + + it->second.parts.clear(); + m_entities.erase(it); +} diff --git a/src/features/editScene/systems/CharacterSlotSystem.hpp b/src/features/editScene/systems/CharacterSlotSystem.hpp new file mode 100644 index 0000000..6b98848 --- /dev/null +++ b/src/features/editScene/systems/CharacterSlotSystem.hpp @@ -0,0 +1,56 @@ +#ifndef EDITSCENE_CHARACTERSLOTSYSTEM_HPP +#define EDITSCENE_CHARACTERSLOTSYSTEM_HPP +#pragma once + +#include +#include +#include +#include +#include +#include + +#include "../components/CharacterSlots.hpp" + +/** + * System that manages multi-slot character meshes with shared skeleton. + * Loads body part catalog from body_part_*.json files and creates/updates + * Ogre entities for each slot, sharing the skeleton from the master slot. + */ +class CharacterSlotSystem { +public: + CharacterSlotSystem(flecs::world &world, Ogre::SceneManager *sceneMgr); + ~CharacterSlotSystem(); + + void initialize(); + void update(); + + /* Static catalog access */ + static void loadCatalog(); + static bool isCatalogLoaded(); + static std::vector getAges(); + static std::vector getSexes(const Ogre::String &age); + static std::vector getSlots(const Ogre::String &age, + const Ogre::String &sex); + static std::vector getMeshes(const Ogre::String &age, + const Ogre::String &sex, + const Ogre::String &slot); + +private: + static bool s_catalogLoaded; + static nlohmann::json s_bodyParts; + static std::set s_meshNames; + + void buildCharacter(flecs::entity e, CharacterSlotsComponent &cs); + void destroyCharacterParts(flecs::entity e); + + flecs::world &m_world; + Ogre::SceneManager *m_sceneMgr; + bool m_initialized = false; + + struct PartEntities { + std::unordered_map parts; + }; + std::unordered_map m_entities; +}; + +#endif // EDITSCENE_CHARACTERSLOTSYSTEM_HPP diff --git a/src/features/editScene/systems/EditorUISystem.cpp b/src/features/editScene/systems/EditorUISystem.cpp index 9fdbee5..6166f03 100644 --- a/src/features/editScene/systems/EditorUISystem.cpp +++ b/src/features/editScene/systems/EditorUISystem.cpp @@ -16,6 +16,7 @@ #include "../components/Primitive.hpp" #include "../components/TriangleBuffer.hpp" #include "../components/LodSettings.hpp" +#include "../components/CharacterSlots.hpp" #include "../components/CellGrid.hpp" #include "../ui/TransformEditor.hpp" #include "../ui/RenderableEditor.hpp" @@ -624,6 +625,13 @@ void EditorUISystem::renderComponentList(flecs::entity entity) m_componentRegistry.render(entity, tb); componentCount++; } + + // Render CharacterSlots if present + if (entity.has()) { + auto &cs = entity.get_mut(); + m_componentRegistry.render(entity, cs); + componentCount++; + } // Render CellGrid if present if (entity.has()) { diff --git a/src/features/editScene/systems/SceneSerializer.cpp b/src/features/editScene/systems/SceneSerializer.cpp index 89dcc5e..b130a65 100644 --- a/src/features/editScene/systems/SceneSerializer.cpp +++ b/src/features/editScene/systems/SceneSerializer.cpp @@ -15,6 +15,7 @@ #include "../components/ProceduralMaterial.hpp" #include "../components/Primitive.hpp" #include "../components/TriangleBuffer.hpp" +#include "../components/CharacterSlots.hpp" #include "../components/CellGrid.hpp" #include "../components/GeneratedPhysicsTag.hpp" #include "EditorUISystem.hpp" @@ -177,6 +178,10 @@ nlohmann::json SceneSerializer::serializeEntity(flecs::entity entity) if (entity.has()) { json["triangleBuffer"] = serializeTriangleBuffer(entity); } + + if (entity.has()) { + json["characterSlots"] = serializeCharacterSlots(entity); + } // CellGrid/Town components if (entity.has()) { @@ -294,6 +299,10 @@ void SceneSerializer::deserializeEntity(const nlohmann::json& json, flecs::entit deserializePrimitive(entity, json["primitive"]); } + if (json.contains("characterSlots")) { + deserializeCharacterSlots(entity, json["characterSlots"]); + } + if (json.contains("triangleBuffer")) { deserializeTriangleBuffer(entity, json["triangleBuffer"]); } @@ -1263,6 +1272,34 @@ void SceneSerializer::deserializeTriangleBuffer(flecs::entity entity, const nloh } +nlohmann::json SceneSerializer::serializeCharacterSlots(flecs::entity entity) +{ + 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) + json["slots"][pair.first] = pair.second; + + return 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()) + cs.slots[slot] = mesh.get(); + } + cs.dirty = true; + entity.set(cs); +} + + // ============================================================================ // CellGrid/Town Component Serialization // ============================================================================ diff --git a/src/features/editScene/systems/SceneSerializer.hpp b/src/features/editScene/systems/SceneSerializer.hpp index a493f77..ff49c6e 100644 --- a/src/features/editScene/systems/SceneSerializer.hpp +++ b/src/features/editScene/systems/SceneSerializer.hpp @@ -54,6 +54,7 @@ private: nlohmann::json serializeProceduralMaterial(flecs::entity entity); nlohmann::json serializePrimitive(flecs::entity entity); nlohmann::json serializeTriangleBuffer(flecs::entity entity); + nlohmann::json serializeCharacterSlots(flecs::entity entity); // CellGrid/Town component serialization nlohmann::json serializeCellGrid(flecs::entity entity); @@ -81,6 +82,7 @@ private: void deserializeProceduralMaterial(flecs::entity entity, const nlohmann::json& json); void deserializePrimitive(flecs::entity entity, const nlohmann::json& json); void deserializeTriangleBuffer(flecs::entity entity, const nlohmann::json& json); + void deserializeCharacterSlots(flecs::entity entity, const nlohmann::json& json); // CellGrid/Town component deserialization void deserializeCellGrid(flecs::entity entity, const nlohmann::json& json); diff --git a/src/features/editScene/ui/CharacterSlotsEditor.cpp b/src/features/editScene/ui/CharacterSlotsEditor.cpp new file mode 100644 index 0000000..f84e9c7 --- /dev/null +++ b/src/features/editScene/ui/CharacterSlotsEditor.cpp @@ -0,0 +1,122 @@ +#include "CharacterSlotsEditor.hpp" +#include "../systems/CharacterSlotSystem.hpp" +#include +#include + +CharacterSlotsEditor::CharacterSlotsEditor(Ogre::SceneManager *sceneMgr) + : m_sceneMgr(sceneMgr) +{ +} + +bool CharacterSlotsEditor::renderComponent(flecs::entity entity, + CharacterSlotsComponent &cs) +{ + bool modified = false; + (void)entity; + + if (ImGui::CollapsingHeader("Character Slots", + ImGuiTreeNodeFlags_DefaultOpen)) { + ImGui::Indent(); + + CharacterSlotSystem::loadCatalog(); + + /* Age selector */ + std::vector ages = + CharacterSlotSystem::getAges(); + Ogre::String currentAge = cs.age; + if (ImGui::BeginCombo("Age", currentAge.c_str())) { + for (const auto &age : ages) { + bool isSelected = (currentAge == age); + if (ImGui::Selectable(age.c_str(), isSelected)) { + cs.age = age; + modified = true; + cs.dirty = true; + } + if (isSelected) + ImGui::SetItemDefaultFocus(); + } + ImGui::EndCombo(); + } + + /* Sex selector */ + std::vector sexes = + CharacterSlotSystem::getSexes(cs.age); + Ogre::String currentSex = cs.sex; + if (ImGui::BeginCombo("Sex", currentSex.c_str())) { + for (const auto &sex : sexes) { + bool isSelected = (currentSex == sex); + if (ImGui::Selectable(sex.c_str(), isSelected)) { + cs.sex = sex; + modified = true; + cs.dirty = true; + } + if (isSelected) + ImGui::SetItemDefaultFocus(); + } + ImGui::EndCombo(); + } + + ImGui::Separator(); + + /* Collect available and configured slots */ + std::vector availableSlots = + CharacterSlotSystem::getSlots(cs.age, cs.sex); + for (const auto &pair : cs.slots) { + if (std::find(availableSlots.begin(), availableSlots.end(), + pair.first) == availableSlots.end()) + availableSlots.push_back(pair.first); + } + std::sort(availableSlots.begin(), availableSlots.end()); + + /* Render mesh selector for each slot */ + for (const auto &slot : availableSlots) { + Ogre::String currentMesh = ""; + auto it = cs.slots.find(slot); + if (it != cs.slots.end()) + currentMesh = it->second; + + std::vector meshes = + CharacterSlotSystem::getMeshes(cs.age, cs.sex, + slot); + + Ogre::String label = slot; + Ogre::String preview = currentMesh.empty() ? + "(none)" : + currentMesh; + + if (ImGui::BeginCombo(label.c_str(), preview.c_str())) { + bool noneSelected = currentMesh.empty(); + if (ImGui::Selectable("(none)", noneSelected)) { + cs.slots[slot] = ""; + modified = true; + cs.dirty = true; + } + if (noneSelected) + ImGui::SetItemDefaultFocus(); + + for (const auto &mesh : meshes) { + bool isSelected = (currentMesh == mesh); + if (ImGui::Selectable(mesh.c_str(), + isSelected)) { + cs.slots[slot] = mesh; + modified = true; + cs.dirty = true; + } + if (isSelected) + ImGui::SetItemDefaultFocus(); + } + ImGui::EndCombo(); + } + } + + /* Rebuild button */ + if (ImGui::Button("Rebuild")) { + cs.dirty = true; + modified = true; + } + + ImGui::Unindent(); + } + + return modified; +} diff --git a/src/features/editScene/ui/CharacterSlotsEditor.hpp b/src/features/editScene/ui/CharacterSlotsEditor.hpp new file mode 100644 index 0000000..8f8d465 --- /dev/null +++ b/src/features/editScene/ui/CharacterSlotsEditor.hpp @@ -0,0 +1,25 @@ +#ifndef EDITSCENE_CHARACTERSLOTSEDITOR_HPP +#define EDITSCENE_CHARACTERSLOTSEDITOR_HPP +#pragma once +#include "ComponentEditor.hpp" +#include "../components/CharacterSlots.hpp" +#include + +/** + * Editor for CharacterSlotsComponent + */ +class CharacterSlotsEditor : public ComponentEditor { +public: + explicit CharacterSlotsEditor(Ogre::SceneManager *sceneMgr); + + const char *getName() const override { return "Character Slots"; } + +protected: + bool renderComponent(flecs::entity entity, + CharacterSlotsComponent &cs) override; + +private: + Ogre::SceneManager *m_sceneMgr; +}; + +#endif // EDITSCENE_CHARACTERSLOTSEDITOR_HPP