Simulating PicLens with Flex and Away3D – Part 3

by Alejandro Santander 4

Step by step guide for creating a PicLens type 3D photo viewer with Flex and Away3d. This is step 3, of a 3 part tutorial that will sweep many useful techniques used in web application design, Flex, and Flash 3D design. You will make this.

Requirements:

Pre Requisites:

  • Having done Part 1 and 2 of this tutorial.
  • Intermediate programming skills.
  • Moderate knowledge of AS3.
  • Basic Familiarity with OOP.
  • Basic knowledge of Flash CS3.

Pic Lens

Step 0:

This is the last section of the tutorial we’ve been working on. We’re going to wrap up everything we’ve done so far and make our work a solid, working application, which we can use and even extend in the future. First, we will centralize all arbitrary parameters into what’s known as a model, then, we’re going to connect the photo gallery into an external xml so we can load images dynamically, and finally we’re going to enhance our graphics a little bit by adding a reflection effect to the 3D scene.

Step 1 – Centralizing data:

As I stated before, we will start by creating a place holder for all arbitrary variables used so far. With arbitrary variables I mean values that we’ve chosen such as the camera’s tween time in the “moveTo()” method within CameraController, or the Z position of the cameraTarget that can greatly affect the nature of the camera’s motion. All these will be placed in a class that will hold these static constants and will also be used to hold dynamic variables further on, so they can be accessed easily from anywhere in the application. So, go ahead and create a new folder next to “view” called “model”. In it, create a new Actionscript Class called “Model.as”.

Now, I need you to work alone for a while. The job to do is recognise all the values you have used so far throughout the application, that you think would be a good idea to name into a variable and control from this central class. Then, all these variables will be declared in Model.as as static constants. This is how my class looks after doing just that:

package  com.li.photoviewer.model

{ 

     public class Model

     {

          public static const  CAMERA_Z:Number = -2000;

          public static const  CAMERA_Z_ZOOMED:Number = -1500;

          public static const  CAMERA_ZOOM_TIME:Number = 1;

          public static const  CAMERA_ZOOM_EASE:String = "easeoutexpo";

          public static const  CAMERATARGET_FRICTION:Number = 0.95;

          public static const  CAMERATARGET_BOUNCE_FACTOR:Number = 0.5;

          public static const  CAMERATARGET_DRAG_FACTOR:Number = 0.1;

          public static const CAMERATARGET_SPEED_FACTOR:Number  = 0.05;

          public static const  CAMERATARGET_Z:Number = -100;

          public static const  CAMERATARGET_MOVETO_TIME:Number = 1;

          public static const  CAMERATARGET_MOVETO_EASE:String = "easeoutexpo";

          public static const  PHOTO_PLANE_SEGMENTS:Number = 3;

          public static const  PHOTO_MATERIAL_SMOOTH:Boolean = true;

          public static const  PHOTO_FADEIN_TIME:Number = 0.25;

          public static const  PHOTO_SPACING:Number = 750;

          public static const  BG_COLOR1:uint = 0x222222;

          public static const  BG_COLOR2:uint = 0x000000;

          public static const  BG_COLOR_RATIO:uint = 80

          public function Model()

          {

          }

     }

}

“public static const” is a way to declare a variable that will be accessible from the outside of the class, can be accessed without instantiating the holder class, and will not change. If you don’t what Im talking about, you should see how we are going to use these variables and then come back to what I’ve just said and you should get it right away.

Step 2 – Connecting to the centralized data:

So far all this is completely useless unless we connect our classes to Model.as, so that the constants can be used. This is done very easily; for instance to connect the “0.05” value representing a camera velocity factor, we do as in the following code:

private function followTarget(evt:Event):void

{

     camera.lookAt(cameraTarget.position);

     var dX:Number = cameraTarget.x - camera.x;

     camera.x  += dX*0.05;

...

Simply change to:

private function followTarget(evt:Event):void

{

     camera.lookAt(cameraTarget.position);

     var dX:Number = cameraTarget.x - camera.x;

     camera.x  += dX*Model.CAMERATARGET_SPEED_FACTOR;

     ...

As long as the model has been imported in CameraController of course:

import com.li.photoviewer.model.Model;

So this is what you should do next. Import Model.as in all the view and controller classes, and connect the values to it. If you’ve imported the model into a class first, you should get useful codehints from Flex to help you complete this task. When you’re done, you’ll have the power to very precisely tweak the entire application from Model.as. You should play around with this to tune it to your liking.

Step 3 – Setting up an external XML:

Now that our model is set up and working, we will read data from an external xml, and also use the model to store the data contained in the xml. We will trigger a service that will read an xml and detail the images that will be loaded in our application, and once this service is done, the 3D scene will build itself based on this data. For this, lets start by designing our xml. Create a folder named “data” and in it a new file named “data.xml”. The folder should be next to “com” or  “away3d” folders. Then design the xml structure of data.xml like this:

<?xml  version="1.0"?>

<imgs>

     <img name="testImage.jpg"/>

     <img  name="1.jpg"/>

     <img  name="2.jpg"/>

     <img  name="3.jpg"/>

     <img  name="4.jpg"/>

     <img  name="5.jpg"/>

     <img  name="6.jpg"/>

     <img  name="7.jpg"/>

     <img  name="8.jpg"/>

     <img  name="9.jpg"/>

     <img  name="10.jpg"/>

</imgs>

The xml is extremely simple, it just contains the name of each image.

Step 4 – Creating an XML Reader:

We should continue by creating a folder called “services” next to “view” and in it a new Actionscript Class called “XmlReader”, which will take care of extracting the data from the xml. This class should have two variables in its constructor, “xmlPath” as a string and “onComplete” as a function, It will also have a “loader” variable as a URLoader:

private var xmlPath:String;

private var onComplete:Function;

private var loader:URLLoader;

public function XmlReader(xmlPath:String,  onComplete:Function)

{

     this.xmlPath = xmlPath;

     this.onComplete = onComplete;

     loader = new URLLoader();

     loader.addEventListener(Event.COMPLETE,  completeHandler);

     loader.load(new URLRequest(xmlPath));

}

The path of the xml file is stored, (as well as “onComplete” which we will see in a minute), then the loader is initialized and triggered. The function “completeHandler()” will, as its name says, handle the loader once it’s done:

private function completeHandler(evt:Event):void

{

     var xmlDocument:XMLDocument = new  XMLDocument();

     xmlDocument.ignoreWhite  = true;

     var xml:XML = new XML(evt.target.data);

     xmlDocument.parseXML(xml.toString());

     for(var i:uint; i<xmlDocument.firstChild.childNodes.length;  i++)

          Model.data.push(xmlDocument.firstChild.childNodes[i].attributes.name);

     onComplete();

}

The handler parses all the data in the xml and injects it in the model. Of course, we need to go back to Model.as and declare this “data” variable that we are referring to. Just add it after all the constants:

public static var data:Array = [];

Finally (referring again to the completeHandler method), “onComplete()” gets called as soon as all the parsing is done. Remember that we designed this XmlReader to receive a reference to this external function when it is instantiated. This will simply be the function to be called when the reader has finished its job.

Step 5 – Using the reader:

Lets implement this now in the application’s main class, PhotoViewer3D.as. Carefully study these modifications:

package

{

     import com.li.photoviewer.services.XmlReader;

     import com.li.photoviewer.view.PhotoScene;

     import flash.display.Sprite;

     import flash.display.StageAlign;

     import flash.display.StageScaleMode;

     [SWF(backgroundColor="0x000000", frameRate="30")]

     public class PhotoViewer3D extends Sprite

     {

          public function PhotoViewer3D()

          {

               stage.scaleMode  = StageScaleMode.NO_SCALE;

               stage.align  = StageAlign.TOP_LEFT;

               var reader:XmlReader = new XmlReader("data/data.xml", init);

          }

          private function init():void

          {

               var photoScene:PhotoScene = new PhotoScene();

               addChild(photoScene);

          }

     }

}

I’ve just instantiated an XmlReader, pointed it to the xml path, put the creation of the scene in its own separate method, and also pointed the reader to this method so its called as soon as its done.

Step 6 – Loading the images:

The next step is pretty obvious. We now have the data array containing all the names of our images, waiting for us in the model and we’ve also delayed the creation of the 3D scene till after this data has been obtained. So we just need to alter the “initObjects()” method of PhotoScene.as to read this data. It’s too simple actually:

private function initObjects():void

{

     for(var i:uint; i<Model.data.length; i++)

     {

          var photo:PhotoLoader = new PhotoLoader("imgs/" + Model.data[i]);

          photo.x  = i*Model.PHOTO_SPACING;

          scene.addChild(photo);

          cameraController.maxX  = photo.x;

          photo.addOnMouseDown(photoClickHandler);

     }

}

That’s it. Test the application and see how it now loads all the corresponding different images. We’re practically done now, I just want to add an additional effect to the app…

Step 7 – Adding a reflection:

As it is usual in many open source projects, many people share their code in classes that can be very handy and easy to use. You can very often download a class that does a particular thing, use it, and never know how it actually works. So that’s what we’ll do, we will greatly enhance the look of our application by downloading a class, and I will make my point of showing how great it is to use other people’s classes, or even design classes yourself to be reusable in this way.

Download Reflection.as and paste it in the view folder.

The class should be ready to work, except that its package path is all wrong. This is pretty logical, since I’ve just used it for another project and it was placed completely on a different place. Correct this path to the class’ current location. You can also look into the class, now or later, to understand how it works and maybe learn new things that could motivate you to other uses or even new ideas with Away3D.

Step 8 – Plugging in the reflection:

Go back to PhotoScene.as and declare the reflection instance (we don’t need to import Reflection.as since it is in the same package as PhotoScene.as):

private var  reflection:Reflection;

Then, in the “initScene()” method, initialize it and add it to the display list:

reflection = new Reflection(scene, camera);
addChild(reflection);

And finally, in “renderScene()” add a call to the reflections rendering method:

private function renderScene(evt:Event):void

{

reflection.render();

view.render();

}

I also took the time to connect the Reflection class to the model and drop its arbitrary variables there. I even set up a USE_REFLECTION constant in the model and applied this Boolean value in the initialization of the reflection in “initScene()” and the “renderScene()” method too. All this allows you to, not only tweak the app to look good, but also to consider performance issues. With the values that you should have in the model after you do this, you can really optimize the application.

public static const USE_REFLECTION:Boolean = false;

public static const REFLECTION_OFFSET:Number = -525;

public static const REFLECTION_BLUR:Number = 8;

public static const REFLECTION_ALPHA:Number = 0.25;

public static const REFLECTION_SCALING:Number = 2;

So, that’s it! The application is flexible enough to be extended easily. Maybe a horizontal scroller could be a good idea, for example… I leave that to your creativity. I wanted to keep the MVC architecture as simple as I could by avoiding custom events, singleton classes, etc, but those could be good things to investigate for future projects. On the other hand, I really recommend playing with the Away3D engine, the possibilities with it are endless!!

I hope you’ve enjoyed the tutorial. I’m looking forward to make another one when I can =)

Leave a Reply

Your email address will not be published.

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>