#include "../Engine/Engine.h" #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" #include #include #include "../Engine/Components/Component.h" // For interface base 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, 8.0f); float yaw = -90.0f; float pitch = 0.0f; float speed = 5.0f; float sensitivity = 0.1f; glm::mat4 view = glm::mat4(1.0f); glm::mat4 projection = glm::mat4(1.0f); }; EditorCamera editorCamera; void ProcessEditorCamera(GLFWwindow *window, float deltaTime) { glm::vec3 front; front.x = cos(glm::radians(editorCamera.yaw)) * cos(glm::radians(editorCamera.pitch)); front.y = sin(glm::radians(editorCamera.pitch)); 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) editorCamera.position -= front * editorCamera.speed * deltaTime; if (glfwGetKey(window, GLFW_KEY_A) == GLFW_PRESS) 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; glfwGetCursorPos(window, &xpos, &ypos); if (lastX == 0 && lastY == 0) { lastX = xpos; lastY = ypos; } float offsetX = (float)(xpos - lastX); float offsetY = (float)(lastY - ypos); lastX = xpos; lastY = ypos; editorCamera.yaw += offsetX * editorCamera.sensitivity; editorCamera.pitch += offsetY * editorCamera.sensitivity; if (editorCamera.pitch > 89.0f) editorCamera.pitch = 89.0f; if (editorCamera.pitch < -89.0f) editorCamera.pitch = -89.0f; } else { lastX = lastY = 0; } editorCamera.view = glm::lookAt(editorCamera.position, editorCamera.position + front, glm::vec3(0, 1, 0)); } int main() { if (!Engine::Init()) return 1; // Load the light icon. LoadIconTexture("assets/icons/light.png", g_LightIconTex, g_LightIconImTex); 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); 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; // 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(light1); entities.push_back(light2); Entity *selectedEntity = nullptr; int selectedIndex = -1; // Variables for the "Change Model" popup. static bool showModelPopup = false; static char newModelPath[256] = ""; float lastFrameTime = (float)glfwGetTime(); while (!glfwWindowShouldClose(Engine::GetWindow())) { float currentFrameTime = (float)glfwGetTime(); float deltaTime = currentFrameTime - lastFrameTime; lastFrameTime = currentFrameTime; ProcessEditorCamera(Engine::GetWindow(), deltaTime); int winWidth, winHeight; glfwGetFramebufferSize(Engine::GetWindow(), &winWidth, &winHeight); editorCamera.projection = glm::perspective(glm::radians(45.0f), (float)winWidth / winHeight, 0.1f, 100.0f); Engine::ResizeFramebuffer(winWidth, winHeight); ImTextureID offscreenTexture = Engine::RenderScene(editorCamera.view, editorCamera.projection, editorCamera.position, entities); glBindFramebuffer(GL_FRAMEBUFFER, 0); glViewport(0, 0, winWidth, winHeight); glClearColor(0.0f, 0.0f, 0.0f, 1.0f); glClear(GL_COLOR_BUFFER_BIT); ImGui_ImplOpenGL3_NewFrame(); ImGui_ImplGlfw_NewFrame(); ImGui::NewFrame(); ImGui::DockSpaceOverViewport(0, ImGui::GetMainViewport(), ImGuiDockNodeFlags_PassthruCentralNode); // Left Panel: Entity List with Add/Remove buttons. 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)) { selectedEntity = entities[i]; selectedIndex = i; } } if (ImGui::Button("Add Cube")) { Entity *newCube = new Entity(EntityType::CUBE); newCube->transform.position = glm::vec3(0.0f); newCube->modelComponent = new ModelComponent(); // Default global model properties. newCube->modelComponent->diffuseColor = glm::vec3(1.0f); newCube->modelComponent->specularColor = glm::vec3(1.0f); newCube->modelComponent->shininess = 32.0f; entities.push_back(newCube); } ImGui::SameLine(); if (ImGui::Button("Add Light")) { Entity *newLight = new Entity(EntityType::LIGHT); newLight->transform.position = glm::vec3(0.0f); newLight->lightComponent = new LightComponent(); newLight->lightComponent->color = glm::vec3(1.0f); newLight->lightComponent->intensity = 1.0f; entities.push_back(newLight); } if (selectedEntity && ImGui::Button("Remove Selected")) { // Remove selected entity. auto it = std::find(entities.begin(), entities.end(), selectedEntity); if (it != entities.end()) { delete *it; entities.erase(it); selectedEntity = nullptr; selectedIndex = -1; } } ImGui::End(); // 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); // Button to open model editor popup. if (ImGui::Button("Change Model")) { showModelPopup = true; // Pre-fill popup with current model path. strncpy(newModelPath, selectedEntity->modelComponent->modelPath.c_str(), sizeof(newModelPath)); } } 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(); // Model Editor Popup. if (showModelPopup && selectedEntity && selectedEntity->modelComponent) { ImGui::OpenPopup("Edit Model"); showModelPopup = false; } if (ImGui::BeginPopupModal("Edit Model", NULL, ImGuiWindowFlags_AlwaysAutoResize)) { ImGui::InputText("Model Path", newModelPath, sizeof(newModelPath)); if (ImGui::Button("Load Model", ImVec2(120, 0))) { // Update the model component with the new model path. selectedEntity->modelComponent->LoadModel(newModelPath); ImGui::CloseCurrentPopup(); } ImGui::SameLine(); if (ImGui::Button("Cancel", ImVec2(120, 0))) { ImGui::CloseCurrentPopup(); } ImGui::Separator(); ImGui::Text("Submesh Textures:"); // Display a grid of textures. if (selectedEntity->modelComponent->meshes.empty()) { ImGui::Text("None"); } else { int columns = 12; // Change this value for a different number of columns. int count = 0; for (size_t i = 0; i < selectedEntity->modelComponent->meshes.size(); i++) { if (count % columns != 0) ImGui::SameLine(); // Convert the diffuse texture to an ImTextureID. ImTextureID texID = (ImTextureID)(intptr_t)(selectedEntity->modelComponent->meshes[i].diffuseTexture); // Display the texture image with a fixed size. ImGui::Image(texID, ImVec2(64, 64)); count++; } } ImGui::EndPopup(); } // New Panel: File Operations (Save/Load). ImGui::Begin("Scene File"); // --- List Files in Scene Directory --- static std::vector sceneFiles; static int selectedSceneFile = -1; if (ImGui::Button("Refresh Files")) { sceneFiles.clear(); std::cout << "Refreshing" << std::endl; for (const auto &entry : std::filesystem::directory_iterator("./assets/scenes/")) { if (entry.is_regular_file() && entry.path().extension() == ".yaml") { sceneFiles.push_back(entry.path().filename().string()); } } } ImGui::Text("Available Scenes:"); for (int i = 0; i < sceneFiles.size(); i++) { if (ImGui::Selectable(sceneFiles[i].c_str(), selectedSceneFile == i)) { selectedSceneFile = i; } } // --- Specify New File Name for Saving --- static char newSceneFileName[128] = "new_scene.yaml"; ImGui::InputText("New Scene File", newSceneFileName, IM_ARRAYSIZE(newSceneFileName)); // --- Save Scene --- if (ImGui::Button("Save")) { std::cout << "[Info] Saving" << std::endl; YAML::Emitter out; out << YAML::BeginMap; // Save an array of entities. out << YAML::Key << "entities" << YAML::Value << YAML::BeginSeq; for (auto e : entities) { out << YAML::BeginMap; out << YAML::Key << "type" << YAML::Value << (e->GetType() == EntityType::CUBE ? "cube" : "light"); out << YAML::Key << "transform" << YAML::Value << e->transform.Save(); if (e->modelComponent) { out << YAML::Key << "model" << YAML::Value << e->modelComponent->Save(); } if (e->lightComponent) { out << YAML::Key << "light" << YAML::Value << e->lightComponent->Save(); } out << YAML::EndMap; } out << YAML::EndSeq; out << YAML::EndMap; // Save file to the scenes directory. std::string path = std::string("./assets/scenes/") + newSceneFileName; std::ofstream fout(path); fout << out.c_str(); fout.close(); std::cout << "[Done] Saving" << std::endl; } // --- Load Scene (delete & recreate entities) --- if (ImGui::Button("Load")) { std::cout << "[Info] Loading" << std::endl; // Only proceed if a file is selected from the list. if (selectedSceneFile >= 0 && selectedSceneFile < sceneFiles.size()) { std::string path = "./assests/scenes/" + sceneFiles[selectedSceneFile]; YAML::Node node = YAML::LoadFile(path); if (node["entities"]) { // Delete current entities. for (auto e : entities) { delete e; // Assumes deletion handles components properly. } entities.clear(); YAML::Node entitiesNode = node["entities"]; for (auto entityNode : entitiesNode) { std::string type = entityNode["type"].as(); Entity *newEntity = nullptr; // Create entity based on type. if (type == "cube") { newEntity = new Entity(EntityType::CUBE); } else if (type == "light") { newEntity = new Entity(EntityType::LIGHT); } if (newEntity) { // Load transform. if (entityNode["transform"]) newEntity->transform.Load(entityNode["transform"]); // Create and load model component for cubes. if (newEntity->GetType() == EntityType::CUBE && entityNode["model"]) { newEntity->modelComponent = new ModelComponent(); newEntity->modelComponent->Load(entityNode["model"]); } // Create and load light component for lights. if (newEntity->GetType() == EntityType::LIGHT && entityNode["light"]) { newEntity->lightComponent = new LightComponent(); newEntity->lightComponent->Load(entityNode["light"]); } entities.push_back(newEntity); } } } std::cout << "[Done] Loading" << std::endl; } } ImGui::End(); // Bottom Panel: Rendered Output. ImGui::Begin("Rendered Output"); ImVec2 viewportSize = ImGui::GetContentRegionAvail(); ImGui::Image(offscreenTexture, viewportSize, ImVec2(0, 1), ImVec2(1, 0)); ImGui::End(); ImGui::Render(); ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData()); glfwSwapBuffers(Engine::GetWindow()); glfwPollEvents(); } for (auto e : entities) delete e; ImGui_ImplOpenGL3_Shutdown(); ImGui_ImplGlfw_Shutdown(); ImGui::DestroyContext(); Engine::Shutdown(); return 0; }