/*
 * 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: elevImage.c,v 1.8 2005/06/08 18:09:39 wes Exp $
 * $Revision: 1.8 $
 * $Name: OpenRM-1-6-0-2-c $
 * $Log: elevImage.c,v $
 * Revision 1.8  2005/06/08 18:09:39  wes
 * Code cleanup to eliminate compiler warnings.
 *
 * Revision 1.7  2003/07/20 14:56:32  wes
 * Minor tweaks to fix compile warnings on Solaris.
 *
 * Revision 1.6  2003/04/13 18:13:23  wes
 * Updated copyright dates.
 *
 * Revision 1.5  2003/01/27 05:07:07  wes
 * Changes to RMpipe initialization sequence APIs. Tested for GLX, but not WGL.
 *
 * Revision 1.4  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.3  2002/06/20 13:11:38  wes
 * Fixed bug where crash occured on some platforms while reading
 * short data.
 *
 * Revision 1.2  2002/06/17 00:36:40  wes
 * Added code to do scanline padding for RM_SHORT pixels when needed.
 * This fixes a bug that showed up on Win32 where pixel data is
 * aligned to 4-byte boundaries per scanline.
 *
 * Revision 1.1  2001/11/03 16:11:53  wes
 * Initial entry.
 *
 */

/*
 * usage: elevImage [-w imgWidthPixels -h imgHeightPixels] [-f | -s (select either RM_FLOAT or RM_SHORT as the pixel type, default is RM_FLOAT) ]
 *
 * This code is a skeleton application template. Use it as the basis
 * for your own OpenRM applications. 
 */

#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;

static RMenum pixelType = RM_FLOAT;

/*#define DEFAULT_IMAGE_WIDTH 1048 */
#define DEFAULT_IMAGE_WIDTH 980
#define DEFAULT_IMAGE_HEIGHT 525

static char dataFilename[]={"data/elevData.txt"};
static int dataWidth, dataHeight;
static float dataMin, dataMax;

static float *floatDataValues=NULL;
static short *shortDataValues = NULL;

int
readData(char *fn,
	 RMenum pixelType)
{
    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;

    if (pixelType == RM_FLOAT)
    {
	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];
	}
    }
    else /* assume RM_SHORT */
    {
	int t;
	shortDataValues = (short *)malloc(sizeof(unsigned short)*n);
	for (i=0;i<n;i++)
	{
	    fscanf(f,"%d",&t);
	    shortDataValues[i] = t;
	    
	    if (shortDataValues[i] < dataMin)
		dataMin = shortDataValues[i];
	    if (shortDataValues[i] > dataMax)
		dataMax = shortDataValues[i];
	}
    }

    printf(" done. min/max are %f, %f\n", dataMin, dataMax);

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

void
make2DCamera(RMnode *n)
{
    RMcamera2D *c = rmCamera2DNew();

    rmCamera2DSetExtents(c, -1.01F, -1.01F, 1.01F, 1.01F);
    rmCamera2DSetAspectRatio(c, 1.0F);

    rmNodeSetSceneCamera2D(n, c);
    rmCamera2DDelete(c);
}

void
makeFBClear(RMnode *n)
{
    RMcolor4D bgColor={.3F, .3F, .3F, 1.0F};
    rmNodeSetSceneBackgroundColor(n, &bgColor);
}

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
makeImages(RMnode *n,
	   RMpipe *pipe,
	   RMenum pixelType)
{
    RMprimitive *p;
    RMvertex2D v00={-1.0F, 0.0F}, v01={-1.0F, -1.0F};
    RMvertex2D v10={0.0F, 0.0F}, v11={0.0F, -1.0F};

    RMimage *baseImage=NULL, *resizedImage=NULL;
    RMimage *scaleBiasImage=NULL;
    RMimage *colorizedImage=NULL;
    RMimage *colorizedScaleBiasImage=NULL;
    float scaleValue, biasValue;
    RMvisMap *m;

    baseImage = rmImageNew(2,dataWidth, dataHeight,1,
			   RM_IMAGE_LUMINANCE, pixelType,
			   RM_COPY_DATA);
    resizedImage = rmImageNew(2, 512, 256, 1, RM_IMAGE_LUMINANCE,
			      pixelType, RM_COPY_DATA);

    if (pixelType == RM_FLOAT)
	rmImageSetPixelData(baseImage, (void *)floatDataValues, RM_COPY_DATA,NULL);
    else
    {
	/* might have to tweak the raw RM_SHORT pixel data to honor scanline
	 padding policy on the local OpenGL platform. */
	short *newShortDataValues=NULL;
	int bps;
	bps = rmImageGetBytesPerScanline(baseImage);

	if (bps != (int)(sizeof(short)*dataWidth))
        {
	    int i;
	    newShortDataValues = (short *)malloc(sizeof(short)*bps * dataHeight);
	    for (i=0;i<dataHeight;i++)
		memcpy((void *)(newShortDataValues + i*(bps/sizeof(short))),
		       (void *)(shortDataValues + i*dataWidth),
		       bps);
	}
	else
	    newShortDataValues = shortDataValues;
	rmImageSetPixelData(baseImage, (void *)newShortDataValues, RM_COPY_DATA,NULL);
	if (bps != (int)(sizeof(short)*dataWidth))
	    free((void *)newShortDataValues);
    }

    rmImageMirror(baseImage, RM_IMAGE_MIRROR_HEIGHT); 
    rmImageResize(baseImage, resizedImage, RM_SOFTWARE, pipe);  
/*    rmImageResize(baseImage, resizedImage, RM_HARDWARE, pipe);   */

    p = rmPrimitiveNew(RM_SPRITE);
    rmPrimitiveSetVertex2D(p, 1, &v00, RM_COPY_DATA, NULL);
    rmPrimitiveSetSprites(p, 1, &resizedImage);
    rmNodeAddPrimitive(n, p);

    scaleBiasImage = rmImageDup(resizedImage);

    if (pixelType == RM_FLOAT)
    {
	/* scale image from dataMin..dataMax to 0..1 */
	scaleValue = 1.0F/(dataMax - dataMin);
	
	/* bias should be in range 0..1. what we want is for data values
	   equal to dataMin to map to 0.0 in pixel values */
	biasValue = -dataMin * scaleValue;
    }
    else /* assume RM_SHORT */
    {
	/* scale pixels into range 0..32768 */
	scaleValue = 32768.0F/(dataMax-dataMin);
	
	biasValue = -dataMin * scaleValue/32768.0F;
    }

    rmImageSetScale(scaleBiasImage, scaleValue);
    rmImageSetBias(scaleBiasImage, biasValue); 

    p = rmPrimitiveNew(RM_SPRITE);
    rmPrimitiveSetVertex2D(p, 1, &v01, RM_COPY_DATA, NULL);
    rmPrimitiveSetSprites(p, 1, &scaleBiasImage);
    rmNodeAddPrimitive(n, p);


    colorizedImage = rmImageDup(resizedImage);
    m = rmDefaultVismap();
    makeGeoVismap(m, dataMin, dataMax);
    
    rmImageSetVismap(colorizedImage,m);
    
    p = rmPrimitiveNew(RM_SPRITE);
    rmPrimitiveSetVertex2D(p, 1, &v10, RM_COPY_DATA, NULL);
    rmPrimitiveSetSprites(p, 1, &colorizedImage);
    rmNodeAddPrimitive(n, p);
    
    colorizedScaleBiasImage = rmImageDup(scaleBiasImage);
    rmImageSetVismap(colorizedScaleBiasImage,m);
    
    p = rmPrimitiveNew(RM_SPRITE);
    rmPrimitiveSetVertex2D(p, 1, &v11, RM_COPY_DATA, NULL);
    rmPrimitiveSetSprites(p, 1, &colorizedScaleBiasImage);
    rmNodeAddPrimitive(n, p);
}

void
myInitFunc(RMpipe *p,
	   RMnode *n)
{
    RMnode *localRoot = rmNodeNew("localRoot", RM_RENDERPASS_2D, RM_RENDERPASS_OPAQUE);
    int w,h;
    
    if (readData(dataFilename, pixelType) == 0)
    {
	rmError(" myInitFunc() error: unable to open source data. Bye!");
	exit(-1);
    }

    makeImages(localRoot,p, pixelType);

    rmPipeGetWindowSize(p, &w, &h);
    make2DCamera(localRoot);
    makeFBClear(localRoot);

    rmNodeAddChild(n, localRoot);

    rmFrame(p, n);
}

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

void
usage(char *s)
{
    printf("usage: %s [-w imgWidthPixels ] [-h imgHeightPixels] [-f | -s (selects either RM_FLOAT or RM_SHORT as the pixel type, default is RM_FLOAT) ]\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++;
	    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],"-f") == 0)
	    pixelType = RM_FLOAT;
	else if (strcmp(av[i],"-s") == 0)
	    pixelType = RM_SHORT;
	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);

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

    rmauxSetInitFunc(myInitFunc);
    rmauxSetRenderFunc(myRenderFunc);
    
    /*
     * set key handler function so this prog will exit on "q" key.
     */
    rmauxSetKeyFunc(myPipe, rmauxDefaultKeyFunc);
    
    rmauxEventLoop(myPipe,rmRootNode(), &msg);

    rmFinish();

    return(1);
}
   
