1126 lines
41 KiB
C++
1126 lines
41 KiB
C++
#include "Engine.h"
|
|
#include "ModelLoader.h"
|
|
#include <iostream>
|
|
#include <fstream>
|
|
|
|
namespace {
|
|
struct MaterialFileData {
|
|
MaterialProperties props;
|
|
std::string albedo;
|
|
std::string overlay;
|
|
std::string normal;
|
|
bool useOverlay = false;
|
|
std::string vertexShader;
|
|
std::string fragmentShader;
|
|
};
|
|
|
|
bool readMaterialFile(const std::string& path, MaterialFileData& outData) {
|
|
std::ifstream f(path);
|
|
if (!f.is_open()) {
|
|
return false;
|
|
}
|
|
|
|
std::string line;
|
|
while (std::getline(f, line)) {
|
|
line.erase(0, line.find_first_not_of(" \t\r\n"));
|
|
if (line.empty() || line[0] == '#') continue;
|
|
auto pos = line.find('=');
|
|
if (pos == std::string::npos) continue;
|
|
std::string key = line.substr(0, pos);
|
|
std::string val = line.substr(pos + 1);
|
|
if (key == "color") {
|
|
sscanf(val.c_str(), "%f,%f,%f", &outData.props.color.r, &outData.props.color.g, &outData.props.color.b);
|
|
} else if (key == "ambient") {
|
|
outData.props.ambientStrength = std::stof(val);
|
|
} else if (key == "specular") {
|
|
outData.props.specularStrength = std::stof(val);
|
|
} else if (key == "shininess") {
|
|
outData.props.shininess = std::stof(val);
|
|
} else if (key == "textureMix") {
|
|
outData.props.textureMix = std::stof(val);
|
|
} else if (key == "albedo") {
|
|
outData.albedo = val;
|
|
} else if (key == "overlay") {
|
|
outData.overlay = val;
|
|
} else if (key == "normal") {
|
|
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;
|
|
}
|
|
|
|
bool writeMaterialFile(const MaterialFileData& data, const std::string& path) {
|
|
std::ofstream f(path);
|
|
if (!f.is_open()) {
|
|
return false;
|
|
}
|
|
f << "# Material\n";
|
|
f << "color=" << data.props.color.r << "," << data.props.color.g << "," << data.props.color.b << "\n";
|
|
f << "ambient=" << data.props.ambientStrength << "\n";
|
|
f << "specular=" << data.props.specularStrength << "\n";
|
|
f << "shininess=" << data.props.shininess << "\n";
|
|
f << "textureMix=" << data.props.textureMix << "\n";
|
|
f << "useOverlay=" << (data.useOverlay ? 1 : 0) << "\n";
|
|
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
|
|
|
|
void window_size_callback(GLFWwindow* window, int width, int height) {
|
|
glViewport(0, 0, width, height);
|
|
}
|
|
|
|
SceneObject* Engine::getSelectedObject() {
|
|
if (selectedObjectId == -1) return nullptr;
|
|
auto it = std::find_if(sceneObjects.begin(), sceneObjects.end(),
|
|
[this](const SceneObject& obj) { return obj.id == selectedObjectId; });
|
|
return (it != sceneObjects.end()) ? &(*it) : nullptr;
|
|
}
|
|
|
|
glm::vec3 Engine::getSelectionCenterWorld(bool worldSpace) const {
|
|
if (selectedObjectIds.empty()) return glm::vec3(0.0f);
|
|
glm::vec3 acc(0.0f);
|
|
int count = 0;
|
|
auto findObj = [&](int id) -> const SceneObject* {
|
|
auto it = std::find_if(sceneObjects.begin(), sceneObjects.end(), [id](const SceneObject& o){ return o.id == id; });
|
|
return it == sceneObjects.end() ? nullptr : &(*it);
|
|
};
|
|
for (int id : selectedObjectIds) {
|
|
const SceneObject* o = findObj(id);
|
|
if (!o) continue;
|
|
acc += worldSpace ? o->position : glm::vec3(0.0f);
|
|
count++;
|
|
}
|
|
if (count == 0) return glm::vec3(0.0f);
|
|
return acc / (float)count;
|
|
}
|
|
|
|
void Engine::setPrimarySelection(int id, bool additive) {
|
|
if (!additive) {
|
|
selectedObjectIds.clear();
|
|
}
|
|
if (id >= 0) {
|
|
selectedObjectIds.push_back(id);
|
|
selectedObjectId = id;
|
|
} else {
|
|
selectedObjectIds.clear();
|
|
selectedObjectId = -1;
|
|
}
|
|
}
|
|
|
|
void Engine::clearSelection() {
|
|
selectedObjectIds.clear();
|
|
selectedObjectId = -1;
|
|
}
|
|
|
|
Camera Engine::makeCameraFromObject(const SceneObject& obj) const {
|
|
Camera cam;
|
|
cam.position = obj.position;
|
|
glm::quat q = glm::quat(glm::radians(obj.rotation));
|
|
glm::mat3 rot = glm::mat3_cast(q);
|
|
cam.front = glm::normalize(rot * glm::vec3(0.0f, 0.0f, -1.0f));
|
|
cam.up = glm::normalize(rot * glm::vec3(0.0f, 1.0f, 0.0f));
|
|
if (!std::isfinite(cam.front.x) || glm::length(cam.front) < 1e-3f) {
|
|
cam.front = glm::vec3(0.0f, 0.0f, -1.0f);
|
|
}
|
|
if (!std::isfinite(cam.up.x) || glm::length(cam.up) < 1e-3f) {
|
|
cam.up = glm::vec3(0.0f, 1.0f, 0.0f);
|
|
}
|
|
return cam;
|
|
}
|
|
|
|
void Engine::DecomposeMatrix(const glm::mat4& matrix, glm::vec3& pos, glm::vec3& rot, glm::vec3& scale) {
|
|
pos = glm::vec3(matrix[3]);
|
|
scale.x = glm::length(glm::vec3(matrix[0]));
|
|
scale.y = glm::length(glm::vec3(matrix[1]));
|
|
scale.z = glm::length(glm::vec3(matrix[2]));
|
|
|
|
glm::mat3 rotMat(matrix);
|
|
if (scale.x != 0.0f) rotMat[0] /= scale.x;
|
|
if (scale.y != 0.0f) rotMat[1] /= scale.y;
|
|
if (scale.z != 0.0f) rotMat[2] /= scale.z;
|
|
|
|
rot = glm::eulerAngles(glm::quat_cast(rotMat));
|
|
}
|
|
|
|
void Engine::recordState(const char* /*reason*/) {
|
|
SceneSnapshot snap;
|
|
snap.objects = sceneObjects;
|
|
snap.selectedIds = selectedObjectIds;
|
|
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.selectedIds = selectedObjectIds;
|
|
current.nextId = nextObjectId;
|
|
|
|
SceneSnapshot snap = undoStack.back();
|
|
undoStack.pop_back();
|
|
|
|
redoStack.push_back(std::move(current));
|
|
sceneObjects = std::move(snap.objects);
|
|
selectedObjectIds = snap.selectedIds;
|
|
selectedObjectId = selectedObjectIds.empty() ? -1 : selectedObjectIds.back();
|
|
nextObjectId = snap.nextId;
|
|
projectManager.currentProject.hasUnsavedChanges = true;
|
|
}
|
|
|
|
void Engine::redo() {
|
|
if (redoStack.empty()) return;
|
|
|
|
SceneSnapshot current;
|
|
current.objects = sceneObjects;
|
|
current.selectedIds = selectedObjectIds;
|
|
current.nextId = nextObjectId;
|
|
|
|
SceneSnapshot snap = redoStack.back();
|
|
redoStack.pop_back();
|
|
|
|
undoStack.push_back(std::move(current));
|
|
sceneObjects = std::move(snap.objects);
|
|
selectedObjectIds = snap.selectedIds;
|
|
selectedObjectId = selectedObjectIds.empty() ? -1 : selectedObjectIds.back();
|
|
nextObjectId = snap.nextId;
|
|
projectManager.currentProject.hasUnsavedChanges = true;
|
|
}
|
|
|
|
bool Engine::init() {
|
|
std::cerr << "[DEBUG] Creating window..." << std::endl;
|
|
editorWindow = window.makeWindow();
|
|
if (!editorWindow) {
|
|
std::cerr << "[DEBUG] Window creation failed!" << std::endl;
|
|
return false;
|
|
}
|
|
std::cerr << "[DEBUG] Window created successfully" << std::endl;
|
|
|
|
glfwSetWindowUserPointer(editorWindow, this);
|
|
glfwSetWindowSizeCallback(editorWindow, window_size_callback);
|
|
|
|
auto mouse_cb = [](GLFWwindow* window, double xpos, double ypos) {
|
|
auto* engine = static_cast<Engine*>(glfwGetWindowUserPointer(window));
|
|
if (!engine) return;
|
|
|
|
int cursorMode = glfwGetInputMode(window, GLFW_CURSOR);
|
|
if (!engine->viewportController.isViewportFocused() || cursorMode != GLFW_CURSOR_DISABLED) {
|
|
return;
|
|
}
|
|
|
|
engine->camera.processMouse(xpos, ypos);
|
|
};
|
|
glfwSetCursorPosCallback(editorWindow, mouse_cb);
|
|
|
|
std::cerr << "[DEBUG] Setting up ImGui..." << std::endl;
|
|
setupImGui();
|
|
std::cerr << "[DEBUG] ImGui setup complete" << std::endl;
|
|
|
|
logToConsole("Engine initialized - Waiting for project selection");
|
|
return true;
|
|
}
|
|
|
|
bool Engine::initRenderer() {
|
|
if (rendererInitialized) return true;
|
|
|
|
try {
|
|
renderer.initialize();
|
|
rendererInitialized = true;
|
|
return true;
|
|
} catch (...) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
void Engine::run() {
|
|
std::cerr << "[DEBUG] Entering main loop, showLauncher=" << showLauncher << std::endl;
|
|
|
|
while (!glfwWindowShouldClose(editorWindow)) {
|
|
if (glfwGetWindowAttrib(editorWindow, GLFW_ICONIFIED)) {
|
|
ImGui_ImplGlfw_Sleep(10);
|
|
continue;
|
|
}
|
|
|
|
// Enforce cursor lock state every frame to avoid backends restoring it.
|
|
int desiredMode = cursorLocked ? GLFW_CURSOR_DISABLED : GLFW_CURSOR_NORMAL;
|
|
if (glfwGetInputMode(editorWindow, GLFW_CURSOR) != desiredMode) {
|
|
glfwSetInputMode(editorWindow, GLFW_CURSOR, desiredMode);
|
|
if (cursorLocked && glfwRawMouseMotionSupported()) {
|
|
glfwSetInputMode(editorWindow, GLFW_RAW_MOUSE_MOTION, GLFW_TRUE);
|
|
} else if (!cursorLocked && glfwRawMouseMotionSupported()) {
|
|
glfwSetInputMode(editorWindow, GLFW_RAW_MOUSE_MOTION, GLFW_FALSE);
|
|
}
|
|
}
|
|
|
|
float currentFrame = glfwGetTime();
|
|
deltaTime = currentFrame - lastFrame;
|
|
lastFrame = currentFrame;
|
|
deltaTime = std::min(deltaTime, 1.0f / 30.0f);
|
|
|
|
glfwPollEvents();
|
|
|
|
if (!showLauncher) {
|
|
handleKeyboardShortcuts();
|
|
}
|
|
|
|
viewportController.update(editorWindow, cursorLocked);
|
|
|
|
if (!viewportController.isViewportFocused() && cursorLocked) {
|
|
cursorLocked = false;
|
|
glfwSetInputMode(editorWindow, GLFW_CURSOR, GLFW_CURSOR_NORMAL);
|
|
camera.firstMouse = true;
|
|
}
|
|
|
|
// Scroll-wheel speed adjustment while freelook is active
|
|
if (viewportController.isViewportFocused() && cursorLocked) {
|
|
float wheel = ImGui::GetIO().MouseWheel;
|
|
if (std::abs(wheel) > 0.0001f) {
|
|
float factor = std::pow(1.12f, wheel);
|
|
float ratio = (camera.moveSpeed > 0.001f) ? (camera.sprintSpeed / camera.moveSpeed) : 2.0f;
|
|
camera.moveSpeed = std::clamp(camera.moveSpeed * factor, 0.5f, 100.0f);
|
|
camera.sprintSpeed = std::clamp(camera.moveSpeed * ratio, 0.5f, 200.0f);
|
|
}
|
|
}
|
|
|
|
if (viewportController.isViewportFocused() && cursorLocked) {
|
|
camera.processKeyboard(deltaTime, editorWindow);
|
|
}
|
|
|
|
if (!showLauncher && projectManager.currentProject.isLoaded && rendererInitialized) {
|
|
glm::mat4 view = camera.getViewMatrix();
|
|
float aspect = static_cast<float>(viewportWidth) / static_cast<float>(viewportHeight);
|
|
if (aspect <= 0.0f) aspect = 1.0f;
|
|
glm::mat4 proj = glm::perspective(glm::radians(FOV), aspect, NEAR_PLANE, FAR_PLANE);
|
|
|
|
renderer.beginRender(view, proj, camera.position);
|
|
renderer.renderScene(camera, sceneObjects, selectedObjectId);
|
|
renderer.endRender();
|
|
}
|
|
|
|
if (firstFrame) {
|
|
std::cerr << "[DEBUG] First frame: starting ImGui NewFrame" << std::endl;
|
|
}
|
|
|
|
ImGui_ImplOpenGL3_NewFrame();
|
|
ImGui_ImplGlfw_NewFrame();
|
|
ImGui::NewFrame();
|
|
|
|
if (firstFrame) {
|
|
std::cerr << "[DEBUG] First frame: ImGui NewFrame complete, rendering UI..." << std::endl;
|
|
}
|
|
|
|
if (showLauncher) {
|
|
if (firstFrame) {
|
|
std::cerr << "[DEBUG] First frame: calling renderLauncher()" << std::endl;
|
|
}
|
|
renderLauncher();
|
|
} else {
|
|
setupDockspace();
|
|
renderMainMenuBar();
|
|
|
|
if (!viewportFullscreen) {
|
|
if (showHierarchy) renderHierarchyPanel();
|
|
if (showInspector) renderInspectorPanel();
|
|
if (showFileBrowser) renderFileBrowserPanel();
|
|
if (showMeshBuilder) renderMeshBuilderPanel();
|
|
if (showConsole) renderConsolePanel();
|
|
if (showEnvironmentWindow) renderEnvironmentWindow();
|
|
if (showCameraWindow) renderCameraWindow();
|
|
if (showProjectBrowser) renderProjectBrowserPanel();
|
|
}
|
|
|
|
renderViewport();
|
|
renderDialogs();
|
|
}
|
|
|
|
if (firstFrame) {
|
|
std::cerr << "[DEBUG] First frame: UI rendering complete, finalizing frame..." << std::endl;
|
|
}
|
|
|
|
int displayW, displayH;
|
|
glfwGetFramebufferSize(editorWindow, &displayW, &displayH);
|
|
glViewport(0, 0, displayW, displayH);
|
|
glClearColor(0.1f, 0.1f, 0.12f, 1.00f);
|
|
glClear(GL_COLOR_BUFFER_BIT);
|
|
|
|
ImGui::Render();
|
|
ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData());
|
|
|
|
ImGuiIO& io = ImGui::GetIO();
|
|
if (io.ConfigFlags & ImGuiConfigFlags_ViewportsEnable) {
|
|
GLFWwindow* backup_current_context = glfwGetCurrentContext();
|
|
ImGui::UpdatePlatformWindows();
|
|
ImGui::RenderPlatformWindowsDefault();
|
|
glfwMakeContextCurrent(backup_current_context);
|
|
}
|
|
|
|
glfwSwapBuffers(editorWindow);
|
|
|
|
if (firstFrame) {
|
|
std::cerr << "[DEBUG] First frame complete!" << std::endl;
|
|
}
|
|
firstFrame = false;
|
|
}
|
|
|
|
std::cerr << "[DEBUG] Exiting main loop" << std::endl;
|
|
}
|
|
|
|
void Engine::shutdown() {
|
|
if (projectManager.currentProject.isLoaded && projectManager.currentProject.hasUnsavedChanges) {
|
|
saveCurrentScene();
|
|
}
|
|
|
|
ImGui_ImplOpenGL3_Shutdown();
|
|
ImGui_ImplGlfw_Shutdown();
|
|
ImGui::DestroyContext();
|
|
glfwTerminate();
|
|
}
|
|
|
|
void Engine::importOBJToScene(const std::string& filepath, const std::string& objectName) {
|
|
recordState("importOBJ");
|
|
std::string errorMsg;
|
|
int meshId = g_objLoader.loadOBJ(filepath, errorMsg);
|
|
|
|
if (meshId < 0) {
|
|
addConsoleMessage("Failed to load OBJ: " + errorMsg, ConsoleMessageType::Error);
|
|
return;
|
|
}
|
|
|
|
int id = nextObjectId++;
|
|
std::string name = objectName.empty() ? fs::path(filepath).stem().string() : objectName;
|
|
|
|
SceneObject obj(name, ObjectType::OBJMesh, id);
|
|
obj.meshPath = filepath;
|
|
obj.meshId = meshId;
|
|
|
|
sceneObjects.push_back(obj);
|
|
setPrimarySelection(id);
|
|
|
|
if (projectManager.currentProject.isLoaded) {
|
|
projectManager.currentProject.hasUnsavedChanges = true;
|
|
}
|
|
|
|
const auto* meshInfo = g_objLoader.getMeshInfo(meshId);
|
|
if (meshInfo) {
|
|
addConsoleMessage("Imported OBJ: " + name + " (" +
|
|
std::to_string(meshInfo->vertexCount) + " vertices, " +
|
|
std::to_string(meshInfo->faceCount) + " faces)",
|
|
ConsoleMessageType::Success);
|
|
} else {
|
|
addConsoleMessage("Imported OBJ: " + name, ConsoleMessageType::Success);
|
|
}
|
|
}
|
|
|
|
void Engine::importModelToScene(const std::string& filepath, const std::string& objectName) {
|
|
recordState("importModel");
|
|
auto& modelLoader = getModelLoader();
|
|
ModelLoadResult result = modelLoader.loadModel(filepath);
|
|
|
|
if (!result.success) {
|
|
addConsoleMessage("Failed to load model: " + result.errorMessage, ConsoleMessageType::Error);
|
|
return;
|
|
}
|
|
|
|
int id = nextObjectId++;
|
|
std::string name = objectName.empty() ? fs::path(filepath).stem().string() : objectName;
|
|
|
|
SceneObject obj(name, ObjectType::Model, id);
|
|
obj.meshPath = filepath;
|
|
obj.meshId = result.meshIndex;
|
|
|
|
sceneObjects.push_back(obj);
|
|
setPrimarySelection(id);
|
|
|
|
if (projectManager.currentProject.isLoaded) {
|
|
projectManager.currentProject.hasUnsavedChanges = true;
|
|
}
|
|
|
|
addConsoleMessage(
|
|
"Imported model: " + name + " (" +
|
|
std::to_string(result.vertexCount) + " verts, " +
|
|
std::to_string(result.faceCount) + " faces, " +
|
|
std::to_string(result.meshCount) + " meshes)",
|
|
ConsoleMessageType::Success
|
|
);
|
|
}
|
|
|
|
void Engine::convertModelToRawMesh(const std::string& filepath) {
|
|
auto& modelLoader = getModelLoader();
|
|
fs::path inPath(filepath);
|
|
fs::path outPath = inPath;
|
|
outPath.replace_extension(".rmesh");
|
|
|
|
std::string error;
|
|
if (modelLoader.exportRawMesh(filepath, outPath.string(), error)) {
|
|
addConsoleMessage("Converted to raw mesh: " + outPath.string(), ConsoleMessageType::Success);
|
|
fileBrowser.needsRefresh = true;
|
|
} else {
|
|
addConsoleMessage("Raw mesh export failed: " + error, ConsoleMessageType::Error);
|
|
}
|
|
}
|
|
|
|
bool Engine::ensureMeshEditTarget(SceneObject* obj) {
|
|
if (!obj) return false;
|
|
fs::path ext = fs::path(obj->meshPath).extension();
|
|
std::string extLower = ext.string();
|
|
std::transform(extLower.begin(), extLower.end(), extLower.begin(), ::tolower);
|
|
if (extLower != ".rmesh") return false;
|
|
|
|
if (meshEditLoaded && meshEditPath == obj->meshPath) {
|
|
if (meshEditSelectedVertices.empty() && !meshEditAsset.positions.empty()) {
|
|
meshEditSelectedVertices.push_back(0);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
std::string err;
|
|
if (!getModelLoader().loadRawMesh(obj->meshPath, meshEditAsset, err)) {
|
|
addConsoleMessage("Mesh edit load failed: " + err, ConsoleMessageType::Error);
|
|
meshEditLoaded = false;
|
|
return false;
|
|
}
|
|
meshEditLoaded = true;
|
|
meshEditPath = obj->meshPath;
|
|
meshEditSelectedVertices.clear();
|
|
meshEditSelectedEdges.clear();
|
|
meshEditSelectedFaces.clear();
|
|
if (!meshEditAsset.positions.empty()) meshEditSelectedVertices.push_back(0);
|
|
return true;
|
|
}
|
|
|
|
bool Engine::syncMeshEditToGPU(SceneObject* obj) {
|
|
if (!obj || !meshEditLoaded) return false;
|
|
std::string err;
|
|
if (!getModelLoader().updateRawMesh(obj->meshId, meshEditAsset, err)) {
|
|
addConsoleMessage("Mesh GPU sync failed: " + err, ConsoleMessageType::Error);
|
|
return false;
|
|
}
|
|
projectManager.currentProject.hasUnsavedChanges = true;
|
|
return true;
|
|
}
|
|
|
|
void Engine::loadMaterialFromFile(SceneObject& obj) {
|
|
if (obj.materialPath.empty()) return;
|
|
try {
|
|
MaterialFileData data;
|
|
if (!readMaterialFile(obj.materialPath, data)) {
|
|
addConsoleMessage("Failed to open material: " + obj.materialPath, ConsoleMessageType::Error);
|
|
return;
|
|
}
|
|
obj.material = data.props;
|
|
obj.albedoTexturePath = data.albedo;
|
|
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 (...) {
|
|
addConsoleMessage("Failed to read material: " + obj.materialPath, ConsoleMessageType::Error);
|
|
}
|
|
}
|
|
|
|
bool Engine::loadMaterialData(const std::string& path, MaterialProperties& props,
|
|
std::string& albedo, std::string& overlay,
|
|
std::string& normal, bool& useOverlay,
|
|
std::string* vertexShaderOut,
|
|
std::string* fragmentShaderOut)
|
|
{
|
|
MaterialFileData data;
|
|
if (!readMaterialFile(path, data)) {
|
|
return false;
|
|
}
|
|
props = data.props;
|
|
albedo = data.albedo;
|
|
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& vertexShader,
|
|
const std::string& fragmentShader)
|
|
{
|
|
MaterialFileData data;
|
|
data.props = props;
|
|
data.albedo = albedo;
|
|
data.overlay = overlay;
|
|
data.normal = normal;
|
|
data.useOverlay = useOverlay;
|
|
data.vertexShader = vertexShader;
|
|
data.fragmentShader = fragmentShader;
|
|
return writeMaterialFile(data, path);
|
|
}
|
|
|
|
void Engine::saveMaterialToFile(const SceneObject& obj) {
|
|
if (obj.materialPath.empty()) {
|
|
addConsoleMessage("Material path is empty", ConsoleMessageType::Warning);
|
|
return;
|
|
}
|
|
try {
|
|
MaterialFileData data;
|
|
data.props = obj.material;
|
|
data.albedo = obj.albedoTexturePath;
|
|
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);
|
|
return;
|
|
}
|
|
addConsoleMessage("Saved material: " + obj.materialPath, ConsoleMessageType::Success);
|
|
} catch (...) {
|
|
addConsoleMessage("Failed to save material: " + obj.materialPath, ConsoleMessageType::Error);
|
|
}
|
|
}
|
|
|
|
void Engine::handleKeyboardShortcuts() {
|
|
static bool f11Pressed = false;
|
|
if (glfwGetKey(editorWindow, GLFW_KEY_F11) == GLFW_PRESS && !f11Pressed) {
|
|
viewportFullscreen = !viewportFullscreen;
|
|
f11Pressed = true;
|
|
}
|
|
if (glfwGetKey(editorWindow, GLFW_KEY_F11) == GLFW_RELEASE) {
|
|
f11Pressed = false;
|
|
}
|
|
|
|
static bool ctrlSPressed = false;
|
|
bool ctrlDown = glfwGetKey(editorWindow, GLFW_KEY_LEFT_CONTROL) == GLFW_PRESS ||
|
|
glfwGetKey(editorWindow, GLFW_KEY_RIGHT_CONTROL) == GLFW_PRESS;
|
|
|
|
if (ctrlDown && glfwGetKey(editorWindow, GLFW_KEY_S) == GLFW_PRESS && !ctrlSPressed) {
|
|
if (projectManager.currentProject.isLoaded) {
|
|
saveCurrentScene();
|
|
}
|
|
ctrlSPressed = true;
|
|
}
|
|
if (glfwGetKey(editorWindow, GLFW_KEY_S) == GLFW_RELEASE) {
|
|
ctrlSPressed = false;
|
|
}
|
|
|
|
static bool ctrlNPressed = false;
|
|
if (ctrlDown && glfwGetKey(editorWindow, GLFW_KEY_N) == GLFW_PRESS && !ctrlNPressed) {
|
|
if (projectManager.currentProject.isLoaded) {
|
|
showNewSceneDialog = true;
|
|
memset(newSceneName, 0, sizeof(newSceneName));
|
|
}
|
|
ctrlNPressed = true;
|
|
}
|
|
if (glfwGetKey(editorWindow, GLFW_KEY_N) == GLFW_RELEASE) {
|
|
ctrlNPressed = false;
|
|
}
|
|
|
|
bool cameraActive = cursorLocked || viewportController.isViewportFocused() && cursorLocked;
|
|
if (!cameraActive) {
|
|
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::BOUNDS;
|
|
if (ImGui::IsKeyPressed(ImGuiKey_T)) mCurrentGizmoOperation = ImGuizmo::UNIVERSAL;
|
|
|
|
if (ImGui::IsKeyPressed(ImGuiKey_U)) {
|
|
mCurrentGizmoMode = (mCurrentGizmoMode == ImGuizmo::LOCAL) ? ImGuizmo::WORLD : ImGuizmo::LOCAL;
|
|
}
|
|
}
|
|
|
|
static bool snapPressed = false;
|
|
static bool snapHeldByCtrl = false;
|
|
static bool snapStateBeforeCtrl = false;
|
|
|
|
if (!snapHeldByCtrl && ctrlDown) {
|
|
snapStateBeforeCtrl = useSnap;
|
|
snapHeldByCtrl = true;
|
|
useSnap = true;
|
|
} else if (snapHeldByCtrl && !ctrlDown) {
|
|
useSnap = snapStateBeforeCtrl;
|
|
snapHeldByCtrl = false;
|
|
}
|
|
|
|
if (!cameraActive) {
|
|
if (ImGui::IsKeyPressed(ImGuiKey_Y) && !snapPressed) {
|
|
useSnap = !useSnap;
|
|
snapPressed = true;
|
|
}
|
|
}
|
|
if (ImGui::IsKeyReleased(ImGuiKey_Y)) {
|
|
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) {
|
|
try {
|
|
if (projectManager.loadProject(path)) {
|
|
// Make sure project folders exist even for older/minimal projects
|
|
if (!fs::exists(projectManager.currentProject.assetsPath)) {
|
|
fs::create_directories(projectManager.currentProject.assetsPath);
|
|
}
|
|
if (!fs::exists(projectManager.currentProject.scenesPath)) {
|
|
fs::create_directories(projectManager.currentProject.scenesPath);
|
|
}
|
|
|
|
if (!initRenderer()) {
|
|
addConsoleMessage("Error: Failed to initialize renderer!", ConsoleMessageType::Error);
|
|
showLauncher = true;
|
|
return;
|
|
}
|
|
|
|
loadRecentScenes();
|
|
fileBrowser.setProjectRoot(projectManager.currentProject.projectPath);
|
|
fileBrowser.currentPath = projectManager.currentProject.projectPath;
|
|
fileBrowser.needsRefresh = true;
|
|
showLauncher = false;
|
|
addConsoleMessage("Opened project: " + projectManager.currentProject.name, ConsoleMessageType::Info);
|
|
} else {
|
|
addConsoleMessage("Error opening project: " + projectManager.errorMessage, ConsoleMessageType::Error);
|
|
}
|
|
} catch (const std::exception& e) {
|
|
addConsoleMessage(std::string("Exception opening project: ") + e.what(), ConsoleMessageType::Error);
|
|
showLauncher = true;
|
|
} catch (...) {
|
|
addConsoleMessage("Unknown exception opening project", ConsoleMessageType::Error);
|
|
showLauncher = true;
|
|
}
|
|
}
|
|
|
|
void Engine::createNewProject(const char* name, const char* location) {
|
|
fs::path basePath(location);
|
|
fs::create_directories(basePath);
|
|
|
|
Project newProject(name, basePath);
|
|
if (newProject.create()) {
|
|
projectManager.currentProject = newProject;
|
|
projectManager.addToRecentProjects(name,
|
|
(newProject.projectPath / "project.modu").string());
|
|
|
|
if (!initRenderer()) {
|
|
logToConsole("Error: Failed to initialize renderer!");
|
|
return;
|
|
}
|
|
|
|
sceneObjects.clear();
|
|
clearSelection();
|
|
nextObjectId = 0;
|
|
|
|
addObject(ObjectType::Cube, "Cube");
|
|
|
|
fileBrowser.setProjectRoot(projectManager.currentProject.projectPath);
|
|
fileBrowser.currentPath = projectManager.currentProject.projectPath;
|
|
fileBrowser.needsRefresh = true;
|
|
|
|
showLauncher = false;
|
|
firstFrame = true;
|
|
|
|
addConsoleMessage("Created new project: " + std::string(name), ConsoleMessageType::Success);
|
|
addConsoleMessage("Project location: " + newProject.projectPath.string(), ConsoleMessageType::Info);
|
|
|
|
saveCurrentScene();
|
|
} else {
|
|
projectManager.errorMessage = "Failed to create project directory";
|
|
}
|
|
}
|
|
|
|
void Engine::loadRecentScenes() {
|
|
sceneObjects.clear();
|
|
clearSelection();
|
|
nextObjectId = 0;
|
|
undoStack.clear();
|
|
redoStack.clear();
|
|
|
|
fs::path scenePath = projectManager.currentProject.getSceneFilePath(projectManager.currentProject.currentSceneName);
|
|
if (fs::exists(scenePath)) {
|
|
if (SceneSerializer::loadScene(scenePath, sceneObjects, nextObjectId)) {
|
|
addConsoleMessage("Loaded scene: " + projectManager.currentProject.currentSceneName, ConsoleMessageType::Success);
|
|
} else {
|
|
addConsoleMessage("Warning: Failed to load scene, starting fresh", ConsoleMessageType::Warning);
|
|
addObject(ObjectType::Cube, "Cube");
|
|
}
|
|
} else {
|
|
addConsoleMessage("Default scene not found, starting with a new scene.", ConsoleMessageType::Info);
|
|
addObject(ObjectType::Cube, "Cube");
|
|
}
|
|
recordState("sceneLoaded");
|
|
|
|
fileBrowser.setProjectRoot(projectManager.currentProject.projectPath);
|
|
fileBrowser.currentPath = projectManager.currentProject.projectPath;
|
|
fileBrowser.needsRefresh = true;
|
|
}
|
|
|
|
void Engine::saveCurrentScene() {
|
|
if (!projectManager.currentProject.isLoaded) return;
|
|
|
|
fs::path scenePath = projectManager.currentProject.getSceneFilePath(projectManager.currentProject.currentSceneName);
|
|
if (SceneSerializer::saveScene(scenePath, sceneObjects, nextObjectId)) {
|
|
projectManager.currentProject.hasUnsavedChanges = false;
|
|
projectManager.currentProject.saveProjectFile();
|
|
addConsoleMessage("Saved scene: " + projectManager.currentProject.currentSceneName, ConsoleMessageType::Success);
|
|
} else {
|
|
addConsoleMessage("Error: Failed to save scene!", ConsoleMessageType::Error);
|
|
}
|
|
}
|
|
|
|
void Engine::loadScene(const std::string& sceneName) {
|
|
if (!projectManager.currentProject.isLoaded) return;
|
|
|
|
if (projectManager.currentProject.hasUnsavedChanges) {
|
|
saveCurrentScene();
|
|
}
|
|
|
|
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();
|
|
clearSelection();
|
|
bool hasDirLight = std::any_of(sceneObjects.begin(), sceneObjects.end(), [](const SceneObject& o) {
|
|
return o.type == ObjectType::DirectionalLight;
|
|
});
|
|
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);
|
|
}
|
|
}
|
|
|
|
void Engine::createNewScene(const std::string& sceneName) {
|
|
if (!projectManager.currentProject.isLoaded || sceneName.empty()) return;
|
|
|
|
if (projectManager.currentProject.hasUnsavedChanges) {
|
|
saveCurrentScene();
|
|
}
|
|
|
|
sceneObjects.clear();
|
|
clearSelection();
|
|
nextObjectId = 0;
|
|
undoStack.clear();
|
|
redoStack.clear();
|
|
|
|
projectManager.currentProject.currentSceneName = sceneName;
|
|
projectManager.currentProject.hasUnsavedChanges = true;
|
|
|
|
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));
|
|
// Light defaults
|
|
if (type == ObjectType::PointLight) {
|
|
sceneObjects.back().light.type = LightType::Point;
|
|
sceneObjects.back().light.range = 12.0f;
|
|
sceneObjects.back().light.intensity = 2.0f;
|
|
} else if (type == ObjectType::SpotLight) {
|
|
sceneObjects.back().light.type = LightType::Spot;
|
|
sceneObjects.back().light.range = 15.0f;
|
|
sceneObjects.back().light.intensity = 2.5f;
|
|
} else if (type == ObjectType::AreaLight) {
|
|
sceneObjects.back().light.type = LightType::Area;
|
|
sceneObjects.back().light.range = 10.0f;
|
|
sceneObjects.back().light.intensity = 3.0f;
|
|
sceneObjects.back().light.size = glm::vec2(2.0f, 2.0f);
|
|
} else if (type == ObjectType::PostFXNode) {
|
|
sceneObjects.back().postFx.enabled = true;
|
|
sceneObjects.back().postFx.bloomEnabled = true;
|
|
sceneObjects.back().postFx.colorAdjustEnabled = true;
|
|
} else if (type == ObjectType::Camera) {
|
|
sceneObjects.back().camera.type = SceneCameraType::Player;
|
|
sceneObjects.back().camera.fov = 60.0f;
|
|
}
|
|
setPrimarySelection(id);
|
|
if (projectManager.currentProject.isLoaded) {
|
|
projectManager.currentProject.hasUnsavedChanges = true;
|
|
}
|
|
logToConsole("Created: " + name);
|
|
}
|
|
|
|
void Engine::duplicateSelected() {
|
|
auto it = std::find_if(sceneObjects.begin(), sceneObjects.end(),
|
|
[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);
|
|
newObj.rotation = it->rotation;
|
|
newObj.scale = it->scale;
|
|
newObj.meshPath = it->meshPath;
|
|
newObj.meshId = it->meshId;
|
|
newObj.material = it->material;
|
|
newObj.materialPath = it->materialPath;
|
|
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;
|
|
newObj.camera = it->camera;
|
|
newObj.postFx = it->postFx;
|
|
|
|
sceneObjects.push_back(newObj);
|
|
setPrimarySelection(id);
|
|
if (projectManager.currentProject.isLoaded) {
|
|
projectManager.currentProject.hasUnsavedChanges = true;
|
|
}
|
|
logToConsole("Duplicated: " + newObj.name);
|
|
}
|
|
}
|
|
|
|
void Engine::deleteSelected() {
|
|
recordState("delete");
|
|
auto it = std::remove_if(sceneObjects.begin(), sceneObjects.end(),
|
|
[this](const SceneObject& obj) { return obj.id == selectedObjectId; });
|
|
|
|
if (it != sceneObjects.end()) {
|
|
logToConsole("Deleted object");
|
|
sceneObjects.erase(it, sceneObjects.end());
|
|
clearSelection();
|
|
if (projectManager.currentProject.isLoaded) {
|
|
projectManager.currentProject.hasUnsavedChanges = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
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; });
|
|
|
|
if (childIt == sceneObjects.end()) return;
|
|
|
|
if (childIt->parentId != -1) {
|
|
auto oldParentIt = std::find_if(sceneObjects.begin(), sceneObjects.end(),
|
|
[&childIt](const SceneObject& obj) { return obj.id == childIt->parentId; });
|
|
if (oldParentIt != sceneObjects.end()) {
|
|
auto& children = oldParentIt->childIds;
|
|
children.erase(std::remove(children.begin(), children.end(), childId), children.end());
|
|
}
|
|
}
|
|
|
|
childIt->parentId = parentId;
|
|
|
|
if (parentId != -1) {
|
|
auto newParentIt = std::find_if(sceneObjects.begin(), sceneObjects.end(),
|
|
[parentId](const SceneObject& obj) { return obj.id == parentId; });
|
|
if (newParentIt != sceneObjects.end()) {
|
|
newParentIt->childIds.push_back(childId);
|
|
}
|
|
}
|
|
|
|
if (projectManager.currentProject.isLoaded) {
|
|
projectManager.currentProject.hasUnsavedChanges = true;
|
|
}
|
|
logToConsole("Reparented object");
|
|
}
|
|
|
|
void Engine::addConsoleMessage(const std::string& message, ConsoleMessageType type) {
|
|
std::string prefix;
|
|
switch (type) {
|
|
case ConsoleMessageType::Info: prefix = "Info: "; break;
|
|
case ConsoleMessageType::Warning: prefix = "Warning: "; break;
|
|
case ConsoleMessageType::Error: prefix = "Error: "; break;
|
|
case ConsoleMessageType::Success: prefix = "Success: "; break;
|
|
}
|
|
|
|
auto now = std::chrono::system_clock::now();
|
|
auto time = std::chrono::system_clock::to_time_t(now);
|
|
std::string timeStr = std::ctime(&time);
|
|
timeStr = timeStr.substr(11, 8);
|
|
|
|
consoleLog.push_back("[" + timeStr + "] " + prefix + " " + message);
|
|
if (consoleLog.size() > 1000) {
|
|
consoleLog.erase(consoleLog.begin());
|
|
}
|
|
}
|
|
|
|
void Engine::logToConsole(const std::string& message) {
|
|
addConsoleMessage(message, ConsoleMessageType::Info);
|
|
}
|
|
|
|
SceneObject* Engine::findObjectByName(const std::string& name) {
|
|
auto it = std::find_if(sceneObjects.begin(), sceneObjects.end(), [&](const SceneObject& o) {
|
|
return o.name == name;
|
|
});
|
|
if (it != sceneObjects.end()) return &(*it);
|
|
return nullptr;
|
|
}
|
|
|
|
SceneObject* Engine::findObjectById(int id) {
|
|
auto it = std::find_if(sceneObjects.begin(), sceneObjects.end(), [&](const SceneObject& o) {
|
|
return o.id == id;
|
|
});
|
|
if (it != sceneObjects.end()) return &(*it);
|
|
return nullptr;
|
|
}
|
|
|
|
fs::path Engine::resolveScriptBinary(const fs::path& sourcePath) {
|
|
ScriptBuildConfig config;
|
|
std::string error;
|
|
fs::path cfg = projectManager.currentProject.scriptsConfigPath.empty()
|
|
? projectManager.currentProject.projectPath / "Scripts.modu"
|
|
: projectManager.currentProject.scriptsConfigPath;
|
|
if (!scriptCompiler.loadConfig(cfg, config, error)) {
|
|
return {};
|
|
}
|
|
ScriptBuildCommands cmds;
|
|
if (!scriptCompiler.makeCommands(config, sourcePath, cmds, error)) {
|
|
return {};
|
|
}
|
|
return cmds.binaryPath;
|
|
}
|
|
|
|
void Engine::compileScriptFile(const fs::path& scriptPath) {
|
|
if (!projectManager.currentProject.isLoaded) {
|
|
addConsoleMessage("No project is loaded", ConsoleMessageType::Warning);
|
|
return;
|
|
}
|
|
|
|
showCompilePopup = true;
|
|
lastCompileLog.clear();
|
|
lastCompileStatus = "Compiling " + scriptPath.filename().string();
|
|
|
|
fs::path configPath = projectManager.currentProject.scriptsConfigPath;
|
|
if (configPath.empty()) {
|
|
configPath = projectManager.currentProject.projectPath / "Scripts.modu";
|
|
}
|
|
|
|
ScriptBuildConfig config;
|
|
std::string error;
|
|
if (!scriptCompiler.loadConfig(configPath, config, error)) {
|
|
lastCompileSuccess = false;
|
|
lastCompileLog = error;
|
|
addConsoleMessage("Script config error: " + error, ConsoleMessageType::Error);
|
|
return;
|
|
}
|
|
|
|
ScriptBuildCommands commands;
|
|
if (!scriptCompiler.makeCommands(config, scriptPath, commands, error)) {
|
|
lastCompileSuccess = false;
|
|
lastCompileLog = error;
|
|
addConsoleMessage("Script build error: " + error, ConsoleMessageType::Error);
|
|
return;
|
|
}
|
|
|
|
ScriptCompileOutput output;
|
|
if (!scriptCompiler.compile(commands, output, error)) {
|
|
lastCompileSuccess = false;
|
|
lastCompileStatus = "Compile failed";
|
|
lastCompileLog = output.compileLog + output.linkLog + error;
|
|
addConsoleMessage("Compile failed: " + error, ConsoleMessageType::Error);
|
|
if (!output.compileLog.empty()) addConsoleMessage(output.compileLog, ConsoleMessageType::Info);
|
|
if (!output.linkLog.empty()) addConsoleMessage(output.linkLog, ConsoleMessageType::Info);
|
|
return;
|
|
}
|
|
|
|
scriptRuntime.unloadAll();
|
|
|
|
lastCompileSuccess = true;
|
|
lastCompileStatus = "Reloading EngineRoot";
|
|
lastCompileLog = output.compileLog + output.linkLog;
|
|
addConsoleMessage("Compiled script -> " + commands.binaryPath.string(), ConsoleMessageType::Success);
|
|
if (!output.compileLog.empty()) addConsoleMessage(output.compileLog, ConsoleMessageType::Info);
|
|
if (!output.linkLog.empty()) addConsoleMessage(output.linkLog, ConsoleMessageType::Info);
|
|
}
|
|
|
|
void Engine::setupImGui() {
|
|
std::cerr << "[DEBUG] setupImGui: getting primary monitor..." << std::endl;
|
|
float mainScale = 1.0f;
|
|
GLFWmonitor* primaryMonitor = glfwGetPrimaryMonitor();
|
|
if (primaryMonitor) {
|
|
std::cerr << "[DEBUG] setupImGui: got primary monitor, getting content scale..." << std::endl;
|
|
mainScale = ImGui_ImplGlfw_GetContentScaleForMonitor(primaryMonitor);
|
|
std::cerr << "[DEBUG] setupImGui: content scale = " << mainScale << std::endl;
|
|
} else {
|
|
std::cerr << "[DEBUG] setupImGui: WARNING - no primary monitor found!" << std::endl;
|
|
}
|
|
|
|
std::cerr << "[DEBUG] setupImGui: creating ImGui context..." << std::endl;
|
|
IMGUI_CHECKVERSION();
|
|
ImGui::CreateContext();
|
|
ImGuiIO& io = ImGui::GetIO();
|
|
|
|
io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard;
|
|
io.ConfigFlags |= ImGuiConfigFlags_DockingEnable;
|
|
#ifndef __linux__
|
|
io.ConfigFlags |= ImGuiConfigFlags_ViewportsEnable;
|
|
#endif
|
|
|
|
std::cerr << "[DEBUG] setupImGui: applying theme..." << std::endl;
|
|
applyModernTheme();
|
|
|
|
ImGuiStyle& style = ImGui::GetStyle();
|
|
style.ScaleAllSizes(mainScale);
|
|
style.FontScaleDpi = mainScale;
|
|
|
|
if (io.ConfigFlags & ImGuiConfigFlags_ViewportsEnable) {
|
|
style.WindowRounding = 0.0f;
|
|
style.Colors[ImGuiCol_WindowBg].w = 1.0f;
|
|
}
|
|
|
|
std::cerr << "[DEBUG] setupImGui: initializing ImGui GLFW backend..." << std::endl;
|
|
ImGui_ImplGlfw_InitForOpenGL(editorWindow, true);
|
|
|
|
std::cerr << "[DEBUG] setupImGui: initializing ImGui OpenGL3 backend..." << std::endl;
|
|
if (!ImGui_ImplOpenGL3_Init("#version 330")) {
|
|
std::cerr << "[DEBUG] ImGui OpenGL3 init failed!" << std::endl;
|
|
throw std::runtime_error("ImGui error");
|
|
}
|
|
std::cerr << "[DEBUG] setupImGui: complete!" << std::endl;
|
|
}
|