added Git Package Manager

This commit is contained in:
Anemunt
2025-12-18 16:38:45 -05:00
parent 2eca8493e1
commit 655d4cce49
12 changed files with 1057 additions and 124 deletions

View File

@@ -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

View File

@@ -271,7 +271,7 @@ void applyModernTheme() {
style.TabBorderSize = 1.0f;
}
void setupDockspace() {
void setupDockspace(const std::function<void()>& 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);

View File

@@ -1,5 +1,6 @@
#pragma once
#include <functional>
#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<void()>& menuBarContent = nullptr);

View File

@@ -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;

View File

@@ -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();

View File

@@ -10,6 +10,9 @@
#include <sstream>
#include <unordered_set>
#include <optional>
#include <future>
#include <chrono>
#include <future>
#ifdef _WIN32
#include <shlobj.h>
@@ -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<PackageTaskResult> 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 were 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 were 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");
}
}
} else if (selectedTab == 1) {
static PackageTaskState packageTask;
static std::string packageStatus;
static char gitUrlBuf[256] = "";
static char gitNameBuf[128] = "";
static char gitIncludeBuf[128] = "include";
if (ImGui::CollapsingHeader("Loaded OBJ Meshes")) {
const auto& meshes = g_objLoader.getAllMeshes();
if (meshes.empty()) {
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<PackageTaskResult>();
}
};
auto startPackageTask = [&](const char* label, std::function<PackageTaskResult()> fn) {
if (packageTask.active) return;
packageTask.active = true;
packageTask.label = label;
packageTask.startTime = static_cast<float>(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<float>(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);

481
src/PackageManager.cpp Normal file
View File

@@ -0,0 +1,481 @@
#include "PackageManager.h"
#include <unordered_set>
#include <array>
#include <cstdio>
#include <sstream>
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<fs::path>& 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<std::string> defineSet(config.defines.begin(), config.defines.end());
std::unordered_set<std::string> linuxLibSet(config.linuxLinkLibs.begin(), config.linuxLinkLibs.end());
std::unordered_set<std::string> 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<std::string> 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<std::string> 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<std::string> 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<unsigned char>(value[start]))) start++;
size_t end = value.size();
while (end > start && std::isspace(static_cast<unsigned char>(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<unsigned char>(c))) {
out.push_back(static_cast<char>(std::tolower(static_cast<unsigned char>(c))));
} else if (c == '-' || c == '_') {
out.push_back(c);
} else if (std::isspace(static_cast<unsigned char>(c))) {
out.push_back('-');
}
}
if (out.empty()) out = "pkg";
return out;
}
bool PackageManager::runCommand(const std::string& command, std::string& output) {
std::array<char, 256> 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<std::string> PackageManager::split(const std::string& input, char delim) const {
std::vector<std::string> 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<std::string>& 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;
}

64
src/PackageManager.h Normal file
View File

@@ -0,0 +1,64 @@
#pragma once
#include "Common.h"
#include "ScriptCompiler.h"
#include <cctype>
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<fs::path> includeDirs;
std::vector<std::string> defines;
std::vector<std::string> linuxLibs;
std::vector<std::string> 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<PackageInfo>& getRegistry() const { return registry; }
const std::vector<std::string>& 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<std::string> split(const std::string& input, char delim) const;
std::string join(const std::vector<std::string>& vals, char delim) const;
fs::path packagesFolder() const;
fs::path projectRoot;
fs::path manifestPath;
std::vector<PackageInfo> registry;
std::vector<std::string> installedIds;
std::string lastError;
};

View File

@@ -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") {

View File

@@ -1072,7 +1072,7 @@ PostFXSettings Renderer::gatherPostFX(const std::vector<SceneObject>& sceneObjec
return combined;
}
void Renderer::applyPostProcessing(const std::vector<SceneObject>& sceneObjects) {
unsigned int Renderer::applyPostProcessing(const std::vector<SceneObject>& 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<SceneObject>& 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<SceneObject>& 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<SceneObject>& 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<SceneObject>& 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<SceneObject>& 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<SceneObject>& 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<SceneObject>& sceneObjects, int width, int height, float fovDeg, float nearPlane, float farPlane) {
unsigned int Renderer::renderScenePreview(const Camera& camera, const std::vector<SceneObject>& 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() {

View File

@@ -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<SceneObject>& sceneObjects, int width, int height, bool unbindFramebuffer, float fovDeg, float nearPlane, float farPlane);
void applyPostProcessing(const std::vector<SceneObject>& sceneObjects);
unsigned int applyPostProcessing(const std::vector<SceneObject>& sceneObjects, unsigned int sourceTexture, int width, int height, bool allowHistory);
PostFXSettings gatherPostFX(const std::vector<SceneObject>& 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<SceneObject>& sceneObjects, int selectedId = -1, float fovDeg = FOV, float nearPlane = NEAR_PLANE, float farPlane = FAR_PLANE);
unsigned int renderScenePreview(const Camera& camera, const std::vector<SceneObject>& sceneObjects, int width, int height, float fovDeg, float nearPlane, float farPlane);
unsigned int renderScenePreview(const Camera& camera, const std::vector<SceneObject>& sceneObjects, int width, int height, float fovDeg, float nearPlane, float farPlane, bool applyPostFX = false);
void endRender();
Skybox* getSkybox() { return skybox; }

View File

@@ -55,6 +55,7 @@ struct CameraComponent {
float fov = FOV;
float nearClip = NEAR_PLANE;
float farClip = FAR_PLANE;
bool applyPostFX = true;
};
struct PostFXSettings {