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