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