Home >
FEATURED GUEST PUBLICATION:
From time to time, InsideRIA has the opportunity to co-publish material from other websites (with their permission). The following article was/will be published on The Tech Labs, http://www.thetechlabs.com. If you are interested in having some of your site content syndicated on InsideRIA.com, please contact me at rtretola@oreilly.comBuilding a 3D album with FIVe3D and TweenLite
1. Setting up the workspace
2. Loading pictures

Now that you have both files open - all the work will consist of editing the 'Main.as' file and testing it by exporting the 'album.fla' file. The first step is to load all the pictures we will be using in our album. They are located in the 'photos' folder, and since the 'swf' file is exported to the root folder, the path to the pictures in our application will look like this: 'photos/photoName.jpg'. Let's start by adding some import statements that we will need at this stage:
import flash.display.Bitmap;
import flash.display.BitmapData;
import flash.display.Loader;
import flash.events.Event;
import flash.net.URLRequest;
import flash.text.TextField;
import flash.text.TextFormat;
import flash.text.TextFieldAutoSize;
public class Main extends Sprite {
private var numPicture:int = 9;
private var picturePath:String = "photos/photo0";
private var pictureExtensiton:String = ".jpg";
private var loadingIndex:int = 1;
private var loadingInfo:TextField;
private var pictures:Array;
private var pictureLoader:Loader;
In a real world solution you would probably load an XML file containing all the paths to all the pictures, the total number and maybe even a title and a description for each one. Loading and parsing XML files goes beyond the scope of this tutorial however, so for the sake of simplicity all those values are hard coded. If you want, after you complete the tutorial, you can modify the code so that it uses an XML file. This would be a great exercise!
The 'loadingIndex' property will helps us keep track of how many pictures are already loaded. We will display this information to the user - this is what the 'loadingInfo' text field is for. All the loaded pictures will be put into the 'pictures' array. Finally, to load the pictures, we will use a Loader object, that I called the 'pictureLoader' (obvious name, isn't it?).
At this moment, we can add some actual code. In the constructor function you can remove the line with the 'trace' command and replace it with this:
loadingInfo = new TextField();
loadingInfo.defaultTextFormat = new TextFormat("Verdana", 10, 0xffffff);
loadingInfo.autoSize = TextFieldAutoSize.LEFT;
loadingInfo.text = "Loading picture " + loadingIndex + " of " + numPicture;
loadingInfo.x = stage.stageWidth/2 - loadingInfo.textWidth/2;
loadingInfo.y = stage.stageHeight/2 - loadingInfo.textHeight/2;
addChild(loadingInfo);
pictures = new Array();
loadPicture();
Apart from adding the text field, we also initialize the 'pictures' array and we call the 'loadPicture' function. If you try to export your fla at this point, you will get an error, since this function is not yet there. Let's add it after right after the constructor:
private function loadPicture() {
pictureLoader = new Loader();
pictureLoader.contentLoaderInfo.addEventListener(Event.COMPLETE, onPicture);
pictureLoader.load(new URLRequest(picturePath + loadingIndex + pictureExtensiton));
loadingIndex++;
}
private function onPicture(e:Event):void {
var picture:BitmapData = (e.target.content as Bitmap).bitmapData;
pictures.push(picture);
if (loadingIndex <= numPicture) {
loadingInfo.text = "Loading picture " + loadingIndex + " of " + numPicture;
loadPicture();
} else {
removeChild(loadingInfo);
buildAlbum();
}
}
private function buildAlbum():void {
trace("Pictures loaded!");
}
The function 'loadPicture' load a single picture and increments the 'loadingIndex' variable by 1.
Since I add an event listener to the loader, when the picture is loaded the 'onPicture' function is invoked. At this point I check if the 'loadingIndex' is still less then the total number of pictures. If this is true - I call loadPicture again to load the next one. If not, it means that all the pictures are there. In that case I can remove the loading information text field and call a function to build the album. Export your fla and you should see the text 'Pictures loaded!' in the output window after a short while.
Take a look a the first line of code in the 'onPicture' method. When external images are loaded, whether it is a jpg, gif or a png, Flash will wrap it into a object of type 'Bitmap'. A Bitmap is a DisplayObject, so it can be added to the display list and thus be display on the screen. However, in our case we do not want to display the pictures directly. We will use them as 3D textures instead, so we do not need the Bitmap object, but a BitmapData objects instead. Fortunately they are easily accessible from Bitmap.bitmapData, and this is what the first line in that function does. In the next line I store the BitmapData in the 'pictures' array for later use.
3. Building 3D album

If you tested the application so far, you should get the 'Pictures loaded!' message in the output window. Now let's display them!
First, we will need to import some more classes. Add this in the import section at the top of the class:
import five3D.display.Scene3D;
import five3D.display.Bitmap3D;
import five3D.display.Sprite3D;
import flash.geom.ColorTransform;
Next, we need to declare some more variables. Right after the previous declarations, enter the following code lines:
private var scene:Scene3D;
private var album:Sprite3D;
private var padding:Number = 60;
private var fullViewZ:Number = 2400;
private var darker:ColorTransform = new ColorTransform(.8, .8, .8, 1, 0, 0, 0, 0);
private var lighter:ColorTransform = new ColorTransform(1, 1, 1, 1, 0, 0, 0, 0);
The 'darker' an 'lighter' ColorTransform object will be used for rollover effects. When applied to a Sprite, the 'darker' makes it slightly... darker. The 'lighter' restores the sprites original colors. One of the coolest things in FIVe3D is that a Sprite3D actually extends a regular Sprite and most of the operations that can be done on a Sprite can also be done on a Sprite3D. That includes the changing transform.colorTransform property that we are going to use here.
The real fun begins now! We will start to build a 3D view of the album. Replace the trace command in the 'buildAlbum' function with this:
scene = new Scene3D();
scene.x = Math.round(stage.stageWidth/2);
scene.y = Math.round(stage.stageHeight / 2);
addChild(scene);
album = new Sprite3D();
for (var i:int = 0; i < pictures.length; i++) {
var bitmap3d:Bitmap3D = new Bitmap3D(pictures[i] as BitmapData);
var picture3d:Sprite3D = new Sprite3D();
bitmap3d.x = bitmap3d.bitmapData.width / -2;
bitmap3d.y = bitmap3d.bitmapData.height / -2;
picture3d.addChild(bitmap3d);
var row:Number = Math.floor(i / 3) - 1;
var col:Number = i % 3 - 1;
var pw:Number = bitmap3d.bitmapData.width + padding;
var ph:Number = bitmap3d.bitmapData.height + padding;
picture3d.x = row * pw;
picture3d.y = col * ph;
picture3d.transform.colorTransform = darker;
picture3d.buttonMode = true;
album.addChild(picture3d);
}
album.z = fullViewZ;
scene.addChild(album);
Next, in a for loop, we traverse the pictures array (remember that one?) It holds a collection of objects of type BitmapData, each one representing a picture. To display the picture in 3D we must wrap it into a Bitmap3D object, and this is what we do first.
Each picture must be clickable. Unfortunately a Bitmap3D does not extend Sprite3D so it doesn't support interactivity - this is the same situation as with Sprite vs. Bitmap in regular AS3. To make the pictures interactive we need to wrap each Bitmap3D into a Sprite3D.
When a child is added its top-left corner is always positioned a 0,0 coordinates of its parent. It works so in AS3, and FIVe3D correctly follows this behavior. What we need here is to position the Bitmap3D inside the Sprite3D in a way that its center is at 0,0 coordinates not its top-left corner. That is why offset the x and y coordinates of the bitma3d by half its width and half its height respectively.
Next, using some simple math we distribute the Sprite3Ds in a 3x3 pattern.
Finally we apply a dark color transform to each Sprite3D, we set their 'buttonMode' property to true and we add it as a child to the 'album' Sprite3D. The final code after the loop positions the album on a correct z distance (while x and y are set 0 by default). And, once the album is ready, we add it to the scene object as a child.
You can test the application now. You should see all 9 pictures as small thumbnails laid out in a 3x3 pattern in the middle of the screen. Not very interesting, huh? Yep! And it won't get any better until we add some interactivity!
4. Adding interactivity
import gs.TweenLite;
import fl.motion.easing.Sine;
import flash.events.MouseEvent;
We also need to declare two more variables:
private var zoomMode:Boolean = false;
private var zoomedPicture:Sprite3D;
Now let's get back to the 'buildAlbum'. Right after the last line, where the album is added to the scene, let's enter some listeners:
album.addEventListener(MouseEvent.ROLL_OVER, onOver, true);
album.addEventListener(MouseEvent.ROLL_OUT, onOut, true);
stage.addEventListener(Event.RESIZE, onResize);
stage.addEventListener(MouseEvent.MOUSE_MOVE, moveAlbum);
Now we can add the functions to handle the rollover/rollout events. Notice how I get a reference to the object that dispatched the event using the 'target' property of the event object:
private function onOver(me:MouseEvent):void {
if (me.target is Sprite3D && !zoomMode) {
(me.target as Sprite3D).transform.colorTransform = lighter;
}
}
private function onOut(me:MouseEvent):void {
if (me.target is Sprite3D && !zoomMode) {
(me.target as Sprite3D).transform.colorTransform = darker;
}
}
private function onResize(e:Event):void {
scene.x = Math.round(stage.stageWidth/2);
scene.y = Math.round(stage.stageHeight / 2);
}
private function moveAlbum(me:MouseEvent):void {
if (zoomMode) return;
var mouseXPos:Number = (me.stageX - stage.stageWidth/2) / (stage.stageWidth / 2);<br />
var mouseYPos:Number = (me.stageY - stage.stageHeight/2) / (stage.stageHeight / 2);</p>
TweenLite.killTweensOf(album);<br />
var props:Object = new Object();<br />
props.rotationY = 45 * mouseXPos;<br />
props.rotationX = -15 * mouseYPos;<br />
props.x = 400 * mouseXPos;<br />
props.y = 300 * mouseYPos;<br />
props.ease = Sine.easeOut;<br />
TweenLite.to(album, .3, props);<br />
}
After this basic check, I divide the mouse position by the stage width in a way to get a value between -1 and 1, where -1 means the mouse is on the left edge of the screen, and 1 means it is on the right.
Next, I call the 'killTweensOf' method to make sure any animation that might be going on now will be terminated. Now I can setup and start a new animation.
I assemble the properties of the new animation in the props object. They are all based on the mouse position and have some maximal values. For example, rotationY can have a minimum value of -45 and a maximum value of 45 degrees. Once the animation properties are ready, I must add a last one, called 'ease' which specifies the type of movement and easing to use. In the last line I call TweenLite and pass the object to be animated - the album, a time for the animation expressed in seconds - 0.3 seconds in this case, and the target properties for the animation.
Try the application now. The 3x3 album should be responding to the mouse movement by rotating and moving sideways. Each picture should become slightly lighter on rollover and darker again on rollout. Finally, if you resize the window, the album will always be centered vertically and horizontally.
You'd better start enjoying 3D flash applications by now. Because you're in one!
5. Zooming pictures

Before we finish we need to add one more important feature: the ability to zoom pictures. We do not need any new import statements, nor do we need to declare any more variables. What we need to do is to add another listener. In 'buildAlbum' function add a last line:
stage.addEventListener(MouseEvent.CLICK, onClick);
private function onClick(me:MouseEvent):void {
var props:Object;
if (me.target is Sprite3D && !zoomMode) {
TweenLite.killTweensOf(album);
props = new Object();
props.z = 0;
props.rotationX = 0;
props.rotationY = 0;
props.x = 0 - me.target.x;
props.y = 0 - me.target.y;
props.ease = Sine.easeInOut;
TweenLite.to(album, .6, props);
zoomedPicture = me.target as Sprite3D;
zoomMode = true;
} else if (zoomMode) {
zoomedPicture.transform.colorTransform = darker;
TweenLite.killTweensOf(album);
props = new Object();
props.z = fullViewZ;
props.x = 0;
props.y = 0;
props.ease = Sine.easeOut;
props.onComplete = onBackToFull;
TweenLite.to(album, .3, props);
}
}
First possibility: a picture was clicked. That means the target is a Sprite3D and the zoomMode is off. This is the situation handled by the first part of the 'if' statement. In that case we start a tween with all the rotation properties set to 0 - we want the picture to face the screen. For the x and y, we also set them to 0 and we subtract the position of the picture inside the album - this way the zoomed picture will always be positioned in the middle of the screen.
The z property for the album is also set to 0, but in that case 0 has a special meaning. In FIVe3D, if you keep the default settings for the Scene3D object (as I do here) and set an objects 'z' property to 0 it will be scaled 1:1 to it's original size. This way we make sure the pictures will have the best possible quality when zoomed in.
Finally we keep a reference to the clicked picture in the 'zoomedPicture' variable and set the 'zoomMode' flag to true.
The next possibility is that a click event occurred when 'zoomMode' in on. In this situation we 'un-zoom' the album no matter what was clicked.
The second part of the 'if' statement does pretty much the opposite of the first one as long as the animation is concerned. What is worth noting is that the 'zoomMode' is not being reset. Instead I create a new property for the TweenLite called 'onComplete'. It takes a function as argument. That function will be called when the animation is over. Here's the code:
private function onBackToFull():void {
zoomMode = false;
}
Conclusion
Now it is your turn to take this application to the next level:
- Load the album information from an XML file
- Add a title and a description for each picture
- Use 3D to dislpay them - FIVe3D is particularly good at handling 3D text
- Make it work with any number of pictures of different size and aspect ratios
- Load the pictures from your flickr account using the Flickr API
- A nice tweak would be to add reflections for the pictures in the bottom row
- In the zoom mode, the other pictures could be accessible without having to zoom out first
- or... anything else you want :)
Hope you enjoyed the tutorial. Have fun!
About the Author
Bartek Drozdz is a Interactive Designer/Developer with 10 years of experience in web technologies. He comes from Poland and currently lives in Stockholm. His main interest are 3d, animation and interaction, his favorite tool: Flash. He runs a blog focusing on this topics called Everyday Flash.




Facebook Application Development
As always, great work Bartek :-)
Interesting article and nice blog you have too!
Thanks!
ilk