Root motion fixed now

This commit is contained in:
2026-04-29 18:45:37 +03:00
parent 02fa78764a
commit 998984f75a
9 changed files with 141 additions and 60 deletions

View File

@@ -37,6 +37,12 @@ struct CharacterComponent {
/* Dirty flag — triggers rebuild of the Jolt character */
bool dirty = true;
/* When true, the scene node position is driven by root motion
* (AnimationTreeSystem), not by physics. The physics character
* position is synced to match the scene node each frame, and
* physics does NOT write its position back to the scene node. */
bool useRootMotion = false;
/* Floor detection: raycast downward to find ground before enabling gravity */
bool hasFloor = false;
float floorCheckDistance = 2.0f;

View File

@@ -165,7 +165,7 @@ void LuaState::lateSetup()
{
Ogre::DataStreamPtr stream =
Ogre::ResourceGroupManager::getSingleton().openResource(
"data.lua", "LuaScripts");
"data2.lua", "LuaScripts");
std::cout << "stream: " << stream->getAsString() << "\n";
if (luaL_dostring(L, stream->getAsString().c_str()) != LUA_OK) {
std::cout << "error: " << lua_tostring(L, -1) << "\n";

View File

@@ -790,10 +790,10 @@ public:
node->_setDerivedOrientation(JoltPhysics::convert(q));
}
for (JPH::Character *ch : characters) {
if (body_interface.IsAdded(ch->GetBodyID())) {
JPH::BodyID bID = ch->GetBodyID();
if (body_interface.IsAdded(bID)) {
ch->PostSimulation(0.1f);
Ogre::SceneNode *node =
id2node[ch->GetBodyID()];
Ogre::SceneNode *node = id2node[bID];
if (node)
node->_setDerivedPosition(
JoltPhysics::convert(
@@ -1633,6 +1633,13 @@ public:
return it->second;
return nullptr;
}
void setRootMotionCharacter(JPH::BodyID id, bool enabled)
{
(void)id;
(void)enabled;
/* No longer needed - root motion drives physics velocity,
* and physics writes position back to scene node normally. */
}
};
void physics()
@@ -1976,5 +1983,10 @@ JoltPhysicsWrapper::getSceneNodeFromBodyID(JPH::BodyID id) const
return phys->getSceneNodeFromBodyID(id);
}
void JoltPhysicsWrapper::setRootMotionCharacter(JPH::BodyID id, bool enabled)
{
phys->setRootMotionCharacter(id, enabled);
}
template <>
JoltPhysicsWrapper *Ogre::Singleton<JoltPhysicsWrapper>::msSingleton = 0;

View File

@@ -230,5 +230,12 @@ public:
bool bodyIsCharacter(JPH::BodyID id) const;
void destroyCharacter(std::shared_ptr<JPH::Character> ch);
Ogre::SceneNode *getSceneNodeFromBodyID(JPH::BodyID id) const;
/* Mark a character body as root-motion-driven.
* When true, Physics::update() will NOT write the character's
* position back to the scene node after the physics step,
* because the scene node position is driven by root motion
* from AnimationTreeSystem. */
void setRootMotionCharacter(JPH::BodyID id, bool enabled);
};
#endif

View File

@@ -300,6 +300,12 @@ void AnimationTreeSystem::update(float deltaTime)
flecs::entity e,
AnimationTreeComponent
&at) {
/* Reset root-motion velocity for all entities.
* Root motion velocity will be set below only for
* entities with active root motion. */
if (e.has<CharacterComponent>())
e.get_mut<CharacterComponent>().linearVelocity =
Ogre::Vector3::ZERO;
resolveTemplate(at);
if (at.dirty) {
@@ -369,39 +375,58 @@ void AnimationTreeSystem::update(float deltaTime)
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;
Ogre::Vector3 delta;
if (info.hasPrevRootPos) {
/*
* Compute delta from previous
* root bone position. Detect
* animation wrapping by checking
* if time decreased (wrapped
* around). When wrapping, add
* the loop translation to
* compensate for the jump from
* end back to start.
*/
delta = thisPos -
info.prevRootPos;
if (thisTime < lastTime) {
/* Animation wrapped */
delta +=
info.loopTranslation;
}
} else {
delta = Ogre::Vector3::ZERO;
}
info.prevRootPos = thisPos;
info.hasPrevRootPos = true;
totalRootMotion += delta * data.weight;
}
}
}
if (at.useRootMotion && sceneNode) {
/*
* Compute root motion velocity from the animation
* displacement. Do NOT move the scene node directly -
* the physics character's velocity drives movement,
* and physics writes the position back to the scene
* node naturally. This avoids jitter caused by
* teleporting the physics character to match a
* root-motion-driven scene node position.
*/
if (e.has<CharacterComponent>()) {
auto &cc = e.get_mut<CharacterComponent>();
cc.useRootMotion = true;
if (deltaTime > 0.0000001f) {
float safeDelta = Ogre::Math::Clamp(
deltaTime, 0.005f, 0.99f);
@@ -421,9 +446,6 @@ void AnimationTreeSystem::update(float deltaTime)
cc.linearVelocity.y, -10.5f,
10.0f);
}
} else {
sceneNode->translate(totalRootMotion,
Ogre::Node::TS_LOCAL);
}
}
@@ -637,9 +659,18 @@ void AnimationTreeSystem::setStateInternal(flecs::entity e,
auto itAnim = state.animations.find(
animNode->animationName);
if (itAnim != state.animations.end() &&
itAnim->second.ogreAnimState)
itAnim->second.ogreAnimState) {
itAnim->second.ogreAnimState
->setTimePosition(0.0f);
/* Reset root motion tracking
* so the first frame of the
* new animation doesn't
* produce a large delta from
* the previous animation's
* root position. */
itAnim->second.hasPrevRootPos =
false;
}
}
}
}

View File

@@ -51,6 +51,11 @@ private:
Ogre::AnimationState *ogreAnimState = nullptr;
Ogre::NodeAnimationTrack *rootTrack = nullptr;
Ogre::Vector3 loopTranslation = Ogre::Vector3::ZERO;
/* Previous root bone position for root motion delta
* computation. Used to detect animation wrapping and
* compute smooth deltas across loop boundaries. */
Ogre::Vector3 prevRootPos = Ogre::Vector3::ZERO;
bool hasPrevRootPos = false;
};
struct FadeInfo {
@@ -67,6 +72,11 @@ private:
Ogre::Quaternion rootBindingOrientation;
Ogre::Vector3 rootBindingScale;
/* Root motion unapply/reapply state */
Ogre::Vector3 appliedRootTranslation = Ogre::Vector3::ZERO;
Ogre::Quaternion appliedRootRotation =
Ogre::Quaternion::IDENTITY;
std::unordered_map<Ogre::String, AnimationRuntimeInfo>
animations;
std::unordered_map<Ogre::String, FadeInfo> fadeStates;

View File

@@ -165,9 +165,10 @@ void CharacterSystem::setupEntity(flecs::entity e, CharacterComponent &cc)
cc.hasFloor = false;
std::cout << "CharacterSystem::setupEntity: entity=" << e.id()
<< " nodePos=" << transform.node->_getDerivedPosition()
<< " parent=" << (transform.node->getParent() ?
transform.node->getParent()->getName() :
"<root>")
<< " parent="
<< (transform.node->getParent() ?
transform.node->getParent()->getName() :
"<root>")
<< " radius=" << cc.radius << " height=" << cc.height
<< " dirty=" << cc.dirty << " hasFloor=" << cc.hasFloor
<< std::endl;
@@ -280,13 +281,24 @@ void CharacterSystem::update(float deltaTime)
Ogre::Vector3 nodePos =
state.sceneNode->_getDerivedPosition();
/* If scene node was moved externally (editor gizmo),
* teleport character there */
/*
* Root motion drives physics velocity (AnimationTreeSystem
* computes it from the animation displacement). The physics
* character moves naturally via velocity, and physics writes
* the position back to the scene node. No teleportation or
* position sync needed - this avoids jitter.
*
* If the scene node was moved externally (editor gizmo),
* teleport the physics character to match.
*/
Ogre::Vector3 diff = nodePos - charPos;
if (diff.squaredLength() > 0.001f) {
state.character->SetPosition(
JoltPhysics::convert(nodePos));
}
/* Root motion velocity is applied via linear velocity
* above. No special physics handling needed - physics
* writes position back to scene node normally. */
/* Apply velocity via Jolt linear velocity.
* Preserve physics-driven Y velocity when no explicit

View File

@@ -10,8 +10,8 @@
#include <cmath>
PathFollowingSystem::PathFollowingSystem(flecs::world &world,
Ogre::SceneManager *sceneMgr,
NavMeshSystem *navSystem)
Ogre::SceneManager *sceneMgr,
NavMeshSystem *navSystem)
: m_world(world)
, m_sceneMgr(sceneMgr)
, m_navSystem(navSystem)
@@ -32,8 +32,8 @@ Ogre::Vector3 PathFollowingSystem::getEntityPosition(flecs::entity e)
}
void PathFollowingSystem::rotateTowards(flecs::entity e,
const Ogre::Vector3 &direction,
float deltaTime)
const Ogre::Vector3 &direction,
float deltaTime)
{
if (!e.has<TransformComponent>())
return;
@@ -95,8 +95,8 @@ void PathFollowingSystem::update(float deltaTime)
navmeshEntity = e;
});
m_world
.query<PathFollowingComponent, CharacterComponent, TransformComponent>()
m_world.query<PathFollowingComponent, CharacterComponent,
TransformComponent>()
.each([&](flecs::entity e, PathFollowingComponent &pf,
CharacterComponent &cc, TransformComponent &trans) {
(void)trans;
@@ -112,7 +112,8 @@ void PathFollowingSystem::update(float deltaTime)
toFinal.y = 0;
float distToFinal = toFinal.length();
if (distToFinal < 0.5f) {
cc.linearVelocity = Ogre::Vector3::ZERO;
if (!cc.useRootMotion)
cc.linearVelocity = Ogre::Vector3::ZERO;
pf.hasTarget = false;
pf.currentLocomotionState = "idle";
pf.path.clear();
@@ -128,8 +129,8 @@ void PathFollowingSystem::update(float deltaTime)
if (navmeshEntity.is_alive() && m_navSystem) {
std::vector<Ogre::Vector3> newPath;
bool found = m_navSystem->findPath(
navmeshEntity, charPos, targetPos,
newPath);
navmeshEntity, charPos,
targetPos, newPath);
if (found && !newPath.empty()) {
pf.path = std::move(newPath);
pf.pathIndex = 0;
@@ -160,9 +161,13 @@ void PathFollowingSystem::update(float deltaTime)
if (pf.pathIndex >= (int)pf.path.size()) {
// Last waypoint - check if close to final target
if (distToFinal < 0.5f) {
cc.linearVelocity = Ogre::Vector3::ZERO;
if (!cc.useRootMotion)
cc.linearVelocity =
Ogre::Vector3::
ZERO;
pf.hasTarget = false;
pf.currentLocomotionState = "idle";
pf.currentLocomotionState =
"idle";
pf.path.clear();
pf.pathIndex = 0;
applyLocomotionState(e);
@@ -192,12 +197,18 @@ void PathFollowingSystem::update(float deltaTime)
pf.currentLocomotionState = "walk";
}
cc.linearVelocity = toTarget * speed;
/* Only set velocity if root motion is not
* active. When root motion is active,
* AnimationTreeSystem already computed the
* velocity from the animation displacement. */
if (!cc.useRootMotion)
cc.linearVelocity = toTarget * speed;
// Rotate character to face movement direction
rotateTowards(e, toTarget, deltaTime);
} else {
cc.linearVelocity = Ogre::Vector3::ZERO;
if (!cc.useRootMotion)
cc.linearVelocity = Ogre::Vector3::ZERO;
}
applyLocomotionState(e);

View File

@@ -303,14 +303,10 @@ void PlayerControllerSystem::updateLocomotion(PlayerControllerComponent &pc,
return;
// Skip locomotion if input is locked by an executing action
if (pc.inputLocked) {
auto &cc = state.targetEntity.get_mut<CharacterComponent>();
cc.linearVelocity = Ogre::Vector3::ZERO;
if (pc.inputLocked)
return;
}
GameInputState &input = m_editorApp->getGameInputState();
auto &cc = state.targetEntity.get_mut<CharacterComponent>();
// Get camera yaw for relative movement
Ogre::Quaternion yawRot(Ogre::Degree(state.yaw), Ogre::Vector3::UNIT_Y);
@@ -325,28 +321,26 @@ void PlayerControllerSystem::updateLocomotion(PlayerControllerComponent &pc,
if (right.squaredLength() > 0.0001f)
right.normalise();
Ogre::Vector3 desiredVel = Ogre::Vector3::ZERO;
Ogre::Vector3 desiredDir = Ogre::Vector3::ZERO;
if (input.w)
desiredVel += forward;
desiredDir += forward;
if (input.s)
desiredVel -= forward;
desiredDir -= forward;
if (input.a)
desiredVel -= right;
desiredDir -= right;
if (input.d)
desiredVel += right;
desiredDir += right;
bool isMoving = desiredVel.squaredLength() > 0.0001f;
float speed = input.shift ? 5.0f : 2.5f;
bool isMoving = desiredDir.squaredLength() > 0.0001f;
if (isMoving) {
desiredVel.normalise();
cc.linearVelocity = desiredVel * speed;
desiredDir.normalise();
// Rotate character to face movement direction
auto &transform =
state.targetEntity.get_mut<TransformComponent>();
if (transform.node) {
Ogre::Vector3 flatForward = desiredVel;
Ogre::Vector3 flatForward = desiredDir;
flatForward.y = 0;
if (flatForward.squaredLength() > 0.0001f) {
flatForward.normalise();
@@ -361,8 +355,6 @@ void PlayerControllerSystem::updateLocomotion(PlayerControllerComponent &pc,
targetRot, true));
}
}
} else {
cc.linearVelocity = Ogre::Vector3::ZERO;
}
// Update animation state