• Advertisement
Sign in to follow this  

Design: Object transformation

This topic is 2133 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

Hello,

in my 3D Engine I have a class SceneObject for scene objects. Now I wonder whats a good solution to handle the positioning/orientation of such objects.

Currently the class has three Vector3 members: mScale, mRotate, mTranslate with according setters/getters like setScale(), setRotate() etc.
It also has a method getWorldMatrix(), with computes the final world matrix (if dirty) like this:
Matrix4 worldMat = scaleMat * xRotationMat * yRotationMat * zRotationMat * translationMat;

The 5 matrices are build from the data vectors mScale, mRotate and mTranslate and as you can see the order is always S*R*T.

Do you think this design is good? Should I stick with this fixed S*R*T order or should I switch to a more general interface like setWorldMatrix(const Matrix& m) to let the user set an arbitrary matrix?

Share this post


Link to post
Share on other sites
Advertisement
Storing your transformations decomposed is a good idea, because it's much easier to modify the individual components. For example, if you store just the composed matrix, it is very expensive to get the scale or rotation values out. Also, the composed matrix will detoriate if you de- and recompose it frequently.

However, storing 5 matrices like you suggest is wasteful and error prone. Especially the rotation matrices, because they'll suffer from gimbal lock and you use certain degrees of freedom.

Usually decomposed transformations consist of these:
Vector3 translation;
Quaternion orientation;
Vector3 scaling; or alternatively a Matrix3 if you want to have shearing also.

I'd also recommend to wrap these decomposed transform in a class for convenient reuse and operators.

Share this post


Link to post
Share on other sites
Well, people use scaling to change the size of objects, for effects like animation or better reusability.

But it's a good idea to stay away from scaling as long as possible, because it introduces many subtle difficulties. For instance, normals of objects are no longer normalized after uniform scaling (same value on all 3 axes) and even distorted after non-uniform scaling.

Share this post


Link to post
Share on other sites

But it's a good idea to stay away from scaling as long as possible, because it introduces many subtle difficulties. For instance, normals of objects are no longer normalized after uniform scaling (same value on all 3 axes) and even distorted after non-uniform scaling.


If you need to transform a normal vector n under a general linear map from R^3 to R^3 with matrix M, you can compute

transpose(inverse(M)) * n

and then renormalize. In the case of rotations, transpose(inverse(M)) = M, plus you don't need to renormalize, so things are a bit easier.

Share this post


Link to post
Share on other sites
Since your objects are stored at the origin I recently worked with a group that did it a bit differently.

You define a constant forward,right,left,up vectors up = <0,1,0,1> down = <0,-1,0,1> forward = <0,0,1,1> right = <1,0,0,1> left = <-1,0,0,1> To get your "forward" or "down" vector it was just a single matrix multiplication away. Just make sure your "forward" of your model is facing <0,0,1,1> and the rest is easy.

There was than 1 matrix for local rotations and scaling, 1 World Matrix where the bottom 4 floats were the position, 1 vector for recording scale so you can use an absolute scale with models.

Was a bit weird but was pretty easy to be honest.

Share this post


Link to post
Share on other sites
I don't understand what you use those "vectors" for or why they have a last coordinate 1 instead of 0... I am trying to fit what you are saying into the relevant math I know, and it's not working.

Share this post


Link to post
Share on other sites

I don't understand what you use those "vectors" for or why they have a last coordinate 1 instead of 0... I am trying to fit what you are saying into the relevant math I know, and it's not working.


Model is centered at <0,0,0,1> 4-D vector or the origin.

So x-axis is <1,0,0,1> and z-axis is <0,0,1,1,>. We arbitrarily define a given vector as "forward" or "backward" or "up" based on which way we preferred. The result is that if you want the "forward" of a model you can just take the "forward" vector and multiply it by the world matrix of a model and get the direction it's facing.

Really 99% of the time we wanted rotation information from the model it was which way is "forward" or "up" or "down" or "left" so we would take the vector denoting the default forward and multiply it by the models world matrix to get which way it was facing. So before model->world transformation we know that forward is the same as the "forward" vector constant. So which way it is facing now is "forward" * WorldMatrix.

Object transformations were simply handled by augmenting this matrix. So you want to rotate it along the Zaxis? Call matrix.RotateZ(float angle) and rotate the object. Want to Translate it? Call matrix.Translate(x,y,z). Was pretty easy overall.

This one matrix inherently had all the information we needed readily available so we just used that.

[ stuff stuff stuff 0]
[stuff stuff stuff 0]
[stuff stuff stuff 0]
[x-pos, y-pos, z-pos 1]

The only objects we needed that needed to know exact angles was the camera which was handled with a Quaternion.

i really only use quaternion's interpolating rotation based animations, for character movement and whatnot matrices are easier imo. . Want the object to slowly move some direction? Get the vector pointing to the object and slerp all day.

Share this post


Link to post
Share on other sites

I see. We use language differently. I think of [1:0:0:1] being a point, not a vector.

You are correct. The W member is defined with a very specific meaning. It is the multiplier for any translation values, so 1 is full translation (a point), 0 removes all translation (a normal/direction), and any value between provides that much of the intended translation.

But in game programming speed is more important than sticking to standards so there are a plethora of optimizations that ignore the W component for vectors and easily allow people to falsely believe that the W component doesn’t matter or has no meaning.

His team should change the W to 0 in order to conform to standards. The way he describes it being used can only work properly if said optimizations (ignoring W completely because you know it is a normal, not a point) are in place. For example, multiplying <0, 0, 1, 1> with the object’s matrix only gives you the correct “the object is facing this way” result if W is ignored in the matrix multiply. If actual non-optimized matrix multiplication is applied, it would tell you with direction from the center of the world the model is, not the direction it is facing. Correct results could only come from a vector <0, 0, 1, 0>.

As an optimization hint to ChaseRLewis, if you wanted the direction the model is facing, it would be faster to just read the appropriate row or column (depending on whether you are row-major or column-major) straight out of the matrix instead of doing matrix multiplication. Apparently it is important to your team to be able to change which axis is up, right, and forward, so macros allow you to easily also assign indices that would allow you to read the appropriate rows/columns whenever you decide to change orientations.


As for the original question at hand, I would heavily suggest not embedding any orientation information directly in your “SceneObject” class, your “Actor”, or any other class than one named “Orientation”. You should definitely make a class whose role is nothing but managing orientations. It becomes reusable in many places and certainly helps keep your code clean, because you will soon discover that you need dirty flags and a lot of other hidden features to make it optimized.

I personally maintain a CVector3 position, CVector3 scale, and CVector3[3] rotation. Compared to using a quaternion, there is extra work involved in rotating things in some ways, but far less work involved in creating the final matrix (which you only do if the dirty flag is set). This is the method used in Halo and over the years it has a very good track record in my opinion. I have used it for many released games that have good performance even with lots of rotating objects, even on low-end devices such as iPhone 3GS.
Basically, there are only a few kinds of rotations that are more expensive than using quaternions (because a lot of rotations involve nothing more than a few dot products in this format). A lot of common rotations, such as rotating around a given axis, will be more expensive with quaternions. Using 3 vectors for rotation matches matrix layouts, so any rotation you can make with a rotation, say around a given axis, translates into nothing but a copy when you pass that translation matrix to the “Orientation” class.
Matrix math offers a lot of shortcuts, such as rotating around a world axis (for example the Z axis). Rotating around any random axis with quaternions is roughly the same speed as with matrices (or slower), and rotating around world axes is not as fast as with matrices thanks to it resolving to only a sin()/cos() pair or a single sincos() call.

This makes it the overall best way to go at the cost of a little extra memory.


L. Spiro

Share this post


Link to post
Share on other sites

I personally maintain a CVector3 position, CVector3 scale, and CVector3[3] rotation.

How do you compose two such objects? Or do you only allow composition if the second transformation doesn't change the scale?

Share this post


Link to post
Share on other sites
Define what you mean by “compose”. I am assuming it means basing one object’s transformation off another, like in a parent-child situation, but it is almost midnight here and I am not so clear-minded right now.


L. Spiro

Share this post


Link to post
Share on other sites
A transformation is a mapping from 3-dimensional affine space into itself. The composition of two mappings f ang g is defined as

(f*g)(x) = f(g(x))

So if you take a point x, you first apply g to it and then f, you should get the same result as if you had done it in one step (f*g).

And yes, you can use composition to describe articulated models where the transformation of the child needs to be considered on top of the transformation of the parent.

The composition of two rotation-and-translation transformations is another rotation-and-translation transformation, but the composition of two scaling-and-rotation-and-translation transformations is not necessarily a scaling-and-rotation-and-translation transformation. So my question is: How do you handle that? Or do you somehow restrict yourself when you compose transformations so the second one cannot have scaling in it?

Share this post


Link to post
Share on other sites
Still late for me but if I guess correctly from the reading resources you provided and typed (and mind you I am self-taught, which is why certain terms can escape me even if the concept does not (and I am also a musician which is why this term specifically is hard for me to remember in the technical sense)) then it is really no big problem.

Currently I am imagining (and feel free to correct me if I am wrong given my sleepiness) that under normal circumstances you would have an arm holding a gun and that arm could swell by a disease, but without special care the gun could swell as well.

As far as I am concerned, by default all transformations that affect a parent should affect children, which includes scaling, so the simple act of cascading down parent world transforms effectively handles this.
However I employ one of the settings in Maya called, “Inherits Transform”, which is a boolean switch that does what it suggests. If not enabled, its coordinates are not local to its parent. Its implementation is simple because I use 2 matrices for all objects.

I keep a COrientation object as mentioned which handles the creation of a matrix from the given position, scale, and rotation members it manages.
It generates for me a matrix that represents the data it holds.

By assuming that data is always local transform, things work intuitively. Basically anything I get from that object is local, and if there is a parent I need to multiply with that parent’s world matrix to get my own final world matrix. Dirty flags cascade up and down here but in practice it really is efficient.

If an object is not set to inherit the parent’s transform, it simply does not ask for parent transform information when a world transform is requested. It just copies the local transform over and returns that.

You are specifically asking about scaling.
With my system as it is, you would need a special flag to indicate that rotation and position are inherited, but scaling is not. And that would be as simple as normalizing the rotation rows or columns of the parent world matrix, multiplied by the scale of the local “scale” member (which will probably be 1 so can be omitted).

This would be trivial to implement and fairly low overhead because only a few objects in any scene would ever use it. The only case I could imagine would be a swelling hand holding onto a gun that needs to point the same way as the hand and be at the same position, bot not swell (scale up). I wouldn’t worry about the extra steps to handle this since they would be very rare in practice.


L. Spiro

Share this post


Link to post
Share on other sites
Actually, even scaling is not a problem, as long as it is uniform in all directions (like in your swelling example). The problem arises with transformations that scale different axes differently. When you combine them with rotations, you can produce shear mappings, which cannot be expressed with a single scaling-rotation-translation transformation.

By the way, I tend to link to Wikipedia when I introduce terms that people here might not be familiar with. I don't know if it comes out as me giving homework or anything, but I am just trying to be more clear.

Share this post


Link to post
Share on other sites
Non-uniform scaling is also no problem, but shearing is. I don’t support shearing.
Combining non-uniform scales with rotation doesn’t cause shearing though. It just causes the body to get fat one way, and then it is rotated about and then translated. The results then carry down to children normally, causing them to also get fat in the rotated direction of the parent’s fatness.

It doesn’t come off as giving homework, at least to me.


L. Spiro

Share this post


Link to post
Share on other sites
The problems happen if you mix scalings with rotations like this:

scaling_A:
1/cos(pi/8) 0
0 sqrt(2)/cos(pi/8)

rotation_B (rotate 45 degrees clockwise):
sqrt(1/2) sqrt(1/2)
-sqrt(1/2) sqrt(1.2)

scaling_C:
(1+sqrt(2))/2 0
0 1/2

rotation_D (rotate 22.5 degrees counterclockwise):
cos(pi/8) -sin(pi/8)
sin(pi/8) cos(pi/8)

Now the product rotation_D * scaling_C * rotation_B * scaling_A is the matrix
1 1
0 1
So clearly, you can achieve shearing.

If you restrict the non-uniform scaling to only happen at the beginning, you are in good shape. That's why I asked if you restrict composition to transformations in which the second transformation doesn't have a non-uniform scaling.

Share this post


Link to post
Share on other sites
Sorry, I meant that shearing is not possible within only one matrix transform, which is basically what you had stated earlier.
You presented a case in which shearing can occur in children, but it is the natural result of that kind of matrix propagation, so my philosophy is that that is what the user gets for doing that. Detecting non-uniform scales and then counting how far they propagate would be too slow, especially given that it is very rare.
So I just combine and if this happens, the user him- or her- self has the choice of how to handle it (or to not).


L. Spiro

Share this post


Link to post
Share on other sites

Sorry, I meant that shearing is not possible within only one matrix transform, which is basically what you had stated earlier.

I didn't say that.

Share this post


Link to post
Share on other sites
I am referring to this:

which cannot be expressed with a single scaling-rotation-translation transformation.

Your wording was more accurate, but this is what I meant.


L. Spiro

Share this post


Link to post
Share on other sites
Sign in to follow this  

  • Advertisement