Procedural texture implementation
This commit is contained in:
@@ -19,6 +19,7 @@ set(EDITSCENE_SOURCES
|
||||
systems/CameraSystem.cpp
|
||||
systems/LodSystem.cpp
|
||||
systems/StaticGeometrySystem.cpp
|
||||
systems/ProceduralTextureSystem.cpp
|
||||
ui/TransformEditor.cpp
|
||||
ui/RenderableEditor.cpp
|
||||
ui/PhysicsColliderEditor.cpp
|
||||
@@ -29,11 +30,13 @@ set(EDITSCENE_SOURCES
|
||||
ui/LodSettingsEditor.cpp
|
||||
ui/StaticGeometryEditor.cpp
|
||||
ui/StaticGeometryMemberEditor.cpp
|
||||
ui/ProceduralTextureEditor.cpp
|
||||
ui/ComponentRegistration.cpp
|
||||
components/LightModule.cpp
|
||||
components/CameraModule.cpp
|
||||
components/LodModule.cpp
|
||||
components/StaticGeometryModule.cpp
|
||||
components/ProceduralTextureModule.cpp
|
||||
camera/EditorCamera.cpp
|
||||
gizmo/Gizmo.cpp
|
||||
physics/physics.cpp
|
||||
@@ -53,7 +56,9 @@ set(EDITSCENE_HEADERS
|
||||
components/LodSettings.hpp
|
||||
components/StaticGeometry.hpp
|
||||
components/StaticGeometryMember.hpp
|
||||
components/ProceduralTexture.hpp
|
||||
systems/EditorUISystem.hpp
|
||||
systems/ProceduralTextureSystem.hpp
|
||||
systems/StaticGeometrySystem.hpp
|
||||
systems/SceneSerializer.hpp
|
||||
systems/PhysicsSystem.hpp
|
||||
@@ -71,6 +76,9 @@ set(EDITSCENE_HEADERS
|
||||
ui/CameraEditor.hpp
|
||||
ui/LodEditor.hpp
|
||||
ui/LodSettingsEditor.hpp
|
||||
ui/StaticGeometryEditor.hpp
|
||||
ui/StaticGeometryMemberEditor.hpp
|
||||
ui/ProceduralTextureEditor.hpp
|
||||
camera/EditorCamera.hpp
|
||||
gizmo/Gizmo.hpp
|
||||
physics/physics.h
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
#include "systems/CameraSystem.hpp"
|
||||
#include "systems/LodSystem.hpp"
|
||||
#include "systems/StaticGeometrySystem.hpp"
|
||||
#include "systems/ProceduralTextureSystem.hpp"
|
||||
#include "camera/EditorCamera.hpp"
|
||||
#include "components/EntityName.hpp"
|
||||
#include "components/Transform.hpp"
|
||||
@@ -19,6 +20,7 @@
|
||||
#include "components/LodSettings.hpp"
|
||||
#include "components/StaticGeometry.hpp"
|
||||
#include "components/StaticGeometryMember.hpp"
|
||||
#include "components/ProceduralTexture.hpp"
|
||||
#include <OgreRTShaderSystem.h>
|
||||
#include <imgui.h>
|
||||
|
||||
@@ -69,6 +71,29 @@ EditorApp::EditorApp()
|
||||
|
||||
EditorApp::~EditorApp()
|
||||
{
|
||||
// Shutdown UI system first (cleans up gizmo while SceneManager is still valid)
|
||||
if (m_uiSystem) {
|
||||
m_uiSystem->shutdown();
|
||||
}
|
||||
|
||||
// Delete all editor entities before OGRE cleanup
|
||||
// This ensures all components with Ogre resources are cleaned up while SceneManager exists
|
||||
m_world.query<EditorMarkerComponent>().each([&](flecs::entity e, EditorMarkerComponent) {
|
||||
e.destruct();
|
||||
});
|
||||
|
||||
// Release all systems
|
||||
m_proceduralTextureSystem.reset();
|
||||
m_staticGeometrySystem.reset();
|
||||
m_lodSystem.reset();
|
||||
m_cameraSystem.reset();
|
||||
m_lightSystem.reset();
|
||||
m_physicsSystem.reset();
|
||||
m_imguiListener.reset();
|
||||
m_uiSystem.reset();
|
||||
m_camera.reset();
|
||||
|
||||
// Now OGRE can shut down safely
|
||||
// Singletons are managed by OGRE, don't delete them
|
||||
}
|
||||
|
||||
@@ -135,6 +160,10 @@ void EditorApp::setup()
|
||||
// Setup StaticGeometry system
|
||||
m_staticGeometrySystem = std::make_unique<StaticGeometrySystem>(m_world, m_sceneMgr);
|
||||
m_staticGeometrySystem->initialize();
|
||||
|
||||
// Setup ProceduralTexture system
|
||||
m_proceduralTextureSystem = std::make_unique<ProceduralTextureSystem>(m_world, m_sceneMgr);
|
||||
m_proceduralTextureSystem->initialize();
|
||||
|
||||
// Add default entities to UI cache
|
||||
for (auto &e : m_defaultEntities) {
|
||||
@@ -179,6 +208,9 @@ void EditorApp::setupECS()
|
||||
// Register StaticGeometry components
|
||||
m_world.component<StaticGeometryComponent>();
|
||||
m_world.component<StaticGeometryMemberComponent>();
|
||||
|
||||
// Register ProceduralTexture component
|
||||
m_world.component<ProceduralTextureComponent>();
|
||||
}
|
||||
|
||||
void EditorApp::createDefaultEntities()
|
||||
@@ -326,6 +358,11 @@ bool EditorApp::frameRenderingQueued(const Ogre::FrameEvent &evt)
|
||||
if (m_staticGeometrySystem) {
|
||||
m_staticGeometrySystem->update();
|
||||
}
|
||||
|
||||
// Update ProceduralTexture system
|
||||
if (m_proceduralTextureSystem) {
|
||||
m_proceduralTextureSystem->update();
|
||||
}
|
||||
|
||||
// Don't call base class - it crashes when iterating input listeners
|
||||
return true;
|
||||
|
||||
@@ -18,6 +18,7 @@ class EditorLightSystem;
|
||||
class EditorCameraSystem;
|
||||
class EditorLodSystem;
|
||||
class StaticGeometrySystem;
|
||||
class ProceduralTextureSystem;
|
||||
|
||||
/**
|
||||
* RenderTargetListener for ImGui frame management
|
||||
@@ -88,6 +89,7 @@ private:
|
||||
std::unique_ptr<EditorCameraSystem> m_cameraSystem;
|
||||
std::unique_ptr<EditorLodSystem> m_lodSystem;
|
||||
std::unique_ptr<StaticGeometrySystem> m_staticGeometrySystem;
|
||||
std::unique_ptr<ProceduralTextureSystem> m_proceduralTextureSystem;
|
||||
|
||||
// State
|
||||
uint16_t m_currentModifiers;
|
||||
|
||||
145
src/features/editScene/components/ProceduralTexture.hpp
Normal file
145
src/features/editScene/components/ProceduralTexture.hpp
Normal file
@@ -0,0 +1,145 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <array>
|
||||
#include <vector>
|
||||
#include <map>
|
||||
#include <Ogre.h>
|
||||
|
||||
/**
|
||||
* @brief Information about a named rectangle in the texture atlas
|
||||
*/
|
||||
struct TextureRectInfo {
|
||||
std::string name;
|
||||
float u1, v1; // Top-left UV (0-1 range)
|
||||
float u2, v2; // Bottom-right UV (0-1 range)
|
||||
|
||||
TextureRectInfo() : u1(0), v1(0), u2(1), v2(1) {}
|
||||
TextureRectInfo(const std::string& n, float left, float top, float right, float bottom)
|
||||
: name(n), u1(left), v1(top), u2(right), v2(bottom) {}
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Component for procedural texture generation with 10x10 colored rectangles
|
||||
*
|
||||
* Uses OgreProcedural to generate a texture with a grid of colored rectangles.
|
||||
* Each rectangle color is individually selectable.
|
||||
* Also supports named rectangles for texture atlas/UV mapping.
|
||||
*/
|
||||
struct ProceduralTextureComponent {
|
||||
// Texture name for Ogre resource
|
||||
std::string textureName;
|
||||
|
||||
// Grid dimensions (default 10x10)
|
||||
static constexpr int GRID_SIZE = 10;
|
||||
static constexpr int RECT_COUNT = GRID_SIZE * GRID_SIZE;
|
||||
|
||||
// Colors for each rectangle (stored as float4: r, g, b, a)
|
||||
std::array<float, RECT_COUNT * 4> colors;
|
||||
|
||||
// Named rectangles for texture atlas (name -> rect info)
|
||||
std::map<std::string, TextureRectInfo> namedRects;
|
||||
|
||||
// Texture size (default 512x512)
|
||||
int textureSize = 512;
|
||||
|
||||
// Whether the texture needs regeneration
|
||||
bool dirty = true;
|
||||
|
||||
// Whether the texture has been generated
|
||||
bool generated = false;
|
||||
|
||||
// Pointer to the generated Ogre texture
|
||||
Ogre::TexturePtr ogreTexture;
|
||||
|
||||
ProceduralTextureComponent() {
|
||||
// Initialize with default colors (checkerboard pattern)
|
||||
for (int i = 0; i < RECT_COUNT; ++i) {
|
||||
int row = i / GRID_SIZE;
|
||||
int col = i % GRID_SIZE;
|
||||
bool isWhite = (row + col) % 2 == 0;
|
||||
|
||||
colors[i * 4 + 0] = isWhite ? 1.0f : 0.0f; // R
|
||||
colors[i * 4 + 1] = isWhite ? 1.0f : 0.0f; // G
|
||||
colors[i * 4 + 2] = isWhite ? 1.0f : 0.0f; // B
|
||||
colors[i * 4 + 3] = 1.0f; // A
|
||||
}
|
||||
}
|
||||
|
||||
// Get color for a specific rectangle
|
||||
Ogre::ColourValue getColor(int index) const {
|
||||
if (index < 0 || index >= RECT_COUNT) return Ogre::ColourValue::White;
|
||||
return Ogre::ColourValue(
|
||||
colors[index * 4 + 0],
|
||||
colors[index * 4 + 1],
|
||||
colors[index * 4 + 2],
|
||||
colors[index * 4 + 3]
|
||||
);
|
||||
}
|
||||
|
||||
// Set color for a specific rectangle
|
||||
void setColor(int index, const Ogre::ColourValue& color) {
|
||||
if (index < 0 || index >= RECT_COUNT) return;
|
||||
colors[index * 4 + 0] = color.r;
|
||||
colors[index * 4 + 1] = color.g;
|
||||
colors[index * 4 + 2] = color.b;
|
||||
colors[index * 4 + 3] = color.a;
|
||||
dirty = true;
|
||||
}
|
||||
|
||||
// Calculate UV coordinates for a rectangle index
|
||||
void getRectUVs(int index, float& u1, float& v1, float& u2, float& v2) const {
|
||||
int row = index / GRID_SIZE;
|
||||
int col = index % GRID_SIZE;
|
||||
float cellSize = 1.0f / GRID_SIZE;
|
||||
|
||||
u1 = col * cellSize;
|
||||
v1 = row * cellSize;
|
||||
u2 = (col + 1) * cellSize;
|
||||
v2 = (row + 1) * cellSize;
|
||||
}
|
||||
|
||||
// Add a named rectangle
|
||||
bool addNamedRect(const std::string& name, int rectIndex) {
|
||||
if (name.empty() || rectIndex < 0 || rectIndex >= RECT_COUNT) return false;
|
||||
|
||||
float u1, v1, u2, v2;
|
||||
getRectUVs(rectIndex, u1, v1, u2, v2);
|
||||
|
||||
namedRects[name] = TextureRectInfo(name, u1, v1, u2, v2);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Remove a named rectangle
|
||||
bool removeNamedRect(const std::string& name) {
|
||||
auto it = namedRects.find(name);
|
||||
if (it != namedRects.end()) {
|
||||
namedRects.erase(it);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Get named rectangle info
|
||||
const TextureRectInfo* getNamedRect(const std::string& name) const {
|
||||
auto it = namedRects.find(name);
|
||||
if (it != namedRects.end()) {
|
||||
return &it->second;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Check if a name exists
|
||||
bool hasNamedRect(const std::string& name) const {
|
||||
return namedRects.find(name) != namedRects.end();
|
||||
}
|
||||
|
||||
// Get all named rectangles
|
||||
const std::map<std::string, TextureRectInfo>& getAllNamedRects() const {
|
||||
return namedRects;
|
||||
}
|
||||
|
||||
void markDirty() {
|
||||
dirty = true;
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,34 @@
|
||||
#include "ProceduralTexture.hpp"
|
||||
#include "../ui/ComponentRegistration.hpp"
|
||||
#include "../ui/ProceduralTextureEditor.hpp"
|
||||
|
||||
// Register ProceduralTexture component
|
||||
REGISTER_COMPONENT("Procedural Texture", ProceduralTextureComponent, ProceduralTextureEditor)
|
||||
{
|
||||
registry.registerComponent<ProceduralTextureComponent>(
|
||||
"Procedural Texture",
|
||||
std::make_unique<ProceduralTextureEditor>(),
|
||||
// Adder
|
||||
[sceneMgr](flecs::entity e) {
|
||||
if (!e.has<ProceduralTextureComponent>()) {
|
||||
e.set<ProceduralTextureComponent>({});
|
||||
}
|
||||
},
|
||||
// Remover
|
||||
[sceneMgr](flecs::entity e) {
|
||||
if (e.has<ProceduralTextureComponent>()) {
|
||||
auto& texture = e.get_mut<ProceduralTextureComponent>();
|
||||
// Clean up Ogre texture - wrap in try/catch since TextureManager may be shutting down
|
||||
if (texture.ogreTexture) {
|
||||
try {
|
||||
if (Ogre::TextureManager::getSingletonPtr()) {
|
||||
Ogre::TextureManager::getSingleton().remove(texture.ogreTexture);
|
||||
}
|
||||
} catch (...) {}
|
||||
texture.ogreTexture.reset();
|
||||
}
|
||||
e.remove<ProceduralTextureComponent>();
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
@@ -41,34 +41,54 @@ Gizmo::Gizmo(Ogre::SceneManager *sceneMgr)
|
||||
m_gizmoNode->setVisible(false);
|
||||
}
|
||||
|
||||
Gizmo::~Gizmo()
|
||||
void Gizmo::shutdown()
|
||||
{
|
||||
if (!m_sceneMgr) {
|
||||
return; // Already shutdown
|
||||
}
|
||||
|
||||
// Detach objects from node first to avoid double-delete
|
||||
if (m_gizmoNode) {
|
||||
if (m_axisX) m_gizmoNode->detachObject(m_axisX);
|
||||
if (m_axisY) m_gizmoNode->detachObject(m_axisY);
|
||||
if (m_axisZ) m_gizmoNode->detachObject(m_axisZ);
|
||||
// Use try-catch since Ogre objects may be invalid during app shutdown
|
||||
if (m_gizmoNode && m_axisX) {
|
||||
try { m_gizmoNode->detachObject(m_axisX); } catch (...) {}
|
||||
}
|
||||
if (m_gizmoNode && m_axisY) {
|
||||
try { m_gizmoNode->detachObject(m_axisY); } catch (...) {}
|
||||
}
|
||||
if (m_gizmoNode && m_axisZ) {
|
||||
try { m_gizmoNode->detachObject(m_axisZ); } catch (...) {}
|
||||
}
|
||||
|
||||
// Now destroy the manual objects
|
||||
if (m_axisX) {
|
||||
m_sceneMgr->destroyManualObject(m_axisX);
|
||||
try { m_sceneMgr->destroyManualObject(m_axisX); } catch (...) {}
|
||||
m_axisX = nullptr;
|
||||
}
|
||||
if (m_axisY) {
|
||||
m_sceneMgr->destroyManualObject(m_axisY);
|
||||
try { m_sceneMgr->destroyManualObject(m_axisY); } catch (...) {}
|
||||
m_axisY = nullptr;
|
||||
}
|
||||
if (m_axisZ) {
|
||||
m_sceneMgr->destroyManualObject(m_axisZ);
|
||||
try { m_sceneMgr->destroyManualObject(m_axisZ); } catch (...) {}
|
||||
m_axisZ = nullptr;
|
||||
}
|
||||
|
||||
// Finally destroy the node
|
||||
if (m_gizmoNode) {
|
||||
m_sceneMgr->destroySceneNode(m_gizmoNode);
|
||||
try { m_sceneMgr->destroySceneNode(m_gizmoNode); } catch (...) {}
|
||||
m_gizmoNode = nullptr;
|
||||
}
|
||||
|
||||
// Mark scene manager as invalid
|
||||
m_sceneMgr = nullptr;
|
||||
}
|
||||
|
||||
Gizmo::~Gizmo()
|
||||
{
|
||||
// If shutdown wasn't called, do cleanup now
|
||||
if (m_sceneMgr) {
|
||||
shutdown();
|
||||
}
|
||||
}
|
||||
|
||||
void Gizmo::attachTo(flecs::entity entity)
|
||||
|
||||
@@ -27,6 +27,11 @@ public:
|
||||
|
||||
Gizmo(Ogre::SceneManager *sceneMgr);
|
||||
~Gizmo();
|
||||
|
||||
/**
|
||||
* Shutdown and cleanup - must be called before SceneManager is destroyed
|
||||
*/
|
||||
void shutdown();
|
||||
|
||||
/**
|
||||
* Attach to an entity's transform
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
#include "../components/Lod.hpp"
|
||||
#include "../components/StaticGeometry.hpp"
|
||||
#include "../components/StaticGeometryMember.hpp"
|
||||
#include "../components/ProceduralTexture.hpp"
|
||||
#include "../components/LodSettings.hpp"
|
||||
#include "../ui/TransformEditor.hpp"
|
||||
#include "../ui/RenderableEditor.hpp"
|
||||
@@ -36,6 +37,14 @@ EditorUISystem::EditorUISystem(flecs::world &world,
|
||||
|
||||
EditorUISystem::~EditorUISystem() = default;
|
||||
|
||||
void EditorUISystem::shutdown()
|
||||
{
|
||||
// Shutdown gizmo before SceneManager is destroyed
|
||||
if (m_gizmo) {
|
||||
m_gizmo->shutdown();
|
||||
}
|
||||
}
|
||||
|
||||
bool EditorUISystem::onMousePressed(const Ogre::Ray &mouseRay)
|
||||
{
|
||||
if (m_gizmo) {
|
||||
@@ -554,6 +563,13 @@ void EditorUISystem::renderComponentList(flecs::entity entity)
|
||||
m_componentRegistry.render<StaticGeometryMemberComponent>(entity, member);
|
||||
componentCount++;
|
||||
}
|
||||
|
||||
// Render ProceduralTexture if present
|
||||
if (entity.has<ProceduralTextureComponent>()) {
|
||||
auto &texture = entity.get_mut<ProceduralTextureComponent>();
|
||||
m_componentRegistry.render<ProceduralTextureComponent>(entity, texture);
|
||||
componentCount++;
|
||||
}
|
||||
|
||||
// Show message if no components
|
||||
if (componentCount == 0) {
|
||||
|
||||
@@ -62,6 +62,11 @@ public:
|
||||
* Get the gizmo for external interaction
|
||||
*/
|
||||
Gizmo *getGizmo() const { return m_gizmo.get(); }
|
||||
|
||||
/**
|
||||
* Shutdown UI system - must be called before SceneManager destruction
|
||||
*/
|
||||
void shutdown();
|
||||
|
||||
/**
|
||||
* Get/set SceneNode parenting mode
|
||||
|
||||
136
src/features/editScene/systems/ProceduralTextureSystem.cpp
Normal file
136
src/features/editScene/systems/ProceduralTextureSystem.cpp
Normal file
@@ -0,0 +1,136 @@
|
||||
#include "ProceduralTextureSystem.hpp"
|
||||
#include "../components/ProceduralTexture.hpp"
|
||||
#include <OgreTextureManager.h>
|
||||
#include <OgreImage.h>
|
||||
#include <OgreLogManager.h>
|
||||
#include <ProceduralTextureGenerator.h>
|
||||
#include <ProceduralTextureBuffer.h>
|
||||
|
||||
ProceduralTextureSystem::ProceduralTextureSystem(flecs::world& world, Ogre::SceneManager* sceneMgr)
|
||||
: m_world(world)
|
||||
, m_sceneMgr(sceneMgr)
|
||||
, m_query(world.query<ProceduralTextureComponent>())
|
||||
{
|
||||
}
|
||||
|
||||
ProceduralTextureSystem::~ProceduralTextureSystem() = default;
|
||||
|
||||
void ProceduralTextureSystem::initialize()
|
||||
{
|
||||
if (m_initialized) return;
|
||||
m_initialized = true;
|
||||
|
||||
Ogre::LogManager::getSingleton().logMessage("ProceduralTextureSystem initialized");
|
||||
}
|
||||
|
||||
void ProceduralTextureSystem::update()
|
||||
{
|
||||
if (!m_initialized) return;
|
||||
|
||||
m_query.each([&](flecs::entity entity, ProceduralTextureComponent& component) {
|
||||
if (component.dirty || !component.generated) {
|
||||
generateTexture(entity, component);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void ProceduralTextureSystem::generateTexture(flecs::entity entity, ProceduralTextureComponent& component)
|
||||
{
|
||||
try {
|
||||
// Generate unique texture name if not set
|
||||
if (component.textureName.empty()) {
|
||||
component.textureName = "ProceduralTex_" + std::to_string(entity.id()) + "_" + std::to_string(m_generatedCount++);
|
||||
}
|
||||
|
||||
const int gridSize = ProceduralTextureComponent::GRID_SIZE;
|
||||
const int rectCount = ProceduralTextureComponent::RECT_COUNT;
|
||||
const int texSize = component.textureSize;
|
||||
const int rectSize = texSize / gridSize;
|
||||
|
||||
// Create a buffer for the final texture
|
||||
Ogre::Image finalImage;
|
||||
finalImage.loadDynamicImage(
|
||||
new Ogre::uchar[texSize * texSize * 4], // Allocate buffer
|
||||
texSize, texSize, 1,
|
||||
Ogre::PF_R8G8B8A8, true // Auto-delete buffer
|
||||
);
|
||||
|
||||
// Clear to transparent
|
||||
std::memset(finalImage.getData(), 0, texSize * texSize * 4);
|
||||
|
||||
// Generate each rectangle using OgreProcedural::Solid and composite into final image
|
||||
for (int i = 0; i < rectCount; ++i) {
|
||||
int row = i / gridSize;
|
||||
int col = i % gridSize;
|
||||
|
||||
// Create a small texture buffer for this rectangle
|
||||
Procedural::TextureBufferPtr rectBuffer(new Procedural::TextureBuffer(rectSize));
|
||||
|
||||
// Get color for this rectangle
|
||||
Ogre::ColourValue color = component.getColor(i);
|
||||
|
||||
// Generate solid color using OgreProcedural
|
||||
Procedural::Solid(rectBuffer).setColour(color).process();
|
||||
|
||||
// Get the image data from the buffer
|
||||
Ogre::Image& rectImage = rectBuffer->getImage();
|
||||
|
||||
// Copy rectangle into final image
|
||||
int destX = col * rectSize;
|
||||
int destY = row * rectSize;
|
||||
|
||||
for (int y = 0; y < rectSize; ++y) {
|
||||
for (int x = 0; x < rectSize; ++x) {
|
||||
Ogre::ColourValue pixel = rectImage.getColourAt(x, y, 0);
|
||||
finalImage.setColourAt(pixel, destX + x, destY + y, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Destroy old texture if exists
|
||||
if (component.ogreTexture) {
|
||||
Ogre::TextureManager::getSingleton().remove(component.ogreTexture);
|
||||
component.ogreTexture.reset();
|
||||
}
|
||||
|
||||
// Create Ogre texture from image
|
||||
component.ogreTexture = Ogre::TextureManager::getSingleton().loadImage(
|
||||
component.textureName,
|
||||
Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME,
|
||||
finalImage,
|
||||
Ogre::TEX_TYPE_2D,
|
||||
0, // numMipmaps
|
||||
1.0f // gamma
|
||||
);
|
||||
|
||||
component.generated = true;
|
||||
component.dirty = false;
|
||||
|
||||
Ogre::LogManager::getSingleton().logMessage(
|
||||
"ProceduralTexture: Generated '" + component.textureName +
|
||||
"' (" + std::to_string(texSize) + "x" + std::to_string(texSize) + ")");
|
||||
|
||||
} catch (const Ogre::Exception& e) {
|
||||
Ogre::LogManager::getSingleton().logMessage(
|
||||
"ProceduralTexture ERROR: " + e.getDescription());
|
||||
component.dirty = true; // Will retry
|
||||
}
|
||||
}
|
||||
|
||||
void ProceduralTextureSystem::regenerateTexture(flecs::entity entity)
|
||||
{
|
||||
if (!entity.is_alive() || !entity.has<ProceduralTextureComponent>()) {
|
||||
return;
|
||||
}
|
||||
|
||||
entity.get_mut<ProceduralTextureComponent>().markDirty();
|
||||
}
|
||||
|
||||
std::string ProceduralTextureSystem::getTextureName(flecs::entity entity)
|
||||
{
|
||||
if (!entity.is_alive() || !entity.has<ProceduralTextureComponent>()) {
|
||||
return "";
|
||||
}
|
||||
|
||||
return entity.get<ProceduralTextureComponent>().textureName;
|
||||
}
|
||||
38
src/features/editScene/systems/ProceduralTextureSystem.hpp
Normal file
38
src/features/editScene/systems/ProceduralTextureSystem.hpp
Normal file
@@ -0,0 +1,38 @@
|
||||
#pragma once
|
||||
|
||||
#include <flecs.h>
|
||||
#include <memory>
|
||||
|
||||
namespace Ogre {
|
||||
class SceneManager;
|
||||
}
|
||||
|
||||
class ProceduralTextureSystem {
|
||||
public:
|
||||
ProceduralTextureSystem(flecs::world& world, Ogre::SceneManager* sceneMgr);
|
||||
~ProceduralTextureSystem();
|
||||
|
||||
// Initialize the system
|
||||
void initialize();
|
||||
|
||||
// Main update - generates dirty textures
|
||||
void update();
|
||||
|
||||
// Force regenerate a specific texture
|
||||
void regenerateTexture(flecs::entity entity);
|
||||
|
||||
// Get the generated texture name for an entity
|
||||
std::string getTextureName(flecs::entity entity);
|
||||
|
||||
private:
|
||||
flecs::world& m_world;
|
||||
Ogre::SceneManager* m_sceneMgr;
|
||||
|
||||
flecs::query<struct ProceduralTextureComponent> m_query;
|
||||
|
||||
bool m_initialized = false;
|
||||
int m_generatedCount = 0;
|
||||
|
||||
// Generate the texture for a component
|
||||
void generateTexture(flecs::entity entity, struct ProceduralTextureComponent& component);
|
||||
};
|
||||
@@ -11,6 +11,7 @@
|
||||
#include "../components/LodSettings.hpp"
|
||||
#include "../components/StaticGeometry.hpp"
|
||||
#include "../components/StaticGeometryMember.hpp"
|
||||
#include "../components/ProceduralTexture.hpp"
|
||||
#include "EditorUISystem.hpp"
|
||||
#include <random>
|
||||
#include <fstream>
|
||||
@@ -156,6 +157,10 @@ nlohmann::json SceneSerializer::serializeEntity(flecs::entity entity)
|
||||
json["staticGeometryMember"] = serializeStaticGeometryMember(entity);
|
||||
}
|
||||
|
||||
if (entity.has<ProceduralTextureComponent>()) {
|
||||
json["proceduralTexture"] = serializeProceduralTexture(entity);
|
||||
}
|
||||
|
||||
// Serialize children
|
||||
json["children"] = nlohmann::json::array();
|
||||
entity.children([&](flecs::entity child) {
|
||||
@@ -233,6 +238,10 @@ void SceneSerializer::deserializeEntity(const nlohmann::json& json, flecs::entit
|
||||
deserializeStaticGeometryMember(entity, json["staticGeometryMember"]);
|
||||
}
|
||||
|
||||
if (json.contains("proceduralTexture")) {
|
||||
deserializeProceduralTexture(entity, json["proceduralTexture"]);
|
||||
}
|
||||
|
||||
// Add to UI system if provided
|
||||
if (uiSystem) {
|
||||
uiSystem->addEntity(entity);
|
||||
@@ -946,3 +955,81 @@ void SceneSerializer::deserializeStaticGeometryMember(flecs::entity entity, cons
|
||||
|
||||
entity.set<StaticGeometryMemberComponent>(member);
|
||||
}
|
||||
|
||||
nlohmann::json SceneSerializer::serializeProceduralTexture(flecs::entity entity)
|
||||
{
|
||||
auto& texture = entity.get<ProceduralTextureComponent>();
|
||||
nlohmann::json json;
|
||||
|
||||
json["textureName"] = texture.textureName;
|
||||
json["textureSize"] = texture.textureSize;
|
||||
|
||||
// Serialize colors array
|
||||
nlohmann::json colorsJson = nlohmann::json::array();
|
||||
for (int i = 0; i < ProceduralTextureComponent::RECT_COUNT; ++i) {
|
||||
nlohmann::json color;
|
||||
color["r"] = texture.colors[i * 4 + 0];
|
||||
color["g"] = texture.colors[i * 4 + 1];
|
||||
color["b"] = texture.colors[i * 4 + 2];
|
||||
color["a"] = texture.colors[i * 4 + 3];
|
||||
colorsJson.push_back(color);
|
||||
}
|
||||
json["colors"] = colorsJson;
|
||||
|
||||
// Serialize named rectangles
|
||||
nlohmann::json namedRectsJson = nlohmann::json::array();
|
||||
for (const auto& pair : texture.namedRects) {
|
||||
const auto& rect = pair.second;
|
||||
nlohmann::json rectJson;
|
||||
rectJson["name"] = rect.name;
|
||||
rectJson["u1"] = rect.u1;
|
||||
rectJson["v1"] = rect.v1;
|
||||
rectJson["u2"] = rect.u2;
|
||||
rectJson["v2"] = rect.v2;
|
||||
namedRectsJson.push_back(rectJson);
|
||||
}
|
||||
json["namedRects"] = namedRectsJson;
|
||||
|
||||
return json;
|
||||
}
|
||||
|
||||
void SceneSerializer::deserializeProceduralTexture(flecs::entity entity, const nlohmann::json& json)
|
||||
{
|
||||
ProceduralTextureComponent texture;
|
||||
|
||||
texture.textureName = json.value("textureName", "");
|
||||
texture.textureSize = json.value("textureSize", 512);
|
||||
|
||||
// Deserialize colors array
|
||||
if (json.contains("colors") && json["colors"].is_array()) {
|
||||
int i = 0;
|
||||
for (const auto& colorJson : json["colors"]) {
|
||||
if (i >= ProceduralTextureComponent::RECT_COUNT) break;
|
||||
texture.colors[i * 4 + 0] = colorJson.value("r", 1.0f);
|
||||
texture.colors[i * 4 + 1] = colorJson.value("g", 1.0f);
|
||||
texture.colors[i * 4 + 2] = colorJson.value("b", 1.0f);
|
||||
texture.colors[i * 4 + 3] = colorJson.value("a", 1.0f);
|
||||
++i;
|
||||
}
|
||||
}
|
||||
|
||||
// Deserialize named rectangles
|
||||
if (json.contains("namedRects") && json["namedRects"].is_array()) {
|
||||
for (const auto& rectJson : json["namedRects"]) {
|
||||
std::string name = rectJson.value("name", "");
|
||||
if (!name.empty()) {
|
||||
TextureRectInfo rect;
|
||||
rect.name = name;
|
||||
rect.u1 = rectJson.value("u1", 0.0f);
|
||||
rect.v1 = rectJson.value("v1", 0.0f);
|
||||
rect.u2 = rectJson.value("u2", 1.0f);
|
||||
rect.v2 = rectJson.value("v2", 1.0f);
|
||||
texture.namedRects[name] = rect;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
texture.dirty = true;
|
||||
|
||||
entity.set<ProceduralTextureComponent>(texture);
|
||||
}
|
||||
|
||||
@@ -50,6 +50,7 @@ private:
|
||||
nlohmann::json serializeLod(flecs::entity entity);
|
||||
nlohmann::json serializeStaticGeometry(flecs::entity entity);
|
||||
nlohmann::json serializeStaticGeometryMember(flecs::entity entity);
|
||||
nlohmann::json serializeProceduralTexture(flecs::entity entity);
|
||||
|
||||
// Component deserialization
|
||||
void deserializeTransform(flecs::entity entity, const nlohmann::json& json, flecs::entity parentEntity);
|
||||
@@ -63,6 +64,7 @@ private:
|
||||
void deserializeLod(flecs::entity entity, const nlohmann::json& json);
|
||||
void deserializeStaticGeometry(flecs::entity entity, const nlohmann::json& json);
|
||||
void deserializeStaticGeometryMember(flecs::entity entity, const nlohmann::json& json);
|
||||
void deserializeProceduralTexture(flecs::entity entity, const nlohmann::json& json);
|
||||
|
||||
flecs::world& m_world;
|
||||
Ogre::SceneManager* m_sceneMgr;
|
||||
|
||||
305
src/features/editScene/ui/ProceduralTextureEditor.cpp
Normal file
305
src/features/editScene/ui/ProceduralTextureEditor.cpp
Normal file
@@ -0,0 +1,305 @@
|
||||
#include "ProceduralTextureEditor.hpp"
|
||||
#include "../components/ProceduralTexture.hpp"
|
||||
#include <imgui.h>
|
||||
#include <OgreColourValue.h>
|
||||
#include <OgreTextureManager.h>
|
||||
#include <OgreImage.h>
|
||||
#include <OgreLogManager.h>
|
||||
|
||||
void ProceduralTextureEditor::renderColorButton(int index, ProceduralTextureComponent &texture)
|
||||
{
|
||||
Ogre::ColourValue color = texture.getColor(index);
|
||||
|
||||
// Create a unique ID for this button
|
||||
ImGui::PushID(index);
|
||||
|
||||
// Show color as a small colored button
|
||||
ImVec4 buttonColor(color.r, color.g, color.b, color.a);
|
||||
ImGui::ColorButton("##color", buttonColor, ImGuiColorEditFlags_NoTooltip, ImVec2(24, 24));
|
||||
|
||||
// Handle click
|
||||
if (ImGui::IsItemClicked()) {
|
||||
m_selectedRect = index;
|
||||
}
|
||||
|
||||
// Show tooltip with rectangle index
|
||||
if (ImGui::IsItemHovered()) {
|
||||
ImGui::SetTooltip("Rect %d: (%.2f, %.2f, %.2f)", index, color.r, color.g, color.b);
|
||||
}
|
||||
|
||||
ImGui::PopID();
|
||||
}
|
||||
|
||||
void ProceduralTextureEditor::renderColorGrid(flecs::entity entity, ProceduralTextureComponent &texture)
|
||||
{
|
||||
const int gridSize = ProceduralTextureComponent::GRID_SIZE;
|
||||
|
||||
ImGui::Text("10x10 Grid - Click a rectangle to edit its color or add to atlas:");
|
||||
|
||||
// Calculate grid width for centering
|
||||
float buttonSize = 24.0f;
|
||||
float spacing = 2.0f;
|
||||
float gridWidth = gridSize * buttonSize + (gridSize - 1) * spacing;
|
||||
|
||||
// Get window width for centering
|
||||
float windowWidth = ImGui::GetContentRegionAvail().x;
|
||||
float startX = ImGui::GetCursorPosX();
|
||||
float offset = (windowWidth - gridWidth) * 0.5f;
|
||||
if (offset < 0) offset = 0;
|
||||
|
||||
// Render the grid - each row needs its own offset
|
||||
for (int row = 0; row < gridSize; ++row) {
|
||||
// Set cursor position for this row
|
||||
ImGui::SetCursorPosX(startX + offset);
|
||||
|
||||
for (int col = 0; col < gridSize; ++col) {
|
||||
int index = row * gridSize + col;
|
||||
renderColorButton(index, texture);
|
||||
|
||||
if (col < gridSize - 1) {
|
||||
ImGui::SameLine(0, spacing);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Color picker and naming for selected rectangle
|
||||
if (m_selectedRect >= 0 && m_selectedRect < ProceduralTextureComponent::RECT_COUNT) {
|
||||
ImGui::Separator();
|
||||
ImGui::Text("Editing Rectangle %d (Row %d, Col %d)",
|
||||
m_selectedRect,
|
||||
m_selectedRect / gridSize,
|
||||
m_selectedRect % gridSize);
|
||||
|
||||
// UV coordinates display
|
||||
float u1, v1, u2, v2;
|
||||
texture.getRectUVs(m_selectedRect, u1, v1, u2, v2);
|
||||
ImGui::TextDisabled("UVs: (%.3f, %.3f) - (%.3f, %.3f)", u1, v1, u2, v2);
|
||||
|
||||
Ogre::ColourValue currentColor = texture.getColor(m_selectedRect);
|
||||
float colorArray[4] = { currentColor.r, currentColor.g, currentColor.b, currentColor.a };
|
||||
|
||||
if (ImGui::ColorEdit4("Color", colorArray)) {
|
||||
Ogre::ColourValue newColor(colorArray[0], colorArray[1], colorArray[2], colorArray[3]);
|
||||
texture.setColor(m_selectedRect, newColor);
|
||||
}
|
||||
|
||||
// Add to atlas section
|
||||
ImGui::Separator();
|
||||
ImGui::Text("Add to Texture Atlas:");
|
||||
|
||||
ImGui::InputText("Name", m_nameBuffer, sizeof(m_nameBuffer));
|
||||
ImGui::SameLine();
|
||||
|
||||
if (ImGui::Button("Add")) {
|
||||
if (strlen(m_nameBuffer) > 0) {
|
||||
if (texture.hasNamedRect(m_nameBuffer)) {
|
||||
ImGui::TextColored(ImVec4(1, 0, 0, 1), "Name already exists!");
|
||||
} else {
|
||||
texture.addNamedRect(m_nameBuffer, m_selectedRect);
|
||||
m_nameBuffer[0] = '\0'; // Clear buffer
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (ImGui::Button("Deselect")) {
|
||||
m_selectedRect = -1;
|
||||
m_nameBuffer[0] = '\0';
|
||||
}
|
||||
|
||||
ImGui::SameLine();
|
||||
|
||||
// Preset colors
|
||||
ImGui::Text("Presets:");
|
||||
ImGui::SameLine();
|
||||
|
||||
const ImVec4 presets[] = {
|
||||
ImVec4(1, 1, 1, 1), // White
|
||||
ImVec4(0, 0, 0, 1), // Black
|
||||
ImVec4(1, 0, 0, 1), // Red
|
||||
ImVec4(0, 1, 0, 1), // Green
|
||||
ImVec4(0, 0, 1, 1), // Blue
|
||||
ImVec4(1, 1, 0, 1), // Yellow
|
||||
ImVec4(1, 0, 1, 1), // Magenta
|
||||
ImVec4(0, 1, 1, 1), // Cyan
|
||||
ImVec4(0.5f, 0.5f, 0.5f, 1), // Gray
|
||||
};
|
||||
|
||||
for (size_t i = 0; i < IM_ARRAYSIZE(presets); ++i) {
|
||||
ImGui::PushID((int)i);
|
||||
if (ImGui::ColorButton("##preset", presets[i], ImGuiColorEditFlags_NoTooltip, ImVec2(20, 20))) {
|
||||
Ogre::ColourValue presetColor(presets[i].x, presets[i].y, presets[i].z, presets[i].w);
|
||||
texture.setColor(m_selectedRect, presetColor);
|
||||
}
|
||||
ImGui::PopID();
|
||||
if (i < IM_ARRAYSIZE(presets) - 1) {
|
||||
ImGui::SameLine(0, 2);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ProceduralTextureEditor::renderNamedRects(flecs::entity entity, ProceduralTextureComponent &texture)
|
||||
{
|
||||
ImGui::Separator();
|
||||
ImGui::Text("Texture Atlas (Named Rectangles):");
|
||||
|
||||
const auto& namedRects = texture.getAllNamedRects();
|
||||
|
||||
if (namedRects.empty()) {
|
||||
ImGui::TextDisabled("No named rectangles. Select a rectangle above and add it to the atlas.");
|
||||
} else {
|
||||
// Table header
|
||||
if (ImGui::BeginTable("NamedRects", 3, ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg)) {
|
||||
ImGui::TableSetupColumn("Name");
|
||||
ImGui::TableSetupColumn("UV Coordinates");
|
||||
ImGui::TableSetupColumn("Action");
|
||||
ImGui::TableHeadersRow();
|
||||
|
||||
for (const auto& pair : namedRects) {
|
||||
const auto& rect = pair.second;
|
||||
ImGui::TableNextRow();
|
||||
|
||||
// Name column
|
||||
ImGui::TableNextColumn();
|
||||
ImGui::Text("%s", rect.name.c_str());
|
||||
|
||||
// UVs column
|
||||
ImGui::TableNextColumn();
|
||||
ImGui::Text("(%.3f, %.3f) - (%.3f, %.3f)", rect.u1, rect.v1, rect.u2, rect.v2);
|
||||
|
||||
// Action column
|
||||
ImGui::TableNextColumn();
|
||||
ImGui::PushID(rect.name.c_str());
|
||||
if (ImGui::Button("Delete")) {
|
||||
texture.removeNamedRect(rect.name);
|
||||
}
|
||||
ImGui::PopID();
|
||||
}
|
||||
|
||||
ImGui::EndTable();
|
||||
}
|
||||
|
||||
ImGui::TextDisabled("Total: %zu named rectangles", namedRects.size());
|
||||
}
|
||||
}
|
||||
|
||||
bool ProceduralTextureEditor::renderComponent(flecs::entity entity, ProceduralTextureComponent &texture)
|
||||
{
|
||||
bool modified = false;
|
||||
|
||||
if (ImGui::CollapsingHeader("Procedural Texture", ImGuiTreeNodeFlags_DefaultOpen)) {
|
||||
ImGui::Indent();
|
||||
|
||||
// Texture info
|
||||
if (texture.generated) {
|
||||
ImGui::TextColored(ImVec4(0, 1, 0, 1), "Status: Generated");
|
||||
ImGui::Text("Texture: %s", texture.textureName.c_str());
|
||||
ImGui::Text("Size: %dx%d", texture.textureSize, texture.textureSize);
|
||||
|
||||
// Save PNG button
|
||||
if (ImGui::Button("Save as PNG...")) {
|
||||
ImGui::OpenPopup("SaveTexturePopup");
|
||||
}
|
||||
|
||||
// Save popup
|
||||
if (ImGui::BeginPopup("SaveTexturePopup")) {
|
||||
static char filename[256] = "procedural_texture.png";
|
||||
ImGui::InputText("Filename", filename, sizeof(filename));
|
||||
|
||||
if (ImGui::Button("Save", ImVec2(120, 0))) {
|
||||
try {
|
||||
if (texture.ogreTexture) {
|
||||
Ogre::Image image;
|
||||
texture.ogreTexture->convertToImage(image);
|
||||
image.save(filename);
|
||||
Ogre::LogManager::getSingleton().logMessage(
|
||||
"ProceduralTexture: Saved to " + std::string(filename));
|
||||
}
|
||||
} catch (const Ogre::Exception& e) {
|
||||
Ogre::LogManager::getSingleton().logMessage(
|
||||
"ProceduralTexture ERROR: Failed to save: " + e.getDescription());
|
||||
}
|
||||
ImGui::CloseCurrentPopup();
|
||||
}
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button("Cancel", ImVec2(120, 0))) {
|
||||
ImGui::CloseCurrentPopup();
|
||||
}
|
||||
ImGui::EndPopup();
|
||||
}
|
||||
} else if (texture.dirty) {
|
||||
ImGui::TextColored(ImVec4(1, 1, 0, 1), "Status: Needs Generation");
|
||||
} else {
|
||||
ImGui::TextColored(ImVec4(0.5f, 0.5f, 0.5f, 1), "Status: Not Generated");
|
||||
}
|
||||
|
||||
ImGui::Separator();
|
||||
|
||||
// Texture size selector
|
||||
const int sizes[] = { 128, 256, 512, 1024, 2048 };
|
||||
int currentSizeIndex = 2; // Default 512
|
||||
for (int i = 0; i < IM_ARRAYSIZE(sizes); ++i) {
|
||||
if (sizes[i] == texture.textureSize) {
|
||||
currentSizeIndex = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
const char* sizeNames[] = { "128x128", "256x256", "512x512", "1024x1024", "2048x2048" };
|
||||
if (ImGui::Combo("Texture Size", ¤tSizeIndex, sizeNames, IM_ARRAYSIZE(sizeNames))) {
|
||||
texture.textureSize = sizes[currentSizeIndex];
|
||||
texture.markDirty();
|
||||
modified = true;
|
||||
}
|
||||
|
||||
ImGui::Separator();
|
||||
|
||||
// Color grid
|
||||
renderColorGrid(entity, texture);
|
||||
|
||||
// Named rectangles list
|
||||
renderNamedRects(entity, texture);
|
||||
|
||||
ImGui::Separator();
|
||||
|
||||
// Global actions
|
||||
if (ImGui::Button("Randomize Colors")) {
|
||||
for (int i = 0; i < ProceduralTextureComponent::RECT_COUNT; ++i) {
|
||||
Ogre::ColourValue randomColor(
|
||||
(float)rand() / RAND_MAX,
|
||||
(float)rand() / RAND_MAX,
|
||||
(float)rand() / RAND_MAX,
|
||||
1.0f
|
||||
);
|
||||
texture.setColor(i, randomColor);
|
||||
}
|
||||
modified = true;
|
||||
}
|
||||
|
||||
ImGui::SameLine();
|
||||
|
||||
if (ImGui::Button("Reset to Checkerboard")) {
|
||||
for (int i = 0; i < ProceduralTextureComponent::RECT_COUNT; ++i) {
|
||||
int row = i / ProceduralTextureComponent::GRID_SIZE;
|
||||
int col = i % ProceduralTextureComponent::GRID_SIZE;
|
||||
bool isWhite = (row + col) % 2 == 0;
|
||||
Ogre::ColourValue color(isWhite ? 1.0f : 0.0f, isWhite ? 1.0f : 0.0f, isWhite ? 1.0f : 0.0f, 1.0f);
|
||||
texture.setColor(i, color);
|
||||
}
|
||||
modified = true;
|
||||
}
|
||||
|
||||
ImGui::SameLine();
|
||||
|
||||
if (ImGui::Button("All White")) {
|
||||
for (int i = 0; i < ProceduralTextureComponent::RECT_COUNT; ++i) {
|
||||
texture.setColor(i, Ogre::ColourValue::White);
|
||||
}
|
||||
modified = true;
|
||||
}
|
||||
|
||||
ImGui::Unindent();
|
||||
}
|
||||
|
||||
return modified;
|
||||
}
|
||||
31
src/features/editScene/ui/ProceduralTextureEditor.hpp
Normal file
31
src/features/editScene/ui/ProceduralTextureEditor.hpp
Normal file
@@ -0,0 +1,31 @@
|
||||
#ifndef EDITSCENE_PROCEDURALTEXTUREEDITOR_HPP
|
||||
#define EDITSCENE_PROCEDURALTEXTUREEDITOR_HPP
|
||||
#pragma once
|
||||
|
||||
#include "ComponentEditor.hpp"
|
||||
#include "../components/ProceduralTexture.hpp"
|
||||
#include <flecs.h>
|
||||
|
||||
/**
|
||||
* Editor for ProceduralTextureComponent
|
||||
* Shows a 10x10 grid of color pickers for each rectangle
|
||||
* Plus named rectangle management for texture atlas
|
||||
*/
|
||||
class ProceduralTextureEditor : public ComponentEditor<ProceduralTextureComponent> {
|
||||
public:
|
||||
bool renderComponent(flecs::entity entity, ProceduralTextureComponent &texture) override;
|
||||
const char *getName() const override { return "Procedural Texture"; }
|
||||
|
||||
private:
|
||||
void renderColorGrid(flecs::entity entity, ProceduralTextureComponent &texture);
|
||||
void renderColorButton(int index, ProceduralTextureComponent &texture);
|
||||
void renderNamedRects(flecs::entity entity, ProceduralTextureComponent &texture);
|
||||
|
||||
// Currently selected color for editing
|
||||
int m_selectedRect = -1;
|
||||
|
||||
// Buffer for new rectangle name
|
||||
char m_nameBuffer[256] = {};
|
||||
};
|
||||
|
||||
#endif // EDITSCENE_PROCEDURALTEXTUREEDITOR_HPP
|
||||
Reference in New Issue
Block a user