Yey! Shader Compilation!
This commit is contained in:
@@ -29,13 +29,9 @@ void Camera::processMouse(double xpos, double ypos) {
|
||||
}
|
||||
|
||||
void Camera::processKeyboard(float deltaTime, GLFWwindow* window) {
|
||||
const float CAMERA_SPEED = 5.0f;
|
||||
const float SPRINT_SPEED = 10.0f;
|
||||
const float ACCELERATION = 15.0f;
|
||||
|
||||
float currentSpeed = CAMERA_SPEED;
|
||||
float currentSpeed = moveSpeed;
|
||||
if (glfwGetKey(window, GLFW_KEY_LEFT_SHIFT) == GLFW_PRESS) {
|
||||
currentSpeed = SPRINT_SPEED;
|
||||
currentSpeed = sprintSpeed;
|
||||
}
|
||||
|
||||
glm::vec3 desiredDir(0.0f);
|
||||
@@ -72,21 +68,29 @@ void Camera::processKeyboard(float deltaTime, GLFWwindow* window) {
|
||||
glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
|
||||
}
|
||||
|
||||
glm::vec3 targetVelocity(0.0f);
|
||||
if (isMoving) {
|
||||
float length = glm::length(desiredDir);
|
||||
if (length > 0.0001f) {
|
||||
desiredDir = desiredDir / length;
|
||||
targetVelocity = desiredDir * currentSpeed;
|
||||
} else {
|
||||
targetVelocity = glm::vec3(0.0f);
|
||||
if (smoothMovement) {
|
||||
glm::vec3 targetVelocity(0.0f);
|
||||
if (isMoving) {
|
||||
float length = glm::length(desiredDir);
|
||||
if (length > 0.0001f) {
|
||||
desiredDir = desiredDir / length;
|
||||
targetVelocity = desiredDir * currentSpeed;
|
||||
}
|
||||
}
|
||||
|
||||
float smoothFactor = 1.0f - std::exp(-acceleration * deltaTime);
|
||||
velocity = glm::mix(velocity, targetVelocity, smoothFactor);
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
float smoothFactor = 1.0f - std::exp(-ACCELERATION * deltaTime);
|
||||
velocity = glm::mix(velocity, targetVelocity, smoothFactor);
|
||||
|
||||
position += velocity * deltaTime;
|
||||
}
|
||||
|
||||
glm::mat4 Camera::getViewMatrix() const {
|
||||
|
||||
@@ -9,6 +9,10 @@ public:
|
||||
glm::vec3 front = glm::vec3(0.0f, 0.0f, -1.0f);
|
||||
glm::vec3 up = glm::vec3(0.0f, 1.0f, 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 pitch = 0.0f;
|
||||
float speed = CAMERA_SPEED;
|
||||
|
||||
133
src/Engine.cpp
133
src/Engine.cpp
@@ -10,6 +10,8 @@ struct MaterialFileData {
|
||||
std::string overlay;
|
||||
std::string normal;
|
||||
bool useOverlay = false;
|
||||
std::string vertexShader;
|
||||
std::string fragmentShader;
|
||||
};
|
||||
|
||||
bool readMaterialFile(const std::string& path, MaterialFileData& outData) {
|
||||
@@ -44,6 +46,10 @@ bool readMaterialFile(const std::string& path, MaterialFileData& outData) {
|
||||
outData.normal = val;
|
||||
} else if (key == "useOverlay") {
|
||||
outData.useOverlay = std::stoi(val) != 0;
|
||||
} else if (key == "vertexShader") {
|
||||
outData.vertexShader = val;
|
||||
} else if (key == "fragmentShader") {
|
||||
outData.fragmentShader = val;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
@@ -64,6 +70,8 @@ bool writeMaterialFile(const MaterialFileData& data, const std::string& path) {
|
||||
f << "albedo=" << data.albedo << "\n";
|
||||
f << "overlay=" << data.overlay << "\n";
|
||||
f << "normal=" << data.normal << "\n";
|
||||
f << "vertexShader=" << data.vertexShader << "\n";
|
||||
f << "fragmentShader=" << data.fragmentShader << "\n";
|
||||
return true;
|
||||
}
|
||||
} // namespace
|
||||
@@ -93,6 +101,55 @@ void Engine::DecomposeMatrix(const glm::mat4& matrix, glm::vec3& pos, glm::vec3&
|
||||
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() {
|
||||
std::cerr << "[DEBUG] Creating window..." << std::endl;
|
||||
editorWindow = window.makeWindow();
|
||||
@@ -207,6 +264,8 @@ void Engine::run() {
|
||||
if (showInspector) renderInspectorPanel();
|
||||
if (showFileBrowser) renderFileBrowserPanel();
|
||||
if (showConsole) renderConsolePanel();
|
||||
if (showEnvironmentWindow) renderEnvironmentWindow();
|
||||
if (showCameraWindow) renderCameraWindow();
|
||||
if (showProjectBrowser) renderProjectBrowserPanel();
|
||||
}
|
||||
|
||||
@@ -258,6 +317,7 @@ void Engine::shutdown() {
|
||||
}
|
||||
|
||||
void Engine::importOBJToScene(const std::string& filepath, const std::string& objectName) {
|
||||
recordState("importOBJ");
|
||||
std::string 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) {
|
||||
recordState("importModel");
|
||||
auto& modelLoader = getModelLoader();
|
||||
ModelLoadResult result = modelLoader.loadModel(filepath);
|
||||
|
||||
@@ -336,6 +397,8 @@ void Engine::loadMaterialFromFile(SceneObject& obj) {
|
||||
obj.overlayTexturePath = data.overlay;
|
||||
obj.normalMapPath = data.normal;
|
||||
obj.useOverlay = data.useOverlay;
|
||||
obj.vertexShaderPath = data.vertexShader;
|
||||
obj.fragmentShaderPath = data.fragmentShader;
|
||||
addConsoleMessage("Applied material: " + obj.materialPath, ConsoleMessageType::Success);
|
||||
projectManager.currentProject.hasUnsavedChanges = true;
|
||||
} catch (...) {
|
||||
@@ -345,7 +408,9 @@ void Engine::loadMaterialFromFile(SceneObject& obj) {
|
||||
|
||||
bool Engine::loadMaterialData(const std::string& path, MaterialProperties& props,
|
||||
std::string& albedo, std::string& overlay,
|
||||
std::string& normal, bool& useOverlay)
|
||||
std::string& normal, bool& useOverlay,
|
||||
std::string* vertexShaderOut,
|
||||
std::string* fragmentShaderOut)
|
||||
{
|
||||
MaterialFileData data;
|
||||
if (!readMaterialFile(path, data)) {
|
||||
@@ -356,12 +421,16 @@ bool Engine::loadMaterialData(const std::string& path, MaterialProperties& props
|
||||
overlay = data.overlay;
|
||||
normal = data.normal;
|
||||
useOverlay = data.useOverlay;
|
||||
if (vertexShaderOut) *vertexShaderOut = data.vertexShader;
|
||||
if (fragmentShaderOut) *fragmentShaderOut = data.fragmentShader;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Engine::saveMaterialData(const std::string& path, const MaterialProperties& props,
|
||||
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;
|
||||
data.props = props;
|
||||
@@ -369,6 +438,8 @@ bool Engine::saveMaterialData(const std::string& path, const MaterialProperties&
|
||||
data.overlay = overlay;
|
||||
data.normal = normal;
|
||||
data.useOverlay = useOverlay;
|
||||
data.vertexShader = vertexShader;
|
||||
data.fragmentShader = fragmentShader;
|
||||
return writeMaterialFile(data, path);
|
||||
}
|
||||
|
||||
@@ -384,6 +455,8 @@ void Engine::saveMaterialToFile(const SceneObject& obj) {
|
||||
data.overlay = obj.overlayTexturePath;
|
||||
data.normal = obj.normalMapPath;
|
||||
data.useOverlay = obj.useOverlay;
|
||||
data.vertexShader = obj.vertexShaderPath;
|
||||
data.fragmentShader = obj.fragmentShaderPath;
|
||||
|
||||
if (!writeMaterialFile(data, obj.materialPath)) {
|
||||
addConsoleMessage("Failed to open material for writing: " + obj.materialPath, ConsoleMessageType::Error);
|
||||
@@ -431,16 +504,43 @@ void Engine::handleKeyboardShortcuts() {
|
||||
ctrlNPressed = false;
|
||||
}
|
||||
|
||||
if (ImGui::IsKeyPressed(ImGuiKey_Q)) mCurrentGizmoOperation = ImGuizmo::TRANSLATE;
|
||||
if (ImGui::IsKeyPressed(ImGuiKey_W)) mCurrentGizmoOperation = ImGuizmo::ROTATE;
|
||||
if (ImGui::IsKeyPressed(ImGuiKey_E)) mCurrentGizmoOperation = ImGuizmo::SCALE;
|
||||
if (ImGui::IsKeyPressed(ImGuiKey_R)) mCurrentGizmoOperation = ImGuizmo::UNIVERSAL;
|
||||
if (!cursorLocked) {
|
||||
if (ImGui::IsKeyPressed(ImGuiKey_Q)) mCurrentGizmoOperation = ImGuizmo::TRANSLATE;
|
||||
if (ImGui::IsKeyPressed(ImGuiKey_W)) mCurrentGizmoOperation = ImGuizmo::ROTATE;
|
||||
if (ImGui::IsKeyPressed(ImGuiKey_E)) mCurrentGizmoOperation = ImGuizmo::SCALE;
|
||||
if (ImGui::IsKeyPressed(ImGuiKey_R)) mCurrentGizmoOperation = ImGuizmo::UNIVERSAL;
|
||||
|
||||
if (ImGui::IsKeyPressed(ImGuiKey_Z)) {
|
||||
mCurrentGizmoMode = (mCurrentGizmoMode == ImGuizmo::LOCAL) ? ImGuizmo::WORLD : ImGuizmo::LOCAL;
|
||||
if (ImGui::IsKeyPressed(ImGuiKey_Z)) {
|
||||
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) {
|
||||
@@ -497,6 +597,8 @@ void Engine::loadRecentScenes() {
|
||||
sceneObjects.clear();
|
||||
selectedObjectId = -1;
|
||||
nextObjectId = 0;
|
||||
undoStack.clear();
|
||||
redoStack.clear();
|
||||
|
||||
fs::path scenePath = projectManager.currentProject.getSceneFilePath(projectManager.currentProject.currentSceneName);
|
||||
if (fs::exists(scenePath)) {
|
||||
@@ -510,6 +612,7 @@ void Engine::loadRecentScenes() {
|
||||
addConsoleMessage("Default scene not found, starting with a new scene.", ConsoleMessageType::Info);
|
||||
addObject(ObjectType::Cube, "Cube");
|
||||
}
|
||||
recordState("sceneLoaded");
|
||||
|
||||
fileBrowser.currentPath = projectManager.currentProject.assetsPath;
|
||||
fileBrowser.needsRefresh = true;
|
||||
@@ -537,6 +640,8 @@ void Engine::loadScene(const std::string& sceneName) {
|
||||
|
||||
fs::path scenePath = projectManager.currentProject.getSceneFilePath(sceneName);
|
||||
if (SceneSerializer::loadScene(scenePath, sceneObjects, nextObjectId)) {
|
||||
undoStack.clear();
|
||||
redoStack.clear();
|
||||
projectManager.currentProject.currentSceneName = sceneName;
|
||||
projectManager.currentProject.hasUnsavedChanges = false;
|
||||
projectManager.currentProject.saveProjectFile();
|
||||
@@ -547,6 +652,7 @@ void Engine::loadScene(const std::string& sceneName) {
|
||||
if (!hasDirLight) {
|
||||
addObject(ObjectType::DirectionalLight, "Directional Light");
|
||||
}
|
||||
recordState("sceneLoaded");
|
||||
addConsoleMessage("Loaded scene: " + sceneName, ConsoleMessageType::Success);
|
||||
} else {
|
||||
addConsoleMessage("Error: Failed to load scene: " + sceneName, ConsoleMessageType::Error);
|
||||
@@ -563,6 +669,8 @@ void Engine::createNewScene(const std::string& sceneName) {
|
||||
sceneObjects.clear();
|
||||
selectedObjectId = -1;
|
||||
nextObjectId = 0;
|
||||
undoStack.clear();
|
||||
redoStack.clear();
|
||||
|
||||
projectManager.currentProject.currentSceneName = sceneName;
|
||||
projectManager.currentProject.hasUnsavedChanges = true;
|
||||
@@ -570,11 +678,13 @@ void Engine::createNewScene(const std::string& sceneName) {
|
||||
addObject(ObjectType::Cube, "Cube");
|
||||
addObject(ObjectType::DirectionalLight, "Directional Light");
|
||||
saveCurrentScene();
|
||||
recordState("newScene");
|
||||
|
||||
addConsoleMessage("Created new scene: " + sceneName, ConsoleMessageType::Success);
|
||||
}
|
||||
|
||||
void Engine::addObject(ObjectType type, const std::string& baseName) {
|
||||
recordState("addObject");
|
||||
int id = nextObjectId++;
|
||||
std::string name = baseName + " " + std::to_string(id);
|
||||
sceneObjects.push_back(SceneObject(name, type, id));
|
||||
@@ -605,6 +715,7 @@ void Engine::duplicateSelected() {
|
||||
[this](const SceneObject& obj) { return obj.id == selectedObjectId; });
|
||||
|
||||
if (it != sceneObjects.end()) {
|
||||
recordState("duplicate");
|
||||
int id = nextObjectId++;
|
||||
SceneObject newObj(it->name + " (Copy)", it->type, id);
|
||||
newObj.position = it->position + glm::vec3(1.0f, 0.0f, 0.0f);
|
||||
@@ -617,6 +728,8 @@ void Engine::duplicateSelected() {
|
||||
newObj.albedoTexturePath = it->albedoTexturePath;
|
||||
newObj.overlayTexturePath = it->overlayTexturePath;
|
||||
newObj.normalMapPath = it->normalMapPath;
|
||||
newObj.vertexShaderPath = it->vertexShaderPath;
|
||||
newObj.fragmentShaderPath = it->fragmentShaderPath;
|
||||
newObj.useOverlay = it->useOverlay;
|
||||
newObj.light = it->light;
|
||||
|
||||
@@ -630,6 +743,7 @@ void Engine::duplicateSelected() {
|
||||
}
|
||||
|
||||
void Engine::deleteSelected() {
|
||||
recordState("delete");
|
||||
auto it = std::remove_if(sceneObjects.begin(), sceneObjects.end(),
|
||||
[this](const SceneObject& obj) { return obj.id == selectedObjectId; });
|
||||
|
||||
@@ -644,6 +758,7 @@ void Engine::deleteSelected() {
|
||||
}
|
||||
|
||||
void Engine::setParent(int childId, int parentId) {
|
||||
recordState("reparent");
|
||||
auto childIt = std::find_if(sceneObjects.begin(), sceneObjects.end(),
|
||||
[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
|
||||
int viewportWidth = 800;
|
||||
int viewportHeight = 600;
|
||||
bool gizmoHistoryCaptured = false;
|
||||
// Standalone material inspection cache
|
||||
std::string inspectedMaterialPath;
|
||||
MaterialProperties inspectedMaterial;
|
||||
std::string inspectedAlbedo;
|
||||
std::string inspectedOverlay;
|
||||
std::string inspectedNormal;
|
||||
std::string inspectedVertShader;
|
||||
std::string inspectedFragShader;
|
||||
bool inspectedUseOverlay = 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;
|
||||
int selectedObjectId = -1;
|
||||
@@ -70,6 +80,8 @@ private:
|
||||
|
||||
char fileBrowserSearch[256] = "";
|
||||
float fileBrowserIconScale = 1.0f; // 0.5 to 2.0 range
|
||||
bool showEnvironmentWindow = true;
|
||||
bool showCameraWindow = true;
|
||||
|
||||
// Private methods
|
||||
SceneObject* getSelectedObject();
|
||||
@@ -85,6 +97,8 @@ private:
|
||||
void renderNewProjectDialog();
|
||||
void renderOpenProjectDialog();
|
||||
void renderMainMenuBar();
|
||||
void renderEnvironmentWindow();
|
||||
void renderCameraWindow();
|
||||
void renderHierarchyPanel();
|
||||
void renderObjectNode(SceneObject& obj, const std::string& filter);
|
||||
void renderFileBrowserPanel();
|
||||
@@ -117,6 +131,9 @@ private:
|
||||
void setParent(int childId, int parentId);
|
||||
void loadMaterialFromFile(SceneObject& obj);
|
||||
void saveMaterialToFile(const SceneObject& obj);
|
||||
void recordState(const char* reason = "");
|
||||
void undo();
|
||||
void redo();
|
||||
|
||||
// Console/logging
|
||||
void addConsoleMessage(const std::string& message, ConsoleMessageType type);
|
||||
@@ -125,10 +142,14 @@ private:
|
||||
// Material helpers
|
||||
bool loadMaterialData(const std::string& path, MaterialProperties& props,
|
||||
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,
|
||||
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
|
||||
void setupImGui();
|
||||
|
||||
@@ -1110,6 +1110,8 @@ void Engine::renderMainMenuBar() {
|
||||
ImGui::MenuItem("File Browser", nullptr, &showFileBrowser);
|
||||
ImGui::MenuItem("Console", nullptr, &showConsole);
|
||||
ImGui::MenuItem("Project", nullptr, &showProjectBrowser);
|
||||
ImGui::MenuItem("Environment", nullptr, &showEnvironmentWindow);
|
||||
ImGui::MenuItem("Camera", nullptr, &showCameraWindow);
|
||||
ImGui::Separator();
|
||||
if (ImGui::MenuItem("Fullscreen Viewport", "F11", viewportFullscreen)) {
|
||||
viewportFullscreen = !viewportFullscreen;
|
||||
@@ -1271,22 +1273,6 @@ void Engine::renderObjectNode(SceneObject& obj, const std::string& filter) {
|
||||
void Engine::renderInspectorPanel() {
|
||||
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;
|
||||
bool browserHasMaterial = false;
|
||||
if (!fileBrowser.selectedFile.empty() && fs::exists(fileBrowser.selectedFile)) {
|
||||
@@ -1301,7 +1287,9 @@ void Engine::renderInspectorPanel() {
|
||||
inspectedAlbedo,
|
||||
inspectedOverlay,
|
||||
inspectedNormal,
|
||||
inspectedUseOverlay
|
||||
inspectedUseOverlay,
|
||||
&inspectedVertShader,
|
||||
&inspectedFragShader
|
||||
);
|
||||
inspectedMaterialPath = selectedMaterialPath.string();
|
||||
}
|
||||
@@ -1387,6 +1375,48 @@ void Engine::renderInspectorPanel() {
|
||||
matChanged |= textureField("Detail Map", "PreviewOverlay", inspectedOverlay);
|
||||
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();
|
||||
if (ImGui::Button("Reload")) {
|
||||
inspectedMaterialValid = loadMaterialData(
|
||||
@@ -1395,7 +1425,9 @@ void Engine::renderInspectorPanel() {
|
||||
inspectedAlbedo,
|
||||
inspectedOverlay,
|
||||
inspectedNormal,
|
||||
inspectedUseOverlay
|
||||
inspectedUseOverlay,
|
||||
&inspectedVertShader,
|
||||
&inspectedFragShader
|
||||
);
|
||||
}
|
||||
ImGui::SameLine();
|
||||
@@ -1406,7 +1438,9 @@ void Engine::renderInspectorPanel() {
|
||||
inspectedAlbedo,
|
||||
inspectedOverlay,
|
||||
inspectedNormal,
|
||||
inspectedUseOverlay))
|
||||
inspectedUseOverlay,
|
||||
inspectedVertShader,
|
||||
inspectedFragShader))
|
||||
{
|
||||
addConsoleMessage("Saved material: " + selectedMaterialPath.string(), ConsoleMessageType::Success);
|
||||
} else {
|
||||
@@ -1427,6 +1461,8 @@ void Engine::renderInspectorPanel() {
|
||||
target->normalMapPath = inspectedNormal;
|
||||
target->useOverlay = inspectedUseOverlay;
|
||||
target->materialPath = selectedMaterialPath.string();
|
||||
target->vertexShaderPath = inspectedVertShader;
|
||||
target->fragmentShaderPath = inspectedFragShader;
|
||||
projectManager.currentProject.hasUnsavedChanges = true;
|
||||
addConsoleMessage("Applied material to " + target->name, ConsoleMessageType::Success);
|
||||
}
|
||||
@@ -1622,6 +1658,48 @@ void Engine::renderInspectorPanel() {
|
||||
materialChanged |= textureField("Detail Map", "ObjOverlay", obj.overlayTexturePath);
|
||||
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::Separator();
|
||||
ImGui::Text("Material Asset");
|
||||
@@ -1977,6 +2055,10 @@ void Engine::renderViewport() {
|
||||
);
|
||||
|
||||
if (ImGuizmo::IsUsing()) {
|
||||
if (!gizmoHistoryCaptured) {
|
||||
recordState("gizmo");
|
||||
gizmoHistoryCaptured = true;
|
||||
}
|
||||
float t[3], r[3], s[3];
|
||||
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]);
|
||||
|
||||
projectManager.currentProject.hasUnsavedChanges = true;
|
||||
} else {
|
||||
gizmoHistoryCaptured = false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2464,3 +2548,82 @@ void Engine::renderProjectBrowserPanel() {
|
||||
|
||||
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 << "overlayTex=" << obj.overlayTexturePath << "\n";
|
||||
file << "normalMap=" << obj.normalMapPath << "\n";
|
||||
file << "vertexShader=" << obj.vertexShaderPath << "\n";
|
||||
file << "fragmentShader=" << obj.fragmentShaderPath << "\n";
|
||||
file << "useOverlay=" << (obj.useOverlay ? 1 : 0) << "\n";
|
||||
file << "lightColor=" << obj.light.color.r << "," << obj.light.color.g << "," << obj.light.color.b << "\n";
|
||||
file << "lightIntensity=" << obj.light.intensity << "\n";
|
||||
@@ -368,6 +370,10 @@ bool SceneSerializer::loadScene(const fs::path& filePath,
|
||||
currentObj->overlayTexturePath = value;
|
||||
} else if (key == "normalMap") {
|
||||
currentObj->normalMapPath = value;
|
||||
} else if (key == "vertexShader") {
|
||||
currentObj->vertexShaderPath = value;
|
||||
} else if (key == "fragmentShader") {
|
||||
currentObj->fragmentShaderPath = value;
|
||||
} else if (key == "useOverlay") {
|
||||
currentObj->useOverlay = (std::stoi(value) != 0);
|
||||
} else if (key == "lightColor") {
|
||||
|
||||
@@ -411,7 +411,9 @@ const OBJLoader::LoadedMesh* OBJLoader::getMeshInfo(int index) const {
|
||||
|
||||
// Renderer implementation
|
||||
Renderer::~Renderer() {
|
||||
delete shader;
|
||||
shaderCache.clear();
|
||||
shader = nullptr;
|
||||
defaultShader = nullptr;
|
||||
delete texture1;
|
||||
delete texture2;
|
||||
delete cubeMesh;
|
||||
@@ -438,13 +440,21 @@ Texture* Renderer::getTexture(const std::string& path) {
|
||||
}
|
||||
|
||||
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) {
|
||||
std::cerr << "Shader compilation failed!\n";
|
||||
delete shader;
|
||||
shader = nullptr;
|
||||
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");
|
||||
texture2 = new Texture("Resources/Textures/awesomeface.png");
|
||||
@@ -463,6 +473,86 @@ void Renderer::initialize() {
|
||||
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() {
|
||||
glGenFramebuffers(1, &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) {
|
||||
if (!shader) 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);
|
||||
if (!defaultShader) return;
|
||||
|
||||
// Collect up to 10 lights
|
||||
struct LightUniform {
|
||||
int type = 0; // 0 dir,1 point,2 spot
|
||||
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;
|
||||
lights.reserve(10);
|
||||
|
||||
// Add directionals first, then spots, then points
|
||||
for (const auto& obj : sceneObjects) {
|
||||
if (obj.light.enabled && obj.type == ObjectType::DirectionalLight) {
|
||||
LightUniform l;
|
||||
@@ -692,36 +772,38 @@ void Renderer::renderScene(const Camera& camera, const std::vector<SceneObject>&
|
||||
}
|
||||
}
|
||||
}
|
||||
int count = static_cast<int>(lights.size());
|
||||
shader->setInt("lightCount", count);
|
||||
for (int i = 0; i < count; ++i) {
|
||||
const auto& l = lights[i];
|
||||
std::string idx = "[" + std::to_string(i) + "]";
|
||||
shader->setInt("lightTypeArr" + idx, l.type);
|
||||
shader->setVec3("lightDirArr" + idx, l.dir);
|
||||
shader->setVec3("lightPosArr" + idx, l.pos);
|
||||
shader->setVec3("lightColorArr" + idx, l.color);
|
||||
shader->setFloat("lightIntensityArr" + idx, l.intensity);
|
||||
shader->setFloat("lightRangeArr" + idx, l.range);
|
||||
shader->setFloat("lightInnerCosArr" + idx, l.inner);
|
||||
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)
|
||||
// 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];
|
||||
std::string idx = "[" + std::to_string(i) + "]";
|
||||
shader->setInt("lightTypeArr" + idx, l.type);
|
||||
shader->setVec3("lightDirArr" + idx, l.dir);
|
||||
shader->setVec3("lightPosArr" + idx, l.pos);
|
||||
shader->setVec3("lightColorArr" + idx, l.color);
|
||||
shader->setFloat("lightIntensityArr" + idx, l.intensity);
|
||||
shader->setFloat("lightRangeArr" + idx, l.range);
|
||||
shader->setFloat("lightInnerCosArr" + idx, l.inner);
|
||||
shader->setFloat("lightOuterCosArr" + idx, l.outer);
|
||||
}
|
||||
|
||||
glm::mat4 model = glm::mat4(1.0f);
|
||||
model = glm::translate(model, obj.position);
|
||||
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;
|
||||
int currentWidth = 800, currentHeight = 600;
|
||||
Shader* shader = nullptr;
|
||||
Shader* defaultShader = nullptr;
|
||||
Texture* texture1 = nullptr;
|
||||
Texture* texture2 = nullptr;
|
||||
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* sphereMesh = nullptr;
|
||||
Mesh* capsuleMesh = nullptr;
|
||||
@@ -76,6 +89,10 @@ public:
|
||||
|
||||
void initialize();
|
||||
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);
|
||||
int getWidth() const { return currentWidth; }
|
||||
int getHeight() const { return currentHeight; }
|
||||
|
||||
@@ -67,6 +67,8 @@ public:
|
||||
std::string albedoTexturePath;
|
||||
std::string overlayTexturePath;
|
||||
std::string normalMapPath;
|
||||
std::string vertexShaderPath;
|
||||
std::string fragmentShaderPath;
|
||||
bool useOverlay = false;
|
||||
LightComponent light; // Only used when type is a light
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
#include "../../include/Shaders/Shader.h"
|
||||
#include <glad/glad.h>
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
#include "../../src/ThirdParty/glm/glm.hpp"
|
||||
#include "../../src/ThirdParty/glm/gtc/type_ptr.hpp"
|
||||
|
||||
@@ -52,12 +53,12 @@ float skyboxVertices[] = {
|
||||
};
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
Skybox::~Skybox() {
|
||||
delete skyboxShader;
|
||||
if (skyboxShader) delete skyboxShader;
|
||||
glDeleteVertexArrays(1, &VAO);
|
||||
glDeleteBuffers(1, &VBO);
|
||||
}
|
||||
@@ -80,6 +81,26 @@ void Skybox::setTimeOfDay(float 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) {
|
||||
// Properly reconstruct the view matrix from the float array
|
||||
glm::mat4 viewMat = glm::make_mat4(view);
|
||||
@@ -98,4 +119,4 @@ void Skybox::draw(const float* view, const float* projection) {
|
||||
glDrawArrays(GL_TRIANGLES, 0, 36);
|
||||
glBindVertexArray(0);
|
||||
glDepthFunc(GL_LESS);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user