Having created a scene last time with texture mapped objects, I’d now like to add some interaction so that we can rotate the camera around the scene and make the 3D objects react to mouse events. As you’ll see this is very straightforward with Away3D as each triangle drawn on the screen individually detects mouse events. For an equivalent tutorial in Papervision3D, check out First steps in Papervision3D : Part 5.
Previous articles summary :
- First steps in Away3D : Part 1 - Getting started
Creation of a new Away3D project within eclipse or Flex Builder 3 and a simple example of a 3D scene illustrating some basic Away3D classes. - First steps in Away3D : Part 2 - Animation
Adding some basic animation to the scene by modifying 3D object parameters when entering a frame. - First steps in Away3D : Part 3 - Texture mapping
Create materials using bitmap data with the aim of having more detailed or realistic objects.
For the scene interaction we’ll be looking at two different types of events. Firstly, the standard flash MouseEvent that we’ll use to rotate the camera when the user clicks on the stage and moves the mouse. Secondly we’ll look at the Away3D-specific event: the MouseEvent3D. This event is created for similar mouse actions on 3D objects but more importantly it contains the associated 3D object. To add MouseEvent3D listeners, all Object3D objects have the following functions for different mouse interactions:
- onMouseDown : called when the mouse button is pressed over the object
- onMouseUp : called when the mouse button is released over the object
- onMouseMove : called when the mouse moves over the object
- onMouseOver : called when the mouse enters the object
- onMouseOut : called when the mouse leaves the object
Another new feature for this tutorial is to add object animation using an external library: Tweener.
Without having to calculate and specify the change in positions (for example) of objects within the code, a tween does all the hard work for us. A tween works on a particular property of an object and at each frame of an animation modifies this property in a particular manner. You’ll find more information on tweens on the web, for example at wikipedia.
Tweener is an opensource library, compatible with Actionscript2 and Actionscript3 as well as JavaScript and HaXE. To install it in your Eclipse/Flex Builder 3 environment simply create a new Flex Library Project, download the sources from the Tweener homepage and extract them into the src directory. The source should be automatically compiled and you can either link your own ActionScript projects to this project or directly used the compiled library. If you need help on compiling new flex library projects in eclipse then you should find useful information on my post for installing and compiling Away3D in eclipse.
Lets take a look at the code. I’m following directly from the example in the last post but am simply adding the new mouse listeners.
package { import away3d.cameras.Camera3D; import away3d.containers.ObjectContainer3D; import away3d.containers.Scene3D; import away3d.containers.View3D; import away3d.core.base.Object3D; import away3d.core.math.Number3D; import away3d.core.utils.Cast; import away3d.events.MouseEvent3D; import away3d.materials.BitmapMaterial; import away3d.materials.TransformBitmapMaterial; import away3d.primitives.Cube; import away3d.primitives.Cylinder; import away3d.primitives.Sphere; import away3d.primitives.Torus; import caurina.transitions.Tweener; import flash.display.Bitmap; import flash.display.Sprite; import flash.display.StageAlign; import flash.display.StageScaleMode; import flash.events.Event; import flash.events.MouseEvent; [SWF(backgroundColor="#000000")] public class Example004 extends Sprite { [Embed(source="/../assets/earth.jpg")] private var EarthImage:Class; private var earthBitmap:Bitmap = new EarthImage(); [Embed(source="/../assets/away3D.png")] private var Away3DImage:Class; private var away3DBitmap:Bitmap = new Away3DImage(); [Embed(source="/../assets/checker.jpg")] private var CheckerImage:Class; private var checkerBitmap:Bitmap = new CheckerImage(); private static const ORBITAL_RADIUS:Number = 150; private static const CAMERA_ORBIT:Number = 600; private var scene:Scene3D; private var camera:Camera3D; private var view:View3D; private var group:ObjectContainer3D; private var sphere:Sphere; private var cube:Cube; private var centerCube:Cube; private var cylinder:Cylinder; private var torus:Torus; private var doRotation:Boolean = false; private var lastMouseX:int; private var lastMouseY:int; private var cameraPitch:Number = 60; private var cameraYaw:Number = -60; public function Example004() { // set up the stage stage.align = StageAlign.TOP_LEFT; stage.scaleMode = StageScaleMode.NO_SCALE; // Add resize event listener stage.addEventListener(Event.RESIZE, onResize); // Listen to mouse up and down events on the stage stage.addEventListener(MouseEvent.MOUSE_DOWN, onMouseDown); stage.addEventListener(MouseEvent.MOUSE_UP, onMouseUp); // Initialise Papervision3D init3D(); // Create the 3D objects createScene(); // Initialise frame-enter loop this.addEventListener(Event.ENTER_FRAME, loop); } private function init3D():void { // Create a new scene where all the 3D object will be rendered scene = new Scene3D(); // Create a new camera, passing some initialisation parameters camera = new Camera3D({zoom:25, focus:30}); setCameraPosition(); // Create a new view that encapsulates the scene and the camera view = new View3D({scene:scene, camera:camera}); // center the viewport to the middle of the stage view.x = stage.stageWidth / 2; view.y = stage.stageHeight / 2; addChild(view); } private function createScene():void { // Create an object container to group the objects on the scene group = new ObjectContainer3D(); scene.addChild(group); // Create a new sphere object using a bitmap material representing the earth var earthMaterial:BitmapMaterial = new BitmapMaterial(Cast.bitmap(earthBitmap)); sphere = new Sphere({material:earthMaterial, radius:50, segmentsW:10, segmentsH:10}); sphere.x = ORBITAL_RADIUS; group.addChild(sphere); // Create a new cube object using a tiled bitmap material var tiledAway3DMaterial:TransformBitmapMaterial = new TransformBitmapMaterial(Cast.bitmap(away3DBitmap), {repeat:true, scaleX:.5, scaleY:.5}); cube = new Cube({material:tiledAway3DMaterial, width:75, height:75, depth:75}); cube.z = -ORBITAL_RADIUS; group.addChild(cube); // Create a cylinder mapping the earth data again cylinder = new Cylinder({material:earthMaterial, radius:25, height:100, segmentsW:16}); cylinder.x = -ORBITAL_RADIUS; group.addChild(cylinder); // Create a torus object and use a checkered bitmap material var checkerBitmapMaterial:BitmapMaterial = new BitmapMaterial(Cast.bitmap(checkerBitmap)); torus = new Torus({material:checkerBitmapMaterial, radius:40, tube:10, segmentsT:8, segmentsR:16}); torus.z = ORBITAL_RADIUS; group.addChild(torus); // Create a new cube object using a smoothed, precise bitmap material var away3DMaterial:BitmapMaterial = new BitmapMaterial(Cast.bitmap(away3DBitmap), {smooth:true, precision:2}); centerCube = new Cube({material:away3DMaterial, width:75, height:75, depth:75}); group.addChild(centerCube); // add mouse listeners to all the 3D objects sphere.addOnMouseDown(onMouseDownOnObject); cube.addOnMouseDown(onMouseDownOnObject); cylinder.addOnMouseDown(onMouseDownOnObject); torus.addOnMouseDown(onMouseDownOnObject); centerCube.addOnMouseDown(onMouseDownOnObject); } private function loop(event:Event):void { // rotate the group of objects group.yaw(2); // rotate the objects sphere.yaw(-4); cube.yaw(-4); cylinder.yaw(-4); torus.yaw(-4); // update the camera position updateCamera(); // Render the 3D scene view.render(); } // updates the camera position private function updateCamera():void { // If the mouse button has been clicked then update the camera position if (doRotation) { // convert the change in mouse position into a change in camera angle var dPitch:Number = (mouseY - lastMouseY) / 2; var dYaw:Number = (mouseX - lastMouseX) / 2; // update the camera angles cameraPitch -= dPitch; cameraYaw -= dYaw; // limit the pitch of the camera if (cameraPitch <= 0) { cameraPitch = 0.1; } else if (cameraPitch >= 180) { cameraPitch = 179.9; } // reset the last mouse position lastMouseX = mouseX; lastMouseY = mouseY; // reposition the camera setCameraPosition(); } } // sets the camera position given pitch and yaw angles private function setCameraPosition():void { camera.y = CAMERA_ORBIT * Math.cos(cameraPitch * Math.PI / 180); camera.x = CAMERA_ORBIT * Math.sin(cameraPitch * Math.PI / 180) * Math.cos(cameraYaw * Math.PI / 180); camera.z = CAMERA_ORBIT * Math.sin(cameraPitch * Math.PI / 180) * Math.sin(cameraYaw * Math.PI / 180); // keep the camera looking at the origin camera.lookAt(new Number3D(0, 0, 0)); } // called when mouse down on stage private function onMouseDown(event:MouseEvent):void { doRotation = true; lastMouseX = event.stageX; lastMouseY = event.stageY; } // called when mouse up on stage private function onMouseUp(event:MouseEvent):void { doRotation = false; } // called when mouse down on a 3D object private function onMouseDownOnObject(event:MouseEvent3D):void { var object:Object3D = event.object; Tweener.addTween(object, {y:200, time:1, transition:"easeOutSine", onComplete:function():void {goBack(object);} }); } // called when a tween created in onMouseDownOnObject has terminated private function goBack(object:Object3D):void { Tweener.addTween(object, {y:0, time:2, transition:"easeOutBounce"}); } // called when the window is resized private function onResize(event:Event):void { view.x = stage.stageWidth / 2; view.y = stage.stageHeight / 2; } } }
The example embeds the same images as the last time so if you need them you can find them in my last post. Don’t forget that you’ll need to link to the Tweener library for this to compile correctly. The resulting Flash movie should be as below - click on the image below to load it. Now when you click on the background and move the mouse you should be able to rotate the scene (effectively moving the camera) and clicking on each object should make them bounce.
First of all, lets have a look how the we add the standard Flash MouseEvent to rotate the camera.
Starting with the constructor we add two listeners.
// Listen to mouse up and down events on the stage stage.addEventListener(MouseEvent.MOUSE_DOWN, onMouseDown); stage.addEventListener(MouseEvent.MOUSE_UP, onMouseUp);
When a mouse click is detected on the stage the function onMouseDown is called. When the button is released onMouseUp is called. These functions indicate when the camera movement should start and stop.
// called when mouse down on stage private function onMouseDown(event:MouseEvent):void { doRotation = true; lastMouseX = event.stageX; lastMouseY = event.stageY; } // called when mouse up on stage private function onMouseUp(event:MouseEvent):void { doRotation = false; }
To start the camera movement we set the boolean doRotation to true and to stop it we set it to false. The amount of rotation depends on how far the mouse moves during a single frame so we store the position of the mouse at every frame as well. The camera is then updated during the loop function
private function loop(event:Event):void { // rotate the group of objects group.yaw(2); // rotate the objects sphere.yaw(-4); cube.yaw(-4); cylinder.yaw(-4); torus.yaw(-4); // update the camera position updateCamera(); // Render the 3D scene view.render(); }
Other than the call to updateCamera the rendering remains identical to the last example. The camera position is then calculated as follows.
// updates the camera position private function updateCamera():void { // If the mouse button has been clicked then update the camera position if (doRotation) { // convert the change in mouse position into a change in camera angle var dPitch:Number = (mouseY - lastMouseY) / 2; var dYaw:Number = (mouseX - lastMouseX) / 2; // update the camera angles cameraPitch -= dPitch; cameraYaw -= dYaw; // limit the pitch of the camera if (cameraPitch <= 0) { cameraPitch = 0.1; } else if (cameraPitch >= 180) { cameraPitch = 179.9; } // reset the last mouse position lastMouseX = mouseX; lastMouseY = mouseY; // reposition the camera setCameraPosition(); } }
From the change in mouse position we calculate (arbitrarily) a change in angle from the y axis (pitch) and similarly around the y axis (yaw). The pitch is kept within limits to avoid the camera from going over the poles and hence changing the up direction of the camera. Once we’ve calculated the new values of pitch and yaw for the camera position we can convert these angles into x, y and z positions. This is done in setCameraPosition.
// sets the camera position given pitch and yaw angles private function setCameraPosition():void { camera.y = CAMERA_ORBIT * Math.cos(cameraPitch * Math.PI / 180); camera.x = CAMERA_ORBIT * Math.sin(cameraPitch * Math.PI / 180) * Math.cos(cameraYaw * Math.PI / 180); camera.z = CAMERA_ORBIT * Math.sin(cameraPitch * Math.PI / 180) * Math.sin(cameraYaw * Math.PI / 180); // keep the camera looking at the origin camera.lookAt(new Number3D(0, 0, 0)); }
Note that we keep the camera at all times looking at the origin.
Moving on to the interaction with the 3D objects using the MouseEvent3D event, for this example I’m just going to look at the events when the mouse is clicked over an object. You’ll see that adding the listeners is very simple: at the end of the createScene a listener is added to each object.
// add mouse listeners to all the 3D objects sphere.addOnMouseDown(onMouseDownOnObject); cube.addOnMouseDown(onMouseDownOnObject); cylinder.addOnMouseDown(onMouseDownOnObject); torus.addOnMouseDown(onMouseDownOnObject); centerCube.addOnMouseDown(onMouseDownOnObject);
The rest of createScene is identical to the previous example. Now, whenever a user clicks on these objects, the function onMouseDownOnObject is called.
// called when mouse down on a 3D object private function onMouseDownOnObject(event:MouseEvent3D):void { var object:Object3D = event.object; Tweener.addTween(object, {y:200, time:1, transition:"easeOutSine", onComplete:function():void {goBack(object);} }); }
The listener is called with the MouseEvent3D passed as an argument. From this we can easily obtain the Object3D for which the event occurred. We then invoke Tweener to perform an animation on the object.
Tweener contains static methods that allow us to modify object properties. In this case we simply modify the y position of the object so that it goes to a value of 200 in 1 second using the easeOutSine transition function. I’d recommend looking at the Tweener documentation on the different transition functions. The onComplete argument allows us to put the sphere back to its original position when the tween has completed.
// called when a tween created in onMouseDownOnObject has terminated private function goBack(object:Object3D):void { Tweener.addTween(object, {y:0, time:2, transition:"easeOutBounce"}); }
This then uses another tween with an easeOutBounce transition to move the sphere back to the x-z plane.
And that’s all there is to it! With Tweener you can modify a number of different properties in parallel. For example you can change y and scale the object at the same time. The interface provided by Away3D is in any case very easy to use. Making a comparison to Papervision3D I find it a bit easier: with Papervision3D we need to say that the say that each individual material is interactive, as well as the View itself. This is surely to produce some performance optimisation. Other than this the interaction with objects is very similar for both engines and, I hope you’ll find, is straightforward to implement!
Next article:
- First steps in Away3D : Part 5 - Lighting and shading
A light source is added to the scene and the materials are changed to illustrate how Away3D renders objects with different shading characteristics.




















