/* 
 * tkOGL.c --
 *
 *	This module implements "OGLwin" widgets. This is the TCL
 *	interface to OpenGL.
 *
 */

#include <assert.h>
#include "tkogl.h"
#include "load3ds.h"
#include "get.h"
#include "printstr.h"
#include "gencyl.h"
#include "nurbs.h"
#include "tess.h"
#include "quadric.h"

/*
 * Class for OGLwin windows
 */
static Tk_Uid OglWinClass = NULL;

/*
 * Information used for argv parsing.
 */

static Tk_ConfigSpec configSpecs[] = {
    {TK_CONFIG_PIXELS, "-height", "height", "Height",
       "300" , Tk_Offset(OGLwin, height), 0},
  
    {TK_CONFIG_PIXELS, "-width", "width", "Width",
       "300" , Tk_Offset(OGLwin, width), 0},

    {TK_CONFIG_STRING, "-context", "context", "Context",
	NULL, Tk_Offset (OGLwin, context), TK_CONFIG_NULL_OK},

	{TK_CONFIG_STRING, "-refresh", "refresh", "Refresh",
	NULL, Tk_Offset (OGLwin, refresh), TK_CONFIG_NULL_OK},

    {TK_CONFIG_BOOLEAN, "-doublebuffer", "doublebuffer", "DoubleBuffer",
	"1", Tk_Offset (OGLwin, doubleBuffer), 0},

    {TK_CONFIG_INT, "-depthsize", "depthsize", "DepthSize",
	"16", Tk_Offset (OGLwin, depthSize), 0},

    {TK_CONFIG_INT, "-stencilsize", "stencilsize", "StencilSize",
	"0", Tk_Offset (OGLwin, stencilSize), 0},

    {TK_CONFIG_INT, "-alphasize", "alphasize", "AlphaSize",
	"0", Tk_Offset (OGLwin, alphaSize), 0},

    {TK_CONFIG_INT, "-accumsize", "accumsize", "AccumSize",
	"0", Tk_Offset (OGLwin, accumSize), 0},

    {TK_CONFIG_DOUBLE, "-aspectratio", "aspectratio", "AspectRatio",
	"0", Tk_Offset (OGLwin, aspectRatio), 0},

    {TK_CONFIG_BORDER, "-background", "background", "Background",
	"#d9d9d9", Tk_Offset(OGLwin, bgBorder), 0},

    {TK_CONFIG_END, (char *) NULL, (char *) NULL, (char *) NULL,
       (char *) NULL, 0, 0}
};

/*
 * Forward declarations for procedures defined later in this file:
 */
static int	OGLwinConfigure _ANSI_ARGS_((Tcl_Interp *interp,
	OGLwin *glxwinPtr, int argc, char **argv, int flags));
static void	OGLwinDestroy _ANSI_ARGS_((char* clientData));
static void	OGLwinEventProc _ANSI_ARGS_((ClientData clientData, XEvent *eventPtr));
static int	OGLwinWidgetCmd _ANSI_ARGS_((ClientData clientData, Tcl_Interp *, int argc, char **argv));
static void OGLwinRedraw _ANSI_ARGS_ ((ClientData clientData));
int OGLwinCmd(ClientData, Tcl_Interp*, int, char**);
static void OGLwinViewport (OGLwin* oglwinPtr);
static void MakeCurrent (OGLwin* oglwinPtr);
int texImage2D( Tcl_Interp *interp, int argc, char* argv[] );

#if defined(__WIN32__) || defined(_WIN32)
    static LONG WINAPI WndProc (HWND, UINT, WPARAM, LPARAM);
    static int WinMakeWindowExist (OGLwin*);
    static void SetDCPixelFormat (OGLwin*);

#else
    static Colormap getColormap _ANSI_ARGS_((Display *dpy, XVisualInfo *vi));

    static int configuration24[] = 
           {GLX_DOUBLEBUFFER, GLX_RGBA, GLX_RED_SIZE, 8, GLX_GREEN_SIZE, 8,
	        GLX_BLUE_SIZE, 8, GLX_DEPTH_SIZE, 16, None};

    static int configuration[] = 
           {GLX_DOUBLEBUFFER, GLX_RGBA, GLX_DEPTH_SIZE, 16, None};
#endif

#define ERRMSG(msg) {\
    Tcl_AppendResult(interp,msg,(char*)NULL); \
    return TCL_ERROR; \
}
#define ERRMSG2(msg1,msg2) {\
    Tcl_AppendResult(interp,msg1,msg2,(char*)NULL); \
    return TCL_ERROR; \
}

#define ARRANGE_REDRAW(glxwinptr) \
       if (!(glxwinptr)->updatePending) {\
	        Tcl_DoWhenIdle ((Tcl_IdleProc *)OGLwinRedraw, (ClientData)(glxwinptr));\
	        (glxwinPtr)->updatePending = 1;\
       }

#ifdef __WIN32__

/*
 *----------------------------------------------------------------------
 *
 * DllEntryPoint --
 *
 *	This wrapper function is used by Windows to invoke the
 *	initialization code for the DLL.  If we are compiling
 *	with Visual C++, this routine will be renamed to DllMain.
 *	routine.
 *
 * Results:
 *	Returns TRUE;
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */
BOOL APIENTRY DllEntryPoint( hInst, reason, reserved )
    HINSTANCE hInst;		/* Library instance handle. */
    DWORD reason;	    	/* Reason this function is being called. */
    LPVOID reserved;		/* Not used. */
{
    return TRUE;
}

static WNDCLASS childClass;	/* Tk Window class for child windows. */

static int createCount = 0;

static LONG WINAPI WndProc (HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
/*
 * This is the event proc for the opengl window
 *
 */
{   
    LONG result;
    switch (msg) {
        case WM_WINDOWPOSCHANGED:
        case WM_MOVE:            
			break;
        default: 
			return TkWinChildProc (hwnd, msg, wParam, lParam);	        
    }

    result = DefWindowProc(hwnd, msg, wParam, lParam);
    Tcl_ServiceAll();
    return result;    
}

#endif

EXPORT(int,Tkogl_Init)(Tcl_Interp* interp) 
{
    Tk_Window topLevel;
    topLevel = Tk_MainWindow(interp);

#ifdef __WIN32__

    /*** Fix floating point exception bug ***/
#ifdef __BORLANDC__
    /* This is how to do it in Borland C */
    _control87(MCW_EM, MCW_EM);
#else
    /* Don't know how to do it in Visual C++ - Need help here */
#endif

#else
    /*** make sure OpenGL's GLX extension supported ***/
    if (!glXQueryExtension(Tk_Display(topLevel), NULL, NULL)) {
       ERRMSG ("X server has no OpenGL GLX extension");
    }
#endif

    /*** Initialize the GL function parse tables ***/
    // InitHashTables ();

    /*** Create Tkogl main tcl commands ***/
    Tcl_CreateCommand( interp, "OGLwin", OGLwinCmd, topLevel, NULL );

    /*** Init standard extensions ***/
    if( RegisterTkOGLExtension( interp, "load3ds", glLoad3DStudio ) != TCL_OK )
		return TCL_ERROR;

    if( RegisterTkOGLExtension( interp, "get", GetGlVal ) != TCL_OK )
		return TCL_ERROR;

	if( RegisterTkOGLExtension( interp, "putString", tkoglPutString ) != TCL_OK )
		return TCL_ERROR;

	if( RegisterTkOGLExtension( interp, "deleteFont", tkoglDeleteFont ) != TCL_OK )
		return TCL_ERROR;

	if( RegisterTkOGLExtension( interp, "gencyl", GenericCylinder ) != TCL_OK )
       return TCL_ERROR;

	if( RegisterTkOGLExtension( interp, "nurbssurface", NurbsSurface )!= TCL_OK )
       return TCL_ERROR;

	if( RegisterTkOGLExtension( interp, "texImage2D", texImage2D ) != TCL_OK )
		return TCL_ERROR;

	if( RegisterTkOGLExtension( interp, "tesselate", Tesselate ) != TCL_OK )
       return TCL_ERROR;

	if (RegisterTkOGLExtension (interp, "cylinder", Quadric) != TCL_OK)
       return TCL_ERROR;

    if (RegisterTkOGLExtension (interp, "disk", Quadric) != TCL_OK)
       return TCL_ERROR;

    if (RegisterTkOGLExtension (interp, "partialdisk", Quadric) != TCL_OK)
       return TCL_ERROR;

    if (RegisterTkOGLExtension (interp, "sphere", Quadric) != TCL_OK)
       return TCL_ERROR;

    return Tcl_PkgProvide( interp, "Tkogl", "2.3" );
}

#ifdef __WIN32__

static int WinMakeWindowExist( OGLwin* oglWinPtr )
/*         ==================
 *
 * Forces window to exist so that we can setup MS's version of OpenGL 
 */
{
   static char* TkOGLClassName = "TkOGL Class";
   static int TkOGLClassInitted = 0;
   TkWindow *winPtr = (TkWindow *)oglWinPtr->tkwin;
   Display *dpy = Tk_Display (oglWinPtr->tkwin);
   Window parent;
   HWND hwnd, parentWin;
   HANDLE hInstance;
   WNDCLASS TkOGLClass;
   Tcl_HashEntry *hPtr;
   int new_flag;
   
   /* Destroy window if already exists */
   if( winPtr->window != None ) {
      XDestroyWindow(dpy, winPtr->window);
   }

   /* Find parent of window */
   /* Necessary for creation */
   if( (winPtr->parentPtr == NULL) || (winPtr->flags & TK_TOP_LEVEL) ) {
      parent = XRootWindow(winPtr->display, winPtr->screenNum);
   }
   else {
      if (winPtr->parentPtr->window == None) {
         Tk_MakeWindowExist((Tk_Window) winPtr->parentPtr);
      }
      parent = winPtr->parentPtr->window;
   }
   
   /* Create a window class for TkOGL windows if not done this yet */     
   parentWin = Tk_GetHWND(parent);
   hInstance = Tk_GetHINSTANCE();
   if (!TkOGLClassInitted) {
       TkOGLClassInitted = 1;
       TkOGLClass.style = CS_HREDRAW | CS_VREDRAW;
       TkOGLClass.cbClsExtra = 0;
       TkOGLClass.cbWndExtra = 4;   /* to save struct OGLwin* */
       TkOGLClass.hInstance = hInstance;
       TkOGLClass.hbrBackground = NULL;
       TkOGLClass.lpszMenuName = NULL;
       TkOGLClass.lpszClassName = TkOGLClassName;
       TkOGLClass.lpfnWndProc = WndProc;
       TkOGLClass.hIcon = NULL;
       TkOGLClass.hCursor = NULL;
       if (!RegisterClass(&TkOGLClass)){
          Tcl_AppendResult(oglWinPtr->interp, "could not register TkOGL class", (char *) NULL);
          return TCL_ERROR;
       }
   }

   /* Create Window */
   hwnd = CreateWindow(TkOGLClassName, NULL, WS_CHILD | WS_CLIPCHILDREN
		| WS_CLIPSIBLINGS, 0, 0, oglWinPtr->width, oglWinPtr->height,
		parentWin, NULL, hInstance, NULL);
   SetWindowLong(hwnd, 0, (LONG) oglWinPtr);
   SetWindowPos(hwnd, HWND_TOP, 0, 0, 0, 0, SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOSIZE);
   oglWinPtr->hwnd = hwnd;
   oglWinPtr->hdc = GetDC(hwnd);

   SetDCPixelFormat (oglWinPtr);
   winPtr->window = Tk_AttachHWND ((Tk_Window) winPtr, hwnd);   

   /* Don't know why the things below are necessary (copied
      from togl code) */
   hPtr = Tcl_CreateHashEntry(&winPtr->dispPtr->winTable,
                              (char *) winPtr->window, &new_flag);
   Tcl_SetHashValue(hPtr, winPtr);
   winPtr->dirtyAtts = 0;
   winPtr->dirtyChanges = 0;
   
   Tk_MapWindow (oglWinPtr->tkwin);
   /* XMapWindow (dpy, Tk_WindowId(oglWinPtr->tkwin)); */
   wglMakeCurrent (oglWinPtr->hdc, oglWinPtr->hrc);

   return TCL_OK;

} /* WinMakeWindowExist */

#endif 

int GetAbsXY (OGLwin *glxwinPtr)
/*  ========
 *
 * Updates the absolute x and y values for the upper left corner of
 * the window. Returns 1 if the recomputed values differ from the previous
 * one, or 0 otherwise
 */
{
   Tk_Window root = glxwinPtr->tkwin;
   int x = Tk_X (root);
   int y = Tk_Y (root);
   int modified;
   do {
      root = Tk_Parent (root);
      x += Tk_X (root);
      y += Tk_Y (root);
   } while (!Tk_IsTopLevel (root));
   modified = (x != glxwinPtr->absx || y != glxwinPtr->absy);
   glxwinPtr->absx = x;
   glxwinPtr->absy = y;
   return modified;
}

/*
 *--------------------------------------------------------------
 *
 * OGLwinCmd --
 *
 *	This procedure is invoked to process the "OGLwin" Tcl
 *	command.  It creates a new "OGLwin" widget.
 *
 * Results:
 *	A standard Tcl result.
 *
 * Side effects:
 *	A new widget is created and configured.
 *
 *--------------------------------------------------------------
 */
int OGLwinCmd(clientData, interp, argc, argv)
    ClientData clientData;	/* Main window associated with
				             * interpreter. */
    Tcl_Interp *interp;		/* Current interpreter. */
    int argc;			    /* Number of arguments. */
    char **argv;		    /* Argument strings. */
{
    Tk_Window main = (Tk_Window) clientData;
    OGLwin *glxwinPtr;
    Tk_Window tkwin;

#ifndef __WIN32__
    int i, j;
    Colormap cmap;
    XVisualInfo *vi;
    int configuration [50], *confPtr;
#endif

    if (argc < 2) {
    	Tcl_AppendResult(interp, "wrong # args:  should be \"",
    		argv[0], " pathName ?options?\"", (char *) NULL);
    	return TCL_ERROR;
    }

    tkwin = Tk_CreateWindowFromPath(interp, main, argv[1], (char *) NULL);
    if (tkwin == NULL) {
       ERRMSG ("Could not create window");
    }

    /*
     * Allocate and initialize the widget record.
     */
    glxwinPtr = (OGLwin *) ckalloc(sizeof(OGLwin));
    glxwinPtr->tkwin = tkwin;
    glxwinPtr->display = Tk_Display(tkwin);
    glxwinPtr->interp = interp;
    glxwinPtr->width  = 300;
    glxwinPtr->height = 300;
    glxwinPtr->absx = glxwinPtr->absy = 0;
   
    glxwinPtr->context = (char*) NULL;
    glxwinPtr->refresh = NULL;
	glxwinPtr->updatePending = 0;
    glxwinPtr->redrawList = -1;
    glxwinPtr->aspectRatio = 0.0;
    glxwinPtr->doubleBuffer = 1;
    glxwinPtr->depthSize = 16;
    glxwinPtr->alphaSize = 0;
    glxwinPtr->accumSize = 0;
    glxwinPtr->stencilSize = 0;
    glxwinPtr->bgBorder = NULL;
#if defined(__WIN32__) || defined(_WIN32)
    glxwinPtr->hwnd = 0;
    glxwinPtr->hdc = 0;
    glxwinPtr->hrc = 0;
    glxwinPtr->hPalette = 0;
#endif
    
    /* create the widget itself */
    Tk_CreateEventHandler(glxwinPtr->tkwin, ExposureMask|StructureNotifyMask, 
		OGLwinEventProc, (ClientData) glxwinPtr);

    Tcl_CreateCommand(interp, Tk_PathName(glxwinPtr->tkwin), OGLwinWidgetCmd,
		      (ClientData)glxwinPtr, (void (*)(int *))NULL);

    if (OGLwinConfigure(interp, glxwinPtr, argc-2, argv+2, 0) != TCL_OK) {
    	Tk_DestroyWindow(glxwinPtr->tkwin);
	    return TCL_ERROR;
    }

#ifdef __WIN32__
    if (WinMakeWindowExist (glxwinPtr) != TCL_OK) {
        Tcl_AppendResult(interp, "Could not make window exist", (char *) NULL);
    	return TCL_ERROR;
    }

#else
    /*
     * For OpenGL under X we may allocate the OpenGL X context and
     * visual here. First, we need to build a configuration 
     * array corresponding to the the widget options.
     */
    confPtr = configuration;
    *confPtr++ = GLX_RGBA;
    if (glxwinPtr->doubleBuffer) {
       *confPtr++ = GLX_DOUBLEBUFFER; 
    }
    if (glxwinPtr->depthSize) {
       *confPtr++ = GLX_DEPTH_SIZE;
       *confPtr++ = glxwinPtr->depthSize;
    }
    if (glxwinPtr->stencilSize) {
       *confPtr++ = GLX_STENCIL_SIZE;
       *confPtr++ = glxwinPtr->stencilSize;
    }
    if (glxwinPtr->accumSize) {
       *confPtr++ = GLX_ACCUM_RED_SIZE;
       *confPtr++ = glxwinPtr->accumSize;
       *confPtr++ = GLX_ACCUM_GREEN_SIZE;
       *confPtr++ = glxwinPtr->accumSize;
       *confPtr++ = GLX_ACCUM_BLUE_SIZE;
       *confPtr++ = glxwinPtr->accumSize;
       if (glxwinPtr->alphaSize) {
    	  *confPtr++ = GLX_ACCUM_ALPHA_SIZE;
	      *confPtr++ = glxwinPtr->accumSize;
       }
    }
    if (glxwinPtr->alphaSize) {
       *confPtr++ = GLX_ALPHA_SIZE;
       *confPtr++ = glxwinPtr->alphaSize;
    }

    /*** find an appropriate visual and a colormap for it ***/
    /* first, try to find a 24 bit visual */
    confPtr [0] = GLX_RED_SIZE; 
    confPtr [1] = 8;
    confPtr [2] = GLX_GREEN_SIZE; 
    confPtr [3] = 8;
    confPtr [4] = GLX_BLUE_SIZE; 
    confPtr [5] = 8;
    confPtr [6] = None;

    vi = glXChooseVisual(glxwinPtr->display, DefaultScreen(glxwinPtr->display), configuration);
    if (vi == NULL) {
       /* try an 8 bit visual */
       confPtr [0] = None;
       vi = glXChooseVisual(glxwinPtr->display, DefaultScreen(glxwinPtr->display), configuration);

       if (vi == NULL) {
	        Tk_DestroyWindow(glxwinPtr->tkwin);
	        ERRMSG ("no appropriate RGB visual");
       }
    }
    cmap = getColormap(glxwinPtr->display, vi);

    if (!Tk_SetWindowVisual (tkwin, vi->visual, vi->depth, cmap)) {
       Tk_DestroyWindow(glxwinPtr->tkwin);
       ERRMSG ("Could not set window visual");
    }

    Tk_SetWindowColormap (tkwin, cmap);

    if ((Tk_Parent(tkwin) != NULL) && (Tk_Colormap(tkwin) != Tk_Colormap (Tk_Parent(tkwin)))) {
		TkWmAddToColormapWindows(tkwin);
    } 

    /* See if this window will share display lists with another */
    if (glxwinPtr->context != NULL) {
        Tcl_CmdInfo info;
        if (!Tcl_GetCommandInfo (interp, glxwinPtr->context, &info) || info.proc != OGLwinWidgetCmd) {
	        Tcl_AppendResult (interp, "Not a gl window: ", glxwinPtr->context, (char*) NULL);	  
    	    Tk_DestroyWindow(glxwinPtr->tkwin);
	        return TCL_ERROR;
        }
        glxwinPtr->cx = glXCreateContext(Tk_Display(tkwin), vi, ((OGLwin *)(info.clientData))->cx,
			/* direct rendering */ GL_TRUE);
     }
     else {
        glxwinPtr->cx = glXCreateContext(Tk_Display(tkwin), vi, 
			/* no sharing of display lists */ NULL,
			/* direct rendering */ GL_TRUE);
     }

    if( glxwinPtr->cx == NULL ) {
        Tcl_AppendResult( interp, "could not create rendering context", (char*) NULL );	  
        Tk_DestroyWindow( glxwinPtr->tkwin );
        return TCL_ERROR;
    }

	
	Tk_MapWindow (tkwin);
    XSetWMColormapWindows( glxwinPtr->display, Tk_WindowId(tkwin), &(Tk_WindowId(tkwin)), 1 );

#endif
    MakeCurrent(glxwinPtr);
    OGLwinViewport (glxwinPtr);
    ARRANGE_REDRAW(glxwinPtr);
    GetAbsXY (glxwinPtr);
    
    interp->result = Tk_PathName(glxwinPtr->tkwin);

    return TCL_OK;
}

/*---------------------------------------------------------------------------
 *
 * OGLwinViewport
 * 
 *    Called when window is resized. If a non-zero aspect ratio was defined
 *    for that window, a corresponding viewport is set by centering it
 *    inside the window.
 *
 *---------------------------------------------------------------------------
 */
static void OGLwinViewport( OGLwin* oglWinPtr ) 
{
	if (oglWinPtr->aspectRatio == 0.0) {
		glViewport (0, 0, oglWinPtr->width, oglWinPtr->height);
	}
	
	else {
		
		if (oglWinPtr->width/oglWinPtr->aspectRatio >  oglWinPtr->height) {
			/* Should reduce the width of the viewport */
			int adjWidth = (int) (oglWinPtr->height * oglWinPtr->aspectRatio);
			glViewport ((oglWinPtr->width - adjWidth) / 2, 0, adjWidth, 
				oglWinPtr->height);
		}
		
		else {
			/* Should reduce the height of the viewport */
			int adjHeight = (int) (oglWinPtr->width/oglWinPtr->aspectRatio);
			glViewport (0, (oglWinPtr->height - adjHeight) / 2, oglWinPtr->width, adjHeight);
		}
	}
}

static void MakeCurrent( OGLwin* oglwinPtr ) 
{
   static OGLwin* previous = NULL;
   if (oglwinPtr == previous) return;
   if (oglwinPtr == NULL) return;
   previous = oglwinPtr;

#ifdef __WIN32__
    wglMakeCurrent (oglwinPtr->hdc, oglwinPtr->hrc);
#else
    glXMakeCurrent(oglwinPtr->display, Tk_WindowId(oglwinPtr->tkwin), 
		oglwinPtr->cx);
#endif
}


/*---------------------------------------------------------------------------
 *
 * OGLwinRedraw
 * 
 *    Sample redraw routine. Redraws
 *    display list redrawList if defined
 *
 *---------------------------------------------------------------------------
 */
static void OGLwinRedraw( ClientData clientData )
{
   OGLwin *glxwinPtr = (OGLwin *) clientData;
   Tk_Window tkwin = glxwinPtr->tkwin;

   glxwinPtr->updatePending = 0;

   if (tkwin == NULL || !Tk_IsMapped(tkwin)) {
      return;
   }

#ifdef __WIN32__
    assert (glxwinPtr->hwnd != 0);
    assert (glxwinPtr->hdc != 0);

    MakeCurrent (glxwinPtr);
	
	if(glxwinPtr->refresh != NULL){
		Tcl_Eval(glxwinPtr->interp,glxwinPtr->refresh);
	}

    if (glxwinPtr->doubleBuffer) {
        SwapBuffers (glxwinPtr->hdc);
    }
    else {
        glFlush ();
    }

#else
   MakeCurrent( glxwinPtr );
   
   if (glxwinPtr->redrawList != -1) {
      glCallList( glxwinPtr->redrawList );
   }

   if (glxwinPtr->doubleBuffer) {
      glXSwapBuffers( Tk_Display (glxwinPtr->tkwin),    
		     Tk_WindowId(glxwinPtr->tkwin) );	
      /* buffer swap does implicit glFlush */
   }
   else {
      glFlush();		
      /* explicit flush for single buffered case */
   }
#endif
}

/*---------------------------------------------------------------------------
 *
 * Management of Extensions to the TKOGL widget
 *
 *---------------------------------------------------------------------------*/
static struct TkOGLExtStruct {
   char* name;
   TkOGLExtProc* proc;
   struct TkOGLExtStruct* next;
} *TkOGLExtTable = NULL;

EXPORT(int,RegisterTkOGLExtension) (interp, extname, extproc)
     Tcl_Interp* interp;
     char* extname;
     TkOGLExtProc* extproc;
{
   struct TkOGLExtStruct* ptr;
   ptr = (struct TkOGLExtStruct*) malloc (sizeof (struct TkOGLExtStruct));
   assert (ptr != NULL);
   ptr->name = (char*) malloc (strlen (extname)+1);
   assert (ptr->name != NULL);
   strcpy (ptr->name, extname);
   ptr->proc = extproc;
   ptr->next = TkOGLExtTable;
   TkOGLExtTable = ptr;
   return TCL_OK;
}

/*
 *--------------------------------------------------------------
 *
 * OGLwinWidgetCmd --
 *
 *	This procedure is invoked to process the Tcl command
 *	that corresponds to a widget managed by this module.
 *	See the user documentation for details on what it does.
 *
 * Results:
 *	A standard Tcl result.
 *
 * Side effects:
 *	See the user documentation.
 *
 *--------------------------------------------------------------
 */
static int OGLwinWidgetCmd( clientData, interp, argc, argv )
    ClientData clientData;		/* Information about glxwin widget. */
    Tcl_Interp *interp;			/* Current interpreter. */
    int argc;				/* Number of arguments. */
    char **argv;			/* Argument strings. */
{
   OGLwin *glxwinPtr = (OGLwin *)clientData;
   Tk_Window tkwin = glxwinPtr->tkwin;
   int result = TCL_OK;
   int length;

#ifdef __WIN32__
#   define MAPWINDOW {\
        if (!Tk_IsMapped (tkwin)) Tk_MapWindow (tkwin); \
        assert (glxwinPtr->hdc != 0);\
        MakeCurrent (glxwinPtr);\
    }
#else
#   define MAPWINDOW { \
       if (!Tk_IsMapped (tkwin)) Tk_MapWindow (tkwin); \
       MakeCurrent(glxwinPtr);\
    }	       
#endif

   if (argc < 2) {
      Tcl_AppendResult(interp, "wrong # args: should be \"",
		       argv[0], " option ?arg arg ...?\"", (char *) NULL);
      return TCL_ERROR;
   }
   Tcl_Preserve( (ClientData)glxwinPtr );
   length = strlen(argv[1]);

   if( strncmp(argv[1], "configure", length ) == 0) {
        /* Standard "configure" tk command */
        if (argc == 2) {
			result = Tk_ConfigureInfo(interp, glxwinPtr->tkwin, 
		        configSpecs, (char *) glxwinPtr, (char *) NULL, 0);
        } 
        else if (argc == 3) {
			result = Tk_ConfigureInfo(interp, glxwinPtr->tkwin, 
				configSpecs, (char *) glxwinPtr, argv[2], 0);
        } 
        else {
			result = OGLwinConfigure(interp, glxwinPtr, argc-2, argv+2,
				TK_CONFIG_ARGV_ONLY);
      }
   }
   
   else if( strncmp(argv[1], "cget", length) == 0 ) {
       /* Standard "cget" tk command */
       if (argc != 3) {
			Tcl_AppendResult(interp, "wrong # args: should be \"",
				argv[0], " cget option\"",(char *) NULL);
			goto error;
       }
       result = Tk_ConfigureValue(interp, glxwinPtr->tkwin, configSpecs,
			(char *)glxwinPtr, argv[2], 0);
   }

   else if( strncmp( argv[1], "project", length ) == 0 || 
		     strncmp( argv[1], "unproject", length ) == 0 ) {
		/* Implementation of gluProject (gluUnProject) utility function */
		/* Given 3 world(window) coordinates, returns the corresponding */
		/* 3 window(world) coordinates */
		GLdouble x, y, z, modelmatrix[16], projectmatrix[16];
		GLint viewport [4], retval;
		if( argc != 5 ) {		
    		Tcl_AppendResult( interp, argv [1], ": x y z coordinates expected", (char*)NULL );
			goto error;
		}
		if( Tcl_GetDouble( interp, argv [2], &x ) != TCL_OK ||
	        Tcl_GetDouble( interp, argv [3], &y ) != TCL_OK ||
	        Tcl_GetDouble( interp, argv [4], &z ) != TCL_OK ) goto error;
      
		MAPWINDOW;
		glGetDoublev( GL_MODELVIEW_MATRIX, modelmatrix );
		glGetDoublev( GL_PROJECTION_MATRIX, projectmatrix );
		glGetIntegerv( GL_VIEWPORT, viewport );
      
		if( strncmp( argv[1], "project", length ) == 0 ) {
    		retval = gluProject( x, y, z, modelmatrix, projectmatrix, viewport, &x, &y, &z );
			y = viewport[3] - y;
		} else {
    		y = viewport[3] - y;
			retval = gluUnProject( x, y, z, modelmatrix, projectmatrix, viewport, &x, &y, &z );	 
		}
		
		if (retval) {
    		sprintf( interp->result, "%f %f %f", x, y, z );
		}
   } 
   
	else if( strncmp(argv[1], "createFont", length) == 0 ) {
		/* Create a font */
		result = tkoglCreateFont( glxwinPtr, interp, argc, argv );
	}

   else if( strncmp(argv[1], "redraw", length) == 0 ) {
      /* perform the redraw */
      ARRANGE_REDRAW(glxwinPtr);
   }
   
   else if( strncmp(argv[1], "swapBuffer", length) == 0 ) {
      /* perform the swapbuffer */
      SwapBuffers(glxwinPtr->hdc);
   }

   else if( strncmp(argv[1], "makeCurrent", length) == 0 ) {
      /* make this the current drawing window */
     MAPWINDOW;
   }

   else {
      /* handle extensions */
      struct TkOGLExtStruct *extPtr = TkOGLExtTable;
      while (extPtr != NULL) {
	      if (strncmp(argv[1], extPtr->name, length) == 0) {
	            MAPWINDOW;
	            result = (*extPtr->proc) (interp, argc, argv);
	            break;
	      }
	      extPtr = extPtr->next;
      }
      if (extPtr == NULL) {
	        Tcl_AppendResult( interp, "bad command: ", argv[1], (char*) NULL );
	        goto error;
      }
   }

   Tcl_Release( (ClientData)glxwinPtr );
   return result;

   error:
   Tcl_Release( (ClientData)glxwinPtr );
   return TCL_ERROR;
}

/* ----------------------------------------
 * texImage2D: Sets up a 2D texture Image.
 * Added by Joel Joyner, November 22, 2000
 * ---------------------------------------- */
int texImage2D( Tcl_Interp *interp, int argc, char* argv[] )
{
	int level, border, i, n;
	char* imagename;
   
	Tk_PhotoHandle handle;
	Tk_PhotoImageBlock block;
   
	if( argc != 5 ) {
		Tcl_AppendResult( interp, "Wrong number of arguments.", (char*)NULL );
		return TCL_ERROR;
	}

	if( Tcl_GetInt( interp, argv[2], &level ) != TCL_OK ) {
		Tcl_AppendResult( interp, "Invalid level paramter.", (char *)NULL );
		return TCL_ERROR;
	}

	if( Tcl_GetInt( interp, argv[3], &border ) != TCL_OK ) {
		Tcl_AppendResult( interp, "Invalid border paramter.", (char *)NULL );
		return TCL_ERROR;
	}

	imagename = (char *)argv[4];
	handle = Tk_FindPhoto( interp, imagename );
   
	if( handle == NULL || Tk_PhotoGetImage( handle, &block ) != 1 ) {
		Tcl_AppendResult( interp, "Error loading image.", (char*)NULL );
		return TCL_ERROR;
	}

	if( block.pixelSize != 3 && block.pixelSize != 4 ) {
		Tcl_AppendResult( interp, "Image has invalid pixel size.", (char*)NULL );
		return TCL_ERROR;
	}

	n = block.width - border;
	for (i = 0; i < 16; i++) {
		if (n == (1 << i)) break;
	}
	if (i == 16) {
		Tcl_AppendResult( interp, "Image width must be a power of 2.", (char*)NULL );
		return TCL_ERROR;
	}
      
	n = block.height - border;
	for (i = 0; i < 16; i++) {
		if (n == (1 << i)) break;
	}
	if (i == 16) {
		Tcl_AppendResult( interp, "Image height must be a power of 2.", (char*)NULL );
		return TCL_ERROR;
	}

	glPixelStorei (GL_UNPACK_ALIGNMENT, 1);
	glTexImage2D (GL_TEXTURE_2D, level, 
		 block.pixelSize, 
		 block.width, block.height, border,
		 block.pixelSize == 3 ? GL_RGB : GL_RGBA,
		 GL_UNSIGNED_BYTE, block.pixelPtr);

	return TCL_OK;

}

/*
 *----------------------------------------------------------------------
 *
 * OGLwinConfigure --
 *
 *	This procedure is called to process an argv/argc list in
 *	conjunction with the Tk option database to configure (or
 *	reconfigure) a glxwin widget.
 *
 * Results:
 *	The return value is a standard Tcl result.  If TCL_ERROR is
 *	returned, then interp->result contains an error message.
 *
 * Side effects:
 *	Configuration information, such as colors, border width,
 *	etc. get set for glxwinPtr;  old resources get freed,
 *	if there were any.
 *
 *----------------------------------------------------------------------
 */
static int OGLwinConfigure( interp, glxwinPtr, argc, argv, flags )
    Tcl_Interp *interp;			/* Used for error reporting. */
    OGLwin *glxwinPtr;			/* Information about widget. */
    int argc;				/* Number of valid entries in argv. */
    char **argv;			/* Arguments. */
    int flags;				/* Flags to pass to
					 * Tk_ConfigureWidget. */
{
    if (Tk_ConfigureWidget(interp, glxwinPtr->tkwin, configSpecs,
	    argc, argv, (char *) glxwinPtr, flags) != TCL_OK) {
	return TCL_ERROR;
    }

    /*
     * Register the desired geometry for the window.  Then arrange for
     * the window to be redisplayed.
     */
    if ((glxwinPtr->width > 0) || (glxwinPtr->height > 0)) {
       Tk_GeometryRequest(glxwinPtr->tkwin, glxwinPtr->width,
			  glxwinPtr->height);
    }
    return TCL_OK;
}

/*
 *--------------------------------------------------------------
 *
 * OGLwinEventProc --
 *
 *	This procedure is invoked by the Tk dispatcher for various
 *	events on glxwins.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	When the window gets deleted, internal structures get
 *	cleaned up.  When it gets exposed, it is redisplayed.
 *
 *--------------------------------------------------------------
 */
static void OGLwinEventProc(clientData, eventPtr)
    ClientData clientData;	/* Information about window. */
    XEvent *eventPtr;		/* Information about event. */
{
    OGLwin *glxwinPtr = (OGLwin *) clientData;
    
#ifdef __WIN32__

    assert (glxwinPtr->hwnd != 0);

    if (eventPtr->type == Expose) {
        if (GetAbsXY (glxwinPtr)) {
           /* This Kludge is necessary since Windows Tk does not pass move events
            * to child windows and hence our OpenGL window might be redrawn
            * in its old position
            */
           Tk_ResizeWindow (glxwinPtr->tkwin, glxwinPtr->width, glxwinPtr->height);
        } 
        ARRANGE_REDRAW(glxwinPtr);
    } 
    else if (eventPtr->type == ConfigureNotify) {
        GLsizei glnWidth = eventPtr->xconfigure.width; 
        GLsizei glnHeight = eventPtr->xconfigure.height; 
        glxwinPtr->width = glnWidth;
        glxwinPtr->height = glnHeight;
        OGLwinViewport (glxwinPtr);
    } 
#else
    if (eventPtr->type == Expose) {
       ARRANGE_REDRAW(glxwinPtr);
    } 
    else if (eventPtr->type == ConfigureNotify) {
       glxwinPtr->width = eventPtr->xconfigure.width;
       glxwinPtr->height = eventPtr->xconfigure.height;
       OGLwinViewport (glxwinPtr);
       ARRANGE_REDRAW(glxwinPtr);
    }
#endif WIN32
    else if (eventPtr->type == DestroyNotify) {
    	Tcl_DeleteCommand(glxwinPtr->interp, Tk_PathName(glxwinPtr->tkwin));
	    glxwinPtr->tkwin = NULL;
	    if (glxwinPtr->updatePending) {
    	   Tcl_CancelIdleCall(OGLwinRedraw, (ClientData) glxwinPtr);
        }
	    Tcl_EventuallyFree((ClientData) glxwinPtr, OGLwinDestroy);
    }

}


/*
 *----------------------------------------------------------------------
 *
 * OGLwinDestroy --
 *
 *	This procedure is invoked by Tcl_EventuallyFree or Tcl_Release
 *	to clean up the internal structure of a glxwin at a safe time
 *	(when no-one is using it anymore).
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Everything associated with the glxwin is freed up.
 *
 *----------------------------------------------------------------------
 */

static void OGLwinDestroy(char* clientData)
{
    OGLwin *glxwinPtr = (OGLwin *) clientData;

#if defined(__WIN32__) || defined(_WIN32)
    if (glxwinPtr->hrc != 0) {
        wglMakeCurrent (NULL, NULL);
        wglDeleteContext (glxwinPtr->hrc);
        MakeCurrent (NULL);
        ReleaseDC (glxwinPtr->hwnd, glxwinPtr->hdc);
        if (glxwinPtr->hPalette != 0) {
            DeleteObject (glxwinPtr->hPalette);
        }
    }
#endif
    Tk_FreeOptions(configSpecs, (char *) glxwinPtr, glxwinPtr->display, 0);
    ckfree((char *) glxwinPtr);
}

#ifdef __WIN32__

/*
 *----------------------------------------------------------------------
 *
 *  SetDCPixelFormat sets the pixel format for a device context in
 *  preparation for creating a rendering context.
 *
 *  Input parameters:
 *    hdc = Device context handle
 *
 *  Returns:
 *    Nothing
 *
 *----------------------------------------------------------------------
 */
static void SetDCPixelFormat( OGLwin* glxwinPtr )
{
    HANDLE hHeap;
    int nColors, i;
    LPLOGPALETTE lpPalette;
    HDC hdc = glxwinPtr->hdc;

    BYTE byRedMask, byGreenMask, byBlueMask;

    PIXELFORMATDESCRIPTOR pfd = {
        sizeof (PIXELFORMATDESCRIPTOR),             // Size of this structure
        1,                                          // Version number
        PFD_DRAW_TO_WINDOW |                        // Flags
        PFD_SUPPORT_OPENGL,
        PFD_TYPE_RGBA,                              // RGBA pixel values
        24,                                         // 24-bit color
        0, 0, 0, 0, 0, 0,                           // Don't care about these
        0, 0,                                       // No alpha buffer
        0, 0, 0, 0, 0,                              // No accumulation buffer
        32,                                         // 32-bit depth buffer
        0,                                          // No stencil buffer
        0,                                          // No auxiliary buffers
        PFD_MAIN_PLANE,                             // Layer type
        0,                                          // Reserved (must be 0)
        0, 0, 0                                     // No layer masks
    };

    int nPixelFormat = 0;
    XVisualInfo VisInf;
    XVisualInfo *visinfo = &VisInf;
    TkWinColormap *cmap = (TkWinColormap *) ckalloc(sizeof(TkWinColormap));
    
    /* Just for portability, define the simplest visinfo */
    visinfo->visual = DefaultVisual(glxwinPtr->display, 
                                    DefaultScreen(glxwinPtr->display));
    visinfo->depth = visinfo->visual->bits_per_rgb;

    /* Set pfd fields according to the capabilities needed */
    if (glxwinPtr->doubleBuffer) {
       pfd.dwFlags |= PFD_DOUBLEBUFFER; 
    }
    else {
       pfd.dwFlags &= ~PFD_DOUBLEBUFFER;
    }

    pfd.cDepthBits = glxwinPtr->depthSize;
    pfd.cStencilBits = glxwinPtr->stencilSize;
    pfd.cAccumBits = glxwinPtr->accumSize;
    pfd.cAlphaBits = glxwinPtr->alphaSize;

    nPixelFormat = ChoosePixelFormat (hdc, &pfd);
    SetPixelFormat (hdc, nPixelFormat, &pfd);
    DescribePixelFormat (hdc, nPixelFormat, sizeof (PIXELFORMATDESCRIPTOR),
                         &pfd);

    glxwinPtr->depthSize = pfd.cDepthBits;
    glxwinPtr->stencilSize = pfd.cStencilBits;
    glxwinPtr->accumSize = pfd.cAccumBits;
    glxwinPtr->alphaSize = pfd.cAlphaBits;
    glxwinPtr->doubleBuffer = (pfd.dwFlags & PFD_DOUBLEBUFFER) != 0;

    if (pfd.dwFlags & PFD_NEED_PALETTE) {
        nColors = 1 << pfd.cColorBits;
        hHeap = GetProcessHeap ();

        (LPLOGPALETTE) lpPalette = HeapAlloc (hHeap, 0,
            sizeof (LOGPALETTE) + (nColors * sizeof (PALETTEENTRY)));
            
        lpPalette->palVersion = 0x300;
        lpPalette->palNumEntries = nColors;

        byRedMask = (1 << pfd.cRedBits) - 1;
        byGreenMask = (1 << pfd.cGreenBits) - 1;
        byBlueMask = (1 << pfd.cBlueBits) - 1;

        for (i=0; i<nColors; i++) {
            lpPalette->palPalEntry[i].peRed =
                (((i >> pfd.cRedShift) & byRedMask) * 255) / byRedMask;
            lpPalette->palPalEntry[i].peGreen =
                (((i >> pfd.cGreenShift) & byGreenMask) * 255) / byGreenMask;
            lpPalette->palPalEntry[i].peBlue =
                (((i >> pfd.cBlueShift) & byBlueMask) * 255) / byBlueMask;
            lpPalette->palPalEntry[i].peFlags = 0;
        }

        glxwinPtr->hPalette = CreatePalette (lpPalette);
        HeapFree (hHeap, 0, lpPalette);

        if (glxwinPtr->hPalette != NULL) {
            SelectPalette (hdc, glxwinPtr->hPalette, FALSE);
            RealizePalette (hdc);
        }
        
        cmap->palette = glxwinPtr->hPalette;
        cmap->size = nColors;
        cmap->stale = 0;
        
        /* Since this is a private colormap of a fix size, we do not need
           a valid hash table, but a dummy one */
        Tcl_InitHashTable(&cmap->refCounts, TCL_ONE_WORD_KEYS);
    }
    else {
        cmap = (TkWinColormap *) DefaultColormap(glxwinPtr->display, 
                               DefaultScreen(glxwinPtr->display));
    }
    
    /* Now, allocate the OpenGL context */
    glxwinPtr->hrc = wglCreateContext (glxwinPtr->hdc);    

    /* See if this window will share display lists with another */
    if (glxwinPtr->context != NULL) {
        Tcl_CmdInfo info;
        if (!Tcl_GetCommandInfo (glxwinPtr->interp, glxwinPtr->context, 
            &info) || info.proc != OGLwinWidgetCmd) {
            Tcl_ResetResult (glxwinPtr->interp);
            Tcl_AppendResult (glxwinPtr->interp, 
                "Not a gl window: ", glxwinPtr->context, (char*) NULL);	  
	        Tcl_BackgroundError (glxwinPtr->interp);
        }
        else if (!wglShareLists(((OGLwin *)(info.clientData))->hrc, 
                  glxwinPtr->hrc)) {
            Tcl_ResetResult (glxwinPtr->interp);
            Tcl_AppendResult (glxwinPtr->interp, 
                "Cannot share lists with: ", glxwinPtr->context, (char*) NULL);	  
	        Tcl_BackgroundError (glxwinPtr->interp);            
        }
    }
    /* Make sure Tk knows how to switch palettes */
    Tk_SetWindowVisual (glxwinPtr->tkwin, visinfo->visual, 
                        visinfo->depth, (Colormap) cmap);
}
#else

/*---------------------------------------------------------------------------
 *
 * The following function allocates an X colormap appropriate for using
 * with OpenGL. This was taken from the example program 'glxdino' by
 * Mark Kilgard
 */
Colormap getColormap( Display *dpy, XVisualInfo * vi )
{
    Status          status;
    XStandardColormap *standardCmaps;
    Colormap        cmap;
    int             i, numCmaps;

    /* if no standard colormap but TrueColor, just make an unshared one */
    status = XmuLookupStandardColormap(dpy, vi->screen, vi->visualid,
        vi->depth, XA_RGB_DEFAULT_MAP, /* replace */ False, /* retain */ True);
    if (status == 1 && (vi->class == DirectColor || vi->depth == 8)) {
	status = XGetRGBColormaps(dpy, RootWindow(dpy, vi->screen),
			     &standardCmaps, &numCmaps, XA_RGB_DEFAULT_MAP);
	if (status == 1)
	    for (i = 0; i < numCmaps; i++)
		if (standardCmaps[i].visualid == vi->visualid) {
		    cmap = standardCmaps[i].colormap;
		    XFree(standardCmaps);
		    return cmap;
		}
    }
    cmap = XCreateColormap(dpy, RootWindow(dpy, vi->screen),
        vi->visual, AllocNone);
    return cmap;
}

#endif



