Color atlas serialization fixes

This commit is contained in:
2026-04-21 11:42:46 +03:00
parent c6fb3bb463
commit 7e4e8f6638
6 changed files with 1094 additions and 824 deletions

View File

@@ -80,7 +80,8 @@ void ImGuiRenderListener::preViewportUpdate(
}
// Render startup menu in game mode (inside ImGui frame scope)
if (m_editorApp && m_editorApp->getGameMode() == EditorApp::GameMode::Game &&
if (m_editorApp &&
m_editorApp->getGameMode() == EditorApp::GameMode::Game &&
m_editorApp->getGamePlayState() == EditorApp::GamePlayState::Menu) {
StartupMenuSystem *sms = m_editorApp->getStartupMenuSystem();
if (sms)
@@ -209,7 +210,8 @@ void EditorApp::setup()
m_uiSystem = std::make_unique<EditorUISystem>(
m_world, m_sceneMgr, getRenderWindow());
if (m_uiSystem)
m_uiSystem->setEditorUIEnabled(m_gameMode == GameMode::Editor);
m_uiSystem->setEditorUIEnabled(m_gameMode ==
GameMode::Editor);
// Setup physics system
m_physicsSystem = std::make_unique<EditorPhysicsSystem>(
@@ -238,13 +240,13 @@ void EditorApp::setup()
// Setup ProceduralTexture system
m_proceduralTextureSystem =
std::make_unique<ProceduralTextureSystem>(m_world,
m_sceneMgr);
m_sceneMgr);
m_proceduralTextureSystem->initialize();
// Setup ProceduralMaterial system
m_proceduralMaterialSystem =
std::make_unique<ProceduralMaterialSystem>(m_world,
m_sceneMgr);
m_sceneMgr);
m_proceduralMaterialSystem->initialize();
// Setup ProceduralMesh system
@@ -263,8 +265,8 @@ void EditorApp::setup()
m_animationTreeSystem->initialize();
// Setup Character physics system
m_characterSystem = std::make_unique<CharacterSystem>(
m_world, m_sceneMgr);
m_characterSystem =
std::make_unique<CharacterSystem>(m_world, m_sceneMgr);
m_characterSystem->initialize();
// Setup CellGrid system
@@ -278,9 +280,8 @@ void EditorApp::setup()
m_roomLayoutSystem->initialize();
// Setup game systems
m_startupMenuSystem =
std::make_unique<StartupMenuSystem>(m_world, m_sceneMgr,
this);
m_startupMenuSystem = std::make_unique<StartupMenuSystem>(
m_world, m_sceneMgr, this);
m_playerControllerSystem =
std::make_unique<PlayerControllerSystem>(
m_world, m_sceneMgr, this);
@@ -317,7 +318,8 @@ void EditorApp::setup()
// Create and register ImGui render listener
m_imguiListener = std::make_unique<ImGuiRenderListener>(
m_imguiOverlay, m_uiSystem.get(), getRenderWindow(), this);
m_imguiOverlay, m_uiSystem.get(), getRenderWindow(),
this);
getRenderWindow()->addListener(m_imguiListener.get());
// Register input listeners
@@ -509,10 +511,13 @@ void EditorApp::createGrid()
grid->end();
Ogre::SceneNode *gridNode =
m_gridNode =
m_sceneMgr->getRootSceneNode()->createChildSceneNode(
"GridNode");
gridNode->attachObject(grid);
m_gridNode->attachObject(grid);
// Set initial visibility based on game mode
m_gridNode->setVisible(m_gameMode == GameMode::Editor);
} catch (const std::exception &e) {
Ogre::LogManager::getSingleton().logMessage(
"Grid creation failed: " + Ogre::String(e.what()));
@@ -553,12 +558,15 @@ void EditorApp::createAxes()
axisZ->end();
// Create axis node
Ogre::SceneNode *axisNode =
m_axisNode =
m_sceneMgr->getRootSceneNode()->createChildSceneNode(
"AxisNode");
axisNode->attachObject(axisX);
axisNode->attachObject(axisY);
axisNode->attachObject(axisZ);
m_axisNode->attachObject(axisX);
m_axisNode->attachObject(axisY);
m_axisNode->attachObject(axisZ);
// Set initial visibility based on game mode
m_axisNode->setVisible(m_gameMode == GameMode::Editor);
} catch (const std::exception &e) {
Ogre::LogManager::getSingleton().logMessage(
"Axis creation failed: " + Ogre::String(e.what()));

View File

@@ -123,14 +123,23 @@ public:
// Game mode management
void setGameMode(GameMode mode);
GameMode getGameMode() const { return m_gameMode; }
GamePlayState getGamePlayState() const { return m_gamePlayState; }
GameMode getGameMode() const
{
return m_gameMode;
}
GamePlayState getGamePlayState() const
{
return m_gamePlayState;
}
void setGamePlayState(GamePlayState state);
void startNewGame(const Ogre::String &scenePath);
void clearScene();
// Input access
GameInputState &getGameInputState() { return m_gameInput; }
GameInputState &getGameInputState()
{
return m_gameInput;
}
// Getters
flecs::entity getSelectedEntity() const;
@@ -142,7 +151,10 @@ public:
{
return &m_world;
}
EditorCamera *getEditorCamera() const { return m_camera.get(); }
EditorCamera *getEditorCamera() const
{
return m_camera.get();
}
AnimationTreeSystem *getAnimationTreeSystem() const
{
return m_animationTreeSystem.get();
@@ -155,7 +167,10 @@ public:
{
return m_startupMenuSystem.get();
}
Ogre::ImGuiOverlay *getImGuiOverlay() const { return m_imguiOverlay; }
Ogre::ImGuiOverlay *getImGuiOverlay() const
{
return m_imguiOverlay;
}
private:
// Ogre objects
@@ -195,6 +210,10 @@ private:
GamePlayState m_gamePlayState = GamePlayState::Menu;
GameInputState m_gameInput;
bool m_setupComplete = false;
// Editor visualization nodes
Ogre::SceneNode *m_gridNode = nullptr;
Ogre::SceneNode *m_axisNode = nullptr;
};
#endif // EDITSCENE_EDITORAPP_HPP

View File

@@ -3,6 +3,7 @@
#include <string>
#include <Ogre.h>
#include <flecs.h>
#include "ProceduralTexture.hpp"
/**
* @brief Component for creating procedural Ogre materials
@@ -11,32 +12,47 @@
* 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;
// Track texture version for automatic rebuild when texture changes
unsigned int textureVersion = 0;
// 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;
}
// Unique identifier for persistent referencing across scene loads
std::string materialId;
// Material name for Ogre resource
std::string materialName;
// Persistent reference to texture by ID
std::string diffuseTextureId;
// Runtime reference to entity with ProceduralTextureComponent (for diffuse)
flecs::entity diffuseTextureEntity = flecs::entity::null();
// 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;
// Track texture version for automatic rebuild when texture changes
unsigned int textureVersion = 0;
// 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;
}
// Helper to check if texture reference is valid
bool hasValidTexture() const
{
return diffuseTextureEntity.is_alive() &&
diffuseTextureEntity.has<ProceduralTextureComponent>();
}
};

View File

@@ -10,13 +10,26 @@
* @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) {}
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)
{
}
};
/**
@@ -27,129 +40,144 @@ struct TextureRectInfo {
* 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;
// UV margin for texture mapping (default 0.01, range 0.01-0.025)
// Used to prevent color bleeding by adding padding around UV coordinates
float uvMargin = 0.01f;
// Whether the texture needs regeneration
bool dirty = true;
// Whether the texture has been generated
bool generated = false;
// Version counter - increments each time texture is regenerated
// Used by dependent systems to detect texture changes
unsigned int version = 0;
// 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);
dirty = true; // Mark texture as dirty since rect atlas changed
return true;
}
// Remove a named rectangle
bool removeNamedRect(const std::string& name) {
auto it = namedRects.find(name);
if (it != namedRects.end()) {
namedRects.erase(it);
dirty = true; // Mark texture as dirty since rect atlas changed
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;
}
// Unique identifier for persistent referencing across scene loads
std::string textureId;
// 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;
// UV margin for texture mapping (default 0.01, range 0.01-0.025)
// Used to prevent color bleeding by adding padding around UV coordinates
float uvMargin = 0.01f;
// Whether the texture needs regeneration
bool dirty = true;
// Whether the texture has been generated
bool generated = false;
// Version counter - increments each time texture is regenerated
// Used by dependent systems to detect texture changes
unsigned int version = 0;
// 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);
dirty = true; // Mark texture as dirty since rect atlas changed
return true;
}
// Remove a named rectangle
bool removeNamedRect(const std::string &name)
{
auto it = namedRects.find(name);
if (it != namedRects.end()) {
namedRects.erase(it);
dirty = true; // Mark texture as dirty since rect atlas changed
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;
}
};

File diff suppressed because it is too large Load Diff

View File

@@ -2,170 +2,207 @@
#include "../components/ProceduralTexture.hpp"
#include <imgui.h>
ProceduralMaterialEditor::ProceduralMaterialEditor(Ogre::SceneManager* sceneMgr)
ProceduralMaterialEditor::ProceduralMaterialEditor(Ogre::SceneManager *sceneMgr)
: m_sceneMgr(sceneMgr)
{
}
void ProceduralMaterialEditor::renderTextureSelector(flecs::entity entity, ProceduralMaterialComponent &material)
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;
}
});
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';
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();
material.diffuseTextureEntity =
flecs::entity::null();
material.diffuseTextureId = "";
} else {
material.diffuseTextureEntity = textureEntities[newIndex];
material.diffuseTextureEntity =
textureEntities[newIndex];
// Also store the persistent texture ID
if (material.diffuseTextureEntity.is_alive() &&
material.diffuseTextureEntity
.has<ProceduralTextureComponent>()) {
const auto &texture =
material.diffuseTextureEntity.get<
ProceduralTextureComponent>();
material.diffuseTextureId =
texture.textureId;
} else {
material.diffuseTextureId = "";
}
}
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!");
ImGui::TextColored(ImVec4(1, 0.5f, 0, 1),
"Create a Procedural Texture first!");
}
}
bool ProceduralMaterialEditor::renderComponent(flecs::entity entity, ProceduralMaterialComponent &material)
bool ProceduralMaterialEditor::renderComponent(
flecs::entity entity, ProceduralMaterialComponent &material)
{
bool modified = false;
if (ImGui::CollapsingHeader("Procedural Material", ImGuiTreeNodeFlags_DefaultOpen)) {
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());
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");
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::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);
strncpy(nameBuf, material.materialName.c_str(),
sizeof(nameBuf) - 1);
nameBuf[sizeof(nameBuf) - 1] = '\0';
if (ImGui::InputText("Material Name", nameBuf, sizeof(nameBuf))) {
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)) {
if (ImGui::SliderFloat("Shininess", &material.shininess, 1.0f,
128.0f)) {
material.markDirty();
modified = true;
}
if (ImGui::SliderFloat("Roughness", &material.roughness, 0.0f, 1.0f)) {
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.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;
}