/* * Copyright (C) 1997-2005, R3vis Corporation. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library 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 * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this library; 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/lgpl.html. * * Original Contributor: * Wes Bethel, R3vis Corporation, Marin County, California * Additional Contributor(s): * * The OpenRM project is located at http://openrm.sourceforge.net/. */ /* * $Id: rmflyui.c,v 1.6 2005/06/08 18:33:02 wes Exp $ * Version: $Name: OpenRM-1-6-0-RC5 $ * $Revision: 1.6 $ * $Log: rmflyui.c,v $ * Revision 1.6 2005/06/08 18:33:02 wes * Code cleanups to eliminate compiler warnings. * * Revision 1.5 2005/02/19 16:08:45 wes * Distro sync and consolidation. * * Revision 1.4 2005/01/23 17:11:49 wes * Copyright update to 2005. * * Revision 1.3 2004/01/17 04:07:53 wes * Updated copyright line for 2004. * * Revision 1.2 2003/02/02 02:07:18 wes * Updated copyright to 2003. * * Revision 1.1.1.1 2003/01/28 02:15:23 wes * Manual rebuild of rm150 repository. * * Revision 1.8 2003/01/16 22:21:18 wes * Updated all source files to reflect new organization of header files: * all header files formerly located in include/rmaux, include/rmi, include/rmv * are now located in include/rm. * * Revision 1.7 2002/04/30 19:39:19 wes * Updated return type of rmauxFlyMotionFunc() to be an int rather * than void. This will ensure consistency and proper functioning * on all platforms. * * Revision 1.6 2001/03/31 17:09:31 wes * v1.4.0-alpha2 checkin. * * Revision 1.5 2000/12/03 22:36:29 wes * First steps towards thread safety. * * Revision 1.4 2000/04/20 16:22:16 wes * JDB modifications: additional documentation, code rearranging. * * Revision 1.3 2000/04/17 00:06:51 wes * Numerous documentation updates and code reorganization courtesy of jdb. * * Revision 1.2 2000/02/29 23:43:57 wes * Compile warning cleanups. * * Revision 1.1.1.1 2000/02/28 21:29:40 wes * OpenRM 1.2 Checkin * * Revision 1.1.1.1 2000/02/28 17:18:48 wes * Initial entry - pre-RM120 release, source base for OpenRM 1.2. * */ #include #include /* * of the routines in this file, only rmauxFlyUI is intended to be * accessible from the application level. the other routines are used * to effect inter-frame changes in the camera and to implement resets. * * 1/15/2000 w.bethel */ #ifdef RM_X #include #endif typedef enum RMflyEnum /* PRIVATE */ { NEEDS_INITIALIZATION = 0, IN_MOTION = 1, PAUSED = 2 } RMflyEnum; typedef struct RMflyStruct /* PRIVATE */ { RMflyEnum flyInMotion; /* tag for state of fly interface */ float flyOrientationScale; /* used to attenuate incremental rotation */ float flyTranslateScale; /* used to attenuate camera movement */ RMcamera3D flyCamera; /* working camera, computed on each frame */ RMvertex3D flyEye; /* working eye point */ RMvertex3D flyAtVector; /* working look-at vector */ RMvertex3D flyUpVector; /* working up vector */ RMmatrix flyMatrix; /* accumulation matrix */ double flyEyeAtMag; /* magnitude of original eye-at vector */ double flyFocalLength; /* of the camera */ int flyWinWidth; /* width of display window in pixels */ int flyWinHeight; /* height of display window in pixels */ float flyFOVdelta; /* FOV of camera */ RMnode *cameraNode; /* node containing the original camera */ RMnode *drawNode; /* root of SG to draw on each frame */ RMvertex3D saveEyeAtVector; /* saved, normalized eye-at vector */ RMvertex3D saveUpVector; /* saved, normalized camera up vector */ RMcamera3D saveCamera; /* virgin camera */ } RMflyStruct; /* static variables - malloc'ed to ensure thread safety */ static RMflyStruct *activeFlyStruct = NULL; #ifdef RM_X static Cursor *motionCursor = NULL; #endif /* PRIVATE declarations */ void private_rmauxFlySetup (RMflyStruct *fs, int w, int h); extern void rmauxInvokeRenderFunc (RMpipe *p, RMnode *n); /* * ---------------------------------------------------- * @Name rmauxFlyUI @pstart void rmauxFlyUI (RMnode *cameraNode, RMnode *sgRoot, float orientationScale, float stepScale) @pend @astart RMnode *cameraRoot - (input) the RMnode containing the RMcamera3D that will be manipulated with a flight model. RMnode *sgRoot - (input) the root of the scene graph that will be drawn on each frame. cameraRoot and sgRoot may be the same node. float orientationScale - (input) a floating point value between 0.0 and 1.0 used to attenuate rotations. A good range of value for this parameter is 1/30...1/60. float stepScale - (input) a floating point value between 0.0 and 1.0 used to attenuate translational camera motion. A good range of values for this parameter is 1/30..1/60. @aend @dstart rmauxFlyUI builds a set of button to event mappings that are useful for terrain flyovers. The fundamental assumption of rmauxFlyUI is that the input RMnode contains an RMcamera3D scene parameter. This 3D camera will be modified - thus, rmauxFlyUI changes the position of the viewer in the scene, but does not change the orientation matrices of any objects. Pressing the left mouse button commences activity (motion), and pressing the left mouse button again stops the viewer (abruptly). Changes in the horizontal location of the pointer cause camera roll to change, while changes to the vertical location of the pointer affect camera pitch. Heading changes as a function of roll & pitch (like flying an aircraft). The input float parameter orientationScale is used to regulate how quickly changes occur. The value of orientationScale is inversely proportional to the number of frames required for a given change to occur. For example, on each fly frame, we compute the displacement between the pointer current position and the center of the window. Then, we adjust the camera to a new heading. The adjustment is computed such that the new heading will be achieved in 1/orientationScale frames. Therefore, orientationScale is most often a value between 0.0 and 1.0. The closer to 1.0, the more rapid will the changes occur. The closer to 0.0, the more slowly they will occur. We may some day incorporate a temporal filter on changes, but for now, we don't. The input float parameter stepScale regulates the amount of camera translation per frame. The amount the camera moves during each frame is a function of stepScale multiplied by the cameraFocalDistance parameter (by default, the camera focal distance parameter is 1.0, which measures units of the distance from the eye point to the view reference, or look at point). Increasing the cameraFocalDistance parameter has no substantive effect on the view matrix except when viewing in stereo. Upon entry, rmauxFlyUI checks to see if the input RMnode "cameraNode" has a 3d camera as a scene parameter. If none is present, an error message isissued and we return. If one is present, we grab a copy of the camera and use the copy for the duration of the UI run. Our copy is modified, then we set the camera3D scene parameter of the target as we fly around. Should the application want to modify the camera, it will be necessary to call rmauxFlyUI again to re-register the new camera with the FlyUI machinery. During operation, the scene graph rooted at the input RMnode "sgRoot" is drawn on each frame from a viewpoint that "flies" through the scene. We use a crude, static-velocity model (zero acceleration). It's like a 3 year old child: either it's completely stopped, or going at full speed, with no speed inbetween. @dend * ---------------------------------------------------- */ void rmauxFlyUI (RMnode *cameraNode, RMnode *sgRoot, RMpipe *p, float orientationScale, float stepScale) { RMcamera3D *tmp = NULL; /* setup fly interface once only */ if (activeFlyStruct == NULL) { activeFlyStruct = (RMflyStruct *)malloc(sizeof(RMflyStruct)); memset((void *)activeFlyStruct, 0, sizeof(RMflyStruct)); } /* check to see if the target node has a 3D camera scene parameter */ if (rmNodeGetSceneCamera3D(cameraNode, &tmp) == RM_WHACKED) { rmError("rmauxFlyUI expects the input RMnode to have an RMcamera3D scene parameter. no such scene parameter is present in the input RMnode. returning without creating the fly UI."); return; } activeFlyStruct->flyCamera = *tmp; rmCamera3DDelete(tmp); activeFlyStruct->saveCamera = activeFlyStruct->flyCamera; activeFlyStruct->cameraNode = cameraNode; activeFlyStruct->drawNode = sgRoot; #ifdef RM_X /* setup fancy X11 cursor */ if (motionCursor == NULL) { motionCursor = (Cursor *)malloc(sizeof(Cursor)); *motionCursor = XCreateFontCursor(rmxPipeGetDisplay(p), XC_trek); } #endif rmauxSetButtonDownFunc(RM_BUTTON1, RM_NONE_MODMASK, rmauxFlyToggleMotion); rmauxSetButtonDownFunc(RM_BUTTON3, RM_NONE_MODMASK, rmauxFlyResetCamera); activeFlyStruct->flyOrientationScale = orientationScale; activeFlyStruct->flyTranslateScale = stepScale; } /* * ---------------------------------------------------- * @Name rmauxFlyResetCamera @pstart int rmauxFlyResetCamera RMAUX_BUTTON_FUNC_PARMS() @pend @astart the macro RMAUX_BUTTON_FUNC_PARMS() expands to: RMpipe *p - a handle to the current RMpipe. int xbutton, ybutton - the current (x, y) positions of the mouse pointer. @aend @dstart This routine restores the 3D camera parameters to what they were when rmauxFlyUI was originally called. It is intended to be called from event loop code. Returns 1 to the caller, presumably an event loop handler, so that processing will continue. @dend * ---------------------------------------------------- */ int rmauxFlyResetCamera RMAUX_BUTTON_FUNC_PARMS() { int winWidth, winHeight; /* reset the camera to its initial value when called */ rmNodeSetSceneCamera3D(activeFlyStruct->cameraNode, &(activeFlyStruct->saveCamera)); rmPipeGetWindowSize(p, &winWidth, &winHeight); private_rmauxFlySetup(activeFlyStruct, winWidth, winHeight); /* foil compiler warning */ xbutton = ybutton = 0; return(1); } /* * ---------------------------------------------------- * @Name rmauxFlyToggleMotion @pstart int rmauxFlyToggleMotion RMAUX_BUTTON_FUNC_PARMS() @pend @astart the macro RMAUX_BUTTON_FUNC_PARMS() expands to: RMpipe *p - a handle to the current RMpipe. int xbutton, ybutton - the current (x, y) positions of the mouse pointer. @aend @dstart This routine turns on and off motion by manipulating the "idle function." When turning motion off, the idle function is removed (nothing happens). When turned on, we set the idle function to rmauxFlyMotionFunc so that we begin to fly. A 1 is returned to the caller. @dend * ---------------------------------------------------- */ int rmauxFlyToggleMotion RMAUX_BUTTON_FUNC_PARMS() { int (*funcptr)(RMpipe *, int, int); if (activeFlyStruct->flyInMotion != IN_MOTION) /* start motion */ { int winWidth, winHeight; funcptr = rmauxFlyMotionFunc; rmauxSetIdleFunc(p, funcptr); #ifdef RM_X /* X11 gets a nice cursor that will be displayed when we start flying */ XDefineCursor(rmxPipeGetDisplay(p), rmPipeGetWindow(p), *motionCursor); #endif /* * get the current window size. we're assuming that the * user probably won't do window resizing while they're * flying around, but checking here allows for the possibility * of responding to window resizes when not flying. */ rmPipeGetWindowSize(p, &winWidth, &winHeight); if (activeFlyStruct->flyInMotion == NEEDS_INITIALIZATION) private_rmauxFlySetup(activeFlyStruct, winWidth, winHeight); /* first, we set a state variable, and assign a new idle function */ activeFlyStruct->flyInMotion = IN_MOTION; } else /* stop motion */ { activeFlyStruct->flyInMotion = PAUSED; rmauxSetIdleFunc(p, NULL); #ifdef RM_X /* restore X11 cursor */ XUndefineCursor(rmxPipeGetDisplay(p), rmPipeGetWindow(p)); #endif } /* foil compiler warning */ xbutton = ybutton = 0; return(1); } /* * ---------------------------------------------------- * @Name rmauxFlyMotionFunc @pstart int rmauxFlyMotionFunc RMAUX_BUTTON_FUNC_PARMS() @pend @astart the macro RMAUX_BUTTON_FUNC_PARMS() expands to: RMpipe *p - a handle to the current RMpipe. int xbutton, ybutton - the current (x, y) positions of the mouse pointer. @aend @dstart Given a new cursor location, update the camera to take into account the rotational and translational effects of the change from the current values and those dictated by the new cursor position. A static structure is used to hold state, so this routine (in fact, all of rmauxFlyUI) is not thread safe at this time. Always returns 1 so that rmauxEventLoop will not terminate. @dend * ---------------------------------------------------- */ int rmauxFlyMotionFunc RMAUX_BUTTON_FUNC_PARMS() { int xCenter, xDelta; int yCenter, yDelta; double deltaPitch; double deltaRoll; double c, s; RMvertex3D newHeading, newUpVector; RMmatrix mPitch, composite; RMmatrix mRoll; RMmatrix mTmp; RMflyStruct *fs; fs = activeFlyStruct; /* * first, look at difference between x-coord of the pointer and the * center of the window. the horizontal difference between the * pointer location and the center of the window will affect roll. * roll is a modification to the local +y axis of the camera. */ xCenter = (fs->flyWinWidth >> 1); xDelta = -(xbutton - xCenter); deltaRoll = (double)xDelta * (double)(fs->flyFOVdelta) * fs->flyOrientationScale; deltaRoll = RM_DEGREES_TO_RADIANS(deltaRoll); /* a change in roll changes the orientation of our viewpoint around the camera's local Y axis */ c = cos(deltaRoll); s = sin(deltaRoll); rmMatrixIdentity(&mRoll); mRoll.m[0][0] = mRoll.m[2][2] = c; mRoll.m[2][0] = s; mRoll.m[0][2] = -s; /* now modify the pitch as a function of the y coordinate of the pointer and the old at vector */ yCenter = (fs->flyWinHeight >> 1); yDelta = yCenter - ybutton; yDelta = yDelta; deltaPitch = (double)yDelta * (double)(fs->flyFOVdelta) * fs->flyOrientationScale; deltaPitch = RM_DEGREES_TO_RADIANS(deltaPitch); /* compute new eye-at vector as a function of the old value and the new heading */ rmMatrixIdentity(&mPitch); /* for now, don't use pitch */ c = cos(deltaPitch); s = sin(deltaPitch); mPitch.m[1][1] = mPitch.m[2][2] = c; mPitch.m[1][2] = s; mPitch.m[2][1] = -s; rmMatrixMultiply(&mPitch, &mRoll, &composite); rmMatrixMultiply(&(fs->flyMatrix), &composite, &(fs->flyMatrix)); mTmp = (fs->flyMatrix); newHeading.x = mTmp.m[0][1]; newHeading.y = mTmp.m[1][1]; newHeading.z = mTmp.m[2][1]; newUpVector.x = mTmp.m[0][2]; newUpVector.y = mTmp.m[1][2]; newUpVector.z = mTmp.m[2][2]; rmVertex3DNormalize(&newHeading); rmVertex3DNormalize(&newUpVector); /* update the new eye point. it's a function of the old * eye point, newheading, eye-at magnitude, camera focal length * and flyTranslateScale */ { float s; s = fs->flyTranslateScale * fs->flyFocalLength * fs->flyEyeAtMag; fs->flyEye.x += s*newHeading.x; fs->flyEye.y += s*newHeading.y; fs->flyEye.z += s*newHeading.z; } #if 0 fprintf(stderr,"new eye point is %g, %g, %g, new heading is %g, %g, %g \n", fs->flyEye.x, fs->flyEye.y, fs->flyEye.z, newHeading.x, newHeading.y, newHeading.z); #endif /* convert at vector to a real look at point for the RMcamera3D */ newHeading.x = (newHeading.x * fs->flyEyeAtMag) + fs->flyEye.x; newHeading.y = (newHeading.y * fs->flyEyeAtMag) + fs->flyEye.y; newHeading.z = (newHeading.z * fs->flyEyeAtMag) + fs->flyEye.z; rmCamera3DSetAt(&(fs->flyCamera), &newHeading); rmCamera3DSetEye(&(fs->flyCamera), &(fs->flyEye)); rmCamera3DSetUpVector(&(fs->flyCamera), &newUpVector); rmNodeSetSceneCamera3D(fs->cameraNode, &(fs->flyCamera)); rmauxInvokeRenderFunc(p, fs->drawNode); return 1; } /* PRIVATE * * intended only for internal use, fills in an internal structure * with info. we need the window width and height from the caller. */ void private_rmauxFlySetup (RMflyStruct *fs, int w, int h) { RMcamera3D *c; RMmatrix *m; fs->flyWinWidth = w; fs->flyWinHeight = h; c = &(fs->saveCamera); rmCamera3DGetEye(c, &(fs->flyEye)); rmCamera3DGetAt(c, &(fs->flyAtVector)); rmVertex3DDiff(&(fs->flyAtVector), &(fs->flyEye), &(fs->flyAtVector)); rmVertex3DMagNormalize(&(fs->flyAtVector), &(fs->flyEyeAtMag)); fs->saveEyeAtVector = fs->flyAtVector; rmCamera3DGetUpVector(c, &(fs->flyUpVector)); fs->saveUpVector = fs->flyUpVector; fs->flyFOVdelta = rmCamera3DGetFOV(c); fs->flyFocalLength = rmCamera3DGetFocalDistance(c); fs->flyFOVdelta = fs->flyFOVdelta / (float)(w); /* 1 pixel = how much heading change? */ rmMatrixIdentity(&(fs->flyMatrix)); m = &(fs->flyMatrix); m->m[0][1] = fs->flyAtVector.x; m->m[1][1] = fs->flyAtVector.y; m->m[2][1] = fs->flyAtVector.z; m->m[0][2] = fs->flyUpVector.x; m->m[1][2] = fs->flyUpVector.y; m->m[2][2] = fs->flyUpVector.z; } /* EOF */