From 39a053d4ee4e29ccc12589dd0230304a6ed3c50f Mon Sep 17 00:00:00 2001 From: Sergey Lapin Date: Sat, 2 May 2026 20:25:16 +0300 Subject: [PATCH] Game mode API --- src/gamedata/CharacterAnimationModule.cpp- | 725 ++++++++++++++++ src/gamedata/CharacterAnimationModule.h- | 953 +++++++++++++++++++++ 2 files changed, 1678 insertions(+) create mode 100644 src/gamedata/CharacterAnimationModule.cpp- create mode 100644 src/gamedata/CharacterAnimationModule.h- diff --git a/src/gamedata/CharacterAnimationModule.cpp- b/src/gamedata/CharacterAnimationModule.cpp- new file mode 100644 index 0000000..cf83c34 --- /dev/null +++ b/src/gamedata/CharacterAnimationModule.cpp- @@ -0,0 +1,725 @@ +#include +#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(); + ecs.component(); + ecs.import (); + ecs.import (); + ecs.import (); + ecs.import (); + ecs.system("HandleAnimations") + .kind(flecs::OnUpdate) + .each([this](flecs::entity e, const CharacterBase &ch, + AnimationControl &anim) { + if (!anim.configured && ch.mSkeleton) { + int i, j; + e.set({}); + 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( + "main") + ->setAnimation("locomotion", + true); + anim.mAnimationSystem + ->get( + "locomotion-state") + ->setAnimation("idle", true); + } else + anim.mAnimationSystem = nullptr; + + anim.configured = true; + } + }); +#if 0 + ecs.system("RootMotionStart") + .kind(flecs::OnUpdate) + .each([this](flecs::entity e, CharacterBase &ch) { + ch.mBoneMotion = Ogre::Vector3::ZERO; + }); +#endif + ecs.system( + "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( + 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( + "HandleRootMotionVelocity") + .kind(flecs::OnUpdate) + .with() + .with() + .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()) { + 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() && + !e.has()) { + 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("HandleSwimming") + .kind(flecs::OnUpdate) + .with() + .with() + .with() + .with() + .without() + .without() + .each([this](flecs::entity e, const EngineData &eng, + const AnimationControl &anim, + const CharacterBase &ch, CharacterVelocity &gr) { + if (anim.mAnimationSystem + ->get( + "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("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("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( + "HandleNPCAnimations") + .kind(flecs::OnUpdate) + .with() + .without() + .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( + "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("HandlePlayerAnimationsActuator") + .kind(flecs::OnUpdate) + .with() + .each([](flecs::entity e, const CharacterBase &ch, + const CharacterInActuator &inact, + AnimationControl &anim) { + if (!anim.configured) + return; + AnimationNodeStateMachine *main_sm = + anim.mAnimationSystem + ->get( + "main"); + AnimationNodeStateMachine *actuator_sm = + anim.mAnimationSystem + ->get( + "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( + "HandlePlayerAnimationsNoActuator") + .kind(flecs::OnUpdate) + .with() + .without() + .each([](flecs::entity e, const CharacterBase &ch, + AnimationControl &anim) { + if (!anim.configured) + return; + if (!anim.mAnimationSystem) + return; + AnimationNodeStateMachine *main_sm = + anim.mAnimationSystem + ->get( + "main"); + Ogre::String current_state = main_sm->getCurrentState(); + if (current_state != "locomotion") + main_sm->setAnimation("locomotion", true); + }); + ecs.system( + "HandlePlayerAnimations") + .kind(flecs::OnUpdate) + .with() + .with() + .without() + .each([](flecs::entity e, const Input &input, + const CharacterBase &ch, AnimationControl &anim) { + if (!anim.configured) + return; + AnimationNodeStateMachine *state_machine = + anim.mAnimationSystem + ->get( + "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("HandlePlayerAnimations2") + .kind(flecs::OnUpdate) + .with() + .with() + .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([&](flecs::entity trig) { + if (Ogre::Math::Abs(input.motion.z - + act.prevMotion.z) > + 0.001f) { + if (input.motion.z < 0) { + trig.get_mut() + .add(e, + "actuator_forward", + trig, e); + trig.modified< + EventData>(); + } + if (input.motion.z > 0) { + trig.get_mut() + .add(e, + "actuator_backward", + trig, e); + trig.modified< + EventData>(); + } + } + if (input.act_pressed) { + trig.get_mut().add( + e, "actuator_action", + trig, e); + trig.modified(); + } + // ECS::get_mut().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().add( + e, + "_in_actuator_forward", + e, e); + } + if (input.motion.z > 0) { + e.get_mut().add( + e, + "_in_actuator_backward", + e, e); + } + } + if (input.act_pressed) { + e.get_mut().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().call_handler("actuator_forward", e); + } + ECS::get_mut().call_handler("actuator_controls_update"); + } +#endif + act.prevMotion = input.motion; + }); + ecs.system("UpdateEvents") + .kind(flecs::OnUpdate) + .with() + .each([](flecs::entity e, EventData &evt) { + for (auto ev : evt.events) { + std::cout << "character event: " << ev.event + << std::endl; + /* parse character events */ + e.each([&](flecs::entity trig) { + /* if triggered, dispatch events to trigger */ + trig.get_mut().add( + ev.sender, ev.event, + trig, // it is easier this way to identify trigger entity + ev.e2); + }); + } + evt.events.clear(); + }); + +#ifdef VDEBUG + ecs.system("CharacterGravityStatus") + .kind(flecs::OnUpdate) + .with() + .with() + .each([](flecs::entity e, const CharacterBase &ch) { + if (e.has()) + std::cout << "gravity\n"; + else + std::cout << "no gravity\n"; + if (e.has()) + 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( + "UpdateBodyCast") + .kind(flecs::OnUpdate) + .without() + .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( + { "idle", { 0, 0, 0 } }); + ECS::get().mLua->call_handler( + "character_enter", e, + ECS::get().entities.at( + const_cast( + result.m_collisionObject))); + } + }); +#endif + struct AnimationSetCommand : public GameWorld::Command { + int operator()(const std::vector &args) + override + { + GameWorld::ValueParameter *param_e = + static_cast *>(args[0]); + OgreAssert(param_e->get().is_valid(), "bad entity"); + GameWorld::ValueParameter *param_node = + static_cast *>(args[1]); + GameWorld::ValueParameter *param_state = + static_cast *>(args[2]); + if (param_e->get().has() && + param_e->get().get().configured == + true) { + const AnimationControl &control = + param_e->get().get(); + AnimationNodeStateMachine *sm = + control.mAnimationSystem + ->get( + param_node->get()); + bool reset = false; + if (args.size() == 4) { + GameWorld::ValueParameter + *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().add_command( + "set_animation_state"); +} +} diff --git a/src/gamedata/CharacterAnimationModule.h- b/src/gamedata/CharacterAnimationModule.h- new file mode 100644 index 0000000..b7b340a --- /dev/null +++ b/src/gamedata/CharacterAnimationModule.h- @@ -0,0 +1,953 @@ +#ifndef CHARACTER_ANIMATION_MODULE_H_ +#define CHARACTER_ANIMATION_MODULE_H_ +#include +#include +#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(t); + Ogre::Node *trackNode = nodeTrack->getAssociatedNode(); +#if 0 + Ogre::Any entityAny = + trackNode->getUserObjectBindings().getUserAny("empty"); + if (entityAny.has_value()) + e = Ogre::any_cast(entityAny); +#endif + Ogre::TransformKeyFrame *vkf = + static_cast(kf); + Ogre::KeyFrame *kf1, *kf2; + Ogre::TransformKeyFrame *k1, *k2; + unsigned short firstKeyIndex; + float tm = t->getKeyFramesAtTime(timeIndex, &kf1, &kf2, + &firstKeyIndex); + k1 = static_cast(kf1); + k2 = static_cast(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().mBoneMotion = deltaMotion; + e.get_mut().mBonePrevMotion = prevTranslation; + e.modified(); + return true; + } +}; +struct AnimationTrigger; +struct AnimationTriggerSubscriber { + virtual void operator()(const AnimationTrigger *trigger) = 0; +}; +struct AnimationTrigger { + Ogre::String name; + float time; + float weight; + std::vector 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 children; + float m_weight; + Ogre::String m_name; + std::multimap 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( + 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 stateMap; + std::set 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( + 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( + 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 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 animation_list; + std::vector 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 animation_nodes; + AnimationNode *parent; + std::list parent_stack; + std::unordered_map nodeMap; + std::vector 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().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( + 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 T *get(const Ogre::String &name) + { + return static_cast(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 > animations; +}; +struct CharacterAnimationModule { + CharacterAnimationModule(flecs::world &ecs); +}; +} +#endif