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