Now that you’re familiar with the fundamentals of 3D (spaces, coordinates, projections, and rotations) and the processes that are performed by a renderer, it’s finally time to begin writing a quick and clean simple 3D renderer in Action Script 3 language. I chose AS3 because it’s easy and quick to develop, but if you don’t like AS3 you can use any language of your choice and apply the same principles shown in this lesson.
Note: To compile the code shown throughout this lesson you will need an ActionScript 3 compiler such as Adobe Flex or an alternative such as AS3Compile from SWFTools which can be downloaded for free from http://www.swftools.org/. The code was written to be compliant with AS3Compile, it’s highly recommended to use it.
1. What To Do From Here
The first thing we need to do is to write the core classes of our system, we will be using fully object oriented programming and I’m sure you’ll enjoy the following paragraphs, it’s finally time to see previous knowledge from a much more practical point of view. By core classes I mean the classes that will be used as a base for everything else in our system. In case you’re not familiar with the term “class”, please visit http://en.wikipedia.org/wiki/Class_(computer_programming) for more information.
If you’re not familiar with AS3 or any programming language for that matter – don’t worry, the examples shown here are ready-to-run, you can just copy+paste them and run them yourself without problems, this way you can test everything first and if you see it’s worth learning a programming language then you’ll have enough motivation already.
Our simple yet useful 3D engine library will be called “xr”, so create a new folder named “xr”. Everytime you need to import the xr library into your AS3 project just copy the entire folder to your new project’s folder. We will not use a stacked model like OpenGL, instead we will use a method much simpler and easier to understand.
Today we will prepare the core classes of the renderer (only the basic ones, other more advanced elements will be done later on), these classes are:
- Vector2D
- Vector3D
- Matrix3D
To begin create a sub-folder named core on the xr folder. All files we will create have “.as” extension and should be placed on this folder.
2. Vector2D.as
[code]
/**
** Vector2D.as
**
** Definition of a 2D vector.
**
** Written by J. Palencia (support@ciachn.com) © 2009-2011
*/
package xr.core
{
/**
** @prot: public class Vector2D
**
** @desc: Describes a vector in 2D-space, that is an object containing
** two coordinates x and y.
*/
public class Vector2D
{
/**
** @prot: public var x: Number;
**
** @desc: X-coordinate (horizontal axis) value of the vector.
*/
public var x: Number;
/**
** @prot: public var y: Number;
**
** @desc: Y-coordinate (vertical axis) value of the vector.
*/
public var y: Number;
/**
** @prot: public function Vector2D (x: Number = 0, y: Number = 0)
**
** @desc: Constructor of the 2D vector. If no parameters are provided
** a vector at the origin (0, 0) is created.
*/
public function Vector2D (x: Number = 0, y: Number = 0)
{
this.x = x;
this.y = y;
}
/**
** @prot: public function clone () : Object
**
** @desc: Returns a clone of the object.
*/
public function clone () : Object
{
return new Vector2D (x, y);
}
};
};
[/code]
As you can see this class is used for storage only, there are no operations at all, this is because we are not really using 2D vectors for calculations, instead we use this class only as a temporal storage when we convert vectors from 3D space to 2D space.
3. Vector3D.as
[code]
/**
** Vector3D.as
**
** Definition of a 3D vector.
**
** Written by J. Palencia (support@ciachn.com) © 2009-2011
*/
package xr.core
{
/**
** @prot: public class Vector3D
**
** @desc: Describes a vector in 3D space, that is an object containing
** three coordinates x, y and z. Several methods to manipulate a
** 3D vector are also provided.
*/
public class Vector3D
{
/**
** @prot: public var x: Number;
**
** @desc: X-coordinate (horizontal axis) value of the vector.
*/
public var x: Number;
/**
** @prot: public var y: Number;
**
** @desc: Y-coordinate (vertical axis) value of the vector.
*/
public var y: Number;
/**
** @prot: public var z: Number;
**
** @desc: Z-coordinate (depth axis) value of the vector.
*/
public var z: Number;
/**
** @prot: public var w: Number;
**
** @desc: W-coordinate (homogeneous coordinate).
*/
public var w: Number;
/**
** @prot: public function Vector3D (x: Number = 0, y: Number = 0, z: Number = 0, w: Number = 1)
**
** @desc: Constructor of the 3D vector. If no parameters are provided
** a vector at the origin (0, 0, 0, 1) is created.
*/
public function Vector3D (x: Number = 0, y: Number = 0, z: Number = 0, w: Number = 1)
{
this.x = x;
this.y = y;
this.z = z;
this.w = w;
}
/**
** @prot: public function scaleByVector (vector: Vector3D) : Vector3D
**
** @desc: Multiplies corresponding coordinate values of the vectors and
** updates the current vector values.
*/
public function scaleByVector (vector: Vector3D) : Vector3D
{
x *= vector.x;
y *= vector.y;
z *= vector.z;
w *= vector.w;
return this;
}
/**
** @prot: public function scaleByFactor (factor: Number) : Vector3D
**
** @desc: Updates each coordinate with the product of the coordinate
** and the given factor.
*/
public function scaleByFactor (factor: Number) : Vector3D
{
x *= factor;
y *= factor;
z *= factor;
w *= factor;
return this;
}
/**
** @prot: public function sumVector (vector: Vector3D) : Vector3D
**
** @desc: Sums the corresponding coordinates of the vectors and
** updates the current vector values.
*/
public function sumVector (vector: Vector3D) : Vector3D
{
x += vector.x;
y += vector.y;
z += vector.z;
w += vector.w;
return this;
}
/**
** @prot: public function subVector (vector: Vector3D) : Vector3D
**
** @desc: Subtracts the corresponding coordinates of the vectors and
** updates the current vector values.
*/
public function subVector (vector: Vector3D) : Vector3D
{
x -= vector.x;
y -= vector.y;
z -= vector.z;
w -= vector.w;
return this;
}
/**
** @prot: public function norm () : Number
**
** @desc: Returns the norm of the vector, that is the square root of
** the sum of squared coordinates.
*/
public function norm () : Number
{
return Math.sqrt (x*x + y*y + z*z + w*w);
}
/**
** @prot: public function crossProduct (vector: Vector3D) : Vector3D
**
** @desc: Builds a new vector formed by the result of the cross product
** between the current and the given vector.
*/
public function crossProduct (vector: Vector3D) : Vector3D
{
return new Vector3D (
y*vector.z – vector.y*z,
-x*vector.z + vector.x*z,
x*vector.y – vector.x*y
);
}
/**
** @prot: public function dotProduct (vector: Vector3D) : Number
**
** @desc: Returns the dot product of the current vector and the given one.
*/
public function dotProduct (vector: Vector3D) : Number
{
return x*vector.x + y*vector.y + z*vector.z + w*vector.w;
}
/**
** @prot: public function normalize () : Vector3D
**
** @desc: Returns a new vector that contains the normalized components
** of the current vector.
*/
public function normalize () : Vector3D
{
var n: Number = this.norm ();
return new Vector3D (x/n, y/n, z/n, w/n);
}
/**
** @prot: public function clone () : Object
**
** @desc: Returns a clone of the object.
*/
public function clone () : Object
{
return new Vector3D (x, y, z, w);
}
};
};
[/code]
The 3D vector class besides containing standard storage for the four coordinates (x, y, z and w) it also has many methods to operate over the vectors, methods such as scale, subtraction, addition, normalization, cross product, and dot product.
The 3D vector will eventually represent the position of a vertex, and a vertex will contain information related to the actual object, such as texture coordinates, light colors, etc. But for now, we’ll just write the basic 3D vector as shown above.
4. Matrix3D.as
[code]
/**
** Matrix3D.as
**
** Definition of a 3D matrix.
**
** Written by J. Palencia (support@ciachn.com) © 2009-2011
*/
package xr.core
{
/**
** @prot: public class Matrix3D
**
** @desc: Provides an interface to create and manipulate homogeneous 4×4 matrices.
*/
public class Matrix3D
{
/**
** @prot: public var values: Array;
**
** @desc: Contains the actual values of the matrix, this array contains a total
** of sixteen (16) floating-point elements.
*/
public var values: Array;
/**
** @prot: public function Matrix3D (values: Array = null)
**
** @desc: Constructs an instance of the matrix class, the given parameter contains
** the values to store in the matrix, if not provided, a zero-matrix will
** be created.
*/
public function Matrix3D (values: Array = null)
{
if (values == null)
this.values = new Array (0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0);
else
this.values = values;
}
/**
** @prot: public static function identity () : Matrix3D
**
** @desc: Returns a new identity matrix, that is, a matrix with it’s diagonal
** set to ones and the rest to zero.
*/
public static function identity () : Matrix3D
{
return new Matrix3D (new Array
(
1.000, 0.000, 0.000, 0.000,
0.000, 1.000, 0.000, 0.000,
0.000, 0.000, 1.000, 0.000,
0.000, 0.000, 0.000, 1.000
));
}
/**
** @prot: public function mul (p: Matrix3D) : Matrix3D
**
** @desc: Returns the product of the current matrix and the given one (p), returns
** a completely new matrix.
*/
public function mul (p: Matrix3D) : Matrix3D
{
var result:Matrix3D = new Matrix3D ();
for (var i:uint = 0; i < 16; i += 4)
{
for (var k:uint = 0; k < 4; k++)
{
var sum:Number = 0;
for (var j:uint = 0; j < 4; j++)
sum += values[i+j] * p.values[k+j*4];
result.values[i+k] = sum;
}
}
return result;
}
/**
** @prot: public function sum (p: Matrix3D) : Matrix3D
**
** @desc: Returns the sum of the current matrix and the given one (p), returns
** a completely new matrix.
*/
public function sum (p: Matrix3D) : Matrix3D
{
var result:Matrix3D = new Matrix3D ();
for (var i:uint = 0; i < 16; i += 4)
{
for (var j:uint = 0; j < 4; j++)
result.values[i+j] = values[i+j] + p.values[i+j];
}
return result;
}
/**
** @prot: public function transpose () : Matrix3D
**
** @desc: Transposes the matrix (reverts the order of the components of the matrix
** from ij to ji) and returns a new matrix.
*/
public function transpose () : Matrix3D
{
var result:Matrix3D = new Matrix3D ();
for (var j:uint = 0; j < 4; j++)
{
for (var i:uint = 0; i < 4; i++)
result.values[j*16+i] = values[i*16 + j];
}
return result;
}
/**
** @prot: public function mulScalar (f: Number) : Matrix3D
**
** @desc: Multiplies the matrix by the given scalar and returns a new matrix.
*/
public function mulScalar (f: Number) : Matrix3D
{
var result:Matrix3D = new Matrix3D ();
for (var i:uint = 0; i < 16; i += 4)
{
for (var j:uint = 0; j < 4; j++)
result.values[i+j] = values[i+j] * f;
}
return result;
}
/**
** @prot: public static function translationMatrix (x:Number, y:Number, z:Number) : Matrix3D
**
** @desc: Returns a translation matrix for the given components.
*/
public static function translationMatrix (x:Number, y:Number, z:Number) : Matrix3D
{
var result:Matrix3D = Matrix3D.identity ();
result.values[0x03] = x;
result.values[0x07] = y;
result.values[0x0B] = z;
return result;
}
/**
** @prot: public static function rotationMatrix (f:Number, x:Number, y:Number, z:Number) : Matrix3D
**
** @desc: Returns a rotation matrix configured to rotate by the given factor (f) along
** the given arbitrary axis (x, y, z).
*/
public static function rotationMatrix (f:Number, x:Number, y:Number, z:Number) : Matrix3D
{
var result:Matrix3D = new Matrix3D ();
var C:Number = 1.0 – Math.cos (f);
var s:Number = Math.sin (f);
var c:Number = Math.cos (f);
result.values[0] = 1 + C*(x*x – 1);
result.values[1] = C*x*y – z*s;
result.values[2] = C*x*z + y*s;
result.values[4] = C*x*y + z*s;
result.values[5] = 1 + C*(y*y – 1);
result.values[6] = C*y*z – x*s;
result.values[8] = C*x*z – y*s;
result.values[9] = C*y*z + x*s;
result.values[10] = 1 + C*(z*z – 1);
result.values[15] = 1.0;
return result;
}
/**
** @prot: public static function scalationMatrix (fx:Number, fy:Number, fz:Number) : Matrix3D
**
** @desc: Returns an scalation matrix configured to scale each x, y and z component
** by the given factors respectively.
*/
public static function scalationMatrix (fx:Number, fy:Number, fz:Number) : Matrix3D
{
var result:Matrix3D = Matrix3D.identity ();
result.values[0x00] = fx;
result.values[0x05] = fy;
result.values[0x0A] = fz;
return result;
}
/**
** @prot: public function mulv (v:Vector3D, rewrite: Boolean = false) : Vector3D
**
** @desc: Returns a vector that is the product of the current matrix and the
** given vector. If rewrite is set the vector components will be updated
** and returned, otherwise a new vector is returned.
*/
public function mulv (v:Vector3D, rewrite: Boolean = false) : Vector3D
{
var x:Number, y:Number, z:Number, w:Number;
x = values[0x00+0] * v.x;
x += values[0x00+1] * v.y;
x += values[0x00+2] * v.z;
x += values[0x00+3] * v.w;
y = values[0x04+0] * v.x;
y += values[0x04+1] * v.y;
y += values[0x04+2] * v.z;
y += values[0x04+3] * v.w;
z = values[0x08+0] * v.x;
z += values[0x08+1] * v.y;
z += values[0x08+2] * v.z;
z += values[0x08+3] * v.w;
w = values[0x0C+0] * v.x;
w += values[0x0C+1] * v.y;
w += values[0x0C+2] * v.z;
w += values[0x0C+3] * v.w;
if (rewrite)
{
v.x = x; v.y = y; v.z = z; v.w = w;
return v;
}
else
return new Vector3D (x, y, z, w);
}
/**
** @prot: public function translate (x:Number, y:Number, z:Number, r:Boolean=false) : Matrix3D
**
** @desc: Translates the current matrix to the given point and returns
** a new matrix.
*/
public function translate (x:Number, y:Number, z:Number, r:Boolean=false) : Matrix3D
{
if (r)
return Matrix3D.translationMatrix (x, y, z).mul (this);
else
return this.mul (Matrix3D.translationMatrix (x, y, z));
}
/**
** @prot: public function rotate (f:Number, x:Number, y:Number, z:Number, r:Boolean=false) : Matrix3D
**
** @desc: Rotates the current matrix by the given factor along the given
** arbitrary axis, returns a new matrix.
*/
public function rotate (f:Number, x:Number, y:Number, z:Number, r:Boolean=false) : Matrix3D
{
if (r)
return Matrix3D.rotationMatrix (f, x, y, z).mul (this);
else
return this.mul (Matrix3D.rotationMatrix (f, x, y, z));
}
/**
** @prot: public function scale (fx:Number, fy:Number, fz:Number, r:Boolean=false) : Matrix3D
**
** @desc: Scales the current matrix by the given factors and returns
** a new matrix.
*/
public function scale (fx:Number, fy:Number, fz:Number, r:Boolean=false) : Matrix3D
{
if (r)
return Matrix3D.scalationMatrix (fx, fy, fz).mul (this);
else
return this.mul (Matrix3D.scalationMatrix (fx, fy, fz));
}
/**
** @prot: public function clone () : Matrix3D
**
** @desc: Returns a clone of the matrix.
*/
public function clone () : Matrix3D
{
var newObj:Matrix3D = new Matrix3D ();
newObj.values = values.concat ();
return newObj;
}
};
};
[/code]
This is a very important class, it may not be as optimized as it should be, but the code is clean and as crystal clear as I could write it, it can be easily understood and will help you to develop more optimized classes later on.
The Matrix3D class stores only 16 floating point numbers representing each element of the 4×4 matrix, the rest of the class are a bunch of methods to help manipulate the matrix, and as you may notice, rotation, scaling and translation is embedded in the class, which will make it quite easy to build our Model View matrices once we get there!!
Right now you should have a folder named “xr” and inside it should be another folder named “core”. The core folder has (for now) only three files, Vector2D.as, Vector3D.as and Matrix3D.as – On the next lesson we will continue writing the rest of the classes in order to actually build our first “Hello World”.