TriangleBuffer

This commit is contained in:
2026-04-04 05:13:15 +03:00
parent b1413d6d00
commit b8c61da1f7
25 changed files with 1669 additions and 0 deletions

View File

@@ -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

View File

@@ -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;

View File

@@ -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;

View 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; }
};

View 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>();
}
}
);
}

View 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;
}
};

View File

@@ -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>();
}
}
);
}

View File

@@ -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

View 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; }
};

View 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>();
}
}
);
}

View File

@@ -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>()) {

View File

@@ -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) {

View 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);
}

View 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);
};

View 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 + "'");
}

View 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);
};

View File

@@ -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);
}

View File

@@ -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;

View 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", &currentType, 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;
}

View 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

View 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;
}

View 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

View File

@@ -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();

View 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;
}

View 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