#include "GameObjectLoader.h" #include #include #include #include "render/Renderer.h" #include "render/TextureManager.h" #include "utils/Utils.h" #include #include "../Character.h" namespace ZL { void set_Texture(Character& npc, const TextureDataStruct& texture); void set_Texture(Character& npc, const std::string& meshName, const TextureDataStruct& texture); using json = nlohmann::json; // ----------------------------------------------------------------------- // Private helpers // ----------------------------------------------------------------------- json GameObjectLoader::loadJson(const std::string& jsonPath, const std::string& zipPath) { std::string content; try { if (zipPath.empty()) { content = readTextFile(jsonPath); } else { auto buf = readFileFromZIP(jsonPath, zipPath); if (buf.empty()) throw std::runtime_error("empty result from zip"); content.assign(buf.begin(), buf.end()); } } catch (const std::exception& e) { throw std::runtime_error("Failed to read " + jsonPath + ": " + e.what()); } try { return json::parse(content); } catch (const std::exception& e) { throw std::runtime_error("JSON parse error in " + jsonPath + ": " + e.what()); } } static GameObjectData parseGameObjectData(const json& item) { GameObjectData data; data.name = item.value("name", "Unknown"); data.texturePath = item.value("texturePath", ""); data.textureDarkandsPath = item.value("textureDarkandsPath", ""); data.meshPath = item.value("meshPath", ""); data.rotationX = item.value("rotationX", 0.0f); data.rotationY = item.value("rotationY", 0.0f); data.rotationZ = item.value("rotationZ", 0.0f); data.positionX = item.value("positionX", 0.0f); data.positionY = item.value("positionY", 0.0f); data.positionZ = item.value("positionZ", 0.0f); data.scale = item.value("scale", 1.0f); return data; } std::vector GameObjectLoader::loadFromJson(const std::string& jsonPath, const std::string& zipPath) { std::vector objects; json j = loadJson(jsonPath, zipPath); if (!j.contains("objects") || !j["objects"].is_array()) { std::cerr << "Warning: 'objects' array not found in " << jsonPath << std::endl; return objects; } for (const auto& item : j["objects"]) { GameObjectData data = parseGameObjectData(item); if (!data.meshPath.empty()) objects.push_back(std::move(data)); } std::cout << "Loaded " << objects.size() << " static objects from " << jsonPath << std::endl; return objects; } std::vector GameObjectLoader::loadInteractiveFromJson(const std::string& jsonPath, const std::string& zipPath) { std::vector objects; json j = loadJson(jsonPath, zipPath); if (!j.contains("objects") || !j["objects"].is_array()) { std::cerr << "Warning: 'objects' array not found in " << jsonPath << std::endl; return objects; } for (const auto& item : j["objects"]) { InteractiveObjectData data; data.base = parseGameObjectData(item); data.interactionRadius = item.value("interactionRadius", 2.0f); data.approachRadius = item.value("approachRadius", data.interactionRadius); data.castShadow = item.value("castShadow", true); data.castShadowNight = item.value("castShadowNight", true); data.isActive = item.value("isActive", true); data.activateFunctionName = item.value("activateFunction", ""); data.pivotX = item.value("pivotX", 0.0f); data.pivotY = item.value("pivotY", 0.0f); data.pivotZ = item.value("pivotZ", 0.0f); data.boundsMinX = item.value("boundsMinX", 0.0f); data.boundsMinY = item.value("boundsMinY", 0.0f); data.boundsMinZ = item.value("boundsMinZ", 0.0f); data.boundsMaxX = item.value("boundsMaxX", 0.0f); data.boundsMaxY = item.value("boundsMaxY", 0.0f); data.boundsMaxZ = item.value("boundsMaxZ", 0.0f); if (item.contains("interactionPositionX")) { data.hasInteractionPosition = true; data.interactionPositionX = item.value("interactionPositionX", 0.0f); data.interactionPositionY = item.value("interactionPositionY", 0.0f); data.interactionPositionZ = item.value("interactionPositionZ", 0.0f); } if (!data.base.meshPath.empty()) objects.push_back(std::move(data)); } std::cout << "Loaded " << objects.size() << " interactive objects from " << jsonPath << std::endl; return objects; } LoadedGameObject GameObjectLoader::buildLoadedObject(const GameObjectData& data, Renderer& renderer, const std::string& zipPath) { LoadedGameObject obj; obj.name = data.name; obj.texture = renderer.textureManager.LoadFromPng(data.texturePath, zipPath); if (!data.textureDarkandsPath.empty()) obj.textureDarklands = renderer.textureManager.LoadFromPng(data.textureDarkandsPath, zipPath); if (data.meshPath.size() > 4 && data.meshPath.compare(data.meshPath.size() - 4, 4, ".bin") == 0) obj.mesh.data = LoadModelFromBinFile(data.meshPath, zipPath); else obj.mesh.data = LoadFromTextFile02(data.meshPath, zipPath); constexpr float kDeg2Rad = static_cast(M_PI) / 180.0f; Eigen::Quaternionf rot = Eigen::Quaternionf::Identity(); if (data.rotationX != 0.0f) rot = Eigen::Quaternionf(Eigen::AngleAxisf(data.rotationX * kDeg2Rad, Eigen::Vector3f::UnitX())) * rot; if (data.rotationY != 0.0f) rot = Eigen::Quaternionf(Eigen::AngleAxisf(data.rotationY * kDeg2Rad, Eigen::Vector3f::UnitY())) * rot; if (data.rotationZ != 0.0f) rot = Eigen::Quaternionf(Eigen::AngleAxisf(data.rotationZ * kDeg2Rad, Eigen::Vector3f::UnitZ())) * rot; obj.mesh.data.RotateByMatrix(rot.toRotationMatrix()); if (data.scale != 1.0f) obj.mesh.data.Scale(data.scale); return obj; } // ----------------------------------------------------------------------- // Public API // ----------------------------------------------------------------------- std::unordered_map GameObjectLoader::loadAndCreateGameObjects( const std::string& jsonPath, Renderer& renderer, const std::string& zipPath) { std::unordered_map gameObjects; for (const auto& data : loadFromJson(jsonPath, zipPath)) { std::cout << "Loading game object: " << data.name << std::endl; try { LoadedGameObject obj = buildLoadedObject(data, renderer, zipPath); if (data.positionX != 0.0f || data.positionY != 0.0f || data.positionZ != 0.0f) obj.mesh.data.Move({ data.positionX, data.positionY, data.positionZ }); obj.mesh.RefreshVBO(); gameObjects[data.name] = std::move(obj); std::cout << "Successfully loaded: " << data.name << std::endl; } catch (const std::exception& e) { std::cerr << "GameObjectLoader: Failed to load '" << data.name << "': " << e.what() << std::endl; } } std::cout << "Total game objects loaded: " << gameObjects.size() << std::endl; return gameObjects; } std::vector GameObjectLoader::loadAndCreateInteractiveObjects( const std::string& jsonPath, Renderer& renderer, const std::string& zipPath) { std::vector interactiveObjects; if (jsonPath.empty()) return interactiveObjects; for (const auto& data : loadInteractiveFromJson(jsonPath, zipPath)) { std::cout << "Loading interactive object: " << data.base.name << std::endl; try { InteractiveObject intObj; intObj.loadedObject = buildLoadedObject(data.base, renderer, zipPath); intObj.interactionRadius = data.interactionRadius; intObj.approachRadius = data.approachRadius; intObj.castShadow = data.castShadow; intObj.castShadowNight = data.castShadowNight; intObj.isActive = data.isActive; intObj.activateFunctionName = data.activateFunctionName; intObj.pivot = Eigen::Vector3f(data.pivotX, data.pivotY, data.pivotZ); intObj.boundsMin = Eigen::Vector3f(data.boundsMinX, data.boundsMinY, data.boundsMinZ); intObj.boundsMax = Eigen::Vector3f(data.boundsMaxX, data.boundsMaxY, data.boundsMaxZ); intObj.hasInteractionPosition = data.hasInteractionPosition; if (data.hasInteractionPosition) intObj.interactionPosition = Eigen::Vector3f(data.interactionPositionX, data.interactionPositionY, data.interactionPositionZ); // Store source data needed for serialization. intObj.loadedObject.texturePath = data.base.texturePath; intObj.loadedObject.textureDarkandsPath = data.base.textureDarkandsPath; intObj.loadedObject.meshPath = data.base.meshPath; intObj.loadedObject.meshRotationX = data.base.rotationX; intObj.loadedObject.meshRotationY = data.base.rotationY; intObj.loadedObject.meshRotationZ = data.base.rotationZ; intObj.loadedObject.meshScale = data.base.scale; intObj.jsonPositionX = data.base.positionX; intObj.jsonPositionY = data.base.positionY; intObj.jsonPositionZ = data.base.positionZ; intObj.loadedObject.mesh.RefreshVBO(); // Center mesh at origin; store corrected world position separately. if (!intObj.loadedObject.mesh.data.PositionData.empty()) { Eigen::Vector3f meshMin = intObj.loadedObject.mesh.data.PositionData[0]; Eigen::Vector3f meshMax = intObj.loadedObject.mesh.data.PositionData[0]; for (const auto& v : intObj.loadedObject.mesh.data.PositionData) { meshMin = meshMin.cwiseMin(v); meshMax = meshMax.cwiseMax(v); } Eigen::Vector3f meshCenter = (meshMin + meshMax) * 0.5f; intObj.loadedObject.mesh.data.Move(-meshCenter); intObj.loadedObject.mesh.RefreshVBO(); intObj.position = Eigen::Vector3f(data.base.positionX, data.base.positionY, data.base.positionZ) + meshCenter; } else { intObj.position = Eigen::Vector3f(data.base.positionX, data.base.positionY, data.base.positionZ); } interactiveObjects.push_back(std::move(intObj)); if (!data.activateFunctionName.empty()) std::cout << "Successfully loaded interactive: " << data.base.name << " (function: " << data.activateFunctionName << ")" << std::endl; else std::cout << "Successfully loaded interactive: " << data.base.name << std::endl; } catch (const std::exception& e) { std::cerr << "GameObjectLoader: Failed to load interactive '" << data.base.name << "': " << e.what() << std::endl; } } std::cout << "Total interactive objects loaded: " << interactiveObjects.size() << std::endl; return interactiveObjects; } std::vector GameObjectLoader::loadNpcsFromJson(const std::string& jsonPath, const std::string& zipPath) { std::vector npcs; json j = loadJson(jsonPath, zipPath); if (!j.contains("npcs") || !j["npcs"].is_array()) { std::cerr << "Warning: 'npcs' array not found in " << jsonPath << std::endl; return npcs; } try { for (const auto& item : j["npcs"]) { NpcData data; data.id = item.value("id", "npc_unknown"); data.name = item.value("name", "Unknown NPC"); data.texturePath = item.value("texturePath", ""); if (item.contains("meshTextures") && item["meshTextures"].is_object()) { for (auto it = item["meshTextures"].begin(); it != item["meshTextures"].end(); ++it) { if (it.value().is_string()) data.meshTextures[it.key()] = it.value().get(); } } data.interactionRadius = item.value("interactionRadius", 0.0f); data.animationIdlePath = item.value("animationIdlePath", ""); data.animationWalkPath = item.value("animationWalkPath", ""); data.animationActionIdlePath = item.value("animationActionIdlePath", ""); data.animationActionAttackPath = item.value("animationActionAttackPath", ""); data.animationStandToActionPath = item.value("animationStandToActionPath", ""); data.animationActionToStandPath = item.value("animationActionToStandPath", ""); data.animationActionToDeathPath = item.value("animationActionToDeathPath", ""); data.animationDeathIdlePath = item.value("animationDeathIdlePath", ""); data.positionX = item.value("positionX", 0.0f); data.positionY = item.value("positionY", 0.0f); data.positionZ = item.value("positionZ", 0.0f); data.walkSpeed = item.value("walkSpeed", 1.5f); data.rotationSpeed = item.value("rotationSpeed", 8.0f); data.modelScale = item.value("modelScale", 0.01f); data.modelCorrectionRotX = item.value("modelCorrectionRotX", 0.0f) * static_cast(M_PI) / 180.0f; data.modelCorrectionRotY = item.value("modelCorrectionRotY", 0.0f) * static_cast(M_PI) / 180.0f; data.modelCorrectionRotZ = item.value("modelCorrectionRotZ", 0.0f) * static_cast(M_PI) / 180.0f; data.facingAngle = item.value("facingAngle", 0.0f) * static_cast(M_PI) / 180.0f; data.hp = item.value("hp", 100.0f); data.canAttack = item.value("canAttack", false); data.enabled = item.value("enabled", true); npcs.push_back(std::move(data)); } std::cout << "Successfully loaded " << npcs.size() << " NPCs from " << jsonPath << std::endl; } catch (const std::exception& e) { std::cerr << "Error loading NPCs from JSON: " << e.what() << std::endl; } return npcs; } std::vector> GameObjectLoader::loadAndCreate_Npcs( const std::string& jsonPath, const std::string& zipPath) { std::vector> npcs; for (const auto& npcData : loadNpcsFromJson(jsonPath, zipPath)) { std::cout << "Loading NPC: " << npcData.name << std::endl; auto npc = std::make_unique(); try { if (npcData.animationIdlePath.substr(npcData.animationIdlePath.size() - 5) == ".anim") npc->loadBinaryAnimation(AnimationState::STAND, npcData.animationIdlePath, zipPath); else npc->loadAnimation(AnimationState::STAND, npcData.animationIdlePath, zipPath); std::cout << " Loaded IDLE animation: " << npcData.animationIdlePath << std::endl; } catch (const std::exception& e) { std::cerr << "GameObjectLoader: Failed to load IDLE animation for '" << npcData.name << "': " << e.what() << std::endl; continue; } try { if (npcData.animationWalkPath.substr(npcData.animationWalkPath.size() - 5) == ".anim") npc->loadBinaryAnimation(AnimationState::WALK, npcData.animationWalkPath, zipPath); else npc->loadAnimation(AnimationState::WALK, npcData.animationWalkPath, zipPath); std::cout << " Loaded WALK animation: " << npcData.animationWalkPath << std::endl; } catch (const std::exception& e) { std::cerr << "GameObjectLoader: Failed to load WALK animation for '" << npcData.name << "': " << e.what() << std::endl; continue; } auto loadOptionalAnim = [&](AnimationState state, const std::string& path, const char* label) { if (path.empty()) return; try { if (path.size() >= 5 && path.substr(path.size() - 5) == ".anim") npc->loadBinaryAnimation(state, path, zipPath); else npc->loadAnimation(state, path, zipPath); std::cout << " Loaded " << label << " animation: " << path << std::endl; } catch (const std::exception& e) { std::cerr << "GameObjectLoader: Failed to load " << label << " animation for '" << npcData.name << "': " << e.what() << std::endl; } }; loadOptionalAnim(AnimationState::ACTION_IDLE, npcData.animationActionIdlePath, "ACTION_IDLE"); loadOptionalAnim(AnimationState::ACTION_ATTACK, npcData.animationActionAttackPath, "ACTION_ATTACK"); loadOptionalAnim(AnimationState::STAND_TO_ACTION, npcData.animationStandToActionPath, "STAND_TO_ACTION"); loadOptionalAnim(AnimationState::ACTION_TO_STAND, npcData.animationActionToStandPath, "ACTION_TO_STAND"); loadOptionalAnim(AnimationState::ACTION_TO_DEATH, npcData.animationActionToDeathPath, "ACTION_TO_DEATH"); loadOptionalAnim(AnimationState::DEATH_IDLE, npcData.animationDeathIdlePath, "DEATH_IDLE"); try { if (!npcData.meshTextures.empty()) { for (const auto& entry : npcData.meshTextures) { //set_Texture(*npc, entry.first, CreateTextureDataFromPng(entry.second, zipPath.c_str())); npc->setTexture(entry.first, std::make_shared(CreateTextureDataFromPng(entry.second, zipPath.c_str()))); std::cout << " -> mesh '" << entry.first << "'" << std::endl; } } else { //set_Texture(*npc, CreateTextureDataFromPng(npcData.texturePath, zipPath.c_str())); npc->setTexture(std::make_shared(CreateTextureDataFromPng(npcData.texturePath, zipPath.c_str()))); std::cout << " Loaded texture: " << npcData.texturePath << std::endl; } } catch (const std::exception& e) { std::cerr << "GameObjectLoader: Failed to load texture for '" << npcData.name << "': " << e.what() << std::endl; continue; } npc->walkSpeed = npcData.walkSpeed; npc->rotationSpeed = npcData.rotationSpeed; npc->modelScale = npcData.modelScale; npc->position = Eigen::Vector3f(npcData.positionX, npcData.positionY, npcData.positionZ); npc->facingAngle = npcData.facingAngle; npc->setHp(npcData.hp); npc->initialHp = npcData.hp; npc->canAttack = npcData.canAttack; npc->enabled = npcData.enabled; npc->interactionRadius = npcData.interactionRadius; npc->npcId = npcData.id; npc->npcName = npcData.name; Eigen::Quaternionf corrRot = Eigen::Quaternionf::Identity(); if (npcData.modelCorrectionRotX != 0.0f) corrRot = Eigen::Quaternionf(Eigen::AngleAxisf(npcData.modelCorrectionRotX, Eigen::Vector3f::UnitX())) * corrRot; if (npcData.modelCorrectionRotY != 0.0f) corrRot = Eigen::Quaternionf(Eigen::AngleAxisf(npcData.modelCorrectionRotY, Eigen::Vector3f::UnitY())) * corrRot; if (npcData.modelCorrectionRotZ != 0.0f) corrRot = Eigen::Quaternionf(Eigen::AngleAxisf(npcData.modelCorrectionRotZ, Eigen::Vector3f::UnitZ())) * corrRot; npc->modelCorrectionRotation = corrRot; npc->setTarget(npc->position); npcs.push_back(std::move(npc)); std::cout << "Successfully loaded NPC: " << npcData.name << " at (" << npcData.positionX << ", " << npcData.positionY << ", " << npcData.positionZ << ")" << std::endl; } std::cout << "Total NPCs loaded: " << npcs.size() << std::endl; return npcs; } } // namespace ZL