World Transform Introduction You can get by just using the Matrix functions to do your Translation, Rotation and Scaling but it doesn't hurt to know a little more about how they work. Each of these 3 transforms will be discussed and then we'll go over how to combine them. This lesson also shows how to render the same object multiple times as if they were separate objects. Translation If nothing ever moved in a game or graphics demo, it would be pretty boring. Translation is movement along the axis without changing orientation. You can create a Translation matrix using the Matrix function Translation. By setting the resulting matrix as your World Matrix you can make your model move around. ```public static Matrix Translation( float x, float y, float z ); ``` ```Public Shared Function Translation( ByVal x As Single, _ ByVal y As Single, _ ByVal z As Single _ ) As Microsoft.DirectX.Matrix ``` The coordinates you provide to the function are used as offsets. Assuming that your model is centered on its origin (0,0,0), then the x,y,z coordinates you supply will be its location in World Space. Essentially these offsets are added to each and every vertex that passes through it. Based on the information in the SDK, I've written my own function to generate a Translation Matrix. It's very easy to do. ``` Matrix dhMatrixTranslation(float p_x,float p_y,float p_z){ Matrix mat = new Matrix(); mat.M11 = 1.0f; mat.M12 = 0.0f; mat.M13 = 0.0f; mat.M14 = 0.0f; mat.M21 = 0.0f; mat.M22 = 1.0f; mat.M23 = 0.0f; mat.M24 = 0.0f; mat.M31 = 0.0f; mat.M32 = 0.0f; mat.M33 = 1.0f; mat.M34 = 0.0f; mat.M41 = p_x; mat.M42 = p_y; mat.M43 = p_z; mat.M44 = 1.0f; return mat; } ``` ``` Private Function dhMatrixTranslation(ByVal x As Single, ByVal y As Single, ByVal z As Single) _ As Matrix Dim mat As Matrix = New Matrix mat.M11 = 1.0F : mat.M12 = 0.0F : mat.M13 = 0.0F : mat.M14 = 0.0F mat.M21 = 0.0F : mat.M22 = 1.0F : mat.M23 = 0.0F : mat.M24 = 0.0F mat.M31 = 0.0F : mat.M32 = 0.0F : mat.M33 = 1.0F : mat.M34 = 0.0F mat.M41 = x : mat.M42 = y : mat.M43 = z : mat.M44 = 1.0F Return mat End Function ``` As you can see there is a diagonal where all values are set to 1.0. If all other values were set to 0, then this would be the Identity Matrix. However, 3 values are set based on parameters passed in. If you happened to pass in all zeros, then as you would expect, the resulting matrix is Identity. To emphasize the changes required from the Identity matrix, the function could also be written like this: ``` Matrix dhMatrixTranslation2(float p_x,float p_y,float p_z){ Matrix mat = dhMatrixIdentity(); mat.M41 = p_x; mat.M42 = p_y; mat.M43 = p_z; return mat; } ``` ``` Private Function dhMatrixTranslation2(ByVal x As Single, ByVal y As Single, ByVal z As Single) _ As Matrix Dim mat As Matrix = dhMatrixIdentity() mat.M41 = x mat.M42 = y mat.M43 = z Return mat End Function ``` To create a Translation Matrix that would move an object to the left of the origin by 5 units, we could use the following code. ```Matrix trans_matrix; trans_matrix = Matrix.Translation(5.0f,0.0f,0.0f); m_device.Transform.World = trans_matrix; ``` ```Matrix trans_matrix; trans_matrix = Matrix.Translation(5.0F,0.0F,0.0F); m_device.Transform.World = trans_matrix; ``` Whether you use Matrix.Translation or either one of the functions I wrote, the result would be the same. Any or all of X, Y, and Z offsets can be used, though this example only uses one of them. Rotation Rotation causes the vertices to orbit around the object's origin. Here is the code to create a matrix that would rotate the vertices around the X axis. ``` Matrix dhMatrixRotationX(float p_angle){ float my_sin, my_cos; Matrix mat = new Matrix(); my_sin=(float)Math.Sin(p_angle); my_cos=(float)Math.Cos(p_angle); mat.M11 = 1.0f; mat.M12 = 0.0f; mat.M13 = 0.0f; mat.M14 = 0.0f; mat.M21 = 0.0f; mat.M22 = my_cos; mat.M23 = my_sin; mat.M24 = 0.0f; mat.M31 = 0.0f; mat.M32 = -my_sin; mat.M33 = my_cos; mat.M34 = 0.0f; mat.M41 = 0.0f; mat.M42 = 0.0f; mat.M43 = 0.0f; mat.M44 = 1.0f; return mat; } ``` ``` Private Function dhMatrixRotationX(ByVal angle As Single) As Matrix Dim mySin, myCos As Single Dim mat As Matrix = New Matrix mySin = Convert.ToSingle(Math.Sin(angle)) myCos = Convert.ToSingle(Math.Cos(angle)) mat.M11 = 1.0F : mat.M12 = 0.0F : mat.M13 = 0.0F : mat.M14 = 0.0F mat.M21 = 0.0F : mat.M22 = myCos : mat.M23 = mySin : mat.M24 = 0.0F mat.M31 = 0.0F : mat.M32 = -mySin : mat.M33 = myCos : mat.M34 = 0.0F mat.M41 = 0.0F : mat.M42 = 0.0F : mat.M43 = 0.0F : mat.M44 = 1.0F Return mat End Function ``` Again, this is simply the Identity matrix with a few changes. The code for rotation around the Y and Z axes are very similar so I won't show it here. They can be found in the code download for those who are interested. Note: All angles are given in radians, not degrees. To rotate about more than one axis at a time, you could either combine the matrices (as we'll show a bit later in this lesson) or use the DirectX-provided RotationYawPitchRoll function. As you probably guessed, there are versions for each of the individual rotations as well: RotationX, RotationY, and RotationZ. ```public static Microsoft.DirectX.Matrix RotationYawPitchRoll ( float yaw, float pitch, float roll ); ``` ```Public Shared Function RotationYawPitchRoll( ByVal yaw As Single, ByVal pitch As Single, ByVal roll As Single ) As Microsoft.DirectX.Matrix ``` The order these rotations are performed is: Roll (z), Pitch(x), then Yaw(y). Scaling Scaling is when you change the size of an object. The parameters passed to the functions are multipliers. If you set the X scale to 1.0 then the size doesn't change. If you set it to 2.0 then it will double in size along the X-axis, and 0.5 will make it half as large. Generally you should try to have your models in the same units as your world, and so scaling is rarely needed. However it may come in handy from time to time, so we'll cover it briefly. Here are 2 versions of code you could use to generate a Scaling Matrix. ``` Matrix dhMatrixScaling(float p_x,float p_y,float p_z){ Matrix mat = new Matrix(); mat.M11 = p_x; mat.M12 = 0.0f; mat.M13 = 0.0f; mat.M14 = 0.0f; mat.M21 = 0.0f; mat.M22 = p_y; mat.M23 = 0.0f; mat.M24 = 0.0f; mat.M31 = 0.0f; mat.M32 = 0.0f; mat.M33 = p_z; mat.M34 = 0.0f; mat.M41 = 0.0f; mat.M42 = 0.0f; mat.M43 = 0.0f; mat.M44 = 1.0f; return mat; } Matrix dhMatrixScaling2(float p_x,float p_y,float p_z){ Matrix mat = dhMatrixIdentity(); mat.M11 = p_x; mat.M22 = p_y; mat.M33 = p_z; return mat; } ``` ``` Private Function dhMatrixScaling(ByVal x As Single, ByVal y As Single, ByVal z As Single) _ As Matrix Dim mat As Matrix = New Matrix mat.M11 = x : mat.M12 = 0.0F : mat.M13 = 0.0F : mat.M14 = 0.0F mat.M21 = 0.0F : mat.M22 = y : mat.M23 = 0.0F : mat.M24 = 0.0F mat.M31 = 0.0F : mat.M32 = 0.0F : mat.M33 = z : mat.M34 = 0.0F mat.M41 = 0.0F : mat.M42 = 0.0F : mat.M43 = 0.0F : mat.M44 = 1.0F Return mat End Function Private Function dhMatrixScaling2(ByVal x As Single, ByVal y As Single, ByVal z As Single) _ As Matrix Dim mat As Matrix = dhMatrixIdentity() mat.M11 = x mat.M22 = y mat.M33 = z Return mat End Function ``` Of course there is the built-in version called Matrix.Scaling, so there's no need to write your own. Since there are 3 different scaling values, you can cause an object to scale by any or all of the 3 axes. Combining Matrices It will often happen that you want to Translate as well as Rotate and object. To do this, you create your separate matrices and then combine them by multiplying them together. It is very important to note that order matters. The order that you multiply the matrices is the order that the transformations will be applied. Typically you want rotation to be performed first, then translation. This will change the object's orientation and then offset it within the world. Doing it in the opposite order would cause the object to be offset into the world, and then revolve around the origin. To multiply 2 matrices, simply multiply them together as you would any numerical type (though not in VB.Net). Alternately you can use the static/shared Multiply member of the Matrix class. ```public static Microsoft.DirectX.Matrix Multiply ( Microsoft.DirectX.Matrix left , Microsoft.DirectX.Matrix right ) ``` ```Public Shared Function Multiply( ByVal left As Microsoft.DirectX.Matrix, ByVal right As Microsoft.DirectX.Matrix ) As Microsoft.DirectX.Matrix ``` Here is a small sample showing how to make a Translation Matrix and a Rotation Matrix, then combine them and set the result as the World Matrix. ```Matrix world_matrix; Matrix translation_matrix; Matrix rotation_matrix; //3 units to the right and 10 units down the Z-axis translation_matrix = Matrix.Translation(3.0f,0.0f,10.0f); //Rotate 90 degrees around the X-axis rotation_matrix = Matrix.RotationX((float)Math.PI/2); world_matrix = Matrix.Multiply(translation_matrix,rotation_matrix); //or world_matrix = translation_matrix * rotation_matrix; m_device.Transform.World = world_matrix; ``` ```Dim rotation_matrix As Matrix Dim translation_matrix As Matrix '3 units to the right and 10 units down the Z-axis translation_matrix = Matrix.Translation(3.0F, 0.0F, 10.0F) 'Rotate 90 degrees around the X-axis rotation_matrix = Matrix.RotationX(Math.PI / 2) world_matrix = Matrix.Multiply(translation_matrix, rotation_matrix) _device.Transform.World = world_matrix ``` Drawing Multiple Times In this lesson we use very simple geometry, a triangle. But even if you have a lot of models you may still use the same objects multiple times just in different locations and with different orientations. A lot of people seem to get confused by the idea of drawing the same geometry in multiple locations, but it couldn't be much easier. If we have 2 translation matrices (trans1 and trans2) and we want to draw our triangle in the 2 different locations represented by those matrices, we simply set one matrix then draw, then set the other and draw again. ``` //Assume these are already initialized Matrix trans1,trans2; m_device.SetStreamSource(0,m_vb,0); m_device.Transform.World = trans1; m_device.DrawPrimitives (D3D.PrimitiveType.TriangleList,0,1); m_device.Transform.World = trans2; m_device.DrawPrimitives (D3D.PrimitiveType.TriangleList,0,1); ``` ```'Assume these are already initialized Dim trans1, trans2 As Matrix _device.SetStreamSource(0,_vb,0) _device.Transform.World = trans1 _device.DrawPrimitives (D3D.PrimitiveType.TriangleList,0,1) _device.Transform.World = trans2 _device.DrawPrimitives (D3D.PrimitiveType.TriangleList,0,1) ``` About The Example Code In the sample code we'll use the Matrix functions provided by DirectX since that is what you will typically do in your projects. However my versions of the functions are included as well and since they take the same parameters as the DirectX functions it's trivial to change them to use my functions if you wish. To show Scaling, a triangle will be drawn with varying scaling values. You'll notice that the X and Y values for scaling increase at different rates, this is to show that scaling does not have to be uniform. Since the triangle has no depth, there is no Z scaling. The animation technique used here is very simple. Variables are used to track the animation state. The biggest problem with this method is that it's frame dependant. On a fast PC it will got much faster than it does on a slower PC. In a later lesson I'll show how to do time-based animation. To show Translation, a triangle will move back and forth across the top of the window. A constant value is used for the Y offset while the X value is animated. For Rotation, we'll have a triangle rotate in the middle of the screen around the Y axis. As mentioned before, triangles viewed from behind are normally culled. Since our triangle will be showing its back to the camera half of the time we probably want to prevent it from being culled. In our init_device_states function we disable culling. If you comment this line out then the triangle will disappear when its back is facing the camera. Matrix Multiplication is shown by combining a Rotation Matrix with a Translation Matrix. This triangle moves back and forth across the bottom of the window while spinning on the Z-axis. As mentioned above, the order that you multiply these matrices is important. First we rotate the triangle, then we translate it. Try changing the order that they're multiplied to see what happens. Even though the same triangle model is used, we can draw it in difference locations and orientations just by setting a different World Matrix. Remember, the World Matrix specifies how the vertices are to be transformed but do not change the vertices themselves. The only way the vertex data itself will be changed is if you Lock the vertex buffer and edited the values. Wrapping Up Though we covered a lot of ground, none of it was too complex. It may take a little while for some of the ideas to really sink in, but if you experiment with the code for a bit you should have no problem at all. Lesson Downloads Source and Executable for C# (19K) Source for C# (10K) Source and Executable for VB.Net (18K) Source for VB.Net (9K) To compile this lesson you will also need the Drunken Hyena Common Code Back