/*
 * 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: rmnode.c,v 1.30 2008/11/23 15:14:16 wes Exp $
 * Version: $Name: v180-alpha-02 $
 * $Revision: 1.30 $
 * $Log: rmnode.c,v $
 * Revision 1.30  2008/11/23 15:14:16  wes
 * 1. Changes to support the RMnode's use of an RMarray to hold
 * children pointers rather than an RMnode * array. This change will
 * allow us to avoid doing a realloc each time you add children to nodes
 * since the RMarray uses a chunking realloc strategy.
 * 2. Add new routine: rmNodeRemoveIthChild().
 *
 * Revision 1.29  2008/09/22 01:47:40  wes
 * SG sync code for client data on nodes and prims
 *
 * Revision 1.28  2007/05/14 15:29:00  wes
 * Minor doc changes
 *
 * Revision 1.27  2006/11/19 17:15:02  wes
 * Modification to rmSubTreeDelete. Added a boolean argument that says
 * whether or not to delete the root node passed in to rmSubTreeDelete.
 *
 * Revision 1.26  2006/08/02 04:58:59  wes
 * In rmPrintSceneGraph, added better code to not report bogus linestyle
 * enumerators.
 *
 * Revision 1.25  2005/09/12 04:03:51  wes
 * Minor documentation updates.
 *
 * Revision 1.24  2005/06/26 18:55:32  wes
 * Updates to rmPrintSceneGraph: added printing of fog scene parameter,
 * made sure all current RMprimitive types are correctly reported.
 *
 * Revision 1.23  2005/06/15 02:10:40  wes
 * Added initial support for application-settable defaults. Apps
 * will use rmSetEnum/rmGetEnum to set or get defaults that are RMenums.
 * The first round of variables that can be set/get by apps are
 * RMnode traversal masks assigned to new scene graph nodes by rmNodeNew.
 *
 * Revision 1.22  2005/06/06 02:04:14  wes
 * Added code that appears to be a bug in how many OpenGL implementations
 * save/restore state when doing a glPushAttrib & clip planes. All that should
 * be needed is GL_ENABLE_BIT and GL_TRANSFORM_BIT. It seems that lighting/
 * color variables are corrupted in some cases, and added GL_LIGHTING_BIT
 * seems to fix the problem (first shown in clipper.c).
 *
 * Revision 1.21  2005/05/28 16:24:22  wes
 * Add new routines: rmNodeSetPickEnable, rmNodeGetPickEnable
 *
 * Revision 1.20  2005/03/16 16:39:15  wes
 * Added more detailed RMprimitive output (colors, normals) in
 * rmPrintSceneGraph.
 *
 * Revision 1.19  2005/02/27 19:34:04  wes
 * Added support for application supplied texture object IDs and display lists.
 *
 * Revision 1.18  2005/02/19 16:35:16  wes
 * Distro sync and consolidation.
 * Beef up output from rmPrintSceneGraph.
 *
 * Revision 1.17  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.16  2004/09/28 00:48:57  wes
 * Added render state cache as a parameter to routines that may modify
 * lighting state to fix a lighting state tracking problem.
 *
 * Revision 1.15  2004/08/07 17:11:32  wes
 * Updates to rmPrintSceneGraph to show information about the fbClear
 * operations at a node. This will be helpful in tracking down errors
 * caused by having multiple fbClears, or fbClears contained in nodes
 * scheduled for multiple rendering passes.
 *
 * Revision 1.14  2004/03/30 14:13:31  wes
 * Fixed declarations and man page docs for several routines.
 *
 * Revision 1.13  2004/03/10 01:47:41  wes
 * Removed a bunch of dead code, and added new routine rmNodeRemoveAllPrims.
 *
 * Revision 1.12  2004/01/16 16:46:09  wes
 * Updated copyright line for 2004.
 *
 * Revision 1.11  2003/11/05 15:28:17  wes
 * Revision update.
 *
 * Revision 1.9  2003/07/06 16:37:13  wes
 * Minor typo in code update fixed.
 *
 * Revision 1.8  2003/07/06 16:32:22  wes
 * Bug fix: refined test for enabling GL_LIGHTING_BIT in attrib mask in
 * private_rmNodeComputeAttribMask(). Thanks to A. Yarnos for pointing
 * out the problem.
 *
 * Revision 1.7  2003/06/14 03:17:13  wes
 * rmNodeLineStyle - code addition.
 *
 * Revision 1.6  2003/05/04 15:58:15  wes
 * Fixed rmNodeComputeBoundingBox bug that resulted in bogus bounding
 * boxes in some circumstances.
 *
 * Revision 1.5  2003/04/05 14:11:24  wes
 * Several changes:
 * 1. renamed rmMutexDestroy to rmMutexDelete for API consistency.
 * 2. fixed logic errors in rmNodeRemoveChild and rmNodeAddPrimitive so that
 * 	mutexes are properly unlocked when error conditions are encountered.
 * 3. RMstate's allocated in rmSceneGraphWalk and descendents are freed (leak).
 *
 * Revision 1.4  2003/02/14 00:17:14  wes
 * No significant changes.
 *
 * 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.22  2003/01/27 05:04:42  wes
 * Changes to RMpipe API and initialization sequence to unify GLX, WGL and CR
 * platforms w/o too much disruption to existing apps.
 *
 * Revision 1.21  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.20  2003/01/12 23:50:06  wes
 * Minor adjustments to texturing environment controls to fix problems with
 * the texture environment not being set correctly.
 *
 * Revision 1.19  2003/01/09 16:39:35  wes
 * Minor documentation update to rmNodeComputeBoundingBox().
 *
 * Revision 1.18  2002/12/31 00:55:22  wes
 *
 * Various enhancements to support Chromium - achitecture-specific sections
 * of RMpipe were cleaned up, etc.
 *
 * Revision 1.17  2002/12/04 14:50:33  wes
 * Cleanup SGI compiles.
 *
 * Revision 1.16  2002/11/14 15:34:51  wes
 * Minor editing for beautification.
 *
 * Revision 1.15  2002/09/22 17:34:49  wes
 * Modified rmSubtreeDelete to not bother looking at an RMnode's refcount
 * while making a decision about recursing. This will fix a memory leak
 * reported by a user application.
 *
 * Revision 1.14  2002/08/17 15:14:15  wes
 * Added a new routine: rmNodeSetRenderOrderCallback, which is used to
 * reorder the render order of a node's children during the render traversal.
 * this callback is invoked ONLY in the view traversal.
 *
 * Revision 1.13  2002/06/29 16:22:00  wes
 * Tagged rmNodeSetOpacity and rmNodeGetOpacity as being deprecated.
 *
 * Revision 1.12  2002/06/10 20:47:13  wes
 * Fixed a bug whereby the "unlit color" was not properly surviving
 * a state push/pop. The unlit color at a node affects the diffuse
 * material property, as well as setting the current RGBA color. By
 * changing the mask from GL_CURRENT_BIT, which captures the current
 * color, to GL_CURRENT_BIT | GL_LIGHTING_BIT, both the current RGBA
 * color as well as current material colors are captured. This bug
 * was revealed by the "pickTest" demo.
 *
 * Revision 1.11  2002/04/30 19:32:47  wes
 * Updated copyright dates.
 *
 * Revision 1.10  2001/06/03 20:48:17  wes
 * Removed unused variables to get rid of compile warnings.
 *
 * Revision 1.9  2001/03/31 17:12:39  wes
 * v1.4.0-alpha-2 checkin.
 *
 * Revision 1.8  2000/12/03 22:33:55  wes
 * Mods for thread-safety.
 *
 * Revision 1.7  2000/08/30 02:03:39  wes
 * Fixed bug in rmNodeSetPolygonMode - added RM_BACK to validity
 * check of input face parameter.
 *
 * Revision 1.6  2000/08/27 17:37:11  wes
 * Minor tweaks to improve process of bbox computation.
 *
 * Revision 1.5  2000/08/23 23:16:02  wes
 * Updates to rmNodeComputeBoundingBox: switch over RMprimitive type,
 * added code to compute bboxes for spheres (contributed by jdb),
 * moved lengthy code from rmNodeComputeBounding box to a "generic
 * bbox compute" routine.
 *
 * Revision 1.4  2000/05/17 14:23:38  wes
 * Fixed compile warnings on private_rmStateInit().
 *
 * Revision 1.3  2000/05/14 23:39:58  wes
 * Single parms to state initialization for rmSceneGraphWalk().
 *
 * Revision 1.2  2000/04/20 16:29:28  wes
 * Significant documentation additions (wes), some code rearrangement.
 * See $RM/RELEASENOTES for specific details concerning API changes.
 *
 * 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.
 *
 */

/* documentation of public routines is incomplete in this file. */

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

static RMnode *root = NULL; /* NOT thread-safe! */

/* PRIVATE declarations */
void                              private_rmSceneGraphWalk (RMnode *r, const RMstate *last, void (*userfunc)(RMnode *node, const RMstate *state, void *clientData), void *clientData);
internals_RMtransformationStruct *private_rmNodeTransformsNew (void);
void                              private_rmPrintSceneGraph (const RMnode *r, int level, RMenum pmode, FILE *f);
RMenum                            *private_rmEnumNew (int n);
_surface_properties               *private_rmSurfacePropsNew (void);
_rendermode_properties            *private_rmRenderModePropsNew (void);


/*
 * ----------------------------------------------------
 * @Name rmRootNode
 @pstart
 RMnode * rmRootNode (void)
 @pend

 @astart
 No arguments.
 @aend

 @dstart

 Returns a handle to the "RM Root Node." The RM Root Node is intended
 to be used as the root for all application-built scene graphs,
 although this isn't strictly necessary.

 Some RM routines (notable, rmFrame()) assume that their activities
 will commence at the rmRootNode() and proceed downward through all
 relative nodes.

 Applications may modify parameters of the rmRootNode(), but must not
 delete it. Parameter modification include setting scene parameters,
 such as the background color, etc. as well as assigning RMprimitives.
 
 @dend
 * ----------------------------------------------------
 */
RMnode *
rmRootNode (void)
{
    return(root);
}


/*
 * ----------------------------------------------------
 * @Name rmNodeNew
 @pstart
 RMnode * rmNodeNew(char *name,
	            RMenum dimsTraversalMask,
		    RMenum opacityTraversalMask)
 @pend

 @astart
 char *name - a character string, used to set the internal RMnode "name"
    attribute (input).
 RMenum dimsTraversalMask - an RMenum value, may be either RM_RENDERPASS_3D,
    RM_RENDERPASS_2D or RM_RENDERPASS_ALL (input).
 RMenum opacityTraversalMask - an RMenum value, may be one of
  RM_RENDERPASS_OPAQUE, RM_RENDERPASS_TRANSPARENT or RM_RENDERPASS_ALL (input).
 @aend

 @dstart
 This routine creates a new RMnode, and returns a handle to the caller upon
 success, or NULL upon failure.

 During render-time traversal of the scene graph, render parameters can be
 compared against certain attributes of the RMnode, thereby terminating
 depth traversal. Three such traversal masks exist at this time (February
 2000): 2D vs. 3D, opaque pass vs. transparent pass, and for stereo rendering,
 left vs. right channel. The first two attributes, opaque vs. transparent and
 2D vs. 3D are specified at the time the RMnode is created. These may be
 subsequently overridden, if desired, using rmNodeSetTraversalMaskOpacity()
 or rmNodeSetTraversalMaskDims().

 Control over the filter masks used to compare against RMnode attributes
 is manipulated by the multipass rendering framework. (Feb 2000, this needs
 to be written).

 When created, the RMnode is assigned the following default values:

 1. The "name" attribute of the RMnode is set to contain a copy of the input
 string "name" (rmNodeSetName()).

 2. The RMnode's bounding box minimum and maximum coordinates are set to
 RM_MAXFLOAT and RM_MINFLOAT, respectively.

 3. The node's "traverse enable" attribute is set to RM_TRUE.
 4. The node's "pick enable" attribute is set to RM_TRUE.

 4. The node is scheduled for rendering during all passes of a stereo
 rendering (rmNodeSetTraversalMaskChannel()).

 5. The node's transformation attributes, if any, are applied to
 the "model-view" matrix stack (as opposed to the texture stack)
 (rmNodeSetTransformMode()).
 
 @dend
 * ----------------------------------------------------
 */

RMnode *
rmNodeNew(char *name,
	  RMenum rVdims,
	  RMenum rOpaque)
{
    RMnode *n;
    RMvertex3D bmin,bmax,center;
    RMenum t;

    if ((rVdims != RM_RENDERPASS_2D) && (rVdims != RM_RENDERPASS_3D) && (rVdims != RM_RENDERPASS_ALL))
    {
	rmError("rmNodeNew(): the input parameter dimsTraversalMask is not valid.");
	return(NULL);
    }

    if ((rOpaque != RM_RENDERPASS_OPAQUE) && (rOpaque != RM_RENDERPASS_TRANSPARENT) && (rOpaque != RM_RENDERPASS_ALL))
    {
	rmError("rmNodeNew(): the input parameter opacityTraversalMask is not valid.");
	return(NULL);
    }

    /* new with component manager */
    n = private_rmNodeNew();

    if (RM_ASSERT(n,"rmNodeNew() error: malloc fails. \n") == RM_WHACKED)
	return(NULL);

    n->parent = NULL;
    /* 
     * New in 1.8.0 - use the RMarray to hold children rather than
     * using a realloc to grow the array each time. These parameters
     * set the realloc threshold at 16
     */
    n->childrenArray = rmArrayNew(0, RM_NODE_CHILD_ARRAY_CHUNKSIZE, sizeof(RMnode *));

#if 0
    /* from 1.6.2 and earlier */
    n->nchildren = 0;
    n->children = NULL;
#endif

    n->nprims = 0;
    n->prims = NULL;

    n->sprops = NULL;
    n->rprops = NULL;
    n->scene_parms = NULL;
    n->fbClear = NULL;

    n->transforms = NULL;

    n->viewPretraverseCallback = NULL;
    n->viewPosttraverseCallback = NULL;
    n->viewSwitchCallback = NULL;
    n->viewRenderOrderCallback = NULL;

    n->renderPretraverseCallback = NULL;
    n->renderPosttraverseCallback = NULL;

    n->clientData = NULL;
    n->clientDataFreeFunc = NULL;

    n->attribMask = 0;
    n->refcount = 0;

    if (rmDataSyncGetPolicy() == RM_DATA_SYNC_DOUBLE_BUFFERED)
	n->nodeMutex = rmMutexNew(RM_MUTEX_UNLOCK);
    else
	n->nodeMutex = NULL;

    /* obtain current value for general traversal mask */
    rmGetEnum(RM_DEFAULT_NODE_TRAVERSAL_MASK, &t);
    rmNodeSetTraverseEnable(n, t);
    
    /* obtain current value for pick traversal mask */
    rmGetEnum(RM_DEFAULT_NODE_PICK_TRAVERSAL_MASK, &t);
    rmNodeSetPickEnable(n, t);

    /* give it a bogus bounding box */
    bmin.x = bmin.y = bmin.z = RM_MAXFLOAT;
    bmax.x = bmax.y = bmax.z = RM_MINFLOAT;
    center.x = center.y = center.z = 0.0F;
    
    rmNodeSetBoundingBox(n,&bmin,&bmax);
    rmNodeSetCenter(n, &center);

    rmNodeSetTraversalMaskChannel(n,RM_ALL_CHANNELS);
    
    rmNodeSetName(n,name);

    rmNodeSetTraversalMaskOpacity(n,rOpaque);
    rmNodeSetTraversalMaskDims(n,rVdims);
    
    return(n);
}

/*
 * ----------------------------------------------------
 * @Name rmNodeDelete
 @pstart
 RMenum rmNodeDelete (RMnode *toDelete)
 @pend

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

 @dstart

 This routine is the opposite of rmNodeNew(), and will delete
 resources associated with an RMnode, including scene parameters,
 rendering parameters, and all RMprimitives. Returns RM_CHILL upon
 success, or RM_WHACKED upon failure.

 This routine deletes only a single RMnode. Use rmSubTreeDelete() to
 delete an entire, connected, rooted scene graph.

 RMnodes maintain an internal reference count; it is an error to
 delete an RMnode that has a non-zero reference count. The RMnode
 reference count is modified by adding nodes into the scene graph
 (rmNodeAddChild), or by removing them from the scene graph
 (rmNodeRemoveChild, rmNodeRemoveAllChildren). Attempts to delete
 RMnodes with a non-zero reference count will not succeed, but will be
 rewarded with an RM_WHACKED return status.

 @dend
 * ----------------------------------------------------
 */
RMenum
rmNodeDelete (RMnode *r)
{
    if (RM_ASSERT(r, "rmNodeDelete() error: the input RMnode pointer is NULL.") == RM_WHACKED)
	return(RM_WHACKED);

    /* fall 2007 */
    if (rmDataSyncGetPolicy() == RM_DATA_SYNC_DOUBLE_BUFFERED &&
	(r->nodeMutex != NULL))
	rmMutexLock(r->nodeMutex);

    if ((private_rmNodeDelete(r) == RM_CHILL) &&
	(rmDataSyncGetPolicy() == RM_DATA_SYNC_DOUBLE_BUFFERED) &&
	(r->nodeMutex != NULL))
    {
	/* 
	 * if the private_rmNodeDelete succeeded, then the node's refcount
	 * was == 0, meaning that it is not part of the scene graph. After
	 * private_rmNodeDelete finishes, the node is moved from the
	 * internal alloc to free list. So, the node is still floating
	 * around and we can do the following calls to release and clean
	 * up the mutex. The mutex release and cleanup business is needed
	 * only when the data sync policy is DOUBLE_BUFFERED.
	 */
	rmMutexUnlock(r->nodeMutex);
	rmMutexDelete(r->nodeMutex);
	r->nodeMutex = NULL;
    }
    else if ((rmDataSyncGetPolicy() == RM_DATA_SYNC_DOUBLE_BUFFERED) && (r->nodeMutex != NULL))
	rmMutexUnlock(r->nodeMutex); /* node's refcount > 0 */

    return(RM_CHILL);
}


/*
 * ----------------------------------------------------
 * @Name rmSubTreeDelete
 @pstart
 RMenum rmSubTreeDelete (RMnode *toDelete, RMenum deleteRootBool)
 @pend

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

 RMenum deleteRootBool - an RMenum: RM_TRUE or RM_FALSE. A value of
 RM_TRUE will cause the node "toDelete" to be deleted as well as all
 its children. A value of RM_FALSE will cause only the children rooted
 at "toDelete" to be deleted; the node "toDelete" will not be deleted.
 @aend

 @dstart

 Use this routine to delete a collection of nodes in a scene graph
 rooted at "toDelete".  This routine performs a depth-first,
 left-to-right traversal of the scene graph rooted at "toDelete", and
 performs an rmNodeDelete operation during the traversal of all nodes
 encountered during the traversal. 

 The root of the traversal, the node "toDelete", will also be deleted
 if the "deleteRootBool" parameter is set to RM_TRUE. If that parm is
 set to RM_FALSE, the root node "toDelete" is not deleted.

 Because of internal reference count machinery, RMnodes in the tree that
 are scheduled for deletion will not actually be deleted if they are a
 part of some other scene graph tree. That condition exists when you build
 up a scene graph using rmNodeAddChild, etc.

 RM_CHILL is returned upon success, or RM_WHACKED upon failure. 

 @dend
 * ----------------------------------------------------
 */
RMenum
rmSubTreeDelete (RMnode *r,
		 RMenum deleteRootBool)
{
    int     i, n;
    RMnode *c;

    /* first, free the children of this node, then free the stuff at this node */
    if (r == NULL)
	return(RM_WHACKED);

    n = rmNodeGetNumChildren(r);

/*    for (i=0; i < n; i++) */
    for (i=n-1; i > 0; i--) 
    {
	c = private_rmNodeGetIthChild(r, i);
	rmNodeRemoveIthChild (r, i);

/*	if (private_rmNodeGetRefcount(c) == 0) */
	/* 9/20/02 removed the condition - causes a memory leak under
	   some circumstances. Note that rmNodeDelete checks the refct
	   of the node - doing so again here is an error */
	rmSubTreeDelete(c, RM_TRUE);
    }

    if (deleteRootBool == RM_TRUE)
	rmNodeDelete(r);

    return(RM_CHILL);
}

/*
 * ----------------------------------------------------
 * @Name rmNodeSetName
 @pstart
 RMenum rmNodeSetName (RMnode *toModify,
	               const char *name)
 @pend

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

 const char *name - a character string (input).
 @aend

 @dstart

 Use this routine to set the "name" attribute of an RMnode. The input
 string is copied into a character string inside the RMnode. The
 maximum string length allowable is defined by the constant
 RM_MAX_STRING_LENGTH.  The RMnode name attribute is used when
 searching for a named node.

 RM_CHILL is returned upon success, and RM_WHACKED is returned upon
 failure.

 @dend
 * ----------------------------------------------------
 */
RMenum
rmNodeSetName (RMnode *n,
	       const char *name)
{
    if (RM_ASSERT(n, "rmNodeSetName() error: the input RMnode is NULL.") == RM_WHACKED)
	return(RM_WHACKED);

    if (strlen(name) >= RM_MAX_STRING_LENGTH)
    {
	rmError("rmNodeSetName() error: the length of the input string exceeds RM_MAX_STRING_LENGTH");
	return(RM_WHACKED);
    }

    memset(n->object_info.name, 0, RM_MAX_STRING_LENGTH);
    strcpy(n->object_info.name, name);

    return(RM_CHILL);
}


/*
 * ----------------------------------------------------
 * @Name rmNodeGetName
 @pstart
 char * rmNodeGetName (const RMnode *toQuery)
 @pend

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

 @dstart

 Use this routine to obtain a COPY of the name string attribute of an
 RMnode. A character string is returned to the caller upon success,
 otherwise NULL is returned.

 Since the return string is a COPY malloc'ed off the heap, the caller
 should free() the return string when it is no longer needed.

 @dend
 * ----------------------------------------------------
 */
char *
rmNodeGetName (const RMnode *n)
{
    char *c;

    if (RM_ASSERT(n, "rmNodeGetName() error: the input RMnode pointer is NULL.") == RM_WHACKED)
	return(NULL);

    c = malloc(sizeof(char)*strlen(n->object_info.name)+1); /* no strdup() ?? */
    strcpy(c, n->object_info.name);

    return(c);
}


/*
 * ----------------------------------------------------
 * @Name rmNodeSetTraverseEnable
 @pstart
 RMenum rmNodeSetTraverseEnable (RMnode *toModify,
			         RMenum newval)
 @pend

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

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

 @dstart

 Use this routine to modify the value of the "general traverse enable"
 attribute of an RMnode. This attribute affects all frame-based traversals: 
 view, render, and pick. A newVal of RM_TRUE specifies that a node will be 
 processed during such traversals. A newVal of RM_FALSE inhibits processing 
 of the node toModify along with all its children.

 As of RM version 1.6.0, the point during node processing when RM checks
 the value of the traversal flag varies between multistage rendering processing,
 serial rendering processing, and picking varies. This inconsistency will be
 remedied in a future RM version. For multistage rendering, the traverse enable
 flag is checked prior to any other node processing. For picking and serial
 rendering, the traverse enable flag is processed after a node's pretraversal
 callback.

 By default, when an RMnode is created with rmNodeNew, the value of
 this attribute is set to RM_TRUE.

 Returns RM_CHILL upon success, or RM_WHACKED upon failure.

 @dend
 * ----------------------------------------------------
 */
RMenum
rmNodeSetTraverseEnable (RMnode *t,
			 RMenum status)
{
    if (RM_ASSERT(t, "rmNodeSetTraverseEnable() error: the input RMnode is NULL.") == RM_WHACKED)
	return(RM_WHACKED);
    
    if ((status != RM_TRUE) && (status != RM_FALSE))
    {
	rmError("rmNodeSetTraverseEnable() enumeration error: new value is neither RM_TRUE nor RM_FALSE. \n");
	return(RM_WHACKED);
    }
    
    t->object_info.posted = status;

    return(RM_CHILL);
}


/*
 * ----------------------------------------------------
 * @Name rmNodeGetTraverseEnable
 @pstart
 RMenum rmNodeGetTraverseEnable (const RMnode *toQuery)
 @pend

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

 @dstart

 Returns to the caller the RMnode's "traverse enable" attribute. Upon
 success, the return value will be either RM_TRUE or RM_FALSE. A
 return value of RM_WHACKED indicates an error condition.

 @dend
 * ----------------------------------------------------
 */
RMenum
rmNodeGetTraverseEnable (const RMnode *t)
{
    if (RM_ASSERT(t, "rmNodeGetTraverseEnable() error: the input RMnode is NULL. ") == RM_WHACKED)
	return(RM_WHACKED);
    
    return(t->object_info.posted);
}

/*
 * ----------------------------------------------------
 * @Name rmNodeGetPickEnable
 @pstart
 RMenum rmNodeGetPickEnable (const RMnode *toQuery)
 @pend

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

 @dstart

 Returns to the caller the RMnode's "pick enable" attribute. Upon
 success, the return value will be either RM_TRUE or RM_FALSE. A
 return value of RM_WHACKED indicates an error condition.

 @dend
 * ----------------------------------------------------
 */
RMenum
rmNodeGetPickEnable (const RMnode *toQuery)
{
    if (RM_ASSERT(toQuery, "rmNodeGetPickEnable() error: the input RMnode is NULL. ") == RM_WHACKED)
	return(RM_WHACKED);
    
    return(toQuery->object_info.pickEnable);
}

/*
 * ----------------------------------------------------
 * @Name rmNodeSetPickEnable
 @pstart
 RMenum rmNodeSetPickEnable (RMnode *toModify,
 RMenum newval)
 @pend

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

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

 @dstart

 Use this routine to modify the "pick enable" attribute of an
 RMnode. Returns RM_CHILL upon success, or RM_WHACKED upon failure.

 The "pick enable" attribute controls whether or not a node is
 traversed during a pick operation. It is similar to the "traverse
 enable" attribute, but is used only during picks. The pick
 enable attribute can be used to accelerate picking operations in
 complex scenes where only a small subset of the scene is pickable.

 By default, when an RMnode is created with rmNodeNew, the value of
 this attribute is set to RM_TRUE.

 @dend
 * ----------------------------------------------------
 */
RMenum
rmNodeSetPickEnable (RMnode *toModify,
		     RMenum newVal)
{
    if (RM_ASSERT(toModify, "rmNodeSetPickEnable() error: the input RMnode is NULL.") == RM_WHACKED)
	return(RM_WHACKED);
    
    if ((newVal != RM_TRUE) && (newVal != RM_FALSE))
    {
	rmError("rmNodeSetPickEnable() enumeration error: new value is neither RM_TRUE nor RM_FALSE. \n");
	return(RM_WHACKED);
    }
    
    toModify->object_info.pickEnable = newVal;

    return(RM_CHILL);
}

/*
 * ----------------------------------------------------
 * @Name rmNodeAddChild
 @pstart
 RMenum rmNodeAddChild (RMnode *parent,
		        RMnode *child)
 @pend

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

 RMnode *child - a handle to an RMnode (input).
 @aend

 @dstart

 Use this routine to establish a parent-child relationship between two
 RM scene graph nodes. This routine is the fundamental building block
 used to construct the scene graph. Returns RM_CHILL upon success or
 RM_WHACKED upon failure.

 The node "child" is appended to the list of children at node
 "parent".  It is not possible to insert a child at a specific index
 within the array of children. This can be done explicitly by calling
 rmNodeAddChild in order, thereby producing the sequence of children
 desired at an RMnode.

 There is no limit to the number of children nodes at an RMnode.

 Feb 2001 - This routine is thread-safe. Multiple application threads may
 simultaneously call this routine; thread-safety is provided by mutex
 locks in the component manager.

 @dend
 * ----------------------------------------------------
 */
RMenum
rmNodeAddChild (RMnode *parent,
		RMnode *child)
{
    extern RMcompMgrHdr *global_RMnodePool;
    
    if ((RM_ASSERT(parent, "rmNodeAddChild() error: the RMnode pointer 'parent' is NULL.") == RM_WHACKED) ||
	(RM_ASSERT(child, "rmNodeAddChild() error: the RMnode pointer 'child' is NULL.") == RM_WHACKED))
	return(RM_WHACKED);
    
    /*
     * rather than realloc each time, we should alloc a chunk of RMnode *'s,
     * then when we fill it up, realloc a larger chunk.
     */
    if (rmMutexLock(global_RMnodePool->guard) == RM_WHACKED)
    {
	rmError("rmNodeAddChild() error: problem locking guard mutex in component manager. ");
	return(RM_WHACKED);
    }

    /* 1.8.0 use RMarray */
    rmArrayAdd(parent->childrenArray, (void *)&child);
#if 0    
    /* 1.6.2 and earlier */
    parent->children = (RMnode **)realloc(parent->children, sizeof(RMnode *)*(parent->nchildren+1));
    parent->children[parent->nchildren] = child;
    parent->nchildren += 1;
#endif

     /* increment reference count of child */
    private_rmNodeIncrementRefcount(child);

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


/*
 * ----------------------------------------------------
 * @Name rmNodeRemoveChild
 @pstart
 RMenum rmNodeRemoveChild (RMnode *parent,
	     	           RMnode *child)
 @pend

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

 RMnode *child - a handle to an RMnode (modified).
 @aend

 @dstart

 Will remove the RMnode "child" from the list of children nodes at
 "parent". If "child" is not a child node of "parent", RM_WHACKED is
 returned. Otherwise, the reference to "child" is removed from
 "parent" and the count of children is decremented by one.

 Upon successful location of "child" in "parent," the array of
 children will be modified so that it is contiguous. Otherwise, the
 metaphor of number of children and child indexing (rmNodeGetIthChild)
 would break.

 Consider this example. Upon entry to rmNodeRemoveChild, the array of
 children at an RMnode is:

 [A B C D]

 and we wish to remove child "B". Upon exit from this routine, the
 array of children will look like this:

 [A C D]

 Feb 2000 - at this time, RMnodes only know about their children, but
 not parents. Therefore, the RMnode child is not modified in any way.
 In the future, RMnodes might know about their parents, in which case
 "child" would be modified so that it has one fewer parent references.

 Feb 2001 - this routine is thread-safe: multiple applications threads
 may safely call this routine simultaneously. Thread-safety is implemented
 by mutexes in the component manager.

 Returns RM_CHILL upon success, or RM_WHACKED upon failure.

 @dend
 * ----------------------------------------------------
 */
RMenum
rmNodeRemoveChild (RMnode *parent,
		   RMnode *child)
{
    int i;
    extern RMcompMgrHdr *global_RMnodePool;

    if ((RM_ASSERT(parent, "rmNodeRemoveChild() error: the RMnode pointer 'parent' is NULL. ") == RM_WHACKED) ||
	(RM_ASSERT(child, "rmNodeRemoveChild() error: the RMnode pointer 'child' is NULL.") == RM_WHACKED))
	return(RM_WHACKED);

    if (rmMutexLock(global_RMnodePool->guard) == RM_WHACKED)
    {
	rmError("rmNodeRemoveChild() error: problem locking guard mutex in component manager. ");
	return(RM_WHACKED);
    }
    
    /* determine the index of this child */
    for (i = 0; i < rmNodeGetNumChildren(parent); i++)
    {
	if (rmNodeGetIthChild(parent, i) == child)
	    break;
    }

    if (i == rmNodeGetNumChildren(parent)) /* didn't find it */
    {
	if (rmMutexUnlock(global_RMnodePool->guard) == RM_WHACKED)
	{
	    rmError("rmNodeRemoveChild() error: problem unlocking guard mutex in component manager. ");
	}
	return(RM_WHACKED);
    }

    rmArrayRemove(parent->childrenArray, i); 	/* found it, remove it from the array */

    private_rmNodeDecrementRefcount(child);

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



/*
 * ----------------------------------------------------
 * @Name rmNodeRemoveIthChild
 @pstart
 RMenum rmNodeRemoveChild (RMnode *parent,
	     	           int indx)
 @pend

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

 int indx - the index of the child to be removed (input)
 @aend

 @dstart

 Will remove the I'th "child" from the list of children nodes at
 "parent". If indx is out of range -- less than zero or greater than 
 the number of child nodes -- RM_WHACKED is  returned. Otherwise, the 
 reference to I'th child is removed from "parent," and the count of 
 children is decremented by one.

 After deleting the I'th child of "parent," the parent's array of 
 children will be modified so that it is contiguous. Otherwise, the
 metaphor of number of children and child indexing (rmNodeGetIthChild)
 would break.

 Consider this example. Upon entry to rmNodeRemoveChild, the array of
 children at an RMnode is:

 [A B C D]

 and we wish to remove the indx=1 (or second) child, which is node "B". 
 Upon exit from this routine, the array of children will look like this:

 [A C D]

 Returns RM_CHILL upon success, or RM_WHACKED upon failure.

 @dend
 * ----------------------------------------------------
 */
RMenum
rmNodeRemoveIthChild (RMnode *parent,
		      int indx)
{
    RMnode *child;
    extern RMcompMgrHdr *global_RMnodePool;

    if ((RM_ASSERT(parent, "rmNodeRemoveChild() error: the RMnode pointer 'parent' is NULL. ") == RM_WHACKED) ||
	(RM_ASSERT(child, "rmNodeRemoveChild() error: the RMnode pointer 'child' is NULL.") == RM_WHACKED))
	return(RM_WHACKED);

    if (rmMutexLock(global_RMnodePool->guard) == RM_WHACKED)
    {
	rmError("rmNodeRemoveChild() error: problem locking guard mutex in component manager. ");
	return(RM_WHACKED);
    }
    
    if ((indx < 0) || (indx >= rmNodeGetNumChildren(parent)))
    {
	if (rmMutexUnlock(global_RMnodePool->guard) == RM_WHACKED)
	{
	    rmError("rmNodeRemoveIthChild() error: problem unlocking guard mutex in component manager. ");
	}
	return(RM_WHACKED);
    }

    child = private_rmNodeGetIthChild(parent, indx);

    rmArrayRemove(parent->childrenArray, indx);	/* found it, remove it from the array */

    private_rmNodeDecrementRefcount(child);

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


/*
 * ----------------------------------------------------
 * @Name rmNodeRemoveAllChildren
 @pstart
 RMenum rmNodeRemoveAllChildren (RMnode *toModify)
 @pend

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

 @dstart
 
 Use this routine to break the parent-child relationship at a given
 RMnode. This routine will simply remove all nodes from the list of
 children at an RMnode, and set the count of the number of children to
 zero. The child nodes are not deleted; only the reference to them is
 removed from the input node.

 RM_CHILL is returned upon success, or RM_WHACKED upon failure.
 
 @dend
 * ----------------------------------------------------
 */
RMenum
rmNodeRemoveAllChildren (RMnode *toModify)
{
    int i, n;

    if (RM_ASSERT(toModify, "rmNodeRemoveAllChildren() error: the input RMnode is NULL. ") == RM_WHACKED)
	return(RM_WHACKED);

    /* 1.8.0 - first iterate over kids, decrement ref count in each */
    n = private_rmNodeGetNumChildren(toModify);
    for (i = 0; i < n; i++)
    {
	RMnode *t;
	t = private_rmNodeGetIthChild(toModify, i);
	private_rmNodeDecrementRefcount(t);
    }

    /* then, just nuke the childArray, and make another one */
    rmArrayDelete(&(toModify->childrenArray));
    toModify->childrenArray = rmArrayNew(0, RM_NODE_CHILD_ARRAY_CHUNKSIZE, sizeof(RMnode *));

#if 0
    /* 1.6.2 and earlier */
    /* quickly, remove all the children of an RMnode */
    n = private_rmNodeGetNumChildren(toModify);
    for (i = 0; i < n; i++)
    {
	RMnode *t;
	t = private_rmNodeGetIthChild(toModify, i);
	private_rmNodeDecrementRefcount(t);
	private_rmNodeGetIthChild(toModify, i) = NULL;
    }
    private_rmNodeGetNumChildren(toModify) = 0;
#endif

    return(RM_CHILL);
}


/*
 * ----------------------------------------------------
 * @Name rmNodeGetNumChildren
 @pstart
 int rmNodeGetNumChildren (const RMnode *toQuery)
 @pend

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

 @dstart

 Returns to the caller the number of child nodes registered with the
 input node. Upon failure, a value of -1 is returned.

 @dend
 * ----------------------------------------------------
 */
int
rmNodeGetNumChildren (const RMnode *to_query)
{
    if (RM_ASSERT(to_query, "rmNodeGetNumChildren() error: input RMnode pointer is NULL.") == RM_WHACKED)
	return(-1);
    else
	return(private_rmNodeGetNumChildren(to_query));
}


/*
 * ----------------------------------------------------
 * @Name rmNodeGetNumPrims
 @pstart
 int rmNodeGetNumPrims (const RMnode *toQuery)
 @pend

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

 @dstart

 Returns to the caller the number of RMprimitives owned by an RMnode.
 If the input RMnode is NULL, a value of -1 is returned.

 Related routines: rmNodeGetPrimitive() retrieves the i'th primitive
 from an RMnode; rmNodeAddPrimitive() adds a new RMprimitive to an
 RMnode.

 @dend
 * ----------------------------------------------------
 */
int
rmNodeGetNumPrims (const RMnode *n)
{
    if (RM_ASSERT(n, "rmNodeGetNumPrimitives() error: input node is NULL.\n") == RM_WHACKED)
	return(-1);
    
    return(n->nprims);
}

/*
 * ----------------------------------------------------
 * @Name rmNodeRemoveAllPrims
 @pstart
 RMenum rmNodeRemoveAllPrims (RMnode *toModify)
 @pend

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

 @dstart
 
 Use this routine to remove all the RMprimitives associated with an
 RMnode object. All the RMprimitives owned by the node toModify will
 be deleted when you call this routine.

 Upon successful completion, RM_CHILL will be returned,
 and the RMnode will contain no primitives. Failure will occur, and
 RM_WHACKED will be return, if the input RMnode is NULL.

 If you pass in an RMnode with no RMprimitives, nothing happens to the
 RMnode and RM_CHILL will be returned.
 
 @dend
 * ----------------------------------------------------
 */
RMenum
rmNodeRemoveAllPrims (RMnode *toModify)
{
    int i, n;

    if (RM_ASSERT(toModify, "rmNodeRemoveAllPrimitives() error: the input RMnode is NULL. ") == RM_WHACKED)
	return RM_WHACKED;

    /* quickly, remove all the children of an RMnode */
    n = rmNodeGetNumPrims(toModify);
    for (i = 0; i < n; i++)
    {
	rmPrimitiveDelete(toModify->prims[i]);
	toModify->prims[i] = NULL;
    }

    toModify->nprims = 0;

    return RM_CHILL;
}

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

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

 int indx - an integer index value.
 @aend

 @dstart

 Use this routine to obtain the RMnode handle of a child node. Since
 the "indx" parameter is a C-style index, an input value of zero
 refers to the first child, a value of one refers to the second child,
 an so forth.

 Use rmNodeGetNumChildren to determine how many children are
 registered at a given RMnode.

 Upon failure, NULL is returned.

 @dend
 * ----------------------------------------------------
 */
RMnode  *
rmNodeGetIthChild (const RMnode *to_query,
		   int indx)
{
    if (RM_ASSERT(to_query, "rmNodeGetIthChild() error: input RMnode pointer is NULL.") == RM_WHACKED)
	return(NULL);

    if ((indx < 0) || (indx >= private_rmNodeGetNumChildren(to_query)))
    {
	rmError("rmNodeGetIthChild() error: the input indx is greater than or equal to the number of children owned by the RMnode 'to_query'.");
	return(NULL);
    }
    else
    {
	RMnode **t = rmArrayGet(to_query->childrenArray, indx);
	return *t;
/* 	return(private_rmNodeGetIthChild(to_query, indx));*/
    }
}


/*
 * ----------------------------------------------------
 * @Name rmFindNamedNode
 @pstart
 RMnode * rmFindNamedNode (const RMnode *start,
		           const char *name)
 @pend

 @astart
 const RMnode *start - an RMnode handle (input).

 const char *name - a search string (input).
 @aend

 @dstart

 This routine searches for an RMnode in a scene graph rooted at
 "start" that contains the name attribute that matches the string in
 the input parameter "name". Upon success, a handle to matching RMnode
 is returned to the caller, otherwise, NULL is returned.

 Aug 2000 - eventually, this routine will be replaced by using a
 customizable traverser.

 @dend
 * ----------------------------------------------------
 */
RMnode *
rmFindNamedNode (const RMnode *start,
		 const char *name)
{
   /*
    * look for a node named "name" starting at the location "start".
    * return it's handle if we find it, NULL otherwise.
    */
    char   *c;
    int     i;
    RMnode *t = NULL;

    if ((RM_ASSERT(start, "rmFindNamedNode() error: the input start RMnode handle is NULL.") == RM_WHACKED) ||
	(RM_ASSERT(name, "rmFindNamedNode() error: the input search string is NULL") == RM_WHACKED))
	return(NULL);

    c = private_rmNodeGetName((RMnode *)start);
    
    if (strcmp(c, name) == 0)
        return((RMnode *)start);
    else
    {
        for (i = 0; i < rmNodeGetNumChildren(start); i++)
	{
	    if ((t = rmFindNamedNode(rmNodeGetIthChild(start, i), name)) != NULL)
	        break;
	}
    }
    return(t);
}

/*
 * Aug 2000: this routine is intentionally undocumented - it is
 * not yet finished.
 */
void
rmSceneGraphWalk (const RMpipe *p,
		  RMnode *r,
		  void (*userfunc)(RMnode *node,
				  const RMstate *state,
				  void *clientData),
		  void *clientData)
{
    /*
     * walk the scene graph.
     * at each node, create the render state and pass that, along
     * with the node to the user function. the user function
     * may provide a pointer to something, and it is dragged along
     * as well.
     */
    RMstate *s;
    int pushedAttribsReturn;

    if (
/*	(RM_ASSERT(p, "rmSceneGraphWalk() error: the input RMpipe is NULL.") == RM_WHACKED) || */
	(RM_ASSERT(r, "rmSceneGraphWalk() error: the input RMnode is NULL") == RM_WHACKED) ||
	(RM_ASSERT((void *)userfunc, "rmSceneGraphWalk() error: the input userfunc is NULL. ") == RM_WHACKED))
	return;
    
    s = rmStateNew();
    private_rmStateInit(p, s, (RMenum)GL_RENDER, NULL, NULL, NULL, NULL);
    private_collectAndApplyMatrices (s, r, NULL, GL_RENDER,
				     &pushedAttribsReturn, RM_FALSE);
    private_updateSceneParms(r, s, RM_FALSE, 0, NULL, NULL);
    private_rmSceneGraphWalk(r, s, userfunc, clientData);

    rmStateDelete(s);
}


/*
 * ----------------------------------------------------
 * @Name rmPrintSceneGraph
 @pstart
 void rmPrintSceneGraph (const RMnode *root,
		         RMenum printMode,
		         const char *fileName)
 @pend

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

 RMenum printMode - an RMenum value specifying a level of
   verbosity. At this time (Feb 2000), this parameter is effectively
   ignore. Use RM_CHILL for the time being.

 const char *fileName - a character string, or NULL (input).
 @aend

 @dstart

 This routine will print the contents of a scene graph rooted at
 "root", performing a depth first, left-to-right traversal.

 If the fileName parameter is NULL, print occurs to stderr. Otherwise,
 printing will be directed to the named file.

 Feb 2000- this routine is not fully implemented, nor fully
 functional. It provides only rudimentary information.

 @dend
 * ----------------------------------------------------
 */
void
rmPrintSceneGraph (const RMnode *r,
		   RMenum printmode,
		   const char *fname)
{
    FILE *f;

    /* prints to stderr the contents of the scene graph starting at the node "r" */
    if (fname == NULL)
	f = stderr;
    else
    {
        f = fopen(fname, "w");
	if (f == NULL)
	{
	    char buf[1024];

	    sprintf(buf, "rmPrintSceneGraph() error: unable to open the file named <%s>. \n", fname);
	    rmError(buf);
	    return;
	}
    }

    private_rmPrintSceneGraph(r,0,printmode,f);

    if (f != stderr)
	fclose(f);
}


/*
 * ----------------------------------------------------
 * @Name rmNodeSetBoundingBox
 @pstart
 RMenum rmNodeSetBoundingBox (RMnode *toModify,
		              const RMvertex3D *vMin,
			      const RMvertex3D *vMax)
 @pend

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

 const RMvertex3D *vMin, *vMax - handles to RMvertex3D objects
   (input). 
 @aend

 @dstart

 Use this routine to update the bounding box attribute of an RMnode.
 The bounding box vertices are specified in world coordinates. The
 bounding box is specified with a minimum and maximum vertex, implying
 that the bounding box is aligned with the axes of the world
 coordinate system.
 
 Each of "vMin" and "vMax" are optional - if you do not wish to update
 either the min or max bounding box coordinate, use a value of NULL.

 Returns RM_CHILL upon success, or RM_WHACKED upon failure.

 @dend
 * ----------------------------------------------------
 */
RMenum
rmNodeSetBoundingBox (RMnode *n,
		      const RMvertex3D *vmin,
		      const RMvertex3D *vmax)
{
    if (RM_ASSERT(n, "rmNodeSetBoundingBox() error: the input RMnode is NULL.") == RM_WHACKED)
	return(RM_WHACKED);

    if (vmin != NULL)
	VCOPY(vmin, &(n->bbox.min));

    if (vmax != NULL)
	VCOPY(vmax, &(n->bbox.max));

    return(RM_CHILL);
}


/*
 * ----------------------------------------------------
 * @Name rmNodeGetBoundingBox
 @pstart
 RMenum rmNodeGetBoundingBox (const RMnode *toQuery,
		              RMvertex3D *vMinReturn,
			      RMvertex3D *vMaxReturn)
 @pend

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

 RMvertex3D *vMinReturn, *vMaxReturn - handles to RMvertex objects
    (modified). 
 @aend

 @dstart

 Use this routine to obtain the bounding box minimum and/or minimum
 coordinate(s) from an RMnode. Upon success, RM_CHILL is returned, and
 the RMnode's bounding box coordinates are copied into caller-supplied
 memory (if non-NULL). Otherwise, RM_WHACKED is returned.

 @dend
 * ----------------------------------------------------
 */
RMenum
rmNodeGetBoundingBox (const RMnode *n,
		      RMvertex3D *vMinReturn,
		      RMvertex3D *vMaxReturn)
{
    if (RM_ASSERT(n, "rmNodeGetBoundingBox() error: the input RMnode is NULL.") == RM_WHACKED)
	return(RM_WHACKED);

    if (vMinReturn != NULL)
	VCOPY(&(n->bbox.min), vMinReturn);

    if (vMaxReturn != NULL)
	VCOPY(&(n->bbox.max),vMaxReturn);

    return(RM_CHILL);
}


/*
 * ----------------------------------------------------
 * @Name rmNodeComputeCenterFromBoundingBox
 @pstart
 RMenum rmNodeComputeCenterFromBoundingBox (RMnode *toModify)
 @pend

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

 @dstart

 This convenience routine will set the RMnode's "center point"
 attribute (rmNodeSetCenter()) with the geometric average of the
 RMnode's bounding box minimum and maximum coordinate.

 Returns RM_CHILL upon success, or RM_WHACKED upon failure.

 @dend
 * ----------------------------------------------------
 */
RMenum
rmNodeComputeCenterFromBoundingBox (RMnode *node)
{
    RMvertex3D c,bmin,bmax;

    if (RM_ASSERT(node, "rmNodeComputeCenterFromBoundingBox() error: the input RMnode is NULL") == RM_WHACKED)
	return(RM_WHACKED);

    rmNodeGetBoundingBox(node,&bmin,&bmax);
    c.x = 0.5 * (bmax.x - bmin.x) + bmin.x;
    c.y = 0.5 * (bmax.y - bmin.y) + bmin.y;
    c.z = 0.5 * (bmax.z - bmin.z) + bmin.z;

    rmNodeSetCenter(node,&c);

    return(RM_CHILL);
}


/*
 * ----------------------------------------------------
 * @Name rmNodeComputeBoundingBox
 @pstart
 RMenum rmNodeComputeBoundingBox (RMnode *toModify)
 @pend

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

 @dstart

 This convenience routine will determine the minimum and maximum
 extents of all geometric primitives owned by an RMnode by performing
 a "union" of all RMprimitive's bounding boxes. Then, this routine
 will also set the RMnode's bounding box minimum and maximum coordinate
 to those minimum and maximum extents.

 In addition, this routine will set the RMnode's center point to be the
 middle of the computed bounding box. Note that setting a center point will
 have an impact on geometric transformations, such as a rotation, that
 might be set later at the RMnode toModify.

 Returns RM_CHILL upon success, or RM_WHACKED upon failure.

 @dend
 * ----------------------------------------------------
 */
RMenum
rmNodeComputeBoundingBox (RMnode *node)
{
    int          i, nprims;
    RMvertex3D   accumMin, accumMax, tmin, tmax, center;
    RMprimitive *p;
    RMenum haveValidBoxData = RM_FALSE;

    if (RM_ASSERT(node, "rmNodeComputeBoundingBox() error: the input RMnode is NULL") == RM_WHACKED)
	return(RM_WHACKED);

    nprims = rmNodeGetNumPrims(node);
    accumMin.x = accumMin.y = accumMin.z = RM_MAXFLOAT;
    accumMax.x = accumMax.y = accumMax.z = RM_MINFLOAT;

    for (i=0; i<nprims; i++)
    {
	p = rmNodeGetPrimitive(node, i);

	/* does the primitive have a bounding box? if not, compute one */
	if (rmPrimitiveGetBoundingBox(p, &tmin, &tmax) == RM_WHACKED)
	{
	    /* if not, try to compute it. */
	    if (rmPrimitiveComputeBoundingBox(p) == RM_WHACKED)
	    {
		char buf[256];
		sprintf(buf," rmNodeComputeBoundingBox warning - unable to obtain or compute the bounding box for the %d'th RMprimitive. The node's bounding box may be inaccurate. ", i);
		rmWarning(buf);

		/* bad news - skip this RMprimitive */
		continue;
	    }
	    else /* grab the newly computed bbox */
		rmPrimitiveGetBoundingBox(p, &tmin, &tmax);

	    haveValidBoxData = RM_TRUE;
	}
	else
	    haveValidBoxData = RM_TRUE;

	/* perform box union of new and old boxes */
	rmUnionBoundingBoxes(&accumMin, &accumMax, &tmin, &tmax, &accumMin, &accumMax);
    }

    if (haveValidBoxData)
    {
	rmNodeSetBoundingBox(node, &accumMin, &accumMax);

	center.x = (accumMax.x - accumMin.x) * 0.5 + accumMin.x;
	center.y = (accumMax.y - accumMin.y) * 0.5 + accumMin.y;
	center.z = (accumMax.z - accumMin.z) * 0.5 + accumMin.z;
	rmNodeSetCenter(node, &center);

	return RM_CHILL;
    }
    else
	return RM_WHACKED;

#if 0
    /* compute for all prims at node */
    for (i = 0; i < nprims; i++)
    {

	p = rmNodeGetPrimitive(node, i);
	
	if (p == NULL)
	    continue;

	Need to add code that replaces this junk use of existing
	    RMprimitive bboxes.

	switch(rmPrimitiveGetType(p))
	{
	case RM_SPHERES:
	    private_computeSpheresBoundingBox(p, &tmin, &tmax);
	    break;		/* end sphere-specific code */

	case RM_CYLINDERS:
	    private_computeCylindersBoundingBox(p, &tmin, &tmax);
	    break;

	default:		/* all other prim types */
	    if (private_computeGenericBoundingBox(p, &tmin, &tmax) == RM_WHACKED)
		return(RM_WHACKED);
	    
	    break;
	} /* end switch over prim types */

	if (i == 0)
	{
	    accumMin = tmin;
	    accumMax = tmax;
	}
	else
	    rmUnionBoundingBoxes(&accumMin, &accumMax, &tmin, &tmax, &accumMin, &accumMax);
	    
    } /* end loop over prims */
    rmNodeSetBoundingBox(node, &accumMin, &accumMax);

    center.x = (accumMax.x - accumMin.x) * 0.5 + accumMin.x;
    center.y = (accumMax.y - accumMin.y) * 0.5 + accumMin.y;
    center.z = (accumMax.z - accumMin.z) * 0.5 + accumMin.z;
    rmNodeSetCenter(node, &center);

    
    return(RM_CHILL);
#endif
}


/*
 * ----------------------------------------------------
 * @Name rmNodeUnionAllBoxes
 @pstart
 RMenum rmNodeUnionAllBoxes (RMnode *toModify)
 @pend

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

 @dstart This routine will perform a traversal of the scene graph
 rooted at the node toModify, and will perform a "bounding box
 union". The union of two bounding boxes can be thought of as the
 smallest 3D box that encloses two sub-boxes.

 Returns RM_CHILL upon success, or RM_WHACKED upon failure.

 @dend
 * ----------------------------------------------------
 */
RMenum
rmNodeUnionAllBoxes (RMnode *n)
{
    int    i;
    RMenum rstat = RM_CHILL;

    if (RM_ASSERT(n, "rmNodeUnionAllBoxes() error: the input RMnode is NULL.") == RM_WHACKED)
	return(RM_WHACKED);

/*    for (i = 0; i < n->nchildren; i++) 1.6.2 */
    for (i = 0; i < private_rmNodeGetNumChildren(n); i++)
    {
	RMnode *kid;
	kid = rmNodeGetIthChild(n, i);
/*	if (n->children[i]->nchildren > 0) 1.6.2 */
	if (private_rmNodeGetNumChildren(kid) > 0)
	    rstat = rmNodeUnionAllBoxes(kid);

	if (rstat == RM_WHACKED)
	    return(rstat);
	
/*	rstat = rmUnionBoundingBoxes(&(n->bbox.min), &(n->bbox.max), &(n->children[i]->bbox.min), &(n->children[i]->bbox.max), &(n->bbox.min), &(n->bbox.max)); 1.6.2 */
	rstat = rmUnionBoundingBoxes(&(n->bbox.min), &(n->bbox.max), &(kid->bbox.min), &(kid->bbox.max), &(n->bbox.min), &(n->bbox.max));
	if (rstat == RM_WHACKED)
	    return(rstat);
    }
    return(rstat);
}


/*
 * ----------------------------------------------------
 * @Name rmNodeSetAmbientColor
 @pstart
 RMenum rmNodeSetAmbientColor (RMnode *toModify,
		               const RMcolor4D *newColor)
 @pend

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

 const RMcolor4D *newColor - a handle to an RMcolor4D object (input).
 @aend

 @dstart

 Use this routine to set the color of the ambient material reflectence
 property at an RMnode. RM_CHILL is returned upon success, or
 RM_WHACKED upon failure.
 
 The shade of each vertex (OpenGL supports only vertex shading) is a
 function of the Lambertian diffuse shading model, combined with Phong
 specular reflection. The weights, or coefficients, applied to each of
 the ambient, diffuse and specular components of the shading equation
 are the linear combination of ambient, diffuse and specular
 coefficients of the surface material properties with the ambient,
 diffuse and specular color components of the light sources and light
 models.

 For lighting/shading to be active, the following three elements must
 be present: light sources, light models and surface normals. Light
 models and light sources are RMnode-level scene parameters (see
 rmNodeSetSceneLight() and rmNodeSetSceneLightModel()). Surface
 normals are RMprimitive attributes. Thus, primitives in 3D are not
 considered to be lit (ie, vertex shade computed using light sources
 and surface reflectance properties) if normals are not present.  When
 lighting is not active, the RMnode's "unlit color" attribute is used
 for the vertex color.
 
 By default, when an RMnode is created with rmNodeNew(), the new node
 contains no surface properties. Surface properties are inherited
 along depth in the scene graph. The RM root node has a set of default
 material reflectance properties, so application nodes that are
 inserted as children of rmRootNode() will inherit those default
 material properties.

 @dend
 * ----------------------------------------------------
 */
RMenum
rmNodeSetAmbientColor (RMnode *n,
		       const RMcolor4D *newColor)
{
    if (RM_ASSERT(n, "rmNodeSetAmbientColor() error: the input RMnode pointer is NULL. ") == RM_WHACKED)
	return(RM_WHACKED);

    /* if no surface properties are defined, create them */
    if (n->sprops == NULL)
	n->sprops = private_rmSurfacePropsNew();

    /* if no ambient color attribute is present, and the newColor
     * isn't NULL, create space for a new 4-tuple color
     */
    if ((n->sprops->ambient_color == NULL) && (newColor != NULL))
	n->sprops->ambient_color = rmColor4DNew(1);

    /* if the newColor isn't NULL, copy data into the node */
    if (newColor != NULL)
    {
	*(n->sprops->ambient_color) = *newColor;
    }
    else
    {
	/* otherwise, newColor == NULL, and we want to "turn off"
	 * the ambient color parm at this node. first, we need to
	 * free up any existing memory
	 */
	if (n->sprops->ambient_color != NULL)
	    rmColor4DDelete(n->sprops->ambient_color);
	n->sprops->ambient_color = NULL;
    }
    private_rmNodeAttribMask(n,private_rmNodeComputeAttribMask(n),RM_SET);
    return(RM_CHILL);
}


/*
 * ----------------------------------------------------
 * @Name rmNodeGetAmbientColor
 @pstart
 RMenum rmNodeGetAmbientColor (const RMnode *toQuery,
		               RMcolor4D *ambientReturn)
 @pend

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

 RMcolor4D *ambientReturn - a handle to a caller-supplied RMcolor4D
    object (modified). 
 @aend

 @dstart

 Use this routine to obtain the 4-component ambient reflectance color
 attribute of an RMnode. Upon success, the ambient reflectance color
 attribute of an RMnode will be copied into the caller-supplied
 memory, and RM_CHILL will be returned.

 If the input RMnode is NULL, or if the ambient reflectance color is
 not defined at an RMnode, RM_WHACKED is returned to the caller.

 The shade of each vertex (OpenGL supports only vertex shading) is a
 function of the Lambertian diffuse shading model, combined with Phong
 specular reflection. The weights, or coefficients, applied to each of
 the ambient, diffuse and specular components of the shading equation
 are the linear combination of ambient, diffuse and specular
 coefficients of the surface material properties with the ambient,
 diffuse and specular color components of the light sources and light
 models.
 
 For lighting/shading to be active, the following three elements must
 be present: light sources, light models and surface normals. Light
 models and light sources are RMnode-level scene parameters (see
 rmNodeSetSceneLight() and rmNodeSetSceneLightModel()). Surface
 normals are RMprimitive attributes. Thus, primitives in 3D are not
 considered to be lit (ie, vertex shade computed using light sources
 and surface reflectance properties) if no normals are present.  When
 lighting is not active, the RMnode's "unlit color" attribute is used
 for the vertex color.
 
 By default, when an RMnode is created with rmNodeNew(), the new node
 contains no surface properties. Surface properties are inherited
 along depth in the scene graph. The RM root node has a set of default
 material reflectance properties, so application nodes that are
 inserted as children of rmRootNode() will inherit those default
 material properties.

 @dend
 * ----------------------------------------------------
 */
RMenum
rmNodeGetAmbientColor (const RMnode *n,
		       RMcolor4D *ar)
{
    if ((RM_ASSERT(n, "rmNodeGetAmbientColor() error: the input RMnode pointer is NULL") == RM_WHACKED) ||
	(RM_ASSERT(ar, "rmNodeGetAmbientColor() error: the RMcolor4D pointer is NULL")) == RM_WHACKED)
	return(RM_WHACKED);
    
    if ((n->sprops == NULL) || (n->sprops->ambient_color == NULL))
	return(RM_WHACKED);

    *ar = *(n->sprops->ambient_color);

    return(RM_CHILL);
}


/*
 * ----------------------------------------------------
 * @Name rmNodeSetDiffuseColor
 @pstart
 RMenum rmNodeSetDiffuseColor (RMnode *toModify,
		               const RMcolor4D *newColor)
 @pend

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

 const RMcolor4D *newColor - a handle to an RMcolor4D object (input).
 @aend

 @dstart

 Use this routine to set the color of the diffuse material reflectence
 property at an RMnode. RM_CHILL is returned upon success, or
 RM_WHACKED upon failure.
 
 The shade of each vertex (OpenGL supports only vertex shading) is a
 function of the Lambertian diffuse shading model, combined with Phong
 specular reflection. The weights, or coefficients, applied to each of
 the ambient, diffuse and specular components of the shading equation
 are the linear combination of ambient, diffuse and specular
 coefficients of the surface material properties with the ambient,
 diffuse and specular color components of the light sources and light
 models.
 
 For lighting/shading to be active, the following three elements must
 be present: light sources, light models and surface normals. Light
 models and light sources are RMnode-level scene parameters (see
 rmNodeSetSceneLight() and rmNodeSetSceneLightModel()). Surface
 normals are RMprimitive attributes. Thus, primitives in 3D are not
 considered to be lit (ie, vertex shade computed using light sources
 and surface reflectance properties) if no normals are present.  When
 lighting is not active, the RMnode's "unlit color" attribute is used
 for the vertex color.
 
 By default, when an RMnode is created with rmNodeNew(), the new node
 contains no surface properties. Surface properties are inherited
 along depth in the scene graph. The RM root node has a set of default
 material reflectance properties, so application nodes that are
 inserted as children of rmRootNode() will inherit those default
 material properties.

 @dend
 * ----------------------------------------------------
 */
RMenum
rmNodeSetDiffuseColor (RMnode *n,
		       const RMcolor4D *newColor)
{
    if (RM_ASSERT(n, "rmNodeSetDiffuseColor() error: the input RMnode pointer is NULL. ") == RM_WHACKED)
	return(RM_WHACKED);

    /* if no surface properties are defined, create them */
    if (n->sprops == NULL)
	n->sprops = private_rmSurfacePropsNew();

    /* if no diffuse color attribute is present, and the newColor
     * isn't NULL, create space for a new 4-tuple color
     */
    if ((n->sprops->diffuse_color == NULL) && (newColor != NULL))
	n->sprops->diffuse_color = rmColor4DNew(1);

    /* if the newColor isn't NULL, copy data into the node */
    if (newColor != NULL)
    {
	*(n->sprops->diffuse_color) = *newColor;
    }
    else
    {
	/* otherwise, newColor == NULL, and we want to "turn off"
	 * the diffuse color parm at this node. first, we need to
	 * free up any existing memory
	 */
	if (n->sprops->diffuse_color != NULL)
	    rmColor4DDelete(n->sprops->diffuse_color);
	n->sprops->diffuse_color = NULL;
    }
    private_rmNodeAttribMask(n,private_rmNodeComputeAttribMask(n),RM_SET);
    return(RM_CHILL);
}


/*
 * ----------------------------------------------------
 * @Name rmNodeGetDiffuseColor
 @pstart
 RMenum rmNodeGetDiffuseColor (const RMnode *toQuery,
		               RMcolor4D *diffuseReturn)
 @pend

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

 RMcolor4D *diffuseReturn - a handle to a caller-supplied RMcolor4D
    object (modified). 
 @aend

 @dstart

 Use this routine to obtain the 4-component diffuse reflectance color
 attribute of an RMnode. Upon success, the diffuse reflectance color
 attribute of an RMnode will be copied into the caller-supplied
 memory, and RM_CHILL will be returned.

 If the input RMnode is NULL, or if the diffuse reflectance color is
 not defined at an RMnode, RM_WHACKED is returned to the caller.

 The shade of each vertex (OpenGL supports only vertex shading) is a
 function of the Lambertian diffuse shading model, combined with Phong
 specular reflection. The weights, or coefficients, applied to each of
 the ambient, diffuse and specular components of the shading equation
 are the linear combination of ambient, diffuse and specular
 coefficients of the surface material properties with the ambient,
 diffuse and specular color components of the light sources and light
 models.
 
 For lighting/shading to be active, the following three elements must
 be present: light sources, light models and surface normals. Light
 models and light sources are RMnode-level scene parameters (see
 rmNodeSetSceneLight() and rmNodeSetSceneLightModel()). Surface
 normals are RMprimitive attributes. Thus, primitives in 3D are not
 considered to be lit (ie, vertex shade computed using light sources
 and surface reflectance properties) if no normals are present.  When
 lighting is not active, the RMnode's "unlit color" attribute is used
 for the vertex color.
 
 By default, when an RMnode is created with rmNodeNew(), the new node
 contains no surface properties. Surface properties are inherited
 along depth in the scene graph. The RM root node has a set of default
 material reflectance properties, so application nodes that are
 inserted as children of rmRootNode() will inherit those default
 material properties.

 @dend
 * ----------------------------------------------------
 */
RMenum
rmNodeGetDiffuseColor (const RMnode *n,
		       RMcolor4D *dr)
{
    if ((RM_ASSERT(n, "rmNodeGetDiffuseColor() error: the input RMnode pointer is NULL") == RM_WHACKED) ||
	(RM_ASSERT(dr, "rmNodeGetDiffuseColor() error: the RMcolor4D pointer is NULL")) == RM_WHACKED)
	return(RM_WHACKED);
    
    if ((n->sprops == NULL) || (n->sprops->diffuse_color == NULL))
	return(RM_WHACKED);

    *dr = *(n->sprops->diffuse_color);

    return(RM_CHILL);
}


/*
 * ----------------------------------------------------
 * @Name rmNodeSetSpecularColor
 @pstart
 RMenum rmNodeSetSpecularColor (RMnode *toModify,
		                const RMcolor4D *newColor)
 @pend

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

 const RMcolor4D *newColor - a handle to an RMcolor4D object (input).
 @aend

 @dstart

 Use this routine to set the color of the specular material
 reflectence property at an RMnode. RM_CHILL is returned upon success,
 or RM_WHACKED upon failure.
 
 The shade of each vertex (OpenGL supports only vertex shading) is a
 function of the Lambertian diffuse shading model, combined with Phong
 specular reflection. The weights, or coefficients, applied to each of
 the ambient, diffuse and specular components of the shading equation
 are the linear combination of ambient, diffuse and specular
 coefficients of the surface material properties with the ambient,
 diffuse and specular color components of the light sources and light
 models.
 
 For lighting/shading to be active, the following three elements must
 be present: light sources, light models and surface normals. Light
 models and light sources are RMnode-level scene parameters (see
 rmNodeSetSceneLight() and rmNodeSetSceneLightModel()). Surface
 normals are RMprimitive attributes. Thus, primitives in 3D are not
 considered to be lit (ie, vertex shade computed using light sources
 and surface reflectance properties) if no normals are present.  When
 lighting is not active, the RMnode's "unlit color" attribute is used
 for the vertex color.
 
 By default, when an RMnode is created with rmNodeNew(), the new node
 contains no surface properties. Surface properties are inherited
 along depth in the scene graph. The RM root node has a set of default
 material reflectance properties, so application nodes that are
 inserted as children of rmRootNode() will inherit those default
 material properties.

 @dend
 * ----------------------------------------------------
 */
RMenum
rmNodeSetSpecularColor (RMnode *n,
		        const RMcolor4D *newColor)
{
    if (RM_ASSERT(n, "rmNodeSetSpecularColor() error: the input RMnode pointer is NULL. ") == RM_WHACKED)
	return(RM_WHACKED);

    /* if no surface properties are defined, create them */
    if (n->sprops == NULL)
	n->sprops = private_rmSurfacePropsNew();

    /* if no specular color attribute is present, and the newColor
     * isn't NULL, create space for a new 4-tuple color
     */
    if ((n->sprops->specular_color == NULL) && (newColor != NULL))
	n->sprops->specular_color = rmColor4DNew(1);

    /* if the newColor isn't NULL, copy data into the node */
    if (newColor != NULL)
    {
	*(n->sprops->specular_color) = *newColor;
    }
    else
    {
	/* otherwise, newColor == NULL, and we want to "turn off"
	 * the specular color parm at this node. first, we need to
	 * free up any existing memory
	 */
	if (n->sprops->specular_color != NULL)
	    rmColor4DDelete(n->sprops->specular_color);

	n->sprops->specular_color = NULL;
    }
    private_rmNodeAttribMask(n,private_rmNodeComputeAttribMask(n),RM_SET);
    return(RM_CHILL);
}


/*
 * ----------------------------------------------------
 * @Name rmNodeGetSpecularColor
 @pstart
 RMenum rmNodeGetSpecularColor (const RMnode *toQuery,
		                RMcolor4D *diffuseReturn)
 @pend

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

 RMcolor4D *specularReturn - a handle to a caller-supplied RMcolor4D
    object (modified). 
 @aend

 @dstart

 Use this routine to obtain the 4-component specular reflectance color
 attribute of an RMnode. Upon success, the specular reflectance color
 attribute of an RMnode will be copied into the caller-supplied
 memory, and RM_CHILL will be returned.

 If the input RMnode is NULL, or if the specular reflectance color is
 not defined at an RMnode, RM_WHACKED is returned to the caller.

 The shade of each vertex (OpenGL supports only vertex shading) is a
 function of the Lambertian diffuse shading model, combined with Phong
 specular reflection. The weights, or coefficients, applied to each of
 the ambient, diffuse and specular components of the shading equation
 are the linear combination of ambient, diffuse and specular
 coefficients of the surface material properties with the ambient,
 diffuse and specular color components of the light sources and light
 models.
 
 For lighting/shading to be active, the following three elements must
 be present: light sources, light models and surface normals. Light
 models and light sources are RMnode-level scene parameters (see
 rmNodeSetSceneLight() and rmNodeSetSceneLightModel()). Surface
 normals are RMprimitive attributes. Thus, primitives in 3D are not
 considered to be lit (ie, vertex shade computed using light sources
 and surface reflectance properties) if no normals are present.  When
 lighting is not active, the RMnode's "unlit color" attribute is used
 for the vertex color.
 
 By default, when an RMnode is created with rmNodeNew(), the new node
 contains no surface properties. Surface properties are inherited
 along depth in the scene graph. The RM root node has a set of default
 material reflectance properties, so application nodes that are
 inserted as children of rmRootNode() will inherit those default
 material properties.

 @dend
 * ----------------------------------------------------
 */
RMenum
rmNodeGetSpecularColor (const RMnode *n,
		        RMcolor4D *sr)
{
    if ((RM_ASSERT(n, "rmNodeGetSpecularColor() error: the input RMnode pointer is NULL") == RM_WHACKED) ||
	(RM_ASSERT(sr, "rmNodeGetSpecularColor() error: the RMcolor4D pointer is NULL")) == RM_WHACKED)
	return(RM_WHACKED);
    
    if ((n->sprops == NULL) || (n->sprops->specular_color == NULL))
	return(RM_WHACKED);

    *sr = *(n->sprops->specular_color);

    return(RM_CHILL);
}


/*
 * ----------------------------------------------------
 * @Name rmNodeSetSpecularExponent
 @pstart
 RMenum rmNodeSetSpecularExponent (RMnode *toModify,
                                   float newValue)
 @pend

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

 float newValue - a floating point value, typically greater than 1.0
    (input). 
 @aend

 @dstart

 Use this routine to set the specular reflectence exponent surface
 reflectance material property. RM_CHILL is returned upon success, or
 RM_WHACKED upon failure.
 
 The shade of each vertex (OpenGL supports only vertex shading) is a
 function of the Lambertian diffuse shading model, combined with Phong
 specular reflection. The weights, or coefficients, applied to each of
 the ambient, diffuse and specular components of the shading equation
 are the linear combination of ambient, diffuse and specular
 coefficients of the surface material properties with the ambient,
 diffuse and specular color components of the light sources and light
 models.

 The specular exponent controls the appearance of specular highlights.
 A smaller exponent value produces a larger specular highlight, while
 a larger exponent value produces a smaller, more focused highlight.
 The default specular exponent in RM is 10.0.

  For lighting/shading to be active, the following three elements must
 be present: light sources, light models and surface normals. Light
 models and light sources are RMnode-level scene parameters (see
 rmNodeSetSceneLight() and rmNodeSetSceneLightModel()). Surface
 normals are RMprimitive attributes. Thus, primitives in 3D are not
 considered to be lit (ie, vertex shade computed using light sources
 and surface reflectance properties) if no normals are present.  When
 lighting is not active, the RMnode's "unlit color" attribute is used
 for the vertex color.
 
 By default, when an RMnode is created with rmNodeNew(), the new node
 contains no surface properties. Surface properties are inherited
 along depth in the scene graph. The RM root node has a set of default
 material reflectance properties, so application nodes that are
 inserted as children of rmRootNode() will inherit those default
 material properties.

 @dend
 * ----------------------------------------------------
 */
RMenum
rmNodeSetSpecularExponent (RMnode *n, float newValue)
{
    if (RM_ASSERT(n, "rmNodeSetSpecularExponent() error: the input RMnode is NULL") == RM_WHACKED)
	return(RM_WHACKED);
    
    /* if no surface properties are defined, create them */
    if (n->sprops == NULL)
	n->sprops = private_rmSurfacePropsNew();

    if (n->sprops->specular_exponent == NULL)
	n->sprops->specular_exponent = rmFloatNew(1);

    *(n->sprops->specular_exponent) = newValue;

    private_rmNodeAttribMask(n,private_rmNodeComputeAttribMask(n),RM_SET);
    return(RM_CHILL);
}


/*
 * ----------------------------------------------------
 * @Name rmNodeGetSpecularExponent
 @pstart
 RMenum rmNodeGetSpecularExponent (const RMnode *toQuery,
			           float *retValue)
 @pend

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

 float *retValue - a handle to a caller-supplied float (modified).
 @aend

 @dstart

 Use this routine to obtain the specular exponent material reflectance
 attribute of an RMnode. Upon success, RM_CHILL is returned, and the
 RMnode's specular exponent is copied into caller-supplied memory.

 If the input node is NULL, or if the input node has no surface
 reflectance properties, or if the specular exponent term has not been
 defined (through explicit assignment using
 rmNodeSetSpecularExponent()), RM_WHACKED is returned and caller
 memory remains undisturbed.

 @dend
 * ----------------------------------------------------
 */
RMenum
rmNodeGetSpecularExponent (const RMnode *n,
			   float *float_ret)
{
    if ((n->sprops == NULL) || (n->sprops->specular_exponent == NULL))
	return(RM_WHACKED);
    
    *float_ret = *(n->sprops->specular_exponent);

    return(RM_CHILL);
}


/*
 * ----------------------------------------------------
 * @Name rmNodeSetUnlitColor
 @pstart
 RMenum rmNodeSetUnlitColor (RMnode *toModify,
		             const RMcolor4D *newColor)
 @pend

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

 const RMcolor4D *newColor - a handle to an RMcolor4D object (input).
 @aend

 @dstart

 Use this routine to set the "unlit" material color of an RMnode.
 RM_CHILL is returned upon success, or RM_WHACKED upon failure.

 The term "unlit color" refers to the color used to draw primitives
 when lighting is not active. Rather than arbitrarily choose from
 among diffuse, ambient or specular material colors for use when
 rendering in the absence of lights, RM provides a node-level
 attribute for that purpose: unlit color. A newColor value of (0,0,0,1)
 specifies the color "black," while a newColor value of (1,1,1,1)
 specifies the color "white."

 For lighting/shading to be active, the following three elements must
 be present: light sources, light models and surface normals. Light
 models and light sources are RMnode-level scene parameters (see
 rmNodeSetSceneLight() and rmNodeSetSceneLightModel()). Surface
 normals are RMprimitive attributes. Thus, primitives in 3D are not
 considered to be lit (ie, vertex shade computed using light sources
 and surface reflectance properties) if no normals are present.  When
 lighting is not active, the RMnode's "unlit color" attribute is used
 for the vertex color.
 
 By default, when an RMnode is created with rmNodeNew(), the new node
 contains no surface properties. Surface properties are inherited
 along depth in the scene graph. The RM root node has a set of default
 material reflectance properties, so application nodes that are
 inserted as children of rmRootNode() will inherit those default
 material properties.
 
 @dend
 * ----------------------------------------------------
 */
RMenum
rmNodeSetUnlitColor (RMnode *n,
		     const RMcolor4D *newColor)
{
    if (RM_ASSERT(n, "rmNodeSetUnlitColor() error: the input RMnode pointer is NULL. ") == RM_WHACKED)
	return(RM_WHACKED);

    /* if no surface properties are defined, create them */
    if (n->sprops == NULL)
	n->sprops = private_rmSurfacePropsNew();

    /* if no unlit color attribute is present, and the newColor
     * isn't NULL, create space for a new 4-tuple color
     */
    if ((n->sprops->unlit_color == NULL) && (newColor != NULL))
	n->sprops->unlit_color = rmColor4DNew(1);

    /* if the newColor isn't NULL, copy data into the node */
    if (newColor != NULL)
    {
	*(n->sprops->unlit_color) = *newColor;
    }
    else
    {
	/* otherwise, newColor == NULL, and we want to "turn off"
	 * the unlit color parm at this node. first, we need to
	 * free up any existing memory
	 */
	if (n->sprops->unlit_color != NULL)
	    rmColor4DDelete(n->sprops->unlit_color);

	n->sprops->unlit_color = NULL;
    }
    private_rmNodeAttribMask(n,private_rmNodeComputeAttribMask(n),RM_SET);
    return(RM_CHILL);
}


/*
 * ----------------------------------------------------
 * @Name rmNodeGetUnlitColor
 @pstart
 RMenum rmNodeGetUnlitColor (const RMnode *toQuery,
		             RMcolor4D *retColor)
 @pend

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

 RMcolor4D *retColor - a handle to an RMcolor4D object (modified).
 @aend

 @dstart

 Use this routine to obtain the "unlit" material color of an RMnode.
 Upon success, RM_CHILL is returned and the RMnode's unlit color
 attribute is copied into caller-supplied memory. If, however, the
 RMnode has no surface reflectance properties defined, or if the unlit
 color attribute has not been explicitly set (with
 rmNodeSetUnlitColor), RM_WHACKED is returned, and caller memory
 remains unmodified.

 The term "unlit color" refers to the color used to draw primitives
 when lighting is not active. Rather than arbitrarily choose from
 among diffuse, ambient or specular material colors for use when
 rendering in the absence of lights, RM provides a node-level
 attribute for that purpose: unlit color.

 For lighting/shading to be active, the following three elements must
 be present: light sources, light models and surface normals. Light
 models and light sources are RMnode-level scene parameters (see
 rmNodeSetSceneLight() and rmNodeSetSceneLightModel()). Surface
 normals are RMprimitive attributes. Thus, primitives in 3D are not
 considered to be lit (ie, vertex shade computed using light sources
 and surface reflectance properties) if no normals are present.  When
 lighting is not active, the RMnode's "unlit color" attribute is used
 for the vertex color.
 
 By default, when an RMnode is created with rmNodeNew(), the new node
 contains no surface properties. Surface properties are inherited
 along depth in the scene graph. The RM root node has a set of default
 material reflectance properties, so application nodes that are
 inserted as children of rmRootNode() will inherit those default
 material properties.

 @dend
 * ----------------------------------------------------
 */
RMenum
rmNodeGetUnlitColor (const RMnode *n,
		     RMcolor4D *ur)
{
    if ((RM_ASSERT(n, "rmNodeGetUnlitColor() error: the input RMnode pointer is NULL") == RM_WHACKED) ||
	(RM_ASSERT(ur, "rmNodeGetUnlitColor() error: the RMcolor4D pointer is NULL")) == RM_WHACKED)
	return(RM_WHACKED);
    
    if ((n->sprops == NULL) || (n->sprops->unlit_color == NULL))
	return(RM_WHACKED);

    *ur = *(n->sprops->unlit_color);

    return(RM_CHILL);
}


/*
 * ----------------------------------------------------
 * @Name rmNodeSetOpacity
 @pstart
 RMenum rmNodeSetOpacity (RMnode *toModify,
                          float newValue)
 @pend

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

 float newValue - a floating point value between 0.0 and 1.0.  A value
    of 0.0 means the object is completely transparent, while a value
    of 1.0 means the object is completely opaque (input).
 @aend

 @dstart

 Use this routine to set the opacity level for all RMprimitives in an
 RMnode. RM_CHILL is returned upon success, or RM_WHACKED upon
 failure.

 In order for objects to be rendered as translucent, a value of
 opacity other than 1.0 must be specified, either at the RMnode level
 or at the RMprimitive level. In the former case, the opacity value
 applies to all RMprimitives in an RMnode (and all descendent
 primitives, unless subsequently overridden by the presence of the
 node-level opacity value or RMprimitive level opacities). In the
 latter case, opacity is specified on a per-vertex level. In addition,
 the RMnode must be scheduled for traversal during the transparent
 rendering pass.  See rmNodeNew() for more details.

 By default, when an RMnode is created with rmNodeNew(), the new node
 contains no surface properties. Surface properties are inherited
 along depth in the scene graph. The RM root node has a set of default
 material reflectance properties, so application nodes that are
 inserted as children of rmRootNode() will inherit those default
 material properties.

 @dend
 * ----------------------------------------------------
 */
RMenum
rmNodeSetOpacity (RMnode *n, float newValue)
{
    rmWarning(" rmNodeSetOpacity() is deprecated. Please set a node's opacity through manip of its unlit color, or one or more of its material properties. rmNodeSetOpacity() will be removed from the API in the 1.4.3 release. ");
    
    if (RM_ASSERT(n, "rmNodeSetOpacity() error: the input RMnode is NULL.") == RM_WHACKED)
	return(RM_WHACKED);
    
    if (n->sprops == NULL)
	n->sprops = private_rmSurfacePropsNew();

    if (n->sprops->opacity == NULL)
	n->sprops->opacity = rmFloatNew(1);

    *(n->sprops->opacity) = newValue;

    return(RM_CHILL);
}


/*
 * ----------------------------------------------------
 * @Name rmNodeGetOpacity
 @pstart
 RMenum rmNodeGetOpacity (const RMnode *toQuery,
                          float *retValue)
 @pend

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

 float *retValue - a handle to a caller-supplied float (modified).
 @aend

 @dstart

 Use this routine to obtain the opacity attribute of an RMnode. Upon
 success, RM_CHILL is returned, and the RMnode's opacity attribute is
 copied into caller-supplied memory. If the RMnode is NULL, or if the
 RMnode has no surface reflectance properties, or if the opacity
 attribute is not present (specified with rmNodeSetOpacity()),
 RM_WHACKED is returned and caller-supplied memory remains
 undisturbed.

 @dend
 * ----------------------------------------------------
 */
RMenum
rmNodeGetOpacity (const RMnode *n,
		  float *float_ret)
{
    rmWarning(" rmNodeGetOpacity() is deprecated, and will be removed from the API in the 1.4.3 release. ");
    
    if ((RM_ASSERT(n,"rmNodeGetOpacity() error: the input RMnode is NULL") == RM_WHACKED) ||
	(RM_ASSERT(float_ret,"rmNodeGetOpacity() error: the return floating point parameter is NULL") == RM_WHACKED))
	return(RM_WHACKED);
    
    if ((n->sprops == NULL) || (n->sprops->opacity==NULL))
	return(RM_WHACKED);
    
    float_ret = n->sprops->opacity;

    return(RM_CHILL);
}

/* drawing attributes */
/*
 * ----------------------------------------------------
 * @Name rmNodeSetNormalizeNormals
 @pstart
 RMenum rmNodeSetNormalizeNormals(RMnode *toModify,
			          RMenum newValue)
 @pend

 @astart
 RMnode *toModify - a handle to an RMnode (modified).
 RMenum newValue - an RMenum value, may be either RM_TRUE or RM_FALSE (input).
 @aend

 @dstart
 By default, surface normals are not "auto-normalized" during rendering.
 In some circumstances, it is desireable to ask OpenGL to normalize
 surface normals prior to rendering, most notably when the governing
 transformation matrix includes a scaling component. The effect of
 scaled normals will appear on-screen as objects becoming brigher or
 dimmer as they are scaled. Enabling auto-normalization will remedy
 this artifact, but at the expense of speed.

 This routine is used to enable or disable auto-normalization of surface
 normals during rendering. Setting this parameter at an RMnode will either
 enable or disable normalization of surface normals at render time for
 the entire scene graph rooted at the RMnode "toModify."

 Upon success, this routine will return RM_CHILL, and modify the RMnode's
 drawing attributes accordingly. Otherwise, RM_WHACKED is returned, and
 the RMnode's drawing attributes remain unmodified.

 Use rmNodeGetNormalizeNormals to query this parameter.

 Note that there is not means at this time (3/2000) to obtain from
 the RM rendering context (RMstate object) any indication of whether
 or not normals are normalized. This may be added in a future release.
 @dend
 * ----------------------------------------------------
 */
RMenum
rmNodeSetNormalizeNormals (RMnode *toModify,
			   RMenum newValue)
{
    RMnode *n;
    
    if (RM_ASSERT(toModify, "rmNodeSetNormalizeNormals() error: the input RMnode is NULL") == RM_WHACKED)
	return(RM_WHACKED);
    
    if ((newValue != RM_TRUE) && (newValue != RM_FALSE))
    {
	rmError("rmNodeSetNormalizeNormals() error: the input RMenum is neither RM_TRUE nor RM_FALSE");
	return(RM_WHACKED);
    }

    n = toModify;
    
    if (n->rprops == NULL)
	n->rprops = (_rendermode_properties *)private_rmRenderModePropsNew();

    if (n->rprops->normalizeNormals == NULL)
	n->rprops->normalizeNormals = private_rmEnumNew(1);

    (*n->rprops->normalizeNormals) = newValue;

    private_rmNodeAttribMask(n,private_rmNodeComputeAttribMask(n),RM_SET);
    return(RM_CHILL);
}

/*
 * ----------------------------------------------------
 * @Name rmNodeGetNormalizeNormals
 @pstart
 RMenum rmNodeGetNormalizeNormals(const RMnode *toQuery,
			          RMenum *valueReturn)
 @pend

 @astart
 const RMnode *toQuery -  a handle to an RMnode (input).
 RMenum *valueReturn - a handle to a caller-supplied RMenum (result).
 @aend

 @dstart
 Use this routine to query the "normals are normalized" drawing
 attribute of an RMnode. If the RMnode has this parameter defined,
 RM_CHILL is returned, and the parameter is copied into caller supplied
 memory (if the parameter is non-NULL). Otherwise, RM_WHACKED is returned
 and caller-supplied memory remains unmodified.

 See rmNodeSetNormalizeNormals() for more information.
 @dend
 * ----------------------------------------------------
 */
RMenum
rmNodeGetNormalizeNormals (const RMnode *n,
			   RMenum *rval)
{
    if (RM_ASSERT(n, "rmNodeGetNormalizeNormals() error: input RMnode is NULL. ") == RM_WHACKED)
	return(RM_WHACKED);
    
    if ((n->rprops == NULL) || (n->rprops->normalizeNormals == NULL))
	return(RM_WHACKED);
    
    *rval = *(n->rprops->normalizeNormals);

    return(RM_CHILL);
}


/*
 * ----------------------------------------------------
 * @Name rmNodeSetLineStyle
 @pstart
 RMenum rmNodeSetLineStyle(RMnode *toModify,
		           RMenum newLineStyle)
 @pend

 @astart
 RMnode *toModify - a handle to an RMnode (modified).
 RMenum newLineStyle - an RMenum value, may be one of RM_LINES_SOLID,
   RM_LINES_DASHED, RM_LINES_DOTTED, RM_LINES_DOT_DASH or
   RM_LINES_DASH_DASH_DOT (input).
 @aend

 @dstart
 Line styles in RM control the appearance of lines when rendered. Lines
 can be rendered as solid lines, or with using a pattern. This routine is
 used to manipulate the pattern used for line rendering using on of a
 predefined set of styles. Please refer to the program "lines2d" in the
 RM demonstration programs for a visual representation of available line
 styles and widths.

 Upon success, the line style attribute is set to the value specified by
 the "newLineStyle" parameter, and RM_CHILL is returned. Upon failure,
 RM_WHACKED is returned and the line style attribute of the RMnode
 "toModify" remains unmodified.

 Use rmNodeGetLineStyle() to obtain the line style attribute of an RMnode,
 or rmStateGetLineStyle() to obtain the current line style attribute from
 an RM rendering context during a render time traversal of the scene graph.

 All RMnode-level drawing properties and modes take effect immediately when
 the RMnode is is processed during a render time traversal of the scene graph.
 These settings remain in effect over all children of the RMnode, unless
 overridden by some child node deeper in the scene graph.
 @dend
 * ----------------------------------------------------
 */
RMenum
rmNodeSetLineStyle(RMnode *n,
		   RMenum s)
{
    if (RM_ASSERT(n,"rmNodeSetLineStyle() error: input RMnode is NULL.") == RM_WHACKED)
	return(RM_WHACKED);

    /* enum check on style enum? */
    
    if (n->rprops == NULL)
	n->rprops = private_rmRenderModePropsNew();

    if (n->rprops->linestyle == NULL)
	n->rprops->linestyle = private_rmEnumNew(1);

    *(n->rprops->linestyle) = s;

    private_rmNodeAttribMask(n,private_rmNodeComputeAttribMask(n),RM_SET);
    return(RM_CHILL);
}


/*
 * ----------------------------------------------------
 * @Name rmNodeGetLineStyle
 @pstart
 RMenum rmNodeGetLineStyle(const RMnode *toQuery,
		           RMenum *lineStyleReturn)
 @pend

 @astart
 const RMnode *toQuery - a handle to an RMnode (input).
 RMenum *lineStyleReturn - a handle to a caller supplied RMenum (result).
 @aend

 @dstart
 Use this routine to query the line style attribute of an RMnode. If one is
 defined, RM_CHILL is returned on the stack, and the RMnode's line style
 attribute will be copied into caller-supplied memory. Otherwise, RM_WHACKED
 is returned. Please refer to rmNodeSetLineStyle() for details about
 line style enumerators.

 To obtain the render-time value of the line width parameter of the
 RM rendering context, use rmStateGetLineStyle() from inside an application
 callback.
 @dend
 * ----------------------------------------------------
 */
RMenum
rmNodeGetLineStyle(const RMnode *p,
		   RMenum *rval)
{
    if (RM_ASSERT(p,"rmNodeGetLineStyle() error: input RMnode is NULL.") == RM_WHACKED)
	return(RM_WHACKED);
    
    if (p->rprops == NULL || p->rprops->linestyle == NULL)
	return(RM_WHACKED);

    if (rval != NULL)
	*rval = *(p->rprops->linestyle);
    
    return(RM_CHILL);
}

/*
 * ----------------------------------------------------
 * @Name rmNodeSetLineWidth
 @pstart
 RMenum rmNodeSetLineWidth(RMnode *toModify,
		           RMenum widthEnum)
 @pend

 @astart
 RMnode *toModify - a handle to an RMnode (modified).
 RMenum widthEnum -  an RMenum value. May be one of RM_LINEWIDTH_NARROW,
   RM_LINEWIDTH_MEDIUM, RM_LINEWIDTH_HEAVY, or RM_LINEWIDTH_X, where
   1 <= X <= 8 (input).
 @aend

 @dstart
 Use this routine to change the thickness of lines. Be default, all lines
 are drawn so they are 1 pixel wide (set on rmRootNode() at the time
 when RM is initialized). The following table shows the relationship between
 the linewidth enumerators and the pixel thickness of lines. The column on
 the left is the actual pixel thickness on the screen, and the second
 column are the associated RM line width enumerators.

 <pre>
 1  -  RM_LINEWIDTH_NARROW,RM_LINEWIDTH_1
 2  -  RM_LINEWIDTH_MEDIUM, RM_LINEWIDTH_2
 3  -  RM_LINEWIDTH_3
 4  -  RM_LINEWIDTH_HEAVY, RM_LINEWIDTH_4
 5  -  RM_LINEWIDTH_5
 6  -  RM_LINEWIDTH_6
 7  -  RM_LINEWIDTH_7
 8  -  RM_LINEWIDTH_8
 </pre>

 Upon success, RM_CHILL is returned, and the RMnode's linewidth attribute is
 set to the value "widthEnum." Upon failure, RM_WHACKED is returned and
 the RMnode's line width attribute remains unmodified.

 Use rmNodeGetLinewidth to obtain the line width attribute from an RMnode,
 or rmStateGetLinewidth to obtain the line width attribute at
 render time.
 @dend
 * ----------------------------------------------------
 */
RMenum
rmNodeSetLineWidth(RMnode *n,
		   RMenum width_enum)
{
    RMenum t;
    
    if (RM_ASSERT(n,"rmNodeSetLineWidth() error: input RMnode is NULL.") == RM_WHACKED)
	return(RM_WHACKED);

    t = width_enum;
    if ((t != RM_LINEWIDTH_NARROW) && (t != RM_LINEWIDTH_MEDIUM) &&
	(t != RM_LINEWIDTH_HEAVY) && (t != RM_LINEWIDTH_1) &&
	(t != RM_LINEWIDTH_2) && (t != RM_LINEWIDTH_3) &&
	(t != RM_LINEWIDTH_4) && (t != RM_LINEWIDTH_5) &&
	(t != RM_LINEWIDTH_6) && (t != RM_LINEWIDTH_7) &&
	(t != RM_LINEWIDTH_8))
    {
	rmError("rmNodeSetLineWidth() error: the input line width enumerator is not valid.");
	return(RM_WHACKED);
    }

    if (n->rprops == NULL)
	n->rprops = private_rmRenderModePropsNew();

    if (n->rprops->linewidth == NULL)
	n->rprops->linewidth = private_rmEnumNew(1);
    
    *(n->rprops->linewidth) = width_enum;
    private_rmNodeAttribMask(n,private_rmNodeComputeAttribMask(n),RM_SET);

    return(RM_CHILL);
}

/*
 * ----------------------------------------------------
 * @Name rmNodeGetLineWidth
 @pstart
 RMenum rmNodeGetLineWidth(const RMnode *toQuery,
		           RMenum *lineWidthReturn)
 @pend

 @astart
 const RMnode *toQuery - a handle to an RMnode (input).
 RMenum *lineWidthReturn - a handle to a caller supplied RMenum (result).
 @aend

 @dstart
 Use this routine to query the line width attribute of an RMnode. If one is
 defined, RM_CHILL is returned on the stack, and the RMnode's line width
 attribute will be copied into caller-supplied memory. Otherwise, RM_WHACKED
 is returned. Please refer to rmNodeSetLineWidth() for details about
 line width enumerators.

 Note that a linewidth enumerator is returned, not the actual pixel thickness
 of the line.

 To obtain the render-time value of the line width parameter of the
 RM rendering context, use rmStateGetLineWidth() from inside an application
 callback.
 @dend
 * ----------------------------------------------------
 */
RMenum
rmNodeGetLineWidth(const RMnode *p,
		   RMenum *rval)
{
   /* note that we're returning an enumeration
      value, not a line width. it is up to the
      caller to correctly convert. */

    if (RM_ASSERT(p,"rmNodeGetLineWidth() error: input RMnode is NULL.") == RM_WHACKED)
	return(RM_WHACKED);
    
    if (p->rprops == NULL || p->rprops->linewidth == NULL)
	return(RM_WHACKED);

    if (rval != NULL)
	*rval = *(p->rprops->linewidth);
    
    return(RM_CHILL);
}


/*
 * ----------------------------------------------------
 * @Name rmNodeSetPointSize
 @pstart
 RMenum rmNodeSetPointSize(RMnode *toModify,
		           float newSize)
 @pend

 @astart
 RMnode *toModify - a handle to an RMnode (modified).
 float newSize - a floating point value, must be greater than zero (input).
 @aend

 @dstart
 By default, a point primitive is drawn as a single pixel on the screen.
 This routine is used to change the the number of pixels used when drawing
 a point primitive. For non antialiased points (3/2000, currently the only
 option), the point primitive is rendered as a square consisting of
 "newSize" pixels on each edge of the square. Non-integer values are
 rounded to the nearest integer.

 Upon success, RM_CHILL is returned, and the new point size value is copied
 into the RMnode. Otherwise, RM_WHACKED is returned, and the RMnode remains
 unmodified.

 Use rmNodeGetPointSize() to obtain the point size attribute from an
 RMnode, or rmStateGetPointSize() from within an application callback to
 query the current point size from the RM rendering context.
 @dend
 * ----------------------------------------------------
 */
RMenum
rmNodeSetPointSize(RMnode *n,
		   float newsize)
{
    if (RM_ASSERT(n,"rmNodeSetPointSize() error: the input RMnode is NULL") == RM_WHACKED)
	return(RM_WHACKED);

    if (newsize <= 0.0F)
    {
	rmError("rmNodeSetPointSize() error: the point size must be greater than zero.");
	return(RM_WHACKED);
    }
    
    if (n->rprops == NULL)
	n->rprops = private_rmRenderModePropsNew();

    if (n->rprops->pointsize == NULL)
	n->rprops->pointsize = (float *)malloc(sizeof(float));
    
    *(n->rprops->pointsize) = newsize;
    private_rmNodeAttribMask(n,private_rmNodeComputeAttribMask(n),RM_SET);
    return(RM_CHILL);
}

/*
 * ----------------------------------------------------
 * @Name rmNodeGetPointSize
 @pstart
 RMenum rmNodeGetPointSize(const RMnode *toQuery,
		           float *sizeReturn)
 @pend

 @astart
 const RMnode *toQuery - a handle to an RMnode (input).
 float *sizeReturn - a handle to a caller-supplied float (result).
 @aend

 @dstart
 Use this routine to obtain the "point size" attribute from an RMnode. If
 it exists, RM_CHILL is returned, and the point size attribute is copied
 into caller-supplied memory (if it is not NULL). Otherwise, RM_WHACKED
 is returned.

 Note this routine queries an RMnode. To obtain the current point size
 attribute of the RM rendering context from within an application callback,
 use rmStateGetPointSize().
 @dend
 * ----------------------------------------------------
 */
RMenum
rmNodeGetPointSize(const RMnode *p,
		   float *rval)
{
    if (RM_ASSERT(p,"rmNodeGetPointSize() error: the input RMnode is NULL") == RM_WHACKED)
	return(RM_WHACKED);
    
    if (p->rprops == NULL || p->rprops->pointsize == NULL)
	return(RM_WHACKED);

    if (rval != NULL)
	*rval = *(p->rprops->pointsize);
    return(RM_CHILL);
}

/*
 * ----------------------------------------------------
 * @Name rmNodeSetPolygonDrawMode
 @pstart
 RMenum rmNodeSetPolygonDrawMode(RMnode *toModify,
		             RMenum whichFace,
			     RMenum newMode)
 @pend

 @astart
 RMnode *toModify - a handle to an RMnode (modified).
 RMenum whichFace - an RMenum value, may be one of RM_FRONT, RM_BACK,
    RM_FRONT_AND_BACK (input).
 RMenum newMode - an RMenum value, may be one of RM_LINE, RM_FILL or RM_POINT.
 @aend

 @dstart
 This routine maps directly to glPolygonMode() - a polygon has two sides,
 a front and back, and might be rendered differently depending upon which
 side is facing the viewer. By default, both front and back are drawn
 the same way: as filled polygons. This routine allows you to change
 how polygons are drawn. There are three options for the parameter "newMode"
 which specifies a new polygon rendering mode. RM_FILL, the default
 polygon mode, specifies that polygons will be drawn as filled areas.
 RM_LINE specifies that only polygon edges will be drawn. RM_POINT
 will cause only polygon vertices to be rendered.

 For the "whichFace" parameter, RM_FRONT means that front-facing polygons
 will be drawn using the new mode. RM_BACK means that back-facing polygons
 will be drawn using the new mode. Use RM_FRONT_AND_BACK, the default, to 
 apply the new mode to both front- and back-facing polygons.

 Note the OpenRM limitation that it is not possible at this time (3/2000) to
 specify one mode for the front-facing polygons and a different mode for
 back-facing polygons.

 Whether or not a polygon is front- or back-facing is determined by the
 "winding rule." See rmNodeSetFrontFace() for more information. Polygons may
 be culled based upon whether or not they are front- or back-facing. See
 rmNodeSetPolygonCullMode() for more information.

 Returns RM_CHILL upon success. Upon failure, the polygon draw mode
 remains unmodified, and RM_WHACKED is returned.
 @dend
 * ----------------------------------------------------
 */
RMenum
rmNodeSetPolygonDrawMode(RMnode *n,
		     RMenum face,
		     RMenum mode)
{

    if (RM_ASSERT(n,"rmNodeSetPolygonDrawMode() error: the input RMnode pointer is NULL.") == RM_WHACKED)
	return(RM_WHACKED);
    
    if ((face != RM_FRONT) && (face != RM_FRONT_AND_BACK) && (face != RM_BACK))
    {
        rmError(" the face tag given to rmNodeSetPolygonDrawMode is invalid. ");
	return(RM_WHACKED);
    }

    if ((mode != RM_POINT) && (mode != RM_LINE) && (mode != RM_FILL))
    {
        rmError(" the mode tag given to rmNodeSetPolygonDrawMode is invalid. ");
	return(RM_WHACKED);
    }

    if (n->rprops == NULL)
	n->rprops = private_rmRenderModePropsNew();

    if (n->rprops->poly_mode_face == NULL)
	n->rprops->poly_mode_face = private_rmEnumNew(1);
    
    if (n->rprops->poly_mode_drawstyle == NULL)
	n->rprops->poly_mode_drawstyle = private_rmEnumNew(1);
    
    *(n->rprops->poly_mode_face) = face; /* range check? */
    *(n->rprops->poly_mode_drawstyle) = mode;

    private_rmNodeAttribMask(n,private_rmNodeComputeAttribMask(n),RM_SET);
    return(RM_WHACKED);
}

/*
 * ----------------------------------------------------
 * @Name  rmNodeGetPolygonDrawMode
 @pstart
 RMenum rmNodeGetPolygonDrawMode(const RMnode *toQuery,
		             RMenum *returnFace,
			     RMenum *returnMode)
 @pend

 @astart
 const RMnode *toQuery - a handle to an RMnode (input).
 RMenum *returnFace, *returnMode - handles to caller-supplied RMenum's
     (result).
 @aend

 @dstart
 Use this routine to obtain the polygon rendering mode specified at an
 RMnode. Upon success, RM_CHILL is returned on the stack, and the RMnode's
 polygon rendering mode and face are copied into caller-supplied memory.
 If the polygon rendering mode is not defined at the RMnode "toQuery,"
 RM_WHACKED is returned and caller-supplied memory remains unmodified.
 See rmNodeSetPolygonDrawMode() for more details about the return values
 for "returnFace" and "returnMode."

 Note this routine queries the polygon mode at a specific node. During
 rendering, a given RMnode may not have a polygon mode defined at the
 RMnode level, but the polygon mode, and other drawing attributes, are
 always defined in the RMstate object. Use the routine rmStateGetPolygonDrawMode()
 from within an application callback to obtain the current render-time
 values for the polygon draw mode.
 @dend
 * ----------------------------------------------------
 */
RMenum
rmNodeGetPolygonDrawMode(const RMnode *n,
			 RMenum *face,
			 RMenum *mode)
{
    if (RM_ASSERT(n,"rmNodeGetPolygonDrawMode() error: the input RMnode is NULL") == RM_WHACKED)
	return(RM_WHACKED);
    
    if (n->rprops == NULL || n->rprops->poly_mode_face == NULL ||
	n->rprops->poly_mode_drawstyle == NULL)
	return(RM_WHACKED);

    if (face != NULL)
	*face = *(n->rprops->poly_mode_face);

    if (mode != NULL)
	*mode = *(n->rprops->poly_mode_drawstyle);
    return(RM_CHILL);
}
/* ----------------------------------------------------
 * @Name rmNodeSetPolygonCullMode
 @pstart
 RMenum rmNodeSetPolygonCullMode(RMnode *toModify,
		                 RMenum newMode)
 @pend

 @astart
 RMnode *toModify - a handle to an RMnode (modified).
 RMenum newMode - an RMenum value. May be one of RM_CULL_NONE, RM_CULL_FRONT,
    RM_CULL_BACK, or RM_CULL_FRONT_AND_BACK.
 @aend

 @dstart
 Culling refers to the process of rejecting polygons from the
 rendering process based upon some criteria. rmNodeSetPolygonCullMode()
 is used to control whether or not polygons are culled based upon
 whether or not they are front- or back-facing. This type of culling is
 performed late in the rendering pipeline - polygon vertices are first
 transformed to eye coordinates, then the polygon cull test is applied to
 accept or reject the polygon. Higher level culling, such as view frustum
 culling, occurs much earlier in the pipeline. Face level culling can
 improve performance in many cases, but frustum culling prior to
 drawing will result in better performance.

 By default, no polygon
 culling is performed (RM_CULL_NONE is applied to rmRootNode() at
 initialization time).

 Whether or not a polygon is front or back facing is a function of how
 vertices are ordered. See rmNodeSetFrontFace() for more information.

 When the input value for "newMode" is RM_CULL_NONE, no polygon culling
 is performed. When set to RM_CULL_FRONT, front-facing polygons will
 be culled after transformation to eye coordinates, but prior to drawing.
 When RM_CULL_BACK is specified, only back-facing polygons are culled
 prior to rasterization. When RM_CULL_FRONT_AND_BACK is specified, all
 polygons are culled.
 @dend
 * ----------------------------------------------------
 */
RMenum
rmNodeSetPolygonCullMode(RMnode *n,
			 RMenum newmode)
{
    if (RM_ASSERT(n,"rmNodeSetPolygonCullMode() error: input node is NULL.") == RM_WHACKED)
	return(RM_WHACKED);

    if ((newmode != RM_CULL_NONE) && (newmode != RM_CULL_FRONT) &&
	(newmode != RM_CULL_BACK) && (newmode != RM_CULL_FRONT_AND_BACK))
    {
	rmError(" the cull mode given to rmNodeSetPolygonCullMode is invalid. \n");
	return(RM_WHACKED);
    }
    if (n->rprops == NULL)
	n->rprops = private_rmRenderModePropsNew();

    if (n->rprops->cull_mode == NULL)
	n->rprops->cull_mode = private_rmEnumNew(1);

    *(n->rprops->cull_mode) = newmode;
    private_rmNodeAttribMask(n,private_rmNodeComputeAttribMask(n),RM_SET);

    return(RM_CHILL);
}

/*
 * ----------------------------------------------------
 * @Name rmNodeGetPolygonCullMode
 @pstart
 RMenum rmNodeGetPolygonCullMode(const RMnode *toQuery,
		                 RMenum *modeReturn)
 @pend

 @astart
 const RMnode *toQuery - a handle to an RMnode (input).
 RMenum *modeReturn - a handle to a caller-supplied RMenum (result).
 @aend

 @dstart
 Use this routine to obtain polygon cull settings at an RMnode. Upon success,
 RM_CHILL is returned, and the polygon cull mode from the RMnode "toQuery"
 is copied into caller-supplied memory. Upon failure, RM_WHACKED is returned,
 and caller-supplied memory remains unmodified.

 Note that this routine obtains the polygon cull settings from an RMnode.
 Not all RMnode's have this attribute defined. During a render-time traversal
 of the scene graph, the current rendering context may be queried using
 rmStateGetPolygonCullMode() from an application callback.
 @dend
 * ----------------------------------------------------
 */
RMenum
rmNodeGetPolygonCullMode(const RMnode *n,
			 RMenum *retmode)
{
    if (RM_ASSERT(n,"rmNodeGetPolygonCullMode() error: input RMnode is NULL. \n") == RM_WHACKED)
	return(RM_WHACKED);
    
    if (n->rprops == NULL || n->rprops->cull_mode == NULL)
	return(RM_WHACKED);

    if (retmode != NULL)
	*retmode = *(n->rprops->cull_mode);
    return(RM_CHILL);
}

/*
 * ----------------------------------------------------
 * @Name rmNodeSetFrontFace
 @pstart
 RMenum rmNodeSetFrontFace(RMnode *toModify,
		           RMenum newMode)
 @pend

 @astart
 RMnode *toModify - a handle to an RMnode (modified).
 RMenum newMode - an RMenum value, may be one of RM_CW or RM_CCW (input).
 @aend

 @dstart
 This routine is used to control how OpenGL determines which faces are
 front--facing. The basic idea is that the ordering of vertices
 in projected screen coordinates determines if a polygon is front-facing.
 Two choices are possible: either a clockwise orientation,
 specified with RM_CW, or a counter-clockwise orientation, RM_CCW.

 Whether or not a polygon is front- or back-facing as computed by
 this winding rule is relevant only in the context of face-level culling.
 Lighting calculations, in contrast, are a function of the dot product
 of the surface normal with the direction to the light source.

 By default, front faces in OpenRM are specified with RM_CCW, so
 polygon vertices that have a counterclockwise winding are front-facing.
 This is accomplished by setting rmNodeSetFrontFace(rmRootNode(), RM_CCW)
 at the time RM is initialized (this is performed internal to RM - applications
 need not perform this task).

 Use rmNodeGetFrontFace() to obtain the front face winding rule parameter
 from an RMnode.
 @dend
 * ----------------------------------------------------
 */
RMenum
rmNodeSetFrontFace(RMnode *n,
		   RMenum newmode)
{
    if (RM_ASSERT(n,"rmNodeSetFrontFace() error: input RMnode is NULL. \n") == RM_WHACKED)
	return(RM_WHACKED);

    if ((newmode != RM_CW) && (newmode != RM_CCW))
    {
	rmError(" the mode given to rmNodeSetFrontface is invalid. ");
	return(RM_WHACKED);
    }
    
    if (n->rprops == NULL)
	n->rprops = private_rmRenderModePropsNew();

    if (n->rprops->front_face == NULL)
	n->rprops->front_face = private_rmEnumNew(1);

    *(n->rprops->front_face) = newmode;

    private_rmNodeAttribMask(n,private_rmNodeComputeAttribMask(n),RM_SET);
    return(RM_CHILL);
}

/*
 * ----------------------------------------------------
 * @Name rmNodeGetFrontFace
 @pstart
 RMenum rmNodeGetFrontFace(const RMnode *toQuery,
		           RMenum *modeReturn)
 @pend

 @astart
 const RMnode *toQuery - a handle to an RMnode (input).
 RMenum *modeReturn - a handle to a caller-supplied RMenum (result).
 @aend

 @dstart
 Use this routine to obtain the "front face" parameter from an RMnode.
 Upon success, RM_CHILL is returned, and the front face attribute from
 the RMnode "toQuery" is copied into caller-supplied memory. Upon failure,
 RM_WHACKED is returned, and caller-supplied memory remains unmodified.

 Note that this routine obtains the front face attribute from an RMnode.
 Use the routine rmStateGetFrontFace() to obtain the front face attribute
 from the render state from inside an application callback during a
 render-time traversal of the scene graph.
 @dend
 * ----------------------------------------------------
 */
RMenum
rmNodeGetFrontFace(const RMnode *n,
		   RMenum *rval)
{
    if (RM_ASSERT(n,"rmNodeGetFrontFace() error: input RMnode is NULL. \n") == RM_WHACKED)
	return(RM_WHACKED);
    
    if (n->rprops == NULL || n->rprops->front_face == NULL)
	return(RM_WHACKED);

    if (rval != NULL)
	*rval = *(n->rprops->front_face);
    return(RM_CHILL);
}

/*
 * ----------------------------------------------------
 * @Name rmNodeSetShader
 @pstart
 RMenum rmNodeSetShader(RMnode *toModify,
		        RMenum newMode)
 @pend

 @astart
 RMnode *toModify - a handle to an RMnode (modified).
 RMenum newMode - an RMenum value (input). Must be one of RM_SHADER_SMOOTH,
   RM_SHADER_FLAT or RM_SHADER_NOLIGHT.
 @aend

 @dstart
 Use this routine to set the shading model used for rendering primitives.
 The new shader, specified by newMode, takes effect at the RMnode "toModify"
 and has scope of control over all descendent nodes in the scene graph,
 unless overridden by a new shader value specified at some child node.
 The permissible values for "newMode" are RM_SHADER_SMOOTH, RM_SHADER_FLAT
 or RM_SHADER_NOLIGHT. RM_SHADER_SMOOTH corresponds to
 glShadeModel(GL_SMOOTH); RM_SHADER_FLAT corresponds to glShadeModel(GL_FLAT)
 and RM_SHADER_NOLIGHT is a combination of glShadeModel(GL_FLAT) with
 lighting disabled.

 RM_SHADER_SMOOTH causes OpenGL's "smooth shading" model to be used.
 In most implementations, this is a Gouroud shader. RM_SHADER_FLAT uses
 a single color for each polygon or line segment. Some implementations
 will average all vertex colors, within the polygon or line segment, to
 produce an average color for the entire polygon or line segment. Other
 implementations use, instead, the last color value specified.
 RM_SHADER_NOLIGHT is the same as RM_SHADER_FLAT, but with lighting
 disabled.

 Upon success, the shade model of the input RMnode is modified to contain
 the value specified by "newMode," and RM_CHILL is returned. Upon failure,
 caused by a NULL input RMnode or an out-of-range shade model enumerator,
 RM_WHACKED is returned and the RMnode "toModify" remains unmodified.

 By default, rmRootNode() is assigned RM_SHADER_SMOOTH as the default
 shader, and will remain in effect unless overridden.
 @dend
 * ----------------------------------------------------
 */
RMenum
rmNodeSetShader(RMnode *n,
		RMenum newMode)
{
    if (RM_ASSERT(n,"rmNodeSetShader() error: the input RMnode is NULL") == RM_WHACKED)
	return(RM_WHACKED);

    if ((newMode != RM_SHADER_SMOOTH) && (newMode != RM_SHADER_FLAT) &&
	(newMode != RM_SHADER_NOLIGHT))
    {
	rmError("rmNodeSetShader() error: the input RMenum shader specification is not one of RM_SHADER_SMOOTH, RM_SHADER_FLAT or RM_SHADER_NOLIGHT");
	return(RM_WHACKED);
    }
    
    if (n->rprops == NULL)
	n->rprops = private_rmRenderModePropsNew();

    if (n->rprops->shademodel == NULL)
	n->rprops->shademodel = private_rmEnumNew(1);
    
    *(n->rprops->shademodel) = newMode;
    
    private_rmNodeAttribMask(n,private_rmNodeComputeAttribMask(n),RM_SET);
    
    return(RM_WHACKED);
}

/*
 * ----------------------------------------------------
 * @Name rmNodeGetShader
 @pstart
 RMenum rmNodeGetShader(const RMnode *toQuery,
		        RMenum *shaderReturn)
 @pend

 @astart
 const RMnode *n - a handle to an RMnode (input).
 RMenum *shaderReturn - a handle to an RMenum (result).
 @aend

 @dstart
 Use this routine to obtain the shader parameter associated with an
 RMnode. If no shader parameter is present in the RMnode "toQuery,"
 or if "toQuery" or "shaderReturn" are NULL,  RM_WHACKED is returned
 and "shaderReturn" remains unmodified. Otherwise, the shader parameter
 of "toQuery" is copied into caller-supplied memory and RM_CHILL is
 returned to the caller.

 Note that this routine does not return the shader that would be active
 at "toQuery" during a render-time traversal of the scene graph. If an
 RMnode does not have a shading parameter, the shader is inherited from
 ancestor nodes. The only means at this time (March 2000) to obtain the
 shader that would be active during a render-time traversal of the
 scene graph is to attach a switch or post-traversal callback to an
 RMnode, then query the RMstate using rmStateGetShader()) (a pre-traversal
 callback won't work because all rendering parameters are processed
 after the pre-traversal callback).
 @dend
 * ----------------------------------------------------
 */
RMenum
rmNodeGetShader(const RMnode *n,
		RMenum *shaderReturn)
{
    if ((RM_ASSERT(n,"rmNodeGetShader() error: the input RMnode is NULL") == RM_WHACKED) ||
	(RM_ASSERT(shaderReturn,"rmNodeGetShader() error: the return RMenum parameter is NULL") == RM_WHACKED))
	return(RM_WHACKED);
    
    if (n->rprops == NULL || n->rprops->shademodel == NULL)
	return(RM_WHACKED);
    *shaderReturn = *(n->rprops->shademodel);
    return(RM_CHILL);
}


/*
 * ----------------------------------------------------
 * @Name rmNodeSetTraversalMaskOpacity
 @pstart
 RMenum rmNodeSetTraversalMaskOpacity(RMnode *toModify,
			              RMenum newVal)
 @pend

 @astart
 RMnode *toModify - a handle to an RMnode (modified).
 RMenum newVal - an RMenum value. May be one of RM_RENDERPASS_TRANSPARENT,
   RM_RENDERPASS_OPAQUE or RM_RENDERPASS_ALL.
 @aend

 @dstart
 This routine is used to update one of the traversal masks of the
 RMnode "toModify." Upon success, the "opacity" traversal mask is modified
 to contain the value "newVal", and RM_CHILL is returned to the caller.
 Upon failure, RM_WHACKED is returned and the RMnode's opacity
 traversal mask remains unmodified.

 The RMnode traversal mask is used to control whether or not a RMnode,
 and its descendents in the scene graph, are processed during a
 scene graph traversal.  Three types of traversal masks are supported:
 opacity (rmNodeSetTraversalMaskOpacity), dimensions
 (rmNodeSetTraversalMaskDims), and stereo channel
 (rmNodeSetTraversalMaskChannel), although more may be added
 in the future. Currently, the traversal masks of RMnodes are tested
 only during a render-time traversal of the scene graph. The traversal
 masks provide a way for nodes to be processed during one or multiple
 passes of multipass rendering.

 In this routine, the permissible values of "newVal" are RM_RENDERPASS_OPAQUE,
 RM_RENDERPASS_TRANSPARENT or RM_RENDERPASS_ALL. Setting the value to
 RM_RENDERPASS_OPAQUE will cause nodes, and their descendents, to be
 processed only during an "opaque" rendering pass; a traversal mask
 value of RM_RENDERPASS_TRANSPARENT will result in nodes and their
 descendents being processed only during a "transparent" rendering pass,
 and RM_RENDERPASS_ALL will cause nodes and their descendents to be
 processed during both opaque and transparent rendering passes.
 @dend
 * ----------------------------------------------------
 */
RMenum
rmNodeSetTraversalMaskOpacity(RMnode *n,
			      RMenum i)
{
    if (RM_ASSERT(n,"rmNodeSetTraversalMaskOpacity() error: the input RMnode is NULL") == RM_WHACKED)
	return(RM_WHACKED);

    if ((i != RM_RENDERPASS_OPAQUE) && (i != RM_RENDERPASS_ALL) &&
	(i != RM_RENDERPASS_TRANSPARENT))
    {
	rmWarning("rmNodeSetTraversalMaskOpacity() error: the input RMenum value is not one of RM_RENDERPASS_OPAQUE, RM_RENDERPASS_TRANSPARENT or RM_RENDERPASS_ALL");
	return(RM_WHACKED);
    }
    
    private_rmNodeSetTraversalMaskOpacity(n,i);
    return(RM_CHILL);
}

/*
 * ----------------------------------------------------
 * @Name rmNodeGetTraversalMaskOpacity
 @pstart
 RMenum rmNodeGetTraversalMaskOpacity(const RMnode *toQuery,
			              RMenum *maskReturn) 
 @pend

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

 @dstart
 Use this routine to obtain the opacity traversal mask from an
 RMnode. Upon success, the opacity traversal mask from the RMnode
 "toQuery" is copied into caller-supplied memory, and RM_CHILL
 is returned. Otherwise, RM_WHACKED is returned, and the caller-supplied
 memory is not modified.

 See rmNodeSetTraversalMaskOpacity for more information about the
 opacity traversal mask.
 @dend
 * ----------------------------------------------------
 */

RMenum
rmNodeGetTraversalMaskOpacity(const RMnode *n,
			      RMenum *maskRet)
{
    if ((RM_ASSERT(n,"rmNodeGetTraversalMaskOpacity() error: the input RMnode is NULL") == RM_WHACKED) ||
	(RM_ASSERT(maskRet,"rmNodeGetTraversalMaskOpacity() error: the return RMenum parameter is NULL") == RM_WHACKED))
	return(RM_WHACKED);
    
    *maskRet = private_rmNodeGetTraversalMaskOpacity(n);
    return(RM_CHILL);
}

/*
 * ----------------------------------------------------
 * @Name rmNodeSetTraversalMaskDims
 @pstart
 RMenum rmNodeSetTraversalMaskDims(RMnode *toModify,
			           RMenum newVal)
 @pend

 @astart
 RMnode *toModify - a handle to an RMnode (modified).
 RMenum newVal - an RMenum value. May be one of RM_RENDERPASS_2D,
   RM_RENDERPASS_3D or RM_RENDERPASS_ALL.
 @aend

 @dstart
 This routine is used to update one of the traversal masks of the
 RMnode "toModify." Upon success, the "dimensions" traversal mask is modified
 to contain the value "newVal", and RM_CHILL is returned to the caller.
 Upon failure, RM_WHACKED is returned and the RMnode's opacity
 traversal mask remains unmodified.

 The RMnode traversal mask is used to control whether or not a RMnode,
 and its descendents in the scene graph, are processed during a
 scene graph traversal.  Three types of traversal masks are supported:
 opacity (rmNodeSetTraversalMaskOpacity), dimensions
 (rmNodeSetTraversalMaskDims), and stereo channel
 (rmNodeSetTraversalMaskChannel), although more may be added
 in the future. Currently, the traversal masks of RMnodes are tested
 only during a render-time traversal of the scene graph. The traversal
 masks provide a way for nodes to be processed during one or multiple
 passes of multipass rendering.

 In this routine, the permissible values of "newVal" are RM_RENDERPASS_2D,
 RM_RENDERPASS_3D or RM_RENDERPASS_ALL. Setting the value to
 RM_RENDERPASS_3D will cause nodes, and their descendents, to be
 processed only during "3D" rendering passes; a traversal mask
 value of RM_RENDERPASS_2D will result in nodes and their
 descendents being processed only during a "2D" rendering pass,
 and RM_RENDERPASS_ALL will cause nodes and their descendents to be
 processed during both 2D and 3D rendering passes.
 @dend
 * ----------------------------------------------------
 */
RMenum
rmNodeSetTraversalMaskDims(RMnode *n,
			   RMenum i)
{
    if (RM_ASSERT(n,"rmNodeSetTraversalMaskDims() error: the input RMnode is NULL") == RM_WHACKED)
	return(RM_WHACKED);

    if ((i != RM_RENDERPASS_3D) && (i != RM_RENDERPASS_ALL) &&
	(i != RM_RENDERPASS_2D))
    {
	rmWarning("rmNodeSetTraversalMaskDims() error: the input RMenum value is not one of RM_RENDERPASS_3D, RM_RENDERPASS_2D or RM_RENDERPASS_ALL");
	return(RM_WHACKED);
    }
    
    private_rmNodeSetTraversalMaskDims(n,i);
    return(RM_CHILL);
}

/*
 * ----------------------------------------------------
 * @Name rmNodeGetTraversalMaskDims
 @pstart
 RMenum rmNodeGetTraversalMaskDims(const RMnode *toQuery,
			           RMenum *maskReturn) 
 @pend

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

 @dstart
 Use this routine to obtain the "dimensions" traversal mask from an
 RMnode. Upon success, the dimensions traversal mask from the RMnode
 "toQuery" is copied into caller-supplied memory, and RM_CHILL
 is returned. Otherwise, RM_WHACKED is returned, and the caller-supplied
 memory is not modified.

 See rmNodeSetTraversalMaskDims for more information about the
 dimensions traversal mask.
 @dend
 * ----------------------------------------------------
 */
RMenum
rmNodeGetTraversalMaskDims(RMnode *n,
			   RMenum *maskRet)
{
    if ((RM_ASSERT(n,"rmNodeGetTraversalMaskDims() error: the input RMnode is NULL") == RM_WHACKED) ||
	(RM_ASSERT(maskRet,"rmNodeGetTraversalMaskDims() error: the return RMenum parameter is NULL") == RM_WHACKED))
	return(RM_WHACKED);
    
    *maskRet = private_rmNodeGetTraversalMaskDims(n);
    return(RM_CHILL);
}

/*
 * ----------------------------------------------------
 * @Name rmNodeSetTraversalMaskChannel
 @pstart
 RMenum rmNodeSetTraversalMaskChannel(RMnode *toModify,
			              RMenum newVal)
 @pend

 @astart
 RMnode *toModify - a handle to an RMnode (modified).
 RMenum newVal - an RMenum value. May be one of RM_LEFT_CHANNEL,
   RM_RIGHT_CHANNEL or RM_ALL_CHANNELS.
 @aend

 @dstart
 This routine is used to update one of the traversal masks of the
 RMnode "toModify." Upon success, the "channel" traversal mask is modified
 to contain the value "newVal", and RM_CHILL is returned to the caller.
 Upon failure, RM_WHACKED is returned and the RMnode's channel
 traversal mask remains unmodified.

 The RMnode traversal mask is used to control whether or not a RMnode,
 and its descendents in the scene graph, are processed during a
 scene graph traversal.  Three types of traversal masks are supported:
 opacity (rmNodeSetTraversalMaskOpacity), dimensions
 (rmNodeSetTraversalMaskDims), and stereo channel
 (rmNodeSetTraversalMaskChannel), although more may be added
 in the future. Currently, the traversal masks of RMnodes are tested
 only during a render-time traversal of the scene graph. The traversal
 masks provide a way for nodes to be processed during one or multiple
 passes of multipass rendering.

 In this routine, the permissible values of "newVal" are RM_LEFT_CHANNEL,
 RM_RIGHT_CHANNEL or RM_ALL_CHANNELS. Setting the value to
 RM_LEFT_CHANNEL will cause nodes, and their descendents, to be
 processed only during the left-channel rendering pass of a multipass
 stereo rendering; a traversal mask
 value of RM_RIGHT_CHANNEL will result in nodes and their
 descendents being processed only during the right-channel rendering pass
 of a multipass stereo rendering,
 and RM_ALL_CHANNELS will cause nodes and their descendents to be
 processed during any rendering pass of a multipass stereo or mono channel
 rendering.
 @dend
 * ----------------------------------------------------
 */
RMenum
rmNodeSetTraversalMaskChannel(RMnode *n,
		              RMenum newval)
{
    if (RM_ASSERT(n,"rmNodeSetTraversalMaskChannel error() the input RMnode is NULL.") == RM_WHACKED)
      return(RM_WHACKED);

    if ((newval != RM_LEFT_CHANNEL) && (newval != RM_RIGHT_CHANNEL) &&
	(newval != RM_ALL_CHANNELS))
    {
        rmError("rmNodeSetTraversalMaskChannel() error: the input channel enumerator is not one of RM_LEFT_CHANNEL, RM_RIGHT_CHANNEL, or RM_ALL_CHANNELS");
	return(RM_WHACKED);
    }

    private_rmNodeSetTraversalMaskChannel(n,newval);
    return(RM_CHILL);
}

/*
 * ----------------------------------------------------
 * @Name rmNodeGetTraversalMaskChannel
 @pstart
 RMenum rmNodeGetTraversalMaskChannel(const RMnode *toQuery,
                                      RMenum *maskReturn)
 @pend

 @astart
 const RMnode *toQuery - a handle to an RMnode to query (input).
 RMenum *maskReturn - a handle to an RMenum value (result).
 @aend

 @dstart
 Use this routine to obtain the channel traversal mask from an
 RMnode. Upon success, the channel traversal mask from the RMnode
 "toQuery" is copied into caller-supplied memory, and RM_CHILL
 is returned. The return value copied into maskReturn will be one
 of RM_LEFT_CHANNEL, RM_RIGHT_CHANNEL or RM_ALL_CHANNELS.
 Otherwise, RM_WHACKED is returned, and the caller-supplied
 memory is not modified.

 See rmNodeSetTraversalMaskChannel for more information about the
 opacity traversal mask.
 @dend
 * ----------------------------------------------------
 */
RMenum
rmNodeGetTraversalMaskChannel(const RMnode *n,
		              RMenum *maskReturn)
{
    if ((RM_ASSERT(n,"rmNodeGetTraversalMaskChannel() error: the input RMnode is NULL ") == RM_WHACKED) ||
	(RM_ASSERT(n,"rmNodeGetTraversalMaskChannel() error: the maskReturn parameter is NULL")))
	return(RM_WHACKED);
    
    *maskReturn = private_rmNodeGetTraversalMaskChannel(n);
    return(RM_CHILL);
}


/* node transformations */

/*
 * ----------------------------------------------------
 * @Name rmNodeSetTransformMode
 @pstart
 RMenum rmNodeSetTransformMode (RMnode *toModify,
		                RMenum newMode)
 @pend

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

 RMenum newMode - an RMenum value. Must be one of
    RM_TRANSFORM_GEOMETRY, RM_TRANSFORM_TEXTURE, or
    RM_TRANSFORM_IGNORE. 
 @aend

 @dstart

 Use this routine to set the transformation mode attribute in an
 RMnode.  This attribute controls how the transformation matrix
 information is applied to the scene graph. Returns RM_CHILL upon
 success, or RM_WHACKED upon failure.

 The mode RM_TRANSFORM_GEOMETRY causes transformation attributes
 within an RMnode to be applied to the GL_MODELVIEW matrix stack.
 RM_TRANSFORM_TEXTURE results in modification of the GL_TEXTURE matrix
 stack. RM_TRANSFORM_IGNORE will disable the application of an
 RMnode's transformation attributes without requiring transformation
 attributes to be neutralized (zero vectors and Identity matrices).

 By default, RMnodes are assigned a transformation mode of
 RM_TRANSFORM_GEOMETRY when created by rmNodeNew().

 Note that RMnode transformations are ALWAYS applied prior to scene
 parameter processing within an RMnode. This means that any lights,
 cameras, etc. that are part of an RMnode's scene parameters will be
 affected by transformations within the node.
 
 @dend
 * ----------------------------------------------------
 */
RMenum
rmNodeSetTransformMode (RMnode *n,
		        RMenum newmode)
{
    if (RM_ASSERT(n, "rmNodeSetTransformMode() error: the input RMnode pointer is NULL. ") == RM_WHACKED)
	return(RM_WHACKED);
    
    if ((newmode != RM_TRANSFORM_IGNORE) && (newmode != RM_TRANSFORM_GEOMETRY) && (newmode != RM_TRANSFORM_TEXTURE))
    {
	rmWarning(" rmNodeSetTransformMode is invalid, existing transform mode is unchanged. \n");
	return(RM_WHACKED);
    }
    if (n->transforms == NULL)
	n->transforms = private_rmNodeTransformsNew();

    n->transforms->transform_mode = newmode;

    return(RM_CHILL);
}


/*
 * ----------------------------------------------------
 * @Name rmNodeGetTransformMode
 @pstart
 RMenum rmNodeGetTransformMode (const RMnode *toQuery)
 @pend

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

 @dstart

 Upon success, the transform mode of the input RMnode is returned to
 the caller, and the return value will be one of
 RM_TRANSFORM_GEOMETRY, RM_TRANSFORM_TEXTURE or
 RM_TRANSFORM_IGNORE. Upon failure, RM_WHACKED is returned. Failure is
 defined as a NULL input RMnode, or if the input RMnode has no
 transformation attributes.

 The transformation mode of RMnodes is by default
 RM_TRANSFORM_GEOMETRY, as set by rmNodeNew(), but may be later
 changed by using rmNodeSetTransformMode.

 @dend
 * ----------------------------------------------------
 */
RMenum
rmNodeGetTransformMode (const RMnode *n)
{
    if (RM_ASSERT(n, "rmNodeGetTransformMode() error: the input RMnode pointer is NULL") == RM_WHACKED)
	return(RM_WHACKED);

    if (n->transforms != NULL)
	return(n->transforms->transform_mode);
    else
	return(RM_WHACKED);
}


/*
 * ----------------------------------------------------
 * @Name rmNodeSetPreMatrix
 @pstart
 RMenum rmNodeSetPreMatrix (RMnode *toModify,
		            const RMmatrix *newMatrix)
 @pend

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

 const RMmatrix *newMatrix - a handle to a caller-supplied RMmatrix
    (input). 
 @aend

 @dstart

 Use this routine to set the "pre matrix" transformation attribute of
 an RMnode.

 See rmNodeGetCompositeModelMatrix() for a discussion of how the
 transformation attributes of an RMnode are combined into a single
 transformation.

 Returns RM_CHILL upon success, or RM_WHACKED upon failure.

 @dend
 * ----------------------------------------------------
 */
RMenum
rmNodeSetPreMatrix (RMnode *n,
		    const RMmatrix *newMatrix)
{
    if ((RM_ASSERT(n, "rmNodeSetPreMatrix() error: the input RMnode pointer is NULL") == RM_WHACKED) ||
	(RM_ASSERT(newMatrix, "rmNodeSetPreMatrix() error: the input RMmatrix pointer is NULL") == RM_WHACKED))
	return(RM_WHACKED);

    if (n->transforms == NULL)
	n->transforms = private_rmNodeTransformsNew();

    n->transforms->pre = *newMatrix;

    return(RM_CHILL);
}


/*
 * ----------------------------------------------------
 * @Name rmNodeGetPreMatrix
 @pstart
 RMenum rmNodeGetPreMatrix (const RMnode *toQuery,
		            RMmatrix *matrixReturn)
 @pend

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

 RMmatrix *matrixReturn - a handle to a caller-supplied RMmatrix
    (modified). 
 @aend

 @dstart

 If the RMnode toQuery has transformation attributes defined, the "pre
 matrix" is copied into caller-supplied memory, and RM_CHILL is
 returned to the caller. Otherwise, RM_WHACKED is returned and the
 caller-supplied memory remains undisturbed.

 @dend
 * ----------------------------------------------------
 */
RMenum
rmNodeGetPreMatrix (const RMnode *n,
		    RMmatrix *ret)
{
    if ((RM_ASSERT(n, "rmNodeGetPreMatrix() error: the input RMnode pointer is NULL") == RM_WHACKED) ||
	(RM_ASSERT(ret, "rmNodeGetPreMatrix() error: the input RMmatrix pointer is NULL") == RM_WHACKED))
	return(RM_WHACKED);

    if (n->transforms == NULL)
	return(RM_WHACKED);

    *ret = n->transforms->pre;

    return(RM_CHILL);
}


/*
 * ----------------------------------------------------
 * @Name rmNodeSetCenter
 @pstart
 RMenum rmNodeSetCenter (RMnode *toModify,
		         const RMvertex3D *newVertex)
 @pend

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

 const RMvertex3D *newVertex - a handle to an RMvertex3D (input).
 @aend

 @dstart

 Use this routine to set the "center point" of an RMnode. The "center
 point" attribute defines the a local origin about which rotation and
 scaling occur for any transformations at this RMnode. See
 rmNodeGetCompositeModelMatrix() for a discussion of how the RMnode
 transformation attributes are combined into a single, composite
 transformation matrix.

 Returns RM_CHILL upon success, or RM_WHACKED upon failure.

 @dend
 * ----------------------------------------------------
 */
RMenum
rmNodeSetCenter (RMnode *n,
		 const RMvertex3D *newVertex)
{
    if ((RM_ASSERT(n, "rmNodeSetCenter() error: the input RMnode pointer is NULL. ") == RM_WHACKED) ||
	(RM_ASSERT(newVertex, "rmNodeSetCenter() error: the RMvertex3D pointer is NULL.") == RM_WHACKED))
	return(RM_WHACKED);

#if 0
    /* changed on 1/8/2001 */
    if (n->transforms == NULL)
	n->transforms = private_rmNodeTransformsNew();

    n->transforms->center = *newVertex;
#endif
    n->center = *newVertex;
    
    return(RM_CHILL);
}


/*
 * ----------------------------------------------------
 * @Name rmNodeGetCenter
 @pstart
 RMenum rmNodeGetCenter (const RMnode *toQuery,
		         RMvertex3D *retVector)
 @pend

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

 RMvertex3D *retVector - a handle to a caller-supplied RMvertex3D
   object (modified). 
 @aend

 @dstart

 Use this routine to obtain the RMnode's "center point." If the input
 RMnode and RMvertex3D objects are NOT NULL, then RM_CHILL is returned,
 and the RMnode's center point attribute is copied into caller-supplied
 memory.  Otherwise, RM_WHACKED is returned, and the caller-supplied
 memory remains undisturbed.

 @dend
 * ----------------------------------------------------
 */
RMenum
rmNodeGetCenter (const RMnode *n,
		 RMvertex3D *ret)
{
    if ((RM_ASSERT(n, "rmNodeGetCenter() error: the input RMnode pointer is NULL. ") == RM_WHACKED) ||
	(RM_ASSERT(ret, "rmNodeGetCenter() error: the return RMvertex3D pointer is NULL.") == RM_WHACKED))
	return(RM_WHACKED);

#if 0
    /* changed on 1/8/2001 */
    if (n->transforms == NULL)
	return(RM_WHACKED);

    *ret = n->transforms->center;
#endif
    *ret = n->center;

    return(RM_CHILL);

}


/*
 * ----------------------------------------------------
 * @Name rmNodeSetScaleMatrix
 @pstart
 RMenum rmNodeSetScaleMatrix (RMnode *toModify,
		              const RMmatrix *newMatrix)
 @pend

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

 const RMmatrix *newMatrix - a handle to a caller-supplied RMmatrix
    (input). 
 @aend

 @dstart

 Use this routine to set the "pre-rotation scale matrix"
 transformation attribute of an RMnode.

 See rmNodeGetCompositeModelMatrix() for a discussion of how the
 transformation attributes of an RMnode are combined into a single
 transformation.

 Returns RM_CHILL upon success, or RM_WHACKED upon failure.

 @dend
 * ----------------------------------------------------
 */
RMenum
rmNodeSetScaleMatrix (RMnode *n,
		      const RMmatrix *newMatrix)
{
    if ((RM_ASSERT(n, "rmNodeSetScaleMatrix() error: the input RMnode pointer is NULL") == RM_WHACKED) ||
	(RM_ASSERT(newMatrix, "rmNodeSetScaleMatrix() error: the input RMmatrix pointer is NULL") == RM_WHACKED))
	return(RM_WHACKED);

    if (n->transforms == NULL)
	n->transforms = private_rmNodeTransformsNew();

    n->transforms->s = *newMatrix;

    return(RM_CHILL);
}


/*
 * ----------------------------------------------------
 * @Name rmNodeGetScaleMatrix
 @pstart
 RMenum rmNodeGetScaleMatrix (const RMnode *toQuery,
		              RMmatrix *matrixReturn)
 @pend

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

 RMmatrix *matrixReturn - a handle to a caller-supplied RMmatrix
    (modified). 
 @aend

 @dstart

 If the RMnode toQuery has transformation attributes defined, the
 "pre-rotate scale matrix" is copied into caller-supplied memory, and
 RM_CHILL is returned to the caller. Otherwise, RM_WHACKED is returned
 and the caller-supplied memory remains undisturbed.

 @dend
 * ----------------------------------------------------
 */
RMenum
rmNodeGetScaleMatrix (const RMnode *n,
		      RMmatrix *ret)
{
    if ((RM_ASSERT(n, "rmNodeGetScaleMatrix() error: the input RMnode pointer is NULL") == RM_WHACKED) ||
	(RM_ASSERT(ret, "rmNodeGetScaleMatrix() error: the input RMmatrix pointer is NULL") == RM_WHACKED))
	return(RM_WHACKED);

    if (n->transforms == NULL)
	return(RM_WHACKED);

    *ret = n->transforms->s;

    return(RM_CHILL);
}


/*
 * ----------------------------------------------------
 * @Name rmNodeSetRotateMatrix
 @pstart
 RMenum rmNodeSetRotateMatrix (RMnode *toModify,
		               const RMmatrix *newMatrix)
 @pend

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

 const RMmatrix *newMatrix - a handle to a caller-supplied RMmatrix
    (input). 
 @aend

 @dstart

 Use this routine to set the "pre-rotation scale matrix"
 transformation attribute of an RMnode.

 See rmNodeGetCompositeModelMatrix() for a discussion of how the
 transformation attributes of an RMnode are combined into a single
 transformation.

 Returns RM_CHILL upon success, or RM_WHACKED upon failure.

 @dend
 * ----------------------------------------------------
 */
RMenum
rmNodeSetRotateMatrix (RMnode *n,
		       const RMmatrix *newMatrix)
{
    if ((RM_ASSERT(n, "rmNodeSetRotateMatrix() error: the input RMnode pointer is NULL") == RM_WHACKED) ||
	(RM_ASSERT(newMatrix, "rmNodeSetRotateMatrix() error: the input RMmatrix pointer is NULL") == RM_WHACKED))
	return(RM_WHACKED);

    if (n->transforms == NULL)
	n->transforms = private_rmNodeTransformsNew();

    n->transforms->r = *newMatrix;

    return(RM_CHILL);
}


/*
 * ----------------------------------------------------
 * @Name rmNodeGetRotateMatrix
 @pstart
 RMenum rmNodeGetRotateMatrix (const RMnode *toQuery,
		               RMmatrix *matrixReturn)
 @pend

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

 RMmatrix *matrixReturn - a handle to a caller-supplied RMmatrix
    (modified). 
 @aend

 @dstart

 If the RMnode toQuery has transformation attributes defined, the
 RMnode's rotation matrix transformation attribute is copied into
 caller-supplied memory, and RM_CHILL is returned to the
 caller. Otherwise, RM_WHACKED is returned and the caller-supplied
 memory remains undisturbed.

 @dend
 * ----------------------------------------------------
 */
RMenum
rmNodeGetRotateMatrix (const RMnode *n,
		       RMmatrix *ret)
{
    if ((RM_ASSERT(n, "rmNodeGetRotateMatrix() error: the input RMnode pointer is NULL") == RM_WHACKED) ||
	(RM_ASSERT(ret, "rmNodeGetRotateMatrix() error: the input RMmatrix pointer is NULL") == RM_WHACKED))
	return(RM_WHACKED);

    if (n->transforms == NULL)
	return(RM_WHACKED);

    *ret = n->transforms->r;

    return(RM_CHILL);
}


/*
 * ----------------------------------------------------
 * @Name rmNodeSetPostRotateScaleMatrix
 @pstart
 RMenum rmNodeSetPostRotateScaleMatrix (RMnode *toModify,
		                        const RMmatrix *newMatrix)
 @pend

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

 const RMmatrix *newMatrix - a handle to a caller-supplied RMmatrix
    (input). 
 @aend

 @dstart

 Use this routine to set the "post-rotation scale matrix"
 transformation attribute of an RMnode.

 See rmNodeGetCompositeModelMatrix() for a discussion of how the
 transformation attributes of an RMnode are combined into a single
 transformation.

 Returns RM_CHILL upon success, or RM_WHACKED upon failure.

 @dend
 * ----------------------------------------------------
 */
RMenum
rmNodeSetPostRotateScaleMatrix (RMnode *n,
			        const RMmatrix *newMatrix)
{
    if ((RM_ASSERT(n, "rmNodeSetPostRotateScaleMatrix() error: the input RMnode pointer is NULL. ") == RM_WHACKED) ||
	(RM_ASSERT(newMatrix, "rmNodeSetPostRotateScaleMatrix() error: the RMmatrix pointer is NULL.") == RM_WHACKED))
	return(RM_WHACKED);

    if (n->transforms == NULL)
	n->transforms = private_rmNodeTransformsNew();

    n->transforms->s2 = *newMatrix;

    return(RM_CHILL);
}


/*
 * ----------------------------------------------------
 * @Name rmNodeGetPostRotateScaleMatrix
 @pstart
 RMenum rmNodeGetPostRotateScaleMatrix (const RMnode *toQuery,
		                        RMmatrix *matrixReturn)
 @pend

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

 RMmatrix *matrixReturn - a handle to a caller-supplied RMmatrix
    (modified). 
 @aend

 @dstart

 If the RMnode toQuery has transformation attributes defined, the
 "post rotate scale matrix" is copied into caller-supplied memory, and
 RM_CHILL is returned to the caller. Otherwise, RM_WHACKED is returned
 and the caller-supplied memory remains undisturbed.

 @dend
 * ----------------------------------------------------
 */
RMenum
rmNodeGetPostRotateScaleMatrix (const RMnode *n,
			        RMmatrix *ret)
{
    if ((RM_ASSERT(n, "rmNodeGetPostRotateScaleMatrix() error: the input RMnode pointer is NULL. ") == RM_WHACKED) ||
	(RM_ASSERT(ret, "rmNodeGetPostRotateScaleMatrix() error: the return RMmatrix pointer is NULL.") == RM_WHACKED))
	return(RM_WHACKED);

    if (n->transforms == NULL)
	return(RM_WHACKED);

    *ret = n->transforms->s2;

    return(RM_CHILL);
}


/*
 * ----------------------------------------------------
 * @Name rmNodeSetTranslateVector
 @pstart
 RMenum rmNodeSetTranslateVector (RMnode *toModify,
			          const RMvertex3D *newVector)
 @pend

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

 const RMvertex3D *newVector - a handle to an RMvertex3D object
    (input). 
 @aend

 @dstart

 Use this routine to set the "translate vector" transformation
 attribute in an RMnode. Returns RM_CHILL upon success, or RM_WHACKED
 upon failure.

 See rmNodeGetCompositeModelMatrix() for information concerning how
 the composite transformation matrix at an RMnode is derived from the
 transformation attributes.

 @dend
 * ----------------------------------------------------
 */
RMenum
rmNodeSetTranslateVector (RMnode *n,
			  const RMvertex3D *newVector)
{
    if ((RM_ASSERT(n, "rmNodeSetTranslateVector() error: the input RMnode pointer is NULL. ") == RM_WHACKED) ||
	(RM_ASSERT(newVector, "rmNodeSetTranslateVector() error: the RMvertex3D pointer is NULL.") == RM_WHACKED))
	return(RM_WHACKED);

    if (n->transforms == NULL)
	n->transforms = private_rmNodeTransformsNew();

    n->transforms->translate = *newVector;

    return(RM_CHILL);

}


/*
 * ----------------------------------------------------
 * @Name rmNodeGetTranslateVector
 @pstart
 RMenum rmNodeGetTranslateVector (const RMnode *toQuery,
			          RMvertex3D *returnVector)
 @pend

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

 RMvertex3D *returnVector - a handle to a caller-supplied RMvertex3D
    object (modified). 
 @aend

 @dstart

 Use this routine to obtain the translation vector transformation
 attribute from an RMnode. If the RMnode has no transformation
 attributes, or if the input RMnode or RMvertex3D objects are NULL,
 RM_WHACKED is returned. Otherwise, RM_CHILL is returned, and the
 translation vector is copied into caller supplied memory.

 See rmNodeGetCompositeModelMatrix() for more details concerning how
 the RMnode transformation attributes are combined into a single,
 composite transformation.

 @dend
 * ----------------------------------------------------
 */
RMenum
rmNodeGetTranslateVector (const RMnode *n,
			  RMvertex3D *ret)
{
    if ((RM_ASSERT(n, "rmNodeGetTranslateVector() error: the input RMnode pointer is NULL. ") == RM_WHACKED) ||
	(RM_ASSERT(ret, "rmNodeGetTranslateVector() error: the return RMvertex3D pointer is NULL.") == RM_WHACKED))
	return(RM_WHACKED);

    if (n->transforms == NULL)
	return(RM_WHACKED);
    
    *ret = n->transforms->translate;

    return(RM_CHILL);
}


/*
 * ----------------------------------------------------
 * @Name rmNodeSetPostMatrix
 @pstart
 RMenum rmNodeSetPostMatrix (RMnode *toModify,
		             const RMmatrix *newMatrix)
 @pend

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

 const RMmatrix *newMatrix - a handle to a caller-supplied RMmatrix
    (input). 
 @aend

 @dstart

 Use this routine to set the "post matrix" transformation attribute of
 an RMnode.

 See rmNodeGetCompositeModelMatrix() for a discussion of how the
 transformation attributes of an RMnode are combined into a single
 transformation.

 Returns RM_CHILL upon success, or RM_WHACKED upon failure.

 @dend
 * ----------------------------------------------------
 */
RMenum
rmNodeSetPostMatrix (RMnode *n,
		     const RMmatrix *newMatrix)
{
    if ((RM_ASSERT(n, "rmNodeSetPostMatrix() error: the input RMnode pointer is NULL") == RM_WHACKED) ||
	(RM_ASSERT(newMatrix, "rmNodeSetPostMatrix() error: the input RMmatrix pointer is NULL") == RM_WHACKED))
	return(RM_WHACKED);

    if (n->transforms == NULL)
	n->transforms = (internals_RMtransformationStruct *)private_rmNodeTransformsNew();

    n->transforms->post = *newMatrix;

    return(RM_CHILL);
}


/*
 * ----------------------------------------------------
 * @Name rmNodeGetPostMatrix
 @pstart
 RMenum rmNodeGetPostMatrix (const RMnode *toQuery,
		             RMmatrix *matrixReturn)
 @pend

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

 RMmatrix *matrixReturn - a handle to a caller-supplied RMmatrix
    (modified). 
 @aend

 @dstart

 If the RMnode toQuery has transformation attributes defined, the
 "post matrix" is copied into caller-supplied memory, and RM_CHILL is
 returned to the caller. Otherwise, RM_WHACKED is returned and the
 caller-supplied memory remains undisturbed.

 @dend
 * ----------------------------------------------------
 */
RMenum
rmNodeGetPostMatrix (const RMnode *n,
		     RMmatrix *ret)
{
    if ((RM_ASSERT(n, "rmNodeGetPostMatrix() error: the input RMnode pointer is NULL") == RM_WHACKED) ||
	(RM_ASSERT(ret, "rmNodeGetPostMatrix() error: the input RMmatrix pointer is NULL") == RM_WHACKED))
	return(RM_WHACKED);

    if (n->transforms == NULL)
	return(RM_WHACKED);

    *ret = n->transforms->post;

    return(RM_CHILL);
}


/*
 * ----------------------------------------------------
 * @Name rmNodeGetCompositeModelMatrix
 @pstart
 RMenum rmNodeGetCompositeModelMatrix (RMnode *toQuery,
			               RMmatrix *matrixReturn)
 @pend

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

 RMmatrix *matrixReturn - a handle to a caller-supplied RMmatrix
    (return). 
 @aend

 @dstart

 Use this routine to obtain the composite transformation of all
 transformation matrices in an RMnode. Since transformations are
 optional node attributes, if no transformation attributes are present
 in an RMnode, the matrixReturn parameter is set to the Identity
 matrix.

 Upon success, RM_CHILL is returned and the node's composite
 transformation matrix is copied into caller-supplied memory (or the
 identity matrix is copied into caller-supplied memory if no
 transformation attributes are present in the input
 RMnode). Otherwise, RM_WHACKED is returned.

 The composite transformation is the matrix product of:

 [Pre -C S R S2 C T Post]

 Where:

 Pre (rmNodeSetPreMatrix, rmNodeGetPreMatrix) is the "pre
 transformation" matrix applied prior to any other transformations.

 -C and C are matrices representing a translation. C represents the
 RMnode's "center point" attribute (rmNodeSetCenter, rmNodeGetCenter).

 S is the "pre rotate" scale matrix (rmNodeSetScaleMatrix,
 rmNodeGetScaleMatrix)

 R is the RMnode's rotation matrix (rmNodeSetRotateMatrix,
 rmNodeGetRotateMatrix).

 S2 is the RMnode's "post rotate" scale matrix
 (rmNodeSetPostRotateScaleMatrix, rmNodeGetPostRotateScaleMatrix).

 T is a translation matrix derived from the RMnode's translation
 vector (rmNodeSetTranslateVector, rmNodeGetTranslateVector).

 Post is a transformation matrix applied after all other
 transformations at an RMnode (rmNodeSetPostMatrix,
 rmNodeGetPostMatrix).
 
 @dend
 * ----------------------------------------------------
 */
RMenum
rmNodeGetCompositeModelMatrix (RMnode *r,
			       RMmatrix *m)
{
    RMmatrix   minusC, SR, C, T, composite;
    RMvertex3D center, translate;

    if ((RM_ASSERT(r, "rmNodeGetCompositeModelMatrix() error: the input RMnode is NULL") == RM_WHACKED) ||
	(RM_ASSERT(m, "rmNodeGetCompositeModelMatrix() error: the return RMmatrix is NULL") == RM_WHACKED))
	return(RM_WHACKED);

    if (r->transforms == NULL)
    {
	rmMatrixIdentity(m);
	return(RM_CHILL);
    }
    
    rmMatrixIdentity(&minusC);
    rmMatrixIdentity(&SR);
    rmMatrixIdentity(&C);
    rmMatrixIdentity(&T);
    rmMatrixIdentity(&composite);

    rmNodeGetCenter(r, &center);
    rmNodeGetTranslateVector(r, &translate);
	
    minusC.m[3][0] = -1.0F * center.x;
    minusC.m[3][1] = -1.0F * center.y;
    minusC.m[3][2] = -1.0F * center.z;

    rmMatrixMultiply(&(r->transforms->s), &(r->transforms->r), &SR);
    rmMatrixMultiply(&SR, &(r->transforms->s2), &SR);
	    
    C.m[3][0] = center.x;
    C.m[3][1] = center.y;
    C.m[3][2] = center.z;

    T.m[3][0] = translate.x;
    T.m[3][1] = translate.y;
    T.m[3][2] = translate.z;

    rmMatrixMultiply(&(r->transforms->pre), &minusC, &minusC); /* 5/10/99 */
    rmMatrixMultiply(&minusC, &SR, &composite);
    rmMatrixMultiply(&composite, &C,&composite);
    rmMatrixMultiply(&composite, &T,&composite);
    rmMatrixMultiply(&composite, &(r->transforms->post), m); /* 5/10/99 */

    return(RM_CHILL);
}


/* node callbacks */

/*
 * ----------------------------------------------------
 * @Name rmNodeSetRenderOrderCallback
 @pstart
 RMenum rmNodeSetRenderOrderCallback (RMnode *toModify,
			              int (*appFunc)(const RMnode *, const RMstate *, int *orderIndices, int nChildren))
 @pend

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

 int (*appFunc)(const RMnode *, const RMstate *, int *, int) - A handle to an
    application callback, or NULL (input). 
 @aend

 @dstart

 Use this routine to assign a "render order callback" at an RMnode. To
 remove a switch callback from an RMnode, call this routine with a
 value of NULL for the appFunc parameter.

 This callback, if present, is invoked ONLY from within the
 RM_VIEW stage of multistage rendering. There is no equivalent
 routine for the RM_DRAW stage of multistage rendering.

 During normal traversal, all children of an RMnodes are
 traversed.  The render order callback is used to alter this behavior: the
 presence of a render order callback means that the application has the
 opportunity to alter the order in which the children of the node are
 renderered. Usually, children are rendered in the order they were added
 to the RMnode.
 
 The render order callback provided by the application will be passed an
 array of integers that contain the values of 0, 1, ... n-1 where n is the
 number of children at the RMnode toModify. These values represent the
 indices arranged in the order the children will be rendered. The application
 may modify the placement of these indices so that children need not be
 rendered in index-order.

 In addition to modifying the indices contained in the orderIndices array,
 the application should also return an integer value that specifies how
 many of the children will be rendered. This value should be in the range
 zero through nchildren-1. A value of zero means that no children will
 be rendered, and a value of nchildren-1 means that all children will be
 rendered, but using the index order defined in the array
 orderIndices.

 Note that RM will process the indices in the orderIndices array in
 increasing order. In other words, RM will render the child referenced
 by orderIndices[0] first, then orderIndices[1] next, and so on, until
 the application-specified number of children have been processed.

 This routine first appeared in v1.4.3.

 @dend
 * ----------------------------------------------------
 */
RMenum rmNodeSetRenderOrderCallback (RMnode *toModify,
				     int (*appFunc)(const RMnode *, const RMstate *, int *orderIndices, int nChildren))
{
    /* check for input null node */
    if (RM_ASSERT(toModify, "rmNodeSetRenderOrderCallBack() error: the input RMnode pointer is NULL.") == RM_WHACKED)
	return(RM_WHACKED);

    /* check for non-null switch callback && non null appFunc, which would
     be an error condition - can't have both switch and render order
     callbacks at the same time. */

    if ((appFunc != NULL) && (toModify->viewSwitchCallback != NULL))
    {
	rmWarning("rmNodeSetRenderOrderCallback() warning: the input node already has a switch callback, and it is an error condition to set a render order callback to a non-null value when there is a non-null switch callback. First set the switch callback to NULL prior to assigning a render order callback.");
	return(RM_WHACKED);
    }
    
    toModify->viewRenderOrderCallback = appFunc;

    return(RM_CHILL);
}
				     

/*
 * ----------------------------------------------------
 * @Name rmNodeSetSwitchCallback
 @pstart
 RMenum rmNodeSetSwitchCallback (RMnode *toModify,
			         int (*appFunc)(const RMnode *, const RMstate *))
 @pend

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

 int (*appFunc)(const RMnode *, const RMstate *) - A handle to an
    application callback, or NULL (input). 
 @aend

 @dstart

 Use this routine to assign a "switch callback" at an RMnode. To
 remove a switch callback from an RMnode, call this routine with a
 value of NULL for the appFunc parameter.

 This callback, if present, is invoked ONLY from within the
 RM_VIEW stage of multistage rendering. There is no equivalent
 routine for the RM_DRAW stage of multistage rendering.

 During normal traversal, all children of an RMnodes are
 traversed.  The switch callback is used to alter this behavior: the
 presence of a switch callback means that one, and only one of the
 child nodes will be traversed. The switch callback returns an integer
 value that is interpreted as an index, indicating which of an
 RMnode's children to traverse. Should the returned index be "out of
 range" (less than zero or greater than or equal to the number of
 children nodes), no children nodes will be processed. However, such a
 condition is indicative of an application error.

 Use an RMnode's "pre traversal" callback to inhibit traversal rather
 than returning an out-of-bounds index from the switch callback.

 The most common use for the switch callback is to perform
 level-of-detail (LOD) model switching based upon view dependent
 operations. The application callback is provided two parameters: a
 handle to the RMnode owning the switch callback, and an RMstate
 object. The RMstate object contains the current model-view and
 projection matrices, their inverses, and all other rendering
 parameters current at the RMnode "toModify" during a render time
 traversal of the scene graph.

 See the rmStateGet*() family of routines for more details about the
 type of render state information available to application callbacks.

 @dend
 * ----------------------------------------------------
 */
RMenum
rmNodeSetSwitchCallback (RMnode *n,
			 int (*appfunc)(const RMnode *, const RMstate *))
{
    if (RM_ASSERT(n, "rmNodeSetSwitchCallBack() error: the input RMnode pointer is NULL.") == RM_WHACKED)
	return(RM_WHACKED);
    
    if ((appfunc != NULL) && (n->viewRenderOrderCallback != NULL))
    {
	rmWarning("rmNodeSetSwitchCallback() warning: the input node already has a render order callback, and it is an error condition to set a switch callback to a non-null value when there is a non-null render order callback. First set the render order callback to NULL prior to assigning a switch callback.");
	return(RM_WHACKED);
    }
    
    n->viewSwitchCallback = appfunc;

    return(RM_CHILL);
}


/*
 * ----------------------------------------------------
 * @Name rmNodeSetPreTraversalCallback
 @pstart
 RMenum rmNodeSetPreTraversalCallback (RMnode *toModify,
                                       RMenum whichPass,
			               int (*appFunc)(const RMnode *, const RMstate *))
 @pend

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

 RMenum whichPass - an RMenum value that specifies during which stage
 of multistage rendering the callback should be invoked. Use either
 RM_VIEW or RM_RENDER.

 int (*appFunc)(const RMnode *, const RMstate *) - A handle to an
    application callback, or NULL (input). 
 @aend

 @dstart

 Use this routine to assign a "pre-traversal callback" at an
 RMnode. To remove a pre-traversal callback from an RMnode, call this
 routine with a value of NULL for the appFunc parameter.

 The callback will be invoked during the view pass if RM_VIEW
 is specified for the "whichPass", or during the rendering pass
 if RM_RENDER is specified. The view pass precedes the rendering
 pass, and is where all view-dependent operations should be
 performed (distance-based model switching, frustum culling, etc).
 Only drawing should be performed during the rendering pass.

 During normal scene graph traversal, all children of an RMnodes are
 traversed.  The pre-traversal callback is used to possibly alter this
 behavior: upon entry to an RMnode, the pre-traversal callback is
 invoked prior to any other RMnode processing. If the pre-traversal
 callback returns a positive value (greater than zero), the RMnode is
 processed "as usual." However, if the pre-traversal callback returns
 a value that is less than or equal to zero, processing of the RMnode
 terminates; no children will be processed, and any other callbacks
 in the RMnode (switch, post-traversal) will NOT be invoked.

 The application callback is provided two parameters: a handle to the
 RMnode owning the switch callback, and an RMstate object. The RMstate
 object contains the current model-view and projection matrices, their
 inverses, and all other rendering parameters current at the RMnode
 "toModify" during a render time traversal of the scene graph.

 The pretraversal callback is invoked *before* any scene parameters
 are processed at the given node. Applications that use the pretraversal
 callback to implement direct OpenGL rendering need to be aware that
 no scene parameters contained in the node will be processed. An alternate
 way to accomplish implementation of application rendering code that takes
 advantage of any scene parameters contained in the node along with the
 application callback is to use the post-traversal callback.

 Important note about RM_RENDER pre traversal callbacks: the return value
 provided by the application-supplied render-stage pre-traversal callback
 is ignored as of the time of this writing (8/11/02). This means that the
 render-stage traversal callback will be invoked during the render stage,
 but will not alter the scene graph traversal algorithm. This incorrect
 behavior will be fixed in a later release.

 See the rmStateGet*() family of routines for more details about the
 type of render state information available to application callbacks.

 @dend
 * ----------------------------------------------------
 */
RMenum
rmNodeSetPreTraversalCallback (RMnode *n,
			       RMenum whichPass,
			       int (*appfunc)(const RMnode *, const RMstate *))
{
    if (RM_ASSERT(n, "rmNodeSetPreTraverseCallBack() error: the input RMnode pointer is NULL.") == RM_WHACKED)
	return(RM_WHACKED);

    if ((whichPass != RM_VIEW) && (whichPass != RM_RENDER))
    {
	rmError("rmNodeSetPreTraversalCallback error: the input \"whichPass\" enumerator is neither RM_VIEW nor RM_RENDER. The scene graph node remains unmodified. ");
	return(RM_WHACKED);
    }

    if (whichPass == RM_VIEW)
	n->viewPretraverseCallback = appfunc;
    else			/* assume RM_RENDER */
	n->renderPretraverseCallback = appfunc;

    return(RM_CHILL);
}


/*
 * ----------------------------------------------------
 * @Name rmNodeSetPostTraversalCallback
 @pstart
 RMenum rmNodeSetPostTraversalCallback (RMnode *toModify,
                                        RMenum whichPass,
			                int (*appFunc)(const RMnode *, const RMstate *))
 @pend

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

 RMenum whichPass - an RMenum value that specifies during which stage
 of multistage rendering the callback should be invoked. Use either
 RM_VIEW or RM_RENDER.

 int (*appFunc)(const RMnode *, const RMstate *) - A handle to an
    application callback, or NULL (input). 
 @aend

 @dstart

 Use this routine to assign a "post-traversal callback" at an
 RMnode. To remove a post-traversal callback from an RMnode, call this
 routine with a value of NULL for the appFunc parameter.

 The callback will be invoked during the view pass if RM_VIEW
 is specified for the "whichPass", or during the rendering pass
 if RM_RENDER is specified. The view pass precedes the rendering
 pass, and is where all view-dependent operations should be
 performed (distance-based model switching, frustum culling, etc).
 Only drawing should be performed during the rendering pass.

 The post-traversal callback does not affect processing of children
 nodes. It is simply a hook provided to applications that is invoked
 after all other processing at an RMnode is complete, including
 traversal of all an RMnode's children.

 The return value from the post traversal callback is ignored.

 The application callback is provided two parameters: a handle to the
 RMnode owning the switch callback, and an RMstate object. The RMstate
 object contains the current model-view and projection matrices, their
 inverses, and all other rendering parameters current at the RMnode
 "toModify" during a render time traversal of the scene graph.

 See the rmStateGet*() family of routines for more details about the
 type of render state information available to application callbacks.

 @dend
 * ----------------------------------------------------
 */
RMenum
rmNodeSetPostTraversalCallback (RMnode *n,
				RMenum whichPass,
			        int (*appfunc)(const RMnode *, const RMstate *))
{
    if (RM_ASSERT(n, "rmNodePostTraversalCallBack() error: the input RMnode pointer is NULL.") == RM_WHACKED)
	return(RM_WHACKED);
    
    if ((whichPass != RM_VIEW) && (whichPass != RM_RENDER))
    {
	rmError("rmNodeSetPostTraversalCallback error: the input \"whichPass\" enumerator is neither RM_VIEW nor RM_RENDER. The scene graph node remains unmodified. ");
	return(RM_WHACKED);
    }

    if (whichPass == RM_VIEW)
	n->viewPosttraverseCallback = appfunc;
    else			/* assume RM_RENDER */
	n->renderPosttraverseCallback = appfunc;

    return(RM_CHILL);
}

/*
 * ----------------------------------------------------
 * @Name rmNodeMutexInit
 @pstart
 RMenum rmNodeMutexInit(RMnode *toModify,
                        RMenum initialLockState)
 @pend

 @astart
 RMnode *toModify - a handle to an RMnode (input).
 RMenum initialLockState - an RMenum value (input). May be either
 RM_MUTEX_LOCK or RM_MUTEX_UNLOCK.
 @aend

 @dstart

 Creates a new RMmutex object, and assigns it to the RMnode
 "toModify." The initial state of the mutex lock is set to the
 value "initialLockState."  Returns  RM_CHILL upon success, or
 RM_WHACKED upon failure.

 Mutexes are control mechanisms used to synchronize access to
 "critical" resources, and are most often used to serialize access
 to variables between multiple, concurrent execution threads. 
 Mutexes are an optional field in an RM scene graph node that
 are explicitly created, manipulated and destroyed by applications.
 The behavior of the mutex synchronization tools in OpenRM is
 modeled closely after that of POSIX threads/semaphores/mutexes.

 In OpenRM, the RMmutex is an abstraction layer that uses
 POSIX mutex in Unix implementations, and native  Win32 methods under
 windows. Like the POSIX model, the following  guidelines apply:

 - Mutexes are created and assigned an initial value.
 - Unlocking the mutex is an atomic, async-safe operation.
 - Mutex locking is a blocking operation that will suspend the caller
 until the mutex is unlocked.
 - A non-blocking lock operation is provided via rmNodeMutexTryLock.
 @dend
 * ----------------------------------------------------
 */
RMenum
rmNodeMutexInit(RMnode *n,
		RMenum initLockStatus)
{
    if (RM_ASSERT(n, "rmNodeMutexInit() error: the input RMnode pointer is NULL.") == RM_WHACKED)
	return(RM_WHACKED);

    if ((n->nodeMutex = rmMutexNew(RM_MUTEX_UNLOCK)) == NULL)
    {
	rmError("rmNodeMutexInit(): error creating node mutex. \n");
	return(RM_WHACKED);
    }

    if (initLockStatus == RM_MUTEX_LOCK)
	rmMutexLock(n->nodeMutex);
    
    return(RM_CHILL);
}

/*
 * ----------------------------------------------------
 * @Name rmNodeMutexUnlock
 @pstart
 RMenum rmNodeMutexUnlock (RMnode *toModify)
 @pend

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

 @dstart

 Performs an atomic "unlock" operation to the RMmutex
 object in an RMnode. This operation is non-blocking, and is async-safe.
 If no RMmutex exists in the RMnode, or if there is some other
 type of error encountered, RM_WHACKED is returned. Otherwise,
 RM_CHILL indicates successful completion of the post operation.
 
 Mutexes are control mechanisms used to synchronize access to
 "critical" resources, and are most often used to serialize access
 to variables between multiple, concurrent execution threads. 
 Mutexes are an optional field in an RM scene graph node that
 are explicitly created, manipulated and destroyed by applications.
 The behavior of the mutex synchronization tools in OpenRM is
 modeled closely after that of POSIX threads/semaphores/mutexes.

 In OpenRM, the RMmutex is an abstraction layer that uses
 POSIX mutex in Unix implementations, and native  Win32 methods under
 windows. Like the POSIX model, the following  guidelines apply:

 - Mutexes are created and assigned an initial value.
 - Unlocking the mutex is an atomic, async-safe operation.
 - Mutex locking is a blocking operation that will suspend the caller
 until the mutex is unlocked.
 - A non-blocking lock operation is provided via rmNodeMutexTryLock.
 @dend
 * ----------------------------------------------------
 */
RMenum
rmNodeMutexUnlock(RMnode *n)
{
    if (RM_ASSERT(n, "rmNodeMutexUnlock() error: the input RMnode pointer is NULL.") == RM_WHACKED)
	return(RM_WHACKED);

    return(rmMutexUnlock(n->nodeMutex));
}

/*
 * ----------------------------------------------------
 * @Name rmNodeMutexLock
 @pstart
 RMenum rmNodeMutexLock (RMnode *toModify)
 @pend

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

 @dstart

 Performs a blocking lock operation: execution will be suspended
 until the RMmutex of the RMnode "toModify" becomes unlocked.
 When the RMmutex becomes unlocked, this routine will lock
 the RMmutex, then return.  Upon successful completion of the wait
 operation, RM_CHILL is returned.  Otherwise, RM_WHACKED indicates an
 error of some type.
 
 Mutexes are control mechanisms used to synchronize access to
 "critical" resources, and are most often used to serialize access
 to variables between multiple, concurrent execution threads. 
 Mutexes are an optional field in an RM scene graph node that
 are explicitly created, manipulated and destroyed by applications.
 The behavior of the mutex synchronization tools in OpenRM is
 modeled closely after that of POSIX threads/semaphores/mutexes.

 In OpenRM, the RMmutex is an abstraction layer that uses
 POSIX mutex in Unix implementations, and native  Win32 methods under
 windows. Like the POSIX model, the following  guidelines apply:

 - Mutexes are created and assigned an initial value.
 - Unlocking the mutex is an atomic, async-safe operation.
 - Mutex locking is a blocking operation that will suspend the caller
 until the mutex is unlocked.
 - A non-blocking lock operation is provided via rmNodeMutexTryLock.
 @dend
 * ----------------------------------------------------
 */
RMenum
rmNodeMutexLock(RMnode *n)
{
    if (RM_ASSERT(n, "rmNodeMutexLock() error: the input RMnode pointer is NULL.") == RM_WHACKED)
	return(RM_WHACKED);

    return(rmMutexLock(n->nodeMutex));
}

/*
 * ----------------------------------------------------
 * @Name rmNodeMutexTryLock
 @pstart
 RMenum rmNodeMutexTryLock (const RMnode *toQuery)
 @pend

 @astart
 const RMnode *toQuery - a handle to an RMnode (input).
 int *returnValue - a handle to a caller-supplied int (return).
 
 @aend

 @dstart

 Performs a non-blocking lock of the RMmutex in the RMnode
 toQuery. Return values are the same as those for rmMutexTryLock.
 
 Mutexes are control mechanisms used to synchronize access to
 "critical" resources, and are most often used to serialize access
 to variables between multiple, concurrent execution threads. 
 Mutexes are an optional field in an RM scene graph node that
 are explicitly created, manipulated and destroyed by applications.
 The behavior of the mutex synchronization tools in OpenRM is
 modeled closely after that of POSIX threads/semaphores/mutexes.

 In OpenRM, the RMmutex is an abstraction layer that uses
 POSIX mutex in Unix implementations, and native  Win32 methods under
 windows. Like the POSIX model, the following  guidelines apply:

 - Mutexes are created and assigned an initial value.
 - Unlocking the mutex is an atomic, async-safe operation.
 - Mutex locking is a blocking operation that will suspend the caller
 until the mutex is unlocked.
 - A non-blocking lock operation is provided via rmNodeMutexTryLock.
 @dend
 * ----------------------------------------------------
 */
RMenum
rmNodeMutexTryLock(const RMnode *n)
{
    if (RM_ASSERT(n, "rmNodeMutexTryLoakc() error: the input RMnode pointer is NULL.") == RM_WHACKED)
	return(RM_WHACKED);

    return(rmMutexTryLock(n->nodeMutex));
}

/*
 * ----------------------------------------------------
 * @Name rmNodeGetMutex
 @pstart
 RMmutex * rmNodeGetMutex (const RMnode *toQuery)
 @pend

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

 @dstart

 Returns to the caller the handle of the RMmutex at the RMnode.
 If no RMmutex is present in the RMnode, NULL is returned.
 Callers may perform any valid operation upon the RMmutex
 (rmMutexLock, rmMutexUnlock or rmMutexTryLock), with
 one exception: callers may not destroy the RMnode mutex directly.
 To destroy the RMnode mutex, use the routine rmNodeMutexDestroy().
 
 Mutexes are control mechanisms used to synchronize access to
 "critical" resources, and are most often used to serialize access
 to variables between multiple, concurrent execution threads. 
 Mutexes are an optional field in an RM scene graph node that
 are explicitly created, manipulated and destroyed by applications.
 The behavior of the mutex synchronization tools in OpenRM is
 modeled closely after that of POSIX threads/semaphores/mutexes.

 In OpenRM, the RMmutex is an abstraction layer that uses
 POSIX mutex in Unix implementations, and native  Win32 methods under
 windows. Like the POSIX model, the following  guidelines apply:

 - Mutexes are created and assigned an initial value.
 - Unlocking the mutex is an atomic, async-safe operation.
 - Mutex locking is a blocking operation that will suspend the caller
 until the mutex is unlocked.
 - A non-blocking lock operation is provided via rmNodeMutexTryLock.
 @dend
 * ----------------------------------------------------
 */
RMmutex *
rmNodeGetMutex(const RMnode *n)
{
    if (RM_ASSERT(n, "rmNodeGetMutex() error: the input RMnode pointer is NULL.") == RM_WHACKED)
	return(NULL);

    return(n->nodeMutex);
}

/*
 * ----------------------------------------------------
 * @Name rmNodeMutexDelete
 @pstart
 RMenum rmNodeMutexDelete (RMnode *toModify)
 @pend

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

 @dstart

 Deletes the RMmutex in the RMnode "toModify." Upon success,
 RM_CHILL is returned. A return value of RM_WHACKED indicates some
 type of error.
 
 Mutexes are control mechanisms used to synchronize access to
 "critical" resources, and are most often used to serialize access
 to variables between multiple, concurrent execution threads. 
 Mutexes are an optional field in an RM scene graph node that
 are explicitly created, manipulated and deleted by applications.
 The behavior of the mutex synchronization tools in OpenRM is
 modeled closely after that of POSIX threads/semaphores/mutexes.

 In OpenRM, the RMmutex is an abstraction layer that uses
 POSIX mutex in Unix implementations, and native  Win32 methods under
 windows. Like the POSIX model, the following  guidelines apply:

 - Mutexes are created and assigned an initial value.
 - Unlocking the mutex is an atomic, async-safe operation.
 - Mutex locking is a blocking operation that will suspend the caller
 until the mutex is unlocked.
 - A non-blocking lock operation is provided via rmNodeMutexTryLock.
 @dend
 * ----------------------------------------------------
 */
RMenum
rmNodeMutexDelete(RMnode *n)
{
    RMenum status=RM_CHILL;
    
    if (RM_ASSERT(n, "rmNodeMutexDelete() error: the input RMnode pointer is NULL.") == RM_WHACKED)
	return(RM_WHACKED);

    if (n->nodeMutex != NULL)
    {
	status = rmMutexDelete(n->nodeMutex);
	n->nodeMutex = NULL;
    }
    return(status);
}

/*
 * ----------------------------------------------------
 * @Name rmNodeFrustumCullCallback
 @pstart
   int rmNodeFrustumCullCallback(const RMnode *n, const RMstate *s)
 @pend

 @astart
 const RMnode *n - an RMnode pointer (input).

 const RMstate *s - an RMstate pointer (input).
 @aend

 @dstart

 This routine implements a view frustum cull test, and is not intended
 to be called directly by applications. Developers should use this
 routine as a parameter to rmNodeSetPretraversalCallback using
 RM_VIEW to implement view frustum calling during the view stage
 of multistage rendering.

 rmNodeFrustumCallback will transform the corners of the RMnode's
 bounding box through the composite viewing matrix (model+view+projection)
 active at this node during the view traversal (transformations
 accumulate!) and will compare the visibility of each of these eight
 transformed vertices. If any are visible (lie within the view frustum),
 a value of 1 is returned. Otherwise, all vertices lie outside the view
 frustum, and a value of zero is returned.

 During a view traversal, if a node is culled by this test, this
 node and any children will not be further processed - and won't
 be rendered during the rendering stage. This type of frustum culling
 is an excellent way to reduce load on the graphics engine by performing
 view-dependent, model-level simplification.

 When this routine is used as indicated in the code example below, the
 result if view frustum culling for RMnodes.

 Example code:

 <pre>

 RMnode *n = rmNodeNew("myNode",RM_RENDERPASS_ALL, RM_RENDERPASS_ALL);

 ... other node config and primitive building omitted ...

 rmNodeComputeBoundingBox(n);

 rmNodeSetPretraversalCallback(n, RM_VIEW, rmNodeFrustumCallback);

 </pre>

 Caveats:

 1. There are some circumstances in which if all bounding box
 vertices lie outside the view frustum, that the interior of the
 box lies inside the view frustum. One example is when the frustum
 is completely contained within the bounding box. This routine needs to
 be modified to account for these types of conditions (using
 a method similar to the Cohen-Sutherland polygon clipping algorithm).

 2. In order for this routine to be effective, nodes need to have
 bounding boxes. It is up to the application to ensure that bounding
 boxes are present at RMnodes for which frustum culling is used.
 See rmNodeComputeBoundingBox, rmNodeSetBoundingBox and
 rmNodeUnionAllBoxes.

 3. This routine has been tested for perspective projections, but
 needs to be tested with orthographic projections.

 For best results, the size of the bounding box should be < the
 size of the frustum.

 @dend
 * ----------------------------------------------------
 */
int
rmNodeFrustumCullCallback(const RMnode *n,
			  const RMstate *s)
{
    int i;
    float v[4],d[4];
    RMvertex3D bmin, bmax;
    RMvertex3D t[8];
    int isVisible;
    const RMmatrix *c;

    /* get the bounding box. */
    if (rmNodeGetBoundingBox(n,&bmin, &bmax) == RM_WHACKED)
	return 1;		/* not sure what to do - just draw it. */

    /* from the two corners of the source box, construct the other
     6 of the fully-specified box. we assume the original box is an
     axis aligned thing. */
    t[0] = bmin;
    t[1] = t[0];
    t[1].x = bmax.x;
    t[2] = t[1];
    t[2].y = bmax.y;
    t[3] = t[2];
    t[3].x = bmin.x;

    t[4] = bmin;
    t[4].z = bmax.z;
    t[5] = t[4];
    t[5].x = bmax.x;
    t[6] = t[5];
    t[6].y = bmax.y;
    t[7] = t[6];
    t[7].x = bmin.x;

    isVisible = 0;
    c = &(s->composite) ;	

    for (i=0;i<8;i++)
    {
	int mask = (1 << i);
	
	v[0] = t[i].x;
	v[1] = t[i].y;
	v[2] = t[i].z;
	v[3] = 1.F;

	/* transform a box vertex through the current
	 model+view+projection matrix. we include the project matrix
	 to simplify comparisons in Z. t*/
	rmPoint4MatrixTransform(v,c,d);

	/* this test is valid for perspective projections. need to
	 test with orthographic projections. */
	if ((d[2] > 0.0) && ( d[2] <= d[3]) && (fabs(d[0]) <= d[3]) && (fabs(d[1]) <= d[3]))
	    isVisible |= mask;

	if (isVisible != 0)	/* one of bbox corners is visible */
	    break;
    }

    if (isVisible == 0)
	return(0);
    else
	return(1);
}

/* PRIVATE */
void
private_rmSceneGraphWalk (RMnode *r,
			  const RMstate *last,
			  void (*userfunc)(RMnode *node,
					  const RMstate *state,
					  void *clientData),
			  void *clientData)
{
    /*
     * walk the scene graph.
     * at each node, create the render state and pass that, along
     * with the node to the user function. the user function
     * may provide a pointer to something, and it is dragged along
     * as well.
     */
    int      i;
    RMstate *s;
    int      pushedAttribsReturn = 0;
    
    if (userfunc == NULL)
        return;
    
    s = rmStateNew();
    rmStateCopy(last, s);

    private_collectAndApplyMatrices (s, r, NULL, GL_RENDER,
				     &pushedAttribsReturn, RM_FALSE);
    
    private_updateSceneParms(r, s, RM_FALSE, 0, NULL, NULL);

    (*userfunc)(r, s, clientData);

    for (i = 0; i < rmNodeGetNumChildren(r); i++)
	private_rmSceneGraphWalk(rmNodeGetIthChild(r, i), s, userfunc, clientData);

    rmStateDelete(s);
}


/* PRIVATE */
void
private_rmNodeIncrementRefcount (RMnode *n)
{
    n->refcount++;
}


/* PRIVATE */
int
private_rmNodeGetRefcount (RMnode *n)
{
    return(n->refcount);
}


/* PRIVATE */
int
private_rmNodeDecrementRefcount (RMnode *n)
{
    return(n->refcount--);
}


/* PRIVATE */
internals_RMtransformationStruct *
private_rmNodeTransformsNew (void)
{
    internals_RMtransformationStruct *t;
    
    t = (internals_RMtransformationStruct *)malloc(sizeof(internals_RMtransformationStruct));

    /* initialize all matrices to I and all vectors to zero */
    memset(t, 0, sizeof(internals_RMtransformationStruct));
    
    rmMatrixIdentity(&(t->pre));
    rmMatrixIdentity(&(t->s));
    rmMatrixIdentity(&(t->r));
    rmMatrixIdentity(&(t->s2));
    rmMatrixIdentity(&(t->post));

#if 0
    /* changed 1/8/2001 */
    t->center.x = t->center.y = t->center.z = 0.0;
#endif

    t->transform_mode = RM_TRANSFORM_GEOMETRY;

    return(t);
}


/* PRIVATE */
void
private_rmNodeTransformsDelete (internals_RMtransformationStruct *t)
{
    if (t != NULL)
	free((void *)t);
}


/* PRIVATE */
void
private_rmPrimTypeToString (RMenum ptype,
			    char *b1)
{
    switch(ptype)
    {
    case RM_LINES:
	strcpy(b1,"RM_LINES");
	break;
	    
    case RM_LINE_STRIP:
	strcpy(b1, "RM_LINE_STRIP");
	break;
	    
    case RM_TRIANGLES:
	strcpy(b1, "RM_TRIANGLES");
	break;
	    
    case RM_TRIANGLE_STRIP:
	strcpy(b1, "RM_TRIANGLE_STRIP");
	break;
	    
    case RM_TRIANGLE_FAN:
	strcpy(b1, "RM_TRIANGLE_FAN");
	break;
	    
    case RM_QUADMESH:
	strcpy(b1, "RM_QUADMESH");
	break;
	    
    case RM_POINTS:
	strcpy(b1, "RM_POINTS");
	break;

    case RM_POLYS:
	strcpy(b1,"RM_POLYS");
	break;

    case RM_QUAD_STRIP:
	strcpy(b1,"RM_QUAD_STRIP");
	break;

    case RM_SPHERES:
	strcpy(b1, "RM_SPHERES");
	break;
	    
    case RM_BOX3D:
	strcpy(b1, "RM_BOX3D");
	break;
	    
    case RM_BOX3D_WIRE:
	strcpy(b1, "RM_BOX3D_WIRE");
	break;
	    
    case RM_CONES:
	strcpy(b1, "RM_CONES");
	break;
	    
    case RM_CYLINDERS:
	strcpy(b1, "RM_CYLINDERS");
	break;
	    
    case RM_OCTMESH:
	strcpy(b1, "RM_OCTMESH");
	break;
	    
    case RM_TEXT:
	strcpy(b1, "RM_TEXT");
	break;
	    
    case RM_INDEXED_TEXT:
	strcpy(b1, "RM_INDEXED_TEXT");
	break;
	    
    case RM_QUADS:
	strcpy(b1, "RM_QUADS");
	break;
	    
    case RM_MARKERS2D:
	strcpy(b1, "RM_MARKERS2D");
	break;
	    
    case RM_CIRCLE2D:
	strcpy(b1, "RM_CIRCLE2D");
	break;
	    
    case RM_BOX2D:
	strcpy(b1, "RM_BOX2D");
	break;
	    
    case RM_ELLIPSE2D:
	strcpy(b1, "RM_ELLIPSE2D");
	break;
	    
    case RM_SPRITE:
	strcpy(b1, "RM_SPRITE");
	break;
	    

    case RM_BITMAP:
	strcpy(b1, "RM_BITMAP");
	break;
	    
    case RM_INDEXED_BITMAP:
	strcpy(b1, "RM_INDEXED_BITMAP");
	break;

    case RM_INDEXED_TFAN:
	strcpy(b1,"RM_INDEXED_TFAN");
	break;

    case RM_INDEXED_QUADS:
	strcpy(b1,"RM_INDEXED_QUADS");
	break;

    case RM_INDEXED_TRIANGLES:
	strcpy(b1,"RM_INDEXED_TRIANGLES");
	break;

    case RM_INDEXED_TRIANGLE_STRIP:
	strcpy(b1, "RM_INDEXED_TRIANGLE_STRIP");
	break;

    case RM_INDEXED_QUAD_STRIP:
	strcpy(b1,"RM_INDEXED_QUAD_STRIP");
	break;

    case RM_APP_DISPLAYLIST:
	strcpy(b1,"RM_APP_DISPLAYLIST");
	break;

    case RM_USERDEFINED_PRIM:
	strcpy(b1, "RM_USERDEFINED_PRIM");
	break;
	    
    default: /* bogus prim enum */
	strcpy(b1, "Undefined Primitive type");
	break;
    }
}

/* PRIVATE */
int
private_rmPrimitiveHackGetNverts (RMprimitive *p) 
{
    RMprimitiveDataBlob *vblob;

    vblob = private_rmBlobFromIndex(p, BLOB_VERTEX_INDEX);

    return(private_rmBlobGetNthings(vblob));
}

int
private_rmPrimitiveHackGetNnormals (RMprimitive *p) 
{
    RMprimitiveDataBlob *vblob;

    vblob = private_rmBlobFromIndex(p, BLOB_NORMAL_INDEX);

    return(private_rmBlobGetNthings(vblob));
}

int
private_rmPrimitiveHackGetNcolors (RMprimitive *p) 
{
    RMprimitiveDataBlob *vblob;

    vblob = private_rmBlobFromIndex(p, BLOB_COLOR_INDEX);

    return(private_rmBlobGetNthings(vblob));
}


/* macros */
#define DOTABS(i,f)	{int j; for(j = 0; j < i; j++) fprintf((f),"\t");}
#define DOSPACES(i,f)	{int j; for(j = 0; j < i; j++) fprintf((f),"  ");}


/* PRIVATE
 *
 * private_rmPrintLight prints an RMlight
 * it is little used and probably incomplete.
 */
void
private_rmPrintLight (RMlight *l,
		      int level,
		      int i,
		      FILE *f)
{
    RMcolor4D a, d, s;
    
    DOTABS(level, f);
    fprintf(f, "Light Source %d is of type %s \n", i,
	    (rmLightGetType(l) == RM_LIGHT_POINT ? "RM_LIGHT_POINT" :
	    (rmLightGetType(l) == RM_LIGHT_DIRECTIONAL ? "RM_LIGHT_DIRECTIONAL" : "RM_LIGHT_SPOT")));

    rmLightGetColor(l, &a, &d, &s);
    DOTABS(level, f);
    fprintf(f, "\tLight color is (amb,diff,spec): (%f %f %f), (%f %f %f), (%f %f %f) \n", a.r, a.g, a.b, d.r, d.g, d.b, s.r, s.g, s.b);
}


/* PRIVATE
 *
 * private_rmPrintNode prints an RMnode
 * it is little used and probably incomplete
 */
static void
private_rmPrintNode (const RMnode *r,
		     int level,
		     int pmode,
		     FILE *f)
{
    char b1[128], b2[128];
    int  i, n;

    pmode = 0; 			/* foil compiler warning */
    /* will eventually use pmode as indicated below */

    /*
     * RM_PRINT_TERSE: prints the node name, and renderpass parms
     *   (vertex dims, transparency), number of prims and prim type,
     *   presence of scene parms, presence of render parms, etc.
     *
     * RM_PRINT_VERBOSE: prints everything.
     */

    DOSPACES(level, f);

    switch(private_rmNodeGetRenderpassOpacity(r))
	{
	case RM_RENDERPASS_OPAQUE:
	    strcpy(b1, "RM_RENDERPASS_OPAQUE");
	    break;

	case RM_RENDERPASS_TRANSPARENT:
	    strcpy(b1, "RM_RENDERPASS_TRANSPARENT");
	    break;

	case RM_RENDERPASS_ALL:
	    strcpy(b1, "RM_RENDERPASS_ALL");
	    break;

	default:
	    strcpy(b1, "Node Renderpass Opacity is UNDEFINED! ");
	    break;
	}

    switch(private_rmNodeGetRenderpassDims(r))
	{
	case RM_RENDERPASS_3D:
	    strcpy(b2, "RM_RENDERPASS_3D");
	    break;
	    
	case RM_RENDERPASS_2D:
	    strcpy(b2, "RM_RENDERPASS_2D");
	    break;
	    
	case RM_RENDERPASS_ALL:
	    strcpy(b2, "RM_RENDERPASS_ALL");
	    break;
	    
	default:
	    strcpy(b2, "RenderpassDims is UNDEFINED!");
	    break;
	}

    fprintf(f, "Name: <%s> %s %s\n", private_rmNodeGetName(r), b1, b2);

    /* print renderpassdims & renderpassopacity  */
    n = rmNodeGetNumPrims(r);
    DOSPACES(level, f);
    fprintf(f, "Node has %d primitives\n", n);

    for (i=0; i < n; i++)
    {
	RMprimitive *p;

	p = r->prims[i];	/* bad! use API */
	private_rmPrimTypeToString(private_rmPrimitiveGetType(p), b1);
	DOSPACES(level, f);
	fprintf(f, "Prim %d type is <%s> and has %d vertices, %d colors and %d normals \n", i, b1, private_rmPrimitiveHackGetNverts(p), private_rmPrimitiveHackGetNcolors(p), private_rmPrimitiveHackGetNnormals(p));
    }

    /* print scene parameters */
    if (r->scene_parms != NULL)
    {
	int i;

	for (i = 0; i < RM_MAX_LIGHTS; i++)
	{
	    RMlight *l;

	    l = r->scene_parms->lightSources[i];
	    if (l != NULL)
		private_rmPrintLight(l, level, i, f);
	}
	{
	    RMcamera2D *c;
	    if (rmNodeGetSceneCamera2D(r, &c) != RM_WHACKED)
	    {
		DOSPACES(level, f);
		fprintf(f," 2D camera present. \n");
	 	rmCamera2DDelete(c);
	    }	
	}
	{
	    RMcamera3D *c;
	    if (rmNodeGetSceneCamera3D(r, &c) != RM_WHACKED)
	    {
		DOSPACES(level, f);
		fprintf(f," 3D camera present. \n");
	 	rmCamera3DDelete(c);
	    }	
	}
	{
	    RMfog *fog;
	    if (rmNodeGetSceneFog(r, &fog) != RM_WHACKED)
	    {
		DOSPACES(level, f);
		fprintf(f," Fog present. \n");
	    }
	}
    } /* end scene parms */

    /* print polygon draw mode and line dashing style */
    
    {
	RMenum lineStyle;
	if (rmNodeGetLineStyle(r, &lineStyle) != RM_WHACKED)
	{
	    DOSPACES(level, f);
	    fprintf(f, "node line style: ");
	    switch (lineStyle)
	    {
		case RM_LINES_DASHED:
		    fprintf(f, "RM_LINES_DASHED \n");
		    break;
		case RM_LINES_DOTTED:
		    fprintf(f, "RM_LINES_DOTTED \n");
		    break;
		case RM_LINES_DOT_DASH:
		    fprintf(f, "RM_LINES_DOT_DASH \n");
		    break;
		case RM_LINES_DASH_DASH_DOT:
		    fprintf(f, "RM_LINES_DASH_DASH_DOT \n");
		    break;
		case RM_LINES_SOLID: /* just in case */
		    fprintf(f, "RM_LINES_SOLID \n");
		    break;
		default:
		    fprintf(f, "undefined line style %x \n", lineStyle);
		    break;
	    }
	}
    }

    if (r->transforms != NULL)
    {
	/*
	 * what we'll do here is just detect and report the presence of any
	 * of the transformations. We could easily print them if needed.
	 */
	DOSPACES(level, f);
	fprintf(f,"Transformations are present. \n");
	
    } /* end transforms */

    /* print fb clear operations */
    if (r->fbClear != NULL)
    {
	DOSPACES(level, f);
	fprintf(f, "fbClear options set: ");
	if (r->fbClear->bgColor != NULL)
	    fprintf(f, "bgColor <%g %g %g %g> ", r->fbClear->bgColor->r, r->fbClear->bgColor->g, r->fbClear->bgColor->b, r->fbClear->bgColor->a);
	if (r->fbClear->depthValue != NULL)
	    fprintf(f, "depthValue <%g> ", *(r->fbClear->depthValue));
	if (r->fbClear->bgImageTile)
	    fprintf(f, "bgImageTile present ");
	if (r->fbClear->depthImage)
	    fprintf(f, "bgDepthImage present ");
	fprintf(f,"\n");
    }
    
    DOSPACES(level, f);
    fprintf(f, "<%s> has %d children node(s).\n", private_rmNodeGetName(r), (int)private_rmNodeGetNumChildren(r));
    
    /* print surface props */
    /* print render props */
    /* print primitive info */
#if 0
    {
	DOTABS(level, f);
	if (r->scene_parms->viewport != NULL)
	{
	    DOTABS(level, f);
	    fprintf(f, " viewport xmin,ymin,xmax,ymax %g, %g, %g, %g \n",
		    r->scene_parms->viewport->xmin, r->scene_parms->viewport->ymin,
		    r->scene_parms.viewport->xmax, r->scene_parms->viewport->ymax);
	}

	if (r->scene_parms->camera3d)
	{
	    DOTABS(level, f);
	    fprintf(f, " node has a 3d camera. \n");
	}

	if (r->scene_parms->camera2d)
	{
	    DOTABS(level, f);
	    fprintf(f, " node has a 2d camera. \n");
	}

	if (r->scene_parms.background_image_tile)
	{
	    DOTABS(level, f);
	    fprintf(f, " node has a background image tile. \n");
	}

	if (r->scene_parms.background_color)
	{
	    DOTABS(level, f);
	    fprintf(f, " node has a background color. \n");
	}
    }

    if (flags & RM_PRINT_NPRIMS)
    {
	int i;

	fprintf(stderr, " RM_PRINT_NPRIMS code needs to be rewritten. \n")

	/* deprecated */
	RMmodel *g;

	for (i = 0; i < RM_MAX_PRIM_MODELS_PER_NODE; i++) /* this is constant... */
	{
	    g = rmNodeGetModel(r, i);

	    if (g)
	    {
		DOTABS(level, f);
		fprintf(f, "LOD %d, Nprims: %d \n", i, rmNodeGetModelNumPrims(r, i));
	    }
	}
    }
#endif
}


/* PRIVATE 
 *
 * private_rmPrintSceneGraph recursively traverses a scene graph
 * and prints stuff. it is little used and probably incomplete
 */
void
private_rmPrintSceneGraph (const RMnode *r,
			   int level,
			   RMenum pmode,
			   FILE *f)
{
    int     i;
    RMnode *c;

    if (pmode == RM_PRINT_VERBOSE)
    {
	rmWarning(" RM_PRINT_VERBOSE mode not quite ready yet. \n");
    }

    /* print name & stuff for this node */
    private_rmPrintNode(r, level, pmode, f);

    /* then print stuff for this node's children */
    for (i = 0; i < rmNodeGetNumChildren(r); i++)
    {
	c = rmNodeGetIthChild(r, i);
	private_rmPrintSceneGraph(c, (level + 1), pmode, f);
    }
}

/* PRIVATE */
RMenum *
private_rmEnumNew (int n)
{
    RMenum *t;

    t = (RMenum *)malloc(sizeof(RMenum)*n);

    return(t);
}


/* PRIVATE */
_surface_properties *
private_rmSurfacePropsNew ()
{
    _surface_properties *t;

    t = (_surface_properties *)malloc(sizeof(_surface_properties));
    memset(t, 0, sizeof(_surface_properties));

    return(t);
}


/* PRIVATE */
_rendermode_properties *
private_rmRenderModePropsNew (void)
{
    _rendermode_properties *t;

    t = (_rendermode_properties *)malloc(sizeof(_rendermode_properties));
    memset(t, 0, sizeof(_rendermode_properties));

    return(t);
}


/* PRIVATE */
void
private_initObjectTree (void)
{
    float      vp[4];
    RMvertex3D bmin, bmax;
    
    root = rmNodeNew("rmlib_root", RM_RENDERPASS_ALL, RM_RENDERPASS_ALL);

    /* tmp, wes */
    rmNodeSetNormalizeNormals(root, RM_TRUE);

    /*
     * the default things happen to the root node:
     * 1. a viewport is created which spans [0..1] in x,y (deprecated)
     * 2. set the bounding box to something which is uninitialized
     * 3. default material properties, rendering properties
     */

    vp[0] = RM_DEFAULT_VIEWPORT_XMIN;
    vp[1] = RM_DEFAULT_VIEWPORT_YMIN;
    vp[2] = RM_DEFAULT_VIEWPORT_XMAX;
    vp[3] = RM_DEFAULT_VIEWPORT_YMAX;

    rmNodeSetSceneViewport(root, vp);

    bmin.x = bmin.y = bmin.z = RM_MAXFLOAT;
    bmax.x = bmax.y = bmax.z = RM_MINFLOAT;
    rmNodeSetBoundingBox(root, &bmin, &bmax);

    /* set the default material properties */
    {
	extern float     RM_DEFAULT_SPECULAR_EXPONENT;
	extern RMcolor4D RM_DEFAULT_AMBIENT_COLOR,RM_DEFAULT_DIFFUSE_COLOR, RM_DEFAULT_SPECULAR_COLOR, RM_DEFAULT_UNLIT_COLOR;
	
	rmNodeSetAmbientColor(root, &RM_DEFAULT_AMBIENT_COLOR);
	rmNodeSetDiffuseColor(root, &RM_DEFAULT_DIFFUSE_COLOR);
	rmNodeSetSpecularColor(root, &RM_DEFAULT_SPECULAR_COLOR);
	rmNodeSetSpecularExponent(root, RM_DEFAULT_SPECULAR_EXPONENT);
	rmNodeSetUnlitColor(root, &RM_DEFAULT_UNLIT_COLOR);
	rmNodeSetNormalizeNormals(root, RM_FALSE);

	/* set the default rendering parms */
	rmNodeSetShader(root, RM_DEFAULT_RENDERMODE); /* RM_RENDERMODE_SMOOTH */

	rmNodeSetPolygonDrawMode(root, RM_FRONT_AND_BACK, RM_FILL);
	
	rmNodeSetPolygonCullMode(root, RM_CULL_NONE);
	rmNodeSetFrontFace(root, RM_CCW);
    }
    rmNodeSetLineStyle(root, RM_LINES_SOLID);
    rmNodeSetLineWidth(root, RM_LINEWIDTH_NARROW);
}

RMenum
private_computeCylindersBoundingBox(RMprimitive *p,
				    RMvertex3D *returnMin,
				    RMvertex3D *returnMax)
{
    /*
     * 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;

    /* 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, returnMin, returnMax);
	}
	else
	{
	    rmUnionBoundingBoxes(returnMin, returnMax, &s1min, &s1max, returnMin, returnMax);
	    rmUnionBoundingBoxes(returnMin, returnMax, &s2min, &s2max, returnMin, returnMax);
	}
    }
    return(RM_CHILL);
}


/* PRIVATE: perform a boolean op on the RMnode's attribMask field */
void
private_rmNodeAttribMask(RMnode *n,
			 GLuint bitmask,
			 RMenum op)
{
    if (op == RM_OR)
	n->attribMask |= bitmask;
    else if (op == RM_AND)
	n->attribMask &= bitmask;
    else if (op == RM_SET)
	n->attribMask = bitmask;
}

/* PRIVATE: return the RMnode's attribMask field */
GLuint
private_rmNodeGetAttribMask(const RMnode *n)
{
    return(n->attribMask);
}

/* PRIVATE: compute an OpenGL attribute mask representing all portions of
 OpenGL rendering state affected by scene parms, material props, etc.
 contained in the input RMnode. */
GLuint
private_rmNodeComputeAttribMask(const RMnode *n)
{
    /*
     * look at all scene parms, etc. in the node and compute an
     * OpenGL attribute mask
     */
    GLuint rval = 0;

    if (n->rprops != NULL)
    {
	if ((n->rprops->normalizeNormals != NULL) &&
	    (*(n->rprops->normalizeNormals) == RM_TRUE))
	    rval |= GL_ENABLE_BIT;
	
	if (n->rprops->shademodel != NULL)
	    rval |= GL_LIGHTING_BIT;

	if (n->rprops->front_face != NULL)
	    rval |= GL_POLYGON_BIT;

	if ((n->rprops->poly_mode_face != NULL) &&
	    (n->rprops->poly_mode_drawstyle != NULL))
	    rval |= GL_POLYGON_BIT;

	if ((n->rprops->linewidth != NULL) ||
	    (n->rprops->linestyle != NULL))
	    rval |= GL_LINE_BIT;

	if (n->rprops->pointsize != NULL)
	    rval |= GL_POINT_BIT;

	if (n->rprops->cull_mode != NULL)
	    rval |= GL_CURRENT_BIT;
    }

    if (n->sprops != NULL)
    {
	if ((n->sprops->ambient_color != NULL) ||
	    (n->sprops->diffuse_color != NULL) ||
	    (n->sprops->specular_color != NULL) ||
	    (n->sprops->specular_exponent != NULL))
	    rval |= GL_LIGHTING_BIT;
    }

    if ((n->sprops != NULL) && (n->sprops->unlit_color != NULL))
	rval |= (GL_CURRENT_BIT | GL_LIGHTING_BIT);

    if (n->scene_parms != NULL)
    {
	if ((n->scene_parms->cp0 != NULL) ||
	    (n->scene_parms->cp1 != NULL) ||
	    (n->scene_parms->cp2 != NULL) ||
	    (n->scene_parms->cp3 != NULL) ||
	    (n->scene_parms->cp4 != NULL) ||
	    (n->scene_parms->cp5 != NULL))
	    rval |= (GL_ENABLE_BIT | GL_TRANSFORM_BIT | GL_LIGHTING_BIT); 
	    /*	    rval |= (GL_ENABLE_BIT | GL_TRANSFORM_BIT);  */
	/*
	 * 6/5/05. For some reason, the color/lighting is also affected by
	 * the clip planes. The clipper RMdemo revealed the problem where
	 * the quad representing the clip plans was green when it should've
	 * been red or blue, depending upon its orientation. Adding
	 * the GL_LIGHTING_BIT to the above mask set when clip planes
	 * are present fixed the problem.
	 */

	if (n->scene_parms->fog != NULL)
	    rval |= GL_FOG_BIT;

	if ((n->scene_parms->lightSources != NULL) ||
	    (n->scene_parms->lmodel != NULL))
        {
	    /* check for non-null lightSources[i] */
	    int i;
	    for (i=0;i<RM_MAX_LIGHTS;i++)
		if (n->scene_parms->lightSources[i] != NULL)
		    break;

	    /* must have lmodel != NULL along with some non-NULL lightSource */
	    if (i < RM_MAX_LIGHTS)
		rval |= GL_LIGHTING_BIT;
	}

	if (n->scene_parms->haveAnyTextures == RM_TRUE)
	{
/*	    rval |= GL_ALL_ATTRIB_BITS;  for debug during CR development */
	    rval |= (GL_TEXTURE_BIT | GL_ENABLE_BIT);
#if 0
	    /* intial dev code, don't need anymore, wes 2/2005 */
	    for (i=0; i <= RM_MAX_MULTITEXTURES; i++)
	    {
		if (n->scene_parms->textures[i] != NULL)
		{
		    rval |= (GL_TEXTURE_BIT | GL_ENABLE_BIT);
		    break;
		}
	    }
#endif
	}

	if (n->scene_parms->viewport != NULL)
	{
	    float *vp = n->scene_parms->viewport;

	    rval |= GL_VIEWPORT_BIT;
	    /*
	     * only if viewport is something other than full window will we
	     * enable a scissor test. this same test is performed in the code
	     * that actually sets the OpenGL parms at draw time.
	     */
	    if ((vp[0] != 0.0) || (vp[1] != 0.0) || (vp[2] != 1.0) || (vp[3] != 1.0))
		rval |= GL_SCISSOR_BIT;
	}
    }

    if (n->fbClear != NULL)
    {	
	if ((n->fbClear->depthImage != NULL) ||
	    (n->fbClear->depthValue != NULL))
	    rval |= GL_DEPTH_BUFFER_BIT;
    }

    return(rval);
}

/* EOF */
