2001-06-01 17:52:17 +00:00
|
|
|
// fg_commands.cxx - internal FGFS commands.
|
|
|
|
|
2006-02-18 13:58:09 +00:00
|
|
|
#ifdef HAVE_CONFIG_H
|
|
|
|
# include "config.h"
|
|
|
|
#endif
|
|
|
|
|
2002-03-20 22:15:22 +00:00
|
|
|
#include <string.h> // strcmp()
|
|
|
|
|
2001-06-05 22:12:17 +00:00
|
|
|
#include <simgear/compiler.h>
|
|
|
|
|
2008-07-25 18:38:29 +00:00
|
|
|
#include <string>
|
|
|
|
#include <fstream>
|
2001-06-01 17:52:17 +00:00
|
|
|
|
2003-05-06 23:46:24 +00:00
|
|
|
#include <simgear/sg_inlines.h>
|
2001-06-01 17:52:17 +00:00
|
|
|
#include <simgear/debug/logstream.hxx>
|
2003-04-04 22:38:35 +00:00
|
|
|
#include <simgear/math/sg_random.h>
|
2017-02-09 18:48:33 +00:00
|
|
|
#include <simgear/io/iostreams/sgstream.hxx>
|
2003-09-24 17:20:55 +00:00
|
|
|
#include <simgear/structure/exception.hxx>
|
|
|
|
#include <simgear/structure/commands.hxx>
|
2003-05-06 23:46:24 +00:00
|
|
|
#include <simgear/props/props.hxx>
|
2011-07-30 09:47:28 +00:00
|
|
|
#include <simgear/props/props_io.hxx>
|
2008-07-31 12:04:32 +00:00
|
|
|
#include <simgear/structure/event_mgr.hxx>
|
2010-07-01 21:43:28 +00:00
|
|
|
#include <simgear/timing/sg_time.hxx>
|
2015-03-11 17:28:01 +00:00
|
|
|
#include <Network/RemoteXMLRequest.hxx>
|
2001-06-01 17:52:17 +00:00
|
|
|
|
2002-11-30 20:10:16 +00:00
|
|
|
#include <FDM/flight.hxx>
|
2005-11-01 13:41:49 +00:00
|
|
|
#include <Aircraft/replay.hxx>
|
2003-11-25 21:08:36 +00:00
|
|
|
#include <Scripting/NasalSys.hxx>
|
2011-05-24 23:03:51 +00:00
|
|
|
#include <Airports/xmlloader.hxx>
|
2012-04-18 09:25:27 +00:00
|
|
|
#include <Network/HTTPClient.hxx>
|
2012-04-25 21:28:00 +00:00
|
|
|
#include <Viewer/viewmgr.hxx>
|
2016-01-25 23:17:17 +00:00
|
|
|
#include <Viewer/view.hxx>
|
2012-04-25 21:28:00 +00:00
|
|
|
#include <Environment/presets.hxx>
|
2014-11-13 21:02:58 +00:00
|
|
|
#include <Navaids/NavDataCache.hxx>
|
2017-03-21 20:59:35 +00:00
|
|
|
#include <GUI/gui.h>
|
2020-11-10 11:32:55 +00:00
|
|
|
#include <Main/sentryIntegration.hxx>
|
2001-06-05 22:12:17 +00:00
|
|
|
|
2002-11-30 20:10:16 +00:00
|
|
|
#include "fg_init.hxx"
|
2003-11-24 01:49:04 +00:00
|
|
|
#include "fg_io.hxx"
|
2009-08-05 14:29:32 +00:00
|
|
|
#include "fg_os.hxx"
|
2001-06-05 22:12:17 +00:00
|
|
|
#include "fg_commands.hxx"
|
2003-08-06 17:13:42 +00:00
|
|
|
#include "fg_props.hxx"
|
2003-11-24 01:49:04 +00:00
|
|
|
#include "globals.hxx"
|
2004-02-15 18:41:14 +00:00
|
|
|
#include "logger.hxx"
|
2003-11-24 01:49:04 +00:00
|
|
|
#include "util.hxx"
|
2005-12-22 13:51:11 +00:00
|
|
|
#include "main.hxx"
|
2012-09-18 19:50:28 +00:00
|
|
|
#include "positioninit.hxx"
|
2001-06-05 22:12:17 +00:00
|
|
|
|
2013-10-20 10:58:20 +00:00
|
|
|
#if FG_HAVE_GPERFTOOLS
|
2012-11-15 10:55:25 +00:00
|
|
|
# include <google/profiler.h>
|
|
|
|
#endif
|
|
|
|
|
2008-07-27 16:25:13 +00:00
|
|
|
using std::string;
|
|
|
|
using std::ifstream;
|
|
|
|
using std::ofstream;
|
2001-06-01 17:52:17 +00:00
|
|
|
|
|
|
|
|
2001-07-09 16:17:20 +00:00
|
|
|
////////////////////////////////////////////////////////////////////////
|
2002-10-25 22:19:51 +00:00
|
|
|
// Static helper functions.
|
2001-07-09 16:17:20 +00:00
|
|
|
////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
|
|
|
2002-10-25 22:19:51 +00:00
|
|
|
static inline SGPropertyNode *
|
2017-07-04 23:23:24 +00:00
|
|
|
get_prop (const SGPropertyNode * arg, SGPropertyNode * root)
|
2001-07-09 16:17:20 +00:00
|
|
|
{
|
2017-07-04 23:23:24 +00:00
|
|
|
if (root != nullptr)
|
|
|
|
{
|
|
|
|
SGPropertyNode *rv = nullptr;
|
2017-07-23 11:41:56 +00:00
|
|
|
rv = root->getNode(arg->getStringValue("property[0]", "/null"), true);
|
2017-07-04 23:23:24 +00:00
|
|
|
if (rv == nullptr)
|
|
|
|
{
|
2017-07-23 11:41:56 +00:00
|
|
|
rv = root->getNode(arg->getStringValue("property[0]", "/null"), true);
|
2017-07-04 23:23:24 +00:00
|
|
|
return fgGetNode(arg->getStringValue("property[0]", "/null"), true);
|
|
|
|
}
|
|
|
|
return rv;
|
|
|
|
}
|
2002-10-25 22:19:51 +00:00
|
|
|
return fgGetNode(arg->getStringValue("property[0]", "/null"), true);
|
2001-07-09 16:17:20 +00:00
|
|
|
}
|
|
|
|
|
2002-10-25 22:19:51 +00:00
|
|
|
static inline SGPropertyNode *
|
2017-07-04 23:23:24 +00:00
|
|
|
get_prop2 (const SGPropertyNode * arg, SGPropertyNode * root)
|
2002-10-25 22:19:51 +00:00
|
|
|
{
|
2017-07-04 23:23:24 +00:00
|
|
|
if (root != nullptr)
|
|
|
|
{
|
|
|
|
SGPropertyNode *rv = nullptr;
|
2017-07-23 11:41:56 +00:00
|
|
|
rv = root->getNode(arg->getStringValue("property[1]", "/null"), true);
|
2017-07-04 23:23:24 +00:00
|
|
|
if (rv == nullptr)
|
|
|
|
{
|
2017-07-23 11:41:56 +00:00
|
|
|
rv = root->getNode(arg->getStringValue("property[1]", "/null"), true);
|
2017-07-04 23:23:24 +00:00
|
|
|
return fgGetNode(arg->getStringValue("property[1]", "/null"), true);
|
|
|
|
}
|
|
|
|
return rv;
|
|
|
|
}
|
2002-10-25 22:19:51 +00:00
|
|
|
return fgGetNode(arg->getStringValue("property[1]", "/null"), true);
|
|
|
|
}
|
2001-07-09 16:17:20 +00:00
|
|
|
|
|
|
|
|
2002-12-30 19:50:17 +00:00
|
|
|
/**
|
|
|
|
* Get a double value and split it as required.
|
|
|
|
*/
|
|
|
|
static void
|
|
|
|
split_value (double full_value, const char * mask,
|
|
|
|
double * unmodifiable, double * modifiable)
|
|
|
|
{
|
|
|
|
if (!strcmp("integer", mask)) {
|
|
|
|
*modifiable = (full_value < 0 ? ceil(full_value) : floor (full_value));
|
|
|
|
*unmodifiable = full_value - *modifiable;
|
|
|
|
} else if (!strcmp("decimal", mask)) {
|
|
|
|
*unmodifiable = (full_value < 0 ? ceil(full_value) : floor(full_value));
|
|
|
|
*modifiable = full_value - *unmodifiable;
|
|
|
|
} else {
|
|
|
|
if (strcmp("all", mask))
|
|
|
|
SG_LOG(SG_GENERAL, SG_ALERT, "Bad value " << mask << " for mask;"
|
|
|
|
<< " assuming 'all'");
|
|
|
|
*unmodifiable = 0;
|
|
|
|
*modifiable = full_value;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Clamp or wrap a value as specified.
|
|
|
|
*/
|
|
|
|
static void
|
|
|
|
limit_value (double * value, const SGPropertyNode * arg)
|
|
|
|
{
|
|
|
|
const SGPropertyNode * min_node = arg->getChild("min");
|
|
|
|
const SGPropertyNode * max_node = arg->getChild("max");
|
|
|
|
|
|
|
|
bool wrap = arg->getBoolValue("wrap");
|
|
|
|
|
|
|
|
if (min_node == 0 || max_node == 0)
|
|
|
|
wrap = false;
|
2017-03-21 20:59:35 +00:00
|
|
|
|
2003-01-04 18:47:25 +00:00
|
|
|
if (wrap) { // wrap such that min <= x < max
|
|
|
|
double min_val = min_node->getDoubleValue();
|
|
|
|
double max_val = max_node->getDoubleValue();
|
|
|
|
double resolution = arg->getDoubleValue("resolution");
|
|
|
|
if (resolution > 0.0) {
|
|
|
|
// snap to (min + N*resolution), taking special care to handle imprecision
|
|
|
|
int n = (int)floor((*value - min_val) / resolution + 0.5);
|
|
|
|
int steps = (int)floor((max_val - min_val) / resolution + 0.5);
|
|
|
|
SG_NORMALIZE_RANGE(n, 0, steps);
|
|
|
|
*value = min_val + resolution * n;
|
|
|
|
} else {
|
|
|
|
// plain circular wrapping
|
|
|
|
SG_NORMALIZE_RANGE(*value, min_val, max_val);
|
|
|
|
}
|
|
|
|
} else { // clamp such that min <= x <= max
|
2002-12-30 19:50:17 +00:00
|
|
|
if ((min_node != 0) && (*value < min_node->getDoubleValue()))
|
|
|
|
*value = min_node->getDoubleValue();
|
|
|
|
else if ((max_node != 0) && (*value > max_node->getDoubleValue()))
|
|
|
|
*value = max_node->getDoubleValue();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2003-01-18 21:13:11 +00:00
|
|
|
static bool
|
|
|
|
compare_values (SGPropertyNode * value1, SGPropertyNode * value2)
|
|
|
|
{
|
|
|
|
switch (value1->getType()) {
|
2009-07-15 23:15:17 +00:00
|
|
|
case simgear::props::BOOL:
|
2003-01-18 21:13:11 +00:00
|
|
|
return (value1->getBoolValue() == value2->getBoolValue());
|
2009-07-15 23:15:17 +00:00
|
|
|
case simgear::props::INT:
|
2003-01-18 21:13:11 +00:00
|
|
|
return (value1->getIntValue() == value2->getIntValue());
|
2009-07-15 23:15:17 +00:00
|
|
|
case simgear::props::LONG:
|
2003-01-18 21:13:11 +00:00
|
|
|
return (value1->getLongValue() == value2->getLongValue());
|
2009-07-15 23:15:17 +00:00
|
|
|
case simgear::props::FLOAT:
|
2003-01-18 21:13:11 +00:00
|
|
|
return (value1->getFloatValue() == value2->getFloatValue());
|
2009-07-15 23:15:17 +00:00
|
|
|
case simgear::props::DOUBLE:
|
2003-01-18 21:13:11 +00:00
|
|
|
return (value1->getDoubleValue() == value2->getDoubleValue());
|
|
|
|
default:
|
|
|
|
return !strcmp(value1->getStringValue(), value2->getStringValue());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2002-12-30 19:50:17 +00:00
|
|
|
|
2013-06-22 15:06:34 +00:00
|
|
|
|
2001-07-09 16:17:20 +00:00
|
|
|
////////////////////////////////////////////////////////////////////////
|
|
|
|
// Command implementations.
|
|
|
|
////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
|
|
|
2001-06-04 21:38:44 +00:00
|
|
|
/**
|
|
|
|
* Built-in command: do nothing.
|
|
|
|
*/
|
|
|
|
static bool
|
2017-07-04 23:23:24 +00:00
|
|
|
do_null (const SGPropertyNode * arg, SGPropertyNode * root)
|
2001-06-04 21:38:44 +00:00
|
|
|
{
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2003-11-25 21:08:36 +00:00
|
|
|
/**
|
|
|
|
* Built-in command: run a Nasal script.
|
|
|
|
*/
|
|
|
|
static bool
|
2017-07-04 23:23:24 +00:00
|
|
|
do_nasal (const SGPropertyNode * arg, SGPropertyNode * root)
|
2003-11-25 21:08:36 +00:00
|
|
|
{
|
2017-07-04 23:23:24 +00:00
|
|
|
return ((FGNasalSys*)globals->get_subsystem("nasal"))->handleCommand(arg, root);
|
2003-11-25 21:08:36 +00:00
|
|
|
}
|
2001-06-04 21:38:44 +00:00
|
|
|
|
2011-10-01 20:58:40 +00:00
|
|
|
/**
|
|
|
|
* Built-in command: replay the FDR buffer
|
|
|
|
*/
|
|
|
|
static bool
|
2017-07-04 23:23:24 +00:00
|
|
|
do_replay (const SGPropertyNode * arg, SGPropertyNode * root)
|
2011-10-01 20:58:40 +00:00
|
|
|
{
|
|
|
|
FGReplay *r = (FGReplay *)(globals->get_subsystem( "replay" ));
|
|
|
|
return r->start();
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Built-in command: pause/unpause the sim
|
|
|
|
*/
|
2011-05-21 11:27:09 +00:00
|
|
|
static bool
|
2017-07-04 23:23:24 +00:00
|
|
|
do_pause (const SGPropertyNode * arg, SGPropertyNode * root)
|
2011-05-21 11:27:09 +00:00
|
|
|
{
|
2015-03-14 21:33:06 +00:00
|
|
|
bool forcePause = arg->getBoolValue("force-pause", false );
|
|
|
|
bool forcePlay = arg->getBoolValue("force-play", false );
|
|
|
|
|
2011-05-21 12:51:10 +00:00
|
|
|
bool paused = fgGetBool("/sim/freeze/master",true) || fgGetBool("/sim/freeze/clock",true);
|
2015-03-14 21:33:06 +00:00
|
|
|
|
|
|
|
if(forcePause) paused = false;
|
|
|
|
if(forcePlay) paused = true;
|
|
|
|
|
2011-10-01 20:58:40 +00:00
|
|
|
if (paused && (fgGetInt("/sim/freeze/replay-state",0)>0))
|
|
|
|
{
|
2017-07-04 23:23:24 +00:00
|
|
|
do_replay(NULL, nullptr);
|
2011-10-01 20:58:40 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
fgSetBool("/sim/freeze/master",!paused);
|
|
|
|
fgSetBool("/sim/freeze/clock",!paused);
|
|
|
|
}
|
2017-03-21 20:59:35 +00:00
|
|
|
|
2015-11-15 23:56:18 +00:00
|
|
|
syncPausePopupState();
|
2011-05-21 12:07:03 +00:00
|
|
|
return true;
|
2011-05-21 11:27:09 +00:00
|
|
|
}
|
2003-01-21 02:07:29 +00:00
|
|
|
|
2001-06-05 22:12:17 +00:00
|
|
|
/**
|
|
|
|
* Built-in command: load flight.
|
|
|
|
*
|
|
|
|
* file (optional): the name of the file to load (relative to current
|
2003-01-21 02:07:29 +00:00
|
|
|
* directory). Defaults to "fgfs.sav"
|
2001-06-05 22:12:17 +00:00
|
|
|
*/
|
|
|
|
static bool
|
2017-07-04 23:23:24 +00:00
|
|
|
do_load (const SGPropertyNode * arg, SGPropertyNode * root)
|
2001-06-05 22:12:17 +00:00
|
|
|
{
|
2015-07-12 16:49:21 +00:00
|
|
|
SGPath file(arg->getStringValue("file", "fgfs.sav"));
|
|
|
|
|
|
|
|
if (file.extension() != "sav")
|
|
|
|
file.concat(".sav");
|
2008-07-11 16:36:54 +00:00
|
|
|
|
2016-07-03 22:59:40 +00:00
|
|
|
SGPath validated_path = fgValidatePath(file, false);
|
|
|
|
if (validated_path.isNull()) {
|
2008-07-11 16:36:54 +00:00
|
|
|
SG_LOG(SG_IO, SG_ALERT, "load: reading '" << file << "' denied "
|
|
|
|
"(unauthorized access)");
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2016-07-03 22:59:40 +00:00
|
|
|
sg_ifstream input(validated_path);
|
2008-07-11 16:36:54 +00:00
|
|
|
if (input.good() && fgLoadFlight(input)) {
|
|
|
|
input.close();
|
|
|
|
SG_LOG(SG_INPUT, SG_INFO, "Restored flight from " << file);
|
|
|
|
return true;
|
|
|
|
} else {
|
|
|
|
SG_LOG(SG_INPUT, SG_WARN, "Cannot load flight from " << file);
|
|
|
|
return false;
|
|
|
|
}
|
2001-06-05 22:12:17 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Built-in command: save flight.
|
|
|
|
*
|
|
|
|
* file (optional): the name of the file to save (relative to the
|
|
|
|
* current directory). Defaults to "fgfs.sav".
|
|
|
|
*/
|
|
|
|
static bool
|
2017-07-04 23:23:24 +00:00
|
|
|
do_save (const SGPropertyNode * arg, SGPropertyNode * root)
|
2001-06-05 22:12:17 +00:00
|
|
|
{
|
2015-07-12 16:49:21 +00:00
|
|
|
SGPath file(arg->getStringValue("file", "fgfs.sav"));
|
|
|
|
|
|
|
|
if (file.extension() != "sav")
|
|
|
|
file.concat(".sav");
|
2008-07-11 16:36:54 +00:00
|
|
|
|
2016-07-03 22:59:40 +00:00
|
|
|
SGPath validated_path = fgValidatePath(file, true);
|
|
|
|
if (validated_path.isNull()) {
|
2009-02-01 23:44:36 +00:00
|
|
|
SG_LOG(SG_IO, SG_ALERT, "save: writing '" << file << "' denied "
|
2008-07-11 16:36:54 +00:00
|
|
|
"(unauthorized access)");
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool write_all = arg->getBoolValue("write-all", false);
|
|
|
|
SG_LOG(SG_INPUT, SG_INFO, "Saving flight");
|
2016-07-15 15:35:17 +00:00
|
|
|
sg_ofstream output(validated_path);
|
2008-07-11 16:36:54 +00:00
|
|
|
if (output.good() && fgSaveFlight(output, write_all)) {
|
|
|
|
output.close();
|
|
|
|
SG_LOG(SG_INPUT, SG_INFO, "Saved flight to " << file);
|
|
|
|
return true;
|
|
|
|
} else {
|
|
|
|
SG_LOG(SG_INPUT, SG_ALERT, "Cannot save flight to " << file);
|
|
|
|
return false;
|
|
|
|
}
|
2001-06-05 22:12:17 +00:00
|
|
|
}
|
|
|
|
|
2012-11-10 20:25:31 +00:00
|
|
|
/**
|
|
|
|
* Built-in command: save flight recorder tape.
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
static bool
|
2017-07-04 23:23:24 +00:00
|
|
|
do_save_tape (const SGPropertyNode * arg, SGPropertyNode * root)
|
2012-11-10 20:25:31 +00:00
|
|
|
{
|
|
|
|
FGReplay* replay = (FGReplay*) globals->get_subsystem("replay");
|
|
|
|
replay->saveTape(arg);
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
/**
|
|
|
|
* Built-in command: load flight recorder tape.
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
static bool
|
2017-07-04 23:23:24 +00:00
|
|
|
do_load_tape (const SGPropertyNode * arg, SGPropertyNode * root)
|
2012-11-10 20:25:31 +00:00
|
|
|
{
|
|
|
|
FGReplay* replay = (FGReplay*) globals->get_subsystem("replay");
|
|
|
|
replay->loadTape(arg);
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
2001-06-05 22:12:17 +00:00
|
|
|
|
2003-01-04 18:47:25 +00:00
|
|
|
static void
|
2013-06-22 15:06:34 +00:00
|
|
|
do_view_next(bool do_it)
|
2002-08-25 20:56:16 +00:00
|
|
|
{
|
2013-06-22 15:06:34 +00:00
|
|
|
// Only switch view if really requested to do so (and not for example while
|
|
|
|
// reset/reposition where /command/view/next is set to false)
|
|
|
|
if( do_it )
|
|
|
|
{
|
2002-08-25 20:56:16 +00:00
|
|
|
globals->get_current_view()->setHeadingOffset_deg(0.0);
|
|
|
|
globals->get_viewmgr()->next_view();
|
2013-06-22 15:06:34 +00:00
|
|
|
}
|
2002-08-25 20:56:16 +00:00
|
|
|
}
|
|
|
|
|
2003-01-04 18:47:25 +00:00
|
|
|
static void
|
2013-06-22 15:06:34 +00:00
|
|
|
do_view_prev(bool do_it)
|
2002-08-25 20:56:16 +00:00
|
|
|
{
|
2013-06-22 15:06:34 +00:00
|
|
|
if( do_it )
|
|
|
|
{
|
2002-08-25 20:56:16 +00:00
|
|
|
globals->get_current_view()->setHeadingOffset_deg(0.0);
|
|
|
|
globals->get_viewmgr()->prev_view();
|
2013-06-22 15:06:34 +00:00
|
|
|
}
|
2002-08-25 20:56:16 +00:00
|
|
|
}
|
|
|
|
|
2001-06-01 17:52:17 +00:00
|
|
|
/**
|
|
|
|
* Built-in command: cycle view.
|
|
|
|
*/
|
|
|
|
static bool
|
2017-07-04 23:23:24 +00:00
|
|
|
do_view_cycle (const SGPropertyNode * arg, SGPropertyNode * root)
|
2001-06-01 17:52:17 +00:00
|
|
|
{
|
Major viewer-code overhaul from Jim Wilson:
Description:
This update includes the new viewer interface as proposed by David M. and
a first pass at cleaning up the viewer/view manager code by Jim W.
Note that I have dropped Main/viewer_lookat.?xx and Main/viewer_rph.?xx and
modified the Makefile.am accordingly.
Detail of work:
Overall:
The code reads a little easier. There are still some unnecessary bits in
there and I'd like to supplement the comments in the viewer.hxx with a tiny
bit on each interface group and what the groupings mean (similar but briefer
than what you emailed me the other day). I tried not to mess up the style,
but there is an occasional inconsistency. In general I wouldn't call it done
(especially since there's no tower yet! :)), but I'd like to get this out
there so others can comment, and test.
In Viewer:
The interface as you suggested has been implemented. Basically everything
seems to work as it did visually. There is no difference that I can see in
performance, although some things might be a tiny bit faster.
I've merged the lookat and rph (pilot view) code into the recalc for the
viewer. There is still some redundancy between the two, but a lot has been
removed. In some cases I've taken some code that we'd likely want to inline
anyway and left it in there in duplicate. You'll see that the code for both
looks a little cleaner. I need to take a closer look at the rotations in
particular. I've cleaned up a little there, but I suspect more can be done
to streamline this.
The external declaration to the Quat_mat in mouse.cxx has been removed. IMHO
the quat doesn't serve any intrinsic purpose in mouse.cxx, but I'm not about
to rip it out. It would seem that there more conventional ways to get
spherical data that are just as fast. In any case all the viewer was pulling
from the quat matrix was the pitch value so I modified mouse.cxx to output to
our pitchOffset input and that works fine.
I've changed the native values to degrees from radians where appropriate.
This required a conversion from degrees to radians in a couple modules that
access the interface. Perhaps we should add interface calls that do the
conversion, e.g. a getHeadingOffset_rad() to go along with the
getHeadingOffset_deg().
On the view_offset (now headingOffset) thing there are two entry points
because of the ability to instantly switch views or to scroll to a new view
angle (by hitting the numeric keys for example). This leaves an anomaly in
the interface which should be resolved by adding "goal" settings to the
interface, e.g. a setGoalHeadingOffset_deg(), setGoalPitchOffset_deg(), etc.
Other than these two issues, the next step here will be to look at some
further optimizations, and to write support code for a tower view. That
should be fairly simple at this point. I was considering creating a
"simulated tower view" or "pedestrian view" that defaulted to a position off
to the right of whereever the plane is at the moment you switch to the tower
view. This could be a fall back when we don't have an actual tower location
at hand (as would be the case with rural airports).
ViewManager:
Basically all I did here was neaten things up by ripping out excess crap and
made it compatible as is with the new interface.
The result is that viewmanager is now ready to be developed. The two
preexisting views are still hardcoded into the view manager. The next step
would be to design configuration xml (eg /sim/view[x]/config/blahblah) that
could be used to set up as many views as we want. If we want to take the easy
way out, we might want to insist that view[0] be a pilot-view and have
viewmanager check for that.
2002-03-20 17:43:28 +00:00
|
|
|
globals->get_current_view()->setHeadingOffset_deg(0.0);
|
2002-03-14 00:29:20 +00:00
|
|
|
globals->get_viewmgr()->next_view();
|
2001-06-01 17:52:17 +00:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
CompositeViewer: Support for multiple view windows using osgViewer::CompositeViewer.
Overview:
Previously Flightgear always used a single osgViewer::Viewer(), which
inherits from both osgViewer::ViewerBase and osgViewer::View, giving a
single view window.
If CompositeViewer is enabled, we instead use a osgViewer::CompositeViewer
which contains a list of osgViewer::View's. Each of these View's can have
its own eye position, so we can have multiple different views of the same
scene.
Enable at runtime with: --composite-viewer=1
Changes to allow use of osgViewer::CompositeViewer:
Previously FGRenderer had this method:
osgViewer::Viewer* getViewer();
This has been replaced by these two new methods:
osgViewer::ViewerBase* getViewerBase();
osgViewer::View* getView();
If CompositeViewer is not enabled (the default), the actual runtime state
is unchanged, and getViewerBase() and getView() both return a pointer to
the singleton osgViewer::Viewer() object.
If CompositeViewer is enabled, getViewerBase() returns a pointer to a
singleton osgViewer::CompositeViewer object, and getView() returns a
pointer to the first osgViewer::View in the osgViewer::CompositeViewer's
list.
The other significant change to FGRenderer() is the new method:
osg::FrameStamp* getFrameStamp()
If CompositeViewer is not enabled, this simply returns
getView()->getFrameStamp(). If CompositeViewer is enabled it returns
getViewerBase()->getFrameStamp(). It is important that code that previously
called getView()->getFrameStamp() is changed to use the new method, because
when CompositeViewer is enabled individual osgViewer::View's appear to
return an osg::FrameStamp with zero frame number).
All code that uses FGRenderer has been patched up to use the new methods so
that things work as before regardless of whether CompositeViewer is enabled
or not.
We make FGRenderer::update() call SviewUpdate() which updates any extra
views.
Extra view windows:
If CompositeViewer is enabled, one can create top-level extra view windows
by calling SviewCreate(). See src/Viewer/sview.hxx for details.
Each extra view window has its own simgear::compositor::Compositor
instance.
Currently SviewCreate() can create extra view windows that clone the
current view, or view from one point to another (e.g. from one multiplayer
aircraft to the user's aircradt) or keep two aircraft in view, one at a
fixed distance in the foreground.
SviewCreate() can be called from nasal via new nasal commands "view-clone",
"view-last-pair", "view-last-pair-double" and "view-push". Associated
changes to fgdata gives access to these via the View menu. The "view-push"
command tags the current view for later use by "view-last-pair" and
"view-last-pair-double".
Extra view windows created by SviewCreate() use a new view system called
Sview, which allows views to be constructed at runtime instead of being
hard-coded in *-set.xml files. This is work in progress and views aren't
all fully implemented. For example Pilot view gets things slightly wrong
with large roll values, Tower View AGL is not implemented, and we don't
implement damping. See top of src/Viewer/sview.cxx for an overview.
OpenSceneGraph-3.4 issues:
OSG-3.4's event handling seems to be incorrect with CompositeViewer -
events get sent for the wrong window which causes issues with resize and
closing. It doesn't seem to be possible to work around this, so closing
extra view windows can end up closing the main window for example.
OSG-3.6 seems to fix the problems.
We warn if CompositeViewer is enabled and OpenSceneGraph is 3.4.
2020-11-16 18:43:46 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Built-in command: view-push.
|
|
|
|
*/
|
|
|
|
static bool
|
|
|
|
do_view_push (const SGPropertyNode * arg, SGPropertyNode * root)
|
|
|
|
{
|
|
|
|
SG_LOG(SG_GENERAL, SG_ALERT, "do_view_push() called");
|
|
|
|
globals->get_viewmgr()->view_push();
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Built-in command: clone view.
|
|
|
|
*/
|
|
|
|
static bool
|
|
|
|
do_view_clone (const SGPropertyNode * arg, SGPropertyNode * root)
|
|
|
|
{
|
|
|
|
SG_LOG(SG_GENERAL, SG_ALERT, "do_view_clone() called");
|
Improved Sview system to allow specification of views from Nasal using properties.
SviewCreate() now takes a SGPropertyNode* config which contains all information
needed to specify the view, including window size and position.
By default views are defined using a series of <step>...</step> nodes which map
to SviewStep* classes internally.
Alternatively, existing extra-view functionality is supported using
type='current', 'last_pair' or 'last_pair_double', plus one can use old-style
<view>...<view> nodes with type='legacy'.
See src/Viewer/sview.hxx for details and examples.
The extra-view commands view-clone, view-last-pair and view-last-pair-double
now pass the supplied SGPropertyNode* through the underlying FGViewMgr to
SviewCreate().
Improved handling of +/- heading and pitch values. For example some legacy
views have inverse relationship between heading-offset-deg and the actual
heading offset, so we now only multiply by -1 early on when handling these
legacy views, and SviewStepRotate does not negate.
Fixed Helicopter view direction bug with multiplayer aircraft - need to use
global /sim/current-view/heading-offset-deg, even for multiplayer aircraft.
Fixed incorrect handling of roll in pilot view - when adding in extra changes
of heading, pitch and roll to things like Helicopter view, simply adding to
heading, pitch and roll doesn't work if aircraft roll is not zero. Instead we
need to do the calculation with SGQuatd, so have added optional rotation params
to SviewStepFinal.
Added support for legacy Tower view look from.
Details:
src/Main/fg_commands.cxx
src/Viewer/viewmgr.cxx
src/Viewer/viewmgr.hxx
Added 'view-new' command.
All recently-added view commands now pass the SGPropertyNode* arg to
globals->get_viewmgr() and from there to SviewCreate().
src/Viewer/sview.cxx
src/Viewer/sview.hxx
SviewStepAircraft and SviewStepNearestTower uses new Callsign
class for tracking multiplayer aircraft by callsign instead of
/ai/models/multiplayer[] number, so we cope when multiplayer aircraft
are renumbered by network outages.
Double views are now implemented as a final view step, instead of
in a separate class derived from SviewView. So all views are
implemented using SviewViewEyeTarget, and there is arguably no
need for the separate SviewView base class.
SviewViewEyeTarget now has a single list of steps, instead of separate
lists for eye and target, because the use of SviewStepCopyToTarget makes
separate lists unnecessary.
2020-12-05 20:39:29 +00:00
|
|
|
globals->get_viewmgr()->clone_current_view(arg);
|
CompositeViewer: Support for multiple view windows using osgViewer::CompositeViewer.
Overview:
Previously Flightgear always used a single osgViewer::Viewer(), which
inherits from both osgViewer::ViewerBase and osgViewer::View, giving a
single view window.
If CompositeViewer is enabled, we instead use a osgViewer::CompositeViewer
which contains a list of osgViewer::View's. Each of these View's can have
its own eye position, so we can have multiple different views of the same
scene.
Enable at runtime with: --composite-viewer=1
Changes to allow use of osgViewer::CompositeViewer:
Previously FGRenderer had this method:
osgViewer::Viewer* getViewer();
This has been replaced by these two new methods:
osgViewer::ViewerBase* getViewerBase();
osgViewer::View* getView();
If CompositeViewer is not enabled (the default), the actual runtime state
is unchanged, and getViewerBase() and getView() both return a pointer to
the singleton osgViewer::Viewer() object.
If CompositeViewer is enabled, getViewerBase() returns a pointer to a
singleton osgViewer::CompositeViewer object, and getView() returns a
pointer to the first osgViewer::View in the osgViewer::CompositeViewer's
list.
The other significant change to FGRenderer() is the new method:
osg::FrameStamp* getFrameStamp()
If CompositeViewer is not enabled, this simply returns
getView()->getFrameStamp(). If CompositeViewer is enabled it returns
getViewerBase()->getFrameStamp(). It is important that code that previously
called getView()->getFrameStamp() is changed to use the new method, because
when CompositeViewer is enabled individual osgViewer::View's appear to
return an osg::FrameStamp with zero frame number).
All code that uses FGRenderer has been patched up to use the new methods so
that things work as before regardless of whether CompositeViewer is enabled
or not.
We make FGRenderer::update() call SviewUpdate() which updates any extra
views.
Extra view windows:
If CompositeViewer is enabled, one can create top-level extra view windows
by calling SviewCreate(). See src/Viewer/sview.hxx for details.
Each extra view window has its own simgear::compositor::Compositor
instance.
Currently SviewCreate() can create extra view windows that clone the
current view, or view from one point to another (e.g. from one multiplayer
aircraft to the user's aircradt) or keep two aircraft in view, one at a
fixed distance in the foreground.
SviewCreate() can be called from nasal via new nasal commands "view-clone",
"view-last-pair", "view-last-pair-double" and "view-push". Associated
changes to fgdata gives access to these via the View menu. The "view-push"
command tags the current view for later use by "view-last-pair" and
"view-last-pair-double".
Extra view windows created by SviewCreate() use a new view system called
Sview, which allows views to be constructed at runtime instead of being
hard-coded in *-set.xml files. This is work in progress and views aren't
all fully implemented. For example Pilot view gets things slightly wrong
with large roll values, Tower View AGL is not implemented, and we don't
implement damping. See top of src/Viewer/sview.cxx for an overview.
OpenSceneGraph-3.4 issues:
OSG-3.4's event handling seems to be incorrect with CompositeViewer -
events get sent for the wrong window which causes issues with resize and
closing. It doesn't seem to be possible to work around this, so closing
extra view windows can end up closing the main window for example.
OSG-3.6 seems to fix the problems.
We warn if CompositeViewer is enabled and OpenSceneGraph is 3.4.
2020-11-16 18:43:46 +00:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Built-in command: view last pair.
|
|
|
|
*/
|
|
|
|
static bool
|
|
|
|
do_view_last_pair (const SGPropertyNode * arg, SGPropertyNode * root)
|
|
|
|
{
|
|
|
|
SG_LOG(SG_GENERAL, SG_ALERT, "do_view_last_pair() called");
|
Improved Sview system to allow specification of views from Nasal using properties.
SviewCreate() now takes a SGPropertyNode* config which contains all information
needed to specify the view, including window size and position.
By default views are defined using a series of <step>...</step> nodes which map
to SviewStep* classes internally.
Alternatively, existing extra-view functionality is supported using
type='current', 'last_pair' or 'last_pair_double', plus one can use old-style
<view>...<view> nodes with type='legacy'.
See src/Viewer/sview.hxx for details and examples.
The extra-view commands view-clone, view-last-pair and view-last-pair-double
now pass the supplied SGPropertyNode* through the underlying FGViewMgr to
SviewCreate().
Improved handling of +/- heading and pitch values. For example some legacy
views have inverse relationship between heading-offset-deg and the actual
heading offset, so we now only multiply by -1 early on when handling these
legacy views, and SviewStepRotate does not negate.
Fixed Helicopter view direction bug with multiplayer aircraft - need to use
global /sim/current-view/heading-offset-deg, even for multiplayer aircraft.
Fixed incorrect handling of roll in pilot view - when adding in extra changes
of heading, pitch and roll to things like Helicopter view, simply adding to
heading, pitch and roll doesn't work if aircraft roll is not zero. Instead we
need to do the calculation with SGQuatd, so have added optional rotation params
to SviewStepFinal.
Added support for legacy Tower view look from.
Details:
src/Main/fg_commands.cxx
src/Viewer/viewmgr.cxx
src/Viewer/viewmgr.hxx
Added 'view-new' command.
All recently-added view commands now pass the SGPropertyNode* arg to
globals->get_viewmgr() and from there to SviewCreate().
src/Viewer/sview.cxx
src/Viewer/sview.hxx
SviewStepAircraft and SviewStepNearestTower uses new Callsign
class for tracking multiplayer aircraft by callsign instead of
/ai/models/multiplayer[] number, so we cope when multiplayer aircraft
are renumbered by network outages.
Double views are now implemented as a final view step, instead of
in a separate class derived from SviewView. So all views are
implemented using SviewViewEyeTarget, and there is arguably no
need for the separate SviewView base class.
SviewViewEyeTarget now has a single list of steps, instead of separate
lists for eye and target, because the use of SviewStepCopyToTarget makes
separate lists unnecessary.
2020-12-05 20:39:29 +00:00
|
|
|
globals->get_viewmgr()->clone_last_pair(arg);
|
CompositeViewer: Support for multiple view windows using osgViewer::CompositeViewer.
Overview:
Previously Flightgear always used a single osgViewer::Viewer(), which
inherits from both osgViewer::ViewerBase and osgViewer::View, giving a
single view window.
If CompositeViewer is enabled, we instead use a osgViewer::CompositeViewer
which contains a list of osgViewer::View's. Each of these View's can have
its own eye position, so we can have multiple different views of the same
scene.
Enable at runtime with: --composite-viewer=1
Changes to allow use of osgViewer::CompositeViewer:
Previously FGRenderer had this method:
osgViewer::Viewer* getViewer();
This has been replaced by these two new methods:
osgViewer::ViewerBase* getViewerBase();
osgViewer::View* getView();
If CompositeViewer is not enabled (the default), the actual runtime state
is unchanged, and getViewerBase() and getView() both return a pointer to
the singleton osgViewer::Viewer() object.
If CompositeViewer is enabled, getViewerBase() returns a pointer to a
singleton osgViewer::CompositeViewer object, and getView() returns a
pointer to the first osgViewer::View in the osgViewer::CompositeViewer's
list.
The other significant change to FGRenderer() is the new method:
osg::FrameStamp* getFrameStamp()
If CompositeViewer is not enabled, this simply returns
getView()->getFrameStamp(). If CompositeViewer is enabled it returns
getViewerBase()->getFrameStamp(). It is important that code that previously
called getView()->getFrameStamp() is changed to use the new method, because
when CompositeViewer is enabled individual osgViewer::View's appear to
return an osg::FrameStamp with zero frame number).
All code that uses FGRenderer has been patched up to use the new methods so
that things work as before regardless of whether CompositeViewer is enabled
or not.
We make FGRenderer::update() call SviewUpdate() which updates any extra
views.
Extra view windows:
If CompositeViewer is enabled, one can create top-level extra view windows
by calling SviewCreate(). See src/Viewer/sview.hxx for details.
Each extra view window has its own simgear::compositor::Compositor
instance.
Currently SviewCreate() can create extra view windows that clone the
current view, or view from one point to another (e.g. from one multiplayer
aircraft to the user's aircradt) or keep two aircraft in view, one at a
fixed distance in the foreground.
SviewCreate() can be called from nasal via new nasal commands "view-clone",
"view-last-pair", "view-last-pair-double" and "view-push". Associated
changes to fgdata gives access to these via the View menu. The "view-push"
command tags the current view for later use by "view-last-pair" and
"view-last-pair-double".
Extra view windows created by SviewCreate() use a new view system called
Sview, which allows views to be constructed at runtime instead of being
hard-coded in *-set.xml files. This is work in progress and views aren't
all fully implemented. For example Pilot view gets things slightly wrong
with large roll values, Tower View AGL is not implemented, and we don't
implement damping. See top of src/Viewer/sview.cxx for an overview.
OpenSceneGraph-3.4 issues:
OSG-3.4's event handling seems to be incorrect with CompositeViewer -
events get sent for the wrong window which causes issues with resize and
closing. It doesn't seem to be possible to work around this, so closing
extra view windows can end up closing the main window for example.
OSG-3.6 seems to fix the problems.
We warn if CompositeViewer is enabled and OpenSceneGraph is 3.4.
2020-11-16 18:43:46 +00:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Built-in command: double view last pair.
|
|
|
|
*/
|
|
|
|
static bool
|
|
|
|
do_view_last_pair_double (const SGPropertyNode * arg, SGPropertyNode * root)
|
|
|
|
{
|
|
|
|
SG_LOG(SG_GENERAL, SG_ALERT, "do_view_last_pair_double() called");
|
Improved Sview system to allow specification of views from Nasal using properties.
SviewCreate() now takes a SGPropertyNode* config which contains all information
needed to specify the view, including window size and position.
By default views are defined using a series of <step>...</step> nodes which map
to SviewStep* classes internally.
Alternatively, existing extra-view functionality is supported using
type='current', 'last_pair' or 'last_pair_double', plus one can use old-style
<view>...<view> nodes with type='legacy'.
See src/Viewer/sview.hxx for details and examples.
The extra-view commands view-clone, view-last-pair and view-last-pair-double
now pass the supplied SGPropertyNode* through the underlying FGViewMgr to
SviewCreate().
Improved handling of +/- heading and pitch values. For example some legacy
views have inverse relationship between heading-offset-deg and the actual
heading offset, so we now only multiply by -1 early on when handling these
legacy views, and SviewStepRotate does not negate.
Fixed Helicopter view direction bug with multiplayer aircraft - need to use
global /sim/current-view/heading-offset-deg, even for multiplayer aircraft.
Fixed incorrect handling of roll in pilot view - when adding in extra changes
of heading, pitch and roll to things like Helicopter view, simply adding to
heading, pitch and roll doesn't work if aircraft roll is not zero. Instead we
need to do the calculation with SGQuatd, so have added optional rotation params
to SviewStepFinal.
Added support for legacy Tower view look from.
Details:
src/Main/fg_commands.cxx
src/Viewer/viewmgr.cxx
src/Viewer/viewmgr.hxx
Added 'view-new' command.
All recently-added view commands now pass the SGPropertyNode* arg to
globals->get_viewmgr() and from there to SviewCreate().
src/Viewer/sview.cxx
src/Viewer/sview.hxx
SviewStepAircraft and SviewStepNearestTower uses new Callsign
class for tracking multiplayer aircraft by callsign instead of
/ai/models/multiplayer[] number, so we cope when multiplayer aircraft
are renumbered by network outages.
Double views are now implemented as a final view step, instead of
in a separate class derived from SviewView. So all views are
implemented using SviewViewEyeTarget, and there is arguably no
need for the separate SviewView base class.
SviewViewEyeTarget now has a single list of steps, instead of separate
lists for eye and target, because the use of SviewStepCopyToTarget makes
separate lists unnecessary.
2020-12-05 20:39:29 +00:00
|
|
|
globals->get_viewmgr()->clone_last_pair_double(arg);
|
CompositeViewer: Support for multiple view windows using osgViewer::CompositeViewer.
Overview:
Previously Flightgear always used a single osgViewer::Viewer(), which
inherits from both osgViewer::ViewerBase and osgViewer::View, giving a
single view window.
If CompositeViewer is enabled, we instead use a osgViewer::CompositeViewer
which contains a list of osgViewer::View's. Each of these View's can have
its own eye position, so we can have multiple different views of the same
scene.
Enable at runtime with: --composite-viewer=1
Changes to allow use of osgViewer::CompositeViewer:
Previously FGRenderer had this method:
osgViewer::Viewer* getViewer();
This has been replaced by these two new methods:
osgViewer::ViewerBase* getViewerBase();
osgViewer::View* getView();
If CompositeViewer is not enabled (the default), the actual runtime state
is unchanged, and getViewerBase() and getView() both return a pointer to
the singleton osgViewer::Viewer() object.
If CompositeViewer is enabled, getViewerBase() returns a pointer to a
singleton osgViewer::CompositeViewer object, and getView() returns a
pointer to the first osgViewer::View in the osgViewer::CompositeViewer's
list.
The other significant change to FGRenderer() is the new method:
osg::FrameStamp* getFrameStamp()
If CompositeViewer is not enabled, this simply returns
getView()->getFrameStamp(). If CompositeViewer is enabled it returns
getViewerBase()->getFrameStamp(). It is important that code that previously
called getView()->getFrameStamp() is changed to use the new method, because
when CompositeViewer is enabled individual osgViewer::View's appear to
return an osg::FrameStamp with zero frame number).
All code that uses FGRenderer has been patched up to use the new methods so
that things work as before regardless of whether CompositeViewer is enabled
or not.
We make FGRenderer::update() call SviewUpdate() which updates any extra
views.
Extra view windows:
If CompositeViewer is enabled, one can create top-level extra view windows
by calling SviewCreate(). See src/Viewer/sview.hxx for details.
Each extra view window has its own simgear::compositor::Compositor
instance.
Currently SviewCreate() can create extra view windows that clone the
current view, or view from one point to another (e.g. from one multiplayer
aircraft to the user's aircradt) or keep two aircraft in view, one at a
fixed distance in the foreground.
SviewCreate() can be called from nasal via new nasal commands "view-clone",
"view-last-pair", "view-last-pair-double" and "view-push". Associated
changes to fgdata gives access to these via the View menu. The "view-push"
command tags the current view for later use by "view-last-pair" and
"view-last-pair-double".
Extra view windows created by SviewCreate() use a new view system called
Sview, which allows views to be constructed at runtime instead of being
hard-coded in *-set.xml files. This is work in progress and views aren't
all fully implemented. For example Pilot view gets things slightly wrong
with large roll values, Tower View AGL is not implemented, and we don't
implement damping. See top of src/Viewer/sview.cxx for an overview.
OpenSceneGraph-3.4 issues:
OSG-3.4's event handling seems to be incorrect with CompositeViewer -
events get sent for the wrong window which causes issues with resize and
closing. It doesn't seem to be possible to work around this, so closing
extra view windows can end up closing the main window for example.
OSG-3.6 seems to fix the problems.
We warn if CompositeViewer is enabled and OpenSceneGraph is 3.4.
2020-11-16 18:43:46 +00:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
Improved Sview system to allow specification of views from Nasal using properties.
SviewCreate() now takes a SGPropertyNode* config which contains all information
needed to specify the view, including window size and position.
By default views are defined using a series of <step>...</step> nodes which map
to SviewStep* classes internally.
Alternatively, existing extra-view functionality is supported using
type='current', 'last_pair' or 'last_pair_double', plus one can use old-style
<view>...<view> nodes with type='legacy'.
See src/Viewer/sview.hxx for details and examples.
The extra-view commands view-clone, view-last-pair and view-last-pair-double
now pass the supplied SGPropertyNode* through the underlying FGViewMgr to
SviewCreate().
Improved handling of +/- heading and pitch values. For example some legacy
views have inverse relationship between heading-offset-deg and the actual
heading offset, so we now only multiply by -1 early on when handling these
legacy views, and SviewStepRotate does not negate.
Fixed Helicopter view direction bug with multiplayer aircraft - need to use
global /sim/current-view/heading-offset-deg, even for multiplayer aircraft.
Fixed incorrect handling of roll in pilot view - when adding in extra changes
of heading, pitch and roll to things like Helicopter view, simply adding to
heading, pitch and roll doesn't work if aircraft roll is not zero. Instead we
need to do the calculation with SGQuatd, so have added optional rotation params
to SviewStepFinal.
Added support for legacy Tower view look from.
Details:
src/Main/fg_commands.cxx
src/Viewer/viewmgr.cxx
src/Viewer/viewmgr.hxx
Added 'view-new' command.
All recently-added view commands now pass the SGPropertyNode* arg to
globals->get_viewmgr() and from there to SviewCreate().
src/Viewer/sview.cxx
src/Viewer/sview.hxx
SviewStepAircraft and SviewStepNearestTower uses new Callsign
class for tracking multiplayer aircraft by callsign instead of
/ai/models/multiplayer[] number, so we cope when multiplayer aircraft
are renumbered by network outages.
Double views are now implemented as a final view step, instead of
in a separate class derived from SviewView. So all views are
implemented using SviewViewEyeTarget, and there is arguably no
need for the separate SviewView base class.
SviewViewEyeTarget now has a single list of steps, instead of separate
lists for eye and target, because the use of SviewStepCopyToTarget makes
separate lists unnecessary.
2020-12-05 20:39:29 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Built-in command: double view last pair.
|
|
|
|
*/
|
|
|
|
static bool
|
|
|
|
do_view_new (const SGPropertyNode * arg, SGPropertyNode * root)
|
|
|
|
{
|
|
|
|
SG_LOG(SG_GENERAL, SG_ALERT, "do_view_new() called");
|
|
|
|
globals->get_viewmgr()->view_new(arg);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2001-06-01 17:52:17 +00:00
|
|
|
/**
|
|
|
|
* Built-in command: toggle a bool property value.
|
|
|
|
*
|
|
|
|
* property: The name of the property to toggle.
|
|
|
|
*/
|
|
|
|
static bool
|
2017-07-04 23:23:24 +00:00
|
|
|
do_property_toggle (const SGPropertyNode * arg, SGPropertyNode * root)
|
2001-06-01 17:52:17 +00:00
|
|
|
{
|
2017-07-04 23:23:24 +00:00
|
|
|
SGPropertyNode * prop = get_prop(arg, root);
|
2001-07-09 16:17:20 +00:00
|
|
|
return prop->setBoolValue(!prop->getBoolValue());
|
2001-06-01 17:52:17 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Built-in command: assign a value to a property.
|
|
|
|
*
|
|
|
|
* property: the name of the property to assign.
|
2002-12-31 03:05:38 +00:00
|
|
|
* value: the value to assign; or
|
|
|
|
* property[1]: the property to copy from.
|
2001-06-01 17:52:17 +00:00
|
|
|
*/
|
|
|
|
static bool
|
2017-07-04 23:23:24 +00:00
|
|
|
do_property_assign (const SGPropertyNode * arg, SGPropertyNode * root)
|
2001-06-01 17:52:17 +00:00
|
|
|
{
|
2017-07-04 23:23:24 +00:00
|
|
|
SGPropertyNode * prop = get_prop(arg,root);
|
2002-10-25 22:19:51 +00:00
|
|
|
const SGPropertyNode * value = arg->getNode("value");
|
2001-06-01 17:52:17 +00:00
|
|
|
|
2002-12-31 03:05:38 +00:00
|
|
|
if (value != 0)
|
|
|
|
return prop->setUnspecifiedValue(value->getStringValue());
|
|
|
|
else
|
2011-07-10 19:25:16 +00:00
|
|
|
{
|
2017-07-04 23:23:24 +00:00
|
|
|
const SGPropertyNode * prop2 = get_prop2(arg,root);
|
2011-07-10 19:25:16 +00:00
|
|
|
if (prop2)
|
|
|
|
return prop->setUnspecifiedValue(prop2->getStringValue());
|
|
|
|
else
|
|
|
|
return false;
|
|
|
|
}
|
2001-06-01 17:52:17 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Built-in command: increment or decrement a property value.
|
|
|
|
*
|
2002-12-31 12:08:32 +00:00
|
|
|
* If the 'step' argument is present, it will be used; otherwise,
|
|
|
|
* the command uses 'offset' and 'factor', usually from the mouse.
|
|
|
|
*
|
2001-06-01 17:52:17 +00:00
|
|
|
* property: the name of the property to increment or decrement.
|
2001-07-27 22:00:35 +00:00
|
|
|
* step: the amount of the increment or decrement (default: 0).
|
2017-03-21 20:59:35 +00:00
|
|
|
* offset: offset from the current setting (used for the mouse; multiplied
|
2002-12-31 12:08:32 +00:00
|
|
|
* by factor)
|
|
|
|
* factor: scaling amount for the offset (defaults to 1).
|
2001-07-27 22:00:35 +00:00
|
|
|
* min: the minimum allowed value (default: no minimum).
|
|
|
|
* max: the maximum allowed value (default: no maximum).
|
2017-03-21 20:59:35 +00:00
|
|
|
* mask: 'integer' to apply only to the left of the decimal point,
|
2002-12-30 19:50:17 +00:00
|
|
|
* 'decimal' to apply only to the right of the decimal point,
|
|
|
|
* or 'all' to apply to the whole number (the default).
|
2001-07-27 22:00:35 +00:00
|
|
|
* wrap: true if the value should be wrapped when it passes min or max;
|
|
|
|
* both min and max must be present for this to work (default:
|
|
|
|
* false).
|
2001-06-01 17:52:17 +00:00
|
|
|
*/
|
|
|
|
static bool
|
2017-07-04 23:23:24 +00:00
|
|
|
do_property_adjust (const SGPropertyNode * arg, SGPropertyNode * root)
|
2001-06-01 17:52:17 +00:00
|
|
|
{
|
2017-07-04 23:23:24 +00:00
|
|
|
SGPropertyNode * prop = get_prop(arg,root);
|
2002-10-25 22:19:51 +00:00
|
|
|
|
2002-12-31 12:08:32 +00:00
|
|
|
double amount = 0;
|
|
|
|
if (arg->hasValue("step"))
|
|
|
|
amount = arg->getDoubleValue("step");
|
|
|
|
else
|
2019-01-06 14:30:58 +00:00
|
|
|
amount = (arg->getDoubleValue("factor", 1.0)
|
2002-12-31 12:08:32 +00:00
|
|
|
* arg->getDoubleValue("offset"));
|
2017-03-21 20:59:35 +00:00
|
|
|
|
2002-12-30 19:50:17 +00:00
|
|
|
double unmodifiable, modifiable;
|
|
|
|
split_value(prop->getDoubleValue(), arg->getStringValue("mask", "all"),
|
|
|
|
&unmodifiable, &modifiable);
|
2002-12-31 12:08:32 +00:00
|
|
|
modifiable += amount;
|
2002-12-30 19:50:17 +00:00
|
|
|
limit_value(&modifiable, arg);
|
2002-10-25 22:19:51 +00:00
|
|
|
|
2002-12-30 19:50:17 +00:00
|
|
|
prop->setDoubleValue(unmodifiable + modifiable);
|
2003-01-04 18:47:25 +00:00
|
|
|
|
|
|
|
return true;
|
2001-06-01 17:52:17 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2001-06-26 21:59:49 +00:00
|
|
|
/**
|
2001-06-29 03:47:39 +00:00
|
|
|
* Built-in command: multiply a property value.
|
|
|
|
*
|
|
|
|
* property: the name of the property to multiply.
|
|
|
|
* factor: the amount by which to multiply.
|
2002-12-30 19:50:17 +00:00
|
|
|
* min: the minimum allowed value (default: no minimum).
|
|
|
|
* max: the maximum allowed value (default: no maximum).
|
2017-03-21 20:59:35 +00:00
|
|
|
* mask: 'integer' to apply only to the left of the decimal point,
|
2002-12-30 19:50:17 +00:00
|
|
|
* 'decimal' to apply only to the right of the decimal point,
|
|
|
|
* or 'all' to apply to the whole number (the default).
|
|
|
|
* wrap: true if the value should be wrapped when it passes min or max;
|
|
|
|
* both min and max must be present for this to work (default:
|
|
|
|
* false).
|
2001-06-29 03:47:39 +00:00
|
|
|
*/
|
|
|
|
static bool
|
2017-07-04 23:23:24 +00:00
|
|
|
do_property_multiply (const SGPropertyNode * arg, SGPropertyNode * root)
|
2001-06-29 03:47:39 +00:00
|
|
|
{
|
2017-07-04 23:23:24 +00:00
|
|
|
SGPropertyNode * prop = get_prop(arg,root);
|
2019-01-06 14:30:58 +00:00
|
|
|
double factor = arg->getDoubleValue("factor", 1.0);
|
2002-10-25 22:19:51 +00:00
|
|
|
|
2002-12-30 19:50:17 +00:00
|
|
|
double unmodifiable, modifiable;
|
|
|
|
split_value(prop->getDoubleValue(), arg->getStringValue("mask", "all"),
|
|
|
|
&unmodifiable, &modifiable);
|
|
|
|
modifiable *= factor;
|
|
|
|
limit_value(&modifiable, arg);
|
2002-10-25 22:19:51 +00:00
|
|
|
|
2002-12-30 19:50:17 +00:00
|
|
|
prop->setDoubleValue(unmodifiable + modifiable);
|
2003-01-04 18:47:25 +00:00
|
|
|
|
|
|
|
return true;
|
2001-06-26 21:59:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2001-06-01 17:52:17 +00:00
|
|
|
/**
|
|
|
|
* Built-in command: swap two property values.
|
|
|
|
*
|
|
|
|
* property[0]: the name of the first property.
|
|
|
|
* property[1]: the name of the second property.
|
|
|
|
*/
|
|
|
|
static bool
|
2017-07-04 23:23:24 +00:00
|
|
|
do_property_swap (const SGPropertyNode * arg, SGPropertyNode * root)
|
2001-06-01 17:52:17 +00:00
|
|
|
{
|
2017-07-04 23:23:24 +00:00
|
|
|
SGPropertyNode * prop1 = get_prop(arg,root);
|
|
|
|
SGPropertyNode * prop2 = get_prop2(arg,root);
|
2001-07-09 16:17:20 +00:00
|
|
|
|
|
|
|
// FIXME: inefficient
|
|
|
|
const string & tmp = prop1->getStringValue();
|
|
|
|
return (prop1->setUnspecifiedValue(prop2->getStringValue()) &&
|
2002-03-19 17:12:13 +00:00
|
|
|
prop2->setUnspecifiedValue(tmp.c_str()));
|
2001-06-01 17:52:17 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2001-06-04 21:38:44 +00:00
|
|
|
/**
|
2002-12-22 19:58:34 +00:00
|
|
|
* Built-in command: Set a property to an axis or other moving input.
|
2001-06-04 21:38:44 +00:00
|
|
|
*
|
|
|
|
* property: the name of the property to set.
|
|
|
|
* setting: the current input setting, usually between -1.0 and 1.0.
|
|
|
|
* offset: the offset to shift by, before applying the factor.
|
|
|
|
* factor: the factor to multiply by (use negative to reverse).
|
|
|
|
*/
|
|
|
|
static bool
|
2017-07-04 23:23:24 +00:00
|
|
|
do_property_scale (const SGPropertyNode * arg, SGPropertyNode * root)
|
2001-06-04 21:38:44 +00:00
|
|
|
{
|
2017-07-04 23:23:24 +00:00
|
|
|
SGPropertyNode * prop = get_prop(arg,root);
|
2002-10-25 22:19:51 +00:00
|
|
|
double setting = arg->getDoubleValue("setting");
|
|
|
|
double offset = arg->getDoubleValue("offset", 0.0);
|
|
|
|
double factor = arg->getDoubleValue("factor", 1.0);
|
|
|
|
bool squared = arg->getBoolValue("squared", false);
|
2003-03-01 21:28:51 +00:00
|
|
|
int power = arg->getIntValue("power", (squared ? 2 : 1));
|
|
|
|
|
|
|
|
int sign = (setting < 0 ? -1 : 1);
|
|
|
|
|
|
|
|
switch (power) {
|
|
|
|
case 1:
|
|
|
|
break;
|
|
|
|
case 2:
|
|
|
|
setting = setting * setting * sign;
|
|
|
|
break;
|
|
|
|
case 3:
|
|
|
|
setting = setting * setting * setting;
|
|
|
|
break;
|
|
|
|
case 4:
|
|
|
|
setting = setting * setting * setting * setting * sign;
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
setting = pow(setting, power);
|
|
|
|
if ((power % 2) == 0)
|
|
|
|
setting *= sign;
|
|
|
|
break;
|
|
|
|
}
|
2002-02-19 23:54:47 +00:00
|
|
|
|
2002-10-25 22:19:51 +00:00
|
|
|
return prop->setDoubleValue((setting + offset) * factor);
|
2001-06-04 21:38:44 +00:00
|
|
|
}
|
|
|
|
|
2002-12-22 19:58:34 +00:00
|
|
|
|
2003-01-18 21:13:11 +00:00
|
|
|
/**
|
|
|
|
* Built-in command: cycle a property through a set of values.
|
|
|
|
*
|
|
|
|
* If the current value isn't in the list, the cycle will
|
|
|
|
* (re)start from the beginning.
|
|
|
|
*
|
|
|
|
* property: the name of the property to cycle.
|
|
|
|
* value[*]: the list of values to cycle through.
|
|
|
|
*/
|
|
|
|
static bool
|
2017-07-04 23:23:24 +00:00
|
|
|
do_property_cycle (const SGPropertyNode * arg, SGPropertyNode * root)
|
2003-01-18 21:13:11 +00:00
|
|
|
{
|
2017-07-04 23:23:24 +00:00
|
|
|
SGPropertyNode * prop = get_prop(arg,root);
|
2013-07-29 21:59:45 +00:00
|
|
|
std::vector<SGPropertyNode_ptr> values = arg->getChildren("value");
|
2017-03-21 20:59:35 +00:00
|
|
|
|
2013-05-12 12:30:48 +00:00
|
|
|
bool wrap = arg->getBoolValue("wrap", true);
|
|
|
|
// compatible with knob/pick animations
|
|
|
|
int offset = arg->getIntValue("offset", 1);
|
2017-03-21 20:59:35 +00:00
|
|
|
|
2003-01-18 21:13:11 +00:00
|
|
|
int selection = -1;
|
|
|
|
int nSelections = values.size();
|
|
|
|
|
|
|
|
if (nSelections < 1) {
|
|
|
|
SG_LOG(SG_GENERAL, SG_ALERT, "No values for property-cycle");
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Try to find the current selection
|
|
|
|
for (int i = 0; i < nSelections; i++) {
|
|
|
|
if (compare_values(prop, values[i])) {
|
2013-05-12 12:30:48 +00:00
|
|
|
selection = i;
|
2003-01-18 21:13:11 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-05-12 12:30:48 +00:00
|
|
|
if (selection < 0) { // default to first selection
|
2003-01-18 21:13:11 +00:00
|
|
|
selection = 0;
|
2013-05-12 12:30:48 +00:00
|
|
|
} else {
|
|
|
|
selection += offset;
|
|
|
|
if (wrap) {
|
|
|
|
selection = (selection + nSelections) % nSelections;
|
|
|
|
} else {
|
|
|
|
SG_CLAMP_RANGE(selection, 0, nSelections - 1);
|
|
|
|
}
|
|
|
|
}
|
2017-03-21 20:59:35 +00:00
|
|
|
|
2003-01-18 21:13:11 +00:00
|
|
|
prop->setUnspecifiedValue(values[selection]->getStringValue());
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2003-04-04 22:38:35 +00:00
|
|
|
/**
|
|
|
|
* Built-in command: randomize a numeric property value.
|
|
|
|
*
|
|
|
|
* property: the name of the property value to randomize.
|
|
|
|
* min: the minimum allowed value.
|
|
|
|
* max: the maximum allowed value.
|
|
|
|
*/
|
|
|
|
static bool
|
2017-07-04 23:23:24 +00:00
|
|
|
do_property_randomize (const SGPropertyNode * arg, SGPropertyNode * root)
|
2003-04-04 22:38:35 +00:00
|
|
|
{
|
2017-07-04 23:23:24 +00:00
|
|
|
SGPropertyNode * prop = get_prop(arg,root);
|
2003-04-04 22:38:35 +00:00
|
|
|
double min = arg->getDoubleValue("min", DBL_MIN);
|
|
|
|
double max = arg->getDoubleValue("max", DBL_MAX);
|
|
|
|
prop->setDoubleValue(sg_random() * (max - min) + min);
|
2003-04-05 00:30:38 +00:00
|
|
|
return true;
|
2003-04-04 22:38:35 +00:00
|
|
|
}
|
|
|
|
|
2012-02-02 21:17:34 +00:00
|
|
|
/**
|
|
|
|
* Built-in command: interpolate a property value over time
|
|
|
|
*
|
2012-09-06 12:05:57 +00:00
|
|
|
* property: the name of the property value to interpolate.
|
2013-03-16 15:43:55 +00:00
|
|
|
* type: the interpolation type ("numeric", "color", etc.)
|
|
|
|
* easing: name of easing function (see http://easings.net/)
|
2012-09-06 12:05:57 +00:00
|
|
|
* value[0..n] any number of constant values to interpolate
|
|
|
|
* time/rate[0..n] time between each value, number of time elements must
|
|
|
|
* match those of value elements. Instead of time also rate can
|
|
|
|
* be used which automatically calculates the time to change
|
|
|
|
* the property value at the given speed.
|
2012-02-02 21:17:34 +00:00
|
|
|
* -or-
|
2013-03-16 15:43:55 +00:00
|
|
|
* property[1..n+1] any number of target values taken from named properties
|
2012-09-06 12:05:57 +00:00
|
|
|
* time/rate[0..n] time between each value, number of time elements must
|
|
|
|
* match those of value elements. Instead of time also rate can
|
|
|
|
* be used which automatically calculates the time to change
|
|
|
|
* the property value at the given speed.
|
2012-02-02 21:17:34 +00:00
|
|
|
*/
|
|
|
|
static bool
|
2017-07-04 23:23:24 +00:00
|
|
|
do_property_interpolate (const SGPropertyNode * arg, SGPropertyNode * root)
|
2012-02-02 21:17:34 +00:00
|
|
|
{
|
2017-07-04 23:23:24 +00:00
|
|
|
SGPropertyNode * prop = get_prop(arg,root);
|
2013-03-17 22:48:42 +00:00
|
|
|
if( !prop )
|
2013-03-16 15:43:55 +00:00
|
|
|
return false;
|
2012-09-06 12:05:57 +00:00
|
|
|
|
2013-03-16 15:43:55 +00:00
|
|
|
simgear::PropertyList time_nodes = arg->getChildren("time");
|
|
|
|
simgear::PropertyList rate_nodes = arg->getChildren("rate");
|
2012-09-06 12:05:57 +00:00
|
|
|
|
2013-03-16 15:43:55 +00:00
|
|
|
if( !time_nodes.empty() && !rate_nodes.empty() )
|
|
|
|
// mustn't specify time and rate
|
|
|
|
return false;
|
2012-02-02 21:17:34 +00:00
|
|
|
|
2013-03-16 15:43:55 +00:00
|
|
|
simgear::PropertyList::size_type num_times = time_nodes.empty()
|
|
|
|
? rate_nodes.size()
|
|
|
|
: time_nodes.size();
|
2012-02-02 21:17:34 +00:00
|
|
|
|
2013-03-16 15:43:55 +00:00
|
|
|
simgear::PropertyList value_nodes = arg->getChildren("value");
|
|
|
|
if( value_nodes.empty() )
|
|
|
|
{
|
|
|
|
simgear::PropertyList prop_nodes = arg->getChildren("property");
|
2012-02-02 21:17:34 +00:00
|
|
|
|
2013-03-16 15:43:55 +00:00
|
|
|
// must have one more property node
|
|
|
|
if( prop_nodes.size() != num_times + 1 )
|
|
|
|
return false;
|
2012-02-02 21:17:34 +00:00
|
|
|
|
2013-03-16 15:43:55 +00:00
|
|
|
value_nodes.reserve(num_times);
|
|
|
|
for( size_t i = 1; i < prop_nodes.size(); ++i )
|
|
|
|
value_nodes.push_back( fgGetNode(prop_nodes[i]->getStringValue()) );
|
|
|
|
}
|
2012-02-02 21:17:34 +00:00
|
|
|
|
2013-03-16 15:43:55 +00:00
|
|
|
// must match
|
|
|
|
if( value_nodes.size() != num_times )
|
|
|
|
return false;
|
2012-02-02 21:17:34 +00:00
|
|
|
|
2013-03-16 15:43:55 +00:00
|
|
|
double_list deltas;
|
|
|
|
deltas.reserve(num_times);
|
|
|
|
|
|
|
|
if( !time_nodes.empty() )
|
|
|
|
{
|
|
|
|
for( size_t i = 0; i < num_times; ++i )
|
|
|
|
deltas.push_back( time_nodes[i]->getDoubleValue() );
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
for( size_t i = 0; i < num_times; ++i )
|
|
|
|
{
|
|
|
|
// TODO calculate delta based on property type
|
|
|
|
double delta = value_nodes[i]->getDoubleValue()
|
|
|
|
- ( i > 0
|
|
|
|
? value_nodes[i - 1]->getDoubleValue()
|
|
|
|
: prop->getDoubleValue()
|
|
|
|
);
|
|
|
|
deltas.push_back( fabs(delta / rate_nodes[i]->getDoubleValue()) );
|
2012-02-02 21:17:34 +00:00
|
|
|
}
|
2013-03-16 15:43:55 +00:00
|
|
|
}
|
2012-02-02 21:17:34 +00:00
|
|
|
|
2013-03-17 22:48:42 +00:00
|
|
|
return prop->interpolate
|
2013-03-16 15:43:55 +00:00
|
|
|
(
|
|
|
|
arg->getStringValue("type", "numeric"),
|
|
|
|
value_nodes,
|
|
|
|
deltas,
|
|
|
|
arg->getStringValue("easing", "linear")
|
|
|
|
);
|
2012-02-02 21:17:34 +00:00
|
|
|
}
|
2003-04-04 22:38:35 +00:00
|
|
|
|
2003-11-24 01:49:04 +00:00
|
|
|
/**
|
2003-11-25 21:08:36 +00:00
|
|
|
* Built-in command: reinit the data logging system based on the
|
|
|
|
* current contents of the /logger tree.
|
2003-11-24 01:49:04 +00:00
|
|
|
*/
|
|
|
|
static bool
|
2017-07-04 23:23:24 +00:00
|
|
|
do_data_logging_commit (const SGPropertyNode * arg, SGPropertyNode * root)
|
2003-11-24 01:49:04 +00:00
|
|
|
{
|
|
|
|
FGLogger *log = (FGLogger *)globals->get_subsystem("logger");
|
|
|
|
log->reinit();
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2003-03-22 10:38:15 +00:00
|
|
|
/**
|
|
|
|
* Built-in command: set log level (0 ... 7)
|
|
|
|
*/
|
|
|
|
static bool
|
2017-07-04 23:23:24 +00:00
|
|
|
do_log_level (const SGPropertyNode * arg, SGPropertyNode * root)
|
2003-03-22 10:38:15 +00:00
|
|
|
{
|
|
|
|
sglog().setLogLevels( SG_ALL, (sgDebugPriority)arg->getIntValue() );
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2004-10-24 09:13:17 +00:00
|
|
|
/**
|
|
|
|
* An fgcommand to allow loading of xml files via nasal,
|
|
|
|
* the xml file's structure will be made available within
|
2007-06-04 10:07:31 +00:00
|
|
|
* a property tree node defined under argument "targetnode",
|
|
|
|
* or in the given argument tree under "data" otherwise.
|
2004-10-24 09:13:17 +00:00
|
|
|
*
|
2007-06-12 16:13:21 +00:00
|
|
|
* @param filename a string to hold the complete path & filename of an XML file
|
2004-10-24 09:13:17 +00:00
|
|
|
* @param targetnode a string pointing to a location within the property tree
|
2007-06-12 16:13:21 +00:00
|
|
|
* where to store the parsed XML file. If <targetnode> is undefined, then the
|
|
|
|
* file contents are stored under a node <data> in the argument tree.
|
2004-10-24 09:13:17 +00:00
|
|
|
*/
|
|
|
|
|
2007-06-04 10:07:31 +00:00
|
|
|
static bool
|
2017-07-04 23:23:24 +00:00
|
|
|
do_load_xml_to_proptree(const SGPropertyNode * arg, SGPropertyNode * root)
|
2004-10-24 09:13:17 +00:00
|
|
|
{
|
2007-06-12 16:13:21 +00:00
|
|
|
SGPath file(arg->getStringValue("filename"));
|
2016-06-23 13:26:34 +00:00
|
|
|
if (file.isNull())
|
2007-06-12 16:13:21 +00:00
|
|
|
return false;
|
|
|
|
|
|
|
|
if (file.extension() != "xml")
|
|
|
|
file.concat(".xml");
|
2017-03-21 20:59:35 +00:00
|
|
|
|
2020-10-08 08:16:00 +00:00
|
|
|
// some Nasal uses loadxml to also speculatively probe for existence of
|
|
|
|
// files. This flag allows us not to be noisy in the logs, in that case.
|
|
|
|
const bool quiet = arg->getBoolValue("quiet", false);
|
|
|
|
|
2011-05-24 23:03:51 +00:00
|
|
|
std::string icao = arg->getStringValue("icao");
|
|
|
|
if (icao.empty()) {
|
|
|
|
if (file.isRelative()) {
|
2016-06-23 13:26:34 +00:00
|
|
|
SGPath absPath = globals->resolve_maybe_aircraft_path(file.utf8Str());
|
2011-07-03 11:06:41 +00:00
|
|
|
if (!absPath.isNull())
|
|
|
|
file = absPath;
|
|
|
|
else
|
|
|
|
{
|
2020-10-08 08:16:00 +00:00
|
|
|
if (!quiet) {
|
|
|
|
SG_LOG(SG_IO, SG_ALERT, "loadxml: Cannot find XML property file '" << file << "'.");
|
|
|
|
}
|
2011-07-03 11:06:41 +00:00
|
|
|
return false;
|
|
|
|
}
|
2011-05-24 23:03:51 +00:00
|
|
|
}
|
|
|
|
} else {
|
2016-06-23 13:26:34 +00:00
|
|
|
if (!XMLLoader::findAirportData(icao, file.utf8Str(), file)) {
|
2020-10-08 08:16:00 +00:00
|
|
|
if (!quiet) {
|
|
|
|
SG_LOG(SG_IO, SG_INFO, "loadxml: failed to find airport data for " << file << " at ICAO:" << icao);
|
|
|
|
}
|
2011-05-24 23:03:51 +00:00
|
|
|
return false;
|
|
|
|
}
|
2010-08-15 10:02:15 +00:00
|
|
|
}
|
2017-03-21 20:59:35 +00:00
|
|
|
|
2020-09-26 10:36:08 +00:00
|
|
|
if (!file.exists()) {
|
2020-10-08 08:16:00 +00:00
|
|
|
if (!quiet) {
|
|
|
|
SG_LOG(SG_IO, SG_WARN, "loadxml: no such file:" << file);
|
|
|
|
}
|
2020-09-26 10:36:08 +00:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2016-07-03 22:59:40 +00:00
|
|
|
SGPath validated_path = fgValidatePath(file, false);
|
|
|
|
if (validated_path.isNull()) {
|
2020-10-08 08:16:00 +00:00
|
|
|
SG_LOG(SG_IO, quiet ? SG_DEV_WARN : SG_ALERT, "loadxml: reading '" << file << "' denied "
|
|
|
|
"(unauthorized directory - authorization no longer follows symlinks; to authorize reading additional directories, pass them to --allow-nasal-read)");
|
2008-07-11 16:36:54 +00:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2007-06-04 10:07:31 +00:00
|
|
|
SGPropertyNode *targetnode;
|
2007-06-12 16:13:21 +00:00
|
|
|
if (arg->hasValue("targetnode"))
|
|
|
|
targetnode = fgGetNode(arg->getStringValue("targetnode"), true);
|
2007-06-04 10:07:31 +00:00
|
|
|
else
|
2007-06-12 16:13:21 +00:00
|
|
|
targetnode = const_cast<SGPropertyNode *>(arg)->getNode("data", true);
|
2004-10-24 09:13:17 +00:00
|
|
|
|
2020-11-10 11:32:55 +00:00
|
|
|
// don't report Sentry errors for Nasal-loaded XML, since it makes
|
|
|
|
// for very noisy reports
|
|
|
|
flightgear::sentryThreadReportXMLErrors(false);
|
2004-10-24 09:13:17 +00:00
|
|
|
try {
|
2016-06-21 11:28:35 +00:00
|
|
|
readProperties(validated_path, targetnode, true);
|
2004-10-24 09:13:17 +00:00
|
|
|
} catch (const sg_exception &e) {
|
2020-10-08 08:16:00 +00:00
|
|
|
SG_LOG(SG_IO, quiet ? SG_DEV_WARN : SG_WARN, "loadxml exception: " << e.getFormattedMessage());
|
2020-11-10 11:32:55 +00:00
|
|
|
flightgear::sentryThreadReportXMLErrors(true);
|
2004-10-24 09:13:17 +00:00
|
|
|
return false;
|
|
|
|
}
|
2020-11-10 11:32:55 +00:00
|
|
|
flightgear::sentryThreadReportXMLErrors(true);
|
2004-10-24 09:13:17 +00:00
|
|
|
|
2008-07-11 16:36:54 +00:00
|
|
|
return true;
|
2004-10-24 09:13:17 +00:00
|
|
|
}
|
|
|
|
|
2012-04-18 09:25:27 +00:00
|
|
|
static bool
|
2017-07-04 23:23:24 +00:00
|
|
|
do_load_xml_from_url(const SGPropertyNode * arg, SGPropertyNode * root)
|
2012-04-18 09:25:27 +00:00
|
|
|
{
|
2012-11-19 11:30:12 +00:00
|
|
|
FGHTTPClient* http = static_cast<FGHTTPClient*>(globals->get_subsystem("http"));
|
|
|
|
if (!http) {
|
|
|
|
SG_LOG(SG_IO, SG_ALERT, "xmlhttprequest: HTTP client not running");
|
|
|
|
return false;
|
|
|
|
}
|
2017-03-21 20:59:35 +00:00
|
|
|
|
2012-04-18 09:25:27 +00:00
|
|
|
std::string url(arg->getStringValue("url"));
|
|
|
|
if (url.empty())
|
|
|
|
return false;
|
2017-03-21 20:59:35 +00:00
|
|
|
|
2012-04-18 09:25:27 +00:00
|
|
|
SGPropertyNode *targetnode;
|
|
|
|
if (arg->hasValue("targetnode"))
|
|
|
|
targetnode = fgGetNode(arg->getStringValue("targetnode"), true);
|
|
|
|
else
|
|
|
|
targetnode = const_cast<SGPropertyNode *>(arg)->getNode("data", true);
|
2017-03-21 20:59:35 +00:00
|
|
|
|
2012-04-18 09:25:27 +00:00
|
|
|
RemoteXMLRequest* req = new RemoteXMLRequest(url, targetnode);
|
2017-03-21 20:59:35 +00:00
|
|
|
|
2012-11-17 18:10:15 +00:00
|
|
|
if (arg->hasChild("body"))
|
2013-10-27 18:03:35 +00:00
|
|
|
req->setBodyData(arg->getChild("body"));
|
2017-03-21 20:59:35 +00:00
|
|
|
|
2012-04-18 09:25:27 +00:00
|
|
|
// connect up optional reporting properties
|
2017-03-21 20:59:35 +00:00
|
|
|
if (arg->hasValue("complete"))
|
2012-04-18 09:25:27 +00:00
|
|
|
req->setCompletionProp(fgGetNode(arg->getStringValue("complete"), true));
|
2017-03-21 20:59:35 +00:00
|
|
|
if (arg->hasValue("failure"))
|
2012-04-18 09:25:27 +00:00
|
|
|
req->setFailedProp(fgGetNode(arg->getStringValue("failure"), true));
|
2017-03-21 20:59:35 +00:00
|
|
|
if (arg->hasValue("status"))
|
2012-04-18 09:25:27 +00:00
|
|
|
req->setStatusProp(fgGetNode(arg->getStringValue("status"), true));
|
2017-03-21 20:59:35 +00:00
|
|
|
|
2012-11-19 11:30:12 +00:00
|
|
|
http->makeRequest(req);
|
2012-04-18 09:25:27 +00:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2004-10-24 09:13:17 +00:00
|
|
|
|
|
|
|
/**
|
2007-06-12 16:13:21 +00:00
|
|
|
* An fgcommand to allow saving of xml files via nasal,
|
2004-10-24 09:13:17 +00:00
|
|
|
* the file's structure will be determined based on what's
|
|
|
|
* encountered in the passed (source) property tree node
|
|
|
|
*
|
|
|
|
* @param filename a string to hold the complete path & filename of the (new)
|
|
|
|
* XML file
|
|
|
|
* @param sourcenode a string pointing to a location within the property tree
|
|
|
|
* where to find the nodes that should be written recursively into an XML file
|
2007-06-06 21:18:25 +00:00
|
|
|
* @param data if no sourcenode is given, then the file contents are taken from
|
|
|
|
* the argument tree's "data" node.
|
2004-10-24 09:13:17 +00:00
|
|
|
*/
|
|
|
|
|
2007-06-12 16:13:21 +00:00
|
|
|
static bool
|
2017-07-04 23:23:24 +00:00
|
|
|
do_save_xml_from_proptree(const SGPropertyNode * arg, SGPropertyNode * root)
|
2004-10-24 09:13:17 +00:00
|
|
|
{
|
2007-06-12 16:13:21 +00:00
|
|
|
SGPath file(arg->getStringValue("filename"));
|
2016-06-23 13:26:34 +00:00
|
|
|
if (file.isNull())
|
2007-06-06 21:18:25 +00:00
|
|
|
return false;
|
|
|
|
|
2007-06-12 16:13:21 +00:00
|
|
|
if (file.extension() != "xml")
|
|
|
|
file.concat(".xml");
|
|
|
|
|
2016-07-03 22:59:40 +00:00
|
|
|
SGPath validated_path = fgValidatePath(file, true);
|
|
|
|
if (validated_path.isNull()) {
|
2016-06-23 13:26:34 +00:00
|
|
|
SG_LOG(SG_IO, SG_ALERT, "savexml: writing to '" << file << "' denied "
|
2015-07-25 09:15:31 +00:00
|
|
|
"(unauthorized directory - authorization no longer follows symlinks)");
|
2008-07-11 16:36:54 +00:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2007-06-12 16:13:21 +00:00
|
|
|
SGPropertyNode *sourcenode;
|
|
|
|
if (arg->hasValue("sourcenode"))
|
|
|
|
sourcenode = fgGetNode(arg->getStringValue("sourcenode"), true);
|
|
|
|
else if (arg->getNode("data", false))
|
|
|
|
sourcenode = const_cast<SGPropertyNode *>(arg)->getNode("data");
|
|
|
|
else
|
2007-06-06 21:18:25 +00:00
|
|
|
return false;
|
2004-10-24 09:13:17 +00:00
|
|
|
|
|
|
|
try {
|
2016-06-21 11:28:35 +00:00
|
|
|
writeProperties (validated_path, sourcenode, true);
|
2004-10-24 09:13:17 +00:00
|
|
|
} catch (const sg_exception &e) {
|
2007-06-19 10:56:50 +00:00
|
|
|
SG_LOG(SG_IO, SG_WARN, "savexml: " << e.getFormattedMessage());
|
2004-10-24 09:13:17 +00:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2012-11-15 10:55:25 +00:00
|
|
|
// Optional profiling commands using gperftools:
|
|
|
|
// http://code.google.com/p/gperftools/
|
|
|
|
|
2013-10-20 10:58:20 +00:00
|
|
|
#if !FG_HAVE_GPERFTOOLS
|
2012-11-15 10:55:25 +00:00
|
|
|
static void
|
|
|
|
no_profiling_support()
|
|
|
|
{
|
|
|
|
SG_LOG
|
|
|
|
(
|
|
|
|
SG_GENERAL,
|
2020-10-17 14:56:14 +00:00
|
|
|
SG_ALERT,
|
2012-11-15 10:55:25 +00:00
|
|
|
"No profiling support! Install gperftools and reconfigure/rebuild fgfs."
|
|
|
|
);
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
|
|
static bool
|
2017-07-04 23:23:24 +00:00
|
|
|
do_profiler_start(const SGPropertyNode *arg, SGPropertyNode *root)
|
2012-11-15 10:55:25 +00:00
|
|
|
{
|
2013-10-20 10:58:20 +00:00
|
|
|
#if FG_HAVE_GPERFTOOLS
|
2012-11-15 10:55:25 +00:00
|
|
|
const char *filename = arg->getStringValue("filename", "fgfs.profile");
|
|
|
|
ProfilerStart(filename);
|
|
|
|
return true;
|
|
|
|
#else
|
|
|
|
no_profiling_support();
|
|
|
|
return false;
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
|
|
|
static bool
|
2017-07-04 23:23:24 +00:00
|
|
|
do_profiler_stop(const SGPropertyNode *arg, SGPropertyNode *root)
|
2012-11-15 10:55:25 +00:00
|
|
|
{
|
2013-10-20 10:58:20 +00:00
|
|
|
#if FG_HAVE_GPERFTOOLS
|
2012-11-15 10:55:25 +00:00
|
|
|
ProfilerStop();
|
|
|
|
return true;
|
|
|
|
#else
|
|
|
|
no_profiling_support();
|
|
|
|
return false;
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
2012-11-17 22:07:00 +00:00
|
|
|
|
2001-07-09 16:17:20 +00:00
|
|
|
////////////////////////////////////////////////////////////////////////
|
|
|
|
// Command setup.
|
|
|
|
////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
|
|
|
2001-06-01 17:52:17 +00:00
|
|
|
/**
|
|
|
|
* Table of built-in commands.
|
|
|
|
*
|
|
|
|
* New commands do not have to be added here; any module in the application
|
|
|
|
* can add a new command using globals->get_commands()->addCommand(...).
|
|
|
|
*/
|
|
|
|
static struct {
|
|
|
|
const char * name;
|
|
|
|
SGCommandMgr::command_t command;
|
|
|
|
} built_ins [] = {
|
2001-06-14 22:18:01 +00:00
|
|
|
{ "null", do_null },
|
2003-11-25 21:08:36 +00:00
|
|
|
{ "nasal", do_nasal },
|
2011-05-21 11:27:09 +00:00
|
|
|
{ "pause", do_pause },
|
2001-06-14 22:18:01 +00:00
|
|
|
{ "load", do_load },
|
|
|
|
{ "save", do_save },
|
2012-11-10 20:25:31 +00:00
|
|
|
{ "save-tape", do_save_tape },
|
|
|
|
{ "load-tape", do_load_tape },
|
2001-06-14 22:18:01 +00:00
|
|
|
{ "view-cycle", do_view_cycle },
|
CompositeViewer: Support for multiple view windows using osgViewer::CompositeViewer.
Overview:
Previously Flightgear always used a single osgViewer::Viewer(), which
inherits from both osgViewer::ViewerBase and osgViewer::View, giving a
single view window.
If CompositeViewer is enabled, we instead use a osgViewer::CompositeViewer
which contains a list of osgViewer::View's. Each of these View's can have
its own eye position, so we can have multiple different views of the same
scene.
Enable at runtime with: --composite-viewer=1
Changes to allow use of osgViewer::CompositeViewer:
Previously FGRenderer had this method:
osgViewer::Viewer* getViewer();
This has been replaced by these two new methods:
osgViewer::ViewerBase* getViewerBase();
osgViewer::View* getView();
If CompositeViewer is not enabled (the default), the actual runtime state
is unchanged, and getViewerBase() and getView() both return a pointer to
the singleton osgViewer::Viewer() object.
If CompositeViewer is enabled, getViewerBase() returns a pointer to a
singleton osgViewer::CompositeViewer object, and getView() returns a
pointer to the first osgViewer::View in the osgViewer::CompositeViewer's
list.
The other significant change to FGRenderer() is the new method:
osg::FrameStamp* getFrameStamp()
If CompositeViewer is not enabled, this simply returns
getView()->getFrameStamp(). If CompositeViewer is enabled it returns
getViewerBase()->getFrameStamp(). It is important that code that previously
called getView()->getFrameStamp() is changed to use the new method, because
when CompositeViewer is enabled individual osgViewer::View's appear to
return an osg::FrameStamp with zero frame number).
All code that uses FGRenderer has been patched up to use the new methods so
that things work as before regardless of whether CompositeViewer is enabled
or not.
We make FGRenderer::update() call SviewUpdate() which updates any extra
views.
Extra view windows:
If CompositeViewer is enabled, one can create top-level extra view windows
by calling SviewCreate(). See src/Viewer/sview.hxx for details.
Each extra view window has its own simgear::compositor::Compositor
instance.
Currently SviewCreate() can create extra view windows that clone the
current view, or view from one point to another (e.g. from one multiplayer
aircraft to the user's aircradt) or keep two aircraft in view, one at a
fixed distance in the foreground.
SviewCreate() can be called from nasal via new nasal commands "view-clone",
"view-last-pair", "view-last-pair-double" and "view-push". Associated
changes to fgdata gives access to these via the View menu. The "view-push"
command tags the current view for later use by "view-last-pair" and
"view-last-pair-double".
Extra view windows created by SviewCreate() use a new view system called
Sview, which allows views to be constructed at runtime instead of being
hard-coded in *-set.xml files. This is work in progress and views aren't
all fully implemented. For example Pilot view gets things slightly wrong
with large roll values, Tower View AGL is not implemented, and we don't
implement damping. See top of src/Viewer/sview.cxx for an overview.
OpenSceneGraph-3.4 issues:
OSG-3.4's event handling seems to be incorrect with CompositeViewer -
events get sent for the wrong window which causes issues with resize and
closing. It doesn't seem to be possible to work around this, so closing
extra view windows can end up closing the main window for example.
OSG-3.6 seems to fix the problems.
We warn if CompositeViewer is enabled and OpenSceneGraph is 3.4.
2020-11-16 18:43:46 +00:00
|
|
|
{ "view-push", do_view_push },
|
|
|
|
{ "view-clone", do_view_clone },
|
|
|
|
{ "view-last-pair", do_view_last_pair },
|
|
|
|
{ "view-last-pair-double", do_view_last_pair_double },
|
Improved Sview system to allow specification of views from Nasal using properties.
SviewCreate() now takes a SGPropertyNode* config which contains all information
needed to specify the view, including window size and position.
By default views are defined using a series of <step>...</step> nodes which map
to SviewStep* classes internally.
Alternatively, existing extra-view functionality is supported using
type='current', 'last_pair' or 'last_pair_double', plus one can use old-style
<view>...<view> nodes with type='legacy'.
See src/Viewer/sview.hxx for details and examples.
The extra-view commands view-clone, view-last-pair and view-last-pair-double
now pass the supplied SGPropertyNode* through the underlying FGViewMgr to
SviewCreate().
Improved handling of +/- heading and pitch values. For example some legacy
views have inverse relationship between heading-offset-deg and the actual
heading offset, so we now only multiply by -1 early on when handling these
legacy views, and SviewStepRotate does not negate.
Fixed Helicopter view direction bug with multiplayer aircraft - need to use
global /sim/current-view/heading-offset-deg, even for multiplayer aircraft.
Fixed incorrect handling of roll in pilot view - when adding in extra changes
of heading, pitch and roll to things like Helicopter view, simply adding to
heading, pitch and roll doesn't work if aircraft roll is not zero. Instead we
need to do the calculation with SGQuatd, so have added optional rotation params
to SviewStepFinal.
Added support for legacy Tower view look from.
Details:
src/Main/fg_commands.cxx
src/Viewer/viewmgr.cxx
src/Viewer/viewmgr.hxx
Added 'view-new' command.
All recently-added view commands now pass the SGPropertyNode* arg to
globals->get_viewmgr() and from there to SviewCreate().
src/Viewer/sview.cxx
src/Viewer/sview.hxx
SviewStepAircraft and SviewStepNearestTower uses new Callsign
class for tracking multiplayer aircraft by callsign instead of
/ai/models/multiplayer[] number, so we cope when multiplayer aircraft
are renumbered by network outages.
Double views are now implemented as a final view step, instead of
in a separate class derived from SviewView. So all views are
implemented using SviewViewEyeTarget, and there is arguably no
need for the separate SviewView base class.
SviewViewEyeTarget now has a single list of steps, instead of separate
lists for eye and target, because the use of SviewStepCopyToTarget makes
separate lists unnecessary.
2020-12-05 20:39:29 +00:00
|
|
|
{ "view-new", do_view_new },
|
2011-01-19 18:36:04 +00:00
|
|
|
/*
|
2004-01-31 19:47:45 +00:00
|
|
|
{ "set-sea-level-air-temp-degc", do_set_sea_level_degc },
|
2003-11-10 21:59:35 +00:00
|
|
|
{ "set-outside-air-temp-degc", do_set_oat_degc },
|
2004-02-22 14:21:37 +00:00
|
|
|
{ "set-dewpoint-sea-level-air-temp-degc", do_set_dewpoint_sea_level_degc },
|
|
|
|
{ "set-dewpoint-temp-degc", do_set_dewpoint_degc },
|
2011-01-19 18:36:04 +00:00
|
|
|
*/
|
2001-06-14 22:18:01 +00:00
|
|
|
{ "property-toggle", do_property_toggle },
|
|
|
|
{ "property-assign", do_property_assign },
|
|
|
|
{ "property-adjust", do_property_adjust },
|
2001-06-26 21:59:49 +00:00
|
|
|
{ "property-multiply", do_property_multiply },
|
2001-06-14 22:18:01 +00:00
|
|
|
{ "property-swap", do_property_swap },
|
|
|
|
{ "property-scale", do_property_scale },
|
2003-01-18 21:13:11 +00:00
|
|
|
{ "property-cycle", do_property_cycle },
|
2003-04-04 22:38:35 +00:00
|
|
|
{ "property-randomize", do_property_randomize },
|
2012-02-02 21:17:34 +00:00
|
|
|
{ "property-interpolate", do_property_interpolate },
|
2003-11-24 01:49:04 +00:00
|
|
|
{ "data-logging-commit", do_data_logging_commit },
|
2003-03-22 10:38:15 +00:00
|
|
|
{ "log-level", do_log_level },
|
2003-07-17 18:24:17 +00:00
|
|
|
{ "replay", do_replay },
|
2011-01-19 18:36:04 +00:00
|
|
|
/*
|
2003-08-09 13:31:44 +00:00
|
|
|
{ "decrease-visibility", do_decrease_visibility },
|
|
|
|
{ "increase-visibility", do_increase_visibility },
|
2011-01-19 18:36:04 +00:00
|
|
|
*/
|
2004-10-24 09:13:17 +00:00
|
|
|
{ "loadxml", do_load_xml_to_proptree},
|
2007-06-04 10:07:31 +00:00
|
|
|
{ "savexml", do_save_xml_from_proptree },
|
2012-04-18 09:25:27 +00:00
|
|
|
{ "xmlhttprequest", do_load_xml_from_url },
|
2017-03-21 20:59:35 +00:00
|
|
|
|
2012-11-15 10:55:25 +00:00
|
|
|
{ "profiler-start", do_profiler_start },
|
|
|
|
{ "profiler-stop", do_profiler_stop },
|
|
|
|
|
2001-06-14 22:18:01 +00:00
|
|
|
{ 0, 0 } // zero-terminated
|
2001-06-01 17:52:17 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Initialize the default built-in commands.
|
|
|
|
*
|
|
|
|
* Other commands may be added by other parts of the application.
|
|
|
|
*/
|
|
|
|
void
|
|
|
|
fgInitCommands ()
|
|
|
|
{
|
2019-03-24 17:59:23 +00:00
|
|
|
// set our property root as the implicit default root for the
|
|
|
|
// command managr
|
|
|
|
SGCommandMgr::instance()->setImplicitRoot(globals->get_props());
|
|
|
|
|
2010-02-15 23:57:56 +00:00
|
|
|
SG_LOG(SG_GENERAL, SG_BULK, "Initializing basic built-in commands:");
|
2001-06-01 17:52:17 +00:00
|
|
|
for (int i = 0; built_ins[i].name != 0; i++) {
|
2010-02-15 23:57:56 +00:00
|
|
|
SG_LOG(SG_GENERAL, SG_BULK, " " << built_ins[i].name);
|
2001-06-01 17:52:17 +00:00
|
|
|
globals->get_commands()->addCommand(built_ins[i].name,
|
|
|
|
built_ins[i].command);
|
|
|
|
}
|
2002-08-25 20:56:16 +00:00
|
|
|
|
|
|
|
typedef bool (*dummy)();
|
|
|
|
fgTie( "/command/view/next", dummy(0), do_view_next );
|
|
|
|
fgTie( "/command/view/prev", dummy(0), do_view_prev );
|
2012-11-16 11:59:44 +00:00
|
|
|
|
2013-10-20 10:58:20 +00:00
|
|
|
globals->get_props()->setValueReadOnly( "/sim/debug/profiler-available",
|
|
|
|
bool(FG_HAVE_GPERFTOOLS) );
|
2001-06-01 17:52:17 +00:00
|
|
|
}
|
|
|
|
|
2002-05-20 16:13:37 +00:00
|
|
|
// end of fg_commands.cxx
|