1// @configure_input@ 2 3/**************************************************************************\ 4 * Copyright (c) Kongsberg Oil & Gas Technologies AS 5 * All rights reserved. 6 * 7 * Redistribution and use in source and binary forms, with or without 8 * modification, are permitted provided that the following conditions are 9 * met: 10 * 11 * Redistributions of source code must retain the above copyright notice, 12 * this list of conditions and the following disclaimer. 13 * 14 * Redistributions in binary form must reproduce the above copyright 15 * notice, this list of conditions and the following disclaimer in the 16 * documentation and/or other materials provided with the distribution. 17 * 18 * Neither the name of the copyright holder nor the names of its 19 * contributors may be used to endorse or promote products derived from 20 * this software without specific prior written permission. 21 * 22 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 23 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 24 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 25 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 26 * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 27 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 28 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 29 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 30 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 31 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 32 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 33\**************************************************************************/ 34 35// NOTE: The So@Gui@Viewer.cpp sourcecode file is completely 36// autogenerated from "templatized" source code. 37 38// ************************************************************************* 39 40/*! 41 \class So@Gui@Viewer Inventor/@Gui@/viewers/So@Gui@Viewer.h 42 \brief The So@Gui@Viewer class is the top level base viewer class. 43 44 \ingroup components viewers 45 46 This is an abstract class, which adds the following features to it's 47 So@Gui@RenderArea superclass: convenient methods for camera 48 handling, an automatic headlight configuration. 49 50 As for the camera handling: when setting a new scenegraph for the 51 viewer, the scenegraph will automatically be scanned for a node 52 derived from SoCamera. If not found, the viewer will itself set up a 53 camera for the scene. The camera can then be conveniently controlled 54 by the application programmers in many aspects: 55 56 <ul> 57 58 <li>camera type: toggle between using an orthographic camera and a 59 perspective camera with So@Gui@Viewer::toggleCameraType()</li> 60 61 <li>zoom out to exactly encompass all scene geometry within the view 62 by using So@Gui@Viewer::viewAll()</li> 63 64 <li>tag a specific position and orientation for the camera as the 65 "home" position with So@Gui@Viewer::saveHomePosition(), which one 66 can then return to by using 67 So@Gui@Viewer::resetToHomePosition()</li> 68 69 <li>automatically fit the near and far clipping planes of the camera 70 around the scene's geometry by using 71 So@Gui@Viewer::setAutoClipping()</li> 72 73 <li>control stereo viewing parameters</li> 74 75 </ul> 76 77 Note that there is no dragger or manipulator attached to the scene 78 camera. The camera transform manipulation is calculated in a more 79 direct manner in the non-abstract viewer classes inheriting 80 So@Gui@Viewer by reading mouse and keyboard events and interpreting 81 how these should influence the camera. The calculations results in 82 new values for SoCamera::position, SoCamera::orientation, and the 83 other SoCamera field values for the camera designated to be the 84 viewer viewpoint camera. These values are then inserted directly 85 into the viewer's SoCamera node. 86 87 See e.g. the source code for So@Gui@ExaminerViewer::processSoEvent() 88 for the details. 89 90 91 92 The So@Gui@Viewer class automatically adds a headlight to the scene, 93 which will always point in the approximate same direction as the 94 current viewer camera, thereby securing that the scene geometry is 95 always lighted and visible. (If you don't want the constant 96 headlight, but rather want to light the scene on your own, this 97 behavior can be turned off with So@Gui@Viewer::setHeadlight()). 98 99 100 So@Gui@Viewer-derived viewers all inherit the following keyboard 101 controls from this class (but only when the viewer is in "examine 102 mode", ie So@Gui@Viewer::isViewing() returns \c TRUE): 103 104 <ul> 105 106 <li>"s": put the viewer in "seek mode", where the end user may click 107 anywhere on scene geometry to trigger an animation which moves the 108 camera towards the point clicked</li> 109 110 <li>"Home": hit this key to move camera back to last saved "home 111 position"</li> 112 113 <li>arrow keys: moves camera slightly left, right, up or down</li> 114 115 <li>"q": exit application</li> 116 117 </ul> 118*/ 119 120// ************************************************************************* 121 122/*! 123 \enum So@Gui@Viewer::AutoClippingStrategy 124 125 Enum for auto clipping strategy. 126 127 \sa setAutoClippingStrategy() 128*/ 129 130/*! 131 \var So@Gui@Viewer::AutoClippingStrategy So@Gui@Viewer::CONSTANT_NEAR_PLANE 132 133 Constant near plane auto clipping strategy. Explained in detail in 134 the documentation for the So@Gui@Viewer::setAutoClippingStrategy() 135 method. 136*/ 137 138/*! 139 \var So@Gui@Viewer::AutoClippingStrategy So@Gui@Viewer::VARIABLE_NEAR_PLANE 140 141 Variable near plane auto clipping strategy. Explained in detail in 142 the documentation for the So@Gui@Viewer::setAutoClippingStrategy() 143 method. 144*/ 145 146// ************************************************************************* 147 148#ifdef HAVE_CONFIG_H 149#include <config.h> 150#endif // HAVE_CONFIG_H 151 152#include <stdlib.h> 153#include <string.h> 154#include <math.h> 155#include <float.h> // FLT_MAX 156 157#include <Inventor/SbLinear.h> 158#include <Inventor/SoDB.h> 159#include <Inventor/SoLists.h> 160#include <Inventor/SoPickedPoint.h> 161#include <Inventor/SoSceneManager.h> 162#include <Inventor/actions/SoGetBoundingBoxAction.h> 163#include <Inventor/actions/SoGetMatrixAction.h> 164#include <Inventor/actions/SoRayPickAction.h> 165#include <Inventor/actions/SoSearchAction.h> 166#include <Inventor/errors/SoDebugError.h> 167#include <Inventor/events/SoKeyboardEvent.h> 168#include <Inventor/events/SoMouseButtonEvent.h> 169#include <Inventor/misc/SoCallbackList.h> 170#include <Inventor/nodekits/SoBaseKit.h> 171#include <Inventor/nodes/SoBaseColor.h> 172#include <Inventor/nodes/SoComplexity.h> 173#include <Inventor/nodes/SoDirectionalLight.h> 174#include <Inventor/nodes/SoDrawStyle.h> 175#include <Inventor/nodes/SoLightModel.h> 176#include <Inventor/nodes/SoLocateHighlight.h> 177#include <Inventor/nodes/SoMaterialBinding.h> 178#include <Inventor/nodes/SoOrthographicCamera.h> 179#include <Inventor/nodes/SoPerspectiveCamera.h> 180#include <Inventor/nodes/SoSeparator.h> 181#include <Inventor/nodes/SoSwitch.h> 182#include <Inventor/SbColor4f.h> 183#include <Inventor/sensors/SoTimerSensor.h> 184 185#ifdef HAVE_SOPOLYGONOFFSET 186#include <Inventor/nodes/SoPolygonOffset.h> 187#endif // HAVE_SOPOLYGONOFFSET 188 189#include <Inventor/@Gui@/So@Gui@.h> 190#include <Inventor/@Gui@/SoAny.h> 191#include <Inventor/@Gui@/common/SbGuiList.h> 192#include <Inventor/@Gui@/common/gl.h> 193#include <Inventor/@Gui@/nodes/SoGuiViewpointWrapper.h> 194#include <Inventor/@Gui@/viewers/So@Gui@Viewer.h> 195#include <Inventor/@Gui@/viewers/SoGuiViewerP.h> 196#include <so@gui@defs.h> 197 198// ************************************************************************* 199 200// (note: this *must* be a #define, not a static variable -- to avoid 201// initialization race conditions with the static variables being set 202// to the value of this) 203#define UNINITIALIZED_ENVVAR -1 // value of envvars before tested 204 205// Environment variable for debugging purpose: display a running 206// frames-per-second counter. See code comments above 207// So@Gui@ViewerP::recordFPS() function below for more information. 208static int COIN_SHOW_FPS_COUNTER = UNINITIALIZED_ENVVAR; 209 210// ************************************************************************* 211 212#define PRIVATE(ptr) (ptr->pimpl) 213#define PUBLIC(ptr) (ptr->pub) 214 215// ************************************************************************* 216 217So@Gui@ViewerP::So@Gui@ViewerP(So@Gui@Viewer * publ) 218{ 219 PUBLIC(this) = publ; 220 this->searchaction = new SoSearchAction; 221 this->matrixaction = new SoGetMatrixAction(SbViewportRegion(100,100)); 222 this->superimpositions = NULL; 223 224 this->storedcamera = NULL; 225 226 // initialize auto clipping parameters 227 this->autoclipstrategy = So@Gui@Viewer::VARIABLE_NEAR_PLANE; 228 this->autoclipvalue = 0.6f; 229 this->autoclipcb = NULL; 230 231 this->stereotype = So@Gui@Viewer::STEREO_NONE; 232 this->stereotypesetexplicit = FALSE; 233 this->stereostencilmaskvp = SbViewportRegion(0, 0); 234 this->stereostencilmask = NULL; 235 this->stereostenciltype = So@Gui@Viewer::STEREO_NONE; 236 this->stereoanaglyphmask[0][0] = TRUE; 237 this->stereoanaglyphmask[0][1] = this->stereoanaglyphmask[0][2] = FALSE; 238 this->stereoanaglyphmask[1][0] = FALSE; 239 this->stereoanaglyphmask[1][1] = this->stereoanaglyphmask[1][2] = TRUE; 240} 241 242So@Gui@ViewerP::~So@Gui@ViewerP() 243{ 244 // This impossible to miss reminder was inserted so we don't 245 // accidentally let an So* v2 slip out the door without fixing this 246 // API design flaw. 20030625 mortene. 247#if (SO@GUI@_MAJOR_VERSION == 2) 248#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. 249#endif // version = 2 250 251 delete[] this->stereostencilmask; 252 253 if ( this->superimpositions != NULL ) delete this->superimpositions; 254 delete this->searchaction; 255 delete this->matrixaction; 256 257 if (this->storedcamera) { this->storedcamera->unref(); } 258} 259 260SoSeparator * 261So@Gui@ViewerP::createSuperScene(void) 262{ 263 static const char * superSceneGraph[] = 264 { 265 "#Inventor V2.1 ascii", 266 "", 267 "Separator {", 268 " renderCaching OFF", 269 " renderCulling OFF", 270 " pickCulling OFF", 271 " boundingBoxCaching OFF", 272 273 // Headlight. By inserting this before any scenegraph camera, the 274 // light will always be pointing in the correct direction. 275 " DEF so@gui@->headlight DirectionalLight {", 276 " direction 1 -1 -10", 277 " }", 278 279 " DEF so@gui@->drawstyleroot Switch {", 280 " whichChild -1", 281 " DEF so@gui@->lightmodel LightModel {", 282 " model BASE_COLOR", 283 " }", 284 " DEF so@gui@->drawstyle DrawStyle {", 285 " pointSize ~", 286 " lineWidth ~", 287 " linePattern ~", 288 " }", 289 " DEF so@gui@->complexity Complexity {", 290 " textureQuality 0.0", 291 " value 0.1", 292 " }", 293 " }", 294 " DEF so@gui@->hiddenlineroot Switch {", 295 " whichChild -1", 296 " DEF so@gui@->basecolor BaseColor { }", 297 " DEF so@gui@->materialbinding MaterialBinding {", 298 " value OVERALL", 299 " }", 300 " DEF so@gui@->polygonoffsetparent Switch {", 301 " whichChild -1", 302#ifdef HAVE_SOPOLYGONOFFSET 303 " DEF so@gui@->polygonoffset PolygonOffset { }", 304#endif // HAVE_SOPOLYGONOFFSET 305 " }", 306 " }", 307 " DEF so@gui@->userscenegraphroot Separator {", 308 // turn off caching to make it possible for users to disable 309 // caching in their scene graphs. 310 " renderCaching OFF\n", 311 " }", 312 "}", 313 NULL 314 }; 315 316 int i; 317 size_t bufsize; 318 for (i = 0, bufsize = 0; superSceneGraph[i]; i++) 319 bufsize += strlen(superSceneGraph[i]) + 1; 320 char * buf = new char [bufsize + 1]; 321 for (i = 0, bufsize = 0; superSceneGraph[i]; i++) { 322 strcpy(buf + bufsize, superSceneGraph[i]); 323 bufsize += strlen(superSceneGraph[i]); 324 buf[bufsize] = '\n'; 325 bufsize++; 326 } 327 SoInput * input = new SoInput; 328 input->setBuffer(buf, bufsize); 329 SoNode * root = NULL; 330 SbBool ok = SoDB::read(input, root); 331 delete input; 332 delete [] buf; 333 if (!ok) { 334 // FIXME: this looks unnecessary robust, and I believe it should 335 // be replaced by an assert()..? 20030430 mortene. 336 SoDebugError::post("So@Gui@ViewerP::createSuperScene", 337 "couldn't create viewer superscene"); 338 return NULL; 339 } 340 assert(root->isOfType(SoSeparator::getClassTypeId())); 341 root->ref(); 342 343 this->searchaction->reset(); 344 this->searchaction->setSearchingAll(TRUE); 345 this->searchaction->setInterest(SoSearchAction::FIRST); 346 347#define LOCATE_NODE(member, type, name) \ 348 do { \ 349 member = NULL; \ 350 this->searchaction->setName(SbName(name)); \ 351 this->searchaction->apply(root); \ 352 if (this->searchaction->getPath() != NULL) { \ 353 SoNode * node = this->searchaction->getPath()->getTail(); \ 354 assert(node != NULL); \ 355 if (node->isOfType(type::getClassTypeId())) \ 356 member = (type *) node; \ 357 } else { \ 358 SoDebugError::post("So@Gui@ViewerP::createSuperScene", \ 359 "didn't locate node \"%s\"", name); \ 360 } \ 361 } while (FALSE) 362 363 LOCATE_NODE(this->headlight, SoDirectionalLight, "so@gui@->headlight"); 364 LOCATE_NODE(this->drawstyleroot, SoSwitch, "so@gui@->drawstyleroot"); 365 LOCATE_NODE(this->hiddenlineroot, SoSwitch, "so@gui@->hiddenlineroot"); 366 LOCATE_NODE(this->polygonoffsetparent, SoSwitch, 367 "so@gui@->polygonoffsetparent"); 368 LOCATE_NODE(this->usersceneroot, SoSeparator, "so@gui@->userscenegraphroot"); 369 370 LOCATE_NODE(this->sobasecolor, SoBaseColor, "so@gui@->basecolor"); 371 LOCATE_NODE(this->socomplexity, SoComplexity, "so@gui@->complexity"); 372 LOCATE_NODE(this->sodrawstyle, SoDrawStyle, "so@gui@->drawstyle"); 373 LOCATE_NODE(this->solightmodel, SoLightModel, "so@gui@->lightmodel"); 374 LOCATE_NODE(this->somaterialbinding, SoMaterialBinding, "so@gui@->materialbinding"); 375 if (this->sobasecolor) this->sobasecolor->setOverride(TRUE); 376 if (this->socomplexity) this->socomplexity->setOverride(TRUE); 377 if (this->sodrawstyle) this->sodrawstyle->setOverride(TRUE); 378 if (this->solightmodel) this->solightmodel->setOverride(TRUE); 379 if (this->somaterialbinding) this->somaterialbinding->setOverride(TRUE); 380#ifdef HAVE_SOPOLYGONOFFSET 381 LOCATE_NODE(this->sopolygonoffset, SoPolygonOffset, "so@gui@->polygonoffset"); 382 if (this->sopolygonoffset) this->sopolygonoffset->setOverride(TRUE); 383#endif // HAVE_SOPOLYGONOFFSET 384 385#undef LOCATE_NODE 386 this->searchaction->reset(); 387 388 root->unrefNoDelete(); 389 return (SoSeparator *) root; 390} 391 392// Returns the coordinate system the current camera is located in. If 393// there are transformations before the camera in the scene graph, 394// this must be considered before doing certain operations. \a matrix 395// and \a inverse will not contain the transformations caused by the 396// camera fields, only the transformations traversed before the camera 397// in the scene graph. 398void 399So@Gui@ViewerP::getCameraCoordinateSystem(SoCamera * cameraarg, 400 SoNode * root, 401 SbMatrix & matrix, 402 SbMatrix & inverse) 403{ 404 this->searchaction->reset(); 405 this->searchaction->setSearchingAll(TRUE); 406 this->searchaction->setInterest(SoSearchAction::FIRST); 407 this->searchaction->setNode(cameraarg); 408 this->searchaction->apply(root); 409 410 matrix = inverse = SbMatrix::identity(); 411 if (this->searchaction->getPath()) { 412 this->matrixaction->apply(this->searchaction->getPath()); 413 matrix = this->matrixaction->getMatrix(); 414 inverse = this->matrixaction->getInverse(); 415 } 416 this->searchaction->reset(); 417} 418 419 420// These functions do this: 421// 422// * when going from orthocam -> perspectivecam: set the 423// heightAngle field to its default value (45�), and move 424// camera to a position where the scene/model would fill about 425// the same screenspace as it did in the orthocam 426// 427// * when going from perspectivecam -> orthocam: keep the 428// current position, but tune the view-volume height so the 429// scene/model takes up about the same screenspace 430// 431// 20020522 mortene. 432 433void 434So@Gui@ViewerP::convertOrtho2Perspective(const SoOrthographicCamera * in, 435 SoPerspectiveCamera * out) 436{ 437 out->aspectRatio.setValue(in->aspectRatio.getValue()); 438 out->focalDistance.setValue(in->focalDistance.getValue()); 439 out->orientation.setValue(in->orientation.getValue()); 440 out->position.setValue(in->position.getValue()); 441 out->viewportMapping.setValue(in->viewportMapping.getValue()); 442 443 SbRotation camrot = in->orientation.getValue(); 444 445 float focaldist = in->height.getValue() / float(2.0*tan(M_PI / 8.0)); 446 447 SbVec3f offset(0,0,focaldist-in->focalDistance.getValue()); 448 449 camrot.multVec(offset,offset); 450 out->position.setValue(offset+in->position.getValue()); 451 452 out->focalDistance.setValue(focaldist); 453 454 // 45� is the default value of this field in SoPerspectiveCamera. 455 out->heightAngle = (float)(M_PI / 4.0); 456 457#if SO@GUI@_DEBUG && 0 // debug 458 SoDebugError::postInfo("So@Gui@ViewerP::convertOrtho2Perspective", 459 "perspective heightAngle==%f", 460 180.0f * out->heightAngle.getValue() / M_PI); 461#endif // debug 462} 463 464void 465So@Gui@ViewerP::convertPerspective2Ortho(const SoPerspectiveCamera * in, 466 SoOrthographicCamera * out) 467{ 468 out->aspectRatio.setValue(in->aspectRatio.getValue()); 469 out->focalDistance.setValue(in->focalDistance.getValue()); 470 out->orientation.setValue(in->orientation.getValue()); 471 out->position.setValue(in->position.getValue()); 472 out->viewportMapping.setValue(in->viewportMapping.getValue()); 473 474 float focaldist = in->focalDistance.getValue(); 475 476 out->height = 2.0f * focaldist * (float)tan(in->heightAngle.getValue() / 2.0); 477 478#if SO@GUI@_DEBUG && 0 // debug 479 SoDebugError::postInfo("So@Gui@ViewerP::convertOrtho2Perspective", 480 "ortho height==%f", 481 out->height.getValue()); 482#endif // debug 483} 484 485void 486So@Gui@ViewerP::reallyRedraw(const SbBool clearcol, const SbBool clearz) 487{ 488 // Recalculate near/far planes. Must be done in reallyRedraw() -- 489 // not actualRedraw() -- so the clipping planes are correct even 490 // when rendering multiple times with different camera settings. 491 if (this->camera && PUBLIC(this)->isAutoClipping()) { 492 // Temporarily turn off notification when changing near and far 493 // clipping planes, to avoid latency. 494 const SbBool notif = this->camera->isNotifyEnabled(); 495 this->camera->enableNotify(FALSE); 496 this->setClippingPlanes(); 497 this->camera->enableNotify(notif); 498 } 499 500 if (this->drawAsHiddenLine()) { 501 502 // First pass: render as filled, but with the background color. 503 504 this->solightmodel->model.setIgnored(FALSE); // override as SoLightModel::BASE 505 this->sodrawstyle->style.setIgnored(TRUE); // draw as-is filled/lines/points 506 this->socomplexity->type.setIgnored(TRUE); // as-is rendering space 507 this->socomplexity->value.setIgnored(TRUE); // as-is complexity on non-simple shapes 508 // textureQuality field of socomplexity node is always 0.0 509 510 this->sobasecolor->rgb.setValue(PUBLIC(this)->getBackgroundColor()); 511 this->sobasecolor->rgb.setIgnored(FALSE); 512 this->somaterialbinding->value.setIgnored(FALSE); // override with OVERALL 513 this->polygonoffsetparent->whichChild = SO_SWITCH_ALL; 514 515 PUBLIC(this)->getSceneManager()->render(clearcol, clearz); 516 517 // Second pass, render wireframe on top. 518 519 this->sodrawstyle->style = SoDrawStyle::LINES; 520 this->sodrawstyle->style.setIgnored(FALSE); // force lines 521 this->sobasecolor->rgb.setIgnored(TRUE); // use as-is line colors 522 this->somaterialbinding->value.setIgnored(TRUE); // as-is 523 this->polygonoffsetparent->whichChild = SO_SWITCH_NONE; 524 525 PUBLIC(this)->getSceneManager()->render(FALSE, FALSE); 526 527 return; 528 } 529 if (this->drawAsWireframeOverlay()) { 530 // First pass: render as-is, with polygon offset 531 532 this->solightmodel->model.setIgnored(TRUE); 533 this->somaterialbinding->value.setIgnored(TRUE); 534 this->sobasecolor->rgb.setIgnored(TRUE); 535 this->sodrawstyle->style.setIgnored(TRUE); // draw as-is filled/lines/points 536 this->socomplexity->type.setIgnored(TRUE); // as-is rendering space 537 this->socomplexity->value.setIgnored(TRUE); // as-is complexity on non-simple shapes 538 this->socomplexity->textureQuality.setIgnored(TRUE); 539 540 this->somaterialbinding->value.setIgnored(TRUE); // override with OVERALL 541 this->polygonoffsetparent->whichChild = SO_SWITCH_ALL; 542 543 PUBLIC(this)->getSceneManager()->render(clearcol, clearz); 544 545 // Second pass, render wireframe on top. 546 this->sobasecolor->rgb.setValue(this->wireframeoverlaycolor); 547 this->sobasecolor->rgb.setIgnored(FALSE); 548 this->somaterialbinding->value.setIgnored(FALSE); // override with OVERALL 549 550 this->solightmodel->model.setIgnored(FALSE); // override as SoLightModel::BASE 551 this->sodrawstyle->style = SoDrawStyle::LINES; 552 this->sodrawstyle->style.setIgnored(FALSE); // force lines 553 this->polygonoffsetparent->whichChild = SO_SWITCH_NONE; 554 this->socomplexity->textureQuality.setIgnored(FALSE); 555 556 PUBLIC(this)->getSceneManager()->render(FALSE, FALSE); 557 558 // disable override nodes 559 (void) this->sobasecolor->rgb.enableNotify(FALSE); 560 this->sobasecolor->rgb.setIgnored(TRUE); 561 (void) this->sobasecolor->rgb.enableNotify(TRUE); 562 563 (void) this->somaterialbinding->value.enableNotify(FALSE); 564 this->somaterialbinding->value.setIgnored(TRUE); 565 (void) this->somaterialbinding->value.enableNotify(TRUE); 566 567 (void) this->solightmodel->model.enableNotify(FALSE); 568 this->solightmodel->model.setIgnored(TRUE); 569 (void) this->solightmodel->model.enableNotify(TRUE); 570 571 (void) this->socomplexity->textureQuality.enableNotify(FALSE); 572 this->socomplexity->textureQuality.setIgnored(TRUE); 573 (void) this->socomplexity->textureQuality.enableNotify(TRUE); 574 575 (void) this->sodrawstyle->style.enableNotify(FALSE); 576 this->sodrawstyle->style.setIgnored(TRUE); 577 (void) this->sodrawstyle->style.enableNotify(TRUE); 578 return; 579 } 580 581 SbBool clearzbuffer = TRUE; 582 So@Gui@Viewer::DrawStyle style = this->currentDrawStyle(); 583 switch (style) { 584 case So@Gui@Viewer::VIEW_LOW_RES_LINE: 585 case So@Gui@Viewer::VIEW_LOW_RES_POINT: 586 case So@Gui@Viewer::VIEW_BBOX: 587 clearzbuffer = FALSE; 588 default: 589 break; // Include "default:" case to avoid compiler warning. 590 } 591 592 PUBLIC(this)->getSceneManager()->render(clearcol, clearzbuffer && clearz); 593} 594 595 596// ************************************************************************* 597 598// Returns a boolean to indicate if the dynamic drawstyle equals 599// the static drawstyle. 600 601SbBool 602So@Gui@ViewerP::drawInteractiveAsStill(void) const 603{ 604 SbBool moveasstill = this->drawstyles[So@Gui@Viewer::INTERACTIVE] == So@Gui@Viewer::VIEW_SAME_AS_STILL; 605 if (! moveasstill) 606 moveasstill = this->drawstyles[So@Gui@Viewer::INTERACTIVE] == this->drawstyles[So@Gui@Viewer::STILL]; 607 if (! moveasstill) 608 moveasstill = 609 this->drawstyles[So@Gui@Viewer::INTERACTIVE] == So@Gui@Viewer::VIEW_NO_TEXTURE && 610 this->drawstyles[So@Gui@Viewer::STILL] != So@Gui@Viewer::VIEW_AS_IS; 611 return moveasstill; 612} 613 614// Returns the current drawing style. 615So@Gui@Viewer::DrawStyle 616So@Gui@ViewerP::currentDrawStyle(void) const 617{ 618 SbBool interactivemode = PUBLIC(this)->getInteractiveCount() > 0 ? TRUE : FALSE; 619 620 if (!interactivemode || this->drawInteractiveAsStill()) 621 return this->drawstyles[So@Gui@Viewer::STILL]; 622 else 623 return this->drawstyles[So@Gui@Viewer::INTERACTIVE]; 624} 625 626// Returns a boolean to indicate if the current drawstyle settings implies 627// hidden line rendering. 628SbBool 629So@Gui@ViewerP::drawAsHiddenLine(void) const 630{ 631 return ((this->currentDrawStyle() == So@Gui@Viewer::VIEW_HIDDEN_LINE) ? TRUE : FALSE); 632} 633 634// Returns a boolean to indicate if the current drawstyle settings 635// implies wirefram overlay rendering. 636SbBool 637So@Gui@ViewerP::drawAsWireframeOverlay(void) const 638{ 639 return ((this->currentDrawStyle() == So@Gui@Viewer::VIEW_WIREFRAME_OVERLAY) ? TRUE : FALSE); 640} 641 642// Use the given style setting to set the correct states in the 643// rendering control nodes. This will affect the way the scene is 644// currently rendered. 645void 646So@Gui@ViewerP::changeDrawStyle(So@Gui@Viewer::DrawStyle style) 647{ 648 // Turn on/off Z-buffering based on the style setting. 649 switch (style) { 650 case So@Gui@Viewer::VIEW_LOW_RES_LINE: 651 case So@Gui@Viewer::VIEW_LOW_RES_POINT: 652 case So@Gui@Viewer::VIEW_BBOX: 653 PUBLIC(this)->glLockNormal(); 654 // FIXME: shouldn't this be done "lazy", i.e. before we do any 655 // actual rendering? 20001126 mortene. 656 glDisable(GL_DEPTH_TEST); 657 PUBLIC(this)->glUnlockNormal(); 658 break; 659 660 default: 661 PUBLIC(this)->glLockNormal(); 662 // FIXME: shouldn't this be done "lazy", i.e. before we do any 663 // actual rendering? 20001126 mortene. 664 glEnable(GL_DEPTH_TEST); 665 PUBLIC(this)->glUnlockNormal(); 666 break; 667 } 668 669 // Render everything as its supposed to be done, don't override 670 // any of the settings in the ``real'' graph. 671 if (style == So@Gui@Viewer::VIEW_AS_IS) { 672 this->drawstyleroot->whichChild = SO_SWITCH_NONE; 673 return; 674 } 675 676 this->drawstyleroot->whichChild = SO_SWITCH_ALL; 677 if ((style == So@Gui@Viewer::VIEW_HIDDEN_LINE) || 678 (style == So@Gui@Viewer::VIEW_WIREFRAME_OVERLAY)) { 679 this->hiddenlineroot->whichChild = SO_SWITCH_ALL; 680 return; 681 } else { 682 this->hiddenlineroot->whichChild = SO_SWITCH_NONE; 683 } 684 685 // Set or unset lightmodel override. 686 switch (style) { 687 case So@Gui@Viewer::VIEW_NO_TEXTURE: 688 case So@Gui@Viewer::VIEW_LOW_COMPLEXITY: 689 this->solightmodel->model.setIgnored(TRUE); // as-is BASE or PHONG 690 break; 691 692 case So@Gui@Viewer::VIEW_LINE: 693 case So@Gui@Viewer::VIEW_POINT: 694 case So@Gui@Viewer::VIEW_BBOX: 695 case So@Gui@Viewer::VIEW_LOW_RES_LINE: 696 case So@Gui@Viewer::VIEW_LOW_RES_POINT: 697 this->solightmodel->model.setIgnored(FALSE); // force BASE lighting 698 break; 699 700 default: 701 assert(FALSE); break; 702 } 703 704 705 // Set or unset drawstyle override. 706 switch (style) { 707 case So@Gui@Viewer::VIEW_NO_TEXTURE: 708 case So@Gui@Viewer::VIEW_LOW_COMPLEXITY: 709 this->sodrawstyle->style.setIgnored(TRUE); // as-is drawing style filled/lines/points 710 break; 711 712 case So@Gui@Viewer::VIEW_LINE: 713 case So@Gui@Viewer::VIEW_LOW_RES_LINE: 714 case So@Gui@Viewer::VIEW_BBOX: 715 this->sodrawstyle->style = SoDrawStyle::LINES; 716 this->sodrawstyle->style.setIgnored(FALSE); // force line rendering 717 break; 718 719 case So@Gui@Viewer::VIEW_POINT: 720 case So@Gui@Viewer::VIEW_LOW_RES_POINT: 721 this->sodrawstyle->style = SoDrawStyle::POINTS; 722 this->sodrawstyle->style.setIgnored(FALSE); // force point rendering 723 break; 724 725 default: 726 assert(FALSE); break; 727 } 728 729 // Set or unset complexity value override. 730 switch (style) { 731 case So@Gui@Viewer::VIEW_NO_TEXTURE: 732 case So@Gui@Viewer::VIEW_LINE: 733 case So@Gui@Viewer::VIEW_POINT: 734 case So@Gui@Viewer::VIEW_BBOX: 735 this->socomplexity->value.setIgnored(TRUE); // as-is complexity 736 break; 737 738 case So@Gui@Viewer::VIEW_LOW_COMPLEXITY: 739 case So@Gui@Viewer::VIEW_LOW_RES_LINE: 740 case So@Gui@Viewer::VIEW_LOW_RES_POINT: 741 this->socomplexity->value.setIgnored(FALSE); // force complexity setting of 0.1 742 break; 743 744 default: 745 assert(FALSE); break; 746 } 747 748 // Set or unset complexity textureQuality override (the value of the 749 // override-field is always 0.0, ie signalling "textures off"). 750 switch (style) { 751 case So@Gui@Viewer::VIEW_HIDDEN_LINE: 752 case So@Gui@Viewer::VIEW_NO_TEXTURE: 753 case So@Gui@Viewer::VIEW_LINE: 754 case So@Gui@Viewer::VIEW_POINT: 755 case So@Gui@Viewer::VIEW_BBOX: 756 case So@Gui@Viewer::VIEW_LOW_RES_LINE: 757 case So@Gui@Viewer::VIEW_LOW_RES_POINT: 758 this->socomplexity->textureQuality.setIgnored(FALSE); // textures off 759 break; 760 761 default: 762 this->socomplexity->textureQuality.setIgnored(TRUE); // don't override 763 break; 764 } 765 766 // Set or unset complexity type override. 767 switch (style) { 768 case So@Gui@Viewer::VIEW_NO_TEXTURE: 769 case So@Gui@Viewer::VIEW_LOW_COMPLEXITY: 770 case So@Gui@Viewer::VIEW_LINE: 771 case So@Gui@Viewer::VIEW_POINT: 772 case So@Gui@Viewer::VIEW_LOW_RES_LINE: 773 case So@Gui@Viewer::VIEW_LOW_RES_POINT: 774 this->socomplexity->type.setIgnored(TRUE); // as-is 775 break; 776 777 case So@Gui@Viewer::VIEW_BBOX: 778 this->socomplexity->type = SoComplexity::BOUNDING_BOX; 779 this->socomplexity->type.setIgnored(FALSE); // force bounding box rendering 780 break; 781 782 default: 783 assert(FALSE); break; 784 } 785 786#if 0 // debug 787 SoDebugError::postInfo("So@Gui@Viewer::changeDrawStyle", 788 "\n" 789 "\tdrawstyle style: 0x%02x (isIgnored() == %s)\n" 790 "\tlightmodel model: 0x%02x, (isIgnored() == %s)\n" 791 "\tcomplexity type: 0x%02x, (isIgnored() == %s)\n" 792 "\tcomplexity value: %f, (isIgnored() == %s)\n" 793 "", 794 this->sodrawstyle->style.getValue(), 795 this->sodrawstyle->style.isIgnored() ? "T" : "F", 796 this->solightmodel->model.getValue(), 797 this->solightmodel->model.isIgnored() ? "T" : "F", 798 this->socomplexity->type.getValue(), 799 this->socomplexity->type.isIgnored() ? "T" : "F", 800 this->socomplexity->value.getValue(), 801 this->socomplexity->value.isIgnored() ? "T" : "F"); 802#endif // debug 803} 804 805// Position the near and far clipping planes just in front of and 806// behind the scene's bounding box. This will give us the optimal 807// utilization of the z buffer resolution by shrinking it to its 808// minimum depth. 809// 810// Near and far clipping planes are specified in the camera fields 811// nearDistance and farDistance. 812void 813So@Gui@ViewerP::setClippingPlanes(void) 814{ 815 // This is necessary to avoid a crash in case there is no scene 816 // graph specified by the user. 817 if (this->camera == NULL) return; 818 819 if (this->autoclipbboxaction == NULL) 820 this->autoclipbboxaction = 821 new SoGetBoundingBoxAction(PUBLIC(this)->getViewportRegion()); 822 else 823 this->autoclipbboxaction->setViewportRegion(PUBLIC(this)->getViewportRegion()); 824 825 this->autoclipbboxaction->apply(this->sceneroot); 826 827 SbXfBox3f xbox = this->autoclipbboxaction->getXfBoundingBox(); 828 829 // Bounding box was calculated in camera space, so we need to "flip" 830 // the box (because camera is pointing in the (0,0,-1) direction 831 // from origo. 832 float nearval = this->camera->nearDistance.getValue(); 833 float farval = this->camera->farDistance.getValue(); 834 if ( !xbox.isEmpty() ) { 835 SbMatrix cammat; 836 SbMatrix inverse; 837 this->getCameraCoordinateSystem(this->camera, this->sceneroot, cammat, inverse); 838 xbox.transform(inverse); 839 840 SbMatrix mat; 841 mat.setTranslate(- this->camera->position.getValue()); 842 xbox.transform(mat); 843 mat = this->camera->orientation.getValue().inverse(); 844 xbox.transform(mat); 845 SbBox3f box = xbox.project(); 846 847 nearval = -box.getMax()[2]; 848 farval = -box.getMin()[2]; 849 } 850 851 // FIXME: what if we have a weird scale transform in the scenegraph? 852 // Could we end up with nearval > farval then? Investigate, then 853 // either use an assert() (if it can't happen) or an So@Gui@Swap() 854 // (to handle it). 20020116 mortene. 855 856 // Check if scene is completely behind us. 857 // Do NOT check for orthographic cameras. 858 if (farval <= 0.0f && !this->camera->isOfType(SoOrthographicCamera::getClassTypeId())) { return; } 859 860 if (this->camera->isOfType(SoPerspectiveCamera::getClassTypeId())) { 861 // Disallow negative and small near clipping plane distance. 862 863 float nearlimit; // the smallest value allowed for nearval 864 if (this->autoclipstrategy == So@Gui@Viewer::CONSTANT_NEAR_PLANE) { 865 nearlimit = this->autoclipvalue; 866 } 867 else { 868 assert(this->autoclipstrategy == So@Gui@Viewer::VARIABLE_NEAR_PLANE); 869 // From glFrustum() documentation: Depth-buffer precision is 870 // affected by the values specified for znear and zfar. The 871 // greater the ratio of zfar to znear is, the less effective the 872 // depth buffer will be at distinguishing between surfaces that 873 // are near each other. If r = far/near, roughly log (2) r bits 874 // of depth buffer precision are lost. Because r approaches 875 // infinity as znear approaches zero, you should never set znear 876 // to zero. 877 878 GLint depthbits[1]; 879 glGetIntegerv(GL_DEPTH_BITS, depthbits); 880 881 int use_bits = (int) (float(depthbits[0]) * (1.0f-this->autoclipvalue)); 882 float r = (float) pow(2.0, (double) use_bits); 883 nearlimit = farval / r; 884 } 885 886 if (nearlimit >= farval) { 887 // (The "5000" magic constant was found by fiddling around a bit 888 // on an OpenGL implementation with a 16-bit depth-buffer 889 // resolution, adjusting to find something that would work well 890 // with both a very "stretched" / deep scene and a more compact 891 // single-model one.) 892 nearlimit = farval / 5000.0f; 893 } 894 895 // adjust the near plane if the the value is too small. 896 if (nearval < nearlimit) { nearval = nearlimit; } 897 } 898 899 // Some slack around the bounding box, in case the scene fits 900 // exactly inside it. This is done to minimize the chance of 901 // artifacts caused by the limitation of the z-buffer 902 // resolution. One common artifact if this is not done is that the 903 // near clipping plane cuts into the corners of the model as it's 904 // rotated. 905 const float SLACK = 0.001f; 906 907 // FrustumCamera can be found in the SmallChange CVS module. We 908 // should not change the nearDistance for this camera, as this will 909 // modify the frustum. 910 // 911 // FIXME: quite the hack that So@Gui@ needs to know about the 912 // FrustumCamera class. Wouldn't it be better if FrustumCamera 913 // instead registered a callback with setAutoClippingStrategy() and 914 // handled this itself? 20040908 mortene. 915 if (this->camera->getTypeId().getName() == "FrustumCamera") { 916 nearval = this->camera->nearDistance.getValue(); 917 farval *= (1.0f + SLACK); 918 if (farval <= nearval) { 919 // nothing is visible, so just set farval to som value > nearval. 920 farval = nearval + 10.0f; 921 } 922 } 923 else { 924 // For orthographic cameras also negative values nearval/farval 925 // are reasonable. 926 nearval *= (1.0f - (nearval>0?1:-1)*SLACK); 927 farval *= (1.0f + (farval>0?1:-1)*SLACK); 928 } 929 930 if (this->autoclipcb) { 931 SbVec2f nearfar(nearval, farval); 932 nearfar = this->autoclipcb(this->autoclipuserdata, nearfar); 933 934 nearval = nearfar[0]; 935 farval = nearfar[1]; 936 } 937 938 if (nearval != this->camera->nearDistance.getValue()) { 939 this->camera->nearDistance = nearval; 940 } 941 if (farval != this->camera->farDistance.getValue()) { 942 this->camera->farDistance = farval; 943 } 944 945 // FIXME: there's a possible optimization to take advantage of here, 946 // since we are able to sometimes know for sure that all geometry is 947 // completely inside the view volume. I quote from the "OpenGL FAQ 948 // and Troubleshooting Guide": 949 // 950 // "10.050 I know my geometry is inside the view volume. How can I 951 // turn off OpenGL's view-volume clipping to maximize performance? 952 // 953 // Standard OpenGL doesn't provide a mechanism to disable the 954 // view-volume clipping test; thus, it will occur for every 955 // primitive you send. 956 // 957 // Some implementations of OpenGL support the 958 // GL_EXT_clip_volume_hint extension. If the extension is 959 // available, a call to 960 // glHint(GL_CLIP_VOLUME_CLIPPING_HINT_EXT,GL_FASTEST) will inform 961 // OpenGL that the geometry is entirely within the view volume and 962 // that view-volume clipping is unnecessary. Normal clipping can 963 // be resumed by setting this hint to GL_DONT_CARE. When clipping 964 // is disabled with this hint, results are undefined if geometry 965 // actually falls outside the view volume." 966 // 967 // 20020117 mortene. 968 969 970 // Debug assistance, can be turned on without recompilation (just 971 // set the environment variable SO@GUI@_DEBUG_CLIPPLANES): 972 973#if SO@GUI@_DEBUG 974 static int debugoutputnearfar = -1; 975 if (debugoutputnearfar == -1) { 976 const char * env = SoAny::si()->getenv("SO@GUI@_DEBUG_CLIPPLANES"); 977 debugoutputnearfar = (env && atoi(env) > 0) ? 1 : 0; 978 } 979 980 if (debugoutputnearfar == 1) { // debug 981 SoDebugError::postInfo("So@Gui@Viewer::setClippingPlanes", 982 "near, far: %f (%f), %f (%f)", 983 nearval, this->camera->nearDistance.getValue(), 984 farval, this->camera->farDistance.getValue()); 985 } 986#endif // debug 987} 988 989// Translate camera a distance equal to the difference in projected, 990// normalized screen coordinates given by the argument. 991void 992So@Gui@ViewerP::moveCameraScreen(const SbVec2f & screenpos) 993{ 994 SoCamera * cam = PUBLIC(this)->getCamera(); 995 assert(cam); 996 997 if (SO@GUI@_DEBUG && 0) { // debug 998 SoDebugError::postInfo("So@Gui@Viewer::moveCameraScreen", 999 "screenpos: <%f, %f>, campos: <%f, %f, %f>", 1000 screenpos[0], screenpos[1], 1001 cam->position.getValue()[0], 1002 cam->position.getValue()[1], 1003 cam->position.getValue()[2]); 1004 } 1005 1006 SbViewVolume vv = cam->getViewVolume(PUBLIC(this)->getGLAspectRatio()); 1007 SbPlane panplane = vv.getPlane(cam->focalDistance.getValue()); 1008 1009 SbLine line; 1010 vv.projectPointToLine(screenpos + SbVec2f(0.5, 0.5f), line); 1011 SbVec3f current_planept; 1012 panplane.intersect(line, current_planept); 1013 vv.projectPointToLine(SbVec2f(0.5f, 0.5f), line); 1014 SbVec3f old_planept; 1015 panplane.intersect(line, old_planept); 1016 1017 // Reposition camera according to the vector difference between the 1018 // projected points. 1019 cam->position = cam->position.getValue() - (current_planept - old_planept); 1020 1021 if (SO@GUI@_DEBUG && 0) { // debug 1022 SoDebugError::postInfo("So@Gui@Viewer::moveCameraScreen", 1023 "newcampos: <%f, %f, %f>", 1024 cam->position.getValue()[0], 1025 cam->position.getValue()[1], 1026 cam->position.getValue()[2]); 1027 } 1028} 1029 1030// Called when viewer enters interactive mode (animation, drag, ...). 1031void 1032So@Gui@ViewerP::interactivestartCB(void *, So@Gui@Viewer * thisp) 1033{ 1034 // In interactive buffer mode, doublebuffering is used during interaction. 1035 if (PRIVATE(thisp)->buffertype == So@Gui@Viewer::BUFFER_INTERACTIVE) { 1036 PRIVATE(thisp)->localsetbuffertype = TRUE; 1037 thisp->So@Gui@RenderArea::setDoubleBuffer(TRUE); 1038 PRIVATE(thisp)->localsetbuffertype = FALSE; 1039 } 1040 1041 // Use the dynamic drawstyle. 1042 if (!PRIVATE(thisp)->drawInteractiveAsStill()) 1043 PRIVATE(thisp)->changeDrawStyle(PRIVATE(thisp)->drawstyles[So@Gui@Viewer::INTERACTIVE]); 1044} 1045 1046// Called when viewer goes out of interactive mode and into "frozen" 1047// mode. 1048void 1049So@Gui@ViewerP::interactiveendCB(void *, So@Gui@Viewer * thisp) 1050{ 1051 // In interactive buffer mode, doublebuffering is used during 1052 // interaction, singelbuffering while the camera is static. 1053 if (PRIVATE(thisp)->buffertype == So@Gui@Viewer::BUFFER_INTERACTIVE) { 1054 PRIVATE(thisp)->localsetbuffertype = TRUE; 1055 thisp->So@Gui@RenderArea::setDoubleBuffer(FALSE); 1056 PRIVATE(thisp)->localsetbuffertype = FALSE; 1057 } 1058 1059 // Back to static drawstyle. 1060 if (!PRIVATE(thisp)->drawInteractiveAsStill()) 1061 PRIVATE(thisp)->changeDrawStyle(PRIVATE(thisp)->drawstyles[So@Gui@Viewer::STILL]); 1062} 1063 1064// Called repeatedly during the seek animation. 1065void 1066So@Gui@ViewerP::seeksensorCB(void * data, SoSensor * s) 1067{ 1068 SbTime currenttime = SbTime::getTimeOfDay(); 1069 1070 So@Gui@Viewer * thisp = (So@Gui@Viewer *)data; 1071 SoTimerSensor * sensor = (SoTimerSensor *)s; 1072 1073 float t = 1074 float((currenttime - sensor->getBaseTime()).getValue()) / PRIVATE(thisp)->seekperiod; 1075 if ((t > 1.0f) || (t + sensor->getInterval().getValue() > 1.0f)) t = 1.0f; 1076 SbBool end = (t == 1.0f); 1077 1078 t = (float) ((1.0 - cos(M_PI*t)) * 0.5); 1079 1080 PRIVATE(thisp)->camera->position = PRIVATE(thisp)->camerastartposition + 1081 (PRIVATE(thisp)->cameraendposition - PRIVATE(thisp)->camerastartposition) * t; 1082 PRIVATE(thisp)->camera->orientation = 1083 SbRotation::slerp(PRIVATE(thisp)->camerastartorient, 1084 PRIVATE(thisp)->cameraendorient, 1085 t); 1086 1087 if (end) thisp->setSeekMode(FALSE); 1088} 1089 1090// Reset the frames-per-second counter upon window resize events, 1091// abnormal delays, etc. 1092// 1093// The methods for recording FPS values are Coin extensions, not 1094// available in the original Open Inventor API. 1095// 1096// \sa addFrametime(), recordFPS() 1097void 1098So@Gui@ViewerP::resetFrameCounter(void) 1099{ 1100 this->framecount = 0; 1101 for (int i = 0; i < So@Gui@ViewerP::FRAMESARRAY_SIZE; i++) 1102 this->frames[i] = SbVec2f(0.0f, 0.0f); 1103 this->totalcoin = 0.0f; 1104 this->totaldraw = 0.0f; 1105 this->lastgettimeofday = SbTime::getTimeOfDay().getValue(); 1106} 1107 1108// Adds the time spent drawing the last frame to the array of past 1109// frame times. Returns the current averaged fps-value. 1110// 1111// The methods for recording FPS values are Coin extensions, not 1112// available in the original Open Inventor API. 1113// 1114// \sa resetFrameCounter(), recordFPS() 1115SbVec2f 1116So@Gui@ViewerP::addFrametime(const double ft) 1117{ 1118 this->framecount++; 1119 1120 int arrayptr = (this->framecount - 1) % FRAMESARRAY_SIZE; 1121 1122 this->totalcoin += (float(ft) - this->frames[arrayptr][0]); 1123 float coinfps = 1124 this->totalcoin / So@Gui@Min(this->framecount, (int) FRAMESARRAY_SIZE); 1125 1126 double timeofday = SbTime::getTimeOfDay().getValue(); 1127 double ct = timeofday - this->lastgettimeofday; 1128 this->totaldraw += (float(ct) - this->frames[arrayptr][1]); 1129 float drawfps = 1130 this->totaldraw / So@Gui@Min(this->framecount, (int) FRAMESARRAY_SIZE); 1131 1132 this->frames[arrayptr] = SbVec2f((float)ft, (float)ct); 1133 this->lastgettimeofday = timeofday; 1134 1135 return SbVec2f(1.0f / coinfps, 1.0f / drawfps); 1136} 1137 1138static unsigned char fps2dfont[][12] = { 1139 { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // 1140 { 0, 0, 12, 12, 0, 8, 12, 12, 12, 12, 12, 0 }, // ! 1141 { 0, 0, 0, 0, 0, 0, 0, 0, 0, 20, 20, 20 }, // \" 1142 { 0, 0, 18, 18, 18, 63, 18, 18, 63, 18, 18, 0 }, // # 1143 { 0, 8, 28, 42, 10, 10, 12, 24, 40, 42, 28, 8 }, // $ 1144 { 0, 0, 6, 73, 41, 22, 8, 52, 74, 73, 48, 0 }, // % 1145 { 0, 12, 18, 18, 12, 25, 37, 34, 34, 29, 0, 0 }, // & 1146 { 12, 12, 24, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // ' 1147 { 0, 6, 8, 8, 16, 16, 16, 16, 16, 8, 8, 6 }, // ( 1148 { 0, 48, 8, 8, 4, 4, 4, 4, 4, 8, 8, 48 }, //) 1149 { 0, 0, 0, 0, 0, 0, 8, 42, 20, 42, 8, 0 }, // * 1150 { 0, 0, 0, 8, 8, 8,127, 8, 8, 8, 0, 0 }, // + 1151 { 0, 24, 12, 12, 0, 0, 0, 0, 0, 0, 0, 0 }, // , 1152 { 0, 0, 0, 0, 0, 0,127, 0, 0, 0, 0, 0 }, // - 1153 { 0, 0, 24, 24, 0, 0, 0, 0, 0, 0, 0, 0 }, // . 1154 { 0, 32, 32, 16, 16, 8, 8, 8, 4, 4, 2, 2 }, // / 1155 { 0, 0, 28, 34, 34, 34, 34, 34, 34, 34, 28, 0 }, // 0 1156 { 0, 0, 8, 8, 8, 8, 8, 8, 40, 24, 8, 0 }, // 1 1157 { 0, 0, 62, 32, 16, 8, 4, 2, 2, 34, 28, 0 }, // 2 1158 { 0, 0, 28, 34, 2, 2, 12, 2, 2, 34, 28, 0 }, // 3 1159 { 0, 0, 4, 4, 4,126, 68, 36, 20, 12, 4, 0 }, // 4 1160 { 0, 0, 28, 34, 2, 2, 2, 60, 32, 32, 62, 0 }, // 5 1161 { 0, 0, 28, 34, 34, 34, 60, 32, 32, 34, 28, 0 }, // 6 1162 { 0, 0, 16, 16, 16, 8, 8, 4, 2, 2, 62, 0 }, // 7 1163 { 0, 0, 28, 34, 34, 34, 28, 34, 34, 34, 28, 0 }, // 8 1164 { 0, 0, 28, 34, 2, 2, 30, 34, 34, 34, 28, 0 }, // 9 1165 { 0, 0, 24, 24, 0, 0, 0, 24, 24, 0, 0, 0 }, // : 1166 { 0, 48, 24, 24, 0, 0, 0, 24, 24, 0, 0, 0 }, // ; 1167 { 0, 0, 0, 2, 4, 8, 16, 8, 4, 2, 0, 0 }, // < 1168 { 0, 0, 0, 0, 0,127, 0,127, 0, 0, 0, 0 }, // = 1169 { 0, 0, 0, 16, 8, 4, 2, 4, 8, 16, 0, 0 }, // > 1170 { 0, 0, 16, 16, 0, 16, 28, 2, 2, 2, 60, 0 }, // ? 1171 { 0, 0, 28, 32, 73, 86, 82, 82, 78, 34, 28, 0 }, // @ 1172 { 0, 0, 33, 33, 33, 63, 18, 18, 18, 12, 12, 0 }, // A 1173 { 0, 0, 60, 34, 34, 34, 60, 34, 34, 34, 60, 0 }, // B 1174 { 0, 0, 14, 16, 32, 32, 32, 32, 32, 18, 14, 0 }, // C 1175 { 0, 0, 56, 36, 34, 34, 34, 34, 34, 36, 56, 0 }, // D 1176 { 0, 0, 62, 32, 32, 32, 60, 32, 32, 32, 62, 0 }, // E 1177 { 0, 0, 16, 16, 16, 16, 30, 16, 16, 16, 30, 0 }, // F 1178 { 0, 0, 14, 18, 34, 34, 32, 32, 32, 18, 14, 0 }, // G 1179 { 0, 0, 34, 34, 34, 34, 62, 34, 34, 34, 34, 0 }, // H 1180 { 0, 0, 62, 8, 8, 8, 8, 8, 8, 8, 62, 0 }, // I 1181 { 0, 0,112, 8, 8, 8, 8, 8, 8, 8, 62, 0 }, // J 1182 { 0, 0, 33, 33, 34, 36, 56, 40, 36, 34, 33, 0 }, // K 1183 { 0, 0, 30, 16, 16, 16, 16, 16, 16, 16, 16, 0 }, // L 1184 { 0, 0, 33, 33, 33, 45, 45, 45, 51, 51, 33, 0 }, // M 1185 { 0, 0, 34, 34, 38, 38, 42, 42, 50, 50, 34, 0 }, // N 1186 { 0, 0, 12, 18, 33, 33, 33, 33, 33, 18, 12, 0 }, // O 1187 { 0, 0, 32, 32, 32, 60, 34, 34, 34, 34, 60, 0 }, // P 1188 { 3, 6, 12, 18, 33, 33, 33, 33, 33, 18, 12, 0 }, // Q 1189 { 0, 0, 34, 34, 34, 36, 60, 34, 34, 34, 60, 0 }, // R 1190 { 0, 0, 60, 2, 2, 6, 28, 48, 32, 32, 30, 0 }, // S 1191 { 0, 0, 8, 8, 8, 8, 8, 8, 8, 8,127, 0 }, // T 1192 { 0, 0, 28, 34, 34, 34, 34, 34, 34, 34, 34, 0 }, // U 1193 { 0, 0, 12, 12, 18, 18, 18, 33, 33, 33, 33, 0 }, // V 1194 { 0, 0, 34, 34, 34, 54, 85, 73, 73, 73, 65, 0 }, // W 1195 { 0, 0, 34, 34, 20, 20, 8, 20, 20, 34, 34, 0 }, // X 1196 { 0, 0, 8, 8, 8, 8, 20, 20, 34, 34, 34, 0 }, // Y 1197 { 0, 0, 62, 32, 16, 16, 8, 4, 4, 2, 62, 0 }, // Z 1198 { 0, 14, 8, 8, 8, 8, 8, 8, 8, 8, 8, 14 }, // [ 1199 { 0, 2, 2, 4, 4, 8, 8, 8, 16, 16, 32, 32 }, // [backslash] 1200 { 0, 56, 8, 8, 8, 8, 8, 8, 8, 8, 8, 56 }, // ] 1201 { 0, 0, 0, 0, 0, 34, 34, 20, 20, 8, 8, 0 }, // ^ 1202 { 0,127, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // _ 1203 { 0, 0, 0, 0, 0, 0, 0, 0, 0, 24, 24, 12 }, // ` 1204 { 0, 0, 29, 34, 34, 30, 2, 34, 28, 0, 0, 0 }, // a 1205 { 0, 0, 60, 34, 34, 34, 34, 50, 44, 32, 32, 32 }, // b 1206 { 0, 0, 14, 16, 32, 32, 32, 16, 14, 0, 0, 0 }, // c 1207 { 0, 0, 26, 38, 34, 34, 34, 34, 30, 2, 2, 2 }, // d 1208 { 0, 0, 28, 34, 32, 62, 34, 34, 28, 0, 0, 0 }, // e 1209 { 0, 0, 16, 16, 16, 16, 16, 16, 62, 16, 16, 14 }, // f 1210 { 28, 2, 2, 26, 38, 34, 34, 34, 30, 0, 0, 0 }, // g 1211 { 0, 0, 34, 34, 34, 34, 34, 50, 44, 32, 32, 32 }, // h 1212 { 0, 0, 8, 8, 8, 8, 8, 8, 56, 0, 8, 8 }, // i 1213 { 56, 4, 4, 4, 4, 4, 4, 4, 60, 0, 4, 4 }, // j 1214 { 0, 0, 33, 34, 36, 56, 40, 36, 34, 32, 32, 32 }, // k 1215 { 0, 0, 8, 8, 8, 8, 8, 8, 8, 8, 8, 56 }, // l 1216 { 0, 0, 73, 73, 73, 73, 73,109, 82, 0, 0, 0 }, // m 1217 { 0, 0, 34, 34, 34, 34, 34, 50, 44, 0, 0, 0 }, // n 1218 { 0, 0, 28, 34, 34, 34, 34, 34, 28, 0, 0, 0 }, // o 1219 { 32, 32, 60, 34, 34, 34, 34, 50, 44, 0, 0, 0 }, // p 1220 { 2, 2, 26, 38, 34, 34, 34, 34, 30, 0, 0, 0 }, // q 1221 { 0, 0, 16, 16, 16, 16, 16, 24, 22, 0, 0, 0 }, // r 1222 { 0, 0, 60, 2, 2, 28, 32, 32, 30, 0, 0, 0 }, // s 1223 { 0, 0, 14, 16, 16, 16, 16, 16, 62, 16, 16, 0 }, // t 1224 { 0, 0, 26, 38, 34, 34, 34, 34, 34, 0, 0, 0 }, // u 1225 { 0, 0, 8, 8, 20, 20, 34, 34, 34, 0, 0, 0 }, // v 1226 { 0, 0, 34, 34, 34, 85, 73, 73, 65, 0, 0, 0 }, // w 1227 { 0, 0, 34, 34, 20, 8, 20, 34, 34, 0, 0, 0 }, // x 1228 { 48, 16, 8, 8, 20, 20, 34, 34, 34, 0, 0, 0 }, // y 1229 { 0, 0, 62, 32, 16, 8, 4, 2, 62, 0, 0, 0 }, // z 1230 { 0, 6, 8, 8, 8, 4, 24, 4, 8, 8, 8, 6 }, // { 1231 { 0, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8 }, // | 1232 { 0, 48, 8, 8, 8, 16, 12, 16, 8, 8, 8, 48 }, // } 1233 { 0, 0, 0, 0, 0, 0, 78, 57, 0, 0, 0, 0 } // ~ 1234}; 1235 1236static void 1237printString(const char * s) 1238{ 1239 size_t i,n; 1240 n = strlen(s); 1241 for (i = 0; i < n; i++) 1242 glBitmap(8, 12, 0.0, 2.0, 10.0, 0.0, fps2dfont[s[i] - 32]); 1243} 1244 1245static void 1246Draw2DString(const char * str, SbVec2s glsize, SbVec2f position) 1247{ 1248 // Store GL state. 1249 glPushAttrib(GL_ENABLE_BIT|GL_CURRENT_BIT); 1250 1251 glDisable(GL_LIGHTING); 1252 glDisable(GL_DEPTH_TEST); 1253 glDisable(GL_TEXTURE_2D); 1254 glDisable(GL_BLEND); 1255 1256 glMatrixMode(GL_MODELVIEW); 1257 glPushMatrix(); 1258 glLoadIdentity(); 1259 1260 glMatrixMode(GL_PROJECTION); 1261 glPushMatrix(); 1262 glLoadIdentity(); 1263 glOrtho(0.0, glsize[0], 0.0, glsize[1], -1, 1); 1264 1265 glPixelStorei(GL_UNPACK_ALIGNMENT, 1); 1266 1267 glColor3f(0.0, 0.0, 0.0); 1268 glRasterPos2f(position[0] + 1, position[1]); 1269 printString(str); 1270 glRasterPos2f(position[0] - 1, position[1]); 1271 printString(str); 1272 glRasterPos2f(position[0], position[1] + 1); 1273 printString(str); 1274 glRasterPos2f(position[0], position[1] - 1); 1275 printString(str); 1276 1277 glColor3f(1.0, 1.0, 0.0); 1278 glRasterPos2f(position[0], position[1]); 1279 printString(str); 1280 1281 glMatrixMode(GL_PROJECTION); 1282 glPopMatrix(); 1283 glMatrixMode(GL_MODELVIEW); 1284 glPopMatrix(); 1285 1286 glPixelStorei(GL_UNPACK_ALIGNMENT, 4); // restore default value 1287 1288 glPopAttrib(); 1289} 1290 1291 1292// FIXME: the following is just a temporary hack to enable the FPS 1293// counter. We should really write a proper interface against it, so 1294// applications can set up feedback loops to control scene complexity 1295// and get a nice and steady maximum framerate, for instance. 1296// 1297// For anyone who want to execute that task, check what TGS has done 1298// first. If their API is fine, use the same approach. 1299// 1300// 20001124 mortene. 1301 1302// Draw a text string showing the current frame-per-seconds value in 1303// the lower left corner of the OpenGL canvas (after recording 1304// information needed to calculate the fps). 1305// 1306// The methods for recording FPS values are Coin extensions, not 1307// available in the original Open Inventor API. 1308// 1309// The two displayed values can be explained as follows: 1310// 1311// The first number is the time it takes for the SoGLRenderAction to 1312// traverse the scene graph (displayed as Hz / FPS). The second is the 1313// interval between each time SoGLRenderAction::apply() is invoked 1314// (i.e. the "actual" rendering rate, as experienced by the user). 1315// 1316// The first number is mainly useful just for internal debugging 1317// purposes. 1318// 1319// The second number will always be <= to the first, because it will 1320// also include the time stalling on glFlush() upon releasing the 1321// OpenGL context after traversal (glFlush() stalls until the GPU 1322// completes all OpenGL commands), and any application-code processing 1323// inbetween rendering. 1324// 1325// There is by the way a useful "trick" to improve application 1326// performance implied in that last paragraph above: if 1327// application-code processing is interleaved between the completion 1328// of the SoGLRenderAction traversal and the release of the OpenGL 1329// context, application-code is likely to run on the CPU in parallel 1330// with GPU processing OpenGL commands. 1331// 1332// FIXME: the above optimization trick should be documented in the 1333// visible API docs somewhere, with code to show how to do it. The 1334// example code would probably involve making an application-specific 1335// viewer, and overriding actualRedraw()? Or can it be done by using a 1336// callback mechanism somewhere? Ask pederb. 20031009 mortene. 1337// 1338// \sa resetFrameCounter(), addFrametime() 1339void 1340So@Gui@ViewerP::recordFPS(const double rendertime) 1341{ 1342 const char * env = SoAny::si()->getenv("COIN_SHOW_FPS_COUNTER"); 1343 if ( !env ) { 1344 COIN_SHOW_FPS_COUNTER = UNINITIALIZED_ENVVAR; 1345 } else { 1346 COIN_SHOW_FPS_COUNTER = atoi(env); 1347 } 1348 1349#if 0 1350 // disabled to make fps-couter dynamically adjustable 1351 if (COIN_SHOW_FPS_COUNTER == UNINITIALIZED_ENVVAR) { 1352 const char * env = SoAny::si()->getenv("COIN_SHOW_FPS_COUNTER"); 1353 COIN_SHOW_FPS_COUNTER = env ? atoi(env) : 0; 1354 } 1355#endif 1356 1357 if (COIN_SHOW_FPS_COUNTER > 0) { 1358 SbVec2f fps = this->addFrametime(rendertime); 1359 1360 char buffer[64]; 1361 int nr = sprintf(buffer, "%.1f/%.1f fps", fps[0], fps[1]); 1362 assert(nr < 64); 1363 Draw2DString(buffer, PUBLIC(this)->getGLSize(), SbVec2f(10, 10)); 1364 } 1365} 1366 1367// ************************************************************************* 1368 1369SO@GUI@_OBJECT_ABSTRACT_SOURCE(So@Gui@Viewer); 1370 1371// ************************************************************************* 1372 1373/*! 1374 \enum So@Gui@Viewer::Type 1375 1376 Hints about what context the viewer will be used in. Usually not 1377 very interesting for the application programmer, it doesn't matter 1378 much which value is used for the viewer type. This "feature" of the 1379 viewer is included just to be compatible with the old SGI Inventor 1380 API. 1381*/ 1382/*! 1383 \var So@Gui@Viewer::Type So@Gui@Viewer::BROWSER 1384 1385 If a user-supplied scenegraph passed into the setSceneGraph() 1386 function does not contain a camera, setting the viewer type to 1387 BROWSER will make the viewer in that case automatically set up a 1388 camera outside the scene, as part of the viewer's private and hidden 1389 "supergraph". 1390*/ 1391/*! 1392 \var So@Gui@Viewer::Type So@Gui@Viewer::EDITOR 1393 1394 If a user-supplied scenegraph passed into the setSceneGraph() 1395 function does not contain a camera, setting the viewer type to 1396 EDITOR will make the viewer in that case automatically set up a 1397 camera \e in the user-supplied scene. 1398 1399 So if you want to avoid having the So@Gui@Viewer class muck about 1400 with your supplied scenegraph, set the type-flag to 1401 So@Gui@Viewer::BROWSER instead, which makes an inserted camera node 1402 go into the viewer's own "wrapper" scene graph instead. 1403*/ 1404 1405/*! 1406 \enum So@Gui@Viewer::DrawType 1407 1408 Contains valid values for the first argument to the 1409 So@Gui@Viewer::setDrawStyle() call. Decides the effect of the second 1410 argument. 1411 1412 \sa So@Gui@Viewer::setDrawStyle(), So@Gui@Viewer::DrawStyle 1413*/ 1414/*! 1415 \var So@Gui@Viewer::DrawType So@Gui@Viewer::STILL 1416 1417 If this value is passed as the first argument of 1418 So@Gui@Viewer::setDrawStyle(), the second argument decides which 1419 draw style to use when the viewer camera is standing still in the 1420 same position with the same orientation -- i.e. when the end user is 1421 \e not interacting with the scene camera. 1422*/ 1423/*! 1424 \var So@Gui@Viewer::DrawType So@Gui@Viewer::INTERACTIVE 1425 1426 If this value is passed as the first argument of 1427 So@Gui@Viewer::setDrawStyle(), the second argument decides which 1428 draw style to use when the end user is interacting with the scene 1429 camera, causing continuous animation redraws. 1430*/ 1431 1432/*! 1433 \enum So@Gui@Viewer::DrawStyle 1434 1435 Decides drawstyle for a scene with either a still camera or an 1436 animating camera. 1437 1438 \sa So@Gui@Viewer::setDrawStyle(), So@Gui@Viewer::DrawType 1439*/ 1440/*! 1441 \var So@Gui@Viewer::DrawStyle So@Gui@Viewer::VIEW_AS_IS 1442 1443 Normal rendering, draws all scene geometry in it's original style. 1444*/ 1445/*! 1446 \var So@Gui@Viewer::DrawStyle So@Gui@Viewer::VIEW_HIDDEN_LINE 1447 1448 Draw scene in "hidden line" mode: that is, as wireframe with no 1449 "see-through". 1450 1451 Note that this is actually an expensive way to render, as the scene 1452 must be rendered twice to achieve the effect of hiding lines behind 1453 the invisible geometry. 1454*/ 1455/*! 1456 \var So@Gui@Viewer::DrawStyle So@Gui@Viewer::VIEW_WIREFRAME_OVERLAY 1457 1458 Render the scene as normal, but overlay a set of lines showing the 1459 contours of all polygons. 1460*/ 1461/*! 1462 \var So@Gui@Viewer::DrawStyle So@Gui@Viewer::VIEW_NO_TEXTURE 1463 1464 Render scene without textures. 1465*/ 1466/*! 1467 \var So@Gui@Viewer::DrawStyle So@Gui@Viewer::VIEW_LOW_COMPLEXITY 1468 1469 Render all "complex" shape types with low complexity to improve 1470 rendering performance. 1471 1472 "Complex shapes" in this context includes spheres, cones, cylinder, 1473 NURBS surfaces, and others which are tesselated to polygons before 1474 being rendered. 1475*/ 1476/*! 1477 \var So@Gui@Viewer::DrawStyle So@Gui@Viewer::VIEW_LINE 1478 1479 View all polygon geometry in wireframe mode. 1480*/ 1481/*! 1482 \var So@Gui@Viewer::DrawStyle So@Gui@Viewer::VIEW_POINT 1483 1484 Render only the vertex positions of the geometry. 1485*/ 1486/*! 1487 \var So@Gui@Viewer::DrawStyle So@Gui@Viewer::VIEW_BBOX 1488 1489 View the scene's bounding boxes, instead of rendering the full 1490 geometry. 1491 1492 A very efficient way of optimizing rendering performance for scenes 1493 with high primitive counts while moving the camera about is to set 1494 this mode for the So@Gui@Viewer::INTERACTIVE DrawType. 1495*/ 1496/*! 1497 \var So@Gui@Viewer::DrawStyle So@Gui@Viewer::VIEW_LOW_RES_LINE 1498 1499 Render as wireframe and don't bother with getting them rendered 1500 correctly in depth. 1501*/ 1502/*! 1503 \var So@Gui@Viewer::DrawStyle So@Gui@Viewer::VIEW_LOW_RES_POINT 1504 1505 Render as vertex points and don't bother with getting them rendered 1506 correctly in depth. 1507*/ 1508/*! 1509 \var So@Gui@Viewer::DrawStyle So@Gui@Viewer::VIEW_SAME_AS_STILL 1510 1511 Always render a scene with an animating camera (ie 1512 So@Gui@Viewer::INTERACTIVE DrawType) in the same manner as scene 1513 with a still camera. 1514*/ 1515 1516/*! 1517 \enum So@Gui@Viewer::BufferType 1518 Set of valid values for So@Gui@Viewer::setBufferingType(). 1519*/ 1520/*! 1521 \var So@Gui@Viewer::BufferType So@Gui@Viewer::BUFFER_SINGLE 1522 Change underlying OpenGL canvas to be single-buffered. 1523*/ 1524/*! 1525 \var So@Gui@Viewer::BufferType So@Gui@Viewer::BUFFER_DOUBLE 1526 Change underlying OpenGL canvas to be double-buffered. 1527*/ 1528/*! 1529 \var So@Gui@Viewer::BufferType So@Gui@Viewer::BUFFER_INTERACTIVE 1530 1531 Set up so animation rendering is done in a double-buffered OpenGL 1532 canvas, but ordinary rendering happens directly in the front-buffer. 1533 1534 This mode can be useful with absurdly large scenes, as the rendering 1535 will \e visibly progress, and one will avoid having the end user 1536 wonder why nothing is happening while the scene is rendered to the 1537 back buffer in the default So@Gui@Viewer::BUFFER_DOUBLE mode. 1538*/ 1539 1540// ************************************************************************* 1541 1542/* 1543 Return the parent node in the scene graph of the given \a node. 1544 NB: this is just a quick'n'dirty thing for often executed code, 1545 and doesn't cover cases where nodes have multiple parents. 1546*/ 1547 1548SoGroup * 1549So@Gui@ViewerP::getParentOfNode(SoNode * root, SoNode * node) const 1550{ 1551 assert(node && root && "called with null argument"); 1552 1553 const SbBool oldsearch = SoBaseKit::isSearchingChildren(); 1554 SoBaseKit::setSearchingChildren(TRUE); 1555 1556 this->searchaction->reset(); 1557 this->searchaction->setSearchingAll(TRUE); 1558 this->searchaction->setNode(node); 1559 this->searchaction->apply(root); 1560 1561 SoPath * p = this->searchaction->getPath(); 1562 1563 SoGroup * parent = NULL; 1564 if (p) { 1565 parent = (SoGroup *) ((SoFullPath *)p)->getNodeFromTail(1); 1566 assert(parent && "couldn't find parent"); 1567 } 1568 1569 this->searchaction->reset(); 1570 1571 SoBaseKit::setSearchingChildren(oldsearch); 1572 1573 return parent; 1574} 1575 1576// ************************************************************************* 1577 1578/*! 1579 Constructor. \a parent, \a name and \a embed are passed on to 1580 So@Gui@RenderArea, so see the documentation for our parent 1581 constructor for for more information on those. 1582 1583 The \a t type setting hints about what context the viewer will be 1584 used in. Usually not very interesting for the application 1585 programmer, but if you want to make sure the So@Gui@Viewer class 1586 doesn't muck about with your supplied scenegraph, set the type-flag 1587 to So@Gui@Viewer::BROWSER. (This "feature" of the viewer is 1588 included just to be compatible with the old SGI Inventor API.) 1589 1590 The \a build flag decides whether or not to delay building the 1591 widgets / window which is going to make up the components of the 1592 viewer. 1593*/ 1594So@Gui@Viewer::So@Gui@Viewer(@WIDGET@ parent, 1595 const char * name, 1596 SbBool embed, 1597 So@Gui@Viewer::Type t, 1598 SbBool build) 1599 : inherited(parent, name, embed, TRUE, TRUE, FALSE) 1600{ 1601 PRIVATE(this) = new So@Gui@ViewerP(this); 1602 1603 // initialization of protected data 1604 PRIVATE(this)->type = t; 1605 PRIVATE(this)->viewingflag = TRUE; 1606 PRIVATE(this)->altdown = FALSE; 1607 PRIVATE(this)->camera = NULL; 1608 PRIVATE(this)->scenegraph = NULL; 1609 1610 // initialization of internal data 1611 PRIVATE(this)->cursoron = TRUE; 1612 PRIVATE(this)->localsetbuffertype = FALSE; 1613 1614 PRIVATE(this)->cameratype = SoPerspectiveCamera::getClassTypeId(); 1615 PRIVATE(this)->buffertype = this->isDoubleBuffer() ? BUFFER_DOUBLE : BUFFER_SINGLE; 1616 1617 PRIVATE(this)->interactionstartCallbacks = new SoCallbackList; 1618 PRIVATE(this)->interactionendCallbacks = new SoCallbackList; 1619 PRIVATE(this)->interactionnesting = 0; 1620 1621 PRIVATE(this)->seekdistance = 50.0f; 1622 PRIVATE(this)->seekdistanceabs = FALSE; 1623 PRIVATE(this)->seektopoint = TRUE; 1624 PRIVATE(this)->seekperiod = 2.0f; 1625 PRIVATE(this)->inseekmode = FALSE; 1626 PRIVATE(this)->seeksensor = new SoTimerSensor(So@Gui@ViewerP::seeksensorCB, this); 1627 1628 PRIVATE(this)->sceneroot = PRIVATE(this)->createSuperScene(); 1629 PRIVATE(this)->sceneroot->ref(); 1630 1631 PRIVATE(this)->drawstyles[STILL] = VIEW_AS_IS; 1632 PRIVATE(this)->drawstyles[INTERACTIVE] = VIEW_SAME_AS_STILL; 1633 1634 this->addStartCallback(So@Gui@ViewerP::interactivestartCB); 1635 this->addFinishCallback(So@Gui@ViewerP::interactiveendCB); 1636 1637 PRIVATE(this)->adjustclipplanes = TRUE; 1638 PRIVATE(this)->autoclipbboxaction = NULL; 1639 1640 PRIVATE(this)->stereoviewing = FALSE; 1641 PRIVATE(this)->stereooffset = 0.1f; 1642 1643 PRIVATE(this)->wireframeoverlaycolor = SbColor(1.0f, 0.0f, 0.0f); 1644 1645 if (build) { 1646 this->setClassName("So@Gui@Viewer"); 1647 @WIDGET@ widget = this->buildWidget(this->getParentWidget()); 1648 this->setBaseWidget(widget); 1649 } 1650 1651 PRIVATE(this)->resetFrameCounter(); 1652} 1653 1654// ************************************************************************* 1655 1656/*! 1657 Destructor. 1658*/ 1659 1660So@Gui@Viewer::~So@Gui@Viewer() 1661{ 1662 delete PRIVATE(this)->autoclipbboxaction; 1663 1664 delete PRIVATE(this)->interactionstartCallbacks; 1665 delete PRIVATE(this)->interactionendCallbacks; 1666 1667 delete PRIVATE(this)->seeksensor; 1668 1669 if (PRIVATE(this)->scenegraph) this->setSceneGraph(NULL); 1670 if (PRIVATE(this)->superimpositions != NULL) { 1671 while ( PRIVATE(this)->superimpositions->getLength() > 0 ) { 1672 SoNode * node = (SoNode *) (*PRIVATE(this)->superimpositions)[0]; 1673 this->removeSuperimposition(node); 1674 } 1675 } 1676 PRIVATE(this)->sceneroot->unref(); 1677 delete PRIVATE(this); 1678} 1679 1680// ************************************************************************* 1681 1682// Note: the following function documentation block will also be used 1683// for all the miscellaneous viewer subclasses, so keep it general. 1684/*! 1685 Set the camera we want the viewer to manipulate when interacting with 1686 the viewer controls. 1687 1688 The camera passed in as an argument to this method \e must already 1689 be part of the viewer's scenegraph. You do \e not inject viewpoint 1690 cameras to the viewer with this method. 1691 1692 You should rather insert a camera into the scene graph first (if 1693 necessary, often one will be present already), then register it as 1694 the camera used by the viewer controls with this method. 1695 1696 If the application code doesn't explicitly set up a camera through 1697 this method, the viewer will automatically scan through the 1698 scenegraph to find a camera to use. If no camera is available in the 1699 scenegraph at all, it will set up it's own camera. 1700 1701 \sa getCamera() 1702*/ 1703void 1704So@Gui@Viewer::setCamera(SoCamera * cam) 1705{ 1706 if (PRIVATE(this)->camera) { 1707 // remove the camera from the super scene graph if we inserted a camera there 1708 int idx = PRIVATE(this)->sceneroot->findChild(PRIVATE(this)->camera); 1709 if (idx >= 0) { 1710 PRIVATE(this)->sceneroot->removeChild(idx); 1711 } 1712 PRIVATE(this)->camera->unref(); 1713 } 1714 1715 if (cam) { 1716 cam->ref(); 1717 PRIVATE(this)->cameratype = cam->getTypeId(); 1718 } 1719 1720 PRIVATE(this)->camera = cam; 1721 1722 this->saveHomePosition(); 1723} 1724 1725// ************************************************************************* 1726 1727/*! 1728 Returns the camera currently used by the viewer for the user's main 1729 viewpoint. 1730 1731 It \e is possible that this function returns \c NULL, for instance 1732 if there's no scenegraph present in the viewer. (This is mostly 1733 meant as a note for developers extending the So@Gui@ library, as 1734 application programmers usually controls if and when a viewer 1735 contains a scenegraph, and therefore know in advance if this method 1736 will return a valid camera pointer.) 1737 1738 \sa setCamera() 1739*/ 1740SoCamera * 1741So@Gui@Viewer::getCamera(void) const 1742{ 1743 // This impossible to miss reminder was inserted so we don't 1744 // accidentally let an So* v2 slip out the door without fixing this 1745 // API design flaw. 20030903 mortene. 1746#if (SO@GUI@_MAJOR_VERSION == 2) 1747#error This is a reminder: when jumping to version 2 of an So* toolkit, the So@Gui@Viewer::getCamera() method should be made virtual. 1748#endif // version = 2 1749 1750 return PRIVATE(this)->camera; 1751} 1752 1753// ************************************************************************* 1754 1755/*! 1756 When the viewer has to make its own camera as a result of the graph 1757 passed to setSceneGraph() not containing any camera nodes, this call 1758 can be made in advance to decide which type the camera will be of. 1759 1760 Default is to use an SoPerspectiveCamera. 1761 1762 If this method is called when there is a scene graph and a camera 1763 already set up, it will delete the old camera and set up a camera 1764 with the new type if the \a t type is different from that of the 1765 current camera. 1766 1767 \sa getCameraType() 1768*/ 1769 1770void 1771So@Gui@Viewer::setCameraType(SoType t) 1772{ 1773 if (PRIVATE(this)->camera && 1774 !PRIVATE(this)->camera->isOfType(SoPerspectiveCamera::getClassTypeId()) && 1775 !PRIVATE(this)->camera->isOfType(SoOrthographicCamera::getClassTypeId())) { 1776#if SO@GUI@_DEBUG 1777 SoDebugError::postWarning("So@Gui@Viewer::setCameraType", 1778 "Only SoPerspectiveCamera and SoOrthographicCamera is supported."); 1779#endif // SO@GUI_DEBUG 1780 return; 1781 } 1782 1783 1784 SoType perspectivetype = SoPerspectiveCamera::getClassTypeId(); 1785 SoType orthotype = SoOrthographicCamera::getClassTypeId(); 1786 SbBool oldisperspective = PRIVATE(this)->cameratype.isDerivedFrom(perspectivetype); 1787 SbBool newisperspective = t.isDerivedFrom(perspectivetype); 1788 1789 if ((oldisperspective && newisperspective) || 1790 (!oldisperspective && !newisperspective)) // Same old, same old.. 1791 return; 1792 1793 if (SO@GUI@_DEBUG) { 1794 SbBool valid = TRUE; 1795 if (t == SoType::badType()) valid = FALSE; 1796 if (valid) { 1797 valid = FALSE; 1798 if (newisperspective) valid = TRUE; 1799 if (t.isDerivedFrom(orthotype)) valid = TRUE; 1800 } 1801 1802 if (!valid) { 1803 SoDebugError::post("So@Gui@Viewer::setCameraType", 1804 "not a valid camera type: '%s'", 1805 t == SoType::badType() ? 1806 "badType" : t.getName().getString()); 1807 return; 1808 } 1809 } 1810 1811 SoCamera * currentcam = PRIVATE(this)->camera; 1812 1813 if (currentcam == NULL) { 1814 // A camera has not been set up for the scene yet, so just store 1815 // the type and short-cut the rest of this function. 1816 PRIVATE(this)->cameratype = t; 1817 return; 1818 } 1819 1820 SoCamera * newcamera = (SoCamera *)t.createInstance(); 1821 1822 // Transfer and convert values from one camera type to the other. 1823 if (newisperspective) { 1824 So@Gui@ViewerP::convertOrtho2Perspective((SoOrthographicCamera *)currentcam, 1825 (SoPerspectiveCamera *)newcamera); 1826 } 1827 else { 1828 So@Gui@ViewerP::convertPerspective2Ortho((SoPerspectiveCamera *)currentcam, 1829 (SoOrthographicCamera *)newcamera); 1830 } 1831 1832 SoGroup * cameraparent = 1833 PRIVATE(this)->getParentOfNode(PRIVATE(this)->sceneroot, currentcam); 1834 if (cameraparent) { cameraparent->replaceChild(currentcam, newcamera); } 1835 else { 1836 // camera not actually present in the scene graph, so just NULL 1837 // and void. 1838 newcamera->ref(); 1839 newcamera->unref(); 1840 newcamera = NULL; 1841 1842 // Yes, this can "legally" happen, if e.g. the camera is taken out 1843 // of the scene graph by the app programmer, and no new camera was 1844 // set with setCamera() -- but this is a quite odd thing to do, so 1845 // we warn about this for now. 1846 SoDebugError::postWarning("So@Gui@Viewer::setCameraType", 1847 "Could not find the current camera in the " 1848 "scene graph, for some odd reason."); 1849 } 1850 1851 // The setCamera() invokation below will set the saved "home" 1852 // position of the camera to the current camera position. We make 1853 // no attempt to avoid this, as it would involve nasty hacks, and 1854 // it shouldn't really matter. 1855 1856 this->setCamera(newcamera); // This will set PRIVATE(this)->cameratype. 1857} 1858 1859// ************************************************************************* 1860 1861/*! 1862 Returns camera type which will be used when the viewer has to make its 1863 own camera. 1864 1865 Note that this call does \e not return the current cameratype, as one 1866 might expect. Use getCamera() and SoType::getTypeId() for that inquiry. 1867 1868 \sa setCameraType() 1869*/ 1870 1871SoType 1872So@Gui@Viewer::getCameraType(void) const 1873{ 1874 return PRIVATE(this)->cameratype; 1875} 1876 1877// ************************************************************************* 1878 1879/*! 1880 Reposition the current camera so we can see the complete scene. 1881*/ 1882void 1883So@Gui@Viewer::viewAll(void) 1884{ 1885 SoCamera * cam = PRIVATE(this)->camera; 1886 if (cam && PRIVATE(this)->scenegraph) { 1887 cam->viewAll(PRIVATE(this)->scenegraph, this->getViewportRegion()); 1888 } 1889} 1890 1891// ************************************************************************* 1892 1893/*! 1894 Store the current camera settings for later retrieval with 1895 resetToHomePosition(). 1896 1897 \sa resetToHomePosition() 1898*/ 1899void 1900So@Gui@Viewer::saveHomePosition(void) 1901{ 1902 if (! PRIVATE(this)->camera) return; // probably a scene-less viewer 1903 1904 // We use SoType::createInstance() to store a copy of the camera, 1905 // not just assuming it's either a perspective or an orthographic 1906 // camera. 1907 1908 SoType t = PRIVATE(this)->camera->getTypeId(); 1909 assert(t.isDerivedFrom(SoNode::getClassTypeId())); 1910 assert(t.canCreateInstance()); 1911 1912 if (PRIVATE(this)->storedcamera) { PRIVATE(this)->storedcamera->unref(); } 1913 1914 PRIVATE(this)->storedcamera = (SoNode *)t.createInstance(); 1915 PRIVATE(this)->storedcamera->ref(); 1916 1917 // We copy the field data directly, instead of using 1918 // SoFieldContainer::copyContents(), as that has one problematic 1919 // side-effect: the new camera node used for storing the data would 1920 // also get the *name* of the old camera, which would overwrite the 1921 // old name->ptr entry of the global dictionary behind 1922 // SoNode::getByName(). This can cause surprising and hard to find 1923 // bugs for app programmers, for instance when using 1924 // SoNode::getByName() to get at a camera loaded from an iv-file. 1925 PRIVATE(this)->storedcamera->copyFieldValues(PRIVATE(this)->camera); 1926} 1927 1928// ************************************************************************* 1929 1930/*! 1931 Restore the saved camera settings. 1932 1933 \sa saveHomePosition() 1934*/ 1935void 1936So@Gui@Viewer::resetToHomePosition(void) 1937{ 1938 if (!PRIVATE(this)->camera) { return; } // probably a scene-less viewer 1939 if (!PRIVATE(this)->storedcamera) { return; } 1940 1941 SoType t = PRIVATE(this)->camera->getTypeId(); 1942 SoType s = PRIVATE(this)->storedcamera->getTypeId(); 1943 1944 // most common case 1945 if (t == s) { 1946 // We copy the field data directly, instead of using 1947 // SoFieldContainer::copyContents(), for the reason described in 1948 // detail in So@Gui@Viewer::saveHomePosition(). 1949 PRIVATE(this)->camera->copyFieldValues(PRIVATE(this)->storedcamera); 1950 } 1951 // handle common case #1 1952 else if (t == SoOrthographicCamera::getClassTypeId() && 1953 s == SoPerspectiveCamera::getClassTypeId()) { 1954 So@Gui@ViewerP::convertPerspective2Ortho((SoPerspectiveCamera *)PRIVATE(this)->storedcamera, 1955 (SoOrthographicCamera *)PRIVATE(this)->camera); 1956 } 1957 // handle common case #2 1958 else if (t == SoPerspectiveCamera::getClassTypeId() && 1959 s == SoOrthographicCamera::getClassTypeId()) { 1960 So@Gui@ViewerP::convertOrtho2Perspective((SoOrthographicCamera *)PRIVATE(this)->storedcamera, 1961 (SoPerspectiveCamera *)PRIVATE(this)->camera); 1962 } 1963 // otherwise, cameras have changed in ways we don't understand since 1964 // the last saveHomePosition() invokation, and so we're just going 1965 // to ignore the reset request 1966} 1967 1968// ************************************************************************* 1969 1970/*! 1971 Turn the camera headlight on or off. 1972 1973 Default is to have a headlight turned on. 1974 1975 \sa isHeadlight(), getHeadlight() 1976*/ 1977 1978void 1979So@Gui@Viewer::setHeadlight(SbBool on) 1980{ 1981 PRIVATE(this)->headlight->on = on; 1982} 1983 1984// ************************************************************************* 1985 1986/*! 1987 Returns status of the viewer headlight, whether it is on or off. 1988 1989 \sa setHeadlight(), getHeadlight() 1990*/ 1991 1992SbBool 1993So@Gui@Viewer::isHeadlight(void) const 1994{ 1995 return PRIVATE(this)->headlight->on.getValue(); 1996} 1997 1998// ************************************************************************* 1999 2000/*! 2001 Returns the a pointer to the directional light node which is the 2002 viewer headlight. 2003 2004 The fields of the node is available for user editing. 2005 2006 \sa isHeadlight(), setHeadlight() 2007*/ 2008 2009SoDirectionalLight * 2010So@Gui@Viewer::getHeadlight(void) const 2011{ 2012 return PRIVATE(this)->headlight; 2013} 2014 2015// ************************************************************************* 2016 2017/*! 2018 Set up a drawing style. The \a type argument specifies if the given 2019 \a style should be interpreted as the drawstyle during animation or 2020 when the camera is static. 2021 2022 Default values for the drawing style is to render the scene "as is" 2023 in both still mode and while the camera is moving. 2024 2025 See the documentation for the \a DrawType and \a DrawStyle for more 2026 information. 2027 2028 \sa getDrawStyle() 2029*/ 2030void 2031So@Gui@Viewer::setDrawStyle(So@Gui@Viewer::DrawType type, 2032 So@Gui@Viewer::DrawStyle style) 2033{ 2034 if (SO@GUI@_DEBUG) { 2035 if ((type != STILL) && (type != INTERACTIVE)) { 2036 SoDebugError::postWarning("So@Gui@Viewer::setDrawStyle", 2037 "unknown drawstyle type setting 0x%x", type); 2038 return; 2039 } 2040 } 2041 2042 if (style == this->getDrawStyle(type)) { 2043 if (SO@GUI@_DEBUG && 0) { // debug 2044 SoDebugError::postWarning("So@Gui@Viewer::setDrawStyle", 2045 "drawstyle for type 0x%02x already 0x%02x", 2046 type, style); 2047 } 2048 return; 2049 } 2050 2051 PRIVATE(this)->drawstyles[type] = style; 2052 PRIVATE(this)->changeDrawStyle(PRIVATE(this)->currentDrawStyle()); 2053} 2054 2055// ************************************************************************* 2056 2057/*! 2058 Return current drawstyles for the given type (\a STILL or 2059 \a INTERACTIVE). 2060 2061 \sa setDrawStyle() 2062*/ 2063 2064So@Gui@Viewer::DrawStyle 2065So@Gui@Viewer::getDrawStyle(const So@Gui@Viewer::DrawType type) const 2066{ 2067 if (SO@GUI@_DEBUG) { 2068 if ((type != STILL) && (type != INTERACTIVE)) { 2069 SoDebugError::postWarning("So@Gui@Viewer::setDrawStyle", 2070 "unknown drawstyle type setting 0x%x", type); 2071 return PRIVATE(this)->drawstyles[STILL]; 2072 } 2073 } 2074 2075 return PRIVATE(this)->drawstyles[type]; 2076} 2077 2078// ************************************************************************* 2079 2080/*! 2081 Set the viewer's buffer type. Available types are \c 2082 So@Gui@Viewer::BUFFER_SINGLE, \c So@Gui@Viewer::BUFFER_DOUBLE and \c 2083 So@Gui@Viewer::BUFFER_INTERACTIVE. 2084 2085 (With a buffer type of \c So@Gui@Viewer::BUFFER_INTERACTIVE, the 2086 viewer will render with doublebuffering during user interaction and 2087 with single buffering otherwise.) 2088 2089 Default is \c So@Gui@Viewer::BUFFER_DOUBLE. 2090 2091 \sa getBufferingType() 2092*/ 2093 2094void 2095So@Gui@Viewer::setBufferingType(So@Gui@Viewer::BufferType type) 2096{ 2097 if (type == PRIVATE(this)->buffertype) return; 2098 2099 if (type != BUFFER_SINGLE && 2100 type != BUFFER_DOUBLE && 2101 type != BUFFER_INTERACTIVE) { 2102 if (SO@GUI@_DEBUG) { 2103 SoDebugError::postWarning("So@Gui@Viewer::setBufferingType", 2104 "unknown buffer type 0x%x", type); 2105 } 2106 return; 2107 } 2108 2109 PRIVATE(this)->buffertype = type; 2110 2111 PRIVATE(this)->localsetbuffertype = TRUE; 2112 inherited::setDoubleBuffer(type == BUFFER_DOUBLE); 2113 PRIVATE(this)->localsetbuffertype = FALSE; 2114} 2115 2116// ************************************************************************* 2117 2118/*! 2119 Return the viewer's buffer type. 2120 2121 \sa setBufferingType() 2122*/ 2123 2124So@Gui@Viewer::BufferType 2125So@Gui@Viewer::getBufferingType(void) const 2126{ 2127 return PRIVATE(this)->buffertype; 2128} 2129 2130// ************************************************************************* 2131 2132// Note: this documentation for setViewing() will also be used for all 2133// the miscellaneous viewer subclasses, so keep it general. 2134/*! 2135 Set view mode. 2136 2137 If the view mode is on, user events will be caught and used to 2138 influence the camera position / orientation. If view mode is off, 2139 all events in the viewer canvas (like for instance keypresses or 2140 mouseclicks and -movements) will be passed along to the scene graph. 2141 2142 Default is to have the view mode active. 2143 2144 \sa isViewing() 2145*/ 2146void 2147So@Gui@Viewer::setViewing(SbBool enable) 2148{ 2149 if (PRIVATE(this)->viewingflag == enable) { 2150 if (SO@GUI@_DEBUG) { 2151 SoDebugError::postWarning("So@Gui@Viewer::setViewing", 2152 "unnecessary called"); 2153 } 2154 return; 2155 } 2156 2157 PRIVATE(this)->viewingflag = enable; 2158 2159 // Turn off the selection indicators when we go back from picking 2160 // mode into viewing mode. 2161 if (PRIVATE(this)->viewingflag) { 2162 SoGLRenderAction * action = this->getGLRenderAction(); 2163 if (action != NULL) 2164 SoLocateHighlight::turnOffCurrentHighlight(action); 2165 } 2166} 2167 2168// ************************************************************************* 2169 2170/*! 2171 Return state of view mode. 2172 2173 \c TRUE means that the mode of the viewer is set such that user 2174 interaction with the mouse is used to modify the position and 2175 orientation of the camera. 2176 2177 \sa setViewing() 2178*/ 2179SbBool 2180So@Gui@Viewer::isViewing(void) const 2181{ 2182 return PRIVATE(this)->viewingflag; 2183} 2184 2185// ************************************************************************* 2186 2187/*! 2188 Set whether or not the mouse cursor representation should be visible 2189 in the viewer canvas. 2190 2191 Default value is on. 2192 2193 \sa isCursorEnabled() 2194*/ 2195 2196void 2197So@Gui@Viewer::setCursorEnabled(SbBool on) 2198{ 2199 PRIVATE(this)->cursoron = on; 2200} 2201 2202// ************************************************************************* 2203 2204/*! 2205 Returns visibility status of mouse cursor. 2206 2207 \sa setCursorEnabled() 2208*/ 2209 2210SbBool 2211So@Gui@Viewer::isCursorEnabled(void) const 2212{ 2213 return PRIVATE(this)->cursoron; 2214} 2215 2216// ************************************************************************* 2217 2218/*! 2219 Turn on or off continuous automatic adjustments of the near and far 2220 clipping planes. 2221 2222 If on, the distance from the camera position to the near and far 2223 planes will be calculated to be a "best fit" around the geometry in 2224 the scene, to maximize the "stretch" of values for the visible 2225 geometry in the z-buffer. This is important, as z-buffer resolution 2226 is usually limited enough that one will quickly see flickering in 2227 the rasterization of close polygons upon lousy utilization of the 2228 z-buffer. 2229 2230 Automatic calculations of near and far clip planes are on as 2231 default. 2232 2233 For better control over what happens in boundary conditions (for 2234 instance when the distance between near and far planes get very far, 2235 or if geometry gets very close to the camera position), it is 2236 possible to use the So@Gui@Viewer::setAutoClippingStrategy() method 2237 to fine-tune the near/far clipping plane settings. 2238 2239 On a major note, be aware that turning auto-updating of near and far 2240 clip planes \e off have a potentially serious detrimental effect on 2241 performance, due to an important side effect: updating the near and 2242 far clip planes triggers an SoGetBoundingBoxAction to traverse the 2243 scene graph, which causes bounding boxes to be calculated and stored 2244 in caches. The bounding box caches are then used by the 2245 SoGLRenderAction traversal for view frustum culling operations. With 2246 no bounding box caches, the rendering will not do culling, which can 2247 cause much worse performance. Kongsberg Oil & Gas Technologies are 2248 working on correcting this problem properly from within the Coin 2249 library. 2250 2251 On a minor note, be aware that notifications will be temporarily 2252 turned off for the scene's SoCamera when changing the near and far 2253 clipping planes (which is done right before each redraw). This is 2254 done to avoid notifications being sent through the scene graph right 2255 before rendering, as that causes some latency. It is mentioned here 2256 in case you have any client code which for some reason needs to 2257 sense all changes to the scene camera. This is however unlikely, so 2258 you can very probably ignore this. 2259 2260 \sa getAutoClipping() 2261*/ 2262 2263void 2264So@Gui@Viewer::setAutoClipping(SbBool enable) 2265{ 2266 if (SO@GUI@_DEBUG) { 2267 if (PRIVATE(this)->adjustclipplanes == enable) { 2268 SoDebugError::postWarning("So@Gui@Viewer::setAutoClipping", 2269 "unnecessary called"); 2270 return; 2271 } 2272 } 2273 2274 PRIVATE(this)->adjustclipplanes = enable; 2275 if (enable) { this->scheduleRedraw(); } 2276} 2277 2278/*! 2279 Set the strategy used for automatic updates of the distances to the 2280 near and far clipping planes. 2281 2282 When auto clipping is enabled, the near plane distance is calculated 2283 so that it is just in front of the scene bounding box. If this near 2284 plane is behind or very close to the projection point, one of the 2285 following strategies will be used to calculate the new clipping 2286 plane. 2287 2288 The VARIABLE_NEAR_PLANE strategy considers the number of z buffer 2289 bits available for the current OpenGL context, and uses \a value to 2290 calculate the number of bits that is lost because of the far/near 2291 ratio. \a value should be in the range [0.0, 1.0]. A higher \a value 2292 will increase the z-buffer precision, but also push the near plane 2293 further away from the projection point. 2294 2295 The CONSTANT_NEAR_PLANE strategy simply sets the near plane to 2296 \a value. If \a value at some point approaches the far clipping 2297 plane distance, the near plane distance will be set to far plane 2298 distance divided by 5000.0. 2299 2300 The default strategy is VARIABLE_NEAR_PLANE. 2301 2302 2303 It is also possible to register a callback method \a cb, which will 2304 then be invoked after the near and far clipping planes has been 2305 calculated by the So@Gui@Viewer code. The callback can then adjust 2306 the values for the distance to the near and far planes to exactly 2307 match the needs of the application (for instance at specific parts 2308 in the scene), to limit the distance to either plane, or whatever 2309 else needs to be controlled. 2310 2311 The signature of the So@Gui@AutoClippingCB callback must match: 2312 \code 2313 SbVec2f myfunc(void * data, const SbVec2f & nearfar); 2314 \endcode 2315 2316 The first argument is the \a cbuserdata passed in along with the 2317 callback function pointer itself (ie the callback function's 2318 closure). The second argument is the near and far clipping plane 2319 distances from the camera position, as calculated internally by the 2320 viewer, including "slack". 2321 2322 The function callback can then modify the near and far clipping 2323 plane distances to what will \e actually be used by the 2324 viewer. These values will then be used unmodified for the viewer's 2325 camera. 2326 2327 This is a good way of dynamically modifying the near and far 2328 distances such that they at all times exactly matches the specific 2329 layout of the application scene, for instance with regard to the 2330 trade-off between z-buffer resolution and how early geometry is 2331 clipped at the near plane (or at the far plane). 2332 2333 Note that the internal near/far calculations should be good enough 2334 for the vast majority of scenes. Application programmers should only 2335 need to set up their own adjustments upon "unusual" scenes, like for 2336 instance scenes with a large world space, but where one would still 2337 like to be able to get up extremely close on details in some parts 2338 of the scene. 2339 2340 2341 \sa setAutoClipping() 2342*/ 2343void 2344So@Gui@Viewer::setAutoClippingStrategy(const AutoClippingStrategy strategy, 2345 const float value, 2346 So@Gui@AutoClippingCB * cb, 2347 void * cbuserdata) 2348{ 2349 PRIVATE(this)->autoclipstrategy = strategy; 2350 PRIVATE(this)->autoclipvalue = value; 2351 PRIVATE(this)->autoclipcb = cb; 2352 PRIVATE(this)->autoclipuserdata = cbuserdata; 2353 2354 if (PRIVATE(this)->autoclipstrategy == VARIABLE_NEAR_PLANE) { 2355 // normalize the value so that the near plane isn't too near or 2356 // too far from the projection point. FIXME: calibrate this 2357 // normalization, pederb, 2002-04-25 2358 float v = So@Gui@Clamp(value, 0.0f, 1.0f); // just in case 2359 v *= 0.8f; 2360 v += 0.1f; // v will be in range [0.1, 0.9] 2361 2362 PRIVATE(this)->autoclipvalue = v; 2363 } 2364 if (PRIVATE(this)->adjustclipplanes) { 2365 this->scheduleRedraw(); 2366 } 2367} 2368 2369// ************************************************************************* 2370 2371/*! 2372 Return value of the automatic near/far clipplane adjustment indicator. 2373 2374 \sa setAutoClipping() 2375*/ 2376 2377SbBool 2378So@Gui@Viewer::isAutoClipping(void) const 2379{ 2380 return PRIVATE(this)->adjustclipplanes; 2381} 2382 2383// ************************************************************************* 2384 2385/*! 2386 Turn stereo viewing on or off. 2387 2388 Note: this function is being obsoleted, you should use the 2389 setStereoType() function instead. 2390 2391 Coin does "correct" stereo rendering, using the method known as 2392 "parallel axis asymmetric frustum perspective projection". For more 2393 information, see this link: 2394 2395 http://astronomy.swin.edu.au/~pbourke/opengl/stereogl/ 2396 2397 \sa isStereoViewing(), setStereoType() 2398*/ 2399 2400void 2401So@Gui@Viewer::setStereoViewing(SbBool enable) 2402{ 2403 PRIVATE(this)->stereoviewing = enable; 2404 this->scheduleRedraw(); 2405} 2406 2407/*! 2408 Returns a boolean indicating whether or not we're in stereo viewing 2409 mode. 2410 2411 NOTE: in the original InventorXt API, this method was virtual. It is not 2412 virtual here. 2413 2414 \sa setStereoViewing(), getStereoType() 2415*/ 2416 2417SbBool 2418So@Gui@Viewer::isStereoViewing(void) const 2419{ 2420 return PRIVATE(this)->stereoviewing; 2421} 2422 2423// ************************************************************************* 2424 2425/*! 2426 \enum So@Gui@Viewer::StereoType 2427 2428 Contains list of supported stereo rendering techniques. 2429 2430 \sa So@Gui@Viewer::setStereoType() 2431*/ 2432/*! 2433 \var So@Gui@Viewer::StereoType So@Gui@Viewer::STEREO_NONE 2434 2435 Use monoscopic rendering. 2436*/ 2437/*! 2438 \var So@Gui@Viewer::StereoType So@Gui@Viewer::STEREO_ANAGLYPH 2439 2440 Render stereo by superimposing two images of the same scene, but with 2441 different color filters over the left and right view (or "eye"). 2442 2443 This is a way of rendering stereo which works on any display, using 2444 color-filter glasses. Such glasses are usually cheap and easy to 2445 come by. 2446 2447 \sa setAnaglyphStereoColorMasks() 2448*/ 2449/*! 2450 \var So@Gui@Viewer::StereoType So@Gui@Viewer::STEREO_QUADBUFFER 2451 2452 Render stereo by using OpenGL quad-buffers. This is the most common 2453 interface for stereo rendering for more expensive hardware devices, 2454 such as shutter glasses and polarized glasses. 2455 2456 The well known Crystal Eyes glasses are commonly used with this type 2457 of stereo display. 2458*/ 2459/*! 2460 \var So@Gui@Viewer::StereoType So@Gui@Viewer::STEREO_INTERLEAVED_ROWS 2461 2462 Interleaving / interlacing rows from the left and right eye is 2463 another stereo rendering method requiring special hardware. One 2464 example of a provider of shutter glasses working with interleaved 2465 glasses is VRex: 2466 2467 http://www.vrex.com/ 2468*/ 2469/*! 2470 \var So@Gui@Viewer::StereoType So@Gui@Viewer::STEREO_INTERLEAVED_COLUMNS 2471 2472 Same basic technique as So@Gui@Viewer::STEREO_INTERLEAVED_ROWS, only 2473 it is vertical lines that are interleaved / interlaced, instead of 2474 horizontal lines. 2475*/ 2476 2477/*! 2478 Set up stereo rendering. 2479 2480 Coin does "correct" stereo rendering, using the method known as 2481 "parallel axis asymmetric frustum perspective projection". For more 2482 information, see this link: 2483 2484 http://astronomy.swin.edu.au/~pbourke/opengl/stereogl/ 2485 2486 2487 Note: it is prefered that one uses this function for control of 2488 which type of stereo rendering to use, instead of the older 2489 So@Gui@Viewer::setStereoViewing() and 2490 So@Gui@GLWidget::setQuadBufferStereo() functions. 2491 2492 The default is to do monoscopic rendering, i.e. the default 2493 So@Gui@Viewer::StereoType value is So@Gui@Viewer::STEREO_NONE. 2494 2495 \sa So@Gui@Viewer::StereoType, SoCamera::setStereoAdjustment 2496 \since So@Gui@ 1.2 2497*/ 2498SbBool 2499So@Gui@Viewer::setStereoType(So@Gui@Viewer::StereoType s) 2500{ 2501 if (s == this->getStereoType()) { return TRUE; } 2502 2503 // We need to know this to keep compatibility with older client 2504 // code, which controlled stereo rendering with setStereoViewing() 2505 // and setQuadBufferStereo() only. 2506 PRIVATE(this)->stereotypesetexplicit = TRUE; 2507 2508 switch (s) { 2509 case So@Gui@Viewer::STEREO_NONE: 2510 this->setQuadBufferStereo(FALSE); 2511 this->setStereoViewing(FALSE); 2512 break; 2513 2514 case So@Gui@Viewer::STEREO_ANAGLYPH: 2515 this->setStereoViewing(TRUE); 2516 this->setQuadBufferStereo(FALSE); 2517 break; 2518 2519 case So@Gui@Viewer::STEREO_QUADBUFFER: 2520 this->setStereoViewing(TRUE); 2521 this->setQuadBufferStereo(TRUE); 2522 2523 // Check, in case GL quad buffers not supported with the driver 2524 // config: 2525 if (!this->isQuadBufferStereo()) { 2526 this->setStereoViewing(FALSE); 2527 return FALSE; 2528 } 2529 break; 2530 2531 case So@Gui@Viewer::STEREO_INTERLEAVED_ROWS: 2532 case So@Gui@Viewer::STEREO_INTERLEAVED_COLUMNS: 2533 this->setStereoViewing(TRUE); 2534 this->setQuadBufferStereo(FALSE); 2535 this->setStencilBuffer(TRUE); 2536 2537 // Check, in case GL stencil buffers not supported with the driver 2538 // config: 2539 if (!this->getStencilBuffer()) { 2540 this->setStereoViewing(FALSE); 2541 return FALSE; 2542 } 2543 break; 2544 2545 default: 2546 assert(FALSE); break; 2547 } 2548 2549 PRIVATE(this)->stereotype = s; 2550 return TRUE; 2551} 2552 2553/*! 2554 Returns the current type of stereo rendering used (or 2555 So@Gui@Viewer::STEREO_NONE if monoscopic). 2556*/ 2557So@Gui@Viewer::StereoType 2558So@Gui@Viewer::getStereoType(void) const 2559{ 2560 // Stereo can be set up without using setStereoType() through the 2561 // older functions setStereoViewing() and setQuadBufferStereo(), so 2562 // we need to check for this separately. 2563 if (!PRIVATE(this)->stereotypesetexplicit) { 2564 if (this->isQuadBufferStereo()) { 2565 PRIVATE(this)->stereotype = So@Gui@Viewer::STEREO_QUADBUFFER; 2566 } 2567 else if (this->isStereoViewing()) { 2568 PRIVATE(this)->stereotype = So@Gui@Viewer::STEREO_ANAGLYPH; 2569 } 2570 } 2571 2572 return PRIVATE(this)->stereotype; 2573} 2574 2575// ************************************************************************* 2576 2577/*! 2578 If display is configured to render in anaglyph stereo, this function 2579 can be used to control which filter is used for each eye. 2580 2581 The default filters are red (i.e. color vector [TRUE,FALSE,FALSE]) 2582 for the left eye, and cyan (color vector [FALSE,TRUE,TRUE]) for the 2583 right eye. 2584 2585 \sa So@Gui@Viewer::StereoType, setStereoType() 2586*/ 2587void 2588So@Gui@Viewer::setAnaglyphStereoColorMasks(const SbBool left[3], const SbBool right[3]) 2589{ 2590 for (unsigned int i = 0; i < 3; i++) { 2591 PRIVATE(this)->stereoanaglyphmask[0][i] = left[i]; 2592 PRIVATE(this)->stereoanaglyphmask[1][i] = right[i]; 2593 } 2594 2595 this->scheduleRedraw(); 2596} 2597 2598/*! 2599 Returns color masks for left and right eye filters in anaglyph 2600 stereo. 2601 2602 \sa setAnaglyphStereoColorMasks() 2603*/ 2604void 2605So@Gui@Viewer::getAnaglyphStereoColorMasks(SbBool left[3], SbBool right[3]) 2606{ 2607 for (unsigned int i = 0; i < 3; i++) { 2608 left[i] = PRIVATE(this)->stereoanaglyphmask[0][i]; 2609 right[i] = PRIVATE(this)->stereoanaglyphmask[1][i]; 2610 } 2611} 2612 2613// ************************************************************************* 2614 2615/*! 2616 Set the offset between the two viewpoints when in stereo mode. 2617 Default value is 0.1. 2618 2619 NOTE: In the original InventorXt API, this method was not virtual. 2620 2621 \sa getStereoOffset() 2622*/ 2623void 2624So@Gui@Viewer::setStereoOffset(const float dist) 2625{ 2626 PRIVATE(this)->stereooffset = dist; 2627 this->scheduleRedraw(); 2628} 2629 2630/*! 2631 Return the offset distance between the two viewpoints when in stereo 2632 mode. 2633 2634 \sa setStereoOffset() 2635*/ 2636float 2637So@Gui@Viewer::getStereoOffset(void) const 2638{ 2639 return PRIVATE(this)->stereooffset; 2640} 2641 2642// ************************************************************************* 2643 2644/*! 2645 Toggle between seeking to a point or seeking to an object. 2646 2647 Default is to seek to a point. 2648 2649 \sa isDetailSeek() 2650*/ 2651 2652void 2653So@Gui@Viewer::setDetailSeek(const SbBool on) 2654{ 2655 if (SO@GUI@_DEBUG) { 2656 if (PRIVATE(this)->seektopoint == on) { 2657 SoDebugError::postWarning("So@Gui@Viewer::setDetailSeek", 2658 "unnecessary called"); 2659 return; 2660 } 2661 } 2662 2663 PRIVATE(this)->seektopoint = on; 2664} 2665 2666// ************************************************************************* 2667 2668/*! 2669 Returns a value indicating whether or not seeks will be performed 2670 to the exact point of picking or just towards the picked object. 2671 2672 \sa setDetailSeek() 2673*/ 2674 2675SbBool 2676So@Gui@Viewer::isDetailSeek(void) const 2677{ 2678 return PRIVATE(this)->seektopoint; 2679} 2680 2681// ************************************************************************* 2682 2683/*! 2684 Set the duration of animating the camera repositioning 2685 after a successful seek. Call with \a seconds equal to \a 0.0 to make 2686 the camera jump immediately to the correct spot. 2687 2688 Default value is 2 seconds. 2689 2690 \sa getSeekTime() 2691*/ 2692 2693void 2694So@Gui@Viewer::setSeekTime(const float seconds) 2695{ 2696 if (seconds < 0.0f) { 2697 if (SO@GUI@_DEBUG) { 2698 SoDebugError::postWarning("So@Gui@Viewer::setSeekTime", 2699 "an attempt was made to set a negative seek " 2700 "time duration"); 2701 } 2702 return; 2703 } 2704 PRIVATE(this)->seekperiod = seconds; 2705} 2706 2707// ************************************************************************* 2708 2709/*! 2710 Returns the camera repositioning duration following a seek action. 2711 2712 \sa setSeekTime() 2713*/ 2714 2715float 2716So@Gui@Viewer::getSeekTime(void) const 2717{ 2718 return PRIVATE(this)->seekperiod; 2719} 2720 2721// ************************************************************************* 2722 2723/*! 2724 Add a function to call when user interaction with the scene starts. 2725 2726 \sa removeStartCallback(), addFinishCallback() 2727*/ 2728 2729void 2730So@Gui@Viewer::addStartCallback(So@Gui@ViewerCB * func, void * data) 2731{ 2732 PRIVATE(this)->interactionstartCallbacks->addCallback((SoCallbackListCB *)func, data); 2733} 2734 2735/*! 2736 Remove one of the functions which has been set up to be called when user 2737 interaction with the scene starts. 2738 2739 \sa addStartCallback(), removeFinishCallback() 2740*/ 2741 2742void 2743So@Gui@Viewer::removeStartCallback(So@Gui@ViewerCB * func, void * data) 2744{ 2745 PRIVATE(this)->interactionstartCallbacks->removeCallback((SoCallbackListCB *)func, 2746 data); 2747} 2748 2749// ************************************************************************* 2750 2751/*! 2752 Add a function to call when user interaction with the scene ends. 2753 2754 \sa removeFinishCallback(), addStartCallback() 2755*/ 2756 2757void 2758So@Gui@Viewer::addFinishCallback(So@Gui@ViewerCB * func, void * data) 2759{ 2760 PRIVATE(this)->interactionendCallbacks->addCallback((SoCallbackListCB *)func, data); 2761} 2762 2763/*! 2764 Remove one of the functions which has been set up to be called when user 2765 interaction with the scene ends. 2766 2767 \sa addFinishCallback(), removeStartCallback() 2768*/ 2769 2770void 2771So@Gui@Viewer::removeFinishCallback(So@Gui@ViewerCB * func, void * data) 2772{ 2773 PRIVATE(this)->interactionendCallbacks->removeCallback((SoCallbackListCB *)func, 2774 data); 2775} 2776 2777// ************************************************************************* 2778 2779/*! 2780 Set the color of the overlay wireframe to \a color. 2781 2782 \sa getWireframeOverlayColor() 2783*/ 2784void So@Gui@Viewer::setWireframeOverlayColor(const SbColor & color) 2785{ 2786 PRIVATE(this)->wireframeoverlaycolor = color; 2787 this->scheduleRedraw(); 2788} 2789 2790// ************************************************************************* 2791 2792/*! 2793 Returns the current color of the overlay wireframe. The default 2794 color is [1,0,0], ie pure red. 2795 2796 \sa setWireframeOverlayColor() 2797*/ 2798const SbColor &So@Gui@Viewer::getWireframeOverlayColor(void) const 2799{ 2800 return PRIVATE(this)->wireframeoverlaycolor; 2801} 2802 2803// ************************************************************************* 2804 2805/*! 2806 Overloaded to update the local bufferingtype variable. 2807 2808 \sa setBufferingType(), getBufferingType() 2809*/ 2810 2811void 2812So@Gui@Viewer::setDoubleBuffer(const SbBool on) 2813{ 2814 if (!PRIVATE(this)->localsetbuffertype) 2815 PRIVATE(this)->buffertype = on ? BUFFER_DOUBLE : BUFFER_SINGLE; 2816 2817 inherited::setDoubleBuffer(on); 2818} 2819 2820// ************************************************************************* 2821 2822/*! 2823 Give the viewer a scenegraph to render and interact with. Overridden 2824 from parent class so the viewer can add it's own nodes to control 2825 rendering in different styles, rendering with a headlight, etc. 2826 2827 The \a root node will be inserted under the \e viewer's root node, 2828 which also covers the nodes necessary to implement the different 2829 preferences drawing style settings. 2830 2831 If no camera is part of the scene graph under \a root, one will 2832 automatically be instantiated and added. You can get a reference to 2833 this camera by using the So@Gui@Viewer::getCamera() method. 2834 2835 \sa getSceneGraph(), setCameraType() 2836*/ 2837 2838void 2839So@Gui@Viewer::setSceneGraph(SoNode * root) 2840{ 2841 if ((root != NULL) && (root == PRIVATE(this)->scenegraph)) { 2842 if (SO@GUI@_DEBUG) { 2843 SoDebugError::postWarning("So@Gui@Viewer::setSceneGraph", 2844 "called with the same root as already set"); 2845 } 2846 return; 2847 } 2848 2849 // If the So@Gui@RenderArea hasn't yet set up its pointer to the 2850 // So@Gui@Viewer "viewer root" (i.e. the viewer-generated root above 2851 // the user-supplied root), do that first. 2852 if (!inherited::getSceneGraph()) 2853 inherited::setSceneGraph(PRIVATE(this)->sceneroot); 2854 2855 if (PRIVATE(this)->scenegraph) { 2856 if (this->getCamera()) 2857 this->setCamera(NULL); 2858 // Release the old user-supplied graph. 2859 PRIVATE(this)->usersceneroot->removeChild(PRIVATE(this)->scenegraph); 2860 // old: PRIVATE(this)->sceneroot->removeChild(PRIVATE(this)->scenegraph); 2861 } 2862 2863 PRIVATE(this)->scenegraph = root; 2864 if (!root) return; 2865 2866 PRIVATE(this)->usersceneroot->addChild(PRIVATE(this)->scenegraph); 2867 2868 // Search for a camera in the user-supplied scenegraph. 2869 2870 SbBool oldsearch = SoBaseKit::isSearchingChildren(); 2871 SoBaseKit::setSearchingChildren(TRUE); 2872 2873 PRIVATE(this)->searchaction->reset(); 2874 PRIVATE(this)->searchaction->setType(SoCamera::getClassTypeId()); 2875 PRIVATE(this)->searchaction->apply(PRIVATE(this)->scenegraph); 2876 2877 SoBaseKit::setSearchingChildren(oldsearch); 2878 2879 SoCamera * scenecamera = NULL; 2880 if ( PRIVATE(this)->searchaction->getPath() != NULL ) { 2881 SoFullPath * fullpath = 2882 (SoFullPath *) PRIVATE(this)->searchaction->getPath(); 2883 scenecamera = (SoCamera *)fullpath->getTail(); 2884 } 2885 2886#if 0 // debug 2887 SoDebugError::postInfo("So@Gui@Viewer::setSceneGraph", 2888 "camera %sfound in graph", 2889 scenecamera ? "" : "not "); 2890#endif // debug 2891 2892 // Make our own camera if none was available. 2893 if (!scenecamera) { 2894 if (SoGuiViewpointWrapper::hasViewpoints(root)) { 2895 scenecamera = new SoGuiViewpointWrapper; 2896 PRIVATE(this)->cameratype = SoGuiViewpointWrapper::getClassTypeId(); 2897 ((SoGuiViewpointWrapper*)scenecamera)->setSceneGraph(root); 2898 } 2899 else { 2900 scenecamera = (SoCamera *) PRIVATE(this)->cameratype.createInstance(); 2901 } 2902 2903 // If type==BROWSER, camera should be inserted in the private 2904 // viewer "supergraph", if it's equal to EDITOR it should be 2905 // inserted in the user-supplied scenegraph. 2906 if (PRIVATE(this)->type == So@Gui@Viewer::BROWSER) { 2907 PRIVATE(this)->sceneroot->insertChild(scenecamera, 1); 2908 } 2909 else { // PRIVATE(this)->type == So@Gui@Viewer::EDITOR 2910 if (PRIVATE(this)->scenegraph->isOfType(SoGroup::getClassTypeId())) { 2911 // At the uppermost leftmost position in the user-supplied 2912 // scenegraph. 2913 ((SoGroup *)PRIVATE(this)->scenegraph)->insertChild(scenecamera, 0); 2914 } 2915 else { 2916 // Make an extra depth level to fit the camera node into the 2917 // user-scenegraph. 2918 SoGroup * g = new SoGroup; 2919 g->addChild(scenecamera); 2920 g->addChild(PRIVATE(this)->scenegraph); 2921 PRIVATE(this)->usersceneroot->removeChild(PRIVATE(this)->scenegraph); 2922 PRIVATE(this)->usersceneroot->addChild(g); 2923 PRIVATE(this)->scenegraph = g; 2924 } 2925 } 2926 if (PRIVATE(this)->cameratype != SoGuiViewpointWrapper::getClassTypeId()) { 2927 scenecamera->viewAll(PRIVATE(this)->scenegraph, this->getViewportRegion()); 2928 } 2929 } 2930 2931 this->setCamera(scenecamera); 2932} 2933 2934// ************************************************************************* 2935 2936// doc in super 2937SoNode * 2938So@Gui@Viewer::getSceneGraph(void) 2939{ 2940 // Overloaded from parent class to return the root of the scene 2941 // graph set by the user, without the extras added by the viewer to 2942 // control rendering. 2943 return PRIVATE(this)->scenegraph; 2944} 2945 2946// ************************************************************************* 2947 2948// Note: the following function documentation block will also be used 2949// for all the miscellaneous viewer subclasses, so keep it general. 2950/*! 2951 Put the viewer in or out of "waiting-to-seek" mode. 2952 2953 If the user performs a mouse button click when the viewer is in 2954 "waiting-to-seek" mode, the camera will be repositioned so the 2955 camera focal point lies on the point of the geometry under the mouse 2956 cursor. 2957 2958 \sa isSeekMode(), setDetailSeek() 2959*/ 2960void 2961So@Gui@Viewer::setSeekMode(SbBool enable) 2962{ 2963 if (SO@GUI@_DEBUG) { 2964 // User might have switched mode during seek, so if enable==FALSE, 2965 // isViewing() is irrelevant. 2966 if (enable) { assert(this->isViewing()); } 2967 } 2968 2969 if (!enable && PRIVATE(this)->seeksensor->isScheduled()) { 2970 PRIVATE(this)->seeksensor->unschedule(); 2971 this->interactiveCountDec(); 2972 } 2973 2974 PRIVATE(this)->inseekmode = enable; 2975} 2976 2977// ************************************************************************* 2978 2979/*! 2980 Return a flag which indicates whether or not the viewer is in 2981 "waiting-to-seek" mode. 2982 2983 (The actual animated translation will not occur until the end user 2984 really \e starts the seek operation, typically by clicking with the 2985 left mousebutton.) 2986 2987 \sa setSeekMode() 2988*/ 2989SbBool 2990So@Gui@Viewer::isSeekMode(void) const 2991{ 2992 return PRIVATE(this)->inseekmode; 2993} 2994 2995// ************************************************************************* 2996 2997/*! 2998 Call this method to initiate a seek action towards the 3D 2999 intersection of the scene and the ray from the screen coordinate's 3000 point and in the same direction as the camera is pointing. 3001 3002 Returns \c TRUE if the ray from the \a screenpos position intersect 3003 with any parts of the onscreen geometry, otherwise \c FALSE. 3004*/ 3005SbBool 3006So@Gui@Viewer::seekToPoint(const SbVec2s screenpos) 3007{ 3008 if (! PRIVATE(this)->camera) 3009 return FALSE; 3010 3011 SoRayPickAction rpaction(this->getViewportRegion()); 3012 rpaction.setPoint(screenpos); 3013 rpaction.setRadius(2); 3014 rpaction.apply(PRIVATE(this)->sceneroot); 3015 3016 SoPickedPoint * picked = rpaction.getPickedPoint(); 3017 if (!picked) { 3018 // FIXME: this inc seems bogus, but is needed now due to buggy 3019 // code in for instance the examinerviewer 3020 // processSoEvent(). 20020510 mortene. 3021#if 1 3022 this->interactiveCountInc(); // decremented in setSeekMode(FALSE) 3023#endif // FIXME 3024 this->setSeekMode(FALSE); 3025 return FALSE; 3026 } 3027 3028 SbVec3f hitpoint; 3029 if (PRIVATE(this)->seektopoint) { 3030 hitpoint = picked->getPoint(); 3031 } 3032 else { 3033 SoGetBoundingBoxAction bbaction(this->getViewportRegion()); 3034 bbaction.apply(picked->getPath()); 3035 SbBox3f bbox = bbaction.getBoundingBox(); 3036 hitpoint = bbox.getCenter(); 3037 } 3038 3039 this->seekToPoint(hitpoint); 3040 return TRUE; 3041} 3042 3043/*! 3044 Call this method to initiate a seek action towards the give 3D world 3045 coordinate point in the scene, \a scenepos. 3046 3047 \since So@Gui@ 1.3.0 3048*/ 3049void 3050So@Gui@Viewer::seekToPoint(const SbVec3f & scenepos) 3051{ 3052 SbVec3f hitpoint(scenepos); 3053 3054 PRIVATE(this)->camerastartposition = PRIVATE(this)->camera->position.getValue(); 3055 PRIVATE(this)->camerastartorient = PRIVATE(this)->camera->orientation.getValue(); 3056 3057 // move point to the camera coordinate system, consider 3058 // transformations before camera in the scene graph 3059 SbMatrix cameramatrix, camerainverse; 3060 PRIVATE(this)->getCameraCoordinateSystem(PRIVATE(this)->camera, 3061 PRIVATE(this)->sceneroot, 3062 cameramatrix, 3063 camerainverse); 3064 camerainverse.multVecMatrix(hitpoint, hitpoint); 3065 3066 float fd = PRIVATE(this)->seekdistance; 3067 if (!PRIVATE(this)->seekdistanceabs) 3068 fd *= (hitpoint - PRIVATE(this)->camera->position.getValue()).length()/100.0f; 3069 PRIVATE(this)->camera->focalDistance = fd; 3070 3071 SbVec3f dir = hitpoint - PRIVATE(this)->camerastartposition; 3072 dir.normalize(); 3073 3074 // find a rotation that rotates current camera direction into new 3075 // camera direction. 3076 SbVec3f olddir; 3077 PRIVATE(this)->camera->orientation.getValue().multVec(SbVec3f(0, 0, -1), olddir); 3078 SbRotation diffrot(olddir, dir); 3079 PRIVATE(this)->cameraendposition = hitpoint - fd * dir; 3080 PRIVATE(this)->cameraendorient = PRIVATE(this)->camera->orientation.getValue() * diffrot; 3081 3082 // Subclasses that want another cameraendorient than what is 3083 // computed here should override this function and set the desired 3084 // orientation there 3085 this->computeSeekFinalOrientation(); 3086 3087 if (PRIVATE(this)->seeksensor->isScheduled()) { 3088 PRIVATE(this)->seeksensor->unschedule(); 3089 this->interactiveCountDec(); 3090 } 3091 3092 PRIVATE(this)->seeksensor->setBaseTime(SbTime::getTimeOfDay()); 3093 PRIVATE(this)->seeksensor->schedule(); 3094 this->interactiveCountInc(); 3095} 3096 3097// ************************************************************************* 3098 3099void 3100So@Gui@ViewerP::setStereoEye(SoCamera * thecamera, 3101 const So@Gui@ViewerP::Eye eye, 3102 So@Gui@ViewerP::StereoData & s) const 3103{ 3104#ifdef HAVE_SOCAMERA_SETSTEREOMODE 3105 3106 // SoCamera::setStereoMode() is a fairly recent addition to Coin and 3107 // TGS Inventor. If available, camera eye setup is quite 3108 // straightforward. 3109 3110 if (eye == So@Gui@ViewerP::LEFT) { 3111 thecamera->setStereoAdjustment(PUBLIC(this)->getStereoOffset()); 3112 thecamera->setStereoMode(SoCamera::LEFT_VIEW); 3113 } 3114 else if (eye == So@Gui@ViewerP::RIGHT) { 3115 thecamera->setStereoMode(SoCamera::RIGHT_VIEW); 3116 } 3117 else { 3118 assert(eye == So@Gui@ViewerP::RESTORE); 3119 3120 thecamera->setStereoMode(SoCamera::MONOSCOPIC); 3121 } 3122 3123#else // ! HAVE_SOCAMERA_SETSTEREOMODE 3124 3125 // To support older versions of Coin, and SGI/TGS Inventor, we also 3126 // provide "manual" tuning of the camera left/right eye split. 3127 3128 if (eye == So@Gui@ViewerP::LEFT) { 3129 s.camerapos = thecamera->position.getValue(); 3130 s.cameradir.setValue(0.0f, 0.0f, -1.0f); 3131 s.offsetvec.setValue(1.0f, 0.0f, 0.0f); 3132 s.offset = PUBLIC(this)->getStereoOffset() * 0.5f; 3133 s.camerarot = thecamera->orientation.getValue(); 3134 s.camerarot.multVec(s.cameradir, s.cameradir); 3135 s.camerarot.multVec(s.offsetvec, s.offsetvec); 3136 s.focalpoint = s.camerapos + s.cameradir * thecamera->focalDistance.getValue(); 3137 3138 s.nodenotify = thecamera->isNotifyEnabled(); 3139 s.positionnotify = thecamera->position.isNotifyEnabled(); 3140 s.orientationnotify = thecamera->orientation.isNotifyEnabled(); 3141 // turn off notification to avoid redraws 3142 thecamera->enableNotify(FALSE); 3143 thecamera->position.enableNotify(FALSE); 3144 thecamera->orientation.enableNotify(FALSE); 3145 3146 thecamera->position = s.camerapos - s.offsetvec * s.offset; 3147 SbVec3f dir = s.focalpoint - thecamera->position.getValue(); 3148 SbRotation rot(s.cameradir, dir); 3149 thecamera->orientation = s.camerarot * rot; 3150 } 3151 else if (eye == So@Gui@ViewerP::RIGHT) { 3152 thecamera->position = s.camerapos + s.offsetvec * s.offset; 3153 SbVec3f dir = s.focalpoint - thecamera->position.getValue(); 3154 SbRotation rot(s.cameradir, dir); 3155 thecamera->orientation = s.camerarot * rot; 3156 } 3157 else { 3158 assert(eye == So@Gui@ViewerP::RESTORE); 3159 3160 thecamera->position = s.camerapos; 3161 thecamera->orientation = s.camerarot; 3162 thecamera->position.enableNotify(s.positionnotify); 3163 thecamera->orientation.enableNotify(s.orientationnotify); 3164 thecamera->enableNotify(s.nodenotify); 3165 } 3166 3167#endif // ! HAVE_SOCAMERA_SETSTEREOMODE 3168} 3169 3170void 3171So@Gui@ViewerP::initStencilBufferForInterleavedStereo(void) 3172{ 3173 const SbViewportRegion & currentvp = PUBLIC(this)->getViewportRegion(); 3174 if (this->stereostencilmaskvp == currentvp) { return; } // the common case 3175 3176 So@Gui@Viewer::StereoType s = PUBLIC(this)->getStereoType(); 3177 assert((s == So@Gui@Viewer::STEREO_INTERLEAVED_ROWS) || 3178 (s == So@Gui@Viewer::STEREO_INTERLEAVED_COLUMNS)); 3179 3180 // Find out whether or not we need to regenerate the mask data. 3181 SbBool allocnewmask = (this->stereostencilmask == NULL); 3182 3183 const SbVec2s neworigin = currentvp.getViewportOriginPixels(); 3184 const SbVec2s newsize = currentvp.getViewportSizePixels(); 3185 3186 const SbVec2s oldorigin = this->stereostencilmaskvp.getViewportOriginPixels(); 3187 const SbVec2s oldsize = this->stereostencilmaskvp.getViewportSizePixels(); 3188 3189 allocnewmask = allocnewmask || 3190 ((oldsize[0] + 7) / 8 * oldsize[1]) < ((newsize[0] + 7) / 8 * newsize[1]); 3191 3192 const SbBool fillmask = allocnewmask || (this->stereostenciltype != s) || 3193 ((s == So@Gui@Viewer::STEREO_INTERLEAVED_ROWS) && (oldsize[0] != newsize[0])); 3194 3195 const SbBool layoutchange = !(this->stereostencilmaskvp == currentvp); 3196 3197 const short bytewidth = (newsize[0] + 7) / 8; 3198 3199 if (allocnewmask) { 3200 delete[] this->stereostencilmask; 3201 this->stereostencilmask = new GLubyte[bytewidth * newsize[1]]; 3202 } 3203 3204 this->stereostencilmaskvp = currentvp; 3205 3206 if (fillmask) { 3207 GLubyte * mask = this->stereostencilmask; 3208 3209 if (s == So@Gui@Viewer::STEREO_INTERLEAVED_COLUMNS) { 3210 // alternating columns of 0's and 1's 3211 (void)memset(mask, 0x55, bytewidth * newsize[1]); 3212 } 3213 else { 3214 // alternating rows of 0's and 1's 3215 for (short h=0; h < newsize[1]; h++) { 3216 const GLubyte fill = (h % 2) ? 0xff : 0x00; 3217 (void)memset(mask + (h * bytewidth), fill, bytewidth); 3218 } 3219 } 3220 3221 this->stereostenciltype = s; 3222 } 3223 3224 if (layoutchange) { 3225 glClearStencil(0x0); 3226 3227 glClear(GL_STENCIL_BUFFER_BIT); 3228 glStencilFunc(GL_ALWAYS, GL_REPLACE, GL_REPLACE); 3229 3230 glMatrixMode(GL_MODELVIEW); 3231 glPushMatrix(); 3232 glLoadIdentity(); 3233 glMatrixMode(GL_PROJECTION); 3234 glPushMatrix(); 3235 glLoadIdentity(); 3236 3237 glViewport(neworigin[0], neworigin[1], newsize[0], newsize[1]); 3238 3239 glOrtho(0, newsize[0], 0, newsize[1], -1.0f, 1.0f); 3240 3241 glPixelStorei(GL_UNPACK_ALIGNMENT, 1); 3242 3243 // FIXME: I've noticed a problem with this approach. If there is 3244 // something in the window system obscuring part of the canvas 3245 // while this is called (as could e.g. happen with a size 3246 // indicator, as with the Sawfish window manager), the stencil 3247 // mask will not be set up for that part. 20041019 mortene. 3248 // 3249 // UPDATE 20041019 mortene: discussed this with pederb, and we 3250 // believe this may be due to a bug in either the OpenGL driver 3251 // (Nvidia 61.11, Linux) or window system or manager (Sawfish, 3252 // XFree86 v4.1.0.1). Should test on other systems to see if they 3253 // show the same artifact. 3254 3255 glRasterPos2f(0, 0); 3256 glDrawPixels(newsize[0], newsize[1], GL_STENCIL_INDEX, GL_BITMAP, 3257 this->stereostencilmask); 3258 3259 glMatrixMode(GL_PROJECTION); 3260 glPopMatrix(); 3261 glMatrixMode(GL_MODELVIEW); 3262 glPopMatrix(); 3263 } 3264} 3265 3266// Documented in superclass. Overridden from parent class to be able 3267// to do the necessary two-pass rendering e.g. if the drawing style is 3268// hidden line. 3269void 3270So@Gui@Viewer::actualRedraw(void) 3271{ 3272 SbTime redrawtime = SbTime::getTimeOfDay(); 3273 const SbBool clearcol = this->isClearBeforeRender(); 3274 const SbBool clearz = this->isClearZBufferBeforeRender(); 3275 const So@Gui@Viewer::StereoType stereotype = this->getStereoType(); 3276 3277 if (stereotype != So@Gui@Viewer::STEREO_NONE) { 3278#if HAVE_SBCOLOR4F_GETBACKGROUNDCOLOR 3279 const SbColor4f bgcol = this->getSceneManager()->getBackgroundColor(); 3280#else 3281 const SbColor4f bgcol(this->getSceneManager()->getBackgroundColor(), 0.0f); 3282#endif 3283 SoCamera * camera = this->getCamera(); 3284 So@Gui@ViewerP::StereoData tmpstorage; 3285 3286 // Render left eye: 3287 3288 PRIVATE(this)->setStereoEye(camera, So@Gui@ViewerP::LEFT, tmpstorage); 3289 3290 switch (stereotype) { 3291 case So@Gui@Viewer::STEREO_ANAGLYPH: 3292 glDrawBuffer(this->isDoubleBuffer() ? GL_BACK : GL_FRONT); 3293 glClearColor(bgcol[0], bgcol[1], bgcol[2], 0.0f); 3294 glClear(GL_DEPTH_BUFFER_BIT|GL_COLOR_BUFFER_BIT); 3295 glColorMask(PRIVATE(this)->stereoanaglyphmask[0][0] ? GL_TRUE : GL_FALSE, 3296 PRIVATE(this)->stereoanaglyphmask[0][1] ? GL_TRUE : GL_FALSE, 3297 PRIVATE(this)->stereoanaglyphmask[0][2] ? GL_TRUE : GL_FALSE, 3298 GL_TRUE); 3299 PRIVATE(this)->reallyRedraw(FALSE, FALSE); 3300 break; 3301 case So@Gui@Viewer::STEREO_QUADBUFFER: 3302 glDrawBuffer(this->isDoubleBuffer() ? GL_BACK_LEFT : GL_FRONT_LEFT); 3303 PRIVATE(this)->reallyRedraw(clearcol, clearz); 3304 break; 3305 case So@Gui@Viewer::STEREO_INTERLEAVED_ROWS: 3306 case So@Gui@Viewer::STEREO_INTERLEAVED_COLUMNS: 3307 PRIVATE(this)->initStencilBufferForInterleavedStereo(); 3308 glEnable(GL_STENCIL_TEST); 3309 // immutable data in the stencil buffer 3310 glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP); 3311 glStencilFunc(GL_EQUAL, 0x1, 0x1); 3312 PRIVATE(this)->reallyRedraw(clearcol, clearz); 3313 break; 3314 3315 default: assert(FALSE); break; 3316 } 3317 3318 // Render right eye: 3319 3320 PRIVATE(this)->setStereoEye(camera, So@Gui@ViewerP::RIGHT, tmpstorage); 3321 3322 switch (stereotype) { 3323 case So@Gui@Viewer::STEREO_ANAGLYPH: 3324 glColorMask(PRIVATE(this)->stereoanaglyphmask[1][0] ? GL_TRUE : GL_FALSE, 3325 PRIVATE(this)->stereoanaglyphmask[1][1] ? GL_TRUE : GL_FALSE, 3326 PRIVATE(this)->stereoanaglyphmask[1][2] ? GL_TRUE : GL_FALSE, 3327 GL_TRUE); 3328 PRIVATE(this)->reallyRedraw(FALSE, TRUE); 3329 break; 3330 case So@Gui@Viewer::STEREO_QUADBUFFER: 3331 glDrawBuffer(this->isDoubleBuffer() ? GL_BACK_RIGHT : GL_FRONT_RIGHT); 3332 PRIVATE(this)->reallyRedraw(clearcol, clearz); 3333 break; 3334 case So@Gui@Viewer::STEREO_INTERLEAVED_ROWS: 3335 case So@Gui@Viewer::STEREO_INTERLEAVED_COLUMNS: 3336 glStencilFunc(GL_NOTEQUAL, 0x1, 0x1); 3337 PRIVATE(this)->reallyRedraw(FALSE, FALSE); 3338 break; 3339 default: assert(FALSE); break; 3340 } 3341 3342 // Clean-up, post-rendering: 3343 3344 PRIVATE(this)->setStereoEye(camera, So@Gui@ViewerP::RESTORE, tmpstorage); 3345 3346 switch (stereotype) { 3347 case So@Gui@Viewer::STEREO_ANAGLYPH: 3348 glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); // restore GL color mask 3349 break; 3350 case So@Gui@Viewer::STEREO_QUADBUFFER: 3351 glDrawBuffer(this->isDoubleBuffer() ? GL_BACK : GL_FRONT); 3352 break; 3353 case So@Gui@Viewer::STEREO_INTERLEAVED_ROWS: 3354 case So@Gui@Viewer::STEREO_INTERLEAVED_COLUMNS: 3355 // FIXME: restore old val. 20040618 mortene. 3356 glDisable(GL_STENCIL_TEST); 3357 break; 3358 default: assert(FALSE); break; 3359 } 3360 } 3361 else { // No stereo: 3362 PRIVATE(this)->reallyRedraw(clearcol, clearz); 3363 } 3364 3365 if (PRIVATE(this)->superimpositions != NULL) { 3366 SoGLRenderAction * raaction = this->getSceneManager()->getGLRenderAction(); 3367 SbBool first = TRUE; 3368 SbBool zWasEnabled = FALSE; 3369 for (int i = 0; i < PRIVATE(this)->superimpositions->getLength(); i++) { 3370 if (PRIVATE(this)->superimpositionsenabled[i] != FALSE) { 3371 if (first) { 3372 // save Z buffer state and disable 3373 zWasEnabled = glIsEnabled(GL_DEPTH_TEST) ? TRUE : FALSE; 3374 glDisable(GL_DEPTH_TEST); 3375 first = FALSE; 3376 } 3377 SoNode * scene = (SoNode *) (*PRIVATE(this)->superimpositions)[i]; 3378 raaction->apply(scene); 3379 } 3380 } 3381 if (!first && zWasEnabled) glEnable(GL_DEPTH_TEST); 3382 } 3383 3384 redrawtime = SbTime::getTimeOfDay() - redrawtime; 3385 PRIVATE(this)->recordFPS(redrawtime.getValue()); 3386} 3387 3388// ************************************************************************* 3389 3390/*! 3391 To be able to trigger callback functions when user interaction starts 3392 and/or stops, we need to keep track of the viewer state (i.e. are we in 3393 still mode or in animation mode?). 3394 3395 So@Gui@Viewer automatically adds callbacks to switch between still and 3396 moving draw style, and to switch between single/double buffer when 3397 the buffer type is \a INTERACTIVE. 3398 3399 \sa interactiveCountDec(), getInteractiveCount() 3400 \sa addStartCallback(), addFinishCallback() 3401 \sa removeStartCallback(), removeFinishCallback() 3402 \sa setDrawStyle(), setBufferingType() 3403*/ 3404 3405void 3406So@Gui@Viewer::interactiveCountInc(void) 3407{ 3408 // Catch problems with missing interactiveCountDec() calls. 3409 assert(PRIVATE(this)->interactionnesting < 100); 3410 3411 if (++(PRIVATE(this)->interactionnesting) == 1) { 3412 PRIVATE(this)->interactionstartCallbacks->invokeCallbacks(this); 3413 PRIVATE(this)->resetFrameCounter(); 3414 } 3415 3416#if 0 // debug 3417 SoDebugError::postInfo("So@Gui@Viewer::interactiveCountInc", "%d -> %d", 3418 PRIVATE(this)->interactionnesting - 1, 3419 PRIVATE(this)->interactionnesting); 3420#endif // debug 3421} 3422 3423// ************************************************************************* 3424 3425/*! 3426 To be able to trigger callback functions when user interaction starts 3427 and/or stops, we need to keep track of the viewer state (i.e. are we in 3428 still mode or in animation mode?). 3429 3430 So@Gui@Viewer automatically adds callbacks to switch between still and 3431 moving draw style, and to switch between single/double buffer when 3432 the buffer type is \a INTERACTIVE. 3433 3434 \sa interactiveCountInc(), getInteractiveCount() 3435 \sa addStartCallback(), addFinishCallback() 3436 \sa removeStartCallback(), removeFinishCallback() 3437 \sa setDrawStyle(), setBufferingType() 3438*/ 3439 3440void 3441So@Gui@Viewer::interactiveCountDec(void) 3442{ 3443 // FIXME: The UI toolkits may cause the interactionnesting to go 3444 // below zero by triggering press and release events in different 3445 // widgets. mariusbu 20010709. 3446 3447 // FIXME: just to clarify; this is due to programming mistakes on 3448 // our behalf and should be cleaned up. We're using a simple 3449 // work-around / ignore strategy for now, though, as getting this 3450 // 100% correct is hard (there are so many possible ways of user 3451 // interaction with a viewer canvas) and the end-user will usually 3452 // not notice any problems at all. So that's why we are using a 3453 // warning instead of an assert(). 20010815 mortene. 3454 3455 // FIXME: here's one known way to trigger the bug: hit "s" in the 3456 // examinerviewer in EXAMINE mode, then while seeking hit ESC to put 3457 // the viewer in INTERACT mode. When the seek is completed, the 3458 // count will become -1. 20010912 mortene. 3459 3460 // FIXME: and another one (tested with SoXt): click and hold LMB in 3461 // the canvas while in INTERACT mode, then hit 'Esc' to switch to 3462 // EXAMINE mode, then release LMB. 20020325 mortene. 3463 3464 if (SO@GUI@_DEBUG) { 3465 if (PRIVATE(this)->interactionnesting <= 0) { 3466 SoDebugError::postWarning("So@Gui@Viewer::interactiveCountDec", 3467 "interaction count nesting went below zero. " 3468 "This is due to an internal So@Gui@ bug."); 3469 } 3470 } 3471 3472 if (--(PRIVATE(this)->interactionnesting) <= 0) { 3473 PRIVATE(this)->interactionendCallbacks->invokeCallbacks(this); 3474 PRIVATE(this)->interactionnesting = 0; 3475 } 3476} 3477 3478// ************************************************************************* 3479 3480/*! 3481 Return current interaction count nesting. If equal to zero, the viewer 3482 is in animation mode, otherwise the camera is still. 3483 3484 \sa interactiveCountInc(), interactiveCountDec() 3485*/ 3486 3487int 3488So@Gui@Viewer::getInteractiveCount(void) const 3489{ 3490 return PRIVATE(this)->interactionnesting; 3491} 3492 3493// ************************************************************************* 3494 3495/*! 3496 Set the value used for calculating how close the camera and intersection 3497 hit point should be made at the end of a seek operation. 3498 3499 The value can be interpreted as an absolute value in the given world 3500 unit (which typically is meters) or as a percentage value of the 3501 distance between the camera starting position and the intersection 3502 hit point. This can be controlled through the 3503 setSeekValueAsPercentage() method. It is as default used as an 3504 absolute value. 3505 3506 Default value is 50 (absolute distance or percent). 3507 3508 \sa getSeekDistance(), setSeekValueAsPercentage(), setSeekTime() 3509*/ 3510 3511void 3512So@Gui@Viewer::setSeekDistance(const float distance) 3513{ 3514 if (distance <= 0.0f) { 3515 if (SO@GUI@_DEBUG) { 3516 SoDebugError::postWarning("So@Gui@Viewer::setSeekDistance", 3517 "invalid seek distance value: %f", 3518 distance); 3519 } 3520 return; 3521 } 3522 PRIVATE(this)->seekdistance = distance; 3523} 3524 3525// ************************************************************************* 3526 3527/*! 3528 Returns the current seek distance. Value given as an absolute scalar 3529 length or as a percentage value of the original distance between 3530 the hitpoint and the camera starting position. 3531 3532 \sa setSeekDistance(), isSeekValueAsPercentage() 3533*/ 3534 3535float 3536So@Gui@Viewer::getSeekDistance(void) const 3537{ 3538 return PRIVATE(this)->seekdistance; 3539} 3540 3541// ************************************************************************* 3542 3543/*! 3544 Control whether or not the seek distance value should be interpreted as 3545 a percentage value or as an absolute distance. See documentation on 3546 setSeekDistance() for more information. 3547 3548 \sa setSeekDistance(), isSeekValueAsPercentage() 3549*/ 3550 3551void 3552So@Gui@Viewer::setSeekValueAsPercentage(const SbBool on) 3553{ 3554 if (SO@GUI@_DEBUG) { 3555 if ((on && this->isSeekValuePercentage()) || 3556 (!on && !this->isSeekValuePercentage())) { 3557 SoDebugError::postWarning("So@Gui@Viewer::setSeekDistanceAsPercentage", 3558 "unnecessary called, value already %s", 3559 on ? "on" : "off"); 3560 return; 3561 } 3562 } 3563 3564 PRIVATE(this)->seekdistanceabs = on ? FALSE : TRUE; 3565} 3566 3567// ************************************************************************* 3568 3569/*! 3570 Returns an boolean which indicates if the seek distance value from 3571 getSeekDistance() should be interpreted as a percentage value or 3572 as an absolute value. 3573 3574 \sa setSeekValuePercentage(), getSeekDistance() 3575*/ 3576 3577SbBool 3578So@Gui@Viewer::isSeekValuePercentage(void) const 3579{ 3580 return PRIVATE(this)->seekdistanceabs ? FALSE : TRUE; 3581} 3582 3583// ************************************************************************ 3584 3585/*! 3586 This method can be overridden in subclasses if the final 3587 orientation of the camera after a seek should be something other 3588 than what is computed in So@Gui@Viewer::seekToPoint(const SbVec3f & 3589 scenepos) 3590 */ 3591void 3592So@Gui@Viewer::computeSeekFinalOrientation(void) 3593{ 3594 3595} 3596 3597// ************************************************************************* 3598 3599/*! 3600 If the current camera is of perspective type, switch to 3601 orthographic, and vice versa. 3602 3603 Automatically calls So@Gui@Viewer::setCameraType() so the change 3604 will immediately take place. 3605*/ 3606void 3607So@Gui@Viewer::toggleCameraType(void) 3608{ 3609 SoType perspectivetype = SoPerspectiveCamera::getClassTypeId(); 3610 SoType orthotype = SoOrthographicCamera::getClassTypeId(); 3611 this->setCameraType(PRIVATE(this)->cameratype.isDerivedFrom(perspectivetype) 3612 ? orthotype : perspectivetype); 3613} 3614 3615// ************************************************************************ 3616 3617/*! 3618 Copies the settings of \a camera into our current camera. Cameras 3619 must be of the same class type. 3620 */ 3621void 3622So@Gui@Viewer::changeCameraValues(// virtual, protected 3623 SoCamera * camera) 3624{ 3625 assert(camera != NULL); 3626 3627 SoCamera * cam = this->getCamera(); 3628 if (!cam) { 3629 if (SO@GUI@_DEBUG) { 3630 SoDebugError::postWarning("So@Gui@Viewer::changeCameraValues", 3631 "no current camera in the scenegraph"); 3632 } 3633 return; 3634 } 3635 if (cam->getTypeId() != camera->getTypeId()) { 3636 if (SO@GUI@_DEBUG) { 3637 SoDebugError::postWarning("So@Gui@Viewer::changeCameraValues", 3638 "tried to copy data from camera of " 3639 "different type"); 3640 } 3641 return; 3642 } 3643 3644 cam->copyFieldValues(camera, FALSE); 3645} 3646 3647// ************************************************************************* 3648 3649// doc in super 3650void 3651So@Gui@Viewer::sizeChanged(const SbVec2s & size) 3652{ 3653 inherited::sizeChanged(size); 3654} 3655 3656// ************************************************************************* 3657 3658// Documented in superclass. 3659SbBool 3660So@Gui@Viewer::processSoEvent(const SoEvent * const event) 3661{ 3662 const SoType type(event->getTypeId()); 3663 const SoKeyboardEvent * keyevent = NULL; 3664 3665 if (type.isDerivedFrom(SoKeyboardEvent::getClassTypeId())) { 3666 keyevent = (SoKeyboardEvent *) event; 3667 switch (keyevent->getKey()) { 3668 3669 // the ESC key switches between view and interact mode 3670 case SoKeyboardEvent::ESCAPE: 3671 if (keyevent->getState() == SoButtonEvent::DOWN) { 3672 this->setViewing(this->isViewing() ? FALSE : TRUE); 3673 return TRUE; 3674 } 3675 break; 3676 3677 // Let the end-user toggle between camera-interaction mode 3678 // ("viewing") and scenegraph-interaction mode with ALT key(s). 3679 case SoKeyboardEvent::LEFT_ALT: 3680 case SoKeyboardEvent::RIGHT_ALT: 3681 if (!this->isViewing() && (keyevent->getState() == SoButtonEvent::DOWN)) { 3682 PRIVATE(this)->altdown = TRUE; 3683 this->setViewing(TRUE); 3684 return TRUE; 3685 } 3686 else if (PRIVATE(this)->altdown && (keyevent->getState() == SoButtonEvent::UP)) { 3687 this->setViewing(FALSE); 3688 PRIVATE(this)->altdown = FALSE; 3689 return TRUE; 3690 } 3691 break; 3692 default: 3693 break; 3694 } 3695 } 3696 3697 // If not viewing, break off further handling and pass the event on 3698 // to the So@Gui@RenderArea, which will pass it on to the 3699 // scenegraph. 3700 if (!this->isViewing()) { return inherited::processSoEvent(event); } 3701 3702 3703 if (keyevent && (keyevent->getState() == SoButtonEvent::DOWN)) { 3704 switch (keyevent->getKey()) { 3705 case SoKeyboardEvent::S: 3706 this->setSeekMode(this->isSeekMode() ? FALSE : TRUE); 3707 return TRUE; 3708 case SoKeyboardEvent::HOME: 3709 this->resetToHomePosition(); 3710 return TRUE; 3711 case SoKeyboardEvent::LEFT_ARROW: 3712 PRIVATE(this)->moveCameraScreen(SbVec2f(-0.1f, 0.0f)); 3713 return TRUE; 3714 case SoKeyboardEvent::UP_ARROW: 3715 PRIVATE(this)->moveCameraScreen(SbVec2f(0.0f, 0.1f)); 3716 return TRUE; 3717 case SoKeyboardEvent::RIGHT_ARROW: 3718 PRIVATE(this)->moveCameraScreen(SbVec2f(0.1f, 0.0f)); 3719 return TRUE; 3720 case SoKeyboardEvent::DOWN_ARROW: 3721 PRIVATE(this)->moveCameraScreen(SbVec2f(0.0f, -0.1f)); 3722 return TRUE; 3723 default: 3724 break; 3725 } 3726 } 3727 3728 if (this->isSeekMode()) { 3729 if (type.isDerivedFrom(SoMouseButtonEvent::getClassTypeId())) { 3730 SoMouseButtonEvent * const e = (SoMouseButtonEvent *) event; 3731 if (e->getButton() == SoMouseButtonEvent::BUTTON1) { 3732 if (e->getState() == SoButtonEvent::DOWN) { 3733 this->seekToPoint(e->getPosition()); 3734 } 3735 else { 3736 // We got an LMB UP-event while in seek-mode, and we just 3737 // swallow the event. 3738 } 3739 return TRUE; 3740 } 3741 } 3742 } 3743 3744 return FALSE; 3745} 3746 3747// ************************************************************************* 3748 3749/*! 3750 This method is for setting up a superimposed scene graph on top 3751 of the viewer scene graph. It will be used for adding spin-rotation 3752 coordinate systems, fly-viewer speed indicators and similar things. 3753 3754 This method is not part of the original InventorXt API. 3755*/ 3756 3757void 3758So@Gui@Viewer::addSuperimposition(SoNode * scene) 3759{ 3760 if (PRIVATE(this)->superimpositions == NULL) 3761 PRIVATE(this)->superimpositions = new SbPList; 3762 assert(scene != NULL); 3763 scene->ref(); 3764 PRIVATE(this)->searchaction->reset(); 3765 PRIVATE(this)->searchaction->setType(SoCamera::getClassTypeId()); 3766 PRIVATE(this)->searchaction->setInterest(SoSearchAction::FIRST); 3767 PRIVATE(this)->searchaction->apply(scene); 3768 if (PRIVATE(this)->searchaction->getPath() == NULL) { 3769 // FIXME: set up default environment if there is no camera in the 3770 // superimposition scene - or not... 3771 if (SO@GUI@_DEBUG) { 3772 SoDebugError::postInfo("So@Gui@Viewer::addSuperimposition", 3773 "cameraless superimpositions are not " 3774 "supported"); 3775 } 3776 scene->unrefNoDelete(); 3777 return; 3778 } 3779 PRIVATE(this)->superimpositions->append(scene); 3780 PRIVATE(this)->superimpositionsenabled.append(TRUE); 3781} 3782 3783// ************************************************************************* 3784 3785/*! 3786 This method is not part of the original InventorXt API. 3787*/ 3788 3789void 3790So@Gui@Viewer::removeSuperimposition(SoNode * scene) 3791{ 3792 assert(scene); 3793 int idx = -1; 3794 if (PRIVATE(this)->superimpositions == NULL) goto error; 3795 idx = PRIVATE(this)->superimpositions->find(scene); 3796 if (idx == -1) goto error; 3797 assert(PRIVATE(this)->superimpositions != NULL); 3798 PRIVATE(this)->superimpositions->remove(idx); 3799 PRIVATE(this)->superimpositionsenabled.remove(idx); 3800 scene->unref(); 3801 return; 3802 3803 error: 3804 if (SO@GUI@_DEBUG) { 3805 SoDebugError::post("So@Gui@Viewer::removeSuperimposition", 3806 "no such superimposition"); 3807 } 3808 return; 3809} 3810 3811// ************************************************************************* 3812 3813/*! 3814 This method sets whether the superimposed scene graph should be traversed 3815 or not. 3816 3817 This method is not part of the original InventorXt API. 3818*/ 3819 3820void 3821So@Gui@Viewer::setSuperimpositionEnabled(SoNode * scene, 3822 const SbBool enable) 3823{ 3824 int idx = -1; 3825 if (PRIVATE(this)->superimpositions == NULL) goto error; 3826 idx = PRIVATE(this)->superimpositions->find(scene); 3827 if (idx == -1) goto error; 3828 PRIVATE(this)->superimpositionsenabled[idx] = enable; 3829 return; 3830 3831 error: 3832 if (SO@GUI@_DEBUG) { 3833 SoDebugError::post("So@Gui@Viewer::setSuperimpositionEnabled", 3834 "no such superimposition"); 3835 } 3836 return; 3837} 3838 3839// ************************************************************************* 3840 3841/*! 3842 This method returns whether the superimposed scene is rendered or not. 3843 3844 This method is not part of the original InventorXt API. 3845*/ 3846 3847SbBool 3848So@Gui@Viewer::getSuperimpositionEnabled(SoNode * scene) const 3849{ 3850 int idx = -1; 3851 if (PRIVATE(this)->superimpositions == NULL) goto error; 3852 idx = PRIVATE(this)->superimpositions->find(scene); 3853 if (idx == -1) goto error; 3854 return PRIVATE(this)->superimpositionsenabled[idx]; 3855 3856 error: 3857 if (SO@GUI@_DEBUG) { 3858 SoDebugError::post("So@Gui@Viewer::getSuperimpositionEnabled", 3859 "no such superimposition"); 3860 } 3861 return FALSE; 3862} 3863 3864// ************************************************************************* 3865 3866#undef PRIVATE 3867#undef PUBLIC 3868 3869