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);
};