Step 1: Reviewing the Flickr API
This application will retrieve photos from Flickr to be displayed within a 3D space. Briefly, the process of downloading photos from Flickr is as follows:
- A Flickr API key is obtained.
- The AS3Flickr and AS3CoreLib ActionScript libraries are downloaded and referenced from Flex Builder.
- The Flickr cross domain policy files are loaded to allow Flex to access images from remote domains.
- A search of Flickr is performed.
- The results are used to find the URL of the Flickr images.
- The images themselves are downloaded and saved/displayed.
This process is covered in depth by a previous tutorial which you can find here. I highly recommend you read that tutorial if you are not familiar with the AS3Flickr library, as these points will only be briefly mentioned in this tutorial.
Step 2: An Introduction to Away3D Lite
The Flash runtime has made some impressive progress over the last few years. The widespread deployment of the Flash runtime, both with Adobe AIR and browser plugins, along with the performance increase in later versions, means Flash has a very diverse ecosystem of libraries and applications. It has gone from being an animation tool to a general purpose development platform for games, business applications, 3D visualizations, video players and more.
3D engines have evolved alongside Flash for some time, going from a curiosity to now being fully featured and even commercial libraries. Away3D is one of the more popular free Flash 3D engines, and recently a smaller, faster version of Away3D was released called Away3D Lite. While Away3D Lite lacks some of the eye candy present in the more complicated 3D engines, its focus on simplicity means that it is perfect for a simple application like a 3D photo album.
Step 3: Get Away3D Lite
You can download a copy of Away3D Lite 1.0 for free from this web page here. Download and extract the engine source code to a convenient location.
Step 4: Get the Greensock TweenMax Library
The movement of the camera within the 3D photo gallery will be performed using the Greensock TweenMax library, which you can get from this page here. Again, download and extract the library to a convenient location.
Step 5: Get AS3Flickr and AS3CoreLib
The AS3Flickr and AS3CoreLib libraries are used to contact the Flickr web service. You can find details about these libraries from the article Build a Photo Viewer Using Flex and the Flickr API.
Step 6: Create a New Project
Create a new Flex project, and add the Away3D Lite, TweenMax, AS3Flickr and AS3CoreLib libraries to the Flex Build Path.
Step 7: Define the Application Attributes
applicationComplete="appComplete()"
This sets the appComplete function to be called when the application has initialized, and will serve as the entry point for our code.
frameRate="100"
This sets the frame rate cap for the application at 100 frames per second. The default is 24, and increasing this will make the application seem more fluid.
width="600" height="400" backgroundGradientAlphas="[1.0, 1.0]" backgroundGradientColors="[#000000, #374040]"
These attributes define the size and background color of the Flex application.
Your Application tag should look something like the code below:
view plaincopy to clipboardprint?
- <mx:Application
- xmlns:mx="http://www.adobe.com/2006/mxml"
- layout="absolute"
- applicationComplete="appComplete()"
- width="600"
- height="400"
- backgroundGradientAlphas="[1.0, 1.0]"
- backgroundGradientColors="[#000000, #374040]"
- frameRate="100">
-
- mx:Application>
Step 8: Add Some Controls
Add the following tags to the Application tag.
view plaincopy to clipboardprint?
- <mx:TextInput x="10" y="10" id="txtSearch"/>
- <mx:Button x="178" y="10" label="Search" fillAlphas="[0.9, 0.9, 0.9, 0.9]" click="{applicationManager.flickrSearch(this.txtSearch.text)}"/>
We'll give the user the ability to search Flickr and display the results within our 3D photo gallery at run time. These two tags add a text box, where the search phrase can be entered, and a button to initiate the search.
The click event for the button calls applicationManager.flickrSearch(this.txtSearch.text). The ApplicationManager will be created in later steps, and the flickrSearch function will contain the code to contact Flickr and download the photos.
Step 9: Add Some Code
Add a Script tag to the Application tag. This is where the ActionScript code will be written.
view plaincopy to clipboardprint?
- <mx:Script>
-
- // code goes here
- ]]>
- mx:Script>
Step 10: Add the Variables
Add the following variable inside the Script tag.
view plaincopy to clipboardprint?
- private var applicationManager:ApplicationManager = null;
private var applicationManager:ApplicationManager = null;
The ApplicationManager class will be responsible for initializing the Away3D engine, loading the Flickr images and moving the camera. We hold a reference to it in the applicationManager variable so the button created in step 8 can call the flickrSearch function.
Step 11: Loading the Cross Domain Policy Files
view plaincopy to clipboardprint?
- Security.loadPolicyFile("http://api.flickr.com/crossdomain.xml");
- Security.loadPolicyFile("http://farm1.static.flickr.com/crossdomain.xml");
- Security.loadPolicyFile("http://farm2.static.flickr.com/crossdomain.xml");
- Security.loadPolicyFile("http://farm3.static.flickr.com/crossdomain.xml");
- Security.loadPolicyFile("http://farm4.static.flickr.com/crossdomain.xml");
Security.loadPolicyFile("http://api.flickr.com/crossdomain.xml"); Security.loadPolicyFile("http://farm1.static.flickr.com/crossdomain.xml"); Security.loadPolicyFile("http://farm2.static.flickr.com/crossdomain.xml"); Security.loadPolicyFile("http://farm3.static.flickr.com/crossdomain.xml"); Security.loadPolicyFile("http://farm4.static.flickr.com/crossdomain.xml");
In order to access the images on the Flickr servers the cross domain policy files need to be loaded. This is covered in more detail in the article Build a Photo Viewer Using Flex and the Flickr API.
Step 12: The appComplete Function
Add a new function called appComplete.
view plaincopy to clipboardprint?
- private function appComplete():void
- {
- applicationManager = new ApplicationManager();
- this.addChildAt(new SpriteUIComponent(applicationManager), 0);
- }
private function appComplete():void { applicationManager = new ApplicationManager(); this.addChildAt(new SpriteUIComponent(applicationManager), 0); }
This function is called when the Flex application has loaded and initialized. It is here that we create a new instance of the ApplicationManager class and add it as a child control. Notice that we actually pass an instance of a class called SpriteUIComponent to the addChildAt function.
As you will see in later steps, the ApplicationManager extends one of the template classes from the Away3D Lite API. These template classes themselves extend the Flex Sprite class. Even though the addChildAt function will accept a Sprite, an exception will be raised at runtime. This is because only classes extending the UIComponent class can be added a child controls of a Flex application. To work around this limitation the SpriteUIComponent extends the UIComponent class, and then adds a Sprite as its own child.
Step 13: The SpriteUIComponent Class
view plaincopy to clipboardprint?
- package
- {
- import flash.display.Sprite;
-
- import mx.core.UIComponent;
-
- public class SpriteUIComponent extends UIComponent
- {
- public function SpriteUIComponent(sprite:Sprite)
- {
- super ();
-
- explicitHeight = sprite.height;
- explicitWidth = sprite.width;
-
- addChild (sprite);
- }
- }
- }
package { import flash.display.Sprite; import mx.core.UIComponent; public class SpriteUIComponent extends UIComponent { public function SpriteUIComponent(sprite:Sprite) { super (); explicitHeight = sprite.height; explicitWidth = sprite.width; addChild (sprite); } } }
As was mentioned above, the SpriteUIComponent class extends the UIComponent class (meaning it can be added as a child of a Flex application), and then adds the supplied Sprite object as its own child.
Step 14: The ApplicationManager Class
Import the following packages
view plaincopy to clipboardprint?
- import away3dlite.core.base.*;
- import away3dlite.core.utils.*;
- import away3dlite.loaders.*;
- import away3dlite.materials.*;
- import away3dlite.primitives.*;
- import away3dlite.templates.*;
-
- import com.adobe.webapis.flickr.*;
- import com.adobe.webapis.flickr.events.*;
-
- import flash.display.*;
- import flash.events.*;
- import flash.geom.*;
- import flash.net.*;
-
- import com.greensock.*;
-
- import mx.collections.*;
import away3dlite.core.base.*; import away3dlite.core.utils.*; import away3dlite.loaders.*; import away3dlite.materials.*; import away3dlite.primitives.*; import away3dlite.templates.*; import com.adobe.webapis.flickr.*; import com.adobe.webapis.flickr.events.*; import flash.display.*; import flash.events.*; import flash.geom.*; import flash.net.*; import com.greensock.*; import mx.collections.*;
Step 15: Extend the BasicTemplate
Define the ApplicationManager class as extending the BasicTemplate class.
view plaincopy to clipboardprint?
- public class ApplicationManager extends BasicTemplate
public class ApplicationManager extends BasicTemplate
A new feature included with Away3D Lite are the template classes. Almost every Away3D application needs to initialize the same underlying classes like a camera and a view, and also setup event listeners for frame events. These common steps, which used to have to be coded by each developer manually, are now performed by one of several template classes. We will extend one of these template classes, BasicTemplate, with the ApplicationManager.
Step 16: Define the Constants
The ApplicationManager will have a number of constant variables that define the appearance and function of the class.
view plaincopy to clipboardprint?
- private static const SEARCH_STRING:String = "landscape";
- private static const MAX_RESULTS:int = 50;
- private static const API_KEY:String = "YourFlickrAPIKey";
private static const SEARCH_STRING:String = "landscape"; private static const MAX_RESULTS:int = 50; private static const API_KEY:String = "YourFlickrAPIKey";
These three constants relate to the Flickr web service.
view plaincopy to clipboardprint?
- private static const PHOTO_HEIGHT:Number = 50;
- private static const PHOTO_WIDTH:Number = PHOTO_HEIGHT * 1.618;
private static const PHOTO_HEIGHT:Number = 50; private static const PHOTO_WIDTH:Number = PHOTO_HEIGHT * 1.618;
These two variables define the size of the photos in 3D space. The width is defined relative to the height using the golden rectangle ratio, which produces a rectangle whose shape is generally considered aesthetically pleasing.
view plaincopy to clipboardprint?
- private static const PHOTO_CLOUD_WIDTH:Number = 1000;
- private static const PHOTO_CLOUD_HEIGHT:Number = 1000;
- private static const PHOTO_CLOUD_DEPTH:Number = 3000;
private static const PHOTO_CLOUD_WIDTH:Number = 1000; private static const PHOTO_CLOUD_HEIGHT:Number = 1000; private static const PHOTO_CLOUD_DEPTH:Number = 3000;
These three variables define the area that all the photos will be contained in.
view plaincopy to clipboardprint?
- private static const NUMBER_PHOTOS:int = 300;
private static const NUMBER_PHOTOS:int = 300;
This variable defines how many individual photos will be created in the area defined above.
view plaincopy to clipboardprint?
- private static const CAMERA_JUMP_TIME:Number = 3;
- private static const CAMERA_DIST_FROM_PHOTO:Number = 30;
- private static const CAMERA_DIST_JUMP_BACK:Number = 100;
private static const CAMERA_JUMP_TIME:Number = 3; private static const CAMERA_DIST_FROM_PHOTO:Number = 30; private static const CAMERA_DIST_JUMP_BACK:Number = 100;
These variables define the movement and speed of the camera as it jumps from one photo to the next. The camera will follow a bezier curve, with the start of the curve being 30 units back from a photo, moving towards a point 100 units back, and then ending at a point 30 units back from the next photo.
Step 17: Embedding the Default Image
view plaincopy to clipboardprint?
- [Embed(source="../media/texture.jpg")]
- protected static const DefaultTexture:Class;
[Embed(source="../media/texture.jpg")] protected static const DefaultTexture:Class;
The images that will be displayed all come from Flickr, which means that there will be an initial delay as the photos are retrieved. In the mean time an image embedded into the SWF file will be displayed, to give the user something to look at other than white rectangles.
Step 18: Adding the Variables
view plaincopy to clipboardprint?
- protected var textures:ArrayCollection = new ArrayCollection();
- protected var photos:ArrayCollection = new ArrayCollection();
- protected var loadedTextures:int = 0;
protected var textures:ArrayCollection = new ArrayCollection(); protected var photos:ArrayCollection = new ArrayCollection(); protected var loadedTextures:int = 0;
The images returned from Flickr are stored in the textures collection. The Away3D Lite meshes that will display the images are stored in the photos collection. The loadedTextures variable keeps a track of how many images have been loaded from Flickr, so they can be stored in the appropriate position in the textures collection.
Step 19: Initializing the ApplicationManager
Add a new function called onInit.
view plaincopy to clipboardprint?
- override protected function onInit():void
override protected function onInit():void
Once the underlying BasicTemplate object has initialized itself, the onInit function will be called. It is here that we initialize the extending class.
view plaincopy to clipboardprint?
- for (var i:int = 0; i <>
- addTexture(Cast.bitmap(DefaultTexture));
for (var i:int = 0; i <> First we create a collection of textures for our 3D photo meshes to use, all initially using the default image we embedded in step 17. Cast is a utility class provided by Away3D Lite, and here we use it to transform the embedded image into a BitmapData object, which is supplied to the addTexture function.
view plaincopy to clipboardprint?
- for (var j:int = 0; j <>
- createPlane();
for (var j:int = 0; j <> Now we create the meshes that will display the photo images. The 3D photos will in fact be planes: 2D rectangles with no depth that will face the camera.
- flickrSearch(SEARCH_STRING);
flickrSearch(SEARCH_STRING);
We then initiate a default search of the Flickr image database by calling flickrSearch, to give the user some images to look at before they perform their own search.
view plaincopy to clipboardprint?
- this.debug = false;
this.debug = false;
The debug property of the BasicTemplate class is set to false, which turns off the default debug stats screen that is normally displayed with an Away3D Lite application.
view plaincopy to clipboardprint?
- this.camera.x = 0;
- this.camera.y = 0;
- this.camera.z = 10;
- this.camera.lookAt(new Vector3D(0, 0, 0), new Vector3D(0, 1, 0));
this.camera.x = 0; this.camera.y = 0; this.camera.z = 10; this.camera.lookAt(new Vector3D(0, 0, 0), new Vector3D(0, 1, 0));
By default the BasicTemplate class places the camera down the negative end of the z axis, looking back towards the origin along a positive z axis. When the planes are created by the createPlane function, they are also created with their front pointing along the positive z axis. This means that the camera will, by default, be looking at the back face of the planes.
Typically in 3D applications polygons with their back to the camera are not visible (this is called back face culling), so to see the planes we need to move the camera up the positive end of the z axis and then look back at the origin along a negative z axis.
view plaincopy to clipboardprint?
- var randomPhoto:Mesh = photos.getItemAt(Math.random() * (MAX_RESULTS - 1)) as Mesh;
var randomPhoto:Mesh = photos.getItemAt(Math.random() * (MAX_RESULTS - 1)) as Mesh;
We then get a reference to a random plane stored in the photos collection.
view plaincopy to clipboardprint?
- this.camera.x = randomPhoto.x;
- this.camera.y = randomPhoto.y;
- this.camera.z = randomPhoto.z + CAMERA_DIST_FROM_PHOTO;
this.camera.x = randomPhoto.x; this.camera.y = randomPhoto.y; this.camera.z = randomPhoto.z + CAMERA_DIST_FROM_PHOTO;
The camera is then position slightly in front of this random plane. This is the first photo the user will be looking at when the application loads.
bezierJump();
Finally we start the TweenMax tweening operation that will move the camera from the current photo to view a new random photo.
Step 20: The addTexture Function
Add a new function called addTexture.
view plaincopy to clipboardprint?
- protected function addTexture(data:BitmapData):void
- {
- var texture:BitmapMaterial = new BitmapMaterial(data);
- texture.smooth = true;
- textures.addItem(texture);
- }
protected function addTexture(data:BitmapData):void { var texture:BitmapMaterial = new BitmapMaterial(data); texture.smooth = true; textures.addItem(texture); }
The addTexture function, called by onInit in step 19, creates a new BitmapMaterial class (which is the class that defines the texture of a 3D photos) using the supplied BitmapData, sets its smooth property to true, and adds it to the textures collection.
Step 21: The createPlane Function
Add a new function called createPlane.
view plaincopy to clipboardprint?
- protected function createPlane():void
- {
- var mesh:Plane = new Plane(textures.getItemAt(Math.random() * (MAX_RESULTS - 1)) as BitmapMaterial,
- PHOTO_WIDTH,
- PHOTO_HEIGHT,
- 1,
- 1,
- false);
-
- mesh.x = Math.random() * PHOTO_CLOUD_WIDTH;
- mesh.y = Math.random() * PHOTO_CLOUD_HEIGHT;
- mesh.z = Math.random() * PHOTO_CLOUD_DEPTH;
-
- scene.addChild(mesh);
-
- photos.addItem(mesh);
- }
protected function createPlane():void { var mesh:Plane = new Plane(textures.getItemAt(Math.random() * (MAX_RESULTS - 1)) as BitmapMaterial, PHOTO_WIDTH, PHOTO_HEIGHT, 1, 1, false); mesh.x = Math.random() * PHOTO_CLOUD_WIDTH; mesh.y = Math.random() * PHOTO_CLOUD_HEIGHT; mesh.z = Math.random() * PHOTO_CLOUD_DEPTH; scene.addChild(mesh); photos.addItem(mesh); }
The createPlane function, called by onInit in step 19, creates a new Plane using the dimensions defined by the constants in step 16 and textured with a random BitmapMaterial from the textures collection, positions them randomly within the "photo cloud" area, adds them to the scene (an Away3D object from the BasicTemplate class) so they are visible, and adds them to the photos collection so we can easily reference them later.
Step 22: Getting Images from Flickr
Add two new functions, one called flickrSearch and the other called onPhotosSearch.
view plaincopy to clipboardprint?
- public function flickrSearch(search:String):void
- {
- var service:FlickrService = new FlickrService(API_KEY);
- service.addEventListener(FlickrResultEvent.PHOTOS_SEARCH, onPhotosSearch);
- service.photos.search("", search, "any", "", null, null, null, null, -1, "", MAX_RESULTS, 1);
- }
-
-
- protected function onPhotosSearch(event:FlickrResultEvent):void
- {
- for each (var photo:Photo in event.data.photos.photos)
- {
- var imageURL:String = 'http://static.flickr.com/' +
- photo.server + '/' +
- photo.id + '_' +
- photo.secret + '_m.jpg';
- var loader:Loader = new Loader();
- loader.contentLoaderInfo.addEventListener(
- Event.COMPLETE,
- function(event:Event):void
- {
- var texture:BitmapMaterial = textures.getItemAt(loadedTextures) as BitmapMaterial;
- texture.bitmap = event.currentTarget.content.bitmapData;
- ++loadedTextures;
- loadedTextures %= MAX_RESULTS;
- }
- );
- loader.load(new URLRequest(imageURL));
- }
- }
public function flickrSearch(search:String):void { var service:FlickrService = new FlickrService(API_KEY); service.addEventListener(FlickrResultEvent.PHOTOS_SEARCH, onPhotosSearch); service.photos.search("", search, "any", "", null, null, null, null, -1, "", MAX_RESULTS, 1); } protected function onPhotosSearch(event:FlickrResultEvent):void { for each (var photo:Photo in event.data.photos.photos) { var imageURL:String = 'http://static.flickr.com/' + photo.server + '/' + photo.id + '_' + photo.secret + '_m.jpg'; var loader:Loader = new Loader(); loader.contentLoaderInfo.addEventListener( Event.COMPLETE, function(event:Event):void { var texture:BitmapMaterial = textures.getItemAt(loadedTextures) as BitmapMaterial; texture.bitmap = event.currentTarget.content.bitmapData; ++loadedTextures; loadedTextures %= MAX_RESULTS; } ); loader.load(new URLRequest(imageURL)); } }
Both these functions are taken almost line for line from Build a Photo Viewer Using Flex and the Flickr API. The only difference is that as the images are loaded from Flickr their BitmapData is assigned to the bitmap property of the BitmapMaterial objects stored in the textures collection. This in turn changes the image that is displayed on the planes within the 3D scene.
In this way we can dynamically update the 3D photos being displayed with multiple calls to the flickrSearch function when the user does a search from the main GUI.
Step 23: The bezierJump Function
The bezierJump function is called repeatedly to jump the camera from one photo to the next.
view plaincopy to clipboardprint?
- protected function bezierJump():void
protected function bezierJump():void
Bezier curves are a convenient way to smoothly move the camera along curve defined by just 3 reference points: the cameras current position in front of a photo, a position behind the camera, and the final destination in front of a new photo.
view plaincopy to clipboardprint?
- var randomPhoto:Mesh = photos.getItemAt(Math.random() * (MAX_RESULTS - 1)) as Mesh;
var randomPhoto:Mesh = photos.getItemAt(Math.random() * (MAX_RESULTS - 1)) as Mesh;
First a new random photo to move to is selected from the photos collection.
view plaincopy to clipboardprint?
- TweenMax.to(this.camera,CAMERA_JUMP_TIME,
- {
- x: randomPhoto.x,
- y: randomPhoto.y,
- z: randomPhoto.z + CAMERA_DIST_FROM_PHOTO,
TweenMax.to(this.camera,CAMERA_JUMP_TIME, { x: randomPhoto.x, y: randomPhoto.y, z: randomPhoto.z + CAMERA_DIST_FROM_PHOTO,
We then use TweenMax to move the camera position (as defined by the x, y and z properties) to just in front of the position of the randomly selected destination photo...
delay: 2,
after an initial delay of 2 seconds (which means the user views each photo for 2 seconds)...
view plaincopy to clipboardprint?
- bezier:
- [{
- x: this.camera.x,
- y: this.camera.y,
- z: this.camera.z + CAMERA_DIST_JUMP_BACK
- }],
bezier: [{ x: this.camera.x, y: this.camera.y, z: this.camera.z + CAMERA_DIST_JUMP_BACK }],
along a bezier curve that is influenced by a point just behind the cameras current position...
- onComplete: bezierJump
- });
onComplete: bezierJump });
recursively calling the bezierJump function once the move is complete.
In this way the camera moves to a photo, views it for 2 seconds, jumps to the next photo and starts the process again. In general, recursively calling tweening functions is a great way to script movements in Flex applications.
No comments:
Post a Comment