3D Spikey ball / Scattering objects with Sandy 3.0

Posted by Dennis on Sep 5, 2007 in 3D, ActionScript, Flash1 comment

Recently a new functionality has been introduced in Sandy 3.0. It is a method in the Geometry3D class called generateVertexNormals(). As the name already implies, this method generates the normal vector for every vertex in a shape. I’ve played with vertex normals before but this time I wanted to align other objects with the normals instead of pulling out the individual vertices.

In order to realise this the following steps have to be taken:

  • Create a Sphere
  • Call the generateVertexNormals() method on the Sphere’s geometry
  • Loop through the vertex- and vertex normal arrays and get the current vertex and vertex normal
  • Create a Cone object and position it on the vertex
  • Get the cross product of the Cone’s direction vector (0, 1, 0) and the vertex normal. This gives us the axis of rotation
  • Use the dot product to get the angle between the Cone’s direction vector (0, 1, 0) and the vertex normal
  • Apply the rotation by calling the Cone’s rotateAxis() method

Here’s the code:


package {
	import flash.display.Bitmap;
	import flash.display.Sprite;
	import flash.events.Event;

	import sandy.core.World3D;
	import sandy.core.data.Vector;
	import sandy.core.data.Vertex;
	import sandy.core.scenegraph.Camera3D;
	import sandy.core.scenegraph.Group;
	import sandy.core.scenegraph.TransformGroup;
	import sandy.materials.Appearance;
	import sandy.materials.BitmapMaterial;
	import sandy.math.VectorMath;
	import sandy.primitive.Cone;
	import sandy.primitive.Sphere;
	import sandy.util.NumberUtil;

	[SWF(width="400", height="400", backgroundColor='#000000', frameRate='48')]

	/**
	 * SandyScatter.as
	 * 5 September 2007
	 * @author Dennis Ippel - http://www.rozengain.com
	 */
	public class SandyScatter extends Sprite
	{
		private var world:World3D;
		private var sphere:Sphere;
		private var vertexCounter:int;
		private var transformGroup:TransformGroup;
		[Embed( source="../assets/890.jpg" )]
		private var MetalTexture:Class;
		private var metalAppearance:Appearance;

		/**
		 * Constructor
		 */
		public function SandyScatter()
		{
			init();

			addEventListener( Event.ENTER_FRAME, enterFrameHandler );
		}

		/**
		 * Initialize the 3D world and prepare the appearance
		 */
		private function init():void
		{
			// -- Create the 3D world
			world = World3D.getInstance();
			world.container = this;
			world.root = new Group( "rootGroup" );
			world.camera = new Camera3D( 400, 400 );
			world.camera.z = -1000;
			world.root.addChild( world.camera );
			transformGroup = new TransformGroup( "tg" );
			world.root.addChild( transformGroup );

			// -- Create the sphere where we will put the cones on
			sphere = new Sphere( "sphere", 200, 8, 8 );
			sphere.geometry.generateVertexNormals();

			// -- Create the appearance for the cones
			var metalBitmap:Bitmap = new MetalTexture() as Bitmap;
			metalAppearance = new Appearance(
				new BitmapMaterial( metalBitmap.bitmapData )
			);
		}

		/**
		 * Do the scatter, rotation and render stuff here
		 * @param event
		 */
		private function enterFrameHandler( event:Event ):void
		{
			scatter();
			transformGroup.rotateX++;
			transformGroup.rotateY++;
			transformGroup.rotateZ++;
			world.render();
		}

		/**
		 * Place the cones on each of the sphere vertices and align them
		 * with the vertex normals
		 */
		private function scatter():void
		{
			if( vertexCounter < sphere.geometry.aVertexNormals.length )
			{
				// -- Get the current vertex and vertex normal
				var vertex:Vertex = sphere.geometry.aVertex[ vertexCounter ] as Vertex;
				var normal:Vertex = sphere.geometry.aVertexNormals[ vertexCounter++ ] as Vertex;

				// -- Create a cone object
				var cone:Cone = new Cone( "cone" + vertexCounter, 10, 80 );
				cone.appearance = metalAppearance;

				// -- Position the cones on the current vertex
				cone.setPosition( vertex.x, vertex.y, vertex.z );

				// -- Get the cross product of two vectors. The Cross product defines the axis
				// -- around which the rotation should be applied. The first vector represents
				// -- the cone's current direction. By default this is (0, 1, 0) because
				// -- it is standing up straight (y is pointing upwards). The second vector is
				// -- the vector normal
				var cross:Vector = VectorMath.cross(
					new Vector( 0, 1, 0 ),
					new Vector( normal.x, normal.y, normal.z )
				);

				// -- Get the rotation angle. The dot product returns the cosine of the angle
				// -- of the two vectors. For our purposes, we need the arccosine.
				var angle:Number =
					Math.acos(
						VectorMath.dot(
							new Vector( 0, 1, 0 ),
							new Vector( normal.x, normal.y, normal.z )
						)
					);

				// -- Apply the rotation
				cone.rotateAxis( cross.x, cross.y, cross.z, NumberUtil.toDegree( angle ) );

				transformGroup.addChild( cone )
			}
		}
	}
}

… and the resulting SWF:

Links



Tags: , , ,


1 comment

» Comments RSS Feed
  1. Hi Dennis!
    Ah, isn’t that beatiful!?
    Yes indeed ;)

Leave a comment