#include "ProjectManager.h" #include "Rendering.h" #include "ModelLoader.h" // Project implementation Project::Project(const std::string& projectName, const fs::path& basePath) : name(projectName) { projectPath = basePath / projectName; scenesPath = projectPath / "Scenes"; assetsPath = projectPath / "Assets"; scriptsPath = projectPath / "Scripts"; scriptsConfigPath = projectPath / "Scripts.modu"; } bool Project::create() { try { fs::create_directories(projectPath); fs::create_directories(scenesPath); fs::create_directories(assetsPath); fs::create_directories(assetsPath / "Textures"); fs::create_directories(assetsPath / "Models"); fs::create_directories(assetsPath / "Shaders"); fs::create_directories(scriptsPath); fs::create_directories(projectPath / "Cache" / "ScriptBin"); saveProjectFile(); // Initialize a default scripting build file fs::path engineRoot = fs::current_path(); std::ofstream scriptCfg(scriptsConfigPath); scriptCfg << "# Scripts.modu\n"; scriptCfg << "cppStandard=c++20\n"; scriptCfg << "scriptsDir=Scripts\n"; scriptCfg << "outDir=Cache/ScriptBin\n"; scriptCfg << "includeDir=" << (engineRoot / "src").string() << "\n"; scriptCfg << "includeDir=" << (engineRoot / "include").string() << "\n"; scriptCfg << "includeDir=" << (engineRoot / "src/ThirdParty").string() << "\n"; scriptCfg << "includeDir=" << (engineRoot / "src/ThirdParty/glm").string() << "\n"; scriptCfg << "define=MODU_SCRIPTING=1\n"; scriptCfg << "define=MODU_PROJECT_NAME=\"" << name << "\"\n"; scriptCfg << "linux.linkLib=pthread\n"; scriptCfg << "linux.linkLib=dl\n"; scriptCfg << "win.linkLib=User32.lib\n"; scriptCfg << "win.linkLib=Advapi32.lib\n"; scriptCfg.close(); currentSceneName = "Main"; isLoaded = true; return true; } catch (const std::exception& e) { std::cerr << "Failed to create project: " << e.what() << std::endl; return false; } } bool Project::load(const fs::path& projectFilePath) { try { projectPath = projectFilePath.parent_path(); scenesPath = projectPath / "Scenes"; assetsPath = projectPath / "Assets"; scriptsPath = projectPath / "Scripts"; scriptsConfigPath = projectPath / "Scripts.modu"; std::ifstream file(projectFilePath); if (!file.is_open()) return false; std::string line; while (std::getline(file, line)) { if (line.find("name=") == 0) { name = line.substr(5); } else if (line.find("lastScene=") == 0) { currentSceneName = line.substr(10); } } file.close(); if (currentSceneName.empty()) { currentSceneName = "Main"; } isLoaded = true; return true; } catch (const std::exception& e) { std::cerr << "Failed to load project: " << e.what() << std::endl; return false; } } void Project::saveProjectFile() const { std::ofstream file(projectPath / "project.modu"); file << "name=" << name << "\n"; file << "lastScene=" << currentSceneName << "\n"; file.close(); } std::vector Project::getSceneList() const { std::vector scenes; try { for (const auto& entry : fs::directory_iterator(scenesPath)) { if (entry.path().extension() == ".scene") { scenes.push_back(entry.path().stem().string()); } } } catch (...) {} return scenes; } fs::path Project::getSceneFilePath(const std::string& sceneName) const { return scenesPath / (sceneName + ".scene"); } // ProjectManager implementation ProjectManager::ProjectManager() { #ifdef _WIN32 const char* appdata = std::getenv("APPDATA"); if (appdata) { appDataPath = fs::path(appdata) / ".Modularity"; } else { appDataPath = fs::current_path() / "AppData"; } #else const char* home = std::getenv("HOME"); if (home) { appDataPath = fs::path(home) / ".Modularity"; } else { appDataPath = fs::current_path() / ".Modularity"; } #endif fs::create_directories(appDataPath); loadRecentProjects(); std::string defaultPath = (fs::current_path() / "Projects").string(); strncpy(newProjectLocation, defaultPath.c_str(), sizeof(newProjectLocation) - 1); } void ProjectManager::loadRecentProjects() { recentProjects.clear(); fs::path recentFile = appDataPath / "recent_projects.txt"; std::cerr << "[DEBUG] Loading recent projects from: " << recentFile << std::endl; if (!fs::exists(recentFile)) { std::cerr << "[DEBUG] Recent projects file does not exist" << std::endl; return; } std::ifstream file(recentFile); std::string line; int lineNum = 0; while (std::getline(file, line)) { lineNum++; if (line.empty()) continue; line.erase(0, line.find_first_not_of(" \t\r\n")); line.erase(line.find_last_not_of(" \t\r\n") + 1); if (line.empty()) continue; RecentProject rp; size_t pos1 = line.find('|'); size_t pos2 = line.find('|', pos1 + 1); if (pos1 != std::string::npos && pos2 != std::string::npos) { rp.name = line.substr(0, pos1); rp.path = line.substr(pos1 + 1, pos2 - pos1 - 1); rp.lastOpened = line.substr(pos2 + 1); rp.path.erase(0, rp.path.find_first_not_of(" \t\r\n")); rp.path.erase(rp.path.find_last_not_of(" \t\r\n") + 1); std::cerr << "[DEBUG] Line " << lineNum << ": name='" << rp.name << "' path='" << rp.path << "' exists=" << fs::exists(rp.path) << std::endl; if (fs::exists(rp.path)) { recentProjects.push_back(rp); } else { std::cerr << "[DEBUG] Project path does not exist, skipping: " << rp.path << std::endl; } } else { std::cerr << "[DEBUG] Line " << lineNum << " malformed: " << line << std::endl; } } file.close(); std::cerr << "[DEBUG] Loaded " << recentProjects.size() << " recent projects" << std::endl; } void ProjectManager::saveRecentProjects() { fs::path recentFile = appDataPath / "recent_projects.txt"; std::cerr << "[DEBUG] Saving recent projects to: " << recentFile << std::endl; std::ofstream file(recentFile); for (const auto& rp : recentProjects) { std::string absolutePath = rp.path; try { if (fs::exists(rp.path)) { absolutePath = fs::canonical(rp.path).string(); } } catch (...) { // Keep original path if canonical fails } file << rp.name << "|" << absolutePath << "|" << rp.lastOpened << "\n"; std::cerr << "[DEBUG] Saved: " << rp.name << " -> " << absolutePath << std::endl; } file.close(); } void ProjectManager::addToRecentProjects(const std::string& name, const std::string& path) { std::string absolutePath = path; try { if (fs::exists(path)) { absolutePath = fs::canonical(path).string(); } else { // For new projects, the file might not exist yet - use absolute() absolutePath = fs::absolute(path).string(); } } catch (...) { // Keep original path if conversion fails } std::cerr << "[DEBUG] Adding to recent: " << name << " -> " << absolutePath << std::endl; recentProjects.erase( std::remove_if(recentProjects.begin(), recentProjects.end(), [&absolutePath](const RecentProject& rp) { return rp.path == absolutePath; }), recentProjects.end() ); std::time_t now = std::time(nullptr); char timeStr[64]; std::strftime(timeStr, sizeof(timeStr), "%Y-%m-%d %H:%M", std::localtime(&now)); RecentProject rp; rp.name = name; rp.path = absolutePath; // Use absolute path rp.lastOpened = timeStr; recentProjects.insert(recentProjects.begin(), rp); saveRecentProjects(); } bool ProjectManager::loadProject(const std::string& path) { if (currentProject.load(path)) { addToRecentProjects(currentProject.name, path); return true; } errorMessage = "Failed to load project file"; return false; } // SceneSerializer implementation bool SceneSerializer::saveScene(const fs::path& filePath, const std::vector& objects, int nextId) { try { std::ofstream file(filePath); if (!file.is_open()) return false; file << "# Scene File\n"; file << "version=7\n"; file << "nextId=" << nextId << "\n"; file << "objectCount=" << objects.size() << "\n"; file << "\n"; for (const auto& obj : objects) { file << "[Object]\n"; file << "id=" << obj.id << "\n"; file << "name=" << obj.name << "\n"; file << "type=" << static_cast(obj.type) << "\n"; file << "parentId=" << obj.parentId << "\n"; file << "position=" << obj.position.x << "," << obj.position.y << "," << obj.position.z << "\n"; file << "rotation=" << obj.rotation.x << "," << obj.rotation.y << "," << obj.rotation.z << "\n"; file << "scale=" << obj.scale.x << "," << obj.scale.y << "," << obj.scale.z << "\n"; file << "hasRigidbody=" << (obj.hasRigidbody ? 1 : 0) << "\n"; if (obj.hasRigidbody) { file << "rbEnabled=" << (obj.rigidbody.enabled ? 1 : 0) << "\n"; file << "rbMass=" << obj.rigidbody.mass << "\n"; file << "rbUseGravity=" << (obj.rigidbody.useGravity ? 1 : 0) << "\n"; file << "rbKinematic=" << (obj.rigidbody.isKinematic ? 1 : 0) << "\n"; file << "rbLinearDamping=" << obj.rigidbody.linearDamping << "\n"; file << "rbAngularDamping=" << obj.rigidbody.angularDamping << "\n"; } file << "hasCollider=" << (obj.hasCollider ? 1 : 0) << "\n"; if (obj.hasCollider) { file << "colliderEnabled=" << (obj.collider.enabled ? 1 : 0) << "\n"; file << "colliderType=" << static_cast(obj.collider.type) << "\n"; file << "colliderBox=" << obj.collider.boxSize.x << "," << obj.collider.boxSize.y << "," << obj.collider.boxSize.z << "\n"; file << "colliderConvex=" << (obj.collider.convex ? 1 : 0) << "\n"; } file << "hasPlayerController=" << (obj.hasPlayerController ? 1 : 0) << "\n"; if (obj.hasPlayerController) { file << "pcEnabled=" << (obj.playerController.enabled ? 1 : 0) << "\n"; file << "pcMoveSpeed=" << obj.playerController.moveSpeed << "\n"; file << "pcLookSensitivity=" << obj.playerController.lookSensitivity << "\n"; file << "pcHeight=" << obj.playerController.height << "\n"; file << "pcRadius=" << obj.playerController.radius << "\n"; file << "pcJumpStrength=" << obj.playerController.jumpStrength << "\n"; } file << "materialColor=" << obj.material.color.r << "," << obj.material.color.g << "," << obj.material.color.b << "\n"; file << "materialAmbient=" << obj.material.ambientStrength << "\n"; file << "materialSpecular=" << obj.material.specularStrength << "\n"; file << "materialShininess=" << obj.material.shininess << "\n"; file << "materialTextureMix=" << obj.material.textureMix << "\n"; file << "materialPath=" << obj.materialPath << "\n"; 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 << "additionalMaterialCount=" << obj.additionalMaterialPaths.size() << "\n"; for (size_t mi = 0; mi < obj.additionalMaterialPaths.size(); ++mi) { file << "additionalMaterial" << mi << "=" << obj.additionalMaterialPaths[mi] << "\n"; } file << "scripts=" << obj.scripts.size() << "\n"; for (size_t si = 0; si < obj.scripts.size(); ++si) { const auto& sc = obj.scripts[si]; file << "script" << si << "_path=" << sc.path << "\n"; file << "script" << si << "_settings=" << sc.settings.size() << "\n"; for (size_t k = 0; k < sc.settings.size(); ++k) { file << "script" << si << "_setting" << k << "=" << sc.settings[k].key << ":" << sc.settings[k].value << "\n"; } } file << "lightColor=" << obj.light.color.r << "," << obj.light.color.g << "," << obj.light.color.b << "\n"; file << "lightIntensity=" << obj.light.intensity << "\n"; file << "lightRange=" << obj.light.range << "\n"; file << "lightInner=" << obj.light.innerAngle << "\n"; file << "lightOuter=" << obj.light.outerAngle << "\n"; file << "lightSize=" << obj.light.size.x << "," << obj.light.size.y << "\n"; file << "lightEnabled=" << (obj.light.enabled ? 1 : 0) << "\n"; file << "cameraType=" << static_cast(obj.camera.type) << "\n"; file << "cameraFov=" << obj.camera.fov << "\n"; file << "cameraNear=" << obj.camera.nearClip << "\n"; file << "cameraFar=" << obj.camera.farClip << "\n"; if (obj.type == ObjectType::PostFXNode) { file << "postEnabled=" << (obj.postFx.enabled ? 1 : 0) << "\n"; file << "postBloomEnabled=" << (obj.postFx.bloomEnabled ? 1 : 0) << "\n"; file << "postBloomThreshold=" << obj.postFx.bloomThreshold << "\n"; file << "postBloomIntensity=" << obj.postFx.bloomIntensity << "\n"; file << "postBloomRadius=" << obj.postFx.bloomRadius << "\n"; file << "postColorAdjustEnabled=" << (obj.postFx.colorAdjustEnabled ? 1 : 0) << "\n"; file << "postExposure=" << obj.postFx.exposure << "\n"; file << "postContrast=" << obj.postFx.contrast << "\n"; file << "postSaturation=" << obj.postFx.saturation << "\n"; file << "postColorFilter=" << obj.postFx.colorFilter.r << "," << obj.postFx.colorFilter.g << "," << obj.postFx.colorFilter.b << "\n"; file << "postMotionBlurEnabled=" << (obj.postFx.motionBlurEnabled ? 1 : 0) << "\n"; file << "postMotionBlurStrength=" << obj.postFx.motionBlurStrength << "\n"; file << "postVignetteEnabled=" << (obj.postFx.vignetteEnabled ? 1 : 0) << "\n"; file << "postVignetteIntensity=" << obj.postFx.vignetteIntensity << "\n"; file << "postVignetteSmoothness=" << obj.postFx.vignetteSmoothness << "\n"; file << "postChromaticEnabled=" << (obj.postFx.chromaticAberrationEnabled ? 1 : 0) << "\n"; file << "postChromaticAmount=" << obj.postFx.chromaticAmount << "\n"; file << "postAOEnabled=" << (obj.postFx.ambientOcclusionEnabled ? 1 : 0) << "\n"; file << "postAORadius=" << obj.postFx.aoRadius << "\n"; file << "postAOStrength=" << obj.postFx.aoStrength << "\n"; } file << "scriptCount=" << obj.scripts.size() << "\n"; for (size_t s = 0; s < obj.scripts.size(); ++s) { const auto& sc = obj.scripts[s]; file << "script" << s << "_path=" << sc.path << "\n"; file << "script" << s << "_settingCount=" << sc.settings.size() << "\n"; for (size_t si = 0; si < sc.settings.size(); ++si) { file << "script" << s << "_setting" << si << "=" << sc.settings[si].key << ":" << sc.settings[si].value << "\n"; } } if ((obj.type == ObjectType::OBJMesh || obj.type == ObjectType::Model) && !obj.meshPath.empty()) { file << "meshPath=" << obj.meshPath << "\n"; } file << "children="; for (size_t i = 0; i < obj.childIds.size(); i++) { if (i > 0) file << ","; file << obj.childIds[i]; } file << "\n\n"; } file.close(); return true; } catch (const std::exception& e) { std::cerr << "Failed to save scene: " << e.what() << std::endl; return false; } } bool SceneSerializer::loadScene(const fs::path& filePath, std::vector& objects, int& nextId) { try { std::ifstream file(filePath); if (!file.is_open()) return false; objects.clear(); std::string line; SceneObject* currentObj = nullptr; while (std::getline(file, line)) { size_t first = line.find_first_not_of(" \t\r\n"); if (first == std::string::npos) { continue; } line.erase(0, first); size_t last = line.find_last_not_of(" \t\r\n"); if (last != std::string::npos) { line.erase(last + 1); } else { continue; } if (line.empty() || line[0] == '#') continue; if (line == "[Object]") { objects.push_back(SceneObject("", ObjectType::Cube, 0)); currentObj = &objects.back(); continue; } size_t eqPos = line.find('='); if (eqPos == std::string::npos) continue; std::string key = line.substr(0, eqPos); std::string value = line.substr(eqPos + 1); if (key == "nextId") { nextId = std::stoi(value); } else if (currentObj) { if (key == "id") { currentObj->id = std::stoi(value); } else if (key == "name") { currentObj->name = value; } else if (key == "type") { currentObj->type = static_cast(std::stoi(value)); if (currentObj->type == ObjectType::DirectionalLight) currentObj->light.type = LightType::Directional; else if (currentObj->type == ObjectType::PointLight) currentObj->light.type = LightType::Point; else if (currentObj->type == ObjectType::SpotLight) currentObj->light.type = LightType::Spot; else if (currentObj->type == ObjectType::AreaLight) currentObj->light.type = LightType::Area; else if (currentObj->type == ObjectType::Camera) { currentObj->camera.type = SceneCameraType::Scene; } } else if (key == "parentId") { currentObj->parentId = std::stoi(value); } else if (key == "position") { sscanf(value.c_str(), "%f,%f,%f", ¤tObj->position.x, ¤tObj->position.y, ¤tObj->position.z); } else if (key == "rotation") { sscanf(value.c_str(), "%f,%f,%f", ¤tObj->rotation.x, ¤tObj->rotation.y, ¤tObj->rotation.z); currentObj->rotation = NormalizeEulerDegrees(currentObj->rotation); } else if (key == "scale") { sscanf(value.c_str(), "%f,%f,%f", ¤tObj->scale.x, ¤tObj->scale.y, ¤tObj->scale.z); } else if (key == "hasRigidbody") { currentObj->hasRigidbody = std::stoi(value) != 0; } else if (key == "rbEnabled") { currentObj->rigidbody.enabled = std::stoi(value) != 0; } else if (key == "rbMass") { currentObj->rigidbody.mass = std::stof(value); } else if (key == "rbUseGravity") { currentObj->rigidbody.useGravity = std::stoi(value) != 0; } else if (key == "rbKinematic") { currentObj->rigidbody.isKinematic = std::stoi(value) != 0; } else if (key == "rbLinearDamping") { currentObj->rigidbody.linearDamping = std::stof(value); } else if (key == "rbAngularDamping") { currentObj->rigidbody.angularDamping = std::stof(value); } else if (key == "hasCollider") { currentObj->hasCollider = std::stoi(value) != 0; } else if (key == "colliderEnabled") { currentObj->collider.enabled = std::stoi(value) != 0; } else if (key == "colliderType") { currentObj->collider.type = static_cast(std::stoi(value)); } else if (key == "colliderBox") { sscanf(value.c_str(), "%f,%f,%f", ¤tObj->collider.boxSize.x, ¤tObj->collider.boxSize.y, ¤tObj->collider.boxSize.z); } else if (key == "colliderConvex") { currentObj->collider.convex = std::stoi(value) != 0; } else if (key == "hasPlayerController") { currentObj->hasPlayerController = std::stoi(value) != 0; } else if (key == "pcEnabled") { currentObj->playerController.enabled = std::stoi(value) != 0; } else if (key == "pcMoveSpeed") { currentObj->playerController.moveSpeed = std::stof(value); } else if (key == "pcLookSensitivity") { currentObj->playerController.lookSensitivity = std::stof(value); } else if (key == "pcHeight") { currentObj->playerController.height = std::stof(value); } else if (key == "pcRadius") { currentObj->playerController.radius = std::stof(value); } else if (key == "pcJumpStrength") { currentObj->playerController.jumpStrength = std::stof(value); } else if (key == "materialColor") { sscanf(value.c_str(), "%f,%f,%f", ¤tObj->material.color.r, ¤tObj->material.color.g, ¤tObj->material.color.b); } else if (key == "materialAmbient") { currentObj->material.ambientStrength = std::stof(value); } else if (key == "materialSpecular") { currentObj->material.specularStrength = std::stof(value); } else if (key == "materialShininess") { currentObj->material.shininess = std::stof(value); } else if (key == "materialTextureMix") { currentObj->material.textureMix = std::stof(value); } else if (key == "materialPath") { currentObj->materialPath = value; } else if (key == "albedoTex") { currentObj->albedoTexturePath = value; } else if (key == "overlayTex") { 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 == "additionalMaterialCount") { int count = std::stoi(value); currentObj->additionalMaterialPaths.resize(std::max(0, count)); } else if (key.rfind("additionalMaterial", 0) == 0) { int idx = std::stoi(key.substr(18)); // length of "additionalMaterial" if (idx >= 0 && idx < (int)currentObj->additionalMaterialPaths.size()) { currentObj->additionalMaterialPaths[idx] = value; } } else if (key == "scripts") { int count = std::stoi(value); currentObj->scripts.resize(std::max(0, count)); } else if (key.rfind("script", 0) == 0) { size_t underscore = key.find('_'); if (underscore != std::string::npos && underscore > 6) { int idx = std::stoi(key.substr(6, underscore - 6)); if (idx >= 0 && idx < (int)currentObj->scripts.size()) { std::string sub = key.substr(underscore + 1); ScriptComponent& sc = currentObj->scripts[idx]; if (sub == "path") { sc.path = value; } else if (sub == "settings" || sub == "settingCount") { int cnt = std::stoi(value); sc.settings.resize(std::max(0, cnt)); } else if (sub.rfind("setting", 0) == 0) { std::string idxStr = sub.substr(7); if (!idxStr.empty() && std::all_of(idxStr.begin(), idxStr.end(), ::isdigit)) { int sIdx = std::stoi(idxStr); if (sIdx >= 0 && sIdx < (int)sc.settings.size()) { size_t sep = value.find(':'); if (sep != std::string::npos) { sc.settings[sIdx].key = value.substr(0, sep); sc.settings[sIdx].value = value.substr(sep + 1); } else { sc.settings[sIdx].value = value; } } } } } } } else if (key == "lightColor") { sscanf(value.c_str(), "%f,%f,%f", ¤tObj->light.color.r, ¤tObj->light.color.g, ¤tObj->light.color.b); } else if (key == "lightIntensity") { currentObj->light.intensity = std::stof(value); } else if (key == "lightRange") { currentObj->light.range = std::stof(value); } else if (key == "lightInner") { currentObj->light.innerAngle = std::stof(value); } else if (key == "lightOuter") { currentObj->light.outerAngle = std::stof(value); } else if (key == "lightSize") { sscanf(value.c_str(), "%f,%f", ¤tObj->light.size.x, ¤tObj->light.size.y); } else if (key == "lightEnabled") { currentObj->light.enabled = (std::stoi(value) != 0); } else if (key == "cameraType") { currentObj->camera.type = static_cast(std::stoi(value)); } else if (key == "cameraFov") { currentObj->camera.fov = std::stof(value); } else if (key == "cameraNear") { currentObj->camera.nearClip = std::stof(value); } else if (key == "cameraFar") { currentObj->camera.farClip = std::stof(value); } else if (key == "postEnabled") { currentObj->postFx.enabled = (std::stoi(value) != 0); } else if (key == "postBloomEnabled") { currentObj->postFx.bloomEnabled = (std::stoi(value) != 0); } else if (key == "postBloomThreshold") { currentObj->postFx.bloomThreshold = std::stof(value); } else if (key == "postBloomIntensity") { currentObj->postFx.bloomIntensity = std::stof(value); } else if (key == "postBloomRadius") { currentObj->postFx.bloomRadius = std::stof(value); } else if (key == "postColorAdjustEnabled") { currentObj->postFx.colorAdjustEnabled = (std::stoi(value) != 0); } else if (key == "postExposure") { currentObj->postFx.exposure = std::stof(value); } else if (key == "postContrast") { currentObj->postFx.contrast = std::stof(value); } else if (key == "postSaturation") { currentObj->postFx.saturation = std::stof(value); } else if (key == "postColorFilter") { sscanf(value.c_str(), "%f,%f,%f", ¤tObj->postFx.colorFilter.r, ¤tObj->postFx.colorFilter.g, ¤tObj->postFx.colorFilter.b); } else if (key == "postMotionBlurEnabled") { currentObj->postFx.motionBlurEnabled = (std::stoi(value) != 0); } else if (key == "postMotionBlurStrength") { currentObj->postFx.motionBlurStrength = std::stof(value); } else if (key == "postVignetteEnabled") { currentObj->postFx.vignetteEnabled = (std::stoi(value) != 0); } else if (key == "postVignetteIntensity") { currentObj->postFx.vignetteIntensity = std::stof(value); } else if (key == "postVignetteSmoothness") { currentObj->postFx.vignetteSmoothness = std::stof(value); } else if (key == "postChromaticEnabled") { currentObj->postFx.chromaticAberrationEnabled = (std::stoi(value) != 0); } else if (key == "postChromaticAmount") { currentObj->postFx.chromaticAmount = std::stof(value); } else if (key == "postAOEnabled") { currentObj->postFx.ambientOcclusionEnabled = (std::stoi(value) != 0); } else if (key == "postAORadius") { currentObj->postFx.aoRadius = std::stof(value); } else if (key == "postAOStrength") { currentObj->postFx.aoStrength = std::stof(value); } else if (key == "scriptCount") { int count = std::stoi(value); currentObj->scripts.resize(std::max(0, count)); } else if (key.rfind("script", 0) == 0) { size_t underscore = key.find('_'); if (underscore != std::string::npos && underscore > 6) { int idx = std::stoi(key.substr(6, underscore - 6)); if (idx >= 0 && idx < (int)currentObj->scripts.size()) { std::string subKey = key.substr(underscore + 1); ScriptComponent& sc = currentObj->scripts[idx]; if (subKey == "path") { sc.path = value; } else if (subKey == "settingCount") { int cnt = std::stoi(value); sc.settings.resize(std::max(0, cnt)); } else if (subKey.rfind("setting", 0) == 0) { int sIdx = std::stoi(subKey.substr(7)); if (sIdx >= 0 && sIdx < (int)sc.settings.size()) { size_t sep = value.find(':'); if (sep != std::string::npos) { sc.settings[sIdx].key = value.substr(0, sep); sc.settings[sIdx].value = value.substr(sep + 1); } else { sc.settings[sIdx].key.clear(); sc.settings[sIdx].value = value; } } } } } } else if (key == "meshPath") { currentObj->meshPath = value; if (!value.empty() && currentObj->type == ObjectType::OBJMesh) { std::string err; currentObj->meshId = g_objLoader.loadOBJ(value, err); } else if (!value.empty() && currentObj->type == ObjectType::Model) { ModelLoadResult result = getModelLoader().loadModel(value); if (result.success) { currentObj->meshId = result.meshIndex; } else { std::cerr << "Failed to load model from scene: " << result.errorMessage << std::endl; currentObj->meshId = -1; } } } else if (key == "children" && !value.empty()) { std::stringstream ss(value); std::string item; while (std::getline(ss, item, ',')) { if (!item.empty()) { currentObj->childIds.push_back(std::stoi(item)); } } } } } file.close(); return true; } catch (const std::exception& e) { std::cerr << "Failed to load scene: " << e.what() << std::endl; return false; } }