/*
 * 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: rmvmesh.c,v 1.8 2007/07/22 17:37:17 wes Exp $
 * Version: $Name: v180-alpha-02 $
 * $Revision: 1.8 $
 * $Log: rmvmesh.c,v $
 * Revision 1.8  2007/07/22 17:37:17  wes
 * Adding usrData and usrCallback parameters to all rmv routines
 *
 * Revision 1.7  2005/06/08 18:33:18  wes
 * Code cleanup to eliminate compiler warnings.
 *
 * Revision 1.6  2005/02/19 16:09:13  wes
 * Distro sync and consolidation.
 *
 * Revision 1.5  2005/01/23 17:11:02  wes
 * Copyright updated to 2005.
 *
 * Revision 1.4  2004/01/17 04:09:26  wes
 * Updated copyright line for 2004.
 *
 * Revision 1.3  2003/10/15 05:44:49  wes
 * Added a flipNormals parameter to rmvJ3MeshSurface, rmvJ3ScatterPoint,
 * and rmvJ3ComputeMeshNormals.
 *
 * Revision 1.2  2003/02/02 02:07:23  wes
 * Updated copyright to 2003.
 *
 * Revision 1.1.1.1  2003/01/28 02:15:23  wes
 * Manual rebuild of rm150 repository.
 *
 * Revision 1.8  2003/01/16 22:21:20  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.7  2002/04/30 19:40:21  wes
 * Updated copyright dates.
 *
 * Revision 1.6  2001/03/31 17:10:08  wes
 * v1.4.0-alpha-2 checkin.
 *
 * Revision 1.5  2000/10/03 11:39:17  wes
 * Contributions from jdb - proto cleanups.
 *
 * Revision 1.4  2000/04/20 16:17:45  wes
 * JDB modifications: code rearrangement, additional docs.
 *
 * Revision 1.3  2000/04/17 00:05:24  wes
 * Lots of documentation updates, courtesy of jdb.
 *
 * Revision 1.2  2000/02/29 23:43:59  wes
 * Compile warning cleanups.
 *
 * Revision 1.1.1.1  2000/02/28 21:29:40  wes
 * OpenRM 1.2 Checkin
 *
 * Revision 1.1.1.1  2000/02/28 17:18:48  wes
 * Initial entry - pre-RM120 release, source base for OpenRM 1.2.
 *
 */

#include <rm/rm.h>
#include <rm/rmv.h>
#include "rmvprivt.h"

#define CLAMP(d,r)	((d) == 0.0 ? (r) : (d))

/* PRIVATE declarations */
void private_rmBuildHorizonObjects(RMnode *n, int size, float *xc, float *yc, float *zc, float *data, float *data2, RMvisMap *vmap, int axis_offset_enum, float zerocrossing_value);
void private_rmv3DGetBarScale(RMvertex3D (*appgridfunc)(int i, int j, void *usrData), void *appGridUsrData, RMvertex3D *base_point, int iu, int usize, int iv, int vsize, float scale, int scale_enum, int axis_offset_enum, float *dx, float *dy, float *dz);
void private_rmvMakeGrid(RMvertex3D *gmin, RMvertex3D *gmax, RMvertex3D *ref_normal, RMnode *n, int usize, int vsize, RMenum linewidth, RMenum linestyle);

/*
 * ----------------------------------------------------
 * @Name rmvJ3ComputeMeshNormals
 @pstart
 RMenum rmvJ3ComputeMeshNormals (RMvertex3D *v,
			         RMvertex3D *normals,
				 int usize,
				 int vsize,
				 RMenum flipNormalsBool)
 @pend

 @astart 
 RMvertex3D *v - a handle to an array of size (usize * vsize)
    of RMvertex3D points (input).
			         
 RMvertex3D *normals - a handle to a caller-supplied array of size
    (usize * vsize) ofRMvertex3D points (modified).
				 
 int usize, vsize - int specifying the dimensions of the 2D grid of
    data (input).

 RMenum flipNormalsBool - set to RM_TRUE to "flip" the normals computed by
    the vector cross product of coordinate differences. Set to RM_FALSE
    to use the normals as computed.
 @aend

 @dstart

 Computes approximate surface normals from a two-dimensional grid of
 3D vertices.  The surface normals are apporximate using a Catmull-Rom
 spline basis, thus producing C^1 continuity across interior vertices.

 Note that although the array of input vertices are processed as
 though they were a 2D array, the array is not actually two-dimensional.

 Upon successful computation of the surface normals, RM_CHILL is
 returned.  Otherwise, RM_WHACKED is returned.

 @dend 
 * ----------------------------------------------------
 */
RMenum
rmvJ3ComputeMeshNormals (RMvertex3D *v,
			 RMvertex3D *normals,
			 int usize,
			 int vsize,
			 RMenum flipNormals)
{
    int        iu, iv;
    int        uplus, uminus, vplus, vminus;
    int        plus_u, minus_u, plus_v, minus_v; 
    double     mag;
    RMvertex3D p, r, c;
    RMvertex3D clast = {0.0, 0.0, 1.0};
    RMvertex3D *nSave = normals;

    /* error check on functions, etc. */
    {
	int s1, s2;

	s1 = RM_ASSERT((void *)v, "rmvJ3ComputeMeshNormals error: NULL input vertices");
	s2 = RM_ASSERT((void *)normals, "rmvJ3ComputeMeshNormals error: NULL  output normals arrays.");
	
	if ((s1 ==  RM_WHACKED) || (s2 == RM_WHACKED))
	    return(RM_WHACKED);
    }

    for (iv = 0; iv < vsize ;iv++)
    {
	if (iv == 0)
	    vminus = 0;
	else
	    vminus = iv - 1;

	if (iv == (vsize - 1))
	    vplus = vsize - 1;
	else
	    vplus = iv + 1;

	for (iu = 0; iu < usize; iu++)
	{
	    if (iu == 0)
		uminus = iu;
	    else
		uminus = iu - 1;

	    if (iu == (usize - 1))
		uplus = usize - 1;
	    else
		uplus = iu + 1;

	    plus_u = uplus + (iv * usize);
	    minus_u = uminus + (iv * usize);

	    plus_v = iu + (vplus * usize);
	    minus_v = iu + (vminus * usize);

	    rmVertex3DDiff((v + plus_u), (v + minus_u), &p);
	    rmVertex3DDiff((v+ plus_v), (v + minus_v), &r);
	    
	    rmVertex3DCross(&p, &r, &c); 
	    rmVertex3DMagNormalize(&c, &mag);

	    if (fabs(mag) < 0.0001)
		VCOPY(&clast, &c);
	    else
		VCOPY(&c, &clast);

	    VCOPY(&c, normals);

	    /*	    fprintf(stderr," normal %d, %d = %g, %g, %g \n", iu, iv, normals->x, normals->y, normals->z); */
	    normals++;
	}
    }

    if (flipNormals == RM_TRUE)
    {
	normals = nSave;
	for (iu = 0; iu < usize*vsize; iu++)
	{
	    normals[iu].x *= -1.0F;
	    normals[iu].y *= -1.0F;
	    normals[iu].z *= -1.0F;
	}
    }
    return(RM_CHILL);
}


/*
 * ----------------------------------------------------
 * @Name rmv3DRuledBox
 @pstart
 void rmv3DRuledBox (RMvertex3D *grid_min,
	             RMvertex3D *grid_max,
		     int iusize,
		     int ivsize,
		     int iwsize,
		     RMenum linewidth,
		     RMenum linestyle,
		     RMenum backface_cull_enable,
		     RMnode *n)
 @pend

 @astart
 RMvertex3D *grid_min, *grid_max - handles to the minimum and maximum
    points of the bounding box for the ruled box (input).
		     
 int iusize, ivsize, iwsize - int specifying the (u, v, w) rulings in
    each dimension (input).
		     
 RMenum linewidth_enum - an RMenum specifying the line width.  Must be
    one of RM_LINEWIDTH_NARROW, RM_LINEWIDTH_MEDIUM,
    RM_LINEWIDTH_HEAVY, or RM_LINEWIDTH_[1..8] (input).

 RMenum linestyle_enum - an RMenum specifying the lline style.  Must
    be one of RM_LINES_SOLID, RM_LINES_DASHED, RM_LINES_DOTTED,
    RM_LINES_DOT_DASH, or RM_LINES_DASH_DASH_DOT (input).
			 
 RMenum backface_cull_enable - an RMenum specifying whether or not to
    enable back face culling for the RMnode.  Must be one of RM_TRUE
    or RM_FALSE (input).
		     
 RMnode *n - a handle to an RMnode (modified).
 @aend

 @dstart

 Creates a ruled wireframe enclosure for annotating 3D structured,
 uniform grids.  By enabling back face culling, as a visualization is
 moved around, the appropriate sides of the enclosure will be culled,
 thus revealing the dataset with a "ruled backstop".

 Upon success, RM_CHILL is returned. Otherwise, RM_WHACKED is returned,
 and the return RMnode remains unmodified.

 @dend
 * ----------------------------------------------------
 */
RMenum
rmv3DRuledBox (RMvertex3D *grid_min,
	       RMvertex3D *grid_max,
	       int iusize,
	       int ivsize,
	       int iwsize,
	       RMenum linewidth,
	       RMenum linestyle,
	       RMenum backface_cull_enable,
	       RMnode *n)
{
    int        usize, vsize, ulimit, vlimit;
    RMnode    *b1, *b2, *b3, *b4, *b5, *b6;
    RMvertex3D gmin, gmax, ref_norm;

    /* error check on functions, etc. */
    {
	int s1, s2, s3, s4;

	s1 = RM_ASSERT((void *)grid_min,"rmv3DRuledBox error: NULL grid_min parameter");
	s2 = RM_ASSERT((void *)grid_max, "rmv3DRuledBox error: NULL grid_max parmeter");
	s3 = RM_ASSERT((void *)n, "rmv3DRuledBox error: NULL RMnode output parameter ");
	s4 = RM_CHILL; /* ? */
	
	if ((s1 ==  RM_WHACKED) || (s2 == RM_WHACKED) || (s3 == RM_WHACKED) || (s4 == RM_WHACKED))
	    return (RM_WHACKED);
    }
    
    usize = iusize;
    vsize = ivsize;

    /* do plane at w == 0 plane */
    gmin = *grid_min;
    gmax = *grid_max;
    gmax.z = gmin.z;
    ulimit = iusize;
    vlimit = ivsize;
    
    ref_norm.x = 0.0;
    ref_norm.y = 0.0;
    ref_norm.z = -1.0;
    
    b1 = rmNodeNew("backstop-w-0", RM_RENDERPASS_3D, RM_RENDERPASS_OPAQUE);
    private_rmvMakeGrid(&gmin, &gmax, &ref_norm, b1, ulimit, vlimit, linewidth, linestyle);

    rmNodeSetPolygonDrawMode(b1, RM_FRONT, RM_LINE); /* busted? */
    rmNodeSetShader(b1, RM_SHADER_NOLIGHT);

    if (backface_cull_enable == RM_TRUE)
	rmNodeSetPolygonCullMode(b1, RM_CULL_BACK);
    rmNodeSetFrontFace(b1, RM_CCW);

    rmNodeAddChild(n, b1);

    /* do plane at w=1  */
    gmin = *grid_min;
    gmax = *grid_max;
    gmin.z = gmax.z;
    ulimit = iusize;
    vlimit = ivsize;
    
    ref_norm.x = 0.0;
    ref_norm.y = 0.0;
    ref_norm.z = 1.0;
    
    b2 = rmNodeNew("backstop-w-1", RM_RENDERPASS_3D, RM_RENDERPASS_OPAQUE);
    private_rmvMakeGrid(&gmin, &gmax, &ref_norm, b2, ulimit, vlimit, linewidth, linestyle);
    
    rmNodeSetPolygonDrawMode(b2, RM_FRONT_AND_BACK, RM_LINE);
    if (backface_cull_enable == RM_TRUE)
	rmNodeSetPolygonCullMode(b2, RM_CULL_BACK);
    rmNodeSetShader(b2, RM_SHADER_NOLIGHT);
    rmNodeSetFrontFace(b2, RM_CW);

    rmNodeAddChild(n, b2);

    /* do plane at u == 0 plane */
    gmin = *grid_min;
    gmax = *grid_max;
    gmax.x = gmin.x;
    ulimit = ivsize;
    vlimit = iwsize;
    
    ref_norm.x = 1.0;
    ref_norm.y = 0.0;
    ref_norm.z = 0.0;
    
    b3 = rmNodeNew("backstop-u-0", RM_RENDERPASS_3D, RM_RENDERPASS_OPAQUE);
    private_rmvMakeGrid(&gmin, &gmax, &ref_norm, b3, ulimit, vlimit, linewidth, linestyle);
    
    rmNodeSetPolygonDrawMode(b3, RM_FRONT_AND_BACK, RM_LINE);
    if (backface_cull_enable == RM_TRUE)
	rmNodeSetPolygonCullMode(b3, RM_CULL_BACK);
    rmNodeSetShader(b3, RM_SHADER_NOLIGHT);
    rmNodeSetFrontFace(b3, RM_CCW);

    rmNodeAddChild(n, b3);

    /* do plane at u == 1 plane */
    gmin = *grid_min;
    gmax = *grid_max;
    gmin.x = gmax.x;
    ulimit = ivsize;
    vlimit = iwsize;
    
    ref_norm.x = -1.0;
    ref_norm.y = 0.0;
    ref_norm.z = 0.0;
    
    b4 = rmNodeNew("backstop-u-1", RM_RENDERPASS_3D, RM_RENDERPASS_OPAQUE);
    private_rmvMakeGrid(&gmin, &gmax, &ref_norm, b4, ulimit, vlimit, linewidth, linestyle);
    
    rmNodeSetPolygonDrawMode(b4, RM_FRONT_AND_BACK, RM_LINE);
    if (backface_cull_enable == RM_TRUE)
	rmNodeSetPolygonCullMode(b4, RM_CULL_BACK);
    rmNodeSetShader(b4, RM_SHADER_NOLIGHT);
    rmNodeSetFrontFace(b4, RM_CW);

    rmNodeAddChild(n, b4);

    /* do plane at v == 0 plane */
    gmin = *grid_min;
    gmax = *grid_max;
    gmax.y = gmin.y;
    ulimit = iusize;
    vlimit = iwsize;
    
    ref_norm.x = 0.0;
    ref_norm.y = 1.0;
    ref_norm.z = 0.0;
    
    b5 = rmNodeNew("backstop-v-0", RM_RENDERPASS_3D, RM_RENDERPASS_OPAQUE);
    private_rmvMakeGrid(&gmin, &gmax, &ref_norm, b5, ulimit, vlimit, linewidth, linestyle);
    
    rmNodeSetPolygonDrawMode(b5, RM_FRONT_AND_BACK, RM_LINE);
    if (backface_cull_enable == RM_TRUE)
	rmNodeSetPolygonCullMode(b5, RM_CULL_BACK);
    rmNodeSetShader(b5, RM_SHADER_NOLIGHT);
    rmNodeSetFrontFace(b5, RM_CW);

    rmNodeAddChild(n, b5);

    /* do plane at v == 1 plane */
    gmin = *grid_min;
    gmax = *grid_max;
    gmin.y = gmax.y;
    ulimit = iusize;
    vlimit = iwsize;
    
    ref_norm.x = 0.0;
    ref_norm.y = 1.0;
    ref_norm.z = 0.0;
    
    b6 = rmNodeNew("backstop-v-1", RM_RENDERPASS_3D, RM_RENDERPASS_OPAQUE);
    private_rmvMakeGrid(&gmin, &gmax, &ref_norm, b6, ulimit, vlimit, linewidth, linestyle);
    
    rmNodeSetPolygonDrawMode(b6, RM_FRONT_AND_BACK, RM_LINE);
    if (backface_cull_enable == RM_TRUE)
	rmNodeSetPolygonCullMode(b6, RM_CULL_BACK);
    rmNodeSetShader(b6, RM_SHADER_NOLIGHT);
    rmNodeSetFrontFace(b6, RM_CCW);

    rmNodeAddChild(n, b6);

    return(RM_CHILL);
}


/*
 * ----------------------------------------------------
 * @Name rmvJ3Bar
 @pstart
 RMenum rmvJ3Bar (RMvertex3D (*appgridfunc)(int i, int j, void *usrData),
                  void *appGridUsrData,
	          float (*appdatafunc)(int i, int j, void *usrData),
                  void *appDataUsrData,
		  float (*appdata2func)(int i, int j, void *usrData),
                  void *appData2UsrData,
		  RMvisMap *vmap,
		  int axis_offset_enum,
		  int iusize,
		  int ivsize,
		  float scale,
		  int scaling_flag,
		  RMnode *n)
 @pend

 @astart

 RMvertex3D (*appgridfunc)(int i, int j, void *appGridUsrData) - a handle to a
    caller-supplied function that returns an RMvertex3D (x, y, z)
    corresponding to the grid point (i, j) (input).
	          
 void *appGridUsrData - (input, optional) you can pass in a void * to
    user data; this void * will be provided as a parameter to the user-defined
    grid evalution callback. If you don't want to pass in a void * to any
    user data for the grid evaluation callback, pass in a NULL for this
    parameter.

 float (*appdatafunc)(int i, int j, void *usrData) - a handle to a
    caller-supplied function that returns a float, which is the scalar
    value at the grid point (i, j) (input).

 void *appDataUsrData - (optional, input) you can pass in a void * to user
    data; this void * will be provided as a parameter to the user-defined
    data field evaluation callback. If you don't want to pass in a void *
    to any user data for the primary data field grid evaluation callback,
    pass in a NULL for this parameter.
			   
 float (*appdata2func)(int i, int j, void *usrData) -- (input, optional)
    A handle to a caller-supplied function that returns a float, which is the
    scalar value at the grid point (i, j) of the optional secondary data field.
    RMV uses the float returned by appdata2func in conjunction with the
    RMvismap to compute vertex color.

 void *appData2UsrData - (optional, input) you can pass in a void * to user
    data; this void * will be provided as a parameter to the user-defined
    secondary data field evaluation callback. If you don't want to pass in
    a void * to any user data for the secondary data field evaluation
    callback, pass in a NULL for this parameter.
    
 RMvisMap *vmap - a handle to an RMvisMap object (input).
		  
 int axis_offset_enum - an integer specifying in which axis to offset
    the glyph.  Must be one of RMV_XAXIS_OFFSET, RMV_YAXIS_OFFSET, or
    RMV_ZAXIS_OFFSET (input).

 int iusize, ivsize - int specifying the dimensions of the 2D grid of
    data (input).
		  
 float scale - a float specifying the scaling factor for the bars.
    When the scaling mode is RMV_SCALE_ABSOLUTE, this parameter
    dictates the "diameter" of each bar directly.  When the mode is
    RMV_SCALE_RELATIVE, the product of this parameter and the adjacent
    grid spacing determines the "diameter" of the bar (input).
		 
 int scaling_flag - an integer specifying the scaling type.  Must be
    one of RMV_SCALE_ABSOLUTE OR RMV_SCALE_RELATIVE (input).
			   
 RMnode *n - a handle to an RMnode (modified).
 @aend

 @dstart

 Creates a 3D bar plot from data, with the data shown as 3D bars.  The
 bars are a single color, the RMnode default color at render time.
 The bar plot can also be colored via the secondary data function and
 the RMvismap.

 The position of each bar is centered on the underlying 2D grid
 defined by the grid function, its height determined by the data
 function, and its "diameter" is controlled with the scaling mode and
 scale parameter.  The axis offset determines the placement of the
 bars relative to the underlying 2D grid.

 Upon success, RM_CHILL is returned and the 3D filled bar plot
 primitives are added to the RMnode.  Otherwise, RM_WHACKED is
 returned.

 @dend
 * ----------------------------------------------------
 */
RMenum
rmvJ3Bar (RMvertex3D (*appgridfunc)(int i, int j, void *usrData),
	  void *appGridUsrData,
	  float (*appdatafunc)(int i, int j, void *usrData),
	  void *appDataUsrData,
	  float (*appdata2func)(int i, int j, void *usrData),
	  void *appData2UsrData,
	  RMvisMap *vmap,
	  int axis_offset_enum,
	  int iusize,
	  int ivsize,
	  float scale,
	  int scaling_flag,
	  RMnode *n)

{
    int          i, j, usize, vsize, nboxes, index, nverts_per_box;
    int          offset = 0;
    int          dest_offset = 0;
    float        smallnum = 0.1;	/* this needs to be computed analytically */
    RMvertex3D  *boxverts;
    RMcolor4D   *boxcolors;
    RMprimitive *q;
    RMvertex3D   p1, p2;
    RMvertex3D   bmin, bmax;

    /* error check on functions, etc. */
    {
	int s1, s2, s3, s4;

	s1 = RM_ASSERT((void *)n, "rmv3DBar error: NULL RMnode for return parameter");
	s2 = RM_ASSERT((void *)appgridfunc, "rmv3DBar error: NULL app grid callback");
	s3 = RM_ASSERT((void *)appdatafunc, "rmv3DBar error: NULL app data callback ");
	s4 = RM_CHILL;
	if (!(((vmap != NULL) && (appdata2func != NULL)) || ((vmap == NULL) && (appdata2func == NULL))))
	    s4 = RM_ASSERT((void *)NULL, "rmv3DBar error: the vismap and secondary data callback function must BOTH be NULL or defined.");

	if ((s1 ==  RM_WHACKED) || (s2 == RM_WHACKED) || (s3 == RM_WHACKED) || (s4 == RM_WHACKED))
	    return(RM_WHACKED);
    }
    
    q = rmPrimitiveNew(RM_BOX3D);

    usize = iusize;
    vsize = ivsize;
    
    nboxes = usize * vsize;
    nverts_per_box =  2;

    boxverts = rmVertex3DNew(nverts_per_box * nboxes);

    if (vmap != NULL)
	boxcolors = rmColor4DNew(nboxes);
    else
	boxcolors = NULL;

    index = 0;
    dest_offset = 0;

    for (j = 0; j < vsize; j++)
    {
	for (i = 0; i < usize; i++)
	{
	    float dx, dy, dz;
	    float data;

	    /* the base vertex will be the same as the grid point */
	    p2 = p1 = (*appgridfunc)(i, j, appGridUsrData);
	    data = (*appdatafunc)(i, j, appDataUsrData);

	    switch(axis_offset_enum)
	       {
	       case RMV_XAXIS_OFFSET:
		  p2.x += CLAMP(data, smallnum);
		  break;
		  
	       case RMV_YAXIS_OFFSET:
		  p2.y += CLAMP(data, smallnum);
		  break;
		  
	       case RMV_ZAXIS_OFFSET:
		  p2.z += CLAMP(data, smallnum);
		  break;
		  
	       default: /* bogus axis offset enum */
		  break;
	       }
	    
	    private_rmv3DGetBarScale(appgridfunc, appGridUsrData, &p1, i, usize, j, vsize, scale, scaling_flag, axis_offset_enum, &dx, &dy, &dz);
	    
	    bmin.x = RM_MIN(p1.x, p2.x);
	    bmin.y = RM_MIN(p1.y, p2.y);
	    bmin.z = RM_MIN(p1.z, p2.z);

	    bmax.x = RM_MAX(p1.x, p2.x);
	    bmax.y = RM_MAX(p1.y, p2.y);
	    bmax.z = RM_MAX(p1.z, p2.z);

	    bmin.x -= dx;
	    bmax.x += dx;
	    bmin.y -= dy;
	    bmax.y += dy;
	    bmin.z -= dz;
	    bmax.z += dz;

	    VCOPY(&bmin,boxverts + dest_offset);
	    dest_offset++;
	    VCOPY(&bmax,boxverts + dest_offset);
	    dest_offset++;

	    if (boxcolors)
	    {
		int   k;
		float d2;

		d2 = (*appdata2func)(i, j, appData2UsrData);
		k = rmVismapIndexFromData(vmap, d2);
		rmVismapGetColor4D(vmap, k, (boxcolors + offset));
		offset++;
	    }
	}
    }

    rmPrimitiveSetVertex3D(q, (nboxes * nverts_per_box), boxverts, RM_COPY_DATA, NULL);

    if (boxcolors != NULL)
    {
	rmPrimitiveSetColor4D(q, nboxes, boxcolors, RM_COPY_DATA, NULL);
	rmColor4DDelete(boxcolors);
    }

    /* now, add the new primitive onto the node */
    rmNodeAddPrimitive(n, q);

    /* compute the bounding box and center point for the node */
    {
	RMvertex3D tmin, tmax, center;
	
	rmPointMinMax((float *)boxverts, (nboxes * nverts_per_box), 3, sizeof(RMvertex3D), &tmin, &tmax);

	rmNodeSetBoundingBox(n, &tmin, &tmax);
	
	center.x = tmin.x + (0.5 * (tmax.x - tmin.x));
	center.y = tmin.y + (0.5 * (tmax.y - tmin.y));
	center.z = tmin.z + (0.5 * (tmax.z - tmin.z));

	rmNodeSetCenter(n, &center);
    }
    rmVertex3DDelete (boxverts);

    /* turn on backface culling */
    rmNodeSetPolygonCullMode(n, RM_CULL_BACK);
    rmNodeSetFrontFace(n, RM_CCW);

    return(RM_WHACKED);
}


/*
 * ----------------------------------------------------
 * @Name rmvJ3BarOutline
 @pstart
 RMenum rmvJ3BarOutline (RMvertex3D (*appgridfunc)(int i, int j, void *usrData),
                         void *appGridUsrData,
		         float (*appdatafunc)(int i, int j, void *usrData),
                         void *appDataUsrData,
			 float (*appdata2func)(int i, int j, void *usrData),
                         void *appData2UsrData,
			 RMvisMap *vmap,
			 int axis_offset_enum,
			 int iusize,
			 int ivsize,
			 float scale,
			 int scaling_flag,
			 int linewidth_enum,
			 int linestyle_enum,
			 RMnode *n)
 @pend

 @astart
 RMvertex3D (*appgridfunc)(int i, int j, void *usrData) - a handle to a
    caller-supplied function that returns an RMvertex3D (x, y, z)
    corresponding to the grid point (i, j) (input).
		         
 void *appGridUsrData - (input, optional) you can pass in a void * to
    user data; this void * will be provided as a parameter to the user-defined
    grid evalution callback. If you don't want to pass in a void * to any
    user data for the grid evaluation callback, pass in a NULL for this
    parameter.

 float (*appdatafunc)(int i, int j, void *usrData) - a handle to a caller-supplied function
    that returns a float which is the scalar value at the grid point
    (i, j) (input).
			   
 void *appDataUsrData - (optional, input) you can pass in a void * to user
    data; this void * will be provided as a parameter to the user-defined
    data field evaluation callback. If you don't want to pass in a void *
    to any user data for the primary data field grid evaluation callback,
    pass in a NULL for this parameter.
    
 float (*appdata2func)(int i, int j, void *usrData) -- (input, optional)
    A handle to a caller-supplied function that returns a float, which is the
    scalar value at the grid point (i, j) of the optional secondary data field.
    RMV uses the float returned by appdata2func in conjunction with the
    RMvismap to compute vertex color.

 void *appData2UsrData - (optional, input) you can pass in a void * to user
    data; this void * will be provided as a parameter to the user-defined
    secondary data field evaluation callback. If you don't want to pass in
    a void * to any user data for the secondary data field evaluation
    callback, pass in a NULL for this parameter.
    
 RMvisMap *vmap - a handle to an RMvisMap object (input).
			 
 int axis_offset_enum - an integer specifying in which axis to offset
    the glyph.  Must be one of RMV_XAXIS_OFFSET, RMV_YAXIS_OFFSET, or
    RMV_ZAXIS_OFFSET (input).

 int iusize, ivsize - int specifying the dimensions of the 2D grid of
    data (input).
			 
 float scale - a float specifying the scaling factor for the bars.
    When the scaling mode is RMV_SCALE_ABSOLUTE, this parameter
    dictates the "diameter" of each bar directly.  When the mode is
    RMV_SCALE_RELATIVE, the product of this parameter and the adjacent
    grid spacing determines the "diameter" of the bar (input).
		 
 int scaling_flag - an integer specifying the scaling type.  Must be
    one of RMV_SCALE_ABSOLUTE OR RMV_SCALE_RELATIVE (input).
			   
 RMenum linewidth_enum - an RMenum specifying the line width.  Must be
    one of RM_LINEWIDTH_NARROW, RM_LINEWIDTH_MEDIUM,
    RM_LINEWIDTH_HEAVY, or RM_LINEWIDTH_[1..8] (input).

 RMenum linestyle_enum - an RMenum specifying the lline style.  Must
    be one of RM_LINES_SOLID, RM_LINES_DASHED, RM_LINES_DOTTED,
    RM_LINES_DOT_DASH, or RM_LINES_DASH_DASH_DOT (input).
			 
 RMnode *n - a handle to an RMnode (modified).
 @aend

 @dstart

 Creates a 3D bar outline plot from data, with the data shown as 3D
 bars.  The bar outlines are a single color, the RMnode default color
 at render time.  The bar plot can also be colored via the secondary
 data function and the RMvismap.

 The position of each bar is centered on the underlying 2D grid
 defined by the grid function, its height determined by the data
 function, and its "diameter" is controlled with the scaling mode and
 scale parameter.  The axis offset determines the placement of the
 bars relative to the underlying 2D grid.

 Upon success, RM_CHILL is returned and the 3D bar outline plot
 primitives are added to the RMnode.  Otherwise, RM_WHACKED is
 returned.

 @dend
 * ----------------------------------------------------
 */
RMenum
rmvJ3BarOutline (RMvertex3D (*appgridfunc)(int i, int j, void *usrData),
		 void *appGridUsrData,
		 float (*appdatafunc)(int i, int j, void *usrData),
		 void *appDataUsrData,
		 float (*appdata2func)(int i, int j, void *usrData),
		 void *appData2UsrData,
		 RMvisMap *vmap,
		 int axis_offset_enum,
		 int iusize,
		 int ivsize,
		 float scale,
		 int scaling_flag,
		 RMenum linewidth_enum,
		 RMenum linestyle_enum,
		 RMnode *n)
{

    int          i, j, usize, vsize, nboxes, index, nverts_per_box;
    int          offset = 0;
    float        smallnum = 0.1;
    RMvertex3D  *boxverts;
    RMcolor4D   *boxcolors;
    RMprimitive *q;
    RMvertex3D   p1, p2;
    RMvertex3D   bmin, bmax;
    RMcolor4D    rgb;

    /* error check on functions, etc. */
    {
	int s1, s2, s3, s4;

	s1 = RM_ASSERT((void *)n, "rmv3DBarOutline error: NULL RMnode for return parameter");
	s2 = RM_ASSERT((void *)appgridfunc, "rmv3DBarOutline error: NULL app grid callback");
	s3 = RM_ASSERT((void *)appdatafunc, "rmv3DBarOutline error: NULL app data callback ");
	s4 = RM_CHILL;
	if (!(((vmap != NULL) && (appdata2func != NULL)) || ((vmap == NULL) && (appdata2func == NULL))))
	    s4 = RM_ASSERT((void *)NULL, "rmv3DBarOutline error: the vismap and secondary data callback function must BOTH be NULL or defined.");

	if ((s1 ==  RM_WHACKED) || (s2 == RM_WHACKED) || (s3 == RM_WHACKED) || (s4 == RM_WHACKED))
	    return(RM_WHACKED);
    }
    
    q = rmPrimitiveNew(RM_LINES);

    usize = iusize;
    vsize = ivsize;
    
    nboxes = usize * vsize;
    nverts_per_box = 24;

    boxverts = rmVertex3DNew(nverts_per_box * nboxes);

    if (vmap != NULL && appdata2func != NULL)
	boxcolors = rmColor4DNew(nverts_per_box*nboxes);
    else
	boxcolors =  NULL;

    index = 0;

    for (j = 0; j < vsize; j++)
    {
	for (i = 0; i < usize; i++)
	{
	    float dx, dy, dz;
	    float data;

	    /* the base vertex will be the same as the grid point */
	    p2 = p1 = (*appgridfunc)(i, j, appGridUsrData);
	    data = (*appdatafunc)(i, j, appDataUsrData);

	    switch(axis_offset_enum)
	       {
	       case RMV_XAXIS_OFFSET:
		  p2.x += CLAMP(data, smallnum);
		  break;
		  
	       case RMV_YAXIS_OFFSET:
		  p2.y += CLAMP(data, smallnum);
		  break;
		  
	       case RMV_ZAXIS_OFFSET:
		  p2.z += CLAMP(data, smallnum);
		  break;
		  
	       default: /* bogus axis offset enum */
		  break;
	       }

	    private_rmv3DGetBarScale(appgridfunc, appGridUsrData, &p1, i, usize, j, vsize, scale, scaling_flag, axis_offset_enum, &dx, &dy, &dz);
	    
	    bmin.x = RM_MIN(p1.x, p2.x);
	    bmin.y = RM_MIN(p1.y, p2.y);
	    bmin.z = RM_MIN(p1.z, p2.z);

	    bmax.x = RM_MAX(p1.x, p2.x);
	    bmax.y = RM_MAX(p1.y, p2.y);
	    bmax.z = RM_MAX(p1.z, p2.z);

	    bmin.x -= dx;
	    bmax.x += dx;
	    bmin.y -= dy;
	    bmax.y += dy;
	    bmin.z -= dz;
	    bmax.z += dz;

	    if (boxcolors)
	    {
		int   k;
		float d2;

		d2 = (*appdata2func)(i, j, appData2UsrData);
		k = rmVismapIndexFromData(vmap, d2);
		rmVismapGetColor4D(vmap, k, &rgb);
		offset++;

	    }
	    private_AxisAlignedWireBox(&bmin, &bmax, boxverts, &index, &rgb, boxcolors);
	}
    }

    rmNodeSetLineWidth(n, linewidth_enum);
    rmNodeSetLineStyle(n, linestyle_enum);

    rmPrimitiveSetVertex3D(q, (nboxes * nverts_per_box), boxverts, RM_COPY_DATA, NULL);

    if (boxcolors != NULL)
    {
	rmPrimitiveSetColor4D(q, (nboxes * nverts_per_box), boxcolors, RM_COPY_DATA, NULL);
	rmColor4DDelete(boxcolors);

    }

    /* now, add the new primitive onto the node */
    rmNodeAddPrimitive(n, q);

    /* compute the bounding box and center point for the node */
    rmNodeComputeBoundingBox(n);
    {
        RMvertex3D bmin, bmax, center;

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

	rmNodeSetCenter(n, &center);
    }
    rmVertex3DDelete (boxverts);
    return(RM_CHILL);
}


/*
 * ----------------------------------------------------
 * @Name rmvJ3Impulse
 @pstart
 RMenum rmvJ3Impulse (RMvertex3D (*appgridfunc)(int i, int j, void *usrData),
                      void *appGridUsrData,
	              float (*appdatafunc)(int i, int j, void *usrData),
                      void *appDataUsrData,
		      float (*appdata2func)(int i, int j, void *usrData),
                      void *appData2UsrData,
		      RMvisMap *vmap,
		      int axis_offset_enum,
		      int usize,
		      int vsize,
		      int linewidth_enum,
		      int linestyle_enum,
		      RMnode *n)
 @pend

 @astart 
 RMvertex3D (*appgridfunc)(int i, int j, void *usrData) - a handle to a
    caller-supplied function that returns an RMvertex3D (x, y, z)
    corresponding to the grid point (i, j) (input).
	              
 void *appGridUsrData - (input, optional) you can pass in a void * to
    user data; this void * will be provided as a parameter to the user-defined
    grid evalution callback. If you don't want to pass in a void * to any
    user data for the grid evaluation callback, pass in a NULL for this
    parameter.
    
 float (*appdatafunc)(int i, int j, void *usrData) - a handle to a caller-supplied function
    that returns a float which is the scalar value at the grid point
    (i, j) (input).
			   
 void *appDataUsrData - (optional, input) you can pass in a void * to user
    data; this void * will be provided as a parameter to the user-defined
    data field evaluation callback. If you don't want to pass in a void *
    to any user data for the primary data field grid evaluation callback,
    pass in a NULL for this parameter.
    
 float (*appdata2func)(int i, int j, void *usrData) -- (input, optional)
    A handle to a caller-supplied function that returns a float, which is the
    scalar value at the grid point (i, j) of the optional secondary data field.
    RMV uses the float returned by appdata2func in conjunction with the
    RMvismap to compute vertex color.

 void *appData2UsrData - (optional, input) you can pass in a void * to user
    data; this void * will be provided as a parameter to the user-defined
    secondary data field evaluation callback. If you don't want to pass in
    a void * to any user data for the secondary data field evaluation
    callback, pass in a NULL for this parameter.
    
 RMvisMap *vmap - a handle to an RMvisMap object (input).
		      
 int axis_offset_enum - an integer specifying in which axis to offset
    the glyph.  Must be one of RMV_XAXIS_OFFSET, RMV_YAXIS_OFFSET, or
    RMV_ZAXIS_OFFSET (input).
		      
 int usize, vsize - int specifying the dimensions of the 2D grid of
    data (input).
		      
 RMenum linewidth_enum - an RMenum specifying the line width.  Must be
    one of RM_LINEWIDTH_NARROW, RM_LINEWIDTH_MEDIUM,
    RM_LINEWIDTH_HEAVY, or RM_LINEWIDTH_[1..8] (input).

 RMenum linestyle_enum - an RMenum specifying the lline style.  Must
    be one of RM_LINES_SOLID, RM_LINES_DASHED, RM_LINES_DOTTED,
    RM_LINES_DOT_DASH, or RM_LINES_DASH_DASH_DOT (input).
			 
 RMnode *n - a handle to an RMnode (modified).
 @aend

 @dstart

 Creates a 3D impulse plot from data, with the data shown as spikes.
 The impulse plot is a single color, the RMnode default color at
 render time.  The impulse spikes can also be colored via the
 secondary data function and the RMvismap.  The (i, j)th impulse spike
 is a combination of the grid point, the primary data function, and
 the axis offset chosen.

 Upon success, RM_CHILL is returned and the 3D impulse plot primitives
 are added to the RMnode.  Otherwise, RM_WHACKED is returned.

 @dend
 * ----------------------------------------------------
 */
RMenum
rmvJ3Impulse (RMvertex3D (*appgridfunc)(int i, int j, void *usrData),
	      void *appGridUsrData,
	      float (*appdatafunc)(int i, int j, void *usrData),
	      void *appDataUsrData,
	      float (*appdata2func)(int i, int j, void *usrData),
	      void *appData2UsrData,
	      RMvisMap *vmap,
	      int axis_offset_enum,
	      int usize,
	      int vsize,
	      RMenum linewidth_enum,
	      RMenum linestyle_enum,
	      RMnode *n)
{
    int          i, j, indx, nverts;
    RMvertex3D  *v;
    RMcolor4D   *c;
    RMprimitive *q;

    /* error check on functions, etc. */
    {
	int s1, s2, s3, s4;

	s1 = RM_ASSERT((void *)n, "rmvI3Impulse error: NULL RMnode for return parameter");
	s2 = RM_ASSERT((void *)appgridfunc, "rmvI3Impulse error: NULL app grid callback");
	s3 = RM_ASSERT((void *)appdatafunc, "rmvI3Impulse error: NULL app data callback ");
	s4 = RM_CHILL;
	if (!(((vmap != NULL) && (appdata2func != NULL)) || ((vmap == NULL) && (appdata2func == NULL))))
	    s4 = RM_ASSERT((void *)NULL, "rmvI3Impulse error: the vismap and secondary data callback function must BOTH be NULL or defined.");

	if ((s1 ==  RM_WHACKED) || (s2 == RM_WHACKED) || (s3 == RM_WHACKED) || (s4 == RM_WHACKED))
	    return(RM_WHACKED);
    }
    
    nverts = usize * vsize;
    v = rmVertex3DNew(nverts * 2);

    if ((appdata2func != NULL) && (vmap != NULL))
	c = rmColor4DNew(nverts * 2); /* per-vertex color */
    else
	c = NULL;

    indx = 0;
    
    for (j = 0; j < vsize; j++)
    {
	for (i = 0; i < usize; i++)
	{
	    float data;
	    
	    v[indx] = (*appgridfunc)(i, j, appGridUsrData);
	    data = (*appdatafunc)(i, j, appDataUsrData);

	    if (c)
	    {
		int   k;
		float d2;

		d2 = (*appdata2func)(i, j, appData2UsrData);
		k = rmVismapIndexFromData(vmap, d2);
		rmVismapGetColor4D(vmap, k, (c + indx));
	    }
	    
	    indx++;
	    v[indx] = v[indx - 1];

	    if (c)
		c[indx] = c[indx - 1];

	    switch(axis_offset_enum)
	       {
	       case RMV_XAXIS_OFFSET:
		  v[indx].x += data;
		  break;
		  
	       case RMV_YAXIS_OFFSET:
		  v[indx].y += data;
		  break;
		  
	       case RMV_ZAXIS_OFFSET:
		  v[indx].z += data;
		  break;
		  
	       default: /* bogus axis offset enum */
		  break;
	       }
	    indx++;
	}
    }

    rmNodeSetLineWidth(n, linewidth_enum);
    rmNodeSetLineStyle(n, linestyle_enum);

    q = rmPrimitiveNew(RM_LINES);
    rmPrimitiveSetVertex3D(q, (nverts * 2), v, RM_COPY_DATA, NULL);

    if (c)
    {
	rmPrimitiveSetColor4D(q, (nverts * 2), c, RM_COPY_DATA, NULL);
	rmColor4DDelete(c);
    }
	
    /* now, add the new primitive onto the node */
    rmNodeAddPrimitive(n, q);
    private_rmvSetBox(n);
    rmVertex3DDelete (v);

    return(RM_CHILL);
}


/*
 * ----------------------------------------------------
 * @Name rmvJ3MeshUOutline
 @pstart
 RMenum rmvJ3MeshUOutline (RMvertex3D (*appgridfunc)(int i, int j, void *usrData),
                           void *appGridUsrData, 
		           float (*appdatafunc)(int i, int j, void *usrData),
                           void *appDataUsrData, 
			   float (*appdata2func)(int i, int j, void *usrData),
                           void *appData2UsrData, 
			   RMvisMap *vmap,
			   int axis_offset_enum,
			   int iusize,
			   int ivsize,
			   int linewidth_enum,
			   int linestyle_enum,
			   RMnode *n)
 @pend

 @astart
 RMvertex3D (*appgridfunc)(int i, int j, void *usrData) - a handle to a
    caller-supplied function that returns an RMvertex3D (x, y, z)
    corresponding to the grid point (i, j) (input).
		          
 float (*appdatafunc)(int i, int j, void *usrData) - a handle to a caller-supplied function
    that returns a float which is the scalar value at the grid point
    (i, j) (input).
			   
 float (*appdata2func)(int i, int j, void *usrData) - a handle to a secondary
    caller-supplied function that returns a float which is the scalar
    value at the grid point (i, j) (input).

 RMvisMap *vmap - a handle to an RMvisMap object (input).
			   
 int axis_offset_enum - an integer specifying in which axis to offset
    the glyph.  Must be one of RMV_XAXIS_OFFSET, RMV_YAXIS_OFFSET, or
    RMV_ZAXIS_OFFSET (input).
 
 int iusize, ivsize - int specifying the dimensions of the 2D grid of
    data (input).
			   
 RMenum linewidth_enum - an RMenum specifying the line width.  Must be
    one of RM_LINEWIDTH_NARROW, RM_LINEWIDTH_MEDIUM,
    RM_LINEWIDTH_HEAVY, or RM_LINEWIDTH_[1..8] (input).

 RMenum linestyle_enum - an RMenum specifying the lline style.  Must
    be one of RM_LINES_SOLID, RM_LINES_DASHED, RM_LINES_DOTTED,
    RM_LINES_DOT_DASH, or RM_LINES_DASH_DASH_DOT (input).
			 
 RMnode *n - a handle to an RMnode (modified).
 @aend

 @dstart

 Creates a wireframe representation of a mesh from the data,
 generating one polyline for each row of constant V of the underlying
 2D grid.  The wireframe is a single color, the default color of the
 RMnode at render time.  The wireframe can also be colored via the
 secondary data function and the RMvismap.

 Upon success, RM_CHILL is returned and the U-mesh outline primitives
 are added to the RMnode.  Otherwise, RM_WHACKED is returned.

 @dend
 * ----------------------------------------------------
 */
RMenum
rmvJ3MeshUOutline (RMvertex3D (*appgridfunc)(int i, int j, void *usrData),
		   void *appGridUsrData,
		   float (*appdatafunc)(int i, int j, void *usrData),
		   void *appDataUsrData,
		   float (*appdata2func)(int i, int j, void *usrData),
		   void *appData2UsrData,
		   RMvisMap *vmap,
		   int axis_offset_enum,
		   int iusize,
		   int ivsize,
		   RMenum linewidth_enum,
		   RMenum linestyle_enum,
		   RMnode *n)
{
    int          iu, iv, usize, vsize;
    RMprimitive *t;
    RMvertex3D  *v;

    /* error check on functions, etc. */
    {
	int s1, s2, s3, s4;

	s1 = RM_ASSERT((void *)n, "rmv3DMeshUOutline error: NULL RMnode for return parameter");
	s2 = RM_ASSERT((void *)appgridfunc, "rmv3DMeshUOutline error: NULL app grid callback");
	s3 = RM_ASSERT((void *)appdatafunc, "rmv3DMeshUOutline error: NULL app data callback ");
	s4 = RM_CHILL;
	if (!(((vmap != NULL) && (appdata2func != NULL)) || ((vmap == NULL) && (appdata2func == NULL))))
	    s4 = RM_ASSERT((void *)NULL, "rmv3DMeshUOutline error: the vismap and secondary data callback function must BOTH be NULL or defined.");

	if ((s1 ==  RM_WHACKED) || (s2 == RM_WHACKED) || (s3 == RM_WHACKED) || (s4 == RM_WHACKED))
	    return(RM_WHACKED);
    }
    
    usize = iusize;
    vsize = ivsize;

    v = rmVertex3DNew(usize);
    for (iv = 0; iv < vsize; iv++)
    {
	t = rmPrimitiveNew(RM_LINE_STRIP);

	for (iu = 0; iu < usize; iu++)
	{
	    float data;
	    
	    v[iu] = (*appgridfunc)(iu, iv, appGridUsrData);
	    data = (*appdatafunc)(iu, iv, appDataUsrData);
	    
	    switch(axis_offset_enum)
	       {
	       case RMV_XAXIS_OFFSET:
		v[iu].x += data;
		  break;
		  
	       case RMV_YAXIS_OFFSET:
		v[iu].y += data;
		  break;
		  
	       case RMV_ZAXIS_OFFSET:
		v[iu].z += data;
		  break;
		  
	       default: /* bogus axis offset enum */
		  break;
	       }
	}
	
	if (vmap && appdata2func)
	{
	    int        i;
	    RMcolor4D *colors;
	
	    /* compute the colors */
	    colors = rmColor4DNew(usize);

	    for (i = 0; i < usize; i++)
	    {
		int   k;
		float d2;

		d2 = (*appdata2func)(i, iv, appData2UsrData);
		k = rmVismapIndexFromData(vmap, d2);
		rmVismapGetColor4D(vmap, k, (colors + i));
	    }

	    /* stuff the colors */
	    rmPrimitiveSetColor4D(t, usize, colors, RM_COPY_DATA, NULL);
	    
	    rmColor4DDelete(colors);
	}

	rmPrimitiveSetVertex3D(t, iu, v, RM_COPY_DATA, NULL);
	
	/* now, add the new primitive onto the node */
	rmNodeAddPrimitive(n, t); 
    }

    rmNodeSetLineWidth(n, linewidth_enum);
    rmNodeSetLineStyle(n, linestyle_enum);
	
    free(v);
    return(RM_CHILL);
}


/*
 * ----------------------------------------------------
 * @Name rmvJ3MeshUHorizon
 @pstart
 RMenum rmvJ3MeshUHorizon (RMvertex3D (*appgridfunc)(int i, int j, void *usrData),
                           void *appGridUsrData,
		           float (*appdatafunc)(int i, int j, void *usrData),
                           void *appDataUsrData,
			   float (*appdata2func)(int i, int j, void *usrData),
                           void *appData2UsrData,
			   RMvisMap *vmap,
			   int axis_offset_enum,
			   int iusize,
			   int ivsize,
			   float zerocrossing,
			   RMnode *n)

 @pend

 @astart
 RMvertex3D (*appgridfunc)(int i, int j, void *usrData) - a handle to a
    caller-supplied function that returns an RMvertex3D (x, y, z)
    corresponding to the grid point (i, j) (input).
		           
 void *appGridUsrData - (input, optional) you can pass in a void * to
    user data; this void * will be provided as a parameter to the user-defined
    grid evalution callback. If you don't want to pass in a void * to any
    user data for the grid evaluation callback, pass in a NULL for this
    parameter.

 float (*appdatafunc)(int i, int j, void *usrData) - a handle to a
    caller-supplied function that returns a float, which is the scalar
    value at the grid point (i, j) (input).

 void *appDataUsrData - (optional, input) you can pass in a void * to user
    data; this void * will be provided as a parameter to the user-defined
    data field evaluation callback. If you don't want to pass in a void *
    to any user data for the primary data field grid evaluation callback,
    pass in a NULL for this parameter.
			   
 float (*appdata2func)(int i, int j, void *usrData) -- (input, optional)
    A handle to a caller-supplied function that returns a float, which is the
    scalar value at the grid point (i, j) of the optional secondary data field.
    RMV uses the float returned by appdata2func in conjunction with the
    RMvismap to compute vertex color.

 void *appData2UsrData - (optional, input) you can pass in a void * to user
    data; this void * will be provided as a parameter to the user-defined
    secondary data field evaluation callback. If you don't want to pass in
    a void * to any user data for the secondary data field evaluation
    callback, pass in a NULL for this parameter.
    
 RMvisMap *vmap - a handle to an RMvisMap object (input).
			   
 int axis_offset_enum - an integer specifying in which axis to offset
    the glyph.  Must be one of RMV_XAXIS_OFFSET, RMV_YAXIS_OFFSET, or
    RMV_ZAXIS_OFFSET (input).
			   
 int iusize, ivsize - int specifying the dimensions of the 2D grid of
    data (input).
			   
 float zerocrossing - a float specifying the zero value within the
    data range.  The "area fill" will be computed relative to this
    zero value (input).
			   
 RMnode *n - a handle to an RMnode (modified).
 @aend

 @dstart

 Creates a series of 3D horizon plots from the data.  A horizon plot
 is similar to an "area fill", such as those created by
 rmv2DAreaFill().  Geometry representing a 2D area fill for each V-row
 of input data is created.  The horizon plot is a single color, the
 default color of the RMnode at render time.  The horizon plot can
 also be colored via the secondary data function and the RMvismap.

 Upon success, RM_CHILL is returned and the U-mesh horizon primitives
 are added to the RMnode.  Otherwise, RM_WHACKED is returned.

 @dend
 * ----------------------------------------------------
 */
RMenum
rmvJ3MeshUHorizon (RMvertex3D (*appgridfunc)(int i, int j, void *usrData),
		   void *appGridUsrData,
		   float (*appdatafunc)(int i, int j, void *usrData),
		   void *appDataUsrData,
		   float (*appdata2func)(int i, int j, void *usrData),
		   void *appData2UsrData,
		   RMvisMap *vmap,
		   int axis_offset_enum,
		   int iusize,
		   int ivsize,
		   float zerocrossing,
		   RMnode *n)

{
    /*
     * assumptions:
     *   1. value segment size: w,h != 1, d,t,e == 1.
     */

    int    do_colors;
    int    iv, usize, vsize, j;
    float *data, *xcoords, *ycoords, *zcoords, *data2;

    /* assertions! */

    usize = iusize;
    vsize = ivsize;

    if ((appdata2func != NULL) && (vmap != NULL))
	do_colors = 1;
    else
	do_colors = 0;
    
    data = (float *)malloc(sizeof(float) * usize);
    xcoords = (float *)malloc(sizeof(float) * usize);
    ycoords = (float *)malloc(sizeof(float) * usize);
    zcoords = (float *)malloc(sizeof(float) * usize);
    
    if (do_colors==1)
	data2 = (float *)malloc(sizeof(float) * usize);
    else
	data2 = NULL;

    for (iv = 0; iv < vsize; iv++)
    {
	int        new_size;
	float     *new_data = NULL, *new_x = NULL, *new_y = NULL, *new_z = NULL;
	float     *new_data2 = NULL;
	RMvertex3D t;

	for (j = 0; j < usize; j++)
	{
	    data[j] = (*appdatafunc)(j, iv, appDataUsrData);
	    t = (*appgridfunc)(j, iv, appGridUsrData);
	    xcoords[j] = t.x;
	    ycoords[j] = t.y;
	    zcoords[j] = t.z;
	    if (data2 != NULL)
		data2[j] = (*appdata2func)(j, iv, appData2UsrData);
	}

	private_rmvInsertZeroCrossings(data, usize, xcoords, ycoords, zcoords, ((do_colors == 1) ? data2 : NULL), &new_data, &new_x, &new_y, &new_z, &new_size, &new_data2, zerocrossing);

	private_rmBuildHorizonObjects(n, new_size, new_x, new_y, new_z, new_data, new_data2, vmap, axis_offset_enum, zerocrossing);

	free(new_x);
	free(new_y);
	free(new_z);
	free(new_data);
	if (new_data2)
	    free(new_data2);
    }

    free((void *)data);
    free((void *)xcoords);
    free((void *)ycoords);
    free((void *)zcoords);
    if (data2)
	free((void *)data2);

    return(RM_CHILL);
}


/*
 * ----------------------------------------------------
 * @Name rmvJ3MeshUHorizonOutline
 @pstart
 RMenum rmvJ3MeshUHorizonOutline (RMvertex3D (*appgridfunc)(int i, int j, void *usrData),
                                  void *appGridUsrData,
			          float (*appdatafunc)(int i, int j, void *usrData),
                                  void *appDataUsrData,
				  float (*appdata2func)(int i, int j, void *usrData),
                                  void *appData2UsrData,
				  RMvisMap *vmap,
				  int axis_offset_enum,
				  int iusize,
				  int ivsize,
				  RMenum linewidth,
				  RMenum linestyle,
				  float zerocrossing,
				  RMnode *n)
 @pend

 @astart
 RMvertex3D (*appgridfunc)(int i, int j, void *usrData) - a handle to a
    caller-supplied function that returns an RMvertex3D (x, y, z)
    corresponding to the grid point (i, j) (input).
			          
 void *appGridUsrData - (input, optional) you can pass in a void * to
    user data; this void * will be provided as a parameter to the user-defined
    grid evalution callback. If you don't want to pass in a void * to any
    user data for the grid evaluation callback, pass in a NULL for this
    parameter.

 float (*appdatafunc)(int i, int j, void *usrData) - a handle to a
    caller-supplied function that returns a float, which is the scalar
    value at the grid point (i, j) (input).

 void *appDataUsrData - (optional, input) you can pass in a void * to user
    data; this void * will be provided as a parameter to the user-defined
    data field evaluation callback. If you don't want to pass in a void *
    to any user data for the primary data field grid evaluation callback,
    pass in a NULL for this parameter.
			   
 float (*appdata2func)(int i, int j, void *usrData) -- (input, optional)
    A handle to a caller-supplied function that returns a float, which is the
    scalar value at the grid point (i, j) of the optional secondary data field.
    RMV uses the float returned by appdata2func in conjunction with the
    RMvismap to compute vertex color.

 void *appData2UsrData - (optional, input) you can pass in a void * to user
    data; this void * will be provided as a parameter to the user-defined
    secondary data field evaluation callback. If you don't want to pass in
    a void * to any user data for the secondary data field evaluation
    callback, pass in a NULL for this parameter.

 RMvisMap *vmap - a handle to an RMvisMap object (input).
		       	  
 int axis_offset_enum - an integer specifying in which axis to offset
    the glyph.  Must be one of RMV_XAXIS_OFFSET, RMV_YAXIS_OFFSET, or
    RMV_ZAXIS_OFFSET (input).
				  
 int iusize, ivsize - int specifying the dimensions of the 2D grid of
     data (input).
				  
 RMenum linewidth_enum - an RMenum specifying the line width.  Must be
    one of RM_LINEWIDTH_NARROW, RM_LINEWIDTH_MEDIUM,
    RM_LINEWIDTH_HEAVY, or RM_LINEWIDTH_[1..8] (input).

 RMenum linestyle_enum - an RMenum specifying the lline style.  Must
    be one of RM_LINES_SOLID, RM_LINES_DASHED, RM_LINES_DOTTED,
    RM_LINES_DOT_DASH, or RM_LINES_DASH_DASH_DOT (input).
			 
 float zerocrossing - a float specifying the zero value within the
    data range.  The "area fill" will be computed relative to this
    zero value (input).
				  
 RMnode *n - a handle to an RMnode (modified).
 @aend

 @dstart

 Creates a series of 3D horizon outline plots from the data.  A
 horizon outline plot is similar to an "area fill", such as those
 created by rmv2DAreaFill().  Geometry representing a 2D area fill for
 each V-row of input data is created.  The horizon outline plot is a
 single color, the default color of the RMnode at render time.  The
 horizon outline plot can also be colored via the secondary data
 function and the RMvismap.

 Upon success, RM_CHILL is returned and the U-mesh horizon outline
 primitives are added to the RMnode.  Otherwise, RM_WHACKED is
 returned.

 @dend
 * ----------------------------------------------------
 */
RMenum
rmvJ3MeshUHorizonOutline (RMvertex3D (*appgridfunc)(int i, int j, void *usrData),
			  void *appGridUsrData,
			  float (*appdatafunc)(int i, int j, void *usrData),
			  void *appDataUsrData,
			  float (*appdata2func)(int i, int j, void *usrData),
			  void *appData2UsrData,
			  RMvisMap *vmap,
			  int axis_offset_enum,
			  int iusize,
			  int ivsize,
			  RMenum linewidth,
			  RMenum linestyle,
			  float zerocrossing,
			  RMnode *n)
{
    int          do_colors;
    int          iv, usize, vsize;
    int          src_index = 0;
    int          i, index = 0;
    float        xoff = 0.0, yoff = 0.0, zoff = 0.0;
    RMprimitive *t;
    RMvertex3D  *v;
    RMcolor4D   *c = NULL;

    /* assertions!? */

    switch(axis_offset_enum)
       {
       case RMV_XAXIS_OFFSET:
	  xoff = zerocrossing;
	  break;
	  
       case RMV_YAXIS_OFFSET:
	  yoff = zerocrossing;
	  break;
	  
       case RMV_ZAXIS_OFFSET:
	  zoff = zerocrossing;
	  break;
	  
       default: /* bogus axis offset enum */
	  break;
       }

    usize = iusize;
    vsize = ivsize;

    v = rmVertex3DNew((usize * 2) + 1);

    if ((vmap != NULL) && (appdata2func != NULL))
    {
	do_colors = 1;
	c = rmColor4DNew((usize * 2) + 1);
    }
    else
	do_colors = 0;

    for (iv = 0; iv < vsize; iv++, src_index += usize)
    {
	index = 0;
	for (i = 0; i < usize; i++)
	{
	    v[index] = (*appgridfunc)(i, iv, appGridUsrData);

	    v[index].x += xoff;
	    v[index].y += yoff;
	    v[index].z += zoff;
	    
	    if (do_colors)
	    {
		int   k;
		float d2;

		d2 = (*appdata2func)(i, iv, appData2UsrData);
		k = rmVismapIndexFromData(vmap, d2);
		rmVismapGetColor4D(vmap, k, (c + index));
	    }
	    
	    index++;
	}

	for (i = (usize - 1); i >= 0; i--)
	{
	    float d;

	    v[index] = (*appgridfunc)(i,iv, appGridUsrData);
	    d = (*appdatafunc)(i,iv, appDataUsrData);
	    
	    switch(axis_offset_enum)
	       {
	       case RMV_XAXIS_OFFSET:
		  v[index].x += d;
		  break;
		  
	       case RMV_YAXIS_OFFSET:
		  v[index].y += d;
		  break;
		  
	       case RMV_ZAXIS_OFFSET:
		  v[index].z += d;
		  break;
		  
	       default: /* bogus axis offset enum */
		  break;
	       }
	    
	    if (do_colors)
	    {
		int   k;
		float d2;

		d2 = (*appdata2func)(i, iv, appData2UsrData);
		k = rmVismapIndexFromData(vmap, d2);
		rmVismapGetColor4D(vmap, k, (c + index));
	    }
	    index++;
	}
	VCOPY(v, (v + index));
	if (do_colors)
	    VCOPY(c, (c + index));
	
	t = rmPrimitiveNew(RM_LINE_STRIP);

	rmNodeSetLineWidth(n, linewidth);
	rmNodeSetLineStyle(n, linestyle);

	rmPrimitiveSetVertex3D(t, (usize * 2) + 1, v, RM_COPY_DATA, NULL);
	
	if (do_colors)
	{
	    rmPrimitiveSetColor4D(t, (usize * 2) + 1, c, RM_COPY_DATA, NULL);
	}
	rmNodeAddPrimitive(n, t); 
    }
    
    if (do_colors)
    {
	rmColor4DDelete(c);
    }

    rmVertex3DDelete(v);

    return(RM_CHILL);
}


/*
 * ----------------------------------------------------
 * @Name rmvJ3MeshVOutline
 @pstart
 RMenum rmvJ3MeshVOutline (RMvertex3D (*appgridfunc)(int i, int j, void *usrData),
                           void *appGridUsrData,
		           float (*appdatafunc)(int i, int j, void *usrData),
                           void *appDataUsrData,
			   float (*appdata2func)(int i, int j, void *usrData),
			   void *appData2UsrData,
			   RMvisMap *vmap,
			   int axis_offset_enum,
			   int iusize,
			   int ivsize,
			   int linewidth,
			   int linestyle,
			   RMnode *n)
 @pend

 @astart 
 RMvertex3D (*appgridfunc)(int i, int j, void *appGridUsrData) - a handle to a
    caller-supplied function that returns an RMvertex3D (x, y, z)
    corresponding to the grid point (i, j) (input).
	          
 void *appGridUsrData - (input, optional) you can pass in a void * to
    user data; this void * will be provided as a parameter to the user-defined
    grid evalution callback. If you don't want to pass in a void * to any
    user data for the grid evaluation callback, pass in a NULL for this
    parameter.

 float (*appdatafunc)(int i, int j, void *usrData) - a handle to a
    caller-supplied function that returns a float, which is the scalar
    value at the grid point (i, j) (input).

 void *appDataUsrData - (optional, input) you can pass in a void * to user
    data; this void * will be provided as a parameter to the user-defined
    data field evaluation callback. If you don't want to pass in a void *
    to any user data for the primary data field grid evaluation callback,
    pass in a NULL for this parameter.
			   
 float (*appdata2func)(int i, int j, void *usrData) -- (input, optional)
    A handle to a caller-supplied function that returns a float, which is the
    scalar value at the grid point (i, j) of the optional secondary data field.
    RMV uses the float returned by appdata2func in conjunction with the
    RMvismap to compute vertex color.

 void *appData2UsrData - (optional, input) you can pass in a void * to user
    data; this void * will be provided as a parameter to the user-defined
    secondary data field evaluation callback. If you don't want to pass in
    a void * to any user data for the secondary data field evaluation
    callback, pass in a NULL for this parameter.
    
 RMvisMap *vmap - a handle to an RMvisMap object (input).
			   
 int axis_offset_enum - an integer specifying in which axis to offset
    the glyph.  Must be one of RMV_XAXIS_OFFSET, RMV_YAXIS_OFFSET, or
    RMV_ZAXIS_OFFSET (input).
			   
 int iusize, ivsize - int specifying the dimensions of the 2D grid of
    data (input).
			   
 RMenum linewidth_enum - an RMenum specifying the line width.  Must be
    one of RM_LINEWIDTH_NARROW, RM_LINEWIDTH_MEDIUM,
    RM_LINEWIDTH_HEAVY, or RM_LINEWIDTH_[1..8] (input).

 RMenum linestyle_enum - an RMenum specifying the lline style.  Must
    be one of RM_LINES_SOLID, RM_LINES_DASHED, RM_LINES_DOTTED,
    RM_LINES_DOT_DASH, or RM_LINES_DASH_DASH_DOT (input).
			 
 RMnode *n - a handle to an RMnode (modified).
 @aend

 @dstart

 Creates a wireframe representation of a mesh, generating one polyline
 for each row of constant U of the underlying 2D grid.  The wireframe
 is a single color, the default color of the RMnode at render time.
 The wireframe can also be colored via the secondary data function and
 the RMvismap.

 Upon success, RM_CHILL is returned and the V-mesh outline primitives
 are added to the RMnode.  Otherwise, RM_WHACKED is returned.

 @dend
 * ----------------------------------------------------
 */
RMenum
rmvJ3MeshVOutline (RMvertex3D (*appgridfunc)(int i, int j, void *usrData),
		   void *appGridUsrData,
		   float (*appdatafunc)(int i, int j, void *usrData),
		   void *appDataUsrData,
		   float (*appdata2func)(int i, int j, void *usrData),
		   void *appData2UsrData,
		   RMvisMap *vmap,
		   int axis_offset_enum,
		   int iusize,
		   int ivsize,
		   RMenum linewidth,
		   RMenum linestyle,
		   RMnode *n)
{
    int          iu, iv, usize, vsize;
    RMprimitive *t;
    RMvertex3D  *v;
    RMcolor4D   *c;
    
    /* error check on functions, etc. */
    {
	int s1, s2, s3, s4;

	s1 = RM_ASSERT((void *)n, "rmvJ3MeshVOutline error: NULL RMnode for return parameter");
	s2 = RM_ASSERT((void *)appgridfunc, "rmvJ3MeshVOutline error: NULL app grid callback");
	s3 = RM_ASSERT((void *)appdatafunc, "rmvJ3MeshVOutline error: NULL app data callback ");
	s4 = RM_CHILL;
	if (!(((vmap != NULL) && (appdata2func != NULL)) || ((vmap == NULL) && (appdata2func == NULL))))
	    s4 = RM_ASSERT((void *)NULL, "rmvJ3MeshVOutline error: the vismap and secondary data callback function must BOTH be NULL or defined.");

	if ((s1 ==  RM_WHACKED) || (s2 == RM_WHACKED) || (s3 == RM_WHACKED) || (s4 == RM_WHACKED))
	    return(RM_WHACKED);
    }
    
    usize = iusize;
    vsize = ivsize;

    v = rmVertex3DNew(vsize);

    if ((appdata2func != NULL) && (vmap != NULL))
	c = rmColor4DNew(vsize);
    else
	c = NULL;

    for (iu = 0; iu < usize; iu++)
    {
	t = rmPrimitiveNew(RM_LINE_STRIP);

	for (iv = 0; iv < vsize; iv++)
	{
	    float data;

	    v[iv] = (*appgridfunc)(iu, iv, appGridUsrData);
	    data = (*appdatafunc)(iu, iv, appDataUsrData);
	    
	    switch(axis_offset_enum)
	       {
	       case RMV_XAXIS_OFFSET:
		v[iv].x += data;
		  break;
		  
	       case RMV_YAXIS_OFFSET:
		v[iv].y += data;
		  break;
		  
	       case RMV_ZAXIS_OFFSET:
		v[iv].z += data;
		  break;
		  
	       default: /* bogus axis offset enum */
		  break;
	       }

	    if (c)
	    {
		int   k;
		float d2;

		d2 = (*appdata2func)(iu, iv, appData2UsrData);
		k = rmVismapIndexFromData(vmap, d2);
		rmVismapGetColor4D(vmap, k, (c + iv));
	    }
	}
	
	if (c)
	{
	    /* stuff the colors */
	    rmPrimitiveSetColor4D(t, vsize, c, RM_COPY_DATA, NULL);
	}

	rmPrimitiveSetVertex3D(t, vsize, v, RM_COPY_DATA, NULL);
	
	/* now, add the new primitive onto the node */
	rmNodeAddPrimitive(n, t); 
    }

    rmNodeSetLineWidth(n, linewidth);
    rmNodeSetLineStyle(n, linestyle);
	
    if (c)
    {
	rmColor4DDelete(c);
    }

    rmVertex3DDelete(v);

    return(RM_CHILL);
}


/*
 * ----------------------------------------------------
 * @Name rmvJ3MeshVHorizon
 @pstart
 RMenum rmvJ3MeshVHorizon (RMvertex3D (*appgridfunc)(int i, int j, void *usrData),
                           void *appGridUsrData,
		           float (*appdatafunc)(int i, int j, void *usrData),
                           void *appDataUsrData,
			   float (*appdata2func)(int i, int j, void *usrData),
                           void *appData2UsrData,
			   RMvisMap *vmap,
			   int axis_offset_enum,
			   int iusize,
			   int ivsize,
			   float zerocrossing,
			   RMnode *n)
 @pend

 @astart
 RMvertex3D (*appgridfunc)(int i, int j, void *appGridUsrData) - a handle to a
    caller-supplied function that returns an RMvertex3D (x, y, z)
    corresponding to the grid point (i, j) (input).
	          
 void *appGridUsrData - (input, optional) you can pass in a void * to
    user data; this void * will be provided as a parameter to the user-defined
    grid evalution callback. If you don't want to pass in a void * to any
    user data for the grid evaluation callback, pass in a NULL for this
    parameter.

 float (*appdatafunc)(int i, int j, void *usrData) - a handle to a
    caller-supplied function that returns a float, which is the scalar
    value at the grid point (i, j) (input).

 void *appDataUsrData - (optional, input) you can pass in a void * to user
    data; this void * will be provided as a parameter to the user-defined
    data field evaluation callback. If you don't want to pass in a void *
    to any user data for the primary data field grid evaluation callback,
    pass in a NULL for this parameter.
			   
 float (*appdata2func)(int i, int j, void *usrData) -- (input, optional)
    A handle to a caller-supplied function that returns a float, which is the
    scalar value at the grid point (i, j) of the optional secondary data field.
    RMV uses the float returned by appdata2func in conjunction with the
    RMvismap to compute vertex color.

 void *appData2UsrData - (optional, input) you can pass in a void * to user
    data; this void * will be provided as a parameter to the user-defined
    secondary data field evaluation callback. If you don't want to pass in
    a void * to any user data for the secondary data field evaluation
    callback, pass in a NULL for this parameter.

 RMvisMap *vmap - a handle to an RMvisMap object (input).
			   
 int axis_offset_enum - an integer specifying in which axis to offset
    the glyph.  Must be one of RMV_XAXIS_OFFSET, RMV_YAXIS_OFFSET, or
    RMV_ZAXIS_OFFSET (input).
		          
 int iusize, ivsize - int specifying the dimensions of the 2D grid of
    data (input).
			   
 float zerocrossing - a float specifying the zero value within the
    data range.  The "area fill" will be computed relative to this
    zero value (input).
			   
 RMnode *n - a handle to an RMnode (modified).
 @aend

 @dstart

 Creates a series of 3D horizon plots from the data.  A horizon plot
 is similar to an "area fill", such as those created by
 rmv2DAreaFill().  Geometry representing a 2D area fill for each U-row
 of input data is created.  The horizon plot is a single color, the
 default color of the RMnode at render time.  The horizon plot can
 also be colored via the secondary data function and the RMvismap.

 Upon success, RM_CHILL is returned and the V-mesh horizon primitives
 are added to the RMnode.  Otherwise, RM_WHACKED is returned.

 @dend
 * ----------------------------------------------------
 */
RMenum
rmvJ3MeshVHorizon (RMvertex3D (*appgridfunc)(int i, int j, void *usrData),
		   void *appGridUsrData,
		   float (*appdatafunc)(int i, int j, void *usrData),
		   void *appDataUsrData,
		   float (*appdata2func)(int i, int j, void *usrData),
		   void *appData2UsrData,
		   RMvisMap *vmap,
		   int axis_offset_enum,
		   int iusize,
		   int ivsize,
		   float zerocrossing,
		   RMnode *n)
{
    int do_colors;
    int iu, usize, vsize;

    /* error check on functions, etc. */
    {
	int s1,s2,s3,s4;

	s1 = RM_ASSERT((void *)n, "rmvJ3MeshVHorizon error: NULL RMnode for return parameter");
	s2 = RM_ASSERT((void *)appgridfunc, "rmvJ3MeshVHorizon error: NULL app grid callback");
	s3 = RM_ASSERT((void *)appdatafunc, "rmvJ3MeshVHorizon error: NULL app data callback ");
	s4 = RM_CHILL;
	if (!(((vmap != NULL) && (appdata2func != NULL)) || ((vmap == NULL) && (appdata2func == NULL))))
	    s4 = RM_ASSERT((void *)NULL, "rmvJ3MeshVHorizon error: the vismap and secondary data callback function must BOTH be NULL or defined.");

	if ((s1 ==  RM_WHACKED) || (s2 == RM_WHACKED) || (s3 == RM_WHACKED) || (s4 == RM_WHACKED))
	    return(RM_WHACKED);
    }

    usize = iusize;
    vsize = ivsize;
    
    if (appdata2func != NULL && vmap != NULL)
	do_colors = 1;
    else
	do_colors = 0;
    
    for (iu = 0;iu < usize; iu++)
    {
	int new_size;
	int i;
	float *new_data = NULL, *new_x = NULL, *new_y = NULL, *new_z = NULL;
	float *new_data2 = NULL;
	float *tdata, *tx, *ty, *tz, *tdata2;

	tx = (float *)malloc(sizeof(float *) * vsize);
	ty = (float *)malloc(sizeof(float *) * vsize);
	tz = (float *)malloc(sizeof(float *) * vsize);
	tdata = (float *)malloc(sizeof(float *) * vsize);
	
	if (do_colors)
	    tdata2 = (float *)malloc(sizeof(float *) * vsize);
	else
	    tdata2 = NULL;

	for (i = 0; i < vsize; i++)
        {
	    RMvertex3D t;

	    t = (*appgridfunc)(iu, i, appGridUsrData);
	    tx[i] = t.x;
	    ty[i] = t.y;
	    tz[i] = t.z;
	    tdata[i] = (*appdatafunc)(iu, i, appDataUsrData);
	    if (do_colors)
		tdata2[i] = (*appdata2func)(iu, i, appData2UsrData);
	}

	/* create 1D versions of data for processing */
	private_rmvInsertZeroCrossings(tdata, vsize, tx, ty, tz, ((do_colors == 1) ? tdata2 : NULL), &new_data, &new_x, &new_y, &new_z, &new_size, &new_data2, zerocrossing);

	free(tx);
	free(ty);
	free(tz);
	free(tdata);
	if (do_colors)
	    free(tdata2);
	
	private_rmBuildHorizonObjects(n, new_size, new_x, new_y, new_z, new_data, new_data2, vmap, axis_offset_enum, zerocrossing);
				      

	free(new_x);
	free(new_y);
	free(new_z);
	free(new_data);
	if (new_data2)
	    free(new_data2);
    }
    return(RM_CHILL);
}


/*
 * ----------------------------------------------------
 * @Name rmvJ3MeshVHorizonOutline
 @pstart
 RMenum rmvJ3MeshVHorizonOutline (RMvertex3D (*appgridfunc)(int i, int j, void *usrData),
                                  void *appGridUsrData,
			          float (*appdatafunc)(int i, int j, void *usrData),
                                  void *appDataUsrData,
				  float (*appdata2func)(int i, int j, void *usrData),
				  void *appData2UsrData,
				  RMvisMap *vmap,
				  int axis_offset_enum,
				  int iusize,
				  int ivsize,
				  RMenum linewidth,
				  RMenum linestyle,
				  float zerocrossing,
				  RMnode *n)
 @pend

 @astart
 RMvertex3D (*appgridfunc)(int i, int j, void *appGridUsrData) - a handle to a
    caller-supplied function that returns an RMvertex3D (x, y, z)
    corresponding to the grid point (i, j) (input).
	          
 void *appGridUsrData - (input, optional) you can pass in a void * to
    user data; this void * will be provided as a parameter to the user-defined
    grid evalution callback. If you don't want to pass in a void * to any
    user data for the grid evaluation callback, pass in a NULL for this
    parameter.

 float (*appdatafunc)(int i, int j, void *usrData) - a handle to a
    caller-supplied function that returns a float, which is the scalar
    value at the grid point (i, j) (input).

 void *appDataUsrData - (optional, input) you can pass in a void * to user
    data; this void * will be provided as a parameter to the user-defined
    data field evaluation callback. If you don't want to pass in a void *
    to any user data for the primary data field grid evaluation callback,
    pass in a NULL for this parameter.
			   
 float (*appdata2func)(int i, int j, void *usrData) -- (input, optional)
    A handle to a caller-supplied function that returns a float, which is the
    scalar value at the grid point (i, j) of the optional secondary data field.
    RMV uses the float returned by appdata2func in conjunction with the
    RMvismap to compute vertex color.

 void *appData2UsrData - (optional, input) you can pass in a void * to user
    data; this void * will be provided as a parameter to the user-defined
    secondary data field evaluation callback. If you don't want to pass in
    a void * to any user data for the secondary data field evaluation
    callback, pass in a NULL for this parameter.
    
 RMvisMap *vmap - a handle to an RMvisMap object (input).
				  
 int axis_offset_enum - an integer specifying in which axis to offset
    the glyph.  Must be one of RMV_XAXIS_OFFSET, RMV_YAXIS_OFFSET, or
    RMV_ZAXIS_OFFSET (input).
				  
 int iusize, ivsize - int specifying the dimensions of the 2D grid of
    data (input).
				  
 RMenum linewidth_enum - an RMenum specifying the line width.  Must be
    one of RM_LINEWIDTH_NARROW, RM_LINEWIDTH_MEDIUM,
    RM_LINEWIDTH_HEAVY, or RM_LINEWIDTH_[1..8] (input).

 RMenum linestyle_enum - an RMenum specifying the lline style.  Must
    be one of RM_LINES_SOLID, RM_LINES_DASHED, RM_LINES_DOTTED,
    RM_LINES_DOT_DASH, or RM_LINES_DASH_DASH_DOT (input).
			 
 float zerocrossing - a float specifying the zero value within the
    data range.  The "area fill" will be computed relative to this
    zero value (input).
				  
 RMnode *n - a handle to an RMnode (modified).
 @aend

 @dstart

 Creates a series of 3D horizon outline plots from the data.  A
 horizon outline plot is similar to an "area fill", such as those
 created by rmv2DAreaFill().  Geometry representing a 2D area fill for
 each U-row of input data is created.  The horizon outline plot is a
 single color, the default color of the RMnode at render time.  The
 horizon outline plot can also be colored via the secondary data
 function and the RMvismap.

 Upon success, RM_CHILL is returned and the V-mesh horizon outline
 primitives are added to the RMnode.  Otherwise, RM_WHACKED is
 returned.

 @dend
 * ----------------------------------------------------
 */
RMenum
rmvJ3MeshVHorizonOutline (RMvertex3D (*appgridfunc)(int i, int j, void *usrData),
			  void *appGridUsrData,
			  float (*appdatafunc)(int i, int j, void *usrData),
			  void *appDataUsrData,
			  float (*appdata2func)(int i, int j, void *usrData),
			  void *appData2UsrData,
			  RMvisMap *vmap,
			  int axis_offset_enum,
			  int iusize,
			  int ivsize,
			  RMenum linewidth,
			  RMenum linestyle,
			  float zerocrossing,
			  RMnode *n)
{
    int          do_colors;
    int          iu, iv, usize, vsize;
    int          src_index=0;
    int          qd[2];
    int          index;
    float        xoff = 0.0, yoff = 0.0, zoff = 0.0;
    RMprimitive *t;
    RMvertex3D  *v, *v2;
    RMcolor4D   *c = NULL, *c2;

    /* assertions!? */

    switch(axis_offset_enum)
       {
       case RMV_XAXIS_OFFSET:
	  xoff = zerocrossing;
	  break;

        case RMV_YAXIS_OFFSET:
        yoff = zerocrossing;
	break;

       case RMV_ZAXIS_OFFSET:
	  zoff = zerocrossing;
	  break;
	  
       default: /* bogus axis offset enum */
	  break;
       }
    
    usize = iusize;
    vsize = ivsize;

    qd[0] = vsize;
    qd[1] = 2;

    v = rmVertex3DNew((vsize * 2) + 1);

    if ((appdata2func != NULL) && (vmap != NULL))
    {
	c = rmColor4DNew((vsize * 2) + 1);
	c2 = c + vsize;
	do_colors = 1;
    }
    else
	do_colors = 0;
    
    v2 = v+vsize;
    
    for (iu = 0; iu < usize; iu++)
    {
	src_index = iu;
	index = 0;
	for (iv = 0; iv < vsize; iv++)
	{
	    v[index] = (*appgridfunc)(iu, iv, appGridUsrData);
#if 0
	    v[index].x = xcoords[src_index + (iv * usize)] + xoff;
	    v[index].y = ycoords[src_index + (iv * usize)] + yoff;
	    v[index].z = zcoords[src_index + (iv * usize)] + zoff;
#endif
	    if (do_colors)
	    {
		int   k;
		float d2;

		d2 = (*appdata2func)(iu, iv, appData2UsrData);
		k = rmVismapIndexFromData(vmap, d2);
		rmVismapGetColor4D(vmap, k, (c + index));
	    }

	    index++;
	}
	for (iv = (vsize - 1); iv >= 0; iv--)
	{
	    float d;
	    
	    v[index] = (*appgridfunc)(iu, iv, appGridUsrData);
	    d = (*appdatafunc)(iu, iv, appDataUsrData);

	    switch (axis_offset_enum)
	       {
	       case RMV_XAXIS_OFFSET:
		  v[index].x += d;
		  v[index].y += yoff;
		  v[index].z += zoff;
		  break;
		  
	       case RMV_YAXIS_OFFSET:
		  v[index].x += xoff;
		  v[index].y += d;
		  v[index].z += zoff;
		  break;
		  
	       case RMV_ZAXIS_OFFSET:
		  v[index].x += xoff;
		  v[index].y += yoff;
		  v[index].z += d;
		  break;
		  
	       default: /* bogus axis offset enum */
		  break;
	       }

	    if (do_colors)
	    {
		int   k;
		float d2;

		d2 = (*appdata2func)(iu, iv, appData2UsrData);
		k = rmVismapIndexFromData(vmap, d2);
		rmVismapGetColor4D(vmap, k, (c + index));
	    }
	    index++;
	}

	VCOPY(v, (v + index));
	
	t = rmPrimitiveNew(RM_LINE_STRIP);

	rmNodeSetLineStyle(n, linestyle);
	rmNodeSetLineWidth(n, linewidth);
	
	if (do_colors)
	{
	    VCOPY(c,c+index);
	    rmPrimitiveSetColor4D(t, (vsize * 2) + 1, c, RM_COPY_DATA, NULL);
	}
	    
	rmPrimitiveSetVertex3D(t, (vsize * 2) + 1, v, RM_COPY_DATA, NULL);
	
	/* now, add the new primitive onto the node */
	rmNodeAddPrimitive(n, t); 
    }

    if (do_colors)
    {
	rmColor4DDelete(c);
    }

    rmVertex3DDelete(v);
    return(RM_CHILL);
}



/*
 * ----------------------------------------------------
 * @Name rmvJ3MeshOutline
 @pstart
 RMenum rmvJ3MeshOutline (RMvertex3D (*appgridfunc)(int i, int j, void *usrData),
                          void *appGridUsrData,
		          float (*appdatafunc)(int i, int j, void *usrData),
                          void *appDataUsrData,
			  float (*appdata2func)(int i, int j, void *usrData),
                          void *appData2UsrData,
			  RMvisMap *vmap,
			  int axis_axis_offset_enum,
			  int iusize,
			  int ivsize,
			  int linewidth,
			  int linestyle,
			  RMnode *n)
 @pend

 @astart
 RMvertex3D (*appgridfunc)(int i, int j, void *usrData) - a handle to a
    caller-supplied function that returns an RMvertex3D (x, y, z)
    corresponding to the grid point (i, j) (input).
		          
 void *appGridUsrData - (input, optional) you can pass in a void * to
    user data; this void * will be provided as a parameter to the user-defined
    grid evalution callback. If you don't want to pass in a void * to any
    user data for the grid evaluation callback, pass in a NULL for this
    parameter.
    
 float (*appdatafunc)(int i, int j, void *usrData) - a handle to a
    caller-supplied function that returns a float, which is the scalar value
    at the grid location (i, j) (input).
			   
 void *appDataUsrData - (optional, input) you can pass in a void * to user
    data; this void * will be provided as a parameter to the user-defined
    data field evaluation callback. If you don't want to pass in a void *
    to any user data for the primary data field grid evaluation callback,
    pass in a NULL for this parameter.
    
 float (*appdata2func)(int i, int j, void *usrData) -- (input, optional)
    A handle to a caller-supplied function that returns a float, which is the
    scalar value at the grid point (i, j) of the optional secondary data field.
    RMV uses the float returned by appdata2func in conjunction with the
    RMvismap to compute vertex color.

 void *appData2UsrData - (optional, input) you can pass in a void * to user
    data; this void * will be provided as a parameter to the user-defined
    secondary data field evaluation callback. If you don't want to pass in
    a void * to any user data for the secondary data field evaluation
    callback, pass in a NULL for this parameter.

 RMvisMap *vmap - a handle to an RMvisMap object (input).
			  
 int axis_axis_offset_enum - an integer specifying in which axis to
    offset the glyph.  Must be one of RMV_XAXIS_OFFSET,
    RMV_YAXIS_OFFSET, or RMV_ZAXIS_OFFSET (input).
			  
 int iusize, ivsize - integer specifying the dimensions of the 2D grid
    of data (input).
			  
 RMenum linewidth_enum - an RMenum specifying the line width.  Must be
    one of RM_LINEWIDTH_NARROW, RM_LINEWIDTH_MEDIUM,
    RM_LINEWIDTH_HEAVY, or RM_LINEWIDTH_[1..8] (input).

 RMenum linestyle_enum - an RMenum specifying the lline style.  Must
    be one of RM_LINES_SOLID, RM_LINES_DASHED, RM_LINES_DOTTED,
    RM_LINES_DOT_DASH, or RM_LINES_DASH_DASH_DOT (input).
			 
 RMnode *n - a handle to an RMnode (modified).
 @aend

 @dstart

 This is just a convenience routine, combining rmvJ3MeshUOutline and
 rmvJ3MeshVOutline in one operation, creating a ruled, wireframe
 representation of a surface from data.  The wireframe is a single
 color, the default color of the RMnode at render time.  The wireframe
 can also be colored via the secondary data function and the RMvismap.

 See rmvJ3MeshVOutlin() and rmvJ3MeshUOutline().

 Upon success, RM_CHILL is returned and the V-mesh horizon primitives
 are added to the RMnode.  Otherwise, RM_WHACKED is returned.

 @dend
 * ----------------------------------------------------
 */
RMenum
rmvJ3MeshOutline (RMvertex3D (*appgridfunc)(int i, int j, void *usrData),
                  void *appGridUsrData,
		  float (*appdatafunc)(int i, int j, void *usrData),
                  void *appDataUsrData,
		  float (*appdata2func)(int i, int j, void *usrData),
                  void *appData2UsrData,
		  RMvisMap *vmap,
		  int axis_axis_offset_enum,
		  int iusize,
		  int ivsize,
		  RMenum linewidth,
		  RMenum linestyle,
		  RMnode *n)
{
    /* error check on functions, etc. */
    {
	int s1, s2, s3, s4;

	s1 = RM_ASSERT((void *)n,"rmvJ3MeshOutline error: NULL RMnode for return parameter");
	s2 = RM_ASSERT((void *)appgridfunc, "rmvJ3MeshOutline error: NULL app grid callback");
	s3 = RM_ASSERT((void *)appdatafunc, "rmvJ3MeshOutline error: NULL app data callback ");
	s4 = RM_CHILL;
	if (!(((vmap != NULL) && (appdata2func != NULL)) || ((vmap == NULL) && (appdata2func == NULL))))
	    s4 = RM_ASSERT((void *)NULL, "rmvJ3MeshOutline error: the vismap and secondary data callback function must BOTH be NULL or defined.");

	if ((s1 ==  RM_WHACKED) || (s2 == RM_WHACKED) || (s3 == RM_WHACKED) || (s4 == RM_WHACKED))
	    return(RM_WHACKED);
    }
    
    rmvJ3MeshUOutline(appgridfunc, appGridUsrData, appdatafunc, appDataUsrData, appdata2func, appData2UsrData, vmap, axis_axis_offset_enum, iusize, ivsize, linewidth, linestyle, n);
    rmvJ3MeshVOutline(appgridfunc, appGridUsrData, appdatafunc, appDataUsrData, appdata2func, appData2UsrData, vmap, axis_axis_offset_enum, iusize, ivsize, linewidth, linestyle, n);

    return(RM_CHILL);
}


/*
 * ----------------------------------------------------
 * @Name rmvJ3MeshSurface
 @pstart
 RMenum rmvJ3MeshSurface (RMvertex3D (*appgridfunc)(int i, int j, void *usrData),
		          float (*appdatafunc)(int i, int j, void *usrData),
			  float (*appdata2func)(int i, int j, void *usrData),
			  RMvisMap *vmap,
			  int axis_offset_enum,
			  int iusize,
			  int ivsize,
			  RMenum flipNormalsBool,
			  RMnode *n)
 @pend

 @astart
 RMvertex3D (*appgridfunc)(int i, int j, void *appGridUsrData) - a handle to a
    caller-supplied function that returns an RMvertex3D (x, y, z)
    corresponding to the grid point (i, j) (input).
	          
 void *appGridUsrData - (input, optional) you can pass in a void * to
    user data; this void * will be provided as a parameter to the user-defined
    grid evalution callback. If you don't want to pass in a void * to any
    user data for the grid evaluation callback, pass in a NULL for this
    parameter.

 float (*appdatafunc)(int i, int j, void *usrData) - a handle to a
    caller-supplied function that returns a float, which is the scalar
    value at the grid point (i, j) (input).

 void *appDataUsrData - (optional, input) you can pass in a void * to user
    data; this void * will be provided as a parameter to the user-defined
    data field evaluation callback. If you don't want to pass in a void *
    to any user data for the primary data field grid evaluation callback,
    pass in a NULL for this parameter.
			   
 float (*appdata2func)(int i, int j, void *usrData) -- (input, optional)
    A handle to a caller-supplied function that returns a float, which is the
    scalar value at the grid point (i, j) of the optional secondary data field.
    RMV uses the float returned by appdata2func in conjunction with the
    RMvismap to compute vertex color.

 void *appData2UsrData - (optional, input) you can pass in a void * to user
    data; this void * will be provided as a parameter to the user-defined
    secondary data field evaluation callback. If you don't want to pass in
    a void * to any user data for the secondary data field evaluation
    callback, pass in a NULL for this parameter.
    
 RMvisMap *vmap - a handle to an RMvisMap object (input).
			  
 int axis_offset_enum - an integer specifying in which axis to
    offset the glyph.  Must be one of RMV_XAXIS_OFFSET,
    RMV_YAXIS_OFFSET, or RMV_ZAXIS_OFFSET (input).
			  
 int iusize, ivsize - int specifying the dimensions of the 2D grid of
    data (input).

 RMenum flipNormalsBool - use RM_TRUE to "flip the normals" computed by
    this routine, or use RM_FALSE to use the normals computed by this routine
    without modification (see rmvJ3ComputeMeshNormals).
			  
 RMnode *n - a handle to an RMnode (modified).
 @aend

 @dstart

 Creates a surface representing the combination of the data and the
 underlying grid function.  The surface is a single color, the RMnode
 default color at render time.  The surface can also be colored via
 the secondary data function and the RMvismap.  The surface vertices
 are a combination of the grid point, the primary data function, and
 the axis offset chosen.

 Upon success, RM_CHILL is returned and the surface primitives are
 added to the RMnode.  Otherwise, RM_WHACKED is returned.

 @dend
 * ----------------------------------------------------
 */
RMenum
rmvJ3MeshSurface (RMvertex3D (*appgridfunc)(int i, int j, void *usrData),
		  void *appGridUsrData, 
		  float (*appdatafunc)(int i, int j, void *usrData),
		  void *appDataUsrData, 
		  float (*appdata2func)(int i, int j, void *usrData),
		  void *appData2UsrData, 
		  RMvisMap *vmap,
		  int axis_offset_enum,
		  int iusize,
		  int ivsize,
		  RMenum flipNormalsBool,
		  RMnode *n)
{

#define TSTRIPS 1
    /*
     * once upon a time, this routine generated real quad meshes.
     * it has since been changed to generate t-strips. this was
     * necessary for the purpose of creating PS output: the occasional
     * non-planar quad would give the depth-sorting code fits.
     */
    int          i, j, indx, usize, vsize;
    RMvertex3D  *v, *normals;
    RMvertex3D  *tv, *tn;
    RMcolor4D   *tc, *colors = NULL;
    RMprimitive *t;

#if !(TSTRIPS)
    int dims[2];

    dims[0] = iusize;
    dims[1] = ivsize;
#endif

    /* error check on functions, etc. */
    {
	int s1,s2,s3,s4;

	s1 = RM_ASSERT((void *)n, "rmv3DMeshSurface error: NULL RMnode for return parameter");
	s2 = RM_ASSERT((void *)appgridfunc, "rmv3DMeshSurface error: NULL app grid callback");
	s3 = RM_ASSERT((void *)appdatafunc, "rmv3DMeshSurface error: NULL app data callback ");
	s4 = RM_CHILL;
	if (!(((vmap != NULL) && (appdata2func != NULL)) || ((vmap == NULL) && (appdata2func == NULL))))
	    s4 = RM_ASSERT((void *)NULL, "rmv3DMeshSurface error: the vismap and secondary data callback function must BOTH be NULL or defined.");

	if ((s1 ==  RM_WHACKED) || (s2 == RM_WHACKED) || (s3 == RM_WHACKED) || (s4 == RM_WHACKED))
	    return(RM_WHACKED);
    }
#if !(TSTRIPS)
    t = rmPrimitiveNew(RM_QUADMESH);
#endif

    usize = iusize;
    vsize = ivsize;
    
    v = rmVertex3DNew(usize * vsize);
    normals = rmVertex3DNew(usize * vsize);

    /* build the base grid */
    indx = 0;
    for (j = 0; j < vsize; j++)
    {
	for (i = 0; i < usize; i++)
	{
	    float d;

	    v[indx] = (*appgridfunc)(i, j, appGridUsrData);
	    d = (*appdatafunc)(i, j, appDataUsrData);

	    switch(axis_offset_enum)
	       {
	       case RMV_XAXIS_OFFSET:
		  v[indx].x += d;
		  break;
		  
	       case RMV_YAXIS_OFFSET:
		  v[indx].y += d;
		  break;
		  
	       case RMV_ZAXIS_OFFSET:
		  v[indx].z += d;
		  break;
		  
	       default: /* bogus axis offset enum */
		  break;
	       }
	    indx++;
	}
    }

    rmvJ3ComputeMeshNormals(v, normals, usize, vsize, flipNormalsBool);
    
#if !(TSTRIPS)
    /* old qmesh code */
    rmPrimitiveSetVertex3D(t, (usize * vsize), v, RM_COPY_DATA, NULL);
    rmPrimitiveSetNormal3D(t, (usize * vsize), normals, RM_COPY_DATA, NULL);
#endif
    
    if (vmap)
    {
	/* compute the colors */
	colors = rmColor4DNew(usize * vsize);

	indx = 0;
	for (j = 0; j < vsize; j++)
	{
	    for (i = 0; i < usize; i++)
	    {
		int   k;
		float d2;

		d2 = (*appdata2func)(i, j, appData2UsrData);
		k = rmVismapIndexFromData(vmap, d2);
		rmVismapGetColor4D(vmap, k, (colors + indx));
		indx++;
	    }
	}

#if !(TSTRIPS)
	/* old qmesh code -  stuff the colors */
	rmPrimitiveSetColor4D(t, (usize * vsize), colors, RM_COPY_DATA, NULL);
	rmColor4DDelete(colors);
#endif
    }

#if TSTRIPS    
    {
	int ntris, npts, j;

	ntris = (usize - 1) * 2;
	npts = ntris + 2;
	tv = rmVertex3DNew(npts);
	tn = rmVertex3DNew(npts);
	if (colors != NULL)
	    tc = rmColor4DNew(npts);
	else
	    tc = NULL;

	for (j = 0; j < (vsize - 1); j++)
	{
	    int index;
	    
	    t = rmPrimitiveNew(RM_TRIANGLE_STRIP);
	    index = 0;
	    
	    for (i = 0; i < usize; i++)
	    {
		
		VCOPY((v + i + ((j + 1) * usize)), (tv + index));
		VCOPY((normals + i + ((j + 1) * usize)), (tn + index));
		if (tc != NULL)
		    VCOPY((colors + i + ((j + 1) * usize)), (tc + index));
		index++;
		
		VCOPY((v + i + (j * usize)), (tv + index));
		VCOPY((normals + i + (j * usize)), (tn + index));
		if (tc != NULL)
		    VCOPY((colors + i + (j * usize)), (tc + index));
		index++;
	    }
	    rmPrimitiveSetVertex3D(t, npts, tv, RM_COPY_DATA, NULL);
	    rmPrimitiveSetNormal3D(t, npts, tn, RM_COPY_DATA, NULL);
	    
	    if (tc != NULL)
		rmPrimitiveSetColor4D(t, npts, tc, RM_COPY_DATA, NULL);
    
	    /* now, add the new primitive onto the node */
	    rmNodeAddPrimitive(n, t);

	}
	rmVertex3DDelete(tv);
	rmVertex3DDelete(tn);
	if (tc != NULL)
	{
	    rmColor4DDelete(tc); 
	}

	if (colors)
	  rmColor4DDelete(colors);
    }
#endif /* TSTRIPS */

#if !(TSTRIPS)
    /* old qmesh code -  set the mesh dimensions */
    rmPrimitiveSetQmeshDims(t, dims[0], dims[1]);
    /* now, add the new primitive onto the node */
    rmNodeAddPrimitive(n, t);

#endif

    rmVertex3DDelete (v);
    rmVertex3DDelete (normals);

    private_rmvSetBox(n);

    return(RM_CHILL);
}



/*
 * ----------------------------------------------------
 * @Name rmvJ3ScatterPoint
 @pstart
 RMenum rmvJ3ScatterPoint (RMvertex3D (*appgridfunc)(int i, int j, void *usrData),
                           void *appGridUsrData,
		           float (*appdatafunc)(int i, int j, void *usrData),
			   void *appDataUsrData,
			   float (*appdata2func)(int i, int j, void *usrData),
			   void *appData2UsrData,
			   RMvisMap *vmap,
			   int axis_offset_enum,
			   int iusize,
			   int ivsize,
			   RMenum compute_normals_enum,
			   RMenum flipNormalsBool,
			   RMnode *n)
 @pend

 @astart
 RMvertex3D (*appgridfunc)(int i, int j, void *appGridUsrData) - a handle to a
    caller-supplied function that returns an RMvertex3D (x, y, z)
    corresponding to the grid point (i, j) (input).
	          
 void *appGridUsrData - (input, optional) you can pass in a void * to
    user data; this void * will be provided as a parameter to the user-defined
    grid evalution callback. If you don't want to pass in a void * to any
    user data for the grid evaluation callback, pass in a NULL for this
    parameter.

 float (*appdatafunc)(int i, int j, void *usrData) - a handle to a
    caller-supplied function that returns a float, which is the scalar
    value at the grid point (i, j) (input).

 void *appDataUsrData - (optional, input) you can pass in a void * to user
    data; this void * will be provided as a parameter to the user-defined
    data field evaluation callback. If you don't want to pass in a void *
    to any user data for the primary data field grid evaluation callback,
    pass in a NULL for this parameter.
			   
 float (*appdata2func)(int i, int j, void *usrData) -- (input, optional)
    A handle to a caller-supplied function that returns a float, which is the
    scalar value at the grid point (i, j) of the optional secondary data field.
    RMV uses the float returned by appdata2func in conjunction with the
    RMvismap to compute vertex color.

 void *appData2UsrData - (optional, input) you can pass in a void * to user
    data; this void * will be provided as a parameter to the user-defined
    secondary data field evaluation callback. If you don't want to pass in
    a void * to any user data for the secondary data field evaluation
    callback, pass in a NULL for this parameter.
    
 RMvisMap *vmap - a handle to an RMvisMap object (input).
			   
 int axis_offset_enum - an integer specifying in which axis to offset
    the glyph.  Must be one of RMV_XAXIS_OFFSET, RMV_YAXIS_OFFSET, or
    RMV_ZAXIS_OFFSET (input).
			   
 int iusize, ivsize - int specifying the dimensions of the 2D grid of
    data (input).
			   
 RMenum compute_normals_enum - an RMenum specifying whether or not to
    compute normals.  Must be one of RM_TRUE or RM_FALSE (input).
			   
 RMenum flipNormalsBool - use RM_TRUE to "flip the normals" computed by
    this routine, or use RM_FALSE to use the normals computed by this routine
    without modification (see rmvJ3ComputeMeshNormals).
    
 RMnode *n - a handle to an RMnode (modified).
 @aend

 @dstart

 Creates a 3D scatterplot from data, with each position represented by
 a point.  The scatterplot is a single color, the RMnode default color
 at render time.  The points can also be colored via the secondary
 data function and the RMvismap.  The size of the point primitives
 used in the scatterplot can be set with rmNodeSetPointSize().

 Upon success, RM_CHILL is returned and the scatterplot point
 primitives are added to the RMnode.  Otherwise, RM_WHACKED is
 returned.

 @dend
 * ----------------------------------------------------
 */
RMenum rmvJ3ScatterPoint (RMvertex3D (*appgridfunc)(int i, int j, void *usrData), 
			  void *appGridUsrData, 
			  float (*appdatafunc)(int i, int j, void *usrData), 
			  void *appDataUsrData, 
			  float (*appdata2func)(int i, int j, void *usrData), 
			  void *appData2UsrData, 
			  RMvisMap *vmap, 
			  int axis_offset_enum, 
			  int iusize, 
			  int ivsize, 
			  RMenum compute_normals_enum, 
			  RMenum flipNormalsBool, 
			  RMnode *n)

{
    int          usize, vsize;
    int          i, j, npoints, indx;
    float        dx, dy, dz;
    RMvertex3D  *pts;
    RMcolor4D   *scolors;
    RMprimitive *q;
    RMvertex3D  *p;

    /* error check on functions, etc. */
    {
	int s1, s2, s3, s4;

	s1 = RM_ASSERT((void *)n, "rmvJ3ScatterPoint error: NULL RMnode for return parameter");
	s2 = RM_ASSERT((void *)appgridfunc, "rmvJ3ScatterPoint error: NULL app grid callback");
	s3 = RM_ASSERT((void *)appdatafunc, "rmvJ3ScatterPoint error: NULL app data callback ");
	s4 = RM_CHILL;
	if (!(((vmap != NULL) && (appdata2func != NULL)) || ((vmap == NULL) && (appdata2func == NULL))))
	    s4 = RM_ASSERT((void *)NULL, "rmvJ3DScatterPoint error: the vismap and secondary data callback function must BOTH be NULL or defined.");

	if ((s1 ==  RM_WHACKED) || (s2 == RM_WHACKED) || (s3 == RM_WHACKED) || (s4 == RM_WHACKED))
	    return(RM_WHACKED);
    }
    q = rmPrimitiveNew(RM_POINTS);

    usize = iusize;
    vsize = ivsize;
    
    npoints = usize * vsize;

    p = pts = rmVertex3DNew(npoints);

    if ((appdata2func != NULL) && (vmap != NULL))
	scolors = rmColor4DNew(npoints);
    else
	scolors = NULL;

    indx = 0;

    dx = dy = dz = 1.0;
    
    for (j = 0; j < vsize; j++)
    {
	for (i = 0; i < usize; i++)
	{
	    float d;
	    
	    p[indx] = (*appgridfunc)(i, j, appGridUsrData);
	    d = (*appdatafunc)(i, j, appDataUsrData);
	  
	switch (axis_offset_enum)
	   {
	   case RMV_XAXIS_OFFSET:
	      p[indx].x += d;
	      break;
	      
	   case RMV_YAXIS_OFFSET:
	      p[indx].y += d;
	      break;
	      
	   case RMV_ZAXIS_OFFSET:
	      p[indx].z += d;
	      break;
	      
	   default: /* bogus axis offset enum */
	      break;
	   }

	    if (scolors)
	    {
	        int   k;
		float d2;

		d2 = (*appdata2func)(i, j, appData2UsrData);
		k = rmVismapIndexFromData(vmap, d2);
		rmVismapGetColor4D(vmap, k, (scolors + indx));
	    }
	    indx++;
	}
    }

    if (compute_normals_enum == RM_TRUE)
    {
	RMvertex3D *normals = rmVertex3DNew(usize * vsize);
	
	rmvJ3ComputeMeshNormals(pts, normals, usize, vsize, flipNormalsBool);
	rmPrimitiveSetNormal3D(q, npoints, normals, RM_COPY_DATA, NULL);
	rmVertex3DDelete(normals);
    }
    
    rmPrimitiveSetVertex3D(q, npoints, pts, RM_COPY_DATA, NULL);
    
    if (scolors)
    {
        rmPrimitiveSetColor4D(q, npoints, scolors, RM_COPY_DATA, NULL);
	rmColor4DDelete(scolors);
    }

    /* now, add the new primitive onto the node */
    rmNodeAddPrimitive(n, q);

    /* compute the bounding box and center point for the node */
    private_rmvSetBox(n);
    
    rmVertex3DDelete (pts);
    return(RM_CHILL);
}



/*
 * ----------------------------------------------------
 * @Name rmvI3ScatterCube 
 @pstart 
 RMenum rmvI3ScatterCube (RMvertex3D (*appgridfunc)(int i),
		          float (*appdatafunc)(int i),
			  float (*appdata2func)(int i),
			  RMvisMap *vmap,
			  int axis_offset_enum,
			  int npts,
			  float scale,
			  RMnode *n)
 @pend

 @astart 

 RMvertex3D (*appgridfunc)(int i, void *appGridUsrData) - a handle to a
    caller-supplied function that returns an RMvertex3D (x, y, z)
    corresponding to the grid point (i) (input).
	          
 void *appGridUsrData - (input, optional) you can pass in a void * to
    user data; this void * will be provided as a parameter to the user-defined
    grid evalution callback. If you don't want to pass in a void * to any
    user data for the grid evaluation callback, pass in a NULL for this
    parameter.

 float (*appdatafunc)(int i, void *usrData) - a handle to a
    caller-supplied function that returns a float, which is the scalar
    value at the grid point (i) (input).

 void *appDataUsrData - (optional, input) you can pass in a void * to user
    data; this void * will be provided as a parameter to the user-defined
    data field evaluation callback. If you don't want to pass in a void *
    to any user data for the primary data field grid evaluation callback,
    pass in a NULL for this parameter.
			   
 float (*appdata2func)(int i, void *usrData) -- (input, optional)
    A handle to a caller-supplied function that returns a float, which is the
    scalar value at the grid point (i) of the optional secondary data field.
    RMV uses the float returned by appdata2func in conjunction with the
    RMvismap to compute vertex color.

 void *appData2UsrData - (optional, input) you can pass in a void * to user
    data; this void * will be provided as a parameter to the user-defined
    secondary data field evaluation callback. If you don't want to pass in
    a void * to any user data for the secondary data field evaluation
    callback, pass in a NULL for this parameter.
    
 RMvisMap *vmap - a handle to an RMvisMap object (input).
			  
 int axis_offset_enum - an integer specifying in which axis to offset
    the cube.  Must be one of RMV_XAXIS_OFFSET, RMV_YAXIS_OFFSET, or
    RMV_ZAXIS_OFFSET (input).
			  
 int npts - an integer specifying number of data points (input).
			  
 float scale - float specifying the size of the cubes, measured in
    world coordinates (input).
			  
 RMnode *n - a handle to an RMnode (modified).
 @aend

 @dstart

 Creates a 3D scatterplot from data, with each position represented by
 a solid cube.  The scatterplot is a single color, the RMnode default
 color at render time.  The solid cubes can also be colored via the
 secondary data function and the RMvismap.

 Upon success, RM_CHILL is returned and the scatterplot solid cube
 primitives are added to the RMnode.  Otherwise, RM_WHACKED is
 returned.

 @dend
 * ----------------------------------------------------
 */
RMenum
rmvI3ScatterCube (RMvertex3D (*appgridfunc)(int i, void *usrData),
		  void *appGridUsrData,
		  float (*appdatafunc)(int i, void *usrData),
		  void *appDataUsrData,
		  float (*appdata2func)(int i, void *usrData),
		  void *appData2UsrData,
		  RMvisMap *vmap,
		  int axis_offset_enum,
		  int npts,
		  float scale,
		  RMnode *n)
{
    int i,nboxes,nverts_per_box;
    int dest_offset;
    float smallnum = 0.1;	/* this needs to be computed analytically */
    float loc_scale;
    RMvertex3D *boxverts;
    RMcolor4D *boxcolors;
    RMprimitive *q;
    RMvertex3D p1;
    RMvertex3D bmin, bmax;

    /* error check on functions, etc. */
    {
	int s1, s2, s3, s4;

	s1 = RM_ASSERT((void *)n, "rmvI3ScatterCube error: NULL RMnode for return parameter");
	s2 = RM_ASSERT((void *)appgridfunc, "rmvI3ScatterCube error: NULL app grid callback");
	s3 = RM_ASSERT((void *)appdatafunc, "rmvI3ScatterCube error: NULL app data callback ");
	s4 = RM_CHILL;
	if (!(((vmap != NULL) && (appdata2func != NULL)) || ((vmap == NULL) && (appdata2func == NULL))))
	    s4 = RM_ASSERT((void *)NULL, "rmvI3ScatterCube error: the vismap and secondary data callback function must BOTH be NULL or defined.");

	if ((s1 ==  RM_WHACKED) || (s2 == RM_WHACKED) || (s3 == RM_WHACKED) || (s4 == RM_WHACKED))
	    return(RM_WHACKED);
    }
    
    q = rmPrimitiveNew(RM_BOX3D);
    
    nboxes = npts;
    nverts_per_box =  2;

    boxverts = rmVertex3DNew(nverts_per_box * nboxes);

    if (appdata2func != NULL && vmap != NULL)
	boxcolors = rmColor4DNew(nboxes);
    else
	boxcolors = NULL;

    loc_scale = scale * 0.5;

    dest_offset = 0;
    
    for (i = 0; i < npts; i++)
    {
	float d;
	
	/* the base vertex will be the same as the grid point */
	p1 = (*appgridfunc)(i, appGridUsrData);
	d = (*appdatafunc)(i, appDataUsrData);
	
	/* the top vertex will be the grid point plus some offset value */
	switch (axis_offset_enum)
	   {
	   case RMV_XAXIS_OFFSET:
	      p1.x += CLAMP(d, smallnum);
	      break;
	      
	   case RMV_YAXIS_OFFSET:
	      p1.y += CLAMP(d, smallnum);
	      break;
	      
	   case RMV_ZAXIS_OFFSET:
	      p1.z += CLAMP(d, smallnum);
	      break;
	      
	   default: /* bogus axis offset enum */
	      break;
	   }

	VCOPY(&p1, &bmin);
	VCOPY(&p1, &bmax);

	bmin.x -= loc_scale;
	bmin.y -= loc_scale;
	bmin.z -= loc_scale;

	bmax.x += loc_scale;
	bmax.y += loc_scale;
	bmax.z += loc_scale;

	VCOPY(&bmin, (boxverts + dest_offset));
	dest_offset++;
	VCOPY(&bmax, (boxverts + dest_offset));
	dest_offset++;

	if (boxcolors)
	{
	    int   j;
	    float d2;

	    d2 = (*appdata2func)(i, appData2UsrData);
	    j = rmVismapIndexFromData(vmap, d2);
	    rmVismapGetColor4D(vmap,j , (boxcolors+i));
	}
    }

    rmPrimitiveSetVertex3D(q, nboxes * nverts_per_box, (void *)boxverts, RM_COPY_DATA, NULL);

    if (boxcolors)
    {
	rmPrimitiveSetColor4D(q, nboxes, boxcolors, RM_COPY_DATA, NULL);
	rmColor4DDelete(boxcolors);
    }

    /* now, add the new primitive onto the node */
    rmNodeAddPrimitive(n,q);

    /* compute the bounding box and center point for the node */
    private_rmvSetBox(n);
    rmVertex3DDelete (boxverts);

    /* turn on backface culling */
    rmNodeSetPolygonCullMode(n, RM_CULL_BACK);
    rmNodeSetFrontFace(n, RM_CCW);

    return(RM_CHILL);
}


/*
 * ----------------------------------------------------
 * @Name rmvI3ScatterWireCube
 @pstart
 RMenum rmvI3ScatterWireCube (RMvertex3D (*appgridfunc)(int i, void *usrData),
                              void *appGridUsrData,
		              float (*appdatafunc)(int i, void *usrData),
			      void *appDataUsrData,
			      float (*appdata2func)(int i, void *usrData),
			      void *appData2UsrData,
			      RMvisMap *vmap,
			      int axis_offset_enum,
			      int npts,
			      float scale,
			      RMenum linewidth,
			      RMenum linestyle,
			      RMnode *n)
 @pend

 @astart
 RMvertex3D (*appgridfunc)(int i, void *appGridUsrData) - a handle to a
    caller-supplied function that returns an RMvertex3D (x, y, z)
    corresponding to the grid point (i) (input).
	          
 void *appGridUsrData - (input, optional) you can pass in a void * to
    user data; this void * will be provided as a parameter to the user-defined
    grid evalution callback. If you don't want to pass in a void * to any
    user data for the grid evaluation callback, pass in a NULL for this
    parameter.

 float (*appdatafunc)(int i, void *usrData) - a handle to a
    caller-supplied function that returns a float, which is the scalar
    value at the grid point (i) (input).

 void *appDataUsrData - (optional, input) you can pass in a void * to user
    data; this void * will be provided as a parameter to the user-defined
    data field evaluation callback. If you don't want to pass in a void *
    to any user data for the primary data field grid evaluation callback,
    pass in a NULL for this parameter.
			   
 float (*appdata2func)(int i, void *usrData) -- (input, optional)
    A handle to a caller-supplied function that returns a float, which is the
    scalar value at the grid point (i) of the optional secondary data field.
    RMV uses the float returned by appdata2func in conjunction with the
    RMvismap to compute vertex color.

 void *appData2UsrData - (optional, input) you can pass in a void * to user
    data; this void * will be provided as a parameter to the user-defined
    secondary data field evaluation callback. If you don't want to pass in
    a void * to any user data for the secondary data field evaluation
    callback, pass in a NULL for this parameter.
    
 RMvisMap *map - a handle to an RMvisMap object (input).
			      
 int axis_offset_enum - an integer specifying in which axis to offset
    the wireframe cube.  Must be one of RMV_XAXIS_OFFSET,
    RMV_YAXIS_OFFSET, or RMV_ZAXIS_OFFSET (input).
			      
 int npts - an integer specifying number of data points (input).
			      
 float scale - float specifying the size of the wireframe cubes,
    measured in world coordinates (input).
			      
 RMenum linewidth_enum - an RMenum specifying the line width.  Must be
    one of RM_LINEWIDTH_NARROW, RM_LINEWIDTH_MEDIUM,
    RM_LINEWIDTH_HEAVY, or RM_LINEWIDTH_[1..8] (input).

 RMenum linestyle_enum - an RMenum specifying the lline style.  Must
    be one of RM_LINES_SOLID, RM_LINES_DASHED, RM_LINES_DOTTED,
    RM_LINES_DOT_DASH, or RM_LINES_DASH_DASH_DOT (input).
			 
 RMnode *n - a handle to an RMnode (modified).
 @aend

 @dstart

 Creates a 3D scatterplot from data, with each position represented by
 a wireframe cube.  The scatterplot is a single color, the RMnode
 default color at render time.  The wireframe cubes can also be
 colored via the secondary data function and the RMvismap.

 Upon success, RM_CHILL is returned and the scatterplot wireframe cube
 primitives are added to the RMnode.  Otherwise, RM_WHACKED is
 returned.

 @dend
 * ----------------------------------------------------
 */
RMenum
rmvI3ScatterWireCube (RMvertex3D (*appgridfunc)(int i, void *usrData),
		      void *appGridUsrData,
		      float (*appdatafunc)(int i, void *usrData),
		      void *appDataUsrData,
		      float (*appdata2func)(int i, void *usrData),
		      void *appData2UsrData,
		      RMvisMap *vmap,
		      int axis_offset_enum,
		      int npts,
		      float scale,
		      RMenum linewidth,
		      RMenum linestyle,
		      RMnode *n)
{
    int          i, nboxes, indx, nverts_per_box;
    float        d;
    float        smallnum = 0.1; /* this needs to be computed analytically */
    RMvertex3D  *boxverts;
    RMcolor4D   *boxcolors=NULL;
    RMcolor4D    rgb;
    RMprimitive *q;
    RMvertex3D   p1;
    RMvertex3D   bmin, bmax;

    /* error check on functions, etc. */
    {
	int s1,s2,s3,s4;

	s1 = RM_ASSERT((void *)n, "rmvI3ScatterWireCube error: NULL RMnode for return parameter");
	s2 = RM_ASSERT((void *)appgridfunc, "rmvI3ScatterWireCube error: NULL app grid callback");
	s3 = RM_ASSERT((void *)appdatafunc, "rmvI3ScatterWireCube error: NULL app data callback ");
	s4 = RM_CHILL;
	if (!(((vmap != NULL) && (appdata2func != NULL)) || ((vmap == NULL) && (appdata2func == NULL))))
	    s4 = RM_ASSERT((void *)NULL, "rmvI3ScatterWireCube error: the vismap and secondary data callback function must BOTH be NULL or defined.");

	if ((s1 ==  RM_WHACKED) || (s2 == RM_WHACKED) || (s3 == RM_WHACKED) || (s4 == RM_WHACKED))
	    return(RM_WHACKED);
    }
    
    q = rmPrimitiveNew(RM_LINES);

    nboxes = npts;
    nverts_per_box = 24;

    boxverts = rmVertex3DNew(nverts_per_box * nboxes);

    if ((appdata2func != NULL) && (vmap != NULL))
        boxcolors = rmColor4DNew(nverts_per_box * nboxes);
    else
        boxcolors = NULL;

    d = scale * 0.5;
    indx = 0;

    for (i = 0; i < npts; i++)
    {
	float data;
	
	p1 = (*appgridfunc)(i, appGridUsrData);
	data = (*appdatafunc)(i, appDataUsrData);

	switch (axis_offset_enum)
	   {
	   case RMV_XAXIS_OFFSET:
	      p1.x += CLAMP(data,smallnum);
	      break;
	      
	   case RMV_YAXIS_OFFSET:
	      p1.y += CLAMP(data,smallnum);
	      break;
	      
	   case RMV_ZAXIS_OFFSET:
	      p1.z += CLAMP(data,smallnum);
	      break;
	      
	   default: /* bogus axis offset enum */
	      break;
	   }

	VCOPY(&p1, &bmin);
	VCOPY(&p1, &bmax);

	bmin.x -= d;
	bmin.y -= d;
	bmin.z -= d;

	bmax.x += d;
	bmax.y += d;
	bmax.z += d;

	if (boxcolors)
	{
	    int   j;
	    float d2;

	    d2 = (*appdata2func)(i, appData2UsrData);
	    j = rmVismapIndexFromData(vmap, d2);
	    rmVismapGetColor4D(vmap, j, &rgb);
	}
	private_AxisAlignedWireBox(&bmin, &bmax, boxverts, &indx, &rgb, boxcolors);

    }
    rmNodeSetLineWidth(n, linewidth);
    rmNodeSetLineStyle(n, linestyle);

    rmPrimitiveSetVertex3D(q, (nboxes * nverts_per_box), (void *)boxverts, RM_COPY_DATA, NULL);

    if (boxcolors)
    {
        rmPrimitiveSetColor4D(q, (nboxes * nverts_per_box), boxcolors, RM_COPY_DATA, NULL);
	rmColor4DDelete(boxcolors);
    }

    /* now, add the new primitive onto the node */
    rmNodeAddPrimitive(n, q);

    private_rmvSetBox(n);

    rmVertex3DDelete (boxverts);
    return(RM_CHILL);
}


/*
 * ----------------------------------------------------
 * @Name rmvI3ScatterSphere
 @pstart
 RMenum rmvI3ScatterSphere (RMvertex3D (*appgridfunc)(int i, void *usrData),
                            void *appGridUsrData,
		            float (*appdatafunc)(int i, void *usrData),
			    void *appDataUsrData,
			    float (*appdata2func)(int i, void *usrData),
			    void *appData2UsrData,
			    RMvisMap *vmap,
			    int axis_offset_enum,
			    int npts,
			    float scale,
			    RMnode *n)
 @pend

 @astart
 RMvertex3D (*appgridfunc)(int i, void *appGridUsrData) - a handle to a
    caller-supplied function that returns an RMvertex3D (x, y, z)
    corresponding to the grid point (i) (input).
	          
 void *appGridUsrData - (input, optional) you can pass in a void * to
    user data; this void * will be provided as a parameter to the user-defined
    grid evalution callback. If you don't want to pass in a void * to any
    user data for the grid evaluation callback, pass in a NULL for this
    parameter.

 float (*appdatafunc)(int i, void *usrData) - a handle to a
    caller-supplied function that returns a float, which is the scalar
    value at the grid point (i) (input).

 void *appDataUsrData - (optional, input) you can pass in a void * to user
    data; this void * will be provided as a parameter to the user-defined
    data field evaluation callback. If you don't want to pass in a void *
    to any user data for the primary data field grid evaluation callback,
    pass in a NULL for this parameter.
			   
 float (*appdata2func)(int i, void *usrData) -- (input, optional)
    A handle to a caller-supplied function that returns a float, which is the
    scalar value at the grid point (i) of the optional secondary data field.
    RMV uses the float returned by appdata2func in conjunction with the
    RMvismap to compute vertex color.

 void *appData2UsrData - (optional, input) you can pass in a void * to user
    data; this void * will be provided as a parameter to the user-defined
    secondary data field evaluation callback. If you don't want to pass in
    a void * to any user data for the secondary data field evaluation
    callback, pass in a NULL for this parameter.
    
 RMvisMap *vmap - a handle to an RMvisMap object (input).
			    
 int axis_offset_enum - an integer specifying in which axis to offset
    the sphere.  Must be one of RMV_XAXIS_OFFSET, RMV_YAXIS_OFFSET, or
    RMV_ZAXIS_OFFSET (input).

 int npts - int specifying number of data points (input).
			    
 float scale - float specifying the size of the spheres, measured in
    world coordinates (input).
			    
 RMnode *n - a handle to an RMnode (modified).
 @aend

 @dstart

 Creates a 3D scatterplot from data, with each position represented by
 a sphere.  The scatterplot is a single color, the RMnode default color
 at render time.  The spheres can also be colored via the secondary
 data function and the RMvismap. 

 Upon success, RM_CHILL is returned and the scatterplot sphere
 primitives are added to the RMnode.  Otherwise, RM_WHACKED is
 returned.

 @dend
 * ----------------------------------------------------
 */
RMenum
rmvI3ScatterSphere (RMvertex3D (*appgridfunc)(int i, void *usrData),
		    void *appGridUsrData,
		    float (*appdatafunc)(int i, void *usrData),
		    void *appDataUsrData,
		    float (*appdata2func)(int i, void *usrData),
		    void *appData2UsrData,
		    RMvisMap *vmap,
		    int axis_offset_enum,
		    int npts,
		    float scale,
		    RMnode *n)
{
    int          i, nspheres;
    float       *rads;
    RMvertex3D  *spherecenters;
    RMcolor4D   *scolors;
    RMprimitive *q;
    RMvertex3D  *p;

    /* error check on functions, etc. */
    {
	int s1, s2, s3, s4;

	s1 = RM_ASSERT((void *)n, "rmvI3ScatterSphere error: NULL RMnode for return parameter");
	s2 = RM_ASSERT((void *)appgridfunc, "rmvI3ScatterSphere error: NULL app grid callback");
	s3 = RM_ASSERT((void *)appdatafunc, "rmvI3ScatterSphere error: NULL app data callback ");
	s4 = RM_CHILL;
	if (!(((vmap != NULL) && (appdata2func != NULL)) || ((vmap == NULL) && (appdata2func == NULL))))
	    s4 = RM_ASSERT((void *)NULL, "rmvI3ScatterSphere error: the vismap and secondary data callback function must BOTH be NULL or defined.");

	if ((s1 ==  RM_WHACKED) || (s2 == RM_WHACKED) || (s3 == RM_WHACKED) || (s4 == RM_WHACKED))
	    return(RM_WHACKED);
    }
    q = rmPrimitiveNew(RM_SPHERES);

    nspheres = npts;

    p = spherecenters = rmVertex3DNew(nspheres);
    rads = rmFloatNew(nspheres);

    if ((appdata2func != NULL) && (vmap != NULL))
	scolors = rmColor4DNew(nspheres);
    else
	scolors = NULL;

    for (i = 0; i < npts; i++)
    {
	float d;
	
	p[i] = (*appgridfunc)(i, appGridUsrData);
	d = (*appdatafunc)(i, appDataUsrData);

	/*
	 * the center point of the sphere will be the grid
	 * point plus some offset value.
	 */
	switch (axis_offset_enum)
	   {
	   case RMV_XAXIS_OFFSET:
	      p[i].x += d;
	      break;
	      
	   case RMV_YAXIS_OFFSET:
	      p[i].y += d;
	      break;
	      
	   case RMV_ZAXIS_OFFSET:
	      p[i].z += d;
	      break;
	      
	   default: /* bogus axis offset enum */
	      break;
	   }

	rads[i] = scale;

	if (scolors)
	{
	    int j;
	    float d2;
	    d2 = (*appdata2func)(i, appData2UsrData);
	    j = rmVismapIndexFromData(vmap, d2);
	    rmVismapGetColor4D(vmap, j, (scolors + i));
	}
    }

    rmPrimitiveSetVertex3D(q, nspheres, (void *)spherecenters, RM_COPY_DATA, NULL);
    rmPrimitiveSetRadii(q, nspheres, rads, RM_COPY_DATA, NULL);

    if (scolors)
    {
        rmPrimitiveSetColor4D(q, nspheres, scolors, RM_COPY_DATA, NULL);
	rmColor4DDelete(scolors);
    }

    /* now, add the new primitive onto the node */
    rmNodeAddPrimitive(n, q);

    /* compute the bounding box and center point for the node */
    private_rmvSetBox(n);
    
    rmVertex3DDelete (spherecenters);
    rmFloatDelete(rads);
    return(RM_CHILL);
}


/*
 * ----------------------------------------------------
 * @Name rmvI3ScatterGlyph
 @pstart
 RMenum rmvI3ScatterGlyph (RMvertex3D (*appgridfunc)(int i, void *usrData),
                           void *appGridUsrData,
		           float (*appdatafunc)(int i, void *usrData),
			   void *appDataUsrData,
			   float (*appdata2func)(int i, void *usrData),
			   void *appData2UsrData,
			   RMvisMap *vmap,
			   int axis_offset_enum,
			   int npts,
			   RMenum size_enum,
			   RMenum marker_enum,
			   RMnode *n)
 @pend

 @astart
 RMvertex3D (*appgridfunc)(int i, void *appGridUsrData) - a handle to a
    caller-supplied function that returns an RMvertex3D (x, y, z)
    corresponding to the grid point (i) (input).
	          
 void *appGridUsrData - (input, optional) you can pass in a void * to
    user data; this void * will be provided as a parameter to the user-defined
    grid evalution callback. If you don't want to pass in a void * to any
    user data for the grid evaluation callback, pass in a NULL for this
    parameter.

 float (*appdatafunc)(int i, void *usrData) - a handle to a
    caller-supplied function that returns a float, which is the scalar
    value at the grid point (i) (input).

 void *appDataUsrData - (optional, input) you can pass in a void * to user
    data; this void * will be provided as a parameter to the user-defined
    data field evaluation callback. If you don't want to pass in a void *
    to any user data for the primary data field grid evaluation callback,
    pass in a NULL for this parameter.
			   
 float (*appdata2func)(int i, void *usrData) -- (input, optional)
    A handle to a caller-supplied function that returns a float, which is the
    scalar value at the grid point (i) of the optional secondary data field.
    RMV uses the float returned by appdata2func in conjunction with the
    RMvismap to compute vertex color.

 void *appData2UsrData - (optional, input) you can pass in a void * to user
    data; this void * will be provided as a parameter to the user-defined
    secondary data field evaluation callback. If you don't want to pass in
    a void * to any user data for the secondary data field evaluation
    callback, pass in a NULL for this parameter.
			   
 RMvisMap *vmap - a handle to an RMvisMap object (input).
			   
 int axis_offset_enum - an integer specifying in which axis to
    offset the glyph.  Must be one of RMV_XAXIS_OFFSET,
    RMV_YAXIS_OFFSET, or RMV_ZAXIS_OFFSET (input).
	   
 int npts - int specifying number of glyph points (input).
			   
 RMenum size_enum - an RMenum specifying the size of the markers
    (input).  Must be one of RM_FONT_XXS, RM_FONT_XS, RM_FONT_S,
    RM_FONT_M, RM_FONT_L, RM_FONT_XL, RM_FONT_XXL (input).
			   
 RMenum marker_enum - an Rmenum specifying the marker type (input).
    Must be one of RMV_ZAPFMARKER_STAR_FILLED,
    RMV_ZAPFMARKER_STAR_UNFILLED, RMV_ZAPFMARKER_ASTERIX_FILLED,
    RMV_ZAPFMARKER_ASTERIX_UNFILLED, RMV_ZAPFMARKER_CIRCLE_FILLED,
    RMV_ZAPFMARKER_CIRCLE_UNFILLED, RMV_ZAPFMARKER_SQUARE_FILLED,
    RMV_ZAPFMARKER_SQUARE_UNFILLED, RMV_ZAPFMARKER_UPTRIANGLE_FILLED,
    RMV_ZAPFMARKER_DOWNTRIANGLE_FILLED, RMV_ZAPFMARKER_CLUBS_FILLED,
    RMV_ZAPFMARKER_DIAMONDS_FILLED, RMV_ZAPFMARKER_HEARTS_FILLED,
    RMV_ZAPFMARKER_SPADES_FILLED (input).
   
 RMnode *n - a handle to an RMnode (modified).
 @aend

 @dstart

 Creates a 3D scatterplot from data, with each position represented
 with a glyph from the Zapf-Dingbats font.  Note that the precise size
 of the markers onscreen is implementation dependent.  The scatterplot
 is a single color, the RMnode default color at render time.  The
 glyph markers can also be colored via the secondary data function and
 the RMvismap.

 Upon success, RM_CHILL is returned and the scatterplot glyph
 primitives are added to the RMnode.  Otherwise, RM_WHACKED is
 returned.

 @dend
 * ----------------------------------------------------
 */
RMenum
rmvI3ScatterGlyph (RMvertex3D (*appgridfunc)(int i, void *usrData),
		   void *appGridUsrData,
		   float (*appdatafunc)(int i, void *usrData),
		   void *appDataUsrData,
		   float (*appdata2func)(int i, void *usrData),
		   void *appData2UsrData,
		   RMvisMap *vmap,
		   int axis_offset_enum,
		   int npts,
		   RMenum size_enum,
		   RMenum marker_enum,
		   RMnode *n)
{
    int          i;
    int         *index_list;
    RMprimitive *t;
    RMvertex3D  *v;
    RMcolor4D   *c;

    /* error check on functions, etc. */
    {
	int s1, s2, s3, s4;

	s1 = RM_ASSERT((void *)n, "rmvI3ScatterGlyph error: NULL RMnode for return parameter");
	s2 = RM_ASSERT((void *)appgridfunc, "rmvI3ScatterGlyph error: NULL app grid callback");
	s3 = RM_ASSERT((void *)appdatafunc, "rmvI3ScatterGlyph error: NULL app data callback ");
	s4 = RM_CHILL;
	if (!(((vmap != NULL) && (appdata2func != NULL)) || ((vmap == NULL) && (appdata2func == NULL))))
	    s4 = RM_ASSERT((void *)NULL, "rmvI3ScatterGlyph error: the vismap and secondary data callback function must BOTH be NULL or defined.");

	if ((s1 ==  RM_WHACKED) || (s2 == RM_WHACKED) || (s3 == RM_WHACKED) || (s4 == RM_WHACKED))
	    return(RM_WHACKED);
    }
    
    v = rmVertex3DNew(npts);
    t = rmPrimitiveNew(RM_INDEXED_TEXT);

    if (vmap != NULL)
	c = rmColor4DNew(npts);
    else
	c = NULL;

    /* first, build the list of (x,y) points */
    for (i = 0; i < npts; i++)
    {
	float data;
	
	v[i] = (*appgridfunc)(i, appGridUsrData);
	data = (*appdatafunc)(i, appDataUsrData);
	
	switch (axis_offset_enum)
	   {
	   case RMV_XAXIS_OFFSET: 
	      v[i].x += data;
	      break;
	      
	   case RMV_YAXIS_OFFSET:
	      v[i].y += data;
	      break;
	      
	   case RMV_ZAXIS_OFFSET:
	      v[i].z += data;
	      break;
	      
	   default: /* bogus axis offset enum */
	      break;
	   }

	if (c)
	{
	    int   k;
	    float d2;

	    d2 = (*appdata2func)(i, appData2UsrData);
	    k = rmVismapIndexFromData(vmap, d2);
	    rmVismapGetColor4D(vmap, k, (c + i));
	}
    }

    /* next, create a single bitmap for the requested marker type */
    switch (marker_enum)
       {
	  
       case RMV_ZAPFMARKER_STAR_FILLED:
       case RMV_ZAPFMARKER_STAR_UNFILLED:
       case RMV_ZAPFMARKER_ASTERIX_FILLED :
       case RMV_ZAPFMARKER_ASTERIX_UNFILLED:
       case RMV_ZAPFMARKER_CIRCLE_FILLED:
       case RMV_ZAPFMARKER_CIRCLE_UNFILLED:
       case RMV_ZAPFMARKER_SQUARE_FILLED:
       case RMV_ZAPFMARKER_SQUARE_UNFILLED:
       case RMV_ZAPFMARKER_UPTRIANGLE_FILLED:
       case RMV_ZAPFMARKER_DOWNTRIANGLE_FILLED:
       case RMV_ZAPFMARKER_CLUBS_FILLED:
       case RMV_ZAPFMARKER_DIAMONDS_FILLED:
       case RMV_ZAPFMARKER_HEARTS_FILLED:
       case RMV_ZAPFMARKER_SPADES_FILLED:
	  {
	     char *c2[1];
	     char  c[2];
	     
	     c[0] = (char)marker_enum;
	     c[1] = '\0';
	     c2[0] = c;
	     
	     rmPrimitiveSetText(t, 1, c2);
	  }
       break;
       
       default:
	  fprintf(stderr," bogus glyph marker enum. \n");
	  break;
       }

    /* build a list of int's, the same length as the number of points
     * that we have.  these int's will contain indices into the
     * bitmap cache for the RM_TEXTCHARS primitive. 
     */
    index_list = (int *)malloc(sizeof(int) * npts);

    /*
     * set them all to zero - this is the same as going through
     * one by one and setting a[i] = 0.  the entire primitive is
     * going to use the text glyph cached at index 0 (that's just
     * how this particular vis routine is written).
     */
    memset(index_list, 0, sizeof(int) * npts);

    /* set the index list */
    rmPrimitiveSetIndices(t, npts, index_list, RM_COPY_DATA, NULL);
    rmPrimitiveSetVertex3D(t, npts, v, RM_COPY_DATA, NULL);

    if (c)
    {
        rmPrimitiveSetColor4D(t, npts, c, RM_COPY_DATA, NULL);
	rmColor4DDelete(c);
    }
    
    /* set justification to center/center for dingbat glyphs */
    {
	RMtextProps *tp;
	tp = rmTextPropsNew();
	rmTextPropsSetAttribs(tp, RM_FONT_DINGBATS, size_enum, RM_FALSE, RM_FALSE, RM_CENTER, RM_CENTER);
	rmNodeSetSceneTextProps(n, tp);
	/*rmTextPropsDelete(tp);*/	/* RM makes a copy */
    }

    /* now, add the new primitive onto the node */
    rmNodeAddPrimitive(n, t); 
    rmVertex3DDelete(v);
    free((void *)index_list);

    return(RM_CHILL);
}


/* PRIVATE */
void
private_AxisAlignedBox (RMvertex3D *bmin, 	/* input :min corner */
		        RMvertex3D *bmax, 	/* input: max corner */
		        RMvertex3D *v,    	/* output: triangle verts */
		        RMvertex3D *n,    	/* output: triangle norms */
		        int *rindex,  		/* input: offset used for v/n. upon return, contains updated offset */
		        float *rgb,
		        RMvertex3D *c,
		        int do_colors)
{
    int        index = *rindex;
#if 0 /* normals for six faces */
    RMvertex3D bn[6] = {{0.0, 0.0, -1.0}, {-1.0, 0.0, 0.0}, {0.0, 0.0, 1.0}, {1.0, 0.0, 0.0}, {0.0, 1.0, 0.0}, {0.0, -1.0, 0.0}};
#endif
    RMvertex3D bn[6] = {{0.0, 0.0, -1.0}, {1.0, 0.0, 0.0}, {0.0, 0.0, 1.0}, {-1.0, 0.0, 0.0}, {0.0, 1.0, 0.0}, {0.0, -1.0, 0.0}};
    RMvertex3D lv[8];

    /* set up for fast processing */
    VCOPY(bmin, lv);
    VCOPY(bmin, (lv + 1));  lv[1].x = bmax->x;
    VCOPY(bmax, (lv + 2));  lv[2].y = bmin->y;
    VCOPY(bmin, (lv + 3));  lv[3].z = bmax->z;

    VCOPY(bmin, (lv + 4));  lv[4].y = bmax->y;
    VCOPY(bmax, (lv + 5));  lv[5].z = bmin->z;
    VCOPY(bmax, (lv + 6));
    VCOPY(bmax, (lv + 7));  lv[7].x = bmin->x;

    /* do front face: triangle 1 */
    VCOPY(lv, (v + index));
    VCOPY((lv + 1), (v + index + 2));
    VCOPY((lv + 5), (v + index + 1));
    
    VCOPY(bn, (n + index));
    VCOPY(bn, (n + index + 2));
    VCOPY(bn, (n + index + 1));

    if (do_colors)
    {
	VCOPY(rgb, (c + index));
	VCOPY(rgb, (c + index + 2));
	VCOPY(rgb, (c + index + 1));
    }

    index += 3;			/* next triangle */
    VCOPY(lv, (v + index));
    VCOPY((lv + 5), (v + index + 2));
    VCOPY((lv + 4), (v + index + 1));

    VCOPY(bn, (n + index));
    VCOPY(bn, (n + index + 2));
    VCOPY(bn, (n + index + 1));

    if (do_colors)
    {
	VCOPY(rgb, (c + index));
	VCOPY(rgb, (c + index + 2));
	VCOPY(rgb, (c + index + 1));
    }
    index += 3;

    /* do right-facing face */
    VCOPY((lv + 1), (v + index));
    VCOPY((lv + 2), (v + index + 2));
    VCOPY((lv + 6), (v + index + 1));
    
    VCOPY((bn + 1), (n + index));
    VCOPY((bn + 1), (n + index + 2));
    VCOPY((bn + 1), (n + index + 1));
    if (do_colors)
    {
	VCOPY(rgb, (c + index));
	VCOPY(rgb, (c + index + 2));
	VCOPY(rgb, (c + index + 1));
    }
    index += 3;			/* next triangle */
    
    VCOPY((lv + 1), (v + index));
    VCOPY((lv + 6), (v + index + 2));
    VCOPY((lv + 5), (v + index + 1));
    
    VCOPY((bn + 1), (n + index));
    VCOPY((bn + 1), (n + index + 2));
    VCOPY((bn + 1), (n + index + 1));
    if (do_colors)
    {
	VCOPY(rgb, (c + index));
	VCOPY(rgb, (c + index + 2));
	VCOPY(rgb, (c + index + 1));
    }
    index += 3;			/* next triangle */

    /* do back facing face */
    VCOPY((lv + 2), (v + index));
    VCOPY((lv + 3), (v + index + 2));
    VCOPY((lv + 7), (v + index + 1));
    
    VCOPY((bn + 2), (n + index));
    VCOPY((bn + 2), (n + index + 2));
    VCOPY((bn + 2), (n + index + 1));
    if (do_colors)
    {
	VCOPY(rgb,(c + index));
	VCOPY(rgb,(c + index + 2));
	VCOPY(rgb,(c + index + 1));
    }
    index += 3;
    
    VCOPY((lv + 2), (v + index));
    VCOPY((lv + 7), (v + index + 2));
    VCOPY((lv + 6), (v + index + 1));
    
    VCOPY((bn + 2), (n + index));
    VCOPY((bn + 2), (n + index + 2));
    VCOPY((bn + 2), (n + index + 1));
    if (do_colors)
    {
	VCOPY(rgb, (c + index));
	VCOPY(rgb, (c + index + 2));
	VCOPY(rgb, (c + index + 1));
    }
    index += 3;

    /* do left facing face */
    VCOPY((lv + 3), (v + index));
    VCOPY(lv, (v + index + 2));
    VCOPY((lv + 4), (v + index + 1));

    VCOPY((bn + 3), (n + index));
    VCOPY((bn + 3), (n + index + 2));
    VCOPY((bn + 3), (n + index + 1));
    if (do_colors)
    {
	VCOPY(rgb, (c + index));
	VCOPY(rgb, (c + index + 2));
	VCOPY(rgb, (c + index + 1));
    }

    index += 3;
    
    VCOPY((lv + 3), (v + index));
    VCOPY((lv + 4), (v + index + 2));
    VCOPY((lv + 7), (v + index + 1));

    VCOPY((bn + 3), (n + index));
    VCOPY((bn + 3), (n + index + 2));
    VCOPY((bn + 3), (n + index + 1));
    if (do_colors)
    {
	VCOPY(rgb, (c + index));
	VCOPY(rgb, (c + index + 2));
	VCOPY(rgb, (c + index + 1));
    }

    index += 3;

    /* do top facing face */
    VCOPY((lv + 4), (v + index));
    VCOPY((lv + 5), (v + index + 2));
    VCOPY((lv + 6), (v + index + 1));
    
    VCOPY((bn + 4), (n + index));
    VCOPY((bn + 4), (n + index + 2));
    VCOPY((bn + 4), (n + index + 1));

    if (do_colors)
    {
	VCOPY(rgb, (c + index));
	VCOPY(rgb, (c + index + 2));
	VCOPY(rgb, (c + index + 1));
    }
    index += 3;
    
    VCOPY((lv + 4), (v + index));
    VCOPY((lv + 6), (v + index + 2));
    VCOPY((lv + 7), (v + index + 1));
    
    VCOPY((bn + 4), (n + index));
    VCOPY((bn + 4), (n + index + 2));
    VCOPY((bn + 4), (n + index + 1));

    if (do_colors)
    {
	VCOPY(rgb, (c + index));
	VCOPY(rgb, (c + index + 2));
	VCOPY(rgb, (c + index + 1));
    }
    index += 3;

    /* do bottom facing face */
    VCOPY((lv + 3), (v + index));
    VCOPY((lv + 2), (v + index + 2));
    VCOPY((lv + 1), (v + index + 1));
    
    VCOPY((bn + 5), (n + index));
    VCOPY((bn + 5), (n + index + 2));
    VCOPY((bn + 5), (n + index + 1));
    if (do_colors)
    {
	VCOPY(rgb, (c + index));
	VCOPY(rgb, (c + index + 2));
	VCOPY(rgb, (c + index + 1));
    }

    index += 3;
    
    VCOPY((lv + 3), (v+index));
    VCOPY((lv + 1), (v+index + 2));
    VCOPY(lv, (v+index + 1));
    
    VCOPY((bn + 5), (n + index));
    VCOPY((bn + 5), (n + index + 2));
    VCOPY((bn + 5), (n + index + 1));
    if (do_colors)
    {
	VCOPY(rgb, (c + index));
	VCOPY(rgb, (c + index + 2));
	VCOPY(rgb, (c + index + 1));
    }
    index += 3;

    *rindex = index;
}


/* PRIVATE */
void
private_rmv3DGetBarScale (RMvertex3D (*appgridfunc)(int i, int j, void *usrData),
			  void *appGridUsrData,
			  RMvertex3D *base_point,
			  int iu,
			  int usize,
			  int iv,
			  int vsize,
			  float scale,
			  int scale_enum,
			  int axis_offset_enum,
			  float *dx,
			  float *dy,
			  float *dz)
{
    /* foil compiler warning */
    usize = vsize = 0;
    
    if (scale_enum == RMV_SCALE_ABSOLUTE)
    {
       switch (axis_offset_enum)
	  {
	  case RMV_XAXIS_OFFSET:
	     *dx = 0.0F;
	     *dz = *dy = scale * 0.5F;
	     break;
	     
	  case RMV_YAXIS_OFFSET:
	     *dz = *dx = scale * 0.5F;
	     *dy = 0.0F;
	     break;
	     
	  case RMV_ZAXIS_OFFSET:
	     *dx = *dy = scale * 0.5F;
	     *dz = 0.0;
	     break;
	     
	  default: /* bogus axis offset enum */
	     break;
	  }
    }
    else /* RMV_SCALE_RELATIVE */
    {
	/* need two points: "next" in i and "next" in j */
	float deltax_i, deltay_i, deltaz_i;
	float deltax_j, deltay_j, deltaz_j;
	
	RMvertex3D next_point;

	if (iu == 0)
	{
	    next_point = (*appgridfunc)((iu + 1), iv, appGridUsrData);
	    deltax_i = next_point.x - base_point->x;
	    deltay_i = next_point.y - base_point->y;
	    deltaz_i = next_point.z - base_point->z;
	}
	else
	{
	    next_point = (*appgridfunc)((iu - 1), iv, appGridUsrData);
	    deltax_i = base_point->x - next_point.x;
	    deltay_i = base_point->y - next_point.y;
	    deltaz_i = base_point->z - next_point.z;
	}
	
	if (iv == 0)
	{
	    next_point = (*appgridfunc)(iu, (iv + 1), appGridUsrData);
	    deltax_j = next_point.x - base_point->x;
	    deltay_j = next_point.y - base_point->y;
	    deltaz_j = next_point.z - base_point->z;
	}
	else
	{
	    next_point = (*appgridfunc)(iu, (iv - 1), appGridUsrData);
	    deltax_j = base_point->x - next_point.x;
	    deltay_j = base_point->y - next_point.y;
	    deltaz_j = base_point->z - next_point.z;
	}
	
	switch (axis_offset_enum)
	   {
	   case RMV_XAXIS_OFFSET:
	      *dx = 0.0F;
	      *dy = deltay_i * scale * 0.5F;
	      *dz = deltaz_j * scale * 0.5F;
	      break;
	      
	   case RMV_YAXIS_OFFSET:
	      *dx = deltax_i * scale * 0.5F;
	      *dy = 0.0F;
	      *dz = deltaz_j * scale * 0.5F;
	      break;
	      
	   case RMV_ZAXIS_OFFSET:
	      *dx = deltax_i * scale * 0.5F;
	      *dy = deltay_j * scale * 0.5F;
	      *dz = 0.0F;
	      break;
	      
	   default: /* bogus axis offset enum */
	      break;
	   }
	
    } /* RMV_SCALE_RELATIVE */
}


/* PRIVATE */
void
private_rmvComputeHorizonNormals (RMvertex3D *v, 
				  RMvertex3D *norms,
				  int n)
{
    int        i;
    RMvertex3D p, r1, r2, c;

    /* assume: v is an array of size n*2. we will compute n-1 normals
     * v[0..n-1] is the amplitude, v[n..n*2-1] is the run
     */
    for (i = 0; i < (n - 1); i++)
    {
	if (i != (n - 1))
	    rmVertex3DDiff((v + i + n + 1), (v + n + i), &p); /* the run */

	rmVertex3DDiff((v + i + n), (v + i), &r1);
	rmVertex3DDiff((v + i + n + 1), (v + i + 1), &r2);

	r1.x = r1.x + r2.x;
	r1.y = r1.y + r2.y;
	r1.z = r1.z + r2.z;
	
	rmVertex3DNormalize(&p);
	rmVertex3DNormalize(&r1);
	rmVertex3DCross(&p, &r1, &c);
	rmVertex3DNormalize(&c);
#if 0	
	c.x *= -1.0;
	c.y *= -1.0;
	c.z *= -1.0;
#endif	    
	VCOPY(&c, (norms + i));
    }
}


/* PRIVATE */
void
private_rmvSetBox (RMnode *n)
{
    RMvertex3D bmin, bmax, center;
    
    rmNodeComputeBoundingBox(n);

    rmNodeGetBoundingBox(n, &bmin, &bmax);
    center.x = bmin.x + (0.5 * (bmax.x - bmin.x));
    center.y = bmin.y + (0.5 * (bmax.y - bmin.y));
    center.z = bmin.z + (0.5 * (bmax.z - bmin.z));
    
    rmNodeSetCenter(n, &center);
}


/* PRIVATE */
void
private_AxisAlignedWireBox (RMvertex3D *bmin, 	/* input :min corner */
			    RMvertex3D *bmax, 	/* input: max corner */
			    RMvertex3D *v,    	/* output: lineseg verts */
			    int *rindex,  	/* input: offset used for v /n. upon return, contains updated offset */
			    RMcolor4D *rgb,
			    RMcolor4D *c)
{
    int        index = *rindex;
    RMvertex3D lv[8];

    /* set up for fast processing.. */
    VCOPY(bmin, lv);
    VCOPY(bmin, (lv + 1));  
    lv[1].x = bmax->x;
    VCOPY(bmax, (lv + 2));  
    lv[2].y = bmin->y;
    VCOPY(bmin, (lv + 3));  
    lv[3].z = bmax->z;

    VCOPY(bmin, (lv + 4));  
    lv[4].y = bmax->y;
    VCOPY(bmax, (lv + 5));  
    lv[5].z = bmin->z;
    VCOPY(bmax, (lv + 6));
    VCOPY(bmax, (lv + 7));  
    lv[7].x = bmin->x;

    /* do front face */
    VCOPY(lv, (v + index));
    VCOPY((lv + 1), (v + index + 1));

    if (c != NULL)
    {
	c[index] = *rgb;
	c[index + 1] = *rgb;
    }
    index += 2;
    
    VCOPY((lv + 1), (v + index));
    VCOPY((lv + 5), (v + index + 1));

    if (c != NULL)
    {
	c[index] = *rgb;
	c[index + 1] = *rgb;
    }
    index += 2;
    
    VCOPY((lv + 5), (v + index));
    VCOPY((lv + 4), (v + index + 1));

    if (c != NULL)
    {
	c[index] = *rgb;
	c[index + 1] = *rgb;
    }
    index += 2;

    VCOPY((lv + 4), (v + index));
    VCOPY(lv, (v + index + 1));

    if (c != NULL)
    {
	c[index] = *rgb;
	c[index + 1] = *rgb;
    }
    index += 2;

    /* do back face */
    VCOPY((lv + 3), (v + index));
    VCOPY((lv + 2), (v + index + 1));

    if (c != NULL)
    {
	c[index] = *rgb;
	c[index + 1] = *rgb;
    }
    index += 2;
    
    VCOPY((lv + 2), (v + index));
    VCOPY((lv + 6), (v + index + 1));

    if (c != NULL)
    {
	c[index] = *rgb;
	c[index + 1] = *rgb;
    }
    index += 2;
    
    VCOPY((lv + 6), (v + index));
    VCOPY((lv + 7), (v + index + 1));

    if (c != NULL)
    {
	c[index] = *rgb;
	c[index + 1] = *rgb;
    }
    index += 2;

    VCOPY((lv + 7), (v + index));
    VCOPY((lv + 3), (v + index + 1));

    if (c != NULL)
    {
	c[index] = *rgb;
	c[index + 1] = *rgb;
    }
    index += 2;

    /* do connectors between front and back face */
    VCOPY((lv + 3), (v + index));
    VCOPY(lv, (v + index + 1));

    if (c != NULL)
    {
	c[index] = *rgb;
	c[index + 1] = *rgb;
    }
    index += 2;
    
    VCOPY((lv + 2), (v + index));
    VCOPY((lv + 1), (v + index + 1));

    if (c != NULL)
    {
	c[index] = *rgb;
	c[index + 1]= *rgb;
    }
    index+= 2;
    
    VCOPY((lv + 6), (v + index));
    VCOPY((lv + 5), (v + index + 1));

    if (c != NULL)
    {
	c[index] = *rgb;
	c[index + 1] = *rgb;
    }
    index += 2;

    VCOPY((lv + 7), (v + index));
    VCOPY((lv + 4), (v + index + 1));

    if (c != NULL)
    {
	c[index] = *rgb;
	c[index + 1] = *rgb;
    }
    index += 2;

    *rindex = index;
}


/* PRIVATE */
void
private_rmBuildHorizonObjects (RMnode *n,
			       int size,
			       float *xc,
			       float *yc,
			       float *zc,
			       float *data,
			       float *data2,
			       RMvisMap *vmap,
			       int axis_offset_enum,
			       float zerocrossing_value)
{
    int          nquads, do_colors, i, indx = 0;
    RMvertex3D  *v, *v2;
    RMcolor4D   *c;
    RMvertex3D  *tv, *tn, *nm;
    RMcolor4D   *tc = NULL;
    RMprimitive *t;

    v = rmVertex3DNew(size * 2);
    v2 = v + size;

    if ((vmap != NULL) && (data2 != NULL))
	do_colors = 1;
    else
	do_colors = 0;
	
    if (do_colors)
	c = rmColor4DNew(size);
    else
	c = NULL;

    for (i = 0; i < size; i++)
    {
       switch (axis_offset_enum)
	  {
	  case RMV_XAXIS_OFFSET:
	     v[i].x = xc[i] + data[i];
	     v[i].y = yc[i];
	     v[i].z = zc[i];
	     break;
	     
	  case RMV_YAXIS_OFFSET:
	     v[i].x = xc[i];
	     v[i].y = yc[i] + data[i];
	     v[i].z = zc[i];
	     break;
	     
	  case RMV_ZAXIS_OFFSET:
	     v[i].x = xc[i];
	     v[i].y = yc[i];
	     v[i].z = zc[i] + data[i];
	     break;
	     
	  default: /* bogus axis offset enum */
	     break;
	  }

	v2[i].x = xc[i];
	v2[i].y = yc[i];
	v2[i].z = zc[i] + ((axis_offset_enum == RMV_ZAXIS_OFFSET) ? zerocrossing_value : 0.0) ;

	if (do_colors)
	{
	    int indx = rmVismapIndexFromData(vmap, data2[i]);

	    rmVismapGetColor4D(vmap, indx, (c + i));
	}
    }

    t = rmPrimitiveNew(RM_QUADS);

    nquads = size - 1;
    nm = rmVertex3DNew(nquads);
    private_rmvComputeHorizonNormals(v, nm, size);
	    
    tv = rmVertex3DNew(nquads * 4);
    tn = rmVertex3DNew(nquads * 4);

    if (c != NULL)
	tc = rmColor4DNew(nquads * 4);
				
    for (i = 0; i < nquads; i++)
    {
	VCOPY((v + i), (tv + indx));
	VCOPY((nm + i), (tn + indx));
	if (c)
	    VCOPY((c + i), (tc + indx));
	indx++;
		
	VCOPY((v + i + 1), (tv + indx));
	VCOPY((nm + i), (tn + indx));
	if (c)
	    VCOPY((c + i + 1), (tc + indx));
	indx++;
		
	VCOPY((v + size + i + 1), (tv + indx));
	VCOPY((nm + i), (tn + indx));
	if (c)
	    VCOPY((c + i + 1), (tc + indx));
	indx++;
		
	VCOPY((v + size + i), (tv + indx));
	VCOPY((nm + i), (tn + indx));
	if (c)
	    VCOPY((c + i), (tc + indx));
	indx++;
    }

    rmPrimitiveSetVertex3D(t, (nquads * 4), tv, RM_COPY_DATA, NULL);

    rmPrimitiveSetNormal3D(t, (nquads * 4), tn, RM_COPY_DATA, NULL);

    if (c)
	rmPrimitiveSetColor4D(t, (nquads * 4), tc, RM_COPY_DATA, NULL);


    /* now, add the new primitive onto the node */
    rmNodeAddPrimitive(n, t);

    rmVertex3DDelete(nm);
    free(tv);
    free(tn);
    if (do_colors)
	free(tc);

    free(v);
    if (do_colors)
	free(c);
}


/* PRIVATE */
void
private_rmvMakeGrid (RMvertex3D *gmin,
		     RMvertex3D *gmax,
		     RMvertex3D *ref_normal,
		     RMnode *n,
		     int usize,
		     int vsize,
		     RMenum linewidth,
		     RMenum linestyle)
{
    int    i, j;
    float  dx, dy, dz;
    float *fastptr, fast, dfast, savefast;
    float *slowptr, slow, dslow;
    float *nochangeptr, nochange;
    float *xcoords, *ycoords, *zcoords;

    RMprimitive *q;
    RMvertex3D  *v, *normals;

    xcoords = (float *)malloc(sizeof(float) * usize * vsize);
    ycoords = (float *)malloc(sizeof(float) * usize * vsize);
    zcoords = (float *)malloc(sizeof(float) * usize * vsize);

    q = rmPrimitiveNew(RM_QUADMESH);

    v = rmVertex3DNew(usize * vsize);
    normals = rmVertex3DNew(usize * vsize);

    dx = gmax->x - gmin->x;
    dy = gmax->y - gmin->y;
    dz = gmax->z - gmin->z;

    if (dx != 0.0)		/* x varies fastest */
    {
	fastptr = xcoords;
	fast = savefast = gmin->x;
	dfast = dx / (usize - 1);

	if (dy != 0.0) /* y varies next fastest */
	{
	    slowptr = ycoords;
	    slow = gmin->y;
	    dslow = dy / (vsize - 1);

	    nochangeptr = zcoords;
	    nochange = gmin->z;
	}
	else /* assume zsize != 1, y is constant */
	{
	    slowptr = zcoords;
	    slow = gmin->z;
	    dslow = dz / (vsize - 1);

	    nochangeptr = ycoords;
	    nochange = gmin->y;
	}
    }
    else /* xsize == 1, we'll assume the other two dimensions are != 1*/
    {
	fastptr = ycoords;
	fast = savefast = gmin->y;
	dfast = dy/ (usize - 1);

	slowptr = zcoords;
	slow = gmin->z;
	dslow = dz / (vsize - 1);

	nochangeptr = xcoords;
	nochange = gmin->x;
    }

    for (j = 0; j < vsize; j++)
    {
	fast = savefast;
	for (i = 0; i < usize; i++)
	{
	    *fastptr++ = fast;   fast += dfast;
	    *slowptr++ = slow;
	    *nochangeptr++ = nochange;
	}
	slow += dslow;
    }

    for (i = 0; i < (usize * vsize); i++)
    {
	v[i].x = xcoords[i];
	v[i].y = ycoords[i];
	v[i].z = zcoords[i];
	
	VCOPY(ref_normal, (normals + i));
    }

    rmNodeSetLineStyle(n, linestyle);
    rmNodeSetLineWidth(n, linewidth);

    rmPrimitiveSetVertex3D(q, (usize * vsize), v, RM_COPY_DATA, NULL);
    rmPrimitiveSetNormal3D(q, (usize * vsize), normals, RM_COPY_DATA, NULL);
    
    /* set the mesh dimensions */
    rmPrimitiveSetQmeshDims(q, usize, vsize);

    /* now, add the new primitive onto the node */
    rmNodeAddPrimitive(n, q); 

    rmVertex3DDelete(v);
    rmVertex3DDelete(normals);
    free((void *)xcoords);
    free((void *)ycoords);
    free((void *)zcoords);
}


/* PRIVATE */
void
private_rmvMake2DGrid (RMvertex3D *gmin,
		       RMvertex3D *gmax,
		       RMvertex3D *ref_normal,
		       RMnode *n,
		       int usize,
		       int vsize,
		       RMenum linewidth,
		       RMenum linestyle)
{
    int          i, j, index = 0;
    int          fdims[2];
    float        x, dx, y, dy, z, dz;
    RMvertex3D  *v, *normals;
    RMprimitive *q;

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

    q = rmPrimitiveNew(RM_QUADMESH);

    v = rmVertex3DNew(usize * vsize);
    normals = rmVertex3DNew(usize * vsize);

    x = gmin->x;
    dx = (gmax->x - gmin->x) / (float)(usize - 1);

    y = gmin->y;
    dy = (gmax->y - gmin->y) / (float)(vsize - 1);

    z = gmin->z;
    dz = (gmax->z - gmin->z) / (float)(vsize - 1);

    for (j = 0; j < vsize; j++, y += dy)
    {
	x = gmin->x;
	z = ((float)(j) / (float)(vsize - 1) * dz) + gmin->z;
	
	for (i = 0; i < usize; i++, x += dx)
	{
	    v[index].x = x;
	    v[index].y = y;
	    v[index].z = z;
	
	    VCOPY(ref_normal, normals + index);
	    index++;
	}
    }

    rmNodeSetLineWidth(n, linewidth);
    rmNodeSetLineStyle(n, linestyle);

    rmPrimitiveSetVertex3D(q, (usize * vsize), v, RM_COPY_DATA, NULL);
    rmPrimitiveSetNormal3D(q, (usize * vsize), normals, RM_COPY_DATA, NULL);
    
    /* set the mesh dimensions */
    rmPrimitiveSetQmeshDims(q, fdims[0], fdims[1]);

    /* now, add the new primitive onto the node */
    rmNodeAddPrimitive(n, q); 

    rmVertex3DDelete (v);
    rmVertex3DDelete (normals);
}
/* EOF */
