/* * Copyright (C) 2000-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: isodrv-mt.c,v 1.11 2004/01/17 03:16:46 wes Exp $ * $Revision: 1.11 $ * $Name: OpenRM-1-5-2-RC1 $ * $Log: isodrv-mt.c,v $ * Revision 1.11 2004/01/17 03:16:46 wes * Updated API call that computes time differences. * * Revision 1.10 2003/11/05 15:52:37 wes * Updated timing calls to use new rmTime* routines introduced in 1.5.1. * * Revision 1.9 2003/04/13 18:13:23 wes * Updated copyright dates. * * Revision 1.8 2003/01/27 05:07:07 wes * Changes to RMpipe initialization sequence APIs. Tested for GLX, but not WGL. * * Revision 1.7 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.6 2002/06/17 00:38:11 wes * Updated copyright line. * * Revision 1.5 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.4 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.3 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.2 2000/12/03 23:48:44 wes * *** empty log message *** * * Revision 1.1 2000/12/02 17:23:09 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/volume.dio"}; dioDataObject *mydataobj=NULL; int do_color=0; int do_print=0; int vis_technique=0; int my_linewidth = RM_LINEWIDTH_MEDIUM; int my_linestyle = RM_LINES_SOLID; static RMenum static_stereo_format; float isolevel=0.5; static int nThreads=1; int use_secondary=0; dioDataObject *secondary=NULL; RMvisMap *vmap=NULL; typedef struct { int myThreadID; int nThreads; RMvisMap *vmap; int usize, vsize, wsize; float isolevel; RMnode *n; } threadArgs; /* * Colorization notes: * 1. when a secondary dataset is provided, the isosurface will be * vertex-colorized by: * 2. at each isosurface triangle vertex, the rmv routine that generates * the isosurface will invoke the app callback to retrieve * data values from the secondary dataset. the RGB(A) color value * from the visualization colormap that corresponds to the data value * from the secondary dataset will be used as the vertex color. * 3. since it is an app callback that provides the secondary data values * via a callback, there is no restriction that says the actual * size, or even dimensionality, of the secondary dataset must be * the same as the primary dataset. * 4. this demo maps the min/max of the secondary data set to the min/max * transfer function values. * 5. this demo uses the primary dataset as the source of the secondary * dataset. that means that we should see an isosurface of constant * color, where the color will essentially be a function of the * isocontouring level. */ void usage(char *av[]) { char buf[256]; sprintf(buf," usage: %s [-i datafilename (defaults to data/volume.dio) [-w img_width] [-h img_height] [-p (print the scene graph to stderr)] [-l isolevel (isocontouring level, default is 0.5, use a value in range 0..1.0 with the default data set)] [-n nThreads (default is 1)]\n",av[0]); #ifdef RM_WIN MessageBox(NULL,buf,"vis3d",MB_OK); #else fprintf(stderr,"%s",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],"-2") == 0) { use_secondary=1; } else if (strcmp(av[i],"-l") == 0) { i++; sscanf(av[i],"%f",&isolevel); } else if (strcmp(av[i],"-n") == 0) { i++; sscanf(av[i],"%d",&nThreads); } else if (strcmp(av[i],"-c") == 0) { do_color=1; } else if (strcmp(av[i],"-p") == 0) do_print = 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, int stereo_format) { RMcamera3D *c=rmCamera3DNew(); rmDefaultCamera3D(c); /* assign it some default values. */ 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 */ 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->dims[0] * j + i; temp3d.x = mydataobj->xcoords[indx]; temp3d.y = mydataobj->ycoords[indx]; temp3d.z = mydataobj->zcoords[indx]; return(temp3d); } RMvertex3D mygridfunc_uvw(int i, int j, int k, int isize, int jsize, int ksize, float *baseX, float *baseY, float *baseZ) { /* * tell RMV what this grid (x,y,z) point is at location (i,j,k). * we assume the data model is sufficiently intelligent to know * it's own dimensions, and is capable of dealing with a three-dimensional * indexing system. * * we assume that the "i" index maps to width, and that "j" * maps to height, and k maps to depth in the local data model. */ RMvertex3D temp3d; int indx; /* indx = mydataobj->width * j + i; */ /* indx = mydataobj->dims[0] * j + i + mydataobj->dims[0]*mydataobj->dims[1]*k; */ indx = isize * j + i + isize*jsize*k; temp3d.x = baseX[indx]; temp3d.y = baseY[indx]; temp3d.z = baseZ[indx]; return(temp3d); } float mydatafunc_uvw(int i, int j, int k, int isize, int jsize, int ksize, float *baseData) { /* * tell RMV what the data value is at grid location (i,j,k). we * assume an n-dimensional array can be accesses as if it * were a one-d array. */ int indx; /* indx = mydataobj->dims[0] * j + i + mydataobj->dims[0]*mydataobj->dims[1]*k; */ indx = isize * j + i + isize*jsize*k; return(baseData[indx]); } void * threadFunc(void *args) { threadArgs *ta = (threadArgs *)args; char buf[32]; RMnode *n; int myZslices, myStartZSlice=0; float *baseData, *xCoordBase, *yCoordBase, *zCoordBase; int indxOffset; sprintf(buf,"thread-%d",ta->myThreadID); n = rmNodeNew(buf,RM_RENDERPASS_3D, RM_RENDERPASS_OPAQUE); myZslices = ta->wsize / ta->nThreads; if (ta->myThreadID == nThreads-1) myZslices += (ta->wsize % ta->nThreads); fprintf(stderr," thread %d processing %d slices of data. \n", ta->myThreadID, myZslices); if (ta->myThreadID == 0) indxOffset = 0; else { myStartZSlice = (ta->myThreadID)* (ta->wsize/ta->nThreads); indxOffset = myStartZSlice * ta->usize * ta->vsize; } xCoordBase = mydataobj->xcoords + indxOffset; yCoordBase = mydataobj->ycoords + indxOffset; zCoordBase = mydataobj->zcoords + indxOffset; baseData = mydataobj->rawdata + indxOffset; /* * add one to the count of # of slices to process, if we're not * working on the "last" partition. without this increment, the * isosurfaces leaves a one-grid "gap" between adjacent partitions. * simply adding one to the slice count will ensure c-zero * continuity. a better approach is needed to ensure c-one smoothness. */ if (ta->myThreadID != nThreads-1) myZslices += 1; rmvK3MarchingCubes(mygridfunc_uvw, mydatafunc_uvw, /* note this is a hack - we should really be using a secondary dataset that is different from the primary dataset. since we're just reusing the primary dataset, what we'll get is a constant- colored isosurface, and the color changes when we change the isocontouring level via the command line*/ (secondary != NULL) ? mydatafunc_uvw : NULL, ta->vmap, ta->usize, ta->vsize, myZslices, ta->isolevel, n, xCoordBase, yCoordBase, zCoordBase, baseData); ta->n = n; } void my_build_objs(void) { pthread_t *threadIDs; threadArgs *tArgs; int i; RMtime s, e; /* * todo: fully threaded across N threads. right now, the best * we can do is one thread due to some limitations in RMV wrt * not having base data pointers passed along. */ RMnode *visnode; MyRoot = rmNodeNew(MyRootName,RM_RENDERPASS_3D, RM_RENDERPASS_OPAQUE); rmNodeAddChild(rmRootNode(),MyRoot); visnode = rmNodeNew("vis",RM_RENDERPASS_3D, RM_RENDERPASS_OPAQUE); threadIDs = (pthread_t *)malloc(sizeof(pthread_t)*nThreads); tArgs = (threadArgs *)malloc(sizeof(threadArgs)*nThreads); fprintf(stderr," spinning %d threads.\n", nThreads); rmTimeCurrent(&s); for (i=0;i<nThreads;i++) { tArgs[i].myThreadID = i; tArgs[i].nThreads = nThreads; tArgs[i].vmap = vmap; tArgs[i].usize = mydataobj->width; tArgs[i].vsize = mydataobj->height; tArgs[i].wsize = mydataobj->depth; tArgs[i].isolevel = isolevel; pthread_create(threadIDs+i, NULL, threadFunc, (void *)(tArgs+i)); #if 0 pthread_join(threadIDs[i], NULL); rmNodeAddChild(visnode, tArgs[i].n); #endif } for (i=0;i<nThreads;i++) { pthread_join(threadIDs[i], NULL); rmNodeAddChild(visnode, tArgs[i].n); } rmTimeCurrent(&e); printf(" elapsed isosurface generation time is %g msec \n",rmTimeDifferenceMS(&s, &e)); rmNodeAddChild(MyRoot,visnode); /* * set the bounding box for the visnode as a function of * the extents of the underlying grid, not the extents of * the derived isosurface. */ rmNodeSetBoundingBox(visnode, (RMvertex3D *)&(mydataobj->corners[0]), (RMvertex3D *)&(mydataobj->corners[1])); rmNodeComputeCenterFromBoundingBox(visnode); rmNodeUnionAllBoxes(rmRootNode()); rmNodeComputeCenterFromBoundingBox(MyRoot); { RMcolor4D bgcolor={0.2,0.2,0.3,1.0}; /* * assign a background color to take effect at "MyRoot" */ rmNodeSetSceneBackgroundColor(MyRoot,&bgcolor); } dioDeleteDataObject(mydataobj); free(tArgs); free(threadIDs); } void my_idle_func(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()); } void dumpimagefunc(RMimage *img, int whichbuffer_enum) { int i; i = 0; dioWriteAVSImage(img,"/tmp/vis3d.x"); } void setup_secondary_dataobj() { secondary = mydataobj; vmap = rmDefaultVismap(); rmVismapSetTfMin(vmap,mydataobj->datamin); rmVismapSetTfMax(vmap,mydataobj->datamax); } void myinitfunc(RMpipe *p, RMnode *n) { my_read_data(datafilename); if (use_secondary == 1) setup_secondary_dataobj(); my_build_objs(); my_set_scene(rmRootNode(), rmPipeGetChannelFormat(p)); /* * set up the event handler to apply geometric transformations at * MyRoot. note that the lights and cameras are placed at rmRootNode(). * therefore, the rotations & scaling applied at MyRoot do not affect * the cameras & lights since they are at a higher level in the * scene graph than MyRoot. */ rmauxSetGeomTransform(MyRoot,p); rmauxSetCamera3DTransform(rmRootNode(), p); if (do_print == 1) /* won't see output in win32 unless you change NULL to a char string with a filename, then the output will be written in ASCII format to that file. */ rmPrintSceneGraph(rmRootNode(),RM_PRINT_VERBOSE,NULL); /* * set handler to reset aspect ratio when the window is resized. */ rmauxSetResizeFunc(p, rmRootNode(), rmauxDefaultResizeFunc); if (rmPipeProcessingModeIsMultithreaded(p) == RM_TRUE) rmFrame(p, rmRootNode()); rmFrame(p, rmRootNode()); } #ifdef RM_WIN int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpszCmdLine, int nCmdShow) { MSG msg; HWND hWnd; void *fptr; int status; RMenum channelFormat; RMenum processingMode = DEFAULT_PROCESSING_MODE; /* in procmode.h */ RMpipe *lone_pipe = NULL; RMenum targetPlatform = RM_PIPE_WGL; parse_args(__argc, __argv); #else /* assume RM_X */ int main(int ac, char *av[]) { int status; 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); 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); /* uncomment this next line if you want the object to rotate while the user is idle. you probably don't want to do this w/o lots of graphics hardware program since this demo generates 300k triangles. */ /* rmauxSetIdleFunc(lone_pipe,my_idle_func); */ /* * 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 }