// @configure_input@ /**************************************************************************\ * * This file is part of the Coin 3D visualization library. * Copyright (C) by Kongsberg Oil & Gas Technologies. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * ("GPL") version 2 as published by the Free Software Foundation. * See the file LICENSE.GPL at the root directory of this source * distribution for additional information about the GNU GPL. * * For using Coin with software that can not be combined with the GNU * GPL, and for taking advantage of the additional benefits of our * support services, please contact Kongsberg Oil & Gas Technologies * about acquiring a Coin Professional Edition License. * * See http://www.coin3d.org/ for more information. * * Kongsberg Oil & Gas Technologies, Bygdoy Alle 5, 0257 Oslo, NORWAY. * http://www.sim.no/ sales@sim.no coin-support@coin3d.org * \**************************************************************************/ // NOTE: The So@Gui@Viewer.cpp sourcecode file is completely // autogenerated from "templatized" source code. // ************************************************************************* /*! \class So@Gui@Viewer Inventor/@Gui@/viewers/So@Gui@Viewer.h \brief The So@Gui@Viewer class is the top level base viewer class. \ingroup components viewers This is an abstract class, which adds the following features to it's So@Gui@RenderArea superclass: convenient methods for camera handling, an automatic headlight configuration. As for the camera handling: when setting a new scenegraph for the viewer, the scenegraph will automatically be scanned for a node derived from SoCamera. If not found, the viewer will itself set up a camera for the scene. The camera can then be conveniently controlled by the application programmers in many aspects: Note that there is no dragger or manipulator attached to the scene camera. The camera transform manipulation is calculated in a more direct manner in the non-abstract viewer classes inheriting So@Gui@Viewer by reading mouse and keyboard events and interpreting how these should influence the camera. The calculations results in new values for SoCamera::position, SoCamera::orientation, and the other SoCamera field values for the camera designated to be the viewer viewpoint camera. These values are then inserted directly into the viewer's SoCamera node. See e.g. the source code for So@Gui@ExaminerViewer::processSoEvent() for the details. The So@Gui@Viewer class automatically adds a headlight to the scene, which will always point in the approximate same direction as the current viewer camera, thereby securing that the scene geometry is always lighted and visible. (If you don't want the constant headlight, but rather want to light the scene on your own, this behavior can be turned off with So@Gui@Viewer::setHeadlight()). So@Gui@Viewer-derived viewers all inherit the following keyboard controls from this class (but only when the viewer is in "examine mode", ie So@Gui@Viewer::isViewing() returns \c TRUE): */ // ************************************************************************* /*! \enum So@Gui@Viewer::AutoClippingStrategy Enum for auto clipping strategy. \sa setAutoClippingStrategy() */ /*! \var So@Gui@Viewer::AutoClippingStrategy So@Gui@Viewer::CONSTANT_NEAR_PLANE Constant near plane auto clipping strategy. Explained in detail in the documentation for the So@Gui@Viewer::setAutoClippingStrategy() method. */ /*! \var So@Gui@Viewer::AutoClippingStrategy So@Gui@Viewer::VARIABLE_NEAR_PLANE Variable near plane auto clipping strategy. Explained in detail in the documentation for the So@Gui@Viewer::setAutoClippingStrategy() method. */ // ************************************************************************* #ifdef HAVE_CONFIG_H #include #endif // HAVE_CONFIG_H #include #include #include #include // FLT_MAX #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 #ifdef HAVE_SOPOLYGONOFFSET #include #endif // HAVE_SOPOLYGONOFFSET #include #include #include #include #include #include #include #include // ************************************************************************* // (note: this *must* be a #define, not a static variable -- to avoid // initialization race conditions with the static variables being set // to the value of this) #define UNINITIALIZED_ENVVAR -1 // value of envvars before tested // Environment variable for debugging purpose: display a running // frames-per-second counter. See code comments above // So@Gui@ViewerP::recordFPS() function below for more information. static int COIN_SHOW_FPS_COUNTER = UNINITIALIZED_ENVVAR; // ************************************************************************* #define PRIVATE(ptr) (ptr->pimpl) #define PUBLIC(ptr) (ptr->pub) // ************************************************************************* So@Gui@ViewerP::So@Gui@ViewerP(So@Gui@Viewer * publ) { PUBLIC(this) = publ; this->searchaction = new SoSearchAction; this->matrixaction = new SoGetMatrixAction(SbViewportRegion(100,100)); this->superimpositions = NULL; this->storedcamera = NULL; // initialize auto clipping parameters this->autoclipstrategy = So@Gui@Viewer::VARIABLE_NEAR_PLANE; this->autoclipvalue = 0.6f; this->autoclipcb = NULL; this->stereotype = So@Gui@Viewer::STEREO_NONE; this->stereotypesetexplicit = FALSE; this->stereostencilmaskvp = SbViewportRegion(0, 0); this->stereostencilmask = NULL; this->stereostenciltype = So@Gui@Viewer::STEREO_NONE; this->stereoanaglyphmask[0][0] = TRUE; this->stereoanaglyphmask[0][1] = this->stereoanaglyphmask[0][2] = FALSE; this->stereoanaglyphmask[1][0] = FALSE; this->stereoanaglyphmask[1][1] = this->stereoanaglyphmask[1][2] = TRUE; } So@Gui@ViewerP::~So@Gui@ViewerP() { // This impossible to miss reminder was inserted so we don't // accidentally let an So* v2 slip out the door without fixing this // API design flaw. 20030625 mortene. #if (SO@GUI@_MAJOR_VERSION == 2) #error This is a reminder: when jumping to version 2 of an So* toolkit, the viewer destructors (at least, possibly also further up in the inheritance hierarchy) should be made virtual. #endif // version = 2 delete[] this->stereostencilmask; if ( this->superimpositions != NULL ) delete this->superimpositions; delete this->searchaction; delete this->matrixaction; if (this->storedcamera) { this->storedcamera->unref(); } } SoSeparator * So@Gui@ViewerP::createSuperScene(void) { static const char * superSceneGraph[] = { "#Inventor V2.1 ascii", "", "Separator {", " renderCaching OFF", " renderCulling OFF", " pickCulling OFF", " boundingBoxCaching OFF", // Headlight. By inserting this before any scenegraph camera, the // light will always be pointing in the correct direction. " DEF so@gui@->headlight DirectionalLight {", " direction 1 -1 -10", " }", " DEF so@gui@->drawstyleroot Switch {", " whichChild -1", " DEF so@gui@->lightmodel LightModel {", " model BASE_COLOR", " }", " DEF so@gui@->drawstyle DrawStyle {", " pointSize ~", " lineWidth ~", " linePattern ~", " }", " DEF so@gui@->complexity Complexity {", " textureQuality 0.0", " value 0.1", " }", " }", " DEF so@gui@->hiddenlineroot Switch {", " whichChild -1", " DEF so@gui@->basecolor BaseColor { }", " DEF so@gui@->materialbinding MaterialBinding {", " value OVERALL", " }", " DEF so@gui@->polygonoffsetparent Switch {", " whichChild -1", #ifdef HAVE_SOPOLYGONOFFSET " DEF so@gui@->polygonoffset PolygonOffset { }", #endif // HAVE_SOPOLYGONOFFSET " }", " }", " DEF so@gui@->userscenegraphroot Separator {", // turn off caching to make it possible for users to disable // caching in their scene graphs. " renderCaching OFF\n", " }", "}", NULL }; int i, bufsize; for (i = bufsize = 0; superSceneGraph[i]; i++) bufsize += strlen(superSceneGraph[i]) + 1; char * buf = new char [bufsize + 1]; for (i = bufsize = 0; superSceneGraph[i]; i++) { strcpy(buf + bufsize, superSceneGraph[i]); bufsize += strlen(superSceneGraph[i]); buf[bufsize] = '\n'; bufsize++; } SoInput * input = new SoInput; input->setBuffer(buf, bufsize); SoNode * root = NULL; SbBool ok = SoDB::read(input, root); delete input; delete [] buf; if (!ok) { // FIXME: this looks unnecessary robust, and I believe it should // be replaced by an assert()..? 20030430 mortene. SoDebugError::post("So@Gui@ViewerP::createSuperScene", "couldn't create viewer superscene"); return NULL; } assert(root->isOfType(SoSeparator::getClassTypeId())); root->ref(); this->searchaction->reset(); this->searchaction->setSearchingAll(TRUE); this->searchaction->setInterest(SoSearchAction::FIRST); #define LOCATE_NODE(member, type, name) \ do { \ member = NULL; \ this->searchaction->setName(SbName(name)); \ this->searchaction->apply(root); \ if (this->searchaction->getPath() != NULL) { \ SoNode * node = this->searchaction->getPath()->getTail(); \ assert(node != NULL); \ if (node->isOfType(type::getClassTypeId())) \ member = (type *) node; \ } else { \ SoDebugError::post("So@Gui@ViewerP::createSuperScene", \ "didn't locate node \"%s\"", name); \ } \ } while (FALSE) LOCATE_NODE(this->headlight, SoDirectionalLight, "so@gui@->headlight"); LOCATE_NODE(this->drawstyleroot, SoSwitch, "so@gui@->drawstyleroot"); LOCATE_NODE(this->hiddenlineroot, SoSwitch, "so@gui@->hiddenlineroot"); LOCATE_NODE(this->polygonoffsetparent, SoSwitch, "so@gui@->polygonoffsetparent"); LOCATE_NODE(this->usersceneroot, SoSeparator, "so@gui@->userscenegraphroot"); LOCATE_NODE(this->sobasecolor, SoBaseColor, "so@gui@->basecolor"); LOCATE_NODE(this->socomplexity, SoComplexity, "so@gui@->complexity"); LOCATE_NODE(this->sodrawstyle, SoDrawStyle, "so@gui@->drawstyle"); LOCATE_NODE(this->solightmodel, SoLightModel, "so@gui@->lightmodel"); LOCATE_NODE(this->somaterialbinding, SoMaterialBinding, "so@gui@->materialbinding"); if (this->sobasecolor) this->sobasecolor->setOverride(TRUE); if (this->socomplexity) this->socomplexity->setOverride(TRUE); if (this->sodrawstyle) this->sodrawstyle->setOverride(TRUE); if (this->solightmodel) this->solightmodel->setOverride(TRUE); if (this->somaterialbinding) this->somaterialbinding->setOverride(TRUE); #ifdef HAVE_SOPOLYGONOFFSET LOCATE_NODE(this->sopolygonoffset, SoPolygonOffset, "so@gui@->polygonoffset"); if (this->sopolygonoffset) this->sopolygonoffset->setOverride(TRUE); #endif // HAVE_SOPOLYGONOFFSET #undef LOCATE_NODE this->searchaction->reset(); root->unrefNoDelete(); return (SoSeparator *) root; } // Returns the coordinate system the current camera is located in. If // there are transformations before the camera in the scene graph, // this must be considered before doing certain operations. \a matrix // and \a inverse will not contain the transformations caused by the // camera fields, only the transformations traversed before the camera // in the scene graph. void So@Gui@ViewerP::getCameraCoordinateSystem(SoCamera * cameraarg, SoNode * root, SbMatrix & matrix, SbMatrix & inverse) { this->searchaction->reset(); this->searchaction->setSearchingAll(TRUE); this->searchaction->setInterest(SoSearchAction::FIRST); this->searchaction->setNode(cameraarg); this->searchaction->apply(root); matrix = inverse = SbMatrix::identity(); if (this->searchaction->getPath()) { this->matrixaction->apply(this->searchaction->getPath()); matrix = this->matrixaction->getMatrix(); inverse = this->matrixaction->getInverse(); } this->searchaction->reset(); } // These functions do this: // // * when going from orthocam -> perspectivecam: set the // heightAngle field to its default value (45°), and move // camera to a position where the scene/model would fill about // the same screenspace as it did in the orthocam // // * when going from perspectivecam -> orthocam: keep the // current position, but tune the view-volume height so the // scene/model takes up about the same screenspace // // 20020522 mortene. void So@Gui@ViewerP::convertOrtho2Perspective(const SoOrthographicCamera * in, SoPerspectiveCamera * out) { out->aspectRatio.setValue(in->aspectRatio.getValue()); out->focalDistance.setValue(in->focalDistance.getValue()); out->orientation.setValue(in->orientation.getValue()); out->position.setValue(in->position.getValue()); out->viewportMapping.setValue(in->viewportMapping.getValue()); SbRotation camrot = in->orientation.getValue(); float focaldist = in->height.getValue() / (2.0*tan(M_PI / 8.0)); SbVec3f offset(0,0,focaldist-in->focalDistance.getValue()); camrot.multVec(offset,offset); out->position.setValue(offset+in->position.getValue()); out->focalDistance.setValue(focaldist); // 45° is the default value of this field in SoPerspectiveCamera. out->heightAngle = (float)(M_PI / 4.0); #if SO@GUI@_DEBUG && 0 // debug SoDebugError::postInfo("So@Gui@ViewerP::convertOrtho2Perspective", "perspective heightAngle==%f", 180.0f * out->heightAngle.getValue() / M_PI); #endif // debug } void So@Gui@ViewerP::convertPerspective2Ortho(const SoPerspectiveCamera * in, SoOrthographicCamera * out) { out->aspectRatio.setValue(in->aspectRatio.getValue()); out->focalDistance.setValue(in->focalDistance.getValue()); out->orientation.setValue(in->orientation.getValue()); out->position.setValue(in->position.getValue()); out->viewportMapping.setValue(in->viewportMapping.getValue()); float focaldist = in->focalDistance.getValue(); out->height = 2.0f * focaldist * (float)tan(in->heightAngle.getValue() / 2.0); #if SO@GUI@_DEBUG && 0 // debug SoDebugError::postInfo("So@Gui@ViewerP::convertOrtho2Perspective", "ortho height==%f", out->height.getValue()); #endif // debug } void So@Gui@ViewerP::reallyRedraw(const SbBool clearcol, const SbBool clearz) { // Recalculate near/far planes. Must be done in reallyRedraw() -- // not actualRedraw() -- so the clipping planes are correct even // when rendering multiple times with different camera settings. if (this->camera && PUBLIC(this)->isAutoClipping()) { // Temporarily turn off notification when changing near and far // clipping planes, to avoid latency. const SbBool notif = this->camera->isNotifyEnabled(); this->camera->enableNotify(FALSE); this->setClippingPlanes(); this->camera->enableNotify(notif); } if (this->drawAsHiddenLine()) { // First pass: render as filled, but with the background color. this->solightmodel->model.setIgnored(FALSE); // override as SoLightModel::BASE this->sodrawstyle->style.setIgnored(TRUE); // draw as-is filled/lines/points this->socomplexity->type.setIgnored(TRUE); // as-is rendering space this->socomplexity->value.setIgnored(TRUE); // as-is complexity on non-simple shapes // textureQuality field of socomplexity node is always 0.0 this->sobasecolor->rgb.setValue(PUBLIC(this)->getBackgroundColor()); this->sobasecolor->rgb.setIgnored(FALSE); this->somaterialbinding->value.setIgnored(FALSE); // override with OVERALL this->polygonoffsetparent->whichChild = SO_SWITCH_ALL; PUBLIC(this)->getSceneManager()->render(clearcol, clearz); // Second pass, render wireframe on top. this->sodrawstyle->style = SoDrawStyle::LINES; this->sodrawstyle->style.setIgnored(FALSE); // force lines this->sobasecolor->rgb.setIgnored(TRUE); // use as-is line colors this->somaterialbinding->value.setIgnored(TRUE); // as-is this->polygonoffsetparent->whichChild = SO_SWITCH_NONE; PUBLIC(this)->getSceneManager()->render(FALSE, FALSE); return; } if (this->drawAsWireframeOverlay()) { // First pass: render as-is, with polygon offset this->solightmodel->model.setIgnored(TRUE); this->somaterialbinding->value.setIgnored(TRUE); this->sobasecolor->rgb.setIgnored(TRUE); this->sodrawstyle->style.setIgnored(TRUE); // draw as-is filled/lines/points this->socomplexity->type.setIgnored(TRUE); // as-is rendering space this->socomplexity->value.setIgnored(TRUE); // as-is complexity on non-simple shapes this->socomplexity->textureQuality.setIgnored(TRUE); this->somaterialbinding->value.setIgnored(TRUE); // override with OVERALL this->polygonoffsetparent->whichChild = SO_SWITCH_ALL; PUBLIC(this)->getSceneManager()->render(clearcol, clearz); // Second pass, render wireframe on top. this->sobasecolor->rgb.setValue(this->wireframeoverlaycolor); this->sobasecolor->rgb.setIgnored(FALSE); this->somaterialbinding->value.setIgnored(FALSE); // override with OVERALL this->solightmodel->model.setIgnored(FALSE); // override as SoLightModel::BASE this->sodrawstyle->style = SoDrawStyle::LINES; this->sodrawstyle->style.setIgnored(FALSE); // force lines this->polygonoffsetparent->whichChild = SO_SWITCH_NONE; this->socomplexity->textureQuality.setIgnored(FALSE); PUBLIC(this)->getSceneManager()->render(FALSE, FALSE); // disable override nodes (void) this->sobasecolor->rgb.enableNotify(FALSE); this->sobasecolor->rgb.setIgnored(TRUE); (void) this->sobasecolor->rgb.enableNotify(TRUE); (void) this->somaterialbinding->value.enableNotify(FALSE); this->somaterialbinding->value.setIgnored(TRUE); (void) this->somaterialbinding->value.enableNotify(TRUE); (void) this->solightmodel->model.enableNotify(FALSE); this->solightmodel->model.setIgnored(TRUE); (void) this->solightmodel->model.enableNotify(TRUE); (void) this->socomplexity->textureQuality.enableNotify(FALSE); this->socomplexity->textureQuality.setIgnored(TRUE); (void) this->socomplexity->textureQuality.enableNotify(TRUE); (void) this->sodrawstyle->style.enableNotify(FALSE); this->sodrawstyle->style.setIgnored(TRUE); (void) this->sodrawstyle->style.enableNotify(TRUE); return; } SbBool clearzbuffer = TRUE; So@Gui@Viewer::DrawStyle style = this->currentDrawStyle(); switch (style) { case So@Gui@Viewer::VIEW_LOW_RES_LINE: case So@Gui@Viewer::VIEW_LOW_RES_POINT: case So@Gui@Viewer::VIEW_BBOX: clearzbuffer = FALSE; default: break; // Include "default:" case to avoid compiler warning. } PUBLIC(this)->getSceneManager()->render(clearcol, clearzbuffer && clearz); } // ************************************************************************* // Returns a boolean to indicate if the dynamic drawstyle equals // the static drawstyle. SbBool So@Gui@ViewerP::drawInteractiveAsStill(void) const { SbBool moveasstill = this->drawstyles[So@Gui@Viewer::INTERACTIVE] == So@Gui@Viewer::VIEW_SAME_AS_STILL; if (! moveasstill) moveasstill = this->drawstyles[So@Gui@Viewer::INTERACTIVE] == this->drawstyles[So@Gui@Viewer::STILL]; if (! moveasstill) moveasstill = this->drawstyles[So@Gui@Viewer::INTERACTIVE] == So@Gui@Viewer::VIEW_NO_TEXTURE && this->drawstyles[So@Gui@Viewer::STILL] != So@Gui@Viewer::VIEW_AS_IS; return moveasstill; } // Returns the current drawing style. So@Gui@Viewer::DrawStyle So@Gui@ViewerP::currentDrawStyle(void) const { SbBool interactivemode = PUBLIC(this)->getInteractiveCount() > 0 ? TRUE : FALSE; if (!interactivemode || this->drawInteractiveAsStill()) return this->drawstyles[So@Gui@Viewer::STILL]; else return this->drawstyles[So@Gui@Viewer::INTERACTIVE]; } // Returns a boolean to indicate if the current drawstyle settings implies // hidden line rendering. SbBool So@Gui@ViewerP::drawAsHiddenLine(void) const { return ((this->currentDrawStyle() == So@Gui@Viewer::VIEW_HIDDEN_LINE) ? TRUE : FALSE); } // Returns a boolean to indicate if the current drawstyle settings // implies wirefram overlay rendering. SbBool So@Gui@ViewerP::drawAsWireframeOverlay(void) const { return ((this->currentDrawStyle() == So@Gui@Viewer::VIEW_WIREFRAME_OVERLAY) ? TRUE : FALSE); } // Use the given style setting to set the correct states in the // rendering control nodes. This will affect the way the scene is // currently rendered. void So@Gui@ViewerP::changeDrawStyle(So@Gui@Viewer::DrawStyle style) { // Turn on/off Z-buffering based on the style setting. switch (style) { case So@Gui@Viewer::VIEW_LOW_RES_LINE: case So@Gui@Viewer::VIEW_LOW_RES_POINT: case So@Gui@Viewer::VIEW_BBOX: PUBLIC(this)->glLockNormal(); // FIXME: shouldn't this be done "lazy", i.e. before we do any // actual rendering? 20001126 mortene. glDisable(GL_DEPTH_TEST); PUBLIC(this)->glUnlockNormal(); break; default: PUBLIC(this)->glLockNormal(); // FIXME: shouldn't this be done "lazy", i.e. before we do any // actual rendering? 20001126 mortene. glEnable(GL_DEPTH_TEST); PUBLIC(this)->glUnlockNormal(); break; } // Render everything as its supposed to be done, don't override // any of the settings in the ``real'' graph. if (style == So@Gui@Viewer::VIEW_AS_IS) { this->drawstyleroot->whichChild = SO_SWITCH_NONE; return; } this->drawstyleroot->whichChild = SO_SWITCH_ALL; if ((style == So@Gui@Viewer::VIEW_HIDDEN_LINE) || (style == So@Gui@Viewer::VIEW_WIREFRAME_OVERLAY)) { this->hiddenlineroot->whichChild = SO_SWITCH_ALL; return; } else { this->hiddenlineroot->whichChild = SO_SWITCH_NONE; } // Set or unset lightmodel override. switch (style) { case So@Gui@Viewer::VIEW_NO_TEXTURE: case So@Gui@Viewer::VIEW_LOW_COMPLEXITY: this->solightmodel->model.setIgnored(TRUE); // as-is BASE or PHONG break; case So@Gui@Viewer::VIEW_LINE: case So@Gui@Viewer::VIEW_POINT: case So@Gui@Viewer::VIEW_BBOX: case So@Gui@Viewer::VIEW_LOW_RES_LINE: case So@Gui@Viewer::VIEW_LOW_RES_POINT: this->solightmodel->model.setIgnored(FALSE); // force BASE lighting break; default: assert(FALSE); break; } // Set or unset drawstyle override. switch (style) { case So@Gui@Viewer::VIEW_NO_TEXTURE: case So@Gui@Viewer::VIEW_LOW_COMPLEXITY: this->sodrawstyle->style.setIgnored(TRUE); // as-is drawing style filled/lines/points break; case So@Gui@Viewer::VIEW_LINE: case So@Gui@Viewer::VIEW_LOW_RES_LINE: case So@Gui@Viewer::VIEW_BBOX: this->sodrawstyle->style = SoDrawStyle::LINES; this->sodrawstyle->style.setIgnored(FALSE); // force line rendering break; case So@Gui@Viewer::VIEW_POINT: case So@Gui@Viewer::VIEW_LOW_RES_POINT: this->sodrawstyle->style = SoDrawStyle::POINTS; this->sodrawstyle->style.setIgnored(FALSE); // force point rendering break; default: assert(FALSE); break; } // Set or unset complexity value override. switch (style) { case So@Gui@Viewer::VIEW_NO_TEXTURE: case So@Gui@Viewer::VIEW_LINE: case So@Gui@Viewer::VIEW_POINT: case So@Gui@Viewer::VIEW_BBOX: this->socomplexity->value.setIgnored(TRUE); // as-is complexity break; case So@Gui@Viewer::VIEW_LOW_COMPLEXITY: case So@Gui@Viewer::VIEW_LOW_RES_LINE: case So@Gui@Viewer::VIEW_LOW_RES_POINT: this->socomplexity->value.setIgnored(FALSE); // force complexity setting of 0.1 break; default: assert(FALSE); break; } // Set or unset complexity textureQuality override (the value of the // override-field is always 0.0, ie signalling "textures off"). switch (style) { case So@Gui@Viewer::VIEW_HIDDEN_LINE: case So@Gui@Viewer::VIEW_NO_TEXTURE: case So@Gui@Viewer::VIEW_LINE: case So@Gui@Viewer::VIEW_POINT: case So@Gui@Viewer::VIEW_BBOX: case So@Gui@Viewer::VIEW_LOW_RES_LINE: case So@Gui@Viewer::VIEW_LOW_RES_POINT: this->socomplexity->textureQuality.setIgnored(FALSE); // textures off break; default: this->socomplexity->textureQuality.setIgnored(TRUE); // don't override break; } // Set or unset complexity type override. switch (style) { case So@Gui@Viewer::VIEW_NO_TEXTURE: case So@Gui@Viewer::VIEW_LOW_COMPLEXITY: case So@Gui@Viewer::VIEW_LINE: case So@Gui@Viewer::VIEW_POINT: case So@Gui@Viewer::VIEW_LOW_RES_LINE: case So@Gui@Viewer::VIEW_LOW_RES_POINT: this->socomplexity->type.setIgnored(TRUE); // as-is break; case So@Gui@Viewer::VIEW_BBOX: this->socomplexity->type = SoComplexity::BOUNDING_BOX; this->socomplexity->type.setIgnored(FALSE); // force bounding box rendering break; default: assert(FALSE); break; } #if 0 // debug SoDebugError::postInfo("So@Gui@Viewer::changeDrawStyle", "\n" "\tdrawstyle style: 0x%02x (isIgnored() == %s)\n" "\tlightmodel model: 0x%02x, (isIgnored() == %s)\n" "\tcomplexity type: 0x%02x, (isIgnored() == %s)\n" "\tcomplexity value: %f, (isIgnored() == %s)\n" "", this->sodrawstyle->style.getValue(), this->sodrawstyle->style.isIgnored() ? "T" : "F", this->solightmodel->model.getValue(), this->solightmodel->model.isIgnored() ? "T" : "F", this->socomplexity->type.getValue(), this->socomplexity->type.isIgnored() ? "T" : "F", this->socomplexity->value.getValue(), this->socomplexity->value.isIgnored() ? "T" : "F"); #endif // debug } // Position the near and far clipping planes just in front of and // behind the scene's bounding box. This will give us the optimal // utilization of the z buffer resolution by shrinking it to its // minimum depth. // // Near and far clipping planes are specified in the camera fields // nearDistance and farDistance. void So@Gui@ViewerP::setClippingPlanes(void) { // This is necessary to avoid a crash in case there is no scene // graph specified by the user. if (this->camera == NULL) return; if (this->autoclipbboxaction == NULL) this->autoclipbboxaction = new SoGetBoundingBoxAction(PUBLIC(this)->getViewportRegion()); else this->autoclipbboxaction->setViewportRegion(PUBLIC(this)->getViewportRegion()); this->autoclipbboxaction->apply(this->sceneroot); SbXfBox3f xbox = this->autoclipbboxaction->getXfBoundingBox(); SbMatrix cammat; SbMatrix inverse; this->getCameraCoordinateSystem(this->camera, this->sceneroot, cammat, inverse); xbox.transform(inverse); SbMatrix mat; mat.setTranslate(- this->camera->position.getValue()); xbox.transform(mat); mat = this->camera->orientation.getValue().inverse(); xbox.transform(mat); SbBox3f box = xbox.project(); // Bounding box was calculated in camera space, so we need to "flip" // the box (because camera is pointing in the (0,0,-1) direction // from origo. float nearval = -box.getMax()[2]; float farval = -box.getMin()[2]; // FIXME: what if we have a weird scale transform in the scenegraph? // Could we end up with nearval > farval then? Investigate, then // either use an assert() (if it can't happen) or an So@Gui@Swap() // (to handle it). 20020116 mortene. // Check if scene is completely behind us. if (farval <= 0.0f) { return; } if (this->camera->isOfType(SoPerspectiveCamera::getClassTypeId())) { // Disallow negative and small near clipping plane distance. float nearlimit; // the smallest value allowed for nearval if (this->autoclipstrategy == So@Gui@Viewer::CONSTANT_NEAR_PLANE) { nearlimit = this->autoclipvalue; } else { assert(this->autoclipstrategy == So@Gui@Viewer::VARIABLE_NEAR_PLANE); // From glFrustum() documentation: Depth-buffer precision is // affected by the values specified for znear and zfar. The // greater the ratio of zfar to znear is, the less effective the // depth buffer will be at distinguishing between surfaces that // are near each other. If r = far/near, roughly log (2) r bits // of depth buffer precision are lost. Because r approaches // infinity as znear approaches zero, you should never set znear // to zero. GLint depthbits[1]; glGetIntegerv(GL_DEPTH_BITS, depthbits); int use_bits = (int) (float(depthbits[0]) * (1.0f-this->autoclipvalue)); float r = (float) pow(2.0, (double) use_bits); nearlimit = farval / r; } if (nearlimit >= farval) { // (The "5000" magic constant was found by fiddling around a bit // on an OpenGL implementation with a 16-bit depth-buffer // resolution, adjusting to find something that would work well // with both a very "stretched" / deep scene and a more compact // single-model one.) nearlimit = farval / 5000.0f; } // adjust the near plane if the the value is too small. if (nearval < nearlimit) { nearval = nearlimit; } } // Some slack around the bounding box, in case the scene fits // exactly inside it. This is done to minimize the chance of // artifacts caused by the limitation of the z-buffer // resolution. One common artifact if this is not done is that the // near clipping plane cuts into the corners of the model as it's // rotated. const float SLACK = 0.001f; // FrustumCamera can be found in the SmallChange CVS module. We // should not change the nearDistance for this camera, as this will // modify the frustum. // // FIXME: quite the hack that So@Gui@ needs to know about the // FrustumCamera class. Wouldn't it be better if FrustumCamera // instead registered a callback with setAutoClippingStrategy() and // handled this itself? 20040908 mortene. if (this->camera->getTypeId().getName() == "FrustumCamera") { nearval = this->camera->nearDistance.getValue(); farval *= (1.0f + SLACK); if (farval <= nearval) { // nothing is visible, so just set farval to som value > nearval. farval = nearval + 10.0f; } } else { nearval *= (1.0f - SLACK); farval *= (1.0f + SLACK); } if (this->autoclipcb) { SbVec2f nearfar(nearval, farval); nearfar = this->autoclipcb(this->autoclipuserdata, nearfar); nearval = nearfar[0]; farval = nearfar[1]; } if (nearval != this->camera->nearDistance.getValue()) { this->camera->nearDistance = nearval; } if (farval != this->camera->farDistance.getValue()) { this->camera->farDistance = farval; } // FIXME: there's a possible optimization to take advantage of here, // since we are able to sometimes know for sure that all geometry is // completely inside the view volume. I quote from the "OpenGL FAQ // and Troubleshooting Guide": // // "10.050 I know my geometry is inside the view volume. How can I // turn off OpenGL's view-volume clipping to maximize performance? // // Standard OpenGL doesn't provide a mechanism to disable the // view-volume clipping test; thus, it will occur for every // primitive you send. // // Some implementations of OpenGL support the // GL_EXT_clip_volume_hint extension. If the extension is // available, a call to // glHint(GL_CLIP_VOLUME_CLIPPING_HINT_EXT,GL_FASTEST) will inform // OpenGL that the geometry is entirely within the view volume and // that view-volume clipping is unnecessary. Normal clipping can // be resumed by setting this hint to GL_DONT_CARE. When clipping // is disabled with this hint, results are undefined if geometry // actually falls outside the view volume." // // 20020117 mortene. // Debug assistance, can be turned on without recompilation (just // set the environment variable SO@GUI@_DEBUG_CLIPPLANES): #if SO@GUI@_DEBUG static int debugoutputnearfar = -1; if (debugoutputnearfar == -1) { const char * env = SoAny::si()->getenv("SO@GUI@_DEBUG_CLIPPLANES"); debugoutputnearfar = (env && atoi(env) > 0) ? 1 : 0; } if (debugoutputnearfar == 1) { // debug SoDebugError::postInfo("So@Gui@Viewer::setClippingPlanes", "near, far: %f (%f), %f (%f)", nearval, this->camera->nearDistance.getValue(), farval, this->camera->farDistance.getValue()); } #endif // debug } // Translate camera a distance equal to the difference in projected, // normalized screen coordinates given by the argument. void So@Gui@ViewerP::moveCameraScreen(const SbVec2f & screenpos) { SoCamera * cam = PUBLIC(this)->getCamera(); assert(cam); if (SO@GUI@_DEBUG && 0) { // debug SoDebugError::postInfo("So@Gui@Viewer::moveCameraScreen", "screenpos: <%f, %f>, campos: <%f, %f, %f>", screenpos[0], screenpos[1], cam->position.getValue()[0], cam->position.getValue()[1], cam->position.getValue()[2]); } SbViewVolume vv = cam->getViewVolume(PUBLIC(this)->getGLAspectRatio()); SbPlane panplane = vv.getPlane(cam->focalDistance.getValue()); SbLine line; vv.projectPointToLine(screenpos + SbVec2f(0.5, 0.5f), line); SbVec3f current_planept; panplane.intersect(line, current_planept); vv.projectPointToLine(SbVec2f(0.5f, 0.5f), line); SbVec3f old_planept; panplane.intersect(line, old_planept); // Reposition camera according to the vector difference between the // projected points. cam->position = cam->position.getValue() - (current_planept - old_planept); if (SO@GUI@_DEBUG && 0) { // debug SoDebugError::postInfo("So@Gui@Viewer::moveCameraScreen", "newcampos: <%f, %f, %f>", cam->position.getValue()[0], cam->position.getValue()[1], cam->position.getValue()[2]); } } // Called when viewer enters interactive mode (animation, drag, ...). void So@Gui@ViewerP::interactivestartCB(void *, So@Gui@Viewer * thisp) { // In interactive buffer mode, doublebuffering is used during interaction. if (PRIVATE(thisp)->buffertype == So@Gui@Viewer::BUFFER_INTERACTIVE) { PRIVATE(thisp)->localsetbuffertype = TRUE; thisp->So@Gui@RenderArea::setDoubleBuffer(TRUE); PRIVATE(thisp)->localsetbuffertype = FALSE; } // Use the dynamic drawstyle. if (!PRIVATE(thisp)->drawInteractiveAsStill()) PRIVATE(thisp)->changeDrawStyle(PRIVATE(thisp)->drawstyles[So@Gui@Viewer::INTERACTIVE]); } // Called when viewer goes out of interactive mode and into "frozen" // mode. void So@Gui@ViewerP::interactiveendCB(void *, So@Gui@Viewer * thisp) { // In interactive buffer mode, doublebuffering is used during // interaction, singelbuffering while the camera is static. if (PRIVATE(thisp)->buffertype == So@Gui@Viewer::BUFFER_INTERACTIVE) { PRIVATE(thisp)->localsetbuffertype = TRUE; thisp->So@Gui@RenderArea::setDoubleBuffer(FALSE); PRIVATE(thisp)->localsetbuffertype = FALSE; } // Back to static drawstyle. if (!PRIVATE(thisp)->drawInteractiveAsStill()) PRIVATE(thisp)->changeDrawStyle(PRIVATE(thisp)->drawstyles[So@Gui@Viewer::STILL]); } // Called repeatedly during the seek animation. void So@Gui@ViewerP::seeksensorCB(void * data, SoSensor * s) { SbTime currenttime = SbTime::getTimeOfDay(); So@Gui@Viewer * thisp = (So@Gui@Viewer *)data; SoTimerSensor * sensor = (SoTimerSensor *)s; float t = float((currenttime - sensor->getBaseTime()).getValue()) / PRIVATE(thisp)->seekperiod; if ((t > 1.0f) || (t + sensor->getInterval().getValue() > 1.0f)) t = 1.0f; SbBool end = (t == 1.0f); t = (float) ((1.0 - cos(M_PI*t)) * 0.5); PRIVATE(thisp)->camera->position = PRIVATE(thisp)->camerastartposition + (PRIVATE(thisp)->cameraendposition - PRIVATE(thisp)->camerastartposition) * t; PRIVATE(thisp)->camera->orientation = SbRotation::slerp(PRIVATE(thisp)->camerastartorient, PRIVATE(thisp)->cameraendorient, t); if (end) thisp->setSeekMode(FALSE); } // Reset the frames-per-second counter upon window resize events, // abnormal delays, etc. // // The methods for recording FPS values are Coin extensions, not // available in the original Open Inventor API. // // \sa addFrametime(), recordFPS() void So@Gui@ViewerP::resetFrameCounter(void) { this->framecount = 0; for (int i = 0; i < So@Gui@ViewerP::FRAMESARRAY_SIZE; i++) this->frames[i] = SbVec2f(0.0f, 0.0f); this->totalcoin = 0.0f; this->totaldraw = 0.0f; this->lastgettimeofday = SbTime::getTimeOfDay().getValue(); } // Adds the time spent drawing the last frame to the array of past // frame times. Returns the current averaged fps-value. // // The methods for recording FPS values are Coin extensions, not // available in the original Open Inventor API. // // \sa resetFrameCounter(), recordFPS() SbVec2f So@Gui@ViewerP::addFrametime(const double ft) { this->framecount++; int arrayptr = (this->framecount - 1) % FRAMESARRAY_SIZE; this->totalcoin += (float(ft) - this->frames[arrayptr][0]); float coinfps = this->totalcoin / So@Gui@Min(this->framecount, (int) FRAMESARRAY_SIZE); double timeofday = SbTime::getTimeOfDay().getValue(); double ct = timeofday - this->lastgettimeofday; this->totaldraw += (float(ct) - this->frames[arrayptr][1]); float drawfps = this->totaldraw / So@Gui@Min(this->framecount, (int) FRAMESARRAY_SIZE); this->frames[arrayptr] = SbVec2f((float)ft, (float)ct); this->lastgettimeofday = timeofday; return SbVec2f(1.0f / coinfps, 1.0f / drawfps); } static unsigned char fps2dfont[][12] = { { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // { 0, 0, 12, 12, 0, 8, 12, 12, 12, 12, 12, 0 }, // ! { 0, 0, 0, 0, 0, 0, 0, 0, 0, 20, 20, 20 }, // \" { 0, 0, 18, 18, 18, 63, 18, 18, 63, 18, 18, 0 }, // # { 0, 8, 28, 42, 10, 10, 12, 24, 40, 42, 28, 8 }, // $ { 0, 0, 6, 73, 41, 22, 8, 52, 74, 73, 48, 0 }, // % { 0, 12, 18, 18, 12, 25, 37, 34, 34, 29, 0, 0 }, // & { 12, 12, 24, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // ' { 0, 6, 8, 8, 16, 16, 16, 16, 16, 8, 8, 6 }, // ( { 0, 48, 8, 8, 4, 4, 4, 4, 4, 8, 8, 48 }, //) { 0, 0, 0, 0, 0, 0, 8, 42, 20, 42, 8, 0 }, // * { 0, 0, 0, 8, 8, 8,127, 8, 8, 8, 0, 0 }, // + { 0, 24, 12, 12, 0, 0, 0, 0, 0, 0, 0, 0 }, // , { 0, 0, 0, 0, 0, 0,127, 0, 0, 0, 0, 0 }, // - { 0, 0, 24, 24, 0, 0, 0, 0, 0, 0, 0, 0 }, // . { 0, 32, 32, 16, 16, 8, 8, 8, 4, 4, 2, 2 }, // / { 0, 0, 28, 34, 34, 34, 34, 34, 34, 34, 28, 0 }, // 0 { 0, 0, 8, 8, 8, 8, 8, 8, 40, 24, 8, 0 }, // 1 { 0, 0, 62, 32, 16, 8, 4, 2, 2, 34, 28, 0 }, // 2 { 0, 0, 28, 34, 2, 2, 12, 2, 2, 34, 28, 0 }, // 3 { 0, 0, 4, 4, 4,126, 68, 36, 20, 12, 4, 0 }, // 4 { 0, 0, 28, 34, 2, 2, 2, 60, 32, 32, 62, 0 }, // 5 { 0, 0, 28, 34, 34, 34, 60, 32, 32, 34, 28, 0 }, // 6 { 0, 0, 16, 16, 16, 8, 8, 4, 2, 2, 62, 0 }, // 7 { 0, 0, 28, 34, 34, 34, 28, 34, 34, 34, 28, 0 }, // 8 { 0, 0, 28, 34, 2, 2, 30, 34, 34, 34, 28, 0 }, // 9 { 0, 0, 24, 24, 0, 0, 0, 24, 24, 0, 0, 0 }, // : { 0, 48, 24, 24, 0, 0, 0, 24, 24, 0, 0, 0 }, // ; { 0, 0, 0, 2, 4, 8, 16, 8, 4, 2, 0, 0 }, // < { 0, 0, 0, 0, 0,127, 0,127, 0, 0, 0, 0 }, // = { 0, 0, 0, 16, 8, 4, 2, 4, 8, 16, 0, 0 }, // > { 0, 0, 16, 16, 0, 16, 28, 2, 2, 2, 60, 0 }, // ? { 0, 0, 28, 32, 73, 86, 82, 82, 78, 34, 28, 0 }, // @ { 0, 0, 33, 33, 33, 63, 18, 18, 18, 12, 12, 0 }, // A { 0, 0, 60, 34, 34, 34, 60, 34, 34, 34, 60, 0 }, // B { 0, 0, 14, 16, 32, 32, 32, 32, 32, 18, 14, 0 }, // C { 0, 0, 56, 36, 34, 34, 34, 34, 34, 36, 56, 0 }, // D { 0, 0, 62, 32, 32, 32, 60, 32, 32, 32, 62, 0 }, // E { 0, 0, 16, 16, 16, 16, 30, 16, 16, 16, 30, 0 }, // F { 0, 0, 14, 18, 34, 34, 32, 32, 32, 18, 14, 0 }, // G { 0, 0, 34, 34, 34, 34, 62, 34, 34, 34, 34, 0 }, // H { 0, 0, 62, 8, 8, 8, 8, 8, 8, 8, 62, 0 }, // I { 0, 0,112, 8, 8, 8, 8, 8, 8, 8, 62, 0 }, // J { 0, 0, 33, 33, 34, 36, 56, 40, 36, 34, 33, 0 }, // K { 0, 0, 30, 16, 16, 16, 16, 16, 16, 16, 16, 0 }, // L { 0, 0, 33, 33, 33, 45, 45, 45, 51, 51, 33, 0 }, // M { 0, 0, 34, 34, 38, 38, 42, 42, 50, 50, 34, 0 }, // N { 0, 0, 12, 18, 33, 33, 33, 33, 33, 18, 12, 0 }, // O { 0, 0, 32, 32, 32, 60, 34, 34, 34, 34, 60, 0 }, // P { 3, 6, 12, 18, 33, 33, 33, 33, 33, 18, 12, 0 }, // Q { 0, 0, 34, 34, 34, 36, 60, 34, 34, 34, 60, 0 }, // R { 0, 0, 60, 2, 2, 6, 28, 48, 32, 32, 30, 0 }, // S { 0, 0, 8, 8, 8, 8, 8, 8, 8, 8,127, 0 }, // T { 0, 0, 28, 34, 34, 34, 34, 34, 34, 34, 34, 0 }, // U { 0, 0, 12, 12, 18, 18, 18, 33, 33, 33, 33, 0 }, // V { 0, 0, 34, 34, 34, 54, 85, 73, 73, 73, 65, 0 }, // W { 0, 0, 34, 34, 20, 20, 8, 20, 20, 34, 34, 0 }, // X { 0, 0, 8, 8, 8, 8, 20, 20, 34, 34, 34, 0 }, // Y { 0, 0, 62, 32, 16, 16, 8, 4, 4, 2, 62, 0 }, // Z { 0, 14, 8, 8, 8, 8, 8, 8, 8, 8, 8, 14 }, // [ { 0, 2, 2, 4, 4, 8, 8, 8, 16, 16, 32, 32 }, // [backslash] { 0, 56, 8, 8, 8, 8, 8, 8, 8, 8, 8, 56 }, // ] { 0, 0, 0, 0, 0, 34, 34, 20, 20, 8, 8, 0 }, // ^ { 0,127, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // _ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 24, 24, 12 }, // ` { 0, 0, 29, 34, 34, 30, 2, 34, 28, 0, 0, 0 }, // a { 0, 0, 60, 34, 34, 34, 34, 50, 44, 32, 32, 32 }, // b { 0, 0, 14, 16, 32, 32, 32, 16, 14, 0, 0, 0 }, // c { 0, 0, 26, 38, 34, 34, 34, 34, 30, 2, 2, 2 }, // d { 0, 0, 28, 34, 32, 62, 34, 34, 28, 0, 0, 0 }, // e { 0, 0, 16, 16, 16, 16, 16, 16, 62, 16, 16, 14 }, // f { 28, 2, 2, 26, 38, 34, 34, 34, 30, 0, 0, 0 }, // g { 0, 0, 34, 34, 34, 34, 34, 50, 44, 32, 32, 32 }, // h { 0, 0, 8, 8, 8, 8, 8, 8, 56, 0, 8, 8 }, // i { 56, 4, 4, 4, 4, 4, 4, 4, 60, 0, 4, 4 }, // j { 0, 0, 33, 34, 36, 56, 40, 36, 34, 32, 32, 32 }, // k { 0, 0, 8, 8, 8, 8, 8, 8, 8, 8, 8, 56 }, // l { 0, 0, 73, 73, 73, 73, 73,109, 82, 0, 0, 0 }, // m { 0, 0, 34, 34, 34, 34, 34, 50, 44, 0, 0, 0 }, // n { 0, 0, 28, 34, 34, 34, 34, 34, 28, 0, 0, 0 }, // o { 32, 32, 60, 34, 34, 34, 34, 50, 44, 0, 0, 0 }, // p { 2, 2, 26, 38, 34, 34, 34, 34, 30, 0, 0, 0 }, // q { 0, 0, 16, 16, 16, 16, 16, 24, 22, 0, 0, 0 }, // r { 0, 0, 60, 2, 2, 28, 32, 32, 30, 0, 0, 0 }, // s { 0, 0, 14, 16, 16, 16, 16, 16, 62, 16, 16, 0 }, // t { 0, 0, 26, 38, 34, 34, 34, 34, 34, 0, 0, 0 }, // u { 0, 0, 8, 8, 20, 20, 34, 34, 34, 0, 0, 0 }, // v { 0, 0, 34, 34, 34, 85, 73, 73, 65, 0, 0, 0 }, // w { 0, 0, 34, 34, 20, 8, 20, 34, 34, 0, 0, 0 }, // x { 48, 16, 8, 8, 20, 20, 34, 34, 34, 0, 0, 0 }, // y { 0, 0, 62, 32, 16, 8, 4, 2, 62, 0, 0, 0 }, // z { 0, 6, 8, 8, 8, 4, 24, 4, 8, 8, 8, 6 }, // { { 0, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8 }, // | { 0, 48, 8, 8, 8, 16, 12, 16, 8, 8, 8, 48 }, // } { 0, 0, 0, 0, 0, 0, 78, 57, 0, 0, 0, 0 } // ~ }; static void printString(const char * s) { int i,n; n = strlen(s); for (i = 0; i < n; i++) glBitmap(8, 12, 0.0, 2.0, 10.0, 0.0, fps2dfont[s[i] - 32]); } static void Draw2DString(const char * str, SbVec2s glsize, SbVec2f position) { // Store GL state. glPushAttrib(GL_ENABLE_BIT|GL_CURRENT_BIT); glDisable(GL_LIGHTING); glDisable(GL_DEPTH_TEST); glDisable(GL_TEXTURE_2D); glDisable(GL_BLEND); glMatrixMode(GL_MODELVIEW); glPushMatrix(); glLoadIdentity(); glMatrixMode(GL_PROJECTION); glPushMatrix(); glLoadIdentity(); glOrtho(0.0, glsize[0], 0.0, glsize[1], -1, 1); glPixelStorei(GL_UNPACK_ALIGNMENT, 1); glColor3f(0.0, 0.0, 0.0); glRasterPos2f(position[0] + 1, position[1]); printString(str); glRasterPos2f(position[0] - 1, position[1]); printString(str); glRasterPos2f(position[0], position[1] + 1); printString(str); glRasterPos2f(position[0], position[1] - 1); printString(str); glColor3f(1.0, 1.0, 0.0); glRasterPos2f(position[0], position[1]); printString(str); glMatrixMode(GL_PROJECTION); glPopMatrix(); glMatrixMode(GL_MODELVIEW); glPopMatrix(); glPixelStorei(GL_UNPACK_ALIGNMENT, 4); // restore default value glPopAttrib(); } // FIXME: the following is just a temporary hack to enable the FPS // counter. We should really write a proper interface against it, so // applications can set up feedback loops to control scene complexity // and get a nice and steady maximum framerate, for instance. // // For anyone who want to execute that task, check what TGS has done // first. If their API is fine, use the same approach. // // 20001124 mortene. // Draw a text string showing the current frame-per-seconds value in // the lower left corner of the OpenGL canvas (after recording // information needed to calculate the fps). // // The methods for recording FPS values are Coin extensions, not // available in the original Open Inventor API. // // The two displayed values can be explained as follows: // // The first number is the time it takes for the SoGLRenderAction to // traverse the scene graph (displayed as Hz / FPS). The second is the // interval between each time SoGLRenderAction::apply() is invoked // (i.e. the "actual" rendering rate, as experienced by the user). // // The first number is mainly useful just for internal debugging // purposes. // // The second number will always be <= to the first, because it will // also include the time stalling on glFlush() upon releasing the // OpenGL context after traversal (glFlush() stalls until the GPU // completes all OpenGL commands), and any application-code processing // inbetween rendering. // // There is by the way a useful "trick" to improve application // performance implied in that last paragraph above: if // application-code processing is interleaved between the completion // of the SoGLRenderAction traversal and the release of the OpenGL // context, application-code is likely to run on the CPU in parallel // with GPU processing OpenGL commands. // // FIXME: the above optimization trick should be documented in the // visible API docs somewhere, with code to show how to do it. The // example code would probably involve making an application-specific // viewer, and overriding actualRedraw()? Or can it be done by using a // callback mechanism somewhere? Ask pederb. 20031009 mortene. // // \sa resetFrameCounter(), addFrametime() void So@Gui@ViewerP::recordFPS(const double rendertime) { const char * env = SoAny::si()->getenv("COIN_SHOW_FPS_COUNTER"); if ( !env ) { COIN_SHOW_FPS_COUNTER = UNINITIALIZED_ENVVAR; } else { COIN_SHOW_FPS_COUNTER = atoi(env); } #if 0 // disabled to make fps-couter dynamically adjustable if (COIN_SHOW_FPS_COUNTER == UNINITIALIZED_ENVVAR) { const char * env = SoAny::si()->getenv("COIN_SHOW_FPS_COUNTER"); COIN_SHOW_FPS_COUNTER = env ? atoi(env) : 0; } #endif if (COIN_SHOW_FPS_COUNTER > 0) { SbVec2f fps = this->addFrametime(rendertime); char buffer[64]; int nr = sprintf(buffer, "%.1f/%.1f fps", fps[0], fps[1]); assert(nr < 64); Draw2DString(buffer, PUBLIC(this)->getGLSize(), SbVec2f(10, 10)); } } // ************************************************************************* SO@GUI@_OBJECT_ABSTRACT_SOURCE(So@Gui@Viewer); // ************************************************************************* /*! \enum So@Gui@Viewer::Type Hints about what context the viewer will be used in. Usually not very interesting for the application programmer, it doesn't matter much which value is used for the viewer type. This "feature" of the viewer is included just to be compatible with the old SGI Inventor API. */ /*! \var So@Gui@Viewer::Type So@Gui@Viewer::BROWSER If a user-supplied scenegraph passed into the setSceneGraph() function does not contain a camera, setting the viewer type to BROWSER will make the viewer in that case automatically set up a camera outside the scene, as part of the viewer's private and hidden "supergraph". */ /*! \var So@Gui@Viewer::Type So@Gui@Viewer::EDITOR If a user-supplied scenegraph passed into the setSceneGraph() function does not contain a camera, setting the viewer type to EDITOR will make the viewer in that case automatically set up a camera \e in the user-supplied scene. So if you want to avoid having the So@Gui@Viewer class muck about with your supplied scenegraph, set the type-flag to So@Gui@Viewer::BROWSER instead, which makes an inserted camera node go into the viewer's own "wrapper" scene graph instead. */ /*! \enum So@Gui@Viewer::DrawType Contains valid values for the first argument to the So@Gui@Viewer::setDrawStyle() call. Decides the effect of the second argument. \sa So@Gui@Viewer::setDrawStyle(), So@Gui@Viewer::DrawStyle */ /*! \var So@Gui@Viewer::DrawType So@Gui@Viewer::STILL If this value is passed as the first argument of So@Gui@Viewer::setDrawStyle(), the second argument decides which draw style to use when the viewer camera is standing still in the same position with the same orientation -- i.e. when the end user is \e not interacting with the scene camera. */ /*! \var So@Gui@Viewer::DrawType So@Gui@Viewer::INTERACTIVE If this value is passed as the first argument of So@Gui@Viewer::setDrawStyle(), the second argument decides which draw style to use when the end user is interacting with the scene camera, causing continuous animation redraws. */ /*! \enum So@Gui@Viewer::DrawStyle Decides drawstyle for a scene with either a still camera or an animating camera. \sa So@Gui@Viewer::setDrawStyle(), So@Gui@Viewer::DrawType */ /*! \var So@Gui@Viewer::DrawStyle So@Gui@Viewer::VIEW_AS_IS Normal rendering, draws all scene geometry in it's original style. */ /*! \var So@Gui@Viewer::DrawStyle So@Gui@Viewer::VIEW_HIDDEN_LINE Draw scene in "hidden line" mode: that is, as wireframe with no "see-through". Note that this is actually an expensive way to render, as the scene must be rendered twice to achieve the effect of hiding lines behind the invisible geometry. */ /*! \var So@Gui@Viewer::DrawStyle So@Gui@Viewer::VIEW_WIREFRAME_OVERLAY Render the scene as normal, but overlay a set of lines showing the contours of all polygons. */ /*! \var So@Gui@Viewer::DrawStyle So@Gui@Viewer::VIEW_NO_TEXTURE Render scene without textures. */ /*! \var So@Gui@Viewer::DrawStyle So@Gui@Viewer::VIEW_LOW_COMPLEXITY Render all "complex" shape types with low complexity to improve rendering performance. "Complex shapes" in this context includes spheres, cones, cylinder, NURBS surfaces, and others which are tesselated to polygons before being rendered. */ /*! \var So@Gui@Viewer::DrawStyle So@Gui@Viewer::VIEW_LINE View all polygon geometry in wireframe mode. */ /*! \var So@Gui@Viewer::DrawStyle So@Gui@Viewer::VIEW_POINT Render only the vertex positions of the geometry. */ /*! \var So@Gui@Viewer::DrawStyle So@Gui@Viewer::VIEW_BBOX View the scene's bounding boxes, instead of rendering the full geometry. A very efficient way of optimizing rendering performance for scenes with high primitive counts while moving the camera about is to set this mode for the So@Gui@Viewer::INTERACTIVE DrawType. */ /*! \var So@Gui@Viewer::DrawStyle So@Gui@Viewer::VIEW_LOW_RES_LINE Render as wireframe and don't bother with getting them rendered correctly in depth. */ /*! \var So@Gui@Viewer::DrawStyle So@Gui@Viewer::VIEW_LOW_RES_POINT Render as vertex points and don't bother with getting them rendered correctly in depth. */ /*! \var So@Gui@Viewer::DrawStyle So@Gui@Viewer::VIEW_SAME_AS_STILL Always render a scene with an animating camera (ie So@Gui@Viewer::INTERACTIVE DrawType) in the same manner as scene with a still camera. */ /*! \enum So@Gui@Viewer::BufferType Set of valid values for So@Gui@Viewer::setBufferingType(). */ /*! \var So@Gui@Viewer::BufferType So@Gui@Viewer::BUFFER_SINGLE Change underlying OpenGL canvas to be single-buffered. */ /*! \var So@Gui@Viewer::BufferType So@Gui@Viewer::BUFFER_DOUBLE Change underlying OpenGL canvas to be double-buffered. */ /*! \var So@Gui@Viewer::BufferType So@Gui@Viewer::BUFFER_INTERACTIVE Set up so animation rendering is done in a double-buffered OpenGL canvas, but ordinary rendering happens directly in the front-buffer. This mode can be useful with absurdly large scenes, as the rendering will \e visibly progress, and one will avoid having the end user wonder why nothing is happening while the scene is rendered to the back buffer in the default So@Gui@Viewer::BUFFER_DOUBLE mode. */ // ************************************************************************* /* Return the parent node in the scene graph of the given \a node. NB: this is just a quick'n'dirty thing for often executed code, and doesn't cover cases where nodes have multiple parents. */ SoGroup * So@Gui@ViewerP::getParentOfNode(SoNode * root, SoNode * node) const { assert(node && root && "called with null argument"); const SbBool oldsearch = SoBaseKit::isSearchingChildren(); SoBaseKit::setSearchingChildren(TRUE); this->searchaction->reset(); this->searchaction->setSearchingAll(TRUE); this->searchaction->setNode(node); this->searchaction->apply(root); SoPath * p = this->searchaction->getPath(); SoGroup * parent = NULL; if (p) { parent = (SoGroup *) ((SoFullPath *)p)->getNodeFromTail(1); assert(parent && "couldn't find parent"); } this->searchaction->reset(); SoBaseKit::setSearchingChildren(oldsearch); return parent; } // ************************************************************************* /*! Constructor. \a parent, \a name and \a embed are passed on to So@Gui@RenderArea, so see the documentation for our parent constructor for for more information on those. The \a t type setting hints about what context the viewer will be used in. Usually not very interesting for the application programmer, but if you want to make sure the So@Gui@Viewer class doesn't muck about with your supplied scenegraph, set the type-flag to So@Gui@Viewer::BROWSER. (This "feature" of the viewer is included just to be compatible with the old SGI Inventor API.) The \a build flag decides whether or not to delay building the widgets / window which is going to make up the components of the viewer. */ So@Gui@Viewer::So@Gui@Viewer(@WIDGET@ parent, const char * name, SbBool embed, So@Gui@Viewer::Type t, SbBool build) : inherited(parent, name, embed, TRUE, TRUE, FALSE) { PRIVATE(this) = new So@Gui@ViewerP(this); // initialization of protected data PRIVATE(this)->type = t; PRIVATE(this)->viewingflag = TRUE; PRIVATE(this)->altdown = FALSE; PRIVATE(this)->camera = NULL; PRIVATE(this)->scenegraph = NULL; // initialization of internal data PRIVATE(this)->cursoron = TRUE; PRIVATE(this)->localsetbuffertype = FALSE; PRIVATE(this)->cameratype = SoPerspectiveCamera::getClassTypeId(); PRIVATE(this)->buffertype = this->isDoubleBuffer() ? BUFFER_DOUBLE : BUFFER_SINGLE; PRIVATE(this)->interactionstartCallbacks = new SoCallbackList; PRIVATE(this)->interactionendCallbacks = new SoCallbackList; PRIVATE(this)->interactionnesting = 0; PRIVATE(this)->seekdistance = 50.0f; PRIVATE(this)->seekdistanceabs = FALSE; PRIVATE(this)->seektopoint = TRUE; PRIVATE(this)->seekperiod = 2.0f; PRIVATE(this)->inseekmode = FALSE; PRIVATE(this)->seeksensor = new SoTimerSensor(So@Gui@ViewerP::seeksensorCB, this); PRIVATE(this)->sceneroot = PRIVATE(this)->createSuperScene(); PRIVATE(this)->sceneroot->ref(); PRIVATE(this)->drawstyles[STILL] = VIEW_AS_IS; PRIVATE(this)->drawstyles[INTERACTIVE] = VIEW_SAME_AS_STILL; this->addStartCallback(So@Gui@ViewerP::interactivestartCB); this->addFinishCallback(So@Gui@ViewerP::interactiveendCB); PRIVATE(this)->adjustclipplanes = TRUE; PRIVATE(this)->autoclipbboxaction = NULL; PRIVATE(this)->stereoviewing = FALSE; PRIVATE(this)->stereooffset = 0.1f; PRIVATE(this)->wireframeoverlaycolor = SbColor(1.0f, 0.0f, 0.0f); if (build) { this->setClassName("So@Gui@Viewer"); @WIDGET@ widget = this->buildWidget(this->getParentWidget()); this->setBaseWidget(widget); } PRIVATE(this)->resetFrameCounter(); } // ************************************************************************* /*! Destructor. */ So@Gui@Viewer::~So@Gui@Viewer() { delete PRIVATE(this)->autoclipbboxaction; delete PRIVATE(this)->interactionstartCallbacks; delete PRIVATE(this)->interactionendCallbacks; delete PRIVATE(this)->seeksensor; if (PRIVATE(this)->scenegraph) this->setSceneGraph(NULL); if (PRIVATE(this)->superimpositions != NULL) { while ( PRIVATE(this)->superimpositions->getLength() > 0 ) { SoNode * node = (SoNode *) (*PRIVATE(this)->superimpositions)[0]; this->removeSuperimposition(node); } } PRIVATE(this)->sceneroot->unref(); delete PRIVATE(this); } // ************************************************************************* // Note: the following function documentation block will also be used // for all the miscellaneous viewer subclasses, so keep it general. /*! Set the camera we want the viewer to manipulate when interacting with the viewer controls. The camera passed in as an argument to this method \e must already be part of the viewer's scenegraph. You do \e not inject viewpoint cameras to the viewer with this method. You should rather insert a camera into the scene graph first (if necessary, often one will be present already), then register it as the camera used by the viewer controls with this method. If the application code doesn't explicitly set up a camera through this method, the viewer will automatically scan through the scenegraph to find a camera to use. If no camera is available in the scenegraph at all, it will set up it's own camera. \sa getCamera() */ void So@Gui@Viewer::setCamera(SoCamera * cam) { if (PRIVATE(this)->camera) { // remove the camera from the super scene graph if we inserted a camera there int idx = PRIVATE(this)->sceneroot->findChild(PRIVATE(this)->camera); if (idx >= 0) { PRIVATE(this)->sceneroot->removeChild(idx); } PRIVATE(this)->camera->unref(); } if (cam) { cam->ref(); PRIVATE(this)->cameratype = cam->getTypeId(); } PRIVATE(this)->camera = cam; this->saveHomePosition(); } // ************************************************************************* /*! Returns the camera currently used by the viewer for the user's main viewpoint. It \e is possible that this function returns \c NULL, for instance if there's no scenegraph present in the viewer. (This is mostly meant as a note for developers extending the So@Gui@ library, as application programmers usually controls if and when a viewer contains a scenegraph, and therefore know in advance if this method will return a valid camera pointer.) \sa setCamera() */ SoCamera * So@Gui@Viewer::getCamera(void) const { // This impossible to miss reminder was inserted so we don't // accidentally let an So* v2 slip out the door without fixing this // API design flaw. 20030903 mortene. #if (SO@GUI@_MAJOR_VERSION == 2) #error This is a reminder: when jumping to version 2 of an So* toolkit, the So@Gui@Viewer::getCamera() method should be made virtual. #endif // version = 2 return PRIVATE(this)->camera; } // ************************************************************************* /*! When the viewer has to make its own camera as a result of the graph passed to setSceneGraph() not containing any camera nodes, this call can be made in advance to decide which type the camera will be of. Default is to use an SoPerspectiveCamera. If this method is called when there is a scene graph and a camera already set up, it will delete the old camera and set up a camera with the new type if the \a t type is different from that of the current camera. \sa getCameraType() */ void So@Gui@Viewer::setCameraType(SoType t) { if (PRIVATE(this)->camera && !PRIVATE(this)->camera->isOfType(SoPerspectiveCamera::getClassTypeId()) && !PRIVATE(this)->camera->isOfType(SoOrthographicCamera::getClassTypeId())) { #if SO@GUI@_DEBUG SoDebugError::postWarning("So@Gui@Viewer::setCameraType", "Only SoPerspectiveCamera and SoOrthographicCamera is supported."); #endif // SO@GUI_DEBUG return; } SoType perspectivetype = SoPerspectiveCamera::getClassTypeId(); SoType orthotype = SoOrthographicCamera::getClassTypeId(); SbBool oldisperspective = PRIVATE(this)->cameratype.isDerivedFrom(perspectivetype); SbBool newisperspective = t.isDerivedFrom(perspectivetype); if ((oldisperspective && newisperspective) || (!oldisperspective && !newisperspective)) // Same old, same old.. return; if (SO@GUI@_DEBUG) { SbBool valid = TRUE; if (t == SoType::badType()) valid = FALSE; if (valid) { valid = FALSE; if (newisperspective) valid = TRUE; if (t.isDerivedFrom(orthotype)) valid = TRUE; } if (!valid) { SoDebugError::post("So@Gui@Viewer::setCameraType", "not a valid camera type: '%s'", t == SoType::badType() ? "badType" : t.getName().getString()); return; } } SoCamera * currentcam = PRIVATE(this)->camera; if (currentcam == NULL) { // A camera has not been set up for the scene yet, so just store // the type and short-cut the rest of this function. PRIVATE(this)->cameratype = t; return; } SoCamera * newcamera = (SoCamera *)t.createInstance(); // Transfer and convert values from one camera type to the other. if (newisperspective) { So@Gui@ViewerP::convertOrtho2Perspective((SoOrthographicCamera *)currentcam, (SoPerspectiveCamera *)newcamera); } else { So@Gui@ViewerP::convertPerspective2Ortho((SoPerspectiveCamera *)currentcam, (SoOrthographicCamera *)newcamera); } SoGroup * cameraparent = PRIVATE(this)->getParentOfNode(PRIVATE(this)->sceneroot, currentcam); if (cameraparent) { cameraparent->replaceChild(currentcam, newcamera); } else { // camera not actually present in the scene graph, so just NULL // and void. newcamera->ref(); newcamera->unref(); newcamera = NULL; // Yes, this can "legally" happen, if e.g. the camera is taken out // of the scene graph by the app programmer, and no new camera was // set with setCamera() -- but this is a quite odd thing to do, so // we warn about this for now. SoDebugError::postWarning("So@Gui@Viewer::setCameraType", "Could not find the current camera in the " "scene graph, for some odd reason."); } // The setCamera() invokation below will set the saved "home" // position of the camera to the current camera position. We make // no attempt to avoid this, as it would involve nasty hacks, and // it shouldn't really matter. this->setCamera(newcamera); // This will set PRIVATE(this)->cameratype. } // ************************************************************************* /*! Returns camera type which will be used when the viewer has to make its own camera. Note that this call does \e not return the current cameratype, as one might expect. Use getCamera() and SoType::getTypeId() for that inquiry. \sa setCameraType() */ SoType So@Gui@Viewer::getCameraType(void) const { return PRIVATE(this)->cameratype; } // ************************************************************************* /*! Reposition the current camera so we can see the complete scene. */ void So@Gui@Viewer::viewAll(void) { SoCamera * cam = PRIVATE(this)->camera; if (cam && PRIVATE(this)->scenegraph) { cam->viewAll(PRIVATE(this)->scenegraph, this->getViewportRegion()); } } // ************************************************************************* /*! Store the current camera settings for later retrieval with resetToHomePosition(). \sa resetToHomePosition() */ void So@Gui@Viewer::saveHomePosition(void) { if (! PRIVATE(this)->camera) return; // probably a scene-less viewer // We use SoType::createInstance() to store a copy of the camera, // not just assuming it's either a perspective or an orthographic // camera. SoType t = PRIVATE(this)->camera->getTypeId(); assert(t.isDerivedFrom(SoNode::getClassTypeId())); assert(t.canCreateInstance()); if (PRIVATE(this)->storedcamera) { PRIVATE(this)->storedcamera->unref(); } PRIVATE(this)->storedcamera = (SoNode *)t.createInstance(); PRIVATE(this)->storedcamera->ref(); // We copy the field data directly, instead of using // SoFieldContainer::copyContents(), as that has one problematic // side-effect: the new camera node used for storing the data would // also get the *name* of the old camera, which would overwrite the // old name->ptr entry of the global dictionary behind // SoNode::getByName(). This can cause surprising and hard to find // bugs for app programmers, for instance when using // SoNode::getByName() to get at a camera loaded from an iv-file. PRIVATE(this)->storedcamera->copyFieldValues(PRIVATE(this)->camera); } // ************************************************************************* /*! Restore the saved camera settings. \sa saveHomePosition() */ void So@Gui@Viewer::resetToHomePosition(void) { if (!PRIVATE(this)->camera) { return; } // probably a scene-less viewer if (!PRIVATE(this)->storedcamera) { return; } SoType t = PRIVATE(this)->camera->getTypeId(); SoType s = PRIVATE(this)->storedcamera->getTypeId(); // most common case if (t == s) { // We copy the field data directly, instead of using // SoFieldContainer::copyContents(), for the reason described in // detail in So@Gui@Viewer::saveHomePosition(). PRIVATE(this)->camera->copyFieldValues(PRIVATE(this)->storedcamera); } // handle common case #1 else if (t == SoOrthographicCamera::getClassTypeId() && s == SoPerspectiveCamera::getClassTypeId()) { So@Gui@ViewerP::convertPerspective2Ortho((SoPerspectiveCamera *)PRIVATE(this)->storedcamera, (SoOrthographicCamera *)PRIVATE(this)->camera); } // handle common case #2 else if (t == SoPerspectiveCamera::getClassTypeId() && s == SoOrthographicCamera::getClassTypeId()) { So@Gui@ViewerP::convertOrtho2Perspective((SoOrthographicCamera *)PRIVATE(this)->storedcamera, (SoPerspectiveCamera *)PRIVATE(this)->camera); } // otherwise, cameras have changed in ways we don't understand since // the last saveHomePosition() invokation, and so we're just going // to ignore the reset request } // ************************************************************************* /*! Turn the camera headlight on or off. Default is to have a headlight turned on. \sa isHeadlight(), getHeadlight() */ void So@Gui@Viewer::setHeadlight(SbBool on) { PRIVATE(this)->headlight->on = on; } // ************************************************************************* /*! Returns status of the viewer headlight, whether it is on or off. \sa setHeadlight(), getHeadlight() */ SbBool So@Gui@Viewer::isHeadlight(void) const { return PRIVATE(this)->headlight->on.getValue(); } // ************************************************************************* /*! Returns the a pointer to the directional light node which is the viewer headlight. The fields of the node is available for user editing. \sa isHeadlight(), setHeadlight() */ SoDirectionalLight * So@Gui@Viewer::getHeadlight(void) const { return PRIVATE(this)->headlight; } // ************************************************************************* /*! Set up a drawing style. The \a type argument specifies if the given \a style should be interpreted as the drawstyle during animation or when the camera is static. Default values for the drawing style is to render the scene "as is" in both still mode and while the camera is moving. See the documentation for the \a DrawType and \a DrawStyle for more information. \sa getDrawStyle() */ void So@Gui@Viewer::setDrawStyle(So@Gui@Viewer::DrawType type, So@Gui@Viewer::DrawStyle style) { if (SO@GUI@_DEBUG) { if ((type != STILL) && (type != INTERACTIVE)) { SoDebugError::postWarning("So@Gui@Viewer::setDrawStyle", "unknown drawstyle type setting 0x%x", type); return; } } if (style == this->getDrawStyle(type)) { if (SO@GUI@_DEBUG && 0) { // debug SoDebugError::postWarning("So@Gui@Viewer::setDrawStyle", "drawstyle for type 0x%02x already 0x%02x", type, style); } return; } PRIVATE(this)->drawstyles[type] = style; PRIVATE(this)->changeDrawStyle(PRIVATE(this)->currentDrawStyle()); } // ************************************************************************* /*! Return current drawstyles for the given type (\a STILL or \a INTERACTIVE). \sa setDrawStyle() */ So@Gui@Viewer::DrawStyle So@Gui@Viewer::getDrawStyle(const So@Gui@Viewer::DrawType type) const { if (SO@GUI@_DEBUG) { if ((type != STILL) && (type != INTERACTIVE)) { SoDebugError::postWarning("So@Gui@Viewer::setDrawStyle", "unknown drawstyle type setting 0x%x", type); return PRIVATE(this)->drawstyles[STILL]; } } return PRIVATE(this)->drawstyles[type]; } // ************************************************************************* /*! Set the viewer's buffer type. Available types are \c So@Gui@Viewer::BUFFER_SINGLE, \c So@Gui@Viewer::BUFFER_DOUBLE and \c So@Gui@Viewer::BUFFER_INTERACTIVE. (With a buffer type of \c So@Gui@Viewer::BUFFER_INTERACTIVE, the viewer will render with doublebuffering during user interaction and with single buffering otherwise.) Default is \c So@Gui@Viewer::BUFFER_DOUBLE. \sa getBufferingType() */ void So@Gui@Viewer::setBufferingType(So@Gui@Viewer::BufferType type) { if (type == PRIVATE(this)->buffertype) return; if (type != BUFFER_SINGLE && type != BUFFER_DOUBLE && type != BUFFER_INTERACTIVE) { if (SO@GUI@_DEBUG) { SoDebugError::postWarning("So@Gui@Viewer::setBufferingType", "unknown buffer type 0x%x", type); } return; } PRIVATE(this)->buffertype = type; PRIVATE(this)->localsetbuffertype = TRUE; inherited::setDoubleBuffer(type == BUFFER_DOUBLE); PRIVATE(this)->localsetbuffertype = FALSE; } // ************************************************************************* /*! Return the viewer's buffer type. \sa setBufferingType() */ So@Gui@Viewer::BufferType So@Gui@Viewer::getBufferingType(void) const { return PRIVATE(this)->buffertype; } // ************************************************************************* // Note: this documentation for setViewing() will also be used for all // the miscellaneous viewer subclasses, so keep it general. /*! Set view mode. If the view mode is on, user events will be caught and used to influence the camera position / orientation. If view mode is off, all events in the viewer canvas (like for instance keypresses or mouseclicks and -movements) will be passed along to the scene graph. Default is to have the view mode active. \sa isViewing() */ void So@Gui@Viewer::setViewing(SbBool enable) { if (PRIVATE(this)->viewingflag == enable) { if (SO@GUI@_DEBUG) { SoDebugError::postWarning("So@Gui@Viewer::setViewing", "unnecessary called"); } return; } PRIVATE(this)->viewingflag = enable; // Turn off the selection indicators when we go back from picking // mode into viewing mode. if (PRIVATE(this)->viewingflag) { SoGLRenderAction * action = this->getGLRenderAction(); if (action != NULL) SoLocateHighlight::turnOffCurrentHighlight(action); } } // ************************************************************************* /*! Return state of view mode. \c TRUE means that the mode of the viewer is set such that user interaction with the mouse is used to modify the position and orientation of the camera. \sa setViewing() */ SbBool So@Gui@Viewer::isViewing(void) const { return PRIVATE(this)->viewingflag; } // ************************************************************************* /*! Set whether or not the mouse cursor representation should be visible in the viewer canvas. Default value is on. \sa isCursorEnabled() */ void So@Gui@Viewer::setCursorEnabled(SbBool on) { PRIVATE(this)->cursoron = on; } // ************************************************************************* /*! Returns visibility status of mouse cursor. \sa setCursorEnabled() */ SbBool So@Gui@Viewer::isCursorEnabled(void) const { return PRIVATE(this)->cursoron; } // ************************************************************************* /*! Turn on or off continuous automatic adjustments of the near and far clipping planes. If on, the distance from the camera position to the near and far planes will be calculated to be a "best fit" around the geometry in the scene, to maximize the "stretch" of values for the visible geometry in the z-buffer. This is important, as z-buffer resolution is usually limited enough that one will quickly see flickering in the rasterization of close polygons upon lousy utilization of the z-buffer. Automatic calculations of near and far clip planes are on as default. For better control over what happens in boundary conditions (for instance when the distance between near and far planes get very far, or if geometry gets very close to the camera position), it is possible to use the So@Gui@Viewer::setAutoClippingStrategy() method to fine-tune the near/far clipping plane settings. On a major note, be aware that turning auto-updating of near and far clip planes \e off have a potentially serious detrimental effect on performance, due to an important side effect: updating the near and far clip planes triggers an SoGetBoundingBoxAction to traverse the scene graph, which causes bounding boxes to be calculated and stored in caches. The bounding box caches are then used by the SoGLRenderAction traversal for view frustum culling operations. With no bounding box caches, the rendering will not do culling, which can cause much worse performance. Kongsberg Oil & Gas Technologies are working on correcting this problem properly from within the Coin library. On a minor note, be aware that notifications will be temporarily turned off for the scene's SoCamera when changing the near and far clipping planes (which is done right before each redraw). This is done to avoid notifications being sent through the scene graph right before rendering, as that causes some latency. It is mentioned here in case you have any client code which for some reason needs to sense all changes to the scene camera. This is however unlikely, so you can very probably ignore this. \sa getAutoClipping() */ void So@Gui@Viewer::setAutoClipping(SbBool enable) { if (SO@GUI@_DEBUG) { if (PRIVATE(this)->adjustclipplanes == enable) { SoDebugError::postWarning("So@Gui@Viewer::setAutoClipping", "unnecessary called"); return; } } PRIVATE(this)->adjustclipplanes = enable; if (enable) { this->scheduleRedraw(); } } /*! Set the strategy used for automatic updates of the distances to the near and far clipping planes. When auto clipping is enabled, the near plane distance is calculated so that it is just in front of the scene bounding box. If this near plane is behind or very close to the projection point, one of the following strategies will be used to calculate the new clipping plane. The VARIABLE_NEAR_PLANE strategy considers the number of z buffer bits available for the current OpenGL context, and uses \a value to calculate the number of bits that is lost because of the far/near ratio. \a value should be in the range [0.0, 1.0]. A higher \a value will increase the z-buffer precision, but also push the near plane further away from the projection point. The CONSTANT_NEAR_PLANE strategy simply sets the near plane to \a value. If \a value at some point approaches the far clipping plane distance, the near plane distance will be set to far plane distance divided by 5000.0. The default strategy is VARIABLE_NEAR_PLANE. It is also possible to register a callback method \a cb, which will then be invoked after the near and far clipping planes has been calculated by the So@Gui@Viewer code. The callback can then adjust the values for the distance to the near and far planes to exactly match the needs of the application (for instance at specific parts in the scene), to limit the distance to either plane, or whatever else needs to be controlled. The signature of the So@Gui@AutoClippingCB callback must match: \code SbVec2f myfunc(void * data, const SbVec2f & nearfar); \endcode The first argument is the \a cbuserdata passed in along with the callback function pointer itself (ie the callback function's closure). The second argument is the near and far clipping plane distances from the camera position, as calculated internally by the viewer, including "slack". The function callback can then modify the near and far clipping plane distances to what will \e actually be used by the viewer. These values will then be used unmodified for the viewer's camera. This is a good way of dynamically modifying the near and far distances such that they at all times exactly matches the specific layout of the application scene, for instance with regard to the trade-off between z-buffer resolution and how early geometry is clipped at the near plane (or at the far plane). Note that the internal near/far calculations should be good enough for the vast majority of scenes. Application programmers should only need to set up their own adjustments upon "unusual" scenes, like for instance scenes with a large world space, but where one would still like to be able to get up extremely close on details in some parts of the scene. \sa setAutoClipping() */ void So@Gui@Viewer::setAutoClippingStrategy(const AutoClippingStrategy strategy, const float value, So@Gui@AutoClippingCB * cb, void * cbuserdata) { PRIVATE(this)->autoclipstrategy = strategy; PRIVATE(this)->autoclipvalue = value; PRIVATE(this)->autoclipcb = cb; PRIVATE(this)->autoclipuserdata = cbuserdata; if (PRIVATE(this)->autoclipstrategy == VARIABLE_NEAR_PLANE) { // normalize the value so that the near plane isn't too near or // too far from the projection point. FIXME: calibrate this // normalization, pederb, 2002-04-25 float v = So@Gui@Clamp(value, 0.0f, 1.0f); // just in case v *= 0.8f; v += 0.1f; // v will be in range [0.1, 0.9] PRIVATE(this)->autoclipvalue = v; } if (PRIVATE(this)->adjustclipplanes) { this->scheduleRedraw(); } } // ************************************************************************* /*! Return value of the automatic near/far clipplane adjustment indicator. \sa setAutoClipping() */ SbBool So@Gui@Viewer::isAutoClipping(void) const { return PRIVATE(this)->adjustclipplanes; } // ************************************************************************* /*! Turn stereo viewing on or off. Note: this function is being obsoleted, you should use the setStereoType() function instead. Coin does "correct" stereo rendering, using the method known as "parallel axis asymmetric frustum perspective projection". For more information, see this link: http://astronomy.swin.edu.au/~pbourke/opengl/stereogl/ \sa isStereoViewing(), setStereoType() */ void So@Gui@Viewer::setStereoViewing(SbBool enable) { PRIVATE(this)->stereoviewing = enable; this->scheduleRedraw(); } /*! Returns a boolean indicating whether or not we're in stereo viewing mode. NOTE: in the original InventorXt API, this method was virtual. It is not virtual here. \sa setStereoViewing(), getStereoType() */ SbBool So@Gui@Viewer::isStereoViewing(void) const { return PRIVATE(this)->stereoviewing; } // ************************************************************************* /*! \enum So@Gui@Viewer::StereoType Contains list of supported stereo rendering techniques. \sa So@Gui@Viewer::setStereoType() */ /*! \var So@Gui@Viewer::StereoType So@Gui@Viewer::STEREO_NONE Use monoscopic rendering. */ /*! \var So@Gui@Viewer::StereoType So@Gui@Viewer::STEREO_ANAGLYPH Render stereo by superimposing two images of the same scene, but with different color filters over the left and right view (or "eye"). This is a way of rendering stereo which works on any display, using color-filter glasses. Such glasses are usually cheap and easy to come by. \sa setAnaglyphStereoColorMasks() */ /*! \var So@Gui@Viewer::StereoType So@Gui@Viewer::STEREO_QUADBUFFER Render stereo by using OpenGL quad-buffers. This is the most common interface for stereo rendering for more expensive hardware devices, such as shutter glasses and polarized glasses. The well known Crystal Eyes glasses are commonly used with this type of stereo display. */ /*! \var So@Gui@Viewer::StereoType So@Gui@Viewer::STEREO_INTERLEAVED_ROWS Interleaving / interlacing rows from the left and right eye is another stereo rendering method requiring special hardware. One example of a provider of shutter glasses working with interleaved glasses is VRex: http://www.vrex.com/ */ /*! \var So@Gui@Viewer::StereoType So@Gui@Viewer::STEREO_INTERLEAVED_COLUMNS Same basic technique as So@Gui@Viewer::STEREO_INTERLEAVED_ROWS, only it is vertical lines that are interleaved / interlaced, instead of horizontal lines. */ /*! Set up stereo rendering. Coin does "correct" stereo rendering, using the method known as "parallel axis asymmetric frustum perspective projection". For more information, see this link: http://astronomy.swin.edu.au/~pbourke/opengl/stereogl/ Note: it is prefered that one uses this function for control of which type of stereo rendering to use, instead of the older So@Gui@Viewer::setStereoViewing() and So@Gui@GLWidget::setQuadBufferStereo() functions. The default is to do monoscopic rendering, i.e. the default So@Gui@Viewer::StereoType value is So@Gui@Viewer::STEREO_NONE. \sa So@Gui@Viewer::StereoType, SoCamera::setStereoAdjustment \since So@Gui@ 1.2 */ SbBool So@Gui@Viewer::setStereoType(So@Gui@Viewer::StereoType s) { if (s == this->getStereoType()) { return TRUE; } // We need to know this to keep compatibility with older client // code, which controlled stereo rendering with setStereoViewing() // and setQuadBufferStereo() only. PRIVATE(this)->stereotypesetexplicit = TRUE; switch (s) { case So@Gui@Viewer::STEREO_NONE: this->setQuadBufferStereo(FALSE); this->setStereoViewing(FALSE); break; case So@Gui@Viewer::STEREO_ANAGLYPH: this->setStereoViewing(TRUE); this->setQuadBufferStereo(FALSE); break; case So@Gui@Viewer::STEREO_QUADBUFFER: this->setStereoViewing(TRUE); this->setQuadBufferStereo(TRUE); // Check, in case GL quad buffers not supported with the driver // config: if (!this->isQuadBufferStereo()) { this->setStereoViewing(FALSE); return FALSE; } break; case So@Gui@Viewer::STEREO_INTERLEAVED_ROWS: case So@Gui@Viewer::STEREO_INTERLEAVED_COLUMNS: this->setStereoViewing(TRUE); this->setQuadBufferStereo(FALSE); this->setStencilBuffer(TRUE); // Check, in case GL stencil buffers not supported with the driver // config: if (!this->getStencilBuffer()) { this->setStereoViewing(FALSE); return FALSE; } break; default: assert(FALSE); break; } PRIVATE(this)->stereotype = s; return TRUE; } /*! Returns the current type of stereo rendering used (or So@Gui@Viewer::STEREO_NONE if monoscopic). */ So@Gui@Viewer::StereoType So@Gui@Viewer::getStereoType(void) const { // Stereo can be set up without using setStereoType() through the // older functions setStereoViewing() and setQuadBufferStereo(), so // we need to check for this separately. if (!PRIVATE(this)->stereotypesetexplicit) { if (this->isQuadBufferStereo()) { PRIVATE(this)->stereotype = So@Gui@Viewer::STEREO_QUADBUFFER; } else if (this->isStereoViewing()) { PRIVATE(this)->stereotype = So@Gui@Viewer::STEREO_ANAGLYPH; } } return PRIVATE(this)->stereotype; } // ************************************************************************* /*! If display is configured to render in anaglyph stereo, this function can be used to control which filter is used for each eye. The default filters are red (i.e. color vector [TRUE,FALSE,FALSE]) for the left eye, and cyan (color vector [FALSE,TRUE,TRUE]) for the right eye. \sa So@Gui@Viewer::StereoType, setStereoType() */ void So@Gui@Viewer::setAnaglyphStereoColorMasks(const SbBool left[3], const SbBool right[3]) { for (unsigned int i = 0; i < 3; i++) { PRIVATE(this)->stereoanaglyphmask[0][i] = left[i]; PRIVATE(this)->stereoanaglyphmask[1][i] = right[i]; } this->scheduleRedraw(); } /*! Returns color masks for left and right eye filters in anaglyph stereo. \sa setAnaglyphStereoColorMasks() */ void So@Gui@Viewer::getAnaglyphStereoColorMasks(SbBool left[3], SbBool right[3]) { for (unsigned int i = 0; i < 3; i++) { left[i] = PRIVATE(this)->stereoanaglyphmask[0][i]; right[i] = PRIVATE(this)->stereoanaglyphmask[1][i]; } } // ************************************************************************* /*! Set the offset between the two viewpoints when in stereo mode. Default value is 0.1. NOTE: In the original InventorXt API, this method was not virtual. \sa getStereoOffset() */ void So@Gui@Viewer::setStereoOffset(const float dist) { PRIVATE(this)->stereooffset = dist; this->scheduleRedraw(); } /*! Return the offset distance between the two viewpoints when in stereo mode. \sa setStereoOffset() */ float So@Gui@Viewer::getStereoOffset(void) const { return PRIVATE(this)->stereooffset; } // ************************************************************************* /*! Toggle between seeking to a point or seeking to an object. Default is to seek to a point. \sa isDetailSeek() */ void So@Gui@Viewer::setDetailSeek(const SbBool on) { if (SO@GUI@_DEBUG) { if (PRIVATE(this)->seektopoint == on) { SoDebugError::postWarning("So@Gui@Viewer::setDetailSeek", "unnecessary called"); return; } } PRIVATE(this)->seektopoint = on; } // ************************************************************************* /*! Returns a value indicating whether or not seeks will be performed to the exact point of picking or just towards the picked object. \sa setDetailSeek() */ SbBool So@Gui@Viewer::isDetailSeek(void) const { return PRIVATE(this)->seektopoint; } // ************************************************************************* /*! Set the duration of animating the camera repositioning after a successful seek. Call with \a seconds equal to \a 0.0 to make the camera jump immediately to the correct spot. Default value is 2 seconds. \sa getSeekTime() */ void So@Gui@Viewer::setSeekTime(const float seconds) { if (seconds < 0.0f) { if (SO@GUI@_DEBUG) { SoDebugError::postWarning("So@Gui@Viewer::setSeekTime", "an attempt was made to set a negative seek " "time duration"); } return; } PRIVATE(this)->seekperiod = seconds; } // ************************************************************************* /*! Returns the camera repositioning duration following a seek action. \sa setSeekTime() */ float So@Gui@Viewer::getSeekTime(void) const { return PRIVATE(this)->seekperiod; } // ************************************************************************* /*! Add a function to call when user interaction with the scene starts. \sa removeStartCallback(), addFinishCallback() */ void So@Gui@Viewer::addStartCallback(So@Gui@ViewerCB * func, void * data) { PRIVATE(this)->interactionstartCallbacks->addCallback((SoCallbackListCB *)func, data); } /*! Remove one of the functions which has been set up to be called when user interaction with the scene starts. \sa addStartCallback(), removeFinishCallback() */ void So@Gui@Viewer::removeStartCallback(So@Gui@ViewerCB * func, void * data) { PRIVATE(this)->interactionstartCallbacks->removeCallback((SoCallbackListCB *)func, data); } // ************************************************************************* /*! Add a function to call when user interaction with the scene ends. \sa removeFinishCallback(), addStartCallback() */ void So@Gui@Viewer::addFinishCallback(So@Gui@ViewerCB * func, void * data) { PRIVATE(this)->interactionendCallbacks->addCallback((SoCallbackListCB *)func, data); } /*! Remove one of the functions which has been set up to be called when user interaction with the scene ends. \sa addFinishCallback(), removeStartCallback() */ void So@Gui@Viewer::removeFinishCallback(So@Gui@ViewerCB * func, void * data) { PRIVATE(this)->interactionendCallbacks->removeCallback((SoCallbackListCB *)func, data); } // ************************************************************************* /*! Set the color of the overlay wireframe to \a color. \sa getWireframeOverlayColor() */ void So@Gui@Viewer::setWireframeOverlayColor(const SbColor & color) { PRIVATE(this)->wireframeoverlaycolor = color; this->scheduleRedraw(); } // ************************************************************************* /*! Returns the current color of the overlay wireframe. The default color is [1,0,0], ie pure red. \sa setWireframeOverlayColor() */ const SbColor &So@Gui@Viewer::getWireframeOverlayColor(void) const { return PRIVATE(this)->wireframeoverlaycolor; } // ************************************************************************* /*! Overloaded to update the local bufferingtype variable. \sa setBufferingType(), getBufferingType() */ void So@Gui@Viewer::setDoubleBuffer(const SbBool on) { if (!PRIVATE(this)->localsetbuffertype) PRIVATE(this)->buffertype = on ? BUFFER_DOUBLE : BUFFER_SINGLE; inherited::setDoubleBuffer(on); } // ************************************************************************* /*! Give the viewer a scenegraph to render and interact with. Overridden from parent class so the viewer can add it's own nodes to control rendering in different styles, rendering with a headlight, etc. The \a root node will be inserted under the \e viewer's root node, which also covers the nodes necessary to implement the different preferences drawing style settings. If no camera is part of the scene graph under \a root, one will automatically be instantiated and added. You can get a reference to this camera by using the So@Gui@Viewer::getCamera() method. \sa getSceneGraph(), setCameraType() */ void So@Gui@Viewer::setSceneGraph(SoNode * root) { if ((root != NULL) && (root == PRIVATE(this)->scenegraph)) { if (SO@GUI@_DEBUG) { SoDebugError::postWarning("So@Gui@Viewer::setSceneGraph", "called with the same root as already set"); } return; } // If the So@Gui@RenderArea hasn't yet set up its pointer to the // So@Gui@Viewer "viewer root" (i.e. the viewer-generated root above // the user-supplied root), do that first. if (!inherited::getSceneGraph()) inherited::setSceneGraph(PRIVATE(this)->sceneroot); if (PRIVATE(this)->scenegraph) { if (this->getCamera()) this->setCamera(NULL); // Release the old user-supplied graph. PRIVATE(this)->usersceneroot->removeChild(PRIVATE(this)->scenegraph); // old: PRIVATE(this)->sceneroot->removeChild(PRIVATE(this)->scenegraph); } PRIVATE(this)->scenegraph = root; if (!root) return; PRIVATE(this)->usersceneroot->addChild(PRIVATE(this)->scenegraph); // Search for a camera in the user-supplied scenegraph. SbBool oldsearch = SoBaseKit::isSearchingChildren(); SoBaseKit::setSearchingChildren(TRUE); PRIVATE(this)->searchaction->reset(); PRIVATE(this)->searchaction->setType(SoCamera::getClassTypeId()); PRIVATE(this)->searchaction->apply(PRIVATE(this)->scenegraph); SoBaseKit::setSearchingChildren(oldsearch); SoCamera * scenecamera = NULL; if ( PRIVATE(this)->searchaction->getPath() != NULL ) { SoFullPath * fullpath = (SoFullPath *) PRIVATE(this)->searchaction->getPath(); scenecamera = (SoCamera *)fullpath->getTail(); } #if 0 // debug SoDebugError::postInfo("So@Gui@Viewer::setSceneGraph", "camera %sfound in graph", scenecamera ? "" : "not "); #endif // debug // Make our own camera if none was available. if (!scenecamera) { if (SoGuiViewpointWrapper::hasViewpoints(root)) { scenecamera = new SoGuiViewpointWrapper; PRIVATE(this)->cameratype = SoGuiViewpointWrapper::getClassTypeId(); ((SoGuiViewpointWrapper*)scenecamera)->setSceneGraph(root); } else { scenecamera = (SoCamera *) PRIVATE(this)->cameratype.createInstance(); } // If type==BROWSER, camera should be inserted in the private // viewer "supergraph", if it's equal to EDITOR it should be // inserted in the user-supplied scenegraph. if (PRIVATE(this)->type == So@Gui@Viewer::BROWSER) { PRIVATE(this)->sceneroot->insertChild(scenecamera, 1); } else { // PRIVATE(this)->type == So@Gui@Viewer::EDITOR if (PRIVATE(this)->scenegraph->isOfType(SoGroup::getClassTypeId())) { // At the uppermost leftmost position in the user-supplied // scenegraph. ((SoGroup *)PRIVATE(this)->scenegraph)->insertChild(scenecamera, 0); } else { // Make an extra depth level to fit the camera node into the // user-scenegraph. SoGroup * g = new SoGroup; g->addChild(scenecamera); g->addChild(PRIVATE(this)->scenegraph); PRIVATE(this)->usersceneroot->removeChild(PRIVATE(this)->scenegraph); PRIVATE(this)->usersceneroot->addChild(g); PRIVATE(this)->scenegraph = g; } } if (PRIVATE(this)->cameratype != SoGuiViewpointWrapper::getClassTypeId()) { scenecamera->viewAll(PRIVATE(this)->scenegraph, this->getViewportRegion()); } } this->setCamera(scenecamera); } // ************************************************************************* // doc in super SoNode * So@Gui@Viewer::getSceneGraph(void) { // Overloaded from parent class to return the root of the scene // graph set by the user, without the extras added by the viewer to // control rendering. return PRIVATE(this)->scenegraph; } // ************************************************************************* // Note: the following function documentation block will also be used // for all the miscellaneous viewer subclasses, so keep it general. /*! Put the viewer in or out of "waiting-to-seek" mode. If the user performs a mouse button click when the viewer is in "waiting-to-seek" mode, the camera will be repositioned so the camera focal point lies on the point of the geometry under the mouse cursor. \sa isSeekMode(), setDetailSeek() */ void So@Gui@Viewer::setSeekMode(SbBool enable) { if (SO@GUI@_DEBUG) { // User might have switched mode during seek, so if enable==FALSE, // isViewing() is irrelevant. if (enable) { assert(this->isViewing()); } } if (!enable && PRIVATE(this)->seeksensor->isScheduled()) { PRIVATE(this)->seeksensor->unschedule(); this->interactiveCountDec(); } PRIVATE(this)->inseekmode = enable; } // ************************************************************************* /*! Return a flag which indicates whether or not the viewer is in "waiting-to-seek" mode. (The actual animated translation will not occur until the end user really \e starts the seek operation, typically by clicking with the left mousebutton.) \sa setSeekMode() */ SbBool So@Gui@Viewer::isSeekMode(void) const { return PRIVATE(this)->inseekmode; } // ************************************************************************* /*! Call this method to initiate a seek action towards the 3D intersection of the scene and the ray from the screen coordinate's point and in the same direction as the camera is pointing. Returns \c TRUE if the ray from the \a screenpos position intersect with any parts of the onscreen geometry, otherwise \c FALSE. */ SbBool So@Gui@Viewer::seekToPoint(const SbVec2s screenpos) { if (! PRIVATE(this)->camera) return FALSE; SoRayPickAction rpaction(this->getViewportRegion()); rpaction.setPoint(screenpos); rpaction.setRadius(2); rpaction.apply(PRIVATE(this)->sceneroot); SoPickedPoint * picked = rpaction.getPickedPoint(); if (!picked) { // FIXME: this inc seems bogus, but is needed now due to buggy // code in for instance the examinerviewer // processSoEvent(). 20020510 mortene. #if 1 this->interactiveCountInc(); // decremented in setSeekMode(FALSE) #endif // FIXME this->setSeekMode(FALSE); return FALSE; } SbVec3f hitpoint; if (PRIVATE(this)->seektopoint) { hitpoint = picked->getPoint(); } else { SoGetBoundingBoxAction bbaction(this->getViewportRegion()); bbaction.apply(picked->getPath()); SbBox3f bbox = bbaction.getBoundingBox(); hitpoint = bbox.getCenter(); } this->seekToPoint(hitpoint); return TRUE; } /*! Call this method to initiate a seek action towards the give 3D world coordinate point in the scene, \a scenepos. \since So@Gui@ 1.3.0 */ void So@Gui@Viewer::seekToPoint(const SbVec3f & scenepos) { SbVec3f hitpoint(scenepos); PRIVATE(this)->camerastartposition = PRIVATE(this)->camera->position.getValue(); PRIVATE(this)->camerastartorient = PRIVATE(this)->camera->orientation.getValue(); // move point to the camera coordinate system, consider // transformations before camera in the scene graph SbMatrix cameramatrix, camerainverse; PRIVATE(this)->getCameraCoordinateSystem(PRIVATE(this)->camera, PRIVATE(this)->sceneroot, cameramatrix, camerainverse); camerainverse.multVecMatrix(hitpoint, hitpoint); float fd = PRIVATE(this)->seekdistance; if (!PRIVATE(this)->seekdistanceabs) fd *= (hitpoint - PRIVATE(this)->camera->position.getValue()).length()/100.0f; PRIVATE(this)->camera->focalDistance = fd; SbVec3f dir = hitpoint - PRIVATE(this)->camerastartposition; dir.normalize(); // find a rotation that rotates current camera direction into new // camera direction. SbVec3f olddir; PRIVATE(this)->camera->orientation.getValue().multVec(SbVec3f(0, 0, -1), olddir); SbRotation diffrot(olddir, dir); PRIVATE(this)->cameraendposition = hitpoint - fd * dir; PRIVATE(this)->cameraendorient = PRIVATE(this)->camera->orientation.getValue() * diffrot; // Subclasses that want another cameraendorient than what is // computed here should override this function and set the desired // orientation there this->computeSeekFinalOrientation(); if (PRIVATE(this)->seeksensor->isScheduled()) { PRIVATE(this)->seeksensor->unschedule(); this->interactiveCountDec(); } PRIVATE(this)->seeksensor->setBaseTime(SbTime::getTimeOfDay()); PRIVATE(this)->seeksensor->schedule(); this->interactiveCountInc(); } // ************************************************************************* void So@Gui@ViewerP::setStereoEye(SoCamera * thecamera, const So@Gui@ViewerP::Eye eye, So@Gui@ViewerP::StereoData & s) const { #ifdef HAVE_SOCAMERA_SETSTEREOMODE // SoCamera::setStereoMode() is a fairly recent addition to Coin and // TGS Inventor. If available, camera eye setup is quite // straightforward. if (eye == So@Gui@ViewerP::LEFT) { thecamera->setStereoAdjustment(PUBLIC(this)->getStereoOffset()); thecamera->setStereoMode(SoCamera::LEFT_VIEW); } else if (eye == So@Gui@ViewerP::RIGHT) { thecamera->setStereoMode(SoCamera::RIGHT_VIEW); } else { assert(eye == So@Gui@ViewerP::RESTORE); thecamera->setStereoMode(SoCamera::MONOSCOPIC); } #else // ! HAVE_SOCAMERA_SETSTEREOMODE // To support older versions of Coin, and SGI/TGS Inventor, we also // provide "manual" tuning of the camera left/right eye split. if (eye == So@Gui@ViewerP::LEFT) { s.camerapos = thecamera->position.getValue(); s.cameradir.setValue(0.0f, 0.0f, -1.0f); s.offsetvec.setValue(1.0f, 0.0f, 0.0f); s.offset = PUBLIC(this)->getStereoOffset() * 0.5f; s.camerarot = thecamera->orientation.getValue(); s.camerarot.multVec(s.cameradir, s.cameradir); s.camerarot.multVec(s.offsetvec, s.offsetvec); s.focalpoint = s.camerapos + s.cameradir * thecamera->focalDistance.getValue(); s.nodenotify = thecamera->isNotifyEnabled(); s.positionnotify = thecamera->position.isNotifyEnabled(); s.orientationnotify = thecamera->orientation.isNotifyEnabled(); // turn off notification to avoid redraws thecamera->enableNotify(FALSE); thecamera->position.enableNotify(FALSE); thecamera->orientation.enableNotify(FALSE); thecamera->position = s.camerapos - s.offsetvec * s.offset; SbVec3f dir = s.focalpoint - thecamera->position.getValue(); SbRotation rot(s.cameradir, dir); thecamera->orientation = s.camerarot * rot; } else if (eye == So@Gui@ViewerP::RIGHT) { thecamera->position = s.camerapos + s.offsetvec * s.offset; SbVec3f dir = s.focalpoint - thecamera->position.getValue(); SbRotation rot(s.cameradir, dir); thecamera->orientation = s.camerarot * rot; } else { assert(eye == So@Gui@ViewerP::RESTORE); thecamera->position = s.camerapos; thecamera->orientation = s.camerarot; thecamera->position.enableNotify(s.positionnotify); thecamera->orientation.enableNotify(s.orientationnotify); thecamera->enableNotify(s.nodenotify); } #endif // ! HAVE_SOCAMERA_SETSTEREOMODE } void So@Gui@ViewerP::initStencilBufferForInterleavedStereo(void) { const SbViewportRegion & currentvp = PUBLIC(this)->getViewportRegion(); if (this->stereostencilmaskvp == currentvp) { return; } // the common case So@Gui@Viewer::StereoType s = PUBLIC(this)->getStereoType(); assert((s == So@Gui@Viewer::STEREO_INTERLEAVED_ROWS) || (s == So@Gui@Viewer::STEREO_INTERLEAVED_COLUMNS)); // Find out whether or not we need to regenerate the mask data. SbBool allocnewmask = (this->stereostencilmask == NULL); const SbVec2s neworigin = currentvp.getViewportOriginPixels(); const SbVec2s newsize = currentvp.getViewportSizePixels(); const SbVec2s oldorigin = this->stereostencilmaskvp.getViewportOriginPixels(); const SbVec2s oldsize = this->stereostencilmaskvp.getViewportSizePixels(); allocnewmask = allocnewmask || ((oldsize[0] + 7) / 8 * oldsize[1]) < ((newsize[0] + 7) / 8 * newsize[1]); const SbBool fillmask = allocnewmask || (this->stereostenciltype != s) || ((s == So@Gui@Viewer::STEREO_INTERLEAVED_ROWS) && (oldsize[0] != newsize[0])); const SbBool layoutchange = !(this->stereostencilmaskvp == currentvp); const short bytewidth = (newsize[0] + 7) / 8; if (allocnewmask) { delete[] this->stereostencilmask; this->stereostencilmask = new GLubyte[bytewidth * newsize[1]]; } this->stereostencilmaskvp = currentvp; if (fillmask) { GLubyte * mask = this->stereostencilmask; if (s == So@Gui@Viewer::STEREO_INTERLEAVED_COLUMNS) { // alternating columns of 0's and 1's (void)memset(mask, 0x55, bytewidth * newsize[1]); } else { // alternating rows of 0's and 1's for (short h=0; h < newsize[1]; h++) { const GLubyte fill = (h % 2) ? 0xff : 0x00; (void)memset(mask + (h * bytewidth), fill, bytewidth); } } this->stereostenciltype = s; } if (layoutchange) { glClearStencil(0x0); glClear(GL_STENCIL_BUFFER_BIT); glStencilFunc(GL_ALWAYS, GL_REPLACE, GL_REPLACE); glMatrixMode(GL_MODELVIEW); glPushMatrix(); glLoadIdentity(); glMatrixMode(GL_PROJECTION); glPushMatrix(); glLoadIdentity(); glViewport(neworigin[0], neworigin[1], newsize[0], newsize[1]); glOrtho(0, newsize[0], 0, newsize[1], -1.0f, 1.0f); glPixelStorei(GL_UNPACK_ALIGNMENT, 1); // FIXME: I've noticed a problem with this approach. If there is // something in the window system obscuring part of the canvas // while this is called (as could e.g. happen with a size // indicator, as with the Sawfish window manager), the stencil // mask will not be set up for that part. 20041019 mortene. // // UPDATE 20041019 mortene: discussed this with pederb, and we // believe this may be due to a bug in either the OpenGL driver // (Nvidia 61.11, Linux) or window system or manager (Sawfish, // XFree86 v4.1.0.1). Should test on other systems to see if they // show the same artifact. glRasterPos2f(0, 0); glDrawPixels(newsize[0], newsize[1], GL_STENCIL_INDEX, GL_BITMAP, this->stereostencilmask); glMatrixMode(GL_PROJECTION); glPopMatrix(); glMatrixMode(GL_MODELVIEW); glPopMatrix(); } } // Documented in superclass. Overridden from parent class to be able // to do the necessary two-pass rendering e.g. if the drawing style is // hidden line. void So@Gui@Viewer::actualRedraw(void) { SbTime redrawtime = SbTime::getTimeOfDay(); const SbBool clearcol = this->isClearBeforeRender(); const SbBool clearz = this->isClearZBufferBeforeRender(); const So@Gui@Viewer::StereoType stereotype = this->getStereoType(); if (stereotype != So@Gui@Viewer::STEREO_NONE) { #if HAVE_SBCOLOR4F_GETBACKGROUNDCOLOR const SbColor4f bgcol = this->getSceneManager()->getBackgroundColor(); #else const SbColor4f bgcol(this->getSceneManager()->getBackgroundColor(), 0.0f); #endif SoCamera * camera = this->getCamera(); So@Gui@ViewerP::StereoData tmpstorage; // Render left eye: PRIVATE(this)->setStereoEye(camera, So@Gui@ViewerP::LEFT, tmpstorage); switch (stereotype) { case So@Gui@Viewer::STEREO_ANAGLYPH: glDrawBuffer(this->isDoubleBuffer() ? GL_BACK : GL_FRONT); glClearColor(bgcol[0], bgcol[1], bgcol[2], 0.0f); glClear(GL_DEPTH_BUFFER_BIT|GL_COLOR_BUFFER_BIT); glColorMask(PRIVATE(this)->stereoanaglyphmask[0][0] ? GL_TRUE : GL_FALSE, PRIVATE(this)->stereoanaglyphmask[0][1] ? GL_TRUE : GL_FALSE, PRIVATE(this)->stereoanaglyphmask[0][2] ? GL_TRUE : GL_FALSE, GL_TRUE); PRIVATE(this)->reallyRedraw(FALSE, FALSE); break; case So@Gui@Viewer::STEREO_QUADBUFFER: glDrawBuffer(this->isDoubleBuffer() ? GL_BACK_LEFT : GL_FRONT_LEFT); PRIVATE(this)->reallyRedraw(clearcol, clearz); break; case So@Gui@Viewer::STEREO_INTERLEAVED_ROWS: case So@Gui@Viewer::STEREO_INTERLEAVED_COLUMNS: PRIVATE(this)->initStencilBufferForInterleavedStereo(); glEnable(GL_STENCIL_TEST); // immutable data in the stencil buffer glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP); glStencilFunc(GL_EQUAL, 0x1, 0x1); PRIVATE(this)->reallyRedraw(clearcol, clearz); break; default: assert(FALSE); break; } // Render right eye: PRIVATE(this)->setStereoEye(camera, So@Gui@ViewerP::RIGHT, tmpstorage); switch (stereotype) { case So@Gui@Viewer::STEREO_ANAGLYPH: glColorMask(PRIVATE(this)->stereoanaglyphmask[1][0] ? GL_TRUE : GL_FALSE, PRIVATE(this)->stereoanaglyphmask[1][1] ? GL_TRUE : GL_FALSE, PRIVATE(this)->stereoanaglyphmask[1][2] ? GL_TRUE : GL_FALSE, GL_TRUE); PRIVATE(this)->reallyRedraw(FALSE, TRUE); break; case So@Gui@Viewer::STEREO_QUADBUFFER: glDrawBuffer(this->isDoubleBuffer() ? GL_BACK_RIGHT : GL_FRONT_RIGHT); PRIVATE(this)->reallyRedraw(clearcol, clearz); break; case So@Gui@Viewer::STEREO_INTERLEAVED_ROWS: case So@Gui@Viewer::STEREO_INTERLEAVED_COLUMNS: glStencilFunc(GL_NOTEQUAL, 0x1, 0x1); PRIVATE(this)->reallyRedraw(FALSE, FALSE); break; default: assert(FALSE); break; } // Clean-up, post-rendering: PRIVATE(this)->setStereoEye(camera, So@Gui@ViewerP::RESTORE, tmpstorage); switch (stereotype) { case So@Gui@Viewer::STEREO_ANAGLYPH: glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); // restore GL color mask break; case So@Gui@Viewer::STEREO_QUADBUFFER: glDrawBuffer(this->isDoubleBuffer() ? GL_BACK : GL_FRONT); break; case So@Gui@Viewer::STEREO_INTERLEAVED_ROWS: case So@Gui@Viewer::STEREO_INTERLEAVED_COLUMNS: // FIXME: restore old val. 20040618 mortene. glDisable(GL_STENCIL_TEST); break; default: assert(FALSE); break; } } else { // No stereo: PRIVATE(this)->reallyRedraw(clearcol, clearz); } if (PRIVATE(this)->superimpositions != NULL) { SoGLRenderAction * raaction = this->getSceneManager()->getGLRenderAction(); SbBool first = TRUE; SbBool zWasEnabled = FALSE; for (int i = 0; i < PRIVATE(this)->superimpositions->getLength(); i++) { if (PRIVATE(this)->superimpositionsenabled[i] != FALSE) { if (first) { // save Z buffer state and disable zWasEnabled = glIsEnabled(GL_DEPTH_TEST) ? TRUE : FALSE; glDisable(GL_DEPTH_TEST); first = FALSE; } SoNode * scene = (SoNode *) (*PRIVATE(this)->superimpositions)[i]; raaction->apply(scene); } } if (!first && zWasEnabled) glEnable(GL_DEPTH_TEST); } redrawtime = SbTime::getTimeOfDay() - redrawtime; PRIVATE(this)->recordFPS(redrawtime.getValue()); } // ************************************************************************* /*! To be able to trigger callback functions when user interaction starts and/or stops, we need to keep track of the viewer state (i.e. are we in still mode or in animation mode?). So@Gui@Viewer automatically adds callbacks to switch between still and moving draw style, and to switch between single/double buffer when the buffer type is \a INTERACTIVE. \sa interactiveCountDec(), getInteractiveCount() \sa addStartCallback(), addFinishCallback() \sa removeStartCallback(), removeFinishCallback() \sa setDrawStyle(), setBufferingType() */ void So@Gui@Viewer::interactiveCountInc(void) { // Catch problems with missing interactiveCountDec() calls. assert(PRIVATE(this)->interactionnesting < 100); if (++(PRIVATE(this)->interactionnesting) == 1) { PRIVATE(this)->interactionstartCallbacks->invokeCallbacks(this); PRIVATE(this)->resetFrameCounter(); } #if 0 // debug SoDebugError::postInfo("So@Gui@Viewer::interactiveCountInc", "%d -> %d", PRIVATE(this)->interactionnesting - 1, PRIVATE(this)->interactionnesting); #endif // debug } // ************************************************************************* /*! To be able to trigger callback functions when user interaction starts and/or stops, we need to keep track of the viewer state (i.e. are we in still mode or in animation mode?). So@Gui@Viewer automatically adds callbacks to switch between still and moving draw style, and to switch between single/double buffer when the buffer type is \a INTERACTIVE. \sa interactiveCountInc(), getInteractiveCount() \sa addStartCallback(), addFinishCallback() \sa removeStartCallback(), removeFinishCallback() \sa setDrawStyle(), setBufferingType() */ void So@Gui@Viewer::interactiveCountDec(void) { // FIXME: The UI toolkits may cause the interactionnesting to go // below zero by triggering press and release events in different // widgets. mariusbu 20010709. // FIXME: just to clarify; this is due to programming mistakes on // our behalf and should be cleaned up. We're using a simple // work-around / ignore strategy for now, though, as getting this // 100% correct is hard (there are so many possible ways of user // interaction with a viewer canvas) and the end-user will usually // not notice any problems at all. So that's why we are using a // warning instead of an assert(). 20010815 mortene. // FIXME: here's one known way to trigger the bug: hit "s" in the // examinerviewer in EXAMINE mode, then while seeking hit ESC to put // the viewer in INTERACT mode. When the seek is completed, the // count will become -1. 20010912 mortene. // FIXME: and another one (tested with SoXt): click and hold LMB in // the canvas while in INTERACT mode, then hit 'Esc' to switch to // EXAMINE mode, then release LMB. 20020325 mortene. if (SO@GUI@_DEBUG) { if (PRIVATE(this)->interactionnesting <= 0) { SoDebugError::postWarning("So@Gui@Viewer::interactiveCountDec", "interaction count nesting went below zero. " "This is due to an internal So@Gui@ bug."); } } if (--(PRIVATE(this)->interactionnesting) <= 0) { PRIVATE(this)->interactionendCallbacks->invokeCallbacks(this); PRIVATE(this)->interactionnesting = 0; } } // ************************************************************************* /*! Return current interaction count nesting. If equal to zero, the viewer is in animation mode, otherwise the camera is still. \sa interactiveCountInc(), interactiveCountDec() */ int So@Gui@Viewer::getInteractiveCount(void) const { return PRIVATE(this)->interactionnesting; } // ************************************************************************* /*! Set the value used for calculating how close the camera and intersection hit point should be made at the end of a seek operation. The value can be interpreted as an absolute value in the given world unit (which typically is meters) or as a percentage value of the distance between the camera starting position and the intersection hit point. This can be controlled through the setSeekValueAsPercentage() method. It is as default used as an absolute value. Default value is 50 (absolute distance or percent). \sa getSeekDistance(), setSeekValueAsPercentage(), setSeekTime() */ void So@Gui@Viewer::setSeekDistance(const float distance) { if (distance <= 0.0f) { if (SO@GUI@_DEBUG) { SoDebugError::postWarning("So@Gui@Viewer::setSeekDistance", "invalid seek distance value: %f", distance); } return; } PRIVATE(this)->seekdistance = distance; } // ************************************************************************* /*! Returns the current seek distance. Value given as an absolute scalar length or as a percentage value of the original distance between the hitpoint and the camera starting position. \sa setSeekDistance(), isSeekValueAsPercentage() */ float So@Gui@Viewer::getSeekDistance(void) const { return PRIVATE(this)->seekdistance; } // ************************************************************************* /*! Control whether or not the seek distance value should be interpreted as a percentage value or as an absolute distance. See documentation on setSeekDistance() for more information. \sa setSeekDistance(), isSeekValueAsPercentage() */ void So@Gui@Viewer::setSeekValueAsPercentage(const SbBool on) { if (SO@GUI@_DEBUG) { if ((on && this->isSeekValuePercentage()) || (!on && !this->isSeekValuePercentage())) { SoDebugError::postWarning("So@Gui@Viewer::setSeekDistanceAsPercentage", "unnecessary called, value already %s", on ? "on" : "off"); return; } } PRIVATE(this)->seekdistanceabs = on ? FALSE : TRUE; } // ************************************************************************* /*! Returns an boolean which indicates if the seek distance value from getSeekDistance() should be interpreted as a percentage value or as an absolute value. \sa setSeekValuePercentage(), getSeekDistance() */ SbBool So@Gui@Viewer::isSeekValuePercentage(void) const { return PRIVATE(this)->seekdistanceabs ? FALSE : TRUE; } // ************************************************************************ /*! This method can be overridden in subclasses if the final orientation of the camera after a seek should be something other than what is computed in So@Gui@Viewer::seekToPoint(const SbVec3f & scenepos) */ void So@Gui@Viewer::computeSeekFinalOrientation(void) { } // ************************************************************************* /*! If the current camera is of perspective type, switch to orthographic, and vice versa. Automatically calls So@Gui@Viewer::setCameraType() so the change will immediately take place. */ void So@Gui@Viewer::toggleCameraType(void) { SoType perspectivetype = SoPerspectiveCamera::getClassTypeId(); SoType orthotype = SoOrthographicCamera::getClassTypeId(); this->setCameraType(PRIVATE(this)->cameratype.isDerivedFrom(perspectivetype) ? orthotype : perspectivetype); } // ************************************************************************ /*! Copies the settings of \a camera into our current camera. Cameras must be of the same class type. */ void So@Gui@Viewer::changeCameraValues(// virtual, protected SoCamera * camera) { assert(camera != NULL); SoCamera * cam = this->getCamera(); if (!cam) { if (SO@GUI@_DEBUG) { SoDebugError::postWarning("So@Gui@Viewer::changeCameraValues", "no current camera in the scenegraph"); } return; } if (cam->getTypeId() != camera->getTypeId()) { if (SO@GUI@_DEBUG) { SoDebugError::postWarning("So@Gui@Viewer::changeCameraValues", "tried to copy data from camera of " "different type"); } return; } cam->copyFieldValues(camera, FALSE); } // ************************************************************************* // doc in super void So@Gui@Viewer::sizeChanged(const SbVec2s & size) { inherited::sizeChanged(size); } // ************************************************************************* // Documented in superclass. SbBool So@Gui@Viewer::processSoEvent(const SoEvent * const event) { const SoType type(event->getTypeId()); const SoKeyboardEvent * keyevent = NULL; if (type.isDerivedFrom(SoKeyboardEvent::getClassTypeId())) { keyevent = (SoKeyboardEvent *) event; switch (keyevent->getKey()) { // the ESC key switches between view and interact mode case SoKeyboardEvent::ESCAPE: if (keyevent->getState() == SoButtonEvent::DOWN) { this->setViewing(this->isViewing() ? FALSE : TRUE); return TRUE; } break; // Let the end-user toggle between camera-interaction mode // ("viewing") and scenegraph-interaction mode with ALT key(s). case SoKeyboardEvent::LEFT_ALT: case SoKeyboardEvent::RIGHT_ALT: if (!this->isViewing() && (keyevent->getState() == SoButtonEvent::DOWN)) { PRIVATE(this)->altdown = TRUE; this->setViewing(TRUE); return TRUE; } else if (PRIVATE(this)->altdown && (keyevent->getState() == SoButtonEvent::UP)) { this->setViewing(FALSE); PRIVATE(this)->altdown = FALSE; return TRUE; } break; default: break; } } // If not viewing, break off further handling and pass the event on // to the So@Gui@RenderArea, which will pass it on to the // scenegraph. if (!this->isViewing()) { return inherited::processSoEvent(event); } if (keyevent && (keyevent->getState() == SoButtonEvent::DOWN)) { switch (keyevent->getKey()) { case SoKeyboardEvent::S: this->setSeekMode(this->isSeekMode() ? FALSE : TRUE); return TRUE; case SoKeyboardEvent::HOME: this->resetToHomePosition(); return TRUE; case SoKeyboardEvent::LEFT_ARROW: PRIVATE(this)->moveCameraScreen(SbVec2f(-0.1f, 0.0f)); return TRUE; case SoKeyboardEvent::UP_ARROW: PRIVATE(this)->moveCameraScreen(SbVec2f(0.0f, 0.1f)); return TRUE; case SoKeyboardEvent::RIGHT_ARROW: PRIVATE(this)->moveCameraScreen(SbVec2f(0.1f, 0.0f)); return TRUE; case SoKeyboardEvent::DOWN_ARROW: PRIVATE(this)->moveCameraScreen(SbVec2f(0.0f, -0.1f)); return TRUE; default: break; } } if (this->isSeekMode()) { if (type.isDerivedFrom(SoMouseButtonEvent::getClassTypeId())) { SoMouseButtonEvent * const e = (SoMouseButtonEvent *) event; if (e->getButton() == SoMouseButtonEvent::BUTTON1) { if (e->getState() == SoButtonEvent::DOWN) { this->seekToPoint(e->getPosition()); } else { // We got an LMB UP-event while in seek-mode, and we just // swallow the event. } return TRUE; } } } return FALSE; } // ************************************************************************* /*! This method is for setting up a superimposed scene graph on top of the viewer scene graph. It will be used for adding spin-rotation coordinate systems, fly-viewer speed indicators and similar things. This method is not part of the original InventorXt API. */ void So@Gui@Viewer::addSuperimposition(SoNode * scene) { if (PRIVATE(this)->superimpositions == NULL) PRIVATE(this)->superimpositions = new SbPList; assert(scene != NULL); scene->ref(); PRIVATE(this)->searchaction->reset(); PRIVATE(this)->searchaction->setType(SoCamera::getClassTypeId()); PRIVATE(this)->searchaction->setInterest(SoSearchAction::FIRST); PRIVATE(this)->searchaction->apply(scene); if (PRIVATE(this)->searchaction->getPath() == NULL) { // FIXME: set up default environment if there is no camera in the // superimposition scene - or not... if (SO@GUI@_DEBUG) { SoDebugError::postInfo("So@Gui@Viewer::addSuperimposition", "cameraless superimpositions are not " "supported"); } scene->unrefNoDelete(); return; } PRIVATE(this)->superimpositions->append(scene); PRIVATE(this)->superimpositionsenabled.append(TRUE); } // ************************************************************************* /*! This method is not part of the original InventorXt API. */ void So@Gui@Viewer::removeSuperimposition(SoNode * scene) { assert(scene); int idx = -1; if (PRIVATE(this)->superimpositions == NULL) goto error; idx = PRIVATE(this)->superimpositions->find(scene); if (idx == -1) goto error; assert(PRIVATE(this)->superimpositions != NULL); PRIVATE(this)->superimpositions->remove(idx); PRIVATE(this)->superimpositionsenabled.remove(idx); scene->unref(); return; error: if (SO@GUI@_DEBUG) { SoDebugError::post("So@Gui@Viewer::removeSuperimposition", "no such superimposition"); } return; } // ************************************************************************* /*! This method sets whether the superimposed scene graph should be traversed or not. This method is not part of the original InventorXt API. */ void So@Gui@Viewer::setSuperimpositionEnabled(SoNode * scene, const SbBool enable) { int idx = -1; if (PRIVATE(this)->superimpositions == NULL) goto error; idx = PRIVATE(this)->superimpositions->find(scene); if (idx == -1) goto error; PRIVATE(this)->superimpositionsenabled[idx] = enable; return; error: if (SO@GUI@_DEBUG) { SoDebugError::post("So@Gui@Viewer::setSuperimpositionEnabled", "no such superimposition"); } return; } // ************************************************************************* /*! This method returns whether the superimposed scene is rendered or not. This method is not part of the original InventorXt API. */ SbBool So@Gui@Viewer::getSuperimpositionEnabled(SoNode * scene) const { int idx = -1; if (PRIVATE(this)->superimpositions == NULL) goto error; idx = PRIVATE(this)->superimpositions->find(scene); if (idx == -1) goto error; return PRIVATE(this)->superimpositionsenabled[idx]; error: if (SO@GUI@_DEBUG) { SoDebugError::post("So@Gui@Viewer::getSuperimpositionEnabled", "no such superimposition"); } return FALSE; } // ************************************************************************* #undef PRIVATE #undef PUBLIC