ActionScript.org Flash, Flex and ActionScript Resources - http://www.actionscript.org/resources
3D Face Extrusion
http://www.actionscript.org/resources/articles/156/1/3D-Face-Extrusion/Page1.html
Leo Meyerovich
This user is yet to take control of their account and provide a biography. If you are the author of this article, please contact us via support AT actionscript DOT org. 
By Leo Meyerovich
Published on September 9, 2005
 
Tutorial details:
Written by: Leo Meyerovich [email:lionspawed@hotmail.com]
Time: 30 minutes
Difficulty Level: Advanced
Requirements: Flash MX
Topics Covered: How to create a 3D shape using ActionScript.
Assumed knowledge: Plenty.

Page 1 of 3
Tutorial details:
Written by: Leo Meyerovich [email:lionspawed@hotmail.com]
Time: 30 minutes
Difficulty Level: Advanced
Requirements: Flash MX
Topics Covered: How to create a 3D shape using ActionScript.
Assumed knowledge: Plenty.

 

3D Face Extrusion: the non-Swift3d spinning logo.
Your client wants a spinning logo. And itsy bitsy little random shapes floating around it. You have 3 basic options: look for a pricey program, code something, or drop the client.
Assuming you want to take the more intellectually challenging route, lets extrude a face. The basic idea is that a function takes in a face, copies it, moves it a bit, and then connects all the lines. The trick is in efficiency.
I’m going to take you into the dark world of 3d engines a bit, but most of it can be glossed over. This is all a one frame movie. Just paste the code into the first frame of your clip and done. Mod to your delight.

The plan:

  • I. 3dOM - syntax/creation of faces/objects we’ll use
  • II. Extrude - actual function to calculate new faces and points with minimum repetition.
  • III. 3D Engine - thing that is needed to work. Just plop it all into the first frame if you’re not really interested.

I. 3DOM - 3d object model. Clever huh?

A. Shapes

A 3d engine takes a point with an x, y, and z coordinate and figures out how to show that point on the screen with just an x and y coordinate. That in itself is useless, so we want to group points together in shapes to form an object. So, we have:

[as]mass.shapes[whichShape][whichPoint][/as]

meaning:
mass = our movieclip
shapes = an array of different shapes/faces (face being a side of 3d object)

The first property of shapes ( [whichShape] ) is the ID of a specific face on the object.
In a cube, we can have shapes[0] the top, shapes[1] the left, shapes[2] the top, etc.
A face is just a collection of points, or vertices. A triangle has 3 points, a square 4. So the second property of shapes will be the ID of a specific point on that shape. If we want the ID of the first point on the first face, all we do is write mass.shapes[0][0] (remember, arrays start on 0, not 1). The second point on the third face would be mass.shapes[2][1].

The goal of the rest of the tutorial will be to write:

[as]extrude(mass, whichShape, 100); // where 100 would be the thickness of our desired logo.[/as]

B. Point List

A lot of faces will share a common point – the corner point of a cube connects to three faces. When telling the computer to rotate the cube, it is really told to rotate the points. In a cube, if each face had a property of 4 points, there would be 24 points to rotate every second (6 faces * 4 points per face. However, if we had a list of points else where, and each face said which one of those points it used, the computer would only rotate 8 points every second (8 points on a cube). So we need an external point list: mass.pts[whichPoint].

Each point needs to have some properties, so for the first point we’d say:

[as]mass.pts[0] = new Object(); // only objects can have properties
p = mass.pts[0]; // don?t want to write mass.pts[0] a lot
p.x = 5; p.y = 10; p.z = 32; // p now has 3 properties, x, y, z[/as]


Page 2 of 3

C. Put it All Together And Turn Yourself About

So lets make us a good ole triangle!

  1. First we need some sort mass, a place to put all the stuff in.

    [as]createEmptyMovieClip("mass", _root.depth++);[/as]

  2. Now we need the 3 points of a triangle.

    [as]mass.pts = new Array();
    // only do this once, makes pts an array

    mass.pts[0] = new Object;
    //makes a point where we can add properties
    mass.pts[0].x = 0; mass.pts[0].y = 0; mass.pts[0].z = 0;

    mass.pts[1] = new Object;
    //makes another point
    mass.pts[1].x = 40; mass.pts[0].y =40; mass.pts[0].z = 0;

    mass.pts[2] = new Object;
    //makes another point
    mass.pts[2].x = 0; mass.pts[0].y =40; mass.pts[0].z = 0;[/as]

  3. Great, but they’re still just points in space. Lets make a shape out of them.
    First, we need to add something to our mass to hold the different shapes:

    [as]mass.shapes = new Array ();
    // only write this once in the script[/as]

    Now we have some space for shapes, we need to make space in a shape for points:

    [as]mass.shapes[0] = new Array();
    // gives room in the first shape for points[/as]

    And now lets finally make a shape out of points:

    [as]mass.shapes[0][0] = 2;
    // first point in shape will be the point with ID = 0
    mass.shapes[0][1] = 0;
    // 2nd point in shape will be the point with ID = 1
    mass.shapes[0][2] = 1;
    // first point in shape will be the point with ID = 2[/as]

    The order didn’t really matter but illustrate how the external point list. For more advanced shapes, it could become a headache – different orders means the lines are connected in the different orders.

II. Extrude - Lets make it 3D

A. Point check

[as]// A very useful function for optimization would be to check if a point exists, and if so, whats its ID
// if it doesn?t find one, the function will return ?1 (an impossible ID)

function checkPts(mc, x, y, z)
{
        f = -1;
        for (b = 0; b < mc.pts.length; b++)
        {
                if (mc.pts[b].x == x)
                if (mc.pts[b].y == y & mc.pts[b].z == z)
                f = b;
                if (f > -1) break;
        }
        return f;
}
[/as]

B. Actual extrude:

[as]function extrude (mc, from, amount) // which mass, shape, and desired depth
{
        // this will go through each point in our shape
        for (i = 0; i < mc.shapes[from].length; i++)
        {
                // let?s check if our point with a change depth (z) already exists
                k = checkPts(mc, mc.pts[mc.shapes[from][i]].x, mc.pts[mc.shapes[from][i]].y,
                 mc.pts[mc.shapes[from][i]].z + amount);
                
                if ( k == -1)
                {
                        // if it doesn?t, lets make a new one with the extruded z
                        k = mc.pts.length;
                        mc.pts[k] = new Object;
                        mc.pts[k].x = mc.pts[mc.shapes[from][i]].x;
                        mc.pts[k].y = mc.pts[mc.shapes[from][i]].y;
                        mc.pts[k].z = mc.pts[mc.shapes[from][i]].z + amount;
                        // the only change from the original coordinate is z, depth
                }
                
                // lets make a face out of this coordinate, the previous, and their extruded copy
                // that means it?ll be on of the sides of the new shape (not top or bottom)
                // need to make sure we start on the second coordinate though:
                if (i > 0)
                {
                        
                        // make a new face
                        mc.shapes[mc.shapes.length] = new Array();
                        mc.shapes[mc.shapes.length-1][0] = checkPts(mc, mc.pts[mc.shapes[from][i]].x,
                         mc.pts[mc.shapes[from][i]].y, mc.pts[mc.shapes[from][i]].z + amount);
                        mc.shapes[mc.shapes.length-1][1] = checkPts(mc, mc.pts[mc.shapes[from][i - 1]].x,
                         mc.pts[mc.shapes[from][i - 1]].y, mc.pts[mc.shapes[from][i - 1]].z + amount);
                        mc.shapes[mc.shapes.length-1][2] = mc.shapes[from][i - 1];
                        mc.shapes[mc.shapes.length-1][3] = mc.shapes[from][i];
                }
        }
        // we still need to make the last face, connecting from the first coordinate, last, and their extruded copies
        
        mc.shapes[mc.shapes.length] = new Array();
        mc.shapes[mc.shapes.length-1][0] = checkPts(mc, mc.pts[mc.shapes[from].length-1].x,
         mc.pts[mc.shapes[from].length-1].y, mc.pts[mc.shapes[from].length-1].z + amount );
        mc.shapes[mc.shapes.length-1][1] = mc.shapes[from][mc.shapes[from].length - 1];
        mc.shapes[mc.shapes.length-1][2] = mc.shapes[from][0];
        mc.shapes[mc.shapes.length-1][3] = checkPts(mc, mc.pts[ mc.shapes[from][0] ].x,
         mc.pts[ mc.shapes[from][0]].y, mc.pts[ mc.shapes[from][0]].z + amount);
        
        // and lets make the top of the shape
        mc.shapes[mc.shapes.length] = new Array();
        for (i = 0; i < mc.shapes[from].length; i++)
        mc.shapes[mc.shapes.length - 1][i] = checkPts(mc, mc.pts[mc.shapes[from][i]].x,
         mc.pts[mc.shapes[from][i]].y, mc.pts[mc.shapes[from][i]].z + amount);
}
[/as]

C. Great.. how do extrude the thing again?

[as]extrude(mass, 0, 20);
// takes the first shape in our mass and extrudes it 20 pixels[/as]


Page 3 of 3

III. 3D Engines are Fun!

I believe this engine started at ultrashock, doing x/y rotation. There are a lot of different ways to rotate points, but this one is common because x/y corresponds to the mouses x/y position on the screen. The math behind it deserves it’s own tutorial :D

I like to simulate a camera change, not a point change, when rotating the mass in order to keep the original points. So we need to start with a mock-camera that’ll rotate everything:

[as]// start with 10 degree angle rotation, and then make it redraw the shape when the mouse moves
// change the 5 to prefrence. Stage.width/2 means middle of the screen. redraw is our render function

mass.ayaxis = 10; mass.axaxis = 10;
mass.onMouseMove = function()
{
        this.axaxis+= (_root._xmouse - Stage.width/2)/5;
        this.ayaxis+= (_root._ymouse - Stage.height/2)/5;
        redraw(mass, axaxis, ayaxis);
}
[/as]

The next step is to make it actually show up. There might be a few objects with their own rotation guidelines and rotation triggers, so lets attach all drawings to our mass object just in case. So:

[as]function redraw(mc, xAngle, yAngle)
{
        // figures out the new coordinates
        rotate3D(mc, xAngle, yAngle);
        
        // clear the previously rendered shape
        mc.clear();
        
        //for each shape in our mass
        for (x = 0; x < mc.shapes.length; x++)
        {
                // red fill with alpha opacity of 20
                mc.beginFill(0xff0000, 20);
                // black 1pt outline with alpha 20
                mc.lineStyle(1, 0x000000, 20);
                
                // start at our first point
                mc.moveTo(mc.pts[mc.shapes[x][0]].sx, mc.pts[mc.shapes[x][0]].sy);
                
                for (var k = 1; k < mc.shapes[x].length; k++)
                {
                        // and draw to our last point
                        mc.lineTo(mc.pts[mc.shapes[x][k]].sx, mc.pts[mc.shapes[x][k]].sy);
                }
                
                // connect the last dot to the original and end the fill
                mc.lineTo(mc.pts[mc.shapes[x][0]].sx, mc.pts[mc.shapes[x][0]].sy);
                mc.endFill();
        }
}
[/as]

Rotate world function

We still need to tell flash how to rotate the points given an x and y angle. So:

[as]function rotate3D(mcArray, xa, ya)
{
        // perspective scale, lower to make interesting
        camdepth = 300;
        c = mcArray;
        rad = Math.PI/180;
        cosYangle = Math.cos(ya*rad);
        sinYangle = Math.sin(ya*rad);
        cosXangle = Math.cos(xa*rad);
        sinXangle = Math.sin(xa*rad);
        
        // figure out the rotated coordinates and rendering position
        p = mcArray.pts;
        for (i = 0; i < mcArray.pts.length; i++)
        {
                tempz = (p[i].z*cosYangle)-(p[i].x*sinYangle);
                tmpx = (p[i].z*sinYangle)+(p[i].x*cosYangle);
                tmpz = (p[i].y*sinXangle)+(tempz*cosXangle);
                tmpy = (p[i].y*cosXangle)-(tempz*sinXangle);
                
                // the actual screen coords
                zratio = camdepth/(tmpz +camdepth);
                p[i].sx = (Stage.width/2)+ tmpx *zratio;
                p[i].sy = (Stage.height/2) - tmpy *zratio;
        }
}
[/as]

That's it! Move the code segments into your first frame (maybe organize it a bit - functions in one frame, shape/point definitions in another)

Thoughts etc

  • Curves. Add another property set: .cx, .cy, .cz (curve point in space) as well as .scx, .scy. You will need to add the curve manip to rotate as well as render
  • curveTo with above method wouldn't work, check were-here on what type of curve it is
  • styles - lineAlpha (negative means no line), lineThickness, lineColor, fillAlpha (negative means no fill), fillColor
  • shading - You can darken the side pieces, or decrease their alpha, to provie more emphasis on the real shape
  • It's easy to mod. There are a lot of prototypes out there as well. It's light weight, but if you want to render multiple objects, make sure the render routine is called for several objects at the same time to help speed.