Matrix3D 101

3D engines are very present in web applications these days. With ActionScript3 become possible develop faster 3d lib and, with that, the technology has spread widely thru the web. Today we have a lot of elaborated 3d engines like Papervision3D, Away3D, Sandy3D, Five3D, Alternativa and many others.

Their usage is quite simple, once that all calculations behind any lib is automatic e independent of the knowledge of the developer. But I think that is extremely important understand what happen in any application, lib or class, for the knowledge or to improve something that has already been created.

Despite the way that is built, any 3d engine have some elements in common, one of them is what we call Transformation Matrix. Yes, matrices. That thing that we learn on school and we don’t have any ideia of what it is until the day that we became developers. But before understand what is a transformation matrix, we must understand what it does. And for this we’ll think in a 3d engine in a basic way.

For example, let’s take a cube as a group of 8 connected points:

cubo

Each point represent a position in space, this position is what mathematicians call as a vector. To move the cube, we need to move these points following an path. Each point transform at the same way that other do from it’s origin. This path is what we call transformation matrix.

This transformation can happen at 3 ways in any axis:

- Translation
translacao
- Scaling
escala
- Rotation
rotacao

To understand what is a Transformation Matrix, we must understand some concepts like vector, trigonometric functions and matrix functions.

First, let’s take a vector as a position in space, a group of 3 coordenates (x, y and z):

vetor

To transform this position, we can add a vector to another resulting in a third vector. Let’s take a vector with x, y and z coord as 5, 2 and 0, and from that position let’s move 4, 2 and 3 units, resulting in a final vector of values 9, 4 and 3.

vetor_soma

This transformation is what we call translation

Taking translation as a sum of two vectors, we can say that scaling is the multiplication of a vector by a number.

vetor_multiplicacao

You can scale in x, y and z axes, in a simple way let’s consider this:

vetor_x_vetor

*Please remember that this is not how it’s done, but for now this is the best way to understand.

To understand how rotation works, we must consider this:

Imagine that a vector represents a position of a point (x, y) with a radius (r) of a rotation axis with angle (a)

So, we can say that:

rotacao_ponto

x = r*cos(a)
y = r*sen(a)

The rotation is the sum of one angle (a) to another angle (b) resulting in a final rotation angle (a+b) of the axis:

x’ = r*cos(a+b)
y’ = r*sen(a+b)

Remembering two trigonometric identities, we know that:

cos(a+b) = cos(a)*cos(b) – sen(a)*cos(b)
sen(a+b) = sen(a)*cos(b) + sen(b)*cos(a)

So:

x’ = r*cos(a+b) = r*cos(a)*cos(b) – r*sen(a)*cos(b)
y’ = r*sen(a+b) = r*sen(a)*cos(b) + r*sen(b)*cos(a)

Replacing r*cos(a) for x and r*sen(a) for y, we say that:

x’ = x*cos(b) – y*sen(b)
y’ = x*sen(b) + y*cos(b)

In a sistem of 3 rotation axes, this would be around the z axis, so it’s position in this axis wouldn’t change.

x’ = x*cos(b) – y*sen(b)
y’ = x*sen(b) + y*cos(b)
z’ = z

Likewise, a rotation around y axis would have this equations

x’ = x*cos(b) + z*sen(b)
y’ = y
z’ = -x*sen(b) + z*cos(b)

and around x axis

x’ = x
y’ = y*cos(b) – z*sen(b)
z’ = y*sen(b) + z*cos(b)

The important thing is not memorize these formulas, but understand the pattern they make.
If we organize these values in a chart, it would be:

matriz

Each row pertains to a part of the target vector. Each column represents an element that will be multiplied. You can expand the formulas by reading the chart. You could read the first row as this:

x’ = x* cos(b) + y* -sin(b) + z* 0

The second as

y’ = x* sin(b) + y* cos(b) + z* 0

and the third

z’ = x* 0 + y* 0 + z* 1

We can consider this as a rotation matrix around the z axis, where b is the angle of rotation.

A vector can be multiplied by a matrix as this:

vetor_matriz1

For scale matrix we have values as this:

matriz_escala

Matrices can be multiplied (concatenated) resulting in a matrix of same order:

multiplicacao_matriz

Once the translation is considered as a vector and not as a matrix, instead of using a third order (3×3) transformation matrix we use a fourth order (4×4) matrix, the fourth row is always all zeros, and the fourth column contains the translation elements.U

matriz_transformacao

At least, we must concatenate all transformation matrices, one for each type of transformation, into a unique transformation matrix, and then apply it to a group of points. A transformation matrix whose values do not change anything is called identity, and it looks as this:P

matriz_identidade

Well, let’s see a practical example how this works. To do this we create a basic class of transformation matrix where we implement methods of rotation, translation and scale, and then we’ll use it to move a 3d cube.

Despite being a post about matrix3d, we should know that the most basic element is the vector. So we must create a basic vector class containing only the coordinates x, y and z in order to implement the transformation.

Remember that this approach is very basic and is to demystify a little bit of the matrix3d concept, remember that there are a number of operations involved in a 3d engine.

Another important point to remember is that Flash Player 10 support vector3d and matrix3d, which implies a better performance since the calculations are done internally.

Come down to business.

First we will create our vector class called Vector3D, containing only 3 properties:

package basic3d.math {
	public class Vector3D {
		public var x:Number;
		public var y:Number;
		public var z:Number;
 
		public function Vector3D(x:Number = 0, y:Number = 0, z:Number = 0):void{
			this.x = x;
			this.y = y;
			this.z = z;
		}
	}
}

Now let’s create a document class for our example and create 8 instances of Vector3D representing the points of the cube, a container where the cube will be drawn and a method to be called constantly to draw these points

package {
	import flash.display.Sprite;
 
	import flash.events.Event;
 
	import basic3d.math.Vector3D;
 
	public class Main extends Sprite {
		private var p0:Vector3D;
		private var p1:Vector3D;
		private var p2:Vector3D;
		private var p3:Vector3D;
		private var p4:Vector3D;
		private var p5:Vector3D;
		private var p6:Vector3D;
		private var p7:Vector3D;
 
		private var container:Sprite;
 
		public function Main():void{
			p0 = new Vector3D(-50, -50, -50);
			p1 = new Vector3D(50, -50, -50);
			p2 = new Vector3D(50, 50, -50);
			p3 = new Vector3D(50, 50, -50);
			p4 = new Vector3D(-50, -50, 50);
			p5 = new Vector3D(50, -50, 50);
			p6 = new Vector3D(50, 50, 50);
			p7 = new Vector3D(50, 50, 50);
 
			container = new Sprite();
 
			container.x = 275;
			container.y = 200;
 
			addChild(container);
 
			addEventListener(Event.ENTER_FRAME, render);
		}
		private function render(e:Event):void{
			trace("draw cube");
		}
	}
}

So far nothing new. Let’s start drawing the cube based on points.

points

Looking at figure above, we can deduce which points are connected. Then using x and y coordinates, we can draw our cube:

private function render(e:Event):void{
	container.graphics.clear();
 
	container.graphics.lineStyle(0, 0xFF0000);
 
	container.graphics.moveTo(p0.x, p0.y);
	container.graphics.lineTo(p1.x, p1.y);
	container.graphics.lineTo(p2.x, p2.y);
	container.graphics.lineTo(p3.x, p3.y);
	container.graphics.lineTo(p0.x, p0.y);
 
	container.graphics.moveTo(p4.x, p4.y);
	container.graphics.lineTo(p5.x, p5.y);
	container.graphics.lineTo(p6.x, p6.y);
	container.graphics.lineTo(p7.x, p7.y);
	container.graphics.lineTo(p4.x, p4.y);
 
	container.graphics.moveTo(p0.x, p0.y);
	container.graphics.lineTo(p4.x, p4.y);
 
	container.graphics.moveTo(p1.x, p1.y);
	container.graphics.lineTo(p5.x, p5.y);
 
	container.graphics.moveTo(p2.x, p2.y);
	container.graphics.lineTo(p6.x, p6.y);
 
	container.graphics.moveTo(p3.x, p3.y);
	container.graphics.lineTo(p7.x, p7.y);
}

cubo0

It looks more a square than a cube, right? This is because the points p0, p1, p2 and p3 have the same x and y position as p4, p5, p6 and p7 points. So they overlap in the 2d space (x and y). In 3d space we have the z axis to make the diference between things, also we need to consider the perspective of the objects.

To change we must create a method that makes the conversion from 3D to 2D, let’s call it getPerspective. This method will receive an vector and returns a point:

private function getPerspective(vector:Vector3D):Point {
	var focalLength:Number = 1000;
	var scaleFactor:Number = focalLength/(focalLength + vector.z);
 
	var point:Point = new Point()
	point.x = vector.x*scaleFactor;
	point.y = vector.y*scaleFactor;
 
	return point;
}

cubo1

Now we see something closer to a cube. But before you understand what’s happening, let’s look more closely the new method. Note these two lines:

var focalLength:Number = 1000;
var scaleFactor:Number = focalLength/(focalLength + vector.z);

The focallength parameter is the focus of our “camera”. To understand this we consider the following. The further away the object is, smaller it gets and vice versa. A focal length is the distance between the camera and the object so its scale is 1. The smaller the focal length, the greater the change of scale on the axis z. To test this we will change our focus to 100:

cubo2

Comparing the two images, we see that the square outside is much bigger, while the inner square is much smaller, so that the points that make these squares had a much larger scale variation at the z axis. But for now we’ll keep the focus in 1000.

Now we’ll create our transformation matrix to transform these points the way we want. First we define the properties of this matrix:

package basic3d.math {
	public class Matrix3D {
 
		/**
		 * X O O O
		 * O O O O
		 * O O O O
		*/
		public var a :Number;
 
		/**
		 * O X O O
		 * O O O O
		 * O O O O
		*/
		public var b :Number;
 
		/**
		 * O O X O
		 * O O O O
		 * O O O O
		*/
		public var c :Number;
 
		/**
		 * O O O O
		 * X O O O
		 * O O O O
		*/
		public var d :Number;
 
		/**
		 * O O O O
		 * O X O O
		 * O O O O
		*/
		public var e :Number;
 
		/**
		 * O O O O
		 * O O X O
		 * O O O O
		*/
		public var f :Number;
 
		/**
		 * O O O O
		 * O O O O
		 * X O O O
		*/
		public var g :Number;
 
		/**
		 * O O O O
		 * O O O O
		 * O X O O
		*/
		public var h :Number;
 
		/**
		 * O O O O
		 * O O O O
		 * O O X O
		*/
		public var i :Number;
 
		/**
		 * O O O X
		 * O O O O
		 * O O O O
		*/
		public var tx :Number;
 
		/**
		 * O O O O
		 * O O O X
		 * O O O O
		*/
		public var ty :Number;
 
		/**
		 * O O O O
		 * O O O O
		 * O O O X
		*/
		public var tz :Number;
 
		public function Matrix3D(a:Number = 1, b:Number = 0, c:Number = 0, d:Number = 0, e:Number = 1, f:Number = 0, g:Number = 0, h:Number = 0, i:Number = 1, tx:Number = 0, ty:Number = 0, tz:Number = 0):void{
			this.a = a;
			this.b = b;
			this.c = c;
 
			this.d = d;
			this.e = e;
			this.f = f;
 
			this.g = g;
			this.h = h;
			this.i = i;
 
			this.tx = tx;
			this.ty = ty;
			this.tz = tz;
		}
	}
}

Note that by default, the matrix assumes the values of the identity matrix.

Now we will create the methods of transformation of this matrix. First the concatenation (multiplication) method, with this method we will make all changes in our matrix.

public function concat(m:Matrix3D):void{
	var values:Object = {};
 
	values.a = a*m.a+d*m.b+g*m.c;	values.b = b*m.a+e*m.b+h*m.c;	values.c = c*m.a+f*m.b+i*m.c;	values.tx = a*m.tx+d*m.ty+g*m.tz+tx;
	values.d = a*m.d+d*m.e+g*m.f;	values.e = b*m.d+e*m.e+h*m.f;	values.f = c*m.d+f*m.e+i*m.f;	values.ty = b*m.tx+e*m.ty+h*m.tz+ty;
	values.g = a*m.g+d*m.h+g*m.i;	values.h = b*m.g+e*m.h+h*m.i;	values.i = c*m.g+f*m.h+i*m.i;	values.tz = c*m.tx+f*m.ty+i*m.tz+tz;
 
	for(var v:String in values){
		this[v] = values[v];
	}
}

A bit confusing, no? If you do not understand very well how this works you can check this link

Now let’s start rotate our cube. We’ll test it only on the y axis and then duplicate the concept to other axes.

First let’s take a look at the formula that we used to rotate around the y axis:

x’ = x*cos(b) + z*sin(b)
y’ = y
z’ = -x*sin(b) + z*cos(b)

With that we can extract a matrix as this:

matriz_rotacao

Remember that the transformation is nothing but concatenate the current matrix to another, in this case we must concatenate with the y rotation matrix

public function rotateY(angle:Number):void {
	var matrix = new Matrix3D();
 
	matrix.a = Math.cos(angle); 	matrix.b = 0;	matrix.c = Math.sin(angle);
	matrix.d = 0;					matrix.e = 1;	matrix.f = 0;
	matrix.g = -Math.sin(angle);	matrix.h = 0;	matrix.i = Math.cos(angle);
 
	concat(matrix);
}

To test, we need to create a var with the rotation value in radians:

private var rotationY:Number = Math.PI*.25;

In this case 45 degrees

Then we need to create a base matrix and apply the rotation

var matrix:Matrix3D = new Matrix3D();
matrix.rotateY(rotationY);

After that, we apply the transformation to each vector. To do this, we must create a method called transform, that recieves and returns a vector. This method multiplies a vector by a matrix and adds the translation values.

public function transform(v:Vector3D):Vector3D {
	var vector:Vector3D = new Vector3D();
 
	vector.x = a*v.x+d*v.y+g*v.z+tx;
	vector.y = b*v.x+e*v.y+h*v.z+ty;
	vector.z = c*v.x+f*v.y+i*v.z+tz;
 
	return vector;
}

Applying the transformation at the vectors, we have the following modification at render method:

var point0:Point = getPerspective(matrix.transform(p0));
var point1:Point = getPerspective(matrix.transform(p1));
var point2:Point = getPerspective(matrix.transform(p2));
var point3:Point = getPerspective(matrix.transform(p3));
var point4:Point = getPerspective(matrix.transform(p4));
var point5:Point = getPerspective(matrix.transform(p5));
var point6:Point = getPerspective(matrix.transform(p6));
var point7:Point = getPerspective(matrix.transform(p7));

Also, we must increase 1 degree at the rotation angle every frame:

rotationY += Math.PI/180;

With that, we can see a cube rotating around the y axis.

This movie requires Flash Player 9

Now let’s duplicate it to x and z axis.

public function rotateX(angle:Number):void {
	var matrix = new Matrix3D();
 
	matrix.a = 1; 		matrix.b = 0;			matrix.c = 0;
	matrix.d = 0;		matrix.e = Math.cos(angle);	matrix.f = -Math.sin(angle);
	matrix.g = 0;		matrix.h = Math.sin(angle);	matrix.i = Math.cos(angle);
 
	concat(matrix);
}
public function rotateZ(angle:Number):void {
	var matrix = new Matrix3D();
 
	matrix.a = Math.cos(angle); 	matrix.b = -Math.sin(angle);	matrix.c = 0;
	matrix.d = Math.sin(angle);	matrix.e = Math.cos(angle);	matrix.f = 0;
	matrix.g = 0;			matrix.h = 0;			matrix.i = 1;
 
	concat(matrix);
}

Let’s create a rotation param to these axes:

private var rotationX:Number = 0;
private var rotationZ:Number = 0;

And increase 1 degree each frame:

rotationX += Math.PI/180;
rotationZ += Math.PI/180;

And call the rotation methods:

matrix.rotateX(rotationX);
matrix.rotateZ(rotationZ);

The result is this:

This movie requires Flash Player 9

Now we can see a 3d cube rotating. But our work is not done, we still need to transform the scale and translation of the cube.
Let’s create the scale transformation matrix:

<span lang="pt">p</span>ublic function scale(sx:Number, sy:Number, sz:Number):void {
	var matrix = new Matrix3D();
 
	matrix.a = sx; 	matrix.b = 0;	matrix.c = 0;
	matrix.d = 0;	matrix.e = sy;	matrix.f = 0;
	matrix.g = 0;	matrix.h = 0;	matrix.i = sz;
 
	concat(matrix);
}

Diferent from rotation where each axis has it own rotation matrix, the scale matrix have all transformation values in one matrix.

Let’s test the same scale in all axes of our cube. For now let’s block the rotation at x and z axis, and implement the scaling. In this example I’ll use a sine curve to increase and decrease the scale:

private var sinCurve:Number = 0;
 
sinCurve += Math.PI/180;
var scale:Number = 1+Math.sin(sinCurve)*.5;
matrix.scale(scale, scale, scale);

We can see that the cube increases and decreases it’s size at 50%:

This movie requires Flash Player 9

Now let’s create the translate method.

public function translate(x:Number, y:Number, z:Number):void {
	tx = x;
	ty = y;
	tz = z;
}

As the translation is considered as an extra column in the matrix, the only thing we need to do is modify it’s values. To test, let’s use the same sine curve, but this time apply it to the x translation axis:

sinCurve += Math.PI/180;
 
var xPos:Number = Math.sin(sinCurve)*150;
matrix.translate(xPos, 0, 0);

We can see that the x position change between 150 and -150 pixels.

This movie requires Flash Player 9

I hope I have helped you understand a little bit about transformation matrices. Remember that there are many more mathematical operations involving matrices and vectors in a 3d engine, and other things like UV map, z-Sorting and many other things.

A book that I recommend for those who would like to learn more is Essential Mathematics for Games and Interactive Applications of James M. Van Verth and Lars M. Bishop. It has a very good approach of mathematical concepts in computer graphics. Despite being targeted for developers C / C + +, the math is explained in a non-especific language.

Source

Any questions, suggestions or criticism, please contact me. I will be happy to help as I can.

Thanks


References:
http://wally.cs.iupui.edu/
http://www.prof2000.pt/
http://pt.wikibooks.org/
http://educacao.uol.com.br/matematica/
http://rrgoncalez.wordpress.com/
http://pt.wikipedia.org/wiki/Produto_de_matrizes
http://pt.wikibooks.org/wiki/Matem%C3%A1tica_elementar/Matrizes

Tags: , ,

4 Responses to “Matrix3D 101”

  1. Paulo Afonso says:

    Muito bom post.
    Obrigado

  2. Onur says:

    Thank you for this great tutorial.
    its realy what i need.
    i have a question, if i want skew or twist the cube. How a method and algorithm should i use?
    Thank you again.

  3. admin says:

    I never tried, but i think it’s possible. You can define a formula to each value of the matrix and create a behaviour like skew, twist, bend and any other modifier.

    But this could be another class that extends the Matrix3D.

  4. admin says:

    valew!!!
    Existem outras coisas que podem ser feitas, se tiver alguma duvida ficarei feliz em responder.

Leave a Reply