|
I assume in this tutorial that you have a passing familiarity with the Apple IDE of your choice and know how to create your NIB and project files (and indeed, that you know what all those acronyms are for). If you don't try working with one or two of the Apple project tutorials, such as the currency converter.
For those of you who are left, create a new project with the type "Cocoa Application." You need to add a framework into your project: "OpenGL.framework".
You can find this in '/System/Library/Frameworks/OpenGL.framework'
Now we're going to move on to the NIB file. Open up your NIB.
First you need to subclass NSOpenGLView. I've called my subclass kGLView, so if you want to be consistant with my project, call it that.
subclassing OpenGLView:
1. In the window "MainMenu.nib", click on the classes tab
2. Find your way to NSView->NSOpenGLView (click on it)
3. In the menu bar, click Classes->Subclass NSOpenGLView
Now, drag an NSView (conveniently called Custom View on your panel) to the window and fill the window with your new NSView.
Let's have some practice in making things via the NIB file. Create a new menu called "OpenGL options" and add the menu item "Full Screen" with the key shortcut "F". Add an action to kGLView called "fullScreen" and attatch the menu item to the action.
Create the files and add them to your project. Then close the NIB. We are done with it for now: it's time to start coding!
First we need to import and include our headers, as well as define a few constants:
#import <OpenGL/OpenGL.h>
#include <OpenGL/gl.h>
#include <OpenGL/glu.h>
#include <OpenGL/glext.h>
#define BITS_PER_PIXEL 32.0
#define DEPTH_SIZE 32.0
#define DEFAULT_TIME_INTERVAL 0.001
|
Next, we fill in the rest of the information for our class:
@interface kGLView : NSOpenGLView
{
bool FullScreenOn;
bool first;
NSWindow *FullScreenWindow;
NSWindow *StartingWindow;
NSTimer *time;
}
- (IBAction)toggleFullScreen:(id)sender;
- (id)initWithFrame:(NSRect) frameRect;
- (void)initGL;
@end
|
The first methods we must complete are initWithFrame and initGL. Let's look at initWithFrame first. In this method, we create an NSOpenGLPixelFormat which is used by the superclass, NSOpenGLView, to create the NSOpenGLContext to which we will be drawing. We return nil if anything goes wrong, which means that the system will display only an empty window. Once the view has been set up properly, we make the NSOpenGLContext in our view into the current context so that all drawing commands will go there. Finally, we call initGL which initializes all the OpenGL parameters we need to initialize.
NSOpenGLPixelFormat *nsglFormat;
NSOpenGLPixelFormatAttribute attr[] =
{
NSOpenGLPFADoubleBuffer,
NSOpenGLPFAAccelerated,
NSOpenGLPFAColorSize, BITS_PER_PIXEL,
NSOpenGLPFADepthSize, DEPTH_SIZE,
0
};
[self setPostsFrameChangedNotifications: YES];
nsglFormat = [[NSOpenGLPixelFormat alloc] initWithAttributes:attr];
if(!nsglFormat) { NSLog(@"Invalid format... terminating."); return nil; }
self = [super initWithFrame:frameRect pixelFormat:nsglFormat];
[nsglFormat release];
if(!self) { NSLog(@"Self not created... terminating."); return nil; }
[[self openGLContext] makeCurrentContext];
[self initGL];
return self;
|
Next let's look at initGL. The comments make this method fairly self explanatory, and really it's just the same code as you might find in the nehe tutorials.
glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
glClearDepth(1.0f);
glDepthFunc(GL_LESS);
glEnable(GL_DEPTH_TEST);
glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);
glHint(GL_POLYGON_SMOOTH_HINT, GL_NICEST);
|
Now we will set ourselves up to take advantage of the operating system's event loop. This is the awakeFromNIB method, which is called when your program starts. We create a timer that will call the selector drawFrame. Selector is just a way to specify that this is a pointer to a method. You could add other timers in here to time things like your frame rate, but we'll just stick with the basics for now.
time = [ [NSTimer scheduledTimerWithTimeInterval: DEFAULT_TIME_INTERVAL
target:self
selector:@selector(drawFrame)
userInfo:nil
repeats:YES]
retain
];
[[NSRunLoop currentRunLoop] addTimer: time forMode: NSEventTrackingRunLoopMode];
[[NSRunLoop currentRunLoop] addTimer: time forMode: NSModalPanelRunLoopMode];
|
Let's look at the reshape method now. We didn't have to do some of the things nehe did in his tutorial in the initGL because when we start the program we will be reshaping the view as it wakes from the NIB.
float aspect;
NSSize bound = [self frame].size;
aspect = bound.width / bound.height;
glViewport(0, 0, bound.width, bound.height);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
gluPerspective(45.0f, (GLfloat)aspect, 0.1f,100.0f);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
|
Here's our draw method. It's really only a stub here, except for the fact that we will be ensuring that this is the first responder. Being a first responder means that mouse and keyboard events will automatically come to this class in order for us to process them.
if( first )
{
first = NO;
if ([[NSApp keyWindow] makeFirstResponder:self] )
NSLog( @"self made first responder" );
else
NSLog( @"self is not first responder");
}
glLoadIdentity();
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
[[self openGLContext] flushBuffer];
|
To actually become a first responder and get all those operating system events sent to us, we need to define two other methods. All these methods need to do is return true.
- (BOOL)acceptsFirstResponder
{ return YES; }
- (BOOL)becomeFirstResponder
{ return YES; }
|
But we don't need those events just yet: all we want to do is be able to quit and switch to a full screen mode. Unfortunately, all our work is for naught if we use apple's GLContext fullScreen method. If we use this, or the code from OmniGroups OpenGL Example code, we will not be first responders, and we will have to implement our own event queue. Not fun. However, there is a solution. Looking at the code for GLUT by Dietmar Planitzer on the Apple site, we see the solution! Don't get rid of the window, just make a new window for full screen! This way, we don't lose our event queue (or our ability to quit, which made me crash while trying to use the standard fullScreen without implementing an event queue of my own).
There are two things to consider with our full screen method. First, we may be in full screen mode already, second, we may not be. We'll look at what to do if we are not in full screen mode first.
StartingWindow = [NSApp keyWindow];
windowStyle = NSBorderlessWindowMask;
contentRect = [[NSScreen mainScreen] frame];
FullScreenWindow = [[NSWindow alloc] initWithContentRect:contentRect
styleMask: windowStyle backing:NSBackingStoreBuffered defer: NO];
if(FullScreenWindow != nil)
{
[FullScreenWindow setTitle: @"myWindow"];
[FullScreenWindow setReleasedWhenClosed: YES];
[FullScreenWindow setContentView: self];
[FullScreenWindow makeKeyAndOrderFront:self ];
[FullScreenWindow setLevel: NSScreenSaverWindowLevel - 1];
[FullScreenWindow makeFirstResponder:self];
FullScreenOn = true;
}
|
We're almost done: all we need to do now is consider what the program should do if we ARE already in full screen mode. This is actually quite simple: just close the full screen window, make our original window the key and frontmost window in the application, and make this the first responder again.
[FullScreenWindow close];
[StartingWindow setContentView: self];
[StartingWindow makeKeyAndOrderFront: self];
[StartingWindow makeFirstResponder: self];
FullScreenOn = false;
|
|