From 655d4cce4919298b69d795c00f6a3dc6394b1ae2 Mon Sep 17 00:00:00 2001 From: Anemunt <69436164+darkresident55@users.noreply.github.com> Date: Thu, 18 Dec 2025 16:38:45 -0500 Subject: [PATCH] added Git Package Manager --- Resources/imgui.ini | 28 ++- src/EditorUI.cpp | 9 +- src/EditorUI.h | 3 +- src/Engine.cpp | 8 +- src/Engine.h | 5 + src/EnginePanels.cpp | 500 ++++++++++++++++++++++++++++++++++------- src/PackageManager.cpp | 481 +++++++++++++++++++++++++++++++++++++++ src/PackageManager.h | 64 ++++++ src/ProjectManager.cpp | 3 + src/Rendering.cpp | 74 +++--- src/Rendering.h | 5 +- src/SceneObject.h | 1 + 12 files changed, 1057 insertions(+), 124 deletions(-) create mode 100644 src/PackageManager.cpp create mode 100644 src/PackageManager.h diff --git a/Resources/imgui.ini b/Resources/imgui.ini index 98994db..2d561b2 100644 --- a/Resources/imgui.ini +++ b/Resources/imgui.ini @@ -20,13 +20,13 @@ Collapsed=0 [Window][Viewport] Pos=306,46 -Size=1265,737 +Size=1265,739 Collapsed=0 DockId=0x00000002,0 [Window][Hierarchy] Pos=0,46 -Size=304,737 +Size=304,739 Collapsed=0 DockId=0x00000001,0 @@ -43,14 +43,14 @@ Collapsed=0 DockId=0x00000006,1 [Window][Console] -Pos=0,785 -Size=785,221 +Pos=0,787 +Size=785,219 Collapsed=0 DockId=0x00000005,0 [Window][Project] -Pos=787,785 -Size=784,221 +Pos=787,787 +Size=784,219 Collapsed=0 DockId=0x00000006,0 @@ -61,7 +61,7 @@ Collapsed=0 [Window][Camera] Pos=0,46 -Size=304,737 +Size=304,739 Collapsed=0 DockId=0x00000001,1 @@ -79,18 +79,24 @@ DockId=0x00000006,1 [Window][Game Viewport] Pos=306,46 -Size=1265,737 +Size=1265,739 Collapsed=0 DockId=0x00000002,1 +[Window][Project Settings] +Pos=306,46 +Size=1265,739 +Collapsed=0 +DockId=0x00000002,2 + [Docking][Data] DockSpace ID=0xD71539A0 Window=0x3DA2F1DE Pos=0,46 Size=1920,960 Split=X DockNode ID=0x00000007 Parent=0xD71539A0 SizeRef=1509,1015 Split=Y - DockNode ID=0x00000003 Parent=0x00000007 SizeRef=1858,792 Split=X + DockNode ID=0x00000003 Parent=0x00000007 SizeRef=1858,739 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=0x00000004 Parent=0x00000007 SizeRef=1858,219 Split=X Selected=0xEA83D666 DockNode ID=0x00000005 Parent=0x00000004 SizeRef=929,221 Selected=0xEA83D666 - DockNode ID=0x00000006 Parent=0x00000004 SizeRef=927,221 Selected=0xDA0DCE3C + DockNode ID=0x00000006 Parent=0x00000004 SizeRef=927,221 Selected=0x9C21DE82 DockNode ID=0x00000008 Parent=0xD71539A0 SizeRef=347,1015 Selected=0x36DC96AB diff --git a/src/EditorUI.cpp b/src/EditorUI.cpp index f173f94..d8d4fe8 100644 --- a/src/EditorUI.cpp +++ b/src/EditorUI.cpp @@ -271,7 +271,7 @@ void applyModernTheme() { style.TabBorderSize = 1.0f; } -void setupDockspace() { +void setupDockspace(const std::function& menuBarContent) { static bool dockspaceOpen = true; static ImGuiDockNodeFlags dockspaceFlags = ImGuiDockNodeFlags_None; @@ -291,6 +291,13 @@ void setupDockspace() { ImGui::Begin("DockSpace", &dockspaceOpen, windowFlags); ImGui::PopStyleVar(3); + if (ImGui::BeginMenuBar()) { + if (menuBarContent) { + menuBarContent(); + } + ImGui::EndMenuBar(); + } + ImGuiID dockspaceId = ImGui::GetID("MainDockspace"); ImGui::DockSpace(dockspaceId, ImVec2(0.0f, 0.0f), dockspaceFlags); diff --git a/src/EditorUI.h b/src/EditorUI.h index 85a559b..1264e8e 100644 --- a/src/EditorUI.h +++ b/src/EditorUI.h @@ -1,5 +1,6 @@ #pragma once +#include #include "Common.h" enum class FileBrowserViewMode { @@ -61,4 +62,4 @@ public: void applyModernTheme(); // Setup ImGui dockspace for the editor -void setupDockspace(); +void setupDockspace(const std::function& menuBarContent = nullptr); diff --git a/src/Engine.cpp b/src/Engine.cpp index 62a8db7..9b9b068 100644 --- a/src/Engine.cpp +++ b/src/Engine.cpp @@ -380,7 +380,7 @@ void Engine::run() { } renderLauncher(); } else { - setupDockspace(); + setupDockspace([this]() { renderPlayControlsBar(); }); renderMainMenuBar(); if (!viewportFullscreen) { @@ -910,6 +910,8 @@ void Engine::OpenProjectPath(const std::string& path) { fs::create_directories(projectManager.currentProject.scenesPath); } + packageManager.setProjectRoot(projectManager.currentProject.projectPath); + if (!initRenderer()) { addConsoleMessage("Error: Failed to initialize renderer!", ConsoleMessageType::Error); showLauncher = true; @@ -948,6 +950,8 @@ void Engine::createNewProject(const char* name, const char* location) { projectManager.addToRecentProjects(name, (newProject.projectPath / "project.modu").string()); + packageManager.setProjectRoot(projectManager.currentProject.projectPath); + if (!initRenderer()) { logToConsole("Error: Failed to initialize renderer!"); return; @@ -1329,6 +1333,8 @@ void Engine::compileScriptFile(const fs::path& scriptPath) { return; } + packageManager.applyToBuildConfig(config); + ScriptBuildCommands commands; if (!scriptCompiler.makeCommands(config, scriptPath, commands, error)) { lastCompileSuccess = false; diff --git a/src/Engine.h b/src/Engine.h index ef78478..3221c00 100644 --- a/src/Engine.h +++ b/src/Engine.h @@ -11,6 +11,7 @@ #include "ScriptRuntime.h" #include "PhysicsSystem.h" #include "AudioSystem.h" +#include "PackageManager.h" #include "../include/Window/Window.h" void window_size_callback(GLFWwindow* window, int width, int height); @@ -71,6 +72,7 @@ private: int draggedObjectId = -1; ProjectManager projectManager; + PackageManager packageManager; bool showLauncher = true; bool showNewSceneDialog = false; bool showSaveSceneAsDialog = false; @@ -97,6 +99,8 @@ private: int previewCameraId = -1; bool gameViewCursorLocked = false; bool gameViewportFocused = false; + bool showUITextOverlay = false; + bool showCanvasOverlay = false; int activePlayerId = -1; MeshBuilder meshBuilder; char meshBuilderPath[260] = ""; @@ -142,6 +146,7 @@ private: void renderNewProjectDialog(); void renderOpenProjectDialog(); void renderMainMenuBar(); + void renderPlayControlsBar(); void renderEnvironmentWindow(); void renderCameraWindow(); void renderHierarchyPanel(); diff --git a/src/EnginePanels.cpp b/src/EnginePanels.cpp index 5e73a2c..1177d04 100644 --- a/src/EnginePanels.cpp +++ b/src/EnginePanels.cpp @@ -10,6 +10,9 @@ #include #include #include +#include +#include +#include #ifdef _WIN32 #include @@ -298,6 +301,106 @@ namespace FileIcons { } } +namespace ImGui { + +// Animated progress bar that keeps circles moving while work happens in the background. +bool BufferingBar(const char* label, float value, const ImVec2& size_arg, const ImU32& bg_col, const ImU32& fg_col) { + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return false; + + ImGuiContext& g = *GImGui; + const ImGuiStyle& style = g.Style; + const ImGuiID id = window->GetID(label); + + ImVec2 pos = window->DC.CursorPos; + ImVec2 size = size_arg; + size.x -= style.FramePadding.x * 2; + + const ImRect bb(pos, ImVec2(pos.x + size.x, pos.y + size.y)); + ItemSize(bb, style.FramePadding.y); + if (!ItemAdd(bb, id)) + return false; + + const float circleStart = size.x * 0.7f; + const float circleEnd = size.x; + const float circleWidth = circleEnd - circleStart; + + window->DrawList->AddRectFilled(bb.Min, ImVec2(pos.x + circleStart, bb.Max.y), bg_col); + window->DrawList->AddRectFilled(bb.Min, ImVec2(pos.x + circleStart * value, bb.Max.y), fg_col); + + const float t = g.Time; + const float r = size.y / 2; + const float speed = 1.5f; + + const float a = speed * 0; + const float b = speed * 0.333f; + const float c = speed * 0.666f; + + const float o1 = (circleWidth + r) * (t + a - speed * (int)((t + a) / speed)) / speed; + const float o2 = (circleWidth + r) * (t + b - speed * (int)((t + b) / speed)) / speed; + const float o3 = (circleWidth + r) * (t + c - speed * (int)((t + c) / speed)) / speed; + + window->DrawList->AddCircleFilled(ImVec2(pos.x + circleEnd - o1, bb.Min.y + r), r, bg_col); + window->DrawList->AddCircleFilled(ImVec2(pos.x + circleEnd - o2, bb.Min.y + r), r, bg_col); + window->DrawList->AddCircleFilled(ImVec2(pos.x + circleEnd - o3, bb.Min.y + r), r, bg_col); + return true; +} + +// Simple loading spinner for indeterminate tasks. +bool Spinner(const char* label, float radius, int thickness, const ImU32& color) { + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return false; + + ImGuiContext& g = *GImGui; + const ImGuiStyle& style = g.Style; + const ImGuiID id = window->GetID(label); + + ImVec2 pos = window->DC.CursorPos; + ImVec2 size((radius) * 2, (radius + style.FramePadding.y) * 2); + + const ImRect bb(pos, ImVec2(pos.x + size.x, pos.y + size.y)); + ItemSize(bb, style.FramePadding.y); + if (!ItemAdd(bb, id)) + return false; + + window->DrawList->PathClear(); + + int num_segments = 30; + int start = abs(ImSin(g.Time * 1.8f) * (num_segments - 5)); + + const float a_min = IM_PI * 2.0f * ((float)start) / (float)num_segments; + const float a_max = IM_PI * 2.0f * ((float)num_segments - 3) / (float)num_segments; + + const ImVec2 centre = ImVec2(pos.x + radius, pos.y + radius + style.FramePadding.y); + + for (int i = 0; i < num_segments; i++) { + const float a = a_min + ((float)i / (float)num_segments) * (a_max - a_min); + window->DrawList->PathLineTo(ImVec2(centre.x + ImCos(a + g.Time * 8) * radius, + centre.y + ImSin(a + g.Time * 8) * radius)); + } + + window->DrawList->PathStroke(color, false, thickness); + return true; +} + +} // namespace ImGui + +namespace { +struct PackageTaskResult { + bool success = false; + std::string message; +}; + +struct PackageTaskState { + bool active = false; + float startTime = 0.0f; + std::string label; + std::future future; +}; +} // namespace + void Engine::renderGameViewportWindow() { gameViewportFocused = false; ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(6.0f, 6.0f)); @@ -308,30 +411,75 @@ void Engine::renderGameViewportWindow() { int width = std::max(160, (int)avail.x); int height = std::max(120, (int)avail.y); - const SceneObject* playerCam = nullptr; - for (const auto& obj : sceneObjects) { + SceneObject* playerCam = nullptr; + for (auto& obj : sceneObjects) { if (obj.type == ObjectType::Camera && obj.camera.type == SceneCameraType::Player) { playerCam = &obj; break; } } + ImGui::PushStyleColor(ImGuiCol_FrameBg, ImVec4(0.08f, 0.09f, 0.10f, 1.0f)); + ImGui::PushStyleColor(ImGuiCol_FrameBgHovered, ImVec4(0.12f, 0.14f, 0.16f, 1.0f)); + ImGui::PushStyleColor(ImGuiCol_FrameBgActive, ImVec4(0.14f, 0.18f, 0.20f, 1.0f)); + ImGui::BeginDisabled(playerCam == nullptr); + bool dummyToggle = false; + bool postFxChanged = false; + if (playerCam) { + bool before = playerCam->camera.applyPostFX; + if (ImGui::Checkbox("Post FX", &playerCam->camera.applyPostFX)) { + postFxChanged = (before != playerCam->camera.applyPostFX); + } + } else { + ImGui::Checkbox("Post FX", &dummyToggle); + } + ImGui::SameLine(); + ImGui::Checkbox("Text", &showUITextOverlay); + ImGui::SameLine(); + ImGui::Checkbox("Canvas Guides", &showCanvasOverlay); + ImGui::EndDisabled(); + ImGui::PopStyleColor(3); + + if (playerCam && postFxChanged) { + projectManager.currentProject.hasUnsavedChanges = true; + } + if (!isPlaying) { gameViewCursorLocked = false; } if (playerCam && rendererInitialized) { - unsigned int tex = renderer.renderScenePreview( - makeCameraFromObject(*playerCam), - sceneObjects, - width, - height, + unsigned int tex = renderer.renderScenePreview( + makeCameraFromObject(*playerCam), + sceneObjects, + width, + height, playerCam->camera.fov, playerCam->camera.nearClip, - playerCam->camera.farClip + playerCam->camera.farClip, + playerCam->camera.applyPostFX ); ImGui::Image((void*)(intptr_t)tex, ImVec2((float)width, (float)height), ImVec2(0, 1), ImVec2(1, 0)); + ImVec2 imageMin = ImGui::GetItemRectMin(); + ImVec2 imageMax = ImGui::GetItemRectMax(); + ImDrawList* drawList = ImGui::GetWindowDrawList(); + if (showCanvasOverlay) { + ImVec2 pad(8.0f, 8.0f); + ImVec2 tl(imageMin.x + pad.x, imageMin.y + pad.y); + ImVec2 br(imageMax.x - pad.x, imageMax.y - pad.y); + drawList->AddRect(tl, br, IM_COL32(110, 170, 255, 180), 8.0f, 0, 2.0f); + } + if (showUITextOverlay) { + const char* textLabel = "Text Overlay"; + ImVec2 textPos(imageMin.x + 16.0f, imageMin.y + 16.0f); + ImVec2 size = ImGui::CalcTextSize(textLabel); + ImVec2 bgPad(6.0f, 4.0f); + ImVec2 bgMin(textPos.x - bgPad.x, textPos.y - bgPad.y); + ImVec2 bgMax(textPos.x + size.x + bgPad.x, textPos.y + size.y + bgPad.y); + drawList->AddRectFilled(bgMin, bgMax, IM_COL32(20, 20, 24, 200), 4.0f); + drawList->AddText(textPos, IM_COL32(235, 235, 245, 255), textLabel); + } bool hovered = ImGui::IsItemHovered(); bool clicked = hovered && isPlaying && ImGui::IsMouseClicked(ImGuiMouseButton_Left); @@ -1302,6 +1450,84 @@ void Engine::renderOpenProjectDialog() { ImGui::End(); } +void Engine::renderPlayControlsBar() { + ImGuiStyle& style = ImGui::GetStyle(); + ImVec2 buttonPadding(10.0f, 4.0f); + const char* playLabel = isPlaying ? "Stop" : "Play"; + const char* pauseLabel = isPaused ? "Resume" : "Pause"; + const char* specLabel = specMode ? "Spec On" : "Spec Mode"; + + auto buttonWidth = [&](const char* label) { + ImVec2 textSize = ImGui::CalcTextSize(label); + return textSize.x + buttonPadding.x * 2.0f + style.FrameBorderSize * 2.0f; + }; + + float playWidth = buttonWidth(playLabel); + float pauseWidth = buttonWidth(pauseLabel); + float specWidth = buttonWidth(specLabel); + float spacing = style.ItemSpacing.x; + float totalWidth = playWidth + pauseWidth + specWidth + spacing * 2.0f; + + // Center the controls inside the dockspace menu bar. + float regionMinX = ImGui::GetWindowContentRegionMin().x; + float regionMaxX = ImGui::GetWindowContentRegionMax().x; + float regionWidth = regionMaxX - regionMinX; + float startX = (regionWidth - totalWidth) * 0.5f + regionMinX; + if (startX < regionMinX) startX = regionMinX; + + ImVec2 cursor = ImGui::GetCursorPos(); + ImGui::SetCursorPos(ImVec2(startX, cursor.y)); + + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, buttonPadding); + bool playPressed = ImGui::Button(playLabel); + ImGui::SameLine(0.0f, spacing); + bool pausePressed = ImGui::Button(pauseLabel); + ImGui::SameLine(0.0f, spacing); + bool specPressed = ImGui::Button(specLabel); + ImGui::PopStyleVar(); + + if (playPressed) { + bool newState = !isPlaying; + if (newState) { + if (physics.isReady() || physics.init()) { + physics.onPlayStart(sceneObjects); + } else { + addConsoleMessage("PhysX failed to initialize; physics disabled for play mode", ConsoleMessageType::Warning); + } + audio.onPlayStart(sceneObjects); + } else { + physics.onPlayStop(); + audio.onPlayStop(); + isPaused = false; + if (specMode && (physics.isReady() || physics.init())) { + physics.onPlayStart(sceneObjects); + } + } + isPlaying = newState; + } + if (pausePressed) { + isPaused = !isPaused; + if (isPaused) isPlaying = true; // placeholder: pausing implies we’re in play mode + } + if (specPressed) { + bool enable = !specMode; + if (enable && !physics.isReady() && !physics.init()) { + addConsoleMessage("PhysX failed to initialize; spec mode disabled", ConsoleMessageType::Warning); + enable = false; + } + specMode = enable; + if (!isPlaying) { + if (specMode) { + physics.onPlayStart(sceneObjects); + audio.onPlayStart(sceneObjects); + } else { + physics.onPlayStop(); + audio.onPlayStop(); + } + } + } +} + void Engine::renderMainMenuBar() { if (ImGui::BeginMainMenuBar()) { ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(14.0f, 8.0f)); @@ -1417,59 +1643,6 @@ void Engine::renderMainMenuBar() { "No Scene Loaded" : projectManager.currentProject.currentSceneName; ImGui::TextUnformatted(sceneLabel.c_str()); - ImGui::SameLine(); - ImGui::Dummy(ImVec2(14.0f, 0.0f)); - ImGui::SameLine(); - - ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(10.0f, 4.0f)); - bool playPressed = ImGui::Button(isPlaying ? "Stop" : "Play"); - ImGui::SameLine(0.0f, 6.0f); - bool pausePressed = ImGui::Button(isPaused ? "Resume" : "Pause"); - ImGui::SameLine(0.0f, 6.0f); - bool specPressed = ImGui::Button(specMode ? "Spec On" : "Spec Mode"); - ImGui::PopStyleVar(); - - if (playPressed) { - bool newState = !isPlaying; - if (newState) { - if (physics.isReady() || physics.init()) { - physics.onPlayStart(sceneObjects); - } else { - addConsoleMessage("PhysX failed to initialize; physics disabled for play mode", ConsoleMessageType::Warning); - } - audio.onPlayStart(sceneObjects); - } else { - physics.onPlayStop(); - audio.onPlayStop(); - isPaused = false; - if (specMode && (physics.isReady() || physics.init())) { - physics.onPlayStart(sceneObjects); - } - } - isPlaying = newState; - } - if (pausePressed) { - isPaused = !isPaused; - if (isPaused) isPlaying = true; // placeholder: pausing implies we’re in play mode - } - if (specPressed) { - bool enable = !specMode; - if (enable && !physics.isReady() && !physics.init()) { - addConsoleMessage("PhysX failed to initialize; spec mode disabled", ConsoleMessageType::Warning); - enable = false; - } - specMode = enable; - if (!isPlaying) { - if (specMode) { - physics.onPlayStart(sceneObjects); - audio.onPlayStart(sceneObjects); - } else { - physics.onPlayStop(); - audio.onPlayStop(); - } - } - } - float rightX = ImGui::GetWindowWidth() - 220.0f; if (rightX > ImGui::GetCursorPosX()) { ImGui::SameLine(rightX); @@ -2497,6 +2670,9 @@ void Engine::renderInspectorPanel() { obj.camera.farClip = std::max(obj.camera.nearClip + 0.05f, obj.camera.farClip); projectManager.currentProject.hasUnsavedChanges = true; } + if (ImGui::Checkbox("Apply Post Processing", &obj.camera.applyPostFX)) { + projectManager.currentProject.hasUnsavedChanges = true; + } ImGui::Unindent(10.0f); ImGui::PopID(); } @@ -4661,7 +4837,8 @@ void Engine::renderViewport() { previewHeight, previewCam->camera.fov, previewCam->camera.nearClip, - previewCam->camera.farClip + previewCam->camera.farClip, + previewCam->camera.applyPostFX ); if (previewTex != 0) { @@ -4902,7 +5079,7 @@ void Engine::renderProjectBrowserPanel() { ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(8.0f, 5.0f)); ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(6.0f, 4.0f)); - ImGui::Begin("Project Manager", &showProjectBrowser); + ImGui::Begin("Project Settings", &showProjectBrowser); if (!projectManager.currentProject.isLoaded) { ImGui::TextDisabled("No project loaded"); @@ -4912,7 +5089,7 @@ void Engine::renderProjectBrowserPanel() { return; } - ImGui::TextColored(ImVec4(0.4f, 0.7f, 0.95f, 1.0f), "[P] %s", projectManager.currentProject.name.c_str()); + ImGui::TextColored(ImVec4(0.4f, 0.7f, 0.95f, 1.0f), "%s", projectManager.currentProject.name.c_str()); if (projectManager.currentProject.hasUnsavedChanges) { ImGui::SameLine(); ImGui::TextColored(ImVec4(1.0f, 0.7f, 0.3f, 1.0f), "*"); @@ -4920,7 +5097,21 @@ void Engine::renderProjectBrowserPanel() { ImGui::Separator(); - if (ImGui::CollapsingHeader("Scenes", ImGuiTreeNodeFlags_DefaultOpen)) { + static int selectedTab = 0; + const char* tabs[] = { "Scenes", "Packages", "Assets" }; + + ImGui::BeginChild("SettingsNav", ImVec2(180, 0), true); + for (int i = 0; i < 3; ++i) { + if (ImGui::Selectable(tabs[i], selectedTab == i, 0, ImVec2(0, 32))) { + selectedTab = i; + } + } + ImGui::EndChild(); + + ImGui::SameLine(); + ImGui::BeginChild("SettingsBody", ImVec2(0, 0), false); + + if (selectedTab == 0) { if (ImGui::Button("+ New Scene")) { showNewSceneDialog = true; memset(newSceneName, 0, sizeof(newSceneName)); @@ -4962,16 +5153,166 @@ void Engine::renderProjectBrowserPanel() { if (scenes.empty()) { ImGui::TextDisabled("No scenes yet"); } - } - - if (ImGui::CollapsingHeader("Loaded OBJ Meshes")) { - const auto& meshes = g_objLoader.getAllMeshes(); - if (meshes.empty()) { + } else if (selectedTab == 1) { + static PackageTaskState packageTask; + static std::string packageStatus; + static char gitUrlBuf[256] = ""; + static char gitNameBuf[128] = ""; + static char gitIncludeBuf[128] = "include"; + + auto pollPackageTask = [&]() { + if (!packageTask.active) return; + if (!packageTask.future.valid()) { + packageTask.active = false; + return; + } + auto state = packageTask.future.wait_for(std::chrono::milliseconds(0)); + if (state == std::future_status::ready) { + PackageTaskResult result = packageTask.future.get(); + packageStatus = result.message; + packageTask.active = false; + packageTask.future = std::future(); + } + }; + + auto startPackageTask = [&](const char* label, std::function fn) { + if (packageTask.active) return; + packageTask.active = true; + packageTask.label = label; + packageTask.startTime = static_cast(ImGui::GetTime()); + packageTask.future = std::async(std::launch::async, std::move(fn)); + }; + + pollPackageTask(); + + if (!packageStatus.empty()) { + ImGui::TextWrapped("%s", packageStatus.c_str()); + } + + if (packageTask.active) { + const ImU32 col = ImGui::GetColorU32(ImGuiCol_ButtonHovered); + const ImU32 bg = ImGui::GetColorU32(ImGuiCol_Button); + float elapsed = static_cast(ImGui::GetTime()) - packageTask.startTime; + float phase = std::fmod(elapsed * 0.25f, 1.0f); + + ImGui::Separator(); + ImGui::Text("%s", packageTask.label.c_str()); + ImGui::BufferingBar("##pkg_buffer", phase, ImVec2(ImGui::GetContentRegionAvail().x, 6.0f), bg, col); + ImGui::Spinner("##pkg_spinner", 10.0f, 4, col); + } + + ImGui::BeginDisabled(packageTask.active); + ImGui::TextDisabled("Add package from Git"); + ImGui::InputTextWithHint("URL", "https://github.com/user/repo.git", gitUrlBuf, sizeof(gitUrlBuf)); + ImGui::InputTextWithHint("Name (optional)", "use repo name", gitNameBuf, sizeof(gitNameBuf)); + ImGui::InputTextWithHint("Include dir", "include", gitIncludeBuf, sizeof(gitIncludeBuf)); + if (ImGui::Button("Add as submodule")) { + std::string url = gitUrlBuf; + std::string name = gitNameBuf; + std::string include = gitIncludeBuf; + startPackageTask("Installing package...", [this, url, name, include]() { + PackageTaskResult result; + std::string newId; + if (packageManager.installGitPackage(url, name, include, newId)) { + result.success = true; + result.message = "Installed package: " + newId; + } else { + result.message = packageManager.getLastError(); + } + return result; + }); + } + + ImGui::EndDisabled(); + ImGui::Separator(); + ImGui::TextDisabled("Installed packages"); + if (packageTask.active) { + ImGui::TextDisabled("Working..."); + } else { + const auto& registry = packageManager.getRegistry(); + const auto& installedIds = packageManager.getInstalled(); + if (installedIds.empty()) { + ImGui::TextDisabled("None installed"); + } else { + for (const auto& id : installedIds) { + const PackageInfo* pkg = nullptr; + for (const auto& p : registry) { + if (p.id == id) { pkg = &p; break; } + } + if (!pkg) continue; + + ImGui::PushID(pkg->id.c_str()); + ImGui::Separator(); + ImGui::Text("%s", pkg->name.c_str()); + ImGui::TextDisabled("%s", pkg->description.c_str()); + if (!pkg->external) { + ImGui::SameLine(); + ImGui::TextColored(ImVec4(0.4f, 0.7f, 0.95f, 1.0f), "[bundled]"); + ImGui::PopID(); + continue; + } + + ImGui::TextDisabled("Path: %s", pkg->localPath.string().c_str()); + ImGui::TextDisabled("Git: %s", pkg->gitUrl.c_str()); + + if (ImGui::Button("Check updates")) { + std::string id = pkg->id; + startPackageTask("Checking package status...", [this, id]() { + PackageTaskResult result; + std::string status; + if (packageManager.checkGitStatus(id, status)) { + result.success = true; + result.message = status; + } else { + result.message = packageManager.getLastError(); + } + return result; + }); + } + ImGui::SameLine(); + if (ImGui::Button("Update")) { + std::string id = pkg->id; + std::string name = pkg->name; + startPackageTask("Updating package...", [this, id, name]() { + PackageTaskResult result; + std::string log; + if (packageManager.updateGitPackage(id, log)) { + result.success = true; + result.message = "Updated " + name + "\n" + log; + } else { + result.message = packageManager.getLastError(); + } + return result; + }); + } + ImGui::SameLine(); + if (ImGui::Button("Uninstall")) { + std::string id = pkg->id; + std::string name = pkg->name; + startPackageTask("Removing package...", [this, id, name]() { + PackageTaskResult result; + if (packageManager.remove(id)) { + result.success = true; + result.message = "Removed " + name; + } else { + result.message = packageManager.getLastError(); + } + return result; + }); + } + ImGui::PopID(); + } + } + } + } else if (selectedTab == 2) { + ImGui::TextDisabled("Loaded OBJ Meshes"); + const auto& meshesObj = g_objLoader.getAllMeshes(); + if (meshesObj.empty()) { ImGui::TextDisabled("No meshes loaded"); ImGui::TextDisabled("Import .obj files from File Browser"); } else { - for (size_t i = 0; i < meshes.size(); i++) { - const auto& mesh = meshes[i]; + for (size_t i = 0; i < meshesObj.size(); i++) { + const auto& mesh = meshesObj[i]; ImGuiTreeNodeFlags flags = ImGuiTreeNodeFlags_Leaf | ImGuiTreeNodeFlags_SpanAvailWidth | ImGuiTreeNodeFlags_NoTreePushOnOpen; @@ -4989,16 +5330,16 @@ void Engine::renderProjectBrowserPanel() { } } } - } - if (ImGui::CollapsingHeader("Loaded Models (Assimp)")) { - const auto& meshes = getModelLoader().getAllMeshes(); - if (meshes.empty()) { + ImGui::Separator(); + ImGui::TextDisabled("Loaded Models (Assimp)"); + const auto& meshesAssimp = getModelLoader().getAllMeshes(); + if (meshesAssimp.empty()) { ImGui::TextDisabled("No models loaded"); ImGui::TextDisabled("Import FBX/GLTF/other supported models from File Browser"); } else { - for (size_t i = 0; i < meshes.size(); i++) { - const auto& mesh = meshes[i]; + for (size_t i = 0; i < meshesAssimp.size(); i++) { + const auto& mesh = meshesAssimp[i]; ImGuiTreeNodeFlags flags = ImGuiTreeNodeFlags_Leaf | ImGuiTreeNodeFlags_SpanAvailWidth | ImGuiTreeNodeFlags_NoTreePushOnOpen; @@ -5018,11 +5359,12 @@ void Engine::renderProjectBrowserPanel() { } } + ImGui::EndChild(); + ImGui::End(); ImGui::PopStyleVar(2); ImGui::PopStyleColor(3); } - void Engine::renderEnvironmentWindow() { if (!showEnvironmentWindow) return; ImGui::Begin("Environment", &showEnvironmentWindow); diff --git a/src/PackageManager.cpp b/src/PackageManager.cpp new file mode 100644 index 0000000..1aa7798 --- /dev/null +++ b/src/PackageManager.cpp @@ -0,0 +1,481 @@ +#include "PackageManager.h" + +#include +#include +#include +#include + +namespace { +fs::path normalizePath(const fs::path& p) { + std::error_code ec; + fs::path canonical = fs::weakly_canonical(p, ec); + if (ec) { + canonical = fs::absolute(p, ec); + } + return canonical.lexically_normal(); +} + +bool containsPath(const std::vector& haystack, const fs::path& needle) { + std::string target = normalizePath(needle).string(); + for (const auto& entry : haystack) { + if (normalizePath(entry).string() == target) { + return true; + } + } + return false; +} + +fs::path guessIncludeDir(const fs::path& repoRoot, const std::string& includeRel) { + if (!includeRel.empty()) { + fs::path candidate = normalizePath(repoRoot / includeRel); + if (fs::exists(candidate) && fs::is_directory(candidate)) { + return candidate; + } + } + + const char* defaults[] = {"include", "Include", "includes", "inc", "Inc"}; + for (const char* name : defaults) { + fs::path candidate = normalizePath(repoRoot / name); + if (fs::exists(candidate) && fs::is_directory(candidate)) { + return candidate; + } + } + + for (const auto& entry : fs::directory_iterator(repoRoot)) { + if (entry.is_directory()) { + return normalizePath(entry.path()); + } + } + + return normalizePath(repoRoot); +} +} // namespace + +PackageManager::PackageManager() { + buildRegistry(); +} + +void PackageManager::setProjectRoot(const fs::path& root) { + buildRegistry(); + projectRoot = root; + manifestPath = projectRoot / "packages.modu"; + loadManifest(); +} + +bool PackageManager::isInstalled(const std::string& id) const { + return std::find(installedIds.begin(), installedIds.end(), id) != installedIds.end(); +} + +bool PackageManager::install(const std::string& id) { + lastError.clear(); + const PackageInfo* pkg = findPackage(id); + if (!pkg) { + lastError = "Unknown package: " + id; + return false; + } + if (isInstalled(id)) { + return true; + } + installedIds.push_back(id); + saveManifest(); + return true; +} + +bool PackageManager::remove(const std::string& id) { + lastError.clear(); + const PackageInfo* pkg = findPackage(id); + if (!pkg) { + lastError = "Unknown package: " + id; + return false; + } + if (pkg->builtIn) { + lastError = "Cannot remove built-in packages."; + return false; + } + if (pkg->external) { + std::string log; + std::string deinitCmd = "git submodule deinit -f \"" + pkg->localPath.string() + "\""; + runCommand(deinitCmd, log); // best-effort + std::string rmCmd = "git rm -f \"" + pkg->localPath.string() + "\""; + if (!runCommand(rmCmd, log)) { + lastError = "Failed to remove submodule. Git log:\n" + log; + return false; + } + } + + auto it = std::remove(installedIds.begin(), installedIds.end(), id); + if (it == installedIds.end()) { + return true; + } + installedIds.erase(it, installedIds.end()); + registry.erase(std::remove_if(registry.begin(), registry.end(), + [&](const PackageInfo& p){ return p.id == id && p.external; }), registry.end()); + + saveManifest(); + return true; +} + +void PackageManager::applyToBuildConfig(ScriptBuildConfig& config) const { + std::unordered_set defineSet(config.defines.begin(), config.defines.end()); + std::unordered_set linuxLibSet(config.linuxLinkLibs.begin(), config.linuxLinkLibs.end()); + std::unordered_set winLibSet(config.windowsLinkLibs.begin(), config.windowsLinkLibs.end()); + + for (const auto& id : installedIds) { + const PackageInfo* pkg = findPackage(id); + if (!pkg) continue; + + for (const auto& dir : pkg->includeDirs) { + if (!containsPath(config.includeDirs, dir)) { + config.includeDirs.push_back(normalizePath(dir)); + } + } + for (const auto& def : pkg->defines) { + if (defineSet.insert(def).second) { + config.defines.push_back(def); + } + } + for (const auto& lib : pkg->linuxLibs) { + if (linuxLibSet.insert(lib).second) { + config.linuxLinkLibs.push_back(lib); + } + } + for (const auto& lib : pkg->windowsLibs) { + if (winLibSet.insert(lib).second) { + config.windowsLinkLibs.push_back(lib); + } + } + } +} + +void PackageManager::buildRegistry() { + registry.clear(); + fs::path engineRoot = fs::current_path(); + + auto add = [this](PackageInfo info) { + for (auto& dir : info.includeDirs) { + dir = normalizePath(dir); + } + registry.push_back(std::move(info)); + }; + + PackageInfo engineCore; + engineCore.id = "engine-core"; + engineCore.name = "Engine Core"; + engineCore.description = "Modularity engine headers and common utilities"; + engineCore.builtIn = true; + engineCore.includeDirs = { + engineRoot / "src", + engineRoot / "include", + engineRoot / "src/ThirdParty", + engineRoot / "src/ThirdParty/glm", + engineRoot / "src/ThirdParty/glad" + }; + engineCore.linuxLibs = {"pthread", "dl"}; + engineCore.windowsLibs = {"User32.lib", "Advapi32.lib"}; + add(engineCore); + + PackageInfo glm; + glm.id = "glm"; + glm.name = "GLM Math"; + glm.description = "Header-only GLM math library (bundled)"; + glm.builtIn = false; // Count as installed instead of hidden built-in + glm.includeDirs = { engineRoot / "src/ThirdParty/glm" }; + add(glm); + + PackageInfo imgui; + imgui.id = "imgui"; + imgui.name = "Dear ImGui"; + imgui.description = "Immediate-mode UI helpers for editor-time tools"; + imgui.builtIn = false; + imgui.includeDirs = { + engineRoot / "src/ThirdParty/imgui", + engineRoot / "src/ThirdParty/imgui/backends" + }; + add(imgui); + + PackageInfo imguizmo; + imguizmo.id = "imguizmo"; + imguizmo.name = "ImGuizmo"; + imguizmo.description = "Gizmo/transform helpers used by the editor"; + imguizmo.builtIn = false; + imguizmo.includeDirs = { engineRoot / "src/ThirdParty/ImGuizmo" }; + add(imguizmo); + + PackageInfo miniaudio; + miniaudio.id = "miniaudio"; + miniaudio.name = "miniaudio"; + miniaudio.description = "Single-header audio helpers (bundled)"; + miniaudio.builtIn = false; + miniaudio.includeDirs = { engineRoot / "include/ThirdParty" }; + add(miniaudio); +} + +void PackageManager::loadManifest() { + installedIds.clear(); + for (const auto& pkg : registry) { + if (!isInstalled(pkg.id)) { + installedIds.push_back(pkg.id); + } + } + + if (manifestPath.empty()) return; + if (!fs::exists(manifestPath)) { + saveManifest(); + return; + } + + std::ifstream file(manifestPath); + if (!file.is_open()) { + lastError = "Unable to open package manifest."; + return; + } + + std::string line; + while (std::getline(file, line)) { + std::string cleaned = trim(line); + if (cleaned.empty() || cleaned[0] == '#') continue; + + std::string id; + if (cleaned.rfind("package=", 0) == 0) { + id = cleaned.substr(8); + if (!id.empty() && !isInstalled(id) && findPackage(id)) { + installedIds.push_back(id); + } + continue; + } + + if (cleaned.rfind("git=", 0) == 0) { + auto payload = cleaned.substr(4); + auto parts = split(payload, '|'); + if (parts.size() < 4) continue; + PackageInfo pkg; + pkg.id = parts[0]; + pkg.name = parts[1]; + pkg.description = "External package from git"; + pkg.external = true; + pkg.gitUrl = parts[2]; + fs::path relPath = parts[3]; + pkg.localPath = normalizePath(projectRoot / relPath); + + std::vector includeTokens; + if (parts.size() > 4) includeTokens = split(parts[4], ';'); + for (const auto& inc : includeTokens) { + if (inc.empty()) continue; + pkg.includeDirs.push_back(normalizePath(projectRoot / inc)); + } + std::vector defTokens; + if (parts.size() > 5) defTokens = split(parts[5], ';'); + pkg.defines = defTokens; + if (parts.size() > 6) pkg.linuxLibs = split(parts[6], ';'); + if (parts.size() > 7) pkg.windowsLibs = split(parts[7], ';'); + + registry.push_back(pkg); + if (!isInstalled(pkg.id)) { + installedIds.push_back(pkg.id); + } + } + } +} + +void PackageManager::saveManifest() const { + if (manifestPath.empty()) return; + std::ofstream file(manifestPath); + if (!file.is_open()) return; + + file << "# Modularity package manifest\n"; + file << "# Add optional script-time dependencies here\n"; + for (const auto& id : installedIds) { + const PackageInfo* pkg = findPackage(id); + if (!pkg) continue; + if (!pkg->external) { + if (!pkg->builtIn) { + file << "package=" << id << "\n"; + } + continue; + } + + // Persist external package metadata + std::vector relIncludes; + for (const auto& inc : pkg->includeDirs) { + std::error_code ec; + fs::path rel = fs::relative(inc, projectRoot, ec); + relIncludes.push_back((!ec ? rel : inc).generic_string()); + } + + file << "git=" << pkg->id << "|" + << pkg->name << "|" + << pkg->gitUrl << "|"; + + std::error_code ec; + fs::path relPath = fs::relative(pkg->localPath, projectRoot, ec); + file << ((!ec ? relPath : pkg->localPath).generic_string()) << "|"; + file << join(relIncludes, ';') << "|"; + file << join(pkg->defines, ';') << "|"; + file << join(pkg->linuxLibs, ';') << "|"; + file << join(pkg->windowsLibs, ';') << "\n"; + } +} + +const PackageInfo* PackageManager::findPackage(const std::string& id) const { + auto it = std::find_if(registry.begin(), registry.end(), [&](const PackageInfo& p) { + return p.id == id; + }); + return it == registry.end() ? nullptr : &(*it); +} + +bool PackageManager::isBuiltIn(const std::string& id) const { + const PackageInfo* pkg = findPackage(id); + return pkg && pkg->builtIn; +} + +std::string PackageManager::trim(const std::string& value) { + size_t start = 0; + while (start < value.size() && std::isspace(static_cast(value[start]))) start++; + size_t end = value.size(); + while (end > start && std::isspace(static_cast(value[end - 1]))) end--; + return value.substr(start, end - start); +} + +std::string PackageManager::slugify(const std::string& value) { + std::string out; + out.reserve(value.size()); + for (char c : value) { + if (std::isalnum(static_cast(c))) { + out.push_back(static_cast(std::tolower(static_cast(c)))); + } else if (c == '-' || c == '_') { + out.push_back(c); + } else if (std::isspace(static_cast(c))) { + out.push_back('-'); + } + } + if (out.empty()) out = "pkg"; + return out; +} + +bool PackageManager::runCommand(const std::string& command, std::string& output) { + std::array buffer{}; + FILE* pipe = popen(command.c_str(), "r"); + if (!pipe) return false; + while (fgets(buffer.data(), buffer.size(), pipe) != nullptr) { + output += buffer.data(); + } + int rc = pclose(pipe); + return rc == 0; +} + +bool PackageManager::ensureProjectRoot() const { + return !projectRoot.empty(); +} + +std::vector PackageManager::split(const std::string& input, char delim) const { + std::vector out; + std::stringstream ss(input); + std::string part; + while (std::getline(ss, part, delim)) { + out.push_back(part); + } + return out; +} + +std::string PackageManager::join(const std::vector& vals, char delim) const { + std::ostringstream oss; + for (size_t i = 0; i < vals.size(); ++i) { + if (i > 0) oss << delim; + oss << vals[i]; + } + return oss.str(); +} + +fs::path PackageManager::packagesFolder() const { + return projectRoot / "Packages"; +} + +bool PackageManager::installGitPackage(const std::string& url, + const std::string& nameHint, + const std::string& includeRel, + std::string& outId) { + lastError.clear(); + if (!ensureProjectRoot()) { + lastError = "Project root not set."; + return false; + } + if (url.empty()) { + lastError = "Git URL is required."; + return false; + } + + std::string repoName = nameHint; + size_t slash = url.find_last_of("/\\"); + if (repoName.empty() && slash != std::string::npos) { + repoName = url.substr(slash + 1); + if (repoName.rfind(".git") != std::string::npos) { + repoName = repoName.substr(0, repoName.size() - 4); + } + } + std::string id = slugify(repoName); + if (isInstalled(id)) { + lastError = "Package already installed: " + id; + return false; + } + + fs::path dest = normalizePath(packagesFolder() / id); + fs::create_directories(dest.parent_path()); + + std::string cmd = "git submodule add --force \"" + url + "\" \"" + dest.string() + "\""; + std::string log; + if (!runCommand(cmd, log)) { + lastError = "git submodule add failed:\n" + log; + return false; + } + + PackageInfo pkg; + pkg.id = id; + pkg.name = repoName.empty() ? id : repoName; + pkg.description = "External package from " + url; + pkg.external = true; + pkg.gitUrl = url; + pkg.localPath = dest; + pkg.includeDirs.push_back(guessIncludeDir(dest, includeRel)); + + registry.push_back(pkg); + installedIds.push_back(id); + saveManifest(); + outId = id; + return true; +} + +bool PackageManager::checkGitStatus(const std::string& id, std::string& outStatus) { + lastError.clear(); + outStatus.clear(); + const PackageInfo* pkg = findPackage(id); + if (!pkg || !pkg->external) { + lastError = "Package is not external or not found."; + return false; + } + + std::string fetchCmd = "git -C \"" + pkg->localPath.string() + "\" fetch --quiet"; + runCommand(fetchCmd, outStatus); // ignore fetch failures here + + std::string statusCmd = "git -C \"" + pkg->localPath.string() + "\" status -sb"; + if (!runCommand(statusCmd, outStatus)) { + lastError = "Failed to read git status."; + return false; + } + return true; +} + +bool PackageManager::updateGitPackage(const std::string& id, std::string& outLog) { + lastError.clear(); + outLog.clear(); + const PackageInfo* pkg = findPackage(id); + if (!pkg || !pkg->external) { + lastError = "Package is not external or not found."; + return false; + } + std::string cmd = "git -C \"" + pkg->localPath.string() + "\" pull --ff-only"; + if (!runCommand(cmd, outLog)) { + lastError = "git pull failed."; + return false; + } + return true; +} diff --git a/src/PackageManager.h b/src/PackageManager.h new file mode 100644 index 0000000..76d848f --- /dev/null +++ b/src/PackageManager.h @@ -0,0 +1,64 @@ +#pragma once + +#include "Common.h" +#include "ScriptCompiler.h" +#include + +struct PackageInfo { + std::string id; + std::string name; + std::string description; + bool builtIn = false; + bool external = false; + std::string gitUrl; + fs::path localPath; // Absolute path for external packages + fs::path includeHint; // Absolute include root for external packages + std::vector includeDirs; + std::vector defines; + std::vector linuxLibs; + std::vector windowsLibs; +}; + +// Minimal package manager for script dependencies. Keeps a small registry of +// known/bundled packages and a per-project manifest of which optional ones are +// enabled. Installed packages augment the script build config (include dirs, +// defines, link libs) before compilation. +class PackageManager { +public: + PackageManager(); + + void setProjectRoot(const fs::path& root); + const std::vector& getRegistry() const { return registry; } + const std::vector& getInstalled() const { return installedIds; } + bool isInstalled(const std::string& id) const; + bool install(const std::string& id); + bool installGitPackage(const std::string& url, + const std::string& nameHint, + const std::string& includeRel, + std::string& outId); + bool checkGitStatus(const std::string& id, std::string& outStatus); + bool updateGitPackage(const std::string& id, std::string& outLog); + bool remove(const std::string& id); + void applyToBuildConfig(ScriptBuildConfig& config) const; + const std::string& getLastError() const { return lastError; } + +private: + void buildRegistry(); + void loadManifest(); + void saveManifest() const; + const PackageInfo* findPackage(const std::string& id) const; + bool isBuiltIn(const std::string& id) const; + static std::string trim(const std::string& value); + static std::string slugify(const std::string& value); + static bool runCommand(const std::string& command, std::string& output); + bool ensureProjectRoot() const; + std::vector split(const std::string& input, char delim) const; + std::string join(const std::vector& vals, char delim) const; + fs::path packagesFolder() const; + + fs::path projectRoot; + fs::path manifestPath; + std::vector registry; + std::vector installedIds; + std::string lastError; +}; diff --git a/src/ProjectManager.cpp b/src/ProjectManager.cpp index 4a92603..cca3606 100644 --- a/src/ProjectManager.cpp +++ b/src/ProjectManager.cpp @@ -352,6 +352,7 @@ bool SceneSerializer::saveScene(const fs::path& filePath, file << "cameraFov=" << obj.camera.fov << "\n"; file << "cameraNear=" << obj.camera.nearClip << "\n"; file << "cameraFar=" << obj.camera.farClip << "\n"; + file << "cameraPostFX=" << (obj.camera.applyPostFX ? 1 : 0) << "\n"; if (obj.type == ObjectType::PostFXNode) { file << "postEnabled=" << (obj.postFx.enabled ? 1 : 0) << "\n"; file << "postBloomEnabled=" << (obj.postFx.bloomEnabled ? 1 : 0) << "\n"; @@ -647,6 +648,8 @@ bool SceneSerializer::loadScene(const fs::path& filePath, currentObj->camera.nearClip = std::stof(value); } else if (key == "cameraFar") { currentObj->camera.farClip = std::stof(value); + } else if (key == "cameraPostFX") { + currentObj->camera.applyPostFX = (std::stoi(value) != 0); } else if (key == "postEnabled") { currentObj->postFx.enabled = (std::stoi(value) != 0); } else if (key == "postBloomEnabled") { diff --git a/src/Rendering.cpp b/src/Rendering.cpp index f828b4b..dda9d02 100644 --- a/src/Rendering.cpp +++ b/src/Rendering.cpp @@ -1072,7 +1072,7 @@ PostFXSettings Renderer::gatherPostFX(const std::vector& sceneObjec return combined; } -void Renderer::applyPostProcessing(const std::vector& sceneObjects) { +unsigned int Renderer::applyPostProcessing(const std::vector& sceneObjects, unsigned int sourceTexture, int width, int height, bool allowHistory) { PostFXSettings settings = gatherPostFX(sceneObjects); GLint polygonMode[2] = { GL_FILL, GL_FILL }; #ifdef GL_POLYGON_MODE @@ -1088,18 +1088,25 @@ void Renderer::applyPostProcessing(const std::vector& sceneObjects) wantsEffects = false; } - if (!wantsEffects || !postShader || currentWidth <= 0 || currentHeight <= 0) { - displayTexture = viewportTexture; - clearHistory(); - return; + if (!wantsEffects || !postShader || width <= 0 || height <= 0 || sourceTexture == 0) { + if (allowHistory) { + displayTexture = sourceTexture; + clearHistory(); + } + return sourceTexture; } - ensureRenderTarget(postTarget, currentWidth, currentHeight); - ensureRenderTarget(historyTarget, currentWidth, currentHeight); - if (postTarget.fbo == 0 || postTarget.texture == 0) { - displayTexture = viewportTexture; - clearHistory(); - return; + RenderTarget& target = allowHistory ? postTarget : previewPostTarget; + ensureRenderTarget(target, width, height); + if (allowHistory) { + ensureRenderTarget(historyTarget, width, height); + } + if (target.fbo == 0 || target.texture == 0) { + if (allowHistory) { + displayTexture = sourceTexture; + clearHistory(); + } + return sourceTexture; } // --- Bloom using bright pass + separable blur (inspired by ProcessingPostFX) --- @@ -1110,9 +1117,9 @@ void Renderer::applyPostProcessing(const std::vector& sceneObjects) brightShader->use(); brightShader->setFloat("threshold", settings.bloomThreshold); glActiveTexture(GL_TEXTURE0); - glBindTexture(GL_TEXTURE_2D, viewportTexture); + glBindTexture(GL_TEXTURE_2D, sourceTexture); glBindFramebuffer(GL_FRAMEBUFFER, bloomTargetA.fbo); - glViewport(0, 0, currentWidth, currentHeight); + glViewport(0, 0, width, height); glClear(GL_COLOR_BUFFER_BIT); drawFullscreenQuad(); @@ -1128,11 +1135,11 @@ void Renderer::applyPostProcessing(const std::vector& sceneObjects) RenderTarget* writeTarget = &bloomTargetB; for (int i = 0; i < 4; ++i) { blurShader->setBool("horizontal", horizontal); - blurShader->setVec2("texelSize", glm::vec2(1.0f / currentWidth, 1.0f / currentHeight)); + blurShader->setVec2("texelSize", glm::vec2(1.0f / width, 1.0f / height)); glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D, pingTex); glBindFramebuffer(GL_FRAMEBUFFER, writeTarget->fbo); - glViewport(0, 0, currentWidth, currentHeight); + glViewport(0, 0, width, height); glClear(GL_COLOR_BUFFER_BIT); drawFullscreenQuad(); @@ -1161,7 +1168,7 @@ void Renderer::applyPostProcessing(const std::vector& sceneObjects) postShader->setVec3("colorFilter", settings.colorFilter); postShader->setBool("enableMotionBlur", settings.motionBlurEnabled); postShader->setFloat("motionBlurStrength", settings.motionBlurStrength); - postShader->setBool("hasHistory", historyValid); + postShader->setBool("hasHistory", allowHistory && historyValid); postShader->setBool("enableVignette", settings.vignetteEnabled); postShader->setFloat("vignetteIntensity", settings.vignetteIntensity); postShader->setFloat("vignetteSmoothness", settings.vignetteSmoothness); @@ -1172,39 +1179,44 @@ void Renderer::applyPostProcessing(const std::vector& sceneObjects) postShader->setFloat("aoStrength", settings.aoStrength); glActiveTexture(GL_TEXTURE0); - glBindTexture(GL_TEXTURE_2D, viewportTexture); + glBindTexture(GL_TEXTURE_2D, sourceTexture); glActiveTexture(GL_TEXTURE1); - glBindTexture(GL_TEXTURE_2D, bloomTex ? bloomTex : viewportTexture); + glBindTexture(GL_TEXTURE_2D, bloomTex ? bloomTex : sourceTexture); glActiveTexture(GL_TEXTURE2); - glBindTexture(GL_TEXTURE_2D, historyTarget.texture); + glBindTexture(GL_TEXTURE_2D, allowHistory ? historyTarget.texture : 0); - glBindFramebuffer(GL_FRAMEBUFFER, postTarget.fbo); - glViewport(0, 0, currentWidth, currentHeight); + glBindFramebuffer(GL_FRAMEBUFFER, target.fbo); + glViewport(0, 0, width, height); glClearColor(0.0f, 0.0f, 0.0f, 1.0f); glClear(GL_COLOR_BUFFER_BIT); drawFullscreenQuad(); glBindFramebuffer(GL_FRAMEBUFFER, 0); glEnable(GL_DEPTH_TEST); - displayTexture = postTarget.texture; + if (allowHistory) { + displayTexture = target.texture; + } - if (settings.motionBlurEnabled && historyTarget.fbo != 0) { - glBindFramebuffer(GL_READ_FRAMEBUFFER, postTarget.fbo); + if (settings.motionBlurEnabled && allowHistory && historyTarget.fbo != 0) { + glBindFramebuffer(GL_READ_FRAMEBUFFER, target.fbo); glBindFramebuffer(GL_DRAW_FRAMEBUFFER, historyTarget.fbo); - glBlitFramebuffer(0, 0, currentWidth, currentHeight, 0, 0, currentWidth, currentHeight, GL_COLOR_BUFFER_BIT, GL_NEAREST); + glBlitFramebuffer(0, 0, width, height, 0, 0, width, height, GL_COLOR_BUFFER_BIT, GL_NEAREST); glBindFramebuffer(GL_FRAMEBUFFER, 0); historyValid = true; - } else { + } else if (allowHistory) { clearHistory(); } + + return target.texture; } void Renderer::renderScene(const Camera& camera, const std::vector& sceneObjects, int /*selectedId*/, float fovDeg, float nearPlane, float farPlane) { renderSceneInternal(camera, sceneObjects, currentWidth, currentHeight, true, fovDeg, nearPlane, farPlane); - applyPostProcessing(sceneObjects); + unsigned int result = applyPostProcessing(sceneObjects, viewportTexture, currentWidth, currentHeight, true); + displayTexture = result ? result : viewportTexture; } -unsigned int Renderer::renderScenePreview(const Camera& camera, const std::vector& sceneObjects, int width, int height, float fovDeg, float nearPlane, float farPlane) { +unsigned int Renderer::renderScenePreview(const Camera& camera, const std::vector& sceneObjects, int width, int height, float fovDeg, float nearPlane, float farPlane, bool applyPostFX) { ensureRenderTarget(previewTarget, width, height); if (previewTarget.fbo == 0) return 0; @@ -1214,7 +1226,11 @@ unsigned int Renderer::renderScenePreview(const Camera& camera, const std::vecto glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); renderSceneInternal(camera, sceneObjects, width, height, true, fovDeg, nearPlane, farPlane); - return previewTarget.texture; + if (!applyPostFX) { + return previewTarget.texture; + } + unsigned int processed = applyPostProcessing(sceneObjects, previewTarget.texture, width, height, false); + return processed ? processed : previewTarget.texture; } void Renderer::endRender() { diff --git a/src/Rendering.h b/src/Rendering.h index 49489e3..0262080 100644 --- a/src/Rendering.h +++ b/src/Rendering.h @@ -69,6 +69,7 @@ private: }; RenderTarget previewTarget; RenderTarget postTarget; + RenderTarget previewPostTarget; RenderTarget historyTarget; RenderTarget bloomTargetA; RenderTarget bloomTargetB; @@ -112,7 +113,7 @@ private: void clearHistory(); void clearTarget(RenderTarget& target); void renderSceneInternal(const Camera& camera, const std::vector& sceneObjects, int width, int height, bool unbindFramebuffer, float fovDeg, float nearPlane, float farPlane); - void applyPostProcessing(const std::vector& sceneObjects); + unsigned int applyPostProcessing(const std::vector& sceneObjects, unsigned int sourceTexture, int width, int height, bool allowHistory); PostFXSettings gatherPostFX(const std::vector& sceneObjects) const; public: @@ -133,7 +134,7 @@ public: void renderSkybox(const glm::mat4& view, const glm::mat4& proj); void renderObject(const SceneObject& obj); void renderScene(const Camera& camera, const std::vector& sceneObjects, int selectedId = -1, float fovDeg = FOV, float nearPlane = NEAR_PLANE, float farPlane = FAR_PLANE); - unsigned int renderScenePreview(const Camera& camera, const std::vector& sceneObjects, int width, int height, float fovDeg, float nearPlane, float farPlane); + unsigned int renderScenePreview(const Camera& camera, const std::vector& sceneObjects, int width, int height, float fovDeg, float nearPlane, float farPlane, bool applyPostFX = false); void endRender(); Skybox* getSkybox() { return skybox; } diff --git a/src/SceneObject.h b/src/SceneObject.h index 8088cf5..6d7d9d0 100644 --- a/src/SceneObject.h +++ b/src/SceneObject.h @@ -55,6 +55,7 @@ struct CameraComponent { float fov = FOV; float nearClip = NEAR_PLANE; float farClip = FAR_PLANE; + bool applyPostFX = true; }; struct PostFXSettings {