BasicOpenGLView.m

/*
 *  BasicOpenGLView.m
 
 *  Created November 3 2004.
 *  Copyright (c) 2004 Apple Computer Inc. All rights reserved.
 
    Disclaimer: IMPORTANT:  This Apple software is supplied to you by Apple Computer, Inc.
            ("Apple") in consideration of your agreement to the following terms, and your
            use, installation, modification or redistribution of this Apple software
            constitutes acceptance of these terms.  If you do not agree with these terms,
            please do not use, install, modify or redistribute this Apple software.
 
            In consideration of your agreement to abide by the following terms, and subject
            to these terms, Apple grants you a personal, non-exclusive license, under AppleÕs
            copyrights in this original Apple software (the "Apple Software"), to use,
            reproduce, modify and redistribute the Apple Software, with or without
            modifications, in source and/or binary forms; provided that if you redistribute
            the Apple Software in its entirety and without modifications, you must retain
            this notice and the following text and disclaimers in all such redistributions of
            the Apple Software.  Neither the name, trademarks, service marks or logos of
            Apple Computer, Inc. may be used to endorse or promote products derived from the
            Apple Software without specific prior written permission from Apple.  Except as
            expressly stated in this notice, no other rights or licenses, express or implied,
            are granted by Apple herein, including but not limited to any patent rights that
            may be infringed by your derivative works or by other works in which the Apple
            Software may be incorporated.
 
            The Apple Software is provided by Apple on an "AS IS" basis.  APPLE MAKES NO
            WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION THE IMPLIED
            WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS FOR A PARTICULAR
            PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND OPERATION ALONE OR IN
            COMBINATION WITH YOUR PRODUCTS.
 
            IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL OR
            CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
            GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
            ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION, MODIFICATION AND/OR DISTRIBUTION
            OF THE APPLE SOFTWARE, HOWEVER CAUSED AND WHETHER UNDER THEORY OF CONTRACT, TORT
            (INCLUDING NEGLIGENCE), STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN
            ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 */
 
#import "BasicOpenGLView.h"
#import "trackball.h"
 
// ==================================
 
recVec gOrigin = {0.0, 0.0, 0.0};
 
// single set of interaction flags and states
GLint gDollyPanStartPoint[2] = {0, 0};
GLfloat gTrackBallRotation [4] = {0.0f, 0.0f, 0.0f, 0.0f};
GLboolean gDolly = GL_FALSE;
GLboolean gPan = GL_FALSE;
GLboolean gTrackball = GL_FALSE;
BasicOpenGLView * gTrackingViewInfo = NULL;
CGDisplayCount gNumDisplays = 0;
 
// time and message info
CFAbsoluteTime gMsgPresistance = 10.0f;
static CFAbsoluteTime gStartTime = 0.0f;
 
// error output
GLString * gErrStringTex;
float gErrorTime;
 
 
// set app start time
static void setStartTime (void)                 { gStartTime = CFAbsoluteTimeGetCurrent (); }
// return float elpased time in seconds since app start
static CFAbsoluteTime getElapsedTime (void)     { return CFAbsoluteTimeGetCurrent () - gStartTime; }
 
// error reporting as both window message and debugger string
void reportError (char * strError)
{
    NSMutableDictionary *attribs = [NSMutableDictionary dictionary];
    [attribs setObject: [NSFont fontWithName: @"Monaco" size: 9.0f] forKey: NSFontAttributeName];
    [attribs setObject: [NSColor whiteColor] forKey: NSForegroundColorAttributeName];
 
    gErrorTime = getElapsedTime ();
    NSMutableString * errString = [NSMutableString stringWithFormat:@"Error: %s (at time: %0.1f secs).", strError, gErrorTime];
    NSLog (@"%@\n", errString);
 
    if (gErrStringTex)
        [gErrStringTex setString:errString];// withAttributes:attribs];
    else
        gErrStringTex = [[(GLString*)[GLString alloc] initWithString:errString] retain];
}
 
// if error dump gl errors to debugger string, return error
GLenum glReportError (void)
{
    GLenum err = glGetError();
    if (GL_NO_ERROR != err)
        reportError ((char *) gluErrorString (err));
    return err;
}
 
@implementation BasicOpenGLView
 
// pixel format definition
+ (NSOpenGLPixelFormat*) basicPixelFormat
{
    NSOpenGLPixelFormatAttribute attributes [] = {
        NSOpenGLPFAWindow,
        NSOpenGLPFADoubleBuffer,    // double buffered
        NSOpenGLPFADepthSize, (NSOpenGLPixelFormatAttribute)16, // 16 bit depth buffer
        (NSOpenGLPixelFormatAttribute)nil
    };
    return [[[NSOpenGLPixelFormat alloc] initWithAttributes:attributes] autorelease];
}
 
// update the projection matrix based on camera and view info
- (void) updateProjection
{
    GLdouble ratio, radians, wd2;
    GLdouble left, right, top, bottom, near, far;
 
    [[self openGLContext] makeCurrentContext];
 
    // set projection
    glMatrixMode (GL_PROJECTION);
    glLoadIdentity ();
    near = -camera.viewPos.z - shapeSize * 0.5;
    if (near < 0.00001)
        near = 0.00001;
    far = -camera.viewPos.z + shapeSize * 0.5;
    //if (near < 1.0)
    //  near = 1.0;
    radians = 0.0174532925 * camera.aperture / 2; // half aperture degrees to radians 
    wd2 = near * tan(radians);
    ratio = camera.viewWidth / (float) camera.viewHeight;
    if (ratio >= 1.0) {
        left  = -ratio * wd2;
        right = ratio * wd2;
        top = wd2;
        bottom = -wd2;  
    } else {
        left  = -wd2;
        right = wd2;
        top = wd2 / ratio;
        bottom = -wd2 / ratio;  
    }
    glFrustum (left, right, bottom, top, near, far);
    [self updateCameraString];
}
 
// updates the contexts model view matrix for object and camera moves
- (void) updateModelView
{
    [[self openGLContext] makeCurrentContext];
    
    // move view
    glMatrixMode (GL_MODELVIEW);
    glLoadIdentity ();
    gluLookAt (camera.viewPos.x, camera.viewPos.y, camera.viewPos.z,
               camera.viewPos.x + camera.viewDir.x,
               camera.viewPos.y + camera.viewDir.y,
               camera.viewPos.z + camera.viewDir.z,
               camera.viewUp.x, camera.viewUp.y ,camera.viewUp.z);
            
    // if we have trackball rotation to map (this IS the test I want as it can be explicitly 0.0f)
    if ((gTrackingViewInfo == self) && gTrackBallRotation[0] != 0.0f) 
        glRotatef (gTrackBallRotation[0], gTrackBallRotation[1], gTrackBallRotation[2], gTrackBallRotation[3]);
    else {
    }
    // accumlated world rotation via trackball
    glRotatef (worldRotation[0], worldRotation[1], worldRotation[2], worldRotation[3]);
    // object itself rotating applied after camera rotation
    rRot[0] = 0.0f; // reset animation rotations (do in all cases to prevent rotating while moving with trackball)
    rRot[1] = 0.0f;
    rRot[2] = 0.0f;
    [self updateCameraString];
}
 
// handles resizing of GL need context update and if the window dimensions change, a
// a window dimension update, reseting of viewport and an update of the projection matrix
- (void) resizeGL
{
    NSRect rectView = [self bounds];
    
    // ensure camera knows size changed
    if ((camera.viewHeight != rectView.size.height) ||
        (camera.viewWidth != rectView.size.width)) {
        camera.viewHeight = rectView.size.height;
        camera.viewWidth = rectView.size.width;
        
        glViewport (0, 0, camera.viewWidth, camera.viewHeight);
        [self updateProjection];  // update projection matrix
        [self updateInfoString];
    }
}
 
 
// move camera in z axis
-(void)mouseDolly: (NSPoint) location
{
    GLfloat dolly = (gDollyPanStartPoint[1] -location.y) * -camera.viewPos.z / 300.0f;
    camera.viewPos.z += dolly;
    if (camera.viewPos.z == 0.0) // do not let z = 0.0
        camera.viewPos.z = 0.0001;
    gDollyPanStartPoint[0] = location.x;
    gDollyPanStartPoint[1] = location.y;
}
 
// move camera in x/y plane
- (void)mousePan: (NSPoint) location
{
    GLfloat panX = (gDollyPanStartPoint[0] - location.x) / (900.0f / -camera.viewPos.z);
    GLfloat panY = (gDollyPanStartPoint[1] - location.y) / (900.0f / -camera.viewPos.z);
    camera.viewPos.x -= panX;
    camera.viewPos.y -= panY;
    gDollyPanStartPoint[0] = location.x;
    gDollyPanStartPoint[1] = location.y;
}
 
// sets the camera data to initial conditions
- (void) resetCamera
{
   camera.aperture = 40;
   camera.rotPoint = gOrigin;
 
   camera.viewPos.x = 0.0;
   camera.viewPos.y = 0.0;
   camera.viewPos.z = -10.0;
   camera.viewDir.x = -camera.viewPos.x; 
   camera.viewDir.y = -camera.viewPos.y; 
   camera.viewDir.z = -camera.viewPos.z;
 
   camera.viewUp.x = 0;  
   camera.viewUp.y = 1; 
   camera.viewUp.z = 0;
}
 
// given a delta time in seconds and current rotation accel, velocity and position, update overall object rotation
- (void) updateObjectRotationForTimeDelta:(CFAbsoluteTime)deltaTime
{
    // update rotation based on vel and accel
    float rotation[4] = {0.0f, 0.0f, 0.0f, 0.0f};
    GLfloat fVMax = 2.0;
    short i;
    // do velocities
    for (i = 0; i < 3; i++) {
        rVel[i] += rAccel[i] * deltaTime * 30.0;
        
        if (rVel[i] > fVMax) {
            rAccel[i] *= -1.0;
            rVel[i] = fVMax;
        } else if (rVel[i] < -fVMax) {
            rAccel[i] *= -1.0;
            rVel[i] = -fVMax;
        }
        
        rRot[i] += rVel[i] * deltaTime * 30.0;
        
        while (rRot[i] > 360.0)
            rRot[i] -= 360.0;
        while (rRot[i] < -360.0)
            rRot[i] += 360.0;
    }
    rotation[0] = rRot[0];
    rotation[1] = 1.0f;
    addToRotationTrackball (rotation, objectRotation);
    rotation[0] = rRot[1];
    rotation[1] = 0.0f; rotation[2] = 1.0f;
    addToRotationTrackball (rotation, objectRotation);
    rotation[0] = rRot[2];
    rotation[2] = 0.0f; rotation[3] = 1.0f;
    addToRotationTrackball (rotation, objectRotation);
}
 
 
// per-window timer function, basic time based animation preformed here
- (void)animationTimer:(NSTimer *)timer
{
    BOOL shouldDraw = NO;
    if (fAnimate) {
        CFTimeInterval deltaTime = CFAbsoluteTimeGetCurrent () - time;
            
        if (deltaTime > 10.0) // skip pauses
            return;
        else {
            // if we are not rotating with trackball in this window
            if (!gTrackball || (gTrackingViewInfo != self)) {
                [self updateObjectRotationForTimeDelta: deltaTime]; // update object rotation
            }
            shouldDraw = YES; // force redraw
        }
    }
    time = CFAbsoluteTimeGetCurrent (); //reset time in all cases
    // if we have current messages
    if (((getElapsedTime () - msgTime) < gMsgPresistance) || ((getElapsedTime () - gErrorTime) < gMsgPresistance))
        shouldDraw = YES; // force redraw
    if (YES == shouldDraw) 
        [self drawRect:[self bounds]]; // redraw now instead dirty to enable updates during live resize
}
 
 
// these functions create or update StringTextures one should expect to have to regenerate the image, bitmap and texture when the string changes thus these functions are not particularly light weight
 
- (void) updateInfoString
{ // update info string texture
    NSMutableString * string = [NSMutableString stringWithFormat:@"(%0.0f x %0.0f) \n%s \n%s", [self bounds].size.width, [self bounds].size.height, glGetString (GL_RENDERER), glGetString (GL_VERSION)];
    if (infoStringTex)
        [infoStringTex setString:string];// withAttributes:stanStringAttrib];
    else
        infoStringTex = [[(GLString*)[GLString alloc] initWithString:string] retain];
}
 
- (void) createHelpString
{
    NSMutableString * string = [NSMutableString stringWithFormat:@" Cmd-O: Opens a file for display\n Cmd-Click, Click: Zoom In / Out\n Alt/Opt-Click: Rotate Object\n Ctrl-Click: Pan Around\n ===========================\n 'H': Toggle Help\n 'R': Reset Rotation\n"];
    helpStringTex = [[(GLString*)[GLString alloc] initWithString:string] retain];
}
 
- (void) createMessageString
{
    NSMutableString * string = [NSMutableString stringWithFormat:@"No messages..."];
    msgStringTex = [[(GLString*)[GLString alloc] initWithString:string] retain];
}
 
- (void) updateCameraString
{ // update info string texture
    NSMutableString *string = [NSMutableString stringWithFormat:@"Camera at (%0.1f, %0.1f, %0.1f) looking at (%0.1f, %0.1f, %0.1f) with %0.1f aperture", camera.viewPos.x, camera.viewPos.y, camera.viewPos.z, camera.viewDir.x, camera.viewDir.y, camera.viewDir.z, camera.aperture];
    if (camStringTex)
        [camStringTex setString:string];// withAttributes:stanStringAttrib];
    else
        camStringTex = [[(GLString*)[GLString alloc] initWithString:string] retain];
}
 
// draw text info using our StringTexture class for much more optimized text drawing
- (void) drawInfo
{   
    GLint matrixMode;
    GLboolean depthTest = glIsEnabled (GL_DEPTH_TEST);
    GLfloat height, width, messageTop = 10.0f;
    
    height = camera.viewHeight;
    width = camera.viewWidth;
    
    glDisable (GL_DEPTH_TEST); // ensure text is not remove by deoth buffer test.
    glEnable (GL_BLEND); // for text fading
    glBlendFunc (GL_ONE, GL_ONE_MINUS_SRC_ALPHA); // ditto
    glEnable (GL_TEXTURE_RECTANGLE_EXT);
    
    // set orthograhic 1:1  pixel transform in local view coords
    glGetIntegerv (GL_MATRIX_MODE, &matrixMode);
    glMatrixMode (GL_PROJECTION);
    glPushMatrix();
        glLoadIdentity ();
        glMatrixMode (GL_MODELVIEW);
        glPushMatrix();
            glLoadIdentity ();
            glScalef (2.0f / width, -2.0f /  height, 1.0f);
            glTranslatef (-width / 2.0f, -height / 2.0f, 0.0f);
            
            glColor4f (1.0f, 1.0f, 1.0f, 1.0f);
            [infoStringTex drawAtPoint:NSMakePoint (10.0f, height - [infoStringTex frameSize].height - 10.0f)];
            [camStringTex drawAtPoint:NSMakePoint (10.0f, messageTop)];
            // GLImage: Info string display
            if([theImage imageInfo])
            {
                GLString *gstr = [theImage imageInfo];
                [gstr drawAtPoint:NSMakePoint((width - ([gstr frameSize].width + 10.0f)), messageTop)];
            }
            
            messageTop += [camStringTex frameSize].height + 3.0f;
 
            if (fDrawHelp)
                [helpStringTex drawAtPoint:NSMakePoint (floor ((width - [helpStringTex frameSize].width) / 2.0f), floor ((height - [helpStringTex frameSize].height) / 3.0f))];
            
            // message string
            float currTime = getElapsedTime ();
            if ((currTime - msgTime) < gMsgPresistance) {
                GLfloat comp = (gMsgPresistance - getElapsedTime () + msgTime) * 0.1; // premultiplied fade
                glColor4f (comp, comp, comp, comp);
                [msgStringTex drawAtPoint:NSMakePoint (10.0f, messageTop)];
                messageTop += [msgStringTex frameSize].height + 3.0f;
            }
            // global error message
            if ((currTime - gErrorTime) < gMsgPresistance) {
                GLfloat comp = (gMsgPresistance - getElapsedTime () + gErrorTime) * 0.1; // premultiplied fade
                glColor4f (comp, comp, comp, comp);
                [(GLString*)gErrStringTex drawAtPoint:NSMakePoint (10.0f, messageTop)];
            }
 
        // reset orginal martices
        glPopMatrix(); // GL_MODELVIEW
        glMatrixMode (GL_PROJECTION);
    glPopMatrix();
    glMatrixMode (matrixMode);
 
    glDisable (GL_TEXTURE_RECTANGLE_EXT);
    glDisable (GL_BLEND);
    if (depthTest)
        glEnable (GL_DEPTH_TEST);
    glReportError ();
}
 
-(IBAction) animate: (id) sender
{
    fAnimate = 1 - fAnimate;
    if (fAnimate)
        [animateMenuItem setState: NSOnState];
    else 
        [animateMenuItem setState: NSOffState];
}
 
-(IBAction) info: (id) sender
{
    fInfo = 1 - fInfo;
    if (fInfo)
        [infoMenuItem setState: NSOnState];
    else
        [infoMenuItem setState: NSOffState];
    [self setNeedsDisplay: YES];
}
 
-(void)keyDown:(NSEvent *)theEvent
{
    NSString *characters = [theEvent characters];
    if ([characters length]) {
        unichar character = [characters characterAtIndex:0];
        switch (character) {
            case 'h':
                // toggle help
                fDrawHelp = 1 - fDrawHelp;
                [self setNeedsDisplay: YES];
                break;
            case 'r':
                // Reset rotation
                [self resetObjectRotation];
                [self setNeedsDisplay:YES];
                break;
        }
    }
}
 
 
- (void)mouseDown:(NSEvent *)theEvent // trackball
{
 
    if ([theEvent modifierFlags] & NSControlKeyMask) // send to pan
        [self rightMouseDown:theEvent];
    else if ([theEvent modifierFlags] & NSAlternateKeyMask)
    {
        NSPoint location = [self convertPoint:[theEvent locationInWindow] fromView:nil];
        location.y = camera.viewHeight - location.y;
        gDolly = GL_FALSE; // no dolly
        gPan = GL_FALSE; // no pan
        gTrackball = GL_TRUE;
        //[self otherMouseDown:theEvent];
        gTrackingViewInfo = self;
        startTrackball (location.x, location.y, 0, 0, camera.viewWidth, camera.viewHeight);
        //[self otherMouseDown:theEvent];
    }
    else {
        // send to dolly
        [self otherMouseDown:theEvent];
    }
}
 
 
- (void)rightMouseDown:(NSEvent *)theEvent // pan
{
    NSPoint location = [self convertPoint:[theEvent locationInWindow] fromView:nil];
    location.y = camera.viewHeight - location.y;
    if (gTrackball) { // if we are currently tracking, end trackball
        if (gTrackBallRotation[0] != 0.0)
            addToRotationTrackball (gTrackBallRotation, worldRotation);
        gTrackBallRotation [0] = gTrackBallRotation [1] = gTrackBallRotation [2] = gTrackBallRotation [3] = 0.0f;
    }
    gDolly = GL_FALSE; // no dolly
    gPan = GL_TRUE; 
    gTrackball = GL_FALSE; // no trackball
    gDollyPanStartPoint[0] = location.x;
    gDollyPanStartPoint[1] = location.y;
    gTrackingViewInfo = self;
}
 
 
- (void)otherMouseDown:(NSEvent *)theEvent //dolly
{
    NSPoint location = [self convertPoint:[theEvent locationInWindow] fromView:nil];
    location.y = camera.viewHeight - location.y;
    if (gTrackball) { // if we are currently tracking, end trackball
        if (gTrackBallRotation[0] != 0.0)
            addToRotationTrackball (gTrackBallRotation, worldRotation);
        gTrackBallRotation [0] = gTrackBallRotation [1] = gTrackBallRotation [2] = gTrackBallRotation [3] = 0.0f;
    }
    gDolly = GL_TRUE;
    gPan = GL_FALSE; // no pan
    gTrackball = GL_FALSE; // no trackball
    gDollyPanStartPoint[0] = location.x;
    gDollyPanStartPoint[1] = location.y;
    gTrackingViewInfo = self;
}
 
 
- (void)mouseUp:(NSEvent *)theEvent
{
    if (gDolly) { // end dolly
        gDolly = GL_FALSE;
    } else if (gPan) { // end pan
        gPan = GL_FALSE;
    } else if (gTrackball) { // end trackball
        gTrackball = GL_FALSE;
        if (gTrackBallRotation[0] != 0.0)
            addToRotationTrackball (gTrackBallRotation, worldRotation);
        gTrackBallRotation [0] = gTrackBallRotation [1] = gTrackBallRotation [2] = gTrackBallRotation [3] = 0.0f;
    } 
    gTrackingViewInfo = NULL;
}
 
 
- (void)rightMouseUp:(NSEvent *)theEvent
{
    [self mouseUp:theEvent];
}
 
 
- (void)otherMouseUp:(NSEvent *)theEvent
{
    [self mouseUp:theEvent];
}
 
 
- (void)mouseDragged:(NSEvent *)theEvent
{
    NSPoint location = [self convertPoint:[theEvent locationInWindow] fromView:nil];
    location.y = camera.viewHeight - location.y;
    if (gTrackball) {
        rollToTrackball (location.x, location.y, gTrackBallRotation);
        [self setNeedsDisplay: YES];
    } else if (gDolly) {
        [self mouseDolly: location];
        [self updateProjection];  // update projection matrix (not normally done on draw)
        [self setNeedsDisplay: YES];
    } else if (gPan) {
        [self mousePan: location];
        [self setNeedsDisplay: YES];
    }
}
 
 
- (void)scrollWheel:(NSEvent *)theEvent
{
    float wheelDelta = [theEvent deltaX] +[theEvent deltaY] + [theEvent deltaZ];
    if (wheelDelta)
    {
        GLfloat deltaAperture = wheelDelta * -camera.aperture / 200.0f;
        camera.aperture += deltaAperture;
        if (camera.aperture < 0.1) // do not let aperture <= 0.1
            camera.aperture = 0.1;
        if (camera.aperture > 179.9) // do not let aperture >= 180
            camera.aperture = 179.9;
        [self updateProjection]; // update projection matrix
        [self setNeedsDisplay: YES];
    }
}
 
 
- (void)rightMouseDragged:(NSEvent *)theEvent
{
    [self mouseDragged: theEvent];
}
 
 
- (void)otherMouseDragged:(NSEvent *)theEvent
{
    [self mouseDragged: theEvent];
}
 
 
- (void) drawRect:(NSRect)rect
{
    if (NO == fGLInit) 
        [self prepareOpenGL];
    fGLInit = YES;
        
    // setup viewport and prespective
    [self resizeGL]; // forces projection matrix update (does test for size changes)
    [self updateModelView];  // update model view matrix for object
 
    // clear our drawable
    glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    
    // GLImage: here's where we draw the textured polygon
    [theImage drawTexture];
 
    if (fInfo)
        [self drawInfo];
        
    if ([self inLiveResize] && !fAnimate)
        glFlush ();
    else
        [[self openGLContext] flushBuffer];
    glReportError ();
}
 
 
// set initial OpenGL state (current context is set)
// called after context is created
- (void) prepareOpenGL
{
    long swapInt = 1;
 
    [[self openGLContext] setValues:&swapInt forParameter:NSOpenGLCPSwapInterval]; // set to vbl sync
 
    // init GL stuff here
    glEnable(GL_DEPTH_TEST);
 
    glShadeModel(GL_SMOOTH);    
    glEnable(GL_CULL_FACE);
    glFrontFace(GL_CCW);
    glPolygonOffset (1.0f, 1.0f);
    
    glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
    [self resetCamera];
    shapeSize = 7.0f; // max radius of of objects
 
    // init fonts for use with strings
    NSFont * font =[NSFont fontWithName:@"Helvetica" size:12.0];
    stanStringAttrib = [[NSMutableDictionary dictionary] retain];
    [stanStringAttrib setObject:font forKey:NSFontAttributeName];
    [stanStringAttrib setObject:[NSColor whiteColor] forKey:NSForegroundColorAttributeName];
    [font release];
    
    GLString *helpStringTex = [[[GLString alloc] init] retain];
    GLString *infoStringTex = [[[GLString alloc] init] retain];;
    GLString *camStringTex = [[[GLString alloc] init] retain];;
    GLString *capStringTex = [[[GLString alloc] init] retain];;
    GLString *msgStringTex = [[[GLString alloc] init] retain];;
 
    // ensure strings are created
    [self createHelpString];
    [self createMessageString];
 
}
 
// this can be a troublesome call to do anything heavyweight, as it is called on window moves, resizes, and display config changes.  So be
// careful of doing too much here.
- (void) update // window resizes, moves and display changes (resize, depth and display config change)
{
msgTime = getElapsedTime ();
[msgStringTex setString:[NSString stringWithFormat:@"update at %0.1f secs", msgTime]];
//  withAttributes:stanStringAttrib];
    [super update];
    if (![self inLiveResize])  {// if not doing live resize
        [self updateInfoString]; // to get change in renderers will rebuld string every time (could test for early out)
        //getCurrentCaps (); // this call checks to see if the current config changed in a reasonably lightweight way to prevent expensive re-allocations
    }
}
 
 
-(id) initWithFrame: (NSRect) frameRect
{
    NSOpenGLPixelFormat * pf = [BasicOpenGLView basicPixelFormat];
 
    self = [super initWithFrame: frameRect pixelFormat: pf];
    return self;
}
 
 
- (BOOL)acceptsFirstResponder
{
  return YES;
}
 
 
- (BOOL)becomeFirstResponder
{
  return  YES;
}
 
 
- (BOOL)resignFirstResponder
{
  return YES;
}
 
 
- (void) awakeFromNib
{
    setStartTime (); // get app start time
    //getCurrentCaps (); // get current GL capabilites for all displays
    
    // set start values...
    rVel[0] = 0.3; rVel[1] = 0.1; rVel[2] = 0.2; 
    rAccel[0] = 0.003; rAccel[1] = -0.005; rAccel[2] = 0.004;
    fInfo = 1;
    fAnimate = 1;
    time = CFAbsoluteTimeGetCurrent ();  // set animation time start time
    fDrawHelp = 1;
 
    // start animation timer
    timer = [NSTimer timerWithTimeInterval:(1.0f/60.0f) target:self selector:@selector(animationTimer:) userInfo:nil repeats:YES];
    [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];
    [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSEventTrackingRunLoopMode]; // ensure timer fires during resize
    
    // GLImage: initialize our object
    theImage = [[[GLImage alloc] init] retain];
}
 
//********************************************
// Additions for NSGLImage sample           //
//********************************************
-(void)resetObjectRotation
{
    int i = 0;
    
    for(i = 0; i < 4; i++)
    {
        gTrackBallRotation[i] = 0.0;
        worldRotation[i] = 0.0;
    }
}
 
- (void)openPanelDidEnd:(NSOpenPanel *)panel returnCode:(int)returnCode contextInfo:(void *)contextInfo
{
    //NSLog(@"Filename: %@ \n Directory: %@ \n", [panel filename], [panel directory]);
    // So we've got an image selected. Check to see if NSImage will open it.
    
    // Make sure we've got a valid NSImage object
    if(!theImage)
        theImage = [[[GLImage alloc] init] retain];
        
    [theImage loadTextureFromFile:[panel filename]];
}
 
-(IBAction)openPanelAction:(id)sender
{
    NSOpenPanel *op = [NSOpenPanel openPanel];
    NSMutableArray *fTypes = [NSMutableArray arrayWithObject:@"jpg"];
    [op setCanChooseFiles:YES];
    [op setAllowsMultipleSelection:NO];
    [op setCanChooseDirectories:NO];
    [op beginSheetForDirectory:[[NSBundle mainBundle] bundlePath]
                            file:nil
                            types:fTypes 
                            modalForWindow:[self window]
                            modalDelegate:self
                            didEndSelector:@selector(openPanelDidEnd: returnCode: contextInfo:)
                            contextInfo:nil];
 
}
 
@end