Seamlessly Combining 2D and 3D in Flash with Planes, Part 3 [AS3] · Jan 12, 09:49 AM
It’s been quite some time since I published parts 1 and 2 of this series, which turned out to be some of the most popular articles on this website. In part 1, I presented some ideas (and math) which either confused you or made a lot of sense to you if you happen to think and learn like I do. In part 2, I wrapped up the concepts into a class (called BasicViewPlus) to help you do the stuff I was talking about without actually thinking about it too hard. In the four months since I published part 2, I have continued to work with and improve BasicViewPlus. At this point, I think it is pretty solid, but I would still consider it beta until more people have tested it.
Since posting part 1, I’ve been promising to post a demo of using BasicViewPlus, and now I’m finally making good on that promise. The purpose of this demo is to illustrate how BasicViewPlus can be used to create an interface that feels interactive, by seamlessly transitioning between 2D and 3D. The following displays a bunch of Flickr photos (by searching for Cloth) in a 2D Grid component, when you click and drag a photo it should feel like you are dragging a piece of cloth:
Download
Download Source (ClothGrid.zip) Here
Overview
This demo uses the WOW physics engine to simulate cloth, but for the purpose of this article I will gloss over this part since it is not really relevant. This shouldn’t really hinder your ability to grasp the main concepts, since all the Physics stuff is handily shoved into a class called Cloth3 which we treat almost exactly the same as we would treat a Papervision3D Plane object. In other words, you could easily add/remove the WOW physics from the code by changing a few lines. (Without the physics stuff, instead of dragging a piece of cloth you would be dragging a plane, or a panel, or whatever you want to think of it as.)
Aside from WOW, I’ve used some other libraries like the BumpSlide UI and the Flickr API. Again, I’m not going to go into detail into using these libraries. My main purpose here is to show you how to use BasicViewPlus and luckily, all the relevant code (in Main.as) is really small and compact.
Prerequisites
This example has been compiled as a pure AS3 project, using the Flex 3 SDK. If you use FlashDevelop on Windows you can use the included project file ClothGrid.as3proj. Regardless of your development environment, you will need to download and add the following libraries to your classpath (in FD do Project>Properties>Class Path):
- Papervision3D 2.0
- Flex3 SDK (frameworks\libs)
- Adobe Corelib
If you are not using the included ClothGrid.as3proj FlashDevelop project file, then you should also include the class path that contains Cloth3.as. This is the path to the src folder that is included with the demo’s source files (ClothGrid.zip).
Included Libraries
To simplify things, I’ve included in ClothGrid.zip some of the necessary libraries needed to compile the demo. The following libraries have been included, (if you compile correctly they will already be in your classpath):
- AS3 Flickr API
- BumpSlide UI
- WOW Engine
- Polygonal Labs AS3 DataStructures for Game Programmers (required by WOW)
- TweenMax (slightly modified for Papervision3D)
- BasicViewPlus v.0.2
I’m going to assume you have some understanding of the purpose of BasicViewPlus, and start off by discussing what I’ve changed/improved since part 2 of this series.
Major Changes to BasicViewPlus since v.0.1
The Constructor:
BasicViewPlus(viewportWidth:Number = 640,
viewportHeight:Number = 480,
scaleToStage:Boolean = true,
interactive:Boolean = false,
cameraType:String = "Target")
- No longer requires a reference to the stage.
- You are allowed to set scaleToStage to true or false
Ability to work with any Object that has the same constructor arguments as Plane:
This functionality is enabled in the form of extra (optional) parameters added to many of the functions, for example freezeDO:
freezeDO(source:DisplayObject,
switches:Object = null,
segmentsW:Number=4,
segmentsH:Number=4,
planeClass:Class=null,
planeProps:Object=null ):DisplayObject3D
- planeClass is the class you want to instantiate. When null, it is assumed you want to instantiate a Plane object.
- planeProps are extra properties which can effect your Plane or Plane-like object.
Before we Jump In
You should probably download the source and open the Main.as file to look at as we go along.
Initializing the Main class
Lets skip the imports section and get straight to instantiating the class-level variables:
public class Main extends MovieClip {private static const API_KEY:String = "9943f1cd0c02696b42b9c7d14c2b7794";private var view:BasicViewPlus;private var photoGrid:Grid;private var _flickrService:FlickrService;private var cloth:Cloth3;private var currPhotoBitmap:Bitmap;private var lastMouseX:Number;private var lastMouseY:Number;
Setting up the Stage
When the stage is ready, we instantiate a Grid and a BasicViewPlus instance. There’s a good chance you’ve never used the BumpSlide Grid, but it’s straightforward, and a really great component. The first argument for the Grid class is the Class we would like the Grid to use to render each grid item. Notice that our instances, photoGrid and view are the same size. The view is placed on top of the photoGrid. Later on, when an object needs to be rendered in 3D it is hidden from the photoGrid and rendered in the view.
public function Main() {addEventListener(Event.ADDED_TO_STAGE, addedToStage);}private function addedToStage(e:Event = null) : void {photoGrid = new Grid(Thumbnail, 250, 250, GridLayout.HORIZONTAL );photoGrid.x = 0;photoGrid.setSize(750, 550);photoGrid.layout.spacing = 2;photoGrid.addEventListener( ThumbnailEvent.MOUSE_DOWN, sourceThumbDown );view = new BasicViewPlus(750, 550, true);addEventListener(Event.ENTER_FRAME, onEnterFrame);addChild(photoGrid);addChild(view);photosSearch("", "cloth");}
Notice that we’ve added an event listener for ThumbnailEvent.MOUSE_DOWN that will fire whenever the user clicks down on a thumbnail in the grid.
Connecting to Flickr
After the components have been initialized, we make a call to the Flickr Service. When the service returns the result, it’s a simple matter of setting the dataProvider property on photoGrid. The results returned by the Flickr service are not the actual photos, but information about the photos. The classes that take care of downloading and displaying the photos are inside of the thumbnail folder included in ClothGrid.zip. If this part is confusing to you, you should read about the Flickr API, and do some experimenting with the BumpSlide UI by downloading the entire package and looking at the included examples.
public function photosSearch( user_id:String = "", query:String = "" ) : void {_flickrService = new FlickrService( API_KEY );_flickrService.addEventListener( FlickrResultEvent.PHOTOS_SEARCH, onResult_photosSearch );_flickrService.photos.search( user_id, "", "any", query, null, null, null, null,-1,"interestingness-desc" );}private function onResult_photosSearch(event:FlickrResultEvent):void {_flickrService.removeEventListener( FlickrResultEvent.PHOTOS_SEARCH, onResult_photosSearch );photoGrid.dataProvider = (event.data.photos as PagedPhotoList).photos;}
Handling MOUSE_DOWN and making the transfer to 3D Space
Now on to the important part. The visual component of the thumbnail is actually a Bitmap object. In the ThumbnailEvent.MOUSE_DOWN event listener we gain access to this Bitmap object with e.content. We use view, our BasicViewPlus instance to temporarily render this object in 3D space. A material will automatically be created for the 3D object. The second argument determines the properties of this material. The third and fourth arguments define the number of segments this 3D object will have. The fifth argument (Cloth3) specifies that the Cloth3 Class is the type of 3D object we want to create, and the last argument will set the forceEnabled property of this new object to false, immediately after it is instantiated:
private function sourceThumbDown(e:ThumbnailEvent=null):void {currPhotoBitmap = e.content;cloth = view.freezeDO(currPhotoBitmap,{ transparent: false, animated:false, doubleSided: true },6,6,Cloth3,{ forceEnabled: false }) as Cloth3;
After the call to freezeDO, the original 2D bitmap is now hidden (visible is false). We can change the properties of the newly created cloth object:
cloth.forceEnabled = true;cloth.useOwnContainer = true;cloth.filters = [ new DropShadowFilter(0, 45, 0, 0.5, 8.0, 8.0) ];
It’s important to call the startRendering function, otherwise view will not do what we want:
view.startRendering();view.visible = true;
Record the position of the mouse, we’ll need these for later:
lastMouseX = mouseX;lastMouseY = mouseY;
In order to position the new 3D object so that it appears exactly where the 2D object was, our cloth object is actually nested inside of two parent containers (DisplayObject3D’s). We use cloth.parent to move along the X, Y, and Z axis.
// tween the drop shadow (to sync with pull out)TweenMax.to(cloth, 0.5, { dropShadowFilter: { distance:32 } } );// pull photo outTweenMax.to(cloth.parent, 0.5, { z: -100 } );// ... and do a little shimmynew TweenGroup([{ target: cloth, time: 0.1, rotY: -12 },{ target: cloth, time: 0.2, rotY: 12 },{ target: cloth, time: 0.1, rotY: 0 }],TweenMax,TweenGroup.ALIGN_SEQUENCE);// position to mouseonMouseMove();
Also notice in the code above that we are tweening a property named rotY. The Cloth3 class has a special property rotY which is similar to rotationY or localRotationY except that changing rotY will effect the physics of the cloth, while the other rotation properties would rotate the cloth without affecting the physics.
Now we add listeners to handle mouse movement and release:
photoGrid.removeEventListener( ThumbnailEvent.MOUSE_DOWN, sourceThumbDown );stage.addEventListener( MouseEvent.MOUSE_UP, mouseUp );stage.addEventListener(MouseEvent.MOUSE_MOVE, onMouseMove);}
Handling MOUSE_MOVE
In the mouse movement handler, we make the cloth follow the mouse and use some tweening to smooth it all out. We also rotate the cloth 40 or -40 degrees depending on the direction the mouse is moving.
private function onMouseMove(e:MouseEvent = null):void {if (cloth) {TweenMax.to(cloth.parent, 0.2, { x: mouseX - cloth.width / 2, y: -mouseY + cloth.height * 0 } );if ( Math.abs(mouseX - lastMouseX) > 5) {var roty:Number = (mouseX > lastMouseX) ? -40 : 40;new TweenGroup([ { target: cloth, time: 0.6, rotY: roty }, { target: cloth, time: 0.6, rotY: 0 } ],TweenMax,TweenGroup.ALIGN_SEQUENCE);}lastMouseX = mouseX;lastMouseY = mouseY;}}
Handling MOUSE_UP and making the transfer back to 2D Space
When the mouse get’s released, we want the thumbnail to tween back into place.
private function mouseUp(e:MouseEvent):void {if (currPhotoBitmap) {stage.removeEventListener( MouseEvent.MOUSE_UP, mouseUp );stage.removeEventListener(MouseEvent.MOUSE_MOVE, onMouseMove);var destPoint:Point = BasicViewPlus.TL2dtoTLStatic3d(new Point(currPhotoBitmap.getBounds(view).x, currPhotoBitmap.getBounds(view).y));TweenLite.to(cloth.parent, 0.3, { x: destPoint.x, y: destPoint.y, z: 0, onComplete:mouseUpComplete } );// alternate, slightly "optimized" version of the previous 2 lines://TweenLite.to(cloth.parent, 0.3, { x: currPhotoBitmap.getBounds(view).x, y: -currPhotoBitmap.getBounds(view).y, z: 0, onComplete:mouseUpComplete } );TweenMax.to(cloth, 0.3, { rotY: 0, dropShadowFilter: { distance:0 } } );}}
Lets take a closer look at the two lines that calculate the return location of the thumbnail. The original Bitmap hasn’t actually gone anywhere, it’s simply been hidden by the freezeDO function we used earlier. In 2D coordinates, we know that the location of this Bitmap in relation to our BasicViewPlus instance, view, is currPhotoBitmap.getBounds(view). To convert these coordinates into the proper 3D space we can use the static function *BasicViewPlus.TL2dtoTLStatic3d. This function was discussed in parts 1 and 2 of this series and is actually very simple.
var destPoint:Point = BasicViewPlus.TL2dtoTLStatic3d(new Point(currPhotoBitmap.getBounds(view).x, currPhotoBitmap.getBounds(view).y));TweenLite.to(cloth.parent, 0.3, { x: destPoint.x, y: destPoint.y, z: 0, onComplete:mouseUpComplete } );
When the tween started in mouseUp has completed, we will transfer back into 2D space. Keep in mind that transfer is just how I like to think about it. All we’re really doing is destroying the 3D object and making the 2D object visible again. We can also stop the rendering to save resources. If you wanted to, you could even remove view from the stage and add it again later in the mouseDown handler.
private function mouseUpComplete():void {view.unfreezeDO(currPhotoBitmap);currPhotoBitmap = null;cloth = null;view.stopRendering(true);view.visible = false;photoGrid.addEventListener( ThumbnailEvent.MOUSE_DOWN, sourceThumbDown );}
One More Thing…
One thing I’ve left out to this point is that in order for the cloth to act like a cloth, it needs a chance to update it’s internal physics, like this:
private function onEnterFrame(e:Event = null):void {if (cloth) cloth.step();}
— Pickle
Comment
InitMonitor v.0.3: Not Just Another PureMVC Initialization Manager [AS3] New Pre-Release SubMicroSite Launched to Destroy Old Site

Hi this is a great tutorial but i havent had a chance to test the code as i am about to download the Flex SDK. In the meantime i wanted to know how would someone go about loading their own picture (local to the swf) instead of one from Flickr? Im assuming just commenting out the Flickr code would disable the rest of the gallery loading.. so after commenting out or even removing the Flickr API code where does one go from there? Any suggestions welcome. I am still new at this Papervision 3D but learning fast and very addicted. thnx
— VC1 · Jun 16, 03:40 PM · #
You need to replace the photoSearch function and related code. This is a key line:
Make sure you understand what that does.
You’ll also need to customize the Thumbnail class.
I can’t offer you a more in-depth explanation. The answer to your question has nothing to do with Papervision3D. You need to read up on the AS3 Loader object, and research the Bumpslide Grid component so you understand how it works. I’d suggest looking at the Bumpslide Grid component examples that come with the Bumpslide source code package.
— Pickle · Jun 16, 06:04 PM · #
Hi Pickle thanks for your prompt reply. I think you’ve pointed me in the right direction. Using AS3 to load images (URLrequest etc) is not a problem for me. I just wasn’t sure if it would be that straighforward. I also know how to load textures in Papervision but am still learning and to be honest this is the first time I have studied code where someone linked the textures from Flickr.. not the 1st I’ve heard of it done but the 1st time for me personally to deal with it.. and i did not want to mess up this beautiful demo.
Yes I will look at the thumbnail class file also to see what its doing with regards to the thumbs.. It seems you’re right about the Bumpslide Package. I just had a quick browse thru subfolders and i’m seeing filename clues which let me know they will helpful to make sense of all the above with a little bit of analysis.
Thanks again for your time and an interesting tutorial. Im gunna bear your advice in mind and jump in at the deep-end now.. Peace out,
VC1
— VC1 · Jun 16, 07:24 PM · #
If you have any questions about my bumpslide libraries, you can post them on the mailing list. Digging through the source code is definitely the great place to start.
— David Knape · Jun 26, 08:14 AM · #
David, thanks for stopping by and for your hard work, BumpSlide kicks ass! Also, it’s good to know about the list, I guess the link was always there on the google code page I just didn’t think to click it (doh!).
— Pickle · Jun 26, 06:04 PM · #
It just worked perfectly! Awesome job! thanks for putting it together.
I had just one problem. when I jump from 2d object to a 3d object. it goes perfectly but it’s creating a plane with wireframe material and segments 1 under it that I have no idea where it’s coming from. For every object I take from 2d to 3d it’s coming with a plane under it and it have no idea how to hide it.
Any clue?
— MP · Jul 21, 03:34 PM · #
MP, haven’t seen that happen before. Send code to gil@ the domain name for this website and I’ll take a look.
— Pickle · Jul 21, 08:07 PM · #
Hi Pickle,
what did you use for the shadow of the clothes?
Cheers
— Riccardo Bartoli · Sep 16, 01:40 AM · #
@Riccardo
— Pickle · Sep 16, 04:54 PM · #