/*
 * Copyright (C) 2001-2005, R3vis Corporation.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 * 
 * This program 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 General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program; 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/gpl.html.
 *
 * Contributor(s):
 *   Wes Bethel, R3vis Corporation, Marin County, California
 *
 * The OpenRM project is located at http://openrm.sourceforge.net/.
 */
/*
 * $Id: fogtest.c,v 1.8 2005/06/26 19:03:51 wes Exp $
 * Version: $Name: OpenRM-1-6-0-2-c $
 * $Revision: 1.8 $
 * $Log: fogtest.c,v $
 * Revision 1.8  2005/06/26 19:03:51  wes
 * Extensive changes:
 * (1) Floor computed with a big quadmesh.
 * (2) Reorganized calls to create SG to experiment with proper order of
 * lights/camera.
 * (3) Refined lights model to produce better visual results.
 *
 * Revision 1.7  2005/06/08 18:07:44  wes
 * Code cleanup to remove dead code and eliminate compiler warnings.
 *
 * Revision 1.6  2004/01/17 03:45:46  wes
 * Added command-line argument [-fr NN] to enable constant-rate rendering.
 *
 * Revision 1.5  2003/04/13 18:13:23  wes
 * Updated copyright dates.
 *
 * Revision 1.4  2003/01/27 05:07:07  wes
 * Changes to RMpipe initialization sequence APIs. Tested for GLX, but not WGL.
 *
 * Revision 1.3  2003/01/16 22:22:45  wes
 * Updated all source files to reflect new organization of header files: all
 * headers that were formerly located in include/rmaux, include/rmv and
 * include/rmi are now located in include/rm.
 *
 * Revision 1.2  2002/06/17 00:37:00  wes
 * Updated copyright line.
 *
 * Revision 1.1  2001/10/15 00:23:22  wes
 * Initial entry.
 *
 */

#define NUM_MARKERS 5
#define NUM_LINES 3

#include <stdio.h>
#ifdef RM_X
#include <strings.h>
#endif
#include <stdlib.h>

#include <rm/rm.h>
#include <rm/rmv.h>
#include <rm/rmi.h>
#include <rm/rmaux.h>

#include "procmode.h"

static int imgWidth=640, imgHeight=512;
static int doFog = 1;
static int frameRate = -1;

/*
 * this is an array of pointers to the post primitives. we create
 * the array inside the addPostsToFloor() routine, and use the
 * pointers later during the traversal callback.
 */
static RMprimitive **postPrims=NULL;
static float *lastDistance=NULL;

void
usage(char *progName)
{
    char buffer[1024];

    sprintf(buffer," usage: %s [-w imgWidth (default=640)] [-h imgHeight (default=512)] [-nf (turn off fog)] \n ",progName);
		
    rmWarning(buffer);
}

int
parseArgs(int ac,
	  char *av[])
{
    int i;
    
    i = 1;
    ac--;

    while (ac > 0)
    {

	if (strcmp(av[i],"-w") == 0)
	{
	    i++;
	    ac--;
	    sscanf(av[i],"%d",&imgWidth);
	}
	else if (strcmp(av[i],"-h") == 0)
	{
	    i++;
	    ac--;
	    sscanf(av[i],"%d",&imgHeight);
	}
	else if (strcmp(av[i],"-nf") == 0)
	{
	    doFog = 0;
	}
	else if (strcmp(av[i],"-fr") == 0)
	{
	    int j;
	    i++;
	    ac--;
	    sscanf(av[i],"%d", &j);
	    if (j > 0)
		frameRate = j;
	}
	else
	{
	    usage(av[0]);
	    return(-1);
	}
	i++;
	ac--;
    }

    return(1);
}

/*
 * the scene for this demo program consists of a floor, and
 * some simple geometric objects. the floor is positioned at
 * z=0, and is the x/y plane. the range of the floor is from
 * (-10,-10,0) to (10,10,0). a simple auto-gen'ed texture is
 * applied to the floor (a checkered pattern).
 *
 * the initial location of the camera is at (0,0,1) looking
 * in the (0,1,0) direction.
 */


void
addFloorTileTexture(RMnode *n)
{
    /*
     * create an RMimage consisting of a simple checkered pattern,
     * then create an RMtexture using the new image, add the
     * texture as a scene parm to the node
     */
#define USE_MIPMAPS 0

#if USE_MIPMAPS
    RMimage **mipMaps;
    int nmipmaps;
#endif
    RMimage *t;
    int i,j;
    
    unsigned char *dst; 
    RMcolor4D blue={0.1,0.1,0.4,1.0};
    RMcolor4D white={0.9,0.9,0.9,1.0};
    RMtexture *texture;

    unsigned char ubBlue[4], ubWhite[4];

    int rows=256,cols=256;
    
    t = rmImageNew(2, cols, rows, 1, RM_IMAGE_RGBA,
		   RM_UNSIGNED_BYTE, RM_COPY_DATA);

    dst = (unsigned char *)rmImageGetPixelData(t);

    ubBlue[0] = (unsigned char)(blue.r * 255.0);
    ubBlue[1] = (unsigned char)(blue.g * 255.0);
    ubBlue[2] = (unsigned char)(blue.b * 255.0);
    ubBlue[3] = (unsigned char)(blue.a * 255.0);
    
    ubWhite[0] = (unsigned char)(white.r * 255.0);
    ubWhite[1] = (unsigned char)(white.g * 255.0);
    ubWhite[2] = (unsigned char)(white.b * 255.0);
    ubWhite[3] = (unsigned char)(white.a * 255.0);
    
    for (j=0;j<rows;j++)
    {
	for (i=0;i<cols;i++)
	{
	    if (j & 0x08)
	    {
		if (i & 0x08)
		    memcpy(dst,ubBlue,4);
		else
		    memcpy(dst,ubWhite,4);
	    }
	    else
	    {
		if (i & 0x08)
		    memcpy(dst,ubWhite,4);
		else
		    memcpy(dst,ubBlue,4);
	    }
	    dst += 4;
	}
    }

    texture = rmTextureNew(2);

#if USE_MIPMAPS
    nmipmaps = rmuImageBuildMipmaps(t,&mipMaps,RM_SOFTWARE);
    rmTextureSetImages(texture,mipMaps, nmipmaps, RM_FALSE);
    rmTextureSetFilterMode(texture,GL_LINEAR_MIPMAP_LINEAR, GL_NEAREST);  
#else
    rmTextureSetImages(texture, &t, 1, RM_FALSE);
    /*
     * set the OpenGL texture minification mode to GL_LINEAR,
     * texture magnification mode to GL_NEAREST. see man glTexParameteri.
     *
     * when the following line of code is commented out, we're using
     * the default OpenGL texture filter modes which are GL_NEAREST, GL_NEAREST
     */
/*    rmTextureSetFilterMode(texture,GL_LINEAR, GL_NEAREST);   */
#endif
    
    rmNodeSetSceneTexture(n,texture);
    
    rmTextureDelete(texture, RM_FALSE);
}

void
addFloor(RMnode *addToNode,
	 RMvertex3D *fmin,
	 RMvertex3D *fmax)
{
    RMnode *floor;
    RMprimitive *p;
    RMvertex3D *v;
    RMvertex2D *tc;
    RMvertex3D *n;
    int rows=20, cols=20;
    int r,c;
    int indx;

    float x,dx,y,dy,s,ds,t,dt;

    floor = rmNodeNew("floor",RM_RENDERPASS_3D, RM_RENDERPASS_OPAQUE);

    p = rmPrimitiveNew(RM_QUADMESH);
    rmPrimitiveSetQmeshDims(p, rows, cols);

    v = rmVertex3DNew(rows*cols);
    tc = rmVertex2DNew(rows*cols);
    n = rmVertex3DNew(rows*cols);

    indx = 0;

    dx = (fmax->x - fmin->x)/(float)rows;
    dy = (fmax->y - fmin->y)/(float)cols;

    dt = 1.0/(float)rows;
    ds = 1.0/(float)cols;

    x = fmin->x;
    t = 0.0F;
    for (r=0;r<rows;r++, x+=dx, t+=dt)
    {
	y = fmin->y;
	s = 0.0F;
	for (c=0;c<cols;c++, y+=dy, s+=ds)
	{
	    v[indx].x = x;
	    v[indx].y = y;
	    v[indx].z = fmin->z;

	    n[indx].x = n[indx].y = 0.0F;
	    n[indx].z = 1.0F;

	    tc[indx].x = t;
	    tc[indx].y = s;

	    indx++;
	}
    }

    rmPrimitiveSetVertex3D(p, rows*cols, v, RM_COPY_DATA, NULL);
    rmPrimitiveSetNormal3D(p, rows*cols, n, RM_COPY_DATA, NULL);
    rmPrimitiveSetTexcoord2D(p, rows*cols, tc, RM_COPY_DATA, NULL); 

    addFloorTileTexture(floor);  
    rmNodeAddPrimitive(floor,p);
    rmNodeAddChild(addToNode,floor);

    rmVertex3DDelete(v);
    rmVertex2DDelete(tc);
    rmVertex3DDelete(n);
}

RMimage *
readAVSImage(char *fname)
{
    RMimage *t;
    int w,h;
    int i,j,indx, indx2;
    FILE *f;
    unsigned char *static_image_data;

    f = fopen(fname,"r");
    if (f == NULL)
    {
	fprintf(stderr," can't open the AVS image file named <%s> \n",fname);
	return(NULL);
    }

    {
        unsigned char buf[4];
	read(fileno(f),(void *)buf,4);
	w = buf[3] | (buf[2] << 8);
	read(fileno(f),(void *)buf,4);
	h = buf[3] | (buf[2] << 8);
    }

    /* sanity check w,h */

    if ((w < 0) || (w > 4096) ||
	(h < 0) || (h > 4096))
    {
	fprintf(stderr," dubious image dimensions: %d, %d, aborting image read. \n",w,h);
	fclose(f);
	return(NULL);
    }

    static_image_data = (unsigned char *)malloc(sizeof(unsigned char)*w*h*4);

    /* this reads everything at once */
    read(fileno(f),(void *)static_image_data,w*h*4);

    fclose(f);

#define ONE_OVER_255 0.0039215686
    
    /* hack to change from ARGB to RGBA */
    indx = 0;
    indx2 = 3;
    for (i=0;i<w*h;i++)
    {
        unsigned char t;

	t = static_image_data[indx];
	for (j=0;j<3;j++)
	    static_image_data[indx+j] = static_image_data[indx+j+1];
	static_image_data[indx+3] = 0xFF;

	indx += 4;
    }

    /* 
     * problem w/RGB mode in glDrawPixels . it looks like it wants all
     * the image data to be 4-byte word aligned.
     */


    t = rmImageNew(2,w,h,1,RM_IMAGE_RGBA,
		   RM_UNSIGNED_BYTE,
		   RM_COPY_DATA);  

    rmImageSetPixelData(t, (void *)static_image_data,
			RM_COPY_DATA,
			NULL);

    rmImageMirror(t,RM_IMAGE_MIRROR_HEIGHT);

    free((void *)static_image_data);

    return(t);
}

void
addCamera(RMnode *addToNode,
	  int imgWidth,
	  int imgHeight)
{
    RMcamera3D *c = rmCamera3DNew();
    RMvertex3D eye, at, up;
    float fov, hither, yon;

    eye.x = eye.y = 0.;
    eye.z = 1.0;

    at = eye;
    at.y += 1.0;

    up.x = up.y = 0.;
    up.z = 1.;

    fov = 45.0F;
    hither = 0.25;
    yon = 20.0;
    
    rmCamera3DSetProjection(c,RM_PROJECTION_PERSPECTIVE); 

    rmCamera3DSetEye(c,&eye);
    rmCamera3DSetUpVector(c,&up);
    rmCamera3DSetAt(c,&at);

    rmCamera3DSetFOV(c,fov);
    rmCamera3DSetHither(c, hither);
    rmCamera3DSetYon(c,yon);
    rmCamera3DSetFocalDistance(c,1.0F);
    rmCamera3DSetAspectRatio(c,(float)imgWidth/(float)imgHeight);

    rmNodeSetSceneCamera3D(addToNode,c);
    rmCamera3DDelete(c);
}

void
addLighting(RMnode *n)
{
    /* light source zero is a from-the-top directional light */
    RMlight   *l0;
    RMcolor4D  diffuse = {0.9, 0.9, 0.9, 1.0};
    RMcolor4D  specular = {0.5, 0.5, 0.5, 1.0};
    RMvertex3D position = {0.0, -1000.0, -1000.0}; 

    l0 = rmLightNew();
    if (l0 == NULL)
	return;

    rmLightSetType(l0, RM_LIGHT_DIRECTIONAL);
    rmLightSetColor(l0, NULL, &diffuse, &specular); 
    rmLightSetXYZ (l0, &position);
    rmNodeSetSceneLight(n, RM_LIGHT0, l0);

    rmLightDelete(l0);
    
    /*
     * when the lights are added as scene parameters, RM makes a copy
     * of them, and we don't need our RMlight objects anymore, so we
     * need to delete them.
     */
    /* set up the light model/lighting environment */
    {
	RMcolor4D     defAmbient =  {0.2, 0.2, 0.2, 1.0};
	RMlightModel *lm = rmLightModelNew();

	if (lm == NULL)
	    return;
	
	rmLightModelSetAmbient(lm, &defAmbient);
	rmLightModelSetTwoSided (lm, RM_TRUE);
	rmLightModelSetLocalViewer(lm, RM_TRUE);
	rmNodeSetSceneLightModel(n, lm);

	rmLightModelDelete (lm);
    }
    
    return;
}

void
addFog(RMnode *n)
{
    RMfog *f;
    RMcolor4D fogColor={0.15,0.06,0.02,1.0};
    
    f = rmFogNew();
    
#if 1
    rmFogSetDensity(f,0.15F);
    rmFogSetMode(f,GL_EXP);
#else
    /* don't use linear fog - most opengl implementations don't
     do it correctly, whereas most do GL_EXP & GL_EXP2 correctly. */
    rmFogSetMode(f,GL_LINEAR);
    rmFogSetStartEnd(f, 10.0F, 40.0F);
#endif    
    rmFogSetColor(f,&fogColor);
    rmNodeSetSceneFog(n,f);
}

int
myPreTraversalCallback(const RMnode *n,
		       const RMstate *s)
{
    float v[4],d[4];
    RMvertex3D c;
    RMprimitive *p;
    RMmatrix renderStateViewAndProjectionMatrix;
    int *indxPtr, indx;
    int isVisible;
#define distanceThreshold 10.0F

    /*
     * this routine implements two things:
     * 1. rudimentary view frustum culling
     * 2. model property switching/modification based upon view
     *    dependent parms. when a given object comes closer to the
     *    viewer than a predefined threshold (define above, units
     *    measured in world coords) we will change two things about
     *    the object: a) we'll change it's color to yellow, and b)
     *    we'll use a higher-res tesselation of the underlying
     *    procedural model. these two things implement a form of
     *    rudimentary view-dependent LOD control.
     */

    isVisible = rmNodeFrustumCullCallback(n,s);
    if (isVisible == 0)
	return 0;

    /*
     * for frustum culling, we're going to use the Node's center point.
     * a more robust test would use an area-based metric (eg, bounding
     * box or sphere).
     */
    rmNodeGetCenter(n,&c);

    v[0] = c.x;
    v[1] = c.y;
    v[2] = c.z;
    v[3] = 1.F;


    /*
     * transform this point through the combined view+projection matrix
     * supplied to us via the RMstate object. we don't need to worry
     * about the model matrix since we're not modifying any model
     * matrices in this demo.
     */
    {
	const RMmatrix *mv, *p;
	mv = rmStateGetModelViewMatrix(s);
	p = rmStateGetProjectionMatrix(s);
	
	rmMatrixMultiply(mv,p,&renderStateViewAndProjectionMatrix);
    }
    rmPoint4MatrixTransform(v,&renderStateViewAndProjectionMatrix,d);

    /*
     * the coordinate contained in "d" is now in "unscaled" ndc space.
     * need more documentation here.
     */
#if 0
    if ((d[2] < 0.) || (d[2] > d[3]))	/* behind us or too far away */
	isVisible = 0;
    else if ((fabs(d[0]) > d[2]) || (fabs(d[1]) > d[2])) /* outside view volume? */
	isVisible = 0;
    else
	isVisible = 1;

    if (isVisible == 0)
	return(0);		/* object "not visible", don't process
				 this node */
#endif
    indxPtr = (int *)rmNodeGetClientData(n);
    indx = *indxPtr;
    p = postPrims[indx];

    if (d[2] <= distanceThreshold)
    {
	RMcolor4D c={1.0,1.0,0.0,1.0};

	/*
	 * only change color when we cross the threshold. when the object
	 * is closer to us than the threshold, we'll ask for a cylinder with
	 * a higher res tesselation level than is used when they're farther
	 * away from us.
	 */
	if ((lastDistance[indx] > distanceThreshold) ||
	    (lastDistance[indx] == RM_MINFLOAT))
	{
	    rmPrimitiveSetColor4D(p,1,&c,RM_COPY_DATA,NULL);
	    rmPrimitiveSetModelFlag(p,RM_CYLINDERS_16);
	}

    }
    else
    {
	RMcolor4D c={1.0,1.0,1.0,1.0};

	/*
	 * only change color when we cross the threshold
	 */
	if ((lastDistance[indx] <= distanceThreshold) ||
	    (lastDistance[indx] == RM_MINFLOAT))
	{
	    rmPrimitiveSetColor4D(p,1,&c,RM_COPY_DATA,NULL);
	    rmPrimitiveSetModelFlag(p,RM_CYLINDERS_4);
	}
    }
    lastDistance[indx] = d[2];

    /* 
     * improvements that could be made here include keeping around info
     * on each prim so that we wouldn't need to set the color each time
     * of every single primitive.
     */

    return(1);
}

RMnode *
makePost(int nthPost,		/*  which post */
	 RMvertex3D *fmin,	/* floor min coordinate */
	 RMvertex3D *fmax,	/* floor max coordinate */
	 float zlimit,		/* the distance between floor and sky */
	 RMprimitive **appPrimArray, /* the static app RMprim * array */
	 int *indexList)
{
    RMprimitive *p;
    RMnode *n;
    char buf[32];
    int r1,r2,r3,r4;
    RMvertex3D v[2];
    RMcolor4D c={1.0F,1.0F,1.0F,1.0F};
    float radius,scale;

    sprintf(buf,"Post%d",nthPost);
    n = rmNodeNew(buf,RM_RENDERPASS_3D,RM_RENDERPASS_OPAQUE);
    *appPrimArray = p = rmPrimitiveNew(RM_CYLINDERS);

    /*
     * use the client data attribute of RMprimitives to store some
     * info for later use. we're storing an address which is the
     * location of an integer, that integer contains an index
     * into an array of RMprimitive pointers. we'll use that info
     * during the pretraversal callback if the primitive is visible.
     */
    rmNodeSetClientData(n,(void *)(indexList),NULL); 

    /* rand numbers in range 0..32767 */
    r1 = rand() & 0x07FFF;
    r2 = rand() & 0x07FFF;
    r3 = rand() & 0x07FFF;
    r4 = rand() & 0x07FFF;

    /*
     * r1 & r2 will give us the x/y coordinate of the post. we need
     * to convert r1 & r2 from 0..32767 to the x/y range defined
     * by fmin/fmax.
     */
    scale = (fmax->x - fmin->x)/32767.0F;
    v[0].x = v[1].x = (float)r1 * scale + fmin->x;
    
    scale = (fmax->y - fmin->y)/32767.0F;
    v[0].y = v[1].y = (float)r2 * scale + fmin->y;

    v[0].z = fmin->z;

    /*
     * r3 gives us the height of the cone. we must convert from
     * 0..32767 to the range defined by zlimit.
     */
    scale = zlimit/32767.0F;
    v[1].z = (float)r3 * scale + fmin->z; 

    /*
     * r4 gives us the radius. we'll convert from 0..32767 to
     * a maximum of 1/60 the hypotenuse length of the overall grid.
     * this choice is completely arbitrary.
     */
    scale = (fmax->x - fmin->x)/(32767.0F * 60.0F);
    radius = (float)r4 * scale;

    rmPrimitiveSetVertex3D(p,2,v,RM_COPY_DATA,NULL);
    rmPrimitiveSetRadii(p,1,&radius,RM_COPY_DATA,NULL);

    /* do color later */

    rmPrimitiveSetColor4D(p,1,&c,RM_COPY_DATA,NULL);
    rmPrimitiveSetModelFlag(p,RM_CYLINDERS_4);

    rmNodeAddPrimitive(n,p);

    {
	RMvertex3D center;

	center.x = v[0].x + 0.5 * (v[1].x - v[0].x);
	center.y = v[0].y + 0.5 * (v[1].y - v[0].y);
	center.z = v[0].z + 0.5 * (v[1].z - v[0].z);

	rmNodeSetCenter(n,&center);

    }

    rmNodeComputeBoundingBox(n);
    rmNodeComputeCenterFromBoundingBox(n);
    rmNodeSetPreTraversalCallback(n,RM_VIEW, myPreTraversalCallback);
/*    rmNodeSetPreTraversalCallback(n,RM_VIEW, rmNodeFrustumCullCallback);  */
    
    return(n);
}

void
myrenderfunc(RMpipe *p,
	     RMnode *n)
{
    rmFrame(p, n);
}

void
addText(RMnode *addTo,
	RMvertex3D *v,
	char *s)
{
    RMnode *n = rmNodeNew("textNode", RM_RENDERPASS_3D, RM_RENDERPASS_OPAQUE);
    RMprimitive *p = rmPrimitiveNew(RM_TEXT);
    RMcolor4D c={1.,1.,1.,1.};
    char *strings[1];

    strings[0] = s;
    rmNodeSetUnlitColor(n, &c);  

    rmPrimitiveSetVertex3D(p, 1, v, RM_COPY_DATA, NULL);
    rmPrimitiveSetText(p, 1, strings);
    rmNodeAddPrimitive(n, p);
    rmNodeAddChild(addTo, n);
}

void
addLineSeg(RMnode *addTo,
	   RMvertex3D *ref,
	   float zHeight)
{
    RMnode *n = rmNodeNew("lineNode", RM_RENDERPASS_3D, RM_RENDERPASS_OPAQUE);
    RMprimitive *p = rmPrimitiveNew(RM_LINES);
    RMcolor4D c={1.,1.,1.,1.};
    RMvertex3D v[2];

    rmNodeSetUnlitColor(n, &c); 

    v[0] = *ref;
    v[0].z = 0.0F;
    v[1] = *ref;
    v[1].z = zHeight;
    
    rmPrimitiveSetVertex3D(p, 2, v, RM_COPY_DATA, NULL);
    rmNodeAddPrimitive(n, p);
    rmNodeAddChild(addTo, n);
}

void
addPost(RMnode *addTo,
	RMvertex3D *ref,
	float zHeight)
{
    RMnode *n = rmNodeNew("postNode", RM_RENDERPASS_3D, RM_RENDERPASS_OPAQUE);
    RMprimitive *p = rmPrimitiveNew(RM_CYLINDERS);
    RMcolor4D c={1.,1.,1.,1.};
    RMvertex3D v[2];
    float r = .5F;

    rmNodeSetUnlitColor(n, &c);  

    v[0] = *ref;
    v[0].z = 0.0F;
    v[1] = *ref;
    v[1].z = zHeight;
    
    rmPrimitiveSetVertex3D(p, 2, v, RM_COPY_DATA, NULL);
    rmPrimitiveSetRadii(p, 1, &r, RM_COPY_DATA, NULL);
    rmNodeAddPrimitive(n, p);
    rmNodeAddChild(addTo, n);
}

void
addSprite(RMnode *addTo,
	  RMvertex3D *ref,
	  RMimage *spriteImage)
{
    RMnode *n = rmNodeNew("spriteNode", RM_RENDERPASS_3D, RM_RENDERPASS_OPAQUE);
    RMprimitive *p = rmPrimitiveNew(RM_SPRITE);
    RMcolor4D c={1.,1.,1.,1.};

    rmNodeSetUnlitColor(n, &c);

    rmPrimitiveSetVertex3D(p, 1, ref, RM_COPY_DATA, NULL);
    rmPrimitiveSetSprites(p, 1, &spriteImage);

    rmNodeAddPrimitive(n, p);
    rmNodeAddChild(addTo, n);
}

void
addMarker(RMnode *addTo,
	  RMvertex3D *v,
	  float xDelta,
	  float zHeight,
	  int numLines,
	  RMimage *spriteImage)
{
    char buf[32];
    RMvertex3D v2;
    int j;

    sprintf(buf,"%g",v->y);
    v2 = *v;
    v2.z = 1.0;
#if 1
    addText(addTo, &v2, buf);
#endif

#if 1
    for (j=0;j<numLines;j++)
    {
	RMvertex3D ref;
	ref = *v;
	ref.x = ((float)j/(float)(numLines - 1)) * xDelta + v->x;
	addLineSeg(addTo,&ref, zHeight);
    }
#endif

#if 1
    for (j=0;j<numLines;j++)
    {
	RMvertex3D ref;
	ref = *v;
	ref.x = ((float)j/(float)(numLines - 1)) * xDelta + v->x;
	ref.y += 5.0F;		/* parameterize!! */
	addPost(addTo,&ref, zHeight);
    }
#endif

#if 1
    {
	RMvertex3D ref;
	ref = *v;
	ref.x += 0.333 * xDelta;
	ref.z = 0.1;
	addSprite(addTo, &ref, spriteImage);

	ref.x += 0.666 * xDelta;
	addSprite(addTo, &ref, spriteImage);
    }
#endif
    
    v2.x += xDelta;
#if 1
    addText(addTo, &v2, buf);
#endif
}


void
addMarkers(RMnode *addToNode,
	   RMvertex3D *floorMin,
	   RMvertex3D *floorMax)
{
    int i, limit;
    RMimage *srcImage = rmiReadJPEG("data/doghead.jpg");
    RMimage *spriteImage = rmImageNew(2, 64, 64, 1, RM_IMAGE_RGB, RM_UNSIGNED_BYTE, RM_COPY_DATA);
    rmImageResize(srcImage, spriteImage, RM_SOFTWARE, NULL);
    rmImageMirror(spriteImage,RM_IMAGE_MIRROR_HEIGHT);

    limit = NUM_MARKERS;
    for (i=0;i<limit;i++)
    {
	float t;
	RMvertex3D v;

	t = ((float)i/(float)NUM_MARKERS);
	
	v.x = floorMin->x;
	v.y = (floorMax->y - floorMin->y) * t + floorMin->y;
	v.z = floorMin->z;

	addMarker(addToNode, &v, 20.0F, 2.0F, NUM_LINES, spriteImage);
    }
}

void
myInitFunc(RMpipe *p,
	   RMnode *n)
{
    int w,h;
    RMnode *objRoot = rmNodeNew("objRoot", RM_RENDERPASS_3D, RM_RENDERPASS_ALL);
    RMnode *camNode = rmNodeNew("camNode", RM_RENDERPASS_3D, RM_RENDERPASS_ALL);
    RMnode *lightsNode = rmNodeNew("lightsNode", RM_RENDERPASS_3D, RM_RENDERPASS_ALL);
    RMvertex3D floorMin, floorMax;

    /*
     * define the extents of the floor object
     */
    floorMin.x = -10.0;
    floorMin.y = 0.0;
    floorMin.z = 0.0F;
    
    floorMax.x = 10.0;
    floorMax.y = 100.0;
    floorMax.z = 0.0F;
    
    addFloor(objRoot, &floorMin, &floorMax);
    addMarkers(objRoot, &floorMin, &floorMax);

    rmPipeGetWindowSize(p, &w, &h);
    addCamera(camNode, w, h);
#if 1
    /* root->camera->lights->objs */
    rmNodeAddChild(n, camNode);
    rmNodeAddChild(camNode, lightsNode);
    rmNodeAddChild(lightsNode, objRoot);
    rmNodeSetNormalizeNormals(n, RM_TRUE);
#else    
    /* root->lights->camera->objs */
    rmNodeAddChild(n, lightsNode);
    rmNodeAddChild(lightsNode, camNode);
    rmNodeAddChild(camNode, objRoot);
    rmNodeSetNormalizeNormals(n, RM_TRUE);
#endif    
    addLighting(lightsNode);

    if (doFog == 1)
	addFog(lightsNode);
    
    {
	RMcolor4D bg={0.2,0.2,0.2,1.0};
	rmPipeSetSceneBackgroundColor(p, &bg);
    }
    
    rmauxFlyUI(camNode, n, p, (float)(1.0/30.0), (float)(1.0/60.0)); 

    if (rmPipeProcessingModeIsMultithreaded(p) == RM_TRUE)
	rmFrame(p, n);
    
    rmFrame(p, n);
}

#ifdef RM_WIN
int WINAPI WinMain (HINSTANCE hInstance,
		    HINSTANCE hPrevInstance,
                    LPSTR lpszCmdLine, int nCmdShow)
{
    MSG      msg; 
    HWND     w;
    void *fptr;
    int status;
    RMpipe *pipe = NULL;
    RMenum channelFormat = RM_MONO_CHANNEL;
    RMenum processingMode = DEFAULT_PROCESSING_MODE; /* in procmode.h */
    RMenum targetPlatform = RM_PIPE_WGL;

    if (parseArgs(__argc, __argv) < 0)
	exit(1);

#else  /* assume RM_X */
int
main(int ac,
     char *av[])
{
    Window w;
    RMenum channelFormat = RM_MONO_CHANNEL;
    RMenum processingMode = DEFAULT_PROCESSING_MODE; /* in procmode.h */
    RMenum targetPlatform = RM_PIPE_GLX;
    RMpipe *pipe=NULL;

    if (parseArgs(ac,av) < 0)
	exit(1);
    
#endif


    rmInit();
    pipe = rmPipeNew(targetPlatform);

    rmPipeSetChannelFormat(pipe, channelFormat);
    rmPipeSetProcessingMode(pipe, processingMode);
    /*    rmPipeSetProcessingMode(pipe, RM_PIPE_SERIAL);    */
    rmPipeSetFrameRate(pipe, frameRate);

#ifdef RM_WIN
    fptr = (void *)(rmauxWndProc);
    w = rmauxCreateW32Window(pipe,
			     NULL, /* no parent window */
			     20,20,imgWidth,imgHeight,"Win32 fogtest",
			     hInstance,fptr);
    if (w == 0)
	exit(-1);
#else
    
    w = rmauxCreateXWindow(pipe,
			   (Window)NULL,
			   0,0,imgWidth,imgHeight,
			   "X11 fogtest","X11 fogtest", RM_TRUE);
#endif
    rmPipeSetWindow(pipe,w,imgWidth,imgHeight);
    rmauxSetInitFunc(myInitFunc); 

    rmPipeMakeCurrent(pipe);

    rmauxSetRenderFunc(myrenderfunc);
    
    /*
     * set key handler function so this prog will exit on "q" key.
     */
    rmauxSetKeyFunc(pipe, rmauxDefaultKeyFunc);
    
#ifdef RM_WIN
    rmauxEventLoop(pipe,rmRootNode(),&msg);
#else
    rmauxEventLoop(pipe,rmRootNode(),NULL);
#endif
    rmPipeDelete(pipe);
    rmFinish();

#ifdef RM_WIN
    return(msg.wParam);
#else
    return(1);
#endif
}
