Erain 3D
-->

Author: Max Pellizzaro
Date: November 22nd 2007
version: 3.0

Modelling a forest using Sprite2D

Objective of the tutorial

This tutorial can be seen as a continuation of the previous tutorial 042 (so I recommend doing or reading that tutorial first). While in tutorial 0042 we have modeled a static forest with no sky and no ground, you’ll now learn how to add these missing parts using common computer games technique. After this tutorial you should have enough knowledge in order to model any kind of “running”, “flying” or “driving” situation.
So let’s start to work.

How to

Set up

The preparation of this tutorial will take a little more effort that the other, but you will find out why soon.
As I might have said in other situation, not everything you see inside a Scene3D must me model as an object 3D (that is as a shape3D). We have already seen that Sprite2D is a nicer way to simulate a 3D object; but there are situations where a good-old static picture will do the job. If you are modeling any ground activity, the sky can me model as a static picture and the user should not notice that. For this reason we are going to place a static picture on our stage representing the sky, as shown in he figure below.

As you might we have paced also some grass on the bottom part of the Stage. To understand the reason of this you must know the notion of cutting plane. In a 3D space everything outside the cutting planes should be clipped by the rendering engine. Sandy has two cutting plane: a far plane and a near plane. You could play with the value of these two planes to understand what do they mean, but I recommend not using other values other than the default ones unless you really know what you are doing. Now, if you move the camera3D too close to a plane3D you might incur in the clipping of part of the plane, whit the result in not a nice rendering. Instead of playing with cutting planes and other camera3D features, just adding this static grass on the bottom of the stage will do the job.

Now let's move to the normal set pf of the AS class, his name must be Example015.as In the archive below you will find also the assets used for this tutorials: the single tree and some skin to model the grass.

example015_b.rar

The AS Code

In this section we report the AS code as a reference, and it will be explained in the next paragraph.

package
{
   import flash.display.*;
   import flash.net.URLRequest;
 
   import flash.events.*;
   import flash.ui.*;
   import sandy.core.Scene3D;
   import sandy.core.data.*;
   import sandy.core.scenegraph.*;
   import sandy.materials.*;
   import sandy.materials.attributes.*;
   import sandy.primitive.*;
   import sandy.util.*;
   import sandy.events.*;

   public class Example015 extends Sprite 
   {
      private var scene:Scene3D;
	  private var camera:Camera3D;
	  private var queue:LoaderQueue;
	  private var g:Group;
	  private var numTree:Number = 50;
	  
	 public function Example015():void
     {
	   queue = new LoaderQueue();
	   queue.add( "grass", new URLRequest("asset/grass02.jpg") );
       queue.add( "tree", new URLRequest("asset/tree.gif") );
	   queue.addEventListener(SandyEvent.QUEUE_COMPLETE, loadComplete );
       queue.start();
	 }
	 
	  public function loadComplete(event:QueueEvent ):void
      {  
		 // We create the camera
		 camera = new Camera3D( 500, 300 );
		 camera.y = 5;
		 camera.z = -100;
		 camera.near=20;
		 
		 // We create the "group" that is the tree of all the visible objects
         var root:Group = createScene();
		 
		 // We create a Scene and we add the camera and the objects tree 
	     scene = new Scene3D( "scene", this, camera, root );
		 scene.rectClipping = false;
		 
		 // Listen to the heart beat and render the scene
         addEventListener( Event.ENTER_FRAME, enterFrameHandler );
		 stage.addEventListener(KeyboardEvent.KEY_DOWN, keyPressedHandler);
		 stage.addEventListener(MouseEvent.MOUSE_MOVE, mouseMovedHandler);
		 
      }
	 
      // Create the scene graph based on the root Group of the scene
      private function createScene():Group
      {
         // Create the root Group
         g = new Group();
		 
		 // let's create some planes that we will use to pipe togther...
		 var material:BitmapMaterial = new BitmapMaterial( queue.data["grass"].bitmapData );
		 material.lightingEnable = true;
	     var app:Appearance = new Appearance( material );
		 
		 var one:Shape3D = new Plane3D('one', 400, 700, 15, 15, 
						Plane3D.ZX_ALIGNED,'tri');
		 one.appearance = app;
         one.y = -20;
		 
		 var two:Shape3D = new Plane3D('two', 400, 700, 15, 15, 
						Plane3D.ZX_ALIGNED,'tri');
		 two.appearance = app;
         two.y = -20;
		 two.z = 400;
		 
		 var three:Shape3D = new Plane3D('three', 400, 700, 15, 15, 
						Plane3D.ZX_ALIGNED,'tri');
		 three.appearance = app;
         three.y = -20;
		 three.z = 800;
		 
		 g.addChild(one);
		 g.addChild(two);
		 g.addChild(three);
		 
		 // let's create the Sprite 2D object
		 for(var i:Number=0; i<numTree; i++)
		 {
		  var bit:Bitmap = new Bitmap(queue.data["tree"].bitmapData);
		  var s:Sprite2D = new Sprite2D("tree"+i,bit,1);
		  s.x = Math.random()*600 - 300;
		  s.z = Math.random()*600;
		  s.y = 0;
		  g.addChild(s);
		 }
		  return g;
      }

      // The Event.ENTER_FRAME event handler tells the Scene3D to render
      private function enterFrameHandler( event : Event ) : void
      {
		 //replace the tree that disapper
		 for (var j:Number=0; j<numTree; j++)
	     {
		  if(g.children[j].z<camera.z && g.children[j].name != "one" && g.children[j].name != "two" && g.children[j].name != "three")
		  {
			g.children[j].z = g.children[j].z+700+Math.random()*100 ;
			g.children[j].x = Math.random()*600-300;
		  }
		  else if(g.children[j].z<camera.z-120 && (g.children[j].name == "one" || g.children[j].name == "two" || g.children[j].name == "three"))
		  {
			 g.children[j].z+=1200;
		  }
		 }
		 scene.render();
      }
	  
	  // This function handles the move foreward or backward simultaion
	  private function keyPressedHandler(event:KeyboardEvent):void {
            switch(event.keyCode) {
				case Keyboard.UP:
				    camera.moveForward(5);
					break;
				case Keyboard.DOWN:
				    camera.moveForward(-5);
				    break;
			}
        }
		
	  // This function handles the direction of the similation movement	
	  private function mouseMovedHandler(event:MouseEvent):void {
           camera.pan=(event.stageX-300/2)/10; 
        }	
   }
}

Examining the code

queue = new LoaderQueue();
queue.add( "grass", new URLRequest("asset/grass02.jpg") );
queue.add( "tree", new URLRequest("asset/tree.gif") );
queue.addEventListener(SandyEvent.QUEUE_COMPLETE, loadComplete );
queue.start();

Each piece of the ground is just a simple plane 3D with a bitmap material used as a skin.

var material:BitmapMaterial = new BitmapMaterial( queue.data["grass"].bitmapData );
material.lightingEnable = true;
var app:Appearance = new Appearance( material );
 
var one:Shape3D = new Plane3D('one', 400, 700, 15, 15, 
			Plane3D.ZX_ALIGNED,'tri');
one.appearance = app;
one.y = -20;
for(var i:Number=0; i<numTree; i++)
{
  var bit:Bitmap = new Bitmap(queue.data["tree"].bitmapData);
  var s:Sprite2D = new Sprite2D("tree"+i,bit,1);
  s.x = Math.random()*600 - 300;
  s.z = Math.random()*600;
  s.y = 0;
  g.addChild(s);
}

If we don’t add nothing else this tutorial will just look like the previous one, so let’s see what make it so different.

if(g.children[j].z<camera.z && g.children[j].name != "one" && g.children[j].name != "two" && g.children[j].name != "three")
 {
     g.children[j].z = g.children[j].z+700+Math.random()*100 ;
     g.children[j].x = Math.random()*600-300;
  }

This part of code check of the tree will be passed by the Camera3D. If so the three will be just moved farther away and will be represented once again to the camera. This does allow us to work only with a fixed number threes but in reality we are modeling an infinitive number of threes, by moving and changing always the new position of them

else if(g.children[j].z<camera.z-120 && (g.children[j].name == "one" || g.children[j].name == "two" || g.children[j].name == "three"))
{
   g.children[j].z+=1200;
 }

This part of code just pipes the 3 pieces of ground together. We will put the two piece of code in for(..) loop, to test all the objects rendered by the engine:

for (var j:Number=0; j<numTree; j++)
{
  ….
  ….
}
scene.render();

This is it! Let’s see our result now.

The output

For this Flash you have the UP, DOWN key to move forward and backward, and the mouse to choose the direction (always double click the flash to get the focus and beeing able to use the UP and DOWN key).