04-01-2002, 11:16 PM
Drawing in 3D is not too difficult if you follow the proper sequence of steps.
The first thing to do is establish your 3 dimensional coordinate space. For the purpose of these essays, we will use a right handed set of coordinates.
It is right handed in the sense that if you hold up your right hand, the X axis increases out the thumb, the Y axis increases up the fingers, and the Z axis comes out the palm.
In our coordinate system, X increases to the left of the screen, Y increases towards the top of the screen and Z increases out of the screen towards the viewer.
Here is what it looks like:
04-01-2002, 11:18 PM
Now that we have a coordinate system, lets make an object. We shall start with a standard cube. Here are some coordinates:
ID X Y Z
0 -20 0 -10
1 -10 0 20
2 20 0 10
3 10 0 -20
4 -20 20 -10
5 -10 20 20
6 20 20 10
7 10 20 -20
This describes a cube sitting on the Y plane and twisted at an angle.
04-01-2002, 11:22 PM
To draw this cube, we merely connect the points with lines. The problem is that our cube occupies 3-D space and we can only draw on a 2-D surface. We could simply ignore the Z dimension and draw only the X and Y components. This will give us a flat picture like:
04-01-2002, 11:25 PM
Alternatively, we can add perspective by using the Z dimension. The following diagram shows how a height Y is shortened to Y' on the screen:
04-01-2002, 11:26 PM
From simple laws of proportion, we can see that Y/D = Y'/D' where D is the distance from the viewer to Y and D' is the distance from the viewer to Y'. In this case, Y is the point in 3-D space and Y' is the point on the 2-D screen.
We can scale X onto the screen in the same manner:
X' = X * D'/D
where D' is the distance from the viewer to the screen and D is the distance from the viewer to the 3-D point. If, as in many coordinate systems, the viewer is considered to be on the origin, then the equation becomes:
X' = X * Zs/Z
Y' = Y * Zs/Z
where Zs is the Z coordinate of the screen.
If the viewer is not on the origin, but is standing on some point (Xv,Yv,Zv), then the equations become:
X' = X * (Zv-Zs)/(Zv-Z)
Y' = Y * (Zv-Zs)/(Zv-Z)
With these equations in place, we get a picture like this:
04-01-2002, 11:30 PM
Here is some example code:
04-01-2002, 11:56 PM
Now that we can draw a 3D image, it would be nice if we could transform (move) it.
Sliding the object along the X, Y or Z axes, or even a combination of axes, is relatively trivial. This kind of tranformation is called a Translation. All we have to do is add or subtract to the appropriate X, Y or Z coordinate.
More interesting is the transformation we call rotation.
Consider the following diagram:
04-01-2002, 11:58 PM
We have a point that starts at X,Y and is rotated about the origin to X',Y'.
The equations that describe this motion are:
X' = X * Cos(Angle) - Y * Sin(Angle)
Y' = X * Sin(Angle) + Y * Cos(Angle)
...for rotation around the Z axis. Rotation around the X and Y axes are similar.
If you want to rotate about a centre point that is NOT the origin, it becomes more complex. In this case, it's easier to simply move the whole object so that the centre of rotation IS on the origin, rotate the object, then move it back out again.
The attached code demonstrates rotation:
04-02-2002, 02:26 AM
This is all well and good but these wire frame drawings are pretty simple.... time to do some hidden surface removal.
The first step is to remove backfaces. A backface is a facet, or surface, that faces away from the viewer. As such, it can't possibly be seen so it is removed. Since our cube is a convex solid, it happens that this is sufficient to fully render our object. We don't need more complex methods such as the Painters Algorithm or Z-buffering at this time.
The basic idea of backface removal is that a facet is part of a plane. You can extrapolate the plane to determine whether or not it passes between the object and the viewer (in which case it is visible) or if it passes behind the object from the viewer (in which case it is hidden).
We can calculate a plane given any three points on that plane. And if those points are given in a specific order (say, clockwise), then we can differentiate between one side of the plane and the other. As it happens, calculating the Determinate of the equation tells us whether or not the plane is visible. If the determinate is positive, then the plane is visible.
Given 3 points taken in a clockwise direction in a right handed coordinate system (x1,y1,z1), (x2,y2,z2) and (x3,y3,z3):
v = x1 * y2 * z3 + x3 * y1 * z2 + x2 * y3 * z1 - x3 * y2 * z1 - x1 * y3 * z2 - x2 * y1 * z3
If v is >0 then the plane is visible.
Now that we are talking about planes rather than lines, our previous method of coordinating a list of points and a list of lines is obsolete. Instead, we will maintain a list of points (for rotation and other transformations) and a list of planes that consist of 3 or more points. Furthermore, we will make sure that each plane lists its points in a clockwise order, to make backface removal possible.
Here is some sample code to demonstrate:
04-03-2002, 01:01 AM
Now that we have the basic techniques down, we can move on to more complex shapes.
The first thing we should do is improve our data structures. That way, we can move to a file based method of loading our objects. The data file will be based on an INI file. That way, we can easily expand the data format later. Here is an excerpt from the file:
; x, y, z
; Angle1=Angle2 *scale + offset
This format is specifically designed to describe a set of linked objects that can move together as a set. The example given is a robot arm, but the format can also describe a vehicle like a crane or a structure like a windmill.
Here is what the robot arm looks like:
04-03-2002, 01:03 AM
Now that we have our format, we need a more efficient way to do transformations (rotations and translations). Matrix math is the method we will use.
Matrices are a shorthand way to describe a set of simultaneous equations. For example....
Rotation around the Z axis is:
X' = X * Cos(a) + Y * Sin(a)
Y' = X * (-Sin(a)) + Y * Cos(a)
Z' = Z
It can be written as:
|X'| |Cos(a) Sin(a) 0 0| |X|
|Y'| = |-Sin(a) Cos(a) 0 0| * |Y|
|Z'| | 0 0 1 0| |Z|
| 1| | 0 0 0 1| |1|
Likewise simple translations like:
X' = X + a
Y' = Y + b
Z' = Z + c
can be written as:
|X'| | 1 0 0 a | |X|
|Y'| = | 0 1 0 b | * |Y|
|Z'| | 0 0 1 c | |Z|
|1 | | 0 0 0 1 | |1|
This may seem like a complicated way to write a simple equations, but having a standard way to write these transformations allows us to easily automate the process. In addition, you can combine different transformations into a single matrix.
The code is below. I hardcoded the path to the INI file so you will have to change that before you run the program....