Erain 3D
-->

Author: Max Pellizzaro
Date: February 18th 2008
version: 3.0

3DS MAX importing Artur V's camero : Part I

Objective of the tutorial

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.

How to

Set up the 3DMAX model

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.

assets.rar

Set up

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.

example0071.rar

The AS Code

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();
    }

    private function loadComplete(event:QueueEvent) {
      camera = new Camera3D( 600, 300 );
      camera.y = 30;
      camera.z = -200;
      camera.near = 10;
      
      var root:Group = createScene();
      scene = new Scene3D( "scene", this, camera, root );
      
      addEventListener( Event.ENTER_FRAME, enterFrameHandler );
      stage.addEventListener(KeyboardEvent.KEY_DOWN, keyPressedHandler);
    }

    private function createScene():Group {
      var g:Group = new Group();
      
      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;
      
      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;
      }
    }

    private function enterFrameHandler( event : Event ) : void {
      scene.render();
    }
  }
}

Examining the code

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!

The output

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!