first commit

This commit is contained in:
Your Name
2022-10-20 20:29:11 +08:00
commit 4d531f8044
3238 changed files with 1387862 additions and 0 deletions

1749
src/AIModel/AIAircraft.cxx Normal file

File diff suppressed because it is too large Load Diff

262
src/AIModel/AIAircraft.hxx Normal file
View File

@@ -0,0 +1,262 @@
// FGAIAircraft - AIBase derived class creates an AI aircraft
//
// Written by David Culp, started October 2003.
//
// Copyright (C) 2003 David P. Culp - davidculp2@comcast.net
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation; either version 2 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#pragma once
#include <string>
#include <string_view>
#include <iostream>
#include "AIBaseAircraft.hxx"
class PerformanceData;
class FGAISchedule;
class FGAIFlightPlan;
class FGATCController;
class FGATCInstruction;
class FGAIWaypoint;
class sg_ofstream;
namespace AILeg
{
enum Type
{
STARTUP_PUSHBACK = 1,
TAXI = 2,
TAKEOFF = 3,
CLIMB = 4,
CRUISE = 5,
APPROACH = 6,
LANDING = 7,
PARKING_TAXI = 8,
PARKING = 9
};
}
class FGAIAircraft : public FGAIBaseAircraft {
public:
FGAIAircraft(FGAISchedule *ref=0);
virtual ~FGAIAircraft();
string_view getTypeString(void) const override { return "aircraft"; }
void readFromScenario(SGPropertyNode* scFileNode) override;
void bind() override;
void update(double dt) override;
void unbind() override;
void setPerformance(const std::string& acType, const std::string& perfString);
void setFlightPlan(const std::string& fp, bool repat = false);
#if 0
void initializeFlightPlan();
#endif
FGAIFlightPlan* GetFlightPlan() const { return fp.get(); };
void ProcessFlightPlan( double dt, time_t now );
time_t checkForArrivalTime(const std::string& wptName);
time_t calcDeparture();
void AccelTo(double speed);
void PitchTo(double angle);
void RollTo(double angle);
#if 0
void YawTo(double angle);
#endif
void ClimbTo(double altitude);
void TurnTo(double heading);
void getGroundElev(double dt); //TODO these 3 really need to be public?
void doGroundAltitude();
bool loadNextLeg (double dist=0);
void resetPositionFromFlightPlan();
double getBearing(double crse);
void setAcType(const std::string& ac) { acType = ac; };
const std::string& getAcType() const { return acType; }
const std::string& getCompany() const { return company; }
void setCompany(const std::string& comp) { company = comp;};
//ATC
void announcePositionToController(); //TODO have to be public?
void processATC(const FGATCInstruction& instruction);
void setTaxiClearanceRequest(bool arg) { needsTaxiClearance = arg; };
bool getTaxiClearanceRequest() { return needsTaxiClearance; };
FGAISchedule * getTrafficRef() { return trafficRef; };
void setTrafficRef(FGAISchedule *ref) { trafficRef = ref; };
void resetTakeOffStatus() { takeOffStatus = 0;};
void setTakeOffStatus(int status) { takeOffStatus = status; };
int getTakeOffStatus() { return takeOffStatus; };
void setTakeOffSlot(time_t timeSlot) { takeOffTimeSlot = timeSlot;};
time_t getTakeOffSlot(){return takeOffTimeSlot;};
void scheduleForATCTowerDepartureControl(int state);
const std::string& GetTransponderCode() { return transponderCode; };
void SetTransponderCode(const std::string& tc) { transponderCode = tc;};
// included as performance data needs them, who else?
inline PerformanceData* getPerformance() { return _performance; };
inline bool onGround() const { return no_roll; };
inline double getSpeed() const { return speed; };
inline double getRoll() const { return roll; };
inline double getPitch() const { return pitch; };
inline double getAltitude() const { return altitude_ft; };
inline double getVerticalSpeedFPM() const { return vs_fps * 60; };
inline double altitudeAGL() const { return props->getFloatValue("position/altitude-agl-ft");};
inline double airspeed() const { return props->getFloatValue("velocities/airspeed-kt");};
const std::string& atGate();
std::string acwakecategory;
void checkTcas();
double calcVerticalSpeed(double vert_ft, double dist_m, double speed, double error);
FGATCController * getATCController() { return controller; };
void clearATCController();
bool isBlockedBy(FGAIAircraft* other);
void dumpCSVHeader(std::unique_ptr<sg_ofstream> &o);
void dumpCSV(std::unique_ptr<sg_ofstream> &o, int lineIndex);
protected:
void Run(double dt);
private:
FGAISchedule *trafficRef;
FGATCController *controller,
*prevController,
*towerController; // Only needed to make a pre-announcement
bool hdg_lock;
bool alt_lock;
double dt_count;
double dt_elev_count;
double headingChangeRate;
double headingError;
double minBearing;
double speedFraction;
/**Zero if FP is not active*/
double groundTargetSpeed;
double groundOffset;
bool use_perf_vs;
SGPropertyNode_ptr refuel_node;
SGPropertyNode_ptr tcasThreatNode;
SGPropertyNode_ptr tcasRANode;
// helpers for Run
//TODO sort out which ones are better protected virtuals to allow
//subclasses to override specific behaviour
bool fpExecutable(time_t now);
void handleFirstWaypoint(void);
bool leadPointReached(FGAIWaypoint* curr, FGAIWaypoint* next, int nextTurnAngle);
bool handleAirportEndPoints(FGAIWaypoint* prev, time_t now);
bool reachedEndOfCruise(double&);
bool aiTrafficVisible(void);
void controlHeading(FGAIWaypoint* curr,
FGAIWaypoint* next);
void controlSpeed(FGAIWaypoint* curr, FGAIWaypoint* next);
void updatePrimaryTargetValues(double dt, bool& flightplanActive, bool& aiOutOfSight);
void updateSecondaryTargetValues(double dt);
void updateHeading(double dt);
void updateBankAngleTarget();
void updateVerticalSpeedTarget(double dt);
void updatePitchAngleTarget();
void updateActualState(double dt);
void updateModelProperties(double dt);
void handleATCRequests(double dt);
inline bool isStationary() {
return ((fabs(speed) <= 0.0001) && (fabs(tgt_speed) <= 0.0001));
}
inline bool needGroundElevation() {
if (!isStationary())
_needsGroundElevation = true;
return _needsGroundElevation;
}
double sign(double x);
#if 0
std::string getTimeString(int timeOffset);
#endif
void lazyInitControlsNodes();
std::string acType;
std::string company;
std::string transponderCode;
int spinCounter;
/**Kills a flight when it's stuck */
const int AI_STUCK_LIMIT = 100;
int stuckCounter = 0;
bool tracked = false;
/**
* Signals a reset to leg 1 at a different airport.
* The leg loading happens at a different place than the parking loading.
* */
bool repositioned = false;
double prevSpeed;
double prev_dist_to_go;
bool holdPos = false;
const char * _getTransponderCode() const;
bool needsTaxiClearance = false;
bool _needsGroundElevation = true;
int takeOffStatus; // 1 = joined departure queue; 2 = Passed DepartureHold waypoint; handover control to tower; 0 = any other state.
time_t takeOffTimeSlot;
time_t timeElapsed;
PerformanceData* _performance; // the performance data for this aircraft
#if 0
void assertSpeed(double speed);
#endif
struct
{
double remainingLength;
std::string startWptName;
std::string finalWptName;
} trackCache;
// these are init-ed on first use by lazyInitControlsNodes()
SGPropertyNode_ptr _controlsLateralModeNode,
_controlsVerticalModeNode,
_controlsTargetHeadingNode,
_controlsTargetRollNode,
_controlsTargetAltitude,
_controlsTargetPitch,
_controlsTargetSpeed;
std::unique_ptr<sg_ofstream> csvFile;
long csvIndex;
};

1243
src/AIModel/AIBallistic.cxx Normal file

File diff suppressed because it is too large Load Diff

230
src/AIModel/AIBallistic.hxx Normal file
View File

@@ -0,0 +1,230 @@
// FGAIBallistic.hxx - AIBase derived class creates an AI ballistic object
//
// Written by David Culp, started November 2003.
// - davidculp2@comcast.net
//
// With major additions by Vivian Meazza, Feb 2008
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation; either version 2 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#pragma once
#include <cmath>
#include <string_view>
#include <vector>
#include <simgear/structure/SGSharedPtr.hxx>
#include <simgear/scene/material/mat.hxx>
#include "AIManager.hxx"
#include "AIBase.hxx"
class FGAIBallistic : public FGAIBase {
public:
FGAIBallistic(object_type ot = object_type::otBallistic);
virtual ~FGAIBallistic() = default;
string_view getTypeString(void) const override { return "ballistic"; }
void readFromScenario(SGPropertyNode* scFileNode) override;
bool init(ModelSearchOrder searchOrder) override;
void bind() override;
void reinit() override;
void update(double dt) override;
void Run(double dt);
void setAzimuth( double az );
void setElevation( double el );
void setAzimuthRandomError(double error);
void setElevationRandomError(double error);
void setRoll( double rl );
void setStabilisation( bool val );
void setDragArea( double a );
void setLife( double seconds );
void setBuoyancy( double fpss );
void setWind_from_east( double fps );
void setWind_from_north( double fps );
void setWind( bool val );
void setCd(double cd);
void setCdRandomness(double randomness);
void setMass( double m );
void setWeight( double w );
void setNoRoll( bool nr );
void setRandom( bool r );
void setLifeRandomness(double randomness);
void setCollision(bool c);
void setExpiry(bool e);
void setImpact(bool i);
void setImpactReportNode(const std::string&);
void setContentsNode(const SGPropertyNode_ptr);
void setFuseRange(double f);
void setSMPath(const std::string&);
void setSubID(int i);
void setSubmodel(const std::string&);
void setExternalForce( bool f );
void setForcePath(const std::string&);
void setContentsPath(const std::string&);
void setForceStabilisation( bool val );
void setGroundOffset(double g);
void setLoadOffset(double l);
void setSlaved(bool s);
void setSlavedLoad(bool s);
void setPch (double e, double dt, double c);
int setHdg (double az, double dt, double c);
void setBnk(double r, double dt, double c);
void setHt(double h, double dt, double c);
void setSpd(double s, double dt, double c);
void setParentNodes(const SGPropertyNode_ptr);
void setParentPos();
void setOffsetPos(SGGeod pos, double heading, double pitch, double roll);
void setOffsetVelocity(double dt, SGGeod pos);
void setTime(double sec);
double _getTime()const;
double getRelBrgHitchToUser() const;
double getElevHitchToUser() const;
double getLoadOffset() const;
double getContents();
double getDistanceToHitch() const;
double getElevToHitch() const;
double getBearingToHitch() const;
SGVec3d getCartHitchPos() const;
bool getHtAGL(double start);
bool getSlaved() const;
bool getSlavedLoad() const;
FGAIBallistic *ballistic = nullptr;
static const double slugs_to_kgs; //conversion factor
static const double slugs_to_lbs; //conversion factor
SGPropertyNode_ptr _force_node;
SGPropertyNode_ptr _force_azimuth_node;
SGPropertyNode_ptr _force_elevation_node;
// Node for parent model
SGPropertyNode_ptr _pnode;
// Nodes for parent parameters
SGPropertyNode_ptr _p_pos_node;
SGPropertyNode_ptr _p_lat_node;
SGPropertyNode_ptr _p_lon_node;
SGPropertyNode_ptr _p_alt_node;
SGPropertyNode_ptr _p_agl_node;
SGPropertyNode_ptr _p_ori_node;
SGPropertyNode_ptr _p_pch_node;
SGPropertyNode_ptr _p_rll_node;
SGPropertyNode_ptr _p_hdg_node;
SGPropertyNode_ptr _p_vel_node;
SGPropertyNode_ptr _p_spd_node;
double _height;
double _speed;
double _ht_agl_ft; // height above ground level
double _azimuth; // degrees true
double _elevation; // degrees
double _rotation; // degrees
double _speed_north_fps = 0.0;
double _speed_east_fps = 0.0;
double _wind_from_east = 0.0; // fps
double _wind_from_north = 0.0; // fps
double hs;
void setTgtXOffset(double x);
void setTgtYOffset(double y);
void setTgtZOffset(double z);
void setTgtOffsets(double dt, double c);
double getTgtXOffset() const;
double getTgtYOffset() const;
double getTgtZOffset() const;
double _tgt_x_offset = 0.0;
double _tgt_y_offset = 0.0;
double _tgt_z_offset = 0.0;
double _elapsed_time;
SGGeod _parentpos;
SGGeod _oldpos;
SGGeod _offsetpos;
SGGeod _oldoffsetpos;
private:
double _az_random_error; // maximum azimuth error in degrees
double _el_random_error; // maximum elevation error in degrees
bool _aero_stabilised; // if true, object will align with trajectory
double _drag_area; // equivalent drag area in ft2
double _cd; // current drag coefficient
double _init_cd; // initial drag coefficient
double _cd_randomness; // randomness of Cd. 1.0 means +- 100%, 0.0 means no randomness
double _buoyancy; // fps^2
double _life_timer; // seconds
bool _wind; // if true, local wind will be applied to object
double _mass; // slugs
bool _random; // modifier for Cd, life, az
double _life_randomness; // dimension for _random, only applies to life at present
double _load_resistance; // ground load resistanc N/m^2
double _frictionFactor = 0.0; // dimensionless modifier for Coefficient of Friction
bool _solid; // if true ground is solid for FDMs
// double _elevation_m = 0.0; // ground elevation in meters
bool _force_stabilised;// if true, object will align to external force
bool _slave_to_ac; // if true, object will be slaved to the parent ac pos and orientation
bool _slave_load_to_ac;// if true, object will be slaved to the parent ac pos
double _contents_lb; // contents of the object
double _weight_lb = 0.0; // weight of the object (no contents if appropriate) (lbs)
std::string _mat_name;
bool _report_collision; // if true a collision point with AI Objects is calculated
bool _report_impact; // if true an impact point on the terrain is calculated
bool _external_force; // if true then apply external force
bool _report_expiry;
SGPropertyNode_ptr _impact_report_node; // report node for impact and collision
SGPropertyNode_ptr _contents_node; // node for droptank etc. contents
double _fuse_range = 0.0;
std::string _force_path;
std::string _contents_path;
void handleEndOfLife(double);
void handle_collision();
void handle_expiry();
void handle_impact();
void report_impact(double elevation, const FGAIBase *target = 0);
void slaveToAC(double dt);
void setContents(double c);
void calcVSHS();
void calcNE();
SGVec3d getCartOffsetPos(SGGeod pos, double heading, double pitch, double roll) const;
double getRecip(double az);
double getMass() const;
double _ground_offset = 0.0;
double _load_offset = 0.0;
SGVec3d _oldcartoffsetPos;
SGVec3d _oldcartPos;
};

1268
src/AIModel/AIBase.cxx Normal file

File diff suppressed because it is too large Load Diff

527
src/AIModel/AIBase.hxx Normal file
View File

@@ -0,0 +1,527 @@
// FGAIBase.hxx - abstract base class for AI objects
// Written by David Culp, started Nov 2003, based on
// David Luff's FGAIEntity class.
// - davidculp2@comcast.net
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation; either version 2 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#pragma once
#include <string>
#include <string_view>
#include <osg/ref_ptr>
#include <simgear/constants.h>
#include <simgear/math/sg_geodesy.hxx>
#include <simgear/misc/sg_path.hxx>
#include <simgear/props/tiedpropertylist.hxx>
#include <simgear/scene/model/placement.hxx>
#include <simgear/sg_inlines.h>
#include <simgear/structure/SGReferenced.hxx>
#include <simgear/structure/SGSharedPtr.hxx>
namespace osg { class PagedLOD; }
namespace simgear { class BVHMaterial; }
class FGAIManager;
class FGAIFlightPlan;
class FGFX;
class FGAIModelData; // defined below
class FGAIBase : public SGReferenced
{
public:
enum class object_type {
otNull = 0,
otAircraft,
otShip,
otCarrier,
otBallistic,
otRocket,
otStorm,
otThermal,
otStatic,
otWingman,
otGroundVehicle,
otEscort,
otMultiplayer,
MAX_OBJECTS // Needs to be last!!!
};
FGAIBase(object_type ot, bool enableHot);
virtual ~FGAIBase();
// these depend on the order in which the models are loaded. OSG is a little vague about this, but
// from experimentation it seems to work best if the LODs are in the range list in terms of detail
// from lowest to highest
const int modelLowDetailIndex = 0;
const int modelHighDetailIndex = 1;
virtual void readFromScenario(SGPropertyNode* scFileNode);
enum class ModelSearchOrder {
DATA_ONLY, // don't search AI/ prefix at all
PREFER_AI, // search AI first, override other paths
PREFER_DATA // search data first but fall back to AI
};
virtual bool init(ModelSearchOrder searchOrder);
virtual void initModel();
virtual void update(double dt);
virtual void bind();
virtual void unbind();
virtual void reinit() {}
// default model radius for LOD.
virtual double getDefaultModelRadius() { return 20.0; }
void updateLOD();
void updateInterior();
void setManager(FGAIManager* mgr, SGPropertyNode* p);
void setPath( const char* model );
void setPathLowres( std::string model );
void setFallbackModelIndex(const int i );
void setSMPath( const std::string& p );
void setCallSign(const std::string& );
void setSpeed( double speed_KTAS );
void setMaxSpeed(double kts);
void setAltitude( double altitude_ft );
void setAltitudeAGL( double altitude_agl_ft );
void setHeading( double heading );
void setLatitude( double latitude );
void setLongitude( double longitude );
void setBank( double bank );
void setPitch( double newpitch );
void setRadius ( double radius );
void setXoffset( double x_offset );
void setYoffset( double y_offset );
void setZoffset( double z_offset );
void setPitchoffset( double x_offset );
void setRolloffset( double y_offset );
void setYawoffset( double z_offset );
void setServiceable ( bool serviceable );
bool getDie();
void setDie( bool die );
bool isValid() const;
void setCollisionData( bool i, double lat, double lon, double elev );
void setImpactData( bool d );
void setImpactLat( double lat );
void setImpactLon( double lon );
void setImpactElev( double e );
void setName(const std::string& n);
bool setParentNode();
void setParentName(const std::string& p);
void calcRangeBearing(double lat, double lon, double lat2, double lon2,
double &range, double &bearing) const;
double calcRelBearingDeg(double bearing, double heading);
double calcTrueBearingDeg(double bearing, double heading);
double calcRecipBearingDeg(double bearing);
int getID() const;
int _getSubID() const;
void setFlightPlan(std::unique_ptr<FGAIFlightPlan> f);
SGGeod getGeodPos() const;
void setGeodPos(const SGGeod& pos);
SGVec3d getCartPosAt(const SGVec3d& off) const;
SGVec3d getCartPos() const;
bool getGroundElevationM(const SGGeod& pos, double& elev,
const simgear::BVHMaterial** material) const;
SGPropertyNode* getPositionFromNode(SGPropertyNode* scFileNode, const std::string& key, SGVec3d& position);
double getTrueHeadingDeg() const { return hdg; }
double _getCartPosX() const;
double _getCartPosY() const;
double _getCartPosZ() const;
osg::LOD* getSceneBranch() const;
/**
*
* @return true if at least one model (either low_res or high_res) is loaded
*/
bool modelLoaded() const;
void setScenarioPath(const std::string& scenarioPath);
protected:
double _elevation_m = 0.0;
double _maxRangeInterior = 50.0;
double _x_offset;
double _y_offset;
double _z_offset;
double _pitch_offset;
double _roll_offset;
double _yaw_offset;
double _max_speed = 300.0;
std::string _path;
std::string _callsign;
std::string _submodel;
std::string _name;
std::string _parent;
std::string _scenarioPath;
/**
* Tied-properties helper, record nodes which are tied for easy un-tie-ing
*/
template <typename T>
void tie(const char* aRelPath, const SGRawValue<T>& aRawValue)
{
_tiedProperties.Tie(props->getNode(aRelPath, true), aRawValue);
}
simgear::TiedPropertyList _tiedProperties;
SGPropertyNode_ptr _selected_ac;
SGPropertyNode_ptr props;
SGPropertyNode_ptr trigger_node;
SGPropertyNode_ptr replay_time;
SGPropertyNode_ptr model_removed; // where to report model removal
FGAIManager* manager = nullptr;
// these describe the model's actual state
SGGeod pos; // WGS84 lat & lon in degrees, elev above sea-level in meters
double hdg; // True heading in degrees
double roll; // degrees, left is negative
double pitch; // degrees, nose-down is negative
double speed; // knots true airspeed
double speed_fps = 0.0; // fps true airspeed
double altitude_ft; // feet above sea level
double vs_fps; // vertical speed
double speed_north_deg_sec;
double speed_east_deg_sec;
double turn_radius_ft; // turn radius ft at 15 kts rudder angle 15 degrees
double altitude_agl_ft = 0.0;
double ft_per_deg_lon;
double ft_per_deg_lat;
// these describe the model's desired state
double tgt_heading; // target heading, degrees true
double tgt_altitude_ft; // target altitude, *feet* above sea level
double tgt_speed; // target speed, KTAS
double tgt_roll;
double tgt_pitch;
double tgt_yaw;
double tgt_vs;
// these describe radar information for the user
bool in_range; // true if in range of the radar, otherwise false
double bearing; // true bearing from user to this model
double elevation; // elevation in degrees from user to this model
double range; // range from user to this model, nm
double rdot; // range rate, in knots
double horiz_offset; // look left/right from user to me, deg
double vert_offset; // look up/down from user to me, deg
double x_shift; // value used by radar display instrument
double y_shift; // value used by radar display instrument
double rotation; // value used by radar display instrument
double ht_diff; // value used by radar display instrument
std::string model_path; // Path to the 3D model
std::string model_path_lowres; // Path to optional low res 3D model
int _fallback_model_index = 0; // Index into /sim/multiplay/fallback-models[]
SGModelPlacement aip;
bool delete_me;
bool invisible = false;
bool no_roll;
bool serviceable;
bool _installed = false;
int _subID = 0;
double life;
std::unique_ptr<FGAIFlightPlan> fp;
bool _impact_reported;
bool _collision_reported;
bool _expiry_reported;
double _impact_lat;
double _impact_lon;
double _impact_elev;
double _impact_hdg;
double _impact_pitch;
double _impact_roll;
double _impact_speed;
ModelSearchOrder _searchOrder = ModelSearchOrder::DATA_ONLY;
void Transform();
void CalculateMach();
double UpdateRadar(FGAIManager* manager);
void removeModel();
void removeSoundFx();
static int _newAIModelID();
private:
int _refID;
object_type _otype;
bool _initialized = false;
osg::ref_ptr<osg::LOD> _model;
osg::ref_ptr<osg::PagedLOD> _low_res;
osg::ref_ptr<osg::PagedLOD> _high_res;
osg::ref_ptr<osg::Group> _group;
osg::ref_ptr<osg::PagedLOD> _interior;
osg::ref_ptr<FGAIModelData> _modeldata;
SGSharedPtr<FGFX> _fx;
std::vector<std::string> resolveModelPath(ModelSearchOrder searchOrder);
public:
object_type getType();
virtual string_view getTypeString(void) const { return "null"; }
bool isa( object_type otype );
void _setVS_fps( double _vs );
void _setAltitude( double _alt );
void _setLongitude( double longitude );
void _setLatitude ( double latitude );
void _setSubID( int s );
double _getAltitudeAGL(SGGeod inpos, double start);
double _getVS_fps() const;
double _getAltitude() const;
double _getLongitude() const;
double _getLatitude() const;
double _getElevationFt() const;
double _getRdot() const;
double _getH_offset() const;
double _getV_offset() const;
double _getX_shift() const;
double _getY_shift() const;
double _getRotation() const;
double _getSpeed() const;
double _getRoll() const;
double _getPitch() const;
double _getHeading() const;
double _get_speed_east_fps() const;
double _get_speed_north_fps() const;
double _get_SubPath() const;
double _getImpactLat() const;
double _getImpactLon() const;
double _getImpactElevFt() const;
double _getImpactHdg() const;
double _getImpactPitch() const;
double _getImpactRoll() const;
double _getImpactSpeed() const;
double _getXOffset() const;
double _getYOffset() const;
double _getZOffset() const;
bool _getServiceable() const;
bool _getFirstTime() const;
bool _getImpact();
bool _getImpactData();
bool _getCollisionData();
bool _getExpiryData();
SGPropertyNode* _getProps() const;
const char* _getPath() const;
const char* _getSMPath() const;
const char* _getCallsign() const;
const char* _getTriggerNode() const;
const char* _getName() const;
const char* _getSubmodel() const;
int _getFallbackModelIndex() const;
// These are used in the Mach number calculations
double rho;
double T; // temperature, degs farenheit
double p; // pressure lbs/sq ft
double a; // speed of sound at altitude (ft/s)
double Mach; // Mach number
static const double e;
static const double lbs_to_slugs;
double _getRange() const { return range; }
double _getBearing() const { return bearing; }
static bool _isNight();
const std::string& getCallSign() const { return _callsign; }
ModelSearchOrder getSearchOrder() const {return _searchOrder;}
};
typedef SGSharedPtr<FGAIBase> FGAIBasePtr;
inline void FGAIBase::setManager(FGAIManager* mgr, SGPropertyNode* p) {
manager = mgr;
props = p;
}
inline void FGAIBase::setPath(const char* model ) {
model_path.append(model);
}
inline void FGAIBase::setPathLowres(std::string model ) {
model_path_lowres.append(model);
}
inline void FGAIBase::setFallbackModelIndex(const int i ) {
_fallback_model_index = i;
}
inline void FGAIBase::setSMPath(const std::string& p) {
_path = p;
}
inline void FGAIBase::setServiceable(bool s) {
serviceable = s;
}
inline void FGAIBase::setSpeed( double speed_KTAS ) {
speed = tgt_speed = speed_KTAS;
}
inline void FGAIBase::setRadius( double radius ) {
turn_radius_ft = radius;
}
inline void FGAIBase::setHeading( double heading ) {
hdg = tgt_heading = heading;
}
inline void FGAIBase::setAltitude( double alt_ft ) {
altitude_ft = tgt_altitude_ft = alt_ft;
pos.setElevationFt(altitude_ft);
}
inline void FGAIBase::setAltitudeAGL( double alt_ft ) {
altitude_agl_ft = alt_ft;
}
inline void FGAIBase::setBank( double bank ) {
roll = tgt_roll = bank;
no_roll = false;
}
inline void FGAIBase::setPitch( double newpitch ) {
pitch = tgt_pitch = newpitch;
}
inline void FGAIBase::setLongitude( double longitude ) {
pos.setLongitudeDeg( longitude );
}
inline void FGAIBase::setLatitude ( double latitude ) {
pos.setLatitudeDeg( latitude );
}
inline void FGAIBase::setCallSign(const std::string& s) {
_callsign = s;
}
inline void FGAIBase::setXoffset(double x) {
_x_offset = x;
}
inline void FGAIBase::setYoffset(double y) {
_y_offset = y;
}
inline void FGAIBase::setZoffset(double z) {
_z_offset = z;
}
inline void FGAIBase::setPitchoffset(double p) {
_pitch_offset = p;
}
inline void FGAIBase::setRolloffset(double r) {
_roll_offset = r;
}
inline void FGAIBase::setYawoffset(double y) {
_yaw_offset = y;
}
inline void FGAIBase::setParentName(const std::string& p) {
_parent = p;
}
inline void FGAIBase::setName(const std::string& n) {
_name = n;
}
inline void FGAIBase::setDie( bool die ) { delete_me = die; }
inline bool FGAIBase::getDie() { return delete_me; }
inline FGAIBase::object_type FGAIBase::getType() { return _otype; }
inline void FGAIBase::calcRangeBearing(double lat, double lon, double lat2, double lon2,
double &range, double &bearing) const
{
// calculate the bearing and range of the second pos from the first
double az2, distance;
geo_inverse_wgs_84(lat, lon, lat2, lon2, &bearing, &az2, &distance);
range = distance * SG_METER_TO_NM;
}
inline double FGAIBase::calcRelBearingDeg(double bearing, double heading){
double angle = bearing - heading;
SG_NORMALIZE_RANGE(angle, -180.0, 180.0);
return angle;
}
inline double FGAIBase::calcTrueBearingDeg(double bearing, double heading){
double angle = bearing + heading;
SG_NORMALIZE_RANGE(angle, 0.0, 360.0);
return angle;
}
inline double FGAIBase::calcRecipBearingDeg(double bearing){
double angle = bearing - 180;
SG_NORMALIZE_RANGE(angle, 0.0, 360.0);
return angle;
}
inline void FGAIBase::setMaxSpeed(double m) {
_max_speed = m;
}

View File

@@ -0,0 +1,92 @@
// FGAIBaseAircraft - abstract base class for AI aircraft
// Written by Stuart Buchanan, started August 2002
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation; either version 2 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#include "AIBaseAircraft.hxx"
#include <Main/globals.hxx>
FGAIBaseAircraft::FGAIBaseAircraft(object_type ot) :
FGAIBase(ot, false),
m_gearPos(0.0),
m_flapsPos(0.0),
m_spoilerPos(0.0),
m_speedbrakePos(0.0),
m_beaconLight(false),
m_cabinLight(false),
m_landingLight(false),
m_navLight(false),
m_strobeLight(false),
m_taxiLight(false)
{}
void FGAIBaseAircraft::bind() {
FGAIBase::bind();
// All gear positions are linked for simplicity
tie("gear/gear[0]/position-norm", SGRawValuePointer<double>(&m_gearPos));
tie("gear/gear[1]/position-norm", SGRawValuePointer<double>(&m_gearPos));
tie("gear/gear[2]/position-norm", SGRawValuePointer<double>(&m_gearPos));
tie("gear/gear[3]/position-norm", SGRawValuePointer<double>(&m_gearPos));
tie("gear/gear[4]/position-norm", SGRawValuePointer<double>(&m_gearPos));
tie("gear/gear[5]/position-norm", SGRawValuePointer<double>(&m_gearPos));
tie("surface-positions/flap-pos-norm",
SGRawValueMethods<FGAIBaseAircraft,double>(*this,
&FGAIBaseAircraft::FlapsPos,
&FGAIBaseAircraft::FlapsPos));
tie("surface-positions/spoiler-pos-norm",
SGRawValueMethods<FGAIBaseAircraft,double>(*this,
&FGAIBaseAircraft::SpoilerPos,
&FGAIBaseAircraft::SpoilerPos));
tie("surface-positions/speedbrake-pos-norm",
SGRawValueMethods<FGAIBaseAircraft,double>(*this,
&FGAIBaseAircraft::SpeedBrakePos,
&FGAIBaseAircraft::SpeedBrakePos));
tie("controls/lighting/beacon",
SGRawValueMethods<FGAIBaseAircraft,bool>(*this,
&FGAIBaseAircraft::BeaconLight,
&FGAIBaseAircraft::BeaconLight));
tie("controls/lighting/cabin-lights",
SGRawValueMethods<FGAIBaseAircraft,bool>(*this,
&FGAIBaseAircraft::CabinLight,
&FGAIBaseAircraft::CabinLight));
tie("controls/lighting/landing-lights",
SGRawValueMethods<FGAIBaseAircraft,bool>(*this,
&FGAIBaseAircraft::LandingLight,
&FGAIBaseAircraft::LandingLight));
tie("controls/lighting/nav-lights",
SGRawValueMethods<FGAIBaseAircraft,bool>(*this,
&FGAIBaseAircraft::NavLight,
&FGAIBaseAircraft::NavLight));
tie("controls/lighting/strobe",
SGRawValueMethods<FGAIBaseAircraft,bool>(*this,
&FGAIBaseAircraft::StrobeLight,
&FGAIBaseAircraft::StrobeLight));
tie("controls/lighting/taxi-lights",
SGRawValueMethods<FGAIBaseAircraft,bool>(*this,
&FGAIBaseAircraft::TaxiLight,
&FGAIBaseAircraft::TaxiLight));
}

View File

@@ -0,0 +1,70 @@
// FGAIBaseAircraft - abstract base class for AI aircraft
// Written by Stuart Buchanan, started August 2002
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation; either version 2 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#pragma once
#include "AIBase.hxx"
class FGAIBaseAircraft : public FGAIBase
{
public:
FGAIBaseAircraft(object_type otype = object_type::otAircraft);
void bind() override;
// Note that this is mapped to all 6 gear indices gear/gear[0..5]
void GearPos(double pos) { m_gearPos = pos; };
void FlapsPos(double pos) { m_flapsPos = pos; };
void SpoilerPos(double pos) { m_spoilerPos = pos; };
void SpeedBrakePos(double pos) { m_speedbrakePos = pos; };
void BeaconLight(bool light) { m_beaconLight = light; };
void LandingLight(bool light) { m_landingLight = light; };
void NavLight(bool light) { m_navLight = light; };
void StrobeLight(bool light) { m_strobeLight = light; };
void TaxiLight(bool light) { m_taxiLight = light; };
void CabinLight(bool light) { m_cabinLight = light; };
double GearPos() const { return m_gearPos; };
double FlapsPos() const { return m_flapsPos; };
double SpoilerPos() const { return m_spoilerPos; };
double SpeedBrakePos() const { return m_speedbrakePos; };
bool BeaconLight() const { return m_beaconLight; };
bool LandingLight() const { return m_landingLight; };
bool NavLight() const { return m_navLight; };
bool StrobeLight() const { return m_strobeLight; };
bool TaxiLight() const { return m_taxiLight; };
bool CabinLight() const { return m_cabinLight; };
protected:
// Aircraft properties.
double m_gearPos;
double m_flapsPos;
double m_spoilerPos;
double m_speedbrakePos;
// Light properties.
bool m_beaconLight;
bool m_cabinLight;
bool m_landingLight;
bool m_navLight;
bool m_strobeLight;
bool m_taxiLight;
};

875
src/AIModel/AICarrier.cxx Normal file
View File

@@ -0,0 +1,875 @@
// FGAICarrier - FGAIShip-derived class creates an AI aircraft carrier
//
// Written by David Culp, started October 2004.
// - davidculp2@comcast.net
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation; either version 2 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#include <config.h>
#include <algorithm>
#include <string>
#include <vector>
#include <simgear/sg_inlines.h>
#include <simgear/math/sg_geodesy.hxx>
#include <cmath>
#include <Main/util.hxx>
#include <Main/globals.hxx>
#include <Main/fg_props.hxx>
#include <Main/globals.hxx>
#include <Main/util.hxx>
#include "AICarrier.hxx"
#include "AINotifications.hxx"
FGAICarrier::FGAICarrier() : FGAIShip(object_type::otCarrier)
{
simgear::Emesary::GlobalTransmitter::instance()->Register(this);
}
FGAICarrier::~FGAICarrier()
{
simgear::Emesary::GlobalTransmitter::instance()->DeRegister(this);
}
void FGAICarrier::readFromScenario(SGPropertyNode* scFileNode) {
if (!scFileNode)
return;
FGAIShip::readFromScenario(scFileNode);
setRadius(scFileNode->getDoubleValue("turn-radius-ft", 2000));
setSign(scFileNode->getStringValue("pennant-number"));
setDeckAltitudeFt(scFileNode->getDoubleValue("deck-altitude"));
setWind_from_east(scFileNode->getDoubleValue("wind_from_east", 0));
setWind_from_north(scFileNode->getDoubleValue("wind_from_north", 0));
setTACANChannelID(scFileNode->getStringValue("TACAN-channel-ID", "029Y"));
setMaxLat(scFileNode->getDoubleValue("max-lat", 0));
setMinLat(scFileNode->getDoubleValue("min-lat", 0));
setMaxLong(scFileNode->getDoubleValue("max-long", 0));
setMinLong(scFileNode->getDoubleValue("min-long", 0));
setMPControl(scFileNode->getBoolValue("mp-control", false));
setAIControl(scFileNode->getBoolValue("ai-control", false));
setCallSign(scFileNode->getStringValue("callsign", ""));
_angled_deck_degrees = scFileNode->getDoubleValue("angled-deck-degrees", -8.5);
SGPropertyNode* flolsNode = getPositionFromNode(scFileNode, "flols-pos", _flolsPosOffset);
if (flolsNode) {
_flolsHeadingOffsetDeg = flolsNode->getDoubleValue("heading-offset-deg", 0.0);
_flolsApproachAngle = flolsNode->getDoubleValue("glidepath-angle-deg", 3.5);
}
else {
_flolsPosOffset(2) = -(_deck_altitude_ft * SG_FEET_TO_METER + 10);
}
//// the FLOLS (or IFLOLS) position doesn't produce an accurate angle;
//// so to fix this we can definition the touchdown position which
//// is the centreline of the 3rd wire
_flolsTouchdownPosition = _flolsPosOffset; // default to the flolsPosition
getPositionFromNode(scFileNode, "flols-touchdown-position", _flolsTouchdownPosition);
if (!getPositionFromNode(scFileNode, "tower-position", _towerPosition)) {
_towerPosition(2) = -(_deck_altitude_ft * SG_FEET_TO_METER + 10);
SG_LOG(SG_AI, SG_INFO, "AICarrier: tower-position not defined - using default");
}
if (!getPositionFromNode(scFileNode, "lso-position", _lsoPosition)){
_lsoPosition(2) = -(_deck_altitude_ft * SG_FEET_TO_METER + 10);
SG_LOG(SG_AI, SG_INFO, "AICarrier: lso-position not defined - using default");
}
std::vector<SGPropertyNode_ptr> props = scFileNode->getChildren("parking-pos");
std::vector<SGPropertyNode_ptr>::const_iterator it;
for (it = props.begin(); it != props.end(); ++it) {
const string name = (*it)->getStringValue("name", "unnamed");
// Transform to the right coordinate frame, configuration is done in
// the usual x-back, y-right, z-up coordinates, computations
// in the simulation usual body x-forward, y-right, z-down coordinates
double offset_x = -(*it)->getDoubleValue("x-offset-m", 0);
double offset_y = (*it)->getDoubleValue("y-offset-m", 0);
double offset_z = -(*it)->getDoubleValue("z-offset-m", 0);
double hd = (*it)->getDoubleValue("heading-offset-deg", 0);
ParkPosition pp(name, SGVec3d(offset_x, offset_y, offset_z), hd);
_ppositions.push_back(pp);
}
}
void FGAICarrier::setWind_from_east(double fps) {
_wind_from_east = fps;
}
void FGAICarrier::setWind_from_north(double fps) {
_wind_from_north = fps;
}
void FGAICarrier::setMaxLat(double deg) {
_max_lat = fabs(deg);
}
void FGAICarrier::setMinLat(double deg) {
_min_lat = fabs(deg);
}
void FGAICarrier::setMaxLong(double deg) {
_max_lon = fabs(deg);
}
void FGAICarrier::setMinLong(double deg) {
_min_lon = fabs(deg);
}
void FGAICarrier::setDeckAltitudeFt(const double altitude_feet) {
_deck_altitude_ft = altitude_feet;
}
void FGAICarrier::setSign(const string& s) {
_sign = s;
}
void FGAICarrier::setTACANChannelID(const string& id) {
_TACAN_channel_id = id;
}
void FGAICarrier::setMPControl(bool c) {
_MPControl = c;
}
void FGAICarrier::setAIControl(bool c) {
_AIControl = c;
}
void FGAICarrier::update(double dt) {
// Now update the position and heading. This will compute new hdg and
// roll values required for the rotation speed computation.
FGAIShip::update(dt);
if (_is_user_craft->getBoolValue()) {
_latitude_node->setDoubleValue(pos.getLatitudeDeg());
_longitude_node->setDoubleValue(pos.getLongitudeDeg());
_altitude_node->setDoubleValue(pos.getElevationFt());
_heading_node->setDoubleValue(hdg);
_pitch_node->setDoubleValue(pitch);
_roll_node->setDoubleValue(roll);
}
//automatic turn into wind with a target wind of 25 kts otd
//SG_LOG(SG_AI, SG_ALERT, "AICarrier: MPControl " << MPControl << " AIControl " << AIControl);
if (_ai_latch_node->getStringValue() != "") {
SG_LOG(SG_AI, SG_DEBUG, "FGAICarrier::update(): not updating because ai-latch=" << _ai_latch_node->getStringValue());
}
else if (!_MPControl && _AIControl){
if(_turn_to_launch_hdg){
TurnToLaunch();
} else if(_turn_to_recovery_hdg ){
TurnToRecover();
} else if(OutsideBox() || _returning ) {// check that the carrier is inside
ReturnToBox(); // the operating box,
} else {
TurnToBase();
}
} else {
FGAIShip::TurnTo(tgt_heading);
FGAIShip::AccelTo(tgt_speed);
}
UpdateWind(dt);
UpdateElevator(dt);
UpdateJBD(dt);
// Transform that one to the horizontal local coordinate system.
SGQuatd ec2hl = SGQuatd::fromLonLat(pos);
// The orientation of the carrier wrt the horizontal local frame
SGQuatd hl2body = SGQuatd::fromYawPitchRollDeg(hdg, pitch, roll);
// and postrotate the orientation of the AIModel wrt the horizontal
// local frame
SGQuatd ec2body = ec2hl * hl2body;
// The cartesian position of the carrier in the wgs84 world
SGVec3d cartPos = SGVec3d::fromGeod(pos);
// The position of the eyepoint - at least near that ...
SGVec3d eyePos(globals->get_ownship_reference_position_cart());
// Add the position offset of the AIModel to gain the earth
// centered position
SGVec3d eyeWrtCarrier = eyePos - cartPos;
// rotate the eyepoint wrt carrier vector into the carriers frame
eyeWrtCarrier = ec2body.transform(eyeWrtCarrier);
// the eyepoints vector wrt the flols position
SGVec3d eyeWrtFlols = eyeWrtCarrier - _flolsPosOffset;
SGVec3d flols_location = getCartPosAt(_flolsPosOffset);
// the distance from the eyepoint to the flols
_flols_dist = norm(eyeWrtFlols);
// lineup (left/right) - stern lights and Carrier landing system (Aircraft/Generic/an_spn_46.nas)
double lineup_hdg, lineup_az2, lineup_s;
SGGeod g_eyePos = SGGeod::fromCart(eyePos);
SGGeod g_carrier = SGGeod::fromCart(cartPos);
//
// set the view as requested by control/view-index.
SGGeod viewPosition;
switch (_view_index) {
default:
case 0:
viewPosition = SGGeod::fromCart(getCartPosAt(_towerPosition));
break;
case 1:
viewPosition = SGGeod::fromCart(getCartPosAt(_flolsTouchdownPosition));
break;
case 2:
viewPosition = SGGeod::fromCart(getCartPosAt(_lsoPosition));
break;
}
_view_position_lat_deg_node->setDoubleValue(viewPosition.getLatitudeDeg());
_view_position_lon_deg_node->setDoubleValue(viewPosition.getLongitudeDeg());
_view_position_alt_ft_node->setDoubleValue(viewPosition.getElevationFt());
SGGeodesy::inverse(g_carrier, g_eyePos, lineup_hdg, lineup_az2, lineup_s);
double target_lineup = _getHeading() + _angled_deck_degrees + 180.0;
SG_NORMALIZE_RANGE(target_lineup, 0.0, 360.0);
_lineup = lineup_hdg - target_lineup;
// now the angle, positive angles are upwards
if (fabs(_flols_dist) < SGLimits<double>::min()) {
_flols_angle = 0;
} else {
double sAngle = -eyeWrtFlols(2) / _flols_dist;
sAngle = SGMiscd::min(1, SGMiscd::max(-1, sAngle));
_flols_angle = SGMiscd::rad2deg(asin(sAngle));
}
if (_flols_dist < 8000){
SGVec3d eyeWrtFlols_tdp = eyeWrtCarrier - _flolsTouchdownPosition;
// the distance from the eyepoint to the flols
double dist_tdp = norm(eyeWrtFlols_tdp);
//double angle_tdp = 0;
// now the angle, positive angles are upwards
if (fabs(dist_tdp) < SGLimits<double>::min()) {
//angle_tdp = 0;
}
else {
double sAngle = -eyeWrtFlols_tdp(2) / dist_tdp;
sAngle = SGMiscd::min(1, SGMiscd::max(-1, sAngle));
//angle_tdp = SGMiscd::rad2deg(asin(sAngle));
}
// printf("angle %5.2f td angle %5.2f \n", _flols_angle, angle_tdp);
//angle += 1.481; // adjust for FLOLS offset (measured on Nimitz class)
}
// set the value of _flols_visible_light
if ( _flols_angle <= 4.35 && _flols_angle > 4.01 )
_flols_visible_light = 1;
else if ( _flols_angle <= 4.01 && _flols_angle > 3.670 )
_flols_visible_light = 2;
else if ( _flols_angle <= 3.670 && _flols_angle > 3.330 )
_flols_visible_light = 3;
else if ( _flols_angle <= 3.330 && _flols_angle > 2.990 )
_flols_visible_light = 4;
else if ( _flols_angle <= 2.990 && _flols_angle > 2.650 )
_flols_visible_light = 5;
else if ( _flols_angle <= 2.650 )
_flols_visible_light = 6;
else
_flols_visible_light = 0;
// only bother with waveoff FLOLS when ownship within a reasonable range.
// red ball is <= 3.075 to 2.65, below this is off. above this is orange.
// only do this when within ~1.8nm
if (_flols_dist < 3200) {
if (_flols_dist > 100) {
bool new_wave_off_lights_demand = (_flols_angle <= 3.0);
if (new_wave_off_lights_demand != _wave_off_lights_demand) {
// start timing when the lights come up.
_wave_off_lights_demand = new_wave_off_lights_demand;
}
//// below 1degrees close in is to low to continue; wave them off.
if (_flols_angle < 2 && _flols_dist < 800) {
_wave_off_lights_demand = true;
}
}
}
else {
_wave_off_lights_demand = true; // sensible default when very far away.
}
} //end update
bool FGAICarrier::init(ModelSearchOrder searchOrder) {
if (!FGAIShip::init(searchOrder))
return false;
_longitude_node = fgGetNode("/position/longitude-deg", true);
_latitude_node = fgGetNode("/position/latitude-deg", true);
_altitude_node = fgGetNode("/position/altitude-ft", true);
_heading_node = fgGetNode("/orientation/true-heading-deg", true);
_pitch_node = fgGetNode("/orientation/pitch-deg", true);
_roll_node = fgGetNode("/orientation/roll-deg", true);
_launchbar_state_node = fgGetNode("/gear/launchbar/state", true);
_surface_wind_from_deg_node = fgGetNode("/environment/config/boundary/entry[0]/wind-from-heading-deg", true);
_surface_wind_speed_node = fgGetNode("/environment/config/boundary/entry[0]/wind-speed-kt", true);
int dmd_course = fgGetInt("/sim/presets/carrier-course");
if (dmd_course == 2) {
// launch
_turn_to_launch_hdg = true;
_turn_to_recovery_hdg = false;
_turn_to_base_course = false;
} else if (dmd_course == 3) {
// recovery
_turn_to_launch_hdg = false;
_turn_to_recovery_hdg = true;
_turn_to_base_course = false;
} else {
// default to base
_turn_to_launch_hdg = false;
_turn_to_recovery_hdg = false;
_turn_to_base_course = true;
}
_returning = false;
_in_to_wind = false;
_mOpBoxPos = pos;
_base_course = hdg;
_base_speed = speed;
_elevator_pos_norm = 0;
_elevator_pos_norm_raw = 0;
_elevators = false;
_elevator_transition_time = 150;
_elevator_time_constant = 0.005;
_jbd_elevator_pos_norm = 0;
_jbd_elevator_pos_norm_raw = 0;
_jbd = false ;
_jbd_transition_time = 3;
_jbd_time_constant = 0.1;
return true;
}
void FGAICarrier::bind(){
FGAIShip::bind();
_is_user_craft = props->getNode("is-user-craft", true /*create*/);
_ai_latch_node = props->getNode("ai-latch", true /*create*/);
props->untie("velocities/true-airspeed-kt");
props->getNode("position/deck-altitude-feet", true)->setDoubleValue(_deck_altitude_ft);
tie("controls/flols/source-lights",
SGRawValuePointer<int>(&_flols_visible_light));
tie("controls/flols/distance-m",
SGRawValuePointer<double>(&_flols_dist));
tie("controls/flols/angle-degs",
SGRawValuePointer<double>(&_flols_angle));
tie("controls/flols/lineup-degs",
SGRawValuePointer<double>(&_lineup));
tie("controls/turn-to-launch-hdg",
SGRawValuePointer<bool>(&_turn_to_launch_hdg));
tie("controls/in-to-wind",
SGRawValuePointer<bool>(&_turn_to_launch_hdg));
tie("controls/base-course-deg",
SGRawValuePointer<double>(&_base_course));
tie("controls/base-speed-kts",
SGRawValuePointer<double>(&_base_speed));
tie("controls/start-pos-lat-deg",
SGRawValueMethods<SGGeod,double>(pos, &SGGeod::getLatitudeDeg));
tie("controls/start-pos-long-deg",
SGRawValueMethods<SGGeod,double>(pos, &SGGeod::getLongitudeDeg));
tie("controls/mp-control",
SGRawValuePointer<bool>(&_MPControl));
tie("controls/ai-control",
SGRawValuePointer<bool>(&_AIControl));
tie("environment/surface-wind-speed-true-kts",
SGRawValuePointer<double>(&_wind_speed_kts));
tie("environment/surface-wind-from-true-degs",
SGRawValuePointer<double>(&_wind_from_deg));
tie("environment/rel-wind-from-degs",
SGRawValuePointer<double>(&_rel_wind_from_deg));
tie("environment/rel-wind-from-carrier-hdg-degs",
SGRawValuePointer<double>(&_rel_wind));
tie("environment/rel-wind-speed-kts",
SGRawValuePointer<double>(&_rel_wind_speed_kts));
tie("environment/in-to-wind",
SGRawValuePointer<bool>(&_in_to_wind));
tie("controls/flols/wave-off-lights-demand",
SGRawValuePointer<bool>(&_wave_off_lights_demand));
tie("controls/elevators",
SGRawValuePointer<bool>(&_elevators));
tie("surface-positions/elevators-pos-norm",
SGRawValuePointer<double>(&_elevator_pos_norm));
tie("controls/constants/elevators/trans-time-s",
SGRawValuePointer<double>(&_elevator_transition_time));
tie("controls/constants/elevators/time-constant",
SGRawValuePointer<double>(&_elevator_time_constant));
tie("controls/jbd",
SGRawValuePointer<bool>(&_jbd));
tie("surface-positions/jbd-pos-norm",
SGRawValuePointer<double>(&_jbd_elevator_pos_norm));
tie("controls/constants/jbd/trans-time-s",
SGRawValuePointer<double>(&_jbd_transition_time));
tie("controls/constants/jbd/time-constant",
SGRawValuePointer<double>(&_jbd_time_constant));
tie("controls/turn-to-recovery-hdg",
SGRawValuePointer<bool>(&_turn_to_recovery_hdg));
tie("controls/turn-to-base-course",
SGRawValuePointer<bool>(&_turn_to_base_course));
tie("controls/view-index", SGRawValuePointer<int>(&_view_index));
props->setBoolValue("controls/flols/cut-lights", false);
props->setBoolValue("controls/flols/wave-off-lights", false);
props->setBoolValue("controls/flols/wave-off-lights-emergency", false);
props->setBoolValue("controls/flols/cond-datum-lights", true);
props->setBoolValue("controls/crew", false);
props->setStringValue("navaids/tacan/channel-ID", _TACAN_channel_id.c_str());
props->setStringValue("sign", _sign.c_str());
std::string island_texture = "island_" + _sign + ".jpg";
props->setStringValue("island_texture", island_texture.c_str());
props->setBoolValue("controls/lighting/deck-lights", false);
props->setDoubleValue("controls/lighting/flood-lights-red-norm", 0);
_flols_x_node = props->getNode("position/flols-x", true);
_flols_y_node = props->getNode("position/flols-y", true);
_flols_z_node = props->getNode("position/flols-z", true);
_view_position_lat_deg_node = props->getNode("position/view-position-lat", true);
_view_position_lon_deg_node = props->getNode("position/view-position-lon", true);
_view_position_alt_ft_node = props->getNode("position/view-position-alt", true);
// Write out a list of the parking positions - useful for the UI to select
// from
for (const auto& ppos : _ppositions) {
if (ppos.name != "") props->addChild("parking-pos")->setStringValue("name", ppos.name);
}
}
bool FGAICarrier::getParkPosition(const string& id, SGGeod& geodPos,
double& hdng, SGVec3d& uvw)
{
// FIXME: does not yet cover rotation speeds.
for (const auto& ppos : _ppositions) {
// Take either the specified one or the first one ...
if (ppos.name == id || id.empty()) {
SGVec3d cartPos = getCartPosAt(ppos.offset);
geodPos = SGGeod::fromCart(cartPos);
hdng = hdg + ppos.heading_deg;
double shdng = sin(ppos.heading_deg * SGD_DEGREES_TO_RADIANS);
double chdng = cos(ppos.heading_deg * SGD_DEGREES_TO_RADIANS);
double speed_fps = speed*1.6878099;
uvw = SGVec3d(chdng*speed_fps, shdng*speed_fps, 0);
return true;
}
}
return false;
}
bool FGAICarrier::getFLOLSPositionHeading(SGGeod& geodPos, double& heading) const
{
SGVec3d cartPos = getCartPosAt(_flolsPosOffset);
geodPos = SGGeod::fromCart(cartPos);
// at present we don't support a heading offset for the FLOLS, so
// heading is just the carrier heading
heading = hdg + _flolsHeadingOffsetDeg;
return true;
}
double FGAICarrier::getFLOLFSGlidepathAngleDeg() const
{
return _flolsApproachAngle;
}
// find relative wind
void FGAICarrier::UpdateWind( double dt) {
//get the surface wind speed and direction
_wind_from_deg = _surface_wind_from_deg_node->getDoubleValue();
_wind_speed_kts = _surface_wind_speed_node->getDoubleValue();
//calculate the surface wind speed north and east in kts
double wind_speed_from_north_kts = cos( _wind_from_deg / SGD_RADIANS_TO_DEGREES )* _wind_speed_kts ;
double wind_speed_from_east_kts = sin( _wind_from_deg / SGD_RADIANS_TO_DEGREES )* _wind_speed_kts ;
//calculate the carrier speed north and east in kts
double speed_north_kts = cos( hdg / SGD_RADIANS_TO_DEGREES )* speed ;
double speed_east_kts = sin( hdg / SGD_RADIANS_TO_DEGREES )* speed ;
//calculate the relative wind speed north and east in kts
double rel_wind_speed_from_east_kts = wind_speed_from_east_kts + speed_east_kts;
double rel_wind_speed_from_north_kts = wind_speed_from_north_kts + speed_north_kts;
//combine relative speeds north and east to get relative windspeed in kts
_rel_wind_speed_kts = sqrt((rel_wind_speed_from_east_kts * rel_wind_speed_from_east_kts)
+ (rel_wind_speed_from_north_kts * rel_wind_speed_from_north_kts));
//calculate the relative wind direction
_rel_wind_from_deg = SGMiscd::rad2deg(atan2(rel_wind_speed_from_east_kts, rel_wind_speed_from_north_kts));
//calculate rel wind
_rel_wind = _rel_wind_from_deg - hdg;
SG_NORMALIZE_RANGE(_rel_wind, -180.0, 180.0);
//set in to wind property
InToWind();
//switch the wave-off lights
//if (InToWind())
// wave_off_lights = false;
//else
// wave_off_lights = true;
// cout << "rel wind: " << rel_wind << endl;
}// end update wind
void FGAICarrier::TurnToLaunch(){
// calculate tgt heading
if (_wind_speed_kts < 3){
tgt_heading = _base_course;
} else {
tgt_heading = _wind_from_deg;
}
//calculate tgt speed
double tgt_speed = 25 - _wind_speed_kts;
if (tgt_speed < 10)
tgt_speed = 10;
//turn the carrier
FGAIShip::TurnTo(tgt_heading);
FGAIShip::AccelTo(tgt_speed);
}
void FGAICarrier::TurnToRecover(){
//these are the rules for adjusting heading to provide a relative wind
//down the angled flightdeck
if (_wind_speed_kts < 3){
tgt_heading = _base_course + 60;
} else if (_rel_wind < -9 && _rel_wind >= -180){
tgt_heading = _wind_from_deg;
} else if (_rel_wind > -7 && _rel_wind < 45){
tgt_heading = _wind_from_deg + 60;
} else if (_rel_wind >=45 && _rel_wind < 180){
tgt_heading = _wind_from_deg + 45;
} else
tgt_heading = hdg;
SG_NORMALIZE_RANGE(tgt_heading, 0.0, 360.0);
//calculate tgt speed
double tgt_speed = 26 - _wind_speed_kts;
if (tgt_speed < 10)
tgt_speed = 10;
//turn the carrier
FGAIShip::TurnTo(tgt_heading);
FGAIShip::AccelTo(tgt_speed);
}
void FGAICarrier::TurnToBase(){
//turn the carrier
FGAIShip::TurnTo(_base_course);
FGAIShip::AccelTo(_base_speed);
}
void FGAICarrier::ReturnToBox(){
double course, distance, az2;
//calculate the bearing and range of the initial position from the carrier
geo_inverse_wgs_84(pos, _mOpBoxPos, &course, &az2, &distance);
distance *= SG_METER_TO_NM;
//cout << "return course: " << course << " distance: " << distance << endl;
//turn the carrier
FGAIShip::TurnTo(course);
FGAIShip::AccelTo(_base_speed);
if (distance >= 1)
_returning = true;
else
_returning = false;
} // end turn to base
bool FGAICarrier::OutsideBox() { //returns true if the carrier is outside operating box
if ( _max_lat == 0 && _min_lat == 0 && _max_lon == 0 && _min_lon == 0) {
SG_LOG(SG_AI, SG_DEBUG, "AICarrier: No Operating Box defined" );
return false;
}
if (_mOpBoxPos.getLatitudeDeg() >= 0) { //northern hemisphere
if (pos.getLatitudeDeg() >= _mOpBoxPos.getLatitudeDeg() + _max_lat)
return true;
if (pos.getLatitudeDeg() <= _mOpBoxPos.getLatitudeDeg() - _min_lat)
return true;
} else { //southern hemisphere
if (pos.getLatitudeDeg() <= _mOpBoxPos.getLatitudeDeg() - _max_lat)
return true;
if (pos.getLatitudeDeg() >= _mOpBoxPos.getLatitudeDeg() + _min_lat)
return true;
}
if (_mOpBoxPos.getLongitudeDeg() >=0) { //eastern hemisphere
if (pos.getLongitudeDeg() >= _mOpBoxPos.getLongitudeDeg() + _max_lon)
return true;
if (pos.getLongitudeDeg() <= _mOpBoxPos.getLongitudeDeg() - _min_lon)
return true;
} else { //western hemisphere
if (pos.getLongitudeDeg() <= _mOpBoxPos.getLongitudeDeg() - _max_lon)
return true;
if (pos.getLongitudeDeg() >= _mOpBoxPos.getLongitudeDeg() + _min_lon)
return true;
}
return false;
} // end OutsideBox
bool FGAICarrier::InToWind() {
_in_to_wind = false;
if ( fabs(_rel_wind) < 10 ){
_in_to_wind = true;
return true;
}
return false;
}
void FGAICarrier::UpdateElevator(double dt) {
double step = 0;
if ((_elevators && _elevator_pos_norm >= 1 ) || (!_elevators && _elevator_pos_norm <= 0 ))
return;
// move the elevators
if (_elevators ) {
step = dt / _elevator_transition_time;
if ( step > 1 )
step = 1;
} else {
step = -dt / _elevator_transition_time;
if ( step < -1 )
step = -1;
}
// assume a linear relationship
_elevator_pos_norm_raw += step;
//low pass filter
_elevator_pos_norm = (_elevator_pos_norm_raw * _elevator_time_constant) + (_elevator_pos_norm * (1 - _elevator_time_constant));
//sanitise the output
if (_elevator_pos_norm_raw >= 1.0) {
_elevator_pos_norm_raw = 1.0;
} else if (_elevator_pos_norm_raw <= 0.0) {
_elevator_pos_norm_raw = 0.0;
}
return;
} // end UpdateElevator
void FGAICarrier::UpdateJBD(double dt) {
const string launchbar_state = _launchbar_state_node->getStringValue();
double step = 0;
if (launchbar_state == "Engaged"){
_jbd = true;
} else {
_jbd = false;
}
if ((_jbd && _jbd_elevator_pos_norm >= 1 ) || ( !_jbd && _jbd_elevator_pos_norm <= 0 )){
return;
}
// move the jbds
if ( _jbd ) {
step = dt / _jbd_transition_time;
if ( step > 1 )
step = 1;
} else {
step = -dt / _jbd_transition_time;
if ( step < -1 )
step = -1;
}
// assume a linear relationship
_jbd_elevator_pos_norm_raw += step;
//low pass filter
_jbd_elevator_pos_norm = (_jbd_elevator_pos_norm_raw * _jbd_time_constant) + (_jbd_elevator_pos_norm * (1 - _jbd_time_constant));
//sanitise the output
if (_jbd_elevator_pos_norm >= 1.0) {
_jbd_elevator_pos_norm = 1.0;
} else if (_jbd_elevator_pos_norm <= 0.0) {
_jbd_elevator_pos_norm = 0.0;
}
return;
} // end UpdateJBD
std::pair<bool, SGGeod> FGAICarrier::initialPositionForCarrier(const std::string& namePennant)
{
FGAIManager::registerScenarios();
// this is actually a three-layer search (we want the scenario with the
// carrier with the correct penanant or name. Sometimes an XPath for
// properties would be quite handy :)
for (auto s : fgGetNode("/sim/ai/scenarios")->getChildren("scenario")) {
auto carriers = s->getChildren("carrier");
auto it = std::find_if(carriers.begin(), carriers.end(),
[namePennant] (const SGPropertyNode* n)
{
// don't want to use a recursive lambda here, so inner search is a flat loop
for (auto nameChild : n->getChildren("name")) {
if (nameChild->getStringValue() == namePennant) return true;
}
return false;
});
if (it == carriers.end()) {
continue;
}
// mark the scenario for loading (which will happen in post-init of the AIManager)
fgGetNode("/sim/ai/")->addChild("scenario")->setStringValue(s->getStringValue("id"));
// read out the initial-position
SGGeod geod = SGGeod::fromDeg((*it)->getDoubleValue("longitude"),
(*it)->getDoubleValue("latitude"));
return std::make_pair(true, geod);
} // of scenarios iteration
return std::make_pair(false, SGGeod());
}
SGSharedPtr<FGAICarrier> FGAICarrier::findCarrierByNameOrPennant(const std::string& namePennant)
{
const FGAIManager* aiManager = globals->get_subsystem<FGAIManager>();
if (!aiManager) {
return {};
}
for (const auto& aiObject : aiManager->get_ai_list()) {
if (aiObject->isa(object_type::otCarrier)) {
SGSharedPtr<FGAICarrier> c = static_cast<FGAICarrier*>(aiObject.get());
if ((c->_sign == namePennant) || (c->_getName() == namePennant)) {
return c;
}
}
} // of all objects iteration
return {};
}
void FGAICarrier::extractCarriersFromScenario(SGPropertyNode_ptr xmlNode, SGPropertyNode_ptr scenario)
{
for (auto c : xmlNode->getChildren("entry")) {
if (c->getStringValue("type") != std::string("carrier"))
continue;
const std::string name = c->getStringValue("name");
const std::string pennant = c->getStringValue("pennant-number");
if (name.empty() && pennant.empty()) {
continue;
}
SGPropertyNode_ptr carrierNode = scenario->addChild("carrier");
// extract the initial position from the scenario
carrierNode->setDoubleValue("longitude", c->getDoubleValue("longitude"));
carrierNode->setDoubleValue("latitude", c->getDoubleValue("latitude"));
// A description of the carrier is also available from the entry. Primarily for use by the launcher
carrierNode->setStringValue("description", c->getStringValue("description"));
// the find code above just looks for anything called a name (so alias
// are possible, for example)
if (!name.empty()) carrierNode->addChild("name")->setStringValue(name);
if (!pennant.empty()) {
carrierNode->addChild("name")->setStringValue(pennant);
carrierNode->addChild("pennant-number")->setStringValue(pennant);
}
// extact parkings
for (auto p : c->getChildren("parking-pos")) {
carrierNode->addChild("parking-pos")->setStringValue(p->getStringValue("name"));
}
}
}
simgear::Emesary::ReceiptStatus FGAICarrier::Receive(simgear::Emesary::INotificationPtr n)
{
auto nctn = dynamic_pointer_cast<NearestCarrierToNotification>(n);
if (nctn) {
if (!nctn->GetCarrier() || nctn->GetDistanceMeters() > nctn->GetDistanceToMeters(pos)) {
nctn->SetCarrier(this, &pos);
nctn->SetViewPositionLatNode(_view_position_lat_deg_node);
nctn->SetViewPositionLonNode(_view_position_lon_deg_node);
nctn->SetViewPositionAltNode(_view_position_alt_ft_node);
nctn->SetDeckheight(_deck_altitude_ft);
nctn->SetHeading(hdg);
nctn->SetVckts(speed);
nctn->SetCarrierIdent(this->_getName());
}
return simgear::Emesary::ReceiptStatus::OK;
}
return simgear::Emesary::ReceiptStatus::NotProcessed;
}

181
src/AIModel/AICarrier.hxx Normal file
View File

@@ -0,0 +1,181 @@
// FGAICarrier - AIShip-derived class creates an AI aircraft carrier
//
// Written by David Culp, started October 2004.
//
// Copyright (C) 2004 David P. Culp - davidculp2@comcast.net
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation; either version 2 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#pragma once
#include <list>
#include <string>
#include <string_view>
#include <simgear/compiler.h>
#include <simgear/emesary/Emesary.hxx>
#include "AIShip.hxx"
#include "AIBase.hxx"
#include "AIManager.hxx"
class FGAIManager;
class FGAICarrier;
class FGAICarrier : public FGAIShip, simgear::Emesary::IReceiver
{
public:
FGAICarrier();
virtual ~FGAICarrier();
string_view getTypeString(void) const override { return "carrier"; }
void readFromScenario(SGPropertyNode* scFileNode) override;
void setSign(const std::string&);
void setDeckAltitudeFt(const double altitude_feet);
void setTACANChannelID(const std::string&);
double getDefaultModelRadius() override { return 350.0; }
void bind() override;
void UpdateWind(double dt);
void setWind_from_east(double fps);
void setWind_from_north(double fps);
void setMaxLat(double deg);
void setMinLat(double deg);
void setMaxLong(double deg);
void setMinLong(double deg);
void setMPControl(bool c);
void setAIControl(bool c);
void TurnToLaunch();
void TurnToRecover();
void TurnToBase();
void ReturnToBox();
bool OutsideBox();
bool init(ModelSearchOrder searchOrder) override;
bool getParkPosition(const std::string& id, SGGeod& geodPos, double& hdng, SGVec3d& uvw);
/**
* @brief type-safe wrapper around AIManager::getObjectFromProperty
*/
static SGSharedPtr<FGAICarrier> findCarrierByNameOrPennant(const std::string& namePennant);
static std::pair<bool, SGGeod> initialPositionForCarrier(const std::string& namePennant);
/**
* for a given scenario node, check for carriers within, and write nodes with
* names, pennants and initial position into the second argument.
* This is used to support 'start on a carrier', since we can quickly find
* the corresponding scenario file to be loaded.
*/
static void extractCarriersFromScenario(SGPropertyNode_ptr xmlNode, SGPropertyNode_ptr scenario);
bool getFLOLSPositionHeading(SGGeod& pos, double& heading) const;
double getFLOLFSGlidepathAngleDeg() const;
double getDeckAltitudeFt() const { return _deck_altitude_ft; }
virtual simgear::Emesary::ReceiptStatus Receive(simgear::Emesary::INotificationPtr n) override;
private:
/// Is sufficient to be private, stores a possible position to place an
/// aircraft on start
struct ParkPosition {
ParkPosition(const ParkPosition& pp)
: name(pp.name), offset(pp.offset), heading_deg(pp.heading_deg)
{
}
ParkPosition(const std::string& n, const SGVec3d& off = SGVec3d(), double heading = 0)
: name(n), offset(off), heading_deg(heading)
{
}
std::string name;
SGVec3d offset;
double heading_deg;
};
void update(double dt) override;
bool InToWind(); // set if the carrier is in to wind
void UpdateElevator(double dt);
void UpdateJBD(double dt);
bool _AIControl = false; // under AI control. Either this or MPControl will be true
SGPropertyNode_ptr _ai_latch_node;
SGPropertyNode_ptr _altitude_node;
double _angled_deck_degrees = -8.55; // angled deck offset from carrier heading. usually negative
double _base_course = 0;
double _base_speed = 0;
double _deck_altitude_ft = 65.0065;
double _elevator_pos_norm = 0;
double _elevator_pos_norm_raw = 0;
double _elevator_time_constant = 0;
double _elevator_transition_time = 0;
bool _elevators = false;
double _flols_angle = 0;
double _flols_dist = 0; // the distance of the eyepoint from the flols
int _flols_visible_light = 0; // the flols light which is visible at the moment
SGPropertyNode_ptr _flols_x_node;
SGPropertyNode_ptr _flols_y_node;
SGPropertyNode_ptr _flols_z_node;
double _flolsApproachAngle = 3.0; ///< glidepath angle for the FLOLS
double _flolsHeadingOffsetDeg = 0.0; /// angle in degrees offset from the carrier centerline
SGVec3d _flolsPosOffset;
SGVec3d _flolsTouchdownPosition;
SGPropertyNode_ptr _heading_node;
bool _in_to_wind = false;
bool _jbd = false;
double _jbd_elevator_pos_norm = 0;
double _jbd_elevator_pos_norm_raw = 0;
double _jbd_time_constant = 0;
double _jbd_transition_time = 0;
SGPropertyNode_ptr _latitude_node;
SGPropertyNode_ptr _launchbar_state_node;
double _lineup = 0; // lineup angle deviation from carrier;
SGPropertyNode_ptr _longitude_node;
SGVec3d _lsoPosition; /// LSO position
double _max_lat = 0;
double _max_lon = 0;
double _min_lat = 0;
double _min_lon = 0;
SGGeod _mOpBoxPos; // operational box limit for carrier.
bool _MPControl = false; // being controlled by MP. Either this or AIControl will be true
SGPropertyNode_ptr _pitch_node;
std::list<ParkPosition> _ppositions; // List of positions where an aircraft can start.
double _rel_wind = 0;
double _rel_wind_from_deg = 0;
double _rel_wind_speed_kts = 0;
bool _returning = false; // set if the carrier is returning to an operating box
SGPropertyNode_ptr _roll_node;
std::string _sign; // The sign (pennant) of this carrier; e.g. CVN-68
SGPropertyNode_ptr _surface_wind_from_deg_node;
SGPropertyNode_ptr _surface_wind_speed_node;
std::string _TACAN_channel_id;
SGVec3d _towerPosition;
bool _turn_to_base_course = true;
bool _turn_to_launch_hdg = true;
bool _turn_to_recovery_hdg = true;
int _view_index = 0;
SGPropertyNode_ptr _view_position_alt_ft_node;
SGPropertyNode_ptr _view_position_lat_deg_node;
SGPropertyNode_ptr _view_position_lon_deg_node;
bool _wave_off_lights_demand = false; // when waveoff requested.
double _wind_from_deg = 0; //true wind direction
double _wind_from_east = 0; // fps
double _wind_from_north = 0; // fps
double _wind_speed_kts = 0; //true wind speed
SGPropertyNode_ptr _is_user_craft;
};

363
src/AIModel/AIEscort.cxx Normal file
View File

@@ -0,0 +1,363 @@
// FGAIEscort - FGAIShip-derived class creates an AI Ground Vehicle
// by adding a ground following utility
//
// Written by Vivian Meazza, started August 2009.
// - vivian.meazza at lineone.net
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation; either version 2 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif
#include <algorithm>
#include <string>
#include <vector>
#include <simgear/sg_inlines.h>
#include <simgear/math/sg_geodesy.hxx>
#include <simgear/math/sg_random.hxx>
#include <cmath>
#include <Main/util.hxx>
#include <Main/globals.hxx>
#include <Viewer/view.hxx>
#include <Scenery/scenery.hxx>
#include "AIEscort.hxx"
using std::string;
FGAIEscort::FGAIEscort() : FGAIShip(object_type::otEscort)
{
invisible = false;
}
void FGAIEscort::readFromScenario(SGPropertyNode* scFileNode) {
if (!scFileNode)
return;
FGAIShip::readFromScenario(scFileNode);
setName(scFileNode->getStringValue("name", "Escort"));
setSMPath(scFileNode->getStringValue("submodel-path", ""));
setStnRange(scFileNode->getDoubleValue("station/range-nm", 1));
setStnBrg(scFileNode->getDoubleValue("station/brg-deg", 0.0));
setStnLimit(scFileNode->getDoubleValue("station/range-limit-nm", 0.2));
setStnAngleLimit(scFileNode->getDoubleValue("station/angle-limit-deg", 15.0));
setStnSpeed(scFileNode->getDoubleValue("station/speed-kts", 2.5));
setStnPatrol(scFileNode->getBoolValue("station/patrol", false));
setStnHtFt(scFileNode->getDoubleValue("station/height-ft", 0.0));
setStnDegTrue(scFileNode->getBoolValue("station/deg-true", false));
setParentName(scFileNode->getStringValue("station/parent", ""));
setMaxSpeed(scFileNode->getDoubleValue("max-speed-kts", 30.0));
setUpdateInterval(scFileNode->getDoubleValue("update-interval-sec", 10.0));
setCallSign(scFileNode->getStringValue("callsign", ""));
if(_patrol)
sg_srandom_time();
}
void FGAIEscort::bind() {
FGAIShip::bind();
tie("station/rel-bearing-deg",
SGRawValuePointer<double>(&_stn_relbrg));
tie("station/true-bearing-deg",
SGRawValuePointer<double>(&_stn_truebrg));
tie("station/range-nm",
SGRawValuePointer<double>(&_stn_range));
tie("station/range-limit-nm",
SGRawValuePointer<double>(&_stn_limit));
tie("station/angle-limit-deg",
SGRawValuePointer<double>(&_stn_angle_limit));
tie("station/speed-kts",
SGRawValuePointer<double>(&_stn_speed));
tie("station/height-ft",
SGRawValuePointer<double>(&_stn_height));
tie("controls/update-interval-sec",
SGRawValuePointer<double>(&_interval));
tie("controls/parent-mp-control",
SGRawValuePointer<bool>(&_MPControl));
tie("station/target-range-nm",
SGRawValuePointer<double>(&_tgtrange));
tie("station/target-brg-deg-t",
SGRawValuePointer<double>(&_tgtbrg));
tie("station/patrol",
SGRawValuePointer<bool>(&_patrol));
}
bool FGAIEscort::init(ModelSearchOrder searchOrder) {
if (!FGAIShip::init(searchOrder))
return false;
reinit();
return true;
}
void FGAIEscort::reinit() {
invisible = false;
no_roll = false;
props->setStringValue("controls/parent-name", _parent.c_str());
if (setParentNode()){
setParent();
pos = _tgtpos;
speed = _parent_speed;
hdg = _parent_hdg;
}
FGAIShip::reinit();
}
void FGAIEscort::update(double dt) {
FGAIShip::update(dt);
RunEscort(dt);
}
void FGAIEscort::setStnRange(double r) {
_stn_range = r;
}
void FGAIEscort::setStnBrg(double b) {
_stn_brg = b;
}
void FGAIEscort::setStnLimit(double l) {
_stn_limit = l;
}
void FGAIEscort::setStnAngleLimit(double al) {
_stn_angle_limit = al;
}
void FGAIEscort::setStnSpeed(double s) {
_stn_speed = s;
}
void FGAIEscort::setStnHtFt(double h) {
_stn_height = h;
}
void FGAIEscort::setStnDegTrue(bool t) {
_stn_deg_true = t;
}
void FGAIEscort::setMaxSpeed(double m) {
_max_speed = m;
}
void FGAIEscort::setUpdateInterval(double i) {
_interval = i;
}
void FGAIEscort::setStnPatrol(bool p) {
_patrol = p;
}
bool FGAIEscort::getGroundElev(SGGeod inpos) {
double height_m ;
const simgear::BVHMaterial* mat = 0;
if (globals->get_scenery()->get_elevation_m(SGGeod::fromGeodM(inpos, 3000), height_m, &mat, 0)){
const SGMaterial* material = dynamic_cast<const SGMaterial*>(mat);
_ht_agl_ft = inpos.getElevationFt() - height_m * SG_METER_TO_FEET;
if (material) {
const std::vector<std::string>& names = material->get_names();
_solid = material->get_solid();
if (!names.empty())
props->setStringValue("material/name", names[0].c_str());
else
props->setStringValue("material/name", "");
//cout << "material " << names[0].c_str()
// << " _elevation_m " << _elevation_m
// << " solid " << _solid
// << " load " << _load_resistance
// << " frictionFactor " << _frictionFactor
// << endl;
}
return true;
} else {
return false;
}
}
void FGAIEscort::setParent()
{
double lat = _selected_ac->getDoubleValue("position/latitude-deg");
double lon = _selected_ac->getDoubleValue("position/longitude-deg");
double elevation = _selected_ac->getDoubleValue("position/altitude-ft");
_MPControl = _selected_ac->getBoolValue("controls/mp-control");
_selectedpos.setLatitudeDeg(lat);
_selectedpos.setLongitudeDeg(lon);
_selectedpos.setElevationFt(elevation);
_parent_speed = _selected_ac->getDoubleValue("velocities/speed-kts");
_parent_hdg = _selected_ac->getDoubleValue("orientation/true-heading-deg");
if(!_stn_deg_true){
_stn_truebrg = calcTrueBearingDeg(_stn_brg, _parent_hdg);
_stn_relbrg = _stn_brg;
//cout << _name <<" set rel"<<endl;
} else {
_stn_truebrg = _stn_brg;
_stn_relbrg = calcRelBearingDeg(_stn_brg, _parent_hdg);
//cout << _name << " set true"<<endl;
}
double course2;
SGGeodesy::direct( _selectedpos, _stn_truebrg, _stn_range * SG_NM_TO_METER,
_tgtpos, course2);
_tgtpos.setElevationFt(_stn_height);
calcRangeBearing(pos.getLatitudeDeg(), pos.getLongitudeDeg(),
_tgtpos.getLatitudeDeg(), _tgtpos.getLongitudeDeg(), _tgtrange, _tgtbrg);
_relbrg = calcRelBearingDeg(_tgtbrg, hdg);
}
void FGAIEscort::calcRangeBearing(double lat, double lon, double lat2, double lon2,
double &range, double &bearing) const
{
// calculate the bearing and range of the second pos from the first
double az2, distance;
geo_inverse_wgs_84(lat, lon, lat2, lon2, &bearing, &az2, &distance);
range = distance * SG_METER_TO_NM;
}
double FGAIEscort::calcTrueBearingDeg(double bearing, double heading)
{
double angle = bearing + heading;
SG_NORMALIZE_RANGE(angle, 0.0, 360.0);
return angle;
}
SGVec3d FGAIEscort::getCartHitchPosAt(const SGVec3d& _off) const {
double hdg = _selected_ac->getDoubleValue("orientation/true-heading-deg");
double pitch = _selected_ac->getDoubleValue("orientation/pitch-deg");
double roll = _selected_ac->getDoubleValue("orientation/roll-deg");
// Transform that one to the horizontal local coordinate system.
SGQuatd hlTrans = SGQuatd::fromLonLat(_selectedpos);
// and postrotate the orientation of the AIModel wrt the horizontal
// local frame
hlTrans *= SGQuatd::fromYawPitchRollDeg(hdg, pitch, roll);
// The offset converted to the usual body fixed coordinate system
// rotated to the earth fiexed coordinates axis
SGVec3d off = hlTrans.backTransform(_off);
// Add the position offset of the AIModel to gain the earth centered position
SGVec3d cartPos = SGVec3d::fromGeod(_selectedpos);
return cartPos + off;
}
void FGAIEscort::setStationSpeed(){
double speed = 0;
double angle = 0;
// these are the AI rules for the manoeuvring of escorts
if (_MPControl && _tgtrange > 4.0 * _stn_limit) {
SG_LOG(SG_AI, SG_ALERT, "AIEscort: " << _name
<< " re-aligning to MP pos");
pos = _tgtpos;
speed = 0.0;
angle = 0.0;
} else if ((_relbrg < -90.0 || _relbrg > 90.0) && _tgtrange > _stn_limit) {
angle =_relbrg;
if(_tgtrange > 4.0 * _stn_limit)
speed = 4.0 * -_stn_speed;
else
speed = -_stn_speed;
} else if ((_relbrg >= -90.0 && _relbrg <= 90.0) && _tgtrange > _stn_limit) {
angle = _relbrg;
if(_tgtrange > 4.0 * _stn_limit)
speed = 4.0 * _stn_speed;
else
speed = _stn_speed;
} else {
if (_patrol){
angle = 15.0 * sg_random();
speed = 5.0 * sg_random();
} else {
angle = 0.0;
speed = 0.0;
}
}
double station_speed = _parent_speed + speed;
SG_CLAMP_RANGE(station_speed, 5.0, _max_speed);
SG_CLAMP_RANGE(angle, -_stn_angle_limit, _stn_angle_limit);
AccelTo(station_speed);
TurnTo(_parent_hdg + angle);
ClimbTo(_stn_height);
}
void FGAIEscort::RunEscort(double dt){
_dt_count += dt;
///////////////////////////////////////////////////////////////////////////
// Check execution time (currently once every 0.05 sec or 20 fps)
// Add a bit of randomization to prevent the execution of all flight plans
// in synchrony, which can add significant periodic framerate flutter.
// Randomization removed to get better appearance
///////////////////////////////////////////////////////////////////////////
//cout << "_start_sec " << _start_sec << " time_sec " << time_sec << endl;
if (_dt_count < _next_run)
return;
_next_run = _interval /*+ (0.015 * sg_random())*/;
if(_parent == ""){
return;
}
setParent();
setStationSpeed();
_dt_count = 0;
}
// end AIEscort

99
src/AIModel/AIEscort.hxx Normal file
View File

@@ -0,0 +1,99 @@
// FGAIGroundVehicle - FGAIShip-derived class creates an AI Ground Vehicle
// by adding a ground following utility
//
// Written by Vivian Meazza, started August 2009.
// - vivian.meazza at lineone.net
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation; either version 2 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#pragma once
#include <list>
#include <string>
#include <string_view>
#include <simgear/compiler.h>
#include "AIBase.hxx"
#include "AIShip.hxx"
#include "AIBase.hxx"
#include "AIManager.hxx"
class FGAIEscort : public FGAIShip
{
public:
FGAIEscort();
virtual ~FGAIEscort() = default;
string_view getTypeString(void) const override { return "escort"; }
void readFromScenario(SGPropertyNode* scFileNode) override;
bool init(ModelSearchOrder searchOrder) override;
void bind() override;
void reinit() override;
void update(double dt) override;
private:
void setStnRange(double r);
void setStnBrg(double y);
void setStationSpeed();
void setStnLimit(double l);
void setStnAngleLimit(double l);
void setStnSpeed(double s);
void setStnHtFt(double h);
void setStnPatrol(bool p);
void setStnDegTrue(bool t);
void setParent();
void setMaxSpeed(double m);
void setUpdateInterval(double i);
void RunEscort(double dt);
bool getGroundElev(SGGeod inpos);
SGVec3d getCartHitchPosAt(const SGVec3d& off) const;
void calcRangeBearing(double lat, double lon, double lat2, double lon2,
double &range, double &bearing) const;
double calcTrueBearingDeg(double bearing, double heading);
SGGeod _selectedpos;
SGGeod _tgtpos;
bool _solid = true; // if true ground is solid for FDMs
double _tgtrange = 0.0;
double _tgtbrg = 0.0;
double _ht_agl_ft = 0.0;
double _relbrg = 0.0;
double _parent_speed = 0.0;
double _parent_hdg = 0.0;
double _interval = 0.0;
double _stn_relbrg = 0.0;
double _stn_truebrg = 0.0;
double _stn_brg = 0.0;
double _stn_range = 0.0;
double _stn_height = 0.0;
double _stn_speed = 0.0;
double _stn_angle_limit = 0.0;
double _stn_limit = 0.0;
bool _MPControl = false;
bool _patrol = false;
bool _stn_deg_true = false;
};

View File

@@ -0,0 +1,647 @@
// // FGAIFlightPlan - class for loading and storing AI flight plans
// Written by David Culp, started May 2004
// - davidculp2@comcast.net
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation; either version 2 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif
#include <iterator>
#include <algorithm>
#include <simgear/constants.h>
#include <simgear/debug/logstream.hxx>
#include <simgear/io/iostreams/sgstream.hxx>
#include <simgear/math/sg_geodesy.hxx>
#include <simgear/misc/sg_path.hxx>
#include <simgear/props/props.hxx>
#include <simgear/props/props_io.hxx>
#include <simgear/structure/exception.hxx>
#include <simgear/timing/sg_time.hxx>
#include <Main/globals.hxx>
#include <Main/fg_props.hxx>
#include <Main/fg_init.hxx>
#include <Airports/airport.hxx>
#include <Airports/dynamics.hxx>
#include <Airports/runways.hxx>
#include <Airports/groundnetwork.hxx>
#include <Environment/environment_mgr.hxx>
#include <Environment/environment.hxx>
#include <Traffic/Schedule.hxx>
#include "AIFlightPlan.hxx"
#include "AIAircraft.hxx"
using std::string;
FGAIWaypoint::FGAIWaypoint() {
speed = 0;
crossat = 0;
finished = 0;
gear_down = 0;
flaps = 0;
on_ground = 0;
routeIndex = 0;
time_sec = 0;
trackLength = 0;
}
bool FGAIWaypoint::contains(const string& target) {
size_t found = name.find(target);
if (found == string::npos)
return false;
else
return true;
}
double FGAIWaypoint::getLatitude()
{
return pos.getLatitudeDeg();
}
double FGAIWaypoint::getLongitude()
{
return pos.getLongitudeDeg();
}
double FGAIWaypoint::getAltitude()
{
return pos.getElevationFt();
}
void FGAIWaypoint::setLatitude(double lat)
{
pos.setLatitudeDeg(lat);
}
void FGAIWaypoint::setLongitude(double lon)
{
pos.setLongitudeDeg(lon);
}
void FGAIWaypoint::setAltitude(double alt)
{
pos.setElevationFt(alt);
}
FGAIFlightPlan::FGAIFlightPlan() :
sid(NULL),
repeat(false),
distance_to_go(0),
lead_distance_ft(0),
leadInAngle(0),
start_time(0),
arrivalTime(0),
leg(0),
lastNodeVisited(0),
isValid(true)
{
wpt_iterator = waypoints.begin();
}
FGAIFlightPlan::FGAIFlightPlan(const string& filename) :
sid(NULL),
repeat(false),
distance_to_go(0),
lead_distance_ft(0),
leadInAngle(0),
start_time(0),
arrivalTime(0),
leg(10),
lastNodeVisited(0),
isValid(parseProperties(filename))
{
}
/**
* This is a modified version of the constructor,
* Which not only reads the waypoints from a
* Flight plan file, but also adds the current
* Position computed by the traffic manager, as well
* as setting speeds and altitude computed by the
* traffic manager.
*/
FGAIFlightPlan::FGAIFlightPlan(FGAIAircraft *ac,
const std::string& p,
double course,
time_t start,
time_t remainingTime,
FGAirport *dep,
FGAirport *arr,
bool firstLeg,
double radius,
double alt,
double lat,
double lon,
double speed,
const string& fltType,
const string& acType,
const string& airline) :
sid(NULL),
repeat(false),
distance_to_go(0),
lead_distance_ft(0),
leadInAngle(0),
start_time(start),
arrivalTime(0),
leg(10),
lastNodeVisited(0),
isValid(false),
departure(dep),
arrival(arr)
{
if (parseProperties(p)) {
isValid = true;
} else {
createWaypoints(ac, course, start, remainingTime, dep, arr, firstLeg, radius,
alt, lat, lon, speed, fltType, acType, airline);
}
}
FGAIFlightPlan::~FGAIFlightPlan()
{
deleteWaypoints();
//delete taxiRoute;
}
void FGAIFlightPlan::createWaypoints(FGAIAircraft *ac,
double course,
time_t start,
time_t remainingTime,
FGAirport *dep,
FGAirport *arr,
bool firstLeg,
double radius,
double alt,
double lat,
double lon,
double speed,
const string& fltType,
const string& acType,
const string& airline)
{
time_t now = globals->get_time_params()->get_cur_time();
time_t timeDiff = now-start;
leg = AILeg::STARTUP_PUSHBACK;
if ((timeDiff > 60) && (timeDiff < 1500))
leg = AILeg::TAXI;
else if ((timeDiff >= 1500) && (timeDiff < 2000))
leg = AILeg::TAKEOFF;
else if (timeDiff >= 2000) {
if (remainingTime > 2000) {
leg = AILeg::CRUISE;
} else {
leg = AILeg::APPROACH;
}
}
SG_LOG(SG_AI, SG_DEBUG, ac->getTrafficRef()->getCallSign() << "|Route from " << dep->getId() << " to " << arr->getId() <<
". Set leg to : " << leg << " " << remainingTime);
wpt_iterator = waypoints.begin();
bool dist = 0;
isValid = create(ac, dep, arr, leg, alt, speed, lat, lon,
firstLeg, radius, fltType, acType, airline, dist);
wpt_iterator = waypoints.begin();
}
bool FGAIFlightPlan::parseProperties(const std::string& filename)
{
SGPath fp = globals->findDataPath("AI/FlightPlans/" + filename);
if (!fp.exists()) {
return false;
}
return readFlightplan(fp);
}
bool FGAIFlightPlan::readFlightplan(const SGPath& file)
{
sg_ifstream f(file);
return readFlightplan(f, file);
}
bool FGAIFlightPlan::readFlightplan(std::istream& stream, const sg_location& loc)
{
SGPropertyNode root;
try {
readProperties(stream, &root);
} catch (const sg_exception& e) {
SG_LOG(SG_AI, SG_ALERT, "Error reading AI flight plan: " << loc.asString() << " message:" << e.getFormattedMessage());
return false;
}
SGPropertyNode* node = root.getNode("flightplan");
if (!node) {
SG_LOG(SG_AI, SG_ALERT, "Error reading AI flight plan: " << loc.asString() << ": no <flightplan> root element");
return false;
}
for (int i = 0; i < node->nChildren(); i++) {
FGAIWaypoint* wpt = new FGAIWaypoint;
SGPropertyNode* wpt_node = node->getChild(i);
bool gear, flaps;
// Calculate some default values if they are not set explicitly in the flightplan
if (wpt_node->getDoubleValue("ktas", 0) < 1.0f) {
// Not moving so assume shut down.
wpt->setPowerDownLights();
flaps = false;
gear = true;
} else if (wpt_node->getDoubleValue("alt", 0) > 10000.0f) {
// Cruise flight;
wpt->setCruiseLights();
flaps = false;
gear = false;
} else if (wpt_node->getBoolValue("on-ground", false)) {
// On ground
wpt->setGroundLights();
flaps = true;
gear = true;
} else if (wpt_node->getDoubleValue("alt", 0) < 3000.0f) {
// In the air below 3000 ft, so flaps and gear down.
wpt->setApproachLights();
flaps = true;
gear = true;
} else {
// In the air 3000-10000 ft
wpt->setApproachLights();
flaps = false;
gear = false;
}
wpt->setName(wpt_node->getStringValue("name", "END"));
wpt->setLatitude(wpt_node->getDoubleValue("lat", 0));
wpt->setLongitude(wpt_node->getDoubleValue("lon", 0));
wpt->setAltitude(wpt_node->getDoubleValue("alt", 0));
wpt->setSpeed(wpt_node->getDoubleValue("ktas", 0));
wpt->setCrossat(wpt_node->getDoubleValue("crossat", -10000));
wpt->setGear_down(wpt_node->getBoolValue("gear-down", gear));
wpt->setFlaps(wpt_node->getBoolValue("flaps-down", flaps) ? 1.0 : 0.0);
wpt->setSpoilers(wpt_node->getBoolValue("spoilers", false) ? 1.0 : 0.0);
wpt->setSpeedBrakes(wpt_node->getBoolValue("speedbrakes", false) ? 1.0 : 0.0);
wpt->setOn_ground(wpt_node->getBoolValue("on-ground", false));
wpt->setTime_sec(wpt_node->getDoubleValue("time-sec", 0));
wpt->setTime(wpt_node->getStringValue("time", ""));
wpt->setFinished((wpt->getName() == "END"));
pushBackWaypoint(wpt);
}
auto lastWp = getLastWaypoint();
if (!lastWp || lastWp->getName().compare("END") != 0) {
SG_LOG(SG_AI, SG_ALERT, "FGAIFlightPlan::Flightplan (" + loc.asString() + ") missing END node");
return false;
}
wpt_iterator = waypoints.begin();
return true;
}
FGAIWaypoint* FGAIFlightPlan::getLastWaypoint()
{
if (waypoints.empty())
return nullptr;
return waypoints.back();
};
FGAIWaypoint* FGAIFlightPlan::getPreviousWaypoint( void ) const
{
if (empty())
return nullptr;
if (wpt_iterator == waypoints.begin()) {
return nullptr;
} else {
wpt_vector_iterator prev = wpt_iterator;
return *(--prev);
}
}
FGAIWaypoint* FGAIFlightPlan::getCurrentWaypoint( void ) const
{
if (wpt_iterator == waypoints.end())
return nullptr;
return *wpt_iterator;
}
FGAIWaypoint* FGAIFlightPlan::getNextWaypoint( void ) const
{
if (wpt_iterator == waypoints.end())
return nullptr;
wpt_vector_iterator last = waypoints.end() - 1;
if (wpt_iterator == last) {
return nullptr;
} else {
return *(wpt_iterator + 1);
}
}
int FGAIFlightPlan::getNextTurnAngle( void ) const
{
return nextTurnAngle;
}
void FGAIFlightPlan::IncrementWaypoint(bool eraseWaypoints )
{
if (empty())
return;
if (eraseWaypoints)
{
if (wpt_iterator == waypoints.begin())
++wpt_iterator;
else
if (!waypoints.empty())
{
delete *(waypoints.begin());
waypoints.erase(waypoints.begin());
wpt_iterator = waypoints.begin();
++wpt_iterator;
}
}
else {
++wpt_iterator;
}
// Calculate the angle of the next turn.
if (wpt_iterator == waypoints.end())
return;
if (wpt_iterator == waypoints.begin())
return;
if (wpt_iterator+1 == waypoints.end())
return;
if (waypoints.size()<3)
return;
FGAIWaypoint* previousWP = *(wpt_iterator -1);
FGAIWaypoint* currentWP = *(wpt_iterator);
FGAIWaypoint* nextWP = *(wpt_iterator + 1);
int currentBearing = this->getBearing(previousWP, currentWP);
int nextBearing = this->getBearing(currentWP, nextWP);
nextTurnAngle = SGMiscd::normalizePeriodic(-180, 180, nextBearing - currentBearing);
if ((previousWP->getSpeed() > 0 && nextWP->getSpeed() < 0) ||
(previousWP->getSpeed() < 0 && nextWP->getSpeed() > 0)) {
nextTurnAngle += 180;
SG_LOG(SG_AI, SG_BULK, "Add 180 to turn angle pushback end");
}
SG_LOG(SG_AI, SG_BULK, "Calculated next turn angle " << nextTurnAngle << " " << previousWP->getName() << " " << currentWP->getName() << " Previous Speed " << previousWP->getSpeed() << " Next Speed " << nextWP->getSpeed());
}
void FGAIFlightPlan::DecrementWaypoint()
{
if (empty())
return;
--wpt_iterator;
}
void FGAIFlightPlan::eraseLastWaypoint()
{
if (empty())
return;
delete (waypoints.back());
waypoints.pop_back();;
wpt_iterator = waypoints.begin();
++wpt_iterator;
}
// gives distance in meters from a position to a waypoint
double FGAIFlightPlan::getDistanceToGo(double lat, double lon, FGAIWaypoint* wp) const{
return SGGeodesy::distanceM(SGGeod::fromDeg(lon, lat), wp->getPos());
}
// sets distance in feet from a lead point to the current waypoint
// basically a catch radius, that triggers the advancement of WPs
void FGAIFlightPlan::setLeadDistance(double speed,
double bearing,
FGAIWaypoint* current,
FGAIWaypoint* next){
double turn_radius_m;
// Handle Ground steering
// At a turn rate of 30 degrees per second, it takes 12 seconds to do a full 360 degree turn
// So, to get an estimate of the turn radius, calculate the cicumference of the circle
// we travel on. Get the turn radius by dividing by PI (*2).
// FIXME Why when going backwards? No fabs
if (speed < 0.5) {
lead_distance_ft = 0.5;
return;
}
if (speed > 0 && speed < 0.5) {
lead_distance_ft = 5 * SG_FEET_TO_METER;
SG_LOG(SG_AI, SG_BULK, "Setting Leaddistance fixed " << (lead_distance_ft*SG_FEET_TO_METER));
return;
}
double speed_mps = speed * SG_KT_TO_MPS;
if (speed < 25) {
turn_radius_m = ((360/30)*fabs(speed_mps)) / (2*M_PI);
} else {
turn_radius_m = 0.1911 * speed * speed; // an estimate for 25 degrees bank
}
double inbound = bearing;
double outbound = getBearing(current, next);
leadInAngle = fabs(inbound - outbound);
if (leadInAngle > 180.0) leadInAngle = 360.0 - leadInAngle;
//if (leadInAngle < 30.0) // To prevent lead_dist from getting so small it is skipped
// leadInAngle = 30.0;
//lead_distance_ft = turn_radius * sin(leadInAngle * SG_DEGREES_TO_RADIANS);
double lead_distance_m = turn_radius_m * tan((leadInAngle * SG_DEGREES_TO_RADIANS)/2);
lead_distance_ft = lead_distance_m * SG_METER_TO_FEET;
SG_LOG(SG_AI, SG_BULK, "Setting Leaddistance " << (lead_distance_ft*SG_FEET_TO_METER) << " Turnradius " << turn_radius_m << " Speed " << speed_mps << " Half turn Angle " << (leadInAngle)/2);
if (lead_distance_ft > 1000) {
SG_LOG(SG_AI, SG_BULK, "Excessive leaddistance possible direction change " << lead_distance_ft << " leadInAngle " << leadInAngle << " inbound " << inbound << " outbound " << outbound);
}
/*
if ((lead_distance_ft > (3*turn_radius)) && (current->on_ground == false)) {
SG_LOG(SG_AI, SG_ALERT, "Warning: Lead-in distance is large. Inbound = " << inbound
<< ". Outbound = " << outbound << ". Lead in angle = " << leadInAngle << ". Turn radius = " << turn_radius);
lead_distance_ft = 3 * turn_radius;
return;
}
if ((leadInAngle > 90) && (current->on_ground == true)) {
lead_distance_ft = turn_radius * tan((90 * SG_DEGREES_TO_RADIANS)/2);
return;
}*/
}
void FGAIFlightPlan::setLeadDistance(double distance_ft){
lead_distance_ft = distance_ft;
}
double FGAIFlightPlan::getBearing(FGAIWaypoint* first, FGAIWaypoint* second) const
{
return SGGeodesy::courseDeg(first->getPos(), second->getPos());
}
double FGAIFlightPlan::getBearing(const SGGeod& aPos, FGAIWaypoint* wp) const
{
return SGGeodesy::courseDeg(aPos, wp->getPos());
}
void FGAIFlightPlan::deleteWaypoints()
{
for (wpt_vector_iterator i = waypoints.begin(); i != waypoints.end(); ++i)
delete (*i);
waypoints.clear();
wpt_iterator = waypoints.begin();
}
// Delete all waypoints except the last,
// which we will recycle as the first waypoint in the next leg;
void FGAIFlightPlan::resetWaypoints()
{
if (waypoints.begin() == waypoints.end())
return;
FGAIWaypoint* wpt = new FGAIWaypoint;
wpt_vector_iterator i = waypoints.end();
--i;
wpt->setName((*i)->getName());
wpt->setPos((*i)->getPos());
wpt->setCrossat((*i)->getCrossat());
wpt->setGear_down((*i)->getGear_down());
wpt->setFlaps((*i)->getFlaps());
wpt->setSpoilers((*i)->getSpoilers());
wpt->setSpeedBrakes((*i)->getSpeedBrakes());
wpt->setBeaconLight((*i)->getBeaconLight());
wpt->setLandingLight((*i)->getLandingLight());
wpt->setNavLight((*i)->getNavLight());
wpt->setStrobeLight((*i)->getStrobeLight());
wpt->setTaxiLight((*i)->getTaxiLight());
wpt->setFinished(false);
wpt->setOn_ground((*i)->getOn_ground());
SG_LOG(SG_AI, SG_DEBUG, "Recycling waypoint " << wpt->getName());
deleteWaypoints();
pushBackWaypoint(wpt);
}
void FGAIFlightPlan::addWaypoint(FGAIWaypoint* wpt)
{
pushBackWaypoint(wpt);
}
void FGAIFlightPlan::pushBackWaypoint(FGAIWaypoint *wpt)
{
if (!wpt) {
SG_LOG(SG_AI, SG_WARN, "Null WPT added");
}
size_t pos = wpt_iterator - waypoints.begin();
if (waypoints.size()>0) {
double dist = SGGeodesy::distanceM( waypoints.back()->getPos(), wpt->getPos());
if( dist == 0 ) {
SG_LOG(SG_AI, SG_DEBUG, "Double WP : \t" << wpt->getName() << " not added ");
} else {
waypoints.push_back(wpt);
SG_LOG(SG_AI, SG_BULK, "Added WP : \t" << setprecision(12) << wpt->getName() << "\t" << wpt->getPos() << "\t" << wpt->getSpeed());
}
} else {
waypoints.push_back(wpt);
SG_LOG(SG_AI, SG_BULK, "Added WP : \t" << setprecision(12) << wpt->getName() << "\t" << wpt->getPos() << "\t" << wpt->getSpeed());
}
// std::vector::push_back invalidates waypoints
// so we should restore wpt_iterator after push_back
// (or it could be an index in the vector)
wpt_iterator = waypoints.begin() + pos;
}
// Start flightplan over from the beginning
void FGAIFlightPlan::restart()
{
wpt_iterator = waypoints.begin();
}
int FGAIFlightPlan::getRouteIndex(int i) {
if ((i > 0) && (i < (int)waypoints.size())) {
return waypoints[i]->getRouteIndex();
}
else
return 0;
}
double FGAIFlightPlan::checkTrackLength(const string& wptName) const {
// skip the first two waypoints: first one is behind, second one is partially done;
double trackDistance = 0;
wpt_vector_iterator wptvec = waypoints.begin();
++wptvec;
++wptvec;
while ((wptvec != waypoints.end())) {
if (*wptvec!=nullptr && (!((*wptvec)->contains(wptName)))) {
break;
}
trackDistance += (*wptvec)->getTrackLength();
++wptvec;
}
if (wptvec == waypoints.end()) {
trackDistance = 0; // name not found
}
return trackDistance;
}
void FGAIFlightPlan::shortenToFirst(unsigned int number, string name)
{
while (waypoints.size() > number + 3) {
eraseLastWaypoint();
}
(waypoints.back())->setName((waypoints.back())->getName() + name);
}
void FGAIFlightPlan::setGate(const ParkingAssignment& pka)
{
gate = pka;
}
FGParking* FGAIFlightPlan::getParkingGate()
{
return gate.parking();
}
FGAirportRef FGAIFlightPlan::departureAirport() const
{
return departure;
}
FGAirportRef FGAIFlightPlan::arrivalAirport() const
{
return arrival;
}
FGAIFlightPlan* FGAIFlightPlan::createDummyUserPlan()
{
FGAIFlightPlan* fp = new FGAIFlightPlan;
fp->isValid = false;
return fp;
}
bool FGAIFlightPlan::empty() const
{
return waypoints.empty();
}

View File

@@ -0,0 +1,329 @@
// FGAIFlightPlan - class for loading and storing AI flight plans
// Written by David Culp, started May 2004
// - davidculp2@comcast.net
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation; either version 2 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
#pragma once
#include <vector>
#include <string>
#include <Airports/dynamics.hxx>
#include <Navaids/positioned.hxx>
#include <simgear/compiler.h>
#include <simgear/math/SGMath.hxx>
#include <simgear/structure/SGSharedPtr.hxx>
#include <simgear/structure/exception.hxx>
// forward decls
class SGPath;
class FGAIWaypoint {
private:
std::string name = "unnamed";
SGGeod pos;
double speed = 0.0;
double crossat = 0.0;
bool finished = false;
bool gear_down = true;
double flaps = 0.0;
double spoilers = 0.0;
double speedbrakes = 0.0;
bool on_ground;
int routeIndex; // For AI/ATC purposes;
double time_sec;
double trackLength = 0.0; // distance from previous FGAIWaypoint (for AI purposes);
std::string time;
bool beacon_light = false;
bool landing_light = false;
bool nav_light = false;
bool strobe_light = false;
bool taxi_light = false;
bool cabin_light = false;
public:
FGAIWaypoint();
virtual ~FGAIWaypoint() {};
void setName (const std::string& nam) { name = nam; };
void setLatitude (double lat);
void setLongitude (double lon);
void setAltitude (double alt);
void setPos (const SGGeod& aPos) { pos = aPos; }
void setSpeed (double spd) { speed = spd; };
void setCrossat (double val) { crossat = val; };
void setFinished (bool fin) { finished = fin; };
void setGear_down (bool grd) { gear_down = grd; };
void setFlaps (double val) { flaps = val; };
void setSpoilers (double val) { spoilers = val; };
void setSpeedBrakes (double val) { speedbrakes = val; };
void setOn_ground (bool grn) { on_ground = grn; };
void setRouteIndex (int rte) { routeIndex = rte; };
void setTime_sec (double ts ) { time_sec = ts; };
void setTrackLength (double tl ) { trackLength = tl; };
void setTime (const std::string& tme) { time = tme; };
void setLights (bool beacon, bool cabin, bool ldg, bool nav, bool strobe, bool taxi) {
beacon_light = beacon;
cabin_light = cabin;
landing_light = ldg;
nav_light = nav;
strobe_light = strobe;
taxi_light = taxi; };
// beacon cabin ldg nav strobe taxi
void setPowerDownLights() { setLights(false, true, false, false, false, false); };
void setGroundLights() { setLights(true, true, false, true, false, true ); };
void setCruiseLights() { setLights(true, true, false, true, true, false); };
void setTakeOffLights() { setLights(true, false, true, true, true, false); };
void setApproachLights() { setLights(true, false, true, true, true, false); };
void setBeaconLight (bool beacon) { beacon_light = beacon; };
void setCabinLight (bool cabin) { cabin_light = cabin; };
void setLandingLight (bool ldg) { landing_light = ldg; };
void setNavLight (bool nav) { nav_light = nav; };
void setStrobeLight (bool strobe) { strobe_light = strobe; };
void setTaxiLight (bool taxi) { taxi_light = taxi; };
bool contains(const std::string& name);
const std::string& getName() { return name; };
const SGGeod& getPos () { return pos; };
double getLatitude ();
double getLongitude ();
double getAltitude ();
double getSpeed () { return speed; };
double getCrossat () { return crossat; };
bool getGear_down () { return gear_down; };
double getFlaps () { return flaps; };
double getSpoilers () { return spoilers; };
double getSpeedBrakes() { return speedbrakes; };
bool getOn_ground () { return on_ground; };
bool getInAir () { return ! on_ground; };
int getRouteIndex () { return routeIndex; };
bool isFinished () { return finished; };
double getTime_sec () { return time_sec; };
double getTrackLength() { return trackLength; };
const std::string& getTime () { return time; };
bool getBeaconLight() { return beacon_light; };
bool getCabinLight() { return cabin_light; };
bool getLandingLight() { return landing_light; };
bool getNavLight() { return nav_light; };
bool getStrobeLight() { return strobe_light;};
bool getTaxiLight() { return taxi_light; };
};
class FGAIFlightPlan {
public:
FGAIFlightPlan();
FGAIFlightPlan(const std::string& filename);
FGAIFlightPlan(FGAIAircraft *,
const std::string& p,
double course,
time_t start,
time_t remainingTime,
FGAirport *dep,
FGAirport *arr,
bool firstLeg,
double radius,
double alt,
double lat,
double lon,
double speed,
const std::string& fltType,
const std::string& acType,
const std::string& airline);
virtual ~FGAIFlightPlan();
/**
@brief create a neatrly empty FlightPlan for the user aircraft, based
on the current position and route-manager data.
*/
static FGAIFlightPlan* createDummyUserPlan();
/**
@brief read a flight-plan from a file.
All current contents of the flight-plan are replaxced, and the current waypoint is reset to the beginning
*/
bool readFlightplan(const SGPath& file);
bool readFlightplan(std::istream& stream, const sg_location& loc = sg_location{});
FGAIWaypoint* getPreviousWaypoint( void ) const;
FGAIWaypoint* getCurrentWaypoint( void ) const;
FGAIWaypoint* getNextWaypoint( void ) const;
int getNextTurnAngle( void ) const;
void IncrementWaypoint( bool erase );
void DecrementWaypoint();
double getDistanceToGo(double lat, double lon, FGAIWaypoint* wp) const;
int getLeg () const { return leg;};
/** Set lead_distance_ft*/
void setLeadDistance(double speed, double bearing, FGAIWaypoint* current, FGAIWaypoint* next);
/** Set lead_distance_ft*/
void setLeadDistance(double distance_ft);
double getLeadDistance( void ) const {return lead_distance_ft;}
double getBearing(FGAIWaypoint* previous, FGAIWaypoint* next) const;
double getBearing(const SGGeod& aPos, FGAIWaypoint* next) const;
double checkTrackLength(const std::string& wptName) const;
time_t getStartTime() const { return start_time; }
time_t getArrivalTime() const { return arrivalTime; }
bool create(FGAIAircraft *, FGAirport *dep, FGAirport *arr, int leg, double alt, double speed, double lat, double lon,
bool firstLeg, double radius, const std::string& fltType, const std::string& aircraftType, const std::string& airline, double distance);
bool createPushBack(FGAIAircraft *, bool, FGAirport*, double radius, const std::string&, const std::string&, const std::string&);
bool createTakeOff(FGAIAircraft *, bool, FGAirport *, const SGGeod& pos, double speed, const std::string& flightType);
void setLeg(int val) {
SG_LOG(SG_AI, SG_BULK, "Set Leg " << leg);
leg = val;
}
void setTime(time_t st) { start_time = st; }
double getLeadInAngle() const { return leadInAngle; }
const std::string& getRunway() const;
void setRepeat(bool r) { repeat = r; }
bool getRepeat(void) const { return repeat; }
void restart(void);
int getNrOfWayPoints() { return waypoints.size(); }
int getRouteIndex(int i); // returns the AI related index of this current routes.
const std::string& getRunway() { return activeRunway; }
bool isActive(time_t time) {return time >= this->getStartTime();}
void incrementLeg() {
SG_LOG(SG_AI, SG_BULK, "Increment Leg " << leg);
leg++;
};
void setRunway(const std::string& rwy) { activeRunway = rwy; };
const char* getRunwayClassFromTrafficType(const std::string& fltType);
void addWaypoint(FGAIWaypoint* wpt);
void setName(const std::string& n) { name = n; };
const std::string& getName() { return name; };
void setSID(FGAIFlightPlan* fp) { sid = fp;};
FGAIFlightPlan* getSID() { return sid; };
FGAIWaypoint *getWayPoint(int i) { return waypoints[i]; };
FGAIWaypoint* getLastWaypoint();
void shortenToFirst(unsigned int number, std::string name);
void setGate(const ParkingAssignment& pka);
FGParking* getParkingGate();
FGAirportRef departureAirport() const;
FGAirportRef arrivalAirport() const;
bool empty() const;
private:
FGAIFlightPlan *sid;
typedef std::vector <FGAIWaypoint*> wpt_vector_type;
typedef wpt_vector_type::const_iterator wpt_vector_iterator;
wpt_vector_type waypoints;
wpt_vector_iterator wpt_iterator;
bool repeat;
double distance_to_go = 0;
//FIXME ft
double lead_distance_ft = 0;
double leadInAngle = 0;
double nextTurnAngle = 0;
time_t start_time;
time_t arrivalTime; // For AI/ATC purposes.
int leg = 0;
ParkingAssignment gate;
FGTaxiNodeRef lastNodeVisited;
std::string activeRunway;
std::string name;
bool isValid;
FGAirportRef departure, arrival;
void createPushBackFallBack(FGAIAircraft *, bool, FGAirport*, double radius, const std::string&, const std::string&, const std::string&);
bool createClimb(FGAIAircraft *, bool, FGAirport *, FGAirport* arrival, double, double, const std::string&);
bool createCruise(FGAIAircraft *, bool, FGAirport*, FGAirport*, const SGGeod& current, double, double, const std::string&);
bool createDescent(FGAIAircraft *, FGAirport *, const SGGeod& current, double speed, double alt,const std::string&, double distance);
bool createLanding(FGAIAircraft *, FGAirport *, const std::string&);
bool createParking(FGAIAircraft *, FGAirport *, double radius);
void deleteWaypoints();
void resetWaypoints();
void eraseLastWaypoint();
void pushBackWaypoint(FGAIWaypoint *wpt);
/**Create an arc flightplan around a center from startAngle to endAngle.*/
void createArc(FGAIAircraft *ac, const SGGeod& center, int startAngle, int endAngle, int increment, int radius, double aElev, double aSpeed, const char* pattern);
void createLine(FGAIAircraft *ac, const SGGeod& startPoint, double azimuth, double dist, double dAlt, double vDescent, const char* pattern);
bool createLandingTaxi(FGAIAircraft *, FGAirport *apt, double radius, const std::string& fltType, const std::string& acType, const std::string& airline);
void createDefaultLandingTaxi(FGAIAircraft *, FGAirport* aAirport);
void createDefaultTakeoffTaxi(FGAIAircraft *, FGAirport* aAirport, FGRunway* aRunway);
bool createTakeoffTaxi(FGAIAircraft *, bool firstFlight, FGAirport *apt, double radius, const std::string& fltType, const std::string& acType, const std::string& airline);
double getTurnRadius(double, bool);
FGAIWaypoint* createOnGround(FGAIAircraft *, const std::string& aName, const SGGeod& aPos, double aElev, double aSpeed);
FGAIWaypoint* createOnRunway(FGAIAircraft *, const std::string& aName, const SGGeod& aPos, double aElev, double aSpeed);
FGAIWaypoint* createInAir(FGAIAircraft *, const std::string& aName, const SGGeod& aPos, double aElev, double aSpeed);
FGAIWaypoint* cloneWithPos(FGAIAircraft *, FGAIWaypoint* aWpt, const std::string& aName, const SGGeod& aPos);
FGAIWaypoint* clone(FGAIWaypoint* aWpt);
//void createCruiseFallback(bool, FGAirport*, FGAirport*, double, double, double, double);
void evaluateRoutePart(double deplat, double deplon, double arrlat, double arrlon);
/**
* look for and parse an PropertyList flight-plan file - essentially
* a flat list waypoint objects, encoded to properties
*/
bool parseProperties(const std::string& filename);
/**
* Creates the waypoints for this Flightplan.
*/
void createWaypoints(FGAIAircraft *ac,
double course,
time_t start,
time_t remainingTime,
FGAirport *dep,
FGAirport *arr,
bool firstLeg,
double radius,
double alt,
double lat,
double lon,
double speed,
const std::string& fltType,
const std::string& acType,
const std::string& airline);
public:
wpt_vector_iterator getFirstWayPoint() { return waypoints.begin(); };
wpt_vector_iterator getLastWayPoint() { return waypoints.end(); };
bool isValidPlan() { return isValid; };
};

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,335 @@
/******************************************************************************
* AIFlightPlanCreateCruise.cxx
* Written by Durk Talsma, started February, 2006.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
*
*
**************************************************************************/
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif
#include <fstream>
#include <Airports/airport.hxx>
#include <Airports/runways.hxx>
#include <Airports/dynamics.hxx>
#include <Environment/environment_mgr.hxx>
#include <Environment/environment.hxx>
#include <Traffic/Schedule.hxx>
#include "AIFlightPlan.hxx"
#include "AIAircraft.hxx"
#include "performancedata.hxx"
using std::string;
/*
void FGAIFlightPlan::evaluateRoutePart(double deplat,
double deplon,
double arrlat,
double arrlon)
{
// First do a prescan of all the waypoints that are within a reasonable distance of the
// ideal route.
intVec nodes;
int tmpNode, prevNode;
SGGeoc dep(SGGeoc::fromDegM(deplon, deplat, 100.0));
SGGeoc arr(SGGeoc::fromDegM(arrlon, arrlat, 100.0));
SGVec3d a = SGVec3d::fromGeoc(dep);
SGVec3d nb = normalize(SGVec3d::fromGeoc(arr));
SGVec3d na = normalize(a);
SGVec3d _cross = cross(nb, na);
double angle = acos(dot(na, nb));
const double angleStep = 0.05 * SG_DEGREES_TO_RADIANS;
tmpNode = 0;
for (double ang = 0.0; ang < angle; ang += angleStep)
{
SGQuatd q = SGQuatd::fromAngleAxis(ang, _cross);
SGGeod geod = SGGeod::fromCart(q.transform(a));
prevNode = tmpNode;
tmpNode = globals->get_airwaynet()->findNearestNode(geod);
FGNode* node = globals->get_airwaynet()->findNode(tmpNode);
if ((tmpNode != prevNode) && (SGGeodesy::distanceM(geod, node->getPosition()) < 25000)) {
nodes.push_back(tmpNode);
}
}
intVecIterator i = nodes.begin();
intVecIterator j = nodes.end();
while (i != nodes.end())
{
j = nodes.end();
while (j != i)
{
j--;
FGAirRoute routePart = globals->get_airwaynet()->findShortestRoute(*i, *j);
if (!(routePart.empty()))
{
airRoute.add(routePart);
i = j;
break;
}
}
i++;
}
}
*/
/*
void FGAIFlightPlan::createCruise(bool firstFlight, FGAirport *dep,
FGAirport *arr, double latitude,
double longitude, double speed, double alt)
{
bool useInitialWayPoint = true;
bool useCurrentWayPoint = false;
double heading;
double lat, lon, az;
double lat2, lon2, az2;
double azimuth;
waypoint *wpt;
waypoint *init_waypoint;
intVec ids;
char buffer[32];
SGPath routefile = globals->get_fg_root();
init_waypoint = new waypoint;
init_waypoint->name = "Initial waypoint";
init_waypoint->latitude = latitude;
init_waypoint->longitude = longitude;;
//wpt->altitude = apt->getElevation(); // should maybe be tn->elev too
init_waypoint->altitude = alt;
init_waypoint->speed = 450; //speed;
init_waypoint->crossat = -10000;
init_waypoint->gear_down = false;
init_waypoint->flaps_down= false;
init_waypoint->finished = false;
init_waypoint->on_ground = false;
pushBackWaypoint(init_waypoint);
routefile.append("Data/AI/FlightPlans");
snprintf(buffer, 32, "%s-%s.txt",
dep->getId().c_str(),
arr->getId().c_str());
routefile.append(buffer);
SG_LOG(SG_AI, SG_DEBUG, "trying to read " << routefile.c_str());
//exit(1);
if (routefile.exists())
{
sg_gzifstream in( routefile.str() );
do {
in >> route;
} while (!(in.eof()));
}
else {
//int runwayId = apt->getDynamics()->getGroundNetwork()->findNearestNode(lat2, lon2);
//int startId = globals->get_airwaynet()->findNearestNode(dep->getLatitude(), dep->getLongitude());
//int endId = globals->get_airwaynet()->findNearestNode(arr->getLatitude(), arr->getLongitude());
//FGAirRoute route;
evaluateRoutePart(dep->getLatitude(), dep->getLongitude(),
arr->getLatitude(), arr->getLongitude());
//exit(1);
}
route.first();
int node;
if (route.empty()) {
// if no route could be found, create a direct gps route...
SG_LOG(SG_AI, SG_DEBUG, "still no route found from " << dep->getName() << " to << " << arr->getName());
//exit(1);
} else {
while(route.next(&node))
{
FGNode *fn = globals->get_airwaynet()->findNode(node);
SG_LOG(SG_AI, SG_BULK, "Checking status of each waypoint: " << fn->getIdent());
SGWayPoint first(init_waypoint->longitude,
init_waypoint->latitude,
alt);
SGWayPoint curr (fn->getLongitude(),
fn->getLatitude(),
alt);
SGWayPoint arr (arr->getLongitude(),
arr->getLatitude(),
alt);
double crse, crsDiff;
double dist;
first.CourseAndDistance(arr, &course, &distance);
first.CourseAndDistance(curr, &crse, &dist);
dist *= SG_METER_TO_NM;
// We're only interested in the absolute value of crsDiff
// wich should fall in the 0-180 deg range.
crsDiff = fabs(crse-course);
if (crsDiff > 180)
crsDiff = 360-crsDiff;
// These are the three conditions that we consider including
// in our flight plan:
// 1) current waypoint is less then 100 miles away OR
// 2) curren waypoint is ahead of us, at any distance
SG_LOG(SG_AI, SG_BULK, " Distance : " << dist << " : Course diff " << crsDiff
<< " crs to dest : " << course
<< " crs to wpt : " << crse);
if ((dist > 20.0) && (crsDiff > 90.0))
{
//useWpt = false;
// Once we start including waypoints, we have to continue, even though
// one of the following way point would suffice.
// so once is the useWpt flag is set to true, we cannot reset it to false.
SG_LOG(SG_AI, SG_BULK, " discarding ");
// << ": Course difference = " << crsDiff
// << "Course = " << course
// << "crse = " << crse);
}
else {
//i = ids.end()-1;
SG_LOG(SG_AI, SG_BULK, " accepting ")
//ids.pop_back();
wpt = new waypoint;
wpt->name = "Airway"; // fixme: should be the name of the waypoint
wpt->latitude = fn->getLatitude();
wpt->longitude = fn->getLongitude();
//wpt->altitude = apt->getElevation(); // should maybe be tn->elev too
wpt->altitude = alt;
wpt->speed = 450; //speed;
wpt->crossat = -10000;
wpt->gear_down = false;
wpt->flaps_down= false;
wpt->finished = false;
wpt->on_ground = false;
pushBackWaypoint(wpt);
}
if (!(routefile.exists()))
{
route.first();
fstream outf( routefile.c_str(), fstream::out );
while (route.next(&node))
outf << node << endl;
}
}
}
arr->getDynamics()->getActiveRunway("com", 2, activeRunway);
if (!(globals->get_runways()->search(arr->getId(),
activeRunway,
&rwy)))
{
SG_LOG(SG_AI, SG_WARN, "Failed to find runway for " << arr->getId());
// Hmm, how do we handle a potential error like this?
exit(1);
}
//string test;
//arr->getActiveRunway(string("com"), 1, test);
//exit(1);
SG_LOG(SG_AI, SG_DEBUG, "Altitude = " << alt);
SG_LOG(SG_AI, SG_DEBUG, "Done");
SG_LOG(SG_AI, SG_DEBUG, "Creating cruise to " << arr->getId() << " " << latitude << " " << longitude);
heading = rwy._heading;
azimuth = heading + 180.0;
while ( azimuth >= 360.0 ) { azimuth -= 360.0; }
// Note: This places us at the location of the active
// runway during initial cruise. This needs to be
// fixed later.
geo_direct_wgs_84 ( 0, rwy._lat, rwy._lon, azimuth,
110000,
&lat2, &lon2, &az2 );
wpt = new waypoint;
wpt->name = "BOD"; //wpt_node->getStringValue("name", "END");
wpt->latitude = lat2;
wpt->longitude = lon2;
wpt->altitude = alt;
wpt->speed = speed;
wpt->crossat = alt;
wpt->gear_down = false;
wpt->flaps_down= false;
wpt->finished = false;
wpt->on_ground = false;
pushBackWaypoint(wpt);
}
*/
/*******************************************************************
* CreateCruise
* initialize the Aircraft at the parking location
*
* Note that this is the original version that does not
* do any dynamic route computation.
******************************************************************/
bool FGAIFlightPlan::createCruise(FGAIAircraft *ac, bool firstFlight, FGAirport *dep,
FGAirport *arr,
const SGGeod& current,
double speed,
double alt,
const string& fltType)
{
double vCruise = ac->getPerformance()->vCruise();
FGAIWaypoint *wpt;
//FIXME usually that will be "before" the next WP
wpt = createInAir(ac, "Cruise", current, alt, vCruise);
if (waypoints.size() == 0) {
pushBackWaypoint(wpt);
SG_LOG(SG_AI, SG_DEBUG, "Cruise spawn " << ac->getCallSign());
} else {
SG_LOG(SG_AI, SG_DEBUG, "Cruise start " << ac->getCallSign());
}
//
const string& rwyClass = getRunwayClassFromTrafficType(fltType);
double heading = ac->getTrafficRef()->getCourse();
arr->getDynamics()->getActiveRunway(rwyClass, 2, activeRunway, heading);
if (!arr->hasRunwayWithIdent(activeRunway)) {
SG_LOG(SG_AI, SG_WARN, ac->getCallSign() << " cruise to" << arr->getId() << activeRunway << " not active");
return false;
}
FGRunway* rwy = arr->getRunwayByIdent(activeRunway);
assert( rwy != NULL );
// begin descent 110km out
double distanceOut = arr->getDynamics()->getApproachController()->getRunway(rwy->name())->getApproachDistance(); //12 * SG_NM_TO_METER;
SGGeod beginDescentPoint = rwy->pointOnCenterline(-3*distanceOut);
SGGeod secondaryDescentPoint = rwy->pointOnCenterline(0);
double distanceToRwy = SGGeodesy::distanceM(current, secondaryDescentPoint);
if (distanceToRwy>4*distanceOut) {
FGAIWaypoint *bodWpt = createInAir(ac, "BOD", beginDescentPoint, alt, vCruise);
pushBackWaypoint(bodWpt);
FGAIWaypoint *bod2Wpt = createInAir(ac, "BOD2", secondaryDescentPoint, alt, vCruise);
pushBackWaypoint(bod2Wpt);
} else {
// We are too near. The descent leg takes care of this (teardrop etc)
FGAIWaypoint *bodWpt = createInAir(ac, "BOD", SGGeodesy::direct(current, ac->getTrueHeadingDeg(), 10000), alt, vCruise);
pushBackWaypoint(bodWpt);
FGAIWaypoint *bod2Wpt = createInAir(ac, "BOD2", SGGeodesy::direct(current, ac->getTrueHeadingDeg(), 15000), alt, vCruise);
pushBackWaypoint(bod2Wpt);
}
return true;
}

View File

@@ -0,0 +1,226 @@
/****************************************************************************
* AIFlightPlanCreatePushBack.cxx
* Written by Durk Talsma, started August 1, 2007.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
**************************************************************************/
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif
#include <cstdlib>
#include <cstdio>
#include <simgear/math/sg_geodesy.hxx>
#include <Airports/airport.hxx>
#include <Airports/runways.hxx>
#include <Airports/dynamics.hxx>
#include <Airports/groundnetwork.hxx>
#include <Environment/environment_mgr.hxx>
#include <Environment/environment.hxx>
#include "AIFlightPlan.hxx"
#include "AIAircraft.hxx"
#include "performancedata.hxx"
using std::string;
// TODO: Use James Turner's createOnGround functions.
bool FGAIFlightPlan::createPushBack(FGAIAircraft *ac,
bool firstFlight,
FGAirport *dep,
double radius,
const string& fltType,
const string& aircraftType,
const string& airline)
{
double vTaxi = ac->getPerformance()->vTaxi();
double vTaxiBackward = vTaxi * (-2.0/3.0);
double vTaxiReduced = vTaxi * (2.0/3.0);
// Active runway can be conditionally set by ATC, so at the start of a new flight, this
// must be reset.
activeRunway.clear();
if (!(dep->getDynamics()->getGroundController()->exists())) {
//cerr << "Push Back fallback" << endl;
SG_LOG(SG_AI, SG_DEV_WARN, "No groundcontroller createPushBackFallBack at " << dep->getId());
createPushBackFallBack(ac, firstFlight, dep,
radius, fltType, aircraftType, airline);
return true;
}
if (firstFlight || !dep->getDynamics()->hasParking(gate.parking())) {
// establish the parking position / gate
// if the airport has no parking positions defined, don't log
// the warning below.
if (!dep->getDynamics()->hasParkings()) {
return false;
}
gate = dep->getDynamics()->getAvailableParking(radius, fltType,
aircraftType, airline);
if (!gate.isValid()) {
SG_LOG(SG_AI, SG_DEV_WARN, "Could not find parking for a " <<
aircraftType <<
" of flight type " << fltType <<
" of airline " << airline <<
" at airport " << dep->getId());
return false;
}
}
if (!gate.isValid()) {
SG_LOG(SG_AI, SG_DEV_WARN, "Gate " << gate.parking()->ident() << " not valid createPushBackFallBack at " << dep->getId());
createPushBackFallBack(ac, firstFlight, dep,
radius, fltType, aircraftType, airline);
return true;
}
FGGroundNetwork* groundNet = dep->groundNetwork();
FGParking *parking = gate.parking();
if (parking && parking->getPushBackPoint() != nullptr) {
FGTaxiRoute route = groundNet->findShortestRoute(parking, parking->getPushBackPoint(), false);
SG_LOG(SG_AI, SG_BULK, "Creating Pushback from " << parking->ident() << " to " << parking->getPushBackPoint()->getIndex());
int size = route.size();
if (size < 2) {
SG_LOG(SG_AI, SG_DEV_WARN, "Push back route from gate " << parking->ident() << " has only " << size << " nodes.\n" << "Using " << parking->getPushBackPoint());
}
route.first();
FGTaxiNodeRef node;
int rte;
if (waypoints.size()>0) {
// This will be a parking from a previous leg which still contains the forward speed
waypoints.back()->setSpeed(vTaxiBackward);
}
while (route.next(node, &rte))
{
char buffer[20];
snprintf (buffer, sizeof(buffer), "pushback-%03d", (short)node->getIndex());
FGAIWaypoint *wpt = createOnGround(ac, string(buffer), node->geod(), dep->getElevation(), vTaxiBackward);
/*
if (previous) {
FGTaxiSegment* segment = groundNet->findSegment(previous, node);
wpt->setRouteIndex(segment->getIndex());
} else {
// not on the route yet, make up a unique segment ID
int x = (int) tn->guid();
wpt->setRouteIndex(x);
}*/
wpt->setRouteIndex(rte);
pushBackWaypoint(wpt);
//previous = node;
}
// some special considerations for the last point:
// This will trigger the release of parking
waypoints.back()->setName(string("PushBackPoint"));
waypoints.back()->setSpeed(vTaxi);
ac->setTaxiClearanceRequest(true);
} else { // In case of a push forward departure...
ac->setTaxiClearanceRequest(false);
double az2 = 0.0;
FGTaxiSegment* pushForwardSegment = dep->groundNetwork()->findSegmentByHeading(parking, parking->getHeading());
if (!pushForwardSegment) {
// there aren't any routes for this parking, so create a simple segment straight ahead for 2 meters based on the parking heading
SG_LOG(SG_AI, SG_DEV_WARN, "Gate " << parking->ident() << " at " << dep->getId()
<< " doesn't seem to have pushforward routes associated with it.");
FGAIWaypoint *wpt = createOnGround(ac, string("park"), parking->geod(), dep->getElevation(), vTaxiReduced);
pushBackWaypoint(wpt);
SGGeod coord;
SGGeodesy::direct(parking->geod(), parking->getHeading(), 2.0, coord, az2);
wpt = createOnGround(ac, string("taxiStart"), coord, dep->getElevation(), vTaxiReduced);
pushBackWaypoint(wpt);
return true;
}
lastNodeVisited = pushForwardSegment->getEnd();
double distance = pushForwardSegment->getLength();
double parkingHeading = parking->getHeading();
SG_LOG(SG_AI, SG_BULK, "Creating Pushforward from ID " << pushForwardSegment->getEnd()->getIndex() << " Length : \t" << distance);
// Add the parking if on first leg and not repeat
if (waypoints.size() == 0) {
pushBackWaypoint( createOnGround(ac, parking->getName(), parking->geod(), dep->getElevation(), vTaxiReduced));
}
// Make sure we have at least three WPs
int numSegments = distance>15?(distance/5.0):3;
for (int i = 1; i < numSegments; i++) {
SGGeod pushForwardPt;
SGGeodesy::direct(parking->geod(), parkingHeading,
(((double)i / numSegments) * distance), pushForwardPt, az2);
char buffer[20];
snprintf(buffer, sizeof(buffer), "pushforward-%03d", (short)i);
FGAIWaypoint *wpt = createOnGround(ac, string(buffer), pushForwardPt, dep->getElevation(), vTaxiReduced);
wpt->setRouteIndex(pushForwardSegment->getIndex());
pushBackWaypoint(wpt);
}
// This will trigger the release of parking
waypoints.back()->setName(string("PushBackPoint-pushforward"));
}
return true;
}
/*******************************************************************
* createPushBackFallBack
* This is the backup function for airports that don't have a
* network yet.
******************************************************************/
void FGAIFlightPlan::createPushBackFallBack(FGAIAircraft *ac, bool firstFlight, FGAirport *dep,
double radius,
const string& fltType,
const string& aircraftType,
const string& airline)
{
double az2 = 0.0;
double vTaxi = ac->getPerformance()->vTaxi();
double vTaxiBackward = vTaxi * (-2.0/3.0);
double vTaxiReduced = vTaxi * (2.0/3.0);
double heading = 180.0; // this is a completely arbitrary heading!
FGAIWaypoint *wpt = createOnGround(ac, string("park"), dep->geod(), dep->getElevation(), vTaxiBackward);
pushBackWaypoint(wpt);
SGGeod coord;
SGGeodesy::direct(dep->geod(), heading, 10, coord, az2);
wpt = createOnGround(ac, string("park2"), coord, dep->getElevation(), vTaxiBackward);
pushBackWaypoint(wpt);
SGGeodesy::direct(dep->geod(), heading, 2.2 * radius, coord, az2);
wpt = createOnGround(ac, string("taxiStart"), coord, dep->getElevation(), vTaxiReduced);
pushBackWaypoint(wpt);
}

View File

@@ -0,0 +1,521 @@
// FGAIGroundVehicle - FGAIShip-derived class creates an AI Ground Vehicle
// by adding a ground following utility
//
// Written by Vivian Meazza, started August 2009.
// - vivian.meazza at lineone.net
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation; either version 2 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#include <simgear/sg_inlines.h>
#include <Viewer/view.hxx>
#include <Scenery/scenery.hxx>
#include <Airports/dynamics.hxx>
#include <Main/globals.hxx>
#include "AIGroundVehicle.hxx"
using std::string;
FGAIGroundVehicle::FGAIGroundVehicle() : FGAIShip(object_type::otGroundVehicle)
{
_dt_count = 0.0;
_next_run = 0.0;
_x_offset = 0.0;
_y_offset = 0.0;
invisible = false;
_parent = "";
}
void FGAIGroundVehicle::readFromScenario(SGPropertyNode* scFileNode) {
if (!scFileNode)
return;
FGAIShip::readFromScenario(scFileNode);
setName(scFileNode->getStringValue("name", "groundvehicle"));
setParentName(scFileNode->getStringValue("parent", ""));
setNoRoll(scFileNode->getBoolValue("no-roll", true));
setContactX1offset(scFileNode->getDoubleValue("contact-x1-offset", 0.0));
setContactX2offset(scFileNode->getDoubleValue("contact-x2-offset", 0.0));
setXOffset(scFileNode->getDoubleValue("hitch-x-offset", 35.0));
setYOffset(scFileNode->getDoubleValue("hitch-y-offset", 0.0));
setZOffset(scFileNode->getDoubleValue("hitch-z-offset", 0.0));
setPitchoffset(scFileNode->getDoubleValue("pitch-offset", 0.0));
setRolloffset(scFileNode->getDoubleValue("roll-offset", 0.0));
setYawoffset(scFileNode->getDoubleValue("yaw-offset", 0.0));
setPitchCoeff(scFileNode->getDoubleValue("pitch-coefficient", 0.1));
setElevCoeff(scFileNode->getDoubleValue("elevation-coefficient", 0.25));
setTowAngleGain(scFileNode->getDoubleValue("tow-angle-gain", 1.0));
setTowAngleLimit(scFileNode->getDoubleValue("tow-angle-limit-deg", 2.0));
setInitialTunnel(scFileNode->getBoolValue("tunnel", false));
//we may need these later for towed vehicles
// setSubID(scFileNode->getIntValue("SubID", 0));
// setGroundOffset(scFileNode->getDoubleValue("ground-offset", 0.0));
// setFormate(scFileNode->getBoolValue("formate", true));
}
void FGAIGroundVehicle::bind() {
FGAIShip::bind();
tie("controls/constants/elevation-coeff",
SGRawValuePointer<double>(&_elevation_coeff));
tie("controls/constants/pitch-coeff",
SGRawValuePointer<double>(&_pitch_coeff));
tie("position/ht-AGL-ft",
SGRawValuePointer<double>(&_ht_agl_ft));
tie("hitch/rel-bearing-deg",
SGRawValuePointer<double>(&_relbrg));
tie("hitch/tow-angle-deg",
SGRawValuePointer<double>(&_tow_angle));
tie("hitch/range-ft",
SGRawValuePointer<double>(&_range_ft));
tie("hitch/x-offset-ft",
SGRawValuePointer<double>(&_x_offset));
tie("hitch/y-offset-ft",
SGRawValuePointer<double>(&_y_offset));
tie("hitch/z-offset-ft",
SGRawValuePointer<double>(&_z_offset));
tie("hitch/parent-x-offset-ft",
SGRawValuePointer<double>(&_parent_x_offset));
tie("hitch/parent-y-offset-ft",
SGRawValuePointer<double>(&_parent_y_offset));
tie("hitch/parent-z-offset-ft",
SGRawValuePointer<double>(&_parent_z_offset));
tie("controls/constants/tow-angle/gain",
SGRawValuePointer<double>(&_tow_angle_gain));
tie("controls/constants/tow-angle/limit-deg",
SGRawValuePointer<double>(&_tow_angle_limit));
tie("controls/contact-x1-offset-ft",
SGRawValuePointer<double>(&_contact_x1_offset));
tie("controls/contact-x2-offset-ft",
SGRawValuePointer<double>(&_contact_x2_offset));
}
bool FGAIGroundVehicle::init(ModelSearchOrder searchOrder) {
if (!FGAIShip::init(searchOrder))
return false;
reinit();
return true;
}
void FGAIGroundVehicle::reinit() {
invisible = false;
_limit = 200;
no_roll = true;
props->setStringValue("controls/parent-name", _parent.c_str());
if (setParentNode()){
_parent_x_offset = _selected_ac->getDoubleValue("hitch/x-offset-ft");
_parent_y_offset = _selected_ac->getDoubleValue("hitch/y-offset-ft");
_parent_z_offset = _selected_ac->getDoubleValue("hitch/z-offset-ft");
_hitch_x_offset_m = _selected_ac->getDoubleValue("hitch/x-offset-ft")
* SG_FEET_TO_METER;
_hitch_y_offset_m = _selected_ac->getDoubleValue("hitch/y-offset-ft")
* SG_FEET_TO_METER;
_hitch_z_offset_m = _selected_ac->getDoubleValue("hitch/z-offset-ft")
* SG_FEET_TO_METER;
setParent();
}
FGAIShip::reinit();
}
void FGAIGroundVehicle::update(double dt) {
// SG_LOG(SG_AI, SG_ALERT, "updating GroundVehicle: " << _name );
FGAIShip::update(dt);
RunGroundVehicle(dt);
}
void FGAIGroundVehicle::setNoRoll(bool nr) {
no_roll = nr;
}
void FGAIGroundVehicle::setContactX1offset(double x1) {
_contact_x1_offset = x1;
}
void FGAIGroundVehicle::setContactX2offset(double x2) {
_contact_x2_offset = x2;
}
void FGAIGroundVehicle::setXOffset(double x) {
_x_offset = x;
}
void FGAIGroundVehicle::setYOffset(double y) {
_y_offset = y;
}
void FGAIGroundVehicle::setZOffset(double z) {
_z_offset = z;
}
void FGAIGroundVehicle::setPitchCoeff(double pc) {
_pitch_coeff = pc;
}
void FGAIGroundVehicle::setElevCoeff(double ec) {
_elevation_coeff = ec;
}
void FGAIGroundVehicle::setTowAngleGain(double g) {
_tow_angle_gain = g;
}
void FGAIGroundVehicle::setTowAngleLimit(double l) {
_tow_angle_limit = l;
}
void FGAIGroundVehicle::setElevation(double h, double dt, double coeff){
double c = dt / (coeff + dt);
_elevation_ft = (h * c) + (_elevation_ft * (1 - c));
}
void FGAIGroundVehicle::setPitch(double p, double dt, double coeff){
double c = dt / (coeff + dt);
_pitch_deg = (p * c) + (_pitch_deg * (1 - c));
}
void FGAIGroundVehicle::setTowAngle(double ta, double dt, double coeff){
ta *= _tow_angle_gain;
double factor = -0.0045 * speed + 1;
double limit = _tow_angle_limit * factor;
// cout << "speed "<< speed << " _factor " << _factor<<" " <<_tow_angle_limit<< endl;
_tow_angle = pow(ta,2) * sign(ta) * factor;
SG_CLAMP_RANGE(_tow_angle, -limit, limit);
}
bool FGAIGroundVehicle::getPitch() {
if (!_tunnel){
double vel = props->getDoubleValue("velocities/true-airspeed-kt", 0);
double contact_offset_x1_m = _contact_x1_offset * SG_FEET_TO_METER;
double contact_offset_x2_m = _contact_x2_offset * SG_FEET_TO_METER;
double _z_offset_m = _parent_z_offset * SG_FEET_TO_METER;
SGVec3d front(-contact_offset_x1_m, 0, 0);
SGVec3d rear(-contact_offset_x2_m, 0, 0);
SGVec3d Front = getCartPosAt(front);
SGVec3d Rear = getCartPosAt(rear);
SGGeod geodFront = SGGeod::fromCart(Front);
SGGeod geodRear = SGGeod::fromCart(Rear);
double front_elev_m = 0;
double rear_elev_m = 0;
double elev_front = 0;
double elev_rear = 0;
if (globals->get_scenery()->get_elevation_m(SGGeod::fromGeodM(geodFront, 3000),
elev_front, NULL, 0)){
front_elev_m = elev_front + _z_offset_m;
} else
return false;
if (globals->get_scenery()->get_elevation_m(SGGeod::fromGeodM(geodRear, 3000),
elev_rear, NULL, 0)){
rear_elev_m = elev_rear;
} else
return false;
if (vel >= 0){
double diff = front_elev_m - rear_elev_m;
_pitch = atan2 (diff,
fabs(contact_offset_x1_m) + fabs(contact_offset_x2_m)) * SG_RADIANS_TO_DEGREES;
_elevation = (rear_elev_m + diff/2) * SG_METER_TO_FEET;
} else {
double diff = rear_elev_m - front_elev_m;
_pitch = atan2 (diff,
fabs(contact_offset_x1_m) + fabs(contact_offset_x2_m)) * SG_RADIANS_TO_DEGREES;
_elevation = (front_elev_m + diff/2) * SG_METER_TO_FEET;
_pitch = -_pitch;
}
} else {
if (prev->getAltitude() == 0 || curr->getAltitude() == 0)
return false;
static double distance;
static double curr_alt;
static double prev_alt;
if (_new_waypoint){
curr_alt = curr->getAltitude();
prev_alt = prev->getAltitude();
static double d_alt = (curr_alt - prev_alt) * SG_METER_TO_FEET;
distance = SGGeodesy::distanceM(SGGeod::fromDeg(prev->getLongitude(), prev->getLatitude()),
SGGeod::fromDeg(curr->getLongitude(), curr->getLatitude()));
_pitch = atan2(d_alt, distance * SG_METER_TO_FEET) * SG_RADIANS_TO_DEGREES;
}
double distance_to_go = SGGeodesy::distanceM(SGGeod::fromDeg(pos.getLongitudeDeg(), pos.getLatitudeDeg()),
SGGeod::fromDeg(curr->getLongitude(), curr->getLatitude()));
if (distance_to_go > distance)
_elevation = prev_alt;
else
_elevation = curr_alt - (tan(_pitch * SG_DEGREES_TO_RADIANS) * distance_to_go * SG_METER_TO_FEET);
}
return true;
}
void FGAIGroundVehicle::setParent(){
double lat = _selected_ac->getDoubleValue("position/latitude-deg");
double lon = _selected_ac->getDoubleValue("position/longitude-deg");
double elevation = _selected_ac->getDoubleValue("position/altitude-ft");
_selectedpos.setLatitudeDeg(lat);
_selectedpos.setLongitudeDeg(lon);
_selectedpos.setElevationFt(elevation);
_parent_speed = _selected_ac->getDoubleValue("velocities/true-airspeed-kt");
SGVec3d rear_hitch(-_hitch_x_offset_m, _hitch_y_offset_m, 0);
SGVec3d RearHitch = getCartHitchPosAt(rear_hitch);
SGGeod rearpos = SGGeod::fromCart(RearHitch);
double user_lat = rearpos.getLatitudeDeg();
double user_lon = rearpos.getLongitudeDeg();
double range, bearing;
calcRangeBearing(pos.getLatitudeDeg(), pos.getLongitudeDeg(),
user_lat, user_lon, range, bearing);
_range_ft = range * 6076.11549;
_relbrg = calcRelBearingDeg(bearing, hdg);
}
void FGAIGroundVehicle::calcRangeBearing(double lat, double lon, double lat2, double lon2,
double &range, double &bearing) const
{
// calculate the bearing and range of the second pos from the first
double az2, distance;
geo_inverse_wgs_84(lat, lon, lat2, lon2, &bearing, &az2, &distance);
range = distance * SG_METER_TO_NM;
}
SGVec3d FGAIGroundVehicle::getCartHitchPosAt(const SGVec3d& _off) const {
double hdg = _selected_ac->getDoubleValue("orientation/true-heading-deg");
double pitch = _selected_ac->getDoubleValue("orientation/pitch-deg");
double roll = _selected_ac->getDoubleValue("orientation/roll-deg");
// Transform that one to the horizontal local coordinate system.
SGQuatd hlTrans = SGQuatd::fromLonLat(_selectedpos);
// and postrotate the orientation of the AIModel wrt the horizontal
// local frame
hlTrans *= SGQuatd::fromYawPitchRollDeg(hdg, pitch, roll);
// The offset converted to the usual body fixed coordinate system
// rotated to the earth fiexed coordinates axis
SGVec3d off = hlTrans.backTransform(_off);
// Add the position offset of the AIModel to gain the earth centered position
SGVec3d cartPos = SGVec3d::fromGeod(_selectedpos);
return cartPos + off;
}
void FGAIGroundVehicle::AdvanceFP(){
double count = 0;
string parent_next_name =_selected_ac->getStringValue("waypoint/name-next");
while(fp->getNextWaypoint() != 0 && fp->getNextWaypoint()->getName() != "END" && count < 5){
SG_LOG(SG_AI, SG_DEBUG, "AIGroundVeh1cle: " << _name
<<" advancing waypoint to: " << parent_next_name);
if (fp->getNextWaypoint()->getName() == parent_next_name){
SG_LOG(SG_AI, SG_DEBUG, "AIGroundVeh1cle: " << _name
<< " not setting waypoint already at: " << fp->getNextWaypoint()->getName());
return;
}
prev = curr;
fp->IncrementWaypoint(false);
curr = fp->getCurrentWaypoint();
next = fp->getNextWaypoint();
if (fp->getNextWaypoint()->getName() == parent_next_name){
SG_LOG(SG_AI, SG_DEBUG, "AIGroundVeh1cle: " << _name
<< " waypoint set to: " << fp->getNextWaypoint()->getName());
return;
}
count++;
}// end while loop
while(fp->getPreviousWaypoint() != 0 && fp->getPreviousWaypoint()->getName() != "END"
&& count > -10){
SG_LOG(SG_AI, SG_DEBUG, "AIGroundVeh1cle: " << _name
<< " retreating waypoint to: " << parent_next_name
<< " at: " << fp->getNextWaypoint()->getName());
if (fp->getNextWaypoint()->getName() == parent_next_name){
SG_LOG(SG_AI, SG_DEBUG, "AIGroundVeh1cle: " << _name
<< " not setting waypoint already at:" << fp->getNextWaypoint()->getName() );
return;
}
prev = curr;
fp->DecrementWaypoint();
curr = fp->getCurrentWaypoint();
next = fp->getNextWaypoint();
if (fp->getNextWaypoint()->getName() == parent_next_name){
SG_LOG(SG_AI, SG_DEBUG, "AIGroundVeh1cle: " << _name
<< " waypoint set to: " << fp->getNextWaypoint()->getName());
return;
}
count--;
}// end while loop
}
void FGAIGroundVehicle::setTowSpeed(){
//double diff = _range_ft - _x_offset;
double x = 0;
if (_range_ft > _x_offset * 3) x = 50;
if (_relbrg < -90 || _relbrg > 90) {
setSpeed(_parent_speed - 5 - x);
//cout << _name << " case 1r _relbrg spd - 5 " << _relbrg << " " << diff << endl;
} else if (_range_ft > _x_offset + 0.25) {
setSpeed(_parent_speed + 1 + x);
//cout << _name << " case 2r _relbrg spd + 1 " << _relbrg << " "
// << diff << " range " << _range_ft << endl;
} else if (_range_ft < _x_offset - 0.25) {
setSpeed(_parent_speed - 1 - x);
//cout << _name << " case 3r _relbrg spd - 2 " << _relbrg << " "
// << diff << " " << _range_ft << endl;
} else {
setSpeed(_parent_speed);
//cout << _name << " else r _relbrg " << _relbrg << " " << diff << endl;
}
}
void FGAIGroundVehicle::RunGroundVehicle(double dt){
_dt_count += dt;
///////////////////////////////////////////////////////////////////////////
// Check execution time (currently once every 0.05 sec or 20 fps)
// Add a bit of randomization to prevent the execution of all flight plans
// in synchrony, which can add significant periodic framerate flutter.
// Randomization removed to get better appearance
///////////////////////////////////////////////////////////////////////////
//cout << "_start_sec " << _start_sec << " time_sec " << time_sec << endl;
if (_dt_count < _next_run)
return;
_next_run = 0.05 /*+ (0.015 * sg_random())*/;
if (getPitch()){
setElevation(_elevation, _dt_count, _elevation_coeff);
ClimbTo(_elevation_ft);
setPitch(_pitch, _dt_count, _pitch_coeff);
PitchTo(_pitch_deg);
}
if(_parent == ""){
AccelTo(prev->getSpeed());
_dt_count = 0;
return;
}
setParent();
string parent_next_name = _selected_ac->getStringValue("waypoint/name-next");
bool parent_waiting = _selected_ac->getBoolValue("waypoint/waiting");
//bool parent_restart = _selected_ac->getBoolValue("controls/restart");
if (parent_next_name == "END" && fp->getNextWaypoint()->getName() != "END" ){
SG_LOG(SG_AI, SG_DEBUG, "AIGroundVeh1cle: " << _name
<< " setting END: getting new waypoints ");
AdvanceFP();
setWPNames();
setTunnel(_initial_tunnel);
if(_restart) _missed_count = 200;
/*} else if (parent_next_name == "WAIT" && fp->getNextWaypoint()->name != "WAIT" ){*/
} else if (parent_waiting && !_waiting){
SG_LOG(SG_AI, SG_DEBUG, "AIGroundVeh1cle: " << _name
<< " setting WAIT/WAITUNTIL: getting new waypoints ");
AdvanceFP();
setWPNames();
_waiting = true;
} else if (parent_next_name != "WAIT" && fp->getNextWaypoint()->getName() == "WAIT"){
SG_LOG(SG_AI, SG_DEBUG, "AIGroundVeh1cle: " << _name
<< " wait done: getting new waypoints ");
_waiting = false;
_wait_count = 0;
fp->IncrementWaypoint(false);
next = fp->getNextWaypoint();
if (next->getName() == "WAITUNTIL" || next->getName() == "WAIT"
|| next->getName() == "END"){
} else {
prev = curr;
fp->IncrementWaypoint(false);
curr = fp->getCurrentWaypoint();
next = fp->getNextWaypoint();
}
setWPNames();
} else if (_range_ft > (_x_offset +_parent_x_offset)* 4
){
SG_LOG(SG_AI, SG_ALERT, "AIGroundVeh1cle: " << _name
<< " rescue: reforming train " << _range_ft
);
setTowAngle(0, dt, 1);
setSpeed(_parent_speed + (10 * sign(_parent_speed)));
} else if (_parent_speed > 1){
setTowSpeed();
setTowAngle(_relbrg, dt, 1);
} else if (_parent_speed < -1){
setTowSpeed();
if (_relbrg < 0)
setTowAngle(-(180 - (360 + _relbrg)), dt, 1);
else
setTowAngle(-(180 - _relbrg), dt, 1);
} else
setSpeed(_parent_speed);
// FGAIShip::update(_dt_count);
_dt_count = 0;
}
// end AIGroundvehicle

View File

@@ -0,0 +1,113 @@
// FGAIGroundVehicle - FGAIShip-derived class creates an AI Ground Vehicle
// by adding a ground following utility
//
// Written by Vivian Meazza, started August 2009.
// - vivian.meazza at lineone.net
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation; either version 2 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#pragma once
#include <cmath>
#include <string_view>
#include <vector>
#include <simgear/structure/SGSharedPtr.hxx>
#include <simgear/scene/material/mat.hxx>
#include "AIShip.hxx"
#include "AIManager.hxx"
#include "AIBase.hxx"
class FGAIGroundVehicle : public FGAIShip {
public:
FGAIGroundVehicle();
virtual ~FGAIGroundVehicle() = default;
string_view getTypeString(void) const override { return "groundvehicle"; }
void readFromScenario(SGPropertyNode* scFileNode) override;
bool init(ModelSearchOrder searchOrder) override;
void bind() override;
void reinit() override;
void update(double dt) override;
private:
void setNoRoll(bool nr);
void setContactX1offset(double x1);
void setContactX2offset(double x2);
void setXOffset(double x);
void setYOffset(double y);
void setZOffset(double z);
void setPitchCoeff(double pc);
void setElevCoeff(double ec);
void setTowAngleGain(double g);
void setTowAngleLimit(double l);
void setElevation(double _elevation, double dt, double _elevation_coeff);
void setPitch(double _pitch, double dt, double _pitch_coeff);
void setTowAngle(double _relbrg, double dt, double _towangle_coeff);
void setTrainSpeed(double s, double dt, double coeff);
void setParent();
void AdvanceFP();
void setTowSpeed();
void RunGroundVehicle(double dt);
bool getGroundElev(SGGeod inpos);
bool getPitch();
SGVec3d getCartHitchPosAt(const SGVec3d& off) const;
void calcRangeBearing(double lat, double lon, double lat2, double lon2,
double &range, double &bearing) const;
SGGeod _selectedpos;
bool _solid = true; // if true ground is solid for FDMs
double _load_resistance = 0.0; // ground load resistanc N/m^2
double _frictionFactor = 0.0; // dimensionless modifier for Coefficient of Friction
double _elevation = 0.0;
double _elevation_coeff = 0.0;
double _ht_agl_ft = 0.0;
double _tow_angle_gain = 0.0;
double _tow_angle_limit = 0.0;
double _contact_x1_offset = 0.0;
double _contact_x2_offset = 0.0;
double _contact_z_offset = 0.0;
double _pitch = 0.0;
double _pitch_coeff = 0.0;
double _pitch_deg = 0.0;
double _speed_coeff = 0.0;
double _speed_kt = 0.0;
double _range_ft = 0.0;
double _relbrg = 0.0;
double _parent_speed = 0.0;
double _parent_x_offset = 0.0;
double _parent_y_offset = 0.0;
double _parent_z_offset = 0.0;
double _hitch_x_offset_m = 0.0;
double _hitch_y_offset_m = 0.0;
double _hitch_z_offset_m = 0.0;
double _break_count = 0.0;
};

783
src/AIModel/AIManager.cxx Normal file
View File

@@ -0,0 +1,783 @@
// AIManager.cxx Based on David Luff's AIMgr:
// - a global management type for AI objects
//
// Written by David Culp, started October 2003.
// - davidculp2@comcast.net
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation; either version 2 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#include <algorithm>
#include <cstring>
#include <simgear/debug/ErrorReportingCallback.hxx>
#include <simgear/math/sg_geodesy.hxx>
#include <simgear/props/props_io.hxx>
#include <simgear/sg_inlines.h>
#include <simgear/structure/SGBinding.hxx>
#include <simgear/structure/commands.hxx>
#include <simgear/structure/exception.hxx>
#include <Add-ons/AddonManager.hxx>
#include <Airports/airport.hxx>
#include <Main/fg_props.hxx>
#include <Main/globals.hxx>
#include <Main/sentryIntegration.hxx>
#include <Scripting/NasalSys.hxx>
#include "AIManager.hxx"
#include "AIAircraft.hxx"
#include "AIShip.hxx"
#include "AIBallistic.hxx"
#include "AIStorm.hxx"
#include "AIThermal.hxx"
#include "AICarrier.hxx"
#include "AIStatic.hxx"
#include "AIMultiplayer.hxx"
#include "AITanker.hxx"
#include "AIWingman.hxx"
#include "AIGroundVehicle.hxx"
#include "AIEscort.hxx"
static bool static_haveRegisteredScenarios = false;
class FGAIManager::Scenario
{
public:
Scenario(FGAIManager* man, const std::string& nm, SGPropertyNode* scenarios) :
_internalName(nm)
{
simgear::ErrorReportContext ec("scenario-name", _internalName);
for (auto scEntry : scenarios->getChildren("entry")) {
FGAIBasePtr ai = man->addObject(scEntry);
if (ai) {
_objects.push_back(ai);
}
} // of scenario entry iteration
SGPropertyNode* nasalScripts = scenarios->getChild("nasal");
if (!nasalScripts) {
return;
}
_unloadScript = nasalScripts->getStringValue("unload");
std::string loadScript = nasalScripts->getStringValue("load");
if (!loadScript.empty()) {
FGNasalSys* nasalSys = globals->get_subsystem<FGNasalSys>();
std::string moduleName = "scenario_" + _internalName;
bool ok = nasalSys->createModule(moduleName.c_str(), moduleName.c_str(),
loadScript.c_str(), loadScript.size(),
nullptr);
if (!ok) {
// TODO: get the Nasal errors logged properly
simgear::reportFailure(simgear::LoadFailure::BadData, simgear::ErrorCode::ScenarioLoad,
"Failed to parse scenario Nasal");
}
}
}
~Scenario()
{
std::for_each(_objects.begin(), _objects.end(),
[](FGAIBasePtr ai) { ai->setDie(true); });
FGNasalSys* nasalSys = globals->get_subsystem<FGNasalSys>();
if (!nasalSys) // happens during shutdown / reset
return;
std::string moduleName = "scenario_" + _internalName;
if (!_unloadScript.empty()) {
nasalSys->createModule(moduleName.c_str(), moduleName.c_str(),
_unloadScript.c_str(), _unloadScript.size(),
nullptr);
}
nasalSys->deleteModule(moduleName.c_str());
}
private:
std::vector<FGAIBasePtr> _objects;
std::string _internalName;
std::string _unloadScript;
};
///////////////////////////////////////////////////////////////////////////////
FGAIManager::FGAIManager() :
cb_ai_bare(SGPropertyChangeCallback<FGAIManager>(this,&FGAIManager::updateLOD,
fgGetNode("/sim/rendering/static-lod/aimp-bare", true))),
cb_ai_detailed(SGPropertyChangeCallback<FGAIManager>(this, &FGAIManager::updateLOD,
fgGetNode("/sim/rendering/static-lod/aimp-detailed", true))),
cb_interior(SGPropertyChangeCallback<FGAIManager>(this, &FGAIManager::updateLOD,
fgGetNode("/sim/rendering/static-lod/aimp-interior", true)))
{
}
FGAIManager::~FGAIManager()
{
std::for_each(ai_list.begin(), ai_list.end(), std::mem_fn(&FGAIBase::unbind));
}
void
FGAIManager::init() {
root = fgGetNode("sim/ai", true);
enabled = root->getNode("enabled", true);
thermal_lift_node = fgGetNode("/environment/thermal-lift-fps", true);
wind_from_east_node = fgGetNode("/environment/wind-from-east-fps",true);
wind_from_north_node = fgGetNode("/environment/wind-from-north-fps",true);
user_altitude_agl_node = fgGetNode("/position/altitude-agl-ft", true);
user_speed_node = fgGetNode("/velocities/uBody-fps", true);
globals->get_commands()->addCommand("load-scenario", this, &FGAIManager::loadScenarioCommand);
globals->get_commands()->addCommand("unload-scenario", this, &FGAIManager::unloadScenarioCommand);
globals->get_commands()->addCommand("add-aiobject", this, &FGAIManager::addObjectCommand);
globals->get_commands()->addCommand("remove-aiobject", this, &FGAIManager::removeObjectCommand);
_environmentVisiblity = fgGetNode("/environment/visibility-m");
_groundSpeedKts_node = fgGetNode("/velocities/groundspeed-kt", true);
// Create an (invisible) AIAircraft representation of the current
// users's aircraft, that mimicks the user aircraft's behavior.
_userAircraft = new FGAIAircraft;
_userAircraft->setCallSign ( fgGetString("/sim/multiplay/callsign") );
_userAircraft->setGeodPos(globals->get_aircraft_position());
_userAircraft->setPerformance("", "jet_transport");
_userAircraft->setHeading(fgGetDouble("/orientation/heading-deg"));
_userAircraft->setSpeed(_groundSpeedKts_node->getDoubleValue());
// radar properties
_simRadarControl = fgGetNode("/sim/controls/radar", true);
if (!_simRadarControl->hasValue()) {
// default to true, but only if not already set
_simRadarControl->setBoolValue(true);
}
_radarRangeNode = fgGetNode("/instrumentation/radar/range", true);
_radarDebugNode = fgGetNode("/instrumentation/radar/debug-mode", true);
// register scenarios if we didn't do it already
registerScenarios();
}
void FGAIManager::registerScenarios(SGPropertyNode_ptr root)
{
if (!root) {
// depending on if we're using a carrier startup, this function may get
// called early or during normal FGAIManager init, so guard against double
// invocation.
// we clear this flag on shudtdown so reset works as expected
if (static_haveRegisteredScenarios)
return;
static_haveRegisteredScenarios = true;
root = globals->get_props();
}
// find all scenarios at standard locations (for driving the GUI)
std::vector<SGPath> scenarioSearchPaths;
scenarioSearchPaths.push_back(globals->get_fg_root() / "AI");
scenarioSearchPaths.push_back(globals->get_fg_home() / "Scenarios");
scenarioSearchPaths.push_back(SGPath(fgGetString("/sim/aircraft-dir")) / "Scenarios");
// add-on scenario directories
const auto& addonsManager = flightgear::addons::AddonManager::instance();
if (addonsManager) {
auto coll = addonsManager->registeredAddons();
std::transform(coll.begin(), coll.end(), std::back_inserter(scenarioSearchPaths),
[](flightgear::addons::AddonRef a) {
return a->getBasePath() / "Scenarios";
});
#if 0
for (auto a : addonsManager->registeredAddons()) {
scenarioSearchPaths.push_back(a->getBasePath() / "Scenarios");
}
#endif
}
SGPropertyNode_ptr scenariosNode = root->getNode("/sim/ai/scenarios", true);
for (auto p : scenarioSearchPaths) {
if (!p.exists())
continue;
simgear::Dir dir(p);
for (auto xmlPath : dir.children(simgear::Dir::TYPE_FILE, ".xml")) {
registerScenarioFile(root, xmlPath);
} // of xml files in the scenario dir iteration
} // of scenario dirs iteration
}
SGPropertyNode_ptr FGAIManager::registerScenarioFile(SGPropertyNode_ptr root, const SGPath& xmlPath)
{
if (!xmlPath.exists()) return {};
auto scenariosNode = root->getNode("/sim/ai/scenarios", true);
SGPropertyNode_ptr sNode;
simgear::ErrorReportContext ectx("scenario-path", xmlPath.utf8Str());
try {
SGPropertyNode_ptr scenarioProps(new SGPropertyNode);
readProperties(xmlPath, scenarioProps);
for (auto xs : scenarioProps->getChildren("scenario")) {
if (!xs->hasChild("name") || !xs->hasChild("description")) {
SG_LOG(SG_AI, SG_DEV_WARN, "Scenario is missing name/description:" << xmlPath);
}
sNode = scenariosNode->addChild("scenario");
const auto bareName = xmlPath.file_base();
sNode->setStringValue("id", bareName);
sNode->setStringValue("path", xmlPath.utf8Str());
if (xs->hasChild("name")) {
sNode->setStringValue("name", xs->getStringValue("name"));
} else {
auto cleanedName = bareName;
// replace _ and - in bareName with spaces
// auto s = simgear::strutils::srep
sNode->setStringValue("name", cleanedName);
}
if (xs->hasChild("description")) {
sNode->setStringValue("description", xs->getStringValue("description"));
}
FGAICarrier::extractCarriersFromScenario(xs, sNode);
} // of scenarios in the XML file
} catch (sg_exception& e) {
SG_LOG(SG_AI, SG_WARN, "Skipping malformed scenario file:" << xmlPath);
simgear::reportFailure(simgear::LoadFailure::BadData, simgear::ErrorCode::ScenarioLoad,
string{"The scenario couldn't be loaded:"} + e.getFormattedMessage(),
e.getLocation());
sNode.reset();
}
return sNode;
}
void
FGAIManager::postinit()
{
// postinit, so that it can access the Nasal subsystem
// scenarios enabled, AI subsystem required
if (!enabled->getBoolValue())
enabled->setBoolValue(true);
// process all scenarios
for (auto n : root->getChildren("scenario")) {
const string& name = n->getStringValue();
if (name.empty())
continue;
if (_scenarios.find(name) != _scenarios.end()) {
SG_LOG(SG_AI, SG_DEV_WARN, "won't load scenario '" << name << "' twice");
continue;
}
SG_LOG(SG_AI, SG_INFO, "loading scenario '" << name << '\'');
loadScenario(name);
}
}
void
FGAIManager::reinit()
{
// shutdown scenarios
unloadAllScenarios();
update(0.0);
std::for_each(ai_list.begin(), ai_list.end(), std::mem_fn(&FGAIBase::reinit));
// (re-)load scenarios
postinit();
}
void
FGAIManager::shutdown()
{
unloadAllScenarios();
for (FGAIBase* ai : ai_list) {
// other subsystems, especially ATC, may have references. This
// lets them detect if the AI object should be skipped
ai->setDie(true);
ai->unbind();
}
ai_list.clear();
_environmentVisiblity.clear();
if (_userAircraft) {
_userAircraft->setDie(true);
// we can't unbind() but we do need to clear these
_userAircraft->clearATCController();
_userAircraft.clear();
}
static_haveRegisteredScenarios = false;
globals->get_commands()->removeCommand("load-scenario");
globals->get_commands()->removeCommand("unload-scenario");
globals->get_commands()->removeCommand("add-aiobject");
globals->get_commands()->removeCommand("remove-aiobject");
}
void
FGAIManager::bind() {
root = globals->get_props()->getNode("ai/models", true);
root->tie("count", SGRawValueMethods<FGAIManager, int>(*this,
&FGAIManager::getNumAiObjects));
}
void
FGAIManager::unbind() {
root->untie("count");
}
void FGAIManager::removeDeadItem(FGAIBase* base)
{
SGPropertyNode *props = base->_getProps();
props->setBoolValue("valid", false);
base->unbind();
// for backward compatibility reset properties, so that aircraft,
// which don't know the <valid> property, keep working
// TODO: remove after a while
props->setIntValue("id", -1);
props->setBoolValue("radar/in-range", false);
props->setIntValue("refuel/tanker", false);
}
void
FGAIManager::update(double dt)
{
// initialize these for finding nearest thermals
range_nearest = 10000.0;
strength = 0.0;
if (!enabled->getBoolValue())
return;
fetchUserState(dt);
// fetch radar state. Ensure we only do this once per frame.
_radarEnabled = _simRadarControl->getBoolValue();
_radarDebugMode = _radarDebugNode->getBoolValue();
_radarRangeM = _radarRangeNode->getDoubleValue() * SG_NM_TO_METER;
// partition the list into dead followed by alive
auto firstAlive =
std::stable_partition(ai_list.begin(), ai_list.end(), std::mem_fn(&FGAIBase::getDie));
// clean up each item and finally remove from the container
for (auto it=ai_list.begin(); it != firstAlive; ++it) {
removeDeadItem(*it);
}
ai_list.erase(ai_list.begin(), firstAlive);
// every remaining item is alive. update them in turn, but guard for
// exceptions, so a single misbehaving AI object doesn't bring down the
// entire subsystem.
for (FGAIBase* base : ai_list) {
try {
if (base->isa(FGAIBase::object_type::otThermal)) {
processThermal(dt, static_cast<FGAIThermal*>(base));
} else {
base->update(dt);
}
} catch (sg_exception& e) {
SG_LOG(SG_AI, SG_WARN, "caught exception updating AI model:" << base->_getName()<< ", which will be killed."
"\n\tError:" << e.getFormattedMessage());
base->setDie(true);
}
} // of live AI objects iteration
thermal_lift_node->setDoubleValue( strength ); // for thermals
}
/** update LOD settings of all AI/MP models */
void
FGAIManager::updateLOD(SGPropertyNode* node)
{
SG_UNUSED(node);
std::for_each(ai_list.begin(), ai_list.end(), std::mem_fn(&FGAIBase::updateLOD));
}
void
FGAIManager::attach(const SGSharedPtr<FGAIBase> &model)
{
string_view typeString = model->getTypeString();
SGPropertyNode* root = globals->get_props()->getNode("ai/models", true);
SGPropertyNode* p;
int i;
// find free index in the property tree, if we have
// more than 10000 mp-aircrafts in the property tree we should optimize the mp-server
for (i = 0; i < 10000; i++) {
p = root->getNode(static_cast<std::string>(typeString), i, false);
if (!p || !p->getBoolValue("valid", false))
break;
if (p->getIntValue("id",-1)==model->getID()) {
p->setStringValue("callsign","***invalid node***"); //debug only, should never set!
}
}
p = root->getNode(static_cast<std::string>(typeString), i, true);
model->setManager(this, p);
ai_list.push_back(model);
model->init(model->getSearchOrder());
model->bind();
p->setBoolValue("valid", true);
}
bool FGAIManager::isVisible(const SGGeod& pos) const
{
double visibility_meters = _environmentVisiblity->getDoubleValue();
return ( dist(globals->get_view_position_cart(), SGVec3d::fromGeod(pos)) ) <= visibility_meters;
}
int
FGAIManager::getNumAiObjects() const
{
return static_cast<int>(ai_list.size());
}
void
FGAIManager::fetchUserState( double dt )
{
globals->get_aircraft_orientation(user_heading, user_pitch, user_roll);
user_speed = user_speed_node->getDoubleValue() * 0.592484;
wind_from_east = wind_from_east_node->getDoubleValue();
wind_from_north = wind_from_north_node->getDoubleValue();
user_altitude_agl = user_altitude_agl_node->getDoubleValue();
_userAircraft->setGeodPos(globals->get_aircraft_position());
_userAircraft->setHeading(user_heading);
_userAircraft->setSpeed(_groundSpeedKts_node->getDoubleValue());
_userAircraft->update(dt);
}
// only keep the results from the nearest thermal
void
FGAIManager::processThermal( double dt, FGAIThermal* thermal ) {
thermal->update(dt);
if ( thermal->_getRange() < range_nearest ) {
range_nearest = thermal->_getRange();
strength = thermal->getStrength();
}
}
bool FGAIManager::loadScenarioCommand(const SGPropertyNode* args, SGPropertyNode *)
{
std::string name = args->getStringValue("name");
if (args->hasChild("load-property")) {
// slightly ugly, to simplify life in the dialogs, make load allow
// loading or unloading based on a bool property.
bool loadIt = fgGetBool(args->getStringValue("load-property"));
if (!loadIt) {
// user actually wants to unload, fine.
return unloadScenario(name);
}
}
if (_scenarios.find(name) != _scenarios.end()) {
SG_LOG(SG_AI, SG_WARN, "scenario '" << name << "' already loaded");
return false;
}
bool ok = loadScenario(name);
if (ok) {
// create /sim/ai node for consistency
SGPropertyNode* scenarioNode = root->addChild("scenario");
scenarioNode->setStringValue(name);
}
return ok;
}
bool FGAIManager::unloadScenarioCommand(const SGPropertyNode * arg, SGPropertyNode * root)
{
SG_UNUSED(root);
std::string name = arg->getStringValue("name");
return unloadScenario(name);
}
bool FGAIManager::addObjectCommand(const SGPropertyNode* arg, const SGPropertyNode* root)
{
SG_UNUSED(root);
if (!arg){
return false;
}
addObject(arg);
return true;
}
FGAIBasePtr FGAIManager::addObject(const SGPropertyNode* definition)
{
const std::string& type = definition->getStringValue("type", "aircraft");
FGAIBase* ai = nullptr;
if (type == "tanker") { // refueling scenarios
ai = new FGAITanker;
} else if (type == "wingman") {
ai = new FGAIWingman;
} else if (type == "aircraft") {
ai = new FGAIAircraft;
} else if (type == "ship") {
ai = new FGAIShip;
} else if (type == "carrier") {
ai = new FGAICarrier;
} else if (type == "groundvehicle") {
ai = new FGAIGroundVehicle;
} else if (type == "escort") {
ai = new FGAIEscort;
} else if (type == "thunderstorm") {
ai = new FGAIStorm;
} else if (type == "thermal") {
ai = new FGAIThermal;
} else if (type == "ballistic") {
ai = new FGAIBallistic;
} else if (type == "static") {
ai = new FGAIStatic;
}
ai->readFromScenario(const_cast<SGPropertyNode*>(definition));
if((ai->isValid())){
attach(ai);
SG_LOG(SG_AI, SG_DEBUG, "attached scenario " << ai->_getName());
}
else{
ai->setDie(true);
SG_LOG(SG_AI, SG_ALERT, "killed invalid scenario " << ai->_getName());
}
return ai;
}
bool FGAIManager::removeObjectCommand(const SGPropertyNode* arg, const SGPropertyNode* root)
{
SG_UNUSED(root);
if (!arg) {
return false;
}
return removeObject(arg);
}
bool FGAIManager::removeObject(const SGPropertyNode* args)
{
int id = args->getIntValue("id");
auto coll = get_ai_list();
auto it_ai = std::find_if(coll.begin(), coll.end(), [id](FGAIBasePtr ai) {
return ai->getID() == id;
});
if (it_ai != coll.end())
(*it_ai)->setDie(true);
#if 0
for (FGAIBase* ai : get_ai_list()) {
if (ai->getID() == id) {
ai->setDie(true);
break;
}
}
#endif
return false;
}
FGAIBasePtr FGAIManager::getObjectFromProperty(const SGPropertyNode* aProp) const
{
auto it = std::find_if(ai_list.begin(), ai_list.end(),
[aProp](FGAIBasePtr ai) { return ai->_getProps() == aProp; });
if (it == ai_list.end()) {
return nullptr;
}
return *it;
}
bool
FGAIManager::loadScenario( const string &id )
{
SGPath path;
SGPropertyNode_ptr file = loadScenarioFile(id, path);
if (!file) {
return false;
}
simgear::ErrorReportContext ec("scenario-path", path.utf8Str());
SGPropertyNode_ptr scNode = file->getChild("scenario");
if (!scNode) {
simgear::reportFailure(simgear::LoadFailure::Misconfigured,
simgear::ErrorCode::ScenarioLoad,
"No <scenario> element in file", path);
return false;
}
assert(_scenarios.find(id) == _scenarios.end());
_scenarios[id] = new Scenario(this, id, scNode);
return true;
}
bool
FGAIManager::unloadScenario( const string &filename)
{
auto it = _scenarios.find(filename);
if (it == _scenarios.end()) {
SG_LOG(SG_AI, SG_WARN, "unload scenario: not found:" << filename);
return false;
}
// remove /sim/ai node
for (auto n : root->getChildren("scenario")) {
if (n->getStringValue() == filename) {
root->removeChild(n);
break;
}
}
delete it->second;
_scenarios.erase(it);
return true;
}
void
FGAIManager::unloadAllScenarios()
{
std::for_each(_scenarios.begin(), _scenarios.end(),
[](const ScenarioDict::value_type& v) { delete v.second; });
// remove /sim/ai node
if(root) {
root->removeChildren("scenario");
}
_scenarios.clear();
}
SGPropertyNode_ptr
FGAIManager::loadScenarioFile(const std::string& scenarioName, SGPath& outPath)
{
auto s = fgGetNode("/sim/ai/scenarios");
if (!s) return {};
for (auto n : s->getChildren("scenario")) {
if (n->getStringValue("id") == scenarioName) {
SGPath path{n->getStringValue("path")};
outPath = path;
simgear::ErrorReportContext ec("scenario-path", path.utf8Str());
try {
SGPropertyNode_ptr root = new SGPropertyNode;
readProperties(path, root);
return root;
} catch (const sg_exception &t) {
SG_LOG(SG_AI, SG_ALERT, "Failed to load scenario '"
<< path << "': " << t.getFormattedMessage());
simgear::reportFailure(simgear::LoadFailure::BadData, simgear::ErrorCode::ScenarioLoad,
"Failed to laod scenario XML:" + t.getFormattedMessage(),
t.getLocation());
}
}
}
return {};
}
const FGAIBase *
FGAIManager::calcCollision(double alt, double lat, double lon, double fuse_range)
{
// we specify tgt extent (ft) according to the AIObject type
double tgt_ht[] = {0, 50, 100, 250, 0, 100, 0, 0, 50, 50, 20, 100, 50};
double tgt_length[] = {0, 100, 200, 750, 0, 50, 0, 0, 200, 100, 40, 200, 100};
ai_list_iterator ai_list_itr = ai_list.begin();
ai_list_iterator end = ai_list.end();
SGGeod pos(SGGeod::fromDegFt(lon, lat, alt));
SGVec3d cartPos(SGVec3d::fromGeod(pos));
while (ai_list_itr != end) {
double tgt_alt = (*ai_list_itr)->_getAltitude();
FGAIBase::object_type type = (*ai_list_itr)->getType();
tgt_ht[static_cast<int>(type)] += fuse_range;
if (fabs(tgt_alt - alt) > tgt_ht[static_cast<int>(type)] || type == FGAIBase::object_type::otBallistic
|| type == FGAIBase::object_type::otStorm || type == FGAIBase::object_type::otThermal ) {
//SG_LOG(SG_AI, SG_DEBUG, "AIManager: skipping "
// << fabs(tgt_alt - alt)
// << " "
// << type
// );
++ai_list_itr;
continue;
}
int id = (*ai_list_itr)->getID();
double range = calcRangeFt(cartPos, (*ai_list_itr));
//SG_LOG(SG_AI, SG_DEBUG, "AIManager: AI list size "
// << ai_list.size()
// << " type " << type
// << " ID " << id
// << " range " << range
// //<< " bearing " << bearing
// << " alt " << tgt_alt
// );
tgt_length[static_cast<int>(type)] += fuse_range;
if (range < tgt_length[static_cast<int>(type)]){
SG_LOG(SG_AI, SG_DEBUG, "AIManager: HIT! "
<< " type " << static_cast<int>(type)
<< " ID " << id
<< " range " << range
<< " alt " << tgt_alt
);
return (*ai_list_itr).get();
}
++ai_list_itr;
}
return nullptr;
}
double
FGAIManager::calcRangeFt(const SGVec3d& aCartPos, const FGAIBase* aObject) const
{
double distM = dist(aCartPos, aObject->getCartPos());
return distM * SG_METER_TO_FEET;
}
FGAIAircraft* FGAIManager::getUserAircraft() const
{
return _userAircraft.get();
}
// Register the subsystem.
SGSubsystemMgr::Registrant<FGAIManager> registrantFGAIManager(
SGSubsystemMgr::POST_FDM,
{{"nasal", SGSubsystemMgr::Dependency::HARD}});
//end AIManager.cxx

180
src/AIModel/AIManager.hxx Normal file
View File

@@ -0,0 +1,180 @@
// AIManager.hxx - David Culp - based on:
// AIMgr.hxx - definition of FGAIMgr
// - a global management class for FlightGear generated AI traffic
//
// Written by David Luff, started March 2002.
//
// Copyright (C) 2002 David C Luff - david.luff@nottingham.ac.uk
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation; either version 2 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#pragma once
#include <list>
#include <map>
#include <simgear/math/SGVec3.hxx>
#include <simgear/misc/sg_path.hxx>
#include <simgear/props/props.hxx>
#include <simgear/structure/SGSharedPtr.hxx>
#include <simgear/structure/subsystem_mgr.hxx>
class FGAIBase;
class FGAIThermal;
class FGAIAircraft;
typedef SGSharedPtr<FGAIBase> FGAIBasePtr;
class FGAIManager : public SGSubsystem
{
public:
FGAIManager();
virtual ~FGAIManager();
// Subsystem API.
void bind() override;
void init() override;
void postinit() override;
void reinit() override;
void shutdown() override;
void unbind() override;
void update(double dt) override;
// Subsystem identification.
static const char* staticSubsystemClassId() { return "ai-model"; }
void updateLOD(SGPropertyNode* node);
void attach(const SGSharedPtr<FGAIBase> &model);
const FGAIBase *calcCollision(double alt, double lat, double lon, double fuse_range);
inline double get_user_heading() const { return user_heading; }
inline double get_user_pitch() const { return user_pitch; }
inline double get_user_speed() const {return user_speed; }
inline double get_wind_from_east() const {return wind_from_east; }
inline double get_wind_from_north() const {return wind_from_north; }
inline double get_user_roll() const { return user_roll; }
inline double get_user_agl() const { return user_altitude_agl; }
bool loadScenario( const std::string &id );
/**
* Static helper to register scenarios. This has to happen very early because
* we need carrier scenarios to start the position-init process for a
* carrier start.
*/
static void registerScenarios(SGPropertyNode_ptr root = {});
static SGPropertyNode_ptr registerScenarioFile(SGPropertyNode_ptr root, const SGPath& p);
static SGPropertyNode_ptr loadScenarioFile(const std::string& id, SGPath& outPath);
FGAIBasePtr addObject(const SGPropertyNode* definition);
bool isVisible(const SGGeod& pos) const;
/**
* @brief given a reference to an /ai/models/<foo>[n] node, return the
* corresponding AIObject implementation, or NULL.
*/
FGAIBasePtr getObjectFromProperty(const SGPropertyNode* aProp) const;
typedef std::vector <FGAIBasePtr> ai_list_type;
const ai_list_type& get_ai_list() const {
return ai_list;
}
double calcRangeFt(const SGVec3d& aCartPos, const FGAIBase* aObject) const;
/**
* @brief Retrieve the representation of the user's aircraft in the AI manager
* the position and velocity of this object are slaved to the user's aircraft,
* so that AI systems such as parking and ATC can see the user and process /
* avoid correctly.
*/
FGAIAircraft* getUserAircraft() const;
bool isRadarEnabled() const
{ return _radarEnabled; }
bool enableRadarDebug() const
{ return _radarDebugMode; }
double radarRangeM() const
{ return _radarRangeM; }
private:
// FGSubmodelMgr is a friend for access to the AI_list
friend class FGSubmodelMgr;
// A list of pointers to AI objects
typedef ai_list_type::iterator ai_list_iterator;
typedef ai_list_type::const_iterator ai_list_const_iterator;
int getNumAiObjects() const;
void removeDeadItem(FGAIBase* base);
// Returns true on success, e.g. returns false if scenario is already loaded.
bool loadScenarioCommand(const SGPropertyNode* args, SGPropertyNode* root);
bool unloadScenarioCommand(const SGPropertyNode* args, SGPropertyNode* root);
bool addObjectCommand(const SGPropertyNode* arg, const SGPropertyNode* root);
bool removeObjectCommand(const SGPropertyNode* arg, const SGPropertyNode* root);
bool removeObject(const SGPropertyNode* args);
bool unloadScenario( const std::string &filename );
void unloadAllScenarios();
SGPropertyNode_ptr root;
SGPropertyNode_ptr enabled;
SGPropertyNode_ptr thermal_lift_node;
SGPropertyNode_ptr user_altitude_agl_node;
SGPropertyNode_ptr user_speed_node;
SGPropertyNode_ptr wind_from_east_node;
SGPropertyNode_ptr wind_from_north_node;
SGPropertyNode_ptr _environmentVisiblity;
SGPropertyNode_ptr _groundSpeedKts_node;
ai_list_type ai_list;
double user_altitude_agl = 0.0;
double user_heading = 0.0;
double user_pitch = 0.0;
double user_roll = 0.0;
double user_speed = 0.0;
double wind_from_east = 0.0;
double wind_from_north = 0.0;
void fetchUserState( double dt );
// used by thermals
double range_nearest = 0.0;
double strength = 0.0;
void processThermal( double dt, FGAIThermal* thermal );
SGPropertyChangeCallback<FGAIManager> cb_ai_bare;
SGPropertyChangeCallback<FGAIManager> cb_ai_detailed;
SGPropertyChangeCallback<FGAIManager> cb_interior;
class Scenario;
typedef std::map<std::string, Scenario*> ScenarioDict;
ScenarioDict _scenarios;
SGSharedPtr<FGAIAircraft> _userAircraft;
SGPropertyNode_ptr _simRadarControl,
_radarRangeNode, _radarDebugNode;
bool _radarEnabled = true,
_radarDebugMode = false;
double _radarRangeM = 0.0;
};

View File

@@ -0,0 +1,922 @@
// FGAIMultiplayer - FGAIBase-derived class creates an AI multiplayer aircraft
//
// Based on FGAIAircraft
// Written by David Culp, started October 2003.
// Also by Gregor Richards, started December 2005.
//
// Copyright (C) 2003 David P. Culp - davidculp2@comcast.net
// Copyright (C) 2005 Gregor Richards
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation; either version 2 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#include <config.h>
#include <string>
#include <stdio.h>
#include <Aircraft/replay.hxx>
#include <Main/globals.hxx>
#include <Main/fg_props.hxx>
#include <Time/TimeManager.hxx>
#include "AIMultiplayer.hxx"
using std::string;
// #define SG_DEBUG SG_ALERT
FGAIMultiplayer::FGAIMultiplayer() : FGAIBase(object_type::otMultiplayer, fgGetBool("/sim/multiplay/hot", false)),
m_simple_time_enabled(fgGetNode("/sim/time/simple-time/enabled", true)),
m_sim_replay_replay_state(fgGetNode("/sim/replay/replay-state", true)),
m_sim_replay_time(fgGetNode("/sim/replay/time", true)),
mLogRawSpeedMultiplayer(fgGetNode("/sim/replay/log-raw-speed-multiplayer", true))
{
no_roll = false;
_searchOrder = ModelSearchOrder::PREFER_DATA;
}
bool FGAIMultiplayer::init(ModelSearchOrder searchOrder)
{
props->setStringValue("sim/model/path", model_path);
props->setIntValue("sim/model/fallback-model-index", _getFallbackModelIndex());
//refuel_node = fgGetNode("systems/refuel/contact", true);
isTanker = false; // do this until this property is
// passed over the net
const string& str1 = _getCallsign();
const string str2 = "MOBIL";
string::size_type loc1= str1.find( str2, 0 );
if ( (loc1 != string::npos && str2 != "") ){
// cout << " string found " << str2 << " in " << str1 << endl;
isTanker = true;
// cout << "isTanker " << isTanker << " " << mCallSign <<endl;
}
// ensure that these are created prior to calling base class init
// as otherwise the MP list will break
m_lagPPSAveragedNode = props->getNode("lag/pps-averaged", true);
m_lagPPSAveragedNode->setDoubleValue(0);
m_lagModAveragedNode = props->getNode("lag/lag-mod-averaged", true);
m_lagModAveragedNode->setDoubleValue(0);
// load model
bool result = FGAIBase::init(searchOrder);
// propagate installation state (used by MP pilot list)
props->setBoolValue("model-installed", _installed);
m_node_simple_time_latest = props->getNode("simple-time/latest", true);
m_node_simple_time_offset = props->getNode("simple-time/offset", true);
m_node_simple_time_offset_smoothed = props->getNode("simple-time/offset-smoothed", true);
m_node_simple_time_compensation = props->getNode("simple-time/compensation", true);
return result;
}
void FGAIMultiplayer::bind()
{
FGAIBase::bind();
//2018.1 mp-clock-mode indicates the clock mode that the client is running, so for backwards
// compatibility ensure this is initialized to 0 which means pre 2018.1
props->setIntValue("sim/multiplay/mp-clock-mode", 0);
tie("refuel/contact", SGRawValuePointer<bool>(&contact));
tie("tanker", SGRawValuePointer<bool>(&isTanker));
tie("controls/invisible",
SGRawValuePointer<bool>(&invisible));
_uBodyNode = props->getNode("velocities/uBody-fps", true);
_vBodyNode = props->getNode("velocities/vBody-fps", true);
_wBodyNode = props->getNode("velocities/wBody-fps", true);
m_node_ai_latch = props->getNode("ai-latch", true /*create*/);
m_node_log_multiplayer = globals->get_props()->getNode("/sim/log-multiplayer-callsign", true /*create*/);
#define AIMPROProp(type, name) \
SGRawValueMethods<FGAIMultiplayer, type>(*this, &FGAIMultiplayer::get##name)
#define AIMPRWProp(type, name) \
SGRawValueMethods<FGAIMultiplayer, type>(*this, \
&FGAIMultiplayer::get##name, &FGAIMultiplayer::set##name)
//tie("callsign", AIMPROProp(const char *, CallSign));
tie("controls/allow-extrapolation",
AIMPRWProp(bool, AllowExtrapolation));
tie("controls/lag-adjust-system-speed",
AIMPRWProp(double, LagAdjustSystemSpeed));
tie("controls/player-lag",
AIMPRWProp(double, playerLag));
tie("controls/compensate-lag",
AIMPRWProp(int, compensateLag));
#undef AIMPROProp
#undef AIMPRWProp
}
void FGAIMultiplayer::FGAIMultiplayerInterpolate(
MotionInfo::iterator prevIt,
MotionInfo::iterator nextIt,
double tau,
SGVec3d& ecPos,
SGQuatf& ecOrient,
SGVec3f& ecLinearVel
)
{
// Here we do just linear interpolation on the position
ecPos = interpolate(tau, prevIt->second.position, nextIt->second.position);
ecOrient = interpolate((float)tau, prevIt->second.orientation,
nextIt->second.orientation);
ecLinearVel = interpolate((float)tau, prevIt->second.linearVel, nextIt->second.linearVel);
speed = norm(ecLinearVel) * SG_METER_TO_NM * 3600.0;
if (prevIt->second.properties.size() == nextIt->second.properties.size()) {
std::vector<FGPropertyData*>::const_iterator prevPropIt;
std::vector<FGPropertyData*>::const_iterator prevPropItEnd;
std::vector<FGPropertyData*>::const_iterator nextPropIt;
std::vector<FGPropertyData*>::const_iterator nextPropItEnd;
prevPropIt = prevIt->second.properties.begin();
prevPropItEnd = prevIt->second.properties.end();
nextPropIt = nextIt->second.properties.begin();
nextPropItEnd = nextIt->second.properties.end();
while (prevPropIt != prevPropItEnd)
{
PropertyMap::iterator pIt = mPropertyMap.find((*prevPropIt)->id);
//cout << " Setting property..." << (*prevPropIt)->id;
if (pIt != mPropertyMap.end())
{
//cout << "Found " << pIt->second->getPath() << ":";
/*
* RJH - 2017-01-25
* During multiplayer operations a series of crashes were encountered that affected all players
* within range of each other and resulting in an exception being thrown at exactly the same moment in time
* (within case props::STRING: ref http://i.imgur.com/y6MBoXq.png)
* Investigation showed that the nextPropIt and prevPropIt were pointing to different properties
* which may be caused due to certain models that have overloaded mp property transmission and
* these craft have their properties truncated due to packet size. However the result of this
* will be different contents in the previous and current packets, so here we protect against
* this by only considering properties where the previous and next id are the same.
* It might be a better solution to search the previous and next lists to locate the matching id's
*/
if (*nextPropIt && (*nextPropIt)->id == (*prevPropIt)->id)
{
switch ((*prevPropIt)->type)
{
case simgear::props::INT:
case simgear::props::BOOL:
case simgear::props::LONG:
// Jean Pellotier, 2018-01-02 : we don't want interpolation for integer values, they are mostly used
// for non linearly changing values (e.g. transponder etc ...)
// fixes: https://sourceforge.net/p/flightgear/codetickets/1885/
pIt->second->setIntValue((*nextPropIt)->int_value);
break;
case simgear::props::FLOAT:
case simgear::props::DOUBLE:
{
float val = (1 - tau)*(*prevPropIt)->float_value +
tau*(*nextPropIt)->float_value;
pIt->second->setFloatValue(val);
}
break;
case simgear::props::STRING:
case simgear::props::UNSPECIFIED:
//cout << "Str: " << (*nextPropIt)->string_value << "\n";
pIt->second->setStringValue((*nextPropIt)->string_value);
break;
default:
// FIXME - currently defaults to float values
{
float val = (1 - tau)*(*prevPropIt)->float_value +
tau*(*nextPropIt)->float_value;
pIt->second->setFloatValue(val);
}
break;
}
}
else
{
SG_LOG(SG_AI, SG_WARN, "MP packet mismatch during lag interpolation: " << (*prevPropIt)->id << " != " << (*nextPropIt)->id << "\n");
}
}
else
{
SG_LOG(SG_AI, SG_DEBUG, "Unable to find property: " << (*prevPropIt)->id << "\n");
}
++prevPropIt;
++nextPropIt;
}
}
}
void FGAIMultiplayer::FGAIMultiplayerExtrapolate(
MotionInfo::iterator nextIt,
double tInterp,
bool motion_logging,
SGVec3d& ecPos,
SGQuatf& ecOrient,
SGVec3f& ecLinearVel
)
{
const FGExternalMotionData& motionInfo = nextIt->second;
// The time to predict, limit to 3 seconds. But don't do this if we are
// running motion tests, because it can mess up the results.
//
double t = tInterp - nextIt->first;
if (!motion_logging)
{
props->setDoubleValue("lag/extrapolation-t", t);
if (t > 3)
{
t = 3;
props->setBoolValue("lag/extrapolation-out-of-range", true);
}
else
{
props->setBoolValue("lag/extrapolation-out-of-range", false);
}
}
// using velocity and acceleration to guess a parabolic position...
ecPos = motionInfo.position;
ecOrient = motionInfo.orientation;
ecLinearVel = motionInfo.linearVel;
SGVec3d ecVel = toVec3d(ecOrient.backTransform(ecLinearVel));
SGVec3f angularVel = motionInfo.angularVel;
SGVec3d ecAcc = toVec3d(ecOrient.backTransform(motionInfo.linearAccel));
double normVel = norm(ecVel);
double normAngularVel = norm(angularVel);
props->setDoubleValue("lag/norm-vel", normVel);
props->setDoubleValue("lag/norm-angular-vel", normAngularVel);
// not doing rotationnal prediction for small speed or rotation rate,
// to avoid agitated parked plane
if (( normAngularVel > 0.05 ) || ( normVel > 1.0 ))
{
ecOrient += t*ecOrient.derivative(angularVel);
}
// not using acceleration for small speed, to have better parked planes
// note that anyway acceleration is not transmit yet by mp
if ( normVel > 1.0 )
{
ecPos += t*(ecVel + 0.5*t*ecAcc);
}
else
{
ecPos += t*(ecVel);
}
std::vector<FGPropertyData*>::const_iterator firstPropIt;
std::vector<FGPropertyData*>::const_iterator firstPropItEnd;
speed = norm(ecLinearVel) * SG_METER_TO_NM * 3600.0;
firstPropIt = motionInfo.properties.begin();
firstPropItEnd = motionInfo.properties.end();
while (firstPropIt != firstPropItEnd)
{
PropertyMap::iterator pIt = mPropertyMap.find((*firstPropIt)->id);
//cout << " Setting property..." << (*firstPropIt)->id;
if (pIt != mPropertyMap.end())
{
switch ((*firstPropIt)->type)
{
case simgear::props::INT:
case simgear::props::BOOL:
case simgear::props::LONG:
pIt->second->setIntValue((*firstPropIt)->int_value);
//cout << "Int: " << (*firstPropIt)->int_value << "\n";
break;
case simgear::props::FLOAT:
case simgear::props::DOUBLE:
pIt->second->setFloatValue((*firstPropIt)->float_value);
//cout << "Flo: " << (*firstPropIt)->float_value << "\n";
break;
case simgear::props::STRING:
case simgear::props::UNSPECIFIED:
pIt->second->setStringValue((*firstPropIt)->string_value);
//cout << "Str: " << (*firstPropIt)->string_value << "\n";
break;
default:
// FIXME - currently defaults to float values
pIt->second->setFloatValue((*firstPropIt)->float_value);
//cout << "Unk: " << (*firstPropIt)->float_value << "\n";
break;
}
}
else
{
SG_LOG(SG_AI, SG_DEBUG, "Unable to find property: " << (*firstPropIt)->id << "\n");
}
++firstPropIt;
}
}
// Populate
// /sim/replay/log-raw-speed-multiplayer-post-*-values/value[] with information
// on motion of MP and user aircraft.
//
// For use with scripts/python/recordreplay.py --test-motion-mp.
//
static void s_MotionLogging(const std::string& _callsign, double tInterp, SGVec3d ecPos, const SGGeod& pos)
{
SGGeod pos_geod = pos;
if (!fgGetBool("/sim/replay/replay-state-eof"))
{
static SGVec3d s_pos_0;
static SGVec3d s_pos_prev;
static double s_t_prev = -1;
SGVec3d pos2 = ecPos;
double sim_replay_time = fgGetDouble("/sim/replay/time");
double t = fgGetBool("/sim/replay/replay-state") ? sim_replay_time : tInterp;
if (s_t_prev == -1)
{
s_pos_0 = pos2;
}
double t_dt = t - s_t_prev;
if (s_t_prev != -1 /*&& t_dt != 0*/)
{
SGVec3d delta_pos = pos2 - s_pos_prev;
SGVec3d delta_pos_norm = normalize(delta_pos);
double distance0 = length(pos2 - s_pos_0);
double distance = length(delta_pos);
double speed = (t_dt) ? distance / t_dt : -999;
if (t_dt)
{
SGPropertyNode* n0 = fgGetNode("/sim/replay/log-raw-speed-multiplayer-post-values", true /*create*/);
SGPropertyNode* n;
n = n0->addChild("value");
n->setDoubleValue(speed);
n = n0->addChild("description");
char buffer[128];
snprintf(buffer, sizeof(buffer), "s_t_prev=%f t=%f t_dt=%f distance=%f",
s_t_prev, t, t_dt, distance);
n->setStringValue(buffer);
}
SGGeod user_pos_geod = globals->get_aircraft_position();
SGVec3d user_pos = globals->get_aircraft_position_cart();
double user_to_mp_distance = SGGeodesy::distanceM(user_pos_geod, pos_geod);
double user_to_mp_bearing = SGGeodesy::courseDeg(user_pos_geod, pos_geod);
double user_distance0 = length(user_pos - s_pos_0);
fgGetNode("/sim/replay/log-raw-speed-multiplayer-post-relative-distance", true /*create*/)
->addChild("value")
->setDoubleValue(user_to_mp_distance);
fgGetNode("/sim/replay/log-raw-speed-multiplayer-post-relative-bearing", true /*create*/)
->addChild("value")
->setDoubleValue(user_to_mp_bearing);
fgGetNode("/sim/replay/log-raw-speed-multiplayer-post-absolute-distance", true /*create*/)
->addChild("value")
->setDoubleValue(distance0);
fgGetNode("/sim/replay/log-raw-speed-multiplayer-post-user-absolute-distance", true /*create*/)
->addChild("value")
->setDoubleValue(user_distance0);
}
if (t_dt)
{
s_t_prev = t;
s_pos_prev = pos2;
}
}
}
void FGAIMultiplayer::update(double dt)
{
using namespace simgear;
if (dt <= 0)
{
return;
}
FGAIBase::update(dt);
// Check if we already got data
if (mMotionInfo.empty())
{
return;
}
// motion_logging is true if we are being run by
// scripts/python/recordreplay.py --test-motion-mp.
//
bool motion_logging = false;
{
string callsign = mLogRawSpeedMultiplayer->getStringValue();
if (!callsign.empty() && this->_callsign == callsign)
{
motion_logging = true;
}
}
double curtime;
double tInterp;
if (m_simple_time_enabled->getBoolValue())
{
// Simplified timekeeping.
//
if (m_sim_replay_replay_state->getBoolValue())
{
tInterp = m_sim_replay_time->getDoubleValue();
SG_LOG(SG_GENERAL, SG_BULK, "tInterp=" << std::fixed << std::setprecision(6) << tInterp);
}
else
{
tInterp = globals->get_subsystem<TimeManager>()->getMPProtocolClockSec();
}
curtime = tInterp;
}
else
{
// Get the last available time
MotionInfo::reverse_iterator motioninfo_back = mMotionInfo.rbegin();
const double curentPkgTime = motioninfo_back->first;
// The current simulation time we need to update for,
// note that the simulation time is updated before calling all the
// update methods. Thus motioninfo_back contains the time intervals *end* time
// 2018: notice this time is specifically used for mp protocol
curtime = globals->get_subsystem<TimeManager>()->getMPProtocolClockSec();
// Dynamically optimize the time offset between the feeder and the client
// Well, 'dynamically' means that the dynamic of that update must be very
// slow. You would otherwise notice huge jumps in the multiplayer models.
// The reason is that we want to avoid huge extrapolation times since
// extrapolation is highly error prone. For that we need something
// approaching the average latency of the packets. This first order lag
// component will provide this. We just take the error of the currently
// requested time to the most recent available packet. This is the
// target we want to reach in average.
double lag = motioninfo_back->second.lag;
rawLag = curentPkgTime - curtime;
realTime = false; //default behaviour
if (!mTimeOffsetSet)
{
mTimeOffsetSet = true;
mTimeOffset = curentPkgTime - curtime - lag;
lastTime = curentPkgTime;
lagModAveraged = remainder((curentPkgTime - curtime), 3600.0);
m_lagPPSAveragedNode->setDoubleValue(lagPpsAveraged);
m_lagModAveragedNode->setDoubleValue(lagModAveraged);
}
else
{
if ((curentPkgTime - lastTime) != 0)
{
lagPpsAveraged = 0.99 * lagPpsAveraged + 0.01 * fabs( 1 / (lastTime - curentPkgTime));
lastTime = curentPkgTime;
rawLagMod = remainder(rawLag, 3600.0);
lagModAveraged = lagModAveraged *0.99 + 0.01 * rawLagMod;
m_lagPPSAveragedNode->setDoubleValue(lagPpsAveraged);
m_lagModAveragedNode->setDoubleValue(lagModAveraged);
}
double offset = 0.0;
//spectator mode, more late to be in the interpolation zone
if (compensateLag == 3)
{
offset = curentPkgTime - curtime - lag + playerLag;
// old behaviour
}
else if (compensateLag == 1)
{
offset = curentPkgTime - curtime - lag;
// using the prediction mode to display the mpaircraft in the futur/past with given playerlag value
//currently compensatelag = 2
}
else if (fabs(lagModAveraged) < 0.3)
{
mTimeOffset = (round(rawLag/3600))*3600; //real time mode if close enough
realTime = true;
}
else
{
offset = curentPkgTime - curtime + 0.48*lag + playerLag;
}
if (!realTime)
{
if ((!mAllowExtrapolation && offset + lag < mTimeOffset) || (offset - 10 > mTimeOffset))
{
mTimeOffset = offset;
SG_LOG(SG_AI, SG_DEBUG, "Resetting time offset adjust system to "
"avoid extrapolation: time offset = " << mTimeOffset);
}
else
{
// the error of the offset, respectively the negative error to avoid
// a minus later ...
double err = offset - mTimeOffset;
// limit errors leading to shorter lag values somehow, that is late
// arriving packets will pessimize the overall lag much more than
// early packets will shorten the overall lag
double sysSpeed;
//trying to slow the rudderlag phenomenon thus using more the prediction system
//if we are off by less than 1.5s, do a little correction, and bigger step above 1.5s
if (fabs(err) < 1.5)
{
if (err < 0)
{
sysSpeed = mLagAdjustSystemSpeed*err*0.01;
}
else
{
sysSpeed = SGMiscd::min(0.5*err*err, 0.05);
}
}
else
{
if (err < 0)
{
// Ok, we have some very late packets and nothing newer increase the
// lag by the given speedadjust
sysSpeed = mLagAdjustSystemSpeed*err;
}
else
{
// We have a too pessimistic display delay shorten that a small bit
sysSpeed = SGMiscd::min(0.1*err*err, 0.5);
}
}
// simple euler integration for that first order system including some
// overshooting guard to prevent to aggressive system speeds
// (stiff systems) to explode the systems state
double systemIncrement = dt*sysSpeed;
if (fabs(err) < fabs(systemIncrement))
{
systemIncrement = err;
}
mTimeOffset += systemIncrement;
SG_LOG(SG_AI, SG_DEBUG, "Offset adjust system: time offset = "
<< mTimeOffset << ", expected longitudinal position error due to "
" current adjustment of the offset: "
<< fabs(norm(motioninfo_back->second.linearVel)*systemIncrement));
}
}
}
// Compute the time in the feeders time scale which fits the current time
// we need to
tInterp = curtime + mTimeOffset;
}
SGVec3d ecPos;
SGQuatf ecOrient;
SGVec3f ecLinearVel;
MotionInfo::iterator nextIt = mMotionInfo.upper_bound(tInterp);
MotionInfo::iterator prevIt = nextIt;
if (nextIt != mMotionInfo.end() && nextIt->first >= tInterp)
{
// Ok, we need a time prevous to the last available packet,
// that is good ...
// the case tInterp = curentPkgTime need to be in the interpolation, to avoid a bug zeroing the position
double tau = 0;
if (nextIt == mMotionInfo.begin())
{
// Leave prevIt and nextIt pointing at same item.
SG_LOG(SG_GENERAL, SG_DEBUG, "Only one frame for interpolation: " << _callsign);
}
else
{
--prevIt;
// Interpolation coefficient is between 0 and 1
double intervalStart = prevIt->first;
double intervalEnd = nextIt->first;
double intervalLen = intervalEnd - intervalStart;
if (intervalLen != 0.0)
{
tau = (tInterp - intervalStart) / intervalLen;
}
}
FGAIMultiplayerInterpolate(prevIt, nextIt, tau, ecPos, ecOrient, ecLinearVel);
}
else
{
// Ok, we need to predict the future, so, take the best data we can have
// and do some eom computation to guess that for now.
--nextIt;
--prevIt; // so mMotionInfo.erase() does the right thing below.
FGAIMultiplayerExtrapolate(nextIt, tInterp, motion_logging, ecPos, ecOrient, ecLinearVel);
}
// Remove any motion information before <prevIt> - we will not need this in
// the future.
//
mMotionInfo.erase(mMotionInfo.begin(), prevIt);
// extract the position
pos = SGGeod::fromCart(ecPos);
double recent_alt_ft = altitude_ft;
altitude_ft = pos.getElevationFt();
// expose a valid vertical speed
if (lastUpdateTime != 0)
{
double dT = curtime - lastUpdateTime;
double Weighting=1;
if (dt < 1.0)
{
Weighting = dt;
}
// simple smoothing over 1 second
vs_fps = (1.0-Weighting)*vs_fps + Weighting * (altitude_ft - recent_alt_ft) / dT * 60;
}
lastUpdateTime = curtime;
// The quaternion rotating from the earth centered frame to the
// horizontal local frame
SGQuatf qEc2Hl = SGQuatf::fromLonLatRad((float)pos.getLongitudeRad(), (float)pos.getLatitudeRad());
// The orientation wrt the horizontal local frame
SGQuatf hlOr = conj(qEc2Hl)*ecOrient;
float hDeg, pDeg, rDeg;
hlOr.getEulerDeg(hDeg, pDeg, rDeg);
hdg = hDeg;
roll = rDeg;
pitch = pDeg;
// expose velocities/u,v,wbody-fps in the mp tree
_uBodyNode->setValue(ecLinearVel[0] * SG_METER_TO_FEET);
_vBodyNode->setValue(ecLinearVel[1] * SG_METER_TO_FEET);
_wBodyNode->setValue(ecLinearVel[2] * SG_METER_TO_FEET);
if (ecLinearVel[0] == 0) {
// MP packets for carriers have zero ecLinearVel, but do specify
// velocities/speed-kts.
double speed_kts = props->getDoubleValue("velocities/speed-kts");
double speed_fps = speed_kts * SG_KT_TO_FPS;
_uBodyNode->setDoubleValue(speed_fps);
}
std::string ai_latch = m_node_ai_latch->getStringValue();
if (ai_latch != m_ai_latch) {
SG_LOG(SG_AI, SG_ALERT, "latching _callsign=" << _callsign << " to mp " << ai_latch);
m_ai_latch = ai_latch;
SGPropertyNode* mp_node = globals->get_props()->getNode(ai_latch, false /*create*/);
m_node_ai_latch_latitude = mp_node ? mp_node->getNode("position/latitude-deg", true /*create*/) : nullptr;
m_node_ai_latch_longitude = mp_node ? mp_node->getNode("position/longitude-deg", true /*create*/) : nullptr;
m_node_ai_latch_altitude = mp_node ? mp_node->getNode("position/altitude-ft", true /*create*/) : nullptr;
m_node_ai_latch_heading = mp_node ? mp_node->getNode("orientation/true-heading-deg", true /*create*/) : nullptr;
m_node_ai_latch_pitch = mp_node ? mp_node->getNode("orientation/pitch-deg", true /*create*/) : nullptr;
m_node_ai_latch_roll = mp_node ? mp_node->getNode("orientation/roll-deg", true /*create*/) : nullptr;
m_node_ai_latch_ubody_fps = mp_node ? mp_node->getNode("velocities/uBody-fps", true /*create*/) : nullptr;
m_node_ai_latch_vbody_fps = mp_node ? mp_node->getNode("velocities/vBody-fps", true /*create*/) : nullptr;
m_node_ai_latch_wbody_fps = mp_node ? mp_node->getNode("velocities/wBody-fps", true /*create*/) : nullptr;
m_node_ai_latch_speed_kts = mp_node ? mp_node->getNode("velocities/speed-kts", true /*create*/) : nullptr;
}
if (ai_latch != "") {
SGPropertyNode* mp_node = globals->get_props()->getNode(ai_latch, true /*create*/);
assert(m_node_ai_latch_latitude->getPath() == mp_node->getNode("position/latitude-deg")->getPath());
m_node_ai_latch_latitude->setDoubleValue(pos.getLatitudeDeg());
m_node_ai_latch_longitude->setDoubleValue(pos.getLongitudeDeg());
m_node_ai_latch_altitude->setDoubleValue(pos.getElevationFt());
m_node_ai_latch_heading->setDoubleValue(hDeg);
m_node_ai_latch_pitch->setDoubleValue(pitch);
m_node_ai_latch_roll->setDoubleValue(roll);
m_node_ai_latch_ubody_fps->setDoubleValue(_uBodyNode->getDoubleValue());
m_node_ai_latch_vbody_fps->setDoubleValue(_vBodyNode->getDoubleValue());
m_node_ai_latch_wbody_fps->setDoubleValue(_wBodyNode->getDoubleValue());
// /ai/models/carrier[]/velocities/speed-kts seems to be used
// to calculate friction between carrier deck and our aircraft
// undercarriage when brakes are on, so we set it here from
// /ai/models/multiplayer[]/velocities/uBody-fps.
//
// [We could possibly use
// /ai/models/multiplayer[]/velocities/true-airspeed-kt instead, but
// seems nicer to use uBody.]
//
// [todo: use rate of change of heading to calculating
// movement/rotation of the ship under the aircraft, so aircraft
// doesn't slip when carrier changes course. Also would be good to
// handle vbody and wbody?]
//
m_node_ai_latch_speed_kts->setDoubleValue(_uBodyNode->getDoubleValue() * SG_FPS_TO_KT);
}
assert(m_node_ai_latch == props->getNode("ai-latch"));
//SG_LOG(SG_AI, SG_DEBUG, "Multiplayer position and orientation: " << ecPos << ", " << hlOr);
if (motion_logging)
{
s_MotionLogging(_callsign, tInterp, ecPos, pos);
}
//###########################//
// do calculations for radar //
//###########################//
double range_ft2 = UpdateRadar(manager);
//************************************//
// Tanker code //
//************************************//
if ( isTanker)
{
//cout << "IS tanker ";
if (range_ft2 < 250.0 * 250.0 && y_shift > 0.0 && elevation > 0.0)
{
// refuel_node->setBoolValue(true);
//cout << "in contact" << endl;
contact = true;
}
else
{
// refuel_node->setBoolValue(false);
//cout << "not in contact" << endl;
contact = false;
}
}
else
{
//cout << "NOT tanker " << endl;
contact = false;
}
Transform();
}
void
FGAIMultiplayer::addMotionInfo(FGExternalMotionData& motionInfo,
long stamp)
{
mLastTimestamp = stamp;
if (m_simple_time_enabled->getBoolValue()) {
// Update simple-time stats and set m_simple_time_compensation.
//
double t = m_sim_replay_replay_state->getBoolValue()
? m_sim_replay_time->getDoubleValue()
: globals->get_subsystem<TimeManager>()->getMPProtocolClockSec()
;
m_simple_time_offset = motionInfo.time - t;
if (m_simple_time_first_time)
{
m_simple_time_first_time = false;
m_simple_time_offset_smoothed = m_simple_time_offset;
}
double e = 0.01;
m_simple_time_offset_smoothed = (1-e) * m_simple_time_offset_smoothed
+ e * m_simple_time_offset;
if (m_simple_time_offset_smoothed < -2.0 || m_simple_time_offset_smoothed > 1)
{
// If the sender is using simple-time mode and their clock is
// synchronised to ours (e.g. both using NTP), <time_offset> will
// be a negative value due to the network delay plus any difference
// in the remote and local UTC time.
//
// Thus we could expect m_time_offset_max around -0.2.
//
// If m_time_offset_max is very different from zero, this indicates
// that the sender is not putting UTC time into MP packets. E.g.
// they are using simple-time mode but their system clock is not
// set correctly; or they are not using simple-time mode so are
// sending their FDM time or a time that has been synchronised with
// other MP aircraft. Another possibility is that our UTC clock is
// incorrect.
//
// We need a time that can be consistently compared with our UTC
// tInterp. So we set m_time_compensation to something to be
// added to all times received in MP packets from _callsign. We
// use compensated time for keys in the the mMotionInfo[]
// map, thus code should generally use these key values, not
// mMotionInfo[].time.
//
m_simple_time_compensation = -m_simple_time_offset_smoothed;
// m_simple_time_offset_smoothed will usually be too big to be
// useful here.
//
m_lagModAveragedNode->setDoubleValue(0.0);
}
else
{
m_simple_time_compensation = 0;
m_lagModAveragedNode->setDoubleValue(m_simple_time_offset_smoothed);
}
m_node_simple_time_latest->setDoubleValue(motionInfo.time);
m_node_simple_time_offset->setDoubleValue(m_simple_time_offset);
m_node_simple_time_offset_smoothed->setDoubleValue(m_simple_time_offset_smoothed);
m_node_simple_time_compensation->setDoubleValue(m_simple_time_compensation);
double t_key = motionInfo.time + m_simple_time_compensation;
if (m_simple_time_recent_packet_time)
{
double dt = t_key - m_simple_time_recent_packet_time;
if (dt != 0)
{
double ep = 0.05;
lagPpsAveraged = (1-ep) * lagPpsAveraged + ep * (1/dt);
m_lagPPSAveragedNode->setDoubleValue(lagPpsAveraged);
}
}
m_simple_time_recent_packet_time = t_key;
// We use compensated time <t_key> as key in mMotionInfo.
// m_time_compensation is set to non-zero if packets seem to have
// wildly different times from us, if simple-time mode is enabled.
//
// So most code with an <iterator> into mMotionInfo that needs to
// use the MP packet's time, will actuall use iterator->first, not
// iterator->second.time..
//
mMotionInfo[t_key] = motionInfo;
}
else
{
mMotionInfo[motionInfo.time] = motionInfo;
}
// We just copied the property (pointer) list - they are ours now. Clear the
// properties list in given/returned object, so former owner won't deallocate them.
motionInfo.properties.clear();
{
// Gather data on multiplayer speed, used by scripts/python/recordreplay.py.
//
if (m_node_log_multiplayer->getStringValue() == this->_callsign)
{
static SGVec3d pos_prev;
static double t_prev = 0;
double distance = length(motionInfo.position - pos_prev);
double dt = motionInfo.time - t_prev;
double speed = distance / dt;
double linear_vel = norm(motionInfo.linearVel);
SGPropertyNode* item = fgGetNode("/sim/log-multiplayer", true /*create*/)->addChild("mppacket");
item->setDoubleValue("distance", distance);
item->setDoubleValue("speed", speed);
item->setDoubleValue("dt", dt);
item->setDoubleValue("linear_vel", linear_vel);
item->setDoubleValue("t", motionInfo.time );
pos_prev = motionInfo.position;
t_prev = motionInfo.time;
}
}
}
#if 0
void
FGAIMultiplayer::setDoubleProperty(const std::string& prop, double val)
{
SGPropertyNode* pNode = props->getChild(prop.c_str(), true);
pNode->setDoubleValue(val);
}
#endif
void FGAIMultiplayer::clearMotionInfo()
{
mMotionInfo.clear();
mLastTimestamp = 0;
}

View File

@@ -0,0 +1,210 @@
// FGAIMultiplayer - AIBase derived class creates an AI multiplayer aircraft
//
// Written by David Culp, started October 2003.
//
// Copyright (C) 2003 David P. Culp - davidculp2@comcast.net
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation; either version 2 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#pragma once
#include <map>
#include <string>
#include <string_view>
#include <MultiPlayer/mpmessages.hxx>
#include "AIBase.hxx"
class FGAIMultiplayer : public FGAIBase {
public:
FGAIMultiplayer();
virtual ~FGAIMultiplayer() = default;
string_view getTypeString(void) const override { return "multiplayer"; }
bool init(ModelSearchOrder searchOrder) override;
void bind() override;
void update(double dt) override;
void addMotionInfo(FGExternalMotionData& motionInfo, long stamp);
#if 0
void setDoubleProperty(const std::string& prop, double val);
#endif
long getLastTimestamp(void) const
{
return mLastTimestamp;
}
void setAllowExtrapolation(bool allowExtrapolation)
{
mAllowExtrapolation = allowExtrapolation;
}
bool getAllowExtrapolation(void) const
{
return mAllowExtrapolation;
}
void setLagAdjustSystemSpeed(double lagAdjustSystemSpeed)
{
if (lagAdjustSystemSpeed < 0)
lagAdjustSystemSpeed = 0;
mLagAdjustSystemSpeed = lagAdjustSystemSpeed;
}
double getLagAdjustSystemSpeed(void) const
{
return mLagAdjustSystemSpeed;
}
void addPropertyId(unsigned id, const char* name)
{
mPropertyMap[id] = props->getNode(name, true);
}
double getplayerLag(void) const
{
return playerLag;
}
void setplayerLag(double mplayerLag)
{
playerLag = mplayerLag;
}
int getcompensateLag(void) const
{
return compensateLag;
}
void setcompensateLag(int mcompensateLag)
{
compensateLag = mcompensateLag;
}
SGPropertyNode* getPropertyRoot()
{
return props;
}
void clearMotionInfo();
private:
// Automatic sorting of motion data according to its timestamp
typedef std::map<double,FGExternalMotionData> MotionInfo;
MotionInfo mMotionInfo;
// Map between the property id's from the multiplayers network packets
// and the property nodes
typedef std::map<unsigned, SGSharedPtr<SGPropertyNode> > PropertyMap;
PropertyMap mPropertyMap;
// Calculates position, orientation and velocity using interpolation between
// *prevIt and *nextIt, specifically (1-tau)*(*prevIt) + tau*(*nextIt).
//
// Cannot call this method 'interpolate' because that would hide the name in
// OSG.
//
void FGAIMultiplayerInterpolate(
MotionInfo::iterator prevIt,
MotionInfo::iterator nextIt,
double tau,
SGVec3d& ecPos,
SGQuatf& ecOrient,
SGVec3f& ecLinearVel
);
// Calculates position, orientation and velocity using extrapolation from
// *nextIt.
//
void FGAIMultiplayerExtrapolate(
MotionInfo::iterator nextIt,
double tInterp,
bool motion_logging,
SGVec3d& ecPos,
SGQuatf& ecOrient,
SGVec3f& ecLinearVel
);
bool mTimeOffsetSet = false;
bool realTime = false;
int compensateLag = 1;
double playerLag = 0.03;
double mTimeOffset = 0.0;
double lastUpdateTime = 0.0;
double lastTime = 0.0;
double lagPpsAveraged = 1.0;
double rawLag = 0.0;
double rawLagMod = 0.0;
double lagModAveraged = 0.0;
/// Properties which are for now exposed for testing
bool mAllowExtrapolation = true;
double mLagAdjustSystemSpeed = 10.0;
long mLastTimestamp = 0;
// Properties for tankers
SGPropertyNode_ptr refuel_node;
bool isTanker = false;
bool contact = false; // set if this tanker is within fuelling range
// velocities/u,v,wbody-fps
SGPropertyNode_ptr _uBodyNode;
SGPropertyNode_ptr _vBodyNode;
SGPropertyNode_ptr _wBodyNode;
// Things for simple-time.
//
SGPropertyNode_ptr m_simple_time_enabled;
SGPropertyNode_ptr m_sim_replay_replay_state;
SGPropertyNode_ptr m_sim_replay_time;
bool m_simple_time_first_time = true;
double m_simple_time_offset = 0.0;
double m_simple_time_offset_smoothed = 0.0;
double m_simple_time_compensation = 0.0;
double m_simple_time_recent_packet_time = 0.0;
SGPropertyNode_ptr m_lagPPSAveragedNode;
SGPropertyNode_ptr m_lagModAveragedNode;
SGPropertyNode_ptr m_node_simple_time_latest;
SGPropertyNode_ptr m_node_simple_time_offset;
SGPropertyNode_ptr m_node_simple_time_offset_smoothed;
SGPropertyNode_ptr m_node_simple_time_compensation;
// For use with scripts/python/recordreplay.py --test-motion-mp.
SGPropertyNode_ptr mLogRawSpeedMultiplayer;
SGPropertyNode_ptr m_node_ai_latch;
std::string m_ai_latch;
SGPropertyNode_ptr m_node_ai_latch_latitude;
SGPropertyNode_ptr m_node_ai_latch_longitude;
SGPropertyNode_ptr m_node_ai_latch_altitude;
SGPropertyNode_ptr m_node_ai_latch_heading;
SGPropertyNode_ptr m_node_ai_latch_pitch;
SGPropertyNode_ptr m_node_ai_latch_roll;
SGPropertyNode_ptr m_node_ai_latch_ubody_fps;
SGPropertyNode_ptr m_node_ai_latch_vbody_fps;
SGPropertyNode_ptr m_node_ai_latch_wbody_fps;
SGPropertyNode_ptr m_node_ai_latch_speed_kts;
SGPropertyNode_ptr m_node_log_multiplayer;
};

View File

@@ -0,0 +1,102 @@
// Emesary notifications for AI system.
//
// Richard Harrison; April 2020.
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation; either version 2 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#ifndef _FG_AINOTIFICATIONS_HXX
#define _FG_AINOTIFICATIONS_HXX
#include <simgear/emesary/Emesary.hxx>
#include <simgear/math/SGMath.hxx>
class NearestCarrierToNotification : public simgear::Emesary::INotification
{
public:
NearestCarrierToNotification(SGGeod _comparisonPosition) : position(),
comparisonPosition(_comparisonPosition),
heading(0),
vckts(0),
deckheight(0),
distanceMeters(std::numeric_limits<double>::max()),
carrier(0)
{
}
/*virtual ~NearestCarrierToNotification()
{
if (position)
delete position;
position = 0;
}*/
const char* GetType() override { return "NearestCarrierToNotification"; }
const SGGeod* GetPosition() const { return position; }
double GetHeading() const { return heading; }
double GetVckts() const { return vckts; }
double GetDeckheight() const { return deckheight; }
const class FGAICarrier* GetCarrier() const { return carrier; }
double GetDistanceMeters() const { return distanceMeters; }
std::string GetCarrierIdent() const { return carrierIdent; }
double GetDistanceToMeters(const SGGeod& pos) const
{
if (carrier)
return SGGeodesy::distanceM(comparisonPosition, pos);
else
return std::numeric_limits<double>::max()-1;
}
void SetPosition(SGGeod* _position) { position = _position; }
void SetHeading(double _heading) { heading = _heading; }
void SetVckts(double _vckts) { vckts = _vckts; }
void SetDeckheight(double _deckheight) { deckheight = _deckheight; }
void SetCarrier(FGAICarrier* _carrier, SGGeod *_position)
{
carrier = _carrier;
distanceMeters = SGGeodesy::distanceM(comparisonPosition, *_position);
position = _position;
}
void SetDistanceMeters(double _distanceMeters) { distanceMeters = _distanceMeters; }
void SetCarrierIdent(std::string _carrierIdent) { carrierIdent = _carrierIdent; }
SGPropertyNode_ptr GetViewPositionLatNode() { return viewPositionLatDegNode; }
SGPropertyNode_ptr GetViewPositionLonNode() { return viewPositionLonDegNode; }
SGPropertyNode_ptr GetViewPositionAltNode() { return viewPositionAltFtNode; }
void SetViewPositionLatNode(SGPropertyNode_ptr _viewPositionLatNode) { viewPositionLatDegNode = _viewPositionLatNode; }
void SetViewPositionLonNode(SGPropertyNode_ptr _viewPositionLonNode) { viewPositionLonDegNode = _viewPositionLonNode; }
void SetViewPositionAltNode(SGPropertyNode_ptr _viewPositionAltNode) { viewPositionAltFtNode = _viewPositionAltNode; }
protected:
SGGeod *position;
SGGeod comparisonPosition;
SGPropertyNode_ptr viewPositionLatDegNode;
SGPropertyNode_ptr viewPositionLonDegNode;
SGPropertyNode_ptr viewPositionAltFtNode;
double heading;
double vckts;
double deckheight;
double distanceMeters;
std::string carrierIdent;
class FGAICarrier* carrier;
};
#endif

1102
src/AIModel/AIShip.cxx Normal file

File diff suppressed because it is too large Load Diff

152
src/AIModel/AIShip.hxx Normal file
View File

@@ -0,0 +1,152 @@
// FGAIShip - AIBase derived class creates an AI ship
//
// Written by David Culp, started November 2003.
// with major amendments and additions by Vivian Meazza, 2004 - 2007
//
// Copyright (C) 2003 David P. Culp - davidculp2@comcast.net
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation; either version 2 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#pragma once
#include <string_view>
#include <simgear/scene/material/mat.hxx>
#include "AIBase.hxx"
#include "AIFlightPlan.hxx"
class FGAIManager;
class FGAIShip : public FGAIBase {
public:
FGAIShip(object_type ot = object_type::otShip);
virtual ~FGAIShip() = default;
string_view getTypeString(void) const override { return "ship"; }
void readFromScenario(SGPropertyNode* scFileNode) override;
bool init(ModelSearchOrder searchOrder) override;
void bind() override;
void update(double dt) override;
void reinit() override;
double getDefaultModelRadius() override { return 200.0; }
void setRudder(float r);
void setRoll(double rl);
void ProcessFlightPlan( double dt);
void AccelTo(double speed);
void PitchTo(double angle);
void RollTo(double angle);
#if 0
void YawTo(double angle);
#endif
void ClimbTo(double altitude);
void TurnTo(double heading);
void setCurrName(const std::string&);
void setNextName(const std::string&);
void setPrevName(const std::string&);
void setLeadAngleGain(double g);
void setLeadAngleLimit(double l);
void setLeadAngleProp(double p);
void setRudderConstant(double rc);
void setSpeedConstant(double sc);
void setFixedTurnRadius(double ft);
void setRollFactor(double rf);
void setTunnel(bool t);
void setInitialTunnel(bool t);
void setWPNames();
void setWPPos();
double sign(double x);
bool _hdg_lock = false;
bool _serviceable = false;
bool _waiting;
bool _new_waypoint;
bool _tunnel, _initial_tunnel;
bool _restart;
double _rudder_constant = 0.0;
double _speed_constant = 0.0;
double _hdg_constant, _limit;
double _elevation_ft;
double _missed_range = 0.0;
double _tow_angle;
double _wait_count = 0.0;
double _missed_count,_wp_range;
double _dt_count, _next_run;
FGAIWaypoint* prev = nullptr; // the one behind you
FGAIWaypoint* curr = nullptr; // the one ahead
FGAIWaypoint* next = nullptr; // the next plus 1
protected:
private:
void setRepeat(bool r);
void setRestart(bool r);
void setMissed(bool m);
void setServiceable(bool s);
void Run(double dt);
void setStartTime(const std::string&);
void setUntilTime(const std::string&);
//void setWPPos();
void setWPAlt();
void setXTrackError();
SGGeod wppos;
double getRange(double lat, double lon, double lat2, double lon2) const;
double getCourse(double lat, double lon, double lat2, double lon2) const;
double getDaySeconds();
double processTimeString(const std::string& time);
bool initFlightPlan();
bool advanceFlightPlan (double elapsed_sec, double day_sec);
float _rudder = 0.0f;
float _tgt_rudder = 0.0f;
double _roll_constant, _roll_factor;
double _sp_turn_radius_ft = 0.0;
double _rd_turn_radius_ft = 0.0;
double _fixed_turn_radius = 0.0;
double _old_range, _range_rate;
double _missed_time_sec;
double _start_sec = 0.0;
double _day;
double _lead_angle;
double _lead_angle_gain = 0.0;
double _lead_angle_limit = 0.0;
double _proportion = 0.0;
double _course = 0.0;
double _xtrack_error;
double _curr_alt, _prev_alt;
std::string _prev_name, _curr_name, _next_name;
std::string _start_time, _until_time;
bool _repeat = false;
bool _fp_init;
bool _missed;
};

38
src/AIModel/AIStatic.cxx Normal file
View File

@@ -0,0 +1,38 @@
// FGAIStatic - FGAIBase-derived class creates an AI static object
//
// Written by David Culp, started Jun 2005.
//
// Copyright (C) 2005 David P. Culp - davidculp2@comcast.net
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation; either version 2 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif
#include <Main/fg_props.hxx>
#include "AIStatic.hxx"
FGAIStatic::FGAIStatic() : FGAIBase(object_type::otStatic, false) {
_searchOrder = ModelSearchOrder::DATA_ONLY;
}
void FGAIStatic::update(double dt) {
FGAIBase::update(dt);
Transform();
}

38
src/AIModel/AIStatic.hxx Normal file
View File

@@ -0,0 +1,38 @@
// FGAIStatic - AIBase derived class creates AI static object
//
// Written by David Culp, started Jun 2005.
//
// Copyright (C) 2005 David P. Culp - davidculp2@comcast.net
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation; either version 2 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#pragma once
#include <string_view>
#include "AIManager.hxx"
#include "AIBase.hxx"
class FGAIStatic : public FGAIBase {
public:
FGAIStatic();
virtual ~FGAIStatic() = default;
string_view getTypeString(void) const override { return "static"; }
void update(double dt) override;
};

142
src/AIModel/AIStorm.cxx Normal file
View File

@@ -0,0 +1,142 @@
// FGAIStorm - FGAIBase-derived class creates an AI thunderstorm or cloud
//
// Written by David Culp, started Feb 2004.
//
// Copyright (C) 2004 David P. Culp - davidculp2@comcast.net
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation; either version 2 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#include <cmath>
#include <cstdlib>
#include <string>
#include <time.h>
#include <Main/fg_props.hxx>
#include <Main/globals.hxx>
#include <Scenery/scenery.hxx>
#include "AIStorm.hxx"
FGAIStorm::FGAIStorm() : FGAIBase(object_type::otStorm, false)
{
delay = 3.6;
subflashes = 1;
timer = 0.0;
random_delay = 3.6;
flash_node = fgGetNode("/environment/lightning/flash", true);
flash_node->setBoolValue(false);
flashed = 0;
flashing = false;
subflash_index = -1;
subflash_array[0] = 1;
subflash_array[1] = 2;
subflash_array[2] = 1;
subflash_array[3] = 3;
subflash_array[4] = 2;
subflash_array[5] = 1;
subflash_array[6] = 1;
subflash_array[7] = 2;
turb_mag_node = fgGetNode("/environment/turbulence/magnitude-norm", true);
turb_rate_node = fgGetNode("/environment/turbulence/rate-hz", true);
}
void FGAIStorm::readFromScenario(SGPropertyNode* scFileNode) {
if (!scFileNode)
return;
FGAIBase::readFromScenario(scFileNode);
setDiameter(scFileNode->getDoubleValue("diameter-ft", 0.0)/6076.11549);
setHeight(scFileNode->getDoubleValue("height-msl", 5000.0));
setStrengthNorm(scFileNode->getDoubleValue("strength-norm", 1.0));
}
void FGAIStorm::update(double dt) {
FGAIBase::update(dt);
Run(dt);
Transform();
}
void FGAIStorm::Run(double dt) {
double speed_north_deg_sec;
double speed_east_deg_sec;
// convert speed to degrees per second
speed_north_deg_sec = cos(hdg / SG_RADIANS_TO_DEGREES) * speed * 1.686 / ft_per_deg_lat;
speed_east_deg_sec = sin(hdg / SG_RADIANS_TO_DEGREES) * speed * 1.686 / ft_per_deg_lon;
// set new position
pos.setLatitudeDeg( pos.getLatitudeDeg() + speed_north_deg_sec * dt);
pos.setLongitudeDeg( pos.getLongitudeDeg() + speed_east_deg_sec * dt);
// do calculations for weather radar display
UpdateRadar(manager);
// **************************************************
// * do lightning *
// **************************************************
if (timer > random_delay) {
srand((unsigned)time(0));
random_delay = delay + (rand()%3) - 1.0;
//cout << "random_delay = " << random_delay << endl;
timer = 0.0;
flashing = true;
subflash_index++;
if (subflash_index == 8) subflash_index = 0;
subflashes = subflash_array[subflash_index];
}
if (flashing) {
if (flashed < subflashes) {
timer += dt;
if (timer < 0.1) {
flash_node->setBoolValue(true);
} else {
flash_node->setBoolValue(false);
if (timer > 0.2) {
timer = 0.0;
flashed++;
}
}
} else {
flashing = false;
timer = 0.0;
flashed = 0;
}
} else {
timer += dt;
}
// ***************************************************
// * do turbulence *
// ***************************************************
// copy user's position from the AIManager
double d = dist(SGVec3d::fromGeod(pos), globals->get_aircraft_position_cart());
double rangeNm = d * SG_METER_TO_NM;
double user_altitude = globals->get_aircraft_position().getElevationFt();
if (rangeNm < (diameter * 0.5) &&
user_altitude > (altitude_ft - 1000.0) &&
user_altitude < height) {
turb_mag_node->setDoubleValue(strength_norm);
turb_rate_node->setDoubleValue(0.5);
}
}

68
src/AIModel/AIStorm.hxx Normal file
View File

@@ -0,0 +1,68 @@
// FGAIStorm - AIBase derived class creates an AI thunderstorm
//
// Written by David Culp, started Feb 2004.
//
// Copyright (C) 2004 David P. Culp - davidculp2@comcast.net
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation; either version 2 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#pragma once
#include <string>
#include <string_view>
#include "AIManager.hxx"
#include "AIBase.hxx"
class FGAIStorm : public FGAIBase {
public:
FGAIStorm();
virtual ~FGAIStorm() = default;
string_view getTypeString(void) const override { return "thunderstorm"; }
void readFromScenario(SGPropertyNode* scFileNode) override;
void update(double dt) override;
inline void setStrengthNorm( double s ) { strength_norm = s; };
inline void setDiameter( double d ) { diameter = d; };
inline void setHeight( double h ) { height = h; };
inline double getStrengthNorm() const { return strength_norm; };
inline double getDiameter() const { return diameter; };
inline double getHeight() const { return height; };
private:
double diameter = 0.0; // diameter of turbulence zone, in nm
double height = 0.0; // top of turbulence zone, in feet MSL
double strength_norm = 0.0; // strength of turbulence
void Run(double dt);
// lightning stuff
double delay; // average time (sec) between lightning flashes
int subflashes; // number of subflashes per flash
double random_delay; // delay +/- random number
double timer;
SGPropertyNode_ptr flash_node;
int flashed; // number of subflashes already done this flash
bool flashing; // true if currently flashing;
int subflash_array[8];
int subflash_index;
// turbulence stuff
SGPropertyNode_ptr turb_mag_node;
SGPropertyNode_ptr turb_rate_node;
};

94
src/AIModel/AISwiftAircraft.cpp Executable file
View File

@@ -0,0 +1,94 @@
// AISwiftAircraft.cpp - Derived AIBase class for swift aircraft
//
// Copyright (C) 2020 - swift Project Community / Contributors (http://swift-project.org/)
// Written by Lars Toenning <dev@ltoenning.de> started on April 2020.
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation; either version 2 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#include "AISwiftAircraft.h"
#include <Main/globals.hxx>
FGAISwiftAircraft::FGAISwiftAircraft(const std::string& callsign, const std::string& modelString) : FGAIBaseAircraft(object_type::otStatic)
{
std::size_t pos = modelString.find("/Aircraft/"); // Only supporting AI models from FGDATA/AI/Aircraft for now
if(pos != std::string::npos)
model_path.append(modelString.substr(pos));
else
model_path.append("INVALID_PATH");
setCallSign(callsign);
_searchOrder = ModelSearchOrder::PREFER_AI;
}
void FGAISwiftAircraft::updatePosition(const SGGeod &position, const SGVec3d &orientation, double groundspeed, bool initPos)
{
m_initPos = initPos;
_setLatitude(position.getLatitudeDeg());
_setLongitude(position.getLongitudeDeg());
_setAltitude(position.getElevationFt());
setPitch(orientation.x());
setBank(orientation.y());
setHeading(orientation.z());
setSpeed(groundspeed);
}
void FGAISwiftAircraft::update(double dt)
{
FGAIBase::update(dt);
Transform();
}
double FGAISwiftAircraft::getGroundElevation(const SGGeod& pos) const
{
if(!m_initPos) { return std::numeric_limits<double>::quiet_NaN(); }
double alt = 0;
SGGeod posReq;
posReq.setElevationFt(30000);
posReq.setLatitudeDeg(pos.getLatitudeDeg());
posReq.setLongitudeDeg(pos.getLongitudeDeg());
if(this->getGroundElevationM(posReq, alt, nullptr))
return alt;
return std::numeric_limits<double>::quiet_NaN();
}
void FGAISwiftAircraft::setPlaneSurface(const AircraftSurfaces& surfaces)
{
GearPos(surfaces.gear);
FlapsPos(surfaces.flaps);
SpoilerPos(surfaces.spoilers);
SpeedBrakePos(surfaces.spoilers);
BeaconLight(surfaces.beaconLight);
LandingLight(surfaces.landingLight);
NavLight(surfaces.navLight);
StrobeLight(surfaces.strobeLight);
TaxiLight(surfaces.taxiLight);
}
void FGAISwiftAircraft::setPlaneTransponder(const AircraftTransponder& transponder)
{
m_transponderCodeNode->setIntValue(transponder.code);
m_transponderCModeNode->setBoolValue(transponder.modeC);
m_transponderIdentNode->setBoolValue(transponder.ident);
}
void FGAISwiftAircraft::initProps()
{
// Setup node properties
m_transponderCodeNode = _getProps()->getNode("swift/transponder/code", true);
m_transponderCModeNode = _getProps()->getNode("swift/transponder/c-mode", true);
m_transponderIdentNode = _getProps()->getNode("swift/transponder/ident", true);
}

View File

@@ -0,0 +1,89 @@
// AISwiftAircraft.h - Derived AIBase class for swift aircraft
//
// Copyright (C) 2020 - swift Project Community / Contributors (http://swift-project.org/)
// Written by Lars Toenning <dev@ltoenning.de> started on April 2020.
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation; either version 2 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#pragma once
#include <string>
#include <string_view>
#include <utility>
#include "AIBaseAircraft.hxx"
struct AircraftTransponder
{
AircraftTransponder(std::string callsign, int code, bool modeC, bool ident)
: callsign(std::move(callsign)), code(code), modeC(modeC), ident(ident)
{}
std::string callsign;
int code;
bool modeC;
bool ident;
};
struct AircraftSurfaces
{
AircraftSurfaces(std::string callsign, double gear, double flaps, double spoilers, double speedBrake, double slats, double wingSweeps,
double thrust, double elevator, double rudder, double aileron, bool landingLight, bool taxiLight, bool beaconLight,
bool strobeLight, bool navLight, int lightPattern)
: callsign(std::move(callsign)), gear(gear), flaps(flaps), spoilers(spoilers), speedBrake(speedBrake), slats(slats), wingSweeps(wingSweeps),
thrust(thrust), elevator(elevator), rudder(rudder), aileron(aileron), landingLight(landingLight), taxiLight(taxiLight), beaconLight(beaconLight),
strobeLight(strobeLight), navLight(navLight), lightPattern(lightPattern){}
std::string callsign;
double gear;
double flaps;
double spoilers;
double speedBrake;
double slats;
double wingSweeps;
double thrust;
double elevator;
double rudder;
double aileron;
bool landingLight;
bool taxiLight;
bool beaconLight;
bool strobeLight;
bool navLight;
int lightPattern;
};
class FGAISwiftAircraft : public FGAIBaseAircraft
{
public:
FGAISwiftAircraft(const std::string& callsign, const std::string& modelString);
virtual ~FGAISwiftAircraft() = default;
string_view getTypeString() const override { return "swift"; }
void update(double dt) override;
void updatePosition(const SGGeod &position, const SGVec3d &orientation, double groundspeed, bool initPos);
double getGroundElevation(const SGGeod& pos) const;
void initProps();
void setPlaneSurface(const AircraftSurfaces& surfaces);
void setPlaneTransponder(const AircraftTransponder& transponder);
private:
bool m_initPos = false;
// Property Nodes for transponder and parts
SGPropertyNode_ptr m_transponderCodeNode;
SGPropertyNode_ptr m_transponderCModeNode;
SGPropertyNode_ptr m_transponderIdentNode;
};

74
src/AIModel/AITanker.cxx Normal file
View File

@@ -0,0 +1,74 @@
// AITanker.cxx Based on David Culp's AIModel code
// - Tanker specific code isolated from AI Aircraft code
// by Thomas Foerster, started June 2007
//
//
// Original code written by David Culp, started October 2003.
// - davidculp2@comcast.net/
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation; either version 2 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#include "AITanker.hxx"
using std::string;
FGAITanker::FGAITanker(FGAISchedule* ref): FGAIAircraft(ref){
}
void FGAITanker::readFromScenario(SGPropertyNode* scFileNode) {
if (!scFileNode)
return;
FGAIAircraft::readFromScenario(scFileNode);
setTACANChannelID(scFileNode->getStringValue("TACAN-channel-ID",""));
setName(scFileNode->getStringValue("name", "Tanker"));
}
void FGAITanker::bind() {
FGAIAircraft::bind();
tie("refuel/contact", SGRawValuePointer<bool>(&contact));
tie("position/altitude-agl-ft",SGRawValuePointer<double>(&altitude_agl_ft));
props->setStringValue("navaids/tacan/channel-ID", TACAN_channel_id.c_str());
props->setStringValue("name", _name.c_str());
props->setBoolValue("tanker", true);
}
void FGAITanker::setTACANChannelID(const string& id) {
TACAN_channel_id = id;
}
void FGAITanker::Run(double dt) {
double start = pos.getElevationFt() + 1000.0;
altitude_agl_ft = _getAltitudeAGL(pos, start);
//###########################//
// do calculations for radar //
//###########################//
double range_ft2 = UpdateRadar(manager);
// check if radar contact
if ( (range_ft2 < 250.0 * 250.0) && (y_shift > 0.0) && (elevation > 0.0) ) {
contact = true;
} else {
contact = false;
}
}
void FGAITanker::update(double dt) {
FGAIAircraft::update(dt);
Run(dt);
Transform();
}

55
src/AIModel/AITanker.hxx Normal file
View File

@@ -0,0 +1,55 @@
// AITanker.hxx Based on David Culp's AIModel code
// - Tanker specific code isolated from AI Aircraft code
// by Thomas Foerster, started June 2007
//
//
// Original code written by David Culp, started October 2003.
// - davidculp2@comcast.net/
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation; either version 2 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#pragma once
#include <string_view>
#include "AIAircraft.hxx"
/**
* An AI tanker for air-air refueling.
*
* This class is just a refactoring of the AA refueling related code in FGAIAircraft. The idea
* is to have a clean generic AIAircraft class without any special functionality. In your
* scenario specification use 'tanker' as the scenario type to use this class.
*
* @author Thomas F<>ster <t.foerster@biologie.hu-berlin.de>
*/
class FGAITanker : public FGAIAircraft {
public:
FGAITanker(FGAISchedule* ref = 0);
virtual ~FGAITanker() = default;
string_view getTypeString(void) const override { return "tanker"; }
void readFromScenario(SGPropertyNode* scFileNode) override;
void bind() override;
void setTACANChannelID(const std::string& id);
private:
std::string TACAN_channel_id; // The TACAN channel of this tanker
bool contact = false; // set if this tanker is within fuelling range
virtual void Run(double dt);
void update(double dt) override;
};

359
src/AIModel/AIThermal.cxx Normal file
View File

@@ -0,0 +1,359 @@
// FGAIThermal - FGAIBase-derived class creates an AI thermal
//
// Copyright (C) 2004  David P. Culp - davidculp2@comcast.net
//
// An attempt to refine the thermal shape and behaviour by WooT 2009
//
// Copyright (C) 2009 Patrice Poly ( WooT )
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation; either version 2 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#include <cmath>
#include <string>
#include <Main/fg_props.hxx>
#include <Main/globals.hxx>
#include <Scenery/scenery.hxx>
#include "AIThermal.hxx"
FGAIThermal::FGAIThermal() : FGAIBase(object_type::otThermal, false)
{
altitude_agl_ft = 0.0;
max_strength = 6.0;
diameter = 0.5;
strength = factor = 0.0;
cycle_timer = 60*(rand()%31); // some random in the birth time
ground_elev_ft = 0.0;
dt_count=0.9;
alt=0.0;
}
void FGAIThermal::readFromScenario(SGPropertyNode* scFileNode) {
if (!scFileNode)
return;
FGAIBase::readFromScenario(scFileNode);
setMaxStrength(scFileNode->getDoubleValue("strength-fps", 8.0));
setDiameter(scFileNode->getDoubleValue("diameter-ft", 0.0)/6076.11549);
setHeight(scFileNode->getDoubleValue("height-msl", 5000.0));
}
bool FGAIThermal::init(ModelSearchOrder searchOrder) {
factor = 8.0 * max_strength / (diameter * diameter * diameter);
setAltitude( height );
_surface_wind_from_deg_node =
fgGetNode("/environment/config/boundary/entry[0]/wind-from-heading-deg", true);
_surface_wind_speed_node =
fgGetNode("/environment/config/boundary/entry[0]/wind-speed-kt", true);
_aloft_wind_from_deg_node =
fgGetNode("/environment/config/aloft/entry[2]/wind-from-heading-deg", true);
_aloft_wind_speed_node =
fgGetNode("/environment/config/aloft/entry[2]/wind-speed-kt", true);
do_agl_calc = true;
return FGAIBase::init(searchOrder);
}
void FGAIThermal::bind() {
FGAIBase::bind();
tie("position/altitude-agl-ft", SGRawValuePointer<double>(&altitude_agl_ft));
tie("alt-rel", SGRawValuePointer<double>(&alt_rel));
tie("time", SGRawValuePointer<double>(&time));
tie("xx", SGRawValuePointer<double>(&xx));
tie("is-forming", SGRawValuePointer<bool>(&is_forming));
tie("is-formed", SGRawValuePointer<bool>(&is_formed));
tie("is-dying", SGRawValuePointer<bool>(&is_dying));
tie("is-dead", SGRawValuePointer<bool>(&is_dead));
}
void FGAIThermal::update(double dt) {
FGAIBase::update(dt);
Run(dt);
Transform();
}
//the formula to get the available portion of VUpMax depending on altitude
//returns a double between 0 and 1
double FGAIThermal::get_strength_fac(double alt_frac) {
double PI = 4.0 * atan(1.0);
double fac = 0.0;
if ( alt_frac <=0.0 ) { // do submarines get thermals ?
fac = 0.0;
} else if ((alt_frac > 0.0) && (alt_frac <= 0.1)) { // ground layer
fac = ( 0.1*( pow( (10.0*alt_frac),10.0) ) );
} else if ((alt_frac > 0.1) && (alt_frac <= 1.0)) { // main body of the thermal
fac = 0.4175 - 0.5825* ( cos ( PI* (1.0-sqrt(alt_frac) ) +PI) ) ;
} else if ((alt_frac > 1.0) && (alt_frac < 1.1)) { //above the ceiling, but not above the cloud
fac = (0.5 * ( 1.0 + cos ( PI*( (-2.0*alt_frac)*5.0 ) ) ) );
} else if (alt_frac >= 1.1) { //above the cloud
fac = 0.0;
}
return fac;
}
void FGAIThermal::Run(double dt) {
// *****************************************
// the thermal characteristics and variables
// *****************************************
cycle_timer += dt ;
// time
// the time needed for the thermal to be completely formed
double tmin1 = 5.0 ;
// the time when the thermal begins to die
double tmin2 = 20.0 ;
// the time when the thermal is completely dead
double tmin3 = 25.0;
double alive_cycle_time = tmin3*60.0;
//the time of the complete cycle, including a period of inactivity
double tmin4 = 30.0;
// some times expressed in a fraction of tmin3;
double t1 = tmin1/tmin3 ;
double t2 = tmin2/tmin3 ;
double t3 = 1.0 ;
double t4 = tmin4/tmin3;
// the time elapsed since the thermal was born, in a 0-1 fraction of tmin3
time = cycle_timer/alive_cycle_time;
// comment above and uncomment below to freeze the time cycle
// time=0.5;
if ( time >= t4) {
cycle_timer = 60*(rand()%31);
}
//the position of the thermal 'top'
double thermal_foot_lat = (pos.getLatitudeDeg());
double thermal_foot_lon = (pos.getLongitudeDeg());
//the max updraft one can expect in this thermal
double MaxUpdraft=max_strength;
//the max sink one can expect in this thermal, this is a negative number
double MinUpdraft=-max_strength*0.25;
//the fraction of MaxUpdraft one can expect at our height and time
double maxstrengthavail;
//max updraft at the user altitude and time
double v_up_max;
//min updraft at the user altitude and time, this is a negative number
double v_up_min;
double wind_speed;
//max radius of the the thermal, including the sink area
double Rmax = diameter/2.0;
// 'shaping' of the thermal, the higher, the more conical the thermal- between 0 and 1
double shaping=0.8;
//the radius of the thermal at our altitude in FT, including sink
double Rsink;
//the relative radius of the thermal where we have updraft, between 0 an 1
double r_up_frac=0.9;
//radius of the thermal where we have updraft, in FT
double Rup;
//how far are we from the thermal center at our altitude in FEET
double dist_center;
//the position of the center of the thermal slice at our altitude
double slice_center_lon;
double slice_center_lat;
//we need to know the thermal foot AGL altitude
//we could do this only once, as thermal don't move
//but then agl info is lost on user reset
//so we only do this every 10 seconds to save cpu
dt_count += dt;
if (dt_count >= 10.0 ) {
if (getGroundElevationM(SGGeod::fromGeodM(pos, 20000), alt, 0)) {
ground_elev_ft = alt * SG_METER_TO_FEET;
do_agl_calc = false;
altitude_agl_ft = height - ground_elev_ft ;
dt_count = 0.0;
}
}
//user altitude relative to the thermal height, seen AGL from the thermal foot
double user_altitude = globals->get_aircraft_position().getElevationFt();
if ( user_altitude < 1.0 ) { user_altitude = 1.0 ;}; // an ugly way to avoid NaNs for users at alt 0
double user_altitude_agl= ( user_altitude - ground_elev_ft ) ;
alt_rel = user_altitude_agl / altitude_agl_ft;
//the updraft user feels !
double Vup;
// *********************
// environment variables
// *********************
// the wind heading at the user alt
double wind_heading_rad;
// the "ambient" sink outside thermals
double global_sink = -1.0;
// **************
// some constants
// **************
double PI = 4.0 * atan(1.0);
// ******************
// thermal main cycle
// ******************
//we get the max strenght proportion we can expect at the time and altitude, formuled between 0 and 1
//double xx;
if (time <= t1) {
xx = ( time / t1 );
maxstrengthavail = xx* get_strength_fac ( alt_rel / xx );
is_forming = true;
is_formed = false;
is_dying = false;
is_dead = false;
} else if ((time > t1) && (time <= t2)) {
maxstrengthavail = get_strength_fac ( (alt_rel) );
is_forming = false;
is_formed = true;
is_dying = false;
is_dead = false;
} else if ((time > t2) && (time <= t3)) {
xx= ( ( time - t2) / (1.0 - t2) ) ;
maxstrengthavail = get_strength_fac ( alt_rel - xx );
is_forming = false;
is_formed = false;
is_dying = true;
is_dead = false;
} else {
maxstrengthavail = 0.0;
is_forming = false;
is_formed = false;
is_dying = false;
is_dead = true;
}
//we get the diameter of the thermal slice at the user altitude
//the thermal has a slight conic shape
if ((alt_rel >= 0.0) && (alt_rel < 1.0)) {
Rsink = (shaping * Rmax) + (((1.0 - shaping) * Rmax * alt_rel) / altitude_agl_ft); // in the main thermal body
} else if ((alt_rel >= 1.0) && (alt_rel < 1.1)) {
Rsink = (Rmax / 2.0) * (1.0 + cos((10.0 * PI * alt_rel) - (2.0 * PI))); // above the ceiling
} else {
Rsink = 0.0; // above the cloud
}
//we get the portion of the diameter that produces lift
Rup = r_up_frac * Rsink ;
//we now determine v_up_max and VupMin depending on our altitude
v_up_max = maxstrengthavail * MaxUpdraft;
v_up_min = maxstrengthavail * MinUpdraft;
// Now we know, for current t and alt, v_up_max, VupMin, Rup, Rsink.
// We still need to know how far we are from the thermal center
// To determine the thermal inclinaison in the wind, we use a ugly approximation,
// in which we say the thermal bends 20° (0.34906 rad ) for 10 kts wind.
// We move the thermal foot upwind, to keep the cloud model over the "center" at ceiling level.
// the displacement distance of the center of the thermal slice, at user altitude,
// and relative to a hipothetical vertical thermal, would be:
// get surface and 9000 ft wind
double ground_wind_from_deg = _surface_wind_from_deg_node->getDoubleValue();
double ground_wind_speed_kts = _surface_wind_speed_node->getDoubleValue();
double aloft_wind_from_deg = _aloft_wind_from_deg_node->getDoubleValue();
double aloft_wind_speed_kts = _aloft_wind_speed_node->getDoubleValue();
double ground_wind_from_rad = (PI/2.0) - PI*( ground_wind_from_deg/180.0);
double aloft_wind_from_rad = (PI/2.0) - PI*( aloft_wind_from_deg/180.0);
wind_heading_rad= PI+ 0.5*( ground_wind_from_rad + aloft_wind_from_rad );
wind_speed = ground_wind_speed_kts + user_altitude * ( (aloft_wind_speed_kts -ground_wind_speed_kts ) / 9000.0 );
double dt_center_alt = -(tan (0.034906*wind_speed)) * ( altitude_agl_ft-user_altitude_agl );
// now, lets find how far we are from this shifted slice
double dt_slice_lon_FT = ( dt_center_alt * cos ( wind_heading_rad ));
double dt_slice_lat_FT = ( dt_center_alt * sin ( wind_heading_rad ));
double dt_slice_lon = dt_slice_lon_FT / ft_per_deg_lon;
double dt_slice_lat = dt_slice_lat_FT / ft_per_deg_lat;
slice_center_lon = thermal_foot_lon + dt_slice_lon;
slice_center_lat = thermal_foot_lat + dt_slice_lat;
dist_center = SGGeodesy::distanceNm(SGGeod::fromDeg(slice_center_lon, slice_center_lat),
globals->get_aircraft_position());
// Now we can calculate Vup
if (max_strength >= 0.0) { // this is a thermal
if ((dist_center >= 0.0) && (dist_center < Rup)) { //user is in the updraft area
Vup = v_up_max * cos(dist_center * PI / (2.0 * Rup));
} else if ((dist_center > Rup) && (dist_center <= ((Rup + Rsink) / 2.0))) { //user in the 1st half of the sink area
Vup = v_up_min * cos((dist_center - (Rup + Rsink) / 2.0) * PI / (2.0 * ((Rup + Rsink) / 2.0 - Rup)));
} else if ((dist_center > ((Rup + Rsink) / 2.0)) && dist_center <= Rsink) { // user in the 2nd half of the sink area
Vup = (global_sink + v_up_min) / 2.0 + (global_sink - v_up_min) / 2.0 * cos((dist_center - Rsink) * PI / ((Rsink - Rup) / 2.0));
} else { // outside the thermal
Vup = global_sink;
}
}
else { // this is a sink, we don't want updraft on the sides, nor do we want to feel sink near or above ceiling and ground
if (alt_rel <= 1.1) {
double fac = (1.0 - (1.0 - 1.815 * alt_rel) * (1.0 - 1.815 * alt_rel));
Vup = fac * (global_sink + ((v_up_max - global_sink) / 2.0) * (1.0 + cos(dist_center * PI / Rmax)));
} else {
Vup = global_sink;
}
}
//correct for no global sink above clouds and outside thermals
if ( ( (alt_rel > 1.0) && (alt_rel <1.1)) && ( dist_center > Rsink ) ) {
Vup = global_sink * ( 11.0 -10.0 * alt_rel );
}
if ( alt_rel >= 1.1 ) {
Vup = 0.0;
}
strength = Vup;
range = dist_center;
}

92
src/AIModel/AIThermal.hxx Normal file
View File

@@ -0,0 +1,92 @@
// FGAIThermal - FGAIBase-derived class creates an AI thermal
//
// Original by Written by David Culp
//
// An attempt to refine the thermal shape and behaviour by WooT 2009
//
// Copyright (C) 2009 Patrice Poly ( WooT )
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation; either version 2 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#pragma once
#include <string_view>
#include "AIBase.hxx"
#include "AIManager.hxx"
#include <string>
class FGAIThermal : public FGAIBase {
public:
FGAIThermal();
virtual ~FGAIThermal() = default;
string_view getTypeString(void) const override { return "thermal"; }
void readFromScenario(SGPropertyNode* scFileNode) override;
bool init(ModelSearchOrder searchOrder) override;
void bind() override;
void update(double dt) override;
inline void setMaxStrength( double s ) { max_strength = s; };
inline void setDiameter( double d ) { diameter = d; };
inline void setHeight( double h ) { height = h; };
inline void setMaxUpdraft( double lift ) { v_up_max = lift; };
inline void setMinUpdraft( double sink ) { v_up_min = sink; };
inline void setR_up_frac( double r ) { r_up_frac = r; };
inline double getStrength() const { return strength; };
inline double getDiameter() const { return diameter; };
inline double getHeight() const { return height; };
inline double getV_up_max() const { return v_up_max; };
inline double getV_up_min() const { return v_up_min; };
inline double getR_up_frac() const { return r_up_frac; };
void getGroundElev(double dt);
private:
void Run(double dt);
double get_strength_fac(double alt_frac);
double max_strength;
double strength;
double diameter;
double height = 0.0;
double factor;
double alt_rel = 0.0;
double alt;
double v_up_max = 0.0;
double v_up_min = 0.0;
double r_up_frac = 0.0;
double cycle_timer;
double dt_count;
double time = 0.0;
double xx = 0.0;
double ground_elev_ft; // ground level in ft
bool do_agl_calc = false;
bool is_forming = false;
bool is_formed = false;
bool is_dying = false;
bool is_dead = false;
SGPropertyNode_ptr _surface_wind_from_deg_node;
SGPropertyNode_ptr _surface_wind_speed_node;
SGPropertyNode_ptr _aloft_wind_from_deg_node;
SGPropertyNode_ptr _aloft_wind_speed_node;
};

531
src/AIModel/AIWingman.cxx Normal file
View File

@@ -0,0 +1,531 @@
// FGAIWingman - FGAIBllistic-derived class creates an AI Wingman
//
// Written by Vivian Meazza, started February 2008.
// - vivian.meazza at lineone.net
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation; either version 2 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif
#include <simgear/sg_inlines.h>
#include <Main/fg_props.hxx>
#include "AIWingman.hxx"
FGAIWingman::FGAIWingman() : FGAIBallistic(object_type::otWingman),
_formate_to_ac(true),
_break(false),
_join(false),
_break_angle(-90),
_coeff_hdg(5.0),
_coeff_pch(5.0),
_coeff_bnk(5.0),
_coeff_spd(2.0)
{
invisible = false;
_parent="";
tgt_heading = 250;
}
void FGAIWingman::readFromScenario(SGPropertyNode* scFileNode) {
if (!scFileNode)
return;
FGAIBase::readFromScenario(scFileNode);
setAzimuth(scFileNode->getDoubleValue("azimuth", 0.0));
setElevation(scFileNode->getDoubleValue("elevation", 0.0));
setLife(scFileNode->getDoubleValue("life", -1));
setNoRoll(scFileNode->getBoolValue("no-roll", false));
setName(scFileNode->getStringValue("name", "Wingman"));
setParentName(scFileNode->getStringValue("parent", ""));
setSubID(scFileNode->getIntValue("SubID", 0));
setXoffset(scFileNode->getDoubleValue("x-offset", 0.0));
setYoffset(scFileNode->getDoubleValue("y-offset", 0.0));
setZoffset(scFileNode->getDoubleValue("z-offset", 0.0));
setPitchoffset(scFileNode->getDoubleValue("pitch-offset", 0.0));
setRolloffset(scFileNode->getDoubleValue("roll-offset", 0.0));
setYawoffset(scFileNode->getDoubleValue("yaw-offset", 0.0));
setGroundOffset(scFileNode->getDoubleValue("ground-offset", 0.0));
setFormate(scFileNode->getBoolValue("formate", true));
setMaxSpeed(scFileNode->getDoubleValue("max-speed-kts", 300.0));
setCoeffHdg(scFileNode->getDoubleValue("coefficients/heading", 5.0));
setCoeffPch(scFileNode->getDoubleValue("coefficients/pitch", 5.0));
setCoeffBnk(scFileNode->getDoubleValue("coefficients/bank", 4.0));
setCoeffSpd(scFileNode->getDoubleValue("coefficients/speed", 2.0));
}
void FGAIWingman::bind() {
FGAIBallistic::bind();
props->untie("controls/slave-to-ac");
tie("id", SGRawValueMethods<FGAIBase,int>(*this,
&FGAIBase::getID));
tie("subID", SGRawValueMethods<FGAIBase,int>(*this,
&FGAIBase::_getSubID));
tie("position/altitude-ft",
SGRawValueMethods<FGAIBase,double>(*this,
&FGAIBase::_getElevationFt,
&FGAIBase::_setAltitude));
tie("position/latitude-deg",
SGRawValueMethods<FGAIBase,double>(*this,
&FGAIBase::_getLatitude,
&FGAIBase::_setLatitude));
tie("position/longitude-deg",
SGRawValueMethods<FGAIBase,double>(*this,
&FGAIBase::_getLongitude,
&FGAIBase::_setLongitude));
tie("controls/break", SGRawValuePointer<bool>(&_break));
tie("controls/join", SGRawValuePointer<bool>(&_join));
tie("controls/formate-to-ac",
SGRawValueMethods<FGAIWingman,bool>
(*this, &FGAIWingman::getFormate, &FGAIWingman::setFormate));
tie("controls/tgt-heading-deg",
SGRawValueMethods<FGAIWingman,double>
(*this, &FGAIWingman::getTgtHdg, &FGAIWingman::setTgtHdg));
tie("controls/tgt-speed-kt",
SGRawValueMethods<FGAIWingman,double>
(*this, &FGAIWingman::getTgtSpd, &FGAIWingman::setTgtSpd));
tie("controls/break-deg-rel",
SGRawValueMethods<FGAIWingman,double>
(*this, &FGAIWingman::getBrkAng, &FGAIWingman::setBrkAng));
tie("controls/coefficients/heading",
SGRawValuePointer<double>(&_coeff_hdg));
tie("controls/coefficients/pitch",
SGRawValuePointer<double>(&_coeff_pch));
tie("controls/coefficients/bank",
SGRawValuePointer<double>(&_coeff_bnk));
tie("controls/coefficients/speed",
SGRawValuePointer<double>(&_coeff_spd));
tie("orientation/pitch-deg", SGRawValuePointer<double>(&pitch));
tie("orientation/roll-deg", SGRawValuePointer<double>(&roll));
tie("orientation/true-heading-deg", SGRawValuePointer<double>(&hdg));
tie("submodels/serviceable", SGRawValuePointer<bool>(&serviceable));
tie("load/rel-brg-to-user-deg",
SGRawValueMethods<FGAIBallistic,double>
(*this, &FGAIBallistic::getRelBrgHitchToUser));
tie("load/elev-to-user-deg",
SGRawValueMethods<FGAIBallistic,double>
(*this, &FGAIBallistic::getElevHitchToUser));
tie("velocities/vertical-speed-fps",
SGRawValueMethods<FGAIBase,double>(*this, &FGAIBase::_getVS_fps, &FGAIBase::_setVS_fps));
tie("velocities/true-airspeed-kt",
SGRawValuePointer<double>(&speed));
tie("velocities/speed-east-fps",
SGRawValuePointer<double>(&_speed_east_fps));
tie("velocities/speed-north-fps",
SGRawValuePointer<double>(&_speed_north_fps));
tie("position/x-offset",
SGRawValueMethods<FGAIBase,double>(*this, &FGAIBase::_getXOffset, &FGAIBase::setXoffset));
tie("position/y-offset",
SGRawValueMethods<FGAIBase,double>(*this, &FGAIBase::_getYOffset, &FGAIBase::setYoffset));
tie("position/z-offset",
SGRawValueMethods<FGAIBase,double>(*this, &FGAIBase::_getZOffset, &FGAIBase::setZoffset));
tie("position/tgt-x-offset",
SGRawValueMethods<FGAIBallistic,double>(*this, &FGAIBallistic::getTgtXOffset, &FGAIBallistic::setTgtXOffset));
tie("position/tgt-y-offset",
SGRawValueMethods<FGAIBallistic,double>(*this, &FGAIBallistic::getTgtYOffset, &FGAIBallistic::setTgtYOffset));
tie("position/tgt-z-offset",
SGRawValueMethods<FGAIBallistic,double>(*this, &FGAIBallistic::getTgtZOffset, &FGAIBallistic::setTgtZOffset));
}
bool FGAIWingman::init(ModelSearchOrder searchOrder)
{
if (!FGAIBallistic::init(searchOrder))
return false;
reinit();
return true;
}
void FGAIWingman::reinit() {
invisible = false;
_tgt_x_offset = _x_offset;
_tgt_y_offset = _y_offset;
_tgt_z_offset = _z_offset;
hdg = _azimuth;
pitch = _elevation;
roll = _rotation;
_ht_agl_ft = 1e10;
if(_parent != ""){
setParentNode();
}
setParentNodes(_selected_ac);
props->setStringValue("submodels/path", _path.c_str());
user_WoW_node = fgGetNode("gear/gear[1]/wow", true);
FGAIBallistic::reinit();
}
void FGAIWingman::update(double dt) {
// FGAIBallistic::update(dt);
if (_formate_to_ac){
formateToAC(dt);
Transform();
setBrkHdg(_break_angle);
}else if (_break) {
FGAIBase::update(dt);
tgt_altitude_ft = altitude_ft;
tgt_speed = speed;
tgt_roll = roll;
tgt_pitch = pitch;
Break(dt);
Transform();
} else {
Join(dt);
Transform();
}
}
double FGAIWingman::calcDistanceM(SGGeod pos1, SGGeod pos2) const {
//calculate the distance load to hitch
SGVec3d cartPos1 = SGVec3d::fromGeod(pos1);
SGVec3d cartPos2 = SGVec3d::fromGeod(pos2);
SGVec3d diff = cartPos1 - cartPos2;
double distance = norm(diff);
return distance;
}
double FGAIWingman::calcAngle(double range, SGGeod pos1, SGGeod pos2){
double angle = 0;
double distance = calcDistanceM(pos1, pos2);
double daltM = pos1.getElevationM() - pos2.getElevationM();
if (fabs(distance) < SGLimits<float>::min()) {
angle = 0;
} else {
double sAngle = daltM/range;
sAngle = SGMiscd::min(1, SGMiscd::max(-1, sAngle));
angle = SGMiscd::rad2deg(asin(sAngle));
}
return angle;
}
void FGAIWingman::formateToAC(double dt){
double p_hdg, p_pch, p_rll, p_agl, p_ht, p_wow = 0;
setTgtOffsets(dt, 25);
if (_pnode != 0) {
setParentPos();
p_hdg = _p_hdg_node->getDoubleValue();
p_pch = _p_pch_node->getDoubleValue();
p_rll = _p_rll_node->getDoubleValue();
p_ht = _p_alt_node->getDoubleValue();
setOffsetPos(_parentpos, p_hdg, p_pch, p_rll);
setSpeed(_p_spd_node->getDoubleValue());
}else {
p_hdg = manager->get_user_heading();
p_pch = manager->get_user_pitch();
p_rll = manager->get_user_roll();
p_ht = globals->get_aircraft_position().getElevationFt();
setOffsetPos(globals->get_aircraft_position(), p_hdg,p_pch, p_rll);
setSpeed(manager->get_user_speed());
}
// elapsed time has a random initialisation so that each
// wingman moves differently
_elapsed_time += dt;
// we derive a sine based factor to give us smoothly
// varying error between -1 and 1
double factor = sin(SGMiscd::deg2rad(_elapsed_time * 10));
double r_angle = 5 * factor;
double p_angle = 2.5 * factor;
double h_angle = 5 * factor;
double h_feet = 3 * factor;
p_agl = manager->get_user_agl();
p_wow = user_WoW_node->getDoubleValue();
if(p_agl <= 10 || p_wow == 1) {
_height = p_ht;
} else if (p_agl <= 150 ) {
setHt(p_ht, dt, 1.0);
} else if (p_agl <= 250) {
setHt(_offsetpos.getElevationFt() + h_feet, dt, 0.75);
} else{
setHt(_offsetpos.getElevationFt() + h_feet, dt, 0.5);
}
pos.setElevationFt(_height);
pos.setLatitudeDeg(_offsetpos.getLatitudeDeg());
pos.setLongitudeDeg(_offsetpos.getLongitudeDeg());
// these calculations are unreliable at slow speeds
// and we don't want random movement on the ground
if(speed >= 10 && p_wow != 1) {
setHdg(p_hdg + h_angle, dt, 0.9);
setPch(p_pch + p_angle + _pitch_offset, dt, 0.9);
if (roll <= 115 && roll >= -115)
setBnk(p_rll + r_angle + _roll_offset, dt, 0.5);
else
roll = p_rll + r_angle + _roll_offset;
} else {
setHdg(p_hdg, dt, 0.9);
setPch(p_pch + _pitch_offset, dt, 0.9);
setBnk(p_rll + _roll_offset, dt, 0.9);
}
setOffsetVelocity(dt, pos);
}// end formateToAC
void FGAIWingman::Break(double dt) {
Run(dt);
//calculate the turn direction: 1 = right, -1 = left
double rel_brg = calcRelBearingDeg(tgt_heading, hdg);
int turn = SGMiscd::sign(rel_brg);
// set heading and pitch
setHdg(tgt_heading, dt, _coeff_hdg);
setPch(0, dt, _coeff_pch);
if (fabs(tgt_heading - hdg) >= 10)
setBnk(45 * turn , dt, _coeff_bnk);
else
setBnk(0, dt, _coeff_bnk);
} // end Break
void FGAIWingman::Join(double dt) {
double range, bearing, az2;
double parent_hdg, parent_spd = 0;
double p_hdg, p_pch, p_rll = 0;
setTgtOffsets(dt, 25);
if (_pnode != 0) {
setParentPos();
p_hdg = _p_hdg_node->getDoubleValue();
p_pch = _p_pch_node->getDoubleValue();
p_rll = _p_rll_node->getDoubleValue();
setOffsetPos(_parentpos, p_hdg, p_pch, p_rll);
parent_hdg = _p_hdg_node->getDoubleValue();
parent_spd = _p_spd_node->getDoubleValue();
}else {
p_hdg = manager->get_user_heading();
p_pch = manager->get_user_pitch();
p_rll = manager->get_user_roll();
setOffsetPos(globals->get_aircraft_position(), p_hdg, p_pch, p_rll);
parent_hdg = manager->get_user_heading();
parent_spd = manager->get_user_speed();
}
setSpeed(parent_spd);
double distance = calcDistanceM(pos, _offsetpos);
double daltM = _offsetpos.getElevationM() - pos.getElevationM();
double limit = 10;
double hdg_l_lim = parent_hdg - limit;
SG_NORMALIZE_RANGE(hdg_l_lim, 0.0, 360.0);
double hdg_r_lim = parent_hdg + limit;
SG_NORMALIZE_RANGE(hdg_r_lim, 0.0, 360.0);
if (distance <= 2 && fabs(daltM) <= 2 &&
(hdg >= hdg_l_lim || hdg <= hdg_r_lim)){
_height = _offsetpos.getElevationFt();
_formate_to_ac = true;
_join = false;
SG_LOG(SG_AI, SG_ALERT, _name << " joined " << " RANGE " << distance
<< " SPEED " << speed );
return;
}
geo_inverse_wgs_84(pos, _offsetpos, &bearing, &az2, &range);
double rel_brg = calcRelBearingDeg(bearing, hdg);
double recip_brg = calcRecipBearingDeg(bearing);
double angle = calcAngle(distance,_offsetpos, pos);
//double approx_angle = atan2(daltM, range);
double frm_spd = 50; // formation speed
double join_rnge = 1000.0;
// double recip_parent_hdg = calcRecipBearingDeg(parent_hdg);
int turn = SGMiscd::sign(rel_brg);// turn direction: 1 = right, -1 = left
if (range <= join_rnge && (hdg >= hdg_l_lim || hdg <= hdg_r_lim)){
//these are the rules governing joining
if ((rel_brg <= -175 || rel_brg >= 175) && range <=10 ){
// station is behind us - back up a bit
setSpeed(parent_spd - ((frm_spd/join_rnge) * range));
setHdg(recip_brg, dt, _coeff_hdg);
setPch(angle, dt, _coeff_pch);
//cout << _name << " backing up HEADING " << hdg
// << " RANGE " << range;
} else if (rel_brg >= -5 && rel_brg <= 5) {
// station is in front of us - slow down
setSpeed(parent_spd + ((frm_spd/100) * range));
//SGMiscd::clip
setHdg(bearing, dt, 1.5);
setPch(angle, dt, _coeff_pch);
//cout << _name << " slowing HEADING " << hdg
// << " RANGE " << range <<endl;
} else if ( range <=10 ){
// station is to one side - equal speed and turn towards
setSpd(parent_spd , dt, 2.0);
setSpeed(_speed);
setHdg(parent_hdg + (5 * turn), dt, _coeff_hdg);
//cout << _name << " equal speed HEADING " << hdg
// << " RANGE " << range<< endl;
} else {
// we missed it - equal speed and turn to recip
setSpd(parent_spd , dt, 2.0);
setSpeed(_speed);
setHdg(recip_brg, dt, _coeff_hdg);
//cout << _name << " WHOOPS!! missed join HEADING " << hdg
// << " RANGE " << range<< endl;
}
} else if (range <= join_rnge) {
// we missed it - equal speed and turn to recip
setSpd(parent_spd , dt, 2.0);
setSpeed(_speed);
setHdg(recip_brg , dt, _coeff_hdg);
//cout << _name << " WHOOPS!! missed approach HEADING " << hdg
// << " " << recip_brg
// /*<< " " << recip_parent_hdg*/
// << " RANGE " << range<< endl;
} else if (range > join_rnge && range <= 2000 ){
//approach phase
//cout << _name << " approach HEADING " << hdg
// << " RANGE " << range<< endl;
setSpd(parent_spd + frm_spd, dt, 2.0);
setSpeed(_speed);
setHdg(bearing, dt, _coeff_hdg);
setPch(angle, dt, _coeff_pch);
} else {
//hurry up
//cout << _name << " hurry up HEADING " << hdg
// << " RANGE " << range<< endl;
setSpd(_max_speed -10, dt, 2.0);
setSpeed(_speed);
setHdg(bearing, dt, _coeff_hdg);
setPch(angle, dt, _coeff_pch);
}
Run(dt);
// set roll
if (fabs(bearing - hdg) >= 10)
setBnk(45 * turn , dt, _coeff_bnk);
else
setBnk(0, dt, _coeff_bnk);
} // end Join
void FGAIWingman::Run(double dt) {
// don't let speed become negative
SG_CLAMP_RANGE(speed, 100.0, _max_speed);
double speed_fps = speed * SG_KT_TO_FPS;
// calculate vertical and horizontal speed components
if (speed == 0.0) {
hs = vs_fps = 0.0;
} else {
vs_fps = sin( pitch * SG_DEGREES_TO_RADIANS ) * speed_fps;
hs = cos( pitch * SG_DEGREES_TO_RADIANS ) * speed_fps;
}
//cout << "vs hs " << vs << " " << hs << endl;
//resolve horizontal speed into north and east components:
double speed_north_fps = cos(hdg / SG_RADIANS_TO_DEGREES) * hs;
double speed_east_fps = sin(hdg / SG_RADIANS_TO_DEGREES) * hs;
// convert horizontal speed (fps) to degrees per second
double speed_north_deg_sec = speed_north_fps / ft_per_deg_lat;
double speed_east_deg_sec = speed_east_fps / ft_per_deg_lon;
//get wind components
_wind_from_north = manager->get_wind_from_north();
_wind_from_east = manager->get_wind_from_east();
// convert wind speed (fps) to degrees lat/lon per second
double wind_speed_from_north_deg_sec = _wind_from_north / ft_per_deg_lat;
double wind_speed_from_east_deg_sec = _wind_from_east / ft_per_deg_lon;
//recombine the horizontal velocity components
hs = sqrt(((speed_north_fps) * (speed_north_fps))
+ ((speed_east_fps)* (speed_east_fps )));
if (hs <= 0.00001)
hs = 0;
if (vs_fps <= 0.00001 && vs_fps >= -0.00001)
vs_fps = 0;
//cout << "lat " << pos.getLatitudeDeg()<< endl;
// set new position
pos.setLatitudeDeg( pos.getLatitudeDeg()
+ (speed_north_deg_sec - wind_speed_from_north_deg_sec) * dt );
pos.setLongitudeDeg( pos.getLongitudeDeg()
+ (speed_east_deg_sec - wind_speed_from_east_deg_sec ) * dt );
pos.setElevationFt(pos.getElevationFt() + vs_fps * dt);
//cout << _name << " run hs " << hs << " vs " << vs << endl;
// recalculate total speed
if ( vs_fps == 0 && hs == 0)
speed = 0;
else
speed = sqrt( vs_fps * vs_fps + hs * hs) / SG_KT_TO_FPS;
// recalculate elevation and azimuth (velocity vectors)
pitch = atan2( vs_fps, hs ) * SG_RADIANS_TO_DEGREES;
hdg = atan2((speed_east_fps),(speed_north_fps))* SG_RADIANS_TO_DEGREES;
// rationalise heading
SG_NORMALIZE_RANGE(hdg, 0.0, 360.0);
}// end Run
// end AIWingman

144
src/AIModel/AIWingman.hxx Normal file
View File

@@ -0,0 +1,144 @@
// FGAIWingman - FGAIBllistic-derived class creates an AI Wingman
//
// Written by Vivian Meazza, started February 2008.
// - vivian.meazza at lineone.net
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation; either version 2 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#pragma once
#include <string_view>
#include <simgear/sg_inlines.h>
#include "AIBallistic.hxx"
#include "AIBase.hxx"
#include "AIManager.hxx"
class FGAIWingman : public FGAIBallistic {
public:
FGAIWingman();
virtual ~FGAIWingman() = default;
string_view getTypeString(void) const override { return "wingman"; }
void readFromScenario(SGPropertyNode* scFileNode) override;
bool init(ModelSearchOrder searchOrder) override;
void bind() override;
void reinit() override;
void update(double dt) override;
private:
void formateToAC(double dt);
void Break(double dt);
void Join(double dt);
void Run(double dt);
double getDistanceToOffset() const;
double getElevToOffset() const;
double calcAngle(double rangeM, SGGeod pos1, SGGeod pos2);
double calcDistanceM(SGGeod pos1, SGGeod pos2) const;
bool _formate_to_ac;
bool _break;
bool _join;
double _break_angle; //degrees relative
double _coeff_hdg; //dimensionless coefficient
double _coeff_pch; //dimensionless coefficient
double _coeff_bnk; //dimensionless coefficient
double _coeff_spd; //dimensionless coefficient
SGPropertyNode_ptr user_WoW_node;
inline void setFormate(bool f);
inline void setTgtHdg(double hdg);
inline void setTgtSpd(double spd);
inline void setBrkHdg(double angle);
inline void setBrkAng(double angle);
inline void setCoeffHdg(double h);
inline void setCoeffPch(double p);
inline void setCoeffBnk(double r);
inline void setCoeffSpd(double s);
inline bool getFormate() const { return _formate_to_ac;}
inline double getTgtHdg() const { return tgt_heading;}
inline double getTgtSpd() const { return tgt_speed;}
inline double getBrkAng() const { return _break_angle;}
inline SGVec3d getCartInPos(SGGeod in_pos) const;
};
void FGAIWingman::setFormate(bool f) {
_formate_to_ac = f;
}
void FGAIWingman::setTgtHdg(double h) {
tgt_heading = h;
}
void FGAIWingman::setTgtSpd(double s) {
tgt_speed = s;
}
void FGAIWingman::setBrkHdg(double a){
tgt_heading = hdg + a ;
SG_NORMALIZE_RANGE(tgt_heading, 0.0, 360.0);
}
void FGAIWingman::setBrkAng(double a){
_break_angle = a ;
SG_NORMALIZE_RANGE(_break_angle, -180.0, 180.0);
}
void FGAIWingman::setCoeffHdg(double h){
_coeff_hdg = h;
}
void FGAIWingman::setCoeffPch(double p){
_coeff_pch = p;
}
void FGAIWingman::setCoeffBnk(double b){
_coeff_bnk = b;
}
void FGAIWingman::setCoeffSpd(double s){
_coeff_spd = s;
}
//bool FGAIWingman::getFormate() const {
// return _formate_to_ac;
//}
//double FGAIWingman::getTgtHdg() const{
// return tgt_heading;
//}
//double FGAIWingman::getTgtSpd() const{
// return tgt_speed;
//}
//double FGAIWingman::getBrkAng() const{
// return _break_angle;
//}
SGVec3d FGAIWingman::getCartInPos(SGGeod in_pos) const {
SGVec3d cartPos = SGVec3d::fromGeod(in_pos);
return cartPos;
}

View File

@@ -0,0 +1,59 @@
include(FlightGearComponent)
set(SOURCES
AIAircraft.cxx
AIBallistic.cxx
AIBase.cxx
AIBaseAircraft.cxx
AICarrier.cxx
AIEscort.cxx
AIFlightPlan.cxx
AIFlightPlanCreate.cxx
AIFlightPlanCreateCruise.cxx
AIFlightPlanCreatePushBack.cxx
AIGroundVehicle.cxx
AIManager.cxx
AIMultiplayer.cxx
AIShip.cxx
AIStatic.cxx
AIStorm.cxx
AITanker.cxx
AIThermal.cxx
AIWingman.cxx
performancedata.cxx
performancedb.cxx
submodel.cxx
VectorMath.cxx
)
set(HEADERS
AIAircraft.hxx
AIBallistic.hxx
AIBase.hxx
AIBaseAircraft.hxx
AICarrier.hxx
AIEscort.hxx
AIFlightPlan.hxx
AIGroundVehicle.hxx
AIManager.hxx
AIMultiplayer.hxx
AINotifications.hxx
AIShip.hxx
AIStatic.hxx
AIStorm.hxx
AITanker.hxx
AIThermal.hxx
AIWingman.hxx
performancedata.hxx
performancedb.hxx
submodel.hxx
VectorMath.cxx
)
# Add sources if swift support is enabled
if(ENABLE_SWIFT)
list(APPEND HEADERS AISwiftAircraft.h)
list(APPEND SOURCES AISwiftAircraft.cpp)
endif()
flightgear_component(AIModel "${SOURCES}" "${HEADERS}")

View File

@@ -0,0 +1,80 @@
// VectorMath - class for vector calculations
// Written by Keith Paterson
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation; either version 2 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
#include <simgear/debug/logstream.hxx>
#include <simgear/math/SGGeod.hxx>
#include "VectorMath.hxx"
std::array<double, 2> VectorMath::innerTangentsAngle( SGGeod m1, SGGeod m2, double r1, double r2) {
std::array<double, 2> ret;
double hypothenuse = SGGeodesy::distanceM(m1, m2);
if (hypothenuse <= r1 + r2) {
SG_LOG(SG_AI, SG_WARN, "innerTangentsAngle turn circles too near");
}
double opposite = r1 + r2;
double angle = asin(opposite/hypothenuse) * SG_RADIANS_TO_DEGREES;
double crs;
if (r1>r2) {
crs = SGGeodesy::courseDeg(m2, m1);
} else {
crs = SGGeodesy::courseDeg(m1, m2);
}
ret[0] = SGMiscd::normalizePeriodic(0, 360, crs - angle);
ret[1] = SGMiscd::normalizePeriodic(0, 360, crs + angle);
return ret;
}
double VectorMath::innerTangentsLength( SGGeod m1, SGGeod m2, double r1, double r2) {
double hypothenuse = SGGeodesy::distanceM(m1, m2);
if (hypothenuse <= r1 + r2) {
SG_LOG(SG_AI, SG_WARN, "innerTangentsLength turn circles too near");
}
double opposite = r1 + r2;
double angle = asin(opposite/hypothenuse) * SG_RADIANS_TO_DEGREES;
double crs;
if (r1>r2) {
crs = SGGeodesy::courseDeg(m2, m1);
} else {
crs = SGGeodesy::courseDeg(m1, m2);
}
double angle1 = SGMiscd::normalizePeriodic(0, 360, crs - angle + 90);
double angle2 = SGMiscd::normalizePeriodic(0, 360, crs - angle - 90);
SGGeod p1 = SGGeodesy::direct(m1, angle1, r1);
SGGeod p2 = SGGeodesy::direct(m2, angle2, r2);
return SGGeodesy::distanceM(p1, p2);
}
std::array<double, 2> VectorMath::outerTangentsAngle( SGGeod m1, SGGeod m2, double r1, double r2) {
std::array<double, 2> ret;
double hypothenuse = SGGeodesy::distanceM(m1, m2);
double radiusDiff = abs(r1 - r2);
double beta = atan2( radiusDiff, hypothenuse ) * SG_RADIANS_TO_DEGREES;
double gamma = SGGeodesy::courseDeg(m1, m2);
ret[0] = SGMiscd::normalizePeriodic(0, 360, gamma - beta);
ret[1] = SGMiscd::normalizePeriodic(0, 360, gamma + beta);
return ret;
}
double VectorMath::outerTangentsLength( SGGeod m1, SGGeod m2, double r1, double r2) {
double hypothenuse = SGGeodesy::distanceM(m1, m2);
double radiusDiff = abs(r1 - r2);
double dist = sqrt(pow(hypothenuse,2)-pow(radiusDiff,2));
return dist;
}

View File

@@ -0,0 +1,34 @@
// VectorMath - class for vector calculations
// Written by Keith Paterson
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation; either version 2 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
#pragma once
#include <array>
class SGGeod;
class VectorMath {
public:
/**Angles of inner tangent between two circles.*/
static std::array<double, 2> innerTangentsAngle( SGGeod m1, SGGeod m2, double r1, double r2);
/**Length of inner tangent between two circles.*/
static double innerTangentsLength( SGGeod m1, SGGeod m2, double r1, double r2);
/**Angles of outer tangent between two circles normalized to 0-360*/
static std::array<double, 2> outerTangentsAngle( SGGeod m1, SGGeod m2, double r1, double r2);
/**Length of outer tangent between two circles.*/
static double outerTangentsLength( SGGeod m1, SGGeod m2, double r1, double r2);
};

View File

@@ -0,0 +1,221 @@
#include "config.h"
#include "performancedata.hxx"
#include <simgear/props/props.hxx>
#include "AIAircraft.hxx"
// For now, make this a define
// Later on, additional class variables can simulate settings such as braking power
// also, the performance parameters can be tweaked a little to add some personality
// to the AIAircraft.
#define BRAKE_SETTING 1.6
PerformanceData* PerformanceData::getDefaultData()
{
static PerformanceData static_instance;
return &static_instance;
}
PerformanceData::PerformanceData() :
_acceleration(4.0),
_deceleration(2.0),
_brakeDeceleration(20.0),
_climbRate(3000.0),
_descentRate(1500.0),
_vRotate(150.0),
_vTakeOff(160.0),
_vClimb(300.0),
_vCruise(430.0),
_vDescent(300.0),
_vApproach(170.0),
_vTouchdown(150.0),
_vTaxi(15.0),
_wingSpan(100.0),
_wingChord(12.0),
_weight(90000.0)
{
_rollrate = 9.0; // degrees per second
_maxbank = 30.0; // passenger friendly bank angle
}
PerformanceData::PerformanceData(PerformanceData* clone) :
_acceleration(clone->_acceleration),
_deceleration(clone->_deceleration),
_brakeDeceleration(clone->_brakeDeceleration),
_climbRate(clone->_climbRate),
_descentRate(clone->_descentRate),
_vRotate(clone->_vRotate),
_vTakeOff(clone->_vTakeOff),
_vClimb(clone->_vClimb),
_vCruise(clone->_vCruise),
_vDescent(clone->_vDescent),
_vApproach(clone->_vApproach),
_vTouchdown(clone->_vTouchdown),
_vTaxi(clone->_vTaxi)
{
_rollrate = clone->_rollrate;
_maxbank = clone->_maxbank;
}
// helper to try various names of a property, in order.
static double readRenamedProp(SGPropertyNode_ptr db, const string_list& namesToTry, double defValue)
{
for (const auto& n : namesToTry) {
auto node = db->getChild(n);
if (node) {
return node->getDoubleValue();
}
}
return defValue;
}
void PerformanceData::initFromProps(SGPropertyNode *db_node)
{
// read the values, using the existing values as defaults
_acceleration = db_node->getDoubleValue("acceleration-kts-hour", _acceleration);
_deceleration = db_node->getDoubleValue("deceleration-kts-hour", _deceleration);
_brakeDeceleration = db_node->getDoubleValue("brake-deceleration-kts-hour", _brakeDeceleration);
_climbRate = readRenamedProp(db_node, {"climb-rate-fpm", "climbrate-fpm"}, _climbRate);
_descentRate = readRenamedProp(db_node, {"descent-rate-fpm", "decentrate-fpm"}, _descentRate);
_vRotate = db_node->getDoubleValue("rotate-speed-kts", _vRotate);
_vTakeOff = db_node->getDoubleValue("takeoff-speed-kts", _vTakeOff);
_vClimb = db_node->getDoubleValue("climb-speed-kts", _vClimb);
_vCruise = db_node->getDoubleValue("cruise-speed-kts", _vCruise);
_vDescent = readRenamedProp(db_node, {"descent-speed-kts", "decent-speed-kts"}, _vDescent);
_vApproach = db_node->getDoubleValue("approach-speed-kts", _vApproach);
_vTouchdown = db_node->getDoubleValue("touchdown-speed-kts", _vTouchdown);
_vTaxi = db_node->getDoubleValue("taxi-speed-kts", _vTaxi);
_wingSpan = db_node->getDoubleValue("geometry/wing/span-ft", _wingSpan);
_wingChord = db_node->getDoubleValue("geometry/wing/chord-ft", _wingChord);
_weight = db_node->getDoubleValue("geometry/weight-lbs", _weight);
}
double PerformanceData::actualSpeed(FGAIAircraft* ac, double tgt_speed, double dt, bool maxBrakes) {
// if (tgt_speed > _vTaxi & ac->onGround()) // maximum taxi speed on ground
// tgt_speed = _vTaxi;
// bad idea for a take off roll :-)
double speed = ac->getSpeed();
double speed_diff = tgt_speed - speed;
if (speed_diff > 0.0) // need to accelerate
{
speed += _acceleration * dt;
if ( speed > tgt_speed )
speed = tgt_speed;
} else if (speed_diff < 0.0) { // decelerate
if (ac->onGround()) {
// deceleration performance is better due to wheel brakes.
double brakePower = 0;
if (maxBrakes) {
brakePower = 2;
} else {
brakePower = 1;
}
speed -= brakePower * _brakeDeceleration * dt;
} else {
speed -= _deceleration * dt;
}
if ( speed < tgt_speed )
speed = tgt_speed;
}
return speed;
}
double PerformanceData::decelerationOnGround() const
{
return _brakeDeceleration;
}
double PerformanceData::actualBankAngle(FGAIAircraft* ac, double tgt_roll, double dt) {
// check maximum bank angle
if (fabs(tgt_roll) > _maxbank)
tgt_roll = _maxbank * tgt_roll/fabs(tgt_roll);
double roll = ac->getRoll();
double bank_diff = tgt_roll - roll;
if (fabs(bank_diff) > 0.2) {
if (bank_diff > 0.0) {
roll += _rollrate * dt;
if (roll > tgt_roll)
roll = tgt_roll;
}
else if (bank_diff < 0.0) {
roll -= _rollrate * dt;
if (roll < tgt_roll)
roll = tgt_roll;
}
//while (roll > 180) roll -= 360;
//while (roll < 180) roll += 360;
}
return roll;
}
double PerformanceData::actualPitch(FGAIAircraft* ac, double tgt_pitch, double dt) {
double pitch = ac->getPitch();
double pdiff = tgt_pitch - pitch;
if (pdiff > 0.0) { // nose up
pitch += 0.005*_climbRate * dt / 3.0; //TODO avoid hardcoded 3 secs
if (pitch > tgt_pitch)
pitch = tgt_pitch;
} else if (pdiff < 0.0) { // nose down
pitch -= 0.002*_descentRate * dt / 3.0;
if (pitch < tgt_pitch)
pitch = tgt_pitch;
}
return pitch;
}
double PerformanceData::actualAltitude(FGAIAircraft* ac, double tgt_altitude, double dt) {
if (ac->onGround()) {
//FIXME: a return sensible value here
return 0.0; // 0 for now to avoid compiler errors
} else
return ac->getAltitude() + ac->getVerticalSpeedFPM()*dt/60.0;
}
double PerformanceData::actualVerticalSpeed(FGAIAircraft* ac, double tgt_vs, double dt) {
double vs = ac->getVerticalSpeedFPM();
double vs_diff = tgt_vs - vs;
if (fabs(vs_diff) > .001) {
if (vs_diff > 0.0) {
vs += _climbRate * dt / 3.0; //TODO avoid hardcoded 3 secs to attain climb rate from level flight
if (vs > tgt_vs)
vs = tgt_vs;
} else if (vs_diff < 0.0) {
vs -= _descentRate * dt / 3.0;
if (vs < tgt_vs)
vs = tgt_vs;
}
}
return vs;
}
bool PerformanceData::gearExtensible(const FGAIAircraft* ac) {
return (ac->altitudeAGL() < 900.0)
&& (ac->airspeed() < _vTouchdown * 1.25);
}

View File

@@ -0,0 +1,82 @@
#ifndef PERFORMANCEDATA_HXX
#define PERFORMANCEDATA_HXX
class FGAIAircraft;
class SGPropertyNode;
/**
Data storage for aircraft performance data. This is used to properly simulate the flight of AIAircrafts.
@author Thomas F<>rster <t.foerster@biologie.hu-berlin.de>
*/
class PerformanceData
{
public:
PerformanceData();
explicit PerformanceData(PerformanceData* clone);
void initFromProps(SGPropertyNode* props);
~PerformanceData() = default;
double actualSpeed(FGAIAircraft* ac, double tgt_speed, double dt, bool needMaxBrake);
double actualBankAngle(FGAIAircraft* ac, double tgt_roll, double dt);
double actualPitch(FGAIAircraft* ac, double tgt_pitch, double dt);
double actualHeading(FGAIAircraft* ac, double tgt_heading, double dt);
double actualAltitude(FGAIAircraft* ac, double tgt_altitude, double dt);
double actualVerticalSpeed(FGAIAircraft* ac, double tgt_vs, double dt);
bool gearExtensible(const FGAIAircraft* ac);
double climbRate() const { return _climbRate; };
double descentRate() const { return _descentRate; };
double vRotate() const { return _vRotate; };
double maximumBankAngle() const { return _maxbank; };
double acceleration() const { return _acceleration; };
double deceleration() const { return _deceleration; };
double brakeDeceleration() const { return _brakeDeceleration; };
double vTaxi() const { return _vTaxi; };
double vTakeoff() const { return _vTakeOff; };
double vClimb() const { return _vClimb; };
double vDescent() const { return _vDescent; };
double vApproach() const { return _vApproach; };
double vTouchdown() const { return _vTouchdown; };
double vCruise() const { return _vCruise; };
double wingSpan() const { return _wingSpan; };
double wingChord() const { return _wingChord; };
double weight() const { return _weight; };
double decelerationOnGround() const;
/**
@brief Last-resort fallback performance data. This is to avoid special-casing
logic in the AIAircraft code, by ensuring we always have a valid _performance pointer.
*/
static PerformanceData* getDefaultData();
private:
double _acceleration;
double _deceleration;
double _brakeDeceleration;
double _climbRate;
double _descentRate;
double _vRotate;
double _vTakeOff;
double _vClimb;
double _vCruise;
double _vDescent;
double _vApproach;
double _vTouchdown;
double _vTaxi;
double _rollrate;
double _maxbank;
// Data for aerodynamic wake computation
double _wingSpan;
double _wingChord;
double _weight;
};
#endif

View File

@@ -0,0 +1,164 @@
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "performancedb.hxx"
#include <simgear/sg_inlines.h>
#include <simgear/misc/sg_path.hxx>
#include <simgear/props/props.hxx>
#include <simgear/props/props_io.hxx>
#include <simgear/structure/exception.hxx>
#include <Main/globals.hxx>
#include <cstring>
#include <iostream>
#include "performancedata.hxx"
using std::string;
void PerformanceDB::init()
{
SGPath dbpath( globals->get_fg_root() );
dbpath.append( "/AI/Aircraft/" );
dbpath.append( "performancedb.xml");
load(dbpath);
if (getDefaultPerformance() == 0) {
SG_LOG(SG_AI, SG_WARN, "PerformanceDB: no default performance data found/loaded");
}
}
void PerformanceDB::shutdown()
{
PerformanceDataDict::iterator it;
for (it = _db.begin(); it != _db.end(); ++it) {
delete it->second;
}
_db.clear();
_aliases.clear();
}
void PerformanceDB::update(double dt)
{
SG_UNUSED(dt);
suspend();
}
void PerformanceDB::registerPerformanceData(const std::string& id, PerformanceData* data) {
//TODO if key exists already replace data "inplace", i.e. copy to existing PerfData instance
// this updates all aircraft currently using the PerfData instance.
_db[id] = data;
}
PerformanceData* PerformanceDB::getDataFor(const string& acType, const string& acClass) const
{
// first, try with the specific aircraft type, such as 738 or A322
PerformanceDataDict::const_iterator it;
it = _db.find(acType);
if (it != _db.end()) {
return it->second;
}
const string& alias = findAlias(acType);
it = _db.find(alias);
if (it != _db.end()) {
return it->second;
}
it = _db.find(acClass);
if (it == _db.end()) {
return getDefaultPerformance();
}
return it->second;
}
PerformanceData* PerformanceDB::getDefaultPerformance() const
{
PerformanceDataDict::const_iterator it = _db.find("jet_transport");
if (it == _db.end())
return NULL;
return it->second;
}
bool PerformanceDB::havePerformanceDataForAircraftType(const std::string& acType) const
{
PerformanceDataDict::const_iterator it = _db.find(acType);
if (it != _db.end())
return true;
const std::string alias(findAlias(acType));
return (_db.find(alias) != _db.end());
}
void PerformanceDB::load(const SGPath& filename)
{
SGPropertyNode root;
try {
readProperties(filename, &root);
} catch (const sg_exception &) {
SG_LOG(SG_AI, SG_ALERT,
"Error reading AI aircraft performance database: " << filename);
return;
}
SGPropertyNode * node = root.getNode("performancedb");
for (int i = 0; i < node->nChildren(); i++) {
SGPropertyNode * db_node = node->getChild(i);
if (db_node->getNameString() == "aircraft") {
PerformanceData* data = NULL;
if (db_node->hasChild("base")) {
std::string baseName = db_node->getStringValue("base");
PerformanceData* baseData = _db[baseName];
if (!baseData) {
SG_LOG(SG_AI, SG_ALERT,
"Error reading AI aircraft performance database: unknown base type " << baseName);
return;
}
// clone base data to 'inherit' from it
data = new PerformanceData(baseData);
} else {
data = new PerformanceData;
}
data->initFromProps(db_node);
std::string name = db_node->getStringValue("type", "heavy_jet");
registerPerformanceData(name, data);
} else if (db_node->getNameString() == "alias") {
std::string alias = db_node->getStringValue("alias");
if (alias.empty()) {
SG_LOG(SG_AI, SG_ALERT, "performance DB alias entry with no <alias> definition");
continue;
}
for (auto matchNode : db_node->getChildren("match")) {
std::string match = matchNode->getStringValue();
_aliases.push_back(StringPair(match, alias));
}
} else {
SG_LOG(SG_AI, SG_ALERT, "unrecognized performance DB entry:" << db_node->getNameString());
}
} // of nodes iteration
}
const string& PerformanceDB::findAlias(const string& acType) const
{
for (const auto& alias : _aliases) {
if (acType.find(alias.first) == 0) { // matched!
return alias.second;
}
} // of alias iteration
static const string empty;
return empty;
}
// Register the subsystem.
SGSubsystemMgr::Registrant<PerformanceDB> registrantPerformanceDB(
SGSubsystemMgr::POST_FDM);

View File

@@ -0,0 +1,60 @@
#pragma once
#include <string>
#include <map>
#include <vector>
class PerformanceData;
class SGPath;
#include <simgear/structure/subsystem_mgr.hxx>
/**
* Registry for performance data.
*
* Allows to store performance data for later reuse/retrieval. Just
* a simple map for now.
*
* @author Thomas F<>rster <t.foerster@biologie.hu-berlin.de>
*/
//TODO provide std::map interface?
class PerformanceDB : public SGSubsystem
{
public:
PerformanceDB() = default;
virtual ~PerformanceDB() = default;
// Subsystem API.
void init() override;
void shutdown() override;
void update(double dt) override;
// Subsystem identification.
static const char* staticSubsystemClassId() { return "aircraft-performance-db"; }
bool havePerformanceDataForAircraftType(const std::string& acType) const;
/**
* get performance data for an aircraft type / class. Type is specific, eg
* '738' or 'A319'. Class is more generic, such as 'jet_transport'.
*/
PerformanceData* getDataFor(const std::string& acType, const std::string& acClass) const;
PerformanceData* getDefaultPerformance() const;
private:
void load(const SGPath& path);
void registerPerformanceData(const std::string& id, PerformanceData* data);
typedef std::map<std::string, PerformanceData*> PerformanceDataDict;
PerformanceDataDict _db;
const std::string& findAlias(const std::string& acType) const;
typedef std::pair<std::string, std::string> StringPair;
/// alias list, to allow type/class names to share data. This is used to merge
/// related types together. Note it's ordered, and not a map since we permit
/// partial matches when merging - the first matching alias is used.
std::vector<StringPair> _aliases;
};

829
src/AIModel/submodel.cxx Normal file
View File

@@ -0,0 +1,829 @@
//// submodel.cxx - models a releasable submodel.
// Written by Dave Culp, started Aug 2004
// With major additions by Vivian Meaaza 2004 - 2007
//
// This file is in the Public Domain and comes with no warranty.
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
#include "submodel.hxx"
#include <algorithm>
#include <simgear/structure/exception.hxx>
#include <simgear/misc/sg_path.hxx>
#include <simgear/math/sg_geodesy.hxx>
#include <simgear/props/props_io.hxx>
#include <Main/fg_props.hxx>
#include <Main/util.hxx>
#include "AIBase.hxx"
#include "AIManager.hxx"
#include "AIBallistic.hxx"
using std::cout;
using std::endl;
using std::string;
using std::vector;
const double FGSubmodelMgr::lbs_to_slugs = 0.031080950172;
FGSubmodelMgr::FGSubmodelMgr()
{
x_offset = y_offset = z_offset = 0.0;
pitch_offset = 0.0;
yaw_offset = 0.0;
//out[0] = out[1] = out[2] = 0;
//string contents_node;
contrail_altitude = 30000;
_count = 0;
_found_sub = true;
}
FGAIManager* FGSubmodelMgr::aiManager()
{
return globals->get_subsystem<FGAIManager>();
}
void FGSubmodelMgr::init()
{
index = 0;
_serviceable_node = fgGetNode("/sim/submodels/serviceable", true);
_serviceable_node->setBoolValue(true);
_user_lat_node = fgGetNode("/position/latitude-deg", true);
_user_lon_node = fgGetNode("/position/longitude-deg", true);
_user_alt_node = fgGetNode("/position/altitude-ft", true);
_user_heading_node = fgGetNode("/orientation/heading-deg", true);
_user_pitch_node = fgGetNode("/orientation/pitch-deg", true);
_user_roll_node = fgGetNode("/orientation/roll-deg", true);
_user_yaw_node = fgGetNode("/orientation/yaw-deg", true);
_user_alpha_node = fgGetNode("/orientation/alpha-deg", true);
_user_speed_node = fgGetNode("/velocities/uBody-fps", true);
_user_wind_from_east_node = fgGetNode("/environment/wind-from-east-fps", true);
_user_wind_from_north_node = fgGetNode("/environment/wind-from-north-fps", true);
_user_speed_down_fps_node = fgGetNode("/velocities/speed-down-fps", true);
_user_speed_east_fps_node = fgGetNode("/velocities/speed-east-fps", true);
_user_speed_north_fps_node = fgGetNode("/velocities/speed-north-fps", true);
_contrail_altitude_node = fgGetNode("/environment/params/contrail-altitude", true);
contrail_altitude = _contrail_altitude_node->getDoubleValue();
_contrail_trigger = fgGetNode("ai/submodels/contrails", true);
_contrail_trigger->setBoolValue(false);
load();
}
void FGSubmodelMgr::postinit()
{
// postinit, so that the AI list is populated
loadAI();
while (_found_sub)
loadSubmodels();
//TODO reload submodels if an MP ac joins
//_model_added_node = fgGetNode("ai/models/model-added", true);
//_model_added_node->addChangeListener(this, false);
}
void FGSubmodelMgr::shutdown()
{
std::for_each(submodels.begin(), submodels.end(), [](submodel* sm) { delete sm; });
submodels.clear();
}
void FGSubmodelMgr::bind()
{
}
void FGSubmodelMgr::unbind()
{
submodel_iterator = submodels.begin();
while (submodel_iterator != submodels.end()) {
(*submodel_iterator)->prop->untie("count");
++submodel_iterator;
}
}
void FGSubmodelMgr::update(double dt)
{
if (!_serviceable_node->getBoolValue())
return;
_impact = false;
_hit = false;
_expiry = false;
// Check if the submodel hit an object or terrain
FGAIManager::ai_list_type sm_list(aiManager()->get_ai_list());
FGAIManager::ai_list_iterator sm_list_itr = sm_list.begin();
FGAIManager::ai_list_iterator end = sm_list.end();
for (; sm_list_itr != end; ++sm_list_itr) {
FGAIBase::object_type object_type =(*sm_list_itr)->getType();
// Continue if object is not ballistic
if (object_type != FGAIBase::object_type::otBallistic) {
continue;
}
int parent_subID = (*sm_list_itr)->_getSubID();
int id = (*sm_list_itr)->getID();
if (parent_subID == 0 || id == -1) // this entry in the list has no associated submodel
continue; // or is invalid so we can continue
//SG_LOG(SG_AI, SG_DEBUG, "Submodel: Impact " << _impact << " hit! "
// << _hit <<" parent_subID " << parent_subID);
_hit = (*sm_list_itr)->_getCollisionData();
_impact = (*sm_list_itr)->_getImpactData();
_expiry = (*sm_list_itr)->_getExpiryData();
//SG_LOG(SG_AI, SG_ALERT, "Submodel: " << (*sm_list_itr)->_getName()
// << " Impact " << _impact << " hit! " << _hit
// << " exipiry :-( " << _expiry );
if (_impact || _hit || _expiry) {
// SG_LOG(SG_AI, SG_ALERT, "Submodel: Impact " << _impact << " hit! " << _hit
//<< " exipiry :-( " << _expiry );
submodel_iterator = submodels.begin();
while (submodel_iterator != submodels.end()) {
int child_ID = (*submodel_iterator)->id;
//cout << "Impact: parent SubID " << parent_subID << " child_ID " << child_ID << endl;
if ( parent_subID == child_ID ) {
_parent_lat = (*sm_list_itr)->_getImpactLat();
_parent_lon = (*sm_list_itr)->_getImpactLon();
_parent_elev = (*sm_list_itr)->_getImpactElevFt();
_parent_hdg = (*sm_list_itr)->_getImpactHdg();
_parent_pitch = (*sm_list_itr)->_getImpactPitch();
_parent_roll = (*sm_list_itr)->_getImpactRoll();
_parent_speed = (*sm_list_itr)->_getImpactSpeed();
(*submodel_iterator)->first_time = true;
//cout << "Impact: parent SubID = child_ID elev " << _parent_elev << endl;
if (release(*submodel_iterator, dt)) {
(*sm_list_itr)->setDie(true);
//cout << "Impact: set die" << (*sm_list_itr)->_getName() << endl;
}
}
++submodel_iterator;
}
}
}
_contrail_trigger->setBoolValue(_user_alt_node->getDoubleValue() > contrail_altitude);
bool trigger = false;
int i = -1;
submodel_iterator = submodels.begin();
while (submodel_iterator != submodels.end()) {
i++;
/*SG_LOG(SG_AI, SG_DEBUG,
"Submodels: " << (*submodel_iterator)->id
<< " name " << (*submodel_iterator)->name
);*/
if ((*submodel_iterator)->trigger_node != 0) {
_trigger_node = (*submodel_iterator)->trigger_node;
trigger = _trigger_node->getBoolValue();
//cout << (*submodel_iterator)->name << "trigger node found " << trigger << endl;
}
else {
trigger = false;
//cout << (*submodel_iterator)->name << " trigger node not found " << trigger << endl;
}
if (trigger && (*submodel_iterator)->count != 0) {
//int id = (*submodel_iterator)->id;
//const string& name = (*submodel_iterator)->name;
SG_LOG(SG_AI, SG_DEBUG,
"Submodels release: " << (*submodel_iterator)->id
<< " name " << (*submodel_iterator)->name
<< " count " << (*submodel_iterator)->count
<< " slaved " << (*submodel_iterator)->slaved
);
release(*submodel_iterator, dt);
} else
(*submodel_iterator)->first_time = true;
++submodel_iterator;
}
}
bool FGSubmodelMgr::release(submodel *sm, double dt)
{
// Only run if first time or repeat is set to true
if (!sm->first_time && !sm->repeat) {
return false;
}
sm->timer += dt;
if (sm->timer < sm->delay) {
//cout << "not yet: timer " << sm->timer << " delay " << sm->delay << endl;
return false;
}
//cout << "released timer: " << sm->timer << " delay " << sm->delay << endl;
sm->timer = 0.0;
if (sm->first_time) {
dt = 0.0;
sm->first_time = false;
}
// Calculate submodel's initial conditions in world-coordinates
transform(sm);
FGAIBallistic* ballist = new FGAIBallistic;
ballist->setPath(sm->model.c_str());
ballist->setName(sm->name);
ballist->setSlaved(sm->slaved);
ballist->setRandom(sm->random);
ballist->setLifeRandomness(sm->life_randomness->get_value());
ballist->setLatitude(offsetpos.getLatitudeDeg());
ballist->setLongitude(offsetpos.getLongitudeDeg());
ballist->setAltitude(offsetpos.getElevationFt());
ballist->setAzimuthRandomError(sm->azimuth_error->get_value());
ballist->setAzimuth(IC.azimuth);
ballist->setElevationRandomError(sm->elevation_error->get_value());
ballist->setElevation(IC.elevation);
ballist->setRoll(IC.roll);
ballist->setSpeed(IC.speed / SG_KT_TO_FPS);
ballist->setWind_from_east(IC.wind_from_east);
ballist->setWind_from_north(IC.wind_from_north);
ballist->setMass(IC.mass);
ballist->setDragArea(sm->drag_area);
ballist->setLife(sm->life);
ballist->setBuoyancy(sm->buoyancy);
ballist->setWind(sm->wind);
ballist->setCdRandomness(sm->cd_randomness->get_value());
ballist->setCd(sm->cd);
ballist->setStabilisation(sm->aero_stabilised);
ballist->setNoRoll(sm->no_roll);
ballist->setCollision(sm->collision);
ballist->setExpiry(sm->expiry);
ballist->setImpact(sm->impact);
ballist->setImpactReportNode(sm->impact_report);
ballist->setFuseRange(sm->fuse_range);
ballist->setSubmodel(sm->submodel.c_str());
ballist->setSubID(sm->sub_id);
ballist->setForceStabilisation(sm->force_stabilised);
ballist->setExternalForce(sm->ext_force);
ballist->setForcePath(sm->force_path.c_str());
ballist->setXoffset(_x_offset);
ballist->setYoffset(_y_offset);
ballist->setZoffset(_z_offset);
ballist->setPitchoffset(sm->pitch_offset->get_value());
ballist->setYawoffset(sm->yaw_offset->get_value());
ballist->setParentNodes(_selected_ac);
ballist->setContentsNode(sm->contents_node);
ballist->setWeight(sm->weight);
aiManager()->attach(ballist);
if (sm->count > 0)
sm->count--;
return true;
}
void FGSubmodelMgr::load()
{
SGPropertyNode_ptr path_node = fgGetNode("/sim/submodels/path");
if (path_node) {
const int id = 0;
const string& path = path_node->getStringValue();
bool serviceable =_serviceable_node->getBoolValue();
setData(id, path, serviceable, "/ai/submodels/submodel", submodels);
}
}
void FGSubmodelMgr::transform(submodel *sm)
{
// Set initial conditions
if (sm->contents_node != 0 && !sm->slaved) {
// Get the weight of the contents (lbs) and convert to mass (slugs)
sm->contents = sm->contents_node->getChild("level-lbs",0,1)->getDoubleValue();
//cout << "transform: contents " << sm->contents << endl;
IC.mass = (sm->weight + sm->contents) * lbs_to_slugs;
//cout << "mass inc contents" << IC.mass << endl;
// Set contents to 0 in the parent
sm->contents_node->getChild("level-gal_us",0,1)->setDoubleValue(0);
/*cout << "contents " << sm->contents_node->getChild("level-gal_us")->getDoubleValue()
<< " " << sm->contents_node->getChild("level-lbs",0,1)->getDoubleValue()
<< endl;*/
}
else {
IC.mass = sm->weight * lbs_to_slugs;
}
int id = sm->id;
//int sub_id = sm->sub_id;
//const string& name = sm->name;
if (sm->speed_node != 0)
sm->speed = sm->speed_node->getDoubleValue();
// set the Initial Conditions for the types of submodel parent
if (_impact || _hit || _expiry) {
_count++;
// Set the data for a submodel tied to a submodel
IC.lat = _parent_lat;
IC.lon = _parent_lon;
IC.alt = _parent_elev;
IC.roll = _parent_roll; // rotation about x axis
IC.elevation = _parent_pitch; // rotation about y axis
IC.azimuth = _parent_hdg; // rotation about z axis
IC.speed = _parent_speed;
IC.speed_down_fps = 0;
IC.speed_east_fps = 0;
IC.speed_north_fps = 0;
}
else if (id == 0) {
// Set the data for a submodel tied to the main model
IC.lat = _user_lat_node->getDoubleValue();
IC.lon = _user_lon_node->getDoubleValue();
IC.alt = _user_alt_node->getDoubleValue();
IC.roll = _user_roll_node->getDoubleValue(); // rotation about x axis
IC.elevation = _user_pitch_node->getDoubleValue(); // rotation about y axis
IC.azimuth = _user_heading_node->getDoubleValue(); // rotation about z axis
IC.speed = _user_speed_node->getDoubleValue();
IC.speed_down_fps = _user_speed_down_fps_node->getDoubleValue();
IC.speed_east_fps = _user_speed_east_fps_node->getDoubleValue();
IC.speed_north_fps = _user_speed_north_fps_node->getDoubleValue();
}
else {
// Set the data for a submodel tied to an AI Object
setParentNode(id);
}
// Set the Initial Conditions that are common to all types of parent
IC.wind_from_east = _user_wind_from_east_node->getDoubleValue();
IC.wind_from_north = _user_wind_from_north_node->getDoubleValue();
// For submodels affected by wind, convert ground speed to airspeed by subtracting wind.
// Since the wind frame is inverted (wind_*from*_{east,north}), the wind values are added.
if (sm->wind) {
IC.speed_east_fps += IC.wind_from_east;
IC.speed_north_fps += IC.wind_from_north;
}
userpos.setLatitudeDeg(IC.lat);
userpos.setLongitudeDeg(IC.lon);
userpos.setElevationFt(IC.alt);
if (sm->offsets_in_meter) {
_x_offset = -sm->x_offset->get_value() * SG_METER_TO_FEET;
_y_offset = sm->y_offset->get_value() * SG_METER_TO_FEET;
_z_offset = sm->z_offset->get_value() * SG_METER_TO_FEET;
}
else {
_x_offset = sm->x_offset->get_value();
_y_offset = sm->y_offset->get_value();
_z_offset = sm->z_offset->get_value();
}
// this updates the 'offsetpos' member of us
setOffsetPos(sm);
// Compute initial orientation using yaw and pitch offsets and parent's orientation
const double yaw_offset = sm->yaw_offset->get_value();
const double pitch_offset = sm->pitch_offset->get_value();
SGQuatd ic_quat = SGQuatd::fromYawPitchRollDeg(IC.azimuth, IC.elevation, IC.roll);
ic_quat *= SGQuatd::fromYawPitchRollDeg(yaw_offset, pitch_offset, 0.0);
// Calculate total speed using speeds of submodel and parent
SGVec3d total_speed = SGVec3d(IC.speed_north_fps, IC.speed_east_fps, IC.speed_down_fps);
total_speed += ic_quat.rotate(SGVec3d(sm->speed, 0, 0));
IC.speed = length(total_speed);
// If speeds are low this calculation can become unreliable
if (IC.speed > 1) {
const double total_speed_north = total_speed.x();
const double total_speed_east = total_speed.y();
const double total_speed_down = total_speed.z();
IC.azimuth = atan2(total_speed_east, total_speed_north) * SG_RADIANS_TO_DEGREES;
// Rationalize the output
if (IC.azimuth < 0)
IC.azimuth += 360;
else if (IC.azimuth >= 360)
IC.azimuth -= 360;
IC.elevation = -atan(total_speed_down / sqrt(total_speed_north
* total_speed_north + total_speed_east * total_speed_east))
* SG_RADIANS_TO_DEGREES;
}
else {
double ic_roll;
ic_quat.getEulerDeg(IC.azimuth, IC.elevation, ic_roll);
}
}
void FGSubmodelMgr::loadAI()
{
SG_LOG(SG_AI, SG_DEBUG, "Submodels: Loading AI submodels");
FGAIManager::ai_list_type sm_list(aiManager()->get_ai_list());
if (sm_list.empty()) {
SG_LOG(SG_AI, SG_ALERT, "Submodels: Unable to read AI submodel list");
return;
}
FGAIManager::ai_list_iterator sm_list_itr = sm_list.begin();
FGAIManager::ai_list_iterator end = sm_list.end();
while (sm_list_itr != end) {
string path = (*sm_list_itr)->_getSMPath();
if (path.empty()) {
++sm_list_itr;
continue;
}
int id = (*sm_list_itr)->getID();
bool serviceable = (*sm_list_itr)->_getServiceable();
//string type = (*sm_list_itr)->getTypeString();
//cout << "loadAI: type " << type << " path "<< path << " serviceable " << serviceable << endl;
setData(id, path, serviceable, "/ai/submodels/submodel", submodels);
++sm_list_itr;
}
}
void FGSubmodelMgr::setData(int id, const string& path, bool serviceable, const string& property_path, submodel_vector_type& models)
{
SGPropertyNode root;
SGPath config = globals->resolve_aircraft_path(path);
if (!config.exists()) {
SG_LOG(SG_AI, SG_DEV_ALERT, "missing AI submodels file: " << config);
return;
}
try {
SG_LOG(SG_AI, SG_DEBUG,
"Submodels: Trying to read AI submodels file: " << config);
readProperties(config, &root);
}
catch (const sg_exception &) {
SG_LOG(SG_AI, SG_ALERT,
"Submodels: Unable to read AI submodels file: " << config);
return;
}
vector<SGPropertyNode_ptr> children = root.getChildren("submodel");
vector<SGPropertyNode_ptr>::iterator it = children.begin();
vector<SGPropertyNode_ptr>::iterator end = children.end();
for (int i = 0; it != end; ++it, i++) {
//cout << "Reading AI submodel " << (*it)->getPath() << endl;
submodel *sm = new submodel;
SGPropertyNode_ptr entry_node = *it;
sm->name = entry_node->getStringValue("name", "none_defined");
sm->model = entry_node->getStringValue("model", "Models/Geometry/rocket.ac");
sm->speed = entry_node->getDoubleValue("speed", 2329.4);
sm->repeat = entry_node->getBoolValue("repeat", false);
sm->delay = entry_node->getDoubleValue("delay", 0.25);
sm->count = entry_node->getIntValue("count", 1);
sm->slaved = entry_node->getBoolValue("slaved", false);
sm->drag_area = entry_node->getDoubleValue("eda", 0.034);
sm->life = entry_node->getDoubleValue("life", 900.0);
sm->buoyancy = entry_node->getDoubleValue("buoyancy", 0);
sm->wind = entry_node->getBoolValue("wind", false);
sm->cd = entry_node->getDoubleValue("cd", 0.193);
sm->weight = entry_node->getDoubleValue("weight", 0.25);
sm->aero_stabilised = entry_node->getBoolValue("aero-stabilised", true);
sm->no_roll = entry_node->getBoolValue("no-roll", false);
sm->collision = entry_node->getBoolValue("collision", false);
sm->expiry = entry_node->getBoolValue("expiry", false);
sm->impact = entry_node->getBoolValue("impact", false);
sm->impact_report = entry_node->getStringValue("impact-reports");
sm->fuse_range = entry_node->getDoubleValue("fuse-range", 0.0);
sm->contents_node = fgGetNode(entry_node->getStringValue("contents", "none"), false);
sm->speed_node = fgGetNode(entry_node->getStringValue("speed-prop", "none"), false);
sm->submodel = entry_node->getStringValue("submodel-path", "");
sm->force_stabilised = entry_node->getBoolValue("force-stabilised", false);
sm->ext_force = entry_node->getBoolValue("external-force", false);
sm->force_path = entry_node->getStringValue("force-path", "");
sm->random = entry_node->getBoolValue("random", false);
SGPropertyNode_ptr prop_root = fgGetNode("/", true);
SGPropertyNode n;
SGPropertyNode_ptr a, b;
// Offsets
a = entry_node->getNode("offsets", false);
sm->offsets_in_meter = (a != 0);
if (a) {
b = a->getNode("x-m");
sm->x_offset = new FGXMLAutopilot::InputValue(*prop_root, b ? *b : n);
b = a->getNode("y-m");
sm->y_offset = new FGXMLAutopilot::InputValue(*prop_root, b ? *b : n);
b = a->getNode("z-m");
sm->z_offset = new FGXMLAutopilot::InputValue(*prop_root, b ? *b : n);
b = a->getNode("heading-deg");
sm->yaw_offset = new FGXMLAutopilot::InputValue(*prop_root, b ? *b : n);
b = a->getNode("pitch-deg");
sm->pitch_offset = new FGXMLAutopilot::InputValue(*prop_root, b ? *b : n);
}
else {
bool old = false;
b = entry_node->getNode("x-offset");
sm->x_offset = new FGXMLAutopilot::InputValue(*prop_root, b ? *b : n);
if (b) old = true;
b = entry_node->getNode("y-offset");
sm->y_offset = new FGXMLAutopilot::InputValue(*prop_root, b ? *b : n);
if (b) old = true;
b = entry_node->getNode("z-offset");
sm->z_offset = new FGXMLAutopilot::InputValue(*prop_root, b ? *b : n);
if (b) old = true;
b = entry_node->getNode("yaw-offset");
sm->yaw_offset = new FGXMLAutopilot::InputValue(*prop_root, b ? *b : n);
if (b) old = true;
b = entry_node->getNode("pitch-offset");
sm->pitch_offset = new FGXMLAutopilot::InputValue(*prop_root, b ? *b : n);
if (b) old = true;
if (old) {
SG_LOG(SG_AI, SG_DEV_WARN, "Submodels: <*-offset> is deprecated. Use <offsets> instead");
}
}
// Randomness
a = entry_node->getNode("randomness", true);
// Maximum azimuth randomness error in degrees
b = a->getNode("azimuth");
sm->azimuth_error = new FGXMLAutopilot::InputValue(*prop_root, b ? *b : n);
// Maximum elevation randomness error in degrees
b = a->getNode("elevation");
sm->elevation_error = new FGXMLAutopilot::InputValue(*prop_root, b ? *b : n);
// Randomness of Cd (plus or minus)
b = a->getNode("cd");
if (!b) {
b = a->getNode("cd", true);
b->setDoubleValue(0.1);
}
sm->cd_randomness = new FGXMLAutopilot::InputValue(*prop_root, *b);
// Randomness of life (plus or minus)
b = a->getNode("life");
if (!b) {
b = a->getNode("life", true);
b->setDoubleValue(0.5);
}
sm->life_randomness = new FGXMLAutopilot::InputValue(*prop_root, *b);
if (sm->contents_node != 0)
sm->contents = sm->contents_node->getDoubleValue();
string trigger_path = entry_node->getStringValue("trigger", "");
if (!trigger_path.empty()) {
sm->trigger_node = fgGetNode(trigger_path, true);
sm->trigger_node->setBoolValue(sm->trigger_node->getBoolValue());
}
else {
sm->trigger_node = 0;
}
if (sm->speed_node != 0)
sm->speed = sm->speed_node->getDoubleValue();
sm->timer = sm->delay;
sm->id = id;
sm->first_time = false;
sm->serviceable = serviceable;
sm->sub_id = 0;
sm->prop = fgGetNode(property_path, index, true);
sm->prop->tie("delay", SGRawValuePointer<double>(&(sm->delay)));
sm->prop->tie("count", SGRawValuePointer<int>(&(sm->count)));
sm->prop->tie("repeat", SGRawValuePointer<bool>(&(sm->repeat)));
sm->prop->tie("id", SGRawValuePointer<int>(&(sm->id)));
sm->prop->tie("sub-id", SGRawValuePointer<int>(&(sm->sub_id)));
sm->prop->tie("serviceable", SGRawValuePointer<bool>(&(sm->serviceable)));
sm->prop->tie("random", SGRawValuePointer<bool>(&(sm->random)));
sm->prop->tie("slaved", SGRawValuePointer<bool>(&(sm->slaved)));
const string& name = sm->name;
sm->prop->setStringValue("name", name.c_str());
const string& submodel = sm->submodel;
sm->prop->setStringValue("submodel", submodel.c_str());
const string& force_path = sm->force_path;
sm->prop->setStringValue("force_path", force_path.c_str());
if (sm->contents_node != 0)
sm->prop->tie("contents-lbs", SGRawValuePointer<double>(&(sm->contents)));
index++;
models.push_back(sm);
}
}
void FGSubmodelMgr::loadSubmodels()
{
SG_LOG(SG_AI, SG_DEBUG, "Submodels: Loading sub submodels");
_found_sub = false;
submodel_iterator = submodels.begin();
while (submodel_iterator != submodels.end()) {
const string& submodel = (*submodel_iterator)->submodel;
if (!submodel.empty()) {
//int id = (*submodel_iterator)->id;
bool serviceable = true;
SG_LOG(SG_AI, SG_DEBUG, "found path sub sub "
<< submodel
<< " index " << index
<< " name " << (*submodel_iterator)->name);
if ((*submodel_iterator)->sub_id == 0) {
(*submodel_iterator)->sub_id = index;
_found_sub = true;
setData(index, submodel, serviceable, "/ai/submodels/subsubmodel", subsubmodels);
}
}
++submodel_iterator;
}
submodels.reserve(submodels.size() + subsubmodels.size());
// Add all elements from subsubmodels to submodels
subsubmodel_iterator = subsubmodels.begin();
while (subsubmodel_iterator != subsubmodels.end()) {
submodels.push_back(*subsubmodel_iterator);
++subsubmodel_iterator;
}
subsubmodels.clear();
}
SGVec3d FGSubmodelMgr::getCartOffsetPos(submodel* sm) const
{
// Transform to the right coordinate frame, configuration is done in
// either x-backward, y-right, z-up coordinates (meter),
// or (deprecated) x-forward, y-right, z-up coordinates (feet).
// computation in the simulation usual body x-forward, y-right, z-down coordinates (meters)
SGVec3d offset;
if (sm->offsets_in_meter) {
offset = SGVec3d(-sm->x_offset->get_value(),
sm->y_offset->get_value(),
-sm->z_offset->get_value());
} else {
offset = SGVec3d(sm->x_offset->get_value() * SG_FEET_TO_METER,
sm->y_offset->get_value() * SG_FEET_TO_METER,
-sm->z_offset->get_value() * SG_FEET_TO_METER);
}
// Transform the user position to the horizontal local coordinate system.
SGQuatd hlTrans = SGQuatd::fromLonLat(userpos);
// And postrotate the orientation of the user model wrt the horizontal
// local frame
hlTrans *= SGQuatd::fromYawPitchRollDeg(
IC.azimuth,
IC.elevation,
IC.roll);
// The offset converted to the usual body fixed coordinate system
// rotated to the earth-fixed coordinates axis
SGVec3d off = hlTrans.backTransform(offset);
// Add the position offset of the user model to get the geocentered position
return SGVec3d::fromGeod(userpos) + off;
}
void FGSubmodelMgr::setOffsetPos(submodel* sm)
{
// Convert the offset geocentered position to geodetic
SGVec3d cartoffsetPos = getCartOffsetPos(sm);
SGGeodesy::SGCartToGeod(cartoffsetPos, offsetpos);
}
void FGSubmodelMgr::valueChanged(SGPropertyNode *prop)
{
// REVIEW: This code has been dead for 10 years
// return; // this isn't working atm
// const char* _model_added = _model_added_node->getStringValue();
// std::basic_string <char>::size_type indexCh2b;
// string str2 = _model_added;
// const char *cstr2b = "multiplayer";
// indexCh2b = str2.find(cstr2b, 0);
// // Ignoring Ballistic Objects; there are potentially too many
// if (indexCh2b != string::npos ) {
// //cout << "Submodels: model added - " << str2 <<" read path "<< endl;
// //return;
// SGPropertyNode *a_node = fgGetNode(_model_added, true);
// SGPropertyNode *sub_node = a_node->getChild("sim", 0, true);
// SGPropertyNode_ptr path_node = sub_node->getChild("path", 0, true);
// SGPropertyNode_ptr callsign_node = a_node->getChild("callsign", 0, true);
// //const string& callsign = callsign_node->getStringValue();
// //cout << "Submodels: model added - " << callsign <<" read callsign "<< endl;
// }
// else {
// cout << "model added - " << str2 <<" returning " << endl;
// }
}
void FGSubmodelMgr::setParentNode(int id)
{
const SGPropertyNode_ptr ai = fgGetNode("/ai/models", true);
for (int i = ai->nChildren() - 1; i >= -1; i--) {
SGPropertyNode_ptr model;
if (i < 0) {
// Last iteration: selected model
model = _selected_ac;
}
else {
model = ai->getChild(i);
int parent_id = model->getIntValue("id");
if (!model->nChildren()) {
continue;
}
if (parent_id == id) {
// Save selected model for last iteration
_selected_ac = model;
break;
}
}
if (!model)
continue;
}
if (_selected_ac != 0) {
const string name = _selected_ac->getStringValue("name");
IC.lat = _selected_ac->getDoubleValue("position/latitude-deg");
IC.lon = _selected_ac->getDoubleValue("position/longitude-deg");
IC.alt = _selected_ac->getDoubleValue("position/altitude-ft");
IC.roll = _selected_ac->getDoubleValue("orientation/roll-deg");
IC.elevation = _selected_ac->getDoubleValue("orientation/pitch-deg");
IC.azimuth = _selected_ac->getDoubleValue("orientation/true-heading-deg");
IC.speed = _selected_ac->getDoubleValue("velocities/true-airspeed-kt") * SG_KT_TO_FPS;
IC.speed_down_fps = -_selected_ac->getDoubleValue("velocities/vertical-speed-fps");
IC.speed_east_fps = _selected_ac->getDoubleValue("velocities/speed-east-fps");
IC.speed_north_fps = _selected_ac->getDoubleValue("velocities/speed-north-fps");
}
else {
SG_LOG(SG_AI, SG_ALERT, "AISubmodel: parent node not found ");
}
}
// Register the subsystem.
SGSubsystemMgr::Registrant<FGSubmodelMgr> registrantFGSubmodelMgr(
SGSubsystemMgr::POST_FDM);

189
src/AIModel/submodel.hxx Normal file
View File

@@ -0,0 +1,189 @@
// submodel.hxx - models a releasable submodel.
// Written by Dave Culp, started Aug 2004
//
// This file is in the Public Domain and comes with no warranty.
#pragma once
#include <string>
#include <vector>
#include <simgear/props/props.hxx>
#include <simgear/structure/subsystem_mgr.hxx>
#include <simgear/math/SGMath.hxx>
#include <Autopilot/inputvalue.hxx>
class FGAIBase;
class FGAIManager;
class FGSubmodelMgr : public SGSubsystem,
public SGPropertyChangeListener
{
public:
typedef struct {
SGPropertyNode_ptr trigger_node;
SGPropertyNode_ptr prop;
SGPropertyNode_ptr contents_node;
SGPropertyNode_ptr submodel_node;
SGPropertyNode_ptr speed_node;
std::string name;
std::string model;
double speed;
bool slaved;
bool repeat;
double delay;
double timer;
int count;
bool offsets_in_meter;
FGXMLAutopilot::InputValue_ptr x_offset;
FGXMLAutopilot::InputValue_ptr y_offset;
FGXMLAutopilot::InputValue_ptr z_offset;
FGXMLAutopilot::InputValue_ptr yaw_offset;
FGXMLAutopilot::InputValue_ptr pitch_offset;
double drag_area;
double life;
double buoyancy;
FGXMLAutopilot::InputValue_ptr azimuth_error;
FGXMLAutopilot::InputValue_ptr elevation_error;
FGXMLAutopilot::InputValue_ptr cd_randomness;
FGXMLAutopilot::InputValue_ptr life_randomness;
bool wind;
bool first_time;
double cd;
double weight;
double mass;
double contents;
bool aero_stabilised;
int id;
bool no_roll;
bool serviceable;
bool random;
bool collision;
bool expiry;
bool impact;
std::string impact_report;
double fuse_range;
std::string submodel;
int sub_id;
bool force_stabilised;
bool ext_force;
std::string force_path;
} submodel;
typedef struct {
double lat;
double lon;
double alt;
double roll;
double azimuth;
double elevation;
double speed;
double wind_from_east;
double wind_from_north;
double speed_down_fps;
double speed_east_fps;
double speed_north_fps;
double mass;
int id;
bool no_roll;
int parent_id;
} IC_struct;
FGSubmodelMgr();
virtual ~FGSubmodelMgr() = default;
// Subsystem API.
void bind() override;
void init() override;
void postinit() override;
void shutdown() override;
void unbind() override;
void update(double dt) override;
// Subsystem identification.
static const char* staticSubsystemClassId() { return "submodel-mgr"; }
void load();
private:
typedef std::vector <submodel*> submodel_vector_type;
typedef submodel_vector_type::iterator submodel_vector_iterator;
submodel_vector_type submodels;
submodel_vector_type subsubmodels;
submodel_vector_iterator submodel_iterator, subsubmodel_iterator;
int index;
double x_offset, y_offset, z_offset;
double pitch_offset, yaw_offset;
double _parent_lat;
double _parent_lon;
double _parent_elev;
double _parent_hdg;
double _parent_pitch;
double _parent_roll;
double _parent_speed;
double _x_offset;
double _y_offset;
double _z_offset;
// Conversion factor
static const double lbs_to_slugs;
double contrail_altitude;
bool _impact;
bool _hit;
bool _expiry;
bool _found_sub;
SGPropertyNode_ptr _serviceable_node;
SGPropertyNode_ptr _user_lat_node;
SGPropertyNode_ptr _user_lon_node;
SGPropertyNode_ptr _user_heading_node;
SGPropertyNode_ptr _user_alt_node;
SGPropertyNode_ptr _user_pitch_node;
SGPropertyNode_ptr _user_roll_node;
SGPropertyNode_ptr _user_yaw_node;
SGPropertyNode_ptr _user_alpha_node;
SGPropertyNode_ptr _user_speed_node;
SGPropertyNode_ptr _user_wind_from_east_node;
SGPropertyNode_ptr _user_wind_from_north_node;
SGPropertyNode_ptr _user_speed_down_fps_node;
SGPropertyNode_ptr _user_speed_east_fps_node;
SGPropertyNode_ptr _user_speed_north_fps_node;
SGPropertyNode_ptr _contrail_altitude_node;
SGPropertyNode_ptr _contrail_trigger;
SGPropertyNode_ptr _count_node;
SGPropertyNode_ptr _trigger_node;
SGPropertyNode_ptr props;
SGPropertyNode_ptr _model_added_node;
SGPropertyNode_ptr _path_node;
SGPropertyNode_ptr _selected_ac;
IC_struct IC;
// Helper to retrieve the AI manager, if it currently exists
FGAIManager* aiManager();
void loadAI();
void loadSubmodels();
void setData(int id, const std::string& path, bool serviceable, const std::string& property_path, submodel_vector_type& models);
void valueChanged (SGPropertyNode *);
void transform(submodel *);
void setParentNode(int parent_id);
bool release(submodel *, double dt);
int _count;
SGGeod userpos;
SGGeod offsetpos;
SGVec3d getCartOffsetPos(submodel* sm) const;
void setOffsetPos(submodel* sm);
};

477
src/ATC/ATCController.cxx Normal file
View File

@@ -0,0 +1,477 @@
// Extracted from trafficrecord.cxx - Implementation of AIModels ATC code.
//
// Written by Durk Talsma, started September 2006.
//
// Copyright (C) 2006 Durk Talsma.
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation; either version 2 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
//
// $Id$
#include <config.h>
#include <algorithm>
#include <cstdio>
#include <random>
#include <osg/Geode>
#include <osg/Geometry>
#include <osg/MatrixTransform>
#include <osg/Shape>
#include <simgear/scene/material/EffectGeode.hxx>
#include <simgear/scene/material/matlib.hxx>
#include <simgear/scene/material/mat.hxx>
#include <simgear/scene/util/OsgMath.hxx>
#include <simgear/timing/sg_time.hxx>
#include <Scenery/scenery.hxx>
#include "trafficcontrol.hxx"
#include "atc_mgr.hxx"
#include <AIModel/AIAircraft.hxx>
#include <AIModel/AIFlightPlan.hxx>
#include <AIModel/performancedata.hxx>
#include <Traffic/TrafficMgr.hxx>
#include <Airports/groundnetwork.hxx>
#include <Airports/dynamics.hxx>
#include <Airports/airport.hxx>
#include <Radio/radio.hxx>
#include <signal.h>
#include <ATC/atc_mgr.hxx>
#include <ATC/trafficcontrol.hxx>
#include <ATC/ATCController.hxx>
using std::sort;
using std::string;
/***************************************************************************
* FGATCController
*
**************************************************************************/
FGATCController::FGATCController()
{
dt_count = 0;
available = true;
lastTransmission = 0;
initialized = false;
lastTransmissionDirection = ATC_AIR_TO_GROUND;
group = NULL;
}
FGATCController::~FGATCController()
{
if (initialized) {
auto mgr = globals->get_subsystem<FGATCManager>();
mgr->removeController(this);
}
_isDestroying = true;
clearTrafficControllers();
}
void FGATCController::init()
{
if (!initialized) {
auto mgr = globals->get_subsystem<FGATCManager>();
mgr->addController(this);
initialized = true;
}
}
string FGATCController::getGateName(FGAIAircraft * ref)
{
return ref->atGate();
}
bool FGATCController::isUserAircraft(FGAIAircraft* ac)
{
return (ac->getCallSign() == fgGetString("/sim/multiplay/callsign")) ? true : false;
};
void FGATCController::transmit(FGTrafficRecord * rec, FGAirportDynamics *parent, AtcMsgId msgId,
AtcMsgDir msgDir, bool audible)
{
string sender, receiver;
int stationFreq = 0;
int taxiFreq = 0;
int towerFreq = 0;
int freqId = 0;
string atisInformation;
string text;
string taxiFreqStr;
string towerFreqStr;
double heading = 0;
string activeRunway;
string fltType;
string rwyClass;
string SID;
string transponderCode;
FGAIFlightPlan *fp;
string fltRules;
string instructionText;
int ground_to_air=0;
//double commFreqD;
sender = rec->getCallsign();
if (rec->getAircraft()->getTaxiClearanceRequest()) {
instructionText = "push-back and taxi";
} else {
instructionText = "taxi";
}
SG_LOG(SG_ATC, SG_DEBUG, "transmitting for: " << sender << "Leg = " << rec->getLeg());
auto depApt = rec->getAircraft()->getTrafficRef()->getDepartureAirport();
if (!depApt) {
SG_LOG(SG_ATC, SG_DEV_ALERT, "TrafficRec has empty departure airport, can't transmit");
return;
}
stationFreq = getFrequency();
taxiFreq = depApt->getDynamics()->getGroundFrequency(2);
towerFreq = depApt->getDynamics()->getTowerFrequency(2);
receiver = getName();
atisInformation = depApt->getDynamics()->getAtisSequence();
// Swap sender and receiver value in case of a ground to air transmission
if (msgDir == ATC_GROUND_TO_AIR) {
string tmp = sender;
sender = receiver;
receiver = tmp;
ground_to_air = 1;
}
switch (msgId) {
case MSG_ANNOUNCE_ENGINE_START:
text = sender + ". Ready to Start up.";
break;
case MSG_REQUEST_ENGINE_START:
text =
receiver + ", This is " + sender + ". Position " +
getGateName(rec->getAircraft()) + ". Information " +
atisInformation + ". " +
rec->getAircraft()->getTrafficRef()->getFlightRules() +
" to " +
rec->getAircraft()->getTrafficRef()->getArrivalAirport()->
getName() + ". Request start-up.";
break;
// Acknowledge engine startup permission
// Assign departure runway
// Assign SID, if necessery (TODO)
case MSG_PERMIT_ENGINE_START:
taxiFreqStr = formatATCFrequency3_2(taxiFreq);
heading = rec->getAircraft()->getTrafficRef()->getCourse();
fltType = rec->getAircraft()->getTrafficRef()->getFlightType();
rwyClass =
rec->getAircraft()->GetFlightPlan()->
getRunwayClassFromTrafficType(fltType);
rec->getAircraft()->getTrafficRef()->getDepartureAirport()->
getDynamics()->getActiveRunway(rwyClass, 1, activeRunway,
heading);
rec->getAircraft()->GetFlightPlan()->setRunway(activeRunway);
fp = NULL;
rec->getAircraft()->GetFlightPlan()->setSID(fp);
if (fp) {
SID = fp->getName() + " departure";
} else {
SID = "fly runway heading ";
}
//snprintf(buffer, 7, "%3.2f", heading);
fltRules = rec->getAircraft()->getTrafficRef()->getFlightRules();
transponderCode = genTransponderCode(fltRules);
rec->getAircraft()->SetTransponderCode(transponderCode);
text =
receiver + ". Start-up approved. " + atisInformation +
" correct, runway " + activeRunway + ", " + SID + ", squawk " +
transponderCode + ". " +
"For "+ instructionText + " clearance call " + taxiFreqStr + ". " +
sender + " control.";
break;
case MSG_DENY_ENGINE_START:
text = receiver + ". Standby.";
break;
case MSG_ACKNOWLEDGE_ENGINE_START:
fp = rec->getAircraft()->GetFlightPlan()->getSID();
if (fp) {
SID =
rec->getAircraft()->GetFlightPlan()->getSID()->getName() +
" departure";
} else {
SID = "fly runway heading ";
}
taxiFreqStr = formatATCFrequency3_2(taxiFreq);
activeRunway = rec->getAircraft()->GetFlightPlan()->getRunway();
transponderCode = rec->getAircraft()->GetTransponderCode();
text =
receiver + ". Start-up approved. " + atisInformation +
" correct, runway " + activeRunway + ", " + SID + ", squawk " +
transponderCode + ". " +
"For " + instructionText + " clearance call " + taxiFreqStr + ". " +
sender + ".";
break;
case MSG_ACKNOWLEDGE_SWITCH_GROUND_FREQUENCY:
taxiFreqStr = formatATCFrequency3_2(taxiFreq);
text = receiver + ". Switching to " + taxiFreqStr + ". " + sender + ".";
break;
case MSG_INITIATE_CONTACT:
text = receiver + ". With you. " + sender + ".";
break;
case MSG_ACKNOWLEDGE_INITIATE_CONTACT:
text = receiver + ". Roger. " + sender + ".";
break;
case MSG_REQUEST_PUSHBACK_CLEARANCE:
if (rec->getAircraft()->getTaxiClearanceRequest()) {
text = receiver + ". Request push-back. " + sender + ".";
} else {
text = receiver + ". Request Taxi clearance. " + sender + ".";
}
break;
case MSG_PERMIT_PUSHBACK_CLEARANCE:
if (rec->getAircraft()->getTaxiClearanceRequest()) {
text = receiver + ". Push-back approved. " + sender + ".";
} else {
text = receiver + ". Cleared to Taxi. " + sender + ".";
}
break;
case MSG_HOLD_PUSHBACK_CLEARANCE:
text = receiver + ". Standby. " + sender + ".";
break;
case MSG_REQUEST_TAXI_CLEARANCE:
text = receiver + ". Ready to Taxi. " + sender + ".";
break;
case MSG_ISSUE_TAXI_CLEARANCE:
text = receiver + ". Cleared to taxi. " + sender + ".";
break;
case MSG_ACKNOWLEDGE_TAXI_CLEARANCE:
text = receiver + ". Cleared to taxi. " + sender + ".";
break;
case MSG_HOLD_POSITION:
text = receiver + ". Hold Position. " + sender + ".";
break;
case MSG_ACKNOWLEDGE_HOLD_POSITION:
text = receiver + ". Holding Position. " + sender + ".";
break;
case MSG_RESUME_TAXI:
text = receiver + ". Resume Taxiing. " + sender + ".";
break;
case MSG_ACKNOWLEDGE_RESUME_TAXI:
text = receiver + ". Continuing Taxi. " + sender + ".";
break;
case MSG_REPORT_RUNWAY_HOLD_SHORT:
activeRunway = rec->getAircraft()->GetFlightPlan()->getRunway();
//activeRunway = "test";
text = receiver + ". Holding short runway "
+ activeRunway
+ ". " + sender + ".";
//text = "test1";
SG_LOG(SG_ATC, SG_DEBUG, "1 Currently at leg " << rec->getLeg());
break;
case MSG_ACKNOWLEDGE_REPORT_RUNWAY_HOLD_SHORT:
activeRunway = rec->getAircraft()->GetFlightPlan()->getRunway();
text = receiver + " Roger. Holding short runway "
// + activeRunway
+ ". " + sender + ".";
//text = "test2";
SG_LOG(SG_ATC, SG_DEBUG, "2 Currently at leg " << rec->getLeg());
break;
case MSG_CLEARED_FOR_TAKEOFF:
activeRunway = rec->getAircraft()->GetFlightPlan()->getRunway();
//activeRunway = "test";
text = receiver + ". Cleared for takeoff runway " + activeRunway + ". " + sender + ".";
break;
case MSG_ACKNOWLEDGE_CLEARED_FOR_TAKEOFF:
activeRunway = rec->getAircraft()->GetFlightPlan()->getRunway();
text = receiver + " Roger. Cleared for takeoff runway " + activeRunway + ". " + sender + ".";
//text = "test2";
break;
case MSG_SWITCH_TOWER_FREQUENCY:
towerFreqStr = formatATCFrequency3_2(towerFreq);
text = receiver + " Contact Tower at " + towerFreqStr + ". " + sender + ".";
//text = "test3";
SG_LOG(SG_ATC, SG_DEBUG, "3 Currently at leg " << rec->getLeg());
break;
case MSG_ACKNOWLEDGE_SWITCH_TOWER_FREQUENCY:
towerFreqStr = formatATCFrequency3_2(towerFreq);
text = receiver + " Roger, switching to tower at " + towerFreqStr + ". " + sender + ".";
//text = "test4";
SG_LOG(SG_ATC, SG_DEBUG, "4 Currently at leg " << rec->getLeg());
break;
default:
//text = "test3";
text = text + sender + ". Transmitting unknown Message.";
break;
}
const bool atcAudioEnabled = fgGetBool("/sim/sound/atc/enabled", false);
if (audible && atcAudioEnabled) {
double onBoardRadioFreq0 =
fgGetDouble("/instrumentation/comm[0]/frequencies/selected-mhz");
double onBoardRadioFreq1 =
fgGetDouble("/instrumentation/comm[1]/frequencies/selected-mhz");
int onBoardRadioFreqI0 = (int) floor(onBoardRadioFreq0 * 100 + 0.5);
int onBoardRadioFreqI1 = (int) floor(onBoardRadioFreq1 * 100 + 0.5);
SG_LOG(SG_ATC, SG_DEBUG, "Using " << onBoardRadioFreq0 << ", " << onBoardRadioFreq1 << " and " << stationFreq << " for " << text << endl);
if( stationFreq == 0 ) {
SG_LOG(SG_ATC, SG_DEBUG, getName() << " stationFreq not found");
}
// Display ATC message only when one of the radios is tuned
// the relevant frequency.
// Note that distance attenuation is currently not yet implemented
if ((stationFreq > 0)&&
((onBoardRadioFreqI0 == stationFreq)||
(onBoardRadioFreqI1 == stationFreq))) {
if (rec->allowTransmissions()) {
if( fgGetBool( "/sim/radio/use-itm-attenuation", false ) ) {
SG_LOG(SG_ATC, SG_DEBUG, "Using ITM radio propagation");
FGRadioTransmission* radio = new FGRadioTransmission();
SGGeod sender_pos;
double sender_alt_ft, sender_alt;
if(ground_to_air) {
sender_pos = parent->parent()->geod();
}
else {
sender_pos= rec->getPos();
}
double frequency = ((double)stationFreq) / 100;
radio->receiveATC(sender_pos, frequency, text, ground_to_air);
delete radio;
}
else {
SG_LOG(SG_ATC, SG_BULK, "Transmitting " << text);
fgSetString("/sim/messages/atc", text.c_str());
}
}
}
} else {
//FGATCDialogNew::instance()->addEntry(1, text);
}
}
void FGATCController::signOff(int id)
{
TrafficVectorIterator i = searchActiveTraffic(id);
if (i == activeTraffic.end()) {
// Dead traffic should never reach here
SG_LOG(SG_ATC, SG_ALERT,
"AI error: Aircraft without traffic record is signing off from " << getName() << " at " << SG_ORIGIN << " list " << activeTraffic.empty());
return;
}
activeTraffic.erase(i);
SG_LOG(SG_ATC, SG_DEBUG, i->getCallsign() << " signing off from " << getName() );
}
bool FGATCController::hasInstruction(int id)
{
// Search activeTraffic for a record matching our id
TrafficVectorIterator i = searchActiveTraffic(id);
if (i == activeTraffic.end()) {
SG_LOG(SG_ATC, SG_ALERT,
"AI error: checking ATC instruction for aircraft without traffic record at " << SG_ORIGIN);
} else {
return i->hasInstruction();
}
return false;
}
FGATCInstruction FGATCController::getInstruction(int id)
{
// Search activeTraffic for a record matching our id
TrafficVectorIterator i = searchActiveTraffic(id);
if (i == activeTraffic.end() || (activeTraffic.size() == 0)) {
SG_LOG(SG_ATC, SG_ALERT,
"AI error: requesting ATC instruction for aircraft without traffic record at " << SG_ORIGIN);
} else {
return i->getInstruction();
}
return FGATCInstruction();
}
/*
* Format integer frequency xxxyy as xxx.yy
* @param freq - integer value
* @return the formatted string
*/
string FGATCController::formatATCFrequency3_2(int freq)
{
char buffer[7]; // does this ever need to be freed?
snprintf(buffer, 7, "%3.2f", ((float) freq / 100.0));
return string(buffer);
}
// TODO: Set transponder codes according to real-world routes.
// The current version just returns a random string of four octal numbers.
string FGATCController::genTransponderCode(const string& fltRules)
{
if (fltRules == "VFR")
return string("1200");
std::default_random_engine generator;
std::uniform_int_distribution<unsigned> distribution(0, 7);
unsigned val = (
distribution(generator) * 1000 +
distribution(generator) * 100 +
distribution(generator) * 10 +
distribution(generator));
return std::to_string(val);
}
void FGATCController::eraseDeadTraffic()
{
auto it = std::remove_if(activeTraffic.begin(), activeTraffic.end(), [](const FGTrafficRecord& traffic)
{
if (traffic.isDead()) {
SG_LOG(SG_ATC, SG_DEBUG, "Remove dead " << traffic.getId() << " " << traffic.isDead());
}
return traffic.isDead();
});
activeTraffic.erase(it, activeTraffic.end());
}
/*
* Search activeTraffic vector to find matching id
* @param id integer to search for in the vector
* @return the matching item OR activeTraffic.end()
*/
TrafficVectorIterator FGATCController::searchActiveTraffic(int id)
{
return std::find_if(activeTraffic.begin(), activeTraffic.end(),
[id] (const FGTrafficRecord& rec)
{ return rec.getId() == id; }
);
}
void FGATCController::clearTrafficControllers()
{
for (const auto& traffic : activeTraffic) {
traffic.clearATCController();
}
}

138
src/ATC/ATCController.hxx Normal file
View File

@@ -0,0 +1,138 @@
// Extracted from trafficcontrol.hxx - classes to manage AIModels based air traffic control
// Written by Durk Talsma, started September 2006.
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation; either version 2 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
//
// $Id$
#ifndef ATC_CONTROLLER_HXX
#define ATC_CONTROLLER_HXX
#include <Airports/airports_fwd.hxx>
#include <osg/Geode>
#include <osg/Geometry>
#include <osg/MatrixTransform>
#include <osg/Shape>
#include <simgear/compiler.h>
#include <simgear/constants.h>
#include <simgear/debug/logstream.hxx>
#include <simgear/structure/SGReferenced.hxx>
#include <simgear/structure/SGSharedPtr.hxx>
#include <ATC/trafficcontrol.hxx>
/**
* class FGATCController
* NOTE: this class serves as an abstraction layer for all sorts of ATC controllers.
*************************************************************************************/
class FGATCController
{
private:
protected:
// guard variable to avoid modifying state during destruction
bool _isDestroying = false;
bool initialized;
bool available;
time_t lastTransmission;
TrafficVector activeTraffic;
double dt_count;
osg::Group* group;
FGAirportDynamics *parent = nullptr;
std::string formatATCFrequency3_2(int );
std::string genTransponderCode(const std::string& fltRules);
bool isUserAircraft(FGAIAircraft*);
void clearTrafficControllers();
TrafficVectorIterator searchActiveTraffic(int id);
void eraseDeadTraffic();
/**Returns the frequency to be used. */
virtual int getFrequency() = 0;
public:
typedef enum {
MSG_ANNOUNCE_ENGINE_START,
MSG_REQUEST_ENGINE_START,
MSG_PERMIT_ENGINE_START,
MSG_DENY_ENGINE_START,
MSG_ACKNOWLEDGE_ENGINE_START,
MSG_REQUEST_PUSHBACK_CLEARANCE,
MSG_PERMIT_PUSHBACK_CLEARANCE,
MSG_HOLD_PUSHBACK_CLEARANCE,
MSG_ACKNOWLEDGE_SWITCH_GROUND_FREQUENCY,
MSG_INITIATE_CONTACT,
MSG_ACKNOWLEDGE_INITIATE_CONTACT,
MSG_REQUEST_TAXI_CLEARANCE,
MSG_ISSUE_TAXI_CLEARANCE,
MSG_ACKNOWLEDGE_TAXI_CLEARANCE,
MSG_HOLD_POSITION,
MSG_ACKNOWLEDGE_HOLD_POSITION,
MSG_RESUME_TAXI,
MSG_ACKNOWLEDGE_RESUME_TAXI,
MSG_REPORT_RUNWAY_HOLD_SHORT,
MSG_ACKNOWLEDGE_REPORT_RUNWAY_HOLD_SHORT,
MSG_CLEARED_FOR_TAKEOFF,
MSG_ACKNOWLEDGE_CLEARED_FOR_TAKEOFF,
MSG_SWITCH_TOWER_FREQUENCY,
MSG_ACKNOWLEDGE_SWITCH_TOWER_FREQUENCY
} AtcMsgId;
typedef enum {
ATC_AIR_TO_GROUND,
ATC_GROUND_TO_AIR
} AtcMsgDir;
FGATCController();
virtual ~FGATCController();
void init();
virtual void announcePosition(int id, FGAIFlightPlan *intendedRoute, int currentRoute,
double lat, double lon,
double hdg, double spd, double alt, double radius, int leg,
FGAIAircraft *aircraft) = 0;
virtual void updateAircraftInformation(int id, SGGeod geod,
double heading, double speed, double alt, double dt) = 0;
virtual void signOff(int id);
bool hasInstruction(int id);
FGATCInstruction getInstruction(int id);
bool hasActiveTraffic() {
return ! activeTraffic.empty();
};
TrafficVector &getActiveTraffic() {
return activeTraffic;
};
double getDt() {
return dt_count;
};
void setDt(double dt) {
dt_count = dt;
};
void transmit(FGTrafficRecord *rec, FGAirportDynamics *parent, AtcMsgId msgId, AtcMsgDir msgDir, bool audible);
std::string getGateName(FGAIAircraft *aircraft);
virtual void render(bool) = 0;
virtual std::string getName() = 0;
virtual void update(double) = 0;
private:
AtcMsgDir lastTransmissionDirection;
};
#endif

592
src/ATC/ATISEncoder.cxx Normal file
View File

@@ -0,0 +1,592 @@
/*
Encode an ATIS into spoken words
Copyright (C) 2014 Torsten Dreyer
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "ATISEncoder.hxx"
#include <Airports/dynamics.hxx>
#include <Main/globals.hxx>
#include <Main/locale.hxx>
#include <simgear/structure/exception.hxx>
#include <simgear/props/props_io.hxx>
#include <map>
#include <vector>
using std::string;
using std::vector;
using simgear::PropertyList;
static string NO_ATIS("nil");
static string EMPTY("");
#define SPACE append(1,' ')
std::string ATCSpeech::getSpokenDigit( int i )
{
string key = "n" + std::to_string(i);
return globals->get_locale()->getLocalizedString(key.c_str(), "atc", "" );
}
string ATCSpeech::getSpokenNumber( string number )
{
string result;
for( string::iterator it = number.begin(); it != number.end(); ++it ) {
if( !result.empty() )
result.SPACE;
result.append( getSpokenDigit( (*it) - '0' ));
}
return result;
}
string ATCSpeech::getSpokenNumber( int number, bool leadingZero, int digits )
{
string_list spokenDigits;
bool negative = false;
if( number < 0 ) {
negative = true;
number = -number;
}
int n = 0;
while( number > 0 ) {
spokenDigits.push_back( getSpokenDigit(number%10) );
number /= 10;
n++;
}
if( digits > 0 ) {
while( n++ < digits ) {
spokenDigits.push_back( getSpokenDigit(0) );
}
}
string result;
if( negative ) {
result.append( globals->get_locale()->getLocalizedString("minus", "atc", "minus" ) );
}
while( !spokenDigits.empty() ) {
if( !result.empty() )
result.SPACE;
result.append( spokenDigits.back() );
spokenDigits.pop_back();
}
return result;
}
string ATCSpeech::getSpokenAltitude( int altitude )
{
string result;
int thousands = altitude / 1000;
int hundrets = (altitude % 1000) / 100;
if( thousands > 0 ) {
result.append( getSpokenNumber(thousands) );
result.SPACE;
result.append( getSpokenDigit(1000) );
result.SPACE;
}
if( hundrets > 0 )
result.append( getSpokenNumber(hundrets) )
.SPACE
.append( getSpokenDigit(100) );
return result;
}
ATISEncoder::ATISEncoder()
{
handlerMap.insert( std::make_pair( "text", &ATISEncoder::processTextToken ));
handlerMap.insert( std::make_pair( "token", &ATISEncoder::processTokenToken ));
handlerMap.insert( std::make_pair( "if", &ATISEncoder::processIfToken ));
handlerMap.insert( std::make_pair( "section", &ATISEncoder::processTokens ));
handlerMap.insert( std::make_pair( "id", &ATISEncoder::getAtisId ));
handlerMap.insert( std::make_pair( "airport-name", &ATISEncoder::getAirportName ));
handlerMap.insert( std::make_pair( "time", &ATISEncoder::getTime ));
handlerMap.insert( std::make_pair( "approach-type", &ATISEncoder::getApproachType ));
handlerMap.insert( std::make_pair( "rwy-land", &ATISEncoder::getLandingRunway ));
handlerMap.insert( std::make_pair( "rwy-to", &ATISEncoder::getTakeoffRunway ));
handlerMap.insert( std::make_pair( "transition-level", &ATISEncoder::getTransitionLevel ));
handlerMap.insert( std::make_pair( "wind-dir", &ATISEncoder::getWindDirection ));
handlerMap.insert( std::make_pair( "wind-from", &ATISEncoder::getWindMinDirection ));
handlerMap.insert( std::make_pair( "wind-to", &ATISEncoder::getWindMaxDirection ));
handlerMap.insert( std::make_pair( "wind-speed-kn", &ATISEncoder::getWindspeedKnots ));
handlerMap.insert( std::make_pair( "gusts", &ATISEncoder::getGustsKnots ));
handlerMap.insert( std::make_pair( "visibility-metric", &ATISEncoder::getVisibilityMetric ));
handlerMap.insert( std::make_pair( "visibility-miles", &ATISEncoder::getVisibilityMiles ));
handlerMap.insert( std::make_pair( "phenomena", &ATISEncoder::getPhenomena ));
handlerMap.insert( std::make_pair( "clouds", &ATISEncoder::getClouds ));
handlerMap.insert( std::make_pair( "clouds-brief", &ATISEncoder::getCloudsBrief ));
handlerMap.insert( std::make_pair( "cavok", &ATISEncoder::getCavok ));
handlerMap.insert( std::make_pair( "temperature-deg", &ATISEncoder::getTemperatureDeg ));
handlerMap.insert( std::make_pair( "dewpoint-deg", &ATISEncoder::getDewpointDeg ));
handlerMap.insert( std::make_pair( "qnh", &ATISEncoder::getQnh ));
handlerMap.insert( std::make_pair( "inhg", &ATISEncoder::getInhg ));
handlerMap.insert( std::make_pair( "inhg-integer", &ATISEncoder::getInhgInteger ));
handlerMap.insert( std::make_pair( "inhg-fraction", &ATISEncoder::getInhgFraction ));
handlerMap.insert( std::make_pair( "trend", &ATISEncoder::getTrend ));
}
ATISEncoder::~ATISEncoder()
{
}
SGPropertyNode_ptr findAtisTemplate( const std::string & stationId, SGPropertyNode_ptr atisSchemaNode )
{
using simgear::strutils::starts_with;
SGPropertyNode_ptr atisTemplate;
PropertyList schemaNodes = atisSchemaNode->getChildren("atis-schema");
for( PropertyList::iterator asit = schemaNodes.begin(); asit != schemaNodes.end(); ++asit ) {
SGPropertyNode_ptr ppp = (*asit)->getNode("station-starts-with", false );
atisTemplate = (*asit)->getNode("atis", false );
if( !atisTemplate.valid() ) continue; // no <atis> node - ignore entry
PropertyList startsWithNodes = (*asit)->getChildren("station-starts-with");
for( PropertyList::iterator swit = startsWithNodes.begin(); swit != startsWithNodes.end(); ++swit ) {
if( starts_with( stationId, (*swit)->getStringValue() ) ) {
return atisTemplate;
}
}
}
return atisTemplate;
}
string ATISEncoder::encodeATIS( ATISInformationProvider * atisInformation )
{
using simgear::strutils::lowercase;
if( !atisInformation->isValid() ) return NO_ATIS;
airport = FGAirport::getByIdent( atisInformation->airportId() );
if( !airport.valid() ) {
SG_LOG( SG_ATC, SG_WARN, "ATISEncoder: unknown airport id " << atisInformation->airportId() );
return NO_ATIS;
}
_atis = atisInformation;
// lazily load the schema file on the first call
if( !atisSchemaNode.valid() ) {
atisSchemaNode = new SGPropertyNode();
try
{
SGPath path = globals->resolve_maybe_aircraft_path("ATC/atis.xml");
readProperties( path, atisSchemaNode );
}
catch (const sg_exception& e)
{
SG_LOG( SG_ATC, SG_ALERT, "ATISEncoder: Failed to load atis schema definition: " << e.getMessage());
return NO_ATIS;
}
}
string stationId = lowercase( airport->ident() );
SGPropertyNode_ptr atisTemplate = findAtisTemplate( stationId, atisSchemaNode );;
if( !atisTemplate.valid() ) {
SG_LOG(SG_ATC, SG_WARN, "no matching atis template for station " << stationId );
return NO_ATIS; // no template for this station!?
}
return processTokens( atisTemplate );
}
string ATISEncoder::processTokens( SGPropertyNode_ptr node )
{
string result;
if( node.valid() ) {
for( int i = 0; i < node->nChildren(); i++ ) {
result.append(processToken( node->getChild(i) ));
}
}
return result;
}
string ATISEncoder::processToken( SGPropertyNode_ptr token )
{
HandlerMap::iterator it = handlerMap.find( token->getNameString());
if( it == handlerMap.end() ) {
SG_LOG(SG_ATC, SG_WARN, "ATISEncoder: unknown token: " << token->getNameString() );
return EMPTY;
}
handler_t h = it->second;
return (this->*h)( token );
}
string ATISEncoder::processTextToken( SGPropertyNode_ptr token )
{
return token->getStringValue();
}
string ATISEncoder::processTokenToken( SGPropertyNode_ptr token )
{
HandlerMap::iterator it = handlerMap.find( token->getStringValue());
if( it == handlerMap.end() ) {
SG_LOG(SG_ATC, SG_WARN, "ATISEncoder: unknown token: " << token->getStringValue() );
return EMPTY;
}
handler_t h = it->second;
return (this->*h)( token );
}
string ATISEncoder::processIfToken( SGPropertyNode_ptr token )
{
using namespace simgear::strutils;
SGPropertyNode_ptr n;
if( (n = token->getChild("empty", false )).valid() ) {
return checkEmptyCondition( n, true) ?
processTokens(token->getChild("then",false)) :
processTokens(token->getChild("else",false));
}
if( (n = token->getChild("not-empty", false )).valid() ) {
return checkEmptyCondition( n, false) ?
processTokens(token->getChild("then",false)) :
processTokens(token->getChild("else",false));
}
if( (n = token->getChild("contains", false )).valid() ) {
return checkCondition( n, true, &contains, "contains") ?
processTokens(token->getChild("then",false)) :
processTokens(token->getChild("else",false));
}
if( (n = token->getChild("not-contains", false )).valid() ) {
return checkCondition( n, false, &contains, "not-contains") ?
processTokens(token->getChild("then",false)) :
processTokens(token->getChild("else",false));
}
if( (n = token->getChild("ends-with", false )).valid() ) {
return checkCondition( n, true, &ends_with, "ends-with") ?
processTokens(token->getChild("then",false)) :
processTokens(token->getChild("else",false));
}
if( (n = token->getChild("not-ends-with", false )).valid() ) {
return checkCondition( n, false, &ends_with, "not-ends-with") ?
processTokens(token->getChild("then",false)) :
processTokens(token->getChild("else",false));
}
if( (n = token->getChild("equals", false )).valid() ) {
return checkCondition( n, true, &equals, "equals") ?
processTokens(token->getChild("then",false)) :
processTokens(token->getChild("else",false));
}
if( (n = token->getChild("not-equals", false )).valid() ) {
return checkCondition( n, false, &equals, "not-equals") ?
processTokens(token->getChild("then",false)) :
processTokens(token->getChild("else",false));
}
if( (n = token->getChild("starts-with", false )).valid() ) {
return checkCondition( n, true, &starts_with, "starts-with") ?
processTokens(token->getChild("then",false)) :
processTokens(token->getChild("else",false));
}
if( (n = token->getChild("not-starts-with", false )).valid() ) {
return checkCondition( n, false, &starts_with, "not-starts-with") ?
processTokens(token->getChild("then",false)) :
processTokens(token->getChild("else",false));
}
SG_LOG(SG_ATC, SG_WARN, "ATISEncoder: no valid token found for <if> element" );
return EMPTY;
}
bool ATISEncoder::checkEmptyCondition( SGPropertyNode_ptr node, bool isEmpty )
{
SGPropertyNode_ptr n1 = node->getNode( "token", false );
if( !n1.valid() ) {
SG_LOG(SG_ATC, SG_WARN, "missing <token> node for (not)-empty" );
return false;
}
return processToken( n1 ).empty() == isEmpty;
}
bool ATISEncoder::checkCondition( SGPropertyNode_ptr node, bool notInverted,
bool (*fp)(const string &, const string &), const string &name )
{
using namespace simgear::strutils;
SGPropertyNode_ptr n1 = node->getNode( "token", 0, false );
SGPropertyNode_ptr n2 = node->getNode( "token", 1, false );
if( n1.valid() && n2.valid() ) {
bool comp = fp( processToken( n1 ), processToken( n2 ) );
return comp == notInverted;
}
if( n1.valid() && !n2.valid() ) {
SGPropertyNode_ptr t1 = node->getNode( "text", 0, false );
if( t1.valid() ) {
string n1s = lowercase( strip( processToken( n1 ) ) );
string t1s = lowercase( strip( processTextToken( t1 ) ) );
return fp( n1s, t1s ) == notInverted;
}
SG_LOG(SG_ATC, SG_WARN, "missing <token> or <text> node for " << name);
return false;
}
SG_LOG(SG_ATC, SG_WARN, "missing <token> node for " << name);
return false;
}
string ATISEncoder::getAtisId( SGPropertyNode_ptr )
{
FGAirportDynamics * dynamics = airport->getDynamics();
if( NULL != dynamics ) {
dynamics->updateAtisSequence( 30*60, false );
return dynamics->getAtisSequence();
}
return EMPTY;
}
string ATISEncoder::getAirportName( SGPropertyNode_ptr )
{
return airport->getName();
}
string ATISEncoder::getTime( SGPropertyNode_ptr )
{
return getSpokenNumber( _atis->getTime() % (100*100), true, 4 );
}
static inline FGRunwayRef findBestRunwayForWind( FGAirportRef airport, int windDeg, int windKt )
{
struct FGAirport::FindBestRunwayForHeadingParams p;
//TODO: ramp down the heading weight with wind speed
p.ilsWeight = 4;
return airport->findBestRunwayForHeading( windDeg, &p );
}
string ATISEncoder::getApproachType( SGPropertyNode_ptr )
{
FGRunwayRef runway = findBestRunwayForWind( airport, _atis->getWindDeg(), _atis->getWindSpeedKt() );
if( runway.valid() ) {
if( NULL != runway->ILS() ) return globals->get_locale()->getLocalizedString("ils", "atc", "ils" );
//TODO: any chance to find other approach types? localizer-dme, vor-dme, vor, ndb?
}
return globals->get_locale()->getLocalizedString("visual", "atc", "visual" );
}
string ATISEncoder::getLandingRunway( SGPropertyNode_ptr )
{
FGRunwayRef runway = findBestRunwayForWind( airport, _atis->getWindDeg(), _atis->getWindSpeedKt() );
if( runway.valid() ) {
string runwayIdent = runway->ident();
if(runwayIdent != "NN") {
return getSpokenNumber(runwayIdent);
}
}
return EMPTY;
}
string ATISEncoder::getTakeoffRunway( SGPropertyNode_ptr p )
{
//TODO: if the airport has more than one runway, probably pick another one?
return getLandingRunway( p );
}
string ATISEncoder::getTransitionLevel(SGPropertyNode_ptr)
{
double hPa = _atis->getQnh();
/* Transition level is the flight level above which aircraft must use standard pressure and below
* which airport pressure settings must be used.
* Following definitions are taken from German ATIS:
* QNH <= 977 hPa: TRL 80
* QNH <= 1013 hPa: TRL 70
* QNH > 1013 hPa: TRL 60
* (maybe differs slightly for other countries...)
*/
int tl;
if (hPa <= 978) {
tl = 80;
} else if (hPa <= 1013) {
tl = 70;
} else if (hPa <= 1046) {
tl = 60;
} else {
tl = 50;
}
// add an offset to the transition level for high altitude airports (just guessing here,
// seems reasonable)
int e = int(airport->getElevation() / 1000.0);
if (e >= 3) {
// TL steps in 10(00)ft
tl += (e - 2) * 10;
}
return getSpokenNumber(tl);
}
string ATISEncoder::getWindDirection( SGPropertyNode_ptr )
{
string variable = globals->get_locale()->getLocalizedString("variable", "atc", "variable" );
bool vrb = _atis->getWindMinDeg() == 0 && _atis->getWindMaxDeg() == 359;
return vrb ? variable : getSpokenNumber( _atis->getWindDeg(), true, 3 );
}
string ATISEncoder::getWindMinDirection( SGPropertyNode_ptr )
{
return getSpokenNumber( _atis->getWindMinDeg(), true, 3 );
}
string ATISEncoder::getWindMaxDirection( SGPropertyNode_ptr )
{
return getSpokenNumber( _atis->getWindMaxDeg(), true, 3 );
}
string ATISEncoder::getWindspeedKnots( SGPropertyNode_ptr )
{
return getSpokenNumber( _atis->getWindSpeedKt() );
}
string ATISEncoder::getGustsKnots( SGPropertyNode_ptr )
{
int g = _atis->getGustsKt();
return g > 0 ? getSpokenNumber( g ) : EMPTY;
}
string ATISEncoder::getCavok( SGPropertyNode_ptr )
{
string CAVOK = globals->get_locale()->getLocalizedString("cavok", "atc", "cavok" );
return _atis->isCavok() ? CAVOK : EMPTY;
}
string ATISEncoder::getVisibilityMetric( SGPropertyNode_ptr )
{
string m = globals->get_locale()->getLocalizedString("meters", "atc", "meters" );
string km = globals->get_locale()->getLocalizedString("kilometers", "atc", "kilometers" );
string or_more = globals->get_locale()->getLocalizedString("ormore", "atc", "or more" );
int v = _atis->getVisibilityMeters();
string reply;
if( v < 5000 ) return reply.append( getSpokenAltitude( v ) ).SPACE.append( m );
if( v >= 9999 ) return reply.append( getSpokenNumber(10) ).SPACE.append( km ).SPACE.append(or_more);
return reply.append( getSpokenNumber( v/1000 ).SPACE.append( km ) );
}
string ATISEncoder::getVisibilityMiles( SGPropertyNode_ptr )
{
string feet = globals->get_locale()->getLocalizedString("feet", "atc", "feet" );
int v = _atis->getVisibilityMeters();
int vft = int( v * SG_METER_TO_FEET / 100 + 0.5 ) * 100; // Rounded to 100 ft
int vsm = int( v * SG_METER_TO_SM + 0.5 );
string reply;
if( vsm < 1 ) return reply.append( getSpokenAltitude( vft ) ).SPACE.append( feet );
if( vsm >= 10 ) return reply.append( getSpokenNumber(10) );
return reply.append( getSpokenNumber( vsm ) );
}
string ATISEncoder::getPhenomena( SGPropertyNode_ptr )
{
return _atis->getPhenomena();
}
string ATISEncoder::getClouds( SGPropertyNode_ptr )
{
string FEET = globals->get_locale()->getLocalizedString("feet", "atc", "feet" );
string reply;
ATISInformationProvider::CloudEntries cloudEntries = _atis->getClouds();
for (ATISInformationProvider::CloudEntries::iterator it = cloudEntries.begin(); it != cloudEntries.end(); ++it) {
if (!reply.empty())
reply.SPACE;
reply.append( it->second ).SPACE.append( getSpokenAltitude(it->first).SPACE.append( FEET ) );
}
return reply;
}
string ATISEncoder::getCloudsBrief( SGPropertyNode_ptr )
{
string reply;
ATISInformationProvider::CloudEntries cloudEntries = _atis->getClouds();
for (ATISInformationProvider::CloudEntries::iterator it = cloudEntries.begin(); it != cloudEntries.end(); ++it) {
if (!reply.empty())
reply.append(",").SPACE;
reply.append( it->second ).SPACE.append( getSpokenAltitude(it->first) );
}
return reply;
}
string ATISEncoder::getTemperatureDeg( SGPropertyNode_ptr )
{
return getSpokenNumber( _atis->getTemperatureDeg() );
}
string ATISEncoder::getDewpointDeg( SGPropertyNode_ptr )
{
return getSpokenNumber( _atis->getDewpointDeg() );
}
string ATISEncoder::getQnh( SGPropertyNode_ptr )
{
return getSpokenNumber( _atis->getQnh() );
}
string ATISEncoder::getInhgInteger( SGPropertyNode_ptr )
{
double qnh = _atis->getQnhInHg();
return getSpokenNumber( (int)qnh, true, 2 );
}
string ATISEncoder::getInhgFraction( SGPropertyNode_ptr )
{
double qnh = _atis->getQnhInHg();
int f = int(100 * (qnh - int(qnh)) + 0.5);
return getSpokenNumber( f, true, 2 );
}
string ATISEncoder::getInhg( SGPropertyNode_ptr node)
{
string DECIMAL = globals->get_locale()->getLocalizedString("dp", "atc", "decimal" );
return getInhgInteger(node)
.SPACE.append(DECIMAL).SPACE
.append(getInhgFraction(node));
}
string ATISEncoder::getTrend( SGPropertyNode_ptr )
{
return _atis->getTrend();
}

133
src/ATC/ATISEncoder.hxx Normal file
View File

@@ -0,0 +1,133 @@
/*
Encode an ATIS into spoken words
Copyright (C) 2014 Torsten Dreyer
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef __ATIS_ENCODER_HXX
#define __ATIS_ENCODER_HXX
#include <string>
#include <Airports/airport.hxx>
#include <simgear/props/props.hxx>
#include <map>
class ATCSpeech {
public:
static std::string getSpokenDigit( int i );
static std::string getSpokenNumber( std::string number );
static std::string getSpokenNumber( int number, bool leadingZero = false, int digits = 1 );
static std::string getSpokenAltitude( int altitude );
};
class ATISInformationProvider {
public:
virtual ~ATISInformationProvider() {}
virtual bool isValid() = 0;
virtual std::string airportId() = 0;
static long makeAtisTime( int day, int hour, int minute ) {
return 100*100l* day + 100l * hour + minute;
}
inline int getAtisTimeDay( long atisTime ) { return atisTime / (100l*100l); }
inline int getAtisTimeHour( long atisTime ) { return (atisTime % (100l*100l)) / 100l; }
inline int getAtisTimeMinute( long atisTime ) { return atisTime % 100l; }
virtual long getTime() = 0; // see makeAtisTime
virtual int getWindDeg() = 0;
virtual int getWindMinDeg() = 0;
virtual int getWindMaxDeg() = 0;
virtual int getWindSpeedKt() = 0;
virtual int getGustsKt() = 0;
virtual int getQnh() = 0;
virtual double getQnhInHg() = 0;
virtual bool isCavok() = 0;
virtual int getVisibilityMeters() = 0;
virtual std::string getPhenomena() = 0;
typedef std::map<int,std::string> CloudEntries;
virtual CloudEntries getClouds() = 0;
virtual int getTemperatureDeg() = 0;
virtual int getDewpointDeg() = 0;
virtual std::string getTrend() = 0;
};
class ATISEncoder : public ATCSpeech {
public:
ATISEncoder();
virtual ~ATISEncoder();
virtual std::string encodeATIS( ATISInformationProvider * atisInformationProvider );
protected:
virtual std::string getAtisId( SGPropertyNode_ptr );
virtual std::string getAirportName( SGPropertyNode_ptr );
virtual std::string getTime( SGPropertyNode_ptr );
virtual std::string getApproachType( SGPropertyNode_ptr );
virtual std::string getLandingRunway( SGPropertyNode_ptr );
virtual std::string getTakeoffRunway( SGPropertyNode_ptr );
virtual std::string getTransitionLevel( SGPropertyNode_ptr );
virtual std::string getWindDirection( SGPropertyNode_ptr );
virtual std::string getWindMinDirection( SGPropertyNode_ptr );
virtual std::string getWindMaxDirection( SGPropertyNode_ptr );
virtual std::string getWindspeedKnots( SGPropertyNode_ptr );
virtual std::string getGustsKnots( SGPropertyNode_ptr );
virtual std::string getCavok( SGPropertyNode_ptr );
virtual std::string getVisibilityMetric( SGPropertyNode_ptr );
virtual std::string getVisibilityMiles( SGPropertyNode_ptr );
virtual std::string getPhenomena( SGPropertyNode_ptr );
virtual std::string getClouds( SGPropertyNode_ptr );
virtual std::string getCloudsBrief( SGPropertyNode_ptr );
virtual std::string getTemperatureDeg( SGPropertyNode_ptr );
virtual std::string getDewpointDeg( SGPropertyNode_ptr );
virtual std::string getQnh( SGPropertyNode_ptr );
virtual std::string getInhgInteger( SGPropertyNode_ptr );
virtual std::string getInhgFraction( SGPropertyNode_ptr );
virtual std::string getInhg( SGPropertyNode_ptr );
virtual std::string getTrend( SGPropertyNode_ptr );
typedef std::string (ATISEncoder::*handler_t)( SGPropertyNode_ptr baseNode );
typedef std::map<std::string, handler_t > HandlerMap;
HandlerMap handlerMap;
SGPropertyNode_ptr atisSchemaNode;
std::string processTokens( SGPropertyNode_ptr baseNode );
std::string processToken( SGPropertyNode_ptr baseNode );
std::string processTextToken( SGPropertyNode_ptr baseNode );
std::string processTokenToken( SGPropertyNode_ptr baseNode );
std::string processIfToken( SGPropertyNode_ptr baseNode );
bool checkEmptyCondition( SGPropertyNode_ptr node, bool isEmpty );
// Wrappers that can be passed as function pointers to checkCondition
// @see simgear::strutils::starts_with
// @see simgear::strutils::ends_with
static bool contains(const string &s, const string &substring)
{ return s.find(substring) != std::string::npos; };
static bool equals(const string &s1, const string &s2)
{ return s1 == s2; };
bool checkCondition( SGPropertyNode_ptr node, bool notInverted,
bool (*fp)(const std::string &, const std::string &),
const std::string &name );
FGAirportRef airport;
ATISInformationProvider * _atis;
};
#endif

View File

@@ -0,0 +1,195 @@
// Extracted from trafficrecord.cxx - Implementation of AIModels ATC code.
//
// Written by Durk Talsma, started September 2006.
//
// Copyright (C) 2006 Durk Talsma.
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation; either version 2 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
//
// $Id$
#include <config.h>
#include <algorithm>
#include <cstdio>
#include <random>
#include <osg/Geode>
#include <osg/Geometry>
#include <osg/MatrixTransform>
#include <osg/Shape>
#include <simgear/scene/material/EffectGeode.hxx>
#include <simgear/scene/material/matlib.hxx>
#include <simgear/scene/material/mat.hxx>
#include <simgear/scene/util/OsgMath.hxx>
#include <simgear/timing/sg_time.hxx>
#include <Scenery/scenery.hxx>
#include "trafficcontrol.hxx"
#include "atc_mgr.hxx"
#include <AIModel/AIAircraft.hxx>
#include <AIModel/AIFlightPlan.hxx>
#include <AIModel/performancedata.hxx>
#include <Traffic/TrafficMgr.hxx>
#include <Airports/groundnetwork.hxx>
#include <Airports/dynamics.hxx>
#include <Airports/airport.hxx>
#include <Radio/radio.hxx>
#include <signal.h>
#include <ATC/atc_mgr.hxx>
#include <ATC/trafficcontrol.hxx>
#include <ATC/ATCController.hxx>
#include <ATC/ApproachController.hxx>
using std::sort;
using std::string;
/***************************************************************************
* class FGApproachController
* subclass of FGATCController
**************************************************************************/
FGApproachController::FGApproachController(FGAirportDynamics *par):
FGATCController()
{
parent = par;
}
FGApproachController::~FGApproachController()
{
}
void FGApproachController::announcePosition(int id,
FGAIFlightPlan * intendedRoute,
int currentPosition,
double lat, double lon,
double heading, double speed,
double alt, double radius,
int leg, FGAIAircraft * ref)
{
init();
// Search activeTraffic for a record matching our id
TrafficVectorIterator i = FGATCController::searchActiveTraffic(id);
// Add a new TrafficRecord if no one exsists for this aircraft.
if (i == activeTraffic.end() || activeTraffic.empty()) {
FGTrafficRecord rec;
rec.setId(id);
rec.setPositionAndHeading(lat, lon, heading, speed, alt);
rec.setRunway(intendedRoute->getRunway());
rec.setLeg(leg);
rec.setCallsign(ref->getCallSign());
rec.setAircraft(ref);
activeTraffic.push_back(rec);
} else {
i->setPositionAndHeading(lat, lon, heading, speed, alt);
}
}
void FGApproachController::updateAircraftInformation(int id, SGGeod geod,
double heading, double speed, double alt,
double dt)
{
// Search activeTraffic for a record matching our id
TrafficVectorIterator i = FGATCController::searchActiveTraffic(id);
TrafficVectorIterator current;
// update position of the current aircraft
if (i == activeTraffic.end() || activeTraffic.empty()) {
SG_LOG(SG_ATC, SG_ALERT,
"AI error: updating aircraft without traffic record at " << SG_ORIGIN);
} else {
i->setPositionAndHeading(geod.getLatitudeDeg(), geod.getLongitudeDeg(), heading, speed, alt);
current = i;
if(current->getAircraft()) {
//FIXME No call to aircraft! -> set instruction
time_t time_diff =
current->getAircraft()->
checkForArrivalTime(string("final001"));
if (time_diff != 0) {
SG_LOG(SG_ATC, SG_BULK, current->getCallsign() << "|ApproachController: checking for speed " << time_diff);
}
if (time_diff > 15) {
current->setSpeedAdjustment(current->getAircraft()->
getPerformance()->vDescent() *
1.35);
} else if (time_diff > 5) {
current->setSpeedAdjustment(current->getAircraft()->
getPerformance()->vDescent() *
1.2);
} else if (time_diff < -15) {
current->setSpeedAdjustment(current->getAircraft()->
getPerformance()->vDescent() *
0.65);
} else if (time_diff < -5) {
current->setSpeedAdjustment(current->getAircraft()->
getPerformance()->vDescent() *
0.8);
} else {
current->clearSpeedAdjustment();
}
}
//current->setSpeedAdjustment(current->getAircraft()->getPerformance()->vDescent() + time_diff);
}
setDt(getDt() + dt);
}
/* Periodically check for and remove dead traffic records */
void FGApproachController::update(double dt)
{
FGATCController::eraseDeadTraffic();
}
ActiveRunway *FGApproachController::getRunway(const string& name)
{
ActiveRunwayVecIterator rwy = activeRunways.begin();
if (activeRunways.size()) {
while (rwy != activeRunways.end()) {
if (rwy->getRunwayName() == name) {
break;
}
rwy++;
}
}
if (rwy == activeRunways.end()) {
ActiveRunway aRwy(name, 0);
activeRunways.push_back(aRwy);
rwy = activeRunways.end() - 1;
}
return &(*rwy);
}
void FGApproachController::render(bool visible) {
// Must be BULK in order to prevent it being called each frame
SG_LOG(SG_ATC, SG_BULK, "FGApproachController::render function not yet implemented");
}
string FGApproachController::getName() {
return string(parent->parent()->getName() + "-approach");
}
int FGApproachController::getFrequency() {
int groundFreq = parent->getApproachFrequency(2);
int towerFreq = parent->getTowerFrequency(2);
return groundFreq>0?groundFreq:towerFreq;
}

View File

@@ -0,0 +1,67 @@
// Extracted from trafficcontrol.hxx - classes to manage AIModels based air traffic control
// Written by Durk Talsma, started September 2006.
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation; either version 2 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
//
// $Id$
#ifndef APPROACH_CONTROLLER_HXX
#define APPROACH_CONTROLLER_HXX
#include <Airports/airports_fwd.hxx>
#include <osg/Geode>
#include <osg/Geometry>
#include <osg/MatrixTransform>
#include <osg/Shape>
#include <simgear/compiler.h>
#include <simgear/constants.h>
#include <simgear/debug/logstream.hxx>
#include <simgear/structure/SGReferenced.hxx>
#include <simgear/structure/SGSharedPtr.hxx>
#include <ATC/ATCController.hxx>
#include <ATC/trafficcontrol.hxx>
/******************************************************************************
* class FGApproachController
*****************************************************************************/
class FGApproachController : public FGATCController
{
private:
ActiveRunwayVec activeRunways;
/**Returns the frequency to be used. */
int getFrequency();
public:
FGApproachController(FGAirportDynamics * parent);
virtual ~FGApproachController();
virtual void announcePosition(int id, FGAIFlightPlan *intendedRoute, int currentRoute,
double lat, double lon,
double hdg, double spd, double alt, double radius, int leg,
FGAIAircraft *aircraft);
virtual void updateAircraftInformation(int id, SGGeod geod,
double heading, double speed, double alt, double dt);
virtual void render(bool);
virtual std::string getName();
virtual void update(double dt);
ActiveRunway* getRunway(const std::string& name);
};
#endif

31
src/ATC/CMakeLists.txt Normal file
View File

@@ -0,0 +1,31 @@
include(FlightGearComponent)
set(SOURCES
atc_mgr.cxx
trafficcontrol.cxx
CommStation.cxx
ATISEncoder.cxx
MetarPropertiesATISInformationProvider.cxx
CurrentWeatherATISInformationProvider.cxx
ATCController.cxx
ApproachController.cxx
GroundController.cxx
StartupController.cxx
TowerController.cxx
)
set(HEADERS
atc_mgr.hxx
trafficcontrol.hxx
CommStation.hxx
ATISEncoder.hxx
MetarPropertiesATISInformationProvider.hxx
CurrentWeatherATISInformationProvider.hxx
ATCController.hxx
ApproachController.hxx
GroundController.hxx
StartupController.hxx
TowerController.hxx
)
flightgear_component(ATC "${SOURCES}" "${HEADERS}")

38
src/ATC/CommStation.cxx Normal file
View File

@@ -0,0 +1,38 @@
#include "config.h"
#include "CommStation.hxx"
#include <Airports/airport.hxx>
#include <Navaids/NavDataCache.hxx>
namespace flightgear {
CommStation::CommStation(PositionedID aGuid, const std::string& name, FGPositioned::Type t, const SGGeod& pos, int range, int freq) :
FGPositioned(aGuid, t, name, pos),
mRangeNM(range),
mFreqKhz(freq),
mAirport(0)
{
}
void CommStation::setAirport(PositionedID apt)
{
mAirport = apt;
}
FGAirportRef CommStation::airport() const
{
return FGPositioned::loadById<FGAirport>(mAirport);
}
double CommStation::freqMHz() const
{
return mFreqKhz / 1000.0;
}
CommStationRef
CommStation::findByFreq(int freqKhz, const SGGeod& pos, FGPositioned::Filter* filt)
{
return (CommStation*) NavDataCache::instance()->findCommByFreq(freqKhz, pos, filt).ptr();
}
} // of namespace flightgear

36
src/ATC/CommStation.hxx Normal file
View File

@@ -0,0 +1,36 @@
#ifndef FG_ATC_COMM_STATION_HXX
#define FG_ATC_COMM_STATION_HXX
#include <Airports/airports_fwd.hxx>
#include <Navaids/positioned.hxx>
namespace flightgear
{
class CommStation : public FGPositioned
{
public:
CommStation(PositionedID aGuid, const std::string& name, FGPositioned::Type t, const SGGeod& pos, int range, int freq);
void setAirport(PositionedID apt);
FGAirportRef airport() const;
int rangeNm() const
{ return mRangeNM; }
int freqKHz() const
{ return mFreqKhz; }
double freqMHz() const;
static CommStationRef findByFreq(int freqKhz, const SGGeod& pos, FGPositioned::Filter* filt = NULL);
private:
int mRangeNM;
int mFreqKhz;
PositionedID mAirport;
};
} // of namespace flightgear
#endif // of FG_ATC_COMM_STATION_HXX

View File

@@ -0,0 +1,145 @@
/*
Provide Data for the ATIS Encoder from metarproperties
Copyright (C) 2014 Torsten Dreyer
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "CurrentWeatherATISInformationProvider.hxx"
#include <Main/fg_props.hxx>
using std::string;
CurrentWeatherATISInformationProvider::CurrentWeatherATISInformationProvider( const std::string & airportId ) :
_airportId(airportId),
_environment(fgGetNode("/environment"))
{
}
static inline int roundToInt( double d )
{
return (static_cast<int>(10.0 * (d + .5))) / 10;
}
static inline int roundToInt( SGPropertyNode_ptr n )
{
return roundToInt( n->getDoubleValue() );
}
static inline int roundToInt( SGPropertyNode_ptr n, const char * child )
{
return roundToInt( n->getNode(child,true) );
}
CurrentWeatherATISInformationProvider::~CurrentWeatherATISInformationProvider()
{
}
bool CurrentWeatherATISInformationProvider::isValid()
{
return true;
}
string CurrentWeatherATISInformationProvider::airportId()
{
return _airportId;
}
long CurrentWeatherATISInformationProvider::getTime()
{
int h = fgGetInt( "/sim/time/utc/hour", 12 );
int m = 20 + fgGetInt( "/sim/time/utc/minute", 0 ) / 30 ; // fake twice per hour
return makeAtisTime( 0, h, m );
}
int CurrentWeatherATISInformationProvider::getWindDeg()
{
// round to 10 degs
int i = 5 + roundToInt( _environment->getNode("config/boundary/entry[0]/wind-from-heading-deg",true) );
i /= 10;
return i*10;
}
int CurrentWeatherATISInformationProvider::getWindSpeedKt()
{
return roundToInt( _environment, "config/boundary/entry[0]/wind-speed-kt" );
}
int CurrentWeatherATISInformationProvider::getGustsKt()
{
return 0;
}
int CurrentWeatherATISInformationProvider::getQnh()
{
// TODO: Calculate QNH correctly from environment
return roundToInt( _environment->getNode("pressure-sea-level-inhg",true)->getDoubleValue() * SG_INHG_TO_PA / 100 );
}
double CurrentWeatherATISInformationProvider::getQnhInHg()
{
// TODO: Calculate QNH correctly from environment
return _environment->getNode("pressure-sea-level-inhg",true)->getDoubleValue();
}
bool CurrentWeatherATISInformationProvider::isCavok()
{
return false;
}
int CurrentWeatherATISInformationProvider::getVisibilityMeters()
{
return roundToInt( _environment, "ground-visibility-m" );
}
string CurrentWeatherATISInformationProvider::getPhenomena()
{
return "";
}
ATISInformationProvider::CloudEntries CurrentWeatherATISInformationProvider::getClouds()
{
using simgear::PropertyList;
ATISInformationProvider::CloudEntries cloudEntries;
PropertyList layers = _environment->getNode("clouds",true)->getChildren("layer");
for( PropertyList::iterator it = layers.begin(); it != layers.end(); ++it ) {
string coverage = (*it)->getStringValue( "coverage", "clear" );
int alt = roundToInt( (*it)->getDoubleValue("elevation-ft", -9999 ) ) / 100;
alt *= 100;
if( coverage != "clear" && alt > 0 )
cloudEntries[alt] = coverage;
}
return cloudEntries;
}
int CurrentWeatherATISInformationProvider::getTemperatureDeg()
{
return roundToInt( _environment, "temperature-sea-level-degc" );
}
int CurrentWeatherATISInformationProvider::getDewpointDeg()
{
return roundToInt( _environment, "dewpoint-sea-level-degc" );
}
string CurrentWeatherATISInformationProvider::getTrend()
{
return "nosig";
}

View File

@@ -0,0 +1,58 @@
/*
Provide Data for the ATIS Encoder from metarproperties
Copyright (C) 2014 Torsten Dreyer
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef __CURRENTWEATHER_ATIS_ENCODER_HXX
#define __CURRENTWEATHER_ATIS_ENCODER_HXX
/* ATIS encoder from current weather */
#include <string>
#include "ATISEncoder.hxx"
class CurrentWeatherATISInformationProvider : public ATISInformationProvider
{
public:
CurrentWeatherATISInformationProvider( const std::string & airportId );
virtual ~CurrentWeatherATISInformationProvider();
protected:
virtual bool isValid();
virtual std::string airportId();
virtual long getTime();
virtual int getWindDeg();
virtual int getWindMinDeg() { return getWindDeg(); }
virtual int getWindMaxDeg() { return getWindDeg(); }
virtual int getWindSpeedKt();
virtual int getGustsKt();
virtual int getQnh();
virtual double getQnhInHg();
virtual bool isCavok();
virtual int getVisibilityMeters();
virtual std::string getPhenomena();
virtual CloudEntries getClouds();
virtual int getTemperatureDeg();
virtual int getDewpointDeg();
virtual std::string getTrend();
private:
std::string _airportId;
SGPropertyNode_ptr _environment;
};
#endif

1002
src/ATC/GroundController.cxx Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,90 @@
// GroundController.hxx - forked from groundnetwork.cxx
//
// Written by Durk Talsma, started June 2005.
//
// Copyright (C) 2004 Durk Talsma.
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation; either version 2 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
//
// $Id$
#ifndef ATC_GROUND_CONTROLLER_HXX
#define ATC_GROUND_CONTROLLER_HXX
#include <simgear/compiler.h>
#include <string>
#include <ATC/trafficcontrol.hxx>
class FGAirportDynamics;
/**************************************************************************************
* class FGGroundController
*************************************************************************************/
class FGGroundController : public FGATCController
{
private:
bool hasNetwork;
bool networkInitialized;
int count;
int version;
FGTowerController *towerController;
/**Returns the frequency to be used. */
int getFrequency();
void checkSpeedAdjustment(int id, double lat, double lon,
double heading, double speed, double alt);
void checkHoldPosition(int id, double lat, double lon,
double heading, double speed, double alt);
void updateStartupTraffic(TrafficVectorIterator i, int& priority, time_t now);
bool updateActiveTraffic(TrafficVectorIterator i, int& priority, time_t now);
public:
FGGroundController(FGAirportDynamics *par);
~FGGroundController();
void setVersion (int v) { version = v;};
int getVersion() { return version; };
bool exists() {
return hasNetwork;
};
void setTowerController(FGTowerController *twrCtrlr) {
towerController = twrCtrlr;
};
virtual void announcePosition(int id, FGAIFlightPlan *intendedRoute, int currentRoute,
double lat, double lon, double hdg, double spd, double alt,
double radius, int leg, FGAIAircraft *aircraft);
virtual void updateAircraftInformation(int id, SGGeod geod, double heading, double speed, double alt, double dt);
bool checkTransmissionState(int minState, int MaxState, TrafficVectorIterator i, time_t now, AtcMsgId msgId,
AtcMsgDir msgDir);
bool checkForCircularWaits(int id);
virtual void render(bool);
virtual std::string getName();
virtual void update(double dt);
void addVersion(int v) {version = v; };
};
#endif

View File

@@ -0,0 +1,130 @@
/*
Provide Data for the ATIS Encoder from metarproperties
Copyright (C) 2014 Torsten Dreyer
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "MetarPropertiesATISInformationProvider.hxx"
#include <Main/globals.hxx>
#include <simgear/constants.h>
using std::string;
MetarPropertiesATISInformationProvider::MetarPropertiesATISInformationProvider( SGPropertyNode_ptr metar ) :
_metar( metar )
{
}
MetarPropertiesATISInformationProvider::~MetarPropertiesATISInformationProvider()
{
}
bool MetarPropertiesATISInformationProvider::isValid()
{
return _metar->getBoolValue( "valid", false );
}
string MetarPropertiesATISInformationProvider::airportId()
{
return _metar->getStringValue( "station-id", "xxxx" );
}
long MetarPropertiesATISInformationProvider::getTime()
{
return makeAtisTime( 0,
_metar->getIntValue( "hour" ) % 24,
_metar->getIntValue( "minute" ) % 60 );
}
int MetarPropertiesATISInformationProvider::getWindDeg()
{
return _metar->getIntValue( "base-wind-dir-deg" );
}
int MetarPropertiesATISInformationProvider::getWindMinDeg()
{
return _metar->getIntValue( "base-wind-range-from" );
}
int MetarPropertiesATISInformationProvider::getWindMaxDeg()
{
return _metar->getIntValue( "base-wind-range-to" );
}
int MetarPropertiesATISInformationProvider::getWindSpeedKt()
{
return _metar->getIntValue( "base-wind-speed-kt" );
}
int MetarPropertiesATISInformationProvider::getGustsKt()
{
return _metar->getIntValue( "gust-wind-speed-kt" );
}
int MetarPropertiesATISInformationProvider::getQnh()
{
return _metar->getDoubleValue("pressure-inhg") * SG_INHG_TO_PA / 100.0;
}
double MetarPropertiesATISInformationProvider::getQnhInHg()
{
return _metar->getDoubleValue("pressure-inhg");
}
bool MetarPropertiesATISInformationProvider::isCavok()
{
return _metar->getBoolValue( "cavok" );
}
int MetarPropertiesATISInformationProvider::getVisibilityMeters()
{
return _metar->getIntValue( "min-visibility-m" );
}
string MetarPropertiesATISInformationProvider::getPhenomena()
{
return _metar->getStringValue( "decoded" );
}
ATISInformationProvider::CloudEntries MetarPropertiesATISInformationProvider::getClouds()
{
CloudEntries reply;
using simgear::PropertyList;
PropertyList layers = _metar->getNode("clouds", true )->getChildren("layer");
for( PropertyList::iterator it = layers.begin(); it != layers.end(); ++it ) {
std::string coverage = (*it)->getStringValue("coverage", "clear");
double elevation = (*it)->getDoubleValue("elevation-ft", -9999 );
if( elevation > 0 ) {
reply[elevation] = coverage;
}
}
return reply;
}
int MetarPropertiesATISInformationProvider::getTemperatureDeg()
{
return _metar->getIntValue( "temperature-degc" );
}
int MetarPropertiesATISInformationProvider::getDewpointDeg()
{
return _metar->getIntValue( "dewpoint-degc" );
}
string MetarPropertiesATISInformationProvider::getTrend()
{
return "nosig";
}

View File

@@ -0,0 +1,58 @@
/*
Provide Data for the ATIS Encoder from metarproperties
Copyright (C) 2014 Torsten Dreyer
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef __METARPROPERTIES_ATIS_ENCODER_HXX
#define __METARPROPERTIES_ATIS_ENCODER_HXX
/* ATIS encoder from metarproperties */
#include <simgear/props/props.hxx>
#include <string>
#include "ATISEncoder.hxx"
class MetarPropertiesATISInformationProvider : public ATISInformationProvider
{
public:
MetarPropertiesATISInformationProvider( SGPropertyNode_ptr metar );
virtual ~MetarPropertiesATISInformationProvider();
protected:
virtual bool isValid();
virtual std::string airportId();
virtual long getTime();
virtual int getWindDeg();
virtual int getWindMinDeg();
virtual int getWindMaxDeg();
virtual int getWindSpeedKt();
virtual int getGustsKt();
virtual int getQnh();
virtual double getQnhInHg();
virtual bool isCavok();
virtual int getVisibilityMeters();
virtual std::string getPhenomena();
virtual CloudEntries getClouds();
virtual int getTemperatureDeg();
virtual int getDewpointDeg();
virtual std::string getTrend();
private:
SGPropertyNode_ptr _metar;
};
#endif

View File

@@ -0,0 +1,442 @@
// Extracted from trafficrecord.cxx - Implementation of AIModels ATC code.
//
// Written by Durk Talsma, started September 2006.
//
// Copyright (C) 2006 Durk Talsma.
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation; either version 2 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
//
// $Id$
#include <config.h>
#include <algorithm>
#include <cstdio>
#include <random>
#include <osg/Geode>
#include <osg/Geometry>
#include <osg/MatrixTransform>
#include <osg/Shape>
#include <simgear/scene/material/EffectGeode.hxx>
#include <simgear/scene/material/matlib.hxx>
#include <simgear/scene/material/mat.hxx>
#include <simgear/scene/util/OsgMath.hxx>
#include <simgear/timing/sg_time.hxx>
#include <Scenery/scenery.hxx>
#include "trafficcontrol.hxx"
#include "atc_mgr.hxx"
#include <AIModel/AIAircraft.hxx>
#include <AIModel/AIFlightPlan.hxx>
#include <AIModel/performancedata.hxx>
#include <Traffic/TrafficMgr.hxx>
#include <Airports/groundnetwork.hxx>
#include <Airports/dynamics.hxx>
#include <Airports/airport.hxx>
#include <Radio/radio.hxx>
#include <signal.h>
#include <ATC/atc_mgr.hxx>
#include <ATC/trafficcontrol.hxx>
#include <ATC/ATCController.hxx>
#include <ATC/StartupController.hxx>
using std::sort;
using std::string;
/***************************************************************************
* class FGStartupController
* subclass of FGATCController
**************************************************************************/
FGStartupController::FGStartupController(FGAirportDynamics *par):
FGATCController()
{
parent = par;
}
FGStartupController::~FGStartupController()
{
}
void FGStartupController::announcePosition(int id,
FGAIFlightPlan * intendedRoute,
int currentPosition, double lat,
double lon, double heading,
double speed, double alt,
double radius, int leg,
FGAIAircraft * ref)
{
init();
// Search activeTraffic for a record matching our id
TrafficVectorIterator i = FGATCController::searchActiveTraffic(id);
// Add a new TrafficRecord if no one exsists for this aircraft.
if (i == activeTraffic.end() || activeTraffic.empty()) {
FGTrafficRecord rec;
rec.setId(id);
rec.setPositionAndHeading(lat, lon, heading, speed, alt);
rec.setRunway(intendedRoute->getRunway());
rec.setLeg(leg);
rec.setPositionAndIntentions(currentPosition, intendedRoute);
rec.setCallsign(ref->getCallSign());
rec.setAircraft(ref);
rec.setHoldPosition(true);
activeTraffic.push_back(rec);
} else {
i->setPositionAndIntentions(currentPosition, intendedRoute);
i->setPositionAndHeading(lat, lon, heading, speed, alt);
}
}
bool FGStartupController::checkTransmissionState(int st, time_t now, time_t startTime, TrafficVectorIterator i, AtcMsgId msgId,
AtcMsgDir msgDir)
{
int state = i->getState();
if ((state == st) && available) {
if ((msgDir == ATC_AIR_TO_GROUND) && isUserAircraft(i->getAircraft())) {
SG_LOG(SG_ATC, SG_BULK, "Checking state " << st << " for " << i->getAircraft()->getCallSign());
SGPropertyNode_ptr trans_num = globals->get_props()->getNode("/sim/atc/transmission-num", true);
int n = trans_num->getIntValue();
if (n == 0) {
trans_num->setIntValue(-1);
// PopupCallback(n);
SG_LOG(SG_ATC, SG_BULK, "Selected transmission message " << n);
//FGATCDialogNew::instance()->removeEntry(1);
} else {
SG_LOG(SG_ATC, SG_BULK, "Creating message for " << i->getAircraft()->getCallSign());
transmit(&(*i), &(*parent), msgId, msgDir, false);
return false;
}
}
if (now > startTime) {
SG_LOG(SG_ATC, SG_BULK, "Transmitting startup msg");
transmit(&(*i), &(*parent), msgId, msgDir, true);
i->updateState();
lastTransmission = now;
available = false;
return true;
}
}
return false;
}
void FGStartupController::updateAircraftInformation(int id, SGGeod geod,
double heading, double speed, double alt,
double dt)
{
// Search activeTraffic for a record matching our id
TrafficVectorIterator i = FGATCController::searchActiveTraffic(id);
TrafficVectorIterator current, closest;
if (i == activeTraffic.end() || (activeTraffic.size() == 0)) {
SG_LOG(SG_ATC, SG_ALERT,
"AI error: updating aircraft without traffic record at " << SG_ORIGIN);
return;
} else {
i->setPositionAndHeading(geod.getLatitudeDeg(), geod.getLongitudeDeg(), heading, speed, alt);
current = i;
}
setDt(getDt() + dt);
int state = i->getState();
// Sentry FLIGHTGEAR-2Q : don't crash on null TrafficRef
// Sentry FLIGHTGEAR-129: don't crash on null aircraft
if (!i->getAircraft() || !i->getAircraft()->getTrafficRef()) {
SG_LOG(SG_ATC, SG_ALERT, "AI traffic: updating aircraft without traffic ref");
return;
}
// The user controlled aircraft should have crased here, because it doesn't have a traffic reference.
// NOTE: if we create a traffic schedule for the user aircraft, we can use this to plan a flight.
time_t startTime = i->getAircraft()->getTrafficRef()->getDepartureTime();
time_t now = globals->get_time_params()->get_cur_time();
if ((startTime - now) > 0) {
SG_LOG(SG_ATC, SG_BULK, i->getAircraft()->getTrafficRef()->getCallSign() << " is scheduled to depart in " << startTime - now << " seconds. Available = " << available << " at parking " << getGateName(i->getAircraft()));
}
if ((now - lastTransmission) > 3 + (rand() % 15)) {
available = true;
}
checkTransmissionState(0, now, (startTime + 0 ), i, MSG_ANNOUNCE_ENGINE_START, ATC_AIR_TO_GROUND);
checkTransmissionState(1, now, (startTime + 60 ), i, MSG_REQUEST_ENGINE_START, ATC_AIR_TO_GROUND);
checkTransmissionState(2, now, (startTime + 80 ), i, MSG_PERMIT_ENGINE_START, ATC_GROUND_TO_AIR);
checkTransmissionState(3, now, (startTime + 100), i, MSG_ACKNOWLEDGE_ENGINE_START, ATC_AIR_TO_GROUND);
if (checkTransmissionState(4, now, (startTime + 130), i, MSG_ACKNOWLEDGE_SWITCH_GROUND_FREQUENCY, ATC_AIR_TO_GROUND)) {
i->nextFrequency();
}
checkTransmissionState(5, now, (startTime + 140), i, MSG_INITIATE_CONTACT, ATC_AIR_TO_GROUND);
checkTransmissionState(6, now, (startTime + 150), i, MSG_ACKNOWLEDGE_INITIATE_CONTACT, ATC_GROUND_TO_AIR);
checkTransmissionState(7, now, (startTime + 180), i, MSG_REQUEST_PUSHBACK_CLEARANCE, ATC_AIR_TO_GROUND);
if ((state == 8) && available) {
if (now > startTime + 200) {
if (i->pushBackAllowed()) {
i->allowRepeatedTransmissions();
transmit(&(*i), &(*parent), MSG_PERMIT_PUSHBACK_CLEARANCE,
ATC_GROUND_TO_AIR, true);
i->updateState();
} else {
transmit(&(*i), &(*parent), MSG_HOLD_PUSHBACK_CLEARANCE,
ATC_GROUND_TO_AIR, true);
i->suppressRepeatedTransmissions();
}
lastTransmission = now;
available = false;
}
}
if ((state == 9) && available) {
i->setHoldPosition(false);
}
}
// Note that this function is copied from simgear. for maintanance purposes, it's probabtl better to make a general function out of that.
static void WorldCoordinate(osg::Matrix& obj_pos, double lat,
double lon, double elev, double hdg, double slope)
{
SGGeod geod = SGGeod::fromDegM(lon, lat, elev);
obj_pos = makeZUpFrame(geod);
// hdg is not a compass heading, but a counter-clockwise rotation
// around the Z axis
obj_pos.preMult(osg::Matrix::rotate(hdg * SGD_DEGREES_TO_RADIANS,
0.0, 0.0, 1.0));
obj_pos.preMult(osg::Matrix::rotate(slope * SGD_DEGREES_TO_RADIANS,
0.0, 1.0, 0.0));
}
void FGStartupController::render(bool visible)
{
SG_LOG(SG_ATC, SG_DEBUG, "Rendering startup controller");
SGMaterialLib *matlib = globals->get_matlib();
if (group) {
//int nr = ;
globals->get_scenery()->get_scene_graph()->removeChild(group);
//while (group->getNumChildren()) {
// SG_LOG(SG_ATC, SG_BULK, "Number of children: " << group->getNumChildren());
//simgear::EffectGeode* geode = (simgear::EffectGeode*) group->getChild(0);
//osg::MatrixTransform *obj_trans = (osg::MatrixTransform*) group->getChild(0);
//geode->releaseGLObjects();
//group->removeChild(geode);
//delete geode;
group = 0;
}
if (visible) {
group = new osg::Group;
FGScenery * local_scenery = globals->get_scenery();
//double elevation_meters = 0.0;
//double elevation_feet = 0.0;
FGGroundNetwork* groundNet = parent->parent()->groundNetwork();
//for ( FGTaxiSegmentVectorIterator i = segments.begin(); i != segments.end(); i++) {
double dx = 0;
time_t now = globals->get_time_params()->get_cur_time();
for (TrafficVectorIterator i = activeTraffic.begin(); i != activeTraffic.end(); i++) {
if (i->isActive(300)) {
// Handle start point
int pos = i->getCurrentPosition();
SG_LOG(SG_ATC, SG_BULK, "rendering for " << i->getAircraft()->getCallSign() << "pos = " << pos);
if (pos > 0) {
FGTaxiSegment *segment = groundNet->findSegment(pos);
SGGeod start = i->getPos();
SGGeod end (segment->getEnd()->geod());
double length = SGGeodesy::distanceM(start, end);
//heading = SGGeodesy::headingDeg(start->geod(), end->geod());
double az2, heading; //, distanceM;
SGGeodesy::inverse(start, end, heading, az2, length);
double coveredDistance = length * 0.5;
SGGeod center;
SGGeodesy::direct(start, heading, coveredDistance, center, az2);
SG_LOG(SG_ATC, SG_BULK, "Active Aircraft : Centerpoint = (" << center.getLatitudeDeg() << ", " << center.getLongitudeDeg() << "). Heading = " << heading);
///////////////////////////////////////////////////////////////////////////////
// Make a helper function out of this
osg::Matrix obj_pos;
osg::MatrixTransform *obj_trans = new osg::MatrixTransform;
obj_trans->setDataVariance(osg::Object::STATIC);
// Experimental: Calculate slope here, based on length, and the individual elevations
double elevationStart;
if (isUserAircraft((i)->getAircraft())) {
elevationStart = fgGetDouble("/position/ground-elev-m");
} else {
elevationStart = ((i)->getAircraft()->_getAltitude() * SG_FEET_TO_METER);
}
double elevationEnd = segment->getEnd()->getElevationM();
if ((elevationEnd == 0) || (elevationEnd == parent->getElevation())) {
SGGeod center2 = end;
center2.setElevationM(SG_MAX_ELEVATION_M);
if (local_scenery->get_elevation_m( center2, elevationEnd, NULL )) {
//elevation_feet = elevationEnd * SG_METER_TO_FEET + 0.5;
//elevation_meters += 0.5;
}
else {
elevationEnd = parent->getElevation();
}
segment->getEnd()->setElevation(elevationEnd);
}
double elevationMean = (elevationStart + elevationEnd) / 2.0;
double elevDiff = elevationEnd - elevationStart;
double slope = atan2(elevDiff, length) * SGD_RADIANS_TO_DEGREES;
SG_LOG(SG_ATC, SG_BULK, "1. Using mean elevation : " << elevationMean << " and " << slope);
WorldCoordinate( obj_pos, center.getLatitudeDeg(), center.getLongitudeDeg(), elevationMean + 0.5 + dx, -(heading), slope );
;
obj_trans->setMatrix( obj_pos );
//osg::Vec3 center(0, 0, 0)
float width = length /2.0;
osg::Vec3 corner(-width, 0, 0.25f);
osg::Vec3 widthVec(2*width + 1, 0, 0);
osg::Vec3 heightVec(0, 1, 0);
osg::Geometry* geometry;
geometry = osg::createTexturedQuadGeometry(corner, widthVec, heightVec);
simgear::EffectGeode* geode = new simgear::EffectGeode;
geode->setName("test");
geode->addDrawable(geometry);
//osg::Node *custom_obj;
SGMaterial *mat;
if (segment->hasBlock(now)) {
mat = matlib->find("UnidirectionalTaperRed", center);
} else {
mat = matlib->find("UnidirectionalTaperGreen", center);
}
if (mat)
geode->setEffect(mat->get_effect());
obj_trans->addChild(geode);
// wire as much of the scene graph together as we can
//->addChild( obj_trans );
group->addChild( obj_trans );
/////////////////////////////////////////////////////////////////////
} else {
SG_LOG(SG_ATC, SG_DEBUG, "BIG FAT WARNING: current position is here : " << pos);
}
for (intVecIterator j = (i)->getIntentions().begin(); j != (i)->getIntentions().end(); j++) {
osg::Matrix obj_pos;
int k = (*j);
if (k > 0) {
SG_LOG(SG_ATC, SG_BULK, "rendering for " << i->getAircraft()->getCallSign() << "intention = " << k);
osg::MatrixTransform *obj_trans = new osg::MatrixTransform;
obj_trans->setDataVariance(osg::Object::STATIC);
FGTaxiSegment *segment = groundNet->findSegment(k);
double elevationStart = segment->getStart()->getElevationM();
double elevationEnd = segment->getEnd ()->getElevationM();
if ((elevationStart == 0) || (elevationStart == parent->getElevation())) {
SGGeod center2 = segment->getStart()->geod();
center2.setElevationM(SG_MAX_ELEVATION_M);
if (local_scenery->get_elevation_m( center2, elevationStart, NULL )) {
//elevation_feet = elevationStart * SG_METER_TO_FEET + 0.5;
//elevation_meters += 0.5;
}
else {
elevationStart = parent->getElevation();
}
segment->getStart()->setElevation(elevationStart);
}
if ((elevationEnd == 0) || (elevationEnd == parent->getElevation())) {
SGGeod center2 = segment->getEnd()->geod();
center2.setElevationM(SG_MAX_ELEVATION_M);
if (local_scenery->get_elevation_m( center2, elevationEnd, NULL )) {
//elevation_feet = elevationEnd * SG_METER_TO_FEET + 0.5;
//elevation_meters += 0.5;
}
else {
elevationEnd = parent->getElevation();
}
segment->getEnd()->setElevation(elevationEnd);
}
double elevationMean = (elevationStart + elevationEnd) / 2.0;
double elevDiff = elevationEnd - elevationStart;
double length = segment->getLength();
double slope = atan2(elevDiff, length) * SGD_RADIANS_TO_DEGREES;
SG_LOG(SG_ATC, SG_BULK, "2. Using mean elevation : " << elevationMean << " and " << slope);
SGGeod segCenter(segment->getCenter());
WorldCoordinate( obj_pos, segCenter.getLatitudeDeg(),
segCenter.getLongitudeDeg(), elevationMean + 0.5 + dx, -(segment->getHeading()), slope );
//WorldCoordinate( obj_pos, segment->getLatitude(), segment->getLongitude(), parent->getElevation()+8+dx, -(segment->getHeading()) );
obj_trans->setMatrix( obj_pos );
//osg::Vec3 center(0, 0, 0)
float width = segment->getLength() /2.0;
osg::Vec3 corner(-width, 0, 0.25f);
osg::Vec3 widthVec(2*width + 1, 0, 0);
osg::Vec3 heightVec(0, 1, 0);
osg::Geometry* geometry;
geometry = osg::createTexturedQuadGeometry(corner, widthVec, heightVec);
simgear::EffectGeode* geode = new simgear::EffectGeode;
geode->setName("test");
geode->addDrawable(geometry);
//osg::Node *custom_obj;
SGMaterial *mat;
if (segment->hasBlock(now)) {
mat = matlib->find("UnidirectionalTaperRed", segCenter);
} else {
mat = matlib->find("UnidirectionalTaperGreen", segCenter);
}
if (mat)
geode->setEffect(mat->get_effect());
obj_trans->addChild(geode);
// wire as much of the scene graph together as we can
//->addChild( obj_trans );
group->addChild( obj_trans );
} else {
SG_LOG(SG_ATC, SG_DEBUG, "BIG FAT WARNING: k is here : " << pos);
}
}
dx += 0.2;
}
}
globals->get_scenery()->get_scene_graph()->addChild(group);
}
}
string FGStartupController::getName() {
return string(parent->parent()->getName() + "-Startup");
}
void FGStartupController::update(double dt)
{
FGATCController::eraseDeadTraffic();
}
int FGStartupController::getFrequency() {
int groundFreq = parent->getGroundFrequency(2);
int towerFreq = parent->getTowerFrequency(2);
return groundFreq>0?groundFreq:towerFreq;
}

View File

@@ -0,0 +1,71 @@
// Extracted from trafficcontrol.hxx - classes to manage AIModels based air traffic control
// Written by Durk Talsma, started September 2006.
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation; either version 2 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
//
// $Id$
#ifndef STARTUP_CONTROLLER_HXX
#define STARTUP_CONTROLLER_HXX
#include <Airports/airports_fwd.hxx>
#include <osg/Geode>
#include <osg/Geometry>
#include <osg/MatrixTransform>
#include <osg/Shape>
#include <simgear/compiler.h>
#include <simgear/constants.h>
#include <simgear/debug/logstream.hxx>
#include <simgear/structure/SGReferenced.hxx>
#include <simgear/structure/SGSharedPtr.hxx>
#include <ATC/ATCController.hxx>
#include <ATC/trafficcontrol.hxx>
/******************************************************************************
* class FGStartupController
* handle
*****************************************************************************/
class FGStartupController : public FGATCController
{
private:
/**Returns the frequency to be used. */
int getFrequency();
public:
FGStartupController(FGAirportDynamics *parent);
virtual ~FGStartupController();
virtual void announcePosition(int id, FGAIFlightPlan *intendedRoute, int currentRoute,
double lat, double lon,
double hdg, double spd, double alt, double radius, int leg,
FGAIAircraft *aircraft);
virtual void updateAircraftInformation(int id, SGGeod geod,
double heading, double speed, double alt, double dt);
virtual void render(bool);
virtual std::string getName();
virtual void update(double dt);
// Hpoefully, we can move this function to the base class, but I need to verify what is needed for the other controllers before doing so.
bool checkTransmissionState(int st, time_t now, time_t startTime, TrafficVectorIterator i, AtcMsgId msgId,
AtcMsgDir msgDir);
};
#endif

308
src/ATC/TowerController.cxx Normal file
View File

@@ -0,0 +1,308 @@
// Extracted from trafficrecord.cxx - Implementation of AIModels ATC code.
//
// Written by Durk Talsma, started September 2006.
//
// Copyright (C) 2006 Durk Talsma.
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation; either version 2 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
//
// $Id$
#include <config.h>
#include <algorithm>
#include <cstdio>
#include <random>
#include <osg/Geode>
#include <osg/Geometry>
#include <osg/MatrixTransform>
#include <osg/Shape>
#include <simgear/scene/material/EffectGeode.hxx>
#include <simgear/scene/material/matlib.hxx>
#include <simgear/scene/material/mat.hxx>
#include <simgear/scene/util/OsgMath.hxx>
#include <simgear/timing/sg_time.hxx>
#include <Scenery/scenery.hxx>
#include "trafficcontrol.hxx"
#include "atc_mgr.hxx"
#include <AIModel/AIAircraft.hxx>
#include <AIModel/AIFlightPlan.hxx>
#include <AIModel/performancedata.hxx>
#include <Traffic/TrafficMgr.hxx>
#include <Airports/groundnetwork.hxx>
#include <Airports/dynamics.hxx>
#include <Airports/airport.hxx>
#include <Radio/radio.hxx>
#include <signal.h>
#include <ATC/atc_mgr.hxx>
#include <ATC/ATCController.hxx>
#include <ATC/trafficcontrol.hxx>
#include <ATC/TowerController.hxx>
using std::sort;
using std::string;
/***************************************************************************
* class FGTowerController
s * subclass of FGATCController
**************************************************************************/
FGTowerController::FGTowerController(FGAirportDynamics *par) :
FGATCController()
{
parent = par;
}
FGTowerController::~FGTowerController()
{
}
//
void FGTowerController::announcePosition(int id,
FGAIFlightPlan * intendedRoute,
int currentPosition, double lat,
double lon, double heading,
double speed, double alt,
double radius, int leg,
FGAIAircraft * ref)
{
init();
// Search activeTraffic for a record matching our id
TrafficVectorIterator i = FGATCController::searchActiveTraffic(id);
// Add a new TrafficRecord if no one exsists for this aircraft.
if (i == activeTraffic.end() || (activeTraffic.empty())) {
FGTrafficRecord rec;
rec.setId(id);
rec.setPositionAndHeading(lat, lon, heading, speed, alt);
rec.setRunway(intendedRoute->getRunway());
rec.setLeg(leg);
//rec.setCallSign(callsign);
rec.setRadius(radius);
rec.setAircraft(ref);
activeTraffic.push_back(rec);
// Don't just schedule the aircraft for the tower controller, also assign if to the correct active runway.
ActiveRunwayVecIterator rwy = activeRunways.begin();
if (! activeRunways.empty()) {
while (rwy != activeRunways.end()) {
if (rwy->getRunwayName() == intendedRoute->getRunway()) {
break;
}
rwy++;
}
}
if (rwy == activeRunways.end()) {
ActiveRunway aRwy(intendedRoute->getRunway(), id);
aRwy.addToDepartureQueue(ref);
activeRunways.push_back(aRwy);
rwy = (activeRunways.end()-1);
} else {
rwy->addToDepartureQueue(ref);
}
SG_LOG(SG_ATC, SG_DEBUG, ref->getTrafficRef()->getCallSign() << " You are number " << rwy->getdepartureQueueSize() << " for takeoff ");
} else {
i->setPositionAndHeading(lat, lon, heading, speed, alt);
}
}
void FGTowerController::updateAircraftInformation(int id, SGGeod geod,
double heading, double speed, double alt,
double dt)
{
// Search activeTraffic for a record matching our id
TrafficVectorIterator i = FGATCController::searchActiveTraffic(id);
setDt(getDt() + dt);
if (i == activeTraffic.end() || (activeTraffic.empty())) {
SG_LOG(SG_ATC, SG_ALERT,
"AI error: updating aircraft without traffic record at " <<
SG_ORIGIN);
return;
}
// Update the position of the current aircraft
FGTrafficRecord& current = *i;
current.setPositionAndHeading(geod.getLatitudeDeg(), geod.getLongitudeDeg(), heading, speed, alt);
// see if we already have a clearance record for the currently active runway
// NOTE: dd. 2011-08-07: Because the active runway has been constructed in the announcePosition function, we may safely assume that is
// already exists here. So, we can simplify the current code.
ActiveRunwayVecIterator rwy = activeRunways.begin();
//if (parent->getId() == fgGetString("/sim/presets/airport-id")) {
// for (rwy = activeRunways.begin(); rwy != activeRunways.end(); rwy++) {
// rwy->printdepartureQueue();
// }
//}
rwy = activeRunways.begin();
while (rwy != activeRunways.end()) {
if (rwy->getRunwayName() == current.getRunway()) {
break;
}
rwy++;
}
// only bother running the following code if the current aircraft is the
// first in line for depature
/* if (current.getAircraft() == rwy->getFirstAircraftIndepartureQueue()) {
if (rwy->getCleared()) {
if (id == rwy->getCleared()) {
current.setHoldPosition(false);
} else {
current.setHoldPosition(true);
}
} else {
// For now. At later stages, this will probably be the place to check for inbound traffc.
rwy->setCleared(id);
}
} */
// only bother with aircraft that have a takeoff status of 2, since those are essentially under tower control
auto ac = rwy->getFirstAircraftInDepartureQueue();
if (ac) {
if (ac->getTakeOffStatus() == 1) {
// transmit takeoff clearance
ac->setTakeOffStatus(2);
transmit(&(*i), &(*parent), MSG_CLEARED_FOR_TAKEOFF, ATC_GROUND_TO_AIR, true);
i->setState(10);
}
}
//FIXME Make it an member of traffic record
if (current.getAircraft()->getTakeOffStatus() == 2) {
current.setHoldPosition(false);
} else {
current.setHoldPosition(true);
}
int clearanceId = rwy->getCleared();
if (clearanceId) {
if (id == clearanceId) {
SG_LOG(SG_ATC, SG_BULK, "Unset Hold " << clearanceId << " for " << rwy->getRunwayName());
current.setHoldPosition(false);
} else {
SG_LOG(SG_ATC, SG_WARN, "Not cleared " << id << " " << clearanceId);
}
} else {
if (current.getAircraft() == rwy->getFirstAircraftInDepartureQueue()) {
SG_LOG(SG_ATC, SG_BULK,
"Cleared " << current.getAircraft()->getCallSign() << " for " << rwy->getRunwayName() << " Id " << id);
rwy->setCleared(id);
auto ac = rwy->getFirstOfStatus(1);
if (ac) {
ac->setTakeOffStatus(2);
// transmit takeoff clearacne? But why twice?
}
} else {
SG_LOG(SG_ATC, SG_BULK,
"Not cleared " << current.getAircraft()->getCallSign() << " " << rwy->getFirstAircraftInDepartureQueue()->getCallSign());
}
}
}
void FGTowerController::signOff(int id)
{
SG_LOG(SG_ATC, SG_BULK, "Signing off " << id << " from Tower");
// ensure we don't modify activeTraffic during destruction
if (_isDestroying)
return;
// Search activeTraffic for a record matching our id
TrafficVectorIterator i = FGATCController::searchActiveTraffic(id);
if (i == activeTraffic.end() || (activeTraffic.empty())) {
SG_LOG(SG_ATC, SG_ALERT,
"AI error: Aircraft without traffic record is signing off from tower at " << SG_ORIGIN);
return;
}
const auto trafficRunway = i->getRunway();
auto runwayIt = std::find_if(activeRunways.begin(), activeRunways.end(),
[&trafficRunway](const ActiveRunway& ar) {
return ar.getRunwayName() == trafficRunway;
});
if (runwayIt != activeRunways.end()) {
SG_LOG(SG_ATC, SG_BULK, "Cleared " << id << " from " << runwayIt->getRunwayName() );
runwayIt->setCleared(0);
runwayIt->updateDepartureQueue();
} else {
SG_LOG(SG_ATC, SG_ALERT,
"AI error: Attempting to erase non-existing runway clearance record in FGTowerController::signoff at " << SG_ORIGIN);
}
i->getAircraft()->resetTakeOffStatus();
FGATCController::signOff(id);
}
// NOTE:
// IF WE MAKE TRAFFICRECORD A MEMBER OF THE BASE CLASS
// THE FOLLOWING THREE FUNCTIONS: SIGNOFF, HAS INSTRUCTION AND GETINSTRUCTION CAN
// BECOME DEVIRTUALIZED AND BE A MEMBER OF THE BASE ATCCONTROLLER CLASS
// WHICH WOULD SIMPLIFY CODE MAINTENANCE.
// Note that this function is probably obsolete
bool FGTowerController::hasInstruction(int id)
{
// Search activeTraffic for a record matching our id
TrafficVectorIterator i = FGATCController::searchActiveTraffic(id);
if (i == activeTraffic.end() || activeTraffic.empty()) {
SG_LOG(SG_ATC, SG_ALERT,
"AI error: checking ATC instruction for aircraft without traffic record at " << SG_ORIGIN);
} else {
return i->hasInstruction();
}
return false;
}
FGATCInstruction FGTowerController::getInstruction(int id)
{
// Search activeTraffic for a record matching our id
TrafficVectorIterator i = FGATCController::searchActiveTraffic(id);
if (i == activeTraffic.end() || activeTraffic.empty()) {
SG_LOG(SG_ATC, SG_ALERT,
"AI error: requesting ATC instruction for aircraft without traffic record at " << SG_ORIGIN);
} else {
return i->getInstruction();
}
return FGATCInstruction();
}
void FGTowerController::render(bool visible) {
// this should be bulk, since its called quite often
SG_LOG(SG_ATC, SG_BULK, "FGTowerController::render function not yet implemented");
}
string FGTowerController::getName() {
return string(parent->getId() + "-tower");
}
void FGTowerController::update(double dt)
{
FGATCController::eraseDeadTraffic();
}
int FGTowerController::getFrequency() {
int towerFreq = parent->getTowerFrequency(2);
return towerFreq;
}

View File

@@ -0,0 +1,68 @@
// Extracted from trafficcontrol.hxx - classes to manage AIModels based air traffic control
// Written by Durk Talsma, started September 2006.
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation; either version 2 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
//
// $Id$
#ifndef TOWER_CONTROLLER_HXX
#define TOWER_CONTROLLER_HXX
#include <Airports/airports_fwd.hxx>
#include <osg/Geode>
#include <osg/Geometry>
#include <osg/MatrixTransform>
#include <osg/Shape>
#include <simgear/compiler.h>
#include <simgear/constants.h>
#include <simgear/debug/logstream.hxx>
#include <simgear/structure/SGReferenced.hxx>
#include <simgear/structure/SGSharedPtr.hxx>
#include <ATC/ATCController.hxx>
#include <ATC/trafficcontrol.hxx>
/******************************************************************************
* class FGTowerControl
*****************************************************************************/
class FGTowerController : public FGATCController
{
private:
ActiveRunwayVec activeRunways;
/**Returns the frequency to be used. */
int getFrequency();
public:
FGTowerController(FGAirportDynamics *parent);
virtual ~FGTowerController();
virtual void announcePosition(int id, FGAIFlightPlan *intendedRoute, int currentRoute,
double lat, double lon,
double hdg, double spd, double alt, double radius, int leg,
FGAIAircraft *aircraft);
void signOff(int id);
bool hasInstruction(int id);
FGATCInstruction getInstruction(int id);
virtual void updateAircraftInformation(int id, SGGeod geod,
double heading, double speed, double alt, double dt);
virtual void render(bool);
virtual std::string getName();
virtual void update(double dt);
};
#endif

414
src/ATC/atc_mgr.cxx Normal file
View File

@@ -0,0 +1,414 @@
/******************************************************************************
* atc_mgr.cxx
* Written by Durk Talsma, started August 1, 2010.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
*
**************************************************************************/
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
#include <Airports/dynamics.hxx>
#include <Airports/airportdynamicsmanager.hxx>
#include <Airports/airport.hxx>
#include <Scenery/scenery.hxx>
#include <Main/globals.hxx>
#include <Main/fg_props.hxx>
#include <AIModel/AIAircraft.hxx>
#include <AIModel/AIManager.hxx>
#include <Traffic/Schedule.hxx>
#include <Traffic/SchedFlight.hxx>
#include <AIModel/AIFlightPlan.hxx>
#include "atc_mgr.hxx"
using std::string;
/**
Constructer, initializes values to private boolean and FGATCController instances
*/
FGATCManager::FGATCManager() :
controller(NULL),
prevController(NULL),
networkVisible(false),
initSucceeded(false)
{
}
/**
Default destructor
*/
FGATCManager::~FGATCManager() {
}
/**
Sets up ATC subsystem parts depending on other subsystems
Override of SGSubsystem::postinit()
Will set private boolean flag "initSucceeded" to true upon conclusion
*/
void FGATCManager::postinit()
{
int leg = 0;
trans_num = globals->get_props()->getNode("/sim/atc/transmission-num", true);
// Assign a controller to the user's aircraft.
// Three scenarios are considered:
// - Starting on ground at a parking position
// - Starting on ground at the runway.
// - Starting in the air
bool onGround = fgGetBool("/sim/presets/onground");
string runway = fgGetString("/sim/atc/runway");
string curAirport = fgGetString("/sim/presets/airport-id");
string parking = fgGetString("/sim/presets/parkpos");
_routeManagerDestinationAirportNode = globals->get_props()->getNode("/autopilot/route-manager/destination/airport", true);
destination = _routeManagerDestinationAirportNode->getStringValue();
FGAIManager* aiManager = globals->get_subsystem<FGAIManager>();
auto userAircraft = aiManager->getUserAircraft();
string callsign = userAircraft->getCallSign();
double aircraftRadius = 40; // note that this is currently hardcoded to a one-size-fits all JumboJet value. Should change later.
// In case a destination is not set yet, make it equal to the current airport
if (destination.empty()) {
destination = curAirport;
}
// NEXT UP: Create a traffic schedule and fill that with appropriate information. This we can use for flight planning.
// Note that these are currently only defaults.
userAircraftTrafficRef.reset(new FGAISchedule);
userAircraftTrafficRef->setFlightType("gate");
userAircraftScheduledFlight.reset(new FGScheduledFlight);
userAircraftScheduledFlight->setDepartureAirport(curAirport);
userAircraftScheduledFlight->setArrivalAirport(destination);
userAircraftScheduledFlight->initializeAirports();
userAircraftScheduledFlight->setFlightRules("IFR");
userAircraftScheduledFlight->setCallSign(callsign);
userAircraftTrafficRef->assign(userAircraftScheduledFlight.get());
std::unique_ptr<FGAIFlightPlan> fp ;
userAircraft->setTrafficRef(userAircraftTrafficRef.get());
string flightPlanName = curAirport + "-" + _routeManagerDestinationAirportNode->getStringValue() + ".xml";
//double cruiseAlt = 100; // Doesn't really matter right now.
//double courseToDest = 180; // Just use something neutral; this value might affect the runway that is used though...
//time_t deptime = 0; // just make sure how flightplan processing is affected by this...
FGAirportDynamicsRef dcs(flightgear::AirportDynamicsManager::find(curAirport));
if (dcs && onGround) {// && !runway.empty()) {
ParkingAssignment pk;
if (parking == "AVAILABLE") {
double radius = fgGetDouble("/sim/dimensions/radius-m");
if (radius > 0) {
pk = dcs->getAvailableParking(radius, string(), string(), string());
if (pk.isValid()) {
fgGetString("/sim/presets/parkpos");
fgSetString("/sim/presets/parkpos", pk.parking()->getName());
}
}
if (!pk.isValid()) {
FGParkingList pkl(dcs->getParkings(true, "gate"));
if (!pkl.empty()) {
std::sort(pkl.begin(), pkl.end(), [](const FGParkingRef& a, const FGParkingRef& b) {
return a->getRadius() > b->getRadius();
});
pk = ParkingAssignment(pkl.front(), dcs);
fgSetString("/sim/presets/parkpos", pkl.front()->getName());
}
}
} else if (!parking.empty()) {
pk = dcs->getAvailableParkingByName(parking);
}
if (pk.isValid()) {
dcs->setParkingAvailable(pk.parking(), false);
fp.reset(new FGAIFlightPlan);
controller = dcs->getStartupController();
int stationFreq = dcs->getGroundFrequency(1);
if (stationFreq > 0)
{
SG_LOG(SG_ATC, SG_DEBUG, "Setting radio frequency to : " << stationFreq);
fgSetDouble("/instrumentation/comm[0]/frequencies/selected-mhz", ((double) stationFreq / 100.0));
}
leg = 1;
//double, lat, lon, head; // Unused variables;
//int getId = apt->getDynamics()->getParking(gateId, &lat, &lon, &head);
aircraftRadius = pk.parking()->getRadius();
string fltType = pk.parking()->getType(); // gate / ramp, ga, etc etc.
string aircraftType; // Unused.
string airline; // Currently used for gate selection, but a fallback mechanism will apply when not specified.
fp->setGate(pk);
if (!(fp->createPushBack(userAircraft,
false,
dcs->parent(),
aircraftRadius,
fltType,
aircraftType,
airline))) {
controller = 0;
return;
}
} else if (!runway.empty()) {
// on a runway
controller = dcs->getTowerController();
int stationFreq = dcs->getTowerFrequency(2);
if (stationFreq > 0)
{
SG_LOG(SG_ATC, SG_DEBUG, "Setting radio frequency to inair frequency : " << stationFreq);
fgSetDouble("/instrumentation/comm[0]/frequencies/selected-mhz", ((double) stationFreq / 100.0));
}
fp.reset(new FGAIFlightPlan);
leg = 3;
string fltType = "ga";
fp->setRunway(runway);
fp->createTakeOff(userAircraft, false, dcs->parent(), {}, 0, fltType);
userAircraft->setTakeOffStatus(2);
} else {
// We're on the ground somewhere. Handle this case later.
// important : we are on the ground, so reset the AIFlightPlan back to
// a dummy one. Otherwise, in the reposition case, we end up with a
// stale flight-plan which confuses other code (eg, PositionInit::finalizeForParking)
// see unit test: PosInitTests::testRepositionAtOccupied
fp.reset(FGAIFlightPlan::createDummyUserPlan());
userAircraft->FGAIBase::setFlightPlan(std::move(fp));
controller = nullptr;
initSucceeded = true; // should be false?
return;
}
if (fp && !fp->empty()) {
fp->getLastWaypoint()->setName( fp->getLastWaypoint()->getName() + string("legend"));
}
} else {
controller = 0;
}
// Create an initial flightplan and assign it to the ai_ac. We won't use this flightplan, but it is necessary to
// keep the ATC code happy.
// note in the reposition case, 'fp' is only the new FlightPlan; if we didn't create one here.
// we will continue using the existing flight plan (and not restart it, for example)
if (fp) {
fp->restart();
fp->setLeg(leg);
userAircraft->FGAIBase::setFlightPlan(std::move(fp));
}
if (controller) {
FGAIFlightPlan* plan = userAircraft->GetFlightPlan();
const int routeIndex = (plan && plan->getCurrentWaypoint()) ? plan->getCurrentWaypoint()->getRouteIndex() : 0;
controller->announcePosition(userAircraft->getID(), plan,
routeIndex,
userAircraft->_getLatitude(),
userAircraft->_getLongitude(),
userAircraft->_getHeading(),
userAircraft->getSpeed(),
userAircraft->getAltitude(),
aircraftRadius, leg, userAircraft);
}
initSucceeded = true;
}
/**
Shutdown method
Clears activeStations vector in preparation for clean shutdown
Override of SGSubsystem::shutdown()
*/
void FGATCManager::shutdown()
{
activeStations.clear();
userAircraftTrafficRef.reset();
userAircraftScheduledFlight.reset();
_routeManagerDestinationAirportNode.clear();
}
void FGATCManager::reposition()
{
prevController = controller = nullptr;
// remove any parking assignment form the user flight-plan, so it's
// available again. postinit() will recompute a new value if required
FGAIManager* aiManager = globals->get_subsystem<FGAIManager>();
auto userAircraft = aiManager->getUserAircraft();
if (userAircraft) {
if (userAircraft->GetFlightPlan()) {
auto userAIFP = userAircraft->GetFlightPlan();
userAIFP->setGate({}); // clear any assignment
}
userAircraft->clearATCController();
}
postinit(); // critical for position-init logic
}
/**
Adds FGATCController instance to std::vector activeStations.
FGATCController is a basic class for every controller
*/
void FGATCManager::addController(FGATCController *controller) {
activeStations.push_back(controller);
}
/**
Searches for and removes FGATCController instance from std::vector activeStations
*/
void FGATCManager::removeController(FGATCController *controller)
{
AtcVecIterator it;
it = std::find(activeStations.begin(), activeStations.end(), controller);
if (it != activeStations.end()) {
activeStations.erase(it);
}
}
/**
Update the subsystem.
FlightGear invokes this method every time the subsystem should
update its state.
@param time The delta time, in seconds, since the last
update. On first update, delta time will be 0.
*/
void FGATCManager::update ( double time ) {
// SG_LOG(SG_ATC, SG_BULK, "ATC update code is running at time: " << time);
// Test code: let my virtual co-pilot handle ATC
FGAIManager* aiManager = globals->get_subsystem<FGAIManager>();
FGAIAircraft* user_ai_ac = aiManager->getUserAircraft();
FGAIFlightPlan *fp = user_ai_ac->GetFlightPlan();
// Update destination
string result = _routeManagerDestinationAirportNode->getStringValue();
if (destination != result && result != "") {
destination = result;
userAircraftScheduledFlight->setArrivalAirport(destination);
userAircraftScheduledFlight->initializeAirports();
userAircraftTrafficRef->clearAllFlights();
userAircraftTrafficRef->assign(userAircraftScheduledFlight.get());
auto userAircraft = aiManager->getUserAircraft();
userAircraft->setTrafficRef(userAircraftTrafficRef.get());
}
/* test code : find out how the routing develops */
if (fp) {
int size = fp->getNrOfWayPoints();
//SG_LOG(SG_ATC, SG_DEBUG, "Setting pos" << pos << " ");
//SG_LOG(SG_ATC, SG_DEBUG, "Setting intentions");
// This indicates that we have run out of waypoints: Im future versions, the
// user should be able to select a new route, but for now just shut down the
// system.
if (size < 3) {
return;
}
#if 0
// Test code: Print how far we're progressing along the taxi route.
SG_LOG(SG_ATC, SG_DEBUG, "Size of waypoint queue " << size);
for (int i = 0; i < size; i++) {
int val = fp->getRouteIndex(i);
SG_LOG(SG_ATC, SG_BULK, fp->getWayPoint(i)->getName() << " ");
//if ((val) && (val != pos)) {
// intentions.push_back(val);
SG_LOG(SG_ATC, SG_BULK, "[done ]");
//}
}
SG_LOG(SG_ATC, SG_BULK, "[done ]");
#endif
}
if (fp) {
SG_LOG(SG_ATC, SG_DEBUG, "User aircraft currently at leg : " << fp->getLeg());
}
// Call getATCController method; returns what FGATCController presently controls the user aircraft
// - e.g. FGStartupController
controller = user_ai_ac->getATCController();
// Update the ATC dialog
//FGATCDialogNew::instance()->update(time);
// Controller manager - if controller is set, then will update controller
if (controller) {
SG_LOG(SG_ATC, SG_DEBUG, "name of previous waypoint : " << fp->getPreviousWaypoint()->getName());
SG_LOG(SG_ATC, SG_DEBUG, "Currently under control of " << controller->getName());
// update aircraft information (simulates transponder)
controller->updateAircraftInformation(user_ai_ac->getID(),
user_ai_ac->getGeodPos(),
user_ai_ac->_getHeading(),
user_ai_ac->getSpeed(),
user_ai_ac->getAltitude(), time);
//string airport = fgGetString("/sim/presets/airport-id");
//FGAirport *apt = FGAirport::findByIdent(airport);
// AT this stage we should update the flightplan, so that waypoint incrementing is conducted as well as leg loading.
// Ground network visibility:
// a) check to see if the message to toggle visibility was called
// b) if so, toggle network visibility and reset the transmission
// c) therafter disable rendering for the old controller (TODO: should this be earlier?)
// d) and render if enabled for the new controller
int n = trans_num->getIntValue();
if (n == 1) {
SG_LOG(SG_ATC, SG_DEBUG, "Toggling ground network visibility " << networkVisible);
networkVisible = !networkVisible;
trans_num->setIntValue(-1);
}
// stop rendering the old controller's groundnetwork
if ((controller != prevController) && (prevController)) {
prevController->render(false);
}
// render the path for the present controller if the ground network is set to visible
controller->render(networkVisible);
SG_LOG(SG_ATC, SG_BULK, "Adding ground network to the scenegraph::update");
// reset previous controller for next update() iteration
prevController = controller;
}
// update the active ATC stations
for (AtcVecIterator atc = activeStations.begin(); atc != activeStations.end(); ++atc) {
(*atc)->update(time);
}
}
// Register the subsystem.
SGSubsystemMgr::Registrant<FGATCManager> registrantFGATCManager(
SGSubsystemMgr::POST_FDM,
{{"FGAIManager", SGSubsystemMgr::Dependency::HARD}});

83
src/ATC/atc_mgr.hxx Normal file
View File

@@ -0,0 +1,83 @@
/* -*- Mode: C++ -*- *****************************************************
* atic.hxx
* Written by Durk Talsma. Started August 1, 2010; based on earlier work
* by David C. Luff
*
* Updated by Jonathan Redpath. Started June 12, 2019. Documenting and extending
* functionality of the ATC subsystem
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
*
**************************************************************************/
/**************************************************************************
* The ATC Manager interfaces the users aircraft within the AI traffic system
* and also monitors the ongoing AI traffic patterns for potential conflicts
* and interferes where necessary.
*************************************************************************/
#ifndef _ATC_MGR_HXX_
#define _ATC_MGR_HXX_
#include <simgear/structure/subsystem_mgr.hxx>
#include <simgear/structure/SGReferenced.hxx>
#include <ATC/trafficcontrol.hxx>
#include <AIModel/AIAircraft.hxx>
#include <Traffic/Schedule.hxx>
#include <Traffic/SchedFlight.hxx>
typedef std::vector<FGATCController*> AtcVec;
typedef std::vector<FGATCController*>::iterator AtcVecIterator;
class FGATCManager : public SGSubsystem
{
private:
AtcVec activeStations;
FGATCController *controller, *prevController; // The ATC controller that is responsible for the user's aircraft.
bool networkVisible;
bool initSucceeded;
SGPropertyNode_ptr trans_num;
string destination;
std::unique_ptr<FGAISchedule> userAircraftTrafficRef;
std::unique_ptr<FGScheduledFlight> userAircraftScheduledFlight;
SGPropertyNode_ptr _routeManagerDestinationAirportNode;
public:
FGATCManager();
virtual ~FGATCManager();
// Subsystem API.
void postinit() override;
void shutdown() override;
void update(double time) override;
// Subsystem identification.
static const char* staticSubsystemClassId() { return "ATC"; }
void addController(FGATCController *controller);
void removeController(FGATCController* controller);
void reposition();
};
#endif // _ATC_MRG_HXX_

579
src/ATC/trafficcontrol.cxx Normal file
View File

@@ -0,0 +1,579 @@
// trafficrecord.cxx - Implementation of AIModels ATC code.
//
// Written by Durk Talsma, started September 2006.
//
// Copyright (C) 2006 Durk Talsma.
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation; either version 2 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
//
// $Id$
#include <config.h>
#include <algorithm>
#include <cstdio>
#include <random>
#include <osg/Geode>
#include <osg/Geometry>
#include <osg/MatrixTransform>
#include <osg/Shape>
#include <simgear/scene/material/EffectGeode.hxx>
#include <simgear/scene/material/matlib.hxx>
#include <simgear/scene/material/mat.hxx>
#include <simgear/scene/util/OsgMath.hxx>
#include <simgear/timing/sg_time.hxx>
#include <simgear/math/sg_geodesy.hxx>
#include <Scenery/scenery.hxx>
#include "trafficcontrol.hxx"
#include "atc_mgr.hxx"
#include <AIModel/AIAircraft.hxx>
#include <AIModel/AIFlightPlan.hxx>
#include <AIModel/performancedata.hxx>
#include <ATC/atc_mgr.hxx>
#include <Traffic/TrafficMgr.hxx>
#include <Airports/groundnetwork.hxx>
#include <Airports/dynamics.hxx>
#include <Airports/airport.hxx>
#include <Radio/radio.hxx>
#include <signal.h>
using std::sort;
using std::string;
/***************************************************************************
* ActiveRunway
**************************************************************************/
ActiveRunway::ActiveRunway(const std::string& r, int cc) :
rwy(r)
{
currentlyCleared = cc;
distanceToFinal = 6.0 * SG_NM_TO_METER;
};
void ActiveRunway::updateDepartureQueue()
{
departureQueue.erase(departureQueue.begin());
}
/*
* Fetch next slot for the active runway
* @param eta time of slot requested
* @return newEta: next slot available; starts at eta paramater
* and adds separation as needed
*/
time_t ActiveRunway::requestTimeSlot(time_t eta)
{
time_t newEta = 0;
// default separation - 60 seconds
time_t separation = 60;
//if (wakeCategory == "heavy_jet") {
// SG_LOG(SG_ATC, SG_DEBUG, "Heavy jet, using extra separation");
// time_t separation = 120;
//}
bool found = false;
// if the aircraft is the first arrival, add to the vector and return eta directly
if (estimatedArrivalTimes.empty()) {
estimatedArrivalTimes.push_back(eta);
SG_LOG(SG_ATC, SG_DEBUG, getRunwayName() << "Checked eta slots, using " << eta);
return eta;
} else {
// First check the already assigned slots to see where we need to fit the flight in
TimeVectorIterator i = estimatedArrivalTimes.begin();
SG_LOG(SG_ATC, SG_DEBUG, getRunwayName() << " Checking eta slots " << eta << " : " << estimatedArrivalTimes.size() << " Timediff " << (eta - globals->get_time_params()->get_cur_time()));
// is this needed - just a debug output?
for (i = estimatedArrivalTimes.begin();
i != estimatedArrivalTimes.end(); i++) {
SG_LOG(SG_ATC, SG_BULK, "Stored time : " << (*i));
}
// if the flight is before the first scheduled slot + separation
i = estimatedArrivalTimes.begin();
if ((eta + separation) < (*i)) {
newEta = eta;
SG_LOG(SG_ATC, SG_BULK, "Storing at beginning");
SG_LOG(SG_ATC, SG_DEBUG, "Done. New ETA : " << newEta);
slotHousekeeping(newEta);
return newEta;
}
// else, look through the rest of the slots
while ((i != estimatedArrivalTimes.end()) && (!found)) {
TimeVectorIterator j = i + 1;
// if the flight is after the last scheduled slot check if separation is needed
if (j == estimatedArrivalTimes.end()) {
if (((*i) + separation) < eta) {
SG_LOG(SG_ATC, SG_BULK, "Storing at end");
newEta = eta;
} else {
newEta = (*i) + separation;
SG_LOG(SG_ATC, SG_BULK, "Storing at end + separation");
}
SG_LOG(SG_ATC, SG_DEBUG, "Done. New ETA : " << newEta);
slotHousekeeping(newEta);
return newEta;
} else {
// potential slot found
// check the distance between the previous and next slots
// distance msut be greater than 2* separation
if ((((*j) - (*i)) > (separation * 2))) {
// now check whether this slot is usable:
// eta should fall between the two points
// i.e. eta > i AND eta < j
SG_LOG(SG_ATC, SG_DEBUG, "Found potential slot after " << (*i));
if (eta > (*i) && (eta < (*j))) {
found = true;
if (eta < ((*i) + separation)) {
newEta = (*i) + separation;
SG_LOG(SG_ATC, SG_BULK, "Using original" << (*i) << " + separation ");
} else {
newEta = eta;
SG_LOG(SG_ATC, SG_BULK, "Using original after " << (*i));
}
} else if (eta < (*i)) {
found = true;
newEta = (*i) + separation;
SG_LOG(SG_ATC, SG_BULK, "Using delayed slot after " << (*i));
}
/*
if (((*j) - separation) < eta) {
found = true;
if (((*i) + separation) < eta) {
newEta = eta;
SG_LOG(SG_ATC, SG_BULK, "Using original after " << (*i));
} else {
newEta = (*i) + separation;
SG_LOG(SG_ATC, SG_BULK, "Using " << (*i) << " + separation ");
}
} */
}
}
i++;
}
}
SG_LOG(SG_ATC, SG_DEBUG, "Done. New ETA : " << newEta);
slotHousekeeping(newEta);
return newEta;
}
void ActiveRunway::slotHousekeeping(time_t newEta)
{
// add the slot to the vector and resort the vector
estimatedArrivalTimes.push_back(newEta);
sort(estimatedArrivalTimes.begin(), estimatedArrivalTimes.end());
// do some housekeeping : remove any slots that are past
time_t now = globals->get_time_params()->get_cur_time();
TimeVectorIterator i = estimatedArrivalTimes.begin();
while (i != estimatedArrivalTimes.end()) {
if ((*i) < now) {
SG_LOG(SG_ATC, SG_BULK, "Deleting timestamp " << (*i) << " (now = " << now << "). ");
estimatedArrivalTimes.erase(i);
i = estimatedArrivalTimes.begin();
} else {
i++;
}
}
}
/* Output the contents of the departure queue vector nicely formatted*/
void ActiveRunway::printDepartureQueue()
{
SG_LOG(SG_ATC, SG_DEBUG, "Departure queue for " << rwy << ": ");
for (auto acft : departureQueue) {
SG_LOG(SG_ATC, SG_DEBUG, " " << acft->getCallSign() << " " << acft->getTakeOffStatus());
SG_LOG(SG_ATC, SG_DEBUG, " " << acft->_getLatitude() << " " << acft->_getLongitude() << acft->getSpeed() << " " << acft->getAltitude());
}
}
/* Fetch the first aircraft in the departure queue with a certain status */
SGSharedPtr<FGAIAircraft>ActiveRunway::getFirstOfStatus(int stat) const
{
auto it = std::find_if(departureQueue.begin(), departureQueue.end(), [stat](const SGSharedPtr<FGAIAircraft>& acft) {
return acft->getTakeOffStatus() == stat;
});
if (it == departureQueue.end()) {
return {};
}
return *it;
}
SGSharedPtr<FGAIAircraft> ActiveRunway::getFirstAircraftInDepartureQueue() const
{
if (departureQueue.empty()) {
return {};
}
return departureQueue.front();
};
void ActiveRunway::addToDepartureQueue(FGAIAircraft *ac)
{
assert(ac);
assert(!ac->getDie());
departureQueue.push_back(ac);
};
/***************************************************************************
* FGTrafficRecord
**************************************************************************/
FGTrafficRecord::FGTrafficRecord():
id(0), waitsForId(0),
currentPos(0),
leg(0),
frequencyId(0),
state(0),
allowTransmission(true),
allowPushback(true),
priority(0),
timer(0),
heading(0), speed(0), altitude(0), radius(0)
{
}
FGTrafficRecord::~FGTrafficRecord()
{
}
void FGTrafficRecord::setPositionAndIntentions(int pos,
FGAIFlightPlan * route)
{
SG_LOG(SG_ATC, SG_DEBUG, "Position: " << pos);
currentPos = pos;
if (!intentions.empty()) {
intVecIterator i = intentions.begin();
if ((*i) != currentPos) {
SG_LOG(SG_ATC, SG_ALERT,
"Error in FGTrafficRecord::setPositionAndIntentions at " << SG_ORIGIN << ", " << (*i));
}
intentions.erase(i);
} else {
//FGAIFlightPlan::waypoint* const wpt= route->getCurrentWaypoint();
int size = route->getNrOfWayPoints();
SG_LOG(SG_ATC, SG_DEBUG, "Setting pos" << currentPos);
SG_LOG(SG_ATC, SG_DEBUG, "Setting intentions");
for (int i = 2; i < size; i++) {
int val = route->getRouteIndex(i);
intentions.push_back(val);
}
}
}
void FGTrafficRecord::setAircraft(FGAIAircraft *ref)
{
aircraft = ref;
}
bool FGTrafficRecord::isDead() const {
if (!aircraft) {
return true;
}
return aircraft->getDie();
}
void FGTrafficRecord::clearATCController() const {
if (aircraft) {
aircraft->clearATCController();
}
}
FGAIAircraft* FGTrafficRecord::getAircraft() const
{
if(aircraft.valid()) {
return aircraft.ptr();
}
return 0;
}
/**
* Check if another aircraft is ahead of the current one, and on the same taxiway
* @return true / false if this is/isn't the case.
*/
bool FGTrafficRecord::checkPositionAndIntentions(FGTrafficRecord & other)
{
bool result = false;
SG_LOG(SG_ATC, SG_BULK, getCallsign() << "|checkPositionAndIntentions");
if (currentPos == other.currentPos && getId() != other.getId() ) {
SG_LOG(SG_ATC, SG_BULK, getCallsign() << "|Check Position and intentions: " << other.getCallsign() << " we are on the same taxiway; Index = " << currentPos);
result = true;
}
// else if (! other.intentions.empty())
// {
// SG_LOG(SG_ATC, SG_BULK, "Start check 2");
// intVecIterator i = other.intentions.begin();
// while (!((i == other.intentions.end()) || ((*i) == currentPos)))
// i++;
// if (i != other.intentions.end()) {
// SG_LOG(SG_ATC, SG_BULK, "Check Position and intentions: current matches other.intentions");
// result = true;
// }
else if (! intentions.empty()) {
SG_LOG(SG_ATC, SG_BULK, getCallsign() << "|Itentions " << intentions.size());
intVecIterator i = intentions.begin();
//while (!((i == intentions.end()) || ((*i) == other.currentPos)))
while (i != intentions.end()) {
if ((*i) == other.currentPos) {
break;
}
i++;
}
if (i != intentions.end()) {
SG_LOG(SG_ATC, SG_BULK, getCallsign() << "| Check Position and intentions: " << other.getCallsign()<< " matches Index = " << (*i));
result = true;
}
}
return result;
}
void FGTrafficRecord::setPositionAndHeading(double lat, double lon,
double hdg, double spd,
double alt)
{
this->pos = SGGeod::fromDegFt(lon, lat, alt);
heading = hdg;
speed = spd;
altitude = alt;
}
int FGTrafficRecord::crosses(FGGroundNetwork * net,
FGTrafficRecord & other)
{
if (checkPositionAndIntentions(other)
|| (other.checkPositionAndIntentions(*this)))
return -1;
intVecIterator i, j;
int currentTargetNode = 0, otherTargetNode = 0;
if (currentPos > 0)
currentTargetNode = net->findSegment(currentPos)->getEnd()->getIndex(); // OKAY,...
if (other.currentPos > 0)
otherTargetNode = net->findSegment(other.currentPos)->getEnd()->getIndex(); // OKAY,...
if ((currentTargetNode == otherTargetNode) && currentTargetNode > 0)
return currentTargetNode;
if (! intentions.empty()) {
for (i = intentions.begin(); i != intentions.end(); i++) {
if ((*i) > 0) {
if (currentTargetNode ==
net->findSegment(*i)->getEnd()->getIndex()) {
SG_LOG(SG_ATC, SG_BULK, "Current crosses at " << currentTargetNode);
return currentTargetNode;
}
}
}
}
if (! other.intentions.empty()) {
for (i = other.intentions.begin(); i != other.intentions.end();
i++) {
if ((*i) > 0) {
if (otherTargetNode ==
net->findSegment(*i)->getEnd()->getIndex()) {
SG_LOG(SG_ATC, SG_BULK, "Other crosses at " << currentTargetNode);
return otherTargetNode;
}
}
}
}
if (! intentions.empty() && ! other.intentions.empty()) {
for (i = intentions.begin(); i != intentions.end(); i++) {
for (j = other.intentions.begin(); j != other.intentions.end();
j++) {
SG_LOG(SG_ATC, SG_BULK, "finding segment " << *i << " and " << *j);
if (((*i) > 0) && ((*j) > 0)) {
currentTargetNode =
net->findSegment(*i)->getEnd()->getIndex();
otherTargetNode =
net->findSegment(*j)->getEnd()->getIndex();
if (currentTargetNode == otherTargetNode) {
SG_LOG(SG_ATC, SG_BULK, "Routes will cross at " << currentTargetNode);
return currentTargetNode;
}
}
}
}
}
return -1;
}
bool FGTrafficRecord::onRoute(FGGroundNetwork * net,
FGTrafficRecord & other)
{
int node = -1, othernode = -1;
if (currentPos > 0)
node = net->findSegment(currentPos)->getEnd()->getIndex();
if (other.currentPos > 0)
othernode =
net->findSegment(other.currentPos)->getEnd()->getIndex();
if ((node == othernode) && (node != -1))
return true;
if (! other.intentions.empty()) {
for (intVecIterator i = other.intentions.begin();
i != other.intentions.end(); i++) {
if (*i > 0) {
othernode = net->findSegment(*i)->getEnd()->getIndex();
if ((node == othernode) && (node > -1))
return true;
}
}
}
//if (other.currentPos > 0)
// othernode = net->findSegment(other.currentPos)->getEnd()->getIndex();
//if (! intentions.empty())
// {
// for (intVecIterator i = intentions.begin(); i != intentions.end(); i++)
// {
// if (*i > 0)
// {
// node = net->findSegment(*i)->getEnd()->getIndex();
// if ((node == othernode) && (node > -1))
// return true;
// }
// }
// }
return false;
}
bool FGTrafficRecord::isOpposing(FGGroundNetwork * net,
FGTrafficRecord & other, int node)
{
// Check if current segment is the reverse segment for the other aircraft
FGTaxiSegment *opp;
SG_LOG(SG_ATC, SG_BULK, "Current segment " << currentPos);
if ((currentPos > 0) && (other.currentPos > 0)) {
opp = net->findSegment(currentPos)->opposite();
if (opp) {
if (opp->getIndex() == other.currentPos)
return true;
}
for (intVecIterator i = intentions.begin(); i != intentions.end();
i++) {
if ((opp = net->findSegment(other.currentPos)->opposite())) {
if ((*i) > 0)
if (opp->getIndex() ==
net->findSegment(*i)->getIndex()) {
if (net->findSegment(*i)->getStart()->getIndex() ==
node) {
{
SG_LOG(SG_ATC, SG_BULK, "Found the node " << node);
return true;
}
}
}
}
if (! other.intentions.empty()) {
for (intVecIterator j = other.intentions.begin();
j != other.intentions.end(); j++) {
SG_LOG(SG_ATC, SG_BULK, "Current segment 1 " << (*i));
if ((*i) > 0) {
if ((opp = net->findSegment(*i)->opposite())) {
if (opp->getIndex() ==
net->findSegment(*j)->getIndex()) {
SG_LOG(SG_ATC, SG_BULK, "Nodes " << net->findSegment(*i)->getIndex()
<< " and " << net->findSegment(*j)->getIndex()
<< " are opposites ");
if (net->findSegment(*i)->getStart()->
getIndex() == node) {
{
SG_LOG(SG_ATC, SG_BULK, "Found the node " << node);
return true;
}
}
}
}
}
}
}
}
}
return false;
}
bool FGTrafficRecord::isActive(int margin) const
{
if (aircraft->getDie()) {
return false;
}
time_t now = globals->get_time_params()->get_cur_time();
time_t deptime = aircraft->getTrafficRef()->getDepartureTime();
return ((now + margin) > deptime);
}
void FGTrafficRecord::setSpeedAdjustment(double spd)
{
instruction.setChangeSpeed(true);
instruction.setSpeed(spd);
}
void FGTrafficRecord::setHeadingAdjustment(double heading)
{
instruction.setChangeHeading(true);
instruction.setHeading(heading);
}
bool FGTrafficRecord::pushBackAllowed() const
{
return allowPushback;
}
/***************************************************************************
* FGATCInstruction
*
**************************************************************************/
FGATCInstruction::FGATCInstruction()
{
holdPattern = false;
holdPosition = false;
changeSpeed = false;
changeHeading = false;
changeAltitude = false;
resolveCircularWait = false;
speed = 0;
heading = 0;
alt = 0;
}
bool FGATCInstruction::hasInstruction() const
{
return (holdPattern || holdPosition || changeSpeed || changeHeading
|| changeAltitude || resolveCircularWait);
}

377
src/ATC/trafficcontrol.hxx Normal file
View File

@@ -0,0 +1,377 @@
// trafficcontrol.hxx - classes to manage AIModels based air traffic control
// Written by Durk Talsma, started September 2006.
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation; either version 2 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
//
// $Id$
#ifndef _TRAFFIC_CONTROL_HXX_
#define _TRAFFIC_CONTROL_HXX_
#include <Airports/airports_fwd.hxx>
#include <osg/Geode>
#include <osg/Geometry>
#include <osg/MatrixTransform>
#include <osg/Shape>
#include <simgear/compiler.h>
// There is probably a better include than sg_geodesy to get the SG_NM_TO_METER...
#include <simgear/math/sg_geodesy.hxx>
#include <simgear/debug/logstream.hxx>
#include <simgear/structure/SGReferenced.hxx>
#include <simgear/structure/SGSharedPtr.hxx>
class FGAIAircraft;
typedef std::vector<FGAIAircraft*> AircraftVec;
typedef std::vector<FGAIAircraft*>::iterator AircraftVecIterator;
class FGAIFlightPlan;
typedef std::vector<FGAIFlightPlan*> FlightPlanVec;
typedef std::vector<FGAIFlightPlan*>::iterator FlightPlanVecIterator;
typedef std::map<std::string, FlightPlanVec> FlightPlanVecMap;
class FGTrafficRecord;
typedef std::list<FGTrafficRecord> TrafficVector;
typedef std::list<FGTrafficRecord>::iterator TrafficVectorIterator;
class ActiveRunway;
typedef std::vector<ActiveRunway> ActiveRunwayVec;
typedef std::vector<ActiveRunway>::iterator ActiveRunwayVecIterator;
typedef std::vector<int> intVec;
typedef std::vector<int>::iterator intVecIterator;
/**************************************************************************************
* class FGATCInstruction
* like class FGATC Controller, this class definition should go into its own file
* and or directory... For now, just testing this stuff out though...
*************************************************************************************/
class FGATCInstruction
{
private:
bool holdPattern;
bool holdPosition;
bool changeSpeed;
bool changeHeading;
bool changeAltitude;
bool resolveCircularWait;
double speed;
double heading;
double alt;
public:
FGATCInstruction();
bool hasInstruction () const;
bool getHoldPattern () const {
return holdPattern;
};
bool getHoldPosition () const {
return holdPosition;
};
bool getChangeSpeed () const {
return changeSpeed;
};
bool getChangeHeading () const {
return changeHeading;
};
bool getChangeAltitude() const {
return changeAltitude;
};
bool getCheckForCircularWait() const {
return resolveCircularWait;
};
double getSpeed () const {
return speed;
};
double getHeading () const {
return heading;
};
double getAlt () const {
return alt;
};
void setHoldPattern (bool val) {
holdPattern = val;
};
void setHoldPosition (bool val) {
holdPosition = val;
};
void setChangeSpeed (bool val) {
changeSpeed = val;
};
void setChangeHeading (bool val) {
changeHeading = val;
};
void setChangeAltitude(bool val) {
changeAltitude = val;
};
void setResolveCircularWait (bool val) {
resolveCircularWait = val;
};
void setSpeed (double val) {
speed = val;
};
void setHeading (double val) {
heading = val;
};
void setAlt (double val) {
alt = val;
};
};
/**************************************************************************************
* class FGTrafficRecord
* Represents the interaction of an AI Aircraft and ATC
*************************************************************************************/
class FGTrafficRecord
{
private:
int id;
int waitsForId;
int currentPos;
int leg;
int frequencyId;
int state;
bool allowTransmission;
bool allowPushback;
int priority;
time_t timer;
intVec intentions;
FGATCInstruction instruction;
SGGeod pos;
double heading, speed, altitude, radius;
std::string callsign;
std::string runway;
SGSharedPtr<FGAIAircraft> aircraft;
public:
FGTrafficRecord();
virtual ~FGTrafficRecord();
void setId(int val) {
id = val;
};
void setRadius(double rad) {
radius = rad;
};
void setPositionAndIntentions(int pos, FGAIFlightPlan *route);
void setRunway(const std::string& rwy) {
runway = rwy;
};
void setLeg(int lg) {
leg = lg;
};
int getId() const {
return id;
};
int getState() const {
return state;
};
void setState(int s) {
state = s;
}
FGATCInstruction getInstruction() const {
return instruction;
};
bool hasInstruction() const {
return instruction.hasInstruction();
};
void setPositionAndHeading(double lat, double lon, double hdg, double spd, double alt);
bool checkPositionAndIntentions(FGTrafficRecord &other);
int crosses (FGGroundNetwork *, FGTrafficRecord &other);
bool isOpposing (FGGroundNetwork *, FGTrafficRecord &other, int node);
bool isActive(int margin) const;
bool isDead() const;
void clearATCController() const;
bool onRoute(FGGroundNetwork *, FGTrafficRecord &other);
bool getSpeedAdjustment() const {
return instruction.getChangeSpeed();
};
SGGeod getPos() {
return pos;
}
double getHeading () const {
return heading ;
};
double getSpeed () const {
return speed ;
};
double getFAltitude () const {
return altitude ;
};
double getRadius () const {
return radius ;
};
int getWaitsForId () const {
return waitsForId;
};
void setSpeedAdjustment(double spd);
void setHeadingAdjustment(double heading);
void clearSpeedAdjustment () {
instruction.setChangeSpeed (false);
};
void clearHeadingAdjustment() {
instruction.setChangeHeading(false);
};
bool hasHeadingAdjustment() const {
return instruction.getChangeHeading();
};
bool hasHoldPosition() const {
return instruction.getHoldPosition();
};
void setHoldPosition (bool inst) {
instruction.setHoldPosition(inst);
};
void setWaitsForId(int id) {
waitsForId = id;
};
void setResolveCircularWait() {
instruction.setResolveCircularWait(true);
};
void clearResolveCircularWait() {
instruction.setResolveCircularWait(false);
};
void setCallsign(std::string clsgn) { callsign = clsgn; };
const std::string& getCallsign() const {
return callsign;
};
const std::string& getRunway() const {
return runway;
};
void setAircraft(FGAIAircraft *ref);
void updateState() {
state++;
allowTransmission=true;
};
//string getCallSign() { return callsign; };
FGAIAircraft *getAircraft() const;
int getTime() const {
return timer;
};
int getLeg() const {
return leg;
};
void setTime(time_t time) {
timer = time;
};
bool pushBackAllowed() const;
bool allowTransmissions() const {
return allowTransmission;
};
void allowPushBack() { allowPushback =true;};
void denyPushBack () { allowPushback = false;};
void suppressRepeatedTransmissions () {
allowTransmission=false;
};
void allowRepeatedTransmissions () {
allowTransmission=true;
};
void nextFrequency() {
frequencyId++;
};
int getNextFrequency() const {
return frequencyId;
};
intVec& getIntentions() {
return intentions;
};
int getCurrentPosition() const {
return currentPos;
};
void setPriority(int p) { priority = p; };
int getPriority() const { return priority; };
};
/***********************************************************************
* Active runway, a utility class to keep track of which aircraft has
* clearance for a given runway.
**********************************************************************/
class ActiveRunway
{
private:
const std::string rwy;
int currentlyCleared;
double distanceToFinal;
TimeVector estimatedArrivalTimes;
using AircraftRefVec = std::vector<SGSharedPtr<FGAIAircraft>>;
AircraftRefVec departureQueue;
public:
ActiveRunway(const std::string& r, int cc);
const std::string& getRunwayName() const
{
return rwy;
};
int getCleared() const
{
return currentlyCleared;
};
double getApproachDistance() const
{
return distanceToFinal;
};
//time_t getEstApproachTime() { return estimatedArrival; };
//void setEstApproachTime(time_t time) { estimatedArrival = time; };
void addToDepartureQueue(FGAIAircraft *ac);
void setCleared(int number) {
currentlyCleared = number;
};
time_t requestTimeSlot(time_t eta);
//time_t requestTimeSlot(time_t eta, std::string wakeCategory);
void slotHousekeeping(time_t newEta);
int getdepartureQueueSize() {
return departureQueue.size();
};
SGSharedPtr<FGAIAircraft> getFirstAircraftInDepartureQueue() const;
SGSharedPtr<FGAIAircraft> getFirstOfStatus(int stat) const;
void updateDepartureQueue();
void printDepartureQueue();
};
#endif // _TRAFFIC_CONTROL_HXX

516
src/Add-ons/Addon.cxx Normal file
View File

@@ -0,0 +1,516 @@
// -*- coding: utf-8 -*-
//
// Addon.cxx --- FlightGear class holding add-on metadata
// Copyright (C) 2017, 2018 Florent Rougon
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License along
// with this program; if not, write to the Free Software Foundation, Inc.,
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
#include <map>
#include <ostream>
#include <sstream>
#include <string>
#include <utility>
#include <vector>
#include <simgear/misc/sg_dir.hxx>
#include <simgear/misc/sg_path.hxx>
#include <simgear/misc/strutils.hxx>
#include <simgear/nasal/cppbind/Ghost.hxx>
#include <simgear/nasal/cppbind/NasalHash.hxx>
#include <simgear/nasal/naref.h>
#include <simgear/props/props.hxx>
#include <simgear/props/props_io.hxx>
#include <Main/fg_props.hxx>
#include <Main/globals.hxx>
#include <Scripting/NasalSys.hxx>
#include "addon_fwd.hxx"
#include "Addon.hxx"
#include "AddonMetadataParser.hxx"
#include "AddonVersion.hxx"
#include "exceptions.hxx"
#include "pointer_traits.hxx"
namespace strutils = simgear::strutils;
using std::string;
using std::vector;
namespace flightgear
{
namespace addons
{
// ***************************************************************************
// * QualifiedUrl *
// ***************************************************************************
QualifiedUrl::QualifiedUrl(UrlType type, string url, string detail)
: _type(type),
_url(std::move(url)),
_detail(std::move(detail))
{ }
UrlType QualifiedUrl::getType() const
{ return _type; }
void QualifiedUrl::setType(UrlType type)
{ _type = type; }
std::string QualifiedUrl::getUrl() const
{ return _url; }
void QualifiedUrl::setUrl(const std::string& url)
{ _url = url; }
std::string QualifiedUrl::getDetail() const
{ return _detail; }
void QualifiedUrl::setDetail(const std::string& detail)
{ _detail = detail; }
// ***************************************************************************
// * Addon *
// ***************************************************************************
Addon::Addon(std::string id, AddonVersion version, SGPath basePath,
std::string minFGVersionRequired, std::string maxFGVersionRequired,
SGPropertyNode* addonNode)
: _id(std::move(id)),
_version(
shared_ptr_traits<AddonVersionRef>::makeStrongRef(std::move(version))),
_basePath(std::move(basePath)),
_storagePath(globals->get_fg_home() / ("Export/Addons/" + _id)),
_minFGVersionRequired(std::move(minFGVersionRequired)),
_maxFGVersionRequired(std::move(maxFGVersionRequired)),
_addonNode(addonNode)
{
if (_minFGVersionRequired.empty()) {
// This add-on metadata class appeared in FlightGear 2017.4.0
_minFGVersionRequired = "2017.4.0";
}
if (_maxFGVersionRequired.empty()) {
_maxFGVersionRequired = "none"; // special value
}
}
std::string Addon::getId() const
{ return _id; }
std::string Addon::getName() const
{ return _name; }
void Addon::setName(const std::string& addonName)
{ _name = addonName; }
AddonVersionRef Addon::getVersion() const
{ return _version; }
void Addon::setVersion(const AddonVersion& addonVersion)
{
using ptr_traits = shared_ptr_traits<AddonVersionRef>;
_version.reset(ptr_traits::makeStrongRef(addonVersion));
}
std::vector<AuthorRef> Addon::getAuthors() const
{ return _authors; }
void Addon::setAuthors(const std::vector<AuthorRef>& addonAuthors)
{ _authors = addonAuthors; }
std::vector<MaintainerRef> Addon::getMaintainers() const
{ return _maintainers; }
void Addon::setMaintainers(const std::vector<MaintainerRef>& addonMaintainers)
{ _maintainers = addonMaintainers; }
std::string Addon::getShortDescription() const
{ return _shortDescription; }
void Addon::setShortDescription(const std::string& addonShortDescription)
{ _shortDescription = addonShortDescription; }
std::string Addon::getLongDescription() const
{ return _longDescription; }
void Addon::setLongDescription(const std::string& addonLongDescription)
{ _longDescription = addonLongDescription; }
std::string Addon::getLicenseDesignation() const
{ return _licenseDesignation; }
void Addon::setLicenseDesignation(const std::string& addonLicenseDesignation)
{ _licenseDesignation = addonLicenseDesignation; }
SGPath Addon::getLicenseFile() const
{ return _licenseFile; }
void Addon::setLicenseFile(const SGPath& addonLicenseFile)
{ _licenseFile = addonLicenseFile; }
std::string Addon::getLicenseUrl() const
{ return _licenseUrl; }
void Addon::setLicenseUrl(const std::string& addonLicenseUrl)
{ _licenseUrl = addonLicenseUrl; }
std::vector<std::string> Addon::getTags() const
{ return _tags; }
void Addon::setTags(const std::vector<std::string>& addonTags)
{ _tags = addonTags; }
SGPath Addon::getBasePath() const
{ return _basePath; }
void Addon::setBasePath(const SGPath& addonBasePath)
{ _basePath = addonBasePath; }
SGPath Addon::getStoragePath() const
{ return _storagePath; }
SGPath Addon::createStorageDir() const
{
if (_storagePath.exists()) {
if (!_storagePath.isDir()) {
string msg =
"Unable to create add-on storage directory because the entry already "
"exists, but is not a directory: '" + _storagePath.utf8Str() + "'";
// Log + throw, because if called from Nasal, only throwing would cause
// the exception message to 1) be truncated and 2) only appear in the
// log, not stopping the sim. Then users would have to figure out why
// their add-on doesn't work...
SG_LOG(SG_GENERAL, SG_POPUP, msg);
throw errors::unable_to_create_addon_storage_dir(msg);
}
} else {
const SGPath authorizedPath = SGPath(_storagePath).validate(/* write */
true);
if (authorizedPath.isNull()) {
string msg =
"Unable to create add-on storage directory because of the FlightGear "
"security policy (refused by SGPath::validate()): '" +
_storagePath.utf8Str() + "'";
SG_LOG(SG_GENERAL, SG_POPUP, msg);
throw errors::unable_to_create_addon_storage_dir(msg);
} else {
simgear::Dir(authorizedPath).create(0777);
}
}
// The sensitive operation (creating the directory) is behind us; return
// _storagePath instead of authorizedPath for consistency with the
// getStoragePath() method (_storagePath and authorizedPath could be
// different in case the former contains symlink components). Further
// sensitive operations beneath _storagePath must use SGPath::validate()
// again every time, of course (otherwise, attackers could use symlinks in
// _storagePath to bypass the security policy).
return _storagePath;
}
std::string Addon::resourcePath(const std::string& relativePath) const
{
if (strutils::starts_with(relativePath, "/")) {
throw errors::invalid_resource_path(
"addon-specific resource path '" + relativePath + "' shouldn't start "
"with a '/'");
}
return "[addon=" + getId() + "]" + relativePath;
}
std::string Addon::getMinFGVersionRequired() const
{ return _minFGVersionRequired; }
void Addon::setMinFGVersionRequired(const string& minFGVersionRequired)
{ _minFGVersionRequired = minFGVersionRequired; }
std::string Addon::getMaxFGVersionRequired() const
{ return _maxFGVersionRequired; }
void Addon::setMaxFGVersionRequired(const string& maxFGVersionRequired)
{
if (maxFGVersionRequired.empty()) {
_maxFGVersionRequired = "none"; // special value
} else {
_maxFGVersionRequired = maxFGVersionRequired;
}
}
std::string Addon::getHomePage() const
{ return _homePage; }
void Addon::setHomePage(const std::string& addonHomePage)
{ _homePage = addonHomePage; }
std::string Addon::getDownloadUrl() const
{ return _downloadUrl; }
void Addon::setDownloadUrl(const std::string& addonDownloadUrl)
{ _downloadUrl = addonDownloadUrl; }
std::string Addon::getSupportUrl() const
{ return _supportUrl; }
void Addon::setSupportUrl(const std::string& addonSupportUrl)
{ _supportUrl = addonSupportUrl; }
std::string Addon::getCodeRepositoryUrl() const
{ return _codeRepositoryUrl; }
void Addon::setCodeRepositoryUrl(const std::string& addonCodeRepositoryUrl)
{ _codeRepositoryUrl = addonCodeRepositoryUrl; }
std::string Addon::getTriggerProperty() const
{ return _triggerProperty; }
void Addon::setTriggerProperty(const std::string& addonTriggerProperty)
{ _triggerProperty = addonTriggerProperty; }
SGPropertyNode_ptr Addon::getAddonNode() const
{ return _addonNode; }
void Addon::setAddonNode(SGPropertyNode* addonNode)
{ _addonNode = SGPropertyNode_ptr(addonNode); }
naRef Addon::getAddonPropsNode() const
{
FGNasalSys* nas = globals->get_subsystem<FGNasalSys>();
return nas->wrappedPropsNode(_addonNode.get());
}
SGPropertyNode_ptr Addon::getLoadedFlagNode() const
{
return { _addonNode->getChild("loaded", 0, 1) };
}
int Addon::getLoadSequenceNumber() const
{ return _loadSequenceNumber; }
void Addon::setLoadSequenceNumber(int num)
{ _loadSequenceNumber = num; }
std::multimap<UrlType, QualifiedUrl> Addon::getUrls() const
{
std::multimap<UrlType, QualifiedUrl> res;
auto appendIfNonEmpty = [&res](UrlType type, string url, string detail = "") {
if (!url.empty()) {
res.emplace(type, QualifiedUrl(type, std::move(url), std::move(detail)));
}
};
for (const auto& author: _authors) {
appendIfNonEmpty(UrlType::author, author->getUrl(), author->getName());
}
for (const auto& maint: _maintainers) {
appendIfNonEmpty(UrlType::maintainer, maint->getUrl(), maint->getName());
}
appendIfNonEmpty(UrlType::homePage, getHomePage());
appendIfNonEmpty(UrlType::download, getDownloadUrl());
appendIfNonEmpty(UrlType::support, getSupportUrl());
appendIfNonEmpty(UrlType::codeRepository, getCodeRepositoryUrl());
appendIfNonEmpty(UrlType::license, getLicenseUrl());
return res;
}
std::vector<SGPropertyNode_ptr> Addon::getMenubarNodes() const
{ return _menubarNodes; }
void Addon::setMenubarNodes(const std::vector<SGPropertyNode_ptr>& menubarNodes)
{ _menubarNodes = menubarNodes; }
void Addon::addToFGMenubar() const
{
SGPropertyNode* menuRootNode = fgGetNode("/sim/menubar/default", true);
for (const auto& node: getMenubarNodes()) {
SGPropertyNode* childNode = menuRootNode->addChild("menu");
::copyProperties(node.ptr(), childNode);
}
}
std::string Addon::str() const
{
std::ostringstream oss;
oss << "addon '" << _id << "' (version = " << *_version
<< ", base path = '" << _basePath.utf8Str()
<< "', minFGVersionRequired = '" << _minFGVersionRequired
<< "', maxFGVersionRequired = '" << _maxFGVersionRequired << "')";
return oss.str();
}
// Static method
SGPath Addon::getMetadataFile(const SGPath& addonPath)
{
return MetadataParser::getMetadataFile(addonPath);
}
SGPath Addon::getMetadataFile() const
{
return getMetadataFile(getBasePath());
}
// Static method
Addon Addon::fromAddonDir(const SGPath& addonPath)
{
Addon::Metadata metadata = MetadataParser::parseMetadataFile(addonPath);
// Object holding all the add-on metadata
Addon addon{std::move(metadata.id), std::move(metadata.version), addonPath,
std::move(metadata.minFGVersionRequired),
std::move(metadata.maxFGVersionRequired)};
addon.setName(std::move(metadata.name));
addon.setAuthors(std::move(metadata.authors));
addon.setMaintainers(std::move(metadata.maintainers));
addon.setShortDescription(std::move(metadata.shortDescription));
addon.setLongDescription(std::move(metadata.longDescription));
addon.setLicenseDesignation(std::move(metadata.licenseDesignation));
addon.setLicenseFile(std::move(metadata.licenseFile));
addon.setLicenseUrl(std::move(metadata.licenseUrl));
addon.setTags(std::move(metadata.tags));
addon.setHomePage(std::move(metadata.homePage));
addon.setDownloadUrl(std::move(metadata.downloadUrl));
addon.setSupportUrl(std::move(metadata.supportUrl));
addon.setCodeRepositoryUrl(std::move(metadata.codeRepositoryUrl));
SGPath menuFile = addonPath / "addon-menubar-items.xml";
if (menuFile.exists()) {
addon.setMenubarNodes(readMenubarItems(menuFile));
}
return addon;
}
// Static method
std::vector<SGPropertyNode_ptr>
Addon::readMenubarItems(const SGPath& menuFile)
{
SGPropertyNode rootNode;
try {
readProperties(menuFile, &rootNode);
} catch (const sg_exception &e) {
throw errors::error_loading_menubar_items_file(
"unable to load add-on menu bar items from file '" +
menuFile.utf8Str() + "': " + e.getFormattedMessage());
}
// Check the 'meta' section
SGPropertyNode *metaNode = rootNode.getChild("meta");
if (metaNode == nullptr) {
throw errors::error_loading_menubar_items_file(
"no /meta node found in add-on menu bar items file '" +
menuFile.utf8Str() + "'");
}
// Check the file type
SGPropertyNode *fileTypeNode = metaNode->getChild("file-type");
if (fileTypeNode == nullptr) {
throw errors::error_loading_menubar_items_file(
"no /meta/file-type node found in add-on menu bar items file '" +
menuFile.utf8Str() + "'");
}
string fileType = fileTypeNode->getStringValue();
if (fileType != "FlightGear add-on menu bar items") {
throw errors::error_loading_menubar_items_file(
"Invalid /meta/file-type value for add-on menu bar items file '" +
menuFile.utf8Str() + "': '" + fileType + "' "
"(expected 'FlightGear add-on menu bar items')");
}
// Check the format version
SGPropertyNode *fmtVersionNode = metaNode->getChild("format-version");
if (fmtVersionNode == nullptr) {
throw errors::error_loading_menubar_items_file(
"no /meta/format-version node found in add-on menu bar items file '" +
menuFile.utf8Str() + "'");
}
int formatVersion = fmtVersionNode->getIntValue();
if (formatVersion != 1) {
throw errors::error_loading_menubar_items_file(
"unknown format version in add-on menu bar items file '" +
menuFile.utf8Str() + "': " + std::to_string(formatVersion));
}
SG_LOG(SG_GENERAL, SG_DEBUG,
"Loaded add-on menu bar items from '" << menuFile.utf8Str() + "'");
SGPropertyNode *menubarItemsNode = rootNode.getChild("menubar-items");
std::vector<SGPropertyNode_ptr> res;
if (menubarItemsNode != nullptr) {
res = menubarItemsNode->getChildren("menu");
}
return res;
}
void Addon::retranslate()
{
Addon::Metadata metadata = MetadataParser::parseMetadataFile(_basePath);
setName(std::move(metadata.name));
setShortDescription(std::move(metadata.shortDescription));
setLongDescription(std::move(metadata.longDescription));
}
// Static method
void Addon::setupGhost(nasal::Hash& addonsModule)
{
nasal::Ghost<AddonRef>::init("addons.Addon")
.member("id", &Addon::getId)
.member("name", &Addon::getName)
.member("version", &Addon::getVersion)
.member("authors", &Addon::getAuthors)
.member("maintainers", &Addon::getMaintainers)
.member("shortDescription", &Addon::getShortDescription)
.member("longDescription", &Addon::getLongDescription)
.member("licenseDesignation", &Addon::getLicenseDesignation)
.member("licenseFile", &Addon::getLicenseFile)
.member("licenseUrl", &Addon::getLicenseUrl)
.member("tags", &Addon::getTags)
.member("basePath", &Addon::getBasePath)
.member("storagePath", &Addon::getStoragePath)
.method("createStorageDir", &Addon::createStorageDir)
.method("resourcePath", &Addon::resourcePath)
.member("minFGVersionRequired", &Addon::getMinFGVersionRequired)
.member("maxFGVersionRequired", &Addon::getMaxFGVersionRequired)
.member("homePage", &Addon::getHomePage)
.member("downloadUrl", &Addon::getDownloadUrl)
.member("supportUrl", &Addon::getSupportUrl)
.member("codeRepositoryUrl", &Addon::getCodeRepositoryUrl)
.member("triggerProperty", &Addon::getTriggerProperty)
.member("node", &Addon::getAddonPropsNode)
.member("loadSequenceNumber", &Addon::getLoadSequenceNumber);
}
std::ostream& operator<<(std::ostream& os, const Addon& addon)
{
return os << addon.str();
}
} // of namespace addons
} // of namespace flightgear

267
src/Add-ons/Addon.hxx Normal file
View File

@@ -0,0 +1,267 @@
// -*- coding: utf-8 -*-
//
// Addon.hxx --- FlightGear class holding add-on metadata
// Copyright (C) 2017, 2018 Florent Rougon
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License along
// with this program; if not, write to the Free Software Foundation, Inc.,
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
#ifndef FG_ADDON_HXX
#define FG_ADDON_HXX
#include <map>
#include <ostream>
#include <string>
#include <vector>
#include <simgear/misc/sg_path.hxx>
#include <simgear/nasal/cppbind/NasalHash.hxx>
#include <simgear/nasal/naref.h>
#include <simgear/props/props.hxx>
#include <simgear/structure/SGReferenced.hxx>
#include "addon_fwd.hxx"
#include "contacts.hxx"
#include "AddonVersion.hxx"
#include "pointer_traits.hxx"
namespace flightgear
{
namespace addons
{
enum class UrlType {
author,
maintainer,
homePage,
download,
support,
codeRepository,
license
};
class QualifiedUrl
{
public:
QualifiedUrl(UrlType type, std::string url, std::string detail = "");
UrlType getType() const;
void setType(UrlType type);
std::string getUrl() const;
void setUrl(const std::string& url);
std::string getDetail() const;
void setDetail(const std::string& detail);
private:
UrlType _type;
std::string _url;
// Used to store the author or maintainer name when _type is UrlType::author
// or UrlType::maintainer. Could be used to record details about a website
// too (e.g., for a UrlType::support, something like “official forum”).
std::string _detail;
};
class Addon : public SGReferenced
{
public:
// An empty value for 'minFGVersionRequired' is translated into "2017.4.0".
// An empty value for 'maxFGVersionRequired' is translated into "none".
Addon(std::string id, AddonVersion version = AddonVersion(),
SGPath basePath = SGPath(), std::string minFGVersionRequired = "",
std::string maxFGVersionRequired = "",
SGPropertyNode* addonNode = nullptr);
// Parse the add-on metadata file inside 'addonPath' (as defined by
// getMetadataFile()) and return the corresponding Addon instance.
static Addon fromAddonDir(const SGPath& addonPath);
template<class T>
static T fromAddonDir(const SGPath& addonPath)
{
using ptr_traits = shared_ptr_traits<T>;
return ptr_traits::makeStrongRef(fromAddonDir(addonPath));
}
std::string getId() const;
std::string getName() const;
void setName(const std::string& addonName);
AddonVersionRef getVersion() const;
void setVersion(const AddonVersion& addonVersion);
std::vector<AuthorRef> getAuthors() const;
void setAuthors(const std::vector<AuthorRef>& addonAuthors);
std::vector<MaintainerRef> getMaintainers() const;
void setMaintainers(const std::vector<MaintainerRef>& addonMaintainers);
std::string getShortDescription() const;
void setShortDescription(const std::string& addonShortDescription);
std::string getLongDescription() const;
void setLongDescription(const std::string& addonLongDescription);
std::string getLicenseDesignation() const;
void setLicenseDesignation(const std::string& addonLicenseDesignation);
SGPath getLicenseFile() const;
void setLicenseFile(const SGPath& addonLicenseFile);
std::string getLicenseUrl() const;
void setLicenseUrl(const std::string& addonLicenseUrl);
std::vector<std::string> getTags() const;
void setTags(const std::vector<std::string>& addonTags);
SGPath getBasePath() const;
void setBasePath(const SGPath& addonBasePath);
// Return $FG_HOME/Export/Addons/ADDON_ID as an SGPath instance.
SGPath getStoragePath() const;
// Create directory $FG_HOME/Export/Addons/ADDON_ID, including any parent,
// if it doesn't already exist. Throw an exception in case of problems.
// Return an SGPath instance for the directory (same as getStoragePath()).
SGPath createStorageDir() const;
// Return a resource path suitable for use with the simgear::ResourceManager.
// 'relativePath' is relative to the add-on base path, and should not start
// with a '/'.
std::string resourcePath(const std::string& relativePath) const;
// Should be valid for use with simgear::strutils::compare_versions()
std::string getMinFGVersionRequired() const;
void setMinFGVersionRequired(const std::string& minFGVersionRequired);
// Should be valid for use with simgear::strutils::compare_versions(),
// except for the special value "none".
std::string getMaxFGVersionRequired() const;
void setMaxFGVersionRequired(const std::string& maxFGVersionRequired);
std::string getHomePage() const;
void setHomePage(const std::string& addonHomePage);
std::string getDownloadUrl() const;
void setDownloadUrl(const std::string& addonDownloadUrl);
std::string getSupportUrl() const;
void setSupportUrl(const std::string& addonSupportUrl);
std::string getCodeRepositoryUrl() const;
void setCodeRepositoryUrl(const std::string& addonCodeRepositoryUrl);
std::string getTriggerProperty() const;
void setTriggerProperty(const std::string& addonTriggerProperty);
// Node pertaining to the add-on in the Global Property Tree
SGPropertyNode_ptr getAddonNode() const;
void setAddonNode(SGPropertyNode* addonNode);
// For Nasal: result as a props.Node object
naRef getAddonPropsNode() const;
// Property node indicating whether the add-on is fully loaded
SGPropertyNode_ptr getLoadedFlagNode() const;
// 0 for the first loaded add-on, 1 for the second, etc.
// -1 means “not set” (as done by the default constructor)
int getLoadSequenceNumber() const;
void setLoadSequenceNumber(int num);
// Get all non-empty URLs pertaining to this add-on
std::multimap<UrlType, QualifiedUrl> getUrls() const;
// Getter and setter for the menu bar item nodes of the add-on
std::vector<SGPropertyNode_ptr> getMenubarNodes() const;
void setMenubarNodes(const std::vector<SGPropertyNode_ptr>& menubarNodes);
// Add the menus defined in addon-menubar-items.xml to /sim/menubar/default
void addToFGMenubar() const;
// Simple string representation
std::string str() const;
static void setupGhost(nasal::Hash& addonsModule);
/**
* @brief update string values (description, etc) based on the active locale
*/
void retranslate();
private:
class Metadata;
class MetadataParser;
// “Compute” a path to the metadata file from the add-on base path
static SGPath getMetadataFile(const SGPath& addonPath);
SGPath getMetadataFile() const;
// Read all menus from addon-menubar-items.xml (under the add-on base path)
static std::vector<SGPropertyNode_ptr>
readMenubarItems(const SGPath& menuFile);
// The add-on identifier, in reverse DNS style. The AddonManager refuses to
// register two add-ons with the same id in a given FlightGear session.
const std::string _id;
// Pretty name for the add-on (not constrained to reverse DNS style)
std::string _name;
// Use a smart pointer to expose the AddonVersion instance to Nasal without
// needing to copy the data every time.
AddonVersionRef _version;
std::vector<AuthorRef> _authors;
std::vector<MaintainerRef> _maintainers;
// Strings describing what the add-on does
std::string _shortDescription;
std::string _longDescription;
std::string _licenseDesignation;
SGPath _licenseFile;
std::string _licenseUrl;
std::vector<std::string> _tags;
SGPath _basePath;
// $FG_HOME/Export/Addons/ADDON_ID
const SGPath _storagePath;
// To be used with simgear::strutils::compare_versions()
std::string _minFGVersionRequired;
// Ditto, but there is a special value: "none"
std::string _maxFGVersionRequired;
std::string _homePage;
std::string _downloadUrl;
std::string _supportUrl;
std::string _codeRepositoryUrl;
// Main node for the add-on in the Property Tree
SGPropertyNode_ptr _addonNode;
// The add-on will be loaded when the property referenced by
// _triggerProperty is written to.
std::string _triggerProperty;
// Semantics explained above
int _loadSequenceNumber = -1;
std::vector<SGPropertyNode_ptr> _menubarNodes;
};
std::ostream& operator<<(std::ostream& os, const Addon& addon);
} // of namespace addons
} // of namespace flightgear
#endif // of FG_ADDON_HXX

View File

@@ -0,0 +1,296 @@
// -*- coding: utf-8 -*-
//
// AddonManager.cxx --- Manager class for FlightGear add-ons
// Copyright (C) 2017 Florent Rougon
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License along
// with this program; if not, write to the Free Software Foundation, Inc.,
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
#include "config.h"
#include <algorithm>
#include <memory>
#include <string>
#include <utility>
#include <vector>
#include <cstdlib>
#include <cassert>
#include <simgear/debug/logstream.hxx>
#include <simgear/misc/sg_path.hxx>
#include <simgear/misc/strutils.hxx>
#include <simgear/props/props.hxx>
#include <simgear/props/props_io.hxx>
#include <simgear/structure/exception.hxx>
#include <Main/fg_props.hxx>
#include <Main/globals.hxx>
#include "addon_fwd.hxx"
#include "Addon.hxx"
#include "AddonManager.hxx"
#include "AddonVersion.hxx"
#include "exceptions.hxx"
#include "pointer_traits.hxx"
namespace strutils = simgear::strutils;
using std::string;
using std::vector;
using std::shared_ptr;
using std::unique_ptr;
namespace flightgear
{
namespace addons
{
static unique_ptr<AddonManager> staticInstance;
// ***************************************************************************
// * AddonManager *
// ***************************************************************************
// Static method
const unique_ptr<AddonManager>&
AddonManager::createInstance()
{
SG_LOG(SG_GENERAL, SG_DEBUG, "Initializing the AddonManager");
staticInstance.reset(new AddonManager());
return staticInstance;
}
// Static method
const unique_ptr<AddonManager>&
AddonManager::instance()
{
return staticInstance;
}
// Static method
void
AddonManager::reset()
{
SG_LOG(SG_GENERAL, SG_DEBUG, "Resetting the AddonManager");
staticInstance.reset();
}
// Static method
void
AddonManager::loadConfigFileIfExists(const SGPath& configFile)
{
if (!configFile.exists()) {
return;
}
SGPropertyNode_ptr configProps(new SGPropertyNode);
try {
readProperties(configFile, configProps);
} catch (const sg_exception &e) {
throw errors::error_loading_config_file(
"unable to load add-on config file '" + configFile.utf8Str() + "': " +
e.getFormattedMessage());
}
// bug https://sourceforge.net/p/flightgear/codetickets/2059/
// since we're loading this after autosave.xml is loaded, the defaults
// always take precedence. To fix this, only copy a value from the
// addon-config if it's not makred as ARCHIVE. (We assume ARCHIVE props
// came from autosave.xml)
copyPropertiesIf(configProps, globals->get_props(), [](const SGPropertyNode* src) {
if (src->nChildren() > 0)
return true;
// find the correspnding destination node
auto dstNode = globals->get_props()->getNode(src->getPath());
if (!dstNode)
return true; // easy, just copy it
// copy if it's NOT marked archive. In other words, we can replace
// values from defaults, but not autosave
return dstNode->getAttribute(SGPropertyNode::USERARCHIVE) == false;
});
SG_LOG(SG_GENERAL, SG_INFO,
"Loaded add-on config file: '" << configFile.utf8Str() + "'");
}
string
AddonManager::registerAddonMetadata(const SGPath& addonPath)
{
using ptr_traits = shared_ptr_traits<AddonRef>;
AddonRef addon = ptr_traits::makeStrongRef(Addon::fromAddonDir(addonPath));
string addonId = addon->getId();
SGPropertyNode* addonPropNode = fgGetNode("addons", true)
->getChild("by-id", 0, 1)
->getChild(addonId, 0, 1);
addon->setAddonNode(addonPropNode);
addon->setLoadSequenceNumber(_loadSequenceNumber++);
// Check that the FlightGear version satisfies the add-on requirements
std::string minFGversion = addon->getMinFGVersionRequired();
if (strutils::compare_versions(FLIGHTGEAR_VERSION, minFGversion) < 0) {
throw errors::fg_version_too_old(
"add-on '" + addonId + "' requires FlightGear " + minFGversion +
" or later, however this is FlightGear " + FLIGHTGEAR_VERSION);
}
std::string maxFGversion = addon->getMaxFGVersionRequired();
if (maxFGversion != "none" &&
strutils::compare_versions(FLIGHTGEAR_VERSION, maxFGversion) > 0) {
throw errors::fg_version_too_recent(
"add-on '" + addonId + "' requires FlightGear " + maxFGversion +
" or earlier, however this is FlightGear " + FLIGHTGEAR_VERSION);
}
// Store the add-on metadata in _idToAddonMap
auto emplaceRetval = _idToAddonMap.emplace(addonId, std::move(addon));
// Prevent registration of two add-ons with the same id
if (!emplaceRetval.second) {
auto existingElt = _idToAddonMap.find(addonId);
assert(existingElt != _idToAddonMap.end());
throw errors::duplicate_registration_attempt(
"attempt to register add-on '" + addonId + "' with base path '"
+ addonPath.utf8Str() + "', however it is already registered with base "
"path '" + existingElt->second->getBasePath().utf8Str() + "'");
}
return addonId;
}
string
AddonManager::registerAddon(const SGPath& addonPath)
{
// Use realpath() as in FGGlobals::append_aircraft_path(), otherwise
// SGPath::validate() will deny access to resources under the add-on path
// if one of its components is a symlink.
const SGPath addonRealPath = addonPath.realpath();
const string addonId = registerAddonMetadata(addonRealPath);
loadConfigFileIfExists(addonRealPath / "addon-config.xml");
globals->append_aircraft_path(addonRealPath);
AddonRef addon{getAddon(addonId)};
addon->getLoadedFlagNode()->setBoolValue(false);
SGPropertyNode_ptr addonNode = addon->getAddonNode();
// Set a few properties for the add-on under this node
addonNode->getNode("id", true)->setStringValue(addonId);
addonNode->getNode("name", true)->setStringValue(addon->getName());
addonNode->getNode("version", true)
->setStringValue(addonVersion(addonId)->str());
addonNode->getNode("path", true)->setStringValue(addonRealPath.utf8Str());
addonNode->getNode("load-seq-num", true)
->setIntValue(addon->getLoadSequenceNumber());
// “Legacy node”. Should we remove these two lines?
SGPropertyNode* seqNumNode = fgGetNode("addons", true)->addChild("addon");
seqNumNode->getNode("path", true)->setStringValue(addonRealPath.utf8Str());
string msg = "Registered add-on '" + addon->getName() + "' (" + addonId +
") version " + addonVersion(addonId)->str() + "; "
"base path is '" + addonRealPath.utf8Str() + "'";
auto dataPath = addonRealPath / "FGData";
if (dataPath.exists()) {
SG_LOG(SG_GENERAL, SG_INFO, "Registering data path for add-on: " << addon->getName());
globals->append_data_path(dataPath, true /* after FG_ROOT */);
}
// This preserves the registration order
_registeredAddons.push_back(addon);
SG_LOG(SG_GENERAL, SG_INFO, msg);
return addonId;
}
bool
AddonManager::isAddonRegistered(const string& addonId) const
{
return (_idToAddonMap.find(addonId) != _idToAddonMap.end());
}
bool
AddonManager::isAddonLoaded(const string& addonId) const
{
return (isAddonRegistered(addonId) &&
getAddon(addonId)->getLoadedFlagNode()->getBoolValue());
}
vector<AddonRef>
AddonManager::registeredAddons() const
{
return _registeredAddons;
}
vector<AddonRef>
AddonManager::loadedAddons() const
{
vector<AddonRef> v;
v.reserve(_idToAddonMap.size()); // will be the right size most of the times
for (const auto& elem: _idToAddonMap) {
if (isAddonLoaded(elem.first)) {
v.push_back(elem.second);
}
}
return v;
}
AddonRef
AddonManager::getAddon(const string& addonId) const
{
const auto it = _idToAddonMap.find(addonId);
if (it == _idToAddonMap.end()) {
throw sg_exception("tried to get add-on '" + addonId + "', however no "
"such add-on has been registered.");
}
return it->second;
}
AddonVersionRef
AddonManager::addonVersion(const string& addonId) const
{
return getAddon(addonId)->getVersion();
}
SGPath
AddonManager::addonBasePath(const string& addonId) const
{
return getAddon(addonId)->getBasePath();
}
SGPropertyNode_ptr AddonManager::addonNode(const string& addonId) const
{
return getAddon(addonId)->getAddonNode();
}
void
AddonManager::addAddonMenusToFGMenubar() const
{
for (const auto& addon: _registeredAddons) {
addon->addToFGMenubar();
}
}
} // of namespace addons
} // of namespace flightgear

View File

@@ -0,0 +1,114 @@
// -*- coding: utf-8 -*-
//
// AddonManager.hxx --- Manager class for FlightGear add-ons
// Copyright (C) 2017 Florent Rougon
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License along
// with this program; if not, write to the Free Software Foundation, Inc.,
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
#ifndef FG_ADDONMANAGER_HXX
#define FG_ADDONMANAGER_HXX
#include <string>
#include <map>
#include <memory> // std::unique_ptr, std::shared_ptr
#include <vector>
#include <simgear/misc/sg_path.hxx>
#include <simgear/props/props.hxx>
#include "addon_fwd.hxx"
#include "Addon.hxx"
#include "AddonVersion.hxx"
namespace flightgear
{
namespace addons
{
class AddonManager
{
public:
AddonManager(const AddonManager&) = delete;
AddonManager& operator=(const AddonManager&) = delete;
AddonManager(AddonManager&&) = delete;
AddonManager& operator=(AddonManager&&) = delete;
// The instance is created by createInstance() -> private constructor
// but it should be deleted by its owning std::unique_ptr -> public destructor
~AddonManager() = default;
// Static creator
static const std::unique_ptr<AddonManager>& createInstance();
// Singleton accessor
static const std::unique_ptr<AddonManager>& instance();
// Reset the static smart pointer, i.e., shut down the AddonManager.
static void reset();
// Register an add-on and return its id.
// 'addonPath': directory containing the add-on to register
//
// This comprises the following steps, where $path = addonPath.realpath():
// - load add-on metadata from $path/addon-metadata.xml and register it
// inside _idToAddonMap (this step is done via registerAddonMetadata());
// - load $path/addon-config.xml into the Property Tree;
// - append $path to the list of aircraft paths;
// - make part of the add-on metadata available in the Property Tree under
// the /addons node (/addons/by-id/<addonId>/{id,version,path,...});
// - append a ref to the Addon instance to _registeredAddons.
std::string registerAddon(const SGPath& addonPath);
// Return the list of registered add-ons in registration order (which, BTW,
// is the same as load order).
std::vector<AddonRef> registeredAddons() const;
bool isAddonRegistered(const std::string& addonId) const;
// A loaded add-on is one whose addon-main.nas file has been loaded. The
// returned vector is sorted by add-on id (cheap sorting based on UTF-8 code
// units, only guaranteed correct for ASCII chars).
std::vector<AddonRef> loadedAddons() const;
bool isAddonLoaded(const std::string& addonId) const;
AddonRef getAddon(const std::string& addonId) const;
AddonVersionRef addonVersion(const std::string& addonId) const;
SGPath addonBasePath(const std::string& addonId) const;
// Base node pertaining to the add-on in the Global Property Tree
SGPropertyNode_ptr addonNode(const std::string& addonId) const;
// Add the 'menu' nodes defined by each registered add-on to
// /sim/menubar/default
void addAddonMenusToFGMenubar() const;
private:
// Constructor called from createInstance() only
explicit AddonManager() = default;
static void loadConfigFileIfExists(const SGPath& configFile);
// Register add-on metadata inside _idToAddonMap and return the add-on id
std::string registerAddonMetadata(const SGPath& addonPath);
// Map each add-on id to the corresponding Addon instance.
std::map<std::string, AddonRef> _idToAddonMap;
// The order in _registeredAddons is the registration order.
std::vector<AddonRef> _registeredAddons;
// 0 for the first loaded add-on, 1 for the second, etc.
// Also note that add-ons are loaded in their registration order.
int _loadSequenceNumber = 0;
};
} // of namespace addons
} // of namespace flightgear
#endif // of FG_ADDONMANAGER_HXX

View File

@@ -0,0 +1,416 @@
// -*- coding: utf-8 -*-
//
// AddonMetadataParser.cxx --- Parser for FlightGear add-on metadata files
// Copyright (C) 2018 Florent Rougon
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License along
// with this program; if not, write to the Free Software Foundation, Inc.,
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
#include <regex>
#include <string>
#include <tuple>
#include <vector>
#include <simgear/debug/logstream.hxx>
#include <simgear/misc/sg_path.hxx>
#include <simgear/misc/strutils.hxx>
#include <simgear/props/props.hxx>
#include <simgear/props/props_io.hxx>
#include "addon_fwd.hxx"
#include "AddonMetadataParser.hxx"
#include "AddonVersion.hxx"
#include "contacts.hxx"
#include "exceptions.hxx"
#include "pointer_traits.hxx"
#include <Main/globals.hxx>
#include <Main/locale.hxx>
namespace strutils = simgear::strutils;
using std::string;
using std::vector;
namespace flightgear
{
namespace addons
{
// Static method
SGPath
Addon::MetadataParser::getMetadataFile(const SGPath& addonPath)
{
return addonPath / "addon-metadata.xml";
}
static string getMaybeLocalized(const string& tag, SGPropertyNode* base, SGPropertyNode* lang)
{
if (lang) {
auto n = lang->getChild(tag);
if (n) {
return strutils::strip(n->getStringValue());
}
}
auto n = base->getChild(tag);
if (n) {
return strutils::strip(n->getStringValue());
}
return {};
}
static SGPropertyNode* getAndCheckLocalizedNode(SGPropertyNode* addonNode,
const SGPath& metadataFile)
{
const auto localizedNode = addonNode->getChild("localized");
if (!localizedNode) {
return nullptr;
}
for (int i = 0; i < localizedNode->nChildren(); ++i) {
const auto node = localizedNode->getChild(i);
const string& name = node->getNameString();
if (name.find('_') != string::npos) {
throw errors::error_loading_metadata_file(
"underscores not allowed in names of children of <localized> "
"(in add-on metadata file '" + metadataFile.utf8Str() + "'); "
"hyphens should be used, as in 'fr-FR' or 'en-GB'");
}
}
return localizedNode;
}
// Static method
Addon::Metadata
Addon::MetadataParser::parseMetadataFile(const SGPath& addonPath)
{
SGPath metadataFile = getMetadataFile(addonPath);
SGPropertyNode addonRoot;
Addon::Metadata metadata;
if (!metadataFile.exists()) {
throw errors::no_metadata_file_found(
"unable to find add-on metadata file '" + metadataFile.utf8Str() + "'");
}
try {
readProperties(metadataFile, &addonRoot);
} catch (const sg_exception &e) {
throw errors::error_loading_metadata_file(
"unable to load add-on metadata file '" + metadataFile.utf8Str() + "': " +
e.getFormattedMessage());
}
// Check the 'meta' section
SGPropertyNode *metaNode = addonRoot.getChild("meta");
if (metaNode == nullptr) {
throw errors::error_loading_metadata_file(
"no /meta node found in add-on metadata file '" +
metadataFile.utf8Str() + "'");
}
// Check the file type
SGPropertyNode *fileTypeNode = metaNode->getChild("file-type");
if (fileTypeNode == nullptr) {
throw errors::error_loading_metadata_file(
"no /meta/file-type node found in add-on metadata file '" +
metadataFile.utf8Str() + "'");
}
string fileType = fileTypeNode->getStringValue();
if (fileType != "FlightGear add-on metadata") {
throw errors::error_loading_metadata_file(
"Invalid /meta/file-type value for add-on metadata file '" +
metadataFile.utf8Str() + "': '" + fileType + "' "
"(expected 'FlightGear add-on metadata')");
}
// Check the format version
SGPropertyNode *fmtVersionNode = metaNode->getChild("format-version");
if (fmtVersionNode == nullptr) {
throw errors::error_loading_metadata_file(
"no /meta/format-version node found in add-on metadata file '" +
metadataFile.utf8Str() + "'");
}
int formatVersion = fmtVersionNode->getIntValue();
if (formatVersion != 1) {
throw errors::error_loading_metadata_file(
"unknown format version in add-on metadata file '" +
metadataFile.utf8Str() + "': " + std::to_string(formatVersion));
}
// Now the data we are really interested in
SGPropertyNode *addonNode = addonRoot.getChild("addon");
if (addonNode == nullptr) {
throw errors::error_loading_metadata_file(
"no /addon node found in add-on metadata file '" +
metadataFile.utf8Str() + "'");
}
const auto localizedNode = getAndCheckLocalizedNode(addonNode, metadataFile);
SGPropertyNode* langStringsNode = globals->get_locale()->selectLanguageNode(localizedNode);
SGPropertyNode *idNode = addonNode->getChild("identifier");
if (idNode == nullptr) {
throw errors::error_loading_metadata_file(
"no /addon/identifier node found in add-on metadata file '" +
metadataFile.utf8Str() + "'");
}
metadata.id = strutils::strip(idNode->getStringValue());
// Require a non-empty identifier for the add-on
if (metadata.id.empty()) {
throw errors::error_loading_metadata_file(
"empty or whitespace-only value for the /addon/identifier node in "
"add-on metadata file '" + metadataFile.utf8Str() + "'");
} else if (metadata.id.find('.') == string::npos) {
SG_LOG(SG_GENERAL, SG_WARN,
"Add-on identifier '" << metadata.id << "' does not use reverse DNS "
"style (e.g., org.flightgear.addons.MyAddon) in add-on metadata "
"file '" << metadataFile.utf8Str() + "'");
}
SGPropertyNode *nameNode = addonNode->getChild("name");
if (nameNode == nullptr) {
throw errors::error_loading_metadata_file(
"no /addon/name node found in add-on metadata file '" +
metadataFile.utf8Str() + "'");
}
metadata.name = getMaybeLocalized("name", addonNode, langStringsNode);
// Require a non-empty name for the add-on
if (metadata.name.empty()) {
throw errors::error_loading_metadata_file(
"empty or whitespace-only value for the /addon/name node in add-on "
"metadata file '" + metadataFile.utf8Str() + "'");
}
SGPropertyNode *versionNode = addonNode->getChild("version");
if (versionNode == nullptr) {
throw errors::error_loading_metadata_file(
"no /addon/version node found in add-on metadata file '" +
metadataFile.utf8Str() + "'");
}
metadata.version = AddonVersion{
strutils::strip(versionNode->getStringValue())};
metadata.authors = parseContactsNode<Author>(metadataFile,
addonNode->getChild("authors"));
metadata.maintainers = parseContactsNode<Maintainer>(
metadataFile, addonNode->getChild("maintainers"));
metadata.shortDescription = getMaybeLocalized("short-description", addonNode, langStringsNode);
metadata.longDescription = getMaybeLocalized("long-description", addonNode, langStringsNode);
std::tie(metadata.licenseDesignation, metadata.licenseFile,
metadata.licenseUrl) = parseLicenseNode(addonPath, addonNode);
SGPropertyNode *tagsNode = addonNode->getChild("tags");
if (tagsNode != nullptr) {
auto tagNodes = tagsNode->getChildren("tag");
for (const auto& node: tagNodes) {
metadata.tags.push_back(strutils::strip(node->getStringValue()));
}
}
SGPropertyNode *minNode = addonNode->getChild("min-FG-version");
if (minNode != nullptr) {
metadata.minFGVersionRequired = strutils::strip(minNode->getStringValue());
} else {
metadata.minFGVersionRequired = string();
}
SGPropertyNode *maxNode = addonNode->getChild("max-FG-version");
if (maxNode != nullptr) {
metadata.maxFGVersionRequired = strutils::strip(maxNode->getStringValue());
} else {
metadata.maxFGVersionRequired = string();
}
metadata.homePage = metadata.downloadUrl = metadata.supportUrl =
metadata.codeRepositoryUrl = string(); // defaults
SGPropertyNode *urlsNode = addonNode->getChild("urls");
if (urlsNode != nullptr) {
SGPropertyNode *homePageNode = urlsNode->getChild("home-page");
if (homePageNode != nullptr) {
metadata.homePage = strutils::strip(homePageNode->getStringValue());
}
SGPropertyNode *downloadUrlNode = urlsNode->getChild("download");
if (downloadUrlNode != nullptr) {
metadata.downloadUrl = strutils::strip(downloadUrlNode->getStringValue());
}
SGPropertyNode *supportUrlNode = urlsNode->getChild("support");
if (supportUrlNode != nullptr) {
metadata.supportUrl = strutils::strip(supportUrlNode->getStringValue());
}
SGPropertyNode *codeRepoUrlNode = urlsNode->getChild("code-repository");
if (codeRepoUrlNode != nullptr) {
metadata.codeRepositoryUrl =
strutils::strip(codeRepoUrlNode->getStringValue());
}
}
SG_LOG(SG_GENERAL, SG_DEBUG,
"Parsed add-on metadata file: '" << metadataFile.utf8Str() + "'");
return metadata;
}
// Utility function for Addon::MetadataParser::parseContactsNode<>()
//
// Read a node such as "name", "email" or "url", child of a contact node (e.g.,
// of an "author" or "maintainer" node).
static string
parseContactsNode_readNode(const SGPath& metadataFile,
SGPropertyNode* contactNode,
string subnodeName, bool allowEmpty)
{
SGPropertyNode *node = contactNode->getChild(subnodeName);
string contents;
if (node != nullptr) {
contents = simgear::strutils::strip(node->getStringValue());
}
if (!allowEmpty && contents.empty()) {
throw errors::error_loading_metadata_file(
"in add-on metadata file '" + metadataFile.utf8Str() + "': "
"when the node " + contactNode->getPath(true) + " exists, it must have "
"a non-empty '" + subnodeName + "' child node");
}
return contents;
};
// Static method template (private and only used in this file)
template <class T>
vector<typename contact_traits<T>::strong_ref>
Addon::MetadataParser::parseContactsNode(const SGPath& metadataFile,
SGPropertyNode* mainNode)
{
using contactTraits = contact_traits<T>;
vector<typename contactTraits::strong_ref> res;
if (mainNode != nullptr) {
auto contactNodes = mainNode->getChildren(contactTraits::xmlNodeName());
res.reserve(contactNodes.size());
for (const auto& contactNode: contactNodes) {
string name, email, url;
name = parseContactsNode_readNode(metadataFile, contactNode.get(),
"name", false /* allowEmpty */);
email = parseContactsNode_readNode(metadataFile, contactNode.get(),
"email", true);
url = parseContactsNode_readNode(metadataFile, contactNode.get(),
"url", true);
using ptr_traits = shared_ptr_traits<typename contactTraits::strong_ref>;
res.push_back(ptr_traits::makeStrongRef(name, email, url));
}
}
return res;
};
// Static method
std::tuple<string, SGPath, string>
Addon::MetadataParser::parseLicenseNode(const SGPath& addonPath,
SGPropertyNode* addonNode)
{
SGPath metadataFile = getMetadataFile(addonPath);
string licenseDesignation;
SGPath licenseFile;
string licenseUrl;
SGPropertyNode *licenseNode = addonNode->getChild("license");
if (licenseNode == nullptr) {
return std::tuple<string, SGPath, string>();
}
SGPropertyNode *licenseDesigNode = licenseNode->getChild("designation");
if (licenseDesigNode != nullptr) {
licenseDesignation = strutils::strip(licenseDesigNode->getStringValue());
}
SGPropertyNode *licenseFileNode = licenseNode->getChild("file");
if (licenseFileNode != nullptr) {
// This effectively disallows filenames starting or ending with whitespace
string licenseFile_s = strutils::strip(licenseFileNode->getStringValue());
if (!licenseFile_s.empty()) {
if (licenseFile_s.find('\\') != string::npos) {
throw errors::error_loading_metadata_file(
"in add-on metadata file '" + metadataFile.utf8Str() + "': the "
"value of /addon/license/file contains '\\'; please use '/' "
"separators only");
}
if (licenseFile_s.find_first_of("/\\") == 0) {
throw errors::error_loading_metadata_file(
"in add-on metadata file '" + metadataFile.utf8Str() + "': the "
"value of /addon/license/file must be relative to the add-on folder, "
"however it starts with '" + licenseFile_s[0] + "'");
}
#ifdef HAVE_WORKING_STD_REGEX
std::regex winDriveRegexp("([a-zA-Z]:).*");
std::smatch results;
if (std::regex_match(licenseFile_s, results, winDriveRegexp)) {
string winDrive = results.str(1);
#else // all this 'else' clause should be removed once we actually require C++11
if (licenseFile_s.size() >= 2 &&
(('a' <= licenseFile_s[0] && licenseFile_s[0] <= 'z') ||
('A' <= licenseFile_s[0] && licenseFile_s[0] <= 'Z')) &&
licenseFile_s[1] == ':') {
string winDrive = licenseFile_s.substr(0, 2);
#endif
throw errors::error_loading_metadata_file(
"in add-on metadata file '" + metadataFile.utf8Str() + "': the "
"value of /addon/license/file must be relative to the add-on folder, "
"however it starts with a Windows drive letter (" + winDrive + ")");
}
licenseFile = addonPath / licenseFile_s;
if ( !(licenseFile.exists() && licenseFile.isFile()) ) {
throw errors::error_loading_metadata_file(
"in add-on metadata file '" + metadataFile.utf8Str() + "': the "
"value of /addon/license/file (pointing to '" + licenseFile.utf8Str() +
"') doesn't correspond to an existing file");
}
} // of if (!licenseFile_s.empty())
} // of if (licenseFileNode != nullptr)
SGPropertyNode *licenseUrlNode = licenseNode->getChild("url");
if (licenseUrlNode != nullptr) {
licenseUrl = strutils::strip(licenseUrlNode->getStringValue());
}
return std::make_tuple(licenseDesignation, licenseFile, licenseUrl);
}
} // of namespace addons
} // of namespace flightgear

View File

@@ -0,0 +1,97 @@
// -*- coding: utf-8 -*-
//
// AddonMetadataParser.hxx --- Parser for FlightGear add-on metadata files
// Copyright (C) 2018 Florent Rougon
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License along
// with this program; if not, write to the Free Software Foundation, Inc.,
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
#ifndef FG_ADDON_METADATA_PARSER_HXX
#define FG_ADDON_METADATA_PARSER_HXX
#include <string>
#include <tuple>
#include <vector>
#include <simgear/misc/sg_path.hxx>
#include "addon_fwd.hxx"
#include "Addon.hxx"
#include "AddonVersion.hxx"
#include "contacts.hxx"
class SGPropertyNode;
namespace flightgear
{
namespace addons
{
class Addon::Metadata
{
public:
// Comments about these fields can be found in Addon.hxx
std::string id;
std::string name;
AddonVersion version;
std::vector<AuthorRef> authors;
std::vector<MaintainerRef> maintainers;
std::string shortDescription;
std::string longDescription;
std::string licenseDesignation;
SGPath licenseFile;
std::string licenseUrl;
std::vector<std::string> tags;
std::string minFGVersionRequired;
std::string maxFGVersionRequired;
std::string homePage;
std::string downloadUrl;
std::string supportUrl;
std::string codeRepositoryUrl;
};
class Addon::MetadataParser
{
public:
// “Compute” a path to the metadata file from the add-on base path
static SGPath getMetadataFile(const SGPath& addonPath);
// Parse the add-on metadata file inside 'addonPath' (as defined by
// getMetadataFile()) and return the corresponding Addon::Metadata instance.
static Addon::Metadata parseMetadataFile(const SGPath& addonPath);
private:
static std::tuple<string, SGPath, string>
parseLicenseNode(const SGPath& addonPath, SGPropertyNode* addonNode);
// Parse an addon-metadata.xml node such as <authors> or <maintainers>.
// Return the corresponding vector<AuthorRef> or vector<MaintainerRef>. If
// the 'mainNode' argument is nullptr, return an empty vector.
template <class T>
static std::vector<typename contact_traits<T>::strong_ref>
parseContactsNode(const SGPath& metadataFile, SGPropertyNode* mainNode);
};
} // of namespace addons
} // of namespace flightgear
#endif // of FG_ADDON_METADATA_PARSER_HXX

View File

@@ -0,0 +1,78 @@
// -*- coding: utf-8 -*-
//
// AddonResourceProvider.cxx --- ResourceProvider subclass for add-on files
// Copyright (C) 2018 Florent Rougon
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License along
// with this program; if not, write to the Free Software Foundation, Inc.,
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
#include <string>
#include <simgear/misc/ResourceManager.hxx>
#include <simgear/misc/sg_path.hxx>
#include <simgear/misc/strutils.hxx>
#include "AddonManager.hxx"
#include "AddonResourceProvider.hxx"
namespace strutils = simgear::strutils;
using std::string;
namespace flightgear
{
namespace addons
{
ResourceProvider::ResourceProvider()
: simgear::ResourceProvider(simgear::ResourceManager::PRIORITY_DEFAULT)
{ }
SGPath
ResourceProvider::resolve(const string& resource, SGPath& context) const
{
if (!strutils::starts_with(resource, "[addon=")) {
return SGPath();
}
string rest = resource.substr(7); // what follows '[addon='
auto endOfAddonId = rest.find(']');
if (endOfAddonId == string::npos) {
return SGPath();
}
string addonId = rest.substr(0, endOfAddonId);
// Extract what follows '[addon=ADDON_ID]'
string relPath = rest.substr(endOfAddonId + 1);
if (relPath.empty()) {
return SGPath();
}
const auto& addonMgr = AddonManager::instance();
SGPath addonDir = addonMgr->addonBasePath(addonId);
SGPath candidate = addonDir / relPath;
if (!candidate.isFile()) {
return SGPath();
}
return SGPath(candidate).validate(/* write */ false);
}
} // of namespace addons
} // of namespace flightgear

View File

@@ -0,0 +1,47 @@
// -*- coding: utf-8 -*-
//
// AddonResourceProvider.hxx --- ResourceProvider subclass for add-on files
// Copyright (C) 2018 Florent Rougon
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License along
// with this program; if not, write to the Free Software Foundation, Inc.,
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
#ifndef FG_ADDON_RESOURCE_PROVIDER_HXX
#define FG_ADDON_RESOURCE_PROVIDER_HXX
#include <string>
#include <simgear/misc/ResourceManager.hxx>
#include <simgear/misc/sg_path.hxx>
namespace flightgear
{
namespace addons
{
class ResourceProvider : public simgear::ResourceProvider
{
public:
ResourceProvider();
virtual SGPath resolve(const std::string& resource, SGPath& context) const
override;
};
} // of namespace addons
} // of namespace flightgear
#endif // of FG_ADDON_RESOURCE_PROVIDER_HXX

View File

@@ -0,0 +1,569 @@
// -*- coding: utf-8 -*-
//
// AddonVersion.cxx --- Version class for FlightGear add-ons
// Copyright (C) 2017 Florent Rougon
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License along
// with this program; if not, write to the Free Software Foundation, Inc.,
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
#include <numeric> // std::accumulate()
#include <ostream>
#include <regex>
#include <string>
#include <tuple>
#include <type_traits>
#include <utility>
#include <vector>
#include <cassert>
#include <simgear/misc/strutils.hxx>
#include <simgear/nasal/cppbind/Ghost.hxx>
#include <simgear/nasal/cppbind/NasalCallContext.hxx>
#include <simgear/nasal/cppbind/NasalHash.hxx>
#include <simgear/sg_inlines.h>
#include <simgear/structure/exception.hxx>
#include "addon_fwd.hxx"
#include "AddonVersion.hxx"
using std::string;
using std::vector;
using simgear::enumValue;
namespace strutils = simgear::strutils;
namespace flightgear
{
namespace addons
{
// ***************************************************************************
// * AddonVersionSuffix *
// ***************************************************************************
AddonVersionSuffix::AddonVersionSuffix(
AddonVersionSuffixPrereleaseType preReleaseType, int preReleaseNum,
bool developmental, int devNum)
: _preReleaseType(preReleaseType),
_preReleaseNum(preReleaseNum),
_developmental(developmental),
_devNum(devNum)
{ }
// Construct an AddonVersionSuffix instance from a tuple (preReleaseType,
// preReleaseNum, developmental, devNum). This would be nicer with
// std::apply(), but it requires C++17.
AddonVersionSuffix::AddonVersionSuffix(
const std::tuple<AddonVersionSuffixPrereleaseType, int, bool, int>& t)
: AddonVersionSuffix(std::get<0>(t), std::get<1>(t), std::get<2>(t),
std::get<3>(t))
{ }
AddonVersionSuffix::AddonVersionSuffix(const std::string& suffix)
: AddonVersionSuffix(suffixStringToTuple(suffix))
{ }
AddonVersionSuffix::AddonVersionSuffix(const char* suffix)
: AddonVersionSuffix(string(suffix))
{ }
// Static method
string
AddonVersionSuffix::releaseTypeStr(AddonVersionSuffixPrereleaseType releaseType)
{
switch (releaseType) {
case AddonVersionSuffixPrereleaseType::alpha:
return string("a");
case AddonVersionSuffixPrereleaseType::beta:
return string("b");
case AddonVersionSuffixPrereleaseType::candidate:
return string("rc");
case AddonVersionSuffixPrereleaseType::none:
return string();
default:
throw sg_error("unexpected value for member of "
"flightgear::addons::AddonVersionSuffixPrereleaseType: " +
std::to_string(enumValue(releaseType)));
}
}
string
AddonVersionSuffix::str() const
{
string res = releaseTypeStr(_preReleaseType);
if (!res.empty()) {
res += std::to_string(_preReleaseNum);
}
if (_developmental) {
res += ".dev" + std::to_string(_devNum);
}
return res;
}
// Static method
std::tuple<AddonVersionSuffixPrereleaseType, int, bool, int>
AddonVersionSuffix::suffixStringToTuple(const std::string& suffix)
{
#ifdef HAVE_WORKING_STD_REGEX
// Use a simplified variant of the syntax described in PEP 440
// <https://www.python.org/dev/peps/pep-0440/>: for the version suffix, only
// allow a pre-release segment and a development release segment, but no
// post-release segment.
std::regex versionSuffixRegexp(R"((?:(a|b|rc)(\d+))?(?:\.dev(\d+))?)");
std::smatch results;
if (std::regex_match(suffix, results, versionSuffixRegexp)) {
const string preReleaseType_s = results.str(1);
const string preReleaseNum_s = results.str(2);
const string devNum_s = results.str(3);
AddonVersionSuffixPrereleaseType preReleaseType = AddonVersionSuffixPrereleaseType::none;
int preReleaseNum = 0;
int devNum = 0;
if (preReleaseType_s.empty()) {
preReleaseType = AddonVersionSuffixPrereleaseType::none;
} else {
if (preReleaseType_s == "a") {
preReleaseType = AddonVersionSuffixPrereleaseType::alpha;
} else if (preReleaseType_s == "b") {
preReleaseType = AddonVersionSuffixPrereleaseType::beta;
} else if (preReleaseType_s == "rc") {
preReleaseType = AddonVersionSuffixPrereleaseType::candidate;
} else {
assert(false); // the regexp should prevent this
}
assert(!preReleaseNum_s.empty());
preReleaseNum = strutils::readNonNegativeInt<int>(preReleaseNum_s);
if (preReleaseNum < 1) {
string msg = "invalid add-on version suffix: '" + suffix + "' "
"(prerelease number must be greater than or equal to 1, but got " +
preReleaseNum_s + ")";
throw sg_format_exception(msg, suffix);
}
}
if (!devNum_s.empty()) {
devNum = strutils::readNonNegativeInt<int>(devNum_s);
if (devNum < 1) {
string msg = "invalid add-on version suffix: '" + suffix + "' "
"(development release number must be greater than or equal to 1, "
"but got " + devNum_s + ")";
throw sg_format_exception(msg, suffix);
}
}
return std::make_tuple(preReleaseType, preReleaseNum, !devNum_s.empty(),
devNum);
#else // all this 'else' clause should be removed once we actually require C++11
bool isMatch;
AddonVersionSuffixPrereleaseType preReleaseType;
int preReleaseNum;
bool developmental;
int devNum;
std::tie(isMatch, preReleaseType, preReleaseNum, developmental, devNum) =
parseVersionSuffixString_noRegexp(suffix);
if (isMatch) {
return std::make_tuple(preReleaseType, preReleaseNum, developmental,
devNum);
#endif // HAVE_WORKING_STD_REGEX
} else { // the regexp didn't match
string msg = "invalid add-on version suffix: '" + suffix + "' "
"(expected form is [{a|b|rc}N1][.devN2] where N1 and N2 are positive "
"integers)";
throw sg_format_exception(msg, suffix);
}
}
// Static method, only needed for compilers that are not C++11-compliant
// (gcc 4.8 pretends to support <regex> as required by C++11 but doesn't, see
// <https://stackoverflow.com/a/12665408/4756009>).
std::tuple<bool, AddonVersionSuffixPrereleaseType, int, bool, int>
AddonVersionSuffix::parseVersionSuffixString_noRegexp(const string& suffix)
{
AddonVersionSuffixPrereleaseType preReleaseType;
string rest;
int preReleaseNum = 0; // alpha, beta or release candidate number, or
// 0 when absent
bool developmental = false; // whether 'suffix' has a .devN2 part
int devNum = 0; // the N2 in question, or 0 when absent
std::tie(preReleaseType, rest) = popPrereleaseTypeFromBeginning(suffix);
if (preReleaseType != AddonVersionSuffixPrereleaseType::none) {
std::size_t startPrerelNum = rest.find_first_of("0123456789");
if (startPrerelNum != 0) { // no prerelease num -> no match
return std::make_tuple(false, preReleaseType, preReleaseNum, false,
devNum);
}
std::size_t endPrerelNum = rest.find_first_not_of("0123456789", 1);
// Works whether endPrerelNum is string::npos or not
string preReleaseNum_s = rest.substr(0, endPrerelNum);
preReleaseNum = strutils::readNonNegativeInt<int>(preReleaseNum_s);
if (preReleaseNum < 1) {
string msg = "invalid add-on version suffix: '" + suffix + "' "
"(prerelease number must be greater than or equal to 1, but got " +
preReleaseNum_s + ")";
throw sg_format_exception(msg, suffix);
}
rest = (endPrerelNum == string::npos) ? "" : rest.substr(endPrerelNum);
}
if (strutils::starts_with(rest, ".dev")) {
rest = rest.substr(4);
std::size_t startDevNum = rest.find_first_of("0123456789");
if (startDevNum != 0) { // no dev num -> no match
return std::make_tuple(false, preReleaseType, preReleaseNum, false,
devNum);
}
std::size_t endDevNum = rest.find_first_not_of("0123456789", 1);
if (endDevNum != string::npos) {
// There is trailing garbage after the development release number
// -> no match
return std::make_tuple(false, preReleaseType, preReleaseNum, false,
devNum);
}
devNum = strutils::readNonNegativeInt<int>(rest);
if (devNum < 1) {
string msg = "invalid add-on version suffix: '" + suffix + "' "
"(development release number must be greater than or equal to 1, "
"but got " + rest + ")";
throw sg_format_exception(msg, suffix);
}
developmental = true;
}
return std::make_tuple(true, preReleaseType, preReleaseNum, developmental,
devNum);
}
// Static method
std::tuple<AddonVersionSuffixPrereleaseType, string>
AddonVersionSuffix::popPrereleaseTypeFromBeginning(const string& s)
{
if (s.empty()) {
return std::make_tuple(AddonVersionSuffixPrereleaseType::none, s);
} else if (s[0] == 'a') {
return std::make_tuple(AddonVersionSuffixPrereleaseType::alpha,
s.substr(1));
} else if (s[0] == 'b') {
return std::make_tuple(AddonVersionSuffixPrereleaseType::beta, s.substr(1));
} else if (strutils::starts_with(s, "rc")) {
return std::make_tuple(AddonVersionSuffixPrereleaseType::candidate,
s.substr(2));
}
return std::make_tuple(AddonVersionSuffixPrereleaseType::none, s);
}
// Beware, this is not suitable for sorting! cf. genSortKey() below.
std::tuple<AddonVersionSuffixPrereleaseType, int, bool, int>
AddonVersionSuffix::makeTuple() const
{
return std::make_tuple(_preReleaseType, _preReleaseNum, _developmental,
_devNum);
}
std::tuple<int,
std::underlying_type<AddonVersionSuffixPrereleaseType>::type,
int, int, int>
AddonVersionSuffix::genSortKey() const
{
using AddonRelType = AddonVersionSuffixPrereleaseType;
// The first element means that a plain .devN is lower than everything else,
// except .devM with M <= N (namely: all dev and non-dev alpha, beta,
// candidates, as well as the empty suffix).
return std::make_tuple(
((_developmental && _preReleaseType == AddonRelType::none) ? 0 : 1),
enumValue(_preReleaseType),
_preReleaseNum,
(_developmental ? 0 : 1), // e.g., 1.0.3a2.devN < 1.0.3a2 for all N
_devNum);
}
bool operator==(const AddonVersionSuffix& lhs, const AddonVersionSuffix& rhs)
{ return lhs.genSortKey() == rhs.genSortKey(); }
bool operator!=(const AddonVersionSuffix& lhs, const AddonVersionSuffix& rhs)
{ return !operator==(lhs, rhs); }
bool operator< (const AddonVersionSuffix& lhs, const AddonVersionSuffix& rhs)
{ return lhs.genSortKey() < rhs.genSortKey(); }
bool operator> (const AddonVersionSuffix& lhs, const AddonVersionSuffix& rhs)
{ return operator<(rhs, lhs); }
bool operator<=(const AddonVersionSuffix& lhs, const AddonVersionSuffix& rhs)
{ return !operator>(lhs, rhs); }
bool operator>=(const AddonVersionSuffix& lhs, const AddonVersionSuffix& rhs)
{ return !operator<(lhs, rhs); }
std::ostream& operator<<(std::ostream& os,
const AddonVersionSuffix& addonVersionSuffix)
{ return os << addonVersionSuffix.str(); }
// ***************************************************************************
// * AddonVersion *
// ***************************************************************************
AddonVersion::AddonVersion(int major, int minor, int patchLevel,
AddonVersionSuffix suffix)
: _major(major),
_minor(minor),
_patchLevel(patchLevel),
_suffix(std::move(suffix))
{ }
// Construct an AddonVersion instance from a tuple (major, minor, patchLevel,
// suffix). This would be nicer with std::apply(), but it requires C++17.
AddonVersion::AddonVersion(
const std::tuple<int, int, int, AddonVersionSuffix>& t)
: AddonVersion(std::get<0>(t), std::get<1>(t), std::get<2>(t), std::get<3>(t))
{ }
AddonVersion::AddonVersion(const std::string& versionStr)
: AddonVersion(versionStringToTuple(versionStr))
{ }
AddonVersion::AddonVersion(const char* versionStr)
: AddonVersion(string(versionStr))
{ }
// Static method
std::tuple<int, int, int, AddonVersionSuffix>
AddonVersion::versionStringToTuple(const std::string& versionStr)
{
#ifdef HAVE_WORKING_STD_REGEX
// Use a simplified variant of the syntax described in PEP 440
// <https://www.python.org/dev/peps/pep-0440/> (always 3 components in the
// release segment, pre-release segment + development release segment; no
// post-release segment allowed).
std::regex versionRegexp(R"((\d+)\.(\d+).(\d+)(.*))");
std::smatch results;
if (std::regex_match(versionStr, results, versionRegexp)) {
const string majorNumber_s = results.str(1);
const string minorNumber_s = results.str(2);
const string patchLevel_s = results.str(3);
const string suffix_s = results.str(4);
int major = strutils::readNonNegativeInt<int>(majorNumber_s);
int minor = strutils::readNonNegativeInt<int>(minorNumber_s);
int patchLevel = strutils::readNonNegativeInt<int>(patchLevel_s);
return std::make_tuple(major, minor, patchLevel,
AddonVersionSuffix(suffix_s));
#else // all this 'else' clause should be removed once we actually require C++11
bool isMatch;
int major, minor, patchLevel;
AddonVersionSuffix suffix;
std::tie(isMatch, major, minor, patchLevel, suffix) =
parseVersionString_noRegexp(versionStr);
if (isMatch) {
return std::make_tuple(major, minor, patchLevel, suffix);
#endif // HAVE_WORKING_STD_REGEX
} else { // the regexp didn't match
string msg = "invalid add-on version number: '" + versionStr + "' "
"(expected form is MAJOR.MINOR.PATCHLEVEL[{a|b|rc}N1][.devN2] where "
"N1 and N2 are positive integers)";
throw sg_format_exception(msg, versionStr);
}
}
// Static method, only needed for compilers that are not C++11-compliant
// (gcc 4.8 pretends to support <regex> as required by C++11 but doesn't, see
// <https://stackoverflow.com/a/12665408/4756009>).
std::tuple<bool, int, int, int, AddonVersionSuffix>
AddonVersion::parseVersionString_noRegexp(const string& versionStr)
{
int major = 0, minor = 0, patchLevel = 0;
AddonVersionSuffix suffix{};
// Major version number
std::size_t endMajor = versionStr.find_first_not_of("0123456789");
if (endMajor == 0 || endMajor == string::npos) { // no match
return std::make_tuple(false, major, minor, patchLevel, suffix);
}
major = strutils::readNonNegativeInt<int>(versionStr.substr(0, endMajor));
// Dot separating the major and minor version numbers
if (versionStr.size() < endMajor + 1 || versionStr[endMajor] != '.') {
return std::make_tuple(false, major, minor, patchLevel, suffix);
}
string rest = versionStr.substr(endMajor + 1);
// Minor version number
std::size_t endMinor = rest.find_first_not_of("0123456789");
if (endMinor == 0 || endMinor == string::npos) { // no match
return std::make_tuple(false, major, minor, patchLevel, suffix);
}
minor = strutils::readNonNegativeInt<int>(rest.substr(0, endMinor));
// Dot separating the minor version number and the patch level
if (rest.size() < endMinor + 1 || rest[endMinor] != '.') {
return std::make_tuple(false, major, minor, patchLevel, suffix);
}
rest = rest.substr(endMinor + 1);
// Patch level
std::size_t endPatchLevel = rest.find_first_not_of("0123456789");
if (endPatchLevel == 0) { // no patch level, therefore no match
return std::make_tuple(false, major, minor, patchLevel, suffix);
}
patchLevel = strutils::readNonNegativeInt<int>(rest.substr(0, endPatchLevel));
if (endPatchLevel != string::npos) { // there is a version suffix, parse it
suffix = AddonVersionSuffix(rest.substr(endPatchLevel));
}
return std::make_tuple(true, major, minor, patchLevel, suffix);
}
int AddonVersion::majorNumber() const
{ return _major; }
int AddonVersion::minorNumber() const
{ return _minor; }
int AddonVersion::patchLevel() const
{ return _patchLevel; }
AddonVersionSuffix AddonVersion::suffix() const
{ return _suffix; }
std::string AddonVersion::suffixStr() const
{ return suffix().str(); }
std::tuple<int, int, int, AddonVersionSuffix> AddonVersion::makeTuple() const
{
return std::make_tuple(majorNumber(), minorNumber(), patchLevel(), suffix());
}
string AddonVersion::str() const
{
// Assemble the major.minor.patchLevel string
vector<int> v({majorNumber(), minorNumber(), patchLevel()});
string relSeg = std::accumulate(std::next(v.begin()), v.end(),
std::to_string(v[0]),
[](string s, int num) {
return s + '.' + std::to_string(num);
});
// Concatenate with the suffix string
return relSeg + suffixStr();
}
bool operator==(const AddonVersion& lhs, const AddonVersion& rhs)
{ return lhs.makeTuple() == rhs.makeTuple(); }
bool operator!=(const AddonVersion& lhs, const AddonVersion& rhs)
{ return !operator==(lhs, rhs); }
bool operator< (const AddonVersion& lhs, const AddonVersion& rhs)
{ return lhs.makeTuple() < rhs.makeTuple(); }
bool operator> (const AddonVersion& lhs, const AddonVersion& rhs)
{ return operator<(rhs, lhs); }
bool operator<=(const AddonVersion& lhs, const AddonVersion& rhs)
{ return !operator>(lhs, rhs); }
bool operator>=(const AddonVersion& lhs, const AddonVersion& rhs)
{ return !operator<(lhs, rhs); }
std::ostream& operator<<(std::ostream& os, const AddonVersion& addonVersion)
{ return os << addonVersion.str(); }
// ***************************************************************************
// * For the Nasal bindings *
// ***************************************************************************
bool AddonVersion::equal(const nasal::CallContext& ctx) const
{
auto other = ctx.requireArg<AddonVersionRef>(0);
return *this == *other;
}
bool AddonVersion::nonEqual(const nasal::CallContext& ctx) const
{
auto other = ctx.requireArg<AddonVersionRef>(0);
return *this != *other;
}
bool AddonVersion::lowerThan(const nasal::CallContext& ctx) const
{
auto other = ctx.requireArg<AddonVersionRef>(0);
return *this < *other;
}
bool AddonVersion::lowerThanOrEqual(const nasal::CallContext& ctx) const
{
auto other = ctx.requireArg<AddonVersionRef>(0);
return *this <= *other;
}
bool AddonVersion::greaterThan(const nasal::CallContext& ctx) const
{
auto other = ctx.requireArg<AddonVersionRef>(0);
return *this > *other;
}
bool AddonVersion::greaterThanOrEqual(const nasal::CallContext& ctx) const
{
auto other = ctx.requireArg<AddonVersionRef>(0);
return *this >= *other;
}
// Static method
void AddonVersion::setupGhost(nasal::Hash& addonsModule)
{
nasal::Ghost<AddonVersionRef>::init("addons.AddonVersion")
.member("majorNumber", &AddonVersion::majorNumber)
.member("minorNumber", &AddonVersion::minorNumber)
.member("patchLevel", &AddonVersion::patchLevel)
.member("suffix", &AddonVersion::suffixStr)
.method("str", &AddonVersion::str)
.method("equal", &AddonVersion::equal)
.method("nonEqual", &AddonVersion::nonEqual)
.method("lowerThan", &AddonVersion::lowerThan)
.method("lowerThanOrEqual", &AddonVersion::lowerThanOrEqual)
.method("greaterThan", &AddonVersion::greaterThan)
.method("greaterThanOrEqual", &AddonVersion::greaterThanOrEqual);
}
} // of namespace addons
} // of namespace flightgear

View File

@@ -0,0 +1,212 @@
// -*- coding: utf-8 -*-
//
// AddonVersion.hxx --- Version class for FlightGear add-ons
// Copyright (C) 2017 Florent Rougon
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License along
// with this program; if not, write to the Free Software Foundation, Inc.,
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
#ifndef FG_ADDONVERSION_HXX
#define FG_ADDONVERSION_HXX
#include <ostream>
#include <string>
#include <tuple>
#include <type_traits>
#include <simgear/nasal/cppbind/NasalCallContext.hxx>
#include <simgear/nasal/cppbind/NasalHash.hxx>
#include <simgear/structure/SGReferenced.hxx>
#include "addon_fwd.hxx"
namespace flightgear
{
namespace addons
{
// Order matters for the sorting/comparison functions
enum class AddonVersionSuffixPrereleaseType {
alpha = 0,
beta,
candidate,
none
};
// ***************************************************************************
// * AddonVersionSuffix *
// ***************************************************************************
class AddonVersionSuffix
{
public:
AddonVersionSuffix(AddonVersionSuffixPrereleaseType _preReleaseType
= AddonVersionSuffixPrereleaseType::none,
int preReleaseNum = 0, bool developmental = false,
int devNum = 0);
// Construct from a string. The empty string is a valid input.
AddonVersionSuffix(const std::string& suffix);
AddonVersionSuffix(const char* suffix);
// Construct from a tuple
explicit AddonVersionSuffix(
const std::tuple<AddonVersionSuffixPrereleaseType, int, bool, int>& t);
// Return all components of an AddonVersionSuffix instance as a tuple.
// Beware, this is not suitable for sorting! cf. genSortKey() below.
std::tuple<AddonVersionSuffixPrereleaseType, int, bool, int> makeTuple() const;
// String representation of an AddonVersionSuffix
std::string str() const;
private:
// String representation of the release type component: "a", "b", "rc" or "".
static std::string releaseTypeStr(AddonVersionSuffixPrereleaseType);
// If 's' starts with a non-empty release type ('a', 'b' or 'rc'), return
// the corresponding enum value along with the remainder of 's' (that is,
// everything after the release type). Otherwise, return
// AddonVersionSuffixPrereleaseType::none along with a copy of 's'.
static std::tuple<AddonVersionSuffixPrereleaseType, std::string>
popPrereleaseTypeFromBeginning(const std::string& s);
// Extract all components from a string representing a version suffix.
// The components of the return value are, in this order:
//
// preReleaseType, preReleaseNum, developmental, devNum
//
// Note: the empty string is a valid input.
static std::tuple<AddonVersionSuffixPrereleaseType, int, bool, int>
suffixStringToTuple(const std::string& suffix);
// Used to implement suffixStringToTuple() for compilers that are not
// C++11-compliant (gcc 4.8 pretends to support <regex> as required by C++11
// but doesn't, see <https://stackoverflow.com/a/12665408/4756009>).
//
// The bool in the first component of the result is true iff 'suffix' is a
// valid version suffix string. The bool is false when 'suffix' is invalid
// in such a way that the generic sg_format_exception thrown at the end of
// suffixStringToTuple() is appropriate. In all other cases, a specific
// exception is thrown.
static std::tuple<bool, AddonVersionSuffixPrereleaseType, int, bool, int>
parseVersionSuffixString_noRegexp(const std::string& suffix);
// Useful for comparisons/sorting purposes
std::tuple<int,
std::underlying_type<AddonVersionSuffixPrereleaseType>::type,
int, int, int> genSortKey() const;
friend bool operator==(const AddonVersionSuffix& lhs,
const AddonVersionSuffix& rhs);
friend bool operator<(const AddonVersionSuffix& lhs,
const AddonVersionSuffix& rhs);
AddonVersionSuffixPrereleaseType _preReleaseType;
int _preReleaseNum; // integer >= 1 (0 when not applicable)
bool _developmental; // whether the suffix ends with '.devN'
int _devNum; // integer >= 1 (0 when not applicable)
};
// operator==() and operator<() are declared above.
bool operator!=(const AddonVersionSuffix& lhs, const AddonVersionSuffix& rhs);
bool operator> (const AddonVersionSuffix& lhs, const AddonVersionSuffix& rhs);
bool operator<=(const AddonVersionSuffix& lhs, const AddonVersionSuffix& rhs);
bool operator>=(const AddonVersionSuffix& lhs, const AddonVersionSuffix& rhs);
std::ostream& operator<<(std::ostream&, const AddonVersionSuffix&);
// ***************************************************************************
// * AddonVersion *
// ***************************************************************************
// I suggest to use either the year-based FlightGear-type versioning, or
// semantic versioning (<http://semver.org/>). For the suffix, we allow things
// like "a1" (alpha1), "b2" (beta2), "rc4" (release candidate 4), "a1.dev3"
// (development release for "a1", which sorts before "a1"), etc. It's a subset
// of the syntax allowed in <https://www.python.org/dev/peps/pep-0440/>.
class AddonVersion : public SGReferenced
{
public:
AddonVersion(int major = 0, int minor = 0, int patchLevel = 0,
AddonVersionSuffix suffix = AddonVersionSuffix());
AddonVersion(const std::string& version);
AddonVersion(const char* version);
explicit AddonVersion(const std::tuple<int, int, int, AddonVersionSuffix>& t);
// Using the method names major() and minor() can lead to incomprehensible
// errors such as "major is not a member of flightgear::addons::AddonVersion"
// because of a hideous glibc bug[1]: major() and minor() are defined by
// standard headers as *macros*!
//
// [1] https://bugzilla.redhat.com/show_bug.cgi?id=130601
int majorNumber() const;
int minorNumber() const;
int patchLevel() const;
AddonVersionSuffix suffix() const;
std::string suffixStr() const;
std::string str() const;
// For the Nasal bindings (otherwise, we have operator==(), etc.)
bool equal(const nasal::CallContext& ctx) const;
bool nonEqual(const nasal::CallContext& ctx) const;
bool lowerThan(const nasal::CallContext& ctx) const;
bool lowerThanOrEqual(const nasal::CallContext& ctx) const;
bool greaterThan(const nasal::CallContext& ctx) const;
bool greaterThanOrEqual(const nasal::CallContext& ctx) const;
static void setupGhost(nasal::Hash& addonsModule);
private:
// Useful for comparisons/sorting purposes
std::tuple<int, int, int, AddonVersionSuffix> makeTuple() const;
static std::tuple<int, int, int, AddonVersionSuffix>
versionStringToTuple(const std::string& versionStr);
// Used to implement versionStringToTuple() for compilers that are not
// C++11-compliant (gcc 4.8 pretends to support <regex> as required by C++11
// but doesn't, see <https://stackoverflow.com/a/12665408/4756009>).
//
// The bool in the first component of the result is true iff 'versionStr' is
// a valid version string. The bool is false when 'versionStr' is invalid in
// such a way that the generic sg_format_exception thrown at the end of
// versionStringToTuple() is appropriate. In all other cases, a specific
// exception is thrown.
static std::tuple<bool, int, int, int, AddonVersionSuffix>
parseVersionString_noRegexp(const std::string& versionStr);
friend bool operator==(const AddonVersion& lhs, const AddonVersion& rhs);
friend bool operator<(const AddonVersion& lhs, const AddonVersion& rhs);
int _major;
int _minor;
int _patchLevel;
AddonVersionSuffix _suffix;
};
// operator==() and operator<() are declared above.
bool operator!=(const AddonVersion& lhs, const AddonVersion& rhs);
bool operator> (const AddonVersion& lhs, const AddonVersion& rhs);
bool operator<=(const AddonVersion& lhs, const AddonVersion& rhs);
bool operator>=(const AddonVersion& lhs, const AddonVersion& rhs);
std::ostream& operator<<(std::ostream&, const AddonVersion&);
} // of namespace addons
} // of namespace flightgear
#endif // of FG_ADDONVERSION_HXX

View File

@@ -0,0 +1,23 @@
include(FlightGearComponent)
set(SOURCES Addon.cxx
AddonManager.cxx
AddonMetadataParser.cxx
AddonResourceProvider.cxx
AddonVersion.cxx
contacts.cxx
exceptions.cxx
)
set(HEADERS addon_fwd.hxx
Addon.hxx
AddonManager.hxx
AddonMetadataParser.hxx
AddonResourceProvider.hxx
AddonVersion.hxx
contacts.hxx
exceptions.hxx
pointer_traits.hxx
)
flightgear_component(AddonManagement "${SOURCES}" "${HEADERS}")

72
src/Add-ons/addon_fwd.hxx Normal file
View File

@@ -0,0 +1,72 @@
// -*- coding: utf-8 -*-
//
// addon_fwd.hxx --- Forward declarations for the FlightGear add-on
// infrastructure
// Copyright (C) 2017 Florent Rougon
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License along
// with this program; if not, write to the Free Software Foundation, Inc.,
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
#ifndef FG_ADDON_FWD_HXX
#define FG_ADDON_FWD_HXX
#include <simgear/structure/SGSharedPtr.hxx>
namespace flightgear
{
namespace addons
{
class Addon;
class AddonManager;
class AddonVersion;
class AddonVersionSuffix;
class ResourceProvider;
enum class UrlType;
class QualifiedUrl;
enum class ContactType;
class Contact;
class Author;
class Maintainer;
using AddonRef = SGSharedPtr<Addon>;
using AddonVersionRef = SGSharedPtr<AddonVersion>;
using ContactRef = SGSharedPtr<Contact>;
using AuthorRef = SGSharedPtr<Author>;
using MaintainerRef = SGSharedPtr<Maintainer>;
namespace errors
{
class error;
class error_loading_config_file;
class no_metadata_file_found;
class error_loading_metadata_file;
class error_loading_menubar_items_file;
class duplicate_registration_attempt;
class fg_version_too_old;
class fg_version_too_recent;
class invalid_resource_path;
class unable_to_create_addon_storage_dir;
} // of namespace errors
} // of namespace addons
} // of namespace flightgear
#endif // of FG_ADDON_FWD_HXX

127
src/Add-ons/contacts.cxx Normal file
View File

@@ -0,0 +1,127 @@
// -*- coding: utf-8 -*-
//
// contacts.cxx --- FlightGear classes holding add-on contact metadata
// Copyright (C) 2018 Florent Rougon
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License along
// with this program; if not, write to the Free Software Foundation, Inc.,
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
#include <string>
#include <utility>
#include <simgear/nasal/cppbind/Ghost.hxx>
#include <simgear/nasal/cppbind/NasalHash.hxx>
#include <simgear/sg_inlines.h>
#include <simgear/structure/exception.hxx>
#include "addon_fwd.hxx"
#include "contacts.hxx"
using std::string;
using simgear::enumValue;
namespace flightgear
{
namespace addons
{
// ***************************************************************************
// * Contact *
// ***************************************************************************
Contact::Contact(ContactType type, string name, string email, string url)
: _type(type),
_name(std::move(name)),
_email(std::move(email)),
_url(std::move(url))
{ }
ContactType Contact::getType() const
{ return _type; }
string Contact::getTypeString() const
{
switch (getType()) {
case ContactType::author:
return "author";
case ContactType::maintainer:
return "maintainer";
default:
throw sg_error("unexpected value for member of "
"flightgear::addons::ContactType: " +
std::to_string(enumValue(getType())));
}
}
string Contact::getName() const
{ return _name; }
void Contact::setName(const string& name)
{ _name = name; }
string Contact::getEmail() const
{ return _email; }
void Contact::setEmail(const string& email)
{ _email = email; }
string Contact::getUrl() const
{ return _url; }
void Contact::setUrl(const string& url)
{ _url = url; }
// Static method
void Contact::setupGhost(nasal::Hash& addonsModule)
{
nasal::Ghost<ContactRef>::init("addons.Contact")
.member("name", &Contact::getName)
.member("email", &Contact::getEmail)
.member("url", &Contact::getUrl);
}
// ***************************************************************************
// * Author *
// ***************************************************************************
Author::Author(string name, string email, string url)
: Contact(ContactType::author, name, email, url)
{ }
// Static method
void Author::setupGhost(nasal::Hash& addonsModule)
{
nasal::Ghost<AuthorRef>::init("addons.Author")
.bases<ContactRef>();
}
// ***************************************************************************
// * Maintainer *
// ***************************************************************************
Maintainer::Maintainer(string name, string email, string url)
: Contact(ContactType::maintainer, name, email, url)
{ }
// Static method
void Maintainer::setupGhost(nasal::Hash& addonsModule)
{
nasal::Ghost<MaintainerRef>::init("addons.Maintainer")
.bases<ContactRef>();
}
} // of namespace addons
} // of namespace flightgear

126
src/Add-ons/contacts.hxx Normal file
View File

@@ -0,0 +1,126 @@
// -*- coding: utf-8 -*-
//
// contacts.hxx --- FlightGear classes holding add-on contact metadata
// Copyright (C) 2018 Florent Rougon
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License along
// with this program; if not, write to the Free Software Foundation, Inc.,
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
#ifndef FG_ADDON_CONTACTS_HXX
#define FG_ADDON_CONTACTS_HXX
#include <string>
#include <simgear/structure/SGReferenced.hxx>
#include "addon_fwd.hxx"
namespace nasal
{
class Hash; // forward declaration
};
namespace flightgear
{
namespace addons
{
enum class ContactType {
author,
maintainer
};
// Class used to store info about an author or maintainer (possibly also a
// mailing-list, things like that)
class Contact : public SGReferenced
{
public:
Contact(ContactType type, std::string name, std::string email = "",
std::string url = "");
virtual ~Contact() = default;
ContactType getType() const;
std::string getTypeString() const;
std::string getName() const;
void setName(const std::string& name);
std::string getEmail() const;
void setEmail(const std::string& email);
std::string getUrl() const;
void setUrl(const std::string& url);
static void setupGhost(nasal::Hash& addonsModule);
private:
const ContactType _type;
std::string _name;
std::string _email;
std::string _url;
};
class Author : public Contact
{
public:
Author(std::string name, std::string email = "", std::string url = "");
static void setupGhost(nasal::Hash& addonsModule);
};
class Maintainer : public Contact
{
public:
Maintainer(std::string name, std::string email = "", std::string url = "");
static void setupGhost(nasal::Hash& addonsModule);
};
// ***************************************************************************
// * contact_traits *
// ***************************************************************************
template <typename T>
struct contact_traits;
template<>
struct contact_traits<Author>
{
using contact_type = Author;
using strong_ref = AuthorRef;
static std::string xmlNodeName()
{
return "author";
}
};
template<>
struct contact_traits<Maintainer>
{
using contact_type = Maintainer;
using strong_ref = MaintainerRef;
static std::string xmlNodeName()
{
return "maintainer";
}
};
} // of namespace addons
} // of namespace flightgear
#endif // of FG_ADDON_CONTACTS_HXX

View File

@@ -0,0 +1,55 @@
// -*- coding: utf-8 -*-
//
// exceptions.cxx --- Exception classes for the FlightGear add-on infrastructure
// Copyright (C) 2017 Florent Rougon
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License along
// with this program; if not, write to the Free Software Foundation, Inc.,
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
#include <string>
#include <simgear/structure/exception.hxx>
#include "exceptions.hxx"
using std::string;
namespace flightgear
{
namespace addons
{
namespace errors
{
// ***************************************************************************
// * Base class for add-on exceptions *
// ***************************************************************************
// Prepending a prefix such as "Add-on error: " would be redundant given the
// messages used in, e.g., the Addon class code.
error::error(const string& message, const string& origin)
: sg_exception(message, origin)
{ }
error::error(const char* message, const char* origin)
: error(string(message), string(origin))
{ }
} // of namespace errors
} // of namespace addons
} // of namespace flightgear

View File

@@ -0,0 +1,77 @@
// -*- coding: utf-8 -*-
//
// exceptions.hxx --- Exception classes for the FlightGear add-on infrastructure
// Copyright (C) 2017 Florent Rougon
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License along
// with this program; if not, write to the Free Software Foundation, Inc.,
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
#ifndef FG_ADDON_EXCEPTIONS_HXX
#define FG_ADDON_EXCEPTIONS_HXX
#include <string>
#include <simgear/structure/exception.hxx>
namespace flightgear
{
namespace addons
{
namespace errors
{
class error : public sg_exception
{
public:
explicit error(const std::string& message,
const std::string& origin = std::string());
explicit error(const char* message, const char* origin = nullptr);
};
class error_loading_config_file : public error
{ using error::error; /* inherit all constructors */ };
class no_metadata_file_found : public error
{ using error::error; };
class error_loading_metadata_file : public error
{ using error::error; };
class error_loading_menubar_items_file : public error
{ using error::error; };
class duplicate_registration_attempt : public error
{ using error::error; };
class fg_version_too_old : public error
{ using error::error; };
class fg_version_too_recent : public error
{ using error::error; };
class invalid_resource_path : public error
{ using error::error; };
class unable_to_create_addon_storage_dir : public error
{ using error::error; };
} // of namespace errors
} // of namespace addons
} // of namespace flightgear
#endif // of FG_ADDON_EXCEPTIONS_HXX

View File

@@ -0,0 +1,67 @@
// -*- coding: utf-8 -*-
//
// pointer_traits.hxx --- Pointer traits classes
// Copyright (C) 2018 Florent Rougon
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License along
// with this program; if not, write to the Free Software Foundation, Inc.,
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
#ifndef FG_ADDON_POINTER_TRAITS_HXX
#define FG_ADDON_POINTER_TRAITS_HXX
#include <memory>
#include <utility>
#include <simgear/structure/SGSharedPtr.hxx>
namespace flightgear
{
namespace addons
{
template <typename T>
struct shared_ptr_traits;
template <typename T>
struct shared_ptr_traits<SGSharedPtr<T>>
{
using element_type = T;
using strong_ref = SGSharedPtr<T>;
template <typename ...Args>
static strong_ref makeStrongRef(Args&& ...args)
{
return strong_ref(new T(std::forward<Args>(args)...));
}
};
template <typename T>
struct shared_ptr_traits<std::shared_ptr<T>>
{
using element_type = T;
using strong_ref = std::shared_ptr<T>;
template <typename ...Args>
static strong_ref makeStrongRef(Args&& ...args)
{
return std::make_shared<T>(std::forward<Args>(args)...);
}
};
} // of namespace addons
} // of namespace flightgear
#endif // of FG_ADDON_POINTER_TRAITS_HXX

View File

@@ -0,0 +1,407 @@
// AircraftPerformance.cxx - compute data about planned acft performance
//
// Copyright (C) 2018 James Turner <james@flightgear.org>
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation; either version 2 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#include "AircraftPerformance.hxx"
#include <cassert>
#include <algorithm>
#include <simgear/constants.h>
#include <Main/fg_props.hxx>
using namespace flightgear;
double distanceForTimeAndSpeeds(double tSec, double v1, double v2)
{
return tSec * 0.5 * (v1 + v2);
}
AircraftPerformance::AircraftPerformance()
{
// read aircraft supplied performance data
if (fgGetNode("/aircraft/performance/bracket")) {
readPerformanceData();
} else {
// falls back to heuristic determination of the category,
// and a plausible default
icaoCategoryData();
}
}
double AircraftPerformance::groundSpeedForAltitudeKnots(int altitudeFt) const
{
auto bracket = bracketForAltitude(altitudeFt);
return bracket->gsForAltitude(altitudeFt);
}
int AircraftPerformance::computePreviousAltitude(double distanceM, int targetAltFt) const
{
auto bracket = bracketForAltitude(targetAltFt);
auto d = bracket->descendDistanceM(bracket->atOrBelowAltitudeFt, targetAltFt);
if (d < distanceM) {
// recurse to previous bracket
return computePreviousAltitude(distanceM - d, bracket->atOrBelowAltitudeFt+1);
}
// work out how far we travel laterally per foot change in altitude
// this value is in metres, we have to map FPM and GS in Knots to make
// everything work out
const double gsMPS = bracket->gsForAltitude(targetAltFt) * SG_KT_TO_MPS;
const double t = distanceM / gsMPS;
return targetAltFt + bracket->descentRateFPM * (t / 60.0);
}
int AircraftPerformance::computeNextAltitude(double distanceM, int initialAltFt) const
{
auto bracket = bracketForAltitude(initialAltFt);
auto d = bracket->climbDistanceM(initialAltFt, bracket->atOrBelowAltitudeFt);
if (d < distanceM) {
// recurse to next bracket
return computeNextAltitude(distanceM - d, bracket->atOrBelowAltitudeFt+1);
}
// work out how far we travel laterally per foot change in altitude
// this value is in metres, we have to map FPM and GS in Knots to make
// everything work out
const double gsMPS = bracket->gsForAltitude(initialAltFt) * SG_KT_TO_MPS;
const double t = distanceM / gsMPS;
return initialAltFt + bracket->climbRateFPM * (t / 60.0);
}
static string_list readTags()
{
string_list r;
const auto tagsNode = fgGetNode("/sim/tags");
if (!tagsNode)
return r;
for (auto t : tagsNode->getChildren("tag")) {
r.push_back(t->getStringValue());
}
return r;
}
static bool stringListContains(const string_list& t, const std::string& s)
{
auto it = std::find(t.begin(), t.end(), s);
return it != t.end();
}
std::string AircraftPerformance::heuristicCatergoryFromTags() const
{
const auto tags(readTags());
if (stringListContains(tags, "turboprop"))
return {ICAO_AIRCRAFT_CATEGORY_C};
// any way we could distuinguish fast and slow GA aircraft?
if (stringListContains(tags, "ga")) {
return {ICAO_AIRCRAFT_CATEGORY_A};
}
if (stringListContains(tags, "jet")) {
return {ICAO_AIRCRAFT_CATEGORY_E};
}
return {ICAO_AIRCRAFT_CATEGORY_C};
}
void AircraftPerformance::icaoCategoryData()
{
std::string propCat = fgGetString("/aircraft/performance/icao-category");
if (propCat.empty()) {
propCat = heuristicCatergoryFromTags();
}
const char aircraftCategory = propCat.front();
// pathTurnRate = 3.0; // 3 deg/sec = 180deg/min = standard rate turn
switch (aircraftCategory) {
case ICAO_AIRCRAFT_CATEGORY_A:
_perfData.push_back(Bracket(4000, 600, 1200, 75));
_perfData.push_back(Bracket(10000, 600, 1200, 140));
break;
case ICAO_AIRCRAFT_CATEGORY_B:
_perfData.push_back(Bracket(4000, 100, 1200, 100));
_perfData.push_back(Bracket(10000, 800, 1200, 160));
_perfData.push_back(Bracket(18000, 600, 1800, 200));
break;
case ICAO_AIRCRAFT_CATEGORY_C:
_perfData.push_back(Bracket(4000, 1800, 1800, 150));
_perfData.push_back(Bracket(10000, 1800, 1800, 200));
_perfData.push_back(Bracket(18000, 1200, 1800, 270));
_perfData.push_back(Bracket(60000, 800, 1200, 0.80, true /* is Mach */));
break;
case ICAO_AIRCRAFT_CATEGORY_D:
case ICAO_AIRCRAFT_CATEGORY_E:
default:
_perfData.push_back(Bracket(4000, 1800, 1800, 180));
_perfData.push_back(Bracket(10000, 1800, 1800, 230));
_perfData.push_back(Bracket(18000, 1200, 1800, 270));
_perfData.push_back(Bracket(60000, 800, 1200, 0.87, true /* is Mach */));
break;
}
}
void AircraftPerformance::readPerformanceData()
{
for (auto nd : fgGetNode("/aircraft/performance/")->getChildren("bracket")) {
const int atOrBelowAlt = nd->getIntValue("at-or-below-ft");
const int climbFPM = nd->getIntValue("climb-rate-fpm");
const int descentFPM = nd->getIntValue("descent-rate-fpm");
bool isMach = nd->hasChild("speed-mach");
double speed;
if (isMach) {
speed = nd->getDoubleValue("speed-mach");
} else {
speed = nd->getIntValue("speed-ias-knots");
}
Bracket b(atOrBelowAlt, climbFPM, descentFPM, speed, isMach);
_perfData.push_back(b);
}
}
auto AircraftPerformance::bracketForAltitude(int altitude) const
-> PerformanceVec::const_iterator
{
assert(!_perfData.empty());
if (_perfData.front().atOrBelowAltitudeFt >= altitude)
return _perfData.begin();
for (auto it = _perfData.begin(); it != _perfData.end(); ++it) {
if (it->atOrBelowAltitudeFt > altitude) {
return it;
}
}
return _perfData.end() - 1;
}
auto AircraftPerformance::rangeForAltitude(int lowAltitude, int highAltitude) const
-> BracketRange
{
return {bracketForAltitude(lowAltitude), bracketForAltitude(highAltitude)};
}
void AircraftPerformance::traverseAltitudeRange(int initialElevationFt, int targetElevationFt,
TraversalFunc tf) const
{
auto r = rangeForAltitude(initialElevationFt, targetElevationFt);
if (r.first == r.second) {
tf(*r.first, initialElevationFt, targetElevationFt);
return;
}
if (initialElevationFt < targetElevationFt) {
tf(*r.first, initialElevationFt, r.first->atOrBelowAltitudeFt);
int previousBracketCapAltitude = r.first->atOrBelowAltitudeFt;
for (auto bracket = r.first + 1; bracket != r.second; ++bracket) {
tf(*bracket, previousBracketCapAltitude, bracket->atOrBelowAltitudeFt);
previousBracketCapAltitude = bracket->atOrBelowAltitudeFt;
}
tf(*r.second, previousBracketCapAltitude, targetElevationFt);
} else {
int nextBracketCapAlt = (r.first - 1)->atOrBelowAltitudeFt;
tf(*r.first, initialElevationFt, nextBracketCapAlt);
for (auto bracket = r.first - 1; bracket != r.second; --bracket) {
nextBracketCapAlt = (r.first - 1)->atOrBelowAltitudeFt;
tf(*bracket, bracket->atOrBelowAltitudeFt, nextBracketCapAlt);
}
tf(*r.second, nextBracketCapAlt, targetElevationFt);
}
}
double AircraftPerformance::distanceNmBetween(int initialElevationFt, int targetElevationFt) const
{
double result = 0.0;
TraversalFunc tf = [&result](const Bracket& bk, int alt1, int alt2) {
result += (alt1 > alt2) ? bk.descendDistanceM(alt1, alt2) : bk.climbDistanceM(alt1, alt2);
};
traverseAltitudeRange(initialElevationFt, targetElevationFt, tf);
return result * SG_METER_TO_NM;
}
double AircraftPerformance::timeBetween(int initialElevationFt, int targetElevationFt) const
{
double result = 0.0;
TraversalFunc tf = [&result](const Bracket& bk, int alt1, int alt2) {
SG_LOG(SG_GENERAL, SG_INFO, "Range:" << alt1 << " " << alt2);
result += (alt1 > alt2) ? bk.descendTime(alt1, alt2) : bk.climbTime(alt1, alt2);
};
traverseAltitudeRange(initialElevationFt, targetElevationFt, tf);
return result;
}
double AircraftPerformance::timeToCruise(double cruiseDistanceNm, int cruiseAltitudeFt) const
{
auto b = bracketForAltitude(cruiseAltitudeFt);
return (cruiseDistanceNm / b->gsForAltitude(cruiseAltitudeFt)) * 3600.0;
}
double oatCForAltitudeFt(int altitudeFt)
{
if (altitudeFt > 36089)
return -56.5;
// lapse rate in C per ft
const double T_r = .0019812;
return 15.0 - (altitudeFt * T_r);
}
double oatKForAltitudeFt(int altitudeFt)
{
return oatCForAltitudeFt(altitudeFt) + 273.15;
}
double pressureAtAltitude(int altitude)
{
/*
p= P_0*(1-6.8755856*10^-6 h)^5.2558797 h<36,089.24ft
p_Tr= 0.2233609*P_0
p=p_Tr*exp(-4.806346*10^-5(h-36089.24)) h>36,089.24ft
magic numbers
6.8755856*10^-6 = T'/T_0, where T' is the standard temperature lapse rate and T_0 is the standard sea-level temperature.
5.2558797 = Mg/RT', where M is the (average) molecular weight of air, g is the acceleration of gravity and R is the gas constant.
4.806346*10^-5 = Mg/RT_tr, where T_tr is the temperature at the tropopause.
*/
const double k = 6.8755856e-6;
const double MgRT = 5.2558797;
const double MgRT_tr = 4.806346e-5;
const double P_0 = 29.92126; // (standard) sea-level pressure
if (altitude > 36089) {
const double P_Tr = 0.2233609 * P_0;
const double altAboveTr = altitude - 36089;
return P_Tr * exp(MgRT_tr * altAboveTr);
} else {
return P_0 * pow(1.0 - (k * altitude), MgRT);
}
}
double computeMachFromIAS(int iasKnots, int altitudeFt)
{
#if 0
// from the aviation formulary
DP=P_0*((1 + 0.2*(IAS/CS_0)^2)^3.5 -1)
M=(5*( (DP/P + 1)^(2/7) -1) )^0.5 (*)
#endif
const double Cs_0 = 661.4786; // speed of sound at sea level, knots
const double P_0 = 29.92126; // (standard) sea-level pressure
const double iasCsRatio = iasKnots / Cs_0;
const double P = pressureAtAltitude(altitudeFt);
// differential pressure
const double DP = P_0 * (pow(1.0 + 0.2 * pow(iasCsRatio, 2.0), 3.5) - 1.0);
const double pressureRatio = DP / P + 1.0;
const double M = pow(5.0 * (pow(pressureRatio, 2.0 / 7.0) - 1.0), 0.5);
if (M > 1.0) {
SG_LOG(SG_GENERAL, SG_INFO, "computeMachFromIAS: computed Mach is supersonic, fix for shock wave");
}
return M;
}
double AircraftPerformance::machForCAS(int altitudeFt, double cas)
{
return computeMachFromIAS(static_cast<int>(cas), altitudeFt);
}
double AircraftPerformance::groundSpeedForCAS(int altitudeFt, double cas)
{
return groundSpeedForMach(altitudeFt, computeMachFromIAS(cas, altitudeFt));
}
double AircraftPerformance::groundSpeedForMach(int altitudeFt, double mach)
{
// CS = sound speed= 38.967854*sqrt(T+273.15) where T is the OAT in celsius.
const double CS = 38.967854 * sqrt(oatKForAltitudeFt(altitudeFt));
const double TAS = mach * CS;
return TAS;
}
int AircraftPerformance::Bracket::gsForAltitude(int altitude) const
{
double M = 0.0;
if (speedIsMach) {
M = speedIASOrMach; // simple
} else {
M = computeMachFromIAS(speedIASOrMach, altitude);
}
return groundSpeedForMach(altitude, M);
}
double AircraftPerformance::Bracket::climbTime(int alt1, int alt2) const
{
return (alt2 - alt1) / static_cast<double>(climbRateFPM) * 60.0;
}
double AircraftPerformance::Bracket::climbDistanceM(int alt1, int alt2) const
{
const double t = climbTime(alt1, alt2);
return distanceForTimeAndSpeeds(t,
SG_KT_TO_MPS * gsForAltitude(alt1),
SG_KT_TO_MPS * gsForAltitude(alt2));
}
double AircraftPerformance::Bracket::descendTime(int alt1, int alt2) const
{
return (alt1 - alt2) / static_cast<double>(descentRateFPM) * 60.0;
}
double AircraftPerformance::Bracket::descendDistanceM(int alt1, int alt2) const
{
const double t = descendTime(alt1, alt2);
return distanceForTimeAndSpeeds(t,
SG_KT_TO_MPS * gsForAltitude(alt1),
SG_KT_TO_MPS * gsForAltitude(alt2));
}
double AircraftPerformance::turnRadiusMForAltitude(int altitudeFt) const
{
#if 0
From the aviation formulary again
In a steady turn, in no wind, with bank angle, b at an airspeed v
tan(b)= v^2/(R g)
With R in feet, v in knots, b in degrees and w in degrees/sec (inconsistent units!), numerical constants are introduced:
R =v^2/(11.23*tan(0.01745*b))
(Example) At 100 knots, with a 45 degree bank, the radius of turn is 100^2/(11.23*tan(0.01745*45))= 891 feet.
The bank angle b_s for a standard rate turn is given by:
b_s = 57.3*atan(v/362.1)
(Example) for 100 knots, b_s = 57.3*atan(100/362.1) = 15.4 degrees
Working in meter-per-second and radians removes a bunch of constants again.
#endif
const double gsKts = groundSpeedForAltitudeKnots(altitudeFt);
const double gs = gsKts * SG_KT_TO_MPS;
const double bankAngleRad = atan(gsKts/362.1);
const double r = (gs * gs)/(SG_g0_m_p_s2 * tan(bankAngleRad));
return r;
}

View File

@@ -0,0 +1,117 @@
// AircraftPerformance.hxx - compute data about planned acft performance
//
// Copyright (C) 2018 James Turner <james@flightgear.org>
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation; either version 2 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#ifndef AIRCRAFTPERFORMANCE_HXX
#define AIRCRAFTPERFORMANCE_HXX
#include <string>
#include <vector>
#include <functional>
namespace flightgear
{
const char ICAO_AIRCRAFT_CATEGORY_A = 'A';
const char ICAO_AIRCRAFT_CATEGORY_B = 'B';
const char ICAO_AIRCRAFT_CATEGORY_C = 'C';
const char ICAO_AIRCRAFT_CATEGORY_D = 'D';
const char ICAO_AIRCRAFT_CATEGORY_E = 'E';
/**
* Calculate flight parameter based on aircraft performance data.
* This is based on simple rules: it does not (yet) include data
* such as winds aloft, payload or temperature impact on engine
* performance. */
class AircraftPerformance
{
public:
AircraftPerformance();
double turnRateDegSec() const;
double turnRadiusMForAltitude(int altitudeFt) const;
double groundSpeedForAltitudeKnots(int altitudeFt) const;
int computePreviousAltitude(double distanceM, int targetAltFt) const;
int computeNextAltitude(double distanceM, int initialAltFt) const;
double distanceNmBetween(int initialElevationFt, int targetElevationFt) const;
double timeBetween(int initialElevationFt, int targetElevationFt) const;
double timeToCruise(double cruiseDistanceNm, int cruiseAltitudeFt) const;
static double groundSpeedForCAS(int altitudeFt, double cas);
static double machForCAS(int altitudeFt, double cas);
static double groundSpeedForMach(int altitudeFt, double mach);
private:
void readPerformanceData();
void icaoCategoryData();
/**
* @brief heuristicCatergoryFromTags - based on the aircraft tags, figure
* out a plausible ICAO category. Returns cat A if nothing better could
* be determined.
* @return a string containing a single ICAO category character A..E
*/
std::string heuristicCatergoryFromTags() const;
class Bracket
{
public:
Bracket(int atOrBelow, int climb, int descent, double speed, bool isMach = false) :
atOrBelowAltitudeFt(atOrBelow),
climbRateFPM(climb),
descentRateFPM(descent),
speedIASOrMach(speed),
speedIsMach(isMach)
{ }
int gsForAltitude(int altitude) const;
double climbTime(int alt1, int alt2) const;
double climbDistanceM(int alt1, int alt2) const;
double descendTime(int alt1, int alt2) const;
double descendDistanceM(int alt1, int alt2) const;
int atOrBelowAltitudeFt;
int climbRateFPM;
int descentRateFPM;
double speedIASOrMach;
bool speedIsMach = false;
};
using PerformanceVec = std::vector<Bracket>;
using BracketRange = std::pair<PerformanceVec::const_iterator, PerformanceVec::const_iterator>;
PerformanceVec::const_iterator bracketForAltitude(int altitude) const;
BracketRange rangeForAltitude(int lowAltitude, int highAltitude) const;
using TraversalFunc = std::function<void(const Bracket& bk, int alt1, int alt2)>;
void traverseAltitudeRange(int initialElevationFt, int targetElevationFt, TraversalFunc tf) const;
PerformanceVec _perfData;
};
}
#endif // AIRCRAFTPERFORMANCE_HXX

View File

@@ -0,0 +1,26 @@
include(FlightGearComponent)
set(SOURCES
controls.cxx
replay.cxx
flightrecorder.cxx
FlightHistory.cxx
initialstate.cxx
AircraftPerformance.cxx
replay-internal.cxx
continuous.cxx
)
set(HEADERS
controls.hxx
replay.hxx
flightrecorder.hxx
FlightHistory.hxx
initialstate.hxx
AircraftPerformance.hxx
continuous.hxx
replay-internal.hxx
)
flightgear_component(Aircraft "${SOURCES}" "${HEADERS}")

View File

@@ -0,0 +1,229 @@
// FlightHistory
//
// Written by James Turner, started December 2012.
//
// Copyright (C) 2012 James Turner - zakalawe (at) mac com
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation; either version 2 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
//
///////////////////////////////////////////////////////////////////////////////
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
#include "FlightHistory.hxx"
#include <algorithm>
#include <simgear/sg_inlines.h>
#include <simgear/debug/logstream.hxx>
#include <simgear/props/props_io.hxx>
#include <simgear/misc/strutils.hxx>
#include <simgear/structure/exception.hxx>
#include <simgear/math/SGMath.hxx>
#include <Main/fg_props.hxx>
#include <Main/globals.hxx>
FGFlightHistory::FGFlightHistory() :
m_sampleInterval(5.0),
m_validSampleCount(SAMPLE_BUCKET_WIDTH)
{
}
FGFlightHistory::~FGFlightHistory()
{
}
void FGFlightHistory::init()
{
m_enabled = fgGetNode("/sim/history/enabled", true);
m_sampleInterval = fgGetDouble("/sim/history/sample-interval-sec", 1.0);
if (m_sampleInterval <= 0.0) { // would be bad
SG_LOG(SG_FLIGHT, SG_INFO, "invalid flight-history sample interval:" << m_sampleInterval
<< ", defaulting to " << m_sampleInterval);
m_sampleInterval = 1.0;
}
// cap memory use at 4MB
m_maxMemoryUseBytes = fgGetInt("/sim/history/max-memory-use-bytes", 1024 * 1024 * 4);
m_weightOnWheels = NULL;
// reset the history when we detect a take-off
if (fgGetBool("/sim/history/clear-on-takeoff", true)) {
m_weightOnWheels = fgGetNode("/gear/gear[1]/wow", 0, true);
m_lastWoW = m_weightOnWheels->getBoolValue();
}
// force bucket re-allocation
m_validSampleCount = SAMPLE_BUCKET_WIDTH;
m_lastCaptureTime = globals->get_sim_time_sec();
}
void FGFlightHistory::shutdown()
{
clear();
}
void FGFlightHistory::reinit()
{
shutdown();
init();
}
void FGFlightHistory::update(double dt)
{
if ((dt == 0.0) || !m_enabled->getBoolValue()) {
return; // paused or disabled
}
if (m_weightOnWheels) {
if (m_lastWoW && !m_weightOnWheels->getBoolValue()) {
SG_LOG(SG_FLIGHT, SG_INFO, "history: detected main-gear takeoff, clearing history");
clear();
}
} // of rest-on-takeoff enabled
// spatial check - moved at least 1m since last capture
if (!m_buckets.empty()) {
SGVec3d lastCaptureCart(SGVec3d::fromGeod(m_buckets.back()->samples[m_validSampleCount - 1].position));
double d2 = distSqr(lastCaptureCart, globals->get_aircraft_position_cart());
if (d2 <= 1.0) {
return;
}
}
double elapsed = globals->get_sim_time_sec() - m_lastCaptureTime;
if (elapsed > m_sampleInterval) {
capture();
}
}
void FGFlightHistory::allocateNewBucket()
{
SampleBucket* bucket = NULL;
if (!m_buckets.empty() && (currentMemoryUseBytes() > m_maxMemoryUseBytes)) {
bucket = m_buckets.front();
m_buckets.erase(m_buckets.begin());
} else {
bucket = new SampleBucket;
}
m_buckets.push_back(bucket);
m_validSampleCount = 0;
}
void FGFlightHistory::capture()
{
if (m_validSampleCount == SAMPLE_BUCKET_WIDTH) {
// bucket is full, allocate a new one
allocateNewBucket();
}
m_lastCaptureTime = globals->get_sim_time_sec();
Sample* sample = m_buckets.back()->samples + m_validSampleCount;
sample->simTimeMSec = static_cast<size_t>(m_lastCaptureTime * 1000.0);
sample->position = globals->get_aircraft_position();
double heading, pitch, roll;
globals->get_aircraft_orientation(heading, pitch, roll);
sample->heading = static_cast<float>(heading);
sample->pitch = static_cast<float>(pitch);
sample->roll = static_cast<float>(roll);
++m_validSampleCount;
}
PagedPathForHistory_ptr FGFlightHistory::pagedPathForHistory(size_t max_entries, size_t newerThan ) const
{
PagedPathForHistory_ptr result = new PagedPathForHistory();
if (m_buckets.empty()) {
return result;
}
for (auto bucket : m_buckets) {
unsigned int count = (bucket == m_buckets.back() ? m_validSampleCount : SAMPLE_BUCKET_WIDTH);
// iterate over all the valid samples in the bucket
for (unsigned int index = 0; index < count; ++index) {
// skip older entries
// TODO: bisect!
if( bucket->samples[index].simTimeMSec <= newerThan )
continue;
if( max_entries ) {
max_entries--;
SGGeod g = bucket->samples[index].position;
result->path.push_back(g);
result->last_seen = bucket->samples[index].simTimeMSec;
} else {
goto exit;
}
} // of samples iteration
} // of buckets iteration
exit:
return result;
}
SGGeodVec FGFlightHistory::pathForHistory(double minEdgeLengthM) const
{
SGGeodVec result;
if (m_buckets.empty()) {
return result;
}
result.push_back(m_buckets.front()->samples[0].position);
SGVec3d lastOutputCart = SGVec3d::fromGeod(result.back());
double minLengthSqr = minEdgeLengthM * minEdgeLengthM;
for (auto bucket : m_buckets) {
unsigned int count = (bucket == m_buckets.back() ? m_validSampleCount : SAMPLE_BUCKET_WIDTH);
// iterate over all the valid samples in the bucket
for (unsigned int index = 0; index < count; ++index) {
SGGeod g = bucket->samples[index].position;
SGVec3d cart(SGVec3d::fromGeod(g));
if (distSqr(cart, lastOutputCart) > minLengthSqr) {
lastOutputCart = cart;
result.push_back(g);
}
} // of samples iteration
} // of buckets iteration
return result;
}
void FGFlightHistory::clear()
{
for (auto ptr : m_buckets) {
delete ptr;
}
m_buckets.clear();
m_validSampleCount = SAMPLE_BUCKET_WIDTH;
}
size_t FGFlightHistory::currentMemoryUseBytes() const
{
return sizeof(SampleBucket) * m_buckets.size();
}
// Register the subsystem.
SGSubsystemMgr::Registrant<FGFlightHistory> registrantFGFlightHistory;

View File

@@ -0,0 +1,132 @@
// FlightHistory
//
// Written by James Turner, started December 2012.
//
// Copyright (C) 2012 James Turner - zakalawe (at) mac com
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation; either version 2 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
//
///////////////////////////////////////////////////////////////////////////////
#ifndef FG_AIRCRAFT_FLIGHT_HISTORY_HXX
#define FG_AIRCRAFT_FLIGHT_HISTORY_HXX
#include <simgear/structure/subsystem_mgr.hxx>
#include <simgear/props/props.hxx>
#include <simgear/math/SGMath.hxx>
#include <vector>
typedef std::vector<SGGeod> SGGeodVec;
class PagedPathForHistory : public SGReferenced
{
public:
PagedPathForHistory() : last_seen(0) {}
virtual ~PagedPathForHistory() {}
SGGeodVec path;
time_t last_seen;
};
typedef SGSharedPtr<PagedPathForHistory> PagedPathForHistory_ptr;
const unsigned int SAMPLE_BUCKET_WIDTH = 1024;
/**
* record the history of the aircraft's movements, making it available
* as a contiguous block. This can be used to show the historical flight-path
* over a long period of time (unlike the replay system), but only a small,
* fixed set of properties are recorded. (Positioned and orientation, but
* not velocity, acceleration, control inputs, or so on)
*/
class FGFlightHistory : public SGSubsystem
{
public:
FGFlightHistory();
virtual ~FGFlightHistory();
// Subsystem API.
void init() override;
void reinit() override;
void shutdown() override;
void update(double dt) override;
// Subsystem identification.
static const char* staticSubsystemClassId() { return "history"; }
PagedPathForHistory_ptr pagedPathForHistory(size_t max_entries, size_t newerThan = 0) const;
/**
* retrieve the path, collapsing segments shorter than
* the specified minimum length
*/
SGGeodVec pathForHistory(double minEdgeLengthM = 50.0) const;
/**
* clear the history
*/
void clear();
private:
/**
* @class A single data sample in the history system.
*/
class Sample
{
public:
SGGeod position;
/// heading, pitch and roll can be recorded at lower precision
/// than a double - actually 16 bits might be sufficient
float heading, pitch, roll;
size_t simTimeMSec;
};
/**
* Bucket is a fixed-size container of samples. This is a crude slab
* allocation of samples, in chunks defined by the width constant above.
* Keep in mind that even with a 1Hz sample frequency, we use less than
* 200kbytes per hour - avoiding continous malloc traffic, or expensive
* std::vector reallocations, is the key factor here.
*/
class SampleBucket
{
public:
Sample samples[SAMPLE_BUCKET_WIDTH];
};
double m_lastCaptureTime;
double m_sampleInterval; ///< sample interval in seconds
/// our store of samples (in buckets). The last bucket is partially full,
/// with the number of valid samples indicated by m_validSampleCount
std::vector<SampleBucket*> m_buckets;
/// number of valid samples in the final bucket
unsigned int m_validSampleCount;
SGPropertyNode_ptr m_weightOnWheels;
SGPropertyNode_ptr m_enabled;
bool m_lastWoW;
size_t m_maxMemoryUseBytes;
void allocateNewBucket();
void capture();
size_t currentMemoryUseBytes() const;
};
#endif

987
src/Aircraft/continuous.cxx Normal file
View File

@@ -0,0 +1,987 @@
#include "continuous.hxx"
#include <Aircraft/flightrecorder.hxx>
#include <Main/fg_props.hxx>
#include <MultiPlayer/mpmessages.hxx>
#include <Viewer/FGEventHandler.hxx>
#include <Viewer/renderer.hxx>
#include <Viewer/viewmgr.hxx>
#include <simgear/io/iostreams/zlibstream.hxx>
#include <simgear/props/props_io.hxx>
#include <simgear/structure/commands.hxx>
#include <osgViewer/ViewerBase>
#include <assert.h>
#include <string.h>
Continuous::Continuous(std::shared_ptr<FGFlightRecorder> flight_recorder)
:
m_flight_recorder(flight_recorder)
{
SGPropertyNode* record_continuous = fgGetNode("/sim/replay/record-continuous", true);
SGPropertyNode* fdm_initialized = fgGetNode("/sim/signals/fdm-initialized", true);
record_continuous->addChangeListener(this, true /*initial*/);
fdm_initialized->addChangeListener(this, true /*initial*/);
}
// Reads binary data from a stream into an instance of a type.
template<typename T>
static void readRaw(std::istream& in, T& data)
{
in.read(reinterpret_cast<char*>(&data), sizeof(data));
}
// Writes instance of a type as binary data to a stream.
template<typename T>
static void writeRaw(std::ostream& out, const T& data)
{
out.write(reinterpret_cast<const char*>(&data), sizeof(data));
}
// Reads uncompressed vector<char> from file. Throws if length field is longer
// than <max_length>.
template<typename SizeType>
static SizeType VectorRead(std::istream& in, std::vector<char>& out, uint32_t max_length=(1u << 31))
{
SizeType length;
readRaw(in, length);
if (sizeof(length) + length > max_length)
{
SG_LOG(SG_SYSTEMS, SG_ALERT, "recording data vector too long."
<< " max_length=" << max_length
<< " sizeof(length)=" << sizeof(length)
<< " length=" << length
);
throw std::runtime_error("Failed to read vector in recording");
}
out.resize(length);
in.read(&out.front(), length);
return sizeof(length) + length;
}
static int16_t read_int16(std::istream& in, size_t& pos)
{
int16_t a;
readRaw(in, a);
pos += sizeof(a);
return a;
}
static std::string read_string(std::istream& in, size_t& pos)
{
int16_t length = read_int16(in, pos);
std::vector<char> path(length);
in.read(&path[0], length);
pos += length;
std::string ret(&path[0], length);
return ret;
}
static int PropertiesWrite(SGPropertyNode* root, std::ostream& out)
{
stringstream buffer;
writeProperties(buffer, root, true /*write_all*/);
uint32_t buffer_len = buffer.str().size() + 1;
writeRaw(out, buffer_len);
out.write(buffer.str().c_str(), buffer_len);
return 0;
}
// Reads extra-property change items in next <length> bytes. Throws if we don't
// exactly read <length> bytes.
static void ReadFGReplayDataExtraProperties(std::istream& in, FGReplayData* replay_data, uint32_t length)
{
SG_LOG(SG_SYSTEMS, SG_BULK, "reading extra-properties. length=" << length);
size_t pos=0;
for(;;)
{
if (pos == length)
{
break;
}
if (pos > length)
{
SG_LOG(SG_SYSTEMS, SG_ALERT, "Overrun while reading extra-properties:"
" length=" << length << ": pos=" << pos);
in.setstate(std::ios_base::failbit);
break;
}
SG_LOG(SG_SYSTEMS, SG_BULK, "length=" << length<< " pos=" << pos);
std::string path = read_string(in, pos);
if (path == "")
{
path = read_string(in, pos);
SG_LOG(SG_SYSTEMS, SG_DEBUG, "property deleted: " << path);
replay_data->replay_extra_property_removals.push_back(path);
}
else
{
std::string value = read_string(in, pos);
SG_LOG(SG_SYSTEMS, SG_DEBUG, "property changed: " << path << "=" << value);
replay_data->replay_extra_property_changes[path] = value;
}
}
}
static bool ReadFGReplayData2(
std::istream& in,
SGPropertyNode* config,
bool load_signals,
bool load_multiplayer,
bool load_extra_properties,
FGReplayData* ret
)
{
ret->raw_data.resize(0);
for (auto data: config->getChildren("data"))
{
std::string data_type = data->getStringValue();
SG_LOG(SG_SYSTEMS, SG_BULK, "in.tellg()=" << in.tellg() << " data_type=" << data_type);
uint32_t length;
readRaw(in, length);
SG_LOG(SG_SYSTEMS, SG_DEBUG, "length=" << length);
if (!in) break;
if (load_signals && data_type == "signals")
{
ret->raw_data.resize(length);
in.read(&ret->raw_data.front(), ret->raw_data.size());
}
else if (load_multiplayer && data_type == "multiplayer")
{
/* Multiplayer information is a vector of vectors. */
ret->multiplayer_messages.clear();
uint32_t pos = 0;
for(;;)
{
assert(pos <= length);
if (pos == length) break;
std::shared_ptr<std::vector<char>> v(new std::vector<char>);
ret->multiplayer_messages.push_back(v);
pos += VectorRead<uint16_t>(in, *ret->multiplayer_messages.back(), length - pos);
SG_LOG(SG_SYSTEMS, SG_BULK, "replaying multiplayer data"
<< " ret->sim_time=" << ret->sim_time
<< " length=" << length
<< " pos=" << pos
<< " callsign=" << ((T_MsgHdr*) &v->front())->Callsign
);
}
}
else if (load_extra_properties && data_type == "extra-properties")
{
ReadFGReplayDataExtraProperties(in, ret, length);
}
else
{
SG_LOG(SG_GENERAL, SG_BULK, "Skipping unrecognised/unwanted data: " << data_type);
in.seekg(length, std::ios_base::cur);
}
if (!in) break;
}
if (!in)
{
SG_LOG(SG_SYSTEMS, SG_DEBUG, "Failed to read fgtape data");
return false;
}
return true;
}
/* Removes items more than <n> away from <it>. <n> can be -ve. */
template<typename Container, typename Iterator>
static void remove_far_away(Container& container, Iterator it, int n)
{
SG_LOG(SG_GENERAL, SG_DEBUG, "container.size()=" << container.size());
if (n > 0)
{
for (int i=0; i<n; ++i)
{
if (it == container.end()) return;
++it;
}
container.erase(it, container.end());
}
else
{
for (int i=0; i<-n-1; ++i)
{
if (it == container.begin()) return;
--it;
}
container.erase(container.begin(), it);
}
SG_LOG(SG_GENERAL, SG_DEBUG, "container.size()=" << container.size());
}
/* Returns FGReplayData for frame at specified position in file. Uses
continuous.m_in_pos_to_frame as a cache, and trims this cache using
remove_far_away(). */
static std::shared_ptr<FGReplayData> ReadFGReplayData(
Continuous& continuous,
std::ifstream& in,
size_t pos,
SGPropertyNode* config,
bool load_signals,
bool load_multiplayer,
bool load_extra_properties,
int in_compression
)
{
std::shared_ptr<FGReplayData> ret;
auto it = continuous.m_in_pos_to_frame.find(pos);
if (it != continuous.m_in_pos_to_frame.end())
{
if (0
|| (load_signals && !it->second->load_signals)
|| (load_multiplayer && !it->second->load_multiplayer)
|| (load_extra_properties && !it->second->load_extra_properties)
)
{
/* This frame is in the continuous.m_in_pos_to_frame cache, but
doesn't contain all of the required items, so we need to reload. */
continuous.m_in_pos_to_frame.erase(it);
it = continuous.m_in_pos_to_frame.end();
}
}
if (it == continuous.m_in_pos_to_frame.end())
{
/* Load FGReplayData at offset <pos>.
We need to clear any eof bit, otherwise seekg() will not work (which is
pretty unhelpful). E.g. see:
https://stackoverflow.com/questions/16364301/whats-wrong-with-the-ifstream-seekg
*/
SG_LOG(SG_SYSTEMS, SG_BULK, "reading frame. pos=" << pos);
in.clear();
in.seekg(pos);
ret.reset(new FGReplayData);
readRaw(in, ret->sim_time);
if (!in)
{
SG_LOG(SG_SYSTEMS, SG_DEBUG, "Failed to read fgtape frame at offset " << pos);
return nullptr;
}
bool ok;
if (in_compression)
{
uint8_t flags;
uint32_t compressed_size;
in.read((char*) &flags, sizeof(flags));
in.read((char*) &compressed_size, sizeof(compressed_size));
simgear::ZlibDecompressorIStream in_decompress(in, SGPath(), simgear::ZLibCompressionFormat::ZLIB_RAW);
ok = ReadFGReplayData2(in_decompress, config, load_signals, load_multiplayer, load_extra_properties, ret.get());
}
else
{
ok = ReadFGReplayData2(in, config, load_signals, load_multiplayer, load_extra_properties, ret.get());
}
if (!ok)
{
SG_LOG(SG_SYSTEMS, SG_DEBUG, "Failed to read fgtape frame at offset " << pos);
return nullptr;
}
it = continuous.m_in_pos_to_frame.lower_bound(pos);
it = continuous.m_in_pos_to_frame.insert(it, std::make_pair(pos, ret));
/* Delete faraway items. */
size_t size_old = continuous.m_in_pos_to_frame.size();
int n = 2;
size_t size_max = 2*n - 1;
remove_far_away(continuous.m_in_pos_to_frame, it, n);
remove_far_away(continuous.m_in_pos_to_frame, it, -n);
size_t size_new = continuous.m_in_pos_to_frame.size();
SG_LOG(SG_GENERAL, SG_DEBUG, ""
<< " n=" << size_old
<< " size_max=" << size_max
<< " size_old=" << size_old
<< " size_new=" << size_new
);
assert(size_new <= size_max);
}
else
{
ret = it->second;
}
return ret;
}
// streambuf that compresses using deflate().
struct compression_streambuf : std::streambuf
{
compression_streambuf(
std::ostream& out,
size_t buffer_uncompressed_size,
size_t buffer_compressed_size
)
:
std::streambuf(),
out(out),
buffer_uncompressed(new char[buffer_uncompressed_size]),
buffer_uncompressed_size(buffer_uncompressed_size),
buffer_compressed(new char[buffer_compressed_size]),
buffer_compressed_size(buffer_compressed_size)
{
zstream.zalloc = nullptr;
zstream.zfree = nullptr;
zstream.opaque = nullptr;
zstream.next_in = nullptr;
zstream.avail_in = 0;
zstream.next_out = (unsigned char*) &buffer_compressed[0];
zstream.avail_out = buffer_compressed_size;
int e = deflateInit2(
&zstream,
Z_DEFAULT_COMPRESSION,
Z_DEFLATED,
-15 /*windowBits*/,
8 /*memLevel*/,
Z_DEFAULT_STRATEGY
);
if (e != Z_OK)
{
throw std::runtime_error("deflateInit2() failed");
}
// We leave space for one character to simplify overflow().
setp(&buffer_uncompressed[0], &buffer_uncompressed[0] + buffer_uncompressed_size - 1);
}
// Flush compressed data to .out and reset zstream.next_out.
void _flush()
{
// Send all data in .buffer_compressed to .out.
size_t n = (char*) zstream.next_out - &buffer_compressed[0];
out.write(&buffer_compressed[0], n);
zstream.next_out = (unsigned char*) &buffer_compressed[0];
zstream.avail_out = buffer_compressed_size;
}
// Compresses specified bytes from buffer_uncompressed into
// buffer_compressed, flushing to .out as necessary. Returns true if we get
// EOF writing to .out.
bool _deflate(size_t n, bool flush)
{
assert(this->pbase() == &buffer_uncompressed[0]);
zstream.next_in = (unsigned char*) &buffer_uncompressed[0];
zstream.avail_in = n;
for(;;)
{
if (!flush && !zstream.avail_in) break;
if (!zstream.avail_out) _flush();
int e = deflate(&zstream, (!zstream.avail_in && flush) ? Z_FINISH : Z_NO_FLUSH);
if (e != Z_OK && e != Z_STREAM_END)
{
throw std::runtime_error("zip_deflate() failed");
}
if (e == Z_STREAM_END) break;
}
if (flush) _flush();
// We leave space for one character to simplify overflow().
setp(&buffer_uncompressed[0], &buffer_uncompressed[0] + buffer_uncompressed_size - 1);
if (!out) return true; // EOF.
return false;
}
int overflow(int c) override
{
// We've deliberately left space for one character, into which we write <c>.
assert(this->pptr() == &buffer_uncompressed[0] + buffer_uncompressed_size - 1);
*this->pptr() = (char) c;
if (_deflate(buffer_uncompressed_size, false /*flush*/)) return EOF;
return c;
}
int sync() override
{
_deflate(pptr() - &buffer_uncompressed[0], true /*flush*/);
return 0;
}
~compression_streambuf()
{
deflateEnd(&zstream);
}
std::ostream& out;
z_stream zstream;
std::unique_ptr<char[]> buffer_uncompressed;
size_t buffer_uncompressed_size;
std::unique_ptr<char[]> buffer_compressed;
size_t buffer_compressed_size;
};
// Accepts uncompressed data via .write(), operator<< etc, and writes
// compressed data to the supplied std::ostream.
struct compression_ostream : std::ostream
{
compression_ostream(
std::ostream& out,
size_t buffer_uncompressed_size,
size_t buffer_compressed_size
)
:
std::ostream(&streambuf),
streambuf(out, buffer_uncompressed_size, buffer_compressed_size)
{
}
compression_streambuf streambuf;
};
static void writeFrame2(FGReplayData* r, std::ostream& out, SGPropertyNode_ptr config)
{
for (auto data: config->getChildren("data"))
{
std::string data_type = data->getStringValue();
if (data_type == "signals")
{
uint32_t signals_size = r->raw_data.size();
writeRaw(out, signals_size);
out.write(&r->raw_data.front(), r->raw_data.size());
}
else if (data_type == "multiplayer")
{
uint32_t length = 0;
for (auto message: r->multiplayer_messages)
{
length += sizeof(uint16_t) + message->size();
}
SG_LOG(SG_SYSTEMS, SG_DEBUG, "data_type=" << data_type << " out.tellp()=" << out.tellp()
<< " length=" << length);
writeRaw(out, length);
for (auto message: r->multiplayer_messages)
{
uint16_t message_size = message->size();
writeRaw(out, message_size);
out.write(&message->front(), message_size);
}
}
else if (data_type == "extra-properties")
{
uint32_t length = r->extra_properties.size();
SG_LOG(SG_SYSTEMS, SG_DEBUG, "data_type=" << data_type << " out.tellp()=" << out.tellp()
<< " length=" << length);
writeRaw(out, length);
out.write(&r->extra_properties[0], length);
}
else
{
SG_LOG(SG_SYSTEMS, SG_ALERT, "unrecognised data_type=" << data_type);
assert(0);
}
}
}
bool continuousWriteFrame(
Continuous& continuous,
FGReplayData* r,
std::ostream& out,
SGPropertyNode_ptr config,
FGTapeType tape_type
)
{
SG_LOG(SG_SYSTEMS, SG_BULK, "writing frame."
<< " out.tellp()=" << out.tellp()
<< " r->sim_time=" << r->sim_time
);
// Don't write frame if no data to write.
//bool r_has_data = false;
bool has_signals = false;
bool has_multiplayer = false;
bool has_extra_properties = false;
for (auto data: config->getChildren("data"))
{
std::string data_type = data->getStringValue();
if (data_type == "signals")
{
has_signals = true;
}
else if (data_type == "multiplayer")
{
if (!r->multiplayer_messages.empty())
{
has_multiplayer = true;
}
}
else if (data_type == "extra-properties")
{
if (!r->extra_properties.empty())
{
has_extra_properties = true;
}
}
else
{
SG_LOG(SG_SYSTEMS, SG_ALERT, "unrecognised data_type=" << data_type);
assert(0);
}
}
if (!has_signals && !has_multiplayer && !has_extra_properties)
{
SG_LOG(SG_SYSTEMS, SG_DEBUG, "Not writing frame because no data to write");
return true;
}
writeRaw(out, r->sim_time);
if (tape_type == FGTapeType_CONTINUOUS && continuous.m_out_compression)
{
uint8_t flags = 0;
if (has_signals) flags |= 1;
if (has_multiplayer) flags |= 2;
if (has_extra_properties) flags |= 4;
out.write((char*) &flags, sizeof(flags));
/* We need to first write the size of the compressed data so compress
to a temporary ostringstream first. */
std::ostringstream compressed;
compression_ostream out_compressing(compressed, 1024, 1024);
writeFrame2(r, out_compressing, config);
out_compressing.flush();
uint32_t compressed_size = compressed.str().size();
out.write((char*) &compressed_size, sizeof(compressed_size));
out.write((char*) compressed.str().c_str(), compressed.str().size());
}
else
{
writeFrame2(r, out, config);
}
bool ok = true;
if (!out) ok = false;
return ok;
}
SGPropertyNode_ptr continuousWriteHeader(
Continuous& continuous,
FGFlightRecorder* flight_recorder,
std::ofstream& out,
const SGPath& path,
FGTapeType tape_type
)
{
continuous.m_out_compression = fgGetInt("/sim/replay/record-continuous-compression");
SGPropertyNode_ptr config = saveSetup(NULL /*Extra*/, path, 0 /*Duration*/,
tape_type, continuous.m_out_compression);
SGPropertyNode* signals = config->getNode("signals", true /*create*/);
flight_recorder->getConfig(signals);
out.open(path.c_str(), std::ofstream::binary | std::ofstream::trunc);
out.write(FlightRecorderFileMagic, strlen(FlightRecorderFileMagic)+1);
PropertiesWrite(config, out);
if (tape_type == FGTapeType_CONTINUOUS)
{
// Ensure that all recorded properties are written in first frame.
//
flight_recorder->resetExtraProperties();
}
if (!out)
{
out.close();
config = nullptr;
}
return config;
}
/* Replays one frame from Continuous recording. <offset> and <offset_old> are
offsets in file of frames that are >= and < <time> respectively. <offset_old>
may be 0, in which case it is ignored.
We load the frame(s) from disc, omitting some data depending on
replay_signals, replay_multiplayer and replay_extra_properties. Then call
m_pRecorder->replay(), which updates the global state.
Returns true on success, otherwise we failed to read from Continuous recording.
*/
static bool replayContinuousInternal(
Continuous& continuous,
FGFlightRecorder* recorder,
double time,
size_t offset,
size_t offset_old,
bool replay_signals,
bool replay_multiplayer,
bool replay_extra_properties,
int* xpos,
int* ypos,
int* xsize,
int* ysize
)
{
std::shared_ptr<FGReplayData> replay_data = ReadFGReplayData(
continuous,
continuous.m_in,
offset,
continuous.m_in_config,
replay_signals,
replay_multiplayer,
replay_extra_properties,
continuous.m_in_compression
);
if (!replay_data)
{
SG_LOG(SG_SYSTEMS, SG_DEBUG, "Failed to read fgtape frame at offset=" << offset << " time=" << time);
return false;
}
assert(replay_data.get());
std::shared_ptr<FGReplayData> replay_data_old;
if (offset_old)
{
replay_data_old = ReadFGReplayData(
continuous,
continuous.m_in,
offset_old,
continuous.m_in_config,
replay_signals,
replay_multiplayer,
replay_extra_properties,
continuous.m_in_compression
);
}
if (replay_extra_properties) SG_LOG(SG_SYSTEMS, SG_DEBUG,
"replay():"
<< " time=" << time
<< " offset=" << offset
<< " offset_old=" << offset_old
<< " replay_data_old=" << replay_data_old
<< " replay_data->raw_data.size()=" << replay_data->raw_data.size()
<< " replay_data->multiplayer_messages.size()=" << replay_data->multiplayer_messages.size()
<< " replay_data->extra_properties.size()=" << replay_data->extra_properties.size()
<< " replay_data->replay_extra_property_changes.size()=" << replay_data->replay_extra_property_changes.size()
);
recorder->replay(time, replay_data.get(), replay_data_old.get(), xpos, ypos, xsize, ysize);
return true;
}
// fixme: this is duplicated in replay.cxx.
static void popupTip(const char* message, int delay)
{
SGPropertyNode_ptr args(new SGPropertyNode);
args->setStringValue("label", message);
args->setIntValue("delay", delay);
globals->get_commands()->execute("show-message", args);
}
void continuous_replay_video_end(Continuous& continuous)
{
if (continuous.m_replay_create_video)
{
SG_LOG(SG_GENERAL, SG_ALERT, "Stopping replay create-video");
auto view_mgr = globals->get_subsystem<FGViewMgr>();
if (view_mgr)
{
view_mgr->video_stop();
}
continuous.m_replay_create_video = false;
}
if (continuous.m_replay_fixed_dt_prev != -1)
{
SG_LOG(SG_GENERAL, SG_ALERT, "Resetting fixed-dt to" << continuous.m_replay_fixed_dt_prev);
fgSetDouble("/sim/time/fixed-dt", continuous.m_replay_fixed_dt_prev);
continuous.m_replay_fixed_dt_prev = -1;
}
}
bool replayContinuous(FGReplayInternal& self, double time)
{
// We need to detect whether replay() updates the values for the main
// window's position and size.
int xpos0 = self.m_sim_startup_xpos->getIntValue();
int ypos0 = self.m_sim_startup_xpos->getIntValue();
int xsize0 = self.m_sim_startup_xpos->getIntValue();
int ysize0 = self.m_sim_startup_xpos->getIntValue();
int xpos = xpos0;
int ypos = ypos0;
int xsize = xsize0;
int ysize = ysize0;
double multiplayer_recent = 3;
// We replay all frames from just after the previously-replayed frame,
// in order to replay extra properties and multiplayer aircraft
// correctly.
//
double t_begin = self.m_continuous->m_in_frame_time_last;
if (time < self.m_continuous->m_in_time_last)
{
// We have gone backwards, e.g. user has clicked on the back
// buttons in the Replay dialogue.
//
if (self.m_continuous->m_in_multiplayer)
{
// Continuous recording has multiplayer data, so replay recent
// ones.
//
t_begin = time - multiplayer_recent;
}
if (self.m_continuous->m_in_extra_properties)
{
// Continuous recording has property changes. we need to replay
// all property changes from the beginning.
//
t_begin = -1;
}
SG_LOG(SG_SYSTEMS, SG_DEBUG, "Have gone backwards."
<< " m_in_time_last=" << self.m_continuous->m_in_time_last
<< " time=" << time
<< " t_begin=" << t_begin
<< " m_in_extra_properties=" << self.m_continuous->m_in_extra_properties
);
}
// Prepare to replay signals from Continuoue recording file. We want
// to find a pair of frames that straddle the requested <time> so that
// we can interpolate.
//
auto p = self.m_continuous->m_in_time_to_frameinfo.lower_bound(time);
bool ret = false;
size_t offset;
size_t offset_prev = 0;
if (p == self.m_continuous->m_in_time_to_frameinfo.end())
{
// We are at end of recording; replay last frame.
continuous_replay_video_end(*self.m_continuous);
--p;
offset = p->second.offset;
ret = true;
}
else if (p->first > time)
{
// Look for preceding item.
if (p == self.m_continuous->m_in_time_to_frameinfo.begin())
{
// <time> is before beginning of recording.
offset = p->second.offset;
}
else
{
// Interpolate between pair of items that straddle <time>.
auto prev = p;
--prev;
offset_prev = prev->second.offset;
offset = p->second.offset;
}
}
else
{
// Exact match.
offset = p->second.offset;
}
// Before interpolating signals, we replay all property changes from
// all frame times t satisfying t_prop_begin < t < time. We also replay
// all recent multiplayer packets in this range, i.e. for which t >
// time - multiplayer_recent.
//
// todo: figure out how to interpolate view position/direction, to
// smooth things out if replay fps is different from record fps e.g.
// with new fixed dt support.
//
for (auto p_before = self.m_continuous->m_in_time_to_frameinfo.upper_bound(t_begin);
p_before != self.m_continuous->m_in_time_to_frameinfo.end();
++p_before)
{
if (p_before->first >= p->first)
{
break;
}
// Replaying a frame is expensive because we read frame data
// from disc each time. So we only replay this frame if it has
// extra_properties, or if it has multiplayer packets and we are
// within <multiplayer_recent> seconds of current time.
//
bool replay_this_frame = p_before->second.has_extra_properties;
if (p_before->second.has_multiplayer && p_before->first > time - multiplayer_recent)
{
replay_this_frame = true;
}
SG_LOG(SG_SYSTEMS, SG_DEBUG, "Looking at extra property changes."
<< " replay_this_frame=" << replay_this_frame
<< " m_continuous->m_in_time_last=" << self.m_continuous->m_in_time_last
<< " m_continuous->m_in_frame_time_last=" << self.m_continuous->m_in_frame_time_last
<< " time=" << time
<< " t_begin=" << t_begin
<< " p_before->first=" << p_before->first
<< " p_before->second=" << p_before->second
);
if (replay_this_frame)
{
size_t pos_prev = 0;
if (p_before != self.m_continuous->m_in_time_to_frameinfo.begin())
{
auto p_before_prev = p_before;
--p_before_prev;
pos_prev = p_before_prev->second.offset;
}
bool ok = replayContinuousInternal(
*self.m_continuous,
self.m_flight_recorder.get(),
p_before->first,
p_before->second.offset,
pos_prev /*offset_old*/,
false /*replay_signals*/,
p_before->first > time - multiplayer_recent /*replay_multiplayer*/,
true /*replay_extra_properties*/,
&xpos,
&ypos,
&xsize,
&ysize
);
if (!ok)
{
if (!self.m_replay_error->getBoolValue())
{
SG_LOG(SG_SYSTEMS, SG_ALERT, "Replay failed: cannot read fgtape data");
popupTip("Replay failed: cannot read fgtape data", 10);
self.m_replay_error->setBoolValue(true);
}
return true;
}
}
}
/* Now replay signals, interpolating between frames atoffset_prev and
offset. */
bool ok = replayContinuousInternal(
*self.m_continuous,
self.m_flight_recorder.get(),
time,
offset,
offset_prev /*offset_old*/,
true /*replay_signals*/,
true /*replay_multiplayer*/,
true /*replay_extra_properties*/,
&xpos,
&ypos,
&xsize,
&ysize
);
if (!ok)
{
if (!self.m_replay_error->getBoolValue())
{
SG_LOG(SG_SYSTEMS, SG_ALERT, "Replay failed: cannot read fgtape data");
popupTip("Replay failed: cannot read fgtape data", 10);
self.m_replay_error->setBoolValue(true);
}
return true;
}
if (0
|| xpos != xpos0
|| ypos != ypos0
|| xsize != xsize0
|| ysize != ysize0
)
{
// Move/resize the main window to reflect the updated values.
globals->get_props()->setIntValue("/sim/startup/xpos", xpos);
globals->get_props()->setIntValue("/sim/startup/ypos", ypos);
globals->get_props()->setIntValue("/sim/startup/xsize", xsize);
globals->get_props()->setIntValue("/sim/startup/ysize", ysize);
osgViewer::ViewerBase* viewer_base = globals->get_renderer()->getViewerBase();
if (viewer_base)
{
std::vector<osgViewer::GraphicsWindow*> windows;
viewer_base->getWindows(windows);
osgViewer::GraphicsWindow* window = windows[0];
// We use FGEventHandler::setWindowRectangle() to move the
// window, because it knows how to convert from window work-area
// coordinates to window-including-furniture coordinates.
//
flightgear::FGEventHandler* event_handler = globals->get_renderer()->getEventHandler();
event_handler->setWindowRectangleInteriorWithCorrection(window, xpos, ypos, xsize, ysize);
}
}
self.m_continuous->m_in_time_last = time;
self.m_continuous->m_in_frame_time_last = p->first;
return ret;
}
/* SGPropertyChangeListener callback for detecing when FDM is initialised and
for when continuous recording is started or stopped. */
void Continuous::valueChanged(SGPropertyNode * node)
{
bool prop_continuous = fgGetBool("/sim/replay/record-continuous");
bool prop_fdm = fgGetBool("/sim/signals/fdm-initialized");
bool continuous = prop_continuous && prop_fdm;
if (continuous == (m_out.is_open() ? true : false))
{
// No change.
return;
}
if (m_out.is_open())
{
// Stop existing continuous recording.
SG_LOG(SG_SYSTEMS, SG_ALERT, "Stopping continuous recording");
m_out.close();
popupTip("Continuous record to file stopped", 5 /*delay*/);
}
if (continuous)
{
// Start continuous recording.
SGPath path_timeless;
SGPath path = makeSavePath(FGTapeType_CONTINUOUS, &path_timeless);
m_out_config = continuousWriteHeader(
*this,
m_flight_recorder.get(),
m_out,
path,
FGTapeType_CONTINUOUS
);
if (!m_out_config)
{
SG_LOG(SG_SYSTEMS, SG_ALERT, "Failed to start continuous recording");
popupTip("Continuous record to file failed to start", 5 /*delay*/);
return;
}
SG_LOG(SG_SYSTEMS, SG_ALERT, "Starting continuous recording");
/* Make a convenience link to the recording. E.g.
harrier-gr3-continuous.fgtape -> harrier-gr3-20201224-005034-continuous.fgtape.
Link destination is in same directory as link so we use leafname
path.file(). */
path_timeless.remove();
bool ok = path_timeless.makeLink(path.file());
if (!ok)
{
SG_LOG(SG_SYSTEMS, SG_ALERT, "Failed to create link " << path_timeless.c_str() << " => " << path.file());
}
SG_LOG(SG_SYSTEMS, SG_DEBUG, "Starting continuous recording to " << path);
if (m_out_compression)
{
popupTip("Continuous+compressed record to file started", 5 /*delay*/);
}
else
{
popupTip("Continuous record to file started", 5 /*delay*/);
}
}
}

View File

@@ -0,0 +1,97 @@
#pragma once
#include "replay-internal.hxx"
#include <simgear/props/props.hxx>
#include <fstream>
#include <mutex>
#include <thread>
struct Continuous : SGPropertyChangeListener
{
Continuous(std::shared_ptr<FGFlightRecorder> flight_recorder);
/* Callback for SGPropertyChangeListener. */
void valueChanged(SGPropertyNode * node) override;
std::shared_ptr<FGFlightRecorder> m_flight_recorder;
std::ifstream m_in;
bool m_in_multiplayer = false;
bool m_in_extra_properties = false;
std::mutex m_in_time_to_frameinfo_lock;
std::map<double, FGFrameInfo> m_in_time_to_frameinfo;
SGPropertyNode_ptr m_in_config;
double m_in_time_last = 0;
double m_in_frame_time_last = 0;
std::map<size_t, std::shared_ptr<FGReplayData>>
m_in_pos_to_frame;
std::ifstream m_indexing_in;
std::streampos m_indexing_pos;
bool m_replay_create_video = false;
double m_replay_fixed_dt = -1;
double m_replay_fixed_dt_prev = -1;
// Only used for gathering statistics that are then written into
// properties.
//
int m_num_frames_extra_properties = 0;
int m_num_frames_multiplayer = 0;
// For writing Continuous fgtape file.
SGPropertyNode_ptr m_out_config;
std::ofstream m_out;
int m_out_compression = 0;
int m_in_compression = 0;
};
/* Attempts to load Continuous recording header properties into
<properties>. If in is null we use internal std::fstream, otherwise we use *in.
Returns 0 on success, +1 if we may succeed after further download, or -1 if
recording is not a Continuous recording. */
int loadContinuousHeader(const std::string& path, std::istream* in, SGPropertyNode* properties);
/* Writes one frame of continuous record information. */
bool continuousWriteFrame(
Continuous& continuous,
FGReplayData* r,
std::ostream& out,
SGPropertyNode_ptr config,
FGTapeType tape_type
);
/* Opens continuous recording file and writes header.
If MetaData is unset, we initialise it by calling saveSetup(). Otherwise should
be already set up.
If Config is unset, we make it point to a new node populated by
m_pRecorder->getConfig(). Otherwise it should be already set up to point to
such information.
If path_override is not "", we use it as the path (instead of the path
determined by saveSetup(). */
SGPropertyNode_ptr continuousWriteHeader(
Continuous& continuous,
FGFlightRecorder* m_pRecorder,
std::ofstream& out,
const SGPath& path,
FGTapeType tape_type
);
/* Replays one frame from Continuous recording.
Returns true on success, otherwise we failed to read from Continuous recording.
*/
bool replayContinuous(FGReplayInternal& self, double time);
/* Stops any video recording that was started because of
continuous->m_replay_create_video. */
void continuous_replay_video_end(Continuous& continuous);

1840
src/Aircraft/controls.cxx Normal file

File diff suppressed because it is too large Load Diff

686
src/Aircraft/controls.hxx Normal file
View File

@@ -0,0 +1,686 @@
// controls.hxx -- defines a standard interface to all flight sim controls
//
// Written by Curtis Olson, started May 1997.
//
// Copyright (C) 1997 Curtis L. Olson - http://www.flightgear.org/~curt
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation; either version 2 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
//
// $Id$
#ifndef _CONTROLS_HXX
#define _CONTROLS_HXX
#include <simgear/structure/subsystem_mgr.hxx>
#include <simgear/props/tiedpropertylist.hxx>
// Define a structure containing the control parameters
class FGControls : public SGSubsystem
{
public:
enum {
ALL_ENGINES = -1,
MAX_ENGINES = 12
};
enum {
ALL_WHEELS = -1,
MAX_WHEELS = 3
};
enum {
ALL_TANKS = -1,
MAX_TANKS = 8
};
enum {
ALL_BOOSTPUMPS = -1,
MAX_BOOSTPUMPS = 2
};
enum {
ALL_HYD_SYSTEMS = -1,
MAX_HYD_SYSTEMS = 4
};
enum {
ALL_PACKS = -1,
MAX_PACKS = 4
};
enum {
ALL_LIGHTS = -1,
MAX_LIGHTS = 4
};
enum {
ALL_STATIONS = -1,
MAX_STATIONS = 12
};
enum {
ALL_AUTOPILOTS = -1,
MAX_AUTOPILOTS = 3
};
enum {
ALL_EJECTION_SEATS = -1,
MAX_EJECTION_SEATS = 10
};
enum {
SEAT_SAFED = -1,
SEAT_ARMED = 0,
SEAT_FAIL = 1
};
enum {
CMD_SEL_NORM = -1,
CMD_SEL_AFT = 0,
CMD_SEL_SOLO = 1
};
private:
// controls/flight/
double aileron;
double aileron_trim;
double elevator;
double elevator_trim;
double rudder;
double rudder_trim;
double flaps;
double slats;
bool BLC; // Boundary Layer Control
double spoilers;
double speedbrake;
double wing_sweep;
bool wing_fold;
bool drag_chute;
// controls/engines/
bool throttle_idle;
// controls/engines/engine[n]/
double throttle[MAX_ENGINES];
bool starter[MAX_ENGINES];
bool fuel_pump[MAX_ENGINES];
bool fire_switch[MAX_ENGINES];
bool fire_bottle_discharge[MAX_ENGINES];
bool cutoff[MAX_ENGINES];
double mixture[MAX_ENGINES];
double prop_advance[MAX_ENGINES];
int magnetos[MAX_ENGINES];
int feed_tank[MAX_ENGINES];
bool nitrous_injection[MAX_ENGINES]; // War Emergency Power
double cowl_flaps_norm[MAX_ENGINES];
bool feather[MAX_ENGINES];
int ignition[MAX_ENGINES];
bool augmentation[MAX_ENGINES];
bool reverser[MAX_ENGINES];
bool water_injection[MAX_ENGINES];
double condition[MAX_ENGINES]; // turboprop speed select
// controls/fuel/
bool dump_valve;
// controls/fuel/tank[n]/
bool fuel_selector[MAX_TANKS];
int to_engine[MAX_TANKS];
int to_tank[MAX_TANKS];
// controls/fuel/tank[n]/pump[p]/
bool boost_pump[MAX_TANKS * MAX_BOOSTPUMPS];
// controls/gear/
double brake_left;
double brake_right;
double copilot_brake_left;
double copilot_brake_right;
double brake_parking;
double steering;
bool nose_wheel_steering;
bool gear_down;
bool antiskid;
bool tailhook;
bool launchbar;
bool catapult_launch_cmd;
bool tailwheel_lock;
// controls/gear/wheel[n]/
bool alternate_extension[MAX_WHEELS];
// controls/anti-ice/
bool wing_heat;
bool pitot_heat;
int wiper;
bool window_heat;
// controls/anti-ice/engine[n]/
bool carb_heat[MAX_ENGINES];
bool inlet_heat[MAX_ENGINES];
// controls/hydraulic/system[n]/
bool engine_pump[MAX_HYD_SYSTEMS];
bool electric_pump[MAX_HYD_SYSTEMS];
// controls/electric/
bool battery_switch;
bool external_power;
bool APU_generator;
// controls/electric/engine[n]/
bool generator_breaker[MAX_ENGINES];
bool bus_tie[MAX_ENGINES];
// controls/pneumatic/
bool APU_bleed;
// controls/pneumatic/engine[n]/
bool engine_bleed[MAX_ENGINES];
// controls/pressurization/
int mode;
bool dump;
double outflow_valve;
// controls/pressurization/pack[n]/
bool pack_on[MAX_PACKS];
// controls/lighting/
bool landing_lights;
bool turn_off_lights;
bool taxi_light;
bool logo_lights;
bool nav_lights;
bool beacon;
bool strobe;
double panel_norm;
double instruments_norm;
double dome_norm;
// controls/armament/
bool master_arm;
int station_select;
bool release_ALL;
// controls/armament/station[n]/
int stick_size[MAX_STATIONS];
bool release_stick[MAX_STATIONS];
bool release_all[MAX_STATIONS];
bool jettison_all[MAX_STATIONS];
// controls/seat/
double vertical_adjust;
double fore_aft_adjust;
bool eject[MAX_EJECTION_SEATS];
int eseat_status[MAX_EJECTION_SEATS];
int cmd_selector_valve;
// controls/APU/
int off_start_run;
bool APU_fire_switch;
// controls/autoflight/autopilot[n]/
bool autopilot_engage[MAX_AUTOPILOTS];
// controls/autoflight/
bool autothrottle_arm;
bool autothrottle_engage;
double heading_select;
double altitude_select;
double bank_angle_select;
double vertical_speed_select;
double speed_select;
double mach_select;
int vertical_mode;
int lateral_mode;
SGPropertyNode_ptr auto_coordination;
SGPropertyNode_ptr auto_coordination_factor;
simgear::TiedPropertyList _tiedProperties;
// we need to node pointers as well, so we can manually
// fire valueChanged for these
SGPropertyNode_ptr _aileronNode;
SGPropertyNode_ptr _elevatorNode;
SGPropertyNode_ptr _aileronTrimNode;
SGPropertyNode_ptr _elevatorTrimNode;
SGPropertyNode_ptr _rudderNode;
simgear::PropertyList _engineThrottleNodes;
simgear::PropertyList _engineMixtureNodes;
simgear::PropertyList _engineStarterNodes;
simgear::PropertyList _engineCutoffNodes;
simgear::PropertyList _engineReverserNodes;
simgear::PropertyList _engineWaterInjectionNodes;
simgear::PropertyList _engineMagnetoNodes;
simgear::PropertyList _engineAugmentationNodes;
public:
FGControls();
~FGControls();
// Subsystem API.
void bind() override;
void init() override;
void reinit() override;
void unbind() override;
void update(double dt) override;
// Subsystem identification.
static const char* staticSubsystemClassId() { return "controls"; }
// Reset function
void reset_all(void);
// Query functions
// controls/flight/
inline double get_aileron() const { return aileron; }
inline double get_aileron_trim() const { return aileron_trim; }
inline double get_elevator() const { return elevator; }
inline double get_elevator_trim() const { return elevator_trim; }
inline double get_rudder() const { return rudder; }
inline double get_rudder_trim() const { return rudder_trim; }
inline double get_flaps() const { return flaps; }
inline double get_slats() const { return slats; }
inline bool get_BLC() const { return BLC; }
inline double get_spoilers() const { return spoilers; }
inline double get_speedbrake() const { return speedbrake; }
inline double get_wing_sweep() const { return wing_sweep; }
inline bool get_wing_fold() const { return wing_fold; }
inline bool get_drag_chute() const { return drag_chute; }
// controls/engines/
inline bool get_throttle_idle() const { return throttle_idle; }
// controls/engines/engine[n]/
inline double get_throttle(int engine) const { return throttle[engine]; }
inline bool get_starter(int engine) const { return starter[engine]; }
inline bool get_fuel_pump(int engine) const { return fuel_pump[engine]; }
inline bool get_fire_switch(int engine) const { return fire_switch[engine]; }
inline bool get_fire_bottle_discharge(int engine) const {
return fire_bottle_discharge[engine];
}
inline bool get_cutoff(int engine) const { return cutoff[engine]; }
inline double get_mixture(int engine) const { return mixture[engine]; }
inline double get_prop_advance(int engine) const {
return prop_advance[engine];
}
inline int get_magnetos(int engine) const { return magnetos[engine]; }
inline int get_feed_tank(int engine) const { return feed_tank[engine]; }
inline bool get_nitrous_injection(int engine) const {
return nitrous_injection[engine];
}
inline double get_cowl_flaps_norm(int engine) const {
return cowl_flaps_norm[engine];
}
inline bool get_feather(int engine) const { return feather[engine]; }
inline int get_ignition(int engine) const { return ignition[engine]; }
inline bool get_augmentation(int engine) const { return augmentation[engine]; }
inline bool get_reverser(int engine) const { return reverser[engine]; }
inline bool get_water_injection(int engine) const {
return water_injection[engine];
}
inline double get_condition(int engine) const { return condition[engine]; }
// controls/fuel/
inline bool get_dump_valve() const { return dump_valve; }
// controls/fuel/tank[n]/
inline bool get_fuel_selector(int tank) const {
return fuel_selector[tank];
}
inline int get_to_engine(int tank) const { return to_engine[tank]; }
inline int get_to_tank(int tank) const { return to_tank[tank]; }
// controls/fuel/tank[n]/pump[p]/
inline bool get_boost_pump(int index) const {
return boost_pump[index];
}
// controls/gear/
inline double get_brake_left() const { return brake_left; }
inline double get_brake_right() const { return brake_right; }
inline double get_copilot_brake_left() const { return copilot_brake_left; }
inline double get_copilot_brake_right() const { return copilot_brake_right; }
inline double get_brake_parking() const { return brake_parking; }
inline double get_steering() const { return steering; }
inline bool get_nose_wheel_steering() const { return nose_wheel_steering; }
inline bool get_gear_down() const { return gear_down; }
inline bool get_antiskid() const { return antiskid; }
inline bool get_tailhook() const { return tailhook; }
inline bool get_launchbar() const { return launchbar; }
inline bool get_catapult_launch_cmd() const { return catapult_launch_cmd; }
inline bool get_tailwheel_lock() const { return tailwheel_lock; }
// controls/gear/wheel[n]/
inline bool get_alternate_extension(int wheel) const {
return alternate_extension[wheel];
}
// controls/anti-ice/
inline bool get_wing_heat() const { return wing_heat; }
inline bool get_pitot_heat() const { return pitot_heat; }
inline int get_wiper() const { return wiper; }
inline bool get_window_heat() const { return window_heat; }
// controls/anti-ice/engine[n]/
inline bool get_carb_heat(int engine) const { return carb_heat[engine]; }
inline bool get_inlet_heat(int engine) const { return inlet_heat[engine]; }
// controls/hydraulic/system[n]/
inline bool get_engine_pump(int system) const { return engine_pump[system]; }
inline bool get_electric_pump(int system) const { return electric_pump[system]; }
// controls/electric/
inline bool get_battery_switch() const { return battery_switch; }
inline bool get_external_power() const { return external_power; }
inline bool get_APU_generator() const { return APU_generator; }
// controls/electric/engine[n]/
inline bool get_generator_breaker(int engine) const {
return generator_breaker[engine];
}
inline bool get_bus_tie(int engine) const { return bus_tie[engine]; }
// controls/pneumatic/
inline bool get_APU_bleed() const { return APU_bleed; }
// controls/pneumatic/engine[n]/
inline bool get_engine_bleed(int engine) const { return engine_bleed[engine]; }
// controls/pressurization/
inline int get_mode() const { return mode; }
inline double get_outflow_valve() const { return outflow_valve; }
inline bool get_dump() const { return dump; }
// controls/pressurization/pack[n]/
inline bool get_pack_on(int pack) const { return pack_on[pack]; }
// controls/lighting/
inline bool get_landing_lights() const { return landing_lights; }
inline bool get_turn_off_lights() const { return turn_off_lights; }
inline bool get_taxi_light() const { return taxi_light; }
inline bool get_logo_lights() const { return logo_lights; }
inline bool get_nav_lights() const { return nav_lights; }
inline bool get_beacon() const { return beacon; }
inline bool get_strobe() const { return strobe; }
inline double get_panel_norm() const { return panel_norm; }
inline double get_instruments_norm() const { return instruments_norm; }
inline double get_dome_norm() const { return dome_norm; }
// controls/armament/
inline bool get_master_arm() const { return master_arm; }
inline int get_station_select() const { return station_select; }
inline bool get_release_ALL() const { return release_ALL; }
// controls/armament/station[n]/
inline int get_stick_size(int station) const { return stick_size[station]; }
inline bool get_release_stick(int station) const { return release_stick[station]; }
inline bool get_release_all(int station) const { return release_all[station]; }
inline bool get_jettison_all(int station) const { return jettison_all[station]; }
// controls/seat/
inline double get_vertical_adjust() const { return vertical_adjust; }
inline double get_fore_aft_adjust() const { return fore_aft_adjust; }
inline bool get_ejection_seat( int which_seat ) const {
return eject[which_seat];
}
inline int get_eseat_status( int which_seat ) const {
return eseat_status[which_seat];
}
inline int get_cmd_selector_valve() const { return cmd_selector_valve; }
// controls/APU/
inline int get_off_start_run() const { return off_start_run; }
inline bool get_APU_fire_switch() const { return APU_fire_switch; }
// controls/autoflight/
inline bool get_autothrottle_arm() const { return autothrottle_arm; }
inline bool get_autothrottle_engage() const { return autothrottle_engage; }
inline double get_heading_select() const { return heading_select; }
inline double get_altitude_select() const { return altitude_select; }
inline double get_bank_angle_select() const { return bank_angle_select; }
inline double get_vertical_speed_select() const {
return vertical_speed_select;
}
inline double get_speed_select() const { return speed_select; }
inline double get_mach_select() const { return mach_select; }
inline int get_vertical_mode() const { return vertical_mode; }
inline int get_lateral_mode() const { return lateral_mode; }
// controls/autoflight/autopilot[n]/
inline bool get_autopilot_engage(int ap) const {
return autopilot_engage[ap];
}
void set_elevator( double pos );
void set_aileron_trim( double pos );
void set_elevator_trim( double pos );
void set_aileron( double pos );
void set_rudder( double pos );
void set_throttle( int engine, double pos );
void set_cutoff( int engine, bool val );
void set_augmentation( int engine, bool val );
void set_reverser( int engine, bool val );
void set_water_injection( int engine, bool val );
void set_magnetos( int engine, int pos );
void set_starter( int engine, bool flag );
void set_mixture( int engine, double pos );
private:
// IMPORTANT: do *not* make these setters public, or you will violate
// the listener-safety of them. If you need to make an accessor public,
// make these as 'inner', and make a public wrapper which correctly calls
// valueChanged (see, set_throttle, set_elevator etc for examples)
// Update functions
// controls/flight/
void _inner_set_aileron( double pos );
void move_aileron( double amt );
void _inner_set_aileron_trim( double pos );
void move_aileron_trim( double amt );
void _inner_set_elevator( double pos );
void move_elevator( double amt );
void _inner_set_elevator_trim( double pos );
void move_elevator_trim( double amt );
void _inner_set_rudder( double pos );
void move_rudder( double amt );
void set_rudder_trim( double pos );
void move_rudder_trim( double amt );
void set_flaps( double pos );
void move_flaps( double amt );
void set_slats( double pos );
void move_slats( double amt );
void set_BLC( bool val );
void set_spoilers( double pos );
void move_spoilers( double amt );
void set_speedbrake( double pos );
void move_speedbrake( double amt );
void set_wing_sweep( double pos );
void move_wing_sweep( double amt );
void set_wing_fold( bool val );
void set_drag_chute( bool val );
// controls/engines/
void set_throttle_idle( bool val );
// controls/engines/engine[n]/
void _inner_set_throttle( int engine, double pos );
void move_throttle( int engine, double amt );
void _inner_set_starter( int engine, bool flag );
void set_fuel_pump( int engine, bool val );
void set_fire_switch( int engine, bool val );
void set_fire_bottle_discharge( int engine, bool val );
void _inner_set_cutoff( int engine, bool val );
void _inner_set_mixture( int engine, double pos );
void move_mixture( int engine, double amt );
void set_prop_advance( int engine, double pos );
void move_prop_advance( int engine, double amt );
void _inner_set_magnetos( int engine, int pos );
void move_magnetos( int engine, int amt );
void set_feed_tank( int engine, int tank );
void set_nitrous_injection( int engine, bool val );
void set_cowl_flaps_norm( int engine, double pos );
void move_cowl_flaps_norm( int engine, double amt );
void set_feather( int engine, bool val );
void set_ignition( int engine, int val );
void _inner_set_augmentation( int engine, bool val );
void _inner_set_reverser( int engine, bool val );
void _inner_set_water_injection( int engine, bool val );
void set_condition( int engine, double val );
// controls/fuel
void set_dump_valve( bool val );
// controls/fuel/tank[n]/
void set_fuel_selector( int tank, bool pos );
void set_to_engine( int tank, int engine );
void set_to_tank( int tank, int dest_tank );
// controls/fuel/tank[n]/pump[p]
void set_boost_pump( int index, bool val );
// controls/gear/
void set_brake_left( double pos );
void move_brake_left( double amt );
void set_brake_right( double pos );
void move_brake_right( double amt );
void set_copilot_brake_left( double pos );
void set_copilot_brake_right( double pos );
void set_brake_parking( double pos );
void set_steering( double pos );
void move_steering( double amt );
void set_nose_wheel_steering( bool nws );
void set_gear_down( bool gear );
void set_antiskid( bool val );
void set_tailhook( bool val );
void set_launchbar( bool val );
void set_catapult_launch_cmd( bool val );
void set_tailwheel_lock( bool val );
// controls/gear/wheel[n]/
void set_alternate_extension( int wheel, bool val );
// controls/anti-ice/
void set_wing_heat( bool val );
void set_pitot_heat( bool val );
void set_wiper( int speed );
void set_window_heat( bool val );
// controls/anti-ice/engine[n]/
void set_carb_heat( int engine, bool val );
void set_inlet_heat( int engine, bool val );
// controls/hydraulic/system[n]/
void set_engine_pump( int system, bool val );
void set_electric_pump( int system, bool val );
// controls/electric/
void set_battery_switch( bool val );
void set_external_power( bool val );
void set_APU_generator( bool val );
// controls/electric/engine[n]/
void set_generator_breaker( int engine, bool val );
void set_bus_tie( int engine, bool val );
// controls/pneumatic/
void set_APU_bleed( bool val );
// controls/pneumatic/engine[n]/
void set_engine_bleed( int engine, bool val );
// controls/pressurization/
void set_mode( int mode );
void set_outflow_valve( double pos );
void move_outflow_valve( double amt );
void set_dump( bool val );
// controls/pressurization/pack[n]/
void set_pack_on( int pack, bool val );
// controls/lighting/
void set_landing_lights( bool val );
void set_turn_off_lights( bool val );
void set_taxi_light( bool val );
void set_logo_lights( bool val );
void set_nav_lights( bool val );
void set_beacon( bool val );
void set_strobe( bool val );
void set_panel_norm( double intensity );
void move_panel_norm( double amt );
void set_instruments_norm( double intensity );
void move_instruments_norm( double amt );
void set_dome_norm( double intensity );
void move_dome_norm( double amt );
// controls/armament/
void set_master_arm( bool val );
void set_station_select( int station );
void set_release_ALL( bool val );
// controls/armament/station[n]/
void set_stick_size( int station, int size );
void set_release_stick( int station, bool val );
void set_release_all( int station, bool val );
void set_jettison_all( int station, bool val );
// controls/seat/
void set_vertical_adjust( double pos );
void move_vertical_adjust( double amt );
void set_fore_aft_adjust( double pos );
void move_fore_aft_adjust( double amt );
void set_ejection_seat( int which_seat, bool val );
void set_eseat_status( int which_seat, int val );
void set_cmd_selector_valve( int val );
// controls/APU/
void set_off_start_run( int pos );
void set_APU_fire_switch( bool val );
// controls/autoflight/
void set_autothrottle_arm( bool val );
void set_autothrottle_engage( bool val );
void set_heading_select( double heading );
void move_heading_select( double amt );
void set_altitude_select( double altitude );
void move_altitude_select( double amt );
void set_bank_angle_select( double angle );
void move_bank_angle_select( double amt );
void set_vertical_speed_select( double vs );
void move_vertical_speed_select( double amt );
void set_speed_select( double speed );
void move_speed_select( double amt );
void set_mach_select( double mach );
void move_mach_select( double amt );
void set_vertical_mode( int mode );
void set_lateral_mode( int mode );
// controls/autoflight/autopilot[n]/
void set_autopilot_engage( int ap, bool val );
void do_autocoordination();
void fireEngineValueChanged(int index, simgear::PropertyList& props);
};
#endif // _CONTROLS_HXX

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,118 @@
// flightrecorder.hxx
//
// Written by Thorsten Brehm, started August 2011.
//
// Copyright (C) 2011 Thorsten Brehm - brehmt (at) gmail com
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation; either version 2 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
//
///////////////////////////////////////////////////////////////////////////////
#ifndef FLIGHTRECORDER_HXX_
#define FLIGHTRECORDER_HXX_
#include <simgear/props/props.hxx>
#include <MultiPlayer/multiplaymgr.hxx>
#include "replay-internal.hxx"
namespace FlightRecorder
{
typedef enum
{
discrete = 0, // no interpolation
linear = 1, // linear interpolation
angular_rad = 2, // angular interpolation, value in radians
angular_deg = 3 // angular interpolation, value in degrees
} TInterpolation;
typedef struct
{
SGPropertyNode_ptr Signal;
TInterpolation Interpolation;
} TCapture;
typedef std::vector<TCapture> TSignalList;
}
class FGFlightRecorder
{
public:
FGFlightRecorder(const char* pConfigName);
virtual ~FGFlightRecorder();
void reinit (void);
void reinit (SGPropertyNode_ptr ConfigNode);
FGReplayData* capture (double SimTime, FGReplayData* pRecycledBuffer);
// Updates main_window_* out-params if we find window move/resize events
// and replay of such events is enabled.
void replay (double SimTime, const FGReplayData* pNextBuffer,
const FGReplayData* pLastBuffer,
int* main_window_xpos,
int* main_window_ypos,
int* main_window_xsize,
int* main_window_ysize
);
int getRecordSize (void) { return m_TotalRecordSize;}
void getConfig (SGPropertyNode* root);
void resetExtraProperties();
private:
SGPropertyNode_ptr getDefault(void);
void initSignalList(const char* pSignalType, FlightRecorder::TSignalList& SignalList,
SGPropertyNode_ptr BaseNode);
void processSignalList(const char* pSignalType, FlightRecorder::TSignalList& SignalList,
SGPropertyNode_ptr SignalListNode,
std::string PropPrefix="", int Count = 1);
bool haveProperty(FlightRecorder::TSignalList& Capture,SGPropertyNode* pProperty);
bool haveProperty(SGPropertyNode* pProperty);
int getConfig(SGPropertyNode* root, const char* typeStr, const FlightRecorder::TSignalList& SignalList);
SGPropertyNode_ptr m_RecorderNode;
SGPropertyNode_ptr m_ConfigNode;
SGPropertyNode_ptr m_ReplayMultiplayer;
SGPropertyNode_ptr m_ReplayExtraProperties;
SGPropertyNode_ptr m_ReplayMainView;
SGPropertyNode_ptr m_ReplayMainWindowPosition;
SGPropertyNode_ptr m_ReplayMainWindowSize;
SGPropertyNode_ptr m_RecordContinuous;
SGPropertyNode_ptr m_RecordExtraProperties;
SGPropertyNode_ptr m_LogRawSpeed;
// This contains copy of all properties that we are recording, so that we
// can send only differences.
//
SGPropertyNode_ptr m_RecordExtraPropertiesReference;
FlightRecorder::TSignalList m_CaptureDouble;
FlightRecorder::TSignalList m_CaptureFloat;
FlightRecorder::TSignalList m_CaptureInteger;
FlightRecorder::TSignalList m_CaptureInt16;
FlightRecorder::TSignalList m_CaptureInt8;
FlightRecorder::TSignalList m_CaptureBool;
unsigned m_TotalRecordSize;
std::string m_ConfigName;
bool m_usingDefaultConfig;
FGMultiplayMgr* m_MultiplayMgr;
};
#endif /* FLIGHTRECORDER_HXX_ */

View File

@@ -0,0 +1,100 @@
// initialstate.cxx -- setup initial state of the aircraft
//
// Written by James Turner
//
// Copyright (C) 2016 James Turner <zakalawe@mac.com>
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation; either version 2 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
//
// $Id$
#include "config.h"
#include "initialstate.hxx"
#include <algorithm>
#include <simgear/debug/logstream.hxx>
#include <simgear/props/props_io.hxx>
#include <Main/fg_props.hxx>
#include <GUI/MessageBox.hxx>
using namespace simgear;
namespace {
class NodeValue
{
public:
NodeValue(const std::string& s) : v(s) {}
bool operator()(const SGPropertyNode_ptr n) const
{
return (v == n->getStringValue());
}
private:
std::string v;
};
SGPropertyNode_ptr nodeForState(const std::string& nm)
{
SGPropertyNode_ptr sim = fgGetNode("/sim");
const PropertyList& states = sim->getChildren("state");
PropertyList::const_iterator it;
for (it = states.begin(); it != states.end(); ++it) {
const PropertyList& names = (*it)->getChildren("name");
if (std::find_if(names.begin(), names.end(), NodeValue(nm)) != names.end()) {
return *it;
}
}
return SGPropertyNode_ptr();
}
} // of anonymous namespace
namespace flightgear
{
bool isInitialStateName(const std::string& name)
{
SGPropertyNode_ptr n = nodeForState(name);
return n.valid();
}
void applyInitialState()
{
std::string nm = fgGetString("/sim/aircraft-state");
if (nm.empty()) {
return;
}
SGPropertyNode_ptr stateNode = nodeForState(nm);
if (!stateNode) {
SG_LOG(SG_AIRCRAFT, SG_WARN, "missing state node for:" << nm);
std::string aircraft = fgGetString("/sim/aircraft");
modalMessageBox("Unknown aircraft state",
"The selected aircraft (" + aircraft + ") does not have a state '" + nm + "'");
return;
}
SG_LOG(SG_AIRCRAFT, SG_INFO, "Applying aircraft state:" << nm);
// copy all overlay properties to the tree
copyProperties(stateNode->getChild("overlay"), globals->get_props());
}
} // of namespace flightgear

View File

@@ -0,0 +1,41 @@
// initialstate.hxx -- setup initial state of the aircraft
//
// Written by James Turner
//
// Copyright (C) 2016 James Turner <zakalawe@mac.com>
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation; either version 2 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
//
// $Id$
#ifndef FG_AIRCRAFT_INITIAL_STATE_HXX
#define FG_AIRCRAFT_INITIAL_STATE_HXX
#include <string>
namespace flightgear
{
/**
* @brief is the supplied name a defined initial-state, or alias of one
*/
bool isInitialStateName(const std::string& name);
void applyInitialState();
} // of namespace flightgear
#endif // FG_AIRCRAFT_INITIAL_STATE_HXX

Some files were not shown because too many files have changed in this diff Show More