3 October 2011
You should have some experience programming with ActionScript and building 3D projects with Flash Professional. Experience with the Proscenium framework is not required to complete this tutorial.
The sample files referenced in this tutorial are included with the Proscenium framework.
Intermediate
In this tutorial you'll take a look at several different ActionScript class files that are used to create 3D sample projects with the Proscenium framework, an ActionScript 3 code library built on top of the Adobe Flash Platform Stage3D APIs that allows for rapid development of interactive 3D content. The goal of this quick introduction is to familiarize you with the Proscenium framework to control 3D elements in Adobe Flash Professional.
Before you begin, be sure to download the Proscenium framework from Adobe Labs and uncompress the ZIP file to your desktop so that you can follow along and open the corresponding AS file as you work through each section in this tutorial. You can analyze the code to see how each step is achieved programmatically.
The sample project class files are located in the code\ProsceniumTutorials\src folder within the Proscenium framework ZIP file. These samples can be compiled and run once the Proscenium project is set up. To set up the project, please be sure to read the setup instructions provided in the sample files.
The sections below cover how to create 3D objects and add them to the scene to display them. You'll also learn how to change a 3D object's material properties, move objects, and add shadows. In the last section of this tutorial, you'll use a class that extends the Sprite class to see how to build a Proscenium application without using the BasicDemo harness class used by the other sections. By extending the Sprite class, you'll gain more control when working with 3D objects and configuring event handlers.
As you follow along with the instructions to work with this sample project, remember the orientation of the world coordinate system. The y-axis is always pointing up. By default, the camera is pointing at the –z direction, which means that the x-axis points to the right side of the screen (see Figure 1).
To get started, you'll create some shapes and add them to the scene. If you haven't already, be sure to download the Proscenium framework from Adobe Labs and uncompress the ZIP file to your desktop. Open the file named Tutorial01_SimpleShapes.as (in the code\ProsceniumTutorials\src folder) in Flash Professional. The goal of this first section is to make objects appear in the 3D scene (see Figure 2).
The code is very straight-forward. The code creates a plane, a cube, a sphere, and a torus, and then adds the 3D shapes to the scene.
To create basic shapes, you'll use the MeshUtils class (previously called the ProceduralGeometry class), as shown in the code example provided below.
The code to create the shapes and add them to the scene is provided below:
package
{
import com.adobe.scenegraph.*;
import flash.display.*;
import flash.display3D.*;
import flash.geom.*;
public class Tutorial01_SimpleShapes extends BasicDemo
{
public function Tutorial01_SimpleShapes()
{
super();
}
override protected function initModels():void
{
// create plane material
var material:MaterialStandard = new MaterialStandard();
material.diffuseColor.set( 0, .4, 0 );
material.specularColor.set( .8, .8, .8 );
material.ambientColor.set( .2, .2, .2 );
// create a plane and add it to the scene
var plane:SceneMesh = MeshUtils.createPlane( 50, 50, 20, 20,
null, "plane" );
plane.appendTranslation( 0, -2, 0 );
scene.addChild( plane );
// create a cube and add it to the scene
var cube:SceneMesh = MeshUtils.createCube( 5 );
cube.appendTranslation( 0, 6, 0 );
scene.addChild( cube );
// create a torus and add it to the scene
var torus:SceneMesh = MeshUtils.createDonut( .25, 1.5, 50, 10,
null, "torus" );
torus.appendTranslation( 10, 2, 0 );
var rotAxis:Vector3D = new Vector3D( 1,1,1 );
rotAxis.normalize();
torus.appendRotation( 45, rotAxis );
scene.addChild( torus );
// create a sphere and add it to the scene
var sphere:SceneMesh = MeshUtils.createSphere( 3, 50, 50,
null, "sphere" );
sphere.setPosition( -10, 2, 0 );
scene.addChild( sphere );
}
}
}
In this section, you'll review how to create an instance of the cube shape. You'll also see how to change its location using the onAnimate() function and change its material based on the shape's location (see Figure 3).
As you review the code, notice that the onAnimate() method is called with time t and the elapsed time since the previous call, dt. The onAnimate() method enables you to change an object's position using the SceneMesh.appendTranslation method.
In this example, a cube is created using the MeshUtils.createCube method. Another instance of the cube is created using the SceneMesh.instance() method. Using this strategy, the two cubes share the same vertex and index buffers.
By default, instanced cube will use the same material named cubeMtrl. This material property can be changed to use a different material using SceneMesh.materialBindings. You begin by creating a new material and binding it to the instanced cube. After that, you can change the material property of an instance as desired.
override protected function initModels():void
{
…
// create a new standard material
var material:MaterialStandard = new MaterialStandard( "cubeMtrl" ); material.diffuseColor.set( 0, 1, 0 );
var cube:SceneMesh = MeshUtils.createCube( 5, material, "cube" );
cube.appendTranslation( 0, 6, -10 );
scene.addChild( cube );
// create an instance of the cube (mesh data is shared)
_cubeInstanced = cube.instance( "cube-instanced" );
_cubeInstanced.appendTranslation( 0, 6, 0 );
scene.addChild( _cubeInstanced );
…
}
// animation is performed in onAnimate
override protected function onAnimate( t:Number, dt:Number ):void
{
_cubeInstanced.setPosition( Math.cos( t ) * 3, 6, Math.sin( t ) * 3 );
// Since a SceneMesh can have multiple submeshes of different materials,
// and since the submeshes can be shared amongst multiple SceneMesh instances,
// direct access to the material is not provided.
// To change material, one can create a new material and add it to the binding
// Note that the material name is used to indicate which material is remapped.
if ( !_cubeInstanced.materialBindings[ "cubeMtrl" ] )
_cubeInstanced.materialBindings[ "cubeMtrl" ] = new MaterialStandard( "cubeMtrl" );
if ( _cubeInstanced.position.x < 0 )
_cubeInstanced.materialBindings[ "cubeMtrl" ].diffuseColor.set( 1, 0, 0 );
else
_cubeInstanced.materialBindings[ "cubeMtrl" ].diffuseColor.set( 0, 1, 0 );
}
In this section, you'll explore how you can add shadows to a 3D scene by adding casters to the lights (see Figure 4).
The process of enabling shadows is easy. If the property BasicDemo.shadowMapEnabled is set, the two default lights of the BasicDemo class are enabled to display shadows.
The next step is to define casters to the lights. To accomplish this, you can use the method in Proscenium called the SceneLight.addToShadowMap method. One easy way is to add BasicDemo.scene, which will make every scene graph node caster for the light, to update light[1] in this tutorial.
Another approach is to define specific casters to each light. For example, check out how each light is controlled individually in the code below for lights[0] and lights[1] in this tutorial. This is usually a good idea, because this ensures that the ground plane will not cast shadow from light[0] in the scene.
override protected function initModels():void
{
…
if ( lights )
{
lights[0].setPosition( 10, 20, 10);
if ( lights[0].shadowMapEnabled )
lights[0].addToShadowMap( _cubeInstanced, cube, torus, sphere ); // define casters
if ( lights[1].shadowMapEnabled )
lights[1].addToShadowMap( scene ); // just set every scene graph object as caster
}
}
In this section, you'll take a look at how to bring in 3D models created in a 3D graphics program. You can use the Proscenium framework to load 3D models from external file. This example illustrates how to load skybox textures and a 3D model (see Figure 5).
The object named SceneSkyBox is the scene graph node object that is specifically provided for skybox. To learn more about the fogging option, see TestFog.
The texture image file names for the six sky box faces are defined like this:
protected static const SKYBOX_FILENAMES:Vector.<String> = new Vector.<String>(6,true);
SKYBOX_FILENAMES[ 0 ] = "../res/textures/skybox/px.png";
SKYBOX_FILENAMES[ 1 ] = "../res/textures/skybox/nx.png";
SKYBOX_FILENAMES[ 2 ] = "../res/textures/skybox/py.png";
SKYBOX_FILENAMES[ 3 ] = "../res/textures/skybox/ny.png";
SKYBOX_FILENAMES[ 4 ] = "../res/textures/skybox/pz.png";
SKYBOX_FILENAMES[ 5 ] = "../res/textures/skybox/nz.png";
Additionally, this example loads a palm tree model. To load both the sky box textures and the palm tree model, the code launches a loader for each. When the loading is complete, each loader issues an event. In this sample project, the two loading tasks are managed by following these steps:
Review the code for loading the external files in the example below:
override protected function initModels():void
{
var plane:SceneMesh = MeshUtils.createPlane(
100,100,20,20,null, "plane" );
plane.transform.appendTranslation( 0, -2, 0 );
scene.addChild( plane );
LoadTracker.loadImages( SKYBOX_FILENAMES, imageLoadComplete );
}
protected function imageLoadComplete( bitmaps:Dictionary ):void
{
var bitmapDatas:Vector.<BitmapData> = new Vector.<BitmapData>( 6, true );
var bitmap:Bitmap;
for ( var i:uint = 0; i < 6; i++ )
bitmapDatas[ i ] = bitmaps[ SKYBOX_FILENAMES[ i ] ].bitmapData;
// sky
_sky = new SceneSkyBox( bitmapDatas, false );
scene.addChild( _sky ); // skybox must be an immediate child of scene root
_sky.name = "Sky"
_treeLoader = new OBJLoader( "../res/models/PalmTrees/PalmTrees.obj" );
_treeLoader.addEventListener( Event.COMPLETE, loadComplete);
}
protected function loadComplete( event:Event ):void
{
var tree:SceneNode = new SceneNode( "PalmTrees" );
var manifest:ModelManifest = _treeLoader.model.addTo( tree );
scene.addChild( tree );
}
All of the previous samples described in this tutorial use the BasicDemo harness to facilitate the event handlers and add other controls. In this last section of the tutorial, you'll see how to create Proscenium applications without using the BasicDemo harness, by directly extending the Sprite class.
public class Tutorial_SpriteBased extends Sprite
By extending the Sprite class, you gain more control over how you can add sprites to the Stage. You also have more options when creating the Stage3D context, and configuring and implementing event handlers (see Figure 6).
Instance3D is the basic Proscenium class that maintains the 3D Molehill context. You'll use the Instance3D class whenever you want to use the Proscenium framework. In this example, the following code creates a new instance of Instance3D:
instance = new Instance3D( stage3D.context3D );
Note: In the previous examples that use the BasicDemo class, it automatically created an Instance3D object named BasicDemo.instance.
In the previous tutorials, the root was named scene, which is set up with BasicDemo.scene. However, since this example is not using the BasicDemo class, the root is simply Instance3D.scene, which is instance.scene in this tutorial.
Note: Instance3D is a class name and instance is a variable.
Once you have set up the scene graph root, the process of creating the 3D models and adding them to the scene involves following the same steps as before. This example creates a light and enables the shadow using casters. Also notice that the example includes key and mouse event handlers to work with user interaction. The function includes the code to turn on the fog effect.
In this section, you will take a look at how to make objects fall, collide, and bounce.
Physics will be enabled by creating a PelletManager object that provides methods used to create Physics-enabled SceneMesh objects. For example, ground plane can be created by call createStaticInfinitePlane, boxes can be created by createBox, and spheres can be created by createSphere:
// create a plane and add it to the scene
var plane:SceneMesh = mSim.createStaticInfinitePlane( 1000, 1000, 2, 2, material, "plane" );
plane.appendTranslation( 0, -2, 0 );
scene.addChild( plane );
// create cubes and add it to the scene
var cube0:SceneMesh = mSim.createBox( 5, 5, 5 );
cube0.appendRotation( 40, Vector3D.X_AXIS );
cube0.appendTranslation( 0, 6, 0 );
scene.addChild( cube0 );
// create a sphere and add it to the scene
var sphere:SceneMesh = mSim.createSphere( 3, 32, 16 );
sphere.setPosition( -10, 2, 0 );
scene.addChild( sphere );
Texture maps can be embedded by the following:
[Embed( source="/../res/content/foliage022.jpg" )]
protected static const BITMAP:Class;
Using this data, a texture map can be created and used for diffuse map as shown in the following code:
var textureMap:TextureMap = new TextureMap( new BITMAP().bitmapData );
material.diffuseMap = textureMap;
In this section, you will take a look at code that loads a COLLADA animation file (see Figure 8).
To load a COLLADA file, refer to this code:
public var loader:ColladaLoader;
override protected function initModels():void {
loader = new ColladaLoader( "../res/content/AnimatedBones.dae" );
loader.addEventListener( Event.COMPLETE, onLoad );
}
When the loading completes, add the model to the scene and bind the animation to the scene:
public var animations:Vector.<AnimationController>, initialized:Boolean;
public function onLoad( event:Event ):void {
var manifest:ModelManifest = loader.model.addTo( scene );
animations = loader.model.animations;
for each ( var anim:AnimationController in animations ) {
anim.bind( scene );
}
initialized = true;
}
Animation is done by advancing time:
override protected function onAnimate( t:Number, dt:Number ):void {
if ( !initialized ) return;
for each ( var anim:AnimationController in animations )
{
anim.time = ( t % anim.length ) + anim.start;
}
}
We hope this tutorial has provided you with an overview of working with the Proscenium framework to create 3D elements for your Flash projects. To learn more about creating 3D animations and games in Flash Professional, check out the following online resources: