#include "Engine.h" #include #include #define STB_IMAGE_IMPLEMENTATION #include "stb_image.h" #include "Entity/Entity.h" // Static member definitions. GLFWwindow *Engine::window = nullptr; GLuint Engine::framebuffer = 0; GLuint Engine::colorTexture = 0; GLuint Engine::depthRenderbuffer = 0; GLuint Engine::shaderProgram = 0; GLuint Engine::skyboxShaderProgram = 0; GLuint Engine::skyboxVAO = 0; GLuint Engine::skyboxVBO = 0; GLuint Engine::skyboxEBO = 0; GLuint Engine::skyboxCubemap = 0; float Engine::rotationAngle = 0.0f; int Engine::fbWidth = 640; int Engine::fbHeight = 400; // Global normal map texture (if needed for legacy models; otherwise each model handles its own) GLuint normalMapTexture = 0; unsigned int loadCubemap(const std::vector& faces) { unsigned int textureID; glGenTextures(1, &textureID); glBindTexture(GL_TEXTURE_CUBE_MAP, textureID); // Disable vertical flipping for cubemaps. stbi_set_flip_vertically_on_load(false); int width, height, nrChannels; for (unsigned int i = 0; i < faces.size(); i++) { std::cout << "Loading Cubemap: " << faces[i] << std::endl; unsigned char *data = stbi_load(faces[i].c_str(), &width, &height, &nrChannels, 0); if (data) { GLenum format = GL_RGB; if(nrChannels == 1) format = GL_RED; else if(nrChannels == 3) format = GL_RGB; else if(nrChannels == 4) format = GL_RGBA; glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, format, width, height, 0, format, GL_UNSIGNED_BYTE, data); stbi_image_free(data); } else { std::cout << "Cubemap texture failed to load at path: " << faces[i] << std::endl; stbi_image_free(data); } } // Set texture parameters glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE); return textureID; } bool Engine::Init() { if (!glfwInit()) { std::cout << "Failed to initialize GLFW\n"; return false; } glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3); glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); window = glfwCreateWindow(1280, 800, "Engine Window", nullptr, nullptr); if (!window) { std::cout << "Failed to create GLFW window\n"; glfwTerminate(); return false; } glfwMakeContextCurrent(window); glewExperimental = GL_TRUE; if (glewInit() != GLEW_OK) { std::cout << "Failed to initialize GLEW\n"; return false; } int width, height; glfwGetFramebufferSize(window, &width, &height); glViewport(0, 0, width, height); // Create framebuffer. glGenFramebuffers(1, &framebuffer); ResizeFramebuffer(fbWidth, fbHeight); // Setup scene-wide shader (this shader is now used to render all models) if (!SetupScene()) { std::cout << "Failed to set up scene\n"; return false; } return true; } GLFWwindow *Engine::GetWindow() { return window; } GLuint Engine::GetShader() { return shaderProgram; } void Engine::ResizeFramebuffer(int width, int height) { // Avoid division by zero. if (height <= 0) height = 1; // Define the desired target aspect ratio (e.g., 16:9). const float targetAspect = 16.0f / 9.0f; float currentAspect = static_cast(width) / static_cast(height); // Adjust dimensions to maintain the target aspect ratio. int newWidth = width; int newHeight = height; if (currentAspect > targetAspect) { newWidth = static_cast(height * targetAspect); } else if (currentAspect < targetAspect) { newHeight = static_cast(width / targetAspect); } fbWidth = newWidth; fbHeight = newHeight; glBindFramebuffer(GL_FRAMEBUFFER, framebuffer); // Delete old attachments if they exist. if (colorTexture) { glDeleteTextures(1, &colorTexture); } if (depthRenderbuffer) { glDeleteRenderbuffers(1, &depthRenderbuffer); } // Create color texture using GL_RGBA. glGenTextures(1, &colorTexture); glBindTexture(GL_TEXTURE_2D, colorTexture); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, fbWidth, fbHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL); 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, colorTexture, 0); // Create depth renderbuffer. glGenRenderbuffers(1, &depthRenderbuffer); glBindRenderbuffer(GL_RENDERBUFFER, depthRenderbuffer); glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, fbWidth, fbHeight); glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER, depthRenderbuffer); GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER); if (status != GL_FRAMEBUFFER_COMPLETE) { std::cout << "Framebuffer is not complete! Status: " << status << std::endl; } glBindFramebuffer(GL_FRAMEBUFFER, 0); } GLuint Engine::CompileShader(const char *vertexSrc, const char *fragmentSrc) { GLuint vertexShader = glCreateShader(GL_VERTEX_SHADER); glShaderSource(vertexShader, 1, &vertexSrc, nullptr); glCompileShader(vertexShader); int success; glGetShaderiv(vertexShader, GL_COMPILE_STATUS, &success); if (!success) { char infoLog[512]; glGetShaderInfoLog(vertexShader, 512, nullptr, infoLog); std::cout << "Vertex shader compilation failed: " << infoLog << std::endl; return 0; } GLuint fragmentShader = glCreateShader(GL_FRAGMENT_SHADER); glShaderSource(fragmentShader, 1, &fragmentSrc, nullptr); glCompileShader(fragmentShader); glGetShaderiv(fragmentShader, GL_COMPILE_STATUS, &success); if (!success) { char infoLog[512]; glGetShaderInfoLog(fragmentShader, 512, nullptr, infoLog); std::cout << "Fragment shader compilation failed: " << infoLog << std::endl; return 0; } GLuint program = glCreateProgram(); glAttachShader(program, vertexShader); glAttachShader(program, fragmentShader); glLinkProgram(program); glGetProgramiv(program, GL_LINK_STATUS, &success); if (!success) { char infoLog[512]; glGetProgramInfoLog(program, 512, nullptr, infoLog); std::cout << "Shader program linking failed: " << infoLog << std::endl; return 0; } glDeleteShader(vertexShader); glDeleteShader(fragmentShader); return program; } bool Engine::SetupScene() { // --- Scene Shader Program --- const char *vertexShaderSrc = R"( #version 330 core layout(location = 0) in vec3 aPos; layout(location = 1) in vec3 aNormal; layout(location = 2) in vec2 aTexCoords; layout(location = 3) in vec3 aTangent; uniform mat4 model; uniform mat4 view; uniform mat4 projection; out vec3 FragPos; out vec3 Normal; out vec2 TexCoords; out vec3 Tangent; void main() { FragPos = vec3(model * vec4(aPos, 1.0)); Normal = mat3(transpose(inverse(model))) * aNormal; TexCoords = aTexCoords; Tangent = mat3(model) * aTangent; gl_Position = projection * view * vec4(FragPos, 1.0); } )"; const char *fragmentShaderSrc = R"( #version 330 core out vec4 FragColor; in vec3 FragPos; in vec3 Normal; in vec2 TexCoords; in vec3 Tangent; uniform vec3 lightPositions[2]; uniform vec3 lightColors[2]; uniform int numLights; uniform vec3 viewPos; // Texture uniforms. uniform sampler2D diffuseTexture; uniform sampler2D normalMap; uniform bool useDiffuseTexture; uniform bool useNormalMap; // Skybox cubemap for ambient lighting. uniform samplerCube skybox; // Material properties. uniform vec3 materialDiffuse; uniform vec3 materialSpecular; uniform float materialShininess; void main() { // Optionally sample the diffuse texture. vec3 diffuseTex = useDiffuseTexture ? texture(diffuseTexture, TexCoords).rgb : vec3(1.0); // Determine the final normal. vec3 finalNormal; if(useNormalMap) { vec3 normMap = texture(normalMap, TexCoords).rgb; normMap = normalize(normMap * 2.0 - 1.0); normMap.z = -normMap.z; vec3 T = normalize(Tangent); vec3 B = normalize(cross(Normal, T)); mat3 TBN = mat3(T, B, normalize(Normal)); finalNormal = normalize(TBN * normMap); } else { finalNormal = normalize(Normal); } // Sample ambient from the skybox cubemap. vec3 ambient = texture(skybox, finalNormal).rgb * 0.1 * materialDiffuse; vec3 lighting = ambient; // Loop through lights. for(int i = 0; i < numLights; i++) { vec3 lightDir = normalize(lightPositions[i] - FragPos); float diff = max(dot(finalNormal, lightDir), 0.0); vec3 diffuse = diff * materialDiffuse * diffuseTex * lightColors[i]; vec3 viewDir = normalize(viewPos - FragPos); vec3 halfDir = normalize(lightDir + viewDir); float spec = pow(max(dot(finalNormal, halfDir), 0.0), materialShininess); vec3 specular = materialSpecular * spec * lightColors[i]; lighting += diffuse + specular; } FragColor = vec4(lighting, 1.0); } )"; shaderProgram = CompileShader(vertexShaderSrc, fragmentShaderSrc); if (shaderProgram == 0) { return false; } // --- Skybox Shader Program --- const char *skyboxVertexShaderSrc = R"( #version 330 core layout(location = 0) in vec3 aPos; out vec3 TexCoords; uniform mat4 view; uniform mat4 projection; void main() { TexCoords = aPos; // Remove translation by setting w equal to z component. vec4 pos = projection * view * vec4(aPos, 1.0); gl_Position = pos.xyww; } )"; const char *skyboxFragmentShaderSrc = R"( #version 330 core in vec3 TexCoords; out vec4 FragColor; uniform samplerCube skybox; void main() { FragColor = texture(skybox, TexCoords); } )"; skyboxShaderProgram = CompileShader(skyboxVertexShaderSrc, skyboxFragmentShaderSrc); if (skyboxShaderProgram == 0) { return false; } // Define the full cube (36 vertices) for the skybox. float skyboxVertices[] = { // positions -1.0f, 1.0f, -1.0f, -1.0f, -1.0f, -1.0f, 1.0f, -1.0f, -1.0f, 1.0f, -1.0f, -1.0f, 1.0f, 1.0f, -1.0f, -1.0f, 1.0f, -1.0f, -1.0f, -1.0f, 1.0f, -1.0f, -1.0f, -1.0f, -1.0f, 1.0f, -1.0f, -1.0f, 1.0f, -1.0f, -1.0f, 1.0f, 1.0f, -1.0f, -1.0f, 1.0f, 1.0f, -1.0f, -1.0f, 1.0f, -1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, -1.0f, 1.0f, -1.0f, -1.0f, -1.0f, -1.0f, 1.0f, -1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, -1.0f, 1.0f, -1.0f, -1.0f, 1.0f, -1.0f, 1.0f, -1.0f, 1.0f, 1.0f, -1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, -1.0f, 1.0f, 1.0f, -1.0f, 1.0f, -1.0f, -1.0f, -1.0f, -1.0f, -1.0f, -1.0f, 1.0f, 1.0f, -1.0f, -1.0f, 1.0f, -1.0f, -1.0f, -1.0f, -1.0f, 1.0f, 1.0f, -1.0f, 1.0f}; unsigned int skyboxVAO, skyboxVBO; glGenVertexArrays(1, &skyboxVAO); glGenBuffers(1, &skyboxVBO); glBindVertexArray(skyboxVAO); glBindBuffer(GL_ARRAY_BUFFER, skyboxVBO); glBufferData(GL_ARRAY_BUFFER, sizeof(skyboxVertices), skyboxVertices, GL_STATIC_DRAW); glEnableVertexAttribArray(0); glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void *)0); glBindVertexArray(0); std::vector faces{ "assets/skybox/right.jpg", "assets/skybox/left.jpg", "assets/skybox/top.jpg", "assets/skybox/bottom.jpg", "assets/skybox/front.jpg", "assets/skybox/back.jpg" }; skyboxCubemap = loadCubemap(faces); return true; } ImTextureID Engine::RenderScene(const glm::mat4 &view, const glm::mat4 &projection, const glm::vec3 &viewPos, const std::vector &entities) { glBindFramebuffer(GL_FRAMEBUFFER, framebuffer); glViewport(0, 0, fbWidth, fbHeight); glEnable(GL_DEPTH_TEST); glClearColor(0.1f, 0.1f, 0.1f, 1.0f); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // --- Draw Scene Objects --- glUseProgram(shaderProgram); glUniformMatrix4fv(glGetUniformLocation(shaderProgram, "view"), 1, GL_FALSE, glm::value_ptr(view)); glUniformMatrix4fv(glGetUniformLocation(shaderProgram, "projection"), 1, GL_FALSE, glm::value_ptr(projection)); glUniform3f(glGetUniformLocation(shaderProgram, "viewPos"), viewPos.x, viewPos.y, viewPos.z); // Bind skybox cubemap for ambient sampling in the scene shader. glActiveTexture(GL_TEXTURE2); glBindTexture(GL_TEXTURE_CUBE_MAP, skyboxCubemap); glUniform1i(glGetUniformLocation(shaderProgram, "skybox"), 2); // Set up lights (up to 2). glm::vec3 lightPositions[2] = {glm::vec3(0.0f), glm::vec3(0.0f)}; glm::vec3 lightColors[2] = {glm::vec3(1.0f), glm::vec3(1.0f)}; int lightCount = 0; for (auto e : entities) { if (e->GetType() == EntityType::LIGHT && lightCount < 2) { lightPositions[lightCount] = e->transform.position; if (e->lightComponent) { lightColors[lightCount] = e->lightComponent->color * e->lightComponent->intensity; } lightCount++; } } glUniform1i(glGetUniformLocation(shaderProgram, "numLights"), lightCount); if (lightCount > 0) { glUniform3fv(glGetUniformLocation(shaderProgram, "lightPositions"), lightCount, glm::value_ptr(lightPositions[0])); glUniform3fv(glGetUniformLocation(shaderProgram, "lightColors"), lightCount, glm::value_ptr(lightColors[0])); } // Render each cube entity using its ModelComponent. for (auto e : entities) { if (e->GetType() == EntityType::CUBE && e->modelComponent) { glm::mat4 modelMatrix = e->transform.GetMatrix(); glUniformMatrix4fv(glGetUniformLocation(shaderProgram, "model"), 1, GL_FALSE, glm::value_ptr(modelMatrix)); for (const auto &mesh : e->modelComponent->meshes) { glUniform3fv(glGetUniformLocation(shaderProgram, "materialDiffuse"), 1, glm::value_ptr(mesh.diffuseColor)); glUniform3fv(glGetUniformLocation(shaderProgram, "materialSpecular"), 1, glm::value_ptr(mesh.specularColor)); glUniform1f(glGetUniformLocation(shaderProgram, "materialShininess"), mesh.shininess); if (mesh.diffuseTexture != 0) { glUniform1i(glGetUniformLocation(shaderProgram, "useDiffuseTexture"), 1); glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D, mesh.diffuseTexture); glUniform1i(glGetUniformLocation(shaderProgram, "diffuseTexture"), 0); } else { glUniform1i(glGetUniformLocation(shaderProgram, "useDiffuseTexture"), 0); } if (mesh.normalTexture != 0) { glUniform1i(glGetUniformLocation(shaderProgram, "useNormalMap"), 1); glActiveTexture(GL_TEXTURE1); glBindTexture(GL_TEXTURE_2D, mesh.normalTexture); glUniform1i(glGetUniformLocation(shaderProgram, "normalMap"), 1); } else { glUniform1i(glGetUniformLocation(shaderProgram, "useNormalMap"), 0); } glBindVertexArray(mesh.VAO); glDrawElements(GL_TRIANGLES, static_cast(mesh.indices.size()), GL_UNSIGNED_INT, 0); glBindVertexArray(0); } } } // Draw Skybox glDepthFunc(GL_LEQUAL); // Allow skybox fragments to pass depth test. glUseProgram(skyboxShaderProgram); glm::mat4 viewNoTrans = glm::mat4(glm::mat3(view)); glUniformMatrix4fv(glGetUniformLocation(skyboxShaderProgram, "view"), 1, GL_FALSE, glm::value_ptr(viewNoTrans)); glUniformMatrix4fv(glGetUniformLocation(skyboxShaderProgram, "projection"), 1, GL_FALSE, glm::value_ptr(projection)); // Bind skybox VAO and cubemap texture. glBindVertexArray(skyboxVAO); glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_CUBE_MAP, skyboxCubemap); glUniform1i(glGetUniformLocation(skyboxShaderProgram, "skybox"), 0); // Draw the cube (36 vertices) glDrawArrays(GL_TRIANGLES, 0, 36); glBindVertexArray(0); glDepthFunc(GL_LESS); // Restore default depth function. glBindFramebuffer(GL_FRAMEBUFFER, 0); return (ImTextureID)(intptr_t)colorTexture; } ImTextureID Engine::GetFinalRenderingTexture() { return (ImTextureID)(intptr_t)colorTexture; } void Engine::Shutdown() { glDeleteProgram(shaderProgram); glDeleteFramebuffers(1, &framebuffer); glDeleteTextures(1, &colorTexture); glDeleteTextures(1, &normalMapTexture); glDeleteRenderbuffers(1, &depthRenderbuffer); glfwDestroyWindow(window); glfwTerminate(); }