[Closed] Converting rotation values for BVH
Hi!
I wrote a BVH exporter for learning purposes. My exporter’s “hierarchy parser” part is already done and is working ok and root positions and rotations are written into bvh file, and i can import it back.
However, problem is that biovision stores rotations in angles. IMO It was relatively straightforward to read angles into object rotations (i also wrote importer part).
But for exporting problem seems to be how to convert rotations to angles, in such manner that i can get the exactly same result back (max -> bvh -> max).
Example:
create a point helper
rotate point 45 degrees along it’s local x
quatToEuler ($point.rotation) shows: (eulerAngles -45 0 0)
rotate point -45 degrees along it’s local z
quatToEuler ($point.rotation) shows: (eulerAngles -45 -1.02453e-005 45)
So far good…
rotate point -45 degrees along it’s local y
Now, this is my problem:
quatToEuler ($point.rotation) shows: (eulerAngles -9.73564 30 54.7356)
I no longer can just read then write the actual x,y and z values and then expect those to work later, when importing bvh file back to max.
Result is an “almost there” walkcycle for example, when i compare it to original bvh file imported with my importer.
I have tested results of my importer compared to other importers and it matches, so i think i’m quite sure that the problem is in how i write the rotation values.
And what comes to from where i read the values; i use world oriented points, linked into joints of hierarchy, in setup frame. I read the rotations in “parent space” of each point, which is point linked to bone one level up in hierarchy.
If someone has enlightening thoughts about rotations and how to solve this i would be delighted. I have tried different methods for getting rotations, not just converting quat rotation value to euler. But i seem to be stuck with this… Maybe i’m going into wrong direction.
I know it’s been almost two months since you asked your question, did you ever find a resolution for this?
I am also currently working on a BVH exporter, and using quatToEuler to try to turn the joint rotations into the BVH format with the specified axis order, and everything works great until I either encounter a situation such as you describe above or the joint moves past 90 degrees (suggesting gimbal lock?).
Surely there’s some way to extract an Euler representation that doesn’t suffer these problems?
This is definitly gimble that is causing the problem. What I’m thinking is you are going to have to get your angles from vectors instead of from rotations. I have not looked at the problem that Sami posted or BVH that close but I’m suspecting that you will need to get angles to be able to store angles correctly.
I’d check your rotation order too as BVH uses ZXY – I’m on holiday at the minute but I’ll be looking into Bvh parsing when I’m back. (tools & mocap shoots etc)
Well in point of fact the particular BVH output that I require uses ZYX, XZY, or YZX, etc., based on the particular joint. I think I have all of that set up correctly, but I’m relatively inexperienced in this area so I might be missing something. I’ll double-check.
Thanks for the replies!
[EDIT] I hope you have a great holiday, Eek!
The key is how you parse it though, it doesnt matter the rotation order of each joint you generally store the rotations always as ZXY. So if one joints rotation order is xyz and another is yxz you still grab the z then x then y.
Also remember how you parse it back using matrix math so, first you want to grab the z x y by. Getting the transform space relative the parent:
I.z[/I] and the positon: tmPos = ($.transform * inverse $.parent.transform).pos
This will get the space the joint exists in and the rotation z value. And now you apply this back with $.rotation[2].value = value.
You could also generate a matrix transformation which maybe a little complex (way i think i’ll do it):
tm = (matrix3
((eulerangles value 0 0) as matrix3).row1
((eulerangles 0 value 0) as matrix3).row2
((eulerangles 0 0 value) as matrix3).row3
tmPos)
And rebuild the space: $.transform = tm * $.parent.transform
So basically at the header of the BVH file you store the direction of each joint i.e the offset position relative to the parent, then the rotations.
I think storing angles may get a little complex, your need vectors and a dot product but also have to use the cross product to determine if its in – or +.
From the web:
The BVH format now becomes a recursive definition. Each segment of the hierarchy contains some data relevant to just that segment then it recursively defines its children. The line following the ROOT keyword contains a single left curly brace ‘{’, the brace is lined up with the “ROOT” keyword. The line following a curly brace is indented by one tab character, these indentations are mostly to just make the file more human readable but there are some BVH file parsers that expect the tabs so if you create a BVH file be sure to make them tabs and not merely spaces. The first piece of information of a segment is the offset of that segment from its parent, or in the case of the root object the offset will generally be zero. The offset is specified by the keyword “OFFSET” followed by the X,Y and Z offset of the segment from its parent. The offset information also indicates the length and direction used for drawing the parent segment. In the BVH format there isn’t any explicit information about how a segment should be drawn. This is usually inferred from the offset of the first child defined for the parent. Typically, only the root and the upper body segments will have multiple children.
Interpreting the data
To calculate the position of a segment you first create a transformation matrix from the local translation and rotation information for that segment. For any joint segment the translation information will simply be the offset as defined in the hierarchy section. The rotation data comes from the motion section. For the root object, the translation data will be the sum of the offset data and the translation data from the motion section. The BVH format doesn’t account for scales so it isn’t necessary to worry about including a scale factor calculation.
A straightforward way to create the rotation matrix is to create 3 separate rotation matrices, one for each axis of rotation. Then concatenate the matrices from left to right Y, X and Z.
vR = vYXZ
An alternative method is to compute the rotation matrix directly. A method for doing this is described in Graphics Gems II, p 322.
Adding the offset information is simple, just poke the X,Y and Z translation data into into the proper locations of the matrix. Once the local transformation is created then concatenate it with the local transformation of its parent, then its grand parent, and so on.
vM = vMchildMparentMgrandparent
http://www.cs.wisc.edu/graphics/Courses/cs-838-1999/Jeff/BVH.html
Im a little rusty, till i get back to my computer. So the first part:
OFFSET 0.0 5.0 10.0 is the xyz i.e the position of the joint relative to its parent ie the parents length. This is crucial as there us no real storing of scale.
CHANNELS ZRotation XRotation YRotation
so if we have an offset of 0 5 10 and say a rotation of 45 45 90 (my rotation only add up to 180!, use a quaternion to get this) we build a matrix:
((eulerangles 45 45 90) as matrix3).row*1,2,3… and for the fourth part we use the offset.
And then take this matrix and and transform it about its parent.
Very cool, thanks for the info.
One part I’m still curious about, though; You said “it doesnt matter the rotation order of each joint you generally store the rotations always as ZXY”, and the example .bvh file on the site you linked to also had ZXY defined as the axis order for each joint (always the same in that example), but the bvh format that I need to create specifies a (possibly) different axis order on each joint like so :
[...snip...]
JOINT lThigh
{
OFFSET 9.509759 -2.926078 2.438399
CHANNELS 3 Xrotation Zrotation Yrotation
JOINT lShin
{
OFFSET -3.657599 -35.844474 0.000000
CHANNELS 3 Xrotation Zrotation Yrotation
JOINT lFoot
{
OFFSET 0.000000 -34.381443 -2.194559
CHANNELS 3 Xrotation Yrotation Zrotation
End Site
{
OFFSET 0.000000 -4.389119 8.290559
}
}
}
}
[...snip...]
JOINT rCollar
{
OFFSET -6.339839 11.948165 -1.463040
CHANNELS 3 Yrotation Zrotation Xrotation
JOINT rShldr
{
OFFSET -5.608320 0.000000 0.000000
CHANNELS 3 Zrotation Yrotation Xrotation
JOINT rForeArm
{
OFFSET -18.287998 0.000000 0.000000
CHANNELS 3 Yrotation Zrotation Xrotation
JOINT rHand
{
OFFSET -14.874242 0.000000 0.000000
CHANNELS 3 Zrotation Yrotation Xrotation
End Site
{
OFFSET -7.315200 0.000000 0.000000
}
}
}
}
}
[...snip...]
That shouldn’t really complicate things though, right? I should be able to generalize what you said even to accomodate that?
I wrote an exporter for this format for another program and had absolutely zero problems with it, the entire thing was done in a day, it’s just Max that is giving me problems
TakuanDaikon:
Nope, i haven’t touched the script for a while – and i don’t have a solution yet, been doing other kind of rigging related stuff on weekends and evenings last two months!
I think i’ll give this hobby project another try – after i have read all these posts! As far as i remember right now, i guess next step for me is to play a bit more with cubes, and try to understand these rotations a bit more.
Rotation order of thing seems to be important, but i noticed it’s not just it causing the problem. I’m aware of ZXY axis order, but that itself doesn’t seem to solve it at least for me (that’s why i asked).
Also, i guess that gimbal lock isn’t the problem in my first tests – it might be when one rotates object more. None of the axis had more that 45 degrees rotation in tests i ran.
But i might be wrong and i’ll have to refresh my memory first! Thanks for replies guys!
[EDIT] Post body removed, as it seems I may have been completely wrong, but I don’t know how to delete my post :-[
I seem to have solved my problems, though the solution is completely unexpected.
First of all, thanks eek for the info, it helped put me on the right track. I don’t understand this stuff well enough to know precisely why this works now, but it does :). It was a very different experience doing this in Max than in the other programs, because each program seems to have things it handles for you “behind the scenes” that the others don’t, and that led me to make many incorrect assumptions.
For my situation, and I don’t know if this applies generally or just to my specific rig, I had to go through each joint/bone (including the FK and IK setups) and set the axis order to what was specified in my .bvh template, meaning that I set the rotation controller’s AxisOrder to 3 (YZX) for the left collarbone, etc.
After I’d double- and triple-checked all of these, it turned out that I could just do the standard “quatToEuler (bone.transform * inverse rot_reference.transform).rotation” code to extract the rotations, and could just store them in the .bvh file in ZYX order. I think that if the target environment used the same coordinate system as Max I could have used the more standard ZXY that eek mentioned above, but my target environment swaps Y and Z so ZYX seemed to work for me.
So it seems as if when all of the bone’s AxisOrder properties are set the way the template BVH specifies, I can just extract and store the rotation information exactly as Eek described (with the exception of swapping Y and Z in my case) and everything works exactly as expected.
It is still possible to encounter gimbal lock, but in practice it doesn’t happen. I assume, though I admit to not having a deep understanding of this stuff, that this is because the target .bvh template specifies each joint’s axis order in a way that should minimize gimbal lock.
So for the right shoulder, for instance, I can rotate on what might be considered the main axis quite as far as I’d like with no problems. If I rotate too far on either of the other two axes I might encounter gimbal lock, but in practice shoulders don’t do that in real life, so it’s not likely to happen.
I hope that made at least a little sense, it’s like 5:00AM and I haven’t had enough coffee yet
In any case, I now have a working .bvh exporter for my custom rig, and I’m pleased as punch. Thanks for the input guys! And best of luck to S-S on getting it working!
.
BVH was designed for this very reason, its essentiallya raw format – you throw your data in that format and parse it how you want. Whats cooler is that any skeleton even broken ones can be parsed into the format.
I’ve worked with two off-sight studios now on mocap setups, so im getting the hang of it.
I really havent dont much, in terms of parsing yet – i’ve been on holiday thinking up a essentially a bvh toolset i want to write. This is what i tend to do nowadays – working on the system then implimenting it.
In theory axis order shouldnt affect transforms matrices though i.e if i extrapolate the YXZ rotations of an object with axis order xyz then put them on another object with axis order of zyx it shouldnt matter. You should i think get the same result.
tmX = (quatToEuler ($.transform * inverse $.parent.transform)).x
tmY = (quatToEuler ($.transform * inverse $.parent.transform)).y
tmZ = (quatToEuler ($.transform * inverse $.parent.transform)).z
tm = matrix3 tmX tmY tmZ offset
Now transforming the matrix tm about its parent with a new axis order shouldnt matter i thought.
$.transform = tm * $.parent.transform
but im not sure, in my view axis order is a system of interpolation of euler axis not there stucture. I.e y is still y when you change the axis order or is y x?
EDIT: Im not sure, i just got back, why might happen is if your values go past 180 your’ll get gimbal as you interpolating on axis that arent meant to go past 180. I need to get back to my maching and look into it.
EDIT TWO: Hmm…
Ok so from a bunch of sites:
http://en.wikipedia.org/wiki/Euler_angles
http://en.wikipedia.org/wiki/Rotation_representation_(mathematics
http://www.softhelp.ru/fileformat/bvh/bvh.htm
Axis order is important, now im wondering whether we need to change it or just build a transfrom space for it.
i.e say weve derived tmX Y and Z, can we just build a transfrom space matrix using this order i.e matrix3 z y x offset instead of x y z offset? A little test i think…