Home Labs Galleries PetitOn

Using Sandy 3D Flash Library, Part 2 Primitives, continued.

More primitives

Welcome back! We have seen how to create a Sandy World, by getting an instance of World3D, a ClipScreen to draw on and a Camera3D, to represent the viewer of the world. Once that was done, we created a rootGroup to hold all other groups ( or nodes ).

To get a rotated cube, we first created a Box and a Transform3D on which we called the rot() function to get the rotation. We then added the Transform3D and the Box to a TransformGroup. Finally the transform group was added to the rootGroup of the world, and it was told to render.

We will repeat this for some other primitives, to see what they look like. Now that we have a good machinery for setting up the world and presenting it, it's all much simpler. We will keep what we have, and just change the code where we create the primitive and the name of the variable holding it. As all primitives in Sandy are sub classes of Object3D, and AS 2.0 supports polymorphism, we can use the same name and type for the reference variable if we wish: body:Object3D = new Box( 40, 40, 40, 'quad' ); I use different names for clarity here.

The Line3D

The simplest primitive is Line3D, which is used to draw a line in our 3D world. However simple it may seem, Line3D is a full blown Class in the Sandy library, and have all the characteristics of an Object3D, the super class of all geometric objects. In it's simplest form it draws a line between two points, expressed as sandy.core.data.Vector's. It can easily be expanded to draw straight lines between many points, by adding more points. To show off a bit, and to get a feeling for the 3D nature, we draw lines between four points.

Here is the new createScene() function.

function createScene( bg:Group ):Void {
	// Start by creating a Line3D primitive. 
	var line:Object3D = new Line3D( new Vector(-50,-50,0), new Vector(0,50,0), new Vector(50,50,0), new Vector(50,50,50) );
	// In this case we must use a SimpleLineSkin
	var skin:Skin = new SimpleLineSkin( 1, 0xff0000, 100 );
	// Apply the skin to the line.
	line.setSkin( skin );
	// To get a better view of the line, let's give it a transformation
	// First we create a TransformationGroup
	var tg:TransformGroup = new TransformGroup();
	// We create Transform3D to carry out a transformation in 3 dimensions
	var rotation:Transform3D = new Transform3D();
	// We have to tell the Transformation what to do.
	// We want it to rotate our line 
	rotation.rot(20,30,0);
	// The transform is set in the transform group which will hold our line
	tg.setTransform( rotation );
	// Our line is added to the transform group
	tg.addChild( line );
	// And finally the transformgroup is added as a child to the rootGroup
	bg.addChild( tg );
}

The Line3D can only use the default skin or the SimpleLineSkin.  We pass a line thickness, a color and an alpha value to its constructor.
An alpha of 100 means it is completely opaque.

It is hard to see how this multi line is drawn in space, and how it is rotated. To get a better understanding, we can introduce the world coordinate system.

The global coordinate system

The coordinate axes

A Sandy world has the usual x, y and z axis in a Cartesian coordinate system. Before we move the camera, the x axis points to the right, the y axis upwards and the z axis into the screen. All new objects, are created in the origin [ 0, 0, 0 ].

This means that when we create the camera, it is positioned in the origin [0,0,0] and points into the screen in the positive z direction. To see our 3D objects , we have to move the camera to a negative z value.

Now that we know how to draw lines, lets draw the coordinate axes of the world. this is what we add to the createScene() function:

	// To place our lines in world, let's add the coordinate axes
	var xAxis:Object3D = new Line3D( new Vector(-150,0,0), new Vector(150,0,0) );
	var yAxis:Object3D = new Line3D( new Vector(0,-150,0), new Vector(0,150,0) );
	var zAxis:Object3D = new Line3D( new Vector(0,0,-150), new Vector(0,0,150) );

	// Give the axes distinct colors
	xAxis.setSkin( new SimpleLineSkin( 1, 0xff0000, 100 ) ); // red
	yAxis.setSkin( new SimpleLineSkin( 1, 0x00ff00, 100 ) ); // green
	zAxis.setSkin( new SimpleLineSkin( 1, 0x0000ff, 100 ) ); // blue

	// Create the coordinate system group
	var coords:Group = new Group();
	coords.addChild(xAxis);
	coords.addChild(yAxis);
	coords.addChild(zAxis);

	// Add the coordinate system to the rootGroup
	bg.addChild( coords);

We create the three lines starting in -150 and ending in +150 on the respective axes, so they will cross in [0,0,0]
To identify the axes, we give them different colors. As we don't want to transform them, we then add the three lines directly to the rootGroup.
If we add them to a branch group ( coords ) as I have done here it is more stringent. We get one rigid coordinate system, which we add to the world and which we may remove at any time, calling removeChild( coords ).
The purpose is to show the axes, and this group is never transformed.

Note. As long as the camera is on the z axis, we will only see the x and y axes.

To get the z axis into view, we have to reposition the camera.
Let's shift it slightly to the right and upwards i.e. a translation in positive x and y direction.

	cam.setPosition(50,50,-500);
	cam.lookAt(0,0,0);

When we translate the camera, it will no longer look at the origin. It's line of sight is still parallel to the z axis.
To see more of our little world, we can move farther from the scene, or we may redirect the camera.
In a simple case like this, we fix it by calling its lookAt( x, y, z ) function, passing suitable coordinate values.
I'll get back to camera movements in a later tutorial.

The Plane3D

We continue to explore the pre-made primitives. If we want a plane in our world, we use the Plane3D class. The class constructor takes four arguments: height, length, quality and mode.
The quality is a Number between 1 and 10, which is the number of parts the plane will be divided into. The mode is 'tri' or 'quad' as for all primitives. Using the 'tri' mode gives the faces of our three vertices, wile 'quad' gives us faces with four vertices.

Note: In the following examples, the camera is moved to slightly different positions as needed, using cam.setPosition(x, y, z).

As we already have a simple coordinate system, let's make it a bit better by introducing coordinate planes.
We'll keep the axes, and add the xy, yz, and zx planes.

Lets start by adding a single plane, to really see what happens. Here is what will become the xy plane:

	var xyPlane:Object3D = new Plane3D(100,100,10,'quad');
	xyPlane.setSkin( new MixedSkin(0x00FF00, 20, 0, 40, 1 ));

We give it height and length of 100 and set the quality to 10, to get 10 divisions in each direction.
We set a MixedSkin with a green color and alpha 20, to make it transparent. The line color is black with alpha of 40.

Then we add the xyPlane to the rootGroup.

This certainly looks more like a zx plane than an xy plane. This is very natural in the Sandy world.
A plane is by default given a height along the z axis and a length along the x axis.
As we can see clearly, positioning the object "at the origin" means the plane is placed with [ 0, 0, 0 ] in the center.

To make this plane an xy plane, i.e. it will contain the x and y axes, we'll have to rotate it 90 degrees around the x axes.
As in the cube case, we must create a TransformGroup with a Transform3D to get the rotation.

	// First we create a TransformationGroup
	var tg:TransformGroup = new TransformGroup();
	// We create Transform3D to carry out a transformation in 3 dimensions
	var rotation:Transform3D = new Transform3D();
	// We have to tell the Transformation what to do.
	rotation.rot(90,0,0);
	// The transform is set in the transform group
	tg.setTransform( rotation );
	// and the plane is added to the transform group
	tg.addChild( xyPlane );

We add the transform group to the rootGroup, and voila! We have a transparent xy plane

Now we'll have to create the yz plane and rotate it around the z axis.
 The zx plane is what we get by default, so it needs no transformation.

Zoom in to view the matching grids and axes!
It looks fairly good, but the grid makes it a little heavy. If we want to use the planes as references in other experiments, we should use a lighter skin.

A reference system

In the following examples, I'll be using a more transparent coordinate system as a reference. It will be created by a call to a utility function createCoordinateSystem(), that adds the axes and planes to the rootGroup.

Here is the code for that function.

// Create the coordinate system.
// doPlanes=false excludes coordinate planes
// grid sets the number of divisions ( 1 - 10 ) ( quality in Sandy parlance )
function createCoordinateSystem( bg:Group, doPlanes:Boolean, grid:Number ):Void {
	// Create the coordinate axes
	var xAxis:Object3D = new Line3D( new Vector(-150,0,0), new Vector(150,0,0) );
	var yAxis:Object3D = new Line3D( new Vector(0,-150,0), new Vector(0,150,0) );
	var zAxis:Object3D = new Line3D( new Vector(0,0,-150), new Vector(0,0,150) );
	// Give the axes distinct colors
	xAxis.setSkin( new SimpleLineSkin( 1, 0xff0000, 60 ) ); // red
	yAxis.setSkin( new SimpleLineSkin( 1, 0x00ff00, 60 ) ); // green
	zAxis.setSkin( new SimpleLineSkin( 1, 0x0000ff, 60 ) ); // blue
	// Create the axes group
	var axes:Group = new Group();
	axes.addChild(xAxis);
	axes.addChild(yAxis);
	axes.addChild(zAxis);
	// Add the coordinate axes to the rootGroup
	bg.addChild( axes );

	// Create the coordinate planes if so ordered
	if( doPlanes){

		var xyPlane:Object3D = new Plane3D(100,100,grid,'quad');
		xyPlane.setSkin( new MixedSkin(0x00FF00, 10, 0, 15, 1));

		var yzPlane:Object3D = new Plane3D(100,100,grid,'quad');
		yzPlane.setSkin( new MixedSkin(0x0000FF, 10, 0, 15, 1));

		var zxPlane:Object3D = new Plane3D(100,100,grid,'quad');
		zxPlane.setSkin( new MixedSkin(0xFF0000, 10, 0, 15, 1));

		// Create rotations for the xy plane and the yz plane
		// The zx plane doesn't need any transformation
		var tg1:TransformGroup = new TransformGroup();
		var rot1:Transform3D = new Transform3D();
		rot1.rot(90,0,0);
		tg1.setTransform( rot1 );
		tg1.addChild( xyPlane );

		var tg2:TransformGroup = new TransformGroup();
		var rot2:Transform3D = new Transform3D();
		rot2.rot(0,0,90);
		tg2.setTransform( rot2 );
		tg2.addChild( yzPlane );

		// Add the transformed coordinate planes to the rootGroup
		bg.addChild( tg1 );
		bg.addChild( tg2 );
		bg.addChild( zxPlane );
	}
}

You can see why I want to get it out of the way ;)
The function takes three arguments. Apart from the rootGroup, there is doPlanes:Boolean, telling the function whether to make the planes or not, and a grid:Number to set the number of divisions. If we set grid = 1, we get no division, maximum is 10. This is the same as the Sandy quality setting.

The Cylinder

The Cylinder is made up of faces like all other bodies in Sandy and any other vector based 3D system. For rounded or curved bodies, like a cylinder, the number of nodes ( vertices )  and faces determine how smooth the appearance will be. In other words, a well rounded cylinder requires lots of faces. That number is set by the q or quality argument of its constructor.

Lets get a nicely skinned cylinder!

function createScene( bg:Group ):Void {
	// Create the coordinate system.
	createCoordinateSystem(bg, true, 1);

	// Create the Cylinder
	var cyl = new Cylinder( 40, 40, 8, 'quad');
	cyl.setSkin( new MixedSkin(0xFEFE4E, 40, 0, 40, 1));
	bg.addChild( cyl );
}

Here we give the cylinder a radius of 40, a height of 40 and a quality of 8, meaning 8 faces along the periphery.
For a nice impression, we give it a MixedSkin with a semi transparent yellow color and a thin edge line.

So how does it look, well .. if we go to 40 faces maybe.

The cylinder in the middle has 40 faces along the periphery, and looks fairly rounded.

The cylinder to the right has a SimpleColorSkin. I have also enable lighting on the skin, which you do by calling

skin.setLightingEnable( true );

The default light source is at infinity, which means parallel light. It doesn't cast any shadows.

If you want a curved body smoother, just add faces in the quality parameter.
Remember though, the more faces you have, the more computing resources it demands.

The Sphere

Another useful body is the sphere, and we create it using the Sandy Sphere primitive.
Let's create the sphere with quality = 5, which is the maximum value.
And lets keep the light on.

function createScene( bg:Group ):Void {
	// Create the coordinate system. doPlanes=false excludes coordinate planes
	createCoordinateSystem(bg, true, 1);
	// Create the sphere
	var sphere = new Sphere( 40, 5, 'quad');
	sphere.setSkin( skin=new MixedSkin(0xFEFE4E, 40, 0, 40, 1));
	skin.setLightingEnable(true);
	bg.addChild( sphere );
}

We create the sphere in 'quad' mode and compare the MixedSkin with the SimpleColorSkin.

Let's see how the creation mode affects the apparent roundness of the sphere!
In the next two scenes, the sphere is created in 'tri' mode.

The creation mode does indeed not affect the smoothness of the skinned model.
As you can see in the left image, we get triangular faces, but they are not reflected in the color skin.

The Pyramid

The Pyramid3D is created with its base in the zx plane and its top on the y axis.
Here is the by now well known createScene function

function createScene( bg:Group ):Void {
	// Create the coordinate system.
	createCoordinateSystem(bg, true, 1);

	// Create the Pyramid
	var pyramid = new Pyramid( 90, 40, 40);
	//pyramid.setSkin( skin=new MixedSkin(0xFEFE4E, 5, 0, 40, 1));
	pyramid.setSkin( skin=new SimpleColorSkin(0xFEFE4E, 80));
	skin.setLightingEnable(true);
	bg.addChild( pyramid );
}

In the right image, the camera is lowered to the zx plane to to show more clearly where the pyramid is created by default.

The Hedra

We are getting close to the end of our primitives journey. The Hedra is the last of the ready-made bodies in the Sandy library.
After that we are on our own, if we need more primitive bodies.

So, what's a Hedra anyway?

Oh, patience please! We'll see in just a moment.
Here is the constructor of the Hedra, directly from the Sandy documentation.

public function Hedra(h:Number, lg:Number, rad:Number)
h represents the height of the Hedra, lg represent its length and rad its radius

Right! If you really don't know what a hedra is, that doesn't tell you much.
So let's create one!

function createScene( bg:Group ):Void {
	// Create the coordinate system.
	createCoordinateSystem(bg, true, 1);
	// Create the hedra
	var hedra = new Hedra( 90, 40, 40);
	hedra.setSkin( skin=new MixedSkin(0xFEFE4E, 80, 0, 40, 1));
	//hedra.setSkin( skin=new SimpleColorSkin(0xFEFE4E, 80));
	skin.setLightingEnable(true);
	bg.addChild( hedra );
}

So, what do you know - it's like two connected pyramids, stretching from -height to +height along the y axis.
Oh, you already knew that? Well, I didn't ;)

Time for another cup of Java, before we explore the highly mobile Sandy Camera.