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