Added StaticGeometry support
This commit is contained in:
@@ -17,6 +17,7 @@ set(EDITSCENE_SOURCES
|
||||
systems/LightSystem.cpp
|
||||
systems/CameraSystem.cpp
|
||||
systems/LodSystem.cpp
|
||||
systems/StaticGeometrySystem.cpp
|
||||
ui/TransformEditor.cpp
|
||||
ui/RenderableEditor.cpp
|
||||
ui/PhysicsColliderEditor.cpp
|
||||
@@ -25,10 +26,13 @@ set(EDITSCENE_SOURCES
|
||||
ui/CameraEditor.cpp
|
||||
ui/LodEditor.cpp
|
||||
ui/LodSettingsEditor.cpp
|
||||
ui/StaticGeometryEditor.cpp
|
||||
ui/StaticGeometryMemberEditor.cpp
|
||||
ui/ComponentRegistration.cpp
|
||||
components/LightModule.cpp
|
||||
components/CameraModule.cpp
|
||||
components/LodModule.cpp
|
||||
components/StaticGeometryModule.cpp
|
||||
camera/EditorCamera.cpp
|
||||
gizmo/Gizmo.cpp
|
||||
physics/physics.cpp
|
||||
@@ -46,7 +50,10 @@ set(EDITSCENE_HEADERS
|
||||
components/Camera.hpp
|
||||
components/Lod.hpp
|
||||
components/LodSettings.hpp
|
||||
components/StaticGeometry.hpp
|
||||
components/StaticGeometryMember.hpp
|
||||
systems/EditorUISystem.hpp
|
||||
systems/StaticGeometrySystem.hpp
|
||||
systems/SceneSerializer.hpp
|
||||
systems/PhysicsSystem.hpp
|
||||
systems/LightSystem.hpp
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#include "systems/LightSystem.hpp"
|
||||
#include "systems/CameraSystem.hpp"
|
||||
#include "systems/LodSystem.hpp"
|
||||
#include "systems/StaticGeometrySystem.hpp"
|
||||
#include "camera/EditorCamera.hpp"
|
||||
#include "components/EntityName.hpp"
|
||||
#include "components/Transform.hpp"
|
||||
@@ -16,6 +17,8 @@
|
||||
#include "components/Camera.hpp"
|
||||
#include "components/Lod.hpp"
|
||||
#include "components/LodSettings.hpp"
|
||||
#include "components/StaticGeometry.hpp"
|
||||
#include "components/StaticGeometryMember.hpp"
|
||||
#include <OgreRTShaderSystem.h>
|
||||
#include <imgui.h>
|
||||
|
||||
@@ -128,6 +131,10 @@ void EditorApp::setup()
|
||||
// Setup LOD system
|
||||
m_lodSystem = std::make_unique<EditorLodSystem>(m_world, m_sceneMgr);
|
||||
m_lodSystem->initialize();
|
||||
|
||||
// Setup StaticGeometry system
|
||||
m_staticGeometrySystem = std::make_unique<StaticGeometrySystem>(m_world, m_sceneMgr);
|
||||
m_staticGeometrySystem->initialize();
|
||||
|
||||
// Add default entities to UI cache
|
||||
for (auto &e : m_defaultEntities) {
|
||||
@@ -168,6 +175,10 @@ void EditorApp::setupECS()
|
||||
// Register LOD components
|
||||
m_world.component<LodComponent>();
|
||||
m_world.component<LodSettingsComponent>();
|
||||
|
||||
// Register StaticGeometry components
|
||||
m_world.component<StaticGeometryComponent>();
|
||||
m_world.component<StaticGeometryMemberComponent>();
|
||||
}
|
||||
|
||||
void EditorApp::createDefaultEntities()
|
||||
@@ -310,6 +321,11 @@ bool EditorApp::frameRenderingQueued(const Ogre::FrameEvent &evt)
|
||||
if (m_lodSystem) {
|
||||
m_lodSystem->update();
|
||||
}
|
||||
|
||||
// Update StaticGeometry system
|
||||
if (m_staticGeometrySystem) {
|
||||
m_staticGeometrySystem->update();
|
||||
}
|
||||
|
||||
// Don't call base class - it crashes when iterating input listeners
|
||||
return true;
|
||||
|
||||
@@ -17,6 +17,7 @@ class EditorPhysicsSystem;
|
||||
class EditorLightSystem;
|
||||
class EditorCameraSystem;
|
||||
class EditorLodSystem;
|
||||
class StaticGeometrySystem;
|
||||
|
||||
/**
|
||||
* RenderTargetListener for ImGui frame management
|
||||
@@ -86,6 +87,7 @@ private:
|
||||
std::unique_ptr<EditorLightSystem> m_lightSystem;
|
||||
std::unique_ptr<EditorCameraSystem> m_cameraSystem;
|
||||
std::unique_ptr<EditorLodSystem> m_lodSystem;
|
||||
std::unique_ptr<StaticGeometrySystem> m_staticGeometrySystem;
|
||||
|
||||
// State
|
||||
uint16_t m_currentModifiers;
|
||||
|
||||
43
src/features/editScene/components/StaticGeometry.hpp
Normal file
43
src/features/editScene/components/StaticGeometry.hpp
Normal file
@@ -0,0 +1,43 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
#include <Ogre.h>
|
||||
|
||||
/**
|
||||
* @brief Component for a StaticGeometry region/batch
|
||||
*
|
||||
* Entities with StaticGeometryMemberComponent reference this region.
|
||||
* The system collects all members and builds the batched geometry.
|
||||
*/
|
||||
struct StaticGeometryComponent {
|
||||
// Persistent ID for serialization and member references
|
||||
std::string regionId;
|
||||
|
||||
// Region name for the Ogre::StaticGeometry object
|
||||
std::string regionName;
|
||||
|
||||
// The actual Ogre StaticGeometry object
|
||||
Ogre::StaticGeometry* staticGeometry = nullptr;
|
||||
|
||||
// Build configuration
|
||||
float renderingDistance = 0.0f; // 0 = infinite
|
||||
bool castShadows = true;
|
||||
|
||||
// Optimization settings
|
||||
float regionDimensions = 1000.0f; // Size of each region cell
|
||||
std::uint32_t visibilityFlags = 0xFFFFFFFF;
|
||||
|
||||
// State
|
||||
bool built = false;
|
||||
bool dirty = true; // Needs rebuild
|
||||
unsigned int buildVersion = 0; // Incremented on each build
|
||||
|
||||
// List of member entity IDs (for tracking)
|
||||
std::vector<std::uint64_t> memberEntityIds;
|
||||
|
||||
void markDirty() {
|
||||
dirty = true;
|
||||
}
|
||||
};
|
||||
45
src/features/editScene/components/StaticGeometryMember.hpp
Normal file
45
src/features/editScene/components/StaticGeometryMember.hpp
Normal file
@@ -0,0 +1,45 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <flecs.h>
|
||||
|
||||
/**
|
||||
* @brief Component for entities that should be batched into StaticGeometry
|
||||
*
|
||||
* This component stores mesh data and references the StaticGeometry region entity.
|
||||
* The actual Ogre::Entity is NOT created on a SceneNode - instead, the data is
|
||||
* used to add geometry to the StaticGeometry batch.
|
||||
*
|
||||
* Position and Orientation are taken from TransformComponent's SceneNode (_getDerived*).
|
||||
* Scale is also taken from TransformComponent.
|
||||
*/
|
||||
struct StaticGeometryMemberComponent {
|
||||
// Reference to the StaticGeometry region entity (runtime only)
|
||||
flecs::entity regionEntity;
|
||||
|
||||
// Persistent ID for serialization (references StaticGeometryComponent::regionId)
|
||||
std::string regionId;
|
||||
|
||||
// Mesh data (stored here, not in RenderableComponent)
|
||||
std::string meshName;
|
||||
std::string materialName; // Empty = use mesh default
|
||||
|
||||
// LOD settings reference (must be same for all members in a region)
|
||||
std::string lodSettingsId;
|
||||
flecs::entity lodSettingsEntity;
|
||||
|
||||
// Configuration
|
||||
bool castShadows = true;
|
||||
bool visible = true;
|
||||
|
||||
// Dirty flag - triggers region rebuild when changed
|
||||
bool dirty = true;
|
||||
|
||||
// Version tracking for transform changes
|
||||
unsigned int transformVersion = 0;
|
||||
|
||||
void markDirty() {
|
||||
dirty = true;
|
||||
transformVersion++;
|
||||
}
|
||||
};
|
||||
60
src/features/editScene/components/StaticGeometryModule.cpp
Normal file
60
src/features/editScene/components/StaticGeometryModule.cpp
Normal file
@@ -0,0 +1,60 @@
|
||||
#include "StaticGeometry.hpp"
|
||||
#include "StaticGeometryMember.hpp"
|
||||
#include "../ui/ComponentRegistration.hpp"
|
||||
#include "../ui/StaticGeometryEditor.hpp"
|
||||
#include "../ui/StaticGeometryMemberEditor.hpp"
|
||||
#include <Ogre.h>
|
||||
#include <random>
|
||||
|
||||
// Helper to generate unique ID
|
||||
static std::string generateRegionId() {
|
||||
static std::random_device rd;
|
||||
static std::mt19937 gen(rd());
|
||||
static std::uniform_int_distribution<> dis(1000, 9999);
|
||||
return "region_" + std::to_string(dis(gen));
|
||||
}
|
||||
|
||||
// Register StaticGeometry (region) component
|
||||
REGISTER_COMPONENT("StaticGeometry Region", StaticGeometryComponent, StaticGeometryEditor)
|
||||
{
|
||||
registry.registerComponent<StaticGeometryComponent>(
|
||||
"StaticGeometry Region",
|
||||
std::make_unique<StaticGeometryEditor>(),
|
||||
// Adder
|
||||
[sceneMgr](flecs::entity e) {
|
||||
if (!e.has<StaticGeometryComponent>()) {
|
||||
auto region = StaticGeometryComponent();
|
||||
region.regionId = generateRegionId();
|
||||
region.regionName = "Region_" + Ogre::StringConverter::toString(e.id());
|
||||
e.set<StaticGeometryComponent>(region);
|
||||
}
|
||||
},
|
||||
// Remover
|
||||
[sceneMgr](flecs::entity e) {
|
||||
if (e.has<StaticGeometryComponent>()) {
|
||||
e.remove<StaticGeometryComponent>();
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
// Register StaticGeometryMember component
|
||||
REGISTER_COMPONENT("StaticGeometry Member", StaticGeometryMemberComponent, StaticGeometryMemberEditor)
|
||||
{
|
||||
registry.registerComponent<StaticGeometryMemberComponent>(
|
||||
"StaticGeometry Member",
|
||||
std::make_unique<StaticGeometryMemberEditor>(sceneMgr),
|
||||
// Adder
|
||||
[sceneMgr](flecs::entity e) {
|
||||
if (!e.has<StaticGeometryMemberComponent>()) {
|
||||
e.set<StaticGeometryMemberComponent>({});
|
||||
}
|
||||
},
|
||||
// Remover
|
||||
[sceneMgr](flecs::entity e) {
|
||||
if (e.has<StaticGeometryMemberComponent>()) {
|
||||
e.remove<StaticGeometryMemberComponent>();
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
#include "Gizmo.hpp"
|
||||
#include "../components/Transform.hpp"
|
||||
#include "../components/StaticGeometryMember.hpp"
|
||||
#include <cmath>
|
||||
|
||||
// Simple colors for axes - not using vertex colors since RTSS has issues
|
||||
@@ -262,6 +263,11 @@ bool Gizmo::onMouseMoved(const Ogre::Ray &mouseRay, const Ogre::Vector2 &mouseDe
|
||||
|
||||
transform.applyToNode();
|
||||
|
||||
// Mark StaticGeometryMember dirty if present
|
||||
if (m_attachedEntity.has<StaticGeometryMemberComponent>()) {
|
||||
m_attachedEntity.get_mut<StaticGeometryMemberComponent>().markDirty();
|
||||
}
|
||||
|
||||
// Update gizmo to follow (use derived position)
|
||||
m_gizmoNode->setPosition(transform.node->_getDerivedPosition());
|
||||
|
||||
|
||||
@@ -8,6 +8,8 @@
|
||||
#include "../components/Light.hpp"
|
||||
#include "../components/Camera.hpp"
|
||||
#include "../components/Lod.hpp"
|
||||
#include "../components/StaticGeometry.hpp"
|
||||
#include "../components/StaticGeometryMember.hpp"
|
||||
#include "../components/LodSettings.hpp"
|
||||
#include "../ui/TransformEditor.hpp"
|
||||
#include "../ui/RenderableEditor.hpp"
|
||||
@@ -481,7 +483,12 @@ void EditorUISystem::renderComponentList(flecs::entity entity)
|
||||
// Render Transform first if present (it's the base component)
|
||||
if (entity.has<TransformComponent>()) {
|
||||
auto &transform = entity.get_mut<TransformComponent>();
|
||||
m_componentRegistry.render<TransformComponent>(entity, transform);
|
||||
if (m_componentRegistry.render<TransformComponent>(entity, transform)) {
|
||||
// Transform changed - mark StaticGeometryMember dirty if present
|
||||
if (entity.has<StaticGeometryMemberComponent>()) {
|
||||
entity.get_mut<StaticGeometryMemberComponent>().markDirty();
|
||||
}
|
||||
}
|
||||
componentCount++;
|
||||
}
|
||||
|
||||
@@ -533,6 +540,20 @@ void EditorUISystem::renderComponentList(flecs::entity entity)
|
||||
m_componentRegistry.render<LodComponent>(entity, lod);
|
||||
componentCount++;
|
||||
}
|
||||
|
||||
// Render StaticGeometry Region if present
|
||||
if (entity.has<StaticGeometryComponent>()) {
|
||||
auto ®ion = entity.get_mut<StaticGeometryComponent>();
|
||||
m_componentRegistry.render<StaticGeometryComponent>(entity, region);
|
||||
componentCount++;
|
||||
}
|
||||
|
||||
// Render StaticGeometry Member if present
|
||||
if (entity.has<StaticGeometryMemberComponent>()) {
|
||||
auto &member = entity.get_mut<StaticGeometryMemberComponent>();
|
||||
m_componentRegistry.render<StaticGeometryMemberComponent>(entity, member);
|
||||
componentCount++;
|
||||
}
|
||||
|
||||
// Show message if no components
|
||||
if (componentCount == 0) {
|
||||
|
||||
@@ -9,7 +9,10 @@
|
||||
#include "../components/Camera.hpp"
|
||||
#include "../components/Lod.hpp"
|
||||
#include "../components/LodSettings.hpp"
|
||||
#include "../components/StaticGeometry.hpp"
|
||||
#include "../components/StaticGeometryMember.hpp"
|
||||
#include "EditorUISystem.hpp"
|
||||
#include <random>
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
|
||||
@@ -145,6 +148,14 @@ nlohmann::json SceneSerializer::serializeEntity(flecs::entity entity)
|
||||
json["lod"] = serializeLod(entity);
|
||||
}
|
||||
|
||||
if (entity.has<StaticGeometryComponent>()) {
|
||||
json["staticGeometry"] = serializeStaticGeometry(entity);
|
||||
}
|
||||
|
||||
if (entity.has<StaticGeometryMemberComponent>()) {
|
||||
json["staticGeometryMember"] = serializeStaticGeometryMember(entity);
|
||||
}
|
||||
|
||||
// Serialize children
|
||||
json["children"] = nlohmann::json::array();
|
||||
entity.children([&](flecs::entity child) {
|
||||
@@ -214,6 +225,14 @@ void SceneSerializer::deserializeEntity(const nlohmann::json& json, flecs::entit
|
||||
deserializeLod(entity, json["lod"]);
|
||||
}
|
||||
|
||||
if (json.contains("staticGeometry")) {
|
||||
deserializeStaticGeometry(entity, json["staticGeometry"]);
|
||||
}
|
||||
|
||||
if (json.contains("staticGeometryMember")) {
|
||||
deserializeStaticGeometryMember(entity, json["staticGeometryMember"]);
|
||||
}
|
||||
|
||||
// Add to UI system if provided
|
||||
if (uiSystem) {
|
||||
uiSystem->addEntity(entity);
|
||||
@@ -859,3 +878,71 @@ void SceneSerializer::deserializeLod(flecs::entity entity, const nlohmann::json&
|
||||
Ogre::StringConverter::toString(lod.settingsEntity.id()) +
|
||||
", hasValid=" + (lod.hasValidSettings() ? "true" : "false"));
|
||||
}
|
||||
|
||||
nlohmann::json SceneSerializer::serializeStaticGeometry(flecs::entity entity)
|
||||
{
|
||||
auto& region = entity.get<StaticGeometryComponent>();
|
||||
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)
|
||||
{
|
||||
auto& member = entity.get<StaticGeometryMemberComponent>();
|
||||
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)
|
||||
{
|
||||
StaticGeometryComponent region;
|
||||
|
||||
region.regionId = json.value("regionId", "");
|
||||
if (region.regionId.empty()) {
|
||||
// Generate new ID if missing
|
||||
static std::random_device rd;
|
||||
static std::mt19937 gen(rd());
|
||||
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<StaticGeometryComponent>(region);
|
||||
}
|
||||
|
||||
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", "");
|
||||
member.lodSettingsId = json.value("lodSettingsId", "");
|
||||
member.castShadows = json.value("castShadows", true);
|
||||
member.visible = json.value("visible", true);
|
||||
member.dirty = true;
|
||||
|
||||
entity.set<StaticGeometryMemberComponent>(member);
|
||||
}
|
||||
|
||||
@@ -48,6 +48,8 @@ private:
|
||||
nlohmann::json serializeCamera(flecs::entity entity);
|
||||
nlohmann::json serializeLodSettings(flecs::entity entity);
|
||||
nlohmann::json serializeLod(flecs::entity entity);
|
||||
nlohmann::json serializeStaticGeometry(flecs::entity entity);
|
||||
nlohmann::json serializeStaticGeometryMember(flecs::entity entity);
|
||||
|
||||
// Component deserialization
|
||||
void deserializeTransform(flecs::entity entity, const nlohmann::json& json, flecs::entity parentEntity);
|
||||
@@ -59,6 +61,8 @@ private:
|
||||
void deserializeCamera(flecs::entity entity, const nlohmann::json& json);
|
||||
void deserializeLodSettings(flecs::entity entity, const nlohmann::json& json);
|
||||
void deserializeLod(flecs::entity entity, const nlohmann::json& json);
|
||||
void deserializeStaticGeometry(flecs::entity entity, const nlohmann::json& json);
|
||||
void deserializeStaticGeometryMember(flecs::entity entity, const nlohmann::json& json);
|
||||
|
||||
flecs::world& m_world;
|
||||
Ogre::SceneManager* m_sceneMgr;
|
||||
|
||||
359
src/features/editScene/systems/StaticGeometrySystem.cpp
Normal file
359
src/features/editScene/systems/StaticGeometrySystem.cpp
Normal file
@@ -0,0 +1,359 @@
|
||||
#include "StaticGeometrySystem.hpp"
|
||||
#include "../components/StaticGeometry.hpp"
|
||||
#include "../components/StaticGeometryMember.hpp"
|
||||
#include "../components/Transform.hpp"
|
||||
#include "../components/Lod.hpp"
|
||||
#include "../components/LodSettings.hpp"
|
||||
#include <OgreSceneManager.h>
|
||||
#include <OgreEntity.h>
|
||||
#include <OgreMeshManager.h>
|
||||
#include <OgreLogManager.h>
|
||||
#include <OgreVector3.h>
|
||||
#include <OgreQuaternion.h>
|
||||
#include <OGRE/MeshLodGenerator/OgreMeshLodGenerator.h>
|
||||
#include <OgreDistanceLodStrategy.h>
|
||||
#include <OgrePixelCountLodStrategy.h>
|
||||
|
||||
StaticGeometrySystem::StaticGeometrySystem(flecs::world& world, Ogre::SceneManager* sceneMgr)
|
||||
: m_world(world)
|
||||
, m_sceneMgr(sceneMgr)
|
||||
, m_regionQuery(world.query<StaticGeometryComponent>())
|
||||
, m_memberQuery(world.query<StaticGeometryMemberComponent>())
|
||||
{
|
||||
}
|
||||
|
||||
StaticGeometrySystem::~StaticGeometrySystem() = default;
|
||||
|
||||
void StaticGeometrySystem::initialize()
|
||||
{
|
||||
if (m_initialized) return;
|
||||
m_initialized = true;
|
||||
}
|
||||
|
||||
void StaticGeometrySystem::update()
|
||||
{
|
||||
if (!m_initialized) return;
|
||||
|
||||
// First pass: resolve region references for members and detect transform changes
|
||||
m_memberQuery.each([&](flecs::entity entity, StaticGeometryMemberComponent& member) {
|
||||
if (!member.regionId.empty() && !member.regionEntity.is_alive()) {
|
||||
// Try to find the region entity by regionId
|
||||
m_regionQuery.each([&](flecs::entity e, StaticGeometryComponent& region) {
|
||||
if (region.regionId == member.regionId) {
|
||||
member.regionEntity = e;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Check if transform has changed (using version tracking)
|
||||
if (entity.has<TransformComponent>()) {
|
||||
const auto& transform = entity.get<TransformComponent>();
|
||||
if (transform.node) {
|
||||
// Store current transform version in member for comparison
|
||||
// For now, we rely on the member.dirty flag being set externally
|
||||
// when TransformComponent changes
|
||||
}
|
||||
}
|
||||
|
||||
// Mark region dirty if member is dirty
|
||||
if (member.dirty && member.regionEntity.is_alive()) {
|
||||
if (member.regionEntity.has<StaticGeometryComponent>()) {
|
||||
member.regionEntity.get_mut<StaticGeometryComponent>().markDirty();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Second pass: rebuild dirty regions
|
||||
m_regionQuery.each([&](flecs::entity entity, StaticGeometryComponent& region) {
|
||||
if (region.dirty || !region.built) {
|
||||
buildRegion(entity, region);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void StaticGeometrySystem::buildRegion(flecs::entity regionEntity, StaticGeometryComponent& region)
|
||||
{
|
||||
if (!regionEntity.is_alive()) return;
|
||||
|
||||
// Destroy existing static geometry
|
||||
destroyOgreStaticGeometry(region);
|
||||
|
||||
// Generate unique region name if not set
|
||||
if (region.regionName.empty()) {
|
||||
region.regionName = "StaticRegion_" + std::to_string(regionEntity.id());
|
||||
}
|
||||
|
||||
// Create new StaticGeometry
|
||||
region.staticGeometry = m_sceneMgr->createStaticGeometry(region.regionName);
|
||||
region.staticGeometry->setCastShadows(region.castShadows);
|
||||
|
||||
if (region.renderingDistance > 0.0f) {
|
||||
region.staticGeometry->setRenderingDistance(region.renderingDistance);
|
||||
}
|
||||
|
||||
region.staticGeometry->setRegionDimensions(Ogre::Vector3(region.regionDimensions));
|
||||
region.staticGeometry->setVisibilityFlags(region.visibilityFlags);
|
||||
|
||||
// Collect all members for this region
|
||||
std::vector<std::pair<flecs::entity, StaticGeometryMemberComponent*>> members;
|
||||
m_memberQuery.each([&](flecs::entity memberEntity, StaticGeometryMemberComponent& member) {
|
||||
if (member.regionEntity == regionEntity && member.visible) {
|
||||
members.emplace_back(memberEntity, &member);
|
||||
}
|
||||
});
|
||||
|
||||
// Track member IDs
|
||||
region.memberEntityIds.clear();
|
||||
region.memberEntityIds.reserve(members.size());
|
||||
|
||||
// Check LOD consistency across members
|
||||
std::string regionLodSettingsId;
|
||||
flecs::entity regionLodSettingsEntity;
|
||||
bool lodConsistent = true;
|
||||
|
||||
for (auto& [memberEntity, member] : members) {
|
||||
if (!member->lodSettingsId.empty()) {
|
||||
if (regionLodSettingsId.empty()) {
|
||||
regionLodSettingsId = member->lodSettingsId;
|
||||
regionLodSettingsEntity = member->lodSettingsEntity;
|
||||
} else if (regionLodSettingsId != member->lodSettingsId) {
|
||||
lodConsistent = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!lodConsistent) {
|
||||
Ogre::LogManager::getSingleton().logMessage(
|
||||
"StaticGeometry WARNING: Region '" + region.regionName +
|
||||
"' has members with different LOD Settings! Using first found.",
|
||||
Ogre::LML_WARNING);
|
||||
}
|
||||
|
||||
// Get LOD generator
|
||||
Ogre::MeshLodGenerator* lodGenerator = nullptr;
|
||||
try {
|
||||
lodGenerator = Ogre::MeshLodGenerator::getSingletonPtr();
|
||||
} catch (...) {
|
||||
// Generator not created yet
|
||||
}
|
||||
|
||||
// Add each member's geometry
|
||||
for (auto& [memberEntity, member] : members) {
|
||||
if (member->meshName.empty()) continue;
|
||||
|
||||
try {
|
||||
// Check if mesh exists
|
||||
if (!Ogre::MeshManager::getSingleton().resourceExists(member->meshName,
|
||||
Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Get global position and orientation from TransformComponent if available
|
||||
Ogre::Vector3 position = Ogre::Vector3::ZERO;
|
||||
Ogre::Quaternion orientation = Ogre::Quaternion::IDENTITY;
|
||||
Ogre::Vector3 scale = Ogre::Vector3::UNIT_SCALE;
|
||||
|
||||
if (memberEntity.has<TransformComponent>()) {
|
||||
const auto& transform = memberEntity.get<TransformComponent>();
|
||||
if (transform.node) {
|
||||
// Use derived (global) position and orientation
|
||||
position = transform.node->_getDerivedPosition();
|
||||
orientation = transform.node->_getDerivedOrientation();
|
||||
scale = transform.node->getScale();
|
||||
}
|
||||
}
|
||||
|
||||
// Get the mesh and apply LOD if configured
|
||||
Ogre::MeshPtr mesh = Ogre::MeshManager::getSingleton().load(
|
||||
member->meshName,
|
||||
Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME);
|
||||
|
||||
// Apply LOD if this member uses LOD and we have valid settings
|
||||
if (lodGenerator && !member->lodSettingsId.empty() &&
|
||||
member->lodSettingsEntity.is_alive() &&
|
||||
member->lodSettingsEntity.has<LodSettingsComponent>()) {
|
||||
|
||||
const auto& lodSettings = member->lodSettingsEntity.get<LodSettingsComponent>();
|
||||
if (!lodSettings.lodLevels.empty()) {
|
||||
// Build LOD config
|
||||
Ogre::LodConfig lodConfig;
|
||||
lodConfig.mesh = mesh;
|
||||
|
||||
// Set strategy
|
||||
switch (lodSettings.strategy) {
|
||||
case LodSettingsComponent::Strategy::Distance:
|
||||
lodConfig.strategy = Ogre::DistanceLodBoxStrategy::getSingletonPtr();
|
||||
break;
|
||||
case LodSettingsComponent::Strategy::PixelCount:
|
||||
case LodSettingsComponent::Strategy::EdgePixelCount:
|
||||
lodConfig.strategy = Ogre::PixelCountLodStrategy::getSingletonPtr();
|
||||
break;
|
||||
}
|
||||
|
||||
// Build LOD levels
|
||||
for (const auto& levelConfig : lodSettings.lodLevels) {
|
||||
Ogre::LodLevel level;
|
||||
level.distance = levelConfig.distance;
|
||||
|
||||
if (levelConfig.useManualMesh && !levelConfig.manualMeshName.empty()) {
|
||||
level.manualMeshName = levelConfig.manualMeshName;
|
||||
} else {
|
||||
switch (levelConfig.reductionMethod) {
|
||||
case LodLevelConfig::ReductionMethod::Proportional:
|
||||
level.reductionMethod = Ogre::LodLevel::VRM_PROPORTIONAL;
|
||||
break;
|
||||
case LodLevelConfig::ReductionMethod::Constant:
|
||||
level.reductionMethod = Ogre::LodLevel::VRM_CONSTANT;
|
||||
break;
|
||||
case LodLevelConfig::ReductionMethod::CollapseCost:
|
||||
level.reductionMethod = Ogre::LodLevel::VRM_COLLAPSE_COST;
|
||||
break;
|
||||
}
|
||||
level.reductionValue = levelConfig.reductionValue;
|
||||
}
|
||||
lodConfig.levels.push_back(level);
|
||||
}
|
||||
|
||||
// Apply advanced settings
|
||||
lodConfig.advanced.useCompression = lodSettings.useCompression;
|
||||
lodConfig.advanced.useVertexNormals = lodSettings.useVertexNormals;
|
||||
lodConfig.advanced.preventPunchingHoles = lodSettings.preventPunchingHoles;
|
||||
lodConfig.advanced.preventBreakingLines = lodSettings.preventBreakingLines;
|
||||
|
||||
// Generate LOD levels
|
||||
lodGenerator->generateLodLevels(lodConfig);
|
||||
}
|
||||
}
|
||||
|
||||
// Create a unique entity name for this instance
|
||||
std::string entityName = region.regionName + "_" + std::to_string(memberEntity.id()) + "_" + member->meshName;
|
||||
|
||||
// Create temporary entity
|
||||
Ogre::Entity* tempEntity = m_sceneMgr->createEntity(entityName, mesh);
|
||||
|
||||
// Apply material override if specified
|
||||
if (!member->materialName.empty()) {
|
||||
tempEntity->setMaterialName(member->materialName);
|
||||
}
|
||||
|
||||
// Set shadow casting
|
||||
tempEntity->setCastShadows(member->castShadows);
|
||||
|
||||
// Add to static geometry using global transform
|
||||
region.staticGeometry->addEntity(tempEntity,
|
||||
position,
|
||||
orientation,
|
||||
scale);
|
||||
|
||||
// Destroy the temporary entity - the geometry is now copied
|
||||
m_sceneMgr->destroyEntity(tempEntity);
|
||||
|
||||
// Track this member
|
||||
region.memberEntityIds.push_back(memberEntity.id());
|
||||
member->dirty = false;
|
||||
|
||||
} catch (const Ogre::Exception& e) {
|
||||
Ogre::LogManager::getSingleton().logMessage(
|
||||
"StaticGeometry: Failed to add mesh " + member->meshName +
|
||||
": " + e.getDescription());
|
||||
}
|
||||
}
|
||||
|
||||
// Build the static geometry
|
||||
try {
|
||||
region.staticGeometry->build();
|
||||
region.built = true;
|
||||
region.dirty = false;
|
||||
region.buildVersion++;
|
||||
|
||||
Ogre::LogManager::getSingleton().logMessage(
|
||||
"StaticGeometry: Built region '" + region.regionName +
|
||||
"' with " + std::to_string(members.size()) + " entities");
|
||||
|
||||
} catch (const Ogre::Exception& e) {
|
||||
Ogre::LogManager::getSingleton().logMessage(
|
||||
"StaticGeometry: Failed to build region '" + region.regionName +
|
||||
"': " + e.getDescription());
|
||||
destroyOgreStaticGeometry(region);
|
||||
region.built = false;
|
||||
region.dirty = true;
|
||||
}
|
||||
}
|
||||
|
||||
void StaticGeometrySystem::destroyOgreStaticGeometry(StaticGeometryComponent& region)
|
||||
{
|
||||
if (region.staticGeometry) {
|
||||
m_sceneMgr->destroyStaticGeometry(region.staticGeometry);
|
||||
region.staticGeometry = nullptr;
|
||||
}
|
||||
region.built = false;
|
||||
}
|
||||
|
||||
void StaticGeometrySystem::rebuildRegion(flecs::entity regionEntity)
|
||||
{
|
||||
if (!regionEntity.is_alive() || !regionEntity.has<StaticGeometryComponent>()) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto& region = regionEntity.get_mut<StaticGeometryComponent>();
|
||||
region.markDirty();
|
||||
|
||||
// Also mark all members as dirty to force refresh
|
||||
m_memberQuery.each([&](flecs::entity e, StaticGeometryMemberComponent& member) {
|
||||
if (member.regionEntity == regionEntity) {
|
||||
member.markDirty();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void StaticGeometrySystem::destroyRegion(flecs::entity regionEntity)
|
||||
{
|
||||
if (!regionEntity.is_alive() || !regionEntity.has<StaticGeometryComponent>()) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto& region = regionEntity.get_mut<StaticGeometryComponent>();
|
||||
destroyOgreStaticGeometry(region);
|
||||
}
|
||||
|
||||
Ogre::StaticGeometry* StaticGeometrySystem::getStaticGeometry(flecs::entity regionEntity)
|
||||
{
|
||||
if (!regionEntity.is_alive() || !regionEntity.has<StaticGeometryComponent>()) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return regionEntity.get<StaticGeometryComponent>().staticGeometry;
|
||||
}
|
||||
|
||||
bool StaticGeometrySystem::isRegionDirty(flecs::entity regionEntity)
|
||||
{
|
||||
if (!regionEntity.is_alive() || !regionEntity.has<StaticGeometryComponent>()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto& region = regionEntity.get<StaticGeometryComponent>();
|
||||
if (region.dirty) return true;
|
||||
|
||||
// Check if any members are dirty
|
||||
return areMembersDirty(regionEntity);
|
||||
}
|
||||
|
||||
bool StaticGeometrySystem::areMembersDirty(flecs::entity regionEntity)
|
||||
{
|
||||
bool dirty = false;
|
||||
m_memberQuery.each([&](flecs::entity e, StaticGeometryMemberComponent& member) {
|
||||
if (member.regionEntity == regionEntity && member.dirty) {
|
||||
dirty = true;
|
||||
}
|
||||
});
|
||||
return dirty;
|
||||
}
|
||||
|
||||
std::size_t StaticGeometrySystem::getMemberCount(flecs::entity regionEntity)
|
||||
{
|
||||
if (!regionEntity.is_alive() || !regionEntity.has<StaticGeometryComponent>()) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return regionEntity.get<StaticGeometryComponent>().memberEntityIds.size();
|
||||
}
|
||||
54
src/features/editScene/systems/StaticGeometrySystem.hpp
Normal file
54
src/features/editScene/systems/StaticGeometrySystem.hpp
Normal file
@@ -0,0 +1,54 @@
|
||||
#pragma once
|
||||
|
||||
#include <flecs.h>
|
||||
#include <memory>
|
||||
#include <Ogre.h>
|
||||
|
||||
class StaticGeometrySystem {
|
||||
public:
|
||||
StaticGeometrySystem(flecs::world& world, Ogre::SceneManager* sceneMgr);
|
||||
~StaticGeometrySystem();
|
||||
|
||||
// Initialize the system
|
||||
void initialize();
|
||||
|
||||
// Main update - processes dirty regions and rebuilds
|
||||
void update();
|
||||
|
||||
// Force rebuild of a specific region
|
||||
void rebuildRegion(flecs::entity regionEntity);
|
||||
|
||||
// Destroy a region's StaticGeometry
|
||||
void destroyRegion(flecs::entity regionEntity);
|
||||
|
||||
// Get the Ogre::StaticGeometry for a region (if built)
|
||||
Ogre::StaticGeometry* getStaticGeometry(flecs::entity regionEntity);
|
||||
|
||||
// Check if a region needs rebuild
|
||||
bool isRegionDirty(flecs::entity regionEntity);
|
||||
|
||||
// Get member count for a region
|
||||
std::size_t getMemberCount(flecs::entity regionEntity);
|
||||
|
||||
private:
|
||||
flecs::world& m_world;
|
||||
Ogre::SceneManager* m_sceneMgr;
|
||||
|
||||
// Queries
|
||||
flecs::query<struct StaticGeometryComponent> m_regionQuery;
|
||||
flecs::query<struct StaticGeometryMemberComponent> m_memberQuery;
|
||||
|
||||
bool m_initialized = false;
|
||||
|
||||
// Build a region's StaticGeometry
|
||||
void buildRegion(flecs::entity regionEntity, struct StaticGeometryComponent& region);
|
||||
|
||||
// Destroy Ogre StaticGeometry object
|
||||
void destroyOgreStaticGeometry(struct StaticGeometryComponent& region);
|
||||
|
||||
// Check if any members are dirty
|
||||
bool areMembersDirty(flecs::entity regionEntity);
|
||||
|
||||
// Validate region exists and is valid
|
||||
bool isValidRegion(flecs::entity regionEntity);
|
||||
};
|
||||
@@ -4,6 +4,10 @@
|
||||
#include <imgui.h>
|
||||
#include <flecs.h>
|
||||
|
||||
// Forward declarations for component editor templates
|
||||
struct StaticGeometryComponent;
|
||||
struct StaticGeometryMemberComponent;
|
||||
|
||||
/**
|
||||
* Base interface for component editors
|
||||
*/
|
||||
|
||||
99
src/features/editScene/ui/StaticGeometryEditor.cpp
Normal file
99
src/features/editScene/ui/StaticGeometryEditor.cpp
Normal file
@@ -0,0 +1,99 @@
|
||||
#include "StaticGeometryEditor.hpp"
|
||||
#include "../components/StaticGeometryMember.hpp"
|
||||
#include "../components/LodSettings.hpp"
|
||||
#include <imgui.h>
|
||||
#include <OgreStaticGeometry.h>
|
||||
|
||||
bool StaticGeometryEditor::renderComponent(flecs::entity entity, StaticGeometryComponent ®ion)
|
||||
{
|
||||
bool changed = false;
|
||||
|
||||
ImGui::Text("Region ID: %s", region.regionId.empty() ? "(none)" : region.regionId.c_str());
|
||||
|
||||
// Region name
|
||||
char nameBuf[256];
|
||||
strncpy(nameBuf, region.regionName.c_str(), sizeof(nameBuf) - 1);
|
||||
nameBuf[sizeof(nameBuf) - 1] = '\0';
|
||||
if (ImGui::InputText("Region Name", nameBuf, sizeof(nameBuf))) {
|
||||
region.regionName = nameBuf;
|
||||
region.markDirty();
|
||||
changed = true;
|
||||
}
|
||||
|
||||
ImGui::Separator();
|
||||
|
||||
// Check LOD consistency
|
||||
std::string regionLodId;
|
||||
bool lodConsistent = true;
|
||||
int lodMemberCount = 0;
|
||||
|
||||
entity.world().query<StaticGeometryMemberComponent>().each([&](flecs::entity e, StaticGeometryMemberComponent& member) {
|
||||
if (member.regionEntity == entity && !member.lodSettingsId.empty()) {
|
||||
lodMemberCount++;
|
||||
if (regionLodId.empty()) {
|
||||
regionLodId = member.lodSettingsId;
|
||||
} else if (regionLodId != member.lodSettingsId) {
|
||||
lodConsistent = false;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (lodMemberCount > 0) {
|
||||
if (lodConsistent) {
|
||||
ImGui::TextColored(ImVec4(0, 1, 0, 1), "LOD: Consistent (%s)", regionLodId.c_str());
|
||||
} else {
|
||||
ImGui::TextColored(ImVec4(1, 0, 0, 1), "LOD: INCONSISTENT!");
|
||||
ImGui::TextWrapped("All members must use the same LOD Settings!");
|
||||
}
|
||||
}
|
||||
|
||||
ImGui::Separator();
|
||||
|
||||
// Build status
|
||||
if (region.built) {
|
||||
ImGui::TextColored(ImVec4(0, 1, 0, 1), "Status: Built (v%d)", region.buildVersion);
|
||||
ImGui::Text("Members: %zu", region.memberEntityIds.size());
|
||||
} else if (region.dirty) {
|
||||
ImGui::TextColored(ImVec4(1, 1, 0, 1), "Status: Needs Rebuild");
|
||||
} else {
|
||||
ImGui::TextColored(ImVec4(0.5f, 0.5f, 0.5f, 1), "Status: Not Built");
|
||||
}
|
||||
|
||||
ImGui::Separator();
|
||||
|
||||
// Settings
|
||||
if (ImGui::Checkbox("Cast Shadows", ®ion.castShadows)) {
|
||||
region.markDirty();
|
||||
changed = true;
|
||||
}
|
||||
|
||||
if (ImGui::DragFloat("Rendering Distance", ®ion.renderingDistance, 10.0f, 0.0f, 10000.0f)) {
|
||||
region.markDirty();
|
||||
changed = true;
|
||||
}
|
||||
if (region.renderingDistance == 0.0f) {
|
||||
ImGui::SameLine();
|
||||
ImGui::TextDisabled("(infinite)");
|
||||
}
|
||||
|
||||
if (ImGui::DragFloat("Region Dimensions", ®ion.regionDimensions, 10.0f, 100.0f, 10000.0f)) {
|
||||
region.markDirty();
|
||||
changed = true;
|
||||
}
|
||||
|
||||
ImGui::Separator();
|
||||
|
||||
// Manual rebuild button
|
||||
if (ImGui::Button("Rebuild Now", ImVec2(120, 0))) {
|
||||
region.markDirty();
|
||||
changed = true;
|
||||
}
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button("Clear Geometry", ImVec2(120, 0))) {
|
||||
region.built = false;
|
||||
region.dirty = true;
|
||||
changed = true;
|
||||
}
|
||||
|
||||
return changed;
|
||||
}
|
||||
18
src/features/editScene/ui/StaticGeometryEditor.hpp
Normal file
18
src/features/editScene/ui/StaticGeometryEditor.hpp
Normal file
@@ -0,0 +1,18 @@
|
||||
#ifndef EDITSCENE_STATICGEOMETRYEDITOR_HPP
|
||||
#define EDITSCENE_STATICGEOMETRYEDITOR_HPP
|
||||
#pragma once
|
||||
|
||||
#include "ComponentEditor.hpp"
|
||||
#include "../components/StaticGeometry.hpp"
|
||||
#include <flecs.h>
|
||||
|
||||
/**
|
||||
* Editor for StaticGeometryComponent (region)
|
||||
*/
|
||||
class StaticGeometryEditor : public ComponentEditor<StaticGeometryComponent> {
|
||||
public:
|
||||
bool renderComponent(flecs::entity entity, StaticGeometryComponent ®ion) override;
|
||||
const char *getName() const override { return "StaticGeometry Region"; }
|
||||
};
|
||||
|
||||
#endif // EDITSCENE_STATICGEOMETRYEDITOR_HPP
|
||||
363
src/features/editScene/ui/StaticGeometryMemberEditor.cpp
Normal file
363
src/features/editScene/ui/StaticGeometryMemberEditor.cpp
Normal file
@@ -0,0 +1,363 @@
|
||||
#include "StaticGeometryMemberEditor.hpp"
|
||||
#include "../components/StaticGeometry.hpp"
|
||||
#include "../components/LodSettings.hpp"
|
||||
#include <imgui.h>
|
||||
#include <OgreResourceGroupManager.h>
|
||||
#include <algorithm>
|
||||
#include <cstring>
|
||||
|
||||
StaticGeometryMemberEditor::StaticGeometryMemberEditor(Ogre::SceneManager *sceneMgr)
|
||||
: m_sceneMgr(sceneMgr)
|
||||
{
|
||||
}
|
||||
|
||||
void StaticGeometryMemberEditor::scanMeshFiles()
|
||||
{
|
||||
if (m_meshesScanned)
|
||||
return;
|
||||
|
||||
m_meshFiles.clear();
|
||||
|
||||
// Get all resource groups
|
||||
Ogre::ResourceGroupManager &rgm = Ogre::ResourceGroupManager::getSingleton();
|
||||
Ogre::StringVector groups = rgm.getResourceGroups();
|
||||
|
||||
for (const auto &group : groups) {
|
||||
// Get file info for this group
|
||||
Ogre::FileInfoListPtr fileList = rgm.findResourceFileInfo(group, "*");
|
||||
|
||||
if (fileList) {
|
||||
for (const auto &fileInfo : *fileList) {
|
||||
const std::string &filename = fileInfo.filename;
|
||||
// Check for supported extensions
|
||||
if (filename.size() > 5) {
|
||||
std::string ext = filename.substr(filename.find_last_of('.') + 1);
|
||||
// Convert to lowercase
|
||||
std::transform(ext.begin(), ext.end(), ext.begin(), ::tolower);
|
||||
|
||||
if (ext == "mesh" || ext == "gltf" || ext == "glb") {
|
||||
m_meshFiles.push_back(filename);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Sort and remove duplicates
|
||||
std::sort(m_meshFiles.begin(), m_meshFiles.end());
|
||||
m_meshFiles.erase(std::unique(m_meshFiles.begin(), m_meshFiles.end()),
|
||||
m_meshFiles.end());
|
||||
|
||||
m_meshesScanned = true;
|
||||
}
|
||||
|
||||
bool StaticGeometryMemberEditor::matchesSearch(const std::string &meshName,
|
||||
const std::string &search)
|
||||
{
|
||||
if (search.empty())
|
||||
return true;
|
||||
|
||||
std::string lowerMesh = meshName;
|
||||
std::string lowerSearch = search;
|
||||
std::transform(lowerMesh.begin(), lowerMesh.end(), lowerMesh.begin(), ::tolower);
|
||||
std::transform(lowerSearch.begin(), lowerSearch.end(), lowerSearch.begin(), ::tolower);
|
||||
|
||||
return lowerMesh.find(lowerSearch) != std::string::npos;
|
||||
}
|
||||
|
||||
void StaticGeometryMemberEditor::renderMeshBrowser(StaticGeometryMemberComponent &member)
|
||||
{
|
||||
if (ImGui::BeginPopupModal("Mesh Browser (StaticGeometry)", &m_showMeshBrowser,
|
||||
ImGuiWindowFlags_AlwaysAutoResize)) {
|
||||
ImGui::Text("Search:");
|
||||
ImGui::SameLine();
|
||||
ImGui::InputText("##search", m_searchBuffer, sizeof(m_searchBuffer));
|
||||
|
||||
ImGui::Separator();
|
||||
|
||||
// Show filtered mesh list
|
||||
ImGui::BeginChild("MeshList", ImVec2(400, 300), true);
|
||||
|
||||
std::string searchStr(m_searchBuffer);
|
||||
|
||||
for (const auto &meshName : m_meshFiles) {
|
||||
if (!matchesSearch(meshName, searchStr))
|
||||
continue;
|
||||
|
||||
// Highlight current selection
|
||||
bool isCurrent = (member.meshName == meshName);
|
||||
if (isCurrent) {
|
||||
ImGui::PushStyleColor(ImGuiCol_Text,
|
||||
ImVec4(0.2f, 0.8f, 0.2f, 1.0f));
|
||||
}
|
||||
|
||||
if (ImGui::Selectable(meshName.c_str(), isCurrent)) {
|
||||
member.meshName = meshName;
|
||||
member.markDirty();
|
||||
}
|
||||
|
||||
if (isCurrent) {
|
||||
ImGui::PopStyleColor();
|
||||
}
|
||||
}
|
||||
|
||||
if (m_meshFiles.empty()) {
|
||||
ImGui::TextDisabled("No mesh files found");
|
||||
}
|
||||
|
||||
ImGui::EndChild();
|
||||
|
||||
ImGui::Separator();
|
||||
|
||||
// Show count
|
||||
int visibleCount = 0;
|
||||
for (const auto &meshName : m_meshFiles) {
|
||||
if (matchesSearch(meshName, searchStr))
|
||||
visibleCount++;
|
||||
}
|
||||
ImGui::Text("Showing %d of %zu meshes", visibleCount, m_meshFiles.size());
|
||||
|
||||
// Buttons
|
||||
if (ImGui::Button("Select", ImVec2(120, 0))) {
|
||||
m_showMeshBrowser = false;
|
||||
ImGui::CloseCurrentPopup();
|
||||
}
|
||||
|
||||
ImGui::SameLine();
|
||||
|
||||
if (ImGui::Button("Cancel", ImVec2(120, 0))) {
|
||||
m_showMeshBrowser = false;
|
||||
ImGui::CloseCurrentPopup();
|
||||
}
|
||||
|
||||
ImGui::SameLine();
|
||||
|
||||
if (ImGui::Button("Refresh", ImVec2(120, 0))) {
|
||||
m_meshesScanned = false;
|
||||
scanMeshFiles();
|
||||
}
|
||||
|
||||
ImGui::EndPopup();
|
||||
}
|
||||
}
|
||||
|
||||
void StaticGeometryMemberEditor::renderRegionSelector(flecs::entity entity, StaticGeometryMemberComponent& member)
|
||||
{
|
||||
// Get the world from the entity
|
||||
flecs::world world = entity.world();
|
||||
|
||||
// Collect all region entities
|
||||
std::vector<flecs::entity> regionEntities;
|
||||
std::vector<std::string> regionIds;
|
||||
std::vector<std::string> regionNames;
|
||||
|
||||
int currentIndex = -1;
|
||||
world.query<StaticGeometryComponent>().each([&](flecs::entity e, StaticGeometryComponent& region) {
|
||||
regionEntities.push_back(e);
|
||||
regionIds.push_back(region.regionId);
|
||||
regionNames.push_back(region.regionName.empty() ? "Region " + std::to_string(e.id()) : region.regionName);
|
||||
|
||||
if (member.regionEntity == e || member.regionId == region.regionId) {
|
||||
currentIndex = (int)regionEntities.size() - 1;
|
||||
}
|
||||
});
|
||||
|
||||
// Default to first region if none selected
|
||||
if (currentIndex == -1 && !regionEntities.empty()) {
|
||||
currentIndex = 0;
|
||||
member.regionEntity = regionEntities[0];
|
||||
member.regionId = regionIds[0];
|
||||
member.markDirty();
|
||||
}
|
||||
|
||||
// Build combo string
|
||||
std::string comboItems;
|
||||
for (size_t i = 0; i < regionNames.size(); ++i) {
|
||||
if (i > 0) comboItems += '\0';
|
||||
comboItems += regionNames[i] + " (" + regionIds[i] + ")";
|
||||
}
|
||||
comboItems += '\0';
|
||||
|
||||
int newIndex = currentIndex < 0 ? 0 : currentIndex;
|
||||
if (ImGui::Combo("Region", &newIndex, comboItems.c_str())) {
|
||||
if (newIndex >= 0 && newIndex < (int)regionEntities.size()) {
|
||||
member.regionEntity = regionEntities[newIndex];
|
||||
member.regionId = regionIds[newIndex];
|
||||
member.markDirty();
|
||||
}
|
||||
}
|
||||
|
||||
if (regionEntities.empty()) {
|
||||
ImGui::TextColored(ImVec4(1, 0, 0, 1), "No StaticGeometry regions! Create one first.");
|
||||
}
|
||||
}
|
||||
|
||||
void StaticGeometryMemberEditor::renderLodSettingsSelector(flecs::entity entity, StaticGeometryMemberComponent& member)
|
||||
{
|
||||
// Get the world from the entity
|
||||
flecs::world world = entity.world();
|
||||
|
||||
// Collect all LOD settings entities
|
||||
std::vector<flecs::entity> settingsEntities;
|
||||
std::vector<std::string> settingsIds;
|
||||
std::vector<std::string> displayNames;
|
||||
|
||||
int currentIndex = -1;
|
||||
int noneIndex = -1;
|
||||
|
||||
// Add "None" option
|
||||
displayNames.push_back("None (no LOD)");
|
||||
settingsEntities.push_back(flecs::entity::null());
|
||||
settingsIds.push_back("");
|
||||
noneIndex = 0;
|
||||
|
||||
world.query<LodSettingsComponent>().each([&](flecs::entity e, LodSettingsComponent& settings) {
|
||||
settingsEntities.push_back(e);
|
||||
settingsIds.push_back(settings.settingsId);
|
||||
|
||||
std::string displayName = settings.settingsId;
|
||||
if (displayName.empty()) {
|
||||
displayName = "LOD Settings " + std::to_string(e.id());
|
||||
}
|
||||
displayNames.push_back(displayName);
|
||||
|
||||
if (settings.settingsId == member.lodSettingsId) {
|
||||
currentIndex = (int)settingsEntities.size() - 1;
|
||||
member.lodSettingsEntity = e;
|
||||
}
|
||||
});
|
||||
|
||||
// If no selection but has ID, try to find it
|
||||
if (currentIndex == -1 && !member.lodSettingsId.empty()) {
|
||||
for (size_t i = 0; i < settingsIds.size(); ++i) {
|
||||
if (settingsIds[i] == member.lodSettingsId) {
|
||||
currentIndex = (int)i;
|
||||
member.lodSettingsEntity = settingsEntities[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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';
|
||||
comboItems += displayNames[i];
|
||||
}
|
||||
comboItems += '\0';
|
||||
|
||||
int newIndex = currentIndex;
|
||||
if (ImGui::Combo("LOD Settings", &newIndex, comboItems.c_str())) {
|
||||
if (newIndex >= 0 && newIndex < (int)settingsEntities.size()) {
|
||||
if (newIndex == noneIndex) {
|
||||
member.lodSettingsId.clear();
|
||||
member.lodSettingsEntity = flecs::entity::null();
|
||||
} else {
|
||||
member.lodSettingsId = settingsIds[newIndex];
|
||||
member.lodSettingsEntity = settingsEntities[newIndex];
|
||||
}
|
||||
member.markDirty();
|
||||
}
|
||||
}
|
||||
|
||||
// Show warning if no LOD settings available
|
||||
if (settingsEntities.size() <= 1) {
|
||||
ImGui::TextDisabled("Create a LOD Settings entity to enable LOD");
|
||||
}
|
||||
|
||||
// Show info about selected settings
|
||||
if (!member.lodSettingsId.empty() && member.lodSettingsEntity.is_alive()) {
|
||||
const auto& settings = member.lodSettingsEntity.get<LodSettingsComponent>();
|
||||
ImGui::TextDisabled("Levels: %zu, Strategy: %s",
|
||||
settings.lodLevels.size(),
|
||||
settings.strategy == LodSettingsComponent::Strategy::Distance ? "Distance" :
|
||||
settings.strategy == LodSettingsComponent::Strategy::PixelCount ? "Pixel Count" : "Edge Pixel");
|
||||
}
|
||||
}
|
||||
|
||||
bool StaticGeometryMemberEditor::renderComponent(flecs::entity entity, StaticGeometryMemberComponent &member)
|
||||
{
|
||||
bool changed = false;
|
||||
|
||||
// Region selection
|
||||
renderRegionSelector(entity, member);
|
||||
|
||||
ImGui::Separator();
|
||||
|
||||
// LOD Settings selection
|
||||
renderLodSettingsSelector(entity, member);
|
||||
|
||||
ImGui::Separator();
|
||||
|
||||
// Mesh selection with browser
|
||||
char meshBuf[256];
|
||||
std::strncpy(meshBuf, member.meshName.c_str(), sizeof(meshBuf) - 1);
|
||||
meshBuf[sizeof(meshBuf) - 1] = '\0';
|
||||
|
||||
if (ImGui::InputText("Mesh Name", meshBuf, sizeof(meshBuf))) {
|
||||
member.meshName = meshBuf;
|
||||
member.markDirty();
|
||||
changed = true;
|
||||
}
|
||||
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button("Browse...")) {
|
||||
scanMeshFiles();
|
||||
m_showMeshBrowser = true;
|
||||
m_searchBuffer[0] = '\0';
|
||||
}
|
||||
|
||||
// Mesh Browser Popup
|
||||
if (m_showMeshBrowser) {
|
||||
ImGui::OpenPopup("Mesh Browser (StaticGeometry)");
|
||||
}
|
||||
|
||||
renderMeshBrowser(member);
|
||||
|
||||
ImGui::Separator();
|
||||
|
||||
// Material input (simple text input)
|
||||
char matBuf[256];
|
||||
std::strncpy(matBuf, member.materialName.c_str(), sizeof(matBuf) - 1);
|
||||
matBuf[sizeof(matBuf) - 1] = '\0';
|
||||
|
||||
if (ImGui::InputText("Material Override", matBuf, sizeof(matBuf))) {
|
||||
member.materialName = matBuf;
|
||||
member.markDirty();
|
||||
changed = true;
|
||||
}
|
||||
ImGui::TextDisabled("(Empty = use mesh default material)");
|
||||
|
||||
ImGui::Separator();
|
||||
|
||||
// Options
|
||||
if (ImGui::Checkbox("Cast Shadows", &member.castShadows)) {
|
||||
member.markDirty();
|
||||
changed = true;
|
||||
}
|
||||
|
||||
if (ImGui::Checkbox("Visible", &member.visible)) {
|
||||
member.markDirty();
|
||||
changed = true;
|
||||
}
|
||||
|
||||
// Info notes
|
||||
ImGui::Separator();
|
||||
ImGui::TextDisabled("Note: Position/Orientation uses Transform component's SceneNode.");
|
||||
ImGui::TextDisabled("LOD: All members in a region must use the same LOD Settings!");
|
||||
|
||||
// Status
|
||||
ImGui::Separator();
|
||||
if (member.dirty) {
|
||||
ImGui::TextColored(ImVec4(1, 1, 0, 1), "Status: Modified (needs rebuild)");
|
||||
} else {
|
||||
ImGui::TextColored(ImVec4(0, 1, 0, 1), "Status: Up to date");
|
||||
}
|
||||
|
||||
return changed;
|
||||
}
|
||||
35
src/features/editScene/ui/StaticGeometryMemberEditor.hpp
Normal file
35
src/features/editScene/ui/StaticGeometryMemberEditor.hpp
Normal file
@@ -0,0 +1,35 @@
|
||||
#ifndef EDITSCENE_STATICGEOMETRYMEMBEREDITOR_HPP
|
||||
#define EDITSCENE_STATICGEOMETRYMEMBEREDITOR_HPP
|
||||
#pragma once
|
||||
|
||||
#include "ComponentEditor.hpp"
|
||||
#include "../components/StaticGeometryMember.hpp"
|
||||
#include <OgreSceneManager.h>
|
||||
#include <flecs.h>
|
||||
#include <vector>
|
||||
#include <string>
|
||||
|
||||
/**
|
||||
* Editor for StaticGeometryMemberComponent
|
||||
*/
|
||||
class StaticGeometryMemberEditor : public ComponentEditor<StaticGeometryMemberComponent> {
|
||||
public:
|
||||
StaticGeometryMemberEditor(Ogre::SceneManager *sceneMgr = nullptr);
|
||||
bool renderComponent(flecs::entity entity, StaticGeometryMemberComponent &member) override;
|
||||
const char *getName() const override { return "StaticGeometry Member"; }
|
||||
|
||||
private:
|
||||
void renderRegionSelector(flecs::entity entity, StaticGeometryMemberComponent& member);
|
||||
void renderLodSettingsSelector(flecs::entity entity, StaticGeometryMemberComponent& member);
|
||||
void renderMeshBrowser(StaticGeometryMemberComponent &member);
|
||||
void scanMeshFiles();
|
||||
bool matchesSearch(const std::string &meshName, const std::string &search);
|
||||
|
||||
Ogre::SceneManager *m_sceneMgr;
|
||||
std::vector<std::string> m_meshFiles;
|
||||
bool m_meshesScanned = false;
|
||||
bool m_showMeshBrowser = false;
|
||||
char m_searchBuffer[256] = {};
|
||||
};
|
||||
|
||||
#endif // EDITSCENE_STATICGEOMETRYMEMBEREDITOR_HPP
|
||||
Reference in New Issue
Block a user