// ------------------------------------------------------------------------------ // Application entry point int main (int argc, char* argv[]) { if (argc <= 1) { printf("assimp: No command specified. Use \'assimp help\' for a detailed command list\n"); return 0; } // assimp version // Display version information if (! strcmp(argv[1], "version")) { const unsigned int flags = aiGetCompileFlags(); printf(AICMD_MSG_ABOUT, aiGetVersionMajor(), aiGetVersionMinor(), (flags & ASSIMP_CFLAGS_DEBUG ? "-debug " : ""), (flags & ASSIMP_CFLAGS_NOBOOST ? "-noboost " : ""), (flags & ASSIMP_CFLAGS_SHARED ? "-shared " : ""), (flags & ASSIMP_CFLAGS_SINGLETHREADED ? "-st " : ""), (flags & ASSIMP_CFLAGS_STLPORT ? "-stlport " : ""), aiGetVersionRevision()); return 0; } // assimp help // Display some basic help (--help and -h work as well // because people could try them intuitively) if (!strcmp(argv[1], "help") || !strcmp(argv[1], "--help") || !strcmp(argv[1], "-h")) { printf("%s",AICMD_MSG_HELP); return 0; } // assimp cmpdump // Compare two mini model dumps (regression suite) if (! strcmp(argv[1], "cmpdump")) { return Assimp_CompareDump (&argv[2],argc-2); } // construct global importer and exporter instances Assimp::Importer imp; imp.SetPropertyBool("GLOB_MEASURE_TIME",true); globalImporter = &imp; #ifndef ASSIMP_BUILD_NO_EXPORT // Assimp::Exporter exp; globalExporter = &exp; #endif // assimp listext // List all file extensions supported by Assimp if (! strcmp(argv[1], "listext")) { aiString s; imp.GetExtensionList(s); printf("%s\n",s.data); return 0; } #ifndef ASSIMP_BUILD_NO_EXPORT // assimp listexport // List all export file formats supported by Assimp (not the file extensions, just the format identifiers!) if (! strcmp(argv[1], "listexport")) { aiString s; for(size_t i = 0, end = exp.GetExportFormatCount(); i < end; ++i) { const aiExportFormatDesc* const e = exp.GetExportFormatDescription(i); s.Append( e->id ); if (i!=end-1) { s.Append("\n"); } } printf("%s\n",s.data); return 0; } // assimp exportinfo // stat an export format if (! strcmp(argv[1], "exportinfo")) { aiString s; if (argc<3) { printf("Expected file format id\n"); return -11; } for(size_t i = 0, end = exp.GetExportFormatCount(); i < end; ++i) { const aiExportFormatDesc* const e = exp.GetExportFormatDescription(i); if (!strcmp(e->id,argv[2])) { printf("%s\n%s\n%s\n",e->id,e->fileExtension,e->description); return 0; } } printf("Unknown file format id: \'%s\'\n",argv[2]); return -12; } // assimp export // Export a model to a file if (! strcmp(argv[1], "export")) { return Assimp_Export (&argv[2],argc-2); } #endif // assimp knowext // Check whether a particular file extension is known by us, return 0 on success if (! strcmp(argv[1], "knowext")) { if (argc<3) { printf("Expected file extension"); return -10; } const bool b = imp.IsExtensionSupported(argv[2]); printf("File extension \'%s\' is %sknown\n",argv[2],(b?"":"not ")); return b?0:-1; } // assimp info // Print basic model statistics if (! strcmp(argv[1], "info")) { return Assimp_Info ((const char**)&argv[2],argc-2); } // assimp dump // Dump a model to a file if (! strcmp(argv[1], "dump")) { return Assimp_Dump (&argv[2],argc-2); } // assimp extract // Extract an embedded texture from a file if (! strcmp(argv[1], "extract")) { return Assimp_Extract (&argv[2],argc-2); } // assimp testbatchload // Used by /test/other/streamload.py to load a list of files // using the same importer instance to check for incompatible // importers. if (! strcmp(argv[1], "testbatchload")) { return Assimp_TestBatchLoad (&argv[2],argc-2); } printf("Unrecognized command. Use \'assimp help\' for a detailed command list\n"); return 1; }
void ManageAnimation::Init(const char *filename, float xRotateCorrection, bool normalize) { this->fRotateXCorrection = xRotateCorrection; // Create an instance of the Importer class Assimp::Importer importer; unsigned int flags = aiProcess_JoinIdenticalVertices|aiProcess_Triangulate|aiProcess_FixInfacingNormals|aiProcess_ValidateDataStructure| aiProcess_GenNormals|aiProcess_LimitBoneWeights; // |aiProcess_OptimizeMeshes if (normalize) flags |= aiProcess_PreTransformVertices; // This flag will remove all bones. importer.SetPropertyBool(AI_CONFIG_PP_PTV_NORMALIZE, true); // TODO: This is only used for aiProcess_PreTransformVertices importer.SetPropertyInteger(AI_CONFIG_PP_LBW_MAX_WEIGHTS, 3); // The shader can only handle three weights for each vertice. const aiScene* scene = importer.ReadFile( filename, flags); if (scene == 0) { ErrorDialog("assimp loading %s: %s\n", filename, importer.GetErrorString()); return; } // Number of vertices and number of indices int vertexSize = 0, indexSize = 0; if (gVerbose) { printf("\nScene: %s (%s)*******************\n", scene->mRootNode->mName.data, filename); printf("%d materials, %d meshes, %d animations, %d textures\n", scene->mNumMaterials, scene->mNumMeshes, scene->mNumAnimations, scene->mNumTextures); } //********************************************************************** // Count how much data is needed. All indices and vetices are saved in // the same buffer. //********************************************************************** fMeshData.reset(new Mesh[scene->mNumMeshes]); fNumMeshes = scene->mNumMeshes; for (unsigned int i=0; i<scene->mNumMeshes; i++) { aiMesh *m = scene->mMeshes[i]; if (m->mNumBones > 0) this->fUsingBones = true; // True if any mesh uses bones aiMaterial *mat = scene->mMaterials[m->mMaterialIndex]; aiColor3D c (0.f,0.f,0.f); mat->Get(AI_MATKEY_COLOR_DIFFUSE, c); fMeshData[i].colour = glm::vec4(c.r, c.g, c.b, 1.0f); indexSize += m->mNumFaces * 3; // Always 3 indices for each face (triangle). vertexSize += m->mNumVertices; if (gVerbose) printf("\tMesh %d ('%s'): %d faces, %d vertices\n", i, m->mName.data, m->mNumFaces, m->mNumVertices); // Find all animation bones in all meshes. They may have been seen in another mesh already. fMeshData[i].bones.resize(m->mNumBones); for (unsigned int j=0; j < m->mNumBones; j++) { aiBone *aib = m->mBones[j]; // Add the bone to the global list of bones if it isn't already there. boneindexIT_t it = fBoneIndex.find(aib->mName.data); unsigned int jointIndex = fBoneIndex.size(); if (it == fBoneIndex.end()) fBoneIndex[aib->mName.data] = jointIndex; else jointIndex = it->second; fMeshData[i].bones[j].jointIndex = jointIndex; CopyaiMat(&aib->mOffsetMatrix, fMeshData[i].bones[j].offset); } } if (gVerbose) printf("Total vertices needed: %d, index count %d\n", vertexSize, indexSize); // Find all mesh transformations and update the bones matrix dependency on parents glm::mat4 meshmatrix[scene->mNumMeshes]; FindMeshTransformations(0, meshmatrix, glm::mat4(1), scene->mRootNode); if (gVerbose) DumpNodeTree(0, scene->mRootNode); // Copy all vertex data into one big buffer and all index data into another buffer VertexDataf vertexData[vertexSize]; unsigned short indexData[indexSize]; int vertexOffset = 0, indexOffset = 0; int previousOffset = 0; // The index offset for the current mesh // Skinning data. Allocate even if not using bones. char numWeights[vertexSize]; memset(numWeights, 0, sizeof numWeights); glm::vec3 weights[vertexSize]; float joints[vertexSize*3]; // Up to 4 joints per vertex, but only three are used. memset(joints, 0, sizeof joints); //********************************************************************** // Traverse the meshes again, generating the vertex data //********************************************************************** for (unsigned int i=0; i<scene->mNumMeshes; i++) { aiMesh *m = scene->mMeshes[i]; if (gVerbose) printf("Mesh %d: %d faces, %d vertices, mtl index %d\n", i, m->mNumFaces, m->mNumVertices, m->mMaterialIndex); #if 1 if (gVerbose) { #if 0 printf("Indices:\n"); for (unsigned int face=0; face < m->mNumFaces; face++) { printf("%d:(", m->mFaces[face].mNumIndices); for (unsigned int ind=0; ind < m->mFaces[face].mNumIndices; ind++) printf("%d,", m->mFaces[face].mIndices[ind]); printf("),"); } printf("\nVertices:\n"); for (unsigned int vert=0; vert < m->mNumVertices; vert++) printf("%6.3f %6.3f %6.3f\n", m->mVertices[vert].x, m->mVertices[vert].y, m->mVertices[vert].z); #endif printf(" Transformation matrix:\n"); PrintMatrix(4, meshmatrix[i]); printf("\n"); } #endif // Copy faces, but only those that are proper triangles. unsigned int numTriangles = 0; // The number of found triangles. for (unsigned int face=0; face < m->mNumFaces; face++) { if (m->mFaces[face].mNumIndices != 3) continue; // Only allow triangles // m->mFaces[face].mIndices is a local index into the current mesh. Value 0 will thus // adress the first vertex in the current mesh. As all vertices are stored in the same buffer, // an offset need to be added to get the correct index of the vertex. indexData[indexOffset++] = m->mFaces[face].mIndices[0] + previousOffset; indexData[indexOffset++] = m->mFaces[face].mIndices[1] + previousOffset; indexData[indexOffset++] = m->mFaces[face].mIndices[2] + previousOffset; numTriangles++; } fMeshData[i].numFaces = numTriangles; // Copy all vertices for (unsigned int v = 0; v < m->mNumVertices; v++) { glm::vec4 v1(m->mVertices[v].x, m->mVertices[v].y, m->mVertices[v].z, 1); glm::vec4 v2 = meshmatrix[i] * v1; vertexData[vertexOffset].SetVertex(glm::vec3(v2)); glm::vec4 n1(m->mNormals[v].x, m->mNormals[v].y, m->mNormals[v].z, 1); glm::vec4 n2 = meshmatrix[i] * glm::normalize(n1); vertexData[vertexOffset].SetNormal(glm::vec3(n2)); if (m->mTextureCoords[0] != 0) { vertexData[vertexOffset].SetTexture(m->mTextureCoords[0][v].x, m->mTextureCoords[0][v].y); } else { vertexData[vertexOffset].SetTexture(0,0); } vertexData[vertexOffset].SetIntensity(255); vertexData[vertexOffset].SetAmbient(100); vertexOffset++; } // Every animation bone in Assimp data structures have a list of weights and vertex index. // We want the weights, 0-3 of them, sorted on vertices instead. // Iterate through all bones used in this mesh, and copy weights to respective vertex data. for (unsigned j=0; j < m->mNumBones; j++) { fMeshData[i].bones[j].offset *= glm::inverse(meshmatrix[i]); aiBone *aib = m->mBones[j]; boneindexIT_t it = fBoneIndex.find(aib->mName.data); if (it == fBoneIndex.end()) ErrorDialog("Mesh %d bone %s not found", i, aib->mName.data); if (gVerbose) { printf(" Offset for mesh %d %s (%d) joint %d:\n", i, aib->mName.data, j, it->second); PrintMatrix(4, fMeshData[i].bones[j].offset); } for (unsigned k=0; k < aib->mNumWeights; k++) { int v = aib->mWeights[k].mVertexId + previousOffset; // Add the local vertex number in the current mesh to the offset to get the global number int w = numWeights[v]++; // Because AI_CONFIG_PP_LBW_MAX_WEIGHTS is maximized to 3, There can't be more than 3 weights for a vertex // unless there is a bug in Assimp. if (w >= 3) ErrorDialog("Too many bone weights on vertice %d, bone %s, model %s", v, aib->mName.data, filename); weights[v][w] = aib->mWeights[k].mWeight; joints[v*3 + w] = it->second; } } previousOffset = vertexOffset; } // Now that the weights are sorted on vertices, it is possible to normalize them. The sum shall be 1. for (int j=0; j < vertexSize; j++) NormalizeWeights(weights[j], numWeights[j]); ASSERT(vertexOffset == vertexSize); ASSERT(indexOffset <= indexSize); // Allocated the vertex data in OpenGL. The buffer object is used with the following layout: // 1. The usual vertex data, and array of type VertexDataf // 2. Skin weights, array of glm::vec3. 3 floats for each vertex. // 3. Bones index, 4 bytes for each vertex (only 3 used) const int AREA1 = vertexSize*sizeof vertexData[0]; const int AREA2 = vertexSize*sizeof weights[0]; const int AREA3 = vertexSize*3*sizeof (float); int bufferSize = AREA1; if (fUsingBones) { // Also need space for weights and bones indices. bufferSize += AREA2 + AREA3; } // Allocate the buffer, random content so far if (!fOpenglBuffer.BindArray(bufferSize, 0)) { ErrorDialog("ManageAnimation::Init: Data size is mismatch with input array\n"); } fOpenglBuffer.ArraySubData(0, AREA1, vertexData); if (this->fUsingBones) { glBufferSubData(GL_ARRAY_BUFFER, AREA1, AREA2, weights); glBufferSubData(GL_ARRAY_BUFFER, AREA1+AREA2, AREA3, joints); } glGenVertexArrays(1, &fVao); glBindVertexArray(fVao); StageOneShader::EnableVertexAttribArray(this->fUsingBones); // Will be remembered in the VAO state // Allocate the index data in OpenGL if (!fIndexBuffer.BindElementsArray(indexSize*sizeof indexData[0], indexData)) { ErrorDialog("ManageAnimation::Init: Data size is mismatch with input array\n"); } StageOneShader::VertexAttribPointer(); if (this->fUsingBones) StageOneShader::VertexAttribPointerSkinWeights(AREA1, AREA1+AREA2); checkError("ManageAnimation::Init"); glBindVertexArray(0); if (gVerbose) { printf("Mesh bones for '%s':\n", filename); for (auto &bone : fBoneIndex) { printf(" %s: joint %d\n", bone.first.c_str(), bone.second); } } // Decode animation information unsigned int numMeshBones = fBoneIndex.size(); if (numMeshBones > 0 && scene->mNumAnimations == 0) ErrorDialog("ManageAnimation::Init %s: Bones but no animations", filename); if (scene->mNumAnimations > 0) { if (numMeshBones == 0) ErrorDialog("ManageAnimation::Init %s: No mesh bones", filename); if (gVerbose) printf("Parsing %d animations\n", scene->mNumAnimations); } //********************************************************************** // Decode the animations // There may be more animated bones than used by meshes. //********************************************************************** fAnimations.reset(new Animation[scene->mNumAnimations]); for (unsigned int i=0; i < scene->mNumAnimations; i++) { aiAnimation *aia = scene->mAnimations[i]; fAnimations[i].name = aia->mName.data; fAnimations[i].keysPerSecond = aia->mTicksPerSecond; fAnimations[i].duration = aia->mDuration; // Set the size of bones to the actual nutmber of bones used by the meshes, not the total number of bones // in the model. fAnimations[i].bones.reset(new AnimationBone[numMeshBones]); // The number of keys has to be the same for all channels. This is a limitation if it is going to be possible to // pre compute all matrices. unsigned numChannels = aia->mNumChannels; if (numChannels == 0) ErrorDialog("ManageAnimation::Init no animation channels for %s, %s", aia->mName.data, filename); if (numChannels != numMeshBones) ErrorDialog("ManageAnimation::Init %s animation %d: Can only handle when all bones are used (%d out of %d)", filename, i, numChannels, numMeshBones); unsigned int numKeys = aia->mChannels[0]->mNumPositionKeys; struct channel { glm::mat4 mat; // Relative transformation matrix to parent channel *parent; aiNodeAnim *node; unsigned joint; #define UNUSEDCHANNEL 0xFFFF }; channel channels[numChannels]; // This is the list of all bones in this animation // Check that there are the same number of keys for all channels, and allocate transformation matrices bool foundOneBone = false; for (unsigned int j=0; j < numChannels; j++) { aiNodeAnim *ain = aia->mChannels[j]; if (ain->mNumPositionKeys != numKeys || ain->mNumRotationKeys != numKeys || ain->mNumScalingKeys != numKeys) ErrorDialog("ManageAnimation::Init %s Bad animation setup: Pos keys %d, rot keys %d, scaling keys %d", filename, ain->mNumPositionKeys, ain->mNumRotationKeys, ain->mNumScalingKeys); channels[j].node = ain; channels[j].parent = 0; boneindexIT_t it = fBoneIndex.find(ain->mNodeName.data); if (it == fBoneIndex.end()) { // Skip this channel (bone animation) channels[j].joint = UNUSEDCHANNEL; // Mark it as not used continue; // ErrorDialog("ManageAnimation::Init: Unknown bone %s in animation %s for %s", ain->mNodeName.data, fAnimations[i].name.c_str(), filename); } foundOneBone = true; unsigned int joint = it->second; fAnimations[i].bones[joint].frameMatrix.reset(new glm::mat4[numKeys]); channels[j].joint = joint; } if (!foundOneBone) ErrorDialog("ManageAnimation::Init %s animation %d no bones used", filename, i); // Find the parent for each animation node for (unsigned j=0; j<numChannels; j++) { aiNode *n = FindNode(scene->mRootNode, channels[j].node->mNodeName.data); if (n == 0 || n->mParent == 0) continue; // No parent n = n->mParent; for (unsigned p=0; p<numChannels; p++) { if (p == j || strcmp(n->mName.data, channels[p].node->mNodeName.data) != 0) continue; // Found it! channels[j].parent = &channels[p]; break; } } fAnimations[i].times.reset(new double[numKeys]); fAnimations[i].numKeys = numKeys; // The first key frame doesn't always start at time 0 double firstKeyTime = channels[0].node->mPositionKeys[0].mTime; for (unsigned k=0; k < numKeys; k++) { fAnimations[i].times[k] = channels[0].node->mPositionKeys[k].mTime - firstKeyTime; // printf("Animation key %d at time %.2f\n", k, fAnimations[i].times[k]); for (unsigned int j=0; j < numChannels; j++) { aiNodeAnim *ain = channels[j].node; glm::mat4 R; CopyaiQuat(R, ain->mRotationKeys[k].mValue); glm::mat4 T = glm::translate(glm::mat4(1), glm::vec3(ain->mPositionKeys[k].mValue.x, ain->mPositionKeys[k].mValue.y, ain->mPositionKeys[k].mValue.z)); glm::mat4 S = glm::scale(glm::mat4(1), glm::vec3(ain->mScalingKeys[k].mValue.x, ain->mScalingKeys[k].mValue.y, ain->mScalingKeys[k].mValue.z)); channels[j].mat = T * R * S; if (gVerbose) { printf(" Key %d animation bone %s relative\n", k, channels[j].node->mNodeName.data); PrintMatrix(10, channels[j].mat); } } // Iterate through each animation bone and compute the transformation matrix using parent matrix. for (unsigned int j=0; j < numChannels; j++) { unsigned joint = channels[j].joint; glm::mat4 mat(1); for (channel *node = &channels[j]; node; node = node->parent) { mat = node->mat * mat; } mat = armature * mat; if (joint != UNUSEDCHANNEL) { fAnimations[i].bones[joint].frameMatrix[k] = mat; if (gVerbose) { printf(" Key %d animation bone %s channel %d absolute\n", k, channels[j].node->mNodeName.data, j); PrintMatrix(10, mat); } } } } } }