Using the mouse in O3D is a little trickier than catching key presses. In this tutorial I am going to use the mouse to rotate our cube.

The general principle of using the mouse for rotation, is the arcball. This allows you to transform the motion of the mouse into rotation matrices to rotate your scene.


The first thing to do though is add a few more libraries that we are going to need.

         o3djs.require('o3djs.quaternions');
         o3djs.require('o3djs.event');
         o3djs.require('o3djs.arcball');

and then declare a few more global variables. Because of the scrolling functionality, the camera is now a global variable instead of being declared directly

         var g_quaternions;
         var g_aball;
         var g_thisRot;
         var g_lastRot;
         var g_dragging = false;
         var g_camera = {
           eye: [0, 0, 5],
           target: [0, 0, 0]
         };

In the initialisation function we now add references to the quaternions library and put in the code to set up the arcball, which should be the same size as the client area, after which we set up the intial rotation matrices.

           g_quaternions = o3djs.quaternions;

           g_aball = o3djs.arcball.create(300, 300);

           g_lastRot = g_math.matrix4.identity();
           g_thisRot = g_math.matrix4.identity();

Lastly, in the initialisation, we add the mouse listeners.

           o3djs.event.addEventListener(g_o3dElement, 'mousedown',
                                                startDragging);
           o3djs.event.addEventListener(g_o3dElement, 'mousemove', drag);
           o3djs.event.addEventListener(g_o3dElement, 'mouseup', stopDragging);
           o3djs.event.addEventListener(g_o3dElement, 'wheel', scrollMe);

The startDragging() function sets up the start of the rotation, and starts the drag operation.

         function startDragging(e) {
           g_lastRot = g_thisRot;
           g_aball.click([e.x, e.y]);
           g_dragging = true;
         }

The drag() function performs the actual rotation. Using the movements of the mouse, it calculates the required rotation of the object, and transforms the matrices accordingly.

         function drag(e) {
           if (g_dragging) {
             var rotationQuat = g_aball.drag([e.x, e.y]);
             var rot_mat =
                  g_quaternions.quaternionToRotation(rotationQuat);
             g_thisRot = g_math.matrix4.mul(g_lastRot, rot_mat);

             var m = g_3dRoot.localMatrix;
             g_math.matrix4.setUpper3x3(m, g_thisRot);
             g_3dRoot.localMatrix = m;

           }
         }

The stopDragging() function stops the rotation when the user lets go of the mouse button.

         function stopDragging(e) {
           g_dragging = false;
         }

The scrollMe() function moves the location of the eye to the camera when the user scrolls the mouse, which gives the effect of zooming in and out of the scene.

         function scrollMe(e) {
           if (e.deltaY) {
             g_camera.eye =
               g_math.mulScalarVector((e.deltaY < 0 ? 11 : 13) / 12,
                                            g_camera.eye);
             g_viewInfo.drawContext.view =
                    g_math.matrix4.lookAt(g_camera.eye,
                                          g_camera.target,
                                          [0, 1, 0]);
          }
         }

Now, after this, the cube should be able to be rotated by using the mouse or keyboard.

<html>
   <head>
      <meta http-equiv="content-type" content="text/html; charset=UTF-8">
      <title>Tutorial 6: Introducing the mouse</title>
      <script type="text/javascript" src="o3djs/base.js"></script>
      <script type="text/javascript">
         o3djs.require('o3djs.util');
         o3djs.require('o3djs.math');
         o3djs.require('o3djs.rendergraph');
         o3djs.require('o3djs.canvas');
         o3djs.require('o3djs.quaternions');
         o3djs.require('o3djs.event');
         o3djs.require('o3djs.arcball');

         // Events
         // Run the init() function once the page has finished loading.
         // Run the uninit() function when the page has is unloaded.
         window.onload = init;
         window.onunload = uninit;

         // global variables
         var g_o3dElement;
         var g_o3d;
         var g_math;
         var g_client;
         var g_pack;
         var g_clock = 0;
         var g_timeMult = 1;
         var g_cubeTransform;
         var g_textCanvas;
         var g_paint;
         var g_canvasLib;
         var g_3dRoot;
         var g_hudRoot;
         var g_viewInfo;
         var g_hudViewInfo;
         var g_keyPressDelta = 0.05;

         var g_quaternions;
         var g_aball;
         var g_thisRot;
         var g_lastRot;
         var g_dragging = false;
         var g_camera = {
           eye: [0, 0, 5],
           target: [0, 0, 0]
         };

         function startDragging(e) {
           g_lastRot = g_thisRot;
           g_aball.click([e.x, e.y]);
           g_dragging = true;
         }

         function drag(e) {
           if (g_dragging) {
             var rotationQuat = g_aball.drag([e.x, e.y]);
             var rot_mat = g_quaternions.quaternionToRotation(rotationQuat);
             g_thisRot = g_math.matrix4.mul(g_lastRot, rot_mat);

             var m = g_3dRoot.localMatrix;
             g_math.matrix4.setUpper3x3(m, g_thisRot);
             g_3dRoot.localMatrix = m;

           }
         }

         function stopDragging(e) {
           g_dragging = false;
         }

         function scrollMe(e) {
           if (e.deltaY) {
             g_camera.eye =
                 g_math.mulScalarVector((e.deltaY < 0 ? 11 : 13) / 12,
                                                g_camera.eye);
             g_viewInfo.drawContext.view =
                            g_math.matrix4.lookAt(g_camera.eye,
                                                  g_camera.target,
                                                  [0, 1, 0]);
          }
         }         

         function createCube(material) {
           var cubeShape = g_pack.createObject('Shape');
           var cubePrimitive = g_pack.createObject('Primitive');
           var streamBank = g_pack.createObject('StreamBank');

           cubePrimitive.material = material;
           cubePrimitive.owner = cubeShape;
           cubePrimitive.streamBank = streamBank;

           cubePrimitive.primitiveType = g_o3d.Primitive.TRIANGLELIST;
           cubePrimitive.numberPrimitives = 12; // 12 triangles
           cubePrimitive.numberVertices = 8;    // 8 vertices in total

           var positionArray = [
             -0.5, -0.5,  0.5,  // vertex 0
              0.5, -0.5,  0.5,  // vertex 1
             -0.5,  0.5,  0.5,  // vertex 2
              0.5,  0.5,  0.5,  // vertex 3
             -0.5,  0.5, -0.5,  // vertex 4
              0.5,  0.5, -0.5,  // vertex 5
             -0.5, -0.5, -0.5,  // vertex 6
              0.5, -0.5, -0.5   // vertex 7
           ];

           var indicesArray = [
               0, 1, 2,  // face 1
               2, 1, 3,
               2, 3, 4,  // face 2
               4, 3, 5,
               4, 5, 6,  // face 3
               6, 5, 7,
               6, 7, 0,  // face 4
               0, 7, 1,
               1, 7, 3,  // face 5
               3, 7, 5,
               6, 0, 4,  // face 6
               4, 0, 2
           ];

           var positionsBuffer = g_pack.createObject('VertexBuffer');
           var positionsField = positionsBuffer.createField('FloatField', 3);
           positionsBuffer.set(positionArray);

           var indexBuffer = g_pack.createObject('IndexBuffer');
           indexBuffer.set(indicesArray);

           streamBank.setVertexStream(
               g_o3d.Stream.POSITION, // This stream stores vertex positions
               0,                     // First (and only) position stream
               positionsField,        // field: the field this stream uses.
               0);                    // start_index

           // Associate the triangle indices Buffer with the primitive.
           cubePrimitive.indexBuffer = indexBuffer;

           return cubeShape;
         }

         function drawText(str) {
           // Clear to completely transparent.
           g_textCanvas.canvas.clear([0.5, 0.5, 0.5, 0.5]);

           // Reuse the global paint object
           var paint = g_paint;
           paint.color = [1, 1, 1, 1];
           paint.textSize = 12;
           paint.textTypeface = 'Comic Sans MS';
           paint.textAlign = g_o3d.CanvasPaint.LEFT;
           paint.shader = null;
           g_textCanvas.canvas.drawText(str, 10, 30, paint);

           g_textCanvas.updateTexture();
        }         

         /**
          * This method gets called every time O3D renders a frame.  Here's
          * where we update the cube's transform to make it spin.
          * @param {o3d.RenderEvent} renderEvent The render event object that
          * gives us the elapsed time since the last time a frame was rendered.
          */
         function renderCallback(renderEvent) {
           g_clock += renderEvent.elapsedTime * g_timeMult;
           drawText("Hello world - " + (Math.round(g_clock * 100) / 100) + "s");
         }

         /**
          * Function performing the rotate action in response to a key-press.
          * Rotates the scene based on key pressed. (w ,s, a, d). Note that the
          * x,y-axis referenced here are relative to the current view of scene.
          * @param {keyPressed} The letter pressed, in lower case.
          * @param {delta} The angle by which the scene should be rotated.
          * @return true if an action was taken.
          */
         function keyPressedAction(keyPressed, delta) {
           var actionTaken = false;
           switch(keyPressed) {
             case 'a':

               g_3dRoot.localMatrix =
                   g_math.matrix4.mul(g_3dRoot.localMatrix,
                                      g_math.matrix4.rotationY(-delta));
               actionTaken = true;
               break;
             case 'd':
               g_3dRoot.localMatrix =
                   g_math.matrix4.mul(g_3dRoot.localMatrix,
                                      g_math.matrix4.rotationY(delta));
               actionTaken = true;
               break;
             case 'w':
               g_3dRoot.localMatrix =
                   g_math.matrix4.mul(g_3dRoot.localMatrix,
                                      g_math.matrix4.rotationX(-delta));
               actionTaken = true;
               break;
             case 's':
               g_3dRoot.localMatrix =
                   g_math.matrix4.mul(g_3dRoot.localMatrix,
                                      g_math.matrix4.rotationX(delta));
               actionTaken = true;
               break;
           }
           return actionTaken;
         }

         /**
          * Callback for the keypress event.
          * Invokes the action to be performed for the key pressed.
          * @param {event} keyPress event passed to us by javascript.
          */
         function keyPressedCallback(event) {
           event = event || window.event;

           // Ignore accelerator key messages.
           if (event.metaKey)
             return;

           var keyChar =String.fromCharCode(o3djs.event.getEventKeyChar(event));
           // Just in case they have capslock on.
           keyChar = keyChar.toLowerCase();

           if (keyPressedAction(keyChar, g_keyPressDelta)) {
             o3djs.event.cancel(event);
           }
         }         

         /**
          * Creates the client area.
          */
         function init() {
           o3djs.util.makeClients(initStep2);
         }

         /**
          * Initializes O3D.
          * @param {Array} clientElements Array of o3d object elements.
          */
         function initStep2(clientElements) {
           // Initializes global variables and libraries.
           g_o3dElement = clientElements[0];
           g_client = g_o3dElement.client;
           g_o3d = g_o3dElement.o3d;
           g_math = o3djs.math;
           g_quaternions = o3djs.quaternions;

           // Initialize O3D sample libraries.
           o3djs.base.init(g_o3dElement);

           // Create a pack to manage the objects created.
           g_pack = g_client.createPack();

           //Create the arcball which is used for the rotation
           g_aball = o3djs.arcball.create(300, 300);

           //Initialise rotation matrixes
           g_lastRot = g_math.matrix4.identity();
           g_thisRot = g_math.matrix4.identity();

           // Create 2 root transforms, one for the 3d parts and 2d parts.
           // This is not strictly neccassary but it is helpful.
           g_3dRoot = g_pack.createObject('Transform');
           g_hudRoot = g_pack.createObject('Transform');

           // Create the render graph for a view.
           g_viewInfo = o3djs.rendergraph.createBasicView(
               g_pack,
               g_3dRoot,
               g_client.renderGraphRoot);

           // Set the background color to black.
           g_viewInfo.clearBuffer.clearColor = [0, 0, 0, 1];

           // Create a second view for the hud.
           g_hudViewInfo = o3djs.rendergraph.createBasicView(
                g_pack,
                g_hudRoot,
                g_client.renderGraphRoot);

           // Make sure the hud gets drawn after the 3d stuff
           g_hudViewInfo.root.priority = g_viewInfo.root.priority + 1;

           // Turn off clearing the color for the hud since that would erase the
           // 3d parts but leave clearing the depth and stencil so the HUD is
           //  unaffected by anything done by the 3d parts.
           g_hudViewInfo.clearBuffer.clearColorFlag = false;

           // Set up a perspective view
           g_viewInfo.drawContext.projection = g_math.matrix4.perspective(
               g_math.degToRad(30), // 30 degree fov.
               g_client.width / g_client.height,
               1,                  // Near plane.
               5000);              // Far plane.

           // Set up our view transformation to look towards the world origin
           // where the cube is located.
           g_viewInfo.drawContext.view =
                      g_math.matrix4.lookAt(g_camera.eye, //eye
                                            g_camera.target,  // target
                                            [0, 1, 0]); // up

           //Set up the 2d orthographic view
           g_hudViewInfo.drawContext.projection = g_math.matrix4.orthographic(
              0 + 0.5,
              g_client.width + 0.5,
              g_client.height + 0.5,
              0 + 0.5,
              0.001,
              1000);

           g_hudViewInfo.drawContext.view = g_math.matrix4.lookAt(
              [0, 0, 1],   // eye
              [0, 0, 0],   // target
              [0, 1, 0]);  // up                                                     

           // Create an Effect object and initialize it using the shaders
           // from the text area.
           var redEffect = g_pack.createObject('Effect');
           var shaderString = document.getElementById('effect').value;
           redEffect.loadFromFXString(shaderString);

           // Create a Material for the mesh.
           var redMaterial = g_pack.createObject('Material');

           // Set the material's drawList.
           redMaterial.drawList = g_viewInfo.performanceDrawList;

           // Apply our effect to this material. The effect tells the 3D
           // hardware which shaders to use.
           redMaterial.effect = redEffect;

           // Create the Shape for the cube mesh and assign its material.
           var cubeShape = createCube(redMaterial);

           // Create a new transform and parent the Shape under it.
           g_cubeTransform = g_pack.createObject('Transform');
           g_cubeTransform.addShape(cubeShape);

           // Parent the cube's transform to the client root.
           g_cubeTransform.parent = g_3dRoot;

           // Generate the draw elements for the cube shape.
           cubeShape.createDrawElements(g_pack, null);

           // Create the global paint object that's used by draw operations.
           g_paint = g_pack.createObject('CanvasPaint');

           // Creates an instance of the canvas utilities library.
           g_canvasLib = o3djs.canvas.create(g_pack, g_hudRoot, g_hudViewInfo);

           // Create a canvas that will be used to display the text.
           g_textCanvas = g_canvasLib.createXYQuad(70, 70, 0, 100, 50, true);
           // Set our render callback for animation.
           // This sets a function to be executed every time frame is rendered.
           g_client.setRenderCallback(renderCallback);

           //Set up a callback to interpret keypresses
           window.document.onkeypress = keyPressedCallback;

           //Set up mouse events
           o3djs.event.addEventListener(g_o3dElement, 'mousedown',
                                                     startDragging);
           o3djs.event.addEventListener(g_o3dElement, 'mousemove', drag);
           o3djs.event.addEventListener(g_o3dElement, 'mouseup', stopDragging);
           o3djs.event.addEventListener(g_o3dElement, 'wheel', scrollMe);
         }

         /**
          * Removes callbacks so they aren't called after the page has unloaded.
          */
         function uninit() {
           if (g_client) {
             g_client.cleanup();
           }
         }

      </script>
   </head>
   <body>
      <h1>Tutorial 6: Introducing the mouse</h1>
      Clicking and dragging will rotate the cube. Using the scroll wheel will zoom in and out.
      <br/>

      <div id="o3d" style="width: 300px; height: 300px;"></div>
      <div style="display:none">
         <!-- Start of effect -->
         <textarea id="effect">
           // World View Projection matrix that will transform
           // the input vertices to screen space.
           float4x4 worldViewProjection : WorldViewProjection;

           // input parameters for our vertex shader
           struct VertexShaderInput {
             float4 position : POSITION;
           };

           // input parameters for our pixel shader
           struct PixelShaderInput {
             float4 position : POSITION;
           };

           /**
            * The vertex shader simply transforms input vertices to screen space
            */
           PixelShaderInput vertexShaderFunction(VertexShaderInput input) {
             PixelShaderInput output;

             // Multiply the vertex positions by the worldViewProjection
             // matrix to transform them to screen space.
             output.position = mul(input.position, worldViewProjection);
             return output;
           }

           /**
            * This pixel shader just returns the color red.
            */
           float4 pixelShaderFunction(PixelShaderInput input): COLOR {
             return float4(1, 0, 0, 1);  // Red.
           }

           // Here we tell our effect file *which* functions are
           // our vertex and pixel shaders.

           // #o3d VertexShaderEntryPoint vertexShaderFunction
           // #o3d PixelShaderEntryPoint pixelShaderFunction
           // #o3d MatrixLoadOrder RowMajor
         </textarea>
         <!-- End of effect -->
    </div>
  </body>
</html>
Share