TriangleBuffer
This commit is contained in:
@@ -20,6 +20,8 @@ set(EDITSCENE_SOURCES
|
||||
systems/LodSystem.cpp
|
||||
systems/StaticGeometrySystem.cpp
|
||||
systems/ProceduralTextureSystem.cpp
|
||||
systems/ProceduralMaterialSystem.cpp
|
||||
systems/ProceduralMeshSystem.cpp
|
||||
ui/TransformEditor.cpp
|
||||
ui/RenderableEditor.cpp
|
||||
ui/PhysicsColliderEditor.cpp
|
||||
@@ -31,12 +33,18 @@ set(EDITSCENE_SOURCES
|
||||
ui/StaticGeometryEditor.cpp
|
||||
ui/StaticGeometryMemberEditor.cpp
|
||||
ui/ProceduralTextureEditor.cpp
|
||||
ui/ProceduralMaterialEditor.cpp
|
||||
ui/PrimitiveEditor.cpp
|
||||
ui/TriangleBufferEditor.cpp
|
||||
ui/ComponentRegistration.cpp
|
||||
components/LightModule.cpp
|
||||
components/CameraModule.cpp
|
||||
components/LodModule.cpp
|
||||
components/StaticGeometryModule.cpp
|
||||
components/ProceduralTextureModule.cpp
|
||||
components/ProceduralMaterialModule.cpp
|
||||
components/PrimitiveModule.cpp
|
||||
components/TriangleBufferModule.cpp
|
||||
camera/EditorCamera.cpp
|
||||
gizmo/Gizmo.cpp
|
||||
physics/physics.cpp
|
||||
@@ -57,7 +65,12 @@ set(EDITSCENE_HEADERS
|
||||
components/StaticGeometry.hpp
|
||||
components/StaticGeometryMember.hpp
|
||||
components/ProceduralTexture.hpp
|
||||
components/ProceduralMaterial.hpp
|
||||
components/Primitive.hpp
|
||||
components/TriangleBuffer.hpp
|
||||
systems/EditorUISystem.hpp
|
||||
systems/ProceduralMaterialSystem.hpp
|
||||
systems/ProceduralMeshSystem.hpp
|
||||
systems/ProceduralTextureSystem.hpp
|
||||
systems/StaticGeometrySystem.hpp
|
||||
systems/SceneSerializer.hpp
|
||||
@@ -79,6 +92,9 @@ set(EDITSCENE_HEADERS
|
||||
ui/StaticGeometryEditor.hpp
|
||||
ui/StaticGeometryMemberEditor.hpp
|
||||
ui/ProceduralTextureEditor.hpp
|
||||
ui/ProceduralMaterialEditor.hpp
|
||||
ui/PrimitiveEditor.hpp
|
||||
ui/TriangleBufferEditor.hpp
|
||||
camera/EditorCamera.hpp
|
||||
gizmo/Gizmo.hpp
|
||||
physics/physics.h
|
||||
|
||||
@@ -7,6 +7,8 @@
|
||||
#include "systems/LodSystem.hpp"
|
||||
#include "systems/StaticGeometrySystem.hpp"
|
||||
#include "systems/ProceduralTextureSystem.hpp"
|
||||
#include "systems/ProceduralMaterialSystem.hpp"
|
||||
#include "systems/ProceduralMeshSystem.hpp"
|
||||
#include "camera/EditorCamera.hpp"
|
||||
#include "components/EntityName.hpp"
|
||||
#include "components/Transform.hpp"
|
||||
@@ -21,6 +23,9 @@
|
||||
#include "components/StaticGeometry.hpp"
|
||||
#include "components/StaticGeometryMember.hpp"
|
||||
#include "components/ProceduralTexture.hpp"
|
||||
#include "components/ProceduralMaterial.hpp"
|
||||
#include "components/Primitive.hpp"
|
||||
#include "components/TriangleBuffer.hpp"
|
||||
#include <OgreRTShaderSystem.h>
|
||||
#include <imgui.h>
|
||||
|
||||
@@ -83,6 +88,8 @@ EditorApp::~EditorApp()
|
||||
});
|
||||
|
||||
// Release all systems
|
||||
m_proceduralMeshSystem.reset();
|
||||
m_proceduralMaterialSystem.reset();
|
||||
m_proceduralTextureSystem.reset();
|
||||
m_staticGeometrySystem.reset();
|
||||
m_lodSystem.reset();
|
||||
@@ -164,6 +171,14 @@ void EditorApp::setup()
|
||||
// Setup ProceduralTexture system
|
||||
m_proceduralTextureSystem = std::make_unique<ProceduralTextureSystem>(m_world, m_sceneMgr);
|
||||
m_proceduralTextureSystem->initialize();
|
||||
|
||||
// Setup ProceduralMaterial system
|
||||
m_proceduralMaterialSystem = std::make_unique<ProceduralMaterialSystem>(m_world, m_sceneMgr);
|
||||
m_proceduralMaterialSystem->initialize();
|
||||
|
||||
// Setup ProceduralMesh system
|
||||
m_proceduralMeshSystem = std::make_unique<ProceduralMeshSystem>(m_world, m_sceneMgr);
|
||||
m_proceduralMeshSystem->initialize();
|
||||
|
||||
// Add default entities to UI cache
|
||||
for (auto &e : m_defaultEntities) {
|
||||
@@ -211,6 +226,13 @@ void EditorApp::setupECS()
|
||||
|
||||
// Register ProceduralTexture component
|
||||
m_world.component<ProceduralTextureComponent>();
|
||||
|
||||
// Register ProceduralMaterial component
|
||||
m_world.component<ProceduralMaterialComponent>();
|
||||
|
||||
// Register Primitive and TriangleBuffer components
|
||||
m_world.component<PrimitiveComponent>();
|
||||
m_world.component<TriangleBufferComponent>();
|
||||
}
|
||||
|
||||
void EditorApp::createDefaultEntities()
|
||||
@@ -363,6 +385,16 @@ bool EditorApp::frameRenderingQueued(const Ogre::FrameEvent &evt)
|
||||
if (m_proceduralTextureSystem) {
|
||||
m_proceduralTextureSystem->update();
|
||||
}
|
||||
|
||||
// Update ProceduralMaterial system
|
||||
if (m_proceduralMaterialSystem) {
|
||||
m_proceduralMaterialSystem->update();
|
||||
}
|
||||
|
||||
// Update ProceduralMesh system
|
||||
if (m_proceduralMeshSystem) {
|
||||
m_proceduralMeshSystem->update();
|
||||
}
|
||||
|
||||
// Don't call base class - it crashes when iterating input listeners
|
||||
return true;
|
||||
|
||||
@@ -19,6 +19,8 @@ class EditorCameraSystem;
|
||||
class EditorLodSystem;
|
||||
class StaticGeometrySystem;
|
||||
class ProceduralTextureSystem;
|
||||
class ProceduralMaterialSystem;
|
||||
class ProceduralMeshSystem;
|
||||
|
||||
/**
|
||||
* RenderTargetListener for ImGui frame management
|
||||
@@ -90,6 +92,8 @@ private:
|
||||
std::unique_ptr<EditorLodSystem> m_lodSystem;
|
||||
std::unique_ptr<StaticGeometrySystem> m_staticGeometrySystem;
|
||||
std::unique_ptr<ProceduralTextureSystem> m_proceduralTextureSystem;
|
||||
std::unique_ptr<ProceduralMaterialSystem> m_proceduralMaterialSystem;
|
||||
std::unique_ptr<ProceduralMeshSystem> m_proceduralMeshSystem;
|
||||
|
||||
// State
|
||||
uint16_t m_currentModifiers;
|
||||
|
||||
37
src/features/editScene/components/Primitive.hpp
Normal file
37
src/features/editScene/components/Primitive.hpp
Normal file
@@ -0,0 +1,37 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <flecs.h>
|
||||
|
||||
/**
|
||||
* @brief Base component for procedural primitives that feed into TriangleBuffer
|
||||
*/
|
||||
struct PrimitiveComponent {
|
||||
// Type of primitive
|
||||
enum class Type {
|
||||
None,
|
||||
Box,
|
||||
Plane
|
||||
};
|
||||
|
||||
Type type = Type::None;
|
||||
|
||||
// Box parameters
|
||||
float boxSizeX = 1.0f;
|
||||
float boxSizeY = 1.0f;
|
||||
float boxSizeZ = 1.0f;
|
||||
|
||||
// Plane parameters
|
||||
float planeSizeX = 1.0f;
|
||||
float planeSizeY = 1.0f;
|
||||
int planeSegmentsX = 1;
|
||||
int planeSegmentsY = 1;
|
||||
|
||||
// Dirty flag - triggers TriangleBuffer rebuild
|
||||
bool dirty = true;
|
||||
|
||||
// Track last seen transform version to detect gizmo movements
|
||||
unsigned int lastTransformVersion = 0;
|
||||
|
||||
void markDirty() { dirty = true; }
|
||||
};
|
||||
24
src/features/editScene/components/PrimitiveModule.cpp
Normal file
24
src/features/editScene/components/PrimitiveModule.cpp
Normal file
@@ -0,0 +1,24 @@
|
||||
#include "Primitive.hpp"
|
||||
#include "../ui/ComponentRegistration.hpp"
|
||||
#include "../ui/PrimitiveEditor.hpp"
|
||||
|
||||
// Register Primitive component
|
||||
REGISTER_COMPONENT("Primitive", PrimitiveComponent, PrimitiveEditor)
|
||||
{
|
||||
registry.registerComponent<PrimitiveComponent>(
|
||||
"Primitive",
|
||||
std::make_unique<PrimitiveEditor>(),
|
||||
// Adder
|
||||
[sceneMgr](flecs::entity e) {
|
||||
if (!e.has<PrimitiveComponent>()) {
|
||||
e.set<PrimitiveComponent>({});
|
||||
}
|
||||
},
|
||||
// Remover
|
||||
[sceneMgr](flecs::entity e) {
|
||||
if (e.has<PrimitiveComponent>()) {
|
||||
e.remove<PrimitiveComponent>();
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
39
src/features/editScene/components/ProceduralMaterial.hpp
Normal file
39
src/features/editScene/components/ProceduralMaterial.hpp
Normal file
@@ -0,0 +1,39 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <Ogre.h>
|
||||
#include <flecs.h>
|
||||
|
||||
/**
|
||||
* @brief Component for creating procedural Ogre materials
|
||||
*
|
||||
* Creates a named material that uses ProceduralTexture as diffuse/albedo map.
|
||||
* The material can be referenced by name for use with meshes/entities.
|
||||
*/
|
||||
struct ProceduralMaterialComponent {
|
||||
// Material name for Ogre resource
|
||||
std::string materialName;
|
||||
|
||||
// Reference to entity with ProceduralTextureComponent (for diffuse)
|
||||
flecs::entity diffuseTextureEntity;
|
||||
|
||||
// Whether the material needs regeneration
|
||||
bool dirty = true;
|
||||
|
||||
// Whether the material has been created
|
||||
bool created = false;
|
||||
|
||||
// Pointer to the created Ogre material
|
||||
Ogre::MaterialPtr ogreMaterial;
|
||||
|
||||
// Material properties
|
||||
float ambient[3] = {0.2f, 0.2f, 0.2f}; // Ambient color (RGB 0-1)
|
||||
float diffuse[3] = {1.0f, 1.0f, 1.0f}; // Diffuse color multiplier (RGB 0-1)
|
||||
float specular[3] = {0.0f, 0.0f, 0.0f}; // Specular color (RGB 0-1)
|
||||
float shininess = 32.0f; // Specular shininess
|
||||
float roughness = 0.5f; // Roughness (0-1, for PBR-style)
|
||||
|
||||
void markDirty() {
|
||||
dirty = true;
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,35 @@
|
||||
#include "ProceduralMaterial.hpp"
|
||||
#include "../ui/ComponentRegistration.hpp"
|
||||
#include "../ui/ProceduralMaterialEditor.hpp"
|
||||
#include <OgreMaterialManager.h>
|
||||
|
||||
// Register ProceduralMaterial component
|
||||
REGISTER_COMPONENT("Procedural Material", ProceduralMaterialComponent, ProceduralMaterialEditor)
|
||||
{
|
||||
registry.registerComponent<ProceduralMaterialComponent>(
|
||||
"Procedural Material",
|
||||
std::make_unique<ProceduralMaterialEditor>(sceneMgr),
|
||||
// Adder
|
||||
[sceneMgr](flecs::entity e) {
|
||||
if (!e.has<ProceduralMaterialComponent>()) {
|
||||
e.set<ProceduralMaterialComponent>({});
|
||||
}
|
||||
},
|
||||
// Remover
|
||||
[sceneMgr](flecs::entity e) {
|
||||
if (e.has<ProceduralMaterialComponent>()) {
|
||||
auto& material = e.get_mut<ProceduralMaterialComponent>();
|
||||
// Clean up Ogre material - wrap in try/catch since MaterialManager may be shutting down
|
||||
if (material.ogreMaterial) {
|
||||
try {
|
||||
if (Ogre::MaterialManager::getSingletonPtr()) {
|
||||
Ogre::MaterialManager::getSingleton().remove(material.ogreMaterial);
|
||||
}
|
||||
} catch (...) {}
|
||||
material.ogreMaterial.reset();
|
||||
}
|
||||
e.remove<ProceduralMaterialComponent>();
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
@@ -12,6 +12,9 @@ struct TransformComponent {
|
||||
Ogre::Vector3 position = Ogre::Vector3::ZERO;
|
||||
Ogre::Quaternion rotation = Ogre::Quaternion::IDENTITY;
|
||||
Ogre::Vector3 scale = Ogre::Vector3::UNIT_SCALE;
|
||||
|
||||
// Version tracking for change detection
|
||||
unsigned int version = 0;
|
||||
|
||||
/**
|
||||
* Apply component values to the scene node
|
||||
@@ -36,6 +39,11 @@ struct TransformComponent {
|
||||
scale = node->getScale();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Mark transform as changed (increment version)
|
||||
*/
|
||||
void markChanged() { version++; }
|
||||
};
|
||||
|
||||
#endif // EDITSCENE_TRANSFORM_HPP
|
||||
|
||||
48
src/features/editScene/components/TriangleBuffer.hpp
Normal file
48
src/features/editScene/components/TriangleBuffer.hpp
Normal file
@@ -0,0 +1,48 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <memory>
|
||||
#include <flecs.h>
|
||||
#include <Ogre.h>
|
||||
#include <ProceduralTriangleBuffer.h>
|
||||
|
||||
/**
|
||||
* @brief Component that holds a procedural triangle buffer
|
||||
*
|
||||
* The buffer contents are NOT serialized - only the configuration.
|
||||
* The buffer is rebuilt from Primitive children when needed.
|
||||
*/
|
||||
struct TriangleBufferComponent {
|
||||
// The actual triangle buffer (runtime only, not serialized)
|
||||
// Using shared_ptr because flecs needs to copy components
|
||||
std::shared_ptr<Procedural::TriangleBuffer> buffer;
|
||||
|
||||
// Reference to entity with ProceduralTexture for UV mapping
|
||||
flecs::entity textureEntity;
|
||||
|
||||
// Name of the rectangle in the texture atlas to use for UVs
|
||||
std::string textureRectName;
|
||||
|
||||
// Reference to entity with ProceduralMaterial for material
|
||||
flecs::entity materialEntity;
|
||||
|
||||
// Whether to attach to StaticGeometry instead of SceneNode
|
||||
bool useStaticGeometry = false;
|
||||
|
||||
// Reference to StaticGeometry region entity (if useStaticGeometry is true)
|
||||
flecs::entity staticGeometryEntity;
|
||||
|
||||
// Mesh name for the generated mesh
|
||||
std::string meshName;
|
||||
|
||||
// Whether the buffer needs rebuilding
|
||||
bool dirty = true;
|
||||
|
||||
// Whether the buffer has been converted to a mesh
|
||||
bool meshCreated = false;
|
||||
|
||||
// Pointer to the created Ogre entity (if not using StaticGeometry)
|
||||
Ogre::Entity* ogreEntity = nullptr;
|
||||
|
||||
void markDirty() { dirty = true; }
|
||||
};
|
||||
40
src/features/editScene/components/TriangleBufferModule.cpp
Normal file
40
src/features/editScene/components/TriangleBufferModule.cpp
Normal file
@@ -0,0 +1,40 @@
|
||||
#include "TriangleBuffer.hpp"
|
||||
#include "../ui/ComponentRegistration.hpp"
|
||||
#include "../ui/TriangleBufferEditor.hpp"
|
||||
#include <OgreMeshManager.h>
|
||||
|
||||
// Register TriangleBuffer component
|
||||
REGISTER_COMPONENT("Triangle Buffer", TriangleBufferComponent, TriangleBufferEditor)
|
||||
{
|
||||
registry.registerComponent<TriangleBufferComponent>(
|
||||
"Triangle Buffer",
|
||||
std::make_unique<TriangleBufferEditor>(sceneMgr),
|
||||
// Adder
|
||||
[sceneMgr](flecs::entity e) {
|
||||
if (!e.has<TriangleBufferComponent>()) {
|
||||
e.set<TriangleBufferComponent>({});
|
||||
}
|
||||
},
|
||||
// Remover
|
||||
[sceneMgr](flecs::entity e) {
|
||||
if (e.has<TriangleBufferComponent>()) {
|
||||
auto& tb = e.get_mut<TriangleBufferComponent>();
|
||||
// Clean up Ogre entity and mesh
|
||||
if (tb.ogreEntity && sceneMgr) {
|
||||
try {
|
||||
sceneMgr->destroyEntity(tb.ogreEntity);
|
||||
} catch (...) {}
|
||||
tb.ogreEntity = nullptr;
|
||||
}
|
||||
if (!tb.meshName.empty()) {
|
||||
try {
|
||||
if (Ogre::MeshManager::getSingletonPtr()) {
|
||||
Ogre::MeshManager::getSingleton().remove(tb.meshName);
|
||||
}
|
||||
} catch (...) {}
|
||||
}
|
||||
e.remove<TriangleBufferComponent>();
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
@@ -282,6 +282,7 @@ bool Gizmo::onMouseMoved(const Ogre::Ray &mouseRay, const Ogre::Vector2 &mouseDe
|
||||
}
|
||||
|
||||
transform.applyToNode();
|
||||
transform.markChanged();
|
||||
|
||||
// Mark StaticGeometryMember dirty if present
|
||||
if (m_attachedEntity.has<StaticGeometryMemberComponent>()) {
|
||||
|
||||
@@ -11,6 +11,9 @@
|
||||
#include "../components/StaticGeometry.hpp"
|
||||
#include "../components/StaticGeometryMember.hpp"
|
||||
#include "../components/ProceduralTexture.hpp"
|
||||
#include "../components/ProceduralMaterial.hpp"
|
||||
#include "../components/Primitive.hpp"
|
||||
#include "../components/TriangleBuffer.hpp"
|
||||
#include "../components/LodSettings.hpp"
|
||||
#include "../ui/TransformEditor.hpp"
|
||||
#include "../ui/RenderableEditor.hpp"
|
||||
@@ -570,6 +573,27 @@ void EditorUISystem::renderComponentList(flecs::entity entity)
|
||||
m_componentRegistry.render<ProceduralTextureComponent>(entity, texture);
|
||||
componentCount++;
|
||||
}
|
||||
|
||||
// Render ProceduralMaterial if present
|
||||
if (entity.has<ProceduralMaterialComponent>()) {
|
||||
auto &material = entity.get_mut<ProceduralMaterialComponent>();
|
||||
m_componentRegistry.render<ProceduralMaterialComponent>(entity, material);
|
||||
componentCount++;
|
||||
}
|
||||
|
||||
// Render Primitive if present
|
||||
if (entity.has<PrimitiveComponent>()) {
|
||||
auto &primitive = entity.get_mut<PrimitiveComponent>();
|
||||
m_componentRegistry.render<PrimitiveComponent>(entity, primitive);
|
||||
componentCount++;
|
||||
}
|
||||
|
||||
// Render TriangleBuffer if present
|
||||
if (entity.has<TriangleBufferComponent>()) {
|
||||
auto &tb = entity.get_mut<TriangleBufferComponent>();
|
||||
m_componentRegistry.render<TriangleBufferComponent>(entity, tb);
|
||||
componentCount++;
|
||||
}
|
||||
|
||||
// Show message if no components
|
||||
if (componentCount == 0) {
|
||||
|
||||
125
src/features/editScene/systems/ProceduralMaterialSystem.cpp
Normal file
125
src/features/editScene/systems/ProceduralMaterialSystem.cpp
Normal file
@@ -0,0 +1,125 @@
|
||||
#include "ProceduralMaterialSystem.hpp"
|
||||
#include "../components/ProceduralMaterial.hpp"
|
||||
#include "../components/ProceduralTexture.hpp"
|
||||
#include <OgreMaterialManager.h>
|
||||
#include <OgreTechnique.h>
|
||||
#include <OgrePass.h>
|
||||
#include <OgreTextureUnitState.h>
|
||||
#include <OgreLogManager.h>
|
||||
|
||||
ProceduralMaterialSystem::ProceduralMaterialSystem(flecs::world& world, Ogre::SceneManager* sceneMgr)
|
||||
: m_world(world)
|
||||
, m_sceneMgr(sceneMgr)
|
||||
, m_query(world.query<ProceduralMaterialComponent>())
|
||||
{
|
||||
}
|
||||
|
||||
ProceduralMaterialSystem::~ProceduralMaterialSystem() = default;
|
||||
|
||||
void ProceduralMaterialSystem::initialize()
|
||||
{
|
||||
if (m_initialized) return;
|
||||
m_initialized = true;
|
||||
|
||||
Ogre::LogManager::getSingleton().logMessage("ProceduralMaterialSystem initialized");
|
||||
}
|
||||
|
||||
void ProceduralMaterialSystem::update()
|
||||
{
|
||||
if (!m_initialized) return;
|
||||
|
||||
m_query.each([&](flecs::entity entity, ProceduralMaterialComponent& component) {
|
||||
// Check if we need to recreate (dirty or not created yet)
|
||||
if (component.dirty || !component.created) {
|
||||
createMaterial(entity, component);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void ProceduralMaterialSystem::createMaterial(flecs::entity entity, ProceduralMaterialComponent& component)
|
||||
{
|
||||
try {
|
||||
// Generate unique material name if not set
|
||||
if (component.materialName.empty()) {
|
||||
component.materialName = "ProceduralMat_" + std::to_string(entity.id()) + "_" + std::to_string(m_createdCount++);
|
||||
}
|
||||
|
||||
// Destroy old material if exists
|
||||
if (component.ogreMaterial) {
|
||||
destroyMaterial(component);
|
||||
}
|
||||
|
||||
// Create new material
|
||||
component.ogreMaterial = Ogre::MaterialManager::getSingleton().create(
|
||||
component.materialName,
|
||||
Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME);
|
||||
|
||||
// Get the default technique and pass
|
||||
Ogre::Technique* technique = component.ogreMaterial->getTechnique(0);
|
||||
Ogre::Pass* pass = technique->getPass(0);
|
||||
|
||||
// Set material colors
|
||||
pass->setAmbient(component.ambient[0], component.ambient[1], component.ambient[2]);
|
||||
pass->setDiffuse(component.diffuse[0], component.diffuse[1], component.diffuse[2], 1.0f);
|
||||
pass->setSpecular(component.specular[0], component.specular[1], component.specular[2], 1.0f);
|
||||
pass->setShininess(component.shininess);
|
||||
|
||||
// Add diffuse texture if we have a reference to a ProceduralTexture entity
|
||||
if (component.diffuseTextureEntity.is_alive() &&
|
||||
component.diffuseTextureEntity.has<ProceduralTextureComponent>()) {
|
||||
|
||||
const auto& textureComp = component.diffuseTextureEntity.get<ProceduralTextureComponent>();
|
||||
if (textureComp.generated && !textureComp.textureName.empty()) {
|
||||
Ogre::TextureUnitState* texUnit = pass->createTextureUnitState();
|
||||
texUnit->setTextureName(textureComp.textureName);
|
||||
texUnit->setTextureAddressingMode(Ogre::TAM_CLAMP);
|
||||
}
|
||||
}
|
||||
|
||||
component.created = true;
|
||||
component.dirty = false;
|
||||
|
||||
Ogre::LogManager::getSingleton().logMessage(
|
||||
"ProceduralMaterial: Created '" + component.materialName + "'");
|
||||
|
||||
} catch (const Ogre::Exception& e) {
|
||||
Ogre::LogManager::getSingleton().logMessage(
|
||||
"ProceduralMaterial ERROR: " + e.getDescription());
|
||||
component.dirty = true; // Will retry
|
||||
}
|
||||
}
|
||||
|
||||
void ProceduralMaterialSystem::destroyMaterial(ProceduralMaterialComponent& component)
|
||||
{
|
||||
if (component.ogreMaterial) {
|
||||
try {
|
||||
Ogre::MaterialManager::getSingleton().remove(component.ogreMaterial);
|
||||
} catch (...) {
|
||||
// Ignore errors during cleanup
|
||||
}
|
||||
component.ogreMaterial.reset();
|
||||
}
|
||||
}
|
||||
|
||||
void ProceduralMaterialSystem::recreateMaterial(flecs::entity entity)
|
||||
{
|
||||
if (!entity.is_alive() || !entity.has<ProceduralMaterialComponent>()) {
|
||||
return;
|
||||
}
|
||||
|
||||
entity.get_mut<ProceduralMaterialComponent>().markDirty();
|
||||
}
|
||||
|
||||
std::string ProceduralMaterialSystem::getMaterialName(flecs::entity entity)
|
||||
{
|
||||
if (!entity.is_alive() || !entity.has<ProceduralMaterialComponent>()) {
|
||||
return "";
|
||||
}
|
||||
|
||||
return entity.get<ProceduralMaterialComponent>().materialName;
|
||||
}
|
||||
|
||||
bool ProceduralMaterialSystem::materialExists(const std::string& name)
|
||||
{
|
||||
return Ogre::MaterialManager::getSingleton().resourceExists(name);
|
||||
}
|
||||
44
src/features/editScene/systems/ProceduralMaterialSystem.hpp
Normal file
44
src/features/editScene/systems/ProceduralMaterialSystem.hpp
Normal file
@@ -0,0 +1,44 @@
|
||||
#pragma once
|
||||
|
||||
#include <flecs.h>
|
||||
#include <memory>
|
||||
|
||||
namespace Ogre {
|
||||
class SceneManager;
|
||||
}
|
||||
|
||||
class ProceduralMaterialSystem {
|
||||
public:
|
||||
ProceduralMaterialSystem(flecs::world& world, Ogre::SceneManager* sceneMgr);
|
||||
~ProceduralMaterialSystem();
|
||||
|
||||
// Initialize the system
|
||||
void initialize();
|
||||
|
||||
// Main update - creates dirty materials
|
||||
void update();
|
||||
|
||||
// Force recreate a specific material
|
||||
void recreateMaterial(flecs::entity entity);
|
||||
|
||||
// Get the material name for an entity
|
||||
std::string getMaterialName(flecs::entity entity);
|
||||
|
||||
// Check if material exists
|
||||
bool materialExists(const std::string& name);
|
||||
|
||||
private:
|
||||
flecs::world& m_world;
|
||||
Ogre::SceneManager* m_sceneMgr;
|
||||
|
||||
flecs::query<struct ProceduralMaterialComponent> m_query;
|
||||
|
||||
bool m_initialized = false;
|
||||
int m_createdCount = 0;
|
||||
|
||||
// Create the Ogre material
|
||||
void createMaterial(flecs::entity entity, struct ProceduralMaterialComponent& component);
|
||||
|
||||
// Destroy the Ogre material
|
||||
void destroyMaterial(struct ProceduralMaterialComponent& component);
|
||||
};
|
||||
347
src/features/editScene/systems/ProceduralMeshSystem.cpp
Normal file
347
src/features/editScene/systems/ProceduralMeshSystem.cpp
Normal file
@@ -0,0 +1,347 @@
|
||||
#include "ProceduralMeshSystem.hpp"
|
||||
#include "../components/TriangleBuffer.hpp"
|
||||
#include "../components/Primitive.hpp"
|
||||
#include "../components/ProceduralTexture.hpp"
|
||||
#include "../components/ProceduralMaterial.hpp"
|
||||
#include "../components/Transform.hpp"
|
||||
#include "../components/StaticGeometry.hpp"
|
||||
#include "../components/StaticGeometryMember.hpp"
|
||||
#include <OgreSceneManager.h>
|
||||
#include <OgreMeshManager.h>
|
||||
#include <OgreEntity.h>
|
||||
#include <OgreSubMesh.h>
|
||||
#include <OgreLogManager.h>
|
||||
#include <ProceduralTriangleBuffer.h>
|
||||
#include <ProceduralBoxGenerator.h>
|
||||
#include <ProceduralPlaneGenerator.h>
|
||||
|
||||
ProceduralMeshSystem::ProceduralMeshSystem(flecs::world& world, Ogre::SceneManager* sceneMgr)
|
||||
: m_world(world)
|
||||
, m_sceneMgr(sceneMgr)
|
||||
, m_triangleBufferQuery(world.query<TriangleBufferComponent>())
|
||||
, m_primitiveQuery(world.query<PrimitiveComponent>())
|
||||
{
|
||||
}
|
||||
|
||||
ProceduralMeshSystem::~ProceduralMeshSystem() = default;
|
||||
|
||||
void ProceduralMeshSystem::initialize()
|
||||
{
|
||||
if (m_initialized) return;
|
||||
m_initialized = true;
|
||||
|
||||
Ogre::LogManager::getSingleton().logMessage("ProceduralMeshSystem initialized");
|
||||
}
|
||||
|
||||
void ProceduralMeshSystem::update()
|
||||
{
|
||||
if (!m_initialized) return;
|
||||
|
||||
// First pass: mark TriangleBuffers dirty if any child primitives are dirty
|
||||
m_triangleBufferQuery.each([&](flecs::entity entity, TriangleBufferComponent& tb) {
|
||||
if (arePrimitivesDirty(entity)) {
|
||||
tb.markDirty();
|
||||
}
|
||||
});
|
||||
|
||||
// Second pass: rebuild dirty triangle buffers
|
||||
m_triangleBufferQuery.each([&](flecs::entity entity, TriangleBufferComponent& tb) {
|
||||
if (tb.dirty) {
|
||||
buildTriangleBuffer(entity, tb);
|
||||
if (tb.buffer && hasValidTriangles(tb.buffer.get())) {
|
||||
convertToMesh(entity, tb);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
bool ProceduralMeshSystem::arePrimitivesDirty(flecs::entity parent)
|
||||
{
|
||||
bool dirty = false;
|
||||
|
||||
// Check all children for PrimitiveComponent
|
||||
parent.children([&](flecs::entity child) {
|
||||
if (child.has<PrimitiveComponent>()) {
|
||||
const auto& prim = child.get<PrimitiveComponent>();
|
||||
if (prim.dirty) {
|
||||
dirty = true;
|
||||
return;
|
||||
}
|
||||
// Check if transform has changed
|
||||
if (child.has<TransformComponent>()) {
|
||||
const auto& transform = child.get<TransformComponent>();
|
||||
// Get stored transform version from primitive's user data or check directly
|
||||
// We'll use a simple approach: store last known version in the primitive
|
||||
auto& primMut = child.get_mut<PrimitiveComponent>();
|
||||
if (transform.version != primMut.lastTransformVersion) {
|
||||
dirty = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return dirty;
|
||||
}
|
||||
|
||||
void ProceduralMeshSystem::buildTriangleBuffer(flecs::entity entity, TriangleBufferComponent& tb)
|
||||
{
|
||||
// Create new triangle buffer
|
||||
tb.buffer = std::make_shared<Procedural::TriangleBuffer>();
|
||||
|
||||
// Collect all primitive children
|
||||
std::vector<std::pair<flecs::entity, PrimitiveComponent*>> primitives;
|
||||
|
||||
entity.children([&](flecs::entity child) {
|
||||
if (child.has<PrimitiveComponent>()) {
|
||||
auto& prim = child.get_mut<PrimitiveComponent>();
|
||||
primitives.emplace_back(child, &prim);
|
||||
prim.dirty = false; // Mark as processed
|
||||
}
|
||||
});
|
||||
|
||||
// Build each primitive into the triangle buffer
|
||||
for (auto& [primEntity, prim] : primitives) {
|
||||
if (prim->type == PrimitiveComponent::Type::None) continue;
|
||||
|
||||
// Get transform from the primitive entity
|
||||
Ogre::Vector3 position = Ogre::Vector3::ZERO;
|
||||
Ogre::Quaternion orientation = Ogre::Quaternion::IDENTITY;
|
||||
|
||||
if (primEntity.has<TransformComponent>()) {
|
||||
const auto& transform = primEntity.get<TransformComponent>();
|
||||
if (transform.node) {
|
||||
position = transform.node->getPosition();
|
||||
orientation = transform.node->getOrientation();
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
Procedural::TriangleBuffer primBuffer;
|
||||
|
||||
switch (prim->type) {
|
||||
case PrimitiveComponent::Type::Box: {
|
||||
Procedural::BoxGenerator boxGen;
|
||||
boxGen.setSizeX(prim->boxSizeX);
|
||||
boxGen.setSizeY(prim->boxSizeY);
|
||||
boxGen.setSizeZ(prim->boxSizeZ);
|
||||
boxGen.addToTriangleBuffer(primBuffer);
|
||||
break;
|
||||
}
|
||||
case PrimitiveComponent::Type::Plane: {
|
||||
Procedural::PlaneGenerator planeGen;
|
||||
planeGen.setSizeX(prim->planeSizeX);
|
||||
planeGen.setSizeY(prim->planeSizeY);
|
||||
planeGen.setNumSegX(prim->planeSegmentsX);
|
||||
planeGen.setNumSegY(prim->planeSegmentsY);
|
||||
planeGen.addToTriangleBuffer(primBuffer);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
// Transform vertices
|
||||
auto& vertices = primBuffer.getVertices();
|
||||
for (auto& vertex : vertices) {
|
||||
Ogre::Vector3 pos = vertex.mPosition;
|
||||
pos = orientation * pos + position;
|
||||
vertex.mPosition = pos;
|
||||
}
|
||||
|
||||
// Merge into main buffer
|
||||
tb.buffer->append(primBuffer);
|
||||
|
||||
// Update transform version tracking
|
||||
if (primEntity.has<TransformComponent>()) {
|
||||
const auto& transform = primEntity.get<TransformComponent>();
|
||||
prim->lastTransformVersion = transform.version;
|
||||
}
|
||||
|
||||
} catch (const std::exception& e) {
|
||||
Ogre::LogManager::getSingleton().logMessage(
|
||||
"ProceduralMesh: Failed to build primitive: " + Ogre::String(e.what()));
|
||||
}
|
||||
}
|
||||
|
||||
tb.dirty = false;
|
||||
|
||||
Ogre::LogManager::getSingleton().logMessage(
|
||||
"ProceduralMesh: Built triangle buffer with " +
|
||||
std::to_string(tb.buffer->getVertices().size()) + " vertices");
|
||||
}
|
||||
|
||||
bool ProceduralMeshSystem::hasValidTriangles(Procedural::TriangleBuffer* buffer)
|
||||
{
|
||||
if (!buffer) return false;
|
||||
|
||||
const auto& indices = buffer->getIndices();
|
||||
if (indices.size() < 3) return false;
|
||||
|
||||
// Check for at least one non-degenerate triangle
|
||||
for (size_t i = 0; i + 2 < indices.size(); i += 3) {
|
||||
const auto& v0 = buffer->getVertices()[indices[i]];
|
||||
const auto& v1 = buffer->getVertices()[indices[i + 1]];
|
||||
const auto& v2 = buffer->getVertices()[indices[i + 2]];
|
||||
|
||||
// Check if triangle has non-zero area
|
||||
Ogre::Vector3 e1 = v1.mPosition - v0.mPosition;
|
||||
Ogre::Vector3 e2 = v2.mPosition - v0.mPosition;
|
||||
|
||||
if (e1.crossProduct(e2).squaredLength() > 0.0001f) {
|
||||
return true; // Found valid triangle
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void ProceduralMeshSystem::applyUVMapping(Procedural::TriangleBuffer* buffer,
|
||||
ProceduralTextureComponent& texture,
|
||||
const std::string& rectName)
|
||||
{
|
||||
if (!buffer || rectName.empty()) return;
|
||||
|
||||
const TextureRectInfo* rect = texture.getNamedRect(rectName);
|
||||
if (!rect) return;
|
||||
|
||||
// Calculate UV mapping with margin
|
||||
const float margin = 0.01f;
|
||||
float uRange = (rect->u2 - rect->u1) * (1.0f - 2.0f * margin);
|
||||
float vRange = (rect->v2 - rect->v1) * (1.0f - 2.0f * margin);
|
||||
float uOffset = rect->u1 + (rect->u2 - rect->u1) * margin;
|
||||
float vOffset = rect->v1 + (rect->v2 - rect->v1) * margin;
|
||||
|
||||
auto& vertices = buffer->getVertices();
|
||||
for (auto& vertex : vertices) {
|
||||
// Map UV from [0,1] to [rect.u1, rect.u2] with margin
|
||||
vertex.mUV.x = uOffset + vertex.mUV.x * uRange;
|
||||
vertex.mUV.y = vOffset + vertex.mUV.y * vRange;
|
||||
}
|
||||
}
|
||||
|
||||
void ProceduralMeshSystem::destroyMesh(TriangleBufferComponent& tb)
|
||||
{
|
||||
if (tb.ogreEntity) {
|
||||
try {
|
||||
m_sceneMgr->destroyEntity(tb.ogreEntity);
|
||||
} catch (...) {}
|
||||
tb.ogreEntity = nullptr;
|
||||
}
|
||||
|
||||
if (!tb.meshName.empty()) {
|
||||
try {
|
||||
Ogre::MeshManager::getSingleton().remove(tb.meshName);
|
||||
} catch (...) {}
|
||||
}
|
||||
|
||||
tb.meshCreated = false;
|
||||
}
|
||||
|
||||
void ProceduralMeshSystem::convertToMesh(flecs::entity entity, TriangleBufferComponent& tb)
|
||||
{
|
||||
if (!tb.buffer) return;
|
||||
|
||||
// Apply UV mapping if texture and rect are specified
|
||||
if (tb.textureEntity.is_alive() && tb.textureEntity.has<ProceduralTextureComponent>()) {
|
||||
auto& texture = tb.textureEntity.get_mut<ProceduralTextureComponent>();
|
||||
applyUVMapping(tb.buffer.get(), texture, tb.textureRectName);
|
||||
}
|
||||
|
||||
// Generate mesh name
|
||||
if (tb.meshName.empty()) {
|
||||
tb.meshName = "ProceduralMesh_" + std::to_string(entity.id()) + "_" + std::to_string(m_meshCount++);
|
||||
}
|
||||
|
||||
// Destroy old mesh if exists
|
||||
destroyMesh(tb);
|
||||
|
||||
try {
|
||||
// Create Ogre mesh from triangle buffer
|
||||
Ogre::MeshPtr mesh = tb.buffer->transformToMesh(tb.meshName);
|
||||
|
||||
// Apply material if specified
|
||||
if (tb.materialEntity.is_alive() && tb.materialEntity.has<ProceduralMaterialComponent>()) {
|
||||
const auto& material = tb.materialEntity.get<ProceduralMaterialComponent>();
|
||||
if (material.created && !material.materialName.empty()) {
|
||||
mesh->getSubMesh(0)->setMaterialName(material.materialName);
|
||||
}
|
||||
}
|
||||
|
||||
// Either attach to SceneNode or StaticGeometry
|
||||
if (tb.useStaticGeometry && tb.staticGeometryEntity.is_alive()) {
|
||||
// Add to StaticGeometry as a member
|
||||
attachToStaticGeometry(entity, tb);
|
||||
} else {
|
||||
// Attach to entity's SceneNode
|
||||
if (entity.has<TransformComponent>()) {
|
||||
auto& transform = entity.get_mut<TransformComponent>();
|
||||
if (transform.node) {
|
||||
tb.ogreEntity = m_sceneMgr->createEntity(tb.meshName);
|
||||
transform.node->attachObject(tb.ogreEntity);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tb.meshCreated = true;
|
||||
|
||||
Ogre::LogManager::getSingleton().logMessage(
|
||||
"ProceduralMesh: Created mesh '" + tb.meshName + "'");
|
||||
|
||||
} catch (const Ogre::Exception& e) {
|
||||
Ogre::LogManager::getSingleton().logMessage(
|
||||
"ProceduralMesh ERROR: Failed to create mesh: " + e.getDescription());
|
||||
}
|
||||
}
|
||||
|
||||
void ProceduralMeshSystem::rebuildTriangleBuffer(flecs::entity entity)
|
||||
{
|
||||
if (!entity.is_alive() || !entity.has<TriangleBufferComponent>()) {
|
||||
return;
|
||||
}
|
||||
|
||||
entity.get_mut<TriangleBufferComponent>().markDirty();
|
||||
}
|
||||
|
||||
void ProceduralMeshSystem::attachToStaticGeometry(flecs::entity entity, TriangleBufferComponent& tb)
|
||||
{
|
||||
if (!tb.staticGeometryEntity.is_alive() || !tb.staticGeometryEntity.has<StaticGeometryComponent>()) {
|
||||
Ogre::LogManager::getSingleton().logMessage(
|
||||
"ProceduralMesh: StaticGeometry entity invalid or missing StaticGeometryComponent");
|
||||
return;
|
||||
}
|
||||
|
||||
auto& region = tb.staticGeometryEntity.get_mut<StaticGeometryComponent>();
|
||||
|
||||
// Get or create StaticGeometryMemberComponent on the TriangleBuffer entity
|
||||
StaticGeometryMemberComponent* member = nullptr;
|
||||
if (entity.has<StaticGeometryMemberComponent>()) {
|
||||
member = &entity.get_mut<StaticGeometryMemberComponent>();
|
||||
} else {
|
||||
// Add the component
|
||||
entity.add<StaticGeometryMemberComponent>();
|
||||
member = &entity.get_mut<StaticGeometryMemberComponent>();
|
||||
}
|
||||
|
||||
// Configure the member
|
||||
member->regionEntity = tb.staticGeometryEntity;
|
||||
member->regionId = region.regionId;
|
||||
member->meshName = tb.meshName;
|
||||
member->castShadows = true;
|
||||
member->visible = true;
|
||||
member->dirty = true;
|
||||
|
||||
// Get material name if available
|
||||
if (tb.materialEntity.is_alive() && tb.materialEntity.has<ProceduralMaterialComponent>()) {
|
||||
const auto& material = tb.materialEntity.get<ProceduralMaterialComponent>();
|
||||
if (material.created && !material.materialName.empty()) {
|
||||
member->materialName = material.materialName;
|
||||
}
|
||||
}
|
||||
|
||||
// Mark the region as dirty to trigger rebuild
|
||||
region.markDirty();
|
||||
|
||||
Ogre::LogManager::getSingleton().logMessage(
|
||||
"ProceduralMesh: Attached mesh '" + tb.meshName + "' to StaticGeometry region '" +
|
||||
region.regionName + "'");
|
||||
}
|
||||
59
src/features/editScene/systems/ProceduralMeshSystem.hpp
Normal file
59
src/features/editScene/systems/ProceduralMeshSystem.hpp
Normal file
@@ -0,0 +1,59 @@
|
||||
#pragma once
|
||||
|
||||
#include <flecs.h>
|
||||
#include <memory>
|
||||
|
||||
namespace Ogre {
|
||||
class SceneManager;
|
||||
}
|
||||
|
||||
namespace Procedural {
|
||||
class TriangleBuffer;
|
||||
}
|
||||
|
||||
class ProceduralMeshSystem {
|
||||
public:
|
||||
ProceduralMeshSystem(flecs::world& world, Ogre::SceneManager* sceneMgr);
|
||||
~ProceduralMeshSystem();
|
||||
|
||||
// Initialize the system
|
||||
void initialize();
|
||||
|
||||
// Main update - builds dirty triangle buffers and converts to meshes
|
||||
void update();
|
||||
|
||||
// Force rebuild of a specific triangle buffer
|
||||
void rebuildTriangleBuffer(flecs::entity entity);
|
||||
|
||||
// Check if triangle buffer has valid triangles (non-degenerate)
|
||||
bool hasValidTriangles(Procedural::TriangleBuffer* buffer);
|
||||
|
||||
private:
|
||||
flecs::world& m_world;
|
||||
Ogre::SceneManager* m_sceneMgr;
|
||||
|
||||
flecs::query<struct TriangleBufferComponent> m_triangleBufferQuery;
|
||||
flecs::query<struct PrimitiveComponent> m_primitiveQuery;
|
||||
|
||||
bool m_initialized = false;
|
||||
int m_meshCount = 0;
|
||||
|
||||
// Build triangle buffer from primitive children
|
||||
void buildTriangleBuffer(flecs::entity entity, struct TriangleBufferComponent& tb);
|
||||
|
||||
// Convert triangle buffer to Ogre mesh
|
||||
void convertToMesh(flecs::entity entity, struct TriangleBufferComponent& tb);
|
||||
|
||||
// Apply UV mapping from named rectangle
|
||||
void applyUVMapping(Procedural::TriangleBuffer* buffer, struct ProceduralTextureComponent& texture,
|
||||
const std::string& rectName);
|
||||
|
||||
// Destroy existing mesh/entity
|
||||
void destroyMesh(struct TriangleBufferComponent& tb);
|
||||
|
||||
// Check if any primitive children are dirty
|
||||
bool arePrimitivesDirty(flecs::entity parent);
|
||||
|
||||
// Attach mesh to StaticGeometry region
|
||||
void attachToStaticGeometry(flecs::entity entity, struct TriangleBufferComponent& tb);
|
||||
};
|
||||
@@ -12,6 +12,9 @@
|
||||
#include "../components/StaticGeometry.hpp"
|
||||
#include "../components/StaticGeometryMember.hpp"
|
||||
#include "../components/ProceduralTexture.hpp"
|
||||
#include "../components/ProceduralMaterial.hpp"
|
||||
#include "../components/Primitive.hpp"
|
||||
#include "../components/TriangleBuffer.hpp"
|
||||
#include "EditorUISystem.hpp"
|
||||
#include <random>
|
||||
#include <fstream>
|
||||
@@ -161,6 +164,18 @@ nlohmann::json SceneSerializer::serializeEntity(flecs::entity entity)
|
||||
json["proceduralTexture"] = serializeProceduralTexture(entity);
|
||||
}
|
||||
|
||||
if (entity.has<ProceduralMaterialComponent>()) {
|
||||
json["proceduralMaterial"] = serializeProceduralMaterial(entity);
|
||||
}
|
||||
|
||||
if (entity.has<PrimitiveComponent>()) {
|
||||
json["primitive"] = serializePrimitive(entity);
|
||||
}
|
||||
|
||||
if (entity.has<TriangleBufferComponent>()) {
|
||||
json["triangleBuffer"] = serializeTriangleBuffer(entity);
|
||||
}
|
||||
|
||||
// Serialize children
|
||||
json["children"] = nlohmann::json::array();
|
||||
entity.children([&](flecs::entity child) {
|
||||
@@ -242,6 +257,18 @@ void SceneSerializer::deserializeEntity(const nlohmann::json& json, flecs::entit
|
||||
deserializeProceduralTexture(entity, json["proceduralTexture"]);
|
||||
}
|
||||
|
||||
if (json.contains("proceduralMaterial")) {
|
||||
deserializeProceduralMaterial(entity, json["proceduralMaterial"]);
|
||||
}
|
||||
|
||||
if (json.contains("primitive")) {
|
||||
deserializePrimitive(entity, json["primitive"]);
|
||||
}
|
||||
|
||||
if (json.contains("triangleBuffer")) {
|
||||
deserializeTriangleBuffer(entity, json["triangleBuffer"]);
|
||||
}
|
||||
|
||||
// Add to UI system if provided
|
||||
if (uiSystem) {
|
||||
uiSystem->addEntity(entity);
|
||||
@@ -1033,3 +1060,147 @@ void SceneSerializer::deserializeProceduralTexture(flecs::entity entity, const n
|
||||
|
||||
entity.set<ProceduralTextureComponent>(texture);
|
||||
}
|
||||
|
||||
nlohmann::json SceneSerializer::serializeProceduralMaterial(flecs::entity entity)
|
||||
{
|
||||
auto& material = entity.get<ProceduralMaterialComponent>();
|
||||
nlohmann::json json;
|
||||
|
||||
json["materialName"] = material.materialName;
|
||||
json["diffuseTextureEntity"] = material.diffuseTextureEntity.id();
|
||||
|
||||
json["ambient"] = {{"r", material.ambient[0]}, {"g", material.ambient[1]}, {"b", material.ambient[2]}};
|
||||
json["diffuse"] = {{"r", material.diffuse[0]}, {"g", material.diffuse[1]}, {"b", material.diffuse[2]}};
|
||||
json["specular"] = {{"r", material.specular[0]}, {"g", material.specular[1]}, {"b", material.specular[2]}};
|
||||
json["shininess"] = material.shininess;
|
||||
json["roughness"] = material.roughness;
|
||||
|
||||
return json;
|
||||
}
|
||||
|
||||
void SceneSerializer::deserializeProceduralMaterial(flecs::entity entity, const nlohmann::json& json)
|
||||
{
|
||||
ProceduralMaterialComponent material;
|
||||
|
||||
material.materialName = json.value("materialName", "");
|
||||
|
||||
// Store the entity ID for now - resolution will happen at runtime
|
||||
if (json.contains("diffuseTextureEntity")) {
|
||||
uint64_t textureEntityId = json["diffuseTextureEntity"];
|
||||
if (textureEntityId != 0) {
|
||||
// Will resolve to actual entity in update loop
|
||||
material.diffuseTextureEntity = m_world.entity(textureEntityId);
|
||||
}
|
||||
}
|
||||
|
||||
if (json.contains("ambient")) {
|
||||
material.ambient[0] = json["ambient"].value("r", 0.2f);
|
||||
material.ambient[1] = json["ambient"].value("g", 0.2f);
|
||||
material.ambient[2] = json["ambient"].value("b", 0.2f);
|
||||
}
|
||||
|
||||
if (json.contains("diffuse")) {
|
||||
material.diffuse[0] = json["diffuse"].value("r", 1.0f);
|
||||
material.diffuse[1] = json["diffuse"].value("g", 1.0f);
|
||||
material.diffuse[2] = json["diffuse"].value("b", 1.0f);
|
||||
}
|
||||
|
||||
if (json.contains("specular")) {
|
||||
material.specular[0] = json["specular"].value("r", 0.0f);
|
||||
material.specular[1] = json["specular"].value("g", 0.0f);
|
||||
material.specular[2] = json["specular"].value("b", 0.0f);
|
||||
}
|
||||
|
||||
material.shininess = json.value("shininess", 32.0f);
|
||||
material.roughness = json.value("roughness", 0.5f);
|
||||
|
||||
material.dirty = true;
|
||||
|
||||
entity.set<ProceduralMaterialComponent>(material);
|
||||
}
|
||||
|
||||
nlohmann::json SceneSerializer::serializePrimitive(flecs::entity entity)
|
||||
{
|
||||
auto& prim = entity.get<PrimitiveComponent>();
|
||||
nlohmann::json json;
|
||||
|
||||
json["type"] = static_cast<int>(prim.type);
|
||||
|
||||
// Box parameters
|
||||
json["boxSizeX"] = prim.boxSizeX;
|
||||
json["boxSizeY"] = prim.boxSizeY;
|
||||
json["boxSizeZ"] = prim.boxSizeZ;
|
||||
|
||||
// Plane parameters
|
||||
json["planeSizeX"] = prim.planeSizeX;
|
||||
json["planeSizeY"] = prim.planeSizeY;
|
||||
json["planeSegmentsX"] = prim.planeSegmentsX;
|
||||
json["planeSegmentsY"] = prim.planeSegmentsY;
|
||||
|
||||
return json;
|
||||
}
|
||||
|
||||
void SceneSerializer::deserializePrimitive(flecs::entity entity, const nlohmann::json& json)
|
||||
{
|
||||
PrimitiveComponent prim;
|
||||
|
||||
prim.type = static_cast<PrimitiveComponent::Type>(json.value("type", 0));
|
||||
|
||||
prim.boxSizeX = json.value("boxSizeX", 1.0f);
|
||||
prim.boxSizeY = json.value("boxSizeY", 1.0f);
|
||||
prim.boxSizeZ = json.value("boxSizeZ", 1.0f);
|
||||
|
||||
prim.planeSizeX = json.value("planeSizeX", 1.0f);
|
||||
prim.planeSizeY = json.value("planeSizeY", 1.0f);
|
||||
prim.planeSegmentsX = json.value("planeSegmentsX", 1);
|
||||
prim.planeSegmentsY = json.value("planeSegmentsY", 1);
|
||||
|
||||
prim.dirty = true;
|
||||
|
||||
entity.set<PrimitiveComponent>(prim);
|
||||
}
|
||||
|
||||
nlohmann::json SceneSerializer::serializeTriangleBuffer(flecs::entity entity)
|
||||
{
|
||||
auto& tb = entity.get<TriangleBufferComponent>();
|
||||
nlohmann::json json;
|
||||
|
||||
// Only serialize configuration, not the buffer contents
|
||||
json["meshName"] = tb.meshName;
|
||||
json["textureEntity"] = tb.textureEntity.id();
|
||||
json["textureRectName"] = tb.textureRectName;
|
||||
json["materialEntity"] = tb.materialEntity.id();
|
||||
json["useStaticGeometry"] = tb.useStaticGeometry;
|
||||
json["staticGeometryEntity"] = tb.staticGeometryEntity.id();
|
||||
|
||||
return json;
|
||||
}
|
||||
|
||||
void SceneSerializer::deserializeTriangleBuffer(flecs::entity entity, const nlohmann::json& json)
|
||||
{
|
||||
TriangleBufferComponent tb;
|
||||
|
||||
tb.meshName = json.value("meshName", "");
|
||||
tb.textureRectName = json.value("textureRectName", "");
|
||||
tb.useStaticGeometry = json.value("useStaticGeometry", false);
|
||||
|
||||
// Store entity IDs for runtime resolution
|
||||
if (json.contains("textureEntity")) {
|
||||
uint64_t id = json["textureEntity"];
|
||||
if (id != 0) tb.textureEntity = m_world.entity(id);
|
||||
}
|
||||
|
||||
if (json.contains("materialEntity")) {
|
||||
uint64_t id = json["materialEntity"];
|
||||
if (id != 0) tb.materialEntity = m_world.entity(id);
|
||||
}
|
||||
|
||||
if (json.contains("staticGeometryEntity")) {
|
||||
uint64_t id = json["staticGeometryEntity"];
|
||||
if (id != 0) tb.staticGeometryEntity = m_world.entity(id);
|
||||
}
|
||||
|
||||
tb.dirty = true;
|
||||
|
||||
entity.set<TriangleBufferComponent>(tb);
|
||||
}
|
||||
|
||||
@@ -51,6 +51,9 @@ private:
|
||||
nlohmann::json serializeStaticGeometry(flecs::entity entity);
|
||||
nlohmann::json serializeStaticGeometryMember(flecs::entity entity);
|
||||
nlohmann::json serializeProceduralTexture(flecs::entity entity);
|
||||
nlohmann::json serializeProceduralMaterial(flecs::entity entity);
|
||||
nlohmann::json serializePrimitive(flecs::entity entity);
|
||||
nlohmann::json serializeTriangleBuffer(flecs::entity entity);
|
||||
|
||||
// Component deserialization
|
||||
void deserializeTransform(flecs::entity entity, const nlohmann::json& json, flecs::entity parentEntity);
|
||||
@@ -65,6 +68,9 @@ private:
|
||||
void deserializeStaticGeometry(flecs::entity entity, const nlohmann::json& json);
|
||||
void deserializeStaticGeometryMember(flecs::entity entity, const nlohmann::json& json);
|
||||
void deserializeProceduralTexture(flecs::entity entity, const nlohmann::json& json);
|
||||
void deserializeProceduralMaterial(flecs::entity entity, const nlohmann::json& json);
|
||||
void deserializePrimitive(flecs::entity entity, const nlohmann::json& json);
|
||||
void deserializeTriangleBuffer(flecs::entity entity, const nlohmann::json& json);
|
||||
|
||||
flecs::world& m_world;
|
||||
Ogre::SceneManager* m_sceneMgr;
|
||||
|
||||
83
src/features/editScene/ui/PrimitiveEditor.cpp
Normal file
83
src/features/editScene/ui/PrimitiveEditor.cpp
Normal file
@@ -0,0 +1,83 @@
|
||||
#include "PrimitiveEditor.hpp"
|
||||
#include <imgui.h>
|
||||
|
||||
bool PrimitiveEditor::renderComponent(flecs::entity entity, PrimitiveComponent &primitive)
|
||||
{
|
||||
bool modified = false;
|
||||
|
||||
if (ImGui::CollapsingHeader("Primitive", ImGuiTreeNodeFlags_DefaultOpen)) {
|
||||
ImGui::Indent();
|
||||
|
||||
// Type selector
|
||||
const char* typeNames[] = {"None", "Box", "Plane"};
|
||||
int currentType = static_cast<int>(primitive.type);
|
||||
|
||||
if (ImGui::Combo("Type", ¤tType, typeNames, IM_ARRAYSIZE(typeNames))) {
|
||||
primitive.type = static_cast<PrimitiveComponent::Type>(currentType);
|
||||
primitive.markDirty();
|
||||
modified = true;
|
||||
}
|
||||
|
||||
ImGui::Separator();
|
||||
|
||||
// Type-specific parameters
|
||||
switch (primitive.type) {
|
||||
case PrimitiveComponent::Type::Box: {
|
||||
ImGui::Text("Box Dimensions:");
|
||||
if (ImGui::DragFloat("Size X", &primitive.boxSizeX, 0.01f, 0.001f, 100.0f)) {
|
||||
primitive.markDirty();
|
||||
modified = true;
|
||||
}
|
||||
if (ImGui::DragFloat("Size Y", &primitive.boxSizeY, 0.01f, 0.001f, 100.0f)) {
|
||||
primitive.markDirty();
|
||||
modified = true;
|
||||
}
|
||||
if (ImGui::DragFloat("Size Z", &primitive.boxSizeZ, 0.01f, 0.001f, 100.0f)) {
|
||||
primitive.markDirty();
|
||||
modified = true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case PrimitiveComponent::Type::Plane: {
|
||||
ImGui::Text("Plane Dimensions:");
|
||||
if (ImGui::DragFloat("Size X", &primitive.planeSizeX, 0.01f, 0.001f, 100.0f)) {
|
||||
primitive.markDirty();
|
||||
modified = true;
|
||||
}
|
||||
if (ImGui::DragFloat("Size Y", &primitive.planeSizeY, 0.01f, 0.001f, 100.0f)) {
|
||||
primitive.markDirty();
|
||||
modified = true;
|
||||
}
|
||||
ImGui::Separator();
|
||||
ImGui::Text("Segments:");
|
||||
if (ImGui::DragInt("Segments X", &primitive.planeSegmentsX, 1, 1, 100)) {
|
||||
primitive.markDirty();
|
||||
modified = true;
|
||||
}
|
||||
if (ImGui::DragInt("Segments Y", &primitive.planeSegmentsY, 1, 1, 100)) {
|
||||
primitive.markDirty();
|
||||
modified = true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
ImGui::TextDisabled("Select a primitive type to configure");
|
||||
break;
|
||||
}
|
||||
|
||||
ImGui::Separator();
|
||||
|
||||
// Status
|
||||
if (primitive.dirty) {
|
||||
ImGui::TextColored(ImVec4(1, 1, 0, 1), "Status: Modified (needs rebuild)");
|
||||
} else {
|
||||
ImGui::TextColored(ImVec4(0, 1, 0, 1), "Status: Up to date");
|
||||
}
|
||||
|
||||
ImGui::TextDisabled("Note: Add this entity as child of a TriangleBuffer entity");
|
||||
|
||||
ImGui::Unindent();
|
||||
}
|
||||
|
||||
return modified;
|
||||
}
|
||||
18
src/features/editScene/ui/PrimitiveEditor.hpp
Normal file
18
src/features/editScene/ui/PrimitiveEditor.hpp
Normal file
@@ -0,0 +1,18 @@
|
||||
#ifndef EDITSCENE_PRIMITIVEEDITOR_HPP
|
||||
#define EDITSCENE_PRIMITIVEEDITOR_HPP
|
||||
#pragma once
|
||||
|
||||
#include "ComponentEditor.hpp"
|
||||
#include "../components/Primitive.hpp"
|
||||
#include <flecs.h>
|
||||
|
||||
/**
|
||||
* Editor for PrimitiveComponent (Box, Plane, etc.)
|
||||
*/
|
||||
class PrimitiveEditor : public ComponentEditor<PrimitiveComponent> {
|
||||
public:
|
||||
bool renderComponent(flecs::entity entity, PrimitiveComponent &primitive) override;
|
||||
const char *getName() const override { return "Primitive"; }
|
||||
};
|
||||
|
||||
#endif // EDITSCENE_PRIMITIVEEDITOR_HPP
|
||||
171
src/features/editScene/ui/ProceduralMaterialEditor.cpp
Normal file
171
src/features/editScene/ui/ProceduralMaterialEditor.cpp
Normal file
@@ -0,0 +1,171 @@
|
||||
#include "ProceduralMaterialEditor.hpp"
|
||||
#include "../components/ProceduralTexture.hpp"
|
||||
#include <imgui.h>
|
||||
|
||||
ProceduralMaterialEditor::ProceduralMaterialEditor(Ogre::SceneManager* sceneMgr)
|
||||
: m_sceneMgr(sceneMgr)
|
||||
{
|
||||
}
|
||||
|
||||
void ProceduralMaterialEditor::renderTextureSelector(flecs::entity entity, ProceduralMaterialComponent &material)
|
||||
{
|
||||
// Get the world from the entity
|
||||
flecs::world world = entity.world();
|
||||
|
||||
// Collect all entities with ProceduralTextureComponent
|
||||
std::vector<flecs::entity> textureEntities;
|
||||
std::vector<std::string> textureNames;
|
||||
std::vector<std::string> displayNames;
|
||||
|
||||
int currentIndex = -1;
|
||||
int noneIndex = 0;
|
||||
|
||||
// Add "None" option
|
||||
displayNames.push_back("None");
|
||||
textureEntities.push_back(flecs::entity::null());
|
||||
textureNames.push_back("");
|
||||
|
||||
world.query<ProceduralTextureComponent>().each([&](flecs::entity e, ProceduralTextureComponent& texture) {
|
||||
textureEntities.push_back(e);
|
||||
|
||||
std::string name = texture.textureName.empty() ?
|
||||
"Texture " + std::to_string(e.id()) : texture.textureName;
|
||||
textureNames.push_back(name);
|
||||
|
||||
// Build display name
|
||||
std::string display = name;
|
||||
if (texture.generated) {
|
||||
display += " (generated)";
|
||||
} else {
|
||||
display += " (pending)";
|
||||
}
|
||||
displayNames.push_back(display);
|
||||
|
||||
if (material.diffuseTextureEntity == e) {
|
||||
currentIndex = (int)textureEntities.size() - 1;
|
||||
}
|
||||
});
|
||||
|
||||
// Default to none if not found
|
||||
if (currentIndex == -1) {
|
||||
currentIndex = noneIndex;
|
||||
}
|
||||
|
||||
// Build combo string
|
||||
std::string comboItems;
|
||||
for (size_t i = 0; i < displayNames.size(); ++i) {
|
||||
if (i > 0) comboItems += '\0';
|
||||
comboItems += displayNames[i];
|
||||
}
|
||||
comboItems += '\0';
|
||||
|
||||
int newIndex = currentIndex;
|
||||
if (ImGui::Combo("Diffuse Texture", &newIndex, comboItems.c_str())) {
|
||||
if (newIndex >= 0 && newIndex < (int)textureEntities.size()) {
|
||||
if (newIndex == noneIndex) {
|
||||
material.diffuseTextureEntity = flecs::entity::null();
|
||||
} else {
|
||||
material.diffuseTextureEntity = textureEntities[newIndex];
|
||||
}
|
||||
material.markDirty();
|
||||
}
|
||||
}
|
||||
|
||||
// Show warning if no textures available
|
||||
if (textureEntities.size() <= 1) {
|
||||
ImGui::TextColored(ImVec4(1, 0.5f, 0, 1), "Create a Procedural Texture first!");
|
||||
}
|
||||
}
|
||||
|
||||
bool ProceduralMaterialEditor::renderComponent(flecs::entity entity, ProceduralMaterialComponent &material)
|
||||
{
|
||||
bool modified = false;
|
||||
|
||||
if (ImGui::CollapsingHeader("Procedural Material", ImGuiTreeNodeFlags_DefaultOpen)) {
|
||||
ImGui::Indent();
|
||||
|
||||
// Material info
|
||||
if (material.created) {
|
||||
ImGui::TextColored(ImVec4(0, 1, 0, 1), "Status: Created");
|
||||
ImGui::Text("Material: %s", material.materialName.c_str());
|
||||
} else if (material.dirty) {
|
||||
ImGui::TextColored(ImVec4(1, 1, 0, 1), "Status: Needs Creation");
|
||||
} else {
|
||||
ImGui::TextColored(ImVec4(0.5f, 0.5f, 0.5f, 1), "Status: Not Created");
|
||||
}
|
||||
|
||||
ImGui::Separator();
|
||||
|
||||
// Material name
|
||||
char nameBuf[256];
|
||||
strncpy(nameBuf, material.materialName.c_str(), sizeof(nameBuf) - 1);
|
||||
nameBuf[sizeof(nameBuf) - 1] = '\0';
|
||||
|
||||
if (ImGui::InputText("Material Name", nameBuf, sizeof(nameBuf))) {
|
||||
material.materialName = nameBuf;
|
||||
material.markDirty();
|
||||
modified = true;
|
||||
}
|
||||
ImGui::TextDisabled("(Leave empty for auto-generated name)");
|
||||
|
||||
ImGui::Separator();
|
||||
|
||||
// Diffuse texture selector
|
||||
renderTextureSelector(entity, material);
|
||||
|
||||
ImGui::Separator();
|
||||
|
||||
// Material colors
|
||||
ImGui::Text("Material Properties:");
|
||||
|
||||
if (ImGui::ColorEdit3("Ambient", material.ambient)) {
|
||||
material.markDirty();
|
||||
modified = true;
|
||||
}
|
||||
|
||||
if (ImGui::ColorEdit3("Diffuse Multiplier", material.diffuse)) {
|
||||
material.markDirty();
|
||||
modified = true;
|
||||
}
|
||||
|
||||
if (ImGui::ColorEdit3("Specular", material.specular)) {
|
||||
material.markDirty();
|
||||
modified = true;
|
||||
}
|
||||
|
||||
if (ImGui::SliderFloat("Shininess", &material.shininess, 1.0f, 128.0f)) {
|
||||
material.markDirty();
|
||||
modified = true;
|
||||
}
|
||||
|
||||
if (ImGui::SliderFloat("Roughness", &material.roughness, 0.0f, 1.0f)) {
|
||||
material.markDirty();
|
||||
modified = true;
|
||||
}
|
||||
|
||||
ImGui::Separator();
|
||||
|
||||
// Recreate button
|
||||
if (ImGui::Button("Recreate Material")) {
|
||||
material.markDirty();
|
||||
modified = true;
|
||||
}
|
||||
|
||||
ImGui::SameLine();
|
||||
|
||||
// Reset to defaults
|
||||
if (ImGui::Button("Reset to Defaults")) {
|
||||
material.ambient[0] = 0.2f; material.ambient[1] = 0.2f; material.ambient[2] = 0.2f;
|
||||
material.diffuse[0] = 1.0f; material.diffuse[1] = 1.0f; material.diffuse[2] = 1.0f;
|
||||
material.specular[0] = 0.0f; material.specular[1] = 0.0f; material.specular[2] = 0.0f;
|
||||
material.shininess = 32.0f;
|
||||
material.roughness = 0.5f;
|
||||
material.markDirty();
|
||||
modified = true;
|
||||
}
|
||||
|
||||
ImGui::Unindent();
|
||||
}
|
||||
|
||||
return modified;
|
||||
}
|
||||
25
src/features/editScene/ui/ProceduralMaterialEditor.hpp
Normal file
25
src/features/editScene/ui/ProceduralMaterialEditor.hpp
Normal file
@@ -0,0 +1,25 @@
|
||||
#ifndef EDITSCENE_PROCEDURALMATERIALEDITOR_HPP
|
||||
#define EDITSCENE_PROCEDURALMATERIALEDITOR_HPP
|
||||
#pragma once
|
||||
|
||||
#include "ComponentEditor.hpp"
|
||||
#include "../components/ProceduralMaterial.hpp"
|
||||
#include <OgreSceneManager.h>
|
||||
#include <flecs.h>
|
||||
|
||||
/**
|
||||
* Editor for ProceduralMaterialComponent
|
||||
*/
|
||||
class ProceduralMaterialEditor : public ComponentEditor<ProceduralMaterialComponent> {
|
||||
public:
|
||||
ProceduralMaterialEditor(Ogre::SceneManager* sceneMgr = nullptr);
|
||||
bool renderComponent(flecs::entity entity, ProceduralMaterialComponent &material) override;
|
||||
const char *getName() const override { return "Procedural Material"; }
|
||||
|
||||
private:
|
||||
void renderTextureSelector(flecs::entity entity, ProceduralMaterialComponent &material);
|
||||
|
||||
Ogre::SceneManager* m_sceneMgr;
|
||||
};
|
||||
|
||||
#endif // EDITSCENE_PROCEDURALMATERIALEDITOR_HPP
|
||||
@@ -126,6 +126,7 @@ bool TransformEditor::renderComponent(flecs::entity entity,
|
||||
// Apply changes to scene node
|
||||
if (modified && transform.node) {
|
||||
transform.applyToNode();
|
||||
transform.markChanged();
|
||||
}
|
||||
|
||||
ImGui::Unindent();
|
||||
|
||||
283
src/features/editScene/ui/TriangleBufferEditor.cpp
Normal file
283
src/features/editScene/ui/TriangleBufferEditor.cpp
Normal file
@@ -0,0 +1,283 @@
|
||||
#include "TriangleBufferEditor.hpp"
|
||||
#include "../components/ProceduralTexture.hpp"
|
||||
#include "../components/ProceduralMaterial.hpp"
|
||||
#include "../components/StaticGeometry.hpp"
|
||||
#include "../components/Primitive.hpp"
|
||||
#include <imgui.h>
|
||||
|
||||
TriangleBufferEditor::TriangleBufferEditor(Ogre::SceneManager* sceneMgr)
|
||||
: m_sceneMgr(sceneMgr)
|
||||
{
|
||||
}
|
||||
|
||||
void TriangleBufferEditor::renderTextureSelector(flecs::entity entity, TriangleBufferComponent &tb)
|
||||
{
|
||||
flecs::world world = entity.world();
|
||||
|
||||
std::vector<flecs::entity> textureEntities;
|
||||
std::vector<std::string> textureNames;
|
||||
|
||||
int currentIndex = -1;
|
||||
int noneIndex = 0;
|
||||
|
||||
textureEntities.push_back(flecs::entity::null());
|
||||
textureNames.push_back("None");
|
||||
|
||||
world.query<ProceduralTextureComponent>().each([&](flecs::entity e, ProceduralTextureComponent& tex) {
|
||||
textureEntities.push_back(e);
|
||||
std::string name = tex.textureName.empty() ? "Texture " + std::to_string(e.id()) : tex.textureName;
|
||||
textureNames.push_back(name);
|
||||
|
||||
if (tb.textureEntity == e) {
|
||||
currentIndex = (int)textureEntities.size() - 1;
|
||||
}
|
||||
});
|
||||
|
||||
if (currentIndex == -1) currentIndex = noneIndex;
|
||||
|
||||
std::string comboItems;
|
||||
for (size_t i = 0; i < textureNames.size(); ++i) {
|
||||
if (i > 0) comboItems += '\0';
|
||||
comboItems += textureNames[i];
|
||||
}
|
||||
comboItems += '\0';
|
||||
|
||||
int newIndex = currentIndex;
|
||||
if (ImGui::Combo("Texture Source", &newIndex, comboItems.c_str())) {
|
||||
if (newIndex == noneIndex) {
|
||||
tb.textureEntity = flecs::entity::null();
|
||||
tb.textureRectName.clear();
|
||||
} else {
|
||||
tb.textureEntity = textureEntities[newIndex];
|
||||
}
|
||||
tb.markDirty();
|
||||
}
|
||||
}
|
||||
|
||||
void TriangleBufferEditor::renderRectSelector(flecs::entity entity, TriangleBufferComponent &tb)
|
||||
{
|
||||
if (!tb.textureEntity.is_alive() || !tb.textureEntity.has<ProceduralTextureComponent>()) {
|
||||
ImGui::TextDisabled("Select a texture first");
|
||||
return;
|
||||
}
|
||||
|
||||
const auto& texture = tb.textureEntity.get<ProceduralTextureComponent>();
|
||||
const auto& namedRects = texture.getAllNamedRects();
|
||||
|
||||
if (namedRects.empty()) {
|
||||
ImGui::TextColored(ImVec4(1, 0.5f, 0, 1), "No named rectangles in texture!");
|
||||
ImGui::TextDisabled("Add named rectangles in the Procedural Texture component");
|
||||
return;
|
||||
}
|
||||
|
||||
std::vector<std::string> rectNames;
|
||||
int currentIndex = -1;
|
||||
int noneIndex = 0;
|
||||
|
||||
rectNames.push_back("None (use full texture)");
|
||||
|
||||
for (const auto& pair : namedRects) {
|
||||
rectNames.push_back(pair.first);
|
||||
if (tb.textureRectName == pair.first) {
|
||||
currentIndex = (int)rectNames.size() - 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (currentIndex == -1) currentIndex = noneIndex;
|
||||
|
||||
std::string comboItems;
|
||||
for (size_t i = 0; i < rectNames.size(); ++i) {
|
||||
if (i > 0) comboItems += '\0';
|
||||
comboItems += rectNames[i];
|
||||
}
|
||||
comboItems += '\0';
|
||||
|
||||
int newIndex = currentIndex;
|
||||
if (ImGui::Combo("Texture Rectangle", &newIndex, comboItems.c_str())) {
|
||||
if (newIndex == noneIndex) {
|
||||
tb.textureRectName.clear();
|
||||
} else {
|
||||
tb.textureRectName = rectNames[newIndex];
|
||||
}
|
||||
tb.markDirty();
|
||||
}
|
||||
}
|
||||
|
||||
void TriangleBufferEditor::renderMaterialSelector(flecs::entity entity, TriangleBufferComponent &tb)
|
||||
{
|
||||
flecs::world world = entity.world();
|
||||
|
||||
std::vector<flecs::entity> materialEntities;
|
||||
std::vector<std::string> materialNames;
|
||||
|
||||
int currentIndex = -1;
|
||||
int noneIndex = 0;
|
||||
|
||||
materialEntities.push_back(flecs::entity::null());
|
||||
materialNames.push_back("None");
|
||||
|
||||
world.query<ProceduralMaterialComponent>().each([&](flecs::entity e, ProceduralMaterialComponent& mat) {
|
||||
materialEntities.push_back(e);
|
||||
std::string name = mat.materialName.empty() ? "Material " + std::to_string(e.id()) : mat.materialName;
|
||||
if (mat.created) name += " (created)";
|
||||
else name += " (pending)";
|
||||
materialNames.push_back(name);
|
||||
|
||||
if (tb.materialEntity == e) {
|
||||
currentIndex = (int)materialEntities.size() - 1;
|
||||
}
|
||||
});
|
||||
|
||||
if (currentIndex == -1) currentIndex = noneIndex;
|
||||
|
||||
std::string comboItems;
|
||||
for (size_t i = 0; i < materialNames.size(); ++i) {
|
||||
if (i > 0) comboItems += '\0';
|
||||
comboItems += materialNames[i];
|
||||
}
|
||||
comboItems += '\0';
|
||||
|
||||
int newIndex = currentIndex;
|
||||
if (ImGui::Combo("Material", &newIndex, comboItems.c_str())) {
|
||||
if (newIndex == noneIndex) {
|
||||
tb.materialEntity = flecs::entity::null();
|
||||
} else {
|
||||
tb.materialEntity = materialEntities[newIndex];
|
||||
}
|
||||
tb.markDirty();
|
||||
}
|
||||
}
|
||||
|
||||
void TriangleBufferEditor::renderStaticGeometrySelector(flecs::entity entity, TriangleBufferComponent &tb)
|
||||
{
|
||||
flecs::world world = entity.world();
|
||||
|
||||
if (!tb.useStaticGeometry) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::vector<flecs::entity> regionEntities;
|
||||
std::vector<std::string> regionNames;
|
||||
|
||||
int currentIndex = -1;
|
||||
int noneIndex = 0;
|
||||
|
||||
regionEntities.push_back(flecs::entity::null());
|
||||
regionNames.push_back("None");
|
||||
|
||||
world.query<StaticGeometryComponent>().each([&](flecs::entity e, StaticGeometryComponent& region) {
|
||||
regionEntities.push_back(e);
|
||||
std::string name = region.regionName.empty() ? "Region " + std::to_string(e.id()) : region.regionName;
|
||||
regionNames.push_back(name);
|
||||
|
||||
if (tb.staticGeometryEntity == e) {
|
||||
currentIndex = (int)regionEntities.size() - 1;
|
||||
}
|
||||
});
|
||||
|
||||
if (currentIndex == -1) currentIndex = noneIndex;
|
||||
|
||||
std::string comboItems;
|
||||
for (size_t i = 0; i < regionNames.size(); ++i) {
|
||||
if (i > 0) comboItems += '\0';
|
||||
comboItems += regionNames[i];
|
||||
}
|
||||
comboItems += '\0';
|
||||
|
||||
int newIndex = currentIndex;
|
||||
if (ImGui::Combo("StaticGeometry Region", &newIndex, comboItems.c_str())) {
|
||||
if (newIndex == noneIndex) {
|
||||
tb.staticGeometryEntity = flecs::entity::null();
|
||||
} else {
|
||||
tb.staticGeometryEntity = regionEntities[newIndex];
|
||||
}
|
||||
tb.markDirty();
|
||||
}
|
||||
|
||||
if (regionEntities.size() <= 1) {
|
||||
ImGui::TextColored(ImVec4(1, 0.5f, 0, 1), "No StaticGeometry regions! Create one first.");
|
||||
}
|
||||
}
|
||||
|
||||
bool TriangleBufferEditor::renderComponent(flecs::entity entity, TriangleBufferComponent &tb)
|
||||
{
|
||||
bool modified = false;
|
||||
|
||||
if (ImGui::CollapsingHeader("Triangle Buffer", ImGuiTreeNodeFlags_DefaultOpen)) {
|
||||
ImGui::Indent();
|
||||
|
||||
// Status
|
||||
if (tb.meshCreated) {
|
||||
ImGui::TextColored(ImVec4(0, 1, 0, 1), "Status: Mesh Created");
|
||||
ImGui::Text("Mesh: %s", tb.meshName.c_str());
|
||||
} else if (tb.dirty) {
|
||||
ImGui::TextColored(ImVec4(1, 1, 0, 1), "Status: Needs Rebuild");
|
||||
} else {
|
||||
ImGui::TextColored(ImVec4(0.5f, 0.5f, 0.5f, 1), "Status: Empty");
|
||||
}
|
||||
|
||||
ImGui::Separator();
|
||||
|
||||
// Mesh name
|
||||
char nameBuf[256];
|
||||
strncpy(nameBuf, tb.meshName.c_str(), sizeof(nameBuf) - 1);
|
||||
nameBuf[sizeof(nameBuf) - 1] = '\0';
|
||||
|
||||
if (ImGui::InputText("Mesh Name", nameBuf, sizeof(nameBuf))) {
|
||||
tb.meshName = nameBuf;
|
||||
tb.markDirty();
|
||||
modified = true;
|
||||
}
|
||||
|
||||
ImGui::Separator();
|
||||
|
||||
// Texture and UV mapping
|
||||
ImGui::Text("Texture Mapping:");
|
||||
renderTextureSelector(entity, tb);
|
||||
renderRectSelector(entity, tb);
|
||||
ImGui::TextDisabled("UVs will be mapped to rectangle with 0.01 margin");
|
||||
|
||||
ImGui::Separator();
|
||||
|
||||
// Material
|
||||
ImGui::Text("Material:");
|
||||
renderMaterialSelector(entity, tb);
|
||||
|
||||
ImGui::Separator();
|
||||
|
||||
// Output mode
|
||||
ImGui::Text("Output:");
|
||||
if (ImGui::Checkbox("Use StaticGeometry", &tb.useStaticGeometry)) {
|
||||
tb.markDirty();
|
||||
modified = true;
|
||||
}
|
||||
|
||||
if (tb.useStaticGeometry) {
|
||||
renderStaticGeometrySelector(entity, tb);
|
||||
}
|
||||
|
||||
ImGui::Separator();
|
||||
|
||||
// Rebuild button
|
||||
if (ImGui::Button("Force Rebuild")) {
|
||||
tb.markDirty();
|
||||
modified = true;
|
||||
}
|
||||
|
||||
ImGui::Separator();
|
||||
|
||||
// Info
|
||||
ImGui::TextDisabled("Add Primitive components as children to build geometry");
|
||||
|
||||
// Count children primitives
|
||||
int primCount = 0;
|
||||
entity.children([&](flecs::entity child) {
|
||||
if (child.has<PrimitiveComponent>()) primCount++;
|
||||
});
|
||||
ImGui::Text("Child primitives: %d", primCount);
|
||||
|
||||
ImGui::Unindent();
|
||||
}
|
||||
|
||||
return modified;
|
||||
}
|
||||
28
src/features/editScene/ui/TriangleBufferEditor.hpp
Normal file
28
src/features/editScene/ui/TriangleBufferEditor.hpp
Normal file
@@ -0,0 +1,28 @@
|
||||
#ifndef EDITSCENE_TRIANGLEBUFFEREDITOR_HPP
|
||||
#define EDITSCENE_TRIANGLEBUFFEREDITOR_HPP
|
||||
#pragma once
|
||||
|
||||
#include "ComponentEditor.hpp"
|
||||
#include "../components/TriangleBuffer.hpp"
|
||||
#include <OgreSceneManager.h>
|
||||
#include <flecs.h>
|
||||
|
||||
/**
|
||||
* Editor for TriangleBufferComponent
|
||||
*/
|
||||
class TriangleBufferEditor : public ComponentEditor<TriangleBufferComponent> {
|
||||
public:
|
||||
TriangleBufferEditor(Ogre::SceneManager* sceneMgr = nullptr);
|
||||
bool renderComponent(flecs::entity entity, TriangleBufferComponent &tb) override;
|
||||
const char *getName() const override { return "Triangle Buffer"; }
|
||||
|
||||
private:
|
||||
void renderTextureSelector(flecs::entity entity, TriangleBufferComponent &tb);
|
||||
void renderRectSelector(flecs::entity entity, TriangleBufferComponent &tb);
|
||||
void renderMaterialSelector(flecs::entity entity, TriangleBufferComponent &tb);
|
||||
void renderStaticGeometrySelector(flecs::entity entity, TriangleBufferComponent &tb);
|
||||
|
||||
Ogre::SceneManager* m_sceneMgr;
|
||||
};
|
||||
|
||||
#endif // EDITSCENE_TRIANGLEBUFFEREDITOR_HPP
|
||||
Reference in New Issue
Block a user