diff --git a/resources/config2/npcs.json b/resources/config2/npcs.json index 0fe2b5e..db92a50 100644 --- a/resources/config2/npcs.json +++ b/resources/config2/npcs.json @@ -3,9 +3,19 @@ { "id": "npc_01_default", "name": "NPC Default Guard", - "texturePath": "resources/w/default_skin001.png", - "animationIdlePath": "resources/w/default_idle002.anim", - "animationWalkPath": "resources/w/default_walk001.anim", + "animationIdlePath": "resources/w/new_anims/gg_stand_idle003.anim", + "animationWalkPath": "resources/w/new_anims/gg_run003.anim", + "meshTextures": { + "Body": "resources/w/new_anims/male_packed0_diffuse.png", + "Bottoms": "resources/w/new_anims/male_packed1_diffuse.png", + "Eyelashes": "resources/w/new_anims/male_packed0_diffuse.png", + "Eyes": "resources/w/new_anims/male_packed0_diffuse.png", + "Eyewear": "resources/w/new_anims/male_packed0_diffuse.png", + "Gloves": "resources/w/new_anims/male_packed1_diffuse.png", + "Hair": "resources/w/new_anims/male_packed0_diffuse.png", + "Shoes": "resources/w/new_anims/male_packed1_diffuse.png", + "Tops": "resources/w/new_anims/male_packed2_diffuse.png" + }, "positionX": 0.0, "positionY": 0.0, "positionZ": -10.0, diff --git a/resources/w/new_anims/male_packed0_diffuse.png b/resources/w/new_anims/male_packed0_diffuse.png new file mode 100644 index 0000000..a191427 --- /dev/null +++ b/resources/w/new_anims/male_packed0_diffuse.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b410dd76cb2e15011916fb6ae954b0d785963311ffa943a82a35c16615a5f538 +size 1328955 diff --git a/resources/w/new_anims/male_packed1_diffuse.png b/resources/w/new_anims/male_packed1_diffuse.png new file mode 100644 index 0000000..c1a3745 --- /dev/null +++ b/resources/w/new_anims/male_packed1_diffuse.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e26cd50c89eb17b7a13932efef959145c3f3403fd3f8e5c12cec35bf7dbe2948 +size 1331360 diff --git a/resources/w/new_anims/male_packed2_diffuse.png b/resources/w/new_anims/male_packed2_diffuse.png new file mode 100644 index 0000000..dbdb1ee --- /dev/null +++ b/resources/w/new_anims/male_packed2_diffuse.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:964eec097fa43f5da8189e09a75eca0c8873624e1d65400bb636dd4c17482b56 +size 731032 diff --git a/src/Character.cpp b/src/Character.cpp index 8c85365..1263e21 100644 --- a/src/Character.cpp +++ b/src/Character.cpp @@ -74,6 +74,27 @@ void Character::setPathPlanner(PathPlanner planner) { pathPlanner = std::move(planner); } +void Character::setTexture(std::shared_ptr texture) +{ + for (auto& animEntry : animations) { + for (const auto& name : animEntry.second.model.meshNamesOrdered) { + meshTextures[name] = texture; + } + } +} + +void Character::setTexture(const std::string& meshName, std::shared_ptr tex) { + meshTextures[meshName] = std::move(tex); +} +/* +void Character::setTexture(std::shared_ptr tex) { + for (auto& animEntry : animations) { + for (const auto& name : animEntry.second.model.meshNamesOrdered) { + meshTextures[name] = tex; + } + } +} +*/ AnimationState Character::resolveActiveState() const { if (animations.count(currentState)) return currentState; @@ -299,7 +320,7 @@ void Character::draw(Renderer& renderer) { AnimationState drawState = resolveActiveState(); auto it = animations.find(drawState); - if (it == animations.end() || !texture) return; + if (it == animations.end()) return; renderer.shaderManager.PushShader(defaultShaderName); renderer.RenderUniform1i(textureUniformName, 0); @@ -315,10 +336,12 @@ void Character::draw(Renderer& renderer) { renderer.RotateMatrix(modelCorrectionRotation.toRotationMatrix()); auto& anim = it->second; - glBindTexture(GL_TEXTURE_2D, texture->getTexID()); for (const auto& name : anim.model.meshNamesOrdered) { auto mit = anim.model.meshes.find(name); if (mit == anim.model.meshes.end()) continue; + auto tit = meshTextures.find(name); + if (tit == meshTextures.end() || !tit->second) continue; + glBindTexture(GL_TEXTURE_2D, tit->second->getTexID()); modelMutable.AssignFrom(mit->second.mesh); modelMutable.RefreshVBO(); renderer.DrawVertexRenderStruct(modelMutable); @@ -335,7 +358,7 @@ void Character::draw(Renderer& renderer) { void Character::drawGpuSkinning(Renderer& renderer) { AnimationState drawState = resolveActiveState(); auto it = animations.find(drawState); - if (it == animations.end() || !texture) + if (it == animations.end()) { return; } @@ -372,9 +395,14 @@ void Character::drawGpuSkinning(Renderer& renderer) { static_cast(anim.gpuSkinningShaderData.skinningMatrices.size()), false, anim.gpuSkinningShaderData.skinningMatrices[0].data()); - glBindTexture(GL_TEXTURE_2D, texture->getTexID()); - - anim.gpuSkinningShaderData.RenderVBO(renderer, anim.model.meshNamesOrdered); + for (const auto& name : anim.model.meshNamesOrdered) { + auto pit = anim.gpuSkinningShaderData.perMesh.find(name); + if (pit == anim.gpuSkinningShaderData.perMesh.end()) continue; + auto tit = meshTextures.find(name); + if (tit == meshTextures.end() || !tit->second) continue; + glBindTexture(GL_TEXTURE_2D, tit->second->getTexID()); + pit->second.RenderVBO(renderer); + } renderer.PopMatrix(); @@ -474,6 +502,8 @@ void Character::drawShadowDepthCpu(Renderer& renderer) { for (const auto& name : anim.model.meshNamesOrdered) { auto mit = anim.model.meshes.find(name); if (mit == anim.model.meshes.end()) continue; + auto tit = meshTextures.find(name); + if (tit == meshTextures.end() || !tit->second) continue; modelMutable.AssignFrom(mit->second.mesh); modelMutable.RefreshVBO(); renderer.DrawVertexRenderStruct(modelMutable); @@ -514,7 +544,13 @@ void Character::drawShadowDepthGpuSkinning(Renderer& renderer) { it->second.gpuSkinningShaderData.skinningMatrices[0].data()); CheckGlError(__FILE__, __LINE__); - it->second.gpuSkinningShaderData.RenderVBO(renderer, it->second.model.meshNamesOrdered); + for (const auto& name : it->second.model.meshNamesOrdered) { + auto pit = it->second.gpuSkinningShaderData.perMesh.find(name); + if (pit == it->second.gpuSkinningShaderData.perMesh.end()) continue; + auto tit = meshTextures.find(name); + if (tit == meshTextures.end() || !tit->second) continue; + pit->second.RenderVBO(renderer); + } CheckGlError(__FILE__, __LINE__); renderer.PopMatrix(); @@ -538,7 +574,7 @@ void Character::drawWithShadow(Renderer& renderer, const Eigen::Matrix4f& lightF void Character::drawCpuWithShadow(Renderer& renderer, const Eigen::Matrix4f& lightFromCamera, GLuint shadowMapTex, const Eigen::Vector3f& lightDirCamera) { AnimationState drawState = resolveActiveState(); auto it = animations.find(drawState); - if (it == animations.end() || !texture) return; + if (it == animations.end()) return; static const std::string shadowShader = "default_shadow"; @@ -563,10 +599,12 @@ void Character::drawCpuWithShadow(Renderer& renderer, const Eigen::Matrix4f& lig renderer.RotateMatrix(modelCorrectionRotation.toRotationMatrix()); auto& anim = it->second; - glBindTexture(GL_TEXTURE_2D, texture->getTexID()); for (const auto& name : anim.model.meshNamesOrdered) { auto mit = anim.model.meshes.find(name); if (mit == anim.model.meshes.end()) continue; + auto tit = meshTextures.find(name); + if (tit == meshTextures.end() || !tit->second) continue; + glBindTexture(GL_TEXTURE_2D, tit->second->getTexID()); modelMutable.AssignFrom(mit->second.mesh); modelMutable.RefreshVBO(); renderer.DrawVertexRenderStruct(modelMutable); @@ -586,7 +624,7 @@ void Character::drawCpuWithShadow(Renderer& renderer, const Eigen::Matrix4f& lig void Character::drawGpuSkinningWithShadow(Renderer& renderer, const Eigen::Matrix4f& lightFromCamera, GLuint shadowMapTex, const Eigen::Vector3f& lightDirCamera) { AnimationState drawState = resolveActiveState(); auto it = animations.find(drawState); - if (it == animations.end() || !texture) return; + if (it == animations.end()) return; CheckGlError(__FILE__, __LINE__); @@ -627,10 +665,15 @@ void Character::drawGpuSkinningWithShadow(Renderer& renderer, const Eigen::Matri it->second.gpuSkinningShaderData.skinningMatrices[0].data()); CheckGlError(__FILE__, __LINE__); - glBindTexture(GL_TEXTURE_2D, texture->getTexID()); - CheckGlError(__FILE__, __LINE__); - it->second.gpuSkinningShaderData.RenderVBO(renderer, it->second.model.meshNamesOrdered); + for (const auto& name : it->second.model.meshNamesOrdered) { + auto pit = it->second.gpuSkinningShaderData.perMesh.find(name); + if (pit == it->second.gpuSkinningShaderData.perMesh.end()) continue; + auto tit = meshTextures.find(name); + if (tit == meshTextures.end() || !tit->second) continue; + glBindTexture(GL_TEXTURE_2D, tit->second->getTexID()); + pit->second.RenderVBO(renderer); + } renderer.PopMatrix(); diff --git a/src/Character.h b/src/Character.h index 7c3bd24..de481f3 100644 --- a/src/Character.h +++ b/src/Character.h @@ -9,6 +9,7 @@ #include #include #include +#include namespace ZL { @@ -30,9 +31,11 @@ public: void loadAnimation(AnimationState state, const std::string& filename, const std::string& zipFile = ""); void loadBinaryAnimation(AnimationState state, const std::string& filename, const std::string& zipFile = ""); - void setTexture(std::shared_ptr texture) { - this->texture = texture; - } + // Assigns the given texture to a specific mesh (by name, as defined in the animation file). + void setTexture(const std::string& meshName, std::shared_ptr texture); + // Assigns the given texture to every mesh in every loaded animation. + // Call AFTER loading animations so the mesh names are known. + void setTexture(std::shared_ptr texture); void update(int64_t deltaMs); // onArrived is called once when the character reaches this target. // From Python you can chain further commands (walk, wait, trigger others). @@ -87,7 +90,7 @@ private: std::map animations; VertexRenderStruct modelMutable; - std::shared_ptr texture; + std::unordered_map> meshTextures; Eigen::Vector3f walkTarget = Eigen::Vector3f(0.f, 0.f, 0.f); Eigen::Vector3f requestedWalkTarget = Eigen::Vector3f(0.f, 0.f, 0.f); diff --git a/src/Game.cpp b/src/Game.cpp index 9ebaeeb..a7675f0 100644 --- a/src/Game.cpp +++ b/src/Game.cpp @@ -24,6 +24,17 @@ namespace ZL { + void set_Texture(Character& npc, TextureDataStruct& texture) + { + auto tt = std::make_shared(texture); + npc.setTexture(tt); + } + void set_Texture(Character& npc, const std::string& meshName, TextureDataStruct& texture) + { + auto tt = std::make_shared(texture); + npc.setTexture(meshName, tt); + } + #ifdef EMSCRIPTEN const char* CONST_ZIP_FILE = "resources.zip"; #else diff --git a/src/Location.cpp b/src/Location.cpp index 434e6e3..0b0a59e 100644 --- a/src/Location.cpp +++ b/src/Location.cpp @@ -11,7 +11,7 @@ #include #include #include "GameConstants.h" - +#include "Character.h" namespace ZL @@ -91,7 +91,7 @@ namespace ZL std::cout << "Load resurces step 9" << std::endl; // Load NPCs from JSON - npcs = GameObjectLoader::loadAndCreateNpcs("resources/config2/npcs.json", CONST_ZIP_FILE); + npcs = GameObjectLoader::loadAndCreate_Npcs("resources/config2/npcs.json", CONST_ZIP_FILE); auto ghostTexture = std::make_shared(CreateTextureDataFromPng("resources/w/ghost_skin001.png", CONST_ZIP_FILE)); diff --git a/src/items/GameObjectLoader.cpp b/src/items/GameObjectLoader.cpp index c7cf2a9..f5b2238 100644 --- a/src/items/GameObjectLoader.cpp +++ b/src/items/GameObjectLoader.cpp @@ -6,15 +6,18 @@ #include "render/TextureManager.h" #include "utils/Utils.h" #include - +#include "../Character.h" namespace ZL { + void set_Texture(Character& npc, TextureDataStruct& texture); + void set_Texture(Character& npc, const std::string& meshName, TextureDataStruct& texture); + using json = nlohmann::json; std::vector GameObjectLoader::loadFromJson(const std::string& jsonPath, const std::string& zipPath) { std::vector objects; - + std::string content; try { if (zipPath.empty()) { @@ -34,6 +37,7 @@ namespace ZL { throw std::runtime_error("Failed to load UI file: " + jsonPath); } + json j; try { j = json::parse(content); @@ -340,6 +344,13 @@ namespace ZL { 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", ""); @@ -378,7 +389,7 @@ namespace ZL { return npcs; } - std::vector> GameObjectLoader::loadAndCreateNpcs( + std::vector> GameObjectLoader::loadAndCreate_Npcs( const std::string& jsonPath, const std::string& zipPath) { @@ -424,11 +435,31 @@ namespace ZL { continue; } - // Load texture + // Load textures: per-mesh map takes precedence; fall back to single texturePath. try { - auto texture = std::make_shared(CreateTextureDataFromPng(npcData.texturePath, zipPath.c_str())); - npc->setTexture(texture); - std::cout << " Loaded texture: " << npcData.texturePath << std::endl; + if (!npcData.meshTextures.empty()) { + std::unordered_map> cache; + for (const auto& entry : npcData.meshTextures) { + const std::string& meshName = entry.first; + const std::string& path = entry.second; + /*auto cit = cache.find(path); + if (cit == cache.end()) { + auto tex = std::make_shared(CreateTextureDataFromPng(path, zipPath.c_str())); + cit = cache.emplace(path, std::move(tex)).first; + std::cout << " Loaded texture: " << path << std::endl; + }*/ + //npc->setTexture(meshName, std::make_shared(CreateTextureDataFromPng(path, zipPath.c_str()))); + set_Texture(*npc, meshName, CreateTextureDataFromPng(path, zipPath.c_str())); + //std::cout << xxa(*npc, CreateTextureDataFromPng(path, zipPath.c_str())) << std::endl; + std::cout << " -> mesh '" << meshName << "'" << std::endl; + } + } + else { + //auto texture = std::make_shared(CreateTextureDataFromPng(npcData.texturePath, zipPath.c_str())); + set_Texture(*npc, CreateTextureDataFromPng(npcData.texturePath, zipPath.c_str())); + //std::cout << xxa(*npc, CreateTextureDataFromPng(npcData.texturePath, zipPath.c_str())) << std::endl; + 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; diff --git a/src/items/GameObjectLoader.h b/src/items/GameObjectLoader.h index 72be42d..02aae87 100644 --- a/src/items/GameObjectLoader.h +++ b/src/items/GameObjectLoader.h @@ -3,15 +3,18 @@ #include #include #include +#include #include "external/nlohmann/json.hpp" #include "TextModel.h" #include "InteractiveObject.h" -#include "Character.h" -namespace ZL { +//#include "Character.h" +//#include "render/TextureManager.h" - struct Texture; - struct VertexRenderStruct; - class Renderer; +namespace ZL { + + //struct Texture; + //struct VertexRenderStruct; + //class Renderer; class Character; struct GameObjectData { @@ -45,6 +48,7 @@ namespace ZL { std::string id; std::string name; std::string texturePath; + std::map meshTextures; std::string animationIdlePath; std::string animationWalkPath; float positionX = 0.0f; @@ -82,7 +86,7 @@ namespace ZL { const std::string& zipPath = "" ); - static std::vector> loadAndCreateNpcs( + static std::vector> loadAndCreate_Npcs( const std::string& jsonPath, const std::string& zipPath = "" ); @@ -90,4 +94,5 @@ namespace ZL { static std::vector loadNpcsFromJson(const std::string& jsonPath, const std::string& zipPath = ""); }; -} // namespace ZL \ No newline at end of file +} // namespace ZL +