Browse Source

- Further work at the BVH loader. Still work in progress.
- Added a helper class to build a mesh for a meshless node hierarchy

git-svn-id: https://assimp.svn.sourceforge.net/svnroot/assimp/trunk@189 67173fc5-114c-0410-ac8e-9d2fd5bffc1f

ulfjorensen 17 years ago
parent
commit
8271164d74

+ 223 - 29
code/BVHLoader.cpp

@@ -13,18 +13,18 @@ with or without modification, are permitted provided that the following
 conditions are met:
 conditions are met:
 
 
 * Redistributions of source code must retain the above
 * Redistributions of source code must retain the above
-  copyright notice, this list of conditions and the
-  following disclaimer.
+copyright notice, this list of conditions and the
+following disclaimer.
 
 
 * Redistributions in binary form must reproduce the above
 * Redistributions in binary form must reproduce the above
-  copyright notice, this list of conditions and the
-  following disclaimer in the documentation and/or other
-  materials provided with the distribution.
+copyright notice, this list of conditions and the
+following disclaimer in the documentation and/or other
+materials provided with the distribution.
 
 
 * Neither the name of the ASSIMP team, nor the names of its
 * Neither the name of the ASSIMP team, nor the names of its
-  contributors may be used to endorse or promote products
-  derived from this software without specific prior
-  written permission of the ASSIMP Development Team.
+contributors may be used to endorse or promote products
+derived from this software without specific prior
+written permission of the ASSIMP Development Team.
 
 
 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 
 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 
 "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 
 "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 
@@ -41,8 +41,10 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
 */
 
 
 #include "AssimpPCH.h"
 #include "AssimpPCH.h"
+#include "../include/aiAnim.h"
 #include "BVHLoader.h"
 #include "BVHLoader.h"
 #include "fast_atof.h"
 #include "fast_atof.h"
+#include "SkeletonMeshBuilder.h"
 
 
 using namespace Assimp;
 using namespace Assimp;
 
 
@@ -99,6 +101,12 @@ void BVHLoader::InternReadFile( const std::string& pFile, aiScene* pScene, IOSys
 	mReader = mBuffer.begin();
 	mReader = mBuffer.begin();
 	mLine = 1;
 	mLine = 1;
 	ReadStructure( pScene);
 	ReadStructure( pScene);
+
+	// build a dummy mesh for the skeleton so that we see something at least
+	SkeletonMeshBuilder meshBuilder( pScene);
+
+	// construct an animation from all the motion data we read
+	CreateAnimation( pScene);
 }
 }
 
 
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
@@ -139,10 +147,6 @@ aiNode* BVHLoader::ReadNode()
 	if( nodeName.empty() || nodeName == "{")
 	if( nodeName.empty() || nodeName == "{")
 		ThrowException( boost::str( boost::format( "Expected node name, but found \"%s\".") % nodeName));
 		ThrowException( boost::str( boost::format( "Expected node name, but found \"%s\".") % nodeName));
 
 
-	// HACK: (thom) end nodes are called "End Site". If the name of the node is "Site", we know it's going to be an end node
-	if( nodeName == "Site")
-		nodeName = "End Site";
-
 	// then an opening brace should follow
 	// then an opening brace should follow
 	std::string openBrace = GetNextToken();
 	std::string openBrace = GetNextToken();
 	if( openBrace != "{")
 	if( openBrace != "{")
@@ -152,30 +156,39 @@ aiNode* BVHLoader::ReadNode()
 	aiNode* node = new aiNode( nodeName);
 	aiNode* node = new aiNode( nodeName);
 	std::vector<aiNode*> childNodes;
 	std::vector<aiNode*> childNodes;
 
 
+	// and create an bone entry for it
+	mNodes.push_back( Node( node));
+	Node& internNode = mNodes.back();
+
 	// now read the node's contents
 	// now read the node's contents
 	while( 1)
 	while( 1)
 	{
 	{
 		std::string token = GetNextToken();
 		std::string token = GetNextToken();
-		
+
 		// node offset to parent node
 		// node offset to parent node
 		if( token == "OFFSET")
 		if( token == "OFFSET")
 			ReadNodeOffset( node);
 			ReadNodeOffset( node);
 		else if( token == "CHANNELS")
 		else if( token == "CHANNELS")
-			ReadNodeChannels( node);
+			ReadNodeChannels( internNode);
 		else if( token == "JOINT")
 		else if( token == "JOINT")
 		{
 		{
 			// child node follows
 			// child node follows
 			aiNode* child = ReadNode();
 			aiNode* child = ReadNode();
+			child->mParent = node;
 			childNodes.push_back( child);
 			childNodes.push_back( child);
-		} else 
-		if( token == "End")
+		} 
+		else if( token == "End")
 		{
 		{
-			// HACK: (thom) end child node follows. Full token is "End Site", then no name, then a node.
-			// But I don't want to write another function for this, so I simply leave the "Site" for ReadNode() as a node name
-			aiNode* child = ReadNode();
+			// The real symbol is "End Site". Second part comes in a separate token
+			std::string siteToken = GetNextToken();
+			if( siteToken != "Site")
+				ThrowException( boost::str( boost::format( "Expected \"End Site\" keyword, but found \"%s %s\".") % token % siteToken));
+
+			aiNode* child = ReadEndSite( nodeName);
+			child->mParent = node;
 			childNodes.push_back( child);
 			childNodes.push_back( child);
-		} else
-		if( token == "}")
+		} 
+		else if( token == "}")
 		{
 		{
 			// we're done with that part of the hierarchy
 			// we're done with that part of the hierarchy
 			break;
 			break;
@@ -198,6 +211,42 @@ aiNode* BVHLoader::ReadNode()
 	return node;
 	return node;
 }
 }
 
 
+// ------------------------------------------------------------------------------------------------
+// Reads an end node and returns the created node.
+aiNode* BVHLoader::ReadEndSite( const std::string& pParentName)
+{
+	// check opening brace
+	std::string openBrace = GetNextToken();
+	if( openBrace != "{")
+		ThrowException( boost::str( boost::format( "Expected opening brace \"{\", but found \"%s\".") % openBrace));
+
+	// Create a node
+	aiNode* node = new aiNode( "EndSite_" + pParentName);
+
+	// now read the node's contents. Only possible entry is "OFFSET"
+	while( 1)
+	{
+		std::string token = GetNextToken();
+
+		// end node's offset
+		if( token == "OFFSET")
+		{
+			ReadNodeOffset( node);
+		} 
+		else if( token == "}")
+		{
+			// we're done with the end node
+			break;
+		} else
+		{
+			// everything else is a parse error
+			ThrowException( boost::str( boost::format( "Unknown keyword \"%s\".") % token));
+		}
+	}
+
+	// and return the sub-hierarchy we built here
+	return node;
+}
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
 // Reads a node offset for the given node
 // Reads a node offset for the given node
 void BVHLoader::ReadNodeOffset( aiNode* pNode)
 void BVHLoader::ReadNodeOffset( aiNode* pNode)
@@ -215,15 +264,31 @@ void BVHLoader::ReadNodeOffset( aiNode* pNode)
 
 
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
 // Reads the animation channels for the given node
 // Reads the animation channels for the given node
-void BVHLoader::ReadNodeChannels( aiNode* pNode)
+void BVHLoader::ReadNodeChannels( BVHLoader::Node& pNode)
 {
 {
 	// number of channels. Use the float reader because we're lazy
 	// number of channels. Use the float reader because we're lazy
 	float numChannelsFloat = GetNextTokenAsFloat();
 	float numChannelsFloat = GetNextTokenAsFloat();
 	unsigned int numChannels = (unsigned int) numChannelsFloat;
 	unsigned int numChannels = (unsigned int) numChannelsFloat;
 
 
-	// TODO: (thom) proper channel parsing. For the moment I just skip the number of tokens
 	for( unsigned int a = 0; a < numChannels; a++)
 	for( unsigned int a = 0; a < numChannels; a++)
-		GetNextToken();
+	{
+		std::string channelToken = GetNextToken();
+
+		if( channelToken == "Xposition")
+			pNode.mChannels.push_back( Channel_PositionX);
+		else if( channelToken == "Yposition")
+			pNode.mChannels.push_back( Channel_PositionY);
+		else if( channelToken == "Zposition")
+			pNode.mChannels.push_back( Channel_PositionZ);
+		else if( channelToken == "Xrotation")
+			pNode.mChannels.push_back( Channel_RotationX);
+		else if( channelToken == "Yrotation")
+			pNode.mChannels.push_back( Channel_RotationY);
+		else if( channelToken == "Zrotation")
+			pNode.mChannels.push_back( Channel_RotationZ);
+		else
+			ThrowException( boost::str( boost::format( "Invalid channel specifier \"%s\".") % channelToken));
+	}
 }
 }
 
 
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
@@ -236,7 +301,7 @@ void BVHLoader::ReadMotion( aiScene* pScene)
 		ThrowException( boost::str( boost::format( "Expected frame count \"Frames:\", but found \"%s\".") % tokenFrames));
 		ThrowException( boost::str( boost::format( "Expected frame count \"Frames:\", but found \"%s\".") % tokenFrames));
 
 
 	float numFramesFloat = GetNextTokenAsFloat();
 	float numFramesFloat = GetNextTokenAsFloat();
-	unsigned int numFrames = (unsigned int) numFramesFloat;
+	mAnimNumFrames = (unsigned int) numFramesFloat;
 
 
 	// Read frame duration
 	// Read frame duration
 	std::string tokenDuration1 = GetNextToken();
 	std::string tokenDuration1 = GetNextToken();
@@ -244,11 +309,25 @@ void BVHLoader::ReadMotion( aiScene* pScene)
 	if( tokenDuration1 != "Frame" || tokenDuration2 != "Time:")
 	if( tokenDuration1 != "Frame" || tokenDuration2 != "Time:")
 		ThrowException( boost::str( boost::format( "Expected frame duration \"Frame Time:\", but found \"%s %s\".") % tokenDuration1 % tokenDuration2));
 		ThrowException( boost::str( boost::format( "Expected frame duration \"Frame Time:\", but found \"%s %s\".") % tokenDuration1 % tokenDuration2));
 
 
-	float frameDuration = GetNextTokenAsFloat();
+	mAnimTickDuration = GetNextTokenAsFloat();
+
+	// resize value vectors for each node
+	for( std::vector<Node>::iterator it = mNodes.begin(); it != mNodes.end(); ++it)
+		it->mChannelValues.reserve( it->mChannels.size() * mAnimNumFrames);
 
 
-	// resize value array accordingly
-	// ************* Continue here ********
-	//mMotionValues.resize( boost::extents[numFrames][numChannels]);
+	// now read all the data and store it in the corresponding node's value vector
+	for( unsigned int frame = 0; frame < mAnimNumFrames; ++frame)
+	{
+		// on each line read the values for all nodes
+		for( std::vector<Node>::iterator it = mNodes.begin(); it != mNodes.end(); ++it)
+		{
+			// get as many values as the node has channels
+			for( unsigned int c = 0; c < it->mChannels.size(); ++c)
+				it->mChannelValues.push_back( GetNextTokenAsFloat());
+		}
+
+		// after one frame worth of values for all nodes there should be a newline, but we better don't rely on it
+	}
 }
 }
 
 
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
@@ -312,3 +391,118 @@ void BVHLoader::ThrowException( const std::string& pError)
 {
 {
 	throw new ImportErrorException( boost::str( boost::format( "%s:%d - %s") % mFileName % mLine % pError));
 	throw new ImportErrorException( boost::str( boost::format( "%s:%d - %s") % mFileName % mLine % pError));
 }
 }
+
+// ------------------------------------------------------------------------------------------------
+// Constructs an animation for the motion data and stores it in the given scene
+void BVHLoader::CreateAnimation( aiScene* pScene)
+{
+	// create the animation
+	pScene->mNumAnimations = 1;
+	pScene->mAnimations = new aiAnimation*[1];
+	aiAnimation* anim = new aiAnimation;
+	pScene->mAnimations[0] = anim;
+
+	// put down the basic parameters
+	anim->mName.Set( "Motion");
+	anim->mTicksPerSecond = 1.0 / double( mAnimTickDuration);
+	anim->mDuration = double( mAnimNumFrames - 1);
+
+	// now generate the tracks for all nodes
+	anim->mNumChannels = mNodes.size();
+	anim->mChannels = new aiNodeAnim*[anim->mNumChannels];
+	for( unsigned int a = 0; a < anim->mNumChannels; a++)
+	{
+		const Node& node = mNodes[a];
+		const char* nodeName = node.mNode->mName.data;
+		aiNodeAnim* nodeAnim = new aiNodeAnim;
+		anim->mChannels[a] = nodeAnim;
+		nodeAnim->mNodeName.Set( std::string( nodeName));
+
+		// translational part, if given
+		if( node.mChannels.size() == 6)
+		{
+			if( node.mChannels[0] != Channel_PositionX || node.mChannels[1] != Channel_PositionY
+				|| node.mChannels[2] != Channel_PositionZ)
+			{
+				throw new ImportErrorException( boost::str( boost::format( "Unexpected animation "
+					"channel setup at node \"%s\".") % nodeName));
+			}
+
+			nodeAnim->mNumPositionKeys = mAnimNumFrames;
+			nodeAnim->mPositionKeys = new aiVectorKey[mAnimNumFrames];
+			aiVectorKey* poskey = nodeAnim->mPositionKeys;
+			for( unsigned int fr = 0; fr < mAnimNumFrames; ++fr)
+			{
+				poskey->mTime = double( fr);
+				poskey->mValue.x = node.mChannelValues[fr * node.mChannels.size() + 0];
+				poskey->mValue.y = node.mChannelValues[fr * node.mChannels.size() + 1];
+				poskey->mValue.z = node.mChannelValues[fr * node.mChannels.size() + 2];
+				++poskey;
+			}
+		} else
+		{
+			// if no translation part is given, put a default sequence
+			aiVector3D nodePos( node.mNode->mTransformation.a4, node.mNode->mTransformation.b4, node.mNode->mTransformation.c4);
+			nodeAnim->mNumPositionKeys = 2;
+			nodeAnim->mPositionKeys = new aiVectorKey[2];
+			nodeAnim->mPositionKeys[0].mTime = 0.0;
+			nodeAnim->mPositionKeys[0].mValue = nodePos;
+			nodeAnim->mPositionKeys[1].mTime = anim->mDuration;
+			nodeAnim->mPositionKeys[1].mValue = nodePos;
+		}
+
+		// rotation part. Always present. First find value offsets
+		{
+			unsigned int rotOffset = 0;
+			if( node.mChannels.size() == 6)
+			{
+				if( node.mChannels[3] != Channel_RotationZ || node.mChannels[4] != Channel_RotationX
+					|| node.mChannels[5] != Channel_RotationY)
+				{
+					throw new ImportErrorException( boost::str( boost::format( "Unexpected animation "
+						"channel setup at node \"%s\".") % nodeName));
+				}
+				rotOffset = 3;
+			} else
+			{
+				if( node.mChannels[0] != Channel_RotationZ || node.mChannels[1] != Channel_RotationX
+					|| node.mChannels[2] != Channel_RotationY || node.mChannels.size() != 3)
+				{
+					throw new ImportErrorException( boost::str( boost::format( "Unexpected animation "
+						"channel setup at node \"%s\".") % nodeName));
+				}
+			}
+
+			// Then create the number of rotation keys
+			nodeAnim->mNumRotationKeys = mAnimNumFrames;
+			nodeAnim->mRotationKeys = new aiQuatKey[mAnimNumFrames];
+			aiQuatKey* rotkey = nodeAnim->mRotationKeys;
+			for( unsigned int fr = 0; fr < mAnimNumFrames; ++fr)
+			{
+				// translate ZXY euler angels into a quaternion
+				float angleZ = node.mChannelValues[fr * node.mChannels.size() + rotOffset + 0] * float( AI_MATH_PI) / 180.0f;
+				float angleX = node.mChannelValues[fr * node.mChannels.size() + rotOffset + 1] * float( AI_MATH_PI) / 180.0f;
+				float angleY = node.mChannelValues[fr * node.mChannels.size() + rotOffset + 2] * float( AI_MATH_PI) / 180.0f;
+				aiMatrix4x4 temp;
+				aiMatrix3x3 rotMatrix;
+				aiMatrix4x4::RotationX( angleX, temp); rotMatrix *= aiMatrix3x3( temp);
+				aiMatrix4x4::RotationY( angleY, temp); rotMatrix *= aiMatrix3x3( temp);
+				aiMatrix4x4::RotationZ( angleZ, temp); rotMatrix *= aiMatrix3x3( temp);
+
+				rotkey->mTime = double( fr);
+				rotkey->mValue = aiQuaternion( rotMatrix);
+				++rotkey;
+			}
+		}
+
+		// scaling part. Always just a default track
+		{
+			nodeAnim->mNumScalingKeys = 2;
+			nodeAnim->mScalingKeys = new aiVectorKey[2];
+			nodeAnim->mScalingKeys[0].mTime = 0.0;
+			nodeAnim->mScalingKeys[0].mValue.Set( 1.0f, 1.0f, 1.0f);
+			nodeAnim->mScalingKeys[1].mTime = anim->mDuration;
+			nodeAnim->mScalingKeys[1].mValue.Set( 1.0f, 1.0f, 1.0f);
+		}
+	}
+}

+ 38 - 5
code/BVHLoader.h

@@ -44,7 +44,6 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 #define AI_BVHLOADER_H_INC
 #define AI_BVHLOADER_H_INC
 
 
 #include "BaseImporter.h"
 #include "BaseImporter.h"
-#include <boost/multi_array.hpp>
 
 
 namespace Assimp
 namespace Assimp
 {
 {
@@ -57,6 +56,28 @@ class BVHLoader : public BaseImporter
 {
 {
 	friend class Importer;
 	friend class Importer;
 
 
+	/** Possible animation channels for which the motion data holds the values */
+	enum ChannelType
+	{
+		Channel_PositionX,
+		Channel_PositionY,
+		Channel_PositionZ,
+		Channel_RotationX,
+		Channel_RotationY,
+		Channel_RotationZ
+	};
+
+	/** Collected list of node. Will be bones of the dummy mesh some day, addressed by their array index */
+	struct Node
+	{
+		const aiNode* mNode;
+		std::vector<ChannelType> mChannels;
+		std::vector<float> mChannelValues; // motion data values for that node. Of size NumChannels * NumFrames
+
+		Node() { }
+		Node( const aiNode* pNode) : mNode( pNode) { }
+	};
+
 protected:
 protected:
 	/** Constructor to be privately used by Importer */
 	/** Constructor to be privately used by Importer */
 	BVHLoader();
 	BVHLoader();
@@ -93,11 +114,14 @@ protected:
 	/** Reads a node and recursively its childs and returns the created node. */
 	/** Reads a node and recursively its childs and returns the created node. */
 	aiNode* ReadNode();
 	aiNode* ReadNode();
 
 
+	/** Reads an end node and returns the created node. */
+	aiNode* ReadEndSite( const std::string& pParentName);
+
 	/** Reads a node offset for the given node */
 	/** Reads a node offset for the given node */
 	void ReadNodeOffset( aiNode* pNode);
 	void ReadNodeOffset( aiNode* pNode);
 
 
-	/** Reads the animation channels for the given node */
-	void ReadNodeChannels( aiNode* pNode);
+	/** Reads the animation channels into the given node */
+	void ReadNodeChannels( BVHLoader::Node& pNode);
 
 
 	/** Reads the motion data */
 	/** Reads the motion data */
 	void ReadMotion( aiScene* pScene);
 	void ReadMotion( aiScene* pScene);
@@ -111,6 +135,9 @@ protected:
 	/** Aborts the file reading with an exception */
 	/** Aborts the file reading with an exception */
 	void ThrowException( const std::string& pError);
 	void ThrowException( const std::string& pError);
 
 
+	/** Constructs an animation for the motion data and stores it in the given scene */
+	void CreateAnimation( aiScene* pScene);
+
 protected:
 protected:
 	/** Filename, for a verbose error message */
 	/** Filename, for a verbose error message */
 	std::string mFileName;
 	std::string mFileName;
@@ -124,8 +151,14 @@ protected:
 	/** Current line, for error messages */
 	/** Current line, for error messages */
 	unsigned int mLine;
 	unsigned int mLine;
 
 
-	/** motion values per frame */
-	boost::multi_array<float, 2> mMotionValues;
+	/** Collected list of nodes. Will be bones of the dummy mesh some day, addressed by their array index.
+	* Also contain the motion data for the node's channels
+	*/
+	std::vector<Node> mNodes;
+
+	/** basic Animation parameters */
+	float mAnimTickDuration;
+	unsigned int mAnimNumFrames;
 };
 };
 
 
 } // end of namespace Assimp
 } // end of namespace Assimp

+ 238 - 0
code/SkeletonMeshBuilder.cpp

@@ -0,0 +1,238 @@
+/** Implementation of a little class to construct a dummy mesh for a skeleton */
+
+/*
+Open Asset Import Library (ASSIMP)
+----------------------------------------------------------------------
+
+Copyright (c) 2006-2008, ASSIMP Development Team
+All rights reserved.
+
+Redistribution and use of this software in source and binary forms, 
+with or without modification, are permitted provided that the 
+following conditions are met:
+
+* Redistributions of source code must retain the above
+copyright notice, this list of conditions and the
+following disclaimer.
+
+* Redistributions in binary form must reproduce the above
+copyright notice, this list of conditions and the
+following disclaimer in the documentation and/or other
+materials provided with the distribution.
+
+* Neither the name of the ASSIMP team, nor the names of its
+contributors may be used to endorse or promote products
+derived from this software without specific prior
+written permission of the ASSIMP Development Team.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+----------------------------------------------------------------------
+*/
+
+#include "AssimpPCH.h"
+#include "../include/aiScene.h"
+#include "SkeletonMeshBuilder.h"
+
+using namespace Assimp;
+
+// ------------------------------------------------------------------------------------------------
+// The constructor processes the given scene and adds a mesh there. 
+SkeletonMeshBuilder::SkeletonMeshBuilder( aiScene* pScene)
+{
+	// nothing to do if there's mesh data already present at the scene
+	if( pScene->mNumMeshes > 0 || pScene->mRootNode == NULL)
+		return;
+
+	// build some faces around each node 
+	CreateGeometry( pScene->mRootNode);
+
+	// create a mesh to hold all the generated faces
+	pScene->mNumMeshes = 1;
+	pScene->mMeshes = new aiMesh*[1];
+	pScene->mMeshes[0] = CreateMesh();
+	// and install it at the root node
+	pScene->mRootNode->mNumMeshes = 1;
+	pScene->mRootNode->mMeshes = new unsigned int[1];
+	pScene->mRootNode->mMeshes[0] = 0;
+
+	// create a dummy material for the mesh
+	pScene->mNumMaterials = 1;
+	pScene->mMaterials = new aiMaterial*[1];
+	pScene->mMaterials[0] = CreateMaterial();
+}
+
+// ------------------------------------------------------------------------------------------------
+// Recursively builds a simple mesh representation for the given node 
+void SkeletonMeshBuilder::CreateGeometry( const aiNode* pNode)
+{
+	// add a joint entry for the node. 
+	const unsigned int boneIndex = mBones.size();
+	const unsigned int vertexStartIndex = mVertices.size();
+
+	// now build the geometry. 
+	if( pNode->mNumChildren > 0)
+	{
+		// If the node has childs, we build little pointers to each of them
+		for( unsigned int a = 0; a < pNode->mNumChildren; a++)
+		{
+			// find a suitable coordinate system
+			const aiMatrix4x4& childTransform = pNode->mChildren[a]->mTransformation;
+			aiVector3D childpos( childTransform.a4, childTransform.b4, childTransform.c4);
+			float distanceToChild = childpos.Length();
+			aiVector3D up = aiVector3D( childpos).Normalize();
+
+			aiVector3D orth( 1.0f, 0.0f, 0.0f);
+			if( abs( orth * up) > 0.99f)
+				orth.Set( 0.0f, 1.0f, 0.0f);
+
+			aiVector3D front = (up ^ orth).Normalize();
+			aiVector3D side = (front ^ up).Normalize();
+
+			mVertices.push_back( -front * distanceToChild * 0.1f);
+			mVertices.push_back( childpos);
+			mVertices.push_back( -side * distanceToChild * 0.1f);
+			mVertices.push_back( -side * distanceToChild * 0.1f);
+			mVertices.push_back( childpos);
+			mVertices.push_back( front * distanceToChild * 0.1f);
+			mVertices.push_back( front * distanceToChild * 0.1f);
+			mVertices.push_back( childpos);
+			mVertices.push_back( side * distanceToChild * 0.1f);
+			mVertices.push_back( side * distanceToChild * 0.1f);
+			mVertices.push_back( childpos);
+			mVertices.push_back( -front * distanceToChild * 0.1f);
+
+			unsigned localVertexStart = vertexStartIndex + a * 12;
+			mFaces.push_back( Face( localVertexStart + 0, localVertexStart + 1, localVertexStart + 2));
+			mFaces.push_back( Face( localVertexStart + 3, localVertexStart + 4, localVertexStart + 5));
+			mFaces.push_back( Face( localVertexStart + 6, localVertexStart + 7, localVertexStart + 8));
+			mFaces.push_back( Face( localVertexStart + 9, localVertexStart + 10, localVertexStart + 11));
+		}
+	} else
+	{
+		// if the node has no children, it's an end node. Put a little knob there instead
+		aiVector3D ownpos( pNode->mTransformation.a4, pNode->mTransformation.b4, pNode->mTransformation.c4);
+		float sizeEstimate = ownpos.Length() * 0.2f;
+
+		mVertices.push_back( aiVector3D( -sizeEstimate, 0.0f, 0.0f));
+		mVertices.push_back( aiVector3D( 0.0f, sizeEstimate, 0.0f));
+		mVertices.push_back( aiVector3D( 0.0f, 0.0f, -sizeEstimate));
+		mVertices.push_back( aiVector3D( 0.0f, sizeEstimate, 0.0f));
+		mVertices.push_back( aiVector3D( sizeEstimate, 0.0f, 0.0f));
+		mVertices.push_back( aiVector3D( 0.0f, 0.0f, -sizeEstimate));
+		mVertices.push_back( aiVector3D( sizeEstimate, 0.0f, 0.0f));
+		mVertices.push_back( aiVector3D( 0.0f, -sizeEstimate, 0.0f));
+		mVertices.push_back( aiVector3D( 0.0f, 0.0f, -sizeEstimate));
+		mVertices.push_back( aiVector3D( 0.0f, -sizeEstimate, 0.0f));
+		mVertices.push_back( aiVector3D( -sizeEstimate, 0.0f, 0.0f));
+		mVertices.push_back( aiVector3D( 0.0f, 0.0f, -sizeEstimate));
+
+		mVertices.push_back( aiVector3D( -sizeEstimate, 0.0f, 0.0f));
+		mVertices.push_back( aiVector3D( 0.0f, 0.0f, sizeEstimate));
+		mVertices.push_back( aiVector3D( 0.0f, sizeEstimate, 0.0f));
+		mVertices.push_back( aiVector3D( 0.0f, sizeEstimate, 0.0f));
+		mVertices.push_back( aiVector3D( 0.0f, 0.0f, sizeEstimate));
+		mVertices.push_back( aiVector3D( sizeEstimate, 0.0f, 0.0f));
+		mVertices.push_back( aiVector3D( sizeEstimate, 0.0f, 0.0f));
+		mVertices.push_back( aiVector3D( 0.0f, 0.0f, sizeEstimate));
+		mVertices.push_back( aiVector3D( 0.0f, -sizeEstimate, 0.0f));
+		mVertices.push_back( aiVector3D( 0.0f, -sizeEstimate, 0.0f));
+		mVertices.push_back( aiVector3D( 0.0f, 0.0f, sizeEstimate));
+		mVertices.push_back( aiVector3D( -sizeEstimate, 0.0f, 0.0f));
+
+		mFaces.push_back( Face( vertexStartIndex + 0, vertexStartIndex + 1, vertexStartIndex + 2));
+		mFaces.push_back( Face( vertexStartIndex + 3, vertexStartIndex + 4, vertexStartIndex + 5));
+		mFaces.push_back( Face( vertexStartIndex + 6, vertexStartIndex + 7, vertexStartIndex + 8));
+		mFaces.push_back( Face( vertexStartIndex + 9, vertexStartIndex + 10, vertexStartIndex + 11));
+		mFaces.push_back( Face( vertexStartIndex + 12, vertexStartIndex + 13, vertexStartIndex + 14));
+		mFaces.push_back( Face( vertexStartIndex + 15, vertexStartIndex + 16, vertexStartIndex + 17));
+		mFaces.push_back( Face( vertexStartIndex + 18, vertexStartIndex + 19, vertexStartIndex + 20));
+		mFaces.push_back( Face( vertexStartIndex + 21, vertexStartIndex + 22, vertexStartIndex + 23));
+	}
+
+	// create a bone affecting all the newly created vertices
+	aiBone* bone = new aiBone;
+	mBones.push_back( bone);
+	bone->mName = pNode->mName;
+
+	// calculate the bone offset matrix by concatenating the inverse transformations of all parents
+	bone->mOffsetMatrix = aiMatrix4x4( pNode->mTransformation).Inverse();
+	for( aiNode* parent = pNode->mParent; parent != NULL; parent = parent->mParent)
+		bone->mOffsetMatrix = aiMatrix4x4( parent->mTransformation).Inverse() * bone->mOffsetMatrix;
+
+	// add all the vertices to the bone's influences
+	unsigned int numVertices = mVertices.size() - vertexStartIndex;
+	bone->mNumWeights = numVertices;
+	bone->mWeights = new aiVertexWeight[numVertices];
+	for( unsigned int a = 0; a < numVertices; a++)
+		bone->mWeights[a] = aiVertexWeight( vertexStartIndex + a, 1.0f);
+
+	// HACK: (thom) transform all vertices to the bone's local space. Should be done before adding
+	// them to the array, but I'm tired now and I'm annoyed.
+	aiMatrix4x4 boneToMeshTransform = aiMatrix4x4( bone->mOffsetMatrix).Inverse();
+	for( unsigned int a = vertexStartIndex; a < mVertices.size(); a++)
+		mVertices[a] = boneToMeshTransform * mVertices[a];
+
+	// and finally recurse into the children list
+	for( unsigned int a = 0; a < pNode->mNumChildren; a++)
+		CreateGeometry( pNode->mChildren[a]);
+}
+
+// ------------------------------------------------------------------------------------------------
+// Creates the mesh from the internally accumulated stuff and returns it.
+aiMesh* SkeletonMeshBuilder::CreateMesh()
+{
+	aiMesh* mesh = new aiMesh();
+
+	// add points
+	mesh->mNumVertices = mVertices.size();
+	mesh->mVertices = new aiVector3D[mesh->mNumVertices];
+	std::copy( mVertices.begin(), mVertices.end(), mesh->mVertices);
+
+	// add faces
+	mesh->mNumFaces = mFaces.size();
+	mesh->mFaces = new aiFace[mesh->mNumFaces];
+	for( unsigned int a = 0; a < mesh->mNumFaces; a++)
+	{
+		const Face& inface = mFaces[a];
+		aiFace& outface = mesh->mFaces[a];
+		outface.mNumIndices = 3;
+		outface.mIndices = new unsigned int[3];
+		outface.mIndices[0] = inface.mIndices[0];
+		outface.mIndices[1] = inface.mIndices[1];
+		outface.mIndices[2] = inface.mIndices[2];
+	}
+
+	// add the bones
+	mesh->mNumBones = mBones.size();
+	mesh->mBones = new aiBone*[mesh->mNumBones];
+	std::copy( mBones.begin(), mBones.end(), mesh->mBones);
+
+	// default
+	mesh->mMaterialIndex = 0;
+
+	return mesh;
+}
+
+// ------------------------------------------------------------------------------------------------
+// Creates a dummy material and returns it.
+aiMaterial* SkeletonMeshBuilder::CreateMaterial()
+{
+	Assimp::MaterialHelper* matHelper = new Assimp::MaterialHelper;
+
+	// Name
+	aiString matName( std::string( "Material"));
+	matHelper->AddProperty( &matName, AI_MATKEY_NAME);
+
+	return matHelper;
+}

+ 102 - 0
code/SkeletonMeshBuilder.h

@@ -0,0 +1,102 @@
+/** Helper class to construct a dummy mesh for file formats containing only motion data */
+
+/*
+Open Asset Import Library (ASSIMP)
+----------------------------------------------------------------------
+
+Copyright (c) 2006-2008, ASSIMP Development Team
+All rights reserved.
+
+Redistribution and use of this software in source and binary forms, 
+with or without modification, are permitted provided that the 
+following conditions are met:
+
+* Redistributions of source code must retain the above
+copyright notice, this list of conditions and the
+following disclaimer.
+
+* Redistributions in binary form must reproduce the above
+copyright notice, this list of conditions and the
+following disclaimer in the documentation and/or other
+materials provided with the distribution.
+
+* Neither the name of the ASSIMP team, nor the names of its
+contributors may be used to endorse or promote products
+derived from this software without specific prior
+written permission of the ASSIMP Development Team.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+----------------------------------------------------------------------
+*/
+
+#ifndef AI_SKELETONMESHBUILDER_H_INC
+#define AI_SKELETONMESHBUILDER_H_INC
+
+#include <vector>
+#include "../include/aiMesh.h"
+
+struct aiScene;
+struct aiNode;
+
+namespace Assimp
+{
+
+/** 
+ * This little helper class constructs a dummy mesh for a given scene
+ * the resembles the node hierarchy. This is useful for file formats
+ * that don't carry any mesh data but only animation data.
+ */
+class SkeletonMeshBuilder
+{
+public:
+  /** The constructor processes the given scene and adds a mesh there. Does nothing
+   * if the scene already has mesh data. 
+   * @param pScene The scene for which a skeleton mesh should be constructed.
+   */
+  SkeletonMeshBuilder( aiScene* pScene);
+
+protected:
+  /** Recursively builds a simple mesh representation for the given node and also creates
+   * a joint for the node that affects this part of the mesh.
+   * @param pNode The node to build geometry for.
+   */
+  void CreateGeometry( const aiNode* pNode);
+
+  /** Creates the mesh from the internally accumulated stuff and returns it. */
+  aiMesh* CreateMesh();
+
+  /** Creates a dummy material and returns it. */
+  aiMaterial* CreateMaterial();
+
+protected:
+  /** space to assemble the mesh data: points */
+  std::vector<aiVector3D> mVertices;
+
+  /** faces */
+  struct Face 
+  { 
+    unsigned int mIndices[3]; 
+    Face();
+    Face( unsigned int p0, unsigned int p1, unsigned int p2)
+    { mIndices[0] = p0; mIndices[1] = p1; mIndices[2] = p2; } 
+  };
+  std::vector<Face> mFaces;
+  
+  /** bones */
+  std::vector<aiBone*> mBones;
+};
+
+} // end of namespace Assimp
+
+#endif // AI_SKELETONMESHBUILDER_H_INC

+ 1 - 4
include/aiMatrix4x4.h

@@ -128,7 +128,6 @@ struct aiMatrix4x4
 	 */
 	 */
 	static aiMatrix4x4& RotationX(float a, aiMatrix4x4& out);
 	static aiMatrix4x4& RotationX(float a, aiMatrix4x4& out);
 
 
-
 	/** \brief Returns a rotation matrix for a rotation around the y axis
 	/** \brief Returns a rotation matrix for a rotation around the y axis
 	 *  \param a Rotation angle, in radians
 	 *  \param a Rotation angle, in radians
 	 *  \param out Receives the output matrix
 	 *  \param out Receives the output matrix
@@ -136,7 +135,6 @@ struct aiMatrix4x4
 	 */
 	 */
 	static aiMatrix4x4& RotationY(float a, aiMatrix4x4& out);
 	static aiMatrix4x4& RotationY(float a, aiMatrix4x4& out);
 
 
-
 	/** \brief Returns a rotation matrix for a rotation around the z axis
 	/** \brief Returns a rotation matrix for a rotation around the z axis
 	 *  \param a Rotation angle, in radians
 	 *  \param a Rotation angle, in radians
 	 *  \param out Receives the output matrix
 	 *  \param out Receives the output matrix
@@ -144,13 +142,12 @@ struct aiMatrix4x4
 	 */
 	 */
 	static aiMatrix4x4& RotationZ(float a, aiMatrix4x4& out);
 	static aiMatrix4x4& RotationZ(float a, aiMatrix4x4& out);
 
 
-
 	/** \brief Returns a translation matrix 
 	/** \brief Returns a translation matrix 
 	 *  \param v Translation vector
 	 *  \param v Translation vector
 	 *  \param out Receives the output matrix
 	 *  \param out Receives the output matrix
 	 *  \return Reference to the output matrix
 	 *  \return Reference to the output matrix
 	 */
 	 */
-	static aiMatrix4x4& Translation(aiVector3D v, aiMatrix4x4& out);
+	static aiMatrix4x4& Translation( const aiVector3D& v, aiMatrix4x4& out);
 
 
 #endif // __cplusplus
 #endif // __cplusplus
 
 

+ 2 - 2
include/aiMatrix4x4.inl

@@ -252,6 +252,7 @@ inline aiMatrix4x4& aiMatrix4x4::RotationX(float a, aiMatrix4x4& out)
 	out.b3 = -(out.c2 = sin(a));
 	out.b3 = -(out.c2 = sin(a));
 	return out;
 	return out;
 }
 }
+
 // ---------------------------------------------------------------------------
 // ---------------------------------------------------------------------------
 inline aiMatrix4x4& aiMatrix4x4::RotationY(float a, aiMatrix4x4& out)
 inline aiMatrix4x4& aiMatrix4x4::RotationY(float a, aiMatrix4x4& out)
 {
 {
@@ -281,7 +282,7 @@ inline aiMatrix4x4& aiMatrix4x4::RotationZ(float a, aiMatrix4x4& out)
 	return out;
 	return out;
 }
 }
 // ---------------------------------------------------------------------------
 // ---------------------------------------------------------------------------
-inline aiMatrix4x4& aiMatrix4x4::Translation(aiVector3D v, aiMatrix4x4& out)
+inline aiMatrix4x4& aiMatrix4x4::Translation( const aiVector3D& v, aiMatrix4x4& out)
 {
 {
 	out = aiMatrix4x4();
 	out = aiMatrix4x4();
 	out.a4 = v.x;
 	out.a4 = v.x;
@@ -290,6 +291,5 @@ inline aiMatrix4x4& aiMatrix4x4::Translation(aiVector3D v, aiMatrix4x4& out)
 	return out;
 	return out;
 }
 }
 
 
-
 #endif // __cplusplus
 #endif // __cplusplus
 #endif // AI_MATRIX4x4_INL_INC
 #endif // AI_MATRIX4x4_INL_INC

+ 6 - 2
workspaces/vc8/assimp.vcproj

@@ -1511,6 +1511,7 @@
 						<Tool
 						<Tool
 							Name="VCCLCompilerTool"
 							Name="VCCLCompilerTool"
 							UsePrecompiledHeader="1"
 							UsePrecompiledHeader="1"
+							PrecompiledHeaderThrough="AssimpPCH.h"
 						/>
 						/>
 					</FileConfiguration>
 					</FileConfiguration>
 					<FileConfiguration
 					<FileConfiguration
@@ -1519,6 +1520,7 @@
 						<Tool
 						<Tool
 							Name="VCCLCompilerTool"
 							Name="VCCLCompilerTool"
 							UsePrecompiledHeader="1"
 							UsePrecompiledHeader="1"
+							PrecompiledHeaderThrough="AssimpPCH.h"
 						/>
 						/>
 					</FileConfiguration>
 					</FileConfiguration>
 					<FileConfiguration
 					<FileConfiguration
@@ -1534,7 +1536,8 @@
 						>
 						>
 						<Tool
 						<Tool
 							Name="VCCLCompilerTool"
 							Name="VCCLCompilerTool"
-							UsePrecompiledHeader="0"
+							UsePrecompiledHeader="1"
+							PrecompiledHeaderThrough="AssimpPCH.h"
 						/>
 						/>
 					</FileConfiguration>
 					</FileConfiguration>
 					<FileConfiguration
 					<FileConfiguration
@@ -1542,7 +1545,8 @@
 						>
 						>
 						<Tool
 						<Tool
 							Name="VCCLCompilerTool"
 							Name="VCCLCompilerTool"
-							UsePrecompiledHeader="0"
+							UsePrecompiledHeader="1"
+							PrecompiledHeaderThrough="AssimpPCH.h"
 						/>
 						/>
 					</FileConfiguration>
 					</FileConfiguration>
 				</File>
 				</File>