Notes for Monday Nov 9 and Wednesday Nov 11 classes -- Hit testing

Monday class

On Monday we introduced hit testing -- checking whether an object hits a specific object in the 3D scene.

The basic idea is to form a ray from the camera into the scene:

    V = [0,0,fl]
    W = normalize([mouseX,mouseY,-fl])
and then see which object that ray hits first. In class we implemented a simple version of this, which works only for a single sphere and doesn't take into account general matrix transformations.

If you want to ray trace to different shapes, such as a cylinder or a cube, then you need to solve for where the ray hits each component of that shape, and then take the intersection of those components.

 

Hit testing between a ray and a unit cylinder

For example, a unit cylinder has two components that need to be intersected: (1) tubular walls and (2) end caps.

First solve for the tubular walls of the unit cylinder:

To see if a ray hits a unit cylinder, we first find the segment along the ray (if any) where the ray hits the infinitely long tube that bounds the curved walls of the unit cylinder:

   x2 + y2 - 1 = 0
Then we intersect that with the slab between z ≥ -1 and z ≤ +1.

The intersection with the tube can be done by solving the quadratic equation:

(Vx + t Wx)2 + (Vy + t Wy)2 - 1 = 0
We can rewrite the above as:
( Wx • Wx + Wy • Wy ) t2 + ( 2 Vx • Wx + 2 Vy • Wy ) t + ( Vx • Vx + Vy • Vy - 1 ) = 0
which gives us the the coefficients A,B,C that we need to solve for t.

If there is no real solution to this quadratic equation, then we have missed the unit cylinder entirely.

If there is a real solution, then the two roots of the quadratic equation give us a range for t for the ray being in the tube: [tenterT ... texitT]

Then solve for the two end caps of the unit cylinder:

The end caps at z ≥ -1 and z ≤ 1 are just linear inequalities, so solving for t at both z = -1 and z = +1 is relatively simple:

Vz + t Wz = -1   →   t = (-1 - Vz) / Wz
Similarly:
Vz + t Wz ≤ +1   →   t = (+1 - Vz) / Wz
The smaller and larger of the above two values of t, respectively, give us the range of t [tenterS ... texitS] within the slab.

Finally, put together the results from the tube and the end caps:

We now just need to intersect the tube along the ray [tenterT ... texitT] with the slab along the ray [tenterS ... texitS].

We can do this by taking the maximum of the enter values, and the minimum of the exit values:

[ max(tenterT, tenterS)   ...   min(texitT, texitS) ]
If the first number is less than the second number, then the ray has hit the unit cylinder.

 

Hit testing between a ray and a unit cube

Similarly to the above, we can find out whether a ray has hit a unit cube by intersecting the three slabs:

x ≥ -1 and x ≤ +1
y ≥ -1 and y ≤ +1
z ≥ -1 and z ≤ +1
The trick is to do the following:
  1. take the maximum of the three values of t where the ray enters each slab;
  2. take the minimum of the three values of t where the ray exits each slab;
  3. see whether the first value of t is less than the second value of t.

 

At the end of class we watched the 2010 short film Augmented (hyper)Reality, a cautionary tale about augmented reality gone too far.

 

Wednesday class

On Wednesday we continued the implementation of hit testing. To do this, we needed to create a drawList, so that all objects would be drawn at the end of the frame, rather than immediately. This allows us to look at all transformed objects before they are rendered, in order to properly determine which (if any) is the nearest object at the cursor.

To create a drawList, we temporarily store each object to be rendered as follows:

  {
     mesh: mesh,
     matrix: m[mTop],
     rgb: rgb
  }
Keeping a drawArray would also be useful if we wanted to render a scene for virtual reality, since VR requires rendering the entire scene for two virtual cameras -- one for the left eye and then again for the right eye.

The trick to do hit testing by ray tracing to an object which has been transformed by a matrix is to invert the matrix, and then use that inverse matrix to invert both the ray origin V and the ray direction W.

Then we can ray trace from this transformed ray to an untransformed version of the object.

Transforming the ray by the inverse matrix looks like this:

   mInv = matrixInver(matrix);
   transformedRay = {
      V :           matrixTransform(mInv, ray.V.concat([1])).splice(0,3);
      W : normalize(matrixTransform(mInv, ray.W.concat([0])).splice(0,3));
   }
To do hit testing, we loop through all objects and one that the ray intersects at the smallest distance t.

Once we figured out what object we have hit, we can do many things with the result, including moving, highlighting or duplicating objects, or using the object at the cursor to set the properties of other objects (eg: as a color palette).

In class, we just set the color of the object red, and we only handled the case of hit testing for spheres.

At the end of class, we watched the groundbreaking 1994 CGI film Evolved Virtual Creatures by Karl Sims, the first CGI film to really show the power of creating animation via artificial intelligence.

FOR HOMEWORK:

Homework

Due next Wednesday before the start of class

Start with the included code in hw10.zip.

  • See if you can use the math in the notes above for Monday's class to do hit testing for cylinders and cubes.

  • See if you can implement reactive objects, buttons, etc.