Skeletal Mesh Exporting Philosophy

Hello people! I’m working on a skeletal animated mesh exporting plugin, and I thought I was succesfully exporting all the info last night, when (very sadly!) I saw that the “quat” field of the bones don’t store the bone orientation but only the changing of the orientation! Because the loc, head, tail are in local space of the bone (or parent bone it depends) I can’t say where they are if I don’t know the real bone local or absolute matrix transform (or quaternion/axisangle/eulerangle or whatever). Besides the bones are not accessible via Object.Get(name).
What I simply need is the frame corresponding to a bone in local or world coordinates, that is, base matrix of the frame and origin of the frame.
Am I wrong with something? Am I missing something? Is it possible doing the proper exportation of bones in any way? Have some one done it (succesfully!) before?
Thanks

I guess you could do that by going down the chain starting from the roots bone and adding the transformation to each bones that are parented to it and so forth.

Martin

I´m digging through the same … ahem… mess, writing an exporter, too.

Go take a look at Jiba´s exporter,

http://oomadness.tuxfamily.org/en/blender2cal3d/

It will probably give you some good hints on how all that works.

BTW, is it just me or does a bone´s “getParent()” always return an integer 1 instead of a Bone? (I´m using Blender2.28a and 2.28c)

That’s the way to do it if quaternions store local transformation data, but the problem is right this, quaternions do not store local nor absolute transformation data. Let me explain, the way it should work, if I’m right:

FOR EVERY BONE:
A(f)*B(f) = C(f+1)
A,B,C transformation data (quaternion/matrix/axisangle whatever)

A is the bone orientation at frame “f” in local or world space.
C is the bone orientation at frame “f+1” in local or world space.
B is the transformation to go from A to C

Bone.quat store the B rotation transformation in quaternion format, for every frame.

What misses? the first orientation A(0) to start the computation of all the transformation chain from tob to bottom.

Again, the example above is my opinion of how blender works, it could be wrong as it is the result of my effort to understand blender internal workings, I hope to be wrong, and hope there is a straighter way of ripping off skeletal animation data from blender!
Thank for the help

der_ton: thanks I’m tryng to understand that script but it’s very intricated, I’m also a beginner in python (2 days), so the effort is quite hard! About getParent() it seems to me that behaves exactly as hasParent(), don’t know why. Thank, and let me know if you discover anything new, I’m currently trying a workaround that for now seems to work, soon I’l try to import in my 3D engine and if it’s sure it works I’ll post a message. Thank again.

well, if you have the head and tail of the bones and the roll, can’t you calculate the starting quat? Or even all quats from them?

Martin

I’ve not understood what roll is in all those parameters, can you explain to me?

A little correction for the above formulas:

Probably blender doesn’t do:
A(f)*B(f) = C(f+1)
but
A(0)*B(f) = C(f)
that is, B(f) is the change of orientation from the “reference pose” of the bones at frame “f” and C(f) is the “real” local or absolute transformation.

I’m starting thinking that there must be a way of making work all this with only this information… mmhhh… let me think, maybe I don’t need the real bone orientation to make it work… mmhhh…

If anyone knows, please correct me if I’m wrong:
Head is the starting bone position in parent space, from parent’s Tail.
Tail is the bone ending position in local space.
Loc is the bone displacement which changes during the animation.
Roll is another rotation along Y axis, but why roll has a separate component here.

Thanks

I just finished converting the cal3d exporter so that I can export blender skeletons to The Nebula Device (one of many 3D engines). What I found:

  1. with bone.head, bone.tail, and bone.roll you can derive the quaternion or rotation/orientation of a bone from it’s parent. It’s not too obvious how to do this, but luckily the python function “blender_bone2matrix” in the cal3d script will do this for you. If you want a quaternion instead of a matrix, use the conversion function. You usually want a matrix for transform offsets between bone and object/global coordinates.

  2. the offset of one bone from it’s parent is usually just (head-tail) of the bone’s parent, since typically bone.head = [0,0,0] I would suggest you start with this before trying to handle bone.head <> 0 cases. As theeth noted, you need to build up the coordinate systems by accumulating matrices of a bone from it parent. A bit of a gotcha here is the root bone needs to be transformed by the object’s transformation matrix, that is skeletonobject.getMatrix(). The (head-tail) is in the bone’s local coordination system, so you may need to inverse transform it if your engine wants object coordinates, not bone-local coordinates.

  3. bone.quat and bone.loc, as you noted, are purely for dynamic animation parts. So you can totally ignore these when exporting the base skeleton.

So for static exporting, I setup the transform of the root bone to be (not real code):

rootrotation =  matrix2quaternion( skeletonobject.GetMatrix() * blender_bone2matrix(rootbone.head, rootbone.tail, rootbone.roll) ) 
roottranslation = skeletonobject.GetMatrix() * rootbone.head

and for a sub-bone:

bonerotation = matrix2quaternion( blender_bone2matrix(bone.head, bone.tail, bone.roll))
bonelocation = inverse (parentrotation) * (parent.tail - parent.head) + bone.head

this is basically how cal3d does it, and I followed the same route in my exporter. Note the inverse(parentrotation) in the sub-bone location; this is because parent.tail and parent.head are from my exporting bone python class and are no longer in bone-local coordinates. If you have access to the parent as the original blender bone you can just pull out the head and tail directly from there, I think.

For the animation, I actually just pull the quat/loc values from the ipos, then multiply/add them to the bone rotation/translation that I’ve previously calculated and dumped them out. I dunno what format you want them in, though.

I can send you my exporter code, but it looks a lot like the cal3d exporter you already have, so I doubt it will help.

BTW, I’m not sure what you mean by “frame” in your post. If A(0) is your reference (non-animated) transformation, you can calculate it as above, from bone.head, bone.tail, and bone.roll. You don’t need bone.quat or bone.loc at all for the reference pose. The python script kinda confuses this since there is a “bone.loc” for blender bones and then exported bones, which have different meanings. Ack.

Gary

Thanks a lot Gary! Just this night while sleeping I’veunderstood that head,tail and roll were there just for this purpose, I was concentrating too much with quaternion, even if I don’t understand why from the many ways of representing a skeletal system they’ve choosen this. However if you don’t mind I’m very interested in youre code, I always like peeking in the code of others, you can learn always something, my mail is [email protected], besides this The Nebula Device, its a quite powerful engine.
Thanks.

Michele

What do you mean: “why they choose quaternions?” or “why the API works like this?”

The choice of quats to represent rotations for bones is quite logical, since quaternion rotations interpolates better than matricial rotations, and you don’t have the odd case where the matrix would choke on a gimbal lock.

Martin

Quaternions are best to describe the animation, yes, but describing the rest pose with head, tail, roll, and mixing coordinate spaces like that, is hard to understand.
Also, from the documentation I cannot see the meaning of all these attributes (loc, head, tail, roll, size, quat). It´s really not easy to understand for a newbie (like me for example).
Apparently Jiba had to dive into Blender´s source to find out about it (see the comments in his script). That information shouldn´t be hidden in the source, but the documentation would be a good place. :slight_smile:

der_ton has told exactly what I think, that is, the quaternion representation of rotation is logical, and in fashion today, so nothing to say about it, the problem is about the head,loc,quat,tail,roll… and the ultra complexity of managing skeletal animation this way and the recursive dependency of data. And again the fact that the documentation basically says loc is loc, head is head, tail is tail, and no really useful documentation is given there about their real meaning.
Besides this I wanted to point out that there are much simpler ways to manage skeletal animation, for example:
1)axis-angle(or quaternion) to represent local orientation of the bone
2)an origin vector
this is a compact representation of the base-matrix of the bone (the so called frame of reference, for the mathematical ones).
3)and a scalar “length” that says the lenght of the bone along the Y(can be choosen any axis) in local space.
4)and if you want, another axis-angle(or quaternion) to store what is now stored in Bone.quat. But given the above data it can be very easily calculated (see quaternion/matrix faq).

This is more or less the data that most 3D game engine usually like to use to manage the skeletal animation (see the X file format or the unreal 2003 developer network documentation), so I simply expected something similar.

I’m also a beginner with Blender so there can be an explanation for this I just can’t figure out, or maybe that this is only a temporany solution to the problem. Please anyone correct me if I’m wrong with something.

Michele

Yah, I think it could have been a little more obvious how to map from head,tail, and roll into a quaternion. I imagine that it is easier to write the GUI code when you use the head,tail representation, since the user can just drag the bone points around and the code can directly modify the head and tail values without a lot of math. The biggest thing I think would be to add a getQuaternion() or somesuch function to the bone API in order to get the static quaternion value. Also, somehow note that bone.quat and bone.loc are dynamic/ipo quantities.

Luckily, we have Jean-Baptiste who looked at the code and wrote up some python glue for us :smiley: . I was about to look at the blender code to figure out the quaternions when I stumbled across his blender2cal3d.py script!

Gary

Hey guys, how is your work progressing?

I am close to finishing my md5 exporter, but there are still some details that are puzzling me.

It seems that a bone´s animation loc (translation due to IPO) is stored in that bone´s coordinate system. The rest pose location is stored in the parent´s coordinate system. So I figured that the simple addition of these two translations doesn´t work, so Jiba´s script would have to be updated. Am I the first to stumble over that, or am I maybe wrong?

It seems that it makes a difference when defining poses, in what order you input the keyframes of loc and rot, and I think that things only work as expected if you use “locrot” keys only, or “rot” keys, but no “loc” keys. If you specify a keyframe with rot only, and afterwards decide to add a translation with a loc key, the export script misinterprets the curves…

Edit: ha, I totally forgot that Jiba wrote in his script that you should only use locrot keys. So it´s already a “known restriction”.
But still, I´d like to extract “baked” animation (baked for example from specifying inverse kinematics restrictions), and that apparently doesn´t result in locrot keys as the script expects them.

der_ton, I dunno is this is your problem or not, but the cal3d exporter grabs the ipo and expects that the curves in the ipo (locx/y/z, and quatx/y/z/w) are in a specific order. That is, the cal3d exporter assumes that the locx/locy/locz values should be curves 0-2 and the quatx/y/z/w are curves 3-7 in the ipo.

If you insert loc and rot keys separately then the curves may not be in this specific order. I just tried baking a set of keys and the resulting ipos have their locx/y/z curves at indices 5-7, instead of 0-2 as the cal3d exporter would expect.

You might try rewriting the ipo code to look up and extract the ipo curves by name, instead of by index. I have some code for my extractor that does this, if you want it. <EDIT> it appears that the blender 2.3 python API has an “ipo.getCurve(string)” method; maybe that’s the way to go?

Gary

Yes, Gary, a snippet of example code on that would be helpful. Thanks!

OK, here’s some code.

Lessee, I build this dictionary which maps from name to index for a given ipo:


	def BuildCurveMap(self, ipo):
		curvemap = {}
		ipocurves = ipo.getCurves()
		for i in range(ipo.getNcurves()):
			thiscurveid = CalcIpoName(ipocurves[i])
			curvemap[thiscurveid] = i
		return curvemap		

So the curvemap will map from a curve name to its index in the ipo list of curves. This way I can extract key data by doing something like:


# in my class
ipo = ipo I want to extract a key from
keytime = location of the key in time
curvemap = self.BuildCurveMap(ipo)
curquatxvalue = ipo.EvaluateCurveOn(curvemap['QuatX'], keytime)
curquatyvalue = ipo.EvaluateCurveOn(curvemap['QuatY'], keytime)
curquatzvalue = ipo.EvaluateCurveOn(curvemap['QuatZ'], keytime)
curlocx = ipo.EvaluateCurveOn(curvemap['LocX'], keytime)

And so on. You would probably want to build up arrays of this stuff instead, though. Note that I slack on error checking; I don’t even check to see if the curves exist in that ipo!

CalcIpoName was written since the Blender 2.28 API didn’t provide proper getName() support for the quaternion curves. Thus, I had to figure them out manually:


def CalcIpoName(ipo):
	"""Hack we need to use until they put in the
	quaternion ipo names.  It turns out we can
	deduce the ipo name from the repr string:
	repr(ipo) ="IpoCurve xxxxx ### xxx xxx ..."
	Where "###" is a number from 25-28, specifying
	the curve name:
	25=QuatW, 26=QuatX, 27=QuatY, 28=QuatZ"""
	curverep = repr(ipo)
	curvebits = curverep.split(' ')
	curvevalue = int(curvebits[2])
	curvename = 'Empty'
	# if it's not a quaternion, catch the exception and fallback to 
	# normal getName() functionality
	try:
		curvename = ('QuatW','QuatX','QuatY','QuatZ')[curvevalue-25]
	except:
		curvename = ipo.getName()	
		print "Error: ipo curve %s is not part of a quaternion" % curvename
	return curvename


I dunno if Blender 2.3 has this fixed or not. If so, you can replace CalcIpoName() in the first code chunk with x.getName().

Gary

That´s great, thanks alot!

I’ve also finished the exporting of the geometry for my OpenGL engine ( Vengeance3D ), I didn’t have had such a problem for the loc after rot transfrmation, I’ll try to test better… I thought that Jiba’s script “locrot” restriction meant “do not use scale transform”, I’m going to test better.

I was insterested in knowing if you have found a way to export bone-weights for subsurfed surfaces: I only get the right vertices but without the weights.