/* * 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: vslicer.c,v 1.13 2003/04/13 18:13:23 wes Exp $ * $Revision: 1.13 $ * $Name: OpenRM-1-5-2-RC1 $ * $Log: vslicer.c,v $ * 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(datafilename,"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, RMimage *texture) { 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,src_volume); 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 q1[4],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(rmRootNode(), 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(rmRootNode(), p); /* * 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()); } 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; int status; RMpipe *myPipe=NULL; parse_args(__argc, __argv); #else /* assume RM_X */ int main(int ac, char *av[]) { int status; RMenum processingMode = DEFAULT_PROCESSING_MODE; /* in procmode.h */ RMenum channelFormat; RMpipe *myPipe=NULL; 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(RM_PIPE_GLX); 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 }