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