Design: Object transformation

Started by
18 comments, last by L. Spiro 12 years ago
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?
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.
I use a type composed by a translation (vector) and a rotation (quaternion), which together describe a direct isometry (a.k.a. "rigid motion").

Since rigid bodies don't normally change in size, I am not sure what people use the scaling matrix for. Can anyone provide examples?
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.

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.
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.
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.

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.
I see. We use language differently. I think of [1:0:0:1] being a point, not a vector.

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

I restore Nintendo 64 video-game OST’s into HD! https://www.youtube.com/channel/UCCtX_wedtZ5BoyQBXEhnVZw/playlists?view=1&sort=lad&flow=grid

This topic is closed to new replies.

Advertisement