/*
 * 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: rmutil.c,v 1.11 2005/06/06 02:04:29 wes Exp $
 * Version: $Name: v180-alpha-02 $
 * $Revision: 1.11 $
 * $Log: rmutil.c,v $
 * Revision 1.11  2005/06/06 02:04:29  wes
 * Lots of small additions to clean up compiler warnings.
 *
 * Revision 1.10  2005/02/27 19:34:04  wes
 * Added support for application supplied texture object IDs and display lists.
 *
 * Revision 1.9  2005/02/19 16:40:20  wes
 * Distro sync and consolidation.
 * Repairs to fix memory leak associated with repeated calls to rmPipeNew,
 * rmPipeMakeCurrent, rmPipeClose.
 *
 * Revision 1.8  2005/01/23 17:00:22  wes
 * Copyright updated to 2005.
 *
 * Revision 1.7  2004/01/16 16:49:13  wes
 * Updated copyright line for 2004.
 *
 * Revision 1.6  2003/11/05 15:33:10  wes
 * Removed rmStartTimer and rmElapsedTime calls; these have been replaced
 * with a new family of rmTime() routines introduced in 1.5.1.
 *
 * Revision 1.5  2003/04/12 19:48:53  wes
 * Bug fix in RM_SOFTWARE 3D image resize operations.
 *
 * Revision 1.4  2003/04/08 02:45:33  wes
 * Implemented RM_SOFTWARE 3D image resizing (finally).
 *
 * Revision 1.3  2003/02/14 00:19:57  wes
 * Memset the context cache to zero upon initialization. Avoids a bug caused
 * by having garbage in uninitialized memory.
 *
 * Revision 1.2  2003/02/02 02:07:16  wes
 * Updated copyright to 2003.
 *
 * Revision 1.1.1.1  2003/01/28 02:15:23  wes
 * Manual rebuild of rm150 repository.
 *
 * Revision 1.11  2003/01/16 22:21:17  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.10  2002/09/05 15:07:39  wes
 * Changed "is a texture" test slightly.
 *
 * Revision 1.9  2002/08/29 22:20:32  wes
 *
 * Massive upgrade to accommodate dynamic object reallocation within
 * the component manager, and within the context cache. Use the
 * debug #define DEBUG_LEVEL DEBUG_REALLOC_TRACE to get a printf
 * whenever a realloc occurs. With this upgrade, there are no
 * OpenRM limits on the size of the scene graph. There will be external
 * limits, such as the amount of RAM and the amount of space available
 * to your OpenGL implementation.
 *
 * Revision 1.8  2002/08/17 15:14:36  wes
 * Changed reference of RM_COMPONENT_POOL_SIZE from a #define to a
 * global variable.
 *
 * Revision 1.7  2002/04/30 19:36:22  wes
 * Updated copyright dates.
 *
 * Revision 1.6  2001/10/15 03:08:40  wes
 * fix a compile warning.
 *
 * Revision 1.5  2001/07/15 17:20:23  wes
 * Added private routines that manage retained mode resources by
 * cleaning the context cache. These routines are draconian in approach,
 * and should be made more selective in future releases.
 *
 * Revision 1.4  2001/03/31 17:12:39  wes
 * v1.4.0-alpha-2 checkin.
 *
 * Revision 1.3  2000/12/03 22:35:38  wes
 * Mods for thread safety.
 *
 * Revision 1.2  2000/04/20 16:29:47  wes
 * Documentation additions/enhancements, some code rearragement.
 *
 * 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.
 *
 */

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

#ifdef RM_X
#include <sys/types.h>
#include <sys/time.h>
#endif 


/*
 * static mutex to protect cache key from simultaneous access by
 * multiple app threads. only one such mutex is needed, so keeping
 * it around as a static variable is ok as far as thread-safety goes.
 */
static RMmutex *cacheKeyMutex=NULL;

/*
 * ----------------------------------------------------
 * @Name rmNearestPowerOfTwo
 @pstart
 int rmNearestPowerOfTwo (int n)
 @pend

 @astart
 int n - an integer value.
 @aend

 @dstart

 This routine computes the integer that is the closest power of two to
 the input integer "n". The algorithm works only for non-negative
 input.

 This routine will come in handy if you have to scale an image so it's
 size is an even power of two.

 @dend
 * ----------------------------------------------------
 */
int
rmNearestPowerOfTwo (int n)
{
    int nbits, j;

    j = n;
    for (nbits = 0; j > 0;)
    {
	j = j >> 1;
	nbits++;
    }

    if (nbits != 0)
	nbits--;

    if ((1<<nbits) == n)
	return(n);
    else
    {
	int a, b, c;

	a = (1 << (nbits + 1));	/* 2^j which is greater than n */
	b = a - n;
	c = a - (a >> 1);
	c = c>>1;
	if (b > c)
	    return(1 << nbits);
	else
	    return(1 << (nbits + 1));
    }
}


/*
 * ----------------------------------------------------
 * @Name rmIntMagnitude 
 @pstart
 int rmIntMagnitude (int m)
 @pend

 @astart
 int m - an integer value.
 @aend

 @dstart

 This routine computes log2(m) for some integer m, and returns an
 integer. It is not an exact log2() replacement. It is useful for
 determining the position of the uppermost "on" bit in an integer.

 @dend
 * ----------------------------------------------------
 */
int
rmIntMagnitude (int m)
{
    int i = 0;

    while (m > 0)
    {
	m = m >> 1;
	i++;
    }
    if (i == 0)
	return(0);
    else
	return(i - 1);
}


/*
 * ----------------------------------------------------
 * @Name rmHSVtoRGB
 @pstart
 void rmHSVtoRGB (float hue,
	          float saturation,
		  float value,
		  float *redReturn,
		  float *greenReturn,
		  float *blueReturn)
 @pend

 @astart

 float hue, saturation, value - floating point values in the range
    [0..1] (input).

 float *redReturn, *greenReturn, *blueReturn - handles to floats. will
    contain floats in the range [0..1] (return). 
 @aend

 @dstart

 Convert a three-component pixel from HSV space to RGB space. Input
 hue is in the range 0..1, with 0 corresponding to 0 degrees on the
 HSV circle, and 1.0 corresponding to 360 degrees on the HSV
 circle. Saturation is in the range 0..1, where 0 is fully desaturated
 and 1 is fully saturated.  Value, or brightness, is in the range
 0..1. A brightness value of 0 is black (not very bright), and a value
 of 1.0 is full brightness.

 The results of the conversion are placed into caller-supplied memory.
 The return RGB values are also in the range 0..1, with 0 representing
 "full off" and 1 representing "full on."

 @dend
 * ----------------------------------------------------
 */
void
rmHSVtoRGB (float h,
	    float s,
	    float v,
	    float *r,
	    float *g,
	    float *b)
{
    int   i;
    float f, p, q, t;
    float tr = 0.0, tg = 0.0, tb = 0.0; /* inits satisfies gcc warning */
    float ht;
 
    /* (h,s,v) in [0..1] --> (r,g,b) will be in [0..1] - Foley & VanDam */
    ht = h;

    if (v == 0.0)
    {
	tr=0.0;
	tg=0.0;
	tb=0.0;
    }
    else
    {
	if (s == 0.0)
	{
	    tr = v;
	    tg = v;
	    tb = v;
	}
	else
	{
	    ht = ht * 6.0;
	    if (ht >= 6.0)
		ht = 0.0;
      
	    i = ht;
	    f = ht - i;
	    p = v * (1.0 - s);
	    q = v * (1.0 - s*f);
	    t = v * (1.0 - (s * (1.0 - f)));
      
 	    if (i == 0) 
	    {
		tr = v;
		tg = t;
		tb = p;
	    }
	    else if (i == 1)
	    {
		tr = q;
		tg = v;
		tb = p;
	    }
	    else if (i == 2)
	    {
		tr = p;
		tg = v;
		tb = t;
	    }
	    else if (i == 3)
	    {
		tr = p;
		tg = q;
		tb = v;
	    }
	    else if (i == 4)
	    {
		tr = t;
		tg = p;
		tb = v;
	    }
	    else if (i == 5)
	    {
		tr = v;
		tg = p;
		tb = q;
	    }
	}
    }
    *r = tr;
    *g = tg;
    *b = tb;
}


/*
 * ----------------------------------------------------
 * @Name rmRGBtoHSV
 @pstart
 void rmRGBtoHSV (float red,
	          float green,
		  float blue,
		  float *hueReturn,
		  float *saturationReturn,
		  float *valueReturn)
 @pend

 @astart
 float red, green, blue - floating point values in the range 0..1 that
    represent a 3-component RGB pixel.

 float *hueReturn, *saturationReturn, *valueReturn - handles to floats
    that will contain the HSV representation of the input RGB pixel
    upon return. The return values are in the range 0..1 (result).
 @aend

 @dstart

 Converts an RGB 3-tuple into HSV space.

 Output hue is in the range 0..1, with 0 corresponding to 0 degrees on
 the HSV circle, and 1.0 corresponding to 360 degrees on the HSV
 circle. Saturation is in the range 0..1, where 0 is fully desaturated
 and 1 is fully saturated.  Value, or brightness, is in the range
 0..1. A brightness value of 0 is black (not very bright), and a value
 of 1.0 is full brightness.

 @dend
 * ----------------------------------------------------
 */
void
rmRGBtoHSV (float rr,
	    float gg,
	    float bb,
	    float *hh,		/* return: 0 <= h <= 1 */
	    float *ss,		/* return: 0 <= s <= 1 */
	    float *vv)		/* return: 0 <= v <= 1 */
{
   double min, max, v, s, h, rc, gc, bc, r, g, b;

    /* (h,s,v) in [0..1] --> (r,g,b) will be in [0..1] - Foley & VanDam */
    r = rr;
    g = gg;
    b = bb;
    
    max = RM_MAX(r, g);
    max = RM_MAX(max, b);

    min = RM_MIN(r, g);
    min = RM_MIN(min, b);

    v = max;

    if (max != 0.0)
	s = (max - min) / max;
    else
	s = 0.0;

    if (s == 0)
      /* h = UNDEFINED_HUE pick something */
      h = 0.0;
    else
    {
	rc = (max - r) / (max - min);
	gc = (max - g) / (max - min);
	bc = (max - b) / (max - min);
	if (r == max)
	    h = bc - gc;
	else 
	   if (g == max)
	      h = 2 + rc - bc;
	else 
	   if (b == max)
	      h = 4 + gc - rc;
	h = h * 60;
	if (h < 0.0)
	    h = h + 360.0;
    }
    *hh = h / 360.0;
    *ss = s;
    *vv = v;
}


/*
 * ----------------------------------------------------
 * @Name rmUnionBoundingBoxes
 @pstart
 RMenum rmUnionBoundingBoxes (const RMvertex3D *s1min,
		              const RMvertex3D *s1max,
			      const RMvertex3D *s2min,
			      const RMvertex3D *s2max,
			      RMvertex3D *dmin,
			      RMvertex3D *dmax)
 @pend

 @astart
 const RMvertex3D *s1min, *s1max, *s2min, *s2max - handles to
    RMvertex3D objects (input).

 RMvertex3D *dmin, *dmax - handles to RMvertex3D objects (modified).
 @aend

 @dstart

 This routine performs a "union" operation of two, 3D boxes, returning
 the minimum and maximum coordinates of the resulting box into
 caller-supplied memory. Returns RM_CHILL upon success, or RM_WHACKED
 upon failure.

 @dend
 * ----------------------------------------------------
 */

RMenum
rmUnionBoundingBoxes (const RMvertex3D *s1min,
		      const RMvertex3D *s1max,
		      const RMvertex3D *s2min,
		      const RMvertex3D *s2max,
		      RMvertex3D *dmin,
		      RMvertex3D *dmax)
{
    if ((s1min == NULL) || (s1max == NULL) || (s2min == NULL) || (s2max == NULL) || (dmin == NULL) || (dmax == NULL))
    {
	rmError("rmUnionBoundingBoxes() error: one of the input parameters is NULL.");
	return(RM_WHACKED);
    }
    dmin->x = RM_MIN(s1min->x, s2min->x);
    dmin->y = RM_MIN(s1min->y, s2min->y);
    dmin->z = RM_MIN(s1min->z, s2min->z);
    
    dmax->x = RM_MAX(s1max->x, s2max->x);
    dmax->y = RM_MAX(s1max->y, s2max->y);
    dmax->z = RM_MAX(s1max->z, s2max->z);

    return(RM_CHILL);
}


/*
 * ----------------------------------------------------
 * @Name rmGLGetError
 @pstart
 int rmGLGetError (const char *string)
 @pend

 @astart
 const char *string - a handle to a string (input).
 @aend

 @dstart

 This routine is a wrapper for the routine glGetError(), and is used
 to obtain OpenGL error codes. If there are no errors, a value of zero
 is returned by rmGLGetError(). Otherwise, the error code is returned
 to the caller, and the error code, along with the message contained
 in the parameter "string" is reported via rmWarning().  Refer to gl.h
 in your OpenGL distribution for definitions of the OpenGL error
 codes, as well as the man page for glGetError().

 This routine was used extensively for debugging during the
 development of RM Scene Graph.

 @dend
 * ----------------------------------------------------
 */
int
rmGLGetError (const char *s)
{
    GLenum rstat = glGetError();

    while (rstat != GL_NO_ERROR)
    {
	if (rstat != 0)
	{
	    char buf[128];

	    sprintf(buf, "%s OpenGL error code (hex): 0x%04x ", s, rstat);
	    rmWarning(buf);
	}
	rstat = glGetError();
    }
    return(rstat);
}


/*
 * ----------------------------------------------------
 * @Name rmImageBuildMipmaps
 @pstart
 int rmImageBuildMipmaps (const RMimage *src,
		          RMimage ***mapsReturn,
			  RMenum hardwareEnum,
			  RMpipe *hwPipe)
 @pend

 @astart
 const RMimage *src - a handle to an RMimage object (input).

 RMimage ***mapsReturn - a pointer to an array of RMimage pointer (return).

 RMenum hardwareEnum - an RMenum value, may be either RM_HARDWARE or
    RM_SOFTWARE (input).

 RMpipe *hwPipe - a handle to an RMpipe (input). A valid RMpipe handle
    is required when RM_HARDWARE is requested via hardwareEnum. When
    RM_SOFTWARE image resizing for mipmap generation is requested,
    callers should specify NULL for the hwPipe parameter.
 @aend

 @dstart

 This convenience routine will generate a full set of mipmaps given an
 input image. The number of mipmaps generated is returned to the
 caller. This value will be positive and non-zero upon success,
 otherwise zero is returned.

 When RM_SOFTWARE is selected, RM will repeatedly call gluScaleImage
 to produce the mipmaps, a software operation. When RM_HARDWARE is
 set, the framebuffer itself is used to scale the image data. In some
 cases, RM_HARDWARE will run faster by an order of magnitude than
 RM_SOFTWARE.

 Inside this routine, an array of RMimage pointer is allocated. The
 number of entries in this array is log2(max(width,height)) of the
 input image, and is the number of mipmaps that will be
 generated. Then, each of these N images is created using
 RM_COPY_DATA, including the first image (which is a duplicate of the
 input image). The second image will be 1/4 the size of the first, and
 so forth down to a 1x1 image.

 At this time (January 2000), RM_HARDWARE occasionally has problems
 with the pixel reads on some framebuffers (esp. nVidia cards under
 Linux).

 At this time (January 2000), rmImageBuildMipmaps works ONLY on
 RMimage objects containing a two-dimensional image. There is no
 support at present for generating 3D mipmaps.

 At this time (January 2000), the scaled images are drawn to the
 framebuffer for debugging purposes, regardless of whether or not
 RM_SOFTWARE or RM_HARDWARE is specified by the caller.

 It is assumed that the input image is an even power of two in size.

 The following code snippet shows how to free the images returned by
 rmImageBuildMipmaps.

 <pre>
 RMimage  *source;
 RMimage **dstMipmaps;
 int       nMipmaps, i;

 ... stuff is assigned to source here (omitted) ...

 nMipmaps = rmImageBuildMipmaps(source, &dstMipmaps, RM_HARDWARE);

 .... do some stuff with the mipmaps (omitted) ...

 // delete the images
 for (i = 0; i < nMipmaps; i++)
     rmImageDelete(dstMipmaps[i]);
 free((void *)dstMipmaps);
 </pre>
 
 @dend
 * ----------------------------------------------------
 */
int
rmImageBuildMipmaps (const RMimage *src,
		     RMimage ***maps_return,
		     RMenum hardware_enum,
		     RMpipe *hwPipe)
{
    /* hack - right now, we can only compute mipmaps for 2D images */
    int nmm;

    if ((RM_ASSERT(src, "rmImageBuildMipMaps() error: the input RMimage object is NULL") == RM_WHACKED) ||
	(RM_ASSERT(src, "rmImageBuildMipMaps() error: the input maps_return pointer is NULL") == RM_WHACKED))
	return(0);

    if ((hardware_enum == RM_HARDWARE) && (hwPipe == NULL))
    {
	rmError("rmImageBuildMipmaps() error: a valid RMpipe must be specified when using RM_HARDWARE as the mipmap generation method. ");
	return(0);
    }

    if (private_rmImageGetNDims(src) == 2)
	nmm = private_rmImage2DBuildMipmaps(src, maps_return, hardware_enum, hwPipe);
    else
    {
	rmWarning(" mipmap generation for 3D images is currently under development.");
	nmm = 0;
    }

    return(nmm); /* number of mipmaps generated */
}


/* PRIVATE
 *
 * this internal routine is used to set up the view and projection
 * matrices, and a simple viewing mode for the purpose of drawing
 * image data into the framebuffer.
 *
 * October 2000 - rewrote to be thread safe.
 */
void
private_rmInitInternalImagingPipeline (RMpipe *p)
{
    int     w, h;

    /*
     * it may not be necessary to make the RMpipe "current", as the
     * application should have made the pipe current already - however,
     * we're taking a conservative approach. at worst, we incur the
     * cost of a call to glxMakeCurrent or wglMakeCurrent.
     */
    rmPipeMakeCurrent(p);

    /*
     * set matrix parameters for drawing images.
     */
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();

    glPixelZoom((GLfloat)1.0, (GLfloat)1.0);
    
    glDisable(GL_DEPTH_TEST);
    glDisable(GL_BLEND);

    rmPipeGetWindowSize(p, &w, &h);

    /* the glOrtho call needs some tweaking. */
    glOrtho(0.0, (double)(w - 1), 0.0, (double)(h - 1), -1.0, 1.0);
    
    glViewport(0, 0, w, h);
    
    glReadBuffer(GL_FRONT);
}


/* PRIVATE */
/*
 * 10/2000 - rewrote to be thread safe.
 */
int
private_rmImage2DBuildMipmaps (const RMimage *img,
			       RMimage ***maps_return,
			       int filter_method,
			       RMpipe *p)
{
    /*
     * TODO:
     *
     * we need to make some checks if we're using hardware for scaling:
     * 1. is the currently defined window large enough to handle our
     *    imaging requirements?
     * 2. if not, what do we do about it?
     *    a. fall back onto software
     *    b. resize the window to fit the problem? it may still be possible
     *       to exceed resources on the display with large images which
     *       won't fit into the window.
     */
    unsigned char *sdata;
    int            w, h, sw, sh;
    int            wmag, hmag;
    int            i, count;
    int            winw, winh;
    RMimage      **tt, *t;
    RMenum         rmformat, rmtype;
    GLenum         format, ptype;

    rmPipeGetWindowSize(p, &winw, &winh);

    w = private_rmImageGetWidth(img);
    h = private_rmImageGetHeight(img);

    sw = rmNearestPowerOfTwo(w);
    sh = rmNearestPowerOfTwo(h);

    /* need to check if sw,sh exceed the size of the current window.
     for now, we'll just report an error.  later we may want to take more
     positive action, such as temporarily resizing the window for use
     in imaging operations. */
    {
/*	fprintf(stderr,"rmImage2DBuildMipaps: need to update code to use pipe for window dims. \n"); */
	if ((sw > winw) || (sh > winh))
	{
	    char buf[512];
	    sprintf(buf, "%s %d by %d", " The current window is not large enough to accomodate texture resizing in hardware. Either increase the size of the window or decrease the size of the texture (NOTE: later versions of RM will temporarily resize the window). Parts of the resized texture may appear 'blacked out.' We need a window of at least \n", sw, sh);
	    rmWarning(buf);
	}
    }

    wmag = rmIntMagnitude(sw);
    hmag = rmIntMagnitude(sh);

    /* figure out how many images to create */
    count = RM_MAX(wmag, hmag) + 1;

    rmformat = private_rmImageGetFormat(img);
    rmtype = private_rmImageGetType(img);

    tt = *maps_return = (RMimage **)malloc(sizeof(RMimage *)*count);

    private_rmInitInternalImagingPipeline(p);

    sdata = private_rmImageGetPixelData(img);

    format = private_rmImageGetOGLFormat(img);
    ptype = private_rmImageGetOGLType(img);

    /*
     * note: format as returned by that routine isn't exactly correct
     * when the image type is one of the indexed types. for example, if
     * we have raw scalars, as is the case when we have indexed images,
     * that routine returns GL_RGB for RM_IMAGE_INDEXED_RGB. do we want
     * blat out raw scalars as, say, luminance or try to do something else?
     */
    glDrawBuffer(GL_FRONT);	/* ?? we could put this into the back buffer and the user would never see it..*/

    /* this draws the original image. not needed, but cute */
    glRasterPos2f(0.0,0.0);

    glDrawPixels(w, h, format, ptype, sdata);
    
    glFinish();
    
    for (i = 0; i < count; i++)
    {
	unsigned char *dest_data;
	int            tw, th;
	int            status;
	float          xzoom, yzoom;

	tw = RM_MAX((sw >> i), 1);
	th = RM_MAX((sh >> i), 1);
	
	tt[i] = t = rmImageNew(2, tw, th, 1, rmformat, rmtype, RM_COPY_DATA);

	if (i == 0)
	{
	    xzoom = (float)tw / (float)w;
	    yzoom = (float)th / (float)h;
	}
	else
	{
	    xzoom = 0.5;
	    yzoom = 0.5;
	}
	
	{
	    unsigned char *image_data;
	    int            lastw, lasth;

	    if (i == 0) 
	    {
		/* draw the original image, but with zoom  */
		image_data = sdata;
		lastw = w;
		lasth = h;
	    }
	    else
	    {
		rmImageGetImageSize(tt[i - 1], NULL, &lastw, &lasth, NULL, NULL, NULL);
		image_data = private_rmImageGetPixelData(tt[i - 1]);
	    }

	    dest_data = private_rmImageGetPixelData(t);
	    
	    if (filter_method == RM_HARDWARE)
	    {
		glPixelZoom((GLfloat)xzoom, (GLfloat)yzoom);
		glRasterPos2f(0.0, 0.0);
		glDrawPixels(lastw, lasth, format, ptype, (const void *)image_data);
		glFinish();

		glReadBuffer(GL_FRONT);

		if (format == GL_LUMINANCE)
	        {
		    glPixelTransferf(GL_RED_SCALE, (GLfloat)0.3);
		    glPixelTransferf(GL_RED_BIAS, (GLfloat)0.0);
		    glPixelTransferf(GL_GREEN_SCALE, (GLfloat)0.59);
		    glPixelTransferf(GL_GREEN_BIAS, (GLfloat)0.0);
		    glPixelTransferf(GL_BLUE_SCALE, (GLfloat)0.1);
		    glPixelTransferf(GL_BLUE_BIAS, (GLfloat)0.0);
		}

		/* read the zoomed image back into a local buffer */
		glReadPixels(0, 0, tw, th, format, ptype, dest_data);

		if (format == GL_LUMINANCE)
		{
		    glPixelTransferf(GL_RED_SCALE, (GLfloat)1.0);
		    glPixelTransferf(GL_GREEN_SCALE, (GLfloat)1.0);
		    glPixelTransferf(GL_BLUE_SCALE, (GLfloat)1.0);
		}
		glFinish();
	
	    }
	    else
	    {
		/* this uses the glu software image scaler rather than  the hardware imaging pipe */
		status = gluScaleImage(format, w, h, ptype, (const void *)(sdata), tw, th, ptype, (void *)(dest_data));
	    }
	}
	glPixelZoom((GLfloat)1.0, (GLfloat)1.0);

	glClear(GL_COLOR_BUFFER_BIT);

	glRasterPos2f(0.0, 0.0);
	glDrawPixels(tw, th, format, ptype, dest_data);

	glFinish();
    }

    return(count);
}


/* PRIVATE
 *
 * here is the guts of the 3D mipmap generator. it's not wired
 * in at this time, but has been used experimentally in the past.
 *
 * jan 2000 - i'm not sure what the status is of this routine at
 * this time...it probably needs to be rewritten.
 */
RMenum
private_rmImage3DResize (const RMimage *src,
			 RMimage *dst,
			 RMenum hardware_enum,
			 RMpipe *hwPipe)
{
    /*
     * 1. for each w x h slice of the source volume, use hardware to
     *    rescale the image to new size.
     * 2. read the pixels back from the framebuffer and dump them to
     *    a temp area (file? memory?)
     * 3. for each w x d slice of the intermediate volume, resize that
     *    image to the new size.
     * 4. read the pixels back from the framebuffer and stuff them
     *    into "dst"
     */

    /*
     * this routine used to have "scale" and "bias" as input parms. why?
     * todo: write software version.
     */

    unsigned char *src_data, *sdata;
    unsigned char *dest_data, *ddata;
    unsigned char *intermed_space; /* hold intermediate step in memory..hope this machine can swap or has a bunch of memory */
    unsigned char *idata;
    unsigned char *wbuf;	/* images of size dest_width * source_depth and is used in pass2 processing */
    unsigned char *lbuf;	/* images of size dst_width * dst_depth used to receive pass2 scaled data. */
    int            i, stride;
    int            sw, sh, sd;
    int            dw, dh, dd;
    float          scale = 1.0;
    float          bias = 0.0;
    float          xzoom, yzoom;
    GLenum         glformat, ptype;

    if ((RM_ASSERT(src, "private_rmImage3DResize() error: source RMimage pointer is NULL") == RM_WHACKED) ||
	(RM_ASSERT(dst, "private_rmImage3DResize() error: dest RMimage pointer is NULL") == RM_WHACKED))
	return(RM_WHACKED);

#if 0
    /* this should now be working */
    if (hardware_enum == RM_SOFTWARE)
	rmWarning("private_rmImage3DResize() warning: RM_SOFTWARE is not yet supported. using RM_HARDWARE for image resize.");
#endif

    rmImageGetImageSize(src, NULL, &sw, &sh, &sd, NULL, NULL);
    rmImageGetImageSize(dst, NULL, &dw, &dh, &dd, NULL, NULL);

    /* check to see if currently-defined window, etc. can accomodate
     the size of images that we're going to throw at it. */

    if (hardware_enum == RM_HARDWARE)
    {
	private_rmInitInternalImagingPipeline(hwPipe);
	glDrawBuffer(GL_FRONT);	/* ?? we could put this into the back */
    }

    /* loop over source depth */
    src_data = private_rmImageGetPixelData(src);
    stride = private_rmImageGetElements(src);
    glformat = private_rmImageGetOGLFormat(src);
    ptype = private_rmImageGetOGLType(src);

#if 0
    srcglformat = private_rmImageGetOGLFormat(src);
    dstglformat = private_rmImageGetOGLFormat(dst);
#endif
	    
    intermed_space = (unsigned char *)malloc(sizeof(unsigned char) * dw * dh * sd * stride);
    wbuf = (unsigned char *)malloc(sizeof(unsigned char) * dw * sd * stride);
    lbuf = (unsigned char *)malloc(sizeof(unsigned char) * dw * dd * stride);
    if ((intermed_space == NULL) || (wbuf == NULL) || (lbuf == NULL))
    {
	rmError(" private_rmImage3DResize..can't get memory for processing. aborting. \n");
	return(RM_WHACKED);
    }

    if (stride == -1)
    {
	rmError(" unknown image format in rmImage3DResize... aborting \n");
	return(RM_WHACKED);
    }

    xzoom = (float)dw / (float)sw;
    yzoom = (float)dh / (float)sh;

    for (i = 0; i < sd; i++)
    {
	sdata = src_data + (i * stride * sw * sh);
	idata = intermed_space + (i * stride * dw * dh);

	if (hardware_enum == RM_SOFTWARE)
	{
	    fake_gluScaleImage(glformat, sw, sh, ptype, sdata,
			       dw, dh, ptype, idata);
	}
	else
	{
	    glRasterPos2f(0.0, 0.0);
	    glPixelZoom((GLfloat)xzoom, (GLfloat)yzoom);
	    glDrawPixels(sw, sh, glformat, ptype, sdata);
	    
	    glReadBuffer(GL_FRONT);
	    
	    if (glformat == GL_LUMINANCE)
	    {
		glPixelTransferf(GL_RED_SCALE, (GLfloat)0.3 * scale);
		glPixelTransferf(GL_RED_BIAS, (GLfloat)bias);
		glPixelTransferf(GL_GREEN_SCALE, (GLfloat)0.59 * scale);
		glPixelTransferf(GL_GREEN_BIAS, (GLfloat)bias);
		glPixelTransferf(GL_BLUE_SCALE, (GLfloat)0.1 * scale);
		glPixelTransferf(GL_BLUE_BIAS, (GLfloat)bias);
	    }
	    else
	    {
		glPixelTransferf(GL_RED_SCALE, (GLfloat)scale);
		glPixelTransferf(GL_RED_BIAS, (GLfloat)bias);
		glPixelTransferf(GL_GREEN_SCALE, (GLfloat)scale);
		glPixelTransferf(GL_GREEN_BIAS, (GLfloat)bias);
		glPixelTransferf(GL_BLUE_SCALE, (GLfloat)scale);
		glPixelTransferf(GL_BLUE_BIAS, (GLfloat)bias);
	    }
	    
	    /* read the zoomed image back into a local buffer */
	    glPixelZoom((GLfloat)1.0f, (GLfloat)1.0F);
	    glReadPixels(0, 0, dw, dh, glformat, ptype, idata);
	    
	    if (glformat == GL_LUMINANCE)
	    {
		glPixelTransferf(GL_RED_SCALE, (GLfloat)1.0);
		glPixelTransferf(GL_GREEN_SCALE, (GLfloat)1.0);
		glPixelTransferf(GL_BLUE_SCALE, (GLfloat)1.0);
		
		glPixelTransferf(GL_RED_BIAS, (GLfloat)0.0);
		glPixelTransferf(GL_GREEN_BIAS, (GLfloat)0.0);
		glPixelTransferf(GL_BLUE_BIAS, (GLfloat)0.0);
	    }
	} /* end RM_HARDWARE */
    }

    dest_data = private_rmImageGetPixelData(dst);
    src_data = intermed_space;

    /* done with pass 1.  now do pass 2. */
    xzoom = 1.0;
    yzoom = (float)dd / (float)sd;

    if (hardware_enum == RM_HARDWARE)
	glPixelZoom((GLfloat)xzoom, (GLfloat)yzoom);
    
    for (i = 0; i < dh; i++)
    {
	int            id, tstride, wbufstride;
	unsigned char *wbuf2;
	
	/* load up temp image from holding buffer */
	sdata = src_data + (i * dw * stride);
	tstride = dw * dh * stride;

	wbuf2 = wbuf;
	wbufstride = dw * stride;
	for (id = 0; id < sd; id++)
        {
	    memcpy((wbuf2), (sdata), (sizeof(unsigned char) * dw * stride));
	    sdata += tstride;
	    wbuf2 += wbufstride;
	}

	/* now that we have a 2d image (representing a w x d slice of the
	 intermediate volume) blast it down the imaging pipeline */

	if (hardware_enum == RM_SOFTWARE)
	{
	    fake_gluScaleImage(glformat, dw, sd, ptype, wbuf,
			       dw, dd, ptype, lbuf);
	}
	else
	{
	    glPixelZoom((GLfloat)xzoom, (GLfloat)yzoom);
	    glRasterPos2f(0.0, 0.0);
	    glDrawPixels(dw, sd, glformat, ptype, wbuf);
	    
	    glReadBuffer(GL_FRONT);
	    
	    if (glformat == GL_LUMINANCE)
	    {
		glPixelTransferf(GL_RED_SCALE, (GLfloat)0.3 * scale);
		glPixelTransferf(GL_RED_BIAS, (GLfloat)bias);
		glPixelTransferf(GL_GREEN_SCALE, (GLfloat)0.59 * scale);
		glPixelTransferf(GL_GREEN_BIAS, (GLfloat)bias);
		glPixelTransferf(GL_BLUE_SCALE, (GLfloat)0.1 * scale);
		glPixelTransferf(GL_BLUE_BIAS, (GLfloat)bias);
	    }
	    else
	    {
		glPixelTransferf(GL_RED_SCALE, (GLfloat)scale);
		glPixelTransferf(GL_RED_BIAS, (GLfloat)bias);
		glPixelTransferf(GL_GREEN_SCALE, (GLfloat)scale);
		glPixelTransferf(GL_GREEN_BIAS, (GLfloat)bias);
		glPixelTransferf(GL_BLUE_SCALE, (GLfloat)scale);
		glPixelTransferf(GL_BLUE_BIAS, (GLfloat)bias);
	    }
	    
	    /* read the zoomed image back into a local buffer. */
	    glPixelZoom((GLfloat)1.0f, (GLfloat)1.0f);
	    glReadPixels(0, 0, dw, dd, glformat, ptype, lbuf);
	    
	    if (glformat == GL_LUMINANCE)
	    {
		glPixelTransferf(GL_RED_SCALE, (GLfloat)1.0);
		glPixelTransferf(GL_GREEN_SCALE, (GLfloat)1.0);
		glPixelTransferf(GL_BLUE_SCALE, (GLfloat)1.0);
	    
		glPixelTransferf(GL_RED_BIAS, (GLfloat)0.0);
		glPixelTransferf(GL_GREEN_BIAS, (GLfloat)0.0);
		glPixelTransferf(GL_BLUE_BIAS, (GLfloat)0.0);
	    }
	} /* end RM_HARDWARE */
	    
	/* now, stuff from lbuf into the final volume buffer */
	ddata = dest_data + (i * dw * stride);
	tstride = dw * dh * stride;
	wbufstride = dw * stride;
	wbuf2 = lbuf;

	for (id = 0; id < dd; id++)
	{
	    memcpy((void *)ddata, (void *)wbuf2, (sizeof(unsigned char) * dw * stride));
	    ddata += tstride;
	    wbuf2 += wbufstride;
	}
    }
    free((void *)lbuf);
    free((void *)wbuf);
    free((void *)intermed_space);

    return(RM_CHILL);
}

/* PRIVATE */
RMenum
private_rmInitCacheKeyMutex(void)
{
    cacheKeyMutex = rmMutexNew(RM_MUTEX_UNLOCK);
    if (cacheKeyMutex == NULL)
    {
	rmError("private_rmInitCacheKeyMutex() error: problem initializing cache key mutex. cache keys are not guaranteed to be unique in the presence of multiple threads.");
	return(RM_WHACKED);
    }
    return(RM_CHILL);
}

/* PRIVATE */
RMenum
private_rmDestroyCacheKeyMutex(void)
{
    rmMutexDelete(cacheKeyMutex);
    return RM_CHILL;
}

/* PRIVATE */
RMcacheKey
private_rmGetNewCacheKey(void)
{
    RMcacheKey rval;
    static unsigned int counter=0;
    /*
     * there's only one component manager, regardless of the
     * number of app threads. we get a cache key that is guaranteed
     * to be unique simply by protecting a counter with a mutex
     * to ensure caller access is serialized.
     */
    
    if (cacheKeyMutex != NULL)
	rmMutexLock(cacheKeyMutex);

    rval = counter;
    counter++;
    
    if (cacheKeyMutex != NULL)
	rmMutexUnlock(cacheKeyMutex);
    
    return(rval);
}

/* PRIVATE */
RMenum
private_rmCacheInit(RMcontextCache **toCreate)
{
    RMcontextCache *c;
    int nPrims, nImgs, nTextures;
    int i;
    
    c = (RMcontextCache *)malloc(sizeof(RMcontextCache));
    memset(c, 0, sizeof(RMcontextCache));

    /* prims, images, textures & associated cache keys */
    nPrims = NUM_ITEMS_PER_PAGE;
    c->primDisplayListIDs = (GLuint *)malloc(sizeof(GLuint)*nPrims);
    c->primCacheKeys = (RMcacheKey *)malloc(sizeof(GLuint)*nPrims);
    c->numPrimDisplayListIDs = nPrims;
    c->numPrimCacheKeys = nPrims;
    
    for (i=0;i<nPrims;i++)
    {
	c->primDisplayListIDs[i] = -1;
	c->primCacheKeys[i] = RM_MAKE_CACHEKEY(-1);
    }
    
    nImgs = NUM_ITEMS_PER_PAGE;
    c->imgDisplayListIDs = (GLuint *)malloc(sizeof(GLuint)*nImgs);
    c->imgCacheKeys = (RMcacheKey *)malloc(sizeof(GLuint)*nImgs);
    c->numImgDisplayListIDs = nImgs;
    c->numImgCacheKeys = nImgs;
    
    for (i=0;i<nImgs;i++)
    {
	c->imgDisplayListIDs[i] = -1;
	c->imgCacheKeys[i] = RM_MAKE_CACHEKEY(-1);
    }
    
    nTextures = NUM_ITEMS_PER_PAGE;
    c->textureIDs = (GLuint *)malloc(sizeof(GLuint)*nTextures);
    c->textureIDCacheKeys = (RMcacheKey *)malloc(sizeof(GLuint)*nTextures);
    c->textureDataCacheKeys = (RMcacheKey *)malloc(sizeof(GLuint)*nTextures);
    c->numTextureIDs = nTextures;
    c->numTextureIDCacheKeys = nTextures;
    c->numTextureDataCacheKeys = nTextures;
    
    for (i=0;i<nTextures;i++)
    {
	c->textureIDs[i] = -1;
	c->textureIDCacheKeys[i] = RM_MAKE_CACHEKEY(-1);
	c->textureDataCacheKeys[i] = RM_MAKE_CACHEKEY(-1);
    }

    /* font registry */
    c->pipeFontRegistry = private_rmFontRegistryNew();

    *toCreate = c;
    return (RM_CHILL);
}

/* PRIVATE */
RMenum
private_rmCacheDelete(RMpipe *p,
		      RMcontextCache *toDelete)
{
    RMcontextCache *c;
    
    c = toDelete;

    private_rmCacheDeleteAllPrimitiveDisplayLists(c);
    private_rmCacheDeleteQuadrics(c);
    private_rmCacheDeleteAllImageDisplayLists(c);
    private_rmCacheDeleteAllTextures(c);
    
    /* prims, images, textures & associated cache keys */
    free((void *)(c->primDisplayListIDs));
    free((void *)(c->primCacheKeys));
    
    free((void *)(c->imgDisplayListIDs));
    free((void *)(c->imgCacheKeys));
    
    free((void *)(c->textureIDs));
    free((void *)(c->textureIDCacheKeys));
    free((void *)(c->textureDataCacheKeys));
    
    /* font registry */
    private_rmFontRegistryDelete(p, c->pipeFontRegistry);

    free((void *)c);
    return (RM_CHILL);
}

/* PRIVATE */
void
private_rmCacheFlush(RMcontextCache *toDelete)
{
    extern RMcompMgrHdr *global_RMimagePool, *global_RMprimitivePool;
    extern RMcompMgrHdr *global_RMtexturePool;
    RMcontextCache *c;

    int nPrims, nImages, nTextures,i;

    /*
     * we could store the number of active prims, images & textures
     * in the RMpipe, but don't since our goal is to effectively
     * hide the many-pipes/renderers to one scene graph metaphor.
     */

    /*
     * temp hack - we should traverse the pseudo linked list
     * in each RMcompMgrHdr structure to visit only RMprims,
     * RMimages and RMtextures that are resident in the scene graph.
     * what we do instead is charge through the entire pool of
     * all such objects. 
     */
    nPrims = global_RMprimitivePool->currentPoolSize;
    nImages = global_RMimagePool->currentPoolSize;
    nTextures = global_RMtexturePool->currentPoolSize;
    
    /* first, release resources associated with any existing display
     lists or textures. */

    /*
     * Implementation/efficiency note: it may be the case that
     * some OpenGL implementations don't recycle display list/
     * texture IDs, in which case, it would be possible to "run out"
     * after some number of cache flushes/rebuilds. We may want
     * to modify our flush strategy by *not* deleting display
     * lists and textures, and simply rebuilding them the next
     * time the i'th RMprimitive, RMimage and RMtexture needs
     * to have retained mode structures rebuilt.
     */

    c = toDelete;
    if (RM_ASSERT(c,"private_rmCacheFlush() error: the input context cache is NULL!") == RM_WHACKED)
	return;
    

    /*
     * each of these loops releases any OpenGL resources that might
     * be used. note that we're re-initializing the entries to
     * default values that mean "empty", then just throwing the
     * memory away a little bit later. gotta do something with
     * those gigahertz CPUs.
     */
    for (i=0;i<nPrims;i++)
    {
	if (glIsList(c->primDisplayListIDs[i]) == GL_TRUE)
	    glDeleteLists(c->primDisplayListIDs[i], 1);
	
	c->primDisplayListIDs[i] = -1;
	c->primCacheKeys[i] = RM_MAKE_CACHEKEY(-1);
    }
    
    for (i=0;i<nImages;i++)
    {
	if (glIsList(c->imgDisplayListIDs[i]) == GL_TRUE)
	    glDeleteLists(c->imgDisplayListIDs[i], 1);
	
	c->imgDisplayListIDs[i] = -1;
	c->imgCacheKeys[i] = RM_MAKE_CACHEKEY(-1);
    }
    
    for (i=0;i<nTextures;i++)
    {
	if (glIsTexture(c->textureIDs[i]) == GL_TRUE)
	    glDeleteTextures(1,&(c->textureIDs[i]));
	
	c->textureIDs[i] = -1;
	c->textureIDCacheKeys[i] = RM_MAKE_CACHEKEY(-1);
	c->textureDataCacheKeys[i] = RM_MAKE_CACHEKEY(-1);
    }

    /* now, free up the memory associated with the cache keys */
    free((void *)c->primDisplayListIDs);
    free((void *)c->primCacheKeys);
    
    free((void *)c->imgDisplayListIDs);
    free((void *)c->imgCacheKeys);
    
    free((void *)c->textureIDs);
    free((void *)c->textureIDCacheKeys);
    free((void *)c->textureDataCacheKeys);

    free((void *)c);
}

/* PRIVATE */
RMenum
private_rmCacheDeleteAllPrimitiveDisplayLists(RMcontextCache *c)
{
    int i;
    /*
     * this routine will delete all display lists associated with 
     * RMprimitive's in the RMpipe's context cache.
     */

    /*
     * 7/4/01 w.bethel
     * the following code makes simplifying assumptions that
     * result in overkill:
     * 1. RM_COMPONENT_POOL_SIZE is the init size of the context
     *    cache, not the real size as the component mgr pool grows
     *    (which isn't yet implemented anyway).
     * 2. the following loop serially examines *all* primDisplayListIDs
     *    rather than looking at only a subset. this is more expensive than
     *    alternative approaches, but will do a more thorough job of
     *    cleansing display lists across potentially disconnected
     *    scene graph nodes.
     * 3. this routine assumes that you really want to blow away all
     *    the display lists owned by an RMpipe.
     *
     * 8/29/02 - cleaned up code to accommodate dynamic object reallocs.
     */
    
    for (i=0; i < c->numPrimDisplayListIDs; i++)
    {
	GLuint list = c->primDisplayListIDs[i];
	
	if ((list != (unsigned int)(-1)) && (glIsList(list)==GL_TRUE))
	{
	    glDeleteLists(list,1);
	    c->primDisplayListIDs[i] = -1;
	    c->primCacheKeys[i] = RM_MAKE_CACHEKEY(-1);
	}
    }
    return RM_CHILL;
}

/* PRIVATE */
RMenum
private_rmCacheDeleteAllTextures(RMcontextCache *c)
{
    int i;
    /*
     * this routine will delete all texture object id's associated with 
     * the RMpipe's context cache.
     */

    /*
     * 7/4/01 w.bethel
     * the following code makes simplifying assumptions that
     * result in overkill:
     * 1. RM_COMPONENT_POOL_SIZE is the init size of the context
     *    cache, not the real size as the component mgr pool grows
     *    (which isn't yet implemented anyway).
     * 2. the following loop serially examines *all* textureIDs
     *    rather than looking at only a subset. this is more expensive than
     *    alternative approaches, but will do a more thorough job of
     *    cleansing textures across potentially disconnected
     *    scene graph nodes.
     * 3. this routine assumes that you really want to blow away all
     *    the textures owned by an RMpipe.
     *
     * 8/29/02 - wes. cleaned up to accommodate dynamic object reallocs.
     */

    for (i=0; i < c->numTextureIDs; i++)
    {
	GLuint objID = c->textureIDs[i];
	
	if ((objID != (GLuint)(0)) && (glIsTexture(objID)==GL_TRUE))
	{
	    glDeleteTextures(1, &objID);
	    c->textureIDs[i] = -1;
	    c->textureIDCacheKeys[i] = RM_MAKE_CACHEKEY(-1);
	    c->textureDataCacheKeys[i] = RM_MAKE_CACHEKEY(-1);
	}
    }
    return RM_CHILL;
}

/* PRIVATE */
RMenum
private_rmCacheDeleteAllImageDisplayLists(RMcontextCache *c)
{
    int i;
    /*
     * this routine will display list id's associated with 
     * RMimages in the RMpipe's context cache.
     */

    /*
     * 7/4/01 w.bethel
     * the following code makes simplifying assumptions that
     * result in overkill:
     * 1. RM_COMPONENT_POOL_SIZE is the init size of the context
     *    cache, not the real size as the component mgr pool grows
     *    (which isn't yet implemented anyway).
     * 2. the following loop serially examines *all* RMimage display list ids
     *    rather than looking at only a subset. this is more expensive than
     *    alternative approaches, but will do a more thorough job of
     *    cleansing display list ids across potentially disconnected
     *    scene graph nodes.
     * 3. this routine assumes that you really want to blow away all
     *    the textures owned by an RMpipe.
     *
     * 8/29/02 - wes. updated to accommodate dynamic object reallocs.
     */

    for (i=0; i < c->numImgDisplayListIDs; i++)
    {
	GLuint list = c->imgDisplayListIDs[i];
	
	if ((list != (unsigned int)(-1)) && (glIsList(list)==GL_TRUE))
	{
	    glDeleteLists(list,1);
	    c->imgDisplayListIDs[i] = RM_MAKE_CACHEKEY(-1);
	    c->imgCacheKeys[i] = (RMcacheKey)(-1);
	}
    }
    return RM_CHILL;
}

/* PRIVATE */
int
private_rmCacheComputeNumberNewPages(int oldNItems,
				     int numItemsPerPage,
				     int newIndx)
{
    /*
     * input: oldNItems - says how many items we have already,
     *        nItemsPerPage - number of items per page in realloc
     *        newIndx - index, or "item number" to meet or exceed.
     *
     * compute and return: number of pages, at newItemsPerPage, must be
     * allocated in order to accommodate an items positioned at "newIndx."
     */
    int i = oldNItems / numItemsPerPage;
    int newTop = oldNItems;

    while (newIndx >= newTop)
    {
	newTop += numItemsPerPage;
	i++;
    }
    return i; /* return a count of new pages needed */
}

/* EOF */

