4. Applying Scaling Transformations
The last of the transformations that we will be
working with for the time being is for scaling the objects that we
render. Scaling matrices can be either uniform, in which case the object scales by the same amount on all three axes; or non-uniform, in which case each axis scales by a different amount.
Figure 5
shows an object in its identity location on the left, with a uniform
scale of 2.0 in the middle, and then on the right with a scale of 4.0
on the x axis and 0.5 on the y axis.
To obtain a scaling matrix, call the static Matrix.CreateScale function. It has three calling methods: it can be passed a single float to perform a uniform scale, it can be passed three floats to perform a non-uniform scale with the values provided for the x, y, and z axes; or it can be passed a Vector3 containing the values for non-uniform scaling.
Passing a scale value of 0 for any of the axes will
squash the shape on that axis so that it is completely flat. It can be
easy to accidentally pass this when you had intended to leave the
scaling unchanged for an axis; for any axis that you want to leave
unchanged when scaling, pass a value of 1.
Negative scale values are also permitted. These will
cause the object to flip over so that the vertices appear on the
opposite side of the negatively scaled axis.
5. Applying Multiple Transformations
To apply a single transformation to our objects, we can simply obtain the required matrix and set it into the effect's World matrix property. We have already seen several examples of this.
Any practical application of matrix transformations
will quickly find that setting the matrix for just a single
transformation is insufficient, however. If we need to perform multiple
transformations at once (for example, perhaps we need to move the
object to another point within the world and then rotate it), we need
some way to combine these transformations together.
Fortunately, matrix transformations are perfectly
suited to this task. We can combine two or more transformations by
simply multiplying the matrices together. The resulting matrix will
contain the effects of both of the input matrices.
The different types of translation can have an
effect on each other that might not at first be obvious, so let's first
look at the effects of applying multiple transformations. We will then
come back to look at how they are implemented in code.
5.1. Rotating Objects
When we rotate an object, we actually rotate its
entire coordinate system because we are transforming the entire world,
not just the object itself. If the identity matrix is loaded, the world
coordinates are reset so that the origin point (0, 0, 0) is in the
center, and no scaling or rotation is applied. Once we begin to
transform this matrix, the coordinate system moves around accordingly.
Objects always move relative to the transformed
coordinate system, not to the identity coordinate system. This means
that, if we rotate an object by 45 degrees around the z axis and then
translate it along the y axis, it will actually move diagonally
onscreen rather than vertically. The rotation has changed the direction
of the axes within the world coordinate system.
The effects of this rotation can be seen in Figure 6.
On the left is the usual unit square at the identity position. In the
middle, we rotate it by 45 degrees around the z axis
(counterclockwise). The pale lines show the x and y axes in the
identity coordinate system, whereas the darker diagonal lines show the
x and y axes for the transformed world coordinate system. On the right,
we translate it along its y axis. Observe that it has moved diagonally
relative to the identity coordinates, though it has followed the
transformed world y axis. Also note that the world coordinate system
follows the translation, too. The coordinate (0, 0, 0) moves along with
the translations that are applied to the world matrix.
This sequence of updates brings us to another
important feature of matrix transformations: the order in which they
are applied is significant. In Figure 5,
we first rotated and then translated our object. If we instead
translate and then rotate it, the coordinate system for the translation
would still be aligned with theidentity coordinate system, so the
movement on the screen would be vertical. This is shown in Figure 7, which contains exactly the same transformations but performed with the translation before the rotation.
As you can see, the object ends up in a different place if we translate it first.
This is actually very easy to visualize. Imagine
that you are standing in place of the square object in these diagrams.
You are initially standing at the origin of the identity coordinate
system, looking along the positive y axis (up the screen).
You then decide to rotate 45 degrees counterclockwise, just as in Figure 5.
You are still facing straight ahead of your body, but relative to the
identity coordinates, you are now looking diagonally. If you now take a
few paces forward, you are walking diagonally in terms of the identity
coordinates but straight ahead in terms of your own position within the
world.
If you hold your arms out to the sides, they will be
pointing along the x axis relative to your position but are once again
at a diagonal angle relative to the identity coordinates.
Any time you want to visualize the transformations
that you are applying, think of this same scenario and apply each
transformation in sequence to yourself in the world. It should then be
easy to see the sequence of transformations that you need to apply to
get from one place to another. (It gets slightly harder to visualize in
three dimensions, but just imagine you have wings or a jet-pack.)
Hopefully, this makes the effects of cumulative
transformations clear. Always remember that, when you transform an
object, the transformation will be relative to the existing transformed
coordinates, not to those of the identity coordinate system.
5.2. Scaling Objects
When we scale an object, the transformation once
again has an effect on the world coordinate system. If we scale an
object so that its size doubles, a movement of one unit in the x axis
in the transformed world coordinate system will correspond to a
movement of two units relative to the identity coordinate system.
If you simply want to draw an object at a different
size but without affecting its position, remember to perform the scale
transformation after all the translations have been completed to avoid
affecting the movement distances.
5.3. Applying Multiple Transformations in XNA
So we know that multiple transformations can be
combined by multiplying them together, so let's see some sample code to
achieve this in XNA.
The first transformation that we want to use can be obtained directly by calling the appropriate static Matrix function. From that point on, subsequent transformations must be obtained and multiplied by the existing calculated matrix.
To translate an object two units along the y axis and then rotate it by a specified angle, we would use the code shown in Listing 2. It causes the object to rotate on the spot a short distance away from the center of the screen.
Example 2. Multiple transformations: translation and then rotation
// First translate... _effect.World = Matrix.CreateTranslation(0, 2, 0); //...then rotate _effect.World = Matrix.CreateRotationZ(_angle) * _effect.World;
|
Notice the order of multiplication: the new
transformation is on the left of the multiplication symbol, and the
existing matrix is on the right. Unlike multiplication of simple
numbers, matrix multiplication is not commutative, which is why the
order of transformations is significant. If we multiply matrix A by
matrix B, we will get results different from multiplying matrix B by
matrix A.
We can swap the order of these transformations so
that we first rotate and then translate along the (rotated) y axis, as
shown in Listing 3.
Example 3. Multiple transformations: rotation and then translation
// First rotate... _effect.World = Matrix.CreateRotationZ(_angle); //...then rotate _effect.World = Matrix.CreateTranslation(0, 2, 0) * _effect.World;
|
Even though we are generating the same matrices with
the same parameter, the resulting behavior is different. Instead of
spinning on the spot, the object now rotates around a circular path,
centered at the identity origin and with a radius of two units (because
this is the distance that the object was translated).
Try plugging each of them into the ColoredSquare project in place of the existing matrix code to see their effects.
We are not limited to using transformation types
just once within a transformation sequence, of course, and some
movement paths will require the same transformation to be applied
repeatedly at different stages of the calculation.
For example, let's get the object to trace a circle as in Listing 3,
but this time the circle will be away from the identity origin, and the
object itself will remain "upright" without rotating at all. This is
achieved using the transformations shown in Listing 4.
Example 4. Repeatedly transforming to achieve a more complex movement path
// First translate to the center of the circular path _effect.World = Matrix.CreateTranslation(0, 3, 0); // Rotate the object towards the current position on the circular path _effect.World = Matrix.CreateRotationZ(_angle) * _effect.World; // Translate to the edge of the circle _effect.World = Matrix.CreateTranslation(0, 2, 0) * _effect.World; //...then rotate back to an upright position _effect.World = Matrix.CreateRotationZ(-_angle) * _effect.World;
|
This time the circular path is centered at (0, 3, 0)
because we translate to here before rotating. Then the object is
rotated toward the point on the circle at which it will be rendered.
The object is then translated to the edge of the circle; as the
translation is two units along the (rotated) y axis, this will be the
radius of the circle. Finally, to keep the object upright, it is
rotated back by its angle. This cancels out the rotation that was
applied in the original rotation. The original rotation therefore
results in having an effect on the position of the object but not its
final angle.
As you can see, this entire series of events is eventually contained in the single _effect.World matrix. There is no limit to the number of calculations that can be accumulated into a single matrix in this way.