/*
 * 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: rmvmeshv.c,v 1.8 2007/07/22 17:37:17 wes Exp $
 * Version: $Name: v180-alpha-02 $
 * $Revision: 1.8 $
 * $Log: rmvmeshv.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/26 18:45:55  wes
 * Fixed buglet where colors not correctly assigned to cone geometry
 * produced by private_rmvBuildCone.
 *
 * Revision 1.6  2005/06/08 18:33:18  wes
 * Code cleanup to eliminate compiler warnings.
 *
 * Revision 1.5  2005/02/19 16:09:13  wes
 * Distro sync and consolidation.
 *
 * Revision 1.4  2005/01/23 17:11:02  wes
 * Copyright updated to 2005.
 *
 * Revision 1.3  2004/01/17 04:09:26  wes
 * Updated copyright line for 2004.
 *
 * 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.6  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.5  2002/04/30 19:40:21  wes
 * Updated copyright dates.
 *
 * Revision 1.4  2001/03/31 17:10:08  wes
 * v1.4.0-alpha-2 checkin.
 *
 * Revision 1.3  2000/04/20 16:17:45  wes
 * JDB modifications: code rearrangement, additional docs.
 *
 * Revision 1.2  2000/04/17 00:05:24  wes
 * Lots of documentation updates, courtesy of jdb.
 *
 * 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"

static float ctable[128], stable[128];
RMvertex3D   workv[RMV_MAX_CONE_SIDES];

/* PRIVATE declarations */
void build_unit_circle (int s);
void private_rmvBuildCone (RMvertex3D *p1, RMvertex3D *p2, double *mag, float cap_half_angle, RMvertex3D *v, RMvertex3D *n, int ns, int j, RMcolor4D *refColor, RMcolor4D *c); 
void private_rmvBuildArrow (RMvertex3D *p1, RMvertex3D *p2, double *mag, float cap_half_angle, float proj_value, RMvertex3D *v, int ns, int j, RMcolor4D *refColor, RMcolor4D *c, int nv);

/*
 * ----------------------------------------------------
 * @Name rmvI3VectorArrows
 @pstart
 RMenum rmvI3VectorArrows (RMvertex3D (*appgridfunc)(int i, void *usrData),
                           void *appGridUsrData,
		           float (*appdatafunc)(int i, void *usrData),
			   void *appDataUsrData,
			   float (*appdata2func)(int i, void *usrData),
			   void *appData2UsrData,
			   RMvertex3D (*appvdatafunc)(int i),
			   RMvisMap *vmap,
			   int axis_offset_flag,
			   int inpts,
			   float scale_value,
			   RMenum scale_type,
			   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.
			   
 RMvertex3D (*appvdatafunc)(int i, void *usrData) - a handle to a
    caller-supplied function that returns an RMvertex3D (x, y, z)
    corresponding to the vector data at grid point (i) (input, required).
			   
 void *appVectorUsrData - (optional, input) you can pass in a void * to user
    data; this void * will be provided as a parameter to the user-defined
    vector data field evaluation callback. If you don't want to pass in
    a void * to any user data for the vector data field evaluation
    callback, pass in a NULL for this parameter.
			   
 RMvisMap *vmap - a handle to an RMvisMap object (input).
			   
 int axis_offset_flag - 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 inpts - int specifying number of glyph points (input).
			   
 float scale_value - a float specifying the scaling parameter for
    glyphs (input).
			   
 int scale_type - an RMenum specifying how glyphs are scaled. Must be
    one of RMV_NO_SCALE, RMV_LINEAR_SCALE, or RMV_LOG10_SCALE (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 line 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 glyph-based vector field visualization using an arrow glyph
 at each sample point.  The arrow glyphs may be colored using a
 combination of an RMvisMap and the secondary data function.

 Upon success, RM_CHILL is returned and the RMnode contains the
 geometry for the glyph-based vector field visualization.  Otherwise,
 RM_WHACKED is returned.

 @dend
 * ----------------------------------------------------
 */
RMenum
rmvI3VectorArrows (RMvertex3D (*appgridfunc)(int i, void *usrData),
		   void *appGridUsrdata,
		   float (*appdatafunc)(int i, void *usrData),
		   void *appDataUsrData,
		   float (*appdata2func)(int i, void *usrData),
		   void *appData2UsrData,
		   RMvertex3D (*appvdatafunc)(int i, void *usrData),
		   void *appVectorUsrData,
		   RMvisMap *vmap,
		   int axis_offset_flag,
		   int inpts,
		   float scale_value,
		   int scale_type,
		   RMenum linewidth,
		   RMenum linestyle,
		   RMnode *n)
{
    int          i;
    int          npts;
    int          ns = RMV_DEFAULT_ARROW_NBARBS;
    int          nsegs_per_arrow;
    int          nverts_total;
    double       dmag;
    RMprimitive *t;
    RMvertex3D  *v;
    RMcolor4D   *c;
    RMvertex3D   p1, p2;
 
    /*
     * the two arrow parameters are:
     * 1. the angle between the arrow barbs and the shaft.  
     * 2. the length of projection of the barbs onto the shaft.  a value
     *    between 0 and 1.  the smaller the number,the smaller the projection.
     */
    float arrow_half_angle = RMV_DEFAULT_ARROWHEAD_HALF_ANGLE;
    float proj_value = RMV_DEFAULT_ARROWHEAD_PROJECTION_LENGTH;

    /* assertions */
    if ((RM_ASSERT(appgridfunc, "rmvI3VectorArrows() error: the input grid callback is NULL.") == RM_WHACKED) ||
	(RM_ASSERT(appdatafunc, "rmvI3VectorArrows() error: the primary data callback is NULL.") == RM_WHACKED) ||
	(RM_ASSERT(appvdatafunc, "rmvI3VectorArrows() error: the vector data callback is NULL. ") == RM_WHACKED) ||
	(RM_ASSERT(n, "rmvI3VectorArrows() error: the return RMnode pointer is NULL. ") == RM_WHACKED))
	return(RM_WHACKED);

    if (((vmap == NULL) && (appdata2func != NULL)) || ((vmap != NULL) && (appdata2func == NULL)))
    {
	rmWarning("rmvI3VectorArrows() error: both the visualization colormap and secondary data callback must both be NULL or both defined.");
	return(RM_WHACKED);
    }
	
    nsegs_per_arrow = ns + 1;
    build_unit_circle(ns);
    
    npts = inpts;
    nverts_total = npts * nsegs_per_arrow * 2;
    
    v = rmVertex3DNew(nverts_total);

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

    for (i = 0; i < npts; i++)
    {
	float     d;
	RMcolor4D tmpColor;

	d = (*appdatafunc)(i, appDataUsrData);
	p1 = (*appgridfunc)(i, appDataUsrData);
	p2 = (*appvdatafunc)(i, appVectorUsrData);
	
	switch (axis_offset_flag)
	   {
	   case RMV_XAXIS_OFFSET:
	      p1.x += d;
	      break;
	      
	   case RMV_YAXIS_OFFSET:
	      p1.y += d;
	      break;
	      
	   case RMV_ZAXIS_OFFSET:
	      p1.z += d;
	      break;
	      
	   default: /* bogus axis offset enum */
	      break;
	   }
	
	rmVertex3DMagNormalize(&p2, &dmag);
	private_rmvScaleMagnitude(&dmag, scale_type, scale_value);

	if (c)
	{
	    int   k;
	    float d2;
	    
	    d2 = (*appdata2func)(i, appData2UsrData);
	    k = rmVismapIndexFromData(vmap, d2);
	    rmVismapGetColor4D(vmap, k, &tmpColor);
	}
	private_rmvBuildArrow(&p1, &p2, &dmag, arrow_half_angle, proj_value, v, ns, i, &tmpColor, c, (nsegs_per_arrow * 2));
    }

    t = rmPrimitiveNew(RM_LINES);
    rmPrimitiveSetVertex3D(t, nverts_total, v, RM_COPY_DATA, NULL);

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

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

    return(RM_CHILL);
}


/*
 * ----------------------------------------------------
 * @Name rmvI3VectorBarbs
 @pstart
 RMenum rmvI3VectorBarbs (RMvertex3D (*appgridfunc)(int i, void *usrData),
                          void *appGridUsrData,
		          float (*appdatafunc)(int i, void *usrData),
			  void *appDataUsrData,
			  float (*appdata2func)(int i, void *usrData),
			  void *appData2UsrData,
			  RMvertex3D (*appvdatafunc)(int i, void *usrData),
			  void *appVectorUsrData,
			  RMvisMap *vmap,
			  int axis_offset_flag,
			  int inpts,
			  float scale_value,
			  RMenum scale_type,
			  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.
			   
 RMvertex3D (*appvdatafunc)(int i, void *usrData) - a handle to a
    caller-supplied function that returns an RMvertex3D (x, y, z)
    corresponding to the vector data at grid point (i) (input, required).
			   
 void *appVectorUsrData - (optional, input) you can pass in a void * to user
    data; this void * will be provided as a parameter to the user-defined
    vector data field evaluation callback. If you don't want to pass in
    a void * to any user data for the vector data field evaluation
    callback, pass in a NULL for this parameter.
			   
 RMvisMap *vmap - a handle to an RMvisMap object (input).
			   
 int axis_offset_flag - 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 inpts - int specifying number of glyph points (input).
			   
 float scale_value - a float specifying the scaling parameter for
    glyphs (input).
			   
 int scale_type - an int specifying how glyphs are scaled. Must be one
    of RMV_NO_SCALE, RMV_LINEAR_SCALE, or RMV_LOG10_SCALE (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 line 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 glyph-based vector field visualization using an barb glyph
 at each sample point.  The barb glyphs may be colored using a
 combination of an RMvisMap and the secondary data function.

 Upon success, RM_CHILL is returned and the RMnode contains the
 geometry for the glyph-based vector field visualization.  Otherwise,
 RM_WHACKED is returned.

 @dend
 * ----------------------------------------------------
 */
RMenum
rmvI3VectorBarbs (RMvertex3D (*appgridfunc)(int i, void *usrData),
		  void *appGridUsrData,
		  float (*appdatafunc)(int i, void *usrData),
		  void *appDataUsrData,
		  float (*appdata2func)(int i, void *usrData),
		  void *appData2UsrData,
		  RMvertex3D (*appvdatafunc)(int i, void *usrData),
		  void *appVectorUsrData,
		  RMvisMap *vmap,
		  int axis_offset_flag,
		  int inpts,
		  float scale_value,
		  RMenum scale_type,
		  RMenum linewidth,
		  RMenum linestyle,
		  RMnode *n)
{
    int          i, nbarbs, npts, index = 0;
    double       dmag;
    RMvertex3D  *v;
    RMcolor4D   *c;
    RMprimitive *t;
    RMvertex3D   p1, p2;

    if ((RM_ASSERT(appgridfunc, "rmvI3VectorBarbs() error: the input grid callback is NULL.") == RM_WHACKED) ||
	(RM_ASSERT(appdatafunc, "rmvI3VectorBarbs() error: the primary data callback is NULL.") == RM_WHACKED) ||
	(RM_ASSERT(appvdatafunc, "rmvI3VectorBarbs() error: the vector data callback is NULL. ") == RM_WHACKED) ||
	(RM_ASSERT(n, "rmvI3VectorBarbs() error: the return RMnode pointer is NULL. ") == RM_WHACKED))
	return(RM_WHACKED);

    if (((vmap == NULL) && (appdata2func != NULL)) || ((vmap != NULL) && (appdata2func == NULL)))
    {
	rmWarning("rmvI3VectorBarbs() error: both the visualization colormap and secondary data callback must both be NULL or both defined.");
	return(RM_WHACKED);
    }
    
    nbarbs = inpts;
    npts = nbarbs * 2;

    v = rmVertex3DNew(npts);

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

    for (i = 0; i < nbarbs; i++)
    {
	float d;

	d = (*appdatafunc)(i, appDataUsrData);
	p1 = (*appgridfunc)(i, appGridUsrData);
	p2 = (*appvdatafunc)(i, appVectorUsrData);

	switch (axis_offset_flag)
	   {
	   case RMV_XAXIS_OFFSET:
	      p1.x += d;
	      break;
	      
	   case RMV_YAXIS_OFFSET:
	      p1.y += d;
	      break;
	      
	   case RMV_ZAXIS_OFFSET:
	      p1.z += d;
	      break;
	      
	   default: /* bogus axis offset enum */
	      break;
	   }
	
	rmVertex3DMagNormalize(&p2, &dmag);
	private_rmvScaleMagnitude(&dmag, scale_type, scale_value);

	/* convert from a normalized vector back into a point */
	p2.x = p1.x + (dmag * p2.x);
	p2.y = p1.y + (dmag * p2.y);
	p2.z = p1.z + (dmag * p2.z);

	VCOPY(&p1, (v + index));
	index++;
	VCOPY(&p2, (v + index));
	index++;

	if (c)
	{
	    int   k;
	    float d2;

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

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

    rmPrimitiveSetVertex3D(t, npts, v, RM_COPY_DATA, NULL);

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

    return(RM_CHILL);
}


/*
 * ----------------------------------------------------
 * @Name rmvI3VectorCones
 @pstart
 RMenum rmvI3VectorCones (RMvertex3D (*appgridfunc)(int i, void *usrData),
                          void *appGridUsrData,
		          float (*appdatafunc)(int i, void *usrData),
			  void *appDataUsrData,
			  float (*appdata2func)(int i, void *usrData),
			  void *appData2UsrData,
			  RMvertex3D (*appvdatafunc)(int i, void *usrData),
			  void *appVectorUsrData,
			  RMvisMap *vmap,
			  int axis_offset_flag,
			  int inpts,
			  float scale_value,
			  RMenum scale_type,
			  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.
			   
 RMvertex3D (*appvdatafunc)(int i, void *usrData) - a handle to a
    caller-supplied function that returns an RMvertex3D (x, y, z)
    corresponding to the vector data at grid point (i) (input, required).
			   
 void *appVectorUsrData - (optional, input) you can pass in a void * to user
    data; this void * will be provided as a parameter to the user-defined
    vector data field evaluation callback. If you don't want to pass in
    a void * to any user data for the vector data field evaluation
    callback, pass in a NULL for this parameter.
			   
 RMvisMap *vmap - a handle to an RMvisMap object (input).
			   
 int axis_offset_flag - an RMenum specifying in which axis to offset
    the glyph.  Must be one of RMV_XAXIS_OFFSET, RMV_YAXIS_OFFSET, or
    RMV_ZAXIS_OFFSET (input).
			   
 int inpts - int specifying number of glyph points (input).
			   
 float scale_value - a float specifying the scaling parameter for
    glyphs (input).
			   
 int scale_type - an int specifying how glyphs are scaled. Must be one
    of RMV_NO_SCALE, RMV_LINEAR_SCALE, or RMV_LOG10_SCALE (input).
			   
 RMnode *n - a handle to an RMnode (modified).
 @aend

 @dstart

 Creates a glyph-based vector field visualization using a cone glyph
 at each sample point.  The cone glyphs markers may be colored using a
 combination of an RMvisMap and the secondary data function.

 Upon success, RM_CHILL is returned and the RMnode contains the
 geometry for the glyph-based vector field visualization.  Otherwise,
 RM_WHACKED is returned.

 @dend
 * ---------------------------------------------------- */
RMenum
rmvI3VectorCones (RMvertex3D (*appgridfunc)(int i, void *usrData),
		  void *appGridUsrData,
		  float (*appdatafunc)(int i, void *usrData),
		  void *appDataUsrData,
		  float (*appdata2func)(int i, void *usrData),
		  void *appData2UsrData,
		  RMvertex3D (*appvdatafunc)(int i, void *usrData),
		  void *appVectorUsrData,
		  RMvisMap *vmap,
		  int axis_offset_flag,
		  int inpts,
		  float scale_value,
		  RMenum scale_type,
		  RMnode *n)
{
    /*
     * this routine needs to be updated to use regular RM cones rather than
     * constructing disjoint triangles. when this routine was first written,
     * there were no cones in RM.
     */
    int          i;
    int          npts;
    int          ns = RMV_DEFAULT_CONE_NSIDES;
    int          ntris_per_cone;
    int          nverts_total;
    double       dmag;
    float        cap_half_angle = RMV_DEFAULT_CONE_HALF_ANGLE;
    RMprimitive *t;
    RMvertex3D  *v, *nm;
    RMcolor4D   *c;
    RMvertex3D   p1 ,p2;

    if ((RM_ASSERT(appgridfunc, "rmvI3VectorCones() error: the input grid callback is NULL.") == RM_WHACKED) ||
	(RM_ASSERT(appdatafunc, "rmvI3VectorCones() error: the primary data callback is NULL.") == RM_WHACKED) ||
	(RM_ASSERT(appvdatafunc, "rmvI3VectorCones() error: the vector data callback is NULL. ") == RM_WHACKED) ||
	(RM_ASSERT(n, "rmvI3VectorCones() error: the return RMnode pointer is NULL. ") == RM_WHACKED))
	return(RM_WHACKED);

    if (((vmap == NULL) && (appdata2func != NULL)) || ((vmap != NULL) && (appdata2func == NULL)))
    {
	rmWarning("rmvI3VectorCones() error: both the visualization colormap and secondary data callback must both be NULL or both defined.");
	return(RM_WHACKED);
    }
    
    ntris_per_cone = ns * 2;	/* the bottom of the cone is included as well */

    build_unit_circle(ns);
    
    npts = inpts;
    nverts_total = npts * ntris_per_cone * 3;
    
    v = rmVertex3DNew(nverts_total);
    nm = rmVertex3DNew(nverts_total);

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

    for (i = 0; i < npts; i++)
    {
	float     d;
	RMcolor4D tmpColor;

	d = (*appdatafunc)(i, appDataUsrData);
	p1 = (*appgridfunc)(i, appGridUsrData);
	p2 = (*appvdatafunc)(i, appVectorUsrData);
	
	switch (axis_offset_flag)
	   {
	   case RMV_XAXIS_OFFSET:
	      p1.x += d;
	      break;
	      
	   case RMV_YAXIS_OFFSET:
	      p1.y += d;
	      break;
	      
	   case RMV_ZAXIS_OFFSET:
	      p1.z += d;
	      break;
	      
	   default: /* bogus axis offset enum */
	      break;
	   }
	
	rmVertex3DMagNormalize(&p2, &dmag);
	private_rmvScaleMagnitude(&dmag, scale_type, scale_value);

	if (c)
	{
	    int   k;
	    float d2;

	    d2 = (*appdata2func)(i, appData2UsrData);
	    k = rmVismapIndexFromData(vmap, d2);
	    rmVismapGetColor4D(vmap, k, &tmpColor);
	}
	private_rmvBuildCone(&p1, &p2, &dmag, cap_half_angle, v, nm, ns, i, &tmpColor, c);

    }
    t = rmPrimitiveNew(RM_TRIANGLES);
    rmPrimitiveSetVertex3D(t, nverts_total, v, RM_COPY_DATA, NULL);
    rmPrimitiveSetNormal3D(t, nverts_total, nm, RM_COPY_DATA, NULL);

    if (c)
    {
	rmPrimitiveSetColor4D(t, nverts_total, c, RM_COPY_DATA, NULL);
	rmColor4DDelete(c);
    }
    
    /* now, add the new primitive onto the node */
    rmNodeAddPrimitive(n, t);

    rmVertex3DDelete(v);
    rmVertex3DDelete(nm);

    return(RM_CHILL);
}


/* PRIVATE */
void
private_rmvScaleMagnitude (double *dmag,
			   int scale_type,
			   float scale_value)
{
    double d;

    d = *dmag;
    
    switch(scale_type)
    {
    case RMV_NO_SCALE:
        break;

    case RMV_LINEAR_SCALE:
	d *= (double)scale_value;
	break;

    case RMV_LOG10_SCALE:
	d += 1.;
	d = log10(d);
	d *= (double)scale_value;
	break;

    default: /* bogus scale type */
       break;
    }
    *dmag = d;
}


/* PRIVATE */
void
build_unit_circle (int s)
{
    int    i, n;
    double t, dt, t2;
    double dscale;

    n=s;

    dscale = 1.;

    dt = (2.0 * RM_PI) / n;
    t = 0.0;
    for (i = 0; i < n; i++, t += dt)
    {
	t2 = dscale * cos(t);
	ctable[i] = t2;
	t2 = dscale * sin(t);
	stable[i] = t2;
    }
}


/* PRIVATE */
void
build_orientation_matrix (RMvertex3D *tp,
			  double *m11,
			  double *m12,
			  double *m13,
			  double *m31,
			  double *m32,
			  double *m33)
{
    double a2c2;
    
    a2c2 = (tp->x * tp->x) + (tp->z * tp->z);
    if (a2c2 == 0.0)  /* a degenerate condition; make an identity matrix */
    {
	*m11 = 1.0;
	*m12 = 0.0;
	*m13 = 0.0;

	*m31 = 0.0;
	*m32 = 0.0;
	*m33 = 1.0;
    }
    else
    {
	a2c2 = 1.0 / a2c2;
	
	*m11 = (tp->x * tp->x * tp->y) + (tp->z * tp->z);
	*m11 *= a2c2;

	*m12 = -1.0 * tp->x;
	
	*m13 = (tp->x * tp->y * tp->z) - (tp->x * tp->z);
	*m13 *= a2c2;

	*m31 = *m13;

	*m32 = -1.0 * tp->z;

	*m33 = (tp->y * tp->z * tp->z) + (tp->x * tp->x);
	*m33 *= a2c2;
    }
}


/* PRIVATE */
void
orient_and_translate_circle (RMvertex3D *base, 	/* grid point */
			     RMvertex3D *vect, 	/* vector data */
			     RMvertex3D *dest, 	/* resultant array */
			     int nsides,     	/* of size 'nsides' */
			     double *lscale, 	/* vector magnitude */
			     double *proj_val, 	/* 0..1 */
			     double *rscale) 	/* radius scale */
{
    /*
     * proj_val - between 0...1. will translate the oriented circle to
     *     some location between the "base" and the vect*magnitude position.
     *     for cones, the value is 1.0, which puts the circle at the base.
     *     if the value were 0.0, the circle would have its center point
     *     at the location base+vect*mag.  for arrowheads, the circle will
     *     be somewhere inbetween.
     */
    int            i;
    register float rrscale;
    double         m11, m12, m32, m13, m33, m31, scale;
    RMvertex3D     translate_vect;

    scale = *lscale * (1.0 - *proj_val);

    rrscale = *rscale;

    translate_vect.x = base->x + (vect->x * scale);
    translate_vect.y = base->y + (vect->y * scale);
    translate_vect.z = base->z + (vect->z * scale);
    
    build_orientation_matrix(vect, &m11, &m12, &m13, &m31, &m32, &m33);

    m11 *= rrscale;
    m12 *= rrscale;
    m13 *= rrscale;
    
    m31 *= rrscale;
    m32 *= rrscale;
    m33 *= rrscale;
    
    /* transform all the points on the scaled unit circle to their new location */
    for (i = 0; i < nsides; i++, dest++)
    {
	dest->x = (ctable[i] * m11) + (stable[i] * m31) + translate_vect.x;
	dest->y = (ctable[i] * m12) + (stable[i] * m32) + translate_vect.y;
	dest->z = (ctable[i] * m13) + (stable[i] * m33) + translate_vect.z;
    }
}


/* PRIVATE */
void
private_rmvBuildCone (RMvertex3D *p1, 		/* the bottom center point */
		      RMvertex3D *p2, 		/* the normalized vector direction */
		      double *mag,
		      float cap_half_angle,	/* half-angle for apex */
		      RMvertex3D *v, 		/* the destination array for vertices */
		      RMvertex3D *n, 		/* dest array for normals */
		      int ns,			/* number of sides per cone */
		      int j,     		/* the j'th cone */
		      RMcolor4D *refColor, 	/* reference color */
		      RMcolor4D *c) 		/* dest color array */
{
    int        indx, i;
    double     d, d2;
    double     one = 1.0;
    RMvertex3D conehead;
    RMvertex3D bottom_normal;

    d2 = RM_DEGREES_TO_RADIANS(cap_half_angle);
    d = sin(d2) * (*mag);
    
    orient_and_translate_circle(p1, p2, workv, ns, mag, &one, &d);

    conehead.x = p1->x + (p2->x * (*mag));
    conehead.y = p1->y + (p2->y * (*mag));
    conehead.z = p1->z + (p2->z * (*mag));

    bottom_normal.x = -p2->x;
    bottom_normal.y = -p2->y;
    bottom_normal.z = -p2->z;
    
    rmVertex3DNormalize(&bottom_normal);

    indx = j * ns * 2 * 3;	/* number of sides (triangles) * 3 verts +  the bottom is another ns triangles */
    for (i = 0; i < ns; i++)
    {
	VCOPY((workv + i), (v + indx));
	
	if (i == (ns - 1))
	    VCOPY(workv, (v + indx + 1));
	else
	    VCOPY((workv + i + 1), (v + indx + 1));
	
	VCOPY(&conehead, (v + indx + 2));

	if (c)
	    c[indx] = c[indx+1] = c[indx+2] = *refColor;
	
	/* now compute the normals */
	rmVertex3DDiff(p1, (v + indx), (n + indx));
	rmVertex3DDiff(p1, (v + indx + 1), (n + indx + 1));

	n[indx + 2].x = (n[indx].x + n[indx + 1].x) * -0.5;
	n[indx + 2].y = (n[indx].y + n[indx + 1].y) * -0.5;
	n[indx + 2].z = (n[indx].z + n[indx + 1].z) * -0.5;

	rmVertex3DNormalize(n + indx);
	rmVertex3DNormalize(n + indx + 1);
	rmVertex3DNormalize(n + indx + 2);

	indx += 3;

	/* do the bottom of the cone*/
	VCOPY(p1, (v + indx));
	VCOPY((workv + i), (v + indx + 1));
	
	if (i == (ns - 1))
	    VCOPY(workv, (v + indx + 2));
	else
	    VCOPY((workv + i + 1), (v + indx + 2));

	if (c)
	    c[indx] = c[indx+1] = c[indx+2] = *refColor;
	
	VCOPY(&bottom_normal, (n + indx));
	VCOPY(&bottom_normal, (n + indx + 1));
	VCOPY(&bottom_normal, (n + indx + 2));

	indx+=3;
    }
}


/* PRIVATE */
void
private_rmvBuildArrow (RMvertex3D *p1, 		/* the bottom center point */
		       RMvertex3D *p2, 		/* the normalized vector direction */
		       double *mag,
		       float cap_half_angle,	/* half-angle for apex */
		       float proj_value,
		       RMvertex3D *v, 		/* the destination array for vertices */
		       int ns,			/* number of sides per cone */
		       int j,			/* the j'th cone */
		       RMcolor4D *refColor,
		       RMcolor4D *c,
		       int nv)			/* number of verts per arrow*/
{
    int        i, indx;
    double     d, d2;
    double     projection_value;
    RMvertex3D arrowhead;

    projection_value = proj_value;
    d2 = RM_DEGREES_TO_RADIANS(cap_half_angle);
    d = sin(d2) * (*mag) * projection_value;
    
    orient_and_translate_circle(p1, p2, workv, ns, mag, &projection_value, &d);

    arrowhead.x = p1->x + (p2->x * (*mag));
    arrowhead.y = p1->y + (p2->y * (*mag));
    arrowhead.z = p1->z + (p2->z * (*mag));
    
    indx = j * nv;

    /* first, build the main arrow shaft */
    VCOPY(p1, (v + indx));
    if (c)
	c[indx] = *refColor;
    indx++;
    VCOPY(&arrowhead, (v + indx));
    if (c)
	c[indx] = *refColor;
    indx++;
    
    for (i = 0; i < ns; i++) 	/* for each arrowhead barb */
    {
	VCOPY((workv + i), (v + indx));
	if (c)
	    c[indx] = *refColor;
	indx++;
	VCOPY(&arrowhead, (v + indx));
	if (c)
	    c[indx] = *refColor;
	indx++;
    }
}
/* EOF */
