/*
 * 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: vslicer.c,v 1.15 2005/06/08 15:47:16 wes Exp $
 * $Revision: 1.15 $
 * $Name: OpenRM-1-6-0-2-c $
 * $Log: vslicer.c,v $
 * Revision 1.15  2005/06/08 15:47:16  wes
 * Code cleanup to remove compiler warnings.
 *
 * Revision 1.14  2005/05/16 01:19:59  wes
 * Minor code streamlining.
 *
 * Revision 1.13  2003/04/13 18:13:23  wes
 * Updated copyright dates.
 *
 * Revision 1.12  2003/04/12 21:02:45  wes
 * Streamline 3d image creation and handling code.
 *
 * Revision 1.9  2002/06/17 00:48:16  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/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.4  2000/08/31 02:43:37  wes
 * Additional documentation concerning the updated UI.
 *
 * 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.12  2000/02/28 17:21:56  wes
 * RM 1.2, pre-OpenRM
 *
 */

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

/*
 * usage: vslicer [-w windowWidth] [-h windowHeight] [-c (colorize)]
 *    [-l[ights] (turn on lighting)]
 */

/*
 * this program creates an arbitrary slice plane that cuts through a 3D volume
 * by using transformable geometry & 3D texturing.
 *
 * shift mouse button 1 + drag rotates the slice plane only,
 * mouse button2 + drag rotates the entire model w/o modifying
 *   the data on the slice plane.
 * button1 + drag translates the camera parallel to the image plane.
 * button3 + drag translates the camera along the Z axis in eye coords.
 *
 * we use a canned data file,
 * "$cwd/data/volume.dat" is used as source data.
 * this scalar data set is displayed in shades of gray.
 *
 * the scene graph we build looks like this:
 *
 * rmRootNode() -
 *
 *    - MyRoot - has 3d camera for entire scene
 *        - MyOpaqueRoot    just a container type node for opaque objs 
 *                 - outline_box   shows extents of 3D grid
 *
 *        - MyTransparentRoot = a container for stuff to be rendered
 *             during the 3D transparent rendering pass
 *                 - textured_slice parent, apply geom transform here
 *                       also has the 3D volume as a texture. following,
 *                       all children nodes of
 *                       this one have access to this texture at render time.
 *
 *                         - the textured slice, texture coord xforms here
 */

RMnode *slicenode_parent, *slicenode;
static char datafilename[]={"data/volume.dat"};  

#define RAW_WIDTH 256
#define RAW_HEIGHT 64
#define RAW_DEPTH 256

static int img_width=600, img_height=480;
static int do_color=0;
static int do_lights=0;
static RMnode *myOpaqueRoot=NULL, *myTransparentRoot=NULL, *MyRoot=NULL;

unsigned char *rawdata=NULL;
static RMimage *src_volume;

RMimage *
mybuild3dtexture(char *fname,
		 int raw_width,
		 int raw_height,
		 int raw_depth,
		 int do_color)
{
    RMimage *src_volume;
    RMvisMap *vmap=NULL;
    int npts, i;
    FILE *f;

    src_volume = rmImageNew(3,RAW_WIDTH,RAW_HEIGHT,RAW_DEPTH,
			    RM_IMAGE_LUMINANCE_ALPHA,
			    RM_UNSIGNED_BYTE,
			    RM_COPY_DATA);
    
    if (do_color == 1)
    {
	vmap = rmDefaultVismap();
	rmVismapSetTfMin(vmap,0.0F);
	rmVismapSetTfMax(vmap,255.0F);
	
	rmImageSetVismap(src_volume,vmap);
    }
	

    npts = raw_width*raw_height*raw_depth;
    
    rawdata = rmImageGetPixelData(src_volume);
    
    f = fopen(fname,"r");

    fread((void *)rawdata,sizeof(unsigned char),npts,f);
    fclose(f);

    /* set "border" values to be 0x80.  */
    {
	int j,k;

	for (k=0;k<raw_depth;k++)
	{
	    for (j=0;j<raw_height;j++)
	    {
		for (i=0;i<raw_width;i++)
		{
		    int indx;
		    
		    if ((i == 0) || (i==raw_width-1) ||
			(j == 0) || (j== raw_height-1) ||
			(k == 0) || (k== raw_depth-1))
		    {

			indx = i + j*raw_width + k*raw_width*raw_height;
			rawdata[indx] = 0x80;
		    }
		}
	    }
	}
    }

    /* perform in-situ scalar to lum-alpha expansion */
    for (i=npts-1;i>=0;i--)
    {
	rawdata[i*2+1] = rawdata[i];
	rawdata[i*2] = rawdata[i];
    }

    return(src_volume);
}

void
build_outline_box(RMnode *parent,
		  int w,
		  int h,
		  int d)
{
    RMnode *boxnode;
    RMprimitive *boxprim;

    RMvertex3D v[16];

    boxnode = rmNodeNew("outline-box",RM_RENDERPASS_3D, RM_RENDERPASS_OPAQUE);
    boxprim = rmPrimitiveNew(RM_LINE_STRIP);

    /* we need a 3d wireframe box primitive. */
    v[0].x = v[0].y = v[0].z = 0.0;

    v[1] = v[0];
    v[1].x = w-1;

    v[2] = v[1];
    v[2].y = h-1;

    v[3] = v[2];
    v[3].x = 0;

    v[4] = v[0];

    v[5] = v[4];
    v[5].z = d-1;

    v[6] = v[5];
    v[6].y = h-1;

    v[7] = v[6];
    v[7].z = 0;

    v[8] = v[7];
    v[8].z = d-1;

    v[9] = v[8];
    v[9].x = w-1;

    v[10] = v[9];
    v[10].z = 0;

    v[11] = v[10];
    v[11].y = 0;

    v[12] = v[11];
    v[12].z = d-1;

    v[13] = v[12];
    v[13].y = h-1;

    v[14] = v[13];
    v[14].y = 0;

    v[15] = v[14];
    v[15].x = 0;

    rmPrimitiveSetVertex3D(boxprim,16,v,RM_COPY_DATA,NULL);
    rmNodeAddPrimitive(boxnode,boxprim);

    rmNodeComputeBoundingBox(boxnode);
    rmNodeAddChild(parent,boxnode); 
}

void
build_slice_plane(RMnode *parent,
		  int w,
		  int h,
		  int d)
{
    RMprimitive *sliceprim;
    RMvertex3D v[4], tc[4], n[4];
    
    int i;

    /*
     * the objective here is to create a primitive containing a single
     * big quad. that quad will have 3d texture coords at each corner,
     * so the entire quad will have volume texture-mapped onto it.
     *
     * we make a-priori knowledge that the outline box, our visual reference
     * object, spans from (0,0,0) to (w-1,h-1,d-1). we position the quad
     * initially so that it's "in the middle" of the volume, and oriented
     * parallel to the plane formed by the x-z axes.
     *
     * two slicenodes are created. two are needed since we want to implement
     * transformations to both the geometry and texture coords, and RMSG
     * supports transformations to one and only one of geometry, texture
     * coords, lights or cameras at a given node. our construction in this
     * demo program has the geom transforms at the slicenode_parent, and the
     * texturecoord transforms at the slicenode. the order of these could
     * be reversed.
     */

    slicenode_parent = rmNodeNew("slice-node-parent",RM_RENDERPASS_3D, RM_RENDERPASS_ALL);

    slicenode = rmNodeNew("slice-node",RM_RENDERPASS_3D, RM_RENDERPASS_TRANSPARENT);
    sliceprim = rmPrimitiveNew(RM_QUADS);

    for (i=0;i<4;i++)
    {
	v[i].y = (float)(h-1)*0.5F; /* in the middle */
	tc[i].y = 0.5F;		/* tc's also in the middle */
	n[i].x = n[i].z = 0.0F;
	n[i].y = -1.0F;
    }

    /* set the corners of the quad.. */
    v[0].x = v[0].z = 0.0F;
    v[1].x = (float)(w-1);
    v[1].z = 0.0F;
    v[2].x = (float)(w-1);
    v[2].z = (float)(d-1);
    v[3].x = 0.0F;
    v[3].z = (float)(d-1);

    /* set the corresponding texture coords for the quad */
    {
	tc[0].x = tc[0].z = 0.0F;
	tc[1].x = 1.0F;
	tc[1].z = 0.0F;
	tc[2].x = 1.0F;
	tc[2].z = 1.0F;
	tc[3].x = 0.0F;
	tc[3].z = 1.0F;
    }
    
    rmPrimitiveSetVertex3D(sliceprim,4,v,RM_COPY_DATA,NULL);

    rmPrimitiveSetTexcoord3D(sliceprim, 4, tc, RM_COPY_DATA, NULL);

    rmPrimitiveSetNormal3D(sliceprim, 4, n, RM_COPY_DATA, NULL);
    
    /*
     * this is necessary since the volume is set to be an "ALPHA"
     * volume. in order to have a the 3d texture "painted" onto an
     * opaque slice, we'd have to convert to texture to LUMINANCE_ALPHA
     * or RGBA or something else in addition to just alpha.
     */
    
    rmNodeSetTransformMode (slicenode,RM_TRANSFORM_TEXTURE); 

    rmNodeAddPrimitive(slicenode,sliceprim);
    {
	/* set the center point to rotate about the center of the texture */
	RMvertex3D c;
	RMmatrix s;
	
	rmMatrixIdentity(&s);
	s.m[0][0] = s.m[2][2] = s.m[1][1] = 1.0;

	c.x = c.y = c.z = 0.5;
	rmNodeSetCenter(slicenode,&c);
	rmNodeSetScaleMatrix(slicenode,&s); /* applies to texture matrix*/


	/* need this for volumes where w,h,d all not equal */
	/* hack */
	{
	    RMmatrix s2;
	    rmMatrixIdentity(&s2);
	    s2.m[1][1] = 4.0;	/* the value of 4.0 is dependent upon the
				 dimensions of the sample dataset included
				 with the rmdemo distribution.*/
	    rmNodeSetPostRotateScaleMatrix(slicenode,&s2);
	}


    }

    rmNodeAddChild(slicenode_parent,slicenode);
    rmNodeAddChild(parent,slicenode_parent);

    {
	RMvertex3D v[2];

	v[0].x = v[0].y = v[0].z = 0.0F;
	v[1].x = w-1;
	v[1].y = h-1;
	v[1].z = d-1;

	rmNodeSetBoundingBox(slicenode_parent,v,v+1);
	
    }
    rmNodeComputeCenterFromBoundingBox(slicenode_parent);

    {
	/*
	 * build and add a texture as a scene parameter
	 */
	RMtexture *t;
	int mipmap_count = 1;
	RMimage **mipmaps;
	
	t = rmTextureNew(3);

#if 0
	/* volume mipmaps not ready yet */
	if (do_mipmaps == 1)
	{
	    rmTextureSetFilterMode(t,GL_LINEAR_MIPMAP_LINEAR,GL_NEAREST);
	    mipmap_count = rmuImageBuildMipmaps(resizedtexture,
						&mipmaps,resize_engine);
	}
	else
#endif
	    mipmaps = &src_volume;

	rmTextureSetFilterMode(t,GL_LINEAR,GL_LINEAR);
	rmTextureSetImages(t,mipmaps,mipmap_count,RM_FALSE);
	
	rmNodeSetSceneTexture(slicenode_parent,t);  
    }
}

void
my_build_objs()
{
    src_volume = mybuild3dtexture(datafilename,RAW_WIDTH,RAW_HEIGHT,RAW_DEPTH,
				  do_color);

    MyRoot = rmNodeNew("MyRoot", RM_RENDERPASS_3D, RM_RENDERPASS_ALL);
    myOpaqueRoot = rmNodeNew("opaque3D",RM_RENDERPASS_3D, RM_RENDERPASS_OPAQUE);
    myTransparentRoot = rmNodeNew("transparent3D",RM_RENDERPASS_3D, RM_RENDERPASS_TRANSPARENT);
    build_outline_box(myOpaqueRoot,RAW_WIDTH,RAW_HEIGHT,RAW_DEPTH);
    build_slice_plane(myTransparentRoot,RAW_WIDTH,RAW_HEIGHT,RAW_DEPTH);
    
    rmNodeUnionAllBoxes(myOpaqueRoot);
    rmNodeUnionAllBoxes(myTransparentRoot);

    rmNodeComputeCenterFromBoundingBox(myOpaqueRoot);
    rmNodeComputeCenterFromBoundingBox(myTransparentRoot);

    {
	RMcolor4D bgcolor={0.2,0.2,0.3,1.0};
	rmNodeSetSceneBackgroundColor(myOpaqueRoot,&bgcolor);
    }

    rmNodeAddChild(MyRoot,myOpaqueRoot);
    rmNodeAddChild(MyRoot,myTransparentRoot);
    rmNodeUnionAllBoxes(MyRoot);
    rmNodeComputeCenterFromBoundingBox(MyRoot);
    rmNodeAddChild(rmRootNode(), MyRoot);
}

void
my_set_scene(RMnode *camNode,
	     int stereo_format)
{
    RMcamera3D *c=rmCamera3DNew();
    
    /* create a camera */
    rmDefaultCamera3D(c);		/* assign it some default values. */

    /* adjust the default view so all geom is visible */
    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 */
    if (do_lights == 1)
	rmDefaultLighting(camNode);
    else
	rmNodeSetShader(camNode, RM_SHADER_NOLIGHT);

}

/*
 * this code was taken from rmaux
 */
static float pixeltovp(int ipixel, int idim)
{
    float t;
    t = (float)(ipixel - (idim>>1)) / (float)(idim >> 1);
    return(t);
}

static float x1,yy1;
RMmatrix save_matrix, myroot_inverse, save_inverse,myroot_orig;
RMmatrix slice_parent_matrix, slice_matrix;
RMmatrix slice_parent_inverse;
RMmatrix slice_texture_rotate_matrix;

int
MyStartSliceXformFunc(RMpipe *p,
		      int ix,
		      int iy)
{
    RMmatrix I;
    int width, height;

    rmPipeGetWindowSize(p,&width, &height);
    
    /* save the starting position */
    x1 = pixeltovp(ix,width);
    yy1 = -1.0F * pixeltovp(iy,height);

    if (rmNodeGetRotateMatrix(slicenode_parent,&slice_parent_matrix) == RM_WHACKED)
	rmMatrixIdentity(&slice_parent_matrix);
    
    rmMatrixInverse(&slice_parent_matrix,&slice_parent_inverse);

    if (rmNodeGetRotateMatrix(slicenode,&slice_texture_rotate_matrix) == RM_WHACKED)
	rmMatrixIdentity(&slice_texture_rotate_matrix);
    
    if (rmNodeGetRotateMatrix(MyRoot,&myroot_orig) == RM_WHACKED)
	rmMatrixIdentity(&myroot_orig);
    
    rmMatrixIdentity(&I);

    rmMatrixInverse(&myroot_orig,&myroot_inverse);

    rmMatrixMultiply(&slice_parent_matrix,&myroot_orig,&slice_parent_matrix);
    rmMatrixMultiply(&slice_texture_rotate_matrix,&myroot_orig,
		     &slice_texture_rotate_matrix);

    return(1);
}

int
MySliceXformFunc(RMpipe *p,
		 int ix,
		 int iy)
{
    RMmatrix mx;
    float x2,y2;
    int width, height;

    rmPipeGetWindowSize(p,&width, &height);

    x2 = pixeltovp(ix,width);
    y2 = -1.0F * pixeltovp(iy,height);

    /* compute the quaternion for x1,y1->x2,y2 */
    rmauxArcBall (&x1, &yy1, &x2, &y2, &mx);

    {
        RMmatrix m;
	
	rmMatrixMultiply(&mx,&myroot_inverse,&m);
	rmMatrixMultiply(&slice_parent_matrix,&m,&m);
	rmNodeSetRotateMatrix(slicenode_parent,&m);

	rmMatrixMultiply(&mx,&myroot_inverse,&m);
	rmMatrixMultiply(&slice_texture_rotate_matrix,&m,&m);
	rmNodeSetRotateMatrix(slicenode,&m);
    }

    rmFrame(p, rmRootNode());
    return(1);
}

void
myinitfunc(RMpipe *p,
	   RMnode *n)
{
    my_build_objs();

    my_set_scene(n, rmPipeGetChannelFormat(p));

    /* 8/27/2000 - changed from B1 to Shift+B1 to rotate the slice */
    rmauxSetButtonDownFunc(RM_BUTTON1, RM_SHIFT_MODIFIER, MyStartSliceXformFunc);
    rmauxSetButtonMotionFunc(RM_BUTTON1, RM_SHIFT_MODIFIER, MySliceXformFunc);

    rmauxSetGeomTransform(MyRoot,p);
    rmauxSetCamera3DTransform(n, p);
    /*
     * 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
usage(char *av[])
{
    fprintf(stderr," usage: %s [-w img_width] [-h img_height] [-c (use default vis colormap to 'colorize' the plot')] [-l(ights) (turn on lighting, which is off by default)]\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 if (strncmp(av[i],"-l",2) == 0)
	{
	    do_lights = 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; 
    RMenum channelFormat;
    RMenum processingMode = DEFAULT_PROCESSING_MODE; /* in procmode.h */
    void *fptr;
    RMpipe *myPipe=NULL;
    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 channelFormat;
    RMpipe *myPipe=NULL;
    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.
     */
    myPipe = rmPipeNew(targetPlatform);
    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,img_width,img_height,"RM for Windows",
			       hInstance,fptr);
	if (hWnd == 0)
	  exit(-1);

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

    }
#endif
#ifdef RM_X
    {
	Window w;

	w = rmauxCreateXWindow(myPipe,
			       (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(myPipe,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 we made the newly created context
     * current as part of the OpenGL initialization sequence.
     */
    rmPipeMakeCurrent(myPipe);	

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

    rmPipeDelete(myPipe);
    rmFinish();

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