The Code Behind the Simple 3D Engine

Below we discuss the code behind our simple 3D dynamic drawing renderer. We use the cube file and focus only on the portions of the code related to 3D functionality. We skip the code that controls sliders. To keep the flow of the code intact, we put our comments in the form of code comments.

 

/*
The 'numVertices' variable holds the number of vertices of our 3D object. Since, in this example, we draw a cube the number is 8. 'numFaces' holds the number of faces. For a cube it is 6. The code below can easily be adapted to other objects. 'numVertices' and 'numFaces' are the first two variables that would possibly change.
*/

var numVertices:int=8;

var numFaces:int=6;

/*
The next variable, 'objRad' is responsible for the size of the object. It isn't really the radius as the object isn't a sphere. For the cube, 'objRad' is half of the lenght of each side. The registration point of the 3D cube will be placed in its center. Since Flash CS4 recognizes 3D objects, we can talk about the registration point of a 3D object. The registration point is the point with (x,y,z) coordinates equal to (0,0,0).
*/

var objRad:Number=70;

/*
Below, we are using two new classes in Flash CS4: Vector and Vector3D. A 'Vector' is simply a typed array; that is, an array whose all elements are of the same declared datatype. A 'Vector3D' is like a 'Point' except it has three properties: x, y, and z (and possibly w). A Vector3D represents a point or a vector in xyz-space. (The fourth w coordinate may hold, for example, the perpective distortion focator for a Vector3D.)
 
We define a Vector 'vertsVec' which consits of Vectors3D. Each of those Vectors3D will represent a vertex of our 3D object. Note the syntax for creating Vectors:
 
Vector.<datatype>
*/

var vertsVec:Vector.<Vector3D>=new Vector.<Vector3D>();

var facesVec:Vector.<Array>=new Vector.<Array>();

/*
'fLen' is reponsible for perspective distortion: the larger fLen the less distortion.
*/

var fLen:Number=500;

/*
'spBoard' will hold the black background for our object. The background will be drawn in such a way that the registration point of spBoard will fall in its center. spBoard is strictly a 2D object. The 2D image of our cube will be drawn in spBoard. (More precisely, its child 'spObjImage'.)
*/

var spBoard:Sprite=new Sprite();

this.addChild(spBoard);

spBoard.x=180;

spBoard.y=205;

spBoard.filters=[ new DropShadowFilter() ];

/*
'shBack' contains is the black background drawn in spBoard by the function 'drawBack'.
*/

var shBack:Shape=new Shape();

spBoard.addChild(shBack);

drawBack();

/*
spObject that we declare below is a Sprite and it becomes a 3D object when we assign values to spObject.rotationX, spObject.rotationY, and spObject.rotationZ. The moment you use rotations on a DisplayObject or set its z coordinate, your object becomes a 3D object and has ALL of the 3D methods available to it. In particular, it has now the transform.matrix3D property. (Before an object becomes a 3D object its matrix3D is null.) The matrix3D property reflects all the 3D transformations that have been applied to the object and describes its current 3D state after rotations, translations etc. In this script, we use only rotations.
 
As you will see if you continue reading the script, spObject remains a rather abstract object throughout the script. spObject is never added to the Display List and does not have any visual representation in our movie. spObject serves as an abstract representation of our 3D object, in this example our 3D cube. All 3D transformations of the cube during rotations will be performed by changing spObject.transform.matrix3D object (in particular, by appending rotations). We will then apply the changed matrix3D to the vertices of the cube to transform them. Finally, the vertices will be projected onto the xy-plane with perspective distortion. Up to this point, only abstact matrix calculations are performed. When we have coordinates of the projected vetrices (and we sorted the order in which faces should appear), we will be ready to draw a view of our rotated cube.
 
We will use the coordinates of the projected vertices to draw the view of the cube in the Sprite spObjImage. The latter is on the Display List and it is a child of spBoard located at the (0,0) of the spBoard. Recall that the registration point, (0,0) of spBoard coincides with its center. During drawing in spObjectImage, coordinates of the projected vetices automatically become relative to the (0,0) of spObjectImage. All of that will be done in the function 'rotateObj' defined later in the script.
 
By separating completely 3D transformations and projections from the actual drawing, we avoid any need for translation and simplify things. spObject's translation coefficients are all 0 all the time. Moving around the spObjectImage Sprite moves the image of the cube but it does not affect spObject.transform.matrix3D. Our abstract cube world, spObject, remains intact.
*/

var spObject:Sprite=new Sprite();

var spObjImage:Sprite=new Sprite();

spBoard.addChild(spObjImage);

spObject.rotationX=0;

spObject.rotationY=0;

spObject.rotationZ=0;

/*
Since we used rotations on spObject, spObject is now a 3D object. Its initial spObject.transform.matrix3D is the identity matrix as all rotation are 0 and the default position of spObject is (0,0,0).
*/

/*
The next three variables are related to rotating with the mouse.
*/

var doRotate:Boolean=false;

var prevX:Number;

var prevY:Number;

/*
Colors of the cube's faces.
*/

var facesColors:Array=[0xFFFFCC,0x00FF66,0x0066FF,0x33FFFF,0x9A7DDF,0xFFCCFF];

/*
The function that draws the black square.
*/

function drawBack():void {

shBack.graphics.beginFill(0x000000);

shBack.graphics.drawRect(-160,-160,320,320);

shBack.graphics.endFill();

}

/*
We call the functions that define the vertices and faces of our cube and the function that creates the intial view of the cube.
*/

setVertices();

setFaces();

rotateObj(0,0,0);

/*
Our 3D object is defined by its vertices and its faces. Below we define the 8 vertices of the cube in the Flash's xyz-coordinate system. If you want to draw a different 3D object, all you need to do is to define different vertices and faces and to adjust the values of the variables 'numVertices' and 'numFaces' at the beginnig of the script. See the Icosahedron example in this tutorial.
*/

function setVertices():void {

vertsVec[0]= new Vector3D(-objRad,-objRad,-objRad);

vertsVec[1]=new Vector3D(objRad,-objRad,-objRad);

vertsVec[2]= new Vector3D(objRad,-objRad,objRad);

vertsVec[3]=new Vector3D(-objRad,-objRad,objRad);

vertsVec[4]= new Vector3D(-objRad,objRad,-objRad);

vertsVec[5]=new Vector3D(objRad,objRad,-objRad);

vertsVec[6]=new Vector3D(objRad,objRad,objRad);

vertsVec[7]=new Vector3D(-objRad,objRad,objRad);

}

/*
Each of the 6 faces is defined as an array of integers that represent numbers of vertices the face comprises. For example, the outline of the first face is obtained by joining vetrices number 0, 4, 5, and 1.
*/

function setFaces():void {

facesVec[0]=[0,4,5,1];

facesVec[1]=[1,5,6,2];

facesVec[2]=[2,6,7,3];

facesVec[3]=[3,7,4,0];

facesVec[4]=[4,5,6,7];

facesVec[5]=[0,1,2,3];

}

/*
'rotateObj' is the main function that renders the view of our 3D object. This function does not have to be changed if you change the cube to a different object. The function rotates the object by rotx about the x-axis, by roty about the y-axis, and by rotz about the z-axis. The rotations rotx, roty, and rotz are added to the current state of the object. So effects of applying the function more than once are cummulative. The object does not go back to its intial state unless you apply 'resetObj' function defined later in the script.
*/

/*
Start of the function 'rotateObj'. Comments within rotateObj function are not in bold for greater clarity.
*/

function rotateObj(rotx:Number,roty:Number,rotz:Number):void {

var i:int;

var j:int;

 

//distArray will be used for sorting faces.

 

var distArray:Array=[];

 

//dispVec will store vertices of the cube

//(or other object) projected onto xy-plane.

 

var dispVec:Vector.<Point>=new Vector.<Point>();

 

//Vertices in 3D, after rotx, roty, rotz rotations are applied,

//will be stored in the next variable.

 

var newVertsVec:Vector.<Vector3D>=new Vector.<Vector3D>();

 

//z coordinates of midpoints of faces, zAverage, will be used for sorting faces.

 

var zAverage:Number;

 

var dist:Number;

var curFace:int;

var curFaceLen:int;

var curObjMat:Matrix3D;

 

//The beauty of the Matrix3D class is that you don't have to manipulate

//matrix elements directly. There are many methods that will do that for you

//and create a matrix that corresponds to the 3D transformation that you want.

//Below we are adding three rotations about the three coordinate axes

//to the current matrix3D property of spObject.

 

spObject.transform.matrix3D.appendRotation(rotx,Vector3D.X_AXIS);

spObject.transform.matrix3D.appendRotation(roty,Vector3D.Y_AXIS);

spObject.transform.matrix3D.appendRotation(rotz,Vector3D.Z_AXIS);

 

//For simplicity of the notation below we are creating a copy of spObject's

//matrix3D. Note: if we didn't use 'clone()' but a simple assignment,

//any possible future changes in curObjMat would automatically be applied

//to spObject.transform.matrix3D as well.

 

curObjMat=spObject.transform.matrix3D.clone();

spObjImage.graphics.clear();

 

//We are transforming vertices of our object using the current matrix3D

//of spObject, curObjMat. Note: We are using 'deltaTransformVector'

//and not 'transformVector' below. The deltaTransformVector method

//only applies the linear portion of a transformation defined by a matrix

//and ignores translation coefficients. The transformVector method takes

//translation coefficients into the account. Since spObject has not been

//translated, there should be no difference in our case between the two methods.

//However, the transformVector method behaves differently in Flash Player

//10.0.2.54 that shipped with the initial release of Flash CS4

//and the current release version 10.0.12.36.

 

for(i=0;i<numVertices;i++){

newVertsVec[i]=curObjMat.deltaTransformVector(vertsVec[i]);

}

 

//For each of the transformed vertices, we are adding the fourth

//coordinate 'w' and applying the 'project' method. 'project' divides

//x, y, and z coordinates of a Vector3D by its w coordinate. It is

//the w coordinate defined below and 'project' that create

//the perspective distortion.

 

for(i=0;i<numVertices;i++){

newVertsVec[i].w=(fLen+newVertsVec[i].z)/fLen;

newVertsVec[i].project();

}

 

//For each face, we are calculating the z coordinate of the center

//of the face. (More precisely, the average of z coordinates of its

//vertices.) We will use zAverage for sorting which face should

//appear in front and which behind. The face with larger zAverage,

//is farther away than a face with smaller zAverage.

 

for(i=0;i<numFaces;i++){

 

//In our case of a cube, the length of each face is 4

//but for other objects it may be different.

 

curFaceLen=facesVec[i].length;

zAverage=0;

for(j=0;j<curFaceLen;j++){

zAverage+=newVertsVec[facesVec[i][j]].z;

}

zAverage/=curFaceLen;

dist=zAverage;

distArray[i]=[dist,i];

}

 

//We are sorting faces by their zAverage. (See the function 'byDist'

//defined later in the script.) Each element of distArray is an array

//of two elements: zAverage and the number of the corresponding face.

//After sorting, the faces in distArray appear in order from

//the back to the front.

 

distArray.sort(byDist);

 

//We are preparing 2D points for drawing by taking x and y coordinates

//of transformed vertices. Those points are stored in a Vector, dispVec.

 

for(i=0;i<numVertices;i++){

dispVec[i]=new Point();

dispVec[i].x=newVertsVec[i].x;

dispVec[i].y=newVertsVec[i].y;

}

 

//Now we are leaving the abstract setting and proceed to draw in spObjImage

//based on points in dispVec and the order of faces detemined by distArray.

 

for(i=0;i<numFaces;i++){

spObjImage.graphics.lineStyle(1,0xCC0000);

curFace=distArray[i][1];

curFaceLen=facesVec[curFace].length;

 

spObjImage.graphics.beginFill(facesColors[curFace],0.7);

 

spObjImage.graphics.moveTo(dispVec[facesVec[curFace][0]].x,

dispVec[facesVec[curFace][0]].y);

 

for(j=1;j<curFaceLen;j++){

spObjImage.graphics.lineTo(dispVec[facesVec[curFace][j]].x,

dispVec[facesVec[curFace][j]].y);

}

 

spObjImage.graphics.lineTo(dispVec[facesVec[curFace][0]].x,

dispVec[facesVec[curFace][0]].y);

 

spObjImage.graphics.endFill();

}

}

 

/*
End of the function 'rotateObj'.
*/

function byDist(v:Array,w:Array):Number {

if (v[0]>w[0]){

return -1;

} else if (v[0]<w[0]){

return 1;

} else {

return 0;

}

}

 

/*
The 'resetObj' function resets the object to its original position by reseting its matrix3D to the identity matrix. ('new Matrix3D()' returns the identity matrix.) The function is called when the RESET button is clicked.
*/

function resetObj():void {

spObject.transform.matrix3D=new Matrix3D();

rotateObj(0,0,0);

}

/*
When the user presses and moves the mouse over the object or slides one of the sliders, the event listeners attached to spBoard and to sliders call repeatedly the function 'rotateObj'. Below are snippents of the code that causes mouse rotations.
*/

...........................

 

spBoard.addEventListener(MouseEvent.MOUSE_MOVE,boardMove);

 

spBoard.addEventListener(MouseEvent.MOUSE_DOWN,boardDown);

 

...........................

 

function boardDown(e:MouseEvent):void {

prevX=spBoard.mouseX;

prevY=spBoard.mouseY;

doRotate=true;

}

 

function boardMove(e:MouseEvent):void {

var locX:Number=prevX;

var locY:Number=prevY;

if(doRotate){

prevX=spBoard.mouseX;

prevY=spBoard.mouseY;

rotateObj(prevY-locY,-(prevX-locX),0);

e.updateAfterEvent();

}

}

 

...........................

Download

Back to Flash CS4 Tutorials              Back to Flash and Math Home

We welcome your comments, suggestions, and contributions. Click the Contact Us link below and email one of us.

Adobe®, Flash®, ActionScript®, Flex® are registered trademarks of Adobe Systems Incorporated.