Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,8 @@ include_directories(
${FUNGT_BASE_DIR}/PBR/TriangleExtractor
${FUNGT_BASE_DIR}/PBR/BVH
${FUNGT_BASE_DIR}/Triangle
${FUNGT_BASE_DIR}/Gizmo/LightGizmo
${FUNGT_BASE_DIR}/Lights
)

# ════════════════════════════════════════════════════════════════════════════
Expand Down Expand Up @@ -280,6 +282,7 @@ set(SOURCE_FILES
${FUNGT_BASE_DIR}/PBR/Space/space.cpp
${FUNGT_BASE_DIR}/PBR/BVH/bvh_builder.cpp
${FUNGT_BASE_DIR}/PBR/TriangleExtractor/triangle_extractor.cpp
${FUNGT_BASE_DIR}/Gizmo/LightGizmo/light_gizmo_renderer.cpp
)

# ════════════════════════════════════════════════════════════════════════════
Expand Down
169 changes: 99 additions & 70 deletions GUI/lights_editor_window.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,24 @@
#define _LIGHT_EDITOR_WINDOW_H_

#include "imgui_window.hpp"
#include "../SceneManager/scene_manager.hpp"
#include "SceneManager/scene_manager.hpp"
#include "Lights/scene_light.hpp"
#include <memory>

/**
* Light Editor Window - Edit scene lighting in real-time
* Works with lights stored in SceneManager
*/
class LightEditorWindow : public ImGuiWindow {
private:
std::shared_ptr<SceneManager> m_sceneManager;
int m_selectedIndex = -1;

const char* lightTypeName(SceneLightType type) {
switch (type) {
case SceneLightType::Point: return "Point";
case SceneLightType::Sun: return "Sun";
case SceneLightType::Spot: return "Spot";
case SceneLightType::Area: return "Area";
}
return "Unknown";
}

public:
LightEditorWindow(std::shared_ptr<SceneManager> sceneManager)
Expand All @@ -20,103 +28,124 @@ class LightEditorWindow : public ImGuiWindow {
}

void onImGuiRender() override {
// Set initial size if window hasn't been created yet
ImGui::SetNextWindowSize(ImVec2(300, 400), ImGuiCond_FirstUseEver);

ImGui::Begin("Light Editor");
ImGui::SetNextWindowSize(ImVec2(320, 500), ImGuiCond_FirstUseEver);
ImGui::Begin("SceneLight Editor");

if (!m_sceneManager) {
ImGui::TextColored(ImVec4(0.7f, 0.7f, 0.7f, 1.0f), "No scene manager");
ImGui::End();
return;
}

ImGui::Text("Scene Light");
ImGui::Separator();
ImGui::Spacing();
auto& lights = m_sceneManager->getLights();

// ====================================================================
// LIGHT POSITION
// ADD LIGHT BUTTONS
// ====================================================================
ImGui::Text("Position");
auto& lightPos = m_sceneManager->getLightPosition();
if (ImGui::DragFloat3("##LightPos", &lightPos.x, 0.1f, -20.0f, 20.0f)) {
// Light position changed - will update on next render automatically!
}
if (ImGui::Button("Add Point")) { SceneLight l; l.type = SceneLightType::Point; l.name = "Point Light"; m_sceneManager->addLight(l); m_selectedIndex = (int)lights.size() - 1; }
ImGui::SameLine();
if (ImGui::Button("Add Sun")) { SceneLight l; l.type = SceneLightType::Sun; l.name = "Sun Light"; m_sceneManager->addLight(l); m_selectedIndex = (int)lights.size() - 1; }
ImGui::SameLine();
if (ImGui::Button("Add Spot")) { SceneLight l; l.type = SceneLightType::Spot; l.name = "Spot Light"; m_sceneManager->addLight(l); m_selectedIndex = (int)lights.size() - 1; }
ImGui::SameLine();
if (ImGui::Button("Add Area")) { SceneLight l; l.type = SceneLightType::Area; l.name = "Area Light"; m_sceneManager->addLight(l); m_selectedIndex = (int)lights.size() - 1; }

ImGui::Spacing();
ImGui::Separator();
ImGui::Spacing();

// ====================================================================
// AMBIENT COLOR
// LIGHT LIST
// ====================================================================
ImGui::Text("Ambient");
auto& ambient = m_sceneManager->getLightAmbient();
ImGui::ColorEdit3("##Ambient", &ambient.x, ImGuiColorEditFlags_Float);

ImGui::Spacing();
ImGui::Text("Lights (%d)", (int)lights.size());
ImGui::BeginChild("LightList", ImVec2(0, 120), true);
for (int i = 0; i < (int)lights.size(); i++) {
char label[64];
snprintf(label, sizeof(label), "[%s] %s", lightTypeName(lights[i].type), lights[i].name.c_str());
if (ImGui::Selectable(label, m_selectedIndex == i)) {
m_selectedIndex = i;
}
}
ImGui::EndChild();

// ====================================================================
// DIFFUSE COLOR
// REMOVE
// ====================================================================
ImGui::Text("Diffuse (Main Color)");
auto& diffuse = m_sceneManager->getLightDiffuse();
ImGui::ColorEdit3("##Diffuse", &diffuse.x, ImGuiColorEditFlags_Float);
if (m_selectedIndex >= 0 && m_selectedIndex < (int)lights.size()) {
if (ImGui::Button("Remove Selected")) {
lights.erase(lights.begin() + m_selectedIndex);
m_selectedIndex = -1;
ImGui::End();
return;
}
}

ImGui::Spacing();
ImGui::Separator();

// ====================================================================
// SPECULAR COLOR
// PROPERTIES OF SELECTED LIGHT
// ====================================================================
ImGui::Text("Specular (Highlights)");
auto& specular = m_sceneManager->getLightSpecular();
ImGui::ColorEdit3("##Specular", &specular.x, ImGuiColorEditFlags_Float);
if (m_selectedIndex < 0 || m_selectedIndex >= (int)lights.size()) {
ImGui::TextColored(ImVec4(0.5f, 0.5f, 0.5f, 1.0f), "Select a light to edit");
ImGui::End();
return;
}

ImGui::Spacing();
ImGui::Separator();
ImGui::Spacing();
SceneLight& light = lights[m_selectedIndex];

// ====================================================================
// PRESETS
// ====================================================================
ImGui::Text("Presets:");
ImGui::Text("Type: %s", lightTypeName(light.type));
ImGui::Spacing();

if (ImGui::Button("Daylight", ImVec2(-1, 0))) {
lightPos = glm::vec3(5.0f, 10.0f, 5.0f);
ambient = glm::vec3(0.3f, 0.3f, 0.3f);
diffuse = glm::vec3(1.0f, 1.0f, 1.0f);
specular = glm::vec3(1.0f, 1.0f, 1.0f);
// Name
char nameBuf[64];
strncpy(nameBuf, light.name.c_str(), sizeof(nameBuf));
if (ImGui::InputText("Name", nameBuf, sizeof(nameBuf))) {
light.name = nameBuf;
}

if (ImGui::Button("Sunset", ImVec2(-1, 0))) {
lightPos = glm::vec3(5.0f, 3.0f, 5.0f);
ambient = glm::vec3(0.2f, 0.15f, 0.1f);
diffuse = glm::vec3(1.0f, 0.6f, 0.3f);
specular = glm::vec3(1.0f, 0.8f, 0.6f);
}
ImGui::Spacing();
ImGui::Separator();

if (ImGui::Button("Night", ImVec2(-1, 0))) {
lightPos = glm::vec3(0.0f, 5.0f, 5.0f);
ambient = glm::vec3(0.05f, 0.05f, 0.1f);
diffuse = glm::vec3(0.3f, 0.3f, 0.5f);
specular = glm::vec3(0.5f, 0.5f, 0.8f);
}
// Common properties
ImGui::Text("Position");
ImGui::DragFloat3("##Pos", &light.position.x, 0.1f);

if (ImGui::Button("Studio (3-Point)", ImVec2(-1, 0))) {
lightPos = glm::vec3(5.0f, 5.0f, 5.0f);
ambient = glm::vec3(0.2f, 0.2f, 0.2f);
diffuse = glm::vec3(0.8f, 0.8f, 0.8f);
specular = glm::vec3(1.0f, 1.0f, 1.0f);
}
ImGui::Text("Color");
ImGui::ColorEdit3("##Color", &light.color.x, ImGuiColorEditFlags_Float);

ImGui::Text("Power");
ImGui::DragFloat("##Power", &light.power, 0.1f, 0.0f, 10000.f);

// ====================================================================
// INFO
// ====================================================================
ImGui::Spacing();
ImGui::Separator();
ImGui::TextColored(ImVec4(0.5f, 0.5f, 0.5f, 1.0f), "Tip:");
ImGui::TextWrapped("Changes apply immediately to all objects in the scene");

// Type specific properties
switch (light.type)
{
case SceneLightType::Point:
ImGui::Text("Radius");
ImGui::DragFloat("##Radius", &light.radius, 0.01f, 0.0f, 100.f);
break;

case SceneLightType::Sun:
ImGui::Text("Direction");
ImGui::DragFloat3("##Dir", &light.direction.x, 0.01f, -1.f, 1.f);
break;

case SceneLightType::Spot:
ImGui::Text("Direction");
ImGui::DragFloat3("##SpotDir", &light.direction.x, 0.01f, -1.f, 1.f);
ImGui::Text("Inner Angle");
ImGui::DragFloat("##Inner", &light.innerAngle, 0.5f, 0.f, light.outerAngle);
ImGui::Text("Outer Angle");
ImGui::DragFloat("##Outer", &light.outerAngle, 0.5f, light.innerAngle, 90.f);
break;

case SceneLightType::Area:
ImGui::Text("Normal");
ImGui::DragFloat3("##Normal", &light.normal.x, 0.01f, -1.f, 1.f);
ImGui::Text("Size");
ImGui::DragFloat2("##Size", &light.size.x, 0.1f, 0.1f, 100.f);
break;
}

ImGui::End();
}
Expand Down
134 changes: 134 additions & 0 deletions Gizmo/LightGizmo/light_gizmo_renderer.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
#include "light_gizmo_renderer.hpp"
#include <cmath>

LightGizmoRenderer::LightGizmoRenderer(SceneManager* sceneManager, Camera* camera)
: m_sceneManager(sceneManager)
, m_camera(camera)
, m_initialized(false)
{
}

LightGizmoRenderer::~LightGizmoRenderer()
{
if (m_sphereMesh.vao) glDeleteVertexArrays(1, &m_sphereMesh.vao);
if (m_sphereMesh.vbo) glDeleteBuffers(1, &m_sphereMesh.vbo);
if (m_quadMesh.vao) glDeleteVertexArrays(1, &m_quadMesh.vao);
if (m_quadMesh.vbo) glDeleteBuffers(1, &m_quadMesh.vbo);
}

void LightGizmoRenderer::buildSphere(float radius, int segments, std::vector<float>& out)
{
// XY plane
for (int i = 0; i < segments; i++)
{
float a1 = (float)i / segments * 2.0f * M_PI;
float a2 = (float)(i + 1) / segments * 2.0f * M_PI;
out.push_back(radius * cos(a1)); out.push_back(radius * sin(a1)); out.push_back(0.f);
out.push_back(radius * cos(a2)); out.push_back(radius * sin(a2)); out.push_back(0.f);
}
// XZ plane
for (int i = 0; i < segments; i++)
{
float a1 = (float)i / segments * 2.0f * M_PI;
float a2 = (float)(i + 1) / segments * 2.0f * M_PI;
out.push_back(radius * cos(a1)); out.push_back(0.f); out.push_back(radius * sin(a1));
out.push_back(radius * cos(a2)); out.push_back(0.f); out.push_back(radius * sin(a2));
}
// YZ plane
for (int i = 0; i < segments; i++)
{
float a1 = (float)i / segments * 2.0f * M_PI;
float a2 = (float)(i + 1) / segments * 2.0f * M_PI;
out.push_back(0.f); out.push_back(radius * cos(a1)); out.push_back(radius * sin(a1));
out.push_back(0.f); out.push_back(radius * cos(a2)); out.push_back(radius * sin(a2));
}
}

void LightGizmoRenderer::uploadMesh(GizmoMesh& mesh, const std::vector<float>& vertices, GLenum drawMode)
{
mesh.drawMode = drawMode;
mesh.vertexCount = (int)vertices.size() / 3;

glGenVertexArrays(1, &mesh.vao);
glGenBuffers(1, &mesh.vbo);

glBindVertexArray(mesh.vao);
glBindBuffer(GL_ARRAY_BUFFER, mesh.vbo);
glBufferData(GL_ARRAY_BUFFER, vertices.size() * sizeof(float), vertices.data(), GL_STATIC_DRAW);

glEnableVertexAttribArray(0);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);

glBindVertexArray(0);
}

void LightGizmoRenderer::init()
{
std::string m_vs = getAssetPath("shaders/gizmo_vs.glsl");
std::string m_fs = getAssetPath("shaders/gizmo_fs.glsl");
m_shader.create(m_vs, m_fs);

// Sphere for point light
std::vector<float> sphereVerts;
buildSphere(0.5f, 32, sphereVerts);
uploadMesh(m_sphereMesh, sphereVerts, GL_LINES);

// Quad billboard for other types
std::vector<float> quadVerts = {
-0.5f, 0.5f, 0.0f,
0.5f, 0.5f, 0.0f,
0.5f, -0.5f, 0.0f,
-0.5f, -0.5f, 0.0f,
-0.5f, 0.5f, 0.0f
};
uploadMesh(m_quadMesh, quadVerts, GL_LINE_STRIP);

m_initialized = true;
}

void LightGizmoRenderer::render(const glm::mat4& projection)
{
if (!m_initialized) return;

const auto& lights = m_sceneManager->getLights();
if (lights.empty()) return;

m_shader.Bind();
m_shader.setUniformMat4fv("ViewMatrix", m_camera->getViewMatrix());
m_shader.setUniformMat4fv("ProjectionMatrix", projection);
m_shader.setUniformVec1f(0.4f, "gizmoScale");

for (const auto& light : lights)
{
glm::vec4 color;
switch (light.type)
{
case SceneLightType::Point: color = glm::vec4(light.color, 1.0f); break;
case SceneLightType::Sun: color = glm::vec4(light.color, 1.0f); break;
case SceneLightType::Spot: color = glm::vec4(light.color, 1.0f); break;
case SceneLightType::Area: color = glm::vec4(light.color, 1.0f);; break;
}

m_shader.setUniformVec3f(light.position, "lightWorldPos");
m_shader.setUniformVec4f(color, "gizmoColor");

if (light.type == SceneLightType::Point)
{
m_shader.set1i(0, "isBillboard");
glBindVertexArray(m_sphereMesh.vao);
glLineWidth(2.0f);
glDrawArrays(m_sphereMesh.drawMode, 0, m_sphereMesh.vertexCount);
glLineWidth(1.0f);
}
else
{
m_shader.set1i(1, "isBillboard");
glBindVertexArray(m_quadMesh.vao);
glDrawArrays(m_quadMesh.drawMode, 0, m_quadMesh.vertexCount);
}

glBindVertexArray(0);
}

m_shader.unBind();
}
Loading