Notes for Thursday April 16 class -- Mouse events and Catmull Rom paths

Mouse event handling

I added basic event handling into the library, by building a layer on top of the browser's native support layer for detecting mouse events.

The resulting API enables the application programmer (you) to attach callback methods to your canvas for down, drag, up and move events. The implementation follows:

addEventListenersToCanvas = function(canvas) {
   let toX = x => 2 * x / canvas.width - 1,
       toY = y => (canvas.height - 2 * y) / canvas.width;

   if (! canvas.onDrag   ) canvas.onDrag    = (x, y) => { };
   if (! canvas.onMove   ) canvas.onMove    = (x, y) => { };
   if (! canvas.onPress  ) canvas.onPress   = (x, y) => { };
   if (! canvas.onRelease) canvas.onRelease = (x, y) => { };

   canvas.addEventListener('mousemove', function(e) { this._response = this._isDown ? this.onDrag : this.onMove;
                                                      this._response(toX(e.clientX), toY(e.clientY));                      }, false);
   canvas.addEventListener('mousedown', function(e) { this.onPress  (toX(e.clientX), toY(e.clientY)); this._isDown = true ;}, false);
   canvas.addEventListener('mouseup'  , function(e) { this.onRelease(toX(e.clientX), toY(e.clientY)); this._isDown = false;}, false);
}
Once you have access to the mouse, you can create the ability for an artist to do many things with it, including draw curves, create, select or delete objects, move or otherwise transform objects in the scene, or change properties for an object such as color or texture.

The example we implemented in class rotates the user's view of the scene as the mouse is dragged. The implementation for this consisted of two parts. The first part uses the results of mouse dragging to generate two rotation values, rotX and rotY:

// COMPUTE VIEW ROTATION ANGLES WHEN USER DRAGS MOUSE.

let dragging, rotX = 0, rotY = 0, x0, y0;

let rotX=0, rotY=0, x0, y0;

canvas1.onPress = (x, y) => { x0 = x; y0 = y; }
canvas1.onDrag  = (x, y) => { rotX += x - x0; x0 = x;
                              rotY += y - y0; y0 = y; }
The second part is applying these two values to transform the root object just before drawing it:
root.identity()
    .rotateY(rotX)
    .rotateX(rotY);

root.draw();
Translation by a vector
Sometimes you want to just translate a matrix by a vector, instead of by three separate x,y,z arguments. To support this, we added a line at the start of the translate() method of class Matrix, so that the application programmer can simply pass in a single vector argument, rather than three separate floating point arguments. The added code is in blue:
   this.translate = function(x, y, z) {
      if (Array.isArray(x)) { z = x[2]; y = x[1]; x = x[0]; }
      let m = [1,0,0,0, 0,1,0,0, 0,0,1,0, x,y,z,1];  // (1) CREATE TRANSLATION MATRIX.
      value = multiply(value, m);                    // (2) MULTIPLY TO CHANGE OBJECT value.
      return this;
   }
Evaluating a Catmull Rom spline
To implement Catmull Rom splines, we first created a Catmull Rom basis matrix, to transform a sequence of four interpolating key points into the four coefficients of a cubic polynomial:
let crBasis = new Matrix();
crBasis.setValue([ -1/2,1,-1/2,0,   3/2,-5/2,0,1,   -3/2,2,1/2,0,   1/2,-1/2,0,0 ]);
Then we implemented a function that takes an array of key points as its first argument, and a parameter t as its second argument, which indicates how far the position the path we sample, where 0 ≤ t ≤ 1.
let evalCRSpline = (keys, t) => {
   t = Math.max(1/1000, Math.min(1-1/1000, t));

   let n = keys.length - 1,
       i = Math.floor(n * t),
       f = n * t % 1;

   let P0 = keys[i > 0 ? i-1 : V3.equal(keys[0], keys[n]) ? n-1 : 0];
       P1 = keys[i];
       P2 = keys[i+1];
       P3 = keys[i+1 < n ? i+2 : V3.equal(keys[0], keys[n]) ? 1 : n];

   let p = [];
   for (let k = 0 ; k < 3 ; k++) {
      let C = crBasis.transform([ P0[k], P1[k], P2[k], P3[k] ]);
      p.push( f*f*f*C[0] + f*f*C[1] + f*C[2] + C[3] );
   }
   return p;
}
Then in index.html we applied this to an example:
// SAMPLE A CATMULL ROM SPLINE PATH.

let P = [
   [-1, -1, -1],
   [-2/3, 1/3, 1],
   [ 1/3, -1/3, 1],
   [ 1,  1, -1],
   [-1, -1, -1],  // IF THE LAST KEY EQUALS THE FIRST, IT'S A LOOP.
];

for (let n = 0 ; n < P.length ; n++) // SHOW KEYS AS LARGE SPHERES.
   test.add(sphere, green_plastic)
       .translate(P[n])
       .scale(.13);

for (let n = 0 ; n <= 100 ; n++) {   // SHOW PATH AS SMALL SPHERES.
   let p = evalCRSpline(P, n/100);
   test.add(sphere, red_plastic)
       .translate(p)
       .scale(.05);
}
Link to cool video
We discussed the Utah Teapot, and Brandon pointed us to a link to a video about the Utah Teapot by Tom Scott.

You can find that video here.

hw8, due before the start of class, Thursday April 23
Starting with the code base we ended up with at the end of class, which is in hw8.zip, do the following:
  • Use the mouse to interact with your 3D scene in some interesting way. For example, you can try drawing a path to create a string of pearls. Or you can use the mouse to move or rotate individual objects that the mouse happens to be over. Or you can create a simple interactive game. There are plenty of possibilities.

  • Create an animation using the Catmull Rom spline. For example, you can use the result of evaluating the Catmull Rom spline to control the positoin or rotation angles over time of the limbs of a jointed animated character.

  • Do anything else you would like to do, for extra credit. You have a lot of tools now. Use them to make something awesome!