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:
- take the maximum of the three values of t where the ray enters each slab;
- take the minimum of the three values of t where the ray exits each slab;
- 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.