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

Overhaul animation support with Skeleton class.

Move frame transform data into Skeleton instead of Animator.
Animator now responsible for mutating Skeleton state.
Skeleton is more flexible for things like vehicle skeletons.
This commit is contained in:
Daniel Evans 2014-12-11 17:48:47 +00:00
parent b65a7d3dcf
commit 584618e991
20 changed files with 459 additions and 399 deletions

View File

@ -0,0 +1,64 @@
#ifndef _SKELETON_HPP_
#define _SKELETON_HPP_
#include <glm/glm.hpp>
#include <glm/gtx/quaternion.hpp>
#include <map>
#include <vector>
class ModelFrame;
/**
* Data class for additional frame transformation and meta data.
*
* Provides interfaces to modify and query the visibility of model frames,
* as well as their transformation. Modified by Animator to animate models.
*/
class Skeleton
{
public:
struct FrameTransform
{
glm::vec3 translation;
glm::quat rotation;
};
static FrameTransform IdentityTransform;
struct FrameData
{
FrameTransform a;
FrameTransform b;
bool enabled;
};
static FrameData IdentityData;
typedef std::map<unsigned int, FrameData> FramesData;
typedef std::map<unsigned int, FrameTransform> TransformData;
Skeleton();
void setAllData(const FramesData& data);
const FrameData& getData(unsigned int frameIdx) const;
void setData(unsigned int frameIdx, const FrameData& data);
void setEnabled(ModelFrame* frame, bool enabled);
void setEnabled(unsigned int frameIdx, bool enabled);
const FrameTransform& getInterpolated(unsigned int frameIdx) const;
glm::mat4 getMatrix(unsigned int frameIdx) const;
glm::mat4 getMatrix(ModelFrame* frame) const;
void interpolate(float alpha);
private:
FramesData framedata;
TransformData interpolateddata;
};
#endif

View File

@ -12,6 +12,8 @@
class Model;
class ModelFrame;
class Skeleton;
/**
* @brief calculates animation frame matrices, as well as procedural frame
* animation.
@ -27,29 +29,21 @@ class Animator
* @brief model The model being animated.
*/
Model* model;
/**
* @brief Skeleton instance.
*/
Skeleton* skeleton;
/**
* @brief Stores data required to animate a model frame
*/
struct FrameInstanceData {
bool visible;
AnimationBone* bone;
// Used if bone is non-null.
AnimationKeyframe first;
AnimationKeyframe second;
// Used if bone is null and entry exists.
glm::quat orientation;
/// Construct from animation data
FrameInstanceData(AnimationBone* bone, const AnimationKeyframe& a, const AnimationKeyframe& b)
: visible(true), bone(bone), first(a), second(b) {}
/// Construct from procedural data
FrameInstanceData(bool visible, const glm::quat& orientation = {})
: visible(visible), bone(nullptr), orientation(orientation) {}
struct BoneInstanceData
{
unsigned int frameIdx;
};
std::map<ModelFrame*, FrameInstanceData> _frameInstances;
std::map<AnimationBone*, BoneInstanceData> boneInstances;
// Used in determining how far the skeleton being animated has moved
// From it's local origin.
@ -67,7 +61,7 @@ class Animator
public:
Animator();
Animator(Model* model, Skeleton* skeleton);
/**
* @brief setAnimation Sets the currently active animation.
@ -87,27 +81,6 @@ public:
const Animation* getAnimation() const
{ return _animations.empty() ? nullptr : _animations.front(); }
void setModel(Model* model);
void setFrameVisibility(ModelFrame* frame, bool visible);
bool getFrameVisibility(ModelFrame* frame) const;
void setFrameOrientation(ModelFrame* frame, const glm::quat& orientation);
glm::quat getFrameOrientation(ModelFrame* frame) const;
FrameInstanceData* getFrameInstance(ModelFrame* frame);
/**
* @brief getFrameMatrix returns the matrix for frame at the given time
* @param t
* @param frame
* @return
*/
glm::mat4 getFrameMatrixAt(ModelFrame* frame, float time, bool disableRoot = true) const;
AnimationKeyframe getKeyframeAt(ModelFrame* frame, float time) const;
glm::mat4 getFrameMatrix(ModelFrame* frame, float alpha = 0.f, bool ignoreRoot = true) const;
/**
* @brief tick Update animation paramters for server-side data.
* @param dt
@ -127,10 +100,10 @@ public:
glm::vec3 getRootTranslation() const;
/**
* @brief getDurationTransform returns the translation of the root bone over the duration of the animation.
* @brief getTimeTranslation returns the translation of the root bone at the current time.
* @return
*/
glm::vec3 getDurationTransform() const;
glm::vec3 getTimeTranslation() const;
/**
* @brief getRootRotation see getRootTranslation

View File

@ -9,6 +9,7 @@
#include <glm/gtc/matrix_transform.hpp>
#include <memory>
class Skeleton;
class CharacterController;
class ModelFrame;
class Animator;
@ -35,6 +36,7 @@ public:
GameWorld* engine;
Animator* animator; /// Object's animator.
Skeleton* skeleton;
/**
* Health value
@ -49,11 +51,12 @@ public:
float _lastHeight;
GameObject(GameWorld* engine, const glm::vec3& pos, const glm::quat& rot, ModelHandle* model)
: _lastPosition(pos), _lastRotation(rot), position(pos), rotation(rot), model(model), engine(engine), animator(nullptr), mHealth(0.f),
: _lastPosition(pos), _lastRotation(rot), position(pos), rotation(rot),
model(model), engine(engine), animator(nullptr), skeleton(nullptr), mHealth(0.f),
_inWater(false), _lastHeight(std::numeric_limits<float>::max())
{}
virtual ~GameObject() {}
virtual ~GameObject();
/**
* @brief Enumeration of possible object types.

View File

@ -58,8 +58,6 @@ private:
void createActor(const glm::vec3& size = glm::vec3(0.35f, 0.35f, 1.3f));
void destroyActor();
bool _fixedAnimation;
// Incredibly hacky "move in this direction".
bool _hasTargetPosition;
glm::vec3 _targetPosition;
@ -129,7 +127,7 @@ public:
void setTargetPosition( const glm::vec3& target );
void clearTargetPosition();
void playAnimation(Animation* animation, bool repeat, bool fixed);
void playAnimation(Animation* animation, bool repeat);
virtual bool isAnimationFixed() const;
void addToInventory( InventoryItem* item );

View File

@ -17,6 +17,7 @@
* transformations.
*/
class ModelFrame {
unsigned int index;
glm::mat3 defaultRotation;
glm::vec3 defaultTranslation;
glm::mat4 matrix;
@ -26,7 +27,7 @@ class ModelFrame {
std::vector<ModelFrame*> childs;
public:
ModelFrame(ModelFrame* parent, glm::mat3 dR, glm::vec3 dT);
ModelFrame(unsigned int index, ModelFrame* parent, glm::mat3 dR, glm::vec3 dT);
void reset();
void setTransform(const glm::mat4& m);
@ -36,6 +37,9 @@ public:
{ name = fname; }
void addGeometry(size_t idx);
unsigned int getIndex() const
{ return index; }
glm::vec3 getDefaultTranslation() const
{ return defaultTranslation; }

View File

@ -60,63 +60,63 @@ void CharacterController::update(float dt)
// 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, false);
character->playAnimation(character->animations.car_sit, true);
}
}
else {
if( d.x > 0.001f ) {
if( running ) {
if( character->animator->getAnimation() != character->animations.run ) {
character->playAnimation(character->animations.run, true, true);
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, true);
character->playAnimation(character->animations.walk, true);
}
}
else if( character->animator->getAnimation() != character->animations.walk ) {
character->playAnimation(character->animations.walk_start, false, true);
character->playAnimation(character->animations.walk_start, false);
}
}
}
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, true);
character->playAnimation(character->animations.walk_back, true);
}
}
else if( character->animator->getAnimation() != character->animations.walk_back ) {
character->playAnimation(character->animations.walk_back_start, false, true);
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, true);
character->playAnimation(character->animations.walk_right, true);
}
}
else if( character->animator->getAnimation() != character->animations.walk_right ) {
character->playAnimation(character->animations.walk_right_start, false, true);
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, true);
character->playAnimation(character->animations.walk_left, true);
}
}
else if( character->animator->getAnimation() != character->animations.walk_left ) {
character->playAnimation(character->animations.walk_left_start, false, true);
character->playAnimation(character->animations.walk_left_start, false);
}
}
}
if( _currentActivity == nullptr ) {
if( glm::length(d) < 0.001f ) {
character->playAnimation(character->animations.idle, true, false);
character->playAnimation(character->animations.idle, true);
}
}
}
@ -193,7 +193,7 @@ bool Activities::EnterVehicle::update(CharacterObject *character, CharacterContr
if( entering ) {
if( character->animator->getAnimation() == anm_open ) {
if( character->animator->isCompleted() ) {
character->playAnimation(anm_enter, false, false);
character->playAnimation(anm_enter, false);
character->enterVehicle(vehicle, seat);
}
else {
@ -220,7 +220,7 @@ bool Activities::EnterVehicle::update(CharacterObject *character, CharacterContr
// Warp character to vehicle orientation
character->controller->setRawMovement({0.f, 0.f, 0.f});
character->rotation = vehicle->getRotation();
character->playAnimation(anm_open, false, false);
character->playAnimation(anm_open, false);
}
else if( targetDistance > 15.f ) {
return true; // Give up if the vehicle is too far away.
@ -260,7 +260,7 @@ bool Activities::ExitVehicle::update(CharacterObject *character, CharacterContro
}
}
else {
character->playAnimation(anm_exit, false, false);
character->playAnimation(anm_exit, false);
}
return false;
@ -281,7 +281,7 @@ bool Activities::ShootWeapon::update(CharacterObject *character, CharacterContro
auto shootanim = character->engine->gameData.animations[wepdata->animation1];
if( shootanim ) {
if( character->animator->getAnimation() != shootanim ) {
character->playAnimation(shootanim, false, false);
character->playAnimation(shootanim, false);
}
auto loopstart = wepdata->animLoopStart / 100.f;

View File

@ -0,0 +1,123 @@
#include <data/Skeleton.hpp>
#include <render/Model.hpp>
#include <glm/gtc/matrix_transform.hpp>
Skeleton::FrameTransform Skeleton::IdentityTransform = { glm::vec3(0.f), glm::quat() };
Skeleton::FrameData Skeleton::IdentityData = { Skeleton::IdentityTransform, Skeleton::IdentityTransform, true };
Skeleton::Skeleton()
{
}
void Skeleton::setAllData(const Skeleton::FramesData& data)
{
framedata = data;
}
const Skeleton::FrameData& Skeleton::getData(unsigned int frameIdx) const
{
auto fdit = framedata.find(frameIdx);
if( fdit == framedata.end() )
{
return Skeleton::IdentityData;
}
return fdit->second;
}
void Skeleton::setData(unsigned int frameIdx, const Skeleton::FrameData& data)
{
framedata[frameIdx] = data;
}
void Skeleton::setEnabled(ModelFrame* frame, bool enabled)
{
auto fdit = framedata.find(frame->getIndex());
if( fdit != framedata.end() )
{
fdit->second.enabled = enabled;
}
else
{
FrameTransform tf { frame->getDefaultTranslation(), glm::quat_cast(frame->getDefaultRotation()) };
framedata.insert(
{frame->getIndex(),
{ tf, tf, enabled }
}
);
}
}
void Skeleton::setEnabled(unsigned int frameIdx, bool enabled)
{
auto fdit = framedata.find(frameIdx);
if( fdit == framedata.end() )
{
framedata.insert(
{ frameIdx, { Skeleton::IdentityTransform, Skeleton::IdentityTransform, enabled } }
);
}
else
{
fdit->second.enabled = enabled;
}
}
const Skeleton::FrameTransform& Skeleton::getInterpolated(unsigned int frameIdx) const
{
auto itit = interpolateddata.find(frameIdx);
if( itit == interpolateddata.end() )
{
return Skeleton::IdentityTransform;
}
return itit->second;
}
void Skeleton::interpolate(float alpha)
{
interpolateddata.clear();
for(auto i = framedata.begin(); i != framedata.end(); ++i)
{
auto& t2 = i->second.a.translation;
auto& t1 = i->second.b.translation;
auto& r2 = i->second.a.rotation;
auto& r1 = i->second.b.rotation;
interpolateddata.insert({
i->first,
{ glm::mix(t1, t2, alpha), glm::slerp(r1, r2, alpha) }
});
}
}
glm::mat4 Skeleton::getMatrix(unsigned int frameIdx) const
{
const FrameTransform& ft = getInterpolated(frameIdx);
glm::mat4 m;
m = glm::translate( m, ft.translation );
m = m * glm::mat4_cast( ft.rotation );
return m;
}
glm::mat4 Skeleton::getMatrix(ModelFrame* frame) const
{
auto itit = interpolateddata.find(frame->getIndex());
if( itit != interpolateddata.end() )
{
glm::mat4 m;
m = glm::translate( m, itit->second.translation );
m = m * glm::mat4_cast( itit->second.rotation );
return m;
}
return frame->getTransform();
}

View File

@ -1,12 +1,15 @@
#include <engine/Animator.hpp>
#include <loaders/LoaderDFF.hpp>
#include <render/Model.hpp>
#include <data/Skeleton.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <iostream>
Animator::Animator()
: model(nullptr), time(0.f), serverTime(0.f), lastServerTime(0.f), playing(true), repeat(true)
Animator::Animator(Model* model, Skeleton* skeleton)
: model(model), skeleton(skeleton), time(0.f), serverTime(0.f),
lastServerTime(0.f), playing(true), repeat(true)
{
reset();
}
void Animator::reset()
@ -15,21 +18,16 @@ void Animator::reset()
serverTime = 0.f;
lastServerTime = 0.f;
if( _frameInstances.empty() ) {
if( ! getAnimation() || ! model ) return;
for( ModelFrame* f : model->frames ) {
auto it = getAnimation()->bones.find(f->getName());
if( it == getAnimation()->bones.end() ) continue;
auto A = getKeyframeAt(f, 0.f);
auto kfit = _frameInstances.find( f );
if( kfit == _frameInstances.end() ) {
_frameInstances.insert( { f, { it->second, A, A } } );
}
else {
kfit->second.first = kfit->second.second;
kfit->second.second = A;
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 } } );
}
}
}
@ -61,79 +59,6 @@ void Animator::next()
reset();
}
void Animator::setModel(Model *model)
{
if(model == this->model) {
return;
}
this->model = model;
_frameInstances.clear();
reset();
}
void Animator::setFrameVisibility(ModelFrame *frame, bool visible)
{
auto fit = _frameInstances.find(frame);
if( fit != _frameInstances.end() ) {
fit->second.visible = visible;
}
else {
_frameInstances.insert({
frame,
{
visible
}
});
}
}
bool Animator::getFrameVisibility(ModelFrame *frame) const
{
auto fit = _frameInstances.find(frame);
if( fit != _frameInstances.end() ) {
return fit->second.visible;
}
return true;
}
void Animator::setFrameOrientation(ModelFrame *frame, const glm::quat &orientation)
{
auto fit = _frameInstances.find(frame);
if( fit != _frameInstances.end() ) {
fit->second.orientation = orientation;
}
else {
_frameInstances.insert({
frame,
{
true,
orientation
}
});
}
}
glm::quat Animator::getFrameOrientation(ModelFrame *frame) const
{
auto fit = _frameInstances.find(frame);
if( fit != _frameInstances.end() ) {
return fit->second.orientation;
}
return glm::toQuat(frame->getDefaultRotation());
}
Animator::FrameInstanceData *Animator::getFrameInstance(ModelFrame *frame)
{
auto fit = _frameInstances.find(frame);
if( fit != _frameInstances.end() ) {
return &fit->second;
}
return nullptr;
}
void Animator::tick(float dt)
{
if( model == nullptr || _animations.empty() ) {
@ -144,11 +69,34 @@ void Animator::tick(float dt)
lastServerTime = serverTime;
serverTime += dt;
}
for( auto& p : _frameInstances ) {
p.second.second = p.second.first;
float t = getAnimationTime();
p.second.first = getKeyframeAt(p.first, t);
for( auto& b : boneInstances )
{
auto kf = b.first->getInterpolatedKeyframe(getAnimationTime());
auto& data = skeleton->getData(b.second.frameIdx);
ModelFrame* frame = model->frames[b.second.frameIdx];
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);
}
if( isCompleted() && ! repeat && _animations.size() > 1 ) {
@ -157,27 +105,6 @@ void Animator::tick(float dt)
}
glm::vec3 Animator::getRootTranslation() const
{
// This is a pretty poor assumption.
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()) {
float df = fmod(lastServerTime, getAnimation()->duration);
float rt = getAnimationTime();
if(df < rt) {
auto lastKF = it->second->getInterpolatedKeyframe(df);
auto KF = it->second->getInterpolatedKeyframe(rt);
return KF.position - lastKF.position;
}
}
}
return glm::vec3();
}
glm::vec3 Animator::getDurationTransform() const
{
if(!model->frames[model->rootFrameIdx]->getChildren().empty() && !_animations.empty()) {
ModelFrame* realRoot = model->frames[model->rootFrameIdx]->getChildren()[0];
@ -193,86 +120,27 @@ glm::vec3 Animator::getDurationTransform() const
return glm::vec3();
}
glm::vec3 Animator::getTimeTranslation() 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() / getAnimation()->duration) );
}
}
return glm::vec3();
}
glm::quat Animator::getRootRotation() const
{
return glm::quat();
}
glm::mat4 Animator::getFrameMatrixAt(ModelFrame* frame, float time, bool disableRoot) const
{
if(getAnimation()) {
auto it = getAnimation()->bones.find(frame->getName());
if(it != getAnimation()->bones.end()) {
auto kf = it->second->getInterpolatedKeyframe(time);
glm::mat4 m;
bool isRoot = frame->getParent() ? !! frame->getParent() : false;
if(it->second->type == AnimationBone::R00 || ( isRoot && disableRoot ) ) {
m = glm::translate(m, frame->getDefaultTranslation());
m = m * glm::mat4_cast(kf.rotation);
}
else if(it->second->type == AnimationBone::RT0) {
m = glm::translate(m, kf.position);
m = m * glm::mat4_cast(kf.rotation);
}
else {
m = glm::translate(m, kf.position);
m = m * glm::mat4_cast(kf.rotation);
}
return m;
}
}
return frame->getTransform();
}
AnimationKeyframe Animator::getKeyframeAt(ModelFrame *frame, float time) const
{
if(getAnimation()) {
auto it = getAnimation()->bones.find(frame->getName());
if(it != getAnimation()->bones.end()) {
return it->second->getInterpolatedKeyframe(time);
}
}
return { glm::toQuat(frame->getDefaultRotation()), frame->getDefaultTranslation(), glm::vec3(1.f), 0.f, 0 };
}
glm::mat4 Animator::getFrameMatrix(ModelFrame *frame, float alpha, bool ignoreRoot) const
{
auto it = _frameInstances.find( frame );
if( it != _frameInstances.end() && it->second.bone ) {
const AnimationKeyframe& S = it->second.first;
const AnimationKeyframe& F = it->second.second;
AnimationKeyframe kf {
glm::slerp(F.rotation, S.rotation, alpha),
glm::mix(F.position, S.position, alpha),
glm::mix(F.scale, S.scale, alpha),
glm::mix(F.starttime, S.starttime, alpha),
S.id
};
glm::mat4 m;
bool isRoot = frame->getParent() ? !! frame->getParent() : false;
if(it->second.bone->type == AnimationBone::R00 || ( isRoot && ignoreRoot ) ) {
m = glm::translate(m, frame->getDefaultTranslation());
m = m * glm::mat4_cast(kf.rotation);
}
else if(it->second.bone->type == AnimationBone::RT0) {
m = glm::translate(m, kf.position);
m = m * glm::mat4_cast(kf.rotation);
}
else {
m = glm::translate(m, kf.position);
m = m * glm::mat4_cast(kf.rotation);
}
return m;
}
else if( it != _frameInstances.end() ) {
return frame->getTransform() * glm::mat4_cast(it->second.orientation);
}
return frame->getTransform();
}
bool Animator::isCompleted() const
{
return getAnimation() ? serverTime >= getAnimation()->duration : true;

View File

@ -1,6 +1,21 @@
#include <engine/GameObject.hpp>
#include <loaders/LoaderIFP.hpp>
#include <loaders/LoaderDFF.hpp>
#include <engine/Animator.hpp>
#include <data/Skeleton.hpp>
GameObject::~GameObject()
{
if(animator)
{
delete animator;
}
if(skeleton)
{
delete skeleton;
}
}
void GameObject::setPosition(const glm::vec3& pos)
{

View File

@ -5,6 +5,7 @@
#include <engine/Animator.hpp>
#include <engine/GameWorld.hpp>
#include <objects/ProjectileObject.hpp>
#include <data/Skeleton.hpp>
void WeaponItem::fireHitscan()
{
@ -12,7 +13,7 @@ void WeaponItem::fireHitscan()
glm::mat4 handMatrix;
if( handFrame ) {
while( handFrame->getParent() ) {
handMatrix = _character->animator->getFrameMatrix(handFrame) * handMatrix;
handMatrix = _character->skeleton->getMatrix(handFrame->getIndex()) * handMatrix;
handFrame = handFrame->getParent();
}
}

View File

@ -71,7 +71,7 @@ void LoaderDFF::readFrameList(Model *model, const RWBStream &stream)
model->rootFrameIdx = f;
}
auto frame = new ModelFrame(parent, data->rotation, data->position);
auto frame = new ModelFrame(f, parent, data->rotation, data->position);
model->frames.push_back(frame);
}

View File

@ -4,6 +4,7 @@
#include <engine/Animator.hpp>
#include <objects/VehicleObject.hpp>
#include <items/InventoryItem.hpp>
#include <data/Skeleton.hpp>
// TODO: make this not hardcoded
static glm::vec3 enter_offset(0.81756252f, 0.34800607f, -0.486281008f);
@ -43,8 +44,8 @@ CharacterObject::CharacterObject(GameWorld* engine, const glm::vec3& pos, const
animations.car_getout_lhs = engine->gameData.animations["car_getout_lhs"];
if(model) {
animator = new Animator();
animator->setModel(model->model);
skeleton = new Skeleton;
animator = new Animator(model->model, skeleton);
createActor();
}
@ -180,7 +181,7 @@ void CharacterObject::updateCharacter(float dt)
glm::vec3 animTranslate;
if( isAnimationFixed() ) {
auto d = animator->getDurationTransform() / animator->getAnimation()->duration;
auto d = animator->getRootTranslation() / animator->getAnimation()->duration;
animTranslate = d * dt;
}
@ -393,15 +394,14 @@ void CharacterObject::clearTargetPosition()
_hasTargetPosition = false;
}
void CharacterObject::playAnimation(Animation *animation, bool repeat, bool fixed)
void CharacterObject::playAnimation(Animation *animation, bool repeat)
{
_fixedAnimation = fixed;
animator->setAnimation(animation, repeat);
}
bool CharacterObject::isAnimationFixed() const
{
return _fixedAnimation;
return getCurrentVehicle() == nullptr;
}
void CharacterObject::addToInventory(InventoryItem *item)

View File

@ -1,15 +1,16 @@
#include <objects/CutsceneObject.hpp>
#include <engine/Animator.hpp>
#include <data/Skeleton.hpp>
CutsceneObject::CutsceneObject(GameWorld *engine, const glm::vec3 &pos, ModelHandle *model)
: GameObject(engine, pos, {}, model), _parent(nullptr), _bone(nullptr)
{
animator = new Animator;
skeleton = new Skeleton;
animator = new Animator(model->model, skeleton);
}
CutsceneObject::~CutsceneObject()
{
delete animator;
}
void CutsceneObject::tick(float dt)

View File

@ -4,6 +4,7 @@
#include <BulletDynamics/Vehicle/btRaycastVehicle.h>
#include <sys/stat.h>
#include <data/CollisionModel.hpp>
#include <data/Skeleton.hpp>
#include <render/Model.hpp>
#include <engine/Animator.hpp>
@ -59,21 +60,21 @@ VehicleObject::VehicleObject(GameWorld* engine, const glm::vec3& pos, const glm:
}
// Hide all LOD and damage frames.
animator = new Animator;
animator->setModel(model->model);
for(ModelFrame* f : model->model->frames) {
auto& name = f->getName();
skeleton = new Skeleton;
for(ModelFrame* frame : model->model->frames)
{
auto& name = frame->getName();
bool isDam = name.find("_dam") != name.npos;
bool isLod = name.find("lo") != name.npos;
bool isDum = name.find("_dummy") != name.npos;
/*bool isOk = name.find("_ok") != name.npos;*/
if(isDam || isLod || isDum ) {
animator->setFrameVisibility(f, false);
skeleton->setEnabled(frame, false);
}
if( isDum ) {
_hingedObjects[f] = {
_hingedObjects[frame] = {
nullptr,
nullptr
};
@ -90,7 +91,6 @@ VehicleObject::~VehicleObject()
delete physVehicle;
delete physRaycaster;
delete animator;
ejectAll();
}
@ -286,8 +286,11 @@ void VehicleObject::tickPhysics(float dt)
if(it.second.body == nullptr) continue;
auto inv = glm::inverse(getRotation());
auto rot = it.second.body->getWorldTransform().getRotation();
animator->setFrameOrientation(it.first,
inv * glm::quat(rot.w(), rot.x(), rot.y(), rot.z()));
auto r2 = inv * glm::quat(rot.w(), rot.x(), rot.y(), rot.z());
auto ldata = skeleton->getData(it.first->getIndex()).a;
ldata.rotation = r2;
skeleton->setData(it.first->getIndex(), { ldata, ldata, true } );
}
}
}
@ -404,7 +407,7 @@ bool VehicleObject::takeDamage(const GameObject::DamageInfo& dmg)
void VehicleObject::setFrameState(ModelFrame* f, FrameState state)
{
bool isOkVis = animator->getFrameVisibility(f);
bool isOkVis = skeleton->getData(f->getIndex()).enabled;
auto damName = f->getName();
damName.replace(damName.find("_ok"), 3, "_dam");
@ -412,14 +415,14 @@ void VehicleObject::setFrameState(ModelFrame* f, FrameState state)
if(DAM == state) {
if( isOkVis ) {
animator->setFrameVisibility(f, false);
animator->setFrameVisibility(damage, true);
skeleton->setEnabled(f, false);
skeleton->setEnabled(damage, true);
}
}
else if(OK == state) {
if(! isOkVis) {
animator->setFrameVisibility(f, true);
animator->setFrameVisibility(damage, false);
if(!isOkVis ) {
skeleton->setEnabled(f, true);
skeleton->setEnabled(damage, false);
}
}
}
@ -477,7 +480,6 @@ void VehicleObject::createObjectHinge(btTransform& local, ModelFrame *frame)
auto gbounds = geom->geometryBounds;
if( fn.find("door") != fn.npos ) {
std::cout << fn << std::endl;
hingeAxis = {0.f, 0.f, 1.f};
hingePosition = {0.f, 0.2f, 0.f};
boxSize = {0.1f, 0.4f, gbounds.radius/2.f};
@ -509,8 +511,7 @@ void VehicleObject::createObjectHinge(btTransform& local, ModelFrame *frame)
auto p = frame->getDefaultTranslation();
auto o = glm::toQuat(frame->getDefaultRotation());
auto bp = btVector3(p.x, p.y, p.z);
tr.setOrigin(bp);
tr.setOrigin(btVector3(p.x, p.y, p.z));
tr.setRotation(btQuaternion(o.x, o.y, o.z, o.w));
dms->setWorldTransform(local * tr);
@ -531,7 +532,7 @@ void VehicleObject::createObjectHinge(btTransform& local, ModelFrame *frame)
auto hinge = new btHingeConstraint(
*physBody,
*subObject,
bp, hingePosition,
tr.getOrigin(), hingePosition,
hingeAxis, hingeAxis);
hinge->setLimit(hingeMin, hingeMax);

View File

@ -15,6 +15,7 @@
#include <items/InventoryItem.hpp>
#include <data/CutsceneData.hpp>
#include <data/Skeleton.hpp>
#include <objects/CutsceneObject.hpp>
#include <render/GameShaders.hpp>
@ -275,6 +276,12 @@ void GameRenderer::renderWorld(const ViewCamera &camera, float alpha)
glActiveTexture(GL_TEXTURE0);
for( GameObject* object : engine->objects ) {
if( object->skeleton )
{
object->skeleton->interpolate(_renderAlpha);
}
switch(object->type()) {
case GameObject::Character:
renderPedestrian(static_cast<CharacterObject*>(object));
@ -457,8 +464,16 @@ void GameRenderer::renderPedestrian(CharacterObject *pedestrian)
glm::mat4 matrixModel = pedestrian->getTimeAdjustedTransform( _renderAlpha );
if(!pedestrian->model->model) return;
// Apply the inverse of the root transform from the current animation.
if( pedestrian->isAnimationFixed() )
{
auto rtranslate = pedestrian->animator->getTimeTranslation();
matrixModel = glm::translate(matrixModel, -rtranslate);
}
auto root = pedestrian->model->model->frames[0];
renderFrame(pedestrian->model->model, root->getChildren()[0], matrixModel, pedestrian, 1.f, pedestrian->animator);
if(pedestrian->getActiveItem()) {
@ -466,7 +481,7 @@ void GameRenderer::renderPedestrian(CharacterObject *pedestrian)
glm::mat4 localMatrix;
if( handFrame ) {
while( handFrame->getParent() ) {
localMatrix = pedestrian->animator->getFrameMatrix(handFrame, _renderAlpha) * localMatrix;
localMatrix = pedestrian->skeleton->getMatrix(handFrame->getIndex()) * localMatrix;
handFrame = handFrame->getParent();
}
}
@ -677,7 +692,7 @@ void GameRenderer::renderCutsceneObject(CutsceneObject *cutscene)
glm::mat4 localMatrix;
auto boneframe = cutscene->getParentFrame();
while( boneframe ) {
localMatrix = cutscene->getParentActor()->animator->getFrameMatrix(boneframe, _renderAlpha, false) * localMatrix;
localMatrix = cutscene->getParentActor()->skeleton->getMatrix(boneframe->getIndex()) * localMatrix;
boneframe = boneframe->getParent();
}
matrixModel = matrixModel * localMatrix;
@ -918,14 +933,11 @@ bool GameRenderer::renderFrame(Model* m, ModelFrame* f, const glm::mat4& matrix,
auto localmatrix = matrix;
bool vis = true;
if(object && object->animator) {
bool animFixed = false;
if( object->type() == GameObject::Character ) {
animFixed = static_cast<CharacterObject*>(object)->isAnimationFixed();
}
localmatrix *= object->animator->getFrameMatrix(f, _renderAlpha, animFixed);
if(object && object->skeleton) {
// Skeleton is loaded with the correct matrix via Animator.
localmatrix *= object->skeleton->getMatrix(f);
vis = object->animator->getFrameVisibility(f);
vis = object->skeleton->getData(f->getIndex()).enabled;
}
else {
localmatrix *= f->getTransform();
@ -933,13 +945,15 @@ bool GameRenderer::renderFrame(Model* m, ModelFrame* f, const glm::mat4& matrix,
if( vis ) {
for(size_t g : f->getGeometries()) {
RW::BSGeometryBounds& bounds = m->geometries[g]->geometryBounds;
/// @todo fix culling animating objects?
glm::vec3 boundpos = bounds.center + glm::vec3(matrix[3]);
if(! _camera.frustum.intersects(boundpos, bounds.radius)) {
culled++;
continue;
if( !object || !object->animator )
{
RW::BSGeometryBounds& bounds = m->geometries[g]->geometryBounds;
glm::vec3 boundpos = bounds.center + glm::vec3(localmatrix[3]);
if(! _camera.frustum.intersects(boundpos, bounds.radius)) {
culled++;
continue;
}
}
renderGeometry(m, g, localmatrix, opacity, object);

View File

@ -16,8 +16,8 @@ Model::Geometry::~Geometry()
}
ModelFrame::ModelFrame(ModelFrame* parent, glm::mat3 dR, glm::vec3 dT)
: defaultRotation(dR), defaultTranslation(dT), parentFrame(parent)
ModelFrame::ModelFrame(unsigned int index, ModelFrame* parent, glm::mat3 dR, glm::vec3 dT)
: index(index), defaultRotation(dR), defaultTranslation(dT), parentFrame(parent)
{
if(parent != nullptr) {
parent->childs.push_back(this);

View File

@ -15,6 +15,7 @@
#include <ai/DefaultAIController.hpp>
#include <data/CutsceneData.hpp>
#include <data/Skeleton.hpp>
#include <objects/CutsceneObject.hpp>
#include <glm/gtx/string_cast.hpp>
@ -420,7 +421,6 @@ VM_OPCODE_DEF( 0x02E6 )
std::transform(animName.begin(), animName.end(), animName.begin(), ::tolower);
Animation* anim = m->getWorld()->gameData.animations[animName];
if( anim ) {
object->animator->setModel(object->model->model);
object->animator->setAnimation(anim, false);
}
else {
@ -465,7 +465,7 @@ VM_OPCODE_DEF( 0x02F4 )
CutsceneObject* object = m->getWorld()->createCutsceneObject(id, m->getWorld()->state.currentCutscene->meta.sceneOffset );
auto headframe = actor->model->model->findFrame("shead");
actor->animator->setFrameVisibility(headframe, false);
actor->skeleton->setEnabled(headframe, false);
object->setParentActor(actor, headframe);
*p->at(2).handle = object;
@ -477,7 +477,6 @@ VM_OPCODE_DEF( 0x02F5 )
std::transform(animName.begin(), animName.end(), animName.begin(), ::tolower);
Animation* anim = m->getWorld()->gameData.animations[animName];
if( anim ) {
object->animator->setModel(object->model->model);
object->animator->setAnimation(anim, false);
}
else {

View File

@ -1,5 +1,6 @@
#include <boost/test/unit_test.hpp>
#include <engine/Animator.hpp>
#include <data/Skeleton.hpp>
#include <render/Model.hpp>
#include <glm/gtx/string_cast.hpp>
#include "test_globals.hpp"
@ -9,14 +10,14 @@ BOOST_AUTO_TEST_SUITE(AnimationTests)
BOOST_AUTO_TEST_CASE(test_matrix)
{
{
Animator animator;
Skeleton skeleton;
Animation animation;
/** Models are currently needed to relate animation bones <=> model frame #s. */
Global::get().e->gameData.loadDFF("player.dff");
ModelHandle* test_model = Global::get().e->gameData.models["player"];
BOOST_REQUIRE( test_model );
Animation animation;
Animator animator(test_model->model, &skeleton);
animation.duration = 1.f;
animation.bones["player"] = new AnimationBone{
@ -32,88 +33,19 @@ BOOST_AUTO_TEST_CASE(test_matrix)
},
}
};
animator.setAnimation(&animation);
animator.setModel( test_model->model );
{
auto intp_matrix = animator.getFrameMatrixAt( test_model->model->frames[0], 0.0f );
auto intp_col = intp_matrix[3];
BOOST_CHECK_EQUAL( glm::distance(glm::vec3(intp_col), glm::vec3(0.f, 0.f, 0.f)), 0.0f );
}
{
auto intp_matrix = animator.getFrameMatrixAt( test_model->model->frames[0], 0.5f );
auto intp_col = intp_matrix[3];
BOOST_CHECK_EQUAL( 0.0f, glm::distance(glm::vec3(intp_col), glm::vec3(0.f, 0.5f, 0.0f)) );
}
}
}
BOOST_AUTO_TEST_CASE(test_interpolate)
{
{
Animator animator;
Global::get().e->gameData.loadDFF("player.dff");
ModelHandle* test_model = Global::get().e->gameData.models["player"];
BOOST_REQUIRE( test_model );
Animation animation;
animation.duration = 2.0f;
animation.bones["player"] = new AnimationBone{
"player",
0, 0, 2.0f,
AnimationBone::RT0,
{
{
glm::quat(), glm::vec3(0.f, 0.f, 0.f), glm::vec3(), 0.f,
},
{
glm::quat(), glm::vec3(0.f, 1.f, 0.f), glm::vec3(), 1.0f,
},
{
glm::quat(), glm::vec3(0.f, 2.f, 0.f), glm::vec3(), 2.0f,
},
}
};
animator.setAnimation(&animation);
animator.setModel( test_model->model );
{
auto intp_matrix = animator.getFrameMatrix( test_model->model->frames[0] );
auto intp_col = intp_matrix[3];
BOOST_CHECK_EQUAL( glm::distance(glm::vec3(intp_col), glm::vec3(0.f, 0.f, 0.f)), 0.0f );
}
animator.tick( 1.f );
{
auto intp_matrix = animator.getFrameMatrix( test_model->model->frames[0], 0.5f );
auto intp_col = intp_matrix[3];
BOOST_CHECK_EQUAL( 0.0f, glm::distance(glm::vec3(intp_col), glm::vec3(0.f, 0.5f, 0.0f)) );
}
animator.tick( 1.f );
{
auto intp_matrix = animator.getFrameMatrix( test_model->model->frames[0], 0.5f );
auto intp_col = intp_matrix[3];
BOOST_CHECK_EQUAL( 0.0f, glm::distance(glm::vec3(intp_col), glm::vec3(0.f, 0.5f, 0.0f)) );
}
animator.tick( 1.f );
{
auto intp_matrix = animator.getFrameMatrix( test_model->model->frames[0], 0.5f );
auto intp_col = intp_matrix[3];
BOOST_CHECK_EQUAL( 0.0f, glm::distance(glm::vec3(intp_col), glm::vec3(0.f, 0.5f, 0.0f)) );
}
animator.tick(0.0f);
BOOST_CHECK( skeleton.getData(0).a.translation == glm::vec3(0.f, 0.f, 0.f) );
BOOST_CHECK( skeleton.getData(0).b.translation == glm::vec3(0.f, 0.f, 0.f) );
animator.tick(1.0f);
BOOST_CHECK( skeleton.getData(0).a.translation == glm::vec3(0.f, 1.f, 0.f) );
BOOST_CHECK( skeleton.getData(0).b.translation == glm::vec3(0.f, 0.f, 0.f) );
}
}

64
tests/test_skeleton.cpp Normal file
View File

@ -0,0 +1,64 @@
#include <boost/test/unit_test.hpp>
#include <data/Skeleton.hpp>
#include <glm/glm.hpp>
BOOST_AUTO_TEST_SUITE(SkeletonTests)
BOOST_AUTO_TEST_CASE(test_methods)
{
Skeleton::FrameTransform t1 { glm::vec3(0.f, 0.f, 0.f), glm::quat(0.f, 0.f, 0.f, 0.f) };
Skeleton::FrameTransform t2 { glm::vec3(1.f, 0.f, 0.f), glm::quat(0.f, 0.f, 1.f, 0.f) };
Skeleton skeleton;
skeleton.setAllData({
{0, { t1, t2, true }}
});
BOOST_CHECK(skeleton.getData(0).a.translation == t1.translation);
BOOST_CHECK(skeleton.getData(0).a.rotation == t1.rotation);
BOOST_CHECK(skeleton.getData(0).b.translation == t2.translation);
BOOST_CHECK(skeleton.getData(0).b.rotation == t2.rotation);
BOOST_CHECK(skeleton.getData(0).enabled);
skeleton.setData(0, {t2, t1, false});
BOOST_CHECK(skeleton.getData(0).a.translation == t2.translation);
BOOST_CHECK(skeleton.getData(0).a.rotation == t2.rotation);
BOOST_CHECK(skeleton.getData(0).b.translation == t1.translation);
BOOST_CHECK(skeleton.getData(0).b.rotation == t1.rotation);
BOOST_CHECK(! skeleton.getData(0).enabled);
}
BOOST_AUTO_TEST_CASE(test_interpolate)
{
Skeleton::FrameTransform t1 { glm::vec3(0.f, 0.f, 0.f), glm::quat(0.f, 0.f, 0.f, 0.f) };
Skeleton::FrameTransform t2 { glm::vec3(1.f, 0.f, 0.f), glm::quat(0.f, 0.f, 1.f, 0.f) };
Skeleton skeleton;
skeleton.setAllData({
{0, { t2, t1, true }}
});
/** Without calling Skeleton::interpolate(alpha) the result is identity */
BOOST_CHECK(skeleton.getInterpolated(0).translation == Skeleton::IdentityTransform.translation);
BOOST_CHECK(skeleton.getInterpolated(0).rotation == Skeleton::IdentityTransform.rotation);
skeleton.interpolate(0.f);
BOOST_CHECK(skeleton.getInterpolated(0).translation == t1.translation);
BOOST_CHECK(skeleton.getInterpolated(0).rotation == t1.rotation);
skeleton.interpolate(1.f);
BOOST_CHECK(skeleton.getInterpolated(0).translation == t2.translation);
BOOST_CHECK(skeleton.getInterpolated(0).rotation == t2.rotation);
}
BOOST_AUTO_TEST_SUITE_END()

View File

@ -2,7 +2,7 @@
#include "test_globals.hpp"
#include <objects/VehicleObject.hpp>
#include <render/Model.hpp>
#include <engine/Animator.hpp>
#include <data/Skeleton.hpp>
BOOST_AUTO_TEST_SUITE(VehicleTests)
@ -38,18 +38,18 @@ BOOST_AUTO_TEST_CASE(vehicle_frame_vis)
BOOST_REQUIRE(bonnet_ok != nullptr);
BOOST_REQUIRE(bonnet_dam != nullptr);
BOOST_CHECK( vehicle->animator->getFrameVisibility(bonnet_ok));
BOOST_CHECK(!vehicle->animator->getFrameVisibility(bonnet_dam));
BOOST_CHECK( vehicle->skeleton->getData(bonnet_ok->getIndex()).enabled );
BOOST_CHECK(!vehicle->skeleton->getData(bonnet_dam->getIndex()).enabled);
vehicle->setFrameState(bonnet_ok, VehicleObject::DAM);
BOOST_CHECK(!vehicle->animator->getFrameVisibility(bonnet_ok));
BOOST_CHECK( vehicle->animator->getFrameVisibility(bonnet_dam));
BOOST_CHECK(!vehicle->skeleton->getData(bonnet_ok->getIndex()).enabled );
BOOST_CHECK( vehicle->skeleton->getData(bonnet_dam->getIndex()).enabled);
vehicle->setFrameState(bonnet_ok, VehicleObject::OK);
BOOST_CHECK( vehicle->animator->getFrameVisibility(bonnet_ok));
BOOST_CHECK(!vehicle->animator->getFrameVisibility(bonnet_dam));
BOOST_CHECK( vehicle->skeleton->getData(bonnet_ok->getIndex()).enabled );
BOOST_CHECK(!vehicle->skeleton->getData(bonnet_dam->getIndex()).enabled);
Global::get().e->destroyObject(vehicle);
}