/* * 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: clipper.c,v 1.10 2003/04/13 18:13:23 wes Exp $ * $Revision: 1.10 $ * $Name: OpenRM-1-5-2-RC1 $ * $Log: clipper.c,v $ * Revision 1.10 2003/04/13 18:13:23 wes * Updated copyright dates. * * Revision 1.9 2003/01/27 05:07:07 wes * Changes to RMpipe initialization sequence APIs. Tested for GLX, but not WGL. * * Revision 1.8 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.7 2002/06/17 00:34:39 wes * replaced rmSubtreeFrame with rmFrame for v1.4.2. * * Revision 1.6 2001/07/15 22:33:18 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.5 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.4 2001/05/26 14:24:49 wes * Added wireframe cubes. * * 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/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.1 2000/08/31 02:14:17 wes * Initial entry. * */ /* * This program demonstates the use of a transformable clip plane, along * with transformation nesting and polygon draw mode manipulation. * A sphere is positioned at the center of the view, with a red directional * light source pointing in from the left, and a blue directional * light source pointing in from the right. In addition, you'll see * a single quad, which represents the position and orientation of * the clip plane. * * The clip plane may be transformed (rotated) by using Shift+Button3. * Button2 will rotate the entire model, Shift+Button2 will perform * isometric scaling, Button1 translates the entire model parallel to * image plane, and Button3 dollys the camera along the Z axis in * eye coordinates. */ #include <rm/rm.h> #include <rm/rmaux.h> #include <time.h> #include "procmode.h" /* * usage: clipper [-w img_width] [-h img_height] */ static char MyRootName[]={"MyRoot"}; static RMnode *MyRoot; static int img_width=400,img_height=300; static RMvertex3D defaultClipPlanePoint={0.0F, 0.0F, 0.0F}; static RMvertex3D defaultClipPlaneNormal={0.0F, 0.0F, 1.0F}; static RMclipPlane clipPlane; static RMnode *quadNode; static RMnode *clipNode; static float saveX, saveY; static RMmatrix startMatrix, quadNodeMatrix, quadNodeMatrixInverse; static RMmatrix MyRootMatrix, MyRootMatrixInverse; void usage(char *av[]) { char buf[256]; sprintf(buf," usage: %s [-w img_width] [-h img_height] \n",av[0]); rmError(buf); } 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 { usage(av); exit(-1); } i++; } } void myLighting(RMnode *addToNode) { /* set up two lights, one red and one blue */ RMlight *l0, *l1; RMcolor4D red = {1.0, 0.1, 0.1, 1.0}; RMcolor4D blue= {0.1, 0.1, 1.0, 1.0}; RMcolor4D specular = {0.5, 0.5, 0.5, 1.0}; RMvertex3D redDirection = {-3.0, 2.0, 1.0}; RMvertex3D blueDirection = {3.0, 2.0, 1.0}; l0 = rmLightNew(); if (l0 == NULL) return; rmLightSetType(l0, RM_LIGHT_DIRECTIONAL); rmLightSetColor(l0, NULL, &red, &specular); rmLightSetXYZ (l0, &redDirection); l1 = rmLightNew(); if (l1 == NULL) return; rmLightSetType(l1, RM_LIGHT_DIRECTIONAL); rmLightSetColor(l1, NULL, &blue, &specular); rmLightSetXYZ(l1, &blueDirection); rmNodeSetSceneLight(addToNode, RM_LIGHT0, l0); rmNodeSetSceneLight(addToNode, RM_LIGHT1, l1); /* * when the lights are added as scene parameters, RM makes a copy * of them, and we don't need our RMlight objects anymore, so we * need to delete them. */ rmLightDelete(l0); rmLightDelete(l1); /* set up the light model/lighting environment */ { RMcolor4D defAmbient = {0.2, 0.2, 0.2, 1.0}; RMlightModel *lm = rmLightModelNew(); if (lm == NULL) return; rmLightModelSetAmbient(lm, &defAmbient); rmLightModelSetTwoSided (lm, RM_FALSE); rmLightModelSetLocalViewer(lm, RM_FALSE); rmNodeSetSceneLightModel(addToNode, lm); rmLightModelDelete (lm); } } void my_set_scene(RMnode *camNode, RMenum stereo_format) { /* * here, we compute the camera parameters such that all of * the geometry is visible. */ RMcamera3D *c=rmCamera3DNew(); /* assign a default view */ rmDefaultCamera3D(c); /* adjust the default view so that 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 a custom lighting model */ myLighting(rmRootNode()); } void my_sphere(RMnode *addto, float cx, float cy, float cz, float radius, RMcolor3D *color) { RMprimitive *sprim; RMvertex3D v; v.x = cx; v.y = cy; v.z = cz; sprim = rmPrimitiveNew(RM_SPHERES); rmPrimitiveSetVertex3D(sprim,1,&v,RM_COPY_DATA,NULL); rmPrimitiveSetRadii(sprim, 1, &radius, RM_COPY_DATA, NULL); if (color != NULL) rmPrimitiveSetColor3D(sprim, 1, color, RM_COPY_DATA, NULL); rmPrimitiveSetModelFlag(sprim,RM_SPHERES_512); rmNodeAddPrimitive(addto, sprim); } void my_quad(RMnode *addToNode, RMvertex3D *point, RMvertex3D *normal, float radius) { RMprimitive *p; RMvertex3D v[4], n[4]; /* need to add code that will create a quad that actually honors the plane equation specified by "normal" */ v[0].z = v[1].z = v[2].z = v[3].z = point->z; v[0].x = point->x - radius; v[0].y = point->y - radius; v[1].x = point->x + radius; v[1].y = point->y - radius; v[2].x = point->x + radius; v[2].y = point->y + radius; v[3].x = point->x - radius; v[3].y = point->y + radius; n[0] = n[1] = n[2] = n[3] = *normal; p = rmPrimitiveNew(RM_QUADS); rmPrimitiveSetVertex3D(p, 4, v, RM_COPY_DATA, NULL); rmPrimitiveSetNormal3D(p, 4, n, RM_COPY_DATA, NULL); rmNodeAddPrimitive(addToNode, p); } void myClipPlane(RMnode *addToNode, RMvertex3D *point, RMvertex3D *normal) { rmClipPlaneSetPointNormal(&clipPlane, point, normal); rmClipPlaneEnable(&clipPlane); rmNodeSetSceneClipPlane(addToNode,RM_SCENE_CLIP_PLANE0, &clipPlane); } void buildInsideStuff(RMnode *addTo, float x, float y, float z, float radius) { RMprimitive *p; RMvertex3D v[4]; RMcolor3D c[2]; p = rmPrimitiveNew(RM_BOX3D); v[0].x = x; v[0].y = y; v[0].z = z; v[1].x = v[0].x + radius * 0.5; v[1].y = v[0].y + radius * 0.5; v[1].z = v[0].z + radius * 0.5; v[2].x = x; v[2].y = y; v[2].z = z; v[3].x = v[2].x - radius * 0.5; v[3].y = v[2].y - radius * 0.5; v[3].z = v[2].z - radius * 0.5; c[0].r = c[0].g = 1.0; c[0].b = 0.0; c[1].r = c[1].b = 0.0; c[1].g = 1.0; rmPrimitiveSetVertex3D(p,4, v, RM_COPY_DATA,NULL); rmPrimitiveSetColor3D(p,2,c,RM_COPY_DATA, NULL); rmNodeAddPrimitive(addTo, p); } void my_build_objs(void) { RMnode *sphereNode, *insideNode, *insideWireNode; RMnode *nonClipNode; RMcolor3D sphereColor={1.0F, 1.0F, 1.0F}; float radius = 5.0; MyRoot = rmNodeNew(MyRootName,RM_RENDERPASS_3D, RM_RENDERPASS_OPAQUE); rmNodeAddChild(rmRootNode(),MyRoot); clipNode = rmNodeNew("clipNode", RM_RENDERPASS_3D, RM_RENDERPASS_OPAQUE); nonClipNode = rmNodeNew("nonClipNode", RM_RENDERPASS_3D, RM_RENDERPASS_OPAQUE); rmNodeAddChild(MyRoot, clipNode); rmNodeAddChild(MyRoot, nonClipNode); /* build objs */ sphereNode = rmNodeNew("sphereNode", RM_RENDERPASS_3D, RM_RENDERPASS_OPAQUE); my_sphere(sphereNode, 0.0F, 0.0F, 0.0F, radius, &sphereColor); rmNodeComputeBoundingBox(sphereNode); /* put some stuff inside the sphere */ insideNode = rmNodeNew("insideNode", RM_RENDERPASS_3D, RM_RENDERPASS_OPAQUE); buildInsideStuff(insideNode, 0.0F, 0.0F, 0.0F, radius); insideWireNode = rmNodeNew("insideWireNode", RM_RENDERPASS_3D, RM_RENDERPASS_OPAQUE); buildInsideStuff(insideWireNode, 0.0F, 0.0F, 0.0F, radius); /* clip plane representation */ quadNode = rmNodeNew("quadNode", RM_RENDERPASS_3D, RM_RENDERPASS_OPAQUE); my_quad(quadNode, &defaultClipPlanePoint, &defaultClipPlaneNormal, radius); rmNodeComputeBoundingBox(quadNode); rmNodeSetPolygonDrawMode(quadNode, RM_BACK, RM_LINE); /* build clip plane */ myClipPlane(clipNode, &defaultClipPlanePoint, &defaultClipPlaneNormal); /* add stuff to the subtree that will have stuff clipped */ rmNodeAddChild(clipNode, sphereNode); rmNodeAddChild(clipNode, insideNode); /* add stuff to the subtree where no clipping occurs */ rmNodeAddChild(nonClipNode, quadNode); rmNodeSetPolygonDrawMode(insideWireNode, RM_FRONT_AND_BACK, RM_LINE); rmNodeSetShader(insideWireNode, RM_SHADER_NOLIGHT); rmNodeAddChild(nonClipNode, insideWireNode); { RMcolor4D bgColor={0.2, 0.2, 0.3, 1.0}; rmNodeSetSceneBackgroundColor(MyRoot, &bgColor); } rmNodeUnionAllBoxes(rmRootNode()); } 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; rmNodeGetRotateMatrix(MyRoot,&old); rmMatrixMultiply(&old,&m,&old); rmNodeSetRotateMatrix(MyRoot,&old); rmFrame(p, rmRootNode()); } static float pixeltovp(int ipixel, int idim) { float t; t = (float)(ipixel - (idim>>1)) / (float)(idim >> 1); return(t); } int MyStartClipXformFunc(RMpipe *p, int ix, int iy) { int width, height; rmPipeGetWindowSize(p,&width, &height); /* save the starting position */ saveX = pixeltovp(ix,width); saveY = -1.0F * pixeltovp(iy,height); if (rmNodeGetRotateMatrix(quadNode, &startMatrix) == RM_WHACKED) rmMatrixIdentity(&startMatrix); rmMatrixInverse(&startMatrix, &quadNodeMatrixInverse); if (rmNodeGetRotateMatrix(MyRoot, &MyRootMatrix) == RM_WHACKED) rmMatrixIdentity(&MyRootMatrix); rmMatrixInverse(&MyRootMatrix, &MyRootMatrixInverse); rmMatrixMultiply(&startMatrix, &MyRootMatrix, &startMatrix); return 1; } int MyClipXformFunc(RMpipe *p, int ix, int iy) { int width, height; float newX, newY; RMmatrix m, prev; RMvertex3D point,normal; rmPipeGetWindowSize(p,&width, &height); newX = pixeltovp(ix,width); newY = -1.0F * pixeltovp(iy,height); /* compute the quaternion for x1,y1->x2,y2 */ rmauxArcBall (&saveX, &saveY, &newX, &newY, &m); /* update the rotation matrix on the quad */ rmMatrixMultiply(&m, &MyRootMatrixInverse, &m); rmMatrixMultiply(&startMatrix, &m, &m); rmNodeSetRotateMatrix(quadNode, &m); /* tweak the clip plane */ rmClipPlaneGetPointNormal(&clipPlane, &point, &normal); rmPointMatrixTransform(&defaultClipPlaneNormal, &m, &normal); rmClipPlaneSetPointNormal(&clipPlane, &point, &normal); rmNodeSetSceneClipPlane(clipNode, RM_SCENE_CLIP_PLANE0, &clipPlane); /* rerender */ rmFrame(p, rmRootNode()); return(1); } void myinitfunc(RMpipe *p, RMnode *n) { my_build_objs(); my_set_scene(rmRootNode(), rmPipeGetChannelFormat(p)); rmauxSetGeomTransform(MyRoot,p); rmauxSetCamera3DTransform(rmRootNode(), p); /* shift+button3 rotates the clip plane around */ rmauxSetButtonDownFunc(RM_BUTTON3, RM_SHIFT_MODIFIER, MyStartClipXformFunc); rmauxSetButtonMotionFunc(RM_BUTTON3, RM_SHIFT_MODIFIER, MyClipXformFunc); /* * 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; RMpipe *lone_pipe=NULL; RMenum targetPlatform = RM_PIPE_WGL; RMenum processingMode = DEFAULT_PROCESSING_MODE; /* in procmode.h */ RMenum channelFormat; int status; parse_args(__argc,__argv); #else int main(int ac, char *av[]) { RMpipe *lone_pipe=NULL; RMenum targetPlatform = RM_PIPE_GLX; int status; RMenum channelFormat; RMenum processingMode = DEFAULT_PROCESSING_MODE; /* in procmode.h */ 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); /* * set the processing mode on the RMpipe. this must happen before * "rmPipeMakeCurrent(). */ 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. */ /* 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 }