diff --git a/rwengine/include/data/Skeleton.hpp b/rwengine/include/data/Skeleton.hpp new file mode 100644 index 00000000..eb8f6045 --- /dev/null +++ b/rwengine/include/data/Skeleton.hpp @@ -0,0 +1,64 @@ +#ifndef _SKELETON_HPP_ +#define _SKELETON_HPP_ + +#include +#include +#include +#include + +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 FramesData; + typedef std::map 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 \ No newline at end of file diff --git a/rwengine/include/engine/Animator.hpp b/rwengine/include/engine/Animator.hpp index df31f4a2..b0f51b7c 100644 --- a/rwengine/include/engine/Animator.hpp +++ b/rwengine/include/engine/Animator.hpp @@ -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 _frameInstances; + std::map 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 diff --git a/rwengine/include/engine/GameObject.hpp b/rwengine/include/engine/GameObject.hpp index fe1e1474..5cc1c5aa 100644 --- a/rwengine/include/engine/GameObject.hpp +++ b/rwengine/include/engine/GameObject.hpp @@ -9,6 +9,7 @@ #include #include +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::max()) {} - virtual ~GameObject() {} + virtual ~GameObject(); /** * @brief Enumeration of possible object types. diff --git a/rwengine/include/objects/CharacterObject.hpp b/rwengine/include/objects/CharacterObject.hpp index cca1dd06..448d22b6 100644 --- a/rwengine/include/objects/CharacterObject.hpp +++ b/rwengine/include/objects/CharacterObject.hpp @@ -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 ); diff --git a/rwengine/include/render/Model.hpp b/rwengine/include/render/Model.hpp index d0b19b98..029d5b9d 100644 --- a/rwengine/include/render/Model.hpp +++ b/rwengine/include/render/Model.hpp @@ -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 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; } diff --git a/rwengine/src/ai/CharacterController.cpp b/rwengine/src/ai/CharacterController.cpp index 883418df..3619ba57 100644 --- a/rwengine/src/ai/CharacterController.cpp +++ b/rwengine/src/ai/CharacterController.cpp @@ -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; diff --git a/rwengine/src/data/Skeleton.cpp b/rwengine/src/data/Skeleton.cpp new file mode 100644 index 00000000..51409b61 --- /dev/null +++ b/rwengine/src/data/Skeleton.cpp @@ -0,0 +1,123 @@ +#include +#include +#include + +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(); +} diff --git a/rwengine/src/engine/Animator.cpp b/rwengine/src/engine/Animator.cpp index d06ddb98..68ba5c35 100644 --- a/rwengine/src/engine/Animator.cpp +++ b/rwengine/src/engine/Animator.cpp @@ -1,12 +1,15 @@ #include #include #include +#include #include +#include -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; diff --git a/rwengine/src/engine/GameObject.cpp b/rwengine/src/engine/GameObject.cpp index ab841723..31099e25 100644 --- a/rwengine/src/engine/GameObject.cpp +++ b/rwengine/src/engine/GameObject.cpp @@ -1,6 +1,21 @@ #include #include #include +#include +#include + +GameObject::~GameObject() +{ + if(animator) + { + delete animator; + } + if(skeleton) + { + delete skeleton; + } +} + void GameObject::setPosition(const glm::vec3& pos) { diff --git a/rwengine/src/items/WeaponItem.cpp b/rwengine/src/items/WeaponItem.cpp index 13fb3424..8b990c3e 100644 --- a/rwengine/src/items/WeaponItem.cpp +++ b/rwengine/src/items/WeaponItem.cpp @@ -5,6 +5,7 @@ #include #include #include +#include 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(); } } diff --git a/rwengine/src/loaders/LoaderDFF.cpp b/rwengine/src/loaders/LoaderDFF.cpp index 5edacc97..241a5cbb 100644 --- a/rwengine/src/loaders/LoaderDFF.cpp +++ b/rwengine/src/loaders/LoaderDFF.cpp @@ -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); } diff --git a/rwengine/src/objects/CharacterObject.cpp b/rwengine/src/objects/CharacterObject.cpp index 6b9b7832..9cccda83 100644 --- a/rwengine/src/objects/CharacterObject.cpp +++ b/rwengine/src/objects/CharacterObject.cpp @@ -4,6 +4,7 @@ #include #include #include +#include // 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) diff --git a/rwengine/src/objects/CutsceneObject.cpp b/rwengine/src/objects/CutsceneObject.cpp index f7348d91..6ba0fb28 100644 --- a/rwengine/src/objects/CutsceneObject.cpp +++ b/rwengine/src/objects/CutsceneObject.cpp @@ -1,15 +1,16 @@ #include #include +#include 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) diff --git a/rwengine/src/objects/VehicleObject.cpp b/rwengine/src/objects/VehicleObject.cpp index 354b3073..8f0b8303 100644 --- a/rwengine/src/objects/VehicleObject.cpp +++ b/rwengine/src/objects/VehicleObject.cpp @@ -4,6 +4,7 @@ #include #include #include +#include #include #include @@ -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); diff --git a/rwengine/src/render/GameRenderer.cpp b/rwengine/src/render/GameRenderer.cpp index bbde1a75..1bb6b59e 100644 --- a/rwengine/src/render/GameRenderer.cpp +++ b/rwengine/src/render/GameRenderer.cpp @@ -15,6 +15,7 @@ #include #include +#include #include #include @@ -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(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(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); diff --git a/rwengine/src/render/Model.cpp b/rwengine/src/render/Model.cpp index 7a3d38fe..60325585 100644 --- a/rwengine/src/render/Model.cpp +++ b/rwengine/src/render/Model.cpp @@ -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); diff --git a/rwengine/src/script/Opcodes3.cpp b/rwengine/src/script/Opcodes3.cpp index 821a2b8a..d30cbae9 100644 --- a/rwengine/src/script/Opcodes3.cpp +++ b/rwengine/src/script/Opcodes3.cpp @@ -15,6 +15,7 @@ #include #include +#include #include #include @@ -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 { diff --git a/tests/test_animation.cpp b/tests/test_animation.cpp index 2a123252..06b6cb92 100644 --- a/tests/test_animation.cpp +++ b/tests/test_animation.cpp @@ -1,5 +1,6 @@ #include #include +#include #include #include #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) ); + } } diff --git a/tests/test_skeleton.cpp b/tests/test_skeleton.cpp new file mode 100644 index 00000000..39a24e85 --- /dev/null +++ b/tests/test_skeleton.cpp @@ -0,0 +1,64 @@ +#include +#include +#include + +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() + diff --git a/tests/test_vehicle.cpp b/tests/test_vehicle.cpp index 2b33bd5c..9e84aec6 100644 --- a/tests/test_vehicle.cpp +++ b/tests/test_vehicle.cpp @@ -2,7 +2,7 @@ #include "test_globals.hpp" #include #include -#include +#include 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); }