diff --git a/Editor/editor.cpp b/Editor/editor.cpp index 73e343e..b4348ad 100644 --- a/Editor/editor.cpp +++ b/Editor/editor.cpp @@ -2,12 +2,54 @@ #include "imgui.h" #include "imgui_impl_glfw.h" #include "imgui_impl_opengl3.h" +#include +#include +#include "../Engine/Entity/Entity.h" +#include "../Engine/Components/TransformComponent.h" +#include "../Engine/Components/ModelComponent.h" +#include "../Engine/Components/LightComponent.h" #include #include +#include +#include "stb_image.h" -// Simple editor camera. +using std::vector; + +// Global light icon texture. +GLuint g_LightIconTex = 0; +ImTextureID g_LightIconImTex = 0; + +// Helper function to load a texture. +bool LoadIconTexture(const char* filepath, GLuint &texOut, ImTextureID &imTexOut) { + int w, h, channels; + unsigned char* data = stbi_load(filepath, &w, &h, &channels, 0); + if (!data) { + printf("Failed to load icon texture: %s\n", filepath); + return false; + } + // Overwrite loaded data with white pixels (all channels set to 255) + int total = w * h * channels; + for (int i = 0; i < total; i++) { + data[i] = 255; + } + glGenTextures(1, &texOut); + glBindTexture(GL_TEXTURE_2D, texOut); + GLenum format = (channels == 3) ? GL_RGB : GL_RGBA; + glTexImage2D(GL_TEXTURE_2D, 0, format, w, h, 0, format, GL_UNSIGNED_BYTE, data); + glGenerateMipmap(GL_TEXTURE_2D); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + stbi_image_free(data); + imTexOut = (ImTextureID)(intptr_t)texOut; + return true; +} + + +// Editor camera. struct EditorCamera { - glm::vec3 position = glm::vec3(0.0f, 0.0f, 5.0f); + glm::vec3 position = glm::vec3(0.0f, 0.0f, 8.0f); float yaw = -90.0f; float pitch = 0.0f; float speed = 5.0f; @@ -25,7 +67,6 @@ void ProcessEditorCamera(GLFWwindow* window, float deltaTime) { front.z = sin(glm::radians(editorCamera.yaw)) * cos(glm::radians(editorCamera.pitch)); front = glm::normalize(front); glm::vec3 right = glm::normalize(glm::cross(front, glm::vec3(0,1,0))); - if (glfwGetKey(window, GLFW_KEY_W) == GLFW_PRESS) editorCamera.position += front * editorCamera.speed * deltaTime; if (glfwGetKey(window, GLFW_KEY_S) == GLFW_PRESS) @@ -34,7 +75,6 @@ void ProcessEditorCamera(GLFWwindow* window, float deltaTime) { editorCamera.position -= right * editorCamera.speed * deltaTime; if (glfwGetKey(window, GLFW_KEY_D) == GLFW_PRESS) editorCamera.position += right * editorCamera.speed * deltaTime; - static double lastX = 0, lastY = 0; if (glfwGetMouseButton(window, GLFW_MOUSE_BUTTON_RIGHT) == GLFW_PRESS) { double xpos, ypos; @@ -57,20 +97,56 @@ void ProcessEditorCamera(GLFWwindow* window, float deltaTime) { int main() { if (!Engine::Init()) return 1; + // Load the light icon. + LoadIconTexture("assets/icons/light.png", g_LightIconTex, g_LightIconImTex); - // Setup ImGui. IMGUI_CHECKVERSION(); ImGui::CreateContext(); ImGuiIO& io = ImGui::GetIO(); io.ConfigFlags |= ImGuiConfigFlags_DockingEnable; ImGui::StyleColorsDark(); - const char* glsl_version = "#version 330"; ImGui_ImplGlfw_InitForOpenGL(Engine::GetWindow(), true); ImGui_ImplOpenGL3_Init(glsl_version); - float lastFrameTime = (float)glfwGetTime(); + vector entities; + // Create two cube entities. + Entity* cube1 = new Entity(EntityType::CUBE); + cube1->transform.position = glm::vec3(-2.0f, 0.0f, 0.0f); + cube1->modelComponent = new ModelComponent(); + cube1->modelComponent->diffuseColor = glm::vec3(0.8f, 0.2f, 0.2f); + cube1->modelComponent->specularColor = glm::vec3(1.0f, 1.0f, 1.0f); + cube1->modelComponent->shininess = 32.0f; + Entity* cube2 = new Entity(EntityType::CUBE); + cube2->transform.position = glm::vec3(2.0f, 0.0f, 0.0f); + cube2->modelComponent = new ModelComponent(); + cube2->modelComponent->diffuseColor = glm::vec3(0.2f, 0.8f, 0.2f); + cube2->modelComponent->specularColor = glm::vec3(1.0f, 1.0f, 1.0f); + cube2->modelComponent->shininess = 16.0f; + + // Create two light entities. + Entity* light1 = new Entity(EntityType::LIGHT); + light1->transform.position = glm::vec3(0.0f, 10.0f, 0.0f); + light1->lightComponent = new LightComponent(); + light1->lightComponent->color = glm::vec3(1.0f, 1.0f, 1.0f); + light1->lightComponent->intensity = 1.5f; + + Entity* light2 = new Entity(EntityType::LIGHT); + light2->transform.position = glm::vec3(0.0f, -10.0f, 0.0f); + light2->lightComponent = new LightComponent(); + light2->lightComponent->color = glm::vec3(0.2f, 0.2f, 1.0f); + light2->lightComponent->intensity = 1.0f; + + entities.push_back(cube1); + entities.push_back(cube2); + entities.push_back(light1); + entities.push_back(light2); + + Entity* selectedEntity = nullptr; + int selectedIndex = -1; + + float lastFrameTime = (float)glfwGetTime(); while (!glfwWindowShouldClose(Engine::GetWindow())) { float currentFrameTime = (float)glfwGetTime(); float deltaTime = currentFrameTime - lastFrameTime; @@ -78,45 +154,71 @@ int main() { ProcessEditorCamera(Engine::GetWindow(), deltaTime); - // Update editor camera projection based on the window size. int winWidth, winHeight; glfwGetFramebufferSize(Engine::GetWindow(), &winWidth, &winHeight); editorCamera.projection = glm::perspective(glm::radians(45.0f), (float)winWidth / winHeight, 0.1f, 100.0f); - // Offscreen rendering: Resize framebuffer and render scene. Engine::ResizeFramebuffer(winWidth, winHeight); - ImTextureID offscreenTexture = Engine::RenderScene(editorCamera.view, editorCamera.projection, editorCamera.position); + ImTextureID offscreenTexture = Engine::RenderScene(editorCamera.view, editorCamera.projection, editorCamera.position, entities); - // Clear the default framebuffer background. glBindFramebuffer(GL_FRAMEBUFFER, 0); glViewport(0, 0, winWidth, winHeight); - glClearColor(0.0f, 0.0f, 0.0f, 1.0f); // Clear with black (or choose any color). + glClearColor(0.0f, 0.0f, 0.0f, 1.0f); glClear(GL_COLOR_BUFFER_BIT); - // Start a single ImGui frame. + + ImGui_ImplOpenGL3_NewFrame(); ImGui_ImplGlfw_NewFrame(); ImGui::NewFrame(); - // Create a full-viewport dock space. ImGui::DockSpaceOverViewport(0, ImGui::GetMainViewport(), ImGuiDockNodeFlags_PassthruCentralNode); - - // Create an "Editor Panel" window. - ImGui::Begin("Editor Panel"); - ImGui::Text("Welcome to the Editor!"); - // (Additional UI elements can go here.) + // Left Panel: Entity List. + ImGui::Begin("Entity List"); + for (int i = 0; i < entities.size(); i++) { + char label[32]; + sprintf(label, "Entity %d", i); + if (ImGui::Selectable(label, selectedIndex == i)) + selectedIndex = i; + if (ImGui::IsItemClicked()) { + selectedEntity = entities[i]; + selectedIndex = i; + } + } ImGui::End(); - // Create a "Rendered Output" window. + // Right Panel: Entity Inspector. + ImGui::Begin("Entity Inspector"); + if (selectedEntity) { + ImGui::Text("Transform"); + ImGui::DragFloat3("Position", glm::value_ptr(selectedEntity->transform.position), 0.1f); + ImGui::DragFloat3("Rotation", glm::value_ptr(selectedEntity->transform.rotation), 0.5f); + ImGui::DragFloat3("Scale", glm::value_ptr(selectedEntity->transform.scale), 0.1f); + if (selectedEntity->GetType() == EntityType::CUBE && selectedEntity->modelComponent) { + ImGui::Separator(); + ImGui::Text("Model Properties"); + ImGui::ColorEdit3("Diffuse", glm::value_ptr(selectedEntity->modelComponent->diffuseColor)); + ImGui::ColorEdit3("Specular", glm::value_ptr(selectedEntity->modelComponent->specularColor)); + ImGui::DragFloat("Shininess", &selectedEntity->modelComponent->shininess, 1.0f, 1.0f, 128.0f); + } + if (selectedEntity->GetType() == EntityType::LIGHT && selectedEntity->lightComponent) { + ImGui::Separator(); + ImGui::Text("Light Properties"); + ImGui::ColorEdit3("Light Color", glm::value_ptr(selectedEntity->lightComponent->color)); + ImGui::DragFloat("Intensity", &selectedEntity->lightComponent->intensity, 0.1f, 0.0f, 10.0f); + } + } else { + ImGui::Text("No entity selected."); + } + ImGui::End(); + + // Bottom Panel: Rendered Output. ImGui::Begin("Rendered Output"); - // Get available region size for the rendered output. ImVec2 viewportSize = ImGui::GetContentRegionAvail(); - // Display the offscreen texture. The UVs are flipped to correct the upside-down image. ImGui::Image(offscreenTexture, viewportSize, ImVec2(0,1), ImVec2(1,0)); ImGui::End(); - // Finalize and render the ImGui frame. ImGui::Render(); ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData()); @@ -124,6 +226,9 @@ int main() { glfwPollEvents(); } + for (auto e : entities) + delete e; + ImGui_ImplOpenGL3_Shutdown(); ImGui_ImplGlfw_Shutdown(); ImGui::DestroyContext(); diff --git a/Engine/Components/LightComponent.cpp b/Engine/Components/LightComponent.cpp new file mode 100644 index 0000000..357f3aa --- /dev/null +++ b/Engine/Components/LightComponent.cpp @@ -0,0 +1,9 @@ +#include "LightComponent.h" + +LightComponent::LightComponent() + : color(1.0f, 1.0f, 1.0f), + intensity(1.0f) { +} + +LightComponent::~LightComponent() { +} diff --git a/Engine/Components/LightComponent.h b/Engine/Components/LightComponent.h new file mode 100644 index 0000000..61ea366 --- /dev/null +++ b/Engine/Components/LightComponent.h @@ -0,0 +1,16 @@ +#ifndef LIGHT_COMPONENT_H +#define LIGHT_COMPONENT_H + +#include + +class LightComponent { +public: + LightComponent(); + ~LightComponent(); + + // Light properties. + glm::vec3 color; + float intensity; +}; + +#endif // LIGHT_COMPONENT_H diff --git a/Engine/Components/ModelComponent.cpp b/Engine/Components/ModelComponent.cpp new file mode 100644 index 0000000..c82e4ac --- /dev/null +++ b/Engine/Components/ModelComponent.cpp @@ -0,0 +1,10 @@ +#include "ModelComponent.h" + +ModelComponent::ModelComponent() + : diffuseColor(1.0f, 1.0f, 1.0f), + specularColor(1.0f, 1.0f, 1.0f), + shininess(32.0f) { +} + +ModelComponent::~ModelComponent() { +} diff --git a/Engine/Components/ModelComponent.h b/Engine/Components/ModelComponent.h new file mode 100644 index 0000000..ba02974 --- /dev/null +++ b/Engine/Components/ModelComponent.h @@ -0,0 +1,17 @@ +#ifndef MODEL_COMPONENT_H +#define MODEL_COMPONENT_H + +#include + +class ModelComponent { +public: + ModelComponent(); + ~ModelComponent(); + + // Material properties. + glm::vec3 diffuseColor; + glm::vec3 specularColor; + float shininess; +}; + +#endif // MODEL_COMPONENT_H diff --git a/Engine/Components/TransformComponent.cpp b/Engine/Components/TransformComponent.cpp new file mode 100644 index 0000000..a778a23 --- /dev/null +++ b/Engine/Components/TransformComponent.cpp @@ -0,0 +1,18 @@ +#include "TransformComponent.h" + +TransformComponent::TransformComponent() + : position(0.0f), rotation(0.0f), scale(1.0f) { +} + +TransformComponent::~TransformComponent() { +} + +glm::mat4 TransformComponent::GetMatrix() const { + glm::mat4 trans = glm::translate(glm::mat4(1.0f), position); + glm::mat4 rotX = glm::rotate(glm::mat4(1.0f), glm::radians(rotation.x), glm::vec3(1,0,0)); + glm::mat4 rotY = glm::rotate(glm::mat4(1.0f), glm::radians(rotation.y), glm::vec3(0,1,0)); + glm::mat4 rotZ = glm::rotate(glm::mat4(1.0f), glm::radians(rotation.z), glm::vec3(0,0,1)); + glm::mat4 rot = rotZ * rotY * rotX; + glm::mat4 scl = glm::scale(glm::mat4(1.0f), scale); + return trans * rot * scl; +} diff --git a/Engine/Components/TransformComponent.h b/Engine/Components/TransformComponent.h new file mode 100644 index 0000000..ce01316 --- /dev/null +++ b/Engine/Components/TransformComponent.h @@ -0,0 +1,20 @@ +#ifndef TRANSFORM_COMPONENT_H +#define TRANSFORM_COMPONENT_H + +#include +#include + +class TransformComponent { +public: + TransformComponent(); + ~TransformComponent(); + + glm::vec3 position; + glm::vec3 rotation; // Euler angles (pitch, yaw, roll) in degrees. + glm::vec3 scale; + + // Returns the world transform matrix. + glm::mat4 GetMatrix() const; +}; + +#endif // TRANSFORM_COMPONENT_H diff --git a/Engine/Engine.h b/Engine/Engine.h index 9de0b24..7b79451 100644 --- a/Engine/Engine.h +++ b/Engine/Engine.h @@ -2,6 +2,7 @@ #define ENGINE_H #include +#include #include #include #include "imgui.h" @@ -14,6 +15,8 @@ #include "Components/Component.h" #include "Components/Camera.h" +// Forward declaration of Entity. +class Entity; class Engine { public: @@ -22,17 +25,14 @@ public: // Initializes GLFW, GLEW, and sets up the main window and offscreen framebuffer. static bool Init(); - // Returns the main GLFW window. static GLFWwindow* GetWindow(); - // Clean up resources and shutdown GLFW. static void Shutdown(); - // Offscreen render function that uses the provided camera parameters. - // It renders the scene (e.g., a spinning cube with red lighting) offscreen - // and returns its color attachment as an ImTextureID. - static ImTextureID RenderScene(const glm::mat4 &view, const glm::mat4 &projection, const glm::vec3 &viewPos); + // Offscreen render function that uses the provided camera parameters and renders the scene + // (with entities) offscreen. Returns the color attachment as an ImTextureID. + static ImTextureID RenderScene(const glm::mat4 &view, const glm::mat4 &projection, const glm::vec3 &viewPos, const std::vector& entities); // Resizes the offscreen framebuffer to the given dimensions. static void ResizeFramebuffer(int width, int height); @@ -40,29 +40,35 @@ public: // Retrieves the final rendered texture. static ImTextureID GetFinalRenderingTexture(); + // --- Billboard Rendering for 3D Icons --- + // Billboard shader and VAO for drawing icons (e.g., light icons) as billboards in 3D space. + static GLuint billboardShaderProgram; + static GLuint billboardVAO; + // Sets up billboard resources. + static bool SetupBillboard(); + // Draws a billboarded icon at the given world position with a specified size. + static void DrawIconIn3DSpace(const glm::vec3 &worldPos, const glm::vec2 &size, ImTextureID texture, const glm::mat4 &view, const glm::mat4 &projection); + // Iterates over entities and draws icons for those of the appropriate type. + static void DrawIconsIn3DSpace(const std::vector& entities, const glm::mat4 &view, const glm::mat4 &projection, ImTextureID iconTexture); + private: // Offscreen framebuffer and its attachments. static GLuint framebuffer; static GLuint colorTexture; static GLuint depthRenderbuffer; - // Geometry for a simple cube. static GLuint cubeVAO; static GLuint cubeVBO; - // Shader program used for rendering the scene. static GLuint shaderProgram; - - // Rotation angle for the spinning cube. + // Rotation angle for the spinning cube (or animated models). static float rotationAngle; - // Current offscreen framebuffer dimensions. static int fbWidth; static int fbHeight; // Helper function to compile and link shaders. static GLuint CompileShader(const char* vertexSrc, const char* fragmentSrc); - // Sets up cube geometry and shader (called during initialization). static bool SetupScene(); }; diff --git a/Engine/Entity/Entity.cpp b/Engine/Entity/Entity.cpp new file mode 100644 index 0000000..8ae15f5 --- /dev/null +++ b/Engine/Entity/Entity.cpp @@ -0,0 +1,20 @@ +#include "Entity.h" + +Entity::Entity(EntityType type) + : type(type), modelComponent(nullptr), lightComponent(nullptr) { +} + +Entity::~Entity() { + if(modelComponent) { + delete modelComponent; + modelComponent = nullptr; + } + if(lightComponent) { + delete lightComponent; + lightComponent = nullptr; + } +} + +EntityType Entity::GetType() const { + return type; +} diff --git a/Engine/Entity/Entity.h b/Engine/Entity/Entity.h new file mode 100644 index 0000000..4f87c1a --- /dev/null +++ b/Engine/Entity/Entity.h @@ -0,0 +1,30 @@ +#ifndef ENTITY_H +#define ENTITY_H + +#include "../Components/TransformComponent.h" +#include "../Components/ModelComponent.h" +#include "../Components/LightComponent.h" + +enum class EntityType { + CUBE, + LIGHT +}; + +class Entity { +public: + Entity(EntityType type); + virtual ~Entity(); + + EntityType GetType() const; + + // Components. + TransformComponent transform; + // Optional components (set to non-null if the entity has them). + ModelComponent* modelComponent; + LightComponent* lightComponent; + +private: + EntityType type; +}; + +#endif // ENTITY_H diff --git a/Engine/engine.cpp b/Engine/engine.cpp index ce28146..210ed56 100644 --- a/Engine/engine.cpp +++ b/Engine/engine.cpp @@ -3,7 +3,7 @@ #include #define STB_IMAGE_IMPLEMENTATION #include "stb_image.h" - +#include "Entity/Entity.h" // Static member definitions. GLFWwindow* Engine::window = nullptr; GLuint Engine::framebuffer = 0; @@ -18,6 +18,9 @@ int Engine::fbHeight = 400; GLuint normalMapTexture = 0; // Global texture for the normal map +GLuint Engine::billboardShaderProgram = 0; +GLuint Engine::billboardVAO = 0; + bool Engine::Init() { if (!glfwInit()) { std::cout << "Failed to initialize GLFW\n"; @@ -153,6 +156,111 @@ GLuint Engine::CompileShader(const char* vertexSrc, const char* fragmentSrc) { return program; } + + + +bool Engine::SetupBillboard() { + // Simple billboard vertex shader. + const char* billboardVertexSrc = R"( + #version 330 core + layout(location = 0) in vec3 aPos; + layout(location = 1) in vec2 aTexCoords; + + uniform mat4 model; + uniform mat4 view; + uniform mat4 projection; + + out vec2 TexCoords; + + void main() { + TexCoords = aTexCoords; + gl_Position = projection * view * model * vec4(aPos, 1.0); + } + )"; + // Simple billboard fragment shader. + const char* billboardFragmentSrc = R"( + #version 330 core + out vec4 FragColor; + in vec2 TexCoords; + uniform sampler2D billboardTexture; + void main() { + FragColor = texture(billboardTexture, TexCoords); + } + )"; + billboardShaderProgram = CompileShader(billboardVertexSrc, billboardFragmentSrc); + if (billboardShaderProgram == 0) return false; + + // Define a quad (two triangles) for the billboard. + float quadVertices[] = { + // positions // texcoords + -0.5f, 0.5f, 0.0f, 0.0f, 1.0f, + -0.5f, -0.5f, 0.0f, 0.0f, 0.0f, + 0.5f, -0.5f, 0.0f, 1.0f, 0.0f, + + -0.5f, 0.5f, 0.0f, 0.0f, 1.0f, + 0.5f, -0.5f, 0.0f, 1.0f, 0.0f, + 0.5f, 0.5f, 0.0f, 1.0f, 1.0f + }; + + GLuint VBO; + glGenVertexArrays(1, &billboardVAO); + glGenBuffers(1, &VBO); + glBindVertexArray(billboardVAO); + glBindBuffer(GL_ARRAY_BUFFER, VBO); + glBufferData(GL_ARRAY_BUFFER, sizeof(quadVertices), quadVertices, GL_STATIC_DRAW); + // position attribute. + glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void*)0); + glEnableVertexAttribArray(0); + // texcoord attribute. + glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void*)(3 * sizeof(float))); + glEnableVertexAttribArray(1); + glBindVertexArray(0); + glDeleteBuffers(1, &VBO); + return true; +} + +void Engine::DrawIconIn3DSpace(const glm::vec3 &worldPos, const glm::vec2 &size, ImTextureID texture, const glm::mat4 &view, const glm::mat4 &projection) { + // Compute billboard matrix (extract inverse rotation from view). + glm::mat4 billboard = glm::mat4(1.0f); + billboard[0] = glm::vec4(view[0][0], view[1][0], view[2][0], 0.0f); + billboard[1] = glm::vec4(view[0][1], view[1][1], view[2][1], 0.0f); + billboard[2] = glm::vec4(view[0][2], view[1][2], view[2][2], 0.0f); + billboard = glm::transpose(billboard); + + glm::mat4 model = glm::translate(glm::mat4(1.0f), worldPos); + model *= billboard; + model = glm::scale(model, glm::vec3(size, 1.0f)); + + glUseProgram(billboardShaderProgram); + glUniformMatrix4fv(glGetUniformLocation(billboardShaderProgram, "model"), 1, GL_FALSE, glm::value_ptr(model)); + glUniformMatrix4fv(glGetUniformLocation(billboardShaderProgram, "view"), 1, GL_FALSE, glm::value_ptr(view)); + glUniformMatrix4fv(glGetUniformLocation(billboardShaderProgram, "projection"), 1, GL_FALSE, glm::value_ptr(projection)); + + // Bind icon texture. + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_2D, (GLuint)(intptr_t)texture); + glUniform1i(glGetUniformLocation(billboardShaderProgram, "billboardTexture"), 0); + + // Disable depth test so icon appears on top. + glDisable(GL_DEPTH_TEST); + glBindVertexArray(billboardVAO); + glDrawArrays(GL_TRIANGLES, 0, 6); + glBindVertexArray(0); + glEnable(GL_DEPTH_TEST); // Re-enable depth testing. +} + + +void Engine::DrawIconsIn3DSpace(const std::vector& entities, const glm::mat4 &view, const glm::mat4 &projection, ImTextureID iconTexture) { + for(auto e : entities) { + if(e->GetType() == EntityType::LIGHT) { + // Draw a billboard icon at the light's world position. + DrawIconIn3DSpace(e->transform.position, glm::vec2(1.0f, 1.0f), iconTexture, view, projection); + } + } +} + + + bool Engine::SetupScene() { // Updated cube vertices: each vertex has: // position (3), normal (3), texcoord (2), tangent (3) = 11 floats per vertex. @@ -226,84 +334,91 @@ bool Engine::SetupScene() { glEnableVertexAttribArray(3); glBindVertexArray(0); - // Vertex shader source. 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; + Tangent = mat3(model) * aTangent; gl_Position = projection * view * vec4(FragPos, 1.0); } )"; - - // Fragment shader source with normal mapping. + const char* fragmentShaderSrc = R"( #version 330 core out vec4 FragColor; - + in vec3 FragPos; in vec3 Normal; in vec2 TexCoords; in vec3 Tangent; - - uniform vec3 lightPos; + + uniform vec3 lightPositions[2]; + uniform vec3 lightColors[2]; + uniform int numLights; uniform vec3 viewPos; uniform sampler2D normalMap; - + + // Material properties. + uniform vec3 materialDiffuse; + uniform vec3 materialSpecular; + uniform float materialShininess; + void main() { - // Obtain the normal from the normal map in range [0,1] and remap to [-1,1]. vec3 normMap = texture(normalMap, TexCoords).rgb; - normMap = normalize(normMap); - - // Calculate TBN matrix. For simplicity, compute bitangent as cross(normal, tangent). + normMap = normalize(normMap * 2.0 - 1.0); + normMap.z = -normMap.z; vec3 N = normalize(Normal); vec3 T = normalize(Tangent); vec3 B = normalize(cross(N, T)); mat3 TBN = mat3(T, B, N); vec3 perturbedNormal = normalize(TBN * normMap); - - // Ambient. - float ambientStrength = 0.5; - vec3 ambient = ambientStrength * vec3(1.0, 0.0, 1.0); - - // Diffuse. - vec3 lightDir = normalize(lightPos - FragPos); - float diff = max(dot(perturbedNormal, lightDir), 0.0); - vec3 diffuse = diff * vec3(1.0, 1.0, 1.0); - - // Specular. - float specularStrength = 0.2; - vec3 viewDir = normalize(viewPos - FragPos); - vec3 reflectDir = reflect(-lightDir, perturbedNormal); - float spec = pow(max(dot(viewDir, reflectDir), 0.0), 32); - vec3 specular = specularStrength * spec * vec3(1.0); - - vec3 result = ambient + diffuse + specular; - FragColor = vec4(result, 1.0); + + vec3 ambient = 0.1 * materialDiffuse; + vec3 lighting = ambient; + for(int i = 0; i < numLights; i++) { + vec3 lightDir = normalize(lightPositions[i] - FragPos); + float diff = max(dot(perturbedNormal, lightDir), 0.0); + vec3 diffuse = diff * materialDiffuse * lightColors[i]; + + vec3 viewDir = normalize(viewPos - FragPos); + vec3 reflectDir = reflect(-lightDir, perturbedNormal); + float spec = pow(max(dot(viewDir, reflectDir), 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; } + if (!SetupBillboard()) { + std::cout << "Failed to set up billboard resources." << std::endl; + return false; + } + + // Load normal map from "./normal.png" int texWidth, texHeight, texChannels; unsigned char* data = stbi_load("./normal.jpg", &texWidth, &texHeight, &texChannels, 0); @@ -330,7 +445,7 @@ bool Engine::SetupScene() { return true; } -ImTextureID Engine::RenderScene(const glm::mat4 &view, const glm::mat4 &projection, const glm::vec3 &viewPos) { +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); @@ -342,24 +457,50 @@ ImTextureID Engine::RenderScene(const glm::mat4 &view, const glm::mat4 &projecti glUniformMatrix4fv(glGetUniformLocation(shaderProgram, "projection"), 1, GL_FALSE, glm::value_ptr(projection)); glUniform3f(glGetUniformLocation(shaderProgram, "viewPos"), viewPos.x, viewPos.y, viewPos.z); - rotationAngle += 0.001f; - glm::mat4 model = glm::rotate(glm::mat4(1.0f), rotationAngle, glm::vec3(1,1,0)); - glUniformMatrix4fv(glGetUniformLocation(shaderProgram, "model"), 1, GL_FALSE, glm::value_ptr(model)); + // Gather lights (up to 2) from entities of type LIGHT. + 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])); + } - glUniform3f(glGetUniformLocation(shaderProgram, "lightPos"), 0.0f, 20.0f, 0.0f); - - // Activate texture unit 1 and bind the normal map. + // Bind the normal map on texture unit 1. glActiveTexture(GL_TEXTURE1); glBindTexture(GL_TEXTURE_2D, normalMapTexture); + // (Assumes that the uniform "normalMap" is already set to 1.) - glBindVertexArray(cubeVAO); - glDrawArrays(GL_TRIANGLES, 0, 36); + // Render cube entities. + 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)); + glUniform3fv(glGetUniformLocation(shaderProgram, "materialDiffuse"), 1, glm::value_ptr(e->modelComponent->diffuseColor)); + glUniform3fv(glGetUniformLocation(shaderProgram, "materialSpecular"), 1, glm::value_ptr(e->modelComponent->specularColor)); + glUniform1f(glGetUniformLocation(shaderProgram, "materialShininess"), e->modelComponent->shininess); + glBindVertexArray(cubeVAO); + glDrawArrays(GL_TRIANGLES, 0, 36); + } + } glBindVertexArray(0); glBindFramebuffer(GL_FRAMEBUFFER, 0); return (ImTextureID)(intptr_t)colorTexture; } + + ImTextureID Engine::GetFinalRenderingTexture() { return (ImTextureID)(intptr_t)colorTexture; } diff --git a/Makefile b/Makefile index 8808db1..5930c86 100644 --- a/Makefile +++ b/Makefile @@ -6,7 +6,7 @@ CXXFLAGS := -std=c++20 -Wall -Wextra -O2 -I/c/msys64/mingw64/include -Ivendor/im LDFLAGS := -Llib -lglfw3 -lopengl32 -lglew32 -lglu32 # Source and build directories (including vendor folder) -SRC_DIRS := . Editor Engine vendor/imgui-docking Engine/Components +SRC_DIRS := . Editor Engine vendor/imgui-docking Engine/Components Engine/Entity BUILD_DIR := build # Find all source files diff --git a/Three-Labs.exe b/Three-Labs.exe index 06a5649..18da5eb 100644 Binary files a/Three-Labs.exe and b/Three-Labs.exe differ diff --git a/assets/icons/light.png b/assets/icons/light.png new file mode 100644 index 0000000..331ea87 Binary files /dev/null and b/assets/icons/light.png differ diff --git a/build/Editor/editor.o b/build/Editor/editor.o index f36759e..7a0e602 100644 Binary files a/build/Editor/editor.o and b/build/Editor/editor.o differ diff --git a/build/Engine/Components/LightComponent.o b/build/Engine/Components/LightComponent.o new file mode 100644 index 0000000..ee10fa4 Binary files /dev/null and b/build/Engine/Components/LightComponent.o differ diff --git a/build/Engine/Components/ModelComponent.o b/build/Engine/Components/ModelComponent.o new file mode 100644 index 0000000..8bd0736 Binary files /dev/null and b/build/Engine/Components/ModelComponent.o differ diff --git a/build/Engine/Components/TransformComponent.o b/build/Engine/Components/TransformComponent.o new file mode 100644 index 0000000..6639163 Binary files /dev/null and b/build/Engine/Components/TransformComponent.o differ diff --git a/build/Engine/Entity/Entity.o b/build/Engine/Entity/Entity.o new file mode 100644 index 0000000..367471c Binary files /dev/null and b/build/Engine/Entity/Entity.o differ diff --git a/build/Engine/engine.o b/build/Engine/engine.o index 56a3314..a345fc9 100644 Binary files a/build/Engine/engine.o and b/build/Engine/engine.o differ diff --git a/imgui.ini b/imgui.ini index 07f66b7..d0e8ff7 100644 --- a/imgui.ini +++ b/imgui.ini @@ -9,14 +9,13 @@ Size=400,400 Collapsed=0 [Window][Editor Panel] -Pos=0,0 -Size=333,800 +Size=272,800 Collapsed=0 DockId=0x00000001,0 [Window][Rendered Output] -Pos=335,0 -Size=945,800 +Pos=247,0 +Size=772,800 Collapsed=0 DockId=0x00000002,0 @@ -25,8 +24,24 @@ Pos=176,231 Size=680,444 Collapsed=0 -[Docking][Data] -DockSpace ID=0x08BD597D Window=0x1BBC0F80 Pos=0,0 Size=1280,800 Split=X Selected=0xB6999AB4 - DockNode ID=0x00000001 Parent=0x08BD597D SizeRef=333,800 Selected=0x5098C5B2 - DockNode ID=0x00000002 Parent=0x08BD597D SizeRef=945,800 CentralNode=1 Selected=0xB6999AB4 +[Window][Entity Inspector] +Pos=1021,0 +Size=259,800 +Collapsed=0 +DockId=0x00000004,0 + +[Window][Entity List] +Pos=0,0 +Size=245,800 +Collapsed=0 +DockId=0x00000005,0 + +[Docking][Data] +DockSpace ID=0x08BD597D Window=0x1BBC0F80 Pos=0,0 Size=1280,800 Split=X Selected=0xB6999AB4 + DockNode ID=0x00000005 Parent=0x08BD597D SizeRef=245,800 Selected=0x5A1EAB5B + DockNode ID=0x00000006 Parent=0x08BD597D SizeRef=1033,800 Split=X + DockNode ID=0x00000003 Parent=0x00000006 SizeRef=772,800 Split=X + DockNode ID=0x00000001 Parent=0x00000003 SizeRef=272,800 Selected=0x5098C5B2 + DockNode ID=0x00000002 Parent=0x00000003 SizeRef=1006,800 CentralNode=1 Selected=0xB6999AB4 + DockNode ID=0x00000004 Parent=0x00000006 SizeRef=259,800 Selected=0x82A01C92 diff --git a/test.cap b/test.cap new file mode 100644 index 0000000..d4e0078 --- /dev/null +++ b/test.cap @@ -0,0 +1,28 @@ +{ + "rdocCaptureSettings": 1, + "settings": { + "autoStart": false, + "commandLine": "", + "environment": [ + ], + "executable": "C:\\Users\\spenc\\OneDrive\\Documents\\GitHub\\Test\\ThreeLab\\Three-Labs.exe", + "inject": false, + "numQueuedFrames": 0, + "options": { + "allowFullscreen": true, + "allowVSync": true, + "apiValidation": false, + "captureAllCmdLists": false, + "captureCallstacks": false, + "captureCallstacksOnlyDraws": false, + "debugOutputMute": true, + "delayForDebugger": 0, + "hookIntoChildren": false, + "refAllResources": false, + "softMemoryLimit": 0, + "verifyBufferAccess": false + }, + "queuedFrameCap": 0, + "workingDir": "" + } +}