Here is the code behind the revolving cube. To keep the flow intact, we keep our comments as comments within the code. We will skip some parts of the code in which we define and format all the dynamic text fields on the Stage. Those parts the same as they would be in Player 9.
package {
import flash.display.*;
import flash.text.*;
import flash.events.*;
import flash.geom.*;
// SWF Metadata for the Flex compiler.
[SWF(width="600", height="600", backgroundColor="#CCCCCC", framerate="30")]
public class CubeMenu extends MovieClip {
/*
We are embedding five jpeg images. In Flash, we would import them to the Library and link
to AS3. The 'embed' tags do not work there.
*/
[Embed(source="parodia.jpg", mimeType="image/jpeg")]
public var Img0:Class;
[Embed(source="anemon.jpg", mimeType="image/jpeg")]
public var Img1:Class;
[Embed(source="anagallis.jpg", mimeType="image/jpeg")]
public var Img2:Class;
[Embed(source="cosmos.jpg", mimeType="image/jpeg")]
public var Img3:Class;
[Embed(source="lila.jpg", mimeType="image/jpeg")]
public var Img4:Class;
/*
We are declaring our variables. First, Bitmap and BitmapData variables
that will store data from loaded images.
*/
private var bdSide0:BitmapData;
private var bmSide0:Bitmap;
private var bdSide1:BitmapData;
private var bmSide1:Bitmap;
private var bdSide2:BitmapData;
private var bmSide2:Bitmap;
private var bdSide3:BitmapData;
private var bmSide3:Bitmap;
private var bdSide4:BitmapData;
private var bmSide4:Bitmap;
/*
The five Sprites corresponding to five sides of the open-sided cube.
The Sprites will contain our Bitmaps. We need the Sprite containers to make the sides
interactive and to ensure that the registration point of each side is in its center.
*/
private var spSide0:Sprite;
private var spSide1:Sprite;
private var spSide2:Sprite;
private var spSide3:Sprite;
private var spSide4:Sprite;
/*
We will position spSide0,..., spSide4 to form a cube within a Sprite, cube.
'cube' will become a child of the Sprite container spBoard.
*/
private var cube:Sprite;
private var spBoard:Sprite;
private var curProj:Number;
/*
Text fields in which we will display all text in our movie. In Flash, we would
put most of text in static boxes.
*/
private var tfTitle:TextField;
private var tfAuthor:TextField;
private var tfInstructions:TextField;
private var tfCoords:TextField;
private var tfLabel:TextField;
private var tfFlowerName:TextField;
private var tfArrows:TextField;
private var tfView:TextField;
private var shBack:Shape;
private var doRotate:Boolean;
private var prevX:Number;
private var prevY:Number;
/*
We will bulid and rotate our cube using the simple (new to Player 10)
rotationX and rotationY properties instead of using the matrix3D class.
We will need the matrix3D class for depths-sorting of the sides of the cube.
We will sort with respect to the midpoint of each face. Each midpoint
is a point in 3D and thus, it can be represented as an instance of the new
Vector3D class. All midpoints will form an array of Vectors3D. An array
whose all elements are of the same datatype can be represented as an instance
of a new class, Vector. So, 'midsVector' is a Vector consisting of Vectors3D.
Note the syntax for declaring Vectors.
*/
private var midsVector:Vector.<Vector3D>;
private var picSize:Number;
private var boardWidth:Number;
private var boardHeight:Number;
private var fLen:Number;
/*
Beginning of the constructor. In the class constructor, we initialize our variables,
draw background, build our cube, and set up listeners.
*/
public function CubeMenu(){
stage.scaleMode = StageScaleMode.NO_SCALE;
//The size of our square images.
picSize=160;
//The size of the black rectangle, our board.
boardWidth=470;
boardHeight=420;
//The Sprite containing the black background, 'shBack', and 'cube'.
spBoard=new Sprite();
addChild(spBoard);
/*
If you look at 'drawBack' function later in the script, it will become clear that
the registration point of spBoard is in the center of the black rectangle.
We position spBoard within the main movie accordingly.
*/
spBoard.x=boardWidth/2+65;
spBoard.y=boardHeight/2+50;
shBack=new Shape();
spBoard.addChild(shBack);
cube=new Sprite();
spBoard.addChild(cube);
//The Sprite 'cube' is positioned at the center of spBoard, (0,0).
cube.x=0;
cube.y=0;
/*
We build the front side of 'cube'. We postion bmSide0 within
spSide0 in such a way that the regstration point of spSide0 falls
onto the center of the image. Then we move spSide0 toward the front
by setting its 'z' coordinate to -picSize/2. Recall, that the z-axis
is pointing away from the screen so a negative 'z' moves an object
forward.
*/
bdSide0=new Img0().bitmapData;
bmSide0=new Bitmap(bdSide0);
spSide0=new Sprite();
spSide0.addChild(bmSide0);
cube.addChild(spSide0);
bmSide0.x=-picSize/2;
bmSide0.y=-picSize/2;
spSide0.z=-picSize/2;
/*
We build the top side, spSide1, similarly. We position
bmSide1 within spSide1. We rotate spSide1 about the x-axis
by -90 degrees which flips the side horizonatlly. Then we lift spSide1
up by setting spSide1.y=-picSize/2.
*/
bdSide1=new Img1().bitmapData;
bmSide1=new Bitmap(bdSide1);
spSide1=new Sprite();
spSide1.addChild(bmSide1);
cube.addChild(spSide1);
bmSide1.x=-picSize/2;
bmSide1.y=-picSize/2;
spSide1.rotationX=-90;
spSide1.x=0;
spSide1.y=-picSize/2;
spSide1.z=0;
/*
We build the other three sides similarly by rotating and adjusting coordinates
within 'cube'. (The sides are children of 'cube'). After the cube is build,
we will be able to rotate it as a whole by manipulating
cube.rotationX and cube.rotationY.
*/
bdSide2=new Img2().bitmapData;
bmSide2=new Bitmap(bdSide2);
spSide2=new Sprite();
spSide2.addChild(bmSide2);
cube.addChild(spSide2);
bmSide2.x=-picSize/2;
bmSide2.y=-picSize/2;
spSide2.rotationX=90;
spSide2.x=0;
spSide2.y=picSize/2;
spSide2.z=0;
bdSide3=new Img3().bitmapData;
bmSide3=new Bitmap(bdSide3);
spSide3=new Sprite();
spSide3.addChild(bmSide3);
cube.addChild(spSide3);
bmSide3.x=-picSize/2;
bmSide3.y=-picSize/2;
spSide3.rotationY=-90;
spSide3.x=picSize/2;
spSide3.y=0;
spSide3.z=0;
bdSide4=new Img4().bitmapData;
bmSide4=new Bitmap(bdSide4);
spSide4=new Sprite();
spSide4.addChild(bmSide4);
cube.addChild(spSide4);
bmSide4.x=-picSize/2;
bmSide4.y=-picSize/2;
spSide4.rotationY=90;
spSide4.x=-picSize/2;
spSide4.y=0;
spSide4.z=0;
/*
We are setting the properties of the perspectiveProjection corresponding
to the root object 'this'. In particular, we are setting projectionCenter
to the center (registration point) of the spBoard. Note that coordinates
below are relative to the main movie. 'projectionCenter' that is, the point
where toward which objects vanish as they move far away, is by default
set to the center of the Stage. We want it to be in the center of spBoard, instead.
*/
this.transform.perspectiveProjection.projectionCenter =
new Point(spBoard.x,spBoard.y);
/*
The fieldOfView property is reponsible for perspective distortion:
the higher the vale, the more distortion.
*/
this.transform.perspectiveProjection.fieldOfView = 60;
curProj=60;
doRotate=false;
fLen=this.transform.perspectiveProjection.focalLength;
//We create a Vector of five Vectors3D.
midsVector=new Vector.<Vector3D>(5);
//We call functions that initialize our application.
drawBack();
setUpListeners();
setMids();
setUpText();
renderView(-180,0);
}
/*
End of the constructor.
*/
/*
3D vectors created below in the setMids function
correspond to the midpoints of the sides of 'cube'. Coordinates
are relative to 'cube'.
Note that accessing elements of a 'Vector', midsVector,
follows the same syntax as that for arrays.
*/
private function setMids():void {
midsVector[0]=new Vector3D(0,0,-picSize/2);
midsVector[1]=new Vector3D(0,-picSize/2,0);
midsVector[2]=new Vector3D(0,picSize/2,0);
midsVector[3]=new Vector3D(picSize/2,0,0);
midsVector[4]=new Vector3D(-picSize/2,0,0);
}
/*
'renderView' function runs as the cube is rotated. The function uses rotationX and rotationY,
and calls the function 'sortFaces' that depths-sorts sides.
*/
private function renderView(a:Number,b:Number):void {
cube.rotationX=a;
cube.rotationY=b;
sortFaces();
}
/*
In order to sort sides in 'sortFaces' below , we need the matrix3D class. For each rotated state
of the cube we retrieve its current matrix3D. We apply the matrix
to the original locations of midpoints to calulate their current location.
All coordinates are relative to 'cube'. Note that the syntax:
curMid=curMatrix.transformVector(midsVector[i]);
works as 'cube' is at (0,0,0) of spBoard so its translation vector is 0. Otherwise, we would use:
curMid=curMatrix.deltaTransformVector(midsVector[i]);
*/
private function sortFaces():void {
var distArray:Array=[];
var dist:Number;
var i:int;
var curMatrix:Matrix3D;
var curMid:Vector3D;
curMatrix=cube.transform.matrix3D.clone();
while(cube.numChildren>0){
cube.removeChildAt(0);
}
for(i=0;i<5;i++){
curMid=curMatrix.transformVector(midsVector[i]);
if(i==2){
tfCoords.text=String(curMid);
}
//We calculate the distance of each midpoint from the observer.
//We could use dist=curMid.z instead. That is, we could simply
// sort by the z-coordinate.
dist=Math.sqrt(Math.pow(curMid.x,2)+Math.pow(curMid.y,2)+
Math.pow((-curMid.z-fLen),2));
distArray[i]=[dist,i];
}
distArray.sort(byDist);
for(i=0;i<5;i++){
cube.addChild(this["spSide"+String(distArray[i][1])]);
}
}
private 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;
}
}
private function drawBack():void {
shBack.graphics.lineStyle(1,0x666666);
shBack.graphics.beginFill(0x000000);
shBack.graphics.drawRect(-boardWidth/2,-boardHeight/2,boardWidth,boardHeight);
shBack.graphics.endFill();
}
private function setUpListeners():void {
spBoard.addEventListener(MouseEvent.ROLL_OUT,boardOut);
spBoard.addEventListener(MouseEvent.MOUSE_MOVE,boardMove);
spBoard.addEventListener(MouseEvent.MOUSE_DOWN,boardDown);
spBoard.addEventListener(MouseEvent.MOUSE_UP,boardUp);
spSide0.doubleClickEnabled=true;
spSide0.addEventListener(MouseEvent.DOUBLE_CLICK,side0Clicked);
spSide1.doubleClickEnabled=true;
spSide1.addEventListener(MouseEvent.DOUBLE_CLICK,side1Clicked);
spSide2.doubleClickEnabled=true;
spSide2.addEventListener(MouseEvent.DOUBLE_CLICK,side2Clicked);
spSide3.doubleClickEnabled=true;
spSide3.addEventListener(MouseEvent.DOUBLE_CLICK,side3Clicked);
spSide4.doubleClickEnabled=true;
spSide4.addEventListener(MouseEvent.DOUBLE_CLICK,side4Clicked);
stage.addEventListener(KeyboardEvent.KEY_DOWN,keyPressed);
}
//The keyboard listeners. The arrow keys change
//this.transform.perspectiveProjection.fieldOfView.
//Other listeners are self-explanatory.
private function keyPressed(evt:KeyboardEvent):void {
if (evt.keyCode == 40) {
if(curProj>10){
curProj+=-2;
this.transform.perspectiveProjection.fieldOfView = curProj;
tfView.text="perspectiveProjection.fieldOfView: "+String(curProj);
}
}
if (evt.keyCode == 38) {
if(curProj<116){
curProj+=2;
this.transform.perspectiveProjection.fieldOfView = curProj;
tfView.text="perspectiveProjection.fieldOfView: "+String(curProj);
}
}
}
private function boardOut(e:MouseEvent):void {
doRotate=false;
}
private function boardDown(e:MouseEvent):void {
prevX=spBoard.mouseX;
prevY=spBoard.mouseY;
doRotate=true;
}
private function boardUp(e:MouseEvent):void {
doRotate=false;
}
private function boardMove(e:MouseEvent):void {
var locX:Number=prevX;
var locY:Number=prevY;
if(doRotate){
var picRotX:Number=cube.rotationX;
var picRotY:Number=cube.rotationY;
prevX=spBoard.mouseX;
prevY=spBoard.mouseY;
renderView((picRotX+1.5*(prevY-locY))%360,(picRotY+(prevX-locX))%360);
tfFlowerName.text="";
e.updateAfterEvent();
}
}
private function side0Clicked(e:MouseEvent):void {
tfFlowerName.text="PARODIA HETERI";
renderView(0,0);
}
//Other listeners to double-clicks are similar.
...............
//We are setting up and formatting text fields. Nothing new here.
private function setUpText():void {
...............
}
}
}
Compare the amount of code that it takes to build a 3D menu in a cube in Player 10 with a similar applet in Player 9 in our tutorial, 'Cube in Bloom: Distorting Images in AS3, 3D Menu on a Cube' The difference is huge. Of course, if you'd like a regular closed-sided cube, all you need to do is to add one more image and one more side to the cube. The sixth image is included in the zip file in this experiment.
Note on PerspectiveProjection Object
Contrary to what the Player 10 Documetation says, the only perpectiveProjection object that can be accessed and manipulated seems to be the perpectiveProjection object of the root object and not for other DisplayObjects. Any attempt to manipulate or assign, for example, spBoard.transform.perspectiveProjection prevents code from executing. So does the following code that creates a new PerspectiveProjection object:
var projObj:PerspectiveProjection=new PerspectiveProjection();
projObj.fieldOfView=60;
projObj.projectionCenter=new Point();
spBoard.transform.perspectiveProjection=projObj;
The only way to assign spBoard.transform.perspectiveProjection seems to be:
spBoard.transform.perspectiveProjection=this.transform.perspectiveProjection;
spBoard.transform.perspectiveProjection.fieldOfView=60;
spBoard.transform.perspectiveProjection.projectionCenter=new Point(a,b);
The latter works (for given a and b) but the new perspectiveProjection object remains relative to the root object in terms of coordinates: a, b are interpreted as relative to the main movie.
Download
- A zip file p10cube.zip
The zip file linked above contains all the 'as' and 'jpg' files related to this experiment.










