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