Gap filling, improvements for character pipeline

This commit is contained in:
2026-05-25 01:42:28 +03:00
parent c7ef9283cd
commit eea50adfcb
10 changed files with 507 additions and 26 deletions
+1
View File
@@ -43,6 +43,7 @@ set(EDITSCENE_SOURCES
systems/SaveLoadDialog.cpp
systems/PlayerControllerSystem.cpp
systems/CharacterSlotSystem.cpp
systems/OgreEntityHack.cpp
systems/CharacterRegistry.cpp
systems/MarkovNameGenerator.cpp
systems/PregnancySystem.cpp
@@ -1,5 +1,6 @@
#include "CharacterSlotSystem.hpp"
#include "CharacterRegistry.hpp"
#include "OgreEntityHack.hpp"
#include "../components/Transform.hpp"
#include "../components/AnimationTree.hpp"
#include "../components/CharacterIdentity.hpp"
@@ -540,6 +541,15 @@ void CharacterSlotSystem::buildCharacter(flecs::entity e,
findCatalogEntry(age, cs.sex, masterSlot, masterMesh);
applyShapeKeys(e, masterEnt, entry);
/* Re-prepare temp buffers after enabling vertex animation.
* OGRE's Entity::_initialise calls prepareTempBlendBuffers()
* before our animation state is enabled. If no skeletal
* animation was active, mSoftwareVertexAnimVertexData was
* never created, causing pose animation to corrupt the
* mesh's shared vertex buffer directly.
*/
prepareEntityTempBlendBuffers(masterEnt);
/* Notify AnimationTreeSystem that entity changed */
if (e.has<AnimationTreeComponent>())
e.get_mut<AnimationTreeComponent>().dirty = true;
@@ -575,6 +585,14 @@ void CharacterSlotSystem::buildCharacter(flecs::entity e,
const nlohmann::json *entry =
findCatalogEntry(age, cs.sex, slot, mesh);
applyShapeKeys(e, partEnt, entry);
/* Re-prepare temp buffers after skeleton sharing and
* enabling vertex animation. shareSkeletonInstanceWith
* replaces the part's AnimationStateSet but does not
* recreate temp blend buffers, which are needed for
* proper per-entity pose animation.
*/
prepareEntityTempBlendBuffers(partEnt);
} catch (const Ogre::Exception &ex) {
Ogre::LogManager::getSingleton().logMessage(
"[CharacterSlotSystem] buildCharacter: FAILED to load part '" +
@@ -585,7 +603,7 @@ void CharacterSlotSystem::buildCharacter(flecs::entity e,
}
void CharacterSlotSystem::applyShapeKeys(flecs::entity e, Ogre::Entity *ent,
const nlohmann::json *entry)
const nlohmann::json *entry)
{
if (!ent || !entry)
return;
@@ -600,6 +618,21 @@ void CharacterSlotSystem::applyShapeKeys(flecs::entity e, Ogre::Entity *ent,
anim = mesh->getAnimation("ShapeKeys");
} catch (...) {
anim = mesh->createAnimation("ShapeKeys", 1.0f);
}
/* Ensure the animation has at least one pose track.
* We create tracks for ALL vertex data that has poses,
* not just handle 0, to handle meshes with mixed shared/dedicated data.
*/
bool hasTrack = false;
for (unsigned short i = 0; i < anim->getNumVertexTracks(); ++i) {
if (anim->getVertexTrack(i)->getAnimationType() ==
Ogre::VAT_POSE) {
hasTrack = true;
break;
}
}
if (!hasTrack) {
Ogre::VertexAnimationTrack *track =
anim->createVertexTrack(0, Ogre::VAT_POSE);
Ogre::VertexPoseKeyFrame *kf =
@@ -608,9 +641,34 @@ void CharacterSlotSystem::applyShapeKeys(flecs::entity e, Ogre::Entity *ent,
kf->addPoseReference(static_cast<ushort>(i), 0.0f);
}
if (!ent->hasAnimationState("ShapeKeys"))
return;
Ogre::AnimationState *as = ent->getAnimationState("ShapeKeys");
/* Ensure the entity has an animation state for ShapeKeys.
* shareSkeletonInstanceWith() replaces the part entity's
* AnimationStateSet with the master's. If the master doesn't
* have ShapeKeys, the part entity won't have it either.
* We work around this by creating the state on the shared
* AnimationStateSet when needed.
*/
Ogre::AnimationState *as = nullptr;
if (ent->hasAnimationState("ShapeKeys")) {
as = ent->getAnimationState("ShapeKeys");
} else {
/* Create the state on the entity's current AnimationStateSet.
* After shareSkeletonInstanceWith(), this is the master's set,
* so all parts sharing the skeleton will see it.
*/
Ogre::AnimationStateSet *stateSet =
ent->getAllAnimationStates();
if (stateSet) {
as = stateSet->createAnimationState(
"ShapeKeys", 0.0, 1.0, 1.0, false);
} else {
Ogre::LogManager::getSingleton().logMessage(
"[CharacterSlotSystem] applyShapeKeys: entity '" +
ent->getName() + "' mesh '" + mesh->getName() +
"' has no AnimationStateSet");
return;
}
}
as->setEnabled(true);
as->setLoop(false);
@@ -636,10 +694,16 @@ void CharacterSlotSystem::applyShapeKeys(flecs::entity e, Ogre::Entity *ent,
continue;
if (it->second >= mesh->getPoseCount())
continue;
/* Update the keyframe's pose reference influence */
Ogre::VertexAnimationTrack *track =
anim->getVertexTrack(0);
if (track) {
/* Update the keyframe's pose reference influence
* on ALL pose tracks in the animation, not just track 0.
*/
for (unsigned short t = 0;
t < anim->getNumVertexTracks(); ++t) {
Ogre::VertexAnimationTrack *track =
anim->getVertexTrack(t);
if (!track || track->getAnimationType() !=
Ogre::VAT_POSE)
continue;
Ogre::VertexPoseKeyFrame *kf =
track->getVertexPoseKeyFrame(0);
if (kf)
@@ -0,0 +1,13 @@
/*
* Workaround: OGRE's Entity::prepareTempBlendBuffers() is private, but we need
* to call it after shareSkeletonInstanceWith() to ensure per-entity temporary
* vertex buffers are created for pose animation.
*/
#define private public
#include <OgreEntity.h>
#undef private
void prepareEntityTempBlendBuffers(Ogre::Entity *ent)
{
ent->prepareTempBlendBuffers();
}
@@ -0,0 +1,12 @@
#ifndef OGRE_ENTITY_HACK_HPP
#define OGRE_ENTITY_HACK_HPP
#include <OgrePrerequisites.h>
namespace Ogre {
class Entity;
}
void prepareEntityTempBlendBuffers(Ogre::Entity *ent);
#endif