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/*!
36  \class So@Gui@FlyViewer Inventor/@Gui@/viewers/So@Gui@FlyViewer.h
37  \brief The So@Gui@FlyViewer class implements controls for moving
38  the camera in a "flying" motion.
39
40  \ingroup viewers
41
42  Controls:
43  <ul>
44
45  <li>Left mouse button increases the speed.</li>
46
47  <li>Middle mouse button decreases the speed.</li>
48
49  <li>Left and middle mouse button together sets the speed to zero.</li>
50
51  <li>"s" puts the viewer in seek mode. Click some geometry with the
52      left mouse button to start the seek zoom animation. (Hitting "s"
53      again before clicking will cancel the seek operation.)</li>
54
55  <li>"u" puts the viewer in up-vector pick mode. Click some geometry
56      with the left mouse button to set the camera's up-vector to the
57      normal vector of the face you pick.
58      (Hitting "u" again before clicking will cancel the pick operation.)</li>
59
60  <li>The control key stops the flying and lets you tilt the camera by moving
61      the pointer.</li>
62
63  </ul>
64*/
65
66/*
67  FIXME:
68  - animate camera when setting up-vector so the scene doesn't just
69    suddenly change.
70*/
71
72#include <so@gui@defs.h>
73#include <Inventor/@Gui@/viewers/So@Gui@FlyViewer.h>
74#include <Inventor/events/SoKeyboardEvent.h>
75#include <Inventor/events/SoLocation2Event.h>
76#include <Inventor/events/SoMouseButtonEvent.h>
77#include <Inventor/nodes/SoCamera.h>
78#include <Inventor/nodes/SoCallback.h>
79#include <Inventor/nodes/SoCoordinate3.h>
80#include <Inventor/nodes/SoSwitch.h>
81#include <Inventor/nodes/SoScale.h>
82#include <Inventor/nodes/SoTranslation.h>
83#include <Inventor/errors/SoDebugError.h>
84#include <Inventor/actions/SoSearchAction.h>
85#include <Inventor/actions/SoHandleEventAction.h>
86#include <Inventor/actions/SoGetBoundingBoxAction.h>
87#include <string.h> // strlen() etc
88#include <stdlib.h> // abs()
89#include <Inventor/@Gui@/So@Gui@Cursor.h>
90
91
92// ************************************************************************
93
94#ifndef DOXYGEN_SKIP_THIS
95
96// FIXME: We should probably move this class out of this
97// impl-file. There is code here that could be factored out and reused
98// in other modules, for example camera handling. Now there is a
99// duplication with some of the code in So@Gui@ConstrainedViewerP, for
100// example. 20021017 rolvs
101class So@Gui@FlyViewerP {
102public:
103  So@Gui@FlyViewerP(So@Gui@FlyViewer * owner);
104  ~So@Gui@FlyViewerP();
105
106  enum ViewerMode {
107    FLYING, TILTING, WAITING_FOR_SEEK, WAITING_FOR_UP_PICK
108  };
109
110  void constructor(SbBool build);
111
112  void dolly(const float delta) const;
113  void updateCursorRepresentation(void); // in SoNativeFlyViewer.cpp
114  void setMode(ViewerMode newmode);
115  int getMode(void) { return this->viewermode; }
116
117#define SO@GUI@_MIN_STEP     0.2f
118#define SO@GUI@_INC_FACTOR   1.2f
119#define SO@GUI@_MAX_SPEED   20.0f
120
121  SbTime * lastrender;
122
123  float currentspeed;
124
125  // Maximum speed, target for currentspeed during
126  // acceleration/decceleration.
127  float maxspeed;
128
129  // Scales speed. Calculated in updateSpeedScalingFactor.
130  float speed_scaling_factor;
131
132  // Used to calculate a new max_speed, on the basis on 'where' we are
133  // in the speed landscape, see {increment|decrement}MaxSpeed().
134  int max_speed_factor;
135
136  // Speed
137  void incrementMaxSpeed();
138  void decrementMaxSpeed();
139  void updateMaxSpeed();
140  void updateSpeedScalingFactor();
141  void stopMoving();
142
143  void updateSpeedIndicator(void);
144  void updateCameraPosition( SoCamera * camera, float speed, float dt );
145  void updateCameraOrientation( SoCamera * camera,
146                                float d_tilt,
147                                float d_pan,
148                                float dt );
149  double calculateChangeInTime();
150  void  updateCurrentSpeed(double dt);
151
152
153  // Current keyboard state.
154  SbBool button1down;
155  SbBool button3down;
156  int lctrldown;
157  int rctrldown;
158  SbBool lshiftdown;
159  SbBool rshiftdown;
160
161  // View, speed display, renderingstate.
162  SoSearchAction * searcher;
163
164  SoNode * superimposition;
165  SoCoordinate3 * sgeometry;
166
167  SoScale * sscale;
168  SoScale * crossscale;
169
170  SoTranslation * stranslation;
171  SoTranslation * crossposition;
172
173  SoSwitch * smaxspeedswitch;
174  SoSwitch * scurrentspeedswitch;
175  SoSwitch * crossswitch;
176
177  SoNode * getSuperimpositionNode(const char * name);
178
179  void superimpositionevent(SoAction * action);
180  static void superimposition_cb(void * closure, SoAction * action);
181
182
183  //
184  float tilt_increment; // Angle-adjustment between View-up and direction
185  float pan_increment;  // Rotation-adjustment around View-up
186
187  SbVec2s mouseloc;
188  SbVec2s lastpos;
189  SbVec2s tiltpos;
190
191  // FIXME: Refactor event handlers and PUBLIC(this)->processSoEvent
192  // in a way similar to that in SoGuiExaminerViewer, where only
193  // internal state is red/set. 20021017 rolvs
194  SbBool processKeyboardEvent(const SoKeyboardEvent * const kevt);
195  SbBool processMouseButtonEvent(const SoMouseButtonEvent * const mevt);
196  SbBool processLocation2Event(const SoLocation2Event * const levt);
197private:
198  So@Gui@FlyViewer * publ;
199  ViewerMode viewermode;
200};
201
202So@Gui@FlyViewerP::So@Gui@FlyViewerP(So@Gui@FlyViewer * owner)
203{
204  this->searcher = NULL;
205  this->publ = owner;
206  this->viewermode = FLYING;
207  this->currentspeed = 0.0f;
208  this->maxspeed = 0.0f;
209  this->speed_scaling_factor = 0.4f;
210  this->max_speed_factor = 0;
211  this->stranslation = NULL;
212  this->sscale = NULL;
213  this->button1down = FALSE;
214  this->button3down = FALSE;
215  this->lctrldown = 0;
216  this->rctrldown = 0;
217  this->lshiftdown = FALSE;
218  this->rshiftdown = FALSE;
219  this->lastrender = new SbTime;
220  this->tilt_increment = 0.0f;
221  this->pan_increment = 0.0f;
222}
223
224So@Gui@FlyViewerP::~So@Gui@FlyViewerP(void)
225{
226  if ( this->searcher != NULL )
227    delete this->searcher;
228  delete this->lastrender;
229
230  // superimposition unrefed in other destructor
231}
232
233#define PRIVATE(o) (o->pimpl)
234#define PUBLIC(o) (o->publ)
235
236
237// Common constructor code.
238void
239So@Gui@FlyViewerP::constructor(SbBool build)
240{
241  PUBLIC(this)->setClassName(PUBLIC(this)->getDefaultWidgetName());
242
243  static const char * superimposed[] = {
244    "#Inventor V2.1 ascii",
245    "",
246    "Separator {",
247    "  MaterialBinding {",
248    "    value OVERALL",
249    "  }",
250    "  OrthographicCamera {",
251    "    height 1",
252    "    nearDistance 0",
253    "    farDistance 1",
254    "  }",
255    "  DEF so@gui@->callback Callback { }",
256    "  Separator {",
257    "    DEF so@gui@->translation Translation {",
258    "      translation 0 0 0",
259    "    }",
260    "    DEF so@gui@->scale Scale {",
261    "      scaleFactor 1 1 1",
262    "    }",
263    "    DEF so@gui@->geometry Coordinate3 {",
264    "      point [",
265    "       -0.8 -0.04 0,",
266    "       -0.8  0    0,",
267    "       -0.8  0.04 0,",
268    "        0   -0.04 0,",
269    "        0    0    0,",
270    "        0    0.04 0,",
271    "        0.8 -0.04 0,",
272    "        0.8  0    0,",
273    "        0.8  0.04 0,",
274    "        0    0.02 0,", // idx 9
275    "        0.8  0.02 0,",
276    "        0.8 -0.02 0,",
277    "        0   -0.02 0,",
278    "        0    0.01 0,", // idx 13
279    "        0.4  0.01 0,",
280    "        0.4 -0.01 0,",
281    "        0   -0.01 0",
282    "      ]",
283    "    }",
284    "    DEF so@gui@->maxspeedswitch Switch {",
285    "      whichChild -3",
286    // max speed indicator
287    "      Material {",
288    "        emissiveColor 1 0 0",
289    "      }",
290    "      IndexedFaceSet {",
291    "        coordIndex [",
292    "          12, 11, 10, 9, -1",
293    "        ]",
294    "      }",
295    "    }",
296    // the coordinate system
297    "    BaseColor {",
298    "      rgb 1 1 1",
299    "    }",
300    "    IndexedLineSet {",
301    "      coordIndex [",
302    "        0, 2, -1,",
303    "        3, 5, -1,",
304    "        6, 8, -1,",
305    "        1, 7, -1",
306    "      ]",
307    "    }",
308    // current speed indicator
309    "    DEF so@gui@->currentspeedswitch Switch {",
310    "      whichChild -3",
311    "      Material {",
312    "        emissiveColor 0 0 1",
313    "      }",
314    "      IndexedFaceSet {",
315    "        coordIndex [",
316    "          16, 15, 14, 13, -1",
317    "        ]",
318    "      }",
319    "    }",
320    "  }",
321    // cross
322    "  DEF so@gui@->crossswitch Switch {",
323    "    whichChild -1",
324    "    DEF so@gui@->crossposition Translation {",
325    "      translation 0 0 0",
326    "    }",
327    "    DEF so@gui@->crossscale Scale {",
328    "      scaleFactor 1 1 1",
329    "    }",
330    "    BaseColor {",
331    "      rgb 1 0 0",
332    "    }",
333    "    Coordinate3 {",
334    "      point [",
335    "        0 -1  0,",
336    "        0  1  0,",
337    "       -1  0  0,",
338    "        1  0  0",
339    "      ]",
340    "    }",
341    "    IndexedLineSet {",
342    "      coordIndex [",
343    "        0, 1, -1,",
344    "        2, 3, -1",
345    "      ]",
346    "    }",
347    "  }",
348    "}",
349    NULL
350  };
351
352  int i;
353  size_t bufsize;
354  for (i = 0, bufsize = 0; superimposed[i]; i++)
355    bufsize += strlen(superimposed[i]) + 1;
356  char * buf = new char [bufsize + 1];
357  for (i = 0, bufsize = 0; superimposed[i]; i++) {
358    strcpy(buf + bufsize, superimposed[i]);
359    bufsize += strlen(superimposed[i]);
360    buf[bufsize] = '\n';
361    bufsize++;
362  }
363  SoInput * input = new SoInput;
364  input->setBuffer(buf, bufsize);
365  SbBool ok = SoDB::read(input, this->superimposition);
366  assert(ok);
367  delete input;
368  delete [] buf;
369  this->superimposition->ref();
370
371
372  this->sscale = (SoScale *)
373    this->getSuperimpositionNode("so@gui@->scale");
374  this->stranslation = (SoTranslation *)
375    this->getSuperimpositionNode("so@gui@->translation");
376  this->sgeometry = (SoCoordinate3 *)
377    this->getSuperimpositionNode("so@gui@->geometry");
378  this->smaxspeedswitch = (SoSwitch *)
379    this->getSuperimpositionNode("so@gui@->maxspeedswitch");
380  this->scurrentspeedswitch = (SoSwitch *)
381    this->getSuperimpositionNode("so@gui@->currentspeedswitch");
382  this->crossswitch = (SoSwitch *)
383    this->getSuperimpositionNode("so@gui@->crossswitch");
384  this->crossposition = (SoTranslation *)
385    this->getSuperimpositionNode("so@gui@->crossposition");
386  this->crossscale = (SoScale *)
387    this->getSuperimpositionNode("so@gui@->crossscale");
388
389  SoCallback * cb = (SoCallback *)
390    this->getSuperimpositionNode("so@gui@->callback");
391  cb->setCallback(So@Gui@FlyViewerP::superimposition_cb, this);
392
393  this->updateSpeedIndicator();
394
395  PUBLIC(this)->addSuperimposition(this->superimposition);
396  PUBLIC(this)->setSuperimpositionEnabled(this->superimposition,TRUE);
397
398  if (build) {
399    @WIDGET@ viewer = PUBLIC(this)->buildWidget(PUBLIC(this)->getParentWidget());
400    PUBLIC(this)->setBaseWidget(viewer);
401  }
402}
403
404// This method dollies the camera back and forth in the scene.
405void
406So@Gui@FlyViewerP::dolly(const float delta) const
407{
408  SoCamera * const camera = PUBLIC(this)->getCamera();
409  if (camera == NULL) { return; } // if there's no scenegraph, for instance
410
411  SbPlane walkplane(PUBLIC(this)->getUpDirection(),
412		    camera->position.getValue());
413
414  SbVec3f campos = camera->position.getValue();
415  SbVec3f camvec;
416  camera->orientation.getValue().multVec(SbVec3f(0, 0, -1), camvec);
417  SbLine cross(campos + camvec,
418                campos + camvec + PUBLIC(this)->getUpDirection());
419  SbVec3f intersect;
420  walkplane.intersect(cross, intersect);
421  SbVec3f dir = intersect - campos;
422  dir.normalize();
423
424  camera->position = campos - dir * delta;
425}
426
427// The viewer is a state machine, and all changes to the current state
428// are made through this call.
429void
430So@Gui@FlyViewerP::setMode(ViewerMode newmode)
431{
432  this->viewermode = newmode;
433  this->updateCursorRepresentation();
434}
435
436// This method locates a named node in the superimposed scene.
437SoNode *
438So@Gui@FlyViewerP::getSuperimpositionNode(const char * name)
439{
440  if (! this->searcher)
441    this->searcher = new SoSearchAction;
442  searcher->reset();
443  searcher->setName(SbName(name));
444  searcher->setInterest(SoSearchAction::FIRST);
445  searcher->setSearchingAll(TRUE);
446  searcher->apply(this->superimposition);
447  assert(searcher->getPath());
448  return searcher->getPath()->getTail();
449}
450
451SbBool
452So@Gui@FlyViewerP::processKeyboardEvent(const SoKeyboardEvent * const ke)
453{
454  assert( ke != NULL );
455  switch (ke->getState()) {
456  case SoButtonEvent::UP:
457    switch (ke->getKey()) {
458    case SoKeyboardEvent::U:
459      do {
460	// either to switch to up-vector pick mode, or back to fly
461	// mode if pick-mode already activated (ie cancel the
462	// up-vector pick operation)
463	SbBool uppickmode =
464	  this->getMode() == So@Gui@FlyViewerP::WAITING_FOR_UP_PICK;
465	this->setMode(uppickmode ? So@Gui@FlyViewerP::FLYING :
466			       So@Gui@FlyViewerP::WAITING_FOR_UP_PICK);
467
468        this->stopMoving();
469
470	this->updateSpeedIndicator();
471	PUBLIC(this)->scheduleRedraw();
472	return TRUE;
473      } while (FALSE);
474      break;
475
476    case SoKeyboardEvent::S:
477      this->stopMoving();
478      this->updateSpeedIndicator();
479      PUBLIC(this)->scheduleRedraw();
480      return FALSE;
481
482    case SoKeyboardEvent::LEFT_SHIFT:
483      this->lshiftdown = FALSE;
484      if (this->lshiftdown < 0) {
485#if SO@GUI@_DEBUG
486	SoDebugError::post("So@Gui@FlyViewerP::processKeyboardEvent",
487			   "left shift key count < 0");
488#endif
489	this->lshiftdown = 0;
490      }
491      break;
492    case SoKeyboardEvent::RIGHT_SHIFT:
493      this->rshiftdown = FALSE;
494      if (this->rshiftdown < 0) {
495#if SO@GUI@_DEBUG
496	SoDebugError::post("So@Gui@FlyViewerP::processKeyboardEvent",
497			   "right shift key count < 0");
498#endif
499	this->rshiftdown = 0;
500      }
501      break;
502    case SoKeyboardEvent::LEFT_CONTROL:
503      this->lctrldown -= 1;
504      if (this->lctrldown < 0) {
505#if SO@GUI@_DEBUG
506	SoDebugError::post("So@Gui@FlyViewerP::processKyeboardEvent",
507			   "left control key count < 0");
508#endif
509	this->lctrldown = 0;
510      }
511      break;
512    case SoKeyboardEvent::RIGHT_CONTROL:
513      this->rctrldown -= 1;
514      if (this->rctrldown < 0) {
515#if SO@GUI@_DEBUG
516	SoDebugError::post("So@Gui@FlyViewerP::processKyeboardEvent",
517			   "right control key count < 0");
518#endif
519	this->rctrldown = 0;
520      }
521      break;
522    default:
523      break;
524    }
525    break;
526  case SoButtonEvent::DOWN:
527    switch (ke->getKey()) {
528    case SoKeyboardEvent::LEFT_SHIFT:
529      this->lshiftdown += 1;
530      if (this->lshiftdown > 2) {
531#if SO@GUI@_DEBUG
532	SoDebugError::post("So@Gui@FlyViewerP::processKeyboardEvent",
533			   "left shift key count > 2");
534#endif
535	this->lshiftdown = 2;
536      }
537      break;
538    case SoKeyboardEvent::RIGHT_SHIFT:
539      this->rshiftdown += 1;
540      if (this->rshiftdown > 2) {
541#if SO@GUI@_DEBUG
542	SoDebugError::post("So@Gui@FlyViewerP::processKeyboardEvent",
543			   "right shift key count > 2");
544#endif
545	this->rshiftdown = 2;
546      }
547      break;
548    case SoKeyboardEvent::LEFT_CONTROL:
549      this->lctrldown += 1;
550      if (this->lctrldown > 2) {
551#if SO@GUI@_DEBUG
552	SoDebugError::post("So@Gui@FlyViewerP::processKeyboardEvent",
553			   "left control key count > 2");
554#endif
555	this->lctrldown = 2;
556      }
557      break;
558    case SoKeyboardEvent::RIGHT_CONTROL:
559      this->rctrldown += 1;
560      if (this->rctrldown > 2) {
561#if SO@GUI@_DEBUG
562	SoDebugError::post("So@Gui@FlyViewer::processSoEvent",
563			   "right control key count > 2");
564#endif
565	this->rctrldown = 2;
566      }
567      break;
568    default:
569      break;
570    }
571    break;
572  default:
573    break;
574  }
575
576  if ((this->getMode() == So@Gui@FlyViewerP::FLYING) &&
577      (this->lctrldown || this->rctrldown)) {
578    this->setMode(So@Gui@FlyViewerP::TILTING);
579
580    this->tiltpos = this->mouseloc;
581    this->lastpos = this->mouseloc;
582
583    this->stopMoving();
584    this->updateSpeedIndicator();
585    this->crossswitch->whichChild.setValue(SO_SWITCH_ALL);
586    PUBLIC(this)->scheduleRedraw();
587    // NOTE; this could be optimized to only draw the superimposition in
588    // question if speed is zero.
589  } else if ((this->getMode() == So@Gui@FlyViewerP::TILTING) &&
590	     !this->lctrldown && !this->rctrldown) {
591    this->setMode(So@Gui@FlyViewerP::FLYING);
592    assert(this->crossswitch != NULL);
593    this->crossswitch->whichChild.setValue(SO_SWITCH_NONE);
594    PUBLIC(this)->scheduleRedraw();
595  }
596  return FALSE;
597}
598
599
600SbBool
601So@Gui@FlyViewerP::processMouseButtonEvent( const SoMouseButtonEvent * const me )
602{
603  assert( me != NULL );
604
605  // FIXME: only for fly mode
606  switch (this->getMode()) {
607  case So@Gui@FlyViewerP::WAITING_FOR_UP_PICK:
608    if ((me->getButton() == SoMouseButtonEvent::BUTTON1) &&
609	(me->getState() == SoButtonEvent::DOWN)) {
610      PUBLIC(this)->findUpDirection(me->getPosition());
611      this->setMode(So@Gui@FlyViewerP::FLYING);
612      return TRUE;
613    }
614    break;
615  case So@Gui@FlyViewerP::FLYING:
616    switch (me->getButton()) {
617    case SoMouseButtonEvent::BUTTON1:
618
619      switch (me->getState()) {
620
621      case SoButtonEvent::DOWN:
622        // Incrementing speed.
623	this->button1down = TRUE;
624	if (this->button3down) {
625          this->stopMoving();
626	}
627        else {
628          this->incrementMaxSpeed();
629	}
630	this->updateSpeedIndicator();
631	PUBLIC(this)->scheduleRedraw();
632	return TRUE;
633      case SoButtonEvent::UP:
634	this->button1down = FALSE;
635	return TRUE;
636      default:
637	break;
638      }
639      break;
640
641    case SoMouseButtonEvent::BUTTON3:
642
643      switch (me->getState()) {
644      case SoButtonEvent::DOWN:
645	this->button3down = TRUE;
646
647        if (this->button1down) {
648          this->stopMoving();
649	}
650        else
651          this->decrementMaxSpeed();
652
653	this->updateSpeedIndicator();
654	PUBLIC(this)->scheduleRedraw();
655	return TRUE;
656      case SoButtonEvent::UP:
657	this->button3down = FALSE;
658	return TRUE;
659      default:
660	break;
661      }
662      break;
663    default:
664      break;
665    }
666  default:
667    break;
668  }
669  return FALSE;
670}
671
672SbBool
673So@Gui@FlyViewerP::processLocation2Event(const SoLocation2Event * const lev)
674{
675  this->mouseloc = lev->getPosition();
676
677  if (this->getMode() == So@Gui@FlyViewerP::TILTING) {
678
679    float pan = (this->lastpos[0] - this->mouseloc[0])/100.0f;
680    float tilt = (this->lastpos[1] - this->mouseloc[1])/100.0f;
681
682    SoCamera * camera = PUBLIC(this)->getCamera();
683    if (camera == NULL)
684      return TRUE; // probably sceneless
685
686    this->updateCameraOrientation( camera, tilt, pan, 1.0f );
687    this->lastpos = this->mouseloc;
688  }
689
690  // FIXME: The size of the glcanvas only changes when the viewer is
691  // resized. The GLSize should be set from the FlyViewer, to remove
692  // the dependency on the PUBLIC(this) class. 20021021 rolvs
693  SbVec2s glsize( PUBLIC(this)->getGLSize() );
694
695  // NOTE: The values are normalized, so that the FlyViewer behaves
696  // the same way no matter the screen-size. The old way to do it made
697  // the possible range of pan and tilt increment depend on the canvas
698  // size. 20021022 rolvs.
699  this->pan_increment = 0.5f - float(this->mouseloc[0])/glsize[0];
700  this->tilt_increment = 0.5f - float(this->mouseloc[1])/glsize[1];
701
702  return TRUE;
703}
704
705void
706So@Gui@FlyViewerP::superimpositionevent(SoAction * action)
707{
708  if (!action->isOfType(SoGLRenderAction::getClassTypeId())) return;
709  SbViewportRegion vpRegion =
710    ((SoGLRenderAction *) action)->getViewportRegion();
711  SbVec2s viewport = vpRegion.getViewportSizePixels();
712  float aspect = float(viewport[0]) / float(viewport[1]);
713  float factorx = 1.0f/float(viewport[1]) * 220.0f;
714  float factory = factorx;
715  if (aspect > 1.0f) {
716    this->stranslation->translation.setValue(SbVec3f(0.0f, -0.4f, 0.0f));
717  } else {
718    this->stranslation->translation.setValue(SbVec3f(0.0f, -0.4f / aspect, 0.0f));
719    factorx /= aspect;
720    factory /= aspect;
721  }
722  if (viewport[0] > 500)
723    factorx *= 500.0f / 400.0f;
724  else
725    factorx *= float(viewport[0]) / 400.0f;
726  this->sscale->scaleFactor.setValue(SbVec3f(factorx, factory, 1.0f));
727
728  if (this->getMode() == TILTING) {
729    assert(this->crossposition != NULL);
730    assert(this->crossscale != NULL);
731    float tx = float(this->tiltpos[0]-float(viewport[0])/2.0f)/(float(viewport[0]));
732    float ty = float(this->tiltpos[1]-float(viewport[1])/2.0f)/(float(viewport[1]));
733    if (aspect > 1.0f) tx *= aspect;
734    else ty /= aspect;
735    this->crossposition->translation.setValue(SbVec3f(tx, ty, 0));
736
737    float sx = (1.0f/float(viewport[0])) * 15.0f;
738    float sy = (1.0f/float(viewport[1])) * 15.0f;
739    if (aspect > 1.0f) sx *= aspect;
740    else sy /= aspect;
741    this->crossscale->scaleFactor.setValue(SbVec3f(sx, sy, 0));
742  }
743}
744
745void
746So@Gui@FlyViewerP::superimposition_cb(void * closure, SoAction * action)
747{
748  assert(closure != NULL);
749  ((So@Gui@FlyViewerP *) closure)->superimpositionevent(action);
750}
751
752void
753So@Gui@FlyViewerP::updateSpeedIndicator(void)
754{
755  assert(this->sgeometry != NULL);
756
757  SbVec3f * points = this->sgeometry->point.startEditing();
758
759  if (points[10][0] == 0.0f)
760    this->smaxspeedswitch->whichChild.setValue(SO_SWITCH_ALL);
761  if (points[14][0] == 0.0f)
762    this->scurrentspeedswitch->whichChild.setValue(SO_SWITCH_ALL);
763  points[10][0] = this->maxspeed / (SO@GUI@_MAX_SPEED / 0.8f);
764  points[11][0] = this->maxspeed / (SO@GUI@_MAX_SPEED / 0.8f);
765  points[14][0] = this->currentspeed / (SO@GUI@_MAX_SPEED / 0.8f);
766  points[15][0] = this->currentspeed / (SO@GUI@_MAX_SPEED / 0.8f);
767  this->sgeometry->point.finishEditing();
768
769  if (this->maxspeed == 0.0f)
770    this->smaxspeedswitch->whichChild.setValue(SO_SWITCH_NONE);
771  if (this->currentspeed == 0.0f)
772    this->scurrentspeedswitch->whichChild.setValue(SO_SWITCH_NONE);
773}
774
775double So@Gui@FlyViewerP::calculateChangeInTime()
776{
777  SbTime thisrender;
778  thisrender.setToTimeOfDay();
779
780  if (this->currentspeed == 0.0f)
781    this->lastrender->setValue(thisrender.getValue() - 0.01);
782
783  // We've had a report on Coin-support that floats may have too low
784  // precision for the subtraction of these two values (ie it becomes
785  // zero), so don't cast to float.
786  //
787  // FIXME: it doesn't sound likely that this was the real cause of
788  // the problem. First of all, it seems improbably that precision
789  // could be so bad for floats, as the time between render frames
790  // should almost be guaranteed to be milliseconds, at least.  It is
791  // suspicious that the error only shows up with the Intel C++
792  // compiler, and not when the reported built with MSVC++ instead.
793  //
794  // Second, the fix is not sufficient. What if the
795  // SbTime::getTimeOfDay() resolution is too low on the particular
796  // system, so we often get zero difference here? That case must be
797  // handled, and from the original bug report, it sounds like it
798  // isn't, which is a separate bug in itself.
799  //
800  // Third, what's up with that magic multiplication factor of 10?
801  // That doesn't seem to make sense.
802  //
803  // 20061212 mortene.
804
805  // This is only a problem on release builds which makes it sound
806  // like it's just some value that is not properly
807  // initialized. (20061212 frodo)
808
809  double t = (thisrender.getValue() - this->lastrender->getValue()) * 10.0;
810
811  if (t >= 1.0)
812    t = 1.0;
813
814  return t;
815}
816
817void So@Gui@FlyViewerP::updateCurrentSpeed(double dt)
818{
819  float speedscale =
820    1.0f - (this->pan_increment * this->pan_increment
821            + this->tilt_increment * this->tilt_increment);
822
823  // NOTE: I don't believe that this boundary condition could ever
824  // happen. 20021022 rolvs
825  if (speedscale < 0.0f)
826    speedscale = 0.0f;
827
828  this->currentspeed +=
829    (((this->currentspeed +
830       this->maxspeed * speedscale) / 2.0f) -
831     this->currentspeed) * float(dt);
832}
833
834void So@Gui@FlyViewerP::updateCameraPosition(SoCamera * camera,
835                                             float current_speed,
836                                             float dt)
837{
838  assert(camera != NULL);
839  SbVec3f dir;
840  camera->orientation.getValue().multVec(SbVec3f(0, 0, -1), dir);
841  dir.normalize();
842  camera->position.setValue(camera->position.getValue() +
843                            dir * (current_speed * dt));
844}
845
846void So@Gui@FlyViewerP::updateCameraOrientation(SoCamera * camera,
847                                                float d_tilt,
848                                                float d_pan,
849                                                float dt)
850{
851  assert(camera != NULL);
852  // FIXME: Make sure that the angle between direction and up-vector
853  // stays larger than zero, or else it gets 'locked' in an undefined
854  // state and starts to act weird. This should probably be done in
855  // parent class. 20021017 rolvs
856  PUBLIC(this)->tiltCamera(d_tilt * dt);
857
858  camera->orientation = camera->orientation.getValue() *
859    SbRotation(PUBLIC(this)->getUpDirection(), d_pan * dt);
860}
861
862void
863So@Gui@FlyViewerP::incrementMaxSpeed(void)
864{
865  this->max_speed_factor++;
866  this->updateMaxSpeed();
867}
868
869
870void
871So@Gui@FlyViewerP::decrementMaxSpeed(void)
872{
873  this->max_speed_factor--;
874  this->updateMaxSpeed();
875}
876
877
878void
879So@Gui@FlyViewerP::updateSpeedScalingFactor(void)
880{
881  SoNode * n = PUBLIC(this)->getSceneGraph();
882  if(n == NULL)
883    return; // Scenegraph not set yet?
884
885  SoGetBoundingBoxAction bbact(PUBLIC(this)->getViewportRegion());
886  bbact.apply(n);
887
888  SbBox3f bbox = bbact.getBoundingBox();
889  float bbox_diagonal = (bbox.getMax() - bbox.getMin()).length();
890
891  // FIXME: It should be possible to create a simple scaling function,
892  // based on some logaritmic evaluation. 20021017 rolvs.
893  if (bbox_diagonal > 100)
894    this->speed_scaling_factor = 1.0f; // log(bbox_diagonal);
895  else if (bbox_diagonal > 10 && bbox_diagonal < 100)
896    this->speed_scaling_factor = 0.4f;
897  else if (bbox_diagonal > 1 && bbox_diagonal < 10)
898    this->speed_scaling_factor = 0.3f;
899  else if (bbox_diagonal > 0.1 && bbox_diagonal < 1)
900    this->speed_scaling_factor = 0.1f;
901  else
902    this->speed_scaling_factor = 0.1f * bbox_diagonal;
903}
904
905void So@Gui@FlyViewerP::stopMoving(void)
906{
907  this->maxspeed = 0.0f;
908  this->currentspeed = 0.0f;
909  this->max_speed_factor = 0;
910}
911
912void So@Gui@FlyViewerP::updateMaxSpeed(void)
913{
914  if (this->max_speed_factor == 0) {
915    this->stopMoving();
916    return;
917  }
918
919  // FIXME: Move this methodcall so that it is called only
920  // once. (e.g. when scene graph is set) 20021021 rolvs
921  this->updateSpeedScalingFactor();
922
923  this->maxspeed =
924    this->max_speed_factor
925    * float(pow(SO@GUI@_INC_FACTOR, abs( this->max_speed_factor)))
926    * this->speed_scaling_factor;
927
928  if (this->maxspeed > SO@GUI@_MAX_SPEED)
929    this->maxspeed = SO@GUI@_MAX_SPEED;
930  else if (this->maxspeed < -1*SO@GUI@_MAX_SPEED)
931    this->maxspeed = -1 * SO@GUI@_MAX_SPEED;
932}
933
934
935
936// Set cursor graphics according to mode.
937void
938So@Gui@FlyViewerP::updateCursorRepresentation(void)
939{
940  if (!PUBLIC(this)->isCursorEnabled()) {
941    PUBLIC(this)->setComponentCursor(So@Gui@Cursor::getBlankCursor());
942    return;
943  }
944
945  switch (this->viewermode) {
946  case So@Gui@FlyViewerP::FLYING:
947    PUBLIC(this)->setComponentCursor(So@Gui@Cursor(So@Gui@Cursor::DEFAULT));
948    break;
949
950  case So@Gui@FlyViewerP::WAITING_FOR_SEEK:
951    PUBLIC(this)->setComponentCursor(So@Gui@Cursor(So@Gui@Cursor::CROSSHAIR));
952    break;
953
954  case So@Gui@FlyViewerP::WAITING_FOR_UP_PICK:
955    PUBLIC(this)->setComponentCursor(So@Gui@Cursor(So@Gui@Cursor::UPARROW));
956    break;
957
958  case So@Gui@FlyViewerP::TILTING:
959    PUBLIC(this)->setComponentCursor(So@Gui@Cursor::getPanCursor());
960    break;
961
962  default:
963    assert(0 && "unknown mode");
964    break;
965  }
966}
967
968#endif // DOXYGEN_SKIP_THIS
969
970// ************************************************************************
971
972SO@GUI@_OBJECT_SOURCE(So@Gui@FlyViewer);
973
974// ************************************************************************
975
976/*!
977  Public constructor.
978*/
979So@Gui@FlyViewer::So@Gui@FlyViewer(@WIDGET@ parent,
980                                   const char * name,
981                                   SbBool embed,
982                                   So@Gui@FullViewer::BuildFlag flag,
983                                   So@Gui@Viewer::Type type)
984  : inherited(parent, name, embed, flag, type, FALSE)
985{
986  PRIVATE(this) = new So@Gui@FlyViewerP(this);
987  PRIVATE(this)->constructor(TRUE);
988}
989
990// ************************************************************************
991
992/*!
993  Protected constructor, used by viewer components derived from the
994  So@Gui@FlyViewer.
995*/
996So@Gui@FlyViewer::So@Gui@FlyViewer(@WIDGET@ parent,
997                                   const char * const name,
998                                   SbBool embed,
999                                   So@Gui@FullViewer::BuildFlag flag,
1000                                   So@Gui@Viewer::Type type,
1001                                   SbBool build)
1002  : inherited(parent, name, embed, flag, type, FALSE)
1003{
1004  PRIVATE(this) = new So@Gui@FlyViewerP(this);
1005  PRIVATE(this)->constructor(build);
1006}
1007
1008// ************************************************************************
1009
1010/*!
1011  Virtual constructor.
1012*/
1013So@Gui@FlyViewer::~So@Gui@FlyViewer()
1014{
1015  if (PRIVATE(this)->superimposition != NULL) {
1016    this->removeSuperimposition(PRIVATE(this)->superimposition);
1017    PRIVATE(this)->superimposition->unref();
1018    PRIVATE(this)->superimposition = NULL;
1019  }
1020  delete PRIVATE(this);
1021}
1022
1023// ************************************************************************
1024
1025// doc in super
1026void
1027So@Gui@FlyViewer::setViewing(SbBool enable)
1028{
1029  if (enable != this->isViewing())
1030    PRIVATE(this)->stopMoving();
1031
1032  inherited::setViewing(enable);
1033  this->setSuperimpositionEnabled(PRIVATE(this)->superimposition, enable);
1034  this->scheduleRedraw();
1035}
1036
1037// ************************************************************************
1038
1039// doc in super
1040void
1041So@Gui@FlyViewer::resetToHomePosition(void)
1042{
1043  PRIVATE(this)->stopMoving();
1044  inherited::resetToHomePosition();
1045}
1046
1047// ************************************************************************
1048
1049// doc in super
1050void
1051So@Gui@FlyViewer::viewAll(void)
1052{
1053  PRIVATE(this)->stopMoving();
1054  inherited::viewAll();
1055}
1056
1057// ************************************************************************
1058
1059// doc in super
1060void
1061So@Gui@FlyViewer::setCamera(SoCamera * camera)
1062{
1063  PRIVATE(this)->stopMoving();
1064
1065  inherited::setCamera(camera);
1066  // FIXME: do something with up-direction?
1067}
1068
1069// ************************************************************************
1070
1071// doc in super
1072void
1073So@Gui@FlyViewer::setCursorEnabled(SbBool enable)
1074{
1075  inherited::setCursorEnabled(enable);
1076  PRIVATE(this)->updateCursorRepresentation();
1077}
1078
1079// ************************************************************************
1080
1081// doc in super
1082void
1083So@Gui@FlyViewer::setCameraType(SoType type)
1084{
1085  PRIVATE(this)->stopMoving();
1086  inherited::setCameraType(type);
1087  // FIXME: what else? 20010907 mortene.
1088}
1089
1090// ************************************************************************
1091
1092// doc in super
1093const char *
1094So@Gui@FlyViewer::getDefaultWidgetName(void) const
1095{
1096  static const char defaultWidgetName[] = "So@Gui@FlyViewer";
1097  return defaultWidgetName;
1098}
1099
1100// ************************************************************************
1101
1102// doc in super
1103const char *
1104So@Gui@FlyViewer::getDefaultTitle(void) const
1105{
1106  static const char defaultTitle[] = "Fly Viewer";
1107  return defaultTitle;
1108}
1109
1110// ************************************************************************
1111
1112// doc in super
1113const char *
1114So@Gui@FlyViewer::getDefaultIconTitle(void) const
1115{
1116  static const char defaultIconTitle[] = "Fly Viewer";
1117  return defaultIconTitle;
1118}
1119
1120// ************************************************************************
1121
1122// Documented in superclass.
1123SbBool
1124So@Gui@FlyViewer::processSoEvent(const SoEvent * const event)
1125{
1126  // FIXME: Refactor the event-handling so that it uses the same
1127  // strategy as in So@Gui@ExaminerViewer, where the event-handler
1128  // only checks the state and the mode from that. 20021016 rolvs.
1129
1130  // We're in "interact" mode (ie *not* the camera modification mode),
1131  // so don't handle the event here. It should either be forwarded to
1132  // the scenegraph, or caught by So@Gui@Viewer::processSoEvent() if
1133  // it's an ESC and ALT press (to switch modes).
1134  if (!this->isViewing()) { return inherited::processSoEvent(event); }
1135
1136  // Events when in "ready-to-seek" mode are ignored, except those
1137  // which influence the seek mode itself -- these are handled further
1138  // up the inheritance hierarchy.
1139  if (this->isSeekMode()) { return inherited::processSoEvent(event); }
1140
1141  // FIXME: There is more parts of the code in
1142  // So@Gui@*FlyViewer::processEvent that should go in to the
1143  // processKeyboardEvent function; to be fixed later.
1144  // 20021015 rolvs
1145
1146  // Keyboard handling
1147  if (event->isOfType(SoKeyboardEvent::getClassTypeId())) {
1148    SbBool result =
1149      PRIVATE(this)->processKeyboardEvent( (SoKeyboardEvent*)event );
1150
1151    if( result ){
1152      return TRUE;
1153    }
1154    // Else: Do nothing, and proceed as usual
1155  }
1156
1157  // Mousebutton handling
1158  // See FIXME and comment for keyboardhandler.
1159  else if (event->isOfType(SoMouseButtonEvent::getClassTypeId())) {
1160    // FIXME: only for fly mode
1161    const SoMouseButtonEvent * const me =
1162      (const SoMouseButtonEvent *const) event;
1163    SbBool result = PRIVATE( this )->processMouseButtonEvent( me );
1164    if( result )
1165      return TRUE;
1166  }
1167
1168  else if (event->isOfType(SoLocation2Event::getClassTypeId())) {
1169    const SoLocation2Event * const le =
1170      (const SoLocation2Event * const) event;
1171    SbBool result = PRIVATE( this )->processLocation2Event( le );
1172    if( result )
1173      return TRUE;
1174  }
1175
1176  return inherited::processSoEvent(event);
1177}
1178
1179// ************************************************************************
1180
1181// doc in super
1182void
1183So@Gui@FlyViewer::setSeekMode(SbBool enable)
1184{
1185  // Note: this method is almost identical to the setSeekMode() in the
1186  // So@Gui@ExaminerViewer, so migrate any changes.
1187
1188#if SO@GUI@_DEBUG
1189  if (enable == this->isSeekMode()) {
1190    SoDebugError::postWarning("So@Gui@FlyViewer::setSeekMode",
1191                              "seek mode already %sset", enable ? "" : "un");
1192    return;
1193  }
1194#endif // SO@GUI@_DEBUG
1195
1196  // FIXME: what if we're in the middle of a seek already? 20010910 mortene.
1197  // larsa - either stop the seek (on false) or reset timer to two new secs
1198
1199  inherited::setSeekMode(enable);
1200  PRIVATE(this)->setMode(enable ? So@Gui@FlyViewerP::WAITING_FOR_SEEK :
1201                            So@Gui@FlyViewerP::FLYING);
1202}
1203
1204// ************************************************************************
1205
1206// doc in super
1207void
1208So@Gui@FlyViewer::actualRedraw(void)
1209{
1210  if (!this->isViewing()) {
1211    inherited::actualRedraw();
1212    return;
1213  }
1214
1215  switch (PRIVATE(this)->getMode()) {
1216  case So@Gui@FlyViewerP::FLYING:
1217    {
1218      PRIVATE(this)->updateCurrentSpeed(PRIVATE(this)->calculateChangeInTime());
1219      PRIVATE(this)->updateSpeedIndicator();
1220
1221      SbTime thisrender;
1222      thisrender.setToTimeOfDay();
1223
1224      if (PRIVATE(this)->currentspeed != 0.0f) {
1225        // We've had a report on Coin-support that floats may have too
1226        // low precision for the subtraction of these two values (ie
1227        // it becomes zero), so don't cast to float.
1228        //
1229        // Note: there's some additional information about this, see
1230        // the FIXME comment in the
1231        // So@Gui@FlyViewerP::calculateChangeInTime() function.
1232        double t = (thisrender.getValue() -
1233                    PRIVATE(this)->lastrender->getValue()) * 2.0;
1234        if (t > 0.0) {
1235          SoCamera * camera = this->getCamera();
1236
1237          if (camera){ // could be a sceneless viewer
1238            PRIVATE(this)->updateCameraPosition
1239              ( camera,
1240                PRIVATE(this)->currentspeed*
1241                PRIVATE(this)->speed_scaling_factor,
1242                float(t) );
1243            PRIVATE(this)->updateCameraOrientation
1244              ( camera,
1245                PRIVATE(this)->tilt_increment,
1246                PRIVATE(this)->pan_increment,
1247                float(t) );
1248          }
1249        }
1250      }
1251
1252      inherited::actualRedraw();
1253
1254      PRIVATE(this)->lastrender->setValue(thisrender.getValue());
1255
1256      if (PRIVATE(this)->currentspeed != 0.0f ||
1257          PRIVATE(this)->maxspeed != 0.0f)
1258        this->scheduleRedraw();
1259    }
1260    break;
1261  default:
1262    inherited::actualRedraw();
1263    break;
1264  }
1265}
1266
1267// ************************************************************************
1268
1269// doc in super
1270void
1271So@Gui@FlyViewer::rightWheelMotion(float value)
1272{
1273  PRIVATE(this)->dolly(value - this->getRightWheelValue());
1274  inherited::rightWheelMotion(value);
1275}
1276
1277// ************************************************************************
1278
1279// doc in super
1280void
1281So@Gui@FlyViewer::afterRealizeHook(void)
1282{
1283  PRIVATE(this)->updateCursorRepresentation();
1284  inherited::afterRealizeHook();
1285}
1286
1287// ************************************************************************
1288
1289#undef PRIVATE
1290#undef PUBLIC
1291
1292