1
0
Fork 0

Allow most default views to work on multiplayer aircraft as well as the user's aircraft.

Currently this works for all the default views except for Tower Look From (for
which it doesn't really make sense) and Fly-by View.

We now search for and load a -set.xml that matches the model .xml,
when new multiplayer aircraft is set up. This allows us to find view
offsets etc, e.g. allowing cockpit and helicopter views to work with
multiplayer aircraft. Properties from the -set.xml are placed into
/ai/models/multiplayer[]/set, so for example viewing offsets are in
/ai/models/multiplayer[]/set/sim/view[]/config/target-{x,y,z}-offset-m. We also
copy the aircraft's chase-distance into the view config params, similar to how
fgdata:defaults.xml does for the user's aircraft. And we also fill in views'
missing offsets from the Helicopter View config; e.g. this enables the new
Tower View AGL to show aircraft correctly centred, despite aircraft currently
not having this view defined in their -set.xml. [We don't currently attempt to
cache or reuse -set.xml data.]

Have ensured that view position responds to mouse movement in the same way for
viewing the user's aircraft as for multiplayer aircraft (previously, Model View
reversed the affect of vertical mouse movements).

Added new Tower AGL view. Behaves similarly to Tower view, but automatically
scales and pans vertically in order to always show the vertical range
extending from just above the aircraft down to the ground immediately below
the aircraft. We use aircrafts chase-distance as an indication of size. We
damp the ground level value to reduce the viewing jumping around too much
e.g. if the aircraft flies over buildings. The amount of damping is set by
fgdata:defaults.xml's lookat-agl-damping value.

Fixed problem where Tower View eye position moves slightly as target
aircraft heading changes. This was caused by us unnecessarily applying the
aircraft-centre correction to the eye position.

src/FDM/flight.cxx: also make /orientation/true-heading-deg. This allows
local orientation to be used like multiplayer orientation, which only
has /ai/models/multiplayer[]/orientation/true-heading-deg. [A better fix
might be to replace all occurrencies of /orientation/true-heading-deg with
/orientation/heading-deg, but this would be a rather large commit.]

Details:

src/Viewer/view.*: removed View::updateData() as is no longer required.

src/MultiPlayer/multiplaymgr.cxx: use helicopter view target offsets as
defaults. E.g. in tower view agl, aircraft won't currently be defining these
offsets. More generally, this allows aircraft to define target offsets only in
helicoter view.

src/FDM/flight.cxx
    FGMultiplayMgr::FillMsgHdr(): Added tie of /orientation/true-heading-deg to
    get_Psi_deg, so it duplictes the existing /orientation/heading-deg.

src/MultiPlayer/multiplaymgr.cxx
src/MultiPlayer/multiplaymgr.hxx
    FGMultiplayMgr::addMultiplayer(): look for and load -set.xml that matches
    the model. Patch various view-related things up in similar way to what we
    do for the user's aircraft, so that multiplayer views work.

    Made FGMultiplayMgr::getMultiplayer() public, so it can be used by
    src/Viewer/view.cxx.

src/Viewer/view.cxx
src/Viewer/view.hxx
    View::View(): Added lookat_agl, lookat_agl_damping params for new Tower
    View AGL. Preserve user's field-of-view in separate variable so that Tower
    View AGL can modify the actual field of view independently.

    Added view_index param so that we can find multiplayer view[]/config/
    properties.

    getViewOffsets(): new fn that finds view offsets for user aircraft or
    multiplayer aircraft.

    View::recalcLookFrom() View::recalcLookAt: Lots of changes to allow things
    to work with multiplayer aircraft. View::recalcLookAt() can now do Tower
    View AGL.

    View::Damping: support for damping, used by Tower View AGL. Might be good
    to use this for other damping.

    View::updateData(): removed, as calculations are now all done inside
    View::recalc*().

    put properties {x,y,z}-offset-m into new View::_adjust_offset_m member
    instead of _offset_m. This avoids confusion between a view's offsets and
    the offsets added in by the user via the 'Adjust View Position' dialogue.

src/Viewer/viewmgr.cxx
    FGViewMgr::init(): pass view number to
    flightgear::View::createFromProperties().
This commit is contained in:
Julian Smith 2019-05-31 23:07:46 +01:00
parent 34a6cb3c74
commit 5772325bc6
6 changed files with 667 additions and 85 deletions

View file

@ -293,6 +293,10 @@ FGInterface::bind ()
&FGInterface::get_Psi_deg,
&FGInterface::set_Psi_deg, false);
fgSetArchivable("/orientation/heading-deg");
_tiedProperties.Tie("/orientation/true-heading-deg", this,
&FGInterface::get_Psi_deg,
&FGInterface::set_Psi_deg, false);
fgSetArchivable("/orientation/true-heading-deg");
_tiedProperties.Tie("/orientation/track-deg", this,
&FGInterface::get_Track); // read-only
_tiedProperties.Tie("/orientation/path-deg", this,

View file

@ -37,9 +37,11 @@
#include <errno.h>
#include <simgear/misc/stdint.hxx>
#include <simgear/misc/sg_dir.hxx>
#include <simgear/timing/timestamp.hxx>
#include <simgear/debug/logstream.hxx>
#include <simgear/props/props.hxx>
#include <simgear/props/props_io.hxx>
#include <simgear/structure/commands.hxx>
#include <simgear/structure/event_mgr.hxx>
@ -2316,6 +2318,44 @@ FGMultiplayMgr::FillMsgHdr(T_MsgHdr *MsgHdr, int MsgId, unsigned _len)
MsgHdr->Callsign[MAX_CALLSIGN_LEN - 1] = '\0';
}
static bool starts_with(const std::string& s, const std::string& prefix)
{
return s.substr(0, prefix.size()) == prefix;
}
static bool ends_with(const std::string& s, const std::string& suffix)
{
return s.substr(s.size()-suffix.size()) == suffix;
}
/* Outputs a SGPropertyNode to a stream, for debugging. */
static std::ostream& output(std::ostream& s, const SGPropertyNode& node, std::string prefix="")
{
s << prefix << node.getName() << ": "
<< "index=" << node.getIndex() << ": "
<< "position=" << node.getPosition() << ": "
;
node.printOn(s);
s << "\n";
for (int i=0; i<node.nChildren(); ++i) {
const SGPropertyNode* node_child = node.getChild(i);
output(s, *node_child, prefix + " ");
}
return s;
}
/* If <from>/<path> exists and <to>/<path> doesn't, copy the former to the
latter. */
static void copy_default(SGPropertyNode* from, const char* path, SGPropertyNode* to) {
SGPropertyNode* from_ = from->getNode(path);
if (from_) {
if (!to->getNode(path)) {
to->setDoubleValue(path, from_->getDoubleValue());
}
}
}
FGAIMultiplayer*
FGMultiplayMgr::addMultiplayer(const std::string& callsign,
const std::string& modelName,
@ -2338,7 +2378,138 @@ FGMultiplayMgr::addMultiplayer(const std::string& callsign,
for (unsigned i = 0; i < numProperties; ++i)
mp->addPropertyId(sIdPropertyList[i].id, sIdPropertyList[i].name);
}
/* Try to find a -set.xml for <modelName>, so that we can use its view
parameters. If found, we install it into a 'set' property node.
If we are reusing an old entry in /ai/models/multiplayer[], there
might be an old set/ node, so remove it.
todo: maybe we should cache the -set.xml nodes in memory and/or share them in
properties?
*/
mp->_getProps()->removeChildren("set");
SGPropertyNode* set = NULL;
if (ends_with(modelName, ".xml") && starts_with(modelName, "Aircraft/")) {
std::string tail = modelName.substr(strlen("Aircraft/"));
PathList dirs(globals->get_aircraft_paths());
/* Need to append <fgdata>/Aircraft, otherwise we won't be able to find
c172p. */
SGPath fgdata_aircraft = globals->get_fg_root();
fgdata_aircraft.append("Aircraft");
dirs.push_back(fgdata_aircraft);
SGPath model_file;
std::string model_file_head;
std::string model_file_tail;
std::string aircraft_dir;
PathList::const_iterator it;
for ( it = dirs.begin(); it != dirs.end(); ++it) {
model_file = *it;
model_file.append(tail);
if (model_file.exists()) {
model_file_head = it->str() + '/';
model_file_tail = model_file.str().substr(model_file_head.size());
ssize_t p = model_file_tail.find('/');
aircraft_dir = model_file_head + model_file_tail.substr(0, p);
break;
}
}
if (it == dirs.end()) {
/* We failed to find model file. */
}
else {
/* Try each -set.xml file in <modelName> aircraft directory. In theory
an aircraft could have a -set.xml in an unrelated directory so we should
scan all directories in globals->get_aircraft_paths(), but in practice
most -set.xml files and models are in the same aircraft directory. */
simgear::Dir dir(aircraft_dir);
std::vector<SGPath> dir_contents = dir.children(0 /*types*/, "-set.xml");
/* simgear::Dir::children() claims that second param is glob, but
actually it's just a suffix. */
for (size_t i=0; i<dir_contents.size(); ++i) {
set = mp->_getProps()->addChild("set");
bool ok = true;
try {
readProperties(dir_contents[i], set);
}
catch ( const std::exception &e ) {
ok = false;
}
if (ok) {
SGPropertyNode* sim_model_path = set->getNode("sim/model/path");
if (sim_model_path && sim_model_path->getStringValue() == modelName) {
/* We've found (and loaded) a matching -set.xml. */
break;
}
}
mp->_getProps()->removeChildren("set");
set = NULL;
}
}
}
/* Copy [set]/sim/chase-distance-m (or -25 if not present) into
mp->set/sim/view[]/config/z-offset-m if not there. This attempts to mimic
what fgdata/defaults.xml does when it defines default views. Also copy
view[1]'s config into other views if not specified. */
double sim_chase_distance_m = -25;
if (set) {
sim_chase_distance_m = set->getDoubleValue("sim/chase-distance-m", sim_chase_distance_m);
}
else {
set = mp->_getProps()->addChild("set");
set->setDoubleValue("sim/chase-distance-m", sim_chase_distance_m);
}
SGPropertyNode* set_sim = set->getNode("sim");
/* For views that are similar to Helicopter View, copy across Helicopter View
target offsets if not specified. E.g. this allows Tower View AGL to work on
aircraft that don't know about it but need non-zero target-*-offset-m values
to centre the view on the middle of the aircraft.
This mimics what fgdata:Nasal/view.nas:manager does for the user aircraft's
views.
*/
SGPropertyNode* view_1 = set_sim->getNode("view", 1);
int views_with_default_z_offset_m[] = {1, 2, 3, 5, 7, 8};
for (unsigned i=0;
i != sizeof(views_with_default_z_offset_m)/sizeof(views_with_default_z_offset_m[0]);
++i) {
int j = views_with_default_z_offset_m[i];
SGPropertyNode* v = set_sim->getChild("view", j);
if (!v) {
v = set_sim->addChild("view", j, false /*append*/);
}
SGPropertyNode* z_offset_m = v->getChild("config/z-offset-m");
if (!z_offset_m) {
v->setDoubleValue("config/z-offset-m", sim_chase_distance_m);
}
copy_default(view_1, "config/target-x-offset-m", v);
copy_default(view_1, "config/target-y-offset-m", v);
copy_default(view_1, "config/target-z-offset-m", v);
}
/* Create a node /ai/models/callsigns/<callsign> containing the index of the
callsign's aircraft's entry in /ai/models/multiplayer[]. This isn't strictly
necessary, but simpifies debugging a lot and seems pretty lightweight. Note
that we need to avoid special characters in the node name, otherwise the
property system forces a fatal error. */
std::string path = "/ai/models/callsigns/";
for (size_t i=0; i<callsign.size(); ++i) {
char c = callsign[i];
if (i==0 && !isalpha(c) && c!='_') c = '_';
if (!isalnum(c) && c!='.' && c!='_' && c!='-') c = '_';
path += c;
}
globals->get_props()->setIntValue( path, mp->_getProps()->getIndex());
return mp;
}

View file

@ -68,6 +68,7 @@ public:
void SendTextMessage(const std::string &sMsgText);
// receiver
FGAIMultiplayer* getMultiplayer(const std::string& callsign);
private:
friend class MPPropertyListener;
@ -94,7 +95,6 @@ private:
FGAIMultiplayer* addMultiplayer(const std::string& callsign,
const std::string& modelName,
const int fallback_model_index);
FGAIMultiplayer* getMultiplayer(const std::string& callsign);
void FillMsgHdr(T_MsgHdr *MsgHdr, int iMsgId, unsigned _len = 0u);
void ProcessPosMsg(const MsgBuf& Msg, const simgear::IPAddress& SenderAddress,
long stamp);

View file

@ -39,10 +39,13 @@
#include <Main/fg_props.hxx>
#include <Main/globals.hxx>
#include <Scenery/scenery.hxx>
#include <MultiPlayer/multiplaymgr.hxx>
#include <AIModel/AIMultiplayer.hxx>
#include "CameraGroup.hxx"
using namespace flightgear;
////////////////////////////////////////////////////////////////////////
// Implementation of FGViewer.
////////////////////////////////////////////////////////////////////////
@ -56,7 +59,9 @@ View::View( ViewType Type, bool from_model, int from_model_index,
double roll_offset_deg,
double fov_deg, double aspect_ratio_multiplier,
double target_x_offset_m, double target_y_offset_m,
double target_z_offset_m, double near_m, bool internal ):
double target_z_offset_m, double near_m, bool internal,
bool lookat_agl, double lookat_agl_damping,
int view_index ):
_dirty(true),
_roll_deg(0),
_pitch_deg(0),
@ -64,6 +69,7 @@ View::View( ViewType Type, bool from_model, int from_model_index,
_target_roll_deg(0),
_target_pitch_deg(0),
_target_heading_deg(0),
_lookat_agl_damping(lookat_agl_damping /*damping*/, 0 /*min*/, 0 /*max*/),
_scaling_type(FG_SCALING_MAX)
{
_absolute_view_pos = SGVec3d(0, 0, 0);
@ -73,8 +79,10 @@ View::View( ViewType Type, bool from_model, int from_model_index,
_at_model = at_model;
_at_model_index = at_model_index;
_internal = internal;
_internal = internal; _internal = true;
_lookat_agl = lookat_agl;
_view_index = view_index;
_dampFactor = SGVec3d::zeros();
_dampOutput = SGVec3d::zeros();
_dampTarget = SGVec3d::zeros();
@ -109,6 +117,8 @@ View::View( ViewType Type, bool from_model, int from_model_index,
}
_configFOV_deg = _fov_deg;
_fov_user_deg = _fov_deg;
_aspect_ratio_multiplier = aspect_ratio_multiplier;
_target_offset_m.x() = target_x_offset_m;
@ -120,7 +130,7 @@ View::View( ViewType Type, bool from_model, int from_model_index,
// a reasonable guess for init, so that the math doesn't blow up
}
View* View::createFromProperties(SGPropertyNode_ptr config)
View* View::createFromProperties(SGPropertyNode_ptr config, int view_index)
{
double aspect_ratio_multiplier
= fgGetDouble("/sim/current-view/aspect-ratio-multiplier");
@ -128,7 +138,9 @@ View* View::createFromProperties(SGPropertyNode_ptr config)
// find out if this is an internal view (e.g. in cockpit, low near plane)
// FIXME : should be a child of config
bool internal = config->getParent()->getBoolValue("internal", false);
std::string root = config->getPath();
// Will typically be /sim/view[]/config.
// FIXME:
// this is assumed to be an aircraft model...we will need to read
@ -166,6 +178,8 @@ View* View::createFromProperties(SGPropertyNode_ptr config)
double target_x_offset_m = config->getDoubleValue("target-x-offset-m");
double target_y_offset_m = config->getDoubleValue("target-y-offset-m");
double target_z_offset_m = config->getDoubleValue("target-z-offset-m");
bool lookat_agl = config->getBoolValue("lookat-agl");
double lookat_agl_damping = config->getDoubleValue("lookat-agl-damping");
v = new View ( FG_LOOKAT, from_model, from_model_index,
at_model, at_model_index,
@ -174,7 +188,8 @@ View* View::createFromProperties(SGPropertyNode_ptr config)
heading_offset_deg, pitch_offset_deg,
roll_offset_deg, fov_deg, aspect_ratio_multiplier,
target_x_offset_m, target_y_offset_m,
target_z_offset_m, near_m, internal );
target_z_offset_m, near_m, internal, lookat_agl,
lookat_agl_damping, view_index );
if (!from_model) {
v->_targetProperties.init(config, "target-");
}
@ -184,7 +199,7 @@ View* View::createFromProperties(SGPropertyNode_ptr config)
x_offset_m, y_offset_m, z_offset_m,
heading_offset_deg, pitch_offset_deg,
roll_offset_deg, fov_deg, aspect_ratio_multiplier,
0, 0, 0, near_m, internal );
0, 0, 0, near_m, internal, false, 0.0, view_index );
}
if (!from_model) {
@ -194,6 +209,7 @@ View* View::createFromProperties(SGPropertyNode_ptr config)
v->_name = config->getParent()->getStringValue("name");
v->_typeString = type;
v->_configHeadingOffsetDeg = config->getDoubleValue("default-heading-offset-deg");
v->_config = config;
return v;
}
@ -251,7 +267,7 @@ View::bind ()
_tiedProperties.Tie("field-of-view", this,
&View::get_fov, &View::set_fov,
&View::get_fov_user, &View::set_fov_user,
false);
fgSetArchivable("/sim/current-view/field-of-view");
@ -270,12 +286,12 @@ View::bind ()
_tiedProperties.Tie("viewer-lat-deg", this, &View::getLat_deg);
_tiedProperties.Tie("viewer-elev-ft", this, &View::getElev_ft);
_tiedProperties.Tie("x-offset-m", this, &View::getXOffset_m,
&View::setXOffset_m, false);
_tiedProperties.Tie("y-offset-m", this, &View::getYOffset_m,
&View::setYOffset_m, false);
_tiedProperties.Tie("z-offset-m", this, &View::getZOffset_m,
&View::setZOffset_m, false);
_tiedProperties.Tie("x-offset-m", this, &View::getAdjustXOffset_m,
&View::setAdjustXOffset_m, false);
_tiedProperties.Tie("y-offset-m", this, &View::getAdjustYOffset_m,
&View::setAdjustYOffset_m, false);
_tiedProperties.Tie("z-offset-m", this, &View::getAdjustZOffset_m,
&View::setAdjustZOffset_m, false);
_tiedProperties.Tie("target-x-offset-m", this, &View::getTargetXOffset_m,
&View::setTargetXOffset_m, false);
@ -340,6 +356,7 @@ void View::resetOffsetsAndFOV()
{
_target_offset_m = _configTargetOffset_m;
_offset_m = _configOffset_m;
_adjust_offset_m = SGVec3d();
_pitch_offset_deg = _configPitchOffsetDeg;
_heading_offset_deg = _configHeadingOffsetDeg;
_roll_offset_deg = _configRollOffsetDeg;
@ -477,6 +494,27 @@ View::setTargetZOffset_m (double target_z_offset_m)
_target_offset_m.z() = target_z_offset_m;
}
void
View::setAdjustXOffset_m (double x_offset_m)
{
_dirty = true;
_adjust_offset_m.x() = x_offset_m;
}
void
View::setAdjustYOffset_m (double y_offset_m)
{
_dirty = true;
_adjust_offset_m.y() = y_offset_m;
}
void
View::setAdjustZOffset_m (double z_offset_m)
{
_dirty = true;
_adjust_offset_m.z() = z_offset_m;
}
void
View::setPositionOffsets (double x_offset_m, double y_offset_m, double z_offset_m)
{
@ -605,24 +643,176 @@ View::recalc ()
set_clean();
}
static FGAIMultiplayer* get_multiplayer(const std::string& callsign)
{
return dynamic_cast<FGMultiplayMgr*>(globals->get_subsystem("mp"))->getMultiplayer(callsign);
}
/* Gets position and orientation of user aircraft or multiplayer aircraft.
root:
Location of aircraft position and oriention properties. Use '' for user's
aircraft, or /ai/models/multiplayer[] for a multiplayer aircraft.
position:
head:
pitch:
roll:
Out parameters.
*/
static void getAircraftPositionOrientation(
const std::string& root,
SGGeod& position,
double& head,
double& pitch,
double& roll
)
{
position = SGGeod::fromDegFt(
globals->get_props()->getDoubleValue(root + "/position/longitude-deg"),
globals->get_props()->getDoubleValue(root + "/position/latitude-deg"),
globals->get_props()->getDoubleValue(root + "/position/altitude-ft")
);
head = globals->get_props()->getDoubleValue(root + "/orientation/true-heading-deg");
pitch = globals->get_props()->getDoubleValue(root + "/orientation/pitch-deg");
roll = globals->get_props()->getDoubleValue(root + "/orientation/roll-deg");
}
/* If <node>/<relative_path> exists, set <found>=true and return as a
double. Otherwise leaves <found> unchanged and returns <default_value). */
static double getDoubleValue(
SGPropertyNode* node,
const std::string& relative_path,
double default_value,
bool& found
)
{
node = node->getNode(relative_path);
if (!node) return default_value;
found = true;
return node->getDoubleValue();
}
/* Finds the offset of a view position from an aircraft model's origin.
We look in either /sim/view[]/config/ for user's aircraft, or
/ai/models/multiplayer[]/set/sim/view[]/config/ for multiplayer aircraft.
If offset information is not available because we can't find the -set.xml files
for a multiplayer aircraft), we set all elements of offset_m to zero.
root:
Path that defines which aircraft. Either '' to use the user's aircraft, or
/ai/models/multiplayer[].
view_index:
The view number. We will look in .../sim/view[view_index].
infix:
We look at .../view[view_index]/config/<infix>x-offset-m etc. Views appear
to use 'z-offset-m' to define the location of pilot's eyes in cockpit
view, but 'target-z-offset' when defining viewpoint of other views such as
Helicopter View. So one should typically set <infix> to '' or 'target-'.
adjust:
Added on to the return value. Can be used to add in the affects of the user
adjusting the view position.
offset_m:
Out param.
Returns true if any component (x, y or z) was found, otherwise false.
*/
static bool getViewOffsets(
const std::string& root,
int view_index,
const std::string& infix,
const SGVec3d& adjust,
SGVec3d& offset_m
)
{
SGPropertyNode* sim = NULL;
if (root == "") {
/* View offsets are in /sim/view[]/config/. */
sim = globals->get_props()->getNode("sim");
}
else {
/* View offsets are in /ai/models/multiplayer[]/set/sim/view[]/config/. We assume
that <root> is /ai/models/multiplayer[]. */
std::string callsign = globals->get_props()->getStringValue(root + "/callsign");
FGAIMultiplayer* multiplayer = get_multiplayer(callsign);
if (multiplayer) {
sim = multiplayer->getPropertyRoot()->getNode("set/sim");
}
}
bool found_any = false;
if (sim) {
/* Most (all?) -set.xml files don't define an offset for view 7, Model
View, so we instead use the offsets for view 1, Helicopter View. */
SGPropertyNode* sim_view = sim->getNode("view", view_index == 7 ? 1 : view_index);
offset_m.x() = getDoubleValue(sim_view, "config/" + infix + "x-offset-m", 0, found_any);
offset_m.y() = getDoubleValue(sim_view, "config/" + infix + "y-offset-m", 0, found_any);
offset_m.z() = getDoubleValue(sim_view, "config/" + infix + "z-offset-m", 0, found_any);
}
else
{
offset_m.x() = 0;
offset_m.y() = 0;
offset_m.z() = 0;
}
offset_m += adjust;
return found_any;
}
// recalculate for LookFrom view type...
// E.g. Cockpit View and Tower View Look From.
void
View::recalcLookFrom ()
{
// Update location data ...
if ( _from_model ) {
_position = globals->get_aircraft_position();
globals->get_aircraft_orientation(_heading_deg, _pitch_deg, _roll_deg);
std::string root = std::string(_config->getStringValue("root"));
if (root == "/") root = "";
/* <root> will be either '' which means we are viewing the user's aircraft,
or /ai/models/multiplayer[] which means we are viewing a multiplayer
aircraft. */
double head;
double pitch;
double roll;
if (_from_model ) {
/* Look up aircraft position; this works for user's aircaft or multiplayer
aircraft. */
getAircraftPositionOrientation( root, _position, head, pitch, roll);
}
double head = _heading_deg;
double pitch = _pitch_deg;
double roll = _roll_deg;
if ( !_from_model ) {
// update from our own data...
setDampTarget(roll, pitch, head);
getDampOutput(roll, pitch, head);
else {
/* Tower View Look From. This isn't multiplayer, so <root> will be ''.
<_config> will be /sim/view[4]/config, so <_config>/eye-lon-deg-path
will be /sim/view[4]/config/eye-lon-deg-path which will contain
/sim/tower/longitude-deg (see fgdata:defaults.xml).
<root> will be '' so we'll end up looking at /sim/tower/longitude-deg
[Might be nice to make a 'TowerView Look From Multiplayer' that uses the
tower that is nearest to a particular multiplayer aircraft. We'd end up
looking at /ai/models/multiplayer[]/sim/tower/longitude-deg, so we'd need
to somehow set up /ai/models/multiplayer[]/sim/tower. ]
*/
_position = SGGeod::fromDegFt(
globals->get_props()->getDoubleValue(root + "/" + _config->getStringValue("eye-lon-deg-path")),
globals->get_props()->getDoubleValue(root + "/" + _config->getStringValue("eye-lat-deg-path")),
globals->get_props()->getDoubleValue(root + "/" + _config->getStringValue("eye-alt-ft-path"))
);
head = globals->get_props()->getDoubleValue(root + "/" + _config->getStringValue("eye-heading-deg-path"));
pitch = globals->get_props()->getDoubleValue(root + "/" + _config->getStringValue("eye-pitch-deg-path"));
roll = globals->get_props()->getDoubleValue(root + "/" + _config->getStringValue("eye-roll-deg-path"));
}
/* Find the offset of the view position relative to the aircraft model's
origin. */
SGVec3d offset_m;
getViewOffsets(root, _view_index, "" /*infix*/, _adjust_offset_m, offset_m);
set_fov(_fov_user_deg);
// The rotation rotating from the earth centerd frame to
// the horizontal local frame
@ -646,41 +836,191 @@ View::recalcLookFrom ()
// This is rotates the x-forward, y-right, z-down coordinate system the where
// simulation runs into the OpenGL camera system with x-right, y-up, z-back.
SGQuatd q(-0.5, -0.5, 0.5, 0.5);
_absolute_view_pos = position + (ec2body*q).backTransform(_offset_m);
_absolute_view_pos = position + (ec2body*q).backTransform(offset_m);
mViewOrientation = ec2body*mViewOffsetOr*q;
}
/* Views of an aircraft e.g. Helicopter View. */
void
View::recalcLookAt ()
{
// The geodetic position of our target to look at
std::string root = std::string(_config->getStringValue("root"));
if (root == "/") root = "";
/* <root> will be either '' which means we are viewing the user's aircraft,
or /ai/models/multiplayer[] which means we are viewing a multiplayer
aircraft. */
/* 2019-06-12: i think maybe all LookAt views have _at_model=true? */
if ( _at_model ) {
_target = globals->get_aircraft_position();
globals->get_aircraft_orientation(_target_heading_deg,
_target_pitch_deg,
_target_roll_deg);
} else {
// if not model then calculate our own target position...
setDampTarget(_target_roll_deg, _target_pitch_deg, _target_heading_deg);
getDampOutput(_target_roll_deg, _target_pitch_deg, _target_heading_deg);
getAircraftPositionOrientation(
root,
_target,
_target_heading_deg,
_target_pitch_deg,
_target_roll_deg
);
}
double eye_heading;
double eye_roll;
double eye_pitch;
{
SGPropertyNode* sim = globals->get_props()->getNode("sim");
SGPropertyNode* sim_view = sim->getNode("view", _view_index);
eye_heading = globals->get_props()->getDoubleValue(root + sim_view->getStringValue("config/eye-heading-deg-path"));
eye_roll = globals->get_props()->getDoubleValue(root + sim_view->getStringValue("config/eye-roll-deg-path"));
eye_pitch = globals->get_props()->getDoubleValue(root + sim_view->getStringValue("config/eye-pitch-deg-path"));
setDampTarget(eye_roll, eye_pitch, eye_heading);
getDampOutput(eye_roll, eye_pitch, eye_heading);
}
SGQuatd geodTargetOr = SGQuatd::fromYawPitchRollDeg(_target_heading_deg,
_target_pitch_deg,
_target_roll_deg);
SGQuatd geodTargetHlOr = SGQuatd::fromLonLat(_target);
SGVec3d target_pos_off;
getViewOffsets(root, _view_index, "target-", SGVec3d(), target_pos_off);
target_pos_off = SGVec3d(
-target_pos_off.z(),
target_pos_off.x(),
-target_pos_off.y()
);
target_pos_off = (geodTargetHlOr*geodTargetOr).backTransform(target_pos_off);
if ( _from_model ) {
_position = globals->get_aircraft_position();
globals->get_aircraft_orientation(_heading_deg, _pitch_deg, _roll_deg);
} else {
// update from our own data, just the rotation here...
setDampTarget(_roll_deg, _pitch_deg, _heading_deg);
getDampOutput(_roll_deg, _pitch_deg, _heading_deg);
SGVec3d targetCart = SGVec3d::fromGeod(_target);
SGVec3d targetCart2 = targetCart + target_pos_off;
SGGeodesy::SGCartToGeod(targetCart2, _target);
_position = _target;
{
const char* eye_lon_deg_path = _config->getStringValue("eye-lon-deg-path");
const char* eye_lat_deg_path = _config->getStringValue("eye-lat-deg-path");
const char* eye_alt_ft_path = _config->getStringValue("eye-alt-ft-path");
if (eye_lon_deg_path && eye_lon_deg_path[0]) _position.setLongitudeDeg(globals->get_props()->getDoubleValue(eye_lon_deg_path));
if (eye_lat_deg_path && eye_lon_deg_path[0]) _position.setLatitudeDeg(globals->get_props()->getDoubleValue(eye_lat_deg_path));
if (eye_alt_ft_path && eye_alt_ft_path[0]) _position.setElevationFt(globals->get_props()->getDoubleValue(eye_alt_ft_path));
}
SGQuatd geodEyeOr = SGQuatd::fromYawPitchRollDeg(_heading_deg, _pitch_deg, _roll_deg);
if (_lookat_agl) {
/* Change angle and field of view so that we can see the aircraft and the
ground immediately below it. */
/* Some aircraft appear to have elevation that is slightly below ground
level when on the ground, e.g. SenecaII, which makes get_elevation_m()
fail. So we pass a slightly incremented elevation. */
double ground_altitude = 0;
const simgear::BVHMaterial* material = NULL;
SGGeod target_plus = _target;
target_plus.setElevationM(target_plus.getElevationM() + 1);
bool ok = globals->get_scenery()->get_elevation_m(target_plus, ground_altitude, &material);
if (!ok)
{
/* Just in case get_elevation_m() fails, we may as well use sea level. */
ground_altitude = 0;
SG_LOG(SG_FLIGHT, SG_DEBUG, "get_elevation_m() failed. _target=" << _target << "\n");
}
double h_distance = SGGeodesy::distanceM(_position, _target);
if (h_distance == 0) {
/* Not sure this should ever happen, but we need to handle this here
otherwise we'll get divide-by-zero. Just use whatever field of view the
user has set. */
set_fov(_fov_user_deg);
}
else {
/* Find vertical region we want to be able to see. */
double relative_height_target = _target.getElevationM() - _position.getElevationM();
double relative_height_ground = ground_altitude - _position.getElevationM();
/* We expand the field of view so that it hopefully shows the
whole aircraft and a little more of the ground.
We use chase-distance as a crude measure of the aircraft's size. There
doesn't seem to be any more definitive information.
We damp our measure of ground level, to avoid the view jumping around if
an aircraft flies over buildings.
*/
relative_height_ground -= 2;
double chase_distance_m;
if (root == "") {
chase_distance_m = globals->get_props()->getDoubleValue("sim/chase-distance-m");
}
else {
chase_distance_m = globals->get_props()->getDoubleValue(root + "/set/sim/chase-distance-m");
if (chase_distance_m == 0) chase_distance_m = 50;
}
double aircraft_size_vertical = fabs(chase_distance_m) * 0.3;
double aircraft_size_horizontal = fabs(chase_distance_m) * 0.9;
double relative_height_target_plus = relative_height_target + aircraft_size_vertical;
double relative_height_ground_ = relative_height_ground;
_lookat_agl_damping.setGet(relative_height_ground);
if (relative_height_ground > relative_height_target) {
/* Damping of relative_height_ground can result in it being
temporarily above the aircraft, so we ensure the aircraft is visible.
*/
relative_height_ground = relative_height_ground_;
}
double angle_v_target = atan(relative_height_target_plus / h_distance);
double angle_v_ground = atan(relative_height_ground / h_distance);
/* The target we want to use is determined by the midpoint of the two
angles we've calculated. */
double angle_v_mid = (angle_v_target + angle_v_ground) / 2;
_target.setElevationM(_position.getElevationM() + h_distance * tan(angle_v_mid));
/* Set field of view. We use fabs to avoid things being upside down if
target is below ground level (e.g. new multiplayer aircraft are briefly
at -9999ft). */
double fov_v = fabs(angle_v_target - angle_v_ground);
double fov_v_deg = fov_v / 3.1415 * 180;
set_fov( fov_v_deg);
/* Ensure that we can see entire horizontal extent of the aircraft
(assuming airplane is horizontal), and also correct things if display
aspect ratio results in set_fov() not setting the vertical field of view
that we want. */
double fov_h;
fov_h = 2 * atan(aircraft_size_horizontal / 2 / h_distance);
double fov_h_deg = fov_h / 3.1415 * 180;
double correction_v = fov_v_deg / get_v_fov();
double correction_h = fov_h_deg / get_h_fov();
double correction = std::max(correction_v, correction_h);
if (correction > 1) {
fov_v_deg *= correction;
}
/* Apply scaling from user field of view setting. */
fov_v_deg *= _fov_user_deg / _configFOV_deg;
set_fov(fov_v_deg);
SG_LOG(SG_FLIGHT, SG_DEBUG, ""
<< " _position=" << _position
<< " _target=" << _target
<< " ground_altitude=" << ground_altitude
<< " relative_height_target_plus=" << relative_height_target_plus
<< " relative_height_ground=" << relative_height_ground
);
}
}
else {
set_fov(_fov_user_deg);
}
SGQuatd geodEyeOr = SGQuatd::fromYawPitchRollDeg(eye_heading, eye_pitch, eye_roll);
SGQuatd geodEyeHlOr = SGQuatd::fromLonLat(_position);
// the rotation offset, don't know why heading is negative here ...
@ -689,6 +1029,7 @@ View::recalcLookAt ()
_roll_offset_deg);
// Offsets to the eye position
getViewOffsets(root, _view_index, "", _adjust_offset_m, _offset_m);
SGVec3d eyeOff(-_offset_m.z(), _offset_m.x(), -_offset_m.y());
SGQuatd ec2eye = geodEyeHlOr*geodEyeOr;
SGVec3d eyeCart = SGVec3d::fromGeod(_position);
@ -697,12 +1038,6 @@ View::recalcLookAt ()
SGVec3d atCart = SGVec3d::fromGeod(_target);
// add target offsets to at_position...
SGVec3d target_pos_off(-_target_offset_m.z(), _target_offset_m.x(),
-_target_offset_m.y());
target_pos_off = (geodTargetHlOr*geodTargetOr).backTransform(target_pos_off);
atCart += target_pos_off;
eyeCart += target_pos_off;
// Compute the eyepoints orientation and position
// wrt the earth centered frame - that is global coorinates
_absolute_view_pos = eyeCart;
@ -768,6 +1103,59 @@ View::updateDampOutput(double dt)
} // of dt subdivision by interval
}
View::Damping::Damping(double damping, double min, double max)
: _id(NULL), _min(min), _max(max), _target(0), _factor(pow(10, -damping)), _current(0)
{
}
void View::Damping::setTarget(double target)
{
_target = target;
}
void View::Damping::update(double dt, void* id)
{
if (id != _id || dt > 1.0) {
_current = _target;
_id = id;
return;
}
const double interval = 0.01;
while (dt > interval) {
if (_factor <= 0.0) {
// un-damped, set output to target directly
_current = _target;
continue;
}
double d = _current - _target;
if (_max > _min) {
if (d > _max) {
_current -= (_max - _min);
} else if (d < _min) {
_current += (_max - _min);
}
}
_current = (_target * _factor) + _current * (1.0 - _factor);
dt -= interval;
}
}
double View::Damping::get()
{
return _current;
}
void View::Damping::setGet(double& io)
{
setTarget(io);
io = get();
}
double
View::get_h_fov()
{
@ -821,37 +1209,12 @@ View::get_v_fov()
return 0.0;
}
void
View::updateData()
{
if (!_from_model) {
SGGeod pos = _eyeProperties.position();
SGVec3d att = _eyeProperties.attitude();
setPosition(pos);
setOrientation(att[2], att[1], att[0]);
} else {
set_dirty();
}
// if lookat (type 1) then get target data...
if (getType() == FG_LOOKAT) {
if (!_from_model) {
SGGeod pos = _targetProperties.position();
SGVec3d att = _targetProperties.attitude();
setTargetPosition(pos);
setTargetOrientation(att[2], att[1], att[0]);
} else {
set_dirty();
}
}
}
void
View::update (double dt)
{
updateData();
recalc();
recalc();
updateDampOutput(dt);
_lookat_agl_damping.update(dt, NULL /*id*/);
int i;
int dt_ms = int(dt * 1000);

View file

@ -58,7 +58,9 @@ public:
FG_LOOKAT = 1
};
static View* createFromProperties(SGPropertyNode_ptr props);
// view_index is to allow us to look up corresponding view information for
// multiplayer aircraft.
static View* createFromProperties(SGPropertyNode_ptr props, int view_index=-1);
// Destructor
virtual ~View();
@ -129,6 +131,12 @@ public:
void setPositionOffsets (double x_offset_m,
double y_offset_m,
double z_offset_m);
double getAdjustXOffset_m () const { return _adjust_offset_m.x(); }
double getAdjustYOffset_m () const { return _adjust_offset_m.y(); }
double getAdjustZOffset_m () const { return _adjust_offset_m.z(); }
void setAdjustXOffset_m (double x_adjust_offset_m);
void setAdjustYOffset_m (double y_adjust_offset_m);
void setAdjustZOffset_m (double z_adjust_offset_m);
// Reference orientation rotations...
// These are rotations that represent the plane attitude effect on
@ -196,10 +204,10 @@ private:
double roll_offset_deg,
double fov_deg, double aspect_ratio_multiplier,
double target_x_offset_m, double target_y_offset_m,
double target_z_offset_m, double near_m, bool internal );
double target_z_offset_m, double near_m, bool internal,
bool lookat_agl, double lookat_agl_damping, int view_index );
void set_clean() { _dirty = false; }
void updateData();
void setHeadingOffset_deg_property (double heading_offset_deg);
void setPitchOffset_deg_property(double pitch_offset_deg);
@ -297,10 +305,14 @@ private:
_fov_deg = fov_deg;
}
double get_fov_user() const { return _fov_user_deg; }
void set_fov_user( double fov_deg ) { _fov_user_deg = fov_deg; }
//////////////////////////////////////////////////////////////////
// private data //
//////////////////////////////////////////////////////////////////
SGPropertyNode_ptr _config;
std::string _name, _typeString;
// flag forcing a recalc of derived view parameters
@ -329,12 +341,34 @@ private:
SGVec3d _dampTarget; ///< current target value we are damping towards
SGVec3d _dampOutput; ///< current output of damping filter
SGVec3d _dampFactor; ///< weighting of the damping filter
/* Generic damping support. */
struct Damping {
Damping(double factor, double min, double max);
void setTarget(double target);
void update(double dt, void* id);
double get();
void setGet(double& io);
private:
void* _id;
double _min;
double _max;
double _target;
double _factor;
double _current;
};
Damping _lookat_agl_damping;
// Position offsets from FDM origin. The X axis is positive
// out the tail, Y is out the right wing, and Z is positive up.
// distance in meters
SGVec3d _offset_m;
SGVec3d _configOffset_m;
SGVec3d _adjust_offset_m;
// Target offsets from FDM origin (for "lookat" targets) The X
// axis is positive out the tail, Y is out the right wing, and Z
@ -358,6 +392,12 @@ private:
// internal view (e.g. cockpit) flag
bool _internal;
// Dynamically update view angle and field of view so that we always
// include the target and the ground below it.
bool _lookat_agl;
int _view_index;
// view is looking from a model
bool _from_model;
@ -366,6 +406,10 @@ private:
// view is looking at a model
bool _at_model;
int _at_model_index; // number of model (for multi model)
// Field of view as requested by user. Usually copied directly into the
// actual field of view, except for Tower AGL view.
double _fov_user_deg;
// the nominal field of view (angle, in degrees)
double _fov_deg;

View file

@ -62,7 +62,7 @@ FGViewMgr::init ()
SGPropertyNode *n = config_list[i];
SGPropertyNode *config = n->getChild("config", 0, true);
flightgear::View* v = flightgear::View::createFromProperties(config);
flightgear::View* v = flightgear::View::createFromProperties(config, n->getIndex());
if (v) {
add_view(v);
}