575 lines
16 KiB
C++
575 lines
16 KiB
C++
// 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 <algorithm> // for std::clamp
|
|
|
|
#include "viewmgr.hxx"
|
|
#include "ViewPropertyEvaluator.hxx"
|
|
|
|
#include <simgear/compiler.h>
|
|
#include <simgear/scene/util/OsgMath.hxx>
|
|
#include <simgear/props/props_io.hxx>
|
|
#include <simgear/screen/video-encoder.hxx>
|
|
#include <simgear/structure/commands.hxx>
|
|
|
|
#include <Main/fg_props.hxx>
|
|
#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<flightgear::CameraGroup> 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<FGViewMgr*>(this)->get_current_view();
|
|
}
|
|
|
|
|
|
flightgear::View*
|
|
FGViewMgr::get_view( int i )
|
|
{
|
|
const auto lastView = static_cast<int>(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<int>(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<int>(views.size());
|
|
setCurrentViewIndex((_current + 1) % numViews);
|
|
_viewNumberProp->fireValueChanged();
|
|
return get_current_view();
|
|
}
|
|
|
|
flightgear::View*
|
|
FGViewMgr::prev_view()
|
|
{
|
|
const auto numViews = static_cast<int>(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 <simgear/scene/util/SGReaderWriterOptions.hxx>
|
|
|
|
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 <name_in> 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<FGViewMgr> registrantFGViewMgr(
|
|
SGSubsystemMgr::DISPLAY);
|