Root motion fixed now
This commit is contained in:
@@ -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;
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user