OpenGL_Image_Utilities.c

/*
 *  OpenGL_Image_Utilities.c
 *  OpenGL Image
 *
 *  Created by ggs on Fri May 11 2001.
 
    Copyright:  Copyright © 2001 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.
 *
 */
 
#ifdef __APPLE_CC__ // project builder
    #include <Carbon/Carbon.h> // standard carbon
    
    #include <OpenGL/gl.h> // for OpenGL API
    #include <OpenGL/glext.h> // for OpenGL extension support 
    
#else // CodeWarrior
    #include <FixMath.h> // for X2Fix
    #include <fp.h> // for trig functions
    #include <DriverServices.h>
    
    #include <gl.h>  // for OpenGL API
    #include <glext.h> // for OpenGL extension support
    
    #include <stdio.h> // standard input/output (sprintf)
    #include <string.h> // for string handling (strstr)
#endif
 
#include "aglString.h" // opengl string handling
#include "Carbon_SetupGL.h" // standard setup code (SetupGL)
#include "Carbon_Error_Handler.h" // standard SetupGL error handling
 
#include "OpenGL_Image.h" // our header
 
 
// drag globals
Point gptDragStart = {0, 0}; // starting point at mouse down for drag
WindowRef gDragWindow = NULL; // the window in which the mouse is dragging
short gDragType = 0; // either kDrag or kRotation to indicate what type of drag the user is doing
 
// ==================================
 
    static void DrawGLTexelGrid (float textureWidth, float textureHeight, float imageWidth, float imageHeight, float zoom); // in pixels
    static long GetNextTextureSize (long textureDimension, long maxTextureSize, Boolean textureRectangle);
    static long GetTextureNumFromTextureDim (long textureDimension, long maxTextureSize, Boolean texturesOverlap, Boolean textureRectangle);
    static void DrawGLImageTile (unsigned long drawType, float imageWidth, float imageHeight, float zoom, float textureWidth, float textureHeight,
                                float offsetX, float offsetY, float endX, float endY, Boolean texturesOverlap, Boolean textureRectangle);
    pascal void IdleTimer (EventLoopTimerRef inTimer, void* userData);
    EventLoopTimerUPP GetTimerUPP (void);
 
// ==================================
// private
 
// draws grid lines in current color between each texel in the texture as it corresponds to orginal image
// i.e., draws grid lines based on texels but constrains output to image dimensions
 
static void DrawGLTexelGrid (float textureWidth, float textureHeight, float imageWidth, float imageHeight, float zoom) // in pixels
{
    long i; // iterator
    float perpenCoord, coord, coordStep; //  perpendicular coordinate, dawing (iteratoring) coordinate, coordiante step amount per line
    
    glBegin (GL_LINES); // draw using lines
        // vertical lines
        perpenCoord = 0.5f * imageHeight * zoom; // 1/2 height of image in world space
        coord =  -0.5f * imageWidth * zoom; // starting scaled coordinate for half of image width (world space)
        coordStep = imageWidth / textureWidth * zoom; // space between each line (maps texture size to image size)
        for (i = 0; i <= textureWidth; i++) // ith column
        {
            glVertex3f (coord, -perpenCoord, 0.0f); // draw from current column, top of image to...
            glVertex3f (coord, perpenCoord, 0.0f); // current column, bottom of image
            coord += coordStep; // step to next column
        }
        // horizontal lines
        perpenCoord = 0.5f * imageWidth * zoom; // 1/2 width of image in world space
        coord =  -0.5f * imageHeight * zoom; // scaled coordinate for half of image height (actual drawing coords)
        coordStep = imageHeight / textureHeight * zoom; // space between each line (maps texture size to image size)
        for (i = 0; i <= textureHeight; i++) // ith row
        {
            glVertex3f (-perpenCoord, coord, 0.0f); // draw from current row, left edge of image to...
            glVertex3f (perpenCoord, coord, 0.0f);// current row, right edge of image
            coord += coordStep; // step to next row
        }
    glEnd(); // end our set of lines
}
 
// ---------------------------------
 
// returns the largest power of 2 texture <= textureDimension and <= gMaxTextureSize
// or in the case of texture rectangle returns the next texture size (can be non-power of two)
 
static long GetNextTextureSize (long textureDimension, long maxTextureSize, Boolean textureRectangle)
{
    long targetTextureSize = maxTextureSize; // start at max texture size
    if (textureRectangle)
    {
        if (textureDimension >= targetTextureSize) // the texture dimension is greater than the target texture size (i.e., it fits)
            return targetTextureSize; // return corresponding texture size
        else
            return textureDimension; // jusr return the dimension
    }
    else
    {
        do // while we have txture sizes check for texture value being equal or greater
        {  
            if (textureDimension >= targetTextureSize) // the texture dimension is greater than the target texture size (i.e., it fits)
                return targetTextureSize; // return corresponding texture size
        }
        while (targetTextureSize >>= 1); // step down to next texture size smaller
    }
    return 0; // no textures fit so return zero
}
 
// ---------------------------------
 
// returns the nuber of textures need to represent a size of textureDimension given
// requirement for power of 2 textures and gMaxTextureSize as the maximum texture size
// for the overlap case each texture effectively covers two less pixels so must iterate through using whole statement
 
static long GetTextureNumFromTextureDim (long textureDimension, long maxTextureSize, Boolean texturesOverlap, Boolean textureRectangle) 
{
    // start at max texture size 
    // loop through each texture size, removing textures in turn which are less than the remaining texture dimension
    // each texture has 2 pixels of overlap (one on each side) thus effective texture removed is 2 less than texture size
    
    long i = 0; // initially no textures
    long bitValue = maxTextureSize; // start at max texture size
    long texOverlapx2 = texturesOverlap ? 2 : 0;
    textureDimension -= texOverlapx2; // ignore texture border since we are using effective texure size (by subtracting 2 from the initial size)
    if (textureRectangle)
    {
        // count number of full textures
        while (textureDimension > (bitValue - texOverlapx2)) // while our texture dimension is greater than effective texture size (i.e., minus the border)
        {
            i++; // count a texture
            textureDimension -= bitValue - texOverlapx2; // remove effective texture size
        }
        // add one partial texture
        i++; 
    }
    else
    {
        do
        {
            while (textureDimension >= (bitValue - texOverlapx2)) // while our texture dimension is greater than effective texture size (i.e., minus the border)
            {
                i++; // count a texture
                textureDimension -= bitValue - texOverlapx2; // remove effective texture size
            }
        }
        while ((bitValue >>= 1) > texOverlapx2); // step down to next texture while we are greater than two (less than 4 can't be used due to 2 pixel overlap)
    if (textureDimension > 0x0) // if any textureDimension is left there is an error, because we can't texture these small segments and in anycase should not have image pixels left
        ReportErrorNum ("GetTextureNumFromTextureDim error: Texture to small to draw, should not ever get here, texture size remaining:", textureDimension);
    }
    return i; // return textures counted
} 
 
// ---------------------------------
 
// draws a single texture piece of the image
// offset is the start point in the image and end is the end point
// all is relative to orginal image size so center around center of image and zoom as required
 
static void DrawGLImageTile (unsigned long drawType, float imageWidth, float imageHeight, float zoom, float textureWidth, float textureHeight,
                            float offsetX, float offsetY, float endX, float endY, Boolean texturesOverlap, Boolean textureRectangle)
{
    float startXDraw = (offsetX - imageWidth * 0.5f) * zoom; // left edge of poly: offset is in image local coordinates convert to world coordinates
    float endXDraw = (endX - imageWidth * 0.5f) * zoom; // right edge of poly: offset is in image local coordinates convert to world coordinates
    float startYDraw = (offsetY - imageHeight * 0.5f) * zoom; // top edge of poly: offset is in image local coordinates convert to world coordinates
    float endYDraw = (endY - imageHeight * 0.5f) * zoom; // bottom edge of poly: offset is in image local coordinates convert to world coordinates
    float texOverlap =  texturesOverlap ? 1.0f : 0.0f; // size of texture overlap, switch based on whether we are using overlap or not
    float startXTexCoord = texOverlap / (textureWidth + 2.0f * texOverlap); // texture right edge coordinate (stepped in one pixel for border if required)
    float endXTexCoord = 1.0f - startXTexCoord; // texture left edge coordinate (stepped in one pixel for border if required)
    float startYTexCoord = texOverlap / (textureHeight + 2.0f * texOverlap); // texture top edge coordinate (stepped in one pixel for border if required)
    float endYTexCoord = 1.0f - startYTexCoord; // texture bottom edge coordinate (stepped in one pixel for border if required)
    if (textureRectangle)
    {
        startXTexCoord = texOverlap; // texture right edge coordinate (stepped in one pixel for border if required)
        endXTexCoord = textureWidth + texOverlap; // texture left edge coordinate (stepped in one pixel for border if required)
        startYTexCoord = texOverlap; // texture top edge coordinate (stepped in one pixel for border if required)
        endYTexCoord = textureHeight + texOverlap; // texture bottom edge coordinate (stepped in one pixel for border if required)
    }
    if (endX > (imageWidth + 0.5)) // handle odd image sizes, (+0.5 is to ensure there is no fp resolution problem in comparing two fp numbers)
    {
        endXDraw = (imageWidth * 0.5f) * zoom; // end should never be past end of image, so set it there
        if (textureRectangle)
            endXTexCoord -= 1.0f;
        else
            endXTexCoord = 1.0f -  2.0f * startXTexCoord; // for the last texture in odd size images there are two texels of padding so step in 2
    }
    if (endY > (imageHeight + 0.5f)) // handle odd image sizes, (+0.5 is to ensure there is no fp resolution problem in comparing two fp numbers)
    {
        endYDraw = (imageHeight * 0.5f) * zoom; // end should never be past end of image, so set it there
        if (textureRectangle)
            endYTexCoord -= 1.0f;
        else
            endYTexCoord = 1.0f -  2.0f * startYTexCoord; // for the last texture in odd size images there are two texels of padding so step in 2
    }
    
    glBegin (drawType); // draw either tri strips of line strips (so this will drw either two tris or 3 lines)
        glTexCoord2f (startXTexCoord, startYTexCoord); // draw upper left in world coordinates
        glVertex3d (startXDraw, startYDraw, 0.0);
 
        glTexCoord2f (endXTexCoord, startYTexCoord); // draw lower left in world coordinates
        glVertex3d (endXDraw, startYDraw, 0.0);
 
        glTexCoord2f (startXTexCoord, endYTexCoord); // draw upper right in world coordinates
        glVertex3d (startXDraw, endYDraw, 0.0);
 
        glTexCoord2f (endXTexCoord, endYTexCoord); // draw lower right in world coordinates
        glVertex3d (endXDraw, endYDraw, 0.0);
    glEnd();
    
    // finish strips
    if (drawType == GL_LINE_STRIP) // draw top and bottom lines which were not draw with above
    {
        glBegin (GL_LINES);
            glVertex3d(startXDraw, endYDraw, 0.0); // top edge
            glVertex3d(startXDraw, startYDraw, 0.0);
    
            glVertex3d(endXDraw, startYDraw, 0.0); // bottom edge
            glVertex3d(endXDraw, endYDraw, 0.0);
        glEnd();
    }
}
 
 
// ---------------------------------
 
// idle timer to window auto rotation updates
 
pascal void IdleTimer (EventLoopTimerRef inTimer, void* userData)
{
    #pragma unused (inTimer)
    pRecImage pWindowInfo = NULL;
    WindowRef window = (WindowRef) userData;
    if (window)
        pWindowInfo = (pRecImage) GetWRefCon (window);
    if (pWindowInfo)
        DrawGL (window);
}
 
// ---------------------------------
 
// builds timer UPP
 
EventLoopTimerUPP GetTimerUPP (void)
{
    static EventLoopTimerUPP    sTimerUPP = NULL;
    
    if (sTimerUPP == NULL)
        sTimerUPP = NewEventLoopTimerUPP (IdleTimer);
    
    return sTimerUPP;
}
 
#pragma mark -
// ==================================
// public
 
// disposes OpenGL context, and associated texture list
 
OSStatus DisposeGLForWindow (WindowRef window)
{
    if (window)
    {
        pRecImage pWindowInfo = (pRecImage) GetWRefCon (window); // get gl data stored in refcon
        SetWRefCon (window, NULL); // ensure the refcon is not used again
        if (NULL == pWindowInfo) // if this is non-existant
            return paramErr; // then drop out
        if (NULL != pWindowInfo->aglContext)
        {
            aglSetCurrentContext (pWindowInfo->aglContext); // ensaure the context we are working with is set to current
            aglUpdateContext (pWindowInfo->aglContext); // ensaure the context we are working with is set to current
            glFinish (); // ensure all gl commands are complete
            glDeleteTextures (pWindowInfo->textureX * pWindowInfo->textureY, pWindowInfo->pTextureName); // delete the complete set of textures used for the window
            DestroyGLFromWindow (&pWindowInfo->aglContext, &pWindowInfo->glInfo); // preoperly destroy GL context and any associated structures
            pWindowInfo->aglContext = NULL; // ensure we don't use invlad context
        }
        if (NULL != pWindowInfo->pTextureName)
        {
            DisposePtr ((Ptr) pWindowInfo->pTextureName); // dispose of the allocate4d texture name storage
            pWindowInfo->pTextureName = NULL; // ensure we do not use it again
        }
        if (pWindowInfo->pImageBuffer) // MUST preserve the buffer if texturing from client memory
        {
            DisposePtr ((Ptr) pWindowInfo->pImageBuffer); // or image buffer
            pWindowInfo->pImageBuffer = NULL;
        }
        DisposePtr ((Ptr) pWindowInfo);
        return noErr; // we are good to go
    }
    else
        return paramErr; // NULL window ref passed in
}
 
// ---------------------------------
 
// builds the GL context and associated state for the window
// loads image into a texture or textures
// disposes of GWorld and image buffer when finished loading textures
 
OSStatus BuildGLForWindow (WindowRef window)
{
    GrafPtr portSave = NULL; // port which is set on entrance to this routine
    pRecImage pWindowInfo = (pRecImage) GetWRefCon (window); // the info structure for the window stored in the refcon
    short i; // iterator
    short fNum; // font number
    GLenum textureTarget = GL_TEXTURE_2D;
   
    if (!pWindowInfo->aglContext) // if we get here and do not have a context built, build one
    {
        GetPort (&portSave);    // save current port
        SetPort ((GrafPtr) GetWindowPort (window)); // set port to the current window
        // set parameters for Carbon SetupGL
        pWindowInfo->glInfo.fAcceleratedMust = false; // must renderer be accelerated?
        pWindowInfo->glInfo.VRAM = 0 * 1048576; // minimum VRAM (if not zero this is always required)
        pWindowInfo->glInfo.textureRAM = 0 * 1048576; // minimum texture RAM (if not zero this is always required)
        if (!CheckMacOSX ())
            pWindowInfo->glInfo.fDraggable = false; // is this a draggable window
        else
            pWindowInfo->glInfo.fDraggable = true; // is this a draggable window
        pWindowInfo->glInfo.fmt = 0; // output pixel format
        
        i = 0; // first attribute in array
        pWindowInfo->glInfo.aglAttributes [i++] = AGL_RGBA; // RGB + Alpha pixels
        pWindowInfo->glInfo.aglAttributes [i++] = AGL_DOUBLEBUFFER; // doble buffered context
        pWindowInfo->glInfo.aglAttributes [i++] = AGL_ACCELERATED; // require hardware acceleration
        pWindowInfo->glInfo.aglAttributes [i++] = AGL_NO_RECOVERY; // Mac OS X 10.0.4 has problems with the GL (disregards UNPACK_ROW_LENGTH) resulting from using no recovery
                                                                   // normally we would use no recovery to ensure the minimum pixel size textures are stored by GL.
        pWindowInfo->glInfo.aglAttributes [i++] = AGL_NONE; // end parameter list
        BuildGLFromWindow (window, &(pWindowInfo->aglContext), &(pWindowInfo->glInfo), NULL); // build opengl context for our window
        if (!pWindowInfo->aglContext) // if could not create context
            DestroyGLFromWindow (&pWindowInfo->aglContext, &pWindowInfo->glInfo); // ensure context is destroyed correctly
        else // we have a valid context
        {
            GLint swap = 0; // swap interval (i.e., VBL sync) setting 1 = sync, 0 = no sync
            Rect rectPort; // window port rectangle
            long width = pWindowInfo->imageWidth, height = pWindowInfo->imageHeight; // image width and height
            GDHandle device; // GDevice to find the constrain the window to
            Rect deviceRect, availRect, rect; // rect of device which window is on (mostly, area wise at least). avialable area for window (minus dock and menu if req), working rect
 
            GetWindowGreatestAreaDevice (window, kWindowContentRgn, &device, &deviceRect); // find device the window is mostly on
            GetAvailableWindowPositioningBounds (device, &availRect); //  get the geretest available area for te windoew (mminus doc and menu if applicable)
            if (width > (availRect.right - availRect.left)) // adjust window width if it is greater than available area (orginally set to image width, see above)
                width = (availRect.right - availRect.left);
            if (height > (availRect.bottom - availRect.top)) // adjust window height if it is greater than available area (orginally set to image width, see above)
                height = (availRect.bottom - availRect.top);
            SizeWindow (window, (short) width, (short) height, true); // size the window to new width and height
            ConstrainWindowToScreen(window, kWindowStructureRgn, kWindowConstrainMayResize, NULL, &rect); // ensure window structure region is on the screen
            GetWindowPortBounds (window, &rectPort); // get port rect for viewport reset
 
            aglSetCurrentContext (pWindowInfo->aglContext); // set our GL context to this one
            aglUpdateContext (pWindowInfo->aglContext); // update the context to account for the resize
            InvalWindowRect (window, &rectPort); // inval the entire window to ensure we get a redraw
            glViewport (0, 0, rectPort.right - rectPort.left, rectPort.bottom - rectPort.top); // reset viewport to entier window area
 
            aglSetInteger (pWindowInfo->aglContext, AGL_SWAP_INTERVAL, &swap); // set swap interval to account for vbl syncing or not
            
            // set correct texture target
        #ifdef GL_TEXTURE_RECTANGLE_EXT // if building on 10.0 or 9 this will be undefined
            if (pWindowInfo->fNPOTTextures)
                textureTarget = GL_TEXTURE_RECTANGLE_EXT;
        #endif
            
            // Set texture mapping parameters
            glEnable (textureTarget); // enable texturing
                
            glClearColor(0.1f, 0.1f, 0.2f, 1.0f); // set clear color buffer to dark gray
            glClear (GL_COLOR_BUFFER_BIT); // clear just to color buffer
            aglSwapBuffers (pWindowInfo->aglContext); // swap the cleared buffer to front
    
            GetFNum ("\pMonaco", &fNum); // get font number for named font
            pWindowInfo->fontList = BuildFontGL (pWindowInfo->aglContext, fNum, normal, 9); // build display list for fonts for this context (see aglString for more info)
            
            // if we can use texture rectangle
        #ifdef GL_TEXTURE_RECTANGLE_EXT // if building on 10.0 or 9 this will be undefined
            if (pWindowInfo->fNPOTTextures)
                glEnable(GL_TEXTURE_RECTANGLE_EXT);
        #endif
            glPixelStorei (GL_UNPACK_ROW_LENGTH, pWindowInfo->textureWidth); // set image width in groups (pixels), accounts for border this ensures proper image alignment row to row
            // get number of textures x and y
                // extract the number of horiz. textures needed to tile image
            pWindowInfo->textureX = GetTextureNumFromTextureDim (pWindowInfo->textureWidth, pWindowInfo->maxTextureSize, pWindowInfo->fOverlapTextures, pWindowInfo->fNPOTTextures); 
                // extract the number of horiz. textures needed to tile image
            pWindowInfo->textureY = GetTextureNumFromTextureDim (pWindowInfo->textureHeight, pWindowInfo->maxTextureSize, pWindowInfo->fOverlapTextures, pWindowInfo->fNPOTTextures); 
            pWindowInfo->pTextureName = (GLuint *) NewPtrClear ((long) sizeof (GLuint) * pWindowInfo->textureX * pWindowInfo->textureY); // allocate storage for texture name lists
            glGenTextures (pWindowInfo->textureX * pWindowInfo->textureY, pWindowInfo->pTextureName); // generate textures names need to support tiling
            {
                long x, y, k = 0, offsetY, offsetX = 0, currWidth, currHeight; // texture iterators, texture name iterator, image offsets for tiling, current texture width and height
                for (x = 0; x < pWindowInfo->textureX; x++) // for all horizontal textures
                {
                    currWidth = GetNextTextureSize (pWindowInfo->textureWidth - offsetX, pWindowInfo->maxTextureSize, pWindowInfo->fNPOTTextures); // use remaining to determine next texture size 
                                                                                                                    // (basically greatest power of 2 which fits into remaining space)
                    offsetY = 0; // reset vertical offest for every column
                    for (y = 0; y < pWindowInfo->textureY; y++) // for all vertical textures
                    {
                        // buffer pointer is at base + rows * row size + columns
                        unsigned char * pBuffer = pWindowInfo->pImageBuffer + 
                                                   offsetY * pWindowInfo->textureWidth * (pWindowInfo->imageDepth >> 3) + 
                                                   offsetX * (pWindowInfo->imageDepth >> 3);
                        currHeight = GetNextTextureSize (pWindowInfo->textureHeight - offsetY, pWindowInfo->maxTextureSize, pWindowInfo->fNPOTTextures); // use remaining to determine next texture size
                        glBindTexture (textureTarget, pWindowInfo->pTextureName[k++]);
                        if (pWindowInfo->fAGPTexturing)
                            glTexParameterf (textureTarget, GL_TEXTURE_PRIORITY, 0.0f); // AGP texturing
                        else
                            glTexParameterf (textureTarget, GL_TEXTURE_PRIORITY, 1.0f);
                            
                    #ifdef GL_UNPACK_CLIENT_STORAGE_APPLE
                        if (pWindowInfo->fClientTextures)
                            glPixelStorei (GL_UNPACK_CLIENT_STORAGE_APPLE, 1);
                        else
                            glPixelStorei (GL_UNPACK_CLIENT_STORAGE_APPLE, 0);
                    #endif
                            
                        glTexParameteri (textureTarget, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
                        glTexParameteri (textureTarget, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
                        glTexParameteri (textureTarget, GL_TEXTURE_WRAP_S, gpOpenGLCaps->edgeClampParam);
                        glTexParameteri (textureTarget, GL_TEXTURE_WRAP_T, gpOpenGLCaps->edgeClampParam);
                        glReportError (); // report any errors so far
                        glTexImage2D (textureTarget, 0, GL_RGBA, currWidth, currHeight, 0, 
                                      GL_BGRA_EXT, pWindowInfo->imageDepth == 32 ? GL_UNSIGNED_INT_8_8_8_8_REV : GL_UNSIGNED_SHORT_1_5_5_5_REV, 
                                      pBuffer); // texture with current width and height at pBuffer location in image buffer with image size as GL_UNPACK_ROW_LENGTH
                        glReportError (); // report any errors
                        offsetY += currHeight - 2 * pWindowInfo->fOverlapTextures; // offset in for the amount of texture used, 
                                                                                       //  since we are overlapping the effective texture used is 2 texels less than texture width
                    }
                    offsetX += currWidth - 2 * pWindowInfo->fOverlapTextures; // offset in for the amount of texture used, 
                                                                              //  since we are overlapping the effective texture used is 2 texels less than texture width
                }
            }
            if (false == pWindowInfo->fClientTextures) // MUST preserve the buffer if texturing from client memory
            {
                DisposePtr ((Ptr) pWindowInfo->pImageBuffer); // or image buffer
                pWindowInfo->pImageBuffer = NULL;
            }
        }
        SetPort (portSave); //reset port
    }
    return noErr; // we done
}
 
// ---------------------------------
 
// Handle updating context for window moves and resizing
 
OSStatus ResizeMoveGLWindow (WindowRef window)
{
    OSStatus err = noErr; // no errors to start
    Rect rectPort; // new port rect
    if (NULL != window) // if we have a window
    {
        pRecImage pWindowInfo = (pRecImage) GetWRefCon (window); // get GL info
        if (NULL != pWindowInfo) // if we have gl info for the window
            if (!aglUpdateContext (pWindowInfo->aglContext)) // update the context to ensure gl knows about the move or resize
                aglReportError (); // report any error with update context
        GetWindowPortBounds (window, &rectPort); // find new bounds
        err = InvalWindowRect (window, &rectPort); // inval entire bounds to ensure a redraw (which will be superfluous on moves but not a big deal
        if (noErr != err)
            ReportErrorNum ("ResizeMoveGLWindow error with InvalWindowRect on window: ", err);  // should not get erro here, but who knows
    }
    else
        err = paramErr; // bad window
    return err; // return any error 
}
 
// ---------------------------------
 
// resets tracking of mouse drag when wmouse is released
 
void MouseUpGLWindow (void)
{
    gptDragStart.v = 0; // reset tracking variables to idle state
    gptDragStart.h = 0;
    gDragWindow = NULL;
}
 
// ---------------------------------
 
// starts tracking of mouse down for drag calculatons
// need to record drag window local position of the mouse and whether we are dragging or rotating
 
void MouseDownGLWindow (WindowRef window, Point mousePoint, UInt32 modifiers)
{
    GrafPtr portSave;
    if (NULL != window) // check for valid window
    {
        GetPort (&portSave); // save current port
        SetPort (GetWindowPort (window)); // set window port (for global to local call)
        GlobalToLocal (&mousePoint); //convert mouse coordinates to local coordintes prior to recording
        gptDragStart = mousePoint; // record start point
        gDragWindow = window; // record window we are dragging in
        gDragType = kDrag; // default is to drag
        if (modifiers & cmdKey) // if option key is down 
            gDragType = kRotation; // rotate instead of dragging
        else if (modifiers & optionKey)
            gDragType = kZoom; // rotate instead of dragging
        SetPort (portSave); // reset port
    }
}
 
// ---------------------------------
 
// main working functioning hamdling live drag and rotation
// moves center based on mouse delta from last check or rotates image based on nagular chnage of mouse (from image center)
 
void DragGLWindow (Point dragPoint)
{
    GrafPtr portSave; // port to save
    Rect rectPort; // reactangle for current point
    float fPi = 3.14159265f; // our PI approximation
    if (NULL != gDragWindow) // use window as latch for dragging being live
    {
        pRecImage pWindowInfo = (pRecImage) GetWRefCon (gDragWindow); // get the gl info from the ref con of the window
        GetPort (&portSave); // save current port
        SetPort (GetWindowPort (gDragWindow));  // set port to our drag window
        GlobalToLocal (&dragPoint); // convert current point to window local (as start point is also window local)
        if (pWindowInfo) // if we have valid gl info for the window
        { 
            GetWindowPortBounds (gDragWindow, &rectPort); // get our bounds rect
            if (gDragType == kDrag) // if we are dragging (as opposed to rotating
            {
                pWindowInfo->centerX += dragPoint.h - gptDragStart.h; // just offset the center of the window by the delta (start to current)
                pWindowInfo->centerY += dragPoint.v - gptDragStart.v;
            }
            else if (gDragType == kRotation) // if we are rotating
            {
                float imageCenterX = rectPort.left + (rectPort.right - rectPort.left) / 2 + pWindowInfo->centerX; // find the image center relative to port
                float imageCenterY = rectPort.top + (rectPort.bottom - rectPort.top) / 2 + pWindowInfo->centerY;
                float deltaX, deltaY; // delta from drag point to image center
                float angle1, angle2; // angular representation of start and current angles off vertical for mouse points (relative to image center
                deltaX = imageCenterX - dragPoint.h; // find delta vector current drag point to image center
                deltaY = imageCenterY - dragPoint.v;
                if (deltaX == 0.0) // if vector is vertical
                    if (deltaY < 0.0) angle1 = 0.0f; // and Y is negative, the angle is 0 (i.e., pointing up)
                    else angle1 = fPi; // else Y must be zero or poistive so vector in PI radians (i.e., pointing down)
                else // if X is not 0, thus no disparate case
                {
                    angle1 = (float) atan (deltaY / deltaX); // the angle (0 to PI / 2) is the arctangent of rise over run (Y / X)
                    if (deltaX  < 0.0) angle1 += 1.5 * fPi; // if X is negative then add 3 / 2 PI (270¡) to the angle from above
                    else angle1 += 0.5 * fPi; // otherwise rotate by 1 / 2 PI (90¡) to angle
                }
                    
                deltaX = imageCenterX - gptDragStart.h; // find delta vector drag start point to image center
                deltaY = imageCenterY - gptDragStart.v;
                if (deltaX == 0.0) // if vector is vertical
                    if (deltaY < 0.0) angle2 = 0.0f; // and Y is negative, the angle is 0 (i.e., pointing up)
                    else angle2 = fPi; // else Y must be zero or poistive so vector in PI radians (i.e., pointing down)
                else
                {
                    angle2 = (float) atan (deltaY / deltaX); // the angle (0 to PI / 2) is the arctangent of rise over run (Y / X)
                    if (deltaX  < 0.0) angle2 += 1.5 * fPi; // if X is negative then add 3 / 2 PI (270¡) to the angle from above
                    else angle2 += 0.5 * fPi; // otherwise rotate by 1 / 2 PI (90¡) to angle
                }
                angle2 -= angle1; // find angle delta (the difference between the start angle and the current angle
                
                if (angle2 > fPi) angle2 -= fPi * 2.0; // correct for shortest path
                if (angle2 < -fPi) angle2 += fPi * 2.0;
                    
                pWindowInfo->rotation -= angle2 * 180 / fPi; // convert to degrees and increment ratation angle 
                while (pWindowInfo->rotation >= 360.0) pWindowInfo->rotation -= 360.0; // ensure within -360 to 360
                while (pWindowInfo->rotation <= -360.0) pWindowInfo->rotation += 360.0;
            }
            else if (gDragType == kZoom) // if we are rotating
            {
                float zoomDelta = (float)(dragPoint.v - gptDragStart.v) * 0.01f; // just offset the center of the window by the delta (start to current)
                zoomDelta = 1.0f - zoomDelta;
                if (zoomDelta < 0.0001f) // prevetns negative zoom values (which would invert the image)
                    zoomDelta = 0.0001f;
                pWindowInfo->zoom *= zoomDelta;
                pWindowInfo->centerX *= zoomDelta;
                pWindowInfo->centerY *= zoomDelta;
            }
            gptDragStart = dragPoint; // reset point to the current point so next pass through will work incrementally
            InvalWindowRect (gDragWindow, &rectPort); // ensure window will be redrawn
        }
        SetPort (portSave); // reset port
    }
}
 
// ---------------------------------
 
void StartRotation (WindowRef window)
{
    if (window)
    {
        pRecImage pWindowInfo = (pRecImage) GetWRefCon (window); // get gl data stored in refcon
        if (pWindowInfo)
        {
            if (pWindowInfo->timer)
                RemoveEventLoopTimer(pWindowInfo->timer);
            pWindowInfo->timer = NULL;
            InstallEventLoopTimer (GetCurrentEventLoop(), 0, pWindowInfo->timerInterval, GetTimerUPP (), (void *) window, &(pWindowInfo->timer));
        }
    }
}
 
// ---------------------------------
 
void StopRotation (WindowRef window)
{
    if (window)
    {
        pRecImage pWindowInfo = (pRecImage) GetWRefCon (window); // get gl data stored in refcon
        if (pWindowInfo)
        {
            if (pWindowInfo->timer)
                RemoveEventLoopTimer(pWindowInfo->timer);
            pWindowInfo->timer = NULL;
        }
    }
}
 
// ---------------------------------
 
// main GL drawing routine, should be valid window passed in (will setupGL if require).  Draw image, then grid, then edges, then information
 
void DrawGL (WindowRef window)
{
    Rect rectPort; // rectangle for port
    pRecImage pWindowInfo; // the gl info for the target window
    long width, height, yRaster = 1; // width and height or the port and the row of the raster position
    char cstr [256]; // string to output to screen
    long effectiveTextureMod = 0; // texture size modification (inset) to account for borders
    long x, y, k = 0, offsetY, offsetX = 0, currTextureWidth, currTextureHeight;
    GLenum textureTarget = GL_TEXTURE_2D;
    
    if (NULL == window) // if we do not have a window
        return; // drop out
    pWindowInfo = (pRecImage) GetWRefCon (window); // get the gl info for the window
    if (NULL == pWindowInfo) // if this is non-existant
        return; // then drop out
    if (NULL == pWindowInfo->aglContext) // try to buld the context if we don't have one (safety check)
        BuildGLForWindow (window);
    if (NULL == pWindowInfo->aglContext) // if we still don't have one then drop out
        return;
        
    // adjust timer
    if (pWindowInfo->spinning)
    {
        AbsoluteTime currTime = UpTime ();
        float frameRate, deltaTime = (float) AbsoluteDeltaToDuration (currTime, pWindowInfo->time);
        if (0 > deltaTime)  // if negative microseconds
            deltaTime /= -1000000.0;
        else                // else milliseconds
            deltaTime /= 1000.0;
        frameRate = (pWindowInfo->frames + 1) / deltaTime;
        if (frameRate * pWindowInfo->timerInterval > 1.0f)  // if timer too slow, increase rate
        {
            pWindowInfo->timerInterval *= 0.80; // reduce interval
            RemoveEventLoopTimer(pWindowInfo->timer);
            InstallEventLoopTimer (GetCurrentEventLoop(), 0, pWindowInfo->timerInterval, GetTimerUPP (), (void *) window, &(pWindowInfo->timer));
        }
        
        // spin
        pWindowInfo->rotation += 1.0;
        while (pWindowInfo->rotation >= 360.0) pWindowInfo->rotation -= 360.0; // ensure within -360 to 360
        while (pWindowInfo->rotation <= -360.0) pWindowInfo->rotation += 360.0;
    }
        
    if (pWindowInfo->fOverlapTextures)
        effectiveTextureMod = 2; // if we overlap then we need to inset the textures passed to the drawing code
    // set texture target
#ifdef GL_TEXTURE_RECTANGLE_EXT
    if (pWindowInfo->fNPOTTextures)
        textureTarget = GL_TEXTURE_RECTANGLE_EXT;
#endif
 
    aglSetCurrentContext (pWindowInfo->aglContext); // ensaure the context we are working with is set to current
    aglUpdateContext (pWindowInfo->aglContext); // ensaure the context we are working with is set to current
 
    GetWindowPortBounds (window, &rectPort); // get the current port (window) bounds
    width = rectPort.right - rectPort.left; // find width
    height = rectPort.bottom - rectPort.top; // and height
    glViewport (0, 0, width, height); // set the viewport to cover entire window
    
    glMatrixMode (GL_PROJECTION); // set projection matrix
    glLoadIdentity (); // to indetity
    glMatrixMode (GL_MODELVIEW); // set modelview matrix
    glLoadIdentity (); // to identity
    glReportError (); // report any GL errors so far
    
    // set the model view matrix for an orthographic view scaled to one screen pixel equal image pixel (independent of image zoom)
    glScalef (2.0f / width, -2.0f /  height, 1.0f); // scale to port per pixel scale
    glTranslatef (pWindowInfo->centerX, pWindowInfo->centerY, 0.0f); // translate for image movement
    glRotatef (pWindowInfo->rotation, 0.0f, 0.0f, 1.0f); // ratate matrix for image rotation
    glReportError (); // report any GL errors
    
    glClear (GL_COLOR_BUFFER_BIT); // clear the color buffer before drawing
 
    // draw image
    glEnable (textureTarget); // enable texturing
    glColor3f (1.0f, 1.0f, 1.0f); // white polygons
    // offset x and y are used to draw the polygon and need to represent the texture effective edges (without borders)
    // so walk the texture size images adjusting for each border
    for (x = 0; x < pWindowInfo->textureX; x++) // for all horizontal textures
    {
        // use remaining to determine next texture size
        currTextureWidth = GetNextTextureSize (pWindowInfo->textureWidth - offsetX, pWindowInfo->maxTextureSize, pWindowInfo->fNPOTTextures) - effectiveTextureMod; // current effective texture width for drawing
        offsetY = 0; // start at top
        for (y = 0; y < pWindowInfo->textureY; y++) // for a complete column
        {
            // use remaining to determine next texture size
            currTextureHeight = GetNextTextureSize (pWindowInfo->textureHeight - offsetY, pWindowInfo->maxTextureSize, pWindowInfo->fNPOTTextures) - effectiveTextureMod; // effective texture height for drawing
            glBindTexture(textureTarget, pWindowInfo->pTextureName[k++]); // work through textures in same order as stored, setting each texture name as current in turn
            glReportError (); // report any errors
            DrawGLImageTile (GL_TRIANGLE_STRIP, pWindowInfo->imageWidth, pWindowInfo->imageHeight, pWindowInfo->zoom, 
                                currTextureWidth, currTextureHeight, // draw this single texture on two tris 
                                offsetX,  offsetY, 
                                pWindowInfo->fTileTextures ? currTextureWidth + offsetX : pWindowInfo->imageWidth, 
                                pWindowInfo->fTileTextures ? currTextureHeight + offsetY : pWindowInfo->imageHeight, 
                                pWindowInfo->fOverlapTextures, pWindowInfo->fNPOTTextures);
            glReportError (); // report any errors
            offsetY += currTextureHeight; // offset drawing position for next texture vertically
        }
        offsetX += currTextureWidth; // offset drawing position for next texture horizontally
    }
    glReportError (); // report any errors
 
    glDisable (textureTarget); // done with texturing
 
    // draw texel grid
    glColor3f (0.0f, 0.0f, 1.0f); // draw texel grid in blue
    if (pWindowInfo->grid) // if drawing is on
    {
        long vertMod = effectiveTextureMod; // find the current mod to get to the effective texture size
        long horizMod = effectiveTextureMod;
        if (pWindowInfo->fTileTextures) // if we are tiling we have to account for odd size images
        {   // if the image size is odd then we know that we had to add an extra border pixel
            // account for this when drawing thegrid to ensure we draw the right amount of texels
            vertMod += (pWindowInfo->imageHeight & 0x01) ? 1 : 0; 
            horizMod += (pWindowInfo->imageWidth & 0x01) ? 1 : 0;
        }
        // draw grid showing the actual texels draw to the screen each frame
        DrawGLTexelGrid (pWindowInfo->textureWidth - horizMod, pWindowInfo->textureHeight - vertMod, pWindowInfo->imageWidth, pWindowInfo->imageHeight, pWindowInfo->zoom);
    }
    glReportError (); // report any errors
 
    // draw polygon edges
    glColor3f (0.0f, 1.0f, 0.0f); // draw in green for polygon edges
    if (pWindowInfo->lines) // if edges turned on
    {
        // offset x and y are used to draw the polygon and need to represent the texture effective edges (without borders)
        // so walk the texture size images adjusting for each border
        offsetX = 0;
        for (x = 0; x < pWindowInfo->textureX; x++) // for all horizontal textures
        {
            // use remaining to determine next texture size
            currTextureWidth = GetNextTextureSize (pWindowInfo->textureWidth - offsetX, pWindowInfo->maxTextureSize, pWindowInfo->fNPOTTextures) - effectiveTextureMod; // current effective texture width for drawing
            offsetY = 0; // start at top
            for (y = 0; y < pWindowInfo->textureY; y++) // for a complete column
            {
                // use remaining to determine next texture size
                currTextureHeight = GetNextTextureSize (pWindowInfo->textureHeight - offsetY, pWindowInfo->maxTextureSize, pWindowInfo->fNPOTTextures) - effectiveTextureMod; // effective texture height for drawing
                DrawGLImageTile (GL_LINE_STRIP, pWindowInfo->imageWidth, pWindowInfo->imageHeight, pWindowInfo->zoom, 
                                    currTextureWidth, currTextureHeight, // draw this single texture on two tris 
                                    offsetX,  offsetY, 
                                    pWindowInfo->fTileTextures ? currTextureWidth + offsetX : pWindowInfo->imageWidth, 
                                    pWindowInfo->fTileTextures ? currTextureHeight + offsetY : pWindowInfo->imageHeight, 
                                    pWindowInfo->fOverlapTextures, pWindowInfo->fNPOTTextures);
                offsetY += currTextureHeight; // offset drawing position for next texture vertically
            }
            offsetX += currTextureWidth; // offset drawing position for next texture horizontally
        }
    }
    glReportError (); // report any errors
 
    // draw info
    if (pWindowInfo->info) // if we are drawing the information overlay
    {
        glLoadIdentity (); // reset model view matrix to identity (eliminates rotation basically)
        glScalef (2.0f / width, -2.0f /  height, 1.0f); // scale to port per pixel scale
        glTranslatef (-(width) / 2.0f, -(height) / 2.0f, 0.0f); // translate center to upper left
 
        glColor3f (1.0f, 1.0f, 0.0f);// draw in yellow
        // center tick
        glBegin (GL_LINES); // draw center tick mark
            glVertex3f(width / 2.0f - 4.5f, height / 2.0f, 0.0f);
            glVertex3f(width / 2.0f + 4.5f, height / 2.0f, 0.0f);
    
            glVertex3f(width / 2.0f, height / 2.0f - 4.5f, 0.0f);
            glVertex3f(width / 2.0f, height / 2.0f + 4.5f, 0.0f);
        glEnd();
 
        glRasterPos3d (10, yRaster++ * 12, 0); // set raster postion of bottom of text, incrementing for next line
        sprintf (cstr, "Image: (%ld x %ld x %ld)", 
                        pWindowInfo->imageWidth, pWindowInfo->imageHeight, pWindowInfo->imageDepth);
        DrawCStringGL (cstr, pWindowInfo->fontList); // draw image size and depth
 
        glRasterPos3d (10, yRaster++ * 12, 0); // set raster postion of bottom of text, incrementing for next line
        sprintf (cstr, "Textures: (%ld x %ld) Texels: (%ld x %ld)", 
                        pWindowInfo->textureX, pWindowInfo->textureY, pWindowInfo->textureWidth, pWindowInfo->textureHeight);
        DrawCStringGL (cstr, pWindowInfo->fontList); // draw texture info (1 x 1 with X texels for single texture code, X x Y for Z texels for tiled code
        if (pWindowInfo->fOverlapTextures)
            DrawCStringGL (" -overlapped-" , pWindowInfo->fontList);
 
        glRasterPos3d (10, yRaster++ * 12, 0); // set raster postion of bottom of text, incrementing for next line
        sprintf (cstr, "Window: (%ld x %ld)", 
                        width, height);
        DrawCStringGL (cstr, pWindowInfo->fontList); // draw window size
 
        glRasterPos3d (10, yRaster++ * 12, 0); // set raster postion of bottom of text, incrementing for next line
        sprintf (cstr, "Position: (%0.1f, %0.1f) at %0.1f deg", 
                        pWindowInfo->centerX / pWindowInfo->zoom, pWindowInfo->centerY / pWindowInfo->zoom, pWindowInfo->rotation);
        DrawCStringGL (cstr, pWindowInfo->fontList); // draw position information
 
        glRasterPos3d (10, yRaster++ * 12, 0); // set raster postion or bottom of text, incrementing for next line
        sprintf (cstr, "Zoom: %0.2f", 
                        pWindowInfo->zoom);
        DrawCStringGL (cstr, pWindowInfo->fontList); // draw zoom level
 
        yRaster = (height - 1) / 12;
 
        glRasterPos3d (10, yRaster-- * 12, 0); // set raster postion of bottom of text, incrementing for next line 
        DrawCStringGL ((char*) glGetString (GL_RENDERER), pWindowInfo->fontList); // drag renderer string
        DrawCStringGL (", ", pWindowInfo->fontList); // drag version string
        DrawCStringGL ((char*) glGetString (GL_VERSION), pWindowInfo->fontList); // drag version string
        
        if (pWindowInfo->fClientTextures || pWindowInfo->fAGPTexturing)
        {
            glRasterPos3d (10, yRaster-- * 12, 0); // set raster postion of bottom of text, incrementing for next line 
            if (pWindowInfo->fClientTextures)
                DrawCStringGL ("<Client Storage> ", pWindowInfo->fontList); // draw zoom level
            if (pWindowInfo->fAGPTexturing)
                DrawCStringGL ("<AGP Texturing Hint>", pWindowInfo->fontList); // draw zoom level
        }
        // print frames per second and texturing rate
        if (pWindowInfo->spinning)
        {
            AbsoluteTime currTime = UpTime ();
            float deltaTime = (float) AbsoluteDeltaToDuration (currTime, pWindowInfo->time);
            if (0 > deltaTime)  // if negative microseconds
                deltaTime /= -1000000.0;
            else                // else milliseconds
                deltaTime /= 1000.0;
        
            (pWindowInfo->frames)++;
            
            if (0.5 <= deltaTime)   // has update interval passed
            {
                double rate = pWindowInfo->frames / deltaTime;
                sprintf (pWindowInfo->cstrTime, "%0.0f FPS (%ld frames in %0.3f secs)", 
                        rate, 
                        pWindowInfo->frames,
                        deltaTime);
                pWindowInfo->time = currTime;   // reset for next time interval
                pWindowInfo->frames = 0;
            }
            glRasterPos3d (10, yRaster-- * 12, 0); // set raster postion or bottom of text, incrementing for next line 
            DrawCStringGL (pWindowInfo->cstrTime, pWindowInfo->fontList);
        }
 
        glReportError ();
    }
        
    aglSwapBuffers (pWindowInfo->aglContext);
}