]> git.mxchange.org Git - flightgear.git/blob - src/Main/viewer.cxx
Silence valgrind error.
[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 = 1;
117     _aspect_ratio_multiplier = aspect_ratio_multiplier;
118     _target_offset_m.x() = target_x_offset_m;
119     _target_offset_m.y() = target_y_offset_m;
120     _target_offset_m.z() = target_z_offset_m;
121     _ground_level_nearplane_m = near_m;
122     // a reasonable guess for init, so that the math doesn't blow up
123 }
124
125
126 // Destructor
127 FGViewer::~FGViewer( void ) {
128 }
129
130 void
131 FGViewer::init ()
132 {
133 }
134
135 void
136 FGViewer::bind ()
137 {
138 }
139
140 void
141 FGViewer::unbind ()
142 {
143 }
144
145 void
146 FGViewer::setType ( int type )
147 {
148   if (type == 0)
149     _type = FG_LOOKFROM;
150   if (type == 1)
151     _type = FG_LOOKAT;
152 }
153
154 void
155 FGViewer::setInternal ( bool internal )
156 {
157   _internal = internal;
158 }
159
160 void
161 FGViewer::setLongitude_deg (double lon_deg)
162 {
163   _dirty = true;
164   _lon_deg = lon_deg;
165 }
166
167 void
168 FGViewer::setLatitude_deg (double lat_deg)
169 {
170   _dirty = true;
171   _lat_deg = lat_deg;
172 }
173
174 void
175 FGViewer::setAltitude_ft (double alt_ft)
176 {
177   _dirty = true;
178   _alt_ft = alt_ft;
179 }
180
181 void
182 FGViewer::setPosition (double lon_deg, double lat_deg, double alt_ft)
183 {
184   _dirty = true;
185   _lon_deg = lon_deg;
186   _lat_deg = lat_deg;
187   _alt_ft = alt_ft;
188 }
189
190 void
191 FGViewer::setTargetLongitude_deg (double lon_deg)
192 {
193   _dirty = true;
194   _target_lon_deg = lon_deg;
195 }
196
197 void
198 FGViewer::setTargetLatitude_deg (double lat_deg)
199 {
200   _dirty = true;
201   _target_lat_deg = lat_deg;
202 }
203
204 void
205 FGViewer::setTargetAltitude_ft (double alt_ft)
206 {
207   _dirty = true;
208   _target_alt_ft = alt_ft;
209 }
210
211 void
212 FGViewer::setTargetPosition (double lon_deg, double lat_deg, double alt_ft)
213 {
214   _dirty = true;
215   _target_lon_deg = lon_deg;
216   _target_lat_deg = lat_deg;
217   _target_alt_ft = alt_ft;
218 }
219
220 void
221 FGViewer::setRoll_deg (double roll_deg)
222 {
223   _dirty = true;
224   _roll_deg = roll_deg;
225 }
226
227 void
228 FGViewer::setPitch_deg (double pitch_deg)
229 {
230   _dirty = true;
231   _pitch_deg = pitch_deg;
232 }
233
234 void
235 FGViewer::setHeading_deg (double heading_deg)
236 {
237   _dirty = true;
238   _heading_deg = heading_deg;
239 }
240
241 void
242 FGViewer::setOrientation (double roll_deg, double pitch_deg, double heading_deg)
243 {
244   _dirty = true;
245   _roll_deg = roll_deg;
246   _pitch_deg = pitch_deg;
247   _heading_deg = heading_deg;
248 }
249
250 void
251 FGViewer::setTargetRoll_deg (double target_roll_deg)
252 {
253   _dirty = true;
254   _target_roll_deg = target_roll_deg;
255 }
256
257 void
258 FGViewer::setTargetPitch_deg (double target_pitch_deg)
259 {
260   _dirty = true;
261   _target_pitch_deg = target_pitch_deg;
262 }
263
264 void
265 FGViewer::setTargetHeading_deg (double target_heading_deg)
266 {
267   _dirty = true;
268   _target_heading_deg = target_heading_deg;
269 }
270
271 void
272 FGViewer::setTargetOrientation (double target_roll_deg, double target_pitch_deg, double target_heading_deg)
273 {
274   _dirty = true;
275   _target_roll_deg = target_roll_deg;
276   _target_pitch_deg = target_pitch_deg;
277   _target_heading_deg = target_heading_deg;
278 }
279
280 void
281 FGViewer::setXOffset_m (double x_offset_m)
282 {
283   _dirty = true;
284   _offset_m.x() = x_offset_m;
285 }
286
287 void
288 FGViewer::setYOffset_m (double y_offset_m)
289 {
290   _dirty = true;
291   _offset_m.y() = y_offset_m;
292 }
293
294 void
295 FGViewer::setZOffset_m (double z_offset_m)
296 {
297   _dirty = true;
298   _offset_m.z() = z_offset_m;
299 }
300
301 void
302 FGViewer::setTargetXOffset_m (double target_x_offset_m)
303 {
304   _dirty = true;
305   _target_offset_m.x() = target_x_offset_m;
306 }
307
308 void
309 FGViewer::setTargetYOffset_m (double target_y_offset_m)
310 {
311   _dirty = true;
312   _target_offset_m.y() = target_y_offset_m;
313 }
314
315 void
316 FGViewer::setTargetZOffset_m (double target_z_offset_m)
317 {
318   _dirty = true;
319   _target_offset_m.z() = target_z_offset_m;
320 }
321
322 void
323 FGViewer::setPositionOffsets (double x_offset_m, double y_offset_m, double z_offset_m)
324 {
325   _dirty = true;
326   _offset_m.x() = x_offset_m;
327   _offset_m.y() = y_offset_m;
328   _offset_m.z() = z_offset_m;
329 }
330
331 void
332 FGViewer::setRollOffset_deg (double roll_offset_deg)
333 {
334   _dirty = true;
335   _roll_offset_deg = roll_offset_deg;
336 }
337
338 void
339 FGViewer::setPitchOffset_deg (double pitch_offset_deg)
340 {
341   _dirty = true;
342   _pitch_offset_deg = pitch_offset_deg;
343 }
344
345 void
346 FGViewer::setHeadingOffset_deg (double heading_offset_deg)
347 {
348   _dirty = true;
349   _heading_offset_deg = heading_offset_deg;
350 }
351
352 void
353 FGViewer::setGoalRollOffset_deg (double goal_roll_offset_deg)
354 {
355   _dirty = true;
356   _goal_roll_offset_deg = goal_roll_offset_deg;
357 }
358
359 void
360 FGViewer::setGoalPitchOffset_deg (double goal_pitch_offset_deg)
361 {
362   _dirty = true;
363   _goal_pitch_offset_deg = goal_pitch_offset_deg;
364   if ( _goal_pitch_offset_deg < -90 ) {
365     _goal_pitch_offset_deg = -90.0;
366   }
367   if ( _goal_pitch_offset_deg > 90.0 ) {
368     _goal_pitch_offset_deg = 90.0;
369   }
370
371 }
372
373 void
374 FGViewer::setGoalHeadingOffset_deg (double goal_heading_offset_deg)
375 {
376   _dirty = true;
377   _goal_heading_offset_deg = goal_heading_offset_deg;
378   while ( _goal_heading_offset_deg < 0.0 ) {
379     _goal_heading_offset_deg += 360;
380   }
381   while ( _goal_heading_offset_deg > 360 ) {
382     _goal_heading_offset_deg -= 360;
383   }
384 }
385
386 void
387 FGViewer::setOrientationOffsets (double roll_offset_deg, double pitch_offset_deg, double heading_offset_deg)
388 {
389   _dirty = true;
390   _roll_offset_deg = roll_offset_deg;
391   _pitch_offset_deg = pitch_offset_deg;
392   _heading_offset_deg = heading_offset_deg;
393 }
394
395 // recalc() is done every time one of the setters is called (making the 
396 // cached data "dirty") on the next "get".  It calculates all the outputs 
397 // for viewer.
398 void
399 FGViewer::recalc ()
400 {
401   if (_type == FG_LOOKFROM) {
402     recalcLookFrom();
403   } else {
404     recalcLookAt();
405   }
406
407   set_clean();
408 }
409
410 // recalculate for LookFrom view type...
411 void
412 FGViewer::recalcLookFrom ()
413 {
414   // Update location data ...
415   double lat, lon, alt, head, pitch, roll;
416   if ( _from_model ) {
417     SGModelPlacement* placement = globals->get_aircraft_model()->get3DModel();
418     lat = placement->getLatitudeDeg();
419     lon = placement->getLongitudeDeg();
420     alt = placement->getElevationFt();
421
422     head = placement->getHeadingDeg();
423     pitch = placement->getPitchDeg();
424     roll = placement->getRollDeg();
425   } else {
426     lat = _lat_deg;
427     lon = _lon_deg;
428     alt = _alt_ft;
429     head = _heading_deg;
430     pitch = _pitch_deg;
431     roll = _roll_deg;
432   }
433   if ( !_from_model ) {
434     // update from our own data...
435     dampEyeData(roll, pitch, head);
436   }
437
438   // The geodetic position of our base view position
439   SGGeod geodPos = SGGeod::fromDegFt(lon, lat, alt);
440   // The rotation rotating from the earth centerd frame to
441   // the horizontal local OpenGL frame
442   SGQuatd hlOr = SGQuatd::viewHL(geodPos);
443
444   // the rotation from the horizontal local frame to the basic view orientation
445   SGQuatd hlToBody = SGQuatd::fromYawPitchRollDeg(head, pitch, roll);
446   hlToBody = SGQuatd::simToView(hlToBody);
447
448   // The cartesian position of the basic view coordinate
449   SGVec3d position = SGVec3d::fromGeod(geodPos);
450   // the rotation offset, don't know why heading is negative here ...
451   SGQuatd viewOffsetOr = SGQuatd::simToView(
452     SGQuatd::fromYawPitchRollDeg(-_heading_offset_deg, _pitch_offset_deg,
453                                  _roll_offset_deg));
454
455   // Compute the eyepoints orientation and position
456   // wrt the earth centered frame - that is global coorinates
457   SGQuatd ec2body = hlOr*hlToBody;
458   _absolute_view_pos = position + ec2body.backTransform(_offset_m);
459   mViewOrientation = ec2body*viewOffsetOr;
460 }
461
462 void
463 FGViewer::recalcLookAt ()
464 {
465   // The geodetic position of our target to look at
466   SGGeod geodTargetPos;
467   SGQuatd geodTargetOr;
468   if ( _at_model ) {
469     SGModelPlacement* placement = globals->get_aircraft_model()->get3DModel();
470     double lat = placement->getLatitudeDeg();
471     double lon = placement->getLongitudeDeg();
472     double alt = placement->getElevationFt();
473     geodTargetPos = SGGeod::fromDegFt(lon, lat, alt);
474
475     double head = placement->getHeadingDeg();
476     double pitch = placement->getPitchDeg();
477     double roll = placement->getRollDeg();
478     geodTargetOr = SGQuatd::fromYawPitchRollDeg(head, pitch, roll);
479   } else {
480     dampEyeData(_target_roll_deg, _target_pitch_deg, _target_heading_deg);
481
482     // if not model then calculate our own target position...
483     geodTargetPos = SGGeod::fromDegFt(_target_lon_deg,
484                                       _target_lat_deg,
485                                       _target_alt_ft);
486     geodTargetOr = SGQuatd::fromYawPitchRollDeg(_target_heading_deg,
487                                                 _target_pitch_deg,
488                                                 _target_roll_deg);
489   }
490   SGQuatd geodTargetHlOr = SGQuatd::fromLonLat(geodTargetPos);
491
492
493   SGGeod geodEyePos;
494   SGQuatd geodEyeOr;
495   if ( _from_model ) {
496     SGModelPlacement* placement = globals->get_aircraft_model()->get3DModel();
497     double lat = placement->getLatitudeDeg();
498     double lon = placement->getLongitudeDeg();
499     double alt = placement->getElevationFt();
500     geodEyePos = SGGeod::fromDegFt(lon, lat, alt);
501
502     double head = placement->getHeadingDeg();
503     double pitch = placement->getPitchDeg();
504     double roll = placement->getRollDeg();
505     geodEyeOr = SGQuatd::fromYawPitchRollDeg(head, pitch, roll);
506   } else {
507     dampEyeData(_roll_deg, _pitch_deg, _heading_deg);
508
509     // update from our own data, just the rotation here...
510     geodEyePos = SGGeod::fromDegFt(_lon_deg, _lat_deg, _alt_ft);
511     geodEyeOr = SGQuatd::fromYawPitchRollDeg(_heading_deg,
512                                              _pitch_deg,
513                                              _roll_deg);
514   }
515   SGQuatd geodEyeHlOr = SGQuatd::fromLonLat(geodEyePos);
516
517   // the rotation offset, don't know why heading is negative here ...
518   SGQuatd eyeOffsetOr =
519     SGQuatd::fromYawPitchRollDeg(-_heading_offset_deg + 180, _pitch_offset_deg,
520                                  _roll_offset_deg);
521
522   // Offsets to the eye position
523   SGVec3d eyeOff(-_offset_m.z(), _offset_m.x(), -_offset_m.y());
524   SGQuatd ec2eye = geodEyeHlOr*geodEyeOr;
525   SGVec3d eyeCart = SGVec3d::fromGeod(geodEyePos);
526   eyeCart += (ec2eye*eyeOffsetOr).backTransform(eyeOff);
527
528   SGVec3d atCart = SGVec3d::fromGeod(geodTargetPos);
529
530   // add target offsets to at_position...
531   SGVec3d target_pos_off(-_target_offset_m.z(), _target_offset_m.x(),
532                          -_target_offset_m.y());
533   target_pos_off = (geodTargetHlOr*geodTargetOr).backTransform(target_pos_off);
534   atCart += target_pos_off;
535   eyeCart += target_pos_off;
536
537   // Compute the eyepoints orientation and position
538   // wrt the earth centered frame - that is global coorinates
539   _absolute_view_pos = eyeCart;
540
541   // the view direction
542   SGVec3d dir = normalize(atCart - eyeCart);
543   // the up directon
544   SGVec3d up = ec2eye.backTransform(SGVec3d(0, 0, -1));
545   // rotate dir to the 0-th unit vector
546   // rotate up to 2-th unit vector
547   mViewOrientation = SGQuatd::fromRotateTo(-dir, 2, up, 1);
548 }
549
550 void
551 FGViewer::dampEyeData(double &roll_deg, double &pitch_deg, double &heading_deg)
552 {
553   const double interval = 0.01;
554
555   static FGViewer *last_view = 0;
556   if (last_view != this) {
557     _damp_sync = 0.0;
558     _damped_roll_deg = roll_deg;
559     _damped_pitch_deg = pitch_deg;
560     _damped_heading_deg = heading_deg;
561     last_view = this;
562     return;
563   }
564
565   if (_damp_sync < interval) {
566     if (_damp_roll > 0.0)
567       roll_deg = _damped_roll_deg;
568     if (_damp_pitch > 0.0)
569       pitch_deg = _damped_pitch_deg;
570     if (_damp_heading > 0.0)
571       heading_deg = _damped_heading_deg;
572     return;
573   }
574
575   while (_damp_sync >= interval) {
576     _damp_sync -= interval;
577
578     double d;
579     if (_damp_roll > 0.0) {
580       d = _damped_roll_deg - roll_deg;
581       if (d >= 180.0)
582         _damped_roll_deg -= 360.0;
583       else if (d < -180.0)
584         _damped_roll_deg += 360.0;
585       roll_deg = _damped_roll_deg = roll_deg * _damp_roll + _damped_roll_deg * (1 - _damp_roll);
586     }
587
588     if (_damp_pitch > 0.0) {
589       d = _damped_pitch_deg - pitch_deg;
590       if (d >= 180.0)
591         _damped_pitch_deg -= 360.0;
592       else if (d < -180.0)
593         _damped_pitch_deg += 360.0;
594       pitch_deg = _damped_pitch_deg = pitch_deg * _damp_pitch + _damped_pitch_deg * (1 - _damp_pitch);
595     }
596
597     if (_damp_heading > 0.0) {
598       d = _damped_heading_deg - heading_deg;
599       if (d >= 180.0)
600         _damped_heading_deg -= 360.0;
601       else if (d < -180.0)
602         _damped_heading_deg += 360.0;
603       heading_deg = _damped_heading_deg = heading_deg * _damp_heading + _damped_heading_deg * (1 - _damp_heading);
604     }
605   }
606 }
607
608 double
609 FGViewer::get_h_fov()
610 {
611     switch (_scaling_type) {
612     case FG_SCALING_WIDTH:  // h_fov == fov
613         return _fov_deg;
614     case FG_SCALING_MAX:
615         if (_aspect_ratio < 1.0) {
616             // h_fov == fov
617             return _fov_deg;
618         } else {
619             // v_fov == fov
620             return
621                 atan(tan(_fov_deg/2 * SG_DEGREES_TO_RADIANS)
622                      / (_aspect_ratio*_aspect_ratio_multiplier))
623                 * SG_RADIANS_TO_DEGREES * 2;
624         }
625     default:
626         assert(false);
627     }
628     return 0.0;
629 }
630
631
632
633 double
634 FGViewer::get_v_fov()
635 {
636     switch (_scaling_type) {
637     case FG_SCALING_WIDTH:  // h_fov == fov
638         return 
639             atan(tan(_fov_deg/2 * SG_DEGREES_TO_RADIANS)
640                  * (_aspect_ratio*_aspect_ratio_multiplier))
641             * SG_RADIANS_TO_DEGREES * 2;
642     case FG_SCALING_MAX:
643         if (_aspect_ratio < 1.0) {
644             // h_fov == fov
645             return
646                 atan(tan(_fov_deg/2 * SG_DEGREES_TO_RADIANS)
647                      * (_aspect_ratio*_aspect_ratio_multiplier))
648                 * SG_RADIANS_TO_DEGREES * 2;
649         } else {
650             // v_fov == fov
651             return _fov_deg;
652         }
653     default:
654         assert(false);
655     }
656     return 0.0;
657 }
658
659 void
660 FGViewer::update (double dt)
661 {
662   _damp_sync += dt;
663
664   int i;
665   int dt_ms = int(dt * 1000);
666   for ( i = 0; i < dt_ms; i++ ) {
667     if ( fabs( _goal_heading_offset_deg - _heading_offset_deg) < 1 ) {
668       setHeadingOffset_deg( _goal_heading_offset_deg );
669       break;
670     } else {
671       // move current_view.headingoffset towards
672       // current_view.goal_view_offset
673       if ( _goal_heading_offset_deg > _heading_offset_deg )
674         {
675           if ( _goal_heading_offset_deg - _heading_offset_deg < 180 ){
676             incHeadingOffset_deg( 0.5 );
677           } else {
678             incHeadingOffset_deg( -0.5 );
679           }
680         } else {
681           if ( _heading_offset_deg - _goal_heading_offset_deg < 180 ){
682             incHeadingOffset_deg( -0.5 );
683           } else {
684             incHeadingOffset_deg( 0.5 );
685           }
686         }
687       if ( _heading_offset_deg > 360 ) {
688         incHeadingOffset_deg( -360 );
689       } else if ( _heading_offset_deg < 0 ) {
690         incHeadingOffset_deg( 360 );
691       }
692     }
693   }
694
695   for ( i = 0; i < dt_ms; i++ ) {
696     if ( fabs( _goal_pitch_offset_deg - _pitch_offset_deg ) < 1 ) {
697       setPitchOffset_deg( _goal_pitch_offset_deg );
698       break;
699     } else {
700       // move current_view.pitch_offset_deg towards
701       // current_view.goal_pitch_offset
702       if ( _goal_pitch_offset_deg > _pitch_offset_deg )
703         {
704           incPitchOffset_deg( 1.0 );
705         } else {
706             incPitchOffset_deg( -1.0 );
707         }
708       if ( _pitch_offset_deg > 90 ) {
709         setPitchOffset_deg(90);
710       } else if ( _pitch_offset_deg < -90 ) {
711         setPitchOffset_deg( -90 );
712       }
713     }
714   }
715
716
717   for ( i = 0; i < dt_ms; i++ ) {
718     if ( fabs( _goal_roll_offset_deg - _roll_offset_deg ) < 1 ) {
719       setRollOffset_deg( _goal_roll_offset_deg );
720       break;
721     } else {
722       // move current_view.roll_offset_deg towards
723       // current_view.goal_roll_offset
724       if ( _goal_roll_offset_deg > _roll_offset_deg )
725         {
726           incRollOffset_deg( 1.0 );
727         } else {
728             incRollOffset_deg( -1.0 );
729         }
730       if ( _roll_offset_deg > 90 ) {
731         setRollOffset_deg(90);
732       } else if ( _roll_offset_deg < -90 ) {
733         setRollOffset_deg( -90 );
734       }
735     }
736   }
737   recalc();
738   _cameraGroup->update(_absolute_view_pos.osg(), mViewOrientation.osg());
739   _cameraGroup->setCameraParameters(get_v_fov(), get_aspect_ratio());
740 }