Game mode API

This commit is contained in:
2026-05-02 20:25:16 +03:00
parent c5da977857
commit 39a053d4ee
2 changed files with 1678 additions and 0 deletions

View File

@@ -0,0 +1,725 @@
#include <iostream>
#include "Components.h"
#include "EventTriggerModule.h"
#include "CharacterModule.h"
#include "PhysicsModule.h"
#include "CharacterAnimationModule.h"
#include "EventModule.h"
#include "TerrainModule.h"
#include "WaterModule.h"
#include "world-build.h"
namespace ECS
{
CharacterAnimationModule::CharacterAnimationModule(flecs::world &ecs)
{
ecs.module<CharacterAnimationModule>();
ecs.component<AnimationControl>();
ecs.import <EventModule>();
ecs.import <TerrainModule>();
ecs.import <WaterModule>();
ecs.import <PhysicsModule>();
ecs.system<const CharacterBase, AnimationControl>("HandleAnimations")
.kind(flecs::OnUpdate)
.each([this](flecs::entity e, const CharacterBase &ch,
AnimationControl &anim) {
if (!anim.configured && ch.mSkeleton) {
int i, j;
e.set<EventData>({});
ch.mSkeleton->setBlendMode(
Ogre::ANIMBLEND_CUMULATIVE);
if (ch.mBodyEnt->getSkeleton()->getName() ==
"normal-male.skeleton") {
anim.mAnimationSystem =
new AnimationSystem(ch.mBodyEnt,
e, false);
anim.mAnimationSystem
->builder()
/* clang-format off */
->output()
->state_machine(ANIM_FADE_SPEED, "main")
->state("locomotion")
->state_machine(ANIM_FADE_SPEED, "locomotion-state")
->state("idle")
->animation("idle")
->end()
->state("walking")
->animation("walking")
->end()
->state("running")
->animation("running")
->end()
->state("treading_water")
->animation("treading_water")
->end()
->state("swimming")
->animation("swimming")
->end()
->state("swimming-fast")
->speed(20.0f)
->animation("swimming")
->end()
->end()
->end()
->end()
->state("actuator")
->state_machine(ANIM_FADE_SPEED * 10.0f, "actuator-state")
->state("hanging-idle")
->animation("hanging-idle")
->end()
->state("swimming-hold-edge")
->animation("swimming-hold-edge")
->end()
->state("swimming-edge-climb")
->animation("swimming-edge-climb")
->trigger(e, "end_of_climb", 0.99f, "animation:swimming-edge-climb:end")
->end()
->state("hanging-climb")
->animation("hanging-climb")
->trigger(e, "end_of_climb2", 0.99f, "animation:hanging-climb:end")
->end()
->state("idle")
->animation("idle-act")
->end()
->state("pass-character")
->animation("pass-character")
->trigger(e, "pass-character", 0.99f, "animation:pass-character:end")
->end()
->state("character-talk")
->animation("character-talk")
->end()
->state("sitting")
->animation("sitting-chair")
->end()
->state("sitting-ground")
->animation("sitting-ground")
->end()
->transition_end("swimming-edge-climb", "idle")
->transition_end("hanging-climb", "idle")
->transition_end("pass-character", "idle")
->end()
->end()
->end();
/* clang-format on */
anim.mAnimationSystem
->get<AnimationNodeStateMachine>(
"main")
->setAnimation("locomotion",
true);
anim.mAnimationSystem
->get<AnimationNodeStateMachine>(
"locomotion-state")
->setAnimation("idle", true);
} else
anim.mAnimationSystem = nullptr;
anim.configured = true;
}
});
#if 0
ecs.system<CharacterBase>("RootMotionStart")
.kind(flecs::OnUpdate)
.each([this](flecs::entity e, CharacterBase &ch) {
ch.mBoneMotion = Ogre::Vector3::ZERO;
});
#endif
ecs.system<const EngineData, CharacterBase, AnimationControl>(
"HandleAnimations1")
.kind(flecs::OnUpdate)
.each([this](flecs::entity e, const EngineData &eng,
CharacterBase &ch, AnimationControl &anim) {
float delta = eng.delta;
// ch.mBoneMotion = Ogre::Vector3::ZERO;
bool result = false;
if (anim.mAnimationSystem) {
result = anim.mAnimationSystem->addTime(delta);
if (!ch.mRootBone)
return;
} else
return;
// The value we get is interpolated value. When result is true it is new step
#if 0
Ogre::Vector3 offset = ch.mRootBone->getPosition();
ch.mBoneMotion = static_cast<RootMotionListener *>(
anim.mListener)
->getDeltaMotion();
ch.mRootBone->setPosition(Ogre::Vector3::ZERO);
Ogre::Vector3 d = offset - ch.mBonePrevMotion;
ch.mBonePrevMotion = offset;
std::cout << "length: " << d.length() << std::endl;
if (d.squaredLength() > 0.02f * 0.02f)
d = offset;
if (d.squaredLength() > 0.02f * 0.02f)
d = Ogre::Vector3::ZERO;
std::cout << "length2: " << d.length() << std::endl;
OgreAssert(d.length() < 0.5f, "bad offset");
ch.mBoneMotion = d;
#endif
#if 0
if (result) {
if (d.squaredLength() > 0.0f)
ch.mBoneMotion =
ch.mRootBone->getPosition() -
ch.mBoneMotion;
else
ch.mBoneMotion =
ch.mRootBone->getPosition();
} else {
ch.mBoneMotion = ch.mRootBone->getPosition() -
ch.mBoneMotion;
}
#endif
#undef VDEBUG
#ifdef VDEBUG
std::cout << "root motion: " << delta << ": "
<< ch.mBoneMotion << " - "
<< ch.mRootBone->getPosition()
<< " result: " << result << std::endl;
#endif
#undef VDEBUG
#if 0
// ch.mRootBone->setPosition(Ogre::Vector3::ZERO);
ch.mBonePrevMotion = offset;
#endif
});
ecs.system<const EngineData, CharacterBase, CharacterVelocity>(
"HandleRootMotionVelocity")
.kind(flecs::OnUpdate)
.with<TerrainReady>()
.with<WaterReady>()
.each([this](flecs::entity e, const EngineData &eng,
CharacterBase &ch, CharacterVelocity &v) {
if (eng.delta < 0.0000001f)
return;
if (!ch.mBodyNode)
return;
Ogre::Quaternion rot = ch.mBodyNode->getOrientation();
Ogre::Vector3 pos = ch.mBodyNode->getPosition();
Ogre::Vector3 boneMotion = ch.mBoneMotion;
v.velocity = Ogre::Vector3::ZERO;
float safeDelta =
Ogre::Math::Clamp(eng.delta, 0.001f, 0.99f);
#if 0
if (!e.has<CharacterInActuator>()) {
v.velocity = Ogre::Math::lerp(
v.velocity,
rot * boneMotion / safeDelta, 0.99f);
} else {
// v.velocity = rot * boneMotion / safeDelta;
v.velocity = Ogre::Math::lerp(
v.velocity,
rot * boneMotion / safeDelta, 0.99f);
}
#endif
v.velocity = rot * boneMotion / safeDelta;
#if 0
if (!e.has<CharacterDisablePhysics>() &&
!e.has<CharacterInActuator>()) {
if (eng.startupDelay <= 0.0f)
v.velocity += v.gvelocity;
v.velocity.y = Ogre::Math::Clamp(v.velocity.y,
-10.5f, 10.0f);
}
#endif
// if (v.velocity.squaredLength() > 1.4f * 1.4f)
// v.velocity = v.velocity.normalisedCopy() * 1.4f;
// ch.mBoneMotion = Ogre::Vector3::ZERO;
// safety
// std::cout << "velocity: " << v.velocity << std::endl;
v.velocity.x =
Ogre::Math::Clamp(v.velocity.x, -16.0f, 16.0f);
v.velocity.z =
Ogre::Math::Clamp(v.velocity.z, -16.0f, 16.0f);
v.velocity.y =
Ogre::Math::Clamp(v.velocity.y, -10.5f, 10.0f);
#if 0
v.velocity.y = 0.0f;
#endif
});
#if 0
ecs.system<const EngineData, const AnimationControl,
const CharacterBase, CharacterVelocity>("HandleSwimming")
.kind(flecs::OnUpdate)
.with<TerrainReady>()
.with<WaterReady>()
.with<InWater>()
.with<CharacterBuoyancy>()
.without<CharacterDisablePhysics>()
.without<CharacterInActuator>()
.each([this](flecs::entity e, const EngineData &eng,
const AnimationControl &anim,
const CharacterBase &ch, CharacterVelocity &gr) {
if (anim.mAnimationSystem
->get<AnimationNodeStateMachine>(
"locomotion-state")
->getCurrentState() == "swimming") {
float h = Ogre::Math::Clamp(
0.0f - ch.mBodyNode->getPosition().y,
0.0f, 2000.0f);
if (h > 0.05 && h < 2.0f)
gr.gvelocity.y += 0.1f * (h + 1.0f) *
h * eng.delta;
}
});
#endif
ecs.system<const EngineData, CharacterBase, AnimationControl,
CharacterVelocity>("HandleRootMotion")
.kind(flecs::OnUpdate)
.each([this](flecs::entity e, const EngineData &eng,
CharacterBase &ch, AnimationControl &anim,
CharacterVelocity &v) {
if (!ch.mBodyNode)
return;
if (eng.delta < 0.0000001f)
return;
OgreAssert(eng.delta > 0.0f, "Zero delta");
int maxPen = 0;
Ogre::Vector3 colNormal;
bool is_on_floor = false;
bool penetration = false;
#if 0
if (eng.startupDelay < 0.0f) {
if (body.mController) {
Ogre::Vector3 rotMotion =
v.velocity * eng.delta;
rotMotion.x = Ogre::Math::Clamp(
rotMotion.x, -0.04f, 0.04f);
rotMotion.y = Ogre::Math::Clamp(
rotMotion.y, -0.025f, 0.1f);
rotMotion.z = Ogre::Math::Clamp(
rotMotion.z, -0.04f, 0.04f);
btVector3 currentPosition =
body.mGhostObject
->getWorldTransform()
.getOrigin();
is_on_floor =
body.mController->isOnFloor();
penetration = body.mController
->isPenetrating();
if (is_on_floor)
v.gvelocity =
Ogre::Vector3::ZERO;
btTransform from(
Ogre::Bullet::convert(
ch.mBodyNode
->getOrientation()),
Ogre::Bullet::convert(
ch.mBodyNode
->getPosition()));
ch.mBodyNode->_setDerivedPosition(
ch.mBodyNode
->_getDerivedPosition() +
rotMotion);
}
}
#endif
});
#if 0
ecs.system<CharacterVelocity, CharacterBase>("HandleRootMotionEnd")
.kind(flecs::OnUpdate)
.each([this](flecs::entity e, CharacterVelocity &v,
CharacterBase &ch) {
// zero the velocity;
// v.velocity = Ogre::Vector3::ZERO;
// ch.mBoneMotion = Ogre::Vector3::ZERO;
});
#endif
ecs.system<const Input, const CharacterBase, AnimationControl>(
"HandleNPCAnimations")
.kind(flecs::OnUpdate)
.with<Character>()
.without<Player>()
.each([](flecs::entity e, const Input &input,
const CharacterBase &ch, AnimationControl &anim) {
if (!anim.configured)
return;
if (!anim.mAnimationSystem)
return;
AnimationNodeStateMachine *state_machine =
anim.mAnimationSystem
->get<AnimationNodeStateMachine>(
"locomotion-state");
Ogre::String current_state =
state_machine->getCurrentState();
Ogre::String next_state = "idle";
if (current_state != "treading_water" &&
ch.is_submerged)
next_state = "treading_water";
if (current_state != "idle" && !ch.is_submerged)
next_state = "idle";
state_machine->setAnimation(next_state);
});
ecs.system<const CharacterBase, const CharacterInActuator,
AnimationControl>("HandlePlayerAnimationsActuator")
.kind(flecs::OnUpdate)
.with<Character>()
.each([](flecs::entity e, const CharacterBase &ch,
const CharacterInActuator &inact,
AnimationControl &anim) {
if (!anim.configured)
return;
AnimationNodeStateMachine *main_sm =
anim.mAnimationSystem
->get<AnimationNodeStateMachine>(
"main");
AnimationNodeStateMachine *actuator_sm =
anim.mAnimationSystem
->get<AnimationNodeStateMachine>(
"actuator-state");
Ogre::String current_state = main_sm->getCurrentState();
if (current_state != "actuator")
main_sm->setAnimation("actuator", true);
actuator_sm->setAnimation(inact.animationState, true);
});
ecs.system<const CharacterBase, AnimationControl>(
"HandlePlayerAnimationsNoActuator")
.kind(flecs::OnUpdate)
.with<Character>()
.without<CharacterInActuator>()
.each([](flecs::entity e, const CharacterBase &ch,
AnimationControl &anim) {
if (!anim.configured)
return;
if (!anim.mAnimationSystem)
return;
AnimationNodeStateMachine *main_sm =
anim.mAnimationSystem
->get<AnimationNodeStateMachine>(
"main");
Ogre::String current_state = main_sm->getCurrentState();
if (current_state != "locomotion")
main_sm->setAnimation("locomotion", true);
});
ecs.system<const Input, const CharacterBase, AnimationControl>(
"HandlePlayerAnimations")
.kind(flecs::OnUpdate)
.with<Character>()
.with<Player>()
.without<CharacterInActuator>()
.each([](flecs::entity e, const Input &input,
const CharacterBase &ch, AnimationControl &anim) {
if (!anim.configured)
return;
AnimationNodeStateMachine *state_machine =
anim.mAnimationSystem
->get<AnimationNodeStateMachine>(
"locomotion-state");
Ogre::String current_state =
state_machine->getCurrentState();
bool controls_idle = input.motion.zeroLength();
bool anim_is_idle = current_state == "idle" ||
current_state == "treading_water";
bool anim_is_walking = current_state == "walking";
bool anim_is_running = current_state == "running";
bool anim_is_swimming_slow = current_state ==
"swimming";
bool anim_is_swimming_fast = current_state ==
"swimming-fast";
bool anim_is_swimming = anim_is_swimming_slow ||
anim_is_swimming_fast;
bool anim_is_motion = anim_is_walking ||
anim_is_running ||
anim_is_swimming;
bool start_motion = !controls_idle && anim_is_idle;
bool end_motion = controls_idle && !anim_is_idle;
Ogre::String next_state = current_state;
if (controls_idle && anim_is_idle) {
if (current_state != "treading_water" &&
ch.is_submerged)
next_state = "treading_water";
else if (current_state != "idle" &&
!ch.is_submerged)
next_state = "idle";
state_machine->setAnimation(next_state);
} else if (start_motion) {
if (ch.is_submerged) {
if (input.fast)
next_state = "swimming-fast";
else
next_state = "swimming";
} else {
if (input.fast)
next_state = "running";
else
next_state = "walking";
}
state_machine->setAnimation(next_state, true);
} else if (end_motion) {
if (ch.is_submerged)
state_machine->setAnimation(
"treading_water");
else
state_machine->setAnimation("idle");
} else {
if (ch.is_submerged) {
if (input.fast &&
!anim_is_swimming_fast) {
next_state = "swimming-fast";
} else if (!input.fast &&
!anim_is_swimming_slow) {
next_state = "swimming";
}
} else {
if (input.fast && !anim_is_running)
next_state = "running";
else if (!input.fast &&
!anim_is_walking)
next_state = "walking";
}
if (current_state != next_state)
state_machine->setAnimation(next_state);
}
});
ecs.system<const Input, const CharacterBase, AnimationControl,
CharacterInActuator>("HandlePlayerAnimations2")
.kind(flecs::OnUpdate)
.with<Character>()
.with<Player>()
.each([](flecs::entity e, const Input &input,
const CharacterBase &ch, AnimationControl &anim,
CharacterInActuator &act) {
bool controls_idle = input.motion.zeroLength();
if (!controls_idle) {
std::cout << "motion.z: "
<< Ogre::Math::Abs(input.motion.z -
act.prevMotion.z)
<< std::endl;
bool trigger_event = false;
e.each<InTrigger>([&](flecs::entity trig) {
if (Ogre::Math::Abs(input.motion.z -
act.prevMotion.z) >
0.001f) {
if (input.motion.z < 0) {
trig.get_mut<EventData>()
.add(e,
"actuator_forward",
trig, e);
trig.modified<
EventData>();
}
if (input.motion.z > 0) {
trig.get_mut<EventData>()
.add(e,
"actuator_backward",
trig, e);
trig.modified<
EventData>();
}
}
if (input.act_pressed) {
trig.get_mut<EventData>().add(
e, "actuator_action",
trig, e);
trig.modified<EventData>();
}
// ECS::get_mut<LuaData>().call_handler(
// "actuator_update", trig, e);
trigger_event = true;
});
if (!trigger_event) {
if (Ogre::Math::Abs(input.motion.z -
act.prevMotion.z) >
0.001f) {
if (input.motion.z < 0) {
e.get_mut<EventData>().add(
e,
"_in_actuator_forward",
e, e);
}
if (input.motion.z > 0) {
e.get_mut<EventData>().add(
e,
"_in_actuator_backward",
e, e);
}
}
if (input.act_pressed) {
e.get_mut<EventData>().add(
e,
"_in_actuator_action",
e, e);
}
}
act.prevMotion.x = input.motion.x;
act.prevMotion.y = input.motion.y;
act.prevMotion.z = input.motion.z;
}
#if 0
if (!controls_idle) {
if (Ogre::Math::Abs(input.motion.z - act.prevMotion.z) > 0.001f) {
if (input.motion.z < 0)
ECS::get_mut<LuaData>().call_handler("actuator_forward", e);
}
ECS::get_mut<LuaData>().call_handler("actuator_controls_update");
}
#endif
act.prevMotion = input.motion;
});
ecs.system<EventData>("UpdateEvents")
.kind(flecs::OnUpdate)
.with<Character>()
.each([](flecs::entity e, EventData &evt) {
for (auto ev : evt.events) {
std::cout << "character event: " << ev.event
<< std::endl;
/* parse character events */
e.each<InTrigger>([&](flecs::entity trig) {
/* if triggered, dispatch events to trigger */
trig.get_mut<EventData>().add(
ev.sender, ev.event,
trig, // it is easier this way to identify trigger entity
ev.e2);
});
}
evt.events.clear();
});
#ifdef VDEBUG
ecs.system<const CharacterBase>("CharacterGravityStatus")
.kind(flecs::OnUpdate)
.with<Character>()
.with<Player>()
.each([](flecs::entity e, const CharacterBase &ch) {
if (e.has<CharacterGravity>())
std::cout << "gravity\n";
else
std::cout << "no gravity\n";
if (e.has<InWater>())
std::cout << "in water\n";
else
std::cout << "out of water\n";
std::cout
<< "h=" << ch.mBodyNode->_getDerivedPosition().y
<< std::endl;
});
#endif
#if 0
ecs.system<const EngineData, const CharacterBase, CharacterBody>(
"UpdateBodyCast")
.kind(flecs::OnUpdate)
.without<CharacterInActuator>()
.each([](flecs::entity e, const EngineData &eng,
const CharacterBase &ch, CharacterBody &body) {
struct ResultCallback
: public btCollisionWorld::RayResultCallback {
btCollisionObject *m_me;
btVector3 m_from, m_to, m_hitNormalWorld,
m_hitPointWorld;
ResultCallback(btCollisionObject *me,
const btVector3 &from,
const btVector3 &to)
: m_me(me)
, m_from(from)
, m_to(to)
{
}
btScalar addSingleResult(
btCollisionWorld::LocalRayResult
&rayResult,
bool normalInWorldSpace) override
{
if (rayResult.m_collisionObject == m_me)
return 1.0f;
if (!btPairCachingGhostObject::upcast(
rayResult.m_collisionObject))
return 1.0f;
if (!(rayResult.m_collisionObject
->getCollisionFlags() &
btCollisionObject::
CF_CHARACTER_OBJECT))
return 1.0f;
m_closestHitFraction =
rayResult.m_hitFraction;
m_collisionObject =
rayResult.m_collisionObject;
if (normalInWorldSpace)
m_hitNormalWorld =
rayResult
.m_hitNormalLocal;
else
m_hitNormalWorld =
m_collisionObject
->getWorldTransform()
.getBasis() *
rayResult
.m_hitNormalLocal;
m_hitPointWorld.setInterpolate3(
m_from, m_to,
rayResult.m_hitFraction);
return rayResult.m_hitFraction;
}
};
Ogre::Vector3 offset(0.0f, 0.5f, 0.0f);
float dist = 0.5f;
btVector3 a = Ogre::Bullet::convert(
ch.mBodyNode->getPosition() + offset),
b(Ogre::Bullet::convert(
ch.mBodyNode->getPosition() +
ch.mBodyNode->getOrientation() *
Ogre::Vector3(0, 0, dist) +
offset));
ResultCallback result(body.mGhostObject, a, b);
// body.mGhostObject->rayTest(a, b, result);
eng.mWorld->getBtWorld()->rayTest(a, b, result);
if (result.hasHit()) {
std::cout << "Hit!!! " << result.m_hitPointWorld
<< std::endl;
e.set<CharacterInActuator>(
{ "idle", { 0, 0, 0 } });
ECS::get<LuaBase>().mLua->call_handler(
"character_enter", e,
ECS::get<Body2Entity>().entities.at(
const_cast<btCollisionObject *>(
result.m_collisionObject)));
}
});
#endif
struct AnimationSetCommand : public GameWorld::Command {
int operator()(const std::vector<GameWorld::Parameter *> &args)
override
{
GameWorld::ValueParameter<flecs::entity> *param_e =
static_cast<GameWorld::ValueParameter<
flecs::entity> *>(args[0]);
OgreAssert(param_e->get().is_valid(), "bad entity");
GameWorld::ValueParameter<std::string> *param_node =
static_cast<GameWorld::ValueParameter<
std::string> *>(args[1]);
GameWorld::ValueParameter<std::string> *param_state =
static_cast<GameWorld::ValueParameter<
std::string> *>(args[2]);
if (param_e->get().has<AnimationControl>() &&
param_e->get().get<AnimationControl>().configured ==
true) {
const AnimationControl &control =
param_e->get().get<AnimationControl>();
AnimationNodeStateMachine *sm =
control.mAnimationSystem
->get<AnimationNodeStateMachine>(
param_node->get());
bool reset = false;
if (args.size() == 4) {
GameWorld::ValueParameter<bool>
*param_reset = static_cast<
GameWorld::ValueParameter<
bool> *>(
args[3]);
reset = param_reset->get();
}
sm->setAnimation(param_state->get(), reset);
std::cout << "animation switch: "
<< param_node->get() << " "
<< param_state->get() << std::endl;
}
return 0;
}
};
ECS::get_mut<GameWorld>().add_command<AnimationSetCommand>(
"set_animation_state");
}
}

View File

@@ -0,0 +1,953 @@
#ifndef CHARACTER_ANIMATION_MODULE_H_
#define CHARACTER_ANIMATION_MODULE_H_
#include <Ogre.h>
#include <flecs.h>
#include "GameData.h"
#include "CharacterModule.h"
#include "LuaData.h"
#include "EventModule.h"
namespace ECS
{
class RootMotionListener : public Ogre::NodeAnimationTrack::Listener {
Ogre::Vector3 prevTranslation;
mutable Ogre::Vector3 deltaMotion;
flecs::entity e;
public:
RootMotionListener(flecs::entity e)
: Ogre::NodeAnimationTrack::Listener()
, e(e)
, prevTranslation(Ogre::Vector3::ZERO)
, deltaMotion(Ogre::Vector3::ZERO)
{
}
bool getInterpolatedKeyFrame(const Ogre::AnimationTrack *t,
const Ogre::TimeIndex &timeIndex,
Ogre::KeyFrame *kf) override
{
const Ogre::NodeAnimationTrack *nodeTrack =
static_cast<const Ogre::NodeAnimationTrack *>(t);
Ogre::Node *trackNode = nodeTrack->getAssociatedNode();
#if 0
Ogre::Any entityAny =
trackNode->getUserObjectBindings().getUserAny("empty");
if (entityAny.has_value())
e = Ogre::any_cast<flecs::entity>(entityAny);
#endif
Ogre::TransformKeyFrame *vkf =
static_cast<Ogre::TransformKeyFrame *>(kf);
Ogre::KeyFrame *kf1, *kf2;
Ogre::TransformKeyFrame *k1, *k2;
unsigned short firstKeyIndex;
float tm = t->getKeyFramesAtTime(timeIndex, &kf1, &kf2,
&firstKeyIndex);
k1 = static_cast<Ogre::TransformKeyFrame *>(kf1);
k2 = static_cast<Ogre::TransformKeyFrame *>(kf2);
Ogre::Vector3 translation;
Ogre::Quaternion rotation;
if (tm == 0.0f) {
rotation = k1->getRotation();
translation = k1->getTranslate();
deltaMotion = translation;
} else {
rotation = Ogre::Quaternion::nlerp(
tm, k1->getRotation(), k2->getRotation(), true);
translation =
k1->getTranslate() +
(k2->getTranslate() - k1->getTranslate()) * tm;
deltaMotion = translation - prevTranslation;
if (deltaMotion.squaredLength() >
translation.squaredLength())
deltaMotion = translation;
}
vkf->setTranslate(deltaMotion);
vkf->setRotation(rotation);
vkf->setScale(Ogre::Vector3(1, 1, 1));
prevTranslation = translation;
e.get_mut<CharacterBase>().mBoneMotion = deltaMotion;
e.get_mut<CharacterBase>().mBonePrevMotion = prevTranslation;
e.modified<CharacterBase>();
return true;
}
};
struct AnimationTrigger;
struct AnimationTriggerSubscriber {
virtual void operator()(const AnimationTrigger *trigger) = 0;
};
struct AnimationTrigger {
Ogre::String name;
float time;
float weight;
std::vector<AnimationTriggerSubscriber *> subscriber_list;
float getTriggerTime() const
{
return time;
}
float getMinWeight() const
{
return weight;
}
const Ogre::String &getName() const
{
return name;
}
void notify(float weight)
{
int i;
if (weight < this->weight)
return;
for (i = 0; i < subscriber_list.size(); i++)
(*subscriber_list[i])(this);
}
void addSubscriber(AnimationTriggerSubscriber *sub)
{
if (std::find(subscriber_list.begin(), subscriber_list.end(),
sub) != subscriber_list.end())
return;
subscriber_list.push_back(sub);
}
void removeSubscriber(AnimationTriggerSubscriber *sub)
{
auto it = std::find(subscriber_list.begin(),
subscriber_list.end(), sub);
if (it != subscriber_list.end())
subscriber_list.erase(it);
}
void clearSubscribers()
{
subscriber_list.clear();
}
AnimationTrigger(const Ogre::String name, float time, float weight)
: name(name)
, time(time)
, weight(weight)
{
}
};
struct Animation {
Ogre::AnimationState *mAnimationState;
Ogre::Animation *mSkelAnimation;
Ogre::NodeAnimationTrack *mHipsTrack;
Ogre::NodeAnimationTrack *mRootTrack;
float m_weight;
float m_accWeight;
Animation(Ogre::Skeleton *skeleton, Ogre::AnimationState *animState,
Ogre::Animation *skelAnimation)
: mAnimationState(animState)
, mSkelAnimation(skelAnimation)
, m_weight(0)
, m_accWeight(0)
{
// FIXME
int j;
mRootTrack = nullptr;
mHipsTrack = nullptr;
for (const auto &it : mSkelAnimation->_getNodeTrackList()) {
Ogre::NodeAnimationTrack *track = it.second;
Ogre::String trackName =
track->getAssociatedNode()->getName();
if (trackName == "mixamorig:Hips") {
mHipsTrack = track;
} else if (trackName == "Root") {
mRootTrack = track;
}
}
OgreAssert(mRootTrack, "no root track");
}
Ogre::String getName()
{
return mAnimationState->getAnimationName();
}
void setLoop(bool loop)
{
mAnimationState->setLoop(loop);
}
bool getLoop() const
{
return mAnimationState->getLoop();
}
void setEnabled(bool enabled)
{
mAnimationState->setEnabled(enabled);
}
bool getEnabled() const
{
return mAnimationState->getEnabled();
}
void setWeight(float weight)
{
bool enabled = weight > 0.001f;
setEnabled(enabled);
mAnimationState->setWeight(weight);
m_weight = weight;
}
float getWeight() const
{
return m_weight;
}
bool addTime(float time)
{
bool result = mAnimationState->getEnabled();
if (!result)
return result;
Ogre::TimeIndex index = mSkelAnimation->_getTimeIndex(
mAnimationState->getTimePosition());
Ogre::KeyFrame *kf1, *kf2;
unsigned short prev_index, next_index;
mRootTrack->getKeyFramesAtTime(index, &kf1, &kf2, &prev_index);
unsigned int previous_frame = index.getKeyIndex();
mAnimationState->addTime(time);
index = mSkelAnimation->_getTimeIndex(
mAnimationState->getTimePosition());
mRootTrack->getKeyFramesAtTime(index, &kf1, &kf2, &next_index);
return prev_index != next_index;
}
void reset()
{
mAnimationState->setTimePosition(0);
}
void resetAccWeight()
{
m_accWeight = 0;
}
void increaseAccWeight(float weight)
{
m_accWeight += weight;
}
float getAccWeight() const
{
return m_accWeight;
}
float getLength() const
{
return mAnimationState->getLength();
if (getEnabled())
return mAnimationState->getLength();
else
return 0.0f;
}
float getTimePosition() const
{
return mAnimationState->getTimePosition();
if (getEnabled())
return mAnimationState->getTimePosition();
else
return 0.0f;
}
};
struct AnimationNode {
std::vector<AnimationNode *> children;
float m_weight;
Ogre::String m_name;
std::multimap<float, AnimationTrigger *> trigger_list;
AnimationNode()
: m_weight(0)
{
}
virtual bool addTime(float time) = 0;
virtual void setWeight(float weight) = 0;
virtual void reset() = 0;
virtual float getLength() const = 0;
virtual float getTimePosition() const = 0;
float getWeight()
{
return m_weight;
}
const Ogre::String &getName()
{
return m_name;
}
void setName(const Ogre::String &name)
{
m_name = name;
}
virtual float getTime() const
{
float l = getLength();
if (l > 0.0f)
return getTimePosition() / l;
return 0.0f;
}
void addTrigger(AnimationTrigger *trigger)
{
trigger_list.insert(std::pair<float, AnimationTrigger *>(
trigger->getTriggerTime(), trigger));
}
void clearTriggers()
{
auto it = trigger_list.begin();
while (it != trigger_list.end()) {
delete it->second;
it++;
}
trigger_list.clear();
}
float mpreUpdateTime;
void preUpdateTriggers()
{
mpreUpdateTime = getTime();
}
void postUpdateTriggers(float delta)
{
float postUpdateTime = getTime();
bool positive = delta >= 0.0f;
if (positive)
updateTriggers(mpreUpdateTime, postUpdateTime);
else
updateTriggers(postUpdateTime, mpreUpdateTime);
}
void updateTriggers(float currentTime, float nextTime)
{
int i;
float weight = getWeight();
if (currentTime <= nextTime) {
auto it = trigger_list.lower_bound(currentTime);
while (it != trigger_list.end()) {
if (nextTime <=
it->second->getTriggerTime()) // in future, sorrted by time
return;
it->second->notify(weight);
it++;
}
} else {
updateTriggers(currentTime, 1);
updateTriggers(0, nextTime);
}
}
};
struct AnimationNodeAnimation : AnimationNode {
Animation *mAnimation;
bool enabled;
AnimationNodeAnimation(Animation *animation)
: AnimationNode()
, mAnimation(animation)
{
}
bool addTime(float time)
{
bool ret;
preUpdateTriggers();
ret = mAnimation->addTime(time);
postUpdateTriggers(time);
return ret;
}
void setWeight(float weight)
{
m_weight = weight;
enabled = weight > 0.001f;
}
void reset()
{
mAnimation->reset();
}
float getLength() const
{
return mAnimation->getLength();
if (enabled)
return mAnimation->getLength();
else
return 0.0f;
}
float getTimePosition() const
{
return mAnimation->getTimePosition();
if (enabled)
return mAnimation->getTimePosition();
else
return 0.0f;
}
};
struct AnimationNodeStateMachineState : AnimationNode {
AnimationNodeStateMachineState()
: AnimationNode()
{
}
bool addTime(float time)
{
bool ret;
preUpdateTriggers();
ret = children[0]->addTime(time);
postUpdateTriggers(time);
return ret;
}
void setWeight(float weight)
{
m_weight = weight;
bool enabled = weight > 0.001f;
children[0]->setWeight(weight);
}
void reset()
{
children[0]->reset();
}
float getLength() const
{
return children[0]->getLength();
}
float getTimePosition() const
{
return children[0]->getTimePosition();
}
};
struct AnimationNodeSpeed : AnimationNode {
float m_speed;
bool enabled;
AnimationNodeSpeed(float speed)
: AnimationNode()
, m_speed(speed)
, enabled(false)
{
}
bool addTime(float time)
{
bool ret;
preUpdateTriggers();
ret = children[0]->addTime(time * m_speed);
postUpdateTriggers(time);
return ret;
}
void setWeight(float weight)
{
m_weight = weight;
children[0]->setWeight(weight);
}
void reset()
{
children[0]->reset();
}
float getLength() const
{
if (m_speed > 0.0f || m_speed < 0.0f)
return children[0]->getLength() / m_speed;
return 0.0f;
}
float getTimePosition() const
{
if (m_speed > 0.0f || m_speed < 0.0f)
return children[0]->getTimePosition() / m_speed;
return 0.0f;
}
float getTime() const override
{
float l = children[0]->getLength();
if (l > 0.0f)
return children[0]->getTimePosition() / l;
return 0.0f;
}
};
struct AnimationNodeStateMachine : AnimationNode {
std::map<Ogre::String, AnimationNode *> stateMap;
std::set<AnimationNode *> fade_in, fade_out;
AnimationNode *currentAnim, *nextAnim;
float fade_speed;
Ogre::String mCurrentStateName;
bool configured;
bool debug;
AnimationNodeStateMachine(float fade_speed, bool debug = false)
: AnimationNode()
, currentAnim(nullptr)
, nextAnim(nullptr)
, fade_speed(fade_speed)
, mCurrentStateName("")
, configured(false)
, debug(debug)
{
m_weight = 1.0f;
}
bool addTime(float time)
{
int i;
preUpdateTriggers();
if (!configured) {
configure();
configured = true;
}
#ifdef VDEBUG
if (debug) {
std::cout << "state machine addTime" << std::endl;
std::cout
<< "state machine children: " << children.size()
<< std::endl;
}
#endif
for (i = 0; i < children.size(); i++) {
#ifdef VDEBUG
if (debug)
std::cout << "child weight: " << i << " "
<< children[i]->getWeight()
<< std::endl;
#endif
AnimationNode *child = children[i];
if (fade_in.find(child) != fade_in.end()) {
Ogre::Real newWeight =
child->getWeight() + time * fade_speed;
child->setWeight(Ogre::Math::Clamp<Ogre::Real>(
newWeight * m_weight, 0, m_weight));
#ifdef VDEBUG
if (debug) {
std::cout << "fade in: " << newWeight
<< std::endl;
std::cout << "m_weight: " << m_weight
<< std::endl;
}
#endif
if (newWeight >= 1)
fade_in.erase(child);
}
if (fade_out.find(child) != fade_out.end()) {
Ogre::Real newWeight =
child->getWeight() - time * fade_speed;
child->setWeight(Ogre::Math::Clamp<Ogre::Real>(
newWeight * m_weight, 0, 1));
if (newWeight <= 0)
fade_out.erase(child);
}
}
OgreAssert(currentAnim, "bad current anim");
bool ret = false;
if (currentAnim)
ret = currentAnim->addTime(time);
postUpdateTriggers(time);
return ret;
}
void setWeight(float weight)
{
int i;
if (weight > m_weight && currentAnim)
fade_in.insert(currentAnim);
if (weight < m_weight && currentAnim &&
currentAnim->getWeight() > weight)
currentAnim->setWeight(weight);
m_weight = weight;
bool enabled = weight > 0.001f;
/* do not update child state yet */
}
void addState(AnimationNode *state)
{
const Ogre::String &name = state->getName();
stateMap[name] = state;
state->setWeight(0);
fade_in.erase(state);
fade_out.erase(state);
std::cout << "registered state: " << name << std::endl;
}
void configure()
{
int i;
if (debug)
std::cout << "children: " << children.size()
<< std::endl;
for (i = 0; i < children.size(); i++)
addState(children[i]);
if (debug)
std::cout << "configure called" << std::endl;
}
void reset()
{
int i;
for (i = 0; i < children.size(); i++)
children[i]->reset();
}
void setAnimation(const Ogre::String &anim_state, bool reset = false)
{
if (!configured) {
configure();
configured = true;
}
OgreAssert(stateMap.find(anim_state) != stateMap.end(),
"Bad animation state: " + anim_state);
nextAnim = stateMap[anim_state];
if (nextAnim == currentAnim)
return;
if (currentAnim != nullptr) {
fade_out.insert(currentAnim);
fade_in.erase(currentAnim);
}
fade_out.erase(nextAnim);
fade_in.insert(nextAnim);
nextAnim->setWeight(0);
if (reset)
nextAnim->reset();
currentAnim = nextAnim;
mCurrentStateName = anim_state;
}
const Ogre::String &getCurrentState() const
{
return mCurrentStateName;
}
float getLength() const
{
if (currentAnim)
return currentAnim->getLength();
else
return 0.0f;
}
float getTimePosition() const
{
if (currentAnim)
return currentAnim->getTimePosition();
else
return 0.0f;
}
};
#define ANIM_FADE_SPEED \
7.5f // animation crossfade speed in % of full weight per second
struct AnimationNodeOutput : AnimationNode {
float m_weight;
float m_speed;
AnimationNodeOutput()
: AnimationNode()
, m_weight(1.0f)
, m_speed(1.0f)
{
}
bool addTime(float time)
{
bool ret;
preUpdateTriggers();
ret = children[0]->addTime(time * m_speed);
postUpdateTriggers(time);
return ret;
}
void setWeight(float weight)
{
m_weight = weight;
bool enabled = weight > 0.001f;
children[0]->setWeight(weight);
}
void reset()
{
children[0]->reset();
}
float getLength() const
{
return children[0]->getLength();
}
float getTimePosition() const
{
return children[0]->getTimePosition();
}
};
struct AnimationSystem : AnimationNode {
RootMotionListener *mListener;
bool debug;
AnimationSystem(Ogre::Entity *ent, flecs::entity e, bool debug = false)
: mListener(OGRE_NEW RootMotionListener(e))
, debug(debug)
, m_builder(this, debug)
{
static std::set<Ogre::Skeleton *> skeletons;
Ogre::AnimationStateSet *animSet = ent->getAllAnimationStates();
Ogre::Skeleton *skeleton = ent->getSkeleton();
const Ogre::AnimationStateMap &states =
animSet->getAnimationStates();
for (auto it = states.begin(); it != states.end(); it++) {
Ogre::Animation *skeletonAnimation =
skeleton->getAnimation(it->first);
Ogre::NodeAnimationTrack *rootTrack = nullptr,
*hipsTrack = nullptr;
for (const auto &it :
skeletonAnimation->_getNodeTrackList()) {
Ogre::NodeAnimationTrack *track = it.second;
Ogre::String trackName =
track->getAssociatedNode()->getName();
if (trackName == "mixamorig:Hips")
hipsTrack = track;
else if (trackName == "Root")
rootTrack = track;
}
if (true || skeletons.find(skeleton) == skeletons.end()) {
if (!rootTrack) {
Ogre::Bone *bone =
skeleton->getBone("Root");
rootTrack =
skeletonAnimation
->createNodeTrack(
bone->getHandle(),
bone);
Ogre::TransformKeyFrame *kf =
rootTrack->createNodeKeyFrame(
0.0f);
kf->setTranslate(Ogre::Vector3::ZERO);
kf->setRotation(
Ogre::Quaternion::IDENTITY);
}
rootTrack->setListener(mListener);
}
OgreAssert(rootTrack, "no root track");
Ogre::Node *controlNode =
rootTrack->getAssociatedNode();
if (controlNode) {
Ogre::Any listenerAny =
controlNode->getUserObjectBindings()
.getUserAny("entity");
if (!listenerAny.has_value()) {
controlNode->getUserObjectBindings()
.setUserAny("entity", e);
}
}
Animation *animation = new Animation(
skeleton, it->second, skeletonAnimation);
#ifdef VDEBUG
std::cout << "animation: " << animNames[i] << std::endl;
#endif
animation->setLoop(true);
add_animation(it->first, animation);
}
skeletons.insert(skeleton);
}
std::unordered_map<Ogre::String, Animation *> animation_list;
std::vector<Animation *> vanimation_list;
void add_animation(const Ogre::String &name, Animation *animation)
{
OgreAssert(animation, "No animation " + name);
animation_list[name] = animation;
vanimation_list.push_back(animation);
}
void clear_animations()
{
animation_list.clear();
vanimation_list.clear();
}
struct AnimationSystemBuilder {
AnimationSystem *mAnimationSystem;
std::vector<AnimationNode *> animation_nodes;
AnimationNode *parent;
std::list<AnimationNode *> parent_stack;
std::unordered_map<Ogre::String, AnimationNode *> nodeMap;
std::vector<AnimationNodeAnimation *> animationNodeList;
bool debug;
AnimationSystemBuilder(AnimationSystem *animationSystem,
bool debug = false)
: mAnimationSystem(animationSystem)
, debug(debug)
{
}
AnimationSystemBuilder *output()
{
AnimationNodeOutput *onode = new AnimationNodeOutput();
animation_nodes.push_back(onode);
parent = onode;
return this;
}
AnimationSystemBuilder *
animation(const Ogre::String &animation_name)
{
OgreAssert(parent, "bad parent");
Animation *animation =
mAnimationSystem->animation_list[animation_name];
OgreAssert(animation,
"bad animation " + animation_name);
AnimationNodeAnimation *onode =
new AnimationNodeAnimation(animation);
OgreAssert(onode, "bad animation");
OgreAssert(onode->mAnimation, "bad animation");
animation_nodes.push_back(onode);
parent->children.push_back(onode);
animationNodeList.push_back(onode);
return this;
}
AnimationSystemBuilder *trigger(flecs::entity e,
const Ogre::String &name,
float time,
const Ogre::String &event)
{
struct EventSubscriber : AnimationTriggerSubscriber {
flecs::entity ent;
Ogre::String event;
void operator()(const AnimationTrigger *trigger)
{
ent.get_mut<EventData>().add(ent, event,
ent, ent);
}
EventSubscriber(flecs::entity e,
const Ogre::String &event)
: ent(e)
, event(event)
{
}
};
OgreAssert(parent, "bad parent");
AnimationTrigger *trigger =
new AnimationTrigger(name, time, 0.1f);
EventSubscriber *sub = new EventSubscriber(e, event);
trigger->addSubscriber(sub);
parent->addTrigger(trigger);
return this;
} // leaf too...
AnimationSystemBuilder *
transition_end(const Ogre::String &state_from,
const Ogre::String &state_to)
{
struct EndTransitionSubscriber
: AnimationTriggerSubscriber {
AnimationNodeStateMachine *sm;
Ogre::String next_state;
bool reset;
void operator()(const AnimationTrigger *trigger)
{
sm->setAnimation(next_state, reset);
}
EndTransitionSubscriber(
AnimationNodeStateMachine *sm,
const Ogre::String &next_state,
bool reset = true)
: sm(sm)
, next_state(next_state)
, reset(reset)
{
}
};
OgreAssert(parent, "no parent");
AnimationNodeStateMachine *sm =
static_cast<AnimationNodeStateMachine *>(
parent);
OgreAssert(sm, "no state machine");
AnimationTrigger *trigger = new AnimationTrigger(
"transition:" + state_from + "_" + state_to,
0.99f, 0.1f);
EndTransitionSubscriber *sub =
new EndTransitionSubscriber(sm, state_to);
int i;
bool ok = false;
for (i = 0; i < sm->children.size(); i++) {
if (sm->children[i]->getName() == state_from) {
trigger->addSubscriber(sub);
sm->children[i]->addTrigger(trigger);
ok = true;
break;
}
}
OgreAssert(ok, "Failed to set transition");
return this;
}
AnimationSystemBuilder *speed(float speed,
const Ogre::String &anchor = "")
{
OgreAssert(parent, "bad parent");
AnimationNodeSpeed *onode =
new AnimationNodeSpeed(speed);
animation_nodes.push_back(onode);
parent->children.push_back(onode);
parent_stack.push_back(parent);
parent = onode;
if (anchor.length() > 0)
nodeMap[anchor] = onode;
return this;
}
AnimationSystemBuilder *
state_machine(float fade_time = ANIM_FADE_SPEED,
const Ogre::String &anchor = "")
{
OgreAssert(parent, "bad parent");
AnimationNodeStateMachine *onode =
new AnimationNodeStateMachine(fade_time, debug);
animation_nodes.push_back(onode);
parent->children.push_back(onode);
parent_stack.push_back(parent);
parent = onode;
if (anchor.length() > 0)
nodeMap[anchor] = onode;
return this;
}
AnimationSystemBuilder *state(const Ogre::String &state_name)
{
OgreAssert(parent, "bad parent");
AnimationNodeStateMachineState *onode =
new AnimationNodeStateMachineState;
animation_nodes.push_back(onode);
parent->children.push_back(onode);
parent_stack.push_back(parent);
parent = onode;
onode->setName(state_name);
return this;
}
AnimationSystemBuilder *end()
{
parent = parent_stack.back();
parent_stack.pop_back();
return this;
}
};
AnimationSystemBuilder m_builder;
bool addTime(float time)
{
int i;
preUpdateTriggers();
bool ret = m_builder.animation_nodes[0]->addTime(time);
for (i = 0; i < m_builder.animationNodeList.size(); i++) {
AnimationNodeAnimation *anim =
m_builder.animationNodeList[i];
OgreAssert(anim->mAnimation, "No animation");
float weight = anim->getWeight();
anim->mAnimation->increaseAccWeight(weight);
#ifdef VDEBUG
if (debug)
std::cout << i << " node: "
<< anim->mAnimation->getName() << " "
<< weight << std::endl;
#endif
}
for (i = 0; i < vanimation_list.size(); i++) {
float weight = vanimation_list[i]->getAccWeight();
vanimation_list[i]->setWeight(weight);
vanimation_list[i]->resetAccWeight();
#define VDEBUG
#ifdef VDEBUG
if (debug && vanimation_list[i]->getEnabled())
std::cout << i << " animation: "
<< vanimation_list[i]->getName()
<< " " << weight << std::endl;
#endif
#undef VDEBUG
}
postUpdateTriggers(time);
return ret;
}
void setWeight(float weight)
{
m_builder.animation_nodes[0]->setWeight(weight);
}
void reset()
{
m_builder.animation_nodes[0]->reset();
}
AnimationSystemBuilder *builder()
{
m_builder.animation_nodes.reserve(8);
m_builder.parent = nullptr;
return &m_builder;
}
template <class T> T *get(const Ogre::String &name)
{
return static_cast<T *>(m_builder.nodeMap[name]);
}
float getLength() const
{
return m_builder.animation_nodes[0]->getLength();
}
float getTimePosition() const
{
return m_builder.animation_nodes[0]->getTimePosition();
}
};
struct AnimationControl {
bool configured;
AnimationSystem *mAnimationSystem;
};
struct DefaultAnimation {
std::vector<std::pair<std::string, std::string> > animations;
};
struct CharacterAnimationModule {
CharacterAnimationModule(flecs::world &ecs);
};
}
#endif