/*
 * Copyright (C) 1997-2009, R3vis Corporation.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
 * USA, or visit http://www.gnu.org/copyleft/lgpl.html.
 *
 * Original Contributor:
 *   Wes Bethel, R3vis Corporation, Marin County, California
 *   http://www.r3vis.com/
 * Additional Contributor(s):
 *
 * The OpenRM project is located at http://openrm.sourceforge.net/.
 */
/*
 * $Id: rmbfuncs.c,v 1.23 2008/11/24 16:55:43 wes Exp $
 * Version: $Name: v180-alpha-02 $
 * $Revision: 1.23 $
 * $Log: rmbfuncs.c,v $
 * Revision 1.23  2008/11/24 16:55:43  wes
 * Check return status from render func callback in X11 rmauxEventLoop
 *
 * Revision 1.22  2008/11/23 15:17:06  wes
 * For all user callbacks, the pointer to the user data is a void * so
 * that the application may modify its own data
 *
 * Revision 1.21  2008/09/22 01:49:44  wes
 * Add usrData pointer to idle and render callbacks
 *
 * Revision 1.20  2007/07/22 17:36:48  wes
 * Fix typos in code doc
 *
 * Revision 1.19  2006/10/15 15:49:27  wes
 * Updated win32 event loop code to use new format of parm list for
 * callbacks (button up/down/motion, keypress).
 *
 * Revision 1.18  2006/08/13 16:56:15  wes
 * Use typedefs for callback function prototypes. Misc code cleanups.
 *
 * Revision 1.17  2006/08/08 03:49:07  wes
 * Ergonomic changes to rmaux callbacks.
 *
 * Revision 1.16  2006/08/07 12:49:05  wes
 * Added new parms to callback functions: RMnode and usrData. Added new
 * infrastructure to manage user data.
 *
 * Revision 1.15  2006/08/02 05:01:24  wes
 * Added RMnode * to parm list for app keyfunc callback routine.
 *
 * Revision 1.14  2005/09/12 04:05:34  wes
 * Minor documentation updates.
 *
 * Revision 1.13  2005/06/08 18:33:02  wes
 * Code cleanups to eliminate compiler warnings.
 *
 * Revision 1.12  2005/04/27 03:36:00  wes
 * Attempting to reduce idle timeout in Win32
 *
 * Revision 1.11  2005/02/19 16:08:45  wes
 * Distro sync and consolidation.
 *
 * Revision 1.10  2005/01/23 17:11:49  wes
 * Copyright update to 2005.
 *
 * Revision 1.9  2004/03/30 14:13:37  wes
 * *** empty log message ***
 *
 * Revision 1.8  2004/03/18 15:51:13  wes
 * Minor changes to switch statements to avoid compile warning on SGI
 * about "statement not reached" when there is a return inside a case.
 *
 * Revision 1.7  2004/01/19 17:10:24  wes
 * Minor docs changes.
 *
 * Revision 1.6  2004/01/17 04:08:17  wes
 * Updated copyright line for 2004. Tuning of auto-spin code.
 *
 * Revision 1.5  2003/12/12 00:36:45  wes
 * Changed timeout parameter on the Win32 routine SetTime from 10 to 1. THe
 * intent is to reduce the idle timer from 10ms to 1ms. After the change,
 * I can't tell any difference, but it didn't seem to break anything. We need
 * to test on a CPU with a higher clock rate to see if there's any difference
 * in performance. This change affects the frequency that the user's
 * idle callback rmaux function will be invoked.
 *
 * Revision 1.4  2003/10/03 19:18:01  wes
 * Add support for auto-spin in both Win32 and X11.
 *
 * Revision 1.3  2003/02/18 15:41:37  wes
 * Added a bit of documentation to rmauxSetKeyFunc.
 *
 * Revision 1.2  2003/02/02 02:07:18  wes
 * Updated copyright to 2003.
 *
 * Revision 1.1.1.1  2003/01/28 02:15:23  wes
 * Manual rebuild of rm150 repository.
 *
 * Revision 1.14  2003/01/16 22:21:18  wes
 * Updated all source files to reflect new organization of header files:
 * all header files formerly located in include/rmaux, include/rmi, include/rmv
 * are now located in include/rm.
 *
 * Revision 1.13  2002/06/17 01:15:18  wes
 * Replaced use of rmSubtreeFrame with rmFrame for default rendering
 * operations. Added conditionals to check for NULL callbacks
 * on event handling calls (button push, motion and release events).
 *
 * Revision 1.12  2002/04/30 19:37:59  wes
 * Added code to Win32 event handler to invoke a caller-supplied
 * key func callback.
 *
 * Revision 1.11  2001/10/14 23:52:42  wes
 * Added button4 and button5 due to crashes reported by some users with
 * mice with scroll wheels, and other funny mice with lots of buttons. Note
 * that buttons 4 and 5 are accessible only under X11; Win32 provides for
 * only 3 buttons in their API.
 *
 * Added a bit of code to the Win32 event processing code to more cleanly
 * delimit those events consumed by the RMaux event loop and those to be
 * consumed by Windows.
 *
 * Revision 1.10  2001/07/15 17:20:57  wes
 * Added an RMnode * parameter to the rmauxInitFunc.
 *
 * Revision 1.9  2001/06/03 20:53:47  wes
 * Added new routines for establishing callback handlers for resize
 * and key handler events.
 *
 * Revision 1.8  2001/03/31 17:09:31  wes
 * v1.4.0-alpha2 checkin.
 *
 * Revision 1.7  2000/12/03 22:36:15  wes
 * First steps towards thread safety.
 *
 * Revision 1.6  2000/10/03 11:38:26  wes
 * In rmauxEventLoop() [X11], upon entry and before entering
 * the main event loop, we first remove any KeyRelease events
 * from the event queue. There was a problem that first appeared
 * in XF864.0.1 in which spurious KeyRelease events would sometimes
 * be present in the event queue for the OpenGL gfx window.
 *
 * Revision 1.5  2000/08/28 01:31:40  wes
 * Minor tweaks to key latching stuff.
 *
 * Revision 1.3  2000/04/20 16:22:16  wes
 * JDB modifications: additional documentation, code rearranging.
 *
 * Revision 1.2  2000/04/17 00:06:51  wes
 * Numerous documentation updates and code reorganization courtesy of jdb.
 *
 * Revision 1.1.1.1  2000/02/28 21:29:40  wes
 * OpenRM 1.2 Checkin
 *
 * Revision 1.1.1.1  2000/02/28 17:18:48  wes
 * Initial entry - pre-RM120 release, source base for OpenRM 1.2.
 *
 */

/*
 * current issues 8/1999
 * 1. rmauxEventLoop under Win32 does not exit upon a non-modifier
 *    keypress.
 */

/*
 * MODIFICATIONS:
 *
 * 1. added simple user key callback 
 * 2. added user window resize callback
 * 3. added view plane translation to UI
 * 4. replaced isometric scaling with camera dolly in UI
 * 5. replaced trackball with ArcBall implementation in UI
 * 6. substituted colored X11 cursors for emphasizing operations in UI 
 * 7. cleaned up some variable and function names, slimmed rmaux state, added docs
 * 8. made button function arrays `static extern' to make them private
 *
 * 08/16/2000 jdb
 *
 * added key and resize default callbacks and API calls for setting rmaux state
 * with users functions for these callbacks
 *
 * 02/16/01 jdb
 */

#include <rm/rm.h>
#include <rm/rmaux.h>
#include "../rm/rmprivat.h"

#ifdef RM_X
#include <X11/cursorfont.h>
#endif

/* PRIVATE declarations */
int  rmauxInvokeRenderFunc (RMpipe *p, RMnode *n);
int  rmauxB1DownFunc RMAUX_BUTTON_CALLBACK_FUNCTION_PARMS();
int  rmauxB1UpFunc RMAUX_BUTTON_CALLBACK_FUNCTION_PARMS();
int  rmauxB1MotionFunc RMAUX_BUTTON_CALLBACK_FUNCTION_PARMS();
int  rmauxB2DownFunc RMAUX_BUTTON_CALLBACK_FUNCTION_PARMS();
int  rmauxB2UpFunc RMAUX_BUTTON_CALLBACK_FUNCTION_PARMS();
int  rmauxB2MotionFunc RMAUX_BUTTON_CALLBACK_FUNCTION_PARMS();
int  rmauxB3DownFunc RMAUX_BUTTON_CALLBACK_FUNCTION_PARMS();
int  rmauxB3UpFunc RMAUX_BUTTON_CALLBACK_FUNCTION_PARMS();
int  rmauxB3MotionFunc RMAUX_BUTTON_CALLBACK_FUNCTION_PARMS();
int  rmauxShiftB2DownFunc RMAUX_BUTTON_CALLBACK_FUNCTION_PARMS();
int  rmauxShiftB2UpFunc RMAUX_BUTTON_CALLBACK_FUNCTION_PARMS();
int  rmauxShiftB2MotionFunc RMAUX_BUTTON_CALLBACK_FUNCTION_PARMS();

int  private_rmauxButtonNumToIndex (int xbutton_num);
int  private_rmauxModifierToIndex (int state);

RMnode *private_rmauxGetCurrentSceneGraph(void);
void private_rmauxSetCurrentSceneGraph(RMnode *n);

RMnode *private_rmauxGetCameraResizeNode(void);
void private_rmauxSetCameraResizeNode(RMnode *n);

/* viewport [0..(width-1)] X [0..(height-1)] --> NDC [-1,1] X [-1,1] */
#define pixeltovp(p,d) ((float)((p) - ((d) >> 1)) / (float)((d) >> 1))

#if 1
#define private_rmauxSpinThreshold(x1,y1,x2,y2,t) (((((x2)-(x1))*((x2)-(x1))+((y2)-(y1))*((y2)-(y1))) - (t)) > 0 ? 1 : -1)
#else
int
private_rmauxSpinThreshold(float x1,
			   float y1,
			   float x2,
			   float y2,
			   float t)
{
    
    float a = (x2-x1)*(x2-x1)+(y2-y1)*(y2-y1);
#if 1
    {
	char buf[256];
	sprintf(buf," p1/p2 = (%g,%g) (%g,%g) \n thresh = %g, v = %g \n", x1, y1, x2, y2, t, a);
	rmNotice(buf);
    }
#endif
    if ((a - t) > 0)
	return 1;
    else
	return -1;
}
#endif

/* 
 * PRIVATE.
 * 8/7/2006. 
 */
int
private_rmauxRMFrameWrapper(RMpipe *p,
			    RMnode *n,
			    void *usrData)
{
    rmFrame(p, n);
    return 1;
}


/* rmaux state - NOT thread-safe! */
static int    (*staticUserIdleCallbackFunc) (RMpipe *p, RMnode *n, void *, int, int) = NULL;
static void   (*staticUserInitCallbackFunc)(RMpipe *, RMnode *, void *) = NULL;
static int    (*staticUserKeypressCallbackFunc)(RMpipe *, RMnode *, void *, char, KeySym) = NULL;
static int    (*staticUserResizeFunc)(RMpipe *,RMnode *, int, int) = NULL;
static int    (*renderCallbackFunc)(RMpipe *, RMnode *, void *usrData) = private_rmauxRMFrameWrapper;

static float    static_pointerPrevX, static_pointerPrevY;       /* stored pointer location */
static RMnode  *camera3DTransformTarget;  /* UI points to target node for interaction */
static RMmatrix ui_pose;    /* UI stores initial target node pose for computation */
static RMnode  *geomTransformTarget; 
static RMmatrix saveScaleMatrix;
static float   xscale_delta, yscale_delta;
static RMnode *static_sceneGraphHandle=NULL;
static RMnode *static_resizeCameraNode=NULL;

static RMenum  spinModeEnabled=RM_FALSE;
static void    (*spinCallbackFunc)(void) = NULL;
static void 	private_rmauxDoSpinCallback(void);
static RMmatrix spinMatrix;
static float   lastBX1, lastBY1, lastBX2, lastBY2;
static float   staticSpinThreshold = RMAUX_DEFAULT_SPIN_THRESHOLD;

#ifdef RM_X
static Cursor   rotate_cursor, translate_cursor, dolly_cursor, scale_cursor;
#endif

/* button function callbacks and user data arrays */
static RMarray **rmauxButtonDownUsrDataArrays=NULL;
static RMarray **rmauxButtonUpUsrDataArrays=NULL;
static RMarray **rmauxButtonMotionUsrDataArrays=NULL;
static RMarray **rmauxIdleFuncUsrDataArray=NULL;
static RMarray **rmauxKeypressUsrDataArray=NULL;
static RMarray **rmauxInitFuncUsrDataArray=NULL;
static RMarray **rmauxRenderFuncUsrDataArray=NULL;

/* default button down functions [rmauxEventNoOp], modified by rmauxSetButtonDownFunc() */
static int rmauxEventNoOp RMAUX_BUTTON_CALLBACK_FUNCTION_PARMS(); 

static RMauxButtonCallbackFuncPrototype rmauxUserButtonDownFuncs[RM_NUM_BUTTON_MODIFIERS][RM_NUM_BUTTONS + 1] = 
{
    {rmauxEventNoOp, rmauxEventNoOp, rmauxEventNoOp, rmauxEventNoOp, rmauxEventNoOp, rmauxEventNoOp},
    {rmauxEventNoOp, rmauxEventNoOp, rmauxEventNoOp, rmauxEventNoOp, rmauxEventNoOp},
    {rmauxEventNoOp, rmauxEventNoOp, rmauxEventNoOp, rmauxEventNoOp, rmauxEventNoOp}
};


/* default button up functions [rmauxEventNoOp], modified by rmauxSetButtonUpFunc() */
static RMauxButtonCallbackFuncPrototype rmauxUserButtonUpFuncs[RM_NUM_BUTTON_MODIFIERS][RM_NUM_BUTTONS + 1] = 
{
    {rmauxEventNoOp, rmauxEventNoOp, rmauxEventNoOp, rmauxEventNoOp, rmauxEventNoOp, rmauxEventNoOp},
    {rmauxEventNoOp, rmauxEventNoOp, rmauxEventNoOp, rmauxEventNoOp, rmauxEventNoOp, rmauxEventNoOp},
    {rmauxEventNoOp, rmauxEventNoOp, rmauxEventNoOp, rmauxEventNoOp, rmauxEventNoOp, rmauxEventNoOp}
};


/* default button motion functions [rmauxEventNoOp], modified by rmauxSetButtonMotionFunc() */
static RMauxButtonCallbackFuncPrototype rmauxUserButtonMotionFuncs[RM_NUM_BUTTON_MODIFIERS][RM_NUM_BUTTONS + 1] =
{
    {rmauxEventNoOp, rmauxEventNoOp, rmauxEventNoOp, rmauxEventNoOp, rmauxEventNoOp, rmauxEventNoOp},
    {rmauxEventNoOp, rmauxEventNoOp, rmauxEventNoOp, rmauxEventNoOp, rmauxEventNoOp, rmauxEventNoOp},
    {rmauxEventNoOp, rmauxEventNoOp, rmauxEventNoOp, rmauxEventNoOp, rmauxEventNoOp, rmauxEventNoOp}
};

/* 
 * PRIVATE
 * this routine will manage assigning void *usrData's to a 2D array of
 * void *'s.
 */
static void
private_rmauxAssignUserData(RMarray ***d,
			    int maxArrays,
			    int maxArrayLength,
			    int whichArray,
			    int whichItem,
			    const void *usrData)
{
    /* 
     * The arrays that hold the void *usrData pointers are constructed/
     * accessed as follows:
     * 1. There are maxArrays arrays, one for each button.
     * 2. Each array is of length maxArrayLength.
     */

    RMarray **t;

    if (*d == NULL) 		/* need to perform initialization */
    {
	int i;
	/* create pointers to hold RM_NUM_BUTTONS RMarrays */
	t = (RMarray **)calloc(maxArrays, sizeof(RMarray *));

	/* then, for each of these RMarrays, initialize it. */
	for (i=0; i<maxArrays; i++)
	{
	    int j;
	    t[i] = rmArrayNew(maxArrayLength, maxArrayLength, sizeof(void *));
	    for (j=0; j<maxArrayLength; j++)
		rmArrayAdd(t[i], NULL);
	}

	/* stuff some null pointers in there */

	*d = t;
    }
    else
	t = *d;

    /* now stash the the usrData void * for later use */
    rmArraySet(t[whichArray], whichItem, &usrData);
}

/* 
 * PRIVATE
 * purpose: retrieve a previously stashed void *usrData for a given
 * button + modifier index.
 */
static void *
private_rmauxRetrieveUserData(RMarray **d,
			      int whichArray,
			      int whichItem)
{
    /* 
     * The arrays that hold the void *usrData pointers are constructed/
     * accessed as follows:
     * 1. There are RM_NUM_BUTTONS arrays, one for each button.
     * 2. Each array is of length RM_NUM_BUTTON_MODIFIERS.
     */
    void *dd;
    void *returnPointer;

    /* first, perform some sanity checks. The following two conditions
       could occur if the application somehow manages to invoke the
       callback w/o first assigning usrData. */
    if (d == NULL) 
	return NULL;

    if (d[whichArray] == NULL)
	return NULL;

    /* obtain and return the data */
/*    return rmArrayGet(d[whichArray], whichItem); */
    dd = rmArrayGet(d[whichArray], whichItem); 

    if (dd == NULL) 	/* shouldn't ever happen */
	return NULL;

    memcpy((void *)&returnPointer, dd, sizeof(void *));
    return returnPointer;
}

/*
 * ----------------------------------------------------
 * @Name rmauxEventLoop
 @pstart
 void rmauxEventLoop (RMpipe *currentPipe,
                      RMnode *subTreeToDraw,
                      void *vmsg)
 @pend

 @astart 
 RMpipe *currentPipe - (input) a handle to an RMpipe object. The
    RMpipe must be fully configured (init'ed, and has a valid opened
    window assigned, and been the victim of rmPipeMakeCurrent()) prior
    to invoking the event handler.
    
 RMnode *subTreeToDraw - (input) a handle to an RMnode. This node is
    considered to be the root of a subtree that will be rendered onto
    "currentPipe" whenever a frame is to be rendered. 
 
 void *vmsg - X11 users - specify NULL for this parameter.  Win32
    users: all WinMain procedures must return (msg.wParam) to Windows
    so it can gracefully exit. Pass a handle to a MSG object in to
    rmauxEventLoop (please refer to the demo programs for sample
    code. (output, return)
 @aend

 @dstart

 This is the main rmaux event handler loop for both Win32 and X11.  In
 the source code, there are actually two different routines that are
 conditionally compiled, since Win32 and X11 are so different.
 However, from the outside, they exhibit (mostly) similar behavior.

 Events are processed until some action handler returns a zero
 status.  The RM demonstration programs install a key handler function
 (rmauxSetKeyPressCallbackFunc(pipe, NULL, rmauxDefaultKeyFunc)) that
 will cause applications to terminate when the "q" key is pressed.

 The default rendering function will be invoked an Expose (X11) or WM_PAINT (Win32)
 event is encountered. Additionally, rmauxUI() defines that frames will
 be rendered when other types of input events are encountered. The
 RMnode * parameter subTreeToDraw defines the root of the scene graph
 that will be rendered during all frame renderings within the rmaux*
 family of routines. Note the distinction between the scene graph root
 (subTreeToDraw) and the RMnode handle used for interactive transformations,
 which is set with rmauxUI(). The RMnode handles to each of these routines
 may point to different RMnodes.
 
 @dend
 * ----------------------------------------------------
 */
#ifdef RM_WIN /* W32 event loop */

static RMpipe *staticPipe=NULL; /* hack! tmp! wes */

void
rmauxEventLoop (RMpipe *win_current_pipe,
		RMnode *subTreeToDraw,
	        void *vmsg)
{
    MSG *msg; 

    private_rmauxSetCurrentSceneGraph(subTreeToDraw);
    
    msg = (MSG *)vmsg; 
    ShowWindow(win_current_pipe->hwnd, 1);

/*    staticPipe = win_current_pipe; */

    if (staticUserInitCallbackFunc != NULL)
    {
        (*staticUserInitCallbackFunc)(win_current_pipe, subTreeToDraw, private_rmauxRetrieveUserData(rmauxInitFuncUsrDataArray, 0, 0));
	staticUserInitCallbackFunc = NULL;
    }
    UpdateWindow(win_current_pipe->hwnd);
	 
    /* message structure, handle of window receiving the message, 
     * lowest message id to examine, highest message id to examine
     */
    while (GetMessage(msg, NULL, 0, 0))         
    {
        TranslateMessage(msg); /* translates messages */
        DispatchMessage(msg);  /* dispatches messages */
    }
}


void private_rmauxConvertW32ButtonFlagsToRMaux(UINT msg, 
					       WPARAM wParam, 
					       int *buttonIndex, 
					       int *modifierIndex)
{
    int mod, bindex=0;

    if ((msg == WM_LBUTTONUP) || (msg == WM_LBUTTONDOWN))
	bindex = Button1;
    else if ((msg == WM_MBUTTONUP) || (msg == WM_MBUTTONDOWN))
	bindex = Button2;
    else if ((msg == WM_RBUTTONUP) || (msg == WM_RBUTTONDOWN))
	bindex = Button3;
    else
	rmWarning(" need to add code to check and set buttons other than left, middle and up for W32. \n");
    *buttonIndex = private_rmauxButtonNumToIndex(bindex);

    mod = 0;
    if (wParam & MK_CONTROL)
	mod |= RM_CONTROL_MODIFIER;
    if (wParam & MK_SHIFT)
	mod |= RM_SHIFT_MODIFIER;
    *modifierIndex = private_rmauxModifierToIndex(mod);
}

/* 
 * the following are used to hold pointer (x,y) coordinates that
 * will survive between calls to the WndProc. we use int *'s so
 * this code will be thread safe.
 */
static int *winPointerXY = NULL;

LONG WINAPI rmauxWndProc(HWND hWnd, 
			 UINT msg,
			 WPARAM wParam, 
			 LPARAM lParam)
{
    static int   buttonIndex = -1;
    static int   modifierIndex = -1;
    static HDC   hDC;
    static HGLRC hRC;
    PAINTSTRUCT  ps;
    GLsizei      glnWidth, glnHeight;
    RMpipe      *win_current_pipe=NULL;
    int    stat = 0;
    int          consumedEvent = 0;

    /* 
     * Windows weirdnesses (2/2000):
     *
     * the routine that processes events (this routine) is not called
     * directly by the app, but by the window system. we need the RMpipe
     * handle to pass back to callbacks, so we cheat and use a static
     * variable inside RM. there's probably a way to wedge this into
     * the parmameters that are passed along from rmauxEventLoop
     * (win32 version) down to this routine, and this will be used
     * in the future.
     *
     * This routine is NOT thread safe because of win_current_pipe.
     *
     * TODO: HOW to end the WndProc/event loop w/o Destroying the Window?
     
     * 4/20/02 - this routine was updated to unify keypress handling on both
     * X11 and Win32 platforms. The same API is used in both places, but the
     * KeySym type is artificially defined on Win32 in order to coerce API
     * consistency across platforms. Presently, the KeySym "code" parameter
     * is not used on Win32. Only printable ASCII chars will trigger a call to
     * the user-defined key-handling function on Win32; refer to your Win32
     * developer documentation to learn more about which keys actually trigger
     * an WM_CHAR event, which in turn initiates a call to the user-supplied
     * key-handling callback.
     */

    if (staticPipe != NULL)
	win_current_pipe = staticPipe;

    /* malloc memory to hold the static variable used to hold the pointer (x,y) position */
    if (winPointerXY == NULL)
    {
        winPointerXY = (int *)malloc(sizeof(int) * 2);
	winPointerXY[0] = winPointerXY[1] = 0;
    }
    switch (msg)
       {
       case WM_TIMER:
	   stat = 1;
	   if (staticUserIdleCallbackFunc != NULL)
	       stat = (*staticUserIdleCallbackFunc)(win_current_pipe, private_rmauxGetCurrentSceneGraph(), private_rmauxRetrieveUserData(rmauxIdleFuncUsrDataArray, 0, 0), winPointerXY[0], winPointerXY[1]);
	   if (spinCallbackFunc != NULL)
	   {
	       (*spinCallbackFunc)();
	       rmauxInvokeRenderFunc(win_current_pipe,
				     private_rmauxGetCurrentSceneGraph());
	       stat = 1;
	   }
	   if (stat <= 0)
	       DestroyWindow(hWnd);

	  consumedEvent = 1;
	  break;
	  
       case WM_CREATE:
	  {
	     /* 
	      * assumption: when the window is created, the event
	      * loop routine has not yet been called. therefore, we need
	      * to grab the current pipe structure and stuff the window
	      * handle into it's field. note that later in the app code,
	      * we will explicitly call rmwPipeSetWindow for the purposes
	      * of syntactical consistency between the X and Win models,
	      * but in fact rmwPipeSetWindow is not *required* because
	      * we do it here.
	      */

	     int     width, height;
	     RMpipe *p;

	     width = (int) LOWORD (lParam);
	     height = (int) HIWORD (lParam);
	     
	     /* grap the RMpipe * passed in through the rmwCreateWindow call. */
	     p = (RMpipe *)(((LPCREATESTRUCT)lParam)->lpCreateParams);
	     
	     /* make it the current RMpipe for this event loop */
	     
	     win_current_pipe = staticPipe = p;
	     
	     /* Select a pixel format and then  create a rendering context from it */
	     if (win_current_pipe != NULL)
	     {
#if 0
		 hDC = GetDC(hWnd);
		 p->hdc = hDC;
    
		 setupPixelFormat(hDC, 16, 0); /* 16 bit zbuffer? */
		 setupPalette(hDC);
		 p->hRC = wglCreateContext(hDC);
		 rmPipeMakeCurrent(win_current_pipe);
		 /* build internal display lists for quadrics objects */
		 private_rmInitQuadrics(win_current_pipe->cache);
#endif
		}
	     consumedEvent = 1;
	     break;
	  }
       
       case WM_SIZE:
	  {
	     /* 
	      * Redefine the viewing volume and viewport when the window size changes 
	      * Make the RC current since we're going to make an OpenGL call here...
	      * get the new size of the client window
	      * note that we size according to the height,
	      * not the smaller of the height or width.
	      */

	     glnWidth = (GLsizei) LOWORD (lParam);
	     glnHeight = (GLsizei) HIWORD (lParam);

	     /*
	      * the following conditional checks for those events generated
	      * by win32 when a window is minimized, and its resulting width
	      * and/or height are set to zero. We avoid invoking the resize
	      * function if this is the case.
	      */
	     if ((glnWidth == 0) || (glnHeight == 0))
		 break;
	     
	     rmPipeSetWindowSize(win_current_pipe, (int)glnWidth, (int)glnHeight);
	     if (staticUserResizeFunc != NULL)
	     {
		 if (staticUserResizeFunc(win_current_pipe, private_rmauxGetCameraResizeNode(), glnWidth, glnHeight) != RM_CHILL)
		     rmError("rmauxEventLoop() error - abnormal return status from user resizing function.");
		 else
		     rmauxInvokeRenderFunc(win_current_pipe,
					   private_rmauxGetCurrentSceneGraph()); /* draw resized window */
	     }
	     
	     consumedEvent = 1;
	     break;
	  }
       
       case WM_PAINT:
	  rmauxInvokeRenderFunc(win_current_pipe,
				private_rmauxGetCurrentSceneGraph());
	  ValidateRect(hWnd, NULL);
	  consumedEvent = 1;
	  break;
	  
       case WM_DESTROY: /* Clean up and terminate */
	  wglDeleteContext( win_current_pipe->hRC );
	  PostQuitMessage( 0 );
	  consumedEvent = 1;
	  break;
	  
/********		   rmWarning(" need to add code to check and set buttons other than left, middle and up \n"); */
		   break;
       case WM_LBUTTONDOWN:	/* button 1 */
       case WM_RBUTTONDOWN:	/* button 3 */
       case WM_MBUTTONDOWN:	/* button 2 */
	  {
	      int ix, iy, rstat;
	     
	      winPointerXY[0] = ix = (int)LOWORD(lParam); /* pointer location */
	      winPointerXY[1] = iy = (int)HIWORD(lParam);

	      private_rmauxConvertW32ButtonFlagsToRMaux(msg, wParam, &buttonIndex, &modifierIndex);
	     
	     if (rmauxUserButtonDownFuncs[modifierIndex][buttonIndex] != NULL)
	       stat = (*rmauxUserButtonDownFuncs[modifierIndex][buttonIndex])(win_current_pipe, private_rmauxGetCurrentSceneGraph(), private_rmauxRetrieveUserData(rmauxButtonDownUsrDataArrays, buttonIndex, modifierIndex), ix, iy);
	     else
		 rstat = 1;
	     consumedEvent = 1;
	     break;
	  }
       
       case WM_MOUSEMOVE:
	  {
	     int ix, iy, rstat;	
	     
	     winPointerXY[0] = ix = (int)LOWORD(lParam); 
	     winPointerXY[1] = iy = (int)HIWORD(lParam);
	     
	     if (buttonIndex != -1)
		{
		    if (rmauxUserButtonMotionFuncs[modifierIndex][buttonIndex] != NULL)
			rstat = (*rmauxUserButtonMotionFuncs[modifierIndex][buttonIndex])(win_current_pipe, private_rmauxGetCurrentSceneGraph(), private_rmauxRetrieveUserData(rmauxButtonMotionUsrDataArrays, buttonIndex, modifierIndex), ix, iy);
		    else
			rstat = 1;

		   consumedEvent = 1;
#if 0
		   /* this code produces failure on some Win98 systems, and
		    is being temporarily removed until this issue is
		    resolved. 10/9/2001. wbethel */
		   
		   /* test removing extraneous WM_MOUSEMOVE events */
		   {
		      LPMSG lpMsg;
		      UINT  filterFirst, filterLast;
		      UINT  removeFlags = PM_REMOVE;
		      
		      filterFirst = filterLast = WM_MOUSEMOVE;
		      
		      while (PeekMessage(lpMsg, hWnd, filterFirst, filterLast, removeFlags))
			 ; /* just remove 'em all */
		   }
#endif
		}
	     break;
	  }
       
       case WM_LBUTTONUP:
       case WM_MBUTTONUP:
       case WM_RBUTTONUP:
	  {
	     int ix, iy;
	     int rstat;

	     winPointerXY[0] = ix = (int)LOWORD(lParam);
	     winPointerXY[1] = iy = (int)HIWORD(lParam);
	     
	     private_rmauxConvertW32ButtonFlagsToRMaux(msg, wParam, &buttonIndex, &modifierIndex);

	     if (rmauxUserButtonUpFuncs[modifierIndex][buttonIndex] != NULL)
	       rstat = (*rmauxUserButtonUpFuncs[modifierIndex][buttonIndex])(win_current_pipe, private_rmauxGetCurrentSceneGraph(), private_rmauxRetrieveUserData(rmauxButtonUpUsrDataArrays, buttonIndex, modifierIndex), ix, iy);
	     else
		 rstat = 1;

	     buttonIndex = -1;
	     consumedEvent = 1;
	     break;
	  }
      case WM_CHAR:
	  {
	      if (staticUserKeypressCallbackFunc != NULL)
	      {
		  consumedEvent = 1;
		  stat = staticUserKeypressCallbackFunc(win_current_pipe, private_rmauxGetCurrentSceneGraph(), private_rmauxRetrieveUserData(rmauxKeypressUsrDataArray, 0, 0), (char)(wParam), 0);
		  if (stat != 1)
		  {
		      DestroyWindow(hWnd);
		  }
	      }
	      break;
	  }
      }
    
    if (consumedEvent != 0)
	return 0;
    else
	return DefWindowProc(hWnd, msg, wParam, lParam);
}

LONG WINAPI rmauxOffscreenWndProc(HWND hWnd, 
				  UINT message,
				  WPARAM wParam, 
				  LPARAM lParam)
{
    switch (message) {
    case WM_CREATE:
	return 0;
    case WM_DESTROY:
	PostQuitMessage(0);
	return 0;
    case WM_SIZE:
    case WM_PALETTECHANGED:
	break;
    case WM_QUERYNEWPALETTE:
	break;
    case WM_PAINT:
	break;
    case WM_CHAR:
	break;
    default:
	break;
    }

    /* Deal with any unprocessed messages */
    return DefWindowProc(hWnd, message, wParam, lParam);
}
#endif /* RM_WIN */

#ifdef RM_X /* X11 event loop (documentation above) */
void
rmauxEventLoop (RMpipe *pipe,
		RMnode *subTree,
	        void *unused)
{
    int    button_index = -1, modifier_index = -1;
    XEvent event;
    long   mask;
    int    stat = 1;
    int    event_stat;
    int    pointerX = 0, pointerY = 0;

    private_rmauxSetCurrentSceneGraph(subTree);
    
    /* clear out any lingering keyrelease & keypress events on the display window */
    mask = KeyPress | KeyRelease;
    XSelectInput(rmxPipeGetDisplay(pipe), rmPipeGetWindow(pipe), mask);

    XSync(rmxPipeGetDisplay(pipe), 0);
    while (XCheckWindowEvent(rmxPipeGetDisplay(pipe),
			     rmPipeGetWindow(pipe), mask,  &event) == True)
	fprintf(stderr," removing errant Keypress or KeyRelease event \n");
    
    if (staticUserInitCallbackFunc != NULL)
    {
        (*staticUserInitCallbackFunc)(pipe, subTree, private_rmauxRetrieveUserData(rmauxInitFuncUsrDataArray, 0, 0));
        staticUserInitCallbackFunc = NULL;
    }
    
    mask = RM_AUX_XWINDOW_EVENT_MASK;
    XSelectInput(rmxPipeGetDisplay(pipe), rmPipeGetWindow(pipe), mask);

    while (stat > 0)
    {
        if ((staticUserIdleCallbackFunc != NULL) || (spinCallbackFunc != NULL))
	   event_stat = XCheckWindowEvent(rmxPipeGetDisplay(pipe), rmPipeGetWindow(pipe), mask, &event);
	else
	   event_stat = XWindowEvent(rmxPipeGetDisplay(pipe), rmPipeGetWindow(pipe), mask, &event);
	
	if ((event_stat == False) && ((staticUserIdleCallbackFunc != NULL) || (spinCallbackFunc != NULL)))
	{
	    if (staticUserIdleCallbackFunc != NULL)
		stat = (*staticUserIdleCallbackFunc)(pipe, subTree, private_rmauxRetrieveUserData(rmauxIdleFuncUsrDataArray, 0, 0), pointerX, pointerY);
	    
	    if (spinCallbackFunc != NULL)
	    {
		(*spinCallbackFunc)();
		stat = rmauxInvokeRenderFunc(pipe, subTree);
	    }
	}
	else
	{
	    switch(event.type)
		{
		case Expose:
		    if (event.xexpose.count == 0)
			stat = rmauxInvokeRenderFunc(pipe, subTree);
		    break;

		case ButtonPress:
		    {
			button_index = private_rmauxButtonNumToIndex(event.xbutton.button);
			modifier_index = private_rmauxModifierToIndex(event.xbutton.state);
			pointerX = event.xbutton.x;
			pointerY = event.xbutton.y;
		
			/* added 6/10/02 to avoid user-induced crash */
			if (rmauxUserButtonDownFuncs[modifier_index][button_index] != NULL)
			  stat = (*rmauxUserButtonDownFuncs[modifier_index][button_index])(pipe, subTree, private_rmauxRetrieveUserData(rmauxButtonDownUsrDataArrays, button_index, modifier_index), pointerX, pointerY);
			break;
		    }
		
		case ButtonRelease:
		    {
			button_index = private_rmauxButtonNumToIndex(event.xbutton.button);
			modifier_index = private_rmauxModifierToIndex(event.xbutton.state);
			pointerX = event.xbutton.x;
			pointerY = event.xbutton.y;

			/* added 6/10/02 to avoid user-induced crash */
			if (rmauxUserButtonUpFuncs[modifier_index][button_index] != NULL)
			  stat = (*rmauxUserButtonUpFuncs[modifier_index][button_index])(pipe, subTree, private_rmauxRetrieveUserData(rmauxButtonUpUsrDataArrays, button_index, modifier_index), pointerX, pointerY);
			
			button_index = modifier_index = -1;
			break;
		    }
		
		case MotionNotify:
		    {
			/*
			 * we'll assume that "button_index" and "modifier_index"
			 * were set by the buttonpress event, and that they
			 * are still valid. sometimes these values aren't properly
			 * set on motion events.  the UI metaphor is that whatever
			 * these values are button index, modifier index) is what
			 * they are for all subsequent motion events until a release 
			 * event.
			 */

			pointerX = event.xbutton.x;
			pointerY = event.xbutton.y; 
		
			/* check for invalid state on button & modifier tags */
			if ((modifier_index == -1) || (button_index == -1))
			    break;	/* bail if they're invalid - this implies just pointer motion w/no button press */
		
			/* call the routine which processes motion events. */
			/* added 6/10/02 to avoid user-induced crash */
			if (rmauxUserButtonMotionFuncs[modifier_index][button_index] != NULL)
			  stat = (*rmauxUserButtonMotionFuncs[modifier_index][button_index])(pipe, subTree, private_rmauxRetrieveUserData(rmauxButtonMotionUsrDataArrays, button_index, modifier_index), pointerX, pointerY);

			/*
			 * while ButtonMotion events remain, peel them off. the
			 * problem we're addressing here is that ButtonMotion
			 * events can pile up when the renderer (or whatever action
			 * is mapped to ButtonMotion) is slower than the user. if
			 * we don't peel off "excess" events and throw them away,
			 * the renderer redraws for all the intermediate positions.
			 * the user probably wants the renderer to draw using
			 * the most recent pointer position, not all the intermediate 
			 * positions.
			 */

			while (XCheckMaskEvent(rmxPipeGetDisplay(pipe), ButtonMotionMask, &event) == True)
			    ;

			break;

		    }

		case ConfigureNotify:
		    {
			/*
			 * when we get notification of a window size change, we
			 * simply set the size attribute of the window pipe. we
			 * make no assumption about modification of the aspect
			 * ratio of cameras within this window, if any, however,
			 * a non-NULL user-defined callback will be called to 
			 * resize the window as required.
			 */

			int new_width, new_height;
		
			new_width = event.xconfigure.width;
			new_height = event.xconfigure.height;
			rmPipeSetWindowSize(pipe, new_width, new_height);

			/* try to resize the window */
			if (staticUserResizeFunc != NULL)
			{
			    if (staticUserResizeFunc(pipe, private_rmauxGetCameraResizeNode(), new_width, new_height) != RM_CHILL)
				rmError("rmauxEventLoop() error - abnormal return status from user resizing function.");
			    else
				rmauxInvokeRenderFunc(pipe, subTree); /* draw resized window */
			}
#if 0
			/* debug */
			printf(" rendering because of configurenotify \n");
#endif
			stat = rmauxInvokeRenderFunc(pipe, subTree);
			break;
		    }

		case KeyPress:
		case KeyRelease:
		    {
			/*
			 * grab both key presses and releases in order to latch
			 * keys for individual application actions
			 *
			 * keys are NOT latched when no key handler callback
			 * exists. when a keyhandler does not exist, the
			 * event loop exits when any non-modifier key
			 * is pressed.
			 *
			 * 8/27/2000 - this code needs more thorough testing.
			 * 
			 * 02/11/01 - added rmaux state key function jdb
			 */

			static char    key, latch = 0;
			static KeySym  code;
			char           buf_return[32];
			int            len;
			KeySym         keysym_ret;
			XComposeStatus status;
			
			len = XLookupString(&(event.xkey), buf_return, 32, &keysym_ret, &status);

			/* check to see if the key is one of the modifiers */
			if (((event.type == KeyPress) || (event.type == KeyRelease)) && ((keysym_ret == XK_Shift_L) || (keysym_ret == XK_Shift_R) || 
							 (keysym_ret == XK_Control_L) || (keysym_ret == XK_Control_R)))
			    stat = 1;
			else /* not a modifier - pass latched key press back to non-NULL key handler */
			{
			    /* release latched key */
			    if ((latch) && (event.type == KeyRelease) && (staticUserKeypressCallbackFunc != NULL))
			    {
				if ((*buf_return == key) || (keysym_ret == code))
				{
				    stat = staticUserKeypressCallbackFunc(pipe, subTree, private_rmauxRetrieveUserData(rmauxKeypressUsrDataArray, 0, 0), *buf_return, keysym_ret);
				}
			    }
			    else /* latch new key press */
			    {
				if ((event.type == KeyPress) && (staticUserKeypressCallbackFunc != NULL))
				{
				    key = *buf_return;
				    code = keysym_ret;
				    latch = 1;
				    stat = 1;
				}
			    }
			}
			break;
		    }

		default: /* event we don't care about other events! */
		    break;

		} /* end event type switch */
	} /* end if have an event */
    } /* end while (stat) */

    /* foil compiler warning */
    unused = NULL;
}
#endif /* RM_X */

/*
 * ----------------------------------------------------
 * @Name rmauxSetGeomTransform
 @pstart
 void rmauxSetGeomTransform (RMnode *target,
	                     RMpipe *usePipe)
 @pend

 @astart 
 RMnode *target - a handle to an RMnode. (input) All model
    transformations will be applied to this node, and will affect all
    children nodes.
	 
 RMpipe *usePipe - a handle to an RMpipe. (input) This is is the
    currently active GL context and is used to get cursors for X11.
 @aend

 @dstart

 rmauxSetGeomTransform assigns a default set of action handlers that will
 be executed when rmauxEventLoop is invoked to begin processing of
 events. The action handlers installed by rmauxSetGeomTransform will
 modify the transformation matrices at the RMnode "subTree" to produce
 either rotations (button2 + motion) or isometric scaling
 (shift+button2+motion). These action handlers will be invoked from
 rmauxEventLoop().

 After calling this routine, your application may override the action
 handlers this routine assigns to button2 (up, down, motion, with and
 without the shift modifier).

 rmauxSetGeomTransform maps RM_BUTTON2 to an arcball-style rotation, and
 SHIFT+RM_BUTTON2 to isometric scaling.  Pressing the RM_BUTTON* on the
 mouse will initiate the transformation. Dragging the mouse while holding
 the RM_BUTTON* down will cause a frame to be rendered for each
 transformation (see Note below), then when the RM_BUTTON* is released,
 the appearance of interactive transformation stops and the system
 becomes quiescient.

 Applications developers may assign their own callbacks to the rmaux
 action handler using rmauxButtonDownFunc, rmauxButtonMotionFunc and
 rmauxButtonUpFunc (as well as rmauxSetIdleCallbackFunc). rmauxUI just assigns
 a default set of action handlers to a small, specific set of button
 events.

 Assumptions:

 1. Whenever a frame needs to be drawn, the routine specified by
 rmauxSetRenderCallbackFunc is invoked. By default, that function is just
 rmFrame(). 

 2. X11 notes: rmauxUI creates cursors for each operation. These
 cursors are valid only on the XDisplay of the currently active
 RMpipe. Be sure to call rmPipeMakeCurrent prior to calling rmauxUI.
 At this time (1/15/2000) this restriction does not apply to Win32
 codes.
 
 @dend
 * ----------------------------------------------------
 */
void
rmauxSetGeomTransform(RMnode *target,
		      RMpipe *usePipe)
{
#ifdef RM_X
    XColor cursor_fg, cursor_bg;

    /* define special X11 cursors for operation emphasis */
    rotate_cursor = XCreateFontCursor(rmxPipeGetDisplay(usePipe), XC_exchange);
    scale_cursor = XCreateFontCursor(rmxPipeGetDisplay(usePipe), XC_sizing);

    /* define cursor colors 
     * (r,g,b) = [0..65535] in X11 colors 
     * (the XServer scales X11 colors to available HW color bit depth)
     */
    cursor_fg.red = 65535;
    cursor_fg.green = 0;
    cursor_fg.blue = 16383;
    cursor_bg.red = 65535;
    cursor_bg.green = 65535;
    cursor_bg.blue = 65535;

    /* color special cursors to emphasize during operation */
    XRecolorCursor(rmxPipeGetDisplay(usePipe), rotate_cursor, &cursor_fg, &cursor_bg);
    XRecolorCursor(rmxPipeGetDisplay(usePipe), scale_cursor, &cursor_fg, &cursor_bg);
#endif
    geomTransformTarget = target;
    
    /* assign RM_BUTTON2: Arcball rotation */
    rmauxSetButtonDownCallbackFunc(rmauxB2DownFunc, NULL, RM_BUTTON2, RM_NONE_MODMASK);
    rmauxSetButtonUpCallbackFunc(rmauxB2UpFunc, NULL, RM_BUTTON2, RM_NONE_MODMASK );
    rmauxSetButtonMotionCallbackFunc(rmauxB2MotionFunc, NULL, RM_BUTTON2, RM_NONE_MODMASK);

    /* shift+button two = scale */
    rmauxSetButtonDownCallbackFunc(rmauxShiftB2DownFunc, NULL, RM_BUTTON2,RM_SHIFT_MODMASK );
    rmauxSetButtonUpCallbackFunc(rmauxShiftB2UpFunc, NULL, RM_BUTTON2,RM_SHIFT_MODMASK );
    rmauxSetButtonMotionCallbackFunc(rmauxShiftB2MotionFunc, NULL, RM_BUTTON2,RM_SHIFT_MODMASK);
}

/*
 * ----------------------------------------------------
 * @Name rmauxSetCamera3DTransform
 @pstart
 void rmauxSetCamera3DTransform (RMnode *target,
	                         RMpipe *usePipe)
 @pend

 @astart 
 RMnode *target - a handle to an RMnode. (input) All transformations
    will be applied to the RMcamera3D scene parameter at target, if
    such a scene parameter exists.
	 
 RMpipe *usePipe - a handle to an RMpipe. (input) This is is the
    currently active GL context and is used to get cursors for X11.
 @aend

 @dstart

 rmauxSetCamera3DTransform assigns a default set of action handlers that will
 be executed when rmauxEventLoop is invoked to begin processing of
 events. The action handlers installed by rmauxSetCamera3DTransform will
 modify eye and look-at attributes of the RMcamera3D scene parameter
 assigned at the input RMnode "target" to achieve image plane
 translation, or dollying along the Z axis of eye coordinates.

 rmauxSetCamera3DTransform maps RM_BUTTON1 to image plane translation,
 and RM_BUTTON3 to camera dolly translation. Pressing the associated
 RM_BUTTON* initiates the transformation.  Dragging the mouse while holding
 the RM_BUTTON* down will cause a frame to be rendered for each
 transformation (see Note below), then when the RM_BUTTON* is released,
 the appearance of interactive transformation stops and the system
 becomes quiescient.
 
 Pressing any key on the keyboard other than a modifier key will cause
 the event loop to latch the pressed key.  Once the key is released, a
 non-NULL user callback function for handling keypresses is called.
 This user key callback is set with rmauxEventLoop(). In the
 event no keypress callback is assigned with rmauxEventLoop, the event
 loop will exit if any non-modifier key is pressed (X11 only, Win32
 code incomplete at this time 8/27/2000).

 Applications developers may assign their own callbacks to the rmaux
 action handler using rmauxButtonDownFunc, rmauxButtonMotionFunc and
 rmauxButtonUpFunc (as well as rmauxSetIdleCallbackFunc). rmauxUI just assigns
 a default set of action handlers to a small, specific set of button
 events.

 Note: none of the rmaux routines are needed to use the rest of
 OpenRM. Your application may use it's own event loop, and you may
 safely ignore all of rmaux if that's what you need.

 Assumptions:

 1. X11 notes: rmauxUI creates cursors for each operation. These
 cursors are valid only on the XDisplay of the currently active
 RMpipe. Be sure to call rmPipeMakeCurrent prior to calling rmauxUI.
 At this time (1/15/2000) this restriction does not apply to Win32
 codes.
 
 @dend
 * ----------------------------------------------------
 */
void
rmauxSetCamera3DTransform(RMnode *target,
			  RMpipe *usePipe)
{
#ifdef RM_X
    XColor cursor_fg, cursor_bg;

    /* define special X11 cursors for operation emphasis */
    translate_cursor = XCreateFontCursor(rmxPipeGetDisplay(usePipe), XC_fleur);
    dolly_cursor = XCreateFontCursor(rmxPipeGetDisplay(usePipe), XC_double_arrow);
    /* define cursor colors 
     * (r,g,b) = [0..65535] in X11 colors 
     * (the XServer scales X11 colors to available HW color bit depth)
     */
    cursor_fg.red = 65535;
    cursor_fg.green = 0;
    cursor_fg.blue = 16383;
    cursor_bg.red = 65535;
    cursor_bg.green = 65535;
    cursor_bg.blue = 65535;

    /* color special cursors to emphasize during operation */
    XRecolorCursor(rmxPipeGetDisplay(usePipe), translate_cursor, &cursor_fg, &cursor_bg);
    XRecolorCursor(rmxPipeGetDisplay(usePipe), dolly_cursor, &cursor_fg, &cursor_bg);
#endif
    camera3DTransformTarget = target;
    
    /* assign RM_BUTTON1: image plane translation */
    rmauxSetButtonDownCallbackFunc(rmauxB1DownFunc, NULL, RM_BUTTON1, RM_NONE_MODMASK );
    rmauxSetButtonUpCallbackFunc(rmauxB1UpFunc, NULL, RM_BUTTON1, RM_NONE_MODMASK );
    rmauxSetButtonMotionCallbackFunc(rmauxB1MotionFunc, NULL, RM_BUTTON1, RM_NONE_MODMASK);

    /* assign RM_BUTTON3: camera dolly translation */
    rmauxSetButtonDownCallbackFunc(rmauxB3DownFunc, NULL, RM_BUTTON3, RM_NONE_MODMASK );
    rmauxSetButtonUpCallbackFunc(rmauxB3UpFunc, NULL, RM_BUTTON3, RM_NONE_MODMASK );
    rmauxSetButtonMotionCallbackFunc(rmauxB3MotionFunc, NULL, RM_BUTTON3, RM_NONE_MODMASK);
}


/*
 * ----------------------------------------------------
 * @Name rmauxUI
 @pstart
 void rmauxUI (RMnode *target,
	       RMpipe *usePipe)
 @pend

 @astart 
 RMnode *target - a handle to an RMnode. (input) All model
    transformations will be applied to this node, and will affect all
    children nodes.
	 
 RMpipe *usePipe - a handle to an RMpipe. (input) This is is the
    currently active GL context and is used to get cursors for X11.
 @aend

 @dstart

 rmauxUI assigns a default set of action handlers that will be
 executed when rmauxEventLoop is invoked to begin processing of
 events. There are three types of transformations implemented by
 rmauxUI: a virtual arcball interface to specify rotations, a camera
 dolly translation, and an image plane translation.

 rmauxUI maps RM_BUTTON2 to an arcball-style rotation, and
 SHIFT+RM_BUTTON2 to isometric scaling.  Pressing the RM_BUTTON* on the
 mouse will initiate the transformation. Dragging the mouse while holding
 the RM_BUTTON* down will cause a frame to be rendered for each
 transformation (see Note below), then when the RM_BUTTON* is released,
 the appearance of interactive transformation stops and the system
 becomes quiescient.

 Pressing any key on the keyboard other than a modifier key will cause
 the event loop to latch the pressed key.  Once the key is released, a
 non-NULL user callback function for handling keypresses is called.
 This user key callback is set with rmauxEventLoop().

 Resizing the window bound to the RMpipe will call a non-NULL user
 callback function for handling window resizing.  This user resize
 callback is set with rmauxEventLoop().

 Applications developers may assign their own callbacks to the rmaux
 action handler using rmauxButtonDownFunc, rmauxButtonMotionFunc and
 rmauxButtonUpFunc (as well as rmauxSetIdleCallbackFunc). rmauxUI just assigns
 a default set of action handlers to a small, specific set of button
 events.

 Note: none of the rmaux routines are needed to use the rest of
 OpenRM. Your application may use it's own event loop, and you may
 safely ignore all of rmaux if that's what you need.

 Assumptions:

 1. X11 notes: rmauxUI creates cursors for each operation. These
 cursors are valid only on the XDisplay of the currently active
 RMpipe. Be sure to call rmPipeMakeCurrent prior to calling rmauxUI.
 At this time (1/15/2000) this restriction does not apply to Win32
 codes.
 
 @dend
 * ----------------------------------------------------
 */
void
rmauxUI (RMnode *target,
	 RMpipe *usePipe)
{
#ifdef RM_X
    XColor cursor_fg, cursor_bg;

    /* define special X11 cursors for operation emphasis */
    translate_cursor = XCreateFontCursor(rmxPipeGetDisplay(usePipe), XC_fleur);
    rotate_cursor = XCreateFontCursor(rmxPipeGetDisplay(usePipe), XC_exchange);
    dolly_cursor = XCreateFontCursor(rmxPipeGetDisplay(usePipe), XC_double_arrow);
    scale_cursor = XCreateFontCursor(rmxPipeGetDisplay(usePipe), XC_sizing);

    /* define cursor colors 
     * (r,g,b) = [0..65535] in X11 colors 
     * (the XServer scales X11 colors to available HW color bit depth)
     */
    cursor_fg.red = 65535;
    cursor_fg.green = 0;
    cursor_fg.blue = 16383;
    cursor_bg.red = 65535;
    cursor_bg.green = 65535;
    cursor_bg.blue = 65535;

    /* color special cursors to emphasize during operation */
    XRecolorCursor(rmxPipeGetDisplay(usePipe), rotate_cursor, &cursor_fg, &cursor_bg);
    XRecolorCursor(rmxPipeGetDisplay(usePipe), scale_cursor, &cursor_fg, &cursor_bg);
#endif

    /* save `UI root' node as target for interaction */
    if (target == NULL)
    {
        rmError("rmauxUI() error - input node is NULL.");
	return;
    }
    geomTransformTarget = target;		 

    /* assign RM_BUTTON2: Arcball rotation */
    rmauxSetButtonDownCallbackFunc(rmauxB2DownFunc, NULL, RM_BUTTON2, RM_NONE_MODMASK );
    rmauxSetButtonUpCallbackFunc(rmauxB2UpFunc, NULL, RM_BUTTON2, RM_NONE_MODMASK );
    rmauxSetButtonMotionCallbackFunc(rmauxB2MotionFunc, NULL, RM_BUTTON2, RM_NONE_MODMASK);

    /* shift+button two = scale */
    rmauxSetButtonDownCallbackFunc(rmauxShiftB2DownFunc, NULL, RM_BUTTON2,RM_SHIFT_MODMASK );
    rmauxSetButtonUpCallbackFunc(rmauxShiftB2UpFunc, NULL, RM_BUTTON2,RM_SHIFT_MODMASK );
    rmauxSetButtonMotionCallbackFunc(rmauxShiftB2MotionFunc, NULL, RM_BUTTON2,RM_SHIFT_MODMASK );

}


/*
 * ----------------------------------------------------
 * @Name rmauxSetInitCallbackFunc
 @pstart
 rmauxSetInitCallbackFunc (void (*userInitCallbackFunc)(RMpipe *drawOn, RMnode *subTree, void *usrData), void *usrData)
 @pend

 @astart

 void (*userInitCallbackFunc)(RMpipe *drawOnPipe, RMnode *subtree, void *usrData) -
   a handle to a caller-supplied initialization function.  

 void *usrData - an optional pointer from your application that will
   be handed back to the userInitCallbackFunc routine.
   
 @aend

 @dstart

 Use this routine to register your application's "init" function with
 RMaux. This initialization function will be called from within
 rmauxEventLoop after all OpenGL and window system initialization had
 been complete, and it is safe to begin using all of OpenRM as well
 as OpenGL.

 When calling rmauxSetInitCallbackFunc, any value you provide for the
 "usrData" parameter will be handed back to your callback routine. 
 
 @dend
 * ----------------------------------------------------
 */
void
rmauxSetInitCallbackFunc ( RMauxInitCallbackFuncPrototype usrInitFunc,
			   void *usrData)
{
    staticUserInitCallbackFunc = usrInitFunc;
    
    private_rmauxAssignUserData(&rmauxInitFuncUsrDataArray, 1, 1, 0, 0, usrData);
}


/*
 * ----------------------------------------------------
 * @Name rmauxSetIdleCallbackFunc
 @pstart
 void rmauxSetIdleCallbackFunc (int (*userCallbackFunc)(RMpipe *currentPipe, RMnode *drawRoot, void *usrData, int pointerX, int pointerY), void *usrData)

 @pend

 @astart

 int (*userCallbackFunc) (RMpipe *currentPipe, RMnode *drawRoot, 
    void *usrData, int xPointerPosition, int yPointerPosition) - the 
    function that will be invoked when the event loops is idle.
    The user function will be passed integer values representing the 
    (x,y) pixel coordinate of the pointer,  a handle to the current/calling 
    RMpipe, the RMnode pointer passed to rmauxEventLoop, and a void * to 
    user data. When the app idle callback returns a zero, event loop
    processing is terminated; when a non-zero value is returned,
    processing continues.

 const void *usrData - a pointer to caller supplied memory. This pointer,
    which may be NULL, will be passed as a parameter to the caller-supplied
    callback function.

 @aend

 @dstart

  Use this routine to register an application function that will be
  called (X11) when there are no other events in the event queue or
  (Win32) a timer with a 20msec interval "alarm" has expired.
  
  Win32 Notes: we need the RMpipe pointer so that we can obtain the
  window handle for the purpose of attaching a "timer" function to a
  window.  because the timer goes off at a specified interval (in the
  absence of other messages to process), it's not really an "idle"
  function except in the sense that the app function is called 20msec
  after a period of idleness is detected.
  
  X: it really is an idle function. note that we detect idleness
  inside the event loop attached to a window, and don't need the
  RMpipe structure.  it is included here so that one API can be used
  for both Win32 and X.

  The value returned by the application idle callback has an effect
  upon event loop processing: when the app callback returns a zero,
  event loop processing is terminated. When a non-zero value is
  returned, event loop processing continues.

 @dend
 * ----------------------------------------------------
 */
void
rmauxSetIdleCallbackFunc (RMauxIdleCallbackFuncPrototype userfunc, 
			  void *usrData)
{
    staticUserIdleCallbackFunc = userfunc;
#ifdef RM_WIN
    /* this stuff needs to be moved somewhere else, like to the beginning
       of the Win32 event loop. */
    {
	HWND hWnd;

	/*	hWnd = rmPipeGetWindow(p); */
	/*	rmError("rmauxSetIdleCallbackFunc need to fix absence of an RMpipe and hWnd"); */
	hWnd = rmPipeGetWindow(staticPipe);

	if (hWnd != 0)
	{
	    if (userfunc != NULL)
		SetTimer(hWnd, 101, 1, NULL); /* call every 1 msec when no other msgs present */
	    else if (spinCallbackFunc == NULL) /* don't turn off timer if
						 we a spinCallbackFunc is set*/
		KillTimer(hWnd, 101);
	}
    }
#endif

   private_rmauxAssignUserData(&rmauxIdleFuncUsrDataArray, 1, 1, 0, 0, usrData);
}

/*
 * ----------------------------------------------------
 * @Name rmauxSetKeypressCallbackFunc
 @pstart
 void rmauxSetKeypressCallbackFunc (int (*userfunc)(RMpipe *p, RMnode *n, void *usrData, char k, KeySym code), void *usrData )
 @pend

 @astart
 
 int (*userCallbackFunc) (RMpipe *currentPipe, RMnode *drawRoot, 
     void *usrData, char k, KeySym code) - pointer to the application
    callback function that will be invoked when a keypress event is detected.
    (input)

 void *usrData - a pointer to caller supplied memory. This pointer,
    which may be NULL, will be passed as a parameter to the caller-supplied
    callback function. (input)
    
 @aend

 @dstart

 Use rmauxSetKeypressCallbackFunc to assign a keypress handler that will be
 invoked by rmauxEventLoop whenever a non-modifier keypress is detected.
 The userCallbackFunc will be provided the keyboard key that was pressed,
 the current RMpipe, an RMnode pointer to the scene content being drawn
 (the RMnode provided to rmauxEventLoop), a void * to any user data specified
 when rmauxSetKeypressCallbackFunc was called, a character (the char value of
 the key that was pressed) and a KeySym code (the KeySym value of the key
 that was pressed).

 Unlike mouse event mappings, where  a different callback is invoked
 depending upon motion, button up or down  events, a single key handler
 performs processing of all keyboard events.  Application-supplied key
 handler callbacks must contain sufficient internal logic to detect
 which key was pressed, and take appropriate action.

 The userCallbackFunc should return 1 to rmauxEventLoop if event processing
 is to continue, or return 0 if event loop processing should terminate.

 The RM demo programs typically assign the default key handler
 rmauxDefaultKeyFunc, which will terminate event loop processing whenever
 the user presses the "q" key on the keyboard.

 Notes:

 4/20/02 - this routine was updated to unify keypress handling on both
 X11 and Win32 platforms. The same API is used in both places, but the
 KeySym type is artificially defined on Win32 in order to coerce API
 consistency across platforms. Presently, the KeySym "code" parameter
 is not used on Win32. Only printable ASCII chars will trigger a call to
 the user-defined key-handling function on Win32; refer to your Win32
 developer documentation to learn more about which keys actually trigger
 an WM_CHAR event, which in turn initiates a call to the user-supplied
 key-handling callback.

 7/29/06 - added RMnode to parameter list of user callback function.
 8/7/06 - added void *usrData to the list of parms in the user
 callback function.

 @dend
 * ----------------------------------------------------
 */
void
rmauxSetKeypressCallbackFunc (RMauxKeypressCallbackFuncPrototype userFunc,
			      void *usrData)
{
    staticUserKeypressCallbackFunc = userFunc;

    private_rmauxAssignUserData(&rmauxKeypressUsrDataArray, 1, 1, 0, 0, usrData);
}


/*
 * ----------------------------------------------------
 * @Name rmauxDefaultKeyFunc
 @pstart
 int rmauxDefaultKeyFunc (RMpipe *currentPipe,
                          RMnode *n,
			  void *usrData,
                          char    key,
			  KeySym  code)
 @pend

 @astart
 RMpipe *currentPipe - a handle to the current pipe (input)
 char key - key from most recent key press (input)
 KeySym code (input) - an X11 KeySym code.
 @aend

 @dstart

 This simple key callback function intercepts "Q/q" key presses and
 then signals to quit application.

 4/20/02 - this routine was updated to unify keypress handling on both
 X11 and Win32 platforms. The same API is used in both places, but the
 KeySym type is artificially defined on Win32 in order to coerce API
 consistency across platforms. Presently, the KeySym "code" parameter
 is not used on Win32. Only printable ASCII chars will trigger a call to
 the user-defined key-handling function on Win32; refer to your Win32
 developer documentation to learn more about which keys actually trigger
 an WM_CHAR event, which in turn initiates a call to the user-supplied
 key-handling callback.

 3/27/04 - the input parameter "code" is ignored by this routine. The
 parameter is present to facilitate API consistency between user-supplied
 key handler callbacks and this RMaux default key handler.

 @dend
 * ----------------------------------------------------
 */
int
rmauxDefaultKeyFunc (RMpipe *currentPipe,
		     RMnode *n,
		     void *usrData,
		     char    key, 
		     KeySym  code)
{
    /* convert to lower case */
    if ((key >= 'A') && (key <= 'Z'))
	key = key - ('A' - 'a');

    /* take appropriate menu action */
    switch (key)
    {
       case 'q': /* quitting time */
	   return 0;

       default: /* do nothing */
	   break;
    }

    /* foil compiler warnings */
    currentPipe = NULL;
    code = (KeySym)0;
    n = NULL;
    usrData = NULL;
    
    return 1;
}


/*
 * ----------------------------------------------------
 * @Name rmauxSetResizeFunc
 @pstart
 void rmauxSetResizeFunc (RMpipe *p,
                          RMnode *cameraNode,
			  RMenum (*userfunc)(RMpipe *p, RMnode *n, int winWidth, int winHeight))
 @pend

 @astart
 RMpipe *p - an RMpipe object (input).

 RMnode *cameraNode - a handle to an RMnode, which should contain either
    an RMcamera2D or RMcamera3D scene parameter (input, but modified at
    runtime when a window resize event occurs).
    
 void (*userfunc)(RMpipe *p, RMnode *n, int winWidth, int winHeight) - a pointer to a user function (input).
 @aend

 @dstart

 The rmaux*UI()/rmauxEventLoop routines will catch all resize events to
 the application window.  The user may inmplement a routine to handle
 window resizing and specify it here.

 The default resize function routine used by rmauxSetResizeFunc()
 adjusts camera parameters according to the resized window geometry.

 See rmauxDefaultResizeFunc() for more details about the parameters
 to the resize callback function.

 @dend
 * ----------------------------------------------------
 */
void
rmauxSetResizeFunc (RMpipe *p,
		    RMnode *cameraNode,
		    int (*userfunc)(RMpipe *currentPipe, RMnode *cameraNode, int pointerX, int pointerY))
{
    staticUserResizeFunc = userfunc;
    private_rmauxSetCameraResizeNode(cameraNode);
    
    /* foil compiler warning */
    p = NULL;
}


/*
 * ----------------------------------------------------
 * @Name rmauxDefaultResizeFunc
 @pstart
 int rmauxDefaultResizeFunc (RMpipe *currentPipe,
                             RMnode *cameraNode,
			     int     winWidth,
			     int     winHeight)
 @pend

 @astart
 RMpipe *currentPipe - a handle to the current pipe
 
 RMnode *cameraNode - a handle to a scene graph node containing either
    an RMcamera2D or RMcamera3D scene parameter.
    
 int winWidth, winHeight - two integers specifying the new window
    size in pixels. These values are provided by rmauxEventLoop to
    the resize callback (input).
 @aend

 @dstart

 This simple resize callback function will adjust the aspect ratio of
 the 2D or 3D camera scene parameter contained in "cameraNode" to
 reflect the ratio of width to height dimensions of the resized window.

 If the target node "cameraNode" does not contain either a RMcamera2D
 or RMcamera3D scene parameter, this routine will issue a warning message.

 This routine always returns RM_CHILL, regardless of errors.

 @dend
 * ----------------------------------------------------
 */
int
rmauxDefaultResizeFunc (RMpipe *currentPipe,
			RMnode *cameraNode,
			int     winWidth,
			int     winHeight)
{
    RMcamera3D * c3=NULL;
    RMcamera2D * c2=NULL;
    RMnode *     r=NULL;

    r = cameraNode;
    if ((rmNodeGetSceneCamera3D (r, &c3)) != RM_WHACKED)
    {
	rmCamera3DSetAspectRatio (c3, ((float)(winWidth) / (float)(winHeight)));
	rmNodeSetSceneCamera3D (r, c3);
    }
    else if (rmNodeGetSceneCamera2D (r, &c2) != RM_WHACKED)
    {
	rmCamera2DSetAspectRatio (c2, ((float)(winWidth) / (float)(winHeight)));
	rmNodeSetSceneCamera2D (r, c2);
    }

    if ((c2 == NULL) && (c3 == NULL))
	rmWarning("rmauxDefaultResizeFunc() - the cameraNode does not contain either a 2D or 3D camera scene parameter!");

    if (c3 != NULL)
	rmCamera3DDelete(c3);
    
    if (c2 != NULL)
	rmCamera2DDelete(c2);

    rmPipeSetWindowSize(currentPipe, winWidth, winHeight);
    
    return (RM_CHILL);
}

/*
 * ----------------------------------------------------
 * @Name rmauxSetRenderCallbackFunc
 @pstart
 void rmauxSetRenderCallbackFunc (int (*usrRenderCallbackFunc)(RMpipe *currentPipe, RMnode *toDraw, void *usrData), 
 @pend

 @astart

 int (*usrRenderCallbackFunc)(RMpipe *currentPipe, RMnode *toDraw, void *usrData) -
 A pointer to a user function that will be invoked whenever RMaux thinks it
 needs to render a frame  (input).

 RMpipe *currentPipe - a pointer to an RMpipe object that is fully initialized
 and ready for rendering. Rendering will occur to this RMpipe.

 RMnode *toDraw - the tree of scene graph content that will be rendered.

 void *usrData - a pointer provided by the caller. This pointer will be
 handed to the routine usrRenderCallbackFunc when invoked.
 @aend

 @dstart

 The rmaux*UI()/rmauxEventLoop routines will occasionally have the
 need to render an image. For the trackball interface, rendering is
 needed whenever the mouse button is pressed and dragged. The
 rmauxEventLoop event handler allows applications to set a routine
 that is called whenever rendering is needed.

 The default rendering routine used by rmauxSetRenderCallbackFunc() is
 (a wrapper around) generic rendering routine rmFrame().

 Your application can assign a callback routine to be invoked whenever
 RMaux thinks it is time to render a frame. Your callback will be provided
 the following parameters:
 1. an RMpipe * - a pointer to the RMpipe being drawn on.
 2. an RMnode * - a pointer to the root of the scene graph being drawn.
 3. a void * - a pointer to user data, which will be NULL unless you
    specifically assigned a non-NULL pointer when calling
    rmauxSetRenderCallbackFunc.

 The return value from the application callback is significant. A
 return value of <= zero will cause the event loop to terminate. A value
 of > zero will cause the event loop to continue processing events.

 @dend
 * ----------------------------------------------------
 */
void
rmauxSetRenderCallbackFunc ( RMauxRenderCallbackFuncPrototype usrFunc,
			    void *usrData)
{
    if (usrFunc != NULL)
	renderCallbackFunc = usrFunc;

    private_rmauxAssignUserData(&rmauxRenderFuncUsrDataArray, 1, 1, 0, 0, usrData);
}


/*
 * ----------------------------------------------------
 * @Name rmauxSetButtonDownCallbackFunc
 @pstart
 void rmauxSetButtonDownCallbackFunc (int (*userCallbackFunc) (RMpipe *currentPipe, RMnode *n, void *usrData, int pointerX, int pointerY),
				      const void *usrData,
				      unsigned int whichbutton,
		                      unsigned int modmask)
 @pend

 @astart
 
 int (*userCallbackFunc) (RMpipe *currentPipe, RMnode *drawRoot, 
    void *usrData, int xPointerPosition, int yPointerPosition) - the 
    function that will be invoked when the named button is pressed and 
    modifier mask conditions are met. The user function will be passed 
    integer values representing the (x,y) pixel coordinate of the pointer, 
    a handle to the current/calling RMpipe, the RMnode pointer passed to
    rmauxEventLoop, and a void * to user data. (input)
    
 const void *usrData - a pointer to caller supplied memory. This pointer,
    which may be NULL, will be passed as a parameter to the caller-supplied
    callback function. (input)
    
 unsigned int whichbutton - An integer value specifying which button
    will be assigned an action handler. Valid values are RM_BUTTON1,
    RM_BUTTON2, RM_BUTTON3, RM_BUTTON4 or RM_BUTTON5. The enumerators
    RM_BUTTON4 and RM_BUTTON5 are valid in X11 environments, but not
    valid in Windows environments. (input)
    
 unsigned int modmask - An integer value specifying a modifier mask
    that can be used to qualify buttons. Valid values are
    RM_NONE_MODMASK, RM_SHIFT_MODMASK or RM_CONTROL_MODMASK. (input)

 @aend

 @dstart

 This routine, rmauxSetButtonDownCallbackFunc, is used to assign a callback to
 correspond to button down events.

 Applications may use this routine to define action handlers that will
 be invoked from inside of rmauxEventLoop(). This routine is one of
 three used to assign an application callback that will be invoked
 when a button is pressed. One of two modifier keys may also be
 specified, the shift key or control key, to be used in conjunction
 with a button.

 The return value from the application callback is significant. A
 return value of zero will cause the event loop to terminate. A value
 of 1 cause the event loop to continue processing events (strictly
 speaking, any non-zero value will keep the event loop going).
 
 @dend
 * ----------------------------------------------------
 */
void
rmauxSetButtonDownCallbackFunc (int (*userCallbackFunc) RMAUX_BUTTON_CALLBACK_FUNCTION_PARMS() ,
				void *usrData,
				unsigned int whichbutton,
				unsigned int modmask)
{
    int button_index, modifier_index;
    
    button_index = private_rmauxButtonNumToIndex(whichbutton);
    modifier_index = private_rmauxModifierToIndex(modmask);
    
    rmauxUserButtonDownFuncs[modifier_index][button_index] = userCallbackFunc;
    private_rmauxAssignUserData(&rmauxButtonDownUsrDataArrays, RM_NUM_BUTTONS+1, RM_NUM_BUTTON_MODIFIERS, whichbutton, modifier_index, usrData);
}


/*
 * ----------------------------------------------------
 * @Name rmauxSetButtonUpCallbackFunc
 @pstart
 void rmauxSetButtonUpCallbackFunc (int (*userCallbackFunc) (RMpipe *currentPipe, RMnode *n, void *usrData, int pointerX, int pointerY),
    				    void *usrData, 
				    unsigned int whichbutton,
		                    unsigned int modmask)
 @pend

 @astart
 int (*userCallbackFunc) (RMpipe *currentPipe, RMnode *drawRoot, 
    const void *usrData, int xPointerPosition, int yPointerPosition) - the 
    function that will be invoked when the named button is released and 
    modifier mask conditions are met. The user function will be passed 
    integer values representing the (x,y) pixel coordinate of the pointer, 
    a handle to the current/calling RMpipe, the RMnode pointer passed to
    rmauxEventLoop, and a void * to user data.

 void *usrData - a pointer to caller supplied memory. This pointer,
    which may be NULL, will be passed as a parameter to the caller-supplied
    callback function.
 
 unsigned int whichbutton - An integer value specifying which button
    will be assigned an action handler. Valid values are RM_BUTTON1,
    RM_BUTTON2, RM_BUTTON3, RM_BUTTON4 or RM_BUTTON5. The enumerators
    RM_BUTTON4 and RM_BUTTON5 are valid in X11 environments but not
    Windows environments.
    
 unsigned int modmask - An integer value specifying a modifier mask
    that can be used to qualify buttons. Valid values are
    RM_NONE_MODMASK, RM_SHIFT_MODMASK or RM_CONTROL_MODMASK.
 
 @aend

 @dstart

 Applications may use this routine to define action handlers that will
 be invoked from inside of rmauxEventLoop(). This routine is one of
 three used to assign an application callback that will be invoked
 when a button release event is detected, and while modifier
 conditions are met. One of two modifier keys may also be specified,
 the shift key or control key, to be used in conjunction with a
 button.

 This routine, rmauxSetButtonUpCallbackFunc, is used to assign a callback to
 correspond to button button release events, possibly with a modifier
 key present.

 The return value from the application callback is significant. A
 return value of zero will cause the event loop to terminate. A value
 of 1 cause the event loop to continue processing events (strictly
 speaking, any non-zero value will keep the event loop going).
 
 @dend
 * ----------------------------------------------------
 */
void
rmauxSetButtonUpCallbackFunc (RMauxButtonCallbackFuncPrototype userFunc,
			      void *usrData,
			      unsigned int whichbutton,
			      unsigned int modmask)
{
    int button_index, modifier_index;
    
    button_index = private_rmauxButtonNumToIndex(whichbutton);
    modifier_index = private_rmauxModifierToIndex(modmask);
    
    rmauxUserButtonUpFuncs[modifier_index][button_index] = userFunc;
    private_rmauxAssignUserData(&rmauxButtonUpUsrDataArrays, RM_NUM_BUTTONS+1, RM_NUM_BUTTON_MODIFIERS, whichbutton, modifier_index, usrData);
}


/*
 * ----------------------------------------------------
 * @Name rmauxSetButtonMotionCallbackFunc
 @pstart
 void rmauxSetButtonMotionCallbackFunc (int (*userCallbackFunc) (RMpipe *currentPipe, RMnode *n, void *usrData, int pointerX, int pointerY), 
					void *usrData,
					unsigned int whichbutton,
		                        unsigned int modmask)
 @pend

 @astart
 
 int (*userCallbackFunc) (RMpipe *currentPipe, RMnode *drawRoot, 
    void *usrData, int xPointerPosition, int yPointerPosition) - the 
    function that will be invoked when the the pointer is moving,
    the named button is pressed and modifier mask conditions are met.
    The user function will be passed integer values representing the 
    (x,y) pixel coordinate of the pointer, a handle to the current/calling 
    RMpipe, the RMnode pointer passed to rmauxEventLoop, and a void * to 
    user data.
    
 void *usrData - a pointer to caller supplied memory. This pointer,
    which may be NULL, will be passed as a parameter to the caller-supplied
    callback function.
    
 unsigned int whichbutton - An integer value specifying which button
    will be assigned an action handler. Valid values are RM_BUTTON1,
    RM_BUTTON2, RM_BUTTON3, RM_BUTTON4 or RM_BUTTON5. The enumerators
    RM_BUTTON4 and RM_BUTTON5 are valid in X11 environments but not
    Windows environments.
    
 unsigned int modmask - An integer value specifying a modifier mask
    that can be used to qualify buttons. Valid values are
    RM_NONE_MODMASK, RM_SHIFT_MODMASK or RM_CONTROL_MODMASK.
 @aend

 @dstart

 Applications may use this routine to define action handlers that will
 be invoked from inside of rmauxEventLoop(). This routine is one of
 three used to assign an application callback that will be invoked
 when a motion event is detected, while a button is pressed and while
 modifier conditions are met. One of two modifier keys may also be
 specified, the shift key or control key, to be used in conjunction
 with a button.

 This routine, rmauxSetButtonMotionCallbackFunc, is used to assign a callback
 to correspond to motion events while a button, and possibly a modifer
 key are depressed.

 The return value from the application callback is significant. A
 return value of zero will cause the event loop to terminate. A value
 of 1 cause the event loop to continue processing events (strictly
 speaking, any non-zero value will keep the event loop going).
 
 @dend
 * ----------------------------------------------------
 */
void
rmauxSetButtonMotionCallbackFunc (RMauxButtonCallbackFuncPrototype userFunc,
				  void *usrData,
				  unsigned int whichbutton,
				  unsigned int modmask)
{
    int button_index, modifier_index;
    
    button_index = private_rmauxButtonNumToIndex(whichbutton);
    modifier_index = private_rmauxModifierToIndex(modmask);
    
    rmauxUserButtonMotionFuncs[modifier_index][button_index] = userFunc;
    private_rmauxAssignUserData(&rmauxButtonMotionUsrDataArrays, RM_NUM_BUTTONS+1, RM_NUM_BUTTON_MODIFIERS, whichbutton, modifier_index, usrData);
}


/* button callback default definition */
static int
rmauxEventNoOp RMAUX_BUTTON_CALLBACK_FUNCTION_PARMS()
{
    /* foil compiler warning */
    xbutton = ybutton = 0;

    /* foil compiler warnings */
    p = NULL;
    n = NULL;
    usrData = NULL;
    
    return(1);
}




/* PRIVATE (rmauxUI, rmFlyUI)
 *
 * Intended only for internal use.
 * This routine will invoke the currently-registered
 * render function assigned to rmaux.
 *
 * since this is intended for internal use, it is not publicly documented.
 */
int
rmauxInvokeRenderFunc (RMpipe *pipe,
		       RMnode *subTreeToDraw)
{
    void *ptr=NULL;
    
    if (rmauxRenderFuncUsrDataArray != NULL)
	ptr = private_rmauxRetrieveUserData(rmauxRenderFuncUsrDataArray, 0, 0);
    return (*renderCallbackFunc)(pipe, subTreeToDraw, ptr);
}


/* PRIVATE (rmauxUI)
 *
 * rmauxB1DownFunc is called from rmauxEventLoop when a
 * button1 down event is detected. rmauxUI assigns rmauxB1DownFunc
 * to be the action handler for button1 down.
 *
 * RM_BUTTON1: image plane translation
 * 
 * applications may override this by calling rmauxSetButtonMotionFunc().
 * since this routine is intended to be called only from within
 * rmauxEventLoop, it is not publicly documented.
 */
int
rmauxB1DownFunc RMAUX_BUTTON_CALLBACK_FUNCTION_PARMS()
{
    int width, height;

#ifdef RM_X
    XDefineCursor(rmxPipeGetDisplay(p), rmPipeGetWindow(p), translate_cursor);
#endif    
    /* save the starting NDC cursor position */
    rmPipeGetWindowSize(p, &width, &height);
    static_pointerPrevX = pixeltovp(xbutton, width);
    static_pointerPrevY = -1.0F * pixeltovp(ybutton, height);

    return(1);
}


/* PRIVATE (rmauxUI)
 *
 * rmauxB1UpFunc is called from rmauxEventLoop when a button2 up
 * event is detected.  rmauxUI assigns rmauxB1UpFunc
 * to be the action handler for button1 up. 
 * 
 * RM_BUTTON1: image plane translation
 * 
 * applications may override this by calling rmauxSetButtonMotionFunc().
 * since this routine is intended to be called only from within
 * rmauxEventLoop, it is not publicly documented.
 */
int
rmauxB1UpFunc RMAUX_BUTTON_CALLBACK_FUNCTION_PARMS()
{
#ifdef RM_X
    XUndefineCursor(rmxPipeGetDisplay(p), rmPipeGetWindow(p));
#endif

    /* foil compiler warning */
    xbutton = ybutton = 0;
    
    return(1);
}


/* PRIVATE (rmauxUI)
 *
 * rmauxB1MotionFunc is called from rmauxEventLoop when a motion
 * event is detected while button1 is held down. rmauxUI assigns 
 * rmauxB1MotionFunc to be the action handler for button1+motion.  
 *
 * RM_BUTTON1: image plane translation
 * 
 * applications may override this by calling rmauxSetButtonMotionFunc().
 * since this routine is intended to be called only from within
 * rmauxEventLoop, it is not publicly documented.
 */
int
rmauxB1MotionFunc RMAUX_BUTTON_CALLBACK_FUNCTION_PARMS()
{
    int   width, height;
    float x2, y2;
    RMcamera3D *c=NULL;

    
    /* get current NDC cursor position */
    rmPipeGetWindowSize(p, &width, &height);
    x2 = pixeltovp(xbutton, width);
    y2 = -1.0F * pixeltovp(ybutton, height);

    /* do specified image plane translation */
    if (rmNodeGetSceneCamera3D(camera3DTransformTarget, &c) != RM_WHACKED)
    {
	rmauxTranslate(c, &static_pointerPrevX, &static_pointerPrevY, &x2, &y2);
	rmNodeSetSceneCamera3D(camera3DTransformTarget, c);
	rmCamera3DDelete(c);
    }
    static_pointerPrevX = x2;
    static_pointerPrevY = y2;

    /* redraw the frame */
    rmauxInvokeRenderFunc(p, private_rmauxGetCurrentSceneGraph());

    return(1);
}


/* PRIVATE (rmauxUI)
 *
 * rmauxB2DownFunc is called from rmauxEventLoop when a
 * button2 down event is detected. rmauxUI assigns rmauxB2DownFunc
 * to be the action handler for button2 down.
 *
 * RM_BUTTON2: Arcball rotation
 *
 * applications may override this by calling rmauxSetButtonMotionFunc().
 * since this routine is intended to be called only from within
 * rmauxEventLoop, it is not publicly documented.
 */
int
rmauxB2DownFunc RMAUX_BUTTON_CALLBACK_FUNCTION_PARMS()
{
    int width, height;

#ifdef RM_X
    XDefineCursor(rmxPipeGetDisplay(p), rmPipeGetWindow(p), rotate_cursor);
#endif

    if (spinCallbackFunc != NULL)
    {
#ifdef RM_WIN
	if (staticUserIdleCallbackFunc == NULL)
	{
	    /*
	     * turn off the timed event generator if the user defined
	     * idle callback isn't defined.
	     */
	    HWND hWnd;
	    hWnd = rmPipeGetWindow(p);
	    KillTimer(hWnd, 101);
	}
#endif
	spinCallbackFunc = NULL;
    }

    /* save the starting NDC cursor position */
    rmPipeGetWindowSize(p, &width, &height);
    
    lastBX1 = lastBX2 = (float)xbutton;
    static_pointerPrevX = pixeltovp(xbutton, width);
    
    lastBY1 = lastBY2 = (float)ybutton;
    static_pointerPrevY = -1.0F * pixeltovp(ybutton, height);
    
    /* save the initial rotation matrix */
    if (rmNodeGetRotateMatrix(geomTransformTarget, &ui_pose) == RM_WHACKED)
	rmMatrixIdentity(&ui_pose);

    return(1);
}

void
private_rmauxComputeScaledSpinPoints(float *px1,
				     float *py1,
				     float *px2,
				     float *py2,
				     int width,
				     int height)
{
    RMvertex2D p1, p2, v;
    double d;

    p1.x = pixeltovp(*px1, width);
    p1.y = pixeltovp(*py1, height);

    p2.x = pixeltovp(*px2, width);
    p2.y = pixeltovp(*py2, height);

    v.x = p2.x - p1.x;
    v.y = p2.y - p1.y;

    d = (v.x * v.x) + (v.y * v.y);
    d = sqrt(d);

    v.x *= d;
    v.y *= d;

    *px2 = *px1 + (v.x*width);
    *py2 = *py1 + (v.y*height);
}

/* PRIVATE (rmauxUI)
 *
 * rmauxB2UpFunc is called from rmauxEventLoop when a button2 up
 * event is detected.  rmauxUI assigns rmauxB2UpFunc
 * to be the action handler for button2 up.
 *
 * RM_BUTTON2: Arcball rotation
 *
 * applications may override this by calling rmauxSetButtonMotionFunc().
 * since this routine is intended to be called only from within
 * rmauxEventLoop, it is not publicly documented.
 */
int
rmauxB2UpFunc RMAUX_BUTTON_CALLBACK_FUNCTION_PARMS()
{
    int width, height;
#ifdef RM_X
    XUndefineCursor(rmxPipeGetDisplay(p), rmPipeGetWindow(p));
#endif

/*    printf(" x,y at release %d, %d \n", xbutton, ybutton);  */

    if (spinModeEnabled == RM_TRUE)
    {
	rmPipeGetWindowSize(p, &width, &height);

#ifdef RM_WIN
	if (private_rmauxSpinThreshold(lastBX2, lastBY2, lastBX1, lastBY1, staticSpinThreshold) > 0)
#else
	if (private_rmauxSpinThreshold(lastBX2, lastBY2, lastBX1, lastBY1, staticSpinThreshold) > 0)
#endif
	{
	    spinCallbackFunc = private_rmauxDoSpinCallback;
#ifdef RM_WIN
	    {
		HWND hWnd;
		hWnd = rmPipeGetWindow(p);
		/*
		 * since we've exceeded the spin threshold, under Win32 we
		 * need to activate a timer to generate events.
		 */

		if (hWnd != 0)
		    SetTimer(hWnd, 101, 1, NULL); /* call every 20 msec when no other msgs present */

	    }
#endif
	    {
		float px1, py1, px2, py2;
		px1 = lastBX2;
		py1 = lastBY2;
		px2 = lastBX1;
		py2 = lastBY1;
		private_rmauxComputeScaledSpinPoints(&px1, &py1, &px2, &py2, width, height);
		
		px1 = pixeltovp(px1, width);
		py1 = -1.0F * pixeltovp(py1, height);
		px2 = pixeltovp(px2, width);
		py2 = -1.0F * pixeltovp(py2, height);
		
		rmauxArcBall(&px1, &py1, &px2, &py2, &spinMatrix);
	    }
	}
    }

    /* foil compiler warning */
    xbutton = ybutton = 0;
    
    return(1);
}


/* PRIVATE (rmauxUI)
 *
 * rmauxB2MotionFunc is called from rmauxEventLoop when a motion
 * event is detected while button2 is held down. rmauxUI assigns 
 * rmauxB2MotionFunc to be the action handler for button2+motion.  
 *
 * RM_BUTTON2: Arcball rotation
 *
 * applications may override this by calling rmauxSetButtonMotionFunc().
 * since this routine is intended to be called only from within
 * rmauxEventLoop, it is not publicly documented.
 */
int
rmauxB2MotionFunc RMAUX_BUTTON_CALLBACK_FUNCTION_PARMS()
{
    int   width, height;
    float x2, y2;
    RMmatrix resultMatrix;

/*    printf(" x,y at motion %d, %d \n", xbutton, ybutton);  */
    
    /* get current NDC cursor position */
    rmPipeGetWindowSize(p, &width, &height);
    
    lastBX2 = lastBX1;
    lastBY2 = lastBY1;
    
    lastBY1 = (float)ybutton;
    y2 = -1.0F * pixeltovp(ybutton, height);
    
    lastBX1 = (float)xbutton;
    x2 = pixeltovp(xbutton, width);

    /* do specified Arcball rotation */
    rmauxArcBall(&static_pointerPrevX, &static_pointerPrevY, &x2, &y2, &resultMatrix);
    rmMatrixMultiply(&ui_pose, &resultMatrix, &resultMatrix);
    rmNodeSetRotateMatrix(geomTransformTarget, &resultMatrix);

    /* redraw the frame */
    rmauxInvokeRenderFunc(p, private_rmauxGetCurrentSceneGraph());

    return(1);
}


/* PRIVATE (rmauxUI)
 *
 * rmauxB3DownFunc is called from rmauxEventLoop when a
 * button3 down event is detected. rmauxUI assigns rmauxB3DownFunc
 * to be the action handler for button3 down.
 *
 * RM_BUTTON3: camera dolly translation
 *
 * applications may override this by calling rmauxSetButtonMotionFunc().
 * since this routine is intended to be called only from within
 * rmauxEventLoop, it is not publicly documented.
 */
int
rmauxB3DownFunc RMAUX_BUTTON_CALLBACK_FUNCTION_PARMS()
{
    int width, height;

#ifdef RM_X
    XDefineCursor(rmxPipeGetDisplay(p), rmPipeGetWindow(p), dolly_cursor);
#endif    
    /* save the starting NDC cursor position */
    rmPipeGetWindowSize(p, &width, &height);
    static_pointerPrevX = pixeltovp(xbutton, width);
    static_pointerPrevY = -1.0F * pixeltovp(ybutton, height);

    return(1);
}


/* PRIVATE (rmauxUI)
 *
 * rmauxB3UpFunc is called from rmauxEventLoop when a button3 up
 * event is detected.  rmauxUI assigns rmauxB1UpFunc
 * to be the action handler for button3 up.
 *
 * RM_BUTTON3: camera dolly translation
 *
 * applications may override this by calling rmauxSetButtonMotionFunc().
 * since this routine is intended to be called only from within
 * rmauxEventLoop, it is not publicly documented.
 */
int
rmauxB3UpFunc RMAUX_BUTTON_CALLBACK_FUNCTION_PARMS()
{
#ifdef RM_X
    XUndefineCursor(rmxPipeGetDisplay(p), rmPipeGetWindow(p));
#endif

    /* foil compiler warning */
    xbutton = ybutton = 0;
    
    return(1);
}


/* PRIVATE (rmauxUI)
 *
 * rmauxB3MotionFunc is called from rmauxEventLoop when a motion
 * event is detected while button3 is held down. rmauxUI assigns 
 * rmauxB3MotionFunc to be the action handler for button3+motion.  
 *
 * RM_BUTTON3: camera dolly translation
 *
 * applications may override this by calling rmauxSetButtonMotionFunc().
 * since this routine is intended to be called only from within
 * rmauxEventLoop, it is not publicly documented.
 */
int
rmauxB3MotionFunc RMAUX_BUTTON_CALLBACK_FUNCTION_PARMS()
{
    int   width, height;
    float x2, y2;
    RMcamera3D *c=NULL;

    /* get current NDC cursor position */
    rmPipeGetWindowSize(p, &width, &height);
    x2 = pixeltovp(xbutton, width);
    y2 = -1.0F * pixeltovp(ybutton, height);

    /* do specified dolly translation */
    if (rmNodeGetSceneCamera3D(camera3DTransformTarget,&c) != RM_WHACKED)
    {
	rmauxDolly(c, &static_pointerPrevX, &static_pointerPrevY, &x2, &y2);
	rmNodeSetSceneCamera3D(camera3DTransformTarget,c);
	rmCamera3DDelete(c);
    }
    static_pointerPrevX = x2;
    static_pointerPrevY = y2;

    /* redraw the frame */
    rmauxInvokeRenderFunc(p, private_rmauxGetCurrentSceneGraph());

    return(1);
}

int
rmauxShiftB2DownFunc RMAUX_BUTTON_CALLBACK_FUNCTION_PARMS()
{
    int w,h;
#ifdef RM_X
    XDefineCursor(rmxPipeGetDisplay(p),
		  rmPipeGetWindow(p),scale_cursor);
#endif

    rmPipeGetWindowSize(p,&w,&h);
    xscale_delta = 1.0F/(float)(w<<1);
    yscale_delta = 1.0F/(float)(h<<1);
    static_pointerPrevX = (float)xbutton;
    static_pointerPrevY = (float)ybutton;
    if (rmNodeGetScaleMatrix(geomTransformTarget,&saveScaleMatrix) == RM_WHACKED)
	rmMatrixIdentity(&saveScaleMatrix);

/* 8/7/06 wes. why calling this here?    (*renderfunc)(p, private_rmauxGetCurrentSceneGraph()); */
    
    return(1);
}

/*
 * rmauxShiftB2MotionFunc is called from rmauxEventLoop when a motion
 * event is detected while button2 and the shift key are depressed.
 * rmauxUI assigns rmauxB2UpFunc
 * to be the action handler for shift+button2+motion. applications may
 * override this by calling rmauxSetButtonDownFunc().
 *
 * since this routine is intended to be called only from within
 * rmauxEventLoop, it is not publicly documented.
 */
int
rmauxShiftB2MotionFunc RMAUX_BUTTON_CALLBACK_FUNCTION_PARMS()
{
    RMmatrix m;
    float xnew;

    rmMatrixIdentity(&m);

    xnew = (float)(xbutton) - static_pointerPrevX;
    xnew *= xscale_delta;
    m.m[0][0] = m.m[1][1] = m.m[2][2] = 1.0F + xnew;
    rmMatrixMultiply(&saveScaleMatrix,&m,&m);
    rmNodeSetScaleMatrix(geomTransformTarget,&m);

    /* foil compiler warning */
    ybutton = 0;
    
    return(1);
}

int
rmauxShiftB2UpFunc RMAUX_BUTTON_CALLBACK_FUNCTION_PARMS()
{
#ifdef RM_X
    XUndefineCursor(rmxPipeGetDisplay(p),
    		    rmPipeGetWindow(p));
#endif
    /* foil compiler warning */
    xbutton = ybutton = 0;
    return(1);
}

/* PRIVATE (rmauxEventLoop)
 *
 * convert from X11 style button enumerators to RM enumerators
 */
int
private_rmauxButtonNumToIndex (int xbutton_num)
{
    int rstat = 0;

    switch(xbutton_num)
    {
    case Button1:
	rstat = RM_BUTTON1;
	break;

    case Button2:
	rstat = RM_BUTTON2;
	break;

    case Button3:
	rstat = RM_BUTTON3;
	break;
	
    case Button4:
	rstat = RM_BUTTON4;
	break;
	
    case Button5:
	rstat = RM_BUTTON5;
	break;
    }
    return(rstat);
}


/* PRIVATE (rmauxEventLoop)
 *
 * return valid RM key modifier from X11 style button modifier state
 */
int
private_rmauxModifierToIndex (int state)
{
    int rstat = RM_NO_MODIFIER;
    
    if (state & RM_SHIFT_MODMASK)
	rstat = RM_SHIFT_MODIFIER;
    else if (state & RM_CONTROL_MODMASK)
	rstat = RM_CONTROL_MODIFIER;

    return(rstat);
}

void
private_rmauxSetCurrentSceneGraph(RMnode *n)
{
    static_sceneGraphHandle = n;
}

RMnode *
private_rmauxGetCurrentSceneGraph(void)
{
    return(static_sceneGraphHandle);
}

void
private_rmauxSetCameraResizeNode(RMnode *n)
{
    static_resizeCameraNode = n;
}

RMnode *
private_rmauxGetCameraResizeNode(void)
{
    return(static_resizeCameraNode);
}

static void
private_rmauxDoSpinCallback(void)
{
    RMmatrix m;
    rmNodeGetRotateMatrix(geomTransformTarget, &m);
    rmMatrixMultiply(&m, &spinMatrix, &m);
    rmNodeSetRotateMatrix(geomTransformTarget, &m);
}

/*
 * ----------------------------------------------------
 * @Name rmauxSetSpinEnable
 @pstart
 void rmauxSetSpinEnable (RMenum spinEnableBoolean)
 @pend

 @astart 

 RMenum spinEnableBoolean - may be either RM_TRUE or RM_FALSE. When set to
    RM_TRUE, "spin mode" will be enabled. When set to RM_FALSE, "spin mode"
    is disabled.
 @aend

 @dstart

 "Spin mode" refers to auto-rotation. With spin mode enabled, if the
 pointer is moving then you release RM_BUTTON2, then the RMnode "target"
 will be autorotated until such a time as you again press RM_BUTTON2.
 The direction and velocity of rotation is a function of how quickly
 the pointer is moving when you release RM_BUTTON2. Internally,
 spin mode is implemented by using an idle callback, so auto-spin will
 occur only when there are no events to process. The idle callback used
 to implement auto-rotation is independent of rmauxSetIdleCallbackFunc(), so
 you may have an idle function and auto-spin will still be operational.

 Auto-spins are applied to the transformation target set with either
 rmauxSetGeomTransform() or rmauxUI(). You may enable auto-spin without
 assigning a transformation target, but no auto-spin will occur unless
 you have established a transformation target.

 @dend
 * ----------------------------------------------------
 */
void
rmauxSetSpinEnable (RMenum spinEnableBool)
{
    if ((spinEnableBool != RM_TRUE) && (spinEnableBool != RM_FALSE))
    {
	rmWarning("rmauxSetSpinEnable() error: the input spinEnableBool parameter must be either RM_TRUE or RM_FALSE.");
	return;
    }

    if ((spinEnableBool == RM_TRUE) && (geomTransformTarget == NULL))
	rmWarning("rmauxSetSpinEnable() warning: you are enabling auto-spins when no transformation target has been established. While this is not an error, no auto-spin will occur unless you establish such a target with either rmauxSetGeomTransform() or rmauxUI(). \n");
    
    spinModeEnabled = spinEnableBool;
}

/*
 * ----------------------------------------------------
 * @Name rmauxGetSpinEnable
 @pstart
 RMenum rmauxGetSpinEnable (void)
 @pend

 @astart 

 @aend

 @dstart

 Returns either RM_TRUE or RM_FALSE, reflecting the current state
 of whether or not "auto spin" is enabled. See the description of
 rmauxSetSpinEnable for more details about auto-spin mode.

 @dend
 * ----------------------------------------------------
 */
RMenum
rmauxGetSpinEnable (void)
{
    return spinModeEnabled;
}

/* EOF */
