September 95 - The Basics of QuickDraw 3D Geometries
The Basics of QuickDraw 3D Geometries
Nick Thompson and Pablo Fernicola
No matter how realistic or sophisticated you want your 3D images to be, you must
always build objects with the primitive geometric shapes provided by the graphics
system. Our article in Issue 22 gave the basic information you need to start developing
applications with QuickDraw 3D. Here we delve deeper into the primitive geometric
shapes provided by QuickDraw 3D and show how to use them effectively. We also give
you some tips we've gained from working with developers.
Geometric shapes -- or geometries -- form the foundation of any 3D scene.
QuickDraw 3D provides a rich set of primitive geometric types that you use to define
the shapes of things. You can apply attributes (such as colors) to geometric objects,
collect geometric objects into groups, and copy, illuminate, texture, transform, or
otherwise modify them to attain the visual effects you want. In other words,
everything that's drawn by QuickDraw 3D is either a geometry or a modification of a
geometry. So you need to know how to define geometries (and usually also how to create
and dispose of them) to work effectively with QuickDraw 3D. This article describes the
geometries available in QuickDraw 3D version 1.0 and shows how they relate to other
aspects of the QuickDraw 3D architecture (such as the class hierarchy).
We're assuming that you're already familiar with the basic capabilities of QuickDraw
3D. For a good introduction, see our article "QuickDraw 3D: A New Dimension for
Macintosh Graphics" in Issue 22 of develop (a copy is on this issue's CD). In that
article, we provided an overview of QuickDraw 3D's architecture and capabilities. You
can think of QuickDraw 3D as having three main parts: graphics, I/O (the QuickDraw
3D metafile), and human interface guidelines. Here, we provide more detail on the
graphics portion of the QuickDraw 3D API and highlight some parts of that API that
could use clarification as you try to implement geometries.
To help you get started using geometries, this issue's CD contains version 1.0 of the
QuickDraw 3D shared library and programming interfaces, sample code, and an
electronic version of the book 3D Graphics Programming With QuickDraw 3D, which
provides complete documentation for the QuickDraw 3D programming interfaces.
A WORD ABOUT RENDERING AND SUBMITTING
Our previous article included an introduction to rendering; we'll review a key concept
here -- retained vs. immediate rendering. We'll also elaborate on an important point
we glossed over in that article: submitting something to be rendered rather than just
rendering it. These concepts will help set the stage for what you'll learn here about
working with geometries.
RETAINED VS. IMMEDIATE MODE RENDERING
A powerful feature of QuickDraw 3D is that it supports both retained and immediate
modes for rendering geometric data; you can even mix these modes within the same
rendering loop. In retained mode, the definition and storage of the geometric data are
kept internal to QuickDraw 3D -- as abstract geometric objects. In immediate mode,
the application keeps the only copy of the geometric data; for efficiency, the
application should use QuickDraw 3D data structures to hold the data, but those
structures can be embedded in application-defined structures. Retained mode
geometric objects and immediate mode geometric data define the shapes of objects.
You'll typically use one or more primitive geometric types provided by QuickDraw 3D
(such as triangles or meshes) to build up a scene.
Whether you use retained or immediate mode to render geometries usually depends on
how much of a model changes from one rendering operation to the next. As we'll
illustrate with examples in this section, we prefer to use retained geometries most of
the time and to use immediate mode only for temporary objects. Since our preference
for retained mode is a departure from the traditional QuickDraw way of drawing, we'll
attempt to convince you that retained mode is a much more efficient method of
rendering geometries.
Immediate mode. When you use immediate mode rendering, the data that defines a
geometry is stored and managed by your application. For example, to draw a triangle
you would write code similar to that in Listing 1. If you wanted to draw this triangle
many times, or from different camera angles, you would have to maintain the data in
your application's data structures.
Listing 1. Rendering a triangle in immediate mode
TQ3TriangleData myTriangle;
// Set up the triangle with appropriate data.
...
// Render the triangle.
Q3View_StartRendering(myView);
Q3Triangle_Submit(&myTriangle, myView);
} while (Q3View_EndRendering(myView) == kQ3ViewStatusRetraverse);
Typically when using immediate mode, you stick to a single type of geometry
(triangles are popular with developers accustomed to lower-level 3D graphics
libraries). If you use multiple geometric types, you need to define a data structure to
manage the order of the geometries. An example of rendering several geometries in
immediate mode is shown in Listing 2.
Listing 2. Rendering several geometries in immediate mode
typedef struct myGeometryStructure {
TQ3ObjectType type;
void *geom;
struct myGeometryStructure *next;
myGeometryStructure *currentGeometry;
...
Q3View_StartRendering(myView);
do {
while (currentGeometry != NULL) {
switch (currentGeometry->type) {
case kQ3GeometryTypeTriangle:
Q3Triangle_Submit(
(TQ3TriangleData *) currentGeometry->geom, myView);
break;
case kQ3GeometryTypePolygon:
Q3Polygon_Submit(
(TQ3PolygonData *) currentGeometry->geom, myView);
break;
}
currentGeometry = currentGeometry->next;
}
} while (Q3View_EndRendering(myView) == kQ3ViewStatusRetraverse);
If you wanted to apply transforms to a geometry as it's being drawn, you would have to
add a new case to the switch statement. This gets complicated pretty quickly. As a
result, many developers, when given a choice, will use immediate mode only for
models that have a fixed geometry and are not being altered.
Retained mode. Creating geometric objects allows renderers to take advantage of
characteristics of particular geometries and thus optimize the drawing code. The code
in Listing 3 draws a triangle in retained mode.
Listing 3. Rendering a triangle in retained mode
TQ3TriangleData triangleData;
// Set up the triangle with appropriate data.
...
// Create the triangle.
triangleObject = Q3Triangle_New(&triangleData);
// Render the triangle.
Q3View_StartRendering(myView);
Q3Object_Submit(triangleObject, myView);
} while (Q3View_EndRendering(myView) == kQ3ViewStatusRetraverse);
SUBMITTING
You'll notice that the routine to draw an object is Q3Object_Submit. This probably
seems a bit strange: why didn't we call it Q3Object_Draw? The reason is that there are
four occasions in which you need to specify a geometry -- when writing data to a file,
when picking, when determining the bounds of a geometry, and when rendering -- and
QuickDraw 3D provides a single routine that you use in all of these cases. To indicate
which operation you want to perform, you call the Submit routine inside a loop that
begins and ends with the appropriate calls. For instance, to render a model, you call
Submit functions inside a rendering loop, which begins with a call to
Q3View_StartRendering and ends with a call to Q3View_EndRendering (as shown in
Listing 3). Similarly, to write a model to a file, you call Submit functions inside a
writing loop, which begins with a call to Q3View_StartWriting and ends with a call to
Q3View_EndWriting.
We recommend that you put all your Submit calls together within a single function
(such as the one shown in Listing 4) that you can then call from your rendering loop,
picking loop, writing loop, or bounding loop. Organizing your code in this fashion will
prevent a common mistake: creating rendering loops that are out of sync with picking
or bounding loops. It also simplifies your rendering and picking loops -- you just call