Example #1
0
// ------------------------------------------------------------------------------
// 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);
					}
				}
			}
		}
	}
}