Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Rotation is the property or collection of properties which control the orientation of an object. In other words, it controls which way the object is "facing". Rotation can be used to make a planet rotate on its axis, to rotate a space ship according to user input, or to spin particles to give them more natural-looking behavior. Objects which implement the IRotatable interface can be rotated. Rotation is performed using radians.
The Rotation property on entities makes it easy to create a moving entity from another rotating entity like a turret. The Rotation property provides four values which can be used to set the velocity of a newly-created entity:
Up
Down
Left
Right
Your code should use one of the four mentioned above depending on which direction the turret faces when it is not rotated at all. For example, if an unrotated turret faces to the right, then the Right property might be used to create an entity, as shown in the following code:
Rotation can be used to rotate an object to "face" another object. For example a turret may rotate to face an enemy. If the cursor is to be used as a target, the following code can be used to rotate a Sprite so it faces the cursor. The assumption on this code is that there is some identifiable "front" to the Sprite such as the nose of a plane or the tip of a turret. When the Sprite is unrotated (has a RotationZ of 0) the front of the Sprite will face a particular direction. This direction will be the "offset" variable.
When presented with a current angle and a target angle, rotating clockwise or counter-clockwise is not always evident. The following method helps with this calculation:
For more information, see the AngleToAngle page.
First, keep in mind that a Sprite's RotationMatrix property has a relationship with the RotationX, RotationY, and RotationZ set of values - updating one automatically updates the other. Therefore, even if you only use the individual rotation values, you can still read from the RotationMatrix for finding the real-world positions of a vertex. There are 4 steps for finding the position of a vertex. 1. Determine the vertex that you want to edit. For this example, I will do the bottom right. The position of the vertex of a default, untouched Sprite would be (1, -1, 0). At ScaleX and ScaleY = 1, these are the coordinates of the bottom right point.
2. Next, scale the vector according to the Sprite's Scale values:
3. The RotationMatrix is what is actually used when the Sprite for its rotation. We can take the point and transform it by the matrix:
4. Finally, the vector's coordinates are relative to the center of the Sprite, so move them by the Sprite's position.
Many video cards expect textures to be "power of two". That means that images used in FlatRedBall should have their width and heights matching one of the following values:
Exponential Notation | Expanded Notation | Value |
---|---|---|
2^1
2
2
2^2
2*2
4
2^3
2*2*2
8
2^4
2*2*2*2
16
2^5
2*2*2*2*2
32
2^6
2*2*2*2*2*2
64
2^7
2*2*2*2*2*2*2
128
2^8
2*2*2*2*2*2*2*2
256
2^9
2*2*2*2*2*2*2*2*2
512
2^10
2*2*2*2*2*2*2*2*2*2
1024
2^11
2*2*2*2*2*2*2*2*2*2*2
2048
Although FlatRedBall XNA provides a Circle class for collisions, you may be interested in writing your own custom circle collision code for customizable behavior. What follows is a conceptual discussion of circle collision.
The most basic form of collision returns true or false to indicate if two circles are touching. To keep things lightweight, consider a class Circle with three properties:
X
Y
Radius
These properties are all we need to perform collisions between two circles. By definition circles are perfectly uniform. No point on the edge of a circle is any further from the center than any other point, and all points on the edge are the distance of the radius away from the center. This makes collision very simple. Conceptually, to perform a circle collision we compare the distance from the centers of the two circles and see if it's greater than the sum of the two radii (plural of radius). Given two circles, the following code determines whether they are touching:
That works well, but efficiency-minded programmers might realize the usage of the Sqrt function when calculating the distanceBetweenCircles. We could do the following to speed things up slightly:
We simply square the sum of the circles' radii to avoid calling the more expensive square root method System.Math.Sqrt.
The image above shows a circle move collision before and after the repositioning occurs. In this example, the top circle stays static while the bottom circle moves to solve the overlapping. The blue dotted line is the line between the center of the two circles and the black line with arrows on the end outlines the distance which must be moved to solve the overlapping. To keep the circles from overlapping we need to find out the properties of the black line. More specifically we need to know the length and direction of that line. The most obvious relationship is that the black line lies directly on top of the line connecting the center of the two circles. In other words, the direction or angle of that line is the same as the direction or angle of the line connecting the center of the two circles. To calculate an angle, we'll use the System.Math.Atan2 method:
Now we have our angle stored. Next, we need to calculate the distance that the circles need to move over. Remember from the previous example that if the distance between two circles is less than the sum of the radii, there is a collision. If the distance between two circles equals the sum of their radii, then the two circles will be barely touching, as in the image on the right. The following relationship is true as well:
If the distance between the circles is equal to the sum of the radii, notice that distanceToMove is 0. Therefore, to calculate the distance by which to move:
At this point we have the angle and the distance by which to move the circles. To keep things simple, I'll just move circle2:
Circle bouncing is a simple way to introduce basic physics into your game. While circle bouncing can be calculated with only trigonometry, linear algebra provides us with a cleaner and faster solution. Circle vs. circle bouncing collision is actually very similar to circle vs. flat surface collision. How is this so? Consider the following image:
Although the example I've shown so far highlights one circle which is moving bouncing off of another which is (and remains) stationary, this is not always the case. In fact, either circle could be moving. Fortunately we can generalize the code so that it will work regardless of who is moving and who is stationary:
Now performing the dot product of the relative velocity vector and the tangent vector gives us the length of the velocity component parallel to the tangent (the length of the orange).
And we simply multiply the normalized tangent vector by the length to get the vector component parallel to the tangent (orange line).
Simply subtracting the velocity component parallel to the tangent (orange) from the relative velocity gives us the velocity component perpendicular to the tangent (red).
Now we have all of the information necessary to perform the collision. At this point we can make some choices regarding how we want the collision to affect the circles. To make both circles bounce off of each other like pool balls after a collision, the velocity component perpendicular to the tangent (red line) should be applied to both. To make one circle move, it should be applied to it twice.
To make just one circle move:
Statistics is a field of mathematics that can you predict the result of a system that includes random numbers. It can also help you select coefficients to use to create desired random behavior.
The normal distribution describes a very common, naturally occurring distribution where the most common values appear near the average. Values which are further from the average are less likely.
One example of a normal distribution is the the average height of people. The average height is 5'8", and this is not just a numerical average, but there are actually more people who are 5'8" than any other height.
People who deviate extremely from the average (people over 7 feet tall or under 5 feet tall) tend to be rare.
In some cases a normal distribution is desired, but perfect accuracy is not needed. For example, if you are creating a particle system, you may want the size of most particles to be around some desired number with some variability in the sizes.
If a precise normal curve is not needed, the shape of a normal curve can be easily approximated.
A random number generator will return an even distribution of results. In this type of distribution, any number within the range is just as likely to occur as any other number.
This can be achieved as follows:
A very rough approximation can be achieved through a "two value distribution". A two value distribution is where two random numbers are added together, then the result is returned. The range of each of the values that is added together should be half of the desired range such that the resulting value still lies between the min and max.
Notice that this distribution is shaped like a triangle - the sides are perfectly flat. While this doesn't quite get the curved sides of a normal curve, it does result in the most common values lying near the average. Values further from the average become less and less likely, eventually having a 0 probability at the min and max values.
This can be achieved as follows:
While a two value distribution gets close, a normal distribution can be approximated even more closely with three values. A three value range results in a curved normal-like distribution.
This distribution be achieved similar to the two value code above:
The term "collision" in game development refers to both detecting whether objects are touching as well as changing the state of these objects when this touching occurs.
The only games free from collision are pure text-based games, but it's likely that if you're working on one of those you probably won't need to use FlatRedBall.
Before we get into how FlatRedBall handles collision, let's cover some basic terminology.
We've already discussed what a collision is, so let's give some examples. The following image shows three situations and labels whether a collision is occurring:
Notice that collision doesn't just occur if the edges of two objects are touching. When FlatRedBall performs collision, it considers objects to be filled in. Therefore, if one object is inside of another, a collision is still occurring.
A move collision is a collision where one or both of the involved shapes are repositioned during the collision to prevent overlap. The term "move" was selected because this type of collision can result in the shapes moving to new positions. The amount that each shape is moved depends on their masses relative to each other. It is very common to make one object static by giving it a non-zero mass and giving the other object a mass of zero. The following image shows the result of a move collision assuming that the rectangle is static and the circle is not.
To use FlatRedBall terms, move collisions will adjust the position of the involved shape(s) if a collision does occur.
A "bounce" collision is a collision which can be used to simulate physics. As the name implies, bounce collisions cause objects to bounce against each other. Just like move collisions, bounce collisions use the masses of two objects to calculate how the forces from the bounce should be applied. Just like with move collisions, it is common to have one object in a bounce collision have a non-zero mass while the other has a mass of zero. This causes one of the shapes (the one with the non-zero mass) to not be affected by the collision.
You may also be expecting there to be a Shape interface or class that all shapes implement. Actually, there isn't such an interface or class. The reason for this is performance. Initial performance measurement tests in FlatRedBall showed that the majority of time spent in collision loops is the actual method calls. In other words, we compared the amount of time required to call collision methods to the amount of time calling "empty" methods and over half of the time was spent in the method call.
Add the following at class scope:
Add the following to Initialize after initializing FlatRedBall:
Add the following to Update:
We'll use Velocity for the following example: Use the same using statements, class-level declarations, and Initialize code as above, then replace the custom Update logic with the following:
The first "1" argument is the mass of the first object. The second "1" is the mass of the second object. Try changing the first argument to 0. This will make secondCircle immovable. Then try changing the first argument back to 1, then the second to 0. This will make the moving circle unaffected by secondCircle.
Now the two Circles will collide against each other and bounce. Of course, you will probably notice that this setup results in secondCircle being moved off of the screen very quickly. You may want to change the firstCircle's mass to 0 to keep the secondCircle on screen so you can test multiple collisions.
The next most common behavior performed when conducting collisions is known as "move" collisions in FlatRedBall. These collisions move objects when a collision occurs so that they no longer overlap. The first step is to understand the concept of how this works with circle collision.
If the moving circle bounces off of the circle at the bottom at the very top point, it will bounce as if it hit a flat surface. The same is true for any other point. Don't believe me? Look at the image and turn your head sideways. In more mathematical terms, the circle on top bounces off of the circle on the bottom as if it collided with a flat surface whose slope is defined by the tangent of the circle at the point of impact. Once we find the tangent at the point of collision, all we have to do is reflect the velocity on that tangent then assign the velocity to the bouncing circle. Now our problem is split into two smaller problems. Note that the previous two sections on determining collision and performing move collision will likely be used prior to performing the bouncing code. That is, you'll want to make sure your circles actually have collided, and you'll also want to separate them so that they don't get "stuck" together. Since we'll be using linear algebra to solve this problem, we'll define our tangent as a vector rather than a angle. There are a few ways to identify the point of impact for the collision, but for this example we'll simply assume the point of impact lies where a line drawn between the two circles touches the either of the circles. Since we've already performed our move, our scenario may look like this: Fortunately, the actual point of impact doesn't matter to us. Only the vector that is parallel to the tangent at that point. This is simple to calculate. The tangent is perpendicular to the line drawn between the center of two points. I will use XNA's Vector2 syntax for all Vector2's.
Once we have the vector parallel to the tangent of the circle at the point of impact we can reflect the velocity on this vector. To do this, we need to break the velocity vector into two vectors - the vector perpendicular to the tangent and the vector parallel to the tangent as shown in the following diagram. Finally, we take the component of the velocity vector that is perpendicular to the tangent, then subtract it twice from the original vector to get the new bounce vector as shown by the green line in the following diagram. If you're not familiar with linear algebra you may be wondering how to calculate the component of the velocity vector that is perpendicular to the tangent (the red line). Fortunately, this is relatively easy. We simply need to "project" the velocity vector on the tangent and that will give us the velocity vector component parallel to the tangent vector (orange line). For our projection to work correctly, the tangent vector must be normalized (have a length of 1).
Almost all games requires some type of collision. For example, Pong used ball vs. paddle collision and Super Mario Bros. performs collision between Mario and the ground to test whether he should fall or not. Even games which might not seem to use collision likely do. For example, mouse-driven adventure games like used collision to detect whether the cursor is overlapping certain areas when clicking.
You're probably pretty familiar with the word "shape". In FlatRedBall, shape defines a shape like a square, triangle, or circle. To be a little more precise, a shape is an object which is a , has collision methods, and is managed by the class.
Of course this ratio can fluctuate depending on the complexity of the collision method and the frequency of collision occurrence. However, making a method virtual makes the method call itself take longer than otherwise, and we decided to give up the convenience of a base class for better performance. FlatRedBall provides a number of shape classes, and this list may continue to grow as more sophisticated collision needs are satisfied. If you'd like to find out which shape classes are available, check out the page.
Most games use to detect whether collision is occurring; however, not all games keep collision visible. Therefore, it is likely that your game will use for collision and another type of object for drawing (such as ). In this situation, the that you use will approximate the collidable area of your visible object. In some cases simple such as and will be sufficient. In cases where more complex collision shapes are needed, the object is a good alternative. In more complex situations multiple Shapes can be used.
As you'll see in the next sections where we begin writing some sample code, Shapes have a Visible property which can be used to control whether the FlatRedBall Engine draws them. In the following examples the that are created through the will automatically be set to be Visible. When using Shapes only for collision and attaching them to other objects, it is not necessary to create them through or add them to the . Keep this in mind as you read other documentation about .
Collision can be detected between shapes simply by calling CollideAgainst. The following code example creates two . One is positioned at the point of the mouse. If a collision occurs, the moving Circle becomes orange. Otherwise, it is blue. Add the following using statements:
WARNING: The code above may not compile because the Color struct is not qualified (the code doesn't know which namespace it is in). Which "using" statement you add to your code depends on which version of FlatRedBall you are using. For more information, see the page.
The CollideAgainst method is a method that only returns whether collision has occurred - it does not modify the calling or the argument . The following sections describe methods which do.
The CollideAgainstMove method returns a true or false depending on whether the calling and the argument are touching. This method also modifies the Position of one or both .
Keep in mind that since this method modifies the Position, we should not directly modify the Positions in custom code every frame. Doing so would result in the CollideAgainstMove and our own code both attempting to control the Position of the involved . While this won't cause a crash, it may result in unexpected or undesirable behavior. In most cases where CollideAgainstMove is used, the involved (or their Parent ) are controlled either by Velocity or Acceleration.
The third method that we'll look at is . The us all of the same functionality as CollideAgainstMove( true/false return value as well as repositioning), but it also modifies the involved Velocity values. For bouncing to behave properly, we have to make sure that we're not controlling the involved position or velocity. Therefore, this leaves us with controlling acceleration. To use , simply replace your custom Update code with the following:
This article covers a lot of information about collisions; however, this topic has a lot of depth. To find out more, start at the page and follow the links to the information you're interested in.
The FlatRedBall Engine (along with many graphical APIs and game engines) use radians as a measurement of rotation. This may seem inconvenient if you are familiar with using degrees as a measurement of angle (which is likely if you haven't done much 3D programming).
So why does FlatRedBall use radians? The reason is because radians are a functional measurement. That is, Pi is not some arbitrary number selected to represent half of a circle. It is actually the distance of an arc that is drawn halfway around a circle that has a radius of 1. That's handy!
Another reason that radians are used is because because all .NET math functions that FlatRedBall uses expect radians. For example, all trig functions such as Sin, Cos, and Atan2 that can be found in System.Math take radian arguments. We could have used degrees for our rotational values and converted things internally, but we didn't do this because we figure that you may be using functions like these in your code, and having rotational values already in the form of radians can simplify things.
Of course, this choice comes at a cost. If you're used to degrees, then that means you have to learn to use radians.
If you've worked with degrees before, then you're likely used to common values like 360, 180, and 90. These values are very common in Radians. When dealing with radians, you usually use the term "Pi". Pi is a number commonly used in math which is approximately 3.14159265. Therefore, if someone ever says "two PI", that means 2 * Pi, which is about 2 * 3.14159265, or about 6.2831853.
You may be wondering "Why does the article say 'about'"? The reason is because PI is what is called an "irrational number". Irrational numbers are numbers which can't be expressed by a fraction or a finite number. In other words, rational numbers are 3, -2, 1/5, and .5 (as a few examples). However, PI is an irrational number meaning any time you write PI down, you are writing an approximation. That is, 3.14159265 is an approximation of PI. A better approximation is 3.141592653589793, but even that is an approximation.
If you are going to deal with Pi, then there should be some values you know:
If you're still looking to use degrees, you can still easily convert between the two units.
XNA provides the following conversion methods:
You can also do it by hand:
The Sine and Cosine (Sin and Cos in C#) functions provide useful information about angles. Given an angle, the Sine function gives the Y component of the vector drawn at that angle and the Cosine function gives the X component of the vector drawn at that angle
The Sin and Cos methods provide the X axis and Y axis components of a vector given a direction and a magnitude. The following converts magnitude and angle to component velocity values:
The Atan2 method provides the angle between two points. The following example finds the angle that a turret would have to be angled at to fire at an enemy;
Let's say that you want to make one object orbit around the center of the world (0,0). To do this, you can simply create an angle function and change it using some time based method. For example:
Once you do this, you can use the Sin and Cos methods to get the position of the object:
The Atan2 method can convert a vector to an angle. This is useful for rotating an object (such as a space ship) to face the direction it is moving.
The following code rotates a PositionedObject so that it faces in the direction that it is moving:
Vectors are commonly used for motion, collision, and other physics-related behavior. While the basic concept of a vector is easy to understand, vectors enable us to create realistic and complicated behavior.
In mathematics, a Vector is a set of values which can simplify common physics calculations. Vectors are usually used to represent:
Position
Velocity
Acceleration
FlatRedBall uses the Vector3 type for position, velocity, and acceleration. The 3 in Vector3 means that the vector has 3 components: X, Y, and Z. In some cases FlatRedBall also uses Vector2, which is a type which only has X and Y values. By default, all objects in FlatRedBall have a Position value of (0,0,0), where the three values represent the X, Y, and Z components of position. Since the Camera in FlatRedBall also has an X and Y of 0, then by default all objects will be positioned at the center of the screen. For example, a new circle (whether added through the FRB editor or code) will be positioned at the center of the screen.
We can modify the position of an object by changing its X, Y, or Z values (although Z values may not have a visual impact on the object in 2D). For example, the Circle above can be moved to the right by setting its X to 100, which will move it 100 pixels to the right of the center of the screen. CircleInstance.Position.X = 100;
In the image above, the (X,Y,Z) values are (100, 0, 0).
The addition of vectors is a common operation in game development. When two vectors are added, each component of the vector is added together. For example, consider the following code:
The code above would result in thirdVector containing values (160, 20, 0). This is the result of adding the X components (100 + 60), Y components (30 - 10), and Z components (0 + 0). When a vector is added to the position of an object, the object is moved by the values contained in the adding vector. For example, the following code would move the player to the right ten units:
A vector can represent a direction which can be used to rotate or move an object. For example, a game which shoots bullets at the player would need to calculate the direction from the enemy shooting the bullet to the player shooting the bullet. This direction can be determined by subtracting the position from one object to another. For example, to determine the vector from the enemy to player, the following code can be used:
Notice that the order of subtraction is (destination - source). If the enemy is represented by a red circle and the player by a green circle, then the subtraction would produce a vector visualized by the red arrow in the following image:
To move toward a direction you'll need a few pieces of information:
What position is your object currently at?
What is the target position?
What speed (in units per second) would you like to move at.
To find the proper velocity, you will first want to calculate the vector from the object's current position to the target position, normalize the vector (make it a unit vector), then finally multiply it by the desired speed (also referred to as magnitude). Here's some code to get you started:
Dot Products are useful when performing collisions using vectors. For more information, see: http://www.sparknotes.com/physics/vectors/vectormultiplication/section1.html And for an interactive visualization tool: http://www.falstad.com/dotproduct/
The following link gives the basics about cross products: http://www.physics.uoguelph.ca/tutorials/torque/Q.torque.cross.html Interactive visualization tool: http://physics.syr.edu/courses/java-suite/crosspro.html
If we have a point and a line (which is represented by two points), the dot product enables us to move the point to the line by the shortest distance possible. This is common when writing collision and physics code. In summary, to move a point to a line the following steps need to be performed:
Find the unit vector parallel to the line.
Perform the dot product of the vector from one point on the line to the point in question and the unit vector parallel to the line.
Multiply the unit vector parallel to the line by the result of the previous dot product to get the projection of the vector from the first point in the line to the point in question.
Subtract the newly created projection vector from the vector from the first point in the line to the vector. This will result in a vector that is perpendicular to the line which is the lenth of the distance of the point from the line.
Subract the vector from the point and it will now be on the line.
3D computer graphics rely heavily on the use of matrices. Unfortunately in pure mathematics, the conceptual understanding of what a matrix "does" or "means" isn't explicitly taught, and this understanding usually doesn't come about until a student has a good understanding of linear algebra.
Although an understanding of matrices is not necessary to make many FRB games, knowing what a matrix does and how to use it in code can help improve your efficiency as a programmer.
This section will discuss matrices at a conceptual level. Don't worry, you don't need to know a lot of math to understand what I am going to explain here.
Although the world is round, and when you move north, the actual direction you are moving depends on your position, I will ignore this fact to keep things simple. That is, we can consider that moving north is always in the same direction and moving east is always in the same direction - much like people thought when they considered the world to be flat.
To begin our example, imagine standing on a large, flat area of land. There are no landmarks. In fact, there is nothing that you can use to determine whether you are facing north, south, east, or west. However, just because you don't know what is north doesn't necessarily mean there isn't a north (no, I'm not trying to get philosophical). In other words, there is a "true" north, only you don't know what it is.
Let's say you are given the command to walk a certain distance in a certain direction. That is, you are given the command, "Walk west 1 unit." In this situation, you have two problems. First, which way is west? Second, even if you knew which way west was, how long is one unit? Is it one foot? One meter? One mile?
To actually walk west 1 units, you will need two things. First, you'll need a compass to tell you which way you are moving. Second, you'll need some kind of measuring device to tell you how far one "unit" is.
Continuing the previous example, let's say that you now have a compass and a measuring stick. The compass tells you which direction you are facing, and the stick can be placed on the ground to indicate the length of one "unit". Now that you have a measure of direction and distance, you can accurately follow directions.
With this new equipment in hand, you receive the following orders:
"Move west 1 unit. Move north 1 unit. Leave a mark in the ground. Move east 2 units. Leave a mark in the ground. Move south 2 units. Leave a mark in the ground. Move west 2 units. Leave a mark in the ground."
In this situation, you should have moved as shown in the following image.
This of course assumes that north is up, east is right, and so on.
So far, we assume that the compass and the measuring stick are accurate. Or perhaps another way to look at it is that the person who gave you the two devices was honest. However, consider what would happen if the person who gave you the compass decided to play a joke. He modified the compass so that it is slightly inaccurate. That is, when the compass indicates that you are facing north, you are actually facing north-east. When the compass indicates east, you are actually facing south-east. South becomes south-west, and west becomes north-west.
An important point to make is that the square still appears to be a regular "square" to you, but to someone who knows which way true north is, it appears rotated by 45 degrees. Another interesting point is that the directions that you received didn't change at all - the only thing that changed is the compass. In fact, by changing the way that the compass points, you can actually draw a square of any orientation. To you, you are simply moving north, south, east, and west according to the compass.
Although this would probably be something you (as a holder of the measuring stick) would realize, in terms of math or instructions it is also possible to change the length of the stick. A measuring stick twice as long would produce a square that is twice as large.
What is important to realize is that the "directions" are separate from orientation and size. Conceptually, it's important to understand that the directions can change while compass or stick stay the same, or vice versa.
This brings us to two terms: "world coordinates" and "object coordinates". These are also referred to as "world space" and "object space". World space is the "authority" on what north is. That is, in our previous example, in world space, north is straight up and east is to the right. Object space is subject to a "compass" and "measuring stick" which may or may not be aligned with world space. In the situation where your compass worked correctly and the measuring stick actually measured one unit, then you were travelling in world space and object space. Object space and world space were aligned perfectly. Once the compass changes, the world space and your object space are no longer aligned. Travelling north in object space may result in you actually travelling a different direction depending on the compass. Also, you may be travelling a further distance than you really think you are if the measuring stick is larger than one unit in world space.
So far, we've treated direction and length as two separate things. That is, in the previous example, you are holding both a compass and a measuring stick. In this section, I will discuss how these two can be combined and represented mathematically.
Rather than using north, south, east, and west, I will use X and Y. Moving in the positive X direction is to the right, and positive Y direction is up.
You may recall first learning about the Cartesian plane in school. One of the first exercises students are given is to graph a series of points. When presented with a point such as (3,2), the students are told to move to the "right" 3 times, and "up" 2 times.
Another way to think about how to graph (3,2) is to use two "lines". That is, consider having one horizontal line of one unit in length and one vertical line also of one unit in length. To move to (3,2), you could use the horizontal line and place it end to end 3 times, then use the vertical line, start at the end of the 3rd horizontal line and move in the vertical line's direction the length of two lines.
The idea is similar to the example from before. To find the point at (3,2), simply place 3 horizontal lines end to end, then 2 vertical lines. Notice that (3,2) no longer means "Move 3 units to the right and 2 units up", but rather "Move 3 units along the blue line and 2 units along the red line". The lines are now our compass and measuring stick and (3,2) are the instructions.
Just as before, we can rotate the lines so that they point to any direction. Also, the lines can be made longer or shorter to stretch or shrink how far away from the origin points appear. Using two lines rather than a compass and measuring stick gives us a few extra "abilities". One is that the lines can rotate independently. They don't have to form 90 degree angles, but can point in similar or even opposite directions. Another ability is that we can stretch or shrink the lines independently.
The next thing that we need to understand is how to define these "lines". So far I've used the word lines to describe the way to move when receiving an "instruction" such as (3,2). A more proper term is vector. Consider that any of the previous vectors that we used to define which way to move when receiving instructions can actually be represented by a coordinate system as well. That is, the horizontal blue line pointed to the right 1 unit and the vertical red line pointed up 1 unit. In terms of vectors, we can say that the horizontal line is a vector represented by (1,0) in world space. That is, in an "unmodified" coordinate system, moving along the blue line from the origin will take you to (1,0). Similarly, the horizontal red line can be represented as the vector (0,1).
This can bring us to the first understanding of what a matrix represents. In terms of how it affects coordinate systems, a matrix is nothing more than a collection of these vectors to define which way is "up" and which way is "right". Therefore, a matrix that represents the red and blue lines may be written as:
Where the first row ( 1 0 ) is the horizontal line and the second row ( 0 1 ) is the vertical line. Therefore when we are told to move 3 untis along the horizontal line and 2 units along the vertical, we could represent this movement as:
which becomes:
which of course becomes:
This matrix happens to "align" with the unmodified world coordinate system. That is, if we follow this matrix to find a point such as (3,2), the point will be in the same position as if we just moved along the x and y axis, as we learned in elementary. In fact, this will be the case for any point we can think of. Whenever a matrix is aligned with the world coordinate system, it is called the "identity matrix". Another way to quickly determine if a matrix is an identity matrix is to note that it has 1's diagonally starting at the top right, and 0's everywhere else. Here is a 4X4 identity matrix:
Although I haven't covered how to actually use matrices to change how things appear on the screen, you can probably imagine that switching the ( 1 0 ) and ( 0 1 ) will result in the Y and X coordinates of an object being switched.
Usually matrices are not as simple as:
That is, in 3D graphics, we are usually concerned with a 3rd axis: the Z axis. This is the direction that gives a scene depth. Therefore, a matrix that represents 3 axes will be 3X3. Here is an example of a 3X3 identity matrix:
If the row ( 1 0 0 ) is the X axis, then you can see that moving along the X axis as defined by the matrix means to stay on the real world axis. Since the Y and Z components of ( 1 0 0 ) are both 0, regardless of what the X value is, it will always move you along the X axis. To return to the previous example, the row ( 1 0 0 ) is just a horizontal line of one unit in length. The extra 0's have no affect on it.
Usually in 3D graphics, we use 4 matrices. The 4th "dimension" also known as W is used to reposition the object so that it is not centered at the origin. This is not critical to understand, so I'm not going to get into it. Just remember that most - if not all - matrices you will deal with in FRB will be 4X4 matrices.
The most common usage of a Matrix in FRB is the Sprite's (or other PositionObject's) RotationMatrix property. This property is the matrix of the Sprite which defines the "compass" for the Sprite when drawing its 4 corners. Any time any of the rotation values are changed, this matrix is modified to reflect the changed rotation. An unrotated Sprite will have a 4X4 identity matrix as its RotationMatrix property.
The RotationMatrix can help you convert object coordinates to world coordinates. For example, consider making an asteroids game. When unrotated, your ship faces up. You decide that you'd like to have a Sprite which represents the booster of the ship appear at the back of the ship. Of course, you could use attachments, but I need an example, and this one works well.
As I mentioned, when unrotated, the ship faces up, so the flame Sprite should be positioned below the ship. Let's say the flame should appear 1 unit below the ship. In object space, the flame's coordinates should be (0, -1). However, that doesn't mean that the flame should always appear one unit below the ship since the ship may rotate. So, how do we set the position of the flame? The first step is to find the actual position of the flame relative to the ship by using the ship's RotationMatrix.
In mathematical terms, we are going to create a vector which represents the position of the flame relative to the ship in object space. Then we will translate the vector using the ship's rotation matrix.
As the ship rotates, the ship.RotationMatrix chagnes, which results in a different relativePosition after the Transform call.
Vectors can also be used to represent velocity. Continuing the example from before, consider adding the ability to fire bullets from the ship. The bullets should fire the way that the ship is facing. In object space, this is along the positive Y axis. This could be represented by a vector (0, 10, 0), or rather, 10 units/second along the Y axis.
The following code will create a bullet which fires in the direction that the ship is facing.
Note that unlike the bullet position example, the velocity doesn't need to be modified. However, you may want to add the ship's velocity if you want the bullet physics to be accurate.
Interestingly enough, if the ship was rotated on its x or y axis, the bullet velocity would take that into consideration and fire towards the camera or away into the distance.
I'm not going to write an example as it would be very similar to the bullet velocity, but acceleration can also be calculated similarly. Simply take an acceleration vector, call Transform to rotate it by the RotationMatrix, then assign it. This could be used to actually implement the force that would be created by the ship's thruster in an asteroids-like game.
One use of the cross product is to determine which side of a line a point is on. This is the core math behind polygon collision. Consider the following example. Using the cross product we can determine which side of the line C is on. However, before we can do that, we need to identify characteristics of the line formed by the points A and B - specifically which point is "first". That is, if a person were standing at point A and looking towards B, to him C would be to the "left" of the line. However, if the person were standing at point B and looking toward point A, C would be to the "right" of the line. Selecting A or B as the first point or origin really doesn't matter as long as the effect is understood and the same ordering is used consistently. For this example, I'll consider A as the first point and use this when creating the vectors to be used in the cross product. Therefore, our two vectors when calculating the cross product will be AB and AC (this is a right handed coordinate system as is used in XNA, but not in Managed DirectX). If AB and AC are considered to be on the Z=0 plane, then the cross of AB and AC is a vector with a positive Z value. So long as the vector is on the "left" side of the line when viewing from A to B, the Z component of the cross of AB and AC will always be positive. If the point is on the right side of the line in the same situation, the Z component of the cross of AB and AC will always be negative. Using this information, we can find out if a point is inside a polygon. In the following image, the point F is inside a polygon formed by points A, B, C, D, E. Each side has a matching vector with which it would be crossed and in all cases the Z component of the cross would be positive. Keep in mind that this is only the case if the polygon is convex. Determining if a point is inside of a concave polygon requires a different method.
The following method can be used to determine the required initial magnitude required for a fired object to pass through a point at RelativeX, RelativeY.
Generally speaking, interpolation is the method of determining or estimating one point by using a set of other known points. In game programming, interpolation is often used to perform "smooth" transitions between one state to another. This transition could be in terms of position, color, rotation, scale, or any other numerical value.
Consider a situation where you are would like a Sprite to move from one position to another. Instead of having the Sprite immediately snap to its desired location, you'd like to have it slide in smoothly.
This is an example of interpolation. Initially the Sprite is at its first position (let's say that's X = 0), and after a given amount of time you'd like it to be at its ending position (let's say that's X = 100). To make the math simple, let's say that this movement should occur over the course of one second. Given this information you can calculate the position of the Sprite at any time in the animation.
Of course, if you are familiar with the FlatRedBall Engine, then you may know that the solution is simply to set the Velocity value (to 100 in this case). But, how exactly does Velocity work, and also, how could we determine the Position of a Sprite at a given time, like .7 seconds after the movement begins?
Using velocity is one way to accomplish interpolation. Most FRB objects have either a velocity or rate value, so this is one way to easily accomplish interpolation without any explicit management of your objects.
Velocity or rate of change is most often used to describe interpolation of positions. That is, if you want an object to move from one position to another, you need to calculate its velocity. Fortunately, this is a relatively easy calculation. It is as follows:
Or in a more concrete example, if you wanted to move from X = 6 to X = 12 in 7 seconds, then:
or
or
This assumes that velocity and amountOfTime both use the same unit of measuring time, such as the second. In FRB, all velocities are measured per second. The following formula calculates the distance to travel:
Be sure to subtract the startingPosition from the endingPosition and not the other way.
This formula must be performed for each axis if moving in 2 or 3 dimensions. Fortunately, with movement, each axis is independent. That is, the XVelocity can be calculated first, then YVelocity, then ZVelocity. There is no need to consider one when calculating the others.
Interpolation always depends on two variables (at least). In a two variable situation, one is independent while the other is dependent. For example, when using the previous position example, we can pick any time and ask about the X value at that given time. It's perfectly ok to say "What is the X .5 seconds after the object starts moving?" Considering the object will only move for 1 second, it isn't very useful to ask questions about the state of the object when more than one second has passed. However, the time that we choose doesn't depend on the current X value. On the other hand, the X value is determined by the amount of time that has passed since the object started moving creation. Because the X value depends on the amount of time that has passed, it is a dependent variable.
When discussing interpolations or equations which depend on time, it is common to abbreviate the variable representing time as "t". More accurately, t represents the amount of time that has passed from the initial state or condition. To return to the sliding object example, the object starts moving at t = 0. It finishes moving when t = 1 (again, assuming t is measured in seconds).
Since we know the inital conditions, we know the following:
But what about when t equals another value? One that may be obvious right away is:
This may make sense because .5 if halfway between 0 and 1, and 50 is halfway between 0 and 100. Another way to look at it is to realize that as t moves from 0 to 1, the value of X is a weighted average. At t = .5, the value is weighted 50% at t = 0, 50% at t = 1. The general formula for a linear interpolation is as follows:
value = startingValue * (1 - percentageToEnd) + endingValue * percentageToEnd
And percentageToEnd is:
percentageToEnd = (t - startingT)/(endingT - startingT)
You can see that as t moves closer to the endingT, then percentage to end increases and eventually becomes 1. In this case, the startingValue is multiplied by a value that approaches 0, while the endingValue is multiplied by a value which that approaches 1. If this confuses you, try running a few examples on paper.
Although "curved interpolation" may not be a formal term for what I am going to describe, I use it because it's very descriptive for what it is used. Using velocity, it is possible and not too difficult to write a method which takes a series of points and sets the velocity of an object at the appropriate times so that it moves through all of the points. If the object is a Sprite, then it can be done with relatively little code and no management using instructions. However, there are times when a curved path is more desirable. To accomplish this, the acceleration values (or acceleration instructions) can be used.
Just like linear interpolation, curved interpolation is axis independent. If you can do it on one axis, then you can extend it to 2 or 3 dimensions.
I'll begin with a basic example. Consider a situation where you want a particlar object to move from one position to another in a given amount of time. However, rather than moving at a constant speed and stopping once it arrives at its destination, you want the object to speed up and speed down, resulting in a "smooth" movement.
Keep this problem in the back of your mind as I present another situation. Let's say you have to drive 60 miles. To simplify it, we'll say the starting location (in miles) is x = 0 and the ending location is x = 60. Furthermore, let's say you had to be there in one hour. How fast would you drive? It's not to difficult to calculate the answer of 60 miles per hour. However, is this the only answer? What if you went faster for part of the trip? Then you could slow down a bit and still make it in one hour. In fact, it's impossible to make the trip with a constant velocity assuming you start the trip with the car parked or not moving. You can't hit the gas and instantly go 60, then instantly stop when you arrive.
Since you can slow down at any time during the trip and make up for it by going faster at another time, there are an infinite number of "ways" to drive the 60 miles. However, if you are to make the trip in exactly one hour, there is one thing that is constant - your average speed over the hour driven must be 60 miles per hour. That is, you could go 90 miles per hour for half of the time, then 30 miles per hour for the other half. The average would be:
.5 * 90 + .5 * 30 = 60
The first step to understanding the method of curved interpolation that I'm going to describe is to realize that you can change the velocity of an object during the interpolation as long as the average remains constant. No matter how you look at it, if you are to go from one point to another in a given amount of time (as long as distance and velocity is signed), your average speed will be constant.
We've seen that if you move one speed for half of the time, then another speed for the other half of the time, then the resutling average speed is the average of the two speeds moved - which makes sense. We could further "segment" the movement into 3 speeds. If a car drove 20 mph for 1/3 of an hour, 60 mph for 1/3 of an hour, and 90 mph for 1/3 of an hour, the resulting average speed is:
20*.3 + 60 * .3 + 90 * .3 = 56.667
To approach a more realistic scenario, if a car is increasing speed (accelerating) at a linear rate, then the average velocity is the average of the starting and ending velocity for that period of time. Keep in mind this is only the case when accelerating at a constant rate.
So if the image above was a graph of a car accelerating linearly from 0 to 60 mph, during the time it spent accelerating, it would have travelled the same distance as a car going a constant 30 mph for the same amount of time. We can generalize this to the following:
averageVelocity = (startingVelocity + endingVelocity) / 2.0
or
averageVelocity = startingVelocity * .5 + endingVelocity * .5
Using some simple algebra, it's possible to determine the ending velocity if the average velocity and the starting velocity is known. That is, if a car were traveling at 80 mph and was to travel 60 miles in one hour, if it were to linearly decelerate, what would its ending velocity be? The formula tells us:
endingVelocity = averageVelocity * 2 + - startingVelocity
or
endingVelocity = 60 * 2 - 80
or
endingVelocity = 40
You can verify that this is the case by averaging the starting velocity and ending velocity to see that it does indeed equal 60.
With the knowledge presented so far, we can already solve a common problem - decelerating an object to rest at a given position in a certain amount of time. For a pracical example, consider a menu system which has a cursor that the user can move from one item to the next. When the user pushes a direction, you would like the cursor to move to the next selectable position, but you'd like the cursor to slow down as it approaches its destination and stop on the menu item. Again, I'll present the example using only one axis, but the exact same can be done for both X and Y to have the object move around in 2 dimensions.
Our two known variables are ending velocity which will be 0 and time, which we will use as 1 second for simplicity. All that is left then is to calculate the starting velocity and the acceleration. The following code moves the cursor from the currentMenuItem's X value to the nextMenuItem's X in the given amount of time.
The example in the Curved Interpolation I section provides a method of slowing down or speeding up objects predictably. However, there are a few limitations when using this method.
Consider the relationship between starting velocity, ending velocity, and average velocity. The average velocity, by name, is the average of the starting and ending velocity. One property of averages is that when averaging two numbers, the average can not be greater or less than both numbers. It must always fall between the two numbers unless they equal eachother - in which case, all three numbers will be equal. Graphically, this means that the average must at some point cross the line that connects the starting and ending velocities, as is shown above in the two images.
This presents a problem if the average velocity is not between the starting and ending. For example, if a car is to drive from one position to another, and it must start and end the trip parked, then its starting and ending velocities will be 0. However, the average of 0 and 0 is (you guessed it) 0. But if the car is to move during its trip, its velocity must be greater than 0 at some point.
Another problem which is related is that the curved interpolation described in the previous section has a constant acceleration. That is, if an object is speeding up, it cannot also be slowing down at the same time.
The following graphic shows a situation where the object is to be in rest at both the starting and ending positions, but it must travel a distance.
Fortunately, we can simply extend a little on what we know so far to solve this problem easily. Consider the following example. A car begins its trip at 0 mph, and linearly accelerates to 60 mph in a certain amount of time. Since the acceleration is linear, we know that the average speed of that trip is 30 mph. Now consider the opposite - a car begins its trip at 60 mph and ends its trip at 0 mph, also linearly accelerating (or decelerating) in the exact same amount of time. Again, in this situation, the average velocity is also 30 mph.
What is interesting about this is that if take two trips which both took the exact same amount of time and combine them into one trip, then the average velocity for the new two-in-one trip is the average of the average times of the other two trips. That is, the 0 to 60 trip averaged 30 mph, and the 60 to 0 trip also averaged 30 mph. Therefore, putting one right after the other will also average 30 mph, as shown in the following graph:
Observe that this graph shows the solution to our previous problem - that is, the starting and ending velocities are both less than the required average velocity. By inserting a "midpoint" velocity and splitting up the trip into two trips, we are able to meet the required average velocity without changing the start or end velocities. In fact, by splitting up a trip into two trips, any problem can be solved regardless of starting velocity, ending velocity, or amount of time to take on the trip (which sets the required velocity).
The math behind this may seem like it is compliated, but we are simply going to combine two formulas together to create one final formula. First, I'll begin with some formulas that are familar. I will break the interpolation into two trips: trip1 and trip2. When discussing the entire trip, I will use the entireTrip variable or prefix.
As stated before, the entire trip's average velocity is the average of the average velocity of the other two trips assuming the two trips are of equal time. What a mouthful!
Also, keep in mind that the velocity at the end of trip1 is the same as the velocity at the beginning of trip2, so the following is true:
In our formulas, the trip1StartVelocity, trip2EndVelocity, and entireTripAverageVelocity are all determined. The variable that we need to calculate is the midpointVelocity.
Since the average velocity of the entire trip is the average of the average of the two trips, we know the following:
Now, that's a little ugly, so let's simplify it
Since we know that the midpoint velocity is equal to trip 1's end and trip 2's start:
As mentioned before, the value we need to calculate is midpointVelocity. With some algebra, we can get the following equation:
And finally, to replace endTripAverageVelocity:
With this method, it is possible to create a series of "trips" using instructions to move between points. Keep in mind that the axes are independent, so this can be performed on both the X and Y axes to create 2D movement - even on the Z axis for 3D movement. Using non-zero velocities for end points when connecting interpolations together allows for the creation of curved paths similar to splines.
As mentioned previously, using this technique, it is possible to create curved path movements using simple acceleration and velocity settings. Perhaps the most exciting aspect of this method is that it doesn't require advanced knowledge of math. So far, we've accomplished speeding up and slowing down from point to point in a predictable amount of time with the use of simple averages.
In Curved Movement II, we discused the ability to move from one point to another by speeding up and slowing down. However, this method works for all situations. That is, the exact same method can be used for situations where the average velocity is greater than both the start and end, where it is between the start and end velocities, and even where the average velocity is less than both the start and end velocities. Therefore, regardless of the starting and ending points, velocities at these points, or the amount of time that the trip should take, this method will calculate the required acceleration settings initially and at the midpoint.
Or rather, with the list of a few point coordinates and time, this method could be used to construct a curved path.
To begin, consider the following points.
To move between these points, we have a few options. The first is simple linear interpolation. To do this, calculate the differences in X and Y and set the velocities so that the object moves between the points correctly. As discussed in the previous sections, we can calculate the required average velocity to get between one point to the next, calculate what the midpoint velocity will have to be, then set the accelerations at the given time. However, this method is still not what would be considered curved movement. If the velocities at each point is 0, the object will speed up as it moves away from the previous point and slow down as it approaches the next point, but the path followed is the same path as a linear interpolation, as shown in the following image.
The key to curved movement is to set a velocity at the points so that the object doesn't stop when reaching each point. One simple way to determine the velocity at a given point is to look at the previous and next points and calculate the required velocity if the object were to move directly from the previous to the next point. That is, the velocity to move from t = 0 to t = 2 would be set as the velocity at t = 1. Running the formula using this velocity gives the following result.
Obviously, you will only be able to use this method to calculate velocities at points which are not endpoints. This method is great for creating curved movement through a set of points, but it is not a requirement. Any velocity can be set at points. Doing so will allows for deformation of the curve, as shown in the following image.