/* * Copyright (C) 1997-2003, 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.14 2003/04/13 18:13:23 wes Exp $ * $Revision: 1.14 $ * $Name: OpenRM-1-5-2-RC1 $ * $Log: vrend.c,v $ * 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. */ static char dataFileName[]={"data/volume.dat"}; /* * the "raw data file" is just raw byte information. a priori knowledge * about the size is required in order to use this program. the size of * the volume is compiled into this program. */ #define RAW_WIDTH 256 #define RAW_HEIGHT 64 #define RAW_DEPTH 256 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"); } 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. */ 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,¢er); 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 */ int status; parse_args(__argc, __argv); #else int main(int ac, char *av[]) { RMpipe *lone_pipe=NULL; int status; 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 }