FlightGear next
view.cxx
Go to the documentation of this file.
1// view.cxx -- class for managing a view 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 "view.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#include <simgear/structure/event_mgr.hxx>
40
41#include <Main/fg_props.hxx>
42#include <Main/globals.hxx>
43#include <Scenery/scenery.hxx>
46#include "CameraGroup.hxx"
47
49
50using namespace flightgear;
51
53// Implementation of FGViewer.
55
56
57// Constructor...
58View::View( ViewType Type, bool from_model, int from_model_index,
59 bool at_model, int at_model_index,
60 double damp_roll, double damp_pitch, double damp_heading,
61 double x_offset_m, double y_offset_m, double z_offset_m,
62 double heading_offset_deg, double pitch_offset_deg,
63 double roll_offset_deg,
64 double fov_deg, double aspect_ratio_multiplier,
65 double target_x_offset_m, double target_y_offset_m,
66 double target_z_offset_m, double near_m, bool internal,
67 bool lookat_agl, double lookat_agl_damping,
68 int view_index ):
69 _dirty(true),
70 _roll_deg(0),
71 _pitch_deg(0),
72 _heading_deg(0),
73 _target_roll_deg(0),
74 _target_pitch_deg(0),
75 _target_heading_deg(0),
76 _lookat_agl_damping(lookat_agl_damping /*damping*/, 0 /*min*/, 0 /*max*/),
77 _lookat_agl_ground_altitude(0),
78 _scaling_type(FG_SCALING_MAX)
79{
80 _absolute_view_pos = SGVec3d(0, 0, 0);
81 _type = Type;
82 _from_model = from_model;
83 _from_model_index = from_model_index;
84 _at_model = at_model;
85 _at_model_index = at_model_index;
86
87 _internal = internal;
88 _lookat_agl = lookat_agl;
89 _view_index = view_index;
90
91 _dampFactor = SGVec3d::zeros();
92 _dampOutput = SGVec3d::zeros();
93 _dampTarget = SGVec3d::zeros();
94
95 if (damp_roll > 0.0)
96 _dampFactor[0] = 1.0 / pow(10.0, fabs(damp_roll));
97 if (damp_pitch > 0.0)
98 _dampFactor[1] = 1.0 / pow(10.0, fabs(damp_pitch));
99 if (damp_heading > 0.0)
100 _dampFactor[2] = 1.0 / pow(10.0, fabs(damp_heading));
101
102 _offset_m.x() = x_offset_m;
103 _offset_m.y() = y_offset_m;
104 _offset_m.z() = z_offset_m;
105 _configOffset_m = _offset_m;
106
107 _heading_offset_deg = heading_offset_deg;
108 _pitch_offset_deg = pitch_offset_deg;
109 _roll_offset_deg = roll_offset_deg;
110 _goal_heading_offset_deg = heading_offset_deg;
111 _goal_pitch_offset_deg = pitch_offset_deg;
112 _goal_roll_offset_deg = roll_offset_deg;
113
114 _configHeadingOffsetDeg = heading_offset_deg;
115 _configPitchOffsetDeg = pitch_offset_deg;
116 _configRollOffsetDeg = roll_offset_deg;
117
118 if (fov_deg > 0) {
119 _fov_deg = fov_deg;
120 } else {
121 _fov_deg = 55;
122 }
123
124 _configFOV_deg = _fov_deg;
125
126 _fov_user_deg = _fov_deg;
127
128 _aspect_ratio_multiplier = aspect_ratio_multiplier;
129 _target_offset_m.x() = target_x_offset_m;
130 _target_offset_m.y() = target_y_offset_m;
131 _target_offset_m.z() = target_z_offset_m;
132 _configTargetOffset_m = _target_offset_m;
133
134 _ground_level_nearplane_m = near_m;
135 // a reasonable guess for init, so that the math doesn't blow up
136
137 resetOffsetsAndFOV();
138}
139
140View* View::createFromProperties(SGPropertyNode_ptr config, int view_index)
141{
142 double aspect_ratio_multiplier
143 = fgGetDouble("/sim/current-view/aspect-ratio-multiplier");
144
145 // find out if this is an internal view (e.g. in cockpit, low near plane)
146 // FIXME : should be a child of config
147 bool internal = config->getParent()->getBoolValue("internal", false);
148
149 std::string root = config->getPath();
150 // Will typically be /sim/view[]/config.
151
152 // FIXME:
153 // this is assumed to be an aircraft model...we will need to read
154 // model-from-type as well.
155
156 // find out if this is a model we are looking from...
157 bool from_model = config->getBoolValue("from-model");
158 int from_model_index = config->getIntValue("from-model-idx");
159
160 double x_offset_m = config->getDoubleValue("x-offset-m");
161 double y_offset_m = config->getDoubleValue("y-offset-m");
162 double z_offset_m = config->getDoubleValue("z-offset-m");
163
164 double heading_offset_deg = config->getDoubleValue("heading-offset-deg");
165 // config->setDoubleValue("heading-offset-deg", heading_offset_deg);
166 double pitch_offset_deg = config->getDoubleValue("pitch-offset-deg");
167 // config->setDoubleValue("pitch-offset-deg", pitch_offset_deg);
168 double roll_offset_deg = config->getDoubleValue("roll-offset-deg");
169 // config->setDoubleValue("roll-offset-deg", roll_offset_deg);
170
171 double fov_deg = config->getDoubleValue("default-field-of-view-deg");
172 double near_m = config->getDoubleValue("ground-level-nearplane-m");
173
174 View* v = nullptr;
175 // supporting two types "lookat" = 1 and "lookfrom" = 0
176 std::string type = config->getParent()->getStringValue("type");
177 if (type == "lookat") {
178 bool at_model = config->getBoolValue("at-model");
179 int at_model_index = config->getIntValue("at-model-idx");
180
181 double damp_roll = config->getDoubleValue("at-model-roll-damping");
182 double damp_pitch = config->getDoubleValue("at-model-pitch-damping");
183 double damp_heading = config->getDoubleValue("at-model-heading-damping");
184
185 double target_x_offset_m = config->getDoubleValue("target-x-offset-m");
186 double target_y_offset_m = config->getDoubleValue("target-y-offset-m");
187 double target_z_offset_m = config->getDoubleValue("target-z-offset-m");
188 bool lookat_agl = config->getBoolValue("lookat-agl");
189 double lookat_agl_damping = config->getDoubleValue("lookat-agl-damping");
190
191 v = new View ( FG_LOOKAT, from_model, from_model_index,
192 at_model, at_model_index,
193 damp_roll, damp_pitch, damp_heading,
194 x_offset_m, y_offset_m,z_offset_m,
195 heading_offset_deg, pitch_offset_deg,
196 roll_offset_deg, fov_deg, aspect_ratio_multiplier,
197 target_x_offset_m, target_y_offset_m,
198 target_z_offset_m, near_m, internal, lookat_agl,
199 lookat_agl_damping, view_index );
200 } else {
201 v = new View ( FG_LOOKFROM, from_model, from_model_index,
202 false, 0, 0.0, 0.0, 0.0,
203 x_offset_m, y_offset_m, z_offset_m,
204 heading_offset_deg, pitch_offset_deg,
205 roll_offset_deg, fov_deg, aspect_ratio_multiplier,
206 0, 0, 0, near_m, internal, false, 0.0, view_index );
207 }
208
209 v->_name = config->getParent()->getStringValue("name");
210 v->_typeString = type;
211 v->_configHeadingOffsetDeg = config->getDoubleValue("default-heading-offset-deg");
212 v->_config = config;
213
214 return v;
215}
216
217
218// Destructor
220{
221 _tiedProperties.Untie();
222}
223
224void
226{
227}
228
229void
231{
232 // Perform an immediate recalculation to ensure that the data for the
233 // viewer position is correct.
234 recalc();
235
236 _tiedProperties.setRoot(fgGetNode("/sim/current-view", true));
237 _tiedProperties.Tie("heading-offset-deg", this,
239 &View::setHeadingOffset_deg_property,
240 false /* do not set current property value */);
241
242 fgSetArchivable("/sim/current-view/heading-offset-deg");
243
244 _tiedProperties.Tie("goal-heading-offset-deg", this,
245 &View::getGoalHeadingOffset_deg,
247 false /* do not set current property value */);
248
249 fgSetArchivable("/sim/current-view/goal-heading-offset-deg");
250
251 _tiedProperties.Tie("pitch-offset-deg", this,
253 &View::setPitchOffset_deg_property,
254 false /* do not set current property value */);
255 fgSetArchivable("/sim/current-view/pitch-offset-deg");
256 _tiedProperties.Tie("goal-pitch-offset-deg", this,
257 &View::getGoalPitchOffset_deg,
258 &View::setGoalPitchOffset_deg,
259 false /* do not set current property value */);
260 fgSetArchivable("/sim/current-view/goal-pitch-offset-deg");
261 _tiedProperties.Tie("roll-offset-deg", this,
263 &View::setRollOffset_deg_property,
264 false /* do not set current property value */);
265 fgSetArchivable("/sim/current-view/roll-offset-deg");
266 _tiedProperties.Tie("goal-roll-offset-deg", this,
267 &View::getGoalRollOffset_deg,
268 &View::setGoalRollOffset_deg,
269 false /* do not set current property value */);
270 fgSetArchivable("/sim/current-view/goal-roll-offset-deg");
271
272
273 _tiedProperties.Tie("field-of-view", this,
274 &View::get_fov_user, &View::set_fov_user,
275 false);
276 fgSetArchivable("/sim/current-view/field-of-view");
277
278
279 _tiedProperties.Tie("aspect-ratio-multiplier", this,
280 &View::get_aspect_ratio_multiplier,
281 &View::set_aspect_ratio_multiplier,
282 false);
283
284 _tiedProperties.Tie("ground-level-nearplane-m", this,
285 &View::getNear_m, &View::setNear_m, false);
286 fgSetArchivable("/sim/current-view/ground-level-nearplane-m");
287
288
289 _tiedProperties.Tie("viewer-lon-deg", this, &View::getLon_deg);
290 _tiedProperties.Tie("viewer-lat-deg", this, &View::getLat_deg);
291 _tiedProperties.Tie("viewer-elev-ft", this, &View::getElev_ft);
292
293 _tiedProperties.Tie("x-offset-m", this, &View::getAdjustXOffset_m,
295 _tiedProperties.Tie("y-offset-m", this, &View::getAdjustYOffset_m,
297 _tiedProperties.Tie("z-offset-m", this, &View::getAdjustZOffset_m,
299
300 _tiedProperties.Tie("target-x-offset-m", this, &View::getTargetXOffset_m,
302 _tiedProperties.Tie("target-y-offset-m", this, &View::getTargetYOffset_m,
304 _tiedProperties.Tie("target-z-offset-m", this, &View::getTargetZOffset_m,
306
307// expose various quaternions under the debug/ subtree
308 _tiedProperties.Tie("debug/orientation-w", this, &View::getOrientation_w);
309 _tiedProperties.Tie("debug/orientation-x", this, &View::getOrientation_x);
310 _tiedProperties.Tie("debug/orientation-y", this, &View::getOrientation_y);
311 _tiedProperties.Tie("debug/orientation-z", this, &View::getOrientation_z);
312
313 _tiedProperties.Tie("debug/orientation_offset-w", this,
314 &View::getOrOffset_w);
315 _tiedProperties.Tie("debug/orientation_offset-x", this,
316 &View::getOrOffset_x);
317 _tiedProperties.Tie("debug/orientation_offset-y", this,
318 &View::getOrOffset_y);
319 _tiedProperties.Tie("debug/orientation_offset-z", this,
320 &View::getOrOffset_z);
321
322 _tiedProperties.Tie("debug/frame-w", this, &View::getFrame_w);
323 _tiedProperties.Tie("debug/frame-x", this, &View::getFrame_x);
324 _tiedProperties.Tie("debug/frame-y", this, &View::getFrame_y);
325 _tiedProperties.Tie("debug/frame-z", this, &View::getFrame_z);
326
327
328// expose the raw (OpenGL) orientation to the property tree,
329// for the sound-manager
330 _tiedProperties.Tie("raw-orientation", 0, this, &View::getRawOrientation_w);
331 _tiedProperties.Tie("raw-orientation", 1, this, &View::getRawOrientation_x);
332 _tiedProperties.Tie("raw-orientation", 2, this, &View::getRawOrientation_y);
333 _tiedProperties.Tie("raw-orientation", 3, this, &View::getRawOrientation_z);
334
335 _tiedProperties.Tie("viewer-x-m", this, &View::getAbsolutePosition_x);
336 _tiedProperties.Tie("viewer-y-m", this, &View::getAbsolutePosition_y);
337 _tiedProperties.Tie("viewer-z-m", this, &View::getAbsolutePosition_z);
338
339// following config properties are exposed on current-view but don't change,
340// so we can simply copy them here.
341 _tiedProperties.getRoot()->setStringValue("name", _name);
342 _tiedProperties.getRoot()->setStringValue("type", _typeString);
343 _tiedProperties.getRoot()->setBoolValue("internal", _internal);
344
345 SGPropertyNode_ptr config = _tiedProperties.getRoot()->getChild("config", 0, true);
346 config->setBoolValue("from-model", _from_model);
347 config->setDoubleValue("heading-offset-deg", _configHeadingOffsetDeg);
348 config->setDoubleValue("pitch-offset-deg", _configPitchOffsetDeg);
349 config->setDoubleValue("roll-offset-deg", _configRollOffsetDeg);
350 config->setDoubleValue("default-field-of-view-deg", _configFOV_deg);
351}
352
353void
355{
356 _tiedProperties.Untie();
357}
358
360{
361 _target_offset_m = _configTargetOffset_m;
362 _offset_m = _configOffset_m;
363 _adjust_offset_m = _configOffset_m;
364 _pitch_offset_deg = _configPitchOffsetDeg;
365 _heading_offset_deg = _configHeadingOffsetDeg;
366 _roll_offset_deg = _configRollOffsetDeg;
367 _fov_deg = _configFOV_deg;
368}
369
370void
371View::setType ( int type )
372{
373 if (type == 0)
374 _type = FG_LOOKFROM;
375 if (type == 1)
376 _type = FG_LOOKAT;
377}
378
379void
380View::setInternal ( bool internal )
381{
382 _internal = internal;
383}
384
385void
386View::setPosition (const SGGeod& geod)
387{
388 _dirty = true;
389 _position = geod;
390}
391
392void
393View::setTargetPosition (const SGGeod& geod)
394{
395 _dirty = true;
396 _target = geod;
397}
398
399void
400View::setRoll_deg (double roll_deg)
401{
402 _dirty = true;
403 _roll_deg = roll_deg;
404}
405
406void
407View::setPitch_deg (double pitch_deg)
408{
409 _dirty = true;
410 _pitch_deg = pitch_deg;
411}
412
413void
414View::setHeading_deg (double heading_deg)
415{
416 _dirty = true;
417 _heading_deg = heading_deg;
418}
419
420void
421View::setOrientation (double roll_deg, double pitch_deg, double heading_deg)
422{
423 _dirty = true;
424 _roll_deg = roll_deg;
425 _pitch_deg = pitch_deg;
426 _heading_deg = heading_deg;
427}
428
429void
430View::setTargetRoll_deg (double target_roll_deg)
431{
432 _dirty = true;
433 _target_roll_deg = target_roll_deg;
434}
435
436void
437View::setTargetPitch_deg (double target_pitch_deg)
438{
439 _dirty = true;
440 _target_pitch_deg = target_pitch_deg;
441}
442
443void
444View::setTargetHeading_deg (double target_heading_deg)
445{
446 _dirty = true;
447 _target_heading_deg = target_heading_deg;
448}
449
450void
451View::setTargetOrientation (double target_roll_deg, double target_pitch_deg, double target_heading_deg)
452{
453 _dirty = true;
454 _target_roll_deg = target_roll_deg;
455 _target_pitch_deg = target_pitch_deg;
456 _target_heading_deg = target_heading_deg;
457}
458
459void
460View::setXOffset_m (double x_offset_m)
461{
462 _dirty = true;
463 _offset_m.x() = x_offset_m;
464}
465
466void
467View::setYOffset_m (double y_offset_m)
468{
469 _dirty = true;
470 _offset_m.y() = y_offset_m;
471}
472
473void
474View::setZOffset_m (double z_offset_m)
475{
476 _dirty = true;
477 _offset_m.z() = z_offset_m;
478}
479
480void
481View::setTargetXOffset_m (double target_x_offset_m)
482{
483 _dirty = true;
484 _target_offset_m.x() = target_x_offset_m;
485}
486
487void
488View::setTargetYOffset_m (double target_y_offset_m)
489{
490 _dirty = true;
491 _target_offset_m.y() = target_y_offset_m;
492}
493
494void
495View::setTargetZOffset_m (double target_z_offset_m)
496{
497 _dirty = true;
498 _target_offset_m.z() = target_z_offset_m;
499}
500
501void
502View::setAdjustXOffset_m (double x_offset_m)
503{
504 _dirty = true;
505 _adjust_offset_m.x() = x_offset_m;
506}
507
508void
509View::setAdjustYOffset_m (double y_offset_m)
510{
511 _dirty = true;
512 _adjust_offset_m.y() = y_offset_m;
513}
514
515void
516View::setAdjustZOffset_m (double z_offset_m)
517{
518 _dirty = true;
519 _adjust_offset_m.z() = z_offset_m;
520}
521
522void
523View::setPositionOffsets (double x_offset_m, double y_offset_m, double z_offset_m)
524{
525 _dirty = true;
526 _offset_m.x() = x_offset_m;
527 _offset_m.y() = y_offset_m;
528 _offset_m.z() = z_offset_m;
529}
530
531void
532View::setRollOffset_deg (double roll_offset_deg)
533{
534 _dirty = true;
535 _roll_offset_deg = roll_offset_deg;
536}
537
538void
539View::setPitchOffset_deg (double pitch_offset_deg)
540{
541 _dirty = true;
542 _pitch_offset_deg = pitch_offset_deg;
543}
544
545void
546View::setHeadingOffset_deg (double heading_offset_deg)
547{
548 _dirty = true;
549 if (_at_model && (_offset_m.x() == 0.0)&&(_offset_m.z() == 0.0))
550 {
551 /* avoid optical effects (e.g. rotating sky) when "looking at" with
552 * heading offsets x==z==0 (view heading cannot change). */
553 _heading_offset_deg = 0.0;
554 }
555 else
556 _heading_offset_deg = heading_offset_deg;
557}
558
559void
560View::setHeadingOffset_deg_property (double heading_offset_deg)
561{
562 setHeadingOffset_deg(heading_offset_deg);
563 setGoalHeadingOffset_deg(heading_offset_deg);
564}
565
566void
567View::setPitchOffset_deg_property (double pitch_offset_deg)
568{
569 setPitchOffset_deg(pitch_offset_deg);
570 setGoalPitchOffset_deg(pitch_offset_deg);
571}
572
573void
574View::setRollOffset_deg_property (double roll_offset_deg)
575{
576 setRollOffset_deg(roll_offset_deg);
577 setGoalRollOffset_deg(roll_offset_deg);
578}
579
580void
581View::setGoalRollOffset_deg (double goal_roll_offset_deg)
582{
583 _dirty = true;
584 _goal_roll_offset_deg = goal_roll_offset_deg;
585}
586
587void
588View::setGoalPitchOffset_deg (double goal_pitch_offset_deg)
589{
590 _dirty = true;
591 _goal_pitch_offset_deg = goal_pitch_offset_deg;
592 /* The angle is set to 1/1000th of a degree from the poles to avoid the
593 * singularity where the azimuthal angle becomes undefined, inducing optical
594 * artefacts. The arbitrary angle offset is visually unnoticeable while
595 * avoiding any possible floating point truncation artefacts. */
596 if ( _goal_pitch_offset_deg < -89.999 ) {
597 _goal_pitch_offset_deg = -89.999;
598 }
599 if ( _goal_pitch_offset_deg > 89.999 ) {
600 _goal_pitch_offset_deg = 89.999;
601 }
602
603}
604
605void
606View::setGoalHeadingOffset_deg (double goal_heading_offset_deg)
607{
608 _dirty = true;
609 if (_at_model && (_offset_m.x() == 0.0)&&(_offset_m.z() == 0.0))
610 {
611 /* avoid optical effects (e.g. rotating sky) when "looking at" with
612 * heading offsets x==z==0 (view heading cannot change). */
613 _goal_heading_offset_deg = 0.0;
614 return;
615 }
616
617 _goal_heading_offset_deg = goal_heading_offset_deg;
618 while ( _goal_heading_offset_deg < 0.0 ) {
619 _goal_heading_offset_deg += 360;
620 }
621 while ( _goal_heading_offset_deg > 360 ) {
622 _goal_heading_offset_deg -= 360;
623 }
624}
625
626void
627View::setOrientationOffsets (double roll_offset_deg, double pitch_offset_deg, double heading_offset_deg)
628{
629 _dirty = true;
630 _roll_offset_deg = roll_offset_deg;
631 _pitch_offset_deg = pitch_offset_deg;
632 _heading_offset_deg = heading_offset_deg;
633}
634
635// recalc() is done every time one of the setters is called (making the
636// cached data "dirty") on the next "get". It calculates all the outputs
637// for viewer.
638void
639View::recalc ()
640{
641 if (_type == FG_LOOKFROM) {
642 recalcLookFrom();
643 } else {
644 recalcLookAt();
645 }
646
647 set_clean();
648}
649
650
651/* Gets position and orientation of user aircraft or multiplayer aircraft.
652
653root:
654 Location of aircraft position and oriention properties. Use '' for user's
655 aircraft, or /ai/models/multiplayer[] for a multiplayer aircraft.
656
657position:
658head:
659pitch:
660roll:
661 Out parameters.
662*/
664 SGGeod& position,
665 double& head,
666 double& pitch,
667 double& roll
668 )
669{
670 position = SGGeod::fromDegFt(
671 ViewPropertyEvaluator::getDoubleValue("((/sim/view[(/sim/current-view/view-number-raw)]/config/root)/position/longitude-deg)"),
672 ViewPropertyEvaluator::getDoubleValue("((/sim/view[(/sim/current-view/view-number-raw)]/config/root)/position/latitude-deg)"),
673 ViewPropertyEvaluator::getDoubleValue("((/sim/view[(/sim/current-view/view-number-raw)]/config/root)/position/altitude-ft)")
674 );
675
676 head = ViewPropertyEvaluator::getDoubleValue("((/sim/view[(/sim/current-view/view-number-raw)]/config/root)/orientation/true-heading-deg)");
677 pitch = ViewPropertyEvaluator::getDoubleValue("((/sim/view[(/sim/current-view/view-number-raw)]/config/root)/orientation/pitch-deg)");
678 roll = ViewPropertyEvaluator::getDoubleValue("((/sim/view[(/sim/current-view/view-number-raw)]/config/root)/orientation/roll-deg)");
679}
680
681
682/* Finds the offset of a view position from an aircraft model's origin.
683
684We look in either /sim/view[]/config/ for user's aircraft, or
685/ai/models/multiplayer[]/set/sim/view[]/config/ for multiplayer aircraft.
686
687If offset information is not available because we can't find the -set.xml files
688for a multiplayer aircraft), we set all elements of offset_m to zero.
689
690root:
691 Path that defines which aircraft. Either '' to use the user's aircraft, or
692 /ai/models/multiplayer[].
693view_index:
694 The view number. We will look in .../sim/view[view_index].
695infix:
696 We look at .../view[view_index]/config/<infix>x-offset-m etc. Views appear
697 to use 'z-offset-m' to define the location of pilot's eyes in cockpit
698 view, but 'target-z-offset' when defining viewpoint of other views such as
699 Helicopter View. So one should typically set <infix> to '' or 'target-'.
700adjust:
701 If not NULL, typically points to copy of offsets in
702 /sim/current-view/?-offset-m, and we add *adjust to the return value, and
703 also subtract /sim/view[]/config/?-offset-m. We do the latter to preserve
704 expectations that /sim/current-view/?-offset-m includes the view offset, so
705 that aircraft that define custom view behaviour continue to work correctly.
706offset_m:
707 Out param.
708
709Returns true if any component (x, y or z) was found, otherwise false.
710*/
711static void getViewOffsets(
712 bool target_infix,
713 const SGVec3d* adjust,
714 SGVec3d& offset_m
715 )
716{
717 std::string root = ViewPropertyEvaluator::getStringValue("(/sim/view[(/sim/current-view/view-number-raw)]/config/root)");
718 if (root == "/" || root == "") {
719 if (target_infix) {
720 offset_m.x() = ViewPropertyEvaluator::getDoubleValue("(/sim/view[(/sim/current-view/view-number-raw)]/config/target-x-offset-m)");
721 offset_m.y() = ViewPropertyEvaluator::getDoubleValue("(/sim/view[(/sim/current-view/view-number-raw)]/config/target-y-offset-m)");
722 offset_m.z() = ViewPropertyEvaluator::getDoubleValue("(/sim/view[(/sim/current-view/view-number-raw)]/config/target-z-offset-m)");
723 }
724 else {
725 offset_m.x() = ViewPropertyEvaluator::getDoubleValue("(/sim/view[(/sim/current-view/view-number-raw)]/config/x-offset-m)");
726 offset_m.y() = ViewPropertyEvaluator::getDoubleValue("(/sim/view[(/sim/current-view/view-number-raw)]/config/y-offset-m)");
727 offset_m.z() = ViewPropertyEvaluator::getDoubleValue("(/sim/view[(/sim/current-view/view-number-raw)]/config/z-offset-m)");
728 }
729 }
730 else {
731 if (target_infix) {
732 offset_m.x() = ViewPropertyEvaluator::getDoubleValue("((/sim/view[(/sim/current-view/view-number-raw)]/config/root)/set/sim/view[(/sim/current-view/view-number-raw)]/config/target-x-offset-m)");
733 offset_m.y() = ViewPropertyEvaluator::getDoubleValue("((/sim/view[(/sim/current-view/view-number-raw)]/config/root)/set/sim/view[(/sim/current-view/view-number-raw)]/config/target-y-offset-m)");
734 offset_m.z() = ViewPropertyEvaluator::getDoubleValue("((/sim/view[(/sim/current-view/view-number-raw)]/config/root)/set/sim/view[(/sim/current-view/view-number-raw)]/config/target-z-offset-m)");
735 }
736 else {
737 offset_m.x() = ViewPropertyEvaluator::getDoubleValue("((/sim/view[(/sim/current-view/view-number-raw)]/config/root)/set/sim/view[(/sim/current-view/view-number-raw)]/config/x-offset-m)");
738 offset_m.y() = ViewPropertyEvaluator::getDoubleValue("((/sim/view[(/sim/current-view/view-number-raw)]/config/root)/set/sim/view[(/sim/current-view/view-number-raw)]/config/y-offset-m)");
739 offset_m.z() = ViewPropertyEvaluator::getDoubleValue("((/sim/view[(/sim/current-view/view-number-raw)]/config/root)/set/sim/view[(/sim/current-view/view-number-raw)]/config/z-offset-m)");
740 }
741 }
742 if (adjust) {
743 SGVec3d offset = *adjust;
744 /* Note that we subtract the raw /sim/view[]/config/?-offset-m
745 regardless of whether we are viwing a multiplayer aircraft or not. */
746 offset.x() -= ViewPropertyEvaluator::getDoubleValue("(/sim/view[(/sim/current-view/view-number-raw)]/config/x-offset-m)");
747 offset.y() -= ViewPropertyEvaluator::getDoubleValue("(/sim/view[(/sim/current-view/view-number-raw)]/config/y-offset-m)");
748 offset.z() -= ViewPropertyEvaluator::getDoubleValue("(/sim/view[(/sim/current-view/view-number-raw)]/config/z-offset-m)");
749 offset_m += offset;
750 }
751
752 /* Handle headtracking :
753 Add offsets coming from the headtracker if there is one. The headtracker offsets have to be added ; they
754 cannot replace /sim/currentview/config/?-offset-m otherwise the offsets of each view are lost, making the
755 headtracking position unusable. */
756 offset_m.x() += ViewPropertyEvaluator::getDoubleValue("(/sim/viewer/camera-offset-x)");
757 offset_m.y() += ViewPropertyEvaluator::getDoubleValue("(/sim/viewer/camera-offset-y)");
758 offset_m.z() += ViewPropertyEvaluator::getDoubleValue("(/sim/viewer/camera-offset-z)");
759
760}
761
762// recalculate for LookFrom view type...
763// E.g. Cockpit View and Tower View Look From.
764void
765View::recalcLookFrom ()
766{
767 double head;
768 double pitch;
769 double roll;
770
771 if (_from_model ) {
772 /* Look up aircraft position; this works for user's aircaft or multiplayer
773 aircraft. */
774 getAircraftPositionOrientation( _position, head, pitch, roll);
775 }
776 else {
777 /* Tower View Look From.
778
779 <_config> will be /sim/view[4]/config, so <_config>/eye-lon-deg-path
780 will be /sim/view[4]/config/eye-lon-deg-path which will contain
781 /sim/tower/longitude-deg (see fgdata:defaults.xml).
782
783 [Might be nice to make a 'TowerView Look From Multiplayer' that uses the
784 tower that is nearest to a particular multiplayer aircraft. We'd end up
785 looking at /ai/models/multiplayer[]/sim/tower/longitude-deg, so we'd need
786 to somehow set up /ai/models/multiplayer[]/sim/tower. ]
787 */
788 _position = SGGeod::fromDegFt(
789 ViewPropertyEvaluator::getDoubleValue("((/sim/view[(/sim/current-view/view-number-raw)]/config/root)/(/sim/view[(/sim/current-view/view-number-raw)]/config/eye-lon-deg-path))"),
790 ViewPropertyEvaluator::getDoubleValue("((/sim/view[(/sim/current-view/view-number-raw)]/config/root)/(/sim/view[(/sim/current-view/view-number-raw)]/config/eye-lat-deg-path))"),
791 ViewPropertyEvaluator::getDoubleValue("((/sim/view[(/sim/current-view/view-number-raw)]/config/root)/(/sim/view[(/sim/current-view/view-number-raw)]/config/eye-alt-ft-path))")
792 );
793 head = ViewPropertyEvaluator::getDoubleValue("((/sim/view[(/sim/current-view/view-number-raw)]/config/root)/(/sim/view[(/sim/current-view/view-number-raw)]/config/eye-heading-deg-path))");
794 pitch = ViewPropertyEvaluator::getDoubleValue("((/sim/view[(/sim/current-view/view-number-raw)]/config/root)/(/sim/view[(/sim/current-view/view-number-raw)]/config/eye-pitch-deg-path))");
795 roll = ViewPropertyEvaluator::getDoubleValue("((/sim/view[(/sim/current-view/view-number-raw)]/config/root)/(/sim/view[(/sim/current-view/view-number-raw)]/config/eye-roll-deg-path))");
796 }
797
798 /* Find the offset of the view position relative to the aircraft model's
799 origin. */
800 SGVec3d offset_m;
801 getViewOffsets(false /*target_infix*/, &_adjust_offset_m, offset_m);
802
803 set_fov(_fov_user_deg);
804
805 // The rotation rotating from the earth centerd frame to
806 // the horizontal local frame
807 SGQuatd hlOr = SGQuatd::fromLonLat(_position);
808
809 // The rotation from the horizontal local frame to the basic view orientation
810 SGQuatd hlToBody = SGQuatd::fromYawPitchRollDeg(head, pitch, roll);
811
812 // The rotation offset, don't know why heading is negative here ...
813 mViewOffsetOr
814 = SGQuatd::fromYawPitchRollDeg(-_heading_offset_deg, _pitch_offset_deg,
815 _roll_offset_deg);
816
817 // Compute the eyepoints orientation and position
818 // wrt the earth centered frame - that is global coorinates
819 SGQuatd ec2body = hlOr*hlToBody;
820
821 // The cartesian position of the basic view coordinate
822 SGVec3d position = SGVec3d::fromGeod(_position);
823
824 // This is rotates the x-forward, y-right, z-down coordinate system the where
825 // simulation runs into the OpenGL camera system with x-right, y-up, z-back.
826 SGQuatd q(-0.5, -0.5, 0.5, 0.5);
827
828 _absolute_view_pos = position + (ec2body*q).backTransform(offset_m);
829 mViewOrientation = ec2body*mViewOffsetOr*q;
830}
831
832
833/* Change angle and field of view so that we can see the aircraft and the
834ground immediately below it. */
835
836/* Some aircraft appear to have elevation that is slightly below ground level
837when on the ground, e.g. SenecaII, which makes get_elevation_m() fail. So we
838pass a slightly incremented elevation. */
839void View::handleAGL()
840{
841 /* Change angle and field of view so that we can see the aircraft and the
842 ground immediately below it. */
843
844 /* Some aircraft appear to have elevation that is slightly below ground
845 level when on the ground, e.g. SenecaII, which makes get_elevation_m()
846 fail. So we pass a slightly incremented elevation. */
847 double ground_altitude = 0;
848 const simgear::BVHMaterial* material = NULL;
849 SGGeod target_plus = _target;
850 target_plus.setElevationM(target_plus.getElevationM() + 1);
851 bool ok = globals->get_scenery()->get_elevation_m(target_plus, ground_altitude, &material);
852
853 if (ok) {
854 _lookat_agl_ground_altitude = ground_altitude;
855 }
856 else {
857 /* get_elevation_m() can fail if scenery has been un-cached, which
858 appears to happen quite often with remote multiplayer aircraft, so we
859 preserve the previous ground altitude to give some consistency and avoid
860 confusing zooming when switching between views.
861
862 [Might be better to have per-aircraft state too, so that switching
863 between multiplayer aircraft doesn't cause zooming.] */
864 ground_altitude = _lookat_agl_ground_altitude;
865 SG_LOG(SG_VIEW, SG_DEBUG, "get_elevation_m() failed. _target=" << _target << "\n");
866 }
867
868 double h_distance = SGGeodesy::distanceM(_position, _target);
869 if (h_distance == 0) {
870 /* Not sure this should ever happen, but we need to handle this here
871 otherwise we'll get divide-by-zero. Just use whatever field of view the
872 user has set. */
873 set_fov(_fov_user_deg);
874 }
875 else {
876 /* Find vertical region we want to be able to see. */
877 double relative_height_target = _target.getElevationM() - _position.getElevationM();
878 double relative_height_ground = ground_altitude - _position.getElevationM();
879
880 /* We expand the field of view so that it hopefully shows the
881 whole aircraft and a little more of the ground.
882
883 We use chase-distance as a crude measure of the aircraft's size. There
884 doesn't seem to be any more definitive information.
885
886 We damp our measure of ground level, to avoid the view jumping around if
887 an aircraft flies over buildings.
888 */
889
890 relative_height_ground -= 2;
891
892 double chase_distance_m;
893 const std::string& root = ViewPropertyEvaluator::getStringValue("(/sim/view[(/sim/current-view/view-number-raw)]/config/root)");
894 if (root == "/" || root == "") {
895 chase_distance_m = ViewPropertyEvaluator::getDoubleValue("(/sim/chase-distance-m)", -25);
896 }
897 else {
898 chase_distance_m = ViewPropertyEvaluator::getDoubleValue(
899 "((/sim/view[(/sim/current-view/view-number-raw)]/config/root)/set/sim/chase-distance-m)",
900 -25
901 );
902 }
903 if (chase_distance_m == 0) {
904 /* ViewPropertyEvaluator::getDoubleValue() doesn't handle default
905 values very well because it always creates empty nodes if they don't
906 exist. So we override the value here. */
907 chase_distance_m = -25;
908 }
909 double aircraft_size_vertical = fabs(chase_distance_m) * 0.3;
910 double aircraft_size_horizontal = fabs(chase_distance_m) * 0.9;
911
912 double relative_height_target_plus = relative_height_target + aircraft_size_vertical;
913 double relative_height_ground_ = relative_height_ground;
914 _lookat_agl_damping.updateTarget(relative_height_ground);
915 if (relative_height_ground > relative_height_target) {
916 /* Damping of relative_height_ground can result in it being
917 temporarily above the aircraft, so we ensure the aircraft is visible.
918 */
919 _lookat_agl_damping.reset(relative_height_ground_);
920 relative_height_ground = relative_height_ground_;
921 }
922
923 /* Apply scaling from user field of view setting, altering only
924 relative_height_ground so that the aircraft is always in view. */
925 {
926 double delta = relative_height_target_plus - relative_height_ground;
927 delta *= (_fov_user_deg / _configFOV_deg);
928 relative_height_ground = relative_height_target_plus - delta;
929 }
930
931 double angle_v_target = atan(relative_height_target_plus / h_distance);
932 double angle_v_ground = atan(relative_height_ground / h_distance);
933
934 /* The target we want to use is determined by the midpoint of the two
935 angles we've calculated. */
936 double angle_v_mid = (angle_v_target + angle_v_ground) / 2;
937 _target.setElevationM(_position.getElevationM() + h_distance * tan(angle_v_mid));
938
939 /* Set field of view. We use fabs to avoid things being upside down if
940 target is below ground level (e.g. new multiplayer aircraft are briefly
941 at -9999ft). */
942 double fov_v = fabs(angle_v_target - angle_v_ground);
943 double fov_v_deg = fov_v / 3.1415 * 180;
944
945 set_fov( fov_v_deg);
946
947 /* Ensure that we can see entire horizontal extent of the aircraft
948 (assuming airplane is horizontal), and also correct things if display
949 aspect ratio results in set_fov() not setting the vertical field of view
950 that we want. */
951 double fov_h;
952 fov_h = 2 * atan(aircraft_size_horizontal / 2 / h_distance);
953 double fov_h_deg = fov_h / 3.1415 * 180;
954
955 double correction_v = fov_v_deg / get_v_fov();
956 double correction_h = fov_h_deg / get_h_fov();
957 double correction = std::max(correction_v, correction_h);
958 if (correction > 1) {
959 fov_v_deg *= correction;
960 set_fov(fov_v_deg);
961 }
962
963 SG_LOG(SG_VIEW, SG_DEBUG, ""
964 << " fov_v_deg=" << fov_v_deg
965 << " _position=" << _position
966 << " _target=" << _target
967 << " ground_altitude=" << ground_altitude
968 << " relative_height_target_plus=" << relative_height_target_plus
969 << " relative_height_ground=" << relative_height_ground
970 << " chase_distance_m=" << chase_distance_m
971 );
972 }
973}
974
975
976/* Views of an aircraft e.g. Helicopter View and Tower View. */
977void
978View::recalcLookAt ()
979{
980 /* 2019-06-12: i think maybe all LookAt views have _at_model=true? */
981 if ( _at_model ) {
983 _target,
984 _target_heading_deg,
985 _target_pitch_deg,
986 _target_roll_deg
987 );
988 }
989
990 double eye_heading = ViewPropertyEvaluator::getDoubleValue("((/sim/view[(/sim/current-view/view-number-raw)]/config/root)/(/sim/view[(/sim/current-view/view-number-raw)]/config/eye-heading-deg-path))");
991 double eye_roll = ViewPropertyEvaluator::getDoubleValue("((/sim/view[(/sim/current-view/view-number-raw)]/config/root)/(/sim/view[(/sim/current-view/view-number-raw)]/config/eye-roll-deg-path))");
992 double eye_pitch = ViewPropertyEvaluator::getDoubleValue("((/sim/view[(/sim/current-view/view-number-raw)]/config/root)/(/sim/view[(/sim/current-view/view-number-raw)]/config/eye-pitch-deg-path))");
993
994 setDampTarget(eye_roll, eye_pitch, eye_heading);
995 getDampOutput(eye_roll, eye_pitch, eye_heading);
996
997 SGQuatd geodTargetOr = SGQuatd::fromYawPitchRollDeg(_target_heading_deg,
998 _target_pitch_deg,
999 _target_roll_deg);
1000 SGQuatd geodTargetHlOr = SGQuatd::fromLonLat(_target);
1001
1002 SGVec3d target_pos_off;
1003 getViewOffsets(true /*target_infix*/, NULL /*adjust*/, target_pos_off);
1004 target_pos_off = SGVec3d(
1005 -target_pos_off.z(),
1006 target_pos_off.x(),
1007 -target_pos_off.y()
1008 );
1009 target_pos_off = (geodTargetHlOr*geodTargetOr).backTransform(target_pos_off);
1010
1011 SGVec3d targetCart = SGVec3d::fromGeod(_target);
1012 SGVec3d targetCart2 = targetCart + target_pos_off;
1013 SGGeodesy::SGCartToGeod(targetCart2, _target);
1014
1015 _position = _target;
1016
1017 bool eye_fixed = ViewPropertyEvaluator::getBoolValue("(/sim/view[(/sim/current-view/view-number-raw)]/config/eye-fixed)");
1018 if (eye_fixed) {
1019 _position.setLongitudeDeg(
1021 "((/sim/view[(/sim/current-view/view-number-raw)]/config/root)(/sim/view[(/sim/current-view/view-number-raw)]/config/eye-lon-deg-path))",
1022 _position.getLongitudeDeg()
1023 )
1024 );
1025 _position.setLatitudeDeg(
1027 "((/sim/view[(/sim/current-view/view-number-raw)]/config/root)(/sim/view[(/sim/current-view/view-number-raw)]/config/eye-lat-deg-path))",
1028 _position.getLatitudeDeg()
1029 )
1030 );
1031 _position.setElevationFt(
1033 "((/sim/view[(/sim/current-view/view-number-raw)]/config/root)(/sim/view[(/sim/current-view/view-number-raw)]/config/eye-alt-ft-path))",
1034 _position.getElevationFt()
1035 )
1036 );
1037 }
1038
1039 _target.setLongitudeDeg(
1041 "((/sim/view[(/sim/current-view/view-number-raw)]/config/root)(/sim/view[(/sim/current-view/view-number-raw)]/config/target-lon-deg-path))",
1042 _target.getLongitudeDeg()
1043 )
1044 );
1045 _target.setLatitudeDeg(
1047 "((/sim/view[(/sim/current-view/view-number-raw)]/config/root)(/sim/view[(/sim/current-view/view-number-raw)]/config/target-lat-deg-path))",
1048 _target.getLatitudeDeg()
1049 )
1050 );
1051 _target.setElevationFt(
1053 "((/sim/view[(/sim/current-view/view-number-raw)]/config/root)(/sim/view[(/sim/current-view/view-number-raw)]/config/target-alt-ft-path))",
1054 _target.getElevationFt()
1055 )
1056 );
1057
1058 if (_lookat_agl) {
1059 handleAGL();
1060 }
1061 else {
1062 set_fov(_fov_user_deg);
1063 }
1064
1065 SGQuatd geodEyeOr = SGQuatd::fromYawPitchRollDeg(eye_heading, eye_pitch, eye_roll);
1066 SGQuatd geodEyeHlOr = SGQuatd::fromLonLat(_position);
1067
1068 // the rotation offset, don't know why heading is negative here ...
1069 mViewOffsetOr =
1070 SGQuatd::fromYawPitchRollDeg(-_heading_offset_deg + 180, _pitch_offset_deg,
1071 _roll_offset_deg);
1072
1073 // Offsets to the eye position
1074 getViewOffsets(false /*target_infix*/, &_adjust_offset_m, _offset_m);
1075 SGVec3d eyeOff(-_offset_m.z(), _offset_m.x(), -_offset_m.y());
1076 SGQuatd ec2eye = geodEyeHlOr*geodEyeOr;
1077 SGVec3d eyeCart = SGVec3d::fromGeod(_position);
1078 eyeCart += (ec2eye*mViewOffsetOr).backTransform(eyeOff);
1079
1080 SGVec3d atCart = SGVec3d::fromGeod(_target);
1081
1082 // add target offsets to at_position...
1083 // Compute the eyepoints orientation and position
1084 // wrt the earth centered frame - that is global coorinates
1085 _absolute_view_pos = eyeCart;
1086
1087 // the view direction
1088 SGVec3d dir = normalize(atCart - eyeCart);
1089 // the up directon
1090 SGVec3d up = ec2eye.backTransform(SGVec3d(0, 0, -1));
1091 // rotate -dir to the 2-th unit vector
1092 // rotate up to 1-th unit vector
1093 // Note that this matches the OpenGL camera coordinate system
1094 // with x-right, y-up, z-back.
1095 mViewOrientation = SGQuatd::fromRotateTo(-dir, 2, up, 1);
1096}
1097
1098void
1099View::setDampTarget(double roll, double pitch, double heading)
1100{
1101 _dampTarget = SGVec3d(roll, pitch, heading);
1102}
1103
1104void
1105View::getDampOutput(double& roll, double& pitch, double& heading)
1106{
1107 roll = _dampOutput[0];
1108 pitch = _dampOutput[1];
1109 heading = _dampOutput[2];
1110}
1111
1112
1113void
1114View::updateDampOutput(double dt)
1115{
1116 static View *last_view = 0;
1117 if ((last_view != this) || (dt > 1.0)) {
1118 _dampOutput = _dampTarget;
1119 last_view = this;
1120 return;
1121 }
1122
1123 const double interval = 0.01;
1124 while (dt > interval) {
1125
1126 for (unsigned int i=0; i<3; ++i) {
1127 if (_dampFactor[i] <= 0.0) {
1128 // axis is un-damped, set output to target directly
1129 _dampOutput[i] = _dampTarget[i];
1130 continue;
1131 }
1132
1133 double d = _dampOutput[i] - _dampTarget[i];
1134 if (d > 180.0) {
1135 _dampOutput[i] -= 360.0;
1136 } else if (d < -180.0) {
1137 _dampOutput[i] += 360.0;
1138 }
1139
1140 _dampOutput[i] = (_dampTarget[i] * _dampFactor[i]) +
1141 (_dampOutput[i] * (1.0 - _dampFactor[i]));
1142 } // of axis iteration
1143
1144 dt -= interval;
1145 } // of dt subdivision by interval
1146}
1147
1148
1149View::Damping::Damping(double damping, double min, double max)
1150: _id(NULL), _min(min), _max(max), _target(0), _factor(pow(10, -damping)), _current(0)
1151{
1152}
1153
1154void View::Damping::setTarget(double target)
1155{
1156 _target = target;
1157}
1158
1159void View::Damping::update(double dt, void* id)
1160{
1161 if (id != _id || dt > 1.0) {
1162 _current = _target;
1163 _id = id;
1164 return;
1165 }
1166 const double interval = 0.01;
1167 while (dt > interval) {
1168
1169 if (_factor <= 0.0) {
1170 // un-damped, set output to target directly
1171 _current = _target;
1172 continue;
1173 }
1174
1175 double d = _current - _target;
1176 if (_max > _min) {
1177 if (d > _max) {
1178 _current -= (_max - _min);
1179 } else if (d < _min) {
1180 _current += (_max - _min);
1181 }
1182 }
1183
1184 _current = (_target * _factor) + _current * (1.0 - _factor);
1185
1186 dt -= interval;
1187 }
1188}
1189
1190double View::Damping::get()
1191{
1192 return _current;
1193}
1194
1195void View::Damping::updateTarget(double& io)
1196{
1197 setTarget(io);
1198 io = get();
1199}
1200
1201void View::Damping::reset(double target)
1202{
1203 _target = target;
1204 _current = target;
1205}
1206
1207double
1209{
1210 double aspectRatio = get_aspect_ratio();
1211 switch (_scaling_type) {
1212 case FG_SCALING_WIDTH: // h_fov == fov
1213 return _fov_deg;
1214 case FG_SCALING_MAX:
1215 if (aspectRatio < 1.0) {
1216 // h_fov == fov
1217 return _fov_deg;
1218 } else {
1219 // v_fov == fov
1220 return
1221 atan(tan(_fov_deg/2 * SG_DEGREES_TO_RADIANS)
1222 / (aspectRatio*_aspect_ratio_multiplier))
1223 * SG_RADIANS_TO_DEGREES * 2;
1224 }
1225 default:
1226 assert(false);
1227 }
1228 return 0.0;
1229}
1230
1231
1232
1233double
1235{
1236 double aspectRatio = get_aspect_ratio();
1237 switch (_scaling_type) {
1238 case FG_SCALING_WIDTH: // h_fov == fov
1239 return
1240 atan(tan(_fov_deg/2 * SG_DEGREES_TO_RADIANS)
1241 * (aspectRatio*_aspect_ratio_multiplier))
1242 * SG_RADIANS_TO_DEGREES * 2;
1243 case FG_SCALING_MAX:
1244 if (aspectRatio < 1.0) {
1245 // h_fov == fov
1246 return
1247 atan(tan(_fov_deg/2 * SG_DEGREES_TO_RADIANS)
1248 * (aspectRatio*_aspect_ratio_multiplier))
1249 * SG_RADIANS_TO_DEGREES * 2;
1250 } else {
1251 // v_fov == fov
1252 return _fov_deg;
1253 }
1254 default:
1255 assert(false);
1256 }
1257 return 0.0;
1258}
1259
1260void
1261View::update (double dt)
1262{
1263 recalc();
1264 updateDampOutput(dt);
1265 _lookat_agl_damping.update(dt, NULL /*id*/);
1266
1267 int i;
1268 int dt_ms = int(dt * 1000);
1269 for ( i = 0; i < dt_ms; i++ ) {
1270 if ( fabs( _goal_heading_offset_deg - _heading_offset_deg) < 1 ) {
1271 setHeadingOffset_deg( _goal_heading_offset_deg );
1272 break;
1273 } else {
1274 // move current_view.headingoffset towards
1275 // current_view.goal_view_offset
1276 if ( _goal_heading_offset_deg > _heading_offset_deg )
1277 {
1278 if ( _goal_heading_offset_deg - _heading_offset_deg < 180 ){
1279 incHeadingOffset_deg( 0.5 );
1280 } else {
1281 incHeadingOffset_deg( -0.5 );
1282 }
1283 } else {
1284 if ( _heading_offset_deg - _goal_heading_offset_deg < 180 ){
1285 incHeadingOffset_deg( -0.5 );
1286 } else {
1287 incHeadingOffset_deg( 0.5 );
1288 }
1289 }
1290 if ( _heading_offset_deg > 360 ) {
1291 incHeadingOffset_deg( -360 );
1292 } else if ( _heading_offset_deg < 0 ) {
1293 incHeadingOffset_deg( 360 );
1294 }
1295 }
1296 }
1297
1298 for ( i = 0; i < dt_ms; i++ ) {
1299 if ( fabs( _goal_pitch_offset_deg - _pitch_offset_deg ) < 1 ) {
1300 setPitchOffset_deg( _goal_pitch_offset_deg );
1301 break;
1302 } else {
1303 // move current_view.pitch_offset_deg towards
1304 // current_view.goal_pitch_offset
1305 if ( _goal_pitch_offset_deg > _pitch_offset_deg )
1306 {
1307 incPitchOffset_deg( 1.0 );
1308 } else {
1309 incPitchOffset_deg( -1.0 );
1310 }
1311 if ( _pitch_offset_deg > 90 ) {
1312 setPitchOffset_deg(90);
1313 } else if ( _pitch_offset_deg < -90 ) {
1314 setPitchOffset_deg( -90 );
1315 }
1316 }
1317 }
1318
1319
1320 for ( i = 0; i < dt_ms; i++ ) {
1321 if ( fabs( _goal_roll_offset_deg - _roll_offset_deg ) < 1 ) {
1322 setRollOffset_deg( _goal_roll_offset_deg );
1323 break;
1324 } else {
1325 // move current_view.roll_offset_deg towards
1326 // current_view.goal_roll_offset
1327 if ( _goal_roll_offset_deg > _roll_offset_deg )
1328 {
1329 incRollOffset_deg( 1.0 );
1330 } else {
1331 incRollOffset_deg( -1.0 );
1332 }
1333 if ( _roll_offset_deg > 90 ) {
1334 setRollOffset_deg(90);
1335 } else if ( _roll_offset_deg < -90 ) {
1336 setRollOffset_deg( -90 );
1337 }
1338 }
1339 }
1340 recalc();
1341}
1342
1343double View::getAbsolutePosition_x() const
1344{
1345 return _absolute_view_pos.x();
1346}
1347
1348double View::getAbsolutePosition_y() const
1349{
1350 return _absolute_view_pos.y();
1351}
1352
1353double View::getAbsolutePosition_z() const
1354{
1355 return _absolute_view_pos.z();
1356}
1357
1358double View::getRawOrientation_w() const
1359{
1360 return mViewOrientation.w();
1361}
1362
1363double View::getRawOrientation_x() const
1364{
1365 return mViewOrientation.x();
1366}
1367
1368double View::getRawOrientation_y() const
1369{
1370 return mViewOrientation.y();
1371}
1372
1373double View::getRawOrientation_z() const
1374{
1375 return mViewOrientation.z();
1376}
1377
1378// This takes the conventional aviation XYZ body system
1379// i.e. x=forward, y=starboard, z=bottom
1380// which is widely used in FGFS
1381// and rotates it into the OpenGL camera system
1382// i.e. Xprime=starboard, Yprime=top, Zprime=aft.
1383static const SGQuatd fsb2sta()
1384{
1385 return SGQuatd(-0.5, -0.5, 0.5, 0.5);
1386}
1387
1388// reference frame orientation.
1389// This is the view orientation you get when you have no
1390// view offset, i.e. the offset operator is the identity.
1391//
1392// For example, in the familiar "cockpit lookfrom" view,
1393// the reference frame is equal to the aircraft attitude,
1394// i.e. it is the view looking towards 12:00 straight ahead.
1395//
1396// FIXME: Somebody needs to figure out what is the reference
1397// frame view for the other view modes.
1398//
1399// Conceptually, this quat represents a rotation relative
1400// to the ECEF reference orientation, as described at
1401// http://www.av8n.com/physics/coords.htm#sec-orientation
1402//
1403// See the NOTE concerning reference orientations, below.
1404//
1405// The components of this quat are expressed in
1406// the conventional aviation basis set,
1407// i.e. x=forward, y=starboard, z=bottom
1408double View::getFrame_w() const
1409{
1410 return ((mViewOrientation*conj(fsb2sta())*conj(mViewOffsetOr))).w();
1411}
1412
1413double View::getFrame_x() const
1414{
1415 return ((mViewOrientation*conj(fsb2sta())*conj(mViewOffsetOr))).x();
1416}
1417
1418double View::getFrame_y() const
1419{
1420 return ((mViewOrientation*conj(fsb2sta())*conj(mViewOffsetOr))).y();
1421}
1422
1423double View::getFrame_z() const
1424{
1425 return ((mViewOrientation*conj(fsb2sta())*conj(mViewOffsetOr))).z();
1426}
1427
1428
1429// view offset.
1430// This rotation takes you from the aforementioned
1431// reference frame view orientation to whatever
1432// actual current view orientation is.
1433//
1434// The components of this quaternion are expressed in
1435// the conventional aviation basis set,
1436// i.e. x=forward, y=starboard, z=bottom
1437double View::getOrOffset_w() const{
1438 return mViewOffsetOr.w();
1439}
1440double View::getOrOffset_x() const{
1441 return mViewOffsetOr.x();
1442}
1443double View::getOrOffset_y() const{
1444 return mViewOffsetOr.y();
1445}
1446double View::getOrOffset_z() const{
1447 return mViewOffsetOr.z();
1448}
1449
1450
1451// current view orientation.
1452// This is a rotation relative to the earth-centered (ec)
1453// reference frame.
1454//
1455// NOTE: Here we remove a factor of fsb2sta so that
1456// the components of this quat are displayed using the
1457// conventional ECEF basis set. This is *not* the way
1458// the view orientation is stored in the views[] array,
1459// but is easier for non-graphics hackers to understand.
1460// If we did not remove this factor of fsb2sta here and
1461// in getCurrentViewFrame, that would be equivalent to
1462// the following peculiar reference orientation:
1463// Suppose you are over the Gulf of Guinea, at (lat,lon) = (0,0).
1464// Then the reference frame orientation can be achieved via:
1465// -- The aircraft X-axis (nose) headed south.
1466// -- The aircraft Y-axis (starboard wingtip) pointing up.
1467// -- The aircraft Z-axis (belly) pointing west.
1468// To say the same thing in other words, and perhaps more to the
1469// point: If we use the OpenGL camera orientation conventions,
1470// i.e. Xprime=starboard, Yprime=top, Zprime=aft, then the
1471// aforementioned peculiar reference orientation at (lat,lon)
1472// = (0,0) can be described as:
1473// -- aircraft Xprime axis (starboard) pointed up
1474// -- aircraft Yprime axis (top) pointed east
1475// -- aircraft Zprime axis (aft) pointed north
1476// meaning the OpenGL axes are aligned with the ECEF axes.
1477double View::getOrientation_w() const{
1478 return (mViewOrientation * conj(fsb2sta())).w();
1479}
1480double View::getOrientation_x() const{
1481 return (mViewOrientation * conj(fsb2sta())).x();
1482}
1483double View::getOrientation_y() const{
1484 return (mViewOrientation * conj(fsb2sta())).y();
1485}
1486double View::getOrientation_z() const{
1487 return (mViewOrientation * conj(fsb2sta())).z();
1488}
1489
1494
1495double View::getLon_deg() const
1496{
1497 return _position.getLongitudeDeg();
1498}
1499
1500double View::getLat_deg() const
1501{
1502 return _position.getLatitudeDeg();
1503}
1504
1505double View::getElev_ft() const
1506{
1507 return _position.getElevationFt();
1508}
1509
1510// Register the subsystem.
1511#if 0
1512SGSubsystemMgr::Registrant<View> registrantView;
1513#endif
#define min(X, Y)
#define i(x)
FGScenery * get_scenery() const
Definition globals.cxx:950
bool get_elevation_m(const SGGeod &geod, double &alt, const simgear::BVHMaterial **material, const osg::Node *butNotFrom=0)
Compute the elevation of the scenery at geodetic latitude lat, geodetic longitude lon and not higher ...
Definition scenery.cxx:527
static CameraGroup * getDefault()
Get the default CameraGroup.
double getMasterAspectRatio() const
get aspect ratio of master camera's viewport
void setTargetYOffset_m(double y_offset_m)
Definition view.cxx:488
void setHeadingOffset_deg(double heading_offset_deg)
Definition view.cxx:546
void setTargetZOffset_m(double z_offset_m)
Definition view.cxx:495
void setAdjustXOffset_m(double x_adjust_offset_m)
Definition view.cxx:502
double getPitchOffset_deg() const
Definition view.hxx:165
void setYOffset_m(double y_offset_m)
Definition view.cxx:467
double get_h_fov()
Definition view.cxx:1208
double get_v_fov()
Definition view.cxx:1234
void setAdjustZOffset_m(double z_adjust_offset_m)
Definition view.cxx:516
double getAdjustZOffset_m() const
Definition view.hxx:138
void bind() override
Definition view.cxx:230
void setType(int type)
Definition view.cxx:371
double getTargetYOffset_m() const
Definition view.hxx:125
void setZOffset_m(double z_offset_m)
Definition view.cxx:474
void update(double dt) override
Definition view.cxx:1261
void setAdjustYOffset_m(double y_adjust_offset_m)
Definition view.cxx:509
void setPositionOffsets(double x_offset_m, double y_offset_m, double z_offset_m)
Definition view.cxx:523
void init() override
Definition view.cxx:225
double getTargetXOffset_m() const
Definition view.hxx:124
double getHeadingOffset_deg() const
Definition view.hxx:166
void resetOffsetsAndFOV()
Definition view.cxx:359
double getRollOffset_deg() const
Definition view.hxx:164
void setGoalHeadingOffset_deg(double goal_heading_offset_deg)
Definition view.cxx:606
virtual ~View()
Definition view.cxx:219
void unbind() override
Definition view.cxx:354
double getAdjustYOffset_m() const
Definition view.hxx:137
double getAdjustXOffset_m() const
Definition view.hxx:136
void setTargetXOffset_m(double x_offset_m)
Definition view.cxx:481
static View * createFromProperties(SGPropertyNode_ptr props, int view_index=-1)
Definition view.cxx:140
double getTargetZOffset_m() const
Definition view.hxx:126
void setInternal(bool internal)
Definition view.cxx:380
double get_aspect_ratio() const
Definition view.cxx:1490
void setXOffset_m(double x_offset_m)
Definition view.cxx:460
void fgSetArchivable(const char *name, bool state)
Set the state of the archive attribute for a property.
Definition fg_props.cxx:598
FGGlobals * globals
Definition globals.cxx:142
const std::string & getStringValue(const char *spec)
double getDoubleValue(const char *spec, double default_)
bool getBoolValue(const char *spec, bool default_)
FlightPlan.hxx - defines a full flight-plan object, including departure, cruise, arrival information ...
Definition Addon.cxx:53
double fgGetDouble(const char *name, double defaultValue)
Get a double value for a property.
Definition proptest.cpp:30
SGPropertyNode * fgGetNode(const char *path, bool create)
Get a property node.
Definition proptest.cpp:27
static void getAircraftPositionOrientation(SGGeod &position, double &head, double &pitch, double &roll)
Definition view.cxx:663
static const SGQuatd fsb2sta()
Definition view.cxx:1383
static void getViewOffsets(bool target_infix, const SGVec3d *adjust, SGVec3d &offset_m)
Definition view.cxx:711