Concatenating Node transforms

Discussion area about developing or extending OGRE, adding plugins for it or building applications on it. No newbie questions please, use the Help forum for that.
User avatar
sparkprime
Ogre Magi
Posts: 1137
Joined: Mon May 07, 2007 3:43 am
Location: Ossining, New York
x 13
Contact:

Concatenating Node transforms

Post by sparkprime »

It seems that from the source code of Ogre::Node, each node stores its position, quaternion, and scale. These values are concatenated using the stack of parent nodes into a derived position, quaternion, and scale in world space. The actual matrix used in the draw is then created based on the derived position, quaternion and scale.

Is this actually correct?

If you have a parent node that is a rotation of 45 degrees about Z, and a child node that is a scale of 150% along x, it seems that it is not possible to concatenate those values into a derived pos,quat,scale. This is because the scale is measured along the x,y,z axes and a derived scale would have to be a scale of 50% in the direction of vector3(0.7,0.7,0)

Mathematically speaking, a tuple (pos,quat,scale) representation of transforms is not closed over concatenation.

It seems the current implementation does not handle scale correctly, in that a child scale is not rotated by the parent rotation. mDerivedScale = parentScale * mScale;

Surely the correct thing to do is to keep a 4x3 matrix at each node, and concatenate those. It is not even possible to get the scale back from an arbitrary 4x3 matrix, since such a scale cannot be represented with a simple 3d vector.

I can believe that this has gone unnoticed because scales are rarely used, and when they are used, they are usually uniform (same scale in every axis).

Am I right?
scrawl
OGRE Expert User
OGRE Expert User
Posts: 1119
Joined: Sat Jan 01, 2011 7:57 pm
x 216

Re: Concatenating Node transforms

Post by scrawl »

Yep. Non-uniform scaling with rotations is broken. It has been discussed before here, and i've also logged it into JIRA.
http://www.ogre3d.org/forums/viewtopic.php?p=303763
https://ogre3d.atlassian.net/browse/OGRE-241
User avatar
lunkhound
Gremlin
Posts: 169
Joined: Sun Apr 29, 2012 1:03 am
Location: Santa Monica, California
x 19

Re: Concatenating Node transforms

Post by lunkhound »

I made an image to show how Ogre handles non-uniform scaling compared to how Blender does:
Nonuniform scaling in Blender vs Ogre
Nonuniform scaling in Blender vs Ogre
blender-test-scale.jpg (144 KiB) Viewed 8568 times
There are 4 screenshots here, the top two are without scaling (Blender on the left, Ogre on the right), the bottom two are with the z-axis of the root bone scaled by 2x.

This sort of "stretch" is commonly used in "toon" style animation. It doesn't fit this character, but you get the idea.
In Blender, the child bones of the root are all stretched in the same direction, which, when animated gives the character a "rubbery" look.

In Ogre, each bone is being scaled in the bone's local z-axis, so rather than making the legs and arms longer, they get thicker instead (because the character was rigged with the local y-axis being lengthwise along the bones). The results you get in Ogre from nonuniform scaling are highly dependent on how the character was rigged. The only way to get the same results in Ogre as you see in Blender is to have all of your bones oriented the same--so no rotations on any bones.

So basically, Ogre can't do "squash and stretch" toon-style animations. It also means that even if you just want to do a little bit of non-uniform scaling, like for a "breathing" animation on a single bone--something which is certainly possible in Ogre--the exporter (the Blender one at least), will generate some odd scaling for any child bones because of the difficulty in mapping scale transforms from Blender to Ogre. This last issue can be worked around--that's why the Node class has flags to disable scale inheritance--but if Ogre handled scale correctly, these workarounds wouldn't be needed.
User avatar
dark_sylinc
OGRE Team Member
OGRE Team Member
Posts: 5299
Joined: Sat Jul 21, 2007 4:55 pm
Location: Buenos Aires, Argentina
x 1279
Contact:

Re: Concatenating Node transforms

Post by dark_sylinc »

I'm thinking of fixing this issue (in Ogre 2.0) after it has been pointed out to me.

Ogre 2.0 DOES create a 4x4 matrix anyway right after manually doing all the concatenation stuff.
I'm starting to think we should keep mDerivedTransform, remove mDerivedPosition, mDerivedOrientation & mDerivedScale; construct a local 4x4 matrix based on local mPosition, mOrientation & mScale and concatenate that local matrix against parent's mDerivedTransform.

This will probably be faster because: a) 2.0 is SIMD, so 4x4 matrix concatenation isn't that bad; b) that exact function is memory bound rather than instruction bound (I profiled thoroughly, you can never be 100% sure, but I'm convinced it's memory bound), and removing three variables (40 bytes) per node should help with the bottleneck.

The question would be then... how do we provide retrieval of derived position, orientation & scale?
* Derived Position is rather easy, just extract Vector( m[3][0], m[3][1], m[3][2] )
* Derived Orientation won't work. But if there is no scale, we can extract the 3x3 matrix. (user can try Orthonormalize them? does anybody know if it actually works?)
* Derived Scale won't work as it used (it can't be represented with just a vector3). The user can extract the 3x3 matrix though. If the user is sure the applied scale is orthogonal, multiplying UNIT_SCALE against the 3x3 should return derived scale we're used to.

Other ideas? suggestions?
I can think of keep calculating the quaternion just to retrieve the derived orientation; that would be for informational purposes to the user, but would not serve a functional purpose inside the engine. Besides, if the matrix ends up skewed, an user operating with quaternions can end up having inconsistencies hard to find.

On the other hand, if we keep orientation, and scale as a separate 3x3 matrix just to inform the user in case he needs them, I want to oppose this approach as it will hurt memory bandwidth.
User avatar
dark_sylinc
OGRE Team Member
OGRE Team Member
Posts: 5299
Joined: Sat Jul 21, 2007 4:55 pm
Location: Buenos Aires, Argentina
x 1279
Contact:

Re: Concatenating Node transforms

Post by dark_sylinc »

Just to clarify, I'm not saying that we need to provide _getDerived* functionality.

Perhaps just providing _getDerivedPosition & _getFullTransform is well enough, and I'm looking it backwards: the most useful operation a user ever needs is _getDerivedPosition.
However the real problem is that if the user wants an independent node to clone another node without inheritance, a 1.9 user would do:

Code: Select all

myClone->setPosition( tracked->_getDerivedPosition() + offset );
myClone->setOrientation( tracked->_getDerivedOrientation() );
myClone->setScale( tracked->_getDerivedScale() );
Whereas it is impossible to achieve if we use a local position/quaternion/scale for local transform, and a full 4x4/4x3 matrix for holding derived transform.
AgentC
Kobold
Posts: 33
Joined: Tue Apr 24, 2012 11:24 am
x 5

Re: Concatenating Node transforms

Post by AgentC »

There may be some cases where you need to concatenate the derived orientation separately in addition to the full transform matrix.

Consider for example a "car" that has been made by scaling a box non-uniformly. Then you attach spotlights into its child nodes for the headlights. How to find the effective forward direction for the lights?

In my own engine I tried at first extracting the derived orientation from the full 4x4 matrix. But in the presence of non-uniform scaling that produced wrong results as the "car" rotated. Then I came to the conclusion (though I hated it) that I needed to concatenate rotation separately. If you try the same in eg. Unity you'll see that such non-uniformly parented spotlights would work flawlessly.
User avatar
dark_sylinc
OGRE Team Member
OGRE Team Member
Posts: 5299
Joined: Sat Jul 21, 2007 4:55 pm
Location: Buenos Aires, Argentina
x 1279
Contact:

Re: Concatenating Node transforms

Post by dark_sylinc »

On a second thought, turning off selectively orientation and scale inheritance is rather difficult, so other alternatives are appreciated.
It's starting to feel the only solution is to keep everything as is, but scale as a 3x3 matrix; which goes against memory budgets...
User avatar
lunkhound
Gremlin
Posts: 169
Joined: Sun Apr 29, 2012 1:03 am
Location: Santa Monica, California
x 19

Re: Concatenating Node transforms

Post by lunkhound »

AgentC wrote:There may be some cases where you need to concatenate the derived orientation separately in addition to the full transform matrix.

Consider for example a "car" that has been made by scaling a box non-uniformly. Then you attach spotlights into its child nodes for the headlights. How to find the effective forward direction for the lights?

In my own engine I tried at first extracting the derived orientation from the full 4x4 matrix. But in the presence of non-uniform scaling that produced wrong results as the "car" rotated. Then I came to the conclusion (though I hated it) that I needed to concatenate rotation separately. If you try the same in eg. Unity you'll see that such non-uniformly parented spotlights would work flawlessly.
If your matrix has skew in it, the very notion of *orientation* is out the window. Having an orientation quaternion implies that the axes of your matrix are orthogonal (which they aren't).

For the problem with the car and the spotlights, shouldn't you be able to get the full transform of the child node and just use one of the axes from the matrix (normalized)? That's if you just need a direction vector.

I think just keeping a matrix would be the best option. It's very simple and straightforward.
Perhaps have a convenience function _getDerivedOrientationIfOrthogonal() or something that tries to extract a quaternion assuming only uniform scaling.
dark_sylinc wrote:On a second thought, turning off selectively orientation and scale inheritance is rather difficult, so other alternatives are appreciated.
Do we need those flags? I'd say get rid of them.
User avatar
lunkhound
Gremlin
Posts: 169
Joined: Sun Apr 29, 2012 1:03 am
Location: Santa Monica, California
x 19

Re: Concatenating Node transforms

Post by lunkhound »

dark_sylinc wrote:On a second thought, turning off selectively orientation and scale inheritance is rather difficult, so other alternatives are appreciated.
It's starting to feel the only solution is to keep everything as is, but scale as a 3x3 matrix; which goes against memory budgets...
Also, it's pretty simple to remove the scale from a matrix, and removing rotation shouldn't be that bad either...

[edit] So there's 4 possible states these flags can be in:
1. inheriting both scale and rotation. Easy, just leave the parent's matrix alone and concatenate as usual.

2. inheriting neither scale nor rotation. Easy. Make a temporary copy of the parent's matrix, and set the 3x3 part to identity.

3. inheriting rotation, but not scale. Easy. Make a temp copy of parent's matrix and normalize the 3x3 rows to nix the scale.

4. inheriting scale but not rotation. Easy. Make a temp copy of parent's matrix and extract the scale by taking the magnitude of each of the 3x3 rows, then replace the 3x3 part with a diagonal matrix containing just the scale.

Case 1 is the most common case, and also the fastest.
User avatar
dark_sylinc
OGRE Team Member
OGRE Team Member
Posts: 5299
Joined: Sat Jul 21, 2007 4:55 pm
Location: Buenos Aires, Argentina
x 1279
Contact:

Re: Concatenating Node transforms

Post by dark_sylinc »

@lunkhound Thanks for shedding some light, non-uniform scaling is not my favourite area.

After a bit of experimenting w/ Blender, it looks like indeed that's how it's working.
I would see that the following changes would be needed:
Local:
  • mPosition stays as usual
  • mOrientation is turned into a 3x3 matrix, since Quaternions can't represent non-orthogonal rotations.
  • Optional: mScale is removed. Scale is calculated from the 3x3 matrix.
Derived:
  • mDerivedPosition, mDerivedOrientation, mDerivedScale are removed.
  • mDerivedTransform is converted from Matrix4 to ArrayMatrix4x3.
  • Probably we'll still have to keep a Matrix4 copy for the AoS calculations; called mDerivedTransformAoS
Functions:
  • _getDerivedPosition extracts the data from the 4x3 matrix, straightforward.
  • getOrientation/_getDerivedOrientation returns a Matrix3 instead of a Quaternion. May not be orthogonal. Requires a math to remove the scale factor.
  • getScale/_getDerivedScale needs additional math to get the Vector3 from the 3x3/4x3 matrix (9 muls, 6 adds, 3 sqrt). It's not used very often anyway though.
As for inheritance, doing branchless selection means we need to:
  • Extract the scale out of the 4x3 matrix.
  • Select between unit scale and extracted scale.
  • Select between identity 3x3 matrix and the orientation matrix.
  • Join scale and orientation again.
  • Concatenate.
Memory usage:
- 12 bytes (mDerivedPos)
- 16 bytes (mDerivedOrient)
- 12 bytes (mDerivedScale)
- 12 bytes (mScale, optional)
- 16 bytes (4x4 -> 4x3)
+ 20 bytes (mOrientation goes from Quaternion -> 3x3)
-----------------------
- 48 bytes subtotal
+ 64 bytes (Still need AoS version for Bounding Box update, RenderQueue, & API)
-----------------------
+ 16 bytes. (+28 if mScale wasn't removed)

So, memory footprint will be higher :(. On the plus side, we won't see the hit of the "extract->select->merge" process caused by the inheritance flags (removing inheritance flags is just 2 bytes; which still leaves at +14; nonetheless this is easy to test: Remove the inheritance code, benchmark, if tests take the same time, inheritance isn't affecting performance at all*).

[s]*If it turns out it does affect performance, then bones and nodes will be different (This feature is most needed by skeletal animations)[/s]

[s]Edit: The more I think about it, the more I'm getting convinced this path should be used for bones, while the older one for regular nodes.[/s]

Edit 2: Often we may want to keep local mScale separate, for example, when we're updating a node's position and orientation while keeping the scale; otherwise this would mean we would always have to extract the scale, and reapply to the new supplied orientation
User avatar
lunkhound
Gremlin
Posts: 169
Joined: Sun Apr 29, 2012 1:03 am
Location: Santa Monica, California
x 19

Re: Concatenating Node transforms

Post by lunkhound »

Earlier I had been thinking that the local-scale would need to be a matrix to support everything Blender can do, but looking closer at Blender that doesn't seem to be true. It looks like Blender represents the local-scale with just a vector. So perhaps we can also?

In other words, maybe our representation for local stuff doesn't need to change at all--just derived.

If we do that, then the scale-inheritance flag will definitely be useful for things like the breathing animation example.
User avatar
dark_sylinc
OGRE Team Member
OGRE Team Member
Posts: 5299
Joined: Sat Jul 21, 2007 4:55 pm
Location: Buenos Aires, Argentina
x 1279
Contact:

Re: Concatenating Node transforms

Post by dark_sylinc »

lunkhound wrote:In other words, maybe our representation for local stuff doesn't need to change at all--just derived.
Technically yes.
But I'm trying to leverage the following stuff:
  • Ability to copy derived's transform into a different node's transform (which can't be done if local stuff uses quaternions and the derived transform is non-orthogonal*)
  • Bones being able to interact with other Nodes (Bones being able to have Objects directly attached to them, as well as having regular SceneNodes as children)
  • Support for non-uniform scaling
  • Being able to toggle inheritance (in Blender 2.49 the options are called Hinge and S; in Blender 2.63 Inherit Scale & Inherit Orientation)
  • Performance
* Though I ask myself, do we really need that feature? (and does it matter performance wise?)
User avatar
lunkhound
Gremlin
Posts: 169
Joined: Sun Apr 29, 2012 1:03 am
Location: Santa Monica, California
x 19

Re: Concatenating Node transforms

Post by lunkhound »

dark_sylinc wrote:
lunkhound wrote:In other words, maybe our representation for local stuff doesn't need to change at all--just derived.
Technically yes.
But I'm trying to leverage the following stuff:
  • Ability to copy derived's transform into a different node's transform (which can't be done if local stuff uses quaternions and the derived transform is non-orthogonal*)
  • Bones being able to interact with other Nodes (Bones being able to have Objects directly attached to them, as well as having regular SceneNodes as children)
  • Support for non-uniform scaling
  • Being able to toggle inheritance (in Blender 2.49 the options are called Hinge and S; in Blender 2.63 Inherit Scale & Inherit Orientation)
  • Performance
* Though I ask myself, do we really need that feature? (and does it matter performance wise?)
I just thought perhaps it would be easier or be better performance-wise if the local transform stayed the same as before. I've been going back and for in my head on this one, but now I'm favoring the local as a matrix again--I'll bet there are some cool ways to use shear transforms to make fields of grass move like it's blowing in the wind. The shear is perfect for this because you can keep the roots of the grass attached to the ground and have the tops moving around a bit.
User avatar
Kojack
OGRE Moderator
OGRE Moderator
Posts: 7157
Joined: Sun Jan 25, 2004 7:35 am
Location: Brisbane, Australia
x 534

Re: Concatenating Node transforms

Post by Kojack »

lunkhound wrote:I'll bet there are some cool ways to use shear transforms to make fields of grass move like it's blowing in the wind. The shear is perfect for this because you can keep the roots of the grass attached to the ground and have the tops moving around a bit.
That's already very easily in ogre, just apply a shear to the world matrix in the vertex shader.
dark_sylinc wrote:Perhaps just providing _getDerivedPosition & _getFullTransform is well enough, and I'm looking it backwards: the most useful operation a user ever needs is _getDerivedPosition.
The inverse of the _getDerivedOrientation of the parent is quite handy when you want to set the world orientation of a child node. (and inverse of a quaternion is way faster than inverse of a matrix, even an orthogonal pure rotation one)
_getDerivedOrientation is used around 48 times throughout ogre in ways such as that for particles, billboards, ribbontrails, etc.


In the current ogre style, child bones won't stretch and skew as they rotate when the parent node has been non uniformly scaled. The child will be consistent, with the parent's non uniform scale inherited as a child local space scale. There are situations where that is a good thing.
dark_sylinc wrote:mOrientation is turned into a 3x3 matrix, since Quaternions can't represent non-orthogonal rotations.
Optional: mScale is removed. Scale is calculated from the 3x3 matrix.
Negative scales would have to be prohibited.
[[-2 0 0] [0 -2 0] [0 0 2]] could be a matrix scaled by 2,2,2 and rotated 180 degrees around z, or it could be a matrix scaled by -2,-2,2 and not rotated. Same end result, extremely different if used for interpolating animation. A quaternion and scale vector don't have that problem.
User avatar
dark_sylinc
OGRE Team Member
OGRE Team Member
Posts: 5299
Joined: Sat Jul 21, 2007 4:55 pm
Location: Buenos Aires, Argentina
x 1279
Contact:

Re: Concatenating Node transforms

Post by dark_sylinc »

Kojack has made very strong arguments, and personally I'm in the battle between the little angel and the little devil.

It's quite clear to me now we don't want shearing/skewing in Nodes (or rather, we don't want to loose _getDerivedOrientation as a Quaternion).

Now, the problem is that skeleton scaling doesn't follow that of authoring tools.
If you disable scale inheritance and stick to local scaling (in Blender to scale around local X axis, press S, then the X twice) Ogre and Blender will match (however the skeleton file doesn't saving the inheritance flags, which means the file format needs an upgrade).
If you scale around XYZ at the same time, Ogre & Blender will match.
For all other cases, they won't match.

Now, supporting non-uniform scaling for Skeletons would mean the internal data representation of the Bones has to change. But this also mean that Nodes will not understand how to be child nor parent of a Bone (something I desperately want because of the plans I have for the new animation system).
User avatar
lunkhound
Gremlin
Posts: 169
Joined: Sun Apr 29, 2012 1:03 am
Location: Santa Monica, California
x 19

Re: Concatenating Node transforms

Post by lunkhound »

Kojack wrote:
lunkhound wrote:I'll bet there are some cool ways to use shear transforms to make fields of grass move like it's blowing in the wind. The shear is perfect for this because you can keep the roots of the grass attached to the ground and have the tops moving around a bit.
That's already very easily in ogre, just apply a shear to the world matrix in the vertex shader.
Well it would be considerably easier if you can just inject the shear directly into the local transform and not have to use a special vertex shader.
Kojack wrote:The inverse of the _getDerivedOrientation of the parent is quite handy when you want to set the world orientation of a child node. (and inverse of a quaternion is way faster than inverse of a matrix, even an orthogonal pure rotation one)
_getDerivedOrientation is used around 48 times throughout ogre in ways such as that for particles, billboards, ribbontrails, etc.
If you disable inherit rotation on the node, you can just set the world orientation directly in the local transform. No need for taking the inverse.
Kojack wrote:In the current ogre style, child bones won't stretch and skew as they rotate when the parent node has been non uniformly scaled. The child will be consistent, with the parent's non uniform scale inherited as a child local space scale. There are situations where that is a good thing.
Getting the children to stretch and skew properly is the main motivation for this. However you can always disable inherit scale on all of the bones and propagate the local scale manually to get it working like it is now. Although I can't really think of an example of where that is useful. (see screenshot earlier in the thread).
Kojack wrote:Negative scales would have to be prohibited.
[[-2 0 0] [0 -2 0] [0 0 2]] could be a matrix scaled by 2,2,2 and rotated 180 degrees around z, or it could be a matrix scaled by -2,-2,2 and not rotated. Same end result, extremely different if used for interpolating animation. A quaternion and scale vector don't have that problem.
This is true. I think animation keyframes will need to keep rotations as quaternions for this reason.
User avatar
dark_sylinc
OGRE Team Member
OGRE Team Member
Posts: 5299
Joined: Sat Jul 21, 2007 4:55 pm
Location: Buenos Aires, Argentina
x 1279
Contact:

Re: Concatenating Node transforms

Post by dark_sylinc »

lunkhound wrote: If you disable inherit rotation on the node, you can just set the world orientation directly in the local transform. No need for taking the inverse.
He wasn't talking about having a child node that reverses orientation. He was talking, for example, that to in order to send the eye vector to the vertex shader in object-space (which is very useful), it has to be multiplied by the inverse orientation.
Same happens with some particle & billboard calculations where some math is done in object space or the particle needs to undo the node's orientation without creating an additional node.
User avatar
Kojack
OGRE Moderator
OGRE Moderator
Posts: 7157
Joined: Sun Jan 25, 2004 7:35 am
Location: Brisbane, Australia
x 534

Re: Concatenating Node transforms

Post by Kojack »

Havok Animation 2013, (very powerful skeleton animation system used in various AAA games such as Skyrim, Bioshock, The Last Of Us, etc), says the following in it's docs:
In Havok Animation transformations are stored with a specialized representation, hkQsTransform[5]. hkQsTransforms have the following properties:
They explicitly store three components: translation, rotation and scale.
Translation is stored as an hkVector4.
Rotation is stored as an hkQuaternion.
Scale is stored as an hkVector4.

hkQsTransforms can represent any orthogonal transformation, i.e., any transformation that does not involve skewing. The reason for that is that non-orthogonal transformations cannot be represented by a concatenation of translation, rotation and scale operations.
and
2.3.1.2. Scale
Unfortunately, when introducing scale, the parallelism with matrices ends. This is because, if we consider the Translation, Rotation and Scale components to be matrices multiplied one after the other, then our result won't always be decomposable as an hkQsTransform; in other words, we would need a hkQsTransform C such as:
C = Tc * Rc * Sc = A * B = (Ta * Ra * Sa) * (Tb * Rb * Sb)
The problem is that, in the general case, it is not possible to find the translation, rotation and scale matrices Tc, Rc and Sc that satisfy the equation above. This is because concatenating rotations and scale operations can produce skewing, which yields a non-orthogonal transform, not representable with hkQsTransform.

So we need to find an alternative interpretation of the multiplication operation which keeps the results inside the space of orthogonal transforms. To do so, we define the resulting scale component of multiplying two hkQsTransforms A and B as:
Scale(C).x = Scale(A).x * Scale(B).x
Scale(C).y = Scale(A).y * Scale(B).y
Scale(C).z = Scale(A).x * Scale(B).z

That is, we define the resulting scale vector as the component-wise multiplication of the scale vector. By doing this we ensure that our multiplication operation works consistently, keeps the results inside the space of valid hkQsTransforms, and follows the expected properties of a transform multiplication operation.
and
In particular, many modelling tools allow the use of skewed transforms, since they use 4×4 matrix-based transformations. The Havok Animation Toolchain will always warn when performing conversions to hkQsTransform where the original 4×4 transform contains skew (as that skew will be lost).
I guess our "broken" bone system is in good company. :)
scrawl
OGRE Expert User
OGRE Expert User
Posts: 1119
Joined: Sat Jan 01, 2011 7:57 pm
x 216

Re: Concatenating Node transforms

Post by scrawl »

I guess our "broken" bone system is in good company.
Keep in mind its not just the animation system that's broken when using nonuniform scale. On Scene nodes it also doesn't work properly.
In particular, in the data set I have to work with, I have a perfectly normal skeletal animation with uniform scales, but for each character there's a "weight" property to make them fatter, which basically scales the whole skeleton on the x-axis only. What I am trying to do is to apply this scale to the scene node. Unfortunately Ogre treats the scale as being in bone-local space, so instead of getting wider the arms (which happen to have a rotated bone) get longer.
User avatar
dark_sylinc
OGRE Team Member
OGRE Team Member
Posts: 5299
Joined: Sat Jul 21, 2007 4:55 pm
Location: Buenos Aires, Argentina
x 1279
Contact:

Re: Concatenating Node transforms

Post by dark_sylinc »

scrawl wrote:
I guess our "broken" bone system is in good company.
Keep in mind its not just the animation system that's broken when using nonuniform scale. On Scene nodes it also doesn't work properly.
In particular, in the data set I have to work with, I have a perfectly normal skeletal animation with uniform scales, but for each character there's a "weight" property to make them fatter, which basically scales the whole skeleton on the x-axis only. What I am trying to do is to apply this scale to the scene node. Unfortunately Ogre treats the scale as being in bone-local space, so instead of getting wider the arms (which happen to have a rotated bone) get longer.
This is valid, expected, and intended behavior (and would be the same the Havok Animation SDK delivers). It just doesn't match what you particularly want to do.
How are you doing this?
scrawl
OGRE Expert User
OGRE Expert User
Posts: 1119
Joined: Sat Jan 01, 2011 7:57 pm
x 216

Re: Concatenating Node transforms

Post by scrawl »

I'm applying the non-uniform scale to a scene node.
To the scene node, I'm attaching a skinned Entity, which is made from an empty mesh (it's just there to serve as a skeleton base)
The actual parts (arms, etc) are separate entities attached to a bone of the skeleton base (via a tag point), so that it's possible to replace them with clothing etc.
expected
Not sure why that would be expected. It surely isn't logical.
Also, the documentation for Node::setScale doesn't mention any of this.
Last edited by scrawl on Thu Nov 28, 2013 6:43 pm, edited 1 time in total.
User avatar
dark_sylinc
OGRE Team Member
OGRE Team Member
Posts: 5299
Joined: Sat Jul 21, 2007 4:55 pm
Location: Buenos Aires, Argentina
x 1279
Contact:

Re: Concatenating Node transforms

Post by dark_sylinc »

scrawl wrote:The actual parts (arms, etc) are separate entities attached to a bone of the skeleton base (via a tag point)
You said the magic word. TagPoints suck. I'm rethinking them for 2.0
scrawl
OGRE Expert User
OGRE Expert User
Posts: 1119
Joined: Sat Jan 01, 2011 7:57 pm
x 216

Re: Concatenating Node transforms

Post by scrawl »

Ah nice. Are you suggesting that my use case would be fixed with the redesign?
User avatar
dark_sylinc
OGRE Team Member
OGRE Team Member
Posts: 5299
Joined: Sat Jul 21, 2007 4:55 pm
Location: Buenos Aires, Argentina
x 1279
Contact:

Re: Concatenating Node transforms

Post by dark_sylinc »

scrawl wrote:Ah nice. Are you suggesting that my use case would be fixed with the redesign?
I can't promise.

But I've been thinking hard about the issue, and realized that your TagPoint thingy is A BUG:
In TagPoint::updateFromParentImpl:

Code: Select all

// Include Entity transform
if (mParentEntity)
The concatenation of the tag point with the entity's transform is inconsistent with Bones: Bones generate their matrices in local space and then concatenate the entity's Matrix4 with the bone's; so that they end up in world space and using the expected scale from the parent's scene node.
However, TagPoint instead of concatenating matrices, uses the PQS approach (Pos Quat. Scale) and hence the tag point is inconsistent (though if you plan to have a node as child of that TagPoint, you're pushing it, it won't work).
User avatar
lunkhound
Gremlin
Posts: 169
Joined: Sun Apr 29, 2012 1:03 am
Location: Santa Monica, California
x 19

Re: Concatenating Node transforms

Post by lunkhound »

Kojack wrote:Havok Animation 2013, (very powerful skeleton animation system used in various AAA games such as Skyrim, Bioshock, The Last Of Us, etc), says the following in it's docs:
In Havok Animation transformations are stored with a specialized representation, hkQsTransform[5]. hkQsTransforms have the following properties:
They explicitly store three components: translation, rotation and scale.
Translation is stored as an hkVector4.
Rotation is stored as an hkQuaternion.
Scale is stored as an hkVector4.

hkQsTransforms can represent any orthogonal transformation, i.e., any transformation that does not involve skewing. The reason for that is that non-orthogonal transformations cannot be represented by a concatenation of translation, rotation and scale operations.
and
2.3.1.2. Scale
Unfortunately, when introducing scale, the parallelism with matrices ends. This is because, if we consider the Translation, Rotation and Scale components to be matrices multiplied one after the other, then our result won't always be decomposable as an hkQsTransform; in other words, we would need a hkQsTransform C such as:
C = Tc * Rc * Sc = A * B = (Ta * Ra * Sa) * (Tb * Rb * Sb)
The problem is that, in the general case, it is not possible to find the translation, rotation and scale matrices Tc, Rc and Sc that satisfy the equation above. This is because concatenating rotations and scale operations can produce skewing, which yields a non-orthogonal transform, not representable with hkQsTransform.

So we need to find an alternative interpretation of the multiplication operation which keeps the results inside the space of orthogonal transforms. To do so, we define the resulting scale component of multiplying two hkQsTransforms A and B as:
Scale(C).x = Scale(A).x * Scale(B).x
Scale(C).y = Scale(A).y * Scale(B).y
Scale(C).z = Scale(A).x * Scale(B).z

That is, we define the resulting scale vector as the component-wise multiplication of the scale vector. By doing this we ensure that our multiplication operation works consistently, keeps the results inside the space of valid hkQsTransforms, and follows the expected properties of a transform multiplication operation.
and
In particular, many modelling tools allow the use of skewed transforms, since they use 4×4 matrix-based transformations. The Havok Animation Toolchain will always warn when performing conversions to hkQsTransform where the original 4×4 transform contains skew (as that skew will be lost).
I guess our "broken" bone system is in good company. :)
Interesting. But I don't see anything which indicates that the hkQsTransform is what they use for world transforms. Looking at the video featured here http://havok.com/products/animation, I see lots of toon-style "squash and stretch" animation, which isn't mathematically possible if you force world transforms to be always orthogonal. Therefore, they must not be using hkQsTransform for world transforms. I would assume they use that form for animation keyframes and for local transforms.
As I said before, I think using the quaternion form for local transforms would be fine, and it absolutely makes sense for keyframe data to be quaternion based. Neither of those would break toon-style animation.

Having a quaternion based world transform (as Ogre currently does) DOES break toon-style animation:
stretch-dude.jpg
stretch-dude.jpg (127.61 KiB) Viewed 8402 times
If you check the Havok video at about 29 seconds in you'll notice there's a clip of Mickey Mouse and some bouncing green bunnies that are stretching and squashing, so I really doubt Havok Animation is broken in that way.
Post Reply