Fixed LOD support
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
project(editScene)
|
||||
set(CMAKE_CXX_STANDARD 17)
|
||||
|
||||
find_package(OGRE REQUIRED COMPONENTS Bites Overlay CONFIG)
|
||||
find_package(OGRE REQUIRED COMPONENTS Bites Overlay MeshLodGenerator CONFIG)
|
||||
find_package(flecs REQUIRED CONFIG)
|
||||
find_package(nlohmann_json REQUIRED)
|
||||
find_package(SDL2 REQUIRED)
|
||||
@@ -16,15 +16,19 @@ set(EDITSCENE_SOURCES
|
||||
systems/PhysicsSystem.cpp
|
||||
systems/LightSystem.cpp
|
||||
systems/CameraSystem.cpp
|
||||
systems/LodSystem.cpp
|
||||
ui/TransformEditor.cpp
|
||||
ui/RenderableEditor.cpp
|
||||
ui/PhysicsColliderEditor.cpp
|
||||
ui/RigidBodyEditor.cpp
|
||||
ui/LightEditor.cpp
|
||||
ui/CameraEditor.cpp
|
||||
ui/LodEditor.cpp
|
||||
ui/LodSettingsEditor.cpp
|
||||
ui/ComponentRegistration.cpp
|
||||
components/LightModule.cpp
|
||||
components/CameraModule.cpp
|
||||
components/LodModule.cpp
|
||||
camera/EditorCamera.cpp
|
||||
gizmo/Gizmo.cpp
|
||||
physics/physics.cpp
|
||||
@@ -40,11 +44,14 @@ set(EDITSCENE_HEADERS
|
||||
components/RigidBody.hpp
|
||||
components/Light.hpp
|
||||
components/Camera.hpp
|
||||
components/Lod.hpp
|
||||
components/LodSettings.hpp
|
||||
systems/EditorUISystem.hpp
|
||||
systems/SceneSerializer.hpp
|
||||
systems/PhysicsSystem.hpp
|
||||
systems/LightSystem.hpp
|
||||
systems/CameraSystem.hpp
|
||||
systems/LodSystem.hpp
|
||||
ui/ComponentEditor.hpp
|
||||
ui/ComponentRegistry.hpp
|
||||
ui/ComponentRegistration.hpp
|
||||
@@ -54,6 +61,8 @@ set(EDITSCENE_HEADERS
|
||||
ui/RigidBodyEditor.hpp
|
||||
ui/LightEditor.hpp
|
||||
ui/CameraEditor.hpp
|
||||
ui/LodEditor.hpp
|
||||
ui/LodSettingsEditor.hpp
|
||||
camera/EditorCamera.hpp
|
||||
gizmo/Gizmo.hpp
|
||||
physics/physics.h
|
||||
@@ -68,6 +77,7 @@ target_link_libraries(editSceneEditor
|
||||
OgreMain
|
||||
OgreBites
|
||||
OgreOverlay
|
||||
OgreMeshLodGenerator
|
||||
flecs::flecs_static
|
||||
nlohmann_json::nlohmann_json
|
||||
Jolt::Jolt
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
#include "systems/PhysicsSystem.hpp"
|
||||
#include "systems/LightSystem.hpp"
|
||||
#include "systems/CameraSystem.hpp"
|
||||
#include "systems/LodSystem.hpp"
|
||||
#include "camera/EditorCamera.hpp"
|
||||
#include "components/EntityName.hpp"
|
||||
#include "components/Transform.hpp"
|
||||
@@ -13,6 +14,8 @@
|
||||
#include "components/RigidBody.hpp"
|
||||
#include "components/Light.hpp"
|
||||
#include "components/Camera.hpp"
|
||||
#include "components/Lod.hpp"
|
||||
#include "components/LodSettings.hpp"
|
||||
#include <OgreRTShaderSystem.h>
|
||||
#include <imgui.h>
|
||||
|
||||
@@ -121,6 +124,10 @@ void EditorApp::setup()
|
||||
|
||||
// Setup camera system
|
||||
m_cameraSystem = std::make_unique<EditorCameraSystem>(m_world, m_sceneMgr);
|
||||
|
||||
// Setup LOD system
|
||||
m_lodSystem = std::make_unique<EditorLodSystem>(m_world, m_sceneMgr);
|
||||
m_lodSystem->initialize();
|
||||
|
||||
// Add default entities to UI cache
|
||||
for (auto &e : m_defaultEntities) {
|
||||
@@ -157,6 +164,10 @@ void EditorApp::setupECS()
|
||||
// Register light and camera components
|
||||
m_world.component<LightComponent>();
|
||||
m_world.component<CameraComponent>();
|
||||
|
||||
// Register LOD components
|
||||
m_world.component<LodComponent>();
|
||||
m_world.component<LodSettingsComponent>();
|
||||
}
|
||||
|
||||
void EditorApp::createDefaultEntities()
|
||||
@@ -294,6 +305,11 @@ bool EditorApp::frameRenderingQueued(const Ogre::FrameEvent &evt)
|
||||
if (m_cameraSystem) {
|
||||
m_cameraSystem->update();
|
||||
}
|
||||
|
||||
// Update LOD system
|
||||
if (m_lodSystem) {
|
||||
m_lodSystem->update();
|
||||
}
|
||||
|
||||
// Don't call base class - it crashes when iterating input listeners
|
||||
return true;
|
||||
|
||||
@@ -16,6 +16,7 @@ class EditorCamera;
|
||||
class EditorPhysicsSystem;
|
||||
class EditorLightSystem;
|
||||
class EditorCameraSystem;
|
||||
class EditorLodSystem;
|
||||
|
||||
/**
|
||||
* RenderTargetListener for ImGui frame management
|
||||
@@ -84,6 +85,7 @@ private:
|
||||
std::unique_ptr<EditorPhysicsSystem> m_physicsSystem;
|
||||
std::unique_ptr<EditorLightSystem> m_lightSystem;
|
||||
std::unique_ptr<EditorCameraSystem> m_cameraSystem;
|
||||
std::unique_ptr<EditorLodSystem> m_lodSystem;
|
||||
|
||||
// State
|
||||
uint16_t m_currentModifiers;
|
||||
|
||||
47
src/features/editScene/components/Lod.hpp
Normal file
47
src/features/editScene/components/Lod.hpp
Normal file
@@ -0,0 +1,47 @@
|
||||
#ifndef EDITSCENE_LOD_HPP
|
||||
#define EDITSCENE_LOD_HPP
|
||||
#pragma once
|
||||
|
||||
#include <Ogre.h>
|
||||
#include <flecs.h>
|
||||
#include <string>
|
||||
#include "LodSettings.hpp"
|
||||
|
||||
/**
|
||||
* LodComponent - Per-entity LOD configuration
|
||||
*
|
||||
* This component references a LodSettingsComponent by its settingsId
|
||||
* for persistent identification across scene loads.
|
||||
*/
|
||||
struct LodComponent {
|
||||
// Reference to the settings by ID (persistent across scene loads)
|
||||
std::string settingsId;
|
||||
|
||||
// Runtime entity reference (resolved from settingsId)
|
||||
flecs::entity settingsEntity = flecs::entity::null();
|
||||
|
||||
// Per-entity distance multiplier (scales all LOD distances)
|
||||
float distanceMultiplier = 1.0f;
|
||||
|
||||
// Whether LOD has been applied to the mesh
|
||||
bool lodApplied = false;
|
||||
|
||||
// Last settings version that was applied
|
||||
uint32_t appliedVersion = 0;
|
||||
|
||||
// Dirty flag for regenerating LOD
|
||||
bool dirty = true;
|
||||
|
||||
void markDirty() { dirty = true; }
|
||||
|
||||
// Helper to check if LOD needs to be regenerated
|
||||
bool needsUpdate() const { return dirty; }
|
||||
|
||||
// Helper to check if settings reference is valid
|
||||
bool hasValidSettings() const {
|
||||
return settingsEntity.is_alive() &&
|
||||
settingsEntity.has<LodSettingsComponent>();
|
||||
}
|
||||
};
|
||||
|
||||
#endif // EDITSCENE_LOD_HPP
|
||||
56
src/features/editScene/components/LodModule.cpp
Normal file
56
src/features/editScene/components/LodModule.cpp
Normal file
@@ -0,0 +1,56 @@
|
||||
#include "Lod.hpp"
|
||||
#include "LodSettings.hpp"
|
||||
#include "Renderable.hpp"
|
||||
#include "../ui/ComponentRegistration.hpp"
|
||||
#include "../ui/LodEditor.hpp"
|
||||
#include "../ui/LodSettingsEditor.hpp"
|
||||
|
||||
// Register LodSettings component
|
||||
REGISTER_COMPONENT("LOD Settings", LodSettingsComponent, LodSettingsEditor)
|
||||
{
|
||||
registry.registerComponent<LodSettingsComponent>(
|
||||
"LOD Settings",
|
||||
std::make_unique<LodSettingsEditor>(),
|
||||
// Adder
|
||||
[sceneMgr](flecs::entity e) {
|
||||
if (!e.has<LodSettingsComponent>()) {
|
||||
auto settings = LodSettingsComponent();
|
||||
// Create default LOD levels
|
||||
settings.createDefaultLevels();
|
||||
e.set<LodSettingsComponent>(settings);
|
||||
}
|
||||
},
|
||||
// Remover
|
||||
[sceneMgr](flecs::entity e) {
|
||||
if (e.has<LodSettingsComponent>()) {
|
||||
e.remove<LodSettingsComponent>();
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
// Register Lod component
|
||||
REGISTER_COMPONENT("LOD", LodComponent, LodEditor)
|
||||
{
|
||||
registry.registerComponent<LodComponent>(
|
||||
"LOD",
|
||||
std::make_unique<LodEditor>(),
|
||||
// Adder
|
||||
[sceneMgr](flecs::entity e) {
|
||||
if (!e.has<LodComponent>()) {
|
||||
// LOD requires Renderable
|
||||
if (!e.has<RenderableComponent>()) {
|
||||
// Auto-add renderable if missing (will need mesh assignment later)
|
||||
e.set<RenderableComponent>({});
|
||||
}
|
||||
e.set<LodComponent>({});
|
||||
}
|
||||
},
|
||||
// Remover
|
||||
[sceneMgr](flecs::entity e) {
|
||||
if (e.has<LodComponent>()) {
|
||||
e.remove<LodComponent>();
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
107
src/features/editScene/components/LodSettings.hpp
Normal file
107
src/features/editScene/components/LodSettings.hpp
Normal file
@@ -0,0 +1,107 @@
|
||||
#ifndef EDITSCENE_LODSETTINGS_HPP
|
||||
#define EDITSCENE_LODSETTINGS_HPP
|
||||
#pragma once
|
||||
|
||||
#include <Ogre.h>
|
||||
#include <vector>
|
||||
#include <string>
|
||||
|
||||
/**
|
||||
* LOD Level configuration - represents one LOD level
|
||||
*/
|
||||
struct LodLevelConfig {
|
||||
// Distance at which to switch to this LOD level
|
||||
float distance = 100.0f;
|
||||
|
||||
// Reduction method
|
||||
enum class ReductionMethod {
|
||||
Proportional, // 0.0-1.0 percentage of vertices to remove
|
||||
Constant, // Exact vertex count to remove
|
||||
CollapseCost // Collapse cost threshold
|
||||
};
|
||||
ReductionMethod reductionMethod = ReductionMethod::Proportional;
|
||||
|
||||
// Reduction value (meaning depends on method)
|
||||
float reductionValue = 0.5f; // 50% reduction by default
|
||||
|
||||
// Optional: manual mesh name (if using manual LOD)
|
||||
std::string manualMeshName;
|
||||
|
||||
bool useManualMesh = false;
|
||||
};
|
||||
|
||||
/**
|
||||
* LodSettingsComponent - Shareable LOD configuration
|
||||
*
|
||||
* This component can be attached to a "settings" entity and referenced
|
||||
* by multiple entities with LodComponent for shared LOD settings.
|
||||
*/
|
||||
struct LodSettingsComponent {
|
||||
// Unique identifier for this LOD settings (for referencing across scenes)
|
||||
std::string settingsId;
|
||||
|
||||
// Strategy for determining LOD level based on distance
|
||||
enum class Strategy {
|
||||
Distance, // Distance from camera
|
||||
PixelCount, // Screen-space pixel count
|
||||
EdgePixelCount // Screen-space edge pixel count
|
||||
};
|
||||
Strategy strategy = Strategy::Distance;
|
||||
|
||||
// LOD levels (from highest quality to lowest)
|
||||
// Level 0 is always the original mesh
|
||||
std::vector<LodLevelConfig> lodLevels;
|
||||
|
||||
// Advanced options
|
||||
bool useCompression = true; // Compress index buffers
|
||||
bool useVertexNormals = true; // Use normals for quality
|
||||
bool preventPunchingHoles = false; // Prevent destroying triangles
|
||||
bool preventBreakingLines = false; // Prevent destroying lines
|
||||
bool useBackgroundQueue = false; // Generate LODs in background
|
||||
|
||||
float outsideWeight = 0.0f; // Weight for outside faces (0 = disabled)
|
||||
float outsideWalkAngle = 0.0f; // Angle for outside walking (-1 to 1)
|
||||
|
||||
// Flag to track if settings have been modified
|
||||
bool dirty = true;
|
||||
|
||||
// Version counter for tracking changes
|
||||
uint32_t version = 1;
|
||||
|
||||
void markDirty() {
|
||||
dirty = true;
|
||||
version++;
|
||||
}
|
||||
|
||||
// Helper to add a proportional reduction level
|
||||
void addProportionalLevel(float distance, float reductionPercent) {
|
||||
LodLevelConfig level;
|
||||
level.distance = distance;
|
||||
level.reductionMethod = LodLevelConfig::ReductionMethod::Proportional;
|
||||
level.reductionValue = reductionPercent;
|
||||
lodLevels.push_back(level);
|
||||
markDirty();
|
||||
}
|
||||
|
||||
// Helper to add a constant reduction level
|
||||
void addConstantLevel(float distance, int verticesToRemove) {
|
||||
LodLevelConfig level;
|
||||
level.distance = distance;
|
||||
level.reductionMethod = LodLevelConfig::ReductionMethod::Constant;
|
||||
level.reductionValue = static_cast<float>(verticesToRemove);
|
||||
lodLevels.push_back(level);
|
||||
markDirty();
|
||||
}
|
||||
|
||||
// Helper to create default LOD levels for a mesh
|
||||
void createDefaultLevels(float baseDistance = 100.0f) {
|
||||
lodLevels.clear();
|
||||
// LOD 1: 50% reduction at base distance
|
||||
addProportionalLevel(baseDistance, 0.5f);
|
||||
// LOD 2: 75% reduction at 2x distance
|
||||
addProportionalLevel(baseDistance * 2.0f, 0.75f);
|
||||
markDirty();
|
||||
}
|
||||
};
|
||||
|
||||
#endif // EDITSCENE_LODSETTINGS_HPP
|
||||
@@ -7,6 +7,8 @@
|
||||
#include "../components/RigidBody.hpp"
|
||||
#include "../components/Light.hpp"
|
||||
#include "../components/Camera.hpp"
|
||||
#include "../components/Lod.hpp"
|
||||
#include "../components/LodSettings.hpp"
|
||||
#include "../ui/TransformEditor.hpp"
|
||||
#include "../ui/RenderableEditor.hpp"
|
||||
#include "../ui/PhysicsColliderEditor.hpp"
|
||||
@@ -361,6 +363,10 @@ void EditorUISystem::renderEntityNode(flecs::entity entity, int depth)
|
||||
indicators += " [RB]";
|
||||
if (entity.has<PhysicsColliderComponent>())
|
||||
indicators += " [C]";
|
||||
if (entity.has<LodSettingsComponent>())
|
||||
indicators += " [LODS]";
|
||||
if (entity.has<LodComponent>())
|
||||
indicators += " [LOD]";
|
||||
|
||||
snprintf(label, sizeof(label), "%s%s##%llu", name.c_str(),
|
||||
indicators.c_str(), (unsigned long long)entity.id());
|
||||
@@ -513,6 +519,20 @@ void EditorUISystem::renderComponentList(flecs::entity entity)
|
||||
m_componentRegistry.render<PhysicsColliderComponent>(entity, collider);
|
||||
componentCount++;
|
||||
}
|
||||
|
||||
// Render LOD Settings if present
|
||||
if (entity.has<LodSettingsComponent>()) {
|
||||
auto &lodSettings = entity.get_mut<LodSettingsComponent>();
|
||||
m_componentRegistry.render<LodSettingsComponent>(entity, lodSettings);
|
||||
componentCount++;
|
||||
}
|
||||
|
||||
// Render LOD if present
|
||||
if (entity.has<LodComponent>()) {
|
||||
auto &lod = entity.get_mut<LodComponent>();
|
||||
m_componentRegistry.render<LodComponent>(entity, lod);
|
||||
componentCount++;
|
||||
}
|
||||
|
||||
// Show message if no components
|
||||
if (componentCount == 0) {
|
||||
|
||||
250
src/features/editScene/systems/LodSystem.cpp
Normal file
250
src/features/editScene/systems/LodSystem.cpp
Normal file
@@ -0,0 +1,250 @@
|
||||
#include "LodSystem.hpp"
|
||||
#include "../components/Lod.hpp"
|
||||
#include "../components/LodSettings.hpp"
|
||||
#include "../components/Renderable.hpp"
|
||||
#include <OgreLogManager.h>
|
||||
#include <OGRE/MeshLodGenerator/OgreMeshLodGenerator.h>
|
||||
#include <OgreDistanceLodStrategy.h>
|
||||
#include <OgrePixelCountLodStrategy.h>
|
||||
|
||||
EditorLodSystem::EditorLodSystem(flecs::world &world,
|
||||
Ogre::SceneManager *sceneMgr)
|
||||
: m_world(world)
|
||||
, m_sceneMgr(sceneMgr)
|
||||
, m_lodQuery(world.query<LodComponent, RenderableComponent>())
|
||||
, m_settingsQuery(world.query<LodSettingsComponent>())
|
||||
{
|
||||
}
|
||||
|
||||
EditorLodSystem::~EditorLodSystem() = default;
|
||||
|
||||
void EditorLodSystem::initialize()
|
||||
{
|
||||
if (m_initialized)
|
||||
return;
|
||||
|
||||
// Create the LOD generator singleton
|
||||
m_lodGenerator = std::make_unique<Ogre::MeshLodGenerator>();
|
||||
|
||||
m_initialized = true;
|
||||
}
|
||||
|
||||
void EditorLodSystem::update()
|
||||
{
|
||||
if (!m_initialized)
|
||||
return;
|
||||
|
||||
// Process entities with LOD components
|
||||
m_lodQuery.each([&](flecs::entity entity, LodComponent &lod,
|
||||
RenderableComponent &renderable) {
|
||||
// Try to resolve settings entity if we have an ID but no valid entity
|
||||
if (!lod.settingsId.empty() && !lod.hasValidSettings()) {
|
||||
m_world.query<LodSettingsComponent>().each(
|
||||
[&](flecs::entity e,
|
||||
LodSettingsComponent &settings) {
|
||||
if (settings.settingsId ==
|
||||
lod.settingsId) {
|
||||
lod.settingsEntity = e;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Fallback: if we have a valid settingsEntity but no settingsId, get the ID from the settings
|
||||
if (lod.settingsId.empty() && lod.settingsEntity.is_alive() &&
|
||||
lod.settingsEntity.has<LodSettingsComponent>()) {
|
||||
const auto &settings =
|
||||
lod.settingsEntity.get<LodSettingsComponent>();
|
||||
if (!settings.settingsId.empty()) {
|
||||
lod.settingsId = settings.settingsId;
|
||||
}
|
||||
}
|
||||
|
||||
// Final fallback: if settingsId is still empty, try to use any available LOD settings
|
||||
if (lod.settingsId.empty() && !lod.hasValidSettings()) {
|
||||
m_world.query<LodSettingsComponent>().each(
|
||||
[&](flecs::entity e,
|
||||
LodSettingsComponent &settings) {
|
||||
if (lod.settingsEntity ==
|
||||
flecs::entity::null()) {
|
||||
lod.settingsEntity = e;
|
||||
lod.settingsId =
|
||||
settings.settingsId;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Check if we need to apply/update LOD
|
||||
bool needsUpdate = lod.dirty;
|
||||
|
||||
// Check if settings have been modified
|
||||
if (lod.hasValidSettings()) {
|
||||
const auto &settings =
|
||||
lod.settingsEntity.get<LodSettingsComponent>();
|
||||
if (settings.version != lod.appliedVersion) {
|
||||
needsUpdate = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Check if mesh changed
|
||||
if (!lod.lodApplied && !renderable.meshName.empty()) {
|
||||
needsUpdate = true;
|
||||
}
|
||||
|
||||
if (needsUpdate) {
|
||||
applyLod(entity, lod, renderable);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void EditorLodSystem::applyLod(flecs::entity entity, LodComponent &lod,
|
||||
RenderableComponent &renderable)
|
||||
{
|
||||
// Skip if no settings entity
|
||||
if (!lod.settingsEntity.is_alive() ||
|
||||
!lod.settingsEntity.has<LodSettingsComponent>()) {
|
||||
lod.lodApplied = false;
|
||||
lod.dirty = false;
|
||||
return;
|
||||
}
|
||||
|
||||
// Get the settings
|
||||
const auto &settings = lod.settingsEntity.get<LodSettingsComponent>();
|
||||
|
||||
// Skip if no LOD levels configured
|
||||
if (settings.lodLevels.empty()) {
|
||||
lod.lodApplied = false;
|
||||
lod.dirty = false;
|
||||
return;
|
||||
}
|
||||
|
||||
// Need a loaded mesh
|
||||
if (renderable.meshName.empty() || !renderable.entity) {
|
||||
lod.dirty = false; // Will retry when mesh is loaded
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
Ogre::MeshPtr mesh = renderable.entity->getMesh();
|
||||
if (!mesh || !mesh->isLoaded()) {
|
||||
lod.dirty = false;
|
||||
return;
|
||||
}
|
||||
|
||||
// Build Ogre LOD config
|
||||
Ogre::LodConfig lodConfig;
|
||||
buildOgreLodConfig(mesh, settings, lodConfig);
|
||||
|
||||
// Apply distance multiplier
|
||||
if (lod.distanceMultiplier != 1.0f) {
|
||||
for (auto &level : lodConfig.levels) {
|
||||
level.distance *= lod.distanceMultiplier;
|
||||
}
|
||||
}
|
||||
|
||||
// Generate LOD levels
|
||||
Ogre::LogManager::getSingleton().logMessage(
|
||||
"Generating LOD levels for: " + renderable.meshName);
|
||||
m_lodGenerator->generateLodLevels(lodConfig);
|
||||
|
||||
lod.lodApplied = true;
|
||||
lod.appliedVersion = settings.version;
|
||||
lod.dirty = false;
|
||||
|
||||
} catch (const Ogre::Exception &e) {
|
||||
Ogre::LogManager::getSingleton().logMessage(
|
||||
"ERROR: Failed to generate LOD for " +
|
||||
renderable.meshName + ": " + e.getDescription());
|
||||
lod.dirty = false; // Don't retry immediately
|
||||
}
|
||||
}
|
||||
|
||||
void EditorLodSystem::buildOgreLodConfig(Ogre::MeshPtr mesh,
|
||||
const LodSettingsComponent &settings,
|
||||
Ogre::LodConfig &outConfig)
|
||||
{
|
||||
outConfig.mesh = mesh;
|
||||
|
||||
// Set strategy
|
||||
switch (settings.strategy) {
|
||||
case LodSettingsComponent::Strategy::Distance:
|
||||
outConfig.strategy =
|
||||
Ogre::DistanceLodBoxStrategy::getSingletonPtr();
|
||||
break;
|
||||
case LodSettingsComponent::Strategy::PixelCount:
|
||||
outConfig.strategy =
|
||||
Ogre::PixelCountLodStrategy::getSingletonPtr();
|
||||
break;
|
||||
case LodSettingsComponent::Strategy::EdgePixelCount:
|
||||
// Fallback to pixel count
|
||||
outConfig.strategy =
|
||||
Ogre::PixelCountLodStrategy::getSingletonPtr();
|
||||
break;
|
||||
}
|
||||
|
||||
// Build LOD levels
|
||||
outConfig.levels.clear();
|
||||
for (const auto &levelConfig : settings.lodLevels) {
|
||||
Ogre::LodLevel level;
|
||||
level.distance = levelConfig.distance;
|
||||
|
||||
if (levelConfig.useManualMesh &&
|
||||
!levelConfig.manualMeshName.empty()) {
|
||||
level.manualMeshName = levelConfig.manualMeshName;
|
||||
} else {
|
||||
// Set reduction method
|
||||
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;
|
||||
default:
|
||||
level.reductionMethod =
|
||||
Ogre::LodLevel::VRM_PROPORTIONAL;
|
||||
break;
|
||||
}
|
||||
level.reductionValue = levelConfig.reductionValue;
|
||||
}
|
||||
|
||||
outConfig.levels.push_back(level);
|
||||
}
|
||||
|
||||
// Advanced settings
|
||||
outConfig.advanced.useCompression = settings.useCompression;
|
||||
outConfig.advanced.useVertexNormals = settings.useVertexNormals;
|
||||
outConfig.advanced.preventPunchingHoles = settings.preventPunchingHoles;
|
||||
outConfig.advanced.preventBreakingLines = settings.preventBreakingLines;
|
||||
outConfig.advanced.useBackgroundQueue = settings.useBackgroundQueue;
|
||||
outConfig.advanced.outsideWeight = settings.outsideWeight;
|
||||
outConfig.advanced.outsideWalkAngle = settings.outsideWalkAngle;
|
||||
}
|
||||
|
||||
void EditorLodSystem::regenerateLodForSettings(flecs::entity settingsEntity)
|
||||
{
|
||||
if (!settingsEntity.is_alive() ||
|
||||
!settingsEntity.has<LodSettingsComponent>()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Mark all LOD components using these settings as dirty
|
||||
m_lodQuery.each([&](flecs::entity entity, LodComponent &lod,
|
||||
RenderableComponent &renderable) {
|
||||
if (lod.settingsEntity == settingsEntity) {
|
||||
lod.markDirty();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
bool EditorLodSystem::hasLodGenerated(Ogre::MeshPtr mesh) const
|
||||
{
|
||||
if (!mesh)
|
||||
return false;
|
||||
return mesh->getNumLodLevels() > 1;
|
||||
}
|
||||
60
src/features/editScene/systems/LodSystem.hpp
Normal file
60
src/features/editScene/systems/LodSystem.hpp
Normal file
@@ -0,0 +1,60 @@
|
||||
#ifndef EDITSCENE_LODSYSTEM_HPP
|
||||
#define EDITSCENE_LODSYSTEM_HPP
|
||||
#pragma once
|
||||
|
||||
#include <flecs.h>
|
||||
#include <Ogre.h>
|
||||
#include <OGRE/MeshLodGenerator/OgreMeshLodGenerator.h>
|
||||
|
||||
/**
|
||||
* LOD System - manages LOD generation for meshes
|
||||
*
|
||||
* This system:
|
||||
* 1. Tracks entities with LodComponent and RenderableComponent
|
||||
* 2. Applies LOD settings from LodSettingsComponent when meshes are loaded
|
||||
* 3. Regenerates LOD when settings change
|
||||
*/
|
||||
class EditorLodSystem {
|
||||
public:
|
||||
EditorLodSystem(flecs::world& world, Ogre::SceneManager* sceneMgr);
|
||||
~EditorLodSystem();
|
||||
|
||||
// Initialize the LOD generator
|
||||
void initialize();
|
||||
|
||||
// Update - process pending LOD operations
|
||||
void update();
|
||||
|
||||
// Force regenerate LOD for all meshes using specific settings
|
||||
void regenerateLodForSettings(flecs::entity settingsEntity);
|
||||
|
||||
// Check if a mesh has LOD generated
|
||||
bool hasLodGenerated(Ogre::MeshPtr mesh) const;
|
||||
|
||||
private:
|
||||
// Apply LOD to a renderable entity
|
||||
void applyLod(flecs::entity entity,
|
||||
class LodComponent& lod,
|
||||
class RenderableComponent& renderable);
|
||||
|
||||
// Convert our config to Ogre::LodConfig
|
||||
void buildOgreLodConfig(Ogre::MeshPtr mesh,
|
||||
const class LodSettingsComponent& settings,
|
||||
Ogre::LodConfig& outConfig);
|
||||
|
||||
flecs::world& m_world;
|
||||
Ogre::SceneManager* m_sceneMgr;
|
||||
|
||||
// The Ogre LOD generator
|
||||
std::unique_ptr<Ogre::MeshLodGenerator> m_lodGenerator;
|
||||
|
||||
// Query for entities with LOD and renderable
|
||||
flecs::query<class LodComponent, class RenderableComponent> m_lodQuery;
|
||||
|
||||
// Query for settings entities
|
||||
flecs::query<class LodSettingsComponent> m_settingsQuery;
|
||||
|
||||
bool m_initialized = false;
|
||||
};
|
||||
|
||||
#endif // EDITSCENE_LODSYSTEM_HPP
|
||||
@@ -7,6 +7,8 @@
|
||||
#include "../components/PhysicsCollider.hpp"
|
||||
#include "../components/Light.hpp"
|
||||
#include "../components/Camera.hpp"
|
||||
#include "../components/Lod.hpp"
|
||||
#include "../components/LodSettings.hpp"
|
||||
#include "EditorUISystem.hpp"
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
@@ -84,6 +86,8 @@ bool SceneSerializer::loadFromFile(const std::string& filepath, EditorUISystem*
|
||||
|
||||
// Load entities
|
||||
if (scene.contains("entities") && scene["entities"].is_array()) {
|
||||
Ogre::LogManager::getSingleton().logMessage(
|
||||
"SceneSerializer: Loading " + Ogre::StringConverter::toString(scene["entities"].size()) + " entities");
|
||||
for (const auto& entityJson : scene["entities"]) {
|
||||
deserializeEntity(entityJson, flecs::entity::null(), uiSystem);
|
||||
}
|
||||
@@ -133,6 +137,14 @@ nlohmann::json SceneSerializer::serializeEntity(flecs::entity entity)
|
||||
json["camera"] = serializeCamera(entity);
|
||||
}
|
||||
|
||||
if (entity.has<LodSettingsComponent>()) {
|
||||
json["lodSettings"] = serializeLodSettings(entity);
|
||||
}
|
||||
|
||||
if (entity.has<LodComponent>()) {
|
||||
json["lod"] = serializeLod(entity);
|
||||
}
|
||||
|
||||
// Serialize children
|
||||
json["children"] = nlohmann::json::array();
|
||||
entity.children([&](flecs::entity child) {
|
||||
@@ -192,6 +204,16 @@ void SceneSerializer::deserializeEntity(const nlohmann::json& json, flecs::entit
|
||||
deserializeCamera(entity, json["camera"]);
|
||||
}
|
||||
|
||||
if (json.contains("lodSettings")) {
|
||||
Ogre::LogManager::getSingleton().logMessage("SceneSerializer: Found lodSettings for entity " + Ogre::StringConverter::toString(entity.id()));
|
||||
deserializeLodSettings(entity, json["lodSettings"]);
|
||||
}
|
||||
|
||||
if (json.contains("lod")) {
|
||||
Ogre::LogManager::getSingleton().logMessage("SceneSerializer: Found lod for entity " + Ogre::StringConverter::toString(entity.id()));
|
||||
deserializeLod(entity, json["lod"]);
|
||||
}
|
||||
|
||||
// Add to UI system if provided
|
||||
if (uiSystem) {
|
||||
uiSystem->addEntity(entity);
|
||||
@@ -664,3 +686,176 @@ void SceneSerializer::deserializeCamera(flecs::entity entity, const nlohmann::js
|
||||
|
||||
entity.set<CameraComponent>(camera);
|
||||
}
|
||||
|
||||
|
||||
nlohmann::json SceneSerializer::serializeLodSettings(flecs::entity entity)
|
||||
{
|
||||
auto& settings = entity.get<LodSettingsComponent>();
|
||||
nlohmann::json json;
|
||||
|
||||
// Serialize settings ID
|
||||
json["settingsId"] = settings.settingsId;
|
||||
|
||||
// Serialize strategy
|
||||
switch (settings.strategy) {
|
||||
case LodSettingsComponent::Strategy::Distance:
|
||||
json["strategy"] = "distance";
|
||||
break;
|
||||
case LodSettingsComponent::Strategy::PixelCount:
|
||||
json["strategy"] = "pixelCount";
|
||||
break;
|
||||
case LodSettingsComponent::Strategy::EdgePixelCount:
|
||||
json["strategy"] = "edgePixelCount";
|
||||
break;
|
||||
}
|
||||
|
||||
// Serialize LOD levels
|
||||
json["lodLevels"] = nlohmann::json::array();
|
||||
for (const auto& level : settings.lodLevels) {
|
||||
nlohmann::json levelJson;
|
||||
levelJson["distance"] = level.distance;
|
||||
|
||||
switch (level.reductionMethod) {
|
||||
case LodLevelConfig::ReductionMethod::Proportional:
|
||||
levelJson["reductionMethod"] = "proportional";
|
||||
break;
|
||||
case LodLevelConfig::ReductionMethod::Constant:
|
||||
levelJson["reductionMethod"] = "constant";
|
||||
break;
|
||||
case LodLevelConfig::ReductionMethod::CollapseCost:
|
||||
levelJson["reductionMethod"] = "collapseCost";
|
||||
break;
|
||||
}
|
||||
|
||||
levelJson["reductionValue"] = level.reductionValue;
|
||||
levelJson["useManualMesh"] = level.useManualMesh;
|
||||
levelJson["manualMeshName"] = level.manualMeshName;
|
||||
|
||||
json["lodLevels"].push_back(levelJson);
|
||||
}
|
||||
|
||||
// Serialize advanced options
|
||||
json["useCompression"] = settings.useCompression;
|
||||
json["useVertexNormals"] = settings.useVertexNormals;
|
||||
json["preventPunchingHoles"] = settings.preventPunchingHoles;
|
||||
json["preventBreakingLines"] = settings.preventBreakingLines;
|
||||
json["useBackgroundQueue"] = settings.useBackgroundQueue;
|
||||
json["outsideWeight"] = settings.outsideWeight;
|
||||
json["outsideWalkAngle"] = settings.outsideWalkAngle;
|
||||
|
||||
return json;
|
||||
}
|
||||
|
||||
nlohmann::json SceneSerializer::serializeLod(flecs::entity entity)
|
||||
{
|
||||
auto& lod = entity.get<LodComponent>();
|
||||
nlohmann::json json;
|
||||
|
||||
// Serialize settings ID (persistent across scene loads)
|
||||
json["settingsId"] = lod.settingsId;
|
||||
|
||||
json["distanceMultiplier"] = lod.distanceMultiplier;
|
||||
|
||||
return json;
|
||||
}
|
||||
|
||||
void SceneSerializer::deserializeLodSettings(flecs::entity entity, const nlohmann::json& json)
|
||||
{
|
||||
LodSettingsComponent settings;
|
||||
|
||||
// Deserialize settings ID
|
||||
settings.settingsId = json.value("settingsId", "");
|
||||
Ogre::LogManager::getSingleton().logMessage(
|
||||
"Deserialize LodSettings: loaded settingsId=" + settings.settingsId);
|
||||
|
||||
// Auto-generate ID if empty
|
||||
if (settings.settingsId.empty()) {
|
||||
settings.settingsId = "lod_settings_" + std::to_string(entity.id());
|
||||
Ogre::LogManager::getSingleton().logMessage(
|
||||
"Deserialize LodSettings: auto-generated settingsId=" + settings.settingsId);
|
||||
}
|
||||
|
||||
// Deserialize strategy
|
||||
std::string strategy = json.value("strategy", "distance");
|
||||
if (strategy == "distance") {
|
||||
settings.strategy = LodSettingsComponent::Strategy::Distance;
|
||||
} else if (strategy == "pixelCount") {
|
||||
settings.strategy = LodSettingsComponent::Strategy::PixelCount;
|
||||
} else if (strategy == "edgePixelCount") {
|
||||
settings.strategy = LodSettingsComponent::Strategy::EdgePixelCount;
|
||||
}
|
||||
|
||||
// Deserialize LOD levels
|
||||
settings.lodLevels.clear();
|
||||
if (json.contains("lodLevels") && json["lodLevels"].is_array()) {
|
||||
for (const auto& levelJson : json["lodLevels"]) {
|
||||
LodLevelConfig level;
|
||||
level.distance = levelJson.value("distance", 100.0f);
|
||||
|
||||
std::string method = levelJson.value("reductionMethod", "proportional");
|
||||
if (method == "proportional") {
|
||||
level.reductionMethod = LodLevelConfig::ReductionMethod::Proportional;
|
||||
} else if (method == "constant") {
|
||||
level.reductionMethod = LodLevelConfig::ReductionMethod::Constant;
|
||||
} else if (method == "collapseCost") {
|
||||
level.reductionMethod = LodLevelConfig::ReductionMethod::CollapseCost;
|
||||
}
|
||||
|
||||
level.reductionValue = levelJson.value("reductionValue", 0.5f);
|
||||
level.useManualMesh = levelJson.value("useManualMesh", false);
|
||||
level.manualMeshName = levelJson.value("manualMeshName", "");
|
||||
|
||||
settings.lodLevels.push_back(level);
|
||||
}
|
||||
}
|
||||
|
||||
// Deserialize advanced options
|
||||
settings.useCompression = json.value("useCompression", true);
|
||||
settings.useVertexNormals = json.value("useVertexNormals", true);
|
||||
settings.preventPunchingHoles = json.value("preventPunchingHoles", false);
|
||||
settings.preventBreakingLines = json.value("preventBreakingLines", false);
|
||||
settings.useBackgroundQueue = json.value("useBackgroundQueue", false);
|
||||
settings.outsideWeight = json.value("outsideWeight", 0.0f);
|
||||
settings.outsideWalkAngle = json.value("outsideWalkAngle", 0.0f);
|
||||
|
||||
// Mark as dirty so LOD system will apply settings
|
||||
settings.markDirty();
|
||||
|
||||
entity.set<LodSettingsComponent>(settings);
|
||||
}
|
||||
|
||||
void SceneSerializer::deserializeLod(flecs::entity entity, const nlohmann::json& json)
|
||||
{
|
||||
LodComponent lod;
|
||||
|
||||
// Deserialize settings ID
|
||||
lod.settingsId = json.value("settingsId", "");
|
||||
Ogre::LogManager::getSingleton().logMessage(
|
||||
"Deserialize LOD: settingsId=" + lod.settingsId);
|
||||
|
||||
// Try to find the settings entity by ID
|
||||
if (!lod.settingsId.empty()) {
|
||||
m_world.query<LodSettingsComponent>().each([&](flecs::entity e, LodSettingsComponent& settings) {
|
||||
Ogre::LogManager::getSingleton().logMessage(
|
||||
"Deserialize LOD: checking settings entity " +
|
||||
Ogre::StringConverter::toString(e.id()) +
|
||||
" with settingsId=" + settings.settingsId);
|
||||
if (settings.settingsId == lod.settingsId) {
|
||||
Ogre::LogManager::getSingleton().logMessage(
|
||||
"Deserialize LOD: MATCH FOUND!");
|
||||
lod.settingsEntity = e;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
lod.distanceMultiplier = json.value("distanceMultiplier", 1.0f);
|
||||
lod.lodApplied = false;
|
||||
lod.dirty = true;
|
||||
|
||||
entity.set<LodComponent>(lod);
|
||||
|
||||
Ogre::LogManager::getSingleton().logMessage(
|
||||
"Deserialize LOD: Final settingsEntity=" +
|
||||
Ogre::StringConverter::toString(lod.settingsEntity.id()) +
|
||||
", hasValid=" + (lod.hasValidSettings() ? "true" : "false"));
|
||||
}
|
||||
|
||||
@@ -46,6 +46,8 @@ private:
|
||||
nlohmann::json serializeCollider(flecs::entity entity);
|
||||
nlohmann::json serializeLight(flecs::entity entity);
|
||||
nlohmann::json serializeCamera(flecs::entity entity);
|
||||
nlohmann::json serializeLodSettings(flecs::entity entity);
|
||||
nlohmann::json serializeLod(flecs::entity entity);
|
||||
|
||||
// Component deserialization
|
||||
void deserializeTransform(flecs::entity entity, const nlohmann::json& json, flecs::entity parentEntity);
|
||||
@@ -55,6 +57,8 @@ private:
|
||||
void deserializeCollider(flecs::entity entity, const nlohmann::json& json);
|
||||
void deserializeLight(flecs::entity entity, const nlohmann::json& json);
|
||||
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);
|
||||
|
||||
flecs::world& m_world;
|
||||
Ogre::SceneManager* m_sceneMgr;
|
||||
|
||||
149
src/features/editScene/ui/LodEditor.cpp
Normal file
149
src/features/editScene/ui/LodEditor.cpp
Normal file
@@ -0,0 +1,149 @@
|
||||
#include "LodEditor.hpp"
|
||||
#include "../components/LodSettings.hpp"
|
||||
#include <imgui.h>
|
||||
#include <vector>
|
||||
#include <string>
|
||||
|
||||
void LodEditor::resolveSettingsEntity(LodComponent& lod)
|
||||
{
|
||||
// If we have a settingsId but no valid entity, try to resolve it
|
||||
if (!lod.settingsId.empty() && !lod.hasValidSettings()) {
|
||||
// This would need access to the world to query - handled in renderSettingsSelector
|
||||
}
|
||||
}
|
||||
|
||||
void LodEditor::renderSettingsSelector(flecs::entity entity, LodComponent& lod)
|
||||
{
|
||||
// Get the world from the entity
|
||||
flecs::world world = entity.world();
|
||||
|
||||
// Collect all entities with LodSettingsComponent
|
||||
std::vector<flecs::entity> settingsEntities;
|
||||
std::vector<std::string> settingsIds;
|
||||
std::vector<std::string> displayNames;
|
||||
int currentIndex = -1;
|
||||
|
||||
world.query<LodSettingsComponent>().each([&](flecs::entity e, LodSettingsComponent& settings) {
|
||||
settingsEntities.push_back(e);
|
||||
settingsIds.push_back(settings.settingsId);
|
||||
|
||||
// Build display name
|
||||
std::string displayName = settings.settingsId;
|
||||
if (displayName.empty()) {
|
||||
displayName = "LOD Settings " + std::to_string(e.id());
|
||||
}
|
||||
displayNames.push_back(displayName);
|
||||
|
||||
// Check if this is the currently selected settings
|
||||
if (settings.settingsId == lod.settingsId) {
|
||||
currentIndex = static_cast<int>(settingsEntities.size()) - 1;
|
||||
lod.settingsEntity = e; // Update runtime reference
|
||||
}
|
||||
});
|
||||
|
||||
if (settingsEntities.empty()) {
|
||||
ImGui::TextColored(ImVec4(1.0f, 0.0f, 0.0f, 1.0f), "No LOD Settings entities found!");
|
||||
ImGui::Text("Create an entity with LOD Settings component first.");
|
||||
return;
|
||||
}
|
||||
|
||||
// Build combo items string (null-terminated)
|
||||
std::string comboItems;
|
||||
for (size_t i = 0; i < displayNames.size(); i++) {
|
||||
if (i > 0) comboItems += '\0';
|
||||
comboItems += displayNames[i];
|
||||
}
|
||||
comboItems += '\0';
|
||||
|
||||
// Show "None" if no selection
|
||||
if (currentIndex < 0) {
|
||||
// Try to find by entity reference as fallback
|
||||
for (size_t i = 0; i < settingsEntities.size(); i++) {
|
||||
if (settingsEntities[i] == lod.settingsEntity) {
|
||||
currentIndex = static_cast<int>(i);
|
||||
lod.settingsId = settingsIds[i]; // Update ID from entity
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// If still not found, show -1 (None)
|
||||
if (currentIndex < 0) {
|
||||
// Prepend "None" option
|
||||
comboItems = "None\0" + comboItems;
|
||||
currentIndex = 0;
|
||||
}
|
||||
}
|
||||
|
||||
int newIndex = currentIndex;
|
||||
if (ImGui::Combo("LOD Settings", &newIndex, comboItems.c_str())) {
|
||||
if (newIndex >= 0 && newIndex < static_cast<int>(settingsEntities.size())) {
|
||||
lod.settingsId = settingsIds[newIndex];
|
||||
lod.settingsEntity = settingsEntities[newIndex];
|
||||
lod.markDirty();
|
||||
} else if (newIndex == 0 && currentIndex < 0) {
|
||||
// Selected "None"
|
||||
lod.settingsId.clear();
|
||||
lod.settingsEntity = flecs::entity::null();
|
||||
lod.markDirty();
|
||||
}
|
||||
}
|
||||
|
||||
// Show info about selected settings
|
||||
if (lod.hasValidSettings()) {
|
||||
const auto& settings = lod.settingsEntity.get<LodSettingsComponent>();
|
||||
ImGui::Text("Levels: %zu", settings.lodLevels.size());
|
||||
ImGui::Text("Strategy: %s",
|
||||
settings.strategy == LodSettingsComponent::Strategy::Distance ? "Distance" :
|
||||
settings.strategy == LodSettingsComponent::Strategy::PixelCount ? "Pixel Count" : "Edge Pixel");
|
||||
}
|
||||
|
||||
// Show the persistent ID
|
||||
if (!lod.settingsId.empty()) {
|
||||
ImGui::TextDisabled("Settings ID: %s", lod.settingsId.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
bool LodEditor::renderComponent(flecs::entity entity, LodComponent &lod)
|
||||
{
|
||||
bool modified = false;
|
||||
|
||||
if (ImGui::CollapsingHeader("LOD", ImGuiTreeNodeFlags_DefaultOpen)) {
|
||||
ImGui::Indent();
|
||||
|
||||
// Settings selector with combo box
|
||||
renderSettingsSelector(entity, lod);
|
||||
|
||||
ImGui::Separator();
|
||||
|
||||
// Distance multiplier
|
||||
if (ImGui::DragFloat("Distance Multiplier", &lod.distanceMultiplier, 0.1f, 0.1f, 10.0f)) {
|
||||
lod.markDirty();
|
||||
modified = true;
|
||||
}
|
||||
ImGui::SetItemTooltip("Scales all LOD distances for this entity");
|
||||
|
||||
ImGui::Separator();
|
||||
|
||||
// Status
|
||||
if (lod.lodApplied) {
|
||||
ImGui::TextColored(ImVec4(0.0f, 1.0f, 0.0f, 1.0f), "LOD Applied");
|
||||
ImGui::Text("Settings Version: %u", lod.appliedVersion);
|
||||
} else {
|
||||
ImGui::TextColored(ImVec4(1.0f, 0.5f, 0.0f, 1.0f), "LOD Not Applied");
|
||||
}
|
||||
|
||||
if (lod.dirty) {
|
||||
ImGui::TextColored(ImVec4(1.0f, 0.5f, 0.0f, 1.0f), "(update pending)");
|
||||
}
|
||||
|
||||
// Force regenerate button
|
||||
if (ImGui::Button("Force Regenerate LOD")) {
|
||||
lod.markDirty();
|
||||
modified = true;
|
||||
}
|
||||
|
||||
ImGui::Unindent();
|
||||
}
|
||||
|
||||
return modified;
|
||||
}
|
||||
22
src/features/editScene/ui/LodEditor.hpp
Normal file
22
src/features/editScene/ui/LodEditor.hpp
Normal file
@@ -0,0 +1,22 @@
|
||||
#ifndef EDITSCENE_LODEDITOR_HPP
|
||||
#define EDITSCENE_LODEDITOR_HPP
|
||||
#pragma once
|
||||
|
||||
#include "ComponentEditor.hpp"
|
||||
#include "../components/Lod.hpp"
|
||||
#include <flecs.h>
|
||||
|
||||
/**
|
||||
* Editor for LodComponent with combo box selector for LOD Settings
|
||||
*/
|
||||
class LodEditor : public ComponentEditor<LodComponent> {
|
||||
public:
|
||||
bool renderComponent(flecs::entity entity, LodComponent &lod) override;
|
||||
const char *getName() const override { return "LOD"; }
|
||||
|
||||
private:
|
||||
void renderSettingsSelector(flecs::entity entity, LodComponent& lod);
|
||||
void resolveSettingsEntity(LodComponent& lod);
|
||||
};
|
||||
|
||||
#endif // EDITSCENE_LODEDITOR_HPP
|
||||
199
src/features/editScene/ui/LodSettingsEditor.cpp
Normal file
199
src/features/editScene/ui/LodSettingsEditor.cpp
Normal file
@@ -0,0 +1,199 @@
|
||||
#include "LodSettingsEditor.hpp"
|
||||
#include <imgui.h>
|
||||
#include <cstring>
|
||||
|
||||
void LodSettingsEditor::renderLodLevel(LodLevelConfig& level, int index)
|
||||
{
|
||||
ImGui::PushID(index);
|
||||
|
||||
if (ImGui::TreeNode("Level", "LOD Level %d", index + 1)) {
|
||||
// Distance
|
||||
if (ImGui::DragFloat("Distance", &level.distance, 1.0f, 0.0f, 10000.0f)) {
|
||||
// Mark settings as dirty through parent
|
||||
}
|
||||
|
||||
// Manual mesh option
|
||||
if (ImGui::Checkbox("Use Manual Mesh", &level.useManualMesh)) {
|
||||
//
|
||||
}
|
||||
|
||||
if (level.useManualMesh) {
|
||||
char meshName[256];
|
||||
std::strncpy(meshName, level.manualMeshName.c_str(), sizeof(meshName) - 1);
|
||||
meshName[sizeof(meshName) - 1] = '\0';
|
||||
if (ImGui::InputText("Mesh Name", meshName, sizeof(meshName))) {
|
||||
level.manualMeshName = meshName;
|
||||
}
|
||||
} else {
|
||||
// Reduction method
|
||||
const char* methods[] = { "Proportional", "Constant", "Collapse Cost" };
|
||||
int currentMethod = static_cast<int>(level.reductionMethod);
|
||||
if (ImGui::Combo("Method", ¤tMethod, methods, IM_ARRAYSIZE(methods))) {
|
||||
level.reductionMethod = static_cast<LodLevelConfig::ReductionMethod>(currentMethod);
|
||||
}
|
||||
|
||||
// Reduction value
|
||||
switch (level.reductionMethod) {
|
||||
case LodLevelConfig::ReductionMethod::Proportional:
|
||||
if (ImGui::SliderFloat("Reduction %", &level.reductionValue, 0.0f, 1.0f, "%.2f")) {
|
||||
//
|
||||
}
|
||||
ImGui::TextDisabled("0.0 = no reduction, 1.0 = all vertices removed");
|
||||
break;
|
||||
|
||||
case LodLevelConfig::ReductionMethod::Constant:
|
||||
if (ImGui::DragInt("Vertices to Remove", reinterpret_cast<int*>(&level.reductionValue), 1, 0, 100000)) {
|
||||
//
|
||||
}
|
||||
break;
|
||||
|
||||
case LodLevelConfig::ReductionMethod::CollapseCost:
|
||||
if (ImGui::DragFloat("Max Collapse Cost", &level.reductionValue, 0.1f, 0.0f, 1000.0f)) {
|
||||
//
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Remove button
|
||||
if (ImGui::Button("Remove Level")) {
|
||||
// This is tricky with ImGui - we mark for removal instead
|
||||
level.distance = -1.0f; // Mark for removal
|
||||
}
|
||||
|
||||
ImGui::TreePop();
|
||||
}
|
||||
|
||||
ImGui::PopID();
|
||||
}
|
||||
|
||||
bool LodSettingsEditor::renderComponent(flecs::entity entity, LodSettingsComponent &settings)
|
||||
{
|
||||
bool modified = false;
|
||||
|
||||
if (ImGui::CollapsingHeader("LOD Settings", ImGuiTreeNodeFlags_DefaultOpen)) {
|
||||
ImGui::Indent();
|
||||
|
||||
// Settings ID (for referencing from LOD components)
|
||||
char idBuffer[256];
|
||||
std::strncpy(idBuffer, settings.settingsId.c_str(), sizeof(idBuffer) - 1);
|
||||
idBuffer[sizeof(idBuffer) - 1] = '\0';
|
||||
if (ImGui::InputText("Settings ID", idBuffer, sizeof(idBuffer))) {
|
||||
settings.settingsId = idBuffer;
|
||||
// Note: We don't mark dirty here as this doesn't affect LOD generation
|
||||
modified = true;
|
||||
}
|
||||
if (ImGui::IsItemHovered()) {
|
||||
ImGui::SetTooltip("Unique identifier used by LOD components to reference these settings");
|
||||
}
|
||||
|
||||
// Auto-generate ID button if empty
|
||||
if (settings.settingsId.empty()) {
|
||||
if (ImGui::Button("Generate ID")) {
|
||||
settings.settingsId = "lod_settings_" + std::to_string(entity.id());
|
||||
modified = true;
|
||||
}
|
||||
}
|
||||
|
||||
ImGui::Separator();
|
||||
|
||||
// Strategy selector
|
||||
const char* strategies[] = { "Distance", "Pixel Count", "Edge Pixel Count" };
|
||||
int currentStrategy = static_cast<int>(settings.strategy);
|
||||
if (ImGui::Combo("Strategy", ¤tStrategy, strategies, IM_ARRAYSIZE(strategies))) {
|
||||
settings.strategy = static_cast<LodSettingsComponent::Strategy>(currentStrategy);
|
||||
settings.markDirty();
|
||||
modified = true;
|
||||
}
|
||||
|
||||
ImGui::Separator();
|
||||
|
||||
// LOD Levels
|
||||
ImGui::Text("LOD Levels (%zu configured):", settings.lodLevels.size());
|
||||
|
||||
// Remove marked levels
|
||||
settings.lodLevels.erase(
|
||||
std::remove_if(settings.lodLevels.begin(), settings.lodLevels.end(),
|
||||
[](const LodLevelConfig& level) { return level.distance < 0.0f; }),
|
||||
settings.lodLevels.end());
|
||||
|
||||
// Render levels
|
||||
for (size_t i = 0; i < settings.lodLevels.size(); i++) {
|
||||
bool levelModified = false;
|
||||
float oldDistance = settings.lodLevels[i].distance;
|
||||
|
||||
renderLodLevel(settings.lodLevels[i], static_cast<int>(i));
|
||||
|
||||
if (settings.lodLevels[i].distance != oldDistance) {
|
||||
levelModified = true;
|
||||
}
|
||||
|
||||
if (levelModified) {
|
||||
settings.markDirty();
|
||||
modified = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Add level button
|
||||
if (ImGui::Button("Add LOD Level")) {
|
||||
float distance = settings.lodLevels.empty() ? 100.0f :
|
||||
settings.lodLevels.back().distance * 2.0f;
|
||||
settings.addProportionalLevel(distance, 0.5f);
|
||||
modified = true;
|
||||
}
|
||||
|
||||
ImGui::SameLine();
|
||||
|
||||
// Auto-configure button
|
||||
if (ImGui::Button("Reset to Defaults")) {
|
||||
settings.createDefaultLevels();
|
||||
modified = true;
|
||||
}
|
||||
|
||||
ImGui::Separator();
|
||||
|
||||
// Advanced options
|
||||
if (ImGui::TreeNode("Advanced Options")) {
|
||||
if (ImGui::Checkbox("Use Compression", &settings.useCompression)) {
|
||||
settings.markDirty();
|
||||
modified = true;
|
||||
}
|
||||
ImGui::SetItemTooltip("Compress index buffers for smaller memory footprint");
|
||||
|
||||
if (ImGui::Checkbox("Use Vertex Normals", &settings.useVertexNormals)) {
|
||||
settings.markDirty();
|
||||
modified = true;
|
||||
}
|
||||
ImGui::SetItemTooltip("Use vertex normals for better quality (slower generation)");
|
||||
|
||||
if (ImGui::Checkbox("Prevent Punching Holes", &settings.preventPunchingHoles)) {
|
||||
settings.markDirty();
|
||||
modified = true;
|
||||
}
|
||||
|
||||
if (ImGui::Checkbox("Prevent Breaking Lines", &settings.preventBreakingLines)) {
|
||||
settings.markDirty();
|
||||
modified = true;
|
||||
}
|
||||
|
||||
if (ImGui::Checkbox("Background Queue", &settings.useBackgroundQueue)) {
|
||||
settings.markDirty();
|
||||
modified = true;
|
||||
}
|
||||
ImGui::SetItemTooltip("Generate LODs in background (requires manual injection)");
|
||||
|
||||
ImGui::TreePop();
|
||||
}
|
||||
|
||||
// Info
|
||||
ImGui::Separator();
|
||||
ImGui::Text("Version: %u", settings.version);
|
||||
if (settings.dirty) {
|
||||
ImGui::TextColored(ImVec4(1.0f, 0.5f, 0.0f, 1.0f), "Settings modified - will apply to meshes");
|
||||
}
|
||||
|
||||
ImGui::Unindent();
|
||||
}
|
||||
|
||||
return modified;
|
||||
}
|
||||
20
src/features/editScene/ui/LodSettingsEditor.hpp
Normal file
20
src/features/editScene/ui/LodSettingsEditor.hpp
Normal file
@@ -0,0 +1,20 @@
|
||||
#ifndef EDITSCENE_LODSETTINGSEDITOR_HPP
|
||||
#define EDITSCENE_LODSETTINGSEDITOR_HPP
|
||||
#pragma once
|
||||
|
||||
#include "ComponentEditor.hpp"
|
||||
#include "../components/LodSettings.hpp"
|
||||
|
||||
/**
|
||||
* Editor for LodSettingsComponent
|
||||
*/
|
||||
class LodSettingsEditor : public ComponentEditor<LodSettingsComponent> {
|
||||
public:
|
||||
bool renderComponent(flecs::entity entity, LodSettingsComponent &settings) override;
|
||||
const char *getName() const override { return "LOD Settings"; }
|
||||
|
||||
private:
|
||||
void renderLodLevel(LodLevelConfig& level, int index);
|
||||
};
|
||||
|
||||
#endif // EDITSCENE_LODSETTINGSEDITOR_HPP
|
||||
Reference in New Issue
Block a user