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