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