Author: Max Pellizzaro
Date: February 18th 2008
Update: July 12th 2009
version: 3.1.1
In this tutorial I will use a very nice 3DMAX model provided by Arthur V and will make it “alive” using Sandy 3D library. In some previous tutorial we have already seen how to import a simple model from a 3DMAX exported file, but with this tutorial you will learn not only to import more than a single element, but also how to skin them with 3DMAX texture and how to move them.
Artur V has provided a very nice video tutorial that shows you how to build the model with 3DMAX. You can find here below his great contribution.
As a result of this 3DMAX model, you will export the model and the skin in three different formats: 3DS, COLLADA and ASE. In the archive below you will find all these formats.
The Document class must be changed to Example0071.as The name of the class in the .as file and the name of the constructor now is: Example0071.
The new updated version can be found here:
example0071_v3.1.1.zip
package
{
import flash.display.*;
import flash.events.*;
import flash.ui.*;
import flash.net.URLRequest;
import sandy.core.Scene3D;
import sandy.core.data.*;
import sandy.core.scenegraph.*;
import sandy.materials.*;
import sandy.materials.attributes.*;
import sandy.primitive.*;
import sandy.parser.*;
import sandy.util.*;
import sandy.events.*;
public class Example0071 extends Sprite
{
private var scene:Scene3D;
private var camera:Camera3D;
private var tg:TransformGroup;
private var car:Shape3D;
private var wheelLF:Shape3D;
private var wheelRF:Shape3D;
private var wheelLR:Shape3D;
private var wheelRR:Shape3D;
private var queue:LoaderQueue;
private var parserStack:ParserStack;
public function Example0071()
{
var parser:IParser = Parser.create("assets/models/ASE/car.ASE",Parser.ASE );
var parserLF:IParser = Parser.create("assets/models/ASE/wheel_Front_L.ASE",Parser.ASE );
var parserRF:IParser = Parser.create("assets/models/ASE/wheel_Front_R.ASE",Parser.ASE );
var parserLR:IParser = Parser.create("assets/models/ASE/wheel_Rear_L.ASE",Parser.ASE );
var parserRR:IParser = Parser.create("assets/models/ASE/wheel_Rear_R.ASE",Parser.ASE );
parserStack = new ParserStack();
parserStack.add("carParser",parser);
parserStack.add("wheelLFParser",parserLF);
parserStack.add("wheelRFParser",parserRF);
parserStack.add("wheelLRParser",parserLR);
parserStack.add("wheelRRParser",parserRR);
parserStack.addEventListener(ParserStack.COMPLETE, parserComplete );
parserStack.start();
}
private function parserComplete(pEvt:Event ):void
{
car = parserStack.getGroupByName("carParser").children[0] as Shape3D;
wheelLF = parserStack.getGroupByName("wheelLFParser").children[0] as Shape3D;
wheelRF = parserStack.getGroupByName("wheelRFParser").children[0] as Shape3D;
wheelLR = parserStack.getGroupByName("wheelLRParser").children[0] as Shape3D;
wheelRR = parserStack.getGroupByName("wheelRRParser").children[0] as Shape3D;
loadSkins();
}
private function loadSkins(){
queue = new LoaderQueue();
queue.add( "carSkin", new URLRequest("assets/textures/car.jpg") );
queue.add( "wheels", new URLRequest("assets/textures/wheel.jpg") );
queue.addEventListener(SandyEvent.QUEUE_COMPLETE, loadComplete );
queue.start();
}
// Create the scene graph based on the root Group of the scene
private function loadComplete(event:QueueEvent)
{
camera = new Camera3D( 600, 300 );
camera.y = 30;
camera.z = -200;
camera.near = 10;
var root:Group = createScene();
var canvas:Sprite = new Sprite();
this.addChild(canvas);
scene = new Scene3D( "scene", canvas, camera, root );
// Listen to the heart beat and render the scene
addEventListener( Event.ENTER_FRAME, enterFrameHandler );
stage.addEventListener(KeyboardEvent.KEY_DOWN, keyPressedHandler);
}
private function createScene():Group
{
// Create the root Group
var g:Group = new Group();
// We need to create a transformGroup
tg = new TransformGroup('myGroup');
var material:BitmapMaterial = new BitmapMaterial( queue.data["carSkin"].bitmapData );
var app:Appearance = new Appearance( material );
car.appearance = app;
var materialW:BitmapMaterial = new BitmapMaterial( queue.data["wheels"].bitmapData );
var appW:Appearance = new Appearance( materialW );
wheelLF.appearance = appW;
wheelRF.appearance = appW;
wheelLR.appearance = appW;
wheelRR.appearance = appW;
// use single container = False
car.useSingleContainer = false;
wheelLF.useSingleContainer = false;
wheelRF.useSingleContainer = false;
wheelLR.useSingleContainer = false;
wheelRR.useSingleContainer = false;
tg.addChild( wheelLF );
tg.addChild( wheelRF );
tg.addChild( wheelLR );
tg.addChild( wheelRR );
tg.addChild( car );
tg.rotateY = -130;
g.addChild( tg );
return g;
}
private function keyPressedHandler(event:flash.events.KeyboardEvent):void
{
switch(event.keyCode) {
case Keyboard.UP:
tg.roll +=2;
break;
case Keyboard.DOWN:
tg.roll -=2;
break;
case Keyboard.LEFT:
tg.pan -=2;
break;
case Keyboard.RIGHT:
tg.pan +=1;
break;
case Keyboard.PAGE_DOWN:
tg.z -=5;
break;
case Keyboard.PAGE_UP:
tg.z +=5;
break;
}
if(tg.z < -100){
tg.visible = false;
} else if (tg.z > -100) {
tg.visible = true;
}
}
// The Event.ENTER_FRAME event handler tells the world to render
private function enterFrameHandler( event : Event ) : void
{
scene.render();
}
}
}
The first important class we will learn to use in this tutorial is called ParserStack. This class can be seen as the “sister” of another class we have used in other tutorials: LoaderQueue. The LoaderQueue class allows to load a number of visual assets (images, swf files) while the ParserStack allows us to load different 3D Models based on the parsing of 3D files. First of all we need to define IParser elements, one for each model we want to load. In our example we have to load 4 wheels and one chassis.
var parser:IParser = Parser.create("assets/models/ASE/car.ASE",Parser.ASE ); var parserLF:IParser = Parser.create("assets/models/ASE/wheel_Front_L.ASE",Parser.ASE ); var parserRF:IParser = Parser.create("assets/models/ASE/wheel_Front_R.ASE",Parser.ASE ); var parserLR:IParser = Parser.create("assets/models/ASE/wheel_Rear_L.ASE",Parser.ASE ); var parserRR:IParser = Parser.create("assets/models/ASE/wheel_Rear_R.ASE",Parser.ASE );
Once we have created these IParser, we will add them to our PareserStack class:
parserStack.add("carParser",parser); parserStack.add("wheelLFParser",parserLF); parserStack.add("wheelRFParser",parserRF); parserStack.add("wheelLRParser",parserLR); parserStack.add("wheelRRParser",parserRR);
We now can tell the parser to load all the models and to call the callback method when loading is completed.
parserStack.addEventListener(ParserStack.COMPLETE, parserComplete ); parserStack.start();
Once inside the parserComplete(..) method, we need to get the Shape3D objects that we have just loaed. To do so we will use a public method provided by the ParserStack class: getGroupByName(..):
car = parserStack.getGroupByName("carParser").children[0] as Shape3D; wheelLF = parserStack.getGroupByName("wheelLFParser").children[0] as Shape3D; wheelRF = parserStack.getGroupByName("wheelRFParser").children[0] as Shape3D; wheelLR = parserStack.getGroupByName("wheelLRParser").children[0] as Shape3D; wheelRR = parserStack.getGroupByName("wheelRRParser").children[0] as Shape3D;
Now that all the objects are created we then need to load we need to load the skins for our camero. To do so we will use the LoaderQueue class that will load two different skins: one for the wheels and one for the chassis.
queue = new LoaderQueue(); queue.add( "carSkin", new URLRequest("assets/textures/car.jpg") ); queue.add( "wheels", new URLRequest("assets/textures/wheel.jpg") ); queue.addEventListener(SandyEvent.QUEUE_COMPLETE, loadComplete ); queue.start();
Now we have all the elements to build the camera in our Sandy Scene3D. First we will define two different type of materials: one for the chassis and on for the wheels:
var material:BitmapMaterial = new BitmapMaterial( queue.data["carSkin"].bitmapData ); … var materialW:BitmapMaterial = new BitmapMaterial( queue.data["wheels"].bitmapData );
These two material will be used as input for the appearance of the chassis and of the wheels of our camero. All the hard stuff is done! You just need to define a Transformation Group and add each element created to the transformation group:
tg = new TransformGroup('myGroup'); … … tg.addChild( wheelLF ); tg.addChild( wheelRF ); tg.addChild( wheelLR ); tg.addChild( wheelRR ); tg.addChild( car );
And now let’s see the result!
Use the arrow keys to rotate the car around and the PAGE_UP and the PAGE_DOWN to zoom in and zoom out the camera. Have fun!