]> git.mxchange.org Git - flightgear.git/blob - src/Viewer/viewer.cxx
Make the view-manager and sound-manager independent.
[flightgear.git] / src / Viewer / 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 "viewer.hxx"
31
32 #include <simgear/compiler.h>
33 #include <cassert>
34
35 #include <simgear/debug/logstream.hxx>
36 #include <simgear/constants.h>
37 #include <simgear/scene/model/placement.hxx>
38 #include <simgear/scene/util/OsgMath.hxx>
39
40 #include <Main/fg_props.hxx>
41 #include <Main/globals.hxx>
42 #include "CameraGroup.hxx"
43
44 using namespace flightgear;
45 \f
46 ////////////////////////////////////////////////////////////////////////
47 // Implementation of FGViewer.
48 ////////////////////////////////////////////////////////////////////////
49
50 // Constructor...
51 FGViewer::FGViewer( fgViewType Type, bool from_model, int from_model_index,
52                     bool at_model, int at_model_index,
53                     double damp_roll, double damp_pitch, double damp_heading,
54                     double x_offset_m, double y_offset_m, double z_offset_m,
55                     double heading_offset_deg, double pitch_offset_deg,
56                     double roll_offset_deg,
57                     double fov_deg, double aspect_ratio_multiplier,
58                     double target_x_offset_m, double target_y_offset_m,
59                     double target_z_offset_m, double near_m, bool internal ):
60     _dirty(true),
61     _roll_deg(0),
62     _pitch_deg(0),
63     _heading_deg(0),
64     _scaling_type(FG_SCALING_MAX),
65     _cameraGroup(CameraGroup::getDefault())
66 {
67     _absolute_view_pos = SGVec3d(0, 0, 0);
68     _type = Type;
69     _from_model = from_model;
70     _from_model_index = from_model_index;
71     _at_model = at_model;
72     _at_model_index = at_model_index;
73
74     _internal = internal;
75
76     _dampFactor = SGVec3d::zeros();
77     _dampOutput = SGVec3d::zeros();
78     _dampTarget = SGVec3d::zeros();
79     
80     if (damp_roll > 0.0)
81       _dampFactor[0] = 1.0 / pow(10.0, fabs(damp_roll));
82     if (damp_pitch > 0.0)
83       _dampFactor[1] = 1.0 / pow(10.0, fabs(damp_pitch));
84     if (damp_heading > 0.0)
85       _dampFactor[2] = 1.0 / pow(10.0, fabs(damp_heading));
86
87     _offset_m.x() = x_offset_m;
88     _offset_m.y() = y_offset_m;
89     _offset_m.z() = z_offset_m;
90     _heading_offset_deg = heading_offset_deg;
91     _pitch_offset_deg = pitch_offset_deg;
92     _roll_offset_deg = roll_offset_deg;
93     _goal_heading_offset_deg = heading_offset_deg;
94     _goal_pitch_offset_deg = pitch_offset_deg;
95     _goal_roll_offset_deg = roll_offset_deg;
96     if (fov_deg > 0) {
97       _fov_deg = fov_deg;
98     } else {
99       _fov_deg = 55;
100     }
101
102     _aspect_ratio_multiplier = aspect_ratio_multiplier;
103     _target_offset_m.x() = target_x_offset_m;
104     _target_offset_m.y() = target_y_offset_m;
105     _target_offset_m.z() = target_z_offset_m;
106     _ground_level_nearplane_m = near_m;
107     // a reasonable guess for init, so that the math doesn't blow up
108 }
109
110
111 // Destructor
112 FGViewer::~FGViewer( void ) {
113 }
114
115 void
116 FGViewer::init ()
117 {
118 }
119
120 void
121 FGViewer::bind ()
122 {
123 }
124
125 void
126 FGViewer::unbind ()
127 {
128 }
129
130 void
131 FGViewer::setType ( int type )
132 {
133   if (type == 0)
134     _type = FG_LOOKFROM;
135   if (type == 1)
136     _type = FG_LOOKAT;
137 }
138
139 void
140 FGViewer::setInternal ( bool internal )
141 {
142   _internal = internal;
143 }
144
145 void
146 FGViewer::setPosition (double lon_deg, double lat_deg, double alt_ft)
147 {
148   _dirty = true;
149   _position = SGGeod::fromDegFt(lon_deg, lat_deg, alt_ft);
150 }
151
152 void
153 FGViewer::setTargetPosition (double lon_deg, double lat_deg, double alt_ft)
154 {
155   _dirty = true;
156   _target = SGGeod::fromDegFt(lon_deg, lat_deg, alt_ft);
157 }
158
159 void
160 FGViewer::setRoll_deg (double roll_deg)
161 {
162   _dirty = true;
163   _roll_deg = roll_deg;
164 }
165
166 void
167 FGViewer::setPitch_deg (double pitch_deg)
168 {
169   _dirty = true;
170   _pitch_deg = pitch_deg;
171 }
172
173 void
174 FGViewer::setHeading_deg (double heading_deg)
175 {
176   _dirty = true;
177   _heading_deg = heading_deg;
178 }
179
180 void
181 FGViewer::setOrientation (double roll_deg, double pitch_deg, double heading_deg)
182 {
183   _dirty = true;
184   _roll_deg = roll_deg;
185   _pitch_deg = pitch_deg;
186   _heading_deg = heading_deg;
187 }
188
189 void
190 FGViewer::setTargetRoll_deg (double target_roll_deg)
191 {
192   _dirty = true;
193   _target_roll_deg = target_roll_deg;
194 }
195
196 void
197 FGViewer::setTargetPitch_deg (double target_pitch_deg)
198 {
199   _dirty = true;
200   _target_pitch_deg = target_pitch_deg;
201 }
202
203 void
204 FGViewer::setTargetHeading_deg (double target_heading_deg)
205 {
206   _dirty = true;
207   _target_heading_deg = target_heading_deg;
208 }
209
210 void
211 FGViewer::setTargetOrientation (double target_roll_deg, double target_pitch_deg, double target_heading_deg)
212 {
213   _dirty = true;
214   _target_roll_deg = target_roll_deg;
215   _target_pitch_deg = target_pitch_deg;
216   _target_heading_deg = target_heading_deg;
217 }
218
219 void
220 FGViewer::setXOffset_m (double x_offset_m)
221 {
222   _dirty = true;
223   _offset_m.x() = x_offset_m;
224 }
225
226 void
227 FGViewer::setYOffset_m (double y_offset_m)
228 {
229   _dirty = true;
230   _offset_m.y() = y_offset_m;
231 }
232
233 void
234 FGViewer::setZOffset_m (double z_offset_m)
235 {
236   _dirty = true;
237   _offset_m.z() = z_offset_m;
238 }
239
240 void
241 FGViewer::setTargetXOffset_m (double target_x_offset_m)
242 {
243   _dirty = true;
244   _target_offset_m.x() = target_x_offset_m;
245 }
246
247 void
248 FGViewer::setTargetYOffset_m (double target_y_offset_m)
249 {
250   _dirty = true;
251   _target_offset_m.y() = target_y_offset_m;
252 }
253
254 void
255 FGViewer::setTargetZOffset_m (double target_z_offset_m)
256 {
257   _dirty = true;
258   _target_offset_m.z() = target_z_offset_m;
259 }
260
261 void
262 FGViewer::setPositionOffsets (double x_offset_m, double y_offset_m, double z_offset_m)
263 {
264   _dirty = true;
265   _offset_m.x() = x_offset_m;
266   _offset_m.y() = y_offset_m;
267   _offset_m.z() = z_offset_m;
268 }
269
270 void
271 FGViewer::setRollOffset_deg (double roll_offset_deg)
272 {
273   _dirty = true;
274   _roll_offset_deg = roll_offset_deg;
275 }
276
277 void
278 FGViewer::setPitchOffset_deg (double pitch_offset_deg)
279 {
280   _dirty = true;
281   _pitch_offset_deg = pitch_offset_deg;
282 }
283
284 void
285 FGViewer::setHeadingOffset_deg (double heading_offset_deg)
286 {
287   _dirty = true;
288   if (_at_model && (_offset_m.x() == 0.0)&&(_offset_m.z() == 0.0))
289   {
290       /* avoid optical effects (e.g. rotating sky) when "looking at" with
291        * heading offsets x==z==0 (view heading cannot change). */
292       _heading_offset_deg = 0.0;
293   }
294   else
295       _heading_offset_deg = heading_offset_deg;
296 }
297
298 void
299 FGViewer::setGoalRollOffset_deg (double goal_roll_offset_deg)
300 {
301   _dirty = true;
302   _goal_roll_offset_deg = goal_roll_offset_deg;
303 }
304
305 void
306 FGViewer::setGoalPitchOffset_deg (double goal_pitch_offset_deg)
307 {
308   _dirty = true;
309   _goal_pitch_offset_deg = goal_pitch_offset_deg;
310   if ( _goal_pitch_offset_deg < -90 ) {
311     _goal_pitch_offset_deg = -90.0;
312   }
313   if ( _goal_pitch_offset_deg > 90.0 ) {
314     _goal_pitch_offset_deg = 90.0;
315   }
316
317 }
318
319 void
320 FGViewer::setGoalHeadingOffset_deg (double goal_heading_offset_deg)
321 {
322   _dirty = true;
323   if (_at_model && (_offset_m.x() == 0.0)&&(_offset_m.z() == 0.0))
324   {
325       /* avoid optical effects (e.g. rotating sky) when "looking at" with
326        * heading offsets x==z==0 (view heading cannot change). */
327       _goal_heading_offset_deg = 0.0;
328       return;
329   }
330   
331   _goal_heading_offset_deg = goal_heading_offset_deg;
332   while ( _goal_heading_offset_deg < 0.0 ) {
333     _goal_heading_offset_deg += 360;
334   }
335   while ( _goal_heading_offset_deg > 360 ) {
336     _goal_heading_offset_deg -= 360;
337   }
338 }
339
340 void
341 FGViewer::setOrientationOffsets (double roll_offset_deg, double pitch_offset_deg, double heading_offset_deg)
342 {
343   _dirty = true;
344   _roll_offset_deg = roll_offset_deg;
345   _pitch_offset_deg = pitch_offset_deg;
346   _heading_offset_deg = heading_offset_deg;
347 }
348
349 // recalc() is done every time one of the setters is called (making the 
350 // cached data "dirty") on the next "get".  It calculates all the outputs 
351 // for viewer.
352 void
353 FGViewer::recalc ()
354 {
355   if (_type == FG_LOOKFROM) {
356     recalcLookFrom();
357   } else {
358     recalcLookAt();
359   }
360
361   set_clean();
362 }
363
364 // recalculate for LookFrom view type...
365 void
366 FGViewer::recalcLookFrom ()
367 {
368   // Update location data ...
369   if ( _from_model ) {
370     _position = globals->get_aircraft_position();
371     globals->get_aircraft_orientation(_heading_deg, _pitch_deg, _roll_deg);
372   }
373
374   double head = _heading_deg;
375   double pitch = _pitch_deg;
376   double roll = _roll_deg;
377   if ( !_from_model ) {
378     // update from our own data...
379     setDampTarget(roll, pitch, head);
380     getDampOutput(roll, pitch, head);
381   }
382
383   // The rotation rotating from the earth centerd frame to
384   // the horizontal local frame
385   SGQuatd hlOr = SGQuatd::fromLonLat(_position);
386
387   // The rotation from the horizontal local frame to the basic view orientation
388   SGQuatd hlToBody = SGQuatd::fromYawPitchRollDeg(head, pitch, roll);
389
390   // The rotation offset, don't know why heading is negative here ...
391   mViewOffsetOr
392       = SGQuatd::fromYawPitchRollDeg(-_heading_offset_deg, _pitch_offset_deg,
393                                      _roll_offset_deg);
394
395   // Compute the eyepoints orientation and position
396   // wrt the earth centered frame - that is global coorinates
397   SGQuatd ec2body = hlOr*hlToBody;
398
399   // The cartesian position of the basic view coordinate
400   SGVec3d position = SGVec3d::fromGeod(_position);
401
402   // This is rotates the x-forward, y-right, z-down coordinate system the where
403   // simulation runs into the OpenGL camera system with x-right, y-up, z-back.
404   SGQuatd q(-0.5, -0.5, 0.5, 0.5);
405
406   _absolute_view_pos = position + (ec2body*q).backTransform(_offset_m);
407   mViewOrientation = ec2body*mViewOffsetOr*q;
408 }
409
410 void
411 FGViewer::recalcLookAt ()
412 {
413   // The geodetic position of our target to look at
414   if ( _at_model ) {
415     _target = globals->get_aircraft_position();
416     globals->get_aircraft_orientation(_target_heading_deg,
417                                       _target_pitch_deg,
418                                       _target_roll_deg);
419   } else {
420     // if not model then calculate our own target position...
421     setDampTarget(_target_roll_deg, _target_pitch_deg, _target_heading_deg);
422     getDampOutput(_target_roll_deg, _target_pitch_deg, _target_heading_deg);
423   }
424
425   SGQuatd geodTargetOr = SGQuatd::fromYawPitchRollDeg(_target_heading_deg,
426                                                    _target_pitch_deg,
427                                                    _target_roll_deg);
428   SGQuatd geodTargetHlOr = SGQuatd::fromLonLat(_target);
429
430
431   if ( _from_model ) {
432     _position = globals->get_aircraft_position();
433     globals->get_aircraft_orientation(_heading_deg, _pitch_deg, _roll_deg);
434   } else {
435     // update from our own data, just the rotation here...
436     setDampTarget(_roll_deg, _pitch_deg, _heading_deg);
437     getDampOutput(_roll_deg, _pitch_deg, _heading_deg);
438   }
439   SGQuatd geodEyeOr = SGQuatd::fromYawPitchRollDeg(_heading_deg, _pitch_deg, _roll_deg);
440   SGQuatd geodEyeHlOr = SGQuatd::fromLonLat(_position);
441
442   // the rotation offset, don't know why heading is negative here ...
443   mViewOffsetOr =
444     SGQuatd::fromYawPitchRollDeg(-_heading_offset_deg + 180, _pitch_offset_deg,
445                                  _roll_offset_deg);
446
447   // Offsets to the eye position
448   SGVec3d eyeOff(-_offset_m.z(), _offset_m.x(), -_offset_m.y());
449   SGQuatd ec2eye = geodEyeHlOr*geodEyeOr;
450   SGVec3d eyeCart = SGVec3d::fromGeod(_position);
451   eyeCart += (ec2eye*mViewOffsetOr).backTransform(eyeOff);
452
453   SGVec3d atCart = SGVec3d::fromGeod(_target);
454
455   // add target offsets to at_position...
456   SGVec3d target_pos_off(-_target_offset_m.z(), _target_offset_m.x(),
457                          -_target_offset_m.y());
458   target_pos_off = (geodTargetHlOr*geodTargetOr).backTransform(target_pos_off);
459   atCart += target_pos_off;
460   eyeCart += target_pos_off;
461
462   // Compute the eyepoints orientation and position
463   // wrt the earth centered frame - that is global coorinates
464   _absolute_view_pos = eyeCart;
465
466   // the view direction
467   SGVec3d dir = normalize(atCart - eyeCart);
468   // the up directon
469   SGVec3d up = ec2eye.backTransform(SGVec3d(0, 0, -1));
470   // rotate -dir to the 2-th unit vector
471   // rotate up to 1-th unit vector
472   // Note that this matches the OpenGL camera coordinate system
473   // with x-right, y-up, z-back.
474   mViewOrientation = SGQuatd::fromRotateTo(-dir, 2, up, 1);
475 }
476
477 void
478 FGViewer::setDampTarget(double roll, double pitch, double heading)
479 {
480   _dampTarget = SGVec3d(roll, pitch, heading);
481 }
482
483 void
484 FGViewer::getDampOutput(double& roll, double& pitch, double& heading)
485 {
486   roll = _dampOutput[0];
487   pitch = _dampOutput[1];
488   heading = _dampOutput[2];
489 }
490
491
492 void
493 FGViewer::updateDampOutput(double dt)
494 {
495   static FGViewer *last_view = 0;
496   if ((last_view != this) || (dt > 1.0)) {
497     _dampOutput = _dampTarget;
498     last_view = this;
499     return;
500   }
501   
502   const double interval = 0.01;
503   while (dt > interval) {
504     
505     for (unsigned int i=0; i<3; ++i) {
506       if (_dampFactor[i] <= 0.0) {
507         // axis is un-damped, set output to target directly
508         _dampOutput[i] = _dampTarget[i];
509         continue;
510       }
511       
512       double d = _dampOutput[i] - _dampTarget[i];
513       if (d > 180.0) {
514         _dampOutput[i] -= 360.0;
515       } else if (d < -180.0) {
516         _dampOutput[i] += 360.0;
517       }
518       
519       _dampOutput[i] = (_dampTarget[i] * _dampFactor[i]) + 
520         (_dampOutput[i] * (1.0 - _dampFactor[i]));
521     } // of axis iteration
522     
523     dt -= interval;
524   } // of dt subdivision by interval
525 }
526
527 double
528 FGViewer::get_h_fov()
529 {
530     double aspectRatio = _cameraGroup->getMasterAspectRatio();
531     switch (_scaling_type) {
532     case FG_SCALING_WIDTH:  // h_fov == fov
533         return _fov_deg;
534     case FG_SCALING_MAX:
535         if (aspectRatio < 1.0) {
536             // h_fov == fov
537             return _fov_deg;
538         } else {
539             // v_fov == fov
540             return
541                 atan(tan(_fov_deg/2 * SG_DEGREES_TO_RADIANS)
542                      / (aspectRatio*_aspect_ratio_multiplier))
543                 * SG_RADIANS_TO_DEGREES * 2;
544         }
545     default:
546         assert(false);
547     }
548     return 0.0;
549 }
550
551
552
553 double
554 FGViewer::get_v_fov()
555 {
556     double aspectRatio = _cameraGroup->getMasterAspectRatio();
557     switch (_scaling_type) {
558     case FG_SCALING_WIDTH:  // h_fov == fov
559         return 
560             atan(tan(_fov_deg/2 * SG_DEGREES_TO_RADIANS)
561                  * (aspectRatio*_aspect_ratio_multiplier))
562             * SG_RADIANS_TO_DEGREES * 2;
563     case FG_SCALING_MAX:
564         if (aspectRatio < 1.0) {
565             // h_fov == fov
566             return
567                 atan(tan(_fov_deg/2 * SG_DEGREES_TO_RADIANS)
568                      * (aspectRatio*_aspect_ratio_multiplier))
569                 * SG_RADIANS_TO_DEGREES * 2;
570         } else {
571             // v_fov == fov
572             return _fov_deg;
573         }
574     default:
575         assert(false);
576     }
577     return 0.0;
578 }
579
580 void
581 FGViewer::update (double dt)
582 {
583   updateDampOutput(dt);
584   
585   int i;
586   int dt_ms = int(dt * 1000);
587   for ( i = 0; i < dt_ms; i++ ) {
588     if ( fabs( _goal_heading_offset_deg - _heading_offset_deg) < 1 ) {
589       setHeadingOffset_deg( _goal_heading_offset_deg );
590       break;
591     } else {
592       // move current_view.headingoffset towards
593       // current_view.goal_view_offset
594       if ( _goal_heading_offset_deg > _heading_offset_deg )
595         {
596           if ( _goal_heading_offset_deg - _heading_offset_deg < 180 ){
597             incHeadingOffset_deg( 0.5 );
598           } else {
599             incHeadingOffset_deg( -0.5 );
600           }
601         } else {
602           if ( _heading_offset_deg - _goal_heading_offset_deg < 180 ){
603             incHeadingOffset_deg( -0.5 );
604           } else {
605             incHeadingOffset_deg( 0.5 );
606           }
607         }
608       if ( _heading_offset_deg > 360 ) {
609         incHeadingOffset_deg( -360 );
610       } else if ( _heading_offset_deg < 0 ) {
611         incHeadingOffset_deg( 360 );
612       }
613     }
614   }
615
616   for ( i = 0; i < dt_ms; i++ ) {
617     if ( fabs( _goal_pitch_offset_deg - _pitch_offset_deg ) < 1 ) {
618       setPitchOffset_deg( _goal_pitch_offset_deg );
619       break;
620     } else {
621       // move current_view.pitch_offset_deg towards
622       // current_view.goal_pitch_offset
623       if ( _goal_pitch_offset_deg > _pitch_offset_deg )
624         {
625           incPitchOffset_deg( 1.0 );
626         } else {
627             incPitchOffset_deg( -1.0 );
628         }
629       if ( _pitch_offset_deg > 90 ) {
630         setPitchOffset_deg(90);
631       } else if ( _pitch_offset_deg < -90 ) {
632         setPitchOffset_deg( -90 );
633       }
634     }
635   }
636
637
638   for ( i = 0; i < dt_ms; i++ ) {
639     if ( fabs( _goal_roll_offset_deg - _roll_offset_deg ) < 1 ) {
640       setRollOffset_deg( _goal_roll_offset_deg );
641       break;
642     } else {
643       // move current_view.roll_offset_deg towards
644       // current_view.goal_roll_offset
645       if ( _goal_roll_offset_deg > _roll_offset_deg )
646         {
647           incRollOffset_deg( 1.0 );
648         } else {
649             incRollOffset_deg( -1.0 );
650         }
651       if ( _roll_offset_deg > 90 ) {
652         setRollOffset_deg(90);
653       } else if ( _roll_offset_deg < -90 ) {
654         setRollOffset_deg( -90 );
655       }
656     }
657   }
658   recalc();
659   if( fgGetBool( "/sim/rendering/draw-otw", true ) ) {
660     _cameraGroup->update(toOsg(_absolute_view_pos), toOsg(mViewOrientation));
661     _cameraGroup->setCameraParameters(get_v_fov(), get_aspect_ratio());
662   }
663 }
664
665 double FGViewer::get_aspect_ratio() const
666 {
667     return _cameraGroup->getMasterAspectRatio();
668 }