XSI collada and animations

I’m not sure if I’m posting this in the right forum. If not, could a moderator please move this to the appropriate forum?

I’ve been trying for a while to get bone animations to work from a Collada file exported with XSI, but I really couldn’t get it to work for the world, so I figured maybe someone on these boards could help me.

The Collada file is loaded in a conversion tool, which loads all the needed data from it and exports it to a binary file, which is then loaded into my game engine. I have no troubles loading static meshes, but for some reason the animation data still doesn’t come out correct.

Here’s the (maybe a bit confusing) code from my tool. This seems to work just fine, but I might be wrong.


// this is called for each controller instance in the nodes
private static void parseController(Document.InstanceController ic)
        {          

            foreach (Document.Controller c in d.controllers)
            {
                if (c.id == ic.url.Fragment) // corresponding controller found
                {
                    if (c.controller.GetType() == typeof(Document.Skin))
                    {
                        result.nodes.Add(new Node());                                     
                        addSkin(c);
                    }
                }                
            }


private static void addSkin(Document.Controller contr)
        {            
            Document.Skin skin = (Document.Skin)contr.controller;
            int boneCount = -1;
            foreach (Document.Input inp in skin.joint.inputs)
            {
                if (inp.semantic == "JOINT")
                {
                    boneCount = ((Document.Source)inp.source).accessor.count; 
                }
            }

            // find the array number the weights are stored in
            int weightArrayId = -1;            
            for (int i = 0; i < skin.vertexWeights.inputs.Count; i++)
            {
                if (skin.vertexWeights.inputs[i].semantic == "WEIGHT")
                {
                    weightArrayId = i;
                }
            }

            // add geometry
            string geometryId = skin.source.Fragment;
            int vertexSize = result.vertices.Count;
            foreach (Document.Geometry geo in d.geometries)
            {
                if (geo.id == geometryId) // corresponding geometry found
                {
                    addMesh(geo.mesh);
                }
            }

            // create buffer to store the links to each bone per vertex
            List<BoneLink> bonelinksBuff = new List<BoneLink>();

            // find the weights and links of each vertex to each bone
            int vertexNr = 0;
            for (int i = 0; i < skin.vertexWeights.v.Length; i += (int)skin.vertexWeights.vcount[vertexNr]*2)
            {
                for (int j = 0; j < skin.vertexWeights.vcount[vertexNr]*2; j += 2)
                {
                    BoneLink bl = new BoneLink();
                    bl.boneIdx = skin.vertexWeights.v[ i + j ];
                    bl.weight = ((Document.Array<float>)((Document.Source)skin.vertexWeights.inputs[weightArrayId].source).array).arr[ skin.vertexWeights.v[ i + j + 1] ];

                    bonelinksBuff.Add(bl);                    
                }
            }

            // add bonelinks to geometry
            int vertexId = 0;
            for (int i = 0; i < bonelinksBuff.Count; i+= boneCount)
            {
                for (int j = 0; j < boneCount; j++)
                {
                    result.vertices[vertexId].boneLinks.Add(bonelinksBuff[i + j]);
                }
                vertexId++;
            }            
            
            // find the bone names
            List<string> boneNames = new List<string>();

            foreach (Document.Source s in skin.sources)
            {
                if (s.arrayType == "IDREF_array") // contains the bone names
                {
                    foreach (string st in ((Document.Array<string>)s.array).arr)
                    {
                        boneNames.Add(st);
                    }                    
                }
            }

            // parse the nodes again to get each bone
            getBonesFromNames(boneNames, null, null);

            // add pivot (controller matrix) to each bone
            foreach (Document.Source s in skin.sources)
            {
                if (s.accessor.stride == 16) // contains the matrices
                {
                    int bonePivotNr = 0;

                    for (int pp = 0; pp < ((Document.Array<float>)s.array).arr.Length; pp += 16)
                    {
                        Matrix4x4 pivot = new Matrix4x4();

                        int j = 0;
                        for (int y = 0; y < 4; y++)
                        {
                            for (int x = 0; x < 4; x++)
                            {
                                // also convert from row based to column based
                                pivot.m[ (y*4) + x ] = ((Document.Array<float>)s.array).arr[pp + (x*4 + y)];
                                j++;
                            }
                        }
                        

                        result.boneList[bonePivotNr].pivot = pivot;
                        bonePivotNr++;
                    }
                }
            }

            

            // now get the animation data for the bones
            foreach (Bone b in result.bones)
            {
                getAnimationsForBone(b);
            }

            
            // add bonelinks to geometry

            Node n = result.nodes[result.nodes.Count - 1];

            foreach (Polygon p in n.polygons)
            {
                foreach (Vertex v in p.vertices)
                {
                    for (int i = 0; i < boneCount; i++)
                    {
                        v.boneLinks.Add(bonelinksBuff[v.vertexIdx - vertexSize + i]);                        
                    }
                }
            }

            // convert all bone matrices to actual transformation matrices
            foreach (Bone b in result.bones)
            {
                createTransformMatrix(b);                
            }

            // done, complete animated mesh added                       
            
        }
            
        }



private static void getBonesFromNames(List<string> boneNames, Document.Node node, Bone parent)
        {
            if (node == null)
            {                
                foreach (Document.VisualScene vs in d.visualScenes)
                {
                    // call the root nodes
                    foreach (Document.Node n in vs.nodes)
                    {
                        getBonesFromNames(boneNames, n, null);
                    }
                }
            }
            else
            {
                Bone newBone = null;
                // if the node is a bone
                if (boneNames.Contains(node.id))
                {
                    // create a new bone object
                    newBone = new Bone();
                    newBone.name = node.id;

                    // create pivot point matrix
                    foreach (Document.TransformNode tn in node.transforms)
                    {
                        if (tn.GetType() == typeof(Document.Translate))
                        {
                            newBone.pivotPoint.m[0] = ((Document.Translate)tn)[0];
                            newBone.pivotPoint.m[1] = ((Document.Translate)tn)[1];
                            newBone.pivotPoint.m[2] = ((Document.Translate)tn)[2];
                        }

                        if (tn.GetType() == typeof(Document.Rotate))
                        {
                            if (tn[0] == 1) // x axis rotation
                                newBone.pivotPoint.m[4] = ((Document.Rotate)tn)[3];
                            if (tn[1] == 1) // y axis rotation
                                newBone.pivotPoint.m[5] = ((Document.Rotate)tn)[3];
                            if (tn[2] == 1) // z axis rotation
                                newBone.pivotPoint.m[6] = ((Document.Rotate)tn)[3];  
                        }
                    }

                    if (parent != null)
                    {
                        // add this bone as a child of its parent
                        parent.children.Add(newBone);
                        result.boneList.Add(newBone);
                    }
                    else
                    {
                        // add this bone as the root of the animation tree
                        result.bones.Add(newBone);
                        result.boneList.Add(newBone);
                    }
                }

                // call for all the children
                if (node.children != null)
                {
                    foreach (Document.Node n in node.children)
                    {
                        getBonesFromNames(boneNames, n, newBone);
                    }
                }
            }

        }



private static void getAnimationsForBone(Bone b)
        {
            // get animation data
            List<int> animIndexes = new List<int>();            
            for (int i = 0; i < d.animations.Count; i++)
            {
                // check if this animation contains data for this bone
                if ((d.animations[i].id.Substring(0, b.name.Length) == b.name) &&
                    (d.animations[i].id[b.name.Length] == '_'))
                {
                    animIndexes.Add(i);                    
                }
            }

            // now that we know which elements the bone matrix consist of, we can construct the matrices


            // first we determine the number of frames needed and all intervals 
            List<Frame> frames = new List<Frame>();
            foreach (int i in animIndexes)
            {
                // call it 'a' for easy referencing
                Document.Animation a = d.animations[i];

                foreach (Document.Source s in a.sources)
                {
                    foreach (Document.Param param in s.accessor.parameters)
                    {
                        if (param.name == "TIME")
                        {
                            foreach (float fl in ((Document.Array<float>)s.array).arr)
                            {
                                Frame f = new Frame();
                                f.time = fl;

                                bool contains = false;

                                foreach (Frame fr in frames)
                                {
                                    if (fr.time == f.time)
                                    {
                                        contains = true;
                                        break;
                                    }
                                }

                                if (!contains)
                                {
                                    frames.Add(f);
                                }
                            }

                        }
                    }
                }
            }

            foreach (Frame f in frames)
            {
                foreach (int i in animIndexes)
                {
                    // call it 'a' for easy referencing
                    Document.Animation a = d.animations[i];

                    foreach (Document.Source s in a.sources)
                    {
                        foreach (Document.Param param in s.accessor.parameters)
                        {
                            if (param.name == "TIME")
                            {
                                // check each element on the timeline with the timings this animelement has                           
                                int index = -1;
                                for (int j = 0; j < ((Document.Array<float>)s.array).arr.Length; j++)
                                {
                                    // the keyframe is equal
                                    if (((Document.Array<float>)s.array).arr[j] == f.time)
                                    {
                                        index = j;
                                        break;                                        
                                    }
                                    if (((Document.Array<float>)s.array).arr[j] > f.time)
                                    {
                                        // todo: implement interpolation
                                        
                                        index = j-1;
                                        break;
                                    }
                                }
                                                                
                                // set the correct value in this frame's matrix                                
                                int matrixIndex = getMatrixIndexFromAnimName(a.id);                                                             

                                // find the index of the array with the output values
                                foreach (Document.Source s2 in a.sources)
                                {
                                    foreach (Document.Param param2 in s2.accessor.parameters)
                                    {
                                        if (param2.name == "VALUE")
                                        {
                                            f.matrix.m[matrixIndex] = ((Document.Array<float>)s2.array).arr[index];
                                        }
                                    }
                                }                                

                            }
                        }
                    }

                }
            }

            // add the frames to the bone          
            b.frames = frames;
            
            // get animations for all the childs
            foreach (Bone child in b.children)
            {
                getAnimationsForBone(child);
            }
        }



private static void createTransformMatrix(Bone b)
        {
            float deg2rad = (float)Math.PI / 180.0f;
            foreach (Frame f in b.frames)
            {

                float xRotAngle = f.matrix.m[0] * deg2rad;
                float yRotAngle = f.matrix.m[1] * deg2rad;
                float zRotAngle = f.matrix.m[2] * deg2rad;

                Matrix xRot = new Matrix(new double[4, 4]{
                    { 1,0,0,0},
                    { 0, Math.Cos(xRotAngle) , -Math.Sin(xRotAngle), 0},
                    { 0, Math.Sin(xRotAngle) ,  Math.Cos(xRotAngle), 0},
                    { 0,0,0,1}
                });

                Matrix yRot = new Matrix(new double[4, 4]{
                    { Math.Cos(yRotAngle),0,Math.Sin(yRotAngle),0},
                    { 0,1,0,0},
                    { -Math.Sin(yRotAngle),0,Math.Cos(yRotAngle),0},
                    { 0,0,0,1}
                });

                Matrix zRot = new Matrix(new double[4, 4]{
                    { Math.Cos(zRotAngle), -Math.Sin(zRotAngle),0,0},
                    { Math.Sin(zRotAngle),  Math.Cos(zRotAngle),0,0},
                    { 0,0,1,0},
                    { 0,0,0,1}
                });

                Matrix translationMatrix = new Matrix(new double[4, 4]{
                            { 1 , 0 , 0 , 0 },
                            { 0 , 1 , 0 , 0 },
                            { 0 , 0 , 1 , 0 },
                            { f.matrix.m[4] , f.matrix.m[5] , f.matrix.m[6] , 1 }
                        });

                // todo: check if they are multiplied in the correct order
                Matrix transform = new Matrix(4, 4);
                transform = ((xRot * yRot) * zRot) * translationMatrix;

                int i = 0;
                for (int y = 0; y < transform.Rows; y++)
                {
                    for (int x = 0; x < transform.Cols; x++)
                    {                    
                        f.matrix.m[ i ] = (float)transform[x, y];
                        i++;
                    }
                }
            }

            // transform pivot point matrix rotations to radians
            b.pivotPoint.m[4] = b.pivotPoint.m[4] * deg2rad;
            b.pivotPoint.m[5] = b.pivotPoint.m[5] * deg2rad;
            b.pivotPoint.m[6] = b.pivotPoint.m[6] * deg2rad;

           
            foreach (Bone child in b.children)
            {
                createTransformMatrix(child);
            }
        }

When this is all done, it gets exported, and loaded into my game engine. Here’s the code for that. The CgModel class has a list of vertices which are rendered after the animation is complete. All the vertex transformations are done in the code below. Note that some datatypes might sound a bit weird, I had to convert this to pseudo-pseudo-code cause there’s some stuff in here that I’m not allowed to publish.


void CgModel::applyAnimation()
{	

	for (int i = 0; i < bones_size; ++i)
	{
		updateBoneAnimation(bones[i]);
	}

	for (int i = 0; i < baseVertices_size; ++i)
	{
		// store in 32 bit vector to not lose data
		VectorFx32 vec, startVec, finalVec;
		startVec.x = baseVertices[i].vertex.x;
		startVec.y = baseVertices[i].vertex.y;
		startVec.z = baseVertices[i].vertex.z;

		finalVec.x = 0;
		finalVec.y = 0;
		finalVec.z = 0;

		vector<BoneLink>::iterator i_links = baseVertices[i].boneLinks.begin();
		for ( ; i_links != baseVertices[i].boneLinks.end(); i_links++)
		{
			// get the matrix and vector to transform			
			MatrixFx32_4x4 transform44 = boneList[ (*i_links).boneId ]->finalMatrix;
			
			vec.x = startVec.x;
			vec.y = startVec.y;
			vec.z = startVec.z;					
					
			// put transformation matrix in 4x3 matrix
			MatrixFx32_4x3 transform;
			ConvertMatrix(&transform44, &transform);			
						
			// transform the vertex			
		        vec = vec * transform;
			
			// add the transformed vertex to the final position
			finalVec.x += vec.x * (*i_links).weight;
			finalVec.y += vec.y * (*i_links).weight;
			finalVec.z += vec.z * (*i_links).weight;
		}

		vertices[i].vertex.x = finalVec.x;
		vertices[i].vertex.y = finalVec.y;
		vertices[i].vertex.z = finalVec.z;
	}
}


void CgModel::updateBoneAnimation(CgBone* curBone)
{
	curBone->finalMatrix = curBone->frames[ currentFrame ];
		
	if (curBone->parent != NULL)
		curBone->finalMatrix =  curBone->finalMatrix * curBone->parent->finalMatrix;
	
	for (int i = 0; i < curBone->childs.size(); ++i)
	{
		updateBoneAnimation(curBone->childs[i]);
	}	
}

Now, at this point I’m basically completely clueless about what goes wrong or where. If anybody has any suggestions, that’d be more than welcome. I understand there’s quite a bit of code in this post, so if you read through all that, thanks for your troubles!

Thomas Krak

Hi,

are you able to import animation data at all? I mean, a simple mesh animated in position - which is not a skin deformer? You should treat animation in a generic way. Bone animation is not different then regular node animation. You should ckech if you support the same transform stack that XSI outputs in your engine or if you should preprocess the data first and generate a matrix per frame.

The only special thing to know about XSI and COLLADA skinning, is that since XSI performs world based skinning (the final deformation of the mesh vertices does not take into account the current transform of the mesh nor it’s animation) as opposed to COLLADA, we have to reparent the skinned mesh under the root node if the mesh or one of it’s parents is animated. Luckily, it is not a common practice to animate a skinned mesh transform or it’s parents one so the exported node transforms hierarchies does corresponds to the XSI hierarchies 99% of the time.

Hoping this helps,
LB

There is another application that let you create GLSL effects and apply them to animations or a movie. It’s a very young project and it’s called Mewa Film.