Home Labs Galleries PetitOn

Using Sandy 3.0 Flash Library

Part 3. The Sandy Primitives

The 3D objects of a Sandy world all inherit from the Shape3D class. In its turn the Shape3D inherits all properties and behaviors as a citizen in the tree graph from the Node class, and its mobility from the ATransformable class.
To be visible on screen, a Shape3D has Geometry3D and Appearance properties, the first containing its geometric description, the second its material properties.

Node    As a member in the tree graph
|  
ATransformable As a mobile object
|  
Shape3D As a visible 3D object in the world
|  
Cylinder As a specific primitive

The 3D objects can be created programmatically or by reading in description files in different formats, such as COLLADA, 3ds or VRML, using specialized parsers. In this tutorial we will study the ready made primitives we can use to programmatically populate our worlds.

The anatomy of a primitive

All primitives extends the Shape3D class and implements the Primitive3D interface.
The interface just states, that every primitive should have a generate() method that  takes an argument list and returns a Geometry3D object. The constructor will take an argument list, specific to the primitive and starting with a name for the object. It will create the geometry property of the object, by calling the generate() method.

[ToDo: Link to commented list of props and methods for the different primitives]

Enough talk now! Let's code.

Programming the primitives

You can download source code for this tutorial here.

To make life simple, we'll use the same structure for all our examples. You have already seen it in the Jump Start section. By convention we use the same name for the fla file as for the document class. The height and width for the stage area are the same as for the camera viewport.

package // The default package is fine for the document class
{
  // Import section 
  // Import all classes or packages you need

  public class SomeDemo extends Sprite	// Extend Sprite or MovieClip
  {
     // Variable declarations
     private var world:World3D;
     private var box:Shape3D;
	
     public function SomeDemo()	// The application constructor
     {
	world = World3D.getInstance();
	world.container = this; 		// The parent container
	world.root = createScene(); 	// Create the object tree
	world.camera = new Camera3D( 200, 200 ); // The camera
	world.camera.z = -200;
	world.root.addChild( world.camera );
		
	// Add an event listener / handler for all dynamic actions
	addEventListener( Event.ENTER_FRAME, enterFrameHandler );	
     }

     // Create the root Group and the object tree 
     private function createScene():Group
     {
	var root:Group = new Group();
	// Create and add all groups and objects with geometries and Appearances
	return root;
     }
     // Do all deeds here, that has to be done per frame
     private function enterFrameHandler( event : Event ) : void
     {
	// Make all dynamic changes here
	world.render(); // Tell the world to render all its children
     }
  }
}

The Box

We'll start with the Box class, that we already used in the Jump start session. Here is the signature of its constructor:

public function Box ( 	p_sName:String    = null, 
			p_nWidth:Number   = 6, 
			p_nHeight:Number  = 6, 
			p_nDepth:Number   = 6, 
			p_sMode:String    = "tri", 
			p_nQuality:Number = 1 )

As we can see, all parameters have default values, which means that all arguments are optional. However AS3 doesn't offer any method to skip over arguments in the middle.

This construct new Box ( "my box", , ,'quad') is not allowed in ActionScript, so if you want to pass an argument value, you have to pass all values before that in the argument list.

The default Box object, is a cube with no name ( null ), a side length of 6, a creation mode 'tri' ( or PrimitiveMode.TRI ) and a quality setting of 1, which means ... triangular polygons on each side of the cube.

Setting different values on the sides, produces other cuboids. For the creation mode, we can choose between PrimitiveMode.TRI and PrimitiveMode.QUAD, building the box surfaces from triangular or rectangular polygons. The TRI mode is the preferred mode if you are working with textured faces, as it gives a better perspective distortion. The quality setting will work a bit differently for different primitives, but the principle is that a higher value gives us more polygons and better texture distortions.

We start as in the Jump Start session, and create a document class, let's call it BoxDemo. We will keep the interactivity, so we can let the user manipulate our box.

Here is the constructor:

	public function BoxDemo()
	{
		world = World3D.getInstance();
		world.container = this;
		world.root = createScene();
		world.camera = new Camera3D( 200, 200 );
		world.camera.z = -50;
		world.root.addChild( world.camera );
		Key.initialize(stage);
		addEventListener( Event.ENTER_FRAME, enterFrameHandler );
	}

The Key.initialize() call initializes the Key class - don't forget to add the class to you project. It enables simple manipulation coding.

As you can see, we have set the property useBright of the world to true and set its ambient light source to the value 0.1. This is to get a little nicer appearance of the objects. I will talk more on the Sandy light in a section on materials. For now you can refer to the API docs.

Now, let's create the scene graph, rooted in the world.root group.

private function createScene():Group
{
	var root:Group = new Group();
	box = new Box( "box 1");
	var materialAttr:MaterialAttributes = new MaterialAttributes( 
				new LineAttributes( 0.5, 0x2111BB, 0.4 ),
				new LightAttributes( true, 0.1)
				);

	var material:Material = new ColorMaterial( 0xFFCC33, 1, materialAttr );
	material.lightingEnable = true;
	var app:Appearance = new Appearance( material );

	box.appearance = app;
	box.rotateZ = 45;
	box.rotateX = 30;
	root.addChild( box );
	return root;
}

In this case we get the default Box object which is 6 by 6 by 6 units created in 'tri' mode and with a quality setting of 1. This gives us the Geometry3D property of the box.

For the Appearance of the box we use a ColorMaterial with (r,g,b) = (FF,CC,33) and an alpha value of 1 ( = opaque ).  It also has MaterialAttribute with a LineAttribute and a LightAttribute. The line attribute has a line thickness of 0.5, a color with (r,g,b) =(21,11,BB) and an alpha value of 0.4 or 40%. The LightAttribute determines how the material will handle light. The first Boolean argument determines if we have strong reflection or not, the second argument is the strength of the ambient light. 

To see the effect of light on the cube, we have to enable light for the material, by setting its lightingEnable to true.

Note that the appearance could have a front and a back material - here we only have a front material, as we are not interested in the inside of the box.

Finally, let's have a look at how the manipulation is set up!
As in the Jump Start example, we use Senocular's Key class, so we can poll the keyboard to see if a certain key is pressed, and take action if it is. Here is the event handler for the ENTER_FRAME event, that takes care of all our dynamic handling.

	private function enterFrameHandler( event : Event ) : void
	{
		if (Key.isDown(Keyboard.LEFT)) {
			box.rotateY += 2;
		}
		if (Key.isDown(Keyboard.RIGHT)) {
 			box.rotateY -= 2;
		}
		if (Key.isDown(Keyboard.UP)) {
 			box.rotateX += 2;
		}
		if (Key.isDown(Keyboard.DOWN)) {
 			box.rotateX -= 2;
		}
		if (Key.isDown(Keyboard.PAGE_UP)) {
 			world.camera.moveForward(1);
		}
		if (Key.isDown(Keyboard.PAGE_DOWN)) {
			world.camera.moveForward(-1)
		}
		world.render();
	}

Our box, as all Shape3D objects, inherit mobility from the ATransformable class, which means you can easily move objects around by changing their x, y and z values, or rotate them, using rotateX, rotateY and rotateZ values. There are lots of other ways to effect the position and attitudes of objects, so we'll have a separate session on that.

Here we also move the camera back and forth, using the PgDown, PgUp keys.

Observe that when you move the camera forward, the cube seem to disappear, and when you move it back, it shows up again. This is because objects are clipped, when they come too close to the camera. The camera has a default near plane, which defines how close an object can get, and still be visible. We'll discuss the camera properties later- suffice to say this has to do with the camera frustum. In this special case the object is rather small, and the camera has to be close to start with.

Now lets check out two more Box objects, to get a grasp on how it can be altered. I'm going to use the same application, and just change the arguments to the Box() constructor. Here are some variants

	box = new Box( 'box 2', 100, 50, 70, PrimitiveMode.QUAD, 2); // box 2
	box = new Box( 'box 3', 50, 50, 50, PrimitiveMode.TRI, 2); // box 3
box 2 box 3

Next up is the plane primitive.