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