/*
 * Copyright (C) 1997-2008, 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: tfly.c,v 1.23 2008/11/09 14:37:04 wes Exp $
 * Version: $Name: v180-alpha-01 $
 * $Revision: 1.23 $
 * $Log: tfly.c,v $
 * Revision 1.23  2008/11/09 14:37:04  wes
 * Update dates to 2008
 *
 * Revision 1.22  2006/08/08 03:49:21  wes
 * Updates to reflect ergonomic changes to rmaux
 *
 * Revision 1.21  2006/08/07 12:53:26  wes
 * Updates to reflect API changes in RMaux for button/key/idle callbacks.
 *
 * Revision 1.20  2005/06/26 19:07:57  wes
 * Several changes:
 * (1) Added code to check texture size via texture proxy, and compute
 * reduced textures until they fit.
 * (2) Replaced tfan floor and clouds layer with big quadmeshes to produce
 * better visual results.
 * (3) Added key handler to toggle wireframe mode no (w key) or back to
 * filled polygon rendering mode (f key).
 * (4) Turned on local viewer attribute in light model to produce better
 * visual results.
 *
 * Revision 1.19  2005/06/08 16:14:51  wes
 * Code cleanup to eliminate compiler warnings.
 *
 * Revision 1.18  2005/05/16 01:18:38  wes
 * Remove dead code.
 *
 * Revision 1.17  2004/02/23 02:58:02  wes
 * Many updates:
 * (1) Added sky field using image of cassiopeia,
 * (2) added boundary walls using a panorma of an Illinois prairie,
 * (3) changed placement of posts and generation of floor to a radial grid.
 *
 * Revision 1.16  2004/01/17 03:19:08  wes
 * No significant changes.
 *
 * Revision 1.15  2003/12/06 03:26:47  wes
 * Added command line argument "-fr NN" to permit use of constant-frame
 * rendering capabilities.
 *
 * Revision 1.14  2003/07/20 14:56:57  wes
 * Minor tweaks to fix compile warnings on Solaris.
 *
 * Revision 1.13  2003/04/13 18:13:23  wes
 * Updated copyright dates.
 *
 * Revision 1.12  2003/01/27 05:07:07  wes
 * Changes to RMpipe initialization sequence APIs. Tested for GLX, but not WGL.
 *
 * Revision 1.11  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.10  2002/06/17 00:44:37  wes
 * Added -s command line flag to generate per-frame rendering statistics.
 * Added -n NN command line argument to set the number of cylinders
 * that are generated for the scene graph.
 *
 * Revision 1.9  2001/07/15 22:33:19  wes
 * Added rmPipeDelete to the end of all demo progs. For those that use
 * an initfunc, added a new RMnode * parm (which is unused, except for rm2screen).
 *
 * Revision 1.8  2001/06/03 19:44:05  wes
 * Add calls to new rmaux routines to handle window resize events, and
 * for keyboard event handling.
 *
 * Revision 1.7  2001/03/31 16:55:18  wes
 * Added procmode.h, which defines an RMpipe processing mode used in
 * most demonstration programs. The default processing mode is
 * RM_PIPE_MULTISTAGE_VIEW_PARALLEL.
 *
 * Revision 1.6  2000/12/02 17:24:32  wes
 * Version 1.4.0-alpha-1 checkin. See the RELEASENOTES file for
 * a summary of changes. With this checkin, these demo programs
 * are no longer compatible with versions of the OpenRM API that
 * are pre-1.4.0.
 *
 * Revision 1.5  2000/09/02 19:26:36  wes
 * Changed sky to RM_SHADER_NOLIGHT.
 *
 * Revision 1.4  2000/08/31 02:59:07  wes
 * Added new parms to Win32 rmauxEventLoop.
 *
 * Revision 1.3  2000/08/28 01:38:18  wes
 * Updated rmaux* interfaces - rmauxEventLoop now takes two additional
 * parameters (keypress and window resize app callbacks); replaced
 * rmauxUI with rmauxSetGeomTransform and, where appropriate,
 * rmauxSetCamera3DTransform.
 *
 * Revision 1.2  2000/04/20 18:04:34  wes
 * Minor tweaks and reorg for OpenRM 1.2.1.
 *
 * Revision 1.1.1.1  2000/02/28 21:55:30  wes
 * OpenRM 1.2 Release
 *
 * Revision 1.2  2000/02/28 17:21:55  wes
 * RM 1.2, pre-OpenRM
 *
 */

#define NUM_POSTS 512

#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;
RMnode *myRoot=NULL;
RMnode *myOpaqueRoot=NULL, *myTransparentRoot=NULL;
RMnode *myPostsRoot=NULL;
int doFog = 0;

static char cloudsTextureFilename[]={"data/clouds.x"};
static char panoramaTextureFilename[]={"data/prairie.jpg"};
static char ceilingTextureFilename[]={"data/cassio.jpg"};

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

static int doStats=0;
static int numPosts=NUM_POSTS;
static int frameRate = -1;

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

    sprintf(buffer," usage: %s [-w imgWidth (default=640)] [-h imgHeight (default=512)] [-f (turn on fog - see tfly.c code for fogging details )] [-s (turn on frame rate statistics)] [-n NNN (set number of cylinders to NNN, default is %d)] [-fr NN (set frame rate to NN frames/second. Default is to go as fast as possible.)]\n ",progName, NUM_POSTS);
		
    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],"-n") == 0)
	{
	    i++;
	    ac--;
	    sscanf(av[i],"%d",&numPosts);
	}
	else if (strcmp(av[i],"-f") == 0)
	{
	    doFog = 1;
	}
	else if (strcmp(av[i],"-s") == 0)
	{
	    doStats = 1;
	}
	else if (strcmp(av[i],"-fr") == 0)
	{
	    i++;
	    ac--;
	    sscanf(av[i],"%d", &frameRate);
	    if (frameRate < 0)
	    {
		fprintf(stderr," please set the framerate value to be a positive integer indicating the number of frames per second. \n");
		return -1;
	    }
	}
	else
	{
	    usage(av[0]);
	    return(-1);
	}
	i++;
	ac--;
    }

    return(1);
}

RMimage *
tryTexture(RMimage *s)
{
    /*
     * in this routine, we test the size of the input texture against
     * what OpenGL will actually digest by using the 2D proxy texture.
     * if the first resolution doesn't fit, we downsize and try again.
     * we repeat the process until we find something that fits.
     */
    GLint border=0; 		/* known for this app */
    GLint internalFormat=GL_RGBA; /* known for this app */
    GLenum texelFormat = GL_RGB; /* known for this app */
    GLenum texelType = GL_UNSIGNED_BYTE; /* known for this app */

    int sWidth, sHeight;
    int done=0;

    rmImageGetImageSize(s, NULL, &sWidth, &sHeight, NULL, NULL, NULL);

    for (; !done; )
    {
	int cWidth;
	
	glTexImage2D(GL_PROXY_TEXTURE_2D, 0, internalFormat, sWidth, sHeight, border, texelFormat, texelType, NULL);
	glGetTexLevelParameteriv(GL_PROXY_TEXTURE_2D, 0, GL_TEXTURE_WIDTH, &cWidth);
	
	if (cWidth != sWidth)
	{
	    RMimage *newImg;
	    int newW, newH;
	    newW = sWidth >> 1;
	    newH = sHeight >> 1;

	    printf(" OpenGL won't take a texture of size (%d, %d), trying (%d,%d) instead. \n", sWidth, sHeight, newW, newH);

	    newImg = rmImageNew(2, newW, newH, 1, RM_IMAGE_RGB, RM_UNSIGNED_BYTE, RM_COPY_DATA);
	    rmImageResize(s, newImg, RM_SOFTWARE, NULL);

	    rmImageDelete(s);

	    s = newImg;
	    sWidth = newW;
	    sHeight = newH;
	}
	else
	    done = 1;
    }

    return s;
}

/*
 * 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
buildQuadMesh(RMnode *addTo,
	      float radius,
	      float height,
	      int subdivisions,
	      int doNormals,
	      float tcOffsetU,
	      float tcOffsetV)
{
    /* build a quadmesh primitive */
    RMvertex3D *v = NULL;
    RMvertex3D *n = NULL;
    RMvertex2D *tc = NULL;
    RMvertex3D refNormal={0.0F, 0.0F, 1.0F};
    int i, j, indx;
    RMprimitive *p = NULL;

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

    v = rmVertex3DNew(subdivisions*subdivisions);
    n = rmVertex3DNew(subdivisions*subdivisions);
    tc = rmVertex2DNew(subdivisions*subdivisions);

    x = -radius;
    dx = 2.0*radius/(double)(subdivisions-1);
    y = -radius;
    dy = 2.0*radius/(double)(subdivisions-1);

    s = tcOffsetU;;
    ds = 1.0/(double)(subdivisions-1);

    t = tcOffsetV;
    dt = 1.0/(double)(subdivisions-1);

    indx = 0;
    for (i=0; i<subdivisions; i++, x+=dx, s+=ds)
    {
	y = -radius;
	t = tcOffsetV;
	for (j=0; j<subdivisions; j++, y+=dy, t+=dt)
	{
	    v[indx].x = x;
	    v[indx].y = y;
	    v[indx].z = height;

	    n[indx] = refNormal;

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

	    indx++;
	}
    }
    
    p = rmPrimitiveNew(RM_QUADMESH);
    rmPrimitiveSetQmeshDims(p, subdivisions, subdivisions);
    rmPrimitiveSetVertex3D(p, subdivisions*subdivisions, v, RM_COPY_DATA, NULL);
    if (doNormals != 0)
	rmPrimitiveSetNormal3D(p, subdivisions*subdivisions, n, RM_COPY_DATA, NULL);
    rmPrimitiveSetTexcoord2D(p, subdivisions*subdivisions, tc, RM_COPY_DATA, NULL);

    rmNodeAddPrimitive(addTo, p);
    
    rmVertex3DDelete(v);
    rmVertex3DDelete(n);
    rmVertex2DDelete(tc);
}

void
addFloor3(RMnode *addTo,
	  float radius,
	  float height,
	  int subdivisions)
{
    RMnode *floor = rmNodeNew("floor", RM_RENDERPASS_3D, RM_RENDERPASS_OPAQUE);
    rmNodeAddChild(addTo, floor);
    
    buildQuadMesh(floor, radius, height, subdivisions, 1, 0.0F, 0.0F);

    addFloorTileTexture(floor);

}

void
addFloor2(RMnode *addTo,
	  float radius,
	  float height,
	  int subdivisions)
{
    /*
     * Here we create a triangle fan centered at the origin and of
     * resolution "subdivisions." It works well except that fogging
     * starts to reveal rendering artifacts due to the fact there
     * are long, skinny triangles.
     */
    RMvertex3D *v = NULL;
    RMvertex3D *n = NULL;
    RMvertex2D *tc = NULL;
    RMvertex3D refNormal={0.0F, 0.0F, 1.0F};
    int i;
    double c, s, a, da, t, dt;
    RMprimitive *p = NULL;
    RMnode *floor = NULL;

    v = rmVertex3DNew(subdivisions+2);
    n = rmVertex3DNew(subdivisions+2);
    tc = rmVertex2DNew(subdivisions+2);

    a = 0.0;
    da = 2.0*RM_PI/(double)(subdivisions-1);

    t = 0.0;
    dt = 1./(double)(subdivisions-1);

    v[0].x = v[0].y = 0.0F;
    v[0].z = height;

    n[0] = refNormal;

    tc[0].x = 0.5;
    tc[0].y = 0.5;
    
    for (i=1; i <= subdivisions+1; i++, a+=da, t+= dt)
    {
	c = cos(a);
	s = sin(a);
	
	v[i].x = c * radius;
	v[i].y = s * radius;
	v[i].z = height;

	n[i] = refNormal;

	tc[i].x = (c + 1.0)*0.5;
	tc[i].y = (s + 1.0)*0.5;

    }

    floor = rmNodeNew("floor", RM_RENDERPASS_3D, RM_RENDERPASS_OPAQUE);
    p = rmPrimitiveNew(RM_TRIANGLE_FAN);
    rmPrimitiveSetVertex3D(p, subdivisions+2, v, RM_COPY_DATA, NULL);
    rmPrimitiveSetNormal3D(p, subdivisions+2, n, RM_COPY_DATA, NULL);
    rmPrimitiveSetTexcoord2D(p, subdivisions+2, tc, RM_COPY_DATA, NULL);

    rmNodeAddPrimitive(floor, p);
    rmNodeAddChild(addTo, floor);

    addFloorTileTexture(floor);   
}

void
addFloor(RMnode *addToNode,
	 RMvertex3D *fmin,
	 RMvertex3D *fmax)
{
    /*
     * Create the floor using a single quad. The same rendering problems
     * that plague the tfan approach apply here as well.
     */
    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];

	/* assign an alpha component that is 2x the red channel
	 component. The image contains either blues or shades of
	white. where there is a lot of red (white), make the alpha
	component more opaque. the result is that we'll be able to
	"see through" regions where there is no white (clouds) */
	static_image_data[indx+3] = RM_MIN(static_image_data[indx] * 2, 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
addCloudTexture(RMnode *addToNode,
		char *fname)
{
    RMimage *img;
    RMtexture *texture;
    
    img = readAVSImage(fname);
    
    texture = rmTextureNew(2);
    rmTextureSetImages(texture, &img, 1, RM_FALSE);
    rmTextureSetWrapMode(texture, GL_REPEAT);
    rmTextureSetFilterMode(texture, GL_LINEAR, GL_LINEAR);

    rmNodeSetSceneTexture(addToNode,texture);
    rmTextureDelete(texture, RM_FALSE);
}

#if 1
void
addCloudLayer(RMnode *addTo,
	      float radius,
	      float height,
	      int subdivisions,
	      float tcOffsetU,
	      float tcOffsetV)
{
    RMnode *sky = rmNodeNew("skylayer", RM_RENDERPASS_3D, RM_RENDERPASS_TRANSPARENT);
    RMcolor4D unlit={1.0,1.0,1.0,0.5};
    
    rmNodeSetUnlitColor(sky, &unlit);

    buildQuadMesh(sky, radius, height, subdivisions, 0,
		  tcOffsetU+0.5F, tcOffsetV+0.5F);

    rmNodeAddChild(addTo, sky);

    addCloudTexture(sky, cloudsTextureFilename);
}

#else 
void
addCloudLayer(RMnode *addTo,
	      float radius,
	      float height,
	      int subdivisions,
	      float tcOffsetU,
	      float tcOffsetV)
{
    RMvertex3D *v = NULL;
    RMvertex2D *tc = NULL;
    int i;
    double c, s, a, da, t, dt;
    RMprimitive *p = NULL;
    RMnode *sky = NULL;
    RMcolor4D unlit={1.0,1.0,1.0,0.75};

    v = rmVertex3DNew(subdivisions+2);
    tc = rmVertex2DNew(subdivisions+2);

    a = 0.0;
    da = 2.0*RM_PI/(double)(subdivisions);

    t = 0.0;
    dt = 1./(double)(subdivisions);

    v[0].x = v[0].y = 0.0F;
    v[0].z = height;

    tc[0].x = 0.5 + tcOffsetU;
    tc[0].y = 0.5 + tcOffsetV;
    
    for (i=1; i <= subdivisions+1; i++, a+=da, t+= dt)
    {
	c = cos(a);
	s = sin(a);
	
	v[i].x = c * radius;
	v[i].y = s * radius;
	v[i].z = height;

	tc[i].x = (c + 1.0)*0.5 + tcOffsetU;
	tc[i].y = (s + 1.0)*0.5 + tcOffsetV;

    }

    sky = rmNodeNew("skylayer", RM_RENDERPASS_3D, RM_RENDERPASS_TRANSPARENT);
    p = rmPrimitiveNew(RM_TRIANGLE_FAN);
    rmPrimitiveSetVertex3D(p, subdivisions+2, v, RM_COPY_DATA, NULL);

    rmPrimitiveSetTexcoord2D(p, subdivisions+2, tc, RM_COPY_DATA, NULL); 

    rmNodeSetUnlitColor(sky, &unlit);
    rmNodeAddPrimitive(sky, p);
    rmNodeAddChild(addTo, sky);

    addCloudTexture(sky, cloudsTextureFilename);
}
#endif


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 = 40.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.};
    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_TRUE);
	
	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);
    rmFogSetDensity(f,0.05F);
    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)
{
    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.
     */
    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 *
makePost2(int nthPost,		/*  which post */
	  float worldRadius,
	  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;
    double a, cs, ss, tr;

    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 are in the range 0..32767.
     * We will first convert them to the range -1..1,
     * then use them as input to acos() and asin() to produce an
     * x/y plan-view coordinate.
     */

    /*
     * convert r1 from the range 0..32767 to the range 0..2pi,
     * use the value to place the post at some theta position
     */
    a = (double)(r1)/32767.0; /* 0..1 */
    a *= 2.0*RM_PI; 		/* 0..2pi */

    tr = (double)(r2)/32767.0; /* 0..1 */
    cs = cos(a) * worldRadius * tr;
    ss = sin(a) * worldRadius * tr;
    v[0].x = cs;
    v[0].y = ss;
    v[0].z = 0.0;

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

    /*
     * r4 gives us the radius. we'll convert from 0..32767 to
     * a maximum of 1/60 the input world radius value.
     * this choice is completely arbitrary.
     */
    scale = (worldRadius*.5)/(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);
    
    return(n);
}

void
addPostsToFloor2(RMnode *addToNode,
		 int nposts,
		 float radius,
		 float zlimit)
{
    RMnode *postsRoot;
    int i;
    RMcolor4D unlit={1.0,1.0,1.0,1.0};

    /*
     * 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);
    rmNodeSetUnlitColor(postsRoot, &unlit);

    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)); */
    srand(123);
    
    for (i=0;i<nposts;i++)
    {
	postPrimIndices[i] = i;
	lastDistance[i] = RM_MINFLOAT; /* an unlikely-to-occur number */
	
	rmNodeAddChild(postsRoot,
		       makePost2(i,radius*0.75,zlimit,postPrims+i,
				postPrimIndices+i));
    }
}

int
myrenderfunc(RMpipe *p, 
	     RMnode *n,
	     void *usrData)
{
    
    if (doStats != 0)
	rmStatsStartTime();

    rmFrame(p, n);

    if (doStats != 0)
    {
	rmStatsEndTime();
	rmStatsPrint();
    }
    return 1;
}

void
addTopCap(RMnode *addTo,
	  float radius,
	  float baseHeight,
	  float centerHeight,
	  int subdivisions)
{
    RMvertex3D *v = NULL;
    RMvertex2D *tc = NULL;
    int i;
    double c, s, a, da, t, dt;
    RMprimitive *p = NULL;
    RMnode *n = NULL;
    RMimage *textureImage = rmiReadJPEG(ceilingTextureFilename);
    RMtexture *texture = rmTextureNew(2);
    RMcolor4D col={0.05F, 0.05F, 0.3F, 1.0F};

    v = rmVertex3DNew(subdivisions+2);
    tc = rmVertex2DNew(subdivisions+2);

    a = 0.0;
    da = 2.0*RM_PI/(double)(subdivisions-1);

    t = 0.0;
    dt = 1./(double)(subdivisions-1);

    v[0].x = v[0].y = 0.0F;
    v[0].z = centerHeight;

    tc[0].x = tc[0].y = 0.5;
    
    for (i=1; i <= subdivisions+1; i++, a+=da, t+= dt)
    {
	c = cos(a);
	s = sin(a);
	
	v[i].x = c * radius;
	v[i].y = s * radius;
	v[i].z = baseHeight;

	tc[i].x = (c + 1.0)*0.5;
	tc[i].y = (s + 1.0)*0.5;

    }

    n = rmNodeNew("topCap", RM_RENDERPASS_3D, RM_RENDERPASS_OPAQUE);
    rmNodeSetUnlitColor(n, &col);
    p = rmPrimitiveNew(RM_TRIANGLE_FAN);
    rmPrimitiveSetVertex3D(p, subdivisions+2, v, RM_COPY_DATA, NULL);

    rmPrimitiveSetTexcoord2D(p, subdivisions+2, tc, RM_COPY_DATA, NULL);

    textureImage = tryTexture(textureImage);

    rmTextureSetImages(texture, &textureImage, 1, 0);
    rmTextureSetFilterMode(texture, GL_LINEAR, GL_LINEAR);
    rmTextureSetEnv(texture, GL_DECAL, NULL);
    rmNodeSetSceneTexture(n, texture);

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

    rmImageDelete(textureImage);
    rmTextureDelete(texture, RM_TRUE);
}

void
addOutsideBoundaries(RMnode *addTo,
		     float radius,
		     float height,
		     int subdivisions)
{
    RMvertex3D *v = NULL;
    RMvertex2D *tc = NULL;
    int i;
    double c, s, a, da, t, dt;
    RMprimitive *p = NULL;
    RMnode *n = NULL;
    RMimage *textureImage = rmiReadJPEG(panoramaTextureFilename);
    RMtexture *texture = rmTextureNew(2);
    RMcolor4D col={0.27, 0.135, 0.07, 1.0};

    v = rmVertex3DNew((subdivisions+1)*2);
    tc = rmVertex2DNew((subdivisions+1)*2);

    a = 0.0;
    da = 2.0*RM_PI/(double)(subdivisions-1);

    t = 0.0;
    dt = 1./(double)(subdivisions-1);
    
    for (i=0; i <= subdivisions; i++, a+=da, t+= dt)
    {
	c = cos(a) * radius;
	s = sin(a) * radius;
	
	v[i*2].x = c;
	v[i*2].y = s;
	v[i*2].z = 0.0;

	tc[i*2].x = t;
	tc[i*2].y = 1.0; 

	
	v[i*2+1].x = c;
	v[i*2+1].y = s;
	v[i*2+1].z = height;

	tc[i*2+1].x = t;
	tc[i*2+1].y = 0.39;
    }

    n = rmNodeNew("outsideBoundary", RM_RENDERPASS_3D, RM_RENDERPASS_OPAQUE);
    rmNodeSetUnlitColor(n, &col);

    p = rmPrimitiveNew(RM_QUAD_STRIP);
    rmPrimitiveSetVertex3D(p, (subdivisions+1)*2, v, RM_COPY_DATA, NULL);
    rmPrimitiveSetTexcoord2D(p, (subdivisions+1)*2, tc, RM_COPY_DATA, NULL);

    textureImage = tryTexture(textureImage);
    rmTextureSetImages(texture, &textureImage, 1, 0);
    
    rmTextureSetFilterMode(texture, GL_LINEAR, GL_LINEAR);
    rmTextureSetEnv(texture, GL_DECAL, NULL);
    rmNodeSetSceneTexture(n, texture);

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

    rmImageDelete(textureImage);
    rmTextureDelete(texture, RM_TRUE);
}

void
myInitFunc(RMpipe *p,
	   RMnode *n,
	   void *usrData)
{
    float orientScale, translateScale;
    RMvertex3D floorMin, floorMax;
    RMvertex3D skyMin, skyMax;
    RMpipe *currentPipe;
    int imgWidth, imgHeight;
    float worldRadius=60.0F;
    int subdivisions = 90;

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

    orientScale = 1./30.0;
    translateScale = 1./60.0;
    
    myRoot = rmNodeNew("myRoot",RM_RENDERPASS_ALL, RM_RENDERPASS_ALL);
    myOpaqueRoot = rmNodeNew("opaqueRoot", RM_RENDERPASS_3D, RM_RENDERPASS_OPAQUE);
    myTransparentRoot = rmNodeNew("transparentRoot", RM_RENDERPASS_3D, RM_RENDERPASS_TRANSPARENT);
    rmNodeAddChild(myRoot, myOpaqueRoot);
    rmNodeAddChild(myRoot, myTransparentRoot);

    /*
     * define the extents of the floor object
     */
    floorMin.x = -30.0;
    floorMin.y = -30.0;
    floorMin.z = 0.0F;
    
    floorMax.x = 30.0;
    floorMax.y = 30.0;
    floorMax.z = 0.0F;
    
    addFloor3(myOpaqueRoot,worldRadius, 0.0F, 20);
    
/*    addFloor(myOpaqueRoot,&floorMin, &floorMax);   */
    
    /*
     * define the extents of the sky object
     * note that the floor and sky are basically just parallel surfaces
     */
    skyMin.x = -30.0;
    skyMin.y = -30.0;
    skyMin.z = 3.0F;
    
    skyMax.x = 30.0;
    skyMax.y = 30.0;
    skyMax.z = 3.0F;
    
    addCloudLayer(myTransparentRoot,worldRadius, 3.0F, subdivisions, 0.0F, 0.0F); 
    addCloudLayer(myTransparentRoot,worldRadius, 5.0F, subdivisions, 0.1F, 0.1F);
    

    skyMin.z = skyMax.z = 2.0;
    addPostsToFloor2(myOpaqueRoot, numPosts, worldRadius,
		     skyMin.z - floorMin.z);

    
    addOutsideBoundaries(myOpaqueRoot, worldRadius, 24.0F, subdivisions);
    addTopCap(myOpaqueRoot, worldRadius, 24.0F, 48.0F, subdivisions);;
    
    addCamera(n, imgWidth, imgHeight);

    /*
     * set handler to reset aspect ratio when the window is resized.
     */
    rmauxSetResizeFunc(p, n, rmauxDefaultResizeFunc);

    
    addLighting(n);
    if (doFog == 1)
	addFog(n);

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

    rmNodeAddChild(n, myRoot);


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

    rmNodeSetTraversalMaskDims(n, 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
     */
    {
	RMcolor4D bg={0.1,0.1,0.15,1.0};
	/*
	 * assign a background color to take effect at "myOpaqueRoot"
	 */
	rmNodeSetSceneBackgroundColor(myOpaqueRoot, &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(n, &bmin, &bmax);
	rmNodeComputeCenterFromBoundingBox(n);
    }

    if (doStats != 0)
	rmStatsComputeDemography(n);

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

int
tflyKeyFunc(RMpipe *currentPipe,
	    RMnode *n,
	    void *usrData,
	    char key,
	    KeySym code)
{
    int rstat = 1;
    switch (key)
    {
    case 'q': 			/* exit on 'q' or 'esc'*/
    case 27:
	rstat = 0;
	break;

    case 'w': 			/* toggle wireframe */
	rmNodeSetPolygonDrawMode(n, RM_FRONT_AND_BACK, RM_LINE);
	rmFrame(currentPipe, n);
	break;

    case 'f':
	rmNodeSetPolygonDrawMode(rmRootNode(), RM_FRONT_AND_BACK, RM_FILL);
	rmFrame(currentPipe, rmRootNode());
	break;
    }
    return rstat;
}
     
#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 stereoFormat = 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 stereoFormat = 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);
    /*    rmPipeSetRenderPassEnable(pipe, RM_TRUE, RM_FALSE, RM_FALSE); */
    rmPipeSetFrameRate(pipe, frameRate);
    rmPipeSetChannelFormat(pipe, stereoFormat);

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

    rmPipeMakeCurrent(pipe);

    rmauxSetRenderCallbackFunc(myrenderfunc, NULL);
    
    rmauxSetKeypressCallbackFunc(tflyKeyFunc, NULL);
    
#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
}
/* EOF */ 
