How to move and rotate things in 3D
How to move and rotate things in 3D | ||
|
|||||
Our journey starts with a grid.
|
I'm not going to explain all the code that will make the car move and will only stick to things related with rendering the model in the correct place. We'll start with a template for Artificial Engines project with camera and input support and add a nice Artificial.Editor.Grid to the scene. It spawns from (-100,0,-100) to (100,0,100).
Now we add our car:
Lotus = New AHStaticModel(AH, Application.StartupPath & "\lotusespritturbo.x") |
And render it in the rendering block:
Lotus.Render() |
It has no wheels, but our car nicely renders at the position (0,0,0):
First you must know how rendering a model works. If you just render the model as it is defined in the .x file, the position of vertices in the world will be exactly the same as in the model. Rendering the model in the same place as in its local space is somehow boring; you want to move and rotate the model in your world. That's where transformations kick in. You can move (translate), rotate, scale and even skew a space with them. And how do you tell the computer how you want to transform the model? You create a matrix! Matrices are mathematical representations of transformations. Each AHStaticModel has a property called RootMatrix, which tells how to move, rotate, scale and skew a model. When you have just created a new AHStaticModel the RootMatrix is set to Identity. This way the model renders directly as it is positioned in its local space, meaning if a vertex in the .x file has position (10,4,7) that's exactly where the vertex will appear in your world.
Let's get back to our car model. The model is already nicely positioned in its .x file, so that it rests just right above the ground. But it is too big for my world and should be scaled down to 20%. However, some other 3D model might be facing the wrong direction or be positioned way off center. Because the model defined in it's local space doesn't just fit with what you want to be a zero position in your world (0,0,0 position, no rotation), you should apply the first transformation to it - what I call the model matrix. In our case we want the model to be smaller so we construct the matrix like this:
ModelMatrix = Matrix.Scaling(0.2, 0.2, 0.2) |
In your case you might want to change the rotation and translation too. Use the Matrix.Multiply() or If you've already upgraded to .NET 2.0 just write the * between them. Remember that the order of the multiplication is very important when dealing with matrices. Imagine what each transformation does to a model and know that the following multiplication will work on whatever the previous one did. So the usual order would be to translate the model, so that the body-mass point would become the (0,0,0) point, then rotate it to face whatever you want the default axis to be and at last scale it to the right size.
In our case, after applying the ModelMatrix to the RootMatrix
Lotus.RootMatrix = ModelMatrix |
the car renders 80% smaller, just the way it should be in the zero position:
This is the basic point from where you should start with any 3D model. If the variable for position and rotation of your game object would be 0, than this is how the model should be placed in the world.
So far so good. The next logical step is positioning the car somewhere else than the zero point. Let's first change the camera to a complete top-down view, with (-100,0,-100) in the left bottom corner and (100,0,100) in top right. X axis goes horizontally, Z axis vertically:
Now that we're ready to see the effect of moving, let's define a 3D vector Position:
Dim Position As Vector3 |
We want the rendering to reflect the value of the Position, so we construct a new matrix CarTransformation, which would translate everything as much as the vector Position tells it to:
Dim CarTransformation As Matrix = Matrix.Translation(Position) |
All that is left to do is to set the RootMatrix to the multiplication of ModelMatrix and CarTransformation, because we want it both scaled down to fit nicely in our world and move to whatever place the CarTransformation tells it to:
Lotus.RootMatrix = Matrix.Multiply(ModelMatrix, CarTransformation) |
If we set the Position to let's say (100,0,50) the car should be rendered on the right side of our grid, half the way from center to the top:
Just to show you how important the matrix multiplication order is, here is the result if the above line of code has CarTransformation and ModelMatrix switched around:
What happened? Why didn't the car move to (100,0,50) and scale down there? That's because transformations affect the whole world and always revolve around the current zero point. Imagine it like this: Without any transformation applied, you get a big car in the center. Now the CarTransformation makes everything move 100 units to the right and 50 units up and then the ModelMatrix makes the whole world - with the car already at (100,0,50) - shrink down to 20% with the zero point of the world, not the car, as the center. That makes the car location appear at (20,0,10). The correct order is of course FIRST scale the big car while it is still in the center and then move the small car to the desired position. You should always apply the scale and rotation matrices to the untranslated model and then move it around.
OK, with this out of the way, how about a nice variable for rotation:
Dim Rotation As Single |
All we have to do now is rotate the model around the Y axis (Y pointing up in the air) and multiply it with the translation.
Dim CarTransformation As Matrix = Matrix.Multiply(Matrix.RotationY(Rotation), Matrix.Translation(Position)) |
Again, be careful about the order; rotation goes first, translation second. If we set the rotation to PI/2 we'll get the car facing right (because 0 means facing up, and PI equals 180 degrees):
SWEET! Our car renders positioned and rotated just the way we want it. Let's go on to moving the car. We want the car to move with a certain speed in the direction he's facing. Direction will be a Vector3 pointing in the way the car is rotated, so we calculate it like this:
Direction = New Vector3(Math.Sin(Rotation), 0, Math.Cos(Rotation)) |
If we render the direction vector (just a line going from Position to Position+Direction) we should get something like this:
We know in what direction the car should move, but not how much. First we should calculate the Velocity. This is again a Vector3 and while pointing in the same way as Direction it has the magnitude of Speed.
Velocity = Vector3.Multiply(Direction, Speed) |
Remember that Speed is just a number that tells how fast the car is going without any information of the direction. That's why we need a vector, which tells both direction and magnitude of the movement. Because Speed tells as how many units the car will move per second, the velocity also tells what distance the car would drive in one second. To get the distance in the current frame (which is shorter than one second) we need to multiply it with time. For that we have a variable Elapsed which tells how much time passed in the previous frame. Finally, we add the calculated distance to the Position vector:
Dim Distance As Vector3 = Vector3.Multiply(Velocity, Elapsed) Position = Vector3.Add(Position, Distance) |
Try it out! Set the speed to some value and rotation to some value and the car will move in the direction it's facing. At this point I've added an acceleration variable which makes the speed move with time, and some basic keyboard input, which make the car accelerate if you press the up key. Left and right would then change the Rotation variable and I already had a nice top down GTA clone:
A minute didn't pass when I had the camera following the car with this code in the processing block:
Camera.AngleHorizontal = Rotation Camera.TargetPosition = Position |
Making it look like this:
Yeeey! But we're still missing the wheels. An array of four AHStaticModels will be helpful here, as well as a WheelMatrix, which will be the model matrix for the wheel. In our case it has just the same 20% scaling:
Dim Wheel(3) As AHStaticModel Dim WheelMatrix As Matrix = Matrix.Scaling(0.2, 0.2, 0.2) |
Rendering right after we set the Wheel's RootMatrix to the WheelMatrix, we have all four wheels stuck in the zero position, but nicely scaled down to suit the car:
Now we need to know the relative positions of the wheels to the car, so that while the car is in the zero position, the wheels are placed in the correct places. First we define the variables,
Dim WheelRelativePosition(3) As Vector3 |
then we set them to their respectful values, in our case
WheelRelativePosition(0) = New Vector3(-7.5, 2.915, 12.035) WheelRelativePosition(1) = New Vector3(7.5, 2.915, 12.035) WheelRelativePosition(2) = New Vector3(-7.5, 2.915, -13.067) WheelRelativePosition(3) = New Vector3(7.5, 2.915, -13.067) |
However, I've obtained these values for the car that is not yet transformed by its model matrix, so I also have to transform this coordinates by the model matrix of the car:
For i As Integer = 0 To 3 WheelRelativePosition(i) = Vector3.TransformCoordinate(WheelRelativePosition(i), ModelMatrix) Next |
Anyhow, you should end up with relative positions that make sense in your world space. With this information and the WheelMatrix we can calculate the WheelTransformation, which tells how to move the wheel in the space relative to the car:
For i As Integer = 0 To 3 Dim WheelTransformation As Matrix = Matrix.Multiply(WheelMatrix, Matrix.Translation(WheelRelativePosition(i))) Wheel(i).RootMatrix = WheelTransformation Next |
And when we render it, all the four wheels are nicely in place:
Now as soon as we move the car, the wheels just stay in the same place. All we need to do is apply the CarTransformation on top of WheelTransformation, which will transform the space relative to the car into world space.
Wheel(i).RootMatrix = Matrix.Multiply(WheelTransformation, CarTransformation) |
And the goal has been almost reached. We're rendering wheels that move together with the parent model. All that is left is rotating the wheels individually. We again need an array, this time of Rotation angles,
Dim WheelRotation(3) As Single |
and an update to the WheelTransformation calculation:
Dim WheelTransformation As Matrix = Matrix.Multiply(WheelMatrix, Matrix.Multiply(Matrix.RotationX(WheelRotation(i)), Matrix.Translation(WheelRelativePosition(i)))) |
If you are smart enough to be doing this in .NET 2.0 a more comprehensive version is possible due to operator overloading and goes something like WheelTransformation = WheelMatrix * Matrix.RotationX(WheelRotation(i)) * Matrix.Translation(WheelRelativePosition(i)). As you can see, we've just added the rotation around the X axis part before we move it to our relative position to the car. As soon as you're done with this and add something like
WheelRotation(i) += Distance.Length / 2.915 * 2 * Math.PI * Math.Sign(Speed) |
you'll have four wheels rotating like crazy, just the way they should. I went a little extra trouble by making the front wheels rotate in the direction the car is turning as you can found out if you download the source code and ride the test drive for yourself.
Here's how it looks like in the end:
You might want to argue that all this is basically moving and rotating in 2D. Yes, it actually is. But as soon as you understand how it works in 2D with Y values always being zero, you will know how to move to 3D. Here's a little something to help. You should have 3 rotation variables instead of 1. They are usually named Yaw, Pitch and Roll, each corresponding to a rotation along one axis. Now all you have to do is calculate the rotation part of the CarTransformation with Matrix.RotationYawPitchRoll instead of Matrix.RotationY. Now your object will face according to all three rotation variables. The more tricky part is in calculating the direction vector to correspond with the new rotation. Since the Roll doesn't affect the direction you could just twiddle with some more trigonometry and come up with this:
Direction = New Vector3(Math.Sin(Rotation) * Math.Cos(Pitch), Math.Sin(-Pitch), Math.Cos(Rotation) * Math.Cos(Pitch)) |
A more elegant way would on the other hand be to use the rotation transformation of the car and transform a direction vector that points straight along the Z axis:
Dim CarRotation As Matrix = Matrix.RotationYawPitchRoll(Rotation, Pitch, Roll) Direction = Vector3.TransformCoordinate(New Vector3(0, 0, 1), CarRotation) |
There you go, you have a working Back to the Future flying car moving in all three possible dimensions. Make sure to try out the finished version.
Download the source code with the executable in the bin folder. You'll need December update of DX to run the executable right away, but do compile it with your own version of DX and AE. In the demo, press C to toggle between cameras, arrow keys will move the car and PageUp/Down make the car fly. The source code is commented so you can have the most out of reading it. If you have any additional questions, this is the forum topic to ask them in.
张志敏所有文章遵循创作共用版权协议,要求署名、非商业 、保持一致。在满足创作共用版权协议的基础上可以转载,但请以超链接形式注明出处。
本博客已经迁移到 GitHub , 围观地址: https://beginor.github.io/