/*
 * 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: vis2d.c,v 1.17 2008/11/09 14:37:04 wes Exp $
 * $Revision: 1.17 $
 * $Name: v180-alpha-01 $
 * $Log: vis2d.c,v $
 * Revision 1.17  2008/11/09 14:37:04  wes
 * Update dates to 2008
 *
 * Revision 1.16  2007/07/22 17:37:47  wes
 * Added usrData and usrCallback parameters to all rmv routines
 *
 * Revision 1.15  2006/08/08 03:49:21  wes
 * Updates to reflect ergonomic changes to rmaux
 *
 * Revision 1.14  2006/08/07 12:53:26  wes
 * Updates to reflect API changes in RMaux for button/key/idle callbacks.
 *
 * Revision 1.13  2005/06/08 18:34:16  wes
 * Code cleanup to eliminate compiler warnings.
 *
 * Revision 1.12  2003/04/13 18:13:23  wes
 * Updated copyright dates.
 *
 * Revision 1.11  2003/01/27 05:07:07  wes
 * Changes to RMpipe initialization sequence APIs. Tested for GLX, but not WGL.
 *
 * Revision 1.10  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.9  2002/06/17 00:46:18  wes
 * Replaced rmSubtreeFrame with rmFrame
 *
 * Revision 1.8  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.7  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.6  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.5  2000/12/03 23:31:17  wes
 * Changed default vis technique back to 0 (was 6).
 *
 * 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/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.10  2000/02/28 17:21:56  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"

/*
 * usage: vis2d -i datafilename [-w img_width] [-h img_height] \
 *     [-v vis_technique (0..8)] [-c (use default vis colormap)]
 */

static RMpipe *lone_pipe=NULL;
static char MyRootName[]={"MyRoot"};
static RMnode *MyRoot;
int img_width=400,img_height=300;
char datafilename[256]={"data/cos.dio"};
dioDataObject *mydataobj=NULL;
int do_color=0;
int vis_technique=0;
RMenum my_linewidth = RM_LINEWIDTH_MEDIUM;
RMenum my_linestyle = RM_LINES_SOLID;

void
usage(char *av[])
{
    char buf[256];

    sprintf(buf," usage: %s [-i datafilename (defaults to data/cos.dio) [-w img_width] [-h img_height] [-c (use default vis colormap to 'colorize' the plot') [-v n (where n=0..8, and indicates which visualization technique to use)]\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],"-v") == 0)
	{
	    i++;
	    sscanf(av[i],"%d",&vis_technique);
	    if (vis_technique < 0 || vis_technique > 8)
	    {
	        usage(av);
		exit(-1);
	    }
	}
	else
	{
	    usage(av);
	    exit(-1);
	}
	i++;
    }
}


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

void
my_set_scene(RMnode *camNode)
{
      
    RMcamera2D *c=rmCamera2DNew();
    float vp[4] = {0.05F,0.05F,0.95F,0.95F};
    
    /* create a camera */
    rmDefaultCamera2D(c);		/* assign it some default values. */

    /* adjust default view so all geom is visible */
    rmCamera2DComputeViewFromGeometry(c,MyRoot);

    {
	/*
	 * modify the 2d camera so that it's extents are 5% larger than
	 * the actual geometry so that stuff on the edges doesn't get
	 * clipped away.
	 */
	float xmin,ymin,xmax,ymax,dx,dy;
	
	rmCamera2DGetExtents(c,&xmin,&ymin,&xmax,&ymax);
	dx = xmax - xmin;
	dy = ymax - ymin;
	xmin -= 0.05*dx;
	xmax += 0.05*dx;
	ymin -= 0.05*dy;
	ymax += 0.05*dy;

	rmCamera2DSetExtents(c,xmin,ymin,xmax,ymax);
    }


    /* add the camera to "my root's" scene parms. */
    rmNodeSetSceneCamera2D(camNode,c);

    /* set the viewport to be a little smaller than the window so that
     the plot doesn't abut to the window edges.*/
    rmNodeSetSceneViewport(camNode,vp);

    rmCamera2DDelete(c);
}


/*
 * the following two routines are the interface between the RMV
 * vis tools and the local data model. RMV wants us to supply routines
 * which will tell the vis tool what the (x,y) point is at some grid
 * location, and what the data point is at some grid location.
 *
 * the local data model is very simple, so we can make simplifying
 * assumptions resulting in very terse routines.
 */
RMvertex2D
mygridfunc(int i,
	   void *usrData)
{
    /*
     * tell RMV what this grid (x,y) point is at location "i". we assume
     * a one-d grid of (x,y) points in the local data model.
     */
    RMvertex2D temp2d;
    
    temp2d.x = mydataobj->xcoords[i];
    temp2d.y = mydataobj->ycoords[i];
    return(temp2d);
}

float
mydatafunc(int i,
	   void *usrData)
{
    /*
     * tell RMV what the data value is at grid location "i". we
     * assume a one-d grid.
     */
    return(mydataobj->rawdata[i]);
}

void
my_build_objs(void)
{
    MyRoot = rmNodeNew(MyRootName,RM_RENDERPASS_2D, RM_RENDERPASS_ALL);
    rmNodeAddChild(rmRootNode(),MyRoot);
    
    /* do the visualization.. */
    {
	RMnode *visnode;
	int offset_flag;
	float *xcoords,*ycoords,*data;
	int npts;
	float zeroval=0.0;
	float shrink = 0.8;
	RMvisMap *vmap=NULL;

	if (mydataobj->width != 1)
	{
	    offset_flag = RMV_YAXIS_OFFSET;
	    npts = mydataobj->width; 
	}
	else
	{
	    offset_flag = RMV_XAXIS_OFFSET;
	    npts = mydataobj->height;
	}

	if (do_color)
	{
	    vmap = rmDefaultVismap();

	    rmVismapSetTfMin(vmap,mydataobj->datamin);
	    rmVismapSetTfMax(vmap,mydataobj->datamax);
	}

	visnode = rmNodeNew("vis",RM_RENDERPASS_2D, RM_RENDERPASS_ALL);

	xcoords = mydataobj->xcoords;
	ycoords = mydataobj->ycoords;
	data = mydataobj->rawdata;


	switch(vis_technique)
	{
	case 0:
	    shrink = 1.0F;
	    rmvI2BarFilled(mygridfunc, NULL,
			   mydatafunc, NULL,
			   (vmap == NULL) ? NULL : mydatafunc, NULL,
			   vmap,
			   offset_flag,
			   npts,
			   shrink,
			   RMV_SCALE_RELATIVE,
			   visnode);
	    break;

	case 1:
	    shrink = 0.5F;
	    my_linewidth = RM_LINEWIDTH_NARROW; 
	    rmvI2BarOutline(mygridfunc, NULL,
			    mydatafunc, NULL,
			    (vmap == NULL) ? NULL : mydatafunc, NULL,
			    vmap,
			    offset_flag,
			    npts,
			    shrink,
			    RMV_SCALE_RELATIVE,
			    my_linewidth,
			    my_linestyle,
			    visnode);
	    break;

	case 2:
	    my_linestyle = RM_LINES_DASHED;
	    my_linewidth = RM_LINEWIDTH_HEAVY;
	    rmvI2Polyline(mygridfunc, NULL,
			  mydatafunc, NULL,
			  (vmap == NULL) ? NULL : mydatafunc, NULL,
			  vmap,
			  offset_flag,
			  npts,
			  my_linewidth,my_linestyle,
			  visnode);
	    break;

	case 3:
	    rmvI2Impulse(mygridfunc, NULL,
			 mydatafunc, NULL,
			 (vmap == NULL) ? NULL : mydatafunc, NULL,
			 vmap,
			 offset_flag,
			 npts,
			 my_linewidth,my_linestyle,
			 visnode);
	    break;

	case 4:

	    zeroval = 0.5;
 
	    rmvI2AreaFill(mygridfunc, NULL,
			  mydatafunc, NULL, 
			  (vmap == NULL) ? NULL : mydatafunc, NULL,
			  vmap, 
			  offset_flag,
			  npts,
			  zeroval,
			  visnode);
	    break;

	case 5:
	    {
		int marker_enum = RMV_2DMARKER_NORTHTRIANGLE_FILLED;
/*		int scaling_flag = RMV_SCALE_ABSOLUTE; experiment with me! */
		int scaling_flag = RMV_SCALE_RELATIVE;

		shrink = 2.0;

		rmvI2ScatterGeom(mygridfunc, NULL,
				 mydatafunc, NULL,
				 (vmap == NULL) ? NULL : mydatafunc, NULL,
				 vmap,
				 offset_flag,
				 npts,
				 shrink,
				 scaling_flag,
				 marker_enum,
				 visnode);
	    }
	    break;

	case 6:
	    {
		/* experiment with us! */
		int marker_enum = RMV_ZAPFMARKER_STAR_FILLED;
		int size_enum = RM_FONT_S;
		
		rmvI2ScatterGlyph(mygridfunc, NULL,
				  mydatafunc, NULL, 
				  (vmap == NULL) ? NULL : mydatafunc, NULL,
				  vmap,
				  offset_flag,
				  npts,
				  size_enum,
				  marker_enum,
				  visnode);

	    }
	    break;

	case 7:
	{
	    rmvI2Impulse(mygridfunc, NULL,
			 mydatafunc, NULL,
			 (vmap == NULL) ? NULL : mydatafunc, NULL,
			 vmap,
			 offset_flag,
			 npts,
			 my_linewidth,my_linestyle,
			 visnode);
	    
	    rmvI2Step(mygridfunc, NULL,
		      mydatafunc, NULL, 
		      (vmap == NULL) ? NULL : mydatafunc, NULL,
		      vmap,
		      offset_flag,
		      npts,
		      zeroval,
		      my_linewidth,
		      my_linestyle,
		      visnode);
	}
	    break;

	case 8:
	{
	    rmvI2AreaOutline(mygridfunc, NULL,
			     mydatafunc, NULL,
			     (vmap == NULL) ? NULL : mydatafunc, NULL,
			     vmap,
			     offset_flag,
			     npts,
			     zeroval,
			     my_linewidth,
			     my_linestyle,
			     visnode);
	    break;
	}


	default:
	    break;
	}

	rmNodeAddChild(MyRoot,visnode);
	rmNodeComputeBoundingBox(visnode);
	
	rmNodeUnionAllBoxes(MyRoot);

	if (vmap != NULL)
	    rmVismapDelete(vmap);
    }

}


void
dumpimagefunc(const RMimage *img,
	      RMenum whichbufferEnum)
{
    char fname[] = "vis2d.jpg";
    char buf[64];
    
    sprintf(buf," writing an image file named <%s> ",fname);
    rmNotice(buf);

    rmiWriteJPEG(fname, 99, img);

    /* foil compiler warning */
    whichbufferEnum = RM_TRUE;
}

int
my_dump_image_func(RMpipe *p,
		   RMnode *n,
		   void *usrData,
		   int xbutton,
		   int ybutton)
{
    /*
     * the goal is to write an image file that contains the
     * contents of the framebuffer.
     */
    rmNotice(" in my_dump_image_func(). \n");

    /*
     * 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(p,dumpimagefunc);
    
    /*
     * render & write the image.
     */
/*    rmFrame(p, rmRootNode()); 1.6.2 */
    rmFrame(p, n);
    
    /*
     * 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(p, NULL);

    /* foil compiler warning */
    xbutton = ybutton = 0;

    return(1);
}

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

    /*
     * restrict rendering in this app to only 2D Opaque objects.
     * we'll set the background color at the rmRootNode(), since we
     * tweak the viewport for 2D rendering in MyRoot. 
     */
    {
	RMcolor4D bgcolor={0.2,0.2,0.3,1.0};
	rmPipeSetRenderPassEnable(p, RM_FALSE, RM_FALSE, RM_TRUE);
	rmPipeSetSceneBackgroundColor(p, &bgcolor);
    }

    rmauxSetGeomTransform(MyRoot,p);

    rmauxSetButtonDownCallbackFunc(my_dump_image_func, NULL, RM_BUTTON1,RM_CONTROL_MODMASK);
    /*
     * set handler to reset aspect ratio when the window is resized.
     */
    rmauxSetResizeFunc(p, MyRoot, rmauxDefaultResizeFunc);
    
    if (rmPipeProcessingModeIsMultithreaded(p) == RM_TRUE)
	rmFrame(p, 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  /* assume RM_X */
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

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

    /* 
     * create the rendering pipe. this step is required in both
     * Win32 and X.
     */

    lone_pipe = rmPipeNew(targetPlatform);
    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);
    }

#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.
     */

    rmauxSetInitCallbackFunc(myinitfunc, NULL); 

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

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

    rmPipeDelete(lone_pipe);
    rmFinish();

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