Table of Contents

Interactive Cast3D Application Sample

This example is to show a full complete cycle on how to get a 3D model ( character) motion controlled by a user keyboard commands.

Starting with a single 3D model file created in Blender 2.45 modeling tool ( it could be any other modeling tool that supports COLLADA file format export). We will go with step by step instructions from import/export process to creation of 3D model instance in Cast3D and further.

First, make sure that you have Cast3D and import utilities version 0.96 or later.

Main steps for this sample

  1. import of .blend file into Cast3D .Xc3 xml file.
  2. working with motion groups
  3. setting up rendering scene, loading and displaying 3D model
  4. setting up navigation

1. Importing 3D model

    C:\work> Dae2X3 -flip -xm walk model.blend mymodel.xc3

As we know that Blender uses Right-Handled coordinate system but flash rendering engines like papervission3D and Sandy3D use left-Handed, '-flip' option is essential. ( otherwise 3d scene most likely will be empty, due to camera inverse settings)

Before exporting file from Blender you'll need to install updated (fixed) COLLADA export scripts.

2. Working with motion groups

this section is optional

Another option '-xm walk' is very important if you want to attach some other motions like lets say 'jump' from another file. What '-xm' option is going to do is to extract motion <MotionGoup> into a separate file(s).

If there are a number of <MotionGroup>s, each will be saved in file with name <name pattern> + ordinal number + '.xci' (if no extension specified).

The content of <MotionGroup> will be replaced with <include source=“filename”> statement in main output file. For example, in this case following files will be created

                 'walk0.xci', 'walk1.xci', walk2.xci' ...

To add external motion you need to extract a different type of motion by creating a new motion in original 3D modeling tool, in this case Blender and export in the same way. Lets say we exported it into 'jump0.xci' file.

Now you will need to open first imported model file 'mymodel.xc3' and add following lines within <tracks> element

<include id=“lowerBack_motion834” source=“jump0.xci”></include>

to attach motion find destination node element

<node id=“lowerBack” parent=”” type=“lowerBack”>

and add following element

<bind bind_id=“lowerBack_motion834” context=“track” path=“tracks”></bind>

One more tip. Now since there are two motion groups attached to 3D model, you do not need to both motion to work at a time. So need to make sure that only one default is enabled by checking and setting attribute og MotionGroup 'enable' = “true/false”.

<motiongroup id=“lowerBack_motion834” start=“0” end=“23” enabled=“false” timeline=“REPEAT” offset=“0”>

2. Setting up 3D scene, loading and displaying 3D model

Setting up scene consists of following steps:

/**
 * initial setup for Papervision3D.
*/		
public function setupPpv3D(): void
{		
	this.viewport = new Viewport3D(300, 400, true, false,false,false);
	 addChild( viewport );
 
	this.scene = new Scene3D();
	this.camera = new Camera3D();			
	this.renderer = new BasicRenderEngine();			
}

* initial setup of Cast3D animation framework.

 /**
* initial setup for Cast3D.
*/		
public function setupCast3D(): void
{
       // init loading status		
        this.loaded = false;
 
       // new Cast3D instance with created earlier 3D scene and camera
	this.animator = new Cast3d(this.scene, this.camera);
 
       // set animation type
	this.animator.animationType = Cast3d.ANIMATION_TYPE_BYFRAME;
 
       // set desired animation frame rate
	this.animator.fps = 22;
 
       // run it once
	this.animator.autoRewind = false;						
}
 /**
*  Function performs 3D data  load from a X3c file.
*/		
private function loadData(): void
{
	/** stealth - simple model, no animation */
	this.loader = new Xc3Loader("../models/walk/fig.xc3");
	this.loader.resourcePath = "../models/walk";
 
      // optimize animation 
	this.loader.preCalcMotion = true;
 
     // register notification of load completion 
	this.loader.addEventListener(LoadEvent.LOAD_COMPLETE, this.cast3dLoadComplete);
     // register notification of load error
	this.loader.addEventListener(LoadEvent.LOAD_ERROR, this.cast3dLoadError);	
 
    // proceed with load 
	this.loader.load(this.animator.source);			
}

If you noticed the line

        this.loader.resourcePath = "../models/walk";

indicated the place where you want to put texture images and other media materials like sound, movie and video and 'include' file ( for example motion group).

3. Setting up navigation

* on successful load completion, call 'setupNavigator' to set up navigation handlers

 
/**
 * Handles the load complete event
 */		
private function cast3dLoadComplete(event: LoadEvent): void
{
	trace("cast3dLoadComplete "); 
 
	setupNavigator();
 
	this.manipulator = new TrackBall(this.animator,this.stage,
							 this.viewport.viewportWidth, this.viewport.viewportHeight,
							 Manipulator.Y_UP,
									 Manipulator.X_S,Manipulator.Z_S,Manipulator.NY_S
							 );
 
	cp.manipulator = this.manipulator;
 
         this.loaded = true;
         removeTimer();       	        	            
         if (loader.loaderror.length)
            {
		statusText.textColor = 0xff0000;       	        	            
		statusText.text = loader.loaderror;            
            }
            else
            {       	        	            
   		statusText.visible = false;
            }
 
  	      this.animator.play();
}

* see comments for details

/**
 * Sets up Navigation Controller for Character node
 */			    
private function setupNavigator():void
{
      	// First lets find the root node of a character
    	// we know by looking at source file,  its Id is 'Cube'
	var nodename:String = "Cube";
	var node:Node3d = this.animator.source.find(nodename) as Node3d;
	if (!node)
	 {
	   statusText.visible = true;
	    statusText.text = "Failed to set Navigation control for node: " + nodename;
	            return;
	  }
 
// create navigation conrloller fo this node.
var nc:NavigationController = new NavigationController(node,"navigator");
_navigations.push(nc); 
 
// We know that "Cube" node actually represents Skinned geometry, which means
// the motion is controlled by another skeleton node(s), in this case "lowerBack" node is
// root skeleton node( see source file)
 nodename = "lowerBack";
node = this.animator.source.find(nodename) as Node3d;
 if (!node)
{
   statusText.visible = true;
   statusText.text = "Failed to set Navigation control for node: " + nodename;
  return;
}
 
var tarck_id:String;
var motionAlias:String;
 
// now we add a 'walking' motion which is represented by MotionGroup class instance with id == "lowerBack_motion"
// again, we know that by looking at source file.
// Notice that 'motion' is produced by different node that we created nvigation controlled, which in that case "cube"
 tarck_id = "lowerBack_motion";
motionAlias = "walk";	        
if (!nc.addMotion(node, tarck_id, motionAlias))
{
	statusText.visible = true;
	 statusText.text = "Failed to add Motion " + motionAlias + " for track: " + tarck_id;
	return;
}
 
// another motion
motionAlias = "jump";
tarck_id = "lowerBack_motionjump";
if (!nc.addMotion(node, tarck_id, motionAlias))
{
	statusText.visible = true;
	statusText.text = "Failed to add Motion " + motionAlias + " for track: " + tarck_id;
	return;
}
nc.position.x -= 5.0; 
nc.rotation.x = 0; nc.rotation.y = 0; nc.rotation.z = 1; nc.rotation.w = 30 * Math.PI/180.0 ; 
 
this.stage.addEventListener(KeyboardEvent.KEY_DOWN, this.keyDownHandler);
}
 
/**
 *  Navigation keyboard handler
 */			    
public function keyDownHandler( event :KeyboardEvent ):void
{
var nc:NavigationController = _navigations[_current_nav];
if (!nc) return;
 
         trace(event.target + "(" + event.currentTarget + "): " + event.keyCode + "/" + event.charCode);
		switch( event.keyCode )
		{
		case 9: // TAB	
					_current_nav++;		    
	        if (_current_nav >= _navigations.length) _current_nav = 0;
			break;	
 
		case 37: // left	
 
		// start walking motion ant rotate  model over 1/4 of motion cycle ( which is one step)
		// in local coordinates by 30 degree rotation about Z
	        nc.run("walk",0.25, null, { x:0, y:0, z:1, w: 30.0*Math.PI/180.0 });
			break;	
 
		case 38: // up
 
		// start walking motion by moving model over 1/4 of motion cycle ( which is one step)
		// and propogation node forward in local coordinates by Y = -0.33
	        nc.run("walk",.25,{x:0, y:-.33, z:0});
			break;	
 
		case 39: // right
 
		// start walking motion ant rotate  model over 1/4 of motion cycle ( which is one step)
		// in local coordinates by 30 degree rotation about -Z
	        nc.run("walk",.25, null, {x:0, y:0, z:-1, w:30.0*Math.PI/180.0 });
				break;	
 
			case 40: // down
 
		// start walking motion by moving model over 1/4 of motion cycle ( which is one step)
		 // and propogation node backwards in local coordinates by Y = 0.33
		// also we reverse timing (last argument) for that motion so characted walks backwards.
	        nc.run("walk",.25,{x:0, y:.33, z:0}, null, true);
					break;	
 
		case 32: // space
 
	        nc.run("jump",1.0);
			break;	
	}
}

results

So, if done it right you should see something like these:


Complete source code of this sample.