]> git.mxchange.org Git - flightgear.git/blob - src/Main/viewer.cxx
Avoid SGLocation usage.
[flightgear.git] / src / Main / viewer.cxx
1 // viewer.cxx -- class for managing a viewer in the flightgear world.
2 //
3 // Written by Curtis Olson, started August 1997.
4 //                          overhaul started October 2000.
5 //   partially rewritten by Jim Wilson jim@kelcomaine.com using interface
6 //                          by David Megginson March 2002
7 //
8 // Copyright (C) 1997 - 2000  Curtis L. Olson  - http://www.flightgear.org/~curt
9 //
10 // This program is free software; you can redistribute it and/or
11 // modify it under the terms of the GNU General Public License as
12 // published by the Free Software Foundation; either version 2 of the
13 // License, or (at your option) any later version.
14 //
15 // This program is distributed in the hope that it will be useful, but
16 // WITHOUT ANY WARRANTY; without even the implied warranty of
17 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
18 // General Public License for more details.
19 //
20 // You should have received a copy of the GNU General Public License
21 // along with this program; if not, write to the Free Software
22 // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
23 //
24 // $Id$
25
26 #ifdef HAVE_CONFIG_H
27 #  include "config.h"
28 #endif
29
30 #include <simgear/compiler.h>
31
32 #include "fg_props.hxx"
33
34 #ifdef HAVE_CONFIG_H
35 #  include <config.h>
36 #endif
37
38 #include <simgear/debug/logstream.hxx>
39 #include <simgear/constants.h>
40 #include <simgear/math/polar3d.hxx>
41 #include <simgear/math/sg_geodesy.hxx>
42 #include <simgear/scene/model/placement.hxx>
43 #include <simgear/math/vector.hxx>
44
45 #include <Main/globals.hxx>
46 #include <Scenery/scenery.hxx>
47 #include <Model/acmodel.hxx>
48
49 #include "viewer.hxx"
50
51 #include "CameraGroup.hxx"
52
53 using namespace flightgear;
54 \f
55 ////////////////////////////////////////////////////////////////////////
56 // Implementation of FGViewer.
57 ////////////////////////////////////////////////////////////////////////
58
59 // Constructor...
60 FGViewer::FGViewer( fgViewType Type, bool from_model, int from_model_index,
61                     bool at_model, int at_model_index,
62                     double damp_roll, double damp_pitch, double damp_heading,
63                     double x_offset_m, double y_offset_m, double z_offset_m,
64                     double heading_offset_deg, double pitch_offset_deg,
65                     double roll_offset_deg,
66                     double fov_deg, double aspect_ratio_multiplier,
67                     double target_x_offset_m, double target_y_offset_m,
68                     double target_z_offset_m, double near_m, bool internal ):
69     _dirty(true),
70     _lon_deg(0),
71     _lat_deg(0),
72     _alt_ft(0),
73     _target_lon_deg(0),
74     _target_lat_deg(0),
75     _target_alt_ft(0),
76     _roll_deg(0),
77     _pitch_deg(0),
78     _heading_deg(0),
79     _damp_sync(0),
80     _damp_roll(0),
81     _damp_pitch(0),
82     _damp_heading(0),
83     _scaling_type(FG_SCALING_MAX),
84     _cameraGroup(CameraGroup::getDefault())
85 {
86     _absolute_view_pos = SGVec3d(0, 0, 0);
87     _type = Type;
88     _from_model = from_model;
89     _from_model_index = from_model_index;
90     _at_model = at_model;
91     _at_model_index = at_model_index;
92
93     _internal = internal;
94
95     if (damp_roll > 0.0)
96       _damp_roll = 1.0 / pow(10.0, fabs(damp_roll));
97     if (damp_pitch > 0.0)
98       _damp_pitch = 1.0 / pow(10.0, fabs(damp_pitch));
99     if (damp_heading > 0.0)
100       _damp_heading = 1.0 / pow(10.0, fabs(damp_heading));
101
102     _offset_m.x() = x_offset_m;
103     _offset_m.y() = y_offset_m;
104     _offset_m.z() = z_offset_m;
105     _heading_offset_deg = heading_offset_deg;
106     _pitch_offset_deg = pitch_offset_deg;
107     _roll_offset_deg = roll_offset_deg;
108     _goal_heading_offset_deg = heading_offset_deg;
109     _goal_pitch_offset_deg = pitch_offset_deg;
110     _goal_roll_offset_deg = roll_offset_deg;
111     if (fov_deg > 0) {
112       _fov_deg = fov_deg;
113     } else {
114       _fov_deg = 55;
115     }
116     _aspect_ratio_multiplier = aspect_ratio_multiplier;
117     _target_offset_m.x() = target_x_offset_m;
118     _target_offset_m.y() = target_y_offset_m;
119     _target_offset_m.z() = target_z_offset_m;
120     _ground_level_nearplane_m = near_m;
121     // a reasonable guess for init, so that the math doesn't blow up
122 }
123
124
125 // Destructor
126 FGViewer::~FGViewer( void ) {
127 }
128
129 void
130 FGViewer::init ()
131 {
132 }
133
134 void
135 FGViewer::bind ()
136 {
137 }
138
139 void
140 FGViewer::unbind ()
141 {
142 }
143
144 void
145 FGViewer::setType ( int type )
146 {
147   if (type == 0)
148     _type = FG_LOOKFROM;
149   if (type == 1)
150     _type = FG_LOOKAT;
151 }
152
153 void
154 FGViewer::setInternal ( bool internal )
155 {
156   _internal = internal;
157 }
158
159 void
160 FGViewer::setLongitude_deg (double lon_deg)
161 {
162   _dirty = true;
163   _lon_deg = lon_deg;
164 }
165
166 void
167 FGViewer::setLatitude_deg (double lat_deg)
168 {
169   _dirty = true;
170   _lat_deg = lat_deg;
171 }
172
173 void
174 FGViewer::setAltitude_ft (double alt_ft)
175 {
176   _dirty = true;
177   _alt_ft = alt_ft;
178 }
179
180 void
181 FGViewer::setPosition (double lon_deg, double lat_deg, double alt_ft)
182 {
183   _dirty = true;
184   _lon_deg = lon_deg;
185   _lat_deg = lat_deg;
186   _alt_ft = alt_ft;
187 }
188
189 void
190 FGViewer::setTargetLongitude_deg (double lon_deg)
191 {
192   _dirty = true;
193   _target_lon_deg = lon_deg;
194 }
195
196 void
197 FGViewer::setTargetLatitude_deg (double lat_deg)
198 {
199   _dirty = true;
200   _target_lat_deg = lat_deg;
201 }
202
203 void
204 FGViewer::setTargetAltitude_ft (double alt_ft)
205 {
206   _dirty = true;
207   _target_alt_ft = alt_ft;
208 }
209
210 void
211 FGViewer::setTargetPosition (double lon_deg, double lat_deg, double alt_ft)
212 {
213   _dirty = true;
214   _target_lon_deg = lon_deg;
215   _target_lat_deg = lat_deg;
216   _target_alt_ft = alt_ft;
217 }
218
219 void
220 FGViewer::setRoll_deg (double roll_deg)
221 {
222   _dirty = true;
223   _roll_deg = roll_deg;
224 }
225
226 void
227 FGViewer::setPitch_deg (double pitch_deg)
228 {
229   _dirty = true;
230   _pitch_deg = pitch_deg;
231 }
232
233 void
234 FGViewer::setHeading_deg (double heading_deg)
235 {
236   _dirty = true;
237   _heading_deg = heading_deg;
238 }
239
240 void
241 FGViewer::setOrientation (double roll_deg, double pitch_deg, double heading_deg)
242 {
243   _dirty = true;
244   _roll_deg = roll_deg;
245   _pitch_deg = pitch_deg;
246   _heading_deg = heading_deg;
247 }
248
249 void
250 FGViewer::setTargetRoll_deg (double target_roll_deg)
251 {
252   _dirty = true;
253   _target_roll_deg = target_roll_deg;
254 }
255
256 void
257 FGViewer::setTargetPitch_deg (double target_pitch_deg)
258 {
259   _dirty = true;
260   _target_pitch_deg = target_pitch_deg;
261 }
262
263 void
264 FGViewer::setTargetHeading_deg (double target_heading_deg)
265 {
266   _dirty = true;
267   _target_heading_deg = target_heading_deg;
268 }
269
270 void
271 FGViewer::setTargetOrientation (double target_roll_deg, double target_pitch_deg, double target_heading_deg)
272 {
273   _dirty = true;
274   _target_roll_deg = target_roll_deg;
275   _target_pitch_deg = target_pitch_deg;
276   _target_heading_deg = target_heading_deg;
277 }
278
279 void
280 FGViewer::setXOffset_m (double x_offset_m)
281 {
282   _dirty = true;
283   _offset_m.x() = x_offset_m;
284 }
285
286 void
287 FGViewer::setYOffset_m (double y_offset_m)
288 {
289   _dirty = true;
290   _offset_m.y() = y_offset_m;
291 }
292
293 void
294 FGViewer::setZOffset_m (double z_offset_m)
295 {
296   _dirty = true;
297   _offset_m.z() = z_offset_m;
298 }
299
300 void
301 FGViewer::setTargetXOffset_m (double target_x_offset_m)
302 {
303   _dirty = true;
304   _target_offset_m.x() = target_x_offset_m;
305 }
306
307 void
308 FGViewer::setTargetYOffset_m (double target_y_offset_m)
309 {
310   _dirty = true;
311   _target_offset_m.y() = target_y_offset_m;
312 }
313
314 void
315 FGViewer::setTargetZOffset_m (double target_z_offset_m)
316 {
317   _dirty = true;
318   _target_offset_m.z() = target_z_offset_m;
319 }
320
321 void
322 FGViewer::setPositionOffsets (double x_offset_m, double y_offset_m, double z_offset_m)
323 {
324   _dirty = true;
325   _offset_m.x() = x_offset_m;
326   _offset_m.y() = y_offset_m;
327   _offset_m.z() = z_offset_m;
328 }
329
330 void
331 FGViewer::setRollOffset_deg (double roll_offset_deg)
332 {
333   _dirty = true;
334   _roll_offset_deg = roll_offset_deg;
335 }
336
337 void
338 FGViewer::setPitchOffset_deg (double pitch_offset_deg)
339 {
340   _dirty = true;
341   _pitch_offset_deg = pitch_offset_deg;
342 }
343
344 void
345 FGViewer::setHeadingOffset_deg (double heading_offset_deg)
346 {
347   _dirty = true;
348   _heading_offset_deg = heading_offset_deg;
349 }
350
351 void
352 FGViewer::setGoalRollOffset_deg (double goal_roll_offset_deg)
353 {
354   _dirty = true;
355   _goal_roll_offset_deg = goal_roll_offset_deg;
356 }
357
358 void
359 FGViewer::setGoalPitchOffset_deg (double goal_pitch_offset_deg)
360 {
361   _dirty = true;
362   _goal_pitch_offset_deg = goal_pitch_offset_deg;
363   if ( _goal_pitch_offset_deg < -90 ) {
364     _goal_pitch_offset_deg = -90.0;
365   }
366   if ( _goal_pitch_offset_deg > 90.0 ) {
367     _goal_pitch_offset_deg = 90.0;
368   }
369
370 }
371
372 void
373 FGViewer::setGoalHeadingOffset_deg (double goal_heading_offset_deg)
374 {
375   _dirty = true;
376   _goal_heading_offset_deg = goal_heading_offset_deg;
377   while ( _goal_heading_offset_deg < 0.0 ) {
378     _goal_heading_offset_deg += 360;
379   }
380   while ( _goal_heading_offset_deg > 360 ) {
381     _goal_heading_offset_deg -= 360;
382   }
383 }
384
385 void
386 FGViewer::setOrientationOffsets (double roll_offset_deg, double pitch_offset_deg, double heading_offset_deg)
387 {
388   _dirty = true;
389   _roll_offset_deg = roll_offset_deg;
390   _pitch_offset_deg = pitch_offset_deg;
391   _heading_offset_deg = heading_offset_deg;
392 }
393
394 // recalc() is done every time one of the setters is called (making the 
395 // cached data "dirty") on the next "get".  It calculates all the outputs 
396 // for viewer.
397 void
398 FGViewer::recalc ()
399 {
400   if (_type == FG_LOOKFROM) {
401     recalcLookFrom();
402   } else {
403     recalcLookAt();
404   }
405
406   set_clean();
407 }
408
409 // recalculate for LookFrom view type...
410 void
411 FGViewer::recalcLookFrom ()
412 {
413   // Update location data ...
414   double lat, lon, alt, head, pitch, roll;
415   if ( _from_model ) {
416     SGModelPlacement* placement = globals->get_aircraft_model()->get3DModel();
417     lat = placement->getLatitudeDeg();
418     lon = placement->getLongitudeDeg();
419     alt = placement->getElevationFt();
420
421     head = placement->getHeadingDeg();
422     pitch = placement->getPitchDeg();
423     roll = placement->getRollDeg();
424   } else {
425     lat = _lat_deg;
426     lon = _lon_deg;
427     alt = _alt_ft;
428     head = _heading_deg;
429     pitch = _pitch_deg;
430     roll = _roll_deg;
431   }
432   if ( !_from_model ) {
433     // update from our own data...
434     dampEyeData(roll, pitch, head);
435   }
436
437   // The geodetic position of our base view position
438   SGGeod geodPos = SGGeod::fromDegFt(lon, lat, alt);
439   // The rotation rotating from the earth centerd frame to
440   // the horizontal local OpenGL frame
441   SGQuatd hlOr = SGQuatd::viewHL(geodPos);
442
443   // the rotation from the horizontal local frame to the basic view orientation
444   SGQuatd hlToBody = SGQuatd::fromYawPitchRollDeg(head, pitch, roll);
445   hlToBody = SGQuatd::simToView(hlToBody);
446
447   // The cartesian position of the basic view coordinate
448   SGVec3d position = SGVec3d::fromGeod(geodPos);
449   // the rotation offset, don't know why heading is negative here ...
450   SGQuatd viewOffsetOr = SGQuatd::simToView(
451     SGQuatd::fromYawPitchRollDeg(-_heading_offset_deg, _pitch_offset_deg,
452                                  _roll_offset_deg));
453
454   // Compute the eyepoints orientation and position
455   // wrt the earth centered frame - that is global coorinates
456   SGQuatd ec2body = hlOr*hlToBody;
457   _absolute_view_pos = position + ec2body.backTransform(_offset_m);
458   mViewOrientation = ec2body*viewOffsetOr;
459 }
460
461 void
462 FGViewer::recalcLookAt ()
463 {
464   // The geodetic position of our target to look at
465   SGGeod geodTargetPos;
466   SGQuatd geodTargetOr;
467   if ( _at_model ) {
468     SGModelPlacement* placement = globals->get_aircraft_model()->get3DModel();
469     double lat = placement->getLatitudeDeg();
470     double lon = placement->getLongitudeDeg();
471     double alt = placement->getElevationFt();
472     geodTargetPos = SGGeod::fromDegFt(lon, lat, alt);
473
474     double head = placement->getHeadingDeg();
475     double pitch = placement->getPitchDeg();
476     double roll = placement->getRollDeg();
477     geodTargetOr = SGQuatd::fromYawPitchRollDeg(head, pitch, roll);
478   } else {
479     dampEyeData(_target_roll_deg, _target_pitch_deg, _target_heading_deg);
480
481     // if not model then calculate our own target position...
482     geodTargetPos = SGGeod::fromDegFt(_target_lon_deg,
483                                       _target_lat_deg,
484                                       _target_alt_ft);
485     geodTargetOr = SGQuatd::fromYawPitchRollDeg(_target_heading_deg,
486                                                 _target_pitch_deg,
487                                                 _target_roll_deg);
488   }
489   SGQuatd geodTargetHlOr = SGQuatd::fromLonLat(geodTargetPos);
490
491
492   SGGeod geodEyePos;
493   SGQuatd geodEyeOr;
494   if ( _from_model ) {
495     SGModelPlacement* placement = globals->get_aircraft_model()->get3DModel();
496     double lat = placement->getLatitudeDeg();
497     double lon = placement->getLongitudeDeg();
498     double alt = placement->getElevationFt();
499     geodEyePos = SGGeod::fromDegFt(lon, lat, alt);
500
501     double head = placement->getHeadingDeg();
502     double pitch = placement->getPitchDeg();
503     double roll = placement->getRollDeg();
504     geodEyeOr = SGQuatd::fromYawPitchRollDeg(head, pitch, roll);
505   } else {
506     dampEyeData(_roll_deg, _pitch_deg, _heading_deg);
507
508     // update from our own data, just the rotation here...
509     geodEyePos = SGGeod::fromDegFt(_lon_deg, _lat_deg, _alt_ft);
510     geodEyeOr = SGQuatd::fromYawPitchRollDeg(_heading_deg,
511                                              _pitch_deg,
512                                              _roll_deg);
513   }
514   SGQuatd geodEyeHlOr = SGQuatd::fromLonLat(geodEyePos);
515
516   // the rotation offset, don't know why heading is negative here ...
517   SGQuatd eyeOffsetOr =
518     SGQuatd::fromYawPitchRollDeg(-_heading_offset_deg + 180, _pitch_offset_deg,
519                                  _roll_offset_deg);
520
521   // Offsets to the eye position
522   SGVec3d eyeOff(-_offset_m.z(), _offset_m.x(), -_offset_m.y());
523   SGQuatd ec2eye = geodEyeHlOr*geodEyeOr;
524   SGVec3d eyeCart = SGVec3d::fromGeod(geodEyePos);
525   eyeCart += (ec2eye*eyeOffsetOr).backTransform(eyeOff);
526
527   SGVec3d atCart = SGVec3d::fromGeod(geodTargetPos);
528
529   // add target offsets to at_position...
530   SGVec3d target_pos_off(-_target_offset_m.z(), _target_offset_m.x(),
531                          -_target_offset_m.y());
532   target_pos_off = (geodTargetHlOr*geodTargetOr).backTransform(target_pos_off);
533   atCart += target_pos_off;
534   eyeCart += target_pos_off;
535
536   // Compute the eyepoints orientation and position
537   // wrt the earth centered frame - that is global coorinates
538   _absolute_view_pos = eyeCart;
539
540   // the view direction
541   SGVec3d dir = normalize(atCart - eyeCart);
542   // the up directon
543   SGVec3d up = ec2eye.backTransform(SGVec3d(0, 0, -1));
544   // rotate dir to the 0-th unit vector
545   // rotate up to 2-th unit vector
546   mViewOrientation = SGQuatd::fromRotateTo(-dir, 2, up, 1);
547 }
548
549 void
550 FGViewer::dampEyeData(double &roll_deg, double &pitch_deg, double &heading_deg)
551 {
552   const double interval = 0.01;
553
554   static FGViewer *last_view = 0;
555   if (last_view != this) {
556     _damp_sync = 0.0;
557     _damped_roll_deg = roll_deg;
558     _damped_pitch_deg = pitch_deg;
559     _damped_heading_deg = heading_deg;
560     last_view = this;
561     return;
562   }
563
564   if (_damp_sync < interval) {
565     if (_damp_roll > 0.0)
566       roll_deg = _damped_roll_deg;
567     if (_damp_pitch > 0.0)
568       pitch_deg = _damped_pitch_deg;
569     if (_damp_heading > 0.0)
570       heading_deg = _damped_heading_deg;
571     return;
572   }
573
574   while (_damp_sync >= interval) {
575     _damp_sync -= interval;
576
577     double d;
578     if (_damp_roll > 0.0) {
579       d = _damped_roll_deg - roll_deg;
580       if (d >= 180.0)
581         _damped_roll_deg -= 360.0;
582       else if (d < -180.0)
583         _damped_roll_deg += 360.0;
584       roll_deg = _damped_roll_deg = roll_deg * _damp_roll + _damped_roll_deg * (1 - _damp_roll);
585     }
586
587     if (_damp_pitch > 0.0) {
588       d = _damped_pitch_deg - pitch_deg;
589       if (d >= 180.0)
590         _damped_pitch_deg -= 360.0;
591       else if (d < -180.0)
592         _damped_pitch_deg += 360.0;
593       pitch_deg = _damped_pitch_deg = pitch_deg * _damp_pitch + _damped_pitch_deg * (1 - _damp_pitch);
594     }
595
596     if (_damp_heading > 0.0) {
597       d = _damped_heading_deg - heading_deg;
598       if (d >= 180.0)
599         _damped_heading_deg -= 360.0;
600       else if (d < -180.0)
601         _damped_heading_deg += 360.0;
602       heading_deg = _damped_heading_deg = heading_deg * _damp_heading + _damped_heading_deg * (1 - _damp_heading);
603     }
604   }
605 }
606
607 double
608 FGViewer::get_h_fov()
609 {
610     switch (_scaling_type) {
611     case FG_SCALING_WIDTH:  // h_fov == fov
612         return _fov_deg;
613     case FG_SCALING_MAX:
614         if (_aspect_ratio < 1.0) {
615             // h_fov == fov
616             return _fov_deg;
617         } else {
618             // v_fov == fov
619             return
620                 atan(tan(_fov_deg/2 * SG_DEGREES_TO_RADIANS)
621                      / (_aspect_ratio*_aspect_ratio_multiplier))
622                 * SG_RADIANS_TO_DEGREES * 2;
623         }
624     default:
625         assert(false);
626     }
627     return 0.0;
628 }
629
630
631
632 double
633 FGViewer::get_v_fov()
634 {
635     switch (_scaling_type) {
636     case FG_SCALING_WIDTH:  // h_fov == fov
637         return 
638             atan(tan(_fov_deg/2 * SG_DEGREES_TO_RADIANS)
639                  * (_aspect_ratio*_aspect_ratio_multiplier))
640             * SG_RADIANS_TO_DEGREES * 2;
641     case FG_SCALING_MAX:
642         if (_aspect_ratio < 1.0) {
643             // h_fov == fov
644             return
645                 atan(tan(_fov_deg/2 * SG_DEGREES_TO_RADIANS)
646                      * (_aspect_ratio*_aspect_ratio_multiplier))
647                 * SG_RADIANS_TO_DEGREES * 2;
648         } else {
649             // v_fov == fov
650             return _fov_deg;
651         }
652     default:
653         assert(false);
654     }
655     return 0.0;
656 }
657
658 void
659 FGViewer::update (double dt)
660 {
661   _damp_sync += dt;
662
663   int i;
664   int dt_ms = int(dt * 1000);
665   for ( i = 0; i < dt_ms; i++ ) {
666     if ( fabs( _goal_heading_offset_deg - _heading_offset_deg) < 1 ) {
667       setHeadingOffset_deg( _goal_heading_offset_deg );
668       break;
669     } else {
670       // move current_view.headingoffset towards
671       // current_view.goal_view_offset
672       if ( _goal_heading_offset_deg > _heading_offset_deg )
673         {
674           if ( _goal_heading_offset_deg - _heading_offset_deg < 180 ){
675             incHeadingOffset_deg( 0.5 );
676           } else {
677             incHeadingOffset_deg( -0.5 );
678           }
679         } else {
680           if ( _heading_offset_deg - _goal_heading_offset_deg < 180 ){
681             incHeadingOffset_deg( -0.5 );
682           } else {
683             incHeadingOffset_deg( 0.5 );
684           }
685         }
686       if ( _heading_offset_deg > 360 ) {
687         incHeadingOffset_deg( -360 );
688       } else if ( _heading_offset_deg < 0 ) {
689         incHeadingOffset_deg( 360 );
690       }
691     }
692   }
693
694   for ( i = 0; i < dt_ms; i++ ) {
695     if ( fabs( _goal_pitch_offset_deg - _pitch_offset_deg ) < 1 ) {
696       setPitchOffset_deg( _goal_pitch_offset_deg );
697       break;
698     } else {
699       // move current_view.pitch_offset_deg towards
700       // current_view.goal_pitch_offset
701       if ( _goal_pitch_offset_deg > _pitch_offset_deg )
702         {
703           incPitchOffset_deg( 1.0 );
704         } else {
705             incPitchOffset_deg( -1.0 );
706         }
707       if ( _pitch_offset_deg > 90 ) {
708         setPitchOffset_deg(90);
709       } else if ( _pitch_offset_deg < -90 ) {
710         setPitchOffset_deg( -90 );
711       }
712     }
713   }
714
715
716   for ( i = 0; i < dt_ms; i++ ) {
717     if ( fabs( _goal_roll_offset_deg - _roll_offset_deg ) < 1 ) {
718       setRollOffset_deg( _goal_roll_offset_deg );
719       break;
720     } else {
721       // move current_view.roll_offset_deg towards
722       // current_view.goal_roll_offset
723       if ( _goal_roll_offset_deg > _roll_offset_deg )
724         {
725           incRollOffset_deg( 1.0 );
726         } else {
727             incRollOffset_deg( -1.0 );
728         }
729       if ( _roll_offset_deg > 90 ) {
730         setRollOffset_deg(90);
731       } else if ( _roll_offset_deg < -90 ) {
732         setRollOffset_deg( -90 );
733       }
734     }
735   }
736   recalc();
737   _cameraGroup->update(_absolute_view_pos.osg(), mViewOrientation.osg());
738   _cameraGroup->setCameraParameters(get_v_fov(), get_aspect_ratio());
739 }