Sophie

Sophie

distrib > Mandriva > 2007.0 > i586 > media > contrib-release > by-pkgid > 8079d983ecf371717db799dd75bd56c2 > files > 133

libopenrm1-1.5.2-2mdv2007.0.i586.rpm

/*
 * Copyright (C) 2001-2003, 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.6 2004/01/17 03:45:46 wes Exp $
 * Version: $Name: OpenRM-1-5-2-RC1 $
 * $Revision: 1.6 $
 * $Log: fogtest.c,v $
 * 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
#define NUM_POSTS 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 RMnode *myRoot=NULL;
static RMnode *myPostsRoot=NULL;
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 int *postPrimIndices=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[4];
    RMvertex2D tc[4];
    RMvertex3D n;

    floor = rmNodeNew("floor",RM_RENDERPASS_3D, RM_RENDERPASS_OPAQUE);
    p = rmPrimitiveNew(RM_QUADS);

    /*
     * note that we're using the min z coordinate for the entire surface.
     */
    v[0].x = fmin->x; tc[0].x = 0.0;
    v[0].y = fmin->y; tc[0].y = 0.0;
    v[0].z = fmin->z;
    
    v[1].x = fmax->x;  tc[1].x = 1.0;
    v[1].y = fmin->y; tc[1].y = 0.0;
    v[1].z = fmin->z;

    v[2].x = fmax->x; tc[2].x = 1.0;
    v[2].y = fmax->y; tc[2].y = 1.0;
    v[2].z = fmin->z;

    v[3].x = fmin->x; tc[3].x = 0.;
    v[3].y = fmax->y;  tc[3].y = 1.;
    v[3].z = fmin->z;

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

    rmPrimitiveSetVertex3D(p,4,v,RM_COPY_DATA,NULL);
    rmPrimitiveSetNormal3D(p,1,&n,RM_COPY_DATA,NULL);
    rmPrimitiveSetTexcoord2D(p,4,tc,RM_COPY_DATA,NULL); 

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

}

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 *toModify)
{
    RMlight *l0;

    /*
     * light source is a directional light pointing from the +Z direction
     */
    RMcolor4D diffuse = { .7,.7,.7,1.0};
    RMcolor4D specular = { .5,0.5,0.5,1.};
    RMcolor4D diffuse2 = { .3, 0.3, 0.5, 1.0 };
    RMvertex3D direction = { 0.5, -0.5, 1.0}; 

    l0 = rmLightNew();

    rmLightSetType(l0, RM_LIGHT_DIRECTIONAL); 
    rmLightSetColor(l0, NULL, &diffuse, &specular); 
    rmLightSetXYZ (l0, &direction);

    rmNodeSetSceneLight(toModify, RM_LIGHT0, l0);

    rmLightDelete(l0);
    

    /* then set the light model parms. */
    {
	RMcolor4D defAmbient =  { .2,.2,.2,1.0};
	RMlightModel *lm;

	lm = rmLightModelNew();
	rmLightModelSetAmbient(lm, &defAmbient);

	rmLightModelSetTwoSided (lm, RM_FALSE);
	rmLightModelSetLocalViewer(lm, RM_FALSE);
	
	rmNodeSetSceneLightModel(toModify, lm); 

	rmLightModelDelete (lm);
    }
}

void
addFog(RMnode *n)
{
    RMfog *f;
    RMcolor4D fogColor={0.15,0.06,0.02,1.0};
    
    f = rmFogNew();
    
    rmFogSetDensity(f,0.15F);
    rmFogSetMode(f,GL_EXP);

#if 0
    /* 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)
{
    int i;
    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);
}


RMnode *
addPostsToFloor(RMnode *addToNode,
		int nposts,
		RMvertex3D *fmin,
		RMvertex3D *fmax,
		float zlimit)
{
    RMnode *postsRoot;
    int i;

    /*
     * in this routine, we're going to create a bunch of objects that
     * will sit on the floor - gives us something to look at while we
     * fly around. each object is going to be added as a node to the
     * postsRoot object. we're using one node per object so we can play
     * some games using view-dependent operations. 
     */

    postsRoot = rmNodeNew("postsRoot", RM_RENDERPASS_3D, RM_RENDERPASS_OPAQUE);
    rmNodeAddChild(addToNode, postsRoot);

    if (postPrimIndices != NULL)
	free((void *)postPrimIndices);
    if (postPrims != NULL)
	free((void *)postPrims);
    if (lastDistance != NULL)
	free((void *)lastDistance);

    postPrims = (RMprimitive **)malloc(sizeof(RMprimitive *)*nposts);
    postPrimIndices = (int *)malloc(sizeof(int)*nposts);
    lastDistance = (float *)malloc(sizeof(float)*nposts);

    /*
     * 
     */
    srand(time(NULL));
    
    for (i=0;i<nposts;i++)
    {
	RMnode *post;

	postPrimIndices[i] = i;
	lastDistance[i] = RM_MINFLOAT; /* an unlikely-to-occur number */
	
	rmNodeAddChild(postsRoot,
		       makePost(i,fmin,fmax,zlimit,postPrims+i,
				postPrimIndices+i));
    }
}

void
myrenderfunc(RMpipe *p, RMnode *n)
{
    unsigned long sleepMsec = 100;
    
/*    usleep(sleepMsec); */
    rmFrame(p, n);

/*    rmSubtreeFrame(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,
	  int numPosts,
	  RMimage *spriteImage)
{
    char buf[32];
    RMvertex3D v2;
    int j;

    sprintf(buf,"%g",v->y);
    v2 = *v;
    v2.z = 1.0;
    addText(addTo, &v2, buf); 
    for (j=0;j<numLines;j++)
    {
	RMvertex3D ref;
	ref = *v;
	ref.x = ((float)j/(float)(numLines - 1)) * xDelta + v->x;
	addLineSeg(addTo,&ref, zHeight);
    }

    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);
    }

    {
	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);
    }
    
    v2.x += xDelta;
    addText(addTo, &v2, buf);
}

void
myInitFunc(RMpipe *p, RMnode *n)
{
    float orientScale, translateScale;
    RMvertex3D floorMin, floorMax;
    RMpipe *currentPipe;
    int imgWidth, imgHeight;
    int i;
    RMimage *srcImage = rmiReadJPEG("data/doghead.jpg");
    RMimage *spriteImage;

    currentPipe = p;
    rmPipeGetWindowSize(currentPipe, &imgWidth, &imgHeight);

    orientScale = 1./30.0;
    translateScale = 1./60.0;
    
    myRoot = rmNodeNew("myRoot",RM_RENDERPASS_ALL, RM_RENDERPASS_OPAQUE);

    /*
     * 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(myRoot,&floorMin, &floorMax);

    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);
    
    for (i=0;i<NUM_MARKERS;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(myRoot, &v, 20.0F, 2.0F, NUM_LINES,NUM_POSTS, spriteImage);
    }

    addCamera(rmRootNode(), imgWidth, imgHeight);

    /*
     * set handler to reset aspect ratio when the window is resized.
     */
    rmauxSetResizeFunc(p, rmRootNode(), rmauxDefaultResizeFunc);
    
    addLighting(rmRootNode()); 
    if (doFog == 1)
	addFog(rmRootNode());

    rmauxFlyUI(rmRootNode(),rmRootNode(),p,
	       orientScale,
	       translateScale);

    rmNodeAddChild(rmRootNode(), myRoot);


    /*
     * modify the rmRootNode() so that it is processed ONLY for
     * the 3D opaque rendering pass.
     */

    rmNodeSetTraversalMaskDims(rmRootNode(), RM_RENDERPASS_3D);
    rmNodeSetTraversalMaskOpacity(rmRootNode(), RM_RENDERPASS_OPAQUE);

    /*
     * or, we coule accomplish the same thing by modifying the
     * RMpipe to have only a single rendering pass: 3D, opaque
     */
#if 0
    rmPipeSetRenderPassEnable(p,RM_TRUE, RM_FALSE, RM_FALSE);
#endif    
    {
/*	RMcolor4D bg={0.1,0.1,0.15,1.0}; */
	RMcolor4D bg={0.5,0.5,0.5,1.0};
	/*
	 * assign a background color to take effect at "MyRoot"
	 */
	rmNodeSetSceneBackgroundColor(rmRootNode(), &bg);
    }
    {
	RMvertex3D bmin, bmax;
	bmin.x = bmin.y = -10.;
	bmin.z = 0.0;		/* temp */

	bmax.x = bmax.y = 10.0;
	bmax.z = 1.0;		/* temp */

	rmNodeSetBoundingBox(rmRootNode(), &bmin, &bmax);
	rmNodeComputeCenterFromBoundingBox(rmRootNode());
    }

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


#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 */
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);

    rmPipeSetProcessingMode(pipe, processingMode);
    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
}