3D Spikey ball / Scattering objects with Sandy 3.0
Posted by Dennis on Sep 5, 2007 in 3D, ActionScript, Flash • 1 commentRecently 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:




Hi Dennis!
Ah, isn’t that beatiful!?
Yes indeed