/* * * Copyright (C) 2000 Silicon Graphics, Inc. All Rights Reserved. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 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 * Lesser General Public License for more details. * * Further, this software is distributed without any warranty that it is * free of the rightful claim of any third person regarding infringement * or the like. Any license provided herein, whether implied or * otherwise, applies only to this software file. Patent licenses, if * any, provided herein do not apply to combinations of this program with * other software, or any other product whatsoever. * * You should have received a copy of the GNU Lesser 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 * * Contact information: Silicon Graphics, Inc., 1600 Amphitheatre Pkwy, * Mountain View, CA 94043, or: * * http://www.sgi.com * * For further information regarding this notice, see: * * http://oss.sgi.com/projects/GenInfo/NoticeExplan/ * */ /* * Copyright (C) 1990-93 Silicon Graphics, Inc. * _______________________________________________________________________ ______________ S I L I C O N G R A P H I C S I N C . ____________ | | $Revision: 1.1.1.1 $ | | Classes : SoXtViewer | | Author(s) : Alain Dumesny | ______________ S I L I C O N G R A P H I C S I N C . ____________ _______________________________________________________________________ */ #include #include #include #include #include #include #include #ifdef __sgi #include #endif #ifndef __sgi #define fcos cos #define ftan tan #define fatan atan #define fsqrt sqrt #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // keep a pointer to global time, since we are going to access it a lot. SoSFTime *SoXtViewer::viewerRealTime = NULL; //////////////////////////////////////////////////////////////////////// // // Constructor. // // Use: protected SoXtViewer::SoXtViewer( Widget parent, const char *name, SbBool buildInsideParent, SoXtViewer::Type t, SbBool buildNow) : SoXtRenderArea( parent, name, buildInsideParent, TRUE, // getMouseInput TRUE, // getKeyboardInput FALSE) // buildNow // //////////////////////////////////////////////////////////////////////// { // init local vars type = t; camera = NULL; cameraType = SoPerspectiveCamera::getClassTypeId(); createdCamera = FALSE; viewingFlag = TRUE; altSwitchBack = FALSE; cursorEnabledFlag = TRUE; interactiveFlag = FALSE; startCBList = new SoCallbackList; finishCBList = new SoCallbackList; interactiveCount = 0; bufferType = isDoubleBuffer() ? BUFFER_DOUBLE : BUFFER_SINGLE; stereoOffset = 3.0; #ifdef __sgi useSGIStereoExt = FALSE; #endif sceneSize = 0.0; // not computed yet. viewerSpeed = 1.0; // default. SoXtFullViewer add UI to increase/decrease // add Enter and Leave notify events for the viewers inputFocus = new SoXtInputFocus; registerDevice(inputFocus); if (! viewerRealTime) viewerRealTime = (SoSFTime *) SoDB::getGlobalField("realTime"); // init auto clipping stuff autoClipFlag = TRUE; minimumNearPlane = 0.001; autoClipBboxAction = new SoGetBoundingBoxAction(SbVec2s(1,1)); // ??? no valid size yet // copy/paste support clipboard = NULL; // init seek animation variables seekDistance = 50.0; seekDistAsPercentage = TRUE; seekModeFlag = FALSE; detailSeekFlag = TRUE; seekAnimTime = 2.0; seekAnimationSensor = new SoFieldSensor(SoXtViewer::seekAnimationSensorCB, this); // // build the small internal graph (nodes used for draw style stuff) // sceneRoot = new SoSeparator(4); drawStyleSwitch = new SoSwitch(6); drawStyleNode = new SoDrawStyle; lightModelNode = new SoLightModel; colorNode = new SoPackedColor; matBindingNode = new SoMaterialBinding; complexityNode = new SoComplexity; sceneGraph = NULL; // note: we cannot setSceneGraph on the renderArea in the constructor // since it calls virtual functions, and all of our members aren't // initialized yet. We'll call it the first time our setSceneGraph // is called. sceneRoot->ref(); sceneRoot->renderCaching.setValue(SoSeparator::OFF); // no caching there sceneRoot->renderCulling.setValue(SoSeparator::OFF); // no culling there sceneRoot->addChild(drawStyleSwitch); drawStyleSwitch->addChild(drawStyleNode); drawStyleSwitch->addChild(lightModelNode); drawStyleSwitch->addChild(colorNode); drawStyleSwitch->addChild(matBindingNode); drawStyleSwitch->addChild(complexityNode); // set the draw style vars and fields that don't change - once we // have a context, will will use glGetString() to pick a better default // draw style. stillDrawStyle = VIEW_AS_IS; interactiveDrawStyle = VIEW_SAME_AS_STILL; checkForDrawStyle = TRUE; drawStyleSwitch->whichChild = SO_SWITCH_NONE; drawStyleNode->setOverride(TRUE); // only use style field drawStyleNode->pointSize = 3.0; drawStyleNode->lineWidth.setIgnored(TRUE); drawStyleNode->linePattern.setIgnored(TRUE); lightModelNode->setOverride(TRUE); colorNode->setOverride(TRUE); matBindingNode->setOverride(TRUE); matBindingNode->value = SoMaterialBinding::OVERALL; complexityNode->setOverride(TRUE); complexityNode->textureQuality = 0; // always turn texture off under switch complexityNode->value = 0.15; addStartCallback(SoXtViewer::drawStyleStartCallback); addFinishCallback(SoXtViewer::drawStyleFinishCallback); // // headlightGroup - we have a rotation which keeps the headlight // moving whenever the camera moves, and a reset xform so // that the rest of the scene is not affected by the first rot. // these leaves the direction field in the headlight open for the // user to edit, allowing for the direction to change w.r.t. the camera. // headlightGroup = new SoGroup(3); headlightRot = new SoRotation; headlightNode = new SoDirectionalLight; headlightGroup->ref(); headlightGroup->addChild(headlightRot); headlightGroup->addChild(headlightNode); headlightGroup->addChild(new SoResetTransform); headlightNode->direction.setValue(SbVec3f(.2, -.2, -.9797958971)); headlightFlag = TRUE; // Build the widget tree, and let SoXtComponent know about our base widget. if (buildNow) { Widget w = buildWidget(getParentWidget()); setBaseWidget(w); } } //////////////////////////////////////////////////////////////////////// // // Destructor. // // Use: protected SoXtViewer::~SoXtViewer() // //////////////////////////////////////////////////////////////////////// { // detach everything if ( sceneGraph != NULL ) setSceneGraph(NULL); sceneRoot->unref(); // delete everything delete inputFocus; delete seekAnimationSensor; delete clipboard; delete autoClipBboxAction; delete startCBList; delete finishCBList; headlightGroup->unref(); } //////////////////////////////////////////////////////////////////////// // // Description: // Set a new user supplied scene graph. // // use: virtual public // void SoXtViewer::setSceneGraph(SoNode *newScene) // //////////////////////////////////////////////////////////////////////// { // if we haven't already given the render area a scene graph sceneRoot, // give it the scene graph now. This is a one shot deal, which // cannot be done in the constructor. if (SoXtRenderArea::getSceneGraph() == NULL) SoXtRenderArea::setSceneGraph(sceneRoot); // draw new scene graphs to the front buffer by default since // the scene will be different (we might has well see something // happening for the first redraw). if (isDrawToFrontBufferEnable()) drawToFrontBuffer = TRUE; // // detach everything that depends on the old sceneGraph // if ( sceneGraph != NULL ) { setCamera(NULL); sceneRoot->removeChild(sceneGraph); } sceneGraph = newScene; // // now assign the new sceneGraph, find or create the new camera // and attach things back. // if ( sceneGraph != NULL ) { sceneRoot->addChild(sceneGraph); // search for first camera in the scene SoSearchAction sa; sa.setType(SoCamera::getClassTypeId()); sa.setSearchingAll(FALSE); // don't look under off switches sa.apply(sceneGraph); SoCamera *newCamera = NULL; if (sa.getPath()) newCamera = (SoCamera *)((SoFullPath *)sa.getPath())->getTail(); // if no camera found create one of the right kind... if ( newCamera == NULL ) { newCamera = (SoCamera*) cameraType.createInstance(); if (newCamera == NULL) { #ifdef DEBUG SoDebugError::post("SoXtViewer::setSceneGraph", "unknown camera type!"); #endif // ??? what should we do here ? cameraType = SoPerspectiveCamera::getClassTypeId(); newCamera = new SoPerspectiveCamera; } createdCamera = TRUE; if (type == SoXtViewer::BROWSER) // add camera after drawstyle stuff sceneRoot->insertChild(newCamera, 1); else { // check to make sure scene starts with at least a group node if ( sceneGraph->isOfType(SoGroup::getClassTypeId()) ) ((SoGroup *)sceneGraph)->insertChild(newCamera, 0); else { // make scene start with a group node SoGroup *group = new SoGroup; group->addChild(newCamera); group->addChild(sceneGraph); sceneRoot->addChild(group); sceneRoot->removeChild(sceneGraph); sceneGraph = group; } } newCamera->viewAll(sceneGraph, SbViewportRegion(getGlxSize())); } setCamera(newCamera); } // recompute the scene size variables... recomputeSceneSize(); } //////////////////////////////////////////////////////////////////////// // // Description: // Recomputes the scene sizes... // // use: virtual public void SoXtViewer::recomputeSceneSize() // //////////////////////////////////////////////////////////////////////// { if (! sceneGraph || ! sceneRoot) { sceneSize = 0.0; return; } // Use assignment notation to disambiguate from expression (edison) SoGetBoundingBoxAction bboxAct = SoGetBoundingBoxAction(SbViewportRegion(getGlxSize())); bboxAct.apply(sceneRoot); SbBox3f bbox = bboxAct.getBoundingBox(); if (bbox.isEmpty()) { sceneSize = 0.0; return; } float x, y, z; bbox.getSize(x, y, z); sceneSize = (x > z) ? x : z; if (y > sceneSize) sceneSize = y; if (sceneSize <= 0.0) sceneSize = 0.0; } //////////////////////////////////////////////////////////////////////// // // Description: // Return the user supplied scene graph. // // use: public, virtual SoNode * SoXtViewer::getSceneGraph() // //////////////////////////////////////////////////////////////////////// { return sceneGraph; } //////////////////////////////////////////////////////////////////////// // // Description: // Sets the camera to use. // // Use: virtual public void SoXtViewer::setCamera(SoCamera *newCamera) // //////////////////////////////////////////////////////////////////////// { // check for trivual return if (camera == newCamera) return; // // detach everything that depended on the old camera // if ( camera != NULL ) { if (headlightFlag) { setHeadlight(FALSE); headlightFlag = TRUE; // can later be turned on } if (viewingFlag) { setViewing(FALSE); viewingFlag = TRUE; // can later be turned on } // remove the camera if we created one outside of the // scene graph. if (createdCamera && type == SoXtViewer::BROWSER) { if (sceneRoot->findChild(camera) >= 0) sceneRoot->removeChild(camera); createdCamera = FALSE; } camera->unref(); } camera = newCamera; // // attach everything that depends on the new camera // if ( camera != NULL) { camera->ref(); if (headlightFlag) { headlightFlag = FALSE; // enables the routine to be called setHeadlight(TRUE); } if (viewingFlag) { viewingFlag = FALSE; // enables the routine to be called setViewing(TRUE); } saveHomePosition(); } } //////////////////////////////////////////////////////////////////////// // // Description: // Set the camera type to create. // // Use: virtual public void SoXtViewer::setCameraType(SoType type) // //////////////////////////////////////////////////////////////////////// { if (type.isDerivedFrom(SoPerspectiveCamera::getClassTypeId()) || type.isDerivedFrom(SoOrthographicCamera::getClassTypeId())) cameraType = type; #ifdef DEBUG else SoDebugError::post("SoXtViewer::setCameraType", "unknown camera type!"); #endif } //////////////////////////////////////////////////////////////////////// // // Description: // See the whole scene from the camera // // Use: public void SoXtViewer::viewAll() // //////////////////////////////////////////////////////////////////////// { if ( camera != NULL ) camera->viewAll(sceneGraph,SbViewportRegion(getGlxSize())); } //////////////////////////////////////////////////////////////////////// // // Description: // Sets the viewing mode. // // Use: virtual public void SoXtViewer::setViewing(SbBool flag) // //////////////////////////////////////////////////////////////////////// { if (flag == viewingFlag) return; viewingFlag = flag; // if we are goind into viewing mode, then de-highlight any // currently highlighted nodes (since the object will never receive // any motion events). if (viewingFlag) { SoGLRenderAction *glAct = getGLRenderAction(); if (glAct) SoLocateHighlight::turnOffCurrentHighlight(glAct); } } //////////////////////////////////////////////////////////////////////// // // Description: // Enables/Disable the viewer cursor on the window. // // Use: virtual public void SoXtViewer::setCursorEnabled(SbBool flag) // //////////////////////////////////////////////////////////////////////// { cursorEnabledFlag = flag; } //////////////////////////////////////////////////////////////////////// // // Description: // Sets the auto clipping mode // // Use: public void SoXtViewer::setAutoClipping(SbBool flag) // //////////////////////////////////////////////////////////////////////// { if (autoClipFlag == flag) return; autoClipFlag = flag; // cause a redraw to correctly place the near and far plane now that // auto clipping is on. if (autoClipFlag) scheduleRedraw(); } //////////////////////////////////////////////////////////////////////// // // Description: // sets stereo mode // // Use: virtual public void SoXtViewer::setStereoViewing(SbBool flag) // //////////////////////////////////////////////////////////////////////// { if (flag == isStereoViewing()) return; // First, check to see if the OpenGL stereo visual can be created setStereoBuffer(flag); #ifdef __sgi // since OpenGL stereo failed, see if the SGI extension will work // by checking whether the X server supports it.... int first_event, first_error; if (flag != isStereoViewing() && XSGIStereoQueryExtension(getDisplay(), &first_event, &first_error)) { if (flag) { // make sure the current window will support stereo // ??? if we havn't been managed yet, just assume this visual // ??? will support stereo viewing (see bug if (! getNormalWindow()) useSGIStereoExt = TRUE; else if (XSGIQueryStereoMode(getDisplay(), getNormalWindow()) != X_STEREO_UNSUPPORTED) // stereo will be turned on in the rendering.... useSGIStereoExt = TRUE; // save the camera original aspect ratio since it will be updated // during rendering to strech the objects. We will restore it // when stereo if OFF. camStereoOrigAspect = camera->aspectRatio.getValue(); camStereoOrigVPMapping = camera->viewportMapping.getValue(); } else { // turn stereo off on the window useSGIStereoExt = FALSE; // clear the left/right buffers to prevent gost images from // the other view...(until the user resets the monitor with setmon) if (isRGBMode()) { SbColor color = getBackgroundColor(); glClearColor(color[0], color[1], color[2], 0); } else glClearIndex(getBackgroundIndex()); glDrawBuffer(GL_FRONT_AND_BACK); XSGISetStereoBuffer(getDisplay(), getNormalWindow(), STEREO_BUFFER_LEFT); XSync(getDisplay(), False); glClear(GL_COLOR_BUFFER_BIT); XSGISetStereoBuffer(getDisplay(), getNormalWindow(), STEREO_BUFFER_RIGHT); XSync(getDisplay(), False); glClear(GL_COLOR_BUFFER_BIT); glDrawBuffer( isDoubleBuffer() ? GL_BACK : GL_FRONT); // restore the camera original aspect ratio (saved above) camera->aspectRatio = camStereoOrigAspect; camera->viewportMapping = camStereoOrigVPMapping; } // now cause a redraw to see the affect since we havn't changed // the actual visual (unlike OpenGL) if (flag == isStereoViewing()) scheduleRedraw(); } #endif } //////////////////////////////////////////////////////////////////////// // // Description: // gets stereo mode // // Use: virtual public SbBool SoXtViewer::isStereoViewing() // //////////////////////////////////////////////////////////////////////// { #ifdef __sgi return (isStereoBuffer() || useSGIStereoExt); #else // done in SoXtGLWidget return isStereoBuffer(); #endif } //////////////////////////////////////////////////////////////////////// // // Description: // adds a directional light to the scene graph that is a headlight // positioned over the left shoulder of the camera. It has a sensor // on the camera, so that it always is pointing in the same direction // as the camera. The sensor is not delayed, so that it is "always" // accurate. // // Use: virtual public // void SoXtViewer::setHeadlight(SbBool insertFlag) // //////////////////////////////////////////////////////////////////////// { // check for trivial return if (camera == NULL || headlightFlag == insertFlag) { headlightFlag = insertFlag; return; } // // find the camera parent to insert/remove the headlight // SoSearchAction sa; if (insertFlag) sa.setNode(camera); else { sa.setNode(headlightGroup); sa.setSearchingAll(TRUE); // find under OFF switches for removal } sa.apply(sceneRoot); SoFullPath *fullPath = (SoFullPath *) sa.getPath(); if (!fullPath) { #if DEBUG SoDebugError::post("SoXtViewer::setHeadlight", insertFlag ? "ERROR: cannot find camera in graph" : "ERROR: cannot find headlight in graph"); #endif return; } SoGroup *parent = (SoGroup *) fullPath->getNodeFromTail(1); headlightFlag = insertFlag; // // inserts/remove the headlight group node // if (headlightFlag) { int camIndex; // check to make sure that the camera parent is not a switch node // (VRML camera viewpoints are kept under a switch node). Otherwise // we will insert the headlight right before the switch node. if (parent->isOfType(SoSwitch::getClassTypeId())) { SoNode *switchNode = parent; parent = (SoGroup *) fullPath->getNodeFromTail(2); camIndex = parent->findChild(switchNode); } else camIndex = parent->findChild(camera); // return if headlight is already there (this should be an error !) if (parent->findChild(headlightGroup) >= 0) return; // insert the light group right after the camera if (camIndex >= 0) parent->insertChild(headlightGroup, camIndex+1); } else { if (parent->findChild(headlightGroup) >= 0) parent->removeChild(headlightGroup); } } //////////////////////////////////////////////////////////////////////// // // Description: // Sets the drawing style. // // Use: virtual public void SoXtViewer::setDrawStyle( SoXtViewer::DrawType type, SoXtViewer::DrawStyle style) // //////////////////////////////////////////////////////////////////////// { // prevent us from picking a default draw style if the user already // has done so... checkForDrawStyle = FALSE; if (type == STILL) { if (stillDrawStyle == style) return; if (style == VIEW_SAME_AS_STILL) { #ifdef DEBUG SoDebugError::post("SoXtViewer::setDrawStyle", "illegal VIEW_SAME_AS_STILL draw style passed for STILL !"); #endif return; } stillDrawStyle = style; if (! interactiveFlag || interactiveDrawStyle == VIEW_SAME_AS_STILL || (interactiveDrawStyle == VIEW_NO_TEXTURE && style != VIEW_AS_IS)) setCurrentDrawStyle(style); else if (interactiveFlag && interactiveDrawStyle == VIEW_NO_TEXTURE && style == VIEW_AS_IS) setCurrentDrawStyle(interactiveDrawStyle); } else { // else it type == INTERACTIVE if (interactiveDrawStyle == style) return; interactiveDrawStyle = style; if (interactiveFlag) { if (style == VIEW_SAME_AS_STILL || (style == VIEW_NO_TEXTURE && stillDrawStyle != VIEW_AS_IS)) setCurrentDrawStyle(stillDrawStyle); else setCurrentDrawStyle(style); } } } //////////////////////////////////////////////////////////////////////// // // Description: // Sets the current drawing style. This only changes the nodes to // match what is passed (called from multiple places) and doesn't // affect the current state. // // Use: private void SoXtViewer::setCurrentDrawStyle(SoXtViewer::DrawStyle style) // //////////////////////////////////////////////////////////////////////// { if (style != VIEW_AS_IS) drawStyleSwitch->whichChild = SO_SWITCH_ALL; switch(style) { case VIEW_AS_IS: drawStyleSwitch->whichChild = SO_SWITCH_NONE; break; case VIEW_HIDDEN_LINE: // texture is always off under the switch node. // List only stuff common to both rendering passes // (the rest is done when rendering) drawStyleNode->style.setIgnored(FALSE); drawStyleNode->pointSize.setIgnored(TRUE); lightModelNode->model = SoLightModel::BASE_COLOR; lightModelNode->model.setIgnored(FALSE); complexityNode->type.setIgnored(TRUE); complexityNode->value.setIgnored(TRUE); break; case VIEW_NO_TEXTURE: case VIEW_LOW_COMPLEXITY: // texture is always off under the switch node drawStyleNode->style.setIgnored(TRUE); drawStyleNode->pointSize.setIgnored(TRUE); lightModelNode->model.setIgnored(TRUE); colorNode->orderedRGBA.setIgnored(TRUE); matBindingNode->value.setIgnored(TRUE); complexityNode->type.setIgnored(TRUE); complexityNode->value.setIgnored(style != VIEW_LOW_COMPLEXITY); break; case VIEW_LINE: case VIEW_LOW_RES_LINE: case VIEW_POINT: case VIEW_LOW_RES_POINT: // texture is always off under the switch node drawStyleNode->style = (style == VIEW_LINE || style == VIEW_LOW_RES_LINE) ? SoDrawStyle::LINES : SoDrawStyle::POINTS; drawStyleNode->style.setIgnored(FALSE); drawStyleNode->pointSize.setIgnored(style != VIEW_POINT && style != VIEW_LOW_RES_POINT); lightModelNode->model = SoLightModel::BASE_COLOR; lightModelNode->model.setIgnored(FALSE); colorNode->orderedRGBA.setIgnored(TRUE); matBindingNode->value.setIgnored(TRUE); // Force a lower complexity for the low res draw styles // ??? this only works if the object didn't have // ??? something lower in the first place... if (style == VIEW_LOW_RES_LINE || style == VIEW_LOW_RES_POINT) { complexityNode->type = SoComplexity::OBJECT_SPACE; complexityNode->type.setIgnored(FALSE); complexityNode->value.setIgnored(FALSE); } else { complexityNode->type.setIgnored(TRUE); complexityNode->value.setIgnored(TRUE); } break; case VIEW_BBOX: // texture is always off under the switch node drawStyleNode->style = SoDrawStyle::LINES; drawStyleNode->style.setIgnored(FALSE); drawStyleNode->pointSize.setIgnored(TRUE); lightModelNode->model = SoLightModel::BASE_COLOR; lightModelNode->model.setIgnored(FALSE); colorNode->orderedRGBA.setIgnored(TRUE); matBindingNode->value.setIgnored(TRUE); complexityNode->type = SoComplexity::BOUNDING_BOX; complexityNode->type.setIgnored(FALSE); complexityNode->value.setIgnored(TRUE); break; case VIEW_SAME_AS_STILL: #ifdef DEBUG SoDebugError::post("SoXtViewer::setCurrentDrawStyle", "VIEW_SAME_AS_STILL was passed !"); #endif break; } setZbufferState(); } //////////////////////////////////////////////////////////////////////// // // Description: // Gets the drawing style. // // Use: public SoXtViewer::DrawStyle SoXtViewer::getDrawStyle(SoXtViewer::DrawType type) // //////////////////////////////////////////////////////////////////////// { return (type == STILL ? stillDrawStyle : interactiveDrawStyle); } //////////////////////////////////////////////////////////////////////// // // Description: // redefine this routine to also correctly set the buffering type // // Use: virtual public void SoXtViewer::setNormalVisual(XVisualInfo *vis) // //////////////////////////////////////////////////////////////////////// { // call parent class SoXtRenderArea::setNormalVisual(vis); // now update the buffering type if (isDoubleBuffer()) setBufferingType(BUFFER_DOUBLE); else setBufferingType(BUFFER_SINGLE); } //////////////////////////////////////////////////////////////////////// // // Description: // redefine this routine from SoXtGLWidget to call the viewer // SoXtViewer::setBufferingType() method which is a superset. // // Use: virtual public // void SoXtViewer::setDoubleBuffer(SbBool flag) // //////////////////////////////////////////////////////////////////////// { setBufferingType(flag ? SoXtViewer::BUFFER_DOUBLE : SoXtViewer::BUFFER_SINGLE); } //////////////////////////////////////////////////////////////////////// // // Description: // Sets the buffering style. // // Use: virtual public void SoXtViewer::setBufferingType(SoXtViewer::BufferType type) // //////////////////////////////////////////////////////////////////////// { if (bufferType == type) return; // remove interactive callback if (bufferType == BUFFER_INTERACTIVE) { removeStartCallback(SoXtViewer::bufferStartCallback); removeFinishCallback(SoXtViewer::bufferFinishCallback); } bufferType = type; switch(bufferType) { case BUFFER_SINGLE: SoXtRenderArea::setDoubleBuffer(FALSE); break; case BUFFER_DOUBLE: SoXtRenderArea::setDoubleBuffer(TRUE); break; case BUFFER_INTERACTIVE: SoXtRenderArea::setDoubleBuffer(FALSE); addStartCallback(SoXtViewer::bufferStartCallback); addFinishCallback(SoXtViewer::bufferFinishCallback); break; } } //////////////////////////////////////////////////////////////////////// // // Description: // Externally set the viewer into/out off seek mode (default OFF). Actual // seeking will not happen until the viewer decides to (ex: mouse click). // // Note: setting the viewer out of seek mode while the camera is being // animated will stop the animation to the current location. // // use: virtual protected void SoXtViewer::setSeekMode(SbBool flag) // //////////////////////////////////////////////////////////////////////// { if (!isViewing()) return; // check if seek is being turned off while seek animation is happening if ( !flag && seekAnimationSensor->getAttachedField() ) { seekAnimationSensor->detach(); seekAnimationSensor->unschedule(); interactiveCountDec(); } seekModeFlag = flag; } //////////////////////////////////////////////////////////////////////// // // Description: // Adjust the camera clipping planes before a redraw. // // use: virtual protected. void SoXtViewer::actualRedraw() // //////////////////////////////////////////////////////////////////////// { if (isAutoClipping() && ! isStereoViewing()) adjustCameraClippingPlanes(); // update the headlight if necessary if (headlightFlag && camera) headlightRot->rotation.setValue(camera->orientation.getValue()); // make sure that we have a valid sceneSize value - but don't compute // a new sceneSize value for every redraw since the walking speed should // really be constant. if (sceneSize == 0.0) recomputeSceneSize(); // // Check to see if we are in stereo mode, if so draw the scene // twice with the camera offseted between the two views, else // do a simple redraw. // if ( isStereoViewing() && camera != NULL) { // Check the camera type, since stereo is different: // // Ortho camera: setereo is accomplished by simply rorating // the camera (around the point of interest) by 6 degree. // // Perspective camera: we translate the camera and rotate // them to look at the same point of interest (idealy we also would // make sure the plane of convergence is exactly the same for // both perspective views, unfortunatly we cannot do this with // the current symetric view volumes). // // save the camera original values to restore the camera after // both views are rendered. This means we will use this in between // left and right view for things like picking. SbVec3f camOrigPos = camera->position.getValue(); SbRotation camOrigRot = camera->orientation.getValue(); // get the camera focal point SbMatrix mx; mx = camOrigRot; SbVec3f forward( -mx[2][0], -mx[2][1], -mx[2][2]); float radius = camera->focalDistance.getValue(); SbVec3f center = camOrigPos + radius * forward; #ifdef __sgi // // if we are splitting the screen in half (loose vertical resolution) // then change the aspect ratio to squish the objects to make them // look square again through the stereo glasses. This is done for // every redraw since we need to manually update the aspect ourself. // if (useSGIStereoExt) { SbVec2s windowSize = getGlxSize(); camera->aspectRatio = 0.5 * windowSize[0] / (float) windowSize[1]; camera->viewportMapping = SoCamera::LEAVE_ALONE; } #endif // // change the camera for the LEFT eye view, and render // #ifdef __sgi if (useSGIStereoExt) { XSGISetStereoBuffer(getDisplay(), getNormalWindow(), STEREO_BUFFER_LEFT); XSync(getDisplay(), False); } else #endif glDrawBuffer( (isDoubleBuffer() && !drawToFrontBuffer) ? GL_BACK_LEFT : GL_FRONT_LEFT); // rotate the camera by - stereoOffset/2 degrees camera->orientation = SbRotation(SbVec3f(0, 1, 0), - stereoOffset * M_PI / 360.0) * camOrigRot; // reposition camera to look at pt of interest mx = camera->orientation.getValue(); forward.setValue( -mx[2][0], -mx[2][1], -mx[2][2]); camera->position = center - radius * forward; if (isAutoClipping()) adjustCameraClippingPlanes(); doRendering(); // // change the camera for the RIGHT eye view, and render // #ifdef __sgi if (useSGIStereoExt) { XSGISetStereoBuffer(getDisplay(), getNormalWindow(), STEREO_BUFFER_RIGHT); XSync(getDisplay(), False); } else #endif glDrawBuffer( (isDoubleBuffer() && !drawToFrontBuffer) ? GL_BACK_RIGHT : GL_FRONT_RIGHT); // rotate the camera by + stereoOffset/2 degrees camera->orientation = SbRotation(SbVec3f(0, 1, 0), stereoOffset * M_PI / 360.0) * camOrigRot; // reposition camera to look at pt of interest mx = camera->orientation.getValue(); forward.setValue( -mx[2][0], -mx[2][1], -mx[2][2]); camera->position = center - radius * forward; if (isAutoClipping()) adjustCameraClippingPlanes(); doRendering(); // // reset the camera original values now that we are done rendering // the stereo views (leave aspect ratio to do correct picking). camera->enableNotify(FALSE); // don't cause a redraw camera->position = camOrigPos; camera->orientation = camOrigRot; camera->enableNotify(TRUE); #ifdef __sgi if (! useSGIStereoExt) #endif // restore to draw to both buffer (viewer feedback) glDrawBuffer( (isDoubleBuffer() && !drawToFrontBuffer) ? GL_BACK : GL_FRONT); } // // else not stereo viewing, so do the regular rendering.... // else doRendering(); } //////////////////////////////////////////////////////////////////////// // // Description: // Do a multiple pass rendering if necessary, else simply call // SoXtRenderAre::actualRedraw() method. // // use: private void SoXtViewer::doRendering() // //////////////////////////////////////////////////////////////////////// { // // check if we need two pass rendering for hidden line rendering // SbBool drawHiddenLine = (stillDrawStyle == VIEW_HIDDEN_LINE && (! interactiveFlag || interactiveDrawStyle == VIEW_NO_TEXTURE || interactiveDrawStyle == VIEW_LOW_COMPLEXITY || interactiveDrawStyle == VIEW_SAME_AS_STILL)) || (interactiveFlag && interactiveDrawStyle == VIEW_HIDDEN_LINE); if (camera != NULL && drawHiddenLine) { // ??? what do we do about highlights ?? // the smaller the near clipping plane is relative to the far // plane, the smaller the zbuffer offset needs to be (because // the granularity will be pretty big). The closer the clipping // planes are relative to each other, the bigger the zbuffer offset // needs to be (because the zbuffer granularity will be small). // The scale factor was found empirically to work best with the // current settings of near/far. float zOffset = camera->nearDistance.getValue() / (40 * camera->farDistance.getValue()); // // render the first pass as solid, using the background color // for the object base color. // drawStyleNode->style = SoDrawStyle::FILLED; colorNode->orderedRGBA = getBackgroundColor().getPackedValue(); colorNode->orderedRGBA.setIgnored(FALSE); matBindingNode->value.setIgnored(FALSE); // ??? this should match the SoXtRenderArea::actualRedraw() // ??? method exactly (apart for not clearing the z-buffer) glDepthRange(zOffset, 1); // enable wireframe to be draw on top getSceneManager()->render(isClearBeforeRender(), TRUE); // // render the second pass as wireframe // (the first pass rendered the objects solid with base color // set to the background color to set the zbuffer values) // drawStyleNode->style = SoDrawStyle::LINES; colorNode->orderedRGBA.setIgnored(TRUE); matBindingNode->value.setIgnored(TRUE); // ??? this should match the SoXtRenderArea::actualRedraw() // ??? method exactly (apart for not clearing the color and z-buffer) glDepthRange(0,1-zOffset); // enable wireframe to be draw on top getSceneManager()->render(FALSE, FALSE); glDepthRange(0, 1); // restore the range } else { // ??? this should match the SoXtRenderArea::actualRedraw() // ??? method exactly (apart for not clearing the z-buffer) getSceneManager()->render(isClearBeforeRender(), ! isZbufferOff()); } } //////////////////////////////////////////////////////////////////////// // // Description: // This is called when we are first mapped - this will check the // configuration of the machine we are running on and decide on what // draw style we should pick. // // use: virtual protected void SoXtViewer::afterRealizeHook() // //////////////////////////////////////////////////////////////////////// { // call the base class SoXtRenderArea::afterRealizeHook(); // only do this once and only IF the user hasn't overwritten this if (! checkForDrawStyle) return; checkForDrawStyle = FALSE; SbBool useTexture = TRUE; // true by default (and for new machines) const char *renderer = (const char *) glGetString(GL_RENDERER); // // On the following SGI machines we don't want texture rendering // to be turned on by default (for speed reason) - machines not listed // below are assumed to be fast enough to leave VIEW_AS_IS draw style. // if (strncmp((const char *)glGetString(GL_VENDOR), "SGI", 3) == 0) { // Indy and XL if (useTexture && strncmp(renderer, "NEWPORT", 7) == 0) useTexture = FALSE; // Personal Iris if (useTexture && strncmp(renderer, "GR1", 3) == 0) useTexture = FALSE; // VGX and VGXT if (useTexture && strncmp(renderer, "VGX", 3) == 0) useTexture = FALSE; // Indigo Entry if (useTexture && strncmp(renderer, "LG1", 3) == 0 || strncmp(renderer, "LIGHT", 5) == 0) useTexture = FALSE; // XS, XZ, Elan, and Extreme if (useTexture && ( strncmp(renderer, "GR2", 3) == 0 || strncmp(renderer, "GR3", 3) == 0 || strncmp(renderer, "GU1", 3) == 0)) useTexture = FALSE; } if (! useTexture) setDrawStyle(SoXtViewer::INTERACTIVE, SoXtViewer::VIEW_NO_TEXTURE); } //////////////////////////////////////////////////////////////////////// // // Description: // Returns TRUE if the zbuffer should be off (based on the viewer // draw styles). // // Use: private SbBool SoXtViewer::isZbufferOff() // //////////////////////////////////////////////////////////////////////// { DrawStyle style = (interactiveFlag ? interactiveDrawStyle : stillDrawStyle); if (interactiveFlag && interactiveDrawStyle == VIEW_SAME_AS_STILL) style = stillDrawStyle; // for these draw styles, turn the zbuffer off return (style == VIEW_LOW_RES_LINE || style == VIEW_LOW_RES_POINT || style == VIEW_BBOX); } //////////////////////////////////////////////////////////////////////// // // Description: // Sets the zbuffer state on the current window. This is called whenever // the windows changes (called by SoXtGLWidget::widgetChanged()) or when // the viewer draw style changes. // // Use: private void SoXtViewer::setZbufferState() // //////////////////////////////////////////////////////////////////////// { if (getNormalWindow() == 0) return; glXMakeCurrent(getDisplay(), getNormalWindow(), getNormalContext()); if (isZbufferOff()) glDisable(GL_DEPTH_TEST); else glEnable(GL_DEPTH_TEST); } //////////////////////////////////////////////////////////////////////// // // Description: // saves the camera values for later restore. // // Use: virtual public void SoXtViewer::saveHomePosition() // //////////////////////////////////////////////////////////////////////// { if (camera == NULL) return; origPosition = camera->position.getValue(); origOrientation = camera->orientation.getValue(); origNearDistance = camera->nearDistance.getValue(); origFarDistance = camera->farDistance.getValue(); origFocalDistance = camera->focalDistance.getValue(); // save camera height (changed by zooming) if (camera->isOfType(SoPerspectiveCamera::getClassTypeId())) origHeight = ((SoPerspectiveCamera *)camera)->heightAngle.getValue(); else if (camera->isOfType(SoOrthographicCamera::getClassTypeId())) origHeight = ((SoOrthographicCamera *)camera)->height.getValue(); } //////////////////////////////////////////////////////////////////////// // // Description: // reset the camera to it's saved values. // // Use: virtual public void SoXtViewer::resetToHomePosition() // //////////////////////////////////////////////////////////////////////// { if (camera == NULL) return; camera->position = origPosition; camera->orientation = origOrientation; camera->nearDistance = origNearDistance; camera->farDistance = origFarDistance; camera->focalDistance = origFocalDistance; // restore camera height (changed by zooming) if (camera->isOfType(SoPerspectiveCamera::getClassTypeId())) ((SoPerspectiveCamera *)camera)->heightAngle.setValue(origHeight); else if (camera->isOfType(SoOrthographicCamera::getClassTypeId())) ((SoOrthographicCamera *)camera)->height.setValue(origHeight); } //////////////////////////////////////////////////////////////////////// // // Description: _ HACK BUG WORKAOUND _ // // This updates the event own internal knowledge about which buttons and // modifiers keys are down based of the event itself. // // ??? for some reason Xt does NOT update the event button and modifier // ??? state until AFTER the event has been sent to the widget. Knowing // ??? which mouse buttons and which modifiers keys are down when receiving // ??? ANY event (uncluding the event itself) is VERY useful and make // ??? the viewer mode chaging code MUCH simpler. // // Use: private - BUG WORKAROUND static void updateEventState(XAnyEvent *xe) // //////////////////////////////////////////////////////////////////////// { XButtonEvent *be; XKeyEvent *ke; KeySym keysym; switch(xe->type) { case ButtonPress: be = (XButtonEvent *)xe; if (be->button == Button1) be->state = (be->state | Button1Mask); else if (be->button == Button2) be->state = (be->state | Button2Mask); break; case ButtonRelease: be = (XButtonEvent *)xe; if (be->button == Button1) be->state = (be->state & ~Button1Mask); else if (be->button == Button2) be->state = (be->state & ~Button2Mask); break; case KeyPress: case KeyRelease: ke = (XKeyEvent *)xe; keysym = XLookupKeysym(ke, 0); if (keysym == XK_Control_L || keysym == XK_Control_R) { if (xe->type == KeyPress) ke->state = (ke->state | ControlMask); else ke->state = (ke->state & ~ControlMask); } break; } } //////////////////////////////////////////////////////////////////////// // // Description: // Process a common set of events which are shared accross all // viewers. Returning TRUE if the event was processed. // // Use: protected SbBool SoXtViewer::processCommonEvents(XAnyEvent *xe) // //////////////////////////////////////////////////////////////////////// { KeySym keysym; // check if the application wants to handle the event itself // instead of giving it to the viewer. This can be used to disable // some simple viewer functionality (like the Arrow keys or Esc key). // ??? this is a simple work around for bug #113991 - Xt translation // ??? tables would be better than dealing with events directly. if (SoXtRenderArea::invokeAppCB(xe)) return TRUE; // // check for special key which turns viewing on/off // if (xe->type == KeyPress) { XKeyEvent *ke = (XKeyEvent *)xe; keysym = XLookupKeysym(ke, 0); if (keysym == XK_Escape) { setViewing( !isViewing() ); // toggle the viewing mode... return TRUE; } else if (!isViewing() && (keysym == XK_Alt_L || keysym == XK_Alt_R) && !(ke->state & Button1Mask || ke->state & Button2Mask)) { // Alt-key goes from PICK to VIEW if // 1] we are not in VIEW mode already // 2] no mouse buttons are pressed // altSwitchBack = TRUE; // later return back setViewing(TRUE); return TRUE; } } else if (xe->type == KeyRelease) { keysym = XLookupKeysym((XKeyEvent *)xe, 0); if (altSwitchBack && (keysym == XK_Alt_L || keysym == XK_Alt_R)) { // if Alt-key, then return to PICK (if we had switched) setViewing(FALSE); altSwitchBack = FALSE; // clear the flag return TRUE; } } else if (xe->type == EnterNotify) { XCrossingEvent *ce = (XCrossingEvent *)xe; // // because the application might use Alt-key for motif menu // accelerators we might not receive a key-up event, so make sure // to reset any Alt mode (temporary viewing) when the mouse re-enters // the window. // if (! isViewing() && ce->state & Mod1Mask) { altSwitchBack = TRUE; // later return back setViewing(TRUE); } else if (altSwitchBack && !(ce->state & Mod1Mask)) { setViewing(FALSE); altSwitchBack = FALSE; // clear the flag } } // send the event to the scene graph if viewing is off if ( !isViewing() ) { // prevent renderArea from sending the event to the app twice // since it is done above... SoXtRenderAreaEventCB *saveFunc = appEventHandler; appEventHandler = NULL; SoXtRenderArea::processEvent(xe); appEventHandler = saveFunc; return TRUE; } // if no camera discard events if (camera == NULL) return TRUE; SbBool handled = TRUE; // ??? workaround what seem to be an Xt bug... updateEventState(xe); switch(xe->type) { case KeyPress: switch ( keysym ) { case XK_Home: resetToHomePosition(); break; case XK_s: setSeekMode( !isSeekMode() ); // ??? this is kind of a hack, but it is needed // ??? until a better solution is found if ( isSeekMode() && interactiveCount != 0 ) { interactiveCount = 0; finishCBList->invokeCallbacks(this); } break; case XK_Left: case XK_Up: case XK_Right: case XK_Down: arrowKeyPressed(keysym); break; default: handled = FALSE; break; } break; default: handled = FALSE; break; } return handled; } //////////////////////////////////////////////////////////////////////// // // Description: // Increment the intercative viewing counter. // // Use: protected. void SoXtViewer::interactiveCountInc() // //////////////////////////////////////////////////////////////////////// { interactiveCount++; if (interactiveCount == 1) startCBList->invokeCallbacks(this); } //////////////////////////////////////////////////////////////////////// // // Description: // Decrement the intercative viewing counter. // // Use: protected. void SoXtViewer::interactiveCountDec() // //////////////////////////////////////////////////////////////////////// { if (interactiveCount > 0) { interactiveCount--; if (interactiveCount == 0) finishCBList->invokeCallbacks(this); } } //////////////////////////////////////////////////////////////////////// // // Description: // This routine is used by subclasses to initiate the seek animation. Given a // screen mouse location, this routine will return the picked point // and the normal at that point. It will also schedule the sensor to animate // if necessary. The routine retuns TRUE if something got picked... // // Note: if detailSeek is on, the point and normal correspond to the exact // 3D location under the cursor. // if detailSeek if off, the object bbox center and the camera // orientation are instead returned. // // Use: protected. SbBool SoXtViewer::seekToPoint(const SbVec2s &mouseLocation) // //////////////////////////////////////////////////////////////////////// { if (camera == NULL) { setSeekMode(FALSE); return FALSE; } // do the picking // Use assignment notation to disambiguate from expression (edison) SoRayPickAction pick = SoRayPickAction(SbViewportRegion(getGlxSize())); pick.setPoint(mouseLocation); pick.setRadius(1.0); pick.setPickAll(FALSE); // pick only the closest object pick.apply(sceneRoot); // makes sure something got picked SoPickedPoint *pp = pick.getPickedPoint(); if ( pp == NULL ) { setSeekMode(FALSE); return FALSE; } // // Get picked point and normal if detailtSeek // if (detailSeekFlag) { seekPoint = pp->getPoint(); seekNormal = pp->getNormal(); // check to make sure normal points torward the camera, else // flip the normal around if ( seekNormal.dot(camera->position.getValue() - seekPoint) < 0 ) seekNormal.negate(); } // // else get object bounding box as the seek point and the camera // orientation as the normal. // else { // get center of object's bounding box // Use assignment notation to disambiguate from expression (edison) SoGetBoundingBoxAction bba = SoGetBoundingBoxAction(SbViewportRegion(getGlxSize())); bba.apply(pp->getPath()); SbBox3f bbox = bba.getBoundingBox(); seekPoint = bbox.getCenter(); // keep the camera oriented the same way SbMatrix mx; mx = camera->orientation.getValue(); seekNormal.setValue(mx[2][0], mx[2][1], mx[2][2]); } // // now check if animation sensor needs to be scheduled // computeSeekVariables = TRUE; if (seekAnimTime == 0) { // jump to new location, no animation needed interpolateSeekAnimation(1.0); } else { // schedule sensor and call viewer start callbacks if ( ! seekAnimationSensor->getAttachedField() ) { seekAnimationSensor->attach(viewerRealTime); seekAnimationSensor->schedule(); interactiveCountInc(); } seekStartTime = viewerRealTime->getValue(); } return TRUE; // successfull } //////////////////////////////////////////////////////////////////////// // // Description: // computes what the final camera seek orientation should be. // // Use: virtual protected void SoXtViewer::computeSeekFinalOrientation() // //////////////////////////////////////////////////////////////////////// { SbMatrix mx; SbVec3f viewVector; // find the camera final orientation if ( isDetailSeek() ) { // get the camera new orientation mx = camera->orientation.getValue(); viewVector.setValue(-mx[2][0], -mx[2][1], -mx[2][2]); SbRotation changeOrient; changeOrient.setValue(viewVector, seekPoint - camera->position.getValue()); newCamOrientation = camera->orientation.getValue() * changeOrient; } else newCamOrientation = camera->orientation.getValue(); } //////////////////////////////////////////////////////////////////////// // // Description: // // Use: virtual protected void SoXtViewer::interpolateSeekAnimation(float t) // //////////////////////////////////////////////////////////////////////// { if (camera == NULL) return; // // check if camera new and old position/orientation have already // been computed. // if (computeSeekVariables) { SbMatrix mx; SbVec3f viewVector; // save camera starting point oldCamPosition = camera->position.getValue(); oldCamOrientation = camera->orientation.getValue(); // compute the distance the camera will be from the seek point // and update the camera focalDistance. float dist; if ( seekDistAsPercentage ) { SbVec3f seekVec(seekPoint - camera->position.getValue()); dist = seekVec.length() * (seekDistance / 100.0); } else dist = seekDistance; camera->focalDistance = dist; // let subclasses have a chance to redefine what the // camera final orientation should be. computeSeekFinalOrientation(); // find the camera final position based on orientation and distance mx = newCamOrientation; viewVector.setValue(-mx[2][0], -mx[2][1], -mx[2][2]); newCamPosition = seekPoint - dist * viewVector; computeSeekVariables = FALSE; } // // Now position the camera according to the animation time // // use and ease-in ease-out approach float cos_t = 0.5 - 0.5 * fcos(t * M_PI); // get camera new rotation camera->orientation = SbRotation::slerp(oldCamOrientation, newCamOrientation, cos_t); // get camera new position camera->position = oldCamPosition + (newCamPosition - oldCamPosition) * cos_t; } //////////////////////////////////////////////////////////////////////// // // Description: // Adjust the camera clipping planes based on the scene bounding // box. (called before every redraws) // // use: virtual protected void SoXtViewer::adjustCameraClippingPlanes() // //////////////////////////////////////////////////////////////////////// { if (camera == NULL) return; // get the scene bounding box autoClipBboxAction->setViewportRegion(SbViewportRegion(getGlxSize())); autoClipBboxAction->apply(sceneRoot); SbXfBox3f xfbbox = autoClipBboxAction->getXfBoundingBox(); // get camera transformation and apply to xfbbox // to align the bounding box to camera space. // This will enable us to simply use the z values of the // transformed bbox for near and far plane values. SbMatrix mx; mx.setTranslate(- camera->position.getValue()); xfbbox.transform(mx); mx = camera->orientation.getValue().inverse(); xfbbox.transform(mx); // get screen align bbox and figure the near and far plane values SbBox3f bbox = xfbbox.project(); // take negative value and opposite to what one might think // because the camera points down the -Z axis float far = - bbox.getMin()[2]; float near = - bbox.getMax()[2]; // scene is behind the camera so don't change the planes if (far < 0) return; // check for minimum near plane value (Value will be negative // when the camera is inside the bounding box). // Note: there needs to be a minimum near value for perspective // camera because of zbuffer resolution problem (plus the values // has to be positive). There is no such restriction for // an Orthographic camera (you can see behind you). if (! camera->isOfType(SoOrthographicCamera::getClassTypeId())) { if (near < (minimumNearPlane * far)) near = minimumNearPlane * far; } // give the near and far distances a little bit of slack in case // the object lies against the bounding box, otherwise the object // will be poping in and out of view. // (example: a cube is the same as it's bbox) near *= 0.999; far *= 1.001; // finally assign camera plane values if (camera->nearDistance.getValue() != near) camera->nearDistance = near; if (camera->farDistance.getValue() != far) camera->farDistance = far; } //////////////////////////////////////////////////////////////////////// // // Copy the camera onto the clipboard. // // Use: private // void SoXtViewer::copyView(Time eventTime) // //////////////////////////////////////////////////////////////////////// { if (camera == NULL) return; if (clipboard == NULL) clipboard = new SoXtClipboard(getWidget()); clipboard->copy(camera, eventTime); } //////////////////////////////////////////////////////////////////////// // // Retrieve the selection from the X server and paste it when it // arrives (in our pasteDone callback). // // Use: private // void SoXtViewer::pasteView(Time eventTime) // //////////////////////////////////////////////////////////////////////// { if (clipboard == NULL) clipboard = new SoXtClipboard(getWidget()); clipboard->paste(eventTime, SoXtViewer::pasteDoneCB, this); } //////////////////////////////////////////////////////////////////////// // // This is called by Xt when the data is ready to be pasted. // // Use: static, private // void SoXtViewer::pasteDoneCB(void *userData, SoPathList *pathList) // //////////////////////////////////////////////////////////////////////// { SoCamera *newCamera = NULL; // search for a camera in the paste data for (int i = 0; i < pathList->getLength(); i++) { SoFullPath *fullP = (SoFullPath *) (*pathList)[i]; if (fullP->getTail()->isOfType(SoCamera::getClassTypeId())) { newCamera = (SoCamera *) fullP->getTail(); break; } } if (newCamera != NULL) ((SoXtViewer *) userData)->changeCameraValues(newCamera); // We delete the callback data when done with it. delete pathList; } //////////////////////////////////////////////////////////////////////// // // Change the values of our camera to newCamera. //??? animate from old values to new? // // Use: virtual, protected // void SoXtViewer::changeCameraValues(SoCamera *newCamera) // //////////////////////////////////////////////////////////////////////// { if (camera == NULL) return; // only paste cameras of the same type if (camera->getTypeId() != newCamera->getTypeId()) return; // give our camera the values of the new camera camera->position = newCamera->position; camera->orientation = newCamera->orientation; camera->nearDistance = newCamera->nearDistance; camera->farDistance = newCamera->farDistance; camera->focalDistance = newCamera->focalDistance; // get the height or heightAngle if (camera->isOfType(SoPerspectiveCamera::getClassTypeId())) ((SoPerspectiveCamera *)camera)->heightAngle = ((SoPerspectiveCamera *)newCamera)->heightAngle; else ((SoOrthographicCamera *)camera)->height = ((SoOrthographicCamera *)newCamera)->height; } //////////////////////////////////////////////////////////////////////// // // Description: // Toggles the current camera type (perspective <--> orthographic) // // Use: virtual protected // void SoXtViewer::toggleCameraType() // //////////////////////////////////////////////////////////////////////// { if (camera == NULL) return; // create the camera of the opposite kind and compute the wanted height // or heightAngle of the new camera. SoCamera *newCam; if (camera->isOfType(SoPerspectiveCamera::getClassTypeId())) { float angle = ((SoPerspectiveCamera *)camera)->heightAngle.getValue(); float height = camera->focalDistance.getValue() * ftan(angle/2); newCam = new SoOrthographicCamera; ((SoOrthographicCamera *)newCam)->height = 2 * height; } else if (camera->isOfType(SoOrthographicCamera::getClassTypeId())) { float height = ((SoOrthographicCamera *)camera)->height.getValue() / 2; float angle = fatan(height / camera->focalDistance.getValue()); newCam = new SoPerspectiveCamera; ((SoPerspectiveCamera *)newCam)->heightAngle = 2 * angle; } else { #ifdef DEBUG SoDebugError::post("SoXtViewer::toggleCameraType", "unknown camera type!"); #endif return; } newCam->ref(); // copy common stuff from the old to the new camera newCam->viewportMapping = camera->viewportMapping.getValue(); newCam->position = camera->position.getValue(); newCam->orientation = camera->orientation.getValue(); newCam->aspectRatio = camera->aspectRatio.getValue(); newCam->focalDistance = camera->focalDistance.getValue(); // search for the old camera and replace it by the new camera SoSearchAction sa; sa.setNode(camera); sa.apply(sceneRoot); SoFullPath *fullCamPath = (SoFullPath *) sa.getPath(); if (fullCamPath) { SoGroup *parent = (SoGroup *)fullCamPath->getNode(fullCamPath->getLength() - 2); parent->insertChild(newCam, parent->findChild(camera)); SoCamera *oldCam = camera; setCamera(newCam); // remove the old camera if it is still there (setCamera() might // have removed it) and set the created flag to true (for next time) if (parent->findChild(oldCam) >= 0) parent->removeChild(oldCam); createdCamera = TRUE; } #ifdef DEBUG else SoDebugError::post("SoXtViewer::toggleCameraType", "camera not found!"); #endif newCam->unref(); } //////////////////////////////////////////////////////////////////////// // // Description: // Called by the processCommonEvent routine whenever the arrow keys // are pressed. Translate the camera in the viewing plane in the arrow // direction half a screen at a time. // // Use: private // void SoXtViewer::arrowKeyPressed(KeySym key) // //////////////////////////////////////////////////////////////////////// { if (camera == NULL) return; // get the camera near plane height value float dist; if (camera->isOfType(SoPerspectiveCamera::getClassTypeId())) { float angle = ((SoPerspectiveCamera *)camera)->heightAngle.getValue(); float length = camera->nearDistance.getValue(); dist = length * ftan(angle); } else if (camera->isOfType(SoOrthographicCamera::getClassTypeId())) dist = ((SoOrthographicCamera *)camera)->height.getValue(); dist /= 2.0; // get camera right/left/up/down direction SbMatrix mx; mx = camera->orientation.getValue(); SbVec3f dir; switch(key) { case XK_Up: dir.setValue(mx[1][0], mx[1][1], mx[1][2]); break; case XK_Down: dir.setValue(-mx[1][0], -mx[1][1], -mx[1][2]); break; case XK_Right: dir.setValue(mx[0][0], mx[0][1], mx[0][2]); dist *= camera->aspectRatio.getValue(); break; case XK_Left: dir.setValue(-mx[0][0], -mx[0][1], -mx[0][2]); dist *= camera->aspectRatio.getValue(); break; } // finally reposition the camera camera->position = camera->position.getValue() + dist * dir; } // //////////////////////////////////////////////////////////////////////// // static callbacks stubs //////////////////////////////////////////////////////////////////////// // void SoXtViewer::bufferStartCallback(void *, SoXtViewer *v) { v->SoXtRenderArea::setDoubleBuffer(TRUE); } void SoXtViewer::bufferFinishCallback(void *, SoXtViewer *v) { v->SoXtRenderArea::setDoubleBuffer(FALSE); } void SoXtViewer::drawStyleStartCallback(void *, SoXtViewer *v) { v->interactiveFlag = TRUE; // must happen first // if still and move draw styles are the same, return... if (v->interactiveDrawStyle == VIEW_SAME_AS_STILL || v->interactiveDrawStyle == v->stillDrawStyle) return; // if we have "MOVE NO TEXTURE", then we have nothing // to do if we have a current draw style (since they all have // texturing turned off in the first place). if (v->interactiveDrawStyle == VIEW_NO_TEXTURE && v->stillDrawStyle != VIEW_AS_IS) return; v->setCurrentDrawStyle(v->interactiveDrawStyle); } void SoXtViewer::drawStyleFinishCallback(void *, SoXtViewer *v) { v->interactiveFlag = FALSE; // must happen first // if still and move draw styles are the same, return... if (v->interactiveDrawStyle == VIEW_SAME_AS_STILL || v->interactiveDrawStyle == v->stillDrawStyle) return; // if we have "MOVE NO TEXTURE", then we have nothing // to do if we have a current draw style (since they all have // texturing turned off in the first place). if (v->interactiveDrawStyle == VIEW_NO_TEXTURE && v->stillDrawStyle != VIEW_AS_IS) return; v->setCurrentDrawStyle(v->stillDrawStyle); } //////////////////////////////////////////////////////////////////////// // // Called whenever the seek animation sensor fires. Finds the amount // of time since we started the seek and call the subclasses routine // to do the correct interpolation. // // Use: static private // void SoXtViewer::seekAnimationSensorCB(void *p, SoSensor *) // //////////////////////////////////////////////////////////////////////// { SoXtViewer *v = (SoXtViewer *)p; // get the time difference SbTime time = viewerRealTime->getValue(); float sec = float((time - v->seekStartTime).getValue()); if (sec == 0.0) sec = 1.0/72.0; // at least one frame (needed for first call) float t = (sec / v->seekAnimTime); // check to make sure the values are correctly clipped if (t > 1.0) t = 1.0; else if ((1.0 - t) < 0.0001) t = 1.0; // this will be the last one... // call subclasses to interpolate the animation v->interpolateSeekAnimation(t); // stops seek if this was the last interval if (t == 1.0) v->setSeekMode(FALSE); } // //////////////////////////////////////////////////////////////////////// // viewer feedback convenience routines //////////////////////////////////////////////////////////////////////// // /* * Defines */ // color used in feedback #define DARK_COLOR glColor3ub(90, 90, 90) #define LIGHT_COLOR glColor3ub(230, 230, 230) #define LINE_THIN 3 // line thickness used in feedback #define LINE_THICK (LINE_THIN + 2) #define CROSS 8 // size of cross hair at screen center for Roll #define RADIUS 15 // radius of center circle (in pix) for Roll #define ANGLE_LEN 14 // angular size in degrees of Roll anchor /* * Globals */ #define ARROW_SIZE 6.0 // size in pix of arrow head // anchor arrow head description static float arrow_data[3][3] = { -ARROW_SIZE, 0, 0, 0, 2*ARROW_SIZE, 0, ARROW_SIZE, 0, 0 }; /* * Macros */ #define DRAW_ARROW_MACRO \ DARK_COLOR; \ glBegin(GL_POLYGON); \ glVertex3fv(arrow_data[0]); \ glVertex3fv(arrow_data[1]); \ glVertex3fv(arrow_data[2]); \ glEnd(); \ LIGHT_COLOR; \ glLineWidth(1); \ glBegin(GL_LINE_LOOP); \ glVertex3fv(arrow_data[0]); \ glVertex3fv(arrow_data[1]); \ glVertex3fv(arrow_data[2]); \ glEnd(); //////////////////////////////////////////////////////////////////////// // // Description: // Sets the default ortho projection when doing viewer feedback. The // zbuffer/lighting is automatically turned off. // // Use: static protected // void SoXtViewer::setFeedbackOrthoProjection(const SbVec2s &size) // //////////////////////////////////////////////////////////////////////// { // push the gl state to revert it back later.... glPushAttrib(GL_DEPTH_BUFFER_BIT | GL_LIGHTING_BIT | GL_LINE_BIT); // ??? should we worry about restoring this matrix later ? glViewport(0, 0, size[0], size[1]); glMatrixMode(GL_PROJECTION); glLoadIdentity(); glOrtho(0, size[0], 0, size[1], -1, 1); // disable zbuffer and lighting.... glDisable(GL_DEPTH_TEST); glDisable(GL_LIGHTING); } //////////////////////////////////////////////////////////////////////// // // Description: // restores the state that was changed when setFeedbackOrthoProjection() // is called. // // Use: static protected // void SoXtViewer::restoreGLStateAfterFeedback() // //////////////////////////////////////////////////////////////////////// { // restore the gl state that were saved in setFeedbackOrthoProjection() glPopAttrib(); } //////////////////////////////////////////////////////////////////////// // // Description: // Draws a simple 2 colored cross at the given location. // // Use: static protected // void SoXtViewer::drawViewerCrossFeedback(SbVec2s loc) // //////////////////////////////////////////////////////////////////////// { LIGHT_COLOR; glLineWidth(4); glBegin(GL_LINES); glVertex2s(loc[0]-CROSS, loc[1]); glVertex2s(loc[0]+CROSS, loc[1]); glVertex2s(loc[0], loc[1]-CROSS); glVertex2s(loc[0], loc[1]+CROSS); glEnd(); DARK_COLOR; glLineWidth(2); glBegin(GL_LINES); glVertex2s(loc[0]-CROSS+1, loc[1]); glVertex2s(loc[0]+CROSS-1, loc[1]); glVertex2s(loc[0], loc[1]-CROSS+1); glVertex2s(loc[0], loc[1]+CROSS-1); glEnd(); } //////////////////////////////////////////////////////////////////////// // // Description: // draws the anchor roll feedback given the point of rotation and the // current mouse location. // // Use: static protected // void SoXtViewer::drawViewerRollFeedback(SbVec2s center, SbVec2s loc) // //////////////////////////////////////////////////////////////////////// { // get angle and distance of mouse from center of rotation float ang, dist; float vx = loc[0] - center[0]; float vy = loc[1] - center[1]; if (vx==0 && vy==0) { ang = 0; dist = 3; // minimum size (given the circle thickness) } else { ang = atan2(vy, vx) * 180.0 / M_PI; dist = fsqrt(vx*vx + vy*vy); if (dist < 3) dist = 3; // minimum size (given the circle thickness) } float cirAng = -ang + 90; // gluPartialDisk() angle is REALLY backward !! static GLUquadricObj *quad = NULL; if (! quad) quad = gluNewQuadric(); // draw all of the circles (first inner, then outer) glTranslatef(center[0], center[1], 0); LIGHT_COLOR; gluDisk(quad, RADIUS, RADIUS+LINE_THICK, 20, 2); gluPartialDisk(quad, dist-2, dist+LINE_THICK-2, 20, 2, cirAng - ANGLE_LEN, 2 * ANGLE_LEN); DARK_COLOR; gluDisk(quad, RADIUS+1, RADIUS+LINE_THICK-1, 20, 2); gluPartialDisk(quad, dist-1, dist+LINE_THICK-3, 20, 2, cirAng - ANGLE_LEN, 2 * ANGLE_LEN); glTranslatef(-center[0], -center[1], 0); // undo the translation // draw connecting line from center to outer circle glLineWidth(LINE_THICK); LIGHT_COLOR; glBegin(GL_LINES); glVertex2s(center[0], center[1]); glVertex2s(loc[0], loc[1]); glEnd(); glLineWidth(LINE_THIN); DARK_COLOR; glBegin(GL_LINES); glVertex2s(center[0], center[1]); glVertex2s(loc[0], loc[1]); glEnd(); // draw the CCW arrow glPushMatrix(); glTranslatef(center[0], center[1], 0); glRotatef(ang+ANGLE_LEN, 0, 0, 1); glTranslatef(dist, 0, 0); DRAW_ARROW_MACRO glPopMatrix(); // draw the CW arrow glPushMatrix(); glTranslatef(center[0], center[1], 0); glRotatef(ang-ANGLE_LEN, 0, 0, 1); glTranslatef(dist, 0, 0); glScalef(1, -1, 1); DRAW_ARROW_MACRO glPopMatrix(); }