FlightGear next
sview.cxx
Go to the documentation of this file.
1/*
2Implementation of 'step' view system.
3
4The basic idea here is calculate view position and direction by a series
5of explicit steps. Steps can move to the origin of the user aircraft or a
6multiplayer aircraft, or modify the current position by a fixed vector (e.g.
7to move from aircraft origin to the pilot's eyepoint), or rotate the current
8direction by a fixed transformation etc. We can also have a step that sets the
9direction to point to a previously-calculated target position.
10
11This is similar to what is already done by FlightGear's existing View code, but
12making the individual steps explicit gives us more flexibility.
13
14The dynamic nature of step views allows view cloning with composite-viewer.
15
16We also allow views to be defined and created at runtime instead of being
17hard-coded in *-set.xml files. For example this makes it possible to define a
18view from the user's aircraft's pilot to the centre of a multiplayer aircraft
19(or to a multiplayer aircraft's pilot).
20*/
21
22/*
23This program is free software; you can redistribute it and/or
24modify it under the terms of the GNU General Public License as
25published by the Free Software Foundation; either version 2 of the
26License, or (at your option) any later version.
27
28This program is distributed in the hope that it will be useful, but
29WITHOUT ANY WARRANTY; without even the implied warranty of
30MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
31General Public License for more details.
32
33You should have received a copy of the GNU General Public License
34along with this program; if not, write to the Free Software
35Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
36*/
37
38#include "sview.hxx"
39
40#include <Main/fg_props.hxx>
41#include <Main/globals.hxx>
42#include <Scenery/scenery.hxx>
44#include <Viewer/renderer.hxx>
47
48#include <simgear/debug/logstream.hxx>
49#include <simgear/math/SGMath.hxx>
50#include <simgear/props/props.hxx>
51#include <simgear/props/props_io.hxx>
52#include <simgear/scene/util/OsgMath.hxx>
53#include <simgear/scene/viewer/Compositor.hxx>
54
55#include <osg/CameraView>
56#include <osg/GraphicsContext>
57#include <osgViewer/CompositeViewer>
58
59static const double pi = 3.141592653589793238463;
60
61static std::ostream& operator << (std::ostream& out, const osg::Vec3f& vec)
62{
63 return out << "Vec3f{"
64 << " x=" << vec._v[0]
65 << " y=" << vec._v[1]
66 << " z=" << vec._v[2]
67 << "}";
68}
69
70static std::ostream& operator << (std::ostream& out, const osg::Quat& quat)
71{
72 return out << "Quat{"
73 << " x=" << quat._v[0]
74 << " y=" << quat._v[1]
75 << " z= " << quat._v[2]
76 << " w=" << quat._v[3]
77 << "}";
78}
79
80static std::ostream& operator << (std::ostream& out, const osg::Matrixd& matrix)
81{
82 osg::Vec3f translation;
83 osg::Quat rotation;
84 osg::Vec3f scale;
85 osg::Quat so;
86 matrix.decompose(translation, rotation, scale, so);
87 return out << "Matrixd {"
88 << " translation=" << translation
89 << " rotation=" << rotation
90 << " scale=" << scale
91 << " so=" << so
92 << "}";
93}
94
95
96/* A position and direction. Repeatedly modified by SviewStep::evaluate() when
97calculating final camera position/orientation. */
99{
101 :
102 heading(0),
103 pitch(0),
104 roll(0),
105 target_is_set(false)
106 {
107 }
108
109 SGGeod position;
110 double heading;
111 double pitch;
112 double roll;
113
114 SGGeod target;
116
117 /* The final position and direction, in a form suitable for setting an
118 osg::Camera's view matrix. */
119 SGVec3d position2;
120 SGQuatd direction2;
121
122 /* If a step sets either/both of these to non-zero, the view will alter
123 zoom to accomodate the required field of views (in degreea). */
124 double fov_h = 0;
125 double fov_v = 0;
126
127 friend std::ostream& operator<< (std::ostream& out, const SviewPosDir& posdir)
128 {
129 out << "SviewPosDir {"
130 << " position=" << posdir.position
131 << " heading=" << posdir.heading
132 << " pitch=" << posdir.pitch
133 << " roll=" << posdir.roll
134 << " target=" << posdir.target
135 << " position2=" << posdir.position2
136 << " direction2=" << posdir.direction2
137 << "}"
138 ;
139 return out;
140 }
141};
142
143
144/* Generic damping support. Improved version of class in view.hxx and view.cxx.
145
146We model dx/dt = (target-current)/damping_time, so damping_time is time for
147value to change by a factor of e. */
148struct Damping {
149
150 /* If m_wrap_max is non-zero, we wrap to ensure values are always between 0
151 and m_wrap_max. E.g. use m_wrap_max=360 for an angle in degrees. */
152 Damping(double damping_time, double wrap_max=0, double current=0)
153 :
154 m_damping_time(damping_time),
155 m_current(current),
156 m_wrap_max(wrap_max)
157 {}
158
159 /* Updates and returns new smoothed value. */
160 double update(double dt, double target)
161 {
162 const double e = 2.718281828459045;
163 double delta = target - m_current;
164 if (m_wrap_max) {
165 if (delta < -m_wrap_max/2) delta += m_wrap_max;
166 if (delta >= m_wrap_max/2) delta -= m_wrap_max;
167 }
168 m_current = target - delta * pow(e, -dt/m_damping_time);
169 if (m_wrap_max) {
170 if (m_current < 0) m_current += m_wrap_max;
171 if (m_current >= m_wrap_max) m_current -= m_wrap_max;
172 }
173 return m_current;
174 }
175
176 /* Forces current value to be <current>. */
177 double reset(double current)
178 {
179 m_current = current;
180 return m_current;
181 }
182
183 private:
184 double m_damping_time;
185 double m_current;
186 double m_wrap_max;
187};
188
189/* Abstract base class for a single view step. A view step modifies a
190SviewPosDir, e.g. translating the position and/or rotating the direction. */
192{
193 /* Updates <posdir>. */
194 virtual void evaluate(SviewPosDir& posdir, double dt=0) = 0;
195
196 /* Modify view angle. */
197 virtual void mouse_drag(double delta_x_deg, double delta_y_deg)
198 {
199 }
200
201 virtual void stream(std::ostream& out) const
202 {
203 out << " <SviewStep>";
204 }
205
206 virtual ~SviewStep() {}
207
208 std::string m_description;
209
210 friend std::ostream& operator<< (std::ostream& out, const SviewStep& step)
211 {
212 out << ' ' << typeid(step).name();
213 step.stream(out);
214 return out;
215 }
216};
217
219/* Info about aircraft with a particular callsign. "" means the user
220aircraft. We use the user aircraft if specified callsign does not exist.
221
222update() must be called before m_root is dereferenced. */
223{
224 Callsign(const std::string& callsign)
225 :
226 m_callsign(callsign)
227 {
228 }
229
230 bool update()
231 /* Returns true if we have changed m_root. */
232 {
233 if (m_callsign == "") {
234 if (m_root) return false;
235 }
236 else if (m_root && m_root->getStringValue("callsign") == m_callsign) {
237 return false;
238 }
239 m_root = nullptr;
240 if (m_callsign != "") {
241 /* Find multiplayer with matching callsign. */
242 SGPropertyNode* c = globals->get_props()->getNode("ai/models/callsigns")->getNode(m_callsign);
243 if (c) {
244 int i = c->getIntValue();
245 m_root = globals->get_props()->getNode("ai/models/multiplayer", i);
246 assert(m_root);
247 }
248 }
249 if (!m_root) {
250 /* Default to user aircraft. */
251 m_root = globals->get_props();
252 }
253 return true;
254 }
255
256 std::string m_callsign;
257 SGPropertyNode_ptr m_root;
258
259};
260
261/* A step that sets position to aircraft origin and direction to aircraft's
262longitudal axis. */
264{
265 SviewStepAircraft(const std::string& callsign)
266 :
267 m_callsign(callsign)
268 {
269 }
270
271 void evaluate(SviewPosDir& posdir, double dt) override
272 {
273 if (m_callsign.update()) {
274 m_longitude = m_callsign.m_root->getNode("position/longitude-deg");
275 m_latitude = m_callsign.m_root->getNode("position/latitude-deg");
276 m_altitude = m_callsign.m_root->getNode("position/altitude-ft");
277 m_heading = m_callsign.m_root->getNode("orientation/true-heading-deg");
278 m_pitch = m_callsign.m_root->getNode("orientation/pitch-deg");
279 m_roll = m_callsign.m_root->getNode("orientation/roll-deg");
280 }
281 posdir.position = SGGeod::fromDegFt(
282 m_longitude->getDoubleValue(),
283 m_latitude->getDoubleValue(),
284 m_altitude->getDoubleValue()
285 );
286
287 posdir.heading = m_heading->getDoubleValue();
288 posdir.pitch = m_pitch->getDoubleValue();
289 posdir.roll = m_roll->getDoubleValue();
290 }
291
292 virtual void stream(std::ostream& out) const
293 {
294 out << " <SviewStepAircraft:" + m_callsign.m_callsign + ">";
295 }
296
297 private:
298
299 Callsign m_callsign;
300
301 SGPropertyNode_ptr m_longitude;
302 SGPropertyNode_ptr m_latitude;
303 SGPropertyNode_ptr m_altitude;
304
305 SGPropertyNode_ptr m_heading;
306 SGPropertyNode_ptr m_pitch;
307 SGPropertyNode_ptr m_roll;
308};
309
310
311/* Moves position by fixed vector, does not change direction.
312
313E.g. for moving from aircraft origin to pilot viewpoint. */
315{
316 SviewStepMove(double forward, double up, double right)
317 :
318 m_offset(-right, -up, -forward)
319 {
320 SG_LOG(SG_VIEW, SG_INFO, "forward=" << forward << " up=" << up << " right=" << right);
321 }
322
323 void evaluate(SviewPosDir& posdir, double dt) override
324 {
325 /* These calculations are copied from View::recalcLookFrom(). */
326
327 /* The rotation rotating from the earth centerd frame to the horizontal
328 local frame. */
329 SGQuatd hlOr = SGQuatd::fromLonLat(posdir.position);
330
331 /* The rotation from the horizontal local frame to the basic view
332 orientation. */
333 SGQuatd hlToBody = SGQuatd::fromYawPitchRollDeg(posdir.heading, posdir.pitch, posdir.roll);
334
335 /* Compute the eyepoints orientation and position wrt the earth
336 centered frame - that is global coorinates. */
337 SGQuatd ec2body = hlOr * hlToBody;
338
339 /* The cartesian position of the basic view coordinate. */
340 SGVec3d position = SGVec3d::fromGeod(posdir.position);
341
342 /* This is rotates the x-forward, y-right, z-down coordinate system the
343 where simulation runs into the OpenGL camera system with x-right, y-up,
344 z-back. */
345 SGQuatd q(-0.5, -0.5, 0.5, 0.5);
346
347 position += (ec2body * q).backTransform(m_offset);
348 posdir.position = SGGeod::fromCart(position);
349 }
350
351 virtual void stream(std::ostream& out) const
352 {
353 out << " <SviewStepMove>" << m_offset;
354 }
355
356 private:
357 SGVec3d m_offset;
358};
359
360/* Modifies heading, pitch and roll by fixed amounts; does not change
361position.
362
363E.g. can be used to preserve direction (relative to aircraft) of Helicopter
364view at the time it was cloned. */
366{
368 double heading,
369 double pitch,
370 double roll,
371 double damping_heading = 0,
372 double damping_pitch = 0,
373 double damping_roll = 0
374 )
375 :
376 m_heading(heading),
377 m_pitch(pitch),
378 m_roll(roll),
379 m_damping_heading(damping_heading, 360 /*m_wrap_max*/),
380 m_damping_pitch(damping_pitch, 360 /*m_wrap_max*/),
381 m_damping_roll(damping_roll, 360 /*m_wrap_max*/)
382 {
383 SG_LOG(SG_VIEW, SG_INFO, "heading=" << heading << " pitch=" << pitch << " roll=" << roll);
384 }
385
386 void evaluate(SviewPosDir& posdir, double dt) override
387 {
388 posdir.heading = m_damping_heading.update(dt, posdir.heading + m_heading);
389 posdir.pitch = m_damping_pitch.update(dt, posdir.pitch + m_pitch);
390 posdir.roll = m_damping_roll.update(dt, posdir.roll + m_roll);
391 }
392
393 virtual void stream(std::ostream& out) const
394 {
395 out << " <SviewStepRotate>"
396 << ' ' << m_heading
397 << ' ' << m_pitch
398 << ' ' << m_roll
399 ;
400 }
401
402 private:
403 double m_heading;
404 double m_pitch;
405 double m_roll;
406 Damping m_damping_heading;
407 Damping m_damping_pitch;
408 Damping m_damping_roll;
409};
410
411/* Modify a view's heading and pitch in response to mouse dragging. */
413{
414 SviewStepMouseDrag(double heading_scale, double pitch_scale)
415 :
416 m_heading_scale(heading_scale),
417 m_pitch_scale(pitch_scale)
418 {}
419
420 void evaluate(SviewPosDir& posdir, double dt) override
421 {
422 posdir.heading += m_heading;
423 posdir.pitch += m_pitch;
424 }
425
426 void mouse_drag(double delta_x_deg, double delta_y_deg) override
427 {
428 m_heading += m_heading_scale * delta_x_deg;
429 m_pitch += m_pitch_scale * delta_y_deg;
430 }
431
434 double m_heading = 0;
435 double m_pitch = 0;
436};
437
438/* Multiply heading, pitch and roll by constants. */
440{
441 SviewStepDirectionMultiply(double heading=0, double pitch=0, double roll=0)
442 :
443 m_heading(heading),
444 m_pitch(pitch),
445 m_roll(roll)
446 {
447 SG_LOG(SG_VIEW, SG_INFO, "heading=" << heading << " pitch=" << pitch << " roll=" << roll);
448 }
449
450 void evaluate(SviewPosDir& posdir, double dt) override
451 {
452 posdir.heading *= m_heading;
453 posdir.pitch *= m_pitch;
454 posdir.roll *= m_roll;
455 }
456
457 virtual void stream(std::ostream& out) const
458 {
459 out << " <SviewStepDirectionMultiply>"
460 << ' ' << m_heading
461 << ' ' << m_pitch
462 << ' ' << m_roll
463 ;
464 }
465
466 private:
467 double m_heading;
468 double m_pitch;
469 double m_roll;
470};
471
472/* Copies current position to posdir.target. Used by SviewEyeTarget()
473to make current position be available as target later on, e.g. by
474SviewStepFinal with to_target=true. */
476{
477 void evaluate(SviewPosDir& posdir, double dt) override
478 {
479 assert(!posdir.target_is_set);
480 posdir.target = posdir.position;
481 posdir.target_is_set = true;
482 }
483
484 virtual void stream(std::ostream& out) const
485 {
486 out << " <SviewStepCopyToTarget>";
487 }
488};
489
490/* Move position to nearest tower. */
492{
493 SviewStepNearestTower(const std::string& callsign)
494 :
495 m_callsign(callsign)
496 {
497 m_description = "Nearest tower";
498 }
499
500 void evaluate(SviewPosDir& posdir, double dt) override
501 {
502 if (m_callsign.update()) {
503 m_latitude = m_callsign.m_root->getNode("sim/tower/latitude-deg", true /*create*/);
504 m_longitude = m_callsign.m_root->getNode("sim/tower/longitude-deg", true /*create*/);
505 m_altitude = m_callsign.m_root->getNode("sim/tower/altitude-ft", true /*create*/);
506 }
507 posdir.position = SGGeod::fromDegFt(
508 m_longitude->getDoubleValue(),
509 m_latitude->getDoubleValue(),
510 m_altitude->getDoubleValue()
511 );
512 posdir.heading = 0;
513 posdir.pitch = 0;
514 posdir.roll = 0;
515 SG_LOG(SG_VIEW, SG_BULK, "moved posdir.postion to: " << posdir.position);
516 }
517
518 virtual void stream(std::ostream& out) const
519 {
520 out << " <SviewStepNearestTower:" + m_callsign.m_callsign + ">";
521 }
522
524 SGPropertyNode_ptr m_latitude;
525 SGPropertyNode_ptr m_longitude;
526 SGPropertyNode_ptr m_altitude;
527};
528
529
530/*
531Converts posdir's eye position/direction to global cartesian
532position/quarternion direction in position2 and direction2, which will be used
533to set the camera parameters.
534
535If posdir.target_is_set is true (i.e. posdir.target is valid), we also change
536the direction to point at the target, using the original direction to determine
537the 'up' vector.
538*/
540{
542 {
543 }
544
545 void evaluate(SviewPosDir& posdir, double dt) override
546 {
547 /* See View::recalcLookFrom(). */
548
549 /* The rotation rotating from the earth centerd frame to the horizontal
550 local frame. */
551 SGQuatd eye_position_direction = SGQuatd::fromLonLat(posdir.position);
552
553 /* The rotation from the horizontal local frame to the basic view
554 orientation. */
555 SGQuatd eye_local_direction = SGQuatd::fromYawPitchRollDeg(posdir.heading, posdir.pitch, posdir.roll);
556
557 /* The cartesian position of the basic view coordinate. */
558 SGVec3d eye_position = SGVec3d::fromGeod(posdir.position);
559
560 /* Compute the eye direction in global coordinates. */
561 SGQuatd eye_direction = eye_position_direction * eye_local_direction;
562
563 if (posdir.target_is_set)
564 {
565 /* Rotate eye direction to point at posdir.target. */
566
567 SGVec3d target_position = SGVec3d::fromGeod(posdir.target);
568
569 /* add target offsets to at_position...
570 Compute the eyepoints orientation and position wrt the earth centered
571 frame - that is global coorinates _absolute_view_pos = eye_position; */
572
573 /* the view direction. */
574 SGVec3d eye_to_target_direction = normalize(target_position - eye_position);
575
576 /* the up directon. */
577 SGVec3d up = eye_direction.backTransform(SGVec3d(0, 0, -1));
578
579 /* rotate -dir to the 2-th unit vector
580 rotate up to 1-th unit vector
581 Note that this matches the OpenGL camera coordinate system with
582 x-right, y-up, z-back. */
583 posdir.direction2 = SGQuatd::fromRotateTo(-eye_to_target_direction, 2, up, 1);
584 }
585 else
586 {
587 /* This is rotates the x-forward, y-right, z-down coordinate system the
588 where simulation runs into the OpenGL camera system with x-right, y-up,
589 z-back. */
590 SGQuatd q(-0.5, -0.5, 0.5, 0.5);
591
592 posdir.direction2 = eye_direction * q;
593 }
594
595 posdir.position2 = eye_position;
596 }
597
598 virtual void stream(std::ostream& out) const
599 {
600 out << " <SviewStepFinal>";
601 }
602};
603
604
605/* Change angle and field of view so that we can see an aircraft and the ground
606immediately below it. */
608{
609 SviewStepAGL(const std::string& callsign, double damping_time)
610 :
611 m_callsign(callsign),
613 {
614 }
615
616 void evaluate(SviewPosDir& posdir, double dt) override
617 {
618 if (m_callsign.update()) {
619 m_chase_distance = -25;
620 if (m_callsign.m_callsign == "") {
621 m_chase_distance = globals->get_props()->getDoubleValue("/sim/chase-distance-m", m_chase_distance);
622 }
623 else {
624 m_chase_distance = m_callsign.m_root->getDoubleValue("set/sim/chase-distance-m", m_chase_distance);
625 }
626 }
627 assert(posdir.target_is_set);
628 double _fov_user_deg = 30;
629 double _configFOV_deg = 30;
630 /* Some aircraft appear to have elevation that is slightly below ground
631 level when on the ground, e.g. SenecaII, which makes get_elevation_m()
632 fail. So we pass a slightly incremented elevation. */
633 double ground_altitude = 0;
634 const simgear::BVHMaterial* material = NULL;
635 SGGeod target0 = posdir.target;
636 SGGeod target_plus = posdir.target;
637 target_plus.setElevationM(target_plus.getElevationM() + 1);
638 bool ok = globals->get_scenery()->get_elevation_m(target_plus, ground_altitude, &material);
639 if (ok) {
640 m_ground_altitude = ground_altitude;
641 }
642 else {
643 /* get_elevation_m() can fail if scenery has been un-cached, which
644 appears to happen quite often with remote multiplayer aircraft, so
645 we preserve the previous ground altitude to give some consistency
646 and avoid confusing zooming when switching between views. */
647 ground_altitude = m_ground_altitude;
648 }
649
650 double h_distance = SGGeodesy::distanceM(posdir.position, posdir.target);
651 if (h_distance == 0) {
652 /* Not sure this should ever happen, but we need to cope with this
653 here otherwise we'll get divide-by-zero. */
654 return;
655 }
656 /* Find vertical region we want to be able to see. */
657 double relative_height_target = posdir.target.getElevationM() - posdir.position.getElevationM();
658 double relative_height_ground = ground_altitude - posdir.position.getElevationM();
659
660 /* We expand the field of view so that it hopefully shows the whole
661 aircraft and a little more of the ground.
662
663 We use chase-distance as a crude measure of the aircraft's
664 size. There doesn't seem to be any more definitive information.
665
666 We damp our measure of ground level, to avoid the view jumping around
667 if an aircraft flies over buildings. */
668
669 relative_height_ground -= 2;
670
671 double aircraft_size_vertical = fabs(m_chase_distance) * 0.3;
672 double aircraft_size_horizontal = fabs(m_chase_distance) * 0.9;
673
674 double relative_height_target_plus = relative_height_target + aircraft_size_vertical;
675 double relative_height_ground_ = relative_height_ground;
676 relative_height_ground = relative_height_ground_damping.update(dt, relative_height_ground);
677 if (relative_height_ground > relative_height_target) {
678 /* Damping of relative_height_ground can result in it being
679 temporarily above the aircraft, so we ensure the aircraft is
680 visible. */
681 relative_height_ground = relative_height_ground_damping.reset(relative_height_ground_);
682 }
683
684 /* Not implemented yet: apply scaling from user field of view
685 setting, altering only relative_height_ground so that the aircraft
686 is always in view. */
687 {
688 double delta = relative_height_target_plus - relative_height_ground;
689 delta *= (_fov_user_deg / _configFOV_deg);
690 relative_height_ground = relative_height_target_plus - delta;
691 }
692
693 double angle_v_target = atan(relative_height_target_plus / h_distance);
694 double angle_v_ground = atan(relative_height_ground / h_distance);
695
696 /* The target we want to use is determined by the midpoint of the two
697 angles we've calculated. */
698 double angle_v_mid = (angle_v_target + angle_v_ground) / 2;
699 double posdir_target_old_elevation = posdir.target.getElevationM();
700 posdir.target.setElevationM(posdir.position.getElevationM() + h_distance * tan(angle_v_mid));
701
702 /* Set required vertical field of view. We use fabs to avoid
703 things being upside down if target is below ground level (e.g. new
704 multiplayer aircraft are briefly at -9999ft). */
705 double fov_v = fabs(angle_v_target - angle_v_ground);
706
707 /* Set required horizontal field of view so that we can see entire
708 horizontal extent of the aircraft (assuming worst case where
709 airplane is horizontal and square-on to viewer). */
710 double fov_h = 2 * atan(aircraft_size_horizontal / 2 / h_distance);
711
712 posdir.fov_v = fov_v * 180 / pi;
713 posdir.fov_h = fov_h * 180 / pi;
714
715 bool verbose = false;
716 if (0) {
717 static time_t t0 = 0;
718 time_t t = time(NULL);
719 if (0 && t - t0 >= 3) {
720 t0 = t;
721 verbose = true;
722 }
723 }
724 if (verbose) SG_LOG(SG_VIEW, SG_ALERT, ""
725 << " target0=" << target0
726 << " fov_v=" << fov_v * 180/pi
727 << " ground_altitude=" << ground_altitude
728 << " relative_height_target_plus=" << relative_height_target_plus
729 << " h_distance=" << h_distance
730 << " relative_height_ground=" << relative_height_ground
731 << " m_chase_distance=" << m_chase_distance
732 << " angle_v_mid=" << angle_v_mid * 180/pi
733 << " posdir_target_old_elevation=" << posdir_target_old_elevation
734 << " posdir.target=" << posdir.target
735 );
736 }
737
742};
743
744/* A step that takes the SviewPosDir's eye and target positions and treats
745them as local and remote points respectively. We choose an eye position and
746direction such that the local and remote points are both visible, with the
747original eye position in the foreground at a fixed distance. */
749{
751 {
753 m_angle_rad = 15 * pi / 180;
754 }
755
756 SviewStepDouble(SGPropertyNode* config)
757 {
758 m_local_chase_distance = config->getDoubleValue("chase-distance");
759 m_angle_rad = config->getDoubleValue("angle") * pi / 180;
760 }
761 void evaluate(SviewPosDir& posdir, double dt) override
762 {
763 /*
764 We choose eye position so that we show the local aircraft a fixed
765 amount below the view midpoint, and the remote aircraft the same fixed
766 amount above the view midpoint.
767
768 L: middle of local aircraft.
769 R: middle of remote aircraft.
770 E: desired eye-point
771
772 ---- R
773 / \
774 E |
775 | L | .................... H (horizon)
776 | |
777 \ /
778 ----
779
780 We require that:
781
782 EL is local aircraft's chase-distance so that local aircraft is in
783 perfect view.
784
785 Angle LER is fixed to give good view of both aircraft in
786 window. (Should be related to the vertical angular size of the
787 window, but at the moment we use a fixed value.)
788
789 We need to calculate angle RLE, and add to HLR, in order to find
790 position of E (eye) relative to L. Then for view pitch we use midpoint
791 of angle of ER and angle EL, so that local and remote aircraft are
792 symmetrically below and above the centre of the view.
793
794 We find angle RLE by using cosine rule twice in the triangle RLE:
795 ER^2 = EL^2 + LR^2 - 2*EL*LR*cos(RLE)
796 LR^2 = ER^2 + EL^2 - 2*ER*EL*cos(LER)
797
798 Wen end up with a quadratic for ER with solution:
799 ER = EL * cos(LER) + sqrt(LR^2 - EL^22*sin(LER)^2)
800 (We discard the -sqrt because it ends up with ER being negative.)
801
802 and:
803 cos(RLE) = (LR^2 + LE^2 - ER^2) / (2*LE*LR)
804
805 So we can find RLE using acos().
806 */
807 bool debug = false;
808 static time_t t0 = 0;
809 time_t t = time(NULL);
810 if (0 && t - t0 > 3) {
811 t0 = t;
812 debug = true;
813 }
814
815 assert(posdir.target_is_set);
816 SviewPosDir posdir_remote = posdir;
817 SviewPosDir posdir_local = posdir;
818
819 posdir_local.target = posdir_local.position;
820
821 if (debug) {
822 SG_LOG(SG_VIEW, SG_ALERT, " posdir =" << posdir);
823 SG_LOG(SG_VIEW, SG_ALERT, " posdir_local =" << posdir_local);
824 SG_LOG(SG_VIEW, SG_ALERT, " posdir_remote=" << posdir_remote);
825 }
826
827 /* Create cartesian coordinates so we can calculate distance <lr>. */
828 SGVec3d local_pos = SGVec3d::fromGeod(posdir_local.target);
829 SGVec3d remote_pos = SGVec3d::fromGeod(posdir_remote.target);
830 double lr = sqrt(distSqr(local_pos, remote_pos));
831
832 /* Desired angle between local and remote aircraft in final view. */
833 double ler = m_angle_rad;
834
835 /* Distance of eye from local aircraft. */
836 double le = m_local_chase_distance;
837
838 /* Find <er>, the distance of eye from remote aircraft. Have to be
839 careful to cope when there is no solution if remote is too close, and
840 choose the +ve sqrt(). */
841 double er_root_term = lr*lr - le*le*sin(ler)*sin(ler);
842 if (er_root_term < 0) {
843 /* This can happen if LR is too small, i.e. remote aircraft too
844 close to local aircraft. */
845 er_root_term = 0;
846 }
847 double er = le * cos(ler) + sqrt(er_root_term);
848
849 /* Now find rle, angle at local aircraft between vector to remote
850 aircraft and vector to desired eye position. Again we have to cope when
851 a real solution is not possible. */
852 double cos_rle = (lr*lr + le*le - er*er) / (2*le*lr);
853 if (cos_rle > 1) cos_rle = 1;
854 if (cos_rle < -1) cos_rle = -1;
855 double rle = acos(cos_rle);
856 double rle_deg = rle * 180 / pi;
857
858 /* Now find the actual eye position. We do this by calculating heading
859 and pitch from local aircraft L to eye position E, then using a
860 temporary SviewStepMove. */
861 double lr_vertical = posdir_remote.target.getElevationM() - posdir_local.target.getElevationM();
862 double lr_horizontal = SGGeodesy::distanceM(posdir_local.target, posdir_remote.target);
863 double hlr = atan2(lr_vertical, lr_horizontal);
864 posdir_local.heading = SGGeodesy::courseDeg(
865 posdir_local.target,
866 posdir_remote.target
867 );
868 posdir_local.pitch = (hlr + rle) * 180 / pi;
869 posdir_local.roll = 0;
870 auto move = SviewStepMove(le, 0, 0);
871 move.evaluate(posdir_local, 0 /*dt*/);
872
873 /* At this point, posdir_local.position is eye position. We make
874 posdir_local.pitch point from this eye position to halfway between the
875 remote and local aircraft. */
876 double er_vertical = posdir_remote.target.getElevationM()
877 - posdir_local.position.getElevationM();
878 double her = asin(er_vertical / er);
879 double hel = (hlr + rle) - pi;
880 posdir_local.pitch = (her + hel) / 2 * 180 / pi;
881 posdir = posdir_local;
882
883 /* Need to ensure that SviewStepFinal will not rotate the view to point
884 at posdir.target. */
885 posdir.target_is_set = false;
886
887 if (debug) {
888 SG_LOG(SG_VIEW, SG_ALERT, ""
889 << " lr=" << lr
890 << " ler=" << ler
891 << " er_root_term=" << er_root_term
892 << " er=" << er
893 << " cos_rle=" << cos_rle
894 << " rle_deg=" << rle_deg
895 << " lr_vertical=" << lr_vertical
896 << " lr_horizontal=" << lr_horizontal
897 << " hlr=" << hlr
898 << " posdir_local=" << posdir_local
899 << " posdir_remote=" << posdir_remote
900 );
901 }
902 }
903
906};
907
908/* Contains a list of SviewStep's that are used to evaluate a SviewPosDir
909(position and orientation)
910*/
912{
913 void add_step(std::shared_ptr<SviewStep> step)
914 {
915 m_steps.push_back(step);
916 }
917
918 void add_step(SviewStep* step)
919 {
920 return add_step(std::shared_ptr<SviewStep>(step));
921 }
922
923 void evaluate(SviewPosDir& posdir, double dt, bool debug=false)
924 {
925 if (debug) SG_LOG(SG_VIEW, SG_ALERT, "evaluating m_name=" << m_name);
926 for (auto step: m_steps) {
927 step->evaluate(posdir, dt);
928 if (debug) SG_LOG(SG_VIEW, SG_ALERT, "posdir=" << posdir);
929 }
930 };
931
932 std::string m_name;
933 std::vector<std::shared_ptr<SviewStep>> m_steps;
934
935 friend std::ostream& operator << (std::ostream& out, const SviewSteps& viewpos)
936 {
937 out << viewpos.m_name << " (" << viewpos.m_steps.size() << ")";
938 for (auto step: viewpos.m_steps) {
939 out << " " << *step;
940 }
941 return out;
942 }
943};
944
945
946/* Base class for views.
947*/
949{
950 SviewView(osgViewer::View* osg_view)
951 :
952 m_osg_view(osg_view)
953 {
954 s_id += 1;
955 }
956
957 /* Description that also includes integer identifier. */
958 const std::string description2()
959 {
960 char buffer[32];
961 snprintf(buffer, sizeof(buffer), "[%i] ", s_id);
962 return buffer + description();
963 }
964
965 /* Description of this view, used in window title etc. */
966 virtual const std::string description() = 0;
967
968 virtual ~SviewView()
969 {
970 if (!m_osg_view) {
971 return;
972 }
973 osgViewer::ViewerBase* viewer_base = m_osg_view->getViewerBase();
974 auto composite_viewer = dynamic_cast<osgViewer::CompositeViewer*>(viewer_base);
975 assert(composite_viewer);
976 for (unsigned i=0; i<composite_viewer->getNumViews(); ++i) {
977 osgViewer::View* view = composite_viewer->getView(i);
978 SG_LOG(SG_VIEW, SG_DEBUG, "composite_viewer view i=" << i << " view=" << view);
979 }
980 SG_LOG(SG_VIEW, SG_DEBUG, "removing m_osg_view=" << m_osg_view);
981 composite_viewer->stopThreading();
982 composite_viewer->removeView(m_osg_view);
983 composite_viewer->startThreading();
984 }
985
986 /* Returns false if window has been closed. */
987 virtual bool update(double dt) = 0;
988
989 virtual void mouse_drag(double delta_x_deg, double delta_y_deg) = 0;
990
991 /* Sets this view's camera position/orientation from <posdir>. */
993 {
994 /* FGViewMgr::update(). */
995 osg::Vec3d position = toOsg(posdir.position2);
996 osg::Quat orientation = toOsg(posdir.direction2);
997
998 osg::Camera* camera = m_osg_view->getCamera();
999 osg::Matrix old_m = camera->getViewMatrix();
1000 /* This calculation is copied from CameraGroup::update(). */
1001 const osg::Matrix new_m(
1002 osg::Matrix::translate(-position)
1003 * osg::Matrix::rotate(orientation.inverse())
1004 );
1005 SG_LOG(SG_VIEW, SG_BULK, "old_m: " << old_m);
1006 SG_LOG(SG_VIEW, SG_BULK, "new_m: " << new_m);
1007 camera->setViewMatrix(new_m);
1008
1009 if (posdir.fov_v || posdir.fov_h) {
1010 /* Update zoom to accomodate required vertical/horizontal field of
1011 views. */
1012 double fovy;
1013 double aspect_ratio;
1014 double zNear;
1015 double zFar;
1016 camera->getProjectionMatrixAsPerspective(fovy, aspect_ratio, zNear, zFar);
1017 if (posdir.fov_v) {
1018 camera->setProjectionMatrixAsPerspective(
1019 posdir.fov_v,
1020 aspect_ratio,
1021 zNear,
1022 zFar
1023 );
1024 if (posdir.fov_h) {
1025 /* Increase fov if necessary so that we include both
1026 fov_h and fov_v. */
1027 double aspect_ratio;
1028 camera->getProjectionMatrixAsPerspective(
1029 fovy,
1030 aspect_ratio,
1031 zNear,
1032 zFar
1033 );
1034 if (fovy * aspect_ratio < posdir.fov_h) {
1035 camera->setProjectionMatrixAsPerspective(
1036 posdir.fov_v * posdir.fov_h / (fovy * aspect_ratio),
1037 aspect_ratio,
1038 zNear,
1039 zFar
1040 );
1041 }
1042 }
1043 }
1044 else {
1045 camera->setProjectionMatrixAsPerspective(
1046 posdir.fov_h / aspect_ratio,
1047 aspect_ratio,
1048 zNear,
1049 zFar
1050 );
1051 }
1052 }
1053 }
1054
1055 osgViewer::View* m_osg_view = nullptr;
1056 simgear::compositor::Compositor* m_compositor = nullptr;
1057
1059 double m_mouse_x = 0;
1060 double m_mouse_y = 0;
1061
1062 static int s_id;
1063};
1064
1065/* Converts legacy damping values (e.g. at-model-heading-damping) to a damping
1066time suitable for use by our Damping class. */
1067static double legacy_damping_time(double damping)
1068{
1069 if (damping <= 0) return 0;
1070 return 1 / damping;
1071}
1072
1073int SviewView::s_id = 0;
1074
1075/* A view defined by a series of steps that defines an eye and a target. Used
1076for clones of main window views. */
1078{
1079 SviewViewEyeTarget(osgViewer::View* view, SGPropertyNode* config)
1080 :
1081 SviewView(view)
1082 {
1083 if (config->getStringValue("type") == "legacy")
1084 {
1085 /* Legacy view. */
1086 std::string callsign = config->getStringValue("callsign");
1087 std::string callsign_desc = (callsign == "") ? "" : ": " + callsign;
1088 SG_LOG(SG_VIEW, SG_INFO, "callsign=" << callsign);
1089
1090 if (config->getBoolValue("view/config/eye-fixed")) {
1091 SG_LOG(SG_VIEW, SG_INFO, "eye-fixed");
1092 m_steps.m_name = std::string() + "legacy tower" + callsign_desc;
1093
1094 if (config->getStringValue("view/type") == "lookat") {
1095 /* E.g. Tower view or Tower view AGL. */
1096
1097 /* Add a step to move to centre of aircraft. target offsets appear
1098 to have reversed sign compared to what we require. */
1099 m_steps.add_step(new SviewStepAircraft(callsign));
1100 m_steps.add_step(new SviewStepMove(
1101 -config->getDoubleValue("view/config/target-z-offset-m"),
1102 -config->getDoubleValue("view/config/target-y-offset-m"),
1103 -config->getDoubleValue("view/config/target-x-offset-m")
1104 ));
1105
1106 /* Add a step to set pitch and roll to zero, otherwise view
1107 from tower (as calculated by SviewStepFinal) rolls/pitches
1108 with aircraft. */
1109 m_steps.add_step(new SviewStepDirectionMultiply(
1110 1 /* heading */,
1111 0 /* pitch */,
1112 0 /* roll */
1113 ));
1114
1115 /* Current position is the target, so add a step that copies it to
1116 SviewPosDir.target. */
1117 m_steps.add_step(new SviewStepCopyToTarget);
1118
1119 /* Added steps to set .m_eye up so that it looks from the nearest
1120 tower. */
1121 m_steps.add_step(new SviewStepNearestTower(callsign));
1122
1123 if (config->getBoolValue("view/config/lookat-agl")) {
1124 double damping = config->getDoubleValue("view/config/lookat-agl-damping");
1125 double damping_time = log(10) * legacy_damping_time(damping);
1126 SG_LOG(SG_VIEW, SG_DEBUG, "lookat-agl");
1127 m_steps.add_step(new SviewStepAGL(callsign, damping_time));
1128 }
1129
1130 m_steps.add_step(new SviewStepFinal);
1131
1132 /* Would be nice to add a step that moves towards the
1133 target a little, like we do with Tower view look from. But
1134 simply adding a SviewStepMove doesn't work because the
1135 preceding SviewStepFinal has finalised the view angle etc.
1136 */
1137 }
1138 else {
1139 /* E.g. Tower view look from. */
1140 m_steps.add_step(new SviewStepNearestTower(callsign));
1141
1142 /* Looks like Tower view look from's heading-offset is reversed. */
1143 m_steps.add_step(new SviewStepRotate(
1144 -globals->get_props()->getDoubleValue("sim/current-view/heading-offset-deg"),
1145 globals->get_props()->getDoubleValue("sim/current-view/pitch-offset-deg"),
1146 globals->get_props()->getDoubleValue("sim/current-view/roll-offset-deg")
1147 ));
1148 /* Move forward a little as though one was walking towards
1149 the window inside the tower; this might improve view of
1150 aircraft near the tower on the ground. Ideally each tower
1151 would have a 'diameter' property, but for now we just use
1152 a hard-coded value. Also it would be nice to make this
1153 movement not change the height. */
1154 m_steps.add_step(new SviewStepMove(1, 0, 0));
1155
1156 m_steps.add_step(new SviewStepMouseDrag(
1157 1 /*mouse_heading_scale*/,
1158 1 /*mouse_pitch_scale*/
1159 ));
1160 m_steps.add_step(new SviewStepFinal);
1161 }
1162 }
1163 else {
1164 SG_LOG(SG_VIEW, SG_INFO, "not eye-fixed");
1165 /* E.g. Pilot view and Helicopter/Chase views. */
1166
1167 SGPropertyNode* global_sim_view = globals->get_props()
1168 ->getNode("sim/view", config->getIntValue("view-number-raw"));
1169
1170 if (global_sim_view->getStringValue("type") == "lookat") {
1171 /* E.g. Helicopter view and Chase views. */
1172 m_steps.m_name = std::string() + "legacy helicopter/chase" + callsign_desc;
1173 m_steps.add_step(new SviewStepAircraft(callsign));
1174
1175 /* Move to centre of aircraft. config/target-z-offset-m
1176 seems to use +ve to indicate movement backwards relative
1177 to the aircraft, so we need to negate the value we pass to
1178 SviewStepMove(). */
1179 m_steps.add_step(new SviewStepMove(
1180 -config->getDoubleValue("view/config/target-z-offset-m"),
1181 -config->getDoubleValue("view/config/target-y-offset-m"),
1182 -config->getDoubleValue("view/config/target-x-offset-m")
1183 ));
1184
1185 /* Add a step that crudely preserves or don't preserve
1186 aircraft's heading, pitch and roll; this enables us to mimic
1187 Helicopter and Chase views. In theory we should evaluate the
1188 specified paths, but in practise we only need to multiply
1189 current values by 0 or 1. */
1190 assert(global_sim_view);
1191 m_steps.add_step(new SviewStepDirectionMultiply(
1192 global_sim_view->getStringValue("config/eye-heading-deg-path")[0] ? 1 : 0,
1193 global_sim_view->getStringValue("config/eye-pitch-deg-path")[0] ? 1 : 0,
1194 global_sim_view->getStringValue("config/eye-roll-deg-path")[0] ? 1 : 0
1195 ));
1196
1197 /* Add a step that applies the current view rotation. We
1198 use -ve heading and pitch because in the legacy
1199 viewing system's Helicopter view etc, increments to
1200 heading-offset-deg and pitch-offset-deg actually correspond
1201 to a decreasing actual heading and pitch values. */
1202 double damping_heading = legacy_damping_time(config->getDoubleValue("view/config/at-model-heading-damping"));
1203 double damping_pitch = legacy_damping_time(config->getDoubleValue("view/config/at-model-pitch-damping"));
1204 double damping_roll = legacy_damping_time(config->getDoubleValue("view/config/at-model-roll-damping"));
1205
1206 m_steps.add_step(new SviewStepRotate(
1207 -globals->get_props()->getDoubleValue("sim/current-view/heading-offset-deg"),
1208 -globals->get_props()->getDoubleValue("sim/current-view/pitch-offset-deg"),
1209 globals->get_props()->getDoubleValue("sim/current-view/roll-offset-deg"),
1210 damping_heading,
1211 damping_pitch,
1212 damping_roll
1213 ));
1214
1215 m_steps.add_step(new SviewStepMouseDrag(
1216 1 /*mouse_heading_scale*/,
1217 -1 /*mouse_pitch_scale*/
1218 ));
1219
1220 /* Set current position as target. This isn't actually
1221 necessary for this view because the direction implied by
1222 heading/pitch/roll will still point to the centre of the
1223 aircraft after the following steps. But it allows double
1224 views to work better - they will use the centre of the
1225 aircraft instead of the eye position. */
1226 m_steps.add_step(new SviewStepCopyToTarget);
1227
1228 /* Add step that moves eye away from aircraft.
1229 config/z-offset-m defaults to /sim/chase-distance-m (see
1230 fgdata:defaults.xml) which is -ve, e.g. -25m. */
1231 m_steps.add_step(new SviewStepMove(
1232 config->getDoubleValue("view/config/z-offset-m"),
1233 -config->getDoubleValue("view/config/y-offset-m"),
1234 config->getDoubleValue("view/config/x-offset-m")
1235 ));
1236
1237 /* Finally add a step that converts
1238 lat,lon,height,heading,pitch,roll into SGVec3d position and
1239 SGQuatd orientation. */
1240 m_steps.add_step(new SviewStepFinal);
1241 }
1242 else {
1243 /* E.g. pilot view.
1244
1245 Add steps to move to the pilot's eye position.
1246
1247 config/z-offset-m seems to be +ve when moving backwards
1248 relative to the aircraft, so we need to negate the value we
1249 pass to SviewStepMove(). */
1250 m_steps.m_name = std::string() + "legacy pilot" + callsign_desc;
1251 m_steps.add_step(new SviewStepAircraft(callsign));
1252 m_steps.add_step(new SviewStepMove(
1253 -config->getDoubleValue("view/config/z-offset-m"),
1254 -config->getDoubleValue("view/config/y-offset-m"),
1255 -config->getDoubleValue("view/config/x-offset-m")
1256 ));
1257
1258 double current_heading_offset = globals->get_props()->getDoubleValue("sim/current-view/heading-offset-deg");
1259 double current_pitch_offset = globals->get_props()->getDoubleValue("sim/current-view/pitch-offset-deg");
1260 double current_roll_offset = globals->get_props()->getDoubleValue("sim/current-view/roll-offset-deg");
1261 /*double default_heading_offset = config->getDoubleValue("view/config/heading-offset-deg");
1262 double default_pitch_offset = config->getDoubleValue("view/config/pitch-offset-deg");
1263 double default_roll_offset = config->getDoubleValue("view/config/roll-offset-deg");*/
1264
1265 /* Apply final rotation. */
1266 m_steps.add_step(new SviewStepRotate(
1267 current_heading_offset,
1268 current_pitch_offset,
1269 current_roll_offset
1270 ));
1271
1272 m_steps.add_step(new SviewStepMouseDrag(
1273 1 /*mouse_heading_scale*/,
1274 1 /*mouse_pitch_scale*/
1275 ));
1276 m_steps.add_step(new SviewStepFinal);
1277 SG_LOG(SG_VIEW, SG_INFO, "m_steps=" << m_steps);
1278 }
1279 }
1280 }
1281 else
1282 {
1283 /* New-style Sview specification, where each step is specified for
1284 us. */
1285 simgear::PropertyList steps = config->getChildren("step");
1286 if (steps.empty()) {
1287 throw std::runtime_error(std::string() + "No steps specified");
1288 }
1289 for (SGPropertyNode* step: steps) {
1290 std::string type = step->getStringValue("type");
1291 if (0) {}
1292 else if (type == "aircraft") {
1293 std::string callsign = step->getStringValue("callsign");
1294 m_steps.add_step(new SviewStepAircraft(callsign));
1295 if (callsign != "") {
1296 m_steps.m_name += " callsign=" + callsign;
1297 }
1298 }
1299 else if (type == "move") {
1300 m_steps.add_step(new SviewStepMove(
1301 step->getDoubleValue("forward"),
1302 step->getDoubleValue("up"),
1303 step->getDoubleValue("right")
1304 ));
1305 }
1306 else if (type == "direction-multiply") {
1307 m_steps.add_step(new SviewStepDirectionMultiply(
1308 step->getDoubleValue("heading"),
1309 step->getDoubleValue("pitch"),
1310 step->getDoubleValue("roll")
1311 ));
1312 }
1313 else if (type == "copy-to-target") {
1314 m_steps.add_step(new SviewStepCopyToTarget);
1315 }
1316 else if (type == "nearest-tower") {
1317 std::string callsign = step->getStringValue("callsign");
1318 m_steps.add_step(new SviewStepNearestTower(callsign));
1319 m_steps.m_name += " tower";
1320 if (callsign != "") m_steps.m_name += " callsign=" + callsign;
1321 }
1322 else if (type == "rotate") {
1323 m_steps.add_step(new SviewStepRotate(
1324 step->getDoubleValue("heading"),
1325 step->getDoubleValue("pitch"),
1326 step->getDoubleValue("roll"),
1327 step->getDoubleValue("damping-heading"),
1328 step->getDoubleValue("damping-pitch"),
1329 step->getDoubleValue("damping-roll")
1330 ));
1331 }
1332 else if (type == "rotate-current-view") {
1333 m_steps.add_step(new SviewStepRotate(
1334 globals->get_props()->getDoubleValue("heading"),
1335 globals->get_props()->getDoubleValue("pitch"),
1336 globals->get_props()->getDoubleValue("roll")
1337 ));
1338 }
1339 else if (type == "mouse-drag")
1340 {
1341 m_steps.add_step(new SviewStepMouseDrag(
1342 step->getDoubleValue("heading-scale", 1),
1343 step->getDoubleValue("pitch-scale", 1)
1344 ));
1345 }
1346 else if (type == "double") {
1347 m_steps.add_step(new SviewStepDouble(step));
1348 m_steps.m_name += " double";
1349 }
1350 else if (type == "agl") {
1351 std::string callsign = step->getStringValue("callsign");
1352 double damping_time = step->getDoubleValue("damping-time");
1353 m_steps.add_step(new SviewStepAGL(callsign, damping_time));
1354 }
1355 else {
1356 throw std::runtime_error(std::string() + "Unrecognised step name: '" + type + "'");
1357 }
1358 }
1359 m_steps.add_step(new SviewStepFinal);
1360 }
1361 SG_LOG(SG_VIEW, SG_DEBUG, "m_steps=" << m_steps);
1362 }
1363
1364 /* Construct a double view using two step sequences. */
1366 osgViewer::View* view,
1367 SGPropertyNode* config,
1368 SviewSteps& a,
1369 SviewSteps& b
1370 )
1371 :
1372 SviewView(view)
1373 {
1374 std::string type = config->getStringValue("type");
1375 if (type.empty()) {
1376 throw std::runtime_error("double-type not specified");
1377 }
1378 if (type == "last_pair" || type == "last_pair_double")
1379 {
1380 /* Copy steps from <b> that will set .target. */
1381 for (auto step: b.m_steps)
1382 {
1383 if (0
1384 || dynamic_cast<SviewStepCopyToTarget*>(step.get())
1385 || dynamic_cast<SviewStepFinal*>(step.get())
1386 )
1387 {
1388 break;
1389 }
1390 m_steps.add_step(step);
1391 }
1392 m_steps.add_step(new SviewStepCopyToTarget);
1393
1394 /* Copy steps from <a> that will set .position. */
1395 for (auto step: a.m_steps)
1396 {
1397 if (0
1398 || dynamic_cast<SviewStepCopyToTarget*>(step.get())
1399 || dynamic_cast<SviewStepFinal*>(step.get())
1400 )
1401 {
1402 break;
1403 }
1404 m_steps.add_step(step);
1405 }
1406
1407 if (type == "last_pair_double")
1408 {
1409 /* We need a final SviewStepDouble step. */
1410 m_steps.add_step(new SviewStepDouble);
1411 m_steps.m_name = std::string() + " double: " + a.m_name + " - " + b.m_name;
1412 }
1413 else
1414 {
1415 m_steps.m_name = std::string() + " pair: " + a.m_name + " - " + b.m_name;
1416 }
1417 m_steps.add_step(new SviewStepFinal);
1418 SG_LOG(SG_VIEW, SG_INFO, " m_steps:" << m_steps);
1419 }
1420 else {
1421 throw std::runtime_error(std::string("Unrecognised double view: ") + type);
1422 }
1423
1424 /* Disable our mouse_drag() method - doesn't make sense for double views. */
1425 m_mouse_drag = false;
1426 SG_LOG(SG_VIEW, SG_DEBUG, "m_steps=" << m_steps);
1427 }
1428
1429 const std::string description() override
1430 {
1431 return m_steps.m_name;
1432 }
1433
1434 bool update(double dt) override
1435 {
1436 bool valid = m_osg_view->getCamera()->getGraphicsContext()->valid();
1437 SG_LOG(SG_VIEW, SG_BULK, "valid=" << valid);
1438 if (!valid) return false;
1439
1440 SviewPosDir posdir;
1441 bool debug = false;
1442 if (m_debug) {
1443 time_t t = time(NULL) / 10;
1444 if (t != m_debug_time) {
1445 m_debug_time = t;
1446 debug = true;
1447 }
1448 }
1449 if (debug) SG_LOG(SG_VIEW, SG_INFO, "evaluating m_steps:");
1450 m_steps.evaluate(posdir, dt, debug);
1451
1452 posdir_to_view(posdir);
1453 return true;
1454 }
1455
1456 void mouse_drag(double delta_x_deg, double delta_y_deg) override
1457 {
1458 if (!m_mouse_drag) return;
1459 for (auto step: m_steps.m_steps) {
1460 step->mouse_drag(delta_x_deg, delta_y_deg);
1461 }
1462 }
1463
1465 bool m_mouse_drag = true;
1466 bool m_debug = false;
1467 time_t m_debug_time = 0;
1468};
1469
1470
1471/* All our view windows. */
1472static std::vector<std::shared_ptr<SviewView>> s_views;
1473
1474/* Recent views, for use by SviewAddLastPair(). */
1475static std::deque<std::shared_ptr<SviewViewEyeTarget>> s_recent_views;
1476
1477
1478/* Returns an independent property tree that defines an Sview that is a copy of
1479the current view. */
1480static SGPropertyNode_ptr SviewConfigForCurrentView()
1481{
1482 SGPropertyNode_ptr config = new SGPropertyNode;
1483 int view_number_raw = globals->get_props()->getIntValue("/sim/current-view/view-number-raw");
1484 config->setIntValue("view-number-raw", view_number_raw);
1485 SGPropertyNode* global_view = globals->get_props()->getNode("/sim/view", view_number_raw /*index*/, true /*create*/);
1486 std::string root_path = global_view->getStringValue("config/root"); /* "" or /ai/models/multiplayer[]. */
1487 SGPropertyNode* root = globals->get_props()->getNode(root_path);
1488 std::string callsign = root->getStringValue("callsign");
1489
1490 config->setStringValue("type", "legacy");
1491 config->setStringValue("callsign", callsign);
1492 SGPropertyNode* config_view = config->getNode("view", true /*create*/);
1493 if (callsign == "") {
1494 /* User aircraft. */
1495 copyProperties(globals->get_props()->getNode("/sim/view", view_number_raw), config_view);
1496 }
1497 else {
1498 /* Multiplayer aircraft. */
1499 copyProperties(root->getNode("set/sim/view", view_number_raw), config_view);
1500 }
1501
1502 config->setDoubleValue(
1503 "direction-delta/heading",
1504 globals->get_props()->getDoubleValue("sim/current-view/heading-offset-deg")
1505 );
1506 config->setDoubleValue(
1507 "direction-delta/pitch",
1508 globals->get_props()->getDoubleValue("sim/current-view/pitch-offset-deg")
1509 );
1510 config->setDoubleValue(
1511 "direction-delta/roll",
1512 globals->get_props()->getDoubleValue("sim/current-view/roll-offset-deg")
1513 );
1514
1515 config->setDoubleValue("zoom-delta", 1);
1516
1517 SG_LOG(SG_VIEW, SG_INFO, "returning:\n" << writePropertiesInline(config, true /*write_all*/));
1518 return config;
1519}
1520
1521
1523{
1524 if (s_recent_views.size() >= 2) {
1525 s_recent_views.pop_front();
1526 }
1527 /* Make a dummy view whose eye and target members will copy the current
1528 view. */
1529 SGPropertyNode_ptr config = SviewConfigForCurrentView();
1530 std::shared_ptr<SviewViewEyeTarget> v(new SviewViewEyeTarget(nullptr /*view*/, config));
1531 s_recent_views.push_back(v);
1532 SG_LOG(SG_VIEW, SG_DEBUG, "Have pushed view: " << v);
1533}
1534
1535
1536void SviewUpdate(double dt)
1537{
1538 bool verbose = 0;
1539 for (size_t i=0; i<s_views.size(); /* incremented in loop*/) {
1540 if (verbose) {
1541 SG_LOG(SG_VIEW, SG_INFO, "updating i=" << i
1542 << ": " << s_views[i]->m_osg_view
1543 << ' ' << s_views[i]->m_compositor
1544 );
1545 }
1546 bool valid = s_views[i]->update(dt);
1547 if (valid) {
1548 const osg::Matrix& view_matrix = s_views[i]->m_osg_view->getCamera()->getViewMatrix();
1549 const osg::Matrix& projection_matrix = s_views[i]->m_osg_view->getCamera()->getProjectionMatrix();
1550 s_views[i]->m_compositor->update(view_matrix, projection_matrix);
1551 i += 1;
1552 }
1553 else {
1554 /* Window has been closed.
1555
1556 This doesn't work reliably with OpenSceneGraph-3.4. We seem to
1557 sometimes get valid=false when a different window from s_views[i]
1558 is closed. And sometimes OSG seems to end up sending a close-window
1559 even to the main window when the user has closed an sview window.
1560 */
1561 auto view_it = s_views.begin() + i;
1562 SG_LOG(SG_VIEW, SG_INFO, "deleting SviewView i=" << i << ": " << (*view_it)->description2());
1563 for (size_t j=0; j<s_views.size(); ++j) {
1564 SG_LOG(SG_VIEW, SG_INFO, " " << j
1565 << ": " << s_views[j]->m_osg_view
1566 << ' ' << s_views[j]->m_compositor
1567 );
1568 }
1569 s_views.erase(view_it);
1570
1571 for (size_t j=0; j<s_views.size(); ++j) {
1572 SG_LOG(SG_VIEW, SG_INFO, " " << j
1573 << ": " << s_views[j]->m_osg_view
1574 << ' ' << s_views[j]->m_compositor
1575 );
1576 }
1577 verbose = true;
1578 }
1579 }
1580}
1581
1583{
1584 s_views.clear();
1585}
1586
1587
1588/* These are set by SViewSetCompositorParams(). */
1589static osg::ref_ptr<simgear::SGReaderWriterOptions> s_compositor_options;
1590static std::string s_compositor_path;
1591
1592
1593struct EventHandler : osgGA::EventHandler
1594{
1595 bool handle(osgGA::Event* event, osg::Object* object, osg::NodeVisitor* nv) override
1596 {
1597 osgGA::GUIEventAdapter* ea = event->asGUIEventAdapter();
1598 if (ea) {
1599 osgGA::GUIEventAdapter::EventType et = ea->getEventType();
1600 if (et != osgGA::GUIEventAdapter::FRAME) {
1601 SG_LOG(SG_GENERAL, SG_BULK, "sview event handler called. ea->getEventType()=" << ea->getEventType());
1602 }
1603 }
1604 else {
1605 SG_LOG(SG_GENERAL, SG_BULK, "sview event handler called...");
1606 }
1607 return true;
1608 }
1609};
1610
1612
1613std::shared_ptr<SviewView> SviewCreate(SGPropertyNode* config)
1614{
1615 assert(config);
1616
1617 FGRenderer* renderer = globals->get_renderer();
1618 osgViewer::ViewerBase* viewer_base = renderer->getViewerBase();
1619 osgViewer::CompositeViewer* composite_viewer = dynamic_cast<osgViewer::CompositeViewer*>(viewer_base);
1620 if (!composite_viewer) {
1621 return nullptr;
1622 }
1623
1624 osgViewer::View* main_view = renderer->getView();
1625 osg::Node* scene_data = main_view->getSceneData();
1626
1627 SG_LOG(SG_GENERAL, SG_DEBUG, "main_view->getNumSlaves()=" << main_view->getNumSlaves());
1628
1629 osgViewer::View* view = new osgViewer::View();
1630 flightgear::FGEventHandler* event_handler = globals->get_renderer()->getEventHandler();
1631 view->addEventHandler(event_handler);
1632
1633 std::shared_ptr<SviewView> sview_view;
1634
1635 std::string type = config->getStringValue("type");
1636 SG_LOG(SG_VIEW, SG_DEBUG, "type=" << type);
1637 if (0) {
1638 }
1639 else if (type == "current") {
1640 SGPropertyNode_ptr config2 = SviewConfigForCurrentView();
1641 copyProperties(config, config2);
1642 config2->setStringValue("type", "legacy"); /* restore it after copy() sets to "current". */
1643 sview_view.reset(new SviewViewEyeTarget(view, config2));
1644 }
1645 else if (type == "last_pair") {
1646 if (s_recent_views.size() < 2) {
1647 SG_LOG(SG_VIEW, SG_ALERT, "Need two pushed views");
1648 return nullptr;
1649 }
1650 auto it = s_recent_views.end();
1651 std::shared_ptr<SviewViewEyeTarget> target = *(--it);
1652 std::shared_ptr<SviewViewEyeTarget> eye = *(--it);
1653 sview_view.reset(new SviewViewEyeTarget(view, config, eye->m_steps, target->m_steps));
1654 }
1655 else if (type == "last_pair_double") {
1656 if (s_recent_views.size() < 2) {
1657 SG_LOG(SG_VIEW, SG_ALERT, "Need two pushed views");
1658 return nullptr;
1659 }
1660 auto it = s_recent_views.end();
1661 std::shared_ptr<SviewViewEyeTarget> remote = *(--it);
1662 std::shared_ptr<SviewViewEyeTarget> local = *(--it);
1663 sview_view.reset(new SviewViewEyeTarget(view, config, local->m_steps, remote->m_steps));
1664 }
1665 else {
1666 SG_LOG(SG_VIEW, SG_ALERT, "config is:\n" << writePropertiesInline(config, true /*write_all*/));
1667 sview_view.reset(new SviewViewEyeTarget(view, config));
1668 }
1669
1670 osg::ref_ptr<osg::GraphicsContext::Traits> traits = new osg::GraphicsContext::Traits;
1671 osg::ref_ptr<osg::GraphicsContext> gc;
1672
1673 /* When we implement canvas views, we won't create a new window here. */
1674 if (1) {
1675 /* Create a new window. */
1676
1677 // osg::GraphicsContext::WindowingSystemInterface* wsi = osg::GraphicsContext::getWindowingSystemInterface();
1678 // assert(wsi);
1680 flightgear::GraphicsWindow* main_window = wsa->getGUIWindow();
1681 assert(main_window); // The GUI window must exist
1682 osg::ref_ptr<osg::GraphicsContext> main_gc = main_window->gc;
1683 const osg::GraphicsContext::Traits* main_traits = main_gc->getTraits();
1684
1685 /* Arbitrary initial position of new window. */
1686 traits->x = config->getIntValue("window-x", 100);
1687 traits->y = config->getIntValue("window-y", 100);
1688
1689 /* We set new window size as fraction of main window. This keeps the
1690 aspect ratio of new window same as that of the main window which allows
1691 us to use main window's projection matrix directly. Presumably we could
1692 calculate a suitable projection matrix to match an arbitrary aspect
1693 ratio, but i don't know the maths well enough to do this. */
1694 traits->width = config->getIntValue("window-width", main_traits->width / 2);
1695 traits->height = config->getIntValue("window-height", main_traits->height / 2);
1696 traits->windowDecoration = true;
1697 traits->doubleBuffer = true;
1698 traits->sharedContext = 0;
1699
1700 traits->readDISPLAY();
1701 if (traits->displayNum < 0) traits->displayNum = 0;
1702 if (traits->screenNum < 0) traits->screenNum = 0;
1703
1704 int bpp = fgGetInt("/sim/rendering/bits-per-pixel");
1705 int cbits = (bpp <= 16) ? 5 : 8;
1706 int zbits = (bpp <= 16) ? 16 : 24;
1707 traits->red = traits->green = traits->blue = cbits;
1708 traits->depth = zbits;
1709
1710 traits->mipMapGeneration = true;
1711 traits->windowName = "Flightgear " + sview_view->description2();
1712 traits->sampleBuffers = fgGetInt("/sim/rendering/multi-sample-buffers", traits->sampleBuffers);
1713 traits->samples = fgGetInt("/sim/rendering/multi-samples", traits->samples);
1714 traits->vsync = fgGetBool("/sim/rendering/vsync-enable", traits->vsync);
1715 traits->stencil = 8;
1716
1717 gc = osg::GraphicsContext::createGraphicsContext(traits);
1718 assert(gc.valid());
1719 }
1720
1721 /* need to ensure that the window is cleared make sure that the complete
1722 window is set the correct colour rather than just the parts of the window
1723 that are under the camera's viewports. */
1724 //gc->setClearColor(osg::Vec4f(0.2f,0.2f,0.6f,1.0f));
1725 //gc->setClearMask(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
1726
1727 view->setSceneData(scene_data);
1728 view->setDatabasePager(FGScenery::getPagerSingleton());
1729
1730 /* https://www.mail-archive.com/osg-users@lists.openscenegraph.org/msg29820.html
1731 Passing (false, false) here seems to cause a hang on startup. */
1732 view->getDatabasePager()->setUnrefImageDataAfterApplyPolicy(true, false);
1733 osg::GraphicsContext::createNewContextID();
1734
1735 osg::Camera* main_camera = main_view->getCamera();
1736 osg::Camera* camera = view->getCamera();
1737 camera->setGraphicsContext(gc.get());
1738
1739 if (0) {
1740 /* Show the projection matrix. */
1741 double left;
1742 double right;
1743 double bottom;
1744 double top;
1745 double zNear;
1746 double zFar;
1747 auto projection_matrix = main_camera->getProjectionMatrix();
1748 bool ok = projection_matrix.getFrustum(left, right, bottom, top, zNear, zFar);
1749 SG_LOG(SG_GENERAL, SG_ALERT, "projection_matrix:"
1750 << " ok=" << ok
1751 << " left=" << left
1752 << " right=" << right
1753 << " bottom=" << bottom
1754 << " top=" << top
1755 << " zNear=" << zNear
1756 << " zFar=" << zFar
1757 );
1758 }
1759
1760 camera->setProjectionMatrix(main_camera->getProjectionMatrix());
1761 camera->setViewMatrix(main_camera->getViewMatrix());
1762 camera->setCullMask(0xffffffff);
1763 camera->setCullMaskLeft(0xffffffff);
1764 camera->setCullMaskRight(0xffffffff);
1765
1766 /* This appears to avoid unhelpful culling of nearby objects. Though the
1767 above SG_LOG() says zNear=0.1 zFar=120000, so not sure what's going on. */
1768 camera->setComputeNearFarMode(osgUtil::CullVisitor::DO_NOT_COMPUTE_NEAR_FAR);
1769
1770 /*
1771 from CameraGroup::buildGUICamera():
1772 camera->setInheritanceMask(osg::CullSettings::ALL_VARIABLES
1773 & ~(osg::CullSettings::COMPUTE_NEAR_FAR_MODE
1774 | osg::CullSettings::CULLING_MODE
1775 | osg::CullSettings::CLEAR_MASK
1776 ));
1777 camera->setCullingMode(osg::CullSettings::NO_CULLING);
1778
1779
1780 main_viewport seems to be null so this doesn't work.
1781 osg::Viewport* main_viewport = main_view->getCamera()->getViewport();
1782 SG_LOG(SG_GENERAL, SG_ALERT, "main_viewport=" << main_viewport);
1783
1784 osg::Viewport* viewport = new osg::Viewport(*main_viewport);
1785
1786 view->getCamera()->setViewport(viewport);
1787 */
1788 view->getCamera()->setViewport(0, 0, traits->width, traits->height);
1789
1790 view->setName("Cloned view");
1791
1792 view->setFrameStamp(composite_viewer->getFrameStamp());
1793
1794 simgear::compositor::Compositor* compositor = simgear::compositor::Compositor::create(
1795 view,
1796 gc,
1797 view->getCamera()->getViewport(),
1800 );
1801
1802 sview_view->m_compositor = compositor;
1803 s_views.push_back(sview_view);
1804
1805 /* stop/start threading:
1806 https://www.mail-archive.com/osg-users@lists.openscenegraph.org/msg54341.html
1807 */
1808 composite_viewer->stopThreading();
1809 composite_viewer->addView(view);
1810 composite_viewer->startThreading();
1811
1812 SG_LOG(SG_GENERAL, SG_DEBUG, "main_view->getNumSlaves()=" << main_view->getNumSlaves());
1813 SG_LOG(SG_GENERAL, SG_DEBUG, "view->getNumSlaves()=" << view->getNumSlaves());
1814
1815 SG_LOG(SG_VIEW, SG_DEBUG, "have added extra view. views are now:");
1816 for (unsigned i=0; i<composite_viewer->getNumViews(); ++i) {
1817 osgViewer::View* view = composite_viewer->getView(i);
1818 SG_LOG(SG_VIEW, SG_DEBUG, "composite_viewer view i=" << i << " view=" << view);
1819 }
1820
1821 return sview_view;
1822}
1823
1824std::shared_ptr<SviewView> SviewCreate(const SGPropertyNode* config)
1825{
1826 return SviewCreate(const_cast<SGPropertyNode*>(config));
1827}
1828
1829simgear::compositor::Compositor* SviewGetEventViewport(const osgGA::GUIEventAdapter& ea)
1830{
1831 const osg::GraphicsContext* gc = ea.getGraphicsContext();
1832 for (std::shared_ptr<SviewView> sview_view: s_views) {
1833 if (sview_view->m_compositor->getGraphicsContext() == gc) {
1834 return sview_view->m_compositor;
1835 }
1836 }
1837 return nullptr;
1838}
1839
1841 osg::ref_ptr<simgear::SGReaderWriterOptions> options,
1842 const std::string& compositor_path
1843 )
1844{
1846 s_compositor_path = compositor_path;
1847}
1848
1849bool SviewMouseMotion(int x, int y, const osgGA::GUIEventAdapter& ea)
1850{
1851 const osg::GraphicsContext* gc = ea.getGraphicsContext();
1852 std::shared_ptr<SviewView> sview_view;
1853 for (auto v: s_views) {
1854 if (v->m_compositor->getGraphicsContext() == gc) {
1855 sview_view = v;
1856 break;
1857 }
1858 }
1859 if (!sview_view) {
1860 return false;
1861 }
1862 double xx;
1863 double yy;
1864 if (!flightgear::eventToWindowCoords(&ea, xx, yy)) {
1865 return false;
1866 }
1867
1868 SG_LOG(SG_GENERAL, SG_DEBUG, "sview_view=" << sview_view.get() << " xx=" << xx << " yy=" << yy);
1869 bool button2 = globals->get_props()->getBoolValue("/devices/status/mice/mouse/button[2]");
1870 if (button2 && sview_view->m_mouse_button2) {
1871 /* Button2 drag. */
1872 double delta_x = xx - sview_view->m_mouse_x;
1873 double delta_y = yy - sview_view->m_mouse_y;
1874 if (delta_x || delta_y) {
1875 /* Convert delta (which is mouse movement in pixels) to a change
1876 in viewing angle in degrees. We do this using the fov and window
1877 size so that the result is the image moving as though it was being
1878 panned by the mouse movement. So angle changes are smaller for
1879 high zoom values or small windows, making high zoom views easy to
1880 control. */
1881 osg::Camera* camera = sview_view->m_osg_view->getCamera();
1882 double fov_y;
1883 double aspect_ratio;
1884 double z_near;
1885 double z_far;
1886 camera->getProjectionMatrixAsPerspective(fov_y, aspect_ratio, z_near, z_far);
1887 double fov_x = fov_y * aspect_ratio;
1888
1889 simgear::compositor::Compositor* compositor = sview_view->m_compositor;
1890 osg::Viewport* viewport = compositor->getViewport();
1891 double delta_x_deg = delta_x / viewport->width() * fov_x;
1892 double delta_y_deg = delta_y / viewport->height() * fov_y;
1893
1894 /* Scale movement a little to make things more similar to normal
1895 operation. */
1896 double scale = 5;
1897 delta_x_deg *= scale;
1898 delta_y_deg *= scale;
1899 sview_view->mouse_drag(delta_x_deg, delta_y_deg);
1900 }
1901 }
1902 sview_view->m_mouse_button2 = button2;
1903 sview_view->m_mouse_x = xx;
1904 sview_view->m_mouse_y = yy;
1905
1906 return true;
1907}
static double scale(int center, int deadband, int min, int max, int value)
bool options(int, char **)
Definition JSBSim.cpp:568
#define i(x)
osgViewer::View * getView()
Definition renderer.cxx:807
osgViewer::ViewerBase * getViewerBase() const
Definition renderer.cxx:782
static flightgear::SceneryPager * getPagerSingleton()
Definition scenery.cxx:560
A window with a graphics context and an integer ID.
osg::ref_ptr< osg::GraphicsContext > gc
The OSG graphics context for this window.
Adapter from windows system / graphics context management API to functions used by flightgear.
GraphicsWindow * getGUIWindow() const
Get the first window marked as GUI (there should only be one).
static WindowSystemAdapter * getWSA()
Get the global WindowSystemAdapter.
const char * name
int fgGetInt(const char *name, int defaultValue)
Get an int value for a property.
Definition fg_props.cxx:532
FGGlobals * globals
Definition globals.cxx:142
bool eventToWindowCoords(const osgGA::GUIEventAdapter *ea, double &x, double &y)
bool fgGetBool(char const *name, bool def)
Get a bool value for a property.
Definition proptest.cpp:25
bool update()
Definition sview.cxx:230
Callsign(const std::string &callsign)
Definition sview.cxx:224
SGPropertyNode_ptr m_root
Definition sview.cxx:257
std::string m_callsign
Definition sview.cxx:256
double reset(double current)
Definition sview.cxx:177
Damping(double damping_time, double wrap_max=0, double current=0)
Definition sview.cxx:152
double update(double dt, double target)
Definition sview.cxx:160
bool handle(osgGA::Event *event, osg::Object *object, osg::NodeVisitor *nv) override
Definition sview.cxx:1595
double pitch
Definition sview.cxx:111
double fov_h
Definition sview.cxx:124
double roll
Definition sview.cxx:112
bool target_is_set
Definition sview.cxx:115
SGQuatd direction2
Definition sview.cxx:120
double fov_v
Definition sview.cxx:125
SGVec3d position2
Definition sview.cxx:119
double heading
Definition sview.cxx:110
SGGeod position
Definition sview.cxx:109
SGGeod target
Definition sview.cxx:114
friend std::ostream & operator<<(std::ostream &out, const SviewPosDir &posdir)
Definition sview.cxx:127
void evaluate(SviewPosDir &posdir, double dt) override
Definition sview.cxx:616
double m_ground_altitude
Definition sview.cxx:740
Damping relative_height_ground_damping
Definition sview.cxx:741
Callsign m_callsign
Definition sview.cxx:739
SviewStepAGL(const std::string &callsign, double damping_time)
Definition sview.cxx:609
double m_chase_distance
Definition sview.cxx:738
SviewStepAircraft(const std::string &callsign)
Definition sview.cxx:265
virtual void stream(std::ostream &out) const
Definition sview.cxx:292
void evaluate(SviewPosDir &posdir, double dt) override
Definition sview.cxx:271
void evaluate(SviewPosDir &posdir, double dt) override
Definition sview.cxx:477
virtual void stream(std::ostream &out) const
Definition sview.cxx:484
virtual void stream(std::ostream &out) const
Definition sview.cxx:457
void evaluate(SviewPosDir &posdir, double dt) override
Definition sview.cxx:450
SviewStepDirectionMultiply(double heading=0, double pitch=0, double roll=0)
Definition sview.cxx:441
double m_angle_rad
Definition sview.cxx:905
void evaluate(SviewPosDir &posdir, double dt) override
Definition sview.cxx:761
double m_local_chase_distance
Definition sview.cxx:904
SviewStepDouble(SGPropertyNode *config)
Definition sview.cxx:756
void evaluate(SviewPosDir &posdir, double dt) override
Definition sview.cxx:545
virtual void stream(std::ostream &out) const
Definition sview.cxx:598
void evaluate(SviewPosDir &posdir, double dt) override
Definition sview.cxx:420
double m_heading_scale
Definition sview.cxx:432
void mouse_drag(double delta_x_deg, double delta_y_deg) override
Definition sview.cxx:426
double m_pitch_scale
Definition sview.cxx:433
SviewStepMouseDrag(double heading_scale, double pitch_scale)
Definition sview.cxx:414
SviewStepMove(double forward, double up, double right)
Definition sview.cxx:316
virtual void stream(std::ostream &out) const
Definition sview.cxx:351
void evaluate(SviewPosDir &posdir, double dt) override
Definition sview.cxx:323
SGPropertyNode_ptr m_longitude
Definition sview.cxx:525
virtual void stream(std::ostream &out) const
Definition sview.cxx:518
SGPropertyNode_ptr m_latitude
Definition sview.cxx:524
void evaluate(SviewPosDir &posdir, double dt) override
Definition sview.cxx:500
SGPropertyNode_ptr m_altitude
Definition sview.cxx:526
SviewStepNearestTower(const std::string &callsign)
Definition sview.cxx:493
virtual void stream(std::ostream &out) const
Definition sview.cxx:393
SviewStepRotate(double heading, double pitch, double roll, double damping_heading=0, double damping_pitch=0, double damping_roll=0)
Definition sview.cxx:367
void evaluate(SviewPosDir &posdir, double dt) override
Definition sview.cxx:386
virtual void mouse_drag(double delta_x_deg, double delta_y_deg)
Definition sview.cxx:197
std::string m_description
Definition sview.cxx:208
virtual ~SviewStep()
Definition sview.cxx:206
friend std::ostream & operator<<(std::ostream &out, const SviewStep &step)
Definition sview.cxx:210
virtual void evaluate(SviewPosDir &posdir, double dt=0)=0
virtual void stream(std::ostream &out) const
Definition sview.cxx:201
std::string m_name
Definition sview.cxx:932
void add_step(std::shared_ptr< SviewStep > step)
Definition sview.cxx:913
std::vector< std::shared_ptr< SviewStep > > m_steps
Definition sview.cxx:933
void add_step(SviewStep *step)
Definition sview.cxx:918
friend std::ostream & operator<<(std::ostream &out, const SviewSteps &viewpos)
Definition sview.cxx:935
void evaluate(SviewPosDir &posdir, double dt, bool debug=false)
Definition sview.cxx:923
void mouse_drag(double delta_x_deg, double delta_y_deg) override
Definition sview.cxx:1456
SviewViewEyeTarget(osgViewer::View *view, SGPropertyNode *config, SviewSteps &a, SviewSteps &b)
Definition sview.cxx:1365
SviewSteps m_steps
Definition sview.cxx:1464
SviewViewEyeTarget(osgViewer::View *view, SGPropertyNode *config)
Definition sview.cxx:1079
const std::string description() override
Definition sview.cxx:1429
bool update(double dt) override
Definition sview.cxx:1434
double m_mouse_y
Definition sview.cxx:1060
SviewView(osgViewer::View *osg_view)
Definition sview.cxx:950
void posdir_to_view(SviewPosDir posdir)
Definition sview.cxx:992
const std::string description2()
Definition sview.cxx:958
double m_mouse_x
Definition sview.cxx:1059
simgear::compositor::Compositor * m_compositor
Definition sview.cxx:1056
osgViewer::View * m_osg_view
Definition sview.cxx:1055
virtual bool update(double dt)=0
bool m_mouse_button2
Definition sview.cxx:1058
virtual ~SviewView()
Definition sview.cxx:968
static int s_id
Definition sview.cxx:1062
virtual void mouse_drag(double delta_x_deg, double delta_y_deg)=0
virtual const std::string description()=0
static std::vector< std::shared_ptr< SviewView > > s_views
Definition sview.cxx:1472
bool SviewMouseMotion(int x, int y, const osgGA::GUIEventAdapter &ea)
Definition sview.cxx:1849
static double legacy_damping_time(double damping)
Definition sview.cxx:1067
void SViewSetCompositorParams(osg::ref_ptr< simgear::SGReaderWriterOptions > options, const std::string &compositor_path)
Definition sview.cxx:1840
static const double pi
Definition sview.cxx:59
void SviewClear()
Definition sview.cxx:1582
static SGPropertyNode_ptr SviewConfigForCurrentView()
Definition sview.cxx:1480
static std::string s_compositor_path
Definition sview.cxx:1590
static std::ostream & operator<<(std::ostream &out, const osg::Vec3f &vec)
Definition sview.cxx:61
void SviewPush()
Definition sview.cxx:1522
static std::deque< std::shared_ptr< SviewViewEyeTarget > > s_recent_views
Definition sview.cxx:1475
std::shared_ptr< SviewView > SviewCreate(SGPropertyNode *config)
Definition sview.cxx:1613
static osg::ref_ptr< simgear::SGReaderWriterOptions > s_compositor_options
Definition sview.cxx:1589
void SviewUpdate(double dt)
Definition sview.cxx:1536
simgear::compositor::Compositor * SviewGetEventViewport(const osgGA::GUIEventAdapter &ea)
Definition sview.cxx:1829