No Title
The code samples shown in this section provide only very rudimentary error handling. You should read the chapter "Error Manager" to learn how to write and register an application-defined error-handling routine, or how to determine explicitly which errors have occurred during the execution of QuickDraw3D routines.<8bat>s
QuickDraw3D currently is supported only on PowerPC-based Macintosh computers. It exists as a shared library, in two forms. A debugging version
is available for use by developers while writing their applications or other software products. An optimized version of the QuickDraw3D shared library is available for end users of those applications and other products. The debugging version provides more extensive information than the optimized version. For instance, the debugging version of QuickDraw3D issues errors, warnings, and notices at the appropriate times; the optimized version issues only errors and warnings.
int
data type, where an int
value is 4 bytes. If your application passes a value of some other size or type for one of those constants, it's likely that QuickDraw3D will not correctly interpret that value. Accordingly, if the default setting of your compiler does not make enumerated constants to be of type int
, you must override that default setting, typically by including pragma
directives in your source code or by using an appropriate compiler option.There are currently three important compiler settings:
int
data type.
char
or short
that are contained in an array that is contained in a structure may be aligned on non-longword boundaries.
long
, float
, or double
are aligned on longword boundaries.
QD3D.h
contains compiler pragmas for several popular C compilers. For example, QD3D.h
contains this line for the PPCC compiler, specifying field alignment on longword boundaries for pointers or data of long
, float
, or double
:
#pragma options align=powerSome compilers might not provide pragmas for the three important compiler settings listed above. For example, the PPCC compiler does not currently provide a pragma for setting the size of enumerated constants. PPCC does however support the
-enums
compiler option, which you can use to set the size of a enumerated constants.Consult the documentation for your compiler to determine how to specify the size of enumerated constants and to configure structure field alignment so as to conform to the settings of QuickDraw3D.<8bat>s
On the Macintosh Operating System, you can verify that QuickDraw3D is available by calling the MyEnvironmentHasQuickDraw3D
function defined in Listing 1-1.
Listing 1-1 Determining whether QuickDraw3D is available
Boolean MyEnvironmentHasQuickDraw3D (void) { return((Boolean) Q3Initialize != kUnresolvedSymbolAddress); }The
MyEnvironmentHasQuickDraw3D
function checks to see whether the address of the Q3Initialize
function has been resolved. If it hasn't been resolved (that is, if the Code Fragment Manager couldn't find the QuickDraw3D shared library when launching your application), MyEnvironmentHasQuickDraw3D
returns the value FALSE
to its caller. Otherwise, if the address of the Q3Initialize
function was successfully resolved, MyEnvironmentHasQuickDraw3D
returns TRUE
.
For the function MyEnvironmentHasQuickDraw3D
to work properly, you must establish soft links (also called weak links) between your application and the QuickDraw3D shared library. For information on soft links, see the book Inside Macintosh: PowerPC System Software. For specific information on establishing soft links, see the documentation for your software development system.<8bat>u
On the Macintosh Operating System, you can verify that QuickDraw3D is available in the current operating environment by calling the Gestalt
function with the gestaltQD3D
selector. Gestalt
returns a long word whose value indicates the availability of QuickDraw3D. Currently these values are defined:
enum { gestaltQD3DNotPresent = 0, gestaltQD3DAvailable = 1 }You should ensure that the value
gestaltQD3DAvailable
is returned before calling any QuickDraw3D routines.
For more information on the Gestalt
function, see Inside Macintosh: Operating System Utilities.<8bat>u
You create and initialize a connection to the QuickDraw3D software by calling the Q3Initialize
function, as illustrated in Listing 1-2.
Listing 1-2 Initializing a connection with QuickDraw3D
OSErr MyInitialize (void) { TQ3Status myStatus; myStatus = Q3Initialize(); /*initialize QuickDraw3D*/ if (myStatus == kQ3Failure) DebugStr("\pQ3Initialize returned failure."); return (noErr); }Once you've successfully called
Q3Initialize
, you can safely call other QuickDraw3D routines. If Q3Initialize
returns unsuccessfully (as kQ3Failure
result code), you shouldn't call any QuickDraw3D routines other than the error-reporting routines (such as Q3Error_Get
or Q3Error_IsFatalError
) or the Q3IsInitialized
function. See the chapter "Error Manager" for details on QuickDraw3D's error-
When you have finished using QuickDraw3D, you should call Q3Exit
to close your connection with QuickDraw3D. In most cases, you'll do this when terminating your application. Listing 1-3 illustrates how to call Q3Exit
.
Listing 1-3 Terminating QuickDraw3D
void MyFinishUp (void) { TQ3Status myStatus; myStatus = Q3Exit(); /*unload QuickDraw3D*/ if (myStatus == kQ3Failure) DebugStr("\pQ3Exit returned failure."); }
Objects in QuickDraw3D are defined using a Cartesian coordinate system that is right-handed (that is, if the thumb of the right hand points in the direction of the positive x axis and the index finger points in the direction of the positive y axis, then the middle finger, when made perpendicular to the other two fingers, points in the direction of the positive z axis). Figure 1-5 shows a right-handed coordinate system.
For a more complete description of the coordinate spaces used by QuickDraw3D, see the chapter "Transform Objects" later in this book.<8bat>u
Figure 1-5 A right-handed Cartesian coordinate system
The model created by the MyNewModel
function defined in Listing 1-4 consists of a number of boxes that spell out the words "Hello World." The words are written in block letters, with each letter composed of a number of individual boxes. MyNewModel
uses the inelegant but straightforward method of defining the 34 boxes by creating four arrays of 34 elements each. As you'll see later (in the chapter "Geometric Objects"), a box is defined by four pieces of information, an origin and three vectors that specify its sides:
typedef struct TQ3BoxData { TQ3Point3D origin; TQ3Vector3D orientation; TQ3Vector3D majorAxis; TQ3Vector3D minorAxis; TQ3AttributeSet *faceAttributeSet; TQ3AttributeSet boxAttributeSet; } TQ3BoxData;First,
MyNewModel
creates a new and empty ordered display group to contain all the boxes. Then the function loops through the data arrays, creating boxes and adding them to the group.
TQ3GroupObject MyNewModel (void) { TQ3GroupObject myModel; TQ3GeometryObject myBox; TQ3BoxData myBoxData; TQ3GroupPosition myGroupPosition; /*Data for boxes comprising Hello and World block letters.*/ long i; float xorigin[34] = { -12.0, -9.0, -11.0, -7.0, -6.0, -6.0, -6.0, -2.0, -1.0, 3.0, 4.0, 8.0, 9.0, 9.0, 11.0, -13.0, -12.0, -11.0, -9.0, -7.0, -6.0, -6.0, -4.0, -2.0, -1.0, -1.0, 1.0, 1.0, 3.0, 4.0, 8.0, 9.0, 9.0, 11.0}; float yorigin[34] = { 0.0, 0.0, 3.0, 0.0, 6.0, 3.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 6.0, 0.0, 0.0, -8.0, -8.0, -7.0, -8.0, -8.0, -8.0, -2.0, -8.0, -8.0, -2.0, -5.0, -4.0, -8.0, -8.0, -8.0, -8.0, -8.0, -2.0, -7.0}; float height[34] = { 7.0, 7.0, 1.0, 7.0, 1.0, 1.0, 1.0, 7.0, 1.0, 7.0, 1.0, 7.0, 1.0, 1.0, 7.0, 7.0, 1.0, 3.0, 7.0, 7.0, 1.0, 1.0, 7.0, 7.0, 1.0, 1.0, 2.0, 3.0, 7.0, 1.0, 7.0, 1.0, 1.0, 5.0}; float width[34] = { 1.0, 1.0, 2.0, 1.0, 3.0, 2.0, 3.0, 1.0, 3.0, 1.0, 3.0, 1.0, 2.0, 2.0, 1.0, 1.0, 3.0, 1.0, 1.0, 1.0, 2.0, 2.0, 1.0, 1.0, 2.0, 2.0, 1.0, 1.0, 1.0, 3.0, 1.0, 2.0, 2.0, 1.0}; /*Create an ordered display group for the complete model.*/ myModel = Q3OrderedDisplayGroup_New(); if (myModel == NULL) goto bail; /*Add all the boxes to the model.*/ myBoxData.faceAttributeSet = NULL; myBoxData.boxAttributeSet = NULL; for (i=0; i<34; i++) { Q3Point3D_Set(&myBoxData.origin, xorigin[i], yorigin[i], 1.0); Q3Vector3D_Set(&myBoxData.orientation, 0, height[i], 0); Q3Vector3D_Set(&myBoxData.minorAxis, width[i], 0, 0); Q3Vector3D_Set(&myBoxData.majorAxis, 0, 0, 2); myBox = Q3Box_New(&myBoxData); myGroupPosition = Q3Group_AddObject(myModel, myBox); /*now that myBox has been added to group, dispose of our reference*/ Q3Object_Dispose(myBox); if (myGroupPosition == NULL) goto bail; } return (myModel); /*return the completed model*/ bail: /*If any of the above failed, then return an empty model.*/ return (NULL); }The
MyNewModel
function can leak memory. Your application should use a different error-recovery
If successful, MyNewModel
returns the group object containing the 34 boxes to
its caller.
struct WindowInfo { TQ3ViewObject view; TQ3GroupObject model; TQ3ShaderObject illumination; TQ3StyleObject interpolation; TQ3StyleObject backfacing; TQ3StyleObject fillstyle; }; typedef struct WindowInfo WindowInfo, *WindowInfoPtr, **WindowInfoHandle;A standard way to attach an application-defined data structure (such as the
WindowInfo
structure) to a window is to set a handle to that structure as For a more complete description of using a window's reference constant to maintain window-specific information, see the discussion of document records in Inside Macintosh: Overview.<8bat>u
Listing 1-5 Creating a new window and attaching a window information structure
void MyNewWindow (void) { WindowPtr myWindow; Rect myBounds = {42, 4, 442, 604}; WindowInfoHandle myWinfo; /*Create new window.*/ myWindow = NewCWindow(0L, &myBounds, "\pWindow!", 1, documentProc, (WindowPtr) -1, true, 0L); if (myWindow == NULL) goto bail; SetPort(myWindow); /*Create storage for the new window and attach it to window.*/ myWinfo = (WindowInfoHandle) NewHandle(sizeof(WindowInfo)); if (myWinfo == NULL) goto bail; SetWRefCon(myWindow, (long) myWinfo); HLock((Handle) myWinfo); /*Create a new view.*/ (**myWinfo).view = MyNewView(myWindow); if ((**myWinfo).view == NULL) goto bail; /*Create model to display.*/ (**myWinfo).model = MyNewModel(); /*see Listing 1-4 on page 120*/ if ((**myWinfo).model == NULL) goto bail; /*Configure an illumination shader.*/ (**myWinfo).illumination = Q3PhongIllumination_New(); if ((**myWinfo).illumination == NULL) goto bail; /*Configure the rendering styles.*/ (**myWinfo).interpolation = Q3InterpolationStyle_New(kQ3InterpolationStyleNone); if ((**myWinfo).interpolation == NULL) goto bail; (**myWinfo).backfacing = Q3BackfacingStyle_New(kQ3BackfacingStyleRemoveBackfacing); if ((**myWinfo).backfacing == NULL) goto bail; (**myWinfo).fillstyle = Q3FillStyle_New(kQ3FillStyleFilled); if ((**myWinfo).fillstyle == NULL) goto bail; HUnlock((Handle) myWinfo); return; bail: /*If failed for any reason, then close the window.*/ if (myWinfo != NULL) DisposeHandle((Handle) myWinfo); if (myWindow != NULL) DisposeWindow(myWindow); }The
MyNewWindow
function creates a new window and a new window information structure, attaches the structure to the window, and then fills out several fields of that structure. In particular, MyNewWindow
creates a new illumination shader that implements a Phong illumination model. You need an illumination shader for a view's lights to have any effect. (See the chapter "Shader Objects" for complete information on the available illumination shaders.) Then MyNewWindow
disables interpolation between vertices of faces, removes unseen backfaces of objects in the model, and sets the renderer to render filled faces on those objects. These settings are actually passed to the renderer by submitting the styles during rendering. See "Rendering a Model," beginning on page 1-31 for details.
The MyNewWindow
function can leak memory. Your application should use a different error-recovery
strategy than is used in Listing 1-5.<8bat>u
MyNewLights
defined in Listing 1-6 creates a group of lights. It creates an ambient light, a point light, and a directional light. See the chapter "Error Manager" for more details on creating lights.Listing 1-6 Creating a group of lights
TQ3GroupObject MyNewLights (void) { TQ3GroupPosition myGroupPosition; TQ3GroupObject myLightList; TQ3LightData myLightData; TQ3PointLightData myPointLightData; TQ3DirectionalLightData myDirLightData; TQ3LightObject myAmbientLight, myPointLight, myFillLight; TQ3Point3D pointLocation = { -10.0, 0.0, 10.0 }; TQ3Vector3D fillDirection = { 10.0, 0.0, 10.0 }; TQ3ColorRGB WhiteLight = { 1.0, 1.0, 1.0 }; /*Set up light data for ambient light.*/ myLightData.isOn = kQ3True; myLightData.brightness = .2; myLightData.color = WhiteLight; /*Create ambient light.*/ myAmbientLight = Q3AmbientLight_New(&myLightData); if (myAmbientLight == NULL) goto bail; /*Create a point light.*/ myLightData.brightness = 1.0; myPointLightData.lightData = myLightData; myPointLightData.castsShadows = kQ3False; myPointLightData.attenuation = kQ3AttenuationTypeLinear; myPointLightData.location = pointLocation; myPointLight = Q3PointLight_New(&myPointLightData); if (myPointLight == NULL) goto bail; /*Create a directional light for fill.*/ myLightData.brightness = .2; myDirLightData.lightData = myLightData; myDirLightData.castsShadows = kQ3False; myDirLightData.direction = fillDirection; myFillLight = Q3DirectionalLight_New(&myDirLightData); if (myFillLight == NULL) goto bail; /*Create light group and add each of the lights to the group.*/ myLightList = Q3LightGroup_New(); if (myLightList == NULL) goto bail; myGroupPosition = Q3Group_AddObject(myLightList, myAmbientLight); Q3Object_Dispose(myAmbientLight); /*balance the reference count*/ if (myGroupPosition == 0) goto bail; myGroupPosition = Q3Group_AddObject(myLightList, myPointLight); Q3Object_Dispose(myPointLight); /*balance the reference count*/ if (myGroupPosition == 0) goto bail; myGroupPosition = Q3Group_AddObject(myLightList, myFillLight); Q3Object_Dispose(myFillLight); /*balance the reference count*/ if (myGroupPosition == 0) goto bail; return (myLightList); bail: /*If any of the above failed, then return nothing!*/ return (NULL); }The
MyNewLights
function is straightforward. It fills out the fields of the relevant data structures (TQ3LightData
, TQ3PointLightData
, and TQ3DirectionalLightData
) and calls the appropriate functions to create new light objects using the information in those structures. If successful, it adds those light objects to a group of lights. The group of lights will be added to a view, as shown in the following section.
The MyNewLights
function can leak memory.<8bat>u
Listing 1-7 Creating a Macintosh draw context
TQ3DrawContextObject MyNewDrawContext (WindowPtr theWindow) { TQ3DrawContextObject myDrawContext; TQ3DrawContextData myDrawContextData; TQ3MacDrawContextData myMacDrawContextData; TQ3ColorARGB myClearColor; /*Set the background color.*/ Q3ColorARGB_Set(&myClearColor, 1.0, 0.6, 0.9, 0.9); /*Fill in draw context data.*/ myDrawContextData.clearImageMethod = kQ3ClearMethodWithColor; myDrawContextData.clearImageColor = myClearColor; myDrawContextData.paneState = kQ3False; myDrawContextData.maskState = kQ3False; myDrawContextData.doubleBufferState = kQ3True; /*Fill in Macintosh-specific draw context data.*/ myMacDrawContextData.drawContextData = myDrawContextData; myMacDrawContextData.window = (CWindowPtr) theWindow; myMacDrawContextData.library = kQ3Mac2DLibraryNone; myMacDrawContextData.viewPort = NULL; myMacDrawContextData.grafPort = NULL; /*Create draw context.*/ myDrawContext = Q3MacDrawContext_New(&myMacDrawContextData); return (myDrawContext); }Essentially,
MyNewDrawContext
just fills in the fields of a TQ3MacDrawContextData
structure and calls Q3MacDrawContext_New
to MyNewCamera
.
TQ3CameraObject MyNewCamera (void) { TQ3CameraObject myCamera; TQ3CameraData myCameraData; TQ3ViewAngleAspectCameraData myViewAngleCameraData; TQ3Point3D cameraFrom = { 0.0, 0.0, 15.0 }; TQ3Point3D cameraTo = { 0.0, 0.0, 0.0 }; TQ3Vector3D cameraUp = { 0.0, 1.0, 0.0 }; /*Fill in camera data.*/ myCameraData.placement.cameraLocation = cameraFrom; myCameraData.placement.pointOfInterest = cameraTo; myCameraData.placement.upVector = cameraUp; myCameraData.range.hither = .1; myCameraData.range.yon = 15.0; myCameraData.viewPort.origin.x = -1.0; myCameraData.viewPort.origin.y = 1.0; myCameraData.viewPort.width = 2.0; myCameraData.viewPort.height = 2.0; myViewAngleCameraData.cameraData = myCameraData; myViewAngleCameraData.fov = Q3Math_DegreesToRadians(100.0); myViewAngleCameraData.aspectRatioXToY = 1; myCamera = Q3ViewAngleAspectCamera_New(&myViewAngleCameraData); /*Return a camera.*/ return (myCamera); }Like before, the
MyNewCamera
function simply fills out the fields of the appropriate data structures and calls the Q3ViewAngleAspectCamera_New
function to create a new camera object.
All angles in QuickDraw3D are specified in radians. You can use the Q3Math_DegreesToRadians
macro to convert degrees to radians, as illustrated in Listing 1-8, which sets the fov
field to 100 degrees.<8bat>s
To create an image in a window, a view must contain at least a camera, a renderer, and a draw context.<8bat>s
TQ3ViewObject MyNewView (WindowPtr theWindow) { TQ3Status myStatus; TQ3ViewObject myView; TQ3DrawContextObject myDrawContext; TQ3RendererObject myRenderer; TQ3CameraObject myCamera; TQ3GroupObject myLights; myView = Q3View_New(); if (myView == NULL) goto bail; /*Create and set draw context.*/ myDrawContext = MyNewDrawContext(theWindow); if (myDrawContext == NULL) goto bail; myStatus = Q3View_SetDrawContext(myView, myDrawContext); Q3Object_Dispose(myDrawContext); if (myStatus == kQ3Failure) goto bail; /*Create and set renderer.*/ myRenderer = Q3Renderer_NewFromType(kQ3RendererTypeInteractive); if (myRenderer == NULL) goto bail; myStatus = Q3View_SetRenderer(myView, myRenderer); Q3Object_Dispose(myRenderer); if (myStatus == kQ3Failure) goto bail; /*Create and set camera.*/ myCamera = MyNewCamera(); if (myCamera == NULL) goto bail; myStatus = Q3View_SetCamera(myView, myCamera); Q3Object_Dispose(myCamera); if (myStatus == kQ3Failure) goto bail; /*Create and set lights.*/ myLights = MyNewLights(); if (myLights == NULL) goto bail; myStatus = Q3View_SetLightGroup(myView, myLights); Q3Object_Dispose(myLights); if (myStatus == kQ3Failure) goto bail; return (myView); bail: /*If any of the above failed, then don't return a view.*/ return (NULL); }
Q3View_StartRendering
function and should end when a call to the Q3View_EndRendering
function returns some value other than kQ3ViewStatusRetraverse
. Within the body of the rendering loop, you should submit the shapes you want rendered. Listing 1-10 shows the general structure of a rendering loop.Listing 1-10 A basic rendering loop
Q3View_StartRendering(myView); do { /*Submit your shape objects here.*/ Q3DisplayGroup_Submit(myGroup, myView); } while (Q3View_EndRendering(myView) == kQ3ViewStatusRetraverse);The
Q3View_EndRendering
function returns a view status value that indicates whether the renderer has finished processing the model. The available view status values are defined by these constants:
typedef enum { kQ3ViewStatusDone, kQ3ViewStatusRetraverse, kQ3ViewStatusError, kQ3ViewStatusCancelled } TQ3ViewStatus;Listing 1-11 illustrates how to render the model defined in Listing 1-4 (page 1-20), using the view created and configured in Listing 1-9 (page 1-30). The
MyDraw
function defined in Listing 1-11 retrieves the window information structure attached to a window and uses the information in it to render Listing 1-11 Rendering a model
void MyDraw (WindowPtr theWindow) { WindowInfoHandle myWinfo; TQ3Status myStat; TQ3DrawContextObject myDrawContext; TQ3ViewStatus myViewStatus; if (theWindow == NULL) return; myWinfo = (WindowInfoHandle) GetWRefCon(theWindow); HLock((Handle) myWinfo); /*Start rendering.*/ myStat = Q3View_StartRendering((**myWinfo).view); if (myStat == kQ3Failure) goto bail; do { myStat = Q3Shader_Submit((**myWinfo).illumination, (**myWinfo).view); if (myStat == kQ3Failure) goto bail; myStat = Q3Style_Submit((**myWinfo).interpolation, (**myWinfo).view); if (myStat == kQ3Failure) goto bail; myStat = Q3Style_Submit((**myWinfo).backfacing, (**myWinfo).view); if (myStat == kQ3Failure) goto bail; myStat = Q3Style_Submit((**myWinfo).fillstyle, (**myWinfo).view); if (myStat == kQ3Failure) goto bail; myStat = Q3DisplayGroup_Submit((**myWinfo).model, (**myWinfo).view); if (myStat == kQ3Failure) goto bail; myViewStatus = Q3View_EndRendering((**myWinfo).view); } while (myViewStatus == kQ3ViewStatusRetraverse); HUnlock((Handle) myWinfo); return; bail: HUnlock((Handle) myWinfo); SysBeep(50); }The rendering loop allows your application to work with any current and future renderers that require multiple passes through a model's data in order to provide features such as transparency and CSG.
For complete information about rendering loops and other kinds of submitting loops, see the chapter "View Objects" in this book.
Let us know what you think of these prototype pages.
Generated with Harlequin WebMaker