From acebe7d7c073cd828213c64f57e9bb26284420e5 Mon Sep 17 00:00:00 2001 From: Anemunt <69436164+darkresident55@users.noreply.github.com> Date: Tue, 9 Dec 2025 17:52:50 -0500 Subject: [PATCH] Yey! Shader Compilation! --- Resources/Shaders/frag.glsl | 3 +- Resources/imgui.ini | 37 ++++--- include/Skybox/Skybox.h | 8 ++ src/Camera.cpp | 42 ++++---- src/Camera.h | 4 + src/Engine.cpp | 133 ++++++++++++++++++++++-- src/Engine.h | 25 ++++- src/EnginePanels.cpp | 201 ++++++++++++++++++++++++++++++++---- src/ProjectManager.cpp | 6 ++ src/Rendering.cpp | 156 +++++++++++++++++++++------- src/Rendering.h | 17 +++ src/SceneObject.h | 2 + src/Skybox/Skybox.cpp | 27 ++++- 13 files changed, 558 insertions(+), 103 deletions(-) diff --git a/Resources/Shaders/frag.glsl b/Resources/Shaders/frag.glsl index 9c6ac12..35f3db5 100644 --- a/Resources/Shaders/frag.glsl +++ b/Resources/Shaders/frag.glsl @@ -16,6 +16,7 @@ uniform vec3 viewPos; uniform vec3 materialColor = vec3(1.0); uniform float ambientStrength = 0.2; +uniform vec3 ambientColor = vec3(1.0); uniform float specularStrength = 0.5; uniform float shininess = 32.0; const int MAX_LIGHTS = 10; @@ -62,7 +63,7 @@ void main() norm = normalize(TBN * mapN); } - vec3 ambient = ambientStrength * baseColor; + vec3 ambient = ambientStrength * ambientColor * baseColor; vec3 lighting = ambient; int count = min(lightCount, MAX_LIGHTS); diff --git a/Resources/imgui.ini b/Resources/imgui.ini index 24d5f8e..7a00c41 100644 --- a/Resources/imgui.ini +++ b/Resources/imgui.ini @@ -15,24 +15,24 @@ Collapsed=0 [Window][DockSpace] Pos=0,21 -Size=1858,1036 +Size=1920,985 Collapsed=0 [Window][Viewport] Pos=306,42 -Size=1203,792 +Size=1265,741 Collapsed=0 DockId=0x00000002,0 [Window][Hierarchy] Pos=0,42 -Size=304,792 +Size=304,741 Collapsed=0 DockId=0x00000001,0 [Window][Inspector] -Pos=1511,42 -Size=347,1015 +Pos=1573,42 +Size=347,964 Collapsed=0 DockId=0x00000008,0 @@ -43,31 +43,42 @@ Collapsed=0 DockId=0x00000006,1 [Window][Console] -Pos=0,836 -Size=754,221 +Pos=0,785 +Size=785,221 Collapsed=0 DockId=0x00000005,0 [Window][Project] -Pos=756,836 -Size=753,221 +Pos=787,785 +Size=784,221 Collapsed=0 DockId=0x00000006,0 [Window][Launcher] -ViewportPos=0,0 -ViewportId=0xF8E7BCBC +Pos=0,0 Size=1000,800 Collapsed=0 +[Window][Camera] +Pos=0,42 +Size=304,741 +Collapsed=0 +DockId=0x00000001,1 + +[Window][Environment] +Pos=1573,42 +Size=347,964 +Collapsed=0 +DockId=0x00000008,1 + [Docking][Data] -DockSpace ID=0xD71539A0 Window=0x3DA2F1DE Pos=62,65 Size=1858,1015 Split=X +DockSpace ID=0xD71539A0 Window=0x3DA2F1DE Pos=0,42 Size=1920,964 Split=X DockNode ID=0x00000007 Parent=0xD71539A0 SizeRef=1509,1015 Split=Y DockNode ID=0x00000003 Parent=0x00000007 SizeRef=1858,792 Split=X DockNode ID=0x00000001 Parent=0x00000003 SizeRef=304,758 Selected=0xBABDAE5E DockNode ID=0x00000002 Parent=0x00000003 SizeRef=694,758 CentralNode=1 Selected=0xC450F867 DockNode ID=0x00000004 Parent=0x00000007 SizeRef=1858,221 Split=X Selected=0xEA83D666 DockNode ID=0x00000005 Parent=0x00000004 SizeRef=929,221 Selected=0xEA83D666 - DockNode ID=0x00000006 Parent=0x00000004 SizeRef=927,221 Selected=0x14395839 + DockNode ID=0x00000006 Parent=0x00000004 SizeRef=927,221 Selected=0x9C21DE82 DockNode ID=0x00000008 Parent=0xD71539A0 SizeRef=347,1015 Selected=0x36DC96AB diff --git a/include/Skybox/Skybox.h b/include/Skybox/Skybox.h index 6d2ee6a..37e6c09 100644 --- a/include/Skybox/Skybox.h +++ b/include/Skybox/Skybox.h @@ -1,15 +1,20 @@ #ifndef SKYBOX_H #define SKYBOX_H +#include + class Shader; class Skybox { private: unsigned int VAO, VBO; Shader* skyboxShader; + std::string vertPath = "Resources/Shaders/skybox_vert.glsl"; + std::string fragPath = "Resources/Shaders/skybox_frag.glsl"; float timeOfDay = 0.5f; // 0.0 = night, 0.25 = sunrise, 0.5 = day, 0.75 = sunset, 1.0 = midnight void setupMesh(); + bool reloadShader(); public: Skybox(); @@ -18,6 +23,9 @@ public: void draw(const float* view, const float* projection); void setTimeOfDay(float time); // 0.0 to 1.0 float getTimeOfDay() const { return timeOfDay; } + void setShaderPaths(const std::string& vert, const std::string& frag); + std::string getVertPath() const { return vertPath; } + std::string getFragPath() const { return fragPath; } }; #endif diff --git a/src/Camera.cpp b/src/Camera.cpp index 000a604..3f627a2 100644 --- a/src/Camera.cpp +++ b/src/Camera.cpp @@ -29,13 +29,9 @@ void Camera::processMouse(double xpos, double ypos) { } void Camera::processKeyboard(float deltaTime, GLFWwindow* window) { - const float CAMERA_SPEED = 5.0f; - const float SPRINT_SPEED = 10.0f; - const float ACCELERATION = 15.0f; - - float currentSpeed = CAMERA_SPEED; + float currentSpeed = moveSpeed; if (glfwGetKey(window, GLFW_KEY_LEFT_SHIFT) == GLFW_PRESS) { - currentSpeed = SPRINT_SPEED; + currentSpeed = sprintSpeed; } glm::vec3 desiredDir(0.0f); @@ -72,21 +68,29 @@ void Camera::processKeyboard(float deltaTime, GLFWwindow* window) { glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); } - glm::vec3 targetVelocity(0.0f); - if (isMoving) { - float length = glm::length(desiredDir); - if (length > 0.0001f) { - desiredDir = desiredDir / length; - targetVelocity = desiredDir * currentSpeed; - } else { - targetVelocity = glm::vec3(0.0f); + if (smoothMovement) { + glm::vec3 targetVelocity(0.0f); + if (isMoving) { + float length = glm::length(desiredDir); + if (length > 0.0001f) { + desiredDir = desiredDir / length; + targetVelocity = desiredDir * currentSpeed; + } + } + + float smoothFactor = 1.0f - std::exp(-acceleration * deltaTime); + velocity = glm::mix(velocity, targetVelocity, smoothFactor); + position += velocity * deltaTime; + } else { + velocity = glm::vec3(0.0f); + if (isMoving) { + float length = glm::length(desiredDir); + if (length > 0.0001f) { + desiredDir /= length; + position += desiredDir * currentSpeed * deltaTime; + } } } - - float smoothFactor = 1.0f - std::exp(-ACCELERATION * deltaTime); - velocity = glm::mix(velocity, targetVelocity, smoothFactor); - - position += velocity * deltaTime; } glm::mat4 Camera::getViewMatrix() const { diff --git a/src/Camera.h b/src/Camera.h index 2a1f2d1..c598d4e 100644 --- a/src/Camera.h +++ b/src/Camera.h @@ -9,6 +9,10 @@ public: glm::vec3 front = glm::vec3(0.0f, 0.0f, -1.0f); glm::vec3 up = glm::vec3(0.0f, 1.0f, 0.0f); glm::vec3 velocity = glm::vec3(0.0f); + float moveSpeed = 5.0f; + float sprintSpeed = 10.0f; + float acceleration = 15.0f; + bool smoothMovement = true; float yaw = -90.0f; float pitch = 0.0f; float speed = CAMERA_SPEED; diff --git a/src/Engine.cpp b/src/Engine.cpp index 63ba2d3..1af0fb8 100644 --- a/src/Engine.cpp +++ b/src/Engine.cpp @@ -10,6 +10,8 @@ struct MaterialFileData { std::string overlay; std::string normal; bool useOverlay = false; + std::string vertexShader; + std::string fragmentShader; }; bool readMaterialFile(const std::string& path, MaterialFileData& outData) { @@ -44,6 +46,10 @@ bool readMaterialFile(const std::string& path, MaterialFileData& outData) { outData.normal = val; } else if (key == "useOverlay") { outData.useOverlay = std::stoi(val) != 0; + } else if (key == "vertexShader") { + outData.vertexShader = val; + } else if (key == "fragmentShader") { + outData.fragmentShader = val; } } return true; @@ -64,6 +70,8 @@ bool writeMaterialFile(const MaterialFileData& data, const std::string& path) { f << "albedo=" << data.albedo << "\n"; f << "overlay=" << data.overlay << "\n"; f << "normal=" << data.normal << "\n"; + f << "vertexShader=" << data.vertexShader << "\n"; + f << "fragmentShader=" << data.fragmentShader << "\n"; return true; } } // namespace @@ -93,6 +101,55 @@ void Engine::DecomposeMatrix(const glm::mat4& matrix, glm::vec3& pos, glm::vec3& rot = glm::eulerAngles(glm::quat_cast(rotMat)); } +void Engine::recordState(const char* /*reason*/) { + SceneSnapshot snap; + snap.objects = sceneObjects; + snap.selectedId = selectedObjectId; + snap.nextId = nextObjectId; + + undoStack.push_back(std::move(snap)); + if (undoStack.size() > 64) { + undoStack.erase(undoStack.begin()); + } + redoStack.clear(); +} + +void Engine::undo() { + if (undoStack.empty()) return; + + SceneSnapshot current; + current.objects = sceneObjects; + current.selectedId = selectedObjectId; + current.nextId = nextObjectId; + + SceneSnapshot snap = undoStack.back(); + undoStack.pop_back(); + + redoStack.push_back(std::move(current)); + sceneObjects = std::move(snap.objects); + selectedObjectId = snap.selectedId; + nextObjectId = snap.nextId; + projectManager.currentProject.hasUnsavedChanges = true; +} + +void Engine::redo() { + if (redoStack.empty()) return; + + SceneSnapshot current; + current.objects = sceneObjects; + current.selectedId = selectedObjectId; + current.nextId = nextObjectId; + + SceneSnapshot snap = redoStack.back(); + redoStack.pop_back(); + + undoStack.push_back(std::move(current)); + sceneObjects = std::move(snap.objects); + selectedObjectId = snap.selectedId; + nextObjectId = snap.nextId; + projectManager.currentProject.hasUnsavedChanges = true; +} + bool Engine::init() { std::cerr << "[DEBUG] Creating window..." << std::endl; editorWindow = window.makeWindow(); @@ -207,6 +264,8 @@ void Engine::run() { if (showInspector) renderInspectorPanel(); if (showFileBrowser) renderFileBrowserPanel(); if (showConsole) renderConsolePanel(); + if (showEnvironmentWindow) renderEnvironmentWindow(); + if (showCameraWindow) renderCameraWindow(); if (showProjectBrowser) renderProjectBrowserPanel(); } @@ -258,6 +317,7 @@ void Engine::shutdown() { } void Engine::importOBJToScene(const std::string& filepath, const std::string& objectName) { + recordState("importOBJ"); std::string errorMsg; int meshId = g_objLoader.loadOBJ(filepath, errorMsg); @@ -292,6 +352,7 @@ void Engine::importOBJToScene(const std::string& filepath, const std::string& ob } void Engine::importModelToScene(const std::string& filepath, const std::string& objectName) { + recordState("importModel"); auto& modelLoader = getModelLoader(); ModelLoadResult result = modelLoader.loadModel(filepath); @@ -336,6 +397,8 @@ void Engine::loadMaterialFromFile(SceneObject& obj) { obj.overlayTexturePath = data.overlay; obj.normalMapPath = data.normal; obj.useOverlay = data.useOverlay; + obj.vertexShaderPath = data.vertexShader; + obj.fragmentShaderPath = data.fragmentShader; addConsoleMessage("Applied material: " + obj.materialPath, ConsoleMessageType::Success); projectManager.currentProject.hasUnsavedChanges = true; } catch (...) { @@ -345,7 +408,9 @@ void Engine::loadMaterialFromFile(SceneObject& obj) { bool Engine::loadMaterialData(const std::string& path, MaterialProperties& props, std::string& albedo, std::string& overlay, - std::string& normal, bool& useOverlay) + std::string& normal, bool& useOverlay, + std::string* vertexShaderOut, + std::string* fragmentShaderOut) { MaterialFileData data; if (!readMaterialFile(path, data)) { @@ -356,12 +421,16 @@ bool Engine::loadMaterialData(const std::string& path, MaterialProperties& props overlay = data.overlay; normal = data.normal; useOverlay = data.useOverlay; + if (vertexShaderOut) *vertexShaderOut = data.vertexShader; + if (fragmentShaderOut) *fragmentShaderOut = data.fragmentShader; return true; } bool Engine::saveMaterialData(const std::string& path, const MaterialProperties& props, const std::string& albedo, const std::string& overlay, - const std::string& normal, bool useOverlay) + const std::string& normal, bool useOverlay, + const std::string& vertexShader, + const std::string& fragmentShader) { MaterialFileData data; data.props = props; @@ -369,6 +438,8 @@ bool Engine::saveMaterialData(const std::string& path, const MaterialProperties& data.overlay = overlay; data.normal = normal; data.useOverlay = useOverlay; + data.vertexShader = vertexShader; + data.fragmentShader = fragmentShader; return writeMaterialFile(data, path); } @@ -384,6 +455,8 @@ void Engine::saveMaterialToFile(const SceneObject& obj) { data.overlay = obj.overlayTexturePath; data.normal = obj.normalMapPath; data.useOverlay = obj.useOverlay; + data.vertexShader = obj.vertexShaderPath; + data.fragmentShader = obj.fragmentShaderPath; if (!writeMaterialFile(data, obj.materialPath)) { addConsoleMessage("Failed to open material for writing: " + obj.materialPath, ConsoleMessageType::Error); @@ -431,16 +504,43 @@ void Engine::handleKeyboardShortcuts() { ctrlNPressed = false; } - if (ImGui::IsKeyPressed(ImGuiKey_Q)) mCurrentGizmoOperation = ImGuizmo::TRANSLATE; - if (ImGui::IsKeyPressed(ImGuiKey_W)) mCurrentGizmoOperation = ImGuizmo::ROTATE; - if (ImGui::IsKeyPressed(ImGuiKey_E)) mCurrentGizmoOperation = ImGuizmo::SCALE; - if (ImGui::IsKeyPressed(ImGuiKey_R)) mCurrentGizmoOperation = ImGuizmo::UNIVERSAL; + if (!cursorLocked) { + if (ImGui::IsKeyPressed(ImGuiKey_Q)) mCurrentGizmoOperation = ImGuizmo::TRANSLATE; + if (ImGui::IsKeyPressed(ImGuiKey_W)) mCurrentGizmoOperation = ImGuizmo::ROTATE; + if (ImGui::IsKeyPressed(ImGuiKey_E)) mCurrentGizmoOperation = ImGuizmo::SCALE; + if (ImGui::IsKeyPressed(ImGuiKey_R)) mCurrentGizmoOperation = ImGuizmo::UNIVERSAL; - if (ImGui::IsKeyPressed(ImGuiKey_Z)) { - mCurrentGizmoMode = (mCurrentGizmoMode == ImGuizmo::LOCAL) ? ImGuizmo::WORLD : ImGuizmo::LOCAL; + if (ImGui::IsKeyPressed(ImGuiKey_Z)) { + mCurrentGizmoMode = (mCurrentGizmoMode == ImGuizmo::LOCAL) ? ImGuizmo::WORLD : ImGuizmo::LOCAL; + } } - if (ImGui::IsKeyPressed(ImGuiKey_LeftCtrl)) useSnap = !useSnap; + static bool snapPressed = false; + if (ImGui::IsKeyPressed(ImGuiKey_X) && !snapPressed) { + useSnap = !useSnap; + snapPressed = true; + } + if (ImGui::IsKeyReleased(ImGuiKey_X)) { + snapPressed = false; + } + + static bool undoPressed = false; + if (ctrlDown && glfwGetKey(editorWindow, GLFW_KEY_Z) == GLFW_PRESS && !undoPressed) { + undo(); + undoPressed = true; + } + if (glfwGetKey(editorWindow, GLFW_KEY_Z) == GLFW_RELEASE) { + undoPressed = false; + } + + static bool redoPressed = false; + if (ctrlDown && glfwGetKey(editorWindow, GLFW_KEY_Y) == GLFW_PRESS && !redoPressed) { + redo(); + redoPressed = true; + } + if (glfwGetKey(editorWindow, GLFW_KEY_Y) == GLFW_RELEASE) { + redoPressed = false; + } } void Engine::OpenProjectPath(const std::string& path) { @@ -497,6 +597,8 @@ void Engine::loadRecentScenes() { sceneObjects.clear(); selectedObjectId = -1; nextObjectId = 0; + undoStack.clear(); + redoStack.clear(); fs::path scenePath = projectManager.currentProject.getSceneFilePath(projectManager.currentProject.currentSceneName); if (fs::exists(scenePath)) { @@ -510,6 +612,7 @@ void Engine::loadRecentScenes() { addConsoleMessage("Default scene not found, starting with a new scene.", ConsoleMessageType::Info); addObject(ObjectType::Cube, "Cube"); } + recordState("sceneLoaded"); fileBrowser.currentPath = projectManager.currentProject.assetsPath; fileBrowser.needsRefresh = true; @@ -537,6 +640,8 @@ void Engine::loadScene(const std::string& sceneName) { fs::path scenePath = projectManager.currentProject.getSceneFilePath(sceneName); if (SceneSerializer::loadScene(scenePath, sceneObjects, nextObjectId)) { + undoStack.clear(); + redoStack.clear(); projectManager.currentProject.currentSceneName = sceneName; projectManager.currentProject.hasUnsavedChanges = false; projectManager.currentProject.saveProjectFile(); @@ -547,6 +652,7 @@ void Engine::loadScene(const std::string& sceneName) { if (!hasDirLight) { addObject(ObjectType::DirectionalLight, "Directional Light"); } + recordState("sceneLoaded"); addConsoleMessage("Loaded scene: " + sceneName, ConsoleMessageType::Success); } else { addConsoleMessage("Error: Failed to load scene: " + sceneName, ConsoleMessageType::Error); @@ -563,6 +669,8 @@ void Engine::createNewScene(const std::string& sceneName) { sceneObjects.clear(); selectedObjectId = -1; nextObjectId = 0; + undoStack.clear(); + redoStack.clear(); projectManager.currentProject.currentSceneName = sceneName; projectManager.currentProject.hasUnsavedChanges = true; @@ -570,11 +678,13 @@ void Engine::createNewScene(const std::string& sceneName) { addObject(ObjectType::Cube, "Cube"); addObject(ObjectType::DirectionalLight, "Directional Light"); saveCurrentScene(); + recordState("newScene"); addConsoleMessage("Created new scene: " + sceneName, ConsoleMessageType::Success); } void Engine::addObject(ObjectType type, const std::string& baseName) { + recordState("addObject"); int id = nextObjectId++; std::string name = baseName + " " + std::to_string(id); sceneObjects.push_back(SceneObject(name, type, id)); @@ -605,6 +715,7 @@ void Engine::duplicateSelected() { [this](const SceneObject& obj) { return obj.id == selectedObjectId; }); if (it != sceneObjects.end()) { + recordState("duplicate"); int id = nextObjectId++; SceneObject newObj(it->name + " (Copy)", it->type, id); newObj.position = it->position + glm::vec3(1.0f, 0.0f, 0.0f); @@ -617,6 +728,8 @@ void Engine::duplicateSelected() { newObj.albedoTexturePath = it->albedoTexturePath; newObj.overlayTexturePath = it->overlayTexturePath; newObj.normalMapPath = it->normalMapPath; + newObj.vertexShaderPath = it->vertexShaderPath; + newObj.fragmentShaderPath = it->fragmentShaderPath; newObj.useOverlay = it->useOverlay; newObj.light = it->light; @@ -630,6 +743,7 @@ void Engine::duplicateSelected() { } void Engine::deleteSelected() { + recordState("delete"); auto it = std::remove_if(sceneObjects.begin(), sceneObjects.end(), [this](const SceneObject& obj) { return obj.id == selectedObjectId; }); @@ -644,6 +758,7 @@ void Engine::deleteSelected() { } void Engine::setParent(int childId, int parentId) { + recordState("reparent"); auto childIt = std::find_if(sceneObjects.begin(), sceneObjects.end(), [childId](const SceneObject& obj) { return obj.id == childId; }); diff --git a/src/Engine.h b/src/Engine.h index 3b9070a..e1a70a4 100644 --- a/src/Engine.h +++ b/src/Engine.h @@ -22,14 +22,24 @@ private: bool cursorLocked = false; // true only while holding right mouse for freelook int viewportWidth = 800; int viewportHeight = 600; + bool gizmoHistoryCaptured = false; // Standalone material inspection cache std::string inspectedMaterialPath; MaterialProperties inspectedMaterial; std::string inspectedAlbedo; std::string inspectedOverlay; std::string inspectedNormal; + std::string inspectedVertShader; + std::string inspectedFragShader; bool inspectedUseOverlay = false; bool inspectedMaterialValid = false; + struct SceneSnapshot { + std::vector objects; + int selectedId = -1; + int nextId = 0; + }; + std::vector undoStack; + std::vector redoStack; std::vector sceneObjects; int selectedObjectId = -1; @@ -70,6 +80,8 @@ private: char fileBrowserSearch[256] = ""; float fileBrowserIconScale = 1.0f; // 0.5 to 2.0 range + bool showEnvironmentWindow = true; + bool showCameraWindow = true; // Private methods SceneObject* getSelectedObject(); @@ -85,6 +97,8 @@ private: void renderNewProjectDialog(); void renderOpenProjectDialog(); void renderMainMenuBar(); + void renderEnvironmentWindow(); + void renderCameraWindow(); void renderHierarchyPanel(); void renderObjectNode(SceneObject& obj, const std::string& filter); void renderFileBrowserPanel(); @@ -117,6 +131,9 @@ private: void setParent(int childId, int parentId); void loadMaterialFromFile(SceneObject& obj); void saveMaterialToFile(const SceneObject& obj); + void recordState(const char* reason = ""); + void undo(); + void redo(); // Console/logging void addConsoleMessage(const std::string& message, ConsoleMessageType type); @@ -125,10 +142,14 @@ private: // Material helpers bool loadMaterialData(const std::string& path, MaterialProperties& props, std::string& albedo, std::string& overlay, - std::string& normal, bool& useOverlay); + std::string& normal, bool& useOverlay, + std::string* vertexShaderOut = nullptr, + std::string* fragmentShaderOut = nullptr); bool saveMaterialData(const std::string& path, const MaterialProperties& props, const std::string& albedo, const std::string& overlay, - const std::string& normal, bool useOverlay); + const std::string& normal, bool useOverlay, + const std::string& vertexShader, + const std::string& fragmentShader); // ImGui setup void setupImGui(); diff --git a/src/EnginePanels.cpp b/src/EnginePanels.cpp index 68b8b06..6c9fb1a 100644 --- a/src/EnginePanels.cpp +++ b/src/EnginePanels.cpp @@ -1110,6 +1110,8 @@ void Engine::renderMainMenuBar() { ImGui::MenuItem("File Browser", nullptr, &showFileBrowser); ImGui::MenuItem("Console", nullptr, &showConsole); ImGui::MenuItem("Project", nullptr, &showProjectBrowser); + ImGui::MenuItem("Environment", nullptr, &showEnvironmentWindow); + ImGui::MenuItem("Camera", nullptr, &showCameraWindow); ImGui::Separator(); if (ImGui::MenuItem("Fullscreen Viewport", "F11", viewportFullscreen)) { viewportFullscreen = !viewportFullscreen; @@ -1271,22 +1273,6 @@ void Engine::renderObjectNode(SceneObject& obj, const std::string& filter) { void Engine::renderInspectorPanel() { ImGui::Begin("Inspector", &showInspector); - // Environment controls - if (Skybox* skybox = renderer.getSkybox()) { - ImGui::PushStyleColor(ImGuiCol_Header, ImVec4(0.25f, 0.45f, 0.55f, 1.0f)); - if (ImGui::CollapsingHeader("Environment", ImGuiTreeNodeFlags_DefaultOpen)) { - float tod = skybox->getTimeOfDay(); - ImGui::TextDisabled("Day/Night Cycle"); - ImGui::SetNextItemWidth(-1); - if (ImGui::SliderFloat("##DayNight", &tod, 0.0f, 1.0f, "%.2f")) { - skybox->setTimeOfDay(tod); - projectManager.currentProject.hasUnsavedChanges = true; - } - } - ImGui::PopStyleColor(); - ImGui::Spacing(); - } - fs::path selectedMaterialPath; bool browserHasMaterial = false; if (!fileBrowser.selectedFile.empty() && fs::exists(fileBrowser.selectedFile)) { @@ -1301,7 +1287,9 @@ void Engine::renderInspectorPanel() { inspectedAlbedo, inspectedOverlay, inspectedNormal, - inspectedUseOverlay + inspectedUseOverlay, + &inspectedVertShader, + &inspectedFragShader ); inspectedMaterialPath = selectedMaterialPath.string(); } @@ -1387,6 +1375,48 @@ void Engine::renderInspectorPanel() { matChanged |= textureField("Detail Map", "PreviewOverlay", inspectedOverlay); matChanged |= textureField("Normal Map", "PreviewNormal", inspectedNormal); + ImGui::Spacing(); + ImGui::TextColored(ImVec4(0.9f, 0.8f, 0.5f, 1.0f), "Shader"); + auto shaderField = [&](const char* label, const char* idSuffix, std::string& path) { + bool changed = false; + ImGui::PushID(idSuffix); + ImGui::TextUnformatted(label); + ImGui::SetNextItemWidth(-140); + char buf[512] = {}; + std::snprintf(buf, sizeof(buf), "%s", path.c_str()); + if (ImGui::InputText("##Path", buf, sizeof(buf))) { + path = buf; + changed = true; + } + ImGui::SameLine(); + if (ImGui::SmallButton("Clear")) { + path.clear(); + changed = true; + } + bool selectionIsShader = false; + if (!fileBrowser.selectedFile.empty() && fs::exists(fileBrowser.selectedFile)) { + selectionIsShader = fileBrowser.getFileCategory(fs::directory_entry(fileBrowser.selectedFile)) == FileCategory::Shader; + } + ImGui::SameLine(); + ImGui::BeginDisabled(!selectionIsShader); + std::string btn = std::string("Use Selection##") + idSuffix; + if (ImGui::SmallButton(btn.c_str())) { + path = fileBrowser.selectedFile.string(); + changed = true; + } + ImGui::EndDisabled(); + ImGui::PopID(); + return changed; + }; + matChanged |= shaderField("Vertex Shader", "PreviewVert", inspectedVertShader); + matChanged |= shaderField("Fragment Shader", "PreviewFrag", inspectedFragShader); + + ImGui::BeginDisabled(inspectedVertShader.empty() && inspectedFragShader.empty()); + if (ImGui::Button("Reload Shader")) { + renderer.forceReloadShader(inspectedVertShader, inspectedFragShader); + } + ImGui::EndDisabled(); + ImGui::Spacing(); if (ImGui::Button("Reload")) { inspectedMaterialValid = loadMaterialData( @@ -1395,7 +1425,9 @@ void Engine::renderInspectorPanel() { inspectedAlbedo, inspectedOverlay, inspectedNormal, - inspectedUseOverlay + inspectedUseOverlay, + &inspectedVertShader, + &inspectedFragShader ); } ImGui::SameLine(); @@ -1406,7 +1438,9 @@ void Engine::renderInspectorPanel() { inspectedAlbedo, inspectedOverlay, inspectedNormal, - inspectedUseOverlay)) + inspectedUseOverlay, + inspectedVertShader, + inspectedFragShader)) { addConsoleMessage("Saved material: " + selectedMaterialPath.string(), ConsoleMessageType::Success); } else { @@ -1427,6 +1461,8 @@ void Engine::renderInspectorPanel() { target->normalMapPath = inspectedNormal; target->useOverlay = inspectedUseOverlay; target->materialPath = selectedMaterialPath.string(); + target->vertexShaderPath = inspectedVertShader; + target->fragmentShaderPath = inspectedFragShader; projectManager.currentProject.hasUnsavedChanges = true; addConsoleMessage("Applied material to " + target->name, ConsoleMessageType::Success); } @@ -1622,6 +1658,48 @@ void Engine::renderInspectorPanel() { materialChanged |= textureField("Detail Map", "ObjOverlay", obj.overlayTexturePath); materialChanged |= textureField("Normal Map", "ObjNormal", obj.normalMapPath); + ImGui::Spacing(); + ImGui::TextColored(ImVec4(0.9f, 0.8f, 0.5f, 1.0f), "Shader"); + auto shaderField = [&](const char* label, const char* idSuffix, std::string& path) { + bool changed = false; + ImGui::PushID(idSuffix); + ImGui::TextUnformatted(label); + ImGui::SetNextItemWidth(-160); + char buf[512] = {}; + std::snprintf(buf, sizeof(buf), "%s", path.c_str()); + if (ImGui::InputText("##Path", buf, sizeof(buf))) { + path = buf; + changed = true; + } + ImGui::SameLine(); + if (ImGui::SmallButton("Clear")) { + path.clear(); + changed = true; + } + bool selectionIsShader = false; + if (!fileBrowser.selectedFile.empty() && fs::exists(fileBrowser.selectedFile)) { + selectionIsShader = fileBrowser.getFileCategory(fs::directory_entry(fileBrowser.selectedFile)) == FileCategory::Shader; + } + ImGui::SameLine(); + ImGui::BeginDisabled(!selectionIsShader); + std::string btn = std::string("Use Selection##") + idSuffix; + if (ImGui::SmallButton(btn.c_str())) { + path = fileBrowser.selectedFile.string(); + changed = true; + } + ImGui::EndDisabled(); + ImGui::PopID(); + return changed; + }; + materialChanged |= shaderField("Vertex Shader", "ObjVert", obj.vertexShaderPath); + materialChanged |= shaderField("Fragment Shader", "ObjFrag", obj.fragmentShaderPath); + + ImGui::BeginDisabled(obj.vertexShaderPath.empty() && obj.fragmentShaderPath.empty()); + if (ImGui::Button("Reload Shader")) { + renderer.forceReloadShader(obj.vertexShaderPath, obj.fragmentShaderPath); + } + ImGui::EndDisabled(); + ImGui::Spacing(); ImGui::Separator(); ImGui::Text("Material Asset"); @@ -1977,6 +2055,10 @@ void Engine::renderViewport() { ); if (ImGuizmo::IsUsing()) { + if (!gizmoHistoryCaptured) { + recordState("gizmo"); + gizmoHistoryCaptured = true; + } float t[3], r[3], s[3]; ImGuizmo::DecomposeMatrixToComponents(glm::value_ptr(modelMatrix), t, r, s); @@ -1985,6 +2067,8 @@ void Engine::renderViewport() { selectedObj->scale = glm::vec3(s[0], s[1], s[2]); projectManager.currentProject.hasUnsavedChanges = true; + } else { + gizmoHistoryCaptured = false; } } @@ -2464,3 +2548,82 @@ void Engine::renderProjectBrowserPanel() { ImGui::End(); } + +void Engine::renderEnvironmentWindow() { + if (!showEnvironmentWindow) return; + ImGui::Begin("Environment", &showEnvironmentWindow); + + Skybox* skybox = renderer.getSkybox(); + if (skybox) { + float tod = skybox->getTimeOfDay(); + ImGui::TextDisabled("Day / Night Cycle"); + ImGui::SetNextItemWidth(-1); + if (ImGui::SliderFloat("##EnvDayNight", &tod, 0.0f, 1.0f, "%.2f")) { + skybox->setTimeOfDay(tod); + projectManager.currentProject.hasUnsavedChanges = true; + } + + static char skyVertBuf[256] = {}; + static char skyFragBuf[256] = {}; + if (skyVertBuf[0] == '\0') std::snprintf(skyVertBuf, sizeof(skyVertBuf), "%s", skybox->getVertPath().c_str()); + if (skyFragBuf[0] == '\0') std::snprintf(skyFragBuf, sizeof(skyFragBuf), "%s", skybox->getFragPath().c_str()); + + ImGui::Separator(); + ImGui::Text("Skybox Shader"); + ImGui::SetNextItemWidth(-1); + if (ImGui::InputText("##SkyVert", skyVertBuf, sizeof(skyVertBuf))) {} + ImGui::SetNextItemWidth(-1); + if (ImGui::InputText("##SkyFrag", skyFragBuf, sizeof(skyFragBuf))) {} + + bool selectionIsShader = false; + if (!fileBrowser.selectedFile.empty() && fs::exists(fileBrowser.selectedFile)) { + selectionIsShader = fileBrowser.getFileCategory(fs::directory_entry(fileBrowser.selectedFile)) == FileCategory::Shader; + } + ImGui::BeginDisabled(!selectionIsShader); + if (ImGui::Button("Use Selection as Vert")) { + std::snprintf(skyVertBuf, sizeof(skyVertBuf), "%s", fileBrowser.selectedFile.string().c_str()); + } + ImGui::SameLine(); + if (ImGui::Button("Use Selection as Frag")) { + std::snprintf(skyFragBuf, sizeof(skyFragBuf), "%s", fileBrowser.selectedFile.string().c_str()); + } + ImGui::EndDisabled(); + if (ImGui::Button("Reload Skybox Shader")) { + skybox->setShaderPaths(skyVertBuf, skyFragBuf); + } + } else { + ImGui::TextDisabled("Skybox not available"); + } + + ImGui::Separator(); + ImGui::Text("Global Ambient"); + glm::vec3 ambient = renderer.getAmbientColor(); + if (ImGui::ColorEdit3("##AmbientColor", &ambient.x, ImGuiColorEditFlags_DisplayRGB)) { + renderer.setAmbientColor(ambient); + projectManager.currentProject.hasUnsavedChanges = true; + } + + ImGui::End(); +} + +void Engine::renderCameraWindow() { + if (!showCameraWindow) return; + ImGui::Begin("Camera", &showCameraWindow); + + ImGui::TextDisabled("Movement"); + ImGui::SetNextItemWidth(-1); + if (ImGui::DragFloat("Base Speed", &camera.moveSpeed, 0.1f, 0.1f, 100.0f, "%.2f")) { + camera.moveSpeed = std::max(0.01f, camera.moveSpeed); + } + ImGui::SetNextItemWidth(-1); + if (ImGui::DragFloat("Sprint Speed", &camera.sprintSpeed, 0.1f, 0.1f, 200.0f, "%.2f")) { + camera.sprintSpeed = std::max(camera.moveSpeed, camera.sprintSpeed); + } + ImGui::Checkbox("Smooth Movement", &camera.smoothMovement); + ImGui::BeginDisabled(!camera.smoothMovement); + ImGui::SetNextItemWidth(-1); + ImGui::DragFloat("Acceleration", &camera.acceleration, 0.1f, 0.1f, 100.0f, "%.2f"); + ImGui::EndDisabled(); + + ImGui::End(); +} diff --git a/src/ProjectManager.cpp b/src/ProjectManager.cpp index 7407859..276681f 100644 --- a/src/ProjectManager.cpp +++ b/src/ProjectManager.cpp @@ -259,6 +259,8 @@ bool SceneSerializer::saveScene(const fs::path& filePath, file << "albedoTex=" << obj.albedoTexturePath << "\n"; file << "overlayTex=" << obj.overlayTexturePath << "\n"; file << "normalMap=" << obj.normalMapPath << "\n"; + file << "vertexShader=" << obj.vertexShaderPath << "\n"; + file << "fragmentShader=" << obj.fragmentShaderPath << "\n"; file << "useOverlay=" << (obj.useOverlay ? 1 : 0) << "\n"; file << "lightColor=" << obj.light.color.r << "," << obj.light.color.g << "," << obj.light.color.b << "\n"; file << "lightIntensity=" << obj.light.intensity << "\n"; @@ -368,6 +370,10 @@ bool SceneSerializer::loadScene(const fs::path& filePath, currentObj->overlayTexturePath = value; } else if (key == "normalMap") { currentObj->normalMapPath = value; + } else if (key == "vertexShader") { + currentObj->vertexShaderPath = value; + } else if (key == "fragmentShader") { + currentObj->fragmentShaderPath = value; } else if (key == "useOverlay") { currentObj->useOverlay = (std::stoi(value) != 0); } else if (key == "lightColor") { diff --git a/src/Rendering.cpp b/src/Rendering.cpp index ae34756..2e58892 100644 --- a/src/Rendering.cpp +++ b/src/Rendering.cpp @@ -411,7 +411,9 @@ const OBJLoader::LoadedMesh* OBJLoader::getMeshInfo(int index) const { // Renderer implementation Renderer::~Renderer() { - delete shader; + shaderCache.clear(); + shader = nullptr; + defaultShader = nullptr; delete texture1; delete texture2; delete cubeMesh; @@ -438,13 +440,21 @@ Texture* Renderer::getTexture(const std::string& path) { } void Renderer::initialize() { - shader = new Shader("Resources/Shaders/vert.glsl", "Resources/Shaders/frag.glsl"); + shader = new Shader(defaultVertPath.c_str(), defaultFragPath.c_str()); + defaultShader = shader; if (shader->ID == 0) { std::cerr << "Shader compilation failed!\n"; delete shader; shader = nullptr; throw std::runtime_error("Shader error"); } + ShaderEntry entry; + entry.shader.reset(defaultShader); + entry.vertPath = defaultVertPath; + entry.fragPath = defaultFragPath; + if (fs::exists(defaultVertPath)) entry.vertTime = fs::last_write_time(defaultVertPath); + if (fs::exists(defaultFragPath)) entry.fragTime = fs::last_write_time(defaultFragPath); + shaderCache[defaultVertPath + "|" + defaultFragPath] = std::move(entry); texture1 = new Texture("Resources/Textures/container.jpg"); texture2 = new Texture("Resources/Textures/awesomeface.png"); @@ -463,6 +473,86 @@ void Renderer::initialize() { glEnable(GL_DEPTH_TEST); } +Shader* Renderer::getShader(const std::string& vert, const std::string& frag) { + std::string vPath = vert.empty() ? defaultVertPath : vert; + std::string fPath = frag.empty() ? defaultFragPath : frag; + std::string key = vPath + "|" + fPath; + + auto reloadEntry = [&](ShaderEntry& e) -> Shader* { + std::unique_ptr newShader = std::make_unique(vPath.c_str(), fPath.c_str()); + if (!newShader || newShader->ID == 0) { + std::cerr << "Shader reload failed for " << key << ", falling back to default\n"; + return defaultShader; + } + e.shader = std::move(newShader); + e.vertPath = vPath; + e.fragPath = fPath; + if (fs::exists(vPath)) e.vertTime = fs::last_write_time(vPath); + if (fs::exists(fPath)) e.fragTime = fs::last_write_time(fPath); + return e.shader.get(); + }; + + auto it = shaderCache.find(key); + if (it != shaderCache.end()) { + ShaderEntry& entry = it->second; + if (autoReloadShaders) { + bool changed = false; + if (fs::exists(vPath)) { + auto t = fs::last_write_time(vPath); + if (t != entry.vertTime) { changed = true; entry.vertTime = t; } + } + if (fs::exists(fPath)) { + auto t = fs::last_write_time(fPath); + if (t != entry.fragTime) { changed = true; entry.fragTime = t; } + } + if (changed) { + return reloadEntry(entry); + } + } + return entry.shader ? entry.shader.get() : defaultShader; + } + + ShaderEntry entry; + entry.vertPath = vPath; + entry.fragPath = fPath; + if (fs::exists(vPath)) entry.vertTime = fs::last_write_time(vPath); + if (fs::exists(fPath)) entry.fragTime = fs::last_write_time(fPath); + entry.shader = std::make_unique(vPath.c_str(), fPath.c_str()); + if (!entry.shader || entry.shader->ID == 0) { + std::cerr << "Shader compile failed for " << key << ", using default\n"; + shaderCache[key] = std::move(entry); + return defaultShader; + } + Shader* ptr = entry.shader.get(); + shaderCache[key] = std::move(entry); + return ptr; +} + +bool Renderer::forceReloadShader(const std::string& vert, const std::string& frag) { + std::string vPath = vert.empty() ? defaultVertPath : vert; + std::string fPath = frag.empty() ? defaultFragPath : frag; + std::string key = vPath + "|" + fPath; + auto it = shaderCache.find(key); + if (it != shaderCache.end()) { + shaderCache.erase(it); + } + ShaderEntry entry; + entry.vertPath = vPath; + entry.fragPath = fPath; + if (fs::exists(vPath)) entry.vertTime = fs::last_write_time(vPath); + if (fs::exists(fPath)) entry.fragTime = fs::last_write_time(fPath); + entry.shader = std::make_unique(vPath.c_str(), fPath.c_str()); + if (!entry.shader || entry.shader->ID == 0) { + std::cerr << "Shader force reload failed for " << key << "\n"; + return false; + } + if (vPath == defaultVertPath && fPath == defaultFragPath) { + defaultShader = entry.shader.get(); + } + shaderCache[key] = std::move(entry); + return true; +} + void Renderer::setupFBO() { glGenFramebuffers(1, &framebuffer); glBindFramebuffer(GL_FRAMEBUFFER, framebuffer); @@ -612,17 +702,8 @@ void Renderer::renderObject(const SceneObject& obj) { } void Renderer::renderScene(const Camera& camera, const std::vector& sceneObjects) { - if (!shader) return; - shader->use(); - shader->setMat4("view", camera.getViewMatrix()); - shader->setMat4("projection", glm::perspective(glm::radians(FOV), (float)currentWidth / (float)currentHeight, NEAR_PLANE, FAR_PLANE)); - shader->setVec3("viewPos", camera.position); - shader->setFloat("ambientStrength", 0.25f); - shader->setFloat("specularStrength", 0.8f); - shader->setFloat("shininess", 64.0f); - shader->setFloat("mixAmount", 0.3f); + if (!defaultShader) return; - // Collect up to 10 lights struct LightUniform { int type = 0; // 0 dir,1 point,2 spot glm::vec3 dir = glm::vec3(0.0f, -1.0f, 0.0f); @@ -649,7 +730,6 @@ void Renderer::renderScene(const Camera& camera, const std::vector& std::vector lights; lights.reserve(10); - // Add directionals first, then spots, then points for (const auto& obj : sceneObjects) { if (obj.light.enabled && obj.type == ObjectType::DirectionalLight) { LightUniform l; @@ -692,36 +772,38 @@ void Renderer::renderScene(const Camera& camera, const std::vector& } } } - int count = static_cast(lights.size()); - shader->setInt("lightCount", count); - for (int i = 0; i < count; ++i) { - const auto& l = lights[i]; - std::string idx = "[" + std::to_string(i) + "]"; - shader->setInt("lightTypeArr" + idx, l.type); - shader->setVec3("lightDirArr" + idx, l.dir); - shader->setVec3("lightPosArr" + idx, l.pos); - shader->setVec3("lightColorArr" + idx, l.color); - shader->setFloat("lightIntensityArr" + idx, l.intensity); - shader->setFloat("lightRangeArr" + idx, l.range); - shader->setFloat("lightInnerCosArr" + idx, l.inner); - shader->setFloat("lightOuterCosArr" + idx, l.outer); - } - - // Bind base textures once per frame (used by objects) - if (texture1) texture1->Bind(0); - else glBindTexture(GL_TEXTURE_2D, 0); - if (texture2) texture2->Bind(1); - else glBindTexture(GL_TEXTURE_2D, 0); - - if (texture1) texture1->Bind(0); - if (texture2) texture2->Bind(1); for (const auto& obj : sceneObjects) { - // Skip light types in the main render pass (they are handled as gizmos) + // Skip light gizmo-only types if (obj.type == ObjectType::PointLight || obj.type == ObjectType::SpotLight || obj.type == ObjectType::AreaLight) { continue; } + Shader* active = getShader(obj.vertexShaderPath, obj.fragmentShaderPath); + if (!active) continue; + shader = active; + shader->use(); + + shader->setMat4("view", camera.getViewMatrix()); + shader->setMat4("projection", glm::perspective(glm::radians(FOV), (float)currentWidth / (float)currentHeight, NEAR_PLANE, FAR_PLANE)); + shader->setVec3("viewPos", camera.position); + shader->setVec3("ambientColor", ambientColor); + shader->setVec3("ambientColor", ambientColor); + + shader->setInt("lightCount", static_cast(lights.size())); + for (size_t i = 0; i < lights.size() && i < 10; ++i) { + const auto& l = lights[i]; + std::string idx = "[" + std::to_string(i) + "]"; + shader->setInt("lightTypeArr" + idx, l.type); + shader->setVec3("lightDirArr" + idx, l.dir); + shader->setVec3("lightPosArr" + idx, l.pos); + shader->setVec3("lightColorArr" + idx, l.color); + shader->setFloat("lightIntensityArr" + idx, l.intensity); + shader->setFloat("lightRangeArr" + idx, l.range); + shader->setFloat("lightInnerCosArr" + idx, l.inner); + shader->setFloat("lightOuterCosArr" + idx, l.outer); + } + glm::mat4 model = glm::mat4(1.0f); model = glm::translate(model, obj.position); model = glm::rotate(model, glm::radians(obj.rotation.x), glm::vec3(1.0f, 0.0f, 0.0f)); diff --git a/src/Rendering.h b/src/Rendering.h index 5a7b0ac..82995a3 100644 --- a/src/Rendering.h +++ b/src/Rendering.h @@ -60,9 +60,22 @@ private: unsigned int framebuffer = 0, viewportTexture = 0, rbo = 0; int currentWidth = 800, currentHeight = 600; Shader* shader = nullptr; + Shader* defaultShader = nullptr; Texture* texture1 = nullptr; Texture* texture2 = nullptr; std::unordered_map> textureCache; + struct ShaderEntry { + std::unique_ptr shader; + fs::file_time_type vertTime; + fs::file_time_type fragTime; + std::string vertPath; + std::string fragPath; + }; + std::unordered_map shaderCache; + std::string defaultVertPath = "Resources/Shaders/vert.glsl"; + std::string defaultFragPath = "Resources/Shaders/frag.glsl"; + bool autoReloadShaders = true; + glm::vec3 ambientColor = glm::vec3(0.2f, 0.2f, 0.2f); Mesh* cubeMesh = nullptr; Mesh* sphereMesh = nullptr; Mesh* capsuleMesh = nullptr; @@ -76,6 +89,10 @@ public: void initialize(); Texture* getTexture(const std::string& path); + Shader* getShader(const std::string& vert, const std::string& frag); + bool forceReloadShader(const std::string& vert, const std::string& frag); + void setAmbientColor(const glm::vec3& color) { ambientColor = color; } + glm::vec3 getAmbientColor() const { return ambientColor; } void resize(int w, int h); int getWidth() const { return currentWidth; } int getHeight() const { return currentHeight; } diff --git a/src/SceneObject.h b/src/SceneObject.h index d974de5..7960c79 100644 --- a/src/SceneObject.h +++ b/src/SceneObject.h @@ -67,6 +67,8 @@ public: std::string albedoTexturePath; std::string overlayTexturePath; std::string normalMapPath; + std::string vertexShaderPath; + std::string fragmentShaderPath; bool useOverlay = false; LightComponent light; // Only used when type is a light diff --git a/src/Skybox/Skybox.cpp b/src/Skybox/Skybox.cpp index c5b83a5..7d34cab 100644 --- a/src/Skybox/Skybox.cpp +++ b/src/Skybox/Skybox.cpp @@ -2,6 +2,7 @@ #include "../../include/Shaders/Shader.h" #include #include +#include #include "../../src/ThirdParty/glm/glm.hpp" #include "../../src/ThirdParty/glm/gtc/type_ptr.hpp" @@ -52,12 +53,12 @@ float skyboxVertices[] = { }; Skybox::Skybox() { - skyboxShader = new Shader("Resources/Shaders/skybox_vert.glsl", "Resources/Shaders/skybox_frag.glsl"); + skyboxShader = new Shader(vertPath.c_str(), fragPath.c_str()); setupMesh(); } Skybox::~Skybox() { - delete skyboxShader; + if (skyboxShader) delete skyboxShader; glDeleteVertexArrays(1, &VAO); glDeleteBuffers(1, &VBO); } @@ -80,6 +81,26 @@ void Skybox::setTimeOfDay(float time) { timeOfDay = time; } +bool Skybox::reloadShader() { + if (skyboxShader) { + delete skyboxShader; + skyboxShader = nullptr; + } + skyboxShader = new Shader(vertPath.c_str(), fragPath.c_str()); + return skyboxShader && skyboxShader->ID != 0; +} + +void Skybox::setShaderPaths(const std::string& vert, const std::string& frag) { + if (!vert.empty()) vertPath = vert; + if (!frag.empty()) fragPath = frag; + if (!reloadShader()) { + std::cerr << "Failed to reload skybox shader, reverting to defaults\n"; + vertPath = "Resources/Shaders/skybox_vert.glsl"; + fragPath = "Resources/Shaders/skybox_frag.glsl"; + reloadShader(); + } +} + void Skybox::draw(const float* view, const float* projection) { // Properly reconstruct the view matrix from the float array glm::mat4 viewMat = glm::make_mat4(view); @@ -98,4 +119,4 @@ void Skybox::draw(const float* view, const float* projection) { glDrawArrays(GL_TRIANGLES, 0, 36); glBindVertexArray(0); glDepthFunc(GL_LESS); -} \ No newline at end of file +}