Raffi Kasparian <quantime2007@cox.net> has been programming on the Macintosh as a hobby since 1990 when he bought his first Mac (an SE). Languages include Pascal, C, C++ and Java. He won the Mactech Programmers' Challenge in July 1993 and again in May 1995. He holds his degrees in music (piano performance) and earns his living as a classical pianist. 
Figure 1. Side View of Window and 3D Object
This situation is analogous to a computer drawing 3D images onto a 2D video screen. Before we proceed, please take a moment to familiarize yourself with the orientation of the 3D axes with respect to a computer screen (Figure 2) . When looking straight at the screen, the xaxis is horizontal and increases to the right, the yaxis is the depth axis and increases as it moves away from the viewer, and the zaxis is vertical and increases as it moves up. Be sure to contrast this with the standard computer screen 2D axes (Figure 3) where the xaxis is horizontal and increases to the right, the yaxis is vertical and increases downwards and the origin (0, 0) is the top left corner of the screen.
Figure 2. 3D Axes When Looking Straight at the Screen
Figure 3. Local 2D Axes When Looking Straight at the Screen
Figure 4 below is a slight modification of Figure 1. In it, the window has been replaced by a video screen and the house is now a virtual 3D object. We are looking at the screen from its side which puts the real world to its left and the virtual world to its right. The horizontal line is our depth or yaxis and the vertical line is our height or zaxis. We ignore the width axis (x) for the moment which would be the distance away from this very page you are reading. For convenience, we place our screen on the y = 0 plane and our viewer on the yaxis. Point v represents the viewer's eye, p is a point on the 3D image and p' is the point on the screen where the computer will draw p. Points behind the screen have positive ycoordinates while points in front of the screen have negative ycoordinates. So, for example, vy is negative, py is positive and p'y = 0. Figure 5 is a view of the same situation from above allowing us to see the xaxis.
Figure 4. Side View of Video Screen and Virtual 3D Object
Figure 5. Aerial View of Video Screen and Virtual 3D Object
To map a 3D point p on the house to a 2D point p' on the screen, we imagine a straight line from point p to the user's eye. Point p' is located on the screen at the intersection of this virtual sight line and the screen. As a matter of fact, all 3D points located on this sight line are mapped to the one 2D point where this sight line intersects the screen. Some computer programs incorrectly assume that sight lines are parallel to the depth axis resulting in an incorrect mapping of 3D points to screen points.
p'_{x}/p_{x} = v_{y}/(v_{y}  p_{y}) From the law of similar triangles. (Refer to Figure 5.)We have now computed the 3D coordinates of p' (p'_{x}, p'_{y}, p'_{z}), but further transformation is required to put our point p' into local screen coordinates. Since the computer's 2D origin is the top left corner of the screen and since its vertical axis increases downwards,
p'_{x} = p_{x}v_{y}/(v_{y}  p_{y}) Solving for p'_{x}.
p'_{y} = 0 By definition.
p'_{z}/p_{z} = v_{y}/(v_{y}  p_{y}) From the law of similar triangles. (Refer to Figure 4.)
p'_{z} = p_{z}v_{y}/(v_{y}  p_{y}) Solving for p'_{z}.
p'_{screenx} = p'_{x} + originx and p'_{screeny} = p'_{z} + originy.The conversion from 3D coordinates to 2D screen coordinates is implemented by SpacePoint.toScreenCoord() which is called for each point of the object whenever the object has been rotated and needs to be redrawn.
SpacePoint.toScreenCoord
class OrderedTriple{//a class to represent a 3D coordinate
static OrderedTriple origin = new OrderedTriple(0, 0, 0);
public OrderedTriple(double x, double y, double z){
//3D coordinate with the ability to map to the 2D screen
//the 2D screen coordinates of the 3D origin which happens to
be located on the screen
//the 3D coordinates of the person viewing the screen
//Convert a 3D coordinate to its 2D screen coordinates

Figure 6. A 3D Virtual Clock Before and After Rotation
To answer this question let's consider the 3D image of a clock (Figure 6). In Frame 1, the clock's back is facing the user and the user's goal is to rotate the clock to its normal position. First, he drags in a straight line from a to b as depicted in frame 1. As expected, the clock turns over to face the user (Frame 2). He then notices that the clock is upside down. If he drags in a straight line from b back to a exactly reversing his path, we would expect the clock to return to its original position. If however, he drags in a semicircle from b to a as depicted in Frame 2 we would expect the clock to rotate counterclockwise to the position in Frame 3. Some computer programs would bring the clock back to its original orientation in both cases reasoning that the cursor returned to its starting position and it doesn't matter where it went in between. In a 2D world this logic would be correct but it is obviously undesirable in the 3D world. We therefore conclude that an object's position after rotation depends on the final position of the cursor as well as the path it took to get there.
Figure 7. How the Mouse's Path is Approximated
DrawingCanvas.mouseDown
//A drawing surface that knows how to draw 3D objects
//A DrawingCanvas class member that remembers the last
3D coordinates of the
//When a mousedown occurs, find out and remember what point on
the object's
//A DrawingCanvas class member whose implementation is left up
to the individual
//Calculate what 3D point on the object's surface corresponds
to the screen coordinates
//We will return surfacePoint
//Set cursorPoint to the 3D coordinates of the cursor
for every face f of theSolid{
//We'll talk about this later
OrderedTriple.sectPlaneLine //Find the 3D coordinates of the intersection of the plane containing
points P1, P2, P3
//Two distinct points on the line
class SmartPolygon{ //Determine whether or not a point is inside the polygon based
on the reasoning that
int prevdir = 0, postdir = 0, crossings = 0;
start++; //for every edge (p1x, p1y) > (p2x, p2y)
if(p1x == p2x && p1y == p2y) continue;//that
is real
//and x is between the endpoints
//and continues in the same direction as the edge
ending on y and crosses y to

DrawingCanvas.mouseDrag
//A DrawingCanvas class member that remembers the distance of
lastCoord from the
//When the cursor has been dragged, calculate where the
selected point on the object's
//map the new cursor location to a 3D point on the "sphere of
influence"
rotateTheSolid(lastCoord, newCoord);//We'll talk about
this later
DrawingCanvas.toSphericalCoord //A DrawingCanvas class member. True if the cursor is mapped
to a point on the back
//Calculate the 3D point on the surface of a sphere with radius
R and center at the
//3D coordinates of the cursor
//intersect the line from the viewer to the cursor with the sphere
//If the line doesn't intersect the sphere then return the point
on the largest visible
}else{
//Find the intersection(s) of the sphere with radius length r
and center at point c and
OrderedTriple v = b.minus(a);
try{
OrderedTriple.solveQuadratic //For the quadratic equation ax2 + bx + c = 0 return (b ±
*(b2  4ac)) / 2a
double roots[];
double d = Math.sqrt(d2); double r1 = (b + d)/(2*a);

Premise 1. The location and orientation of an object are completely determined from the location of any three distinct noncollinear points on it or in its space.Premise 2. If an object has been rotated, the prerotation and postrotation coordinates of any three distinct noncollinear points p1, p2 and p3 on the object or in its space are sufficient to determine the precise rotation of the object.
prerotation > postrotationLet p1 be the point on the surface of the object that the user manipulates with the mouse. Its pre and postrotation coordinates a and d were determined in the previous section.
p1: (a_{x}, a_{y}, a_{z}) > (d_{x}, d_{y}, d_{z})
p2: (b_{x}, b_{y}, b_{z}) > (e_{x}, e_{y}, e_{z})
p3: (c_{x}, c_{y}, c_{z}) > (f_{x}, f_{y}, f_{z})
Figure 8. Graphic View of the Parameters of Rotation
Premise 3. For any desired rotation of an object about the origin, there is a unique 3X3 matrix that can multiply the prerotation coordinates of every point to yield its postrotation coordinates.Matrix multiplication is a common way for a computer program to rotate a 3D object. One simply multiplies the prerotation coordinates of every one of the object's vertices by a matrix to yield its postrotation coordinates. From Premises 1, 2 and 3, it follows that if we can find one 3X3 matrix M that transforms the prerotation coordinates of p1, p2 and p3 to their respective postrotation coordinates, then M will be the desired unique rotation matrix. Here is the logic that will enable us to find M from the pre and postrotation coordinates of p1, p2 and p3:
M's desired properties:Unfortunately, not all matrices have unique inverses so we must be careful in choosing p1, p2 and p3 to guarantee the existence of a unique inverse of P. Since the existence of M is predicated on the rotation being around the origin, we can't consider the origin in our computations. Nor can we consider any pair of points that is collinear with the origin. So, p1 and p2 are useful but we must choose a p3 other than the origin. Since any point that is in a definable position with respect to p1 and p2's prerotation coordinates will, after rotation, be in that same definable position with respect to their postrotation coordinates, we'll set p3 to p1 X p2. This sets p3's prerotation coordinates c to a X b and its postrotation coordinates f to d X e. Now that we have calculated a, b, c, d, e, and f, we can solve for M.
M(a_{x}, a_{y}, a_{z}) = (d_{x}, d_{y}, d_{z})
M(b_{x}, b_{y}, b_{z}) = (e_{x}, e_{y}, e_{z})
M(c_{x}, c_{y}, c_{z}) = (f_{x}, f_{y}, f_{z})
All variables except M are known, so we simply solve for M:
MP = N by substitution into M's desired properties
MPP^{1} = NP^{1} multiplying both sides of the equation by the inverse of P
MI = NP^{1} since any matrix times its inverse equals the identity matrix
M = NP^{1} since any matrix times the identity matrix equals itself
Quick3X3Matrix.findRotationMatrix
//A streamlined matrix specializing in rotating 3D coordinates about
the origin
//find a matrix M that will rotate the coordinate space around
the origin to bring p1 to
//Find the pre and postrotation coordinates of a second point.
We will choose a
//Find the pre and postrotation coordinates of a third point.
//Use Matrix algebra to determine the rotation matrix

Having determined the correct rotation matrix, we may now rotate the
solid.
DrawingCanvas.rotateTheSolid
//Rotate the solid from lastCoord to newCoord according to the
user's expectations.
Quick3X3Matrix M = Quick3X3Matrix.findRotationMatrix(
//rotate theSolid
Quick3X3Matrix.times //A streamlined method that treats an OrderedTriple like a matrix
to multiply it.

Figure 9. Correcting an OutOfBounds Mouse Path
Figure 10. The Largest Visible Cross Section
If we consider Figure 11, we can calculate the radius length r and the center point d of the largest visible cross section. The sphere has radius length R and center at the origin. Point v is the viewer. R and v are known.
Figure 11. Determining the Largest Visible Cross Section
d_{y}/R = R/v_{y} law of similar trianglesThe largest visible cross section of the sphere is a circle on the plane y = dy with center at (0, dy, 0) and radius of r.
d_{y} = R^{2}/v_{y} solving for d
d_{y}^{2} + r^{2} = R^{2} Pythagorean theorem
r = *(R^{2}  d_{y}^{2}) solving for r
DrawingCanvas.outOfBoundsMouseDrag
//A DrawingCanvas class member. True if the cursor directly
maps to a point on the
//Place the cursor on the nearest point on the largest visible
cross section.
//the radius of the largest visible cross section
double m = cursorPoint.length();//cursor's distance from the yaxis //what we will return as the cursor's 3D location
inBoundsPoint = cursorPoint.times(r/m);

Code Excerpt 1
//Determine whether the selected point is in back or in front
of the largest visible cross
backSide = surfacePoint.y > d;

Thereafter, while the user is rotating the solid we keep the cursor
on that same side of the sphere of influence. In other words, if
the cursor was on the front side, we pick the intersection on the front
side and if the cursor was on the back side we pick the intersection on
the back side. We make the following exception: Each time the user
drags the cursor back into bounds after leaving bounds, we give him control
over the other side of the sphere. By alternating front and back
sides this way, we allow the user to rotate the object full circle (or
more) by dragging the cursor back and forth in and out of bounds.
(Code Excerpt 2)
Code Excerpt 2
//Alternate front and back sides to allow the user to rotate the
solid full circle. Place
//switch to the other side of the sphere of influence

Code Excerpt 3
//a DrawingCanvas member. Possible values are 'x', 'y',
and 'z' for constrained rotation
//Constrain rotation to the x or zaxis. Place
this within DrawingCanvas.mouseDrag to

Yaxis rotation is enabled whenever the user clicks on a point not on
the virtual object. We assign the cursor a ycoordinate of 0 and
multiply its x and zcoordinates to keep them on a predetermined circle
around the origin.
Code Excerpt 4
//Place within toSurfaceCoord when we discover that the user has
clicked outside of
//a DrawingCanvas member. An arbitrary radius value we use when
theSolid is
//Keeping the cursor's direction from the origin the same, bring
it to the circumference
//remember the new radius of the sphere of influence
//place the coordinate on this sphere

We continue to do this as the user drags the cursor. (Code Excerpt 5).
What the user can't do by hand, we do with a little bit of code.
Code Excerpt 5
//Constrain rotation to the yaxis. Place this within DrawingCanvas.mouseDrag
before
//the cursor's 3D coordinates
//if the cursor is on the origin we can't use it
