From 584618e991500737213878e7a9a38c1a5be7985a Mon Sep 17 00:00:00 2001 From: Daniel Evans Date: Thu, 11 Dec 2014 17:48:47 +0000 Subject: [PATCH] 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. --- rwengine/include/data/Skeleton.hpp | 64 +++++ rwengine/include/engine/Animator.hpp | 55 ++-- rwengine/include/engine/GameObject.hpp | 7 +- rwengine/include/objects/CharacterObject.hpp | 4 +- rwengine/include/render/Model.hpp | 6 +- rwengine/src/ai/CharacterController.cpp | 30 +-- rwengine/src/data/Skeleton.cpp | 123 +++++++++ rwengine/src/engine/Animator.cpp | 252 +++++-------------- rwengine/src/engine/GameObject.cpp | 15 ++ rwengine/src/items/WeaponItem.cpp | 3 +- rwengine/src/loaders/LoaderDFF.cpp | 2 +- rwengine/src/objects/CharacterObject.cpp | 12 +- rwengine/src/objects/CutsceneObject.cpp | 5 +- rwengine/src/objects/VehicleObject.cpp | 41 +-- rwengine/src/render/GameRenderer.cpp | 46 ++-- rwengine/src/render/Model.cpp | 4 +- rwengine/src/script/Opcodes3.cpp | 5 +- tests/test_animation.cpp | 106 ++------ tests/test_skeleton.cpp | 64 +++++ tests/test_vehicle.cpp | 14 +- 20 files changed, 459 insertions(+), 399 deletions(-) create mode 100644 rwengine/include/data/Skeleton.hpp create mode 100644 rwengine/src/data/Skeleton.cpp create mode 100644 tests/test_skeleton.cpp 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); }