/*
 * Copyright (C) 2004-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: indexedPrims2D.c,v 1.6 2008/11/09 14:37:04 wes Exp $
 * $Revision: 1.6 $
 * $Name: v180-alpha-01 $
 * $Log: indexedPrims2D.c,v $
 * Revision 1.6  2008/11/09 14:37:04  wes
 * Update dates to 2008
 *
 * Revision 1.5  2006/08/08 03:49:21  wes
 * Updates to reflect ergonomic changes to rmaux
 *
 * Revision 1.4  2006/08/07 12:53:26  wes
 * Updates to reflect API changes in RMaux for button/key/idle callbacks.
 *
 * Revision 1.3  2005/06/08 17:52:55  wes
 * Code cleanup to eliminate compiler warnings.
 *
 * Revision 1.2  2004/03/10 01:50:53  wes
 * Added routine to generate an RM_INDEXED_QUAD_STRIP primitive, and
 * some minor index reordering to produce more reasonable primitives.
 *
 * Revision 1.1  2004/02/23 02:53:08  wes
 * Initial entry.
 *
 */

/*
 * usage: indexedPrims2D [-w imgWidthPixels ] [-h imgHeightPixels ] [ -prim ( iquads | itstrips | itris | iqstrips (default is iquads)) ] [-draw ( fill | line | point (default is fill)) ]
 */

#include <stdio.h>
#ifndef RM_WIN
#include <unistd.h>
#endif
#include <rm/rm.h>   
#include <rm/rmaux.h>
#include "procmode.h"

static RMpipe *myPipe=NULL;

#define DEFAULT_IMAGE_WIDTH 900
#define DEFAULT_IMAGE_HEIGHT 480

static float *floatDataValues=NULL;
static RMcolor3D *colorValues=NULL;
static float dataMin, dataMax;
static int   dataWidth, dataHeight;
static RMvertex2D *baseVertices=NULL;
static RMnode *myRoot=NULL, *updateNode=NULL;
static RMprimitive *updatePrimitive;
static int *indices=NULL;
static RMenum indexedPrimType = RM_INDEXED_QUADS;
static RMenum drawMode = RM_FILL;

/*
 * the following are the lat/lon values associated with the elev data.
 * for details, see: www.openrm.org/docs/elevImage-NOTES.txt
 */
RMvertex2D coordMin={-134.0, -51.0}, coordMax={-66.0, -21.0};


int
readData(char *fn)
{
    FILE *f = fopen(fn,"r");
    int w,h,n,i;

    if (f == NULL)
	return(0);

    fscanf(f,"%d %d",&w,&h);
    printf(" data width/height are %d, %d \n", w,h);
    n = w*h;

    printf(" reading data ...");

    dataMin = 1.0e10;
    dataMax = -dataMin;

    floatDataValues = (float *)malloc(sizeof(float)*n);
    for (i=0;i<n;i++)
    {
	fscanf(f,"%f",floatDataValues+i);
	if (floatDataValues[i] < dataMin)
	    dataMin = floatDataValues[i];
	if (floatDataValues[i] > dataMax)
	    dataMax = floatDataValues[i];
    }
    printf(" done. min/max are %f, %f\n", dataMin, dataMax);

    dataWidth = w;
    dataHeight = h;
	
    return 1;
}

void
makeGeoVismap(RMvisMap *m,
	      float dataMin,
	      float dataMax)
{
    int zeroIndex;
    float t;
    int i, n;
    RMcolor3D terrainStartHSV = {0.33, 0.9, 0.9};
    RMcolor3D terrainEndHSV = {0.0, 0.9, 0.9};

    RMcolor3D waterStartHSV = {0.475, 0.9, 0.9};
    RMcolor3D waterEndHSV = {0.60, 0.9, 0.9};
    float dh, ds, dv, h, s, v;

    /* objective: create a colormap such that negative values (below
     sea level) are shades of blue, and positive values range from
     greens through yellows to red (hue ramp). */

    /* find index in range 0..255 that corresponds to zero elevation */
    t = 255.0F * (-dataMin/(dataMax - dataMin));
    zeroIndex = (int)t;
    zeroIndex += 1;

    n = 255 - zeroIndex + 1;

    /* do below sea level values */
    dh = (waterEndHSV.r - waterStartHSV.r)/ (float)(n);
    ds = (waterEndHSV.g - waterStartHSV.g)/ (float)(n);
    dv = (waterEndHSV.b - waterStartHSV.b)/ (float)(n);

    h = waterStartHSV.r;
    s = waterStartHSV.g;
    v = waterStartHSV.b;

    for (i=zeroIndex-1; i >= 0; i--)
    {
	RMcolor3D c;
	rmHSVtoRGB(h, s, v, &c.r, &c.g, &c.b);
	rmVismapSetColor3D(m, i, &c);

	h += dh;
	s += ds;
	v += dv;
    }
    
    /* do above sea level values */
    dh = (terrainEndHSV.r - terrainStartHSV.r)/ (float)(n);
    ds = (terrainEndHSV.g - terrainStartHSV.g)/ (float)(n);
    dv = (terrainEndHSV.b - terrainStartHSV.b)/ (float)(n);

    h = terrainStartHSV.r;
    s = terrainStartHSV.g;
    v = terrainStartHSV.b;

    for (i=zeroIndex; i<255; i++)
    {
	RMcolor3D c;
	rmHSVtoRGB(h, s, v, &c.r, &c.g, &c.b);
	rmVismapSetColor3D(m, i, &c);

	h += dh;
	s += ds;
	v += dv;
    }
}

void
createColors(int dWidth,
	     int dHeight,
	     float dMin,
	     float dMax,
	     float *f,
	     RMcolor3D **returnColors)
{
    int i;
    RMvisMap *m = rmVismapNew(256);
    RMcolor3D *c;
    makeGeoVismap(m, dMin,dMax);
    rmVismapSetTfMin(m, dMin);
    rmVismapSetTfMax(m, dMax);
    
    *returnColors = c = rmColor3DNew(dWidth*dHeight);

    for (i=0;i<dWidth*dHeight;i++)
    {
	int indx;
	indx = rmVismapIndexFromData(m, f[i]);
	rmVismapGetColor3D(m, indx, c+i);
    }
}

void
createBaseVertices(const RMvertex2D *coordMin,
		   const RMvertex2D *coordMax,
		   int dWidth,
		   int dHeight,
		   RMvertex2D **baseVertices)
{
    /*
     * compute a set of vertices that contain the coordinates for the
     * entire wad of data. The min/max coordinates, or grid corners, are
     * defined by constants at the top of this file, and correspond to
     * lat/lon values. We are basically going to interpolate between the
     * grid corners to construct a full resolution grid.
     */
    float x, dx;
    float y, dy;
    int i,j, indx=0 ;
    RMvertex2D *v = rmVertex2DNew(dWidth*dHeight);
    *baseVertices = v;

    dx = (coordMax->x - coordMin->x)/(float)(dWidth-1);
    dy = (coordMax->y - coordMin->y)/(float)(dHeight-1);

    y = coordMin->y;
    for (j=0;j<dHeight;j++, y+=dy)
    {
	x = coordMin->x;
	for (i=0;i<dWidth;i++, x+=dx, indx++)
	{
	    v[indx].x = x;
	    v[indx].y = y;
	}
    }
}

void
myFreeFunc(void *d)
{
    /*
     * this is a no-op function as it is called on each frame since we
     * assign, then update, the primitive indices.
     */
    
    /* foil compiler warning */
    d = NULL;
}

void
createBaseSceneGraph(RMnode **myRoot,
		     RMnode **updateNode,
		     RMvertex2D *gMin,
		     RMvertex2D *gMax,
		     RMvertex2D *baseVertices,
		     RMcolor3D *colorValues,
		     int dWidth,
		     int dHeight)
{
    RMnode *n, *u, *n2;
    RMcamera2D *camera = rmCamera2DNew();
    RMcolor4D bgColor = {0.2, 0.2, 0.2, 1.0};
    RMprimitive *outlineBox;
    RMvertex2D extents[2];
    *myRoot = n = rmNodeNew("myRoot", RM_RENDERPASS_2D, RM_RENDERPASS_OPAQUE);
    *updateNode = u = rmNodeNew("updateNode", RM_RENDERPASS_2D, RM_RENDERPASS_OPAQUE);
    rmNodeAddChild(rmRootNode(), n);
    rmNodeSetSceneBackgroundColor(n, &bgColor);

    {
	extents[0].x = gMin->x - (gMax->x - gMin->x)*0.1;
	extents[0].y = gMin->y - (gMax->y - gMin->y)*0.1;
	
	extents[1].x = gMax->x + (gMax->x - gMin->x)*0.1;
	extents[1].y = gMax->y + (gMax->y - gMin->y)*0.1;
	
	rmCamera2DSetExtents(camera, extents[0].x, extents[1].y, extents[1].x, extents[0].y);
    }
    rmNodeSetSceneCamera2D(n, camera);
    rmNodeAddChild(n, u);

    {
	RMvertex2D v[2];
	v[0] = *gMin;
	v[1] = *gMax;
	outlineBox = rmPrimitiveNew(RM_BOX2D);
	rmPrimitiveSetVertex2D(outlineBox, 2, v, RM_COPY_DATA, NULL);
	n2 = rmNodeNew("staticBox", RM_RENDERPASS_2D, RM_RENDERPASS_OPAQUE);
	rmNodeAddPrimitive(n2, outlineBox);
	rmNodeSetPolygonDrawMode(n2, RM_FRONT_AND_BACK, RM_LINE);
	rmNodeAddChild(n, n2);
    }

    updatePrimitive = rmPrimitiveNew(indexedPrimType);
    rmNodeAddPrimitive(u, updatePrimitive);
    rmNodeSetPolygonDrawMode(u, RM_FRONT_AND_BACK, drawMode); 

    rmPrimitiveSetVertex2D(updatePrimitive, dWidth*dHeight, baseVertices, RM_DONT_COPY_DATA, myFreeFunc);
    rmPrimitiveSetColor3D(updatePrimitive, dWidth*dHeight, colorValues, RM_DONT_COPY_DATA, myFreeFunc);

}

void
createBaseIndices(int dWidth,
		  int dHeight,
		  RMenum indexedPrimType,
		  int **indices)
{
    /*
     * size of index pool is a function of dWidth, not dHeight. See
     * use of frameNumber % dWidth in updateSceneGraph().
     */
    int count;

    switch (indexedPrimType)
    {
    case RM_INDEXED_QUADS:
	count = (dWidth+1)*(dWidth+1)*4;
	break;
    case RM_INDEXED_TRIANGLES:
	count = (dWidth+1)*(dWidth+1)*6;
	break;
    case RM_INDEXED_QUAD_STRIP:
    case RM_INDEXED_TRIANGLE_STRIP:
	count = ((dWidth+1)*2+4)*(dWidth+1); /* extra points to create
						degenerate prims */
	break;
    default:
	rmWarning(" createBaseIndices() error - unable to compute the number of indices needed for the requested primitive type. \n");
	count = 1;
	break;
    }
    *indices = (int *)malloc(sizeof(int)*count);

    /* foil compiler warning */
    dHeight = 0;
}

void
myInitFunc(RMpipe *p,
	   RMnode *n,
	   void *usrData)
{
    /* read in the data we'll be working with */
    readData("data/elevData.txt");

    /* create a big array of RGB values for each data point */
    createColors(dataWidth, dataHeight, dataMin, dataMax, floatDataValues, &colorValues);

    /* compute a set of x/y vertices for each possibled grid point */
    createBaseVertices(&coordMin, &coordMax, dataWidth, dataHeight, &baseVertices);

    /* malloc some memory for indices */
    createBaseIndices(dataWidth, dataHeight, indexedPrimType, &indices);

    /* generate the base-level scene graph. */
    createBaseSceneGraph(&myRoot, &updateNode, &coordMin, &coordMax, baseVertices, colorValues, dataWidth, dataHeight);

    rmFrame(p, n);
}


#define makeIndex(i,j,w) ((j)*(w)+(i))

void
generateIndices(int frameNumber)
{
    int indexCount;
    int *ii = indices;
    int indx;
    int nDivisions = frameNumber % dataWidth;
    int i,j;
    int *iDivs, *jDivs;
    float d,dDiv;

    iDivs = (int *)malloc(sizeof(int)*(nDivisions+2));
    jDivs = (int *)malloc(sizeof(int)*(nDivisions+2));

    d = dDiv = (float)dataWidth/(float)(nDivisions+1);
    iDivs[0] = 0;
    for (i=1;i<=nDivisions;i++,d+=dDiv)
	iDivs[i] = (int)d;
    iDivs[i] = dataWidth-1;
    
    d = dDiv = (float)dataHeight/(float)(nDivisions+1);
    jDivs[0] = 0;
    for (i=1;i<=nDivisions;i++,d+=dDiv)
	jDivs[i] = (int)d;
    jDivs[i] = dataHeight-1;

    indx = 0;

    switch (indexedPrimType)
    {
    case RM_INDEXED_QUADS:
	{
	    indexCount = (nDivisions+1)*(nDivisions+1)*4;
	    for (j=0;j<nDivisions+1;j++)
	    {
		for (i=0;i<nDivisions+1;i++)
		{
		    ii[indx++] = makeIndex(iDivs[i], jDivs[j], dataWidth);
		    ii[indx++] = makeIndex(iDivs[i+1], jDivs[j], dataWidth);
		    ii[indx++] = makeIndex(iDivs[i+1], jDivs[j+1], dataWidth);
		    ii[indx++] = makeIndex(iDivs[i], jDivs[j+1], dataWidth);
		}
	    }
	}
	break;

    case RM_INDEXED_TRIANGLES:
	{
	    indexCount = (nDivisions+1)*(nDivisions+1)*6;
	    for (j=0;j<nDivisions+1;j++)
	    {
		for (i=0;i<nDivisions+1;i++)
		{
		    ii[indx++] = makeIndex(iDivs[i], jDivs[j], dataWidth);
		    ii[indx++] = makeIndex(iDivs[i+1], jDivs[j], dataWidth);
		    ii[indx++] = makeIndex(iDivs[i], jDivs[j+1], dataWidth);
	    
		    ii[indx++] = makeIndex(iDivs[i], jDivs[j+1], dataWidth);
		    ii[indx++] = makeIndex(iDivs[i+1], jDivs[j], dataWidth);
		    ii[indx++] = makeIndex(iDivs[i+1], jDivs[j+1], dataWidth);
		}
	    }
	}
	break;

    case RM_INDEXED_TRIANGLE_STRIP:
	{
	    indexCount =  ((nDivisions+1)*2+4) * (nDivisions+1);
	    for (j=0;j<nDivisions+1;j++)
	    {
		for (i=0;i<nDivisions+1;i++)
		{
		    ii[indx++] = makeIndex(iDivs[i], jDivs[j], dataWidth);
		    ii[indx++] = makeIndex(iDivs[i], jDivs[j+1], dataWidth);
		}
		ii[indx++] = makeIndex(iDivs[i], jDivs[j], dataWidth);
		ii[indx++] = makeIndex(iDivs[i], jDivs[j+1], dataWidth);
		
		ii[indx++] = makeIndex(iDivs[i], jDivs[j+1], dataWidth);
		ii[indx++] = makeIndex(iDivs[0], jDivs[j+1], dataWidth);
	    }
	}
	break;

    case RM_INDEXED_QUAD_STRIP:
	{
	    indexCount =  ((nDivisions+1)*2+4) * (nDivisions+1); 
	    for (j=0;j<nDivisions+1;j++) 
	    {
		for (i=0;i<nDivisions+1;i++)
		{
		    ii[indx++] = makeIndex(iDivs[i], jDivs[j], dataWidth); 
		    ii[indx++] = makeIndex(iDivs[i], jDivs[j+1], dataWidth);
		}
		ii[indx++] = makeIndex(iDivs[i], jDivs[j], dataWidth);
		ii[indx++] = makeIndex(iDivs[i], jDivs[j+1], dataWidth);
		
		ii[indx++] = makeIndex(iDivs[i], jDivs[j+1], dataWidth);
		ii[indx++] = makeIndex(iDivs[0], jDivs[j+1], dataWidth);
	    }
	}
	break;

    default:
	rmWarning(" generateIndices() error - invalid primitive type. Exiting now. \n");
	exit(-1);
	break;
    }

    free((void *)iDivs);
    free((void *)jDivs);

    rmPrimitiveSetIndices(updatePrimitive, indexCount, indices, RM_DONT_COPY_DATA, myFreeFunc);
}

int
myRenderFunc(RMpipe *myPipe,
	     RMnode *subTree,
	     void *usrData)
{
    /* insert code to call frame-based renderer here */
    int frameNumber;
    rmFrame(myPipe, subTree);

    frameNumber = rmPipeGetFrameNumber(myPipe);
    fprintf(stderr," render frame number = %d \n", frameNumber);
    return 1;
}

int
myIdleFunc(RMpipe *p,
	   RMnode *n,
	   void *usrData,
	   int px,
	   int py)
{
    int frameNumber = rmPipeGetFrameNumber(p);
    fprintf(stderr," idle frame number = %d \n", frameNumber);

    generateIndices(frameNumber);
    
    rmFrame(p, rmRootNode());

    /* foil compiler warnings */
    px = py = 0;
    usrData = NULL;
    n = NULL;
    
    return 1;
}

void
usage(char *s)
{
    fprintf(stderr,"usage: %s [-w imgWidthPixels ] [-h imgHeightPixels ] [ -prim ( iquads | itstrips | itris | iqstrips (default is iquads)) ] [-draw ( fill | line | point (default is fill)) ] \n",s);
}

void
parseArgs(int ac,
	  char *av[],
	  int *imgWidth,
	  int *imgHeight)
{
    int i;

    i = 1;
    while (i < ac)
    {
	if (strcmp(av[i],"-w") == 0)
	{
	    i++;
	    sscanf(av[i],"%d", imgWidth);
	}
	else if (strcmp(av[i],"-h") == 0)
	{
	    i++;
	    sscanf(av[i],"%d", imgHeight);
	}
	else if (strcmp(av[i],"-prim") == 0)
	{
	    i++;
	    if (strcmp(av[i],"iquads") == 0)
		indexedPrimType = RM_INDEXED_QUADS;
	    else if (strcmp(av[i],"itstrips") == 0)
		indexedPrimType = RM_INDEXED_TRIANGLE_STRIP;
	    else if (strcmp(av[i],"itris") == 0)
		indexedPrimType = RM_INDEXED_TRIANGLES;
	    else if (strcmp(av[i],"iqstrips") == 0)
		indexedPrimType = RM_INDEXED_QUAD_STRIP;
	    else
	    {
		fprintf(stderr," Bogus primitive type. \n");
		usage(av[0]);
		exit(-1);
	    }
	}
	else if (strcmp(av[i],"-draw") == 0)
	{
	    i++;
	    if (strcmp(av[i],"fill") == 0)
		drawMode = RM_FILL;
	    else if (strcmp(av[i],"line") == 0)
		drawMode = RM_LINE;
	    else if (strcmp(av[i],"point") == 0)
		drawMode = RM_POINT;
	    else
	    {
		fprintf(stderr," Bogus draw type. \n");
		usage(av[0]);
		exit(-1);
	    }
	}
	else
	{
	    usage(av[0]);
	    exit(-1);
	}
	i++;
    }
}

#ifdef RM_WIN
int WINAPI WinMain (HINSTANCE hInstance,
		    HINSTANCE hPrevInstance,
                    LPSTR lpszCmdLine, int nCmdShow)
{
    MSG      msg; 
    HWND     hWnd; 
    void *fptr;
    
    int imgWidth, imgHeight;
    RMenum processingMode = DEFAULT_PROCESSING_MODE; /* in procmode.h */
    RMenum targetPlatform = RM_PIPE_WGL;

    imgWidth = DEFAULT_IMAGE_WIDTH;
    imgHeight = DEFAULT_IMAGE_HEIGHT;
    
    parseArgs(__argc, __argv, &imgWidth, &imgHeight);
    
#else  /* assume RM_X */
int
main(int argc,
     char *argv[])
{
    void *msg;			/* needed for rmauxEventLoop
				 win32/unix API consistency */
    int imgWidth, imgHeight;
    RMenum processingMode = DEFAULT_PROCESSING_MODE; /* in procmode.h */
    RMenum targetPlatform = RM_PIPE_GLX;

    imgWidth = DEFAULT_IMAGE_WIDTH;
    imgHeight = DEFAULT_IMAGE_HEIGHT;
    
    parseArgs(argc, argv, &imgWidth, &imgHeight);
#endif

    /* 
     * first stage of RM initialization.
     */
    rmInit();

    /* 
     * create the rendering pipe. this step is required in both
     * Win32 and X.
     */
    myPipe = rmPipeNew(targetPlatform);
    
    processingMode = RM_PIPE_MULTISTAGE;
    rmPipeSetProcessingMode(myPipe, processingMode);
    rmPipeSetDisplayListEnable(myPipe, RM_FALSE);
    rmPipeSetFrameRate(myPipe, 20); 

#ifdef RM_WIN
    {
        /*
	 * Win32: when a window is created, we have to tell windows the
	 * name of the "WndProc," the procedure that gets called by
	 * windows with events (the event loop) (contrast to the X model 
	 * where the name of the event loop is not part of the window). 
	 * Since we're using RMaux, we know about the event handling 
	 * procedure named "rmauxWndProc" and we provide that here. 
	 */

        fptr = (void *)(rmauxWndProc);
	hWnd = rmauxCreateW32Window(myPipe,
			       NULL, /* no parent window */
			       20,20,imgWidth,imgHeight,"RM for Windows",
			       hInstance,fptr);
	if (hWnd == 0)
	  exit(-1);

	/* 
	 * assign the new window handle to the rendering pipe.
	 */
	rmPipeSetWindow(myPipe,hWnd, imgWidth, imgHeight);
    }
#endif
#ifdef RM_X
    {
	Window w;

	w = rmauxCreateXWindow(myPipe,
			       (Window)NULL, /* parent window */
			       0,0,imgWidth,imgHeight,
			       "RM for X-Windows","RM for X-Windows",RM_TRUE);

	/* 
	 * assign the window to the rendering pipe.
	 */
	rmPipeSetWindow(myPipe,w,imgWidth,imgHeight);
    }
#endif

    /* 
     * X-ism: once the window is created and assigned to the 
     * rendering pipe, rmPipeMakeCurrent makes the OpenGL rendering context
     * current for the pipe+window combination. 
     *
     * this step is required for X. in these demo programs, it is not 
     * strictly required by Win32, as the newly created context is made
     * current as part of the OpenGL initialization sequence.
     */
    rmPipeMakeCurrent(myPipe);	

    rmauxSetInitCallbackFunc(myInitFunc, NULL);
    rmauxSetRenderCallbackFunc(myRenderFunc, NULL);

    rmauxSetIdleCallbackFunc(myIdleFunc, NULL);
    
    /*
     * set key handler function so this prog will exit on "q" key.
     */
    rmauxSetKeypressCallbackFunc(rmauxDefaultKeyFunc, NULL);
    
    rmauxEventLoop(myPipe, rmRootNode(), &msg);

    rmPipeDelete(myPipe);
    rmFinish();

    return(1);
}
   
/* EOF */ 
