Yey! Shader Compilation!

This commit is contained in:
Anemunt
2025-12-09 17:52:50 -05:00
parent 07bc0177d5
commit acebe7d7c0
13 changed files with 558 additions and 103 deletions

View File

@@ -16,6 +16,7 @@ uniform vec3 viewPos;
uniform vec3 materialColor = vec3(1.0); uniform vec3 materialColor = vec3(1.0);
uniform float ambientStrength = 0.2; uniform float ambientStrength = 0.2;
uniform vec3 ambientColor = vec3(1.0);
uniform float specularStrength = 0.5; uniform float specularStrength = 0.5;
uniform float shininess = 32.0; uniform float shininess = 32.0;
const int MAX_LIGHTS = 10; const int MAX_LIGHTS = 10;
@@ -62,7 +63,7 @@ void main()
norm = normalize(TBN * mapN); norm = normalize(TBN * mapN);
} }
vec3 ambient = ambientStrength * baseColor; vec3 ambient = ambientStrength * ambientColor * baseColor;
vec3 lighting = ambient; vec3 lighting = ambient;
int count = min(lightCount, MAX_LIGHTS); int count = min(lightCount, MAX_LIGHTS);

View File

@@ -15,24 +15,24 @@ Collapsed=0
[Window][DockSpace] [Window][DockSpace]
Pos=0,21 Pos=0,21
Size=1858,1036 Size=1920,985
Collapsed=0 Collapsed=0
[Window][Viewport] [Window][Viewport]
Pos=306,42 Pos=306,42
Size=1203,792 Size=1265,741
Collapsed=0 Collapsed=0
DockId=0x00000002,0 DockId=0x00000002,0
[Window][Hierarchy] [Window][Hierarchy]
Pos=0,42 Pos=0,42
Size=304,792 Size=304,741
Collapsed=0 Collapsed=0
DockId=0x00000001,0 DockId=0x00000001,0
[Window][Inspector] [Window][Inspector]
Pos=1511,42 Pos=1573,42
Size=347,1015 Size=347,964
Collapsed=0 Collapsed=0
DockId=0x00000008,0 DockId=0x00000008,0
@@ -43,31 +43,42 @@ Collapsed=0
DockId=0x00000006,1 DockId=0x00000006,1
[Window][Console] [Window][Console]
Pos=0,836 Pos=0,785
Size=754,221 Size=785,221
Collapsed=0 Collapsed=0
DockId=0x00000005,0 DockId=0x00000005,0
[Window][Project] [Window][Project]
Pos=756,836 Pos=787,785
Size=753,221 Size=784,221
Collapsed=0 Collapsed=0
DockId=0x00000006,0 DockId=0x00000006,0
[Window][Launcher] [Window][Launcher]
ViewportPos=0,0 Pos=0,0
ViewportId=0xF8E7BCBC
Size=1000,800 Size=1000,800
Collapsed=0 Collapsed=0
[Window][Camera]
Pos=0,42
Size=304,741
Collapsed=0
DockId=0x00000001,1
[Window][Environment]
Pos=1573,42
Size=347,964
Collapsed=0
DockId=0x00000008,1
[Docking][Data] [Docking][Data]
DockSpace ID=0xD71539A0 Window=0x3DA2F1DE Pos=62,65 Size=1858,1015 Split=X DockSpace ID=0xD71539A0 Window=0x3DA2F1DE Pos=0,42 Size=1920,964 Split=X
DockNode ID=0x00000007 Parent=0xD71539A0 SizeRef=1509,1015 Split=Y DockNode ID=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,792 Split=X
DockNode ID=0x00000001 Parent=0x00000003 SizeRef=304,758 Selected=0xBABDAE5E DockNode ID=0x00000001 Parent=0x00000003 SizeRef=304,758 Selected=0xBABDAE5E
DockNode ID=0x00000002 Parent=0x00000003 SizeRef=694,758 CentralNode=1 Selected=0xC450F867 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,221 Split=X Selected=0xEA83D666
DockNode ID=0x00000005 Parent=0x00000004 SizeRef=929,221 Selected=0xEA83D666 DockNode ID=0x00000005 Parent=0x00000004 SizeRef=929,221 Selected=0xEA83D666
DockNode ID=0x00000006 Parent=0x00000004 SizeRef=927,221 Selected=0x14395839 DockNode ID=0x00000006 Parent=0x00000004 SizeRef=927,221 Selected=0x9C21DE82
DockNode ID=0x00000008 Parent=0xD71539A0 SizeRef=347,1015 Selected=0x36DC96AB DockNode ID=0x00000008 Parent=0xD71539A0 SizeRef=347,1015 Selected=0x36DC96AB

View File

@@ -1,15 +1,20 @@
#ifndef SKYBOX_H #ifndef SKYBOX_H
#define SKYBOX_H #define SKYBOX_H
#include <string>
class Shader; class Shader;
class Skybox { class Skybox {
private: private:
unsigned int VAO, VBO; unsigned int VAO, VBO;
Shader* skyboxShader; Shader* skyboxShader;
std::string vertPath = "Resources/Shaders/skybox_vert.glsl";
std::string fragPath = "Resources/Shaders/skybox_frag.glsl";
float timeOfDay = 0.5f; // 0.0 = night, 0.25 = sunrise, 0.5 = day, 0.75 = sunset, 1.0 = midnight float timeOfDay = 0.5f; // 0.0 = night, 0.25 = sunrise, 0.5 = day, 0.75 = sunset, 1.0 = midnight
void setupMesh(); void setupMesh();
bool reloadShader();
public: public:
Skybox(); Skybox();
@@ -18,6 +23,9 @@ public:
void draw(const float* view, const float* projection); void draw(const float* view, const float* projection);
void setTimeOfDay(float time); // 0.0 to 1.0 void setTimeOfDay(float time); // 0.0 to 1.0
float getTimeOfDay() const { return timeOfDay; } float getTimeOfDay() const { return timeOfDay; }
void setShaderPaths(const std::string& vert, const std::string& frag);
std::string getVertPath() const { return vertPath; }
std::string getFragPath() const { return fragPath; }
}; };
#endif #endif

View File

@@ -29,13 +29,9 @@ void Camera::processMouse(double xpos, double ypos) {
} }
void Camera::processKeyboard(float deltaTime, GLFWwindow* window) { void Camera::processKeyboard(float deltaTime, GLFWwindow* window) {
const float CAMERA_SPEED = 5.0f; float currentSpeed = moveSpeed;
const float SPRINT_SPEED = 10.0f;
const float ACCELERATION = 15.0f;
float currentSpeed = CAMERA_SPEED;
if (glfwGetKey(window, GLFW_KEY_LEFT_SHIFT) == GLFW_PRESS) { if (glfwGetKey(window, GLFW_KEY_LEFT_SHIFT) == GLFW_PRESS) {
currentSpeed = SPRINT_SPEED; currentSpeed = sprintSpeed;
} }
glm::vec3 desiredDir(0.0f); glm::vec3 desiredDir(0.0f);
@@ -72,21 +68,29 @@ void Camera::processKeyboard(float deltaTime, GLFWwindow* window) {
glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
} }
if (smoothMovement) {
glm::vec3 targetVelocity(0.0f); glm::vec3 targetVelocity(0.0f);
if (isMoving) { if (isMoving) {
float length = glm::length(desiredDir); float length = glm::length(desiredDir);
if (length > 0.0001f) { if (length > 0.0001f) {
desiredDir = desiredDir / length; desiredDir = desiredDir / length;
targetVelocity = desiredDir * currentSpeed; targetVelocity = desiredDir * currentSpeed;
} else {
targetVelocity = glm::vec3(0.0f);
} }
} }
float smoothFactor = 1.0f - std::exp(-ACCELERATION * deltaTime); float smoothFactor = 1.0f - std::exp(-acceleration * deltaTime);
velocity = glm::mix(velocity, targetVelocity, smoothFactor); velocity = glm::mix(velocity, targetVelocity, smoothFactor);
position += velocity * deltaTime; position += velocity * deltaTime;
} else {
velocity = glm::vec3(0.0f);
if (isMoving) {
float length = glm::length(desiredDir);
if (length > 0.0001f) {
desiredDir /= length;
position += desiredDir * currentSpeed * deltaTime;
}
}
}
} }
glm::mat4 Camera::getViewMatrix() const { glm::mat4 Camera::getViewMatrix() const {

View File

@@ -9,6 +9,10 @@ public:
glm::vec3 front = glm::vec3(0.0f, 0.0f, -1.0f); glm::vec3 front = glm::vec3(0.0f, 0.0f, -1.0f);
glm::vec3 up = glm::vec3(0.0f, 1.0f, 0.0f); glm::vec3 up = glm::vec3(0.0f, 1.0f, 0.0f);
glm::vec3 velocity = glm::vec3(0.0f); glm::vec3 velocity = glm::vec3(0.0f);
float moveSpeed = 5.0f;
float sprintSpeed = 10.0f;
float acceleration = 15.0f;
bool smoothMovement = true;
float yaw = -90.0f; float yaw = -90.0f;
float pitch = 0.0f; float pitch = 0.0f;
float speed = CAMERA_SPEED; float speed = CAMERA_SPEED;

View File

@@ -10,6 +10,8 @@ struct MaterialFileData {
std::string overlay; std::string overlay;
std::string normal; std::string normal;
bool useOverlay = false; bool useOverlay = false;
std::string vertexShader;
std::string fragmentShader;
}; };
bool readMaterialFile(const std::string& path, MaterialFileData& outData) { bool readMaterialFile(const std::string& path, MaterialFileData& outData) {
@@ -44,6 +46,10 @@ bool readMaterialFile(const std::string& path, MaterialFileData& outData) {
outData.normal = val; outData.normal = val;
} else if (key == "useOverlay") { } else if (key == "useOverlay") {
outData.useOverlay = std::stoi(val) != 0; outData.useOverlay = std::stoi(val) != 0;
} else if (key == "vertexShader") {
outData.vertexShader = val;
} else if (key == "fragmentShader") {
outData.fragmentShader = val;
} }
} }
return true; return true;
@@ -64,6 +70,8 @@ bool writeMaterialFile(const MaterialFileData& data, const std::string& path) {
f << "albedo=" << data.albedo << "\n"; f << "albedo=" << data.albedo << "\n";
f << "overlay=" << data.overlay << "\n"; f << "overlay=" << data.overlay << "\n";
f << "normal=" << data.normal << "\n"; f << "normal=" << data.normal << "\n";
f << "vertexShader=" << data.vertexShader << "\n";
f << "fragmentShader=" << data.fragmentShader << "\n";
return true; return true;
} }
} // namespace } // namespace
@@ -93,6 +101,55 @@ void Engine::DecomposeMatrix(const glm::mat4& matrix, glm::vec3& pos, glm::vec3&
rot = glm::eulerAngles(glm::quat_cast(rotMat)); rot = glm::eulerAngles(glm::quat_cast(rotMat));
} }
void Engine::recordState(const char* /*reason*/) {
SceneSnapshot snap;
snap.objects = sceneObjects;
snap.selectedId = selectedObjectId;
snap.nextId = nextObjectId;
undoStack.push_back(std::move(snap));
if (undoStack.size() > 64) {
undoStack.erase(undoStack.begin());
}
redoStack.clear();
}
void Engine::undo() {
if (undoStack.empty()) return;
SceneSnapshot current;
current.objects = sceneObjects;
current.selectedId = selectedObjectId;
current.nextId = nextObjectId;
SceneSnapshot snap = undoStack.back();
undoStack.pop_back();
redoStack.push_back(std::move(current));
sceneObjects = std::move(snap.objects);
selectedObjectId = snap.selectedId;
nextObjectId = snap.nextId;
projectManager.currentProject.hasUnsavedChanges = true;
}
void Engine::redo() {
if (redoStack.empty()) return;
SceneSnapshot current;
current.objects = sceneObjects;
current.selectedId = selectedObjectId;
current.nextId = nextObjectId;
SceneSnapshot snap = redoStack.back();
redoStack.pop_back();
undoStack.push_back(std::move(current));
sceneObjects = std::move(snap.objects);
selectedObjectId = snap.selectedId;
nextObjectId = snap.nextId;
projectManager.currentProject.hasUnsavedChanges = true;
}
bool Engine::init() { bool Engine::init() {
std::cerr << "[DEBUG] Creating window..." << std::endl; std::cerr << "[DEBUG] Creating window..." << std::endl;
editorWindow = window.makeWindow(); editorWindow = window.makeWindow();
@@ -207,6 +264,8 @@ void Engine::run() {
if (showInspector) renderInspectorPanel(); if (showInspector) renderInspectorPanel();
if (showFileBrowser) renderFileBrowserPanel(); if (showFileBrowser) renderFileBrowserPanel();
if (showConsole) renderConsolePanel(); if (showConsole) renderConsolePanel();
if (showEnvironmentWindow) renderEnvironmentWindow();
if (showCameraWindow) renderCameraWindow();
if (showProjectBrowser) renderProjectBrowserPanel(); if (showProjectBrowser) renderProjectBrowserPanel();
} }
@@ -258,6 +317,7 @@ void Engine::shutdown() {
} }
void Engine::importOBJToScene(const std::string& filepath, const std::string& objectName) { void Engine::importOBJToScene(const std::string& filepath, const std::string& objectName) {
recordState("importOBJ");
std::string errorMsg; std::string errorMsg;
int meshId = g_objLoader.loadOBJ(filepath, errorMsg); int meshId = g_objLoader.loadOBJ(filepath, errorMsg);
@@ -292,6 +352,7 @@ void Engine::importOBJToScene(const std::string& filepath, const std::string& ob
} }
void Engine::importModelToScene(const std::string& filepath, const std::string& objectName) { void Engine::importModelToScene(const std::string& filepath, const std::string& objectName) {
recordState("importModel");
auto& modelLoader = getModelLoader(); auto& modelLoader = getModelLoader();
ModelLoadResult result = modelLoader.loadModel(filepath); ModelLoadResult result = modelLoader.loadModel(filepath);
@@ -336,6 +397,8 @@ void Engine::loadMaterialFromFile(SceneObject& obj) {
obj.overlayTexturePath = data.overlay; obj.overlayTexturePath = data.overlay;
obj.normalMapPath = data.normal; obj.normalMapPath = data.normal;
obj.useOverlay = data.useOverlay; obj.useOverlay = data.useOverlay;
obj.vertexShaderPath = data.vertexShader;
obj.fragmentShaderPath = data.fragmentShader;
addConsoleMessage("Applied material: " + obj.materialPath, ConsoleMessageType::Success); addConsoleMessage("Applied material: " + obj.materialPath, ConsoleMessageType::Success);
projectManager.currentProject.hasUnsavedChanges = true; projectManager.currentProject.hasUnsavedChanges = true;
} catch (...) { } catch (...) {
@@ -345,7 +408,9 @@ void Engine::loadMaterialFromFile(SceneObject& obj) {
bool Engine::loadMaterialData(const std::string& path, MaterialProperties& props, bool Engine::loadMaterialData(const std::string& path, MaterialProperties& props,
std::string& albedo, std::string& overlay, std::string& albedo, std::string& overlay,
std::string& normal, bool& useOverlay) std::string& normal, bool& useOverlay,
std::string* vertexShaderOut,
std::string* fragmentShaderOut)
{ {
MaterialFileData data; MaterialFileData data;
if (!readMaterialFile(path, data)) { if (!readMaterialFile(path, data)) {
@@ -356,12 +421,16 @@ bool Engine::loadMaterialData(const std::string& path, MaterialProperties& props
overlay = data.overlay; overlay = data.overlay;
normal = data.normal; normal = data.normal;
useOverlay = data.useOverlay; useOverlay = data.useOverlay;
if (vertexShaderOut) *vertexShaderOut = data.vertexShader;
if (fragmentShaderOut) *fragmentShaderOut = data.fragmentShader;
return true; return true;
} }
bool Engine::saveMaterialData(const std::string& path, const MaterialProperties& props, bool Engine::saveMaterialData(const std::string& path, const MaterialProperties& props,
const std::string& albedo, const std::string& overlay, const std::string& albedo, const std::string& overlay,
const std::string& normal, bool useOverlay) const std::string& normal, bool useOverlay,
const std::string& vertexShader,
const std::string& fragmentShader)
{ {
MaterialFileData data; MaterialFileData data;
data.props = props; data.props = props;
@@ -369,6 +438,8 @@ bool Engine::saveMaterialData(const std::string& path, const MaterialProperties&
data.overlay = overlay; data.overlay = overlay;
data.normal = normal; data.normal = normal;
data.useOverlay = useOverlay; data.useOverlay = useOverlay;
data.vertexShader = vertexShader;
data.fragmentShader = fragmentShader;
return writeMaterialFile(data, path); return writeMaterialFile(data, path);
} }
@@ -384,6 +455,8 @@ void Engine::saveMaterialToFile(const SceneObject& obj) {
data.overlay = obj.overlayTexturePath; data.overlay = obj.overlayTexturePath;
data.normal = obj.normalMapPath; data.normal = obj.normalMapPath;
data.useOverlay = obj.useOverlay; data.useOverlay = obj.useOverlay;
data.vertexShader = obj.vertexShaderPath;
data.fragmentShader = obj.fragmentShaderPath;
if (!writeMaterialFile(data, obj.materialPath)) { if (!writeMaterialFile(data, obj.materialPath)) {
addConsoleMessage("Failed to open material for writing: " + obj.materialPath, ConsoleMessageType::Error); addConsoleMessage("Failed to open material for writing: " + obj.materialPath, ConsoleMessageType::Error);
@@ -431,6 +504,7 @@ void Engine::handleKeyboardShortcuts() {
ctrlNPressed = false; ctrlNPressed = false;
} }
if (!cursorLocked) {
if (ImGui::IsKeyPressed(ImGuiKey_Q)) mCurrentGizmoOperation = ImGuizmo::TRANSLATE; if (ImGui::IsKeyPressed(ImGuiKey_Q)) mCurrentGizmoOperation = ImGuizmo::TRANSLATE;
if (ImGui::IsKeyPressed(ImGuiKey_W)) mCurrentGizmoOperation = ImGuizmo::ROTATE; if (ImGui::IsKeyPressed(ImGuiKey_W)) mCurrentGizmoOperation = ImGuizmo::ROTATE;
if (ImGui::IsKeyPressed(ImGuiKey_E)) mCurrentGizmoOperation = ImGuizmo::SCALE; if (ImGui::IsKeyPressed(ImGuiKey_E)) mCurrentGizmoOperation = ImGuizmo::SCALE;
@@ -439,8 +513,34 @@ void Engine::handleKeyboardShortcuts() {
if (ImGui::IsKeyPressed(ImGuiKey_Z)) { if (ImGui::IsKeyPressed(ImGuiKey_Z)) {
mCurrentGizmoMode = (mCurrentGizmoMode == ImGuizmo::LOCAL) ? ImGuizmo::WORLD : ImGuizmo::LOCAL; mCurrentGizmoMode = (mCurrentGizmoMode == ImGuizmo::LOCAL) ? ImGuizmo::WORLD : ImGuizmo::LOCAL;
} }
}
if (ImGui::IsKeyPressed(ImGuiKey_LeftCtrl)) useSnap = !useSnap; static bool snapPressed = false;
if (ImGui::IsKeyPressed(ImGuiKey_X) && !snapPressed) {
useSnap = !useSnap;
snapPressed = true;
}
if (ImGui::IsKeyReleased(ImGuiKey_X)) {
snapPressed = false;
}
static bool undoPressed = false;
if (ctrlDown && glfwGetKey(editorWindow, GLFW_KEY_Z) == GLFW_PRESS && !undoPressed) {
undo();
undoPressed = true;
}
if (glfwGetKey(editorWindow, GLFW_KEY_Z) == GLFW_RELEASE) {
undoPressed = false;
}
static bool redoPressed = false;
if (ctrlDown && glfwGetKey(editorWindow, GLFW_KEY_Y) == GLFW_PRESS && !redoPressed) {
redo();
redoPressed = true;
}
if (glfwGetKey(editorWindow, GLFW_KEY_Y) == GLFW_RELEASE) {
redoPressed = false;
}
} }
void Engine::OpenProjectPath(const std::string& path) { void Engine::OpenProjectPath(const std::string& path) {
@@ -497,6 +597,8 @@ void Engine::loadRecentScenes() {
sceneObjects.clear(); sceneObjects.clear();
selectedObjectId = -1; selectedObjectId = -1;
nextObjectId = 0; nextObjectId = 0;
undoStack.clear();
redoStack.clear();
fs::path scenePath = projectManager.currentProject.getSceneFilePath(projectManager.currentProject.currentSceneName); fs::path scenePath = projectManager.currentProject.getSceneFilePath(projectManager.currentProject.currentSceneName);
if (fs::exists(scenePath)) { if (fs::exists(scenePath)) {
@@ -510,6 +612,7 @@ void Engine::loadRecentScenes() {
addConsoleMessage("Default scene not found, starting with a new scene.", ConsoleMessageType::Info); addConsoleMessage("Default scene not found, starting with a new scene.", ConsoleMessageType::Info);
addObject(ObjectType::Cube, "Cube"); addObject(ObjectType::Cube, "Cube");
} }
recordState("sceneLoaded");
fileBrowser.currentPath = projectManager.currentProject.assetsPath; fileBrowser.currentPath = projectManager.currentProject.assetsPath;
fileBrowser.needsRefresh = true; fileBrowser.needsRefresh = true;
@@ -537,6 +640,8 @@ void Engine::loadScene(const std::string& sceneName) {
fs::path scenePath = projectManager.currentProject.getSceneFilePath(sceneName); fs::path scenePath = projectManager.currentProject.getSceneFilePath(sceneName);
if (SceneSerializer::loadScene(scenePath, sceneObjects, nextObjectId)) { if (SceneSerializer::loadScene(scenePath, sceneObjects, nextObjectId)) {
undoStack.clear();
redoStack.clear();
projectManager.currentProject.currentSceneName = sceneName; projectManager.currentProject.currentSceneName = sceneName;
projectManager.currentProject.hasUnsavedChanges = false; projectManager.currentProject.hasUnsavedChanges = false;
projectManager.currentProject.saveProjectFile(); projectManager.currentProject.saveProjectFile();
@@ -547,6 +652,7 @@ void Engine::loadScene(const std::string& sceneName) {
if (!hasDirLight) { if (!hasDirLight) {
addObject(ObjectType::DirectionalLight, "Directional Light"); addObject(ObjectType::DirectionalLight, "Directional Light");
} }
recordState("sceneLoaded");
addConsoleMessage("Loaded scene: " + sceneName, ConsoleMessageType::Success); addConsoleMessage("Loaded scene: " + sceneName, ConsoleMessageType::Success);
} else { } else {
addConsoleMessage("Error: Failed to load scene: " + sceneName, ConsoleMessageType::Error); addConsoleMessage("Error: Failed to load scene: " + sceneName, ConsoleMessageType::Error);
@@ -563,6 +669,8 @@ void Engine::createNewScene(const std::string& sceneName) {
sceneObjects.clear(); sceneObjects.clear();
selectedObjectId = -1; selectedObjectId = -1;
nextObjectId = 0; nextObjectId = 0;
undoStack.clear();
redoStack.clear();
projectManager.currentProject.currentSceneName = sceneName; projectManager.currentProject.currentSceneName = sceneName;
projectManager.currentProject.hasUnsavedChanges = true; projectManager.currentProject.hasUnsavedChanges = true;
@@ -570,11 +678,13 @@ void Engine::createNewScene(const std::string& sceneName) {
addObject(ObjectType::Cube, "Cube"); addObject(ObjectType::Cube, "Cube");
addObject(ObjectType::DirectionalLight, "Directional Light"); addObject(ObjectType::DirectionalLight, "Directional Light");
saveCurrentScene(); saveCurrentScene();
recordState("newScene");
addConsoleMessage("Created new scene: " + sceneName, ConsoleMessageType::Success); addConsoleMessage("Created new scene: " + sceneName, ConsoleMessageType::Success);
} }
void Engine::addObject(ObjectType type, const std::string& baseName) { void Engine::addObject(ObjectType type, const std::string& baseName) {
recordState("addObject");
int id = nextObjectId++; int id = nextObjectId++;
std::string name = baseName + " " + std::to_string(id); std::string name = baseName + " " + std::to_string(id);
sceneObjects.push_back(SceneObject(name, type, id)); sceneObjects.push_back(SceneObject(name, type, id));
@@ -605,6 +715,7 @@ void Engine::duplicateSelected() {
[this](const SceneObject& obj) { return obj.id == selectedObjectId; }); [this](const SceneObject& obj) { return obj.id == selectedObjectId; });
if (it != sceneObjects.end()) { if (it != sceneObjects.end()) {
recordState("duplicate");
int id = nextObjectId++; int id = nextObjectId++;
SceneObject newObj(it->name + " (Copy)", it->type, id); SceneObject newObj(it->name + " (Copy)", it->type, id);
newObj.position = it->position + glm::vec3(1.0f, 0.0f, 0.0f); newObj.position = it->position + glm::vec3(1.0f, 0.0f, 0.0f);
@@ -617,6 +728,8 @@ void Engine::duplicateSelected() {
newObj.albedoTexturePath = it->albedoTexturePath; newObj.albedoTexturePath = it->albedoTexturePath;
newObj.overlayTexturePath = it->overlayTexturePath; newObj.overlayTexturePath = it->overlayTexturePath;
newObj.normalMapPath = it->normalMapPath; newObj.normalMapPath = it->normalMapPath;
newObj.vertexShaderPath = it->vertexShaderPath;
newObj.fragmentShaderPath = it->fragmentShaderPath;
newObj.useOverlay = it->useOverlay; newObj.useOverlay = it->useOverlay;
newObj.light = it->light; newObj.light = it->light;
@@ -630,6 +743,7 @@ void Engine::duplicateSelected() {
} }
void Engine::deleteSelected() { void Engine::deleteSelected() {
recordState("delete");
auto it = std::remove_if(sceneObjects.begin(), sceneObjects.end(), auto it = std::remove_if(sceneObjects.begin(), sceneObjects.end(),
[this](const SceneObject& obj) { return obj.id == selectedObjectId; }); [this](const SceneObject& obj) { return obj.id == selectedObjectId; });
@@ -644,6 +758,7 @@ void Engine::deleteSelected() {
} }
void Engine::setParent(int childId, int parentId) { void Engine::setParent(int childId, int parentId) {
recordState("reparent");
auto childIt = std::find_if(sceneObjects.begin(), sceneObjects.end(), auto childIt = std::find_if(sceneObjects.begin(), sceneObjects.end(),
[childId](const SceneObject& obj) { return obj.id == childId; }); [childId](const SceneObject& obj) { return obj.id == childId; });

View File

@@ -22,14 +22,24 @@ private:
bool cursorLocked = false; // true only while holding right mouse for freelook bool cursorLocked = false; // true only while holding right mouse for freelook
int viewportWidth = 800; int viewportWidth = 800;
int viewportHeight = 600; int viewportHeight = 600;
bool gizmoHistoryCaptured = false;
// Standalone material inspection cache // Standalone material inspection cache
std::string inspectedMaterialPath; std::string inspectedMaterialPath;
MaterialProperties inspectedMaterial; MaterialProperties inspectedMaterial;
std::string inspectedAlbedo; std::string inspectedAlbedo;
std::string inspectedOverlay; std::string inspectedOverlay;
std::string inspectedNormal; std::string inspectedNormal;
std::string inspectedVertShader;
std::string inspectedFragShader;
bool inspectedUseOverlay = false; bool inspectedUseOverlay = false;
bool inspectedMaterialValid = false; bool inspectedMaterialValid = false;
struct SceneSnapshot {
std::vector<SceneObject> objects;
int selectedId = -1;
int nextId = 0;
};
std::vector<SceneSnapshot> undoStack;
std::vector<SceneSnapshot> redoStack;
std::vector<SceneObject> sceneObjects; std::vector<SceneObject> sceneObjects;
int selectedObjectId = -1; int selectedObjectId = -1;
@@ -70,6 +80,8 @@ private:
char fileBrowserSearch[256] = ""; char fileBrowserSearch[256] = "";
float fileBrowserIconScale = 1.0f; // 0.5 to 2.0 range float fileBrowserIconScale = 1.0f; // 0.5 to 2.0 range
bool showEnvironmentWindow = true;
bool showCameraWindow = true;
// Private methods // Private methods
SceneObject* getSelectedObject(); SceneObject* getSelectedObject();
@@ -85,6 +97,8 @@ private:
void renderNewProjectDialog(); void renderNewProjectDialog();
void renderOpenProjectDialog(); void renderOpenProjectDialog();
void renderMainMenuBar(); void renderMainMenuBar();
void renderEnvironmentWindow();
void renderCameraWindow();
void renderHierarchyPanel(); void renderHierarchyPanel();
void renderObjectNode(SceneObject& obj, const std::string& filter); void renderObjectNode(SceneObject& obj, const std::string& filter);
void renderFileBrowserPanel(); void renderFileBrowserPanel();
@@ -117,6 +131,9 @@ private:
void setParent(int childId, int parentId); void setParent(int childId, int parentId);
void loadMaterialFromFile(SceneObject& obj); void loadMaterialFromFile(SceneObject& obj);
void saveMaterialToFile(const SceneObject& obj); void saveMaterialToFile(const SceneObject& obj);
void recordState(const char* reason = "");
void undo();
void redo();
// Console/logging // Console/logging
void addConsoleMessage(const std::string& message, ConsoleMessageType type); void addConsoleMessage(const std::string& message, ConsoleMessageType type);
@@ -125,10 +142,14 @@ private:
// Material helpers // Material helpers
bool loadMaterialData(const std::string& path, MaterialProperties& props, bool loadMaterialData(const std::string& path, MaterialProperties& props,
std::string& albedo, std::string& overlay, std::string& albedo, std::string& overlay,
std::string& normal, bool& useOverlay); std::string& normal, bool& useOverlay,
std::string* vertexShaderOut = nullptr,
std::string* fragmentShaderOut = nullptr);
bool saveMaterialData(const std::string& path, const MaterialProperties& props, bool saveMaterialData(const std::string& path, const MaterialProperties& props,
const std::string& albedo, const std::string& overlay, const std::string& albedo, const std::string& overlay,
const std::string& normal, bool useOverlay); const std::string& normal, bool useOverlay,
const std::string& vertexShader,
const std::string& fragmentShader);
// ImGui setup // ImGui setup
void setupImGui(); void setupImGui();

View File

@@ -1110,6 +1110,8 @@ void Engine::renderMainMenuBar() {
ImGui::MenuItem("File Browser", nullptr, &showFileBrowser); ImGui::MenuItem("File Browser", nullptr, &showFileBrowser);
ImGui::MenuItem("Console", nullptr, &showConsole); ImGui::MenuItem("Console", nullptr, &showConsole);
ImGui::MenuItem("Project", nullptr, &showProjectBrowser); ImGui::MenuItem("Project", nullptr, &showProjectBrowser);
ImGui::MenuItem("Environment", nullptr, &showEnvironmentWindow);
ImGui::MenuItem("Camera", nullptr, &showCameraWindow);
ImGui::Separator(); ImGui::Separator();
if (ImGui::MenuItem("Fullscreen Viewport", "F11", viewportFullscreen)) { if (ImGui::MenuItem("Fullscreen Viewport", "F11", viewportFullscreen)) {
viewportFullscreen = !viewportFullscreen; viewportFullscreen = !viewportFullscreen;
@@ -1271,22 +1273,6 @@ void Engine::renderObjectNode(SceneObject& obj, const std::string& filter) {
void Engine::renderInspectorPanel() { void Engine::renderInspectorPanel() {
ImGui::Begin("Inspector", &showInspector); ImGui::Begin("Inspector", &showInspector);
// Environment controls
if (Skybox* skybox = renderer.getSkybox()) {
ImGui::PushStyleColor(ImGuiCol_Header, ImVec4(0.25f, 0.45f, 0.55f, 1.0f));
if (ImGui::CollapsingHeader("Environment", ImGuiTreeNodeFlags_DefaultOpen)) {
float tod = skybox->getTimeOfDay();
ImGui::TextDisabled("Day/Night Cycle");
ImGui::SetNextItemWidth(-1);
if (ImGui::SliderFloat("##DayNight", &tod, 0.0f, 1.0f, "%.2f")) {
skybox->setTimeOfDay(tod);
projectManager.currentProject.hasUnsavedChanges = true;
}
}
ImGui::PopStyleColor();
ImGui::Spacing();
}
fs::path selectedMaterialPath; fs::path selectedMaterialPath;
bool browserHasMaterial = false; bool browserHasMaterial = false;
if (!fileBrowser.selectedFile.empty() && fs::exists(fileBrowser.selectedFile)) { if (!fileBrowser.selectedFile.empty() && fs::exists(fileBrowser.selectedFile)) {
@@ -1301,7 +1287,9 @@ void Engine::renderInspectorPanel() {
inspectedAlbedo, inspectedAlbedo,
inspectedOverlay, inspectedOverlay,
inspectedNormal, inspectedNormal,
inspectedUseOverlay inspectedUseOverlay,
&inspectedVertShader,
&inspectedFragShader
); );
inspectedMaterialPath = selectedMaterialPath.string(); inspectedMaterialPath = selectedMaterialPath.string();
} }
@@ -1387,6 +1375,48 @@ void Engine::renderInspectorPanel() {
matChanged |= textureField("Detail Map", "PreviewOverlay", inspectedOverlay); matChanged |= textureField("Detail Map", "PreviewOverlay", inspectedOverlay);
matChanged |= textureField("Normal Map", "PreviewNormal", inspectedNormal); matChanged |= textureField("Normal Map", "PreviewNormal", inspectedNormal);
ImGui::Spacing();
ImGui::TextColored(ImVec4(0.9f, 0.8f, 0.5f, 1.0f), "Shader");
auto shaderField = [&](const char* label, const char* idSuffix, std::string& path) {
bool changed = false;
ImGui::PushID(idSuffix);
ImGui::TextUnformatted(label);
ImGui::SetNextItemWidth(-140);
char buf[512] = {};
std::snprintf(buf, sizeof(buf), "%s", path.c_str());
if (ImGui::InputText("##Path", buf, sizeof(buf))) {
path = buf;
changed = true;
}
ImGui::SameLine();
if (ImGui::SmallButton("Clear")) {
path.clear();
changed = true;
}
bool selectionIsShader = false;
if (!fileBrowser.selectedFile.empty() && fs::exists(fileBrowser.selectedFile)) {
selectionIsShader = fileBrowser.getFileCategory(fs::directory_entry(fileBrowser.selectedFile)) == FileCategory::Shader;
}
ImGui::SameLine();
ImGui::BeginDisabled(!selectionIsShader);
std::string btn = std::string("Use Selection##") + idSuffix;
if (ImGui::SmallButton(btn.c_str())) {
path = fileBrowser.selectedFile.string();
changed = true;
}
ImGui::EndDisabled();
ImGui::PopID();
return changed;
};
matChanged |= shaderField("Vertex Shader", "PreviewVert", inspectedVertShader);
matChanged |= shaderField("Fragment Shader", "PreviewFrag", inspectedFragShader);
ImGui::BeginDisabled(inspectedVertShader.empty() && inspectedFragShader.empty());
if (ImGui::Button("Reload Shader")) {
renderer.forceReloadShader(inspectedVertShader, inspectedFragShader);
}
ImGui::EndDisabled();
ImGui::Spacing(); ImGui::Spacing();
if (ImGui::Button("Reload")) { if (ImGui::Button("Reload")) {
inspectedMaterialValid = loadMaterialData( inspectedMaterialValid = loadMaterialData(
@@ -1395,7 +1425,9 @@ void Engine::renderInspectorPanel() {
inspectedAlbedo, inspectedAlbedo,
inspectedOverlay, inspectedOverlay,
inspectedNormal, inspectedNormal,
inspectedUseOverlay inspectedUseOverlay,
&inspectedVertShader,
&inspectedFragShader
); );
} }
ImGui::SameLine(); ImGui::SameLine();
@@ -1406,7 +1438,9 @@ void Engine::renderInspectorPanel() {
inspectedAlbedo, inspectedAlbedo,
inspectedOverlay, inspectedOverlay,
inspectedNormal, inspectedNormal,
inspectedUseOverlay)) inspectedUseOverlay,
inspectedVertShader,
inspectedFragShader))
{ {
addConsoleMessage("Saved material: " + selectedMaterialPath.string(), ConsoleMessageType::Success); addConsoleMessage("Saved material: " + selectedMaterialPath.string(), ConsoleMessageType::Success);
} else { } else {
@@ -1427,6 +1461,8 @@ void Engine::renderInspectorPanel() {
target->normalMapPath = inspectedNormal; target->normalMapPath = inspectedNormal;
target->useOverlay = inspectedUseOverlay; target->useOverlay = inspectedUseOverlay;
target->materialPath = selectedMaterialPath.string(); target->materialPath = selectedMaterialPath.string();
target->vertexShaderPath = inspectedVertShader;
target->fragmentShaderPath = inspectedFragShader;
projectManager.currentProject.hasUnsavedChanges = true; projectManager.currentProject.hasUnsavedChanges = true;
addConsoleMessage("Applied material to " + target->name, ConsoleMessageType::Success); addConsoleMessage("Applied material to " + target->name, ConsoleMessageType::Success);
} }
@@ -1622,6 +1658,48 @@ void Engine::renderInspectorPanel() {
materialChanged |= textureField("Detail Map", "ObjOverlay", obj.overlayTexturePath); materialChanged |= textureField("Detail Map", "ObjOverlay", obj.overlayTexturePath);
materialChanged |= textureField("Normal Map", "ObjNormal", obj.normalMapPath); materialChanged |= textureField("Normal Map", "ObjNormal", obj.normalMapPath);
ImGui::Spacing();
ImGui::TextColored(ImVec4(0.9f, 0.8f, 0.5f, 1.0f), "Shader");
auto shaderField = [&](const char* label, const char* idSuffix, std::string& path) {
bool changed = false;
ImGui::PushID(idSuffix);
ImGui::TextUnformatted(label);
ImGui::SetNextItemWidth(-160);
char buf[512] = {};
std::snprintf(buf, sizeof(buf), "%s", path.c_str());
if (ImGui::InputText("##Path", buf, sizeof(buf))) {
path = buf;
changed = true;
}
ImGui::SameLine();
if (ImGui::SmallButton("Clear")) {
path.clear();
changed = true;
}
bool selectionIsShader = false;
if (!fileBrowser.selectedFile.empty() && fs::exists(fileBrowser.selectedFile)) {
selectionIsShader = fileBrowser.getFileCategory(fs::directory_entry(fileBrowser.selectedFile)) == FileCategory::Shader;
}
ImGui::SameLine();
ImGui::BeginDisabled(!selectionIsShader);
std::string btn = std::string("Use Selection##") + idSuffix;
if (ImGui::SmallButton(btn.c_str())) {
path = fileBrowser.selectedFile.string();
changed = true;
}
ImGui::EndDisabled();
ImGui::PopID();
return changed;
};
materialChanged |= shaderField("Vertex Shader", "ObjVert", obj.vertexShaderPath);
materialChanged |= shaderField("Fragment Shader", "ObjFrag", obj.fragmentShaderPath);
ImGui::BeginDisabled(obj.vertexShaderPath.empty() && obj.fragmentShaderPath.empty());
if (ImGui::Button("Reload Shader")) {
renderer.forceReloadShader(obj.vertexShaderPath, obj.fragmentShaderPath);
}
ImGui::EndDisabled();
ImGui::Spacing(); ImGui::Spacing();
ImGui::Separator(); ImGui::Separator();
ImGui::Text("Material Asset"); ImGui::Text("Material Asset");
@@ -1977,6 +2055,10 @@ void Engine::renderViewport() {
); );
if (ImGuizmo::IsUsing()) { if (ImGuizmo::IsUsing()) {
if (!gizmoHistoryCaptured) {
recordState("gizmo");
gizmoHistoryCaptured = true;
}
float t[3], r[3], s[3]; float t[3], r[3], s[3];
ImGuizmo::DecomposeMatrixToComponents(glm::value_ptr(modelMatrix), t, r, s); ImGuizmo::DecomposeMatrixToComponents(glm::value_ptr(modelMatrix), t, r, s);
@@ -1985,6 +2067,8 @@ void Engine::renderViewport() {
selectedObj->scale = glm::vec3(s[0], s[1], s[2]); selectedObj->scale = glm::vec3(s[0], s[1], s[2]);
projectManager.currentProject.hasUnsavedChanges = true; projectManager.currentProject.hasUnsavedChanges = true;
} else {
gizmoHistoryCaptured = false;
} }
} }
@@ -2464,3 +2548,82 @@ void Engine::renderProjectBrowserPanel() {
ImGui::End(); ImGui::End();
} }
void Engine::renderEnvironmentWindow() {
if (!showEnvironmentWindow) return;
ImGui::Begin("Environment", &showEnvironmentWindow);
Skybox* skybox = renderer.getSkybox();
if (skybox) {
float tod = skybox->getTimeOfDay();
ImGui::TextDisabled("Day / Night Cycle");
ImGui::SetNextItemWidth(-1);
if (ImGui::SliderFloat("##EnvDayNight", &tod, 0.0f, 1.0f, "%.2f")) {
skybox->setTimeOfDay(tod);
projectManager.currentProject.hasUnsavedChanges = true;
}
static char skyVertBuf[256] = {};
static char skyFragBuf[256] = {};
if (skyVertBuf[0] == '\0') std::snprintf(skyVertBuf, sizeof(skyVertBuf), "%s", skybox->getVertPath().c_str());
if (skyFragBuf[0] == '\0') std::snprintf(skyFragBuf, sizeof(skyFragBuf), "%s", skybox->getFragPath().c_str());
ImGui::Separator();
ImGui::Text("Skybox Shader");
ImGui::SetNextItemWidth(-1);
if (ImGui::InputText("##SkyVert", skyVertBuf, sizeof(skyVertBuf))) {}
ImGui::SetNextItemWidth(-1);
if (ImGui::InputText("##SkyFrag", skyFragBuf, sizeof(skyFragBuf))) {}
bool selectionIsShader = false;
if (!fileBrowser.selectedFile.empty() && fs::exists(fileBrowser.selectedFile)) {
selectionIsShader = fileBrowser.getFileCategory(fs::directory_entry(fileBrowser.selectedFile)) == FileCategory::Shader;
}
ImGui::BeginDisabled(!selectionIsShader);
if (ImGui::Button("Use Selection as Vert")) {
std::snprintf(skyVertBuf, sizeof(skyVertBuf), "%s", fileBrowser.selectedFile.string().c_str());
}
ImGui::SameLine();
if (ImGui::Button("Use Selection as Frag")) {
std::snprintf(skyFragBuf, sizeof(skyFragBuf), "%s", fileBrowser.selectedFile.string().c_str());
}
ImGui::EndDisabled();
if (ImGui::Button("Reload Skybox Shader")) {
skybox->setShaderPaths(skyVertBuf, skyFragBuf);
}
} else {
ImGui::TextDisabled("Skybox not available");
}
ImGui::Separator();
ImGui::Text("Global Ambient");
glm::vec3 ambient = renderer.getAmbientColor();
if (ImGui::ColorEdit3("##AmbientColor", &ambient.x, ImGuiColorEditFlags_DisplayRGB)) {
renderer.setAmbientColor(ambient);
projectManager.currentProject.hasUnsavedChanges = true;
}
ImGui::End();
}
void Engine::renderCameraWindow() {
if (!showCameraWindow) return;
ImGui::Begin("Camera", &showCameraWindow);
ImGui::TextDisabled("Movement");
ImGui::SetNextItemWidth(-1);
if (ImGui::DragFloat("Base Speed", &camera.moveSpeed, 0.1f, 0.1f, 100.0f, "%.2f")) {
camera.moveSpeed = std::max(0.01f, camera.moveSpeed);
}
ImGui::SetNextItemWidth(-1);
if (ImGui::DragFloat("Sprint Speed", &camera.sprintSpeed, 0.1f, 0.1f, 200.0f, "%.2f")) {
camera.sprintSpeed = std::max(camera.moveSpeed, camera.sprintSpeed);
}
ImGui::Checkbox("Smooth Movement", &camera.smoothMovement);
ImGui::BeginDisabled(!camera.smoothMovement);
ImGui::SetNextItemWidth(-1);
ImGui::DragFloat("Acceleration", &camera.acceleration, 0.1f, 0.1f, 100.0f, "%.2f");
ImGui::EndDisabled();
ImGui::End();
}

View File

@@ -259,6 +259,8 @@ bool SceneSerializer::saveScene(const fs::path& filePath,
file << "albedoTex=" << obj.albedoTexturePath << "\n"; file << "albedoTex=" << obj.albedoTexturePath << "\n";
file << "overlayTex=" << obj.overlayTexturePath << "\n"; file << "overlayTex=" << obj.overlayTexturePath << "\n";
file << "normalMap=" << obj.normalMapPath << "\n"; file << "normalMap=" << obj.normalMapPath << "\n";
file << "vertexShader=" << obj.vertexShaderPath << "\n";
file << "fragmentShader=" << obj.fragmentShaderPath << "\n";
file << "useOverlay=" << (obj.useOverlay ? 1 : 0) << "\n"; file << "useOverlay=" << (obj.useOverlay ? 1 : 0) << "\n";
file << "lightColor=" << obj.light.color.r << "," << obj.light.color.g << "," << obj.light.color.b << "\n"; file << "lightColor=" << obj.light.color.r << "," << obj.light.color.g << "," << obj.light.color.b << "\n";
file << "lightIntensity=" << obj.light.intensity << "\n"; file << "lightIntensity=" << obj.light.intensity << "\n";
@@ -368,6 +370,10 @@ bool SceneSerializer::loadScene(const fs::path& filePath,
currentObj->overlayTexturePath = value; currentObj->overlayTexturePath = value;
} else if (key == "normalMap") { } else if (key == "normalMap") {
currentObj->normalMapPath = value; currentObj->normalMapPath = value;
} else if (key == "vertexShader") {
currentObj->vertexShaderPath = value;
} else if (key == "fragmentShader") {
currentObj->fragmentShaderPath = value;
} else if (key == "useOverlay") { } else if (key == "useOverlay") {
currentObj->useOverlay = (std::stoi(value) != 0); currentObj->useOverlay = (std::stoi(value) != 0);
} else if (key == "lightColor") { } else if (key == "lightColor") {

View File

@@ -411,7 +411,9 @@ const OBJLoader::LoadedMesh* OBJLoader::getMeshInfo(int index) const {
// Renderer implementation // Renderer implementation
Renderer::~Renderer() { Renderer::~Renderer() {
delete shader; shaderCache.clear();
shader = nullptr;
defaultShader = nullptr;
delete texture1; delete texture1;
delete texture2; delete texture2;
delete cubeMesh; delete cubeMesh;
@@ -438,13 +440,21 @@ Texture* Renderer::getTexture(const std::string& path) {
} }
void Renderer::initialize() { void Renderer::initialize() {
shader = new Shader("Resources/Shaders/vert.glsl", "Resources/Shaders/frag.glsl"); shader = new Shader(defaultVertPath.c_str(), defaultFragPath.c_str());
defaultShader = shader;
if (shader->ID == 0) { if (shader->ID == 0) {
std::cerr << "Shader compilation failed!\n"; std::cerr << "Shader compilation failed!\n";
delete shader; delete shader;
shader = nullptr; shader = nullptr;
throw std::runtime_error("Shader error"); throw std::runtime_error("Shader error");
} }
ShaderEntry entry;
entry.shader.reset(defaultShader);
entry.vertPath = defaultVertPath;
entry.fragPath = defaultFragPath;
if (fs::exists(defaultVertPath)) entry.vertTime = fs::last_write_time(defaultVertPath);
if (fs::exists(defaultFragPath)) entry.fragTime = fs::last_write_time(defaultFragPath);
shaderCache[defaultVertPath + "|" + defaultFragPath] = std::move(entry);
texture1 = new Texture("Resources/Textures/container.jpg"); texture1 = new Texture("Resources/Textures/container.jpg");
texture2 = new Texture("Resources/Textures/awesomeface.png"); texture2 = new Texture("Resources/Textures/awesomeface.png");
@@ -463,6 +473,86 @@ void Renderer::initialize() {
glEnable(GL_DEPTH_TEST); glEnable(GL_DEPTH_TEST);
} }
Shader* Renderer::getShader(const std::string& vert, const std::string& frag) {
std::string vPath = vert.empty() ? defaultVertPath : vert;
std::string fPath = frag.empty() ? defaultFragPath : frag;
std::string key = vPath + "|" + fPath;
auto reloadEntry = [&](ShaderEntry& e) -> Shader* {
std::unique_ptr<Shader> newShader = std::make_unique<Shader>(vPath.c_str(), fPath.c_str());
if (!newShader || newShader->ID == 0) {
std::cerr << "Shader reload failed for " << key << ", falling back to default\n";
return defaultShader;
}
e.shader = std::move(newShader);
e.vertPath = vPath;
e.fragPath = fPath;
if (fs::exists(vPath)) e.vertTime = fs::last_write_time(vPath);
if (fs::exists(fPath)) e.fragTime = fs::last_write_time(fPath);
return e.shader.get();
};
auto it = shaderCache.find(key);
if (it != shaderCache.end()) {
ShaderEntry& entry = it->second;
if (autoReloadShaders) {
bool changed = false;
if (fs::exists(vPath)) {
auto t = fs::last_write_time(vPath);
if (t != entry.vertTime) { changed = true; entry.vertTime = t; }
}
if (fs::exists(fPath)) {
auto t = fs::last_write_time(fPath);
if (t != entry.fragTime) { changed = true; entry.fragTime = t; }
}
if (changed) {
return reloadEntry(entry);
}
}
return entry.shader ? entry.shader.get() : defaultShader;
}
ShaderEntry entry;
entry.vertPath = vPath;
entry.fragPath = fPath;
if (fs::exists(vPath)) entry.vertTime = fs::last_write_time(vPath);
if (fs::exists(fPath)) entry.fragTime = fs::last_write_time(fPath);
entry.shader = std::make_unique<Shader>(vPath.c_str(), fPath.c_str());
if (!entry.shader || entry.shader->ID == 0) {
std::cerr << "Shader compile failed for " << key << ", using default\n";
shaderCache[key] = std::move(entry);
return defaultShader;
}
Shader* ptr = entry.shader.get();
shaderCache[key] = std::move(entry);
return ptr;
}
bool Renderer::forceReloadShader(const std::string& vert, const std::string& frag) {
std::string vPath = vert.empty() ? defaultVertPath : vert;
std::string fPath = frag.empty() ? defaultFragPath : frag;
std::string key = vPath + "|" + fPath;
auto it = shaderCache.find(key);
if (it != shaderCache.end()) {
shaderCache.erase(it);
}
ShaderEntry entry;
entry.vertPath = vPath;
entry.fragPath = fPath;
if (fs::exists(vPath)) entry.vertTime = fs::last_write_time(vPath);
if (fs::exists(fPath)) entry.fragTime = fs::last_write_time(fPath);
entry.shader = std::make_unique<Shader>(vPath.c_str(), fPath.c_str());
if (!entry.shader || entry.shader->ID == 0) {
std::cerr << "Shader force reload failed for " << key << "\n";
return false;
}
if (vPath == defaultVertPath && fPath == defaultFragPath) {
defaultShader = entry.shader.get();
}
shaderCache[key] = std::move(entry);
return true;
}
void Renderer::setupFBO() { void Renderer::setupFBO() {
glGenFramebuffers(1, &framebuffer); glGenFramebuffers(1, &framebuffer);
glBindFramebuffer(GL_FRAMEBUFFER, framebuffer); glBindFramebuffer(GL_FRAMEBUFFER, framebuffer);
@@ -612,17 +702,8 @@ void Renderer::renderObject(const SceneObject& obj) {
} }
void Renderer::renderScene(const Camera& camera, const std::vector<SceneObject>& sceneObjects) { void Renderer::renderScene(const Camera& camera, const std::vector<SceneObject>& sceneObjects) {
if (!shader) return; if (!defaultShader) return;
shader->use();
shader->setMat4("view", camera.getViewMatrix());
shader->setMat4("projection", glm::perspective(glm::radians(FOV), (float)currentWidth / (float)currentHeight, NEAR_PLANE, FAR_PLANE));
shader->setVec3("viewPos", camera.position);
shader->setFloat("ambientStrength", 0.25f);
shader->setFloat("specularStrength", 0.8f);
shader->setFloat("shininess", 64.0f);
shader->setFloat("mixAmount", 0.3f);
// Collect up to 10 lights
struct LightUniform { struct LightUniform {
int type = 0; // 0 dir,1 point,2 spot int type = 0; // 0 dir,1 point,2 spot
glm::vec3 dir = glm::vec3(0.0f, -1.0f, 0.0f); glm::vec3 dir = glm::vec3(0.0f, -1.0f, 0.0f);
@@ -649,7 +730,6 @@ void Renderer::renderScene(const Camera& camera, const std::vector<SceneObject>&
std::vector<LightUniform> lights; std::vector<LightUniform> lights;
lights.reserve(10); lights.reserve(10);
// Add directionals first, then spots, then points
for (const auto& obj : sceneObjects) { for (const auto& obj : sceneObjects) {
if (obj.light.enabled && obj.type == ObjectType::DirectionalLight) { if (obj.light.enabled && obj.type == ObjectType::DirectionalLight) {
LightUniform l; LightUniform l;
@@ -692,9 +772,26 @@ void Renderer::renderScene(const Camera& camera, const std::vector<SceneObject>&
} }
} }
} }
int count = static_cast<int>(lights.size());
shader->setInt("lightCount", count); for (const auto& obj : sceneObjects) {
for (int i = 0; i < count; ++i) { // Skip light gizmo-only types
if (obj.type == ObjectType::PointLight || obj.type == ObjectType::SpotLight || obj.type == ObjectType::AreaLight) {
continue;
}
Shader* active = getShader(obj.vertexShaderPath, obj.fragmentShaderPath);
if (!active) continue;
shader = active;
shader->use();
shader->setMat4("view", camera.getViewMatrix());
shader->setMat4("projection", glm::perspective(glm::radians(FOV), (float)currentWidth / (float)currentHeight, NEAR_PLANE, FAR_PLANE));
shader->setVec3("viewPos", camera.position);
shader->setVec3("ambientColor", ambientColor);
shader->setVec3("ambientColor", ambientColor);
shader->setInt("lightCount", static_cast<int>(lights.size()));
for (size_t i = 0; i < lights.size() && i < 10; ++i) {
const auto& l = lights[i]; const auto& l = lights[i];
std::string idx = "[" + std::to_string(i) + "]"; std::string idx = "[" + std::to_string(i) + "]";
shader->setInt("lightTypeArr" + idx, l.type); shader->setInt("lightTypeArr" + idx, l.type);
@@ -707,21 +804,6 @@ void Renderer::renderScene(const Camera& camera, const std::vector<SceneObject>&
shader->setFloat("lightOuterCosArr" + idx, l.outer); shader->setFloat("lightOuterCosArr" + idx, l.outer);
} }
// Bind base textures once per frame (used by objects)
if (texture1) texture1->Bind(0);
else glBindTexture(GL_TEXTURE_2D, 0);
if (texture2) texture2->Bind(1);
else glBindTexture(GL_TEXTURE_2D, 0);
if (texture1) texture1->Bind(0);
if (texture2) texture2->Bind(1);
for (const auto& obj : sceneObjects) {
// Skip light types in the main render pass (they are handled as gizmos)
if (obj.type == ObjectType::PointLight || obj.type == ObjectType::SpotLight || obj.type == ObjectType::AreaLight) {
continue;
}
glm::mat4 model = glm::mat4(1.0f); glm::mat4 model = glm::mat4(1.0f);
model = glm::translate(model, obj.position); model = glm::translate(model, obj.position);
model = glm::rotate(model, glm::radians(obj.rotation.x), glm::vec3(1.0f, 0.0f, 0.0f)); model = glm::rotate(model, glm::radians(obj.rotation.x), glm::vec3(1.0f, 0.0f, 0.0f));

View File

@@ -60,9 +60,22 @@ private:
unsigned int framebuffer = 0, viewportTexture = 0, rbo = 0; unsigned int framebuffer = 0, viewportTexture = 0, rbo = 0;
int currentWidth = 800, currentHeight = 600; int currentWidth = 800, currentHeight = 600;
Shader* shader = nullptr; Shader* shader = nullptr;
Shader* defaultShader = nullptr;
Texture* texture1 = nullptr; Texture* texture1 = nullptr;
Texture* texture2 = nullptr; Texture* texture2 = nullptr;
std::unordered_map<std::string, std::unique_ptr<Texture>> textureCache; std::unordered_map<std::string, std::unique_ptr<Texture>> textureCache;
struct ShaderEntry {
std::unique_ptr<Shader> shader;
fs::file_time_type vertTime;
fs::file_time_type fragTime;
std::string vertPath;
std::string fragPath;
};
std::unordered_map<std::string, ShaderEntry> shaderCache;
std::string defaultVertPath = "Resources/Shaders/vert.glsl";
std::string defaultFragPath = "Resources/Shaders/frag.glsl";
bool autoReloadShaders = true;
glm::vec3 ambientColor = glm::vec3(0.2f, 0.2f, 0.2f);
Mesh* cubeMesh = nullptr; Mesh* cubeMesh = nullptr;
Mesh* sphereMesh = nullptr; Mesh* sphereMesh = nullptr;
Mesh* capsuleMesh = nullptr; Mesh* capsuleMesh = nullptr;
@@ -76,6 +89,10 @@ public:
void initialize(); void initialize();
Texture* getTexture(const std::string& path); Texture* getTexture(const std::string& path);
Shader* getShader(const std::string& vert, const std::string& frag);
bool forceReloadShader(const std::string& vert, const std::string& frag);
void setAmbientColor(const glm::vec3& color) { ambientColor = color; }
glm::vec3 getAmbientColor() const { return ambientColor; }
void resize(int w, int h); void resize(int w, int h);
int getWidth() const { return currentWidth; } int getWidth() const { return currentWidth; }
int getHeight() const { return currentHeight; } int getHeight() const { return currentHeight; }

View File

@@ -67,6 +67,8 @@ public:
std::string albedoTexturePath; std::string albedoTexturePath;
std::string overlayTexturePath; std::string overlayTexturePath;
std::string normalMapPath; std::string normalMapPath;
std::string vertexShaderPath;
std::string fragmentShaderPath;
bool useOverlay = false; bool useOverlay = false;
LightComponent light; // Only used when type is a light LightComponent light; // Only used when type is a light

View File

@@ -2,6 +2,7 @@
#include "../../include/Shaders/Shader.h" #include "../../include/Shaders/Shader.h"
#include <glad/glad.h> #include <glad/glad.h>
#include <iostream> #include <iostream>
#include <string>
#include "../../src/ThirdParty/glm/glm.hpp" #include "../../src/ThirdParty/glm/glm.hpp"
#include "../../src/ThirdParty/glm/gtc/type_ptr.hpp" #include "../../src/ThirdParty/glm/gtc/type_ptr.hpp"
@@ -52,12 +53,12 @@ float skyboxVertices[] = {
}; };
Skybox::Skybox() { Skybox::Skybox() {
skyboxShader = new Shader("Resources/Shaders/skybox_vert.glsl", "Resources/Shaders/skybox_frag.glsl"); skyboxShader = new Shader(vertPath.c_str(), fragPath.c_str());
setupMesh(); setupMesh();
} }
Skybox::~Skybox() { Skybox::~Skybox() {
delete skyboxShader; if (skyboxShader) delete skyboxShader;
glDeleteVertexArrays(1, &VAO); glDeleteVertexArrays(1, &VAO);
glDeleteBuffers(1, &VBO); glDeleteBuffers(1, &VBO);
} }
@@ -80,6 +81,26 @@ void Skybox::setTimeOfDay(float time) {
timeOfDay = time; timeOfDay = time;
} }
bool Skybox::reloadShader() {
if (skyboxShader) {
delete skyboxShader;
skyboxShader = nullptr;
}
skyboxShader = new Shader(vertPath.c_str(), fragPath.c_str());
return skyboxShader && skyboxShader->ID != 0;
}
void Skybox::setShaderPaths(const std::string& vert, const std::string& frag) {
if (!vert.empty()) vertPath = vert;
if (!frag.empty()) fragPath = frag;
if (!reloadShader()) {
std::cerr << "Failed to reload skybox shader, reverting to defaults\n";
vertPath = "Resources/Shaders/skybox_vert.glsl";
fragPath = "Resources/Shaders/skybox_frag.glsl";
reloadShader();
}
}
void Skybox::draw(const float* view, const float* projection) { void Skybox::draw(const float* view, const float* projection) {
// Properly reconstruct the view matrix from the float array // Properly reconstruct the view matrix from the float array
glm::mat4 viewMat = glm::make_mat4(view); glm::mat4 viewMat = glm::make_mat4(view);