Create A 3D Product Viewer in Flex 3

Tweet

This is the winning article from the recent competition we ran to promote Flex articles on sitepoint.com.

I remember the first time I saw the Mini Configurator on the Mini USA site. I was blown away — I loved just playing with the colors and the options — it was a truly immersive experience of the kind that only Flash applications can offer, and I’m sure it sold more than a few Minis.

Ever since then I’ve wanted to create one of my own, and in this article I’ll show you how to do just that. For this example, I’ll assume that you’re across the basics of working with Flex 3 — that you’re set up with an editor such as Flex Builder 3, are familiar with MXML and ActionScript 3, and can create basic applications. If you’re after a beginner’s tutorial, or need a refresher, try Rhys Tague’s beginner’s tutorial.

Getting Started

The idea of a "configurator" is pretty simple: provide a 3D model of some object, then allow the user to change the colors, or to hide and show various parts of the model.

To build your own configurator, you need three things.

  1. a 3D modeling tool
  2. a 3D model of an object (either something that you’ve downloaded or created yourself)
  3. a tool for displaying that model in a Flex application

As far as 3D modelling tools go, I’ve found that Google’s Sketchup is ideal. It’s free, which is always good, but the other bonus is the huge database of models in Sketchup format that are available in the Sketchup 3D Warehouse.
After downloading and installing Sketchup, I visited the 3D warehouse and selected a model to use in my application. For this article, I’ve chosen one of the light cycles from the Disney movie Tron. My decision to use this object was partly cultural (every true geek loves the movie) and partly because it’s a fairly simple shape, so allowing the user to alter it (for example, by changing its color) wouldn’t be too complicated.

The light cycle model I’ve used is displayed in the image below.

Figure 1. A cherry-colored model of a light cycle, from the movie Tron

My model light cycle didn’t actually exist in the exact format pictured above. To begin with, there were a pair of light cycles — I deleted the second one, and tilted the remaining cycle a little, in order to begin with a shape that was perfectly vertical. I’d recommend you do the same for your own model.

You should also reposition your model so that it’s centred around the origin point (this point is represented by the intersection of the red, green, and blue lines in Sketchup). This step is really important, because when you load the model you want to know where it exists in your scene. If the model is floating off in space somewhere, it’s going to be difficult to find, so be sure it’s oriented around the point (0, 0, 0).

To import the light cycle into your Flex application, you need to save it in a standard file format. The format that seems to be the most popular for 3D tasks is Collada, but unfortunately the Export to Collada format functionality is only available in Sketchup Pro.

Luckily, I have a trick up my sleeve: if you export the model in Google Earth format, there’s a Collada file hidden in the Google Earth .kmz file. All you need to do is change the extension of the Google Earth file from .kmz to .zip, and unzip the file. Among the unzipped files you’ll find a directory named models that contains the Collada model. Voila! You’ve exported a Collada file from the free version of Sketchup!

Installing PaperVision 3D

With our Collada model in hand, it’s time for us to find a way import our light cycle into a Flex application. Our first task is to select a 3D rendering engine for Flash to display it with. There are two free 3D engines to choose from at the moment; PaperVision 3D and Away3D. For this example, I’ve chosen PaperVision because of its integration with the ASCollada library, which is a very comprehensive Collada parser for ActionScript.

To download the latest version of PaperVision 3D, perform an SVN checkout from the PaperVision Subversion repository. If you’re not comfortable working with Subversion, you can download the files that I’ve used to create this example from this article’s code archive.

Then create a new Flex application project (either using Flex Builder 3 or the Flex 3 SDK) and copy the com and org directories from the GreatWhite branch into your Flex application project.

Next, create a new assets folder, and copy the model file I exported from Sketchup into it; call that file cycle.dae. If your model contains texture files, then you’ll need to copy those files into your Flex project as well. You’ll also need to edit the .dae file (which is really just XML) to make sure that the texture objects point to the correct texture paths. Thankfully the little light cycle model that we’re using for this example doesn’t make use of any textures.

With everything in place, your project should look something like the image shown below.

Figure 2. Our Flex 3 Configurator project

The assets directory holds the model and any textures it needs. And the com and org folders come from the PaperVision Great White code.

Viewing the Model

To get our feet wet, we’ll first try something very simple: viewing the model. The code for this Flex application, which I’ve called model.mxml, is shown below:

<?xml version="1.0" encoding="utf-8"?>  
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="horizontal"  
 creationComplete="onInit(event);">  
<mx:Script>  
<![CDATA[  
import mx.core.UIComponent;  
 
import org.papervision3d.cameras.FreeCamera3D;  
import org.papervision3d.render.BasicRenderEngine;  
import org.papervision3d.objects.parsers.DAE;  
import org.papervision3d.objects.DisplayObject3D;  
import org.papervision3d.scenes.Scene3D;  
import org.papervision3d.view.Viewport3D;  
 
private var renderer:BasicRenderEngine = new BasicRenderEngine();  
private var scene:Scene3D = new Scene3D();  
private var camera:FreeCamera3D = new FreeCamera3D();  
private var viewport:Viewport3D = new Viewport3D( 200, 200, true, true );  
 
[Embed(source="assets/cycle.dae", mimeType="application/octet-stream")] private var  
MyModel:Class;  
protected function onInit( event:Event ) : void {  
 var flexComp:UIComponent = new UIComponent();  
 cnvUnderneath.addChild( flexComp );  
 flexComp.addChild( viewport );  
 var modelCol:DAE = new DAE();  
 modelCol.load( XML( new MyModel() ) );  
 var model:DisplayObject3D = scene.addChild( modelCol );  
 camera.y = -2000;  
 camera.z = 2500;  
 camera.lookAt( model );  
 addEventListener(Event.ENTER_FRAME,onEnterFrame);  
}  
 
private function onEnterFrame( event : Event ):void  
{  
 renderer.renderScene(scene,camera,viewport);  
}  
]]>  
</mx:Script>  
<mx:Canvas id="cnvUnderneath" width="100%" height="100%" />  
</mx:Application>

This is about as simple as 3D gets — which, admittedly, is not particularly simple. To render something in 3D, you need four pieces of information:

  1. The Scene: The layout of the model, or models, in space.
  2. The Viewport: The Flash sprite that will receive the rendered image.
  3. The Camera: This is the camera, or more specifically, the location and rotation of the camera within the scene.
  4. The Renderer: The engine which takes the scene and camera and renders the image for the viewport.

Without breaking down every line, our onInit method, which is called when the application starts up, does the following:

  1. Load the model.
  2. Add the model to the scene.
  3. Position the camera.
  4. Have the camera look at the model.

Since our model is located at position (0, 0, 0), the code above moves the camera back from the model by adjusting the y and z coordinates. The onEnterFrame method finishes the job by using the renderer to render the scene into the viewport.

Launch this application in Flex Builder 3, and you should see something like the view shown in Figure 3.

Figure 3. Our cherry-colored light cycle in Flex

Not too shabby! In fact, what we’ve achieved here is quite significant — especially considering that Collada is actually a very complex XML standard. You may find that not all models exported from Sketchup will work with PaperVision — in fact, you’ll probably have to do some tweaking of both your Sketchup model (to simplify the shape) and your Flex application in order to produce something that works well.

One other important point to remember is that the more complicated your model, the longer it will take to load and render. You should therefore keep your model as simple as possible. For example, if your model is of a car, and you want to allow your users to choose the paint color of the chassis, then your model should not include any information about the car’s interior. All of that interior stuff represents unnecessary complexity that will result in lower performance and longer load times for your user.

Interacting With the Model

To keep things simple, we’ll restrict the changes that our configurator users can make to just one — the color of the light cycle. This means we’re going to change the color of the "material" used in the model. All 3D models are composed of polygons covered in a "material." That material can be colored, shaded, textured, bump-mapped, and distorted in all manner of ways. In this case, we’re going to use a shaded color material.

The code for our light cycle color configurator is shown below:

<?xml version="1.0" encoding="utf-8"?>  
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="horizontal"  
creationComplete="onInit(event);">  
<mx:Script>  
<![CDATA[  
import mx.utils.ColorUtil;  
import mx.core.UIComponent;  
 
import org.papervision3d.materials.utils.MaterialsList;  
â‹®  
import org.papervision3d.lights.PointLight3D;  
 
private var renderer:BasicRenderEngine = new BasicRenderEngine();  
private var scene:Scene3D = new Scene3D();  
private var camera:FreeCamera3D = new FreeCamera3D();  
private var viewport:Viewport3D = new Viewport3D( 200, 200, true, true );  
private var model:DisplayObject3D = null;  
 
[Embed(source="assets/cycle.dae", mimeType="application/octet-stream")] private var MyModel:Class;  
 
protected function onInit( event:Event ) : void {  
 var flexComp:UIComponent = new UIComponent();  
 cnvUnderneath.addChild( flexComp );  
 flexComp.addChild( viewport );  
 
 loadModel();  
 
 camera.y = -2700;  
 camera.x = 0;  
 camera.z = 2000;  
 camera.lookAt( model );  
 addEventListener(Event.ENTER_FRAME,onEnterFrame);  
}  
private function loadModel() : void {  
 if ( model != null )  
 scene.removeChild( model );  
 var light:PointLight3D = new PointLight3D( true,true );  
 light.z = -2000;  
 light.x = 500;  
 light.y = 500;  
 var lightColor:uint = 0x111111;  
 var modelCol:DAE = new DAE();  
 modelCol.scale = 1.1;  
 modelCol.load( XML( new MyModel() ), new MaterialsList( {  
   material0:new FlatShadeMaterial( light, 0x000000, lightColor ),  
   ForegroundColor:new FlatShadeMaterial( light, 0x000000, lightColor ),  
   material1:new FlatShadeMaterial( light, clrPicker.selectedColor,  
     lightColor ),  
   material2:new FlatShadeMaterial( light,  
   mx.utils.ColorUtil.adjustBrightness(clrPicker.selectedColor,-20), lightColor ),  
   FrontColor:new FlatShadeMaterial( light, 0xFFFFFF, lightColor ),  
   material3:new FlatShadeMaterial( light, 0x000099, lightColor ),  
   material4:new FlatShadeMaterial( light,  
   mx.utils.ColorUtil.adjustBrightness(clrPicker.selectedColor,-200), lightColor )  
 } ) );  
 modelCol.roll( 28 );  
 
 model = scene.addChild( modelCol );  
 
 light.lookAt(model);  
}  
private function onEnterFrame( event : Event ):void  
{  
 renderer.renderScene(scene,camera,viewport);  
}  
]]>  
</mx:Script>  
<mx:Panel title="Properties">  
 <mx:Form>  
   <mx:FormItem label="Color">  
     <mx:ColorPicker id="clrPicker" selectedColor="#8888DD"  
       change="loadModel();" />  
   </mx:FormItem>  
 </mx:Form>  
</mx:Panel>  
<mx:Canvas id="cnvUnderneath" width="100%" height="100%" />  
</mx:Application>

As you can see, I’ve moved the code that loads the model into a new method called loadModel. This method is executed when the Flex application starts up, as well as any time our user chooses a new color.

Our code also provides a MaterialList object to the DAE parser that loads the Collada light cycle model. This list of material corresponds to the materials exported by Google Sketchup. I found the names by looking into the DAE file myself and experimenting with which materials changed which portions of the Cycle.

The material I chose for the color portions was a FlatShadedMaterial. This material takes:

  • a light source
  • a color for the material
  • a color for the light

We’re using the color provided by the color picker, and adjusting it to make it darker or lighter using the ColorUtil Flex class.

Running our configurator application in Flex Builder produces the following result.

Figure 4. Our completed light cycle configurator

Our users can now select a color using the standard ColorPicker control, and the light cycle model will change accordingly.

Providing a light source really adds to our model some depth that wasn’t apparent in the first rendering. Also, the fact that our model is clearly constructed from polygons actually adds to the whole "Tron" look and feel.

Where to Go From Here

There are lots of directions in which you could take this example. For example, you could hide or show various parts of the model by adjusting the visible parameter on the DisplayObject3Delements property within the model; you could add some direct interaction with the model by allowing the user to alter the position of the camera using the mouse; you could even use Flex effects on the model to make it glow, fade or zoom away when the customer takes it for a spin!

Whichever direction you take, you can have lots of fun with this code (don’t forget to download it!). I look forward to seeing your creations in an online store window soon!

Free book: Jump Start HTML5 Basics

Grab a free copy of one our latest ebooks! Packed with hints and tips on HTML5's most powerful new features.

No Reader comments