]> git.mxchange.org Git - flightgear.git/blob - src/Main/viewer.cxx
41f01e4ad8c846d9dc9188fffdc8cbf2e30e39e2
[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 #include <simgear/scene/model/location.hxx>
41 #include <simgear/scene/model/placement.hxx>
42 #include <simgear/math/vector.hxx>
43
44 #include <Main/globals.hxx>
45 #include <Scenery/scenery.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, 
135                     bool at_model, int at_model_index, double at_model_damping,
136                     double x_offset_m, double y_offset_m, double z_offset_m, 
137                     double heading_offset_deg, double pitch_offset_deg,
138                     double roll_offset_deg, double fov_deg,
139                     double target_x_offset_m, double target_y_offset_m,
140                     double target_z_offset_m, double near_m ):
141     _dirty(true),
142     _lon_deg(0),
143     _lat_deg(0),
144     _alt_ft(0),
145     _target_lon_deg(0),
146     _target_lat_deg(0),
147     _target_alt_ft(0),
148     _roll_deg(0),
149     _pitch_deg(0),
150     _heading_deg(0),
151     _scaling_type(FG_SCALING_MAX)
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
160     _damp = _damp_alt = 0.0;
161     if (at_model_damping > 0.0) {
162       _damp = 1 - 1.0 / pow(10, at_model_damping);
163       _damp_alt = _damp * _damp * _damp * _damp;
164     } else if (at_model_damping < 0.0)
165       _damp = 1 - 1.0 / pow(10, -at_model_damping);
166
167     _x_offset_m = x_offset_m;
168     _y_offset_m = y_offset_m;
169     _z_offset_m = z_offset_m;
170     _heading_offset_deg = heading_offset_deg;
171     _pitch_offset_deg = pitch_offset_deg;
172     _roll_offset_deg = roll_offset_deg;
173     _goal_heading_offset_deg = heading_offset_deg;
174     _goal_pitch_offset_deg = pitch_offset_deg;
175     _goal_roll_offset_deg = roll_offset_deg;
176     if (fov_deg > 0) {
177       _fov_deg = fov_deg;
178     } else {
179       _fov_deg = 55;
180     }
181     _target_x_offset_m = target_x_offset_m;
182     _target_y_offset_m = target_y_offset_m;
183     _target_z_offset_m = target_z_offset_m;
184     _ground_level_nearplane_m = near_m;
185     // a reasonable guess for init, so that the math doesn't blow up
186 }
187
188
189 // Destructor
190 FGViewer::~FGViewer( void ) {
191 }
192
193 void
194 FGViewer::init ()
195 {
196   if ( _from_model )
197     _location = (SGLocation *) globals->get_aircraft_model()->get3DModel()->getSGLocation();
198   else
199     _location = (SGLocation *) new SGLocation;
200
201   if ( _type == FG_LOOKAT ) {
202     if ( _at_model )
203       _target_location = (SGLocation *) globals->get_aircraft_model()->get3DModel()->getSGLocation();
204     else
205       _target_location = (SGLocation *) new SGLocation;
206   }
207 }
208
209 void
210 FGViewer::bind ()
211 {
212 }
213
214 void
215 FGViewer::unbind ()
216 {
217 }
218
219 void
220 FGViewer::setType ( int type )
221 {
222   if (type == 0)
223     _type = FG_LOOKFROM;
224   if (type == 1)
225     _type = FG_LOOKAT;
226 }
227
228 void
229 FGViewer::setLongitude_deg (double lon_deg)
230 {
231   _dirty = true;
232   _lon_deg = lon_deg;
233 }
234
235 void
236 FGViewer::setLatitude_deg (double lat_deg)
237 {
238   _dirty = true;
239   _lat_deg = lat_deg;
240 }
241
242 void
243 FGViewer::setAltitude_ft (double alt_ft)
244 {
245   _dirty = true;
246   _alt_ft = alt_ft;
247 }
248
249 void
250 FGViewer::setPosition (double lon_deg, double lat_deg, double alt_ft)
251 {
252   _dirty = true;
253   _lon_deg = lon_deg;
254   _lat_deg = lat_deg;
255   _alt_ft = alt_ft;
256 }
257
258 void
259 FGViewer::setTargetLongitude_deg (double lon_deg)
260 {
261   _dirty = true;
262   _target_lon_deg = lon_deg;
263 }
264
265 void
266 FGViewer::setTargetLatitude_deg (double lat_deg)
267 {
268   _dirty = true;
269   _target_lat_deg = lat_deg;
270 }
271
272 void
273 FGViewer::setTargetAltitude_ft (double alt_ft)
274 {
275   _dirty = true;
276   _target_alt_ft = alt_ft;
277 }
278
279 void
280 FGViewer::setTargetPosition (double lon_deg, double lat_deg, double alt_ft)
281 {
282   _dirty = true;
283   _target_lon_deg = lon_deg;
284   _target_lat_deg = lat_deg;
285   _target_alt_ft = alt_ft;
286 }
287
288 void
289 FGViewer::setRoll_deg (double roll_deg)
290 {
291   _dirty = true;
292   _roll_deg = roll_deg;
293 }
294
295 void
296 FGViewer::setPitch_deg (double pitch_deg)
297 {
298   _dirty = true;
299   _pitch_deg = pitch_deg;
300 }
301
302 void
303 FGViewer::setHeading_deg (double heading_deg)
304 {
305   _dirty = true;
306   _heading_deg = heading_deg;
307 }
308
309 void
310 FGViewer::setOrientation (double roll_deg, double pitch_deg, double heading_deg)
311 {
312   _dirty = true;
313   _roll_deg = roll_deg;
314   _pitch_deg = pitch_deg;
315   _heading_deg = heading_deg;
316 }
317
318 void
319 FGViewer::setTargetRoll_deg (double target_roll_deg)
320 {
321   _dirty = true;
322   _target_roll_deg = target_roll_deg;
323 }
324
325 void
326 FGViewer::setTargetPitch_deg (double target_pitch_deg)
327 {
328   _dirty = true;
329   _target_pitch_deg = target_pitch_deg;
330 }
331
332 void
333 FGViewer::setTargetHeading_deg (double target_heading_deg)
334 {
335   _dirty = true;
336   _target_heading_deg = target_heading_deg;
337 }
338
339 void
340 FGViewer::setTargetOrientation (double target_roll_deg, double target_pitch_deg, double target_heading_deg)
341 {
342   _dirty = true;
343   _target_roll_deg = target_roll_deg;
344   _target_pitch_deg = target_pitch_deg;
345   _target_heading_deg = target_heading_deg;
346 }
347
348 void
349 FGViewer::setXOffset_m (double x_offset_m)
350 {
351   _dirty = true;
352   _x_offset_m = x_offset_m;
353 }
354
355 void
356 FGViewer::setYOffset_m (double y_offset_m)
357 {
358   _dirty = true;
359   _y_offset_m = y_offset_m;
360 }
361
362 void
363 FGViewer::setZOffset_m (double z_offset_m)
364 {
365   _dirty = true;
366   _z_offset_m = z_offset_m;
367 }
368
369 void
370 FGViewer::setTargetXOffset_m (double target_x_offset_m)
371 {
372   _dirty = true;
373   _target_x_offset_m = target_x_offset_m;
374 }
375
376 void
377 FGViewer::setTargetYOffset_m (double target_y_offset_m)
378 {
379   _dirty = true;
380   _target_y_offset_m = target_y_offset_m;
381 }
382
383 void
384 FGViewer::setTargetZOffset_m (double target_z_offset_m)
385 {
386   _dirty = true;
387   _target_z_offset_m = target_z_offset_m;
388 }
389
390 void
391 FGViewer::setPositionOffsets (double x_offset_m, double y_offset_m, double z_offset_m)
392 {
393   _dirty = true;
394   _x_offset_m = x_offset_m;
395   _y_offset_m = y_offset_m;
396   _z_offset_m = z_offset_m;
397 }
398
399 void
400 FGViewer::setRollOffset_deg (double roll_offset_deg)
401 {
402   _dirty = true;
403   _roll_offset_deg = roll_offset_deg;
404 }
405
406 void
407 FGViewer::setPitchOffset_deg (double pitch_offset_deg)
408 {
409   _dirty = true;
410   _pitch_offset_deg = pitch_offset_deg;
411 }
412
413 void
414 FGViewer::setHeadingOffset_deg (double heading_offset_deg)
415 {
416   _dirty = true;
417   _heading_offset_deg = heading_offset_deg;
418 }
419
420 void
421 FGViewer::setGoalRollOffset_deg (double goal_roll_offset_deg)
422 {
423   _dirty = true;
424   _goal_roll_offset_deg = goal_roll_offset_deg;
425 }
426
427 void
428 FGViewer::setGoalPitchOffset_deg (double goal_pitch_offset_deg)
429 {
430   _dirty = true;
431   _goal_pitch_offset_deg = goal_pitch_offset_deg;
432   if ( _goal_pitch_offset_deg < -90 ) {
433     _goal_pitch_offset_deg = -90.0;
434   }
435   if ( _goal_pitch_offset_deg > 90.0 ) {
436     _goal_pitch_offset_deg = 90.0;
437   }
438
439 }
440
441 void
442 FGViewer::setGoalHeadingOffset_deg (double goal_heading_offset_deg)
443 {
444   _dirty = true;
445   _goal_heading_offset_deg = goal_heading_offset_deg;
446   while ( _goal_heading_offset_deg < 0.0 ) {
447     _goal_heading_offset_deg += 360;
448   }
449   while ( _goal_heading_offset_deg > 360 ) {
450     _goal_heading_offset_deg -= 360;
451   }
452 }
453
454 void
455 FGViewer::setOrientationOffsets (double roll_offset_deg, double pitch_offset_deg, double heading_offset_deg)
456 {
457   _dirty = true;
458   _roll_offset_deg = roll_offset_deg;
459   _pitch_offset_deg = pitch_offset_deg;
460   _heading_offset_deg = heading_offset_deg;
461 }
462
463 double *
464 FGViewer::get_absolute_view_pos () 
465 {
466   if (_dirty)
467     recalc();
468   return _absolute_view_pos;
469 }
470
471 float *
472 FGViewer::getRelativeViewPos () 
473 {
474   if (_dirty)
475     recalc();
476   return _relative_view_pos;
477 }
478
479 float *
480 FGViewer::getZeroElevViewPos () 
481 {
482   if (_dirty)
483     recalc();
484   return _zero_elev_view_pos;
485 }
486
487 void
488 FGViewer::updateFromModelLocation (SGLocation * location)
489 {
490   sgCopyMat4(LOCAL, location->getCachedTransformMatrix());
491 }
492
493 void
494 FGViewer::updateAtModelLocation (SGLocation * location)
495 {
496   sgCopyMat4(ATLOCAL, 
497              location->getCachedTransformMatrix());
498 }
499
500 void
501 FGViewer::recalcOurOwnLocation (SGLocation * location, double lon_deg, double lat_deg, double alt_ft, 
502                         double roll_deg, double pitch_deg, double heading_deg)
503 {
504   // update from our own data...
505   if (_damp > 0.0) {
506
507     static FGViewer *last = 0;
508     if (last != this) {
509       _damped_alt_ft = alt_ft;
510       _damped_roll_deg = roll_deg;
511       _damped_pitch_deg = pitch_deg;
512       _damped_heading_deg = heading_deg;
513       last = this;
514     }
515
516     double d;
517     d = _damped_roll_deg - roll_deg;
518     if (d >= 180.0)
519       _damped_roll_deg -= 360.0;
520     else if (d < -180.0)
521       _damped_roll_deg += 360.0;
522
523     d = _damped_pitch_deg - pitch_deg;
524     if (d >= 180.0)
525       _damped_pitch_deg -= 360.0;
526     else if (d < -180.0)
527       _damped_pitch_deg += 360.0;
528
529     d = _damped_heading_deg - heading_deg;
530     if (d >= 180.0)
531       _damped_heading_deg -= 360.0;
532     else if (d < -180.0)
533       _damped_heading_deg += 360.0;
534
535     d = 1.0 - _damp;
536     roll_deg = _damped_roll_deg = roll_deg * d + _damped_roll_deg * _damp;
537     pitch_deg = _damped_pitch_deg = pitch_deg * d + _damped_pitch_deg * _damp;
538     heading_deg = _damped_heading_deg = heading_deg * d + _damped_heading_deg * _damp;
539
540     if (_damp_alt > 0.0)
541       alt_ft = _damped_alt_ft = alt_ft * (1 - _damp_alt) + _damped_alt_ft * _damp_alt;
542   }
543   location->setPosition( lon_deg, lat_deg, alt_ft );
544   location->setOrientation( roll_deg, pitch_deg, heading_deg );
545   sgCopyMat4(LOCAL,
546              location->getTransformMatrix(globals->get_scenery()->get_center()));
547 }
548
549 // recalc() is done every time one of the setters is called (making the 
550 // cached data "dirty") on the next "get".  It calculates all the outputs 
551 // for viewer.
552 void
553 FGViewer::recalc ()
554 {
555   if (_type == FG_LOOKFROM) {
556     recalcLookFrom();
557   } else {
558     recalcLookAt();
559   }
560
561   set_clean();
562 }
563
564 // recalculate for LookFrom view type...
565 void
566 FGViewer::recalcLookFrom ()
567 {
568
569   sgVec3 right, forward;
570   // sgVec3 eye_pos;
571   sgVec3 position_offset; // eye position offsets (xyz)
572
573   // LOOKFROM mode...
574
575   // Update location data...
576   if ( _from_model ) {
577     // update or data from model location
578     updateFromModelLocation(_location);
579   } else {
580     // update from our own data...
581     recalcOurOwnLocation( _location, _lon_deg, _lat_deg, _alt_ft, 
582           _roll_deg, _pitch_deg, _heading_deg );
583   }
584
585   // copy data from location class to local items...
586   copyLocationData();
587
588   // make sg vectors view up, right and forward vectors from LOCAL
589   sgSetVec3( _view_up, LOCAL[2][0], LOCAL[2][1], LOCAL[2][2] );
590   sgSetVec3( right, LOCAL[1][0], LOCAL[1][1], LOCAL[1][2] );
591   sgSetVec3( forward, -LOCAL[0][0], -LOCAL[0][1], -LOCAL[0][2] );
592
593
594   // Note that when in "lookfrom" view the "view up" vector is always applied
595   // to the viewer.  View up is based on verticle of the aircraft itself. (see
596   // "LOCAL" matrix above)
597
598   // Orientation Offsets matrix
599   MakeVIEW_OFFSET( VIEW_OFFSET,
600     _heading_offset_deg  * SG_DEGREES_TO_RADIANS, _view_up,
601     _pitch_offset_deg  * SG_DEGREES_TO_RADIANS, right );
602
603   // Make the VIEW matrix.
604   sgSetVec4(VIEW[0], right[0], right[1], right[2],SG_ZERO);
605   sgSetVec4(VIEW[1], forward[0], forward[1], forward[2],SG_ZERO);
606   sgSetVec4(VIEW[2], _view_up[0], _view_up[1], _view_up[2],SG_ZERO);
607   sgSetVec4(VIEW[3], SG_ZERO, SG_ZERO, SG_ZERO,SG_ONE);
608
609   // rotate model or local matrix to get a matrix to apply Eye Position Offsets
610   sgMat4 VIEW_UP; // L0 forward L1 right L2 up
611   sgCopyVec4(VIEW_UP[0], LOCAL[1]); 
612   sgCopyVec4(VIEW_UP[1], LOCAL[2]);
613   sgCopyVec4(VIEW_UP[2], LOCAL[0]);
614   sgZeroVec4(VIEW_UP[3]);
615
616   // Eye Position Offsets to vector
617   sgSetVec3( position_offset, _x_offset_m, _y_offset_m, _z_offset_m );
618   sgXformVec3( position_offset, position_offset, VIEW_UP);
619
620   // add the offsets including rotations to the translation vector
621   sgAddVec3( _view_pos, position_offset );
622
623   // multiply the OFFSETS (for heading and pitch) into the VIEW
624   sgPostMultMat4(VIEW, VIEW_OFFSET);
625
626   // add the position data to the matrix
627   sgSetVec4(VIEW[3], _view_pos[0], _view_pos[1], _view_pos[2],SG_ONE);
628
629 }
630
631 void
632 FGViewer::recalcLookAt ()
633 {
634
635   sgVec3 right;
636   sgVec3 eye_pos, at_pos;
637   sgVec3 position_offset; // eye position offsets (xyz)
638   sgVec3 target_position_offset; // target position offsets (xyz)
639
640   // The position vectors originate from the view point or target location
641   // depending on the type of view.
642
643   // LOOKAT mode...
644
645   // Update location data for target...
646   if ( _at_model ) {
647     // update or data from model location
648     updateAtModelLocation(_target_location);
649   } else {
650     // if not model then calculate our own target position...
651     recalcOurOwnLocation( _target_location, _target_lon_deg, _target_lat_deg, _target_alt_ft, 
652           _target_roll_deg, _target_pitch_deg, _target_heading_deg );
653   }
654   // calculate the "at" target object positon relative to eye or view's tile center...
655   sgdVec3 dVec3;
656   sgdSetVec3(dVec3,  _location->get_tile_center()[0], _location->get_tile_center()[1], _location->get_tile_center()[2]);
657   sgdSubVec3(dVec3,
658              _target_location->get_absolute_view_pos(globals->get_scenery()->get_center()),
659              dVec3 );
660   sgSetVec3(at_pos, dVec3[0], dVec3[1], dVec3[2]);
661
662   // Update location data for eye...
663   if ( _from_model ) {
664     // update or data from model location
665     updateFromModelLocation(_location);
666   } else {
667     // update from our own data, just the rotation here...
668     recalcOurOwnLocation( _location, _lon_deg, _lat_deg, _alt_ft, 
669           _roll_deg, _pitch_deg, _heading_deg );
670   }
671   // save the eye positon...
672   sgCopyVec3(eye_pos,  _location->get_view_pos());
673
674   // copy data from location class to local items...
675   copyLocationData();
676
677   // make sg vectors view up, right and forward vectors from LOCAL
678   sgSetVec3( _view_up, LOCAL[2][0], LOCAL[2][1], LOCAL[2][2] );
679   sgSetVec3( right, LOCAL[1][0], LOCAL[1][1], LOCAL[1][2] );
680
681   // rotate model or local matrix to get a matrix to apply Eye Position Offsets
682   sgMat4 VIEW_UP; // L0 forward L1 right L2 up
683   sgCopyVec4(VIEW_UP[0], LOCAL[1]); 
684   sgCopyVec4(VIEW_UP[1], LOCAL[2]);
685   sgCopyVec4(VIEW_UP[2], LOCAL[0]);
686   sgZeroVec4(VIEW_UP[3]);
687
688   // get Orientation Offsets matrix
689   MakeVIEW_OFFSET( VIEW_OFFSET,
690     (_heading_offset_deg - 180) * SG_DEGREES_TO_RADIANS, _view_up,
691     _pitch_offset_deg * SG_DEGREES_TO_RADIANS, right );
692
693   // add in the position offsets
694   sgSetVec3( position_offset, _y_offset_m, _x_offset_m, _z_offset_m );
695   sgXformVec3( position_offset, position_offset, VIEW_UP);
696
697   // apply the Orientation offsets
698   sgXformVec3( position_offset, position_offset, VIEW_OFFSET );
699
700   // add the Position offsets from object to the eye position
701   sgAddVec3( eye_pos, eye_pos, position_offset );
702
703   // add target offsets to at_position...
704   sgSetVec3(target_position_offset, _target_z_offset_m,  _target_x_offset_m,
705                                     _target_y_offset_m );
706   sgXformVec3(target_position_offset, target_position_offset, ATLOCAL);
707   sgAddVec3( at_pos, at_pos, target_position_offset);
708
709   sgAddVec3( eye_pos, eye_pos, target_position_offset);
710
711   // Make the VIEW matrix for a "LOOKAT".
712   sgMakeLookAtMat4( VIEW, eye_pos, at_pos, _view_up );
713
714 }
715
716 // copy results from location class to viewer...
717 // FIXME: some of these should be changed to reference directly to SGLocation...
718 void
719 FGViewer::copyLocationData()
720 {
721   // Get our friendly vectors from the eye location...
722   sgCopyVec3(_zero_elev_view_pos,  _location->get_zero_elev());
723   sgCopyVec3(_relative_view_pos, _location->get_view_pos());
724   sgdCopyVec3(_absolute_view_pos,
725               _location->get_absolute_view_pos(globals->get_scenery()->get_center()));
726   sgCopyMat4(UP, _location->getCachedUpMatrix());
727   sgCopyVec3(_world_up, _location->get_world_up());
728   // these are the vectors that the sun and moon code like to get...
729   sgCopyVec3(_surface_east, _location->get_surface_east());
730   sgCopyVec3(_surface_south, _location->get_surface_south());
731
732   // Update viewer's postion data for the eye location...
733   _lon_deg = _location->getLongitude_deg();
734   _lat_deg = _location->getLatitude_deg();
735   _alt_ft = _location->getAltitudeASL_ft();
736   _roll_deg = _location->getRoll_deg();
737   _pitch_deg = _location->getPitch_deg();
738   _heading_deg = _location->getHeading_deg();
739
740   // Update viewer's postion data for the target (at object) location
741   if (_type == FG_LOOKAT) {
742     _target_lon_deg = _target_location->getLongitude_deg();
743     _target_lat_deg = _target_location->getLatitude_deg();
744     _target_alt_ft = _target_location->getAltitudeASL_ft();
745     _target_roll_deg = _target_location->getRoll_deg();
746     _target_pitch_deg = _target_location->getPitch_deg();
747     _target_heading_deg = _target_location->getHeading_deg();
748   }
749
750   // copy coordinates to outputs for viewer...
751   sgCopyVec3(_zero_elev, _zero_elev_view_pos);
752   sgCopyVec3(_view_pos, _relative_view_pos);
753 }
754
755 double
756 FGViewer::get_h_fov()
757 {
758     switch (_scaling_type) {
759     case FG_SCALING_WIDTH:  // h_fov == fov
760         return _fov_deg;
761     case FG_SCALING_MAX:
762         if (_aspect_ratio < 1.0) {
763             // h_fov == fov
764             return _fov_deg;
765         } else {
766             // v_fov == fov
767             return atan(tan(_fov_deg/2 * SG_DEGREES_TO_RADIANS) / _aspect_ratio) *
768                 SG_RADIANS_TO_DEGREES * 2;
769         }
770     default:
771         assert(false);
772     }
773     return 0.0;
774 }
775
776
777
778 double
779 FGViewer::get_v_fov()
780 {
781     switch (_scaling_type) {
782     case FG_SCALING_WIDTH:  // h_fov == fov
783         return atan(tan(_fov_deg/2 * SG_DEGREES_TO_RADIANS) * _aspect_ratio) *
784             SG_RADIANS_TO_DEGREES * 2;
785     case FG_SCALING_MAX:
786         if (_aspect_ratio < 1.0) {
787             // h_fov == fov
788             return atan(tan(_fov_deg/2 * SG_DEGREES_TO_RADIANS) * _aspect_ratio) *
789                 SG_RADIANS_TO_DEGREES * 2;
790         } else {
791             // v_fov == fov
792             return _fov_deg;
793         }
794     default:
795         assert(false);
796     }
797     return 0.0;
798 }
799
800 void
801 FGViewer::update (double dt)
802 {
803   int i;
804   int dt_ms = int(dt * 1000);
805   for ( i = 0; i < dt_ms; i++ ) {
806     if ( fabs( _goal_heading_offset_deg - _heading_offset_deg) < 1 ) {
807       setHeadingOffset_deg( _goal_heading_offset_deg );
808       break;
809     } else {
810       // move current_view.headingoffset towards
811       // current_view.goal_view_offset
812       if ( _goal_heading_offset_deg > _heading_offset_deg )
813         {
814           if ( _goal_heading_offset_deg - _heading_offset_deg < 180 ){
815             incHeadingOffset_deg( 0.5 );
816           } else {
817             incHeadingOffset_deg( -0.5 );
818           }
819         } else {
820           if ( _heading_offset_deg - _goal_heading_offset_deg < 180 ){
821             incHeadingOffset_deg( -0.5 );
822           } else {
823             incHeadingOffset_deg( 0.5 );
824           }
825         }
826       if ( _heading_offset_deg > 360 ) {
827         incHeadingOffset_deg( -360 );
828       } else if ( _heading_offset_deg < 0 ) {
829         incHeadingOffset_deg( 360 );
830       }
831     }
832   }
833
834   for ( i = 0; i < dt_ms; i++ ) {
835     if ( fabs( _goal_pitch_offset_deg - _pitch_offset_deg ) < 1 ) {
836       setPitchOffset_deg( _goal_pitch_offset_deg );
837       break;
838     } else {
839       // move current_view.pitch_offset_deg towards
840       // current_view.goal_pitch_offset
841       if ( _goal_pitch_offset_deg > _pitch_offset_deg )
842         {
843           incPitchOffset_deg( 1.0 );
844         } else {
845             incPitchOffset_deg( -1.0 );
846         }
847       if ( _pitch_offset_deg > 90 ) {
848         setPitchOffset_deg(90);
849       } else if ( _pitch_offset_deg < -90 ) {
850         setPitchOffset_deg( -90 );
851       }
852     }
853   }
854 }