independent slots for clothes, converted to .mesh workflow

This commit is contained in:
2026-02-16 09:44:00 +03:00
parent 01c1210b1b
commit be10abda16
15 changed files with 362 additions and 98 deletions

View File

@@ -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<ECS::GUI>();
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<ECS::GUI>().grab = false;
ECS::get_mut<ECS::GUI>().grabChanged = true;

View File

@@ -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}

View File

@@ -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

View File

@@ -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)

View File

@@ -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

View File

@@ -29,27 +29,27 @@ CharacterAnimationModule::CharacterAnimationModule(flecs::world &ecs)
if (!anim.configured) {
int i, j;
e.set<EventData>({});
ch.mBodyEnt->getSkeleton()->setBlendMode(
Ogre::ANIMBLEND_CUMULATIVE);
Ogre::Entity *ent = static_cast<Ogre::Entity *>(
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

View File

@@ -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<LivesIn>(town);
@@ -177,21 +181,27 @@ CharacterManagerModule::createPlayer(const Ogre::Vector3 &position,
std::cout << "Begin player create" << std::endl;
player.add<Player>();
ECS::get_mut<CharacterModule>().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<CharacterModule>();
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<CharacterModule>().createCharacter(e, position, rotation,
model);
modelFace, modelHair,
modelTop, modelBottom,
modelFeet);
ECS::modified<CharacterModule>();
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<int, TownNPCs::NPCData> 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<int>();
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;

View File

@@ -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<ActionNodeList::ActionNode> 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

View File

@@ -22,11 +22,14 @@ CharacterModule::CharacterModule(flecs::world &ecs)
ecs.component<CharacterBase>()
.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<CharacterModule>();
}
@@ -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<EngineData>();
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<CharacterModule>();
}
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<CharacterBase>() || e.has<AnimationControl>())
return;
if (characterNodes.find(e) != characterNodes.end())
return;
e.set<CharacterLocation>({ 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<CharacterVelocity>(
{ { 0.0f, 0.0f, 0.0f }, { 0.0f, 0.0f, 0.0f } });
e.add<CharacterGravity>();
e.add<CharacterBuoyancy>();
e.add<Character>();
e.add<CharacterBase>();
e.add<AnimationControl>();
}
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<CharacterBase>() || e.has<AnimationControl>())
Ogre::SkeletonPtr masterSkel = masterMesh->getSkeleton();
Ogre::SkeletonPtr clothSkel = clothMesh->getSkeleton();
if (!masterSkel || !clothSkel)
return;
if (characterNodes.find(e) != characterNodes.end())
return;
e.set<CharacterLocation>({ rotation, position });
characterOrientations[e] = rotation;
characterPositions[e] = position;
characterModels[e] = model;
e.set<CharacterVelocity>(
{ { 0.0f, 0.0f, 0.0f }, { 0.0f, 0.0f, 0.0f } });
e.add<CharacterGravity>();
e.add<CharacterBuoyancy>();
e.add<Character>();
e.add<CharacterBase>();
e.add<AnimationControl>();
// 1. Create a Lookup Table: ClothIndex -> MasterIndex
std::map<unsigned short, unsigned short> 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<unsigned char *>(
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<unsigned char>(
indexMap[pIndices[k]]);
}
vertex += vbuf->getVertexSize();
}
vbuf->unlock();
}
// 3. Link to Master Skeleton and rebuild
clothMesh->setSkeletonName(masterSkel->getName());
clothMesh->_compileBoneAssignments();
}
}

View File

@@ -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<flecs::entity_t, Ogre::SceneNode *> characterNodes;
std::unordered_map<flecs::entity_t, Ogre::Entity *> characterEntities;
std::unordered_map<flecs::entity_t, Ogre::String> characterModels;
std::unordered_map<flecs::entity_t, Ogre::Entity *> characterEntitiesFace;
std::unordered_map<flecs::entity_t, Ogre::Entity *> characterEntitiesHair;
std::unordered_map<flecs::entity_t, Ogre::Entity *> characterEntitiesTop;
std::unordered_map<flecs::entity_t, Ogre::Entity *> characterEntitiesBottom;
std::unordered_map<flecs::entity_t, Ogre::Entity *> characterEntitiesFeet;
std::unordered_map<flecs::entity_t, Ogre::String> characterModelsFace;
std::unordered_map<flecs::entity_t, Ogre::String> characterModelsHair;
std::unordered_map<flecs::entity_t, Ogre::String> characterModelsTop;
std::unordered_map<flecs::entity_t, Ogre::String> characterModelsBottom;
std::unordered_map<flecs::entity_t, Ogre::String> characterModelsFeet;
std::unordered_map<flecs::entity_t, Ogre::Vector3> characterPositions;
std::unordered_map<flecs::entity_t, Ogre::Quaternion> characterOrientations;
void remapMeshToMasterSkeleton(Ogre::MeshPtr clothMesh,
Ogre::MeshPtr masterMesh);
};
}
#endif

View File

@@ -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<CharacterManagerModule>()
.createCharacterData(type, npcPos, orientation);
.createCharacterData(face, hair, top, bottom,
feet, npcPos, orientation);
ECS::modified<CharacterManagerModule>();
lua_pushinteger(L, idmap.add_entity(e));
return 1;

View File

@@ -2216,8 +2216,17 @@ bool editNPCs(nlohmann::json &npcs)
("Spawn##" + Ogre::StringConverter::toString(id))
.c_str())) {
int sex = npc["sex"].get<int>();
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<CharacterManagerModule>()
.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))