/*
 * Copyright (C) 1997-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: elev.c,v 1.16 2005/06/08 18:17:05 wes Exp $
 * $Revision: 1.16 $
 * $Name: OpenRM-1-6-0-2-c $
 * $Log: elev.c,v $
 * Revision 1.16  2005/06/08 18:17:05  wes
 * Code cleanup to eliminate compiler warnings.
 *
 * Revision 1.15  2004/01/19 17:08:42  wes
 * Fixed minor command line parsing buglet in parseArgs.
 *
 * Revision 1.14  2004/01/17 03:43:28  wes
 * Added command line arguments [-fr NN] and [-spin] to enable constant
 * rate rendering and auto-spin mode.
 *
 * Revision 1.13  2003/11/05 15:53:18  wes
 * Added parameters to modified 1.5.1 rmv routines that specify whether
 * or not to flip surface normals.
 *
 * Revision 1.12  2003/10/15 05:48:30  wes
 * Added rmauxSetSpinEnable(RM_TRUE) to enable spinning.
 *
 * Revision 1.11  2003/04/13 18:13:23  wes
 * Updated copyright dates.
 *
 * Revision 1.10  2003/01/27 05:07:07  wes
 * Changes to RMpipe initialization sequence APIs. Tested for GLX, but not WGL.
 *
 * Revision 1.9  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.8  2002/06/17 00:35:31  wes
 * replaced rmSubtreeFrame with rmFrame
 *
 * Revision 1.7  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.6  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.5  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.4  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.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/16 14:40:30  wes
 * Minor cleanups.
 *
 * Revision 1.1.1.1  2000/02/28 21:55:30  wes
 * OpenRM 1.2 Release
 *
 * Revision 1.10  2000/02/28 17:21:55  wes
 * RM 1.2, pre-OpenRM
 *
 */

#include <rm/rm.h>
#include <rm/rmaux.h>
#include <rm/rmv.h>
#include <rm/rmi.h>
#include "libdio.h"
#include "procmode.h"

static RMpipe *lone_pipe=NULL;
static char MyRootName[]={"MyRoot"};
static RMnode *MyRoot;
int img_width=400,img_height=300;
char datafilename[256]={"data/elev.dio"};
RMenum channelFormat;

dioDataObject *mydataobj=NULL;
dioDataObject *newdataobj=NULL;

int do_color=0;
int my_linewidth = RM_LINEWIDTH_MEDIUM;
int my_linestyle = RM_LINES_SOLID;

static RMenum autoSpinMode = RM_FALSE;
static int    frameRate = -1;

void
usage(char *av[])
{
    char buf[256];
    sprintf(buf," usage: %s [-i datafilename (defaults to data/elev.dio) [-w img_width] [-h img_height] [-c (use default vis colormap to 'colorize' the plot')] [-spin (enable auto-spin mode, default is no auto-spin)] [-fr NN (set contsant rate rendering frame rate to NN frames per second, default is free-running)] \n",av[0]);
    rmError(buf);
}

void
parse_args(int ac,
	   char *av[])
{
    int i;

    i = 1;
    while (i < ac)
    {
	if (strcmp(av[i],"-w") == 0)
	{
	    i++;
	    sscanf(av[i],"%d",&img_width);
	}
	else if (strcmp(av[i],"-h") == 0)
        {
	    i++;
	    sscanf(av[i],"%d",&img_height);
	}
	else if (strcmp(av[i],"-i") == 0)
	{
	    i++;
	    strcpy(datafilename,av[i]);
	}
	else if (strcmp(av[i],"-c") == 0)
	{
	    do_color=1;
	}
	else if (strcmp(av[i],"-spin") == 0)
	{
	    autoSpinMode = RM_TRUE;
	}
	else if (strcmp(av[i],"-fr") == 0)
	{
	    int j;
	    
	    i++;
	    sscanf(av[i],"%d", &j);
	    if (j > 0)
		frameRate = j;
	}
	else
	{
	    usage(av);
	    exit(-1);
	}
	i++;
    }
}


void
my_read_data(char *datafilename)
{
    float scalef=4.0;
    
    mydataobj = dioReadDataObject(datafilename);
    if (mydataobj == NULL)
    {
	fprintf(stderr," error reading input data file. exiting. \n");
	exit(-1);
    }
    dioObjectScaleData(mydataobj, scalef);
    dioObjectConditioner(mydataobj);
}

void
my_set_scene(RMnode *camNode,
	     int stereo_format)
{
    /*
     * here, we compute the camera parameters such that all of
     * the geometry is visible.
     */
      
    RMcamera3D *c=rmCamera3DNew();
    
    rmDefaultCamera3D(c);		/* assign it some default values. */
    
    rmCamera3DComputeViewFromGeometry(c,MyRoot, img_width, img_height);

    if (stereo_format != RM_MONO_CHANNEL)
    {
	rmCamera3DSetStereo(c,RM_TRUE);
	rmCamera3DSetEyeSeparation(c,2.5F);
	rmCamera3DSetFocalDistance (c,0.707F);
	
    }

    rmNodeSetSceneCamera3D(camNode,c); 
    
    rmCamera3DDelete(c);
    
    /* use RM's default lighting model */
    rmDefaultLighting(camNode);
}

RMvertex3D
mygridfunc_uv(int i,
	      int j)
{
    /*
     * tell RMV what this grid (x,y,z) point is at location (i,j).
     * we assume the data model is sufficiently intelligent to know
     * it's own dimensions, and is capable of dealing with a two-dimensional
     * indexing system.
     *
     * we assume that the "i" index maps to width, and that "j"
     * maps to height in the local data model.
     */
    RMvertex3D temp3d;
    int indx;

    /*    indx = mydataobj->width * j + i; */
    indx = newdataobj->dims[0] * j + i;
    
    temp3d.x = newdataobj->xcoords[indx];
    temp3d.y = newdataobj->ycoords[indx];
    temp3d.z = newdataobj->zcoords[indx];
    
    return(temp3d);
}

RMvertex3D
mypoints_gridfunc(int i,
		  int j)
{
    /*
     * this routine shows how to "trick" rmvJ3ScatterPoints - it steps
     * through 2 logical grid dimensions, but all we really have is just
     * one. this routine is the interface to the local data model..which
     * fakes a 2d grid with 1d unstructured grid.
     */
    RMvertex3D temp3d;
    
    temp3d.x = mydataobj->xcoords[i];
    temp3d.y = mydataobj->ycoords[i];
    temp3d.z = mydataobj->rawdata[i]; /* heh heh */

    /* foil compiler warning */
    j = 0;
    
    return(temp3d);
}

float
mypoints_datafunc(int i,
		  int j)
{
    /*
     * this routine shows how to "trick" rmvJ3ScatterPoints - it steps
     * through 2 logical grid dimensions, but all we really have is just
     * one. this routine is the interface to the local data model..which
     * fakes a 2d grid with 1d unstructured grid.
     */

    /* foil compiler warning */
    j = 0;
    
    return(mydataobj->rawdata[i]);
}

float
mydatafunc_uv(int i,
	      int j)
{
    /*
     * tell RMV what the data value is at grid location (i,j). we
     * assume a one-d grid.
     */
    int indx;

    indx = newdataobj->dims[0] * j + i;
    return(newdataobj->rawdata[indx]);
}

void
my_build_objs(void)
{
    MyRoot = rmNodeNew(MyRootName,RM_RENDERPASS_3D, RM_RENDERPASS_OPAQUE);
    rmNodeAddChild(rmRootNode(),MyRoot);
    
    /* do the visualization.. */
    {
	RMnode *visnode;
	float *xcoords,*ycoords,*zcoords, *data,*data2;
	RMvisMap *vmap=NULL;
	
	float *newdata;
	int new_usize=30, new_vsize=30;

	newdata = (float *)malloc(sizeof(float)*new_usize*new_vsize);
	
	/*
	 * note that we're making certain assumptions about the
	 * input data file:
	 * 1. it has xcoords & ycoords
	 * 2. we treat the "raw data" as z coords,
	 * 3. effectively ignoring any z coords that may be present in the file
	 */
	
	xcoords = mydataobj->xcoords;
	ycoords = mydataobj->ycoords;
	zcoords = mydataobj->rawdata;
	data = mydataobj->rawdata;

	if (do_color)
	{
	    vmap = rmDefaultVismap();

	    rmVismapSetTfMin(vmap,mydataobj->datamin);
	    rmVismapSetTfMax(vmap,mydataobj->datamax);
	    data2 = mydataobj->rawdata;
	}
	else
	{
	    data2 = NULL;
	    vmap = NULL;
	}

	visnode = rmNodeNew("vis",RM_RENDERPASS_3D, RM_RENDERPASS_OPAQUE);
	/* show the raw data as point primitives */
	{
	    rmvJ3ScatterPoint(mypoints_gridfunc,
			      mypoints_datafunc,
			      (vmap == NULL) ? NULL : mypoints_datafunc,
			      vmap, RMV_NOAXIS_OFFSET,
			      mydataobj->width,1,
			      RM_TRUE, RM_FALSE, 
			      visnode);
	    rmNodeSetPointSize(visnode, 2.0F); /* make them fat points */
	}

	/* do the surface fitting */
	{
	    RMvertex2D omin, omax;
	    int method;
	    float float_parm;

	    /*
	     * the surface fit routine will compute a surface over a grid
	     * of resolution (new_usize, new_vsize), the corners of which
	     * are located at omin,omax (those are x,y points). we're asking
	     * the surface fitter to compute a surface over the entire
	     * spatial range of data.
	     */
	    omin.x = mydataobj->corners[0].x;
	    omax.x = mydataobj->corners[1].x;
	    omin.y = mydataobj->corners[0].y;
	    omax.y = mydataobj->corners[1].y;

	    /* 
	     * uncomment this next section to try out RMV_GRID2D_IDSFFT
	     * surface fitting. if you do, be sure to comment the other
	     * section of code that does RMV_GRID2D_KRIGE fitting.
	     */
#if 0
	    method = RMV_GRID2D_IDSFFT;

	    /*  floatparm = number of additional data points used for esti- */
	    /*           mating partial derivatives at each data point */
	    /*           (must be 2 or greater, but smaller than the number
			  of total data points (internal restrictions limit
			  the max for this parameter to 25), */
	    /*  recommended values for this parm are between 3-5 */

	    float_parm = 3;
#endif

	    method = RMV_GRID2D_KRIGE;
	    float_parm = 2.0;

	    rmv2DSurfaceFit(xcoords, ycoords, zcoords, mydataobj->width,
			    &omin, &omax, new_usize, new_vsize, method,
			    float_parm, newdata);
	    
			    
	}

	/*
	 * now, configure the new data object for visualization using the
	 * results of the surface fitting operation.
	 */
	newdataobj = dioDataObjectNew();
	newdataobj->width = new_usize;
	newdataobj->height = new_vsize;
	newdataobj->depth = 1;
	newdataobj->corners = rmVertex3DNew(2);
	newdataobj->corners[0] = mydataobj->corners[0];
	newdataobj->corners[1] = mydataobj->corners[1];
	newdataobj->corners[0].z = 0.0;
	newdataobj->corners[1].z = 0.0;
	newdataobj->rawdata = newdata;

	dioObjectConditioner(newdataobj);

	rmvJ3MeshSurface(mygridfunc_uv,
			 mydatafunc_uv,
			 (vmap == NULL) ? NULL : mydatafunc_uv,
			 vmap,
			 RMV_ZAXIS_OFFSET,
			 new_usize,
			 new_vsize,
			 RM_FALSE,
			 visnode);
	

	rmNodeAddChild(MyRoot,visnode);
	rmNodeComputeBoundingBox(visnode);
	
    }
    
    rmNodeUnionAllBoxes(MyRoot);
    
    {
	RMvertex3D bmin,bmax,center;
	rmNodeGetBoundingBox(MyRoot,&bmin,&bmax);
	center.x = bmin.x + 0.5 * (bmax.x - bmin.x);
	center.y = bmin.y + 0.5 * (bmax.y - bmin.y);
	center.z = bmin.z + 0.5 * (bmax.z - bmin.z);
	rmNodeSetCenter(MyRoot,&center);
    }
    {
	RMcolor4D bgcolor={0.0,0.0,0.0,0.0};
	/*
	 * assign a background color to take effect at "MyRoot"
	 */
	rmNodeSetSceneBackgroundColor(MyRoot,&bgcolor);
    }
}

void
my_idle_func(RMpipe *p,
	     int ix,
	     int iy)
{
    RMmatrix m,old;
    double d,c,s;
    rmMatrixIdentity(&m);
    d = RM_DEGREES_TO_RADIANS(1.0);
    c = cos(d);
    s = sin(d);
    m.m[0][0] = m.m[2][2] = c;
    m.m[0][2] = -s;
    m.m[2][0] = s;
    
    if (rmNodeGetRotateMatrix(MyRoot,&old) == RM_WHACKED)
	rmMatrixIdentity(&old);
    rmMatrixMultiply(&old,&m,&old);
    rmNodeSetRotateMatrix(MyRoot,&old);

    rmFrame(p, rmRootNode());

    /* foil compiler warning */
    ix = iy = 0;
}

void
dumpimagefunc(const RMimage *img,
	      RMenum whichbuffer_enum)
{
    char fname[] = {"elev.jpg"};
    char buf[256];
    rmiWriteJPEG(fname, 99, img);

    sprintf(buf, " Wrote image file named %s", fname);
    rmNotice(buf);
    
    /* foil compiler warning */
    whichbuffer_enum = RM_TRUE;
}

int
my_dump_image_func(RMpipe *p,
		   int xbutton,
		   int ybutton)
{
    /*
     * the goal is to write an image file that contains the
     * contents of the framebuffer.
     */

    /*
     * assign a "post render" function - it will be invoked after
     * the scene has been rendered, and will write the framebuffer
     * contents to a file.
     */
    rmPipeSetPostRenderFunc(lone_pipe,dumpimagefunc);

    /*
     * render & write the image.
     */
    rmFrame(p, rmRootNode());

    /*
     * now, remove the "write image" function from the pipe. if we
     * didn't remove this callback, we'd write a file ever time the
     * frame is rendered - which is useful, but not what we want
     * in this context.
     */
    rmPipeSetPostRenderFunc(lone_pipe,NULL);

    /* foil compiler warning */
    xbutton = ybutton = 0;
    
    return(1);
}

void
myinitfunc(RMpipe *p,
	   RMnode *n)
{
    my_read_data(datafilename);
    my_build_objs();
    my_set_scene(n, channelFormat);

    /*
     * set up the event handler to apply geometric transformations at
     * MyRoot. note that the lights and cameras are placed at rmRootNode().
     * therefore, the rotations & scaling applied at MyRoot do not affect
     * the cameras & lights since they are at a higher level in the
     * scene graph than MyRoot.
     */
    rmauxSetGeomTransform(MyRoot, p);
    rmauxSetSpinEnable(autoSpinMode);
    rmauxSetCamera3DTransform(n, p);

    /* call "my_dump_image_func()" when button1 is pressed */
    rmauxSetButtonDownFunc(RM_BUTTON1,RM_CONTROL_MODMASK,my_dump_image_func);

    /*
     * set handler to reset aspect ratio when the window is resized.
     */
    rmauxSetResizeFunc(p, n, rmauxDefaultResizeFunc);
    
    if (rmPipeProcessingModeIsMultithreaded(p) == RM_TRUE)
	rmFrame(p, n);
    
    rmFrame(p, n);
}

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

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

    parse_args(__argc,__argv);
#else
int
main(int ac,
     char *av[])
{
    RMenum processingMode = DEFAULT_PROCESSING_MODE; /* in procmode.h */
    RMenum targetPlatform = RM_PIPE_GLX;
    void *msg;			/* needed for rmauxEventLoop
				 win32/unix API consistency */
    parse_args(ac,av);
#endif

    /* 
     * pick a stereo format:
     * RM_MONO_CHANNEL - plain old single-view
     * RM_REDBLUE_STEREO_CHANNEL - left channel in red, right channel in cyan
     * RM_BLUERED_STEREO_CHANNEL - left in cyan, right in red
     * RM_MBUF_STEREO_CHANNEL - multibuffered stereo, requires special
     *    hardware. 
     */
    channelFormat = RM_MONO_CHANNEL;

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

    /* 
     * create the rendering pipe. this step is required in both
     * Win32 and X.
     */
    lone_pipe = rmPipeNew(targetPlatform);

    rmPipeSetChannelFormat(lone_pipe, channelFormat);

    rmPipeSetProcessingMode(lone_pipe, 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(lone_pipe,
			       NULL, /* no parent window */
			       20,20,img_width,img_height,"RM for Windows",
			       hInstance,fptr);
	if (hWnd == 0)
	  exit(-1);

	/* 
	 * assign the new window handle to the rendering pipe.
	 */
	rmPipeSetWindow(lone_pipe,hWnd, img_width, img_height);

       /* uncomment this next line if you want the object to rotate
	  while the user is idle. */
	/* rmauxSetIdleFunc(my_idle_func, hWnd); */
    }
#endif
#ifdef RM_X
    {
	Window w;
	w = rmauxCreateXWindow(lone_pipe,
			       (Window)NULL, /* parent window */
			       0,0,img_width,img_height,
			       "RM for X-Windows","icon-title", RM_TRUE);
	/* 
	 * assign the window to the rendering pipe.
	 */
	rmPipeSetWindow(lone_pipe,w,img_width,img_height);
    }
#endif

    /* 
     * specify the name of the "init" function. the "init" function is
     * mandatory in the Win32 world, and optional in the X world. 
     *
     * in Win32, we don't want to call RM services until OpenGL is
     * ready. we can be assured of readiness by using an init function
     * with RMaux. 
     *
     * in X, at this point, the window is mapped and OpenGL is ready,
     * and we could call our init function directly.
     */

    rmauxSetInitFunc(myinitfunc); 

    /* 
     * 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(lone_pipe);
    rmPipeSetFrameRate(lone_pipe, frameRate);
    
    rmauxSetRenderFunc(myrenderfunc);

    /*
     * set key handler function so this prog will exit on "q" key
     */
    rmauxSetKeyFunc(lone_pipe, rmauxDefaultKeyFunc);
    
    rmauxEventLoop(lone_pipe,rmRootNode(), &msg);

    rmPipeDelete(lone_pipe);
    rmFinish();

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