]> git.mxchange.org Git - flightgear.git/blob - src/Main/viewer.cxx
Avoid casts if there is a method for the apropriate job.
[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   if ( _from_model ) {
416     SGModelPlacement* placement = globals->get_aircraft_model()->get3DModel();
417     _lat_deg = placement->getLatitudeDeg();
418     _lon_deg = placement->getLongitudeDeg();
419     _alt_ft = placement->getElevationFt();
420
421     _heading_deg = placement->getHeadingDeg();
422     _pitch_deg = placement->getPitchDeg();
423     _roll_deg = placement->getRollDeg();
424   }
425   double lat = _lat_deg;
426   double lon = _lon_deg;
427   double alt = _alt_ft;
428   double head = _heading_deg;
429   double pitch = _pitch_deg;
430   double roll = _roll_deg;
431   if ( !_from_model ) {
432     // update from our own data...
433     dampEyeData(roll, pitch, head);
434   }
435
436   // The geodetic position of our base view position
437   SGGeod geodPos = SGGeod::fromDegFt(lon, lat, alt);
438   // The rotation rotating from the earth centerd frame to
439   // the horizontal local OpenGL frame
440   SGQuatd hlOr = SGQuatd::viewHL(geodPos);
441
442   // the rotation from the horizontal local frame to the basic view orientation
443   SGQuatd hlToBody = SGQuatd::fromYawPitchRollDeg(head, pitch, roll);
444   hlToBody = SGQuatd::simToView(hlToBody);
445
446   // The cartesian position of the basic view coordinate
447   SGVec3d position = SGVec3d::fromGeod(geodPos);
448   // the rotation offset, don't know why heading is negative here ...
449   SGQuatd viewOffsetOr = SGQuatd::simToView(
450     SGQuatd::fromYawPitchRollDeg(-_heading_offset_deg, _pitch_offset_deg,
451                                  _roll_offset_deg));
452
453   // Compute the eyepoints orientation and position
454   // wrt the earth centered frame - that is global coorinates
455   SGQuatd ec2body = hlOr*hlToBody;
456   _absolute_view_pos = position + ec2body.backTransform(_offset_m);
457   mViewOrientation = ec2body*viewOffsetOr;
458 }
459
460 void
461 FGViewer::recalcLookAt ()
462 {
463   // The geodetic position of our target to look at
464   if ( _at_model ) {
465     SGModelPlacement* placement = globals->get_aircraft_model()->get3DModel();
466     _target_lat_deg = placement->getLatitudeDeg();
467     _target_lon_deg = placement->getLongitudeDeg();
468     _target_alt_ft = placement->getElevationFt();
469     _target_heading_deg = placement->getHeadingDeg();
470     _target_pitch_deg = placement->getPitchDeg();
471     _target_roll_deg = placement->getRollDeg();
472   } else {
473     // if not model then calculate our own target position...
474     dampEyeData(_target_roll_deg, _target_pitch_deg, _target_heading_deg);
475
476   }
477   SGGeod geodTargetPos = SGGeod::fromDegFt(_target_lon_deg,
478                                            _target_lat_deg,
479                                            _target_alt_ft);
480   SGQuatd geodTargetOr = SGQuatd::fromYawPitchRollDeg(_target_heading_deg,
481                                                       _target_pitch_deg,
482                                                       _target_roll_deg);
483   SGQuatd geodTargetHlOr = SGQuatd::fromLonLat(geodTargetPos);
484
485
486   if ( _from_model ) {
487     SGModelPlacement* placement = globals->get_aircraft_model()->get3DModel();
488     _lat_deg = placement->getLatitudeDeg();
489     _lon_deg = placement->getLongitudeDeg();
490     _alt_ft = placement->getElevationFt();
491     _heading_deg = placement->getHeadingDeg();
492     _pitch_deg = placement->getPitchDeg();
493     _roll_deg = placement->getRollDeg();
494   } else {
495     // update from our own data, just the rotation here...
496     dampEyeData(_roll_deg, _pitch_deg, _heading_deg);
497   }
498   SGGeod geodEyePos = SGGeod::fromDegFt(_lon_deg, _lat_deg, _alt_ft);
499   SGQuatd geodEyeOr = SGQuatd::fromYawPitchRollDeg(_heading_deg,
500                                                    _pitch_deg,
501                                                    _roll_deg);
502   SGQuatd geodEyeHlOr = SGQuatd::fromLonLat(geodEyePos);
503
504   // the rotation offset, don't know why heading is negative here ...
505   SGQuatd eyeOffsetOr =
506     SGQuatd::fromYawPitchRollDeg(-_heading_offset_deg + 180, _pitch_offset_deg,
507                                  _roll_offset_deg);
508
509   // Offsets to the eye position
510   SGVec3d eyeOff(-_offset_m.z(), _offset_m.x(), -_offset_m.y());
511   SGQuatd ec2eye = geodEyeHlOr*geodEyeOr;
512   SGVec3d eyeCart = SGVec3d::fromGeod(geodEyePos);
513   eyeCart += (ec2eye*eyeOffsetOr).backTransform(eyeOff);
514
515   SGVec3d atCart = SGVec3d::fromGeod(geodTargetPos);
516
517   // add target offsets to at_position...
518   SGVec3d target_pos_off(-_target_offset_m.z(), _target_offset_m.x(),
519                          -_target_offset_m.y());
520   target_pos_off = (geodTargetHlOr*geodTargetOr).backTransform(target_pos_off);
521   atCart += target_pos_off;
522   eyeCart += target_pos_off;
523
524   // Compute the eyepoints orientation and position
525   // wrt the earth centered frame - that is global coorinates
526   _absolute_view_pos = eyeCart;
527
528   // the view direction
529   SGVec3d dir = normalize(atCart - eyeCart);
530   // the up directon
531   SGVec3d up = ec2eye.backTransform(SGVec3d(0, 0, -1));
532   // rotate dir to the 0-th unit vector
533   // rotate up to 2-th unit vector
534   mViewOrientation = SGQuatd::fromRotateTo(-dir, 2, up, 1);
535 }
536
537 void
538 FGViewer::dampEyeData(double &roll_deg, double &pitch_deg, double &heading_deg)
539 {
540   const double interval = 0.01;
541
542   static FGViewer *last_view = 0;
543   if (last_view != this) {
544     _damp_sync = 0.0;
545     _damped_roll_deg = roll_deg;
546     _damped_pitch_deg = pitch_deg;
547     _damped_heading_deg = heading_deg;
548     last_view = this;
549     return;
550   }
551
552   if (_damp_sync < interval) {
553     if (_damp_roll > 0.0)
554       roll_deg = _damped_roll_deg;
555     if (_damp_pitch > 0.0)
556       pitch_deg = _damped_pitch_deg;
557     if (_damp_heading > 0.0)
558       heading_deg = _damped_heading_deg;
559     return;
560   }
561
562   while (_damp_sync >= interval) {
563     _damp_sync -= interval;
564
565     double d;
566     if (_damp_roll > 0.0) {
567       d = _damped_roll_deg - roll_deg;
568       if (d >= 180.0)
569         _damped_roll_deg -= 360.0;
570       else if (d < -180.0)
571         _damped_roll_deg += 360.0;
572       roll_deg = _damped_roll_deg = roll_deg * _damp_roll + _damped_roll_deg * (1 - _damp_roll);
573     }
574
575     if (_damp_pitch > 0.0) {
576       d = _damped_pitch_deg - pitch_deg;
577       if (d >= 180.0)
578         _damped_pitch_deg -= 360.0;
579       else if (d < -180.0)
580         _damped_pitch_deg += 360.0;
581       pitch_deg = _damped_pitch_deg = pitch_deg * _damp_pitch + _damped_pitch_deg * (1 - _damp_pitch);
582     }
583
584     if (_damp_heading > 0.0) {
585       d = _damped_heading_deg - heading_deg;
586       if (d >= 180.0)
587         _damped_heading_deg -= 360.0;
588       else if (d < -180.0)
589         _damped_heading_deg += 360.0;
590       heading_deg = _damped_heading_deg = heading_deg * _damp_heading + _damped_heading_deg * (1 - _damp_heading);
591     }
592   }
593 }
594
595 double
596 FGViewer::get_h_fov()
597 {
598     switch (_scaling_type) {
599     case FG_SCALING_WIDTH:  // h_fov == fov
600         return _fov_deg;
601     case FG_SCALING_MAX:
602         if (_aspect_ratio < 1.0) {
603             // h_fov == fov
604             return _fov_deg;
605         } else {
606             // v_fov == fov
607             return
608                 atan(tan(_fov_deg/2 * SG_DEGREES_TO_RADIANS)
609                      / (_aspect_ratio*_aspect_ratio_multiplier))
610                 * SG_RADIANS_TO_DEGREES * 2;
611         }
612     default:
613         assert(false);
614     }
615     return 0.0;
616 }
617
618
619
620 double
621 FGViewer::get_v_fov()
622 {
623     switch (_scaling_type) {
624     case FG_SCALING_WIDTH:  // h_fov == fov
625         return 
626             atan(tan(_fov_deg/2 * SG_DEGREES_TO_RADIANS)
627                  * (_aspect_ratio*_aspect_ratio_multiplier))
628             * SG_RADIANS_TO_DEGREES * 2;
629     case FG_SCALING_MAX:
630         if (_aspect_ratio < 1.0) {
631             // h_fov == fov
632             return
633                 atan(tan(_fov_deg/2 * SG_DEGREES_TO_RADIANS)
634                      * (_aspect_ratio*_aspect_ratio_multiplier))
635                 * SG_RADIANS_TO_DEGREES * 2;
636         } else {
637             // v_fov == fov
638             return _fov_deg;
639         }
640     default:
641         assert(false);
642     }
643     return 0.0;
644 }
645
646 void
647 FGViewer::update (double dt)
648 {
649   _damp_sync += dt;
650
651   int i;
652   int dt_ms = int(dt * 1000);
653   for ( i = 0; i < dt_ms; i++ ) {
654     if ( fabs( _goal_heading_offset_deg - _heading_offset_deg) < 1 ) {
655       setHeadingOffset_deg( _goal_heading_offset_deg );
656       break;
657     } else {
658       // move current_view.headingoffset towards
659       // current_view.goal_view_offset
660       if ( _goal_heading_offset_deg > _heading_offset_deg )
661         {
662           if ( _goal_heading_offset_deg - _heading_offset_deg < 180 ){
663             incHeadingOffset_deg( 0.5 );
664           } else {
665             incHeadingOffset_deg( -0.5 );
666           }
667         } else {
668           if ( _heading_offset_deg - _goal_heading_offset_deg < 180 ){
669             incHeadingOffset_deg( -0.5 );
670           } else {
671             incHeadingOffset_deg( 0.5 );
672           }
673         }
674       if ( _heading_offset_deg > 360 ) {
675         incHeadingOffset_deg( -360 );
676       } else if ( _heading_offset_deg < 0 ) {
677         incHeadingOffset_deg( 360 );
678       }
679     }
680   }
681
682   for ( i = 0; i < dt_ms; i++ ) {
683     if ( fabs( _goal_pitch_offset_deg - _pitch_offset_deg ) < 1 ) {
684       setPitchOffset_deg( _goal_pitch_offset_deg );
685       break;
686     } else {
687       // move current_view.pitch_offset_deg towards
688       // current_view.goal_pitch_offset
689       if ( _goal_pitch_offset_deg > _pitch_offset_deg )
690         {
691           incPitchOffset_deg( 1.0 );
692         } else {
693             incPitchOffset_deg( -1.0 );
694         }
695       if ( _pitch_offset_deg > 90 ) {
696         setPitchOffset_deg(90);
697       } else if ( _pitch_offset_deg < -90 ) {
698         setPitchOffset_deg( -90 );
699       }
700     }
701   }
702
703
704   for ( i = 0; i < dt_ms; i++ ) {
705     if ( fabs( _goal_roll_offset_deg - _roll_offset_deg ) < 1 ) {
706       setRollOffset_deg( _goal_roll_offset_deg );
707       break;
708     } else {
709       // move current_view.roll_offset_deg towards
710       // current_view.goal_roll_offset
711       if ( _goal_roll_offset_deg > _roll_offset_deg )
712         {
713           incRollOffset_deg( 1.0 );
714         } else {
715             incRollOffset_deg( -1.0 );
716         }
717       if ( _roll_offset_deg > 90 ) {
718         setRollOffset_deg(90);
719       } else if ( _roll_offset_deg < -90 ) {
720         setRollOffset_deg( -90 );
721       }
722     }
723   }
724   recalc();
725   _cameraGroup->update(_absolute_view_pos.osg(), mViewOrientation.osg());
726   _cameraGroup->setCameraParameters(get_v_fov(), get_aspect_ratio());
727 }