// viewmgr.cxx -- class for managing all the views in the flightgear world. // // Written by Curtis Olson, started October 2000. // partially rewritten by Jim Wilson March 2002 // // Copyright (C) 2000 Curtis L. Olson - http://www.flightgear.org/~curt // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License as // published by the Free Software Foundation; either version 2 of the // License, or (at your option) any later version. // // This program is distributed in the hope that it will be useful, but // WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. // // $Id$ #include "config.h" #include // for std::clamp #include "viewmgr.hxx" #include "ViewPropertyEvaluator.hxx" #include #include #include #include #include #include
#include "view.hxx" #include "sview.hxx" #include "renderer.hxx" #include "CameraGroup.hxx" #include "Scenery/scenery.hxx" // Constructor FGViewMgr::FGViewMgr(void) { } // Destructor FGViewMgr::~FGViewMgr( void ) { } void FGViewMgr::init () { if (_inited) { SG_LOG(SG_VIEW, SG_WARN, "duplicate init of view manager"); return; } _inited = true; // Ensure that /sim/chase-distance-m is negative if it is specified. // E.g. see https://sourceforge.net/p/flightgear/codetickets/2454/ // SGPropertyNode* chase_distance_node = fgGetNode("/sim/chase-distance-m"); if (chase_distance_node) { double chase_distance = chase_distance_node->getDoubleValue(); if (chase_distance > 0) { chase_distance = -chase_distance; SG_LOG(SG_VIEW, SG_ALERT, "sim/chase-distance-m is positive; correcting to " << chase_distance); chase_distance_node->setDoubleValue(chase_distance); } } config_list = fgGetNode("/sim", true)->getChildren("view"); _current = fgGetInt("/sim/current-view/view-number"); if (_current != 0 && (_current < 0 || _current >= (int) views.size())) { SG_LOG(SG_VIEW, SG_ALERT, "Invalid /sim/current-view/view-number=" << _current << ". views.size()=" << views.size() << ". Will use zero." ); _current = 0; } for (unsigned int i = 0; i < config_list.size(); i++) { SGPropertyNode* n = config_list[i]; SGPropertyNode* config = n->getChild("config", 0, true); flightgear::View* v = flightgear::View::createFromProperties(config, n->getIndex()); if (v) { add_view(v); } else { SG_LOG(SG_VIEW, SG_DEV_WARN, "Failed to create view from:" << config->getPath()); } } if (get_current_view()) { get_current_view()->bind(); } else { SG_LOG(SG_VIEW, SG_DEV_WARN, "FGViewMgr::init: current view " << _current << " failed to create"); } } void FGViewMgr::postinit() { // force update now so many properties of the current view are valid, // eg view position and orientation (as exposed via globals) update(0.0); } void FGViewMgr::shutdown() { if (!_inited) { return; } _inited = false; views.clear(); } void FGViewMgr::reinit () { viewer_list::iterator it; for (it = views.begin(); it != views.end(); ++it) { (*it)->resetOffsetsAndFOV(); } } void FGViewMgr::bind() { // these are bound to the current view properties _tiedProperties.setRoot(fgGetNode("/sim/current-view", true)); _tiedProperties.Tie("view-number", this, &FGViewMgr::getCurrentViewIndex, &FGViewMgr::setCurrentViewIndex, false); _viewNumberProp = _tiedProperties.getRoot()->getNode("view-number"); _viewNumberProp->setAttribute(SGPropertyNode::ARCHIVE, false); _viewNumberProp->setAttribute(SGPropertyNode::PRESERVE, true); _viewNumberProp->setAttribute(SGPropertyNode::LISTENER_SAFE, true); } void FGViewMgr::unbind () { flightgear::View* v = get_current_view(); if (v) { v->unbind(); } _tiedProperties.Untie(); _viewNumberProp.clear(); ViewPropertyEvaluator::clear(); SviewClear(); } static void videoEncodingPopup(const std::string& message, int delay) { SGPropertyNode_ptr args(new SGPropertyNode); args->setStringValue("label", message); args->setIntValue("delay", (delay) ? delay : 15); SG_LOG(SG_GENERAL, SG_ALERT, message); globals->get_commands()->execute("show-message", args); } static void vidoEncodingUpdateStatus(const std::string& path) { fgSetString("/sim/video/encoding-path", path); bool encoding = (path != ""); SGPropertyNode* node = fgGetNode("/sim/menubar/default"); /* Enable/disable menu items 'File/Video encode start' and 'File/Video encode stop'. */ for (auto a: node->getChildren("menu")) { std::string name = a->getStringValue("name"); if (name == "file") { for (auto b: a->getChildren("item")) { std::string name = b->getStringValue("name"); if (name == "video-start") { b->setBoolValue("enabled", !encoding); } if (name == "video-stop") { b->setBoolValue("enabled", encoding); } } break; } } } static void videoEncodingError(const std::string& message) { globals->get_props()->setIntValue("/sim/video/error", 1); vidoEncodingUpdateStatus(""); videoEncodingPopup(message, 15); } void FGViewMgr::update (double dt) { flightgear::View* currentView = get_current_view(); if (!currentView) { return; } // Update the current view currentView->update(dt); // update the camera now osg::ref_ptr cameraGroup = flightgear::CameraGroup::getDefault(); if (cameraGroup) { cameraGroup->setCameraParameters(currentView->get_v_fov(), cameraGroup->getMasterAspectRatio()); cameraGroup->update(toOsg(currentView->getViewPosition()), toOsg(currentView->getViewOrientation())); } SviewUpdate(dt); if (_video_encoder) { flightgear::CameraGroup* camera_group = flightgear::CameraGroup::getDefault(); for (auto& camera_info : camera_group->getCameras()) { if (camera_info->flags & flightgear::CameraInfo::GUI) continue; osg::GraphicsContext* gc = camera_info->compositor->getGraphicsContext(); try { _video_encoder->encode(dt, gc); } catch (std::exception& e) { videoEncodingError(e.what()); _video_encoder.reset(); } break; } } std::string callsign = globals->get_props()->getStringValue("/sim/log-multiplayer-callsign"); if (callsign != "") { auto multiplayers = globals->get_props()->getNode("/ai/models")->getChildren("multiplayer"); for (auto mutiplayer: multiplayers) { std::string callsign2 = mutiplayer->getStringValue("callsign"); if (callsign2 == callsign) { static SGVec3d pos_prev; static double t = 0; SGGeod pos_geod = SGGeod::fromDegFt( mutiplayer->getDoubleValue("position/longitude-deg"), mutiplayer->getDoubleValue("position/latitude-deg"), mutiplayer->getDoubleValue("position/altitude-ft") ); SGVec3d pos = SGVec3d::fromGeod(pos_geod); double distance = length(pos - pos_prev); double speed = distance / dt; SGPropertyNode* item = fgGetNode("/sim/log-multiplayer", true /*create*/)->addChild("mp"); item->setDoubleValue("distance", distance); item->setDoubleValue("speed", speed); item->setDoubleValue("dt", dt); item->setDoubleValue("t", t); item->setDoubleValue("ubody", mutiplayer->getDoubleValue("velocities/uBody-fps")); item->setDoubleValue("vbody", mutiplayer->getDoubleValue("velocities/vBody-fps")); item->setDoubleValue("wbody", mutiplayer->getDoubleValue("velocities/wBody-fps")); pos_prev = pos; t += dt; break; } } } } void FGViewMgr::clear() { views.clear(); } flightgear::View* FGViewMgr::get_current_view() { if (views.empty()) return nullptr; if (_current < 0 || _current >= (int) views.size()) { SG_LOG(SG_VIEW, SG_ALERT, "Invalid _current=" << _current << ". views.size()=" << views.size() << ". Will use zero." ); _current = 0; } return views[_current]; } const flightgear::View* FGViewMgr::get_current_view() const { return const_cast(this)->get_current_view(); } flightgear::View* FGViewMgr::get_view( int i ) { const auto lastView = static_cast(views.size()) - 1; const int c = std::clamp(i, 0, lastView); return views.at(c); } const flightgear::View* FGViewMgr::get_view( int i ) const { const auto lastView = static_cast(views.size()) - 1; const int c = std::clamp(i, 0, lastView); return views.at(c); } flightgear::View* FGViewMgr::next_view() { const auto numViews = static_cast(views.size()); setCurrentViewIndex((_current + 1) % numViews); _viewNumberProp->fireValueChanged(); return get_current_view(); } flightgear::View* FGViewMgr::prev_view() { const auto numViews = static_cast(views.size()); // subtract 1, but add a full numViews, to ensure the integer // modulo returns a +ve result; negative values mean something // else to setCurrentViewIndex setCurrentViewIndex((_current - 1 + numViews) % numViews); _viewNumberProp->fireValueChanged(); return get_current_view(); } void FGViewMgr::view_push() { SviewPush(); } void s_clone_internal(const SGPropertyNode* config, const std::string& type) { SGPropertyNode_ptr config2 = new SGPropertyNode; copyProperties(config, config2); config2->setStringValue("type", type); SviewCreate(config2); } void FGViewMgr::clone_current_view(const SGPropertyNode* config) { s_clone_internal(config, "current"); } void FGViewMgr::clone_last_pair(const SGPropertyNode* config) { s_clone_internal(config, "last_pair"); } void FGViewMgr::clone_last_pair_double(const SGPropertyNode* config) { s_clone_internal(config, "last_pair_double"); } void FGViewMgr::view_new(const SGPropertyNode* config) { SviewCreate(config); } #include void FGViewMgr::add_view( flightgear::View * v ) { views.push_back(v); v->init(); } bool FGViewMgr::video_start( const std::string& name_in, const std::string& codec_in, double quality, double speed, int bitrate ) { SG_LOG(SG_GENERAL, SG_ALERT, "FGViewMgr::video_start():" << " name_in=" << name_in << " codec_in=" << codec_in << " quality=" << quality << " speed=" << speed << " bitrate=" << bitrate ); globals->get_props()->setIntValue("/sim/video/error", 0); std::string name; std::string name_link; if (name_in == "") { /* Use a default name containing aircraft-name, current date and time and the configured video container. */ time_t calendar_time = time(NULL); struct tm* local_tm = localtime(&calendar_time); char time_string[256]; strftime(time_string, sizeof(time_string), "-%Y%m%d-%H%M%S", local_tm); std::string suffix = "." + fgGetString("/sim/video/container", "mpeg"); name = std::string("fgvideo-") + fgGetString("/sim/aircraft"); name_link = name + suffix; name += time_string + suffix; } else { /* We assume already has the desired suffix. We leave name_link empty so we don't attempt to create a link. */ name = name_in; } std::string codec = codec_in; string directory = fgGetString("/sim/video/directory"); SGPath path = SGPath(directory); path.append(name); if (path.exists()) { videoEncodingError("Video encoding failure, output file already exists: " + path.str()); return false; } SGPath path_link; if (name_link != "") { path_link = SGPath(directory); path_link.append(name_link); path_link.remove(); bool ok = path_link.makeLink(path.file()); if (!ok) { SG_LOG(SG_SYSTEMS, SG_ALERT, "Failed to create link " << path_link.c_str() << " => " << path.file() ); } } if (codec == "") codec = fgGetString("/sim/video/codec"); if (quality == -1) quality = fgGetDouble("/sim/video/quality"); if (speed == -1) speed = fgGetDouble("/sim/video/speed"); if (bitrate == 0) bitrate = fgGetInt("/sim/video/bitrate"); std::string warning; if (quality != -1 && (quality < 0 || quality > 1)) { warning += "Ignoring quality=" + std::to_string(quality) + " because should be -1 or in range 0-1.\n"; quality = -1; } if (speed != -1 && (speed < 0 || speed > 1)) { warning += "Ignoring speed=" + std::to_string(speed) + " because should be -1 or in range 0-1.\n"; speed = -1; } if (bitrate < 0) { warning += "Ignoring bitrate=" + std::to_string(bitrate) + " because should be >= 0.\n"; bitrate = 0; } if (warning != "") { videoEncodingPopup(warning, 10); } SG_LOG(SG_SYSTEMS, SG_ALERT, "Video encoding starting." << " codec=" << codec << " quality=" << quality << " speed=" << speed << " bitrate=" << bitrate << " path=" << path << " path_link=" << path_link ); bool log_sws_scale_stats = globals->get_props()->getNode("/sim/video/log_sws_scale_stats", true /*create*/)->getBoolValue(); try { _video_encoder.reset( new simgear::VideoEncoder(path.str(), codec, quality, speed, bitrate, log_sws_scale_stats) ); } catch (std::exception& e) { videoEncodingError(e.what()); return false; } vidoEncodingUpdateStatus(path.str()); return true; } void FGViewMgr::video_stop() { if (_video_encoder) { _video_encoder.reset(); vidoEncodingUpdateStatus(""); videoEncodingPopup("Video encoding stopped.", 5); } else { videoEncodingPopup("[Video encoding already stopped]", 5); } } int FGViewMgr::getCurrentViewIndex() const { return _current; } void FGViewMgr::setCurrentViewIndex(int newview) { if (newview == _current) { return; } // negative numbers -> set view with node index -newview if (newview < 0) { for (int i = 0; i < (int)config_list.size(); i++) { int index = -config_list[i]->getIndex(); if (index == newview) newview = i; } if (newview < 0) { SG_LOG(SG_VIEW, SG_ALERT, "Failed to find -ve newview=" << newview); return; } } if (newview < 0 || newview >= (int) views.size()) { SG_LOG(SG_VIEW, SG_ALERT, "Ignoring invalid newview=" << newview << ". views.size()=" << views.size() ); return; } if (get_current_view()) { get_current_view()->unbind(); } _current = newview; if (get_current_view()) { get_current_view()->bind(); } } // Register the subsystem. SGSubsystemMgr::Registrant registrantFGViewMgr( SGSubsystemMgr::DISPLAY);