Fig. 2.1 Walls and objects.
Defining Objects in Object Space
Virtually all objects we will describe in 3D space using Fastgraph will
consist of faces. In Figure 2.1, above, you can see two walls and a cube. The
cube has six faces, several of which are hidden. The walls have one face each
(They are very thin walls). In this section, I will describe
two different ways to define faces of objects in 3D space.
Let's think about the cube first. A cube is an object that is reuseable.
That is, I may want to have many cubes in my 3D world. Ideally, I only want
to define it once, and then put it many places. The best way to do this is
by defining a set of coordinates in the cube's own object space.
In this context, object space simply means the cube is defined around the
origin. The center of the cube is (0,0,0), and the corners of the cube
are some distance away from the origin, say a distance of 1. This describes
a generic cube. The cube will never actually be displayed at the origin.
Every time we show the cube, it will be translated and rotated
by Fastgraph. The translation and rotation moves the center of the cube
to a point (x,y,z), and rotates it.
Figure 2.2 shows the coordinates of one face of the cube.
Let's think about the cube first. A cube is an object that is reuseable. That is, I may want to have many cubes in my 3D world. Ideally, I only want to define it once, and then put it many places. The best way to do this is by defining a set of coordinates in the cube's own object space.
In this context, object space simply means the cube is defined around the origin. The center of the cube is (0,0,0), and the corners of the cube are some distance away from the origin, say a distance of 1. This describes a generic cube. The cube will never actually be displayed at the origin. Every time we show the cube, it will be translated and rotated by Fastgraph. The translation and rotation moves the center of the cube to a point (x,y,z), and rotates it.
Figure 2.2 shows the coordinates of one face of the cube.
Fig. 2.2 Defining a cube face in object space.
Notice how the origin is in the middle of the cube. Later, I will refer to this
as the "center of gravity" although that is a misnomer. We don't really know
anything about the mass of the cube, and we can define the center to be anywhere
we want. If we define the center to be at one corner, and then try to rotate the
cube, it would spin around that corner. In general, it's a good idea to center an
object around something in its approximate middle, so it will look good if you
Defining Objects in World Space
The walls in Figure 2.1 have different properties than the faces of the cube. Each wall is unique. They have different sizes, angles, textures, etc. We decide to define our walls in world space. That means walls are defined in terms of absolute coordinates, rather than centered around the origin. The wall coordinates may look something like figure 2.3:
Fig. 2.3 Defining a face in world space.
Notice how we define the the coordinates in a clockwise order. This is useful
when you want to take advantage of Fastgraph's backface removal capabilities.
You should get in the habit of doing this even if you don't use backface removal.
Consistency will pay off.
You will use a different Fastgraph function to display an object defined in world space than to display an object defined in object space.
Note that defining walls in world space may not be the most efficient option. You may choose to define several walls in object space and reuse them by moving them to different locations in world space. It will depend on several factors: how many of your walls are unique, how your level editor works, etc. Put a little thought into this before you get started to avoid re-doing your work later on.
Rotation, Translation, and Projection
If you are unfamiliar with 3D programming, these words may sound a bit
intimidating. So let's start with some simple definitions, then we will
look at the Fastgraph functions that handle these activities.
For those who are not currently interested in getting bogged down in matrix
mathematics, and who want to start writing some 3D programs, let's carry on with
For those who are not currently interested in getting bogged down in matrix mathematics, and who want to start writing some 3D programs, let's carry on with this tutorial.
Steps in Writing a 3D Program
Any time you write a 3D program, you are going to need to take the following steps.
Some of them have already been documented, but we will go into more detail and
provide some examples shortly.
Source code for fgtutex1.c
With luck, you should be able to cut and paste the above code into your compiler. If you manage to compile and link with Fastgraph 6.0 for Windows, the result is a cube that looks like this:
Fig. 2.4 Output from fgtutex1.c.
I wrote the above program in straight C with some global variables. I did it that
way because I wanted to make the program as short as possible (a bit of a challenge
with Windows programs) so it would fit nicely on a web page. Later, I will give
you some more interesting programs in C++. For now, let's dissect the above program
and make sure we understand all the parts. I'll work down in order of the functions.
Notes on the source codeThe first function in the above program is WinMain(). You should know how this works by now. If you don't, go look it up in a beginning book on Windows programming. Some compilers, like Borland C++Builder, hide this function from you. It's a nice one to hide.
The next function is WindowProc(). Again, you should generally know what this does before you embark on any kind of Windows programming. The interesting bits of our WindowProc() function are the parts that handle the Create, Paint and Destroy messages.
Under the WM_CREATE you see your first Fastgraph call: fg_setdc(). That function simply notifies Fastgraph which window to draw to. It doesn't have to be the first Fastgraph function you call, it just happens to be this time. If this were a DirectX program, you would need to call fg_ddsetup() as your first fastgraph function. See the Fastgraph manual for more information about that. Personally, I'm not too interested in DirectX, so we will skip that stuff for now.
After setting the device context with fg_setdc() we then initialize and realize the palette with fg_defpal() and fg_realize(). Again, see the Fastgraph manual for more information about how these work. They are only important in 8-bit SVGA programs (256 colors). If you are running in a high-color or true-color mode, you don't need to worry about the palette. But it does no harm to have those functions in there, and you may give your program to somebody using a 256-color display some day. It's best to be prepared to run under those conditions.
Next under WM_CREATE we call three functions to initialize the virtual buffers, initialize the 3D geometry system, and draw the cube. I'll get back to those in a minute. Virtually all the work in this program happens under WM_CREATE. All that is left after that is to wait for a keystroke and exit. Now let's take a quick look at the other Windows messages.
Under WM_PAINT we call fg_vbscale() to tell Fastgraph to take the virtual buffer and blit it to the screen while scaling it. So if the user stretches the corner of the window, our cube image will stretch with it. This is a good idea if you are using a scalable window. The unscaleable counterpart to fg_vbscale() is fg_vbpaste(). It is a little faster than fg_vbscale() because it doesn't have to calculate the scaling. I like to use fg_vbpaste() when I am writing a game that uses a full-screen mode, or a game that is in a fixed size in a window that can't be scaled. If you are trying to write a very fast FPS, consider using a fixed sized window so you can take advantage of the speed of fg_vbpaste().
Under WM_DESTROY we free all the memory and destroy all the handles. It is very important to clean up your virtual buffers, z-buffers, palettes, and of course, anything else you have allocated in your Windows program. Make it a habit to double check here so you don't miss anything.
The next function in our program is InitVirtualBuffers(). This calls the Fastgraph functions that initialize the virtual buffer system, define the color depth of the buffers, allocate memory for a virtual buffer, assign a handle to it, assign a color palette to it, and then open it and fill it with white pixels. This is all pretty simple and it is documented in the Fastgraph manual. Probably the most interesting thing here is the color depth. If the color depth of the virtual buffer does not match the color depth of the display, your program will work just fine. But it will be slower because Fastgraph will have to do "on the fly" conversions. It is not always possible to match the color depth of the virtual buffer to the color depth of the display. For example, if you want to display a JPEG file in a 256-color mode, you will need to make a high color or true color virtual buffer, and then let Fastgraph handle the color reduction on the fly. If you are interested in writing a high-speed 3D game, it is probably better if the color depth of the virtual buffer and the display match. For simplicity, we will force our color depth to 8 bits (256 colors).
The next function is Init3D(). This is where you use the functions we discussed Chapter 1. We initialize the 3D geometry system and the point of view. We establish a viewport, which is the area of the virtual buffer where 3D objects will be projected. We also allocate space for the Z-buffer and we set clipping limits for Z clipping. More about those concepts in the next chapter.
Finally, it is time to draw our cube in DrawCube(). We start by initializing the cube data as a set of points around the origin. These take the form of (x,y,z) coordinates of the four corners of the six sides. Remember to put these in clockwise order! The center of gravity is smack dab in the center of the cube, as in figure 2.2. We call the fg_3Drenderstate() function to set the render state, which in this case happens to be the default state: filled polygons with backface removal. We use the fg_3Dsetobject() function to tell Fastgraph the location and rotation of the cube. Then we set the color. Finally, we use fg_3Dpolygonobject() to display each face of the cube. That's it! Now you're a 3D programmer. Wasn't that easy?
Objects in 3D space are usually built as a collection of polygons. Polygons
can be defined in world space or object space. Once defined, polygons can be
translated, rotated, and projected into a virtual buffer. The
virtual buffer is then blitted to the screen. Incorporating this technology into
a Windows program is not difficult. Fastgraph can greatly simplify the process.
Chapter 1 | Chapter 2 | Chapter 3
Chapter 4 | Chapter 5 | Chapter 6 | Chapter 7
Appendix 1 | Appendix 2 | Appendix 3
Fastgraph Home Page