diff --git a/src/FDM/flight.cxx b/src/FDM/flight.cxx index 2937725dc..1909c648c 100644 --- a/src/FDM/flight.cxx +++ b/src/FDM/flight.cxx @@ -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, diff --git a/src/MultiPlayer/multiplaymgr.cxx b/src/MultiPlayer/multiplaymgr.cxx index ef66c0f81..efa8cf1c1 100644 --- a/src/MultiPlayer/multiplaymgr.cxx +++ b/src/MultiPlayer/multiplaymgr.cxx @@ -37,11 +37,14 @@ #include #include +#include #include #include #include +#include #include #include +#include #include #include @@ -959,7 +962,10 @@ FGMultiplayMgr::FGMultiplayMgr() pMultiPlayDebugLevel = fgGetNode("/sim/multiplay/debug-level", true); pMultiPlayTransmitPropertyBase = fgGetNode("/sim/multiplay/transmit-filter-property-base", true); pMultiPlayRange = fgGetNode("/sim/multiplay/visibility-range-nm", true); - pMultiPlayRange->setIntValue(100); + if (pMultiPlayRange->getIntValue() == 0) { + /* Only set if currently zero - this allows override e.g. in ~/.fgfsrc. */ + pMultiPlayRange->setIntValue(100); + } } // FGMultiplayMgr::FGMultiplayMgr() ////////////////////////////////////////////////////////////////////// @@ -2316,6 +2322,30 @@ FGMultiplayMgr::FillMsgHdr(T_MsgHdr *MsgHdr, int MsgId, unsigned _len) MsgHdr->Callsign[MAX_CALLSIGN_LEN - 1] = '\0'; } + +/* If / exists and / 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()); + } + } +} + +static std::string makeStringPropertyNameSafe(const std::string& s) +{ + std::string ret; + for (size_t i=0; iaddPropertyId(sIdPropertyList[i].id, sIdPropertyList[i].name); } + + /* Try to find a -set.xml for , 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 (simgear::strutils::ends_with(modelName, ".xml") + && simgear::strutils::starts_with(modelName, "Aircraft/")) { + + std::string tail = modelName.substr(strlen("Aircraft/")); + + PathList dirs(globals->get_aircraft_paths()); + + /* Need to append /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; + PathList::const_iterator it = std::find_if(dirs.begin(), dirs.end(), + [&](SGPath dir) { + model_file = dir; + model_file.append(tail); + return model_file.exists(); + }); + + if (it != dirs.end()) { + /* We've found the model file. + + Now try each -set.xml file in 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. */ + std::string model_file_head = it->str() + '/'; + std::string model_file_tail = model_file.str().substr(model_file_head.size()); + ssize_t p = model_file_tail.find('/'); + std::string aircraft_dir = model_file_head + model_file_tail.substr(0, p); + simgear::Dir dir(aircraft_dir); + std::vector 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 (auto path: dir_contents) { + set = mp->_getProps()->addChild("set"); + bool ok = true; + try { + readProperties(path, 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); + std::initializer_list views_with_default_z_offset_m = {1, 2, 3, 5, 7, 8}; + for (int j: views_with_default_z_offset_m) { + 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/ 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/" + makeStringPropertyNameSafe(callsign); + globals->get_props()->setIntValue(path, mp->_getProps()->getIndex()); + return mp; } diff --git a/src/MultiPlayer/multiplaymgr.hxx b/src/MultiPlayer/multiplaymgr.hxx index bc2556631..13a6a4523 100644 --- a/src/MultiPlayer/multiplaymgr.hxx +++ b/src/MultiPlayer/multiplaymgr.hxx @@ -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); diff --git a/src/Viewer/renderer_legacy.cxx b/src/Viewer/renderer_legacy.cxx index 448960b25..508258ca9 100644 --- a/src/Viewer/renderer_legacy.cxx +++ b/src/Viewer/renderer_legacy.cxx @@ -347,6 +347,7 @@ bool FGScenerySwitchCallback::scenery_enabled = false; FGRenderer::FGRenderer() : _sky(NULL), + MaximumTextureSize(0), _ambientFactor( new osg::Uniform( "fg_SunAmbientColor", osg::Vec4f() ) ), _sunDiffuse( new osg::Uniform( "fg_SunDiffuseColor", osg::Vec4f() ) ), _sunSpecular( new osg::Uniform( "fg_SunSpecularColor", osg::Vec4f() ) ), @@ -356,8 +357,7 @@ FGRenderer::FGRenderer() : _fogDensity( new osg::Uniform( "fg_FogDensity", 0.0001f ) ), _shadowNumber( new osg::Uniform( "fg_ShadowNumber", (int)4 ) ), _shadowDistances( new osg::Uniform( "fg_ShadowDistances", osg::Vec4f(5.0, 50.0, 500.0, 5000.0 ) ) ), - _depthInColor( new osg::Uniform( "fg_DepthInColor", false ) ), - MaximumTextureSize(0) + _depthInColor( new osg::Uniform( "fg_DepthInColor", false ) ) { // it's not the real root, whatever that means _root = new osg::Group; diff --git a/src/Viewer/view.cxx b/src/Viewer/view.cxx index e2dbc4bb2..4ac34c5b7 100644 --- a/src/Viewer/view.cxx +++ b/src/Viewer/view.cxx @@ -39,10 +39,13 @@ #include
#include
+#include +#include +#include #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,154 @@ View::recalc () set_clean(); } + +/* 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"); +} + + +/* 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/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 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 is /ai/models/multiplayer[]. */ + std::string callsign = globals->get_props()->getStringValue(root + "/callsign"); + FGAIMultiplayer* multiplayer = globals->get_subsystem()->getMultiplayer(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() = sim_view->getDoubleValue("config/" + infix + "x-offset-m", 0); + offset_m.y() = sim_view->getDoubleValue("config/" + infix + "y-offset-m", 0); + offset_m.z() = sim_view->getDoubleValue("config/" + infix + "z-offset-m", 0); + } + else + { + offset_m = SGVec3d::zeros(); + } + 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 = ""; + /* 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 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). + + 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 +814,204 @@ 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; } + +/* 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. */ +void View::handleAGL(const std::string& root) +{ + /* 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.updateTarget(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 + ); + } +} + + +/* 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 = ""; + /* 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) { + handleAGL(root); + } + 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 +1020,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 +1029,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 +1094,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::updateTarget(double& io) +{ + setTarget(io); + io = get(); +} + double View::get_h_fov() { @@ -821,37 +1200,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); diff --git a/src/Viewer/view.hxx b/src/Viewer/view.hxx index b3a5be3a1..6a5e84b5e 100644 --- a/src/Viewer/view.hxx +++ b/src/Viewer/view.hxx @@ -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); @@ -259,6 +267,7 @@ private: void setTargetHeading_deg (double heading_deg); void setTargetOrientation (double roll_deg, double pitch_deg, double heading_deg); + void handleAGL(const std::string& root); // Orientation offsets rotations from reference orientation. // Goal settings are for smooth transition from prior @@ -297,10 +306,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 +342,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 updateTarget(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 +393,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 +407,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; diff --git a/src/Viewer/viewmgr.cxx b/src/Viewer/viewmgr.cxx index 01ad18ad4..7080c276a 100644 --- a/src/Viewer/viewmgr.cxx +++ b/src/Viewer/viewmgr.cxx @@ -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); }