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