10. Advanced Interactivity


Today we're going to learn how to handle more advanced interactivity. We'll be making something like this:

Click on a face of the cube to zoom into it. Click it again to make the cube spin again.

So, in this tutorial I'm going to show you exactly how to work out which material has been clicked on the cube and act accordingly.

If you haven't read it already, I strongly suggest that you read the Basic Interactivity tutorial first or you'll probably miss something.

Ok, so on to the code.

Firstly we'll want six materials to apply to each face of the cube. Here are mine, although you can create whatever materials you like:

Actionscript:
  1.       private var frontMaterial:BitmapFileMaterial = new BitmapFileMaterial("http://papervision2.com/wp-content/pvTex/front.jpg");
  2.        private var backMaterial:BitmapFileMaterial = new BitmapFileMaterial("http://papervision2.com/wp-content/pvTex/back.jpg");
  3.        private var leftMaterial:BitmapFileMaterial = new BitmapFileMaterial("http://papervision2.com/wp-content/pvTex/left.jpg");
  4.        private var rightMaterial:BitmapFileMaterial = new BitmapFileMaterial("http://papervision2.com/wp-content/pvTex/right.jpg");
  5.        private var topMaterial:BitmapFileMaterial = new BitmapFileMaterial("http://papervision2.com/wp-content/pvTex/top.jpg");
  6.        private var bottomMaterial:BitmapFileMaterial = new BitmapFileMaterial("http://papervision2.com/wp-content/pvTex/bottom.jpg");

So, in this code I'm just loading six images from my server to use as each face.

Also, add the following declaration beneath the texture declarations to hold our Cube object:

Actionscript:
  1. private var cube:Cube;

Now we need to set up the 3d initiation code. In this code we will firstly set all of our materials as interactive, and then give each of our materials a name. This is VERY important to be able to easily find out which material has been clicked. If we didn't give each material a name then we wouldn't be able to easily work out which face has been clicked.

So, add the following code to your init3d() function:

Actionscript:
  1.       override protected function init3d():void {
  2.          // We need to be able to identify each side. We'll do this
  3.          // by asssigning names to each material. During this process
  4.          // we'll also make the materials interactive.
  5.          frontMaterial.interactive = true;
  6.          frontMaterial.name = "front";
  7.          backMaterial.interactive = true;
  8.          backMaterial.name = "back";
  9.          leftMaterial.interactive = true;
  10.          leftMaterial.name = "left";
  11.          rightMaterial.interactive = true;
  12.          rightMaterial.name = "right";
  13.          topMaterial.interactive = true;
  14.          topMaterial.name = "top";
  15.          bottomMaterial.interactive = true;
  16.          bottomMaterial.name = "bottom";
  17.          // ---------------------------------------------
  18.          
  19.          cube = new Cube(new MaterialsList( {
  20.               front: frontMaterial,
  21.               back: backMaterial,
  22.               left: leftMaterial,
  23.               right: rightMaterial,
  24.               top: topMaterial,
  25.               bottom: bottomMaterial
  26.               } ), 500, 500, 500, 3, 3, 3);
  27.          cube.addEventListener( InteractiveScene3DEvent.OBJECT_PRESS, onPress);
  28.          default_scene.addChild(cube);
  29.        }

So, In this code, we've set each material as interactive, given each one a sensible name, and initialised our cube with the materials assigned to the correct faces.

We then add an event listener to trigger the "onPress" event when the cube is clicked, then finally we add the cube to the scene.

We've now got a cube with six materials on it listening for a click event.

Now, the code which will let us work out which face has been clicked:

Actionscript:
  1. private function onPress( e:InteractiveScene3DEvent ):void {
  2.     switch(e.face3d.material.name) {
  3.        case "front":
  4.          // This code will be run when the front face is clicked
  5.        break;
  6.        case "back":
  7.          // This code will be run when the back face is clicked
  8.        break;
  9.        case "left":
  10.          // This code will be run when the left face is clicked
  11.        break;
  12.        case "right":
  13.          // This code will be run when the right face is clicked
  14.        break;
  15.        case "top":
  16.          // This code will be run when the top face is clicked
  17.        break;
  18.        case "bottom":
  19.          // This code will be run when the bottom face is clicked
  20.        break;
  21.     }
  22. }

Pretty self explanitary. The "e" variable in this code holds lots of data about the click event, including which material has been clicked, so, because we know which material is on each face, we can tell by the materials name which face has been clicked!

With a little bit of code, you can make a nice spinning cube gallery like the example above:

Here is my final code, have fun!

Actionscript:
  1. package  {
  2.     
  3.     import flash.display.DisplayObject;
  4.     import org.papervision3d.materials.BitmapFileMaterial;
  5.     import org.papervision3d.materials.utils.MaterialsList;
  6.     import org.papervision3d.events.InteractiveScene3DEvent;
  7.     import org.papervision3d.objects.primitives.Cube;
  8.     
  9.     public class Main extends PaperBase {
  10.       
  11.        private var frontMaterial:BitmapFileMaterial = new BitmapFileMaterial("http://papervision2.com/wp-content/pvTex/front.jpg");
  12.        private var backMaterial:BitmapFileMaterial = new BitmapFileMaterial("http://papervision2.com/wp-content/pvTex/back.jpg");
  13.        private var leftMaterial:BitmapFileMaterial = new BitmapFileMaterial("http://papervision2.com/wp-content/pvTex/left.jpg");
  14.        private var rightMaterial:BitmapFileMaterial = new BitmapFileMaterial("http://papervision2.com/wp-content/pvTex/right.jpg");
  15.        private var topMaterial:BitmapFileMaterial = new BitmapFileMaterial("http://papervision2.com/wp-content/pvTex/top.jpg");
  16.        private var bottomMaterial:BitmapFileMaterial = new BitmapFileMaterial("http://papervision2.com/wp-content/pvTex/bottom.jpg");
  17.       
  18.        private var targetrotationX:Number = 0;
  19.        private var targetrotationY:Number = 0;
  20.        private var targetrotationZ:Number = 0;
  21.       
  22.        private var tweening:Boolean = false;
  23.       
  24.        private var cube:Cube;
  25.       
  26.        public function Main() {
  27.          init(400, 400);
  28.        }
  29.       
  30.        override protected function init3d():void {
  31.          // We need to be able to identify each side. We'll do this
  32.          // by asssigning names to each material. During this process
  33.          // we'll also make the materials interactive.
  34.          frontMaterial.interactive = true;
  35.          frontMaterial.name = "front";
  36.          backMaterial.interactive = true;
  37.          backMaterial.name = "back";
  38.          leftMaterial.interactive = true;
  39.          leftMaterial.name = "left";
  40.          rightMaterial.interactive = true;
  41.          rightMaterial.name = "right";
  42.          topMaterial.interactive = true;
  43.          topMaterial.name = "top";
  44.          bottomMaterial.interactive = true;
  45.          bottomMaterial.name = "bottom";
  46.          // ---------------------------------------------
  47.          
  48.          cube = new Cube(new MaterialsList( {
  49.               front: frontMaterial,
  50.               back: backMaterial,
  51.               left: leftMaterial,
  52.               right: rightMaterial,
  53.               top: topMaterial,
  54.               bottom: bottomMaterial
  55.               } ), 500, 500, 500, 3, 3, 3);
  56.          // Listen for the click:
  57.          cube.addEventListener( InteractiveScene3DEvent.OBJECT_PRESS, onPress);
  58.          // Add to scene:
  59.          default_scene.addChild(cube);
  60.        }
  61.       
  62.        private function onPress( e:InteractiveScene3DEvent ):void {
  63.          // If the cube has been moved to the front:
  64.          if (tweening) {
  65.               // Let it rotate again
  66.               tweening = false;
  67.          }else {
  68.               // Find which rotation we need to be able to see
  69.               // the face image:
  70.               switch(e.face3d.material.name) {
  71.                   case "front":
  72.                      targetrotationX = 0;
  73.                      targetrotationY = 180;
  74.                      targetrotationZ = 0;
  75.                      tweening = true;
  76.                   break;
  77.                   case "back":
  78.                      targetrotationX = 0;
  79.                      targetrotationY = 0;
  80.                      targetrotationZ = 0;
  81.                      tweening = true;
  82.                   break;
  83.                   case "left":
  84.                      targetrotationX = 0;
  85.                      targetrotationY = -90;
  86.                      targetrotationZ = 0;
  87.                      tweening = true;
  88.                   break;
  89.                   case "right":
  90.                      targetrotationX = 0;
  91.                      targetrotationY = 90;
  92.                      targetrotationZ = 0;
  93.                      tweening = true;
  94.                   break;
  95.                   case "top":
  96.                      targetrotationX = 90;
  97.                      targetrotationY = 0;
  98.                      targetrotationZ = 0;
  99.                      tweening = true;
  100.                   break;
  101.                   case "bottom":
  102.                      targetrotationX = -90;
  103.                      targetrotationY = 0;
  104.                      targetrotationZ = 180;
  105.                      tweening = true;
  106.                   break;
  107.               }
  108.          }
  109.        }
  110.       
  111.        override protected function processFrame():void {
  112.          if (tweening) {
  113.               // If a face has been clicked
  114.               if (default_camera.zoom <6.8) {
  115.                   // If the camera isn't zoomed enough then zoom in a bit more:
  116.                   default_camera.zoom += Math.sqrt(6.8-default_camera.zoom)/5;
  117.               }
  118.              
  119.               // Test each rotation and rotate it towards the target rotation:
  120.               // X axis:
  121.               if (cube.rotationX <targetrotationX) {
  122.                   cube.rotationX += Math.sqrt(targetrotationX-cube.rotationX);
  123.                   cube.rotationX = Math.round(cube.rotationX);
  124.               }else if (cube.rotationX> targetrotationX) {
  125.                   cube.rotationX -= Math.sqrt(cube.rotationX-targetrotationX);
  126.                   cube.rotationX = Math.round(cube.rotationX);
  127.               }
  128.               // Y axis:
  129.               if (cube.rotationY <targetrotationY) {
  130.                   cube.rotationY += Math.sqrt(targetrotationY-cube.rotationY);
  131.                   cube.rotationY = Math.round(cube.rotationY);
  132.               }else if (cube.rotationY> targetrotationY) {
  133.                   cube.rotationY -= Math.sqrt(cube.rotationY-targetrotationY);
  134.                   cube.rotationY = Math.round(cube.rotationY);
  135.               }
  136.               // Z axis:
  137.               if (cube.rotationZ <targetrotationZ) {
  138.                   cube.rotationZ += Math.sqrt(targetrotationZ-cube.rotationZ);
  139.                   cube.rotationZ = Math.round(cube.rotationZ);
  140.               }else if (cube.rotationZ> targetrotationZ) {
  141.                   cube.rotationZ -= Math.sqrt(cube.rotationZ-targetrotationZ);
  142.                   cube.rotationZ = Math.round(cube.rotationZ);
  143.               }
  144.          }else {
  145.               // If the camera is zoomed in, it shouldn't be now
  146.               if (default_camera.zoom> 2) {
  147.                   // So zoom out a bit.
  148.                   default_camera.zoom -= Math.sqrt(default_camera.zoom-2)/5;
  149.               }
  150.              
  151.               // Rotate the cube a bit:
  152.               cube.rotationX += 2;
  153.               cube.rotationY += 2;
  154.              
  155.               // Make sure that we dont "wind up" the rotation
  156.               if (cube.rotationX>= 360) cube.rotationX = 0;
  157.               if (cube.rotationY>= 360) cube.rotationY = 0;
  158.          }
  159.        }
  160.     }
  161. }



9. Basic Interactivity


In this tutorial, we're going to learn how to handle "interactive scene3d events", in particular the events which occur when your mouse moves over an object, out of an object and clicks an object. We're going to make something like this:

When you place your mouse over the plane, the material will change. When you click it, it will spin in the opposite direction.

Unfortunately we have to edit the base class.. again.. All we need to do is change one line so that the viewport becomes interactive when we initialise it.

Open up PaperBase.as, and find this line:

Actionscript:
  1. viewport = new Viewport3D(vpWidth, vpHeight);

Swap it for this line:

Actionscript:
  1. viewport = new Viewport3D(vpWidth, vpHeight, false, true);

This will make the viewport interactive, which means it can trigger events.

Now that you've updated the base class, create a new project. Make the Main class extend PaperBase as usual and then add the following imports:

Actionscript:
  1.    import org.papervision3d.materials.BitmapFileMaterial;
  2.     import org.papervision3d.objects.DisplayObject3D;
  3.     import org.papervision3d.events.InteractiveScene3DEvent;
  4.     import org.papervision3d.objects.primitives.Plane;

Now, add the following declarations under "public class Main extends PaperBase {" :

Actionscript:
  1.       private var ourMaterial:BitmapFileMaterial = new BitmapFileMaterial("http://papervision2.com/wp-content/pvTex/front.jpg");
  2.        private var ourOverMaterial:BitmapFileMaterial = new BitmapFileMaterial("http://papervision2.com/wp-content/pvTex/front_over.jpg");
  3.        private var yawspeed:Number = 5;
  4.        private var plane :DisplayObject3D;

ourMaterial is the material that we'll put onto the plane.
ourOverMaterial is the material that will be activated on mouse over.
We'll load both of these materials from files on my server.
yawspeed will be how much we're going to yaw() the plane by each frame
and "plane" will be the plane.

This next function will show you how to make the material interactive, and how to add an event listener to the stage:

Actionscript:
  1.       override protected function init3d():void {
  2.          ourMaterial.interactive = true; // You need to set the interactive property of the materal to true.
  3.          ourMaterial.doubleSided = true; // We want to be able to see both sides of the plane
  4.          ourOverMaterial.interactive = true; // Same for the mouseover material
  5.          ourOverMaterial.doubleSided = true;
  6.          
  7.          plane = new Plane(ourMaterial, 1000, 1000, 4, 4); // Create a new plane
  8.          default_scene.addChild(plane); // Add it to the scene
  9.          
  10.          // These lines add event listeners to the plane which will trigger the functions on the event specified.
  11.          plane.addEventListener( InteractiveScene3DEvent.OBJECT_PRESS, onPress ); // Will trigger "onPress" when the object is pressed (clicked)
  12.          plane.addEventListener( InteractiveScene3DEvent.OBJECT_OVER, onOver ); // Will trigger "onOver" when the mouse rolls over the object
  13.          plane.addEventListener( InteractiveScene3DEvent.OBJECT_OUT, onOut ); // Will trigger "onOut" when the mouse rolls out of the object
  14.        }

I've commented the code so it should be easy to understand.

Now we need to add the "on..." functions. You can do whatever you want in these functions, but in this tutorial we're going to make the effect like above.

Actionscript:
  1.       private function onOver ( e:InteractiveScene3DEvent ):void {
  2.          plane.material = ourOverMaterial; // Change the material to "ourOverMaterial"
  3.        }
  4.       
  5.        private function onOut ( e:InteractiveScene3DEvent ):void {
  6.          plane.material = ourMaterial; // Change the material back to "ourMaterial"
  7.        }
  8.       
  9.        private function onPress( e:InteractiveScene3DEvent ):void {
  10.          yawspeed *= -1; // Reverse the yaw speed
  11.        }

Finally, we need to add the processFrame code, to rotate the plane by the angle "yawspeed".

Actionscript:
  1.       override protected function processFrame():void {
  2.          plane.yaw(yawspeed);
  3.        }

Done! Run the project and you should see the result like above. Here's the complete code:

Actionscript:
  1. package  {
  2.     import org.papervision3d.materials.BitmapFileMaterial;
  3.     import org.papervision3d.objects.DisplayObject3D;
  4.     import org.papervision3d.events.InteractiveScene3DEvent;
  5.     import org.papervision3d.objects.primitives.Plane;
  6.     
  7.     public class Main extends PaperBase {
  8.       
  9.        private var ourMaterial:BitmapFileMaterial = new BitmapFileMaterial("http://papervision2.com/wp-content/pvTex/front.jpg");
  10.        private var ourOverMaterial:BitmapFileMaterial = new BitmapFileMaterial("http://papervision2.com/wp-content/pvTex/front_over.jpg");
  11.        private var yawspeed:Number = 5;