Our Favorite Way of Assigning a PerspectiveProjection Object to a Display Object

It is well-explained and illustrated in the ActionScript 3 for Flash Player 10 documentation and manuals how to manipulate the perspectiveProjection property of the root object. Examples in the documentation show how to change the fieldOfView and the projectionCenter for the root object. There is no explanation of how to assign a PerspectiveProjection object to individual DisplayObjects. We address this question in the present tutorial. We begin on this page with the way of assigning PerspectiveProjection which we consider the soundest.

Download

  • Download all the source files corresponding to this tutorial: pp.zip

The 'fla' file corresponding to the example above is called pp_show1.fla. The code is exhaustively commented.

While experimenting with the PerspectiveProjection class, we have discovered many rules that seem to hold. Some are surprising and a bit counterintuitive. We summarize our observations on the next page.

On the subsequent pages, we show a couple of experiments that illustrate those observations. On the last page, we show the simplest albeit not very flexible or versatile way of assigning a custom PerspectiveProjection object to a DisplayObject.

The Code

/*
Every DisplayObject is a 2D object for as long as no 3D methods or properties have been used on it. In other words, for as long as its transform.matrix3D property is null. The moment, you use a 3D property, for example, assign a value of the object's z coordinate (even if it is 0), the object becomes a 3D object. Every 3D object when rotated using rotationX, rotationY or rotationZ will inherit the default PerspectiveProjection settings of the root (in our case the MainTimeline). The default root.transform.perspectiveProjection property is an instance of the PerspectiveProjection class with root.transform.perspectiveProjection.fieldOfView=55 and root.transform.perspectiveProjection.projectionCenter located in the middle of the Stage. This inheritance works well for objects located near the center of the Stage but may not look good otherwise. Hence, it is important to be able to assign a perspectiveProjection object to individual DisplayObjects.
*/

/*
We imported to the Library a jpeg image, pic144.jpg, and linked it to AS3 as a BitmapData object under the name Pic144. We are storing the dimension of the image and creating three Bitmaps, img0, img1, img2, corresponding to three instances of Pic144.
*/

var picWidth:Number=144;

var picHeight:Number=108;

var img1:Bitmap=new Bitmap(new Pic144(144,108));

var img0:Bitmap=new Bitmap(new Pic144(144,108));

var img2:Bitmap=new Bitmap(new Pic144(144,108));

/*
For each of the three images, we create two containers: holder0,..,holder2 and container0,...,container2. We will see below and in other examples in this tutorial why it is convenient to do so. container0,...,container2 are children of the root, holder0,...,holder2 are children of container0,...,container2. Holders, in turn, contain our Bitmaps. We position all elements in such a way that the registration points of holder0,...,holder2 are at their centers. This is because we want images to rotate about their centers. The registration points of container0,...,container2 are also at their centers.
*/

var container1:Sprite=new Sprite();

var container0:Sprite=new Sprite();

var container2:Sprite=new Sprite();

var holder1:Sprite=new Sprite();

var holder0:Sprite=new Sprite();

var holder2:Sprite=new Sprite();

this.addChild(container1);

this.addChild(container0);

this.addChild(container2);

container1.x=100;

container1.y=120;

container0.x=300;

container0.y=250;

container2.x=500;

container2.y=390;

container1.addChild(holder1);

container0.addChild(holder0);

container2.addChild(holder2);

holder1.addChild(img1);

img1.x=-picWidth/2;

img1.y=-picHeight/2;

holder0.addChild(img0);

img0.x=-picWidth/2;

img0.y=-picHeight/2;

holder2.addChild(img2);

img2.x=-picWidth/2;

img2.y=-picHeight/2;

/*
We create two instances of the PerspectiveProjection class, pp1, pp2. We set their two properties: fieldOfView and projectionCenter. fieldOfView corresponds to the viewing angle and the amount of perspective distortion; projectionCenter to the 'vanishing point' of the projection - the point toward which parts farther from the viewer are getting closer to. We will assign pp1 to container1 and pp2 to container2. That is, the upper and the lower tiles will have custom PerspectiveProjection. container0, the middle tile, will inherit the perspectiveProjection settings of the MainTimeline. We choose fieldOfView equal to 100 degrees and projectionCenter at (0,0) for both containers. The (0,0) coordinates are, in this case, relative to container1 and container2, respectively. Thus, they are in the center of the upper and the lower tile. pp1, pp2, and the perspectiveProjecton of the MainTimline (this.transform.perspectiveProjection) are completely independent of each other. Thus, you can choose different values for fieldOfView and projectionCenter for each one. We chose equal values (100). That creates a nice synchronous effect.
*/

/*
pp1, pp2, and the perspectiveProjecton of the MainTimline (this.transform.perspectiveProjection) are independent of each other. Thus, you can choose different values for fieldOfView and projectionCenter for each one. We chose equal values (100) for fieldOfView. That creates a nice synchronous effect.
*/

var pp1:PerspectiveProjection=new PerspectiveProjection();

pp1.fieldOfView=100;

pp1.projectionCenter=new Point(0,0);

var pp2:PerspectiveProjection=new PerspectiveProjection();

pp2.fieldOfView=100;

pp2.projectionCenter=new Point(0,0);

container1.transform.perspectiveProjection=pp1;

container2.transform.perspectiveProjection=pp2;

/*
The default value of fieldOfView for the root object (MainTimeline) is 55 degrees. We want it to be 100. Thus, the next line.
*/

this.transform.perspectiveProjection.fieldOfView=100;

/*
The function spinImage rotates holders by 2 degrees counterclockwise.
*/

function spinImgs(e:Event):void {

holder1.rotationY+=2;

holder0.rotationY+=2;

holder2.rotationY+=2;

}

/*
Rotation starts when Start button is clicked and stops when Stop button is clicked.
*/

btnStart.addEventListener(MouseEvent.CLICK,btnStartClick);

 

function btnStartClick(e:MouseEvent):void {

this.addEventListener(Event.ENTER_FRAME, spinImgs);

}

 

btnStop.addEventListener(MouseEvent.CLICK,btnStopClick);

 

function btnStopClick(e:MouseEvent):void {

this.removeEventListener(Event.ENTER_FRAME, spinImgs);

}

/*
The Remove button removes pp1 and pp2 as perspectiveProjection values for container1 and container2. When that happens container1 and container2 are subject to the perspectiveProjection of the MainTimeline. As you can see by clicking on the button, the results are quite dramatic. The Restore button restores the pp1 and pp2 assignments.
*/

btnRemove.addEventListener(MouseEvent.CLICK,btnRemoveClick);

 

function btnRemoveClick(e:MouseEvent):void {

container1.transform.perspectiveProjection=null;

container2.transform.perspectiveProjection=null;

}

 

btnRestore.addEventListener(MouseEvent.CLICK,btnRestoreClick);

 

function btnRestoreClick(e:MouseEvent):void {

container1.transform.perspectiveProjection=pp1;

container2.transform.perspectiveProjection=pp2;

}

/*
holder0,...,holder2 were created to move the registration point of our images to their center. What is exactly the purpose of container1 and container2? (container0 was created just for consistency reasons as for the middle tile perspectiveProjection is inherited.) container1 and container2 allow us to assign coordinate values for projectionCenter relative to the images and and not to the Stage. The containers also allow for moving the upper and the lower tile around the Stage without affecting their perspective center. When you look at other examples in this tutorial, the adavtages of having the containers will become clear.
*/

Questions

Here is the list of questions that come to mind right away. We answer these questions on the next page and discuss the rules that perspectiveProjection property seems to obey.
  • What would happen if we didn't have container1 and container2 and assigned pp1 and pp2 directly to holder1 and holder2?
  • container1 and container2 are 2D Sprites. (That is, no 3D methods were used on them and their transform.matrix3D property is null.) What would happen if they were 3D objects?
  • What of pp1 and pp2 where assigned to holder1 and holder2 but but the holders were still children of container1 and container2? What if, in this situation, the containers were 3D objects?

The answers to some of these questions will surprise you.

Note

In the example above, we created a dedicated 2D container for each object that we wanted to have a custom perspective projection property, and we assigned a PerspectiveProjection object to the transform.perspectiveProjection property of that container. One of the advantages of such approach is that usually this is what happens when you create a class that extends the Sprite of the MovieClip classes and you want 3D objects contained in each instance of your class to have a perspective projection defined within the class. An instance of the class is a container and, within the class, you assign a PerspectiveProjection object to it. Then, all 3D children obey the assignment. See, for example, the SpinnerMenu class in: XML-Driven Spinning 3D Menu and Photo Gallery in Flash CS4.

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.