Fixed Renderer and added Light Components
This commit is contained in:
@@ -104,6 +104,8 @@ add_library(Core STATIC
|
||||
src/core/systems/assets/MeshLoaderObj.h
|
||||
src/core/systems/Scene/Components/CameraComponent.cpp
|
||||
src/core/systems/Scene/Components/CameraComponent.h
|
||||
src/core/systems/Scene/Components/PointLightComponent.cpp
|
||||
src/core/systems/Scene/Components/PointLightComponent.h
|
||||
)
|
||||
|
||||
target_include_directories(Core PUBLIC src/core)
|
||||
|
||||
@@ -83,9 +83,9 @@ namespace OX
|
||||
{
|
||||
OX_PROFILE_FUNCTION();
|
||||
|
||||
renderer.Begin(GetScene());
|
||||
renderer.RenderSceneDebugNormals(GetScene(),
|
||||
0.2f, 1.5f);
|
||||
if (renderer.Begin(GetScene())) {
|
||||
renderer.RenderSceneShaded(GetScene());
|
||||
}
|
||||
renderer.End();
|
||||
|
||||
for (auto &layer: m_layers)
|
||||
|
||||
@@ -1,57 +1,47 @@
|
||||
#include "renderer/Renderer.h"
|
||||
|
||||
#include <glm/gtc/matrix_transform.hpp>
|
||||
#include <algorithm>
|
||||
#include <cstdint>
|
||||
#include <limits>
|
||||
#include <vector>
|
||||
|
||||
#define GLM_ENABLE_EXPERIMENTAL
|
||||
#include <glm/gtc/matrix_transform.hpp>
|
||||
#include <glm/gtx/compatibility.hpp>
|
||||
|
||||
#include "systems/Scene/Scene.h"
|
||||
#include "systems/Scene/GameObject.h"
|
||||
#include "systems/Scene/Components/TransformComponent.h"
|
||||
#include "systems/Scene/Components/MeshComponent.h"
|
||||
#include "systems/Scene/Components/CameraComponent.h"
|
||||
#include "systems/Scene/Components/PointLightComponent.h"
|
||||
|
||||
namespace OX
|
||||
{
|
||||
// If your API names differ, change these two helpers.
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// Small helpers
|
||||
|
||||
// 1) Iterate roots
|
||||
static inline const std::vector<GameObject *> &SceneRoots(const Scene &s)
|
||||
{
|
||||
// EXPECTED: Scene::GetRootObjects() -> const std::vector<GameObject*>&
|
||||
return s.roots(); // ← change if your API differs
|
||||
}
|
||||
|
||||
// 2) Iterate children
|
||||
static inline const std::vector<GameObject *> &Children(GameObject *go)
|
||||
{
|
||||
// EXPECTED: GameObject::children() -> const std::vector<GameObject*>&
|
||||
return go->children(); // ← change if your API differs
|
||||
}
|
||||
|
||||
// Accessors for vertex (supports .position/.normal or .pos/.norm)
|
||||
// Expect vertex layout: pos(3) + normal(3) tightly packed. Avoids field names.
|
||||
template<class V>
|
||||
static inline glm::vec3 VPos(const V &v)
|
||||
inline void ExtractPN(const V &v, glm::vec3 &p, glm::vec3 &n)
|
||||
{
|
||||
if constexpr (requires { v.position; }) return glm::vec3(v.position);
|
||||
else if constexpr (requires { v.pos; }) return glm::vec3(v.pos);
|
||||
else return glm::vec3(0.0f);
|
||||
const float *f = reinterpret_cast<const float *>(&v);
|
||||
p = glm::vec3(f[0], f[1], f[2]);
|
||||
n = glm::normalize(glm::vec3(f[3], f[4], f[5]));
|
||||
if (!glm::all(glm::isfinite(n))) n = glm::vec3(0, 1, 0);
|
||||
}
|
||||
|
||||
template<class V>
|
||||
static inline glm::vec3 VNorm(const V &v)
|
||||
{
|
||||
if constexpr (requires { v.normal; }) return glm::vec3(v.normal);
|
||||
else if constexpr (requires { v.norm; }) return glm::vec3(v.norm);
|
||||
else return glm::vec3(0, 1, 0);
|
||||
}
|
||||
|
||||
// ── lifecycle ─────────────────────────────────────────────────────────────────
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// Lifecycle
|
||||
|
||||
Renderer::~Renderer()
|
||||
{
|
||||
if (m_lineVBO)
|
||||
glDeleteBuffers(1, &m_lineVBO);
|
||||
if (m_lineVAO)
|
||||
glDeleteVertexArrays(1, &m_lineVAO);
|
||||
if (m_meshEBO)
|
||||
glDeleteBuffers(1, &m_meshEBO);
|
||||
if (m_meshVBO)
|
||||
glDeleteBuffers(1, &m_meshVBO);
|
||||
if (m_meshVAO)
|
||||
glDeleteVertexArrays(1, &m_meshVAO);
|
||||
|
||||
if (m_depthRBO)
|
||||
glDeleteRenderbuffers(1, &m_depthRBO);
|
||||
@@ -63,7 +53,7 @@ namespace OX
|
||||
void Renderer::Init(int targetWidth, int targetHeight)
|
||||
{
|
||||
CreateFramebuffer(targetWidth, targetHeight);
|
||||
EnsureLinePipeline();
|
||||
EnsureMeshPipeline();
|
||||
}
|
||||
|
||||
void Renderer::ResizeTarget(int width, int height)
|
||||
@@ -84,8 +74,7 @@ namespace OX
|
||||
|
||||
glGenTextures(1, &m_colorTex);
|
||||
glBindTexture(GL_TEXTURE_2D, m_colorTex);
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, width, height,
|
||||
0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
||||
|
||||
@@ -95,14 +84,13 @@ namespace OX
|
||||
|
||||
glGenFramebuffers(1, &m_fbo);
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, m_fbo);
|
||||
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
|
||||
GL_TEXTURE_2D, m_colorTex, 0);
|
||||
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT,
|
||||
GL_RENDERBUFFER, m_depthRBO);
|
||||
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, m_colorTex, 0);
|
||||
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER, m_depthRBO);
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, 0);
|
||||
}
|
||||
|
||||
// ── camera & begin/end ────────────────────────────────────────────────────────
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// Scene traversal / camera lookup
|
||||
|
||||
static void FindPrimaryCamera(const Scene &scene,
|
||||
const GameObject *&camObj,
|
||||
@@ -111,183 +99,220 @@ namespace OX
|
||||
camObj = nullptr;
|
||||
cam = nullptr;
|
||||
|
||||
// Pass 1: primary
|
||||
for (auto *r: SceneRoots(scene)) {
|
||||
std::vector<GameObject *> stack{r};
|
||||
while (!stack.empty()) {
|
||||
GameObject *n = stack.back();
|
||||
stack.pop_back();
|
||||
if (auto *c = n->getComponent<CameraComponent>(); c && c->isPrimary()) {
|
||||
camObj = n;
|
||||
cam = c;
|
||||
return;
|
||||
auto tryFind = [&](bool requirePrimary)
|
||||
{
|
||||
const auto &roots = scene.roots();
|
||||
for (const auto &rup: roots) {
|
||||
std::vector<GameObject *> stack{rup.get()};
|
||||
while (!stack.empty()) {
|
||||
GameObject *n = stack.back();
|
||||
stack.pop_back();
|
||||
|
||||
if (auto *c = n->getComponent<CameraComponent>()) {
|
||||
if (!requirePrimary || c->isPrimary()) {
|
||||
camObj = n;
|
||||
cam = c;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
for (const auto &cup: n->children())
|
||||
stack.push_back(cup.get());
|
||||
}
|
||||
for (auto *ch: Children(n)) stack.push_back(ch);
|
||||
}
|
||||
}
|
||||
// Pass 2: first camera
|
||||
for (auto *r: SceneRoots(scene)) {
|
||||
std::vector<GameObject *> stack{r};
|
||||
while (!stack.empty()) {
|
||||
GameObject *n = stack.back();
|
||||
stack.pop_back();
|
||||
if (auto *c = n->getComponent<CameraComponent>()) {
|
||||
camObj = n;
|
||||
cam = c;
|
||||
return;
|
||||
}
|
||||
for (auto *ch: Children(n)) stack.push_back(ch);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
if (tryFind(true)) return;
|
||||
(void) tryFind(false);
|
||||
}
|
||||
|
||||
bool Renderer::BeginScene(const Scene &scene)
|
||||
static bool ComputeSceneWorldAABB(const Scene &scene,
|
||||
glm::vec3 &outMin, glm::vec3 &outMax,
|
||||
glm::mat4 (*BuildModel)(const TransformComponent *))
|
||||
{
|
||||
bool any = false;
|
||||
glm::vec3 wsMin(std::numeric_limits<float>::max());
|
||||
glm::vec3 wsMax(-std::numeric_limits<float>::max());
|
||||
|
||||
const auto &roots = scene.roots();
|
||||
for (const auto &rup: roots) {
|
||||
std::vector<GameObject *> stack{rup.get()};
|
||||
while (!stack.empty()) {
|
||||
GameObject *go = stack.back();
|
||||
stack.pop_back();
|
||||
|
||||
auto *tr = go->getComponent<TransformComponent>();
|
||||
auto *mesh = go->getComponent<MeshComponent>();
|
||||
if (tr && mesh) {
|
||||
const auto &vin = mesh->vertices();
|
||||
if (!vin.empty()) {
|
||||
const glm::mat4 M = BuildModel(tr);
|
||||
for (const auto &v: vin) {
|
||||
glm::vec3 lp, ln;
|
||||
ExtractPN(v, lp, ln);
|
||||
glm::vec3 wp = glm::vec3(M * glm::vec4(lp, 1.0f));
|
||||
wsMin = glm::min(wsMin, wp);
|
||||
wsMax = glm::max(wsMax, wp);
|
||||
}
|
||||
any = true;
|
||||
}
|
||||
}
|
||||
|
||||
for (const auto &cup: go->children())
|
||||
stack.push_back(cup.get());
|
||||
}
|
||||
}
|
||||
|
||||
if (!any) return false;
|
||||
outMin = wsMin;
|
||||
outMax = wsMax;
|
||||
return true;
|
||||
}
|
||||
|
||||
static void BuildFramingCameraFromAABB(const glm::vec3 &wsMin, const glm::vec3 &wsMax,
|
||||
float aspect, glm::mat4 &outV, glm::mat4 &outP)
|
||||
{
|
||||
const glm::vec3 center = 0.5f * (wsMin + wsMax);
|
||||
const glm::vec3 ext = 0.5f * (wsMax - wsMin);
|
||||
const float radius = glm::length(ext);
|
||||
|
||||
const float fovYDeg = 60.0f;
|
||||
const float fovY = glm::radians(fovYDeg);
|
||||
const float fovX = 2.0f * std::atan(std::tan(fovY * 0.5f) * aspect);
|
||||
|
||||
const float dVert = radius / std::max(0.1f, std::sin(fovY * 0.5f));
|
||||
const float dHorz = radius / std::max(0.1f, std::sin(fovX * 0.5f));
|
||||
const float dist = 1.1f * std::max(dVert, dHorz);
|
||||
|
||||
const glm::vec3 eye = center + glm::vec3(0, 0, dist);
|
||||
outV = glm::lookAt(eye, center, glm::vec3(0, 1, 0));
|
||||
|
||||
float nearZ = std::max(0.01f, dist - radius * 2.0f);
|
||||
float farZ = (dist + radius * 2.0f);
|
||||
if (farZ <= nearZ + 0.01f) farZ = nearZ + 0.02f;
|
||||
|
||||
outP = glm::perspective(fovY, std::max(0.001f, aspect), nearZ, farZ);
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// Begin / End
|
||||
|
||||
bool Renderer::Begin(const Scene &scene)
|
||||
{
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, m_fbo);
|
||||
glViewport(0, 0, m_size.x, m_size.y);
|
||||
|
||||
const float aspect = (m_size.y > 0) ? float(m_size.x) / float(m_size.y) : 1.0f;
|
||||
|
||||
const GameObject *camObj = nullptr;
|
||||
const CameraComponent *cam = nullptr;
|
||||
FindPrimaryCamera(scene, camObj, cam);
|
||||
|
||||
if (!camObj || !cam) {
|
||||
// No camera — magenta so it's obvious
|
||||
glDisable(GL_DEPTH_TEST);
|
||||
glClearColor(1, 0, 1, 1);
|
||||
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
|
||||
m_viewProj = glm::mat4(1.0f);
|
||||
return false;
|
||||
glm::mat4 V(1), P(1);
|
||||
|
||||
if (camObj && cam) {
|
||||
if (const auto *camTr = camObj->getComponent<TransformComponent>()) {
|
||||
P = BuildProjectionMatrix(cam, aspect);
|
||||
V = BuildViewMatrix(camTr);
|
||||
} else {
|
||||
camObj = nullptr; // fallback
|
||||
}
|
||||
}
|
||||
|
||||
const auto *tr = camObj->getComponent<TransformComponent>();
|
||||
if (!tr) {
|
||||
glDisable(GL_DEPTH_TEST);
|
||||
glClearColor(1, 0, 1, 1);
|
||||
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
|
||||
m_viewProj = glm::mat4(1.0f);
|
||||
return false;
|
||||
if (!camObj) {
|
||||
glm::vec3 wsMin, wsMax;
|
||||
if (ComputeSceneWorldAABB(scene, wsMin, wsMax, &Renderer::BuildModelMatrix)) {
|
||||
BuildFramingCameraFromAABB(wsMin, wsMax, aspect, V, P);
|
||||
} else {
|
||||
glDisable(GL_DEPTH_TEST);
|
||||
glClearColor(1, 0, 1, 1);
|
||||
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
|
||||
m_viewProj = glm::mat4(1.0f);
|
||||
m_cameraPosWS = glm::vec3(0);
|
||||
m_frameLights.clear();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
const float aspect = (m_size.y > 0) ? (float) m_size.x / (float) m_size.y : 1.0f;
|
||||
const glm::mat4 P = BuildProjectionMatrix(cam, aspect);
|
||||
const glm::mat4 V = BuildViewMatrix(tr);
|
||||
m_viewProj = P * V;
|
||||
// Camera world position from inverse view
|
||||
glm::mat4 invV = glm::inverse(V);
|
||||
m_cameraPosWS = glm::vec3(invV[3]);
|
||||
|
||||
glEnable(GL_DEPTH_TEST);
|
||||
glDisable(GL_CULL_FACE);
|
||||
glClearColor(0.09f, 0.09f, 0.10f, 1.0f);
|
||||
glEnable(GL_CULL_FACE);
|
||||
glClearColor(0.10f, 0.10f, 0.11f, 1.0f);
|
||||
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
|
||||
|
||||
EnsureLinePipeline();
|
||||
// Gather lights for this frame (after we know camera)
|
||||
GatherPointLights(scene);
|
||||
|
||||
EnsureMeshPipeline();
|
||||
return true;
|
||||
}
|
||||
|
||||
void Renderer::EndScene()
|
||||
void Renderer::End()
|
||||
{
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, 0);
|
||||
}
|
||||
|
||||
// ── draw-list collect/sort/draw ───────────────────────────────────────────────
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// Draw list + render
|
||||
|
||||
void Renderer::CollectDrawItemsRecursive(GameObject *go,
|
||||
const glm::mat4 &view,
|
||||
std::vector<DrawItem> &out)
|
||||
void Renderer::CollectDrawList(const Scene &scene, std::vector<DrawItem> &out, glm::mat4 &outView) const
|
||||
{
|
||||
auto *tr = go->getComponent<TransformComponent>();
|
||||
auto *mesh = go->getComponent<MeshComponent>();
|
||||
if (tr && mesh) {
|
||||
const glm::mat4 M = BuildModelMatrix(tr);
|
||||
const glm::vec3 wsO = glm::vec3(M * glm::vec4(0, 0, 0, 1));
|
||||
const glm::vec3 vsO = glm::vec3(view * glm::vec4(wsO, 1));
|
||||
DrawItem di;
|
||||
di.go = go;
|
||||
di.tr = tr;
|
||||
di.mesh = mesh;
|
||||
di.depth = vsO.z;
|
||||
out.push_back(di);
|
||||
}
|
||||
for (auto *ch: Children(go))
|
||||
CollectDrawItemsRecursive(ch, view, out);
|
||||
}
|
||||
|
||||
void Renderer::RenderSceneDebugNormals(const Scene &scene,
|
||||
float normalScale,
|
||||
float lineWidth)
|
||||
{
|
||||
// Build draw list
|
||||
std::vector<DrawItem> list;
|
||||
list.reserve(256);
|
||||
|
||||
// Need current V from cached VP; reconstruct by inverting a dummy P?
|
||||
// Easier: recompute V from camera again (cheap) to get view-space depth.
|
||||
// (Optional) compute a view matrix to sort by view-space depth if a camera exists
|
||||
const GameObject *camObj = nullptr;
|
||||
const CameraComponent *cam = nullptr;
|
||||
FindPrimaryCamera(scene, camObj, cam);
|
||||
if (!camObj || !cam) {
|
||||
// show a small cross to indicate pipeline working
|
||||
EnsureLinePipeline();
|
||||
m_lineShader.Use();
|
||||
m_lineShader.SetMat4("u_VP", m_viewProj);
|
||||
m_lineShader.SetVec3("u_Color", {0.9f, 0.75f, 0.3f});
|
||||
const glm::vec3 cross[] = {
|
||||
{-0.25f, 0, 0}, {+0.25f, 0, 0},
|
||||
{0, -0.25f, 0}, {0, +0.25f, 0}
|
||||
};
|
||||
glBindVertexArray(m_lineVAO);
|
||||
glBindBuffer(GL_ARRAY_BUFFER, m_lineVBO);
|
||||
glBufferData(GL_ARRAY_BUFFER, sizeof(cross), cross, GL_STREAM_DRAW);
|
||||
glLineWidth(lineWidth);
|
||||
glDrawArrays(GL_LINES, 0, 4);
|
||||
glBindVertexArray(0);
|
||||
return;
|
||||
outView = glm::mat4(1.0f);
|
||||
if (camObj && cam) {
|
||||
if (const auto *camTr = camObj->getComponent<TransformComponent>())
|
||||
outView = BuildViewMatrix(camTr);
|
||||
}
|
||||
const auto *camTr = camObj->getComponent<TransformComponent>();
|
||||
const glm::mat4 V = BuildViewMatrix(camTr);
|
||||
|
||||
for (auto *r: SceneRoots(scene))
|
||||
CollectDrawItemsRecursive(r, V, list);
|
||||
const auto &roots = scene.roots();
|
||||
for (const auto &rup: roots) {
|
||||
std::vector<GameObject *> stack{rup.get()};
|
||||
while (!stack.empty()) {
|
||||
GameObject *go = stack.back();
|
||||
stack.pop_back();
|
||||
|
||||
// Sort: front-to-back (smaller view-space Z first)
|
||||
std::stable_sort(list.begin(), list.end(),
|
||||
[](const DrawItem &a, const DrawItem &b) { return a.depth < b.depth; });
|
||||
auto *tr = go->getComponent<TransformComponent>();
|
||||
auto *mesh = go->getComponent<MeshComponent>();
|
||||
if (tr && mesh) {
|
||||
const glm::mat4 M = BuildModelMatrix(tr);
|
||||
const glm::vec3 ws = glm::vec3(M * glm::vec4(0, 0, 0, 1));
|
||||
const glm::vec3 vs = glm::vec3(outView * glm::vec4(ws, 1));
|
||||
out.push_back(DrawItem{go, tr, mesh, vs.z});
|
||||
}
|
||||
|
||||
// Build one big line buffer and draw
|
||||
std::vector<glm::vec3> lines;
|
||||
lines.reserve(8192);
|
||||
|
||||
for (const DrawItem &di: list) {
|
||||
const auto &verts = di.mesh->vertices();
|
||||
if (verts.empty()) continue;
|
||||
|
||||
const glm::mat4 M = BuildModelMatrix(di.tr);
|
||||
const glm::mat3 N = glm::mat3(M);
|
||||
|
||||
for (const auto &v: verts) {
|
||||
const glm::vec3 p = glm::vec3(M * glm::vec4(VPos(v), 1.f));
|
||||
const glm::vec3 n = glm::normalize(N * VNorm(v));
|
||||
lines.push_back(p);
|
||||
lines.push_back(p + n * normalScale);
|
||||
for (const auto &cup: go->children())
|
||||
stack.push_back(cup.get());
|
||||
}
|
||||
}
|
||||
|
||||
EnsureLinePipeline();
|
||||
m_lineShader.Use();
|
||||
m_lineShader.SetMat4("u_VP", m_viewProj);
|
||||
m_lineShader.SetVec3("u_Color", {0.9f, 0.75f, 0.3f});
|
||||
|
||||
glBindVertexArray(m_lineVAO);
|
||||
glBindBuffer(GL_ARRAY_BUFFER, m_lineVBO);
|
||||
glBufferData(GL_ARRAY_BUFFER,
|
||||
lines.size() * sizeof(glm::vec3),
|
||||
lines.data(),
|
||||
GL_STREAM_DRAW);
|
||||
|
||||
glLineWidth(lineWidth);
|
||||
glDrawArrays(GL_LINES, 0, static_cast<GLsizei>(lines.size()));
|
||||
glBindVertexArray(0);
|
||||
std::stable_sort(out.begin(), out.end(),
|
||||
[](const DrawItem &a, const DrawItem &b) { return a.depth < b.depth; });
|
||||
}
|
||||
|
||||
// ── math helpers ──────────────────────────────────────────────────────────────
|
||||
void Renderer::RenderSceneShaded(const Scene &scene)
|
||||
{
|
||||
std::vector<DrawItem> list;
|
||||
list.reserve(256);
|
||||
glm::mat4 V;
|
||||
CollectDrawList(scene, list, V);
|
||||
|
||||
// Set lighting uniforms once per frame
|
||||
m_meshShader.Use();
|
||||
UploadFrameLightsUniforms();
|
||||
|
||||
for (const DrawItem &di: list)
|
||||
DrawMeshLambert(di, m_viewProj);
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// Math helpers
|
||||
|
||||
glm::mat4 Renderer::BuildModelMatrix(const TransformComponent *tr)
|
||||
{
|
||||
@@ -320,37 +345,241 @@ namespace OX
|
||||
}
|
||||
}
|
||||
|
||||
// ── line pipeline ─────────────────────────────────────────────────────────────
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// Lights: gather + upload
|
||||
|
||||
void Renderer::EnsureLinePipeline()
|
||||
void Renderer::GatherPointLights(const Scene &scene)
|
||||
{
|
||||
if (!m_lineShaderBuilt) {
|
||||
m_frameLights.clear();
|
||||
constexpr int MAX_LIGHTS = 32; // keep below typical uniform limits
|
||||
|
||||
const auto &roots = scene.roots();
|
||||
for (const auto &rup: roots) {
|
||||
std::vector<GameObject *> stack{rup.get()};
|
||||
while (!stack.empty()) {
|
||||
GameObject *go = stack.back();
|
||||
stack.pop_back();
|
||||
|
||||
if (auto *lc = go->getComponent<PointLightComponent>()) {
|
||||
if (lc->enabled() && lc->intensity() > 0.0f && lc->range() > 0.01f) {
|
||||
if (auto *tr = go->getComponent<TransformComponent>()) {
|
||||
PointLightGPU L;
|
||||
auto p = tr->getPosition();
|
||||
L.positionWS = {p[0], p[1], p[2]};
|
||||
L.range = lc->range();
|
||||
L.color = lc->color();
|
||||
L.intensity = lc->intensity();
|
||||
m_frameLights.push_back(L);
|
||||
if ((int) m_frameLights.size() >= MAX_LIGHTS) goto done; // stop early
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (const auto &cup: go->children())
|
||||
stack.push_back(cup.get());
|
||||
}
|
||||
}
|
||||
done:;
|
||||
}
|
||||
|
||||
void Renderer::UploadFrameLightsUniforms()
|
||||
{
|
||||
// Program already bound by m_meshShader.Use()
|
||||
GLint prog = 0;
|
||||
glGetIntegerv(GL_CURRENT_PROGRAM, &prog);
|
||||
if (prog == 0) return;
|
||||
|
||||
const int count = (int) m_frameLights.size();
|
||||
|
||||
// Scalar uniforms
|
||||
if (auto loc = glGetUniformLocation(prog, "u_LightCount"); loc >= 0)
|
||||
glUniform1i(loc, count);
|
||||
if (auto loc = glGetUniformLocation(prog, "u_Ambient"); loc >= 0)
|
||||
glUniform3f(loc, m_ambient.x, m_ambient.y, m_ambient.z);
|
||||
if (auto loc = glGetUniformLocation(prog, "u_SpecColor"); loc >= 0)
|
||||
glUniform3f(loc, m_specColor.x, m_specColor.y, m_specColor.z);
|
||||
if (auto loc = glGetUniformLocation(prog, "u_Shininess"); loc >= 0)
|
||||
glUniform1f(loc, m_shininess);
|
||||
if (auto loc = glGetUniformLocation(prog, "u_CameraPosWS"); loc >= 0)
|
||||
glUniform3f(loc, m_cameraPosWS.x, m_cameraPosWS.y, m_cameraPosWS.z);
|
||||
|
||||
if (count == 0) return;
|
||||
|
||||
// Pack arrays
|
||||
std::vector<glm::vec3> pos(count), col(count);
|
||||
std::vector<float> range(count), intensity(count);
|
||||
|
||||
for (int i = 0; i < count; i++) {
|
||||
pos[i] = m_frameLights[i].positionWS;
|
||||
col[i] = m_frameLights[i].color;
|
||||
range[i] = m_frameLights[i].range;
|
||||
intensity[i] = m_frameLights[i].intensity;
|
||||
}
|
||||
|
||||
// Upload arrays (query [0] base)
|
||||
if (auto loc = glGetUniformLocation(prog, "u_LightPos[0]"); loc >= 0)
|
||||
glUniform3fv(loc, count, &pos[0].x);
|
||||
if (auto loc = glGetUniformLocation(prog, "u_LightColor[0]"); loc >= 0)
|
||||
glUniform3fv(loc, count, &col[0].x);
|
||||
if (auto loc = glGetUniformLocation(prog, "u_LightRange[0]"); loc >= 0)
|
||||
glUniform1fv(loc, count, range.data());
|
||||
if (auto loc = glGetUniformLocation(prog, "u_LightIntensity[0]"); loc >= 0)
|
||||
glUniform1fv(loc, count, intensity.data());
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// Mesh pipeline
|
||||
|
||||
void Renderer::EnsureMeshPipeline()
|
||||
{
|
||||
if (!m_meshShaderReady) {
|
||||
// MAX_LIGHTS must match UploadFrameLightsUniforms() assumptions
|
||||
const char *vs = R"GLSL(
|
||||
#version 330 core
|
||||
layout(location=0) in vec3 aPos;
|
||||
layout(location=1) in vec3 aNormal;
|
||||
|
||||
uniform mat4 u_M;
|
||||
uniform mat4 u_VP;
|
||||
void main(){ gl_Position = u_VP * vec4(aPos,1.0); }
|
||||
uniform mat3 u_N;
|
||||
|
||||
out vec3 vNormalWS;
|
||||
out vec3 vPosWS;
|
||||
|
||||
void main(){
|
||||
vec4 ws = u_M * vec4(aPos, 1.0);
|
||||
vPosWS = ws.xyz;
|
||||
vNormalWS = normalize(u_N * aNormal);
|
||||
gl_Position = u_VP * ws;
|
||||
}
|
||||
)GLSL";
|
||||
|
||||
const char *fs = R"GLSL(
|
||||
#version 330 core
|
||||
#define MAX_LIGHTS 32
|
||||
|
||||
in vec3 vNormalWS;
|
||||
in vec3 vPosWS;
|
||||
|
||||
out vec4 FragColor;
|
||||
uniform vec3 u_Color;
|
||||
void main(){ FragColor = vec4(u_Color,1.0); }
|
||||
|
||||
// Per-object
|
||||
uniform vec3 u_BaseColor;
|
||||
|
||||
// Per-frame
|
||||
uniform int u_LightCount;
|
||||
uniform vec3 u_LightPos[MAX_LIGHTS];
|
||||
uniform vec3 u_LightColor[MAX_LIGHTS];
|
||||
uniform float u_LightIntensity[MAX_LIGHTS];
|
||||
uniform float u_LightRange[MAX_LIGHTS];
|
||||
|
||||
uniform vec3 u_Ambient;
|
||||
uniform vec3 u_SpecColor;
|
||||
uniform float u_Shininess;
|
||||
uniform vec3 u_CameraPosWS;
|
||||
|
||||
// Smooth inverse-square-ish attenuation with soft edge at range
|
||||
float smoothAtten(float dist, float range)
|
||||
{
|
||||
// 0..1 smooth factor (1 near, 0 at range)
|
||||
float x = clamp(1.0 - dist / max(range, 1e-4), 0.0, 1.0);
|
||||
// ease-in curve (square)
|
||||
float fall = x * x;
|
||||
// add mild inverse-square to keep highlights crisp
|
||||
float inv2 = 1.0 / (1.0 + dist*dist);
|
||||
return fall * inv2;
|
||||
}
|
||||
|
||||
void main(){
|
||||
vec3 N = normalize(vNormalWS);
|
||||
vec3 V = normalize(u_CameraPosWS - vPosWS);
|
||||
|
||||
vec3 color = u_Ambient * u_BaseColor;
|
||||
vec3 spec = vec3(0.0);
|
||||
|
||||
for (int i=0; i<u_LightCount && i<MAX_LIGHTS; ++i) {
|
||||
vec3 Lvec = u_LightPos[i] - vPosWS;
|
||||
float d = length(Lvec);
|
||||
if (d <= 0.0001) continue;
|
||||
vec3 L = Lvec / d;
|
||||
|
||||
float ndl = max(dot(N, L), 0.0);
|
||||
if (ndl <= 0.0) continue;
|
||||
|
||||
float att = smoothAtten(d, u_LightRange[i]);
|
||||
vec3 diff = u_LightColor[i] * (u_LightIntensity[i] * ndl * att);
|
||||
|
||||
// Blinn-Phong specular
|
||||
vec3 H = normalize(L + V);
|
||||
float ndh = max(dot(N, H), 0.0);
|
||||
float sp = pow(ndh, max(u_Shininess, 1.0)) * att;
|
||||
spec += u_SpecColor * sp;
|
||||
|
||||
color += diff * u_BaseColor;
|
||||
}
|
||||
|
||||
vec3 outCol = color + spec;
|
||||
FragColor = vec4(outCol, 1.0);
|
||||
}
|
||||
)GLSL";
|
||||
|
||||
m_lineShader.SetName("DebugLineShader");
|
||||
m_lineShader.LoadFromSource(vs, fs);
|
||||
m_lineShaderBuilt = true;
|
||||
m_meshShader.SetName("PointLitMesh");
|
||||
m_meshShader.LoadFromSource(vs, fs);
|
||||
m_meshShaderReady = true;
|
||||
}
|
||||
|
||||
if (m_lineVAO == 0) {
|
||||
glGenVertexArrays(1, &m_lineVAO);
|
||||
glGenBuffers(1, &m_lineVBO);
|
||||
glBindVertexArray(m_lineVAO);
|
||||
glBindBuffer(GL_ARRAY_BUFFER, m_lineVBO);
|
||||
glEnableVertexAttribArray(0);
|
||||
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(glm::vec3), (void *) 0);
|
||||
if (m_meshVAO == 0) {
|
||||
glGenVertexArrays(1, &m_meshVAO);
|
||||
glGenBuffers(1, &m_meshVBO);
|
||||
glGenBuffers(1, &m_meshEBO);
|
||||
|
||||
glBindVertexArray(m_meshVAO);
|
||||
glBindBuffer(GL_ARRAY_BUFFER, m_meshVBO);
|
||||
glEnableVertexAttribArray(0); // pos
|
||||
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(float) * 6, (void *) 0);
|
||||
glEnableVertexAttribArray(1); // normal
|
||||
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, sizeof(float) * 6, (void *) (sizeof(float) * 3));
|
||||
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_meshEBO);
|
||||
glBindVertexArray(0);
|
||||
}
|
||||
}
|
||||
|
||||
void Renderer::DrawMeshLambert(const DrawItem &di, const glm::mat4 &VP)
|
||||
{
|
||||
const auto &vin = di.mesh->vertices();
|
||||
const auto &idx = di.mesh->indices();
|
||||
if (vin.empty() || idx.empty()) return;
|
||||
|
||||
// Interleave [px,py,pz, nx,ny,nz]
|
||||
std::vector<float> packed;
|
||||
packed.reserve(vin.size() * 6);
|
||||
for (const auto &v: vin) {
|
||||
glm::vec3 p, n;
|
||||
ExtractPN(v, p, n);
|
||||
packed.insert(packed.end(), {p.x, p.y, p.z, n.x, n.y, n.z});
|
||||
}
|
||||
|
||||
std::vector<uint32_t> indices;
|
||||
indices.reserve(idx.size());
|
||||
for (auto i: idx) indices.push_back(static_cast<uint32_t>(i));
|
||||
|
||||
const glm::mat4 M = BuildModelMatrix(di.tr);
|
||||
const glm::mat3 N = glm::mat3(glm::transpose(glm::inverse(M)));
|
||||
|
||||
m_meshShader.Use();
|
||||
m_meshShader.SetMat4("u_M", M);
|
||||
m_meshShader.SetMat4("u_VP", VP);
|
||||
m_meshShader.SetMat3("u_N", N);
|
||||
m_meshShader.SetVec3("u_BaseColor", glm::vec3(0.82f, 0.82f, 0.84f)); // light gray by default
|
||||
|
||||
glBindVertexArray(m_meshVAO);
|
||||
glBindBuffer(GL_ARRAY_BUFFER, m_meshVBO);
|
||||
glBufferData(GL_ARRAY_BUFFER, sizeof(float) * packed.size(), packed.data(), GL_STREAM_DRAW);
|
||||
|
||||
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_meshEBO);
|
||||
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(uint32_t) * indices.size(), indices.data(), GL_STREAM_DRAW);
|
||||
|
||||
glDrawElements(GL_TRIANGLES, static_cast<GLsizei>(indices.size()), GL_UNSIGNED_INT, nullptr);
|
||||
glBindVertexArray(0);
|
||||
}
|
||||
} // namespace OX
|
||||
|
||||
@@ -1,12 +1,8 @@
|
||||
#pragma once
|
||||
#include <glm/glm.hpp>
|
||||
#include <GL/glew.h>
|
||||
#include <vector>
|
||||
#include <glm/glm.hpp>
|
||||
#include "types/vec2.h"
|
||||
|
||||
#include "systems/Shader.h"
|
||||
#include "types/vec2.h" // your Vec2i
|
||||
|
||||
// fwd
|
||||
namespace OX
|
||||
{
|
||||
class Scene;
|
||||
@@ -15,6 +11,31 @@ namespace OX
|
||||
class TransformComponent;
|
||||
class CameraComponent;
|
||||
|
||||
|
||||
class Shader; // your wrapper
|
||||
}
|
||||
|
||||
#include "systems/Shader.h"
|
||||
#include <GL/glew.h>
|
||||
|
||||
namespace OX
|
||||
{
|
||||
struct DrawItem
|
||||
{
|
||||
GameObject *go{};
|
||||
TransformComponent *tr{};
|
||||
MeshComponent *mesh{};
|
||||
float depth{};
|
||||
};
|
||||
|
||||
struct PointLightGPU
|
||||
{
|
||||
glm::vec3 positionWS{0.0f};
|
||||
float range{10.0f};
|
||||
glm::vec3 color{1.0f};
|
||||
float intensity{1.0f};
|
||||
};
|
||||
|
||||
class Renderer
|
||||
{
|
||||
public:
|
||||
@@ -24,19 +45,13 @@ namespace OX
|
||||
|
||||
void ResizeTarget(int width, int height);
|
||||
|
||||
// New simple begin/end that bind the FBO and cache VP
|
||||
bool BeginScene(const Scene &scene);
|
||||
// Begin locates camera (or auto-frames AABB) and binds FBO
|
||||
bool Begin(const Scene &scene);
|
||||
|
||||
void EndScene();
|
||||
void End();
|
||||
|
||||
// Back-compat (Core.cpp can keep using these)
|
||||
bool Begin(const Scene &scene) { return BeginScene(scene); }
|
||||
void End() { EndScene(); }
|
||||
|
||||
// Build vector (ordered) and draw (normals for now)
|
||||
void RenderSceneDebugNormals(const Scene &scene,
|
||||
float normalScale = 0.1f,
|
||||
float lineWidth = 1.5f);
|
||||
// Build draw list (front-to-back) and render with point lights
|
||||
void RenderSceneShaded(const Scene &scene);
|
||||
|
||||
GLuint GetColorTexture() const { return m_colorTex; }
|
||||
Vec2i GetTargetSize() const { return m_size; }
|
||||
@@ -44,41 +59,45 @@ namespace OX
|
||||
private:
|
||||
void CreateFramebuffer(int width, int height);
|
||||
|
||||
void EnsureLinePipeline();
|
||||
|
||||
// Scene traversal helpers
|
||||
static glm::mat4 BuildModelMatrix(const TransformComponent *tr);
|
||||
|
||||
static glm::mat4 BuildViewMatrix(const TransformComponent *camTr);
|
||||
|
||||
static glm::mat4 BuildProjectionMatrix(const CameraComponent *cam, float aspect);
|
||||
|
||||
// draw-list bits (very plain)
|
||||
struct DrawItem
|
||||
{
|
||||
GameObject *go = nullptr;
|
||||
TransformComponent *tr = nullptr;
|
||||
MeshComponent *mesh = nullptr;
|
||||
float depth = 0.0f; // view-space Z of object origin
|
||||
};
|
||||
void EnsureMeshPipeline();
|
||||
|
||||
void CollectDrawItemsRecursive(GameObject *go,
|
||||
const glm::mat4 &view,
|
||||
std::vector<DrawItem> &out);
|
||||
void CollectDrawList(const Scene &scene, std::vector<DrawItem> &out, glm::mat4 &outView) const;
|
||||
|
||||
void GatherPointLights(const Scene &scene);
|
||||
|
||||
void UploadFrameLightsUniforms(); // uses glGetIntegerv(GL_CURRENT_PROGRAM, ...)
|
||||
|
||||
void DrawMeshLambert(const DrawItem &di, const glm::mat4 &VP);
|
||||
|
||||
private:
|
||||
// Offscreen target
|
||||
Vec2i m_size{0, 0};
|
||||
Vec2i m_size{};
|
||||
GLuint m_fbo = 0;
|
||||
GLuint m_colorTex = 0;
|
||||
GLuint m_depthRBO = 0;
|
||||
|
||||
// Debug line pipeline
|
||||
Shader m_lineShader;
|
||||
GLuint m_lineVAO = 0;
|
||||
GLuint m_lineVBO = 0;
|
||||
bool m_lineShaderBuilt = false;
|
||||
// Mesh pipeline
|
||||
Shader m_meshShader;
|
||||
bool m_meshShaderReady = false;
|
||||
GLuint m_meshVAO = 0;
|
||||
GLuint m_meshVBO = 0;
|
||||
GLuint m_meshEBO = 0;
|
||||
|
||||
// Cached VP for current frame
|
||||
// Per-frame state
|
||||
glm::mat4 m_viewProj{1.0f};
|
||||
glm::vec3 m_cameraPosWS{0.0f};
|
||||
std::vector<PointLightGPU> m_frameLights;
|
||||
|
||||
// Artistic tuning
|
||||
glm::vec3 m_ambient{0.06f, 0.065f, 0.07f};
|
||||
glm::vec3 m_specColor{0.18f, 0.18f, 0.18f};
|
||||
float m_shininess = 32.0f;
|
||||
};
|
||||
} // namespace OX
|
||||
|
||||
35
src/core/systems/Scene/Components/PointLightComponent.cpp
Normal file
35
src/core/systems/Scene/Components/PointLightComponent.cpp
Normal file
@@ -0,0 +1,35 @@
|
||||
#include "PointLightComponent.h"
|
||||
|
||||
namespace OX
|
||||
{
|
||||
YAML::Node PointLightComponent::Serialize() const
|
||||
{
|
||||
YAML::Node n;
|
||||
n["type"] = "PointLight";
|
||||
n["enabled"] = m_enabled;
|
||||
n["color"] = YAML::Node(YAML::NodeType::Sequence);
|
||||
n["color"].push_back(m_color.x);
|
||||
n["color"].push_back(m_color.y);
|
||||
n["color"].push_back(m_color.z);
|
||||
n["intensity"] = m_intensity;
|
||||
n["range"] = m_range;
|
||||
n["cast_shadows"] = m_castShadows;
|
||||
return n;
|
||||
}
|
||||
|
||||
void PointLightComponent::Deserialize(const YAML::Node &n)
|
||||
{
|
||||
if (!n || !n.IsMap()) return;
|
||||
|
||||
if (auto v = n["enabled"]) m_enabled = v.as<bool>();
|
||||
if (auto v = n["intensity"]) m_intensity = v.as<float>();
|
||||
if (auto v = n["range"]) m_range = v.as<float>();
|
||||
if (auto v = n["cast_shadows"]) m_castShadows = v.as<bool>();
|
||||
|
||||
if (auto c = n["color"]; c && c.IsSequence() && c.size() >= 3) {
|
||||
m_color.x = c[0].as<float>();
|
||||
m_color.y = c[1].as<float>();
|
||||
m_color.z = c[2].as<float>();
|
||||
}
|
||||
}
|
||||
} // namespace OX
|
||||
45
src/core/systems/Scene/Components/PointLightComponent.h
Normal file
45
src/core/systems/Scene/Components/PointLightComponent.h
Normal file
@@ -0,0 +1,45 @@
|
||||
#pragma once
|
||||
#include "Component.h"
|
||||
#include <glm/glm.hpp>
|
||||
#include <yaml-cpp/yaml.h>
|
||||
|
||||
namespace OX
|
||||
{
|
||||
class PointLightComponent : public Component
|
||||
{
|
||||
public:
|
||||
PointLightComponent() = default;
|
||||
|
||||
// Enable / disable
|
||||
void setEnabled(bool v) { m_enabled = v; }
|
||||
bool enabled() const { return m_enabled; }
|
||||
|
||||
// Color (linear)
|
||||
void setColor(const glm::vec3 &c) { m_color = c; }
|
||||
glm::vec3 color() const { return m_color; }
|
||||
|
||||
// Luminous intensity (arbitrary units for now)
|
||||
void setIntensity(float i) { m_intensity = i; }
|
||||
float intensity() const { return m_intensity; }
|
||||
|
||||
// Effective range in world units (for culling / attenuation)
|
||||
void setRange(float r) { m_range = r; }
|
||||
float range() const { return m_range; }
|
||||
|
||||
// (Reserved for later)
|
||||
void setCastShadows(bool v) { m_castShadows = v; }
|
||||
bool castShadows() const { return m_castShadows; }
|
||||
|
||||
// Serialization
|
||||
YAML::Node Serialize() const override;
|
||||
|
||||
void Deserialize(const YAML::Node &n) override;
|
||||
|
||||
private:
|
||||
bool m_enabled = true;
|
||||
glm::vec3 m_color{1.0f, 1.0f, 1.0f};
|
||||
float m_intensity = 5.0f; // “brightness”
|
||||
float m_range = 10.0f; // meters / world units
|
||||
bool m_castShadows = false; // unused for now
|
||||
};
|
||||
} // namespace OX
|
||||
@@ -6,7 +6,6 @@
|
||||
|
||||
namespace OX
|
||||
{
|
||||
|
||||
Shader::Shader(const std::string &vertexPath, const std::string &fragmentPath)
|
||||
{
|
||||
LoadFromFiles(vertexPath, fragmentPath);
|
||||
@@ -108,7 +107,7 @@ namespace OX
|
||||
}
|
||||
|
||||
try {
|
||||
OX_PROFILE_LABEL("Get Write Time");
|
||||
OX_PROFILE_LABEL("Get Write Time");
|
||||
auto vertTime = std::filesystem::last_write_time(m_vertexPath);
|
||||
auto fragTime = std::filesystem::last_write_time(m_fragmentPath);
|
||||
|
||||
@@ -118,7 +117,6 @@ namespace OX
|
||||
}
|
||||
} catch (const std::exception &e) {
|
||||
Logger::LogError("Failed to Reload Shader '%s', %s", m_name.c_str(), e.what());
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -169,6 +167,13 @@ namespace OX
|
||||
glUniform4fv(GetUniformLocation(name), 1, &value[0]);
|
||||
}
|
||||
|
||||
|
||||
void Shader::SetMat3(const std::string &name, const glm::mat3 &value)
|
||||
{
|
||||
glUniformMatrix3fv(GetUniformLocation(name), 1, GL_FALSE, &value[0][0]);
|
||||
}
|
||||
|
||||
|
||||
void Shader::SetMat4(const std::string &name, const glm::mat4 &value)
|
||||
{
|
||||
glUniformMatrix4fv(GetUniformLocation(name), 1, GL_FALSE, &value[0][0]);
|
||||
|
||||
@@ -40,6 +40,8 @@ namespace OX
|
||||
|
||||
void SetVec4(const std::string &name, const glm::vec4 &value);
|
||||
|
||||
void SetMat3(const std::string &name, const glm::mat3 &value);
|
||||
|
||||
void SetMat4(const std::string &name, const glm::mat4 &value);
|
||||
|
||||
void SetBool(const std::string &name, bool value);
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
#include "systems/Scene/Components/TransformComponent.h"
|
||||
#include "systems/Scene/Components/MeshComponent.h"
|
||||
#include "systems/Scene/Components/CameraComponent.h"
|
||||
#include "systems/Scene/Components/PointLightComponent.h"
|
||||
|
||||
|
||||
#include <imgui.h>
|
||||
@@ -272,6 +273,11 @@ namespace OX
|
||||
ImGui::Dummy(ImVec2(0, 4));
|
||||
}
|
||||
|
||||
if (go->getComponent<PointLightComponent>()) {
|
||||
DrawPointLightComponent(go);
|
||||
ImGui::Dummy(ImVec2(0, 4));
|
||||
}
|
||||
|
||||
|
||||
// Popup-only add
|
||||
DrawAddComponentMenu(go);
|
||||
@@ -478,6 +484,76 @@ namespace OX
|
||||
}
|
||||
}
|
||||
|
||||
void InspectorWindow::DrawPointLightComponent(GameObject *go)
|
||||
{
|
||||
bool toRemove = false;
|
||||
bool open = ComponentHeader("Point Light", &m_plOpen, &toRemove);
|
||||
if (open) {
|
||||
if (auto *pl = go->getComponent<PointLightComponent>()) {
|
||||
if (ImGui::BeginTable("##pl_tbl", 2,
|
||||
ImGuiTableFlags_SizingStretchProp |
|
||||
ImGuiTableFlags_BordersInnerV)) {
|
||||
ImGui::TableSetupColumn("Label", ImGuiTableColumnFlags_WidthFixed, 110.0f);
|
||||
ImGui::TableSetupColumn("Value", ImGuiTableColumnFlags_WidthStretch);
|
||||
|
||||
// Enabled
|
||||
ImGui::TableNextRow();
|
||||
ImGui::TableSetColumnIndex(0);
|
||||
ImGui::AlignTextToFramePadding();
|
||||
ImGui::TextUnformatted("Enabled");
|
||||
ImGui::TableSetColumnIndex(1);
|
||||
bool enabled = pl->enabled();
|
||||
if (ImGui::Checkbox("##pl_enabled", &enabled)) pl->setEnabled(enabled);
|
||||
|
||||
// Color
|
||||
ImGui::TableNextRow();
|
||||
ImGui::TableSetColumnIndex(0);
|
||||
ImGui::AlignTextToFramePadding();
|
||||
ImGui::TextUnformatted("Color");
|
||||
ImGui::TableSetColumnIndex(1);
|
||||
glm::vec3 c = pl->color();
|
||||
if (ImGui::ColorEdit3("##pl_color", &c.x, ImGuiColorEditFlags_NoInputs))
|
||||
pl->setColor(c);
|
||||
|
||||
// Intensity
|
||||
ImGui::TableNextRow();
|
||||
ImGui::TableSetColumnIndex(0);
|
||||
ImGui::AlignTextToFramePadding();
|
||||
ImGui::TextUnformatted("Intensity");
|
||||
ImGui::TableSetColumnIndex(1);
|
||||
float I = pl->intensity();
|
||||
if (ImGui::DragFloat("##pl_intensity", &I, 0.05f, 0.0f, 1000.0f))
|
||||
pl->setIntensity(std::max(0.0f, I));
|
||||
|
||||
// Range
|
||||
ImGui::TableNextRow();
|
||||
ImGui::TableSetColumnIndex(0);
|
||||
ImGui::AlignTextToFramePadding();
|
||||
ImGui::TextUnformatted("Range");
|
||||
ImGui::TableSetColumnIndex(1);
|
||||
float R = pl->range();
|
||||
if (ImGui::DragFloat("##pl_range", &R, 0.05f, 0.01f, 100000.0f))
|
||||
pl->setRange(std::max(0.01f, R));
|
||||
|
||||
// Shadows (reserved)
|
||||
ImGui::TableNextRow();
|
||||
ImGui::TableSetColumnIndex(0);
|
||||
ImGui::AlignTextToFramePadding();
|
||||
ImGui::TextUnformatted("Cast Shadows");
|
||||
ImGui::TableSetColumnIndex(1);
|
||||
bool cs = pl->castShadows();
|
||||
if (ImGui::Checkbox("##pl_shadows", &cs)) pl->setCastShadows(cs);
|
||||
|
||||
ImGui::EndTable();
|
||||
}
|
||||
}
|
||||
ImGui::TreePop();
|
||||
}
|
||||
|
||||
if (toRemove) (void) go->removeComponent<PointLightComponent>();
|
||||
}
|
||||
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// Popup-only add
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
@@ -490,6 +566,7 @@ namespace OX
|
||||
const bool hasTr = go->getComponent<TransformComponent>() != nullptr;
|
||||
const bool hasMesh = go->getComponent<MeshComponent>() != nullptr;
|
||||
const bool hasCam = go->getComponent<CameraComponent>() != nullptr;
|
||||
const bool hasPoint = go->getComponent<PointLightComponent>() != nullptr;
|
||||
|
||||
|
||||
if (ImGui::MenuItem("TransformComponent", nullptr, false, !hasTr)) {
|
||||
@@ -504,11 +581,15 @@ namespace OX
|
||||
|
||||
if (ImGui::MenuItem("MeshComponent", nullptr, false, !hasMesh)) {
|
||||
go->addComponent(std::make_unique<MeshComponent>());
|
||||
// If we just added one, clear path field so user can type
|
||||
m_meshPath.clear();
|
||||
ImGui::CloseCurrentPopup();
|
||||
}
|
||||
|
||||
if (ImGui::MenuItem("PointLightComponent", nullptr, false, !hasPoint)) {
|
||||
go->addComponent(std::make_unique<PointLightComponent>());
|
||||
ImGui::CloseCurrentPopup();
|
||||
}
|
||||
|
||||
ImGui::EndPopup();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,6 +41,8 @@ namespace OX
|
||||
|
||||
void DrawCameraComponent(GameObject *go);
|
||||
|
||||
void DrawPointLightComponent(GameObject *go);
|
||||
|
||||
// Popup to add components
|
||||
void DrawAddComponentMenu(GameObject *go);
|
||||
|
||||
@@ -50,5 +52,6 @@ namespace OX
|
||||
std::string m_meshPath;
|
||||
bool m_trOpen = true;
|
||||
bool m_camOpen = true;
|
||||
bool m_plOpen = true;
|
||||
};
|
||||
} // namespace OX
|
||||
|
||||
Reference in New Issue
Block a user