Vector Math is a key element to any 3D Game. You can't do anything without it. So, it seems like a great place to start. This is the vector math class that I use in my game. Hopefully this is useful to someone even if they don't follow the rest of this tutorial.
The class is pretty basic except for one thing, I implemented a simple stack. We will come back to that. Other than the unique stack, the rest of the class is self explanatory. Here is the class:
- Code: Select all
public class Vector3
{
public float x,y,z;
// ****************************
// Stack allocation and freeing
// ****************************
private static int STACK_SIZE = 100;
private static Vector3[] stack;
private static boolean initialized = false;
private static int stack_index = 0;
public static Vector3 alloc()
{
if (!initialized)
init();
return stack[stack_index++];
}
public static void free()
{
if (!initialized)
init();
if (stack_index > 0)
stack_index--;
}
private static void init()
{
stack = new Vector3[STACK_SIZE];
for (int i=0; i<STACK_SIZE; i++)
{
stack[i] = new Vector3();
}
initialized = true;
}
// Constructor
public Vector3()
{
x = 0;
y = 0;
z = 0;
}
public Vector3(float xin, float yin, float zin)
{
x=xin; y=yin; z=zin;
}
public void Set(float xin, float yin, float zin)
{
x=xin; y=yin; z=zin;
}
static public void add(Vector3 v1, Vector3 v2, Vector3 v3)
{
v1.x = v2.x + v3.x;
v1.y = v2.y + v3.y;
v1.z = v2.z + v3.z;
}
static public void subtract(Vector3 v1, Vector3 v2, Vector3 v3)
{
v1.x = v2.x - v3.x;
v1.y = v2.y - v3.y;
v1.z = v2.z - v3.z;
}
static public void copy(Vector3 v1, Vector3 v2)
{
v1.x = v2.x;
v1.y = v2.y;
v1.z = v2.z;
}
static public void reflect(Vector3 result, Vector3 dir, Vector3 normal, float damp)
{
float len;
float ratio;
float dot;
Vector3 v1 = alloc();
Vector3 v2 = alloc();
Vector3 v3 = alloc();
len = length(dir);
normalize(v1,dir);
normalize(v2,normal);
inverse(v3,v1);
dot = (1+damp)*dot_product(v3,v2);
scale(v2,v2,dot);
add(result,v1,v2);
ratio = length(v1);
scale(result,result,len*ratio);
free();
free();
free();
}
static public void inverse(Vector3 v1, Vector3 v2)
{
v1.x = -v2.x;
v1.y = -v2.y;
v1.z = -v2.z;
}
static public void scale(Vector3 v1, Vector3 v2, float s)
{
v1.x = v2.x * s;
v1.y = v2.y * s;
v1.z = v2.z * s;
}
static public float length(Vector3 v1)
{
return (float) Math.sqrt(v1.x * v1.x + v1.y * v1.y + v1.z * v1.z);
}
static public void normalize(Vector3 result, Vector3 v1)
{
float len = length(v1);
if (len != 0)
{
result.x = v1.x / len;
result.y = v1.y / len;
result.z = v1.z / len;
}
}
// Written with temp vector in order to work when result is also one of
// other two parameters
static public void cross_product(Vector3 result, Vector3 v1, Vector3 v2)
{
Vector3 temp = alloc();
temp.x = v1.y * v2.z - v1.z * v2.y;
temp.y = v1.z * v2.x - v1.x * v2.z;
temp.z = v1.x * v2.y - v1.y * v2.x;
copy(result,temp);
free();
}
static public float dot_product(Vector3 v, Vector3 w)
{
return( v.x * w.x + v.y * w.y + v.z * w.z);
}
}
An instance of this class holds three member variables for the vector: x,y,z. The rest of the class is a series of static methods for doing math. Personally, I prefer that the math methods are static because it makes other code more readable (at least to me) and it is a little faster at execution. You can change this class to have these methods not be static if you want, but I wouldn't.
There are methods for scaling, addition, subtraction, normalization, dot and cross products, and reflection. Each method, other than those that return a variable, puts the result of the operation in the first parameter. Here is an example of some code using the class.
- Code: Select all
// A function to keep the direction of your velocity but change the magnitude
public void changeVelocity(Vector3 velocity, float magnitude)
{
// Normalize the velocity
Vector3.normalize(velocity,velocity);
// Scale the normalized velocity using the incoming magnitude
Vector3.scale(velocity,magnitude);
}
The only really unique concept in the class is the stack. Garbage collection is a very bad thing for games. You don't want ANY garbage collection in your games. That said, there is often the need to create temporary objects in functions to hold working data. But, you don't want to create these objects using new or you get garbage. So, my solution, although others probably won't like it, was to create a stack for my objects. In this case, if you need a temp Vector3 object in a function, you can call the static alloc to get it and later call the static free to release it. Typically, you would allocate at the beginning of a function and free at the end. For example:
- Code: Select all
// In this method we want to change v1 using v2 BUT NOT CHANGE the data in v2.
// In order to do this we need to copy v2 into a temporary variable, manipulate it,
// and then add it to v1.
public void doSomeStuff (Vector3 v1, Vector3 v2)
{
// Allocate a temp Vector3 on the stack
Vector3 temp_vector = Vector3.alloc();
Vector3.normalize(temp_vector,v1);
Vector3.inverse(temp_vector,temp_vector);
Vector3.add(v1,temp_vector);
Vector3.free(); // temp_vector
}
OK, well this is really convenient and creates NO garbage. Are there any problems with it. The short answer is TONS! You must be extremely careful with the stack allocation. I wrote this for my own use and have not tried to make it safe to use in any way. I am depending on my own ability to be safe when using it. In a tutorial, providing this unsafe method is probably a really bad thing to do. But, it is what I am doing. I will leave it up to others to provide better solutions. I will tell you what to look out for.
1) Stack size. You must size your stack correctly. If you don't, you will get a bounds exception.
2) Don't forget the frees. If you do, well then you are in trouble. You will probably get unexplained exceptions. What I do is comment every free with the variable that it applies to. You need an equal number of frees at the end of each function to the number of allocs at the beginning of the function.
3) Don't return early. If you do, make sure you free there too.
4) THIS IS NOT THREAD SAFE. Don't allocate Vector3s in multiple threads using alloc, period. Making it thread safe is probably pretty easy, I just didn't have a need to do it.
Well, that is is. My Vector3 class. Not much of a start but without it we go nowhere.
Coming next:
Part 2 - An OpenGl framework application.
Part 3 - Organizing Game Objects into Classes