/*
 * 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: rmprim.c,v 1.20 2008/11/24 16:37:04 wes Exp $
 * Version: $Name: v180-alpha-02 $
 * $Revision: 1.20 $
 * $Log: rmprim.c,v $
 * Revision 1.20  2008/11/24 16:37:04  wes
 * Typedef for the data free func so as to eliminate compile warnings
 * when the client data updates are routed through the data sync mechanism
 *
 * Revision 1.19  2008/11/23 15:14:55  wes
 * Minor docs correction
 *
 * Revision 1.18  2008/09/22 01:47:40  wes
 * SG sync code for client data on nodes and prims
 *
 * Revision 1.17  2006/07/14 21:01:38  wes
 * Update rmPrimitiveSet* routines to use a size_t rather than int parm
 * for the count of number of things being set. This change will allow
 * >2G things to be set in a single blob (you better hope you have
 * enough memory on your machine for this action!!).
 *
 * Revision 1.16  2006/04/16 15:11:49  wes
 * Updated private_rmBlobSetData to use size_t for malloc's rather than an
 * int. This change supports having more than 32 bits worth of blob (primitive)
 * data.
 *
 * Revision 1.15  2005/06/09 00:45:29  wes
 * More compiler warning fixes turned up by Windows build.
 *
 * Revision 1.14  2005/06/06 02:04:29  wes
 * Lots of small additions to clean up compiler warnings.
 *
 * Revision 1.13  2005/05/06 16:37:31  wes
 * Fixed buglet whereby 1D texture coords had the wrong stride assigned.
 *
 * Revision 1.12  2005/02/27 19:34:04  wes
 * Added support for application supplied texture object IDs and display lists.
 *
 * Revision 1.11  2005/02/19 16:42:30  wes
 * Distro sync and consolidation.
 * Remove dead code.
 *
 * Revision 1.10  2005/02/12 00:55:20  wes
 * Preliminary support for multitexturing.
 *
 * Revision 1.9  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.8  2004/03/10 01:46:41  wes
 * Added RM_INDEXED_QUAD_STRIP primitive type.
 *
 * Revision 1.7  2004/02/23 03:04:32  wes
 * New primitives: RM_QUAD_STRIP, RM_INDEXED_TRIANGLES, RM_INDEXED_QUADS,
 * RM_INDEXED_TRIANGLE_STRIP.
 *
 * Revision 1.6  2004/01/16 16:47:07  wes
 * Updated copyright line for 2004.
 *
 * Revision 1.5  2003/04/05 14:13:07  wes
 * Add code to rmNodeAddPrimitive that will leave the mutex in an unlocked
 * state upon detection of an error condition, and premature routine exit.
 *
 * Revision 1.4  2003/02/14 00:18:55  wes
 * Remove dead code.
 *
 * Revision 1.3  2003/02/02 17:50:57  wes
 * Added bounding boxes to RMprimitives, as a supplement to node-level bboxes.
 * The RMprimitive level bboxes are needed for the retained-mode CR work.
 *
 * Revision 1.2  2003/02/02 02:07:15  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  2003/01/11 18:44:22  wes
 * Added global control over whether or not display lists are used during
 * rendering by adding the new RMpipe controls rmPipeSetDisplayListEnable()
 * and rmPipeGetDisplayListEnable().
 *
 * Revision 1.9  2002/12/02 16:24:56  wes
 * Bug fixes:
 * (1) fixed memory leak in rmPrimitiveSetText;
 * (2) fixed problem with textProps not being applied to 2nd and later
 * strings in an RM_TEXT text primitive.
 *
 * Revision 1.8  2002/09/22 17:34:06  wes
 * Updated private_rmPrimitiveSetItem() to return the correct status to
 * parent routines, such as rmPrimitiveSetVertex3D.
 *
 * Revision 1.7  2002/06/02 15:16:40  wes
 * Added RMstateCache code to help eliminate the number of state changes
 * made during the render traversal. The RMstateCache tracks
 * the actual OpenGL rendering state w/o the need for querying OpenGL
 * directly, and  is queried by draw code that then decides if any
 * real state changes are required given the configuration of data
 * within an RMprimitive.
 *
 * Revision 1.6  2002/04/30 19:33:05  wes
 * Updated copyright dates.
 *
 * Revision 1.5  2001/03/31 17:12:39  wes
 * v1.4.0-alpha-2 checkin.
 *
 * Revision 1.4  2000/12/03 22:33:55  wes
 * Mods for thread-safety.
 *
 * Revision 1.3  2000/05/17 14:24:04  wes
 * Added RM_POLYS (thanks to Matt and Todd of VRCO).
 *
 * 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"
#include <string.h>

/*
 * this file contains routines that interface to the RMprimitive object.
 * the trickiest thing about primitives is that large-payload data
 * in primitives (verts, normals, etc) are stored in "data blobs."
 * most of the rmPrimitiveSet* routines that cache large-payload data
 * call an internal routine that maps from the requested data type
 * (verts, normals, indices, etc) into a "data blob" map.
 */

/* PRIVATE declarations */
RMenum private_rmPrimitiveSetItem(RMprimitive *p, int tag, size_t num, int stride, void *stuff, RMenum copy_flag, void (*freefunc)( void *));
RMenum private_rmPrimitiveSetMultiTexcoordBlob(RMprimitive *p, int tcTag, int n, int stride,  void *stuff, RMenum copyFlag, void (*freefunc)(void *), int textureUnit);
void private_rmPrimitiveSetCacheKey(RMprimitive *p);

/*
 * ----------------------------------------------------
 * @Name rmPrimitiveNew
 @pstart
 RMprimitive * rmPrimitiveNew (RMenum primType)
 @pend

 @astart
 RMenum primType - an RMenum value specifying the RM primitive type.
    See below for more information  (input).
 @aend

 @dstart

 Use this routine to create a new RMprimitive object of a specified
 type. Returns a handle to the new RMprimitive upon success, or NULL
 upon failure.

 The current list of valid RMenum values for the primType parameter
 are: RM_LINES, RM_LINE_STRIP, RM_TRIANGLES, RM_TRIANGLE_STRIP,
 RM_SPHERES, RM_QUADMESH, RM_OCTMESH, RM_TEXT, RM_POINTS, RM_CONES,
 RM_CYLINDERS, RM_INDEXED_TEXT, RM_QUADS, RM_MARKERS2D, RM_BOX3D_WIRE,
 RM_CIRCLE2D, RM_ELLIPSE2D, RM_SPRITE, RM_TRIANGLE_FAN,
 RM_INDEXED_TFAN, RM_POLYS, RM_QUAD_STRIP, RM_INDEXED_QUADS,
 RM_INDEXED_TRIANGLES, RM_INDEXED_TRIANGLE_STRIP, RM_USERDEFINED_PRIM,
 RM_APP_DISPLAYLIST.

 Some of the procedural primitives are assigned default "model flag"
 attributes (see rmPrimitiveSetModelFlag). These are:

 RM_SPHERES: RM_SPHERES_32 produces a 32-faced sphere tesselation.

 RM_OCTMESH: RM_OCTMESH_1 produces a "full resolution" octmesh
 primitive.

 RM_CONES: RM_CONES_16 produces a cone consisting of 16 radial
 subdivisions.

 RM_CYLINDERS: RM_CYLINDERS_16 produces a cylinder consisting of 16
 radial subdivisions.

 When the RMprimitive is created, it has no vertices, normals,
 etc. and it is not associated with any scene graph node. Use
 rmNodeAddPrimitive() to add an RMprimitive to a node.
 
 @dend
 * ----------------------------------------------------
 */
RMprimitive *
rmPrimitiveNew (RMenum primType)
{
    void       (*rfunc)OGLPRIMPARMLIST() = NULL;
    RMenum     (*boxFunc)(RMprimitive *);
    
    RMprimitive *t;
    RMprimitiveDataBlob *b;
    extern RMenum  RM_DEFAULT_PRIMITIVE_DISPLAY_LIST_ENABLE;
    
    t = private_rmPrimitiveNew();
    if (t == NULL)
    {
	rmError("rmPrimitiveNew() error: primitive malloc failure.");
	return(NULL);
    }

    t->clientData = NULL;
    t->clientDataFreeFunc = NULL;
    t->p1 = NULL;

    t->bmin = t->bmax = NULL;

    t->flags1 = t->model_flag = 0;
    rmPrimitiveSetDisplayListEnable(t, RM_DEFAULT_PRIMITIVE_DISPLAY_LIST_ENABLE);

    b = (RMprimitiveDataBlob *)malloc(sizeof(RMprimitiveDataBlob) * RM_MAXBLOBS_PER_PRIMITIVE);
    memset(b, 0, sizeof(RMprimitiveDataBlob) * RM_MAXBLOBS_PER_PRIMITIVE);
    t->blobs = b;

    /* 2/2005 multitexture support */
    t->multiTextureCoordBlobs = NULL;
    t->numMultiTextureCoordBlobs = 0;
    t->multiTextureCoordBlobsMask = 0;
    
    private_rmPrimitiveSetType(t, primType);

    switch (primType)
	{
	case RM_APP_DISPLAYLIST:
	    rfunc = rmAppDisplayList;
	    boxFunc = private_rmPrimitiveNullBoxFunc;
	    break;
	    
	case RM_LINES:
	    rfunc = rmLinesDisjoint;
	    boxFunc = private_rmPrimitiveComputeGenericBoundingBox;
	    break;
	    
	case RM_LINE_STRIP:
	    boxFunc = private_rmPrimitiveComputeGenericBoundingBox;
	    rfunc = rmLineStrip;
	    break;
	    
	case RM_TRIANGLES:
	    boxFunc = private_rmPrimitiveComputeGenericBoundingBox;
	    rfunc = rmTrianglesDisjoint;
	    break;
	    
	case RM_TRIANGLE_STRIP:
	    boxFunc = private_rmPrimitiveComputeGenericBoundingBox;
	    rfunc = rmTrianglesConnected;
	    break;
	    
	case RM_QUAD_STRIP:
	    boxFunc = private_rmPrimitiveComputeGenericBoundingBox;
	    rfunc = rmQuadStrip;
	    break;
	    
	case RM_SPHERES:
	    rmPrimitiveSetModelFlag(t, RM_SPHERES_32);
	    boxFunc = private_rmPrimitiveComputeSpheresBoundingBox;
	    rfunc = rmSpheres;
	    break;
	    
	case RM_QUADMESH:
	    boxFunc = private_rmPrimitiveComputeGenericBoundingBox;
	    rfunc = rmQuadmesh;
	    break;
	    
	case RM_OCTMESH:
	    rmPrimitiveSetModelFlag(t, RM_OCTMESH_1);
	    boxFunc = private_rmPrimitiveComputeOctmeshBoundingBox;
	    rfunc = rmOctmesh;
	    break;
	    
	case RM_TEXT:
	    boxFunc = private_rmPrimitiveComputeGenericBoundingBox;
	    rfunc = rmText;
	    break;
	    
	case RM_POINTS:
	    boxFunc = private_rmPrimitiveComputeGenericBoundingBox;
	    rfunc = rmPoints;
	    break;
	    
	case RM_CONES:
	    rmPrimitiveSetModelFlag(t, RM_CONES_16);
	    boxFunc = private_rmPrimitiveComputeConesBoundingBox;
	    rfunc = rmCones;
	    break;
	    
	case RM_CYLINDERS:
	    rmPrimitiveSetModelFlag(t, RM_CYLINDERS_16);
	    boxFunc = private_rmPrimitiveComputeCylindersBoundingBox;
	    rfunc = rmCylinders;
	    break;
	    
	case RM_INDEXED_TEXT:
	    boxFunc = private_rmPrimitiveComputeGenericBoundingBox;
	    rfunc = rmIndexedText;
	    break;
	    
	case RM_QUADS:
	    boxFunc = private_rmPrimitiveComputeGenericBoundingBox;
	    rfunc = rmQuads;
	    break;

	case RM_INDEXED_QUADS:
	    boxFunc = private_rmPrimitiveComputeGenericBoundingBox;
	    rfunc = rmIndexedQuads;
	    break;
	    
	case RM_INDEXED_QUAD_STRIP:
	    boxFunc = private_rmPrimitiveComputeGenericBoundingBox;
	    rfunc = rmIndexedQuadStrip;
	    break;
	    
	case RM_INDEXED_TRIANGLES:
	    boxFunc = private_rmPrimitiveComputeGenericBoundingBox;
	    rfunc = rmIndexedTriangles;
	    break;
	    
	case RM_INDEXED_TRIANGLE_STRIP:
	    boxFunc = private_rmPrimitiveComputeGenericBoundingBox;
	    rfunc = rmIndexedTriangleStrip;
	    break;
	    
	case RM_MARKERS2D:
	    boxFunc = private_rmPrimitiveComputeGenericBoundingBox;
	    rfunc = rmMarkers2D;
	    break;
	    
	case RM_BOX3D:
	    boxFunc = private_rmPrimitiveComputeGenericBoundingBox;
	    rfunc= rmBox3d;
	    break;
	    
	case RM_BOX3D_WIRE:
	    boxFunc = private_rmPrimitiveComputeGenericBoundingBox;
	    rfunc = rmBox3dWire;
	    break;
	    
	case RM_CIRCLE2D:
	    boxFunc = private_rmPrimitiveCompute2DCircleBoundingBox;
	    rfunc = rmCircle2d;
	    break;
	    
	case RM_BOX2D:
	    boxFunc = private_rmPrimitiveComputeGenericBoundingBox;
	    rfunc = rmBox2d;
	    break;
	    
	case RM_ELLIPSE2D:
	    boxFunc = private_rmPrimitiveCompute2DEllipseBoundingBox;
	    rfunc= rmEllipse2d;
	    break;
	    
	case RM_SPRITE:
	    boxFunc = private_rmPrimitiveComputeGenericBoundingBox;
	    rfunc = rmSprite;
	    break;
	    
	case RM_BITMAP:
	    boxFunc = private_rmPrimitiveComputeGenericBoundingBox;
	    rfunc = rmBitmap;
	    break;
	    
	case RM_INDEXED_BITMAP:
	    boxFunc = private_rmPrimitiveComputeGenericBoundingBox;
	    rfunc = rmIndexedBitmap;
	    break;
	    
	case RM_TRIANGLE_FAN:
	    boxFunc = private_rmPrimitiveComputeGenericBoundingBox;
	    rfunc = rmTriangleFan;
	    break;
	    
	case RM_INDEXED_TFAN:
	    rfunc = rmIndexedTriangleFan;
	    boxFunc = private_rmPrimitiveComputeGenericBoundingBox;
	    break;

	case RM_POLYS:
	    rfunc = rmPolys;
	    boxFunc = private_rmPrimitiveComputeGenericBoundingBox;
	    break;
	    
	default:
	    boxFunc = private_rmPrimitiveComputeGenericBoundingBox;
	    break;
	}
    t->primitiveComputeBoundingBoxFunc = boxFunc;
    rmPrimitiveSetRenderFunc(t, rfunc);

    return(t);
}


/*
 * ----------------------------------------------------
 * @Name rmPrimitiveDelete
 @pstart
 void rmPrimitiveDelete (RMprimitive *toDelete)
 @pend

 @astart

 RMprimitive *toDelete - a handle to an RMprimitive to delete
    (modified). 
 @aend

 @dstart

 Use this routine to release resources associated with an RMprimitive.

 If shared data management was specified (when rmPrimitiveSetVertex3D
 was called, for example), those application callback functions will
 be invoked to free large-payload memory.
 
 @dend
 * ----------------------------------------------------
 */
void
rmPrimitiveDelete (RMprimitive *p)
{
    /* ignore NULL prims */
    if (p == NULL)		
	return;

    private_rmPrimitiveDelete(p);
}


/*
 * ----------------------------------------------------
 * @Name rmPrimitiveSetRenderFunc
 @pstart
 RMenum rmPrimitiveSetRenderFunc (RMprimitive *toModify,
			          void (*drawFunc) OGLPRIMPARMLIST() )
 @pend

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

 void (*drawFunc) OGLPRIMPARMLIST() - a callback invoked to draw the
    primitive (input). 
 @aend

 @dstart

 This routine assigns the draw callback to an RMprimitive. When an
 RMprimitive is created with rmPrimitiveNew(), the draw callback is
 automatically assigned except when rmPrimitiveNew is invoked with
 RM_USERDEFINED_PRIM. Applications that want to assign a draw function
 to an RMprimitive should use this function. This routine returns
 RM_CHILL upon success, or RM_WHACKED upon failure.

 The draw callback contains raw OpenGL code that performs the
 rendering.  The parameter list to the draw callback is represented
 with the macro OGLPRIMPARMLIST(). At this time (June 2002), that macro
 expands to:

 (RMprimitive *primToDraw, RMnode *owningNode, RMstate *currentRenderState, RMpipe *renderPipe, RMstateCache *internalStateCache)
 
 @dend
 * ----------------------------------------------------
 */
RMenum
rmPrimitiveSetRenderFunc (RMprimitive *p,
			  void (*renderfunc)OGLPRIMPARMLIST() )
{
    if (RM_ASSERT(p, "rmPrimitiveSetRenderFunc() error: the input RMprimitive pointer is NULL.") == RM_WHACKED)
	return(RM_WHACKED);
    
    p->renderfunc = renderfunc;

    return(RM_CHILL);
}


/*
 * ----------------------------------------------------
 * @Name rmPrimitiveGetRenderFunc
 @pstart
 void * rmPrimitiveGetRenderFunc (const RMprimitive *toQuery)
 @pend

 @astart
 const RMprimitive *toQuery - a handle to the RMprimitive object to
    query (input).
 @aend

 @dstart

 Returns to the caller the handle to the draw callback associated with
 the input RMprimitive.

 @dend
 * ----------------------------------------------------
 */
void *
rmPrimitiveGetRenderFunc (const RMprimitive *p)
{
    if (RM_ASSERT(p, "rmPrimitiveGetRenderFunc() error: the input RMprimitive is NULL") == RM_WHACKED)
	return(NULL);
    
    return((void *)(p->renderfunc));
}


/*
 * ----------------------------------------------------
 * @Name rmPrimitiveSetModelFlag
 @pstart
 RMenum rmPrimitiveSetModelFlag (RMprimitive *toModify,
			         int newVal)
 @pend

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

 int newVal - an integer value specifying a new RMprimitive model flag
    (see below for more info) (input). 
 @aend

 @dstart

 Use this routine to modify the "primitive model flag" for an
 RMprimitive.  Returns RM_CHILL upon success or RM_WHACKED upon
 failure.

 The RMprimitive model flag is used to control the rendering
 resolution of primitives. Only a few RMprimitive types are eligible
 for this kind of resolution control: RM_SPHERES, RM_CONES,
 RM_CYLINDERS and RM_OCTMESH.

 RM_SPHERES model flags: RM_SPHERES_8, RM_SPHERES_32, RM_SPHERES_128,
 and RM_SPHERES_512. These flags affect the tesselation resolution
 used when rendering spheres. RM_SPHERES_8 produces an octahedron, and
 each of the other model flags is a midpoint-subdivision refinement
 refinement of the octahedron. The RM sphere tesselation model is
 superior to the one in GLU for two reasons. First, the faces of the
 tesselation are all equal in area. Second, the faces of the
 tesselation are triangles, so there are no non-planar faces;
 gluSphere tesselates a sphere into non-planar quads. Internally, RM
 builds OpenGL display lists for each sphere tesselation then invokes
 that display list at render time, positioned and scaled to the
 specifications of the RMprimitive.

 RM_CONES and RM_CYLINDERS use the following set: RM_CONES_4,
 RM_CONES_8, RM_CONES_12, RM_CONES_16, RM_CONES_32, RM_CONES_64, and
 RM_CONES_128; RM_CYLINDERS_4, RM_CYLINDERS_8, RM_CYLINDERS_12,
 RM_CYLINDERS_16, RM_CYLINDERS_32, RM_CYLINDERS_64, and
 RM_CYLINDERS_128. The tesselation of cones and cylinders is nearly
 identical: the ideal circle (at the base of the cone, and at each end
 of the cylinder) is discretized with 4, 8, 12, 16, 32, 64 or 128
 points, respectively. In the case of cones, a single triangle joints
 each adjacent pair of sample points with the cone apex, while a
 cylinder uses a pair of triangles to join each pair of sample points
 between ends of the cylinder. Internally, RM builds OpenGL display
 lists (t-fans for cones and t-strips for cylinders).

 RM_OCTMESH: RM_OCTMESH_1, RM_OCTMESH_2, RM_OCTMESH_4, RM_OCTMESH_8,
 RM_OCTMESH_16. The octmesh primitive model flag can be considered a
 "divide by" constant. In other words, if the base resolution of the
 octmesh grid is 64x64x64 and RM_OCTMESH_2 is used, then the
 resolution of the polygonalized model will be 32x32x32, but will be
 fill the space (volume) specified by the octmesh grid. This model
 flag can accelerate rendering of volume data on pixel-fill limited
 systems.

 Note: the model flag values are not RMenum's - they are in fact
 indices into internal tables.  Please don't change the #defines for
 the the model flags.

 @dend
 * ----------------------------------------------------
 */
RMenum
rmPrimitiveSetModelFlag (RMprimitive *p,
			 int newval)
{
    if (RM_ASSERT(p, "rmPrimitiveSetModelFlag() error: the input RMprimitive is NULL") == RM_WHACKED)
	return(RM_WHACKED);
    
    p->model_flag = newval;

    return(RM_CHILL);
}


/*
 * ----------------------------------------------------
 * @Name rmPrimitiveGetModelFlag
 @pstart
 int rmPrimitiveGetModelFlag (const RMprimitive *toQuery)
 @pend

 @astart

 const RMprimitive *toQuery - a handle to an RMprimitive to query
    (input). 
 @aend

 @dstart

 Returns to the caller the current primitive model flag associated
 with the RMprimitive object.

 RM_WHACKED is returned for RMprimitives that don't know about model
 flags.

 @dend
 * ----------------------------------------------------
 */
int
rmPrimitiveGetModelFlag (const RMprimitive *p)
{
    int rstat;
    
    if (RM_ASSERT(p, "rmPrimitiveGetModelFlag() error: the input RMprimitive is NULL. \n") == RM_WHACKED)
	return(RM_WHACKED);

    switch(private_rmPrimitiveGetType(p))
	{
	case RM_LINES:
	case RM_LINE_STRIP:
	case RM_TRIANGLES:
	case RM_TRIANGLE_STRIP:
	case RM_TRIANGLE_FAN:
	case RM_QUADMESH:
	case RM_POINTS:
	case RM_BOX3D:
	case RM_BOX3D_WIRE:
	case RM_TEXT:
	case RM_INDEXED_TEXT:
	case RM_QUADS:
	case RM_MARKERS2D:
	case RM_BOX2D:
	case RM_SPRITE:
	case RM_BITMAP:
	case RM_INDEXED_BITMAP:
	case RM_POLYS:
	case RM_QUAD_STRIP:
	case RM_INDEXED_TFAN:
	case RM_INDEXED_QUADS:
	case RM_INDEXED_QUAD_STRIP:
	case RM_INDEXED_TRIANGLES:
	case RM_INDEXED_TRIANGLE_STRIP:
	case RM_USERDEFINED_PRIM:
	    rstat = (int)RM_WHACKED;
	    break;
	    
	case RM_ELLIPSE2D:
	case RM_CIRCLE2D:
	case RM_OCTMESH:
	case RM_CONES:
	case RM_CYLINDERS:
	case RM_SPHERES:
	    rstat = private_rmPrimitiveGetModelFlag(p);
	    break;
	    
	default: /* bogus prim type */
	    rstat = (int)RM_WHACKED;
	    break;
	}
    return(rstat);
}

/*
 * ----------------------------------------------------
 * @Name rmPrimitiveComputeBoundingBox
 @pstart
 RMenum rmPrimitiveComputeBoundingBox (RMprimitive *toModify)
 @pend

 @astart

 RMprimitive *toModify - a handle to an RMprimitive (modified).

 @aend

 @dstart

 Use this routine to automatically compute an RMprimitive's bounding box.
 This routine returns RM_CHILL upon success, or RM_WHACKED upon failure.
 Failure will occur if the input RMprimitive is NULL.

 See the description of rmPrimitiveSetBoundingBox for more details about
 when an RMprimitive's bounding box should be set, or computed, as well
 as discussion about the relationship between the RMprimitive and RMnode
 bounding boxes. Use rmPrimitiveGetBoundingBox() to obtain an RMprimitive's
 bounding box.

 @dend
 * ----------------------------------------------------
 */
RMenum
rmPrimitiveComputeBoundingBox(RMprimitive *p)
{
    if (RM_ASSERT(p, "rmPrimitiveComputeBoundingBox() error: the input RMprimitive is NULL.") == RM_WHACKED)
	return(RM_WHACKED);

    if (p->primitiveComputeBoundingBoxFunc == NULL)
	return RM_WHACKED;

    return ((*p->primitiveComputeBoundingBoxFunc)(p));
}

/*
 * ----------------------------------------------------
 * @Name rmPrimitiveSetBoundingBox
 @pstart
 RMenum rmPrimitiveSetBoundingBox (RMprimitive *toModify, const RMvertex3D *bmin, const RMvertex3D *bmax)
 @pend

 @astart

 RMprimitive *toModify - a handle to an RMprimitive (modified).
 const RMvertex3D *bmin, *bmax - handles to RMvertex3D's.

 @aend

 @dstart

 Use this routine to explicitly set the bounding box at an RMprimitive. The
 bounding box of the RMprimitive toModify will be set to the RMvertex3D
 values specified in the input parameters bmin and bmax. Returns RM_CHILL
 upon success, or RM_WHACKED upon failure. If bmin or bmax are NULL, those
 portions of the RMprimitive's bounding box are removed, leaving the
 bounding box in an undefined and uninitialized state.

 The RMprimitive's bounding box is automatically computed by RM in the
 following circumstances:
 1. list them

 The relationship between the RMprimitive and RMnode bounding box is ...

 @dend
 * ----------------------------------------------------
 */
RMenum
rmPrimitiveSetBoundingBox(RMprimitive *p,
			  const RMvertex3D *bmin,
			  const RMvertex3D *bmax)
{
    if (RM_ASSERT(p, "rmPrimitiveSetBoundingBox() error: the input RMprimitive is NULL.") == RM_WHACKED)
	return(RM_WHACKED);

    if (p->bmin != NULL)
	free((void *)(p->bmin));
    
    if (bmin != NULL)
    {
	p->bmin = rmVertex3DNew(1);
	*(p->bmin) = *bmin;
    }

    if (p->bmax != NULL)
	free((void *)(p->bmax));

    if (bmax != NULL)
    {
	p->bmax = rmVertex3DNew(1);
	*(p->bmax) = *bmax;
    }

    return RM_CHILL;
}

/*
 * ----------------------------------------------------
 * @Name rmPrimitiveGetBoundingBox
 @pstart
 RMenum rmPrimitiveGetBoundingBox (const RMprimitive *toQuery, RMvertex3D *bminReturn, RMvertex3D *bmaxReturn)
 @pend

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

 RMvertex3D *bminReturn, *bmaxReturn;
 @aend

 @dstart

 Use this routine to obtain an RMprimitive's bounding box. Upon success,
 RM_CHILL is returned, and the bounding box minimum and maximum are copied
 into the application-supplied RMvertex3D parameters. If either of the
 RMprimitive's minimum or maximum bounding box parameters are NULL (i.e.,
 they have not been initialized), this routine will return RM_WHACKED.

 The application can request one, or both, of the bounding box minimum or
 maximum values. In other words, the application may specify NULL for
 either of the minimum or maximum bounding box return parameters.

 @dend
 * ----------------------------------------------------
 */
RMenum
rmPrimitiveGetBoundingBox(const RMprimitive *p,
			  RMvertex3D *bmin,
			  RMvertex3D *bmax)
{
    if (RM_ASSERT(p, "rmPrimitiveGetBoundingBox() error: the input RMprimitive is NULL.") == RM_WHACKED)
	return(RM_WHACKED);

    if (p->bmin == NULL)
	return RM_WHACKED;

    if (bmin != NULL)
	*bmin = *(p->bmin);

    if (p->bmax == NULL)
	return RM_WHACKED;

    if (bmax != NULL)
	*bmax = *(p->bmax);

    return RM_CHILL;
}


/*
 * ----------------------------------------------------
 * @Name rmPrimitiveGetType
 @pstart
 RMenum rmPrimitiveGetType (const RMprimitive *toQuery)
 @pend

 @astart const RMprimitive *toQuery - a handle to an RMprimitive
    (input). 
 @aend

 @dstart
 
 Returns to the caller the input RMprimitive's "type" RMenum
 attribute.  RM_WHACKED is returned if the input RMprimitive is
 NULL. For a list of RMprimitive type enums, please see
 rmPrimitiveNew().
 
 @dend
 * ----------------------------------------------------
 */
RMenum
rmPrimitiveGetType (const RMprimitive *t)
{
    if (RM_ASSERT(t, "rmPrimitiveGetType() error: the input RMprimitive is NULL.") == RM_WHACKED)
	return(RM_WHACKED);

    return(private_rmPrimitiveGetType(t));
}


/*
 * ----------------------------------------------------
 * @Name rmPrimitiveSetClientData
 @pstart
 RMenum rmPrimitiveSetClientData (RMprimitive *toModify,
			          void *clientData,
				  void (*cdFreeFunc)(RMprimitive *,void *))
 @pend

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

 void *clientData - a handle (input).

 void (*cdFreeFunc)(RMprimitive *, void *) - a handle to an application
    callback (input). 
 @aend

 @dstart

 This routine stores a memory handle (pointer) in an RMprimitive,
 returning RM_CHILL upon success or RM_WHACKED upon failure. RM
 basically ignores this handle; it's use, management and so forth is
 entirely under control of the application. This is a simple mechanism
 for applications to store a memory handle in an RMprimitive for
 subsequent use in an application-specific manner.

 Client data may be stored in both RMprimitives and RMnodes
 (rmNodeSetClientData).
 
 The "client data" handle may be later accessed using
 rmPrimitiveGetClientData.

 The callback function will be invoked when the RMprimitive is
 deleted.  The callback takes two parameters, a handle to the
 RMprimitive containing the client data handle, and the client data
 handle itself. The callback is provided so that applications may
 delete the data referenced by the client data handle stored in the
 RMprimitive.
 
 When the input client data handle is NULL, the old handle value is
 effectively overwritten. Any memory pointed to by the old client data
 handle will be lost (a potential memory leak).

 @dend
 * ----------------------------------------------------
 */
RMenum
rmPrimitiveSetClientData (RMprimitive *p,
			  void *cd,
			  void (*cd_free_func)(RMprimitive *,void *))
{
    if (RM_ASSERT(p, "rmPrimitiveSetClientData() error: the input RMprimitive is NULL") == RM_WHACKED)
	return(RM_WHACKED);
    
    p->clientData = cd;
    p->clientDataFreeFunc = cd_free_func;

    return(RM_CHILL);
}


/*
 * ----------------------------------------------------
 * @Name rmPrimitiveGetClientData
 @pstart
 void * rmPrimitiveGetClientData (const RMprimitive *toQuery)
 @pend

 @astart 
 const RMprimitive *toQuery - a handle to an RMprimitive (input).
 @aend

 @dstart

 Use this routine to obtain the client data handle stored in an
 RMprimitive.

 @dend
 * ----------------------------------------------------
 */
void *
rmPrimitiveGetClientData (const RMprimitive *p)
{
    if (RM_ASSERT(p, "rmPrimitiveGetClientData() error: the input RMprimitive is NULL") == RM_WHACKED)
	return((void *)NULL);
    
    return(p->clientData);
}


/*
 * ----------------------------------------------------
 * @Name rmPrimitiveSetVertex2D
 @pstart
 RMenum rmPrimitiveSetVertex2D (RMprimitive *toModify,
		                size_t nVertices,
			        RMvertex2D *vertexData,
			        RMenum copyEnum,
			        void (*appFreeFunc)(void *))
 @pend

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

 size_t nVertices - a size_t value, specifies the number of vertices
    that will be assigned to the RMprimitive (input).

 RMvertex2D *vertexData - a handle to a flat array of RMvertex2D
    objects (input).

 RMenum copyEnum - an RMenum value, may be either RM_COPY_DATA or
    RM_DONT_COPY_DATA (input).

 void (*appFreeFunc)(void *) - a handle to an application
    callback. When "copyEnum" is RM_DONT_COPY_DATA, the application
    must provide a callback used to release memory when the
    RMprimitive is delete (input). 
 @aend

 @dstart

 rmPrimitiveSetVertex2D is one of a family of routines used to assign
 data to RMprimitives. This routine assigns raw 2D vertex data to an
 RMprimitive, returning RM_CHILL upon success or RM_WHACKED upon
 failure.

 When "copyEnum" is set to RM_COPY_DATA, RM will make a copy of the
 raw vertex data provided in "vertexData." When "copyEnum" is set to
 RM_DONT_COPY_DATA, RM will not make a copy of the RMvertex2D data,
 but instead will simply copy the handle "vertexData" into the
 RMprimitive, and refer to the caller-supplied memory directly in
 later operations.

 When RM_DONT_COPY_DATA is specified, applications must supply a
 callback function that will be invoked when the RMvertex2D data in an
 RMprimitive is deleted. Such deletion occurs when the RMprimitive
 itself is deleted, the RMnode containing the RMprimitive is deleted
 (if the RMprimitive was assigned to an RMnode with rmNodeAddChild()),
 or if new RMvertex2D is assigned (more precisely, if new vertex data
 is assigned, regardless of whether or not it is 3D or 2D).  The
 application callback takes a single parameter: a handle to the
 underlying data array that is managed by the application.
 
 There is no corresponding "get vertex" routine. Primitive vertex data
 should be considered write-only by the application.
 
 @dend
 * ----------------------------------------------------
 */
RMenum
rmPrimitiveSetVertex2D (RMprimitive *p,
		        size_t n,
		        RMvertex2D *v,
		        RMenum copy_flag,
		        void (*freefunc)(void *))
{
    if ((private_rmPrimSetAssert(p, n, (void *)v, copy_flag, freefunc, "rmPrimitiveSetVertex2D")) == RM_WHACKED)
        return(RM_WHACKED);
    
    return(private_rmPrimitiveSetItem(p, RM_PRIMITIVE_2DVERTICES, n, sizeof(RMvertex2D), (void *)v, copy_flag, freefunc));
}


/*
 * ----------------------------------------------------
 * @Name rmPrimitiveSetVertex3D
 @pstart
 RMenum rmPrimitiveSetVertex3D (RMprimitive *toModify,
		                size_t nVertices,
			        RMvertex3D *vertexData,
			        RMenum copyEnum,
			        void (*appFreeFunc)(void *))
 @pend

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

 size_t nVertices - a size value, specifies the number of vertices
    that will be assigned to the RMprimitive (input).

 RMvertex3D *vertexData - a handle to a flat array of RMvertex3D
    objects (input).

 RMenum copyEnum - an RMenum value, may be either RM_COPY_DATA or
    RM_DONT_COPY_DATA (input).

 void (*appFreeFunc)(void *) - a handle to an application
    callback. When "copyEnum" is RM_DONT_COPY_DATA, the application
    must provide a callback used to release memory when the
    RMprimitive is delete (input). 
 @aend

 @dstart

 rmPrimitiveSetVertex3D is one of a family of routines used to assign
 data to RMprimitives. This routine assigns raw 3D vertex data to an
 RMprimitive, returning RM_CHILL upon success or RM_WHACKED upon
 failure.

 When "copyEnum" is set to RM_COPY_DATA, RM will make a copy of the
 raw vertex data provided in "vertexData." When "copyEnum" is set to
 RM_DONT_COPY_DATA, RM will not make a copy of the RMvertex3D data,
 but instead will simply copy the handle "vertexData" into the
 RMprimitive, and refer to the caller-supplied memory directly in
 later operations.

 When RM_DONT_COPY_DATA is specified, applications must supply a
 callback function that will be invoked when the RMvertex3D data in an
 RMprimitive is deleted. Such deletion occurs when the RMprimitive
 itself is deleted, the RMnode containing the RMprimitive is deleted
 (if the RMprimitive was assigned to an RMnode with rmNodeAddChild()),
 or if new RMvertex3D is assigned (more precisely, if new vertex data
 is assigned, regardless of whether or not it is 3D or 2D).  The
 application callback takes a single parameter: a handle to the
 underlying data array that is managed by the application.

 There is no corresponding "get vertex" routine. Primitive vertex data
 should be considered write-only by the application.

 NOTE: some compilers enforce 8-byte alignment/padding of C
 structures.  Since the RMvertex3D object consists of 3 floats, some
 compilers will pad memory to produce 8-byte alignment. The underlying
 RM data management infrastructure accomodates this added
 complexity. However, on such systems, callers that pass in a flat
 array of floats cast to RMvertex3D * should be aware that the data is
 considered to be a flat array of RMvertex3D objects inside this
 routine, not a flat array of floats.

 @dend
 * ----------------------------------------------------
 */
RMenum
rmPrimitiveSetVertex3D (RMprimitive *p,
		        size_t n,
		        RMvertex3D *v,
		        RMenum copy_flag,
		        void (*freefunc)(void *))
{
    if ((private_rmPrimSetAssert(p, n, (void *)v, copy_flag, freefunc, "rmPrimitiveSetVertex3D")) == RM_WHACKED)
        return(RM_WHACKED);

    return(private_rmPrimitiveSetItem(p, RM_PRIMITIVE_3DVERTICES, n, sizeof(RMvertex3D), (void *)v, copy_flag, freefunc));
}


/*
 * ----------------------------------------------------
 * @Name rmPrimitiveSetColor3D
 @pstart
 RMenum rmPrimitiveSetColor3D (RMprimitive *toModify,
		               size_t nColors,
			       RMcolor3D *colorData,
			       RMenum copyEnum,
			       void (*appFreeFunc)(void *))
 @pend

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

 size_t nColors - a size_t value, specifies the number of 3-component
    colors that will be assigned to the RMprimitive (input).

 RMcolor3D *vertexData - a handle to a flat array of RMcolor3D objects
    (input).

 RMenum copyEnum - an RMenum value, may be either RM_COPY_DATA or
    RM_DONT_COPY_DATA (input).

 void (*appFreeFunc)(void *) - a handle to an application
    callback. When "copyEnum" is RM_DONT_COPY_DATA, the application
    must provide a callback used to release memory when the
    RMprimitive is delete (input). 
 @aend

 @dstart

 rmPrimitiveSetColor3D is one of a family of routines used to assign
 data to RMprimitives. This routine assigns raw 3-component color data
 to an RMprimitive, returning RM_CHILL upon success or RM_WHACKED upon
 failure.

 When "copyEnum" is set to RM_COPY_DATA, RM will make a copy of the
 raw color data provided in "colorData." When "copyEnum" is set to
 RM_DONT_COPY_DATA, RM will not make a copy of the RMcolor3D data, but
 instead will simply copy the handle "colorData" into the RMprimitive,
 and refer to the caller-supplied memory directly in later operations.

 When RM_DONT_COPY_DATA is specified, applications must supply a
 callback function that will be invoked when the RMcolor3D data in an
 RMprimitive is deleted. Such deletion occurs when the RMprimitive
 itself is deleted, the RMnode containing the RMprimitive is deleted
 (if the RMprimitive was assigned to an RMnode with rmNodeAddChild()),
 or if new RMcolor3D is assigned (more precisely, if new color data is
 assigned, regardless of whether or not it is 3D or 4D).  The
 application callback takes a single parameter: a handle to the
 underlying data array that is managed by the application.

 There is no corresponding "get colors" routine. Primitive color data
 should be considered write-only by the application.

 NOTE: some compilers enforce 8-byte alignment/padding of C
 structures.  Since the RMcolor3D object consists of 3 floats, some
 compilers will pad memory to produce 8-byte alignment. The underlying
 RM data management infrastructure accomodates this added
 complexity. However, on such systems, callers that pass in a flat
 array of floats cast to RMcolor3D * should be aware that the data is
 considered to be a flat array of RMcolor3D objects inside this
 routine, not a flat array of floats.

 In RM, 3-component color data is RGB. 4-component colors are RGBA.

 @dend
 * ----------------------------------------------------
 */
RMenum
rmPrimitiveSetColor3D (RMprimitive *p,
		       size_t n,
		       RMcolor3D *v,
		       RMenum copy_flag,
		       void (*freefunc)(void *))
{
    if ((private_rmPrimSetAssert(p, n, (void *)v, copy_flag, freefunc, "rmPrimitiveSetColor3D")) == RM_WHACKED)
        return(RM_WHACKED);
    
    return(private_rmPrimitiveSetItem(p, RM_PRIMITIVE_3COLORS, n, sizeof(RMvertex3D), (void *)v, copy_flag, freefunc));
}


/*
 * ----------------------------------------------------
 * @Name rmPrimitiveSetColor4D
 @pstart
 RMenum rmPrimitiveSetColor4D (RMprimitive *toModify,
		               size_t nColors,
			       RMcolor4D *colorData,
			       RMenum copyEnum,
			       void (*appFreeFunc)(void *))
 @pend

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

 size_t nColors - a size_t value, specifies the number of 4-component
    colors that will be assigned to the RMprimitive (input).

 RMvertex4D *vertexData - a handle to a flat array of RMcolor4D
    objects (input).

 RMenum copyEnum - an RMenum value, may be either RM_COPY_DATA or
    RM_DONT_COPY_DATA (input).

 void (*appFreeFunc)(void *) - a handle to an application
    callback. When "copyEnum" is RM_DONT_COPY_DATA, the application
    must provide a callback used to release memory when the
    RMprimitive is delete (input). 
 @aend

 @dstart

 rmPrimitiveSetColor4D is one of a family of routines used to assign
 data to RMprimitives. This routine assigns raw 4-component color data
 to an RMprimitive, returning RM_CHILL upon success or RM_WHACKED upon
 failure.

 When "copyEnum" is set to RM_COPY_DATA, RM will make a copy of the
 raw color data provided in "colorData." When "copyEnum" is set to
 RM_DONT_COPY_DATA, RM will not make a copy of the RMcolor4D data, but
 instead will simply copy the handle "colorData" into the RMprimitive,
 and refer to the caller-supplied memory directly in later operations.

 When RM_DONT_COPY_DATA is specified, applications must supply a
 callback function that will be invoked when the RMcolor4D data in an
 RMprimitive is deleted. Such deletion occurs when the RMprimitive
 itself is deleted, the RMnode containing the RMprimitive is deleted
 (if the RMprimitive was assigned to an RMnode with rmNodeAddChild()),
 or if new RMcolor4D is assigned (more precisely, if new color data is
 assigned, regardless of whether or not it is 3D or 4D).  The
 application callback takes a single parameter: a handle to the
 underlying data array that is managed by the application.

 There is no corresponding "get colors" routine. Primitive color data
 should be considered write-only by the application.

 In RM, 3-component color data is RGB. 4-component colors are RGBA.

 @dend
 * ----------------------------------------------------
 */
RMenum
rmPrimitiveSetColor4D (RMprimitive *p,
		       size_t n,
		       RMcolor4D *v,
		       RMenum copy_flag,
		       void (*freefunc)(void *))
{
    if ((private_rmPrimSetAssert(p, n, (void *)v, copy_flag, freefunc, "rmPrimitiveSetColor4D")) == RM_WHACKED)
        return(RM_WHACKED);
    
    return(private_rmPrimitiveSetItem(p, RM_PRIMITIVE_4COLORS, n, sizeof(RMvertex4D), (void *)v, copy_flag, freefunc));
}


/*
 * ----------------------------------------------------
 * @Name  rmPrimitiveSetRadii
 @pstart
 RMenum rmPrimitiveSetRadii (RMprimitive *toModify,
		             size_t nRadii,
			     float *radii,
			     RMenum copyEnum,
			     void (*freeFunc)(void *))
 @pend

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

 size_t nRadii - a size_t values specifying the number of input radius
    values, the length of the radii[] array (input).

 float *radii - a flat array of floating point values (input).

 RMenum copyEnum - an RMenum value, may be either RM_COPY_DATA or
    RM_DONT_COPY_DATA (input).

 void (*freeFunc)(void *) - a handle to an application callback,
    required when copyEnum is RM_DONT_COPY_DATA (input).
 @aend

 @dstart

 Use this routine to assign radius values to RMprimitives. Radius
 values are used by sphere, cone and cylinder primitives. Returns
 RM_CHILL upon success, or RM_WHACKED upon failure.
 
 When "copyEnum" is set to RM_COPY_DATA, RM will make a copy of the
 raw radius data provided in "radii." When "copyEnum" is set to
 RM_DONT_COPY_DATA, RM will not make a copy of the radius data, but
 instead will simply copy the handle "colorData" into the RMprimitive,
 and refer to the caller-supplied memory directly in later operations.

 When RM_DONT_COPY_DATA is specified, applications must supply a
 callback function that will be invoked when the radius data in an
 RMprimitive is deleted. Such deletion occurs when the RMprimitive
 itself is deleted, the RMnode containing the RMprimitive is deleted
 (if the RMprimitive was assigned to an RMnode with rmNodeAddChild()),
 or if new radius is assigned.  The application callback takes a
 single parameter: a handle to the underlying data array that is
 managed by the application.

 There is no corresponding "get radii" routine. Primitive radius data
 should be considered write-only by the application.

 @dend
 * ----------------------------------------------------
 */
RMenum
rmPrimitiveSetRadii (RMprimitive *p,
		     size_t n,
		     float *r,
		     RMenum copy_flag,
		     void (*freefunc)(void *))
{
    if (( private_rmPrimSetAssert(p, n, (void *)r, copy_flag, freefunc, "rmPrimitiveSetRadii")) ==RM_WHACKED)
        return(RM_WHACKED);
    
    return(private_rmPrimitiveSetItem(p, RM_PRIMITIVE_RADII, n, sizeof(float), (void *)r, copy_flag, freefunc));
}


/*
 * ----------------------------------------------------
 * @Name rmPrimitiveSetNormal3D
 @pstart
 RMenum rmPrimitiveSetNormal3D (RMprimitive *toModify,
		                size_t nNormals,
			        RMvertex3D *normalsData,
			        RMenum copyEnum,
			        void (*freeFunc)(void *))
 @pend

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

 size_t nNormals - a size_t value specifying the number of input
    normals in the normals[] array (input).

 RMvertex3D *normalsData - a handle to an RMvertex3D array (input).

 RMenum copyEnum - an RMenum value, may be either RM_COPY_DATA or
    RM_DONT_COPY_DATA (input).

 void (*freeFunc)(void *) - a handle to an application callback,
    required when copyEnum is RM_DONT_COPY_DATA (input). 
 @aend

 @dstart

 This routine is used to assign a flat array of normals to an
 RMprimitive, returning RM_CHILL upon success or RM_WHACKED upon
 failure.  In most instances, per-vertex normals are required. Quads
 and disjoint triangle primitives allow per-face normals.

 When "copyEnum" is set to RM_COPY_DATA, RM will make a copy of the
 raw normals data provided in "normalsData." When "copyEnum" is set to
 RM_DONT_COPY_DATA, RM will not make a copy of the RMvertex3D data,
 but instead will simply copy the handle "normalsData" into the
 RMprimitive, and refer to the caller-supplied memory directly in
 later operations.

 When RM_DONT_COPY_DATA is specified, applications must supply a
 callback function that will be invoked when the RMvertex3D data in an
 RMprimitive is deleted. Such deletion occurs when the RMprimitive
 itself is deleted, the RMnode containing the RMprimitive is deleted
 (if the RMprimitive was assigned to an RMnode with rmNodeAddChild()),
 or if new RMvertex3D is assigned.  The application callback takes a
 single parameter: a handle to the underlying data array that is
 managed by the application.

 There is no corresponding "get normals" routine. Primitive normal
 data should be considered write-only by the application.

 NOTE: some compilers enforce 8-byte alignment/padding of C
 structures.  Since the RMvertex3D object consists of 3 floats, some
 compilers will pad memory to produce 8-byte alignment. The underlying
 RM data management infrastructure accomodates this added
 complexity. However, on such systems, callers that pass in a flat
 array of floats cast to RMvertex3D * should be aware that the data is
 considered to be a flat array of RMvertex3D objects inside this
 routine, not a flat array of floats.

 @dend
 * ----------------------------------------------------
 */
RMenum
rmPrimitiveSetNormal3D (RMprimitive *p,
		        size_t n,
		        RMvertex3D *r,
		        RMenum copy_flag,
		        void (*freefunc)(void *))
{
    if (( private_rmPrimSetAssert(p, n, (void *)r, copy_flag, freefunc, "rmPrimitiveSetNormal3D")) == RM_WHACKED)
        return(RM_WHACKED);
    
    return(private_rmPrimitiveSetItem(p, RM_PRIMITIVE_NORMALS, n, sizeof(RMvertex3D), (void *)r, copy_flag, freefunc));
}


/*
 * ----------------------------------------------------
 * @Name rmPrimitiveSetTexcoord1D
 @pstart
 RMenum rmPrimitiveSetTexcoord1D (RMprimitive *toModify,
		                  size_t nTexCoords,
				  float *texCoordData,
				  RMenum copyEnum,
				  void (*appFreeFunc)(void *))
 @pend

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

 size_t nTexCoords - a size_t value, specifies the number of 1D texture
    coordinates that will be assigned to the RMprimitive (input).

 float *texCoordData - a handle to a flat array of float (input).

 RMenum copyEnum - an RMenum value, may be either RM_COPY_DATA or
    RM_DONT_COPY_DATA (input).

 void (*appFreeFunc)(void *) - a handle to an application
    callback. When "copyEnum" is RM_DONT_COPY_DATA, the application
    must provide a callback used to release memory when the
    RMprimitive is delete (input). 
 @aend

 @dstart

 rmPrimitiveSetTexcoord1D is one of a family of routines used to
 assign data to RMprimitives. This routine assigns 1D texture
 coordinates to an RMprimitive, returning RM_CHILL upon success or
 RM_WHACKED upon failure.

 When "copyEnum" is set to RM_COPY_DATA, RM will make a copy of the
 raw vertex data provided in "texCoordData." When "copyEnum" is set to
 RM_DONT_COPY_DATA, RM will not make a copy of the float data,
 but instead will simply copy the handle "texCoordData" into the
 RMprimitive, and refer to the caller-supplied memory directly in
 later operations.

 When RM_DONT_COPY_DATA is specified, applications must supply a
 callback function that will be invoked when the texture coordinate
 data in an RMprimitive is deleted. Such deletion occurs when the
 RMprimitive itself is deleted, the RMnode containing the RMprimitive
 is deleted (if the RMprimitive was assigned to an RMnode with
 rmNodeAddChild()), or if new texture coordinate data is assigned.
 The application callback takes a single parameter: a handle to the
 underlying data array that is managed by the application.
 
 There is no corresponding "get texture coordinate" routine. Primitive
 vertex data should be considered write-only by the application.

 @dend
 * ----------------------------------------------------
 */
RMenum
rmPrimitiveSetTexcoord1D (RMprimitive *p,
			  size_t n,
			  float *r,
			  RMenum copy_flag,
			  void (*freefunc)(void *))
{
    if ((private_rmPrimSetAssert(p, n, (void *)r, copy_flag, freefunc, "rmPrimitiveSetTexcoord1D")) == RM_WHACKED)
	return(RM_WHACKED);
 
    return(private_rmPrimitiveSetItem(p, RM_PRIMITIVE_1DTCOORDS, n, sizeof(float), (void *)r, copy_flag, freefunc));
}

/*
 * ----------------------------------------------------
 * @Name rmPrimitiveSetTexcoord2D
 @pstart
 RMenum rmPrimitiveSetTexcoord2D (RMprimitive *toModify,
		                  size_t nTexCoords,
				  RMvertex2D *texCoordData,
				  RMenum copyEnum,
				  void (*appFreeFunc)(void *))
 @pend

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

 size_t nTexCoords - a size_t value, specifies the number of 2D texture
    coordinates that will be assigned to the RMprimitive (input).

 RMvertex2D *texCoordData - a handle to a flat array of RMvertex2D
    objects (input).

 RMenum copyEnum - an RMenum value, may be either RM_COPY_DATA or
    RM_DONT_COPY_DATA (input).

 void (*appFreeFunc)(void *) - a handle to an application
    callback. When "copyEnum" is RM_DONT_COPY_DATA, the application
    must provide a callback used to release memory when the
    RMprimitive is delete (input). 
 @aend

 @dstart

 rmPrimitiveSetTexcoord2D is one of a family of routines used to
 assign data to RMprimitives. This routine assigns 2D texture
 coordinates to an RMprimitive, returning RM_CHILL upon success or
 RM_WHACKED upon failure.

 When "copyEnum" is set to RM_COPY_DATA, RM will make a copy of the
 raw vertex data provided in "texCoordData." When "copyEnum" is set to
 RM_DONT_COPY_DATA, RM will not make a copy of the RMvertex2D data,
 but instead will simply copy the handle "texCoordData" into the
 RMprimitive, and refer to the caller-supplied memory directly in
 later operations.

 When RM_DONT_COPY_DATA is specified, applications must supply a
 callback function that will be invoked when the texture coordinate
 data in an RMprimitive is deleted. Such deletion occurs when the
 RMprimitive itself is deleted, the RMnode containing the RMprimitive
 is deleted (if the RMprimitive was assigned to an RMnode with
 rmNodeAddChild()), or if new texture coordinate data is assigned.
 The application callback takes a single parameter: a handle to the
 underlying data array that is managed by the application.
 
 There is no corresponding "get texture coordinate" routine. Primitive
 vertex data should be considered write-only by the application.

 @dend
 * ----------------------------------------------------
 */
RMenum
rmPrimitiveSetTexcoord2D (RMprimitive *p,
			  size_t n,
			  RMvertex2D *r,
			  RMenum copy_flag,
			  void (*freefunc)(void *))
{
    if ((private_rmPrimSetAssert(p, n, (void *)r, copy_flag, freefunc, "rmPrimitiveSetTexcoord2D")) == RM_WHACKED)
	return(RM_WHACKED);
 
    return(private_rmPrimitiveSetItem(p, RM_PRIMITIVE_2DTCOORDS, n, sizeof(RMvertex2D), (void *)r, copy_flag, freefunc));
}



/*
 * ----------------------------------------------------
 * @Name rmPrimitiveSetTexcoord3D
 @pstart
 RMenum rmPrimitiveSetTexcoord3D (RMprimitive *toModify,
		                  size_t nTexcoords,
				  RMvertex3D *texCoordData,
				  RMenum copyEnum,
				  void (*appFreeFunc)(void *))
 @pend

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

 size_t nTexcoords - a size_t value, specifies the number of texture
    coordinates that will be assigned to the RMprimitive (input).

 RMvertex3D *texCoordData - a handle to a flat array of RMvertex3D
    objects (input).

 RMenum copyEnum - an RMenum value, may be either RM_COPY_DATA or
    RM_DONT_COPY_DATA (input).

 void (*appFreeFunc)(void *) - a handle to an application
    callback. When "copyEnum" is RM_DONT_COPY_DATA, the application
    must provide a callback used to release memory when the
    RMprimitive is delete (input). 
 @aend

 @dstart

 rmPrimitiveSetTexcoord3D is one of a family of routines used to
 assign data to RMprimitives. This routine assigns raw 3D texture
 coordinate data to an RMprimitive, returning RM_CHILL upon success or
 RM_WHACKED upon failure.

 When "copyEnum" is set to RM_COPY_DATA, RM will make a copy of the
 raw texture coordinate data provided in "texCoordData." When
 "copyEnum" is set to RM_DONT_COPY_DATA, RM will not make a copy of
 the RMvertex3D data, but instead will simply copy the handle
 "texCoordData" into the RMprimitive, and refer to the caller-supplied
 memory directly in later operations.

 When RM_DONT_COPY_DATA is specified, applications must supply a
 callback function that will be invoked when the RMvertex3D data in an
 RMprimitive is deleted. Such deletion occurs when the RMprimitive
 itself is deleted, the RMnode containing the RMprimitive is deleted
 (if the RMprimitive was assigned to an RMnode with rmNodeAddChild()),
 or if new RMvertex3D is assigned (more precisely, if new texture
 coordinate data is assigned, regardless of whether or not it is 3D or
 2D).  The application callback takes a single parameter: a handle to
 the underlying data array that is managed by the application.

 There is no corresponding "get texture coordinate" routine. Primitive
 vertex data should be considered write-only by the application.

 NOTE: some compilers enforce 8-byte alignment/padding of C
 structures.  Since the RMvertex3D object consists of 3 floats, some
 compilers will pad memory to produce 8-byte alignment. The underlying
 RM data management infrastructure accomodates this added
 complexity. However, on such systems, callers that pass in a flat
 array of floats cast to RMvertex3D * should be aware that the data is
 considered to be a flat array of RMvertex3D objects inside this
 routine, not a flat array of floats.

 @dend
 * ----------------------------------------------------
 */
RMenum
rmPrimitiveSetTexcoord3D (RMprimitive *p,
			  size_t n,
			  RMvertex3D *r,
			  RMenum copy_flag,
			  void (*freefunc)(void *))
{
    if ((private_rmPrimSetAssert(p, n, (void *)r, copy_flag, freefunc, "rmPrimitiveSetTexcoord3D")) == RM_WHACKED)
        return(RM_WHACKED);

    return(private_rmPrimitiveSetItem(p, RM_PRIMITIVE_3DTCOORDS, n, sizeof(RMvertex3D), (void *)r, copy_flag, freefunc));
}


/*
 * ----------------------------------------------------
 * @Name rmPrimitiveSetMultiTexcoord1D
 @pstart
 RMenum rmPrimitiveSetMultiTexcoord1D (RMprimitive *toModify,
		                       size_t nTexCoords,
				       float *texCoordData,
				       RMenum copyEnum,
				       void (*appFreeFunc)(void *),
				       int textureUnit)
 @pend

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

 size_t nTexCoords - a size_t value, specifies the number of 1D texture
    coordinates that will be assigned to the RMprimitive (input).

 float *texCoordData - a handle to a flat array of floats (input).

 RMenum copyEnum - an RMenum value, may be either RM_COPY_DATA or
    RM_DONT_COPY_DATA (input).

 void (*appFreeFunc)(void *) - a handle to an application
    callback. When "copyEnum" is RM_DONT_COPY_DATA, the application
    must provide a callback used to release memory when the
    RMprimitive is delete (input).

 int textureUnit - an integer value specifying with which multitexturing
    unit the texture coordinates will be used. Valid values are in the
    range zero through RM_MAX_MULTITEXTURES-1.
 @aend

 @dstart

 rmPrimitiveSetMultiTexcoord1D is nearly identical in function and
 return values to rmPrimitiveSetTexcoord1D. The difference is the ability
 to assign texture coordinates to a specific texturing unit in a
 multitexturing environment.

 Note that while valid values for the input textureUnit parameter are
 in the range zero through RM_MAX_MULTITEXTURES-1, this routine performs
 no error checking to verify that the textureUnit input value is valid
 for a particular OpenGL implementation. The constant RM_MAX_MULTITEXTURES
 is independent of the actual number of texture units supported by an
 OpenGL implementation. When multitexturing is supported (use the routine
 rmPipeGetNumMultitextureUnits to check for the availability on a given
 OpenGL implementation), the minimum number of texture units to be supported
 is two; our Quadro4 cards have four texture units; Mesa provides eight
 texture units.

 @dend
 * ----------------------------------------------------
 */
RMenum
rmPrimitiveSetMultiTexcoord1D (RMprimitive *p,
			       size_t n,
			       float *r,
			       RMenum copy_flag,
			       void (*freefunc)(void *),
			       int textureUnit)
{
    RMenum stat;
    if ((private_rmPrimSetAssert(p, n, (void *)r, copy_flag, freefunc, "rmPrimitiveSetMultiTexcoord1D")) == RM_WHACKED)
	return(RM_WHACKED);


    stat = private_rmPrimitiveSetMultiTexcoordBlob(p, RM_PRIMITIVE_MULTI_1DTCOORDS, n, sizeof(float), (void *)r, copy_flag, freefunc, textureUnit);

    return stat;
}


/*
 * ----------------------------------------------------
 * @Name rmPrimitiveSetMultiTexcoord2D
 @pstart
 RMenum rmPrimitiveSetMultiTexcoord2D (RMprimitive *toModify,
		                       size_t nTexCoords,
				       RMvertex2D *texCoordData,
				       RMenum copyEnum,
				       void (*appFreeFunc)(void *),
				       int textureUnit)
 @pend

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

 size_t nTexCoords - a size_t value, specifies the number of 2D texture
    coordinates that will be assigned to the RMprimitive (input).

 RMvertex2D *texCoordData - a handle to a flat array of RMvertex2D
    objects (input).

 RMenum copyEnum - an RMenum value, may be either RM_COPY_DATA or
    RM_DONT_COPY_DATA (input).

 void (*appFreeFunc)(void *) - a handle to an application
    callback. When "copyEnum" is RM_DONT_COPY_DATA, the application
    must provide a callback used to release memory when the
    RMprimitive is delete (input).

 int textureUnit - an integer value specifying with which multitexturing
    unit the texture coordinates will be used. Valid values are in the
    range zero through RM_MAX_MULTITEXTURES-1.
 @aend

 @dstart

 rmPrimitiveSetMultiTexcoord2D is nearly identical in function and
 return values to rmPrimitiveSetTexcoord2D. The difference is the ability
 to assign texture coordinates to a specific texturing unit in a
 multitexturing environment.

 Note that while valid values for the input textureUnit parameter are
 in the range zero through RM_MAX_MULTITEXTURES-1, this routine performs
 no error checking to verify that the textureUnit input value is valid
 for a particular OpenGL implementation. The constant RM_MAX_MULTITEXTURES
 is independent of the actual number of texture units supported by an
 OpenGL implementation. When multitexturing is supported (use the routine
 rmPipeGetNumMultitextureUnits to check for the availability on a given
 OpenGL implementation), the minimum number of texture units to be supported
 is two; our Quadro4 cards have four texture units; Mesa provides eight
 texture units.

 @dend
 * ----------------------------------------------------
 */
RMenum
rmPrimitiveSetMultiTexcoord2D (RMprimitive *p,
			       size_t n,
			       RMvertex2D *r,
			       RMenum copy_flag,
			       void (*freefunc)(void *),
			       int textureUnit)
{
    RMenum stat;

    if ((private_rmPrimSetAssert(p, n, (void *)r, copy_flag, freefunc, "rmPrimitiveSetMultiTexcoord2D")) == RM_WHACKED)
	return(RM_WHACKED);

    stat = private_rmPrimitiveSetMultiTexcoordBlob(p, RM_PRIMITIVE_MULTI_2DTCOORDS, n, sizeof(RMvertex2D), (void *)r, copy_flag, freefunc, textureUnit);

    return stat;
}


/*
 * ----------------------------------------------------
 * @Name rmPrimitiveSetMultiTexcoord3D
 @pstart
 RMenum rmPrimitiveSetMultiTexcoord3D (RMprimitive *toModify,
		                       size_t nTexCoords,
				       RMvertex3D *texCoordData,
				       RMenum copyEnum,
				       void (*appFreeFunc)(void *),
				       int textureUnit)
 @pend

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

 size_t nTexCoords - a size_t value, specifies the number of 3D texture
    coordinates that will be assigned to the RMprimitive (input).

 RMvertex3D *texCoordData - a handle to a flat array of RMvertex3D's. (input)

 RMenum copyEnum - an RMenum value, may be either RM_COPY_DATA or
    RM_DONT_COPY_DATA (input).

 void (*appFreeFunc)(void *) - a handle to an application
    callback. When "copyEnum" is RM_DONT_COPY_DATA, the application
    must provide a callback used to release memory when the
    RMprimitive is delete (input).

 int textureUnit - an integer value specifying with which multitexturing
    unit the texture coordinates will be used. Valid values are in the
    range zero through RM_MAX_MULTITEXTURES-1.
 @aend

 @dstart

 rmPrimitiveSetMultiTexcoord3D is nearly identical in function and
 return values to rmPrimitiveSetTexcoord3D. The difference is the ability
 to assign texture coordinates to a specific texturing unit in a
 multitexturing environment.

 Note that while valid values for the input textureUnit parameter are
 in the range zero through RM_MAX_MULTITEXTURES-1, this routine performs
 no error checking to verify that the textureUnit input value is valid
 for a particular OpenGL implementation. The constant RM_MAX_MULTITEXTURES
 is independent of the actual number of texture units supported by an
 OpenGL implementation. When multitexturing is supported (use the routine
 rmPipeGetNumMultitextureUnits to check for the availability on a given
 OpenGL implementation), the minimum number of texture units to be supported
 is two; our Quadro4 cards have four texture units; Mesa provides eight
 texture units.

 @dend
 * ----------------------------------------------------
 */
RMenum
rmPrimitiveSetMultiTexcoord3D (RMprimitive *p,
			       size_t n,
			       RMvertex3D *r,
			       RMenum copy_flag,
			       void (*freefunc)(void *),
			       int textureUnit)
{
    RMenum stat;
    if ((private_rmPrimSetAssert(p, n, (void *)r, copy_flag, freefunc, "rmPrimitiveSetMultiTexcoord3D")) == RM_WHACKED)
	return(RM_WHACKED);

    stat = private_rmPrimitiveSetMultiTexcoordBlob(p, RM_PRIMITIVE_MULTI_3DTCOORDS, n, sizeof(RMvertex2D), (void *)r, copy_flag, freefunc, textureUnit);

    return stat;
    /*    return(private_rmPrimitiveSetItem(p, RM_PRIMITIVE_MULTI_2DTCOORDS, n, sizeof(RMvertex2D), (void *)r, copy_flag, freefunc, textureUnit)); */
}


/*
 * ----------------------------------------------------
 * @Name rmPrimitiveSetIndices 
 @pstart
 RMenum rmPrimitiveSetIndices (RMprimitive *toModify,
		               size_t numIndices,
			       int *indicesArray,
			       RMenum copyEnum,
			       void (*appFreeFunc)(void *))
 @pend

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

 size_t numIndices - a size_t value specifying the length of the
    indicesArray[] array (input).

 int *indicesArray - a flat array of integer indices (input).

 RMenum copyEnum - an RMenum value, may be either RM_COPY_DATA or
    RM_DONT_COPY_DATA (input).

 void (*appFreeFunc)(void *) - a handle to an application callback,
    required when copyEnum is RM_DONT_COPY_DATA (input).
 @aend

 @dstart

 Use this routine to set "index values" in an RMprimitive. Returns
 RM_CHILL upon success, or RM_WHACKED upon failure.

 Some RMprimitive objects allow the use of "indexed" vertices. At this
 time (Feb 2004), these include RM_INDEXED_TFAN, RM_INDEXED_TEXT, and
 RM_INDEXED_BITMAP, RM_INDEXED_QUADS, RM_INDEXED_TRIANGLES,
 and RM_INDEXED_TRIANGLE_STRIP. (Plans
 are underway to grow the number of indexed primitives).

 In non-indexed primitives, the number of objects drawn on screen is a
 function of the number of vertices in the RMprimitive and the
 primitive type itself. For example, in RM_TRIANGLES primitives
 (disjoint triangles), the number of triangles that are drawn is the
 number of vertices divided by 3. For RM_INDEXED_TRIANGLES, the number
 of triangles that will be drawn is instead the number of indices
 divided by 3. For RM_INDEXED_QUADS, the number of quads drawn is the
 number of indices divided by 4. For RM_INDEXED_TRIANGLE_STRIP, the
 number of triangles draw is the number of indices-2 (e.g, four
 indices produces 2 triangles).

 Each entry in the index array is an index into another array.
 
 Index values in the RM_INDEXED_TEXT are offsets into an array of text
 strings. Index values in RM_INDEXED_BITMAP primitives are offsets
 into an array of RMbitmap objects. Index values in RM_INDEXED_TFAN
 are offsets into a vertex array.

 When "copyEnum" is set to RM_COPY_DATA, RM will make a copy of the
 raw index data provided in "indexArray." When "copyEnum" is set to
 RM_DONT_COPY_DATA, RM will not make a copy of the index data, but
 instead will simply copy the handle "indexArray" into the
 RMprimitive, and refer to the caller-supplied memory directly in
 later operations.

 When RM_DONT_COPY_DATA is specified, applications must supply a
 callback function that will be invoked when the index data in an
 RMprimitive is deleted. Such deletion occurs when the RMprimitive
 itself is deleted, the RMnode containing the RMprimitive is deleted
 (if the RMprimitive was assigned to an RMnode with rmNodeAddChild()),
 or if new index is assigned. The application callback takes a single
 parameter: a handle to the underlying data array that is managed by
 the application.

 @dend
 * ----------------------------------------------------
 */
RMenum
rmPrimitiveSetIndices (RMprimitive *t,
		       size_t npts,
		       int *indices,
		       RMenum copy_enum,
		       void (*freefunc)(void *))
{
    if (private_rmPrimSetAssert(t, npts, (void *)indices, copy_enum, freefunc, "rmPrimitiveSetIndices") == RM_WHACKED)
	return(RM_WHACKED);
    
    return(private_rmPrimitiveSetItem((RMprimitive *)t, RM_PRIMITIVE_INDICES, npts, sizeof(int), (void *)indices, copy_enum, freefunc));
}


/*
 * ----------------------------------------------------
 * @Name rmPrimitiveSetSprites
 @pstart
 RMenum rmPrimitiveSetSprites (RMprimitive *toModify,
		               size_t nSprites,
			       RMimage **spriteArray)
 @pend

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

 size_t nSprites - an integer value specifying the number of sprites that
    will be assigned to an RM_SPRITE primitive (input).

 RMimage **spriteArray - an array of RMimage handles (input).
 @aend

 @dstart

 RM_SPRITE primitives are image-based primitives consisting of vertex
 and image data. rmPrimitiveSetSprites is used to assign the image
 data to the RM_SPRITE primitive.

 Shared data management of the raw pixel data is specified as an
 interface to the RMimage object, hence specification of
 RM_COPY_DATA/RM_DONT_COPY_DATA is not necessary at the RMprimitive
 level for RM_SPRITES. Inside this routine, all the images in the
 spriteArray are duplicated with rmImageDup(). Please refer to
 rmImageDup() for more details about shared data management of pixel
 data in RMimage objects.

 @dend
 * ----------------------------------------------------
 */
RMenum
rmPrimitiveSetSprites (RMprimitive *p,
		       size_t nsprites,
		       RMimage **sprite_array)
{
    if ((private_rmPrimSetAssert(p, nsprites, (void *)sprite_array, RM_COPY_DATA,NULL, "rmPrimitiveSetSprites")) == RM_WHACKED)
        return(RM_WHACKED);
    
    return(private_rmPrimitiveSetItem(p, RM_PRIMITIVE_SPRITES, nsprites, sizeof(RMimage *), sprite_array, RM_COPY_DATA, NULL));
}


/*
 * ----------------------------------------------------
 * @Name rmPrimitiveSetBitmaps
 @pstart
 RMenum rmPrimitiveSetBitmaps (RMprimitive *toModify,
		               size_t nBitmaps,
			       RMbitmap **bmapArray)
 @pend

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

 size_t nBitmaps - an integer value specifying the number of RMbitmap
    objects to assign to an RMprimitive (input).

 RMbitmap **bmapArray - an array of RMbitmap handles (input). 
 @aend

 @dstart

 Primitives of the type RM_BITMAP or RM_INDEXED_BITMAP consist of
 vertex data and RMbitmap objects (the indexed form also takes
 indices). Use this routine to assign RMbitmap data to the
 RMprimitive. It returns RM_CHILL upon success, or RM_WHACKED upon
 failure.

 There is no shared data management of RMbitmap data since these
 objects are small.

 @dend
 * ----------------------------------------------------
 */
RMenum
rmPrimitiveSetBitmaps (RMprimitive *p,
		       size_t nbitmaps,
		       RMbitmap **bmaplist)
{
    if ((private_rmPrimSetAssert(p, nbitmaps, (void *)bmaplist, RM_COPY_DATA, NULL, "rmPrimitiveSetBitmaps")) == RM_WHACKED)
        return(RM_WHACKED);

    return(private_rmPrimitiveSetItem(p, RM_PRIMITIVE_BITMAPS, nbitmaps, sizeof(RMbitmap), (void *)bmaplist, RM_COPY_DATA, NULL));
}


/*
 * ----------------------------------------------------
 * @Name rmPrimitiveSetQmeshDims
 @pstart
 RMenum rmPrimitiveSetQmeshDims (RMprimitive *toModify,
			         int uSize,
				 int vSize)
 @pend

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

 int uSize, vSize - integer values specifying the dimensions of a
    lattice defining a quadmesh (input). 
 @aend

 @dstart

 A quadmesh primitive may be considered as a two-dimensional lattice
 of points. The points themselves may be specified with two or three
 dimensional coordinate values. This routine is used to specify the
 number of points in each of the two dimensions of the lattice, and
 returns RM_CHILL upon success or RM_WHACKED upon failure.

 Quadmesh primitives require, in addition to a grid size set with
 rmPrimitiveSetQmeshDims, vertex data set with rmPrimitiveSetVertex3D
 or rmPrimitiveSetVertex2D. Colors, normals and texture coordinates
 are all optional.

 The routines rmPrimitiveSetQmeshDims and rmPrimitiveSetVertex3D/2D
 may be called in any order, but both must be called before a quadmesh
 primitive is rendered.
 
 @dend
 * ----------------------------------------------------
 */
RMenum
rmPrimitiveSetQmeshDims (RMprimitive *p,
			 int usize,
			 int vsize)
{
    int dims[2];

    if (RM_ASSERT(p, "rmPrimitiveSetQmeshDims() error: the input RMprimitive is NULL. ") == RM_WHACKED)
	return(RM_WHACKED);

    dims[0] = usize;
    dims[1] = vsize;

    return(private_rmPrimitiveSetItem(p, RM_PRIMITIVE_QMESHDIMS, 2, sizeof(int), (void *)dims, RM_COPY_DATA, NULL));
}


/*
 * ----------------------------------------------------
 * @Name rmPrimitiveSetOmeshDims
 @pstart
 RMenum rmPrimitiveSetOmeshDims (RMprimitive *toModify,
			         int isize,
				 int jsize,
				 int ksize)
 @pend

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

 int isize, jsize, ksize - integer values specifying the number of
    points in a three dimensional hexahedral lattice (input).
 @aend

 @dstart

 The OpenRM Octmesh primitive is logically a three-dimensional,
 hexahedral lattice. Use this routine to set the dimensions of the
 lattice. In most cases, "isize" will correspond to the x-axis,
 "jsize" to the y-axis and "ksize" to the z-axis.

 In OpenRM, direct volume rendering is achieved with a combination of
 an octmesh primitive and a 3D texture. The 3D texture provides color
 and opacity information, while the octmesh primitive specifies the
 geometric placement and resolution of the underlying 3D lattice.

 The octmesh primitive is procedural in that texture coordinates are
 automatically generated at render time. Use rmPrimitiveSetModelFlag
 to coarsen or refine render-time sampling of the underlying Octmesh
 grid.

 (Jan 2000) - use rmPrimitiveSetOmeshMinMaxGrid() to specify the
 corners of the 3D lattice. Use of rmPrimitiveSetVertex3D on octmesh
 primitives is not yet implemented.

 Note: the dimensions of the Octmesh lattice must be specified prior
 (using this routine, rmPrimitiveSetOmeshDims) prior to the
 specification of the corners of the lattice (using
 rmPrimitiveSetOmeshMinMaxGrid).

 @dend
 * ----------------------------------------------------
 */
RMenum
rmPrimitiveSetOmeshDims (RMprimitive *p,
			 int isize,
			 int jsize,
			 int ksize)
{
    int dims[3];

    if (RM_ASSERT(p, "rmPrimitiveSetOmeshDims() error: the input RMprimitive is NULL. ") == RM_WHACKED)
	return(RM_WHACKED);

    dims[0] = isize;
    dims[1] = jsize;
    dims[2] = ksize;

    return(private_rmPrimitiveSetItem(p, RM_PRIMITIVE_OMESHDIMS, 3, sizeof(int), (void *)dims, RM_COPY_DATA, NULL));
}


/*
 * ----------------------------------------------------
 * @Name rmPrimitiveSetOmeshMinMaxGrid
 @pstart
 RMenum rmPrimitiveSetOmeshMinMaxGrid (RMprimitive *toModify,
			               const RMvertex3D *gridMin,
				       const RMvertex3D *gridMax)
 @pend

 @astart
 RMprimitive *p - a handle to an RMprimitive (modified).

 const RMvertex3D *gridMin, *gridMax - handles to RMvertex3D objects
    (input).
 @aend

 @dstart

 Use this routine to set the minimum and maximum coordinates for the
 3D lattice that defines an octmesh primitive. The spatial extents of
 the lattice are specified with this routine, whereas the resolution
 of the lattice is specified with rmPrimitiveSetOmeshDims().

 Returns RM_CHILL upon success, or RM_WHACKED upon failure.
 
 Note: the dimensions of the Octmesh lattice must be specified prior
 (using rmPrimitiveSetOmeshDims) prior to the specification of the
 corners of the lattice (using rmPrimitiveSetOmeshMinMaxGrid).

 @dend
 * ----------------------------------------------------
 */
RMenum
rmPrimitiveSetOmeshMinMaxGrid (RMprimitive *p,
			       const RMvertex3D *gridMin,
			       const RMvertex3D *gridMax)
{
    RMvertex3D v[2];

    if ((RM_ASSERT(p, "rmPrimitiveSetOmeshMinMaxGrid() error: the input RMprimitive is NULL") == RM_WHACKED ) ||
	(RM_ASSERT(gridMin, "rmPrimitiveSetOmeshMinMaxGrid() error: the input gridMin pointer is NULL") == RM_WHACKED) ||
	(RM_ASSERT(gridMax, "rmPrimitiveSetOmeshMinMaxGrid() error: the input gridMax pointer is NULL") == RM_WHACKED))
	return(RM_WHACKED);

    v[0] = *gridMin;
    v[1] = *gridMax;
    
    return(private_rmPrimitiveSetItem(p, RM_PRIMITIVE_OMESH_MINMAX_GRID, 2, sizeof(RMvertex3D), (void *)v, RM_COPY_DATA, NULL));
}


/*
 * ----------------------------------------------------
 * @Name rmPrimitiveSetMarkerScale
 @pstart
 RMenum rmPrimitiveSetMarkerScale (RMprimitive *toModify,
			           int npts,
				   float *scales,
				   RMenum copyEnum,
				   void (*appFreeFunc)(void *))
 @pend

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

 int npts - an integer value specifying the length of the "scales"
    array (input).

 float *scales - a flat array of floats, expected to be "npts" in
    length (input).

 RMenum copyEnum - an RMenum value, may be either RM_COPY_DATA or
    RM_DONT_COPY_DATA (input).

 void (*appFreeFunc)(void *) - a handle to an application callback,
    required when copyEnum is RM_DONT_COPY_DATA (input).
 @aend

 @dstart

 Use this routine to set the scale values applied to RM "marker
 primitives."  Returns RM_CHILL upon success, or RM_WHACKED upon
 failure.

 The "marker primitive" in RM is a procedural primitive built from a
 number of predefined shapes, such as a triangles, squares, and so
 forth (see rmv.h). A fully populated marker primitive will consist of
 some number of vertices (each vertex defines the center point for the
 procedural marker), optional scale values used to isometrically
 shrink or expand the underlying marker geometric model, and optional
 color values. The number of vertices defined by
 rmPrimitiveSetVertex2D/3D defines the number of marker primitives
 that will be drawn.

 The number of marker shapes that are drawn by a given RMprimitive
 object is either 1 (drawn at many places in the scene, defined by
 rmPrimitiveSetVertex2D/3D), or the same as the number of vertices
 defined by rmPrimitiveSetVertex2D/3D. The marker primitives
 themselves are created by rmInternalMarker2DNew() and assigned to the
 RMprimitive with rmPrimitiveSetMarkerPrims().

 The number of scale values may be either 1, or the same as the number
 of vertices specified with rmPrimitiveSetVertex2D/3D.

 @dend
 * ----------------------------------------------------
 */
RMenum
rmPrimitiveSetMarkerScale (RMprimitive *p,
			   int npts,
			   float *s,
			   RMenum copy_enum,
			   void (*appfreefunc)(void *))
{
    if ((private_rmPrimSetAssert(p, npts, (void *)s, copy_enum, appfreefunc, "rmPrimitiveSetMarkerScale")) == RM_WHACKED)
        return(RM_WHACKED);
    
    return(private_rmPrimitiveSetItem((RMprimitive *)p, RM_PRIMITIVE_MARKERS2D_SCALE, npts, sizeof(float), (void *)s, copy_enum, appfreefunc));
}


/*
 * ----------------------------------------------------
 * @Name rmPrimitiveSetMarkerPrims
 @pstart
 RMenum rmPrimitiveSetMarkerPrims (RMprimitive *toModify,
			           int nMarkerPrims,
				   RMinternalMarker2D **mArray)
 @pend

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

 int nMarkerPrims - an integer value specifying the number of input
    "marker primitives" in the mArray parameter (input).

 RMinternalMarker2D **mArray - a handle to a flat array of
    RMinternalMarker2D handles (input). 
 @aend

 @dstart

 Use this routine to assign some number of procedural marker
 primitives (RMinternalMarker2D) to an RMprimitive. Returns RM_CHILL
 upon success, or RM_WHACKED upon failure.  Use
 rmInternalMarker2DNew() to create the marker primitives.

 The "marker primitive" in RM is a procedural primitive built from a
 number of predefined shapes, such as a triangles, squares, and so
 forth (see rmv.h). A fully populated marker primitive will consist of
 some number of vertices (each vertex defines the center point for the
 procedural marker), optional scale values used to isometrically
 shrink or expand the underlying marker geometric model, and optional
 color values. The number of vertices defined by
 rmPrimitiveSetVertex2D/3D defines the number of marker primitives
 that will be drawn.

 The number of marker shapes that are drawn by a given RMprimitive
 object is either 1 (drawn at many places in the scene, defined by
 rmPrimitiveSetVertex2D/3D), or the same as the number of vertices
 defined by rmPrimitiveSetVertex2D/3D. The marker primitives
 themselves are created by rmInternalMarker2DNew() and assigned to the
 RMprimitive with rmPrimitiveSetMarkerPrims().

 The number of scale values may be either 1, or the same as the number
 of vertices specified with rmPrimitiveSetVertex2D/3D.

 @dend
 * ----------------------------------------------------
 */
RMenum
rmPrimitiveSetMarkerPrims (RMprimitive *p,
			   int nmarkerprims,
			   RMinternalMarker2D **marray)
{
    if (RM_ASSERT(p, "rmPrimitiveSetMarkerPrims() error: primitive is NULL") == RM_WHACKED)
	return(RM_WHACKED);

    if (!((nmarkerprims != 0) && (marray != NULL)))
    {
	rmError("rmPrimitiveSetMarkerPrims() error: null markerprims array pointer and non-zero count");
	return(RM_WHACKED);
    }

    return(private_rmPrimitiveSetItem((RMprimitive *)p, RM_PRIMITIVE_MARKERS2D_PRIM, nmarkerprims, sizeof(RMinternalMarker2D), (void *)marray, RM_COPY_DATA, NULL));
}


/*
 * ----------------------------------------------------
 * @Name  rmPrimitiveSetEllipse2DRotate
 @pstart
 RMenum rmPrimitiveSetEllipse2DRotate (RMprimitive *toModify,
			               int nVals,
				       float *rotationValues,
				       RMenum copyEnum,
				       void (*appFreeFunc)(void *))
 @pend

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

 int nVals - an integer value specifying the length of the
    rotationValues[] array (input).

 float *rotationValues - an array of floats, must be nVals in length
    (input).

 RMenum copyEnum - an RMenum value, may be either RM_COPY_DATA or
    RM_DONT_COPY_DATA (input).

 void (*appFreeFunc)(void *) - a handle to an application callback,
    required when copyEnum is RM_DONT_COPY_DATA (input). 
 @aend

 @dstart

 Use this routine to set the rotation angles for some number of
 ellipses in an RM_ELLIPSE2D primitive. Returns RM_CHILL upon success,
 or RM_WHACKED upon failure.

 RM ellipse primitives consist of vertices that define the center of
 each ellipse (rmPrimitiveSetVertex2D), optional per-ellipse color
 data, optional per-ellipse scale values (2 radius values per ellipse)
 and an optional per-ellipse rotation value.

 At render time, for each input vertex, or ellipse center, an
 idealized ellipse is first scaled, rotated, then translated to the
 desired location.

 The per-ellipse rotation value defines a counterclockwise rotation
 about the Z-axis, where it is assumed that the ellipse is defined in
 the x/y plane. The rotation values are specified in degrees (not
 radians).  A input rotation value of 90 will cause the ellipse to be
 rotated 90 degrees counterclockwise.

 When copyEnum is set to RM_COPY_DATA, the rotation values provided by
 the caller will be copied. When copyEnum is set to RM_DONT_COPY_DATA,
 the handle "rotationValues" will be used directly by the RMprimitive.
 The caller must provide a callback in conjunction with
 RM_DONT_COPY_DATA that will be invoked when the RMprimitive is
 deleted.

 @dend
 * ----------------------------------------------------
 */
RMenum
rmPrimitiveSetEllipse2DRotate (RMprimitive *p,
			       int nvals,
			       float *rots,
			       RMenum copy_enum,
			       void (*appfreefunc)(void *))
{
    if ((private_rmPrimSetAssert(p, nvals, (void *)rots, copy_enum, appfreefunc, "rmPrimitiveSetEllipse2DRotate")) == RM_WHACKED)
	return(RM_WHACKED);
    
    return(private_rmPrimitiveSetItem(p, RM_PRIMITIVE_INDICES, nvals, sizeof(float), (void *)rots, copy_enum, appfreefunc));
}


/*
 * ----------------------------------------------------
 * @Name rmPrimitiveSetText
 @pstart
 RMenum rmPrimitiveSetText (RMprimitive *toModify,
		            size_t nStrings,
			    char *strings[])
 @pend

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

 size_t nStrings - a size_t value specifying the number of text strings
    in the strings[] array (input).

 char *strings[] - an array of character strings (input).
 @aend

 @dstart

 Use this routine to assign some number of text strings to an
 RMprimitive of type RM_TEXT or RM_INDEXED_TEXT. The input text
 strings are duplicated in the RMprimitive; there is no shared data
 management. Returns RM_CHILL upon success, or RM_WHACKED upon
 failure.

 Text rendering in RM is achieved by creating an RMprimitive of type
 RM_TEXT or RM_INDEXED_TEXT, supplying text strings and vertex
 locations to the RMprimitive (colors are optional, but indices are
 required for the RM_INDEXED_TEXT primitive). This RMprimitive data
 specifies the location of the text string, along with the text to be
 rendered.

 The appearance of the text string, such as typeface, size,
 italicization and so forth are manipulated through the RMnode scene
 parameter RMtextProps (see rmTextPropsSetAttribs).

 @dend
 * ----------------------------------------------------
 */
RMenum
rmPrimitiveSetText (RMprimitive *t,
		    size_t nstrings,
		    char *strings[])
{
    size_t i;
    RMtextPrim *p;

    if ((private_rmPrimSetAssert(t, nstrings, (void *)strings, RM_COPY_DATA, NULL, "rmPrimitiveSetText")) == RM_WHACKED)
        return(RM_WHACKED);
 
    /* first, remove any stuff */
    if (t->p1)
    {
	p = t->p1;
	/* free each old string */
	for (i=0; i<(size_t)(t->flags1); i++)
	    free((void *)p[i].string);
	
	free((void *)t->p1);
    }

    p = (RMtextPrim *)malloc(sizeof(RMtextPrim)*nstrings);

    for (i = 0; i < nstrings; i++)
    {
	p[i].string = strdup(strings[i]);
	p[i].bh = p[i].bw = -1; /* need to have these set */
	p[i].ptag = -1;		/* mark as uninitialized */
    }

    t->p1 = (void *)p;
    t->flags1 = nstrings;

    return(RM_CHILL);
}


/*
 * ----------------------------------------------------
 * @Name rmPrimitiveSetDisplayListEnable
 @pstart
 RMenum rmPrimitiveSetDisplayListEnable (RMprimitive *toModify,
				         RMenum newMode)
 @pend

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

 RMenum newMode - an RMenum value, may be either RM_TRUE or RM_FALSE
    (input).
 @aend

 @dstart

 In RM, OpenGL display lists are automatically built and cached on a
 per-primitive basis. In some cases, the application may wish to
 override this behavior, and inhibit the automatic construction of
 display lists. Primitive which are dynamic, such as those that have
 geometry, colors, etc. that change often (like every frame) should
 not be display-listed. The cost of building and storing the display
 list will not be recouped by the shortened rendering time unless the
 display list is used in many frames.

 Specify RM_TRUE for newMode to enable display lists for the
 RMprimitive toModify, otherwise, specify RM_FALSE to disable display
 list building for the RMprimitive.

 Applications can control use of display lists in two ways: at the RMpipe
 level, and at the RMprimitive level. At the RMpipe level, you can 
 enable or disable use of display lists for all RMprimitives drawn on RMpipe
 using the routine rmPipeSetDisplayListEnable. At the RMprimitive level,
 you can enable or disable the use of display lists for a single primitive
 using rmPrimitiveSetDisplayListEnable().

 The RMprimitive display list  policy does not override the display list
 policy set at the RMpipe level. In other words, if the policy at the RMpipe
 level is set to RM_FALSE, then no display lists will be used, even if the
 policy at the RMprimitive level is set to RM_TRUE. On the other hand, if
 the policy at the RMpipe level is set to RM_TRUE, a policy at the RMprimitive
 level of RM_FALSE will result on no display lists being used for the
 one RMprimitive. In order for display lists to be used at any given
 RMprimitive, the logical AND of RMpipe and RMprimitive display list
 policies must be RM_TRUE.

 Returns RM_CHILL upon success, or RM_WHACKED upon failure.

 (Jan 2000) At this time, there is no corresponding "get display list
 enable" routine.

 See also rmPipeSetDisplayListEnable().
 
 @dend
 * ----------------------------------------------------
 */
RMenum
rmPrimitiveSetDisplayListEnable (RMprimitive *p,
				 RMenum newMode)
{
    if (RM_ASSERT(p, "rmPrimitiveSetDisplayListEnable() error: the input RMprimitive pointer is NULL. ") == RM_WHACKED)
	return(RM_WHACKED);

    if ((newMode != RM_TRUE) && (newMode != RM_FALSE))
    {
	rmError("rmPrimitiveDisplayListEnable() error: the input newMode enumerator is neither RM_TRUE nor RM_FALSE.");
	return(RM_WHACKED);
    }

    /*
     * bookkeeping chores:
     * if the new mode is RM_FALSE, delete any existing display list,
     * and set the "compiled render function" pointer to NULL.
     */
    private_rmPrimitiveSetDisplayListEnable(p, newMode);

    return(RM_CHILL);
}


/*
 * ----------------------------------------------------
 * @Name rmNodeAddPrimitive
 @pstart
 RMenum rmNodeAddPrimitive (RMnode *addTo,
		            RMprimitive *src)
 @pend

 @astart
 RMnode *addTo - a handle to an RMnode (modified).

 RMprimitive *src - a handle to an RMprimitive (input).
 @aend

 @dstart

 This routine adds an RMprimitive to an RMnode. Returns RM_CHILL upon
 success, or RM_WHACKED upon failure.

 Each RMnode may contain an arbitrary number of RMprimitive objects.
 Each of the RMprimitive objects is added to the RMnode using
 rmNodeAddPrimitive. The list of RMprimitives in an RMnode is a flat
 array, and this array grows to accomodate new RMprimitives as they
 are added.

 January 2000 - at this time, there is no corresponding "delete
 primitive from an RMnode" operation. The closest equivalent is
 rmNodeDelete().  Applications that anticipate the need to delete
 RMprimitives should carefully consider the subject of scene graph
 design, and use RMnodes that contain just a single primitive; the
 delete operations should be performed at the RMnode level, not the
 RMprimitive level.

 February 2001 - this routine is thread safe: multiple application threads
 may simultaneously call this routine to add primitives to the same node.
 Thread safety is provided by mutex locks in the component manager.

 NOTE: Applications SHOULD NOT use an RMprimitive in multiple RMnodes
 in order to accomplish instancing. Instancing is properly achieved in
 RM by instancing at the RMnode level, not the RMprimitive level.

 
 @dend
 * ----------------------------------------------------
 */
RMenum
rmNodeAddPrimitive (RMnode *n,
		    RMprimitive *prim)
{
    extern RMcompMgrHdr *global_RMnodePool;
    
    if (RM_ASSERT(n, "rmNodeAddPrimitive() error: input node is NULL\n") == RM_WHACKED)
	return(RM_WHACKED);

    if (rmMutexLock(global_RMnodePool->guard) == RM_WHACKED)
    {
	rmError("rmNodeAddPrimitive() error: problem locking guard mutex in component manager. ");
	return(RM_WHACKED);
    }
    
    if ((n->prims = (void **)realloc(n->prims, sizeof(void *) * (n->nprims + 1))) == NULL)
    {
	rmError("rmNodeAddPrimitive() error: realloc failure. the primitive list at this node is now in an undetermined state, and may contain garbage. ");
	
	if (rmMutexUnlock(global_RMnodePool->guard) == RM_WHACKED)
	{
	    rmError("rmNodeAddPrimitive() error: problem unlocking guard mutex in component manager. ");
	    return(RM_WHACKED);
	}
	return(RM_WHACKED);
    }
    n->prims[n->nprims] = prim;
    n->nprims++;

    if (rmMutexUnlock(global_RMnodePool->guard) == RM_WHACKED)
    {
	rmError("rmNodeAddPrimitive() error: problem unlocking guard mutex in component manager. ");
	return(RM_WHACKED);
    }
    
    return(RM_CHILL);
}


/*
 * ----------------------------------------------------
 * @Name rmNodeGetPrimitive
 @pstart
 RMprimitive * rmNodeGetPrimitive (const RMnode *toQuery,
		                   int indx)
 @pend

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

 int indx - an integer value interpreted as an index (input).
 @aend

 @dstart

 Returns to the caller the RMprimitive handle of the i'th primitive in
 an RMnode.

 @dend
 * ----------------------------------------------------
 */
RMprimitive *
rmNodeGetPrimitive (const RMnode *m,
		    int j)
{
    if (RM_ASSERT(m, "rmNodeGetPrimitive() error: input node is NULL. \n") == RM_WHACKED)
	return(NULL);

    if (m->prims == NULL)
	return(NULL);

    if ((j < 0) || (j >= m->nprims)) /* error? warning? */
	return(NULL);
    
    return(m->prims[j]);
}


/*
 * ----------------------------------------------------
 * @Name rmNodeSetClientData
 @pstart
 RMenum rmNodeSetClientData (RMnode *toModify,
		             void *clientData,
			     void (*cdFreeFunc)(RMnode *,void *))
 @pend

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

 void *clientData - a handle (input).

 void (*cdFreeFunc)(RMnode *,void *) - a handle to an application
   callback (input).
 @aend

 @dstart

 Applications may store a handle to arbitrary memory in RMnodes or
 RMprimitives. This routine is used to place a handle to arbitrary
 memory in an RMnode, and returns RM_CHILL upon success or RM_WHACKED
 upon failure.
 
 Client data may be stored in both RMprimitives and RMnodes
 (rmNodeSetClientData).
 
 The "client data" handle may be later accessed using
 rmNodeGetClientData.

 The callback function will be invoked when the RMnode is deleted.
 The callback takes two parameters, a handle to the RMnode containing
 the client data handle, and the client data handle itself. The
 callback is provided so that applications may delete the data
 referenced by the client data handle stored in the RMnode.
 
 When the input client data handle is NULL, the old handle value is
 effectively overwritten. Any memory pointed to by the old client data
 handle will be lost (a potential memory leak).

 @dend
 * ----------------------------------------------------
 */
RMenum
rmNodeSetClientData (RMnode *n,
		     void *cd,
		     void (*cd_free_func)(RMnode *,void *))
{
    extern RMcompMgrHdr *global_RMnodePool; /* fall 2007, tmp */
    
    if (RM_ASSERT(n, "rmNodeSetClientData() error: the input RMnode is NULL") == RM_WHACKED)
	return(RM_WHACKED);

    /* fall 2007, tmp */

    if (rmDataSyncGetPolicy() == RM_DATA_SYNC_DOUBLE_BUFFERED)
    {

	/* the node had better have its mutex alloc'ed already. */
	if (n->nodeMutex == NULL)
	{
	    rmError("rmNodeSetClientData() error: the data sync policy is double buffered, but there is no node-level mutex. Aborting program.");
	    exit(-1);
	}

	/* lock the node-level mutex before examining data fields*/
	rmMutexLock(n->nodeMutex);

#if 0	
	/* first, check to see if there's any client data at the node */
	if (n->clientData == NULL)
	{
	    /* no existing data, just write the new stuff in there */
	    n->clientData = cd;
	    n->clientDataFreeFunc = cd_free_func;
	}
	else /* add to dirty list for later processing */
	{
#endif
	    private_rmAppendDirtyList(RM_DATA_SYNC_TARGET_NODE,
				      n,
				      RM_DATA_SYNC_TARGET_NODE_CLIENT_DATA,
				      cd,
				      (DirtyListFreeFuncPrototype)cd_free_func);
#if 0
	}
#endif

	/* unlock the node-level mutex mutex */
	rmMutexUnlock(n->nodeMutex);
	return RM_CHILL; 	/* all done */
    }

    if (rmDataSyncGetPolicy() == RM_DATA_SYNC_DRACONIAN)
	rmMutexLock(global_RMnodePool->guard);
    
    n->clientData = cd;
    n->clientDataFreeFunc = cd_free_func;

    /* fall 2007, tmp  */
    if (rmDataSyncGetPolicy() == RM_DATA_SYNC_DRACONIAN)
	rmMutexUnlock(global_RMnodePool->guard);

    return(RM_CHILL);
}

/* tmp, fall 2007 */
RMenum
rmNodeSetClientDataMT(int whichThread,
		      RMnode *n,
		      void *cd,
		      void (*cd_free_func)(RMnode *,void *))
{
    extern RMcompMgrHdr *global_RMnodePool; /* fall 2007, tmp */
    
    if (RM_ASSERT(n, "rmNodeSetClientData() error: the input RMnode is NULL") == RM_WHACKED)
	return(RM_WHACKED);

    /* fall 2007, tmp */

    if (rmDataSyncGetPolicy() == RM_DATA_SYNC_DOUBLE_BUFFERED)
    {

	/* the node had better have its mutex alloc'ed already. */
	if (n->nodeMutex == NULL)
	{
	    rmError("rmNodeSetClientData() error: the data sync policy is double buffered, but there is no node-level mutex. Aborting program.");
	    exit(-1);
	}

	/* lock the node-level mutex before examining data fields*/
	rmMutexLock(n->nodeMutex);

#if 0	
	/* first, check to see if there's any client data at the node */
	if (n->clientData == NULL)
	{
	    /* no existing data, just write the new stuff in there */
	    n->clientData = cd;
	    n->clientDataFreeFunc = cd_free_func;
	}
	else /* add to dirty list for later processing */
	{
#endif
	    private_rmAppendDirtyListMT(whichThread,
					RM_DATA_SYNC_TARGET_NODE,
					n,
					RM_DATA_SYNC_TARGET_NODE_CLIENT_DATA,
					cd,
					(DirtyListFreeFuncPrototype)cd_free_func);
#if 0
	}
#endif

	/* unlock the node-level mutex mutex */
	rmMutexUnlock(n->nodeMutex);
	return RM_CHILL; 	/* all done */
    }

    if (rmDataSyncGetPolicy() == RM_DATA_SYNC_DRACONIAN)
	rmMutexLock(global_RMnodePool->guard);
    
    n->clientData = cd;
    n->clientDataFreeFunc = cd_free_func;

    /* fall 2007, tmp  */
    if (rmDataSyncGetPolicy() == RM_DATA_SYNC_DRACONIAN)
	rmMutexUnlock(global_RMnodePool->guard);

    return(RM_CHILL);
}


/*
 * ----------------------------------------------------
 * @Name rmNodeGetClientData
 @pstart
 void * rmNodeGetClientData (const RMnode *toQuery)
 @pend

 @astart
 const RMnode *toQuery - a handle to an RMnode (input).
 @aend

 @dstart

 Use this routine to obtain the client data handle stored in an
 RMnode.

 @dend
 * ----------------------------------------------------
 */
void *
rmNodeGetClientData (const RMnode *n)
{
    if (RM_ASSERT(n, "rmNodeGetClientData() error: the input RMnode is NULL") == RM_WHACKED)
	return((void *)NULL);
    
    return(n->clientData);
}


/*
 * ----------------------------------------------------
 * @Name rmPrimitiveSetAppDisplayList
 @pstart
 RMenum rmPrimitiveSetAppDisplayList (RMprimitive *toModify, GLuint appDisplayList)
 @pend

 @astart
 RMprimitive *toModify - a handle to an RMprimitive object (input).
 GLuint appDisplayList - a GLuint value (input).
 @aend

 @dstart
 Use this routine to assign a GL display list ID to an RMprimitive of
 type RM_APP_DISPLAYLIST. This type of RMprimitive is intended to be used
 by applications that need to create an OpenGL display list, perhaps using
 third-party modeling tools, and then have the display list contents rendered
 within an RM Scene Graph. Upon success, RM_CHILL is returned, while RM_WHACKED
 is returned upon failure.

 Restrictions apply when using application-defined display lists:
 (1) Applications are completely responsible for ensuring that the GLuint
 assigned as a display list ID is indeed valid.
 (2) After invoking the application-supplied display list, the the OpenGL
 state must be in exactly the same condition as it was before the
 display list was invoked. Failure to adhere to this rule will likely cause
 rendering errors that result from RM's inability to know about state changes
 made by the application display list.
 
 @dend
 * ----------------------------------------------------
 */
RMenum
rmPrimitiveSetAppDisplayList(RMprimitive *toModify,
			     GLuint displayListID)
{
    if (RM_ASSERT(toModify, "rmPrimitiveSetAppDisplayList() error: the input RMprimitive is NULL") == RM_WHACKED)
	return RM_WHACKED;

    if (private_rmPrimitiveGetType(toModify) != RM_APP_DISPLAYLIST)
    {
	rmError("rmPrimitiveSetAppDisplayList() error - the input primitive is not of type RM_APP_DISPLAYLIST");
	return RM_WHACKED;
    }

    toModify->flags1 = displayListID;
    
    return RM_CHILL;
}

RMenum 
private_rmPrimitiveSetMultiTexcoordBlob(RMprimitive *p, 
					int tcTag, 
					int num, 
					int stride,  
					void *stuff, 
					RMenum copyFlag, 
					void (*freefunc)(void *), 
					int textureUnit)
/* 
 * 2/2005.
 * Unlike private_rmPrimitiveSetItem, which is intended to be a reasonably
 * general interface to map the most common primitive elements into a
 * finite number of data blobs, this routine instead accesses blob array
 * specifically set aside for multitexture TCs. We still use the blob 
 * interface for setting/getting stuff in the data blobs, but the
 * more general approach doesn't work so well for multitexture TCs.
 */
{
    /* 
     * input validity checking -
     * 1. textureUnit. We can only check for 0 <= textureUnit <= RM_MAX_MULTITEXTURES.
     *    we can't compare against the number of multitexture units supported
     *    by the OpenGL implementation here - that's a render-time check.
     */
    RMprimitiveDataBlob *b;
    int newMask;
    int numMTCs, i, junk;

    if ((textureUnit < 0) || (textureUnit >= RM_MAX_MULTITEXTURES))
    {
	rmWarning("private_rmPrimitiveSetMultiTexcoordBlob error() - the input textureUnit is either less than zero or greater than or equal to RM_MAX_MULTITEXTURES. Failing to assign multitexture coords as requested. ");
	return RM_WHACKED;
    }

    private_rmPrimitiveSetCacheKey(p);

    /* 
     * RMprimitives don't start out with prealloced blobs for multitexture TCs.
     * create a pile of blobs if none exist.
     */
    if (p->multiTextureCoordBlobs == NULL)
    {
	p->multiTextureCoordBlobs = (RMprimitiveDataBlob *)malloc(sizeof(RMprimitiveDataBlob) * RM_MAX_MULTITEXTURES);
	memset(p->multiTextureCoordBlobs, 0, sizeof(RMprimitiveDataBlob) * RM_MAX_MULTITEXTURES);
    }

    /* we directly access the MTC blobs based upon textureUnit */
    b = &(p->multiTextureCoordBlobs[textureUnit]);

    if ((copyFlag == RM_DONT_COPY_DATA) && (freefunc != NULL))
	private_rmBlobSetFreefunc(b, freefunc);
    else if ((copyFlag == RM_DONT_COPY_DATA) && (freefunc == NULL))
    {
	rmError("private_rmPrimitiveSetMultiTexcoordBlob: a freefunc is required when you use RM_DONT_COPY_DATA.");
	return RM_WHACKED;
    }

    /* use normal interface to set data in the blob */
    private_rmBlobSetNthings(b ,num);
    private_rmBlobSetStride(b, stride);
    private_rmBlobSetData(b, num, stride, stuff, copyFlag);

    switch (tcTag)
    {
    case RM_PRIMITIVE_MULTI_1DTCOORDS:
	private_rmBlobSetVeclen(b, 1);
	break;

    case RM_PRIMITIVE_MULTI_2DTCOORDS:
	private_rmBlobSetVeclen(b, 2);
	break;

    case RM_PRIMITIVE_MULTI_3DTCOORDS:
	private_rmBlobSetVeclen(b, 3);
	break;
    }

    /* update the mask to reflect the addition of new MTC's */
    newMask = 1 << textureUnit;

    /* we might be writing over the MTCs for a texture unit
       set during a previous invocation (app data update) */
    junk = p->multiTextureCoordBlobsMask |= newMask;

    /* count up the number of MTC blobs */
    for (i=0, numMTCs=0; (i<RM_MAX_MULTITEXTURES) && (junk != 0); i++)
    {
	if (junk & 0x1)
	    numMTCs++;
	junk = junk >> 1;
    }

    /* and set the field indicating the number of MTC blobs */
    p->numMultiTextureCoordBlobs = numMTCs;

    return RM_CHILL;
}

/* PRIVATE
 *
 * the following is a private, interal routine used to interface between
 * application level API and the guts of the RMprimitive "blob model"
 * of data management.
 */
RMenum
private_rmPrimitiveSetItem (RMprimitive *p, 		/* primitive to add to */
			    int tag,			/* definition */
			    size_t num,			/* how many */
			    int stride,			/* size of individual thing in bytes -stride*/
			    void *stuff, 		/* pointer to the stuff */
			    RMenum copy_flag, 		/* RM_COPY_DATA/RM_DONT_COPY_DATA */
			    void (*freefunc)(void *))  /* free function for prim */
{
    /*
     * Tue Sep 30 20:36:53 PDT 1997 - added clutch code that sits
     * between RMvertex3D and true float[]'s - to avoid padding magic
     * that happens on some compilers.  
     */
    /*
     * Fri Oct  2 08:42:46 PDT 1998 - copy flag and client data free
     * func added. some compilers on 64bit OS's pad structures to 8-byte
     * boundaries. therefore, an  RMvertex3D structure that consists of
     * 3 floats is padded out to the next 8-byte boundary. OpenGL
     * vertex arrays allow for this kind of thing with a stride
     * parameter.
     *
     * Sat Oct 28 10:53:57 PDT 2000 - all prims get their cache key
     * updated whenever anything in the prim is set.
     */
    int                  blob_index;
    RMenum               rstat = RM_CHILL;
    RMprimitiveDataBlob *b;
	
    private_rmPrimitiveSetCacheKey(p);

    switch (tag)
	{
	case RM_PRIMITIVE_3DVERTICES:
	case RM_PRIMITIVE_2DVERTICES:
	    {
		blob_index = private_rmBlobIndexFromPrimAtom(tag);
		b = private_rmBlobFromIndex(p, blob_index);
		
		private_rmBlobSetNthings(b, num);
		private_rmBlobSetStride(b, stride);
		private_rmBlobSetVeclen(b, (tag == RM_PRIMITIVE_3DVERTICES) ? 3 : 2);
		private_rmBlobSetData(b, num, stride, stuff, copy_flag);
		private_rmBlobSetType(b, blob_index);

		
		if ((copy_flag == RM_DONT_COPY_DATA) && (freefunc != NULL))
		    private_rmBlobSetFreefunc(b, freefunc);
		else if ((copy_flag == RM_DONT_COPY_DATA) && (freefunc == NULL))
		{
		    rmError("rmPrimitiveSetItem: a freefunc is required when you use RM_DONT_COPY_DATA.");
		    rstat = RM_WHACKED;
		}
	    }
        break;
	
	case RM_PRIMITIVE_4COLORS:
	case RM_PRIMITIVE_3COLORS:
	    {
		blob_index = private_rmBlobIndexFromPrimAtom(tag);
		b = private_rmBlobFromIndex(p, blob_index);
		
		private_rmBlobSetNthings(b ,num);
		private_rmBlobSetStride(b, stride);
		private_rmBlobSetData(b, num, stride, stuff, copy_flag);
		private_rmBlobSetVeclen(b, (tag == RM_PRIMITIVE_4COLORS) ? 4 : 3);
		private_rmBlobSetType(b, blob_index);


		if ((copy_flag == RM_DONT_COPY_DATA) && (freefunc != NULL))
		    private_rmBlobSetFreefunc(b, freefunc);
		else if ((copy_flag == RM_DONT_COPY_DATA) && (freefunc == NULL))
		{
		    rmError("rmPrimitiveSetItem: a freefunc is required when you use RM_DONT_COPY_DATA.");
		    rstat = RM_WHACKED;
		}
		break;
	    }
	
	case RM_PRIMITIVE_1DTCOORDS:
	case RM_PRIMITIVE_2DTCOORDS:
	case RM_PRIMITIVE_3DTCOORDS:
	case RM_PRIMITIVE_NORMALS:
	    {
		int veclen = 0; /* init satisfies gcc warning */
		
		blob_index = private_rmBlobIndexFromPrimAtom(tag);
		b = private_rmBlobFromIndex(p, blob_index);
		
		private_rmBlobSetNthings(b, num);
		private_rmBlobSetStride(b, stride);
		private_rmBlobSetData(b, num, stride, stuff, copy_flag);
		
		/* set veclen */
		if ((tag == RM_PRIMITIVE_NORMALS) || (tag == RM_PRIMITIVE_3DTCOORDS))
		    veclen = 3;
		else if (tag == RM_PRIMITIVE_2DTCOORDS)
		    veclen = 2;
		else if (tag == RM_PRIMITIVE_1DTCOORDS)
		    veclen = 1;
		
		private_rmBlobSetVeclen(b, veclen);
		
		if ((copy_flag == RM_DONT_COPY_DATA) && (freefunc != NULL))
		    private_rmBlobSetFreefunc(b, freefunc);
		else if ((copy_flag == RM_DONT_COPY_DATA) && (freefunc == NULL))
		{
		    rmError("rmPrimitiveSetItem: a freefunc is required when you use RM_DONT_COPY_DATA.");
		    rstat = RM_WHACKED;
		}
		break;
	    }
	
	case RM_PRIMITIVE_OMESHDIMS:
	case RM_PRIMITIVE_QMESHDIMS:
	    {
		blob_index = private_rmBlobIndexFromPrimAtom(tag);
		b = private_rmBlobFromIndex(p, blob_index);
		
		private_rmBlobSetNthings(b, num);
		private_rmBlobSetStride(b, stride );
		private_rmBlobSetData(b, num, stride, stuff, copy_flag);
		
		if ((copy_flag == RM_DONT_COPY_DATA) && (freefunc != NULL))
		    private_rmBlobSetFreefunc(b, freefunc);
		else if ((copy_flag == RM_DONT_COPY_DATA) && (freefunc == NULL))
		{
		    rmError("rmPrimitiveSetItem: a freefunc is required when you use RM_DONT_COPY_DATA.");
		    rstat = RM_WHACKED;
		}
		break;
	    }
	    
	case RM_PRIMITIVE_MARKERS2D_SCALE:
	case RM_PRIMITIVE_CYLINDER_RADII:
	case RM_PRIMITIVE_CONE_RADII:
	case RM_PRIMITIVE_RADII:
	case RM_PRIMITIVE_INDICES:
	    {
		blob_index = private_rmBlobIndexFromPrimAtom(tag);
		b = private_rmBlobFromIndex(p, blob_index);
		
		private_rmBlobSetNthings(b, num);
		private_rmBlobSetStride(b, stride);
		private_rmBlobSetData(b, num, stride, stuff, copy_flag);
		
		/* set veclength */
		
		if ((copy_flag == RM_DONT_COPY_DATA) && (freefunc != NULL))
		    private_rmBlobSetFreefunc(b, freefunc);
		else if ((copy_flag == RM_DONT_COPY_DATA) && (freefunc == NULL))
		{
		    rmError("rmPrimitiveSetItem: a freefunc is required when you use RM_DONT_COPY_DATA.");
		    rstat = RM_WHACKED;
		}
		    
		break;
	    }

	case RM_PRIMITIVE_OMESH_MINMAX_GRID:
	    {
		/*
		 * the omesh min-max grid is expanded to a rectilinear
		 * grid here. we go from two RMvertex's to an array of
		 * floats, the length of which is usize+vsize+wsize;
		 *
		 * the dimensions of the octmesh MUST be defined before
		 * the grid is defined, otherwise this hunk of code fails.
		 *
		 * we ignore the copy flag passed in to this routine, and
		 * create our own "copy" of the grid via the expansion from
		 * corner points to 3 rectilinear arrays.
		 */
		
		int                  i, *dims;
		float                t, dt, *f, *xspacing, *yspacing, *zspacing;
		RMvertex3D          *gmin,*gmax;
		RMprimitiveDataBlob *s;
		
		/* check to see if usize, vsize and wsize are set */
		i = private_rmBlobIndexFromPrimAtom(RM_PRIMITIVE_OMESHDIMS);
		s = private_rmBlobFromIndex(p, i);
		
		{
		    int npts = private_rmBlobGetNthings(s);
		    
		    dims = (int *)private_rmBlobGetData(s);
		    if ((dims == NULL) || (npts != 3))
			{ 
			    rmError(" the size of the Octmesh must be defined prior to setting the grid. Skipping the assignment of the grid. \n");
			    return(RM_WHACKED);
			}
		}
		
		/* malloc the hunk of memory used to hold the expanded grid */
		f = (float *)malloc(sizeof(float) * (dims[0] + dims[1] + dims[2]));
		xspacing = f;
		yspacing = xspacing + dims[0];
		zspacing = yspacing + dims[1];
		
		gmin = (RMvertex3D *)stuff;
		gmax = gmin + 1;	/* ?? */
		
		/* do the x axis */
		t = gmin->x;
		dt = (gmax->x - gmin->x) / (dims[0] - 1); 
		
		for (i = 0; i < dims[0]; i++, t += dt)
		    xspacing[i] = t;
		
		/* do the y axis */
		t = gmin->y;
		dt = (gmax->y - gmin->y) / (dims[1]- 1);
		
		for (i = 0; i < dims[1]; i++, t += dt)
		    yspacing[i] = t;
		
		/* do the z-axis */
		t = gmin->z;
		dt = (gmax->z - gmin->z) / (dims[2] - 1);
		
		for (i = 0; i < dims[2]; i++, t += dt)
		    zspacing[i] = t;
		
		i = private_rmBlobIndexFromPrimAtom(tag);
		b = private_rmBlobFromIndex(p, i);
		private_rmBlobSetType(b, i);
		
		private_rmBlobSetNthings(b, (dims[0] + dims[1] + dims[2]));
		private_rmBlobSetStride(b, sizeof(float));
		private_rmBlobSetData(b, (dims[0] + dims[1] + dims[2]), sizeof(float), f, RM_COPY_DATA);
		
		free(f);
		break;
	    }
	
	case RM_PRIMITIVE_MARKERS2D_PRIM:
	    {
		RMinternalMarker2D **m;
		
		if (p->p1)
		    free(p->p1);
		
		/* hack - we assume that there is only one of these things that is being passed in. */
		m = (RMinternalMarker2D **)stuff;
		
		p->p1 = (void *)rmInternalMarker2DNew(m[0]->npts, m[0]->gl_begin_flag, m[0]->vlist);
		p->flags1 = num;
		
		rstat = RM_CHILL;
		break;
	    }
	
	case RM_PRIMITIVE_SPRITES:
	    {
		int       i;
		RMimage **img, **src;
		
		/*
		 * in sprite primitives, we use p1 to point to a list of
		 * RMimages's.  the flags1 field is set in this chunk of
		 * code to reflect the actual number of cached sprites.
		 * we use this internal field so that we can properly
		 * free up old sprite's.
		 */
		
		if (rmPrimitiveGetType(p) != RM_SPRITE)
		{
		    rmError("error trying to add sprites to a non-sprite primitive type.");
		    return(RM_WHACKED);
		}
		if (p->p1)
		{
		    /* free up old one's */
		    img = (RMimage **) (p->p1);
		    for (i = 0; i < (int)(p->flags1); i++)
			rmImageDelete(img[i]);
			
		    free((void *)(img));
		    p->flags1 = 0;
		}
		
		/* now build a new list, duplicating the input images */
		src = (RMimage **)stuff;
		img = (RMimage **)malloc(sizeof(RMimage *)*num);
		for (i = 0; i < num; i++)
		    img[i] = rmImageDup(src[i]); 
		
		p->p1 = (void *)img;
		p->flags1 = num;

		break;
	    }
	
	case RM_PRIMITIVE_BITMAPS:
	    {
		/*
		 * the "p1" field is used as a pointer to a list of bitmaps.
		 * the "flags1" field is used to hold the count of bitmaps
		 *    in the p1 field.
		 */
		int        i;
		RMbitmap **src, **bmp;
		
		i = rmPrimitiveGetType(p);
		
		if ((i != RM_BITMAP) && (i != RM_INDEXED_BITMAP) && (i != RM_TEXT) && (i != RM_INDEXED_TEXT))
		{
		    rmError("attempting to add bitmaps to a primitive which is not of type RM_BITMAP or RM_BITMAP_INDICES. \n");
		    return(RM_WHACKED);
		}
		
		if (p->p1)
		{
		    /* free the old ones. */
		    bmp = (RMbitmap **) (p->p1);
		    for (i = 0; i < (int)(p->flags1); i++)
			rmBitmapDelete(bmp[i]);
			
		    free((void *)(bmp));
		    p->flags1 = 0;
		}
		
		/* now build a new list, duplicating the input images */
		src = (RMbitmap **)stuff;
		
		bmp = (RMbitmap **)malloc(sizeof(RMbitmap *)*num);
		for (i = 0; i < num; i++)
		    bmp[i] = rmBitmapDup(src[i]);
		
		p->p1 = (void *)bmp;
		p->flags1 = num;

		break;
	    }
	
	default:
	    rmWarning(" undefined primitive type used in private_rmPrimitiveSetItem() ");
	    rstat = RM_WHACKED;
	    break;
	}
    return(rstat);
}


/* PRIVATE
 *
 * this private routine is used by some of the draw functions to
 * get at specific parts of the RMprimitive data. vertices, colors,
 * indices, etc are all handled by the blob data model, and attempting
 * to access those items with this routine will produce an error message.
 */
RMenum
private_rmPrimitiveGetItem (RMprimitive *p,
			    int tag,
			    int *num,
			    void **stuff)
{
    RMenum rstat = RM_WHACKED;
	
    switch (tag)
    {
    case RM_PRIMITIVE_3DVERTICES:
    case RM_PRIMITIVE_2DVERTICES:
    case RM_PRIMITIVE_2DTCOORDS:
    case RM_PRIMITIVE_4COLORS:
    case RM_PRIMITIVE_3COLORS:
    case RM_PRIMITIVE_NORMALS:
    case RM_PRIMITIVE_MARKERS2D_SCALE:
    case RM_PRIMITIVE_CYLINDER_RADII:
    case RM_PRIMITIVE_CONE_RADII:
    case RM_PRIMITIVE_RADII:
    case RM_PRIMITIVE_QMESHDIMS:
    case RM_PRIMITIVE_OMESHDIMS:
    case RM_PRIMITIVE_BITMAP_INDICES:
	{
	    fprintf(stderr," improper use of RMprimitiveGetItem! \n");
	    break;
	}
	
    case RM_PRIMITIVE_RENDERFUNC:
	{
	    if (p->renderfunc)
	    {
		*stuff = (void *)(p->renderfunc);
		rstat = RM_CHILL;
	    }
	    else
		rstat = RM_WHACKED;
	}
	break;

	
    case RM_PRIMITIVE_MARKERS2D_PRIM:
    case RM_PRIMITIVE_SPRITES:
    case RM_PRIMITIVE_BITMAPS:
    case RM_PRIMITIVE_TEXTPRIM:
	{
	    if (p->p1)
	    {
		*num = p->flags1;
		*stuff = p->p1;
		rstat = RM_CHILL;
	    }
	    else
		*num = 0;
	}
	break;
    }
    return(rstat);
}


/* PRIVATE */
RMenum
private_rmPrimitiveGetText (RMprimitive *t,
			    int *nstringsReturn,
			    RMtextPrim **primsReturn)
{
    /* this routine needs some work */
    if ((t->flags1 != 0) && (t->p1 != NULL))
    {
	*nstringsReturn = t->flags1;
	*primsReturn = (RMtextPrim *)(t->p1);
	return(RM_CHILL);
    }
    else
	return(RM_WHACKED);
}

/* PRIVATE */
void
private_rmBlobSetType (RMprimitiveDataBlob *b,
		       int newtype)
{
    /*
     * the intent is to store in "blobtype" the RM_PRIMITIVE_* tag
     * assigned by the user. we need this info because, to some extent,
     * the user data is homogonized in the cast-to-blob process. some
     * primitive rendering & mem. mgt handling code in RM needs this
     * info.
     */
    b->blobtype = newtype;
}


/* PRIVATE */
int
private_rmBlobGetType (RMprimitiveDataBlob *b)
{
    return(b->blobtype);
}


/* PRIVATE */
void
private_rmBlobSetNthings (RMprimitiveDataBlob *b,
			  int num)
{
    b->nthings = num;
}


/* PRIVATE */
int
private_rmBlobGetNthings (RMprimitiveDataBlob *b)
{
    return(b->nthings);
}


/* PRIVATE */
void
private_rmBlobSetStride (RMprimitiveDataBlob *b,
			 int stride)
{
    b->stride = stride;
}

/* PRIVATE */
void
private_rmBlobSetVeclen (RMprimitiveDataBlob *b,
			 int veclen)
{
    b->veclen = veclen;
}


/* PRIVATE */
int
private_rmBlobGetVeclen (RMprimitiveDataBlob *b)
{
    return(b->veclen);
}


/* PRIVATE */
void
private_rmBlobSetData (RMprimitiveDataBlob *b,
		       size_t num,
		       size_t stride,
		       void *stuff,
		       RMenum copy_flag)
{
    /*
     * first, free up old stuff. if RM is responsible for the data, do
     * a free(). if the app is repsonsible, invoke the appfreefunc().
     */
    if ((b->copyflag == RM_COPY_DATA) && (b->ptr != NULL))
	free(b->ptr);
    else if ((b->copyflag == RM_DONT_COPY_DATA) && (b->appfreefunc != NULL))
	(*(b->appfreefunc))(b->ptr);
	
    if (copy_flag == RM_COPY_DATA)
    {
	size_t nbytes;
	nbytes = stride*num;
	b->ptr = (void *)malloc(nbytes);
	b->copyflag = copy_flag;
	memcpy(b->ptr, stuff, nbytes);
    }
    else
    {
	b->ptr = stuff;
	b->copyflag = copy_flag;
    }
}


/* PRIVATE */
void
private_rmBlobSetFreefunc (RMprimitiveDataBlob *b,
			   void (*freefunc)(void *))
{
    b->appfreefunc = freefunc;
}


/* PRIVATE */
int
private_rmBlobGetStride (RMprimitiveDataBlob *b)
{
    return(b->stride);
}


/* PRIVATE */
void *
private_rmBlobGetData (RMprimitiveDataBlob *b)
{
    return(b->ptr);
}


/* PRIVATE */
int
private_rmBlobIndexFromPrimAtom (int tag)
{
    int rstat;
    /*
     * this code translates from an RM_PRIMITIVE_* tag to a blob tag.
     * this is used by rm routines that need to stuff app's prim data
     * into blobs
     */
    switch(tag)
    {
    case RM_PRIMITIVE_3DVERTICES:
    case RM_PRIMITIVE_2DVERTICES:
	rstat = BLOB_VERTEX_INDEX;
	break;

    case RM_PRIMITIVE_OMESH_MINMAX_GRID:
	rstat = BLOB_OMESH_RECTGRID_INDEX;
	break;
	
    case RM_PRIMITIVE_4COLORS:
    case RM_PRIMITIVE_3COLORS:
	rstat = BLOB_COLOR_INDEX;
	break;
	
    case RM_PRIMITIVE_NORMALS:
	rstat = BLOB_NORMAL_INDEX;
	break;

    case RM_PRIMITIVE_1DTCOORDS:
    case RM_PRIMITIVE_2DTCOORDS:
    case RM_PRIMITIVE_3DTCOORDS:
	rstat = BLOB_TC_INDEX;
	break;
	    
    case RM_PRIMITIVE_MARKERS2D_SCALE:
    case RM_PRIMITIVE_CYLINDER_RADII:
    case RM_PRIMITIVE_CONE_RADII:
    case RM_PRIMITIVE_RADII:
	rstat = BLOB_SCALE_INDEX;
	break;
	
    case RM_PRIMITIVE_QMESHDIMS:
	rstat = BLOB_QMESHDIMS_INDEX;
	break;
	
    case RM_PRIMITIVE_OMESHDIMS:
	rstat = BLOB_OMESHDIMS_INDEX;
	break;

    case RM_PRIMITIVE_INDICES:
	rstat = BLOB_INDEX_INDEX;
	break;

    default:
        rstat = RM_WHACKED;
        break;

	/* no texture coords yet */
    }
    return(rstat);
}


/* PRIVATE */
RMprimitiveDataBlob *
private_rmBlobFromIndex (RMprimitive *p,
			 int tag)
{
    /* used by rm routines that need to get at the data in blobs */
    RMprimitiveDataBlob *b;

    switch (tag)
    {
    case BLOB_VERTEX_INDEX:
    case BLOB_OMESH_RECTGRID_INDEX:
	b = &(p->blobs[0]);
	break;

    case BLOB_COLOR_INDEX:
	b = &(p->blobs[1]);
	break;

    case BLOB_TC_INDEX:
	b = &(p->blobs[3]);
	break;

    case BLOB_NORMAL_INDEX:
	b = &(p->blobs[2]);
	break;

    case BLOB_QMESHDIMS_INDEX:
    case BLOB_OMESHDIMS_INDEX:
    case BLOB_INDEX_INDEX:
	b = &(p->blobs[4]);
	break;
	
    case BLOB_SCALE_INDEX:
	b = &(p->blobs[5]);
	break;
	
    default:
	b = NULL;
	fprintf(stderr, "private_rmBlobFromIndex() code not finished or unrecognized blob type \n");
	break;
    }
    return(b);
}


/* PRIVATE 
 *
 * the following is a convenience routine used by most of the methods
 * that assign data to RMprimitives. it is a way to do sanity checking
 * of input parameters, and avoids a lot of duplicated code. it's a
 * compact, reusable routine for performing input validation.
 */
RMenum
private_rmPrimSetAssert (RMprimitive *t,
			 int n,
			 void *dataptr,
			 RMenum copyEnum,
			 void (*freefunc)(),
			 const char *rname)
{
    char buf[256];

    sprintf(buf, "%s error: the input primitive is NULL", rname);
    if (RM_ASSERT(t, buf) == RM_WHACKED)
	return(RM_WHACKED);
    
    sprintf(buf, "%s error: non-zero data count but have NULL data pointer.", rname);
    if (((n != 0) && (dataptr == NULL)) == 1)
    {
	rmError(buf);
	return(RM_WHACKED);
    }
	
    sprintf(buf, "%s error: when the copy enum is set to RM_DONT_COPY_DATA, the application MUST provide a free function ", rname);
    if ((freefunc == NULL) && (copyEnum == RM_DONT_COPY_DATA))
    {
	rmError(buf);
	return(RM_WHACKED);
    }
    return(RM_CHILL);
}

/* private */
RMenum
private_rmPrimitiveComputeGenericBoundingBox(RMprimitive *p)
{
    int                  nverts, stride;
    float               *v;
    RMprimitiveDataBlob *b;
    RMvertex3D           tmin, tmax;
    
    b = private_rmBlobFromIndex(p, BLOB_VERTEX_INDEX);
    v = (float *)private_rmBlobGetData(b);
    
    nverts = private_rmBlobGetNthings(b);
    stride = private_rmBlobGetStride(b);
    rmPointMinMax(v, nverts, private_rmBlobGetVeclen(b), stride, &tmin, &tmax);

    rmPrimitiveSetBoundingBox(p, &tmin, &tmax);
    return(RM_CHILL);
}

/* private */
RMenum
private_rmPrimitiveComputeSpheresBoundingBox(RMprimitive *p)
{
    int         j, rstride, nradii, rveclen, vstride, nvertices, vveclen;
    float      *radii, *vertices;
    RMvertex3D  smin, smax;
    RMvertex3D boxMin, boxMax;


    private_rmGetBlobData(BLOB_VERTEX_INDEX, p, &vstride, &nvertices, (void **)&vertices, &vveclen);
    private_rmGetBlobData(BLOB_SCALE_INDEX, p, &rstride, &nradii, (void **)&radii, &rveclen);

    /* compute bounding boxes for all spheres */
    for (j = 0; j < nvertices; j++, vertices += vstride, radii += rstride)
    {
	/* extents = center +/- radius */
	memcpy((void *)&smin, vertices, sizeof(RMvertex3D));
	smax = smin;

	smin.x -= *radii; smin.y -= *radii; smin.z -= *radii;
	smax.x += *radii; smax.y += *radii; smax.z += *radii;
		    
	/* tally sphere bounding boxes */
	if (j == 0)
	{
	    boxMin = smin;
	    boxMax = smax;
	}
	else
	    rmUnionBoundingBoxes(&boxMin, &boxMax, &smin, &smax, &boxMin, &boxMax);
    }
    /* just scan through all the vertices to find min/max */

    rmPrimitiveSetBoundingBox(p, &boxMin, &boxMax);

    return RM_CHILL;
}

/* private */
RMenum
private_rmPrimitiveComputeCylindersBoundingBox(RMprimitive *p)
{
    /*
     * we assume that the bbox for a cylinder is "almost the same as"
     * the union of the bbox formed by two spheres of radius R at
     * each end of the cylinder.
     */
    int         j, rstride, nradii, rveclen, vstride, nvertices, vveclen;
    float      *radii, *vertices;
    RMvertex3D  s1min, s1max, s2min, s2max;
    RMvertex3D  boxMin, boxMax;

    /* get vertex and radius blob info */
    private_rmGetBlobData(BLOB_VERTEX_INDEX, p, &vstride, &nvertices, (void **)&vertices, &vveclen);
    private_rmGetBlobData(BLOB_SCALE_INDEX, p, &rstride, &nradii, (void **)&radii, &rveclen);

    /* compute bounding boxes for all cylinders */
    for (j = 0; j < nvertices/2; j++, vertices += vstride, radii += rstride)
    {
	/* extents = center +/- radius */
	memcpy((void *)&s1min, vertices, sizeof(RMvertex3D));
	vertices += vstride;
	s1max = s1min;
	memcpy((void *)&s2min, vertices, sizeof(RMvertex3D));
	s2max = s2min;

	s1min.x -= *radii; s1min.y -= *radii; s1min.z -= *radii;
	s1max.x += *radii; s1max.y += *radii; s1max.z += *radii;
		    
	s2min.x -= *radii; s2min.y -= *radii; s2min.z -= *radii;
	s2max.x += *radii; s2max.y += *radii; s2max.z += *radii;

	/* tally sphere bounding boxes */
	if (j == 0)
	{
	    rmUnionBoundingBoxes(&s1min, &s1max, &s2min, &s2max, &boxMin, &boxMax);
	}
	else
	{
	    rmUnionBoundingBoxes(&boxMin, &boxMax, &s1min, &s1max, &boxMin, &boxMax);
	    rmUnionBoundingBoxes(&boxMin, &boxMax, &s2min, &s2max, &boxMin, &boxMax);
	}
    }

    rmPrimitiveSetBoundingBox(p, &boxMin, &boxMax);
    return(RM_CHILL);
}

/* private */
RMenum
private_rmPrimitiveComputeConesBoundingBox(RMprimitive *p)
{
    /*
     * we assume that the bbox for a cone can be estimated as the
     * combination of a sphere centered at the base of the cone, and the
     * line segment formed by the two cone endpoints.
     */
    int         j, rstride, nradii, rveclen, vstride, nvertices, vveclen;
    float      *radii, *vertices;
    RMvertex3D  s1min, s1max, s2min, s2max;
    RMvertex3D  boxMin, boxMax;

    /* get vertex and radius blob info */
    private_rmGetBlobData(BLOB_VERTEX_INDEX, p, &vstride, &nvertices, (void **)&vertices, &vveclen);
    private_rmGetBlobData(BLOB_SCALE_INDEX, p, &rstride, &nradii, (void **)&radii, &rveclen);

    /* compute bounding boxes for all cones */
    for (j = 0; j < nvertices/2; j++, vertices += vstride, radii += rstride)
    {
	/* extents = center +/- radius */
	memcpy((void *)&s1min, vertices, sizeof(RMvertex3D));
	vertices += vstride;
	s1max = s1min;
	memcpy((void *)&s2min, vertices, sizeof(RMvertex3D));
	s2max = s2min;

	s1min.x -= *radii; s1min.y -= *radii; s1min.z -= *radii;
	s1max.x += *radii; s1max.y += *radii; s1max.z += *radii;
		    
	/* tally sphere bounding boxes */
	if (j == 0)
	{
	    rmUnionBoundingBoxes(&s1min, &s1max, &s2min, &s2max, &boxMin, &boxMax);
	}
	else
	{
	    rmUnionBoundingBoxes(&boxMin, &boxMax, &s1min, &s1max, &boxMin, &boxMax);
	    rmUnionBoundingBoxes(&boxMin, &boxMax, &s2min, &s2max, &boxMin, &boxMax);
	}
    }

    rmPrimitiveSetBoundingBox(p, &boxMin, &boxMax);
    return(RM_CHILL);
}

/* private */
RMenum
private_rmPrimitiveComputeOctmeshBoundingBox(RMprimitive *p)
{
    float               *v;
    RMprimitiveDataBlob *b;
    RMvertex3D           tmin, tmax;
    RMenum               haveValidOctmeshBox=RM_FALSE;
    
    /* this doesn't work for the weird octmesh grid types */
    b = private_rmBlobFromIndex(p, BLOB_VERTEX_INDEX);
    v = (float *)private_rmBlobGetData(b);
	
    switch(private_rmBlobGetType(b))
    {

	/* 2/1/03 - the only support grid type for octmeshes is the
	   minmax grid. that grid is converted into a rectilinear
	   form when the app sets the grid corners. */
       case BLOB_OMESH_RECTGRID_INDEX:
       {
	   int                 *dims, i;
	   RMprimitiveDataBlob *s;
	   int npts;
		    
	   i = private_rmBlobIndexFromPrimAtom(RM_PRIMITIVE_OMESHDIMS);
	   s = private_rmBlobFromIndex(p, i);
	   
	   npts = private_rmBlobGetNthings(s);
	   dims = (int *)private_rmBlobGetData(s);
	   if (dims == NULL) 	/* no size info, can't compute size so leave it unchanged*/
	       return(RM_WHACKED);

	   haveValidOctmeshBox = RM_TRUE;
	   
	   tmin.x = tmax.x = v[0];
	   for (i = 1; i < dims[0]; i++)
	       {
		   if (v[i] < tmin.x)
		       tmin.x = v[i];
		   if (v[i] > tmax.x)
		       tmax.x = v[i];
	       }
	   
	   v += dims[0];
	   tmin.y = tmax.y = v[0];
	   for (i = 1; i < dims[1]; i++)
	       {
		   if (v[i] < tmin.y)
		       tmin.y = v[i];
		   if (v[i] > tmax.y)
		       tmax.y = v[i];
	       }
	   
	   v += dims[1];
	   tmin.z = tmax.z = v[0];
	   for (i = 1; i < dims[2]; i++)
	       {
		   if (v[i] < tmin.z)
		       tmin.z = v[i];
		   if (v[i] > tmax.z)
		       tmax.z = v[i];
	       }
	   break;
       }
	    
    default: /* bogus blob type */
	break;
    } /* end blobtype switch */

    if (haveValidOctmeshBox == RM_TRUE)
    {
	rmPrimitiveSetBoundingBox(p, &tmin, &tmax);
	return RM_CHILL;
    }
    else
    {
	rmWarning("private_rmPrimitiveComputeOctmeshBoundingBox() - unable to obtain valid octmesh grid data. ");
	return RM_WHACKED;
    }
}

/* private */
RMenum
private_rmPrimitiveCompute2DCircleBoundingBox(RMprimitive *p)
{
    /* just scan through all the vertices to find min/max */
    rmNotice("private_rmPrimitiveCompute2DCircleBoundingBox(RMprimitive *p) - no code yet.");
    p = NULL; 			/* foil compiler warning */
    return RM_WHACKED;
}

/* private */
RMenum
private_rmPrimitiveCompute2DEllipseBoundingBox(RMprimitive *p)
{
    /* just scan through all the vertices to find min/max */
    rmNotice("private_rmPrimitiveCompute2DEllipseBoundingBox(RMprimitive *p) - no code yet. ");
    p = NULL; 			/* foil compiler warning */
    return RM_WHACKED;
}

/* private */
RMenum
private_rmPrimitiveNullBoxFunc(RMprimitive *p)
{
    /* */
    p = NULL; 			/* foil compiler warning */
    return RM_WHACKED;
}
/* EOF */
