diff --git a/assets/shaders/Line.frag b/assets/shaders/Line.frag new file mode 100644 index 0000000..3d1dcdb --- /dev/null +++ b/assets/shaders/Line.frag @@ -0,0 +1,6 @@ +#version 330 core +out vec4 FragColor; +uniform vec4 uColor; +void main() { + FragColor = uColor; +} \ No newline at end of file diff --git a/assets/shaders/Line.vert b/assets/shaders/Line.vert new file mode 100644 index 0000000..11d57ca --- /dev/null +++ b/assets/shaders/Line.vert @@ -0,0 +1,8 @@ +#version 330 core +layout (location = 0) in vec3 aPos; +uniform mat4 uMVP; +void main() { + gl_Position = uMVP * vec4(aPos, 1.0); +} + + diff --git a/imgui.ini b/imgui.ini index db95345..b2668ba 100644 --- a/imgui.ini +++ b/imgui.ini @@ -80,8 +80,8 @@ Collapsed=0 DockId=0x0000001F,0 [Window][Performance##performance] -Pos=8,702 -Size=335,467 +Pos=8,581 +Size=335,588 Collapsed=0 DockId=0x0000001C,0 @@ -105,7 +105,7 @@ DockId=0x0000000F,0 [Window][Scene Window##SceneWindow] Pos=8,28 -Size=335,672 +Size=335,551 Collapsed=0 DockId=0x0000001B,0 @@ -173,8 +173,8 @@ Column 3 Weight=0.6950 DockSpace ID=0x14621557 Window=0x3DA2F1DE Pos=8,51 Size=1904,1141 Split=X Selected=0xF7365A5A DockNode ID=0x00000020 Parent=0x14621557 SizeRef=884,684 Split=X DockNode ID=0x00000013 Parent=0x00000020 SizeRef=335,1142 Split=Y Selected=0x818D04BB - DockNode ID=0x0000001B Parent=0x00000013 SizeRef=264,672 HiddenTabBar=1 Selected=0x1D5D92B6 - DockNode ID=0x0000001C Parent=0x00000013 SizeRef=264,467 HiddenTabBar=1 Selected=0x818D04BB + DockNode ID=0x0000001B Parent=0x00000013 SizeRef=264,551 HiddenTabBar=1 Selected=0x1D5D92B6 + DockNode ID=0x0000001C Parent=0x00000013 SizeRef=264,588 HiddenTabBar=1 Selected=0x818D04BB DockNode ID=0x00000014 Parent=0x00000020 SizeRef=547,1142 Split=X DockNode ID=0x00000015 Parent=0x00000014 SizeRef=1158,1142 Split=X DockNode ID=0x00000011 Parent=0x00000015 SizeRef=265,1142 Selected=0x1D5D92B6 diff --git a/src/Engine.cpp b/src/Engine.cpp index c64516c..35a3994 100644 --- a/src/Engine.cpp +++ b/src/Engine.cpp @@ -36,6 +36,7 @@ #include "Engine/InputManager.h" #include "Engine/ScopedTimer.h" #include "Engine/Profiler.h" +#include "Engine/Settings.h" // #define YAML_CPP_STATIC_DEFINE #include @@ -50,6 +51,7 @@ SceneManager g_SceneManager; InputManager g_InputManager; +Settings g_SettingsManager; std::vector> g_GameObjects; @@ -58,6 +60,8 @@ std::shared_ptr g_RuntimeCameraObject; int g_GPU_Triangles_drawn_to_screen = 0; int g_GPU_Draw_Calls = 0; +bool DrawBBBO; + GameObject *g_SelectedObject; // Pointer to the currently selected object int m_GameRunning = 0; @@ -65,6 +69,10 @@ int m_GameRunning = 0; bool MyEngine::Init(int width, int height, const std::string &title) { DEBUG_PRINT("[START] Engine Init"); + + g_SettingsManager.S_LineColor = glm::vec4(1.0f, 0.27058823529f, 0.0f, 1.0f); + + // ------------------------------------------ // 1) Initialize GLFW // ------------------------------------------ @@ -170,6 +178,8 @@ bool MyEngine::Init(int width, int height, const std::string &title) m_FirstTickGameRunning = true; m_showProfiler = true; + DrawBBBO = false; + g_LoggerWindow = m_LoggerWindow.get(); // Optionally, call 'onInit' Lua function @@ -206,8 +216,6 @@ void MyEngine::Run() glfwPollEvents(); } - - { SCOPE_TIMER("InputManagerUpdate"); g_InputManager.Update(m_Window); @@ -432,6 +440,22 @@ void MyEngine::ShowDockSpace() ImGui::Checkbox("Show Profiler", &m_showProfiler); // Add a checkbox to toggle the profiler ImGui::EndMenu(); } + if (ImGui::BeginMenu("Settings")) + { + // A checkbox to enable/disable bounding boxes + ImGui::Checkbox("Show Box's", &DrawBBBO); + + // On the same line, we place a small color edit widget + ImGui::SameLine(); + ImGui::ColorEdit4("Box Color", + reinterpret_cast(&g_SettingsManager.S_LineColor), + ImGuiColorEditFlags_NoInputs | ImGuiColorEditFlags_NoLabel); + // Explanation of Flags: + // - ImGuiColorEditFlags_NoInputs hides numeric RGBA input fields + // - ImGuiColorEditFlags_NoLabel hides the default label, since we already have "Box Color" + ImGui::EndMenu(); + } + if (ImGui::BeginMenu("Engine")) { diff --git a/src/Engine/AssetManager.cpp b/src/Engine/AssetManager.cpp index 8fe488d..d792741 100644 --- a/src/Engine/AssetManager.cpp +++ b/src/Engine/AssetManager.cpp @@ -520,6 +520,7 @@ Model *LoadModelFromList(const std::string &path) // Initialize OpenGL buffers for the submesh submesh.Initialize(); + } if (materialToSubmesh.empty()) diff --git a/src/Engine/AssetManager.h b/src/Engine/AssetManager.h index 84af886..1da42d7 100644 --- a/src/Engine/AssetManager.h +++ b/src/Engine/AssetManager.h @@ -82,10 +82,31 @@ struct Submesh std::vector indices; std::vector textures; GLuint vao = 0, vbo = 0, ebo = 0; + unsigned int bboxVAO, bboxVBO; + GLsizei bboxVertexCount; + + glm::vec3 minExtents; + glm::vec3 maxExtents; // Initialize OpenGL buffers for the submesh void Initialize() { + glm::vec3 minExtents(FLT_MAX); + glm::vec3 maxExtents(-FLT_MAX); + for (auto &v : vertices) + { + minExtents.x = std::min(minExtents.x, v.position[0]); + minExtents.y = std::min(minExtents.y, v.position[1]); + minExtents.z = std::min(minExtents.z, v.position[2]); + + maxExtents.x = std::max(maxExtents.x, v.position[0]); + maxExtents.y = std::max(maxExtents.y, v.position[1]); + maxExtents.z = std::max(maxExtents.z, v.position[2]); + } + + // Store in the submesh + this->minExtents = minExtents; + this->maxExtents = maxExtents; glGenVertexArrays(1, &vao); glGenBuffers(1, &vbo); glGenBuffers(1, &ebo); @@ -109,6 +130,58 @@ struct Submesh glVertexAttribPointer(2, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void *)(5 * sizeof(float))); glBindVertexArray(0); + + std::vector bboxLines = { + // bottom face + {minExtents.x, minExtents.y, minExtents.z}, + {maxExtents.x, minExtents.y, minExtents.z}, + {maxExtents.x, minExtents.y, minExtents.z}, + {maxExtents.x, minExtents.y, maxExtents.z}, + {maxExtents.x, minExtents.y, maxExtents.z}, + {minExtents.x, minExtents.y, maxExtents.z}, + {minExtents.x, minExtents.y, maxExtents.z}, + {minExtents.x, minExtents.y, minExtents.z}, + // top face + {minExtents.x, maxExtents.y, minExtents.z}, + {maxExtents.x, maxExtents.y, minExtents.z}, + {maxExtents.x, maxExtents.y, minExtents.z}, + {maxExtents.x, maxExtents.y, maxExtents.z}, + {maxExtents.x, maxExtents.y, maxExtents.z}, + {minExtents.x, maxExtents.y, maxExtents.z}, + {minExtents.x, maxExtents.y, maxExtents.z}, + {minExtents.x, maxExtents.y, minExtents.z}, + // vertical edges + {minExtents.x, minExtents.y, minExtents.z}, + {minExtents.x, maxExtents.y, minExtents.z}, + {maxExtents.x, minExtents.y, minExtents.z}, + {maxExtents.x, maxExtents.y, minExtents.z}, + {maxExtents.x, minExtents.y, maxExtents.z}, + {maxExtents.x, maxExtents.y, maxExtents.z}, + {minExtents.x, minExtents.y, maxExtents.z}, + {minExtents.x, maxExtents.y, maxExtents.z}}; + + glGenVertexArrays(1, &bboxVAO); + glGenBuffers(1, &bboxVBO); + + glBindVertexArray(bboxVAO); + glBindBuffer(GL_ARRAY_BUFFER, bboxVBO); + glBufferData( + GL_ARRAY_BUFFER, + bboxLines.size() * sizeof(glm::vec3), + bboxLines.data(), + GL_STATIC_DRAW); + + glEnableVertexAttribArray(0); + glVertexAttribPointer( + 0, 3, GL_FLOAT, GL_FALSE, + sizeof(glm::vec3), (void *)0); + + glBindVertexArray(0); + + bboxVAO = bboxVAO; + bboxVBO = bboxVBO; + bboxVertexCount = (GLsizei)bboxLines.size(); + } // Render the submesh @@ -200,9 +273,9 @@ public: // Debug: Log the variant type if (std::holds_alternative>(it->second)) { - #ifdef DEBUG +#ifdef DEBUG DebugAssetMap(); - #endif +#endif std::cout << "[AssetManager] Retrieved asset from cache: " << key << std::endl; return std::get>(it->second); } diff --git a/src/Engine/Settings.h b/src/Engine/Settings.h new file mode 100644 index 0000000..e992740 --- /dev/null +++ b/src/Engine/Settings.h @@ -0,0 +1,6 @@ +#include "imgui.h" +#include + +struct Settings { + glm::vec4 S_LineColor; +}; \ No newline at end of file diff --git a/src/Rendering/Shader.cpp b/src/Rendering/Shader.cpp index 43f3bc2..e5a56a5 100644 --- a/src/Rendering/Shader.cpp +++ b/src/Rendering/Shader.cpp @@ -152,6 +152,12 @@ void Shader::SetVec3(const std::string &name, const glm::vec3 &value) const glUniform3fv(glGetUniformLocation(ID, name.c_str()), 1, &value[0]); } + +void Shader::SetVec4(const std::string &name, const glm::vec4 &value) const +{ + glUseProgram(ID); // Ensure the shader program is active + glUniform4fv(glGetUniformLocation(ID, name.c_str()), 1, &value[0]); +} void Shader::SetSampler2D(const std::string &name, int textureUnit) const { glUseProgram(ID); // Ensure the shader program is active diff --git a/src/Rendering/Shader.h b/src/Rendering/Shader.h index e2c872e..500c6d5 100644 --- a/src/Rendering/Shader.h +++ b/src/Rendering/Shader.h @@ -26,6 +26,8 @@ public: void SetMat4(const std::string &name, const glm::mat4 &mat) const; // For setting 4x4 matrices void SetSampler2D(const std::string &name, int textureUnit) const; void SetVec3(const std::string &name, const glm::vec3 &value) const; + void SetVec4(const std::string &name, const glm::vec4 &value) const; + private: // Caching uniform locations for performance diff --git a/src/Windows/PerformanceWindow.cpp b/src/Windows/PerformanceWindow.cpp index 87e52e4..0a434ac 100644 --- a/src/Windows/PerformanceWindow.cpp +++ b/src/Windows/PerformanceWindow.cpp @@ -1,6 +1,7 @@ #include "PerformanceWindow.h" #include "imgui.h" -#include // for std::max_element, etc. +#include // for std::max +#include // for FLT_MAX #include "Engine/ThemeManager.h" #include "Engine/ScopedTimer.h" @@ -8,46 +9,44 @@ extern int LoadedAssets; extern int g_GPU_Triangles_drawn_to_screen; extern int g_GPU_Draw_Calls; - const char *polygonModeOptions[] = {"Fill", "Wireframe", "Points"}; -const int numPolygonModes = sizeof(polygonModeOptions) / sizeof(polygonModeOptions[0]); - -int polygonMode = 0; +static int polygonMode = 0; // Initialize static members int PerformanceWindow::m_OpenGLCallCount = 0; int PerformanceWindow::m_TriangleCount = 0; // We'll store up to 60 data points for each stat. -static float s_FpsHistory[60] = {0.0f}; -static float s_MsHistory[60] = {0.0f}; -static float s_CallsHistory[60] = {0.0f}; +static float s_FpsHistory[60] = {0.0f}; +static float s_MsHistory[60] = {0.0f}; +static float s_CallsHistory[60] = {0.0f}; static float s_TriangleHistory[60] = {0.0f}; // Current dynamic max scale for FPS and ms static float s_FpsScale = 120.0f; // default starting scale for FPS -static float s_MsScale = 25.0f; // default starting scale for ms +static float s_MsScale = 25.0f; // default starting scale for ms -// This function shifts the old values left and appends a new value at the end. -static void PushValueToHistory(float *historyArray, int historySize, float newValue) +// Push new value into history, shifting old values left +static void PushValueToHistory(float* historyArray, int historySize, float newValue) { for (int i = 0; i < historySize - 1; i++) + { historyArray[i] = historyArray[i + 1]; + } historyArray[historySize - 1] = newValue; } // We'll track when we last pushed data to our history. static double s_LastPushTime = 0.0; - // We'll also track when we last updated the scale static double s_LastScaleUpdate = 0.0; -// Update counters from the outside void PerformanceWindow::UpdatePerformanceStats(int newCallCount, int newTriangleCount) { m_OpenGLCallCount = newCallCount; - m_TriangleCount = newTriangleCount; + m_TriangleCount = newTriangleCount; + // Reset GPU counters each frame, or each time you show g_GPU_Triangles_drawn_to_screen = 0; g_GPU_Draw_Calls = 0; } @@ -59,7 +58,7 @@ void PerformanceWindow::Show(float fps, float ms) // 1) Get current time from ImGui's internal clock double currentTime = ImGui::GetTime(); - // 2) If at least 0.05s has passed, push new data (about 20 updates per second) + // 2) If at least 0.05s has passed, push new data (~20 times a second) if ((currentTime - s_LastPushTime) >= 0.05) { s_LastPushTime = currentTime; @@ -71,141 +70,234 @@ void PerformanceWindow::Show(float fps, float ms) PushValueToHistory(s_TriangleHistory, 60, (float)m_TriangleCount); } - // 3) Every 1 second, recalculate the max scale for FPS and ms + // We'll calculate a fallback max for calls + static float callsMax = 300.0f; + + // 3) Every 1 second, recalculate the max scale for FPS, ms, and calls if ((currentTime - s_LastScaleUpdate) >= 1.0) { s_LastScaleUpdate = currentTime; - // Find the maximum in s_FpsHistory + // Recompute s_FpsScale float maxFps = 0.0f; for (int i = 0; i < 60; i++) - { - if (s_FpsHistory[i] > maxFps) - maxFps = s_FpsHistory[i]; - } - // Scale it by +15%, ensure it's not below 1.0 - maxFps *= 1.15f; - if (maxFps < 1.0f) - maxFps = 1.0f; + maxFps = std::max(maxFps, s_FpsHistory[i]); + maxFps = std::max(1.0f, maxFps * 1.15f); // add ~15% headroom s_FpsScale = maxFps; - // Find the maximum in s_MsHistory + // Recompute s_MsScale float maxMs = 0.0f; for (int i = 0; i < 60; i++) - { - if (s_MsHistory[i] > maxMs) - maxMs = s_MsHistory[i]; - } - // Scale it by +15%, ensure it's not below 1.0 - maxMs *= 1.15f; - if (maxMs < 1.0f) - maxMs = 1.0f; + maxMs = std::max(maxMs, s_MsHistory[i]); + maxMs = std::max(1.0f, maxMs * 1.15f); s_MsScale = maxMs; + + // Recompute callsMax + float localMax = 0.0f; + for (int i = 0; i < 60; i++) + localMax = std::max(localMax, s_CallsHistory[i]); + if (localMax > 0.0f) + callsMax = localMax * 1.2f; // 20% headroom + else + callsMax = 300.0f; // default } // Optional style adjustments ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(10, 10)); + + // Optionally make the window auto-resize or set constraints: + // ImGui::SetNextWindowSizeConstraints(ImVec2(350, 300), ImVec2(FLT_MAX, FLT_MAX)); + // ImGui::Begin("Performance##performance", nullptr, ImGuiWindowFlags_AlwaysAutoResize); + ImGui::Begin("Performance##performance"); - // Colored header + // A color-coded main header ImGui::TextColored(ImVec4(1.0f, 0.8f, 0.2f, 1.0f), "Performance Stats"); ImGui::Separator(); + ImGui::Spacing(); - // Show current FPS/ms - ImGui::TextColored(ImVec4(0.4f, 1.0f, 0.4f, 1.0f), "FPS: %.1f", fps); - ImGui::SameLine(); - ImGui::TextColored(ImVec4(0.4f, 1.0f, 0.8f, 1.0f), "| ms: %.3f", ms); - - // Graphs for FPS + MS - // min = 0, max = s_FpsScale or s_MsScale - ImGui::PlotLines("FPS", - s_FpsHistory, - IM_ARRAYSIZE(s_FpsHistory), - 0, - nullptr, - 0.0f, - s_FpsScale, - ImVec2(0, 60)); - - ImGui::PlotHistogram("ms/frame", - s_MsHistory, - IM_ARRAYSIZE(s_MsHistory), - 0, - nullptr, - 0.0f, - s_MsScale, - ImVec2(0, 60)); - - ImGui::Separator(); - - // Show OpenGL calls + Triangles - ImGui::Text("OpenGL Calls: %d", m_OpenGLCallCount); - ImGui::PlotLines("GL Calls", - s_CallsHistory, - IM_ARRAYSIZE(s_CallsHistory), - 0, - nullptr, - 0.0f, - 300.0f, - ImVec2(0, 50)); - - ImGui::Text("Indices: %d", m_TriangleCount); - ImGui::PlotHistogram("Indices", - s_TriangleHistory, - IM_ARRAYSIZE(s_TriangleHistory), - 0, - nullptr, - 0.0f, - m_TriangleCount * 2.5, - ImVec2(0, 50)); - - ImGui::Separator(); - - // Show asset count - ImGui::TextColored(ImVec4(0.4f, 1.0f, 0.4f, 1.0f), "Assets: %d", LoadedAssets); - - ImGui::Separator(); - - const char *options[] = {"Bootsrap", "Duck Red", "Windark", "Deep Dark", "Tesseract Black"}; - static int current_option = -1; // No selection initially - - const char *preview_value = (current_option >= 0 && current_option < 3) ? options[current_option] : "Select an option"; - if (ImGui::BeginCombo("Theme", preview_value)) + // Show current FPS / ms in color { - for (int n = 0; n < IM_ARRAYSIZE(options); n++) + // Line 1: FPS (green) + MS (teal) + ImGui::TextColored(ImVec4(0.4f, 1.0f, 0.4f, 1.0f), "FPS: %.1f", fps); + ImGui::SameLine(); + ImGui::TextColored(ImVec4(0.4f, 1.0f, 0.8f, 1.0f), "| ms: %.3f", ms); + } + + // Collapsible header for the performance graphs + if (ImGui::CollapsingHeader("Performance Graphs", ImGuiTreeNodeFlags_DefaultOpen)) + { + ImGui::Spacing(); + + // --------- FPS & MS side by side ---------- + ImGui::BeginGroup(); // left group { - bool is_selected = (current_option == n); - if (ImGui::Selectable(options[n], is_selected)) + ImGui::Text("FPS"); + ImGui::PushStyleColor(ImGuiCol_PlotLines, IM_COL32(0, 255, 0, 255)); // green + ImGui::PushStyleColor(ImGuiCol_PlotLinesHovered, IM_COL32(255, 0, 0, 255)); // red on hover + + ImGui::PlotLines("##FPS", // hidden label + s_FpsHistory, IM_ARRAYSIZE(s_FpsHistory), + 0, // offset + nullptr, // overlay + 0.0f, + s_FpsScale, + ImVec2(150, 60)); + + if (ImGui::IsItemHovered()) { - current_option = n; // Update current option - ThemeManager_ChangeTheme(n); // Call the function with the selected option + ImGui::BeginTooltip(); + ImGui::Text("Frames per second over time"); + ImGui::EndTooltip(); } - // Set the initial focus when opening the combo (optional) - if (is_selected) - ImGui::SetItemDefaultFocus(); + + ImGui::PopStyleColor(2); } - ImGui::EndCombo(); - } - if (ImGui::Combo("Polygon Mode", &polygonMode, polygonModeOptions, numPolygonModes)) - { - switch (polygonMode) + ImGui::EndGroup(); + + ImGui::SameLine(); + + ImGui::BeginGroup(); // right group { - case 0: - glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); - break; - case 1: - glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); - break; - case 2: - glPolygonMode(GL_FRONT_AND_BACK, GL_POINT); - break; - default: - glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); - break; + ImGui::Text("ms/frame"); + ImGui::PlotHistogram("##ms/frame", + s_MsHistory, + IM_ARRAYSIZE(s_MsHistory), + 0, + nullptr, + 0.0f, + s_MsScale, + ImVec2(150, 60)); + + if (ImGui::IsItemHovered()) + { + ImGui::BeginTooltip(); + ImGui::Text("Frame time in milliseconds"); + ImGui::EndTooltip(); + } + } + ImGui::EndGroup(); + + ImGui::Spacing(); + ImGui::Separator(); + ImGui::Spacing(); + + // --------- OpenGL Calls & Indices ----------- + // 1) GL Calls + ImGui::Text("OpenGL Calls: %d", m_OpenGLCallCount); + + ImGui::PlotLines("##GL Calls", + s_CallsHistory, + IM_ARRAYSIZE(s_CallsHistory), + 0, // offset + nullptr, // overlay text + 0.0f, + callsMax, + ImVec2(-1, 50)); // auto-fill width, fixed height=50 + + if (ImGui::IsItemHovered()) + { + ImGui::BeginTooltip(); + ImGui::Text("Number of GL draw calls per frame"); + ImGui::EndTooltip(); + } + + ImGui::Spacing(); + + // 2) Indices + ImGui::Text("Indices: %d", m_TriangleCount); + + // For Indices, we use a histogram. We could also do dynamic range like callsMax. + float indexHistMax = std::max(1.0f, (float)m_TriangleCount * 2.5f); + + ImGui::PlotHistogram("##Indices", + s_TriangleHistory, + IM_ARRAYSIZE(s_TriangleHistory), + 0, + nullptr, + 0.0f, + indexHistMax, + ImVec2(-1, 50)); // auto-size width, height=50 + + if (ImGui::IsItemHovered()) + { + ImGui::BeginTooltip(); + ImGui::Text("Number of indices being rendered per frame"); + ImGui::EndTooltip(); } } - ImGui::End(); + ImGui::Spacing(); + ImGui::Separator(); + ImGui::Spacing(); + // -------------- Asset Count -------------- + { + ImGui::Text("Loaded Assets: "); + ImGui::SameLine(); + ImGui::TextColored(ImVec4(0.4f, 1.0f, 0.4f, 1.0f), "%d", LoadedAssets); + } + + ImGui::Spacing(); + ImGui::Separator(); + ImGui::Spacing(); + + // -------------- Theme Combo Box -------------- + { + const char* options[] = {"Bootstrap", "Duck Red", "Windark", "Deep Dark", "Tesseract Black"}; + static int current_option = -1; + const char* preview_value = (current_option >= 0 && current_option < IM_ARRAYSIZE(options)) + ? options[current_option] + : "Select an option"; + + ImGui::Text("Theme: "); + ImGui::SameLine(); + if (ImGui::BeginCombo("##ThemeCombo", preview_value)) + { + for (int n = 0; n < IM_ARRAYSIZE(options); n++) + { + bool is_selected = (current_option == n); + if (ImGui::Selectable(options[n], is_selected)) + { + current_option = n; + // Switch theme + ThemeManager_ChangeTheme(n); + } + if (is_selected) + ImGui::SetItemDefaultFocus(); + } + ImGui::EndCombo(); + } + } + + ImGui::Spacing(); + + // -------------- Polygon Mode Combo Box -------------- + { + ImGui::Text("Polygon Mode: "); + ImGui::SameLine(); + if (ImGui::Combo("##PolygonMode", &polygonMode, polygonModeOptions, IM_ARRAYSIZE(polygonModeOptions))) + { + switch (polygonMode) + { + case 0: // Fill + glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); + break; + case 1: // Wireframe + glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); + break; + case 2: // Points + glPolygonMode(GL_FRONT_AND_BACK, GL_POINT); + break; + default: + glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); + break; + } + } + } + + ImGui::End(); // End of "Performance##performance" + + // Pop the style var ImGui::PopStyleVar(); } diff --git a/src/Windows/RenderWindow.cpp b/src/Windows/RenderWindow.cpp index 1c30aee..de3773f 100644 --- a/src/Windows/RenderWindow.cpp +++ b/src/Windows/RenderWindow.cpp @@ -8,6 +8,8 @@ #include #include "imgui.h" +#include "Engine/Settings.h" + #include "gcml.h" #include "Componenets/GameObject.h" @@ -28,12 +30,126 @@ extern std::vector> g_GameObjects; // Extern reference to our global (or extern) asset manager extern AssetManager g_AssetManager; +extern Settings g_SettingsManager; extern std::shared_ptr g_RuntimeCameraObject; extern int g_GPU_Triangles_drawn_to_screen; extern int g_GPU_Draw_Calls; +extern bool DrawBBBO; + +// Simple container for six planes +struct FrustumPlanes +{ + glm::vec4 planes[6]; // Each plane is (A,B,C,D) +}; + +// Extract 6 planes from the combined view-projection matrix +inline FrustumPlanes ExtractFrustumPlanes(const glm::mat4 &vp) +{ + FrustumPlanes frustum; + + // Left + frustum.planes[0] = glm::vec4( + vp[0][3] + vp[0][0], + vp[1][3] + vp[1][0], + vp[2][3] + vp[2][0], + vp[3][3] + vp[3][0]); + + // Right + frustum.planes[1] = glm::vec4( + vp[0][3] - vp[0][0], + vp[1][3] - vp[1][0], + vp[2][3] - vp[2][0], + vp[3][3] - vp[3][0]); + + // Bottom + frustum.planes[2] = glm::vec4( + vp[0][3] + vp[0][1], + vp[1][3] + vp[1][1], + vp[2][3] + vp[2][1], + vp[3][3] + vp[3][1]); + + // Top + frustum.planes[3] = glm::vec4( + vp[0][3] - vp[0][1], + vp[1][3] - vp[1][1], + vp[2][3] - vp[2][1], + vp[3][3] - vp[3][1]); + + // Near + frustum.planes[4] = glm::vec4( + vp[0][3] + vp[0][2], + vp[1][3] + vp[1][2], + vp[2][3] + vp[2][2], + vp[3][3] + vp[3][2]); + + // Far + frustum.planes[5] = glm::vec4( + vp[0][3] - vp[0][2], + vp[1][3] - vp[1][2], + vp[2][3] - vp[2][2], + vp[3][3] - vp[3][2]); + + // Normalize planes (optional but recommended) + for (int i = 0; i < 6; i++) + { + float length = glm::length(glm::vec3( + frustum.planes[i].x, + frustum.planes[i].y, + frustum.planes[i].z)); + if (length > 0.0f) + { + frustum.planes[i] /= length; + } + } + + return frustum; +} + +inline bool IsBoxInFrustum(const glm::vec3 &bmin, + const glm::vec3 &bmax, + const FrustumPlanes &frustum) +{ + // Build the 8 corners in world space + // (We assume bmin/bmax are already in world space.) + std::array corners = { + glm::vec3(bmin.x, bmin.y, bmin.z), + glm::vec3(bmin.x, bmin.y, bmax.z), + glm::vec3(bmin.x, bmax.y, bmin.z), + glm::vec3(bmin.x, bmax.y, bmax.z), + glm::vec3(bmax.x, bmin.y, bmin.z), + glm::vec3(bmax.x, bmin.y, bmax.z), + glm::vec3(bmax.x, bmax.y, bmin.z), + glm::vec3(bmax.x, bmax.y, bmax.z)}; + + // For each plane, check if all corners are behind it. + // If yes => box is completely outside. + for (int p = 0; p < 6; p++) + { + const glm::vec4 &plane = frustum.planes[p]; + int outCount = 0; + + for (const auto &corner : corners) + { + // Plane eq: A*x + B*y + C*z + D + float distance = plane.x * corner.x + + plane.y * corner.y + + plane.z * corner.z + + plane.w; + + if (distance < 0.0f) + outCount++; + } + + // If all corners are 'behind' the plane, box is outside + if (outCount == 8) + return false; + } + // Otherwise, it's at least partially inside + return true; +} bool PlayPauseButton(const char *label, bool *isPlaying, ImVec2 Size) { @@ -167,6 +283,17 @@ void RenderWindow::InitGLResources() m_ShaderPtr = shaderAsset.get(); } + { + std::shared_ptr shaderAsset = g_AssetManager.loadAsset(AssetType::SHADER, "assets/shaders/Line"); + if (!shaderAsset) + { + fprintf(stderr, "[RenderWindow] Failed to load shader via AssetManager.\n"); + return; + } + // Cast back to your Shader class + m_LineShaderPtr = shaderAsset.get(); + } + // ---------------------------------------------------- // 3) Load TEXTURE from the asset manager // ---------------------------------------------------- @@ -205,12 +332,11 @@ void CheckOpenGLError(const std::string &location) #include // For glm::value_ptr #include // Ensure is included - void RenderWindow::RenderSceneToFBO(bool *GameRunning) { m_RotationAngle += 0.001f; // Spin per frame - // Bind the FBO + // 1) Bind the FBO and set up viewport m_FBO.Bind(); glViewport(0, 0, m_LastWidth, m_LastHeight); @@ -224,135 +350,188 @@ void RenderWindow::RenderSceneToFBO(bool *GameRunning) glClearColor(0.f, 0.f, 0.f, 1.f); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); - // Use our loaded shader + // 2) Check our main rendering shader if (!m_ShaderPtr) { DEBUG_PRINT("[RenderWindow] Shader pointer is null. Cannot render."); m_FBO.Unbind(); - return; // Can't render without a shader + return; } - m_ShaderPtr->Use(); - // Define view and projection matrices once + // 3) Obtain view/projection from the active camera (or fallback) std::shared_ptr activeCamera = nullptr; - - glm::mat4 view; - glm::mat4 proj; + glm::mat4 view, proj; if (*GameRunning && g_RuntimeCameraObject) { activeCamera = g_RuntimeCameraObject; } - // Ensure that an active camera is available if (activeCamera) { - // Obtain view and projection matrices from the active camera view = activeCamera->GetViewMatrix(); proj = activeCamera->GetProjectionMatrix(); } else { - // Fallback to default view and projection if no camera is available + // Fallback if no camera view = glm::translate(glm::mat4(1.f), glm::vec3(0.f, 0.f, -5.f)); float aspect = (m_LastHeight != 0) ? (float)m_LastWidth / (float)m_LastHeight : 1.0f; proj = glm::perspective(glm::radians(CAM_FOV), aspect, CAM_NEAR_PLAIN, CAM_FAR_PLAIN); } - // Iterate over each GameObject and render it + // 4) Extract frustum planes for culling + glm::mat4 vp = proj * view; + FrustumPlanes frustum = ExtractFrustumPlanes(vp); + + // 5) Iterate over each GameObject and render + glm::vec4 LineColor = g_SettingsManager.S_LineColor; + for (auto &obj : g_GameObjects) { glm::mat4 model = glm::mat4(1.f); - std::shared_ptr transform = obj->GetComponent(); - std::shared_ptr mesh = obj->GetComponent(); + auto transform = obj->GetComponent(); + auto mesh = obj->GetComponent(); - if (transform && mesh && mesh) + if (transform && mesh) { - // Apply transformations + // 5A) Build the Model matrix model = glm::translate(model, transform->position); model = glm::rotate(model, glm::radians(transform->rotation.x), glm::vec3(1.f, 0.f, 0.f)); model = glm::rotate(model, glm::radians(transform->rotation.y), glm::vec3(0.f, 1.f, 0.f)); model = glm::rotate(model, glm::radians(transform->rotation.z), glm::vec3(0.f, 0.f, 1.f)); model = glm::scale(model, transform->scale); - // Compute MVP matrix - glm::mat4 mvp = proj * view * model; // why is this the wrong way? - - // Pass MVP and Model matrices to the shader - m_ShaderPtr->SetMat4("uMVP", mvp); - m_ShaderPtr->SetMat4("uModel", model); - - // Iterate through each submesh - for (const auto &submesh : mesh->submeshes) + // 5B) For each submesh + for (auto &submesh : mesh->submeshes) { - // Validate VAO + // Check if VAO is valid if (submesh.vao == 0) { DEBUG_PRINT("[RenderWindow] Warning: Submesh VAO is not initialized."); continue; } - // Update triangle count + // ------------------------- + // *** FRUSTUM CULLING *** + // Transform submesh bounding box from local -> world space + glm::vec3 worldMin(FLT_MAX), worldMax(-FLT_MAX); + std::array corners = { + glm::vec3(submesh.minExtents.x, submesh.minExtents.y, submesh.minExtents.z), + glm::vec3(submesh.minExtents.x, submesh.minExtents.y, submesh.maxExtents.z), + glm::vec3(submesh.minExtents.x, submesh.maxExtents.y, submesh.minExtents.z), + glm::vec3(submesh.minExtents.x, submesh.maxExtents.y, submesh.maxExtents.z), + glm::vec3(submesh.maxExtents.x, submesh.minExtents.y, submesh.minExtents.z), + glm::vec3(submesh.maxExtents.x, submesh.minExtents.y, submesh.maxExtents.z), + glm::vec3(submesh.maxExtents.x, submesh.maxExtents.y, submesh.minExtents.z), + glm::vec3(submesh.maxExtents.x, submesh.maxExtents.y, submesh.maxExtents.z)}; + + for (auto &c : corners) + { + // Transform corner by the model matrix + glm::vec4 worldPos = model * glm::vec4(c, 1.0f); + + // Expand worldMin / worldMax + worldMin.x = std::min(worldMin.x, worldPos.x); + worldMin.y = std::min(worldMin.y, worldPos.y); + worldMin.z = std::min(worldMin.z, worldPos.z); + + worldMax.x = std::max(worldMax.x, worldPos.x); + worldMax.y = std::max(worldMax.y, worldPos.y); + worldMax.z = std::max(worldMax.z, worldPos.z); + } + + // Now test that box against frustum + if (!IsBoxInFrustum(worldMin, worldMax, frustum)) + { + // The submesh is completely outside the camera's view => skip + continue; + } + // ------------------------- + + // 5C) Compute MVP and pass to shader + glm::mat4 mvp = vp * model; + m_ShaderPtr->SetMat4("uMVP", mvp); + m_ShaderPtr->SetMat4("uModel", model); + + // 5D) Update tri count g_GPU_Triangles_drawn_to_screen += static_cast(submesh.indices.size() / 3); - // Bind textures for the submesh - // Assuming the shader has uniform arrays like uTextures.texture_diffuse[32] - const int MAX_DIFFUSE = 32; // Must match the shader's MAX_DIFFUSE + // 5E) Bind textures + const int MAX_DIFFUSE = 32; int textureUnit = 0; - - // Iterate through all textures and bind those with type "texture_diffuse" - for (const auto &texture : submesh.textures) + for (auto &texture : submesh.textures) { if (texture.type == "texture_diffuse") { if (textureUnit >= MAX_DIFFUSE) { - DEBUG_PRINT("[RenderWindow] Warning: Exceeded maximum number of diffuse textures (%d) for shader.", MAX_DIFFUSE); - break; // Prevent exceeding the array bounds in the shader + DEBUG_PRINT("[RenderWindow] Warning: Exceeded maximum diffuse textures."); + break; } - - // Activate the appropriate texture unit glActiveTexture(GL_TEXTURE0 + textureUnit); glBindTexture(GL_TEXTURE_2D, texture.id); - // Construct the uniform name dynamically (e.g., "uTextures.texture_diffuse[0]") std::string uniformName = "uTextures.texture_diffuse[" + std::to_string(textureUnit) + "]"; m_ShaderPtr->SetInt(uniformName, textureUnit); textureUnit++; } } - - // Assign default texture to unused texture slots to prevent shader errors + // Assign default texture to unused slots for (int i = textureUnit; i < MAX_DIFFUSE; ++i) { std::string uniformName = "uTextures.texture_diffuse[" + std::to_string(i) + "]"; - m_ShaderPtr->SetInt(uniformName, 0); // Assign texture unit 0 (ensure texture 0 is a valid default) + m_ShaderPtr->SetInt(uniformName, 0); } - - // Set the number of active diffuse textures m_ShaderPtr->SetInt("uNumDiffuseTextures", textureUnit); - // Draw the submesh + // 5F) Draw submesh (filled) glBindVertexArray(submesh.vao); - glDrawElements(GL_TRIANGLES, static_cast(submesh.indices.size()), GL_UNSIGNED_INT, nullptr); - g_GPU_Draw_Calls += 1; + glDrawElements(GL_TRIANGLES, + static_cast(submesh.indices.size()), + GL_UNSIGNED_INT, + nullptr); + g_GPU_Draw_Calls++; glBindVertexArray(0); - - // Reset active texture to default glActiveTexture(GL_TEXTURE0); + + // ---------------------------------------- + // 5G) OPTIONAL: Draw a bounding box outline + // ---------------------------------------- + if (DrawBBBO && m_LineShaderPtr && submesh.bboxVAO != 0) + { + // Use a simpler line shader that doesn't rely on textures + m_LineShaderPtr->Use(); + + // Recompute the MVP for the bounding box (same model, same vp) + glm::mat4 bboxMVP = vp * model; + m_LineShaderPtr->SetMat4("uMVP", bboxMVP); + m_LineShaderPtr->SetVec4("uColor", LineColor); + glLineWidth(2.0f); + + // If your line shader has a uniform color: + // m_LineShaderPtr->SetVec4("uColor", glm::vec4(1,1,0,1)); // e.g., yellow + + // Draw the bounding box in wireframe lines + glBindVertexArray(submesh.bboxVAO); + // We assume submesh.bboxVertexCount is the number of line-vertices + glDrawArrays(GL_LINES, 0, submesh.bboxVertexCount); + + glBindVertexArray(0); + glUseProgram(0); + } } } } #if TRANSPERANCY - glDisable(GL_BLEND); #endif glUseProgram(0); - // Unbind the FBO + // 6) Unbind the FBO m_FBO.Unbind(); } diff --git a/src/Windows/RenderWindow.h b/src/Windows/RenderWindow.h index c821583..45b947e 100644 --- a/src/Windows/RenderWindow.h +++ b/src/Windows/RenderWindow.h @@ -35,4 +35,13 @@ private: // The loaded shader program (via AssetManager) Shader* m_ShaderPtr = nullptr; + Shader* m_LineShaderPtr = nullptr; }; + + +struct Ray +{ + glm::vec3 origin; + glm::vec3 direction; // Should be normalized +}; +