]> git.mxchange.org Git - flightgear.git/blob - src/Main/viewer.cxx
f9e62dbcf763c6c8d57a70fa1062ef7469375514
[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  - curt@flightgear.org
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., 675 Mass Ave, Cambridge, MA 02139, USA.
23 //
24 // $Id$
25
26
27 #include <simgear/compiler.h>
28
29 #include "fg_props.hxx"
30
31 #ifdef HAVE_CONFIG_H
32 #  include <config.h>
33 #endif
34
35 #include <simgear/debug/logstream.hxx>
36 #include <simgear/constants.h>
37 #include <simgear/math/point3d.hxx>
38 #include <simgear/math/polar3d.hxx>
39 #include <simgear/math/sg_geodesy.hxx>
40
41 #include <Scenery/scenery.hxx>
42 //#include <Main/location.hxx>
43
44 #include <simgear/math/vector.hxx>
45 #include <Main/globals.hxx>
46 #include <Model/acmodel.hxx>
47 #include <Model/model.hxx>
48
49 #include "viewer.hxx"
50
51 \f
52 //////////////////////////////////////////////////////////////////
53 // Norman's Optimized matrix rotators!                          //
54 //////////////////////////////////////////////////////////////////
55
56
57 // Since these are pure rotation matrices we can save some bookwork
58 // by considering them to be 3x3 until the very end -- NHV
59 static void MakeVIEW_OFFSET( sgMat4 dst,
60                       const float angle1, const sgVec3 axis1,
61                       const float angle2, const sgVec3 axis2 )
62 {
63     // make rotmatrix1 from angle and axis
64     float s = (float) sin ( angle1 ) ;
65     float c = (float) cos ( angle1 ) ;
66     float t = SG_ONE - c ;
67
68     sgMat3 mat1;
69     float tmp = t * axis1[0];
70     mat1[0][0] = tmp * axis1[0] + c ;
71     mat1[0][1] = tmp * axis1[1] + s * axis1[2] ;
72     mat1[0][2] = tmp * axis1[2] - s * axis1[1] ;
73
74     tmp = t * axis1[1];
75     mat1[1][0] = tmp * axis1[0] - s * axis1[2] ;
76     mat1[1][1] = tmp * axis1[1] + c ;
77     mat1[1][2] = tmp * axis1[2] + s * axis1[0] ;
78
79     tmp = t * axis1[2];
80     mat1[2][0] = tmp * axis1[0] + s * axis1[1] ;
81     mat1[2][1] = tmp * axis1[1] - s * axis1[0] ;
82     mat1[2][2] = tmp * axis1[2] + c ;
83
84     // make rotmatrix2 from angle and axis
85     s = (float) sin ( angle2 ) ;
86     c = (float) cos ( angle2 ) ;
87     t = SG_ONE - c ;
88
89     sgMat3 mat2;
90     tmp = t * axis2[0];
91     mat2[0][0] = tmp * axis2[0] + c ;
92     mat2[0][1] = tmp * axis2[1] + s * axis2[2] ;
93     mat2[0][2] = tmp * axis2[2] - s * axis2[1] ;
94
95     tmp = t * axis2[1];
96     mat2[1][0] = tmp * axis2[0] - s * axis2[2] ;
97     mat2[1][1] = tmp * axis2[1] + c ;
98     mat2[1][2] = tmp * axis2[2] + s * axis2[0] ;
99
100     tmp = t * axis2[2];
101     mat2[2][0] = tmp * axis2[0] + s * axis2[1] ;
102     mat2[2][1] = tmp * axis2[1] - s * axis2[0] ;
103     mat2[2][2] = tmp * axis2[2] + c ;
104
105     // multiply matrices
106     for ( int j = 0 ; j < 3 ; j++ ) {
107         dst[0][j] = mat2[0][0] * mat1[0][j] +
108                     mat2[0][1] * mat1[1][j] +
109                     mat2[0][2] * mat1[2][j];
110
111         dst[1][j] = mat2[1][0] * mat1[0][j] +
112                     mat2[1][1] * mat1[1][j] +
113                     mat2[1][2] * mat1[2][j];
114
115         dst[2][j] = mat2[2][0] * mat1[0][j] +
116                     mat2[2][1] * mat1[1][j] +
117                     mat2[2][2] * mat1[2][j];
118     }
119     // fill in 4x4 matrix elements
120     dst[0][3] = SG_ZERO; 
121     dst[1][3] = SG_ZERO; 
122     dst[2][3] = SG_ZERO;
123     dst[3][0] = SG_ZERO;
124     dst[3][1] = SG_ZERO;
125     dst[3][2] = SG_ZERO;
126     dst[3][3] = SG_ONE;
127 }
128
129
130 ////////////////////////////////////////////////////////////////////////
131 // Implementation of FGViewer.
132 ////////////////////////////////////////////////////////////////////////
133
134 // Constructor
135 FGViewer::FGViewer( fgViewType Type, bool from_model, int from_model_index, bool at_model, int at_model_index, double x_offset_m, double y_offset_m, double z_offset_m, double near_m ):
136     _scaling_type(FG_SCALING_MAX),
137     _fov_deg(55.0),
138     _dirty(true),
139     _lon_deg(0),
140     _lat_deg(0),
141     _alt_ft(0),
142     _target_lon_deg(0),
143     _target_lat_deg(0),
144     _target_alt_ft(0),
145     _roll_deg(0),
146     _pitch_deg(0),
147     _heading_deg(0),
148     _heading_offset_deg(0),
149     _pitch_offset_deg(0),
150     _roll_offset_deg(0),
151     _goal_heading_offset_deg(0.0),
152     _goal_pitch_offset_deg(0.0)
153 {
154     sgdZeroVec3(_absolute_view_pos);
155     _type = Type;
156     _from_model = from_model;
157     _from_model_index = from_model_index;
158     _at_model = at_model;
159     _at_model_index = at_model_index;
160     _x_offset_m = x_offset_m;
161     _y_offset_m = y_offset_m;
162     _z_offset_m = z_offset_m;
163     _ground_level_nearplane_m = near_m;
164     //a reasonable guess for init, so that the math doesn't blow up
165 }
166
167
168 // Destructor
169 FGViewer::~FGViewer( void ) {
170 }
171
172 void
173 FGViewer::init ()
174 {
175   if ( _from_model )
176     _location = (FGLocation *) globals->get_aircraft_model()->get3DModel()->getFGLocation();
177   else
178     _location = (FGLocation *) new FGLocation;
179
180   if ( _type == FG_LOOKAT ) {
181     if ( _at_model )
182       _target_location = (FGLocation *) globals->get_aircraft_model()->get3DModel()->getFGLocation();
183     else
184       _target_location = (FGLocation *) new FGLocation;
185   }
186 }
187
188 void
189 FGViewer::bind ()
190 {
191 }
192
193 void
194 FGViewer::unbind ()
195 {
196 }
197
198 void
199 FGViewer::setType ( int type )
200 {
201   if (type == 0)
202     _type = FG_LOOKFROM;
203   if (type == 1)
204     _type = FG_LOOKAT;
205 }
206
207 void
208 FGViewer::setLongitude_deg (double lon_deg)
209 {
210   _dirty = true;
211   _lon_deg = lon_deg;
212 }
213
214 void
215 FGViewer::setLatitude_deg (double lat_deg)
216 {
217   _dirty = true;
218   _lat_deg = lat_deg;
219 }
220
221 void
222 FGViewer::setAltitude_ft (double alt_ft)
223 {
224   _dirty = true;
225   _alt_ft = alt_ft;
226 }
227
228 void
229 FGViewer::setPosition (double lon_deg, double lat_deg, double alt_ft)
230 {
231   _dirty = true;
232   _lon_deg = lon_deg;
233   _lat_deg = lat_deg;
234   _alt_ft = alt_ft;
235 }
236
237 void
238 FGViewer::setTargetLongitude_deg (double lon_deg)
239 {
240   _dirty = true;
241   _target_lon_deg = lon_deg;
242 }
243
244 void
245 FGViewer::setTargetLatitude_deg (double lat_deg)
246 {
247   _dirty = true;
248   _target_lat_deg = lat_deg;
249 }
250
251 void
252 FGViewer::setTargetAltitude_ft (double alt_ft)
253 {
254   _dirty = true;
255   _target_alt_ft = alt_ft;
256 }
257
258 void
259 FGViewer::setTargetPosition (double lon_deg, double lat_deg, double alt_ft)
260 {
261   _dirty = true;
262   _target_lon_deg = lon_deg;
263   _target_lat_deg = lat_deg;
264   _target_alt_ft = alt_ft;
265 }
266
267 void
268 FGViewer::setRoll_deg (double roll_deg)
269 {
270   _dirty = true;
271   _roll_deg = roll_deg;
272 }
273
274 void
275 FGViewer::setPitch_deg (double pitch_deg)
276 {
277   _dirty = true;
278   _pitch_deg = pitch_deg;
279 }
280
281 void
282 FGViewer::setHeading_deg (double heading_deg)
283 {
284   _dirty = true;
285   _heading_deg = heading_deg;
286 }
287
288 void
289 FGViewer::setOrientation (double roll_deg, double pitch_deg, double heading_deg)
290 {
291   _dirty = true;
292   _roll_deg = roll_deg;
293   _pitch_deg = pitch_deg;
294   _heading_deg = heading_deg;
295 }
296
297 void
298 FGViewer::setTargetRoll_deg (double target_roll_deg)
299 {
300   _dirty = true;
301   _target_roll_deg = target_roll_deg;
302 }
303
304 void
305 FGViewer::setTargetPitch_deg (double target_pitch_deg)
306 {
307   _dirty = true;
308   _target_pitch_deg = target_pitch_deg;
309 }
310
311 void
312 FGViewer::setTargetHeading_deg (double target_heading_deg)
313 {
314   _dirty = true;
315   _target_heading_deg = target_heading_deg;
316 }
317
318 void
319 FGViewer::setTargetOrientation (double target_roll_deg, double target_pitch_deg, double target_heading_deg)
320 {
321   _dirty = true;
322   _target_roll_deg = target_roll_deg;
323   _target_pitch_deg = target_pitch_deg;
324   _target_heading_deg = target_heading_deg;
325 }
326
327 void
328 FGViewer::setXOffset_m (double x_offset_m)
329 {
330   _dirty = true;
331   _x_offset_m = x_offset_m;
332 }
333
334 void
335 FGViewer::setYOffset_m (double y_offset_m)
336 {
337   _dirty = true;
338   _y_offset_m = y_offset_m;
339 }
340
341 void
342 FGViewer::setZOffset_m (double z_offset_m)
343 {
344   _dirty = true;
345   _z_offset_m = z_offset_m;
346 }
347
348 void
349 FGViewer::setPositionOffsets (double x_offset_m, double y_offset_m, double z_offset_m)
350 {
351   _dirty = true;
352   _x_offset_m = x_offset_m;
353   _y_offset_m = y_offset_m;
354   _z_offset_m = z_offset_m;
355 }
356
357 void
358 FGViewer::setRollOffset_deg (double roll_offset_deg)
359 {
360   _dirty = true;
361   _roll_offset_deg = roll_offset_deg;
362 }
363
364 void
365 FGViewer::setPitchOffset_deg (double pitch_offset_deg)
366 {
367   _dirty = true;
368   _pitch_offset_deg = pitch_offset_deg;
369 }
370
371 void
372 FGViewer::setHeadingOffset_deg (double heading_offset_deg)
373 {
374   _dirty = true;
375   _heading_offset_deg = heading_offset_deg;
376 }
377
378 void
379 FGViewer::setGoalRollOffset_deg (double goal_roll_offset_deg)
380 {
381   _dirty = true;
382   _goal_roll_offset_deg = goal_roll_offset_deg;
383 }
384
385 void
386 FGViewer::setGoalPitchOffset_deg (double goal_pitch_offset_deg)
387 {
388   _dirty = true;
389   _goal_pitch_offset_deg = goal_pitch_offset_deg;
390   if ( _goal_pitch_offset_deg < -90 ) {
391     _goal_pitch_offset_deg = -90.0;
392   }
393   if ( _goal_pitch_offset_deg > 90.0 ) {
394     _goal_pitch_offset_deg = 90.0;
395   }
396
397 }
398
399 void
400 FGViewer::setGoalHeadingOffset_deg (double goal_heading_offset_deg)
401 {
402   _dirty = true;
403   _goal_heading_offset_deg = goal_heading_offset_deg;
404   while ( _goal_heading_offset_deg < 0.0 ) {
405     _goal_heading_offset_deg += 360;
406   }
407   while ( _goal_heading_offset_deg > 360 ) {
408     _goal_heading_offset_deg -= 360;
409   }
410 }
411
412 void
413 FGViewer::setOrientationOffsets (double roll_offset_deg, double pitch_offset_deg, double heading_offset_deg)
414 {
415   _dirty = true;
416   _roll_offset_deg = roll_offset_deg;
417   _pitch_offset_deg = pitch_offset_deg;
418   _heading_offset_deg = heading_offset_deg;
419 }
420
421 double *
422 FGViewer::get_absolute_view_pos () 
423 {
424   if (_dirty)
425     recalc();
426   return _absolute_view_pos;
427 }
428
429 float *
430 FGViewer::getRelativeViewPos () 
431 {
432   if (_dirty)
433     recalc();
434   return _relative_view_pos;
435 }
436
437 float *
438 FGViewer::getZeroElevViewPos () 
439 {
440   if (_dirty)
441     recalc();
442   return _zero_elev_view_pos;
443 }
444
445 void
446 FGViewer::updateFromModelLocation (FGLocation * location)
447 {
448   sgCopyMat4(LOCAL, location->getCachedTransformMatrix());
449   _lon_deg = location->getLongitude_deg();
450   _lat_deg = location->getLatitude_deg();
451   _alt_ft = location->getAltitudeASL_ft();
452   _roll_deg = location->getRoll_deg();
453   _pitch_deg = location->getPitch_deg();
454   _heading_deg = location->getHeading_deg();
455   sgCopyVec3(_zero_elev_view_pos,  location->get_zero_elev());
456   sgCopyVec3(_relative_view_pos, location->get_view_pos());
457   sgdCopyVec3(_absolute_view_pos, location->get_absolute_view_pos());
458   sgCopyMat4(UP, location->getCachedUpMatrix());
459   sgCopyVec3(_world_up, location->get_world_up());
460   // these are the vectors that the sun and moon code like to get...
461   sgCopyVec3(_surface_east, location->get_surface_east());
462   sgCopyVec3(_surface_south, location->get_surface_south());
463 }
464
465 void
466 FGViewer::recalcOurOwnLocation (double lon_deg, double lat_deg, double alt_ft, 
467                         double roll_deg, double pitch_deg, double heading_deg)
468 {
469   // update from our own data...
470   _location->setPosition( lon_deg, lat_deg, alt_ft );
471   _location->setOrientation( roll_deg, pitch_deg, heading_deg );
472   sgCopyMat4(LOCAL, _location->getTransformMatrix());
473   sgCopyVec3(_zero_elev_view_pos,  _location->get_zero_elev());
474   sgCopyVec3(_relative_view_pos, _location->get_view_pos());
475   sgdCopyVec3(_absolute_view_pos, _location->get_absolute_view_pos());
476   // these are the vectors that the sun and moon code like to get...
477   sgCopyVec3(_surface_east, _location->get_surface_east());
478   sgCopyVec3(_surface_south, _location->get_surface_south());
479 }
480
481 // recalc() is done every time one of the setters is called (making the 
482 // cached data "dirty") on the next "get".  It calculates all the outputs 
483 // for viewer.
484 void
485 FGViewer::recalc ()
486 {
487   sgVec3 right, forward;
488   sgMat4 tmpROT;  // temp rotation work matrices
489   sgVec3 eye_pos, at_pos;
490   sgVec3 position_offset; // eye position offsets (xyz)
491
492   // The position vectors originate from the view point or target location
493   // depending on the type of view.
494
495   if (_type == FG_LOOKFROM) {
496     // LOOKFROM mode...
497     if ( _from_model ) {
498       // update or data from model location
499       updateFromModelLocation(_location);
500     } else {
501       // update from our own data...
502       recalcOurOwnLocation( _lon_deg, _lat_deg, _alt_ft, 
503             _roll_deg, _pitch_deg, _heading_deg );
504       // get world up data from just recalced location
505       sgCopyMat4(UP, _location->getUpMatrix());
506       sgCopyVec3(_world_up, _location->get_world_up());
507     }
508
509   } else {
510
511     // LOOKAT mode...
512     if ( _from_model ) {
513       // update or data from model location
514       updateFromModelLocation(_location);
515     } else {
516       // update from our own data, just the rotation here...
517       recalcOurOwnLocation( _lon_deg, _lat_deg, _alt_ft, 
518             _roll_deg, _pitch_deg, _heading_deg );
519       // get world up data from just recalced location
520       sgCopyMat4(UP, _location->getUpMatrix());
521       sgCopyVec3(_world_up, _location->get_world_up());
522     }
523     // save they eye positon...
524     sgCopyVec3(eye_pos,  _location->get_view_pos());
525     // save the eye rotation before getting target values!!!
526     sgCopyMat4(tmpROT, LOCAL);
527
528     if ( _at_model ) {
529       // update or data from model location
530       updateFromModelLocation(_target_location);
531     } else {
532       // if not model then calculate our own target position...
533       recalcOurOwnLocation( _target_lon_deg, _target_lat_deg, _target_alt_ft, 
534             _target_roll_deg, _target_pitch_deg, _target_heading_deg );
535     }
536     // restore the eye rotation (the from position rotation)
537     sgCopyMat4(LOCAL, tmpROT);
538
539   }
540
541   // the coordinates generated by the above "recalcPositionVectors"
542   sgCopyVec3(_zero_elev, _zero_elev_view_pos);
543   sgCopyVec3(_view_pos, _relative_view_pos);
544
545   // FIXME:
546   // Doing this last recalc here for published values...where the airplane is
547   // This should be per aircraft or model (for published values) before
548   // multiple FDM can be done.
549   // This info should come directly from the model (not through viewer), 
550   // because in some cases there is no model directly assigned as a lookfrom
551   // position.  The problem that arises is related to the FDM interface looking
552   // indirectly to the viewer to find the altitude of the aircraft on the runway.
553   //
554   // Note that recalcPositionVectors can be removed from viewer when this 
555   // issue is addressed.
556   //
557   if (!_from_model) {
558     recalcPositionVectors(fgGetDouble("/position/longitude-deg"),
559                         fgGetDouble("/position/latitude-deg"),
560                         fgGetDouble("/position/altitude-ft"));
561   }
562
563   // make sg vectors view up, right and forward vectors from LOCAL
564   sgSetVec3( _view_up, LOCAL[2][0], LOCAL[2][1], LOCAL[2][2] );
565   sgSetVec3( right, LOCAL[1][0], LOCAL[1][1], LOCAL[1][2] );
566   sgSetVec3( forward, -LOCAL[0][0], -LOCAL[0][1], -LOCAL[0][2] );
567
568   if (_type == FG_LOOKAT) {
569
570     // Note that when in "lookat" view the "world up" vector is always applied
571     // to the viewer.  World up is based on verticle at a given lon/lat (see
572     // matrix "UP" above).
573
574     // Orientation Offsets matrix
575     MakeVIEW_OFFSET( VIEW_OFFSET,
576       (_heading_offset_deg -_heading_deg) * SG_DEGREES_TO_RADIANS, _world_up,
577       _pitch_offset_deg * SG_DEGREES_TO_RADIANS, right );
578    
579     // add in the Orientation Offsets here
580     sgSetVec3( position_offset, _x_offset_m, _y_offset_m, _z_offset_m );
581     sgXformVec3( position_offset, position_offset, UP);
582
583     sgXformVec3( position_offset, position_offset, VIEW_OFFSET );
584
585     // add the Position offsets from object to the eye position
586     sgAddVec3( eye_pos, eye_pos, position_offset );
587
588     // at position (what we are looking at)
589     sgCopyVec3( at_pos, _view_pos );
590
591     // Make the VIEW matrix for a "LOOKAT".
592     sgMakeLookAtMat4( VIEW, eye_pos, at_pos, _view_up );
593   }
594
595   if (_type == FG_LOOKFROM) {
596
597     // Note that when in "lookfrom" view the "view up" vector is always applied
598     // to the viewer.  View up is based on verticle of the aircraft itself. (see
599     // "LOCAL" matrix above)
600
601     // Orientation Offsets matrix
602     MakeVIEW_OFFSET( VIEW_OFFSET,
603       _heading_offset_deg  * SG_DEGREES_TO_RADIANS, _view_up,
604       _pitch_offset_deg  * SG_DEGREES_TO_RADIANS, right );
605
606     // Make the VIEW matrix.
607     sgSetVec4(VIEW[0], right[0], right[1], right[2],SG_ZERO);
608     sgSetVec4(VIEW[1], forward[0], forward[1], forward[2],SG_ZERO);
609     sgSetVec4(VIEW[2], _view_up[0], _view_up[1], _view_up[2],SG_ZERO);
610     sgSetVec4(VIEW[3], SG_ZERO, SG_ZERO, SG_ZERO,SG_ONE);
611
612     // rotate matrix to get a matrix to apply Eye Position Offsets
613     sgMat4 VIEW_UP; // L0 forward L1 right L2 up
614     sgCopyVec4(VIEW_UP[0], LOCAL[1]); 
615     sgCopyVec4(VIEW_UP[1], LOCAL[2]);
616     sgCopyVec4(VIEW_UP[2], LOCAL[0]);
617     sgZeroVec4(VIEW_UP[3]);
618
619     // Eye Position Offsets to vector
620     sgSetVec3( position_offset, _x_offset_m, _y_offset_m, _z_offset_m );
621     sgXformVec3( position_offset, position_offset, VIEW_UP);
622
623     // add the offsets including rotations to the translation vector
624     sgAddVec3( _view_pos, position_offset );
625
626     // multiply the OFFSETS (for heading and pitch) into the VIEW
627     sgPostMultMat4(VIEW, VIEW_OFFSET);
628
629     // add the position data to the matrix
630     sgSetVec4(VIEW[3], _view_pos[0], _view_pos[1], _view_pos[2],SG_ONE);
631
632   }
633
634   set_clean();
635 }
636
637 void
638 FGViewer::recalcPositionVectors (double lon_deg, double lat_deg, double alt_ft) const
639 {
640   double sea_level_radius_m;
641   double lat_geoc_rad;
642
643
644                                 // Convert from geodetic to geocentric
645                                 // coordinates.
646   sgGeodToGeoc(lat_deg * SGD_DEGREES_TO_RADIANS,
647                alt_ft * SG_FEET_TO_METER,
648                &sea_level_radius_m,
649                &lat_geoc_rad);
650
651                                 // Calculate the cartesian coordinates
652                                 // of point directly below at sea level.
653                                 // aka Zero Elevation Position
654   Point3D p = Point3D(lon_deg * SG_DEGREES_TO_RADIANS,
655                       lat_geoc_rad,
656                       sea_level_radius_m);
657   Point3D tmp = sgPolarToCart3d(p) - scenery.get_next_center();
658   sgSetVec3(_zero_elev_view_pos, tmp[0], tmp[1], tmp[2]);
659
660                                 // Calculate the absolute view position
661                                 // in fgfs coordinates.
662                                 // aka Absolute View Position
663   p.setz(p.radius() + alt_ft * SG_FEET_TO_METER);
664   tmp = sgPolarToCart3d(p);
665   sgdSetVec3(_absolute_view_pos, tmp[0], tmp[1], tmp[2]);
666
667                                 // Calculate the relative view position
668                                 // from the scenery center.
669                                 // aka Relative View Position
670   sgdVec3 scenery_center;
671   sgdSetVec3(scenery_center,
672              scenery.get_next_center().x(),
673              scenery.get_next_center().y(),
674              scenery.get_next_center().z());
675   sgdVec3 view_pos;
676   sgdSubVec3(view_pos, _absolute_view_pos, scenery_center);
677   sgSetVec3(_relative_view_pos, view_pos);
678
679 }
680
681 double
682 FGViewer::get_h_fov()
683 {
684     switch (_scaling_type) {
685     case FG_SCALING_WIDTH:  // h_fov == fov
686         return _fov_deg;
687     case FG_SCALING_MAX:
688         if (_aspect_ratio < 1.0) {
689             // h_fov == fov
690             return _fov_deg;
691         } else {
692             // v_fov == fov
693             return atan(tan(_fov_deg/2 * SG_DEGREES_TO_RADIANS) / _aspect_ratio) *
694                 SG_RADIANS_TO_DEGREES * 2;
695         }
696     default:
697         assert(false);
698     }
699     return 0.0;
700 }
701
702 double
703 FGViewer::get_v_fov()
704 {
705     switch (_scaling_type) {
706     case FG_SCALING_WIDTH:  // h_fov == fov
707         return atan(tan(_fov_deg/2 * SG_DEGREES_TO_RADIANS) * _aspect_ratio) *
708             SG_RADIANS_TO_DEGREES * 2;
709     case FG_SCALING_MAX:
710         if (_aspect_ratio < 1.0) {
711             // h_fov == fov
712             return atan(tan(_fov_deg/2 * SG_DEGREES_TO_RADIANS) * _aspect_ratio) *
713                 SG_RADIANS_TO_DEGREES * 2;
714         } else {
715             // v_fov == fov
716             return _fov_deg;
717         }
718     default:
719         assert(false);
720     }
721     return 0.0;
722 }
723
724 void
725 FGViewer::update (int dt)
726 {
727   int i;
728   for ( i = 0; i < dt; i++ ) {
729     if ( fabs( _goal_heading_offset_deg - _heading_offset_deg) < 1 ) {
730       setHeadingOffset_deg( _goal_heading_offset_deg );
731       break;
732     } else {
733       // move current_view.headingoffset towards
734       // current_view.goal_view_offset
735       if ( _goal_heading_offset_deg > _heading_offset_deg )
736         {
737           if ( _goal_heading_offset_deg - _heading_offset_deg < 180 ){
738             incHeadingOffset_deg( 0.5 );
739           } else {
740             incHeadingOffset_deg( -0.5 );
741           }
742         } else {
743           if ( _heading_offset_deg - _goal_heading_offset_deg < 180 ){
744             incHeadingOffset_deg( -0.5 );
745           } else {
746             incHeadingOffset_deg( 0.5 );
747           }
748         }
749       if ( _heading_offset_deg > 360 ) {
750         incHeadingOffset_deg( -360 );
751       } else if ( _heading_offset_deg < 0 ) {
752         incHeadingOffset_deg( 360 );
753       }
754     }
755   }
756
757   for ( i = 0; i < dt; i++ ) {
758     if ( fabs( _goal_pitch_offset_deg - _pitch_offset_deg ) < 1 ) {
759       setPitchOffset_deg( _goal_pitch_offset_deg );
760       break;
761     } else {
762       // move current_view.pitch_offset_deg towards
763       // current_view.goal_pitch_offset
764       if ( _goal_pitch_offset_deg > _pitch_offset_deg )
765         {
766           incPitchOffset_deg( 1.0 );
767         } else {
768             incPitchOffset_deg( -1.0 );
769         }
770       if ( _pitch_offset_deg > 90 ) {
771         setPitchOffset_deg(90);
772       } else if ( _pitch_offset_deg < -90 ) {
773         setPitchOffset_deg( -90 );
774       }
775     }
776   }
777 }