Fixed crash with entity destruction

This commit is contained in:
2026-04-26 17:56:19 +03:00
parent 9b29b68b33
commit 425bb8411d
2 changed files with 238 additions and 217 deletions

View File

@@ -61,7 +61,8 @@ Ogre::Entity *AnimationTreeSystem::findAnimatedEntity(flecs::entity e)
t.node->getAttachedObject(i);
if (obj->getMovableType() == "Entity") {
Ogre::Entity *ent =
static_cast<Ogre::Entity *>(obj);
static_cast<Ogre::Entity *>(
obj);
if (ent->hasSkeleton())
return ent;
}
@@ -87,13 +88,14 @@ bool AnimationTreeSystem::setupEntity(flecs::entity e,
EntityAnimTreeState &state = m_states[e.id()];
state.ogreEntity = ent;
state.ogreEntityName = ent->getName();
state.rootBone = nullptr;
state.animations.clear();
Ogre::SkeletonInstance *skel = ent->getSkeleton();
/* Find and freeze root bone */
const char *rootNames[] = {"Root", "mixamorig:Hips", "Spineroot"};
const char *rootNames[] = { "Root", "mixamorig:Hips", "Spineroot" };
for (const char *name : rootNames) {
try {
state.rootBone = skel->getBone(name);
@@ -102,7 +104,8 @@ bool AnimationTreeSystem::setupEntity(flecs::entity e,
}
}
if (state.rootBone) {
state.rootBindingPosition = state.rootBone->getInitialPosition();
state.rootBindingPosition =
state.rootBone->getInitialPosition();
state.rootBindingOrientation =
state.rootBone->getInitialOrientation();
state.rootBindingScale = state.rootBone->getInitialScale();
@@ -124,11 +127,9 @@ bool AnimationTreeSystem::setupEntity(flecs::entity e,
* Animation::apply() in OGRE 14) */
if (state.rootBone) {
as->destroyBlendMask();
as->createBlendMask(skel->getNumBones(),
1.0f);
as->createBlendMask(skel->getNumBones(), 1.0f);
as->setBlendMaskEntry(
state.rootBone->getHandle(),
0.0f);
state.rootBone->getHandle(), 0.0f);
}
AnimationRuntimeInfo info;
@@ -142,18 +143,20 @@ bool AnimationTreeSystem::setupEntity(flecs::entity e,
state.rootBone->getHandle();
if (anim->hasNodeTrack(handle)) {
info.rootTrack =
anim->getNodeTrack(handle);
if (info.rootTrack->getNumKeyFrames() >=
anim->getNodeTrack(
handle);
if (info.rootTrack
->getNumKeyFrames() >=
2) {
Ogre::TransformKeyFrame *tkfBeg =
info.rootTrack
->getNodeKeyFrame(0);
Ogre::TransformKeyFrame *tkfEnd =
info.rootTrack
->getNodeKeyFrame(
info.rootTrack
->getNumKeyFrames() -
1);
0);
Ogre::TransformKeyFrame *tkfEnd =
info.rootTrack->getNodeKeyFrame(
info.rootTrack
->getNumKeyFrames() -
1);
info.loopTranslation =
tkfEnd->getTranslate() -
tkfBeg->getTranslate();
@@ -169,9 +172,11 @@ bool AnimationTreeSystem::setupEntity(flecs::entity e,
/* Initialize default state machine states */
initializeTreeStates(at.root, at);
std::cout << " setupEntity done: animations=" << state.animations.size()
<< " rootBone=" << (state.rootBone ? state.rootBone->getName() : "null")
<< std::endl;
std::cout
<< " setupEntity done: animations=" << state.animations.size()
<< " rootBone="
<< (state.rootBone ? state.rootBone->getName() : "null")
<< std::endl;
return true;
}
@@ -183,16 +188,40 @@ void AnimationTreeSystem::teardownEntity(flecs::entity e)
EntityAnimTreeState &state = it->second;
for (auto &pair : state.animations) {
if (pair.second.ogreAnimState)
pair.second.ogreAnimState->destroyBlendMask();
/*
* The Ogre::Entity may have already been destroyed by
* EditorUISystem::deleteEntity before the Flecs OnRemove
* observer fires. In that case the AnimationState pointers
* are dangling and we must not touch them.
*
* We detect this by checking whether the cached Ogre::Entity
* name is still known to the scene manager. We use the
* cached name string (ogreEntityName) rather than
* ogreEntity->getName() because the pointer may already be
* dangling.
*/
bool ogreEntityAlive = false;
if (!state.ogreEntityName.empty()) {
try {
ogreEntityAlive =
m_sceneMgr->hasEntity(state.ogreEntityName);
} catch (...) {
ogreEntityAlive = false;
}
}
disableAllAnimations(state);
if (ogreEntityAlive) {
for (auto &pair : state.animations) {
if (pair.second.ogreAnimState)
pair.second.ogreAnimState->destroyBlendMask();
}
if (state.rootBone) {
state.rootBone->setManuallyControlled(false);
state.rootBone = nullptr;
disableAllAnimations(state);
if (state.rootBone) {
state.rootBone->setManuallyControlled(false);
state.rootBone = nullptr;
}
}
state.animations.clear();
@@ -210,15 +239,16 @@ void AnimationTreeSystem::disableAllAnimations(EntityAnimTreeState &state)
}
}
void AnimationTreeSystem::initializeTreeStates(
const AnimationTreeNode &node, AnimationTreeComponent &at)
void AnimationTreeSystem::initializeTreeStates(const AnimationTreeNode &node,
AnimationTreeComponent &at)
{
if (node.type == "stateMachine") {
if (at.currentStates.find(node.name) ==
at.currentStates.end()) {
for (const auto &child : node.children) {
if (child.type == "state") {
at.currentStates[node.name] = child.name;
at.currentStates[node.name] =
child.name;
break;
}
}
@@ -236,9 +266,9 @@ void AnimationTreeSystem::resolveTemplate(AnimationTreeComponent &at)
AnimationTreeTemplate *templ = nullptr;
AnimationTreeComponent *templAt = nullptr;
m_world.query<AnimationTreeTemplate, AnimationTreeComponent>()
.each([&](flecs::entity, AnimationTreeTemplate &t,
AnimationTreeComponent &ta) {
m_world.query<AnimationTreeTemplate, AnimationTreeComponent>().each(
[&](flecs::entity, AnimationTreeTemplate &t,
AnimationTreeComponent &ta) {
if (t.name == at.templateName) {
templ = &t;
templAt = &ta;
@@ -246,8 +276,8 @@ void AnimationTreeSystem::resolveTemplate(AnimationTreeComponent &at)
});
if (!templ) {
m_world.query<AnimationTreeTemplate>()
.each([&](flecs::entity, AnimationTreeTemplate &t) {
m_world.query<AnimationTreeTemplate>().each(
[&](flecs::entity, AnimationTreeTemplate &t) {
if (t.name == at.templateName)
templ = &t;
});
@@ -266,159 +296,155 @@ void AnimationTreeSystem::update(float deltaTime)
if (!m_initialized)
return;
m_world.query<AnimationTreeComponent>().each(
[this, deltaTime](flecs::entity e,
AnimationTreeComponent &at) {
resolveTemplate(at);
m_world.query<AnimationTreeComponent>().each([this, deltaTime](
flecs::entity e,
AnimationTreeComponent
&at) {
resolveTemplate(at);
if (at.dirty) {
if (setupEntity(e, at))
at.dirty = false;
}
if (at.dirty) {
if (setupEntity(e, at))
at.dirty = false;
}
auto it = m_states.find(e.id());
if (it == m_states.end())
return;
auto it = m_states.find(e.id());
if (it == m_states.end())
return;
/* Validate cached entity pointer -
/* Validate cached entity pointer -
* CharacterSlotSystem rebuilds can destroy and
* recreate the Ogre::Entity */
Ogre::Entity *currentEnt = findAnimatedEntity(e);
if (currentEnt != it->second.ogreEntity) {
if (!setupEntity(e, at)) {
return;
}
it = m_states.find(e.id());
if (it == m_states.end())
return;
}
EntityAnimTreeState &state = it->second;
if (!state.ogreEntity)
return;
if (!at.enabled) {
disableAllAnimations(state);
Ogre::Entity *currentEnt = findAnimatedEntity(e);
if (currentEnt != it->second.ogreEntity) {
if (!setupEntity(e, at)) {
return;
}
it = m_states.find(e.id());
if (it == m_states.end())
return;
}
/* Sync external state changes (editor) */
syncStateChanges(at, state);
EntityAnimTreeState &state = it->second;
if (!state.ogreEntity)
return;
/* Evaluate tree */
EvalContext ctx;
ctx.deltaTime = deltaTime;
evaluateNode(at.root, 1.0f, 1.0f, at, state, ctx);
if (!at.enabled) {
disableAllAnimations(state);
return;
}
/* Apply animation weights and advance */
Ogre::Vector3 totalRootMotion =
Ogre::Vector3::ZERO;
Ogre::SceneNode *sceneNode =
state.ogreEntity->getParentSceneNode();
/* Sync external state changes (editor) */
syncStateChanges(at, state);
for (auto &pair : ctx.animData) {
const Ogre::String &animName = pair.first;
AnimEvalData &data = pair.second;
/* Evaluate tree */
EvalContext ctx;
ctx.deltaTime = deltaTime;
evaluateNode(at.root, 1.0f, 1.0f, at, state, ctx);
auto itAnim =
state.animations.find(animName);
if (itAnim == state.animations.end())
continue;
AnimationRuntimeInfo &info =
itAnim->second;
/* Apply animation weights and advance */
Ogre::Vector3 totalRootMotion = Ogre::Vector3::ZERO;
Ogre::SceneNode *sceneNode =
state.ogreEntity->getParentSceneNode();
if (!info.ogreAnimState)
continue;
for (auto &pair : ctx.animData) {
const Ogre::String &animName = pair.first;
AnimEvalData &data = pair.second;
bool active = data.weight > 0.001f;
info.ogreAnimState->setEnabled(active);
info.ogreAnimState->setWeight(
Ogre::Math::Clamp(data.weight, 0.0f,
1.0f));
auto itAnim = state.animations.find(animName);
if (itAnim == state.animations.end())
continue;
AnimationRuntimeInfo &info = itAnim->second;
if (active && data.timeDelta != 0.0f) {
float lastTime = info.ogreAnimState
->getTimePosition();
info.ogreAnimState->addTime(
data.timeDelta);
float thisTime =
info.ogreAnimState
->getTimePosition();
float length =
info.ogreAnimState
->getLength();
bool loop =
info.ogreAnimState
->getLoop();
if (!info.ogreAnimState)
continue;
int loops = 0;
if (loop && length > 0.0f) {
loops = (int)std::round(
(lastTime + data.timeDelta -
thisTime) /
length);
}
bool active = data.weight > 0.001f;
info.ogreAnimState->setEnabled(active);
info.ogreAnimState->setWeight(
Ogre::Math::Clamp(data.weight, 0.0f, 1.0f));
if (at.useRootMotion &&
info.rootTrack) {
Ogre::TransformKeyFrame tkf(
nullptr, 0.0f);
info.rootTrack
->getInterpolatedKeyFrame(
lastTime, &tkf);
Ogre::Vector3 lastPos =
tkf.getTranslate();
info.rootTrack
->getInterpolatedKeyFrame(
thisTime, &tkf);
Ogre::Vector3 thisPos =
tkf.getTranslate();
Ogre::Vector3 delta =
thisPos - lastPos +
loops *
info.loopTranslation;
totalRootMotion +=
delta * data.weight;
}
if (active && data.timeDelta != 0.0f) {
float lastTime =
info.ogreAnimState->getTimePosition();
info.ogreAnimState->addTime(data.timeDelta);
float thisTime =
info.ogreAnimState->getTimePosition();
float length = info.ogreAnimState->getLength();
bool loop = info.ogreAnimState->getLoop();
int loops = 0;
if (loop && length > 0.0f) {
loops = (int)std::round(
(lastTime + data.timeDelta -
thisTime) /
length);
}
if (at.useRootMotion && info.rootTrack) {
Ogre::TransformKeyFrame tkf(nullptr,
0.0f);
info.rootTrack->getInterpolatedKeyFrame(
lastTime, &tkf);
Ogre::Vector3 lastPos =
tkf.getTranslate();
info.rootTrack->getInterpolatedKeyFrame(
thisTime, &tkf);
Ogre::Vector3 thisPos =
tkf.getTranslate();
Ogre::Vector3 delta =
thisPos - lastPos +
loops * info.loopTranslation;
totalRootMotion += delta * data.weight;
}
}
}
if (at.useRootMotion && sceneNode) {
if (e.has<CharacterComponent>()) {
auto &cc = e.get_mut<CharacterComponent>();
if (deltaTime > 0.0000001f) {
float safeDelta = Ogre::Math::Clamp(deltaTime, 0.005f, 0.99f);
Ogre::Quaternion worldRot = sceneNode->_getDerivedOrientation();
cc.linearVelocity = worldRot * totalRootMotion / safeDelta;
cc.linearVelocity.x = Ogre::Math::Clamp(cc.linearVelocity.x, -16.0f, 16.0f);
cc.linearVelocity.z = Ogre::Math::Clamp(cc.linearVelocity.z, -16.0f, 16.0f);
cc.linearVelocity.y = Ogre::Math::Clamp(cc.linearVelocity.y, -10.5f, 10.0f);
}
} else {
sceneNode->translate(totalRootMotion,
if (at.useRootMotion && sceneNode) {
if (e.has<CharacterComponent>()) {
auto &cc = e.get_mut<CharacterComponent>();
if (deltaTime > 0.0000001f) {
float safeDelta = Ogre::Math::Clamp(
deltaTime, 0.005f, 0.99f);
Ogre::Quaternion worldRot =
sceneNode
->_getDerivedOrientation();
cc.linearVelocity = worldRot *
totalRootMotion /
safeDelta;
cc.linearVelocity.x = Ogre::Math::Clamp(
cc.linearVelocity.x, -16.0f,
16.0f);
cc.linearVelocity.z = Ogre::Math::Clamp(
cc.linearVelocity.z, -16.0f,
16.0f);
cc.linearVelocity.y = Ogre::Math::Clamp(
cc.linearVelocity.y, -10.5f,
10.0f);
}
} else {
sceneNode->translate(totalRootMotion,
Ogre::Node::TS_LOCAL);
}
}
}
/* Reset root bone to binding pose */
if (state.rootBone) {
state.rootBone->setPosition(
state.rootBindingPosition);
state.rootBone->setOrientation(
state.rootBindingOrientation);
state.rootBone->setScale(
state.rootBindingScale);
}
/* Reset root bone to binding pose */
if (state.rootBone) {
state.rootBone->setPosition(state.rootBindingPosition);
state.rootBone->setOrientation(
state.rootBindingOrientation);
state.rootBone->setScale(state.rootBindingScale);
}
/* Handle end-of-animation transitions */
checkEndTransitions(e, at, state, ctx);
});
/* Handle end-of-animation transitions */
checkEndTransitions(e, at, state, ctx);
});
}
void AnimationTreeSystem::evaluateNode(
const AnimationTreeNode &node, float parentWeight, float timeMul,
const AnimationTreeComponent &at, EntityAnimTreeState &state,
EvalContext &ctx)
void AnimationTreeSystem::evaluateNode(const AnimationTreeNode &node,
float parentWeight, float timeMul,
const AnimationTreeComponent &at,
EntityAnimTreeState &state,
EvalContext &ctx)
{
if (parentWeight < 0.001f)
return;
@@ -453,8 +479,7 @@ void AnimationTreeSystem::evaluateNode(
/* Update fades */
std::vector<Ogre::String> doneIn;
for (const auto &inName : fade.fadeIn) {
fade.weights[inName] +=
ctx.deltaTime * node.fadeSpeed;
fade.weights[inName] += ctx.deltaTime * node.fadeSpeed;
if (fade.weights[inName] >= 1.0f) {
fade.weights[inName] = 1.0f;
doneIn.push_back(inName);
@@ -465,8 +490,7 @@ void AnimationTreeSystem::evaluateNode(
std::vector<Ogre::String> doneOut;
for (const auto &outName : fade.fadeOut) {
fade.weights[outName] -=
ctx.deltaTime * node.fadeSpeed;
fade.weights[outName] -= ctx.deltaTime * node.fadeSpeed;
if (fade.weights[outName] <= 0.0f) {
fade.weights[outName] = 0.0f;
doneOut.push_back(outName);
@@ -481,19 +505,18 @@ void AnimationTreeSystem::evaluateNode(
continue;
float w = fade.weights[child.name];
if (w > 0.001f)
evaluateNode(child, parentWeight * w,
timeMul, at, state, ctx);
evaluateNode(child, parentWeight * w, timeMul,
at, state, ctx);
}
/* Queue end-transition check */
if (node.endTransitions.find(currentName) !=
node.endTransitions.end())
ctx.endChecks.push_back(
{node.name, currentName});
ctx.endChecks.push_back({ node.name, currentName });
} else if (node.type == "state") {
if (!node.children.empty())
evaluateNode(node.children[0], parentWeight,
timeMul, at, state, ctx);
evaluateNode(node.children[0], parentWeight, timeMul,
at, state, ctx);
} else if (node.type == "animation") {
auto &data = ctx.animData[node.animationName];
data.weight += parentWeight;
@@ -501,8 +524,8 @@ void AnimationTreeSystem::evaluateNode(
}
}
void AnimationTreeSystem::syncStateChanges(
AnimationTreeComponent &at, EntityAnimTreeState &state)
void AnimationTreeSystem::syncStateChanges(AnimationTreeComponent &at,
EntityAnimTreeState &state)
{
for (auto &pair : at.currentStates) {
auto itPrev = state.prevStates.find(pair.first);
@@ -519,15 +542,15 @@ void AnimationTreeSystem::syncStateChanges(
}
/* Also track newly added state machines */
for (auto &pair : at.currentStates) {
if (state.prevStates.find(pair.first) ==
state.prevStates.end())
if (state.prevStates.find(pair.first) == state.prevStates.end())
state.prevStates[pair.first] = pair.second;
}
}
void AnimationTreeSystem::checkEndTransitions(
flecs::entity e, AnimationTreeComponent &at,
EntityAnimTreeState &state, EvalContext &ctx)
void AnimationTreeSystem::checkEndTransitions(flecs::entity e,
AnimationTreeComponent &at,
EntityAnimTreeState &state,
EvalContext &ctx)
{
for (auto &pair : ctx.endChecks) {
const Ogre::String &smName = pair.first;
@@ -546,32 +569,28 @@ void AnimationTreeSystem::checkEndTransitions(
findStateNode(*smNode, stateName);
if (!stNode)
continue;
const AnimationTreeNode *animNode =
findAnimationNode(*stNode);
const AnimationTreeNode *animNode = findAnimationNode(*stNode);
if (!animNode)
continue;
auto itAnim = state.animations.find(
animNode->animationName);
auto itAnim = state.animations.find(animNode->animationName);
if (itAnim == state.animations.end())
continue;
Ogre::AnimationState *as =
itAnim->second.ogreAnimState;
Ogre::AnimationState *as = itAnim->second.ogreAnimState;
if (!as)
continue;
float t = as->getTimePosition();
float len = as->getLength();
if (len > 0.0f && t >= len * 0.99f)
setStateInternal(e, at, state, smName,
itTrans->second, true);
setStateInternal(e, at, state, smName, itTrans->second,
true);
}
}
void AnimationTreeSystem::setState(flecs::entity e,
const Ogre::String &stateMachineName,
const Ogre::String &stateName,
bool reset)
const Ogre::String &stateName, bool reset)
{
auto it = m_states.find(e.id());
if (it == m_states.end())
@@ -579,15 +598,15 @@ void AnimationTreeSystem::setState(flecs::entity e,
if (!e.has<AnimationTreeComponent>())
return;
auto &at = e.get_mut<AnimationTreeComponent>();
setStateInternal(e, at, it->second, stateMachineName,
stateName, reset);
setStateInternal(e, at, it->second, stateMachineName, stateName, reset);
}
void AnimationTreeSystem::setStateInternal(
flecs::entity e, AnimationTreeComponent &at,
EntityAnimTreeState &state,
const Ogre::String &stateMachineName,
const Ogre::String &stateName, bool reset)
void AnimationTreeSystem::setStateInternal(flecs::entity e,
AnimationTreeComponent &at,
EntityAnimTreeState &state,
const Ogre::String &stateMachineName,
const Ogre::String &stateName,
bool reset)
{
(void)e;
auto itCurrent = at.currentStates.find(stateMachineName);
@@ -627,8 +646,9 @@ void AnimationTreeSystem::setStateInternal(
}
}
Ogre::String AnimationTreeSystem::getCurrentState(
flecs::entity e, const Ogre::String &stateMachineName)
Ogre::String
AnimationTreeSystem::getCurrentState(flecs::entity e,
const Ogre::String &stateMachineName)
{
if (!e.has<AnimationTreeComponent>())
return "";
@@ -639,8 +659,9 @@ Ogre::String AnimationTreeSystem::getCurrentState(
return "";
}
const AnimationTreeNode *AnimationTreeSystem::findStateMachineNode(
const AnimationTreeNode &root, const Ogre::String &name) const
const AnimationTreeNode *
AnimationTreeSystem::findStateMachineNode(const AnimationTreeNode &root,
const Ogre::String &name) const
{
if (root.type == "stateMachine" && root.name == name)
return &root;
@@ -653,9 +674,9 @@ const AnimationTreeNode *AnimationTreeSystem::findStateMachineNode(
return nullptr;
}
const AnimationTreeNode *AnimationTreeSystem::findStateNode(
const AnimationTreeNode &smNode,
const Ogre::String &stateName) const
const AnimationTreeNode *
AnimationTreeSystem::findStateNode(const AnimationTreeNode &smNode,
const Ogre::String &stateName) const
{
for (const auto &child : smNode.children) {
if (child.type == "state" && child.name == stateName)
@@ -664,14 +685,13 @@ const AnimationTreeNode *AnimationTreeSystem::findStateNode(
return nullptr;
}
const AnimationTreeNode *AnimationTreeSystem::findAnimationNode(
const AnimationTreeNode &stateNode) const
const AnimationTreeNode *
AnimationTreeSystem::findAnimationNode(const AnimationTreeNode &stateNode) const
{
if (stateNode.type == "animation")
return &stateNode;
for (const auto &child : stateNode.children) {
const AnimationTreeNode *found =
findAnimationNode(child);
const AnimationTreeNode *found = findAnimationNode(child);
if (found)
return found;
}

View File

@@ -61,12 +61,14 @@ private:
struct EntityAnimTreeState {
Ogre::Entity *ogreEntity = nullptr;
Ogre::String ogreEntityName; /* cached for safe teardown */
Ogre::Bone *rootBone = nullptr;
Ogre::Vector3 rootBindingPosition;
Ogre::Quaternion rootBindingOrientation;
Ogre::Vector3 rootBindingScale;
std::unordered_map<Ogre::String, AnimationRuntimeInfo> animations;
std::unordered_map<Ogre::String, AnimationRuntimeInfo>
animations;
std::unordered_map<Ogre::String, FadeInfo> fadeStates;
/* Track previous currentStates to detect external changes */
std::unordered_map<Ogre::String, Ogre::String> prevStates;
@@ -80,7 +82,7 @@ private:
struct EvalContext {
float deltaTime = 0.0f;
std::unordered_map<Ogre::String, AnimEvalData> animData;
std::vector<std::pair<Ogre::String, Ogre::String>> endChecks;
std::vector<std::pair<Ogre::String, Ogre::String> > endChecks;
};
bool setupEntity(flecs::entity e, AnimationTreeComponent &at);
@@ -92,8 +94,7 @@ private:
float timeMul, const AnimationTreeComponent &at,
EntityAnimTreeState &state, EvalContext &ctx);
void checkEndTransitions(flecs::entity e, AnimationTreeComponent &at,
EntityAnimTreeState &state,
EvalContext &ctx);
EntityAnimTreeState &state, EvalContext &ctx);
void syncStateChanges(AnimationTreeComponent &at,
EntityAnimTreeState &state);
void setStateInternal(flecs::entity e, AnimationTreeComponent &at,
@@ -104,14 +105,14 @@ private:
/* Resolve template reference and copy tree if template changed */
void resolveTemplate(AnimationTreeComponent &at);
const AnimationTreeNode *findStateMachineNode(
const AnimationTreeNode &root,
const Ogre::String &name) const;
const AnimationTreeNode *findStateNode(
const AnimationTreeNode &smNode,
const Ogre::String &stateName) const;
const AnimationTreeNode *findAnimationNode(
const AnimationTreeNode &stateNode) const;
const AnimationTreeNode *
findStateMachineNode(const AnimationTreeNode &root,
const Ogre::String &name) const;
const AnimationTreeNode *
findStateNode(const AnimationTreeNode &smNode,
const Ogre::String &stateName) const;
const AnimationTreeNode *
findAnimationNode(const AnimationTreeNode &stateNode) const;
flecs::world &m_world;
Ogre::SceneManager *m_sceneMgr;