In one my projects last year, I had to build something like this:

Forum Intro

It seems fairly simple, just a bunch of cubes, right? I just need to draw their outlines, some showing their back faces and some not. So I started with Papervision3D.

The thing is there is nothing like an ‘OutlineMaterial’ in Papervision. WireframeMaterial is pretty close, but I couldn’t find a way to draw just the face borders instead of drawing all the triangles. MovieMaterial with an outlined square shape could be an option, but the result doesn’t look any good:

In the Papervision3D documentation I found the class Line3D. This would probably give me a good looking result for the outlines. However I wouldn’t be building primitives anymore, as each line would have to be a separate object. Rotating the cubes and hiding their back faces could become a nightmare. So, Papervision3D is great, but it couldn’t help me in this particular need. Besides, it is quite a heavy library to add to a project for such a simple effect.

With Away3DLite I could only get the same wireframe triangles result. I thought Sandy3D could help me when I found their Outlines Demo, but the result is more like a ‘glow’ around the whole object instead of outlines for each face like I needed.

Then I tried Flash Player 10 native 3D. It is not as easy to create a cube as with the other 3D engines, but luckily Keith Peters taught me how to do it in his book AdvancED ActionScript 3.0 Animation. I just had to pass an outlined shape to all of the cube faces. The result is actually quite good:

Good, but not good enough. Depending on the angle of rotation, the outlines look a bit jerky. They vary according to their distance to the camera. What I wanted rotating and moving in three dimensions while keeping the outlines as sharp as they would look in a two dimension screen – like the ones we get when we use graphics.lineTo(x, y). So I needed to find the vertices 3D coordinates (x, y, z) and project them onto 2D coordinates (x, y). Hmm, this looks familiar. I am sure this can be done with 3D engines, but it can also be done without any engine.
The good and old scale = fl / (z + fl) can give just the right result in a lighter, faster and more fun way. This is the result:

 

To do that, I created a class called CubeOldSchool and this is what happens inside it:

  1. Create vertices.
  2. Create faces.
  3. Create rotation getters/setters.
  4. Render.
    • a. Append rotation to vertices.
    • b. Convert 3D vertices into 2D points.
    • c. Perform back face culling. [optional]
    • d. Draw lines.

 

1. Create vertices
A cube has eight vertices. If we say our cube will have width, height and depth of 100px, then our vertices should look like this:

var _vertex:Vector.<Vector3D> = new Vector.<Vector3D>(8, true);
 
_vertex[0] = (new Vector3D(-50, -50, -50));
_vertex[1] = (new Vector3D(50, -50, -50));
_vertex[2] = (new Vector3D(50, 50, -50));
_vertex[3] = (new Vector3D(-50, 50, -50));
_vertex[4] = (new Vector3D(-50, -50, 50));
_vertex[5] = (new Vector3D(50, -50, 50));
_vertex[6] = (new Vector3D(50, 50, 50));
_vertex[7] = (new Vector3D(-50, 50, 50));

If the project is targeting Flash Player 10, we can use Vector3D to represent a point in a three dimensional space. If project needs to run in an earlier Flash Player, then it is just a matter of creating a simple class with public x, y and z properties.

 

2. Create faces.
A cube has six faces. We need to define which vertices belong to each face and also in which order they should be drawn.

Cube vertices and faces

In the image above, every number is an index in the _vertex Vector. And every face is a collection of four indexes. To organize that in a readable way, let’s create a simple Face class:

class Face 
{
	public var index:Vector.<int> = new Vector.<int>(4, true);
 
	public function Face(i0:int, i1:int, i2:int, i3:int) 
	{
		index[0] = i0;
		index[1] = i1;
		index[2] = i2;
		index[3] = i3;
	}
}

Then create the six faces:

var _faces:Vector.<Face> = new Vector.<Face>();
 
_faces.push(new Face(1, 2, 6, 5));
_faces.push(new Face(2, 3, 7, 6));
_faces.push(new Face(3, 0, 4, 7));
_faces.push(new Face(0, 1, 5, 4));
_faces.push(new Face(0, 3, 2, 1));
_faces.push(new Face(5, 6, 7, 4));

The order of the indexes in all faces is important to perform the back face culling. In this example all faces are drawn counter-clockwise, although clockwise would work just the same. The important thing is to make sure all faces are drawn in the same direction.

 

3. Create rotation getters/setters.
When we say _cube.rotationX = 10; we want to rotate our vertices, not the whole Sprite. So we need to override the rotation properties and store their values.

override public function get rotationX():Number { return _rotationX; }
 
override public function set rotationX(value:Number):void
{
	_rotationX = value;
}
 
override public function get rotationY():Number { return _rotationY; }
 
override public function set rotationY(value:Number):void 
{
	_rotationY = value;
}
 
override public function get rotationZ():Number { return _rotationZ; }
 
override public function set rotationZ(value:Number):void 
{
	_rotationZ = value;
}

 

4. Render.
This is where we transform the coordinates and draw the outline. Render should be called on enter frame.

a. Append rotation to vertices.
All vertices need to be transformed according to the cube’s rotations on all three axes. To do this we need a 3D transformation matrix. Luckily Flash Player 10 has Matrix3D to make our lives easier. The code below resets the matrix3D, applies all rotations and stores all transformed vertices in a new vector.

var _matrix3D:Matrix3D = new Matrix3D();
var _points3D:Vector.<Vector3D> = new Vector.<Vector3D>();
 
_matrix3D.identity();
_matrix3D.appendRotation(_rotationY, Vector3D.Y_AXIS);
_matrix3D.appendRotation(_rotationX, Vector3D.X_AXIS);
_matrix3D.appendRotation(_rotationZ, Vector3D.Z_AXIS);
_points3D = new Vector.<Vector3D>();
 
for each (var vertex:Vector3D in _vertex) 
{
	_points3D.push(_matrix3D.transformVector(vertex));
}

If you need to target an earlier Flash Player or if you want to understand what happens in these matrix transformations, my friend André Anaya has a very detailed post about this in his blog.

 

b. Convert 3D vertices into 2D points.
Here is where the famous perspective formula scale = fl / (z + fl) kicks in. It needs to be applied to each vertex in each face and the result is a new Vector, this time storing 2D Point objects.

var vectors:Vector.<Vector3D> = new Vector.<Vector3D>();
var points:Vector.<Point> = new Vector.<Point>();
var scale:Number;
 
for each (var face:Face in _faces)
{
	for (i = 0; i < 4; i++) 
	{
		vectors[i] = _points3D[face.index[i]];
		scale = 500 / (vectors[i].z + 500);
		points[i] = new Point();
		points[i].x = vectors[i].x * scale; 
		points[i].y = vectors[i].y * scale; 
	}
}

 

c. Perform back face culling. [optional]
First time I read about how to do back face culling I was amazed about how smart and simple it is. Well, at least in this situation where we have a fixed camera. All we need to do is take the cross product of three points in each face. The result is the magnitude of the vector in the z-axis. If magnitude is positive we can tell the face is not facing the camera and skip drawing it. This works only if the orientation of the points in the faces is kept consistent (in this case, counter-clockwise).

Back face culling

if (( points[2].x - points[1].x ) * ( points[0].y - points[1].y ) -
    ( points[2].y - points[1].y ) * ( points[0].x - points[1].x ) > 0) continue;

 

d. Draw lines.
Finally, draw the outlines!

for (i = 0; i < 4; i++) 
{
	j = (i < 3) ? i + 1 : 0;
	graphics.moveTo(points[i].x, points[i].y);
	graphics.lineTo(points[j].x, points[j].y);
}

 

Now that we can control vertices and faces, why not try some crazy 3D objects like this one:

 

LINKS
Example Files
Forum (project using 3D outlines)

 


4 Comments on “3D Outlines”

You can track this conversation through its atom feed.

  1. Ozan says:

    that project was awesome, like many others you guys did in GK.

  2. admin says:

    @Ozan Thanks. You have some pretty impressive stuff in your portfolio too!

  3. Ozan says:

    thank you Bruno for the nice words, very much appreciated.

  4. toninho says:

    i searched for “brazilian boobies” and google sent me here.

    hi.

Leave a Reply

XHTML: You can use these tags: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>