aiMatrix4x4 GetMeshBakingTransform(aiNode* meshNode, aiNode* modelRootNode) { if (meshNode == modelRootNode) return aiMatrix4x4(); else return GetDerivedTransform(meshNode, modelRootNode); }
// ------------------------------------------------------------------------------------------------ // Calculates the bone matrices for the given mesh. const std::vector<aiMatrix4x4>& SceneAnim::GetBoneMatrices( const aiNode* pNode, size_t pMeshIndex /* = 0 */) { ai_assert( pMeshIndex < pNode->mNumMeshes); size_t meshIndex = pNode->mMeshes[pMeshIndex]; ai_assert( meshIndex < mScene->mNumMeshes); const aiMesh* mesh = mScene->mMeshes[meshIndex]; // resize array and initialise it with identity matrices mTransforms.resize( mesh->mNumBones, aiMatrix4x4()); // calculate the mesh's inverse global transform aiMatrix4x4 globalInverseMeshTransform = GetGlobalTransform( pNode); globalInverseMeshTransform.Inverse(); // Bone matrices transform from mesh coordinates in bind pose to mesh coordinates in skinned pose // Therefore the formula is offsetMatrix * currentGlobalTransform * inverseCurrentMeshTransform for( size_t a = 0; a < mesh->mNumBones; ++a) { const aiBone* bone = mesh->mBones[a]; const aiMatrix4x4& currentGlobalTransform = GetGlobalTransform( mBoneNodesByName[ bone->mName.data ]); mTransforms[a] = globalInverseMeshTransform * currentGlobalTransform * bone->mOffsetMatrix; } // and return the result return mTransforms; }
Bone::Bone(aiMatrix4x4 transform, aiMatrix4x4 global, Bone* parent) : m_transform(transform), m_global(global), m_parent(parent) { m_numChildren=0; m_offset = aiMatrix4x4(); }
static inline aiMatrix4x4 ofMatrix4x4ToAiMatrix44(const ofMatrix4x4 &m) { const float *d = m.getPtr(); return aiMatrix4x4(d[0], d[4], d[8], d[12], d[1], d[5], d[9], d[13], d[2], d[6], d[10], d[14], d[3], d[7], d[11], d[15]); }
void CGLView::Camera_Set(const size_t pCameraNumber) { SHelper_Camera& hcam = mHelper_Camera;// reference with short name for conveniance. aiVector3D up; if(mCamera_DefaultAdded || (pCameraNumber >= mScene->mNumCameras))// If default camera used then 'pCameraNumber' doesn't matter. { // Transformation parameters hcam = mHelper_CameraDefault; up.Set(0, 1, 0); } else { const aiCamera& camera_cur = *mScene->mCameras[pCameraNumber]; const aiNode* camera_node; aiMatrix4x4 camera_mat; aiQuaternion camera_quat_rot; aiVector3D camera_tr; up = camera_cur.mUp; // // Try to get real coordinates of the camera. // // Find node camera_node = mScene->mRootNode->FindNode(camera_cur.mName); if(camera_node != nullptr) Matrix_NodeToRoot(camera_node, camera_mat); hcam.Position = camera_cur.mLookAt; hcam.Target = camera_cur.mPosition; hcam.Rotation_AroundCamera = aiMatrix4x4(camera_quat_rot.GetMatrix()); hcam.Rotation_AroundCamera.Transpose(); // get components of transformation matrix. camera_mat.DecomposeNoScaling(camera_quat_rot, camera_tr); hcam.Rotation_Scene = aiMatrix4x4(); hcam.Translation_ToScene = camera_tr; } // Load identity matrix - travel to world begin. glMatrixMode(GL_MODELVIEW); glLoadIdentity(); // Set camera and update picture gluLookAt(hcam.Position.x, hcam.Position.y, hcam.Position.z, hcam.Target.x, hcam.Target.y, hcam.Target.z, up.x, up.y, up.z); }
aiMatrix4x4 NCAssimpModel::returnRotation(aiMatrix4x4 incmat) { aiVector3D pos; aiQuaternion quat; aiVector3D scale; incmat.DecomposeNoScaling(quat, pos); aiMatrix4x4 toreturn = aiMatrix4x4(quat.GetMatrix()); return toreturn; }
void Mesh::ReadNodeHeirarchyCont(float AnimationTime, const aiNode* pNode, const glm::mat4& ParentTf, unsigned int anim_index) { std::string NodeName(pNode->mName.data); glm::mat4 NodeTf; CopyaiMat(pNode->mTransformation, NodeTf); // NOTE: the skeleton must use the same naming convention used by the .bvh files if(_b2i_map.find(NodeName) == _b2i_map.end()) { printf("Node: %15s\n", NodeName.c_str()); return; // bone does not exist } // const aiNodeAnim* pNodeAnim=FindNodeAnim(_pSceneMesh->mAnimations[anim_index],NodeName); const aiNodeAnim* pNodeAnim = _channels[anim_index][NodeName]; if(pNodeAnim) { // Interpolate scaling and generate scaling transformation matrix aiVector3D sc; CalcInterpolatedScaling(sc, AnimationTime, pNodeAnim); glm::mat4 ScMat=glm::scale(glm::mat4(1.0f), glm::vec3(sc.x, sc.y, sc.z)); // printf("Node name: %s: Sc: %f %f %f\n", NodeName.c_str(), sc.x, sc.y, sc.z); aiQuaternion quat; CalcInterpolatedRotation(quat, AnimationTime, pNodeAnim); glm::mat4 RotMat; CopyaiMat(aiMatrix4x4(quat.GetMatrix()), RotMat); aiVector3D tran; CalcInterpolatedPosition(tran, AnimationTime, pNodeAnim); glm::mat4 TranMat = glm::translate(glm::mat4(1.0f), glm::vec3(tran.x, tran.y, tran.z)); NodeTf = TranMat*RotMat*ScMat; } glm::mat4 GlobalTf = ParentTf * NodeTf; unsigned int BoneIdx = _b2i_map[NodeName]; _boneTfs[BoneIdx].FinalTf= _globalInvTf * GlobalTf * _boneTfs[BoneIdx].Offset; for(unsigned int i=0; i< pNode->mNumChildren; i++) { ReadNodeHeirarchyCont(AnimationTime, pNode->mChildren[i], GlobalTf, anim_index); } }
aiBone* makeBone(const char* name, aiVertexWeight weights[], unsigned int numWeights) { aiVertexWeight* heapedWeights = new aiVertexWeight[numWeights]; memcpy(heapedWeights, weights, numWeights * sizeof(aiVertexWeight)); aiBone* bone = new aiBone(); bone->mName = aiString(name); bone->mNumWeights = numWeights; bone->mOffsetMatrix = aiMatrix4x4(); bone->mWeights = heapedWeights; return bone; }
void Mesh::ReadNodeHeirarchyDisc(uint frame_index, const aiNode* pNode, const glm::mat4& ParentTf, uint anim_index) { std::string NodeName(pNode->mName.data); glm::mat4 NodeTf; CopyaiMat(pNode->mTransformation, NodeTf); if(_b2i_map.find(NodeName) == _b2i_map.end()) { printf("Node: %15s\n", NodeName.c_str()); return; // bone does not exist } const aiNodeAnim* pNodeAnim = _channels[anim_index][NodeName]; // assume all transform types share the same amount of keys frame_index = frame_index % pNodeAnim->mNumPositionKeys; const aiVector3D& sc = pNodeAnim->mScalingKeys[frame_index].mValue; const aiQuaternion& qt = pNodeAnim->mRotationKeys[frame_index].mValue; const aiVector3D& tr = pNodeAnim->mPositionKeys[frame_index].mValue; glm::mat4 ScMat=glm::scale(glm::mat4(1.0f), glm::vec3(sc.x, sc.y, sc.z)); glm::mat4 RotMat; CopyaiMat(aiMatrix4x4(qt.GetMatrix()), RotMat); glm::mat4 TranMat = glm::translate(glm::mat4(1.0f), glm::vec3(tr.x, tr.y, tr.z)); NodeTf = TranMat * RotMat * ScMat; glm::mat4 GlobalTf = ParentTf * NodeTf; unsigned int BoneIdx = _b2i_map[NodeName]; _boneTfs[BoneIdx].FinalTf= _globalInvTf * GlobalTf * _boneTfs[BoneIdx].Offset; // For debugging: print joint position in 3D: printf("%-15s %7.4f %7.4f %7.4f\n", pNode->mName.data, _boneTfs[BoneIdx].FinalTf[3][0], _boneTfs[BoneIdx].FinalTf[3][1], _boneTfs[BoneIdx].FinalTf[3][2]); // recursively update the children bones for(unsigned int i=0; i< pNode->mNumChildren; i++) ReadNodeHeirarchyDisc(frame_index, pNode->mChildren[i], GlobalTf, anim_index); }
void CGLView::Matrix_NodeToRoot(const aiNode* pNode, aiMatrix4x4& pOutMatrix) { const aiNode* node_cur; std::list<aiMatrix4x4> mat_list; pOutMatrix = aiMatrix4x4(); // starting walk from current element to root node_cur = pNode; if(node_cur != nullptr) { do { // if cur_node is group then store group transformation matrix in list. mat_list.push_back(node_cur->mTransformation); node_cur = node_cur->mParent; } while(node_cur != nullptr); } // multiplicate all matrices in reverse order for(std::list<aiMatrix4x4>::reverse_iterator rit = mat_list.rbegin(); rit != mat_list.rend(); rit++) pOutMatrix = pOutMatrix * (*rit); }
const aiScene* ShapeMesh::loadMesh(const string& fileName) { aiPropertyStore* propertyStore = aiCreatePropertyStore(); aiSetImportPropertyInteger(propertyStore, AI_CONFIG_PP_SBP_REMOVE, aiPrimitiveType_POINT | aiPrimitiveType_LINE ); // remove points and lines const aiScene* scene = aiImportFileExWithProperties(fileName.c_str(), aiProcess_GenNormals | aiProcess_Triangulate | aiProcess_JoinIdenticalVertices | aiProcess_SortByPType | aiProcess_OptimizeMeshes, NULL, propertyStore); aiReleasePropertyStore(propertyStore); // Assimp rotates collada files such that the up-axis (specified in the collada file) aligns with assimp's y-axis. // Here we are reverting this rotation. // We are only catching files with the .dae file ending here. We might miss files with an .xml file ending, // which would need to be looked into to figure out whether they are collada files. if(fileName.length() >= 4 && fileName.substr(fileName.length() - 4, 4) == ".dae") { scene->mRootNode->mTransformation = aiMatrix4x4(); } return scene; }
Mesh* createMeshFromAsset(const aiScene* scene, const Eigen::Vector3d &scale, const std::string &resource_name) { if (!scene->HasMeshes()) { logWarn("Assimp reports scene in %s has no meshes", resource_name.c_str()); return NULL; } EigenSTL::vector_Vector3d vertices; std::vector<unsigned int> triangles; extractMeshData(scene, scene->mRootNode, aiMatrix4x4(), scale, vertices, triangles); if (vertices.empty()) { logWarn("There are no vertices in the scene %s", resource_name.c_str()); return NULL; } if (triangles.empty()) { logWarn("There are no triangles in the scene %s", resource_name.c_str()); return NULL; } return createMeshFromVertices(vertices, triangles); }
void copyAiAnimations(const aiScene *scene, const aiMesh *sourceMesh, Skeleton &skeleton) { //Create map from bone names to their indices. std::map<std::string, unsigned int> boneNameToIndex; for (unsigned int i = 0; i < skeleton.bones.size(); ++i) { if (!boneNameToIndex.insert(std::make_pair(skeleton.bones[i].name, i)).second) { std::cerr << "Bone name '" << skeleton.bones[i].name << "' is not unique!" << std::endl; throw std::exception(); } } assert(boneNameToIndex.size() == skeleton.bones.size()); //Create map from node names to their pointers. std::map<std::string, const aiNode *> nodeNameToPointer; detail::setAiNodePointers(scene->mRootNode, nodeNameToPointer); //We need to keep track of all transformation matrices. std::map<std::string, aiMatrix4x4> nodeNameToMatrix; for (std::map<std::string, const aiNode *>::const_iterator i = nodeNameToPointer.begin(); i != nodeNameToPointer.end(); ++i) { nodeNameToMatrix.insert(std::make_pair(i->first, aiMatrix4x4())); } //Process all animations. skeleton.animations.clear(); for (unsigned int i = 0; i < scene->mNumAnimations; ++i) { copyAiAnimation(scene, sourceMesh, i, boneNameToIndex, nodeNameToPointer, nodeNameToMatrix, skeleton); } }
aiMatrix4x4 NCAssimpModel::toAi(ofMatrix4x4 & m){ return aiMatrix4x4(m(0,0),m(1,0),m(2,0),m(3,0), m(0,1),m(1,1),m(2,1),m(3,1), m(0,2),m(1,2),m(2,2),m(3,2), m(0,3),m(1,3),m(2,3),m(3,3)); }
// ------------------------------------------------------------------------------------------------ // Evaluates the animation tracks for a given time stamp. void cAnimEvaluator::Evaluate( float pTime, std::map<std::string, cBone*>& bones) { pTime *= TicksPerSecond; float time = 0.0f; if( Duration > 0.0) time = fmod( pTime, Duration); // calculate the transformations for each animation channel for( unsigned int a = 0; a < Channels.size(); a++){ const cAnimationChannel* channel = &Channels[a]; std::map<std::string, cBone*>::iterator bonenode = bones.find(channel->Name); if(bonenode == bones.end()) { OUTPUT_DEBUG_MSG("did not find the bone node "<<channel->Name); continue; } // ******** Position ***** aiVector3D presentPosition( 0, 0, 0); if( channel->mPositionKeys.size() > 0){ // Look for present frame number. Search from last position if time is after the last time, else from beginning // Should be much quicker than always looking from start for the average use case. unsigned int frame = (time >= mLastTime) ? std::get<0>(mLastPositions[a]): 0; while( frame < channel->mPositionKeys.size() - 1){ if( time < channel->mPositionKeys[frame+1].mTime){ break; } frame++; } // interpolate between this frame's value and next frame's value unsigned int nextFrame = (frame + 1) % channel->mPositionKeys.size(); const aiVectorKey& key = channel->mPositionKeys[frame]; const aiVectorKey& nextKey = channel->mPositionKeys[nextFrame]; double diffTime = nextKey.mTime - key.mTime; if( diffTime < 0.0) diffTime += Duration; if( diffTime > 0) { float factor = float( (time - key.mTime) / diffTime); presentPosition = key.mValue + (nextKey.mValue - key.mValue) * factor; } else { presentPosition = key.mValue; } std::get<0>(mLastPositions[a]) = frame; } // ******** Rotation ********* aiQuaternion presentRotation( 1, 0, 0, 0); if( channel->mRotationKeys.size() > 0) { unsigned int frame = (time >= mLastTime) ? std::get<1>(mLastPositions[a]) : 0; while( frame < channel->mRotationKeys.size() - 1){ if( time < channel->mRotationKeys[frame+1].mTime) break; frame++; } // interpolate between this frame's value and next frame's value unsigned int nextFrame = (frame + 1) % channel->mRotationKeys.size() ; const aiQuatKey& key = channel->mRotationKeys[frame]; const aiQuatKey& nextKey = channel->mRotationKeys[nextFrame]; double diffTime = nextKey.mTime - key.mTime; if( diffTime < 0.0) diffTime += Duration; if( diffTime > 0) { float factor = float( (time - key.mTime) / diffTime); aiQuaternion::Interpolate( presentRotation, key.mValue, nextKey.mValue, factor); } else presentRotation = key.mValue; std::get<1>(mLastPositions[a]) = frame; } // ******** Scaling ********** aiVector3D presentScaling( 1, 1, 1); if( channel->mScalingKeys.size() > 0) { unsigned int frame = (time >= mLastTime) ? std::get<2>(mLastPositions[a]) : 0; while( frame < channel->mScalingKeys.size() - 1){ if( time < channel->mScalingKeys[frame+1].mTime) break; frame++; } presentScaling = channel->mScalingKeys[frame].mValue; std::get<2>(mLastPositions[a]) = frame; } aiMatrix4x4 mat = aiMatrix4x4( presentRotation.GetMatrix()); mat.a1 *= presentScaling.x; mat.b1 *= presentScaling.x; mat.c1 *= presentScaling.x; mat.a2 *= presentScaling.y; mat.b2 *= presentScaling.y; mat.c2 *= presentScaling.y; mat.a3 *= presentScaling.z; mat.b3 *= presentScaling.z; mat.c3 *= presentScaling.z; mat.a4 = presentPosition.x; mat.b4 = presentPosition.y; mat.c4 = presentPosition.z; mat.Transpose(); TransformMatrix(bonenode->second->LocalTransform, mat); } mLastTime = time; }
void Model::draw(unsigned int animation, double timeIn) { if (scene->mNumAnimations <= animation) return; aiAnimation* currentAnimation = scene->mAnimations[animation]; //Step 1: Interpolate position, rotation, scale in the all channels and add to transformation for (unsigned int i = 0; i < currentAnimation->mNumChannels; i++) { aiNodeAnim* currentChannel = currentAnimation->mChannels[i]; aiVector3D currentPosition = interpolatePosition(currentChannel, timeIn); aiQuaternion currentRotation = interpolateRotation(currentChannel, timeIn); aiVector3D currentScale = interpolateScale(currentChannel, timeIn); aiMatrix4x4& transformation = aiMatrix4x4(currentScale, currentRotation, currentPosition); aiNode* currentNode = scene->mRootNode->FindNode(currentChannel->mNodeName); currentNode->mTransformation = transformation; } for (unsigned int k = 0; k < scene->mNumMeshes; k++) { //Step 2: Bone transformation, change transformation of the bone according to animation aiMesh* currentMesh = scene->mMeshes[k]; std::vector<aiMatrix4x4> boneTransformations = std::vector<aiMatrix4x4>(currentMesh->mNumBones); for (unsigned int i = 0; i < currentMesh->mNumBones; i++) { aiBone* currentBone = currentMesh->mBones[i]; aiNode* currentNode = scene->mRootNode->FindNode(currentBone->mName); boneTransformations[i] = parentMultiplication(currentNode); boneTransformations[i] *= currentMesh->mBones[i]->mOffsetMatrix; } //Step 3: Skinning std::vector<aiVector3D> resultPosition(currentMesh->mNumVertices); for (size_t k = 0; k < currentMesh->mNumBones; k++) { const aiBone* currentBone = currentMesh->mBones[k]; const aiMatrix4x4& positionMatrix = boneTransformations[k]; for (size_t j = 0; j < currentBone->mNumWeights; j++) { const aiVertexWeight& weight = currentBone->mWeights[j]; size_t vertexId = weight.mVertexId; const aiVector3D& srcPosition = currentMesh->mVertices[vertexId]; resultPosition[vertexId] += weight.mWeight * (positionMatrix * srcPosition); } } //Step 4: Draw the final model // For every face size_t cv = 0, ctc = 0; glColor3d(1.0, 1.0, 1.0); // Set the face color to white for (int cf = 0; cf < currentMesh->mNumFaces; cf++) { const aiFace& face = currentMesh->mFaces[cf]; // For all vertices in face (Final drawing) if (wireframe) glBegin(GL_LINE_LOOP); else glBegin(GL_TRIANGLES); for (int cfi = 0; cfi < 3; cfi++) { double x = resultPosition[face.mIndices[cfi]].x; double y = resultPosition[face.mIndices[cfi]].y; double z = resultPosition[face.mIndices[cfi]].z; glVertex3d(x, y, z); } glEnd(); } } }
void copyAiAnimation(const aiScene *scene, const aiMesh *sourceMesh, const unsigned int &animationIndex, std::map<std::string, unsigned int> &boneNameToIndex, std::map<std::string, const aiNode *> &nodeNameToPointer, std::map<std::string, aiMatrix4x4> &nodeNameToMatrix, Skeleton &skeleton) { const aiAnimation *sourceAnimation = scene->mAnimations[animationIndex]; const std::string animationName = std::string(sourceAnimation->mName.data); skeleton.animations.insert(std::pair<std::string, Animation>(animationName, Animation())); Animation *animation = &skeleton.animations[animationName]; const size_t nrFrames = getAiNrAnimationFrames(sourceAnimation); animation->name = animationName; animation->frames.assign(nrFrames*skeleton.bones.size(), KeyFrame()); if (sourceAnimation->mNumChannels != skeleton.bones.size()) { std::cerr << "Warning: animation '" << animation->name << "' has an invalid number of channels (" << sourceAnimation->mNumChannels << " for " << skeleton.bones.size() << " bones)!" << std::endl; } //Store inverse of global transformation. aiMatrix4x4 inverseGlobalTransformation = scene->mRootNode->mTransformation; inverseGlobalTransformation.Inverse(); //Process all frames. KeyFrame *frames = &animation->frames[0]; for (size_t frame = 0; frame < nrFrames; ++frame) { //For this frame, first reset all transformations to their originals. for (std::map<std::string, aiMatrix4x4>::iterator i = nodeNameToMatrix.begin(); i != nodeNameToMatrix.end(); ++i) { assert(nodeNameToPointer[i->first]); i->second = nodeNameToPointer[i->first]->mTransformation; } //Then, retrieve all transformations that are stored in the animation data for the corresponding nodes. for (size_t i = 0; i < sourceAnimation->mNumChannels; ++i) { const aiNodeAnim *nodeAnim = sourceAnimation->mChannels[i]; //Get data for this frame. aiVector3D scale(1.0f, 1.0f, 1.0f); aiQuaternion rotate(1.0f, 0.0f, 0.0f, 0.0f); aiVector3D translate(0.0f, 0.0f, 0.0f); if (frame < nodeAnim->mNumScalingKeys) scale = nodeAnim->mScalingKeys[frame].mValue; else if (nodeAnim->mNumScalingKeys > 0) scale = nodeAnim->mScalingKeys[nodeAnim->mNumScalingKeys - 1].mValue; if (frame < nodeAnim->mNumRotationKeys) rotate = nodeAnim->mRotationKeys[frame].mValue; else if (nodeAnim->mNumRotationKeys > 0) rotate = nodeAnim->mRotationKeys[nodeAnim->mNumRotationKeys - 1].mValue; if (frame < nodeAnim->mNumPositionKeys) translate = nodeAnim->mPositionKeys[frame].mValue; else if (nodeAnim->mNumPositionKeys > 0) translate = nodeAnim->mPositionKeys[nodeAnim->mNumPositionKeys - 1].mValue; //Create transformation matrix. if (nodeNameToMatrix.find(nodeAnim->mNodeName.data) == nodeNameToMatrix.end()) { std::cerr << "Warning: animation data for node '" << nodeAnim->mNodeName.data << "' is not available!" << std::endl; throw std::exception(); } aiMatrix4x4 scaleMatrix; aiMatrix4x4 rotationMatrix = aiMatrix4x4(rotate.GetMatrix()); aiMatrix4x4 translationMatrix; aiMatrix4x4::Scaling(scale, scaleMatrix); aiMatrix4x4::Translation(translate, translationMatrix); nodeNameToMatrix[nodeAnim->mNodeName.data] = translationMatrix*rotationMatrix*scaleMatrix; } //Recursively update these transformations. updateAiNodeMatrices(scene->mRootNode, aiMatrix4x4(), nodeNameToMatrix); //Assign the updated transformations to the corresponding bones. for (std::map<std::string, aiMatrix4x4>::const_iterator i = nodeNameToMatrix.begin(); i != nodeNameToMatrix.end(); ++i) { std::map<std::string, unsigned int>::const_iterator boneIterator = boneNameToIndex.find(i->first); if (boneIterator != boneNameToIndex.end()) { const unsigned int boneIndex = boneIterator->second; //const aiMatrix4x4 finalTransformation = inverseGlobalTransformation*i->second*sourceMesh->mBones[boneIndex]->mOffsetMatrix; const aiMatrix4x4 finalTransformation = i->second*sourceMesh->mBones[boneIndex]->mOffsetMatrix; aiVector3D scale(1.0f, 1.0f, 1.0f); aiQuaternion rotate(1.0f, 0.0f, 0.0f, 0.0f); aiVector3D translate(0.0f, 0.0f, 0.0f); finalTransformation.Decompose(scale, rotate, translate); frames[boneIndex] = KeyFrame(vec3(scale.x, scale.y, scale.z), frame, quatconj(vec4(rotate.x, rotate.y, rotate.z, rotate.w)), vec4(translate.x, translate.y, translate.z, 0.0f)); } } //Advance to next frame. frames += skeleton.bones.size(); } #ifndef NDEBUG std::cerr << "Added animation '" << animation->name << "' with " << nrFrames << " frames, resulting in " << animation->frames.size() << " keyframes." << std::endl; #endif }
// calculates the node transformations for the scene // returns false of the animation is completed or if there is no animation bool Animator::UpdateAnimation(float time, bool loop) { if (currentAnimation && currentAnimation->mDuration > 0.0 && CurAnimation != NULL) { // map into animation's duration double timeInTicks = 0.0f; float dur = (CurAnimation->endFrame - CurAnimation->startFrame)/FPS; if(time == 0) { timeInTicks = 0; } else if (dur > 0.0) { timeInTicks = (CurAnimation->startFrame/FPS) + fmod(time-(1/FPS), dur); } else { timeInTicks = CurAnimation->startFrame/FPS; } if (boneTransforms.size() != currentAnimation->mNumChannels) { boneTransforms.resize(currentAnimation->mNumChannels); } //calculate the transformations for each animation channel for (unsigned int i = 0; i < currentAnimation->mNumChannels; i++) { const aiNodeAnim* channel = currentAnimation->mChannels[i]; //******** Position ***** aiVector3D currentPosition(0, 0, 0); if (channel->mNumPositionKeys > 0) { // Look for present frame number. Search from last position if time is after the last time, else from beginning unsigned int frame = (timeInTicks >= lastTime) ? lastFramePosition[i].x : 0; while (frame < channel->mNumPositionKeys - 1) { if (timeInTicks < channel->mPositionKeys[frame + 1].mTime) { break; } frame++; } // interpolate between this frame's value and next frame's value unsigned int nextFrame = (frame + 1) % channel->mNumPositionKeys; const aiVectorKey& key = channel->mPositionKeys[frame]; const aiVectorKey& nextKey = channel->mPositionKeys[nextFrame]; double dt = nextKey.mTime - key.mTime; if (dt < 0.0) { dt += dur; } if (dt > 0) { float interpolationFactor = (float) ((timeInTicks - key.mTime) / dt); currentPosition = key.mValue + (nextKey.mValue - key.mValue) * interpolationFactor; } else { currentPosition = key.mValue; } lastFramePosition[i].x = frame; } //******** Rotation ********* aiQuaternion currentRotation(1, 0, 0, 0); if (channel->mNumRotationKeys > 0) { unsigned int frame = (timeInTicks >= lastTime) ? lastFramePosition[i].y : 0; while (frame < channel->mNumRotationKeys - 1) { if (timeInTicks < channel->mRotationKeys[frame + 1].mTime) { break; } frame++; } // interpolate between this frame's value and next frame's value unsigned int nextFrame = (frame + 1) % channel->mNumRotationKeys; const aiQuatKey& key = channel->mRotationKeys[frame]; const aiQuatKey& nextKey = channel->mRotationKeys[nextFrame]; double dt = nextKey.mTime - key.mTime; if (dt < 0.0) { dt += dur; } if (dt > 0) { float interpolationFactor = (float) ((timeInTicks - key.mTime) / dt); aiQuaternion::Interpolate(currentRotation, key.mValue, nextKey.mValue, interpolationFactor); } else { currentRotation = key.mValue; } lastFramePosition[i].y = frame; } //******** Scaling ********** aiVector3D currentScaling(1, 1, 1); if (channel->mNumScalingKeys > 0) { unsigned int frame = (timeInTicks >= lastTime) ? lastFramePosition[i].z : 0; while (frame < channel->mNumScalingKeys - 1) { if (timeInTicks < channel->mScalingKeys[frame + 1].mTime) { break; } frame++; } currentScaling = channel->mScalingKeys[frame].mValue; lastFramePosition[i].z = frame; } // build a transformation matrix from the current position, rotation, and scale aiMatrix4x4& transformation = boneTransforms[i]; transformation = aiMatrix4x4(currentRotation.GetMatrix()); transformation.a1 *= currentScaling.x; transformation.b1 *= currentScaling.x; transformation.c1 *= currentScaling.x; transformation.a2 *= currentScaling.y; transformation.b2 *= currentScaling.y; transformation.c2 *= currentScaling.y; transformation.a3 *= currentScaling.z; transformation.b3 *= currentScaling.z; transformation.c3 *= currentScaling.z; transformation.a4 = currentPosition.x; transformation.b4 = currentPosition.y; transformation.c4 = currentPosition.z; } lastTime = timeInTicks; // update all node transformations with the results UpdateTransforms(rootNode, boneTransforms); if(time <= dur || loop) { if(CurAnimation != NULL) CurAnimation->isPlaying = true; return true; } } if(CurAnimation != NULL) CurAnimation->isPlaying = false; return false; }
// ----------------------------------------------------------------------------------- void FindSpecialPoints(const aiScene* scene,const aiNode* root,aiVector3D special_points[3],const aiMatrix4x4& mat=aiMatrix4x4()) { // XXX that could be greatly simplified by using code from code/ProcessHelper.h // XXX I just don't want to include it here. const aiMatrix4x4 trafo = root->mTransformation*mat; for(unsigned int i = 0; i < root->mNumMeshes; ++i) { const aiMesh* mesh = scene->mMeshes[root->mMeshes[i]]; for(unsigned int a = 0; a < mesh->mNumVertices; ++a) { aiVector3D v = trafo*mesh->mVertices[a]; special_points[0].x = std::min(special_points[0].x,v.x); special_points[0].y = std::min(special_points[0].y,v.y); special_points[0].z = std::min(special_points[0].z,v.z); special_points[1].x = std::max(special_points[1].x,v.x); special_points[1].y = std::max(special_points[1].y,v.y); special_points[1].z = std::max(special_points[1].z,v.z); } } for(unsigned int i = 0; i < root->mNumChildren; ++i) { FindSpecialPoints(scene,root->mChildren[i],special_points,trafo); } }
/** Get a string configuration property * * The return value remains valid until the property is modified. * @see GetPropertyInteger() */ const std::string GetPropertyString(const char* szName, const std::string& sErrorReturn = "") const; // ------------------------------------------------------------------- /** Get a matrix configuration property * * The return value remains valid until the property is modified. * @see GetPropertyInteger() */ const aiMatrix4x4 GetPropertyMatrix(const char* szName, const aiMatrix4x4& sErrorReturn = aiMatrix4x4()) const; // ------------------------------------------------------------------- /** Supplies a custom IO handler to the importer to use to open and * access files. If you need the importer to use custom IO logic to * access the files, you need to provide a custom implementation of * IOSystem and IOFile to the importer. Then create an instance of * your custom IOSystem implementation and supply it by this function. * * The Importer takes ownership of the object and will destroy it * afterwards. The previously assigned handler will be deleted. * Pass NULL to take again ownership of your IOSystem and reset Assimp * to use its default implementation. * * @param pIOHandler The IO handler to be used in all file accesses * of the Importer.
void ofxAssimpAnimation::updateAnimationNodes() { for(unsigned int i=0; i<animation->mNumChannels; i++) { const aiNodeAnim * channel = animation->mChannels[i]; aiNode * targetNode = scene->mRootNode->FindNode(channel->mNodeName); aiVector3D presentPosition(0, 0, 0); if(channel->mNumPositionKeys > 0) { unsigned int frame = 0; while(frame < channel->mNumPositionKeys - 1) { if(progressInSeconds < channel->mPositionKeys[frame+1].mTime) { break; } frame++; } unsigned int nextFrame = (frame + 1) % channel->mNumPositionKeys; const aiVectorKey & key = channel->mPositionKeys[frame]; const aiVectorKey & nextKey = channel->mPositionKeys[nextFrame]; double diffTime = nextKey.mTime - key.mTime; if(diffTime < 0.0) { diffTime += getDurationInSeconds(); } if(diffTime > 0) { float factor = float((progressInSeconds - key.mTime) / diffTime); presentPosition = key.mValue + (nextKey.mValue - key.mValue) * factor; } else { presentPosition = key.mValue; } } aiQuaternion presentRotation(1, 0, 0, 0); if(channel->mNumRotationKeys > 0) { unsigned int frame = 0; while(frame < channel->mNumRotationKeys - 1) { if(progressInSeconds < channel->mRotationKeys[frame+1].mTime) { break; } frame++; } unsigned int nextFrame = (frame + 1) % channel->mNumRotationKeys; const aiQuatKey& key = channel->mRotationKeys[frame]; const aiQuatKey& nextKey = channel->mRotationKeys[nextFrame]; double diffTime = nextKey.mTime - key.mTime; if(diffTime < 0.0) { diffTime += getDurationInSeconds(); } if(diffTime > 0) { float factor = float((progressInSeconds - key.mTime) / diffTime); aiQuaternion::Interpolate(presentRotation, key.mValue, nextKey.mValue, factor); } else { presentRotation = key.mValue; } } aiVector3D presentScaling(1, 1, 1); if(channel->mNumScalingKeys > 0) { unsigned int frame = 0; while(frame < channel->mNumScalingKeys - 1){ if(progressInSeconds < channel->mScalingKeys[frame+1].mTime) { break; } frame++; } presentScaling = channel->mScalingKeys[frame].mValue; } aiMatrix4x4 mat = aiMatrix4x4(presentRotation.GetMatrix()); mat.a1 *= presentScaling.x; mat.b1 *= presentScaling.x; mat.c1 *= presentScaling.x; mat.a2 *= presentScaling.y; mat.b2 *= presentScaling.y; mat.c2 *= presentScaling.y; mat.a3 *= presentScaling.z; mat.b3 *= presentScaling.z; mat.c3 *= presentScaling.z; mat.a4 = presentPosition.x; mat.b4 = presentPosition.y; mat.c4 = presentPosition.z; targetNode->mTransformation = mat; } }
aiMatrix4x4 floatsToMatrix( float* data ) { return aiMatrix4x4( data[0], data[1], data[2], data[3], data[4], data[5], data[6], data[7], data[8], data[9], data[10], data[11], data[12], data[13], data[14], data[15] ); }
void AnimEvaluator::Evaluate( float pTime, std::map<std::string, SkinData::Bone*>& bones ) { pTime *= mTicksPerSecond; float time = 0.0f; if (mDuration > 0.0) time = fmod(pTime, mDuration); // Calculate transformations for each channel for (UINT i = 0; i < Channels.size(); ++i) { const SkinData::AnimationChannel* channel = &Channels[i]; std::map<std::string, SkinData::Bone*>::iterator boneNode = bones.find(channel->Name); // Did not find bone node if (boneNode == bones.end()) continue; //--------------------------------------------- // Position //--------------------------------------------- aiVector3D presentPosition(0, 0, 0); if (channel->PositionKeys.size() > 0) { // Look for present frame number UINT frame = (time >= mLastTime) ? std::get<0>(mLastPositions[i]) : 0; while (frame < channel->PositionKeys.size() - 1) { if (time < channel->PositionKeys[frame+1].mTime) break; frame++; } // Interpolate between this frame's value and the next frame's value UINT nextFrame = (frame + 1) % channel->PositionKeys.size(); const aiVectorKey& key = channel->PositionKeys[frame]; const aiVectorKey& nextKey = channel->PositionKeys[nextFrame]; double diffTime = nextKey.mTime - key.mTime; if (diffTime < 0.0) diffTime += mDuration; if (diffTime > 0) { float factor = float((time - key.mTime) / diffTime); presentPosition = key.mValue + (nextKey.mValue - key.mValue) * factor; } else { presentPosition = key.mValue; } std::get<0>(mLastPositions[i]) = frame; } //--------------------------------------------- // Rotation //--------------------------------------------- aiQuaternion presentRotation(1, 0, 0, 0); if (channel->RotationKeys.size() > 0) { UINT frame = (time >= mLastTime) ? std::get<1>(mLastPositions[i]) : 0; while (frame < channel->RotationKeys.size() - 1) { if (time < channel->RotationKeys[frame+1].mTime) break; frame++; } UINT nextFrame = (frame + 1) % channel->RotationKeys.size(); const aiQuatKey& key = channel->RotationKeys[frame]; const aiQuatKey& nextKey = channel->RotationKeys[nextFrame]; double diffTime = nextKey.mTime - key.mTime; if (diffTime < 0.0) diffTime += mDuration; if (diffTime > 0) { float factor = float((time - key.mTime) / diffTime); aiQuaternion::Interpolate(presentRotation, key.mValue, nextKey.mValue, factor); } else { presentRotation = key.mValue; } std::get<1>(mLastPositions[i]) = frame; } //--------------------------------------------- // Scaling //--------------------------------------------- aiVector3D presentScaling(1, 1, 1); if (channel->ScalingKeys.size() > 0) { UINT frame = (time >= mLastTime) ? std::get<2>(mLastPositions[i]) : 0; while (frame < channel->ScalingKeys.size() - 1) { if (time < channel->ScalingKeys[frame+1].mTime) break; frame++; } presentScaling = channel->ScalingKeys[frame].mValue; std::get<2>(mLastPositions[i]) = frame; } aiMatrix4x4 mat = aiMatrix4x4(presentRotation.GetMatrix()); mat.a1 *= presentScaling.x; mat.b1 *= presentScaling.x; mat.c1 *= presentScaling.x; mat.a2 *= presentScaling.y; mat.b2 *= presentScaling.y; mat.c2 *= presentScaling.y; mat.a3 *= presentScaling.z; mat.b3 *= presentScaling.z; mat.c3 *= presentScaling.z; mat.a4 = presentPosition.x; mat.b4 = presentPosition.y; mat.c4 = presentPosition.z; mat.Transpose(); SkinData::ReadAiMatrix(boneNode->second->LocalTransform, mat); } // Channel end mLastTime = time; }
//------------------------------------------- void ofxAssimpModelLoader::updateAnimation(unsigned int animationIndex, float currentTime){ const aiAnimation* mAnim = scene->mAnimations[animationIndex]; // calculate the transformations for each animation channel for( unsigned int a = 0; a < mAnim->mNumChannels; a++) { const aiNodeAnim* channel = mAnim->mChannels[a]; aiNode* targetNode = scene->mRootNode->FindNode(channel->mNodeName); // ******** Position ***** aiVector3D presentPosition( 0, 0, 0); if( channel->mNumPositionKeys > 0) { // Look for present frame number. Search from last position if time is after the last time, else from beginning // Should be much quicker than always looking from start for the average use case. unsigned int frame = 0;// (currentTime >= lastAnimationTime) ? lastFramePositionIndex : 0; while( frame < channel->mNumPositionKeys - 1) { if( currentTime < channel->mPositionKeys[frame+1].mTime) break; frame++; } // interpolate between this frame's value and next frame's value unsigned int nextFrame = (frame + 1) % channel->mNumPositionKeys; const aiVectorKey& key = channel->mPositionKeys[frame]; const aiVectorKey& nextKey = channel->mPositionKeys[nextFrame]; double diffTime = nextKey.mTime - key.mTime; if( diffTime < 0.0) diffTime += mAnim->mDuration; if( diffTime > 0) { float factor = float( (currentTime - key.mTime) / diffTime); presentPosition = key.mValue + (nextKey.mValue - key.mValue) * factor; } else { presentPosition = key.mValue; } } // ******** Rotation ********* aiQuaternion presentRotation( 1, 0, 0, 0); if( channel->mNumRotationKeys > 0) { unsigned int frame = 0;//(currentTime >= lastAnimationTime) ? lastFrameRotationIndex : 0; while( frame < channel->mNumRotationKeys - 1) { if( currentTime < channel->mRotationKeys[frame+1].mTime) break; frame++; } // interpolate between this frame's value and next frame's value unsigned int nextFrame = (frame + 1) % channel->mNumRotationKeys; const aiQuatKey& key = channel->mRotationKeys[frame]; const aiQuatKey& nextKey = channel->mRotationKeys[nextFrame]; double diffTime = nextKey.mTime - key.mTime; if( diffTime < 0.0) diffTime += mAnim->mDuration; if( diffTime > 0) { float factor = float( (currentTime - key.mTime) / diffTime); aiQuaternion::Interpolate( presentRotation, key.mValue, nextKey.mValue, factor); } else { presentRotation = key.mValue; } } // ******** Scaling ********** aiVector3D presentScaling( 1, 1, 1); if( channel->mNumScalingKeys > 0) { unsigned int frame = 0;//(currentTime >= lastAnimationTime) ? lastFrameScaleIndex : 0; while( frame < channel->mNumScalingKeys - 1) { if( currentTime < channel->mScalingKeys[frame+1].mTime) break; frame++; } // TODO: (thom) interpolation maybe? This time maybe even logarithmic, not linear presentScaling = channel->mScalingKeys[frame].mValue; } // build a transformation matrix from it //aiMatrix4x4& mat;// = mTransforms[a]; aiMatrix4x4 mat = aiMatrix4x4( presentRotation.GetMatrix()); mat.a1 *= presentScaling.x; mat.b1 *= presentScaling.x; mat.c1 *= presentScaling.x; mat.a2 *= presentScaling.y; mat.b2 *= presentScaling.y; mat.c2 *= presentScaling.y; mat.a3 *= presentScaling.z; mat.b3 *= presentScaling.z; mat.c3 *= presentScaling.z; mat.a4 = presentPosition.x; mat.b4 = presentPosition.y; mat.c4 = presentPosition.z; //mat.Transpose(); targetNode->mTransformation = mat; } lastAnimationTime = currentTime; // update mesh position for the animation for (unsigned int i = 0; i < modelMeshes.size(); ++i){ // current mesh we are introspecting const aiMesh* mesh = modelMeshes[i].mesh; // calculate bone matrices std::vector<aiMatrix4x4> boneMatrices( mesh->mNumBones); for( size_t a = 0; a < mesh->mNumBones; ++a) { const aiBone* bone = mesh->mBones[a]; // find the corresponding node by again looking recursively through the node hierarchy for the same name aiNode* node = scene->mRootNode->FindNode(bone->mName); // start with the mesh-to-bone matrix boneMatrices[a] = bone->mOffsetMatrix; // and now append all node transformations down the parent chain until we're back at mesh coordinates again const aiNode* tempNode = node; while( tempNode) { // check your matrix multiplication order here!!! boneMatrices[a] = tempNode->mTransformation * boneMatrices[a]; // boneMatrices[a] = boneMatrices[a] * tempNode->mTransformation; tempNode = tempNode->mParent; } modelMeshes[i].hasChanged = true; modelMeshes[i].validCache = false; } modelMeshes[i].animatedPos.assign(modelMeshes[i].animatedPos.size(),0); if(mesh->HasNormals()){ modelMeshes[i].animatedNorm.assign(modelMeshes[i].animatedNorm.size(),0); } // loop through all vertex weights of all bones for( size_t a = 0; a < mesh->mNumBones; ++a) { const aiBone* bone = mesh->mBones[a]; const aiMatrix4x4& posTrafo = boneMatrices[a]; for( size_t b = 0; b < bone->mNumWeights; ++b) { const aiVertexWeight& weight = bone->mWeights[b]; size_t vertexId = weight.mVertexId; const aiVector3D& srcPos = mesh->mVertices[vertexId]; modelMeshes[i].animatedPos[vertexId] += weight.mWeight * (posTrafo * srcPos); } if(mesh->HasNormals()){ // 3x3 matrix, contains the bone matrix without the translation, only with rotation and possibly scaling aiMatrix3x3 normTrafo = aiMatrix3x3( posTrafo); for( size_t b = 0; b < bone->mNumWeights; ++b) { const aiVertexWeight& weight = bone->mWeights[b]; size_t vertexId = weight.mVertexId; const aiVector3D& srcNorm = mesh->mNormals[vertexId]; modelMeshes[i].animatedNorm[vertexId] += weight.mWeight * (normTrafo * srcNorm); } } } } }
const aiScene* MeshShape::loadMesh( const std::string& _uri, const common::ResourceRetrieverPtr& _retriever) { // Remove points and lines from the import. aiPropertyStore* propertyStore = aiCreatePropertyStore(); aiSetImportPropertyInteger(propertyStore, AI_CONFIG_PP_SBP_REMOVE, aiPrimitiveType_POINT | aiPrimitiveType_LINE ); // Wrap ResourceRetriever in an IOSystem from Assimp's C++ API. Then wrap // the IOSystem in an aiFileIO from Assimp's C API. Yes, this API is // completely ridiculous... AssimpInputResourceRetrieverAdaptor systemIO(_retriever); aiFileIO fileIO = createFileIO(&systemIO); // Import the file. const aiScene* scene = aiImportFileExWithProperties( _uri.c_str(), aiProcess_GenNormals | aiProcess_Triangulate | aiProcess_JoinIdenticalVertices | aiProcess_SortByPType | aiProcess_OptimizeMeshes, &fileIO, propertyStore ); // If succeeded, store the importer in the scene to keep it alive. This is // necessary because the importer owns the memory that it allocates. if(!scene) { dtwarn << "[MeshShape::loadMesh] Failed loading mesh '" << _uri << "'.\n"; return nullptr; } // Assimp rotates collada files such that the up-axis (specified in the // collada file) aligns with assimp's y-axis. Here we are reverting this // rotation. We are only catching files with the .dae file ending here. We // might miss files with an .xml file ending, which would need to be looked // into to figure out whether they are collada files. std::string extension; const size_t extensionIndex = _uri.find_last_of('.'); if(extensionIndex != std::string::npos) extension = _uri.substr(extensionIndex); std::transform(std::begin(extension), std::end(extension), std::begin(extension), ::tolower); if(extension == ".dae" || extension == ".zae") scene->mRootNode->mTransformation = aiMatrix4x4(); // Finally, pre-transform the vertices. We can't do this as part of the // import process, because we may have changed mTransformation above. scene = aiApplyPostProcessing(scene, aiProcess_PreTransformVertices); if(!scene) dtwarn << "[MeshShape::loadMesh] Failed pre-transforming vertices.\n"; return scene; }
bool Model::loadFromFile(const std::string& filename) { Assimp::Importer importer; std::string strModelsResourcesFolder = "Data/Resources/Models/"; std::string strModelFinalFilename = strModelsResourcesFolder + filename; // Component to be removed when importing file importer.SetPropertyInteger(AI_CONFIG_PP_RVC_FLAGS, aiComponent_COLORS // | aiComponent_TANGENTS_AND_BITANGENTS | aiComponent_BONEWEIGHTS | aiComponent_ANIMATIONS | aiComponent_LIGHTS | aiComponent_CAMERAS); importer.SetPropertyInteger(AI_CONFIG_PP_PTV_NORMALIZE, 1); // And have it read the given file with some example postprocessing // Usually - if speed is not the most important aspect for you - you'll // propably to request more postprocessing than we do in this example. const aiScene* scene = importer.ReadFile(strModelFinalFilename, aiProcess_Triangulate | aiProcess_JoinIdenticalVertices | aiProcess_RemoveComponent | aiProcess_GenNormals | aiProcess_CalcTangentSpace | aiProcess_PreTransformVertices //| aiProcess_GenUVCoords //| aiProcess_MakeLeftHanded ); // const aiScene* scene = importer.ReadFile( filename, // aiProcessPreset_TargetRealtime_MaxQuality // ); // If the import failed, report it if( !scene) { std::cerr << importer.GetErrorString() << '\n'; return false; } m_nodes.clear(); aiNode* rootNode = scene->mRootNode; handleNode(rootNode, aiMatrix4x4()); m_meshes.clear(); m_meshes.resize(scene->mNumMeshes); m_materials.clear(); //m_materials.resize(scene->mNumMaterials); //std::string dir = filename; //size_t lastSeparator = dir.find_last_of('/'); //if (lastSeparator != std::string::npos) { // dir.erase(lastSeparator, dir.size()); //} //else { // dir = std::string("."); //} for (unsigned int i = 0; i < scene->mNumMeshes; ++i) { m_meshes[i].loadFromAssimpMesh(scene->mMeshes[i]); } //for (unsigned int i = 0; i < scene->mNumMaterials; ++i) { // m_materials[i].loadFromAssimpMaterial(scene->mMaterials[i], dir); //} // const aiMesh* mesh = scene->mMeshes[0]; // std::cerr << "Mesh HasFaces ? " << mesh->HasFaces() << '\n'; // std::cerr << "Mesh HasNormals ? " << mesh->HasNormals() << '\n'; // std::cerr << "Mesh HasPositions ? " << mesh->HasPositions() << '\n'; // std::cerr << "Mesh HasTangentsAndBitangents ? " << mesh->HasTangentsAndBitangents() << '\n'; // std::cerr << "Mesh HasBones ? " << mesh->HasBones() << '\n'; // std::cerr << "Mesh HasTextureCoords ? " << mesh->HasTextureCoords(0) << '\n'; // std::cerr << "mesh->mTextureCoords[0] != NULL ? " << (mesh->mTextureCoords[0] != NULL) << '\n'; // std::cerr << "Mesh GetNumUVChannels ? " << mesh->GetNumUVChannels() << '\n'; // std::cerr << "Mesh GetNumColorChannels ? " << mesh->GetNumColorChannels() << '\n'; // std::cerr << "Mesh mNumFaces ? " << mesh->mNumFaces << '\n'; // std::cerr << "Mesh mNumVertices ? " << mesh->mNumVertices << '\n'; // std::cerr << "Mesh mPrimitiveTypes ? " << mesh->mPrimitiveTypes << " and should be " << aiPrimitiveType_TRIANGLE << '\n'; return true; }
Node::Node() : entity(NULL) { transformation = aiMatrix4x4(); }