Advertisement
  1. Code
  2. 3D

Create a Revolving Atom in Papervision3D

Scroll to top
Read Time: 25 min

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 600x500. 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.

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

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

1
2
3
stage.addEventListener (Event.RESIZE, createBackground);
4
5
_backGround = new Sprite;
6
addChild (_backGround);
7
			
8
createBackground ();
9
10
var helium3:Helium3Atom = new Helium3Atom;
11
12
addChild (helium3);

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

1
2
private function createBackground (e:Event = null):void
3
{
4
	var g:Graphics = _backGround.graphics;
5
6
	g.clear ();
7
	var fillType:String = GradientType.RADIAL;
8
	var colors:Array = [0x0000FF, 0x000000];
9
	var alphas:Array = [1, 1];
10
	var ratios:Array = [0x00, 0xFF];
11
	var matr:Matrix = new Matrix();
12
	matr.createGradientBox(stage.stageWidth, stage.stageHeight, 0, 0, 0);
13
	var spreadMethod:String = SpreadMethod.PAD;
14
	g.beginGradientFill(fillType, colors, alphas, ratios, matr, spreadMethod);
15
	g.drawRect (0, 0, stage.stageWidth, stage.stageHeight);
16
}

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:

1
2
package
3
{
4
	import flash.display.GradientType;
5
	import flash.display.Graphics;
6
	import flash.display.SpreadMethod;
7
	import flash.display.Sprite;
8
	import flash.events.Event;
9
	import flash.geom.Matrix;
10
11
	public class Main extends Sprite
12
	{
13
 		private var _backGround:Sprite;
14
15
		public function Main():void
16
 		{
17
 			if (stage) init();
18
			else addEventListener(Event.ADDED_TO_STAGE, init);
19
 		}
20
21
		private function init(e:Event = null):void
22
 		{
23
 			removeEventListener(Event.ADDED_TO_STAGE, init);
24
25
			stage.addEventListener (Event.RESIZE, createBackground);
26
			
27
			_backGround = new Sprite;
28
			addChild (_backGround);
29
			
30
			createBackground ();
31
			
32
			var helium3:Helium3Atom = new Helium3Atom;
33
			
34
			addChild (helium3);
35
 		}
36
		
37
		private function createBackground (e:Event = null):void
38
		{
39
			var g:Graphics = _backGround.graphics;
40
			
41
			g.clear ();
42
			var fillType:String = GradientType.RADIAL;
43
			var colors:Array = [0x0000FF, 0x000000];
44
			var alphas:Array = [1, 1];
45
			var ratios:Array = [0x00, 0xFF];
46
			var matr:Matrix = new Matrix();
47
			matr.createGradientBox(stage.stageWidth, stage.stageHeight, 0, 0, 0);
48
			var spreadMethod:String = SpreadMethod.PAD;
49
			g.beginGradientFill(fillType, colors, alphas, ratios, matr, spreadMethod);
50
			g.drawRect (0, 0, stage.stageWidth, stage.stageHeight);
51
		}
52
 	}
53
}

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.

1
2
import flash.events.Event;
3
import flash.events.MouseEvent;
4
import org.papervision3d.lights.PointLight3D;
5
import org.papervision3d.materials.ColorMaterial;
6
import org.papervision3d.materials.shadematerials.GouraudMaterial;
7
import org.papervision3d.materials.WireframeMaterial;
8
import org.papervision3d.objects.DisplayObject3D;
9
import org.papervision3d.objects.primitives.Cylinder;
10
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:

1
2
package
3
{
4
	import flash.events.Event;
5
	import flash.events.MouseEvent;
6
	import org.papervision3d.lights.PointLight3D;
7
	import org.papervision3d.materials.ColorMaterial;
8
	import org.papervision3d.materials.shadematerials.GouraudMaterial;
9
	import org.papervision3d.materials.WireframeMaterial;
10
	import org.papervision3d.objects.DisplayObject3D;
11
	import org.papervision3d.objects.primitives.Cylinder;
12
	import org.papervision3d.objects.primitives.Sphere;
13
	import org.papervision3d.view.BasicView;
14
15
	public class Helium3Atom extends BasicView
16
	{
17
		public function Helium3Atom ()
18
		{
19
20
		}
21
22
	}
23
24
}

Step 10: Instance Variables

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

1
2
private var _do3DArray:Array;//will hold all the references to all the eletrons and their rings

3
private var _easeOut:Number = .3;//easing strength when moving the camera

4
private var _reachX:Number = .1;//the lower this is set, the farther the reach towards it's x axis

5
private var _reachY:Number = .1;//same as reachX, but applies on the y axis

6
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.

7
private var _rotX:Number = 0.5;//the value used for the camera's rotation on the x axis

8
private var _rotY:Number = 0.5;//same as _rotX, but applies for the camera's y axis

9
private var _camPitch:Number = 0;//orbiting on the x axis calculated on the fly inside the onRenderTick () method

10
private var _camYaw:Number = 0;//orbiting on the y axis calculated on the fly inside the onRenderTick () method

11
private var _zDist:Number = 4;//controls the strength of zooming the camera in and out

12
private var _colorArray:Array = [0xCC490B, 0x26D965, 0xCC490B];//colors for the neutrons and protons inside the nucleus

13
private var _freeOrbit:Boolean = true;//switches the orbit mode of the camera

Step 11: The Constructor

Add the following lines inside the Constructor method.

1
2
if (stage) init ();
3
else addEventListener (Event.ADDED_TO_STAGE, init);
4
5
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:

1
2
private function init (e:Event = null):void
3
{
4
	removeEventListener (Event.ADDED_TO_STAGE, init);
5
6
	createAtom ();
7
}

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:

1
2
private function createAtom ():void
3
{
4
	_do3DArray = [];
5
6
	var light:PointLight3D = new PointLight3D;
7
	light.x = 300; light.y = 700; light.z = 0;
8
	scene.addChild (light)
9
10
	var atom:DisplayObject3D = new DisplayObject3D;
11
	scene.addChild (atom);
12
	
13
	var nucleus:DisplayObject3D = new DisplayObject3D;
14
	nucleus.rotationX = 90;
15
	atom.addChild (nucleus);
16
17
	var sphere:Sphere = new Sphere (null, 25, 1, 1);
18
	scene.addChild (sphere);
19
}

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.

1
2
for (var i:uint = 1; i < sphere.geometry.vertices.length-1; i++)
3
{
4
	var np:Sphere = new Sphere (new GouraudMaterial (light, _colorArray[i - 1], 0, 0), 23, 12, 9);
5
	np.x = sphere.geometry.vertices[i].x;
6
	np.y = sphere.geometry.vertices[i].y;
7
	np.z = sphere.geometry.vertices[i].z;
8
	
9
	nucleus.addChild (np);
10
}

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:

1
2
for (i = 0; i < 2; i++)
3
{
4
	var do3d:DisplayObject3D = new DisplayObject3D;
5
6
	var ring:Cylinder;
7
8
	var electron:Sphere;
9
	electron = new Sphere (new ColorMaterial (0xEFECCA, 1), 7);
10
	ring = new Cylinder (new WireframeMaterial (0xFFFFFF, .05, 2), 300, 1, 48, 1, -1, false, false);
11
	electron.x = 303;//add half size of electron

12
13
	ring.material.doubleSided = true;
14
15
	do3d.localRotationZ = 360 / 4 * i + 45;
16
	do3d.localRotationY = 360 * Math.random ();
17
18
	do3d.addChild (ring);
19
20
	do3d.addChild (electron);
21
22
	_do3DArray.push (do3d);
23
24
	atom.addChild (do3d);
25
}

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:

1
2
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:

1
2
private function onStageClick (e:MouseEvent):void
3
{
4
	_freeOrbit = ! _freeOrbit;
5
}

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.

1
2
override protected function onRenderTick (event:Event = null):void
3
{
4
	super.onRenderTick (event);
5
	for (var i:uint = 0; i < _do3DArray.length; i++)
6
	{
7
		_do3DArray[i].yaw (10);
8
	}
9
10
	var xDist:Number = mouseX - stage.stageWidth * .5;
11
	var yDist:Number = mouseY - stage.stageHeight * .5;
12
13
	if (_freeOrbit)
14
	{
15
		camera.x += (xDist - camera.x * _reachX) * _easeOut;
16
		camera.y += (yDist - camera.y * _reachY) * _easeOut;
17
		camera.z += (-mouseY * _zDist - camera.z) * _reachZ;
18
	}
19
	else
20
	{
21
		_camPitch += ((-yDist * _rotX) - _camPitch + 90) * _easeOut;
22
		_camYaw += ((xDist * _rotY) - _camYaw + 270) * _easeOut;
23
24
		if(_camPitch < 5) _camPitch = 5;
25
		if(_camPitch > 175) _camPitch = 175;
26
		camera.orbit (_camPitch, _camYaw);
27
	}
28
}

Step 19: Testing the Completed Class

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

1
2
package
3
{
4
	import flash.events.Event;
5
	import flash.events.MouseEvent;
6
	import org.papervision3d.lights.PointLight3D;
7
	import org.papervision3d.materials.ColorMaterial;
8
	import org.papervision3d.materials.shadematerials.GouraudMaterial;
9
	import org.papervision3d.materials.WireframeMaterial;
10
	import org.papervision3d.objects.DisplayObject3D;
11
	import org.papervision3d.objects.primitives.Cylinder;
12
	import org.papervision3d.objects.primitives.Sphere;
13
	import org.papervision3d.view.BasicView;
14
15
	public class Helium3Atom extends BasicView
16
	{
17
		private var _do3DArray:Array;
18
19
		private var _easeOut:Number = .3;
20
21
		private var _reachX:Number = .1;
22
		private var _reachY:Number = .1;
23
		private var _reachZ:Number = .5;
24
25
		private var _rotX:Number = 0.5;
26
		private var _rotY:Number = 0.5;
27
28
		private var _camPitch:Number = 0;
29
		private var _camYaw:Number = 0;
30
31
		private var _zDist:Number = 4;
32
33
		private var _colorArray:Array = [0xCC490B, 0x26D965, 0xCC490B];
34
 		private var _freeOrbit:Boolean = true;
35
36
		public function Helium3Atom ()
37
		{
38
			if (stage) init ();
39
			else addEventListener (Event.ADDED_TO_STAGE, init);
40
41
			startRendering ();
42
		}
43
44
		private function init (e:Event = null):void
45
		{
46
			removeEventListener (Event.ADDED_TO_STAGE, init);
47
			stage.addEventListener (MouseEvent.CLICK, onStageClick);
48
49
			createAtom ();
50
		}
51
52
		private function onStageClick (e:MouseEvent):void
53
		{
54
			_freeOrbit = ! _freeOrbit;
55
		}
56
57
		private function createAtom ():void
58
		{
59
			_do3DArray = [];
60
61
			var light:PointLight3D = new PointLight3D;
62
			light.x = 300; light.y = 700; light.z = 0;
63
			scene.addChild (light)
64
65
			var atom:DisplayObject3D = new DisplayObject3D;
66
			scene.addChild (atom);
67
68
			var nucleus:DisplayObject3D = new DisplayObject3D;
69
			nucleus.rotationX = 90;
70
			atom.addChild (nucleus);
71
72
			var sphere:Sphere = new Sphere (null, 25, 1, 1);//invisible guide

73
74
			for (var i:uint = 1; i < sphere.geometry.vertices.length-1; i++)
75
			{
76
				var np:Sphere = new Sphere (new GouraudMaterial (light, _colorArray[i - 1], 0, 0), 23, 12, 9);
77
				np.x = sphere.geometry.vertices[i].x;
78
				np.y = sphere.geometry.vertices[i].y;
79
				np.z = sphere.geometry.vertices[i].z;
80
81
				nucleus.addChild (np);
82
			}
83
84
			for (i = 0; i < 2; i++)
85
			{
86
				var do3d:DisplayObject3D = new DisplayObject3D;
87
88
				var ring:Cylinder;
89
90
				var electron:Sphere;
91
				electron = new Sphere (new ColorMaterial (0xEFECCA, 1), 7);
92
93
				ring = new Cylinder (new WireframeMaterial (0xFFFFFF, .05, 2), 300, 1, 48, 1, -1, false, false);
94
				electron.x = 303;//add half size of electron

95
96
				ring.material.doubleSided = true;
97
98
				do3d.localRotationZ = 360 / 4 * i + 45;
99
				do3d.localRotationY = 360 * Math.random ();
100
101
				do3d.addChild (ring);
102
103
				do3d.addChild (electron);
104
105
				_do3DArray.push (do3d);
106
107
				atom.addChild (do3d);
108
			}
109
		}
110
111
		override protected function onRenderTick (event:Event = null):void
112
		{
113
			super.onRenderTick (event);
114
115
			for (var i:uint = 0; i < _do3DArray.length; i++)
116
			{
117
				_do3DArray[i].yaw (10);
118
			}
119
120
			var xDist:Number = mouseX - stage.stageWidth * .5;
121
			var yDist:Number = mouseY - stage.stageHeight * .5;
122
123
			if (_freeOrbit)
124
			{
125
				camera.x += (xDist - camera.x * _reachX) * _easeOut;
126
				camera.y += (yDist - camera.y * _reachY) * _easeOut;
127
				camera.z += (-mouseY * _zDist - camera.z) * _reachZ;
128
			}
129
			else
130
			{
131
				_camPitch += ((-yDist * _rotX) - _camPitch + 90) * _easeOut;
132
				_camYaw += ((xDist * _rotY) - _camYaw + 270) * _easeOut;
133
134
				if(_camPitch < 5) _camPitch = 5;
135
				if(_camPitch > 175) _camPitch = 175;
136
				camera.orbit (_camPitch, _camYaw);
137
			}
138
		}
139
140
	}
141
}

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:

1
2
3
randomizeColorArray ();

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

1
2
private function randomizeColorArray ():void
3
{
4
	_colorArray = [];
5
6
	var tempArray:Array = [];
7
8
	for (var i:uint = 0; i < 12; i++)
9
	{
10
		if (i < 6) var color:uint = 0x004080;	//shade of blue

11
		else color = 0xA40000;	//shade of red

12
13
		tempArray.push (color);
14
	}
15
16
	while (tempArray.length > 6)
17
	{
18
		_colorArray.push (tempArray.splice (uint (Math.random () * tempArray.length), 1));
19
	}
20
	_colorArray = _colorArray.concat (tempArray);
21
}

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:

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

with:

1
2
if (i == 1 || i == 5)
3
{
4
	ring = new Cylinder (new WireframeMaterial (0xFFFFFF, .05, 2), 450, 1, 48, 1, -1, false, false);
5
	electron.x = 453;
6
}
7
else
8
{
9
	ring = new Cylinder (new WireframeMaterial (0xFFFFFF, .05, 2), 610, 1, 48, 1, -1, false, false);
10
	electron.x = 613;
11
}

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:

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

with:

1
2
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:

1
2
package
3
{
4
	import flash.display.StageQuality;
5
	import flash.events.Event;
6
	import flash.events.MouseEvent;
7
	import org.papervision3d.lights.PointLight3D;
8
	import org.papervision3d.materials.ColorMaterial;
9
	import org.papervision3d.materials.shadematerials.GouraudMaterial;
10
	import org.papervision3d.materials.WireframeMaterial;
11
	import org.papervision3d.objects.DisplayObject3D;
12
	import org.papervision3d.objects.primitives.Cylinder;
13
	import org.papervision3d.objects.primitives.Sphere;
14
	import org.papervision3d.view.BasicView;
15
	
16
	public class CarbonAtom extends BasicView
17
	{
18
		private var _do3DArray:Array;
19
20
		private var _easeOut:Number = .3;
21
22
		private var _reachX:Number = .1;
23
		private var _reachY:Number = .1;
24
		private var _reachZ:Number = .5;
25
26
		private var _rotX:Number = 0.5;
27
		private var _rotY:Number = 0.5;
28
29
		private var _camPitch:Number = 0;
30
		private var _camYaw:Number = 0;
31
32
		private var _zDist:Number = 4;
33
34
		private var _colorArray:Array;
35
		private var _freeOrbit:Boolean = true;
36
37
		public function CarbonAtom ()
38
		{
39
			if (stage) init ();
40
			else addEventListener (Event.ADDED_TO_STAGE, init);
41
42
			startRendering ();
43
		}
44
		private function init (e:Event = null):void
45
		{
46
			removeEventListener (Event.ADDED_TO_STAGE, init);
47
			stage.addEventListener (MouseEvent.CLICK, onStageClick);
48
49
			randomizeColorArray ();
50
51
			createAtom ();
52
		}
53
54
		private function onStageClick (e:MouseEvent):void
55
		{
56
			_freeOrbit = ! _freeOrbit;
57
		}
58
59
		private function randomizeColorArray ():void
60
		{
61
			_colorArray = [];
62
63
			var tempArray:Array = [];
64
65
			for (var i:uint = 0; i < 12; i++)
66
			{
67
				if (i < 6) var color:uint = 0x004080;
68
				else color = 0xA40000;
69
70
				tempArray.push (color);
71
			}
72
73
			while (tempArray.length > 6)
74
			{
75
				_colorArray.push (tempArray.splice (uint (Math.random () * tempArray.length), 1));
76
			}
77
			_colorArray = _colorArray.concat (tempArray);
78
		}
79
80
		private function createAtom ():void
81
		{
82
			_do3DArray = [];
83
84
			var light:PointLight3D = new PointLight3D;
85
			light.x = 300; light.y = 700; light.z = 0;
86
			scene.addChild (light)
87
88
			var atom:DisplayObject3D = new DisplayObject3D;
89
			scene.addChild (atom);
90
91
			var nucleus:DisplayObject3D = new DisplayObject3D;
92
			atom.addChild (nucleus);
93
94
			var sphere:Sphere = new Sphere (null, 60, 4, 4);
95
96
			for (var i:uint = 1; i < sphere.geometry.vertices.length-1; i++)
97
			{
98
				var np:Sphere = new Sphere (new GouraudMaterial (light, _colorArray[i - 1], 0, 0), 23);
99
				np.x = sphere.geometry.vertices[i].x;
100
				np.y = sphere.geometry.vertices[i].y;
101
				np.z = sphere.geometry.vertices[i].z;
102
103
				nucleus.addChild (np);
104
			}
105
106
			for (i = 0; i < 6; i++)
107
			{
108
				var do3d:DisplayObject3D = new DisplayObject3D;
109
110
				var ring:Cylinder;
111
112
				var electron:Sphere;
113
				electron = new Sphere (new ColorMaterial (0xEFECCA, 1), 7);
114
115
				if (i == 1 || i == 5)
116
				{
117
					ring = new Cylinder (new WireframeMaterial (0xFFFFFF, .05, 2), 450, 1, 48, 1, -1, false, false);
118
					electron.x = 453;
119
				}
120
				else
121
				{
122
					ring = new Cylinder (new WireframeMaterial (0xFFFFFF, .05, 2), 610, 1, 48, 1, -1, false, false);
123
					electron.x = 613;
124
				}
125
126
				ring.material.doubleSided = true;
127
128
				do3d.localRotationZ = 360 / 12 * i + 180;
129
				do3d.localRotationY = 360 * Math.random ();
130
131
				do3d.addChild (ring);
132
133
				do3d.addChild (electron);
134
135
				_do3DArray.push (do3d);
136
137
				atom.addChild (do3d);
138
			}
139
		}
140
141
		override protected function onRenderTick (event:Event = null):void
142
		{
143
			super.onRenderTick (event);
144
145
			for (var i:uint = 0; i < _do3DArray.length; i++)
146
			{
147
				_do3DArray[i].yaw (10);
148
			}
149
150
			var xDist:Number = mouseX - stage.stageWidth * .5;
151
			var yDist:Number = mouseY - stage.stageHeight * .5;
152
153
			if (_freeOrbit)
154
			{
155
				camera.x += (xDist - camera.x * _reachX) * _easeOut;
156
				camera.y += (yDist - camera.y * _reachY) * _easeOut;
157
				camera.z += (-mouseY * _zDist - camera.z) * _reachZ;
158
			}
159
			else
160
			{
161
				_camPitch += ((-yDist * _rotX) - _camPitch + 90) * _easeOut;
162
				_camYaw += ((xDist * _rotY) - _camYaw + 270) * _easeOut;
163
164
				if(_camPitch < 5) _camPitch = 5;
165
				if(_camPitch > 175) _camPitch = 175;
166
167
				camera.orbit (_camPitch, _camYaw);
168
			}
169
		}
170
171
	}

Open the Main document class and replace the code:

1
2
var helium3:Helium3Atom = new Helium3Atom;
3
addChild (helium3);

with:

1
2
var carbon:CarbonAtom = new CarbonAtom;
3
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!

Advertisement
Did you find this post useful?
Want a weekly email summary?
Subscribe below and we’ll send you a weekly email summary of all new Code tutorials. Never miss out on learning about the next big thing.
Advertisement
Looking for something to help kick start your next project?
Envato Market has a range of items for sale to help get you started.