added Git Package Manager
This commit is contained in:
@@ -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
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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 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");
|
||||
}
|
||||
}
|
||||
} 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
481
src/PackageManager.cpp
Normal 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
64
src/PackageManager.h
Normal 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;
|
||||
};
|
||||
@@ -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") {
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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; }
|
||||
|
||||
@@ -55,6 +55,7 @@ struct CameraComponent {
|
||||
float fov = FOV;
|
||||
float nearClip = NEAR_PLANE;
|
||||
float farClip = FAR_PLANE;
|
||||
bool applyPostFX = true;
|
||||
};
|
||||
|
||||
struct PostFXSettings {
|
||||
|
||||
Reference in New Issue
Block a user