Yey! Shader Compilation!
This commit is contained in:
@@ -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);
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
121
src/Engine.cpp
121
src/Engine.cpp
@@ -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; });
|
||||||
|
|
||||||
|
|||||||
25
src/Engine.h
25
src/Engine.h
@@ -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();
|
||||||
|
|||||||
@@ -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();
|
||||||
|
}
|
||||||
|
|||||||
@@ -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") {
|
||||||
|
|||||||
@@ -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));
|
||||||
|
|||||||
@@ -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; }
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
Reference in New Issue
Block a user