]> git.mxchange.org Git - flightgear.git/blob - src/Main/viewer.cxx
Allow setting of NED velocities.
[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
32 #include "fg_props.hxx"
33
34 #include <simgear/debug/logstream.hxx>
35 #include <simgear/constants.h>
36 #include <simgear/scene/model/placement.hxx>
37 #include <simgear/math/vector.hxx>
38
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     _damp_sync(0),
68     _damp_roll(0),
69     _damp_pitch(0),
70     _damp_heading(0),
71     _scaling_type(FG_SCALING_MAX),
72     _aspect_ratio(0),
73     _cameraGroup(CameraGroup::getDefault())
74 {
75     _absolute_view_pos = SGVec3d(0, 0, 0);
76     _type = Type;
77     _from_model = from_model;
78     _from_model_index = from_model_index;
79     _at_model = at_model;
80     _at_model_index = at_model_index;
81
82     _internal = internal;
83
84     if (damp_roll > 0.0)
85       _damp_roll = 1.0 / pow(10.0, fabs(damp_roll));
86     if (damp_pitch > 0.0)
87       _damp_pitch = 1.0 / pow(10.0, fabs(damp_pitch));
88     if (damp_heading > 0.0)
89       _damp_heading = 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     _aspect_ratio = 1;
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   _heading_offset_deg = heading_offset_deg;
293 }
294
295 void
296 FGViewer::setGoalRollOffset_deg (double goal_roll_offset_deg)
297 {
298   _dirty = true;
299   _goal_roll_offset_deg = goal_roll_offset_deg;
300 }
301
302 void
303 FGViewer::setGoalPitchOffset_deg (double goal_pitch_offset_deg)
304 {
305   _dirty = true;
306   _goal_pitch_offset_deg = goal_pitch_offset_deg;
307   if ( _goal_pitch_offset_deg < -90 ) {
308     _goal_pitch_offset_deg = -90.0;
309   }
310   if ( _goal_pitch_offset_deg > 90.0 ) {
311     _goal_pitch_offset_deg = 90.0;
312   }
313
314 }
315
316 void
317 FGViewer::setGoalHeadingOffset_deg (double goal_heading_offset_deg)
318 {
319   _dirty = true;
320   _goal_heading_offset_deg = goal_heading_offset_deg;
321   while ( _goal_heading_offset_deg < 0.0 ) {
322     _goal_heading_offset_deg += 360;
323   }
324   while ( _goal_heading_offset_deg > 360 ) {
325     _goal_heading_offset_deg -= 360;
326   }
327 }
328
329 void
330 FGViewer::setOrientationOffsets (double roll_offset_deg, double pitch_offset_deg, double heading_offset_deg)
331 {
332   _dirty = true;
333   _roll_offset_deg = roll_offset_deg;
334   _pitch_offset_deg = pitch_offset_deg;
335   _heading_offset_deg = heading_offset_deg;
336 }
337
338 // recalc() is done every time one of the setters is called (making the 
339 // cached data "dirty") on the next "get".  It calculates all the outputs 
340 // for viewer.
341 void
342 FGViewer::recalc ()
343 {
344   if (_type == FG_LOOKFROM) {
345     recalcLookFrom();
346   } else {
347     recalcLookAt();
348   }
349
350   set_clean();
351 }
352
353 // recalculate for LookFrom view type...
354 void
355 FGViewer::recalcLookFrom ()
356 {
357   // Update location data ...
358   if ( _from_model ) {
359     SGModelPlacement* placement = globals->get_aircraft_model()->get3DModel();
360     _position = placement->getPosition();
361   
362     _heading_deg = placement->getHeadingDeg();
363     _pitch_deg = placement->getPitchDeg();
364     _roll_deg = placement->getRollDeg();
365   }
366
367   double head = _heading_deg;
368   double pitch = _pitch_deg;
369   double roll = _roll_deg;
370   if ( !_from_model ) {
371     // update from our own data...
372     dampEyeData(roll, pitch, head);
373   }
374
375   // The rotation rotating from the earth centerd frame to
376   // the horizontal local OpenGL frame
377   SGQuatd hlOr = SGQuatd::viewHL(_position);
378
379   // the rotation from the horizontal local frame to the basic view orientation
380   SGQuatd hlToBody = SGQuatd::fromYawPitchRollDeg(head, pitch, roll);
381   hlToBody = SGQuatd::simToView(hlToBody);
382
383   // The cartesian position of the basic view coordinate
384   SGVec3d position = SGVec3d::fromGeod(_position);
385   // the rotation offset, don't know why heading is negative here ...
386   SGQuatd viewOffsetOr = SGQuatd::simToView(
387     SGQuatd::fromYawPitchRollDeg(-_heading_offset_deg, _pitch_offset_deg,
388                                  _roll_offset_deg));
389
390   // Compute the eyepoints orientation and position
391   // wrt the earth centered frame - that is global coorinates
392   SGQuatd ec2body = hlOr*hlToBody;
393   _absolute_view_pos = position + ec2body.backTransform(_offset_m);
394   mViewOrientation = ec2body*viewOffsetOr;
395 }
396
397 void
398 FGViewer::recalcLookAt ()
399 {
400   // The geodetic position of our target to look at
401   if ( _at_model ) {
402     SGModelPlacement* placement = globals->get_aircraft_model()->get3DModel();
403     _target = placement->getPosition();
404     _target_heading_deg = placement->getHeadingDeg();
405     _target_pitch_deg = placement->getPitchDeg();
406     _target_roll_deg = placement->getRollDeg();
407   } else {
408     // if not model then calculate our own target position...
409     dampEyeData(_target_roll_deg, _target_pitch_deg, _target_heading_deg);
410
411   }
412
413   SGQuatd geodTargetOr = SGQuatd::fromYawPitchRollDeg(_target_heading_deg,
414                                                       _target_pitch_deg,
415                                                       _target_roll_deg);
416   SGQuatd geodTargetHlOr = SGQuatd::fromLonLat(_target);
417
418
419   if ( _from_model ) {
420     SGModelPlacement* placement = globals->get_aircraft_model()->get3DModel();
421     _position = placement->getPosition();
422     _heading_deg = placement->getHeadingDeg();
423     _pitch_deg = placement->getPitchDeg();
424     _roll_deg = placement->getRollDeg();
425   } else {
426     // update from our own data, just the rotation here...
427     dampEyeData(_roll_deg, _pitch_deg, _heading_deg);
428   }
429   SGQuatd geodEyeOr = SGQuatd::fromYawPitchRollDeg(_heading_deg,
430                                                    _pitch_deg,
431                                                    _roll_deg);
432   SGQuatd geodEyeHlOr = SGQuatd::fromLonLat(_position);
433
434   // the rotation offset, don't know why heading is negative here ...
435   SGQuatd eyeOffsetOr =
436     SGQuatd::fromYawPitchRollDeg(-_heading_offset_deg + 180, _pitch_offset_deg,
437                                  _roll_offset_deg);
438
439   // Offsets to the eye position
440   SGVec3d eyeOff(-_offset_m.z(), _offset_m.x(), -_offset_m.y());
441   SGQuatd ec2eye = geodEyeHlOr*geodEyeOr;
442   SGVec3d eyeCart = SGVec3d::fromGeod(_position);
443   eyeCart += (ec2eye*eyeOffsetOr).backTransform(eyeOff);
444
445   SGVec3d atCart = SGVec3d::fromGeod(_target);
446
447   // add target offsets to at_position...
448   SGVec3d target_pos_off(-_target_offset_m.z(), _target_offset_m.x(),
449                          -_target_offset_m.y());
450   target_pos_off = (geodTargetHlOr*geodTargetOr).backTransform(target_pos_off);
451   atCart += target_pos_off;
452   eyeCart += target_pos_off;
453
454   // Compute the eyepoints orientation and position
455   // wrt the earth centered frame - that is global coorinates
456   _absolute_view_pos = eyeCart;
457
458   // the view direction
459   SGVec3d dir = normalize(atCart - eyeCart);
460   // the up directon
461   SGVec3d up = ec2eye.backTransform(SGVec3d(0, 0, -1));
462   // rotate dir to the 0-th unit vector
463   // rotate up to 2-th unit vector
464   mViewOrientation = SGQuatd::fromRotateTo(-dir, 2, up, 1);
465 }
466
467 void
468 FGViewer::dampEyeData(double &roll_deg, double &pitch_deg, double &heading_deg)
469 {
470   const double interval = 0.01;
471
472   static FGViewer *last_view = 0;
473   if (last_view != this) {
474     _damp_sync = 0.0;
475     _damped_roll_deg = roll_deg;
476     _damped_pitch_deg = pitch_deg;
477     _damped_heading_deg = heading_deg;
478     last_view = this;
479     return;
480   }
481
482   if (_damp_sync < interval) {
483     if (_damp_roll > 0.0)
484       roll_deg = _damped_roll_deg;
485     if (_damp_pitch > 0.0)
486       pitch_deg = _damped_pitch_deg;
487     if (_damp_heading > 0.0)
488       heading_deg = _damped_heading_deg;
489     return;
490   }
491
492   while (_damp_sync >= interval) {
493     _damp_sync -= interval;
494
495     double d;
496     if (_damp_roll > 0.0) {
497       d = _damped_roll_deg - roll_deg;
498       if (d >= 180.0)
499         _damped_roll_deg -= 360.0;
500       else if (d < -180.0)
501         _damped_roll_deg += 360.0;
502       roll_deg = _damped_roll_deg = roll_deg * _damp_roll + _damped_roll_deg * (1 - _damp_roll);
503     }
504
505     if (_damp_pitch > 0.0) {
506       d = _damped_pitch_deg - pitch_deg;
507       if (d >= 180.0)
508         _damped_pitch_deg -= 360.0;
509       else if (d < -180.0)
510         _damped_pitch_deg += 360.0;
511       pitch_deg = _damped_pitch_deg = pitch_deg * _damp_pitch + _damped_pitch_deg * (1 - _damp_pitch);
512     }
513
514     if (_damp_heading > 0.0) {
515       d = _damped_heading_deg - heading_deg;
516       if (d >= 180.0)
517         _damped_heading_deg -= 360.0;
518       else if (d < -180.0)
519         _damped_heading_deg += 360.0;
520       heading_deg = _damped_heading_deg = heading_deg * _damp_heading + _damped_heading_deg * (1 - _damp_heading);
521     }
522   }
523 }
524
525 double
526 FGViewer::get_h_fov()
527 {
528     switch (_scaling_type) {
529     case FG_SCALING_WIDTH:  // h_fov == fov
530         return _fov_deg;
531     case FG_SCALING_MAX:
532         if (_aspect_ratio < 1.0) {
533             // h_fov == fov
534             return _fov_deg;
535         } else {
536             // v_fov == fov
537             return
538                 atan(tan(_fov_deg/2 * SG_DEGREES_TO_RADIANS)
539                      / (_aspect_ratio*_aspect_ratio_multiplier))
540                 * SG_RADIANS_TO_DEGREES * 2;
541         }
542     default:
543         assert(false);
544     }
545     return 0.0;
546 }
547
548
549
550 double
551 FGViewer::get_v_fov()
552 {
553     switch (_scaling_type) {
554     case FG_SCALING_WIDTH:  // h_fov == fov
555         return 
556             atan(tan(_fov_deg/2 * SG_DEGREES_TO_RADIANS)
557                  * (_aspect_ratio*_aspect_ratio_multiplier))
558             * SG_RADIANS_TO_DEGREES * 2;
559     case FG_SCALING_MAX:
560         if (_aspect_ratio < 1.0) {
561             // h_fov == fov
562             return
563                 atan(tan(_fov_deg/2 * SG_DEGREES_TO_RADIANS)
564                      * (_aspect_ratio*_aspect_ratio_multiplier))
565                 * SG_RADIANS_TO_DEGREES * 2;
566         } else {
567             // v_fov == fov
568             return _fov_deg;
569         }
570     default:
571         assert(false);
572     }
573     return 0.0;
574 }
575
576 void
577 FGViewer::update (double dt)
578 {
579   _damp_sync += dt;
580
581   int i;
582   int dt_ms = int(dt * 1000);
583   for ( i = 0; i < dt_ms; i++ ) {
584     if ( fabs( _goal_heading_offset_deg - _heading_offset_deg) < 1 ) {
585       setHeadingOffset_deg( _goal_heading_offset_deg );
586       break;
587     } else {
588       // move current_view.headingoffset towards
589       // current_view.goal_view_offset
590       if ( _goal_heading_offset_deg > _heading_offset_deg )
591         {
592           if ( _goal_heading_offset_deg - _heading_offset_deg < 180 ){
593             incHeadingOffset_deg( 0.5 );
594           } else {
595             incHeadingOffset_deg( -0.5 );
596           }
597         } else {
598           if ( _heading_offset_deg - _goal_heading_offset_deg < 180 ){
599             incHeadingOffset_deg( -0.5 );
600           } else {
601             incHeadingOffset_deg( 0.5 );
602           }
603         }
604       if ( _heading_offset_deg > 360 ) {
605         incHeadingOffset_deg( -360 );
606       } else if ( _heading_offset_deg < 0 ) {
607         incHeadingOffset_deg( 360 );
608       }
609     }
610   }
611
612   for ( i = 0; i < dt_ms; i++ ) {
613     if ( fabs( _goal_pitch_offset_deg - _pitch_offset_deg ) < 1 ) {
614       setPitchOffset_deg( _goal_pitch_offset_deg );
615       break;
616     } else {
617       // move current_view.pitch_offset_deg towards
618       // current_view.goal_pitch_offset
619       if ( _goal_pitch_offset_deg > _pitch_offset_deg )
620         {
621           incPitchOffset_deg( 1.0 );
622         } else {
623             incPitchOffset_deg( -1.0 );
624         }
625       if ( _pitch_offset_deg > 90 ) {
626         setPitchOffset_deg(90);
627       } else if ( _pitch_offset_deg < -90 ) {
628         setPitchOffset_deg( -90 );
629       }
630     }
631   }
632
633
634   for ( i = 0; i < dt_ms; i++ ) {
635     if ( fabs( _goal_roll_offset_deg - _roll_offset_deg ) < 1 ) {
636       setRollOffset_deg( _goal_roll_offset_deg );
637       break;
638     } else {
639       // move current_view.roll_offset_deg towards
640       // current_view.goal_roll_offset
641       if ( _goal_roll_offset_deg > _roll_offset_deg )
642         {
643           incRollOffset_deg( 1.0 );
644         } else {
645             incRollOffset_deg( -1.0 );
646         }
647       if ( _roll_offset_deg > 90 ) {
648         setRollOffset_deg(90);
649       } else if ( _roll_offset_deg < -90 ) {
650         setRollOffset_deg( -90 );
651       }
652     }
653   }
654   recalc();
655   _cameraGroup->update(_absolute_view_pos.osg(), mViewOrientation.osg());
656   _cameraGroup->setCameraParameters(get_v_fov(), get_aspect_ratio());
657 }