/*
 * 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: rmtxture.c,v 1.13 2005/06/06 02:04:29 wes Exp $
 * Version: $Name: v180-alpha-02 $
 * $Revision: 1.13 $
 * $Log: rmtxture.c,v $
 * Revision 1.13  2005/06/06 02:04:29  wes
 * Lots of small additions to clean up compiler warnings.
 *
 * Revision 1.12  2005/05/07 15:40:42  wes
 * Remove error message indicating 1D texture downloads not tested.
 *
 * Revision 1.11  2005/05/06 16:33:49  wes
 * Update texture state/env manipulation for 1D textures. Update
 * documentation to reflect that 1D textures are supported.
 *
 * 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:43:48  wes
 * Distro sync and consolidation.
 * Remove dead and test code.
 *
 * Revision 1.8  2005/01/23 17:08:25  wes
 * Copyright updated to 2005.
 * Updates to support access and use of extensions; multitexturing on all
 * platforms, use of 3d texture extension to realize implementation of
 * volume rendering and related functions on Windows platforms.
 *
 * Revision 1.7  2004/01/16 16:49:13  wes
 * Updated copyright line for 2004.
 *
 * Revision 1.6  2003/11/22 00:53:41  wes
 * Changes to support RMtextures being shared when assigned as scene parameters
 * to properly implement instancing.
 *
 * Revision 1.5  2003/06/14 03:17:20  wes
 * Minor documentation tweaks.
 *
 * Revision 1.4  2003/03/16 21:56:16  wes
 * Documentation updates.
 *
 * Revision 1.3  2003/02/14 00:19:25  wes
 * No significant changes.
 *
 * 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.16  2003/01/27 05:04:42  wes
 * Changes to RMpipe API and initialization sequence to unify GLX, WGL and CR
 * platforms w/o too much disruption to existing apps.
 *
 * Revision 1.15  2003/01/20 05:39:49  wes
 * Rewrote texture state handling code.
 *
 * Revision 1.14  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.13  2003/01/12 23:50:06  wes
 * Minor adjustments to texturing environment controls to fix problems with
 * the texture environment not being set correctly.
 *
 * Revision 1.12  2002/11/14 15:34:51  wes
 * Minor editing for beautification.
 *
 * Revision 1.11  2002/09/17 14:18:30  wes
 * Conditional code added to rmTextureSetImages() that will do a more
 * conservative job of deleting old mipmaps when new images are
 * assigned to an RMtexture.
 *
 * Revision 1.10  2002/04/30 19:36:03  wes
 * Added support for non-byte texture data for 3D textures. All RM
 * image formats are now supported for 3D textures.
 *
 * Revision 1.9  2001/06/03 20:50:48  wes
 * Removed dead code.
 *
 * Revision 1.8  2001/03/31 17:12:39  wes
 * v1.4.0-alpha-2 checkin.
 *
 * Revision 1.7  2000/12/05 03:04:53  wes
 * Cosmetic changes.
 *
 * Revision 1.6  2000/12/04 00:42:35  wes
 * Minor tweaks to eliminate compile warnings.
 *
 * Revision 1.5  2000/12/03 22:35:27  wes
 * Mods for thread safety.
 *
 * Revision 1.4  2000/08/22 16:19:36  wes
 * Moved code that turns off texturing to "the right place" after
 * textures have been downloaded.
 *
 * Revision 1.3  2000/05/17 14:19:19  wes
 * Removed #ifdef MESA from around the glIsTexture call - this crash
 * bug was fixed in the nVidia driver released on 1 May 2000.
 *
 * 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"

/*
 * ----------------------------------------------------
 * @Name rmTextureNew
 @pstart
 RMtexture * rmTextureNew (int ntdims)
 @pend

 @astart
 int ntdims - an integer value specifying whether the new texture
    object will be a 1D, 2D or 3D texture. The value for this parameter
    must be one of 1, 2 or 3.
    (input).
 @aend

 @dstart

 Creates a new RMtexture object, returning a handle to the caller upon
 success, or NULL upon failure.

 Texture mapping in RM is accomplished by assigning an RMtexture
 object to an RMnode as a scene parameter (see rmNodeSetSceneTexture).
 Primitives that have texture coordinates (assigned with one of
 rmPrimitiveSetTexcoord1D, rmPrimitiveSetTexcoord2D, or
 rmPrimitiveSetTexcoord3D) that are located within any descendent
 nodes, including the node containing the RMtexture object, that has
 an RMprimitive with texture coordinates, will be rendered with
 texture mapping active. The presence of the RMtexture object causes
 texture mapping to become active, and texture coordinates cause
 texture mapping to occur once active.

 The RMtexture object is a container for both texture images
 (rmTextureSetImages) and texturing environment, so it encompasses
 both the OpenGL texture object as well as the OpenGL texturing
 environment. The texturing environment consists of wrap mode
 (rmTextureSetWrapMode), any texture filtering that occurs when
 textures get small or large (rmTextureSetFilterMode), selection of
 one of several environmental modes that control how the texture color
 is blended with the pixel fragment color (rmTextureSetEnv),
 manipulation over how the texture is physically represented in the
 OpenGL pipeline (rmTextureSetGLTexelFormat).

 Upon creation, rmTextureNew sets the following defaults all texture objects:

 1. Texturing environment (blending) set to GL_MODULATE
 (rmTextureSetEnv).

 2. Wrap mode is set to GL_CLAMP (rmTextureSetWrapMode).

 4. Texel storage format is GL_RGBA. Can be overridden by a subsequent
 call to rmTextureGLSetTexelFormat().

 For the texture filtering mode, 3D textures are assigned GL_NEAREST for
 maximum speed, while 1D and 2D textures are assigned the value of
 GL_LINEAR for best quality.

 Before assigning an RMtexture object to an RMnode as a scene
 parameter with rmNodeSetSceneTexture, the application must populate
 the RMtexture object with RMimage object(s) that contain the texture
 data.

 @dend
 * ----------------------------------------------------
 */
RMtexture *
rmTextureNew (int ntdims)
{
    int i;
    int save;
    
    RMtexture *t = private_rmTextureNew();
    save = t->compListIndx;
    memset(t,0,sizeof(RMtexture));
    t->compListIndx = save;

    /*
     * initialize the struct (formerly done with bzero).
     */
    for (i=0;i<RM_MAX_MIPMAPS;i++)
	t->images[i] = NULL;

    t->nmipmaps = 0;
    
    t->blendColor = NULL;

    /* OpenGL texel format is initially left unspecified. the default
     * behavior will be to ask for GL_RGBA as the OpenGL texel format.
     * the default can be overridden by the application by calling
     * rmTextureGLSetTexelFormat().
     */
    rmTextureSetGLTexelFormat(t, GL_RGBA);

    /*
     * 11/21/03 - initialize the cache ID and data keys to a known value
     */
    t->cacheKeyID = RM_CACHEKEY_UNINITIALIZED_VALUE;
    t->cacheKeyData = RM_CACHEKEY_UNINITIALIZED_VALUE;

    /*
     * 11/21/03 - initialize the reference count to 0. 
     */
    private_rmTextureSetRefCount(t, 0);
    
    /* now fill in caller-supplied info. */

    t->dims = ntdims;

    /* set the default filtering modes */
    switch (ntdims)
       {
       case 3: /* 3D textures: use GL_NEAREST for speed */
	  rmTextureSetFilterMode(t, GL_NEAREST, GL_NEAREST);
	  break;
	  
       default:
	  /*
	   * for all non 3D textures, use GL_LINEAR and pals for best images.
	   * apps that use mipmaps will probably want to use
	   * GL_LINEAR_MIPMAP_LINEAR for better images.
	   */
	  rmTextureSetFilterMode(t, GL_LINEAR, GL_LINEAR);
	  break;
       }

    /* set the default wrap mode */
    rmTextureSetWrapMode(t, GL_CLAMP);

    /* set the default texture environment mode - it determines how texels
     are combined with existing fragments*/
    rmTextureSetEnv(t, GL_MODULATE, NULL);
    
    return(t);
}


/*
 * ----------------------------------------------------
 * @Name rmTextureDelete
 @pstart
 RMenum rmTextureDelete (RMtexture *toDelete,
		         RMenum deleteImagesBool)
 @pend

 @astart
 RMtexture *toDelete - a handle to an RMtexture object to delete.

 RMenum deleteImagesBool - an RMenum value, either RM_TRUE or
    RM_FALSE, that indicates whether or not to delete the underlying
    RMimage data.
 @aend

 @dstart

 Use this routine to free an RMtexture object when no longer
 needed. At this time (Jan 2000), it is the applications
 responsibility to dictate whether or not the underlying RMimage
 texture data will be deleted. This control may become obsolete with
 the addition of reference counting at the RMimage level, but this
 feature is not implemented at this time.

 When the application specifies RM_TRUE for "deleteImagesBool", the
 underlying RMimage data will be deleted (using rmImageDelete). If the
 underlying RMimage's share management of the pixel data with the
 application (specified using RM_DONT_COPY_DATA with
 rmImageSetPixelData), the application-supplied callback will be
 invoked at this time to release the image pixel data.

 This routine also deletes the underlying OpenGL texture object.
 
 @dend
 * ----------------------------------------------------
 */
RMenum
rmTextureDelete (RMtexture *t,
		 RMenum delete_images_bool)
{
    if (RM_ASSERT(t, "rmTextureDelete() error: the input texture object is NULL. \n") == RM_WHACKED)
	return(RM_WHACKED);

    /* 11/21/03 - don't permit texture deletion if the RMtexture is used
     as a shared texture scene parameter at some RMnode. */
    if (t->refCount > 0) 
    {
#if 0
	rmWarning("rmTextureDelete() warning: cannot delete the texture because its reference count is > 0. That means that it is being used as a scene parameter by some RMnode.");
#endif
	return RM_WHACKED;
    }
    
    /* free the texture images? */
    if (delete_images_bool == RM_TRUE)
    {
	int      i;
	RMimage *ri;
	
	for (i = 0; i < private_rmTextureGetNMipMaps(t); i++)
	{
	    ri = t->images[i];
	    rmImageDelete(ri);
	}
    }

#if 0
    /* we need an RMpipe to properly delete an OpenGL texture.
     at this point (10/2000) we have no way to explicitly remove
     OpenGL textures once they have been created, except via
     rmFinish(). */
    
    /* release opengl resources - this is deprecated code */
    if (glIsTexture(t->tname) == GL_TRUE)
	glDeleteTextures(1, (GLuint *)&(t->tname));
#endif
    
    /* finally, delete the texture itself */
    private_rmTextureDelete(t);
    
    return(RM_CHILL);
}


/*
 * ----------------------------------------------------
 * @Name rmTextureSetImages
 @pstart
 RMenum rmTextureSetImages (RMtexture *toModify,
		            RMimage **imageArray,
			    int nimages,
			    int borderWidth)
 @pend


 @astart
 RMtexture *toModify - a handle to the RMtexture object to which image
    texture data will be assigned (modified).

 RMimage **imageArray - a handle to an array of RMimage objects that
    will be assigned to the RMtexture object as texture data. This
    list will not be modified by this routine.

 int nimages - an integer value indicating both the length or size of
    the imageArray parameter.

 int borderWidth - an integer value specifying the texture border
    width. May be either 1 or 0.
 @aend

 @dstart

 Use this routine to assign texture image data to an RMtexture object.
 Returns RM_CHILL upon success, or RM_WHACKED upon failure.

 In OpenGL, all images that are used as textures MUST BE and even
 power of 2 in size, although image width and height need not be
 equal. Thus, one may have image data which is 512 pixels wide by 64
 pixels high.  Use rmImageResize or gluScaleImage to resize an image
 if needed to honor the power-of-two size restriction.

 One or more images may be assigned to a single texture object. The
 practice of assigning multiple images to a single texture object is
 called "Mip-mapping." The basic idea behind mip-mapping is that when
 a single texel (pixel from the texture data) covers very few screen
 pixels, one can use a low-resolution version of the texture data
 since the high-frequency texture components are lost by the
 rasterization process. When a single texel covers many screen pixels,
 a high-resolution version of the texture data is used in order to
 preserve, as much as possible, the high-frequency component of the
 texture data.

 While mip-map construction is both a science and an art, for the most
 part, applications may simply use scaled down versions of a
 high-resolution texture for each mipmap level.

 When mipmapping is used (nImages > 1), the "proper" number of mipmaps
 to create and use is 1+log2(largest dimension size of source
 texture). So, if the original texture is 256x64, a total of 8 images
 are needed to make a complete mipmap. The eight images will be sized:
 256x64 (the original image), 128x32, 64x16, 32x8, 16x4, 8x2, 4x1,
 2x1, 1x1 for a total of 9 images (log2(256)+1). In other words, you
 must specify all images that are sized from the original image down
 to a 1x1 representation.

 Inside this routine, each image of the RMimage array is COPIED into
 the texture object using the routine rmImageDup. A fine point to be
 aware of is the fact that rmImageDup does not necessarily duplicate
 the underlying pixel data, so duplication of the imageArray does not
 necessarily result in a significant use of memory. Whether or not the
 image pixel data is copied is a function of whether or not the pixel
 data is assigned to the RMimage using RM_COPY_DATA or
 RM_DONT_COPY_DATA.

 The border width parameter (either 0 or 1) indicates whether or not
 the "edge" pixels of the texture image data are to be treated as
 border pixels. Above, we mentioned that OpenGL textures must be an
 even power of two in size. More precisely, for a two-dimensional
 texture, the size of the input image data is (2^n + 2b, 2^m + 2b)
 where n,m are positive integers, and "b" is the borderWidth
 parameter. The treatment of border pixels is more fully described in
 the OpenGL Red Book.

 Note that the borderWidth parameter applies to ALL images in a set of
 Mipmaps, not just the first image.
 
 @dend
 * ----------------------------------------------------
 */
RMenum
rmTextureSetImages (RMtexture *t,
		    RMimage **imagearray,
		    int nimages,
		    int borderWidth)
{
    int i;

    if (RM_ASSERT(t, "rmTextureSetImages() error: the input texture is NULL.") == RM_WHACKED)
	return(RM_WHACKED);

    /* first, free up any existing RMimage objects */
    for (i = 0; i < private_rmTextureGetNMipMaps(t); i++)
    {
	RMimage *img;
	img = t->images[i];
	if (img != NULL)
	    rmImageDelete(img);
	t->images[i] = NULL;
    }
	    
    private_rmTextureSetNMipMaps(t, nimages);
    
    t->borderWidth = borderWidth;

    for (i = 0; i < private_rmTextureGetNMipMaps(t); i++)
    {
	RMimage *img;
	img = rmImageDup(imagearray[i]);
	t->images[i] = img;
    }

    private_rmTextureSetDataCacheKey(t);

    return(RM_CHILL);
}


/*
 * ----------------------------------------------------
 * @Name rmTextureGetImages
 @pstart
 RMenum rmTextureGetImages (RMtexture *toQuery,
		            RMimage **imageArray,
			    int *nimages,
			    int *borderWidth)
 @pend

 @astart
 RMtexture *toQuery - a handle to the RMtexture object to which image
    texture data will be assigned (input).

 RMimage **imageArray - a handle to an array of RMimage objects that
    will be assigned to the RMtexture object as texture data. This
    list will not be modified by this routine.

 int *nimages, *borderWidth - handles to an integers (results).
 @aend

 @dstart

 This routine is currently only a stub.

 @dend
 Use this routine to read back the textures assigned to an RMtexture
 object.
 
 Upon successful completion, RM_CHILL is returned and the number of
 mipmaps, border width, and copies of the actual texture images are
 returned to the caller.  Otherwise, RM_WHACKED is returned.

 * ----------------------------------------------------
 */
RMenum
rmTextureGetImages (const RMtexture *t,
		    RMimage ***imagearray,
		    int *nimages,
		    int *borderWidth)
{
    /* stub - implement! */
    rmWarning("rmTextureGetImages is not yet implemented. \n");

    /* foil compiler warning */
    t = NULL;
    imagearray = NULL;
    nimages = borderWidth = NULL;
    
    return(RM_CHILL);
}


/*
 * ----------------------------------------------------
 * @Name rmTextureSetFilterMode
 @pstart
 RMenum rmTextureSetFilterMode (RMtexture *toModify,
		                GLenum minMode,
			        GLenum magMode)
 @pend

 @astart
 RMtexture *toModify - handle to an RMtexture object (modified).

 GLenum minMode - a GLenum value specifying a texture minification
    filter. May be one of GL_NEAREST, GL_LINEAR,
    GL_NEAREST_MIPMAP_NEAREST, GL_NEAREST_MIPMAP_LINEAR,
    GL_LINEAR_MIPMAP_NEAREST, GL_LINEAR_MIPMAP_LINEAR.

 GLenum magMode - a GLenum value specifying a texture magnification
    filter.  May be one of GL_NEAREST or GL_LINEAR.
 @aend

 @dstart

 Use this routine to set the texture filtering mode at of an RMtexture
 object. Returns RM_CHILL upon success, or RM_WHACKED upon
 failure. Note that the filter does not take effect until the texture
 object has been placed into the scene graph using either
 rmNodeSetSceneTexture or rmNodeUpdateSceneTexture.

 When texels project to screen pixels, they rarely cover an integral
 number of pixels. The texture filtering mode is used to control how
 texels are interpolated to screen pixels. When a screen pixel covers
 a very small portion of the texture, the texture is said to be
 magnified. Conversely, when the screen pixel covers multiple texels,
 the texture is said to be minified.

 By default, the RMtexture object uses the minification and
 magnification filters of GL_NEAREST, GL_NEAREST, respectively. These
 filters favor performance over quality. The minification filter
 modes, GL_NEAREST, GL_LINEAR, ... , GL_LINEAR_MIPMAP_LINEAR represent
 increasing levels of interpolation quality, but at increasing
 performance cost. Only two magnification filters, GL_NEAREST and
 GL_LINEAR, are available inside OpenGL. Please see the OpenGL red
 book for more information about texture filtering.
 
 @dend
 * ----------------------------------------------------
 */
RMenum
rmTextureSetFilterMode (RMtexture *t,
		        GLenum min_mode,
		        GLenum mag_mode)
{
    if (RM_ASSERT(t, "rmTextureSetFilterMode() error: input texture is NULL. ") == RM_WHACKED)
	return(RM_WHACKED);

    t->min_filter_mode = min_mode;
    t->mag_filter_mode = mag_mode;

    return(RM_CHILL);
}


/*
 * ----------------------------------------------------
 * @Name rmTextureGetFilterMode
 @pstart
 RMenum rmTextureGetFilterMode (const RMtexture *toQuery,
		                GLenum *minModeReturn,
			        GLenum *magModeReturn)
 @pend

 @astart
 const RMtexture *toQuery - a handle to an RMtexture object to query
    (input).

 GLenum *minModeReturn, *magModeReturn - handles to GLenum's
    (modified, return).
 @aend

 @dstart

 Use this routine to get the texture filtering modes from an RMtexture
 object.  Upon success, RM_CHILL is returned and the filtering modes
 are copied from the RMtexture object into caller-supplied
 memory. Otherwise, RM_WHACKED is returned.

 @dend
 * ----------------------------------------------------
 */
RMenum
rmTextureGetFilterMode (const RMtexture *t,
		        GLenum *min_mode,
		        GLenum *mag_mode)
{
    if (RM_ASSERT(t, "rmTextureGetFilterMode() error: input texture is NULL. ") == RM_WHACKED)
	return(RM_WHACKED);

    if (min_mode != NULL)
	*min_mode = t->min_filter_mode;

    if (mag_mode != NULL)
	*mag_mode = t->mag_filter_mode;

    return(RM_CHILL);
}


/*
 * ----------------------------------------------------
 * @Name rmTextureSetWrapMode
 @pstart
 RMenum rmTextureSetWrapMode (RMtexture *toModify,
		              GLenum wrapMode)
 @pend

 @astart
 RMtexture *toModify - a handle to an RMtexture object to modify
    (modified).

 GLenum wrapMode - a GLenum specifying a texture wrap mode
    (input). May be one of GL_CLAMP or GL_REPEAT (OpenGL 1.2 adds the
    new texture wrap mode of GL_CLAMP_TO_EDGE).
 @aend

 @dstart

 Use this routine to set the "wrap mode" of an RMtexture
 object. Returns RM_CHILL upon success, or RM_WHACKED upon failure.

 By definition, texture coordinates range from 0.0 to 1.0, and define
 a parametric index into a texture. The texture wrap mode comes into
 play when texture coordinates go outside the range 0..1.

 When GL_CLAMP is used, texture coordinates greater than 1.0 are
 clamped to 1.0, and those less than 0.0 are clamped to 0.0. Texture
 coordinates within the range 0..1 remain unmodified.

 When GL_REPEAT is used, texture coordinates outside the range 0..1
 are treated as if they were inside the range 0..1. The exact method
 used to implement this interpretation is platform-dependent, but a
 safe assumption is that most implementations ingore all but the
 fractional part of the texture coordinate.

 Note that when GL_REPEAT is used, the texture border color is always
 ignored (rmTextureSetEnv). Similarly, when the GL_NEAREST filtering
 mode is used, the border color is also always ignored.

 Please refer to the OpenGL red book for more information about
 texture wrapping.

 Note that the RM implementation allows specification of onlye a
 single wrap mode. The one wrap mode is used for all texture
 dimensions. In this API, it is not possible to specify different wrap
 policies for each texture axis, although OpenGL does allow for this
 possibility. This RM policy reflects a simplification to the OpenGL
 API.
 
 @dend
 * ----------------------------------------------------
 */
RMenum
rmTextureSetWrapMode (RMtexture *t,
		      GLenum wrap_mode)
{
    if (RM_ASSERT(t, "rmTextureSetWrapMode() error: input texture is NULL. ") == RM_WHACKED)
	return(RM_WHACKED);

    t->wrap_mode = wrap_mode;
    return(RM_CHILL);
}


/*
 * ----------------------------------------------------
 * @Name rmTextureGetWrapMode
 @pstart
 RMenum rmTextureGetWrapMode (const RMtexture *toQuery,
		              GLenum *wrapModeReturn)
 @pend

 @astart
 const RMtexture *toQuery - a handle to an RMtexture object that will
   be queried (input).

 GLenum *wrapModeReturn - a handle to a GLenum (result).
 @aend

 @dstart
 
 Use this routine to obtain the texture wrap mode of an RMtexture
 object.  Upon success, RM_CHILL is returned, and the texture wrap
 mode is copied from the RMtexture object into the caller-supplied
 GLenum. Otherwise, RM_WHACKED is returned.
 
 @dend
 * ----------------------------------------------------
 */
RMenum
rmTextureGetWrapMode (const RMtexture *t,
		      GLenum *wrap_mode)
{
    if (RM_ASSERT(t, "rmTextureGetWrapMode() error: input texture is NULL. ") == RM_WHACKED)
	return(RM_WHACKED);

    if (wrap_mode != NULL)
       *wrap_mode = t->wrap_mode;

    return(RM_CHILL);
}


/*
 * ----------------------------------------------------
 * @Name rmTextureSetEnv
 @pstart
 RMenum rmTextureSetEnv (RMtexture *toModify,
		         GLenum envMode,
			 const RMcolor4D *blendColor)
 @pend

 @astart
 RMtexture *toModify - a handle to an RMtexture object to modify
    (modified).

 GLenum envMode - a GLenum value specifying one of several texture
     mapping modes. May be one of GL_DECAL, GL_REPLACE, GL_MODULATE or
     GL_BLEND (input).

 const RMcolor4D *blendColor - an handle to an RMcolor4D object (NULL
     is acceptable) (input).
 @aend

 @dstart

 Sets texture environment parameters in an RMtexture object. Returns
 RM_CHILL upon success, or RM_WHACKED upon failure.

 The "texture environment" specifies how, precisely, the color of a
 texture is combined with the color of a pixel fragment. Please refer
 to the OpenGL Red Book for more information about the effect of each
 of the texture environment modes GL_DECAL, GL_REPLACE, GL_MODULATE
 and GL_BLEND.

 The blendColor parameter is optional, and takes effect only when
 GL_BLEND is used for the texturing method. The default value for
 blendColor when left unspecified by the application is (0,0,0,0).  If
 blendColor is NULL, the existing blendColor in the RMtexture object,
 if any, remains unmodified.
 
 @dend
 * ----------------------------------------------------
 */
RMenum
rmTextureSetEnv (RMtexture *t,
		 GLenum envMode,
		 const RMcolor4D *blendColor)
{
    if (RM_ASSERT(t, "rmTextureSetEnv() error: the input texture is NULL") == RM_WHACKED)
	return(RM_WHACKED);

    t->envMode = envMode;

    if (blendColor != NULL)
    {
	if (t->blendColor != NULL)
	{
	    rmColor4DDelete(t->blendColor);
	    t->blendColor = NULL;
	}
	
	t->blendColor = rmColor4DNew(1);
	*(t->blendColor) = *blendColor;
    }
    return(RM_CHILL);
}


/*
 * ----------------------------------------------------
 * @Name rmTextureGetEnv
 @pstart
 RMenum rmTextureGetEnv (const RMtexture *toQuery,
		         GLenum *envModeReturn,
			 RMcolor4D *blendColorReturn)
 @pend

 @astart
 const RMtexture *toQuery - a handle to an RMtexture object (input).

 GLenum *envModeReturn - a handle to a GLenum (return).

 RMcolor4D *blendColorReturn - a handle to an RMcolor4D object
    (return).
 @aend

 @dstart

 Use this routine to obtain the texture environment parameters of an
 RMtexture object. Upon success, RM_CHILL is returned, and the texture
 environment mode and blend color are copied into caller-supplied
 memory. Otherwise, RM_WHACKED is returned.

 @dend
 * ----------------------------------------------------
 */
RMenum
rmTextureGetEnv (const RMtexture *t,
		 GLenum *envMode,
		 RMcolor4D *blendColor)
{
    if (RM_ASSERT(t, "rmTextureGetEnv() error: the input texture is NULL") == RM_WHACKED)
	return(RM_WHACKED);

    if (envMode != NULL)
	*envMode = t->envMode;

    if (blendColor != NULL && t->blendColor != NULL)
	*blendColor = *(t->blendColor);

    return(RM_CHILL);
}


/*
 * ----------------------------------------------------
 * @Name rmTextureSetGLTexelFormat
 @pstart
 RMenum rmTextureSetGLTexelFormat (RMtexture *toModify,
			           GLenum internalTexelFormat)
 @pend

 @astart
 RMtexture *toModify - a handle to an RMtexture object (modified).

 GLenum internalTexelFormat - a GLenum pixel format descriptor. See
    below for acceptable values (input).
 @aend

 @dstart

 Use this routine to tell OpenGL how texture pixel data will be stored
 in OpenGL texture memory. Returns RM_CHILL upon success, or
 RM_WHACKED upon failure.

 The permissible values for internalTexelFormat are a function of the
 particular OpenGL implementation that you are using. The set of
 GL_LUMINANCE, GL_LUMINANCE_ALPHA, GL_RGB, GL_RGBA are supported
 across all implementations. Some implementations support more
 specific values, such as GL_RGBA4, meaning that 4 bits of space are
 used for each of the R, G, B and A components of the texture.

 OpenGL converts from the source image format (in the RMimage object)
 to the format requested by this routine. The application need not be
 concerned with performing this pixel reformatting.

 By default, RM uses GL_RGBA unless overridden by the application's
 use of this routine.

 @dend
 * ----------------------------------------------------
 */
RMenum
rmTextureSetGLTexelFormat (RMtexture *t,
			   GLenum texelFormat)
{
    if (RM_ASSERT(t, "rmTextureSetGLTexelFormat error: the input RMtexture pointer is NULL") == RM_WHACKED)
	return(RM_WHACKED);

    if (t->oglTexelFormat == NULL)
	t->oglTexelFormat = (GLenum *)malloc(sizeof(GLenum));
    
    *(t->oglTexelFormat) = texelFormat;

    return(RM_CHILL);
}


/*
 * ----------------------------------------------------
 * @Name rmTextureGetGLTexelFormat
 @pstart
 RMenum rmTextureGetGLTexelFormat (const RMtexture *toQuery,
			           GLenum *returnTexelFormat)
 @pend

 @astart
 const RMtexture *toQuery - a handle to an RMtexture object (input).

 GLenum *returnTexelFormat - a handle to a GLenum value (return).
 @aend

 @dstart
 
 Use this routine to obtain the texel storage format used by the
 RMtexture object toQuery. Upon success, the texel storage format will
 be copied into caller-supplied memory and RM_CHILL will be
 returned. Otherwise, RM_WHACKED is returned.
 
 @dend
 * ----------------------------------------------------
 */
RMenum
rmTextureGetGLTexelFormat (const RMtexture *t,
			   GLenum *returnTexelFormat)
{
    if ((RM_ASSERT(t, "rmTextureGetGLTexelFormat error: the input RMtexture pointer is NULL") == RM_WHACKED) ||
	(RM_ASSERT(returnTexelFormat, "rmTextureGLGetTexelFormat error: the return texel format enumerator pointer is NULL.") == RM_WHACKED))
	return(RM_WHACKED);

    if (returnTexelFormat != NULL)
    {
	if (t->oglTexelFormat == NULL)
	    return(RM_WHACKED);
	else
	{
	    *returnTexelFormat = *(t->oglTexelFormat);
	    return(RM_CHILL);
	}
    }
    return(RM_CHILL);
}

/*
 * ----------------------------------------------------
 * @Name rmTextureGetTextureID
 @pstart
 RMenum rmTextureGetTextureID (const RMtexture *toQuery,
			       GLuint *returnTextureID)
 @pend

 @astart
 const RMtexture *toQuery - a handle to an RMtexture object (input).
 GLuint *returnTetureID - a handle to a GLuint, application-provided memory (result).
 @aend

 @dstart

 Use this routine to obtain the GL textureID handle associated with an
 RMtexture as specified by an earlier call to rmTextureSetTextureID.
 This routine does NOT return the GL textureID for textures built and
 managed by OpenRM/RM.

 Upon success, this routine returns RM_CHILL, and the value of the
 application-supplied GL textureID will be copied into caller-supplied
 memory. Upon failure, RM_WHACKED is returned, and caller-supplied memory
 remains unmodified.

 See the documentation for rmTextureSetTextureID for more information about
 application-supplied textures.
 
 @dend
 * ----------------------------------------------------
 */
RMenum
rmTextureGetTextureID (const RMtexture *toQuery, 
		       GLuint *returnTextureID)
{
    if ((RM_ASSERT(toQuery, "rmTextureGetTextureID error: the input RMtexture pointer is NULL") == RM_WHACKED) ||
	(RM_ASSERT(returnTextureID, "rmTextureGetTextureID error: the returnTextureID parameter is NULL")))
	return RM_WHACKED;

    if (toQuery->appTextureID == NULL)
	return RM_WHACKED;

    *returnTextureID = *(toQuery->appTextureID);

    return RM_CHILL;
}

/*
 * ----------------------------------------------------
 * @Name rmTextureSetTextureID
 @pstart
 RMenum rmTextureSetTextureID (RMtexture *toModify,
			       GLuint *textureID)
 @pend

 @astart
 RMtexture *toModify - a handle to an RMtexture object (input).
 GLuint *textureID - a pointer to a GLuint texture ID (input).
 @aend

 @dstart

 Use this routine to assign an application-generated GL texture ID to an
 RMtexture object in lieu of providing RMimage texel data with
 rmTextureSetImages. The intent of this approach to texturing is to help 
 support those applications that use third-party tools to create and 
 load textures to OpenGL external to OpenRM. 

 You may specify a value of NULL for the textureID parameter, in which
 case the application-supplied textureID field will be removed from the
 RMtexture object "toModify."
 
 This routine returns RM_CHILL upon success, and RM_WHACKED upon failure.

 The procedure to use for texturing with application-supplied textures 
 is as follows:
 1. (Required) Create the RMtexture with rmTextureNew(ndims). Use ndims=1 
 for 1D textures, 2 for 2D textures or 3 for 3D textures.
 2. (Optional) Specify texturing environment parameters using rmTextureSet*
 routines.
 3. (Required) Assign the application-supplied GL texture ID with
 rmTextureSetTextureID. 
 4. (Required) Assign the RMtexture as a scene parameter to an RMnode.

 The application-supplied texture must be created only after OpenGL is
 active, which means that an OpenGL context has been created and made
 current. Usually, this set of conditions is met after the application
 makes a call to rmPipeMakeCurrent().

 Also note that your RMprimitive must have texture coordinates of the
 "same species" as the GL texture. In other words, use 1D texture coordinates
 with 1D textures, and so forth.
 
 @dend
 * ----------------------------------------------------
 */
RMenum      
rmTextureSetTextureID (RMtexture *toModify, 
		       GLuint *textureID)
{
    if (RM_ASSERT(toModify, "rmTextureSetTextureID error: the input RMtexture pointer is NULL") == RM_WHACKED)
	return RM_WHACKED;

    /* 
     * While we can't check for a valid GL texture ID right now, since
     * we might not be the rendering thread or have an active, bound OpenGL
     * context, we can check for obviously bogus values.
     */

    if ((textureID == NULL) || (*textureID <= 0))
    {
	rmError("rmTextureSetTextureID() error - the input textureID value is less than or equal to zero. ");
	return RM_WHACKED;
    }

    if (toModify->appTextureID != NULL)
    {
	free((void *)(toModify->appTextureID));
	toModify->appTextureID = NULL;
    }

    if (textureID != NULL)
    {
	if ((toModify->appTextureID = (GLuint *)private_rmMemDup((void *)textureID, sizeof(GLuint))) == NULL)
	    return RM_WHACKED;
    }

    return RM_CHILL;
}

/*
 * PRIVATE
 * download 3D textures to OpenGL
 */
void
private_rmTexture3DDownload(RMpipe *p,
			    RMtexture *t,
			    int isNew)
{
    RMvisMap *vmap=NULL;
    GLenum srcGLTexelFormat, dstGLTexelFormat;
    int i;

    glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_S, t->wrap_mode);
    glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_T, t->wrap_mode);
    glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_R_EXT, t->wrap_mode);
    glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_MAG_FILTER, t->mag_filter_mode);
    glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_MIN_FILTER, t->min_filter_mode);
	
    glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, t->envMode);
	     
    if (t->blendColor != NULL)
	glTexEnvfv(GL_TEXTURE_ENV, GL_TEXTURE_ENV_COLOR, (GLfloat *)(t->blendColor));
	     
    for (i = 0; i < private_rmTextureGetNMipMaps(t); i++)
    {
	int w,h,d;
	
	if (t->images[i] == NULL)
	{
	    char buf[128];
	    sprintf(buf," the RMimage at mipmap level %d for a 3D texture is missing. The texture download likely did not succeed, and your texturing will not be correct. \n",i);
	    rmError(buf);
	    break; /* exit the loop over mipmaps */
	}

	vmap = private_rmImageGetVismap(t->images[i]);
	
	/* convert between RM and OpenGL type enumerators */
	srcGLTexelFormat = private_rmImageGetOGLFormat(t->images[i]);
	rmTextureGetGLTexelFormat(t, &dstGLTexelFormat);

	if (vmap != NULL)
	    private_rmSetPixelTransferMode(vmap);
	else
	    private_rmUnsetPixelTransferMode();
	
	rmImageGetImageSize(t->images[i], NULL, &w, &h, &d, NULL, NULL);
	     
	/* updates to allow for non-byte input texel data 2/19/02 */
	if (isNew == 1)
	{
	    if (p->caps->rm_glTexImage3D != NULL)
		(*(p->caps->rm_glTexImage3D))(GL_TEXTURE_3D, i, dstGLTexelFormat, w, h, d, t->borderWidth, srcGLTexelFormat, private_rmImageGetOGLType(t->images[i]), (const GLvoid *)(private_rmImageGetPixelData(t->images[i])));
#if 0
	    glTexImage3D(GL_TEXTURE_3D, i, dstGLTexelFormat, w, h, d, t->borderWidth, srcGLTexelFormat, private_rmImageGetOGLType(t->images[i]), (const GLvoid *)(private_rmImageGetPixelData(t->images[i])));
#else
#endif
	    rmGLGetError(" while loading 3D texture(s) ");
	}
	else
	{
#if 0
	    glTexSubImage3D(GL_TEXTURE_3D, i, 0, 0, 0, w, h, d, srcGLTexelFormat, private_rmImageGetOGLType(t->images[0]), (const GLvoid *)(private_rmImageGetPixelData(t->images[i])));
#else
#endif
	    rmGLGetError(" while loading 3D subtexture(s) ");
	}
    }
    
    private_rmUnsetPixelTransferMode();
}

/*
 * PRIVATE
 * download 2D textures to OpenGL
 */
void
private_rmTexture2DDownload(RMtexture *t,
			    int isNew)
{
    RMvisMap *vmap=NULL;
    GLenum srcGLTexelFormat, dstGLTexelFormat;
    int i;

    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, t->wrap_mode);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, t->wrap_mode);

    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, t->mag_filter_mode);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, t->min_filter_mode);
	
/*    glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, t->envMode); */
	     
    if (t->blendColor != NULL)
	glTexEnvfv(GL_TEXTURE_ENV, GL_TEXTURE_ENV_COLOR, (GLfloat *)(t->blendColor));
	     
    for (i = 0; i < private_rmTextureGetNMipMaps(t); i++)
    {
	int w,h;
	
	if (t->images[i] == NULL)
	{
	    char buf[128];
	    sprintf(buf," the RMimage at mipmap level %d for a 2D texture is missing. The texture download likely did not succeed, and your texturing will not be correct. \n",i);
	    rmError(buf);
	    break; /* exit the loop over mipmaps */
	}

	vmap = private_rmImageGetVismap(t->images[i]);
	
	/* convert between RM and OpenGL type enumerators */
	srcGLTexelFormat = private_rmImageGetOGLFormat(t->images[i]);
	rmTextureGetGLTexelFormat(t, &dstGLTexelFormat);

	if (vmap != NULL)
	    private_rmSetPixelTransferMode(vmap);
#if 1
	else
	    private_rmUnsetPixelTransferMode();
#else
	fprintf(stderr,"private_rmTexture2DDownload() - temporarily disabling rmUnsetPixelTransferMode prior to texel download.");
	fflush(stderr);
#endif
	
	rmImageGetImageSize(t->images[i], NULL, &w, &h, NULL, NULL, NULL);
	     
	if (isNew == 1)
	{
	    glTexImage2D(GL_TEXTURE_2D, i, dstGLTexelFormat, w, h, t->borderWidth, srcGLTexelFormat, private_rmImageGetOGLType(t->images[i]), (const GLvoid *)(private_rmImageGetPixelData(t->images[i]))); 
	    rmGLGetError(" while loading 2D texture(s) ");
	}
	else
	{
	    glTexSubImage2D(GL_TEXTURE_2D, i, 0, 0, w, h, srcGLTexelFormat, private_rmImageGetOGLType(t->images[0]), (const GLvoid *)(private_rmImageGetPixelData(t->images[i])));
	    rmGLGetError(" while loading 2D subtexture(s) ");
	}
    }

    private_rmUnsetPixelTransferMode();
}

/*
 * PRIVATE
 * download 1D textures to OpenGL
 */
void
private_rmTexture1DDownload(RMtexture *t,
			    int isNew)
{
    RMvisMap *vmap=NULL;
    GLenum srcGLTexelFormat, dstGLTexelFormat;
    int i;

    glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_WRAP_S, t->wrap_mode);

    glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_MAG_FILTER, t->mag_filter_mode);
    glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_MIN_FILTER, t->min_filter_mode);
	
	     
    if (t->blendColor != NULL)
	glTexEnvfv(GL_TEXTURE_ENV, GL_TEXTURE_ENV_COLOR, (GLfloat *)(t->blendColor));
	     
    for (i = 0; i < private_rmTextureGetNMipMaps(t); i++)
    {
	int w;
	
	if (t->images[i] == NULL)
	{
	    char buf[128];
	    sprintf(buf," the RMimage at mipmap level %d for a 2D texture is missing. The texture download likely did not succeed, and your texturing will not be correct. \n",i);
	    rmError(buf);
	    break; /* exit the loop over mipmaps */
	}

	vmap = private_rmImageGetVismap(t->images[i]);
	
	/* convert between RM and OpenGL type enumerators */
	srcGLTexelFormat = private_rmImageGetOGLFormat(t->images[i]);
	rmTextureGetGLTexelFormat(t, &dstGLTexelFormat);

	if (vmap != NULL)
	    private_rmSetPixelTransferMode(vmap);
#if 1
	else
	    private_rmUnsetPixelTransferMode();
#else
	fprintf(stderr,"private_rmTexture2DDownload() - temporarily disabling rmUnsetPixelTransferMode prior to texel download.");
	fflush(stderr);
#endif
	
	rmImageGetImageSize(t->images[i], NULL, &w, NULL, NULL, NULL, NULL);
	     
	if (isNew == 1)
	{
	    glTexImage1D(GL_TEXTURE_1D, i, dstGLTexelFormat, w, t->borderWidth, srcGLTexelFormat, private_rmImageGetOGLType(t->images[i]), (const GLvoid *)(private_rmImageGetPixelData(t->images[i]))); 
	    rmGLGetError(" while loading 1D texture(s) ");
	}
	else
	{
	    glTexSubImage1D(GL_TEXTURE_2D, i, 0, w, srcGLTexelFormat, private_rmImageGetOGLType(t->images[0]), (const GLvoid *)(private_rmImageGetPixelData(t->images[i])));
	    rmGLGetError(" while loading 2D subtexture(s) ");
	}
    }

    private_rmUnsetPixelTransferMode();
}

/* PRIVATE
 *
 * internal routine to push texture data over to OpenGL
 */
void
private_rmTextureToOGL (RMpipe *p,
			RMtexture *t,
		        int isNew)
{
    if (RM_ASSERT(t->images[0], "an RMtexture was encountered for which no RMimage data has been assigned.") == RM_WHACKED)
	return;

    switch(private_rmTextureGetDims(t))
    {
    case 3:
	private_rmTexture3DDownload(p, t, isNew);
	break;

    case 2:
	private_rmTexture2DDownload(t, isNew);
	break;

    case 1:
	private_rmTexture1DDownload(t, isNew);
	break;

    default:
	rmError("private_rmTextureToOGL error - the input RMtexture is not 1D, 2D or 3D.");
	break;
    }
}


/* PRIVATE */
void
private_rmSetPixelTransferMode (const RMvisMap *vm)
{
    int n;

    if (RM_ASSERT(vm, "private_rmSetPixelTranserMode error: the input colormap is NULL.") == RM_WHACKED)
	return;
    
    glPixelTransferi(GL_MAP_COLOR, GL_TRUE);

    n = rmVismapGetSize(vm);

    glPixelMapfv(GL_PIXEL_MAP_R_TO_R, n, vm->r);
    glPixelMapfv(GL_PIXEL_MAP_G_TO_G, n, vm->g);
    glPixelMapfv(GL_PIXEL_MAP_B_TO_B, n, vm->b);
    glPixelMapfv(GL_PIXEL_MAP_A_TO_A, n, vm->a);

}


/* PRIVATE */
void
private_rmUnsetPixelTransferMode (void)
{
    glPixelTransferi(GL_MAP_COLOR, GL_FALSE);

#if 0
    /* 1/26/03 - just turn off pixel transfer. No need to set the
       map sizes to zero with NULL values, because doing so can cause
       an OpenGL error */
    glPixelMapfv(GL_PIXEL_MAP_R_TO_R, 0, NULL);
    glPixelMapfv(GL_PIXEL_MAP_G_TO_G, 0, NULL);
    glPixelMapfv(GL_PIXEL_MAP_B_TO_B, 0, NULL);
    glPixelMapfv(GL_PIXEL_MAP_A_TO_A, 0, NULL);
#endif    
}

/* PRIVATE */
/*
 * an RMtexture's "reference count" indicates the number of times a given
 * RMtexture is used as a shared texture scene parameter at an RMnode.
 * When an RMtexture is added as a shared texture scene parameter, the
 * RMtexture's reference counter is automatically incremented, and when
 * the shared RMtexture scene parameter is removed, it is decremented.
 * Deleting RMtextures is prohibited when the RMtexture's reference count
 * is a positive integer.
 */
void
private_rmTextureSetRefCount(RMtexture *t,
			     int newVal)
{
    t->refCount = newVal;
}

/* PRIVATE */
int
private_rmTextureGetRefCount(const RMtexture *t)
{
    return t->refCount;
}


/* EOF */
