/*
 * 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: vrend.c,v 1.17 2005/08/08 00:57:10 wes Exp $
 * $Revision: 1.17 $
 * $Name: OpenRM-1-6-0-2-c $
 * $Log: vrend.c,v $
 * Revision 1.17  2005/08/08 00:57:10  wes
 * Restored large volume (256x64x256) rather than the small volume. The
 * larger volume is known to not work properly with Mesa-only systems
 * due to 3D texture size limitations with Mesa.
 *
 * Revision 1.16  2005/06/08 15:49:23  wes
 * Code cleanup to remove compiler warnings.
 *
 * Revision 1.15  2005/05/16 01:19:40  wes
 * Change default dataset to smaller 64x64x64 electron potential rather
 * than larger one.
 *
 * Revision 1.14  2003/04/13 18:13:23  wes
 * Updated copyright dates.
 *
 * Revision 1.13  2003/04/12 21:02:45  wes
 * Streamline 3d image creation and handling code.
 *
 * Revision 1.12  2003/02/02 17:52:00  wes
 * Add explicit call to compute the Octmesh RMprim bbox (code coverage test).
 *
 * 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:47:58  wes
 * Replaced rmSubtreeFrame with rmFrame.
 *
 * Revision 1.8  2001/10/15 00:27:06  wes
 * Changed background color from blue to white.
 *
 * 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/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.11  2000/02/28 17:21:56  wes
 * RM 1.2, pre-OpenRM
 *
 */

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

/*
 * this program demonstrates direct volume rendering within RM.
 * a canned datafile, "$cwd/data/volume.dat" is used as source data.
 * this scalar data set is displayed in shades of gray, unless
 * -c is specified on the command line, in which case a default
 * colormap and transfer function is used to create an RGBa volume.
 *
 * win32 NOTE: Microsoft OpenGL 1.1 DOES NOT SUPPORT 3D TEXTURES - this
 * program will not work using MS OpenGL 1.1. Maybe one day they'll release
 * an OpenGL 1.2 that supports 3D Textures.
 */

#define BIG_VOL 1
#if BIG_VOL
static char dataFileName[]={"data/volume.dat"};  
#define RAW_WIDTH  256
#define RAW_HEIGHT 64
#define RAW_DEPTH  256
#else
static char dataFileName[]={"data/hydro.dat"};  
#define RAW_WIDTH  64
#define RAW_HEIGHT 64
#define RAW_DEPTH  64
#endif

static int img_width=600, img_height=480;

static int ModelToggle=1;
static RMnode *modelToToggle=NULL;
static int do_color=0;

int
MyModelSwitchFunc(RMpipe *p,
		  int ix,
		  int iy)
{
    ModelToggle = (ModelToggle == 0) ? 1 : 0;

    if (ModelToggle == 1)
    {
	rmNodeSetTraverseEnable(modelToToggle, RM_TRUE);
	printf(" enabling display of volume data \n");
    }
    else
    {
	rmNodeSetTraverseEnable(modelToToggle, RM_FALSE);
	printf(" disabling display of volume data \n");
    }

    /* foil compiler warning */
    ix = iy = 0;
    
    rmFrame(p, rmRootNode());
    return(1);
}

int
myswitchcallbackfunc(const RMnode *n,
		     const RMstate *s)
{
    /*
     * this switch callback function is used to perform render-time
     * model switching. when it returns 0, the full res volume
     * is drawn, when it returns 1, a wireframe box is drawn.
     *
     * press button 1 on the mouse to toggle between models.
     */
    
    /* foil compiler warning */
    n = NULL;
    s = NULL;
    
    return(ModelToggle);
}

RMnode *
myBuildObjs(RMnode *addTo,
	    unsigned char *volumeData,
	    int volumeDims[3])
{
    RMnode *omesh;
    RMnode *wirebox;
    RMnode *myRoot, *opaqueRoot, *transparentRoot;
    RMimage *volumeImage;

    RMvisMap *vmap = NULL;

    myRoot = rmNodeNew("myRoot", RM_RENDERPASS_ALL, RM_RENDERPASS_ALL);
    opaqueRoot = rmNodeNew("opaqueRoot", RM_RENDERPASS_3D, RM_RENDERPASS_OPAQUE);
    transparentRoot = rmNodeNew("transparentRoot", RM_RENDERPASS_3D, RM_RENDERPASS_TRANSPARENT);
    modelToToggle = transparentRoot;

    rmNodeAddChild(myRoot, opaqueRoot);
    rmNodeAddChild(myRoot, transparentRoot);
    rmNodeAddChild(addTo, myRoot);

    /* set the background color */
    {
	RMcolor4D bgColor={.1F,.1F,.2F,1.F};
	rmNodeSetSceneBackgroundColor(opaqueRoot,&bgColor);
    }

    /* build the 3D image that will later be used as a texture */
    volumeImage = rmImageNew(3,volumeDims[0], volumeDims[1], volumeDims[2],
			     RM_IMAGE_LUMINANCE_ALPHA,
			     RM_UNSIGNED_BYTE,
			     RM_COPY_DATA);

    rmImageSetPixelData(volumeImage,volumeData,RM_COPY_DATA,NULL); 

    /* if selected, add a colormap to the image to colorize the data */
    if (do_color == 1)
    {
	vmap = rmDefaultVismap();
	rmImageSetVismap(volumeImage, vmap);
    }

    /* build the octmesh node */
    
    omesh = rmNodeNew("octmesh",RM_RENDERPASS_3D, RM_RENDERPASS_TRANSPARENT);
    
    {
	RMprimitive *p;
	RMvertex3D v[2];
	RMvertex3D center;
	
	p = rmPrimitiveNew(RM_OCTMESH);

	v[0].x = v[0].y = v[0].z = 0.F;
	v[1].x = volumeDims[0];
	v[1].y = volumeDims[1];
	v[1].z = volumeDims[2];

	rmPrimitiveSetOmeshDims(p,volumeDims[0], volumeDims[1], volumeDims[2]);
	rmPrimitiveSetOmeshMinMaxGrid(p,v,v+1);

/*	rmPrimitiveSetModelFlag(p,RM_OCTMESH_4); */

	center.x = (v[1].x - v[0].x)*0.5 + v[0].x;
	center.y = (v[1].y - v[0].y)*0.5 + v[0].y;
	center.z = (v[1].z - v[0].z)*0.5 + v[0].z;

	rmPrimitiveComputeBoundingBox(p);
	
	rmNodeSetCenter(omesh,&center);
	rmNodeSetBoundingBox(omesh,v,v+1);

	rmNodeAddPrimitive(omesh,p);
	rmNodeAddChild(transparentRoot, omesh);
    }

    /* build the RMtexture using the 3D volume image */
    {
	RMtexture *t=NULL;

	t = rmTextureNew(3);

	rmTextureSetImages(t, &volumeImage, 1, RM_FALSE);

	rmTextureSetFilterMode(t, GL_LINEAR, GL_LINEAR);
/*	rmNodeSetSceneTexture(MyRoot,t); */
	rmNodeSetSceneTexture(omesh,t); 
    }

    /* build an outline box */
    {
	RMprimitive *box;
	RMvertex3D v[2];
	RMcolor4D c[2]={{1.0, 1.0, 1.0, 1.0},{1.,1.,1.,1.}};

	wirebox = rmNodeNew("wire-box",RM_RENDERPASS_3D, RM_RENDERPASS_OPAQUE);
	
	box = rmPrimitiveNew(RM_BOX3D_WIRE);
	
	v[0].x = v[0].y = v[0].z = 0.0;
	v[1].x = volumeDims[0];
	v[1].y = volumeDims[1];
	v[1].z = volumeDims[2];

	rmPrimitiveSetVertex3D(box, 2, v,RM_COPY_DATA,NULL);
	rmPrimitiveSetColor4D(box, 2, c, RM_COPY_DATA, NULL);

	rmNodeAddPrimitive(wirebox,box);
	rmNodeAddChild(opaqueRoot, wirebox);
    }
    
    rmNodeUnionAllBoxes(myRoot);
    rmNodeComputeCenterFromBoundingBox(myRoot);

    return myRoot;
}

void
mySetScene(RMnode *addTo,
	   RMenum channelFormat)
{
    RMcamera3D *c=rmCamera3DNew();
    
    /* create a camera */
    rmDefaultCamera3D(c);		/* assign it some default values. */

    /* adjust the default view so all geom is visible */
    rmCamera3DComputeViewFromGeometry(c,addTo,img_width,img_height);

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

    }

    rmNodeSetSceneCamera3D(addTo,c);
}

unsigned char *
readVolumeData(const char *dataFileName,
	       int volumeDimsReturn[3])
{
    unsigned char *rawdata;
    int npts;
    FILE *f;

    npts = RAW_WIDTH * RAW_HEIGHT * RAW_DEPTH;
    
    volumeDimsReturn[0] = RAW_WIDTH;
    volumeDimsReturn[1] = RAW_HEIGHT;
    volumeDimsReturn[2] = RAW_DEPTH;
    
    /* npts * 2 because we'll expand from scalar to luminance-alpha */
    rawdata = (unsigned char *)malloc(sizeof(unsigned char)*npts*2);
    f = fopen(dataFileName,"r");
    fread((void *)rawdata,sizeof(unsigned char),npts,f);
    fclose(f);

    {
	/* perform in-situ scalar to lum-alpha expansion */
	int j;
	for (j=npts-1;j>=0;j--)
	{
	    rawdata[j*2+1] = rawdata[j];
	    rawdata[j*2] = rawdata[j];
	}
    }
    return rawdata;
}
    
void
myinitfunc(RMpipe *p, RMnode *n)
{
    RMnode *myRoot;
    int volumeDims[3];
    unsigned char *rawVolumeDataLumAlpha=NULL;

    rawVolumeDataLumAlpha = readVolumeData(dataFileName, volumeDims);
    
    myRoot = myBuildObjs(n, rawVolumeDataLumAlpha, volumeDims);
    
    mySetScene(myRoot, rmPipeGetChannelFormat(p));

    rmauxSetGeomTransform(myRoot,p);
    rmauxSetCamera3DTransform(myRoot, p);

    /* 8/27/2000 - changed from B1 down to Shift+B1 down to toggle between
     outline box and fully rendered model for transformations. */
    rmauxSetButtonDownFunc(RM_BUTTON1,RM_SHIFT_MODIFIER,MyModelSwitchFunc);
    /*
     * set handler to reset aspect ratio when the window is resized.
     */
    rmauxSetResizeFunc(p, myRoot, rmauxDefaultResizeFunc);
    
    if (rmPipeProcessingModeIsMultithreaded(p) == RM_TRUE)
	rmFrame(p, rmRootNode());
    
    rmFrame(p, rmRootNode());
}

void
usage(char *av[])
{
    fprintf(stderr," usage: %s [-w img_width] [-h img_height] [-c (use default vis colormap to 'colorize' the plot')] \n",av[0]);
}

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],"-c") == 0)
	{
	    do_color=1;
	}
	else
	{
	    usage(av);
	    exit(-1);
	}
	i++;
    }
}

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

    parse_args(__argc, __argv);
    
#else
int
main(int ac,
     char *av[])
{
    RMpipe *lone_pipe=NULL;
    RMenum processingMode = DEFAULT_PROCESSING_MODE; /* in procmode.h */
    RMenum channelFormat;
    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);

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

    /*
     * 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
}
