Character display
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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 <OgreRTShaderSystem.h>
|
||||
@@ -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<CharacterSlotSystem>(
|
||||
m_world, m_sceneMgr);
|
||||
m_characterSlotSystem->initialize();
|
||||
|
||||
// Setup CellGrid system
|
||||
m_cellGridSystem =
|
||||
std::make_unique<CellGridSystem>(m_world, m_sceneMgr);
|
||||
@@ -289,6 +297,9 @@ void EditorApp::setupECS()
|
||||
m_world.component<PrimitiveComponent>();
|
||||
m_world.component<TriangleBufferComponent>();
|
||||
|
||||
// Register CharacterSlots component
|
||||
m_world.component<CharacterSlotsComponent>();
|
||||
|
||||
// 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();
|
||||
|
||||
@@ -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<ProceduralTextureSystem> m_proceduralTextureSystem;
|
||||
std::unique_ptr<ProceduralMaterialSystem> m_proceduralMaterialSystem;
|
||||
std::unique_ptr<ProceduralMeshSystem> m_proceduralMeshSystem;
|
||||
std::unique_ptr<CharacterSlotSystem> m_characterSlotSystem;
|
||||
std::unique_ptr<CellGridSystem> m_cellGridSystem;
|
||||
std::unique_ptr<RoomLayoutSystem> m_roomLayoutSystem;
|
||||
|
||||
|
||||
20
src/features/editScene/components/CharacterSlots.hpp
Normal file
20
src/features/editScene/components/CharacterSlots.hpp
Normal file
@@ -0,0 +1,20 @@
|
||||
#ifndef EDITSCENE_CHARACTERSLOTS_HPP
|
||||
#define EDITSCENE_CHARACTERSLOTS_HPP
|
||||
#pragma once
|
||||
#include <Ogre.h>
|
||||
#include <unordered_map>
|
||||
|
||||
/**
|
||||
* 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<Ogre::String, Ogre::String> slots;
|
||||
bool dirty = true;
|
||||
|
||||
CharacterSlotsComponent() = default;
|
||||
};
|
||||
|
||||
#endif // EDITSCENE_CHARACTERSLOTS_HPP
|
||||
35
src/features/editScene/components/CharacterSlotsModule.cpp
Normal file
35
src/features/editScene/components/CharacterSlotsModule.cpp
Normal file
@@ -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<CharacterSlotsComponent>(
|
||||
"Character Slots",
|
||||
"Rendering",
|
||||
std::make_unique<CharacterSlotsEditor>(sceneMgr),
|
||||
/* Adder */
|
||||
[sceneMgr](flecs::entity e) {
|
||||
if (!e.has<TransformComponent>()) {
|
||||
TransformComponent transform;
|
||||
transform.node =
|
||||
sceneMgr->getRootSceneNode()
|
||||
->createChildSceneNode();
|
||||
e.set<TransformComponent>(transform);
|
||||
}
|
||||
CharacterSlotsComponent cs;
|
||||
cs.dirty = true;
|
||||
e.set<CharacterSlotsComponent>(cs);
|
||||
},
|
||||
/* Remover */
|
||||
[sceneMgr](flecs::entity e) {
|
||||
(void)sceneMgr;
|
||||
e.remove<CharacterSlotsComponent>();
|
||||
}
|
||||
);
|
||||
}
|
||||
273
src/features/editScene/systems/CharacterSlotSystem.cpp
Normal file
273
src/features/editScene/systems/CharacterSlotSystem.cpp
Normal file
@@ -0,0 +1,273 @@
|
||||
#include "CharacterSlotSystem.hpp"
|
||||
#include "../components/Transform.hpp"
|
||||
#include <OgreDataStream.h>
|
||||
#include <OgreEntity.h>
|
||||
#include <OgreLogManager.h>
|
||||
#include <OgreResourceGroupManager.h>
|
||||
#include <OgreSceneNode.h>
|
||||
#include <iostream>
|
||||
|
||||
bool CharacterSlotSystem::s_catalogLoaded = false;
|
||||
nlohmann::json CharacterSlotSystem::s_bodyParts = nlohmann::json::object();
|
||||
std::set<Ogre::String> CharacterSlotSystem::s_meshNames;
|
||||
|
||||
CharacterSlotSystem::CharacterSlotSystem(flecs::world &world,
|
||||
Ogre::SceneManager *sceneMgr)
|
||||
: m_world(world)
|
||||
, m_sceneMgr(sceneMgr)
|
||||
{
|
||||
m_world.observer<CharacterSlotsComponent>("CharacterSlotsCleanup")
|
||||
.event(flecs::OnRemove)
|
||||
.each([this](flecs::entity e, CharacterSlotsComponent &) {
|
||||
destroyCharacterParts(e);
|
||||
});
|
||||
}
|
||||
|
||||
CharacterSlotSystem::~CharacterSlotSystem()
|
||||
{
|
||||
std::vector<flecs::entity_t> 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<Ogre::String> 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>();
|
||||
Ogre::String sex = jdata["sex"].get<Ogre::String>();
|
||||
Ogre::String slot = jdata["slot"].get<Ogre::String>();
|
||||
Ogre::String mesh = jdata["mesh"].get<Ogre::String>();
|
||||
|
||||
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<Ogre::String> CharacterSlotSystem::getAges()
|
||||
{
|
||||
std::vector<Ogre::String> ages;
|
||||
if (!s_catalogLoaded)
|
||||
return ages;
|
||||
for (auto &el : s_bodyParts.items())
|
||||
ages.push_back(el.key());
|
||||
return ages;
|
||||
}
|
||||
|
||||
std::vector<Ogre::String> CharacterSlotSystem::getSexes(
|
||||
const Ogre::String &age)
|
||||
{
|
||||
std::vector<Ogre::String> 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<Ogre::String> CharacterSlotSystem::getSlots(
|
||||
const Ogre::String &age, const Ogre::String &sex)
|
||||
{
|
||||
std::vector<Ogre::String> 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<Ogre::String> CharacterSlotSystem::getMeshes(
|
||||
const Ogre::String &age, const Ogre::String &sex,
|
||||
const Ogre::String &slot)
|
||||
{
|
||||
std::vector<Ogre::String> 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<Ogre::String>());
|
||||
return meshes;
|
||||
}
|
||||
|
||||
void CharacterSlotSystem::update()
|
||||
{
|
||||
if (!m_initialized)
|
||||
return;
|
||||
|
||||
m_world.query<CharacterSlotsComponent>().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<TransformComponent>()) {
|
||||
std::cout << " no TransformComponent" << std::endl;
|
||||
return;
|
||||
}
|
||||
|
||||
auto &transform = e.get_mut<TransformComponent>();
|
||||
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);
|
||||
}
|
||||
56
src/features/editScene/systems/CharacterSlotSystem.hpp
Normal file
56
src/features/editScene/systems/CharacterSlotSystem.hpp
Normal file
@@ -0,0 +1,56 @@
|
||||
#ifndef EDITSCENE_CHARACTERSLOTSYSTEM_HPP
|
||||
#define EDITSCENE_CHARACTERSLOTSYSTEM_HPP
|
||||
#pragma once
|
||||
|
||||
#include <flecs.h>
|
||||
#include <Ogre.h>
|
||||
#include <nlohmann/json.hpp>
|
||||
#include <set>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
#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<Ogre::String> getAges();
|
||||
static std::vector<Ogre::String> getSexes(const Ogre::String &age);
|
||||
static std::vector<Ogre::String> getSlots(const Ogre::String &age,
|
||||
const Ogre::String &sex);
|
||||
static std::vector<Ogre::String> 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<Ogre::String> 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<Ogre::String, Ogre::Entity *> parts;
|
||||
};
|
||||
std::unordered_map<flecs::entity_t, PartEntities> m_entities;
|
||||
};
|
||||
|
||||
#endif // EDITSCENE_CHARACTERSLOTSYSTEM_HPP
|
||||
@@ -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<TriangleBufferComponent>(entity, tb);
|
||||
componentCount++;
|
||||
}
|
||||
|
||||
// Render CharacterSlots if present
|
||||
if (entity.has<CharacterSlotsComponent>()) {
|
||||
auto &cs = entity.get_mut<CharacterSlotsComponent>();
|
||||
m_componentRegistry.render<CharacterSlotsComponent>(entity, cs);
|
||||
componentCount++;
|
||||
}
|
||||
|
||||
// Render CellGrid if present
|
||||
if (entity.has<CellGridComponent>()) {
|
||||
|
||||
@@ -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<TriangleBufferComponent>()) {
|
||||
json["triangleBuffer"] = serializeTriangleBuffer(entity);
|
||||
}
|
||||
|
||||
if (entity.has<CharacterSlotsComponent>()) {
|
||||
json["characterSlots"] = serializeCharacterSlots(entity);
|
||||
}
|
||||
|
||||
// CellGrid/Town components
|
||||
if (entity.has<CellGridComponent>()) {
|
||||
@@ -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<CharacterSlotsComponent>();
|
||||
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<std::string>();
|
||||
}
|
||||
cs.dirty = true;
|
||||
entity.set<CharacterSlotsComponent>(cs);
|
||||
}
|
||||
|
||||
|
||||
// ============================================================================
|
||||
// CellGrid/Town Component Serialization
|
||||
// ============================================================================
|
||||
|
||||
@@ -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);
|
||||
|
||||
122
src/features/editScene/ui/CharacterSlotsEditor.cpp
Normal file
122
src/features/editScene/ui/CharacterSlotsEditor.cpp
Normal file
@@ -0,0 +1,122 @@
|
||||
#include "CharacterSlotsEditor.hpp"
|
||||
#include "../systems/CharacterSlotSystem.hpp"
|
||||
#include <imgui.h>
|
||||
#include <algorithm>
|
||||
|
||||
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<Ogre::String> 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<Ogre::String> 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<Ogre::String> 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<Ogre::String> 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;
|
||||
}
|
||||
25
src/features/editScene/ui/CharacterSlotsEditor.hpp
Normal file
25
src/features/editScene/ui/CharacterSlotsEditor.hpp
Normal file
@@ -0,0 +1,25 @@
|
||||
#ifndef EDITSCENE_CHARACTERSLOTSEDITOR_HPP
|
||||
#define EDITSCENE_CHARACTERSLOTSEDITOR_HPP
|
||||
#pragma once
|
||||
#include "ComponentEditor.hpp"
|
||||
#include "../components/CharacterSlots.hpp"
|
||||
#include <OgreSceneManager.h>
|
||||
|
||||
/**
|
||||
* Editor for CharacterSlotsComponent
|
||||
*/
|
||||
class CharacterSlotsEditor : public ComponentEditor<CharacterSlotsComponent> {
|
||||
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
|
||||
Reference in New Issue
Block a user