diff --git a/Game.cpp b/Game.cpp index ec1fa60..961d923 100644 --- a/Game.cpp +++ b/Game.cpp @@ -371,14 +371,22 @@ public: } void locateResources() override { - Ogre::ResourceGroupManager::getSingleton().createResourceGroup( + Ogre::ResourceGroupManager::getSingleton().createResourceGroup( + "Characters", true); + Ogre::ResourceGroupManager::getSingleton().createResourceGroup( "Water", true); Ogre::ResourceGroupManager::getSingleton().createResourceGroup( "LuaScripts", false); Ogre::ResourceGroupManager::getSingleton().addResourceLocation( "./lua-scripts", "FileSystem", "LuaScripts", true, true); - OgreBites::ApplicationContext::locateResources(); + Ogre::ResourceGroupManager::getSingleton().addResourceLocation( + "./characters/male", "FileSystem", "Characters", false, + true); + Ogre::ResourceGroupManager::getSingleton().addResourceLocation( + "./characters/female", "FileSystem", "Characters", + false, true); + OgreBites::ApplicationContext::locateResources(); } void loadResources() override { @@ -517,8 +525,8 @@ public: setWindowGrab(gui.grab); gui.grabChanged = false; ECS::get().modified(); - std::cout << "updateWorld " << gui.grabChanged - << " " << gui.grab << std::endl; + // std::cout << "updateWorld " << gui.grabChanged + // << " " << gui.grab << std::endl; } } end: @@ -717,9 +725,8 @@ end: .each([this](ECS::GUI &gui) { if (gui.grabChanged) setWindowGrab(gui.grab); - std::cout << "grab: " << gui.grab << "\n"; - std::cout << "GUI enabled: " << gui.enabled - << "\n"; + // std::cout << "grab: " << gui.grab << "\n"; + // std::cout << "GUI enabled: " << gui.enabled << "\n"; }); ECS::get_mut().grab = false; ECS::get_mut().grabChanged = true; diff --git a/assets/blender/characters/CMakeLists.txt b/assets/blender/characters/CMakeLists.txt index f09a7d5..8f4c04a 100644 --- a/assets/blender/characters/CMakeLists.txt +++ b/assets/blender/characters/CMakeLists.txt @@ -13,6 +13,7 @@ add_custom_command( ${CMAKE_BINARY_DIR}/assets/blender/vrm-vroid-normal-${EDITED_BLEND}.blend ${CMAKE_CURRENT_SOURCE_DIR}/copy_animations.py ${CMAKE_BINARY_DIR}/assets/blender/mixamo + ${CMAKE_CURRENT_BINARY_DIR}/blender-addons-installed COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_SOURCE_DIR}/edited-normal-${EDITED_BLEND}.blend ${CMAKE_CURRENT_BINARY_DIR}/edited-normal-${EDITED_BLEND}.blend @@ -25,6 +26,7 @@ add_custom_command( list(APPEND EDITED_BLEND_TARGETS ${CMAKE_BINARY_DIR}/assets/blender/characters/edited-normal-${EDITED_BLEND}.blend) list(APPEND CHARACTER_GLBS ${CMAKE_BINARY_DIR}/characters/${EDITED_BLEND}/normal-${EDITED_BLEND}.glb) endforeach() +list(APPEND CHARACTER_GLBS ${CMAKE_BINARY_DIR}/characters/male/male-clothes-toprobe.glb) add_custom_command(OUTPUT ${CMAKE_BINARY_DIR}/assets/blender/mixamo COMMAND ${CMAKE_COMMAND} -E copy_directory ${CMAKE_SOURCE_DIR}/assets/blender/mixamo ${CMAKE_BINARY_DIR}/assets/blender/mixamo DEPENDS ${CMAKE_SOURCE_DIR}/assets/blender/mixamo @@ -46,7 +48,7 @@ set(VRM_IMPORTED_BLENDS # DEPENDS ${CMAKE_SOURCE_DIR}/assets/blender/scripts/export_models.py ${VRM_IMPORTED_BLENDS} ${EDITED_BLEND_TARGETS} # WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) set(FEMALE_OBJECTS "Body;Hair;Face;BackHair;Tops;Bottoms;Shoes;Accessory") -set(MALE_OBJECTS "BodyTop;BodyBottom;BodyFeet;Hair;Face;BackHair;Tops;Bottoms;Shoes;Accessory") +set(MALE_OBJECTS "BodyTopRobe;BodyTop;BodyBottom;BodyFeet;Hair;Face;BackHair;Bottoms;Shoes;Accessory") add_custom_command( OUTPUT ${CMAKE_BINARY_DIR}/characters/male/normal-male.glb COMMAND ${CMAKE_COMMAND} -E make_directory ${CREATE_DIRECTORIES} @@ -56,9 +58,14 @@ add_custom_command( "${MALE_OBJECTS}" "male" tmp-edited-male.blend + COMMAND ${CMAKE_COMMAND} -D FILE=${CMAKE_BINARY_DIR}/characters/male/normal-male.glb + -P ${CMAKE_CURRENT_SOURCE_DIR}/cmake/check_file_size.cmake + COMMAND ${CMAKE_COMMAND} -D FILE=${CMAKE_BINARY_DIR}/characters/male/normal-male.scene + -P ${CMAKE_CURRENT_SOURCE_DIR}/cmake/check_file_size.cmake DEPENDS ${CMAKE_SOURCE_DIR}/assets/blender/scripts/export_models2.py ${VRM_IMPORTED_BLENDS} ${CMAKE_CURRENT_BINARY_DIR}/edited-normal-male.blend + ${CMAKE_CURRENT_BINARY_DIR}/blender-addons-installed WORKING_DIRECTORY ${CMAKE_BINARY_DIR} VERBATIM ) @@ -72,12 +79,28 @@ add_custom_command( "${FEMALE_OBJECTS}" "female" tmp-edited-female.blend + COMMAND ${CMAKE_COMMAND} -D FILE=${CMAKE_BINARY_DIR}/characters/female/normal-female.glb + -P ${CMAKE_CURRENT_SOURCE_DIR}/cmake/check_file_size.cmake + COMMAND ${CMAKE_COMMAND} -D FILE=${CMAKE_BINARY_DIR}/characters/female/normal-female.scene + -P ${CMAKE_CURRENT_SOURCE_DIR}/cmake/check_file_size.cmake DEPENDS ${CMAKE_SOURCE_DIR}/assets/blender/scripts/export_models2.py ${VRM_IMPORTED_BLENDS} ${CMAKE_CURRENT_BINARY_DIR}/edited-normal-female.blend + ${CMAKE_CURRENT_BINARY_DIR}/blender-addons-installed WORKING_DIRECTORY ${CMAKE_BINARY_DIR} VERBATIM ) +add_custom_command( + OUTPUT ${CMAKE_BINARY_DIR}/characters/male/male-clothes-toprobe.glb + COMMAND ${BLENDER} -b -Y ${CMAKE_CURRENT_SOURCE_DIR}/edited-normal-male.blend + -P ${CMAKE_CURRENT_SOURCE_DIR}/export_clothes.py + -- ${CMAKE_BINARY_DIR}/characters/male/male-clothes-toprobe.glb + BodyTopRobe + COMMAND ${CMAKE_COMMAND} -D FILE=${CMAKE_BINARY_DIR}/characters/male/male-clothes-toprobe.glb + -P ${CMAKE_CURRENT_SOURCE_DIR}/cmake/check_file_size.cmake + DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/edited-normal-male.blend + ${CMAKE_CURRENT_BINARY_DIR}/blender-addons-installed +) set(VRM_SOURCE) @@ -100,13 +123,29 @@ foreach(MIXAMO_FILE ${MIXAMO_FILES}) list(APPEND VRM_SOURCE "${OUTPUT_FILE}") endforeach() add_custom_command(OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/blender-addons-installed - COMMAND ${CMAKE_COMMAND} -E copy_directory ${CMAKE_SOURCE_DIR}/assets/blender/scripts/addons ${CMAKE_BINARY_DIR}/assets/blender/scripts/addons COMMAND ${BLENDER} -b -Y -P ${CMAKE_SOURCE_DIR}/assets/blender/scripts/install_addons.py COMMAND ${CMAKE_COMMAND} -E touch ${CMAKE_CURRENT_BINARY_DIR}/blender-addons-installed - DEPENDS ${CMAKE_SOURCE_DIR}/assets/blender/scripts/addons/3.6/VRM_Addon_for_Blender-release.zip - ${CMAKE_SOURCE_DIR}/assets/blender/scripts/addons/3.6/io_ogre.zip + DEPENDS ${CMAKE_BINARY_DIR}/assets/blender/scripts/addons/3.6/VRM_Addon_for_Blender-release.zip + ${CMAKE_BINARY_DIR}/assets/blender/scripts/addons/3.6/io_ogre.zip ${CMAKE_SOURCE_DIR}/assets/blender/scripts/install_addons.py + WORKING_DIRECTORY ${CMAKE_BINARY_DIR} ) +add_custom_command(OUTPUT ${CMAKE_BINARY_DIR}/assets/blender/scripts/addons/3.6/VRM_Addon_for_Blender-release.zip + COMMAND ${CMAKE_COMMAND} -E copy + ${CMAKE_SOURCE_DIR}/assets/blender/scripts/addons/3.6/VRM_Addon_for_Blender-release.zip + ${CMAKE_BINARY_DIR}/assets/blender/scripts/addons/3.6/VRM_Addon_for_Blender-release.zip + DEPENDS ${CMAKE_SOURCE_DIR}/assets/blender/scripts/addons/3.6/VRM_Addon_for_Blender-release.zip + WORKING_DIRECTORY ${CMAKE_BINARY_DIR} +) +add_custom_command(OUTPUT ${CMAKE_BINARY_DIR}/assets/blender/scripts/addons/3.6/io_ogre.zip + COMMAND zip -r ${CMAKE_BINARY_DIR}/assets/blender/scripts/addons/3.6/io_ogre.zip io_ogre + DEPENDS ${CMAKE_SOURCE_DIR}/assets/blender/scripts/blender2ogre/io_ogre + ${CMAKE_SOURCE_DIR}/assets/blender/scripts/blender2ogre/io_ogre/ui/export.py + ${CMAKE_SOURCE_DIR}/assets/blender/scripts/blender2ogre/io_ogre/ogre/skeleton.py + WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/assets/blender/scripts/blender2ogre +) +add_custom_target(install_addons ALL DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/blender-addons-installed) + #add_custom_command(OUTPUT ${VRM_IMPORTED_BLENDS} # COMMAND ${CMAKE_COMMAND} -E make_directory ${CREATE_DIRECTORIES} diff --git a/assets/blender/characters/edited-normal-female.blend b/assets/blender/characters/edited-normal-female.blend index f8dfaf6..8417145 100644 --- a/assets/blender/characters/edited-normal-female.blend +++ b/assets/blender/characters/edited-normal-female.blend @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0717ea9321c227a8a7082b94afa111b517182caa46022d20d81470de7154bfe1 -size 17175776 +oid sha256:32e08e3a601faef1b5fe336b35c4bffdbad916a58ec9e68f92674e4de8933ae3 +size 17161044 diff --git a/assets/blender/characters/edited-normal-male.blend b/assets/blender/characters/edited-normal-male.blend index 2dd8674..80d10bb 100644 --- a/assets/blender/characters/edited-normal-male.blend +++ b/assets/blender/characters/edited-normal-male.blend @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d5293ee4e8db14dc47cea8b071afe8f08ec7b11783666b4f1378c99ef230f34d -size 13581930 +oid sha256:7766a4dd6abc6547f19754f2f823c7a2b20c4fcc30a97dc4ed75b9c96fe13f9f +size 17830659 diff --git a/assets/blender/scripts/addons/3.6/io_ogre.zip b/assets/blender/scripts/addons/3.6/io_ogre.zip index 719fa0d..97fd9e7 100644 --- a/assets/blender/scripts/addons/3.6/io_ogre.zip +++ b/assets/blender/scripts/addons/3.6/io_ogre.zip @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b27cb68a387dcd9f2b7d2ea01ce5a9887c2a6badd86e71b7d7438134b3c30377 -size 218297 +oid sha256:f0d5281a18dcc6c50c8e7d9ab87ded8544ac3c5317d34ee11823c7e822508ac1 +size 191303 diff --git a/assets/blender/scripts/blender2ogre/io_ogre/ogre/skeleton.py b/assets/blender/scripts/blender2ogre/io_ogre/ogre/skeleton.py index e209e49..90e6ab2 100644 --- a/assets/blender/scripts/blender2ogre/io_ogre/ogre/skeleton.py +++ b/assets/blender/scripts/blender2ogre/io_ogre/ogre/skeleton.py @@ -32,6 +32,8 @@ def dot_skeleton(obj, path, **kwargs): name = kwargs.get('force_name') or obj.data.name if config.get('SHARED_ARMATURE') is True: name = kwargs.get('force_name') or arm.data.name + if name == "Armature": + name = arm.name name = util.clean_object_name(name) # Lets export the Armature only once diff --git a/assets/blender/scripts/export_models2.py b/assets/blender/scripts/export_models2.py index 0555301..2af50b4 100644 --- a/assets/blender/scripts/export_models2.py +++ b/assets/blender/scripts/export_models2.py @@ -290,6 +290,28 @@ for mapping in[CommandLineMapping()]: export_lights=False, export_skins=True) print("exported to: " + mapping.gltf_path) + obj_names = mapping.objs + prefix = mapping.armature_name + "_" + + for name in obj_names: + obj = bpy.data.objects.get(name) + + if obj and obj.type == 'MESH': + # 1. Rename Mesh Data + if not obj.data.name.startswith(prefix): + obj.data.name = prefix + obj.data.name + + # 2. Iterate through all Material Slots on the object + for slot in obj.material_slots: + if slot.material: + mat = slot.material + # 3. Check if material already has the prefix + if not mat.name.startswith(prefix): + mat.name = prefix + mat.name + print(f"Renamed material '{mat.name}' on object '{name}'") + armobj = bpy.data.objects.get(mapping.armature_name) + armobj.data.name = armobj.name + bpy.ops.ogre.export(filepath=mapping.gltf_path.replace(".glb", ".scene"), EX_SELECTED_ONLY=False, EX_SHARED_ARMATURE=True, EX_LOD_GENERATION='0', EX_GENERATE_TANGENTS='4') bpy.ops.wm.read_homefile(use_empty=True) time.sleep(2) diff --git a/resources.cfg b/resources.cfg index a7670e0..e2b30cc 100644 --- a/resources.cfg +++ b/resources.cfg @@ -73,9 +73,8 @@ FileSystem=resources/fonts [LuaScripts] FileSystem=lua-scripts -[Characters] -FileSystem=./characters/male -FileSystem=./characters/female +#[Characters] +#FileSystem=./characters [Audio] FileSystem=./audio/gui diff --git a/src/gamedata/CharacterAnimationModule.cpp b/src/gamedata/CharacterAnimationModule.cpp index 6fb3812..66d6870 100644 --- a/src/gamedata/CharacterAnimationModule.cpp +++ b/src/gamedata/CharacterAnimationModule.cpp @@ -29,27 +29,27 @@ CharacterAnimationModule::CharacterAnimationModule(flecs::world &ecs) if (!anim.configured) { int i, j; e.set({}); - ch.mBodyEnt->getSkeleton()->setBlendMode( - Ogre::ANIMBLEND_CUMULATIVE); + Ogre::Entity *ent = static_cast( + ch.mBodyNode->getAttachedObject(0)); + ent->getSkeleton()->setBlendMode( + Ogre::ANIMBLEND_CUMULATIVE); Ogre::AnimationStateSet *animStateSet = - ch.mBodyEnt->getAllAnimationStates(); + ent->getAllAnimationStates(); const Ogre::AnimationStateMap &animMap = animStateSet->getAnimationStates(); anim.mAnimationSystem = new AnimationSystem::AnimationSystem( false); - ch.mBodyEnt->getSkeleton() + ent->getSkeleton() ->getBone("Root") ->removeAllChildren(); for (auto it = animMap.begin(); it != animMap.end(); it++) { AnimationSystem::Animation *animation = new AnimationSystem::Animation( - ch.mBodyEnt - ->getSkeleton(), + ent->getSkeleton(), it->second, - ch.mBodyEnt - ->getSkeleton() + ent->getSkeleton() ->getAnimation( it->first)); #ifdef VDEBUG diff --git a/src/gamedata/CharacterManagerModule.cpp b/src/gamedata/CharacterManagerModule.cpp index 3301992..9d581fa 100644 --- a/src/gamedata/CharacterManagerModule.cpp +++ b/src/gamedata/CharacterManagerModule.cpp @@ -129,7 +129,11 @@ CharacterManagerModule::CharacterManagerModule(flecs::world &ecs) 10000.0f) { if (!data.e.is_valid()) { data.e = createCharacterData( - data.model, + data.modelFace, + data.modelHair, + data.modelTop, + data.modelBottom, + data.modelFeet, data.position, data.orientation); data.e.add(town); @@ -177,21 +181,27 @@ CharacterManagerModule::createPlayer(const Ogre::Vector3 &position, std::cout << "Begin player create" << std::endl; player.add(); ECS::get_mut().createCharacter( - player, position, rotation, "normal-male.glb"); + player, position, rotation, "male_Face.mesh", + "male_Hair001.mesh", "male_BodyTop.mesh", + "male_BodyBottom.mesh", "male_BodyFeet.mesh"); ECS::modified(); std::cout << "End player create" << std::endl; count++; - return player; + return player; } -flecs::entity -CharacterManagerModule::createCharacterData(const Ogre::String model, - const Ogre::Vector3 &position, - const Ogre::Quaternion &rotation) + +flecs::entity CharacterManagerModule::createCharacterData( + const Ogre::String &modelFace, const Ogre::String &modelHair, + const Ogre::String &modelTop, const Ogre::String &modelBottom, + const Ogre::String &modelFeet, const Ogre::Vector3 &position, + const Ogre::Quaternion &rotation) { ZoneScoped; flecs::entity e = ECS::get().entity(); ECS::get_mut().createCharacter(e, position, rotation, - model); + modelFace, modelHair, + modelTop, modelBottom, + modelFeet); ECS::modified(); return e; } @@ -199,8 +209,13 @@ CharacterManagerModule::createCharacterData(const Ogre::String model, void CharacterManagerModule::registerTownCharacters(flecs::entity town) { ZoneScoped; - Ogre::MeshManager::getSingleton().load("normal-male.glb", "General"); - Ogre::MeshManager::getSingleton().load("normal-female.glb", "General"); + { + Ogre::MeshPtr maleMesh = Ogre::MeshManager::getSingleton().load( + "normal-male.glb", "Characters"); + Ogre::MeshPtr femaleMesh = + Ogre::MeshManager::getSingleton().load( + "normal-female.glb", "Characters"); + } Ogre::String props = StaticGeometryModule::getItemProperties(town); nlohmann::json j = nlohmann::json::parse(props); nlohmann::json npcs = nlohmann::json::array(); @@ -212,17 +227,29 @@ void CharacterManagerModule::registerTownCharacters(flecs::entity town) int index = 0; std::map npcMap; for (auto &npc : npcs) { - const char *models[] = { "normal-male.glb", - "normal-female.glb" }; + struct desc { + const char *face, *hair, *top, *bottom, *feet; + }; + struct desc models[] = { + { "male_Face.mesh", "male_Hair001.mesh", + "male_BodyTop.mesh", "male_BodyBottom.mesh", + "male_BodyFeet.mesh" }, + { "normal-female.glb", "normal-female.glb", + "normal-female.glb", "normal-female.glb", + "normal-female.glb" } + }; int sex = npc["sex"].get(); Ogre::Vector3 npcPosition; Ogre::Quaternion npcOrientation; from_json(npc["position"], npcPosition); from_json(npc["orientation"], npcOrientation); - Ogre::String model = models[sex]; TownNPCs::NPCData npcData; npcData.e = flecs::entity(); - npcData.model = model; + npcData.modelFace = models[sex].face; + npcData.modelHair = models[sex].hair; + npcData.modelTop = models[sex].top; + npcData.modelBottom = models[sex].bottom; + npcData.modelFeet = models[sex].feet; npcData.orientation = npcOrientation; npcData.position = npcPosition; npcData.props = npc; diff --git a/src/gamedata/CharacterManagerModule.h b/src/gamedata/CharacterManagerModule.h index 77aab35..64dc19d 100644 --- a/src/gamedata/CharacterManagerModule.h +++ b/src/gamedata/CharacterManagerModule.h @@ -12,7 +12,11 @@ struct TownNPCs { nlohmann::json props; Ogre::Vector3 position; Ogre::Quaternion orientation; - Ogre::String model; + Ogre::String modelFace; + Ogre::String modelHair; + Ogre::String modelTop; + Ogre::String modelBottom; + Ogre::String modelFeet; std::vector actionNodes; }; @@ -25,8 +29,12 @@ struct CharacterManagerModule { CharacterManagerModule(flecs::world &ecs); flecs::entity createPlayer(const Ogre::Vector3 &position, const Ogre::Quaternion &rotation); - flecs::entity createCharacterData(const Ogre::String model, - const Ogre::Vector3 &position, + flecs::entity createCharacterData(const Ogre::String &modelFace, + const Ogre::String &modelHair, + const Ogre::String &modelTop, + const Ogre::String &modelBottom, + const Ogre::String &modelFeet, + const Ogre::Vector3 &position, const Ogre::Quaternion &rotation); void removeCharacterData(int id); flecs::entity getPlayer() const diff --git a/src/gamedata/CharacterModule.cpp b/src/gamedata/CharacterModule.cpp index 26f1373..f206359 100644 --- a/src/gamedata/CharacterModule.cpp +++ b/src/gamedata/CharacterModule.cpp @@ -22,11 +22,14 @@ CharacterModule::CharacterModule(flecs::world &ecs) ecs.component() .on_remove([this](flecs::entity e, CharacterBase &ch) { ZoneScoped; - // FIXME: clean up data - if (characterEntities.find(e) != - characterEntities.end() || + if (characterEntitiesFace.find(e) != + characterEntitiesFace.end() || characterNodes.find(e) != characterNodes.end()) { - characterEntities.erase(e); + // FIXME: clean up data + characterEntitiesFace.erase(e); + characterEntitiesTop.erase(e); + characterEntitiesBottom.erase(e); + characterEntitiesFeet.erase(e); characterNodes.erase(e); ECS::modified(); } @@ -34,30 +37,92 @@ CharacterModule::CharacterModule(flecs::world &ecs) .on_add([this](flecs::entity e, CharacterBase &ch) { if (characterNodes.find(e) == characterNodes.end()) { ZoneScoped; - OgreAssert(characterModels.find(e) != - characterModels.end(), + OgreAssert(characterModelsFace.find(e) != + characterModelsFace.end(), "no model set"); const EngineData &eng = ECS::get(); Ogre::SceneNode *bodyNode = eng.mScnMgr->getRootSceneNode() ->createChildSceneNode(); - Ogre::Entity *bodyEnt = + Ogre::Entity *faceEnt = eng.mScnMgr->createEntity( - characterModels[e]); + characterModelsFace[e]); + bodyNode->attachObject(faceEnt); characterNodes[e] = bodyNode; - characterEntities[e] = bodyEnt; + characterEntitiesFace[e] = faceEnt; + Ogre::Entity *hairEnt = + eng.mScnMgr->createEntity( + characterModelsHair[e]); + hairEnt->shareSkeletonInstanceWith(faceEnt); + bodyNode->attachObject(hairEnt); + characterEntitiesHair[e] = hairEnt; + Ogre::Entity *topEnt = + eng.mScnMgr->createEntity( + characterModelsTop[e]); + topEnt->shareSkeletonInstanceWith(faceEnt); + bodyNode->attachObject(topEnt); + characterEntitiesTop[e] = topEnt; + Ogre::Entity *bottomEnt = + eng.mScnMgr->createEntity( + characterModelsBottom[e]); + bottomEnt->shareSkeletonInstanceWith(faceEnt); + bodyNode->attachObject(bottomEnt); + characterEntitiesBottom[e] = bottomEnt; + Ogre::Entity *feetEnt = + eng.mScnMgr->createEntity( + characterModelsFeet[e]); + feetEnt->shareSkeletonInstanceWith(faceEnt); + bodyNode->attachObject(feetEnt); + characterEntitiesFeet[e] = feetEnt; +#if 0 + if (characterModelsTop.find(e) != + characterModelsTop.end()) { + Ogre::String skeletonName = + bodyEnt->getMesh() + ->getSkeletonName(); + Ogre::MeshPtr mesh = + Ogre::MeshManager::getSingleton() + .load(characterModelsTop + [e], + "General"); + Ogre::String mname = mesh->getName(); + mesh = mesh->clone(mname + "_clone"); + OgreAssert( + mesh, + "No mesh " + + characterModelsTop[e]); + Ogre::String clothSkeleton = + mesh->getSkeletonName(); + if (clothSkeleton != skeletonName) { + mesh->setSkeletonName( + skeletonName); + mesh->load(); + if (Ogre::SkeletonManager::getSingleton() + .resourceExists( + clothSkeleton)) + Ogre::SkeletonManager:: + getSingleton() + .remove(clothSkeleton); + } + Ogre::Entity *characterTop = + eng.mScnMgr->createEntity(mesh); + characterTop->shareSkeletonInstanceWith( + bodyEnt); + bodyNode->attachObject(characterTop); + } +#endif ECS::modified(); } OgreAssert(characterOrientations.find(e) != characterOrientations.end(), "Bad orientation/position"); - ch.mBodyEnt = characterEntities[e]; ch.mBodyNode = characterNodes[e]; ch.mBodyNode->setOrientation(characterOrientations[e]); ch.mBodyNode->setPosition(characterPositions[e]); - ch.mBodyNode->attachObject(ch.mBodyEnt); - OgreAssert(ch.mBodyEnt->getSkeleton()->hasBone("Root"), - "No root bone"); + OgreAssert( + characterEntitiesFace[e]->getSkeleton()->hasBone( + "Root"), + "No root bone"); ch.mBoneMotion = Ogre::Vector3::ZERO; ch.mBonePrevMotion = Ogre::Vector3::ZERO; }); @@ -462,6 +527,34 @@ void CharacterModule::updateCameraGoal(Camera &camera, Ogre::Real deltaYaw, } } +void CharacterModule::createCharacter( + flecs::entity e, const Ogre::Vector3 &position, + const Ogre::Quaternion &rotation, const Ogre::String &faceModel, + const Ogre::String &hairModel, const Ogre::String &topModel, + const Ogre::String &bottomModel, const Ogre::String &feetModel) +{ + ZoneScoped; + if (e.has() || e.has()) + return; + if (characterNodes.find(e) != characterNodes.end()) + return; + e.set({ rotation, position }); + characterOrientations[e] = rotation; + characterPositions[e] = position; + characterModelsFace[e] = faceModel; + characterModelsHair[e] = hairModel; + characterModelsTop[e] = topModel; + characterModelsBottom[e] = bottomModel; + characterModelsFeet[e] = feetModel; + e.set( + { { 0.0f, 0.0f, 0.0f }, { 0.0f, 0.0f, 0.0f } }); + e.add(); + e.add(); + e.add(); + e.add(); + e.add(); +} + void applyWeightBasedScale(Ogre::Entity *ent, const Ogre::String &targetBoneName, const Ogre::Vector3 &scale) @@ -549,26 +642,63 @@ void applyWeightBasedScale(Ogre::Entity *ent, } } -void CharacterModule::createCharacter(flecs::entity e, - const Ogre::Vector3 &position, - const Ogre::Quaternion &rotation, - const Ogre::String model) +void CharacterModule::remapMeshToMasterSkeleton(Ogre::MeshPtr clothMesh, + Ogre::MeshPtr masterMesh) { - ZoneScoped; - if (e.has() || e.has()) + Ogre::SkeletonPtr masterSkel = masterMesh->getSkeleton(); + Ogre::SkeletonPtr clothSkel = clothMesh->getSkeleton(); + + if (!masterSkel || !clothSkel) return; - if (characterNodes.find(e) != characterNodes.end()) - return; - e.set({ rotation, position }); - characterOrientations[e] = rotation; - characterPositions[e] = position; - characterModels[e] = model; - e.set( - { { 0.0f, 0.0f, 0.0f }, { 0.0f, 0.0f, 0.0f } }); - e.add(); - e.add(); - e.add(); - e.add(); - e.add(); + + // 1. Create a Lookup Table: ClothIndex -> MasterIndex + std::map indexMap; + for (unsigned short i = 0; i < clothSkel->getNumBones(); ++i) { + Ogre::String boneName = clothSkel->getBone(i)->getName(); + if (masterSkel->hasBone(boneName)) { + indexMap[i] = + masterSkel->getBone(boneName)->getHandle(); + } else { + indexMap[i] = 0; // Fallback to root + } + } + + // 2. Update the Hardware Buffers for each SubMesh + for (unsigned short i = 0; i < clothMesh->getNumSubMeshes(); ++i) { + Ogre::SubMesh *sub = clothMesh->getSubMesh(i); + Ogre::VertexData *vdata = sub->useSharedVertices ? + clothMesh->sharedVertexData : + sub->vertexData; + + // Find the element containing bone indices (VES_BLEND_INDICES) + const Ogre::VertexElement *idxElem = + vdata->vertexDeclaration->findElementBySemantic( + Ogre::VES_BLEND_INDICES); + if (!idxElem) + continue; + + Ogre::HardwareVertexBufferSharedPtr vbuf = + vdata->vertexBufferBinding->getBuffer( + idxElem->getSource()); + unsigned char *vertex = static_cast( + vbuf->lock(Ogre::HardwareBuffer::HBL_NORMAL)); + + for (size_t j = 0; j < vdata->vertexCount; ++j) { + unsigned char *pIndices; + idxElem->baseVertexPointerToElement(vertex, &pIndices); + + // Remap the 4 indices (Ogre hardware skinning usually uses 4 bytes) + for (int k = 0; k < 4; ++k) { + pIndices[k] = static_cast( + indexMap[pIndices[k]]); + } + vertex += vbuf->getVertexSize(); + } + vbuf->unlock(); + } + + // 3. Link to Master Skeleton and rebuild + clothMesh->setSkeletonName(masterSkel->getName()); + clothMesh->_compileBoneAssignments(); } } diff --git a/src/gamedata/CharacterModule.h b/src/gamedata/CharacterModule.h index 9f3a702..ec78638 100644 --- a/src/gamedata/CharacterModule.h +++ b/src/gamedata/CharacterModule.h @@ -23,7 +23,6 @@ struct CharacterBase { Ogre::Vector3 mBonePrevMotion; Ogre::Vector3 mGoalDirection; // actual intended direction in world-space Ogre::SceneNode *mBodyNode; - Ogre::Entity *mBodyEnt; bool is_submerged; }; struct CharacterLocation { @@ -40,13 +39,23 @@ struct CharacterModule { void updateCameraGoal(Camera &camera, Ogre::Real deltaYaw, Ogre::Real deltaPitch, Ogre::Real deltaZoom); void createCharacter(flecs::entity e, const Ogre::Vector3 &position, - const Ogre::Quaternion &rotation, - const Ogre::String model); + const Ogre::Quaternion &rotation, const Ogre::String &faceModel, const Ogre::String &hairModel, + const Ogre::String &topModel, const Ogre::String &bottomModel, const Ogre::String &feetModel); std::unordered_map characterNodes; - std::unordered_map characterEntities; - std::unordered_map characterModels; + std::unordered_map characterEntitiesFace; + std::unordered_map characterEntitiesHair; + std::unordered_map characterEntitiesTop; + std::unordered_map characterEntitiesBottom; + std::unordered_map characterEntitiesFeet; + std::unordered_map characterModelsFace; + std::unordered_map characterModelsHair; + std::unordered_map characterModelsTop; + std::unordered_map characterModelsBottom; + std::unordered_map characterModelsFeet; std::unordered_map characterPositions; std::unordered_map characterOrientations; + void remapMeshToMasterSkeleton(Ogre::MeshPtr clothMesh, + Ogre::MeshPtr masterMesh); }; } #endif diff --git a/src/gamedata/LuaModule/LuaData.cpp b/src/gamedata/LuaModule/LuaData.cpp index 590211d..e772a5d 100644 --- a/src/gamedata/LuaModule/LuaData.cpp +++ b/src/gamedata/LuaModule/LuaData.cpp @@ -485,23 +485,32 @@ LuaData::LuaData() }); lua_setglobal(L, "ecs_trigger_get_animation"); lua_pushcfunction(L, [](lua_State *L) -> int { - OgreAssert(lua_gettop(L) == 5, "Invalid parameters"); - luaL_checktype(L, 1, LUA_TSTRING); // type - luaL_checktype(L, 2, LUA_TNUMBER); - luaL_checktype(L, 3, LUA_TNUMBER); - luaL_checktype(L, 4, LUA_TNUMBER); - luaL_checktype(L, 5, LUA_TNUMBER); - Ogre::String type = lua_tostring(L, 1); - float yaw = lua_tonumber(L, 5); - float x = lua_tonumber(L, 2); - float y = lua_tonumber(L, 3); - float z = lua_tonumber(L, 4); + OgreAssert(lua_gettop(L) == 8, "Invalid parameters"); + luaL_checktype(L, 1, LUA_TSTRING); // face + luaL_checktype(L, 2, LUA_TSTRING); // hair + luaL_checktype(L, 3, LUA_TSTRING); // top + luaL_checktype(L, 4, LUA_TSTRING); // bottom + luaL_checktype(L, 5, LUA_TSTRING); // feet + luaL_checktype(L, 6, LUA_TNUMBER); + luaL_checktype(L, 7, LUA_TNUMBER); + luaL_checktype(L, 8, LUA_TNUMBER); + luaL_checktype(L, 9, LUA_TNUMBER); + Ogre::String face = lua_tostring(L, 1); + Ogre::String hair = lua_tostring(L, 2); + Ogre::String top = lua_tostring(L, 3); + Ogre::String bottom = lua_tostring(L, 4); + Ogre::String feet = lua_tostring(L, 5); + float yaw = lua_tonumber(L, 8); + float x = lua_tonumber(L, 5); + float y = lua_tonumber(L, 6); + float z = lua_tonumber(L, 7); Ogre::Quaternion orientation(Ogre::Radian(yaw), Ogre::Vector3::UNIT_Y); Ogre::Vector3 npcPos(x, y, z); flecs::entity e = ECS::get_mut() - .createCharacterData(type, npcPos, orientation); + .createCharacterData(face, hair, top, bottom, + feet, npcPos, orientation); ECS::modified(); lua_pushinteger(L, idmap.add_entity(e)); return 1; diff --git a/src/gamedata/items/town.cpp b/src/gamedata/items/town.cpp index 93b9e6b..76d159c 100644 --- a/src/gamedata/items/town.cpp +++ b/src/gamedata/items/town.cpp @@ -2216,8 +2216,17 @@ bool editNPCs(nlohmann::json &npcs) ("Spawn##" + Ogre::StringConverter::toString(id)) .c_str())) { int sex = npc["sex"].get(); - const char *models[] = { "normal-male.glb", - "normal-female.glb" }; + struct desc { + const char *face, *hair, *top, *bottom, *feet; + }; + struct desc models[] = { + { "male_Face.mesh", "male_Hair001.mesh", + "male_BodyTop.mesh", "male_BodyBottom.mesh", + "male_BodyFeet.mesh" }, + { "normal-female.glb", "normal-female.glb", + "normal-female.glb", "normal-female.glb", + "normal-female.glb" } + }; Ogre::Vector3 npcPosition; Ogre::Quaternion npcOrientation; from_json(npc["position"], npcPosition); @@ -2225,8 +2234,11 @@ bool editNPCs(nlohmann::json &npcs) // FIXME: create TownCharacterManager and register NPCs through there ECS::get_mut() - .createCharacterData(models[sex], npcPosition, - npcOrientation); + .createCharacterData( + models[sex].face, models[sex].hair, + models[sex].top, models[sex].bottom, + models[sex].feet, npcPosition, + npcOrientation); } if (ImGui::SmallButton( ("Delete##" + Ogre::StringConverter::toString(id))