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.
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 - 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]
So lets make us a good ole triangle!
[as]createEmptyMovieClip("mass", _root.depth++);[/as]
[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]
[as]mass.shapes = new Array ();
// only write this once in the script[/as]
[as]mass.shapes[0] = new Array();
// gives room in the first shape for points[/as]
[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]
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]
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