Category Archives: Technical 3D

3D Rendering Tutorial Part Two

Welcome again! It’s time to continue writing more code for our small, simple and regularly fast 3D renderer. Today we will write a few more classes: Vertex3D, Plane3D, Object3D, World3D, FlatFiller, and Triangle. – Our “Hello World”, which is actually a rotating flat-filled triangle, will also be written on this lesson.

1. Vertex3D.as

A 3D vertex is basically a 3D point (x, y, z) that includes more information related to the presentation of the object that contains the vertex. Information such as color, texture information, light and more. – But for now, we’ll write a small vertex code that has includes only color information.

The following code goes in a file named “Vertex3D.as” on the “xr/core” folder.
[code]
/**
** Vertex3D.as
**
** Definition of a 3D Vertex3D.
**
** Written by J. Palencia (support@ciachn.com) (c) 2009-2011
*/

package xr.core
{
/**
** @prot: public class Vertex3D
**
** @desc: Describes a 3D Vertex. That is, a vector in 3D space that also
** has related properties such as light, texture information, etc.
*/

public class Vertex3D extends xr.core.Vector3D
{
/**
** @prot: public var vs: xr.core.Vector3D;
**
** @desc: Coordinates of the vertex in view-space (not world-space).
*/

public var vs: xr.core.Vector3D = new Vector3D ();

/**
** @prot: public var color: uint;
**
** @desc: Color of the vertex.
*/

public var color: uint;

/**
** @prot: public var _2d: Vector2D = null;
**
** @desc: Coordinates of the point in 2D space of the 3D vertex after
** a 3D to 2D projection is performed.
*/

public var _2d: Vector2D = new Vector2D ();

/**
** @prot: public function Vertex3D (x: Number = 0, y: Number = 0, z: Number = 0)
**
** @desc: Constructor of the 3D vertex. If no parameters are provided
** a vector at the origin (0, 0, 0) is created.
*/

public function Vertex3D (x: Number = 0, y: Number = 0, z: Number = 0)
{
this.x = x;
this.y = y;
this.z = z;
}

/**
** @prot: public function project () : void
**
** @desc: Performs a projection and stores the resulting 2D coordinates on
** the point2d object of the vertex.
*/

public function project () : void
{
this._2d.x = 0.5*640 + 256 * (vs.x / (vs.z + 256));
this._2d.y = 0.5*480 – 256 * (vs.y / (vs.z + 256));
}

/**
** @prot: public function transform (tm: Matrix3D) : void
**
** @desc: Transforms the vertex using the given matrix.
*/

public function transform (tm: Matrix3D) : void
{
tm.mulv (vs, true);
}

/**
** @prot: public function reset () : Vector3D
**
** @desc: Resets the ViewSpace vector and returns it.
*/

public function reset () : Vector3D
{
this.vs.x = x;
this.vs.y = y;
this.vs.z = z;
this.vs.w = w;

return this.vs;
}

};
};
[/code]
As you can see, the actual information is only the “color” field, which specifies the color of the vertex. As we advance to make a slightly more complicated renderer, we will modify this class to include more informational fields.

2. Plane3D.as

This is the most important part of the engine because all objects in our world are made up of planes. Since this will be seen thousands of times in the object queue it needs to be small and simple. Same as last time, this file goes into the “xr/core” folder.
[code]
/**
** Plane3D.as
**
** Definition of a plane in 3D space.
**
** Written by J. Palencia (support@ciachn.com) (c) 2009-2011
*/

package xr.core
{
/**
** @prot: public class Plane3D
**
** @desc: Describes a 3D plane. This is the most important part of the
** engine because all objects are made up of planes.
*/

public class Plane3D
{
/**
** @prot: public var v1: Vertex3D, v2: Vertex3D, v3: Vertex3D;
**
** @desc: Vertices that make up the 3D plane. Only three vertices are
** needed because we are using triangular planes.
*/

public var v1: Vertex3D;
public var v2: Vertex3D;
public var v3: Vertex3D;

/**
** @prot: public var filler: String;
**
** @desc: Name of the filler class. This is used to fill the plane.
*/

public var filler: String;

/**
** @prot: public function Plane3D (v1: Vertex3D, v2: Vertex3D, v3: Vertex3D)
**
** @desc: Constructor of the 3D plane.
*/

public function Plane3D (v1: Vertex3D = null, v2: Vertex3D = null, v3: Vertex3D = null)
{
this.v1 = v1;
this.v2 = v2;
this.v3 = v3;
}

/**
** @prot: public function depth () : Number
**
** @desc: Returns an approximation of the depth level of the plane, this
** is calculated as the average of the Z coordinate of each vertex.
*/

public function depth () : Number
{
return (this.v1.z + this.v2.z + this.v3.z) / 3;
}

/**
** @prot: public function project () : void
**
** @desc: Performs a projection on each of the vertices of the plane.
*/

public function project () : void
{
v1.project ();
v2.project ();
v3.project ();
}

/**
** @prot: public function transform (tm: Matrix3D) : void
**
** @desc: Transforms the vertices of the plane.
*/

public function transform (tm: Matrix3D) : void
{
v1.transform ™;
v2.transform ™;
v3.transform ™;
}
};
};
[/code]

The depth() function is used by the renderer to perform object ordering before showing the objects, this will be highly required to build a correct final scene. Later on we will see two small flash files, one with depth ordering and one without it, the difference will be highly noticeable.

The “filler” string is used to determine what is the class that fills the contents of the plane, a string is better because it needs little storage. On this lesson we will write code for one common filler known as the FlatFiller (for flat filling, that is a plain-single-colored plane 🙂

3. Object3D.as

The Object3D class provides a base for all objects that are used for modelling. It provides the famous ModelView matrix and currently only the render() method which should be overridden by child classes.
[code]
/**
** Object3D.as
**
** Generic 3D object. It is the base object for all modelling object. It includes
** model view matrix and rendering methods.
**
** Written by J. Palencia (support@ciachn.com) (c) 2009-2011
*/

package xr.core
{
/**
** @prot: public class Object3D
**
** @desc: Generic 3D object. It is the base object for all modelling
** object. It includes model view matrix and rendering methods.
*/

public class Object3D
{
/**
** @prot: public var modelView: Matrix3D;
**
** @desc: Model view matrix of the object.
*/

public var modelView: Matrix3D;

/**
** @prot: public function Object3D ()
**
** @desc: Constructor of the generic object.
*/

public function Object3D ()
{
this.modelView = Matrix3D.identity ();
}

/**
** @prot: public function render () : void
**
** @desc: Renders by transforming the object and generating fundamental
** objects (Plane3D) if necessary, the results are sent to the
** World’s rendering queue for further drawing.
*/

public function render () : void
{
// Should be implemented by child classes.
}
};
};
[/code]

4. World3D.as

[code]
/**
** World3D.as
**
** The world global object provides a clean mechanism to store and render objects
** using their “object data” which in this scope are just Plane3D objects.
**
** Written by J. Palencia (support@ciachn.com) (c) 2009-2011
*/

package xr.core
{
/**
** @prot: public class World3D
**
** @desc: The world global object provides a clean mechanism to store and
** render objects using their “object data” which in this scope are
** just Plane3D objects.
*/

public class World3D
{
/**
** @prot: public static var cameraView: Matrix3D;
**
** @desc: Camera view matrix of the world.
*/

public static var cameraView: Matrix3D = Matrix3D.identity ();

/**
** @prot: private static var queue: Array;
**
** @desc: Internal queue to store each Plane3D object.
*/

private static var queue: Array = new Array ();

/**
** @prot: private static var fillers: Object;
**
** @desc: List of registered plane fillers.
*/

private static var fillers: Object = new Object ();

/**
** @prot: public static function addFiller (name: String, code: Object) : Boolean
**
** @desc: Registers a filler in the system.
*/

public static function addFiller (name: String, code: Object) : Boolean
{
fillers[name] = code;
return true;
}

/**
** @prot: public static function clear () : void
**
** @desc: Clears the queue.
*/

public static function clear () : void
{
queue = new Array ();
}

/**
** @prot: public static function push (obj: Plane3D) : void
**
** @desc: Adds an object to the queue.
*/

public static function push (obj: Plane3D) : void
{
queue.push (obj);
}

/**
** @prot: public static function draw (g: flash.display.Graphics) : void
**
** @desc: Draws all primitive objects stored in the queue.
*/

public static function draw (g: flash.display.Graphics) : void
{
for each (var i in queue)
{
i.transform (cameraView);
i.project ();
}

g.clear ();

for each (var i in queue)
{
if (fillers[i.filler] == null)
continue;

fillers[i.filler].draw (i, g);
}
}
};
};
[/code]

5. FlatFiller.as

The FlatFiller provides an standard draw() method which will fill a provided plane with the color of the first vertex of the plane. The result is written to a graphics surface provided as a parameter. For this file you will need to create another folder on xr named “filler”. The file path should be xr/fillers/FlatFiller.as.
[code]
/**
** FlatFiller.as
**
** Flat filler. Nothing much to say.
**
** Written by J. Palencia (support@ciachn.com) (c) 2009-2011
*/

package xr.fillers
{
import xr.core.World3D;

/**
** @prot: public class FlatFiller
**
** @desc: Flat filler. Nothing much to say.
*/

public class FlatFiller
{
/**
** @prot: public static var reg: Boolean;
**
** @desc: Registers the FlatFiller class using the name “flat”.
*/

public static var reg: Boolean = World3D.addFiller (“flat”, new FlatFiller ());

/**
** @prot: public function draw (p: xr.core.Plane3D, g: Graphics) : void
**
** @desc: Draws the plane on the given graphics surface. The actual color
** used to fill is the color of the first vertex.
*/

public function draw (p: xr.core.Plane3D, g: flash.display.Graphics) : void
{
g.beginFill (p.v1.color);
g.moveTo (p.v1._2d.x, p.v1._2d.y);
g.lineTo (p.v2._2d.x, p.v2._2d.y);
g.lineTo (p.v3._2d.x, p.v3._2d.y);
g.endFill ();
}
};
};
[/code]
All filler classes should implement a draw() method as shown above receiving two parameters, one of type xr.core.Plane3D and another of type flash.display.Graphics. The filler can assume that the “_2d” field of the vertices of the plane are ready (aka already projected to 2D space).

A filler should also register itself using World3D.addFiller() with a string specifying its name, this way when a Plane3D object uses the same name on its “filler” field the correct object will be located.

6. Triangle.as

Since we’re now done with the core and the fillers, you can forget about those folders. Now create another one with the name “model”, here we will be storing the modelling object. And for our first object we’ll create “Triangle”.
[code]
/**
** Triangle.as
**
** Triangle class for object modelling.
**
** Written by J. Palencia (support@ciachn.com) (c) 2009-2011
*/

package xr.model
{
import xr.core.Object3D;

import xr.core.Vertex3D;
import xr.core.Plane3D;

import xr.core.World3D;

/**
** @prot: public class Triangle
**
** @desc: Triangle class for object modelling.
*/

public class Triangle extends xr.core.Object3D
{
/**
** @prot: public var plane: Plane3D;
**
** @desc: Plane that describes the triangle.
*/

public var plane: Plane3D = new Plane3D ();

/**
** Properties used to forward to the plane properties.
*/

public function get v1 () { return plane.v1; }
public function get v2 () { return plane.v2; }
public function get v3 () { return plane.v3; }
public function get filler () { return plane.filler; }

public function set v1 (v: Vertex3D) { plane.v1 = v; }
public function set v2 (v: Vertex3D) { plane.v2 = v; }
public function set v3 (v: Vertex3D) { plane.v3 = v; }
public function set filler (v: String) { plane.filler = v; }

/**
** @prot: public function Triangle (v1: Vertex3D, v2: Vertex3D, v3: Vertex3D)
**
** @desc: Constructor of the 3D triangle.
*/

public function Triangle (v1: Vertex3D = null, v2: Vertex3D = null, v3: Vertex3D = null)
{
this.v1 = v1;
this.v2 = v2;
this.v3 = v3;
}

/**
** @prot: public override function render () : void
**
** @desc: Renders the object. Overrides Object3D.render().
*/

public override function render () : void
{
modelView.mulv (plane.v1.reset (), true);
modelView.mulv (plane.v2.reset (), true);
modelView.mulv (plane.v3.reset (), true);

World3D.push (plane);
}
};
};
[/code]
Well, now we have everything we need for the hello world #1!! The following code is the Main.as file which will use all the stuff we have done so far.
[code]
/**
** Main.as
**
** Skeleton code for an ActionScript 3 application.
**
** Compile using: as3compile -X 640 -Y 480 Main.as
** Download as3compile from: http://www.swftools.org/
*/

import flash.display.MovieClip;
import flash.events.*;

import xr.core.Vector2D;
import xr.core.Vector3D;
import xr.core.Matrix3D;
import xr.core.Vertex3D;
import xr.core.Plane3D;
import xr.core.World3D;

import xr.fillers.FlatFiller;

import xr.model.Triangle;

// Main package.
package Main
{
// Main class.
public class Main extends MovieClip
{
public var a, b;

public function Main ()
{
var w: int = 50;
var h: int = 50;

a = new Triangle (); a.filler = “flat”;
a.v1 = new Vertex3D (w, h, 0);
a.v2 = new Vertex3D (-w, -h, 0);
a.v3 = new Vertex3D (-w, h, 0);
a.v1.color = 0xFF0000;

b = new Triangle (); b.filler = “flat”;
b.v1 = new Vertex3D (w, h, 0);
b.v2 = new Vertex3D (w, -h, 0);
b.v3 = new Vertex3D (-w, -h, 0);
b.v1.color = 0xFFF000;

stage.addEventListener (Event.ENTER_FRAME, onEnterFrame);
}

public function onEnterFrame (e: Event)
{
World3D.clear ();

World3D.cameraView =
World3D.cameraView
.rotate (0.06, 0, 0, 1)
.rotate (0.02, 0, 1, 0)
.rotate (0.07, 1, 0, 0);

a.render ();
b.render ();

World3D.draw (this.graphics);
}
};
};
[/code]
Don’t worry about compilation, or which folder to use. The result is a square with two colors that rotates on all axes on every frame that is updated on the AS3 project.

If you want to download the files (all of them) including the executables, examples and the batch files used to compile you can click here: http://www.ciachn.com/lab/3d/06.zip

Next lesson is going to be a little bit harder because it will be time to write a lot more object modelling classes and begin writing more complex figures! 🙂

3D101 Starting a Simple Renderer

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”.

 

 

3D 101 Expanding on Matrices

Affine Transformations

First and foremost, an affine transformation is simply a linear transformation followed by an addition. A linear transformation is an equation that operates on a vector to transform it into something else, the most popular linear transformations are rotation, and scaling.

Ordinarily we perform linear transformations by evaluating equations, take for example the equations for X-axis rotation found on the first lesson of this tutorial, which are defined by:

Z’ = Z*COS(Θ) – Y*SIN(Θ)

Y’ = Z*SIN(Θ) + Y*COS(Θ)

 

The above equations contain 4 multiplications and 2 additions, for a total of 6 arithmetic operations just for X-axis rotation. Now imagine we have about 3000 vertices that need to be rotated in X, Y and Z axes, and then need to be scaled (which requires 3 multiplications one for each vector component), that gives us a total of 63,000 operations just to rotate and scale each vertex, imagine if we had a more complex object that had more vertices and required more actions such as scaling more than once, or translating, and rotating using different axes, the final number of operations required would easily grow to millions, here’s where our beloved matrices come in!

Note: Before anything, there is need to mention a slight mistake in the previous lesson, when describing matrix multiplication I strictly said only squared matrices can be multiplied, but in fact that’s not true. In order to correctly multiply matrices A and B, the matrix A should be of size NxM and matrix B must be of size MxP, (in other words, the number of columns of A should match the number of rows of B), now after processing the multiplication the resulting matrix will be of size NxP. – Apologies for my mistake.

With matrices we are able to perform parallel multiplication of vectors, to make this clear imagine we combine the above equations into a single matrix, the result would be:

As you can see, the matrix shown above (A) can be multiplied with a 3D vector (V) and the result obtained (R) will be the same result as the one obtained with equations for X-axis rotation. But still, we have a small problem, what if our equations were changed to something like this:

 

Z’ = Z*COS(Θ) – Y*SIN(Θ) + 777

Y’ = Z*SIN(Θ) + Y*COS(Θ) + 555

 

I have deliberately added an addition on both equations (the +777 and +555), if you think about this for about five minutes you will realize there is no way to use a 3×3 matrix to produce the exact same output as these equations.

 

1. Homogeneous Coordinate Space

Homogeneous coordinate space is the solution to the problem we found a few seconds ago in the previous paragraph. The term “homogeneous” can be more easily understood as “to be symmetric”, for example imagine when you’re baking, and the cake mix has some small lumps, you whisk and keep whisking the mix until it’s smooth and all evened out, right? What you did was to obtain a “homogeneous” mix.

The same applies to matrix arithmetic, with matrices we are able to multiply, and divide, but addition of an scalar value is not possible. But when using homogeneous coordinates we are able to use all basic arithmetic operations and combine them in a single matrix.

Imagine we have the usual 3D vector <x, y, z>, to make it homogeneous the only thing we need is to add a new coordinate to the vector with the value of “1”, the actual letter for the new coordinate may vary, but most books use the letter “w”. Our vector <x, y, z> becomes the homogeneous vector <x, y, z, w> with “w” being the value “1”.

Similarly we do the same with our matrices, if we had a 3×3 matrix we will now add another column and another row to make it 4×4. And now we’re using homogeneous vectors and homogeneous matrices.

Let’s retry the previous problem, and let’s see if now we’re able to create a single matrix that can produce the same result as the equations shown below:

Z’ = Z*COS(Θ) – Y*SIN(Θ) + 777

Y’ = Z*SIN(Θ) + Y*COS(Θ) + 555

 

Our brand new homogenous matrix is:

The matrix “A” (also known as augmented matrix, or affine transformation matrix) effectively produces the correct result when multiplied with a homogeneous vector, and as you can see using the magical w coordinate we can perform addition within the same matrix without problems of any kind.

But what does this mean?

This means, one simple thing that’s incredibly important: We can now combine several linear transformations such as rotation, scaling and now even translation into one single matrix and use it only when we need it. A single matrix multiplication operation with a homogeneous vector will be the equivalent of evaluating ALL the linear transformations at once, saving indeed some speed and code complexity.

To combine the transformations, first you have to figure out the affine transformation matrix for the equations of the transformation you want to combine (let’s call this matrix B), just like we did on this lesson to figure out the matrix for the X-axis rotation.

Once you have that matrix, just multiply it with an existing matrix (let’s call it A) where A is the current matrix, which initially is set to an identity matrix. The identity matrix is a matrix where it’s diagonal contains only “1”s.

2. Affine Transformation Matrices

The following is a list of matrices for most of the operations we will be needing on our simple 3D renderer.

 

In all the matrices,  Θ is the angle in radians. fX, fY and fZ are scaling factors for the X, Y and Z axes respectively. And dX, dY and dZ are displacement values used when translating the X, Y and Z axes respectively.

Combine multiple matrices by multiplying them, and the resulting matrix will be the matrix representation of the operands. For example, if we have matrix A for X-rotation, matrix B for scaling matrix C for translation, and a homogeneous vector v which is the vector we want to transform. The goal is to perform A, B then C on the vector.

Evaluating step by step, we have:

v1 = A * v

v2 = B * v1

v3 = C * v2

v’ = v3

Above you can see v1, v2 and v3, which are temporal vectors which represent the product of the respective matrix with the previous vector. v’ represents the result vector. Now, the previous statements are equivalent to say:

v’ = C * (B * (A * v))

The following was obtained by replacing temporal vectors v1, v2, v3 on the final equation for the result “v’ = v3”. We can now remove parenthesis and we get:

v’ = C * B * A * v

Using associative properties of matrices we can continue factorizing to obtain:

v’ = ((C * B) * A) * v

And finally we combine the matrix product of C*B and A on a single matrix we’ll call M, and the final result is:

v’ = M * v

What you see here, this simple equation is the one we’ll see the most on our 3D renderer, the matrix M is obtained by multiplying C by B and then by A. Note that the order is VERY important, matrix multiplication is NOT commutative. Meaning that A*B is NOT the same as B*A, be careful there.

The matrix M is known as the Model View Matrix, which is the combination of all the transformations required to make our 3D model look the way it’s supposed to on the rendered world, all of our 3D objects have a model view matrix.

On the next lesson we will begin coding all these fundamentals in actual code.

 

3D101 Introduction to Matrices

Introduction to Matrices

We have talked about transformations, projections, spaces and many other cool things belonging to 3D fundamentals. But one of the most important things in this 3D world is to know your way around matrices, we’ll learn why they are used and how we can use them properly to optimize our final code.

 

  1. What the heck is a matrix?

 

According to Wikipedia The Free Encyclopedia, a Matrix is defined as follows:

 

            In mathematics, a matrix (plural matrices, or less commonly matrixes) is

a rectangular array of numbers, symbols, or expressions. The individual

items in a matrix are called its elements or entries.

 

 

To make things simpler, let’s take a look at the matrix shown below. Just like Wiki said, a Matrix is simply a rectangular disposition of elements, similar to a table. All matrices have size, and it is defined as MxN where M is the number of rows, and N is the number of columns. Hence the matrix on the sample is a 3 by 2 matrix (3 rows, and 2 columns), which can also be written just 3×2. – Most times matrices are written inside square brakets as shown on the figure, but you can also use curly braces and even huge parenthesis.

 

If the values of M and N are the same (such as 3×3 or 4×4) the matrix is said to be a  squared matrix because the number of columns and rows are the same, effectively resembling a perfect square figure. Squared matrices have special properties that will be discussed on following sections.

Each element of a matrix can be any mathematical object (such as a number, an equation, and even another matrix!) each of these elements on the matrix have their own location, and this location is defined by coordinates of the form (i, j) where i is the row number and j is the column number. To follow the example matrix shown above the location of the numbers: 1 is (1, 1), 5 is (2,2), 6 is (3,2), 2 is (2, 1), etc. This is very important to remember and to understand.

Since matrices are mathematical objects, we are able to perform several operations with them just like we do with numbers. With numbers we can add, subtract, multiply, divide and many other operations. With matrices we can also perform operations such as: Addition and Subtraction, Scaling, Transpose, and Multiplication. There are several other operations that can be performed over matrices, but to avoid lengthening too much this lesson we will cover only the basic operations mentioned above.

 

1.1 Matrix Addition

Addition and subtraction are essentially the same operation, the only difference is simply convenience for us humans, since it is faster for us to evaluate 1 – 2  (written as a subtraction) than 1 + (-2) (written as an addition). To perform matrix addition we need two matrices of the same size. For example, let’s evaluate the following matrix operation:

To evaluate this expression correctly we need to make sure that all matrices are of the same size. The first matrix has size 3×2, and the second is also 3×2 so we can now proceed without issues.

Given two matrices A and B, both of size MxN, the addition of those matrices will produce a matrix C also of size MxN, each element C(i,j) is defined to be: C(i,j) = A(i,j) + B(i,j)

In more easier terms the previous statement means that each element of the resulting matrix C will be the sum of the corresponding element on A and B. So, the result is:

The same principle applies for subtraction. As follows:

1.2 Matrix Scaling

Scaling a matrix is also known as multiplying by an scalar factor, an “scalar” is simply a number. Scaling is a binary operation involving one matrix and one scalar factor. The factor is usually written on the left side of the matrix as a coefficient.

Given a matrix A of size MxN and an scalar factor k, the scalar multiplications of both terms will produce a matrix also of size MxN, where each element of C = k*A is will be k*A(i,j). – In other words, each element of the matrix A will be multiplied by the factor k, and that will be the corresponding element of the resulting matrix C.

1.3 Matrix Transposition

Transposition operation is used to reverse the size of the matrix (including all elements inside it). This is a unary operation in which only one matrix is required. The transposition operation is written using an uppercase T as a super index on the matrix as in: AT

Given a matrix A of size MxN, assuming B is the transpose of A written as B = AT then each element of B is defined as B(i,j) = A(j,i) and the resulting matrix B will have size NxM.

1.4 Matrix Multiplication

Multiplication of matrices is most of times what makes people really angry in just a few minutes, specially when multiplying on paper. It is a lot more difficult than the rest of the matrix operations. So, let’s start already! – Matrix multiplication is a binary operation that requires forcefully two squared matrices of the same size.

Before going further, there is need to define the dot product of two vectors. Imagine we have two vectors A and B for instance A = <1, 2, 3> and B = <4, 5, 6>, the dot product of A and B defined as C = A•B is defined to be the sum of the product of each corresponding element on the vectors, so, C = 1*4 + 2*5 + 3*6, therefore C = 32.

Given two matrices A and B both of size NxN, if C = A*B (matrix multiplication) then the resulting matrix will also be of the NxN, and each element of C is defined to be

C(i,j) = A(row-i) • B(col-j) , where “•” is the dot product operation.

Let’s view the following example, here we are multiplying matrix A (bottom left) with matrix B (top right), the result is matrix C (bottom right).

The definition basically says that for every ROW i in A, we have to take one COLUMN j from B and perform dot product, the result is the element (i, j) of the resulting matrix.

In the example ROW i=1 of A is <1, 4, 7> and COLUMN j=1 of B is <1, 2, 3>, the dot product operation explained is 1*1 + 4*2 + 7*3 which results in 30. This final result is the element (1, 1) of the result matrix.

Matrices can be a bit complicated at first, but once you get the grasp of them you will see how useful they actually are. Matrices will save us a lot of time, and they have a lot of uses, some of those uses apply to 3D algorithms as well and will be detailed on our next lesson.

Make sure you understand all matrix operations, specially multiplication. Doing that will be give you great advantage on the next lesson “Affine Transformations”.

Matrices are scary at first, but they are your friends. 🙂

 

3D 101 Fundamentals of 3D Renderers

Now that you’re familiar with the fundamentals of 3D (spaces, coordinates, projections, and rotations) it’s time to begin to learn how to write a quick and clean 3D renderer. But first, let’s talk about how 3D objects are defined.

1. Object Definitions

In 3D we define objects using vertices just like in any other space, for example a line is defined as an object having two vertices. Certain other objects require a different definition such as the sphere which is defined by a center vertex and a radius. There are also fundamental objects such as the triangle which can be used to form much more complex objects such as planes, spheres, hexagons and many many more.

Most of time objects are defined using the latter method, that is, using triangles to define more complex objects. Triangles are used because it’s a fundamental figure, the first that is a closed shape with minimum number of points (3).

Now imagine a complex polygon as shown on the following figure (left figure) that polygon can be represented using triangles only as it is shown in the right figure. The action of converting one polygon to a sequence of smaller objects which collectively can accurately represent the source polygon is known as tessellation.

Before we continue, there’s need to think about what a 3D renderer actually is. To make things simple it’s enough to say that “A 3D renderer performs rendering which in this context is drawing 3D objects in a 2D display with as much accuracy as possible.”

There are two basic types of renderers, real-time and non-real-time. Non-real-time renderers use mathematical equations to generate thousands of vertices and draw objects with great accuracy. This type of renderer is very very slow but has an amazing output image quality due to the incredible number of vertices generated. Non-real-time renderers are used in commercial applications such as movies and animated series.

Real-time renderers on the other hard are incredibly fast and efficient because they need to render an entire scene dynamically on demand in real time. These renderers lack of high detail because of the technique used, which uses less detailed approximations of the real object to make rendering time faster, that is the primary goal of real-time renderers, provide output as fast as possible with averagely good quality. Real-time renderers are used in games, software, interactive applications and more.

As shown on the figure above, the left side shows a generic triangle rendered as a one piece object which is usually how a real-time renderer would draw a triangle. The right side shows a tessellated triangle with 16 sub-pieces, this has a much higher detail when lights and reflections are rendered, this is how a non-real-time renderer would draw the triangle, although probably with more than just 16 sub-pieces, since non-real-time renderers use micro polygons which can be as small as 2 or 3 pixels each.

Nowadays real-time renderers are much more efficient than they were 18 years ago when Doom(Game, 1993, ID Software) blew our minds for the first time, this is due to the powerful help of 3D accelerated graphics cards which can render millions of polygons per second, allowing us to build highly detailed frames without worrying about speed.

2. Renderer Operations

As we mentioned before, a renderer has to take care of drawing objects appropriately in a 2D display, to do this there are several basic stages that we need to go through in order to achieve a correct output image. The following sections will describe these basic rendering stages. Note that a fully featured renderer may or may not implement more stages in this process, depending on the level of quality and methods used. In future lessons these processing stages will be written in code one by one.

2.1. Consumption of Object Data

In this stage the data of objects in the scene is sent to the renderer for further processing, these data is added to the object queue. By object data we refer to all the required information about an object that is required in order to draw it correctly, such as vertices, texture coordinates, shading method, light parameters, view model matrix, etc.

2.2. Vertex Lighting

In this stage a light modeling algorithm such as the phong reflection model is used along with the light sources defined in the scene to calculate the color and intensity of lights on the all the vertices of all objects in the object queue. Lighting is an optional stage at first and should be added only until the basic renderer is stable and produces correct results.

2.3. Camera Transformation

In this stage a camera view matrix is used to transform the entire world to make it look like it’s being viewed from point of view of the camera. This is commonly known as converting from world-coordinate-space to view-coordinate-space.

2.4. Fundamentals Generation

In this stage the renderer has to convert or tessellate objects (if necessary) to generate fundamental polygons (triangles). After this stage has finished the object queue will be composed only of fundamental polygons.

2.5. Polygon Culling

In this stage the renderer has to discard from the object queue all polygons that are either facing backwards (process known as backface culling) or those which are behind a non-translucent polygon (known as hidden surface removal), the result is a much smaller set of polygons which are always visible.

2.6. Clipping

In this stage all polygons in the object queue have to be checked to verify that they are in fact inside the view volume, all polygons outside it are removed. Sometimes a polygon lies partially outside the view volume, in which case the polygon is clipped against the view volume and this usually results in the generation of one or more smaller polygons that effectively lie inside the view volume. Clipping is essential to render frames correctly.

2.7. Projection Transformation

In this stage all polygons are projected to 2D space using a projection matrix. This is usually known as converting from view-coordinate-space to viewport-coordinate-space.

2.8. Viewport Clipping

In this stage the resulting points from the last stage are clipped against the viewport to make sure no polygon lies outside the valid rectangle viewport range.

2.9. Polygon Scan Conversion

In this stage all polygons are scan-converted to generate scanlines which can easily be drawn later by even the most basic graphics library.

2.10. Polygon Filling

In this final stage all polygons are drawn one by one simply by filling the scanlines with appropriate data using an specified shading method, such as a plain color (flat shading), a gradient (gouraud shading), an image (texturing) or even a simple wireframe.

All these stages are executed by hardware when 3D accelerated cards are available, to make use of these cards a hardware-enabled library such as OpenGL or DirectX should be used when developing 3D applications instead of a custom made library.

Despite of all the scary stages, there’s no reason to panic! A highly simplistic renderer can be developed with only four stages (2.1, 2.7, 2.9 and 2.10). Later on in future lessons we will develop a somewhat simple renderer with 6 to 7 stages.

The Fundamentals of 3D

It gives me great pleasure to introduce Jonathan Palencia; our new technical 3D blogger.  He really knows his stuff and has a plan already jotted out for the series, so I’ll leave you in his capable hands:

Fundamentals

Welcome to 3D101! Before anything please read the following to make sure you’re on the right place; this is the first part of the 3D101 tutorial which will deal with 3D Fundamentals from scratch. If you’re attempting to build your own 3D engine or you just want to learn more about the actual underlying infrastructure, algorithms and techniques then you’re on the right place.

 

1. Let’s Talk About Dimensions

 

Now let’s begin by trying to define what this stuff really is. 3D stands for “Three Dimensional Space”, and a dimension is -mathematically speaking- the minimum number of coordinates required to describe any location in that space, and physically speaking a dimension describes the level of movement of an object on that space. Coordinates are usually written as a tuple of N-numbers within parenthesis, each coordinate means something on the specific dimension it is in. For example in 2D space as taught on high school we have the x-coordinate which stands for horizontal movement and y-coordinate for vertical movement. A location is described with this coordinates as (x, y). As an example, if we have coordinates (2,-4) it describes a point located 2 units to the right, and 4 units down.

 

Imagine a 1D space, as its name implies it needs one coordinate to describe any location in it, graphically speaking a 1D space is just a line because in a line you just need one coordinate (number) to describe any position in it, such as (10), or (-3). The level of movement is simply horizontal, you move to the left and to the right only. The tuple for 1D space is (x), “x” coordinate meaning horizontal movement.

 

Similarly a 2D space needs two coordinates to describe any location, graphically a 2D space is the Cartesian Coordinate Plane. The level of movement on a 2D space is now much more free than 1D, you can move left, right, up and down. The tuple for 2D space is (x, y) where “x” coordinate is for horizontal movement, and “y” coordinate for vertical movement.

 

Now that you have the idea, you can realize that a 3D space needs three coordinates, and that graphically speaking 3D is exactly the world we live in. The level of movement is amazingly fluid and free for all of us living in it, we can move left, right, up, down, back and forth. The tuple for 3D space is (x, y, z) where “x” and “y” have the same meaning as in previous spaces, and “z” now indicates “depth”.

 

For imagination’s sake, why don’t we take this one step further? Imagine now a 4D space, it’s harder isn’t it? We know it needs four coordinates (x, y, z, t) but what is the last coordinate for? It could be … Hmmm. Maybe time!! The “t” coordinate could specify a moment in time, meaning we can be on a location (x, y, z) at any given time (t) which means we could travel through time back and forth without issues of any kind simply by changing our “t” coordinate! Maybe some creatures somewhere in the universe are able to do this -or maybe not- but well, it feels good to imagine that it might be possible.

 

2. Showing 3D Objects on a 2D Screen

 

The display screen is a 2D surface, and we need to show 3D objects on it, How in earth can that be done? One simple word to answer all questions: Projection.

 

A projection is a linear transformation P from one coordinate space to another such that the source image remains as unchanged as possible. – For example, in real life light bounces off objects and that is what our eyes register, we are not seeing the entire object, but rather just the light reflected from them that falls on our retina. This partial reflection we see is a projection of the 3D object on our 2D retina. When our brain processes the images received from both eyes we get the feeling we’re seeing in 3D, but that’s only because our amazing brains are able to fill-in-the-blanks to determine the depth of what we’re seeing, this is known as depth perception.

 

To display 3D objects on our display screen all we need is to project 3D coordinates on a 2D plane we’d like to see. There are several equations that need to be figured out first to perform this projection, but for simplicity’s sake I’ll just write them here. If you want more information about this you can search internet for “Linear Projection”.

 

The following equations represent a simplified linear transformation from R3 (3D space) to R2 (2D space). Note that the equations have been slightly modified to match actual screen coordinate system (inverted Y-axis).

 

X2D = 0.5*ScreenWidth + Scale*(X3D / (Z3D + ZNearPlane))

Y2D = 0.5*ScreenHeight – Scale*(Y3D / (Z3D + ZNearPlane))

The ScreenWidth and ScreenHeight variables represent the resolution of the display mode currently active. Scale is a small factor used for the entire stage, a usual value is 256. And the ZNearPlane defines the Z-plane where the eye is located on (nearest plane), a regular value is 256 as well.

 

3. Rotations in 3D Space

 

Now we’re able to have a bunch of 3D points and convert them to 2D for visualization, but without the ability to rotate the 3D points all we have is an static object on the screen, with no life, and no meaning. Rotating objects in 3D space is similar to rotating in 2D, with the exception that in 2D we only have one plane (XY), and in 3D we have three planes (XY, YZ, and XZ) meaning we have to perform one rotation on one of those planes depending on the kind of movement we want.

 

The following equations define rotations for the X, Y and Z axis respectively. Instead of memorizing each equation, just memorize the XY-plane rotation (Z-rotation) and then try to realize how to figure out the others.

 

 

X-Rotation 

 

Z’ = Z*COS(Θ) – Y*SIN(Θ)

Y’ = Z*SIN(Θ) + Y*COS(Θ)

Y-Rotation 

 

X’ = X*COS(Θ) – Z*SIN(Θ)

Z’ = X*SIN(Θ) + Z*COS(Θ)

Z-Rotation 

 

X’ = X*COS(Θ) – Y*SIN(Θ)

Y’ = X*SIN(Θ) + Y*COS(Θ)

 

Using these simple equations now you can move your objects freely in any direction, then project them to 2D space and show them on your display.