/* * 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: vis3d.c,v 1.13 2004/01/17 03:20:31 wes Exp $ * $Revision: 1.13 $ * $Name: OpenRM-1-5-2-RC1 $ * $Log: vis3d.c,v $ * Revision 1.13 2004/01/17 03:20:31 wes * Added command line arguments [-fr NN] and [-spin] to enable constant-rate * rendering and auto-spin mode, respectively. Also, turned off the printing * of per-frame statistics because the timings aren't correct on all platforms. * * Revision 1.12 2003/10/15 05:46:34 wes * Added -flip command line argument to enable normal flipping on * certain visualization techniques. * * Revision 1.11 2003/04/13 18:13:23 wes * Updated copyright dates. * * Revision 1.10 2003/01/27 05:07:07 wes * Changes to RMpipe initialization sequence APIs. Tested for GLX, but not WGL. * * Revision 1.9 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.8 2002/06/17 00:47:21 wes * Added -noauto command line flag. This will turn off auto normalization * of surface normals. * * 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 <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 do_print=0; int do_backstop=0; int vis_technique=0; int doAutoNormalize=1; RMenum my_linewidth = RM_LINEWIDTH_MEDIUM; RMenum my_linestyle = RM_LINES_SOLID; int do_bg_image=0; RMimage *bgimage=NULL; RMenum flipNormalsBool = RM_FALSE; RMenum spinModeEnabled = RM_FALSE; int frameRate = -1; void usage(char *av[]) { char buf[1024]; 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') [-v n (where n=0..11, and indicates which visualization technique to use)] [-bs (make a 'vanishing backstop') [-bg (tile the background with the AVS image data/orangegrid.x))] [-p (print the scene graph to stderr)] [-noauto (turn off autonormalization of mesh surface generated by vis technique] [-flip (flip the normals generated by vis techniques 0 and 8, default is to not flip normals)] [-spin (turns on auto-spin mode, default is off)] [-fr NN (set frame rendering rate to NN frames per second, NN should be > 0; default is no constant-rate rendering)] \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],"-v") == 0) { i++; sscanf(av[i],"%d",&vis_technique); if ((vis_technique < 0) || (vis_technique > 11)) { usage(av); exit(0); } } else if (strcmp(av[i],"-bs") == 0) { do_backstop = 1; } else if (strcmp(av[i],"-bg") == 0) { do_bg_image = 1; bgimage = dioReadAVSImage("data/orangegrid.x"); if (bgimage == NULL) { fprintf(stderr," there was some kind of error reading the image file. skipping it and continuing..\n"); do_bg_image = 0; } } else if (strcmp(av[i],"-p") == 0) do_print = 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 any surface normals computed during visualization \n"); flipNormalsBool = RM_TRUE; } else if (strcmp(av[i],"-spin") == 0) { spinModeEnabled = RM_TRUE; } else if (strcmp(av[i],"-fr") == 0) { i++; sscanf(av[i],"%d",&frameRate); } 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. */ /* 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 (do_bg_image==1 && bgimage != NULL) rmNodeSetSceneBackgroundImage(MyRoot,bgimage); else { RMcolor4D bgcolor={0.2,0.2,0.3,1.0}; rmNodeSetSceneBackgroundColor(MyRoot,&bgcolor); } 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; float zeroval=0.0; float shrink = 0.8; 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; switch(vis_technique) { case 0: 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); break; case 1: { int scaling_flag = RMV_SCALE_RELATIVE; shrink = 0.6F; rmvJ3Bar(mygridfunc_uv, mydatafunc_uv, (vmap == NULL) ? NULL : mydatafunc_uv, vmap, offset_flag, usize,vsize, shrink, scaling_flag, visnode); break; } case 2: { int scaling_flag = RMV_SCALE_ABSOLUTE; shrink = 0.5; rmvJ3BarOutline(mygridfunc_uv, mydatafunc_uv, (vmap == NULL) ? NULL : mydatafunc_uv, vmap, offset_flag, usize,vsize, shrink, scaling_flag, my_linewidth, my_linestyle, visnode); break; } case 3: rmvJ3Impulse(mygridfunc_uv, mydatafunc_uv, (vmap == NULL) ? NULL : mydatafunc_uv, vmap, offset_flag, usize,vsize, my_linewidth, my_linestyle, visnode); break; case 4: rmvJ3MeshOutline(mygridfunc_uv, mydatafunc_uv, (vmap == NULL) ? NULL : mydatafunc_uv, vmap, offset_flag, usize, vsize, my_linewidth, my_linestyle, visnode); break; case 5: rmvJ3MeshUHorizon(mygridfunc_uv, mydatafunc_uv, (vmap == NULL) ? NULL : mydatafunc_uv, vmap, offset_flag, usize, vsize, zeroval,visnode); rmvJ3MeshUHorizonOutline(mygridfunc_uv, mydatafunc_uv, NULL, NULL, offset_flag, usize, vsize, my_linewidth, my_linestyle, zeroval, visnode); break; case 6: rmvJ3MeshVHorizon(mygridfunc_uv, mydatafunc_uv, (vmap == NULL) ? NULL : mydatafunc_uv, vmap, offset_flag, usize, vsize, zeroval,visnode); rmvJ3MeshVHorizonOutline(mygridfunc_uv, mydatafunc_uv, NULL, NULL, offset_flag, usize, vsize, my_linewidth, my_linestyle, zeroval, visnode); break; case 7: { shrink = 1.0; rmvI3ScatterCube(mygridfunc_u, mydatafunc_u, (vmap == NULL) ? NULL : mydatafunc_u, vmap, offset_flag, usize*vsize, shrink, visnode); break; } case 8: rmvJ3ScatterPoint(mygridfunc_uv, mydatafunc_uv, (vmap == NULL) ? NULL : mydatafunc_uv, vmap, offset_flag, usize, vsize, RM_TRUE, flipNormalsBool, visnode); rmNodeSetPointSize(visnode,5.0F); break; case 9: rmvI3ScatterSphere(mygridfunc_u, mydatafunc_u, (vmap == NULL) ? NULL: mydatafunc_u, vmap,offset_flag, usize*vsize, shrink, visnode); break; case 10: { shrink = 1.0; rmvI3ScatterWireCube(mygridfunc_u, mydatafunc_u, (vmap == NULL) ? NULL : mydatafunc_u, vmap, offset_flag, usize* vsize, shrink, my_linewidth,my_linestyle, visnode); break; } case 11: { float clevels[3]; int nlevels = 3; RMcolor4D colors[3]= {{0.6, 0.5, 0.9,1.0}, {0.8, 0.4, 0.4,1.0}, {1.0, 0.25, 0.25,1.0}}; clevels[0] = mydataobj->datamin + 0.25 * (mydataobj->datamax - mydataobj->datamin); clevels[1] = mydataobj->datamin + 0.5 * (mydataobj->datamax - mydataobj->datamin); clevels[2] = mydataobj->datamin + 0.75 * (mydataobj->datamax - mydataobj->datamin); rmvJ3SliceContour(mygridfunc_uv,mydatafunc_uv, offset_flag, usize, vsize, nlevels, clevels, colors, my_linewidth, my_linestyle, visnode); rmvJ3MeshSurface(mygridfunc_uv, mydatafunc_uv, (vmap == NULL) ? NULL : mydatafunc_uv, vmap, offset_flag, usize, vsize, flipNormalsBool, visnode); break; } default: break; } if (do_backstop) { RMnode *bsnode; RMvertex3D bmin,bmax; float data_min,data_max; bsnode = rmNodeNew("backstop-node",RM_RENDERPASS_3D, RM_RENDERPASS_OPAQUE); bmin = mygridfunc_uv(0,0); bmax = mygridfunc_uv(usize-1,vsize-1); data_min = mydataobj->datamin; data_max = mydataobj->datamax; if (offset_flag == RMV_ZAXIS_OFFSET) { bmin.z = data_min; bmax.z = data_max; rmv3DRuledBox(&bmin,&bmax, usize,vsize,2, my_linewidth, my_linestyle, RM_TRUE, bsnode); } if (offset_flag == RMV_YAXIS_OFFSET) { bmin.y = data_min; bmax.y = data_max; rmv3DRuledBox(&bmin,&bmax, usize,2,vsize, my_linewidth, my_linestyle, RM_TRUE, bsnode); } if (offset_flag == RMV_XAXIS_OFFSET) { bmin.x = data_min; bmax.x = data_max; rmv3DRuledBox(&bmin,&bmax, 2,usize,vsize, my_linewidth, my_linestyle, RM_TRUE, bsnode); } rmNodeAddChild(MyRoot, bsnode); } rmNodeAddChild(MyRoot,visnode); rmNodeComputeBoundingBox(visnode); rmNodeUnionAllBoxes(MyRoot); rmNodeComputeCenterFromBoundingBox(MyRoot); if (vmap != NULL) rmVismapDelete(vmap); } } 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 myinitfunc(RMpipe *p, RMnode *n) { my_read_data(datafilename); my_build_objs(); my_set_scene(rmRootNode(), rmPipeGetChannelFormat(p)); rmauxSetGeomTransform(MyRoot, p); rmauxSetSpinEnable(spinModeEnabled); rmauxSetCamera3DTransform(rmRootNode(), p); /* * set handler to reset aspect ratio when the window is resized. */ rmauxSetResizeFunc(p, rmRootNode(), rmauxDefaultResizeFunc); 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); rmStatsComputeDemography(rmRootNode()); if (rmPipeProcessingModeIsMultithreaded(p) == RM_TRUE) rmFrame(p, rmRootNode()); rmFrame(p, rmRootNode()); } void myrenderfunc(RMpipe *p, RMnode *n) { rmFrame(p, n); #if 0 /* * for 1.5.1 release, comment out the status stuff; the timing part of * the stats isn't always correct as we need to put glFinish() into * the mix, and doing so really slows down the app. If you're * interested in fps figures, check out the fpsVis3d demo. */ rmStatsStartTime(); rmFrame(p, n); rmStatsEndTime(); rmStatsPrint(); #endif } #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 */ int status; 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[]) { int status; 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); 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,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); 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 }