Create a Revolving Atom in Papervision3D

Just as the title suggests, we’re going to simulate an Atom with shells of electrons orbiting a nucleus, using Papervision 3D. Let’s get going..

This project was created purely with ActionScript 3.0 using FlashDevelop. If you want to do it the same way, FlashDevelop can be downloaded here. (Follow the configuration instructions here.) Otherwise, you can use Flash CS3 or above.


Final Result Preview

Let’s take a look at the final result we will be working towards:

Move the mouse around and the camera will orbit the Atom freely. Get the Atom in a position you like and click the mouse on the stage, this will put the Atom on a fixed ‘z’ position allowing you to rotate the camera on its x & y axes. Click the stage again to release the camera’s ‘z’ orbit.


Step 1: Create a New Project

Open FlashDevelop and click Project > New Project


Step 2: Set Up

Choose ActionScript 3 > AS3Project. For the name of the Project put in “Atoms”. For the location, click and navigate to the folder you would like to save it into. Leave the “create directory for project” checkbox selected and Click OK.

If you want to use Flash CS3/CS4, create a new flash file and set the width and height to 600×500. Set the background color to black. Name it “atoms.fla” and save it anywhere you’d like.


Step 3: Papervision 3D Installation

For Flash, copy or drag the “org’ and “nochump” folders from the source download into the folder where you have saved the atoms.fla file.

For FlashDevelop, go ahead and copy or drag Papervision3D_2.1.932.swc from the source files (downloadable above) into the lib folder for this project. For more information about PV3D, you can visit the website here.


Step 4: Assign the External Library

In FlashDevelop, click View > Project Manager. Click the ‘+’ sign to the left of the lib folder to expand it. Now right-click Papervision3D_2.1.932.swc and select Add To Library.


Step 6: The Document Class

For FlashDevelop, open the project manager again (refer to step 4), expand the src folder and double-click Main.as
Below the imports and right above the class definition, add the following metadata tag to set up the stage properties.

[SWF (width = 600, height = 500, frameRate = 30, backgroundColor = 0)]

Within the init () method after the comment “entry point”, add the following lines of code.


stage.addEventListener (Event.RESIZE, createBackground);

_backGround = new Sprite;
addChild (_backGround);

createBackground ();

var helium3:Helium3Atom = new Helium3Atom;

addChild (helium3);

Next, let’s create a simple gradient background. Add the following lines of code after the init () method:

private function createBackground (e:Event = null):void
{
	var g:Graphics = _backGround.graphics;

	g.clear ();
	var fillType:String = GradientType.RADIAL;
	var colors:Array = [0x0000FF, 0x000000];
	var alphas:Array = [1, 1];
	var ratios:Array = [0x00, 0xFF];
	var matr:Matrix = new Matrix();
	matr.createGradientBox(stage.stageWidth, stage.stageHeight, 0, 0, 0);
	var spreadMethod:String = SpreadMethod.PAD;
	g.beginGradientFill(fillType, colors, alphas, ratios, matr, spreadMethod);
	g.drawRect (0, 0, stage.stageWidth, stage.stageHeight);
}

That’s it for the Main document class, if you’re using FlashDevelop.

For Flash, create a new Main.as class in the same folder as your project. Make sure the Main.as class is in the same folder as the atoms.fla, “org” & “nochump” folders.

Add the following lines:

package
{
	import flash.display.GradientType;
	import flash.display.Graphics;
	import flash.display.SpreadMethod;
	import flash.display.Sprite;
	import flash.events.Event;
	import flash.geom.Matrix;

	public class Main extends Sprite
	{
 		private var _backGround:Sprite;

		public function Main():void
 		{
 			if (stage) init();
			else addEventListener(Event.ADDED_TO_STAGE, init);
 		}

		private function init(e:Event = null):void
 		{
 			removeEventListener(Event.ADDED_TO_STAGE, init);

			stage.addEventListener (Event.RESIZE, createBackground);

			_backGround = new Sprite;
			addChild (_backGround);

			createBackground ();

			var helium3:Helium3Atom = new Helium3Atom;

			addChild (helium3);
 		}

		private function createBackground (e:Event = null):void
		{
			var g:Graphics = _backGround.graphics;

			g.clear ();
			var fillType:String = GradientType.RADIAL;
			var colors:Array = [0x0000FF, 0x000000];
			var alphas:Array = [1, 1];
			var ratios:Array = [0x00, 0xFF];
			var matr:Matrix = new Matrix();
			matr.createGradientBox(stage.stageWidth, stage.stageHeight, 0, 0, 0);
			var spreadMethod:String = SpreadMethod.PAD;
			g.beginGradientFill(fillType, colors, alphas, ratios, matr, spreadMethod);
			g.drawRect (0, 0, stage.stageWidth, stage.stageHeight);
		}
 	}
}

Open Flash and assign “Main” as the Document class. (Check out this quick introduction to document classes if you’re not sure what we’re doing.)

If you try to run this now, you will get an error since we haven’t created the Helium3Atom class yet. So just make sure to save the file and leave it for now.


Step 7: Create the Helium3Atom Class

From FlashDevelop, click View > Project Manager, right-click the src folder and choose Add > New Class.


Step 8: Setting Up the Class

Name the class Helium3Atom, click the browse button for the base class and enter “org.papervision3d.view.BasicView”. Hit OK to complete.


Step 9: Importing Required Classes

(Still in FlashDevelop) Add all the necessary imports inside the package brackets right above “import org.papervision3d.view.BasicView;” and save the file.

import flash.events.Event;
import flash.events.MouseEvent;
import org.papervision3d.lights.PointLight3D;
import org.papervision3d.materials.ColorMaterial;
import org.papervision3d.materials.shadematerials.GouraudMaterial;
import org.papervision3d.materials.WireframeMaterial;
import org.papervision3d.objects.DisplayObject3D;
import org.papervision3d.objects.primitives.Cylinder;
import org.papervision3d.objects.primitives.Sphere;

For Flash, create a new ActionScript file, name it Helium3Atom and save it into the same directory you have been using. It should be right next to the atoms.fla file, the “org” & “nochump” folders, and the Main.as class. Add the following code:

package
{
	import flash.events.Event;
	import flash.events.MouseEvent;
	import org.papervision3d.lights.PointLight3D;
	import org.papervision3d.materials.ColorMaterial;
	import org.papervision3d.materials.shadematerials.GouraudMaterial;
	import org.papervision3d.materials.WireframeMaterial;
	import org.papervision3d.objects.DisplayObject3D;
	import org.papervision3d.objects.primitives.Cylinder;
	import org.papervision3d.objects.primitives.Sphere;
	import org.papervision3d.view.BasicView;

	public class Helium3Atom extends BasicView
	{
		public function Helium3Atom ()
		{

		}

	}

}

Step 10: Instance Variables

Inside the class bracket, right before the constructor method, add the following lines of code:

private var _do3DArray:Array;//will hold all the references to all the eletrons and their rings
private var _easeOut:Number = .3;//easing strength when moving the camera
private var _reachX:Number = .1;//the lower this is set, the farther the reach towards it's x axis
private var _reachY:Number = .1;//same as reachX, but applies on the y axis
private var _reachZ:Number = .5;//used on conjunction with the -mouseY, the closer the mouseY is to the stage center, the closer the camera moves towards the atom.
private var _rotX:Number = 0.5;//the value used for the camera's rotation on the x axis
private var _rotY:Number = 0.5;//same as _rotX, but applies for the camera's y axis
private var _camPitch:Number = 0;//orbiting on the x axis calculated on the fly inside the onRenderTick () method
private var _camYaw:Number = 0;//orbiting on the y axis calculated on the fly inside the onRenderTick () method
private var _zDist:Number = 4;//controls the strength of zooming the camera in and out
private var _colorArray:Array = [0xCC490B, 0x26D965, 0xCC490B];//colors for the neutrons and protons inside the nucleus
private var _freeOrbit:Boolean = true;//switches the orbit mode of the camera

Step 11: The Constructor

Add the following lines inside the Constructor method.

if (stage) init ();
else addEventListener (Event.ADDED_TO_STAGE, init);

startRendering ();

The first two lines call the init () method. (If the stage is not yet available, it adds a listener for when the instance is added to the stage, and then calls the init () method.)

The startRendering () method is then called afterwards. This method is from BasicView’s super class, the “AbstractView”. What this method does is add an ENTER_FRAME listener that triggers the onRenderTick () method. We will need to override this protected method later to animate the atom and the move the camera.


Step 12: Instance Initialization

Inside the init () method called by the constructor, we first remove the event listener for adding the instance to the stage and then call the createAtom () method

Add the following lines of code below the constructor () method:

private function init (e:Event = null):void
{
	removeEventListener (Event.ADDED_TO_STAGE, init);

	createAtom ();
}

Step 13: Atom Elements (1st half)

Let’s just go through what’s happening in the createAtom () method. The _do3DArray is instantiated, we need to put references of all the DisplayObject3D that hold the rings and electrons into this array so we can access them later for animation.

A local variable named “light” is assigned a PointLight3D object instance and positioned on the top-right side of the scene.

Another local variable named “atom” is assigned a DisplayObject3D instance and is added into the scene. This will be the parent DisplayObject3D for all the other elements.

The “nucleus”, also a DisplayObject3D is then instantiated. It is rotated 90 degrees on its x axis to make it face the camera and then added into the “atom” DisplayObject3D instance.

The “sphere” variable, also a local variable, is then assigned a Sphere object. A Sphere is a built-in primitive of PV3D. With this particular instance, we assign “null” for it’s material parameter, “25″ for its radius parameter, and “1″ for both segmentsW and segmentsH parameters.

Add the following lines of code after their init () method:

private function createAtom ():void
{
	_do3DArray = [];

	var light:PointLight3D = new PointLight3D;
	light.x = 300; light.y = 700; light.z = 0;
	scene.addChild (light)

	var atom:DisplayObject3D = new DisplayObject3D;
	scene.addChild (atom);

	var nucleus:DisplayObject3D = new DisplayObject3D;
	nucleus.rotationX = 90;
	atom.addChild (nucleus);

	var sphere:Sphere = new Sphere (null, 25, 1, 1);
	scene.addChild (sphere);
}

Step 14: Checking the Sphere

Go ahead and hit CTRL + Enter on you keyboard. The sphere looks more like a polygon with 5 corners. This is because we assigned 1 for both the segmentsW & segmentsH. It has a total of 5 vertices.

After testing, remove the addChild method. The “sphere” will not be added to the scene, instead, it will be used as guide to position the neutrons and protons as you will see next. Add the code below inside the createAtom () method after the “sphere” declaration where you removed the addChild method.

for (var i:uint = 1; i < sphere.geometry.vertices.length-1; i++)
{
	var np:Sphere = new Sphere (new GouraudMaterial (light, _colorArray[i - 1], 0, 0), 23, 12, 9);
	np.x = sphere.geometry.vertices[i].x;
	np.y = sphere.geometry.vertices[i].y;
	np.z = sphere.geometry.vertices[i].z;

	nucleus.addChild (np);
}

What this loop does is iterate from 1 through 4 and skips 0 and 5. A local variable conveniently named “np” (neutrons & protons) is created during each loop and assigned a Sphere primitive. Each “np” Sphere is assigned a GouraudMaterial with the PointLight3D object we created earlier for its light and colorArray[i – 1] for the lightColor parameter. The “np” Sphere’s second parameter is assigned a radius of “23″ and the last 2 parameters are for the segmentsW & segmentsH which are assigned “12″ & “9″ respectively. We can afford higher amounts of segments since there are only 3 spheres inside the nucleus. The result is a more rounded Sphere..

Each “np” Sphere is then added inside the nucleus with the coordinates based from the current iteration’s vertex inside the “sphere” Sphere.


Step 15: Atom Elements (2nd half)

From here, we iterate twice to create the electrons and their corresponding rings.

We first instantiate a DisplayObject3D and assign it to “do3d”. This DisplayObject3D will house both the electron and its ring.

A Sphere primitive is then created and assigned to “electron”. This makes the off-white colored electrons that orbit the nucleus. The parameters we assigned to create each electron are as follows – a ColorMaterial with a value of 0xEFECCA with full opacity, the radius of the sphere with a value of 7. The last two optional parameters we left out defaulted with values of 8 & 6.

A Cylinder primitive is then created to make the rings. Here we just use a WireframeMaterial white in color, an alpha of .05, a thickness of 2, a radius of 300, height of 1, segmentsW of 48 to make it really round and segmentsH of 1 since the height is also 1. The topRadius value is left with -1 which is its default value and we set both topFace and bottomFace to “false”” since we don’t need them here.

The electron is then positioned on 303 so the ring is right in the middle of the electron.

We then set the doubleSided property of the ring’s material to “true” to make the ring show both its inner and outer shells.

After that, the do3d’s localRotationZ is then calculated by dividing 360 by twice the number of rings, multiplying by i, and adding 45 degrees to the result. When the ring is first created, it’s lying flat like the bottom of a cup. We multiply the number of rings by 2 to rotate its “z” axis from 0 to 90 and add 45 degrees to make a nice “X” formation for the 2 rings. We then set the do3d’s “y” rotation to a random position of a full circle so the electrons will all have a different position when they orbit the nucleus.

The rings and electrons are then added inside do3d, then a reference is added into the _do3dArray.

Finally, the do3d is added inside the atom DisplayObject3D.

Add the following lines of code right below the closing bracket of the first loop:

for (i = 0; i < 2; i++)
{
	var do3d:DisplayObject3D = new DisplayObject3D;

	var ring:Cylinder;

	var electron:Sphere;
	electron = new Sphere (new ColorMaterial (0xEFECCA, 1), 7);
	ring = new Cylinder (new WireframeMaterial (0xFFFFFF, .05, 2), 300, 1, 48, 1, -1, false, false);
	electron.x = 303;//add half size of electron

	ring.material.doubleSided = true;

	do3d.localRotationZ = 360 / 4 * i + 45;
	do3d.localRotationY = 360 * Math.random ();

	do3d.addChild (ring);

	do3d.addChild (electron);

	_do3DArray.push (do3d);

	atom.addChild (do3d);
}

Step 16: Checking Our Progress

Let’s test it and see if it worked. If everything went well, you should have an image like below:

We’re almost done, we just need to add animation now.


Step 17: Camera Orbit Mode

Inside the init () method (refer to Step 12), before the “createAtom ()” method call, add the following line of code:

stage.addEventListener (MouseEvent.CLICK, onStageClick);

This will trigger the change in the camera’s mode of orbit. Add the following code below the init () method’s closing brace:

private function onStageClick (e:MouseEvent):void
{
	_freeOrbit = ! _freeOrbit;
}

All this does is toggle _freeOrbit between true or false.


Step 18: Orbiting the Atom

The onRenderTick () method is triggered via an Event.ENTER_FRAME from BasicView’s superclass – AbstractView.

Inside this, super.onRenderTick (event) is called to render the scene.

The DisplayObject3D that holds the rings and electrons inside _do3dArray is then applied a yaw of 10. Applying yaw to a DisplayObject3D is the same as do3d.localRotationY += 10;. This is what makes the rings and electrons orbit the nucleus.

The xDist and yDist simply calculate how far the mouseX & mouseY are from the center of the stage.

Then we have the conditional that checks the current state of _freeOrbit. If _freeOrbit is true, the camera moves freely on its 3 axes. Moving the mouse to the left will cause the camera to ease towards its left, whilst moving the mouse to the right will do the opposite. The same applies for the camera’s y axis (moving up and down). When the camera is closest to the center, a zoom effect is applied.

On the other hand, if _freeOrbit is false, the camera instead, only orbits freely on it’s x and y axes and has a fixed z position. This has the effect of the camera moving around the atom. The equation is easier to understand if you play with the values of the properties. 90 degrees is applied to _camPitch so that the atom is fully facing the center of the stage when the mouse is in the middle of the stage; if not applied, the atom will be on its left side when the mouse is centered on the stage.

This is the same reason 270 degrees is applied to _camYaw; if not added, the atom will be on its top side when the mouse is in the middle of the stage. Also, we limit the orbit of the camera when you move the mouse up and down on the stage. If you remove the _camPitch’s limits, there’s a point where the camera becomes upside down and will correct itself. This has an undesirable effect. When the class is completed in the next step, try removing the limits to see what happens.

Add the following lines of code after the createAtom () method.

override protected function onRenderTick (event:Event = null):void
{
	super.onRenderTick (event);
	for (var i:uint = 0; i < _do3DArray.length; i++)
	{
		_do3DArray[i].yaw (10);
	}

	var xDist:Number = mouseX - stage.stageWidth * .5;
	var yDist:Number = mouseY - stage.stageHeight * .5;

	if (_freeOrbit)
	{
		camera.x += (xDist - camera.x * _reachX) * _easeOut;
		camera.y += (yDist - camera.y * _reachY) * _easeOut;
		camera.z += (-mouseY * _zDist - camera.z) * _reachZ;
	}
	else
	{
		_camPitch += ((-yDist * _rotX) - _camPitch + 90) * _easeOut;
		_camYaw += ((xDist * _rotY) - _camYaw + 270) * _easeOut;

		if(_camPitch < 5) _camPitch = 5;
		if(_camPitch > 175) _camPitch = 175;
		camera.orbit (_camPitch, _camYaw);
	}
}

Step 19: Testing the Completed Class

That should be all of it for the Helium3Atom class. Your class should look exactly like the following:

package
{
	import flash.events.Event;
	import flash.events.MouseEvent;
	import org.papervision3d.lights.PointLight3D;
	import org.papervision3d.materials.ColorMaterial;
	import org.papervision3d.materials.shadematerials.GouraudMaterial;
	import org.papervision3d.materials.WireframeMaterial;
	import org.papervision3d.objects.DisplayObject3D;
	import org.papervision3d.objects.primitives.Cylinder;
	import org.papervision3d.objects.primitives.Sphere;
	import org.papervision3d.view.BasicView;

	public class Helium3Atom extends BasicView
	{
		private var _do3DArray:Array;

		private var _easeOut:Number = .3;

		private var _reachX:Number = .1;
		private var _reachY:Number = .1;
		private var _reachZ:Number = .5;

		private var _rotX:Number = 0.5;
		private var _rotY:Number = 0.5;

		private var _camPitch:Number = 0;
		private var _camYaw:Number = 0;

		private var _zDist:Number = 4;

		private var _colorArray:Array = [0xCC490B, 0x26D965, 0xCC490B];
 		private var _freeOrbit:Boolean = true;

		public function Helium3Atom ()
		{
			if (stage) init ();
			else addEventListener (Event.ADDED_TO_STAGE, init);

			startRendering ();
		}

		private function init (e:Event = null):void
		{
			removeEventListener (Event.ADDED_TO_STAGE, init);
			stage.addEventListener (MouseEvent.CLICK, onStageClick);

			createAtom ();
		}

		private function onStageClick (e:MouseEvent):void
		{
			_freeOrbit = ! _freeOrbit;
		}

		private function createAtom ():void
		{
			_do3DArray = [];

			var light:PointLight3D = new PointLight3D;
			light.x = 300; light.y = 700; light.z = 0;
			scene.addChild (light)

			var atom:DisplayObject3D = new DisplayObject3D;
			scene.addChild (atom);

			var nucleus:DisplayObject3D = new DisplayObject3D;
			nucleus.rotationX = 90;
			atom.addChild (nucleus);

			var sphere:Sphere = new Sphere (null, 25, 1, 1);//invisible guide

			for (var i:uint = 1; i < sphere.geometry.vertices.length-1; i++)
			{
				var np:Sphere = new Sphere (new GouraudMaterial (light, _colorArray[i - 1], 0, 0), 23, 12, 9);
				np.x = sphere.geometry.vertices[i].x;
				np.y = sphere.geometry.vertices[i].y;
				np.z = sphere.geometry.vertices[i].z;

				nucleus.addChild (np);
			}

			for (i = 0; i < 2; i++)
			{
				var do3d:DisplayObject3D = new DisplayObject3D;

				var ring:Cylinder;

				var electron:Sphere;
				electron = new Sphere (new ColorMaterial (0xEFECCA, 1), 7);

				ring = new Cylinder (new WireframeMaterial (0xFFFFFF, .05, 2), 300, 1, 48, 1, -1, false, false);
				electron.x = 303;//add half size of electron

				ring.material.doubleSided = true;

				do3d.localRotationZ = 360 / 4 * i + 45;
				do3d.localRotationY = 360 * Math.random ();

				do3d.addChild (ring);

				do3d.addChild (electron);

				_do3DArray.push (do3d);

				atom.addChild (do3d);
			}
		}

		override protected function onRenderTick (event:Event = null):void
		{
			super.onRenderTick (event);

			for (var i:uint = 0; i < _do3DArray.length; i++)
			{
				_do3DArray[i].yaw (10);
			}

			var xDist:Number = mouseX - stage.stageWidth * .5;
			var yDist:Number = mouseY - stage.stageHeight * .5;

			if (_freeOrbit)
			{
				camera.x += (xDist - camera.x * _reachX) * _easeOut;
				camera.y += (yDist - camera.y * _reachY) * _easeOut;
				camera.z += (-mouseY * _zDist - camera.z) * _reachZ;
			}
			else
			{
				_camPitch += ((-yDist * _rotX) - _camPitch + 90) * _easeOut;
				_camYaw += ((xDist * _rotY) - _camYaw + 270) * _easeOut;

				if(_camPitch < 5) _camPitch = 5;
				if(_camPitch > 175) _camPitch = 175;
				camera.orbit (_camPitch, _camYaw);
			}
		}

	}
}

Hit CTRL + Enter and you should get something like you see below:


Step 20: The Carbon Atom

Ok, let’s now create a slightly more complex atom. Create a new class (if you are using FlashDevelop, refer to Steps 7 & 8). The only difference should be the name, it should be “CarbonAtom”. It has pretty much the same code, with a few changes so go ahead and copy all the content of the Helium3Atom, select all the content inside the CarbonAtom and replace it with the code copied from Helium3Atom.

Let’s start modifying things from the top. Inside the class declaration where we have _colorsArray, remove the assigned array that holds the 3 colors.

Next, go into the init () method. Add the code below before the createAtom () method call:


randomizeColorArray ();

Go below the onStageClick () method and add this new method:

private function randomizeColorArray ():void
{
	_colorArray = [];

	var tempArray:Array = [];

	for (var i:uint = 0; i < 12; i++)
	{
		if (i < 6) var color:uint = 0x004080;	//shade of blue
		else color = 0xA40000;	//shade of red

		tempArray.push (color);
	}

	while (tempArray.length > 6)
	{
		_colorArray.push (tempArray.splice (uint (Math.random () * tempArray.length), 1));
	}
	_colorArray = _colorArray.concat (tempArray);
}

This method randomizes the position of 2 colors that are stored in _colorArray. This makes 6 equal parts of red & blue randomly positioned inside _colorArray.

Next, go into the createAtom () method right where “nucleus” is instantiated and remove the “nucleus.rotationX = 90″ assignment.

Go down 2 lines and change the sphere instantiation parameters to “var sphere:Sphere = new Sphere (null, 60, 4, 4);”. We need a bigger Sphere with more vertices to hold all 12 Protons and Neutrons.

Now go inside the first loop where the “np” Sphere is instantiated and remove the last 2 parameters of 12 & 9. This sets the segmentsW & segmentsH to their defaults, 8 & 6, respectively, and reduces the hit on performance.

Next, in the second loop, change the amount of loops from 2 to 6. Go right below the electron instantiation and replace the following code:

ring = new Cylinder (new WireframeMaterial (0xFFFFFF, .05, 2), 300, 1, 48, 1, -1, false, false);
electron.x = 303;

with:

if (i == 1 || i == 5)
{
	ring = new Cylinder (new WireframeMaterial (0xFFFFFF, .05, 2), 450, 1, 48, 1, -1, false, false);
	electron.x = 453;
}
else
{
	ring = new Cylinder (new WireframeMaterial (0xFFFFFF, .05, 2), 610, 1, 48, 1, -1, false, false);
	electron.x = 613;
}

This is where the two shells (rings) are created for the CarbonAtom.

Now go below where you see the “ring.material.doubleSided = true” assignment and replace the code:

do3d.localRotationZ = 360 / 4 * i + 45;

with:

do3d.localRotationZ = 360 / 12 * i + 180;

This gives the 6 electrons proper placement to fully cover the nucleus. That’s it. Save the file before testing.


Step 21: Testing the CarbonAtom

The CarbonAtom class should look exactly like this:

package
{
	import flash.display.StageQuality;
	import flash.events.Event;
	import flash.events.MouseEvent;
	import org.papervision3d.lights.PointLight3D;
	import org.papervision3d.materials.ColorMaterial;
	import org.papervision3d.materials.shadematerials.GouraudMaterial;
	import org.papervision3d.materials.WireframeMaterial;
	import org.papervision3d.objects.DisplayObject3D;
	import org.papervision3d.objects.primitives.Cylinder;
	import org.papervision3d.objects.primitives.Sphere;
	import org.papervision3d.view.BasicView;

	public class CarbonAtom extends BasicView
	{
		private var _do3DArray:Array;

		private var _easeOut:Number = .3;

		private var _reachX:Number = .1;
		private var _reachY:Number = .1;
		private var _reachZ:Number = .5;

		private var _rotX:Number = 0.5;
		private var _rotY:Number = 0.5;

		private var _camPitch:Number = 0;
		private var _camYaw:Number = 0;

		private var _zDist:Number = 4;

		private var _colorArray:Array;
		private var _freeOrbit:Boolean = true;

		public function CarbonAtom ()
		{
			if (stage) init ();
			else addEventListener (Event.ADDED_TO_STAGE, init);

			startRendering ();
		}
		private function init (e:Event = null):void
		{
			removeEventListener (Event.ADDED_TO_STAGE, init);
			stage.addEventListener (MouseEvent.CLICK, onStageClick);

			randomizeColorArray ();

			createAtom ();
		}

		private function onStageClick (e:MouseEvent):void
		{
			_freeOrbit = ! _freeOrbit;
		}

		private function randomizeColorArray ():void
		{
			_colorArray = [];

			var tempArray:Array = [];

			for (var i:uint = 0; i < 12; i++)
			{
				if (i < 6) var color:uint = 0x004080;
				else color = 0xA40000;

				tempArray.push (color);
			}

			while (tempArray.length > 6)
			{
				_colorArray.push (tempArray.splice (uint (Math.random () * tempArray.length), 1));
			}
			_colorArray = _colorArray.concat (tempArray);
		}

		private function createAtom ():void
		{
			_do3DArray = [];

			var light:PointLight3D = new PointLight3D;
			light.x = 300; light.y = 700; light.z = 0;
			scene.addChild (light)

			var atom:DisplayObject3D = new DisplayObject3D;
			scene.addChild (atom);

			var nucleus:DisplayObject3D = new DisplayObject3D;
			atom.addChild (nucleus);

			var sphere:Sphere = new Sphere (null, 60, 4, 4);

			for (var i:uint = 1; i < sphere.geometry.vertices.length-1; i++)
			{
				var np:Sphere = new Sphere (new GouraudMaterial (light, _colorArray[i - 1], 0, 0), 23);
				np.x = sphere.geometry.vertices[i].x;
				np.y = sphere.geometry.vertices[i].y;
				np.z = sphere.geometry.vertices[i].z;

				nucleus.addChild (np);
			}

			for (i = 0; i < 6; i++)
			{
				var do3d:DisplayObject3D = new DisplayObject3D;

				var ring:Cylinder;

				var electron:Sphere;
				electron = new Sphere (new ColorMaterial (0xEFECCA, 1), 7);

				if (i == 1 || i == 5)
				{
					ring = new Cylinder (new WireframeMaterial (0xFFFFFF, .05, 2), 450, 1, 48, 1, -1, false, false);
					electron.x = 453;
				}
				else
				{
					ring = new Cylinder (new WireframeMaterial (0xFFFFFF, .05, 2), 610, 1, 48, 1, -1, false, false);
					electron.x = 613;
				}

				ring.material.doubleSided = true;

				do3d.localRotationZ = 360 / 12 * i + 180;
				do3d.localRotationY = 360 * Math.random ();

				do3d.addChild (ring);

				do3d.addChild (electron);

				_do3DArray.push (do3d);

				atom.addChild (do3d);
			}
		}

		override protected function onRenderTick (event:Event = null):void
		{
			super.onRenderTick (event);

			for (var i:uint = 0; i < _do3DArray.length; i++)
			{
				_do3DArray[i].yaw (10);
			}

			var xDist:Number = mouseX - stage.stageWidth * .5;
			var yDist:Number = mouseY - stage.stageHeight * .5;

			if (_freeOrbit)
			{
				camera.x += (xDist - camera.x * _reachX) * _easeOut;
				camera.y += (yDist - camera.y * _reachY) * _easeOut;
				camera.z += (-mouseY * _zDist - camera.z) * _reachZ;
			}
			else
			{
				_camPitch += ((-yDist * _rotX) - _camPitch + 90) * _easeOut;
				_camYaw += ((xDist * _rotY) - _camYaw + 270) * _easeOut;

				if(_camPitch < 5) _camPitch = 5;
				if(_camPitch > 175) _camPitch = 175;

				camera.orbit (_camPitch, _camYaw);
			}
		}

	}

Open the Main document class and replace the code:

var helium3:Helium3Atom = new Helium3Atom;
addChild (helium3);

with:

var carbon:CarbonAtom = new CarbonAtom;
addChild (new CarbonAtom);

You should see something like the preview when you test the movie.


Conclusion

Papervision is a very straight forward and powerful tool. Experiment with it and you will come up with all kinds of cool 3D effects ranging from simple simulations to sophisticated interfaces.

Also, I made another version of the carbon atom named CarbonAtom2 which is also included with the source download. That one has a more realistic electron behavior, check it out! =)

As always, for any comments, suggestions or concerns, please leave a note in the comment section. Thanks for reading!

Website Search Engine Marketing SEO SEM Professional DAILY $ by shopatwork

Hello, I require someone to do full time Search engine marketing and SEO to bring leads and customers to my website from the United Kingdom. Please draft a report of what you will do for my website… (Budget: $30-250, Jobs: Anything Goes, Bulk Marketing, Marketing, SEM / Adwords, SEO)

Good Flash Actionscripter needed existing page urgent by edvconsulting

Payment-Terms: Escrow 100 %. We need this within one day, please only bid if you can do it and have experiences in action script, so you are able to understand the current code. The modifications are not very problematic but urgent… (Budget: $30-250, Jobs: ActionScript, Flash)

PSD to XHTML/CSS – Make it cheap by deren95

Hello Freelancers, I am in need of someone who can first make a small touchup (if necessary)and convert the psd into an xhtml/css validated site. I am on a low budget and I have had trouble with the designer (this being one of the reasons of the budget)… (Budget: $30-250, Jobs: CSS, HTML, jQuery / Prototype, PSD to HTML)