Professional Documents
Culture Documents
CH13
CH13
CH13
13.1 INTRODUCTION
Hold on to your mathematical hats. This is a heavy-duty chapter that presents us, the
illustrious authors of this book, with a no-win situation. If we explain every detail of the
programs presented in this chapter, you will most assuredly wonder about our Student
Friendly title. It is not possible to explain complex three-dimensional graphics in a simple,
easy to learn approach. It is also possible to avoid complex programs by not including
them in this chapter or this book. You see our no-win situation. Either avoid complexity
and do not include some nice program examples or start explaining concepts that are
tough to explain and digest.
This chapter will not plunge into 3d graphics. We will start with 2d graphics and explore a
variety of rotation techniques and perspective. These techniques are necessary for
successful programming in 3d.
The initial programs will start with the usual inefficiency. We simply cannot escape this
pattern. Inefficient, less desirable executions are frequently much easier to code and
When all is said and done, the truth is that 3d graphics is very satisfying. It is somewhat
like the Animation chapter. Topics like Animation and 3d Graphics are precisely why we
enjoy working with graphics programs. A few eager folks out there may like 3d graphics
because of some masochistic tendencies towards complex math. However, the majority
of computer enthusiasts like the dramatic effects that 3d graphics bring to your monitor. As
always, have fun.
▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄
Way, way back in Chapter IV the humble Circle function was introduced. The concept of
the Circle function was based on sin and cos function values. We will repeat a program
here that is specifically designed to demonstrate how a combination of a sin and a cos
value identifies a pixel coordinate in a rotating pattern. Program CH13_01.CPP
demonstrates this rotation by drawing 16 points. A circle rotates through 2 PI radians and
the program below shows sin and cos values that use arguments from 0 radians to
almost 2 PI radians.
────────────────────────────────────────────────────────────────────────────────
/**********************************************************************/
/*** CH13_01.CPP demonstrates how the sin and cos functions ***/
/*** compute points on a circle. This program is identical to an ***/
/*** earlier program, CH4_05.CPP, presented in Chapter IV. ***/
/**********************************************************************/
#include "GFXLIB8.HPP"
void main()
{
StartGfx();
Line(MIDX,0,MIDX,MAXY,1);
Line(0,MIDY,MAXX,MIDY,1);
TrigPixel(0.000*PI); // (0/8) * PI
TrigPixel(0.125*PI); // (1/8) * PI
TrigPixel(0.250*PI); // (2/8) * PI
The ever-wonderful sin and cos functions give us circular values that plot points on a
circle. The same values can be used to rotate graphics objects. In the next program,
CH13_02.CPP, we take four points on the circle, connect them in a square, and then
move the points in a clockwise rotation. The result is a rotating square. If you look closely
you will see that this program is conceptually the exact same as the previous program.
The only difference is that we are not plotting points but we are moving a square by
selecting four specific points on the circle. Function TrigPixel is not totally gone, it has just
been changed into FindTrigCoords. A new function called TrigSquare calls function
FindTrigCoords four times, draws four lines and we are in business. You may wonder if
this second program is really the same as the previous program. Look at it closely. The
code, and some names may be different, but the logic is exactly the same.
────────────────────────────────────────────────────────────────────────────────
/**********************************************************************/
/*** CH13_02.CPP is similar to the previous, except that it draws ***/
/*** four points and connects them to make a rotating square. ***/
/**********************************************************************/
#include "GFXLIB8.HPP"
void DrawAxes()
{
Line(MIDX,0,MIDX,MAXY,15);
Line(0,MIDY,MAXX,MIDY,15);
} // End void function DrawAxes.
────────────────────────────────────────────────────────────────────────────────
Program CH13_03.CPP goes one more step in the "rotating square" business. We now
make the square rotate smoothly. This requires very minimal changes. Program
CH13_02.CPP called function TrigSquare 16 times in the main program body. Program
CH13_03.CPP uses a loop in the main body that calls the TrigSquare function
continuously until a key is pressed.
#include "GFXLIB8.HPP"
void main()
{
int X,Y;
float Angle = 0;
StartGfx();
while ( !kbhit() )
{
ClearGfx(0);
DrawAxes();
TrigSquare(Angle);
Angle = Angle + (PI * 0.03125);
delay(10);
} // End while loop.
EndGfx();
} // End main program CH13_03.CPP.
void DrawAxes()
{
Line(MIDX,0,MIDX,MAXY,15);
Line(0,MIDY,MAXX,MIDY,15);
} // End void function DrawAxes.
────────────────────────────────────────────────────────────────────────────────
▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄
When you work with 3d graphics it becomes necessary to define rotation in terms of
coordinates. Imagine that a cube is placed somewhere in 3d space. The vertices of the
cube are eight different coordinates. Rotating this cube just using some center point,
angles and radiuses would be a major pain. We need to work with these eight coordinates
and find new coordinate locations as the cube rotates.
The execution of program CH13_04.CPP will once again demonstrate a set of rotating
pixels. This execution will appear the same as the first program in this chapter. There is a
very significant difference. Program CH13_01.CPP started with 0 radian value as
arguments for the sin angle and cos angle.
Now take a look at program CH13_04.CPP and you will see that the initial values are X =
80 and Y = -10. This is a set of coordinates. We are not concerned in the slightest with a
starting angle. Now, do not get confused because we will still need to use an angle, but
now the angle must be computed based on a given coordinate point. The computation of
this angle business will be explained shortly. Look at the program execution. It shows the
same result, but it is achieved in a different manner.
────────────────────────────────────────────────────────────────────────────────
/**********************************************************************/
/*** CH13_04.CPP shows how to rotate a given (X,Y) point by ***/
/*** a given radian value. ***/
/**********************************************************************/
#include "GFXLIB8.HPP"
void main()
{
int X = 80,Y = -10; // starting coordinates
float Angle;
StartGfx();
DrawAxes();
Angle = atan((float) Y / X); // calculate starting angle
if (X < 0)
Angle = PI + Angle; // correct if angle is in quadrant II or III
do
{
RotatePixel(X,Y,Angle);
Pixel(X + MIDX,Y + MIDY,15);
Angle = Angle + (PI * 0.0625); // PI / 16
delay(100);
} // End do loop.
void DrawAxes()
{
Line(MIDX,0,MIDX,MAXY,1);
Line(0,MIDY,MAXX,MIDY,1);
} // End void function DrawAxes.
────────────────────────────────────────────────────────────────────────────────
Now comes the fun part, explaining the math involved. Imagine that we are starting
rotation at coordinate point (3,2). We now need to find the vector quantity that equals this
coordinate point. So where does the angle come from? Well, just a moment ago we
mentioned that a vector quantity is an angle times its magnitude, which is the radius in our
case.
We have a coordinate so let us start by computing the radius. The trusty distance formula
can be used to for this calculation. The program statement in our program accomplishes
this with:
────────────────────────────────────────────────────────────────────────────────
Radius = sqrt(sqr(X)+sqr(Y));
────────────────────────────────────────────────────────────────────────────────
Now trigonometry tells us that the Tangent of an angle of triangle equals the opposite side
over the adjacent side. It should not be difficult to create a right triangle in a coordinate
system. Look at the drawing on the next page.
Figure 1 shows a lovely triangle between the coordinate (3,2) and the origin. N
represents the radius. The vertical distance of the triangle is 2, the horizontal distance is
3, and we need to compute the value of theta.
If Tangent Θ = 2/3 then the angle theta we want is the inverse, or arctan of 2/3. In the
program this is handled with the statement:
────────────────────────────────────────────────────────────────────────────────
Angle = arctan(Y/X);
────────────────────────────────────────────────────────────────────────────────
We are not "out of the woods" yet. The coordinate system operates in four quadrants, and
the arctan function behaves periodically. It is true that a computed angle is correct in
quadrants I and IV, but not in quadrants II and III. Any coordinate point that is found in
quadrants II and III will equal an arctan value that is negative. This means that we must
add PI when the X value is less than 0.
The program statement to accomplish the quadrant problem is:
────────────────────────────────────────────────────────────────────────────────
if (X < 0) then
Basically, the distance formula combined with some trigonometry and some quadrant
concerns result in giving us the desired angle. Do study this section carefully. You may
get fooled and believe that we are doing the same stuff. Just remember one way is
starting with an angle and ending up at an unknown coordinate. The other, more practical
way, is starting with a coordinate and computing the desired angle.
The last program rotated a single point to an absolute angle. Working with multiple points
requires rotation of all points relative to each other. Function RotatePixel in the previous
program rotated to an absolute angle. Now we want this function to rotate a specific angle
amount. This is different from rotating to a specific angle.
Therefore we need to store the starting coordinates, as well as the starting angle and
radius. With this information we can compute the desired angle amount that needs to be
increased each time. This concept will be shown with a hexagon that rotates all six
coordinates clockwise by the same angle amount. The main workhorse in program
CH13_05.CPP is function SetAngle, which computes the angle amounts.
Notice the use of the first library, GRAF3D01.HPP. This contains two functions,
SetZAngle and RotateAroundZAxis. SetZAngle accepts an X,Y coordinate pair and
returns the correct angle and radius. It checks for any quadrant problems and also makes
protects against divide by zero errors when X = 0. The second function,
RotateAroundZAxis, performs the same rotation process used before in functions like
TrigPixel and FindTrigCoords. Do not be concerned about the letter 'Z' appearing in
these function names. This is the correct term for this type of rotation (Z-Axis rotation).
This name will make more sense to you later in the chapter.
────────────────────────────────────────────────────────────────────────────────
/**********************************************************************/
/*** This CH13_05.CPP shows how to rotate a set of (X,Y) points. ***/
/**********************************************************************/
#include "GFXLIB8.HPP"
#include "GRAF3D01.HPP"
int X1,Y1,X2,Y2,X3,Y3,X4,Y4,X5,Y5,X6,Y6;
float A1,A2,A3,A4,A5,A6;
float R1,R2,R3,R4,R5,R6;
float ZAngle;
void MakeHexagon();
void DrawHexagon(float Angle);
void main()
{
void MakeHexagon()
{
X1 = 0;
Y1 = -40;
SetZAngle(X1, Y1, A1, R1);
X2 = 40;
Y2 = -25;
SetZAngle(X2, Y2,A2, R2);
X3 = 40;
Y3 = 25;
SetZAngle(X3, Y3, A3, R3);
X4 = 0;
Y4 = 40;
SetZAngle(X4, Y4, A4, R4);
X5 = -40;
Y5 = 25;
SetZAngle(X5, Y5, A5, R5);
X6 = -40;
Y6 = -25;
SetZAngle(X6, Y6, A6, R6);
} // End void function MakeHexagon.
RotateAroundZAxis(ResX1,ResY1,A1,R1,Angle);
RotateAroundZAxis(ResX2, ResY2, A2, R2, Angle);
RotateAroundZAxis(ResX3, ResY3, A3, R3, Angle);
RotateAroundZAxis(ResX4, ResY4, A4, R4, Angle);
RotateAroundZAxis(ResX5, ResY5, A5, R5, Angle);
RotateAroundZAxis(ResX6, ResY6, A6, R6, Angle);
────────────────────────────────────────────────────────────────────────────────
This program may look pretty massive. Once again, examine each section as a
maneagable part to make understanding easier. First, look at function MakeHexagon.
It may seem silly to store the original values for X1, Y1, X2, Y2, X3, Y3, X4 and Y4 in
function MakeHexagon if they are only used once to calculate angle and radius values.
Hang in there, because this silliness will soon be explained, and proper understanding of
why we did it this way will make 3d graphics easier.
This stuff may require some multiple chewing before it soaks in properly. Please keep one
thing in mind. This book does not attempt to teach Geometry, Trigonometry or any other
type of mathematics. Three-dimensional graphics cannot exist without using a lot of math.
We will try to make some attempt at explaining some of the math involved but please do
not feel bad if this stuff does not make sense right away. This is especially true if you have
not have had a Trigonometry or Pre-Calculus class.
▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄
We are going to put the math skills, and concepts of the previous sections to work and
start rotating the XY plane. We have operated strictly in a two-dimensional world. This is
logical because the screen is two-dimensional, but if we hope to play around with 3d
graphics then we need to think about handling the third dimension.
In the coming programs you need to understand the difference between coordinates and
resultants. Mathematically speaking there is a correct X coordinate, Y coordinate and
Z coordinate for every point in 3d space. When we write 3d programs we must operate
with the XResultant, YResultant and ZResultant. These resulants are also coordinates,
but they are the coordinates that we need to use in 2d space to make the graphics objects
appear to be in 3d space. The tricks used to make a drawn 2d object appear to be a 3d
object are called perspective. Most people are familiar with this technique. Railroad
tracks are not drawn parallel but angled towards each other in the distance. The issue of
how perspective is achieved will be discussed later, but it needs to be mentioned now to
justify why we will be using some weird sounding critters like XResultant, YResultant and
ZResultant.
Now back to the immediate task at hand. Program CH13_06.CPP rotates a square
around the Y Axis. We are not concerned with perspective right now. We are trying to
make the square rotate away from the monitor towards us and then into the monitor. This
depth is something that cannot be handled right now so we ignore it and any computation
of ZResultant is a waste of time. The ZResultant calculation is mathematically correct,
however, so we will leave it in comments in the program for later reference. Also, the true
Y coordinate does not change when an object rotates around the Y Axis, so the
YResultant does not need to be calculated. This leaves the XResultant as the only value
to be calculated.
────────────────────────────────────────────────────────────────────────────────
/**********************************************************************/
/*** CH13_06.CPP, unlike the last few programs, rotates a ***/
/*** square around the Y-axis. ***/
/**********************************************************************/
#include "GFXLIB8.HPP"
int X1, Y1, Z1, X2, Y2, Z2, X3, Y3, Z3, X4, Y4, Z4;
float YAn1, YAn2, YAn3, YAn4;
float YRd1, YRd2, YRd3, YRd4;
int XRes1, XRes2, XRes3, XRes4;
float YAngle;
void main()
{
StartGfx();
YAngle = 0.0;
MakeSquare();
do
{
ClearGfx(0);
DrawAxes();
DrawSquare(YAngle);
YAngle = YAngle + PI * 0.01325; // PI / 32
delay(100);
} // End do loop.
while ( !kbhit() );
EndGfx();
} // End main program CH13_06.CPP.
void MakeSquare()
{
X1 = -50;
Y1 = -50;
Z1 = 0;
SetYAngle(X1, Z1, YAn1, YRd1);
X2 = 50;
Y2 = -50;
Z2 = 0;
SetYAngle(X2, Z2, YAn2, YRd2);
X3 = 50;
Y3 = 50;
Z3 = 0;
SetYAngle(X3, Z3, YAn3, YRd3);
X4 = -50;
Y4 = 50;
Z4 = 0;
SetYAngle(X4, Z4, YAn4, YRd4);
} // End void function MakeSquare
void DrawAxes()
{
Line(0, MIDY, MAXX, MIDY, 15);
Line(MIDX, 0, MIDX, MAXY, 15);
} // End void function DrawAxes.
────────────────────────────────────────────────────────────────────────────────
It will take only some small changes to rotate the square around the X Axis. Once again
we will ignore the ZResultant because depth is not available on the monitor, at least not
without perspective tricks. This time the X coordinates do not change, which leaves only
the YResultant to be computed. Program CH13_07.CPP demonstrates this rotation. You
will find the source using the same logic as the previous program.
────────────────────────────────────────────────────────────────────────────────
/**********************************************************************/
/*** CH13_07.CPP rotates a square around the X-axis. ***/
/**********************************************************************/
#include "GFXLIB8.HPP"
int X1, Y1, Z1, X2, Y2, Z2, X3, Y3, Z3, X4, Y4, Z4;
float XAn1, XAn2, XAn3, XAn4;
float XRd1, XRd2, XRd3, XRd4;
int YRes1,YRes2, YRes3, YRes4;
float XAngle;
void main()
{
StartGfx();
XAngle = 0.0;
MakeSquare();
do
{
ClearGfx(0);
DrawAxes();
DrawSquare(XAngle);
XAngle = XAngle + 0.03125;
delay(100);
} // End do loop.
while ( !kbhit() );
void MakeSquare()
{
X1 = -50;
Y1 = -50;
Z1 = 0;
SetXAngle(Y1, Z1, XAn1, XRd1);
X2 = 50;
Y2 = -50;
Z2 = 0;
SetXAngle(Y2, Z2, XAn2, XRd2);
X3 = 50;
Y3 = 50;
Z3 = 0;
SetXAngle(Y3, Z3, XAn3, XRd3);
X4 = -50;
Y4 = 50;
Z4 = 0;
SetXAngle(Y4, Z4, XAn4, XRd4);
} // End void function MakeSquare.
void DrawAxes()
{
Line(0,MIDY,MAXX,MIDY,15);
Line(MIDX,0,MIDX,MAXY,15);
} // End void function DrawAxes
▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄
Perspective is a big deal in our world. It is all around us. Every time that we watch
television, go to a movie or look at somebody's pictures, we are exposed to perspective.
We walk, eat and breathe in 3d space, but at the same time we constantly represent this
3d space in a 2d environment. A camera has no problems with perspective at all. It picks
up all the 3d images, and what is closer is larger, and what is further away becomes
smaller. Basically, our eyes are fooled to perceive the true 2d object to be a
representation of a 3d object.
Consider program CH13_08.CPP. This program displays a simple little cube sitting still
and minding its own business. The cube makes some attempt at 3d perspective by using
angled lines and making one square appear to be "behind" the "front" square. This
program actually shows the cube three times. The first display shows a cube of all white
lines. Now, which square is the front and which square is the back? You will probably find
that you mind switches them, and some people will argue which is in front. Your 3d clues
are confusing. The perspective is not very good. Even some minor tricks can assist you.
Press the <Enter> key once and you will see the same cube with some color added. It is
more clear now that the green square represents the "front" of the cube. This perception
is created because the green square lines cover the red square lines. The red lines
appear to be "behind" the green lines. Press <Enter> once more. Now the red square
appears to be in front due to the same "cover line" logic.
────────────────────────────────────────────────────────────────────────────────
/**********************************************************************/
/*** CH13_08.CPP displays three cubes using simple routines. ***/
/**********************************************************************/
#include "GFXLIB8.HPP"
void main()
{
StartGfx();
DrawCube(15, 15);
Stop();
DrawCube(2, 4);
Stop();
DrawCube(4, 2);
────────────────────────────────────────────────────────────────────────────────
Program CH13_08 is not an attempt to demonstrate perspective. This program is meant
to illustrate that correct perspective is not easily achieved. You will find that programs that
try hard to be correct can still be perceived differently from their intentions. Our eyes are
easily fooled, and it takes a number of tricks to put our mind at ease that we see an image
only one possible way.
Let us take a look at two perspective examples side by side. Program CH13_09.CPP
shows two rectangles. At the start of execution you see two identical rectangles side by
side. You have no clue that these rectangles are meant to be rotated. now press the
<Enter> key. Both rectangles rotate 45 degrees on the X Axis. This means that the
bottom of the rectangle is closest to the viewer, and the top of the rectangle is farther
away.
The rectangle on the left works in a strict mathematical sense as if you looked at the
rectangle straight on without any perspective. The result is that you see a shorter
rectangle. This view makes the top and bottom of the rectangle come closer to each
other.
The rectangle on the right side uses perspective, and it is easier to see that rotation has
occurred. The top of the rectangle appears further away. Now look at the code of
program CH13_09.CPP. There is some explaining to do. Perspective is no obvious topic
by anybody's measuring stick.
────────────────────────────────────────────────────────────────────────────────
/**********************************************************************/
/*** CH13_09.CPP displays a square on the screen. It is not created ***/
/*** flush to the screen--it is actually laid back 45 degrees. ***/
/*** If you press enter, the square pops into perspective. ***/
/**********************************************************************/
/* This is how perspective works: The distance of a point from the origin along the
x axis is determined by the x coordinate:
- Overhead view of entire monitor box: -
Overhead view:
X Axis
|
V
B
n××××××
| ××××××
| ××××××
| ××××××
X | ×××××× ║
| ××××××║b
| !×××××
| X' ║ ××××××
|┐ ║┐ Θ ×××××× o--
+------------------------------------║--------------------- ( Observer
|---------------------|
focal length
(ZDist')
|----------------------------------------------------------|
ZDistance
ZDistance X
------------ = ---
Focal Length X'
X * Focal Length
X' = ----------------
ZDistance
This is great for any point located directly on the x-axis. What
if a point is in front of that axis (closer to the screen and the
observer) or farther away?
ZDistance - Z X
--------------- = ---
Focal Length X'
X * Focal Length
X' = ----------------
ZDistance - Z
Likewise, if all of this was redone using the Y axis, the results would
be the same, with Y substituted in for X.
*/
#include "GFXLIB8.HPP"
#include "GRAF3D02.HPP"
int X1, Y1, Z1, X2, Y2, Z2, X3, Y3, Z3, X4, Y4, Z4;
float XRd1, XRd2, XRd3, XRd4;
float XAn1, XAn2, XAn3, XAn4;
void CreateSquare();
void DrawAxes();
void DrawSquare(int XOffset);
void CalculatePerspective();
void RotateSquareBack();
void main()
{
StartGfx();
CreateSquare();
ClearGfx(0);
DrawAxes();
DrawSquare(MIDX / 2);
DrawSquare(MAXX - MIDX / 2);
Stop();
RotateSquareBack();
ClearGfx(0);
DrawAxes();
DrawSquare(MIDX / 2);
CalculatePerspective();
DrawSquare(MAXX - MIDX / 2);
Stop();
EndGfx();
} // End main program CH13_09.CPP.
void CreateSquare()
{
X1 = -50;
Y1 = -50;
Z1 = 0;
X2 = 50;
Y2 = -50;
Z2 = 0;
X3 = 50;
Y3 = 50;
Z3 = 0;
X4 = -50;
Y4 = 50;
void DrawAxes()
{
Line(MIDX / 2, 0, MIDX / 2, MAXY, 8);
Line(MAXX - MIDX / 2, 0, MAXX - MIDX / 2, MAXY, 8);
Line(0, MIDY, MAXX, MIDY, 8);
} // End void function DrawAxes.
void CalculatePerspective()
{
X1 = (X1 * FOCAL) / (ZDIST - Z1);
Y1 = (Y1 * FOCAL) / (ZDIST - Z1);
X2 = (X2 * FOCAL) / (ZDIST - Z2);
Y2 = (Y2 * FOCAL) / (ZDIST - Z2);
X3 = (X3 * FOCAL) / (ZDIST - Z3);
Y3 = (Y3 * FOCAL) / (ZDIST - Z3);
X4 = (X4 * FOCAL) / (ZDIST - Z4);
Y4 = (Y4 * FOCAL) / (ZDIST - Z4);
} // End void function CalculatePerspective.
void RotateSquareBack()
{
RotateAroundXAxis(Y1, Z1, XAn1, XRd1, -PI * 0.25);
RotateAroundXAxis(Y2, Z2, XAn2, XRd2, -PI * 0.25);
RotateAroundXAxis(Y3, Z3, XAn3, XRd3, -PI * 0.25);
RotateAroundXAxis(Y4, Z4, XAn4, XRd4, -PI * 0.25);
} // End void function RotateSquareBack.
────────────────────────────────────────────────────────────────────────────────
The first thing we need to do is visualize 3d space in the monitor. It is not possible to draw
the X,Y and Z axes correctly. From the top view we see the XZ plane and the Y Axis is
vertical from the origin straight up to the view point from above.
Basically, our perspective problem revolves around determining the location of point 3.
Figure 3
Geometry tells us that two triangles with two congruent angles are similar meaning that
they have proportional side lengths. These two triangles (ABC) and (abC) both have a
right angle, and they share an angle at C. This means that the triangles are similar and
therefore proportional.
ZDistance = X
FocalLength X’
X * FocalLength
X1 = ZDistance
This is great for any point located directly on the X Axis. What if the point is in front of that
axis (closer to the screen and the observer) or farther away?
The ZDistance is what changes, and nothing else. The ZDistance now equals the
distance from the observer to the origin, minus the offset of the Z Coordinate (the depth
into the screen from the origin). The minus is due to the fact that positive Z coordinates
move away. That is, that a coordinate which is in front of the origin (or the XY plane) has a
positive value, making the ZDistance smaller (shorter), meaning we have to subtract.
Likewise, negative coordinates behind the origin are subtracted from the ZDistance, and it
becomes larger, because the ZDistance (which is positive) minus a negative number
becomes even larger.
ZDistance – Z X
FocalLength = X’
X * Focal Length
X’ = Z Distance – Z
────────────────────────────────────────────────────────────────────────────────
void CalculatePerspective()
{
X1 = (X1 * FOCAL) / (ZDIST - Z1);
Y1 = (Y1 * FOCAL) / (ZDIST - Z1);
X2 = (X2 * FOCAL) / (ZDIST - Z2);
Y2 = (Y2 * FOCAL) / (ZDIST - Z2);
X3 = (X3 * FOCAL) / (ZDIST - Z3);
Y3 = (Y3 * FOCAL) / (ZDIST - Z3);
X4 = (X4 * FOCAL) / (ZDIST - Z4);
Y4 = (Y4 * FOCAL) / (ZDIST - Z4);
} // End void function CalculatePerspective.
────────────────────────────────────────────────────────────────────────────────
Program CH13_10.CPP is similar to the previous program. This time we will let the two
squares continue to rotate around the X Axis. The square on the left will rotate without
perspective, and the plane on the right will use the perspective formulas. When you look
at the main body of the program you will note a loop that calls function DrawSquare twice.
Each time after the first DrawSquare call to display the left square, function
CalculatePerspective is called before the second square is drawn.
Also, the need for resulting variables makes a little more sense in this program. We do
not want to store the "adjusted" persepective values for rotation. TrueX and TrueY are
used instead to keep perspective and original coordinates seperate.
────────────────────────────────────────────────────────────────────────────────
/*************************************************************************/
/*** CH13_10.CPP compares two squares rotating around the X axis. The ***/
/*** one on the left does not use persepective, and the one on the ***/
/*** right does. ***/
/*************************************************************************/
#include "GFXLIB8.HPP"
#include "GRAF3D03.HPP"
int X1, Y1, Z1, X2, Y2, Z2, X3, Y3, Z3, X4, Y4, Z4;
int TrueX1, TrueY1, TrueX2, TrueY2;
int TrueX3, TrueY3, TrueX4, TrueY4;
float XAn1, XAn2, XAn3, XAn4;
float XRd1, XRd2, XRd3, XRd4;
float XAngle;
void CreateSquare();
void main()
{
StartGfx();
CreateSquare();
XAngle = 0.0;
do
{
RotateSquare(XAngle);
DrawAxes();
DrawSquare(MIDX / 2);
CalcPersp();
DrawSquare(MAXX - MIDX / 2);
XAngle = XAngle + 0.1;
delay(100);
} // End do loop.
while ( !kbhit() );
Stop();
} // End main program CH13_10.CPP.
void CreateSquare()
{
X1 = -50;
Y1 = -50;
Z1 = 0;
X2 = 50;
Y2 = -50;
Z2 = 0;
X3 = 50;
Y3 = 50;
Z3 = 0;
X4 = -50;
Y4 = 50;
Z4 = 0;
SetXAngle(Y1, Z1, XAn1, XRd1);
SetXAngle(Y2, Z2, XAn2, XRd2);
SetXAngle(Y3, Z3, XAn3, XRd3);
SetXAngle(Y4, Z4, XAn4, XRd4);
} // End void function CreateSquare.
void DrawAxes()
{
ClearGfx(0);
Line((MIDX / 2), 0, (MIDX / 2), MAXY, 8);
Line(MAXX - (MIDX / 2), 0, MAXX - (MIDX / 2), MAXY, 8);
Line(0, MIDY, MAXX, MIDY, 8);
} // End void function DrawAxes.
void CalcPersp()
{
CalculatePerspective(TrueX1,TrueY1,X1,Y1,Z1);
CalculatePerspective(TrueX2,TrueY2,X2,Y2,Z2);
CalculatePerspective(TrueX3,TrueY3,X3,Y3,Z3);
CalculatePerspective(TrueX4,TrueY4,X4,Y4,Z4);
} // End void function CalcPersp.
────────────────────────────────────────────────────────────────────────────────
CalculatePerspective is included in the new GRAF3D03.LIB along with all three angle
setting and rotation routines.
All the groundwork is now available for rotating around another axis. We have the formula
available to compute perspective. We also know how to compute required information of
the resultants. What changes the rotation is the different resultants that are computed. In
the previous program we rotated around the X Axis. This meant that we needed to
compute the YResultant and the ZResultant. Now we want to rotate around the Y Axis.
All the Y coordinates stay fixed and now we must compute the XResultant and the
ZResultant. Everything else follows the same logic.
────────────────────────────────────────────────────────────────────────────────
/***********************************************************************/
/*** CH13_11.CPP compares 2 squares rotating around the Y axis. The ***/
/*** one on the left does not use persepective, and the one on the ***/
/*** right does. ***/
/***********************************************************************/
#include "GFXLIB8.HPP"
#include "GRAF3D03.HPP"
int X1, Y1, Z1, X2, Y2, Z2, X3, Y3, Z3, X4, Y4, Z4;
int TrueX1, TrueY1, TrueX2, TrueY2;
int TrueX3, TrueY3, TrueX4, TrueY4;
float YAn1, YAn2, YAn3, YAn4;
void CreateSquare();
void DrawAxes();
void RotateSquare(float YAngle);
void DrawSquare(int XOffset);
void CalcPersp();
void CreateSquare()
{
X1 = -50;
Y1 = -50;
Z1 = 0;
X2 = 50;
Y2 = -50;
Z2 = 0;
X3 = 50;
Y3 = 50;
Z3 = 0;
X4 = -50;
Y4 = 50;
Z4 = 0;
SetYAngle(X1, Z1, YAn1, YRd1);
SetYAngle(X2, Z2, YAn2, YRd2);
SetYAngle(X3, Z3, YAn3, YRd3);
SetYAngle(X4, Z4, YAn4, YRd4);
} // End void function CreateSquare.
void DrawAxes()
{
ClearGfx(0);
Line((MIDX / 2), 0, (MIDX / 2), MAXY, 8);
Line(MAXX - (MIDX / 2), 0, MAXX - (MIDX / 2), MAXY, 8);
Line(0, MIDY, MAXX, MIDY, 8);
} // End void function DrawAxes.
────────────────────────────────────────────────────────────────────────────────
The last program in this sequence will rotate two squares around the Z Axis. By now you
know the routine. The ZResultant is ignored because it does not change and the
XResultant and the YResultant are computed. The square on the left does not get the
benefit of perspective and the square on the right is called after the perspective formulas
are activated. So what do you see in program CH13_12.CPP? Two lovely squares that
show no discernible difference, with or without perspective. The reason for this oddity is
the issue of depth. Perspective tries to give the illusion of depth. When an object rotates
around the Z Axis depth is not an issue. Every point on the object stays the same
distance away from the observer. Furthermore, the object rotates in the XY plane, which
happens to be parallel to the plane of the computer monitor screen.
────────────────────────────────────────────────────────────────────────────────
/***********************************************************************/
/*** CH13_12.CPP compares 2 squares rotating around the Z axis. The ***/
/*** one on the left does not use perspective, and the one on the ***/
/*** right does. Notice that there is no discernable difference. ***/
/***********************************************************************/
#include "GFXLIB8.HPP"
#include "GRAF3D03.HPP"
int X1, Y1, Z1, X2, Y2, Z2, X3, Y3, Z3, X4, Y4, Z4;
int TrueX1, TrueY1, TrueX2, TrueY2;
int TrueX3, TrueY3, TrueX4, TrueY4;
float ZAn1, ZAn2, ZAn3, ZAn4;
float ZRd1, ZRd2, ZRd3, ZRd4;
float ZAngle;
void CreateSquare();
void DrawAxes();
void RotateSquare(float ZAngle);
void DrawSquare(int XOffset);
void CalcPersp();
void main()
{
StartGfx();
CreateSquare();
ZAngle = 0.0;
do
void CreateSquare()
{
X1 = -50;
Y1 = -50;
Z1 = 0;
X2 = 50;
Y2 = -50;
Z2 = 0;
X3 = 50;
Y3 = 50;
Z3 = 0;
X4 = -50;
Y4 = 50;
Z4 = 0;
SetZAngle(X1, Y1, ZAn1, ZRd1);
SetZAngle(X2, Y2, ZAn2, ZRd2);
SetZAngle(X3, Y3, ZAn3, ZRd3);
SetZAngle(X4, Y4, ZAn4, ZRd4);
} // End void function CreateSquare.
void DrawAxes()
{
ClearGfx(0);
Line((MIDX / 2), 0, (MIDX / 2), MAXY, 8);
Line(MAXX - (MIDX / 2), 0, MAXX - (MIDX / 2), MAXY, 8);
Line(0, MIDY, MAXX, MIDY, 8);
} // End void function DrawAxes.
In this section we are moving to greater complexity by rotating around more than one axis.
This is quite a bit more complex. All the formulas are basically the same but the end result
is a combination of various computations to compute the proper pixel display location.
Program CH13_13.CPP rotates around the X Axis and the Y Axis. The rotation is
somewhat strange looking. It looks like a diagonal rotation around the origin between the
X Axis and the Y Axis. This program has a problem called a "propagated error".
────────────────────────────────────────────────────────────────────────────────
/**********************************************************************/
/*** CH13_13.CPP rotates a square around two axes, X and Y. This ***/
/*** program will have some errors if run for a few minutes. ***/
/**********************************************************************/
/***************************************************************************
This program perfoms a rotation around two axes, but several things which
are not immediately apparent are neccessary. This program is intended not
to work correctly all the time ... as is noted below.
First, try to visualize the problem of rotating a point around two seperate
axes. Do you see that there are two main steps, rotation around the first
axis, and then the second.
This does make sense, with the previous programs showing how the X coordinate
does not change in X axis rotation. Now it is time for Y axis rotation:
This can be argued for the same reason as in step one, but consider
our current (X,Y,Z) coordinates: (X,3,-2).
We recall that our Z value and Y value have been recently changed.
So, we use those values from then on.
The best analogy we can think of is the standard "Rubik's cube" puzzle.
If you think about a point on the graph being somewhere on one of the
six outside planes of a "Rubik's" cube, you can rotate that plane in any
increment of 90 degrees. Rotation around two axes must be done seperately
with the "Rubik's" cube, as with rotation of a point around two axes.
This all makes sense, however you will notice two subtle differences.
First, notice that the actual (X,Y,Z) coordinates are changed at each step
according to the rotation. This is necessary because the rotation is a two
step process, and the second step requires the results of the first.
Therefore, the paramaters to RotateSquare remain constant.
The second difference lies in the execution of the program. Watch the square
rotate around for several minutes. You will see it begin to stretch out of
proportion and become a strange figure. This is called "propagated error".
Also, we have left off the newly acquired "perspective" for ease of
illustration.
*****************************************************************************/
#include "GFXLIB8.HPP"
#include "GRAF3D03.HPP"
int X1, Y1, Z1, X2, Y2, Z2, X3, Y3, Z3, X4, Y4, Z4;
float XAn1, XAn2, XAn3, XAn4;
float YAn1, YAn2, YAn3, YAn4;
float XRd1, XRd2, XRd3, XRd4;
float YRd1, YRd2, YRd3, YRd4;
void MakeSquare();
void RotateSquare(float XAngle, float YAngle);
void DrawSquare();
void main()
{
StartGfx();
MakeSquare();
do
{
ClearGfx(0);
Line(MIDX, 0, MIDX, MAXY, 15);
Line(0, MIDY, MAXX, MIDY, 15);
RotateSquare(PI * 0.03125, PI * 0.03125);
DrawSquare();
delay(100);
} // End do loop.
while ( !kbhit() );
EndGfx();
} // End main program CH13_13.CPP.
void MakeSquare()
{
X1 = -50;
Y1 = -50;
void DrawSquare()
{
Line(X1+MIDX, Y1+MIDY, X2+MIDX, Y2+MIDY, 1);
Line(X2+MIDX, Y2+MIDY, X3+MIDX, Y3+MIDY, 2);
Line(X3+MIDX, Y3+MIDY, X4+MIDX, Y4+MIDY, 3);
Line(X4+MIDX, Y4+MIDY, X1+MIDX, Y1+MIDY, 4);
} // End void function DrawSquare.
────────────────────────────────────────────────────────────────────────────────
It makes sense that you need to recalculate the angles and radii of each point when you
are rotating around multiple axes, because you can be assured they will interfere with
each other and get each other out of whack. The proof of this is elegant but complex, and
this is not a Geometry book anyway.
────────────────────────────────────────────────────────────────────────────────
/***********************************************************************/
/*** CH13_14.CPP rotates a square around two axes, X and Y. It also ***/
/*** corrects the "propigated error" of the last program. ***/
/***********************************************************************/
/**************************************************************************
Why is there propigated error in the last program? The answer lies in
the problem of round-off. At each rotation, the coordinates are rounded.
We start with correct original coordinates, rotate them to some correct
location, and then round off that decimal approximation. This new rounded
off value is now stored as the original value, which is very incorrect.
#include "GFXLIB8.HPP"
#include "GRAF3D03.HPP"
int X1, Y1, Z1, X2, Y2, Z2, X3, Y3, Z3, X4, Y4, Z4;
float XAn1, XAn2, XAn3, XAn4;
float YAn1, YAn2, YAn3, YAn4;
float XRd1, XRd2, XRd3, XRd4;
float YRd1, YRd2, YRd3, YRd4;
int XRes1, YRes1, ZRes1, XRes2, YRes2, ZRes2;
int XRes3, YRes3, ZRes3, XRes4, YRes4, ZRes4;
float XAngle, YAngle;
void MakeSquare();
void RotateSquare(float XAngle, float YAngle);
void DrawSquare();
void main()
{
StartGfx();
MakeSquare();
XAngle = 0.0;
YAngle = 0.0;
do
{
ClearGfx(0);
Line(MIDX,0,MIDX,MAXY,15);
Line(0,MIDY,MAXX,MIDY,15);
RotateSquare(XAngle,YAngle);
DrawSquare();
XAngle = XAngle + PI * 0.03125;
void MakeSquare()
{
X1 = -50;
Y1 = -50;
Z1 = 50;
X2 = 50;
Y2 = -50;
Z2 = 50;
X3 = 50;
Y3 = 50;
Z3 = 50;
X4 = -50;
Y4 = 50;
Z4 = 50;
SetXAngle(Y1, Z1, XAn1, XRd1);
SetYAngle(X1, Z1, YAn1, YRd1);
SetXAngle(Y2, Z2, XAn2, XRd2);
SetYAngle(X2, Z2, YAn2, YRd2);
SetXAngle(Y3, Z3, XAn3, XRd3);
SetYAngle(X3, Z3, YAn3, YRd3);
SetXAngle(Y4, Z4, XAn4, XRd4);
SetYAngle(X4, Z4, YAn4, YRd4);
} // End void function MakeSquare.
void DrawSquare()
{
Line(XRes1+MIDX, YRes1+MIDY, XRes2+MIDX, YRes2+MIDY, 1);
Line(XRes2+MIDX, YRes2+MIDY, XRes3+MIDX, YRes3+MIDY, 2);
Line(XRes3+MIDX, YRes3+MIDY, XRes4+MIDX, YRes4+MIDY, 3);
Line(XRes4+MIDX, YRes4+MIDY, XRes1+MIDX, YRes1+MIDY, 4);
} // End void function DrawSquare.
────────────────────────────────────────────────────────────────────────────────
Program CH13_15.CPP rotates a plane around the X Axis, Y Axis and the Z Axis. The
logic of the program follows the same angle computation, resultant computation of
previous programs. There are more computations because there are more situations to
consider as the object rotates through three different dimensions. Study the
RotateSquare function carefully. The transition between original coordinates and
resultant coordinates is very, very important. Notice how the function starts by using
X1,Y1, and Z1 in the SetXAngle function. In the next section, X1,YRes1, and ZRes1 are
being used, and by the last part only resultants are in place. Draw many pictures and
visualize the situations in order to understand this function.
────────────────────────────────────────────────────────────────────────────────
/**********************************************************************/
/*** CH13_15.CPP rotates a square around all three axes. ***/
/**********************************************************************/
#include "GFXLIB8.HPP"
#include "GRAF3D03.HPP"
int X1, Y1, Z1, X2, Y2, Z2, X3, Y3, Z3, X4, Y4, Z4;
float XAn1, XAn2, XAn3, XAn4;
float YAn1, YAn2, YAn3, YAn4;
float ZAn1, ZAn2, ZAn3, ZAn4;
float XRd1, XRd2, XRd3, XRd4;
float YRd1, YRd2, YRd3, YRd4;
float ZRd1, ZRd2, ZRd3, ZRd4;
int XRes1, YRes1, ZRes1, XRes2, YRes2, ZRes2;
int XRes3, YRes3, ZRes3, XRes4, YRes4, ZRes4;
float XAngle, YAngle, ZAngle;
void MakeSquare();
void RotateSquare(float XAngle, float YAngle, float ZAngle);
void DrawSquare();
void main()
{
StartGfx();
MakeSquare();
XAngle = 0.0;
YAngle = 0.0;
ZAngle = 0.0;
do
{
ClearGfx(0);
Line(MIDX, 0, MIDX, MAXY, 15);
Line(0, MIDY, MAXX, MIDY, 15);
RotateSquare(XAngle, YAngle, ZAngle);
DrawSquare();
XAngle = XAngle + PI * 0.015625; // 1 / 64
YAngle = YAngle + PI * 0.015625;
ZAngle = ZAngle + PI * 0.015625;
delay(100);
} // End do loop.
while ( !kbhit() );
void MakeSquare()
{
X1 = -50;
Y1 = -50;
Z1 = 50;
X2 = 50;
Y2 = -50;
Z2 = 50;
X3 = 50;
Y3 = 50;
Z3 = 50;
X4 = -50;
Y4 = 50;
Z4 = 50;
SetXAngle(Y1, Z1, XAn1, XRd1);
SetYAngle(X1, Z1, YAn1, YRd1);
SetZAngle(X1, Y1, ZAn1, ZRd1);
SetXAngle(Y2, Z2, XAn2, XRd2);
SetYAngle(X2, Z2, YAn2, YRd2);
SetZAngle(X2, Y2, ZAn2, ZRd2);
SetXAngle(Y3, Z3, XAn3, XRd3);
SetYAngle(X3, Z3, YAn3, YRd3);
SetZAngle(X3, Y3, ZAn3, ZRd3);
SetXAngle(Y4, Z4, XAn4, XRd4);
SetYAngle(X4, Z4, YAn4, YRd4);
SetZAngle(X4, Y4, ZAn4, ZRd4);
} // End void function MakeSquare.
────────────────────────────────────────────────────────────────────────────────
It is not easy to visualize the movement of the square through three dimensions. Your
eyes can get fooled very easily. The next program will assist your eyes. We are going to
connect the square with four lines to the origin. The result is a graphic object that looks
like a pyramid. The vertex of the pyramid stays fixed, and as the pyramid rotates around,
its base (the square) is easier to follow. This program example is concerned with all the
computations except perspective, which will be added in the next program.
────────────────────────────────────────────────────────────────────────────────
/**********************************************************************/
/*** CH13_16.CPP rotates a square around all three axes. Each ***/
/*** point of the square is connected to the origin to create a ***/
/*** "contrived" 3-d pyramid. ***/
/**********************************************************************/
/**************************************************************************
In this program, we connect each of the points of the square to the origin
to create a "contrived" 3-D shape. We have actually only created a square,
as before, but by connecting to the origin, the square looks like a pyramid.
***************************************************************************/
#include "GFXLIB8.HPP"
#include "GRAF3D03.HPP"
int X1, Y1, Z1, X2, Y2, Z2, X3, Y3, Z3, X4, Y4, Z4;
float XAn1, XAn2, XAn3, XAn4;
float YAn1, YAn2, YAn3, YAn4;
float ZAn1, ZAn2, ZAn3, ZAn4;
float XRd1, XRd2, XRd3, XRd4;
float YRd1, YRd2, YRd3, YRd4;
float ZRd1, ZRd2, ZRd3, ZRd4;
int XRes1, YRes1, ZRes1, XRes2, YRes2, ZRes2;
int XRes3, YRes3, ZRes3, XRes4, YRes4, ZRes4;
float XAngle, YAngle, ZAngle;
void MakeSquare();
void RotateSquare(float XAngle, float YAngle, float ZAngle);
void DrawSquare();
void main()
{
StartGfx();
MakeSquare();
XAngle = 0.0;
YAngle = 0.0;
ZAngle = 0.0;
do
{
ClearGfx(0);
Line(MIDX, 0, MIDX, MAXY,15);
Line(0, MIDY, MAXX, MIDY,15);
void MakeSquare()
{
X1 = -50;
Y1 = -50;
Z1 = 50;
X2 = 50;
Y2 = -50;
Z2 = 50;
X3 = 50;
Y3 = 50;
Z3 = 50;
X4 = -50;
Y4 = 50;
Z4 = 50;
SetXAngle(Y1, Z1, XAn1, XRd1);
SetYAngle(X1, Z1, YAn1, YRd1);
SetZAngle(X1, Y1, ZAn1, ZRd1);
SetXAngle(Y2, Z2, XAn2, XRd2);
SetYAngle(X2, Z2, YAn2, YRd2);
SetZAngle(X2, Y2, ZAn2, ZRd2);
SetXAngle(Y3, Z3, XAn3, XRd3);
SetYAngle(X3, Z3, YAn3, YRd3);
SetZAngle(X3, Y3, ZAn3, ZRd3);
SetXAngle(Y4, Z4, XAn4, XRd4);
SetYAngle(X4, Z4, YAn4, YRd4);
SetZAngle(X4, Y4, ZAn4, ZRd4);
} // End void function MakeSquare.
void DrawSquare()
{
Line(XRes1+MIDX, YRes1+MIDY, XRes2+MIDX, YRes2+MIDY, 1);
Line(XRes2+MIDX, YRes2+MIDY, XRes3+MIDX, YRes3+MIDY, 2);
Line(XRes3+MIDX, YRes3+MIDY, XRes4+MIDX, YRes4+MIDY, 3);
Line(XRes4+MIDX, YRes4+MIDY, XRes1+MIDX, YRes1+MIDY, 4);
Line(XRes1+MIDX, YRes1+MIDY, MIDX, MIDY, 14);
Line(XRes2+MIDX, YRes2+MIDY, MIDX, MIDY, 14);
Line(XRes3+MIDX, YRes3+MIDY, MIDX, MIDY, 14);
Line(XRes4+MIDX, YRes4+MIDY, MIDX, MIDY, 14);
} // End void function DrawSquare.
────────────────────────────────────────────────────────────────────────────────
────────────────────────────────────────────────────────────────────────────────
/**********************************************************************/
/*** CH13_17.CPP rotates a square around all 3 axes. Each point ***/
/*** of the square is connected to the origin to create a ***/
/*** "contrived" 3-d pyramid. Perspective is also added. ***/
/**********************************************************************/
#include "GFXLIB8.HPP"
#include "GRAF3D03.HPP"
int X1, Y1, Z1, X2, Y2, Z2, X3, Y3, Z3, X4, Y4, Z4;
float XAn1, XAn2, XAn3, XAn4;
float YAn1, YAn2, YAn3, YAn4;
float ZAn1, ZAn2, ZAn3, ZAn4;
float XRd1, XRd2, XRd3, XRd4;
float YRd1, YRd2, YRd3, YRd4;
float ZRd1, ZRd2, ZRd3, ZRd4;
int XRes1, YRes1, ZRes1, XRes2, YRes2, ZRes2;
int XRes3, YRes3, ZRes3, XRes4, YRes4, ZRes4;
float XAngle, YAngle, ZAngle;
void MakeSquare();
void RotateSquare(float XAngle, float YAngle, float ZAngle);
void DrawSquare();
void CalcPersp();
void main()
{
StartGfx();
MakeSquare();
void MakeSquare()
{
X1 = -50;
Y1 = -50;
Z1 = 50;
X2 = 50;
Y2 = -50;
Z2 = 50;
X3 = 50;
Y3 = 50;
Z3 = 50;
X4 = -50;
Y4 = 50;
Z4 = 50;
SetXAngle(Y1, Z1, XAn1, XRd1);
SetYAngle(X1, Z1, YAn1, YRd1);
SetZAngle(X1, Y1, ZAn1, ZRd1);
SetXAngle(Y2, Z2, XAn2, XRd2);
SetYAngle(X2, Z2, YAn2, YRd2);
SetZAngle(X2, Y2, ZAn2, ZRd2);
SetXAngle(Y3, Z3, XAn3, XRd3);
SetYAngle(X3, Z3, YAn3, YRd3);
SetZAngle(X3, Y3, ZAn3, ZRd3);
SetXAngle(Y4, Z4, XAn4, XRd4);
SetYAngle(X4, Z4, YAn4, YRd4);
SetZAngle(X4, Y4, ZAn4, ZRd4);
} // End void function MakeSquare.
void DrawSquare()
{
Line(XRes1+MIDX, YRes1+MIDY, XRes2+MIDX, YRes2+MIDY, 1);
Line(XRes2+MIDX, YRes2+MIDY, XRes3+MIDX, YRes3+MIDY, 2);
Line(XRes3+MIDX, YRes3+MIDY, XRes4+MIDX, YRes4+MIDY, 3);
Line(XRes4+MIDX, YRes4+MIDY, XRes1+MIDX, YRes1+MIDY, 4);
Line(XRes1+MIDX, YRes1+MIDY, MIDX, MIDY, 14);
Line(XRes2+MIDX, YRes2+MIDY, MIDX, MIDY, 14);
Line(XRes3+MIDX, YRes3+MIDY, MIDX, MIDY, 14);
Line(XRes4+MIDX, YRes4+MIDY, MIDX, MIDY, 14);
} // End void function DrawSquare.
void CalcPersp()
{
CalculatePerspective(XRes1, YRes1, XRes1, YRes1, ZRes1);
CalculatePerspective(XRes2, YRes2, XRes2, YRes2, ZRes2);
CalculatePerspective(XRes3, YRes3, XRes3, YRes3, ZRes3);
CalculatePerspective(XRes4, YRes4, XRes4, YRes4, ZRes4);
} // End void function CalcPersp.
────────────────────────────────────────────────────────────────────────────────
▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄
We are ready for the big boys now. The time has come to rotate some serious objects.
The last couple of programs may have appeared that we rotated some 3d objects
(pyramids) through 3d space. In reality we were rotating a square that was connected to
the origin. In other words the four points of the square did the rotation. The fifth point of
the pyramid always stayed in place.
For the first program example, we will view what happens when two parallel squares are
#include "GFXLIB8.HPP"
#include "GRAF3D03.HPP"
int X1, Y1, Z1, X2, Y2, Z2, X3, Y3, Z3, X4, Y4, Z4;
int X5, Y5, Z5, X6, Y6, Z6, X7, Y7, Z7, X8, Y8, Z8;
float XAn1, XAn2, XAn3, XAn4, XAn5, XAn6, XAn7, XAn8;
float YAn1, YAn2, YAn3, YAn4, YAn5, YAn6, YAn7, YAn8;
float ZAn1, ZAn2, ZAn3, ZAn4, ZAn5, ZAn6, ZAn7, ZAn8;
float XRd1, XRd2, XRd3, XRd4, XRd5, XRd6, XRd7, XRd8;
float YRd1, YRd2, YRd3, YRd4, YRd5, YRd6, YRd7, YRd8;
float ZRd1, ZRd2, ZRd3, ZRd4, ZRd5, ZRd6, ZRd7, ZRd8;
int XRes1, YRes1, ZRes1, XRes2, YRes2, ZRes2;
int XRes3, YRes3, ZRes3, XRes4, YRes4, ZRes4;
int XRes5, YRes5, ZRes5, XRes6, YRes6, ZRes6;
int XRes7, YRes7, ZRes7, XRes8, YRes8, ZRes8;
float XAngle, YAngle, ZAngle;
void MakeSquares();
void RotateSquares(float XAngle, float YAngle, float ZAngle);
void DrawSquares();
void CalcPersp();
void main()
{
StartGfx();
XAngle = 0.0;
YAngle = 0.0;
ZAngle = 0.0;
MakeSquares();
do
{
ClearGfx(0);
Line(MIDX, 0, MIDX, MAXY, 15);
Line(0, MIDY, MAXX, MIDY, 15);
RotateSquares(XAngle, YAngle, ZAngle);
CalcPersp();
DrawSquares();
XAngle = XAngle + PI * 0.015625; // 1 / 64
YAngle = YAngle + PI * 0.015625;
ZAngle = ZAngle + PI * 0.015625;
delay(100);
} // End do loop.
while ( !kbhit() );
EndGfx();
} // End main program CH13_18.CPP.
void MakeSquares()
{
void SetXYZAngle(int X, int Y, int Z, float& XAn, float& YAn, float& ZAn,
float& XRd, float& YRd, float& ZRd);
void SetXYZAngle(int X, int Y, int Z, float& XAn, float& YAn, float& ZAn,
float& XRd, float& YRd, float& ZRd)
{
SetXAngle(Y, Z, XAn, XRd);
SetYAngle(X, Z, YAn, YRd);
SetZAngle(X, Y, ZAn, ZRd);
} // End void function SetXYZAngle.
void DrawSquares()
{
Line(XRes1+MIDX, YRes1+MIDY, XRes2+MIDX, YRes2+MIDY, 1);
Line(XRes2+MIDX, YRes2+MIDY, XRes3+MIDX, YRes3+MIDY, 2);
Line(XRes3+MIDX, YRes3+MIDY, XRes4+MIDX, YRes4+MIDY, 3);
Line(XRes4+MIDX, YRes4+MIDY, XRes1+MIDX, YRes1+MIDY, 4);
Line(XRes5+MIDX, YRes5+MIDY, XRes6+MIDX, YRes6+MIDY, 1);
Line(XRes6+MIDX, YRes6+MIDY, XRes7+MIDX, YRes7+MIDY, 2);
Line(XRes7+MIDX, YRes7+MIDY, XRes8+MIDX, YRes8+MIDY, 3);
Line(XRes8+MIDX, YRes8+MIDY, XRes5+MIDX, YRes5+MIDY, 4);
} // End void function DrawSquares.
void CalcPersp()
{
CalculatePerspective(XRes1,YRes1,XRes1,YRes1,ZRes1);
CalculatePerspective(XRes2,YRes2,XRes2,YRes2,ZRes2);
CalculatePerspective(XRes3,YRes3,XRes3,YRes3,ZRes3);
CalculatePerspective(XRes4,YRes4,XRes4,YRes4,ZRes4);
CalculatePerspective(XRes5,YRes5,XRes5,YRes5,ZRes5);
CalculatePerspective(XRes6,YRes6,XRes6,YRes6,ZRes6);
CalculatePerspective(XRes7,YRes7,XRes7,YRes7,ZRes7);
CalculatePerspective(XRes8,YRes8,XRes8,YRes8,ZRes8);
} // End void function CalcPersp.
────────────────────────────────────────────────────────────────────────────────
────────────────────────────────────────────────────────────────────────────────
/**********************************************************************/
/*** CH13_19.CPP rotates a cube around all 3 axes. Arrays are used ***/
/*** to simplify the variable declaration section. ***/
/**********************************************************************/
#include "GFXLIB8.HPP"
#include "GRAF3D03.HPP"
const NUM = 8;
void MakeCube();
void RotateCube(float XAngle, float YAngle, float ZAngle);
void CalcPersp();
void DrawCube();
void DrawAxes();
void main()
{
StartGfx();
XAngle = 0.0;
YAngle = 0.0;
ZAngle = 0.0;
MakeCube();
do
{
DrawAxes();
RotateCube(XAngle, YAngle, ZAngle);
CalcPersp();
DrawCube();
XAngle = XAngle + PI * 0.015625; // 1 / 64
YAngle = YAngle + PI * 0.015625;
ZAngle = ZAngle + PI * 0.015625;
delay(100);
} // End do loop.
while ( !kbhit() );
EndGfx();
} // End main program CH13_19.CPP.
void MakeCube()
{
X[0] = -50; Y[0] = -50; Z[0] = -50;
X[1] = 50; Y[1] = -50; Z[1] = -50;
X[2] = 50; Y[2] = -50; Z[2] = 50;
X[3] = -50; Y[3] = -50; Z[3] = 50;
X[4] = -50; Y[4] = 50; Z[4] = -50;
X[5] = 50; Y[5] = 50; Z[5] = -50;
X[6] = 50; Y[6] = 50; Z[6] = 50;
X[7] = -50; Y[7] = 50; Z[7] = 50;
void DrawCube()
{
Line(TrueX[0]+MIDX, TrueY[0]+MIDY, TrueX[1]+MIDX, TrueY[1]+MIDY, 4);
Line(TrueX[1]+MIDX, TrueY[1]+MIDY, TrueX[2]+MIDX, TrueY[2]+MIDY, 4);
Line(TrueX[2]+MIDX, TrueY[2]+MIDY, TrueX[3]+MIDX, TrueY[3]+MIDY, 4);
Line(TrueX[3]+MIDX, TrueY[3]+MIDY, TrueX[0]+MIDX, TrueY[0]+MIDY, 4);
void DrawAxes()
{
ClearGfx(0);
Line(MIDX, 0, MIDX, MAXY, 15);
Line(0, MIDY, MAXX, MIDY, 15);
} // End void function DrawAxes.
────────────────────────────────────────────────────────────────────────────────
ROTATING WITH A NEW ROTATION FORMULA
All the rotation math that has been used up to this point was specifically picked because it
was simpler. Some folks right now are not feeling so comfortable. Your idea of simple
does not include the last 40 odd pages of this chapter. Well you are right this has been the
toughest chapter in the whole book, and yet we tried to simplify the complexity of 3d
graphics. The methods used in the previous program slowed down program execution.
This was not a problem for our simple squares and modest cube, but it a problem for more
complex movements. We have introduced a new formula which is explained in the
comment of the next program.
────────────────────────────────────────────────────────────────────────────────
/**********************************************************************/
/*** GFX3D20.CPP rotates a cube around all three axes. The new ***/
/*** rotation formula is used. ***/
/**********************************************************************/
/*
By polar definition: a point (x,y) is x = r×cos Θ y = r×sin Θ
Θz+αz
:: around z axis ..
x' = x × cos α - y × sin α
y' = y × cos α + x × sin α
:: around x axis ..
z'' = z × cos α - y × sin α
y'' = y × cos α + z × sin α
:: around y axis ..
x''' = x'' × cos α - z'' × sin α
z''' = z'' × cos α + x'' × sin α
:: around z axis ..
x' = x × cos α - y × sin α
y' = y × cos α + x × sin α
:: around x axis ..
z' = z × cos α - y' × sin α
y' = y'× cos α + z × sin α
:: around y axis ..
x'' = x' × cos α - z' × sin α
z'' = z' × cos α + x' × sin α
#include "GFXLIB8.HPP"
#include "GRAF3D03.HPP"
const NUM = 8;
void main()
{
StartGfx();
XAngle = 0.0;
YAngle = 0.0;
ZAngle = 0.0;
MakeCube();
do
{
DrawAxes();
RotateCube(XAngle,YAngle,ZAngle);
CalcPersp();
DrawCube();
XAngle = XAngle + PI / 64;
YAngle = YAngle + PI / 64;
ZAngle = YAngle + PI / 64;
delay(100);
} // End do loop.
while ( !kbhit() );
EndGfx();
} // End main program CH13_20.
void MakeCube()
{
X[0] = -50; Y[0] = -50; Z[0] = -50;
X[1] = 50; Y[1] = -50; Z[1] = -50;
X[2] = 50; Y[2] = -50; Z[2] = 50;
X[3] = -50; Y[3] = -50; Z[3] = 50;
X[4] = -50; Y[4] = 50; Z[4] = -50;
X[5] = 50; Y[5] = 50; Z[5] = -50;
X[6] = 50; Y[6] = 50; Z[6] = 50;
X[7] = -50; Y[7] = 50; Z[7] = 50;
} // End void function MakeCube.
void CalcPersp()
{
for (Cnt = 0; Cnt < NUM; Cnt++)
CalculatePerspective(TrueX[Cnt], TrueY[Cnt], XRes[Cnt],
YRes[Cnt], ZRes[Cnt]);
} // End void function CalcPersp.
void DrawCube()
{
void DrawAxes()
{
ClearGfx(0);
Line(MIDX, 0, MIDX, MAXY, 15);
Line(0, MIDY, MAXX, MIDY, 15);
} // End void function DrawAxes.
────────────────────────────────────────────────────────────────────────────────
▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄
There are many more interesting programs to show with 3d graphics. We are having a
serious space limitation. This book is limited to a definite finite size for the binding method
that our printer can handle. There are also budget problems, and it may not surprise you
that this book became thicker than we intended.
Our solution is simple. Everybody does get two disks with this book. All the previous
programs have been printed in this book because it is easier to study the program logic,
and it helps in explaining the program flow. We did mention that the final program
examples in 3d graphics were meant to be seen and studied, but they would not be
explained.
Check out the graphics disk and in the ADV3DGFX subdirectory and you will see an
additional set of programs that demonstrate some very interesting 3d graphics concepts.
Be aware, the math is sophisticated, and we did say before that a healthy knowledge of
pre-calculus math is a prerequisite to a clear understanding of all this rotation business.
▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄
────────────────────────────────────────────────────────────────────────────────
────────────────────────────────────────────────────────────────────────────────
/**********************************************************************/
/*** GRAF3D03.HPP contains routines for setting X, Y, and Z angles, ***/
/*** and rotating points around the X, Y, and Z axes, and a ***/
/*** perspective routine. ***/
/**********************************************************************/
────────────────────────────────────────────────────────────────────────────────