1
0
mirror of https://github.com/rwengine/openrw.git synced 2024-11-22 10:22:52 +01:00

Overhaul animation system and character activities

Replaces animator with a simpler system that can overlay multiple
animations

Character animation split into two layers, motion and action.
Walking, running and jumping animations are played on the first
layer, actions such as firing can be overlayed on the second.
More work is needed to limit overlap to only the weapons that make
sense.
This commit is contained in:
Daniel Evans 2016-04-17 04:54:19 +01:00
parent ceb711b25e
commit 8a5be54b91
12 changed files with 494 additions and 442 deletions

View File

@ -61,10 +61,6 @@ protected:
bool updateActivity();
void setActivity(Activity* activity);
glm::vec3 rawMovement;
bool running;
float vehicleIdle;
// Goal related variables
@ -108,7 +104,7 @@ public:
*/
CharacterObject* getCharacter() const;
void setRawMovement(const glm::vec3& movement);
void setMoveDirection(const glm::vec3& movement);
void setRunning(bool run);

View File

@ -1,13 +1,12 @@
#pragma once
#ifndef _ANIMATOR_HPP_
#define _ANIMATOR_HPP_
#include <map>
#include <cstdint>
#include <glm/glm.hpp>
#include <glm/gtx/quaternion.hpp>
#include <queue>
#include <map>
#include <rw/defines.hpp>
#include <loaders/LoaderIFP.hpp>
#include <cstdint>
class Model;
class ModelFrame;
@ -17,13 +16,37 @@ class Skeleton;
/**
* @brief calculates animation frame matrices, as well as procedural frame
* animation.
*
* Animations are played using the playAnimation() interface, this will add
* the animation to the animator. This sets the configuration to use for the
* animation, such as it's speed and time.
*
* The Animator will blend all active animations together.
*/
class Animator
{
/**
* @brief _animations Queue of animations to play.
* @brief Stores data required to animate a model frame
*/
std::queue<Animation*> _animations;
struct BoneInstanceData
{
unsigned int frameIndex;
};
/**
* @brief The AnimationState struct stores information about playing animations
*/
struct AnimationState
{
Animation* animation;
/// Timestamp of the last frame
float time;
/// Speed multiplier
float speed;
/// Automatically restart
bool repeat;
std::map<AnimationBone*, BoneInstanceData> boneInstances;
};
/**
* @brief model The model being animated.
@ -36,50 +59,40 @@ class Animator
Skeleton* skeleton;
/**
* @brief Stores data required to animate a model frame
* @brief Currently playing animations
*/
struct BoneInstanceData
{
unsigned int frameIdx;
};
std::map<AnimationBone*, BoneInstanceData> boneInstances;
// Used in determining how far the skeleton being animated has moved
// From it's local origin.
glm::vec3 lastRootPosition;
glm::quat lastRootRotation;
float time;
float serverTime;
float lastServerTime;
bool playing;
bool repeat;
void reset();
std::vector<AnimationState> animations;
public:
Animator(Model* model, Skeleton* skeleton);
/**
* @brief setAnimation Sets the currently active animation.
* @param animation
* @param repeat If true animation will restart after ending.
* @todo Interpolate between the new and old frames.
*/
void setAnimation(Animation* animation, bool repeat = true);
Animation* getAnimation(unsigned int slot)
{
if (slot < animations.size())
{
return animations[slot].animation;
}
return nullptr;
}
void queueAnimation(Animation* animation);
void playAnimation(unsigned int slot, Animation* anim, float speed, bool repeat)
{
if (slot >= animations.size())
{
animations.resize(slot+1);
}
animations[slot] = { anim, 0.f, speed, repeat, {} };
}
void next();
const std::queue<Animation*> getAnimationQueue() const
{ return _animations; }
const Animation* getAnimation() const
{ return _animations.empty() ? nullptr : _animations.front(); }
void setAnimationSpeed(unsigned int slot, float speed)
{
RW_CHECK(slot < animations.size(), "Slot out of range");
if (slot < animations.size())
{
animations[slot].speed = speed;
}
}
/**
* @brief tick Update animation paramters for server-side data.
@ -87,39 +100,12 @@ public:
*/
void tick(float dt);
/**
* @brief render Update frame matricies for client-side animation.
* @param dt
*/
void render(float dt);
/**
* @brief getRootTranslation Returns the translation of the root bone from the last server-side frame.
* @return
*/
glm::vec3 getRootTranslation() const;
/**
* @brief getTimeTranslation returns the translation of the root bone at the current time.
* @return
*/
glm::vec3 getTimeTranslation(float alpha) const;
/**
* @brief getRootRotation see getRootTranslation
* @return
*/
glm::quat getRootRotation() const;
/**
* Returns true if the animation has finished playing.
*/
bool isCompleted() const;
float getAnimationTime(float alpha = 0.f) const;
void setAnimationTime(float time);
void setPlaying( bool play ) { playing = play; }
bool isCompleted(unsigned int slot) const;
float getAnimationTime(unsigned int slot) const;
void setAnimationTime(unsigned int slot, float time);
};
#endif

View File

@ -9,6 +9,10 @@
constexpr size_t maxInventorySlots = 13;
// Animation slots used for character animation blending
constexpr unsigned int AnimIndexMovement = 0;
constexpr unsigned int AnimIndexAction = 1;
struct CharacterWeaponSlot
{
// Assuming these match the entries in weapon.dat
@ -66,12 +70,18 @@ struct AnimationGroup
Animation* car_getin_rhs;
Animation* car_getout_rhs;
Animation* kd_front;
Animation* ko_shot_front;
AnimationGroup()
: idle(nullptr), walk(nullptr), walk_start(nullptr), run(nullptr),
jump_start(nullptr), jump_glide(nullptr), jump_land(nullptr),
car_sit(nullptr), car_sit_low(nullptr), car_open_lhs(nullptr),
car_getin_lhs(nullptr), car_getout_lhs(nullptr), car_open_rhs(nullptr),
car_getin_rhs(nullptr), car_getout_rhs(nullptr)
car_getin_rhs(nullptr)
, car_getout_rhs(nullptr)
, kd_front(nullptr)
, ko_shot_front(nullptr)
{}
};
@ -90,12 +100,15 @@ private:
void createActor(const glm::vec2& size = glm::vec2(0.45f, 1.2f));
void destroyActor();
// Incredibly hacky "move in this direction".
bool _hasTargetPosition;
glm::vec3 _targetPosition;
glm::vec3 movement;
bool running;
bool jumped;
float jumpSpeed;
bool motionBlockedByActivity;
glm::vec3 updateMovementAnimation(float dt);
public:
static const float DefaultJumpSpeed;
@ -161,6 +174,11 @@ public:
void jump();
void setJumpSpeed(float speed);
float getJumpSpeed() const;
bool isOnGround() const;
bool canTurn() const;
void setRunning(bool run) { running = run; }
bool isRunning() const { return running; }
/**
* Resets the Actor to the nearest AI Graph node
@ -168,10 +186,23 @@ public:
*/
void resetToAINode();
void setTargetPosition( const glm::vec3& target );
void clearTargetPosition();
void setMovement(const glm::vec3& _m) { movement = _m; }
const glm::vec3& getMovement() const { return movement; }
void playAnimation(Animation* animation, bool repeat);
/**
* @brief playActivityAnimation Plays an animation for an activity.
* @param animation The animation to play
* @param repeat
* @param blocking Wether movement is still alowed
*
* This allows controller activities to play their own animations and
* controll blending with movement.
*/
void playActivityAnimation(Animation* animation, bool repeat, bool blocking);
/**
* @brief activityFinished removes activity animation
*/
void activityFinished();
void addToInventory( InventoryItem* item );
void setActiveItem( int slot );

View File

@ -20,15 +20,16 @@ void CharacterController::setActivity(CharacterController::Activity* activity)
{
if( _currentActivity ) delete _currentActivity;
_currentActivity = activity;
if( _currentActivity == nullptr ) {
// TODO make idle an actual activity or something,
character->clearTargetPosition();
}
}
CharacterController::CharacterController(CharacterObject* character)
: character(character), _currentActivity(nullptr), _nextActivity(nullptr), running(false),
vehicleIdle(0.f), currentGoal(None), leader(nullptr), targetNode(nullptr)
: character(character)
, _currentActivity(nullptr)
, _nextActivity(nullptr)
, vehicleIdle(0.f)
, currentGoal(None)
, leader(nullptr)
, targetNode(nullptr)
{
character->controller = this;
}
@ -52,11 +53,11 @@ void CharacterController::setNextActivity(CharacterController::Activity* activit
void CharacterController::update(float dt)
{
auto d = rawMovement;
if( character->getCurrentVehicle() ) {
// Nevermind, the player is in a vehicle.
auto& d = character->getMovement();
if( character->getCurrentSeat() == 0 )
{
character->getCurrentVehicle()->setSteeringAngle(d.y);
@ -68,11 +69,7 @@ void CharacterController::update(float dt)
character->getCurrentVehicle()->setThrottle(d.x);
}
// if the character isn't doing anything, play sitting anim.
if( _currentActivity == nullptr ) {
/// @todo play _low variant if car has low flag.
character->playAnimation(character->animations.car_sit, true);
if( glm::length( d ) <= 0.1f )
{
vehicleIdle += dt;
@ -95,70 +92,9 @@ void CharacterController::update(float dt)
}
}
}
else {
if( glm::length(d) > 0.01f ) {
if( running ) {
if( character->animator->getAnimation() != character->animations.run ) {
character->playAnimation(character->animations.run, true);
}
}
else {
if( character->animator->getAnimation() == character->animations.walk_start ) {
if( character->animator->isCompleted() ) {
character->playAnimation(character->animations.walk, true);
}
}
else if( character->animator->getAnimation() != character->animations.walk ) {
character->playAnimation(character->animations.walk_start, false);
}
}
}
// TODO how should characters strafe?
/*else if( d.x < -0.001f ) {
if( character->animator->getAnimation() == character->animations.walk_back_start ) {
if( character->animator->isCompleted() ) {
character->playAnimation(character->animations.walk_back, true);
}
}
else if( character->animator->getAnimation() != character->animations.walk_back ) {
character->playAnimation(character->animations.walk_back_start, false);
}
}
else if( glm::abs(d.y) > 0.001f ) {
if( d.y < 0.f ) {
if( character->animator->getAnimation() == character->animations.walk_right_start ) {
if( character->animator->isCompleted() ) {
character->playAnimation(character->animations.walk_right, true);
}
}
else if( character->animator->getAnimation() != character->animations.walk_right ) {
character->playAnimation(character->animations.walk_right_start, false);
}
}
else {
if( character->animator->getAnimation() == character->animations.walk_left_start ) {
if( character->animator->isCompleted() ) {
character->playAnimation(character->animations.walk_left, true);
}
}
else if( character->animator->getAnimation() != character->animations.walk_left ) {
character->playAnimation(character->animations.walk_left_start, false);
}
}
}*/
if( _currentActivity == nullptr ) {
if( glm::length(d) < 0.001f ) {
character->playAnimation(character->animations.idle, true);
}
}
}
// Reset raw movement between activity updates.
setRawMovement(glm::vec3());
if( updateActivity() ) {
character->activityFinished();
if( _currentActivity ) {
delete _currentActivity;
_currentActivity = nullptr;
@ -175,14 +111,14 @@ CharacterObject *CharacterController::getCharacter() const
return character;
}
void CharacterController::setRawMovement(const glm::vec3 &movement)
void CharacterController::setMoveDirection(const glm::vec3 &movement)
{
rawMovement = movement;
character->setMovement(movement);
}
void CharacterController::setRunning(bool run)
{
running = run;
character->setRunning(run);
}
@ -202,7 +138,7 @@ bool Activities::GoTo::update(CharacterObject *character, CharacterController *c
glm::quat r( glm::vec3{ 0.f, 0.f, atan2(targetDirection.y, targetDirection.x) - glm::half_pi<float>() } );
character->rotation = r;
controller->setRawMovement({1.f, 0.f, 0.f});
controller->setMoveDirection({1.f, 0.f, 0.f});
controller->setRunning(sprint);
return false;
@ -210,28 +146,16 @@ bool Activities::GoTo::update(CharacterObject *character, CharacterController *c
bool Activities::Jump::update(CharacterObject* character, CharacterController* controller)
{
if( jumped )
if( !jumped )
{
if( character->physCharacter->canJump() )
{
character->playAnimation(character->animations.jump_land, false);
return true;
}
if( character->animator->getAnimation() == character->animations.jump_start )
{
if( character->animator->isCompleted() )
{
character->playAnimation(character->animations.jump_glide, true);
}
}
character->jump();
jumped = true;
}
else
{
character->jump();
character->playAnimation( character->animations.jump_start, false );
jumped = true;
if (character->physCharacter->canJump()) {
return true;
}
}
return false;
@ -273,12 +197,12 @@ bool Activities::EnterVehicle::update(CharacterObject *character, CharacterContr
}
if( entering ) {
if( character->animator->getAnimation() == anm_open ) {
if( character->animator->isCompleted() ) {
character->playAnimation(anm_enter, false);
if( character->animator->getAnimation(AnimIndexAction) == anm_open ) {
if( character->animator->isCompleted(AnimIndexAction) ) {
character->playActivityAnimation(anm_enter, false, true);
character->enterVehicle(vehicle, seat);
}
else if( entryDoor && character->animator->getAnimationTime() >= 0.5f )
else if( entryDoor && character->animator->getAnimationTime(AnimIndexAction) >= 0.5f )
{
vehicle->setPartTarget(entryDoor, true, entryDoor->openAngle);
}
@ -287,8 +211,8 @@ bool Activities::EnterVehicle::update(CharacterObject *character, CharacterContr
character->rotation = vehicle->getRotation();
}
}
else if( character->animator->getAnimation() == anm_enter ) {
if( character->animator->isCompleted() ) {
else if( character->animator->getAnimation(AnimIndexAction) == anm_enter ) {
if( character->animator->isCompleted(AnimIndexAction) ) {
// VehicleGetIn is over, finish activity
return true;
}
@ -304,19 +228,19 @@ bool Activities::EnterVehicle::update(CharacterObject *character, CharacterContr
if( targetDistance <= 0.4f ) {
entering = true;
// Warp character to vehicle orientation
character->controller->setRawMovement({0.f, 0.f, 0.f});
character->controller->setMoveDirection({0.f, 0.f, 0.f});
character->controller->setRunning(false);
character->rotation = vehicle->getRotation();
// Determine if the door open animation should be skipped.
if( entryDoor == nullptr || (entryDoor->constraint != nullptr && glm::abs(entryDoor->constraint->getHingeAngle()) >= 0.6f ) )
{
character->playAnimation(anm_enter, false);
character->playActivityAnimation(anm_enter, false, true);
character->enterVehicle(vehicle, seat);
}
else
{
character->playAnimation(anm_open, false);
character->playActivityAnimation(anm_open, false, true);
}
}
else {
@ -325,7 +249,7 @@ bool Activities::EnterVehicle::update(CharacterObject *character, CharacterContr
}
glm::quat r( glm::vec3{ 0.f, 0.f, atan2(targetDirection.y, targetDirection.x) - glm::half_pi<float>() } );
character->rotation = r;
character->controller->setRawMovement({1.f, 0.f, 0.f});
character->controller->setMoveDirection({1.f, 0.f, 0.f});
}
}
return false;
@ -355,8 +279,8 @@ bool Activities::ExitVehicle::update(CharacterObject *character, CharacterContro
return true;
}
if( character->animator->getAnimation() == anm_exit ) {
if( character->animator->isCompleted() ) {
if( character->animator->getAnimation(AnimIndexAction) == anm_exit ) {
if( character->animator->isCompleted(AnimIndexAction) ) {
auto exitpos = vehicle->getSeatEntryPosition(seat);
character->enterVehicle(nullptr, seat);
@ -366,7 +290,7 @@ bool Activities::ExitVehicle::update(CharacterObject *character, CharacterContro
}
}
else {
character->playAnimation(anm_exit, false);
character->playActivityAnimation(anm_exit, false, true);
if( door )
{
vehicle->setPartTarget(door, true, door->openAngle);
@ -391,28 +315,28 @@ bool Activities::ShootWeapon::update(CharacterObject *character, CharacterContro
auto shootanim = character->engine->data->animations[wepdata->animation1];
if( shootanim ) {
if( character->animator->getAnimation() != shootanim ) {
character->playAnimation(shootanim, false);
if( character->animator->getAnimation(AnimIndexAction) != shootanim ) {
character->playActivityAnimation(shootanim, false, false);
}
auto loopstart = wepdata->animLoopStart / 100.f;
auto loopend = wepdata->animLoopEnd / 100.f;
auto firetime = wepdata->animFirePoint / 100.f;
auto currID = character->animator->getAnimationTime();
auto currID = character->animator->getAnimationTime(AnimIndexAction);
if( currID >= firetime && ! _fired ) {
_item->fire(character);
_fired = true;
}
if( currID > loopend ) {
character->animator->setAnimationTime( loopstart );
character->animator->setAnimationTime( AnimIndexAction, loopstart );
_fired = false;
}
}
}
else {
if( character->animator->isCompleted() ) {
if( character->animator->isCompleted(AnimIndexAction) ) {
return true;
}
}
@ -422,25 +346,25 @@ bool Activities::ShootWeapon::update(CharacterObject *character, CharacterContro
auto shootanim = character->engine->data->animations[wepdata->animation1];
auto throwanim = character->engine->data->animations[wepdata->animation2];
if( character->animator->getAnimation() == shootanim ) {
if( character->animator->isCompleted() ) {
character->animator->setAnimation(throwanim, false);
if( character->animator->getAnimation(AnimIndexAction) == shootanim ) {
if( character->animator->isCompleted(AnimIndexAction) ) {
character->playActivityAnimation(throwanim, false, false);
}
}
else if( character->animator->getAnimation() == throwanim ) {
else if( character->animator->getAnimation(AnimIndexAction) == throwanim ) {
auto firetime = wepdata->animCrouchFirePoint / 100.f;
auto currID = character->animator->getAnimationTime();
auto currID = character->animator->getAnimationTime(AnimIndexAction);
if( currID >= firetime && !_fired ) {
_item->fire(character);
_fired = true;
}
if( character->animator->isCompleted() ) {
if( character->animator->isCompleted(AnimIndexAction) ) {
return true;
}
}
else {
character->animator->setAnimation(throwanim, false);
character->playActivityAnimation(throwanim, false, true);
}
}
else if( wepdata->fireType == WeaponData::MELEE ) {

View File

@ -30,7 +30,7 @@ void PlayerController::updateMovementDirection(const glm::vec3& dir, const glm::
{
if( _currentActivity == nullptr ) {
direction = dir;
setRawMovement(rawdirection);
setMoveDirection(rawdirection);
}
}
@ -64,15 +64,6 @@ void PlayerController::enterNearestVehicle()
void PlayerController::update(float dt)
{
// TODO: Determine if the player is allowed to interupt the current activity.
if( glm::length(direction) > 0.001f && _currentActivity != nullptr ) {
skipActivity();
}
if( _currentActivity == nullptr && glm::length(rawMovement) > 0.0f ) {
character->rotation = glm::quat(glm::vec3(0.f, 0.f, -atan2(direction.x, direction.y)));
}
CharacterController::update(dt);
}

View File

@ -3,166 +3,129 @@
#include <data/Model.hpp>
#include <data/Skeleton.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <iostream>
Animator::Animator(Model* model, Skeleton* skeleton)
: model(model), skeleton(skeleton), time(0.f), serverTime(0.f),
lastServerTime(0.f), playing(true), repeat(true)
: model(model)
, skeleton(skeleton)
{
reset();
}
void Animator::reset()
{
time = 0.f;
serverTime = 0.f;
lastServerTime = 0.f;
boneInstances.clear();
if( getAnimation() )
{
for( unsigned int f = 0; f < model->frames.size(); ++f )
{
auto bit = getAnimation()->bones.find( model->frames[f]->getName() );
if( bit != getAnimation()->bones.end() )
{
boneInstances.insert( { bit->second, { f } } );
}
}
}
}
void Animator::setAnimation(Animation *animation, bool repeat)
{
if(!_animations.empty() && animation == _animations.front()) {
return;
}
while(!_animations.empty()) _animations.pop();
queueAnimation(animation);
this->repeat = repeat;
//_frameInstances.clear();
reset();
}
void Animator::queueAnimation(Animation *animation)
{
_animations.push(animation);
}
void Animator::next()
{
_animations.pop();
reset();
}
void Animator::tick(float dt)
{
if( model == nullptr || _animations.empty() ) {
if( model == nullptr || animations.empty() ) {
return;
}
if( playing ) {
lastServerTime = serverTime;
serverTime += dt;
struct BoneTransform
{
glm::vec3 translation;
glm::quat rotation;
};
// Blend all active animations together
std::map<unsigned int, BoneTransform> blendFrames;
for (AnimationState& state : animations)
{
RW_CHECK(state.animation != nullptr, "AnimationState with no animation");
if (state.animation == nullptr) continue;
if (state.boneInstances.size() == 0) {
for( unsigned int f = 0; f < model->frames.size(); ++f )
{
auto bit = state.animation->bones.find( model->frames[f]->getName() );
if( bit != state.animation->bones.end() )
{
state.boneInstances.insert( { bit->second, { f } } );
}
}
}
float animTime = state.time;
if (! state.repeat)
{
animTime = std::min(animTime, state.animation->duration);
}
else
{
animTime = fmod(animTime, state.animation->duration);
}
for( auto& b : state.boneInstances )
{
if (b.first->frames.size() == 0) continue;
auto kf = b.first->getInterpolatedKeyframe(animTime);
BoneTransform xform;
if(b.first->type == AnimationBone::R00 ) {
xform.rotation = kf.rotation;
}
else if(b.first->type == AnimationBone::RT0) {
xform.rotation = kf.rotation;
xform.translation = kf.position;
}
else {
xform.rotation = kf.rotation;
xform.translation = kf.position;
}
#if 0
auto prevAnim = blendFrames.find(b.second.frameIndex);
if (prevAnim != blendFrames.end())
{
prevAnim->second.translation += xform.translation;
prevAnim->second.rotation *= xform.rotation;
}
else
{
blendFrames[b.second.frameIndex] = xform;
}
#else
blendFrames[b.second.frameIndex] = xform;
#endif
}
state.time = state.time + dt;
}
for( auto& b : boneInstances )
for (auto& p : blendFrames)
{
auto kf = b.first->getInterpolatedKeyframe(getAnimationTime(1.f));
auto& data = skeleton->getData(b.second.frameIdx);
ModelFrame* frame = model->frames[b.second.frameIdx];
auto& data = skeleton->getData(p.first);
Skeleton::FrameData fd;
fd.b = data.a;
if(b.first->type == AnimationBone::R00 ) {
fd.a.rotation = kf.rotation;
fd.a.translation = frame->getDefaultTranslation();
}
else if(b.first->type == AnimationBone::RT0) {
fd.a.rotation = kf.rotation;
fd.a.translation = kf.position;
}
else {
fd.a.rotation = kf.rotation;
fd.a.translation = kf.position;
}
fd.enabled = data.enabled;
skeleton->setData(b.second.frameIdx, fd);
}
fd.a.translation = model->frames[p.first]->getDefaultTranslation()
+ p.second.translation;
fd.a.rotation = p.second.rotation;
if( isCompleted() && ! repeat && _animations.size() > 1 ) {
next();
skeleton->setData(p.first, fd);
}
}
glm::vec3 Animator::getRootTranslation() const
bool Animator::isCompleted(unsigned int slot) const
{
if(!model->frames[model->rootFrameIdx]->getChildren().empty() && !_animations.empty()) {
ModelFrame* realRoot = model->frames[model->rootFrameIdx]->getChildren()[0];
auto it = getAnimation()->bones.find(realRoot->getName());
if(it != getAnimation()->bones.end()) {
auto start = it->second->frames.front().position;
auto end = it->second->frames.back().position;
return end - start;
}
if (slot < animations.size())
{
return animations[slot].animation ?
animations[slot].time >= animations[slot].animation->duration
: true;
}
return glm::vec3();
return false;
}
glm::vec3 Animator::getTimeTranslation(float alpha) const
float Animator::getAnimationTime(unsigned int slot) const
{
if(!model->frames[model->rootFrameIdx]->getChildren().empty() && !_animations.empty()) {
ModelFrame* realRoot = model->frames[model->rootFrameIdx]->getChildren()[0];
auto it = getAnimation()->bones.find(realRoot->getName());
if(it != getAnimation()->bones.end()) {
auto start = it->second->frames.front().position;
auto end = it->second->frames.back().position;
return glm::mix(start, end, (getAnimationTime(alpha) / getAnimation()->duration) );
}
if (slot < animations.size())
{
return animations[slot].time;
}
return glm::vec3();
return 0.f;
}
glm::quat Animator::getRootRotation() const
void Animator::setAnimationTime(unsigned int slot, float time)
{
return glm::quat();
}
bool Animator::isCompleted() const
{
return getAnimation() ? serverTime >= getAnimation()->duration : true;
}
float Animator::getAnimationTime(float alpha) const
{
float td = serverTime - lastServerTime;
if(repeat) {
float t = serverTime + td * alpha;
while( t > getAnimation()->duration )
{
t -= getAnimation()->duration;
}
return t;
if (slot < animations.size())
{
animations[slot].time = time;
}
return serverTime + td * alpha;
}
void Animator::setAnimationTime(float time)
{
lastServerTime = serverTime;
serverTime = time;
}

View File

@ -18,12 +18,13 @@ CharacterObject::CharacterObject(GameWorld* engine, const glm::vec3& pos, const
, currentState({})
, currentVehicle(nullptr)
, currentSeat(0)
, _hasTargetPosition(false)
, ped(data)
, physCharacter(nullptr)
, running(false)
, jumped(false)
, controller(nullptr)
, jumpSpeed(DefaultJumpSpeed)
, motionBlockedByActivity(false)
{
// TODO move AnimationGroup creation somewhere else.
animations.idle = engine->data->animations["idle_stance"];
@ -54,6 +55,9 @@ CharacterObject::CharacterObject(GameWorld* engine, const glm::vec3& pos, const
animations.car_getin_rhs = engine->data->animations["car_getin_rhs"];
animations.car_getout_rhs = engine->data->animations["car_getout_rhs"];
animations.kd_front = engine->data->animations["kd_front"];
animations.ko_shot_front = engine->data->animations["ko_shot_front"];
if(model) {
skeleton = new Skeleton;
animator = new Animator(model->resource, skeleton);
@ -115,6 +119,135 @@ void CharacterObject::destroyActor()
}
}
glm::vec3 CharacterObject::updateMovementAnimation(float dt)
{
glm::vec3 animTranslate;
if (motionBlockedByActivity)
{
// Clear any residual motion animation
animator->playAnimation(AnimIndexMovement, nullptr, 1.f, false);
return glm::vec3();
}
// Things are simpler if we're in a vehicle
if (getCurrentVehicle())
{
animator->playAnimation(0, animations.car_sit, 1.f, true);
return glm::vec3();
}
Animation* movementAnimation = animations.idle;
Animation* currentAnim = animator->getAnimation(AnimIndexMovement);
bool isActionHappening = (animator->getAnimation(AnimIndexAction) != nullptr);
float animationSpeed = 1.f;
bool repeat = true;
constexpr float movementEpsilon = 0.1f;
float movementLength = glm::length(movement);
if (!isAlive()) {
movementAnimation = animations.ko_shot_front;
repeat = false;
}
else if (jumped) {
repeat = false;
if (currentAnim == animations.jump_start &&
animator->isCompleted(AnimIndexMovement)) {
movementAnimation = animations.jump_start;
}
if (isOnGround()) {
if (currentAnim != animations.jump_land
|| !animator->isCompleted(AnimIndexMovement)) {
movementAnimation = animations.jump_land;
}
else {
// We are done jumping
jumped = false;
}
}
else {
movementAnimation = animations.jump_glide;
}
}
else if (movementLength > movementEpsilon)
{
if (running && !isActionHappening) {
// No slow running
movementAnimation = animations.run;
animationSpeed = 1.f;
}
else {
animationSpeed = 1.f / movementLength;
// Determine if we need to play the walk start animation
if (currentAnim != animations.walk)
{
if (currentAnim != animations.walk_start || !animator->isCompleted(AnimIndexMovement))
{
movementAnimation = animations.walk_start;
}
else
{
movementAnimation = animations.walk;
}
}
else
{
// Keep walkin
movementAnimation = animations.walk;
}
}
}
// Check if we need to change the animation or change speed
if (animator->getAnimation(AnimIndexMovement) != movementAnimation)
{
animator->playAnimation(AnimIndexMovement, movementAnimation, animationSpeed, repeat);
}
else
{
animator->setAnimationSpeed(AnimIndexMovement, animationSpeed);
}
// If we have to, interrogate the movement animation
if (movementAnimation != animations.idle)
{
if(! model->resource->frames[0]->getChildren().empty() )
{
ModelFrame* root = model->resource->frames[0]->getChildren()[0];
auto it = movementAnimation->bones.find(root->getName());
if (it != movementAnimation->bones.end())
{
AnimationBone* rootBone = it->second;
float step = dt;
const float duration = animator->getAnimation(AnimIndexMovement)->duration;
float animTime = fmod(animator->getAnimationTime(AnimIndexMovement), duration);
// Handle any remaining transformation before the end of the keyframes
if ((animTime+step) > duration)
{
glm::vec3 a = rootBone->getInterpolatedKeyframe(animTime).position;
glm::vec3 b = rootBone->getInterpolatedKeyframe(duration).position;
glm::vec3 d = (b-a);
animTranslate.y += d.y;
step -= (duration - animTime);
animTime = 0.f;
}
glm::vec3 a = rootBone->getInterpolatedKeyframe(animTime).position;
glm::vec3 b = rootBone->getInterpolatedKeyframe(animTime+step).position;
glm::vec3 d = (b-a);
animTranslate.y += d.y;
Skeleton::FrameData fd = skeleton->getData(root->getIndex());
fd.a.translation.y = 0.f;
skeleton->setData(root->getIndex(), fd);
}
}
}
return animTranslate;
}
void CharacterObject::tick(float dt)
{
if(controller) {
@ -157,83 +290,46 @@ void CharacterObject::changeCharacterModel(const std::string &name)
void CharacterObject::updateCharacter(float dt)
{
/*
* You can fire weapons while moving
*
* Two Modes: Moving and Action
* Moving covers walking & jumping
* Action covers complex things like shooting, entering vehicles etc.
*
* Movement animation should be handled here
* If Current weapon is one handed, then it can be used while walking.
* This means blending the weapon animation with the walk animation.
* No weapons can be used while sprinting.
* Need an "aim vector" to apply torso correction.
*
* If movement vector is less than some threshold, fully walk animation
* (time adjusted for velocity).
*/
if(physCharacter) {
// Check to see if the character should be knocked down.
btManifoldArray manifoldArray;
btBroadphasePairArray& pairArray = physObject->getOverlappingPairCache()->getOverlappingPairArray();
int numPairs = pairArray.size();
for (int i=0;i<numPairs;i++)
{
manifoldArray.clear();
const btBroadphasePair& pair = pairArray[i];
//unless we manually perform collision detection on this pair, the contacts are in the dynamics world paircache:
btBroadphasePair* collisionPair = engine->dynamicsWorld->getPairCache()->findPair(pair.m_pProxy0,pair.m_pProxy1);
if (!collisionPair)
continue;
if (collisionPair->m_algorithm)
collisionPair->m_algorithm->getAllContactManifolds(manifoldArray);
for (int j=0;j<manifoldArray.size();j++)
{
btPersistentManifold* manifold = manifoldArray[j];
for (int p=0;p<manifold->getNumContacts();p++)
{
const btManifoldPoint&pt = manifold->getContactPoint(p);
if (pt.getDistance() < 0.f)
{
auto otherObject = static_cast<const btCollisionObject*>(
manifold->getBody0() == physObject ? manifold->getBody1() : manifold->getBody0());
if(otherObject->getUserPointer()) {
GameObject* object = static_cast<GameObject*>(otherObject->getUserPointer());
if(object->type() == Vehicle) {
VehicleObject* vehicle = static_cast<VehicleObject*>(object);
if(vehicle->physBody->getLinearVelocity().length() > 0.1f) {
/// @todo play knocked down animation.
}
}
}
}
}
}
}
glm::vec3 walkDir;
glm::vec3 animTranslate;
if( isAnimationFixed() && animator->getAnimation() != nullptr ) {
auto d = animator->getRootTranslation() / animator->getAnimation()->duration;
animTranslate = d * dt;
if(! model->resource->frames[0]->getChildren().empty() )
{
auto root = model->resource->frames[0]->getChildren()[0];
Skeleton::FrameData fd = skeleton->getData(root->getIndex());
fd.a.translation -= d * animator->getAnimationTime(1.f);
skeleton->setData(root->getIndex(), fd);
}
}
glm::vec3 walkDir = updateMovementAnimation(dt);
position = getPosition();
walkDir = rotation * animTranslate;
walkDir = rotation * walkDir;
if( jumped )
{
if( physCharacter->onGround() )
{
jumped = false;
}
else
if( !isOnGround() )
{
walkDir = rotation * glm::vec3(0.f, jumpSpeed * dt, 0.f);
}
}
physCharacter->setWalkDirection(btVector3(walkDir.x, walkDir.y, walkDir.z));
if (isAlive())
{
physCharacter->setWalkDirection(btVector3(walkDir.x, walkDir.y, walkDir.z));
}
else
{
physCharacter->setWalkDirection(btVector3(0.f, 0.f, 0.f));
}
auto Pos = physCharacter->getGhostObject()->getWorldTransform().getOrigin();
position = glm::vec3(Pos.x(), Pos.y(), Pos.z());
@ -271,6 +367,10 @@ void CharacterObject::updateCharacter(float dt)
}
_lastHeight = getPosition().z;
}
else
{
updateMovementAnimation(dt);
}
}
void CharacterObject::setPosition(const glm::vec3& pos)
@ -297,14 +397,14 @@ glm::vec3 CharacterObject::getPosition() const
}
if(currentVehicle) {
/// @todo this is hacky.
if( animator->getAnimation() == animations.car_getout_lhs ) {
if( animator->getAnimation(AnimIndexAction) == animations.car_getout_lhs ) {
return currentVehicle->getSeatEntryPosition(currentSeat);
}
auto v = getCurrentVehicle();
auto R = glm::mat3_cast(v->getRotation());
glm::vec3 offset;
auto o = (animator->getAnimation() == animations.car_getin_lhs) ? enter_offset : glm::vec3();
auto o = (animator->getAnimation(AnimIndexAction) == animations.car_getin_lhs) ? enter_offset : glm::vec3();
if(getCurrentSeat() < v->info->seats.size()) {
offset = R * (v->info->seats[getCurrentSeat()].offset -
o);
@ -402,6 +502,7 @@ void CharacterObject::jump()
if( physCharacter ) {
physCharacter->jump();
jumped = true;
animator->playAnimation(AnimIndexMovement, animations.jump_start, 1.f, false);
}
}
@ -410,6 +511,19 @@ float CharacterObject::getJumpSpeed() const
return jumpSpeed;
}
bool CharacterObject::isOnGround() const
{
if (physCharacter) {
return physCharacter->onGround();
}
return true;
}
bool CharacterObject::canTurn() const
{
return isOnGround() && !jumped && isAlive();
}
void CharacterObject::setJumpSpeed(float speed)
{
jumpSpeed = speed;
@ -445,20 +559,17 @@ void CharacterObject::resetToAINode()
}
}
void CharacterObject::setTargetPosition(const glm::vec3 &target)
void CharacterObject::playActivityAnimation(Animation* animation, bool repeat, bool blocked)
{
_targetPosition = target;
_hasTargetPosition = true;
RW_CHECK(animator != nullptr, "No Animator");
animator->playAnimation(AnimIndexAction, animation, 1.f, repeat);
motionBlockedByActivity = blocked;
}
void CharacterObject::clearTargetPosition()
void CharacterObject::activityFinished()
{
_hasTargetPosition = false;
}
void CharacterObject::playAnimation(Animation *animation, bool repeat)
{
animator->setAnimation(animation, repeat);
animator->playAnimation(AnimIndexAction, nullptr, 1.f, false);
motionBlockedByActivity = false;
}
void CharacterObject::addToInventory(InventoryItem *item)

View File

@ -556,7 +556,7 @@ void game_set_cutscene_anim(const ScriptArguments& args)
std::transform(animName.begin(), animName.end(), animName.begin(), ::tolower);
Animation* anim = args.getWorld()->data->animations[animName];
if( anim ) {
object->animator->setAnimation(anim, false);
object->animator->playAnimation(0, anim, 1.f, false);
}
else {
args.getWorld()->logger->error("SCM", "Failed to load cutscene anim: " + animName);
@ -622,7 +622,7 @@ void game_set_head_animation(const ScriptArguments& args)
std::transform(animName.begin(), animName.end(), animName.begin(), ::tolower);
Animation* anim = args.getWorld()->data->animations[animName];
if( anim ) {
object->animator->setAnimation(anim, false);
object->animator->playAnimation(0, anim, 1.f, false);
}
else {
args.getWorld()->logger->error("SCM", "Failed to load cutscene anim: " + animName);

View File

@ -209,11 +209,30 @@ void IngameState::tick(float dt)
// Update player with camera yaw
if( player->isInputEnabled() )
{
player->updateMovementDirection(angle * _movement, _movement);
if (player->getCharacter()->getCurrentVehicle())
{
player->setMoveDirection(_movement);
}
else
{
glm::vec3 direction = glm::normalize(_movement);
float length = glm::length(direction);
player->setMoveDirection(glm::vec3(length, 0.f, 0.f));
float movementAngle = angleYaw - M_PI/2.f;
if (length > 0.1f)
{
movementAngle += atan2(direction.y, direction.x);
}
if (player->getCharacter()->canTurn())
{
player->getCharacter()->rotation =
glm::angleAxis(movementAngle, glm::vec3(0.f, 0.f, 1.f));
}
}
}
else
{
player->updateMovementDirection(glm::vec3(0.f, 0.f, 0.f), glm::vec3(0.f,0.f,0.f));
player->setMoveDirection(glm::vec3(0.f));
}
float len2d = glm::length(glm::vec2(lookdir));

View File

@ -93,5 +93,5 @@ void ModelViewer::loadAnimations(const QString& file)
void ModelViewer::playAnimation(Animation* anim)
{
viewerWidget->currentObject()->animator->setAnimation(anim);
viewerWidget->currentObject()->animator->playAnimation(0, anim, 1.f, true);
}

View File

@ -34,7 +34,7 @@ BOOST_AUTO_TEST_CASE(test_matrix)
}
};
animator.setAnimation(&animation);
animator.playAnimation(0, &animation, 1.f, true);
animator.tick(0.0f);

View File

@ -3,6 +3,7 @@
#include <objects/VehicleObject.hpp>
#include <ai/DefaultAIController.hpp>
#include "test_globals.hpp"
#include <engine/Animator.hpp>
BOOST_AUTO_TEST_SUITE(CharacterTests)
@ -87,5 +88,35 @@ BOOST_AUTO_TEST_CASE(test_activities)
}
}
BOOST_AUTO_TEST_CASE(test_death)
{
{
auto character = Global::get().e->createPedestrian(1, {100.f, 100.f, 50.f});
BOOST_REQUIRE( character != nullptr );
auto controller = new DefaultAIController(character);
BOOST_CHECK_EQUAL( character->getCurrentState().health, 100.f );
BOOST_CHECK( character->isAlive() );
GameObject::DamageInfo dmg;
dmg.type = GameObject::DamageInfo::Bullet;
dmg.hitpoints = character->getCurrentState().health + 1.f;
// Do some damage
BOOST_CHECK(character->takeDamage(dmg));
BOOST_CHECK( ! character->isAlive() );
character->tick(0.16f);
BOOST_CHECK_EQUAL(
character->animator->getAnimation(0),
character->animations.ko_shot_front);
Global::get().e->destroyObject(character);
delete controller;
}
}
BOOST_AUTO_TEST_SUITE_END()