Browse Source

Added animation support to AssetImporter.
Added functions in Animation to set name and length, and add/remove tracks.
AnimationState now uses name hashes to query for bones.
Use world bounding box size for LOD scaled distance of GeometryNode and subclasses.
Removed manual animation LOD adjustments from NinjaSnowWar, as they became unnecessary.
Lowered the animation LOD basescale slightly, as now the bounding box dimensions are taken into account correctly.

Lasse Öörni 15 years ago
parent
commit
ba10256fca

+ 2 - 2
Engine/Renderer/AnimatedModel.cpp

@@ -522,7 +522,7 @@ void AnimatedModel::updateNode(const FrameInfo& frame)
             return;
             return;
         // Multiply the distance by a constant so that invisible nodes don't update that often
         // Multiply the distance by a constant so that invisible nodes don't update that often
         static const Vector3 dotScale(1 / 3.0f, 1 / 3.0f, 1 / 3.0f);
         static const Vector3 dotScale(1 / 3.0f, 1 / 3.0f, 1 / 3.0f);
-        float scale = getWorldScale().dotProduct(dotScale);
+        float scale = getWorldBoundingBox().getSize().dotProduct(dotScale);
         mAnimationLodDistance = frame.mCamera->getLodDistance(ANIMATION_LOD_INVISIBLE_FACTOR * distance, scale, mLodBias);
         mAnimationLodDistance = frame.mCamera->getLodDistance(ANIMATION_LOD_INVISIBLE_FACTOR * distance, scale, mLodBias);
     }
     }
     
     
@@ -534,7 +534,7 @@ void AnimatedModel::updateDistance(const FrameInfo& frame)
     mDistance = frame.mCamera->getDistance(getWorldPosition());
     mDistance = frame.mCamera->getDistance(getWorldPosition());
     
     
     static const Vector3 dotScale(1 / 3.0f, 1 / 3.0f, 1 / 3.0f);
     static const Vector3 dotScale(1 / 3.0f, 1 / 3.0f, 1 / 3.0f);
-    float scale = getWorldScale().dotProduct(dotScale);
+    float scale = getWorldBoundingBox().getSize().dotProduct(dotScale);
     float newLodDistance = frame.mCamera->getLodDistance(mDistance, scale, mLodBias);
     float newLodDistance = frame.mCamera->getLodDistance(mDistance, scale, mLodBias);
     
     
     // If model is rendered from several views, use the minimum LOD distance for animation LOD
     // If model is rendered from several views, use the minimum LOD distance for animation LOD

+ 69 - 1
Engine/Renderer/Animation.cpp

@@ -24,6 +24,7 @@
 #include "Precompiled.h"
 #include "Precompiled.h"
 #include "Animation.h"
 #include "Animation.h"
 #include "Deserializer.h"
 #include "Deserializer.h"
+#include "Log.h"
 #include "Serializer.h"
 #include "Serializer.h"
 
 
 #include "DebugNew.h"
 #include "DebugNew.h"
@@ -125,6 +126,73 @@ void Animation::save(Serializer& dest)
     }
     }
 }
 }
 
 
+void Animation::setAnimationName(const std::string& name)
+{
+    mAnimationName = name;
+    mAnimationNameHash = StringHash(name);
+}
+
+void Animation::setLength(float length)
+{
+    mLength = max(length, 0.0f);
+}
+
+void Animation::setTracks(const std::vector<AnimationTrack>& tracks)
+{
+    mTracks = tracks;
+}
+
+void Animation::addTrack(const AnimationTrack& track)
+{
+    AnimationTrack* existing = const_cast<AnimationTrack*>(getTrack(track.mName));
+    // Make sure the name hash is correct
+    if (existing)
+    {
+        *existing = track;
+        existing->mNameHash = StringHash(existing->mName);
+    }
+    else
+    {
+        mTracks.push_back(track);
+        AnimationTrack* newTrack = &mTracks[mTracks.size() - 1];
+        newTrack->mNameHash = StringHash(newTrack->mName);
+    }
+}
+
+void Animation::removeTrack(unsigned index)
+{
+    if (index >= mTracks.size())
+    {
+        LOGERROR("Illegal track index");
+        return;
+    }
+    mTracks.erase(mTracks.begin() + index);
+}
+
+void Animation::removeTrack(const std::string& name)
+{
+    for (std::vector<AnimationTrack>::iterator i = mTracks.begin(); i != mTracks.end(); ++i)
+    {
+        if (i->mName == name)
+        {
+            mTracks.erase(i);
+            return;
+        }
+    }
+}
+
+void Animation::removeTrack(StringHash nameHash)
+{
+    for (std::vector<AnimationTrack>::iterator i = mTracks.begin(); i != mTracks.end(); ++i)
+    {
+        if (i->mNameHash == nameHash)
+        {
+            mTracks.erase(i);
+            return;
+        }
+    }
+}
+
 unsigned Animation::getNumTracks() const
 unsigned Animation::getNumTracks() const
 {
 {
     return mTracks.size();
     return mTracks.size();
@@ -149,7 +217,7 @@ const AnimationTrack* Animation::getTrack(const std::string& name) const
     return 0;
     return 0;
 }
 }
 
 
-const AnimationTrack* Animation::getTrack(const StringHash nameHash) const
+const AnimationTrack* Animation::getTrack(StringHash nameHash) const
 {
 {
     for (std::vector<AnimationTrack>::const_iterator i = mTracks.begin(); i != mTracks.end(); ++i)
     for (std::vector<AnimationTrack>::const_iterator i = mTracks.begin(); i != mTracks.end(); ++i)
     {
     {

+ 16 - 1
Engine/Renderer/Animation.h

@@ -81,6 +81,21 @@ public:
     //! Save resource. Throw exception on error
     //! Save resource. Throw exception on error
     virtual void save(Serializer& dest);
     virtual void save(Serializer& dest);
     
     
+    //! Set animation name
+    void setAnimationName(const std::string& name);
+    //! Set animation length
+    void setLength(float length);
+    //! Set animation tracks
+    void setTracks(const std::vector<AnimationTrack>& tracks);
+    //! Add an animation track. If already exists with the same name, replace
+    void addTrack(const AnimationTrack& track);
+    //! Remove an animation track by index
+    void removeTrack(unsigned index);
+    //! Remove an animation track by bone name
+    void removeTrack(const std::string& name);
+    //! Remove an animation track by bone name hash
+    void removeTrack(StringHash nameHash);
+    
     //! Return animation name
     //! Return animation name
     const std::string& getAnimationName() const { return mAnimationName; }
     const std::string& getAnimationName() const { return mAnimationName; }
     //! Return animation name hash
     //! Return animation name hash
@@ -96,7 +111,7 @@ public:
     //! Return animation track by bone name
     //! Return animation track by bone name
     const AnimationTrack* getTrack(const std::string& name) const;
     const AnimationTrack* getTrack(const std::string& name) const;
     //! Return animation track by bone name hash
     //! Return animation track by bone name hash
-    const AnimationTrack* getTrack(const StringHash nameHash) const;
+    const AnimationTrack* getTrack(StringHash nameHash) const;
     
     
 private:
 private:
     //! Animation name
     //! Animation name

+ 2 - 2
Engine/Renderer/AnimationState.cpp

@@ -138,11 +138,11 @@ void AnimationState::setStartBone(Bone* startBone)
         
         
         // Try to find a bone from the skeleton that corresponds to this track
         // Try to find a bone from the skeleton that corresponds to this track
         // with the limitation that it's startBone, or one of its children
         // with the limitation that it's startBone, or one of its children
-        if (startBone->getName() == tracks[i].mName)
+        if (startBone->getNameHash() == tracks[i].mNameHash)
             trackBone = startBone;
             trackBone = startBone;
         else
         else
         {
         {
-            trackBone = dynamic_cast<Bone*>(startBone->getChild(tracks[i].mName, true));
+            trackBone = dynamic_cast<Bone*>(startBone->getChild(tracks[i].mNameHash, true));
             // Make sure the child bone actually belongs to the skeleton
             // Make sure the child bone actually belongs to the skeleton
             if (trackBone)
             if (trackBone)
             {
             {

+ 1 - 3
Engine/Renderer/BillboardSet.cpp

@@ -261,9 +261,7 @@ void BillboardSet::updateDistance(const FrameInfo& frame)
     
     
     // Calculate scaled distance for animation LOD
     // Calculate scaled distance for animation LOD
     static const Vector3 dotScale(1 / 3.0f, 1 / 3.0f, 1 / 3.0f);
     static const Vector3 dotScale(1 / 3.0f, 1 / 3.0f, 1 / 3.0f);
-    float scale = 1.0f;
-    if (mScaleBillboards)
-        scale = getWorldScale().dotProduct(dotScale);
+    float scale = getWorldBoundingBox().getSize().dotProduct(dotScale);
     mLodDistance = frame.mCamera->getLodDistance(mDistance, scale, mLodBias);
     mLodDistance = frame.mCamera->getLodDistance(mDistance, scale, mLodBias);
 }
 }
 
 

+ 1 - 1
Engine/Renderer/GeometryNode.cpp

@@ -169,7 +169,7 @@ void GeometryNode::updateDistance(const FrameInfo& frame)
     mDistance = frame.mCamera->getDistance(getWorldPosition());
     mDistance = frame.mCamera->getDistance(getWorldPosition());
     
     
     static const Vector3 dotScale(1 / 3.0f, 1 / 3.0f, 1 / 3.0f);
     static const Vector3 dotScale(1 / 3.0f, 1 / 3.0f, 1 / 3.0f);
-    float scale = getWorldScale().dotProduct(dotScale);
+    float scale = getWorldBoundingBox().getSize().dotProduct(dotScale);
     float newLodDistance = frame.mCamera->getLodDistance(mDistance, scale, mLodBias);
     float newLodDistance = frame.mCamera->getLodDistance(mDistance, scale, mLodBias);
     
     
     if (newLodDistance != mLodDistance)
     if (newLodDistance != mLodDistance)

+ 1 - 1
Engine/Renderer/RendererDefs.h

@@ -214,7 +214,7 @@ static const unsigned MASK_INSTANCEMATRIX3 = 0x2000;
 static const unsigned MASK_DEFAULT = 0xffffffff;
 static const unsigned MASK_DEFAULT = 0xffffffff;
 static const unsigned NO_ELEMENT = 0xffffffff;
 static const unsigned NO_ELEMENT = 0xffffffff;
 
 
-static const float ANIMATION_LOD_BASESCALE = 2500.0f;
+static const float ANIMATION_LOD_BASESCALE = 2000.0f;
 static const float ANIMATION_LOD_INVISIBLE_FACTOR = 2.0f;
 static const float ANIMATION_LOD_INVISIBLE_FACTOR = 2.0f;
 
 
 static const unsigned MAX_VERTEX_LIGHTS = 4;
 static const unsigned MAX_VERTEX_LIGHTS = 4;

+ 1 - 1
Engine/Resource/ResourceCache.h

@@ -112,7 +112,7 @@ public:
     //! Return whether a file exists by name
     //! Return whether a file exists by name
     bool exists(const std::string& name) const;
     bool exists(const std::string& name) const;
     //! Return whether a file exists by name hash
     //! Return whether a file exists by name hash
-    bool exists(const StringHash nameHash) const;
+    bool exists(StringHash nameHash) const;
     //! Return memory budget for a resource type
     //! Return memory budget for a resource type
     unsigned getMemoryBudget(ShortStringHash type) const;
     unsigned getMemoryBudget(ShortStringHash type) const;
     //! Return total memory use for a resource type
     //! Return total memory use for a resource type

+ 0 - 3
Examples/NinjaSnowWar/Ninja.cpp

@@ -249,8 +249,6 @@ void Ninja::onCreate(const Vector3& position, const Quaternion& orientation)
     model->setMaterial(cache->getResource<Material>("Materials/Ninja.xml"));
     model->setMaterial(cache->getResource<Material>("Materials/Ninja.xml"));
     model->setDrawDistance(tRenderDistance);
     model->setDrawDistance(tRenderDistance);
     model->setCastShadows(true);
     model->setCastShadows(true);
-    // World unit is centimeter, so need bigger animation LOD bias (default 1.0 is for meters)
-    model->setAnimationLodBias(100.0f);
     
     
     // Create body
     // Create body
     RigidBody* body = createComponent<RigidBody>();
     RigidBody* body = createComponent<RigidBody>();
@@ -524,7 +522,6 @@ bool Ninja::onDeathUpdate(float time)
             ParticleEmitter* emitter = obj->createComponent<ParticleEmitter>();
             ParticleEmitter* emitter = obj->createComponent<ParticleEmitter>();
             emitter->loadParameters(cache->getResource<XMLFile>("Particle/Smoke.xml"), cache);
             emitter->loadParameters(cache->getResource<XMLFile>("Particle/Smoke.xml"), cache);
             emitter->setPosition(Vector3(0,-50,0));
             emitter->setPosition(Vector3(0,-50,0));
-            emitter->setAnimationLodBias(100.0f);
             getBody()->addChild(emitter);
             getBody()->addChild(emitter);
             mSmoke = true;
             mSmoke = true;
         }
         }

+ 0 - 1
Examples/NinjaSnowWar/SnowBall.cpp

@@ -197,6 +197,5 @@ void SnowBall::onRemove()
     ParticleEmitter* emitter = obj->createComponent<ParticleEmitter>();
     ParticleEmitter* emitter = obj->createComponent<ParticleEmitter>();
     emitter->loadParameters(cache->getResource<XMLFile>("Particle/SnowExplosion.xml"), cache);
     emitter->loadParameters(cache->getResource<XMLFile>("Particle/SnowExplosion.xml"), cache);
     emitter->setPosition(getBody()->getPhysicsPosition());
     emitter->setPosition(getBody()->getPhysicsPosition());
-    emitter->setAnimationLodBias(100.0f);
 }
 }
 
 

+ 0 - 1
Examples/NinjaSnowWar/SnowCrate.cpp

@@ -118,7 +118,6 @@ void SnowCrate::onRemove()
     ParticleEmitter* emitter = obj->createComponent<ParticleEmitter>();
     ParticleEmitter* emitter = obj->createComponent<ParticleEmitter>();
     emitter->loadParameters(cache->getResource<XMLFile>("Particle/SnowExplosionBig.xml"), cache);
     emitter->loadParameters(cache->getResource<XMLFile>("Particle/SnowExplosionBig.xml"), cache);
     emitter->setPosition(getBody()->getPhysicsPosition());
     emitter->setPosition(getBody()->getPhysicsPosition());
-    emitter->setAnimationLodBias(100.0f);
     
     
     GameObject* obj2 = spawnObject<Potion>("Potion");
     GameObject* obj2 = spawnObject<Potion>("Potion");
     obj2->create(getBody()->getPhysicsPosition());
     obj2->create(getBody()->getPhysicsPosition());

+ 204 - 10
Tools/AssetImporter/AssetImporter.cpp

@@ -22,6 +22,7 @@
 //
 //
 
 
 #include "Exception.h"
 #include "Exception.h"
+#include "Animation.h"
 #include "File.h"
 #include "File.h"
 #include "Geometry.h"
 #include "Geometry.h"
 #include "IndexBuffer.h"
 #include "IndexBuffer.h"
@@ -59,6 +60,7 @@ struct ExportModel
     std::vector<aiMesh*> mMeshes;
     std::vector<aiMesh*> mMeshes;
     std::vector<aiNode*> mMeshNodes;
     std::vector<aiNode*> mMeshNodes;
     std::vector<aiNode*> mBones;
     std::vector<aiNode*> mBones;
+    std::vector<aiAnimation*> mAnimations;
     std::vector<float> mBoneRadii;
     std::vector<float> mBoneRadii;
     std::vector<BoundingBox> mBoneHitboxes;
     std::vector<BoundingBox> mBoneHitboxes;
     aiNode* mRootBone;
     aiNode* mRootBone;
@@ -73,8 +75,10 @@ void exportModel(ExportModel& model);
 void collectMeshes(ExportModel& model, aiNode* node);
 void collectMeshes(ExportModel& model, aiNode* node);
 void collectBones(ExportModel& model);
 void collectBones(ExportModel& model);
 void collectBonesFinal(std::vector<aiNode*>& dest, const std::set<aiNode*>& necessary, aiNode* node);
 void collectBonesFinal(std::vector<aiNode*>& dest, const std::set<aiNode*>& necessary, aiNode* node);
-void generateBoneCollisionInfo(ExportModel& model);
-void buildModel(ExportModel& model);
+void collectAnimations(ExportModel& model);
+void buildBoneCollisionInfo(ExportModel& model);
+void buildAndSaveModel(ExportModel& model);
+void buildAndSaveAnimations(ExportModel& model);
 unsigned getBoneIndex(ExportModel& model, const std::string& boneName);
 unsigned getBoneIndex(ExportModel& model, const std::string& boneName);
 aiBone* getMeshBone(ExportModel& model, const std::string& boneName);
 aiBone* getMeshBone(ExportModel& model, const std::string& boneName);
 void getBlendData(ExportModel& model, aiMesh* mesh, std::vector<unsigned>& boneMappings, std::vector<std::vector<unsigned char> >& blendIndices, std::vector<std::vector<float> >& blendWeights);
 void getBlendData(ExportModel& model, aiMesh* mesh, std::vector<unsigned>& boneMappings, std::vector<std::vector<unsigned char> >& blendIndices, std::vector<std::vector<float> >& blendWeights);
@@ -90,6 +94,7 @@ Vector3 toVector3(const aiVector3D& vec);
 Vector2 toVector2(const aiVector2D& vec);
 Vector2 toVector2(const aiVector2D& vec);
 Quaternion toQuaternion(const aiQuaternion& quat);
 Quaternion toQuaternion(const aiQuaternion& quat);
 aiMatrix4x4 getWorldTransform(aiNode* node, aiNode* rootNode);
 aiMatrix4x4 getWorldTransform(aiNode* node, aiNode* rootNode);
+aiMatrix4x4 getWorldTransform(aiMatrix4x4 transform, aiNode* node, aiNode* rootNode);
 void getPosRotScale(const aiMatrix4x4& transform, Vector3& pos, Quaternion& rot, Vector3& scale);
 void getPosRotScale(const aiMatrix4x4& transform, Vector3& pos, Quaternion& rot, Vector3& scale);
 void errorExit(const std::string& error);
 void errorExit(const std::string& error);
 
 
@@ -178,8 +183,10 @@ void exportModel(ExportModel& model)
 {
 {
     collectMeshes(model, model.mRootNode);
     collectMeshes(model, model.mRootNode);
     collectBones(model);
     collectBones(model);
-    generateBoneCollisionInfo(model);
-    buildModel(model);
+    collectAnimations(model);
+    buildBoneCollisionInfo(model);
+    buildAndSaveModel(model);
+    buildAndSaveAnimations(model);
 }
 }
 
 
 void collectMeshes(ExportModel& model, aiNode* node)
 void collectMeshes(ExportModel& model, aiNode* node)
@@ -265,7 +272,29 @@ void collectBonesFinal(std::vector<aiNode*>& dest, const std::set<aiNode*>& nece
     }
     }
 }
 }
 
 
-void generateBoneCollisionInfo(ExportModel& model)
+void collectAnimations(ExportModel& model)
+{
+    const aiScene* scene = model.mScene;
+    for (unsigned i = 0; i < scene->mNumAnimations; ++i)
+    {
+        aiAnimation* anim = scene->mAnimations[i];
+        bool modelBoneFound = false;
+        for (unsigned j = 0; j < anim->mNumChannels; ++j)
+        {
+            aiNodeAnim* channel = anim->mChannels[j];
+            std::string channelName = toStdString(channel->mNodeName);
+            if (getBoneIndex(model, channelName) != M_MAX_UNSIGNED)
+            {
+                modelBoneFound = true;
+                break;
+            }
+        }
+        if (modelBoneFound)
+            model.mAnimations.push_back(anim);
+    }
+}
+
+void buildBoneCollisionInfo(ExportModel& model)
 {
 {
     for (unsigned i = 0; i < model.mMeshes.size(); ++i)
     for (unsigned i = 0; i < model.mMeshes.size(); ++i)
     {
     {
@@ -276,6 +305,8 @@ void generateBoneCollisionInfo(ExportModel& model)
             aiBone* bone = mesh->mBones[j];
             aiBone* bone = mesh->mBones[j];
             std::string boneName = toStdString(bone->mName);
             std::string boneName = toStdString(bone->mName);
             unsigned boneIndex = getBoneIndex(model, boneName);
             unsigned boneIndex = getBoneIndex(model, boneName);
+            if (boneIndex == M_MAX_UNSIGNED)
+                continue;
             aiNode* boneNode = model.mBones[boneIndex];
             aiNode* boneNode = model.mBones[boneIndex];
             aiMatrix4x4 boneWorldTransform = getWorldTransform(boneNode, model.mRootNode);
             aiMatrix4x4 boneWorldTransform = getWorldTransform(boneNode, model.mRootNode);
             aiMatrix4x4 boneInverse = boneWorldTransform;
             aiMatrix4x4 boneInverse = boneWorldTransform;
@@ -298,7 +329,7 @@ void generateBoneCollisionInfo(ExportModel& model)
     }
     }
 }
 }
 
 
-void buildModel(ExportModel& model)
+void buildAndSaveModel(ExportModel& model)
 {
 {
     if (!model.mRootNode)
     if (!model.mRootNode)
         errorExit("Null root node for model");
         errorExit("Null root node for model");
@@ -539,6 +570,149 @@ void buildModel(ExportModel& model)
     outModel->save(outFile);
     outModel->save(outFile);
 }
 }
 
 
+void buildAndSaveAnimations(ExportModel& model)
+{
+    for (unsigned i = 0; i < model.mAnimations.size(); ++i)
+    {
+        aiAnimation* anim = model.mAnimations[i];
+        std::string animName = toStdString(anim->mName);
+        if (animName.empty())
+            animName = "Anim" + toString(i + 1);
+        std::cout << "Writing animation " + animName << std::endl;
+        std::string animOutName = getPath(model.mOutName) + getFileName(model.mOutName) + "_" + animName + ".ani";
+        
+        SharedPtr<Animation> outAnim(new Animation());
+        float tickConversion = 1.0f / (float)anim->mTicksPerSecond;
+        outAnim->setAnimationName(animName);
+        outAnim->setLength((float)anim->mDuration * tickConversion);
+        
+        std::cout << "Animation length " << outAnim->getLength() << std::endl;
+        
+        for (unsigned j = 0; j < anim->mNumChannels; ++j)
+        {
+            aiNodeAnim* channel = anim->mChannels[j];
+            std::string channelName = toStdString(channel->mNodeName);
+            std::cout << "Animation track " << channelName << std::endl;
+            unsigned boneIndex = getBoneIndex(model, channelName);
+            if (boneIndex == M_MAX_UNSIGNED)
+            {
+                std::cout << "Warning: skipping animation track " << channelName << " not found in model skeleton" << std::endl;
+                continue;
+            }
+            
+            aiNode* boneNode = model.mBones[boneIndex];
+            
+            AnimationTrack track;
+            track.mName = channelName;
+            track.mNameHash = StringHash(channelName);
+            
+            // Check which channels are used
+            track.mChannelMask = 0;
+            if (channel->mNumPositionKeys > 1)
+                track.mChannelMask |= CHANNEL_POSITION;
+            if (channel->mNumRotationKeys > 1)
+                track.mChannelMask |= CHANNEL_ROTATION;
+            if (channel->mNumScalingKeys > 1)
+                track.mChannelMask |= CHANNEL_SCALE;
+            // Check for redundant identity scale in all keyframes and remove in that case
+            if (track.mChannelMask & CHANNEL_SCALE)
+            {
+                bool redundantScale = true;
+                for (unsigned k = 0; k < channel->mNumScalingKeys; ++k)
+                {
+                    float SCALE_EPSILON = 0.000001f;
+                    Vector3 scaleVec = toVector3(channel->mScalingKeys[k].mValue);
+                    if ((fabsf(scaleVec.mX - 1.0f) >= SCALE_EPSILON) || (fabsf(scaleVec.mY - 1.0f) >= SCALE_EPSILON) ||
+                        (fabsf(scaleVec.mZ - 1.0f) >= SCALE_EPSILON))
+                    {
+                        redundantScale = false;
+                        break;
+                    }
+                }
+                if (redundantScale)
+                    track.mChannelMask &= ~CHANNEL_SCALE;
+            }
+            
+            if (!track.mChannelMask)
+                std::cout << "Warning: skipping animation track " << channelName << " with no keyframes" << std::endl;
+            
+            // Currently only same amount of keyframes is supported
+            // Note: should also check the times of individual keyframes for match
+            if (((channel->mNumPositionKeys > 1) && (channel->mNumRotationKeys > 1) && (channel->mNumPositionKeys != channel->mNumRotationKeys)) ||
+                ((channel->mNumPositionKeys > 1) && (channel->mNumScalingKeys > 1) && (channel->mNumPositionKeys != channel->mNumScalingKeys)) ||
+                ((channel->mNumRotationKeys > 1) && (channel->mNumScalingKeys > 1) && (channel->mNumRotationKeys != channel->mNumScalingKeys)))
+            {
+                std::cout << "Warning: differing amount of channel keyframes, skipping animation track " << channelName << std::endl;
+                continue;
+            }
+            
+            unsigned keyFrames = channel->mNumPositionKeys;
+            if (channel->mNumRotationKeys > keyFrames)
+                keyFrames = channel->mNumRotationKeys;
+            if (channel->mNumScalingKeys > keyFrames)
+                keyFrames = channel->mNumScalingKeys;
+            
+            for (unsigned k = 0; k < keyFrames; ++k)
+            {
+                AnimationKeyFrame kf;
+                kf.mTime = 0.0f;
+                kf.mPosition = Vector3::sZero;
+                kf.mRotation = Quaternion::sIdentity;
+                kf.mScale = Vector3::sUnity;
+                
+                // Get time for the keyframe
+                if ((track.mChannelMask & CHANNEL_POSITION) && (k < channel->mNumPositionKeys))
+                    kf.mTime = (float)channel->mPositionKeys[k].mTime * tickConversion;
+                else if ((track.mChannelMask & CHANNEL_ROTATION) && (k < channel->mNumRotationKeys))
+                    kf.mTime = (float)channel->mRotationKeys[k].mTime * tickConversion;
+                else if ((track.mChannelMask & CHANNEL_SCALE) && (k < channel->mNumScalingKeys))
+                    kf.mTime = (float)channel->mScalingKeys[k].mTime * tickConversion;
+                
+                // Start with the bone's base transform
+                aiMatrix4x4 boneTransform = boneNode->mTransformation;
+                aiVector3D pos, scale;
+                aiQuaternion rot;
+                boneTransform.Decompose(scale, rot, pos);
+                // Then apply the active channels
+                if ((track.mChannelMask & CHANNEL_POSITION) && (k < channel->mNumPositionKeys))
+                    pos = channel->mPositionKeys[k].mValue;
+                if ((track.mChannelMask & CHANNEL_ROTATION) && (k < channel->mNumRotationKeys))
+                    rot = channel->mRotationKeys[k].mValue;
+                if ((track.mChannelMask & CHANNEL_SCALE) && (k < channel->mNumScalingKeys))
+                    scale = channel->mScalingKeys[k].mValue;
+                
+                // If root bone, transform with the model root node transform
+                if (!boneIndex)
+                {
+                    aiMatrix4x4 transMat, scaleMat, rotMat;
+                    aiMatrix4x4::Translation(pos, transMat);
+                    aiMatrix4x4::Scaling(scale, scaleMat);
+                    rotMat = aiMatrix4x4(rot.GetMatrix());
+                    aiMatrix4x4 tform = transMat * rotMat * scaleMat;
+                    tform = getWorldTransform(tform, boneNode, model.mRootNode);
+                    tform.Decompose(scale, rot, pos);
+                }
+                
+                if (track.mChannelMask & CHANNEL_POSITION)
+                    kf.mPosition = toVector3(pos);
+                if (track.mChannelMask & CHANNEL_ROTATION)
+                    kf.mRotation = toQuaternion(rot);
+                if (track.mChannelMask & CHANNEL_SCALE)
+                    kf.mScale = toVector3(scale);
+                
+                std::cout << toString(kf.mRotation) << std::endl;
+                
+                track.mKeyFrames.push_back(kf);
+            }
+            
+            outAnim->addTrack(track);
+        }
+        
+        File outFile(animOutName, FILE_WRITE);
+        outAnim->save(outFile);
+    }
+}
+
 unsigned getBoneIndex(ExportModel& model, const std::string& boneName)
 unsigned getBoneIndex(ExportModel& model, const std::string& boneName)
 {
 {
     for (unsigned i = 0; i < model.mBones.size(); ++i)
     for (unsigned i = 0; i < model.mBones.size(); ++i)
@@ -546,7 +720,7 @@ unsigned getBoneIndex(ExportModel& model, const std::string& boneName)
         if (toStdString(model.mBones[i]->mName) == boneName)
         if (toStdString(model.mBones[i]->mName) == boneName)
             return i;
             return i;
     }
     }
-    errorExit("Bone " + boneName + " not found");
+    return M_MAX_UNSIGNED;
 }
 }
 
 
 aiBone* getMeshBone(ExportModel& model, const std::string& boneName)
 aiBone* getMeshBone(ExportModel& model, const std::string& boneName)
@@ -580,7 +754,10 @@ void getBlendData(ExportModel& model, aiMesh* mesh, std::vector<unsigned>& boneM
         for (unsigned i = 0; i < mesh->mNumBones; ++i)
         for (unsigned i = 0; i < mesh->mNumBones; ++i)
         {
         {
             aiBone* bone = mesh->mBones[i];
             aiBone* bone = mesh->mBones[i];
-            unsigned globalIndex = getBoneIndex(model, toStdString(bone->mName));
+            std::string boneName = toStdString(bone->mName);
+            unsigned globalIndex = getBoneIndex(model, boneName);
+            if (globalIndex == M_MAX_UNSIGNED)
+                errorExit("Bone " + boneName + " not found");
             boneMappings[i] = globalIndex;
             boneMappings[i] = globalIndex;
             for (unsigned j = 0; j < bone->mNumWeights; ++j)
             for (unsigned j = 0; j < bone->mNumWeights; ++j)
             {
             {
@@ -597,7 +774,10 @@ void getBlendData(ExportModel& model, aiMesh* mesh, std::vector<unsigned>& boneM
         for (unsigned i = 0; i < mesh->mNumBones; ++i)
         for (unsigned i = 0; i < mesh->mNumBones; ++i)
         {
         {
             aiBone* bone = mesh->mBones[i];
             aiBone* bone = mesh->mBones[i];
-            unsigned globalIndex = getBoneIndex(model, toStdString(bone->mName));
+            std::string boneName = toStdString(bone->mName);
+            unsigned globalIndex = getBoneIndex(model, boneName);
+            if (globalIndex == M_MAX_UNSIGNED)
+                errorExit("Bone " + boneName + " not found");
             for (unsigned j = 0; j < bone->mNumWeights; ++j)
             for (unsigned j = 0; j < bone->mNumWeights; ++j)
             {
             {
                 unsigned vertex = bone->mWeights[j].mVertexId;
                 unsigned vertex = bone->mWeights[j].mVertexId;
@@ -740,7 +920,10 @@ aiNode* findNode(const std::string& name, aiNode* rootNode, bool caseSensitive)
 
 
 std::string toStdString(const aiString& str)
 std::string toStdString(const aiString& str)
 {
 {
-    return std::string(str.data);
+    if ((!str.data) || (!str.length))
+        return std::string();
+    else
+        return std::string(str.data);
 }
 }
 
 
 Vector3 toVector3(const aiVector3D& vec)
 Vector3 toVector3(const aiVector3D& vec)
@@ -770,6 +953,17 @@ aiMatrix4x4 getWorldTransform(aiNode* node, aiNode* rootNode)
     return current;
     return current;
 }
 }
 
 
+aiMatrix4x4 getWorldTransform(aiMatrix4x4 transform, aiNode* node, aiNode* rootNode)
+{
+    // If basenode is defined, go only up to it in the parent chain
+    while ((node->mParent) && (node != rootNode))
+    {
+        node = node->mParent;
+        transform = node->mTransformation * transform;
+    }
+    return transform;
+}
+
 void getPosRotScale(const aiMatrix4x4& transform, Vector3& pos, Quaternion& rot, Vector3& scale)
 void getPosRotScale(const aiMatrix4x4& transform, Vector3& pos, Quaternion& rot, Vector3& scale)
 {
 {
     aiVector3D aiPos;
     aiVector3D aiPos;