FlightGear next
viewmgr.cxx
Go to the documentation of this file.
1/*
2 * SPDX-FileName: viewmgr.cxx
3 * SPDX-FileComment: class for managing all the views in the flightgear world
4 * SPDX-FileCopyrightText: Copyright (C) 2000 Curtis L. Olson - http://www.flightgear.org/~curt
5 * SPDX-FileContributor: partially rewritten by Jim Wilson March 2002
6 * SPDX-License-Identifier: GPL-2.0-or-later
7 */
8
9#include "config.h"
10
11#include <algorithm> // for std::clamp
12
13#include "viewmgr.hxx"
15
16#include <simgear/compiler.h>
17#include <simgear/scene/util/OsgMath.hxx>
18#include <simgear/props/props_io.hxx>
19#include <simgear/screen/video-encoder.hxx>
20#include <simgear/structure/commands.hxx>
21
22#include <Main/fg_props.hxx>
23#include "view.hxx"
24#include "sview.hxx"
25#include "renderer.hxx"
26
27#include "CameraGroup.hxx"
28#include "Scenery/scenery.hxx"
29
30
31// Constructor
35
36// Destructor
38{
39}
40
41void
43{
44 if (_inited) {
45 SG_LOG(SG_VIEW, SG_WARN, "duplicate init of view manager");
46 return;
47 }
48
49 _inited = true;
50
51 // Ensure that /sim/chase-distance-m is negative if it is specified.
52 // E.g. see https://sourceforge.net/p/flightgear/codetickets/2454/
53 //
54 SGPropertyNode* chase_distance_node = fgGetNode("/sim/chase-distance-m");
55 if (chase_distance_node) {
56 double chase_distance = chase_distance_node->getDoubleValue();
57 if (chase_distance > 0) {
58 chase_distance = -chase_distance;
59 SG_LOG(SG_VIEW, SG_ALERT, "sim/chase-distance-m is positive; correcting to " << chase_distance);
60 chase_distance_node->setDoubleValue(chase_distance);
61 }
62 }
63
64 config_list = fgGetNode("/sim", true)->getChildren("view");
65 _current = fgGetInt("/sim/current-view/view-number");
66 if (_current != 0 && (_current < 0 || _current >= (int) views.size())) {
67 SG_LOG(SG_VIEW, SG_ALERT,
68 "Invalid /sim/current-view/view-number=" << _current
69 << ". views.size()=" << views.size()
70 << ". Will use zero."
71 );
72 _current = 0;
73 }
74
75 for (unsigned int i = 0; i < config_list.size(); i++) {
76 SGPropertyNode* n = config_list[i];
77 SGPropertyNode* config = n->getChild("config", 0, true);
78
80 if (v) {
81 add_view(v);
82 } else {
83 SG_LOG(SG_VIEW, SG_DEV_WARN, "Failed to create view from:" << config->getPath());
84 }
85 }
86
87 if (get_current_view()) {
89 } else {
90 SG_LOG(SG_VIEW, SG_DEV_WARN, "FGViewMgr::init: current view " << _current << " failed to create");
91 }
92}
93
94void
96{
97 // force update now so many properties of the current view are valid,
98 // eg view position and orientation (as exposed via globals)
99 update(0.0);
100}
101
102void
104{
105 if (!_inited) {
106 return;
107 }
108
109 _inited = false;
110 views.clear();
111}
112
113void
115{
116 viewer_list::iterator it;
117 for (it = views.begin(); it != views.end(); ++it) {
118 (*it)->resetOffsetsAndFOV();
119 }
120}
121
122void
124{
125 // these are bound to the current view properties
126 _tiedProperties.setRoot(fgGetNode("/sim/current-view", true));
127
128
129 _tiedProperties.Tie("view-number", this,
131 &FGViewMgr::setCurrentViewIndex, false);
132 _viewNumberProp = _tiedProperties.getRoot()->getNode("view-number");
133 _viewNumberProp->setAttribute(SGPropertyNode::ARCHIVE, false);
134 _viewNumberProp->setAttribute(SGPropertyNode::PRESERVE, true);
135 _viewNumberProp->setAttribute(SGPropertyNode::LISTENER_SAFE, true);
136}
137
138
139void
141{
143 if (v) {
144 v->unbind();
145 }
146
147 _tiedProperties.Untie();
148 _viewNumberProp.clear();
149
151 SviewClear();
152}
153
154static void videoEncodingPopup(const std::string& message, int delay)
155{
156 SGPropertyNode_ptr args(new SGPropertyNode);
157 args->setStringValue("label", message);
158 args->setIntValue("delay", (delay) ? delay : 15);
159 SG_LOG(SG_GENERAL, SG_ALERT, message);
160 globals->get_commands()->execute("show-message", args);
161}
162
163static void vidoEncodingUpdateStatus(const std::string& path)
164{
165 fgSetString("/sim/video/encoding-path", path);
166
167 // For enable/disable Start and Stop buttons in video control dialog
168 bool encoding = (path != "");
169 fgSetBool("/sim/video/is-encoding", encoding);
170}
171
172static void videoEncodingError(const std::string& message)
173{
174 globals->get_props()->setIntValue("/sim/video/error", 1);
176 videoEncodingPopup(message, 15);
177}
178
179void
181{
182 flightgear::View* currentView = get_current_view();
183 if (!currentView) {
184 return;
185 }
186
187 // Update the current view
188 currentView->update(dt);
189
190 // update the camera now
191 osg::ref_ptr<flightgear::CameraGroup> cameraGroup = flightgear::CameraGroup::getDefault();
192 if (cameraGroup) {
193 cameraGroup->setCameraParameters(currentView->get_v_fov(),
194 cameraGroup->getMasterAspectRatio());
195 cameraGroup->update(toOsg(currentView->getViewPosition()),
196 toOsg(currentView->getViewOrientation()));
197 }
198
199 SviewUpdate(dt);
200
201 if (_video_encoder)
202 {
204
205 for (auto& camera_info : camera_group->getCameras())
206 {
207 if (camera_info->flags & flightgear::CameraInfo::GUI) continue;
208 osg::GraphicsContext* gc = camera_info->compositor->getGraphicsContext();
209 try
210 {
211 _video_encoder->encode(dt, gc);
212 }
213 catch (std::exception& e)
214 {
215 videoEncodingError(e.what());
216 _video_encoder.reset();
217 }
218 break;
219 }
220 }
221
222 std::string callsign = globals->get_props()->getStringValue("/sim/log-multiplayer-callsign");
223 if (callsign != "")
224 {
225 auto multiplayers = globals->get_props()->getNode("/ai/models")->getChildren("multiplayer");
226 for (auto mutiplayer: multiplayers)
227 {
228 std::string callsign2 = mutiplayer->getStringValue("callsign");
229 if (callsign2 == callsign)
230 {
231 static SGVec3d pos_prev;
232 static double t = 0;
233 SGGeod pos_geod = SGGeod::fromDegFt(
234 mutiplayer->getDoubleValue("position/longitude-deg"),
235 mutiplayer->getDoubleValue("position/latitude-deg"),
236 mutiplayer->getDoubleValue("position/altitude-ft")
237 );
238 SGVec3d pos = SGVec3d::fromGeod(pos_geod);
239 double distance = length(pos - pos_prev);
240 double speed = distance / dt;
241
242 SGPropertyNode* item = fgGetNode("/sim/log-multiplayer", true /*create*/)->addChild("mp");
243 item->setDoubleValue("distance", distance);
244 item->setDoubleValue("speed", speed);
245 item->setDoubleValue("dt", dt);
246 item->setDoubleValue("t", t);
247 item->setDoubleValue("ubody", mutiplayer->getDoubleValue("velocities/uBody-fps"));
248 item->setDoubleValue("vbody", mutiplayer->getDoubleValue("velocities/vBody-fps"));
249 item->setDoubleValue("wbody", mutiplayer->getDoubleValue("velocities/wBody-fps"));
250 pos_prev = pos;
251 t += dt;
252 break;
253 }
254 }
255 }
256}
257
259{
260 views.clear();
261}
262
265{
266 if (views.empty())
267 return nullptr;
268 if (_current < 0 || _current >= (int) views.size()) {
269 SG_LOG(SG_VIEW, SG_ALERT, "Invalid _current=" << _current
270 << ". views.size()=" << views.size()
271 << ". Will use zero."
272 );
273 _current = 0;
274 }
275 return views[_current];
276}
277
278const flightgear::View*
280{
281 return const_cast<FGViewMgr*>(this)->get_current_view();
282}
283
284
287{
288 const auto lastView = static_cast<int>(views.size()) - 1;
289 const int c = std::clamp(i, 0, lastView);
290 return views.at(c);
291}
292
293const flightgear::View*
295{
296 const auto lastView = static_cast<int>(views.size()) - 1;
297 const int c = std::clamp(i, 0, lastView);
298 return views.at(c);
299}
300
303{
304 const auto numViews = static_cast<int>(views.size());
305 setCurrentViewIndex((_current + 1) % numViews);
306 _viewNumberProp->fireValueChanged();
307 return get_current_view();
308}
309
312{
313 const auto numViews = static_cast<int>(views.size());
314 // subtract 1, but add a full numViews, to ensure the integer
315 // modulo returns a +ve result; negative values mean something
316 // else to setCurrentViewIndex
317 setCurrentViewIndex((_current - 1 + numViews) % numViews);
318 _viewNumberProp->fireValueChanged();
319 return get_current_view();
320}
321
323{
324 SviewPush();
325}
326
327void s_clone_internal(const SGPropertyNode* config, const std::string& type)
328{
329 SGPropertyNode_ptr config2 = new SGPropertyNode;
330 copyProperties(config, config2);
331 config2->setStringValue("type", type);
332 SviewCreate(config2);
333}
334
335void FGViewMgr::clone_current_view(const SGPropertyNode* config)
336{
337 s_clone_internal(config, "current");
338}
339
340void FGViewMgr::clone_last_pair(const SGPropertyNode* config)
341{
342 s_clone_internal(config, "last_pair");
343}
344
345void FGViewMgr::clone_last_pair_double(const SGPropertyNode* config)
346{
347 s_clone_internal(config, "last_pair_double");
348}
349
350void FGViewMgr::view_new(const SGPropertyNode* config)
351{
352 SviewCreate(config);
353}
354
355#include <simgear/scene/util/SGReaderWriterOptions.hxx>
356
357void
359{
360 views.push_back(v);
361 v->init();
362}
363
365 const std::string& name_in,
366 const std::string& codec_in,
367 double quality,
368 double speed,
369 int bitrate
370 )
371{
372 SG_LOG(SG_GENERAL, SG_ALERT, "FGViewMgr::video_start():"
373 << " name_in=" << name_in
374 << " codec_in=" << codec_in
375 << " quality=" << quality
376 << " speed=" << speed
377 << " bitrate=" << bitrate
378 );
379 globals->get_props()->setIntValue("/sim/video/error", 0);
380
381 std::string name;
382 std::string name_link;
383
384 if (name_in == "")
385 {
386 /* Use a default name containing aircraft-name, current date and time
387 and the configured video container. */
388 time_t calendar_time = time(NULL);
389 struct tm* local_tm = localtime(&calendar_time);
390 char time_string[256];
391 strftime(time_string, sizeof(time_string), "-%Y%m%d-%H%M%S", local_tm);
392
393 std::string suffix = "." + fgGetString("/sim/video/container", "mpeg");
394
395 name = std::string("fgvideo-") + fgGetString("/sim/aircraft");
396 name_link = name + suffix;
397 name += time_string + suffix;
398 }
399 else
400 {
401 /* We assume <name_in> already has the desired suffix. We leave
402 name_link empty so we don't attempt to create a link. */
403 name = name_in;
404 }
405
406 std::string codec = codec_in;
407
408 std::string directory = fgGetString("/sim/video/directory");
409 SGPath path = SGPath(directory);
410 path.append(name);
411 if (path.exists())
412 {
413 videoEncodingError("Video encoding failure, output file already exists: " + path.str());
414 return false;
415 }
416 SGPath path_link;
417 if (name_link != "")
418 {
419 path_link = SGPath(directory);
420 path_link.append(name_link);
421 path_link.remove();
422 bool ok = path_link.makeLink(path.file());
423 if (!ok)
424 {
425 SG_LOG(SG_SYSTEMS, SG_ALERT, "Failed to create link "
426 << path_link.c_str() << " => " << path.file()
427 );
428 }
429 }
430 if (codec == "") codec = fgGetString("/sim/video/codec");
431 if (quality == -1) quality = fgGetDouble("/sim/video/quality");
432 if (speed == -1) speed = fgGetDouble("/sim/video/speed");
433 if (bitrate == 0) bitrate = fgGetInt("/sim/video/bitrate");
434
435 std::string warning;
436 if (quality != -1 && (quality < 0 || quality > 1))
437 {
438 warning += "Ignoring quality=" + std::to_string(quality) + " because should be -1 or in range 0-1.\n";
439 quality = -1;
440 }
441 if (speed != -1 && (speed < 0 || speed > 1))
442 {
443 warning += "Ignoring speed=" + std::to_string(speed) + " because should be -1 or in range 0-1.\n";
444 speed = -1;
445 }
446 if (bitrate < 0)
447 {
448 warning += "Ignoring bitrate=" + std::to_string(bitrate) + " because should be >= 0.\n";
449 bitrate = 0;
450 }
451 if (warning != "")
452 {
453 videoEncodingPopup(warning, 10);
454 }
455
456 SG_LOG(SG_SYSTEMS, SG_ALERT, "Video encoding starting."
457 << " codec=" << codec
458 << " quality=" << quality
459 << " speed=" << speed
460 << " bitrate=" << bitrate
461 << " path=" << path
462 << " path_link=" << path_link
463 );
464 bool log_sws_scale_stats = globals->get_props()->getNode("/sim/video/log_sws_scale_stats", true /*create*/)->getBoolValue();
465 try
466 {
467 _video_encoder.reset(
468 new simgear::VideoEncoder(path.str(), codec, quality, speed, bitrate, log_sws_scale_stats)
469 );
470 }
471 catch (std::exception& e)
472 {
473 videoEncodingError(e.what());
474 return false;
475 }
476
477 vidoEncodingUpdateStatus(path.str());
478 return true;
479}
480
482{
483 if (_video_encoder)
484 {
485 _video_encoder.reset();
487 videoEncodingPopup("Video encoding stopped.", 5);
488 }
489 else
490 {
491 videoEncodingPopup("[Video encoding already stopped]", 5);
492 }
493}
494
496{
497 return _current;
498}
499
500void FGViewMgr::setCurrentViewIndex(int newview)
501{
502 if (newview == _current) {
503 return;
504 }
505 // negative numbers -> set view with node index -newview
506 if (newview < 0) {
507 for (int i = 0; i < (int)config_list.size(); i++) {
508 int index = -config_list[i]->getIndex();
509 if (index == newview)
510 newview = i;
511 }
512 if (newview < 0) {
513 SG_LOG(SG_VIEW, SG_ALERT, "Failed to find -ve newview=" << newview);
514 return;
515 }
516 }
517 if (newview < 0 || newview >= (int) views.size()) {
518 SG_LOG(SG_VIEW, SG_ALERT, "Ignoring invalid newview=" << newview
519 << ". views.size()=" << views.size()
520 );
521 return;
522 }
523
524 if (get_current_view()) {
526 }
527
528 _current = newview;
529
530 if (get_current_view()) {
532 }
533}
534
535
536// Register the subsystem.
537SGSubsystemMgr::Registrant<FGViewMgr> registrantFGViewMgr(
538 SGSubsystemMgr::DISPLAY);
#define i(x)
void view_new(const SGPropertyNode *config)
Definition viewmgr.cxx:350
void reinit() override
Definition viewmgr.cxx:114
void clear()
Definition viewmgr.cxx:258
void view_push()
Definition viewmgr.cxx:322
FGViewMgr(void)
Definition viewmgr.cxx:32
void bind() override
Definition viewmgr.cxx:123
void postinit() override
Definition viewmgr.cxx:95
void unbind() override
Definition viewmgr.cxx:140
void shutdown() override
Definition viewmgr.cxx:103
void video_stop()
Definition viewmgr.cxx:481
void update(double dt) override
Definition viewmgr.cxx:180
flightgear::View * next_view()
Definition viewmgr.cxx:302
flightgear::View * prev_view()
Definition viewmgr.cxx:311
void init() override
Definition viewmgr.cxx:42
~FGViewMgr(void)
Definition viewmgr.cxx:37
int getCurrentViewIndex() const
Definition viewmgr.cxx:495
void clone_current_view(const SGPropertyNode *config)
Definition viewmgr.cxx:335
void clone_last_pair_double(const SGPropertyNode *config)
Definition viewmgr.cxx:345
flightgear::View * get_view(int i)
Definition viewmgr.cxx:286
bool video_start(const std::string &name="", const std::string &codec="", double quality=-1, double speed=-1, int bitrate=0)
Definition viewmgr.cxx:364
flightgear::View * get_current_view()
Definition viewmgr.cxx:264
void clone_last_pair(const SGPropertyNode *config)
Definition viewmgr.cxx:340
void add_view(flightgear::View *v)
Definition viewmgr.cxx:358
static CameraGroup * getDefault()
Get the default CameraGroup.
const CameraList & getCameras()
double get_v_fov()
Definition view.cxx:1234
void bind() override
Definition view.cxx:230
const SGQuatd & getViewOrientation()
Definition view.hxx:178
void update(double dt) override
Definition view.cxx:1261
void init() override
Definition view.cxx:225
const SGVec3d & getViewPosition()
Definition view.hxx:177
void unbind() override
Definition view.cxx:354
static View * createFromProperties(SGPropertyNode_ptr props, int view_index=-1)
Definition view.cxx:140
const char * name
int fgGetInt(const char *name, int defaultValue)
Get an int value for a property.
Definition fg_props.cxx:532
std::string fgGetString(const char *name, const char *defaultValue)
Get a string value for a property.
Definition fg_props.cxx:556
FGGlobals * globals
Definition globals.cxx:142
bool fgSetBool(char const *name, bool val)
Set a bool value for a property.
Definition proptest.cpp:24
double fgGetDouble(const char *name, double defaultValue)
Get a double value for a property.
Definition proptest.cpp:30
bool fgSetString(char const *name, char const *str)
Set a string value for a property.
Definition proptest.cpp:26
SGPropertyNode * fgGetNode(const char *path, bool create)
Get a property node.
Definition proptest.cpp:27
@ GUI
Camera draws the GUI.
void SviewClear()
Definition sview.cxx:1582
void SviewPush()
Definition sview.cxx:1522
std::shared_ptr< SviewView > SviewCreate(SGPropertyNode *config)
Definition sview.cxx:1613
void SviewUpdate(double dt)
Definition sview.cxx:1536
static void videoEncodingError(const std::string &message)
Definition viewmgr.cxx:172
SGSubsystemMgr::Registrant< FGViewMgr > registrantFGViewMgr(SGSubsystemMgr::DISPLAY)
static void vidoEncodingUpdateStatus(const std::string &path)
Definition viewmgr.cxx:163
static void videoEncodingPopup(const std::string &message, int delay)
Definition viewmgr.cxx:154
void s_clone_internal(const SGPropertyNode *config, const std::string &type)
Definition viewmgr.cxx:327