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

@@ -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();
}
}