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