]> git.mxchange.org Git - flightgear.git/blob - src/Viewer/viewer.cxx
View can created itself from config properties
[flightgear.git] / src / Viewer / 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 "viewer.hxx"
31
32 #include <simgear/compiler.h>
33 #include <cassert>
34
35 #include <simgear/debug/logstream.hxx>
36 #include <simgear/constants.h>
37 #include <simgear/scene/model/placement.hxx>
38 #include <simgear/scene/util/OsgMath.hxx>
39
40 #include <Main/fg_props.hxx>
41 #include <Main/globals.hxx>
42 #include "CameraGroup.hxx"
43
44 using namespace flightgear;
45 \f
46 ////////////////////////////////////////////////////////////////////////
47 // Implementation of FGViewer.
48 ////////////////////////////////////////////////////////////////////////
49
50 // Constructor...
51 View::View( ViewType Type, bool from_model, int from_model_index,
52                     bool at_model, int at_model_index,
53                     double damp_roll, double damp_pitch, double damp_heading,
54                     double x_offset_m, double y_offset_m, double z_offset_m,
55                     double heading_offset_deg, double pitch_offset_deg,
56                     double roll_offset_deg,
57                     double fov_deg, double aspect_ratio_multiplier,
58                     double target_x_offset_m, double target_y_offset_m,
59                     double target_z_offset_m, double near_m, bool internal ):
60     _dirty(true),
61     _roll_deg(0),
62     _pitch_deg(0),
63     _heading_deg(0),
64     _target_roll_deg(0),
65     _target_pitch_deg(0),
66     _target_heading_deg(0),
67     _scaling_type(FG_SCALING_MAX)
68 {
69     _absolute_view_pos = SGVec3d(0, 0, 0);
70     _type = Type;
71     _from_model = from_model;
72     _from_model_index = from_model_index;
73     _at_model = at_model;
74     _at_model_index = at_model_index;
75
76     _internal = internal;
77
78     _dampFactor = SGVec3d::zeros();
79     _dampOutput = SGVec3d::zeros();
80     _dampTarget = SGVec3d::zeros();
81     
82     if (damp_roll > 0.0)
83       _dampFactor[0] = 1.0 / pow(10.0, fabs(damp_roll));
84     if (damp_pitch > 0.0)
85       _dampFactor[1] = 1.0 / pow(10.0, fabs(damp_pitch));
86     if (damp_heading > 0.0)
87       _dampFactor[2] = 1.0 / pow(10.0, fabs(damp_heading));
88
89     _offset_m.x() = x_offset_m;
90     _offset_m.y() = y_offset_m;
91     _offset_m.z() = z_offset_m;
92     _heading_offset_deg = heading_offset_deg;
93     _pitch_offset_deg = pitch_offset_deg;
94     _roll_offset_deg = roll_offset_deg;
95     _goal_heading_offset_deg = heading_offset_deg;
96     _goal_pitch_offset_deg = pitch_offset_deg;
97     _goal_roll_offset_deg = roll_offset_deg;
98     if (fov_deg > 0) {
99       _fov_deg = fov_deg;
100     } else {
101       _fov_deg = 55;
102     }
103
104     _aspect_ratio_multiplier = aspect_ratio_multiplier;
105     _target_offset_m.x() = target_x_offset_m;
106     _target_offset_m.y() = target_y_offset_m;
107     _target_offset_m.z() = target_z_offset_m;
108     _ground_level_nearplane_m = near_m;
109     // a reasonable guess for init, so that the math doesn't blow up
110 }
111
112 View* View::createFromProperties(SGPropertyNode_ptr config)
113 {
114     double aspect_ratio_multiplier
115         = fgGetDouble("/sim/current-view/aspect-ratio-multiplier");
116
117     // find out if this is an internal view (e.g. in cockpit, low near plane)
118     // FIXME : should be a child of config
119     bool internal = config->getParent()->getBoolValue("internal", false);
120
121     // FIXME:
122     // this is assumed to be an aircraft model...we will need to read
123     // model-from-type as well.
124
125     // find out if this is a model we are looking from...
126     bool from_model = config->getBoolValue("from-model");
127     int from_model_index = config->getIntValue("from-model-idx");
128
129     double x_offset_m = config->getDoubleValue("x-offset-m");
130     double y_offset_m = config->getDoubleValue("y-offset-m");
131     double z_offset_m = config->getDoubleValue("z-offset-m");
132
133     double heading_offset_deg = config->getDoubleValue("heading-offset-deg");
134     config->setDoubleValue("heading-offset-deg", heading_offset_deg);
135     double pitch_offset_deg = config->getDoubleValue("pitch-offset-deg");
136     config->setDoubleValue("pitch-offset-deg", pitch_offset_deg);
137     double roll_offset_deg = config->getDoubleValue("roll-offset-deg");
138     config->setDoubleValue("roll-offset-deg", roll_offset_deg);
139
140     double fov_deg = config->getDoubleValue("default-field-of-view-deg");
141     double near_m = config->getDoubleValue("ground-level-nearplane-m");
142
143     // supporting two types "lookat" = 1 and "lookfrom" = 0
144     const char *type = config->getParent()->getStringValue("type");
145     if (!strcmp(type, "lookat")) {
146         bool at_model = config->getBoolValue("at-model");
147         int at_model_index = config->getIntValue("at-model-idx");
148
149         double damp_roll = config->getDoubleValue("at-model-roll-damping");
150         double damp_pitch = config->getDoubleValue("at-model-pitch-damping");
151         double damp_heading = config->getDoubleValue("at-model-heading-damping");
152
153         double target_x_offset_m = config->getDoubleValue("target-x-offset-m");
154         double target_y_offset_m = config->getDoubleValue("target-y-offset-m");
155         double target_z_offset_m = config->getDoubleValue("target-z-offset-m");
156
157         return new View ( FG_LOOKAT, from_model, from_model_index,
158                            at_model, at_model_index,
159                            damp_roll, damp_pitch, damp_heading,
160                            x_offset_m, y_offset_m,z_offset_m,
161                            heading_offset_deg, pitch_offset_deg,
162                            roll_offset_deg, fov_deg, aspect_ratio_multiplier,
163                            target_x_offset_m, target_y_offset_m,
164                          target_z_offset_m, near_m, internal );
165     } else {
166         return new View ( FG_LOOKFROM, from_model, from_model_index,
167                        false, 0, 0.0, 0.0, 0.0,
168                        x_offset_m, y_offset_m, z_offset_m,
169                        heading_offset_deg, pitch_offset_deg,
170                        roll_offset_deg, fov_deg, aspect_ratio_multiplier,
171                          0, 0, 0, near_m, internal );
172     }
173
174 }
175
176
177 // Destructor
178 View::~View( void ) {
179 }
180
181 void
182 View::init ()
183 {
184 }
185
186 void
187 View::bind ()
188 {
189 }
190
191 void
192 View::unbind ()
193 {
194 }
195
196 void
197 View::setType ( int type )
198 {
199   if (type == 0)
200     _type = FG_LOOKFROM;
201   if (type == 1)
202     _type = FG_LOOKAT;
203 }
204
205 void
206 View::setInternal ( bool internal )
207 {
208   _internal = internal;
209 }
210
211 void
212 View::setPosition (double lon_deg, double lat_deg, double alt_ft)
213 {
214   _dirty = true;
215   _position = SGGeod::fromDegFt(lon_deg, lat_deg, alt_ft);
216 }
217
218 void
219 View::setTargetPosition (double lon_deg, double lat_deg, double alt_ft)
220 {
221   _dirty = true;
222   _target = SGGeod::fromDegFt(lon_deg, lat_deg, alt_ft);
223 }
224
225 void
226 View::setRoll_deg (double roll_deg)
227 {
228   _dirty = true;
229   _roll_deg = roll_deg;
230 }
231
232 void
233 View::setPitch_deg (double pitch_deg)
234 {
235   _dirty = true;
236   _pitch_deg = pitch_deg;
237 }
238
239 void
240 View::setHeading_deg (double heading_deg)
241 {
242   _dirty = true;
243   _heading_deg = heading_deg;
244 }
245
246 void
247 View::setOrientation (double roll_deg, double pitch_deg, double heading_deg)
248 {
249   _dirty = true;
250   _roll_deg = roll_deg;
251   _pitch_deg = pitch_deg;
252   _heading_deg = heading_deg;
253 }
254
255 void
256 View::setTargetRoll_deg (double target_roll_deg)
257 {
258   _dirty = true;
259   _target_roll_deg = target_roll_deg;
260 }
261
262 void
263 View::setTargetPitch_deg (double target_pitch_deg)
264 {
265   _dirty = true;
266   _target_pitch_deg = target_pitch_deg;
267 }
268
269 void
270 View::setTargetHeading_deg (double target_heading_deg)
271 {
272   _dirty = true;
273   _target_heading_deg = target_heading_deg;
274 }
275
276 void
277 View::setTargetOrientation (double target_roll_deg, double target_pitch_deg, double target_heading_deg)
278 {
279   _dirty = true;
280   _target_roll_deg = target_roll_deg;
281   _target_pitch_deg = target_pitch_deg;
282   _target_heading_deg = target_heading_deg;
283 }
284
285 void
286 View::setXOffset_m (double x_offset_m)
287 {
288   _dirty = true;
289   _offset_m.x() = x_offset_m;
290 }
291
292 void
293 View::setYOffset_m (double y_offset_m)
294 {
295   _dirty = true;
296   _offset_m.y() = y_offset_m;
297 }
298
299 void
300 View::setZOffset_m (double z_offset_m)
301 {
302   _dirty = true;
303   _offset_m.z() = z_offset_m;
304 }
305
306 void
307 View::setTargetXOffset_m (double target_x_offset_m)
308 {
309   _dirty = true;
310   _target_offset_m.x() = target_x_offset_m;
311 }
312
313 void
314 View::setTargetYOffset_m (double target_y_offset_m)
315 {
316   _dirty = true;
317   _target_offset_m.y() = target_y_offset_m;
318 }
319
320 void
321 View::setTargetZOffset_m (double target_z_offset_m)
322 {
323   _dirty = true;
324   _target_offset_m.z() = target_z_offset_m;
325 }
326
327 void
328 View::setPositionOffsets (double x_offset_m, double y_offset_m, double z_offset_m)
329 {
330   _dirty = true;
331   _offset_m.x() = x_offset_m;
332   _offset_m.y() = y_offset_m;
333   _offset_m.z() = z_offset_m;
334 }
335
336 void
337 View::setRollOffset_deg (double roll_offset_deg)
338 {
339   _dirty = true;
340   _roll_offset_deg = roll_offset_deg;
341 }
342
343 void
344 View::setPitchOffset_deg (double pitch_offset_deg)
345 {
346   _dirty = true;
347   _pitch_offset_deg = pitch_offset_deg;
348 }
349
350 void
351 View::setHeadingOffset_deg (double heading_offset_deg)
352 {
353   _dirty = true;
354   if (_at_model && (_offset_m.x() == 0.0)&&(_offset_m.z() == 0.0))
355   {
356       /* avoid optical effects (e.g. rotating sky) when "looking at" with
357        * heading offsets x==z==0 (view heading cannot change). */
358       _heading_offset_deg = 0.0;
359   }
360   else
361       _heading_offset_deg = heading_offset_deg;
362 }
363
364 void
365 View::setGoalRollOffset_deg (double goal_roll_offset_deg)
366 {
367   _dirty = true;
368   _goal_roll_offset_deg = goal_roll_offset_deg;
369 }
370
371 void
372 View::setGoalPitchOffset_deg (double goal_pitch_offset_deg)
373 {
374   _dirty = true;
375   _goal_pitch_offset_deg = goal_pitch_offset_deg;
376   /* The angle is set to 1/1000th of a degree from the poles to avoid the
377    * singularity where the azimuthal angle becomes undefined, inducing optical
378    * artefacts.  The arbitrary angle offset is visually unnoticeable while
379    * avoiding any possible floating point truncation artefacts. */
380   if ( _goal_pitch_offset_deg < -89.999 ) {
381     _goal_pitch_offset_deg = -89.999;
382   }
383   if ( _goal_pitch_offset_deg > 89.999 ) {
384     _goal_pitch_offset_deg = 89.999;
385   }
386
387 }
388
389 void
390 View::setGoalHeadingOffset_deg (double goal_heading_offset_deg)
391 {
392   _dirty = true;
393   if (_at_model && (_offset_m.x() == 0.0)&&(_offset_m.z() == 0.0))
394   {
395       /* avoid optical effects (e.g. rotating sky) when "looking at" with
396        * heading offsets x==z==0 (view heading cannot change). */
397       _goal_heading_offset_deg = 0.0;
398       return;
399   }
400   
401   _goal_heading_offset_deg = goal_heading_offset_deg;
402   while ( _goal_heading_offset_deg < 0.0 ) {
403     _goal_heading_offset_deg += 360;
404   }
405   while ( _goal_heading_offset_deg > 360 ) {
406     _goal_heading_offset_deg -= 360;
407   }
408 }
409
410 void
411 View::setOrientationOffsets (double roll_offset_deg, double pitch_offset_deg, double heading_offset_deg)
412 {
413   _dirty = true;
414   _roll_offset_deg = roll_offset_deg;
415   _pitch_offset_deg = pitch_offset_deg;
416   _heading_offset_deg = heading_offset_deg;
417 }
418
419 // recalc() is done every time one of the setters is called (making the 
420 // cached data "dirty") on the next "get".  It calculates all the outputs 
421 // for viewer.
422 void
423 View::recalc ()
424 {
425   if (_type == FG_LOOKFROM) {
426     recalcLookFrom();
427   } else {
428     recalcLookAt();
429   }
430
431   set_clean();
432 }
433
434 // recalculate for LookFrom view type...
435 void
436 View::recalcLookFrom ()
437 {
438   // Update location data ...
439   if ( _from_model ) {
440     _position = globals->get_aircraft_position();
441     globals->get_aircraft_orientation(_heading_deg, _pitch_deg, _roll_deg);
442   }
443
444   double head = _heading_deg;
445   double pitch = _pitch_deg;
446   double roll = _roll_deg;
447   if ( !_from_model ) {
448     // update from our own data...
449     setDampTarget(roll, pitch, head);
450     getDampOutput(roll, pitch, head);
451   }
452
453   // The rotation rotating from the earth centerd frame to
454   // the horizontal local frame
455   SGQuatd hlOr = SGQuatd::fromLonLat(_position);
456
457   // The rotation from the horizontal local frame to the basic view orientation
458   SGQuatd hlToBody = SGQuatd::fromYawPitchRollDeg(head, pitch, roll);
459
460   // The rotation offset, don't know why heading is negative here ...
461   mViewOffsetOr
462       = SGQuatd::fromYawPitchRollDeg(-_heading_offset_deg, _pitch_offset_deg,
463                                      _roll_offset_deg);
464
465   // Compute the eyepoints orientation and position
466   // wrt the earth centered frame - that is global coorinates
467   SGQuatd ec2body = hlOr*hlToBody;
468
469   // The cartesian position of the basic view coordinate
470   SGVec3d position = SGVec3d::fromGeod(_position);
471
472   // This is rotates the x-forward, y-right, z-down coordinate system the where
473   // simulation runs into the OpenGL camera system with x-right, y-up, z-back.
474   SGQuatd q(-0.5, -0.5, 0.5, 0.5);
475
476   _absolute_view_pos = position + (ec2body*q).backTransform(_offset_m);
477   mViewOrientation = ec2body*mViewOffsetOr*q;
478 }
479
480 void
481 View::recalcLookAt ()
482 {
483   // The geodetic position of our target to look at
484   if ( _at_model ) {
485     _target = globals->get_aircraft_position();
486     globals->get_aircraft_orientation(_target_heading_deg,
487                                       _target_pitch_deg,
488                                       _target_roll_deg);
489   } else {
490     // if not model then calculate our own target position...
491     setDampTarget(_target_roll_deg, _target_pitch_deg, _target_heading_deg);
492     getDampOutput(_target_roll_deg, _target_pitch_deg, _target_heading_deg);
493   }
494
495   SGQuatd geodTargetOr = SGQuatd::fromYawPitchRollDeg(_target_heading_deg,
496                                                    _target_pitch_deg,
497                                                    _target_roll_deg);
498   SGQuatd geodTargetHlOr = SGQuatd::fromLonLat(_target);
499
500
501   if ( _from_model ) {
502     _position = globals->get_aircraft_position();
503     globals->get_aircraft_orientation(_heading_deg, _pitch_deg, _roll_deg);
504   } else {
505     // update from our own data, just the rotation here...
506     setDampTarget(_roll_deg, _pitch_deg, _heading_deg);
507     getDampOutput(_roll_deg, _pitch_deg, _heading_deg);
508   }
509   SGQuatd geodEyeOr = SGQuatd::fromYawPitchRollDeg(_heading_deg, _pitch_deg, _roll_deg);
510   SGQuatd geodEyeHlOr = SGQuatd::fromLonLat(_position);
511
512   // the rotation offset, don't know why heading is negative here ...
513   mViewOffsetOr =
514     SGQuatd::fromYawPitchRollDeg(-_heading_offset_deg + 180, _pitch_offset_deg,
515                                  _roll_offset_deg);
516
517   // Offsets to the eye position
518   SGVec3d eyeOff(-_offset_m.z(), _offset_m.x(), -_offset_m.y());
519   SGQuatd ec2eye = geodEyeHlOr*geodEyeOr;
520   SGVec3d eyeCart = SGVec3d::fromGeod(_position);
521   eyeCart += (ec2eye*mViewOffsetOr).backTransform(eyeOff);
522
523   SGVec3d atCart = SGVec3d::fromGeod(_target);
524
525   // add target offsets to at_position...
526   SGVec3d target_pos_off(-_target_offset_m.z(), _target_offset_m.x(),
527                          -_target_offset_m.y());
528   target_pos_off = (geodTargetHlOr*geodTargetOr).backTransform(target_pos_off);
529   atCart += target_pos_off;
530   eyeCart += target_pos_off;
531
532   // Compute the eyepoints orientation and position
533   // wrt the earth centered frame - that is global coorinates
534   _absolute_view_pos = eyeCart;
535
536   // the view direction
537   SGVec3d dir = normalize(atCart - eyeCart);
538   // the up directon
539   SGVec3d up = ec2eye.backTransform(SGVec3d(0, 0, -1));
540   // rotate -dir to the 2-th unit vector
541   // rotate up to 1-th unit vector
542   // Note that this matches the OpenGL camera coordinate system
543   // with x-right, y-up, z-back.
544   mViewOrientation = SGQuatd::fromRotateTo(-dir, 2, up, 1);
545 }
546
547 void
548 View::setDampTarget(double roll, double pitch, double heading)
549 {
550   _dampTarget = SGVec3d(roll, pitch, heading);
551 }
552
553 void
554 View::getDampOutput(double& roll, double& pitch, double& heading)
555 {
556   roll = _dampOutput[0];
557   pitch = _dampOutput[1];
558   heading = _dampOutput[2];
559 }
560
561
562 void
563 View::updateDampOutput(double dt)
564 {
565   static View *last_view = 0;
566   if ((last_view != this) || (dt > 1.0)) {
567     _dampOutput = _dampTarget;
568     last_view = this;
569     return;
570   }
571   
572   const double interval = 0.01;
573   while (dt > interval) {
574     
575     for (unsigned int i=0; i<3; ++i) {
576       if (_dampFactor[i] <= 0.0) {
577         // axis is un-damped, set output to target directly
578         _dampOutput[i] = _dampTarget[i];
579         continue;
580       }
581       
582       double d = _dampOutput[i] - _dampTarget[i];
583       if (d > 180.0) {
584         _dampOutput[i] -= 360.0;
585       } else if (d < -180.0) {
586         _dampOutput[i] += 360.0;
587       }
588       
589       _dampOutput[i] = (_dampTarget[i] * _dampFactor[i]) + 
590         (_dampOutput[i] * (1.0 - _dampFactor[i]));
591     } // of axis iteration
592     
593     dt -= interval;
594   } // of dt subdivision by interval
595 }
596
597 double
598 View::get_h_fov()
599 {
600     double aspectRatio = get_aspect_ratio();
601     switch (_scaling_type) {
602     case FG_SCALING_WIDTH:  // h_fov == fov
603         return _fov_deg;
604     case FG_SCALING_MAX:
605         if (aspectRatio < 1.0) {
606             // h_fov == fov
607             return _fov_deg;
608         } else {
609             // v_fov == fov
610             return
611                 atan(tan(_fov_deg/2 * SG_DEGREES_TO_RADIANS)
612                      / (aspectRatio*_aspect_ratio_multiplier))
613                 * SG_RADIANS_TO_DEGREES * 2;
614         }
615     default:
616         assert(false);
617     }
618     return 0.0;
619 }
620
621
622
623 double
624 View::get_v_fov()
625 {
626     double aspectRatio = get_aspect_ratio();
627     switch (_scaling_type) {
628     case FG_SCALING_WIDTH:  // h_fov == fov
629         return 
630             atan(tan(_fov_deg/2 * SG_DEGREES_TO_RADIANS)
631                  * (aspectRatio*_aspect_ratio_multiplier))
632             * SG_RADIANS_TO_DEGREES * 2;
633     case FG_SCALING_MAX:
634         if (aspectRatio < 1.0) {
635             // h_fov == fov
636             return
637                 atan(tan(_fov_deg/2 * SG_DEGREES_TO_RADIANS)
638                      * (aspectRatio*_aspect_ratio_multiplier))
639                 * SG_RADIANS_TO_DEGREES * 2;
640         } else {
641             // v_fov == fov
642             return _fov_deg;
643         }
644     default:
645         assert(false);
646     }
647     return 0.0;
648 }
649
650 void
651 View::update (double dt)
652 {
653   updateDampOutput(dt);
654   
655   int i;
656   int dt_ms = int(dt * 1000);
657   for ( i = 0; i < dt_ms; i++ ) {
658     if ( fabs( _goal_heading_offset_deg - _heading_offset_deg) < 1 ) {
659       setHeadingOffset_deg( _goal_heading_offset_deg );
660       break;
661     } else {
662       // move current_view.headingoffset towards
663       // current_view.goal_view_offset
664       if ( _goal_heading_offset_deg > _heading_offset_deg )
665         {
666           if ( _goal_heading_offset_deg - _heading_offset_deg < 180 ){
667             incHeadingOffset_deg( 0.5 );
668           } else {
669             incHeadingOffset_deg( -0.5 );
670           }
671         } else {
672           if ( _heading_offset_deg - _goal_heading_offset_deg < 180 ){
673             incHeadingOffset_deg( -0.5 );
674           } else {
675             incHeadingOffset_deg( 0.5 );
676           }
677         }
678       if ( _heading_offset_deg > 360 ) {
679         incHeadingOffset_deg( -360 );
680       } else if ( _heading_offset_deg < 0 ) {
681         incHeadingOffset_deg( 360 );
682       }
683     }
684   }
685
686   for ( i = 0; i < dt_ms; i++ ) {
687     if ( fabs( _goal_pitch_offset_deg - _pitch_offset_deg ) < 1 ) {
688       setPitchOffset_deg( _goal_pitch_offset_deg );
689       break;
690     } else {
691       // move current_view.pitch_offset_deg towards
692       // current_view.goal_pitch_offset
693       if ( _goal_pitch_offset_deg > _pitch_offset_deg )
694         {
695           incPitchOffset_deg( 1.0 );
696         } else {
697             incPitchOffset_deg( -1.0 );
698         }
699       if ( _pitch_offset_deg > 90 ) {
700         setPitchOffset_deg(90);
701       } else if ( _pitch_offset_deg < -90 ) {
702         setPitchOffset_deg( -90 );
703       }
704     }
705   }
706
707
708   for ( i = 0; i < dt_ms; i++ ) {
709     if ( fabs( _goal_roll_offset_deg - _roll_offset_deg ) < 1 ) {
710       setRollOffset_deg( _goal_roll_offset_deg );
711       break;
712     } else {
713       // move current_view.roll_offset_deg towards
714       // current_view.goal_roll_offset
715       if ( _goal_roll_offset_deg > _roll_offset_deg )
716         {
717           incRollOffset_deg( 1.0 );
718         } else {
719             incRollOffset_deg( -1.0 );
720         }
721       if ( _roll_offset_deg > 90 ) {
722         setRollOffset_deg(90);
723       } else if ( _roll_offset_deg < -90 ) {
724         setRollOffset_deg( -90 );
725       }
726     }
727   }
728   recalc();
729 }
730
731 double View::get_aspect_ratio() const
732 {
733     return flightgear::CameraGroup::getDefault()->getMasterAspectRatio();
734 }
735