/*
 * Copyright (C) 2003-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: fpsVis3d.c,v 1.9 2005/06/08 18:02:40 wes Exp $
 * $Revision: 1.9 $
 * $Name: OpenRM-1-6-0-2-c $
 * $Log: fpsVis3d.c,v $
 * Revision 1.9  2005/06/08 18:02:40  wes
 * Code cleanup to eliminate compiler warnings.
 *
 * Revision 1.8  2005/05/16 01:15:05  wes
 * Use new RMpipe-level fbclear operator.
 *
 * Revision 1.7  2004/01/17 03:15:26  wes
 * Updated API call to compute time difference.
 *
 * Revision 1.6  2003/12/01 02:16:54  wes
 * Removed debug/dev code, use straight up OpenRM routines to do
 * constant-rate rendering.
 *
 * Revision 1.5  2003/11/16 16:22:14  wes
 * Added code to implement (by brute force using rmTime* calls) constant
 * frame rate rendering.
 *
 * Revision 1.4  2003/11/05 15:41:57  wes
 * Minor revision control changes.
 *
 * Revision 1.2  2003/10/15 06:00:37  wes
 * Fix some compile warnings.
 *
 * Revision 1.1  2003/10/15 05:50:50  wes
 * Initial entry.
 *
 */

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

static char MyRootName[]={"MyRoot"};
static RMnode *MyRoot;
int img_width=400,img_height=300;
char datafilename[256]={"data/func10.dio"};
dioDataObject *mydataobj=NULL;
int do_color=0;
int doAutoNormalize=1;
int flipNormalsBool = RM_FALSE;

RMtime startTime, endTime;
static int firstFrame=1;
static int frameNumber=0;

/* constant frame rate control stuff */
static int frameRate= -1;
static float msecPerFrame= -1.0;

void
usage(char *av[])
{
    char buf[512];
    sprintf(buf," usage: %s [-i datafilename (defaults to data/func10.dio) [-w img_width] [-h img_height] [-c (use default vis colormap to 'colorize' the plot') [-noauto (turn off autonormalization of mesh surface generated by vis technique )] [-flip (flip the auto-computed surface normals, default is to not flip normals)] [-fr N (says to limit rendering to N frames per second. Default is no frame-rate limiting)\n",av[0]);

#ifdef RM_WIN
    MessageBox(NULL,buf,"vis3d",MB_OK);
#else
    rmError(buf);
#endif
}

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],"-noauto") == 0)
	{
	    fprintf(stderr," turning off autonormalization of surface normals when using visualization technique #1. \n");
	    doAutoNormalize = 0;
	}
	else if (strcmp(av[i],"-flip") == 0)
	{
	    fprintf(stderr," will flip the auto-generated surface normals. \n");
	    flipNormalsBool = RM_TRUE;
	}
	else if (strcmp(av[i],"-fr") == 0)
	{
	    i++;
	    ac--;
	    sscanf(av[i],"%d",&frameRate);
	    /*
	     * frameRate gives frames/sec. compute msec/frame
	     */
	    if (frameRate == 0)
	    {
	        fprintf(stderr," error: a framerate of zero fps (via -fr 0) is not practicable. \n");
		exit(-1);
	    }
	    msecPerFrame = (float)1000.0/(float)frameRate;
	    fprintf(stderr," computed frame time = %g ms \n", msecPerFrame);
	}
	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,
	     int stereo_format,
	     RMpipe *p)
{
    RMcolor4D bgcolor={0.2,0.2,0.3,1.0};
    RMcamera3D *c=rmCamera3DNew();
    
    rmDefaultCamera3D(c);		/* assign it some default values. */

    /* adjust default view so all geom is visible */
    rmCamera3DComputeViewFromGeometry(c,MyRoot, img_width, img_height);
    /*
     * uncomment the following line to get orthographic projection
     */
/*    rmCamera3DSetProjection(c,RM_PROJECTION_ORTHOGRAPHIC);  */

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

    rmNodeSetSceneCamera3D(camNode,c);

#if 1
    rmPipeSetSceneBackgroundColor(p,&bgcolor);
#else
    rmNodeSetSceneBackgroundColor(MyRoot,&bgcolor);
#endif

    rmCamera3DDelete(c);

    /* use RM's default lighting model */
    /*
     * adding the lights to the root node, but then affecting transformations
     * one level down has the effect of making the lights (directions,
     * position, etc.) immune from interactive transformations. in
     * other words, the lights stay at a fixed point in space regardless
     * of the orientation of the objects in the scene.
     */
	
    rmDefaultLighting(camNode);
}

/*
 * 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,z) 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.
 */
RMvertex3D
mygridfunc_u(int i)
{
    /*
     * tell RMV what this grid (x,y,z) point is at location "i". we assume
     * a one-d grid of (x,y,z) points in the local data model.
     */
    RMvertex3D temp3d;
    
    temp3d.x = mydataobj->xcoords[i];
    temp3d.y = mydataobj->ycoords[i];
    temp3d.z = mydataobj->zcoords[i];
    return(temp3d);
}

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 = mydataobj->dims[0] * j + i;
    
    temp3d.x = mydataobj->xcoords[indx];
    temp3d.y = mydataobj->ycoords[indx];
    temp3d.z = mydataobj->zcoords[indx];
    
    return(temp3d);
}

float
mydatafunc_u(int i)
{
    /*
     * tell RMV what the data value is at grid location "i". we
     * assume a one-d grid.
     */
    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 = mydataobj->dims[0] * j + i;
    return(mydataobj->rawdata[indx]);
}

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

	/* we assume that one and only one of w,h,d is equal to 1 */
	
	if (mydataobj->height == 1)
	{
	    /* width & depth are != 1, so offset from the y axis */
	    offset_flag = RMV_YAXIS_OFFSET;
	    usize = mydataobj->width;
	    vsize = mydataobj->depth;
	}
	else if (mydataobj->width == 1)
	{
	    /* height & depth are != 1, so offset from the X axis */
	    offset_flag = RMV_XAXIS_OFFSET;
	    usize = mydataobj->height;
	    vsize = mydataobj->depth;
	}
	else			/* assume depth==1, offset from z axis */
	{
	    offset_flag = RMV_ZAXIS_OFFSET;
	    usize = mydataobj->width;
	    vsize = mydataobj->height;
	}

	if (do_color)
	{
	    vmap = rmDefaultVismap();

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

	visnode = rmNodeNew("vis", RM_RENDERPASS_3D, RM_RENDERPASS_OPAQUE);

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


	rmvJ3MeshSurface(mygridfunc_uv,
			 mydatafunc_uv,
			 (vmap == NULL) ? NULL : mydatafunc_uv,
			 vmap,
			 offset_flag,
			 usize,vsize,
			 flipNormalsBool,
			 visnode);
	if (doAutoNormalize == 1)
	    rmNodeSetNormalizeNormals(visnode, RM_TRUE);
	else
	    rmNodeSetNormalizeNormals(visnode, RM_FALSE);

	rmNodeAddChild(MyRoot,visnode); 
	rmNodeComputeBoundingBox(visnode);
	rmNodeUnionAllBoxes(MyRoot);
	rmNodeComputeCenterFromBoundingBox(MyRoot);
	
	if (vmap != NULL)
	    rmVismapDelete(vmap);
    }
}


void
myinitfunc(RMpipe *p,
	   RMnode *n)
{
    my_read_data(datafilename);
    my_build_objs();
    my_set_scene(n, rmPipeGetChannelFormat(p), p);
    rmauxSetGeomTransform(MyRoot, p);
    rmauxSetSpinEnable(RM_TRUE);
    rmauxSetCamera3DTransform(n, p);

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

void
myrenderfunc(RMpipe *p,
	     RMnode *n)
{
    if (firstFrame == 1)
    {
	firstFrame = 0;
	rmTimeCurrent(&startTime);
    }
    frameNumber++;

    rmFrame(p, n);
    
    rmTimeCurrent(&endTime);
    if (rmTimeDifferenceMS(&startTime, &endTime) > 3000.0F)
    {
	float rate = (float)frameNumber/(rmTimeDifferenceMS(&startTime, &endTime) / 1000.0F);
	printf(" frames %d, elapsed msec=%g \n", frameNumber, rmTimeDifferenceMS(&startTime, &endTime));
	printf(" %5.2f fps \n", rate);
	rmStatsPrint();

	frameNumber = 0;
/*	startTime = endTime; */
	rmTimeCurrent(&startTime);
    }
}

int
myIdleFunc(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()); */
    myrenderfunc(p, rmRootNode());

    /* foil compiler warnings */
    ix = iy = 0;
    
    return 1;
}

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

    /* 
     * first stage of RM initialization.
     */
    rmInit();
    
    parse_args(__argc, __argv);

#else  /* assume RM_X */
int
main(int ac,
     char *av[])
{
    RMenum channelFormat;
    RMenum processingMode = DEFAULT_PROCESSING_MODE; /* in procmode.h */
    RMpipe *lone_pipe = NULL;
    RMenum targetPlatform = RM_PIPE_GLX;
    void *msg;			/* needed for rmauxEventLoop
				 win32/unix API consistency */

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

    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;

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

    lone_pipe = rmPipeNew(targetPlatform);
    
    rmPipeSetProcessingMode(lone_pipe, processingMode);

    /*
     * set fps
     */
    rmPipeSetFrameRate(lone_pipe, frameRate);

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

    rmauxSetInitFunc(myinitfunc); 

    /* uncomment this next line if you want the object to rotate
       while the user is idle. */
    rmauxSetIdleFunc(lone_pipe,myIdleFunc);
    
    /* 
     * X-ism: once the window is created and assigned to the 
     * rendering pipe, rmUsePipe 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);	

    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
}
