// RenderWindow.cpp #include "RenderWindow.h" #include // Added as per your inclusion #include // Ensure GLEW is initialized before using OpenGL functions #include #include #include "imgui.h" #include "gcml.h" #include "Componenets/GameObject.h" // Corrected typo: "Componenets" -> "Components" #include "Componenets/Mesh.h" // Corrected typo: "mesh.h" -> "Mesh.h" #include "Componenets/Transform.h" // Corrected typo: "transform.h" -> "Transform.h" #include "Engine/AssetManager.h" #include "Icons.h" // External References extern std::vector> g_GameObjects; extern AssetManager g_AssetManager; extern std::shared_ptr g_RuntimeCameraObject; extern int g_GPU_Triangles_drawn_to_screen; // Example cube data (position + UVs) static float g_CubeVertices[] = { // Positions // UVs // Front Face -1.f, -1.f, 1.f, 0.f, 0.f, -1.f, 1.f, 1.f, 0.f, 1.f, 1.f, 1.f, 1.f, 1.f, 1.f, 1.f, -1.f, 1.f, 1.f, 0.f, // Back Face -1.f, -1.f, -1.f, 1.f, 0.f, -1.f, 1.f, -1.f, 1.f, 1.f, 1.f, 1.f, -1.f, 0.f, 1.f, 1.f, -1.f, -1.f, 0.f, 0.f, // Left Face -1.f, -1.f, -1.f, 0.f, 0.f, -1.f, 1.f, -1.f, 0.f, 1.f, -1.f, 1.f, 1.f, 1.f, 1.f, -1.f, -1.f, 1.f, 1.f, 0.f, // Right Face 1.f, -1.f, -1.f, 1.f, 0.f, 1.f, 1.f, -1.f, 1.f, 1.f, 1.f, 1.f, 1.f, 0.f, 1.f, 1.f, -1.f, 1.f, 0.f, 0.f, // Top Face -1.f, 1.f, -1.f, 0.f, 1.f, -1.f, 1.f, 1.f, 0.f, 0.f, 1.f, 1.f, 1.f, 1.f, 0.f, 1.f, 1.f, -1.f, 1.f, 1.f, // Bottom Face -1.f, -1.f, -1.f, 1.f, 1.f, -1.f, -1.f, 1.f, 1.f, 0.f, 1.f, -1.f, 1.f, 0.f, 0.f, 1.f, -1.f, -1.f, 0.f, 1.f, }; static unsigned int g_CubeIndices[] = { // Front 0, 1, 2, 2, 3, 0, // Back 4, 5, 6, 6, 7, 4, // Left 8, 9,10, 10,11, 8, // Right 12,13,14, 14,15,12, // Top 16,17,18, 18,19,16, // Bottom 20,21,22, 22,23,20 }; // Play/Pause Button Implementation bool PlayPauseButton(const char* label, bool* isPlaying, ImVec2 Size) { // Begin the button if (ImGui::Button(label, Size)) { // Toggle the state *isPlaying = !(*isPlaying); return true; // Indicate that the state was toggled } // Add tooltip if (ImGui::IsItemHovered()) { ImGui::SetTooltip(*isPlaying ? "Pause (Space)" : "Play (Space)"); } // Get the current window's draw list ImDrawList* draw_list = ImGui::GetWindowDrawList(); // Get the position of the button ImVec2 button_pos = ImGui::GetItemRectMin(); ImVec2 button_size = ImGui::GetItemRectSize(); ImVec2 center = ImVec2(button_pos.x + button_size.x * 0.5f, button_pos.y + button_size.y * 0.5f); // Define icon size float icon_size = 0.4f * Size.x; float half_icon_size = icon_size / 2.0f; // Define colors ImU32 icon_color = ImGui::GetColorU32(ImGuiCol_Text); if (*isPlaying) { // Draw Pause Icon (two vertical bars) float bar_width = 4.0f; float spacing = 0.1f * Size.x; // Left bar ImVec2 left_bar_p1 = ImVec2(center.x - spacing - bar_width, center.y - half_icon_size); ImVec2 left_bar_p2 = ImVec2(center.x - spacing, center.y + half_icon_size); draw_list->AddRectFilled(left_bar_p1, left_bar_p2, icon_color, 2.0f); // Right bar ImVec2 right_bar_p1 = ImVec2(center.x + spacing, center.y - half_icon_size); ImVec2 right_bar_p2 = ImVec2(center.x + spacing + bar_width, center.y + half_icon_size); draw_list->AddRectFilled(right_bar_p1, right_bar_p2, icon_color, 2.0f); } else { // Draw Play Icon (triangle) ImVec2 p1 = ImVec2(center.x - half_icon_size, center.y - half_icon_size); ImVec2 p2 = ImVec2(center.x - half_icon_size, center.y + half_icon_size); ImVec2 p3 = ImVec2(center.x + half_icon_size, center.y); draw_list->AddTriangleFilled(p1, p2, p3, icon_color); } return false; // No toggle occurred } // Constructor RenderWindow::RenderWindow() { } // Destructor RenderWindow::~RenderWindow() { // Delete main FBO if (m_FBO != 0) { glDeleteFramebuffers(1, &m_FBO); } // Delete shadow FBO and shadow map if (m_ShadowFBO != 0) { glDeleteFramebuffers(1, &m_ShadowFBO); } if (m_ShadowMap) { glDeleteTextures(1, &m_ShadowMap); } // Delete VAO, VBO, EBO if (m_VAO != 0) glDeleteVertexArrays(1, &m_VAO); if (m_VBO != 0) glDeleteBuffers(1, &m_VBO); if (m_EBO != 0) glDeleteBuffers(1, &m_EBO); // Delete quad VAO and VBO if (m_QuadVAO != 0) glDeleteVertexArrays(1, &m_QuadVAO); if (m_QuadVBO != 0) glDeleteBuffers(1, &m_QuadVBO); // Delete textures if (m_TextureIDLoaded != 0) glDeleteTextures(1, &m_TextureIDLoaded); if (m_TextureID != 0) glDeleteTextures(1, &m_TextureID); // Delete shaders if (m_ShaderPtr) { delete m_ShaderPtr; m_ShaderPtr = nullptr; } if (m_ShadowShaderPtr) { delete m_ShadowShaderPtr; m_ShadowShaderPtr = nullptr; } if (m_VisualizeShaderPtr) { delete m_VisualizeShaderPtr; m_VisualizeShaderPtr = nullptr; } } // Example implementation of RenderShadowMapPreview void RenderWindow::RenderShadowMapPreview() { // Begin ImGui window ImGui::Begin("Shadow Map Preview"); // Light Camera Controls ImGui::Text("Light Camera Controls"); ImGui::Separator(); // Position Controls ImGui::DragFloat3("Position", glm::value_ptr(m_LightPosition), 0.1f, -20.0f, 20.0f); // Rotation Controls (Euler angles in degrees) ImGui::DragFloat3("Rotation", glm::value_ptr(m_LightRotation), 1.0f, -180.0f, 180.0f); // Update Light View Matrix based on Position and Rotation // Convert Euler angles to radians glm::vec3 rotationRad = glm::radians(m_LightRotation); // Create rotation matrices glm::mat4 rotX = glm::rotate(glm::mat4(1.0f), rotationRad.x, glm::vec3(1.0f, 0.0f, 0.0f)); glm::mat4 rotY = glm::rotate(glm::mat4(1.0f), rotationRad.y, glm::vec3(0.0f, 1.0f, 0.0f)); glm::mat4 rotZ = glm::rotate(glm::mat4(1.0f), rotationRad.z, glm::vec3(0.0f, 0.0f, 1.0f)); // Combined rotation glm::mat4 rotation = rotZ * rotY * rotX; // Update Light View Matrix m_LightViewMatrix = glm::lookAt(m_LightPosition, glm::vec3(0.0f), glm::vec3(0.0f, 1.0f, 0.0f)) * rotation; // Shadow Map Rendering if (1) { // Define the size of the preview image ImVec2 imageSize = ImVec2(256, 256); // Adjust as needed // Configure OpenGL state for rendering the quad glDisable(GL_DEPTH_TEST); // Disable depth test so quad renders over everything m_VisualizeShaderPtr->Use(); m_VisualizeShaderPtr->SetInt("depthMap", 0); // Texture unit 0 // Bind the depth map texture glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D, m_ShadowMap); // Render the quad glBindVertexArray(m_QuadVAO); glDrawArrays(GL_TRIANGLES, 0, 6); glBindVertexArray(0); // Re-enable depth testing glEnable(GL_DEPTH_TEST); // Display the shadow map texture in ImGui ImGui::Image((intptr_t)m_ShadowMap, imageSize, ImVec2(0, 1), ImVec2(1, 0)); } else { ImGui::Text("Shadow map not available."); } ImGui::End(); } void RenderWindow::Show(bool* GameRunning) { ImGui::Begin(ICON_FA_GAMEPAD " Editor##EditorWindow"); if (!m_Initialized) { InitGLResources(); m_LightProjMatrix = glm::ortho(-10.0f, 10.0f, -10.0f, 10.0f, 1.0f, 20.0f); m_LightViewMatrix = glm::mat4(1.0f); // Will be updated based on position and rotation m_Initialized = true; } ImVec2 size = ImGui::GetContentRegionAvail(); int w = static_cast(size.x); int h = static_cast(size.y); // If there's space, render to the FBO, then show it as an ImGui image if (w > 0 && h > 0) { if (w != m_LastWidth || h != m_LastHeight) { // Since m_FBO is a GLuint, you cannot call Create() on it directly. // Instead, you need to delete the existing FBO and recreate it with the new size. // Delete existing FBO and its attachments if (m_FBO != 0) { glDeleteFramebuffers(1, &m_FBO); glDeleteTextures(1, &m_TextureID); } // Recreate the main FBO with the new size glGenFramebuffers(1, &m_FBO); glBindFramebuffer(GL_FRAMEBUFFER, m_FBO); // Create a new color attachment texture glGenTextures(1, &m_TextureID); glBindTexture(GL_TEXTURE_2D, m_TextureID); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, w, h, 0, GL_RGB, GL_UNSIGNED_BYTE, nullptr); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, m_TextureID, 0); // Create a renderbuffer object for depth and stencil attachment GLuint rbo; glGenRenderbuffers(1, &rbo); glBindRenderbuffer(GL_RENDERBUFFER, rbo); glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, w, h); glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER, rbo); // Check if framebuffer is complete if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) std::cerr << "[RenderWindow] Main FBO is not complete after resizing." << std::endl; glBindFramebuffer(GL_FRAMEBUFFER, 0); m_LastWidth = w; m_LastHeight = h; } // Render the scene to the FBO with shadow mapping RenderSceneToFBO(GameRunning); // Display the main FBO's color texture in ImGui ImGui::Image((intptr_t)m_TextureID, size, ImVec2(0, 0), ImVec2(1, 1)); // Calculate button position to place it slightly right and down from the top-left of the image ImVec2 imagePos = ImGui::GetItemRectMin(); // Add an offset to position the button ImVec2 buttonOffset(10.0f, 10.0f); // Adjust these values as needed for the desired offset ImVec2 buttonPos = ImVec2(imagePos.x + buttonOffset.x, imagePos.y + buttonOffset.y); // Set cursor position for the button ImGui::SetCursorScreenPos(buttonPos); // Dynamically calculate button size based on window size float buttonWidth = size.x * 0.03f; // 3% of the window width ImVec2 buttonSize = ImVec2(buttonWidth, buttonWidth); // Render the Play/Pause button with the calculated size PlayPauseButton("##PlayPauseButton", GameRunning, buttonSize); } ImGui::End(); RenderShadowMapPreview(); } void RenderWindow::InitGLResources() { // ---------------------------------------------------- // 1) Load SHADER from the asset manager // ---------------------------------------------------- { std::shared_ptr shaderAsset = g_AssetManager.loadAsset(AssetType::SHADER, "assets/shaders/UnlitMaterial"); if (!shaderAsset) { fprintf(stderr, "[RenderWindow] Failed to load shader via AssetManager.\n"); return; } // Cast back to your Shader class m_ShaderPtr = shaderAsset.get(); } { std::shared_ptr shaderAsset = g_AssetManager.loadAsset(AssetType::SHADER, "assets/shaders/Depth"); if (!shaderAsset) { fprintf(stderr, "[RenderWindow] Failed to load shadow shader via AssetManager.\n"); return; } // Cast back to your Shader class m_ShadowShaderPtr = shaderAsset.get(); } { std::shared_ptr shaderAsset = g_AssetManager.loadAsset(AssetType::SHADER, "assets/shaders/DepthVisualize"); if (!shaderAsset) { fprintf(stderr, "[RenderWindow] Failed to load visualization shader via AssetManager.\n"); return; } // Cast back to your Shader class m_VisualizeShaderPtr = shaderAsset.get(); } // ---------------------------------------------------- // 2) Create VAO/VBO/EBO for the cube // ---------------------------------------------------- glGenVertexArrays(1, &m_VAO); glBindVertexArray(m_VAO); glGenBuffers(1, &m_VBO); glBindBuffer(GL_ARRAY_BUFFER, m_VBO); glBufferData(GL_ARRAY_BUFFER, sizeof(g_CubeVertices), g_CubeVertices, GL_STATIC_DRAW); glGenBuffers(1, &m_EBO); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_EBO); glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(g_CubeIndices), g_CubeIndices, GL_STATIC_DRAW); // Position = location 0, UV = location 1 glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void*)0); glEnableVertexAttribArray(0); glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void*)(3 * sizeof(float))); glEnableVertexAttribArray(1); glBindVertexArray(0); // ---------------------------------------------------- // 3) Load TEXTURE from the asset manager // ---------------------------------------------------- { std::shared_ptr texAsset = g_AssetManager.loadAsset(AssetType::TEXTURE, "assets/textures/wood.png"); if (!texAsset) { fprintf(stderr, "[RenderWindow] Failed to load texture.\n"); } else { // Cast from shared_ptr to GLuint m_TextureIDLoaded = *texAsset; // Assign the GLuint value } } // ---------------------------------------------------- // 4) Setup Visualization Quad // ---------------------------------------------------- { float quadVertices[] = { // positions // texCoords -1.0f, 1.0f, 0.0f, 1.0f, -1.0f, -1.0f, 0.0f, 0.0f, 1.0f, -1.0f, 1.0f, 0.0f, -1.0f, 1.0f, 0.0f, 1.0f, 1.0f, -1.0f, 1.0f, 0.0f, 1.0f, 1.0f, 1.0f, 1.0f }; glGenVertexArrays(1, &m_QuadVAO); glGenBuffers(1, &m_QuadVBO); glBindVertexArray(m_QuadVAO); glBindBuffer(GL_ARRAY_BUFFER, m_QuadVBO); glBufferData(GL_ARRAY_BUFFER, sizeof(quadVertices), &quadVertices, GL_STATIC_DRAW); glEnableVertexAttribArray(0); glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(float), (void*)0); glEnableVertexAttribArray(1); glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(float), (void*)(2 * sizeof(float))); glBindVertexArray(0); } m_Initialized = true; } void CheckOpenGLError(const std::string& location) { GLenum err; bool hasError = false; while ((err = glGetError()) != GL_NO_ERROR) { std::cerr << "[OpenGL Error] (" << err << ") at " << location << std::endl; hasError = true; } if (hasError) { // Optionally, handle the error (e.g., throw an exception, assert, etc.) } } // RenderWindow.cpp void RenderWindow::RenderSceneToFBO(bool* GameRunning) { if (!m_Initialized) { std::cerr << "[RenderWindow] OpenGL resources not initialized." << std::endl; return; } m_RotationAngle += 0.001f; // Spin per frame // 1. Shadow Pass: Render the scene from the light's perspective to create the shadow map glViewport(0, 0, 1024, 1024); // Shadow map resolution glBindFramebuffer(GL_FRAMEBUFFER, m_ShadowFBO); glClear(GL_DEPTH_BUFFER_BIT); m_ShadowShaderPtr->Use(); m_ShadowShaderPtr->SetMat4("uLightView", m_LightViewMatrix); m_ShadowShaderPtr->SetMat4("uLightProj", m_LightProjMatrix); // Render all objects to the shadow map for (auto& obj : g_GameObjects) { std::shared_ptr transform = obj->GetComponent(); std::shared_ptr mesh = obj->GetComponent(); if (transform && mesh) { glm::mat4 model = glm::mat4(1.0f); model = glm::translate(model, transform->position); model = glm::rotate(model, glm::radians(transform->rotation.x), glm::vec3(1.0f, 0.0f, 0.0f)); model = glm::rotate(model, glm::radians(transform->rotation.y), glm::vec3(0.0f, 1.0f, 0.0f)); model = glm::rotate(model, glm::radians(transform->rotation.z), glm::vec3(0.0f, 0.0f, 1.0f)); model = glm::scale(model, transform->scale); m_ShadowShaderPtr->SetMat4("uModel", model); for (const auto& submesh : mesh->submeshes) { if (submesh.vao == 0) { std::cerr << "[RenderWindow] Warning: Submesh VAO is not initialized." << std::endl; continue; } glBindVertexArray(submesh.vao); glDrawElements(GL_TRIANGLES, static_cast(submesh.indices.size()), GL_UNSIGNED_INT, nullptr); glBindVertexArray(0); } } } glBindFramebuffer(GL_FRAMEBUFFER, 0); // Unbind shadow FBO // 2. Render Pass: Render the scene from the camera's perspective using the shadow map glBindFramebuffer(GL_FRAMEBUFFER, m_FBO); glViewport(0, 0, m_LastWidth, m_LastHeight); glEnable(GL_DEPTH_TEST); glClearColor(0.f, 0.f, 0.f, 1.f); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); m_ShaderPtr->Use(); // Define view and projection matrices std::shared_ptr activeCamera = nullptr; glm::mat4 view; glm::mat4 proj; if (*GameRunning && g_RuntimeCameraObject) { activeCamera = g_RuntimeCameraObject; } if (activeCamera) { view = activeCamera->GetViewMatrix(); proj = activeCamera->GetProjectionMatrix(); } else { view = glm::translate(glm::mat4(1.f), glm::vec3(0.f, 0.f, -5.f)); float aspect = (m_LastHeight != 0) ? static_cast(m_LastWidth) / static_cast(m_LastHeight) : 1.0f; proj = glm::perspective(glm::radians(45.0f), aspect, 0.1f, 100.0f); // Replace with your CAM_FOV, CAM_NEAR_PLAIN, CAM_FAR_PLAIN } // Set uniforms for the main shader glm::mat4 lightSpaceMatrix = m_LightProjMatrix * m_LightViewMatrix; m_ShaderPtr->SetMat4("uLightSpaceMatrix", lightSpaceMatrix); // Bind shadow map texture to texture unit 1 glActiveTexture(GL_TEXTURE1); glBindTexture(GL_TEXTURE_2D, m_ShadowMap); m_ShaderPtr->SetInt("uShadowMap", 1); // Set light parameters glm::vec3 lightDir = glm::normalize(glm::vec3(-2.0f, -4.0f, -1.0f)); m_ShaderPtr->SetVec3("light.direction", lightDir); m_ShaderPtr->SetVec3("light.position", -lightDir * 10.0f); // Same as lightPos in shadow pass // Set camera/view position if (activeCamera && activeCamera->GetOwner()) { auto transformComp = activeCamera->GetOwner()->GetComponent(); if (transformComp) m_ShaderPtr->SetVec3("viewPos", transformComp->position); else m_ShaderPtr->SetVec3("viewPos", glm::vec3(0.f, 0.f, 0.f)); } else { m_ShaderPtr->SetVec3("viewPos", glm::vec3(0.f, 0.f, 0.f)); } // Render all objects for (auto& obj : g_GameObjects) { std::shared_ptr transform = obj->GetComponent(); std::shared_ptr mesh = obj->GetComponent(); if (transform && mesh) { glm::mat4 model = glm::mat4(1.f); 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); glm::mat4 mvp = proj * view * model; m_ShaderPtr->SetMat4("uMVP", mvp); m_ShaderPtr->SetMat4("uModel", model); for (const auto& submesh : mesh->submeshes) { if (submesh.vao == 0) { std::cerr << "[RenderWindow] Warning: Submesh VAO is not initialized." << std::endl; continue; } // Update triangle count g_GPU_Triangles_drawn_to_screen += static_cast(submesh.indices.size() / 3); // Bind diffuse textures const int MAX_DIFFUSE = 32; int textureUnit = 0; for (const auto& texture : submesh.textures) { if (texture.type == "texture_diffuse") { if (textureUnit >= MAX_DIFFUSE) { std::cerr << "[RenderWindow] Warning: Exceeded maximum number of diffuse textures (" << MAX_DIFFUSE << ") for shader." << std::endl; break; } glActiveTexture(GL_TEXTURE0 + textureUnit); glBindTexture(GL_TEXTURE_2D, texture.id); // Assuming texture.id is GLuint std::string uniformName = "uTextures.texture_diffuse[" + std::to_string(textureUnit) + "]"; m_ShaderPtr->SetInt(uniformName, textureUnit); textureUnit++; } } // 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); // Texture unit 0 should have a default texture bound } // Set number of active diffuse textures m_ShaderPtr->SetInt("uNumDiffuseTextures", textureUnit); // Draw the submesh glBindVertexArray(submesh.vao); glDrawElements(GL_TRIANGLES, static_cast(submesh.indices.size()), GL_UNSIGNED_INT, nullptr); glBindVertexArray(0); // Reset active texture glActiveTexture(GL_TEXTURE0); } } } // Unbind shader and framebuffer glUseProgram(0); glBindFramebuffer(GL_FRAMEBUFFER, 0); // 3. Render Shadow Map Preview (if not already rendered in Show) // If you have integrated RenderShadowMapPreview into Show, you might not need to call it here // Otherwise, uncomment the following line: // RenderShadowMapPreview(); }