by Michael Sweet
WHAT YOU'LL LEARN IN THIS CHAPTER:
How To | Functions You'll Use |
---|---|
Choose appropriate pixel formats for OpenGL rendering |
|
Manage OpenGL drawing contexts |
|
Do double-buffered drawing |
|
Create bitmap text fonts |
|
This chapter discusses the OpenGL interfaces on MacOS X. We cover both AGL and NSOpenGL, the Carbon and Cocoa interfaces, respectively. You learn how to create and manage OpenGL contexts as well as how to create OpenGL drawing areas for Carbon and Cocoa applications. This chapter also shows you how to use GLUT on MacOS X with all the examples in this book.
OpenGL on MacOS X is exposed via three APIs: AGL for Carbon applications, NSOpenGL for Cocoa applications, and CGL for applications that need direct access to the screen. This chapter discusses using AGL and NSOpenGL; the CGL API provides similar functionality, but because it bypasses the windowing system (no OpenGL in windows, only full screen), we do not cover it. There is, however, a tutorial for CGL on the book's Web site (see Appendix A, “Further Reading”).
In addition to the three standard APIs, the Apple developer tools also include a GLUT port that can be used to compile and use any of the GLUT-based examples in this book.
All the APIs discussed in this chapter are provided via frameworks in MacOS X. A framework is a collection of header files and libraries that provide specific functionality. Table 14.1 lists the OpenGL-related frameworks discussed in this chapter.
Table 14.1. OpenGL-Related Frameworks in MacOS X
The GLUT API is provided in the OpenGL examples folder of the Apple Developer Tools CD-ROM. To use the GLUT API, copy this folder to disk and then add the GLUT framework in the directory to the list of frameworks in the Xcode window for your application. Use the following linker options if you are building your software with a makefile:
-framework /path/to/GLUT.framework -framework AGL -framework OpenGL -framework Carbon -framework ApplicationServices
The AGL and Carbon APIs provide a relatively simple interface for C and C++ programs to display OpenGL graphics. Because Carbon-based applications are supported on MacOS 8 through X, you can use the AGL and Carbon APIs to develop applications that work on multiple versions of MacOS.
Programs of this type require the AGL, ApplicationServices, Carbon, and OpenGL frameworks; you can start with a Carbon-based application in the Xcode application and add the AGL and OpenGL frameworks or use the following linker options:
-framework AGL -framework OpenGL -framework Carbon -framework ApplicationServices
As you'll see in the following sections, AGL functions start with the prefix agl
.
AGL exposes multiple pixel formats that provide different rendering capabilities. For example, a particular graphics card might be able to provide full-scene antialiasing with a 16-bit depth buffer but not with a 24-bit or 32-bit depth buffer due to limited memory or other implementation-specific issues.
The aglChoosePixelFormat
function allows an application to choose an appropriate pixel format using a list of integer attributes describing the desired output capabilities. Table 14.2 lists the attribute constants defined by the AGL API.
Table 14.2. AGL Pixel Format Attribute List Constants
You might use the following code to find a double-buffered RGB pixel format:
GLint attributes[] = { AGL_RGBA, AGL_DOUBLEBUFFER, AGL_RED_SIZE, 4, AGL_GREEN_SIZE, 4, AGL_BLUE_SIZE, 4, AGL_DEPTH_SIZE, 16, AGL_NONE }; AGLPixelFormat format; format = aglChoosePixelFormat(NULL, 0, attributes);
Note that the last value is required to be AGL_NONE
. After aglChoosePixelFormat
is called, an AGLPixelFormat
value is returned; this value provides information on the correct pixel format to use. If no matching pixel format can be found, a NULL
pointer is returned, and you should retry with different attributes or inform the users that the window cannot be displayed on their system.
AGL provides four functions for managing OpenGL rendering contexts: aglCreateContext
, aglDestroyContext
, aglSetCurrentContext
, and aglSetDrawable
. The aglCreateContext
function creates an OpenGL context using the AGLPixelFormat
value returned by the aglChoosePixelFormat
function:
AGLContext context; context = aglCreateContext(format, NULL);
The aglCreateContext
function accepts two arguments: the pixel format and a context to share display list, texture object, and vertex array information with. Pass NULL
if you do not want to share information with another context.
After you have created the context, it needs to be bound to a window or offscreen buffer using the aglSetDrawable
function:
WindowPtr window; aglSetDrawable(context, GetWindowPort(window));
Next, call the aglSetCurrentContext
function to use the context to do OpenGL rendering:
aglSetCurrentContext(context);
Finally, when you are done using the context, call the aglDestroyContext
function to release the resources that were used by the context:
aglDestroyContext(context);
You enable double-buffered rendering by using the appropriate pixel format. The drawing code in your program then merely needs to call the aglSwapBuffers
function after doing any OpenGL rendering to make it visible:
aglSwapBuffers(context);
Listing 14.1 shows a basic Carbon application that creates a window for OpenGL rendering and displays a spinning cube. The application responds to mouse clicks and drags to alter the spinning of the cube and exits after the user closes the window. Figure 14.1 shows the result.
Example 14.1. The CARBON
Sample Program
/* * Include necessary headers... */ #include <stdio.h> #include <stdlib.h> #include <Carbon/Carbon.h> #include <AGL/agl.h> /* * Globals... */ float CubeRotation[3], /* Rotation of cube */ CubeRate[3]; /* Rotation rate of cube */ int CubeMouseButton, /* Button that was pressed */ CubeMouseX, /* Start X position of mouse */ CubeMouseY; /* Start Y position of mouse */ int CubeX, /* X position of window */ CubeY, /* Y position of window */ CubeWidth, /* Width of window */ CubeHeight; /* Height of window */ int CubeVisible; /* Is the window visible? */ AGLContext CubeContext; /* OpenGL context */ /* * Functions... */ void DisplayFunc(void); static pascal OSStatus EventHandler(EventHandlerCallRef nextHandler, EventRef event, void *userData); void IdleFunc(void); void MotionFunc(int x, int y); void MouseFunc(int button, int state, int x, int y); void ReshapeFunc(int width, int height); /* * 'main()' - Main entry for example program. */ int /* O - Exit status */ main(int argc, /* I - Number of command-line args */ char *argv[]) /* I - Command-line args */ { AGLPixelFormat format; /* OpenGL pixel format */ WindowPtr window; /* Window */ int winattrs; /* Window attributes */ Str255 title; /* Title of window */ Rect rect; /* Rectangle definition */ EventHandlerUPP handler; /* Event handler */ EventLoopTimerUPP thandler; /* Timer handler */ EventLoopTimerRef timer; /* Timer for animating the window */ ProcessSerialNumber psn; /* Process serial number */ static EventTypeSpec events[] = /* Events we are interested in... */ { { kEventClassMouse, kEventMouseDown }, { kEventClassMouse, kEventMouseUp }, { kEventClassMouse, kEventMouseMoved }, { kEventClassMouse, kEventMouseDragged }, { kEventClassWindow, kEventWindowDrawContent }, { kEventClassWindow, kEventWindowShown }, { kEventClassWindow, kEventWindowHidden }, { kEventClassWindow, kEventWindowActivated }, { kEventClassWindow, kEventWindowDeactivated }, { kEventClassWindow, kEventWindowClose }, { kEventClassWindow, kEventWindowBoundsChanged } }; static GLint attributes[] = /* OpenGL attributes */ { AGL_RGBA, AGL_GREEN_SIZE, 1, AGL_DOUBLEBUFFER, AGL_DEPTH_SIZE, 16, AGL_NONE }; //Set initial values for window const int origWinHeight = 628; const int origWinWidth = 850; const int origWinXOffset = 50; const int origWinYOffset = 50; /* * Create the window... */ CubeContext = 0; CubeVisible = 0; SetRect(&rect, origWinXOffset, origWinYOffset, origWinWidth, origWinHeight); winattrs = kWindowStandardHandlerAttribute | kWindowCloseBoxAttribute | kWindowCollapseBoxAttribute | kWindowFullZoomAttribute | kWindowResizableAttribute | kWindowLiveResizeAttribute; winattrs &= GetAvailableWindowAttributes(kDocumentWindowClass); strcpy(title + 1, "Carbon OpenGL Example"); title[0] = strlen(title + 1); CreateNewWindow(kDocumentWindowClass, winattrs, &rect, &window); SetWTitle(window, title); handler = NewEventHandlerUPP(EventHandler); InstallWindowEventHandler(window, handler, sizeof(events) / sizeof(events[0]), events, NULL, 0L); thandler = NewEventLoopTimerUPP((void (*)(EventLoopTimerRef, void *))IdleFunc); InstallEventLoopTimer(GetMainEventLoop(), 0, 0, thandler, 0, &timer); GetCurrentProcess(&psn); SetFrontProcess(&psn); DrawGrowIcon(window); ShowWindow(window); /* * Create the OpenGL context and bind it to the window... */ format = aglChoosePixelFormat(NULL, 0, attributes); CubeContext = aglCreateContext(format, NULL); if (!CubeContext) { puts("Unable to get OpenGL context!"); return (1); } aglDestroyPixelFormat(format); aglSetDrawable(CubeContext, GetWindowPort(window)); /* * Setup remaining globals... */ CubeX = 50; CubeY = 50; CubeWidth = 400; CubeHeight = 400; CubeRotation[0] = 45.0f; CubeRotation[1] = 45.0f; CubeRotation[2] = 45.0f; CubeRate[0] = 1.0f; CubeRate[1] = 1.0f; CubeRate[2] = 1.0f; //Set the initial size of the cube ReshapeFunc(origWinWidth – origWinXOffset, origWinHeight – origWinYOffset); /* * Loop forever... */ for (;;) { if (CubeVisible) SetEventLoopTimerNextFireTime(timer, 0.05); RunApplicationEventLoop(); if (CubeVisible) { /* * Animate the cube... */ DisplayFunc(); } } } /* * 'DisplayFunc()' - Draw a cube. */ void DisplayFunc(void) { int i, j; /* Looping vars */ float aspectRatio,windowWidth, windowHeight; static const GLfloat corners[8][3] = /* Corner vertices */ { { 1.0f, 1.0f, 1.0f }, /* Front top right */ { 1.0f, -1.0f, 1.0f }, /* Front bottom right */ { -1.0f, -1.0f, 1.0f }, /* Front bottom left */ { -1.0f, 1.0f, 1.0f }, /* Front top left */ { 1.0f, 1.0f, -1.0f }, /* Back top right */ { 1.0f, -1.0f, -1.0f }, /* Back bottom right */ { -1.0f, -1.0f, -1.0f }, /* Back bottom left */ { -1.0f, 1.0f, -1.0f } /* Back top left */ }; static const int sides[6][4] = /* Sides */ { { 0, 1, 2, 3 }, /* Front */ { 4, 5, 6, 7 }, /* Back */ { 0, 1, 5, 4 }, /* Right */ { 2, 3, 7, 6 }, /* Left */ { 0, 3, 7, 4 }, /* Top */ { 1, 2, 6, 5 } /* Bottom */ }; static const GLfloat colors[6][3] = /* Colors */ { { 1.0f, 0.0f, 0.0f }, /* Red */ { 0.0f, 1.0f, 0.0f }, /* Green */ { 1.0f, 1.0f, 0.0f }, /* Yellow */ { 0.0f, 0.0f, 1.0f }, /* Blue */ { 1.0f, 0.0f, 1.0f }, /* Magenta */ { 0.0f, 1.0f, 1.0f } /* Cyan */ }; /* * Set the current OpenGL context... */ aglSetCurrentContext(CubeContext); /* * Clear the window... */ glViewport(0, 0, CubeWidth, CubeHeight); glClearColor(0.0f, 0.0f, 0.0f, 0.0f); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); /* * Setup the matrices... */ glMatrixMode(GL_PROJECTION); glLoadIdentity(); aspectRatio = (GLfloat)CubeWidth / (GLfloat)CubeHeight; if(CubeWidth <= CubeHeight) { windowWidth = 2.0f; windowHeight = 2.0f / aspectRatio; glOrtho(-2.0f, 2.0f, -windowHeight, windowHeight, 2.0f, -2.0f); } else { windowWidth = 2.0f * aspectRatio; windowHeight = 2.0f; glOrtho(-windowHeight, windowHeight, -2.0f, 2.0f, 2.0f, -2.0f); } glMatrixMode(GL_MODELVIEW); glLoadIdentity(); glRotatef(CubeRotation[0], 1.0f, 0.0f, 0.0f); glRotatef(CubeRotation[1], 0.0f, 1.0f, 0.0f); glRotatef(CubeRotation[2], 0.0f, 0.0f, 1.0f); /* * Draw the cube... */ glEnable(GL_DEPTH_TEST); glBegin(GL_QUADS); for (i = 0; i < 6; i ++) { glColor3fv(colors[i]); for (j = 0; j < 4; j ++) glVertex3fv(corners[sides[i][j]]); } glEnd(); /* * Swap the front and back buffers... */ aglSwapBuffers(CubeContext); } /* * 'EventHandler()' - Handle window and mouse events from the window manager. */ static pascal OSStatus // O - noErr on success or error code EventHandler(EventHandlerCallRef nextHandler, /* I - Next handler to call */ EventRef event, /* I - Event reference */ void *userData) /* I - User data (not used) */ { UInt32 kind; /* Kind of event */ Rect rect; /* New window size */ EventMouseButton button; /* Mouse button */ Point point; /* Mouse position */ kind = GetEventKind(event); if (GetEventClass(event) == kEventClassWindow) { switch (kind) { case kEventWindowDrawContent : if (CubeVisible && CubeContext) DisplayFunc(); break; case kEventWindowBoundsChanged : GetEventParameter(event, kEventParamCurrentBounds, typeQDRectangle, NULL, sizeof(Rect), NULL, &rect); CubeX = rect.left; CubeY = rect.top; if (CubeContext) aglUpdateContext(CubeContext); ReshapeFunc(rect.right - rect.left, rect.bottom - rect.top); if (CubeVisible && CubeContext) DisplayFunc(); break; case kEventWindowShown : CubeVisible = 1; if (CubeContext) DisplayFunc(); break; case kEventWindowHidden : CubeVisible = 0; break; case kEventWindowClose : ExitToShell(); break; } } else { switch (kind) { case kEventMouseDown : GetEventParameter(event, kEventParamMouseButton, typeMouseButton, NULL, sizeof(EventMouseButton), NULL, &button); GetEventParameter(event, kEventParamMouseLocation, typeQDPoint, NULL, sizeof(Point), NULL, &point); if (point.v < CubeY || (point.v > (CubeY + CubeHeight - 8) && point.h > (CubeX + CubeWidth - 8))) return (CallNextEventHandler(nextHandler, event)); MouseFunc(button, 0, point.h, point.v); break; case kEventMouseUp : GetEventParameter(event, kEventParamMouseButton, typeMouseButton, NULL, sizeof(EventMouseButton), NULL, &button); GetEventParameter(event, kEventParamMouseLocation, typeQDPoint, NULL, sizeof(Point), NULL, &point); if (point.v < CubeY || (point.v > (CubeY + CubeHeight - 8) && point.h > (CubeX + CubeWidth - 8))) return (CallNextEventHandler(nextHandler, event)); MouseFunc(button, 1, point.h, point.v); break; case kEventMouseDragged : GetEventParameter(event, kEventParamMouseLocation, typeQDPoint, NULL, sizeof(Point), NULL, &point); if (point.v < CubeY || (point.v > (CubeY + CubeHeight - 8) && point.h > (CubeX + CubeWidth - 8))) return (CallNextEventHandler(nextHandler, event)); MotionFunc(point.h, point.v); break; default : return (CallNextEventHandler(nextHandler, event)); } } /* * Return whether we handled the event... */ return (noErr); } /* * 'IdleFunc()' - Rotate and redraw the cube. */ void IdleFunc(void) { CubeRotation[0] += CubeRate[0]; CubeRotation[1] += CubeRate[1]; CubeRotation[2] += CubeRate[2]; QuitApplicationEventLoop(); } /* * 'MotionFunc()' - Handle mouse pointer motion. */ void MotionFunc(int x, /* I - X position */ int y) /* I - Y position */ { /* * Get the mouse movement... */ x -= CubeMouseX; y -= CubeMouseY; /* * Update the cube rotation rate based upon the mouse movement and * button... */ switch (CubeMouseButton) { case 0 : /* Button 1 */ CubeRate[0] = 0.01f * y; CubeRate[1] = 0.01f * x; CubeRate[2] = 0.0f; break; case 1 : /* Button 2 */ CubeRate[0] = 0.0f; CubeRate[1] = 0.01f * y; CubeRate[2] = 0.01f * x; break; default : /* Button 3 */ CubeRate[0] = 0.01f * y; CubeRate[1] = 0.0f; CubeRate[2] = 0.01f * x; break; } } /* * 'MouseFunc()' - Handle mouse button press/release events. */ void MouseFunc(int button, /* I - Button that was pressed */ int state, /* I - Button state (0 = down) */ int x, /* I - X position */ int y) /* I - Y position */ { /* * Only respond to button presses... */ if (state) return; /* * Save the mouse state... */ CubeMouseButton = button; CubeMouseX = x; CubeMouseY = y; /* * Zero-out the rotation rates... */ CubeRate[0] = 0.0f; CubeRate[1] = 0.0f; CubeRate[2] = 0.0f; } /* * 'ReshapeFunc()' - Resize the window. */ void ReshapeFunc(int width, /* I - Width of window */ int height) /* I - Height of window */ { CubeWidth = width; CubeHeight = height; }
The AGL framework provides a single function for using bitmap fonts for OpenGL rendering: aglUseFont
. This function works in conjunction with the Carbon GetFNum
function and OpenGL glGenLists
function to extract bitmaps and create display lists for each character you want in the font. The following code demonstrates how to extract the visible ASCII characters from the Courier New Bold font at a 14-pixel-high size:
GLint listbase; short font; Str255 fontname; strcpy(fontname + 1, "Courier New"); fontname[0] = strlen(fontname + 1); GetFNum(fontname, &font); listbase = glGenLists(96); aglUseFont(context, font, bold, 14, ' ', 96, listbase);
The font number is used along with a font style (bold
in this case) to select the actual font to render. The fifth and sixth arguments are the starting character and the number of characters to extract, respectively. The listbase
variable in the example points to the start of a block of 96 consecutive display lists to use for each character.
After the characters are extracted, you can draw text using a combination of the glRasterPos()
, glPushAttrib()
, glListBase()
, glCallLists()
, and glPopAttrib()
functions, as follows:
char *s = "Hello, World!"; glPushAttrib(GL_LIST_BIT); glListBase(listbase - ' '), glRasterPos3f(0.0f, 0.0f, 0.0f); glCallLists(strlen(s), GL_BYTE, s); glPopAttrib();
The example in Listing 14.2 uses this code to draw the names of each side of the cube. Figure 14.2 shows the result.
Example 14.2. The CARBONFONTS
Sample Program
/* * Include necessary headers... */ #include <stdio.h> #include <stdlib.h> #include <Carbon/Carbon.h> #include <AGL/agl.h> /* * Globals... */ float CubeRotation[3], /* Rotation of cube */ CubeRate[3]; /* Rotation rate of cube */ int CubeMouseButton, /* Button that was pressed */ CubeMouseX, /* Start X position of mouse */ CubeMouseY; /* Start Y position of mouse */ int CubeX, /* X position of window */ CubeY, /* Y position of window */ CubeWidth, /* Width of window */ CubeHeight; /* Height of window */ int CubeVisible; /* Is the window visible? */ AGLContext CubeContext; /* OpenGL context */ GLuint CubeFont; /* Display list base for font */ /* * Functions... */ void DisplayFunc(void); static pascal OSStatus EventHandler(EventHandlerCallRef nextHandler, EventRef event, void *userData); void IdleFunc(void); void MotionFunc(int x, int y); void MouseFunc(int button, int state, int x, int y); void ReshapeFunc(int width, int height); /* * 'main()' - Main entry for example program. */ int /* O - Exit status */ main(int argc, /* I - Number of command-line args */ char *argv[]) /* I - Command-line args */ { AGLPixelFormat format; /* OpenGL pixel format */ WindowPtr window; /* Window */ int winattrs; /* Window attributes */ Str255 title; /* Title of window */ Rect rect; /* Rectangle definition */ EventHandlerUPP handler; /* Event handler */ EventLoopTimerUPP thandler; /* Timer handler */ EventLoopTimerRef timer; /* Timer for animating the window */ ProcessSerialNumber psn; /* Process serial number */ short font; /* Font number */ Str255 fontname; /* Font name */ static EventTypeSpec events[] = /* Events we are interested in... */ { { kEventClassMouse, kEventMouseDown }, { kEventClassMouse, kEventMouseUp }, { kEventClassMouse, kEventMouseMoved }, { kEventClassMouse, kEventMouseDragged }, { kEventClassWindow, kEventWindowDrawContent }, { kEventClassWindow, kEventWindowShown }, { kEventClassWindow, kEventWindowHidden }, { kEventClassWindow, kEventWindowActivated }, { kEventClassWindow, kEventWindowDeactivated }, { kEventClassWindow, kEventWindowClose }, { kEventClassWindow, kEventWindowBoundsChanged } }; static GLint attributes[] = /* OpenGL attributes */ { AGL_RGBA, AGL_GREEN_SIZE, 1, AGL_DOUBLEBUFFER, AGL_DEPTH_SIZE, 16, AGL_NONE }; //Set initial values for window const int origWinHeight = 628; const int origWinWidth = 850; const int origWinXOffset = 50; const int origWinYOffset = 50; /* * Create the window... */ CubeContext = 0; CubeVisible = 0; SetRect(&rect, origWinXOffset, origWinYOffset, origWinWidth, origWinHeight); winattrs = kWindowStandardHandlerAttribute | kWindowCloseBoxAttribute | kWindowCollapseBoxAttribute | kWindowFullZoomAttribute | kWindowResizableAttribute | kWindowLiveResizeAttribute; winattrs &= GetAvailableWindowAttributes(kDocumentWindowClass); strcpy(title + 1, "Carbon OpenGL Example"); title[0] = strlen(title + 1); CreateNewWindow(kDocumentWindowClass, winattrs, &rect, &window); SetWTitle(window, title); handler = NewEventHandlerUPP(EventHandler); InstallWindowEventHandler(window, handler, sizeof(events) / sizeof(events[0]), events, NULL, 0L); thandler = NewEventLoopTimerUPP((void (*)(EventLoopTimerRef, void *))IdleFunc); InstallEventLoopTimer(GetMainEventLoop(), 0, 0, thandler, 0, &timer); GetCurrentProcess(&psn); SetFrontProcess(&psn); DrawGrowIcon(window); ShowWindow(window); /* * Create the OpenGL context and bind it to the window... */ format = aglChoosePixelFormat(NULL, 0, attributes); CubeContext = aglCreateContext(format, NULL); if (!CubeContext) { puts("Unable to get OpenGL context!"); return (1); } aglDestroyPixelFormat(format); aglSetDrawable(CubeContext, GetWindowPort(window)); /* * Setup remaining globals... */ CubeX = 50; CubeY = 50; CubeWidth = 400; CubeHeight = 400; CubeRotation[0] = 45.0f; CubeRotation[1] = 45.0f; CubeRotation[2] = 45.0f; CubeRate[0] = 1.0f; CubeRate[1] = 1.0f; CubeRate[2] = 1.0f; /* * Setup font... */ strcpy(fontname + 1, "Courier New"); fontname[0] = strlen(fontname + 1); GetFNum(fontname, &font); CubeFont = glGenLists(96); aglUseFont(CubeContext, font, bold, 14, ' ', 96, CubeFont); //Set the initial size of the cube ReshapeFunc(origWinWidth – origWinXOffset, origWinHeight – origWinYOffset); /* * Loop forever... */ for (;;) { if (CubeVisible) SetEventLoopTimerNextFireTime(timer, 0.05); RunApplicationEventLoop(); if (CubeVisible) { /* * Animate the cube... */ DisplayFunc(); } } } /* * 'DisplayFunc()' - Draw a cube. */ void DisplayFunc(void) { int i, j; /* Looping vars */ float aspectRatio,windowWidth, windowHeight; static const GLfloat corners[8][3] = /* Corner vertices */ { { 1.0f, 1.0f, 1.0f }, /* Front top right */ { 1.0f, -1.0f, 1.0f }, /* Front bottom right */ { -1.0f, -1.0f, 1.0f }, /* Front bottom left */ { -1.0f, 1.0f, 1.0f }, /* Front top left */ { 1.0f, 1.0f, -1.0f }, /* Back top right */ { 1.0f, -1.0f, -1.0f }, /* Back bottom right */ { -1.0f, -1.0f, -1.0f }, /* Back bottom left */ { -1.0f, 1.0f, -1.0f } /* Back top left */ }; static const int sides[6][4] = /* Sides */ { { 0, 1, 2, 3 }, /* Front */ { 4, 5, 6, 7 }, /* Back */ { 0, 1, 5, 4 }, /* Right */ { 2, 3, 7, 6 }, /* Left */ { 0, 3, 7, 4 }, /* Top */ { 1, 2, 6, 5 } /* Bottom */ }; static const GLfloat colors[6][3] = /* Colors */ { { 1.0f, 0.0f, 0.0f }, /* Red */ { 0.0f, 1.0f, 0.0f }, /* Green */ { 1.0f, 1.0f, 0.0f }, /* Yellow */ { 0.0f, 0.0f, 1.0f }, /* Blue */ { 1.0f, 0.0f, 1.0f }, /* Magenta */ { 0.0f, 1.0f, 1.0f } /* Cyan */ }; /* * Set the current OpenGL context... */ aglSetCurrentContext(CubeContext); /* * Clear the window... */ glViewport(0, 0, CubeWidth, CubeHeight); glClearColor(0.0f, 0.0f, 0.0f, 0.0f); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); /* * Setup the matrices... */ glMatrixMode(GL_PROJECTION); glLoadIdentity(); aspectRatio = (GLfloat)CubeWidth / (GLfloat)CubeHeight; if(CubeWidth <= CubeHeight) { windowWidth = 2.0f; windowHeight = 2.0f / aspectRatio; glOrtho(-2.0f, 2.0f, -windowHeight, windowHeight, 2.0f, -2.0f); } else { windowWidth = 2.0f * aspectRatio; windowHeight = 2.0f; glOrtho(-windowHeight, windowHeight, -2.0f, 2.0f, 2.0f, -2.0f); } glMatrixMode(GL_MODELVIEW); glLoadIdentity(); glRotatef(CubeRotation[0], 1.0f, 0.0f, 0.0f); glRotatef(CubeRotation[1], 0.0f, 1.0f, 0.0f); glRotatef(CubeRotation[2], 0.0f, 0.0f, 1.0f); /* * Draw the cube... */ glEnable(GL_DEPTH_TEST); glBegin(GL_QUADS); for (i = 0; i < 6; i ++) { glColor3fv(colors[i]); for (j = 0; j < 4; j ++) glVertex3fv(corners[sides[i][j]]); } glEnd(); /* * Draw lines coming out of the cube... */ glColor3f(1.0f, 1.0f, 1.0f); glBegin(GL_LINES); glVertex3f(0.0f, 0.0f, -1.5f); glVertex3f(0.0f, 0.0f, 1.5f); glVertex3f(-1.5f, 0.0f, 0.0f); glVertex3f(1.5f, 0.0f, 0.0f); glVertex3f(0.0f, 1.5f, 0.0f); glVertex3f(0.0f, -1.5f, 0.0f); glEnd(); /* * Draw text for each side... */ glPushAttrib(GL_LIST_BIT); glListBase(CubeFont - ' '), glRasterPos3f(0.0f, 0.0f, -1.5f); glCallLists(4, GL_BYTE, "Back"); glRasterPos3f(0.0f, 0.0f, 1.5f); glCallLists(5, GL_BYTE, "Front"); glRasterPos3f(-1.5f, 0.0f, 0.0f); glCallLists(4, GL_BYTE, "Left"); glRasterPos3f(1.5f, 0.0f, 0.0f); glCallLists(5, GL_BYTE, "Right"); glRasterPos3f(0.0f, 1.5f, 0.0f); glCallLists(3, GL_BYTE, "Top"); glRasterPos3f(0.0f, -1.5f, 0.0f); glCallLists(6, GL_BYTE, "Bottom"); glPopAttrib(); /* * Swap the front and back buffers... */ aglSwapBuffers(CubeContext); } /* * 'EventHandler()' - Handle window and mouse events from the window manager. */ static pascal OSStatus /* O - noErr on success or error code */ EventHandler(EventHandlerCallRef nextHandler, /* I - Next handler to call */ EventRef event, /* I - Event reference */ void *userData) /* I - User data (not used) */ { UInt32 kind; /* Kind of event */ Rect rect; /* New window size */ EventMouseButton button; /* Mouse button */ Point point; /* Mouse position */ kind = GetEventKind(event); if (GetEventClass(event) == kEventClassWindow) { switch (kind) { case kEventWindowDrawContent : if (CubeVisible && CubeContext) DisplayFunc(); break; case kEventWindowBoundsChanged : GetEventParameter(event, kEventParamCurrentBounds, typeQDRectangle, NULL, sizeof(Rect), NULL, &rect); CubeX = rect.left; CubeY = rect.top; if (CubeContext) aglUpdateContext(CubeContext); ReshapeFunc(rect.right - rect.left, rect.bottom - rect.top); if (CubeVisible && CubeContext) DisplayFunc(); break; case kEventWindowShown : CubeVisible = 1; if (CubeContext) DisplayFunc(); break; case kEventWindowHidden : CubeVisible = 0; break; case kEventWindowClose : ExitToShell(); break; } } else { switch (kind) { case kEventMouseDown : GetEventParameter(event, kEventParamMouseButton, typeMouseButton, NULL, sizeof(EventMouseButton), NULL, &button); GetEventParameter(event, kEventParamMouseLocation, typeQDPoint, NULL, sizeof(Point), NULL, &point); if (point.v < CubeY || (point.v > (CubeY + CubeHeight - 8) && point.h > (CubeX + CubeWidth - 8))) return (CallNextEventHandler(nextHandler, event)); MouseFunc(button, 0, point.h, point.v); break; case kEventMouseUp : GetEventParameter(event, kEventParamMouseButton, typeMouseButton, NULL, sizeof(EventMouseButton), NULL, &button); GetEventParameter(event, kEventParamMouseLocation, typeQDPoint, NULL, sizeof(Point), NULL, &point); if (point.v < CubeY || (point.v > (CubeY + CubeHeight - 8) && point.h > (CubeX + CubeWidth - 8))) return (CallNextEventHandler(nextHandler, event)); MouseFunc(button, 1, point.h, point.v); break; case kEventMouseDragged : GetEventParameter(event, kEventParamMouseLocation, typeQDPoint, NULL, sizeof(Point), NULL, &point); if (point.v < CubeY || (point.v > (CubeY + CubeHeight - 8) && point.h > (CubeX + CubeWidth - 8))) return (CallNextEventHandler(nextHandler, event)); MotionFunc(point.h, point.v); break; default : return (CallNextEventHandler(nextHandler, event)); } } /* * Return whether we handled the event... */ return (noErr); } /* * 'IdleFunc()' - Rotate and redraw the cube. */ void IdleFunc(void) { CubeRotation[0] += CubeRate[0]; CubeRotation[1] += CubeRate[1]; CubeRotation[2] += CubeRate[2]; QuitApplicationEventLoop(); } /* * 'MotionFunc()' - Handle mouse pointer motion. */ void MotionFunc(int x, /* I - X position */ int y) /* I - Y position */ { /* * Get the mouse movement... */ x -= CubeMouseX; y -= CubeMouseY; /* * Update the cube rotation rate based upon the mouse movement and * button... */ switch (CubeMouseButton) { case 0 : /* Button 1 */ CubeRate[0] = 0.01f * y; CubeRate[1] = 0.01f * x; CubeRate[2] = 0.0f; break; case 1 : /* Button 2 */ CubeRate[0] = 0.0f; CubeRate[1] = 0.01f * y; CubeRate[2] = 0.01f * x; break; default : /* Button 3 */ CubeRate[0] = 0.01f * y; CubeRate[1] = 0.0f; CubeRate[2] = 0.01f * x; break; } } /* * 'MouseFunc()' - Handle mouse button press/release events. */ void MouseFunc(int button, /* I - Button that was pressed */ int state, /* I - Button state (0 = down) */ int x, /* I - X position */ int y) /* I - Y position */ { /* * Only respond to button presses... */ if (state) return; /* * Save the mouse state... */ CubeMouseButton = button; CubeMouseX = x; CubeMouseY = y; /* * Zero-out the rotation rates... */ CubeRate[0] = 0.0f; CubeRate[1] = 0.0f; CubeRate[2] = 0.0f; } /* * 'ReshapeFunc()' - Resize the window. */ void ReshapeFunc(int width, /* I - Width of window */ int height) /* I - Height of window */ { CubeWidth = width; CubeHeight = height; }
The Cocoa API is suitable for applications developed using Objective C and the Cocoa user interface classes. Cocoa provides a single OpenGL rendering class that you must subclass to do OpenGL rendering.
Cocoa programs of this type require the ApplicationServices, Cocoa, and OpenGL frameworks; you can start with a Cocoa-based application in the project builder application and add the OpenGL framework or use the following linker options:
-framework Cocoa -framework OpenGL -framework ApplicationServices
The NSOpenGL
class provides the basis for all OpenGL-based Cocoa components. You must subclass this class to implement a Cocoa-based OpenGL display, implementing three required methods: basicPixelFormat
, drawRect
, and initWithFrame
. The basicPixelFormat
method is used to create a copy of an attribute array similar to that used by the aglChoosePixelFormat
function. The following code shows a typical implementation that requests a double-buffered pixel format with at least 16 bits of depth buffer:
+ (NSOpenGLPixelFormat*) basicPixelFormat { static NSOpenGLPixelFormatAttribute attributes[] = { NSOpenGLPFAWindow, NSOpenGLPFADoubleBuffer, NSOpenGLPFADepthSize, (NSOpenGLPixelFormatAttribute)16, (NSOpenGLPixelFormatAttribute)nil }; return ([[[NSOpenGLPixelFormat alloc] initWithAttributes] autoRelease]); }
Each attribute value is of type NSOpenGLPixelFormatAttribute
and the attribute list is terminated by a nil
(zero) value. Table 14.3 lists the valid attribute values.
Table 14.3. NSOpenGLPixelFormatAttribute
Constants
The drawRect
method does any OpenGL drawing calls to redraw the widget; the rect
argument can be used to set the viewport and viewing transformation as necessary:
- (void)drawRect:(NSRect)rect { int width, height; // Get the current bounding rectangle... width = rect.size.width; height = rect.size.height; // Set the viewport... glViewport(0, 0, width, height); // Setup the projection matrix... glMatrixMode(GL_PROJECTION); glLoadIdentity(); glOrtho(-2.0f, 2.0f, -2.0f * height / width, 2.0f * height / width, -2.0f, 2.0f); // Call any OpenGL functions to draw the scene... ... }
Finally, the initWithFrame
method initializes the OpenGL drawing area:
- (id)initWithFrame:(NSRect)frameRect { NSOpenGLPixelFormat *pf; // Get the pixel format and return a new window from it... pf = [MyClass basicPixelFormat]; self = [super initWithFrame: frameRect pixelFormat: pf]; return (self); }
The NSOpenGL
class handles the creation of an OpenGL context for use with the pixel format that you have supplied.
Now that we have covered the basics of using the NSOpenGL
class for Cocoa applications, we will convert the Carbon Cube example to Cocoa. All the Carbon code is fully contained in a class called Cube
that is based on NSOpenGL
and implements all the required methods described in the previous section. It also implements the mouse methods so that you can click and drag the mouse to rotate the cube. Listing 14.3 shows the completed Cocoa application that displays a spinning cube. The results are shown in Figure 14.3.
Example 14.3. The cocoa.m
Sample Program
#import <Cocoa/Cocoa.h> #import <Carbon/Carbon.h> #import <OpenGL/gl.h> #import <OpenGL/glext.h> #import <OpenGL/glu.h> // Interface definition for our NIB CustomView // In the NIB builder, derive as subclass named // Cube from NSOpenGLView. Then assign this to a // customview that fills the window. @interface Cube : NSOpenGLView { bool initialized; NSTimer *timer; float rotation[3], rate[3]; int mouse_x, mouse_y; GLuint font; } @end // // 'main()' - Main entry for program. // int // O - Exit status main(int argc, // I - Number of command-line args const char *argv[]) // I - Command-line arguments { return (NSApplicationMain(argc, argv)); } // // Cube class based upon NSOpenGLView // @implementation Cube : NSOpenGLView { bool initialized; // Are we initialized? NSTimer *timer; // Timer for animation float rotation[3], // Rotation of cube rate[3]; // Rotation rate of cube int mouse_x, // Start X position of mouse mouse_y; // Start Y position of mouse } // // 'basicPixelFormat()' - Set the pixel format for the window. // + (NSOpenGLPixelFormat*) basicPixelFormat { static NSOpenGLPixelFormatAttribute attributes[] = // OpenGL attributes { NSOpenGLPFAWindow, NSOpenGLPFADoubleBuffer, NSOpenGLPFADepthSize, (NSOpenGLPixelFormatAttribute)16, (NSOpenGLPixelFormatAttribute)nil }; return ([[[NSOpenGLPixelFormat alloc] initWithAttributes:attributes]autorelease]); } // // 'resizeGL()' - Resize the window. // - (void) resizeGL { } // // 'idle()' - Update the display using the current rotation rates... // - (void)idle:(NSTimer *)timer { // Rotate... rotation[0] += rate[0]; rotation[1] += rate[1]; rotation[2] += rate[2]; // Redraw the window... [self drawRect:[self bounds]]; } // // 'mouseDown()' - Handle left mouse button presses... // - (void)mouseDown:(NSEvent *)theEvent { NSPoint point; // Mouse position // Get and save the mouse position point = [self convertPoint:[theEvent locationInWindow] fromView:nil]; mouse_x = point.x; mouse_y = point.y; // Null the rotation rates... rate[0] = 0.0f; rate[1] = 0.0f; rate[2] = 0.0f; } // // 'rightMouseDown()' - Handle right mouse button presses... // - (void)rightMouseDown:(NSEvent *)theEvent { NSPoint point; // Mouse position // Get and save the mouse position point = [self convertPoint:[theEvent locationInWindow] fromView:nil]; mouse_x = point.x; mouse_y = point.y; // Null the rotation rates... rate[0] = 0.0f; rate[1] = 0.0f; rate[2] = 0.0f; } // // 'otherMouseDown()' - Handle middle mouse button presses... // - (void)otherMouseDown:(NSEvent *)theEvent { NSPoint point; // Mouse position // Get and save the mouse position point = [self convertPoint:[theEvent locationInWindow] fromView:nil]; mouse_x = point.x; mouse_y = point.y; // Null the rotation rates... rate[0] = 0.0f; rate[1] = 0.0f; rate[2] = 0.0f; } // // 'mouseDragged()' - Handle drags using the left mouse button. // - (void)mouseDragged:(NSEvent *)theEvent { NSPoint point; // Mouse position // Get the mouse position and update the rotation rates... point = [self convertPoint:[theEvent locationInWindow] fromView:nil]; rate[0] = 0.01f * (mouse_y - point.y); rate[1] = 0.01f * (mouse_x - point.x); } // // 'rightMouseDragged()' - Handle drags using the right mouse button. // - (void)rightMouseDragged:(NSEvent *)theEvent { NSPoint point; // Mouse position // Get the mouse position and update the rotation rates... point = [self convertPoint:[theEvent locationInWindow] fromView:nil]; rate[0] = 0.01f * (mouse_y - point.y); rate[2] = 0.01f * (mouse_x - point.x); } // // 'otherMouseDragged()' - Handle drags using the middle mouse button. // - (void)otherMouseDragged:(NSEvent *)theEvent { NSPoint point; // Mouse position // Get the mouse position and update the rotation rates... point = [self convertPoint:[theEvent locationInWindow] fromView:nil]; rate[1] = 0.01f * (mouse_y - point.y); rate[2] = 0.01f * (mouse_x - point.x); } - (void)drawRect:(NSRect)rect { int width, // Width of window height; // Height of window int i, j; // Looping vars float aspectRatio, windowWidth, windowHeight; static const GLfloat corners[8][3] = // Corner vertices { { 1.0f, 1.0f, 1.0f }, // Front top right { 1.0f, -1.0f, 1.0f }, // Front bottom right { -1.0f, -1.0f, 1.0f }, // Front bottom left { -1.0f, 1.0f, 1.0f }, // Front top left { 1.0f, 1.0f, -1.0f }, // Back top right { 1.0f, -1.0f, -1.0f }, // Back bottom right { -1.0f, -1.0f, -1.0f }, // Back bottom left { -1.0f, 1.0f, -1.0f } // Back top left }; static const int sides[6][4] = // Sides { { 0, 1, 2, 3 }, // Front { 4, 5, 6, 7 }, // Back { 0, 1, 5, 4 }, // Right { 2, 3, 7, 6 }, // Left { 0, 3, 7, 4 }, // Top { 1, 2, 6, 5 } // Bottom }; static const GLfloat colors[6][3] = // Colors { { 1.0f, 0.0f, 0.0f }, // Red { 0.0f, 1.0f, 0.0f }, // Green { 1.0f, 1.0f, 0.0f }, // Yellow { 0.0f, 0.0f, 1.0f }, // Blue { 1.0f, 0.0f, 1.0f }, // Magenta { 0.0f, 1.0f, 1.0f } // Cyan }; // Set the current OpenGL context... if (!initialized) { rotation[0] = 45.0f; rotation[1] = 45.0f; rotation[2] = 45.0f; rate[0] = 1.0f; rate[1] = 1.0f; rate[2] = 1.0f; initialized = true; } // Use the current bounding rectangle for the cube window... width = rect.size.width; height = rect.size.height; // Clear the window... glViewport(0, 0, width, height); glClearColor(0.0f, 0.0f, 0.0f, 0.0f); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // Setup the matrices... glMatrixMode(GL_PROJECTION); glLoadIdentity(); aspectRatio = (GLfloat)width / (GLfloat)height; if(width <= height) { windowWidth = 2.0f; windowHeight = 2.0f / aspectRatio; glOrtho(-2.0f, 2.0f, -windowHeight, windowHeight, 2.0f, -2.0f); } else { windowWidth = 2.0f * aspectRatio; windowHeight = 2.0f; glOrtho(-windowHeight, windowHeight, -2.0f, 2.0f, 2.0f, -2.0f); } glMatrixMode(GL_MODELVIEW); glLoadIdentity(); glRotatef(rotation[0], 1.0f, 0.0f, 0.0f); glRotatef(rotation[1], 0.0f, 1.0f, 0.0f); glRotatef(rotation[2], 0.0f, 0.0f, 1.0f); // Draw the cube... glEnable(GL_DEPTH_TEST); glBegin(GL_QUADS); for (i = 0; i < 6; i ++) { glColor3fv(colors[i]); for (j = 0; j < 4; j ++) glVertex3fv(corners[sides[i][j]]); } glEnd(); // Draw lines coming out of the cube... glColor3f(1.0f, 1.0f, 1.0f); glBegin(GL_LINES); glVertex3f(0.0f, 0.0f, -1.5f); glVertex3f(0.0f, 0.0f, 1.5f); glVertex3f(-1.5f, 0.0f, 0.0f); glVertex3f(1.5f, 0.0f, 0.0f); glVertex3f(0.0f, 1.5f, 0.0f); glVertex3f(0.0f, -1.5f, 0.0f); glEnd(); // Swap the front and back buffers... [[self openGLContext]flushBuffer]; } // // 'acceptsFirstResponder()' ... // - (BOOL)acceptsFirstResponder { return (YES); } - (BOOL) becomeFirstResponder { return (YES); } - (BOOL) resignFirstResponder { return (YES); } // // 'initWithFrame()' - Initialize the cube. // - (id)initWithFrame:(NSRect)frameRect { NSOpenGLPixelFormat *pf; // Get the pixel format and return a new cube window from it... pf = [Cube basicPixelFormat]; self = [super initWithFrame: frameRect pixelFormat: pf]; return (self); } // // 'awakeFromNib()' - Do stuff once the UI is loaded from the NIB file... // - (void)awakeFromNib { // Set initial values... initialized = false; //start cube rotating [self drawRect:[self bounds]]; // Start the timer running... timer = [NSTimer timerWithTimeInterval:(0.05f) target:self selector:@selector(idle:) userInfo:nil repeats:YES]; [[NSRunLoop currentRunLoop]addTimer:timer forMode:NSDefaultRunLoopMode]; [[NSRunLoop currentRunLoop]addTimer:timer forMode:NSEventTrackingRunLoopMode]; } @end
OpenGL is well supported on MacOS X and can be accessed using several different APIs provided by Apple: AGL, CGL, Cocoa, and GLUT. The AGL API provides OpenGL support for Carbon-based applications and is supported on MacOS 8 through X, the CGL API provides full-screen OpenGL support and is meant for use with games and other full-screen applications, the Cocoa API is an Objective C interface for complex GUIs that include OpenGL-based components, and the GLUT API provides a simple, cross-platform interface for the sample programs in this book as well as for simple applications.
aglChoosePixelFormat | |
---|---|
Purpose: | Enables you to choose a pixel format for OpenGL rendering. |
Include File: |
|
Syntax: | |
AGLPixelFormat aglChoosePixelFormat(const AGLDevice *gdevs, GLint ndev, const GLint *attribs); | |
Description: | This function locates a compatible pixel format for all the listed devices. If |
Parameters: | |
|
|
|
|
|
|
Returns: | A compatible pixel format or |
See Also: |
|
aglCreateContext | |
---|---|
Purpose: | Creates an OpenGL context for rendering. |
Include File: |
|
Syntax: | |
AGLContext aglCreateContext(AGLPixelFormat pix, AGLContext share); | |
Description: | This function creates a new OpenGL rendering context. If the |
Parameters: | |
|
|
|
|
Returns: | A new OpenGL context or |
See Also: |
|
aglDestroyContext | |
---|---|
Purpose: | To destroy an OpenGL rendering context. |
Include File: |
|
Syntax: | |
GLboolean aglDestroyContext(AGLContext ctx);
| |
Description: | This function destroys the specified OpenGL context. |
Parameters: | |
|
|
Returns: |
|
See Also: |
|
aglSetCurrentContext | |
---|---|
Purpose: | Sets the current context for OpenGL rendering. |
Include File: |
|
Syntax: | |
GLboolean aglSetCurrentContext(AGLContext ctx);
| |
Description: | This function sets the current OpenGL context for rendering. |
Parameters: | |
|
|
Returns: |
|
See Also: |
|
aglSetDrawable | |
---|---|
Purpose: | Sets the window or offscreen buffer associated with a context. |
Include File: |
|
Syntax: | |
GLboolean aglSetDrawable(AGLContext ctx, AGLDrawable draw); | |
Description: | This function binds a window or offscreen buffer to an OpenGL context. You must call this function after creating the context and before using the context to do any rendering. |
Parameters: | |
|
|
|
|
Returns: |
|
See Also: |
AglSwapBuffers | |
---|---|
Purpose: | Swaps the front and back buffers in an OpenGL window. |
Include File: |
|
Syntax: | |
void aglSwapBuffers(AGLContext ctx);
| |
Description: | This function swaps the front and back buffers of the double-buffered OpenGL window bound to the specified context. You typically call this function after drawing a scene or frame using OpenGL functions. |
Parameters: | |
|
|
Returns: | Nothing |
See Also: |
|
AglUseFont | |
---|---|
Purpose: | Creates a collection of bitmap display lists. |
Include File: |
|
Syntax: | |
GLboolean aglUseFont(AGLContext ctx, GLint fontID, Style face, GLint size, int first, int count, int base); | |
Description: | This function creates |
Parameters: | |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Returns: | |
See Also: |
|