first commit
This commit is contained in:
1749
src/AIModel/AIAircraft.cxx
Normal file
1749
src/AIModel/AIAircraft.cxx
Normal file
File diff suppressed because it is too large
Load Diff
262
src/AIModel/AIAircraft.hxx
Normal file
262
src/AIModel/AIAircraft.hxx
Normal 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
1243
src/AIModel/AIBallistic.cxx
Normal file
File diff suppressed because it is too large
Load Diff
230
src/AIModel/AIBallistic.hxx
Normal file
230
src/AIModel/AIBallistic.hxx
Normal 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
1268
src/AIModel/AIBase.cxx
Normal file
File diff suppressed because it is too large
Load Diff
527
src/AIModel/AIBase.hxx
Normal file
527
src/AIModel/AIBase.hxx
Normal 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;
|
||||
}
|
||||
|
||||
92
src/AIModel/AIBaseAircraft.cxx
Normal file
92
src/AIModel/AIBaseAircraft.cxx
Normal 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));
|
||||
}
|
||||
|
||||
70
src/AIModel/AIBaseAircraft.hxx
Normal file
70
src/AIModel/AIBaseAircraft.hxx
Normal 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
875
src/AIModel/AICarrier.cxx
Normal 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
181
src/AIModel/AICarrier.hxx
Normal 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
363
src/AIModel/AIEscort.cxx
Normal 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
99
src/AIModel/AIEscort.hxx
Normal 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;
|
||||
};
|
||||
647
src/AIModel/AIFlightPlan.cxx
Normal file
647
src/AIModel/AIFlightPlan.cxx
Normal 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();
|
||||
}
|
||||
329
src/AIModel/AIFlightPlan.hxx
Normal file
329
src/AIModel/AIFlightPlan.hxx
Normal 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; };
|
||||
};
|
||||
|
||||
1328
src/AIModel/AIFlightPlanCreate.cxx
Normal file
1328
src/AIModel/AIFlightPlanCreate.cxx
Normal file
File diff suppressed because it is too large
Load Diff
335
src/AIModel/AIFlightPlanCreateCruise.cxx
Normal file
335
src/AIModel/AIFlightPlanCreateCruise.cxx
Normal 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;
|
||||
}
|
||||
226
src/AIModel/AIFlightPlanCreatePushBack.cxx
Normal file
226
src/AIModel/AIFlightPlanCreatePushBack.cxx
Normal 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);
|
||||
|
||||
}
|
||||
521
src/AIModel/AIGroundVehicle.cxx
Normal file
521
src/AIModel/AIGroundVehicle.cxx
Normal 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
|
||||
113
src/AIModel/AIGroundVehicle.hxx
Normal file
113
src/AIModel/AIGroundVehicle.hxx
Normal 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
783
src/AIModel/AIManager.cxx
Normal 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
180
src/AIModel/AIManager.hxx
Normal 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;
|
||||
};
|
||||
922
src/AIModel/AIMultiplayer.cxx
Normal file
922
src/AIModel/AIMultiplayer.cxx
Normal 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;
|
||||
}
|
||||
210
src/AIModel/AIMultiplayer.hxx
Normal file
210
src/AIModel/AIMultiplayer.hxx
Normal 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;
|
||||
};
|
||||
102
src/AIModel/AINotifications.hxx
Normal file
102
src/AIModel/AINotifications.hxx
Normal 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
1102
src/AIModel/AIShip.cxx
Normal file
File diff suppressed because it is too large
Load Diff
152
src/AIModel/AIShip.hxx
Normal file
152
src/AIModel/AIShip.hxx
Normal 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
38
src/AIModel/AIStatic.cxx
Normal 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
38
src/AIModel/AIStatic.hxx
Normal 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
142
src/AIModel/AIStorm.cxx
Normal 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
68
src/AIModel/AIStorm.hxx
Normal 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
94
src/AIModel/AISwiftAircraft.cpp
Executable 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);
|
||||
}
|
||||
89
src/AIModel/AISwiftAircraft.h
Normal file
89
src/AIModel/AISwiftAircraft.h
Normal 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
74
src/AIModel/AITanker.cxx
Normal 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
55
src/AIModel/AITanker.hxx
Normal 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
359
src/AIModel/AIThermal.cxx
Normal 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
92
src/AIModel/AIThermal.hxx
Normal 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
531
src/AIModel/AIWingman.cxx
Normal 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
144
src/AIModel/AIWingman.hxx
Normal 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;
|
||||
}
|
||||
59
src/AIModel/CMakeLists.txt
Normal file
59
src/AIModel/CMakeLists.txt
Normal 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}")
|
||||
80
src/AIModel/VectorMath.cxx
Normal file
80
src/AIModel/VectorMath.cxx
Normal 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;
|
||||
}
|
||||
34
src/AIModel/VectorMath.hxx
Normal file
34
src/AIModel/VectorMath.hxx
Normal 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);
|
||||
};
|
||||
221
src/AIModel/performancedata.cxx
Normal file
221
src/AIModel/performancedata.cxx
Normal 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);
|
||||
}
|
||||
82
src/AIModel/performancedata.hxx
Normal file
82
src/AIModel/performancedata.hxx
Normal 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
|
||||
164
src/AIModel/performancedb.cxx
Normal file
164
src/AIModel/performancedb.cxx
Normal 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);
|
||||
60
src/AIModel/performancedb.hxx
Normal file
60
src/AIModel/performancedb.hxx
Normal 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
829
src/AIModel/submodel.cxx
Normal 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
189
src/AIModel/submodel.hxx
Normal file
@@ -0,0 +1,189 @@
|
||||
// submodel.hxx - models a releasable submodel.
|
||||
// Written by Dave Culp, started Aug 2004
|
||||
//
|
||||
// This file is in the Public Domain and comes with no warranty.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include <simgear/props/props.hxx>
|
||||
#include <simgear/structure/subsystem_mgr.hxx>
|
||||
#include <simgear/math/SGMath.hxx>
|
||||
|
||||
#include <Autopilot/inputvalue.hxx>
|
||||
|
||||
class FGAIBase;
|
||||
class FGAIManager;
|
||||
|
||||
class FGSubmodelMgr : public SGSubsystem,
|
||||
public SGPropertyChangeListener
|
||||
{
|
||||
public:
|
||||
typedef struct {
|
||||
SGPropertyNode_ptr trigger_node;
|
||||
SGPropertyNode_ptr prop;
|
||||
SGPropertyNode_ptr contents_node;
|
||||
SGPropertyNode_ptr submodel_node;
|
||||
SGPropertyNode_ptr speed_node;
|
||||
|
||||
std::string name;
|
||||
std::string model;
|
||||
double speed;
|
||||
bool slaved;
|
||||
bool repeat;
|
||||
double delay;
|
||||
double timer;
|
||||
int count;
|
||||
bool offsets_in_meter;
|
||||
FGXMLAutopilot::InputValue_ptr x_offset;
|
||||
FGXMLAutopilot::InputValue_ptr y_offset;
|
||||
FGXMLAutopilot::InputValue_ptr z_offset;
|
||||
FGXMLAutopilot::InputValue_ptr yaw_offset;
|
||||
FGXMLAutopilot::InputValue_ptr pitch_offset;
|
||||
double drag_area;
|
||||
double life;
|
||||
double buoyancy;
|
||||
FGXMLAutopilot::InputValue_ptr azimuth_error;
|
||||
FGXMLAutopilot::InputValue_ptr elevation_error;
|
||||
FGXMLAutopilot::InputValue_ptr cd_randomness;
|
||||
FGXMLAutopilot::InputValue_ptr life_randomness;
|
||||
bool wind;
|
||||
bool first_time;
|
||||
double cd;
|
||||
double weight;
|
||||
double mass;
|
||||
double contents;
|
||||
bool aero_stabilised;
|
||||
int id;
|
||||
bool no_roll;
|
||||
bool serviceable;
|
||||
bool random;
|
||||
bool collision;
|
||||
bool expiry;
|
||||
bool impact;
|
||||
std::string impact_report;
|
||||
double fuse_range;
|
||||
std::string submodel;
|
||||
int sub_id;
|
||||
bool force_stabilised;
|
||||
bool ext_force;
|
||||
std::string force_path;
|
||||
} submodel;
|
||||
|
||||
typedef struct {
|
||||
double lat;
|
||||
double lon;
|
||||
double alt;
|
||||
double roll;
|
||||
double azimuth;
|
||||
double elevation;
|
||||
double speed;
|
||||
double wind_from_east;
|
||||
double wind_from_north;
|
||||
double speed_down_fps;
|
||||
double speed_east_fps;
|
||||
double speed_north_fps;
|
||||
double mass;
|
||||
int id;
|
||||
bool no_roll;
|
||||
int parent_id;
|
||||
} IC_struct;
|
||||
|
||||
FGSubmodelMgr();
|
||||
virtual ~FGSubmodelMgr() = default;
|
||||
|
||||
// Subsystem API.
|
||||
void bind() override;
|
||||
void init() override;
|
||||
void postinit() override;
|
||||
void shutdown() override;
|
||||
void unbind() override;
|
||||
void update(double dt) override;
|
||||
|
||||
// Subsystem identification.
|
||||
static const char* staticSubsystemClassId() { return "submodel-mgr"; }
|
||||
|
||||
void load();
|
||||
|
||||
private:
|
||||
typedef std::vector <submodel*> submodel_vector_type;
|
||||
typedef submodel_vector_type::iterator submodel_vector_iterator;
|
||||
|
||||
submodel_vector_type submodels;
|
||||
submodel_vector_type subsubmodels;
|
||||
submodel_vector_iterator submodel_iterator, subsubmodel_iterator;
|
||||
|
||||
int index;
|
||||
|
||||
double x_offset, y_offset, z_offset;
|
||||
double pitch_offset, yaw_offset;
|
||||
|
||||
double _parent_lat;
|
||||
double _parent_lon;
|
||||
double _parent_elev;
|
||||
double _parent_hdg;
|
||||
double _parent_pitch;
|
||||
double _parent_roll;
|
||||
double _parent_speed;
|
||||
|
||||
double _x_offset;
|
||||
double _y_offset;
|
||||
double _z_offset;
|
||||
|
||||
// Conversion factor
|
||||
static const double lbs_to_slugs;
|
||||
|
||||
double contrail_altitude;
|
||||
|
||||
bool _impact;
|
||||
bool _hit;
|
||||
bool _expiry;
|
||||
bool _found_sub;
|
||||
|
||||
SGPropertyNode_ptr _serviceable_node;
|
||||
SGPropertyNode_ptr _user_lat_node;
|
||||
SGPropertyNode_ptr _user_lon_node;
|
||||
SGPropertyNode_ptr _user_heading_node;
|
||||
SGPropertyNode_ptr _user_alt_node;
|
||||
SGPropertyNode_ptr _user_pitch_node;
|
||||
SGPropertyNode_ptr _user_roll_node;
|
||||
SGPropertyNode_ptr _user_yaw_node;
|
||||
SGPropertyNode_ptr _user_alpha_node;
|
||||
SGPropertyNode_ptr _user_speed_node;
|
||||
SGPropertyNode_ptr _user_wind_from_east_node;
|
||||
SGPropertyNode_ptr _user_wind_from_north_node;
|
||||
SGPropertyNode_ptr _user_speed_down_fps_node;
|
||||
SGPropertyNode_ptr _user_speed_east_fps_node;
|
||||
SGPropertyNode_ptr _user_speed_north_fps_node;
|
||||
SGPropertyNode_ptr _contrail_altitude_node;
|
||||
SGPropertyNode_ptr _contrail_trigger;
|
||||
SGPropertyNode_ptr _count_node;
|
||||
SGPropertyNode_ptr _trigger_node;
|
||||
SGPropertyNode_ptr props;
|
||||
SGPropertyNode_ptr _model_added_node;
|
||||
SGPropertyNode_ptr _path_node;
|
||||
SGPropertyNode_ptr _selected_ac;
|
||||
|
||||
IC_struct IC;
|
||||
|
||||
// Helper to retrieve the AI manager, if it currently exists
|
||||
FGAIManager* aiManager();
|
||||
|
||||
void loadAI();
|
||||
void loadSubmodels();
|
||||
void setData(int id, const std::string& path, bool serviceable, const std::string& property_path, submodel_vector_type& models);
|
||||
void valueChanged (SGPropertyNode *);
|
||||
void transform(submodel *);
|
||||
void setParentNode(int parent_id);
|
||||
bool release(submodel *, double dt);
|
||||
|
||||
int _count;
|
||||
|
||||
SGGeod userpos;
|
||||
SGGeod offsetpos;
|
||||
|
||||
SGVec3d getCartOffsetPos(submodel* sm) const;
|
||||
void setOffsetPos(submodel* sm);
|
||||
};
|
||||
477
src/ATC/ATCController.cxx
Normal file
477
src/ATC/ATCController.cxx
Normal file
@@ -0,0 +1,477 @@
|
||||
// Extracted from trafficrecord.cxx - Implementation of AIModels ATC code.
|
||||
//
|
||||
// Written by Durk Talsma, started September 2006.
|
||||
//
|
||||
// Copyright (C) 2006 Durk Talsma.
|
||||
//
|
||||
// This program is free software; you can redistribute it and/or
|
||||
// modify it under the terms of the GNU General Public License as
|
||||
// published by the Free Software Foundation; either version 2 of the
|
||||
// License, or (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful, but
|
||||
// WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program; if not, write to the Free Software
|
||||
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
//
|
||||
// $Id$
|
||||
|
||||
#include <config.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdio>
|
||||
#include <random>
|
||||
|
||||
#include <osg/Geode>
|
||||
#include <osg/Geometry>
|
||||
#include <osg/MatrixTransform>
|
||||
#include <osg/Shape>
|
||||
|
||||
#include <simgear/scene/material/EffectGeode.hxx>
|
||||
#include <simgear/scene/material/matlib.hxx>
|
||||
#include <simgear/scene/material/mat.hxx>
|
||||
#include <simgear/scene/util/OsgMath.hxx>
|
||||
#include <simgear/timing/sg_time.hxx>
|
||||
|
||||
#include <Scenery/scenery.hxx>
|
||||
|
||||
#include "trafficcontrol.hxx"
|
||||
#include "atc_mgr.hxx"
|
||||
#include <AIModel/AIAircraft.hxx>
|
||||
#include <AIModel/AIFlightPlan.hxx>
|
||||
#include <AIModel/performancedata.hxx>
|
||||
#include <Traffic/TrafficMgr.hxx>
|
||||
#include <Airports/groundnetwork.hxx>
|
||||
#include <Airports/dynamics.hxx>
|
||||
#include <Airports/airport.hxx>
|
||||
#include <Radio/radio.hxx>
|
||||
#include <signal.h>
|
||||
|
||||
#include <ATC/atc_mgr.hxx>
|
||||
#include <ATC/trafficcontrol.hxx>
|
||||
#include <ATC/ATCController.hxx>
|
||||
|
||||
using std::sort;
|
||||
using std::string;
|
||||
|
||||
/***************************************************************************
|
||||
* FGATCController
|
||||
*
|
||||
**************************************************************************/
|
||||
|
||||
FGATCController::FGATCController()
|
||||
{
|
||||
dt_count = 0;
|
||||
available = true;
|
||||
lastTransmission = 0;
|
||||
initialized = false;
|
||||
lastTransmissionDirection = ATC_AIR_TO_GROUND;
|
||||
group = NULL;
|
||||
}
|
||||
|
||||
FGATCController::~FGATCController()
|
||||
{
|
||||
if (initialized) {
|
||||
auto mgr = globals->get_subsystem<FGATCManager>();
|
||||
mgr->removeController(this);
|
||||
}
|
||||
_isDestroying = true;
|
||||
clearTrafficControllers();
|
||||
}
|
||||
|
||||
void FGATCController::init()
|
||||
{
|
||||
if (!initialized) {
|
||||
auto mgr = globals->get_subsystem<FGATCManager>();
|
||||
mgr->addController(this);
|
||||
initialized = true;
|
||||
}
|
||||
}
|
||||
|
||||
string FGATCController::getGateName(FGAIAircraft * ref)
|
||||
{
|
||||
return ref->atGate();
|
||||
}
|
||||
|
||||
bool FGATCController::isUserAircraft(FGAIAircraft* ac)
|
||||
{
|
||||
return (ac->getCallSign() == fgGetString("/sim/multiplay/callsign")) ? true : false;
|
||||
};
|
||||
|
||||
void FGATCController::transmit(FGTrafficRecord * rec, FGAirportDynamics *parent, AtcMsgId msgId,
|
||||
AtcMsgDir msgDir, bool audible)
|
||||
{
|
||||
string sender, receiver;
|
||||
int stationFreq = 0;
|
||||
int taxiFreq = 0;
|
||||
int towerFreq = 0;
|
||||
int freqId = 0;
|
||||
string atisInformation;
|
||||
string text;
|
||||
string taxiFreqStr;
|
||||
string towerFreqStr;
|
||||
double heading = 0;
|
||||
string activeRunway;
|
||||
string fltType;
|
||||
string rwyClass;
|
||||
string SID;
|
||||
string transponderCode;
|
||||
FGAIFlightPlan *fp;
|
||||
string fltRules;
|
||||
string instructionText;
|
||||
int ground_to_air=0;
|
||||
|
||||
//double commFreqD;
|
||||
sender = rec->getCallsign();
|
||||
if (rec->getAircraft()->getTaxiClearanceRequest()) {
|
||||
instructionText = "push-back and taxi";
|
||||
} else {
|
||||
instructionText = "taxi";
|
||||
}
|
||||
|
||||
SG_LOG(SG_ATC, SG_DEBUG, "transmitting for: " << sender << "Leg = " << rec->getLeg());
|
||||
|
||||
auto depApt = rec->getAircraft()->getTrafficRef()->getDepartureAirport();
|
||||
|
||||
if (!depApt) {
|
||||
SG_LOG(SG_ATC, SG_DEV_ALERT, "TrafficRec has empty departure airport, can't transmit");
|
||||
return;
|
||||
}
|
||||
|
||||
stationFreq = getFrequency();
|
||||
taxiFreq = depApt->getDynamics()->getGroundFrequency(2);
|
||||
towerFreq = depApt->getDynamics()->getTowerFrequency(2);
|
||||
receiver = getName();
|
||||
atisInformation = depApt->getDynamics()->getAtisSequence();
|
||||
|
||||
// Swap sender and receiver value in case of a ground to air transmission
|
||||
if (msgDir == ATC_GROUND_TO_AIR) {
|
||||
string tmp = sender;
|
||||
sender = receiver;
|
||||
receiver = tmp;
|
||||
ground_to_air = 1;
|
||||
}
|
||||
|
||||
switch (msgId) {
|
||||
case MSG_ANNOUNCE_ENGINE_START:
|
||||
text = sender + ". Ready to Start up.";
|
||||
break;
|
||||
case MSG_REQUEST_ENGINE_START:
|
||||
text =
|
||||
receiver + ", This is " + sender + ". Position " +
|
||||
getGateName(rec->getAircraft()) + ". Information " +
|
||||
atisInformation + ". " +
|
||||
rec->getAircraft()->getTrafficRef()->getFlightRules() +
|
||||
" to " +
|
||||
rec->getAircraft()->getTrafficRef()->getArrivalAirport()->
|
||||
getName() + ". Request start-up.";
|
||||
break;
|
||||
// Acknowledge engine startup permission
|
||||
// Assign departure runway
|
||||
// Assign SID, if necessery (TODO)
|
||||
case MSG_PERMIT_ENGINE_START:
|
||||
taxiFreqStr = formatATCFrequency3_2(taxiFreq);
|
||||
|
||||
heading = rec->getAircraft()->getTrafficRef()->getCourse();
|
||||
fltType = rec->getAircraft()->getTrafficRef()->getFlightType();
|
||||
rwyClass =
|
||||
rec->getAircraft()->GetFlightPlan()->
|
||||
getRunwayClassFromTrafficType(fltType);
|
||||
|
||||
rec->getAircraft()->getTrafficRef()->getDepartureAirport()->
|
||||
getDynamics()->getActiveRunway(rwyClass, 1, activeRunway,
|
||||
heading);
|
||||
rec->getAircraft()->GetFlightPlan()->setRunway(activeRunway);
|
||||
fp = NULL;
|
||||
rec->getAircraft()->GetFlightPlan()->setSID(fp);
|
||||
if (fp) {
|
||||
SID = fp->getName() + " departure";
|
||||
} else {
|
||||
SID = "fly runway heading ";
|
||||
}
|
||||
//snprintf(buffer, 7, "%3.2f", heading);
|
||||
fltRules = rec->getAircraft()->getTrafficRef()->getFlightRules();
|
||||
transponderCode = genTransponderCode(fltRules);
|
||||
rec->getAircraft()->SetTransponderCode(transponderCode);
|
||||
text =
|
||||
receiver + ". Start-up approved. " + atisInformation +
|
||||
" correct, runway " + activeRunway + ", " + SID + ", squawk " +
|
||||
transponderCode + ". " +
|
||||
"For "+ instructionText + " clearance call " + taxiFreqStr + ". " +
|
||||
sender + " control.";
|
||||
break;
|
||||
case MSG_DENY_ENGINE_START:
|
||||
text = receiver + ". Standby.";
|
||||
break;
|
||||
case MSG_ACKNOWLEDGE_ENGINE_START:
|
||||
fp = rec->getAircraft()->GetFlightPlan()->getSID();
|
||||
if (fp) {
|
||||
SID =
|
||||
rec->getAircraft()->GetFlightPlan()->getSID()->getName() +
|
||||
" departure";
|
||||
} else {
|
||||
SID = "fly runway heading ";
|
||||
}
|
||||
taxiFreqStr = formatATCFrequency3_2(taxiFreq);
|
||||
activeRunway = rec->getAircraft()->GetFlightPlan()->getRunway();
|
||||
transponderCode = rec->getAircraft()->GetTransponderCode();
|
||||
|
||||
text =
|
||||
receiver + ". Start-up approved. " + atisInformation +
|
||||
" correct, runway " + activeRunway + ", " + SID + ", squawk " +
|
||||
transponderCode + ". " +
|
||||
"For " + instructionText + " clearance call " + taxiFreqStr + ". " +
|
||||
sender + ".";
|
||||
break;
|
||||
case MSG_ACKNOWLEDGE_SWITCH_GROUND_FREQUENCY:
|
||||
taxiFreqStr = formatATCFrequency3_2(taxiFreq);
|
||||
text = receiver + ". Switching to " + taxiFreqStr + ". " + sender + ".";
|
||||
break;
|
||||
case MSG_INITIATE_CONTACT:
|
||||
text = receiver + ". With you. " + sender + ".";
|
||||
break;
|
||||
case MSG_ACKNOWLEDGE_INITIATE_CONTACT:
|
||||
text = receiver + ". Roger. " + sender + ".";
|
||||
break;
|
||||
case MSG_REQUEST_PUSHBACK_CLEARANCE:
|
||||
if (rec->getAircraft()->getTaxiClearanceRequest()) {
|
||||
text = receiver + ". Request push-back. " + sender + ".";
|
||||
} else {
|
||||
text = receiver + ". Request Taxi clearance. " + sender + ".";
|
||||
}
|
||||
break;
|
||||
case MSG_PERMIT_PUSHBACK_CLEARANCE:
|
||||
if (rec->getAircraft()->getTaxiClearanceRequest()) {
|
||||
text = receiver + ". Push-back approved. " + sender + ".";
|
||||
} else {
|
||||
text = receiver + ". Cleared to Taxi. " + sender + ".";
|
||||
}
|
||||
break;
|
||||
case MSG_HOLD_PUSHBACK_CLEARANCE:
|
||||
text = receiver + ". Standby. " + sender + ".";
|
||||
break;
|
||||
case MSG_REQUEST_TAXI_CLEARANCE:
|
||||
text = receiver + ". Ready to Taxi. " + sender + ".";
|
||||
break;
|
||||
case MSG_ISSUE_TAXI_CLEARANCE:
|
||||
text = receiver + ". Cleared to taxi. " + sender + ".";
|
||||
break;
|
||||
case MSG_ACKNOWLEDGE_TAXI_CLEARANCE:
|
||||
text = receiver + ". Cleared to taxi. " + sender + ".";
|
||||
break;
|
||||
case MSG_HOLD_POSITION:
|
||||
text = receiver + ". Hold Position. " + sender + ".";
|
||||
break;
|
||||
case MSG_ACKNOWLEDGE_HOLD_POSITION:
|
||||
text = receiver + ". Holding Position. " + sender + ".";
|
||||
break;
|
||||
case MSG_RESUME_TAXI:
|
||||
text = receiver + ". Resume Taxiing. " + sender + ".";
|
||||
break;
|
||||
case MSG_ACKNOWLEDGE_RESUME_TAXI:
|
||||
text = receiver + ". Continuing Taxi. " + sender + ".";
|
||||
break;
|
||||
case MSG_REPORT_RUNWAY_HOLD_SHORT:
|
||||
activeRunway = rec->getAircraft()->GetFlightPlan()->getRunway();
|
||||
//activeRunway = "test";
|
||||
text = receiver + ". Holding short runway "
|
||||
+ activeRunway
|
||||
+ ". " + sender + ".";
|
||||
//text = "test1";
|
||||
SG_LOG(SG_ATC, SG_DEBUG, "1 Currently at leg " << rec->getLeg());
|
||||
break;
|
||||
case MSG_ACKNOWLEDGE_REPORT_RUNWAY_HOLD_SHORT:
|
||||
activeRunway = rec->getAircraft()->GetFlightPlan()->getRunway();
|
||||
text = receiver + " Roger. Holding short runway "
|
||||
// + activeRunway
|
||||
+ ". " + sender + ".";
|
||||
//text = "test2";
|
||||
SG_LOG(SG_ATC, SG_DEBUG, "2 Currently at leg " << rec->getLeg());
|
||||
break;
|
||||
case MSG_CLEARED_FOR_TAKEOFF:
|
||||
activeRunway = rec->getAircraft()->GetFlightPlan()->getRunway();
|
||||
//activeRunway = "test";
|
||||
text = receiver + ". Cleared for takeoff runway " + activeRunway + ". " + sender + ".";
|
||||
break;
|
||||
case MSG_ACKNOWLEDGE_CLEARED_FOR_TAKEOFF:
|
||||
activeRunway = rec->getAircraft()->GetFlightPlan()->getRunway();
|
||||
text = receiver + " Roger. Cleared for takeoff runway " + activeRunway + ". " + sender + ".";
|
||||
//text = "test2";
|
||||
break;
|
||||
case MSG_SWITCH_TOWER_FREQUENCY:
|
||||
towerFreqStr = formatATCFrequency3_2(towerFreq);
|
||||
text = receiver + " Contact Tower at " + towerFreqStr + ". " + sender + ".";
|
||||
//text = "test3";
|
||||
SG_LOG(SG_ATC, SG_DEBUG, "3 Currently at leg " << rec->getLeg());
|
||||
break;
|
||||
case MSG_ACKNOWLEDGE_SWITCH_TOWER_FREQUENCY:
|
||||
towerFreqStr = formatATCFrequency3_2(towerFreq);
|
||||
text = receiver + " Roger, switching to tower at " + towerFreqStr + ". " + sender + ".";
|
||||
//text = "test4";
|
||||
SG_LOG(SG_ATC, SG_DEBUG, "4 Currently at leg " << rec->getLeg());
|
||||
break;
|
||||
default:
|
||||
//text = "test3";
|
||||
text = text + sender + ". Transmitting unknown Message.";
|
||||
break;
|
||||
}
|
||||
|
||||
const bool atcAudioEnabled = fgGetBool("/sim/sound/atc/enabled", false);
|
||||
if (audible && atcAudioEnabled) {
|
||||
double onBoardRadioFreq0 =
|
||||
fgGetDouble("/instrumentation/comm[0]/frequencies/selected-mhz");
|
||||
double onBoardRadioFreq1 =
|
||||
fgGetDouble("/instrumentation/comm[1]/frequencies/selected-mhz");
|
||||
int onBoardRadioFreqI0 = (int) floor(onBoardRadioFreq0 * 100 + 0.5);
|
||||
int onBoardRadioFreqI1 = (int) floor(onBoardRadioFreq1 * 100 + 0.5);
|
||||
SG_LOG(SG_ATC, SG_DEBUG, "Using " << onBoardRadioFreq0 << ", " << onBoardRadioFreq1 << " and " << stationFreq << " for " << text << endl);
|
||||
if( stationFreq == 0 ) {
|
||||
SG_LOG(SG_ATC, SG_DEBUG, getName() << " stationFreq not found");
|
||||
}
|
||||
|
||||
// Display ATC message only when one of the radios is tuned
|
||||
// the relevant frequency.
|
||||
// Note that distance attenuation is currently not yet implemented
|
||||
|
||||
if ((stationFreq > 0)&&
|
||||
((onBoardRadioFreqI0 == stationFreq)||
|
||||
(onBoardRadioFreqI1 == stationFreq))) {
|
||||
if (rec->allowTransmissions()) {
|
||||
|
||||
if( fgGetBool( "/sim/radio/use-itm-attenuation", false ) ) {
|
||||
SG_LOG(SG_ATC, SG_DEBUG, "Using ITM radio propagation");
|
||||
FGRadioTransmission* radio = new FGRadioTransmission();
|
||||
SGGeod sender_pos;
|
||||
double sender_alt_ft, sender_alt;
|
||||
if(ground_to_air) {
|
||||
sender_pos = parent->parent()->geod();
|
||||
}
|
||||
else {
|
||||
sender_pos= rec->getPos();
|
||||
}
|
||||
double frequency = ((double)stationFreq) / 100;
|
||||
radio->receiveATC(sender_pos, frequency, text, ground_to_air);
|
||||
delete radio;
|
||||
}
|
||||
else {
|
||||
SG_LOG(SG_ATC, SG_BULK, "Transmitting " << text);
|
||||
fgSetString("/sim/messages/atc", text.c_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
//FGATCDialogNew::instance()->addEntry(1, text);
|
||||
}
|
||||
}
|
||||
|
||||
void FGATCController::signOff(int id)
|
||||
{
|
||||
TrafficVectorIterator i = searchActiveTraffic(id);
|
||||
if (i == activeTraffic.end()) {
|
||||
// Dead traffic should never reach here
|
||||
SG_LOG(SG_ATC, SG_ALERT,
|
||||
"AI error: Aircraft without traffic record is signing off from " << getName() << " at " << SG_ORIGIN << " list " << activeTraffic.empty());
|
||||
return;
|
||||
}
|
||||
activeTraffic.erase(i);
|
||||
SG_LOG(SG_ATC, SG_DEBUG, i->getCallsign() << " signing off from " << getName() );
|
||||
}
|
||||
|
||||
bool FGATCController::hasInstruction(int id)
|
||||
{
|
||||
// Search activeTraffic for a record matching our id
|
||||
TrafficVectorIterator i = searchActiveTraffic(id);
|
||||
|
||||
if (i == activeTraffic.end()) {
|
||||
SG_LOG(SG_ATC, SG_ALERT,
|
||||
"AI error: checking ATC instruction for aircraft without traffic record at " << SG_ORIGIN);
|
||||
} else {
|
||||
return i->hasInstruction();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
FGATCInstruction FGATCController::getInstruction(int id)
|
||||
{
|
||||
// Search activeTraffic for a record matching our id
|
||||
TrafficVectorIterator i = searchActiveTraffic(id);
|
||||
|
||||
if (i == activeTraffic.end() || (activeTraffic.size() == 0)) {
|
||||
SG_LOG(SG_ATC, SG_ALERT,
|
||||
"AI error: requesting ATC instruction for aircraft without traffic record at " << SG_ORIGIN);
|
||||
} else {
|
||||
return i->getInstruction();
|
||||
}
|
||||
return FGATCInstruction();
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Format integer frequency xxxyy as xxx.yy
|
||||
* @param freq - integer value
|
||||
* @return the formatted string
|
||||
*/
|
||||
string FGATCController::formatATCFrequency3_2(int freq)
|
||||
{
|
||||
char buffer[7]; // does this ever need to be freed?
|
||||
snprintf(buffer, 7, "%3.2f", ((float) freq / 100.0));
|
||||
return string(buffer);
|
||||
}
|
||||
|
||||
// TODO: Set transponder codes according to real-world routes.
|
||||
// The current version just returns a random string of four octal numbers.
|
||||
string FGATCController::genTransponderCode(const string& fltRules)
|
||||
{
|
||||
if (fltRules == "VFR")
|
||||
return string("1200");
|
||||
|
||||
std::default_random_engine generator;
|
||||
std::uniform_int_distribution<unsigned> distribution(0, 7);
|
||||
|
||||
unsigned val = (
|
||||
distribution(generator) * 1000 +
|
||||
distribution(generator) * 100 +
|
||||
distribution(generator) * 10 +
|
||||
distribution(generator));
|
||||
|
||||
return std::to_string(val);
|
||||
}
|
||||
|
||||
void FGATCController::eraseDeadTraffic()
|
||||
{
|
||||
auto it = std::remove_if(activeTraffic.begin(), activeTraffic.end(), [](const FGTrafficRecord& traffic)
|
||||
{
|
||||
if (traffic.isDead()) {
|
||||
SG_LOG(SG_ATC, SG_DEBUG, "Remove dead " << traffic.getId() << " " << traffic.isDead());
|
||||
}
|
||||
return traffic.isDead();
|
||||
});
|
||||
activeTraffic.erase(it, activeTraffic.end());
|
||||
}
|
||||
|
||||
/*
|
||||
* Search activeTraffic vector to find matching id
|
||||
* @param id integer to search for in the vector
|
||||
* @return the matching item OR activeTraffic.end()
|
||||
*/
|
||||
TrafficVectorIterator FGATCController::searchActiveTraffic(int id)
|
||||
{
|
||||
return std::find_if(activeTraffic.begin(), activeTraffic.end(),
|
||||
[id] (const FGTrafficRecord& rec)
|
||||
{ return rec.getId() == id; }
|
||||
);
|
||||
}
|
||||
|
||||
void FGATCController::clearTrafficControllers()
|
||||
{
|
||||
for (const auto& traffic : activeTraffic) {
|
||||
traffic.clearATCController();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
138
src/ATC/ATCController.hxx
Normal file
138
src/ATC/ATCController.hxx
Normal file
@@ -0,0 +1,138 @@
|
||||
// Extracted from trafficcontrol.hxx - classes to manage AIModels based air traffic control
|
||||
// Written by Durk Talsma, started September 2006.
|
||||
//
|
||||
// This program is free software; you can redistribute it and/or
|
||||
// modify it under the terms of the GNU General Public License as
|
||||
// published by the Free Software Foundation; either version 2 of the
|
||||
// License, or (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful, but
|
||||
// WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program; if not, write to the Free Software
|
||||
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
//
|
||||
// $Id$
|
||||
|
||||
#ifndef ATC_CONTROLLER_HXX
|
||||
#define ATC_CONTROLLER_HXX
|
||||
|
||||
#include <Airports/airports_fwd.hxx>
|
||||
|
||||
#include <osg/Geode>
|
||||
#include <osg/Geometry>
|
||||
#include <osg/MatrixTransform>
|
||||
#include <osg/Shape>
|
||||
|
||||
#include <simgear/compiler.h>
|
||||
#include <simgear/constants.h>
|
||||
#include <simgear/debug/logstream.hxx>
|
||||
#include <simgear/structure/SGReferenced.hxx>
|
||||
#include <simgear/structure/SGSharedPtr.hxx>
|
||||
|
||||
#include <ATC/trafficcontrol.hxx>
|
||||
|
||||
/**
|
||||
* class FGATCController
|
||||
* NOTE: this class serves as an abstraction layer for all sorts of ATC controllers.
|
||||
*************************************************************************************/
|
||||
class FGATCController
|
||||
{
|
||||
private:
|
||||
|
||||
|
||||
protected:
|
||||
// guard variable to avoid modifying state during destruction
|
||||
bool _isDestroying = false;
|
||||
bool initialized;
|
||||
bool available;
|
||||
time_t lastTransmission;
|
||||
TrafficVector activeTraffic;
|
||||
|
||||
double dt_count;
|
||||
osg::Group* group;
|
||||
FGAirportDynamics *parent = nullptr;
|
||||
|
||||
std::string formatATCFrequency3_2(int );
|
||||
std::string genTransponderCode(const std::string& fltRules);
|
||||
bool isUserAircraft(FGAIAircraft*);
|
||||
void clearTrafficControllers();
|
||||
TrafficVectorIterator searchActiveTraffic(int id);
|
||||
void eraseDeadTraffic();
|
||||
/**Returns the frequency to be used. */
|
||||
virtual int getFrequency() = 0;
|
||||
public:
|
||||
typedef enum {
|
||||
MSG_ANNOUNCE_ENGINE_START,
|
||||
MSG_REQUEST_ENGINE_START,
|
||||
MSG_PERMIT_ENGINE_START,
|
||||
MSG_DENY_ENGINE_START,
|
||||
MSG_ACKNOWLEDGE_ENGINE_START,
|
||||
MSG_REQUEST_PUSHBACK_CLEARANCE,
|
||||
MSG_PERMIT_PUSHBACK_CLEARANCE,
|
||||
MSG_HOLD_PUSHBACK_CLEARANCE,
|
||||
MSG_ACKNOWLEDGE_SWITCH_GROUND_FREQUENCY,
|
||||
MSG_INITIATE_CONTACT,
|
||||
MSG_ACKNOWLEDGE_INITIATE_CONTACT,
|
||||
MSG_REQUEST_TAXI_CLEARANCE,
|
||||
MSG_ISSUE_TAXI_CLEARANCE,
|
||||
MSG_ACKNOWLEDGE_TAXI_CLEARANCE,
|
||||
MSG_HOLD_POSITION,
|
||||
MSG_ACKNOWLEDGE_HOLD_POSITION,
|
||||
MSG_RESUME_TAXI,
|
||||
MSG_ACKNOWLEDGE_RESUME_TAXI,
|
||||
MSG_REPORT_RUNWAY_HOLD_SHORT,
|
||||
MSG_ACKNOWLEDGE_REPORT_RUNWAY_HOLD_SHORT,
|
||||
MSG_CLEARED_FOR_TAKEOFF,
|
||||
MSG_ACKNOWLEDGE_CLEARED_FOR_TAKEOFF,
|
||||
MSG_SWITCH_TOWER_FREQUENCY,
|
||||
MSG_ACKNOWLEDGE_SWITCH_TOWER_FREQUENCY
|
||||
} AtcMsgId;
|
||||
|
||||
typedef enum {
|
||||
ATC_AIR_TO_GROUND,
|
||||
ATC_GROUND_TO_AIR
|
||||
} AtcMsgDir;
|
||||
FGATCController();
|
||||
virtual ~FGATCController();
|
||||
void init();
|
||||
|
||||
virtual void announcePosition(int id, FGAIFlightPlan *intendedRoute, int currentRoute,
|
||||
double lat, double lon,
|
||||
double hdg, double spd, double alt, double radius, int leg,
|
||||
FGAIAircraft *aircraft) = 0;
|
||||
virtual void updateAircraftInformation(int id, SGGeod geod,
|
||||
double heading, double speed, double alt, double dt) = 0;
|
||||
|
||||
virtual void signOff(int id);
|
||||
bool hasInstruction(int id);
|
||||
FGATCInstruction getInstruction(int id);
|
||||
|
||||
bool hasActiveTraffic() {
|
||||
return ! activeTraffic.empty();
|
||||
};
|
||||
TrafficVector &getActiveTraffic() {
|
||||
return activeTraffic;
|
||||
};
|
||||
|
||||
double getDt() {
|
||||
return dt_count;
|
||||
};
|
||||
void setDt(double dt) {
|
||||
dt_count = dt;
|
||||
};
|
||||
void transmit(FGTrafficRecord *rec, FGAirportDynamics *parent, AtcMsgId msgId, AtcMsgDir msgDir, bool audible);
|
||||
std::string getGateName(FGAIAircraft *aircraft);
|
||||
virtual void render(bool) = 0;
|
||||
virtual std::string getName() = 0;
|
||||
virtual void update(double) = 0;
|
||||
|
||||
|
||||
private:
|
||||
AtcMsgDir lastTransmissionDirection;
|
||||
};
|
||||
|
||||
#endif
|
||||
592
src/ATC/ATISEncoder.cxx
Normal file
592
src/ATC/ATISEncoder.cxx
Normal file
@@ -0,0 +1,592 @@
|
||||
/*
|
||||
Encode an ATIS into spoken words
|
||||
Copyright (C) 2014 Torsten Dreyer
|
||||
|
||||
This program is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU General Public License
|
||||
as published by the Free Software Foundation; either version 2
|
||||
of the License, or (at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#include "ATISEncoder.hxx"
|
||||
#include <Airports/dynamics.hxx>
|
||||
#include <Main/globals.hxx>
|
||||
#include <Main/locale.hxx>
|
||||
#include <simgear/structure/exception.hxx>
|
||||
#include <simgear/props/props_io.hxx>
|
||||
|
||||
#include <map>
|
||||
#include <vector>
|
||||
|
||||
using std::string;
|
||||
using std::vector;
|
||||
using simgear::PropertyList;
|
||||
|
||||
static string NO_ATIS("nil");
|
||||
static string EMPTY("");
|
||||
#define SPACE append(1,' ')
|
||||
|
||||
std::string ATCSpeech::getSpokenDigit( int i )
|
||||
{
|
||||
string key = "n" + std::to_string(i);
|
||||
return globals->get_locale()->getLocalizedString(key.c_str(), "atc", "" );
|
||||
}
|
||||
|
||||
string ATCSpeech::getSpokenNumber( string number )
|
||||
{
|
||||
string result;
|
||||
for( string::iterator it = number.begin(); it != number.end(); ++it ) {
|
||||
if( !result.empty() )
|
||||
result.SPACE;
|
||||
result.append( getSpokenDigit( (*it) - '0' ));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
string ATCSpeech::getSpokenNumber( int number, bool leadingZero, int digits )
|
||||
{
|
||||
string_list spokenDigits;
|
||||
bool negative = false;
|
||||
if( number < 0 ) {
|
||||
negative = true;
|
||||
number = -number;
|
||||
}
|
||||
|
||||
int n = 0;
|
||||
while( number > 0 ) {
|
||||
spokenDigits.push_back( getSpokenDigit(number%10) );
|
||||
number /= 10;
|
||||
n++;
|
||||
}
|
||||
|
||||
if( digits > 0 ) {
|
||||
while( n++ < digits ) {
|
||||
spokenDigits.push_back( getSpokenDigit(0) );
|
||||
}
|
||||
}
|
||||
|
||||
string result;
|
||||
if( negative ) {
|
||||
result.append( globals->get_locale()->getLocalizedString("minus", "atc", "minus" ) );
|
||||
}
|
||||
|
||||
while( !spokenDigits.empty() ) {
|
||||
if( !result.empty() )
|
||||
result.SPACE;
|
||||
|
||||
result.append( spokenDigits.back() );
|
||||
spokenDigits.pop_back();
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
string ATCSpeech::getSpokenAltitude( int altitude )
|
||||
{
|
||||
string result;
|
||||
int thousands = altitude / 1000;
|
||||
int hundrets = (altitude % 1000) / 100;
|
||||
|
||||
if( thousands > 0 ) {
|
||||
result.append( getSpokenNumber(thousands) );
|
||||
result.SPACE;
|
||||
result.append( getSpokenDigit(1000) );
|
||||
result.SPACE;
|
||||
}
|
||||
if( hundrets > 0 )
|
||||
result.append( getSpokenNumber(hundrets) )
|
||||
.SPACE
|
||||
.append( getSpokenDigit(100) );
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
ATISEncoder::ATISEncoder()
|
||||
{
|
||||
handlerMap.insert( std::make_pair( "text", &ATISEncoder::processTextToken ));
|
||||
handlerMap.insert( std::make_pair( "token", &ATISEncoder::processTokenToken ));
|
||||
handlerMap.insert( std::make_pair( "if", &ATISEncoder::processIfToken ));
|
||||
handlerMap.insert( std::make_pair( "section", &ATISEncoder::processTokens ));
|
||||
|
||||
handlerMap.insert( std::make_pair( "id", &ATISEncoder::getAtisId ));
|
||||
handlerMap.insert( std::make_pair( "airport-name", &ATISEncoder::getAirportName ));
|
||||
handlerMap.insert( std::make_pair( "time", &ATISEncoder::getTime ));
|
||||
handlerMap.insert( std::make_pair( "approach-type", &ATISEncoder::getApproachType ));
|
||||
handlerMap.insert( std::make_pair( "rwy-land", &ATISEncoder::getLandingRunway ));
|
||||
handlerMap.insert( std::make_pair( "rwy-to", &ATISEncoder::getTakeoffRunway ));
|
||||
handlerMap.insert( std::make_pair( "transition-level", &ATISEncoder::getTransitionLevel ));
|
||||
handlerMap.insert( std::make_pair( "wind-dir", &ATISEncoder::getWindDirection ));
|
||||
handlerMap.insert( std::make_pair( "wind-from", &ATISEncoder::getWindMinDirection ));
|
||||
handlerMap.insert( std::make_pair( "wind-to", &ATISEncoder::getWindMaxDirection ));
|
||||
handlerMap.insert( std::make_pair( "wind-speed-kn", &ATISEncoder::getWindspeedKnots ));
|
||||
handlerMap.insert( std::make_pair( "gusts", &ATISEncoder::getGustsKnots ));
|
||||
handlerMap.insert( std::make_pair( "visibility-metric", &ATISEncoder::getVisibilityMetric ));
|
||||
handlerMap.insert( std::make_pair( "visibility-miles", &ATISEncoder::getVisibilityMiles ));
|
||||
handlerMap.insert( std::make_pair( "phenomena", &ATISEncoder::getPhenomena ));
|
||||
handlerMap.insert( std::make_pair( "clouds", &ATISEncoder::getClouds ));
|
||||
handlerMap.insert( std::make_pair( "clouds-brief", &ATISEncoder::getCloudsBrief ));
|
||||
handlerMap.insert( std::make_pair( "cavok", &ATISEncoder::getCavok ));
|
||||
handlerMap.insert( std::make_pair( "temperature-deg", &ATISEncoder::getTemperatureDeg ));
|
||||
handlerMap.insert( std::make_pair( "dewpoint-deg", &ATISEncoder::getDewpointDeg ));
|
||||
handlerMap.insert( std::make_pair( "qnh", &ATISEncoder::getQnh ));
|
||||
handlerMap.insert( std::make_pair( "inhg", &ATISEncoder::getInhg ));
|
||||
handlerMap.insert( std::make_pair( "inhg-integer", &ATISEncoder::getInhgInteger ));
|
||||
handlerMap.insert( std::make_pair( "inhg-fraction", &ATISEncoder::getInhgFraction ));
|
||||
handlerMap.insert( std::make_pair( "trend", &ATISEncoder::getTrend ));
|
||||
}
|
||||
|
||||
ATISEncoder::~ATISEncoder()
|
||||
{
|
||||
}
|
||||
|
||||
SGPropertyNode_ptr findAtisTemplate( const std::string & stationId, SGPropertyNode_ptr atisSchemaNode )
|
||||
{
|
||||
using simgear::strutils::starts_with;
|
||||
SGPropertyNode_ptr atisTemplate;
|
||||
|
||||
PropertyList schemaNodes = atisSchemaNode->getChildren("atis-schema");
|
||||
for( PropertyList::iterator asit = schemaNodes.begin(); asit != schemaNodes.end(); ++asit ) {
|
||||
SGPropertyNode_ptr ppp = (*asit)->getNode("station-starts-with", false );
|
||||
atisTemplate = (*asit)->getNode("atis", false );
|
||||
if( !atisTemplate.valid() ) continue; // no <atis> node - ignore entry
|
||||
|
||||
PropertyList startsWithNodes = (*asit)->getChildren("station-starts-with");
|
||||
for( PropertyList::iterator swit = startsWithNodes.begin(); swit != startsWithNodes.end(); ++swit ) {
|
||||
|
||||
if( starts_with( stationId, (*swit)->getStringValue() ) ) {
|
||||
return atisTemplate;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return atisTemplate;
|
||||
}
|
||||
|
||||
string ATISEncoder::encodeATIS( ATISInformationProvider * atisInformation )
|
||||
{
|
||||
using simgear::strutils::lowercase;
|
||||
|
||||
if( !atisInformation->isValid() ) return NO_ATIS;
|
||||
|
||||
airport = FGAirport::getByIdent( atisInformation->airportId() );
|
||||
if( !airport.valid() ) {
|
||||
SG_LOG( SG_ATC, SG_WARN, "ATISEncoder: unknown airport id " << atisInformation->airportId() );
|
||||
return NO_ATIS;
|
||||
}
|
||||
|
||||
_atis = atisInformation;
|
||||
|
||||
// lazily load the schema file on the first call
|
||||
if( !atisSchemaNode.valid() ) {
|
||||
atisSchemaNode = new SGPropertyNode();
|
||||
try
|
||||
{
|
||||
SGPath path = globals->resolve_maybe_aircraft_path("ATC/atis.xml");
|
||||
readProperties( path, atisSchemaNode );
|
||||
}
|
||||
catch (const sg_exception& e)
|
||||
{
|
||||
SG_LOG( SG_ATC, SG_ALERT, "ATISEncoder: Failed to load atis schema definition: " << e.getMessage());
|
||||
return NO_ATIS;
|
||||
}
|
||||
}
|
||||
|
||||
string stationId = lowercase( airport->ident() );
|
||||
|
||||
SGPropertyNode_ptr atisTemplate = findAtisTemplate( stationId, atisSchemaNode );;
|
||||
if( !atisTemplate.valid() ) {
|
||||
SG_LOG(SG_ATC, SG_WARN, "no matching atis template for station " << stationId );
|
||||
return NO_ATIS; // no template for this station!?
|
||||
}
|
||||
|
||||
return processTokens( atisTemplate );
|
||||
}
|
||||
|
||||
string ATISEncoder::processTokens( SGPropertyNode_ptr node )
|
||||
{
|
||||
string result;
|
||||
if( node.valid() ) {
|
||||
for( int i = 0; i < node->nChildren(); i++ ) {
|
||||
result.append(processToken( node->getChild(i) ));
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
string ATISEncoder::processToken( SGPropertyNode_ptr token )
|
||||
{
|
||||
HandlerMap::iterator it = handlerMap.find( token->getNameString());
|
||||
if( it == handlerMap.end() ) {
|
||||
SG_LOG(SG_ATC, SG_WARN, "ATISEncoder: unknown token: " << token->getNameString() );
|
||||
return EMPTY;
|
||||
}
|
||||
handler_t h = it->second;
|
||||
return (this->*h)( token );
|
||||
}
|
||||
|
||||
string ATISEncoder::processTextToken( SGPropertyNode_ptr token )
|
||||
{
|
||||
return token->getStringValue();
|
||||
}
|
||||
|
||||
string ATISEncoder::processTokenToken( SGPropertyNode_ptr token )
|
||||
{
|
||||
HandlerMap::iterator it = handlerMap.find( token->getStringValue());
|
||||
if( it == handlerMap.end() ) {
|
||||
SG_LOG(SG_ATC, SG_WARN, "ATISEncoder: unknown token: " << token->getStringValue() );
|
||||
return EMPTY;
|
||||
}
|
||||
handler_t h = it->second;
|
||||
return (this->*h)( token );
|
||||
}
|
||||
|
||||
string ATISEncoder::processIfToken( SGPropertyNode_ptr token )
|
||||
{
|
||||
using namespace simgear::strutils;
|
||||
|
||||
SGPropertyNode_ptr n;
|
||||
|
||||
if( (n = token->getChild("empty", false )).valid() ) {
|
||||
return checkEmptyCondition( n, true) ?
|
||||
processTokens(token->getChild("then",false)) :
|
||||
processTokens(token->getChild("else",false));
|
||||
}
|
||||
|
||||
if( (n = token->getChild("not-empty", false )).valid() ) {
|
||||
return checkEmptyCondition( n, false) ?
|
||||
processTokens(token->getChild("then",false)) :
|
||||
processTokens(token->getChild("else",false));
|
||||
}
|
||||
|
||||
if( (n = token->getChild("contains", false )).valid() ) {
|
||||
return checkCondition( n, true, &contains, "contains") ?
|
||||
processTokens(token->getChild("then",false)) :
|
||||
processTokens(token->getChild("else",false));
|
||||
}
|
||||
|
||||
if( (n = token->getChild("not-contains", false )).valid() ) {
|
||||
return checkCondition( n, false, &contains, "not-contains") ?
|
||||
processTokens(token->getChild("then",false)) :
|
||||
processTokens(token->getChild("else",false));
|
||||
}
|
||||
|
||||
if( (n = token->getChild("ends-with", false )).valid() ) {
|
||||
return checkCondition( n, true, &ends_with, "ends-with") ?
|
||||
processTokens(token->getChild("then",false)) :
|
||||
processTokens(token->getChild("else",false));
|
||||
}
|
||||
|
||||
if( (n = token->getChild("not-ends-with", false )).valid() ) {
|
||||
return checkCondition( n, false, &ends_with, "not-ends-with") ?
|
||||
processTokens(token->getChild("then",false)) :
|
||||
processTokens(token->getChild("else",false));
|
||||
}
|
||||
|
||||
if( (n = token->getChild("equals", false )).valid() ) {
|
||||
return checkCondition( n, true, &equals, "equals") ?
|
||||
processTokens(token->getChild("then",false)) :
|
||||
processTokens(token->getChild("else",false));
|
||||
}
|
||||
|
||||
if( (n = token->getChild("not-equals", false )).valid() ) {
|
||||
return checkCondition( n, false, &equals, "not-equals") ?
|
||||
processTokens(token->getChild("then",false)) :
|
||||
processTokens(token->getChild("else",false));
|
||||
}
|
||||
|
||||
if( (n = token->getChild("starts-with", false )).valid() ) {
|
||||
return checkCondition( n, true, &starts_with, "starts-with") ?
|
||||
processTokens(token->getChild("then",false)) :
|
||||
processTokens(token->getChild("else",false));
|
||||
}
|
||||
|
||||
if( (n = token->getChild("not-starts-with", false )).valid() ) {
|
||||
return checkCondition( n, false, &starts_with, "not-starts-with") ?
|
||||
processTokens(token->getChild("then",false)) :
|
||||
processTokens(token->getChild("else",false));
|
||||
}
|
||||
|
||||
SG_LOG(SG_ATC, SG_WARN, "ATISEncoder: no valid token found for <if> element" );
|
||||
|
||||
return EMPTY;
|
||||
}
|
||||
|
||||
bool ATISEncoder::checkEmptyCondition( SGPropertyNode_ptr node, bool isEmpty )
|
||||
{
|
||||
SGPropertyNode_ptr n1 = node->getNode( "token", false );
|
||||
if( !n1.valid() ) {
|
||||
SG_LOG(SG_ATC, SG_WARN, "missing <token> node for (not)-empty" );
|
||||
return false;
|
||||
}
|
||||
return processToken( n1 ).empty() == isEmpty;
|
||||
}
|
||||
|
||||
bool ATISEncoder::checkCondition( SGPropertyNode_ptr node, bool notInverted,
|
||||
bool (*fp)(const string &, const string &), const string &name )
|
||||
{
|
||||
using namespace simgear::strutils;
|
||||
|
||||
SGPropertyNode_ptr n1 = node->getNode( "token", 0, false );
|
||||
SGPropertyNode_ptr n2 = node->getNode( "token", 1, false );
|
||||
|
||||
if( n1.valid() && n2.valid() ) {
|
||||
bool comp = fp( processToken( n1 ), processToken( n2 ) );
|
||||
return comp == notInverted;
|
||||
}
|
||||
|
||||
if( n1.valid() && !n2.valid() ) {
|
||||
SGPropertyNode_ptr t1 = node->getNode( "text", 0, false );
|
||||
if( t1.valid() ) {
|
||||
string n1s = lowercase( strip( processToken( n1 ) ) );
|
||||
string t1s = lowercase( strip( processTextToken( t1 ) ) );
|
||||
return fp( n1s, t1s ) == notInverted;
|
||||
}
|
||||
SG_LOG(SG_ATC, SG_WARN, "missing <token> or <text> node for " << name);
|
||||
return false;
|
||||
}
|
||||
|
||||
SG_LOG(SG_ATC, SG_WARN, "missing <token> node for " << name);
|
||||
return false;
|
||||
}
|
||||
|
||||
string ATISEncoder::getAtisId( SGPropertyNode_ptr )
|
||||
{
|
||||
FGAirportDynamics * dynamics = airport->getDynamics();
|
||||
if( NULL != dynamics ) {
|
||||
dynamics->updateAtisSequence( 30*60, false );
|
||||
return dynamics->getAtisSequence();
|
||||
}
|
||||
return EMPTY;
|
||||
}
|
||||
|
||||
string ATISEncoder::getAirportName( SGPropertyNode_ptr )
|
||||
{
|
||||
return airport->getName();
|
||||
}
|
||||
|
||||
string ATISEncoder::getTime( SGPropertyNode_ptr )
|
||||
{
|
||||
return getSpokenNumber( _atis->getTime() % (100*100), true, 4 );
|
||||
}
|
||||
|
||||
static inline FGRunwayRef findBestRunwayForWind( FGAirportRef airport, int windDeg, int windKt )
|
||||
{
|
||||
struct FGAirport::FindBestRunwayForHeadingParams p;
|
||||
//TODO: ramp down the heading weight with wind speed
|
||||
p.ilsWeight = 4;
|
||||
return airport->findBestRunwayForHeading( windDeg, &p );
|
||||
}
|
||||
|
||||
string ATISEncoder::getApproachType( SGPropertyNode_ptr )
|
||||
{
|
||||
FGRunwayRef runway = findBestRunwayForWind( airport, _atis->getWindDeg(), _atis->getWindSpeedKt() );
|
||||
if( runway.valid() ) {
|
||||
if( NULL != runway->ILS() ) return globals->get_locale()->getLocalizedString("ils", "atc", "ils" );
|
||||
//TODO: any chance to find other approach types? localizer-dme, vor-dme, vor, ndb?
|
||||
}
|
||||
|
||||
return globals->get_locale()->getLocalizedString("visual", "atc", "visual" );
|
||||
}
|
||||
|
||||
string ATISEncoder::getLandingRunway( SGPropertyNode_ptr )
|
||||
{
|
||||
FGRunwayRef runway = findBestRunwayForWind( airport, _atis->getWindDeg(), _atis->getWindSpeedKt() );
|
||||
if( runway.valid() ) {
|
||||
string runwayIdent = runway->ident();
|
||||
if(runwayIdent != "NN") {
|
||||
return getSpokenNumber(runwayIdent);
|
||||
}
|
||||
}
|
||||
return EMPTY;
|
||||
}
|
||||
|
||||
string ATISEncoder::getTakeoffRunway( SGPropertyNode_ptr p )
|
||||
{
|
||||
//TODO: if the airport has more than one runway, probably pick another one?
|
||||
return getLandingRunway( p );
|
||||
}
|
||||
|
||||
string ATISEncoder::getTransitionLevel(SGPropertyNode_ptr)
|
||||
{
|
||||
double hPa = _atis->getQnh();
|
||||
|
||||
/* Transition level is the flight level above which aircraft must use standard pressure and below
|
||||
* which airport pressure settings must be used.
|
||||
* Following definitions are taken from German ATIS:
|
||||
* QNH <= 977 hPa: TRL 80
|
||||
* QNH <= 1013 hPa: TRL 70
|
||||
* QNH > 1013 hPa: TRL 60
|
||||
* (maybe differs slightly for other countries...)
|
||||
*/
|
||||
int tl;
|
||||
if (hPa <= 978) {
|
||||
tl = 80;
|
||||
} else if (hPa <= 1013) {
|
||||
tl = 70;
|
||||
} else if (hPa <= 1046) {
|
||||
tl = 60;
|
||||
} else {
|
||||
tl = 50;
|
||||
}
|
||||
|
||||
// add an offset to the transition level for high altitude airports (just guessing here,
|
||||
// seems reasonable)
|
||||
int e = int(airport->getElevation() / 1000.0);
|
||||
if (e >= 3) {
|
||||
// TL steps in 10(00)ft
|
||||
tl += (e - 2) * 10;
|
||||
}
|
||||
|
||||
return getSpokenNumber(tl);
|
||||
}
|
||||
|
||||
string ATISEncoder::getWindDirection( SGPropertyNode_ptr )
|
||||
{
|
||||
string variable = globals->get_locale()->getLocalizedString("variable", "atc", "variable" );
|
||||
|
||||
bool vrb = _atis->getWindMinDeg() == 0 && _atis->getWindMaxDeg() == 359;
|
||||
return vrb ? variable : getSpokenNumber( _atis->getWindDeg(), true, 3 );
|
||||
}
|
||||
|
||||
string ATISEncoder::getWindMinDirection( SGPropertyNode_ptr )
|
||||
{
|
||||
return getSpokenNumber( _atis->getWindMinDeg(), true, 3 );
|
||||
}
|
||||
|
||||
string ATISEncoder::getWindMaxDirection( SGPropertyNode_ptr )
|
||||
{
|
||||
return getSpokenNumber( _atis->getWindMaxDeg(), true, 3 );
|
||||
}
|
||||
|
||||
string ATISEncoder::getWindspeedKnots( SGPropertyNode_ptr )
|
||||
{
|
||||
return getSpokenNumber( _atis->getWindSpeedKt() );
|
||||
}
|
||||
|
||||
string ATISEncoder::getGustsKnots( SGPropertyNode_ptr )
|
||||
{
|
||||
int g = _atis->getGustsKt();
|
||||
return g > 0 ? getSpokenNumber( g ) : EMPTY;
|
||||
}
|
||||
|
||||
string ATISEncoder::getCavok( SGPropertyNode_ptr )
|
||||
{
|
||||
string CAVOK = globals->get_locale()->getLocalizedString("cavok", "atc", "cavok" );
|
||||
|
||||
return _atis->isCavok() ? CAVOK : EMPTY;
|
||||
}
|
||||
|
||||
string ATISEncoder::getVisibilityMetric( SGPropertyNode_ptr )
|
||||
{
|
||||
string m = globals->get_locale()->getLocalizedString("meters", "atc", "meters" );
|
||||
string km = globals->get_locale()->getLocalizedString("kilometers", "atc", "kilometers" );
|
||||
string or_more = globals->get_locale()->getLocalizedString("ormore", "atc", "or more" );
|
||||
|
||||
int v = _atis->getVisibilityMeters();
|
||||
string reply;
|
||||
if( v < 5000 ) return reply.append( getSpokenAltitude( v ) ).SPACE.append( m );
|
||||
if( v >= 9999 ) return reply.append( getSpokenNumber(10) ).SPACE.append( km ).SPACE.append(or_more);
|
||||
return reply.append( getSpokenNumber( v/1000 ).SPACE.append( km ) );
|
||||
}
|
||||
|
||||
string ATISEncoder::getVisibilityMiles( SGPropertyNode_ptr )
|
||||
{
|
||||
string feet = globals->get_locale()->getLocalizedString("feet", "atc", "feet" );
|
||||
|
||||
int v = _atis->getVisibilityMeters();
|
||||
int vft = int( v * SG_METER_TO_FEET / 100 + 0.5 ) * 100; // Rounded to 100 ft
|
||||
int vsm = int( v * SG_METER_TO_SM + 0.5 );
|
||||
|
||||
string reply;
|
||||
if( vsm < 1 ) return reply.append( getSpokenAltitude( vft ) ).SPACE.append( feet );
|
||||
if( vsm >= 10 ) return reply.append( getSpokenNumber(10) );
|
||||
return reply.append( getSpokenNumber( vsm ) );
|
||||
}
|
||||
|
||||
string ATISEncoder::getPhenomena( SGPropertyNode_ptr )
|
||||
{
|
||||
return _atis->getPhenomena();
|
||||
}
|
||||
|
||||
string ATISEncoder::getClouds( SGPropertyNode_ptr )
|
||||
{
|
||||
string FEET = globals->get_locale()->getLocalizedString("feet", "atc", "feet" );
|
||||
string reply;
|
||||
|
||||
ATISInformationProvider::CloudEntries cloudEntries = _atis->getClouds();
|
||||
|
||||
for (ATISInformationProvider::CloudEntries::iterator it = cloudEntries.begin(); it != cloudEntries.end(); ++it) {
|
||||
if (!reply.empty())
|
||||
reply.SPACE;
|
||||
reply.append( it->second ).SPACE.append( getSpokenAltitude(it->first).SPACE.append( FEET ) );
|
||||
}
|
||||
|
||||
return reply;
|
||||
}
|
||||
|
||||
string ATISEncoder::getCloudsBrief( SGPropertyNode_ptr )
|
||||
{
|
||||
string reply;
|
||||
|
||||
ATISInformationProvider::CloudEntries cloudEntries = _atis->getClouds();
|
||||
|
||||
for (ATISInformationProvider::CloudEntries::iterator it = cloudEntries.begin(); it != cloudEntries.end(); ++it) {
|
||||
if (!reply.empty())
|
||||
reply.append(",").SPACE;
|
||||
reply.append( it->second ).SPACE.append( getSpokenAltitude(it->first) );
|
||||
}
|
||||
|
||||
return reply;
|
||||
}
|
||||
|
||||
string ATISEncoder::getTemperatureDeg( SGPropertyNode_ptr )
|
||||
{
|
||||
return getSpokenNumber( _atis->getTemperatureDeg() );
|
||||
}
|
||||
|
||||
string ATISEncoder::getDewpointDeg( SGPropertyNode_ptr )
|
||||
{
|
||||
return getSpokenNumber( _atis->getDewpointDeg() );
|
||||
}
|
||||
|
||||
string ATISEncoder::getQnh( SGPropertyNode_ptr )
|
||||
{
|
||||
return getSpokenNumber( _atis->getQnh() );
|
||||
}
|
||||
|
||||
string ATISEncoder::getInhgInteger( SGPropertyNode_ptr )
|
||||
{
|
||||
double qnh = _atis->getQnhInHg();
|
||||
return getSpokenNumber( (int)qnh, true, 2 );
|
||||
}
|
||||
|
||||
string ATISEncoder::getInhgFraction( SGPropertyNode_ptr )
|
||||
{
|
||||
double qnh = _atis->getQnhInHg();
|
||||
int f = int(100 * (qnh - int(qnh)) + 0.5);
|
||||
return getSpokenNumber( f, true, 2 );
|
||||
}
|
||||
|
||||
string ATISEncoder::getInhg( SGPropertyNode_ptr node)
|
||||
{
|
||||
string DECIMAL = globals->get_locale()->getLocalizedString("dp", "atc", "decimal" );
|
||||
return getInhgInteger(node)
|
||||
.SPACE.append(DECIMAL).SPACE
|
||||
.append(getInhgFraction(node));
|
||||
}
|
||||
|
||||
string ATISEncoder::getTrend( SGPropertyNode_ptr )
|
||||
{
|
||||
return _atis->getTrend();
|
||||
}
|
||||
|
||||
133
src/ATC/ATISEncoder.hxx
Normal file
133
src/ATC/ATISEncoder.hxx
Normal file
@@ -0,0 +1,133 @@
|
||||
/*
|
||||
Encode an ATIS into spoken words
|
||||
Copyright (C) 2014 Torsten Dreyer
|
||||
|
||||
This program is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU General Public License
|
||||
as published by the Free Software Foundation; either version 2
|
||||
of the License, or (at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#ifndef __ATIS_ENCODER_HXX
|
||||
#define __ATIS_ENCODER_HXX
|
||||
|
||||
#include <string>
|
||||
#include <Airports/airport.hxx>
|
||||
#include <simgear/props/props.hxx>
|
||||
#include <map>
|
||||
|
||||
class ATCSpeech {
|
||||
public:
|
||||
static std::string getSpokenDigit( int i );
|
||||
static std::string getSpokenNumber( std::string number );
|
||||
static std::string getSpokenNumber( int number, bool leadingZero = false, int digits = 1 );
|
||||
static std::string getSpokenAltitude( int altitude );
|
||||
};
|
||||
|
||||
class ATISInformationProvider {
|
||||
public:
|
||||
virtual ~ATISInformationProvider() {}
|
||||
virtual bool isValid() = 0;
|
||||
virtual std::string airportId() = 0;
|
||||
|
||||
static long makeAtisTime( int day, int hour, int minute ) {
|
||||
return 100*100l* day + 100l * hour + minute;
|
||||
}
|
||||
inline int getAtisTimeDay( long atisTime ) { return atisTime / (100l*100l); }
|
||||
inline int getAtisTimeHour( long atisTime ) { return (atisTime % (100l*100l)) / 100l; }
|
||||
inline int getAtisTimeMinute( long atisTime ) { return atisTime % 100l; }
|
||||
virtual long getTime() = 0; // see makeAtisTime
|
||||
|
||||
virtual int getWindDeg() = 0;
|
||||
virtual int getWindMinDeg() = 0;
|
||||
virtual int getWindMaxDeg() = 0;
|
||||
virtual int getWindSpeedKt() = 0;
|
||||
virtual int getGustsKt() = 0;
|
||||
virtual int getQnh() = 0;
|
||||
virtual double getQnhInHg() = 0;
|
||||
virtual bool isCavok() = 0;
|
||||
virtual int getVisibilityMeters() = 0;
|
||||
virtual std::string getPhenomena() = 0;
|
||||
|
||||
typedef std::map<int,std::string> CloudEntries;
|
||||
virtual CloudEntries getClouds() = 0;
|
||||
virtual int getTemperatureDeg() = 0;
|
||||
virtual int getDewpointDeg() = 0;
|
||||
virtual std::string getTrend() = 0;
|
||||
};
|
||||
|
||||
class ATISEncoder : public ATCSpeech {
|
||||
public:
|
||||
ATISEncoder();
|
||||
virtual ~ATISEncoder();
|
||||
virtual std::string encodeATIS( ATISInformationProvider * atisInformationProvider );
|
||||
|
||||
protected:
|
||||
virtual std::string getAtisId( SGPropertyNode_ptr );
|
||||
virtual std::string getAirportName( SGPropertyNode_ptr );
|
||||
virtual std::string getTime( SGPropertyNode_ptr );
|
||||
virtual std::string getApproachType( SGPropertyNode_ptr );
|
||||
virtual std::string getLandingRunway( SGPropertyNode_ptr );
|
||||
virtual std::string getTakeoffRunway( SGPropertyNode_ptr );
|
||||
virtual std::string getTransitionLevel( SGPropertyNode_ptr );
|
||||
virtual std::string getWindDirection( SGPropertyNode_ptr );
|
||||
virtual std::string getWindMinDirection( SGPropertyNode_ptr );
|
||||
virtual std::string getWindMaxDirection( SGPropertyNode_ptr );
|
||||
virtual std::string getWindspeedKnots( SGPropertyNode_ptr );
|
||||
virtual std::string getGustsKnots( SGPropertyNode_ptr );
|
||||
virtual std::string getCavok( SGPropertyNode_ptr );
|
||||
virtual std::string getVisibilityMetric( SGPropertyNode_ptr );
|
||||
virtual std::string getVisibilityMiles( SGPropertyNode_ptr );
|
||||
virtual std::string getPhenomena( SGPropertyNode_ptr );
|
||||
virtual std::string getClouds( SGPropertyNode_ptr );
|
||||
virtual std::string getCloudsBrief( SGPropertyNode_ptr );
|
||||
virtual std::string getTemperatureDeg( SGPropertyNode_ptr );
|
||||
virtual std::string getDewpointDeg( SGPropertyNode_ptr );
|
||||
virtual std::string getQnh( SGPropertyNode_ptr );
|
||||
virtual std::string getInhgInteger( SGPropertyNode_ptr );
|
||||
virtual std::string getInhgFraction( SGPropertyNode_ptr );
|
||||
virtual std::string getInhg( SGPropertyNode_ptr );
|
||||
virtual std::string getTrend( SGPropertyNode_ptr );
|
||||
|
||||
typedef std::string (ATISEncoder::*handler_t)( SGPropertyNode_ptr baseNode );
|
||||
typedef std::map<std::string, handler_t > HandlerMap;
|
||||
HandlerMap handlerMap;
|
||||
|
||||
SGPropertyNode_ptr atisSchemaNode;
|
||||
|
||||
std::string processTokens( SGPropertyNode_ptr baseNode );
|
||||
std::string processToken( SGPropertyNode_ptr baseNode );
|
||||
|
||||
std::string processTextToken( SGPropertyNode_ptr baseNode );
|
||||
std::string processTokenToken( SGPropertyNode_ptr baseNode );
|
||||
std::string processIfToken( SGPropertyNode_ptr baseNode );
|
||||
|
||||
bool checkEmptyCondition( SGPropertyNode_ptr node, bool isEmpty );
|
||||
|
||||
// Wrappers that can be passed as function pointers to checkCondition
|
||||
// @see simgear::strutils::starts_with
|
||||
// @see simgear::strutils::ends_with
|
||||
static bool contains(const string &s, const string &substring)
|
||||
{ return s.find(substring) != std::string::npos; };
|
||||
static bool equals(const string &s1, const string &s2)
|
||||
{ return s1 == s2; };
|
||||
|
||||
bool checkCondition( SGPropertyNode_ptr node, bool notInverted,
|
||||
bool (*fp)(const std::string &, const std::string &),
|
||||
const std::string &name );
|
||||
|
||||
FGAirportRef airport;
|
||||
ATISInformationProvider * _atis;
|
||||
|
||||
};
|
||||
|
||||
#endif
|
||||
195
src/ATC/ApproachController.cxx
Normal file
195
src/ATC/ApproachController.cxx
Normal file
@@ -0,0 +1,195 @@
|
||||
// Extracted from trafficrecord.cxx - Implementation of AIModels ATC code.
|
||||
//
|
||||
// Written by Durk Talsma, started September 2006.
|
||||
//
|
||||
// Copyright (C) 2006 Durk Talsma.
|
||||
//
|
||||
// This program is free software; you can redistribute it and/or
|
||||
// modify it under the terms of the GNU General Public License as
|
||||
// published by the Free Software Foundation; either version 2 of the
|
||||
// License, or (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful, but
|
||||
// WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program; if not, write to the Free Software
|
||||
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
//
|
||||
// $Id$
|
||||
|
||||
#include <config.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdio>
|
||||
#include <random>
|
||||
|
||||
#include <osg/Geode>
|
||||
#include <osg/Geometry>
|
||||
#include <osg/MatrixTransform>
|
||||
#include <osg/Shape>
|
||||
|
||||
#include <simgear/scene/material/EffectGeode.hxx>
|
||||
#include <simgear/scene/material/matlib.hxx>
|
||||
#include <simgear/scene/material/mat.hxx>
|
||||
#include <simgear/scene/util/OsgMath.hxx>
|
||||
#include <simgear/timing/sg_time.hxx>
|
||||
|
||||
#include <Scenery/scenery.hxx>
|
||||
|
||||
#include "trafficcontrol.hxx"
|
||||
#include "atc_mgr.hxx"
|
||||
#include <AIModel/AIAircraft.hxx>
|
||||
#include <AIModel/AIFlightPlan.hxx>
|
||||
#include <AIModel/performancedata.hxx>
|
||||
#include <Traffic/TrafficMgr.hxx>
|
||||
#include <Airports/groundnetwork.hxx>
|
||||
#include <Airports/dynamics.hxx>
|
||||
#include <Airports/airport.hxx>
|
||||
#include <Radio/radio.hxx>
|
||||
#include <signal.h>
|
||||
|
||||
#include <ATC/atc_mgr.hxx>
|
||||
#include <ATC/trafficcontrol.hxx>
|
||||
#include <ATC/ATCController.hxx>
|
||||
#include <ATC/ApproachController.hxx>
|
||||
|
||||
using std::sort;
|
||||
using std::string;
|
||||
|
||||
/***************************************************************************
|
||||
* class FGApproachController
|
||||
* subclass of FGATCController
|
||||
**************************************************************************/
|
||||
|
||||
FGApproachController::FGApproachController(FGAirportDynamics *par):
|
||||
FGATCController()
|
||||
{
|
||||
parent = par;
|
||||
}
|
||||
|
||||
FGApproachController::~FGApproachController()
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
void FGApproachController::announcePosition(int id,
|
||||
FGAIFlightPlan * intendedRoute,
|
||||
int currentPosition,
|
||||
double lat, double lon,
|
||||
double heading, double speed,
|
||||
double alt, double radius,
|
||||
int leg, FGAIAircraft * ref)
|
||||
{
|
||||
init();
|
||||
|
||||
// Search activeTraffic for a record matching our id
|
||||
TrafficVectorIterator i = FGATCController::searchActiveTraffic(id);
|
||||
|
||||
// Add a new TrafficRecord if no one exsists for this aircraft.
|
||||
if (i == activeTraffic.end() || activeTraffic.empty()) {
|
||||
FGTrafficRecord rec;
|
||||
rec.setId(id);
|
||||
|
||||
rec.setPositionAndHeading(lat, lon, heading, speed, alt);
|
||||
rec.setRunway(intendedRoute->getRunway());
|
||||
rec.setLeg(leg);
|
||||
rec.setCallsign(ref->getCallSign());
|
||||
rec.setAircraft(ref);
|
||||
activeTraffic.push_back(rec);
|
||||
} else {
|
||||
i->setPositionAndHeading(lat, lon, heading, speed, alt);
|
||||
}
|
||||
}
|
||||
|
||||
void FGApproachController::updateAircraftInformation(int id, SGGeod geod,
|
||||
double heading, double speed, double alt,
|
||||
double dt)
|
||||
{
|
||||
// Search activeTraffic for a record matching our id
|
||||
TrafficVectorIterator i = FGATCController::searchActiveTraffic(id);
|
||||
TrafficVectorIterator current;
|
||||
|
||||
// update position of the current aircraft
|
||||
if (i == activeTraffic.end() || activeTraffic.empty()) {
|
||||
SG_LOG(SG_ATC, SG_ALERT,
|
||||
"AI error: updating aircraft without traffic record at " << SG_ORIGIN);
|
||||
} else {
|
||||
i->setPositionAndHeading(geod.getLatitudeDeg(), geod.getLongitudeDeg(), heading, speed, alt);
|
||||
current = i;
|
||||
if(current->getAircraft()) {
|
||||
//FIXME No call to aircraft! -> set instruction
|
||||
time_t time_diff =
|
||||
current->getAircraft()->
|
||||
checkForArrivalTime(string("final001"));
|
||||
if (time_diff != 0) {
|
||||
SG_LOG(SG_ATC, SG_BULK, current->getCallsign() << "|ApproachController: checking for speed " << time_diff);
|
||||
}
|
||||
if (time_diff > 15) {
|
||||
current->setSpeedAdjustment(current->getAircraft()->
|
||||
getPerformance()->vDescent() *
|
||||
1.35);
|
||||
} else if (time_diff > 5) {
|
||||
current->setSpeedAdjustment(current->getAircraft()->
|
||||
getPerformance()->vDescent() *
|
||||
1.2);
|
||||
} else if (time_diff < -15) {
|
||||
current->setSpeedAdjustment(current->getAircraft()->
|
||||
getPerformance()->vDescent() *
|
||||
0.65);
|
||||
} else if (time_diff < -5) {
|
||||
current->setSpeedAdjustment(current->getAircraft()->
|
||||
getPerformance()->vDescent() *
|
||||
0.8);
|
||||
} else {
|
||||
current->clearSpeedAdjustment();
|
||||
}
|
||||
}
|
||||
//current->setSpeedAdjustment(current->getAircraft()->getPerformance()->vDescent() + time_diff);
|
||||
}
|
||||
setDt(getDt() + dt);
|
||||
}
|
||||
|
||||
/* Periodically check for and remove dead traffic records */
|
||||
void FGApproachController::update(double dt)
|
||||
{
|
||||
FGATCController::eraseDeadTraffic();
|
||||
}
|
||||
|
||||
|
||||
|
||||
ActiveRunway *FGApproachController::getRunway(const string& name)
|
||||
{
|
||||
ActiveRunwayVecIterator rwy = activeRunways.begin();
|
||||
if (activeRunways.size()) {
|
||||
while (rwy != activeRunways.end()) {
|
||||
if (rwy->getRunwayName() == name) {
|
||||
break;
|
||||
}
|
||||
rwy++;
|
||||
}
|
||||
}
|
||||
if (rwy == activeRunways.end()) {
|
||||
ActiveRunway aRwy(name, 0);
|
||||
activeRunways.push_back(aRwy);
|
||||
rwy = activeRunways.end() - 1;
|
||||
}
|
||||
return &(*rwy);
|
||||
}
|
||||
|
||||
void FGApproachController::render(bool visible) {
|
||||
// Must be BULK in order to prevent it being called each frame
|
||||
SG_LOG(SG_ATC, SG_BULK, "FGApproachController::render function not yet implemented");
|
||||
}
|
||||
|
||||
string FGApproachController::getName() {
|
||||
return string(parent->parent()->getName() + "-approach");
|
||||
}
|
||||
|
||||
int FGApproachController::getFrequency() {
|
||||
int groundFreq = parent->getApproachFrequency(2);
|
||||
int towerFreq = parent->getTowerFrequency(2);
|
||||
return groundFreq>0?groundFreq:towerFreq;
|
||||
}
|
||||
67
src/ATC/ApproachController.hxx
Normal file
67
src/ATC/ApproachController.hxx
Normal file
@@ -0,0 +1,67 @@
|
||||
// Extracted from trafficcontrol.hxx - classes to manage AIModels based air traffic control
|
||||
// Written by Durk Talsma, started September 2006.
|
||||
//
|
||||
// This program is free software; you can redistribute it and/or
|
||||
// modify it under the terms of the GNU General Public License as
|
||||
// published by the Free Software Foundation; either version 2 of the
|
||||
// License, or (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful, but
|
||||
// WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program; if not, write to the Free Software
|
||||
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
//
|
||||
// $Id$
|
||||
|
||||
#ifndef APPROACH_CONTROLLER_HXX
|
||||
#define APPROACH_CONTROLLER_HXX
|
||||
|
||||
#include <Airports/airports_fwd.hxx>
|
||||
|
||||
#include <osg/Geode>
|
||||
#include <osg/Geometry>
|
||||
#include <osg/MatrixTransform>
|
||||
#include <osg/Shape>
|
||||
|
||||
#include <simgear/compiler.h>
|
||||
#include <simgear/constants.h>
|
||||
#include <simgear/debug/logstream.hxx>
|
||||
#include <simgear/structure/SGReferenced.hxx>
|
||||
#include <simgear/structure/SGSharedPtr.hxx>
|
||||
|
||||
#include <ATC/ATCController.hxx>
|
||||
#include <ATC/trafficcontrol.hxx>
|
||||
|
||||
/******************************************************************************
|
||||
* class FGApproachController
|
||||
*****************************************************************************/
|
||||
class FGApproachController : public FGATCController
|
||||
{
|
||||
private:
|
||||
ActiveRunwayVec activeRunways;
|
||||
/**Returns the frequency to be used. */
|
||||
int getFrequency();
|
||||
public:
|
||||
FGApproachController(FGAirportDynamics * parent);
|
||||
virtual ~FGApproachController();
|
||||
|
||||
virtual void announcePosition(int id, FGAIFlightPlan *intendedRoute, int currentRoute,
|
||||
double lat, double lon,
|
||||
double hdg, double spd, double alt, double radius, int leg,
|
||||
FGAIAircraft *aircraft);
|
||||
virtual void updateAircraftInformation(int id, SGGeod geod,
|
||||
double heading, double speed, double alt, double dt);
|
||||
|
||||
virtual void render(bool);
|
||||
virtual std::string getName();
|
||||
virtual void update(double dt);
|
||||
|
||||
ActiveRunway* getRunway(const std::string& name);
|
||||
|
||||
};
|
||||
|
||||
#endif
|
||||
31
src/ATC/CMakeLists.txt
Normal file
31
src/ATC/CMakeLists.txt
Normal file
@@ -0,0 +1,31 @@
|
||||
include(FlightGearComponent)
|
||||
|
||||
set(SOURCES
|
||||
atc_mgr.cxx
|
||||
trafficcontrol.cxx
|
||||
CommStation.cxx
|
||||
ATISEncoder.cxx
|
||||
MetarPropertiesATISInformationProvider.cxx
|
||||
CurrentWeatherATISInformationProvider.cxx
|
||||
ATCController.cxx
|
||||
ApproachController.cxx
|
||||
GroundController.cxx
|
||||
StartupController.cxx
|
||||
TowerController.cxx
|
||||
)
|
||||
|
||||
set(HEADERS
|
||||
atc_mgr.hxx
|
||||
trafficcontrol.hxx
|
||||
CommStation.hxx
|
||||
ATISEncoder.hxx
|
||||
MetarPropertiesATISInformationProvider.hxx
|
||||
CurrentWeatherATISInformationProvider.hxx
|
||||
ATCController.hxx
|
||||
ApproachController.hxx
|
||||
GroundController.hxx
|
||||
StartupController.hxx
|
||||
TowerController.hxx
|
||||
)
|
||||
|
||||
flightgear_component(ATC "${SOURCES}" "${HEADERS}")
|
||||
38
src/ATC/CommStation.cxx
Normal file
38
src/ATC/CommStation.cxx
Normal file
@@ -0,0 +1,38 @@
|
||||
#include "config.h"
|
||||
|
||||
#include "CommStation.hxx"
|
||||
#include <Airports/airport.hxx>
|
||||
#include <Navaids/NavDataCache.hxx>
|
||||
|
||||
namespace flightgear {
|
||||
|
||||
CommStation::CommStation(PositionedID aGuid, const std::string& name, FGPositioned::Type t, const SGGeod& pos, int range, int freq) :
|
||||
FGPositioned(aGuid, t, name, pos),
|
||||
mRangeNM(range),
|
||||
mFreqKhz(freq),
|
||||
mAirport(0)
|
||||
{
|
||||
}
|
||||
|
||||
void CommStation::setAirport(PositionedID apt)
|
||||
{
|
||||
mAirport = apt;
|
||||
}
|
||||
|
||||
FGAirportRef CommStation::airport() const
|
||||
{
|
||||
return FGPositioned::loadById<FGAirport>(mAirport);
|
||||
}
|
||||
|
||||
double CommStation::freqMHz() const
|
||||
{
|
||||
return mFreqKhz / 1000.0;
|
||||
}
|
||||
|
||||
CommStationRef
|
||||
CommStation::findByFreq(int freqKhz, const SGGeod& pos, FGPositioned::Filter* filt)
|
||||
{
|
||||
return (CommStation*) NavDataCache::instance()->findCommByFreq(freqKhz, pos, filt).ptr();
|
||||
}
|
||||
|
||||
} // of namespace flightgear
|
||||
36
src/ATC/CommStation.hxx
Normal file
36
src/ATC/CommStation.hxx
Normal file
@@ -0,0 +1,36 @@
|
||||
#ifndef FG_ATC_COMM_STATION_HXX
|
||||
#define FG_ATC_COMM_STATION_HXX
|
||||
|
||||
#include <Airports/airports_fwd.hxx>
|
||||
#include <Navaids/positioned.hxx>
|
||||
|
||||
namespace flightgear
|
||||
{
|
||||
|
||||
class CommStation : public FGPositioned
|
||||
{
|
||||
public:
|
||||
CommStation(PositionedID aGuid, const std::string& name, FGPositioned::Type t, const SGGeod& pos, int range, int freq);
|
||||
|
||||
void setAirport(PositionedID apt);
|
||||
FGAirportRef airport() const;
|
||||
|
||||
int rangeNm() const
|
||||
{ return mRangeNM; }
|
||||
|
||||
int freqKHz() const
|
||||
{ return mFreqKhz; }
|
||||
|
||||
double freqMHz() const;
|
||||
|
||||
static CommStationRef findByFreq(int freqKhz, const SGGeod& pos, FGPositioned::Filter* filt = NULL);
|
||||
private:
|
||||
int mRangeNM;
|
||||
int mFreqKhz;
|
||||
PositionedID mAirport;
|
||||
};
|
||||
|
||||
} // of namespace flightgear
|
||||
|
||||
#endif // of FG_ATC_COMM_STATION_HXX
|
||||
|
||||
145
src/ATC/CurrentWeatherATISInformationProvider.cxx
Normal file
145
src/ATC/CurrentWeatherATISInformationProvider.cxx
Normal file
@@ -0,0 +1,145 @@
|
||||
/*
|
||||
Provide Data for the ATIS Encoder from metarproperties
|
||||
Copyright (C) 2014 Torsten Dreyer
|
||||
|
||||
This program is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU General Public License
|
||||
as published by the Free Software Foundation; either version 2
|
||||
of the License, or (at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#include "CurrentWeatherATISInformationProvider.hxx"
|
||||
#include <Main/fg_props.hxx>
|
||||
|
||||
using std::string;
|
||||
|
||||
CurrentWeatherATISInformationProvider::CurrentWeatherATISInformationProvider( const std::string & airportId ) :
|
||||
_airportId(airportId),
|
||||
_environment(fgGetNode("/environment"))
|
||||
{
|
||||
}
|
||||
|
||||
static inline int roundToInt( double d )
|
||||
{
|
||||
return (static_cast<int>(10.0 * (d + .5))) / 10;
|
||||
}
|
||||
|
||||
static inline int roundToInt( SGPropertyNode_ptr n )
|
||||
{
|
||||
return roundToInt( n->getDoubleValue() );
|
||||
}
|
||||
|
||||
static inline int roundToInt( SGPropertyNode_ptr n, const char * child )
|
||||
{
|
||||
return roundToInt( n->getNode(child,true) );
|
||||
}
|
||||
|
||||
|
||||
CurrentWeatherATISInformationProvider::~CurrentWeatherATISInformationProvider()
|
||||
{
|
||||
}
|
||||
|
||||
bool CurrentWeatherATISInformationProvider::isValid()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
string CurrentWeatherATISInformationProvider::airportId()
|
||||
{
|
||||
return _airportId;
|
||||
}
|
||||
|
||||
long CurrentWeatherATISInformationProvider::getTime()
|
||||
{
|
||||
int h = fgGetInt( "/sim/time/utc/hour", 12 );
|
||||
int m = 20 + fgGetInt( "/sim/time/utc/minute", 0 ) / 30 ; // fake twice per hour
|
||||
return makeAtisTime( 0, h, m );
|
||||
}
|
||||
|
||||
int CurrentWeatherATISInformationProvider::getWindDeg()
|
||||
{
|
||||
// round to 10 degs
|
||||
int i = 5 + roundToInt( _environment->getNode("config/boundary/entry[0]/wind-from-heading-deg",true) );
|
||||
i /= 10;
|
||||
return i*10;
|
||||
}
|
||||
|
||||
int CurrentWeatherATISInformationProvider::getWindSpeedKt()
|
||||
{
|
||||
return roundToInt( _environment, "config/boundary/entry[0]/wind-speed-kt" );
|
||||
}
|
||||
|
||||
int CurrentWeatherATISInformationProvider::getGustsKt()
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
int CurrentWeatherATISInformationProvider::getQnh()
|
||||
{
|
||||
// TODO: Calculate QNH correctly from environment
|
||||
return roundToInt( _environment->getNode("pressure-sea-level-inhg",true)->getDoubleValue() * SG_INHG_TO_PA / 100 );
|
||||
}
|
||||
|
||||
double CurrentWeatherATISInformationProvider::getQnhInHg()
|
||||
{
|
||||
// TODO: Calculate QNH correctly from environment
|
||||
return _environment->getNode("pressure-sea-level-inhg",true)->getDoubleValue();
|
||||
}
|
||||
|
||||
bool CurrentWeatherATISInformationProvider::isCavok()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
int CurrentWeatherATISInformationProvider::getVisibilityMeters()
|
||||
{
|
||||
return roundToInt( _environment, "ground-visibility-m" );
|
||||
}
|
||||
|
||||
string CurrentWeatherATISInformationProvider::getPhenomena()
|
||||
{
|
||||
return "";
|
||||
}
|
||||
|
||||
ATISInformationProvider::CloudEntries CurrentWeatherATISInformationProvider::getClouds()
|
||||
{
|
||||
using simgear::PropertyList;
|
||||
|
||||
ATISInformationProvider::CloudEntries cloudEntries;
|
||||
PropertyList layers = _environment->getNode("clouds",true)->getChildren("layer");
|
||||
for( PropertyList::iterator it = layers.begin(); it != layers.end(); ++it ) {
|
||||
string coverage = (*it)->getStringValue( "coverage", "clear" );
|
||||
int alt = roundToInt( (*it)->getDoubleValue("elevation-ft", -9999 ) ) / 100;
|
||||
alt *= 100;
|
||||
|
||||
if( coverage != "clear" && alt > 0 )
|
||||
cloudEntries[alt] = coverage;
|
||||
|
||||
}
|
||||
return cloudEntries;
|
||||
}
|
||||
|
||||
int CurrentWeatherATISInformationProvider::getTemperatureDeg()
|
||||
{
|
||||
return roundToInt( _environment, "temperature-sea-level-degc" );
|
||||
}
|
||||
|
||||
int CurrentWeatherATISInformationProvider::getDewpointDeg()
|
||||
{
|
||||
return roundToInt( _environment, "dewpoint-sea-level-degc" );
|
||||
}
|
||||
|
||||
string CurrentWeatherATISInformationProvider::getTrend()
|
||||
{
|
||||
return "nosig";
|
||||
}
|
||||
|
||||
58
src/ATC/CurrentWeatherATISInformationProvider.hxx
Normal file
58
src/ATC/CurrentWeatherATISInformationProvider.hxx
Normal file
@@ -0,0 +1,58 @@
|
||||
/*
|
||||
Provide Data for the ATIS Encoder from metarproperties
|
||||
Copyright (C) 2014 Torsten Dreyer
|
||||
|
||||
This program is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU General Public License
|
||||
as published by the Free Software Foundation; either version 2
|
||||
of the License, or (at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#ifndef __CURRENTWEATHER_ATIS_ENCODER_HXX
|
||||
#define __CURRENTWEATHER_ATIS_ENCODER_HXX
|
||||
|
||||
/* ATIS encoder from current weather */
|
||||
|
||||
#include <string>
|
||||
#include "ATISEncoder.hxx"
|
||||
|
||||
class CurrentWeatherATISInformationProvider : public ATISInformationProvider
|
||||
{
|
||||
public:
|
||||
CurrentWeatherATISInformationProvider( const std::string & airportId );
|
||||
virtual ~CurrentWeatherATISInformationProvider();
|
||||
|
||||
protected:
|
||||
virtual bool isValid();
|
||||
virtual std::string airportId();
|
||||
virtual long getTime();
|
||||
virtual int getWindDeg();
|
||||
virtual int getWindMinDeg() { return getWindDeg(); }
|
||||
virtual int getWindMaxDeg() { return getWindDeg(); }
|
||||
virtual int getWindSpeedKt();
|
||||
virtual int getGustsKt();
|
||||
virtual int getQnh();
|
||||
virtual double getQnhInHg();
|
||||
virtual bool isCavok();
|
||||
virtual int getVisibilityMeters();
|
||||
virtual std::string getPhenomena();
|
||||
virtual CloudEntries getClouds();
|
||||
virtual int getTemperatureDeg();
|
||||
virtual int getDewpointDeg();
|
||||
virtual std::string getTrend();
|
||||
private:
|
||||
std::string _airportId;
|
||||
SGPropertyNode_ptr _environment;
|
||||
|
||||
};
|
||||
|
||||
#endif
|
||||
1002
src/ATC/GroundController.cxx
Normal file
1002
src/ATC/GroundController.cxx
Normal file
File diff suppressed because it is too large
Load Diff
90
src/ATC/GroundController.hxx
Normal file
90
src/ATC/GroundController.hxx
Normal file
@@ -0,0 +1,90 @@
|
||||
// GroundController.hxx - forked from groundnetwork.cxx
|
||||
//
|
||||
// Written by Durk Talsma, started June 2005.
|
||||
//
|
||||
// Copyright (C) 2004 Durk Talsma.
|
||||
//
|
||||
// This program is free software; you can redistribute it and/or
|
||||
// modify it under the terms of the GNU General Public License as
|
||||
// published by the Free Software Foundation; either version 2 of the
|
||||
// License, or (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful, but
|
||||
// WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program; if not, write to the Free Software
|
||||
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
//
|
||||
// $Id$
|
||||
|
||||
#ifndef ATC_GROUND_CONTROLLER_HXX
|
||||
#define ATC_GROUND_CONTROLLER_HXX
|
||||
|
||||
#include <simgear/compiler.h>
|
||||
|
||||
#include <string>
|
||||
|
||||
#include <ATC/trafficcontrol.hxx>
|
||||
|
||||
class FGAirportDynamics;
|
||||
|
||||
/**************************************************************************************
|
||||
* class FGGroundController
|
||||
*************************************************************************************/
|
||||
class FGGroundController : public FGATCController
|
||||
{
|
||||
private:
|
||||
|
||||
bool hasNetwork;
|
||||
bool networkInitialized;
|
||||
int count;
|
||||
int version;
|
||||
|
||||
FGTowerController *towerController;
|
||||
/**Returns the frequency to be used. */
|
||||
int getFrequency();
|
||||
|
||||
|
||||
void checkSpeedAdjustment(int id, double lat, double lon,
|
||||
double heading, double speed, double alt);
|
||||
void checkHoldPosition(int id, double lat, double lon,
|
||||
double heading, double speed, double alt);
|
||||
|
||||
|
||||
void updateStartupTraffic(TrafficVectorIterator i, int& priority, time_t now);
|
||||
bool updateActiveTraffic(TrafficVectorIterator i, int& priority, time_t now);
|
||||
public:
|
||||
FGGroundController(FGAirportDynamics *par);
|
||||
~FGGroundController();
|
||||
|
||||
void setVersion (int v) { version = v;};
|
||||
int getVersion() { return version; };
|
||||
|
||||
bool exists() {
|
||||
return hasNetwork;
|
||||
};
|
||||
void setTowerController(FGTowerController *twrCtrlr) {
|
||||
towerController = twrCtrlr;
|
||||
};
|
||||
|
||||
|
||||
virtual void announcePosition(int id, FGAIFlightPlan *intendedRoute, int currentRoute,
|
||||
double lat, double lon, double hdg, double spd, double alt,
|
||||
double radius, int leg, FGAIAircraft *aircraft);
|
||||
virtual void updateAircraftInformation(int id, SGGeod geod, double heading, double speed, double alt, double dt);
|
||||
|
||||
bool checkTransmissionState(int minState, int MaxState, TrafficVectorIterator i, time_t now, AtcMsgId msgId,
|
||||
AtcMsgDir msgDir);
|
||||
bool checkForCircularWaits(int id);
|
||||
virtual void render(bool);
|
||||
virtual std::string getName();
|
||||
virtual void update(double dt);
|
||||
|
||||
void addVersion(int v) {version = v; };
|
||||
};
|
||||
|
||||
|
||||
#endif
|
||||
130
src/ATC/MetarPropertiesATISInformationProvider.cxx
Normal file
130
src/ATC/MetarPropertiesATISInformationProvider.cxx
Normal file
@@ -0,0 +1,130 @@
|
||||
/*
|
||||
Provide Data for the ATIS Encoder from metarproperties
|
||||
Copyright (C) 2014 Torsten Dreyer
|
||||
|
||||
This program is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU General Public License
|
||||
as published by the Free Software Foundation; either version 2
|
||||
of the License, or (at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#include "MetarPropertiesATISInformationProvider.hxx"
|
||||
#include <Main/globals.hxx>
|
||||
#include <simgear/constants.h>
|
||||
|
||||
using std::string;
|
||||
|
||||
MetarPropertiesATISInformationProvider::MetarPropertiesATISInformationProvider( SGPropertyNode_ptr metar ) :
|
||||
_metar( metar )
|
||||
{
|
||||
}
|
||||
|
||||
MetarPropertiesATISInformationProvider::~MetarPropertiesATISInformationProvider()
|
||||
{
|
||||
}
|
||||
|
||||
bool MetarPropertiesATISInformationProvider::isValid()
|
||||
{
|
||||
return _metar->getBoolValue( "valid", false );
|
||||
}
|
||||
|
||||
string MetarPropertiesATISInformationProvider::airportId()
|
||||
{
|
||||
return _metar->getStringValue( "station-id", "xxxx" );
|
||||
}
|
||||
|
||||
long MetarPropertiesATISInformationProvider::getTime()
|
||||
{
|
||||
return makeAtisTime( 0,
|
||||
_metar->getIntValue( "hour" ) % 24,
|
||||
_metar->getIntValue( "minute" ) % 60 );
|
||||
}
|
||||
|
||||
int MetarPropertiesATISInformationProvider::getWindDeg()
|
||||
{
|
||||
return _metar->getIntValue( "base-wind-dir-deg" );
|
||||
}
|
||||
|
||||
int MetarPropertiesATISInformationProvider::getWindMinDeg()
|
||||
{
|
||||
return _metar->getIntValue( "base-wind-range-from" );
|
||||
}
|
||||
int MetarPropertiesATISInformationProvider::getWindMaxDeg()
|
||||
{
|
||||
return _metar->getIntValue( "base-wind-range-to" );
|
||||
}
|
||||
int MetarPropertiesATISInformationProvider::getWindSpeedKt()
|
||||
{
|
||||
return _metar->getIntValue( "base-wind-speed-kt" );
|
||||
}
|
||||
|
||||
int MetarPropertiesATISInformationProvider::getGustsKt()
|
||||
{
|
||||
return _metar->getIntValue( "gust-wind-speed-kt" );
|
||||
}
|
||||
|
||||
int MetarPropertiesATISInformationProvider::getQnh()
|
||||
{
|
||||
return _metar->getDoubleValue("pressure-inhg") * SG_INHG_TO_PA / 100.0;
|
||||
}
|
||||
|
||||
double MetarPropertiesATISInformationProvider::getQnhInHg()
|
||||
{
|
||||
return _metar->getDoubleValue("pressure-inhg");
|
||||
}
|
||||
|
||||
bool MetarPropertiesATISInformationProvider::isCavok()
|
||||
{
|
||||
return _metar->getBoolValue( "cavok" );
|
||||
}
|
||||
|
||||
int MetarPropertiesATISInformationProvider::getVisibilityMeters()
|
||||
{
|
||||
return _metar->getIntValue( "min-visibility-m" );
|
||||
}
|
||||
|
||||
string MetarPropertiesATISInformationProvider::getPhenomena()
|
||||
{
|
||||
return _metar->getStringValue( "decoded" );
|
||||
}
|
||||
|
||||
ATISInformationProvider::CloudEntries MetarPropertiesATISInformationProvider::getClouds()
|
||||
{
|
||||
CloudEntries reply;
|
||||
|
||||
using simgear::PropertyList;
|
||||
PropertyList layers = _metar->getNode("clouds", true )->getChildren("layer");
|
||||
for( PropertyList::iterator it = layers.begin(); it != layers.end(); ++it ) {
|
||||
std::string coverage = (*it)->getStringValue("coverage", "clear");
|
||||
double elevation = (*it)->getDoubleValue("elevation-ft", -9999 );
|
||||
if( elevation > 0 ) {
|
||||
reply[elevation] = coverage;
|
||||
}
|
||||
}
|
||||
return reply;
|
||||
}
|
||||
|
||||
int MetarPropertiesATISInformationProvider::getTemperatureDeg()
|
||||
{
|
||||
return _metar->getIntValue( "temperature-degc" );
|
||||
}
|
||||
|
||||
int MetarPropertiesATISInformationProvider::getDewpointDeg()
|
||||
{
|
||||
return _metar->getIntValue( "dewpoint-degc" );
|
||||
}
|
||||
|
||||
string MetarPropertiesATISInformationProvider::getTrend()
|
||||
{
|
||||
return "nosig";
|
||||
}
|
||||
|
||||
58
src/ATC/MetarPropertiesATISInformationProvider.hxx
Normal file
58
src/ATC/MetarPropertiesATISInformationProvider.hxx
Normal file
@@ -0,0 +1,58 @@
|
||||
/*
|
||||
Provide Data for the ATIS Encoder from metarproperties
|
||||
Copyright (C) 2014 Torsten Dreyer
|
||||
|
||||
This program is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU General Public License
|
||||
as published by the Free Software Foundation; either version 2
|
||||
of the License, or (at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#ifndef __METARPROPERTIES_ATIS_ENCODER_HXX
|
||||
#define __METARPROPERTIES_ATIS_ENCODER_HXX
|
||||
|
||||
/* ATIS encoder from metarproperties */
|
||||
|
||||
#include <simgear/props/props.hxx>
|
||||
|
||||
#include <string>
|
||||
#include "ATISEncoder.hxx"
|
||||
|
||||
class MetarPropertiesATISInformationProvider : public ATISInformationProvider
|
||||
{
|
||||
public:
|
||||
MetarPropertiesATISInformationProvider( SGPropertyNode_ptr metar );
|
||||
virtual ~MetarPropertiesATISInformationProvider();
|
||||
|
||||
protected:
|
||||
virtual bool isValid();
|
||||
virtual std::string airportId();
|
||||
virtual long getTime();
|
||||
virtual int getWindDeg();
|
||||
virtual int getWindMinDeg();
|
||||
virtual int getWindMaxDeg();
|
||||
virtual int getWindSpeedKt();
|
||||
virtual int getGustsKt();
|
||||
virtual int getQnh();
|
||||
virtual double getQnhInHg();
|
||||
virtual bool isCavok();
|
||||
virtual int getVisibilityMeters();
|
||||
virtual std::string getPhenomena();
|
||||
virtual CloudEntries getClouds();
|
||||
virtual int getTemperatureDeg();
|
||||
virtual int getDewpointDeg();
|
||||
virtual std::string getTrend();
|
||||
private:
|
||||
SGPropertyNode_ptr _metar;
|
||||
};
|
||||
|
||||
#endif
|
||||
442
src/ATC/StartupController.cxx
Normal file
442
src/ATC/StartupController.cxx
Normal file
@@ -0,0 +1,442 @@
|
||||
// Extracted from trafficrecord.cxx - Implementation of AIModels ATC code.
|
||||
//
|
||||
// Written by Durk Talsma, started September 2006.
|
||||
//
|
||||
// Copyright (C) 2006 Durk Talsma.
|
||||
//
|
||||
// This program is free software; you can redistribute it and/or
|
||||
// modify it under the terms of the GNU General Public License as
|
||||
// published by the Free Software Foundation; either version 2 of the
|
||||
// License, or (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful, but
|
||||
// WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program; if not, write to the Free Software
|
||||
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
//
|
||||
// $Id$
|
||||
|
||||
#include <config.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdio>
|
||||
#include <random>
|
||||
|
||||
#include <osg/Geode>
|
||||
#include <osg/Geometry>
|
||||
#include <osg/MatrixTransform>
|
||||
#include <osg/Shape>
|
||||
|
||||
#include <simgear/scene/material/EffectGeode.hxx>
|
||||
#include <simgear/scene/material/matlib.hxx>
|
||||
#include <simgear/scene/material/mat.hxx>
|
||||
#include <simgear/scene/util/OsgMath.hxx>
|
||||
#include <simgear/timing/sg_time.hxx>
|
||||
|
||||
#include <Scenery/scenery.hxx>
|
||||
|
||||
#include "trafficcontrol.hxx"
|
||||
#include "atc_mgr.hxx"
|
||||
#include <AIModel/AIAircraft.hxx>
|
||||
#include <AIModel/AIFlightPlan.hxx>
|
||||
#include <AIModel/performancedata.hxx>
|
||||
#include <Traffic/TrafficMgr.hxx>
|
||||
#include <Airports/groundnetwork.hxx>
|
||||
#include <Airports/dynamics.hxx>
|
||||
#include <Airports/airport.hxx>
|
||||
#include <Radio/radio.hxx>
|
||||
#include <signal.h>
|
||||
|
||||
#include <ATC/atc_mgr.hxx>
|
||||
#include <ATC/trafficcontrol.hxx>
|
||||
#include <ATC/ATCController.hxx>
|
||||
#include <ATC/StartupController.hxx>
|
||||
|
||||
using std::sort;
|
||||
using std::string;
|
||||
|
||||
/***************************************************************************
|
||||
* class FGStartupController
|
||||
* subclass of FGATCController
|
||||
**************************************************************************/
|
||||
FGStartupController::FGStartupController(FGAirportDynamics *par):
|
||||
FGATCController()
|
||||
{
|
||||
parent = par;
|
||||
}
|
||||
|
||||
FGStartupController::~FGStartupController()
|
||||
{
|
||||
}
|
||||
|
||||
void FGStartupController::announcePosition(int id,
|
||||
FGAIFlightPlan * intendedRoute,
|
||||
int currentPosition, double lat,
|
||||
double lon, double heading,
|
||||
double speed, double alt,
|
||||
double radius, int leg,
|
||||
FGAIAircraft * ref)
|
||||
{
|
||||
init();
|
||||
// Search activeTraffic for a record matching our id
|
||||
TrafficVectorIterator i = FGATCController::searchActiveTraffic(id);
|
||||
|
||||
// Add a new TrafficRecord if no one exsists for this aircraft.
|
||||
if (i == activeTraffic.end() || activeTraffic.empty()) {
|
||||
FGTrafficRecord rec;
|
||||
rec.setId(id);
|
||||
|
||||
rec.setPositionAndHeading(lat, lon, heading, speed, alt);
|
||||
rec.setRunway(intendedRoute->getRunway());
|
||||
rec.setLeg(leg);
|
||||
rec.setPositionAndIntentions(currentPosition, intendedRoute);
|
||||
rec.setCallsign(ref->getCallSign());
|
||||
rec.setAircraft(ref);
|
||||
rec.setHoldPosition(true);
|
||||
activeTraffic.push_back(rec);
|
||||
} else {
|
||||
i->setPositionAndIntentions(currentPosition, intendedRoute);
|
||||
i->setPositionAndHeading(lat, lon, heading, speed, alt);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
bool FGStartupController::checkTransmissionState(int st, time_t now, time_t startTime, TrafficVectorIterator i, AtcMsgId msgId,
|
||||
AtcMsgDir msgDir)
|
||||
{
|
||||
int state = i->getState();
|
||||
if ((state == st) && available) {
|
||||
if ((msgDir == ATC_AIR_TO_GROUND) && isUserAircraft(i->getAircraft())) {
|
||||
|
||||
SG_LOG(SG_ATC, SG_BULK, "Checking state " << st << " for " << i->getAircraft()->getCallSign());
|
||||
SGPropertyNode_ptr trans_num = globals->get_props()->getNode("/sim/atc/transmission-num", true);
|
||||
int n = trans_num->getIntValue();
|
||||
if (n == 0) {
|
||||
trans_num->setIntValue(-1);
|
||||
// PopupCallback(n);
|
||||
SG_LOG(SG_ATC, SG_BULK, "Selected transmission message " << n);
|
||||
//FGATCDialogNew::instance()->removeEntry(1);
|
||||
} else {
|
||||
SG_LOG(SG_ATC, SG_BULK, "Creating message for " << i->getAircraft()->getCallSign());
|
||||
transmit(&(*i), &(*parent), msgId, msgDir, false);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (now > startTime) {
|
||||
SG_LOG(SG_ATC, SG_BULK, "Transmitting startup msg");
|
||||
transmit(&(*i), &(*parent), msgId, msgDir, true);
|
||||
i->updateState();
|
||||
lastTransmission = now;
|
||||
available = false;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void FGStartupController::updateAircraftInformation(int id, SGGeod geod,
|
||||
double heading, double speed, double alt,
|
||||
double dt)
|
||||
{
|
||||
// Search activeTraffic for a record matching our id
|
||||
TrafficVectorIterator i = FGATCController::searchActiveTraffic(id);
|
||||
TrafficVectorIterator current, closest;
|
||||
|
||||
if (i == activeTraffic.end() || (activeTraffic.size() == 0)) {
|
||||
SG_LOG(SG_ATC, SG_ALERT,
|
||||
"AI error: updating aircraft without traffic record at " << SG_ORIGIN);
|
||||
return;
|
||||
} else {
|
||||
i->setPositionAndHeading(geod.getLatitudeDeg(), geod.getLongitudeDeg(), heading, speed, alt);
|
||||
current = i;
|
||||
}
|
||||
setDt(getDt() + dt);
|
||||
|
||||
int state = i->getState();
|
||||
|
||||
// Sentry FLIGHTGEAR-2Q : don't crash on null TrafficRef
|
||||
// Sentry FLIGHTGEAR-129: don't crash on null aircraft
|
||||
if (!i->getAircraft() || !i->getAircraft()->getTrafficRef()) {
|
||||
SG_LOG(SG_ATC, SG_ALERT, "AI traffic: updating aircraft without traffic ref");
|
||||
return;
|
||||
}
|
||||
|
||||
// The user controlled aircraft should have crased here, because it doesn't have a traffic reference.
|
||||
// NOTE: if we create a traffic schedule for the user aircraft, we can use this to plan a flight.
|
||||
time_t startTime = i->getAircraft()->getTrafficRef()->getDepartureTime();
|
||||
time_t now = globals->get_time_params()->get_cur_time();
|
||||
|
||||
|
||||
if ((startTime - now) > 0) {
|
||||
SG_LOG(SG_ATC, SG_BULK, i->getAircraft()->getTrafficRef()->getCallSign() << " is scheduled to depart in " << startTime - now << " seconds. Available = " << available << " at parking " << getGateName(i->getAircraft()));
|
||||
}
|
||||
|
||||
if ((now - lastTransmission) > 3 + (rand() % 15)) {
|
||||
available = true;
|
||||
}
|
||||
|
||||
checkTransmissionState(0, now, (startTime + 0 ), i, MSG_ANNOUNCE_ENGINE_START, ATC_AIR_TO_GROUND);
|
||||
checkTransmissionState(1, now, (startTime + 60 ), i, MSG_REQUEST_ENGINE_START, ATC_AIR_TO_GROUND);
|
||||
checkTransmissionState(2, now, (startTime + 80 ), i, MSG_PERMIT_ENGINE_START, ATC_GROUND_TO_AIR);
|
||||
checkTransmissionState(3, now, (startTime + 100), i, MSG_ACKNOWLEDGE_ENGINE_START, ATC_AIR_TO_GROUND);
|
||||
if (checkTransmissionState(4, now, (startTime + 130), i, MSG_ACKNOWLEDGE_SWITCH_GROUND_FREQUENCY, ATC_AIR_TO_GROUND)) {
|
||||
i->nextFrequency();
|
||||
}
|
||||
checkTransmissionState(5, now, (startTime + 140), i, MSG_INITIATE_CONTACT, ATC_AIR_TO_GROUND);
|
||||
checkTransmissionState(6, now, (startTime + 150), i, MSG_ACKNOWLEDGE_INITIATE_CONTACT, ATC_GROUND_TO_AIR);
|
||||
checkTransmissionState(7, now, (startTime + 180), i, MSG_REQUEST_PUSHBACK_CLEARANCE, ATC_AIR_TO_GROUND);
|
||||
|
||||
|
||||
|
||||
if ((state == 8) && available) {
|
||||
if (now > startTime + 200) {
|
||||
if (i->pushBackAllowed()) {
|
||||
i->allowRepeatedTransmissions();
|
||||
transmit(&(*i), &(*parent), MSG_PERMIT_PUSHBACK_CLEARANCE,
|
||||
ATC_GROUND_TO_AIR, true);
|
||||
i->updateState();
|
||||
} else {
|
||||
transmit(&(*i), &(*parent), MSG_HOLD_PUSHBACK_CLEARANCE,
|
||||
ATC_GROUND_TO_AIR, true);
|
||||
i->suppressRepeatedTransmissions();
|
||||
}
|
||||
lastTransmission = now;
|
||||
available = false;
|
||||
}
|
||||
}
|
||||
if ((state == 9) && available) {
|
||||
i->setHoldPosition(false);
|
||||
}
|
||||
}
|
||||
|
||||
// Note that this function is copied from simgear. for maintanance purposes, it's probabtl better to make a general function out of that.
|
||||
static void WorldCoordinate(osg::Matrix& obj_pos, double lat,
|
||||
double lon, double elev, double hdg, double slope)
|
||||
{
|
||||
SGGeod geod = SGGeod::fromDegM(lon, lat, elev);
|
||||
obj_pos = makeZUpFrame(geod);
|
||||
// hdg is not a compass heading, but a counter-clockwise rotation
|
||||
// around the Z axis
|
||||
obj_pos.preMult(osg::Matrix::rotate(hdg * SGD_DEGREES_TO_RADIANS,
|
||||
0.0, 0.0, 1.0));
|
||||
obj_pos.preMult(osg::Matrix::rotate(slope * SGD_DEGREES_TO_RADIANS,
|
||||
0.0, 1.0, 0.0));
|
||||
}
|
||||
|
||||
|
||||
void FGStartupController::render(bool visible)
|
||||
{
|
||||
SG_LOG(SG_ATC, SG_DEBUG, "Rendering startup controller");
|
||||
SGMaterialLib *matlib = globals->get_matlib();
|
||||
if (group) {
|
||||
//int nr = ;
|
||||
globals->get_scenery()->get_scene_graph()->removeChild(group);
|
||||
//while (group->getNumChildren()) {
|
||||
// SG_LOG(SG_ATC, SG_BULK, "Number of children: " << group->getNumChildren());
|
||||
//simgear::EffectGeode* geode = (simgear::EffectGeode*) group->getChild(0);
|
||||
//osg::MatrixTransform *obj_trans = (osg::MatrixTransform*) group->getChild(0);
|
||||
//geode->releaseGLObjects();
|
||||
//group->removeChild(geode);
|
||||
//delete geode;
|
||||
group = 0;
|
||||
}
|
||||
if (visible) {
|
||||
group = new osg::Group;
|
||||
FGScenery * local_scenery = globals->get_scenery();
|
||||
//double elevation_meters = 0.0;
|
||||
//double elevation_feet = 0.0;
|
||||
|
||||
FGGroundNetwork* groundNet = parent->parent()->groundNetwork();
|
||||
|
||||
//for ( FGTaxiSegmentVectorIterator i = segments.begin(); i != segments.end(); i++) {
|
||||
double dx = 0;
|
||||
time_t now = globals->get_time_params()->get_cur_time();
|
||||
|
||||
for (TrafficVectorIterator i = activeTraffic.begin(); i != activeTraffic.end(); i++) {
|
||||
if (i->isActive(300)) {
|
||||
// Handle start point
|
||||
int pos = i->getCurrentPosition();
|
||||
SG_LOG(SG_ATC, SG_BULK, "rendering for " << i->getAircraft()->getCallSign() << "pos = " << pos);
|
||||
if (pos > 0) {
|
||||
FGTaxiSegment *segment = groundNet->findSegment(pos);
|
||||
SGGeod start = i->getPos();
|
||||
SGGeod end (segment->getEnd()->geod());
|
||||
|
||||
double length = SGGeodesy::distanceM(start, end);
|
||||
//heading = SGGeodesy::headingDeg(start->geod(), end->geod());
|
||||
|
||||
double az2, heading; //, distanceM;
|
||||
SGGeodesy::inverse(start, end, heading, az2, length);
|
||||
double coveredDistance = length * 0.5;
|
||||
SGGeod center;
|
||||
SGGeodesy::direct(start, heading, coveredDistance, center, az2);
|
||||
SG_LOG(SG_ATC, SG_BULK, "Active Aircraft : Centerpoint = (" << center.getLatitudeDeg() << ", " << center.getLongitudeDeg() << "). Heading = " << heading);
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// Make a helper function out of this
|
||||
osg::Matrix obj_pos;
|
||||
osg::MatrixTransform *obj_trans = new osg::MatrixTransform;
|
||||
obj_trans->setDataVariance(osg::Object::STATIC);
|
||||
// Experimental: Calculate slope here, based on length, and the individual elevations
|
||||
double elevationStart;
|
||||
if (isUserAircraft((i)->getAircraft())) {
|
||||
elevationStart = fgGetDouble("/position/ground-elev-m");
|
||||
} else {
|
||||
elevationStart = ((i)->getAircraft()->_getAltitude() * SG_FEET_TO_METER);
|
||||
}
|
||||
double elevationEnd = segment->getEnd()->getElevationM();
|
||||
if ((elevationEnd == 0) || (elevationEnd == parent->getElevation())) {
|
||||
SGGeod center2 = end;
|
||||
center2.setElevationM(SG_MAX_ELEVATION_M);
|
||||
if (local_scenery->get_elevation_m( center2, elevationEnd, NULL )) {
|
||||
//elevation_feet = elevationEnd * SG_METER_TO_FEET + 0.5;
|
||||
//elevation_meters += 0.5;
|
||||
}
|
||||
else {
|
||||
elevationEnd = parent->getElevation();
|
||||
}
|
||||
segment->getEnd()->setElevation(elevationEnd);
|
||||
}
|
||||
|
||||
double elevationMean = (elevationStart + elevationEnd) / 2.0;
|
||||
double elevDiff = elevationEnd - elevationStart;
|
||||
|
||||
double slope = atan2(elevDiff, length) * SGD_RADIANS_TO_DEGREES;
|
||||
|
||||
SG_LOG(SG_ATC, SG_BULK, "1. Using mean elevation : " << elevationMean << " and " << slope);
|
||||
|
||||
WorldCoordinate( obj_pos, center.getLatitudeDeg(), center.getLongitudeDeg(), elevationMean + 0.5 + dx, -(heading), slope );
|
||||
;
|
||||
|
||||
obj_trans->setMatrix( obj_pos );
|
||||
//osg::Vec3 center(0, 0, 0)
|
||||
|
||||
float width = length /2.0;
|
||||
osg::Vec3 corner(-width, 0, 0.25f);
|
||||
osg::Vec3 widthVec(2*width + 1, 0, 0);
|
||||
osg::Vec3 heightVec(0, 1, 0);
|
||||
osg::Geometry* geometry;
|
||||
geometry = osg::createTexturedQuadGeometry(corner, widthVec, heightVec);
|
||||
simgear::EffectGeode* geode = new simgear::EffectGeode;
|
||||
geode->setName("test");
|
||||
geode->addDrawable(geometry);
|
||||
//osg::Node *custom_obj;
|
||||
SGMaterial *mat;
|
||||
if (segment->hasBlock(now)) {
|
||||
mat = matlib->find("UnidirectionalTaperRed", center);
|
||||
} else {
|
||||
mat = matlib->find("UnidirectionalTaperGreen", center);
|
||||
}
|
||||
if (mat)
|
||||
geode->setEffect(mat->get_effect());
|
||||
obj_trans->addChild(geode);
|
||||
// wire as much of the scene graph together as we can
|
||||
//->addChild( obj_trans );
|
||||
group->addChild( obj_trans );
|
||||
/////////////////////////////////////////////////////////////////////
|
||||
} else {
|
||||
SG_LOG(SG_ATC, SG_DEBUG, "BIG FAT WARNING: current position is here : " << pos);
|
||||
}
|
||||
for (intVecIterator j = (i)->getIntentions().begin(); j != (i)->getIntentions().end(); j++) {
|
||||
osg::Matrix obj_pos;
|
||||
int k = (*j);
|
||||
if (k > 0) {
|
||||
SG_LOG(SG_ATC, SG_BULK, "rendering for " << i->getAircraft()->getCallSign() << "intention = " << k);
|
||||
osg::MatrixTransform *obj_trans = new osg::MatrixTransform;
|
||||
obj_trans->setDataVariance(osg::Object::STATIC);
|
||||
FGTaxiSegment *segment = groundNet->findSegment(k);
|
||||
|
||||
double elevationStart = segment->getStart()->getElevationM();
|
||||
double elevationEnd = segment->getEnd ()->getElevationM();
|
||||
if ((elevationStart == 0) || (elevationStart == parent->getElevation())) {
|
||||
SGGeod center2 = segment->getStart()->geod();
|
||||
center2.setElevationM(SG_MAX_ELEVATION_M);
|
||||
if (local_scenery->get_elevation_m( center2, elevationStart, NULL )) {
|
||||
//elevation_feet = elevationStart * SG_METER_TO_FEET + 0.5;
|
||||
//elevation_meters += 0.5;
|
||||
}
|
||||
else {
|
||||
elevationStart = parent->getElevation();
|
||||
}
|
||||
segment->getStart()->setElevation(elevationStart);
|
||||
}
|
||||
if ((elevationEnd == 0) || (elevationEnd == parent->getElevation())) {
|
||||
SGGeod center2 = segment->getEnd()->geod();
|
||||
center2.setElevationM(SG_MAX_ELEVATION_M);
|
||||
if (local_scenery->get_elevation_m( center2, elevationEnd, NULL )) {
|
||||
//elevation_feet = elevationEnd * SG_METER_TO_FEET + 0.5;
|
||||
//elevation_meters += 0.5;
|
||||
}
|
||||
else {
|
||||
elevationEnd = parent->getElevation();
|
||||
}
|
||||
segment->getEnd()->setElevation(elevationEnd);
|
||||
}
|
||||
|
||||
double elevationMean = (elevationStart + elevationEnd) / 2.0;
|
||||
double elevDiff = elevationEnd - elevationStart;
|
||||
double length = segment->getLength();
|
||||
double slope = atan2(elevDiff, length) * SGD_RADIANS_TO_DEGREES;
|
||||
|
||||
SG_LOG(SG_ATC, SG_BULK, "2. Using mean elevation : " << elevationMean << " and " << slope);
|
||||
|
||||
SGGeod segCenter(segment->getCenter());
|
||||
WorldCoordinate( obj_pos, segCenter.getLatitudeDeg(),
|
||||
segCenter.getLongitudeDeg(), elevationMean + 0.5 + dx, -(segment->getHeading()), slope );
|
||||
|
||||
//WorldCoordinate( obj_pos, segment->getLatitude(), segment->getLongitude(), parent->getElevation()+8+dx, -(segment->getHeading()) );
|
||||
|
||||
obj_trans->setMatrix( obj_pos );
|
||||
//osg::Vec3 center(0, 0, 0)
|
||||
|
||||
float width = segment->getLength() /2.0;
|
||||
osg::Vec3 corner(-width, 0, 0.25f);
|
||||
osg::Vec3 widthVec(2*width + 1, 0, 0);
|
||||
osg::Vec3 heightVec(0, 1, 0);
|
||||
osg::Geometry* geometry;
|
||||
geometry = osg::createTexturedQuadGeometry(corner, widthVec, heightVec);
|
||||
simgear::EffectGeode* geode = new simgear::EffectGeode;
|
||||
geode->setName("test");
|
||||
geode->addDrawable(geometry);
|
||||
//osg::Node *custom_obj;
|
||||
SGMaterial *mat;
|
||||
if (segment->hasBlock(now)) {
|
||||
mat = matlib->find("UnidirectionalTaperRed", segCenter);
|
||||
} else {
|
||||
mat = matlib->find("UnidirectionalTaperGreen", segCenter);
|
||||
}
|
||||
if (mat)
|
||||
geode->setEffect(mat->get_effect());
|
||||
obj_trans->addChild(geode);
|
||||
// wire as much of the scene graph together as we can
|
||||
//->addChild( obj_trans );
|
||||
group->addChild( obj_trans );
|
||||
} else {
|
||||
SG_LOG(SG_ATC, SG_DEBUG, "BIG FAT WARNING: k is here : " << pos);
|
||||
}
|
||||
}
|
||||
dx += 0.2;
|
||||
}
|
||||
}
|
||||
globals->get_scenery()->get_scene_graph()->addChild(group);
|
||||
}
|
||||
}
|
||||
|
||||
string FGStartupController::getName() {
|
||||
return string(parent->parent()->getName() + "-Startup");
|
||||
}
|
||||
|
||||
void FGStartupController::update(double dt)
|
||||
{
|
||||
FGATCController::eraseDeadTraffic();
|
||||
}
|
||||
|
||||
int FGStartupController::getFrequency() {
|
||||
int groundFreq = parent->getGroundFrequency(2);
|
||||
int towerFreq = parent->getTowerFrequency(2);
|
||||
return groundFreq>0?groundFreq:towerFreq;
|
||||
}
|
||||
|
||||
71
src/ATC/StartupController.hxx
Normal file
71
src/ATC/StartupController.hxx
Normal file
@@ -0,0 +1,71 @@
|
||||
// Extracted from trafficcontrol.hxx - classes to manage AIModels based air traffic control
|
||||
// Written by Durk Talsma, started September 2006.
|
||||
//
|
||||
// This program is free software; you can redistribute it and/or
|
||||
// modify it under the terms of the GNU General Public License as
|
||||
// published by the Free Software Foundation; either version 2 of the
|
||||
// License, or (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful, but
|
||||
// WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program; if not, write to the Free Software
|
||||
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
//
|
||||
// $Id$
|
||||
|
||||
#ifndef STARTUP_CONTROLLER_HXX
|
||||
#define STARTUP_CONTROLLER_HXX
|
||||
|
||||
#include <Airports/airports_fwd.hxx>
|
||||
|
||||
#include <osg/Geode>
|
||||
#include <osg/Geometry>
|
||||
#include <osg/MatrixTransform>
|
||||
#include <osg/Shape>
|
||||
|
||||
#include <simgear/compiler.h>
|
||||
#include <simgear/constants.h>
|
||||
#include <simgear/debug/logstream.hxx>
|
||||
#include <simgear/structure/SGReferenced.hxx>
|
||||
#include <simgear/structure/SGSharedPtr.hxx>
|
||||
|
||||
#include <ATC/ATCController.hxx>
|
||||
#include <ATC/trafficcontrol.hxx>
|
||||
|
||||
/******************************************************************************
|
||||
* class FGStartupController
|
||||
* handle
|
||||
*****************************************************************************/
|
||||
|
||||
class FGStartupController : public FGATCController
|
||||
{
|
||||
private:
|
||||
/**Returns the frequency to be used. */
|
||||
int getFrequency();
|
||||
|
||||
public:
|
||||
FGStartupController(FGAirportDynamics *parent);
|
||||
virtual ~FGStartupController();
|
||||
|
||||
virtual void announcePosition(int id, FGAIFlightPlan *intendedRoute, int currentRoute,
|
||||
double lat, double lon,
|
||||
double hdg, double spd, double alt, double radius, int leg,
|
||||
FGAIAircraft *aircraft);
|
||||
virtual void updateAircraftInformation(int id, SGGeod geod,
|
||||
double heading, double speed, double alt, double dt);
|
||||
|
||||
virtual void render(bool);
|
||||
virtual std::string getName();
|
||||
virtual void update(double dt);
|
||||
|
||||
// Hpoefully, we can move this function to the base class, but I need to verify what is needed for the other controllers before doing so.
|
||||
bool checkTransmissionState(int st, time_t now, time_t startTime, TrafficVectorIterator i, AtcMsgId msgId,
|
||||
AtcMsgDir msgDir);
|
||||
|
||||
};
|
||||
|
||||
#endif
|
||||
308
src/ATC/TowerController.cxx
Normal file
308
src/ATC/TowerController.cxx
Normal file
@@ -0,0 +1,308 @@
|
||||
// Extracted from trafficrecord.cxx - Implementation of AIModels ATC code.
|
||||
//
|
||||
// Written by Durk Talsma, started September 2006.
|
||||
//
|
||||
// Copyright (C) 2006 Durk Talsma.
|
||||
//
|
||||
// This program is free software; you can redistribute it and/or
|
||||
// modify it under the terms of the GNU General Public License as
|
||||
// published by the Free Software Foundation; either version 2 of the
|
||||
// License, or (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful, but
|
||||
// WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program; if not, write to the Free Software
|
||||
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
//
|
||||
// $Id$
|
||||
|
||||
#include <config.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdio>
|
||||
#include <random>
|
||||
|
||||
#include <osg/Geode>
|
||||
#include <osg/Geometry>
|
||||
#include <osg/MatrixTransform>
|
||||
#include <osg/Shape>
|
||||
|
||||
#include <simgear/scene/material/EffectGeode.hxx>
|
||||
#include <simgear/scene/material/matlib.hxx>
|
||||
#include <simgear/scene/material/mat.hxx>
|
||||
#include <simgear/scene/util/OsgMath.hxx>
|
||||
#include <simgear/timing/sg_time.hxx>
|
||||
|
||||
#include <Scenery/scenery.hxx>
|
||||
|
||||
#include "trafficcontrol.hxx"
|
||||
#include "atc_mgr.hxx"
|
||||
#include <AIModel/AIAircraft.hxx>
|
||||
#include <AIModel/AIFlightPlan.hxx>
|
||||
#include <AIModel/performancedata.hxx>
|
||||
#include <Traffic/TrafficMgr.hxx>
|
||||
#include <Airports/groundnetwork.hxx>
|
||||
#include <Airports/dynamics.hxx>
|
||||
#include <Airports/airport.hxx>
|
||||
#include <Radio/radio.hxx>
|
||||
#include <signal.h>
|
||||
|
||||
#include <ATC/atc_mgr.hxx>
|
||||
#include <ATC/ATCController.hxx>
|
||||
#include <ATC/trafficcontrol.hxx>
|
||||
#include <ATC/TowerController.hxx>
|
||||
|
||||
using std::sort;
|
||||
using std::string;
|
||||
|
||||
/***************************************************************************
|
||||
* class FGTowerController
|
||||
s * subclass of FGATCController
|
||||
**************************************************************************/
|
||||
|
||||
FGTowerController::FGTowerController(FGAirportDynamics *par) :
|
||||
FGATCController()
|
||||
{
|
||||
parent = par;
|
||||
}
|
||||
|
||||
FGTowerController::~FGTowerController()
|
||||
{
|
||||
}
|
||||
|
||||
//
|
||||
void FGTowerController::announcePosition(int id,
|
||||
FGAIFlightPlan * intendedRoute,
|
||||
int currentPosition, double lat,
|
||||
double lon, double heading,
|
||||
double speed, double alt,
|
||||
double radius, int leg,
|
||||
FGAIAircraft * ref)
|
||||
{
|
||||
init();
|
||||
|
||||
// Search activeTraffic for a record matching our id
|
||||
TrafficVectorIterator i = FGATCController::searchActiveTraffic(id);
|
||||
|
||||
// Add a new TrafficRecord if no one exsists for this aircraft.
|
||||
if (i == activeTraffic.end() || (activeTraffic.empty())) {
|
||||
FGTrafficRecord rec;
|
||||
rec.setId(id);
|
||||
|
||||
rec.setPositionAndHeading(lat, lon, heading, speed, alt);
|
||||
rec.setRunway(intendedRoute->getRunway());
|
||||
rec.setLeg(leg);
|
||||
//rec.setCallSign(callsign);
|
||||
rec.setRadius(radius);
|
||||
rec.setAircraft(ref);
|
||||
activeTraffic.push_back(rec);
|
||||
// Don't just schedule the aircraft for the tower controller, also assign if to the correct active runway.
|
||||
ActiveRunwayVecIterator rwy = activeRunways.begin();
|
||||
if (! activeRunways.empty()) {
|
||||
while (rwy != activeRunways.end()) {
|
||||
if (rwy->getRunwayName() == intendedRoute->getRunway()) {
|
||||
break;
|
||||
}
|
||||
rwy++;
|
||||
}
|
||||
}
|
||||
if (rwy == activeRunways.end()) {
|
||||
ActiveRunway aRwy(intendedRoute->getRunway(), id);
|
||||
aRwy.addToDepartureQueue(ref);
|
||||
activeRunways.push_back(aRwy);
|
||||
rwy = (activeRunways.end()-1);
|
||||
} else {
|
||||
rwy->addToDepartureQueue(ref);
|
||||
}
|
||||
|
||||
SG_LOG(SG_ATC, SG_DEBUG, ref->getTrafficRef()->getCallSign() << " You are number " << rwy->getdepartureQueueSize() << " for takeoff ");
|
||||
} else {
|
||||
i->setPositionAndHeading(lat, lon, heading, speed, alt);
|
||||
}
|
||||
}
|
||||
|
||||
void FGTowerController::updateAircraftInformation(int id, SGGeod geod,
|
||||
double heading, double speed, double alt,
|
||||
double dt)
|
||||
{
|
||||
// Search activeTraffic for a record matching our id
|
||||
TrafficVectorIterator i = FGATCController::searchActiveTraffic(id);
|
||||
|
||||
setDt(getDt() + dt);
|
||||
|
||||
if (i == activeTraffic.end() || (activeTraffic.empty())) {
|
||||
SG_LOG(SG_ATC, SG_ALERT,
|
||||
"AI error: updating aircraft without traffic record at " <<
|
||||
SG_ORIGIN);
|
||||
return;
|
||||
}
|
||||
|
||||
// Update the position of the current aircraft
|
||||
FGTrafficRecord& current = *i;
|
||||
current.setPositionAndHeading(geod.getLatitudeDeg(), geod.getLongitudeDeg(), heading, speed, alt);
|
||||
|
||||
// see if we already have a clearance record for the currently active runway
|
||||
// NOTE: dd. 2011-08-07: Because the active runway has been constructed in the announcePosition function, we may safely assume that is
|
||||
// already exists here. So, we can simplify the current code.
|
||||
|
||||
ActiveRunwayVecIterator rwy = activeRunways.begin();
|
||||
//if (parent->getId() == fgGetString("/sim/presets/airport-id")) {
|
||||
// for (rwy = activeRunways.begin(); rwy != activeRunways.end(); rwy++) {
|
||||
// rwy->printdepartureQueue();
|
||||
// }
|
||||
//}
|
||||
|
||||
rwy = activeRunways.begin();
|
||||
while (rwy != activeRunways.end()) {
|
||||
if (rwy->getRunwayName() == current.getRunway()) {
|
||||
break;
|
||||
}
|
||||
rwy++;
|
||||
}
|
||||
|
||||
// only bother running the following code if the current aircraft is the
|
||||
// first in line for depature
|
||||
/* if (current.getAircraft() == rwy->getFirstAircraftIndepartureQueue()) {
|
||||
if (rwy->getCleared()) {
|
||||
if (id == rwy->getCleared()) {
|
||||
current.setHoldPosition(false);
|
||||
} else {
|
||||
current.setHoldPosition(true);
|
||||
}
|
||||
} else {
|
||||
// For now. At later stages, this will probably be the place to check for inbound traffc.
|
||||
rwy->setCleared(id);
|
||||
}
|
||||
} */
|
||||
// only bother with aircraft that have a takeoff status of 2, since those are essentially under tower control
|
||||
auto ac = rwy->getFirstAircraftInDepartureQueue();
|
||||
if (ac) {
|
||||
if (ac->getTakeOffStatus() == 1) {
|
||||
// transmit takeoff clearance
|
||||
ac->setTakeOffStatus(2);
|
||||
transmit(&(*i), &(*parent), MSG_CLEARED_FOR_TAKEOFF, ATC_GROUND_TO_AIR, true);
|
||||
i->setState(10);
|
||||
}
|
||||
}
|
||||
//FIXME Make it an member of traffic record
|
||||
if (current.getAircraft()->getTakeOffStatus() == 2) {
|
||||
current.setHoldPosition(false);
|
||||
} else {
|
||||
current.setHoldPosition(true);
|
||||
}
|
||||
int clearanceId = rwy->getCleared();
|
||||
if (clearanceId) {
|
||||
if (id == clearanceId) {
|
||||
SG_LOG(SG_ATC, SG_BULK, "Unset Hold " << clearanceId << " for " << rwy->getRunwayName());
|
||||
current.setHoldPosition(false);
|
||||
} else {
|
||||
SG_LOG(SG_ATC, SG_WARN, "Not cleared " << id << " " << clearanceId);
|
||||
}
|
||||
} else {
|
||||
if (current.getAircraft() == rwy->getFirstAircraftInDepartureQueue()) {
|
||||
SG_LOG(SG_ATC, SG_BULK,
|
||||
"Cleared " << current.getAircraft()->getCallSign() << " for " << rwy->getRunwayName() << " Id " << id);
|
||||
rwy->setCleared(id);
|
||||
auto ac = rwy->getFirstOfStatus(1);
|
||||
if (ac) {
|
||||
ac->setTakeOffStatus(2);
|
||||
// transmit takeoff clearacne? But why twice?
|
||||
}
|
||||
} else {
|
||||
SG_LOG(SG_ATC, SG_BULK,
|
||||
"Not cleared " << current.getAircraft()->getCallSign() << " " << rwy->getFirstAircraftInDepartureQueue()->getCallSign());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void FGTowerController::signOff(int id)
|
||||
{
|
||||
SG_LOG(SG_ATC, SG_BULK, "Signing off " << id << " from Tower");
|
||||
// ensure we don't modify activeTraffic during destruction
|
||||
if (_isDestroying)
|
||||
return;
|
||||
|
||||
// Search activeTraffic for a record matching our id
|
||||
TrafficVectorIterator i = FGATCController::searchActiveTraffic(id);
|
||||
if (i == activeTraffic.end() || (activeTraffic.empty())) {
|
||||
SG_LOG(SG_ATC, SG_ALERT,
|
||||
"AI error: Aircraft without traffic record is signing off from tower at " << SG_ORIGIN);
|
||||
return;
|
||||
}
|
||||
|
||||
const auto trafficRunway = i->getRunway();
|
||||
auto runwayIt = std::find_if(activeRunways.begin(), activeRunways.end(),
|
||||
[&trafficRunway](const ActiveRunway& ar) {
|
||||
return ar.getRunwayName() == trafficRunway;
|
||||
});
|
||||
|
||||
if (runwayIt != activeRunways.end()) {
|
||||
SG_LOG(SG_ATC, SG_BULK, "Cleared " << id << " from " << runwayIt->getRunwayName() );
|
||||
runwayIt->setCleared(0);
|
||||
runwayIt->updateDepartureQueue();
|
||||
} else {
|
||||
SG_LOG(SG_ATC, SG_ALERT,
|
||||
"AI error: Attempting to erase non-existing runway clearance record in FGTowerController::signoff at " << SG_ORIGIN);
|
||||
}
|
||||
|
||||
i->getAircraft()->resetTakeOffStatus();
|
||||
FGATCController::signOff(id);
|
||||
}
|
||||
|
||||
// NOTE:
|
||||
// IF WE MAKE TRAFFICRECORD A MEMBER OF THE BASE CLASS
|
||||
// THE FOLLOWING THREE FUNCTIONS: SIGNOFF, HAS INSTRUCTION AND GETINSTRUCTION CAN
|
||||
// BECOME DEVIRTUALIZED AND BE A MEMBER OF THE BASE ATCCONTROLLER CLASS
|
||||
// WHICH WOULD SIMPLIFY CODE MAINTENANCE.
|
||||
// Note that this function is probably obsolete
|
||||
bool FGTowerController::hasInstruction(int id)
|
||||
{
|
||||
// Search activeTraffic for a record matching our id
|
||||
TrafficVectorIterator i = FGATCController::searchActiveTraffic(id);
|
||||
|
||||
if (i == activeTraffic.end() || activeTraffic.empty()) {
|
||||
SG_LOG(SG_ATC, SG_ALERT,
|
||||
"AI error: checking ATC instruction for aircraft without traffic record at " << SG_ORIGIN);
|
||||
} else {
|
||||
return i->hasInstruction();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
FGATCInstruction FGTowerController::getInstruction(int id)
|
||||
{
|
||||
// Search activeTraffic for a record matching our id
|
||||
TrafficVectorIterator i = FGATCController::searchActiveTraffic(id);
|
||||
|
||||
if (i == activeTraffic.end() || activeTraffic.empty()) {
|
||||
SG_LOG(SG_ATC, SG_ALERT,
|
||||
"AI error: requesting ATC instruction for aircraft without traffic record at " << SG_ORIGIN);
|
||||
} else {
|
||||
return i->getInstruction();
|
||||
}
|
||||
return FGATCInstruction();
|
||||
}
|
||||
|
||||
void FGTowerController::render(bool visible) {
|
||||
// this should be bulk, since its called quite often
|
||||
SG_LOG(SG_ATC, SG_BULK, "FGTowerController::render function not yet implemented");
|
||||
}
|
||||
|
||||
string FGTowerController::getName() {
|
||||
return string(parent->getId() + "-tower");
|
||||
}
|
||||
|
||||
void FGTowerController::update(double dt)
|
||||
{
|
||||
FGATCController::eraseDeadTraffic();
|
||||
}
|
||||
|
||||
int FGTowerController::getFrequency() {
|
||||
int towerFreq = parent->getTowerFrequency(2);
|
||||
return towerFreq;
|
||||
}
|
||||
68
src/ATC/TowerController.hxx
Normal file
68
src/ATC/TowerController.hxx
Normal file
@@ -0,0 +1,68 @@
|
||||
// Extracted from trafficcontrol.hxx - classes to manage AIModels based air traffic control
|
||||
// Written by Durk Talsma, started September 2006.
|
||||
//
|
||||
// This program is free software; you can redistribute it and/or
|
||||
// modify it under the terms of the GNU General Public License as
|
||||
// published by the Free Software Foundation; either version 2 of the
|
||||
// License, or (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful, but
|
||||
// WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program; if not, write to the Free Software
|
||||
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
//
|
||||
// $Id$
|
||||
|
||||
#ifndef TOWER_CONTROLLER_HXX
|
||||
#define TOWER_CONTROLLER_HXX
|
||||
|
||||
#include <Airports/airports_fwd.hxx>
|
||||
|
||||
#include <osg/Geode>
|
||||
#include <osg/Geometry>
|
||||
#include <osg/MatrixTransform>
|
||||
#include <osg/Shape>
|
||||
|
||||
#include <simgear/compiler.h>
|
||||
#include <simgear/constants.h>
|
||||
#include <simgear/debug/logstream.hxx>
|
||||
#include <simgear/structure/SGReferenced.hxx>
|
||||
#include <simgear/structure/SGSharedPtr.hxx>
|
||||
|
||||
#include <ATC/ATCController.hxx>
|
||||
#include <ATC/trafficcontrol.hxx>
|
||||
|
||||
/******************************************************************************
|
||||
* class FGTowerControl
|
||||
*****************************************************************************/
|
||||
class FGTowerController : public FGATCController
|
||||
{
|
||||
private:
|
||||
ActiveRunwayVec activeRunways;
|
||||
/**Returns the frequency to be used. */
|
||||
int getFrequency();
|
||||
|
||||
public:
|
||||
FGTowerController(FGAirportDynamics *parent);
|
||||
virtual ~FGTowerController();
|
||||
|
||||
virtual void announcePosition(int id, FGAIFlightPlan *intendedRoute, int currentRoute,
|
||||
double lat, double lon,
|
||||
double hdg, double spd, double alt, double radius, int leg,
|
||||
FGAIAircraft *aircraft);
|
||||
void signOff(int id);
|
||||
bool hasInstruction(int id);
|
||||
FGATCInstruction getInstruction(int id);
|
||||
virtual void updateAircraftInformation(int id, SGGeod geod,
|
||||
double heading, double speed, double alt, double dt);
|
||||
|
||||
virtual void render(bool);
|
||||
virtual std::string getName();
|
||||
virtual void update(double dt);
|
||||
};
|
||||
|
||||
#endif
|
||||
414
src/ATC/atc_mgr.cxx
Normal file
414
src/ATC/atc_mgr.cxx
Normal file
@@ -0,0 +1,414 @@
|
||||
/******************************************************************************
|
||||
* atc_mgr.cxx
|
||||
* Written by Durk Talsma, started August 1, 2010.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License as
|
||||
* published by the Free Software Foundation; either version 2 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*
|
||||
*
|
||||
**************************************************************************/
|
||||
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
# include "config.h"
|
||||
#endif
|
||||
|
||||
#include <Airports/dynamics.hxx>
|
||||
#include <Airports/airportdynamicsmanager.hxx>
|
||||
#include <Airports/airport.hxx>
|
||||
#include <Scenery/scenery.hxx>
|
||||
#include <Main/globals.hxx>
|
||||
#include <Main/fg_props.hxx>
|
||||
#include <AIModel/AIAircraft.hxx>
|
||||
#include <AIModel/AIManager.hxx>
|
||||
#include <Traffic/Schedule.hxx>
|
||||
#include <Traffic/SchedFlight.hxx>
|
||||
#include <AIModel/AIFlightPlan.hxx>
|
||||
|
||||
#include "atc_mgr.hxx"
|
||||
|
||||
|
||||
using std::string;
|
||||
|
||||
/**
|
||||
Constructer, initializes values to private boolean and FGATCController instances
|
||||
*/
|
||||
FGATCManager::FGATCManager() :
|
||||
controller(NULL),
|
||||
prevController(NULL),
|
||||
networkVisible(false),
|
||||
initSucceeded(false)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
Default destructor
|
||||
*/
|
||||
FGATCManager::~FGATCManager() {
|
||||
}
|
||||
|
||||
/**
|
||||
Sets up ATC subsystem parts depending on other subsystems
|
||||
Override of SGSubsystem::postinit()
|
||||
Will set private boolean flag "initSucceeded" to true upon conclusion
|
||||
*/
|
||||
void FGATCManager::postinit()
|
||||
{
|
||||
int leg = 0;
|
||||
|
||||
trans_num = globals->get_props()->getNode("/sim/atc/transmission-num", true);
|
||||
|
||||
// Assign a controller to the user's aircraft.
|
||||
// Three scenarios are considered:
|
||||
// - Starting on ground at a parking position
|
||||
// - Starting on ground at the runway.
|
||||
// - Starting in the air
|
||||
bool onGround = fgGetBool("/sim/presets/onground");
|
||||
string runway = fgGetString("/sim/atc/runway");
|
||||
string curAirport = fgGetString("/sim/presets/airport-id");
|
||||
string parking = fgGetString("/sim/presets/parkpos");
|
||||
|
||||
_routeManagerDestinationAirportNode = globals->get_props()->getNode("/autopilot/route-manager/destination/airport", true);
|
||||
destination = _routeManagerDestinationAirportNode->getStringValue();
|
||||
|
||||
FGAIManager* aiManager = globals->get_subsystem<FGAIManager>();
|
||||
auto userAircraft = aiManager->getUserAircraft();
|
||||
string callsign = userAircraft->getCallSign();
|
||||
|
||||
double aircraftRadius = 40; // note that this is currently hardcoded to a one-size-fits all JumboJet value. Should change later.
|
||||
|
||||
// In case a destination is not set yet, make it equal to the current airport
|
||||
if (destination.empty()) {
|
||||
destination = curAirport;
|
||||
}
|
||||
|
||||
// NEXT UP: Create a traffic schedule and fill that with appropriate information. This we can use for flight planning.
|
||||
// Note that these are currently only defaults.
|
||||
userAircraftTrafficRef.reset(new FGAISchedule);
|
||||
userAircraftTrafficRef->setFlightType("gate");
|
||||
|
||||
userAircraftScheduledFlight.reset(new FGScheduledFlight);
|
||||
userAircraftScheduledFlight->setDepartureAirport(curAirport);
|
||||
userAircraftScheduledFlight->setArrivalAirport(destination);
|
||||
userAircraftScheduledFlight->initializeAirports();
|
||||
userAircraftScheduledFlight->setFlightRules("IFR");
|
||||
userAircraftScheduledFlight->setCallSign(callsign);
|
||||
|
||||
userAircraftTrafficRef->assign(userAircraftScheduledFlight.get());
|
||||
std::unique_ptr<FGAIFlightPlan> fp ;
|
||||
userAircraft->setTrafficRef(userAircraftTrafficRef.get());
|
||||
|
||||
string flightPlanName = curAirport + "-" + _routeManagerDestinationAirportNode->getStringValue() + ".xml";
|
||||
//double cruiseAlt = 100; // Doesn't really matter right now.
|
||||
//double courseToDest = 180; // Just use something neutral; this value might affect the runway that is used though...
|
||||
//time_t deptime = 0; // just make sure how flightplan processing is affected by this...
|
||||
|
||||
|
||||
FGAirportDynamicsRef dcs(flightgear::AirportDynamicsManager::find(curAirport));
|
||||
if (dcs && onGround) {// && !runway.empty()) {
|
||||
|
||||
ParkingAssignment pk;
|
||||
|
||||
if (parking == "AVAILABLE") {
|
||||
double radius = fgGetDouble("/sim/dimensions/radius-m");
|
||||
if (radius > 0) {
|
||||
pk = dcs->getAvailableParking(radius, string(), string(), string());
|
||||
if (pk.isValid()) {
|
||||
fgGetString("/sim/presets/parkpos");
|
||||
fgSetString("/sim/presets/parkpos", pk.parking()->getName());
|
||||
}
|
||||
}
|
||||
|
||||
if (!pk.isValid()) {
|
||||
FGParkingList pkl(dcs->getParkings(true, "gate"));
|
||||
if (!pkl.empty()) {
|
||||
std::sort(pkl.begin(), pkl.end(), [](const FGParkingRef& a, const FGParkingRef& b) {
|
||||
return a->getRadius() > b->getRadius();
|
||||
});
|
||||
pk = ParkingAssignment(pkl.front(), dcs);
|
||||
fgSetString("/sim/presets/parkpos", pkl.front()->getName());
|
||||
}
|
||||
}
|
||||
} else if (!parking.empty()) {
|
||||
pk = dcs->getAvailableParkingByName(parking);
|
||||
}
|
||||
|
||||
if (pk.isValid()) {
|
||||
dcs->setParkingAvailable(pk.parking(), false);
|
||||
fp.reset(new FGAIFlightPlan);
|
||||
controller = dcs->getStartupController();
|
||||
int stationFreq = dcs->getGroundFrequency(1);
|
||||
if (stationFreq > 0)
|
||||
{
|
||||
SG_LOG(SG_ATC, SG_DEBUG, "Setting radio frequency to : " << stationFreq);
|
||||
fgSetDouble("/instrumentation/comm[0]/frequencies/selected-mhz", ((double) stationFreq / 100.0));
|
||||
}
|
||||
leg = 1;
|
||||
//double, lat, lon, head; // Unused variables;
|
||||
//int getId = apt->getDynamics()->getParking(gateId, &lat, &lon, &head);
|
||||
aircraftRadius = pk.parking()->getRadius();
|
||||
string fltType = pk.parking()->getType(); // gate / ramp, ga, etc etc.
|
||||
string aircraftType; // Unused.
|
||||
string airline; // Currently used for gate selection, but a fallback mechanism will apply when not specified.
|
||||
fp->setGate(pk);
|
||||
if (!(fp->createPushBack(userAircraft,
|
||||
false,
|
||||
dcs->parent(),
|
||||
aircraftRadius,
|
||||
fltType,
|
||||
aircraftType,
|
||||
airline))) {
|
||||
controller = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
|
||||
} else if (!runway.empty()) {
|
||||
// on a runway
|
||||
|
||||
controller = dcs->getTowerController();
|
||||
int stationFreq = dcs->getTowerFrequency(2);
|
||||
if (stationFreq > 0)
|
||||
{
|
||||
SG_LOG(SG_ATC, SG_DEBUG, "Setting radio frequency to inair frequency : " << stationFreq);
|
||||
fgSetDouble("/instrumentation/comm[0]/frequencies/selected-mhz", ((double) stationFreq / 100.0));
|
||||
}
|
||||
fp.reset(new FGAIFlightPlan);
|
||||
leg = 3;
|
||||
string fltType = "ga";
|
||||
fp->setRunway(runway);
|
||||
fp->createTakeOff(userAircraft, false, dcs->parent(), {}, 0, fltType);
|
||||
userAircraft->setTakeOffStatus(2);
|
||||
} else {
|
||||
// We're on the ground somewhere. Handle this case later.
|
||||
|
||||
// important : we are on the ground, so reset the AIFlightPlan back to
|
||||
// a dummy one. Otherwise, in the reposition case, we end up with a
|
||||
// stale flight-plan which confuses other code (eg, PositionInit::finalizeForParking)
|
||||
// see unit test: PosInitTests::testRepositionAtOccupied
|
||||
fp.reset(FGAIFlightPlan::createDummyUserPlan());
|
||||
userAircraft->FGAIBase::setFlightPlan(std::move(fp));
|
||||
controller = nullptr;
|
||||
|
||||
initSucceeded = true; // should be false?
|
||||
return;
|
||||
}
|
||||
|
||||
if (fp && !fp->empty()) {
|
||||
fp->getLastWaypoint()->setName( fp->getLastWaypoint()->getName() + string("legend"));
|
||||
}
|
||||
} else {
|
||||
controller = 0;
|
||||
}
|
||||
|
||||
// Create an initial flightplan and assign it to the ai_ac. We won't use this flightplan, but it is necessary to
|
||||
// keep the ATC code happy.
|
||||
// note in the reposition case, 'fp' is only the new FlightPlan; if we didn't create one here.
|
||||
// we will continue using the existing flight plan (and not restart it, for example)
|
||||
if (fp) {
|
||||
fp->restart();
|
||||
fp->setLeg(leg);
|
||||
userAircraft->FGAIBase::setFlightPlan(std::move(fp));
|
||||
}
|
||||
|
||||
if (controller) {
|
||||
FGAIFlightPlan* plan = userAircraft->GetFlightPlan();
|
||||
const int routeIndex = (plan && plan->getCurrentWaypoint()) ? plan->getCurrentWaypoint()->getRouteIndex() : 0;
|
||||
controller->announcePosition(userAircraft->getID(), plan,
|
||||
routeIndex,
|
||||
userAircraft->_getLatitude(),
|
||||
userAircraft->_getLongitude(),
|
||||
userAircraft->_getHeading(),
|
||||
userAircraft->getSpeed(),
|
||||
userAircraft->getAltitude(),
|
||||
aircraftRadius, leg, userAircraft);
|
||||
}
|
||||
initSucceeded = true;
|
||||
}
|
||||
|
||||
/**
|
||||
Shutdown method
|
||||
Clears activeStations vector in preparation for clean shutdown
|
||||
Override of SGSubsystem::shutdown()
|
||||
*/
|
||||
void FGATCManager::shutdown()
|
||||
{
|
||||
activeStations.clear();
|
||||
userAircraftTrafficRef.reset();
|
||||
userAircraftScheduledFlight.reset();
|
||||
_routeManagerDestinationAirportNode.clear();
|
||||
}
|
||||
|
||||
void FGATCManager::reposition()
|
||||
{
|
||||
prevController = controller = nullptr;
|
||||
|
||||
// remove any parking assignment form the user flight-plan, so it's
|
||||
// available again. postinit() will recompute a new value if required
|
||||
FGAIManager* aiManager = globals->get_subsystem<FGAIManager>();
|
||||
auto userAircraft = aiManager->getUserAircraft();
|
||||
if (userAircraft) {
|
||||
if (userAircraft->GetFlightPlan()) {
|
||||
auto userAIFP = userAircraft->GetFlightPlan();
|
||||
userAIFP->setGate({}); // clear any assignment
|
||||
}
|
||||
|
||||
userAircraft->clearATCController();
|
||||
}
|
||||
|
||||
postinit(); // critical for position-init logic
|
||||
}
|
||||
|
||||
/**
|
||||
Adds FGATCController instance to std::vector activeStations.
|
||||
FGATCController is a basic class for every controller
|
||||
*/
|
||||
void FGATCManager::addController(FGATCController *controller) {
|
||||
activeStations.push_back(controller);
|
||||
}
|
||||
|
||||
/**
|
||||
Searches for and removes FGATCController instance from std::vector activeStations
|
||||
*/
|
||||
void FGATCManager::removeController(FGATCController *controller)
|
||||
{
|
||||
AtcVecIterator it;
|
||||
it = std::find(activeStations.begin(), activeStations.end(), controller);
|
||||
if (it != activeStations.end()) {
|
||||
activeStations.erase(it);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Update the subsystem.
|
||||
FlightGear invokes this method every time the subsystem should
|
||||
update its state.
|
||||
|
||||
@param time The delta time, in seconds, since the last
|
||||
update. On first update, delta time will be 0.
|
||||
*/
|
||||
void FGATCManager::update ( double time ) {
|
||||
// SG_LOG(SG_ATC, SG_BULK, "ATC update code is running at time: " << time);
|
||||
|
||||
// Test code: let my virtual co-pilot handle ATC
|
||||
FGAIManager* aiManager = globals->get_subsystem<FGAIManager>();
|
||||
FGAIAircraft* user_ai_ac = aiManager->getUserAircraft();
|
||||
FGAIFlightPlan *fp = user_ai_ac->GetFlightPlan();
|
||||
|
||||
// Update destination
|
||||
string result = _routeManagerDestinationAirportNode->getStringValue();
|
||||
|
||||
if (destination != result && result != "") {
|
||||
destination = result;
|
||||
userAircraftScheduledFlight->setArrivalAirport(destination);
|
||||
userAircraftScheduledFlight->initializeAirports();
|
||||
userAircraftTrafficRef->clearAllFlights();
|
||||
userAircraftTrafficRef->assign(userAircraftScheduledFlight.get());
|
||||
|
||||
auto userAircraft = aiManager->getUserAircraft();
|
||||
userAircraft->setTrafficRef(userAircraftTrafficRef.get());
|
||||
}
|
||||
|
||||
/* test code : find out how the routing develops */
|
||||
if (fp) {
|
||||
int size = fp->getNrOfWayPoints();
|
||||
//SG_LOG(SG_ATC, SG_DEBUG, "Setting pos" << pos << " ");
|
||||
//SG_LOG(SG_ATC, SG_DEBUG, "Setting intentions");
|
||||
// This indicates that we have run out of waypoints: Im future versions, the
|
||||
// user should be able to select a new route, but for now just shut down the
|
||||
// system.
|
||||
if (size < 3) {
|
||||
return;
|
||||
}
|
||||
#if 0
|
||||
// Test code: Print how far we're progressing along the taxi route.
|
||||
SG_LOG(SG_ATC, SG_DEBUG, "Size of waypoint queue " << size);
|
||||
for (int i = 0; i < size; i++) {
|
||||
int val = fp->getRouteIndex(i);
|
||||
SG_LOG(SG_ATC, SG_BULK, fp->getWayPoint(i)->getName() << " ");
|
||||
//if ((val) && (val != pos)) {
|
||||
// intentions.push_back(val);
|
||||
SG_LOG(SG_ATC, SG_BULK, "[done ]");
|
||||
//}
|
||||
}
|
||||
SG_LOG(SG_ATC, SG_BULK, "[done ]");
|
||||
#endif
|
||||
}
|
||||
if (fp) {
|
||||
SG_LOG(SG_ATC, SG_DEBUG, "User aircraft currently at leg : " << fp->getLeg());
|
||||
}
|
||||
|
||||
// Call getATCController method; returns what FGATCController presently controls the user aircraft
|
||||
// - e.g. FGStartupController
|
||||
controller = user_ai_ac->getATCController();
|
||||
|
||||
// Update the ATC dialog
|
||||
//FGATCDialogNew::instance()->update(time);
|
||||
|
||||
// Controller manager - if controller is set, then will update controller
|
||||
if (controller) {
|
||||
SG_LOG(SG_ATC, SG_DEBUG, "name of previous waypoint : " << fp->getPreviousWaypoint()->getName());
|
||||
SG_LOG(SG_ATC, SG_DEBUG, "Currently under control of " << controller->getName());
|
||||
|
||||
// update aircraft information (simulates transponder)
|
||||
|
||||
controller->updateAircraftInformation(user_ai_ac->getID(),
|
||||
user_ai_ac->getGeodPos(),
|
||||
user_ai_ac->_getHeading(),
|
||||
user_ai_ac->getSpeed(),
|
||||
user_ai_ac->getAltitude(), time);
|
||||
|
||||
//string airport = fgGetString("/sim/presets/airport-id");
|
||||
//FGAirport *apt = FGAirport::findByIdent(airport);
|
||||
// AT this stage we should update the flightplan, so that waypoint incrementing is conducted as well as leg loading.
|
||||
|
||||
// Ground network visibility:
|
||||
// a) check to see if the message to toggle visibility was called
|
||||
// b) if so, toggle network visibility and reset the transmission
|
||||
// c) therafter disable rendering for the old controller (TODO: should this be earlier?)
|
||||
// d) and render if enabled for the new controller
|
||||
int n = trans_num->getIntValue();
|
||||
|
||||
if (n == 1) {
|
||||
SG_LOG(SG_ATC, SG_DEBUG, "Toggling ground network visibility " << networkVisible);
|
||||
networkVisible = !networkVisible;
|
||||
trans_num->setIntValue(-1);
|
||||
}
|
||||
|
||||
// stop rendering the old controller's groundnetwork
|
||||
if ((controller != prevController) && (prevController)) {
|
||||
prevController->render(false);
|
||||
}
|
||||
|
||||
// render the path for the present controller if the ground network is set to visible
|
||||
controller->render(networkVisible);
|
||||
|
||||
SG_LOG(SG_ATC, SG_BULK, "Adding ground network to the scenegraph::update");
|
||||
|
||||
// reset previous controller for next update() iteration
|
||||
prevController = controller;
|
||||
}
|
||||
|
||||
// update the active ATC stations
|
||||
for (AtcVecIterator atc = activeStations.begin(); atc != activeStations.end(); ++atc) {
|
||||
(*atc)->update(time);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Register the subsystem.
|
||||
SGSubsystemMgr::Registrant<FGATCManager> registrantFGATCManager(
|
||||
SGSubsystemMgr::POST_FDM,
|
||||
{{"FGAIManager", SGSubsystemMgr::Dependency::HARD}});
|
||||
83
src/ATC/atc_mgr.hxx
Normal file
83
src/ATC/atc_mgr.hxx
Normal file
@@ -0,0 +1,83 @@
|
||||
/* -*- Mode: C++ -*- *****************************************************
|
||||
* atic.hxx
|
||||
* Written by Durk Talsma. Started August 1, 2010; based on earlier work
|
||||
* by David C. Luff
|
||||
*
|
||||
* Updated by Jonathan Redpath. Started June 12, 2019. Documenting and extending
|
||||
* functionality of the ATC subsystem
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License as
|
||||
* published by the Free Software Foundation; either version 2 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*
|
||||
*
|
||||
**************************************************************************/
|
||||
|
||||
/**************************************************************************
|
||||
* The ATC Manager interfaces the users aircraft within the AI traffic system
|
||||
* and also monitors the ongoing AI traffic patterns for potential conflicts
|
||||
* and interferes where necessary.
|
||||
*************************************************************************/
|
||||
|
||||
#ifndef _ATC_MGR_HXX_
|
||||
#define _ATC_MGR_HXX_
|
||||
|
||||
|
||||
#include <simgear/structure/subsystem_mgr.hxx>
|
||||
#include <simgear/structure/SGReferenced.hxx>
|
||||
|
||||
#include <ATC/trafficcontrol.hxx>
|
||||
#include <AIModel/AIAircraft.hxx>
|
||||
#include <Traffic/Schedule.hxx>
|
||||
#include <Traffic/SchedFlight.hxx>
|
||||
|
||||
typedef std::vector<FGATCController*> AtcVec;
|
||||
typedef std::vector<FGATCController*>::iterator AtcVecIterator;
|
||||
|
||||
class FGATCManager : public SGSubsystem
|
||||
{
|
||||
private:
|
||||
AtcVec activeStations;
|
||||
FGATCController *controller, *prevController; // The ATC controller that is responsible for the user's aircraft.
|
||||
bool networkVisible;
|
||||
bool initSucceeded;
|
||||
SGPropertyNode_ptr trans_num;
|
||||
string destination;
|
||||
|
||||
std::unique_ptr<FGAISchedule> userAircraftTrafficRef;
|
||||
std::unique_ptr<FGScheduledFlight> userAircraftScheduledFlight;
|
||||
|
||||
SGPropertyNode_ptr _routeManagerDestinationAirportNode;
|
||||
|
||||
public:
|
||||
FGATCManager();
|
||||
virtual ~FGATCManager();
|
||||
|
||||
// Subsystem API.
|
||||
void postinit() override;
|
||||
void shutdown() override;
|
||||
void update(double time) override;
|
||||
|
||||
|
||||
|
||||
// Subsystem identification.
|
||||
static const char* staticSubsystemClassId() { return "ATC"; }
|
||||
|
||||
void addController(FGATCController *controller);
|
||||
void removeController(FGATCController* controller);
|
||||
|
||||
void reposition();
|
||||
|
||||
};
|
||||
|
||||
#endif // _ATC_MRG_HXX_
|
||||
579
src/ATC/trafficcontrol.cxx
Normal file
579
src/ATC/trafficcontrol.cxx
Normal file
@@ -0,0 +1,579 @@
|
||||
// trafficrecord.cxx - Implementation of AIModels ATC code.
|
||||
//
|
||||
// Written by Durk Talsma, started September 2006.
|
||||
//
|
||||
// Copyright (C) 2006 Durk Talsma.
|
||||
//
|
||||
// This program is free software; you can redistribute it and/or
|
||||
// modify it under the terms of the GNU General Public License as
|
||||
// published by the Free Software Foundation; either version 2 of the
|
||||
// License, or (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful, but
|
||||
// WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program; if not, write to the Free Software
|
||||
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
//
|
||||
// $Id$
|
||||
|
||||
#include <config.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdio>
|
||||
#include <random>
|
||||
|
||||
#include <osg/Geode>
|
||||
#include <osg/Geometry>
|
||||
#include <osg/MatrixTransform>
|
||||
#include <osg/Shape>
|
||||
|
||||
#include <simgear/scene/material/EffectGeode.hxx>
|
||||
#include <simgear/scene/material/matlib.hxx>
|
||||
#include <simgear/scene/material/mat.hxx>
|
||||
#include <simgear/scene/util/OsgMath.hxx>
|
||||
#include <simgear/timing/sg_time.hxx>
|
||||
#include <simgear/math/sg_geodesy.hxx>
|
||||
|
||||
#include <Scenery/scenery.hxx>
|
||||
|
||||
#include "trafficcontrol.hxx"
|
||||
#include "atc_mgr.hxx"
|
||||
#include <AIModel/AIAircraft.hxx>
|
||||
#include <AIModel/AIFlightPlan.hxx>
|
||||
#include <AIModel/performancedata.hxx>
|
||||
#include <ATC/atc_mgr.hxx>
|
||||
#include <Traffic/TrafficMgr.hxx>
|
||||
#include <Airports/groundnetwork.hxx>
|
||||
#include <Airports/dynamics.hxx>
|
||||
#include <Airports/airport.hxx>
|
||||
#include <Radio/radio.hxx>
|
||||
#include <signal.h>
|
||||
|
||||
using std::sort;
|
||||
using std::string;
|
||||
|
||||
/***************************************************************************
|
||||
* ActiveRunway
|
||||
**************************************************************************/
|
||||
|
||||
ActiveRunway::ActiveRunway(const std::string& r, int cc) :
|
||||
rwy(r)
|
||||
{
|
||||
currentlyCleared = cc;
|
||||
distanceToFinal = 6.0 * SG_NM_TO_METER;
|
||||
};
|
||||
|
||||
void ActiveRunway::updateDepartureQueue()
|
||||
{
|
||||
departureQueue.erase(departureQueue.begin());
|
||||
}
|
||||
|
||||
/*
|
||||
* Fetch next slot for the active runway
|
||||
* @param eta time of slot requested
|
||||
* @return newEta: next slot available; starts at eta paramater
|
||||
* and adds separation as needed
|
||||
*/
|
||||
time_t ActiveRunway::requestTimeSlot(time_t eta)
|
||||
{
|
||||
time_t newEta = 0;
|
||||
// default separation - 60 seconds
|
||||
time_t separation = 60;
|
||||
//if (wakeCategory == "heavy_jet") {
|
||||
// SG_LOG(SG_ATC, SG_DEBUG, "Heavy jet, using extra separation");
|
||||
// time_t separation = 120;
|
||||
//}
|
||||
bool found = false;
|
||||
|
||||
// if the aircraft is the first arrival, add to the vector and return eta directly
|
||||
if (estimatedArrivalTimes.empty()) {
|
||||
estimatedArrivalTimes.push_back(eta);
|
||||
SG_LOG(SG_ATC, SG_DEBUG, getRunwayName() << "Checked eta slots, using " << eta);
|
||||
return eta;
|
||||
} else {
|
||||
// First check the already assigned slots to see where we need to fit the flight in
|
||||
TimeVectorIterator i = estimatedArrivalTimes.begin();
|
||||
SG_LOG(SG_ATC, SG_DEBUG, getRunwayName() << " Checking eta slots " << eta << " : " << estimatedArrivalTimes.size() << " Timediff " << (eta - globals->get_time_params()->get_cur_time()));
|
||||
|
||||
// is this needed - just a debug output?
|
||||
for (i = estimatedArrivalTimes.begin();
|
||||
i != estimatedArrivalTimes.end(); i++) {
|
||||
SG_LOG(SG_ATC, SG_BULK, "Stored time : " << (*i));
|
||||
}
|
||||
|
||||
// if the flight is before the first scheduled slot + separation
|
||||
i = estimatedArrivalTimes.begin();
|
||||
if ((eta + separation) < (*i)) {
|
||||
newEta = eta;
|
||||
SG_LOG(SG_ATC, SG_BULK, "Storing at beginning");
|
||||
SG_LOG(SG_ATC, SG_DEBUG, "Done. New ETA : " << newEta);
|
||||
slotHousekeeping(newEta);
|
||||
return newEta;
|
||||
}
|
||||
|
||||
// else, look through the rest of the slots
|
||||
while ((i != estimatedArrivalTimes.end()) && (!found)) {
|
||||
TimeVectorIterator j = i + 1;
|
||||
|
||||
// if the flight is after the last scheduled slot check if separation is needed
|
||||
if (j == estimatedArrivalTimes.end()) {
|
||||
if (((*i) + separation) < eta) {
|
||||
SG_LOG(SG_ATC, SG_BULK, "Storing at end");
|
||||
newEta = eta;
|
||||
} else {
|
||||
newEta = (*i) + separation;
|
||||
SG_LOG(SG_ATC, SG_BULK, "Storing at end + separation");
|
||||
}
|
||||
SG_LOG(SG_ATC, SG_DEBUG, "Done. New ETA : " << newEta);
|
||||
slotHousekeeping(newEta);
|
||||
return newEta;
|
||||
} else {
|
||||
// potential slot found
|
||||
// check the distance between the previous and next slots
|
||||
// distance msut be greater than 2* separation
|
||||
if ((((*j) - (*i)) > (separation * 2))) {
|
||||
// now check whether this slot is usable:
|
||||
// eta should fall between the two points
|
||||
// i.e. eta > i AND eta < j
|
||||
SG_LOG(SG_ATC, SG_DEBUG, "Found potential slot after " << (*i));
|
||||
if (eta > (*i) && (eta < (*j))) {
|
||||
found = true;
|
||||
if (eta < ((*i) + separation)) {
|
||||
newEta = (*i) + separation;
|
||||
SG_LOG(SG_ATC, SG_BULK, "Using original" << (*i) << " + separation ");
|
||||
} else {
|
||||
newEta = eta;
|
||||
SG_LOG(SG_ATC, SG_BULK, "Using original after " << (*i));
|
||||
}
|
||||
} else if (eta < (*i)) {
|
||||
found = true;
|
||||
newEta = (*i) + separation;
|
||||
SG_LOG(SG_ATC, SG_BULK, "Using delayed slot after " << (*i));
|
||||
}
|
||||
/*
|
||||
if (((*j) - separation) < eta) {
|
||||
found = true;
|
||||
if (((*i) + separation) < eta) {
|
||||
newEta = eta;
|
||||
SG_LOG(SG_ATC, SG_BULK, "Using original after " << (*i));
|
||||
} else {
|
||||
newEta = (*i) + separation;
|
||||
SG_LOG(SG_ATC, SG_BULK, "Using " << (*i) << " + separation ");
|
||||
}
|
||||
} */
|
||||
}
|
||||
}
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
SG_LOG(SG_ATC, SG_DEBUG, "Done. New ETA : " << newEta);
|
||||
slotHousekeeping(newEta);
|
||||
return newEta;
|
||||
}
|
||||
|
||||
void ActiveRunway::slotHousekeeping(time_t newEta)
|
||||
{
|
||||
// add the slot to the vector and resort the vector
|
||||
estimatedArrivalTimes.push_back(newEta);
|
||||
sort(estimatedArrivalTimes.begin(), estimatedArrivalTimes.end());
|
||||
|
||||
// do some housekeeping : remove any slots that are past
|
||||
time_t now = globals->get_time_params()->get_cur_time();
|
||||
|
||||
TimeVectorIterator i = estimatedArrivalTimes.begin();
|
||||
while (i != estimatedArrivalTimes.end()) {
|
||||
if ((*i) < now) {
|
||||
SG_LOG(SG_ATC, SG_BULK, "Deleting timestamp " << (*i) << " (now = " << now << "). ");
|
||||
estimatedArrivalTimes.erase(i);
|
||||
i = estimatedArrivalTimes.begin();
|
||||
} else {
|
||||
i++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Output the contents of the departure queue vector nicely formatted*/
|
||||
void ActiveRunway::printDepartureQueue()
|
||||
{
|
||||
SG_LOG(SG_ATC, SG_DEBUG, "Departure queue for " << rwy << ": ");
|
||||
for (auto acft : departureQueue) {
|
||||
SG_LOG(SG_ATC, SG_DEBUG, " " << acft->getCallSign() << " " << acft->getTakeOffStatus());
|
||||
SG_LOG(SG_ATC, SG_DEBUG, " " << acft->_getLatitude() << " " << acft->_getLongitude() << acft->getSpeed() << " " << acft->getAltitude());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/* Fetch the first aircraft in the departure queue with a certain status */
|
||||
SGSharedPtr<FGAIAircraft>ActiveRunway::getFirstOfStatus(int stat) const
|
||||
{
|
||||
auto it = std::find_if(departureQueue.begin(), departureQueue.end(), [stat](const SGSharedPtr<FGAIAircraft>& acft) {
|
||||
return acft->getTakeOffStatus() == stat;
|
||||
});
|
||||
|
||||
if (it == departureQueue.end()) {
|
||||
return {};
|
||||
}
|
||||
|
||||
return *it;
|
||||
}
|
||||
|
||||
SGSharedPtr<FGAIAircraft> ActiveRunway::getFirstAircraftInDepartureQueue() const
|
||||
{
|
||||
if (departureQueue.empty()) {
|
||||
return {};
|
||||
}
|
||||
|
||||
return departureQueue.front();
|
||||
};
|
||||
|
||||
void ActiveRunway::addToDepartureQueue(FGAIAircraft *ac)
|
||||
{
|
||||
assert(ac);
|
||||
assert(!ac->getDie());
|
||||
departureQueue.push_back(ac);
|
||||
};
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
* FGTrafficRecord
|
||||
**************************************************************************/
|
||||
|
||||
FGTrafficRecord::FGTrafficRecord():
|
||||
id(0), waitsForId(0),
|
||||
currentPos(0),
|
||||
leg(0),
|
||||
frequencyId(0),
|
||||
state(0),
|
||||
allowTransmission(true),
|
||||
allowPushback(true),
|
||||
priority(0),
|
||||
timer(0),
|
||||
heading(0), speed(0), altitude(0), radius(0)
|
||||
{
|
||||
}
|
||||
|
||||
FGTrafficRecord::~FGTrafficRecord()
|
||||
{
|
||||
}
|
||||
|
||||
void FGTrafficRecord::setPositionAndIntentions(int pos,
|
||||
FGAIFlightPlan * route)
|
||||
{
|
||||
SG_LOG(SG_ATC, SG_DEBUG, "Position: " << pos);
|
||||
currentPos = pos;
|
||||
if (!intentions.empty()) {
|
||||
intVecIterator i = intentions.begin();
|
||||
if ((*i) != currentPos) {
|
||||
SG_LOG(SG_ATC, SG_ALERT,
|
||||
"Error in FGTrafficRecord::setPositionAndIntentions at " << SG_ORIGIN << ", " << (*i));
|
||||
}
|
||||
intentions.erase(i);
|
||||
} else {
|
||||
//FGAIFlightPlan::waypoint* const wpt= route->getCurrentWaypoint();
|
||||
int size = route->getNrOfWayPoints();
|
||||
SG_LOG(SG_ATC, SG_DEBUG, "Setting pos" << currentPos);
|
||||
SG_LOG(SG_ATC, SG_DEBUG, "Setting intentions");
|
||||
for (int i = 2; i < size; i++) {
|
||||
int val = route->getRouteIndex(i);
|
||||
intentions.push_back(val);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void FGTrafficRecord::setAircraft(FGAIAircraft *ref)
|
||||
{
|
||||
aircraft = ref;
|
||||
}
|
||||
|
||||
bool FGTrafficRecord::isDead() const {
|
||||
if (!aircraft) {
|
||||
return true;
|
||||
}
|
||||
return aircraft->getDie();
|
||||
}
|
||||
|
||||
void FGTrafficRecord::clearATCController() const {
|
||||
if (aircraft) {
|
||||
aircraft->clearATCController();
|
||||
}
|
||||
}
|
||||
|
||||
FGAIAircraft* FGTrafficRecord::getAircraft() const
|
||||
{
|
||||
if(aircraft.valid()) {
|
||||
return aircraft.ptr();
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
/**
|
||||
* Check if another aircraft is ahead of the current one, and on the same taxiway
|
||||
* @return true / false if this is/isn't the case.
|
||||
*/
|
||||
bool FGTrafficRecord::checkPositionAndIntentions(FGTrafficRecord & other)
|
||||
{
|
||||
bool result = false;
|
||||
SG_LOG(SG_ATC, SG_BULK, getCallsign() << "|checkPositionAndIntentions");
|
||||
if (currentPos == other.currentPos && getId() != other.getId() ) {
|
||||
SG_LOG(SG_ATC, SG_BULK, getCallsign() << "|Check Position and intentions: " << other.getCallsign() << " we are on the same taxiway; Index = " << currentPos);
|
||||
result = true;
|
||||
}
|
||||
// else if (! other.intentions.empty())
|
||||
// {
|
||||
// SG_LOG(SG_ATC, SG_BULK, "Start check 2");
|
||||
// intVecIterator i = other.intentions.begin();
|
||||
// while (!((i == other.intentions.end()) || ((*i) == currentPos)))
|
||||
// i++;
|
||||
// if (i != other.intentions.end()) {
|
||||
// SG_LOG(SG_ATC, SG_BULK, "Check Position and intentions: current matches other.intentions");
|
||||
// result = true;
|
||||
// }
|
||||
else if (! intentions.empty()) {
|
||||
SG_LOG(SG_ATC, SG_BULK, getCallsign() << "|Itentions " << intentions.size());
|
||||
intVecIterator i = intentions.begin();
|
||||
//while (!((i == intentions.end()) || ((*i) == other.currentPos)))
|
||||
while (i != intentions.end()) {
|
||||
if ((*i) == other.currentPos) {
|
||||
break;
|
||||
}
|
||||
i++;
|
||||
}
|
||||
if (i != intentions.end()) {
|
||||
SG_LOG(SG_ATC, SG_BULK, getCallsign() << "| Check Position and intentions: " << other.getCallsign()<< " matches Index = " << (*i));
|
||||
result = true;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
void FGTrafficRecord::setPositionAndHeading(double lat, double lon,
|
||||
double hdg, double spd,
|
||||
double alt)
|
||||
{
|
||||
this->pos = SGGeod::fromDegFt(lon, lat, alt);
|
||||
heading = hdg;
|
||||
speed = spd;
|
||||
altitude = alt;
|
||||
}
|
||||
|
||||
int FGTrafficRecord::crosses(FGGroundNetwork * net,
|
||||
FGTrafficRecord & other)
|
||||
{
|
||||
if (checkPositionAndIntentions(other)
|
||||
|| (other.checkPositionAndIntentions(*this)))
|
||||
return -1;
|
||||
intVecIterator i, j;
|
||||
int currentTargetNode = 0, otherTargetNode = 0;
|
||||
if (currentPos > 0)
|
||||
currentTargetNode = net->findSegment(currentPos)->getEnd()->getIndex(); // OKAY,...
|
||||
if (other.currentPos > 0)
|
||||
otherTargetNode = net->findSegment(other.currentPos)->getEnd()->getIndex(); // OKAY,...
|
||||
if ((currentTargetNode == otherTargetNode) && currentTargetNode > 0)
|
||||
return currentTargetNode;
|
||||
if (! intentions.empty()) {
|
||||
for (i = intentions.begin(); i != intentions.end(); i++) {
|
||||
if ((*i) > 0) {
|
||||
if (currentTargetNode ==
|
||||
net->findSegment(*i)->getEnd()->getIndex()) {
|
||||
SG_LOG(SG_ATC, SG_BULK, "Current crosses at " << currentTargetNode);
|
||||
return currentTargetNode;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (! other.intentions.empty()) {
|
||||
for (i = other.intentions.begin(); i != other.intentions.end();
|
||||
i++) {
|
||||
if ((*i) > 0) {
|
||||
if (otherTargetNode ==
|
||||
net->findSegment(*i)->getEnd()->getIndex()) {
|
||||
SG_LOG(SG_ATC, SG_BULK, "Other crosses at " << currentTargetNode);
|
||||
return otherTargetNode;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (! intentions.empty() && ! other.intentions.empty()) {
|
||||
for (i = intentions.begin(); i != intentions.end(); i++) {
|
||||
for (j = other.intentions.begin(); j != other.intentions.end();
|
||||
j++) {
|
||||
SG_LOG(SG_ATC, SG_BULK, "finding segment " << *i << " and " << *j);
|
||||
if (((*i) > 0) && ((*j) > 0)) {
|
||||
currentTargetNode =
|
||||
net->findSegment(*i)->getEnd()->getIndex();
|
||||
otherTargetNode =
|
||||
net->findSegment(*j)->getEnd()->getIndex();
|
||||
if (currentTargetNode == otherTargetNode) {
|
||||
SG_LOG(SG_ATC, SG_BULK, "Routes will cross at " << currentTargetNode);
|
||||
return currentTargetNode;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
bool FGTrafficRecord::onRoute(FGGroundNetwork * net,
|
||||
FGTrafficRecord & other)
|
||||
{
|
||||
int node = -1, othernode = -1;
|
||||
if (currentPos > 0)
|
||||
node = net->findSegment(currentPos)->getEnd()->getIndex();
|
||||
if (other.currentPos > 0)
|
||||
othernode =
|
||||
net->findSegment(other.currentPos)->getEnd()->getIndex();
|
||||
if ((node == othernode) && (node != -1))
|
||||
return true;
|
||||
if (! other.intentions.empty()) {
|
||||
for (intVecIterator i = other.intentions.begin();
|
||||
i != other.intentions.end(); i++) {
|
||||
if (*i > 0) {
|
||||
othernode = net->findSegment(*i)->getEnd()->getIndex();
|
||||
if ((node == othernode) && (node > -1))
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
//if (other.currentPos > 0)
|
||||
// othernode = net->findSegment(other.currentPos)->getEnd()->getIndex();
|
||||
//if (! intentions.empty())
|
||||
// {
|
||||
// for (intVecIterator i = intentions.begin(); i != intentions.end(); i++)
|
||||
// {
|
||||
// if (*i > 0)
|
||||
// {
|
||||
// node = net->findSegment(*i)->getEnd()->getIndex();
|
||||
// if ((node == othernode) && (node > -1))
|
||||
// return true;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
bool FGTrafficRecord::isOpposing(FGGroundNetwork * net,
|
||||
FGTrafficRecord & other, int node)
|
||||
{
|
||||
// Check if current segment is the reverse segment for the other aircraft
|
||||
FGTaxiSegment *opp;
|
||||
SG_LOG(SG_ATC, SG_BULK, "Current segment " << currentPos);
|
||||
if ((currentPos > 0) && (other.currentPos > 0)) {
|
||||
opp = net->findSegment(currentPos)->opposite();
|
||||
if (opp) {
|
||||
if (opp->getIndex() == other.currentPos)
|
||||
return true;
|
||||
}
|
||||
|
||||
for (intVecIterator i = intentions.begin(); i != intentions.end();
|
||||
i++) {
|
||||
if ((opp = net->findSegment(other.currentPos)->opposite())) {
|
||||
if ((*i) > 0)
|
||||
if (opp->getIndex() ==
|
||||
net->findSegment(*i)->getIndex()) {
|
||||
if (net->findSegment(*i)->getStart()->getIndex() ==
|
||||
node) {
|
||||
{
|
||||
SG_LOG(SG_ATC, SG_BULK, "Found the node " << node);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (! other.intentions.empty()) {
|
||||
for (intVecIterator j = other.intentions.begin();
|
||||
j != other.intentions.end(); j++) {
|
||||
SG_LOG(SG_ATC, SG_BULK, "Current segment 1 " << (*i));
|
||||
if ((*i) > 0) {
|
||||
if ((opp = net->findSegment(*i)->opposite())) {
|
||||
if (opp->getIndex() ==
|
||||
net->findSegment(*j)->getIndex()) {
|
||||
SG_LOG(SG_ATC, SG_BULK, "Nodes " << net->findSegment(*i)->getIndex()
|
||||
<< " and " << net->findSegment(*j)->getIndex()
|
||||
<< " are opposites ");
|
||||
if (net->findSegment(*i)->getStart()->
|
||||
getIndex() == node) {
|
||||
{
|
||||
SG_LOG(SG_ATC, SG_BULK, "Found the node " << node);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool FGTrafficRecord::isActive(int margin) const
|
||||
{
|
||||
if (aircraft->getDie()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
time_t now = globals->get_time_params()->get_cur_time();
|
||||
time_t deptime = aircraft->getTrafficRef()->getDepartureTime();
|
||||
return ((now + margin) > deptime);
|
||||
}
|
||||
|
||||
|
||||
void FGTrafficRecord::setSpeedAdjustment(double spd)
|
||||
{
|
||||
instruction.setChangeSpeed(true);
|
||||
instruction.setSpeed(spd);
|
||||
}
|
||||
|
||||
void FGTrafficRecord::setHeadingAdjustment(double heading)
|
||||
{
|
||||
instruction.setChangeHeading(true);
|
||||
instruction.setHeading(heading);
|
||||
}
|
||||
|
||||
bool FGTrafficRecord::pushBackAllowed() const
|
||||
{
|
||||
return allowPushback;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
* FGATCInstruction
|
||||
*
|
||||
**************************************************************************/
|
||||
|
||||
FGATCInstruction::FGATCInstruction()
|
||||
{
|
||||
holdPattern = false;
|
||||
holdPosition = false;
|
||||
changeSpeed = false;
|
||||
changeHeading = false;
|
||||
changeAltitude = false;
|
||||
resolveCircularWait = false;
|
||||
|
||||
speed = 0;
|
||||
heading = 0;
|
||||
alt = 0;
|
||||
}
|
||||
|
||||
bool FGATCInstruction::hasInstruction() const
|
||||
{
|
||||
return (holdPattern || holdPosition || changeSpeed || changeHeading
|
||||
|| changeAltitude || resolveCircularWait);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
377
src/ATC/trafficcontrol.hxx
Normal file
377
src/ATC/trafficcontrol.hxx
Normal file
@@ -0,0 +1,377 @@
|
||||
// trafficcontrol.hxx - classes to manage AIModels based air traffic control
|
||||
// Written by Durk Talsma, started September 2006.
|
||||
//
|
||||
// This program is free software; you can redistribute it and/or
|
||||
// modify it under the terms of the GNU General Public License as
|
||||
// published by the Free Software Foundation; either version 2 of the
|
||||
// License, or (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful, but
|
||||
// WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program; if not, write to the Free Software
|
||||
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
//
|
||||
// $Id$
|
||||
|
||||
|
||||
#ifndef _TRAFFIC_CONTROL_HXX_
|
||||
#define _TRAFFIC_CONTROL_HXX_
|
||||
|
||||
#include <Airports/airports_fwd.hxx>
|
||||
|
||||
#include <osg/Geode>
|
||||
#include <osg/Geometry>
|
||||
#include <osg/MatrixTransform>
|
||||
#include <osg/Shape>
|
||||
|
||||
#include <simgear/compiler.h>
|
||||
// There is probably a better include than sg_geodesy to get the SG_NM_TO_METER...
|
||||
#include <simgear/math/sg_geodesy.hxx>
|
||||
#include <simgear/debug/logstream.hxx>
|
||||
#include <simgear/structure/SGReferenced.hxx>
|
||||
#include <simgear/structure/SGSharedPtr.hxx>
|
||||
|
||||
class FGAIAircraft;
|
||||
typedef std::vector<FGAIAircraft*> AircraftVec;
|
||||
typedef std::vector<FGAIAircraft*>::iterator AircraftVecIterator;
|
||||
|
||||
class FGAIFlightPlan;
|
||||
typedef std::vector<FGAIFlightPlan*> FlightPlanVec;
|
||||
typedef std::vector<FGAIFlightPlan*>::iterator FlightPlanVecIterator;
|
||||
typedef std::map<std::string, FlightPlanVec> FlightPlanVecMap;
|
||||
|
||||
class FGTrafficRecord;
|
||||
typedef std::list<FGTrafficRecord> TrafficVector;
|
||||
typedef std::list<FGTrafficRecord>::iterator TrafficVectorIterator;
|
||||
|
||||
class ActiveRunway;
|
||||
typedef std::vector<ActiveRunway> ActiveRunwayVec;
|
||||
typedef std::vector<ActiveRunway>::iterator ActiveRunwayVecIterator;
|
||||
|
||||
typedef std::vector<int> intVec;
|
||||
typedef std::vector<int>::iterator intVecIterator;
|
||||
|
||||
/**************************************************************************************
|
||||
* class FGATCInstruction
|
||||
* like class FGATC Controller, this class definition should go into its own file
|
||||
* and or directory... For now, just testing this stuff out though...
|
||||
*************************************************************************************/
|
||||
class FGATCInstruction
|
||||
{
|
||||
private:
|
||||
bool holdPattern;
|
||||
bool holdPosition;
|
||||
bool changeSpeed;
|
||||
bool changeHeading;
|
||||
bool changeAltitude;
|
||||
bool resolveCircularWait;
|
||||
|
||||
double speed;
|
||||
double heading;
|
||||
double alt;
|
||||
public:
|
||||
FGATCInstruction();
|
||||
|
||||
bool hasInstruction () const;
|
||||
bool getHoldPattern () const {
|
||||
return holdPattern;
|
||||
};
|
||||
bool getHoldPosition () const {
|
||||
return holdPosition;
|
||||
};
|
||||
bool getChangeSpeed () const {
|
||||
return changeSpeed;
|
||||
};
|
||||
bool getChangeHeading () const {
|
||||
return changeHeading;
|
||||
};
|
||||
bool getChangeAltitude() const {
|
||||
return changeAltitude;
|
||||
};
|
||||
bool getCheckForCircularWait() const {
|
||||
return resolveCircularWait;
|
||||
};
|
||||
|
||||
double getSpeed () const {
|
||||
return speed;
|
||||
};
|
||||
double getHeading () const {
|
||||
return heading;
|
||||
};
|
||||
double getAlt () const {
|
||||
return alt;
|
||||
};
|
||||
|
||||
void setHoldPattern (bool val) {
|
||||
holdPattern = val;
|
||||
};
|
||||
void setHoldPosition (bool val) {
|
||||
holdPosition = val;
|
||||
};
|
||||
void setChangeSpeed (bool val) {
|
||||
changeSpeed = val;
|
||||
};
|
||||
void setChangeHeading (bool val) {
|
||||
changeHeading = val;
|
||||
};
|
||||
void setChangeAltitude(bool val) {
|
||||
changeAltitude = val;
|
||||
};
|
||||
void setResolveCircularWait (bool val) {
|
||||
resolveCircularWait = val;
|
||||
};
|
||||
|
||||
void setSpeed (double val) {
|
||||
speed = val;
|
||||
};
|
||||
void setHeading (double val) {
|
||||
heading = val;
|
||||
};
|
||||
void setAlt (double val) {
|
||||
alt = val;
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/**************************************************************************************
|
||||
* class FGTrafficRecord
|
||||
* Represents the interaction of an AI Aircraft and ATC
|
||||
*************************************************************************************/
|
||||
class FGTrafficRecord
|
||||
{
|
||||
private:
|
||||
int id;
|
||||
int waitsForId;
|
||||
int currentPos;
|
||||
int leg;
|
||||
int frequencyId;
|
||||
int state;
|
||||
bool allowTransmission;
|
||||
bool allowPushback;
|
||||
int priority;
|
||||
time_t timer;
|
||||
intVec intentions;
|
||||
FGATCInstruction instruction;
|
||||
SGGeod pos;
|
||||
double heading, speed, altitude, radius;
|
||||
std::string callsign;
|
||||
std::string runway;
|
||||
SGSharedPtr<FGAIAircraft> aircraft;
|
||||
|
||||
|
||||
public:
|
||||
FGTrafficRecord();
|
||||
virtual ~FGTrafficRecord();
|
||||
|
||||
void setId(int val) {
|
||||
id = val;
|
||||
};
|
||||
void setRadius(double rad) {
|
||||
radius = rad;
|
||||
};
|
||||
void setPositionAndIntentions(int pos, FGAIFlightPlan *route);
|
||||
void setRunway(const std::string& rwy) {
|
||||
runway = rwy;
|
||||
};
|
||||
void setLeg(int lg) {
|
||||
leg = lg;
|
||||
};
|
||||
int getId() const {
|
||||
return id;
|
||||
};
|
||||
int getState() const {
|
||||
return state;
|
||||
};
|
||||
void setState(int s) {
|
||||
state = s;
|
||||
}
|
||||
FGATCInstruction getInstruction() const {
|
||||
return instruction;
|
||||
};
|
||||
bool hasInstruction() const {
|
||||
return instruction.hasInstruction();
|
||||
};
|
||||
void setPositionAndHeading(double lat, double lon, double hdg, double spd, double alt);
|
||||
bool checkPositionAndIntentions(FGTrafficRecord &other);
|
||||
int crosses (FGGroundNetwork *, FGTrafficRecord &other);
|
||||
bool isOpposing (FGGroundNetwork *, FGTrafficRecord &other, int node);
|
||||
|
||||
bool isActive(int margin) const;
|
||||
bool isDead() const;
|
||||
void clearATCController() const;
|
||||
|
||||
bool onRoute(FGGroundNetwork *, FGTrafficRecord &other);
|
||||
|
||||
bool getSpeedAdjustment() const {
|
||||
return instruction.getChangeSpeed();
|
||||
};
|
||||
|
||||
SGGeod getPos() {
|
||||
return pos;
|
||||
}
|
||||
double getHeading () const {
|
||||
return heading ;
|
||||
};
|
||||
double getSpeed () const {
|
||||
return speed ;
|
||||
};
|
||||
double getFAltitude () const {
|
||||
return altitude ;
|
||||
};
|
||||
double getRadius () const {
|
||||
return radius ;
|
||||
};
|
||||
|
||||
int getWaitsForId () const {
|
||||
return waitsForId;
|
||||
};
|
||||
|
||||
void setSpeedAdjustment(double spd);
|
||||
void setHeadingAdjustment(double heading);
|
||||
void clearSpeedAdjustment () {
|
||||
instruction.setChangeSpeed (false);
|
||||
};
|
||||
void clearHeadingAdjustment() {
|
||||
instruction.setChangeHeading(false);
|
||||
};
|
||||
|
||||
bool hasHeadingAdjustment() const {
|
||||
return instruction.getChangeHeading();
|
||||
};
|
||||
bool hasHoldPosition() const {
|
||||
return instruction.getHoldPosition();
|
||||
};
|
||||
void setHoldPosition (bool inst) {
|
||||
instruction.setHoldPosition(inst);
|
||||
};
|
||||
|
||||
void setWaitsForId(int id) {
|
||||
waitsForId = id;
|
||||
};
|
||||
|
||||
void setResolveCircularWait() {
|
||||
instruction.setResolveCircularWait(true);
|
||||
};
|
||||
void clearResolveCircularWait() {
|
||||
instruction.setResolveCircularWait(false);
|
||||
};
|
||||
|
||||
void setCallsign(std::string clsgn) { callsign = clsgn; };
|
||||
const std::string& getCallsign() const {
|
||||
return callsign;
|
||||
};
|
||||
|
||||
const std::string& getRunway() const {
|
||||
return runway;
|
||||
};
|
||||
|
||||
void setAircraft(FGAIAircraft *ref);
|
||||
|
||||
void updateState() {
|
||||
state++;
|
||||
allowTransmission=true;
|
||||
};
|
||||
//string getCallSign() { return callsign; };
|
||||
FGAIAircraft *getAircraft() const;
|
||||
|
||||
int getTime() const {
|
||||
return timer;
|
||||
};
|
||||
int getLeg() const {
|
||||
return leg;
|
||||
};
|
||||
void setTime(time_t time) {
|
||||
timer = time;
|
||||
};
|
||||
|
||||
bool pushBackAllowed() const;
|
||||
bool allowTransmissions() const {
|
||||
return allowTransmission;
|
||||
};
|
||||
void allowPushBack() { allowPushback =true;};
|
||||
void denyPushBack () { allowPushback = false;};
|
||||
void suppressRepeatedTransmissions () {
|
||||
allowTransmission=false;
|
||||
};
|
||||
void allowRepeatedTransmissions () {
|
||||
allowTransmission=true;
|
||||
};
|
||||
void nextFrequency() {
|
||||
frequencyId++;
|
||||
};
|
||||
int getNextFrequency() const {
|
||||
return frequencyId;
|
||||
};
|
||||
intVec& getIntentions() {
|
||||
return intentions;
|
||||
};
|
||||
int getCurrentPosition() const {
|
||||
return currentPos;
|
||||
};
|
||||
void setPriority(int p) { priority = p; };
|
||||
int getPriority() const { return priority; };
|
||||
};
|
||||
|
||||
/***********************************************************************
|
||||
* Active runway, a utility class to keep track of which aircraft has
|
||||
* clearance for a given runway.
|
||||
**********************************************************************/
|
||||
class ActiveRunway
|
||||
{
|
||||
private:
|
||||
const std::string rwy;
|
||||
int currentlyCleared;
|
||||
double distanceToFinal;
|
||||
TimeVector estimatedArrivalTimes;
|
||||
|
||||
using AircraftRefVec = std::vector<SGSharedPtr<FGAIAircraft>>;
|
||||
AircraftRefVec departureQueue;
|
||||
|
||||
public:
|
||||
ActiveRunway(const std::string& r, int cc);
|
||||
|
||||
const std::string& getRunwayName() const
|
||||
{
|
||||
return rwy;
|
||||
};
|
||||
int getCleared() const
|
||||
{
|
||||
return currentlyCleared;
|
||||
};
|
||||
double getApproachDistance() const
|
||||
{
|
||||
return distanceToFinal;
|
||||
};
|
||||
//time_t getEstApproachTime() { return estimatedArrival; };
|
||||
|
||||
//void setEstApproachTime(time_t time) { estimatedArrival = time; };
|
||||
void addToDepartureQueue(FGAIAircraft *ac);
|
||||
|
||||
void setCleared(int number) {
|
||||
currentlyCleared = number;
|
||||
};
|
||||
time_t requestTimeSlot(time_t eta);
|
||||
//time_t requestTimeSlot(time_t eta, std::string wakeCategory);
|
||||
void slotHousekeeping(time_t newEta);
|
||||
int getdepartureQueueSize() {
|
||||
return departureQueue.size();
|
||||
};
|
||||
|
||||
SGSharedPtr<FGAIAircraft> getFirstAircraftInDepartureQueue() const;
|
||||
|
||||
SGSharedPtr<FGAIAircraft> getFirstOfStatus(int stat) const;
|
||||
|
||||
void updateDepartureQueue();
|
||||
|
||||
void printDepartureQueue();
|
||||
};
|
||||
|
||||
|
||||
#endif // _TRAFFIC_CONTROL_HXX
|
||||
516
src/Add-ons/Addon.cxx
Normal file
516
src/Add-ons/Addon.cxx
Normal file
@@ -0,0 +1,516 @@
|
||||
// -*- coding: utf-8 -*-
|
||||
//
|
||||
// Addon.cxx --- FlightGear class holding add-on metadata
|
||||
// Copyright (C) 2017, 2018 Florent Rougon
|
||||
//
|
||||
// This program is free software; you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation; either version 2 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License along
|
||||
// with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
|
||||
#include <map>
|
||||
#include <ostream>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include <simgear/misc/sg_dir.hxx>
|
||||
#include <simgear/misc/sg_path.hxx>
|
||||
#include <simgear/misc/strutils.hxx>
|
||||
#include <simgear/nasal/cppbind/Ghost.hxx>
|
||||
#include <simgear/nasal/cppbind/NasalHash.hxx>
|
||||
#include <simgear/nasal/naref.h>
|
||||
#include <simgear/props/props.hxx>
|
||||
#include <simgear/props/props_io.hxx>
|
||||
|
||||
#include <Main/fg_props.hxx>
|
||||
#include <Main/globals.hxx>
|
||||
#include <Scripting/NasalSys.hxx>
|
||||
|
||||
#include "addon_fwd.hxx"
|
||||
#include "Addon.hxx"
|
||||
#include "AddonMetadataParser.hxx"
|
||||
#include "AddonVersion.hxx"
|
||||
#include "exceptions.hxx"
|
||||
#include "pointer_traits.hxx"
|
||||
|
||||
namespace strutils = simgear::strutils;
|
||||
|
||||
using std::string;
|
||||
using std::vector;
|
||||
|
||||
namespace flightgear
|
||||
{
|
||||
|
||||
namespace addons
|
||||
{
|
||||
|
||||
// ***************************************************************************
|
||||
// * QualifiedUrl *
|
||||
// ***************************************************************************
|
||||
|
||||
QualifiedUrl::QualifiedUrl(UrlType type, string url, string detail)
|
||||
: _type(type),
|
||||
_url(std::move(url)),
|
||||
_detail(std::move(detail))
|
||||
{ }
|
||||
|
||||
UrlType QualifiedUrl::getType() const
|
||||
{ return _type; }
|
||||
|
||||
void QualifiedUrl::setType(UrlType type)
|
||||
{ _type = type; }
|
||||
|
||||
std::string QualifiedUrl::getUrl() const
|
||||
{ return _url; }
|
||||
|
||||
void QualifiedUrl::setUrl(const std::string& url)
|
||||
{ _url = url; }
|
||||
|
||||
std::string QualifiedUrl::getDetail() const
|
||||
{ return _detail; }
|
||||
|
||||
void QualifiedUrl::setDetail(const std::string& detail)
|
||||
{ _detail = detail; }
|
||||
|
||||
// ***************************************************************************
|
||||
// * Addon *
|
||||
// ***************************************************************************
|
||||
|
||||
Addon::Addon(std::string id, AddonVersion version, SGPath basePath,
|
||||
std::string minFGVersionRequired, std::string maxFGVersionRequired,
|
||||
SGPropertyNode* addonNode)
|
||||
: _id(std::move(id)),
|
||||
_version(
|
||||
shared_ptr_traits<AddonVersionRef>::makeStrongRef(std::move(version))),
|
||||
_basePath(std::move(basePath)),
|
||||
_storagePath(globals->get_fg_home() / ("Export/Addons/" + _id)),
|
||||
_minFGVersionRequired(std::move(minFGVersionRequired)),
|
||||
_maxFGVersionRequired(std::move(maxFGVersionRequired)),
|
||||
_addonNode(addonNode)
|
||||
{
|
||||
if (_minFGVersionRequired.empty()) {
|
||||
// This add-on metadata class appeared in FlightGear 2017.4.0
|
||||
_minFGVersionRequired = "2017.4.0";
|
||||
}
|
||||
|
||||
if (_maxFGVersionRequired.empty()) {
|
||||
_maxFGVersionRequired = "none"; // special value
|
||||
}
|
||||
}
|
||||
|
||||
std::string Addon::getId() const
|
||||
{ return _id; }
|
||||
|
||||
std::string Addon::getName() const
|
||||
{ return _name; }
|
||||
|
||||
void Addon::setName(const std::string& addonName)
|
||||
{ _name = addonName; }
|
||||
|
||||
AddonVersionRef Addon::getVersion() const
|
||||
{ return _version; }
|
||||
|
||||
void Addon::setVersion(const AddonVersion& addonVersion)
|
||||
{
|
||||
using ptr_traits = shared_ptr_traits<AddonVersionRef>;
|
||||
_version.reset(ptr_traits::makeStrongRef(addonVersion));
|
||||
}
|
||||
|
||||
std::vector<AuthorRef> Addon::getAuthors() const
|
||||
{ return _authors; }
|
||||
|
||||
void Addon::setAuthors(const std::vector<AuthorRef>& addonAuthors)
|
||||
{ _authors = addonAuthors; }
|
||||
|
||||
std::vector<MaintainerRef> Addon::getMaintainers() const
|
||||
{ return _maintainers; }
|
||||
|
||||
void Addon::setMaintainers(const std::vector<MaintainerRef>& addonMaintainers)
|
||||
{ _maintainers = addonMaintainers; }
|
||||
|
||||
std::string Addon::getShortDescription() const
|
||||
{ return _shortDescription; }
|
||||
|
||||
void Addon::setShortDescription(const std::string& addonShortDescription)
|
||||
{ _shortDescription = addonShortDescription; }
|
||||
|
||||
std::string Addon::getLongDescription() const
|
||||
{ return _longDescription; }
|
||||
|
||||
void Addon::setLongDescription(const std::string& addonLongDescription)
|
||||
{ _longDescription = addonLongDescription; }
|
||||
|
||||
std::string Addon::getLicenseDesignation() const
|
||||
{ return _licenseDesignation; }
|
||||
|
||||
void Addon::setLicenseDesignation(const std::string& addonLicenseDesignation)
|
||||
{ _licenseDesignation = addonLicenseDesignation; }
|
||||
|
||||
SGPath Addon::getLicenseFile() const
|
||||
{ return _licenseFile; }
|
||||
|
||||
void Addon::setLicenseFile(const SGPath& addonLicenseFile)
|
||||
{ _licenseFile = addonLicenseFile; }
|
||||
|
||||
std::string Addon::getLicenseUrl() const
|
||||
{ return _licenseUrl; }
|
||||
|
||||
void Addon::setLicenseUrl(const std::string& addonLicenseUrl)
|
||||
{ _licenseUrl = addonLicenseUrl; }
|
||||
|
||||
std::vector<std::string> Addon::getTags() const
|
||||
{ return _tags; }
|
||||
|
||||
void Addon::setTags(const std::vector<std::string>& addonTags)
|
||||
{ _tags = addonTags; }
|
||||
|
||||
SGPath Addon::getBasePath() const
|
||||
{ return _basePath; }
|
||||
|
||||
void Addon::setBasePath(const SGPath& addonBasePath)
|
||||
{ _basePath = addonBasePath; }
|
||||
|
||||
SGPath Addon::getStoragePath() const
|
||||
{ return _storagePath; }
|
||||
|
||||
SGPath Addon::createStorageDir() const
|
||||
{
|
||||
if (_storagePath.exists()) {
|
||||
if (!_storagePath.isDir()) {
|
||||
string msg =
|
||||
"Unable to create add-on storage directory because the entry already "
|
||||
"exists, but is not a directory: '" + _storagePath.utf8Str() + "'";
|
||||
// Log + throw, because if called from Nasal, only throwing would cause
|
||||
// the exception message to 1) be truncated and 2) only appear in the
|
||||
// log, not stopping the sim. Then users would have to figure out why
|
||||
// their add-on doesn't work...
|
||||
SG_LOG(SG_GENERAL, SG_POPUP, msg);
|
||||
throw errors::unable_to_create_addon_storage_dir(msg);
|
||||
}
|
||||
} else {
|
||||
const SGPath authorizedPath = SGPath(_storagePath).validate(/* write */
|
||||
true);
|
||||
if (authorizedPath.isNull()) {
|
||||
string msg =
|
||||
"Unable to create add-on storage directory because of the FlightGear "
|
||||
"security policy (refused by SGPath::validate()): '" +
|
||||
_storagePath.utf8Str() + "'";
|
||||
SG_LOG(SG_GENERAL, SG_POPUP, msg);
|
||||
throw errors::unable_to_create_addon_storage_dir(msg);
|
||||
} else {
|
||||
simgear::Dir(authorizedPath).create(0777);
|
||||
}
|
||||
}
|
||||
|
||||
// The sensitive operation (creating the directory) is behind us; return
|
||||
// _storagePath instead of authorizedPath for consistency with the
|
||||
// getStoragePath() method (_storagePath and authorizedPath could be
|
||||
// different in case the former contains symlink components). Further
|
||||
// sensitive operations beneath _storagePath must use SGPath::validate()
|
||||
// again every time, of course (otherwise, attackers could use symlinks in
|
||||
// _storagePath to bypass the security policy).
|
||||
return _storagePath;
|
||||
}
|
||||
|
||||
std::string Addon::resourcePath(const std::string& relativePath) const
|
||||
{
|
||||
if (strutils::starts_with(relativePath, "/")) {
|
||||
throw errors::invalid_resource_path(
|
||||
"addon-specific resource path '" + relativePath + "' shouldn't start "
|
||||
"with a '/'");
|
||||
}
|
||||
|
||||
return "[addon=" + getId() + "]" + relativePath;
|
||||
}
|
||||
|
||||
std::string Addon::getMinFGVersionRequired() const
|
||||
{ return _minFGVersionRequired; }
|
||||
|
||||
void Addon::setMinFGVersionRequired(const string& minFGVersionRequired)
|
||||
{ _minFGVersionRequired = minFGVersionRequired; }
|
||||
|
||||
std::string Addon::getMaxFGVersionRequired() const
|
||||
{ return _maxFGVersionRequired; }
|
||||
|
||||
void Addon::setMaxFGVersionRequired(const string& maxFGVersionRequired)
|
||||
{
|
||||
if (maxFGVersionRequired.empty()) {
|
||||
_maxFGVersionRequired = "none"; // special value
|
||||
} else {
|
||||
_maxFGVersionRequired = maxFGVersionRequired;
|
||||
}
|
||||
}
|
||||
|
||||
std::string Addon::getHomePage() const
|
||||
{ return _homePage; }
|
||||
|
||||
void Addon::setHomePage(const std::string& addonHomePage)
|
||||
{ _homePage = addonHomePage; }
|
||||
|
||||
std::string Addon::getDownloadUrl() const
|
||||
{ return _downloadUrl; }
|
||||
|
||||
void Addon::setDownloadUrl(const std::string& addonDownloadUrl)
|
||||
{ _downloadUrl = addonDownloadUrl; }
|
||||
|
||||
std::string Addon::getSupportUrl() const
|
||||
{ return _supportUrl; }
|
||||
|
||||
void Addon::setSupportUrl(const std::string& addonSupportUrl)
|
||||
{ _supportUrl = addonSupportUrl; }
|
||||
|
||||
std::string Addon::getCodeRepositoryUrl() const
|
||||
{ return _codeRepositoryUrl; }
|
||||
|
||||
void Addon::setCodeRepositoryUrl(const std::string& addonCodeRepositoryUrl)
|
||||
{ _codeRepositoryUrl = addonCodeRepositoryUrl; }
|
||||
|
||||
std::string Addon::getTriggerProperty() const
|
||||
{ return _triggerProperty; }
|
||||
|
||||
void Addon::setTriggerProperty(const std::string& addonTriggerProperty)
|
||||
{ _triggerProperty = addonTriggerProperty; }
|
||||
|
||||
SGPropertyNode_ptr Addon::getAddonNode() const
|
||||
{ return _addonNode; }
|
||||
|
||||
void Addon::setAddonNode(SGPropertyNode* addonNode)
|
||||
{ _addonNode = SGPropertyNode_ptr(addonNode); }
|
||||
|
||||
naRef Addon::getAddonPropsNode() const
|
||||
{
|
||||
FGNasalSys* nas = globals->get_subsystem<FGNasalSys>();
|
||||
return nas->wrappedPropsNode(_addonNode.get());
|
||||
}
|
||||
|
||||
SGPropertyNode_ptr Addon::getLoadedFlagNode() const
|
||||
{
|
||||
return { _addonNode->getChild("loaded", 0, 1) };
|
||||
}
|
||||
|
||||
int Addon::getLoadSequenceNumber() const
|
||||
{ return _loadSequenceNumber; }
|
||||
|
||||
void Addon::setLoadSequenceNumber(int num)
|
||||
{ _loadSequenceNumber = num; }
|
||||
|
||||
std::multimap<UrlType, QualifiedUrl> Addon::getUrls() const
|
||||
{
|
||||
std::multimap<UrlType, QualifiedUrl> res;
|
||||
|
||||
auto appendIfNonEmpty = [&res](UrlType type, string url, string detail = "") {
|
||||
if (!url.empty()) {
|
||||
res.emplace(type, QualifiedUrl(type, std::move(url), std::move(detail)));
|
||||
}
|
||||
};
|
||||
|
||||
for (const auto& author: _authors) {
|
||||
appendIfNonEmpty(UrlType::author, author->getUrl(), author->getName());
|
||||
}
|
||||
|
||||
for (const auto& maint: _maintainers) {
|
||||
appendIfNonEmpty(UrlType::maintainer, maint->getUrl(), maint->getName());
|
||||
}
|
||||
|
||||
appendIfNonEmpty(UrlType::homePage, getHomePage());
|
||||
appendIfNonEmpty(UrlType::download, getDownloadUrl());
|
||||
appendIfNonEmpty(UrlType::support, getSupportUrl());
|
||||
appendIfNonEmpty(UrlType::codeRepository, getCodeRepositoryUrl());
|
||||
appendIfNonEmpty(UrlType::license, getLicenseUrl());
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
std::vector<SGPropertyNode_ptr> Addon::getMenubarNodes() const
|
||||
{ return _menubarNodes; }
|
||||
|
||||
void Addon::setMenubarNodes(const std::vector<SGPropertyNode_ptr>& menubarNodes)
|
||||
{ _menubarNodes = menubarNodes; }
|
||||
|
||||
void Addon::addToFGMenubar() const
|
||||
{
|
||||
SGPropertyNode* menuRootNode = fgGetNode("/sim/menubar/default", true);
|
||||
|
||||
for (const auto& node: getMenubarNodes()) {
|
||||
SGPropertyNode* childNode = menuRootNode->addChild("menu");
|
||||
::copyProperties(node.ptr(), childNode);
|
||||
}
|
||||
}
|
||||
|
||||
std::string Addon::str() const
|
||||
{
|
||||
std::ostringstream oss;
|
||||
oss << "addon '" << _id << "' (version = " << *_version
|
||||
<< ", base path = '" << _basePath.utf8Str()
|
||||
<< "', minFGVersionRequired = '" << _minFGVersionRequired
|
||||
<< "', maxFGVersionRequired = '" << _maxFGVersionRequired << "')";
|
||||
|
||||
return oss.str();
|
||||
}
|
||||
|
||||
// Static method
|
||||
SGPath Addon::getMetadataFile(const SGPath& addonPath)
|
||||
{
|
||||
return MetadataParser::getMetadataFile(addonPath);
|
||||
}
|
||||
|
||||
SGPath Addon::getMetadataFile() const
|
||||
{
|
||||
return getMetadataFile(getBasePath());
|
||||
}
|
||||
|
||||
// Static method
|
||||
Addon Addon::fromAddonDir(const SGPath& addonPath)
|
||||
{
|
||||
Addon::Metadata metadata = MetadataParser::parseMetadataFile(addonPath);
|
||||
|
||||
// Object holding all the add-on metadata
|
||||
Addon addon{std::move(metadata.id), std::move(metadata.version), addonPath,
|
||||
std::move(metadata.minFGVersionRequired),
|
||||
std::move(metadata.maxFGVersionRequired)};
|
||||
addon.setName(std::move(metadata.name));
|
||||
addon.setAuthors(std::move(metadata.authors));
|
||||
addon.setMaintainers(std::move(metadata.maintainers));
|
||||
addon.setShortDescription(std::move(metadata.shortDescription));
|
||||
addon.setLongDescription(std::move(metadata.longDescription));
|
||||
addon.setLicenseDesignation(std::move(metadata.licenseDesignation));
|
||||
addon.setLicenseFile(std::move(metadata.licenseFile));
|
||||
addon.setLicenseUrl(std::move(metadata.licenseUrl));
|
||||
addon.setTags(std::move(metadata.tags));
|
||||
addon.setHomePage(std::move(metadata.homePage));
|
||||
addon.setDownloadUrl(std::move(metadata.downloadUrl));
|
||||
addon.setSupportUrl(std::move(metadata.supportUrl));
|
||||
addon.setCodeRepositoryUrl(std::move(metadata.codeRepositoryUrl));
|
||||
|
||||
SGPath menuFile = addonPath / "addon-menubar-items.xml";
|
||||
|
||||
if (menuFile.exists()) {
|
||||
addon.setMenubarNodes(readMenubarItems(menuFile));
|
||||
}
|
||||
|
||||
return addon;
|
||||
}
|
||||
|
||||
// Static method
|
||||
std::vector<SGPropertyNode_ptr>
|
||||
Addon::readMenubarItems(const SGPath& menuFile)
|
||||
{
|
||||
SGPropertyNode rootNode;
|
||||
|
||||
try {
|
||||
readProperties(menuFile, &rootNode);
|
||||
} catch (const sg_exception &e) {
|
||||
throw errors::error_loading_menubar_items_file(
|
||||
"unable to load add-on menu bar items from file '" +
|
||||
menuFile.utf8Str() + "': " + e.getFormattedMessage());
|
||||
}
|
||||
|
||||
// Check the 'meta' section
|
||||
SGPropertyNode *metaNode = rootNode.getChild("meta");
|
||||
if (metaNode == nullptr) {
|
||||
throw errors::error_loading_menubar_items_file(
|
||||
"no /meta node found in add-on menu bar items file '" +
|
||||
menuFile.utf8Str() + "'");
|
||||
}
|
||||
|
||||
// Check the file type
|
||||
SGPropertyNode *fileTypeNode = metaNode->getChild("file-type");
|
||||
if (fileTypeNode == nullptr) {
|
||||
throw errors::error_loading_menubar_items_file(
|
||||
"no /meta/file-type node found in add-on menu bar items file '" +
|
||||
menuFile.utf8Str() + "'");
|
||||
}
|
||||
|
||||
string fileType = fileTypeNode->getStringValue();
|
||||
if (fileType != "FlightGear add-on menu bar items") {
|
||||
throw errors::error_loading_menubar_items_file(
|
||||
"Invalid /meta/file-type value for add-on menu bar items file '" +
|
||||
menuFile.utf8Str() + "': '" + fileType + "' "
|
||||
"(expected 'FlightGear add-on menu bar items')");
|
||||
}
|
||||
|
||||
// Check the format version
|
||||
SGPropertyNode *fmtVersionNode = metaNode->getChild("format-version");
|
||||
if (fmtVersionNode == nullptr) {
|
||||
throw errors::error_loading_menubar_items_file(
|
||||
"no /meta/format-version node found in add-on menu bar items file '" +
|
||||
menuFile.utf8Str() + "'");
|
||||
}
|
||||
|
||||
int formatVersion = fmtVersionNode->getIntValue();
|
||||
if (formatVersion != 1) {
|
||||
throw errors::error_loading_menubar_items_file(
|
||||
"unknown format version in add-on menu bar items file '" +
|
||||
menuFile.utf8Str() + "': " + std::to_string(formatVersion));
|
||||
}
|
||||
|
||||
SG_LOG(SG_GENERAL, SG_DEBUG,
|
||||
"Loaded add-on menu bar items from '" << menuFile.utf8Str() + "'");
|
||||
|
||||
SGPropertyNode *menubarItemsNode = rootNode.getChild("menubar-items");
|
||||
std::vector<SGPropertyNode_ptr> res;
|
||||
|
||||
if (menubarItemsNode != nullptr) {
|
||||
res = menubarItemsNode->getChildren("menu");
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
void Addon::retranslate()
|
||||
{
|
||||
Addon::Metadata metadata = MetadataParser::parseMetadataFile(_basePath);
|
||||
setName(std::move(metadata.name));
|
||||
setShortDescription(std::move(metadata.shortDescription));
|
||||
setLongDescription(std::move(metadata.longDescription));
|
||||
}
|
||||
|
||||
// Static method
|
||||
void Addon::setupGhost(nasal::Hash& addonsModule)
|
||||
{
|
||||
nasal::Ghost<AddonRef>::init("addons.Addon")
|
||||
.member("id", &Addon::getId)
|
||||
.member("name", &Addon::getName)
|
||||
.member("version", &Addon::getVersion)
|
||||
.member("authors", &Addon::getAuthors)
|
||||
.member("maintainers", &Addon::getMaintainers)
|
||||
.member("shortDescription", &Addon::getShortDescription)
|
||||
.member("longDescription", &Addon::getLongDescription)
|
||||
.member("licenseDesignation", &Addon::getLicenseDesignation)
|
||||
.member("licenseFile", &Addon::getLicenseFile)
|
||||
.member("licenseUrl", &Addon::getLicenseUrl)
|
||||
.member("tags", &Addon::getTags)
|
||||
.member("basePath", &Addon::getBasePath)
|
||||
.member("storagePath", &Addon::getStoragePath)
|
||||
.method("createStorageDir", &Addon::createStorageDir)
|
||||
.method("resourcePath", &Addon::resourcePath)
|
||||
.member("minFGVersionRequired", &Addon::getMinFGVersionRequired)
|
||||
.member("maxFGVersionRequired", &Addon::getMaxFGVersionRequired)
|
||||
.member("homePage", &Addon::getHomePage)
|
||||
.member("downloadUrl", &Addon::getDownloadUrl)
|
||||
.member("supportUrl", &Addon::getSupportUrl)
|
||||
.member("codeRepositoryUrl", &Addon::getCodeRepositoryUrl)
|
||||
.member("triggerProperty", &Addon::getTriggerProperty)
|
||||
.member("node", &Addon::getAddonPropsNode)
|
||||
.member("loadSequenceNumber", &Addon::getLoadSequenceNumber);
|
||||
}
|
||||
|
||||
std::ostream& operator<<(std::ostream& os, const Addon& addon)
|
||||
{
|
||||
return os << addon.str();
|
||||
}
|
||||
|
||||
} // of namespace addons
|
||||
|
||||
} // of namespace flightgear
|
||||
267
src/Add-ons/Addon.hxx
Normal file
267
src/Add-ons/Addon.hxx
Normal file
@@ -0,0 +1,267 @@
|
||||
// -*- coding: utf-8 -*-
|
||||
//
|
||||
// Addon.hxx --- FlightGear class holding add-on metadata
|
||||
// Copyright (C) 2017, 2018 Florent Rougon
|
||||
//
|
||||
// This program is free software; you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation; either version 2 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License along
|
||||
// with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
|
||||
#ifndef FG_ADDON_HXX
|
||||
#define FG_ADDON_HXX
|
||||
|
||||
#include <map>
|
||||
#include <ostream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include <simgear/misc/sg_path.hxx>
|
||||
#include <simgear/nasal/cppbind/NasalHash.hxx>
|
||||
#include <simgear/nasal/naref.h>
|
||||
#include <simgear/props/props.hxx>
|
||||
#include <simgear/structure/SGReferenced.hxx>
|
||||
|
||||
#include "addon_fwd.hxx"
|
||||
#include "contacts.hxx"
|
||||
#include "AddonVersion.hxx"
|
||||
#include "pointer_traits.hxx"
|
||||
|
||||
namespace flightgear
|
||||
{
|
||||
|
||||
namespace addons
|
||||
{
|
||||
|
||||
enum class UrlType {
|
||||
author,
|
||||
maintainer,
|
||||
homePage,
|
||||
download,
|
||||
support,
|
||||
codeRepository,
|
||||
license
|
||||
};
|
||||
|
||||
class QualifiedUrl
|
||||
{
|
||||
public:
|
||||
QualifiedUrl(UrlType type, std::string url, std::string detail = "");
|
||||
|
||||
UrlType getType() const;
|
||||
void setType(UrlType type);
|
||||
|
||||
std::string getUrl() const;
|
||||
void setUrl(const std::string& url);
|
||||
|
||||
std::string getDetail() const;
|
||||
void setDetail(const std::string& detail);
|
||||
|
||||
private:
|
||||
UrlType _type;
|
||||
std::string _url;
|
||||
// Used to store the author or maintainer name when _type is UrlType::author
|
||||
// or UrlType::maintainer. Could be used to record details about a website
|
||||
// too (e.g., for a UrlType::support, something like “official forum”).
|
||||
std::string _detail;
|
||||
};
|
||||
|
||||
class Addon : public SGReferenced
|
||||
{
|
||||
public:
|
||||
// An empty value for 'minFGVersionRequired' is translated into "2017.4.0".
|
||||
// An empty value for 'maxFGVersionRequired' is translated into "none".
|
||||
Addon(std::string id, AddonVersion version = AddonVersion(),
|
||||
SGPath basePath = SGPath(), std::string minFGVersionRequired = "",
|
||||
std::string maxFGVersionRequired = "",
|
||||
SGPropertyNode* addonNode = nullptr);
|
||||
|
||||
// Parse the add-on metadata file inside 'addonPath' (as defined by
|
||||
// getMetadataFile()) and return the corresponding Addon instance.
|
||||
static Addon fromAddonDir(const SGPath& addonPath);
|
||||
|
||||
template<class T>
|
||||
static T fromAddonDir(const SGPath& addonPath)
|
||||
{
|
||||
using ptr_traits = shared_ptr_traits<T>;
|
||||
return ptr_traits::makeStrongRef(fromAddonDir(addonPath));
|
||||
}
|
||||
|
||||
std::string getId() const;
|
||||
|
||||
std::string getName() const;
|
||||
void setName(const std::string& addonName);
|
||||
|
||||
AddonVersionRef getVersion() const;
|
||||
void setVersion(const AddonVersion& addonVersion);
|
||||
|
||||
std::vector<AuthorRef> getAuthors() const;
|
||||
void setAuthors(const std::vector<AuthorRef>& addonAuthors);
|
||||
|
||||
std::vector<MaintainerRef> getMaintainers() const;
|
||||
void setMaintainers(const std::vector<MaintainerRef>& addonMaintainers);
|
||||
|
||||
std::string getShortDescription() const;
|
||||
void setShortDescription(const std::string& addonShortDescription);
|
||||
|
||||
std::string getLongDescription() const;
|
||||
void setLongDescription(const std::string& addonLongDescription);
|
||||
|
||||
std::string getLicenseDesignation() const;
|
||||
void setLicenseDesignation(const std::string& addonLicenseDesignation);
|
||||
|
||||
SGPath getLicenseFile() const;
|
||||
void setLicenseFile(const SGPath& addonLicenseFile);
|
||||
|
||||
std::string getLicenseUrl() const;
|
||||
void setLicenseUrl(const std::string& addonLicenseUrl);
|
||||
|
||||
std::vector<std::string> getTags() const;
|
||||
void setTags(const std::vector<std::string>& addonTags);
|
||||
|
||||
SGPath getBasePath() const;
|
||||
void setBasePath(const SGPath& addonBasePath);
|
||||
|
||||
// Return $FG_HOME/Export/Addons/ADDON_ID as an SGPath instance.
|
||||
SGPath getStoragePath() const;
|
||||
// Create directory $FG_HOME/Export/Addons/ADDON_ID, including any parent,
|
||||
// if it doesn't already exist. Throw an exception in case of problems.
|
||||
// Return an SGPath instance for the directory (same as getStoragePath()).
|
||||
SGPath createStorageDir() const;
|
||||
|
||||
// Return a resource path suitable for use with the simgear::ResourceManager.
|
||||
// 'relativePath' is relative to the add-on base path, and should not start
|
||||
// with a '/'.
|
||||
std::string resourcePath(const std::string& relativePath) const;
|
||||
|
||||
// Should be valid for use with simgear::strutils::compare_versions()
|
||||
std::string getMinFGVersionRequired() const;
|
||||
void setMinFGVersionRequired(const std::string& minFGVersionRequired);
|
||||
|
||||
// Should be valid for use with simgear::strutils::compare_versions(),
|
||||
// except for the special value "none".
|
||||
std::string getMaxFGVersionRequired() const;
|
||||
void setMaxFGVersionRequired(const std::string& maxFGVersionRequired);
|
||||
|
||||
std::string getHomePage() const;
|
||||
void setHomePage(const std::string& addonHomePage);
|
||||
|
||||
std::string getDownloadUrl() const;
|
||||
void setDownloadUrl(const std::string& addonDownloadUrl);
|
||||
|
||||
std::string getSupportUrl() const;
|
||||
void setSupportUrl(const std::string& addonSupportUrl);
|
||||
|
||||
std::string getCodeRepositoryUrl() const;
|
||||
void setCodeRepositoryUrl(const std::string& addonCodeRepositoryUrl);
|
||||
|
||||
std::string getTriggerProperty() const;
|
||||
void setTriggerProperty(const std::string& addonTriggerProperty);
|
||||
|
||||
// Node pertaining to the add-on in the Global Property Tree
|
||||
SGPropertyNode_ptr getAddonNode() const;
|
||||
void setAddonNode(SGPropertyNode* addonNode);
|
||||
// For Nasal: result as a props.Node object
|
||||
naRef getAddonPropsNode() const;
|
||||
|
||||
// Property node indicating whether the add-on is fully loaded
|
||||
SGPropertyNode_ptr getLoadedFlagNode() const;
|
||||
|
||||
// 0 for the first loaded add-on, 1 for the second, etc.
|
||||
// -1 means “not set” (as done by the default constructor)
|
||||
int getLoadSequenceNumber() const;
|
||||
void setLoadSequenceNumber(int num);
|
||||
|
||||
// Get all non-empty URLs pertaining to this add-on
|
||||
std::multimap<UrlType, QualifiedUrl> getUrls() const;
|
||||
|
||||
// Getter and setter for the menu bar item nodes of the add-on
|
||||
std::vector<SGPropertyNode_ptr> getMenubarNodes() const;
|
||||
void setMenubarNodes(const std::vector<SGPropertyNode_ptr>& menubarNodes);
|
||||
// Add the menus defined in addon-menubar-items.xml to /sim/menubar/default
|
||||
void addToFGMenubar() const;
|
||||
|
||||
// Simple string representation
|
||||
std::string str() const;
|
||||
|
||||
static void setupGhost(nasal::Hash& addonsModule);
|
||||
|
||||
/**
|
||||
* @brief update string values (description, etc) based on the active locale
|
||||
*/
|
||||
void retranslate();
|
||||
|
||||
private:
|
||||
class Metadata;
|
||||
class MetadataParser;
|
||||
|
||||
// “Compute” a path to the metadata file from the add-on base path
|
||||
static SGPath getMetadataFile(const SGPath& addonPath);
|
||||
SGPath getMetadataFile() const;
|
||||
|
||||
// Read all menus from addon-menubar-items.xml (under the add-on base path)
|
||||
static std::vector<SGPropertyNode_ptr>
|
||||
readMenubarItems(const SGPath& menuFile);
|
||||
|
||||
// The add-on identifier, in reverse DNS style. The AddonManager refuses to
|
||||
// register two add-ons with the same id in a given FlightGear session.
|
||||
const std::string _id;
|
||||
// Pretty name for the add-on (not constrained to reverse DNS style)
|
||||
std::string _name;
|
||||
// Use a smart pointer to expose the AddonVersion instance to Nasal without
|
||||
// needing to copy the data every time.
|
||||
AddonVersionRef _version;
|
||||
|
||||
std::vector<AuthorRef> _authors;
|
||||
std::vector<MaintainerRef> _maintainers;
|
||||
|
||||
// Strings describing what the add-on does
|
||||
std::string _shortDescription;
|
||||
std::string _longDescription;
|
||||
|
||||
std::string _licenseDesignation;
|
||||
SGPath _licenseFile;
|
||||
std::string _licenseUrl;
|
||||
|
||||
std::vector<std::string> _tags;
|
||||
SGPath _basePath;
|
||||
// $FG_HOME/Export/Addons/ADDON_ID
|
||||
const SGPath _storagePath;
|
||||
|
||||
// To be used with simgear::strutils::compare_versions()
|
||||
std::string _minFGVersionRequired;
|
||||
// Ditto, but there is a special value: "none"
|
||||
std::string _maxFGVersionRequired;
|
||||
|
||||
std::string _homePage;
|
||||
std::string _downloadUrl;
|
||||
std::string _supportUrl;
|
||||
std::string _codeRepositoryUrl;
|
||||
|
||||
// Main node for the add-on in the Property Tree
|
||||
SGPropertyNode_ptr _addonNode;
|
||||
// The add-on will be loaded when the property referenced by
|
||||
// _triggerProperty is written to.
|
||||
std::string _triggerProperty;
|
||||
// Semantics explained above
|
||||
int _loadSequenceNumber = -1;
|
||||
|
||||
std::vector<SGPropertyNode_ptr> _menubarNodes;
|
||||
};
|
||||
|
||||
std::ostream& operator<<(std::ostream& os, const Addon& addon);
|
||||
|
||||
} // of namespace addons
|
||||
|
||||
} // of namespace flightgear
|
||||
|
||||
#endif // of FG_ADDON_HXX
|
||||
296
src/Add-ons/AddonManager.cxx
Normal file
296
src/Add-ons/AddonManager.cxx
Normal file
@@ -0,0 +1,296 @@
|
||||
// -*- coding: utf-8 -*-
|
||||
//
|
||||
// AddonManager.cxx --- Manager class for FlightGear add-ons
|
||||
// Copyright (C) 2017 Florent Rougon
|
||||
//
|
||||
// This program is free software; you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation; either version 2 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License along
|
||||
// with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include <cstdlib>
|
||||
#include <cassert>
|
||||
|
||||
#include <simgear/debug/logstream.hxx>
|
||||
#include <simgear/misc/sg_path.hxx>
|
||||
#include <simgear/misc/strutils.hxx>
|
||||
#include <simgear/props/props.hxx>
|
||||
#include <simgear/props/props_io.hxx>
|
||||
#include <simgear/structure/exception.hxx>
|
||||
|
||||
#include <Main/fg_props.hxx>
|
||||
#include <Main/globals.hxx>
|
||||
|
||||
#include "addon_fwd.hxx"
|
||||
#include "Addon.hxx"
|
||||
#include "AddonManager.hxx"
|
||||
#include "AddonVersion.hxx"
|
||||
#include "exceptions.hxx"
|
||||
#include "pointer_traits.hxx"
|
||||
|
||||
namespace strutils = simgear::strutils;
|
||||
|
||||
using std::string;
|
||||
using std::vector;
|
||||
using std::shared_ptr;
|
||||
using std::unique_ptr;
|
||||
|
||||
namespace flightgear
|
||||
{
|
||||
|
||||
namespace addons
|
||||
{
|
||||
|
||||
static unique_ptr<AddonManager> staticInstance;
|
||||
|
||||
// ***************************************************************************
|
||||
// * AddonManager *
|
||||
// ***************************************************************************
|
||||
|
||||
// Static method
|
||||
const unique_ptr<AddonManager>&
|
||||
AddonManager::createInstance()
|
||||
{
|
||||
SG_LOG(SG_GENERAL, SG_DEBUG, "Initializing the AddonManager");
|
||||
|
||||
staticInstance.reset(new AddonManager());
|
||||
return staticInstance;
|
||||
}
|
||||
|
||||
// Static method
|
||||
const unique_ptr<AddonManager>&
|
||||
AddonManager::instance()
|
||||
{
|
||||
return staticInstance;
|
||||
}
|
||||
|
||||
// Static method
|
||||
void
|
||||
AddonManager::reset()
|
||||
{
|
||||
SG_LOG(SG_GENERAL, SG_DEBUG, "Resetting the AddonManager");
|
||||
staticInstance.reset();
|
||||
}
|
||||
|
||||
// Static method
|
||||
void
|
||||
AddonManager::loadConfigFileIfExists(const SGPath& configFile)
|
||||
{
|
||||
if (!configFile.exists()) {
|
||||
return;
|
||||
}
|
||||
|
||||
SGPropertyNode_ptr configProps(new SGPropertyNode);
|
||||
try {
|
||||
readProperties(configFile, configProps);
|
||||
} catch (const sg_exception &e) {
|
||||
throw errors::error_loading_config_file(
|
||||
"unable to load add-on config file '" + configFile.utf8Str() + "': " +
|
||||
e.getFormattedMessage());
|
||||
}
|
||||
|
||||
// bug https://sourceforge.net/p/flightgear/codetickets/2059/
|
||||
// since we're loading this after autosave.xml is loaded, the defaults
|
||||
// always take precedence. To fix this, only copy a value from the
|
||||
// addon-config if it's not makred as ARCHIVE. (We assume ARCHIVE props
|
||||
// came from autosave.xml)
|
||||
copyPropertiesIf(configProps, globals->get_props(), [](const SGPropertyNode* src) {
|
||||
if (src->nChildren() > 0)
|
||||
return true;
|
||||
|
||||
// find the correspnding destination node
|
||||
auto dstNode = globals->get_props()->getNode(src->getPath());
|
||||
if (!dstNode)
|
||||
return true; // easy, just copy it
|
||||
|
||||
// copy if it's NOT marked archive. In other words, we can replace
|
||||
// values from defaults, but not autosave
|
||||
return dstNode->getAttribute(SGPropertyNode::USERARCHIVE) == false;
|
||||
});
|
||||
|
||||
SG_LOG(SG_GENERAL, SG_INFO,
|
||||
"Loaded add-on config file: '" << configFile.utf8Str() + "'");
|
||||
}
|
||||
|
||||
string
|
||||
AddonManager::registerAddonMetadata(const SGPath& addonPath)
|
||||
{
|
||||
using ptr_traits = shared_ptr_traits<AddonRef>;
|
||||
AddonRef addon = ptr_traits::makeStrongRef(Addon::fromAddonDir(addonPath));
|
||||
string addonId = addon->getId();
|
||||
|
||||
SGPropertyNode* addonPropNode = fgGetNode("addons", true)
|
||||
->getChild("by-id", 0, 1)
|
||||
->getChild(addonId, 0, 1);
|
||||
addon->setAddonNode(addonPropNode);
|
||||
addon->setLoadSequenceNumber(_loadSequenceNumber++);
|
||||
|
||||
// Check that the FlightGear version satisfies the add-on requirements
|
||||
std::string minFGversion = addon->getMinFGVersionRequired();
|
||||
if (strutils::compare_versions(FLIGHTGEAR_VERSION, minFGversion) < 0) {
|
||||
throw errors::fg_version_too_old(
|
||||
"add-on '" + addonId + "' requires FlightGear " + minFGversion +
|
||||
" or later, however this is FlightGear " + FLIGHTGEAR_VERSION);
|
||||
}
|
||||
|
||||
std::string maxFGversion = addon->getMaxFGVersionRequired();
|
||||
if (maxFGversion != "none" &&
|
||||
strutils::compare_versions(FLIGHTGEAR_VERSION, maxFGversion) > 0) {
|
||||
throw errors::fg_version_too_recent(
|
||||
"add-on '" + addonId + "' requires FlightGear " + maxFGversion +
|
||||
" or earlier, however this is FlightGear " + FLIGHTGEAR_VERSION);
|
||||
}
|
||||
|
||||
// Store the add-on metadata in _idToAddonMap
|
||||
auto emplaceRetval = _idToAddonMap.emplace(addonId, std::move(addon));
|
||||
|
||||
// Prevent registration of two add-ons with the same id
|
||||
if (!emplaceRetval.second) {
|
||||
auto existingElt = _idToAddonMap.find(addonId);
|
||||
assert(existingElt != _idToAddonMap.end());
|
||||
throw errors::duplicate_registration_attempt(
|
||||
"attempt to register add-on '" + addonId + "' with base path '"
|
||||
+ addonPath.utf8Str() + "', however it is already registered with base "
|
||||
"path '" + existingElt->second->getBasePath().utf8Str() + "'");
|
||||
}
|
||||
|
||||
return addonId;
|
||||
}
|
||||
|
||||
string
|
||||
AddonManager::registerAddon(const SGPath& addonPath)
|
||||
{
|
||||
// Use realpath() as in FGGlobals::append_aircraft_path(), otherwise
|
||||
// SGPath::validate() will deny access to resources under the add-on path
|
||||
// if one of its components is a symlink.
|
||||
const SGPath addonRealPath = addonPath.realpath();
|
||||
const string addonId = registerAddonMetadata(addonRealPath);
|
||||
loadConfigFileIfExists(addonRealPath / "addon-config.xml");
|
||||
globals->append_aircraft_path(addonRealPath);
|
||||
|
||||
AddonRef addon{getAddon(addonId)};
|
||||
addon->getLoadedFlagNode()->setBoolValue(false);
|
||||
SGPropertyNode_ptr addonNode = addon->getAddonNode();
|
||||
|
||||
// Set a few properties for the add-on under this node
|
||||
addonNode->getNode("id", true)->setStringValue(addonId);
|
||||
addonNode->getNode("name", true)->setStringValue(addon->getName());
|
||||
addonNode->getNode("version", true)
|
||||
->setStringValue(addonVersion(addonId)->str());
|
||||
addonNode->getNode("path", true)->setStringValue(addonRealPath.utf8Str());
|
||||
addonNode->getNode("load-seq-num", true)
|
||||
->setIntValue(addon->getLoadSequenceNumber());
|
||||
|
||||
// “Legacy node”. Should we remove these two lines?
|
||||
SGPropertyNode* seqNumNode = fgGetNode("addons", true)->addChild("addon");
|
||||
seqNumNode->getNode("path", true)->setStringValue(addonRealPath.utf8Str());
|
||||
|
||||
string msg = "Registered add-on '" + addon->getName() + "' (" + addonId +
|
||||
") version " + addonVersion(addonId)->str() + "; "
|
||||
"base path is '" + addonRealPath.utf8Str() + "'";
|
||||
|
||||
auto dataPath = addonRealPath / "FGData";
|
||||
if (dataPath.exists()) {
|
||||
SG_LOG(SG_GENERAL, SG_INFO, "Registering data path for add-on: " << addon->getName());
|
||||
globals->append_data_path(dataPath, true /* after FG_ROOT */);
|
||||
}
|
||||
|
||||
// This preserves the registration order
|
||||
_registeredAddons.push_back(addon);
|
||||
SG_LOG(SG_GENERAL, SG_INFO, msg);
|
||||
|
||||
return addonId;
|
||||
}
|
||||
|
||||
bool
|
||||
AddonManager::isAddonRegistered(const string& addonId) const
|
||||
{
|
||||
return (_idToAddonMap.find(addonId) != _idToAddonMap.end());
|
||||
}
|
||||
|
||||
bool
|
||||
AddonManager::isAddonLoaded(const string& addonId) const
|
||||
{
|
||||
return (isAddonRegistered(addonId) &&
|
||||
getAddon(addonId)->getLoadedFlagNode()->getBoolValue());
|
||||
}
|
||||
|
||||
vector<AddonRef>
|
||||
AddonManager::registeredAddons() const
|
||||
{
|
||||
return _registeredAddons;
|
||||
}
|
||||
|
||||
vector<AddonRef>
|
||||
AddonManager::loadedAddons() const
|
||||
{
|
||||
vector<AddonRef> v;
|
||||
v.reserve(_idToAddonMap.size()); // will be the right size most of the times
|
||||
|
||||
for (const auto& elem: _idToAddonMap) {
|
||||
if (isAddonLoaded(elem.first)) {
|
||||
v.push_back(elem.second);
|
||||
}
|
||||
}
|
||||
|
||||
return v;
|
||||
}
|
||||
|
||||
AddonRef
|
||||
AddonManager::getAddon(const string& addonId) const
|
||||
{
|
||||
const auto it = _idToAddonMap.find(addonId);
|
||||
|
||||
if (it == _idToAddonMap.end()) {
|
||||
throw sg_exception("tried to get add-on '" + addonId + "', however no "
|
||||
"such add-on has been registered.");
|
||||
}
|
||||
|
||||
return it->second;
|
||||
}
|
||||
|
||||
AddonVersionRef
|
||||
AddonManager::addonVersion(const string& addonId) const
|
||||
{
|
||||
return getAddon(addonId)->getVersion();
|
||||
}
|
||||
|
||||
SGPath
|
||||
AddonManager::addonBasePath(const string& addonId) const
|
||||
{
|
||||
return getAddon(addonId)->getBasePath();
|
||||
}
|
||||
|
||||
SGPropertyNode_ptr AddonManager::addonNode(const string& addonId) const
|
||||
{
|
||||
return getAddon(addonId)->getAddonNode();
|
||||
}
|
||||
|
||||
void
|
||||
AddonManager::addAddonMenusToFGMenubar() const
|
||||
{
|
||||
for (const auto& addon: _registeredAddons) {
|
||||
addon->addToFGMenubar();
|
||||
}
|
||||
}
|
||||
|
||||
} // of namespace addons
|
||||
|
||||
} // of namespace flightgear
|
||||
114
src/Add-ons/AddonManager.hxx
Normal file
114
src/Add-ons/AddonManager.hxx
Normal file
@@ -0,0 +1,114 @@
|
||||
// -*- coding: utf-8 -*-
|
||||
//
|
||||
// AddonManager.hxx --- Manager class for FlightGear add-ons
|
||||
// Copyright (C) 2017 Florent Rougon
|
||||
//
|
||||
// This program is free software; you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation; either version 2 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License along
|
||||
// with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
|
||||
#ifndef FG_ADDONMANAGER_HXX
|
||||
#define FG_ADDONMANAGER_HXX
|
||||
|
||||
#include <string>
|
||||
#include <map>
|
||||
#include <memory> // std::unique_ptr, std::shared_ptr
|
||||
#include <vector>
|
||||
|
||||
#include <simgear/misc/sg_path.hxx>
|
||||
#include <simgear/props/props.hxx>
|
||||
|
||||
#include "addon_fwd.hxx"
|
||||
#include "Addon.hxx"
|
||||
#include "AddonVersion.hxx"
|
||||
|
||||
namespace flightgear
|
||||
{
|
||||
|
||||
namespace addons
|
||||
{
|
||||
|
||||
class AddonManager
|
||||
{
|
||||
public:
|
||||
AddonManager(const AddonManager&) = delete;
|
||||
AddonManager& operator=(const AddonManager&) = delete;
|
||||
AddonManager(AddonManager&&) = delete;
|
||||
AddonManager& operator=(AddonManager&&) = delete;
|
||||
// The instance is created by createInstance() -> private constructor
|
||||
// but it should be deleted by its owning std::unique_ptr -> public destructor
|
||||
~AddonManager() = default;
|
||||
|
||||
// Static creator
|
||||
static const std::unique_ptr<AddonManager>& createInstance();
|
||||
// Singleton accessor
|
||||
static const std::unique_ptr<AddonManager>& instance();
|
||||
// Reset the static smart pointer, i.e., shut down the AddonManager.
|
||||
static void reset();
|
||||
|
||||
// Register an add-on and return its id.
|
||||
// 'addonPath': directory containing the add-on to register
|
||||
//
|
||||
// This comprises the following steps, where $path = addonPath.realpath():
|
||||
// - load add-on metadata from $path/addon-metadata.xml and register it
|
||||
// inside _idToAddonMap (this step is done via registerAddonMetadata());
|
||||
// - load $path/addon-config.xml into the Property Tree;
|
||||
// - append $path to the list of aircraft paths;
|
||||
// - make part of the add-on metadata available in the Property Tree under
|
||||
// the /addons node (/addons/by-id/<addonId>/{id,version,path,...});
|
||||
// - append a ref to the Addon instance to _registeredAddons.
|
||||
std::string registerAddon(const SGPath& addonPath);
|
||||
// Return the list of registered add-ons in registration order (which, BTW,
|
||||
// is the same as load order).
|
||||
std::vector<AddonRef> registeredAddons() const;
|
||||
bool isAddonRegistered(const std::string& addonId) const;
|
||||
|
||||
// A loaded add-on is one whose addon-main.nas file has been loaded. The
|
||||
// returned vector is sorted by add-on id (cheap sorting based on UTF-8 code
|
||||
// units, only guaranteed correct for ASCII chars).
|
||||
std::vector<AddonRef> loadedAddons() const;
|
||||
bool isAddonLoaded(const std::string& addonId) const;
|
||||
|
||||
AddonRef getAddon(const std::string& addonId) const;
|
||||
AddonVersionRef addonVersion(const std::string& addonId) const;
|
||||
SGPath addonBasePath(const std::string& addonId) const;
|
||||
|
||||
// Base node pertaining to the add-on in the Global Property Tree
|
||||
SGPropertyNode_ptr addonNode(const std::string& addonId) const;
|
||||
|
||||
// Add the 'menu' nodes defined by each registered add-on to
|
||||
// /sim/menubar/default
|
||||
void addAddonMenusToFGMenubar() const;
|
||||
|
||||
private:
|
||||
// Constructor called from createInstance() only
|
||||
explicit AddonManager() = default;
|
||||
|
||||
static void loadConfigFileIfExists(const SGPath& configFile);
|
||||
// Register add-on metadata inside _idToAddonMap and return the add-on id
|
||||
std::string registerAddonMetadata(const SGPath& addonPath);
|
||||
|
||||
// Map each add-on id to the corresponding Addon instance.
|
||||
std::map<std::string, AddonRef> _idToAddonMap;
|
||||
// The order in _registeredAddons is the registration order.
|
||||
std::vector<AddonRef> _registeredAddons;
|
||||
// 0 for the first loaded add-on, 1 for the second, etc.
|
||||
// Also note that add-ons are loaded in their registration order.
|
||||
int _loadSequenceNumber = 0;
|
||||
};
|
||||
|
||||
} // of namespace addons
|
||||
|
||||
} // of namespace flightgear
|
||||
|
||||
#endif // of FG_ADDONMANAGER_HXX
|
||||
416
src/Add-ons/AddonMetadataParser.cxx
Normal file
416
src/Add-ons/AddonMetadataParser.cxx
Normal file
@@ -0,0 +1,416 @@
|
||||
// -*- coding: utf-8 -*-
|
||||
//
|
||||
// AddonMetadataParser.cxx --- Parser for FlightGear add-on metadata files
|
||||
// Copyright (C) 2018 Florent Rougon
|
||||
//
|
||||
// This program is free software; you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation; either version 2 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License along
|
||||
// with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
|
||||
#include <regex>
|
||||
#include <string>
|
||||
#include <tuple>
|
||||
#include <vector>
|
||||
|
||||
#include <simgear/debug/logstream.hxx>
|
||||
#include <simgear/misc/sg_path.hxx>
|
||||
#include <simgear/misc/strutils.hxx>
|
||||
#include <simgear/props/props.hxx>
|
||||
#include <simgear/props/props_io.hxx>
|
||||
|
||||
#include "addon_fwd.hxx"
|
||||
#include "AddonMetadataParser.hxx"
|
||||
#include "AddonVersion.hxx"
|
||||
#include "contacts.hxx"
|
||||
#include "exceptions.hxx"
|
||||
#include "pointer_traits.hxx"
|
||||
|
||||
#include <Main/globals.hxx>
|
||||
#include <Main/locale.hxx>
|
||||
|
||||
namespace strutils = simgear::strutils;
|
||||
|
||||
using std::string;
|
||||
using std::vector;
|
||||
|
||||
namespace flightgear
|
||||
{
|
||||
|
||||
namespace addons
|
||||
{
|
||||
|
||||
// Static method
|
||||
SGPath
|
||||
Addon::MetadataParser::getMetadataFile(const SGPath& addonPath)
|
||||
{
|
||||
return addonPath / "addon-metadata.xml";
|
||||
}
|
||||
|
||||
static string getMaybeLocalized(const string& tag, SGPropertyNode* base, SGPropertyNode* lang)
|
||||
{
|
||||
if (lang) {
|
||||
auto n = lang->getChild(tag);
|
||||
if (n) {
|
||||
return strutils::strip(n->getStringValue());
|
||||
}
|
||||
}
|
||||
|
||||
auto n = base->getChild(tag);
|
||||
if (n) {
|
||||
return strutils::strip(n->getStringValue());
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
static SGPropertyNode* getAndCheckLocalizedNode(SGPropertyNode* addonNode,
|
||||
const SGPath& metadataFile)
|
||||
{
|
||||
const auto localizedNode = addonNode->getChild("localized");
|
||||
if (!localizedNode) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
for (int i = 0; i < localizedNode->nChildren(); ++i) {
|
||||
const auto node = localizedNode->getChild(i);
|
||||
const string& name = node->getNameString();
|
||||
|
||||
if (name.find('_') != string::npos) {
|
||||
throw errors::error_loading_metadata_file(
|
||||
"underscores not allowed in names of children of <localized> "
|
||||
"(in add-on metadata file '" + metadataFile.utf8Str() + "'); "
|
||||
"hyphens should be used, as in 'fr-FR' or 'en-GB'");
|
||||
}
|
||||
}
|
||||
|
||||
return localizedNode;
|
||||
}
|
||||
|
||||
// Static method
|
||||
Addon::Metadata
|
||||
Addon::MetadataParser::parseMetadataFile(const SGPath& addonPath)
|
||||
{
|
||||
SGPath metadataFile = getMetadataFile(addonPath);
|
||||
SGPropertyNode addonRoot;
|
||||
Addon::Metadata metadata;
|
||||
|
||||
if (!metadataFile.exists()) {
|
||||
throw errors::no_metadata_file_found(
|
||||
"unable to find add-on metadata file '" + metadataFile.utf8Str() + "'");
|
||||
}
|
||||
|
||||
try {
|
||||
readProperties(metadataFile, &addonRoot);
|
||||
} catch (const sg_exception &e) {
|
||||
throw errors::error_loading_metadata_file(
|
||||
"unable to load add-on metadata file '" + metadataFile.utf8Str() + "': " +
|
||||
e.getFormattedMessage());
|
||||
}
|
||||
|
||||
// Check the 'meta' section
|
||||
SGPropertyNode *metaNode = addonRoot.getChild("meta");
|
||||
if (metaNode == nullptr) {
|
||||
throw errors::error_loading_metadata_file(
|
||||
"no /meta node found in add-on metadata file '" +
|
||||
metadataFile.utf8Str() + "'");
|
||||
}
|
||||
|
||||
// Check the file type
|
||||
SGPropertyNode *fileTypeNode = metaNode->getChild("file-type");
|
||||
if (fileTypeNode == nullptr) {
|
||||
throw errors::error_loading_metadata_file(
|
||||
"no /meta/file-type node found in add-on metadata file '" +
|
||||
metadataFile.utf8Str() + "'");
|
||||
}
|
||||
|
||||
string fileType = fileTypeNode->getStringValue();
|
||||
if (fileType != "FlightGear add-on metadata") {
|
||||
throw errors::error_loading_metadata_file(
|
||||
"Invalid /meta/file-type value for add-on metadata file '" +
|
||||
metadataFile.utf8Str() + "': '" + fileType + "' "
|
||||
"(expected 'FlightGear add-on metadata')");
|
||||
}
|
||||
|
||||
// Check the format version
|
||||
SGPropertyNode *fmtVersionNode = metaNode->getChild("format-version");
|
||||
if (fmtVersionNode == nullptr) {
|
||||
throw errors::error_loading_metadata_file(
|
||||
"no /meta/format-version node found in add-on metadata file '" +
|
||||
metadataFile.utf8Str() + "'");
|
||||
}
|
||||
|
||||
int formatVersion = fmtVersionNode->getIntValue();
|
||||
if (formatVersion != 1) {
|
||||
throw errors::error_loading_metadata_file(
|
||||
"unknown format version in add-on metadata file '" +
|
||||
metadataFile.utf8Str() + "': " + std::to_string(formatVersion));
|
||||
}
|
||||
|
||||
// Now the data we are really interested in
|
||||
SGPropertyNode *addonNode = addonRoot.getChild("addon");
|
||||
if (addonNode == nullptr) {
|
||||
throw errors::error_loading_metadata_file(
|
||||
"no /addon node found in add-on metadata file '" +
|
||||
metadataFile.utf8Str() + "'");
|
||||
}
|
||||
|
||||
const auto localizedNode = getAndCheckLocalizedNode(addonNode, metadataFile);
|
||||
SGPropertyNode* langStringsNode = globals->get_locale()->selectLanguageNode(localizedNode);
|
||||
|
||||
SGPropertyNode *idNode = addonNode->getChild("identifier");
|
||||
if (idNode == nullptr) {
|
||||
throw errors::error_loading_metadata_file(
|
||||
"no /addon/identifier node found in add-on metadata file '" +
|
||||
metadataFile.utf8Str() + "'");
|
||||
}
|
||||
metadata.id = strutils::strip(idNode->getStringValue());
|
||||
|
||||
// Require a non-empty identifier for the add-on
|
||||
if (metadata.id.empty()) {
|
||||
throw errors::error_loading_metadata_file(
|
||||
"empty or whitespace-only value for the /addon/identifier node in "
|
||||
"add-on metadata file '" + metadataFile.utf8Str() + "'");
|
||||
} else if (metadata.id.find('.') == string::npos) {
|
||||
SG_LOG(SG_GENERAL, SG_WARN,
|
||||
"Add-on identifier '" << metadata.id << "' does not use reverse DNS "
|
||||
"style (e.g., org.flightgear.addons.MyAddon) in add-on metadata "
|
||||
"file '" << metadataFile.utf8Str() + "'");
|
||||
}
|
||||
|
||||
SGPropertyNode *nameNode = addonNode->getChild("name");
|
||||
if (nameNode == nullptr) {
|
||||
throw errors::error_loading_metadata_file(
|
||||
"no /addon/name node found in add-on metadata file '" +
|
||||
metadataFile.utf8Str() + "'");
|
||||
}
|
||||
|
||||
metadata.name = getMaybeLocalized("name", addonNode, langStringsNode);
|
||||
|
||||
// Require a non-empty name for the add-on
|
||||
if (metadata.name.empty()) {
|
||||
throw errors::error_loading_metadata_file(
|
||||
"empty or whitespace-only value for the /addon/name node in add-on "
|
||||
"metadata file '" + metadataFile.utf8Str() + "'");
|
||||
}
|
||||
|
||||
SGPropertyNode *versionNode = addonNode->getChild("version");
|
||||
if (versionNode == nullptr) {
|
||||
throw errors::error_loading_metadata_file(
|
||||
"no /addon/version node found in add-on metadata file '" +
|
||||
metadataFile.utf8Str() + "'");
|
||||
}
|
||||
metadata.version = AddonVersion{
|
||||
strutils::strip(versionNode->getStringValue())};
|
||||
|
||||
metadata.authors = parseContactsNode<Author>(metadataFile,
|
||||
addonNode->getChild("authors"));
|
||||
metadata.maintainers = parseContactsNode<Maintainer>(
|
||||
metadataFile, addonNode->getChild("maintainers"));
|
||||
|
||||
metadata.shortDescription = getMaybeLocalized("short-description", addonNode, langStringsNode);
|
||||
metadata.longDescription = getMaybeLocalized("long-description", addonNode, langStringsNode);
|
||||
|
||||
std::tie(metadata.licenseDesignation, metadata.licenseFile,
|
||||
metadata.licenseUrl) = parseLicenseNode(addonPath, addonNode);
|
||||
|
||||
SGPropertyNode *tagsNode = addonNode->getChild("tags");
|
||||
if (tagsNode != nullptr) {
|
||||
auto tagNodes = tagsNode->getChildren("tag");
|
||||
for (const auto& node: tagNodes) {
|
||||
metadata.tags.push_back(strutils::strip(node->getStringValue()));
|
||||
}
|
||||
}
|
||||
|
||||
SGPropertyNode *minNode = addonNode->getChild("min-FG-version");
|
||||
if (minNode != nullptr) {
|
||||
metadata.minFGVersionRequired = strutils::strip(minNode->getStringValue());
|
||||
} else {
|
||||
metadata.minFGVersionRequired = string();
|
||||
}
|
||||
|
||||
SGPropertyNode *maxNode = addonNode->getChild("max-FG-version");
|
||||
if (maxNode != nullptr) {
|
||||
metadata.maxFGVersionRequired = strutils::strip(maxNode->getStringValue());
|
||||
} else {
|
||||
metadata.maxFGVersionRequired = string();
|
||||
}
|
||||
|
||||
metadata.homePage = metadata.downloadUrl = metadata.supportUrl =
|
||||
metadata.codeRepositoryUrl = string(); // defaults
|
||||
SGPropertyNode *urlsNode = addonNode->getChild("urls");
|
||||
if (urlsNode != nullptr) {
|
||||
SGPropertyNode *homePageNode = urlsNode->getChild("home-page");
|
||||
if (homePageNode != nullptr) {
|
||||
metadata.homePage = strutils::strip(homePageNode->getStringValue());
|
||||
}
|
||||
|
||||
SGPropertyNode *downloadUrlNode = urlsNode->getChild("download");
|
||||
if (downloadUrlNode != nullptr) {
|
||||
metadata.downloadUrl = strutils::strip(downloadUrlNode->getStringValue());
|
||||
}
|
||||
|
||||
SGPropertyNode *supportUrlNode = urlsNode->getChild("support");
|
||||
if (supportUrlNode != nullptr) {
|
||||
metadata.supportUrl = strutils::strip(supportUrlNode->getStringValue());
|
||||
}
|
||||
|
||||
SGPropertyNode *codeRepoUrlNode = urlsNode->getChild("code-repository");
|
||||
if (codeRepoUrlNode != nullptr) {
|
||||
metadata.codeRepositoryUrl =
|
||||
strutils::strip(codeRepoUrlNode->getStringValue());
|
||||
}
|
||||
}
|
||||
|
||||
SG_LOG(SG_GENERAL, SG_DEBUG,
|
||||
"Parsed add-on metadata file: '" << metadataFile.utf8Str() + "'");
|
||||
|
||||
return metadata;
|
||||
}
|
||||
|
||||
// Utility function for Addon::MetadataParser::parseContactsNode<>()
|
||||
//
|
||||
// Read a node such as "name", "email" or "url", child of a contact node (e.g.,
|
||||
// of an "author" or "maintainer" node).
|
||||
static string
|
||||
parseContactsNode_readNode(const SGPath& metadataFile,
|
||||
SGPropertyNode* contactNode,
|
||||
string subnodeName, bool allowEmpty)
|
||||
{
|
||||
SGPropertyNode *node = contactNode->getChild(subnodeName);
|
||||
string contents;
|
||||
|
||||
if (node != nullptr) {
|
||||
contents = simgear::strutils::strip(node->getStringValue());
|
||||
}
|
||||
|
||||
if (!allowEmpty && contents.empty()) {
|
||||
throw errors::error_loading_metadata_file(
|
||||
"in add-on metadata file '" + metadataFile.utf8Str() + "': "
|
||||
"when the node " + contactNode->getPath(true) + " exists, it must have "
|
||||
"a non-empty '" + subnodeName + "' child node");
|
||||
}
|
||||
|
||||
return contents;
|
||||
};
|
||||
|
||||
// Static method template (private and only used in this file)
|
||||
template <class T>
|
||||
vector<typename contact_traits<T>::strong_ref>
|
||||
Addon::MetadataParser::parseContactsNode(const SGPath& metadataFile,
|
||||
SGPropertyNode* mainNode)
|
||||
{
|
||||
using contactTraits = contact_traits<T>;
|
||||
vector<typename contactTraits::strong_ref> res;
|
||||
|
||||
if (mainNode != nullptr) {
|
||||
auto contactNodes = mainNode->getChildren(contactTraits::xmlNodeName());
|
||||
res.reserve(contactNodes.size());
|
||||
|
||||
for (const auto& contactNode: contactNodes) {
|
||||
string name, email, url;
|
||||
|
||||
name = parseContactsNode_readNode(metadataFile, contactNode.get(),
|
||||
"name", false /* allowEmpty */);
|
||||
email = parseContactsNode_readNode(metadataFile, contactNode.get(),
|
||||
"email", true);
|
||||
url = parseContactsNode_readNode(metadataFile, contactNode.get(),
|
||||
"url", true);
|
||||
|
||||
using ptr_traits = shared_ptr_traits<typename contactTraits::strong_ref>;
|
||||
res.push_back(ptr_traits::makeStrongRef(name, email, url));
|
||||
}
|
||||
}
|
||||
|
||||
return res;
|
||||
};
|
||||
|
||||
// Static method
|
||||
std::tuple<string, SGPath, string>
|
||||
Addon::MetadataParser::parseLicenseNode(const SGPath& addonPath,
|
||||
SGPropertyNode* addonNode)
|
||||
{
|
||||
SGPath metadataFile = getMetadataFile(addonPath);
|
||||
string licenseDesignation;
|
||||
SGPath licenseFile;
|
||||
string licenseUrl;
|
||||
|
||||
SGPropertyNode *licenseNode = addonNode->getChild("license");
|
||||
if (licenseNode == nullptr) {
|
||||
return std::tuple<string, SGPath, string>();
|
||||
}
|
||||
|
||||
SGPropertyNode *licenseDesigNode = licenseNode->getChild("designation");
|
||||
if (licenseDesigNode != nullptr) {
|
||||
licenseDesignation = strutils::strip(licenseDesigNode->getStringValue());
|
||||
}
|
||||
|
||||
SGPropertyNode *licenseFileNode = licenseNode->getChild("file");
|
||||
if (licenseFileNode != nullptr) {
|
||||
// This effectively disallows filenames starting or ending with whitespace
|
||||
string licenseFile_s = strutils::strip(licenseFileNode->getStringValue());
|
||||
|
||||
if (!licenseFile_s.empty()) {
|
||||
if (licenseFile_s.find('\\') != string::npos) {
|
||||
throw errors::error_loading_metadata_file(
|
||||
"in add-on metadata file '" + metadataFile.utf8Str() + "': the "
|
||||
"value of /addon/license/file contains '\\'; please use '/' "
|
||||
"separators only");
|
||||
}
|
||||
|
||||
if (licenseFile_s.find_first_of("/\\") == 0) {
|
||||
throw errors::error_loading_metadata_file(
|
||||
"in add-on metadata file '" + metadataFile.utf8Str() + "': the "
|
||||
"value of /addon/license/file must be relative to the add-on folder, "
|
||||
"however it starts with '" + licenseFile_s[0] + "'");
|
||||
}
|
||||
|
||||
#ifdef HAVE_WORKING_STD_REGEX
|
||||
std::regex winDriveRegexp("([a-zA-Z]:).*");
|
||||
std::smatch results;
|
||||
|
||||
if (std::regex_match(licenseFile_s, results, winDriveRegexp)) {
|
||||
string winDrive = results.str(1);
|
||||
#else // all this 'else' clause should be removed once we actually require C++11
|
||||
if (licenseFile_s.size() >= 2 &&
|
||||
(('a' <= licenseFile_s[0] && licenseFile_s[0] <= 'z') ||
|
||||
('A' <= licenseFile_s[0] && licenseFile_s[0] <= 'Z')) &&
|
||||
licenseFile_s[1] == ':') {
|
||||
string winDrive = licenseFile_s.substr(0, 2);
|
||||
#endif
|
||||
throw errors::error_loading_metadata_file(
|
||||
"in add-on metadata file '" + metadataFile.utf8Str() + "': the "
|
||||
"value of /addon/license/file must be relative to the add-on folder, "
|
||||
"however it starts with a Windows drive letter (" + winDrive + ")");
|
||||
}
|
||||
|
||||
licenseFile = addonPath / licenseFile_s;
|
||||
if ( !(licenseFile.exists() && licenseFile.isFile()) ) {
|
||||
throw errors::error_loading_metadata_file(
|
||||
"in add-on metadata file '" + metadataFile.utf8Str() + "': the "
|
||||
"value of /addon/license/file (pointing to '" + licenseFile.utf8Str() +
|
||||
"') doesn't correspond to an existing file");
|
||||
}
|
||||
} // of if (!licenseFile_s.empty())
|
||||
} // of if (licenseFileNode != nullptr)
|
||||
|
||||
SGPropertyNode *licenseUrlNode = licenseNode->getChild("url");
|
||||
if (licenseUrlNode != nullptr) {
|
||||
licenseUrl = strutils::strip(licenseUrlNode->getStringValue());
|
||||
}
|
||||
|
||||
return std::make_tuple(licenseDesignation, licenseFile, licenseUrl);
|
||||
}
|
||||
|
||||
} // of namespace addons
|
||||
|
||||
} // of namespace flightgear
|
||||
97
src/Add-ons/AddonMetadataParser.hxx
Normal file
97
src/Add-ons/AddonMetadataParser.hxx
Normal file
@@ -0,0 +1,97 @@
|
||||
// -*- coding: utf-8 -*-
|
||||
//
|
||||
// AddonMetadataParser.hxx --- Parser for FlightGear add-on metadata files
|
||||
// Copyright (C) 2018 Florent Rougon
|
||||
//
|
||||
// This program is free software; you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation; either version 2 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License along
|
||||
// with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
|
||||
#ifndef FG_ADDON_METADATA_PARSER_HXX
|
||||
#define FG_ADDON_METADATA_PARSER_HXX
|
||||
|
||||
#include <string>
|
||||
#include <tuple>
|
||||
#include <vector>
|
||||
|
||||
#include <simgear/misc/sg_path.hxx>
|
||||
|
||||
#include "addon_fwd.hxx"
|
||||
#include "Addon.hxx"
|
||||
#include "AddonVersion.hxx"
|
||||
#include "contacts.hxx"
|
||||
|
||||
class SGPropertyNode;
|
||||
|
||||
namespace flightgear
|
||||
{
|
||||
|
||||
namespace addons
|
||||
{
|
||||
|
||||
class Addon::Metadata
|
||||
{
|
||||
public:
|
||||
// Comments about these fields can be found in Addon.hxx
|
||||
std::string id;
|
||||
std::string name;
|
||||
AddonVersion version;
|
||||
|
||||
std::vector<AuthorRef> authors;
|
||||
std::vector<MaintainerRef> maintainers;
|
||||
|
||||
std::string shortDescription;
|
||||
std::string longDescription;
|
||||
|
||||
std::string licenseDesignation;
|
||||
SGPath licenseFile;
|
||||
std::string licenseUrl;
|
||||
|
||||
std::vector<std::string> tags;
|
||||
|
||||
std::string minFGVersionRequired;
|
||||
std::string maxFGVersionRequired;
|
||||
|
||||
std::string homePage;
|
||||
std::string downloadUrl;
|
||||
std::string supportUrl;
|
||||
std::string codeRepositoryUrl;
|
||||
};
|
||||
|
||||
class Addon::MetadataParser
|
||||
{
|
||||
public:
|
||||
// “Compute” a path to the metadata file from the add-on base path
|
||||
static SGPath getMetadataFile(const SGPath& addonPath);
|
||||
|
||||
// Parse the add-on metadata file inside 'addonPath' (as defined by
|
||||
// getMetadataFile()) and return the corresponding Addon::Metadata instance.
|
||||
static Addon::Metadata parseMetadataFile(const SGPath& addonPath);
|
||||
|
||||
private:
|
||||
static std::tuple<string, SGPath, string>
|
||||
parseLicenseNode(const SGPath& addonPath, SGPropertyNode* addonNode);
|
||||
|
||||
// Parse an addon-metadata.xml node such as <authors> or <maintainers>.
|
||||
// Return the corresponding vector<AuthorRef> or vector<MaintainerRef>. If
|
||||
// the 'mainNode' argument is nullptr, return an empty vector.
|
||||
template <class T>
|
||||
static std::vector<typename contact_traits<T>::strong_ref>
|
||||
parseContactsNode(const SGPath& metadataFile, SGPropertyNode* mainNode);
|
||||
};
|
||||
|
||||
} // of namespace addons
|
||||
|
||||
} // of namespace flightgear
|
||||
|
||||
#endif // of FG_ADDON_METADATA_PARSER_HXX
|
||||
78
src/Add-ons/AddonResourceProvider.cxx
Normal file
78
src/Add-ons/AddonResourceProvider.cxx
Normal file
@@ -0,0 +1,78 @@
|
||||
// -*- coding: utf-8 -*-
|
||||
//
|
||||
// AddonResourceProvider.cxx --- ResourceProvider subclass for add-on files
|
||||
// Copyright (C) 2018 Florent Rougon
|
||||
//
|
||||
// This program is free software; you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation; either version 2 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License along
|
||||
// with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
|
||||
#include <string>
|
||||
|
||||
#include <simgear/misc/ResourceManager.hxx>
|
||||
#include <simgear/misc/sg_path.hxx>
|
||||
#include <simgear/misc/strutils.hxx>
|
||||
|
||||
#include "AddonManager.hxx"
|
||||
#include "AddonResourceProvider.hxx"
|
||||
|
||||
namespace strutils = simgear::strutils;
|
||||
|
||||
using std::string;
|
||||
|
||||
namespace flightgear
|
||||
{
|
||||
|
||||
namespace addons
|
||||
{
|
||||
|
||||
ResourceProvider::ResourceProvider()
|
||||
: simgear::ResourceProvider(simgear::ResourceManager::PRIORITY_DEFAULT)
|
||||
{ }
|
||||
|
||||
SGPath
|
||||
ResourceProvider::resolve(const string& resource, SGPath& context) const
|
||||
{
|
||||
if (!strutils::starts_with(resource, "[addon=")) {
|
||||
return SGPath();
|
||||
}
|
||||
|
||||
string rest = resource.substr(7); // what follows '[addon='
|
||||
auto endOfAddonId = rest.find(']');
|
||||
|
||||
if (endOfAddonId == string::npos) {
|
||||
return SGPath();
|
||||
}
|
||||
|
||||
string addonId = rest.substr(0, endOfAddonId);
|
||||
// Extract what follows '[addon=ADDON_ID]'
|
||||
string relPath = rest.substr(endOfAddonId + 1);
|
||||
|
||||
if (relPath.empty()) {
|
||||
return SGPath();
|
||||
}
|
||||
|
||||
const auto& addonMgr = AddonManager::instance();
|
||||
SGPath addonDir = addonMgr->addonBasePath(addonId);
|
||||
SGPath candidate = addonDir / relPath;
|
||||
|
||||
if (!candidate.isFile()) {
|
||||
return SGPath();
|
||||
}
|
||||
|
||||
return SGPath(candidate).validate(/* write */ false);
|
||||
}
|
||||
|
||||
} // of namespace addons
|
||||
|
||||
} // of namespace flightgear
|
||||
47
src/Add-ons/AddonResourceProvider.hxx
Normal file
47
src/Add-ons/AddonResourceProvider.hxx
Normal file
@@ -0,0 +1,47 @@
|
||||
// -*- coding: utf-8 -*-
|
||||
//
|
||||
// AddonResourceProvider.hxx --- ResourceProvider subclass for add-on files
|
||||
// Copyright (C) 2018 Florent Rougon
|
||||
//
|
||||
// This program is free software; you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation; either version 2 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License along
|
||||
// with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
|
||||
#ifndef FG_ADDON_RESOURCE_PROVIDER_HXX
|
||||
#define FG_ADDON_RESOURCE_PROVIDER_HXX
|
||||
|
||||
#include <string>
|
||||
|
||||
#include <simgear/misc/ResourceManager.hxx>
|
||||
#include <simgear/misc/sg_path.hxx>
|
||||
|
||||
namespace flightgear
|
||||
{
|
||||
|
||||
namespace addons
|
||||
{
|
||||
|
||||
class ResourceProvider : public simgear::ResourceProvider
|
||||
{
|
||||
public:
|
||||
ResourceProvider();
|
||||
|
||||
virtual SGPath resolve(const std::string& resource, SGPath& context) const
|
||||
override;
|
||||
};
|
||||
|
||||
} // of namespace addons
|
||||
|
||||
} // of namespace flightgear
|
||||
|
||||
#endif // of FG_ADDON_RESOURCE_PROVIDER_HXX
|
||||
569
src/Add-ons/AddonVersion.cxx
Normal file
569
src/Add-ons/AddonVersion.cxx
Normal file
@@ -0,0 +1,569 @@
|
||||
// -*- coding: utf-8 -*-
|
||||
//
|
||||
// AddonVersion.cxx --- Version class for FlightGear add-ons
|
||||
// Copyright (C) 2017 Florent Rougon
|
||||
//
|
||||
// This program is free software; you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation; either version 2 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License along
|
||||
// with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
|
||||
#include <numeric> // std::accumulate()
|
||||
#include <ostream>
|
||||
#include <regex>
|
||||
#include <string>
|
||||
#include <tuple>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include <cassert>
|
||||
|
||||
#include <simgear/misc/strutils.hxx>
|
||||
#include <simgear/nasal/cppbind/Ghost.hxx>
|
||||
#include <simgear/nasal/cppbind/NasalCallContext.hxx>
|
||||
#include <simgear/nasal/cppbind/NasalHash.hxx>
|
||||
#include <simgear/sg_inlines.h>
|
||||
#include <simgear/structure/exception.hxx>
|
||||
|
||||
#include "addon_fwd.hxx"
|
||||
#include "AddonVersion.hxx"
|
||||
|
||||
using std::string;
|
||||
using std::vector;
|
||||
using simgear::enumValue;
|
||||
|
||||
namespace strutils = simgear::strutils;
|
||||
|
||||
namespace flightgear
|
||||
{
|
||||
|
||||
namespace addons
|
||||
{
|
||||
|
||||
// ***************************************************************************
|
||||
// * AddonVersionSuffix *
|
||||
// ***************************************************************************
|
||||
|
||||
AddonVersionSuffix::AddonVersionSuffix(
|
||||
AddonVersionSuffixPrereleaseType preReleaseType, int preReleaseNum,
|
||||
bool developmental, int devNum)
|
||||
: _preReleaseType(preReleaseType),
|
||||
_preReleaseNum(preReleaseNum),
|
||||
_developmental(developmental),
|
||||
_devNum(devNum)
|
||||
{ }
|
||||
|
||||
// Construct an AddonVersionSuffix instance from a tuple (preReleaseType,
|
||||
// preReleaseNum, developmental, devNum). This would be nicer with
|
||||
// std::apply(), but it requires C++17.
|
||||
AddonVersionSuffix::AddonVersionSuffix(
|
||||
const std::tuple<AddonVersionSuffixPrereleaseType, int, bool, int>& t)
|
||||
: AddonVersionSuffix(std::get<0>(t), std::get<1>(t), std::get<2>(t),
|
||||
std::get<3>(t))
|
||||
{ }
|
||||
|
||||
AddonVersionSuffix::AddonVersionSuffix(const std::string& suffix)
|
||||
: AddonVersionSuffix(suffixStringToTuple(suffix))
|
||||
{ }
|
||||
|
||||
AddonVersionSuffix::AddonVersionSuffix(const char* suffix)
|
||||
: AddonVersionSuffix(string(suffix))
|
||||
{ }
|
||||
|
||||
// Static method
|
||||
string
|
||||
AddonVersionSuffix::releaseTypeStr(AddonVersionSuffixPrereleaseType releaseType)
|
||||
{
|
||||
switch (releaseType) {
|
||||
case AddonVersionSuffixPrereleaseType::alpha:
|
||||
return string("a");
|
||||
case AddonVersionSuffixPrereleaseType::beta:
|
||||
return string("b");
|
||||
case AddonVersionSuffixPrereleaseType::candidate:
|
||||
return string("rc");
|
||||
case AddonVersionSuffixPrereleaseType::none:
|
||||
return string();
|
||||
default:
|
||||
throw sg_error("unexpected value for member of "
|
||||
"flightgear::addons::AddonVersionSuffixPrereleaseType: " +
|
||||
std::to_string(enumValue(releaseType)));
|
||||
}
|
||||
}
|
||||
|
||||
string
|
||||
AddonVersionSuffix::str() const
|
||||
{
|
||||
string res = releaseTypeStr(_preReleaseType);
|
||||
|
||||
if (!res.empty()) {
|
||||
res += std::to_string(_preReleaseNum);
|
||||
}
|
||||
|
||||
if (_developmental) {
|
||||
res += ".dev" + std::to_string(_devNum);
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
// Static method
|
||||
std::tuple<AddonVersionSuffixPrereleaseType, int, bool, int>
|
||||
AddonVersionSuffix::suffixStringToTuple(const std::string& suffix)
|
||||
{
|
||||
#ifdef HAVE_WORKING_STD_REGEX
|
||||
// Use a simplified variant of the syntax described in PEP 440
|
||||
// <https://www.python.org/dev/peps/pep-0440/>: for the version suffix, only
|
||||
// allow a pre-release segment and a development release segment, but no
|
||||
// post-release segment.
|
||||
std::regex versionSuffixRegexp(R"((?:(a|b|rc)(\d+))?(?:\.dev(\d+))?)");
|
||||
std::smatch results;
|
||||
|
||||
if (std::regex_match(suffix, results, versionSuffixRegexp)) {
|
||||
const string preReleaseType_s = results.str(1);
|
||||
const string preReleaseNum_s = results.str(2);
|
||||
const string devNum_s = results.str(3);
|
||||
|
||||
AddonVersionSuffixPrereleaseType preReleaseType = AddonVersionSuffixPrereleaseType::none;
|
||||
int preReleaseNum = 0;
|
||||
int devNum = 0;
|
||||
|
||||
if (preReleaseType_s.empty()) {
|
||||
preReleaseType = AddonVersionSuffixPrereleaseType::none;
|
||||
} else {
|
||||
if (preReleaseType_s == "a") {
|
||||
preReleaseType = AddonVersionSuffixPrereleaseType::alpha;
|
||||
} else if (preReleaseType_s == "b") {
|
||||
preReleaseType = AddonVersionSuffixPrereleaseType::beta;
|
||||
} else if (preReleaseType_s == "rc") {
|
||||
preReleaseType = AddonVersionSuffixPrereleaseType::candidate;
|
||||
} else {
|
||||
assert(false); // the regexp should prevent this
|
||||
}
|
||||
|
||||
assert(!preReleaseNum_s.empty());
|
||||
preReleaseNum = strutils::readNonNegativeInt<int>(preReleaseNum_s);
|
||||
|
||||
if (preReleaseNum < 1) {
|
||||
string msg = "invalid add-on version suffix: '" + suffix + "' "
|
||||
"(prerelease number must be greater than or equal to 1, but got " +
|
||||
preReleaseNum_s + ")";
|
||||
throw sg_format_exception(msg, suffix);
|
||||
}
|
||||
}
|
||||
|
||||
if (!devNum_s.empty()) {
|
||||
devNum = strutils::readNonNegativeInt<int>(devNum_s);
|
||||
|
||||
if (devNum < 1) {
|
||||
string msg = "invalid add-on version suffix: '" + suffix + "' "
|
||||
"(development release number must be greater than or equal to 1, "
|
||||
"but got " + devNum_s + ")";
|
||||
throw sg_format_exception(msg, suffix);
|
||||
}
|
||||
}
|
||||
|
||||
return std::make_tuple(preReleaseType, preReleaseNum, !devNum_s.empty(),
|
||||
devNum);
|
||||
#else // all this 'else' clause should be removed once we actually require C++11
|
||||
bool isMatch;
|
||||
AddonVersionSuffixPrereleaseType preReleaseType;
|
||||
int preReleaseNum;
|
||||
bool developmental;
|
||||
int devNum;
|
||||
|
||||
std::tie(isMatch, preReleaseType, preReleaseNum, developmental, devNum) =
|
||||
parseVersionSuffixString_noRegexp(suffix);
|
||||
|
||||
if (isMatch) {
|
||||
return std::make_tuple(preReleaseType, preReleaseNum, developmental,
|
||||
devNum);
|
||||
#endif // HAVE_WORKING_STD_REGEX
|
||||
} else { // the regexp didn't match
|
||||
string msg = "invalid add-on version suffix: '" + suffix + "' "
|
||||
"(expected form is [{a|b|rc}N1][.devN2] where N1 and N2 are positive "
|
||||
"integers)";
|
||||
throw sg_format_exception(msg, suffix);
|
||||
}
|
||||
}
|
||||
|
||||
// Static method, only needed for compilers that are not C++11-compliant
|
||||
// (gcc 4.8 pretends to support <regex> as required by C++11 but doesn't, see
|
||||
// <https://stackoverflow.com/a/12665408/4756009>).
|
||||
std::tuple<bool, AddonVersionSuffixPrereleaseType, int, bool, int>
|
||||
AddonVersionSuffix::parseVersionSuffixString_noRegexp(const string& suffix)
|
||||
{
|
||||
AddonVersionSuffixPrereleaseType preReleaseType;
|
||||
string rest;
|
||||
int preReleaseNum = 0; // alpha, beta or release candidate number, or
|
||||
// 0 when absent
|
||||
bool developmental = false; // whether 'suffix' has a .devN2 part
|
||||
int devNum = 0; // the N2 in question, or 0 when absent
|
||||
|
||||
std::tie(preReleaseType, rest) = popPrereleaseTypeFromBeginning(suffix);
|
||||
|
||||
if (preReleaseType != AddonVersionSuffixPrereleaseType::none) {
|
||||
std::size_t startPrerelNum = rest.find_first_of("0123456789");
|
||||
if (startPrerelNum != 0) { // no prerelease num -> no match
|
||||
return std::make_tuple(false, preReleaseType, preReleaseNum, false,
|
||||
devNum);
|
||||
}
|
||||
|
||||
std::size_t endPrerelNum = rest.find_first_not_of("0123456789", 1);
|
||||
// Works whether endPrerelNum is string::npos or not
|
||||
string preReleaseNum_s = rest.substr(0, endPrerelNum);
|
||||
preReleaseNum = strutils::readNonNegativeInt<int>(preReleaseNum_s);
|
||||
|
||||
if (preReleaseNum < 1) {
|
||||
string msg = "invalid add-on version suffix: '" + suffix + "' "
|
||||
"(prerelease number must be greater than or equal to 1, but got " +
|
||||
preReleaseNum_s + ")";
|
||||
throw sg_format_exception(msg, suffix);
|
||||
}
|
||||
|
||||
rest = (endPrerelNum == string::npos) ? "" : rest.substr(endPrerelNum);
|
||||
}
|
||||
|
||||
if (strutils::starts_with(rest, ".dev")) {
|
||||
rest = rest.substr(4);
|
||||
std::size_t startDevNum = rest.find_first_of("0123456789");
|
||||
if (startDevNum != 0) { // no dev num -> no match
|
||||
return std::make_tuple(false, preReleaseType, preReleaseNum, false,
|
||||
devNum);
|
||||
}
|
||||
|
||||
std::size_t endDevNum = rest.find_first_not_of("0123456789", 1);
|
||||
if (endDevNum != string::npos) {
|
||||
// There is trailing garbage after the development release number
|
||||
// -> no match
|
||||
return std::make_tuple(false, preReleaseType, preReleaseNum, false,
|
||||
devNum);
|
||||
}
|
||||
|
||||
devNum = strutils::readNonNegativeInt<int>(rest);
|
||||
if (devNum < 1) {
|
||||
string msg = "invalid add-on version suffix: '" + suffix + "' "
|
||||
"(development release number must be greater than or equal to 1, "
|
||||
"but got " + rest + ")";
|
||||
throw sg_format_exception(msg, suffix);
|
||||
}
|
||||
|
||||
developmental = true;
|
||||
}
|
||||
|
||||
return std::make_tuple(true, preReleaseType, preReleaseNum, developmental,
|
||||
devNum);
|
||||
}
|
||||
|
||||
// Static method
|
||||
std::tuple<AddonVersionSuffixPrereleaseType, string>
|
||||
AddonVersionSuffix::popPrereleaseTypeFromBeginning(const string& s)
|
||||
{
|
||||
if (s.empty()) {
|
||||
return std::make_tuple(AddonVersionSuffixPrereleaseType::none, s);
|
||||
} else if (s[0] == 'a') {
|
||||
return std::make_tuple(AddonVersionSuffixPrereleaseType::alpha,
|
||||
s.substr(1));
|
||||
} else if (s[0] == 'b') {
|
||||
return std::make_tuple(AddonVersionSuffixPrereleaseType::beta, s.substr(1));
|
||||
} else if (strutils::starts_with(s, "rc")) {
|
||||
return std::make_tuple(AddonVersionSuffixPrereleaseType::candidate,
|
||||
s.substr(2));
|
||||
}
|
||||
|
||||
return std::make_tuple(AddonVersionSuffixPrereleaseType::none, s);
|
||||
}
|
||||
|
||||
// Beware, this is not suitable for sorting! cf. genSortKey() below.
|
||||
std::tuple<AddonVersionSuffixPrereleaseType, int, bool, int>
|
||||
AddonVersionSuffix::makeTuple() const
|
||||
{
|
||||
return std::make_tuple(_preReleaseType, _preReleaseNum, _developmental,
|
||||
_devNum);
|
||||
}
|
||||
|
||||
std::tuple<int,
|
||||
std::underlying_type<AddonVersionSuffixPrereleaseType>::type,
|
||||
int, int, int>
|
||||
AddonVersionSuffix::genSortKey() const
|
||||
{
|
||||
using AddonRelType = AddonVersionSuffixPrereleaseType;
|
||||
|
||||
// The first element means that a plain .devN is lower than everything else,
|
||||
// except .devM with M <= N (namely: all dev and non-dev alpha, beta,
|
||||
// candidates, as well as the empty suffix).
|
||||
return std::make_tuple(
|
||||
((_developmental && _preReleaseType == AddonRelType::none) ? 0 : 1),
|
||||
enumValue(_preReleaseType),
|
||||
_preReleaseNum,
|
||||
(_developmental ? 0 : 1), // e.g., 1.0.3a2.devN < 1.0.3a2 for all N
|
||||
_devNum);
|
||||
}
|
||||
|
||||
bool operator==(const AddonVersionSuffix& lhs, const AddonVersionSuffix& rhs)
|
||||
{ return lhs.genSortKey() == rhs.genSortKey(); }
|
||||
|
||||
bool operator!=(const AddonVersionSuffix& lhs, const AddonVersionSuffix& rhs)
|
||||
{ return !operator==(lhs, rhs); }
|
||||
|
||||
bool operator< (const AddonVersionSuffix& lhs, const AddonVersionSuffix& rhs)
|
||||
{ return lhs.genSortKey() < rhs.genSortKey(); }
|
||||
|
||||
bool operator> (const AddonVersionSuffix& lhs, const AddonVersionSuffix& rhs)
|
||||
{ return operator<(rhs, lhs); }
|
||||
|
||||
bool operator<=(const AddonVersionSuffix& lhs, const AddonVersionSuffix& rhs)
|
||||
{ return !operator>(lhs, rhs); }
|
||||
|
||||
bool operator>=(const AddonVersionSuffix& lhs, const AddonVersionSuffix& rhs)
|
||||
{ return !operator<(lhs, rhs); }
|
||||
|
||||
std::ostream& operator<<(std::ostream& os,
|
||||
const AddonVersionSuffix& addonVersionSuffix)
|
||||
{ return os << addonVersionSuffix.str(); }
|
||||
|
||||
// ***************************************************************************
|
||||
// * AddonVersion *
|
||||
// ***************************************************************************
|
||||
|
||||
AddonVersion::AddonVersion(int major, int minor, int patchLevel,
|
||||
AddonVersionSuffix suffix)
|
||||
: _major(major),
|
||||
_minor(minor),
|
||||
_patchLevel(patchLevel),
|
||||
_suffix(std::move(suffix))
|
||||
{ }
|
||||
|
||||
// Construct an AddonVersion instance from a tuple (major, minor, patchLevel,
|
||||
// suffix). This would be nicer with std::apply(), but it requires C++17.
|
||||
AddonVersion::AddonVersion(
|
||||
const std::tuple<int, int, int, AddonVersionSuffix>& t)
|
||||
: AddonVersion(std::get<0>(t), std::get<1>(t), std::get<2>(t), std::get<3>(t))
|
||||
{ }
|
||||
|
||||
AddonVersion::AddonVersion(const std::string& versionStr)
|
||||
: AddonVersion(versionStringToTuple(versionStr))
|
||||
{ }
|
||||
|
||||
AddonVersion::AddonVersion(const char* versionStr)
|
||||
: AddonVersion(string(versionStr))
|
||||
{ }
|
||||
|
||||
// Static method
|
||||
std::tuple<int, int, int, AddonVersionSuffix>
|
||||
AddonVersion::versionStringToTuple(const std::string& versionStr)
|
||||
{
|
||||
#ifdef HAVE_WORKING_STD_REGEX
|
||||
// Use a simplified variant of the syntax described in PEP 440
|
||||
// <https://www.python.org/dev/peps/pep-0440/> (always 3 components in the
|
||||
// release segment, pre-release segment + development release segment; no
|
||||
// post-release segment allowed).
|
||||
std::regex versionRegexp(R"((\d+)\.(\d+).(\d+)(.*))");
|
||||
std::smatch results;
|
||||
|
||||
if (std::regex_match(versionStr, results, versionRegexp)) {
|
||||
const string majorNumber_s = results.str(1);
|
||||
const string minorNumber_s = results.str(2);
|
||||
const string patchLevel_s = results.str(3);
|
||||
const string suffix_s = results.str(4);
|
||||
|
||||
int major = strutils::readNonNegativeInt<int>(majorNumber_s);
|
||||
int minor = strutils::readNonNegativeInt<int>(minorNumber_s);
|
||||
int patchLevel = strutils::readNonNegativeInt<int>(patchLevel_s);
|
||||
|
||||
return std::make_tuple(major, minor, patchLevel,
|
||||
AddonVersionSuffix(suffix_s));
|
||||
#else // all this 'else' clause should be removed once we actually require C++11
|
||||
bool isMatch;
|
||||
int major, minor, patchLevel;
|
||||
AddonVersionSuffix suffix;
|
||||
|
||||
std::tie(isMatch, major, minor, patchLevel, suffix) =
|
||||
parseVersionString_noRegexp(versionStr);
|
||||
|
||||
if (isMatch) {
|
||||
return std::make_tuple(major, minor, patchLevel, suffix);
|
||||
#endif // HAVE_WORKING_STD_REGEX
|
||||
} else { // the regexp didn't match
|
||||
string msg = "invalid add-on version number: '" + versionStr + "' "
|
||||
"(expected form is MAJOR.MINOR.PATCHLEVEL[{a|b|rc}N1][.devN2] where "
|
||||
"N1 and N2 are positive integers)";
|
||||
throw sg_format_exception(msg, versionStr);
|
||||
}
|
||||
}
|
||||
|
||||
// Static method, only needed for compilers that are not C++11-compliant
|
||||
// (gcc 4.8 pretends to support <regex> as required by C++11 but doesn't, see
|
||||
// <https://stackoverflow.com/a/12665408/4756009>).
|
||||
std::tuple<bool, int, int, int, AddonVersionSuffix>
|
||||
AddonVersion::parseVersionString_noRegexp(const string& versionStr)
|
||||
{
|
||||
int major = 0, minor = 0, patchLevel = 0;
|
||||
AddonVersionSuffix suffix{};
|
||||
|
||||
// Major version number
|
||||
std::size_t endMajor = versionStr.find_first_not_of("0123456789");
|
||||
if (endMajor == 0 || endMajor == string::npos) { // no match
|
||||
return std::make_tuple(false, major, minor, patchLevel, suffix);
|
||||
}
|
||||
major = strutils::readNonNegativeInt<int>(versionStr.substr(0, endMajor));
|
||||
|
||||
// Dot separating the major and minor version numbers
|
||||
if (versionStr.size() < endMajor + 1 || versionStr[endMajor] != '.') {
|
||||
return std::make_tuple(false, major, minor, patchLevel, suffix);
|
||||
}
|
||||
string rest = versionStr.substr(endMajor + 1);
|
||||
|
||||
// Minor version number
|
||||
std::size_t endMinor = rest.find_first_not_of("0123456789");
|
||||
if (endMinor == 0 || endMinor == string::npos) { // no match
|
||||
return std::make_tuple(false, major, minor, patchLevel, suffix);
|
||||
}
|
||||
minor = strutils::readNonNegativeInt<int>(rest.substr(0, endMinor));
|
||||
|
||||
// Dot separating the minor version number and the patch level
|
||||
if (rest.size() < endMinor + 1 || rest[endMinor] != '.') {
|
||||
return std::make_tuple(false, major, minor, patchLevel, suffix);
|
||||
}
|
||||
rest = rest.substr(endMinor + 1);
|
||||
|
||||
// Patch level
|
||||
std::size_t endPatchLevel = rest.find_first_not_of("0123456789");
|
||||
if (endPatchLevel == 0) { // no patch level, therefore no match
|
||||
return std::make_tuple(false, major, minor, patchLevel, suffix);
|
||||
}
|
||||
patchLevel = strutils::readNonNegativeInt<int>(rest.substr(0, endPatchLevel));
|
||||
|
||||
if (endPatchLevel != string::npos) { // there is a version suffix, parse it
|
||||
suffix = AddonVersionSuffix(rest.substr(endPatchLevel));
|
||||
}
|
||||
|
||||
return std::make_tuple(true, major, minor, patchLevel, suffix);
|
||||
}
|
||||
|
||||
int AddonVersion::majorNumber() const
|
||||
{ return _major; }
|
||||
|
||||
int AddonVersion::minorNumber() const
|
||||
{ return _minor; }
|
||||
|
||||
int AddonVersion::patchLevel() const
|
||||
{ return _patchLevel; }
|
||||
|
||||
AddonVersionSuffix AddonVersion::suffix() const
|
||||
{ return _suffix; }
|
||||
|
||||
std::string AddonVersion::suffixStr() const
|
||||
{ return suffix().str(); }
|
||||
|
||||
std::tuple<int, int, int, AddonVersionSuffix> AddonVersion::makeTuple() const
|
||||
{
|
||||
return std::make_tuple(majorNumber(), minorNumber(), patchLevel(), suffix());
|
||||
}
|
||||
|
||||
string AddonVersion::str() const
|
||||
{
|
||||
// Assemble the major.minor.patchLevel string
|
||||
vector<int> v({majorNumber(), minorNumber(), patchLevel()});
|
||||
string relSeg = std::accumulate(std::next(v.begin()), v.end(),
|
||||
std::to_string(v[0]),
|
||||
[](string s, int num) {
|
||||
return s + '.' + std::to_string(num);
|
||||
});
|
||||
|
||||
// Concatenate with the suffix string
|
||||
return relSeg + suffixStr();
|
||||
}
|
||||
|
||||
|
||||
bool operator==(const AddonVersion& lhs, const AddonVersion& rhs)
|
||||
{ return lhs.makeTuple() == rhs.makeTuple(); }
|
||||
|
||||
bool operator!=(const AddonVersion& lhs, const AddonVersion& rhs)
|
||||
{ return !operator==(lhs, rhs); }
|
||||
|
||||
bool operator< (const AddonVersion& lhs, const AddonVersion& rhs)
|
||||
{ return lhs.makeTuple() < rhs.makeTuple(); }
|
||||
|
||||
bool operator> (const AddonVersion& lhs, const AddonVersion& rhs)
|
||||
{ return operator<(rhs, lhs); }
|
||||
|
||||
bool operator<=(const AddonVersion& lhs, const AddonVersion& rhs)
|
||||
{ return !operator>(lhs, rhs); }
|
||||
|
||||
bool operator>=(const AddonVersion& lhs, const AddonVersion& rhs)
|
||||
{ return !operator<(lhs, rhs); }
|
||||
|
||||
std::ostream& operator<<(std::ostream& os, const AddonVersion& addonVersion)
|
||||
{ return os << addonVersion.str(); }
|
||||
|
||||
|
||||
// ***************************************************************************
|
||||
// * For the Nasal bindings *
|
||||
// ***************************************************************************
|
||||
|
||||
bool AddonVersion::equal(const nasal::CallContext& ctx) const
|
||||
{
|
||||
auto other = ctx.requireArg<AddonVersionRef>(0);
|
||||
return *this == *other;
|
||||
}
|
||||
|
||||
bool AddonVersion::nonEqual(const nasal::CallContext& ctx) const
|
||||
{
|
||||
auto other = ctx.requireArg<AddonVersionRef>(0);
|
||||
return *this != *other;
|
||||
}
|
||||
|
||||
bool AddonVersion::lowerThan(const nasal::CallContext& ctx) const
|
||||
{
|
||||
auto other = ctx.requireArg<AddonVersionRef>(0);
|
||||
return *this < *other;
|
||||
}
|
||||
|
||||
bool AddonVersion::lowerThanOrEqual(const nasal::CallContext& ctx) const
|
||||
{
|
||||
auto other = ctx.requireArg<AddonVersionRef>(0);
|
||||
return *this <= *other;
|
||||
}
|
||||
|
||||
bool AddonVersion::greaterThan(const nasal::CallContext& ctx) const
|
||||
{
|
||||
auto other = ctx.requireArg<AddonVersionRef>(0);
|
||||
return *this > *other;
|
||||
}
|
||||
|
||||
bool AddonVersion::greaterThanOrEqual(const nasal::CallContext& ctx) const
|
||||
{
|
||||
auto other = ctx.requireArg<AddonVersionRef>(0);
|
||||
return *this >= *other;
|
||||
}
|
||||
|
||||
// Static method
|
||||
void AddonVersion::setupGhost(nasal::Hash& addonsModule)
|
||||
{
|
||||
nasal::Ghost<AddonVersionRef>::init("addons.AddonVersion")
|
||||
.member("majorNumber", &AddonVersion::majorNumber)
|
||||
.member("minorNumber", &AddonVersion::minorNumber)
|
||||
.member("patchLevel", &AddonVersion::patchLevel)
|
||||
.member("suffix", &AddonVersion::suffixStr)
|
||||
.method("str", &AddonVersion::str)
|
||||
.method("equal", &AddonVersion::equal)
|
||||
.method("nonEqual", &AddonVersion::nonEqual)
|
||||
.method("lowerThan", &AddonVersion::lowerThan)
|
||||
.method("lowerThanOrEqual", &AddonVersion::lowerThanOrEqual)
|
||||
.method("greaterThan", &AddonVersion::greaterThan)
|
||||
.method("greaterThanOrEqual", &AddonVersion::greaterThanOrEqual);
|
||||
}
|
||||
|
||||
} // of namespace addons
|
||||
|
||||
} // of namespace flightgear
|
||||
212
src/Add-ons/AddonVersion.hxx
Normal file
212
src/Add-ons/AddonVersion.hxx
Normal file
@@ -0,0 +1,212 @@
|
||||
// -*- coding: utf-8 -*-
|
||||
//
|
||||
// AddonVersion.hxx --- Version class for FlightGear add-ons
|
||||
// Copyright (C) 2017 Florent Rougon
|
||||
//
|
||||
// This program is free software; you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation; either version 2 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License along
|
||||
// with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
|
||||
#ifndef FG_ADDONVERSION_HXX
|
||||
#define FG_ADDONVERSION_HXX
|
||||
|
||||
#include <ostream>
|
||||
#include <string>
|
||||
#include <tuple>
|
||||
#include <type_traits>
|
||||
|
||||
#include <simgear/nasal/cppbind/NasalCallContext.hxx>
|
||||
#include <simgear/nasal/cppbind/NasalHash.hxx>
|
||||
#include <simgear/structure/SGReferenced.hxx>
|
||||
|
||||
#include "addon_fwd.hxx"
|
||||
|
||||
namespace flightgear
|
||||
{
|
||||
|
||||
namespace addons
|
||||
{
|
||||
|
||||
// Order matters for the sorting/comparison functions
|
||||
enum class AddonVersionSuffixPrereleaseType {
|
||||
alpha = 0,
|
||||
beta,
|
||||
candidate,
|
||||
none
|
||||
};
|
||||
|
||||
// ***************************************************************************
|
||||
// * AddonVersionSuffix *
|
||||
// ***************************************************************************
|
||||
|
||||
class AddonVersionSuffix
|
||||
{
|
||||
public:
|
||||
AddonVersionSuffix(AddonVersionSuffixPrereleaseType _preReleaseType
|
||||
= AddonVersionSuffixPrereleaseType::none,
|
||||
int preReleaseNum = 0, bool developmental = false,
|
||||
int devNum = 0);
|
||||
// Construct from a string. The empty string is a valid input.
|
||||
AddonVersionSuffix(const std::string& suffix);
|
||||
AddonVersionSuffix(const char* suffix);
|
||||
// Construct from a tuple
|
||||
explicit AddonVersionSuffix(
|
||||
const std::tuple<AddonVersionSuffixPrereleaseType, int, bool, int>& t);
|
||||
|
||||
// Return all components of an AddonVersionSuffix instance as a tuple.
|
||||
// Beware, this is not suitable for sorting! cf. genSortKey() below.
|
||||
std::tuple<AddonVersionSuffixPrereleaseType, int, bool, int> makeTuple() const;
|
||||
|
||||
// String representation of an AddonVersionSuffix
|
||||
std::string str() const;
|
||||
|
||||
private:
|
||||
// String representation of the release type component: "a", "b", "rc" or "".
|
||||
static std::string releaseTypeStr(AddonVersionSuffixPrereleaseType);
|
||||
|
||||
// If 's' starts with a non-empty release type ('a', 'b' or 'rc'), return
|
||||
// the corresponding enum value along with the remainder of 's' (that is,
|
||||
// everything after the release type). Otherwise, return
|
||||
// AddonVersionSuffixPrereleaseType::none along with a copy of 's'.
|
||||
static std::tuple<AddonVersionSuffixPrereleaseType, std::string>
|
||||
popPrereleaseTypeFromBeginning(const std::string& s);
|
||||
|
||||
// Extract all components from a string representing a version suffix.
|
||||
// The components of the return value are, in this order:
|
||||
//
|
||||
// preReleaseType, preReleaseNum, developmental, devNum
|
||||
//
|
||||
// Note: the empty string is a valid input.
|
||||
static std::tuple<AddonVersionSuffixPrereleaseType, int, bool, int>
|
||||
suffixStringToTuple(const std::string& suffix);
|
||||
|
||||
// Used to implement suffixStringToTuple() for compilers that are not
|
||||
// C++11-compliant (gcc 4.8 pretends to support <regex> as required by C++11
|
||||
// but doesn't, see <https://stackoverflow.com/a/12665408/4756009>).
|
||||
//
|
||||
// The bool in the first component of the result is true iff 'suffix' is a
|
||||
// valid version suffix string. The bool is false when 'suffix' is invalid
|
||||
// in such a way that the generic sg_format_exception thrown at the end of
|
||||
// suffixStringToTuple() is appropriate. In all other cases, a specific
|
||||
// exception is thrown.
|
||||
static std::tuple<bool, AddonVersionSuffixPrereleaseType, int, bool, int>
|
||||
parseVersionSuffixString_noRegexp(const std::string& suffix);
|
||||
|
||||
// Useful for comparisons/sorting purposes
|
||||
std::tuple<int,
|
||||
std::underlying_type<AddonVersionSuffixPrereleaseType>::type,
|
||||
int, int, int> genSortKey() const;
|
||||
|
||||
friend bool operator==(const AddonVersionSuffix& lhs,
|
||||
const AddonVersionSuffix& rhs);
|
||||
friend bool operator<(const AddonVersionSuffix& lhs,
|
||||
const AddonVersionSuffix& rhs);
|
||||
|
||||
AddonVersionSuffixPrereleaseType _preReleaseType;
|
||||
int _preReleaseNum; // integer >= 1 (0 when not applicable)
|
||||
bool _developmental; // whether the suffix ends with '.devN'
|
||||
int _devNum; // integer >= 1 (0 when not applicable)
|
||||
};
|
||||
|
||||
|
||||
// operator==() and operator<() are declared above.
|
||||
bool operator!=(const AddonVersionSuffix& lhs, const AddonVersionSuffix& rhs);
|
||||
bool operator> (const AddonVersionSuffix& lhs, const AddonVersionSuffix& rhs);
|
||||
bool operator<=(const AddonVersionSuffix& lhs, const AddonVersionSuffix& rhs);
|
||||
bool operator>=(const AddonVersionSuffix& lhs, const AddonVersionSuffix& rhs);
|
||||
|
||||
std::ostream& operator<<(std::ostream&, const AddonVersionSuffix&);
|
||||
|
||||
// ***************************************************************************
|
||||
// * AddonVersion *
|
||||
// ***************************************************************************
|
||||
|
||||
// I suggest to use either the year-based FlightGear-type versioning, or
|
||||
// semantic versioning (<http://semver.org/>). For the suffix, we allow things
|
||||
// like "a1" (alpha1), "b2" (beta2), "rc4" (release candidate 4), "a1.dev3"
|
||||
// (development release for "a1", which sorts before "a1"), etc. It's a subset
|
||||
// of the syntax allowed in <https://www.python.org/dev/peps/pep-0440/>.
|
||||
class AddonVersion : public SGReferenced
|
||||
{
|
||||
public:
|
||||
AddonVersion(int major = 0, int minor = 0, int patchLevel = 0,
|
||||
AddonVersionSuffix suffix = AddonVersionSuffix());
|
||||
AddonVersion(const std::string& version);
|
||||
AddonVersion(const char* version);
|
||||
explicit AddonVersion(const std::tuple<int, int, int, AddonVersionSuffix>& t);
|
||||
|
||||
// Using the method names major() and minor() can lead to incomprehensible
|
||||
// errors such as "major is not a member of flightgear::addons::AddonVersion"
|
||||
// because of a hideous glibc bug[1]: major() and minor() are defined by
|
||||
// standard headers as *macros*!
|
||||
//
|
||||
// [1] https://bugzilla.redhat.com/show_bug.cgi?id=130601
|
||||
int majorNumber() const;
|
||||
int minorNumber() const;
|
||||
int patchLevel() const;
|
||||
AddonVersionSuffix suffix() const;
|
||||
std::string suffixStr() const;
|
||||
|
||||
std::string str() const;
|
||||
|
||||
// For the Nasal bindings (otherwise, we have operator==(), etc.)
|
||||
bool equal(const nasal::CallContext& ctx) const;
|
||||
bool nonEqual(const nasal::CallContext& ctx) const;
|
||||
bool lowerThan(const nasal::CallContext& ctx) const;
|
||||
bool lowerThanOrEqual(const nasal::CallContext& ctx) const;
|
||||
bool greaterThan(const nasal::CallContext& ctx) const;
|
||||
bool greaterThanOrEqual(const nasal::CallContext& ctx) const;
|
||||
|
||||
static void setupGhost(nasal::Hash& addonsModule);
|
||||
|
||||
private:
|
||||
// Useful for comparisons/sorting purposes
|
||||
std::tuple<int, int, int, AddonVersionSuffix> makeTuple() const;
|
||||
|
||||
static std::tuple<int, int, int, AddonVersionSuffix>
|
||||
versionStringToTuple(const std::string& versionStr);
|
||||
|
||||
// Used to implement versionStringToTuple() for compilers that are not
|
||||
// C++11-compliant (gcc 4.8 pretends to support <regex> as required by C++11
|
||||
// but doesn't, see <https://stackoverflow.com/a/12665408/4756009>).
|
||||
//
|
||||
// The bool in the first component of the result is true iff 'versionStr' is
|
||||
// a valid version string. The bool is false when 'versionStr' is invalid in
|
||||
// such a way that the generic sg_format_exception thrown at the end of
|
||||
// versionStringToTuple() is appropriate. In all other cases, a specific
|
||||
// exception is thrown.
|
||||
static std::tuple<bool, int, int, int, AddonVersionSuffix>
|
||||
parseVersionString_noRegexp(const std::string& versionStr);
|
||||
|
||||
friend bool operator==(const AddonVersion& lhs, const AddonVersion& rhs);
|
||||
friend bool operator<(const AddonVersion& lhs, const AddonVersion& rhs);
|
||||
|
||||
int _major;
|
||||
int _minor;
|
||||
int _patchLevel;
|
||||
AddonVersionSuffix _suffix;
|
||||
};
|
||||
|
||||
// operator==() and operator<() are declared above.
|
||||
bool operator!=(const AddonVersion& lhs, const AddonVersion& rhs);
|
||||
bool operator> (const AddonVersion& lhs, const AddonVersion& rhs);
|
||||
bool operator<=(const AddonVersion& lhs, const AddonVersion& rhs);
|
||||
bool operator>=(const AddonVersion& lhs, const AddonVersion& rhs);
|
||||
|
||||
std::ostream& operator<<(std::ostream&, const AddonVersion&);
|
||||
|
||||
} // of namespace addons
|
||||
|
||||
} // of namespace flightgear
|
||||
|
||||
#endif // of FG_ADDONVERSION_HXX
|
||||
23
src/Add-ons/CMakeLists.txt
Normal file
23
src/Add-ons/CMakeLists.txt
Normal file
@@ -0,0 +1,23 @@
|
||||
include(FlightGearComponent)
|
||||
|
||||
set(SOURCES Addon.cxx
|
||||
AddonManager.cxx
|
||||
AddonMetadataParser.cxx
|
||||
AddonResourceProvider.cxx
|
||||
AddonVersion.cxx
|
||||
contacts.cxx
|
||||
exceptions.cxx
|
||||
)
|
||||
|
||||
set(HEADERS addon_fwd.hxx
|
||||
Addon.hxx
|
||||
AddonManager.hxx
|
||||
AddonMetadataParser.hxx
|
||||
AddonResourceProvider.hxx
|
||||
AddonVersion.hxx
|
||||
contacts.hxx
|
||||
exceptions.hxx
|
||||
pointer_traits.hxx
|
||||
)
|
||||
|
||||
flightgear_component(AddonManagement "${SOURCES}" "${HEADERS}")
|
||||
72
src/Add-ons/addon_fwd.hxx
Normal file
72
src/Add-ons/addon_fwd.hxx
Normal file
@@ -0,0 +1,72 @@
|
||||
// -*- coding: utf-8 -*-
|
||||
//
|
||||
// addon_fwd.hxx --- Forward declarations for the FlightGear add-on
|
||||
// infrastructure
|
||||
// Copyright (C) 2017 Florent Rougon
|
||||
//
|
||||
// This program is free software; you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation; either version 2 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License along
|
||||
// with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
|
||||
#ifndef FG_ADDON_FWD_HXX
|
||||
#define FG_ADDON_FWD_HXX
|
||||
|
||||
#include <simgear/structure/SGSharedPtr.hxx>
|
||||
|
||||
namespace flightgear
|
||||
{
|
||||
|
||||
namespace addons
|
||||
{
|
||||
|
||||
class Addon;
|
||||
class AddonManager;
|
||||
class AddonVersion;
|
||||
class AddonVersionSuffix;
|
||||
class ResourceProvider;
|
||||
|
||||
enum class UrlType;
|
||||
class QualifiedUrl;
|
||||
|
||||
enum class ContactType;
|
||||
class Contact;
|
||||
class Author;
|
||||
class Maintainer;
|
||||
|
||||
using AddonRef = SGSharedPtr<Addon>;
|
||||
using AddonVersionRef = SGSharedPtr<AddonVersion>;
|
||||
using ContactRef = SGSharedPtr<Contact>;
|
||||
using AuthorRef = SGSharedPtr<Author>;
|
||||
using MaintainerRef = SGSharedPtr<Maintainer>;
|
||||
|
||||
namespace errors
|
||||
{
|
||||
|
||||
class error;
|
||||
class error_loading_config_file;
|
||||
class no_metadata_file_found;
|
||||
class error_loading_metadata_file;
|
||||
class error_loading_menubar_items_file;
|
||||
class duplicate_registration_attempt;
|
||||
class fg_version_too_old;
|
||||
class fg_version_too_recent;
|
||||
class invalid_resource_path;
|
||||
class unable_to_create_addon_storage_dir;
|
||||
|
||||
} // of namespace errors
|
||||
|
||||
} // of namespace addons
|
||||
|
||||
} // of namespace flightgear
|
||||
|
||||
#endif // of FG_ADDON_FWD_HXX
|
||||
127
src/Add-ons/contacts.cxx
Normal file
127
src/Add-ons/contacts.cxx
Normal file
@@ -0,0 +1,127 @@
|
||||
// -*- coding: utf-8 -*-
|
||||
//
|
||||
// contacts.cxx --- FlightGear classes holding add-on contact metadata
|
||||
// Copyright (C) 2018 Florent Rougon
|
||||
//
|
||||
// This program is free software; you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation; either version 2 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License along
|
||||
// with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
#include <simgear/nasal/cppbind/Ghost.hxx>
|
||||
#include <simgear/nasal/cppbind/NasalHash.hxx>
|
||||
#include <simgear/sg_inlines.h>
|
||||
#include <simgear/structure/exception.hxx>
|
||||
|
||||
#include "addon_fwd.hxx"
|
||||
#include "contacts.hxx"
|
||||
|
||||
using std::string;
|
||||
using simgear::enumValue;
|
||||
|
||||
namespace flightgear
|
||||
{
|
||||
|
||||
namespace addons
|
||||
{
|
||||
|
||||
// ***************************************************************************
|
||||
// * Contact *
|
||||
// ***************************************************************************
|
||||
|
||||
Contact::Contact(ContactType type, string name, string email, string url)
|
||||
: _type(type),
|
||||
_name(std::move(name)),
|
||||
_email(std::move(email)),
|
||||
_url(std::move(url))
|
||||
{ }
|
||||
|
||||
ContactType Contact::getType() const
|
||||
{ return _type; }
|
||||
|
||||
string Contact::getTypeString() const
|
||||
{
|
||||
switch (getType()) {
|
||||
case ContactType::author:
|
||||
return "author";
|
||||
case ContactType::maintainer:
|
||||
return "maintainer";
|
||||
default:
|
||||
throw sg_error("unexpected value for member of "
|
||||
"flightgear::addons::ContactType: " +
|
||||
std::to_string(enumValue(getType())));
|
||||
}
|
||||
}
|
||||
|
||||
string Contact::getName() const
|
||||
{ return _name; }
|
||||
|
||||
void Contact::setName(const string& name)
|
||||
{ _name = name; }
|
||||
|
||||
string Contact::getEmail() const
|
||||
{ return _email; }
|
||||
|
||||
void Contact::setEmail(const string& email)
|
||||
{ _email = email; }
|
||||
|
||||
string Contact::getUrl() const
|
||||
{ return _url; }
|
||||
|
||||
void Contact::setUrl(const string& url)
|
||||
{ _url = url; }
|
||||
|
||||
// Static method
|
||||
void Contact::setupGhost(nasal::Hash& addonsModule)
|
||||
{
|
||||
nasal::Ghost<ContactRef>::init("addons.Contact")
|
||||
.member("name", &Contact::getName)
|
||||
.member("email", &Contact::getEmail)
|
||||
.member("url", &Contact::getUrl);
|
||||
}
|
||||
|
||||
// ***************************************************************************
|
||||
// * Author *
|
||||
// ***************************************************************************
|
||||
|
||||
Author::Author(string name, string email, string url)
|
||||
: Contact(ContactType::author, name, email, url)
|
||||
{ }
|
||||
|
||||
// Static method
|
||||
void Author::setupGhost(nasal::Hash& addonsModule)
|
||||
{
|
||||
nasal::Ghost<AuthorRef>::init("addons.Author")
|
||||
.bases<ContactRef>();
|
||||
}
|
||||
|
||||
// ***************************************************************************
|
||||
// * Maintainer *
|
||||
// ***************************************************************************
|
||||
|
||||
Maintainer::Maintainer(string name, string email, string url)
|
||||
: Contact(ContactType::maintainer, name, email, url)
|
||||
{ }
|
||||
|
||||
// Static method
|
||||
void Maintainer::setupGhost(nasal::Hash& addonsModule)
|
||||
{
|
||||
nasal::Ghost<MaintainerRef>::init("addons.Maintainer")
|
||||
.bases<ContactRef>();
|
||||
}
|
||||
|
||||
} // of namespace addons
|
||||
|
||||
} // of namespace flightgear
|
||||
126
src/Add-ons/contacts.hxx
Normal file
126
src/Add-ons/contacts.hxx
Normal file
@@ -0,0 +1,126 @@
|
||||
// -*- coding: utf-8 -*-
|
||||
//
|
||||
// contacts.hxx --- FlightGear classes holding add-on contact metadata
|
||||
// Copyright (C) 2018 Florent Rougon
|
||||
//
|
||||
// This program is free software; you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation; either version 2 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License along
|
||||
// with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
|
||||
#ifndef FG_ADDON_CONTACTS_HXX
|
||||
#define FG_ADDON_CONTACTS_HXX
|
||||
|
||||
#include <string>
|
||||
|
||||
#include <simgear/structure/SGReferenced.hxx>
|
||||
|
||||
#include "addon_fwd.hxx"
|
||||
|
||||
namespace nasal
|
||||
{
|
||||
class Hash; // forward declaration
|
||||
};
|
||||
|
||||
namespace flightgear
|
||||
{
|
||||
|
||||
namespace addons
|
||||
{
|
||||
|
||||
enum class ContactType {
|
||||
author,
|
||||
maintainer
|
||||
};
|
||||
|
||||
// Class used to store info about an author or maintainer (possibly also a
|
||||
// mailing-list, things like that)
|
||||
class Contact : public SGReferenced
|
||||
{
|
||||
public:
|
||||
Contact(ContactType type, std::string name, std::string email = "",
|
||||
std::string url = "");
|
||||
virtual ~Contact() = default;
|
||||
|
||||
ContactType getType() const;
|
||||
std::string getTypeString() const;
|
||||
|
||||
std::string getName() const;
|
||||
void setName(const std::string& name);
|
||||
|
||||
std::string getEmail() const;
|
||||
void setEmail(const std::string& email);
|
||||
|
||||
std::string getUrl() const;
|
||||
void setUrl(const std::string& url);
|
||||
|
||||
static void setupGhost(nasal::Hash& addonsModule);
|
||||
|
||||
private:
|
||||
const ContactType _type;
|
||||
std::string _name;
|
||||
std::string _email;
|
||||
std::string _url;
|
||||
};
|
||||
|
||||
class Author : public Contact
|
||||
{
|
||||
public:
|
||||
Author(std::string name, std::string email = "", std::string url = "");
|
||||
|
||||
static void setupGhost(nasal::Hash& addonsModule);
|
||||
};
|
||||
|
||||
class Maintainer : public Contact
|
||||
{
|
||||
public:
|
||||
Maintainer(std::string name, std::string email = "", std::string url = "");
|
||||
|
||||
static void setupGhost(nasal::Hash& addonsModule);
|
||||
};
|
||||
|
||||
// ***************************************************************************
|
||||
// * contact_traits *
|
||||
// ***************************************************************************
|
||||
|
||||
template <typename T>
|
||||
struct contact_traits;
|
||||
|
||||
template<>
|
||||
struct contact_traits<Author>
|
||||
{
|
||||
using contact_type = Author;
|
||||
using strong_ref = AuthorRef;
|
||||
|
||||
static std::string xmlNodeName()
|
||||
{
|
||||
return "author";
|
||||
}
|
||||
};
|
||||
|
||||
template<>
|
||||
struct contact_traits<Maintainer>
|
||||
{
|
||||
using contact_type = Maintainer;
|
||||
using strong_ref = MaintainerRef;
|
||||
|
||||
static std::string xmlNodeName()
|
||||
{
|
||||
return "maintainer";
|
||||
}
|
||||
};
|
||||
|
||||
} // of namespace addons
|
||||
|
||||
} // of namespace flightgear
|
||||
|
||||
#endif // of FG_ADDON_CONTACTS_HXX
|
||||
55
src/Add-ons/exceptions.cxx
Normal file
55
src/Add-ons/exceptions.cxx
Normal file
@@ -0,0 +1,55 @@
|
||||
// -*- coding: utf-8 -*-
|
||||
//
|
||||
// exceptions.cxx --- Exception classes for the FlightGear add-on infrastructure
|
||||
// Copyright (C) 2017 Florent Rougon
|
||||
//
|
||||
// This program is free software; you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation; either version 2 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License along
|
||||
// with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
|
||||
#include <string>
|
||||
|
||||
#include <simgear/structure/exception.hxx>
|
||||
|
||||
#include "exceptions.hxx"
|
||||
|
||||
using std::string;
|
||||
|
||||
namespace flightgear
|
||||
{
|
||||
|
||||
namespace addons
|
||||
{
|
||||
|
||||
namespace errors
|
||||
{
|
||||
|
||||
// ***************************************************************************
|
||||
// * Base class for add-on exceptions *
|
||||
// ***************************************************************************
|
||||
|
||||
// Prepending a prefix such as "Add-on error: " would be redundant given the
|
||||
// messages used in, e.g., the Addon class code.
|
||||
error::error(const string& message, const string& origin)
|
||||
: sg_exception(message, origin)
|
||||
{ }
|
||||
|
||||
error::error(const char* message, const char* origin)
|
||||
: error(string(message), string(origin))
|
||||
{ }
|
||||
|
||||
} // of namespace errors
|
||||
|
||||
} // of namespace addons
|
||||
|
||||
} // of namespace flightgear
|
||||
77
src/Add-ons/exceptions.hxx
Normal file
77
src/Add-ons/exceptions.hxx
Normal file
@@ -0,0 +1,77 @@
|
||||
// -*- coding: utf-8 -*-
|
||||
//
|
||||
// exceptions.hxx --- Exception classes for the FlightGear add-on infrastructure
|
||||
// Copyright (C) 2017 Florent Rougon
|
||||
//
|
||||
// This program is free software; you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation; either version 2 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License along
|
||||
// with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
|
||||
#ifndef FG_ADDON_EXCEPTIONS_HXX
|
||||
#define FG_ADDON_EXCEPTIONS_HXX
|
||||
|
||||
#include <string>
|
||||
|
||||
#include <simgear/structure/exception.hxx>
|
||||
|
||||
namespace flightgear
|
||||
{
|
||||
|
||||
namespace addons
|
||||
{
|
||||
|
||||
namespace errors
|
||||
{
|
||||
|
||||
class error : public sg_exception
|
||||
{
|
||||
public:
|
||||
explicit error(const std::string& message,
|
||||
const std::string& origin = std::string());
|
||||
explicit error(const char* message, const char* origin = nullptr);
|
||||
};
|
||||
|
||||
class error_loading_config_file : public error
|
||||
{ using error::error; /* inherit all constructors */ };
|
||||
|
||||
class no_metadata_file_found : public error
|
||||
{ using error::error; };
|
||||
|
||||
class error_loading_metadata_file : public error
|
||||
{ using error::error; };
|
||||
|
||||
class error_loading_menubar_items_file : public error
|
||||
{ using error::error; };
|
||||
|
||||
class duplicate_registration_attempt : public error
|
||||
{ using error::error; };
|
||||
|
||||
class fg_version_too_old : public error
|
||||
{ using error::error; };
|
||||
|
||||
class fg_version_too_recent : public error
|
||||
{ using error::error; };
|
||||
|
||||
class invalid_resource_path : public error
|
||||
{ using error::error; };
|
||||
|
||||
class unable_to_create_addon_storage_dir : public error
|
||||
{ using error::error; };
|
||||
|
||||
} // of namespace errors
|
||||
|
||||
} // of namespace addons
|
||||
|
||||
} // of namespace flightgear
|
||||
|
||||
#endif // of FG_ADDON_EXCEPTIONS_HXX
|
||||
67
src/Add-ons/pointer_traits.hxx
Normal file
67
src/Add-ons/pointer_traits.hxx
Normal file
@@ -0,0 +1,67 @@
|
||||
// -*- coding: utf-8 -*-
|
||||
//
|
||||
// pointer_traits.hxx --- Pointer traits classes
|
||||
// Copyright (C) 2018 Florent Rougon
|
||||
//
|
||||
// This program is free software; you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation; either version 2 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License along
|
||||
// with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
|
||||
#ifndef FG_ADDON_POINTER_TRAITS_HXX
|
||||
#define FG_ADDON_POINTER_TRAITS_HXX
|
||||
|
||||
#include <memory>
|
||||
#include <utility>
|
||||
|
||||
#include <simgear/structure/SGSharedPtr.hxx>
|
||||
|
||||
namespace flightgear
|
||||
{
|
||||
|
||||
namespace addons
|
||||
{
|
||||
|
||||
template <typename T>
|
||||
struct shared_ptr_traits;
|
||||
|
||||
template <typename T>
|
||||
struct shared_ptr_traits<SGSharedPtr<T>>
|
||||
{
|
||||
using element_type = T;
|
||||
using strong_ref = SGSharedPtr<T>;
|
||||
|
||||
template <typename ...Args>
|
||||
static strong_ref makeStrongRef(Args&& ...args)
|
||||
{
|
||||
return strong_ref(new T(std::forward<Args>(args)...));
|
||||
}
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
struct shared_ptr_traits<std::shared_ptr<T>>
|
||||
{
|
||||
using element_type = T;
|
||||
using strong_ref = std::shared_ptr<T>;
|
||||
|
||||
template <typename ...Args>
|
||||
static strong_ref makeStrongRef(Args&& ...args)
|
||||
{
|
||||
return std::make_shared<T>(std::forward<Args>(args)...);
|
||||
}
|
||||
};
|
||||
|
||||
} // of namespace addons
|
||||
|
||||
} // of namespace flightgear
|
||||
|
||||
#endif // of FG_ADDON_POINTER_TRAITS_HXX
|
||||
407
src/Aircraft/AircraftPerformance.cxx
Normal file
407
src/Aircraft/AircraftPerformance.cxx
Normal file
@@ -0,0 +1,407 @@
|
||||
// AircraftPerformance.cxx - compute data about planned acft performance
|
||||
//
|
||||
// Copyright (C) 2018 James Turner <james@flightgear.org>
|
||||
// This program is free software; you can redistribute it and/or
|
||||
// modify it under the terms of the GNU General Public License as
|
||||
// published by the Free Software Foundation; either version 2 of the
|
||||
// License, or (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful, but
|
||||
// WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program; if not, write to the Free Software
|
||||
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
|
||||
#include "AircraftPerformance.hxx"
|
||||
|
||||
#include <cassert>
|
||||
#include <algorithm>
|
||||
|
||||
#include <simgear/constants.h>
|
||||
|
||||
#include <Main/fg_props.hxx>
|
||||
|
||||
using namespace flightgear;
|
||||
|
||||
double distanceForTimeAndSpeeds(double tSec, double v1, double v2)
|
||||
{
|
||||
return tSec * 0.5 * (v1 + v2);
|
||||
}
|
||||
|
||||
AircraftPerformance::AircraftPerformance()
|
||||
{
|
||||
// read aircraft supplied performance data
|
||||
if (fgGetNode("/aircraft/performance/bracket")) {
|
||||
readPerformanceData();
|
||||
} else {
|
||||
// falls back to heuristic determination of the category,
|
||||
// and a plausible default
|
||||
icaoCategoryData();
|
||||
}
|
||||
}
|
||||
|
||||
double AircraftPerformance::groundSpeedForAltitudeKnots(int altitudeFt) const
|
||||
{
|
||||
auto bracket = bracketForAltitude(altitudeFt);
|
||||
return bracket->gsForAltitude(altitudeFt);
|
||||
}
|
||||
|
||||
int AircraftPerformance::computePreviousAltitude(double distanceM, int targetAltFt) const
|
||||
{
|
||||
auto bracket = bracketForAltitude(targetAltFt);
|
||||
auto d = bracket->descendDistanceM(bracket->atOrBelowAltitudeFt, targetAltFt);
|
||||
if (d < distanceM) {
|
||||
// recurse to previous bracket
|
||||
return computePreviousAltitude(distanceM - d, bracket->atOrBelowAltitudeFt+1);
|
||||
}
|
||||
|
||||
// work out how far we travel laterally per foot change in altitude
|
||||
// this value is in metres, we have to map FPM and GS in Knots to make
|
||||
// everything work out
|
||||
const double gsMPS = bracket->gsForAltitude(targetAltFt) * SG_KT_TO_MPS;
|
||||
const double t = distanceM / gsMPS;
|
||||
return targetAltFt + bracket->descentRateFPM * (t / 60.0);
|
||||
}
|
||||
|
||||
int AircraftPerformance::computeNextAltitude(double distanceM, int initialAltFt) const
|
||||
{
|
||||
auto bracket = bracketForAltitude(initialAltFt);
|
||||
auto d = bracket->climbDistanceM(initialAltFt, bracket->atOrBelowAltitudeFt);
|
||||
if (d < distanceM) {
|
||||
// recurse to next bracket
|
||||
return computeNextAltitude(distanceM - d, bracket->atOrBelowAltitudeFt+1);
|
||||
}
|
||||
|
||||
// work out how far we travel laterally per foot change in altitude
|
||||
// this value is in metres, we have to map FPM and GS in Knots to make
|
||||
// everything work out
|
||||
const double gsMPS = bracket->gsForAltitude(initialAltFt) * SG_KT_TO_MPS;
|
||||
const double t = distanceM / gsMPS;
|
||||
return initialAltFt + bracket->climbRateFPM * (t / 60.0);
|
||||
}
|
||||
|
||||
static string_list readTags()
|
||||
{
|
||||
string_list r;
|
||||
const auto tagsNode = fgGetNode("/sim/tags");
|
||||
if (!tagsNode)
|
||||
return r;
|
||||
|
||||
for (auto t : tagsNode->getChildren("tag")) {
|
||||
r.push_back(t->getStringValue());
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
static bool stringListContains(const string_list& t, const std::string& s)
|
||||
{
|
||||
auto it = std::find(t.begin(), t.end(), s);
|
||||
return it != t.end();
|
||||
}
|
||||
|
||||
std::string AircraftPerformance::heuristicCatergoryFromTags() const
|
||||
{
|
||||
const auto tags(readTags());
|
||||
|
||||
if (stringListContains(tags, "turboprop"))
|
||||
return {ICAO_AIRCRAFT_CATEGORY_C};
|
||||
|
||||
// any way we could distuinguish fast and slow GA aircraft?
|
||||
if (stringListContains(tags, "ga")) {
|
||||
return {ICAO_AIRCRAFT_CATEGORY_A};
|
||||
}
|
||||
|
||||
if (stringListContains(tags, "jet")) {
|
||||
return {ICAO_AIRCRAFT_CATEGORY_E};
|
||||
}
|
||||
|
||||
return {ICAO_AIRCRAFT_CATEGORY_C};
|
||||
}
|
||||
|
||||
void AircraftPerformance::icaoCategoryData()
|
||||
{
|
||||
std::string propCat = fgGetString("/aircraft/performance/icao-category");
|
||||
if (propCat.empty()) {
|
||||
propCat = heuristicCatergoryFromTags();
|
||||
}
|
||||
|
||||
const char aircraftCategory = propCat.front();
|
||||
// pathTurnRate = 3.0; // 3 deg/sec = 180deg/min = standard rate turn
|
||||
switch (aircraftCategory) {
|
||||
case ICAO_AIRCRAFT_CATEGORY_A:
|
||||
_perfData.push_back(Bracket(4000, 600, 1200, 75));
|
||||
_perfData.push_back(Bracket(10000, 600, 1200, 140));
|
||||
break;
|
||||
|
||||
case ICAO_AIRCRAFT_CATEGORY_B:
|
||||
_perfData.push_back(Bracket(4000, 100, 1200, 100));
|
||||
_perfData.push_back(Bracket(10000, 800, 1200, 160));
|
||||
_perfData.push_back(Bracket(18000, 600, 1800, 200));
|
||||
break;
|
||||
|
||||
case ICAO_AIRCRAFT_CATEGORY_C:
|
||||
_perfData.push_back(Bracket(4000, 1800, 1800, 150));
|
||||
_perfData.push_back(Bracket(10000, 1800, 1800, 200));
|
||||
_perfData.push_back(Bracket(18000, 1200, 1800, 270));
|
||||
_perfData.push_back(Bracket(60000, 800, 1200, 0.80, true /* is Mach */));
|
||||
break;
|
||||
|
||||
case ICAO_AIRCRAFT_CATEGORY_D:
|
||||
case ICAO_AIRCRAFT_CATEGORY_E:
|
||||
default:
|
||||
_perfData.push_back(Bracket(4000, 1800, 1800, 180));
|
||||
_perfData.push_back(Bracket(10000, 1800, 1800, 230));
|
||||
_perfData.push_back(Bracket(18000, 1200, 1800, 270));
|
||||
_perfData.push_back(Bracket(60000, 800, 1200, 0.87, true /* is Mach */));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void AircraftPerformance::readPerformanceData()
|
||||
{
|
||||
for (auto nd : fgGetNode("/aircraft/performance/")->getChildren("bracket")) {
|
||||
const int atOrBelowAlt = nd->getIntValue("at-or-below-ft");
|
||||
const int climbFPM = nd->getIntValue("climb-rate-fpm");
|
||||
const int descentFPM = nd->getIntValue("descent-rate-fpm");
|
||||
bool isMach = nd->hasChild("speed-mach");
|
||||
double speed;
|
||||
if (isMach) {
|
||||
speed = nd->getDoubleValue("speed-mach");
|
||||
} else {
|
||||
speed = nd->getIntValue("speed-ias-knots");
|
||||
}
|
||||
|
||||
Bracket b(atOrBelowAlt, climbFPM, descentFPM, speed, isMach);
|
||||
_perfData.push_back(b);
|
||||
}
|
||||
}
|
||||
|
||||
auto AircraftPerformance::bracketForAltitude(int altitude) const
|
||||
-> PerformanceVec::const_iterator
|
||||
{
|
||||
assert(!_perfData.empty());
|
||||
if (_perfData.front().atOrBelowAltitudeFt >= altitude)
|
||||
return _perfData.begin();
|
||||
|
||||
for (auto it = _perfData.begin(); it != _perfData.end(); ++it) {
|
||||
if (it->atOrBelowAltitudeFt > altitude) {
|
||||
return it;
|
||||
}
|
||||
}
|
||||
|
||||
return _perfData.end() - 1;
|
||||
}
|
||||
|
||||
auto AircraftPerformance::rangeForAltitude(int lowAltitude, int highAltitude) const
|
||||
-> BracketRange
|
||||
{
|
||||
return {bracketForAltitude(lowAltitude), bracketForAltitude(highAltitude)};
|
||||
}
|
||||
|
||||
void AircraftPerformance::traverseAltitudeRange(int initialElevationFt, int targetElevationFt,
|
||||
TraversalFunc tf) const
|
||||
{
|
||||
auto r = rangeForAltitude(initialElevationFt, targetElevationFt);
|
||||
if (r.first == r.second) {
|
||||
tf(*r.first, initialElevationFt, targetElevationFt);
|
||||
return;
|
||||
}
|
||||
|
||||
if (initialElevationFt < targetElevationFt) {
|
||||
tf(*r.first, initialElevationFt, r.first->atOrBelowAltitudeFt);
|
||||
int previousBracketCapAltitude = r.first->atOrBelowAltitudeFt;
|
||||
for (auto bracket = r.first + 1; bracket != r.second; ++bracket) {
|
||||
tf(*bracket, previousBracketCapAltitude, bracket->atOrBelowAltitudeFt);
|
||||
previousBracketCapAltitude = bracket->atOrBelowAltitudeFt;
|
||||
}
|
||||
|
||||
tf(*r.second, previousBracketCapAltitude, targetElevationFt);
|
||||
} else {
|
||||
int nextBracketCapAlt = (r.first - 1)->atOrBelowAltitudeFt;
|
||||
tf(*r.first, initialElevationFt, nextBracketCapAlt);
|
||||
for (auto bracket = r.first - 1; bracket != r.second; --bracket) {
|
||||
nextBracketCapAlt = (r.first - 1)->atOrBelowAltitudeFt;
|
||||
tf(*bracket, bracket->atOrBelowAltitudeFt, nextBracketCapAlt);
|
||||
}
|
||||
|
||||
tf(*r.second, nextBracketCapAlt, targetElevationFt);
|
||||
}
|
||||
}
|
||||
|
||||
double AircraftPerformance::distanceNmBetween(int initialElevationFt, int targetElevationFt) const
|
||||
{
|
||||
double result = 0.0;
|
||||
TraversalFunc tf = [&result](const Bracket& bk, int alt1, int alt2) {
|
||||
result += (alt1 > alt2) ? bk.descendDistanceM(alt1, alt2) : bk.climbDistanceM(alt1, alt2);
|
||||
};
|
||||
traverseAltitudeRange(initialElevationFt, targetElevationFt, tf);
|
||||
return result * SG_METER_TO_NM;
|
||||
}
|
||||
|
||||
double AircraftPerformance::timeBetween(int initialElevationFt, int targetElevationFt) const
|
||||
{
|
||||
double result = 0.0;
|
||||
TraversalFunc tf = [&result](const Bracket& bk, int alt1, int alt2) {
|
||||
SG_LOG(SG_GENERAL, SG_INFO, "Range:" << alt1 << " " << alt2);
|
||||
result += (alt1 > alt2) ? bk.descendTime(alt1, alt2) : bk.climbTime(alt1, alt2);
|
||||
};
|
||||
traverseAltitudeRange(initialElevationFt, targetElevationFt, tf);
|
||||
return result;
|
||||
}
|
||||
|
||||
double AircraftPerformance::timeToCruise(double cruiseDistanceNm, int cruiseAltitudeFt) const
|
||||
{
|
||||
auto b = bracketForAltitude(cruiseAltitudeFt);
|
||||
return (cruiseDistanceNm / b->gsForAltitude(cruiseAltitudeFt)) * 3600.0;
|
||||
}
|
||||
|
||||
double oatCForAltitudeFt(int altitudeFt)
|
||||
{
|
||||
if (altitudeFt > 36089)
|
||||
return -56.5;
|
||||
|
||||
// lapse rate in C per ft
|
||||
const double T_r = .0019812;
|
||||
return 15.0 - (altitudeFt * T_r);
|
||||
}
|
||||
|
||||
double oatKForAltitudeFt(int altitudeFt)
|
||||
{
|
||||
return oatCForAltitudeFt(altitudeFt) + 273.15;
|
||||
}
|
||||
|
||||
double pressureAtAltitude(int altitude)
|
||||
{
|
||||
/*
|
||||
p= P_0*(1-6.8755856*10^-6 h)^5.2558797 h<36,089.24ft
|
||||
p_Tr= 0.2233609*P_0
|
||||
p=p_Tr*exp(-4.806346*10^-5(h-36089.24)) h>36,089.24ft
|
||||
|
||||
magic numbers
|
||||
6.8755856*10^-6 = T'/T_0, where T' is the standard temperature lapse rate and T_0 is the standard sea-level temperature.
|
||||
5.2558797 = Mg/RT', where M is the (average) molecular weight of air, g is the acceleration of gravity and R is the gas constant.
|
||||
4.806346*10^-5 = Mg/RT_tr, where T_tr is the temperature at the tropopause.
|
||||
*/
|
||||
|
||||
const double k = 6.8755856e-6;
|
||||
const double MgRT = 5.2558797;
|
||||
const double MgRT_tr = 4.806346e-5;
|
||||
const double P_0 = 29.92126; // (standard) sea-level pressure
|
||||
if (altitude > 36089) {
|
||||
const double P_Tr = 0.2233609 * P_0;
|
||||
const double altAboveTr = altitude - 36089;
|
||||
return P_Tr * exp(MgRT_tr * altAboveTr);
|
||||
} else {
|
||||
return P_0 * pow(1.0 - (k * altitude), MgRT);
|
||||
}
|
||||
}
|
||||
|
||||
double computeMachFromIAS(int iasKnots, int altitudeFt)
|
||||
{
|
||||
#if 0
|
||||
// from the aviation formulary
|
||||
DP=P_0*((1 + 0.2*(IAS/CS_0)^2)^3.5 -1)
|
||||
M=(5*( (DP/P + 1)^(2/7) -1) )^0.5 (*)
|
||||
#endif
|
||||
const double Cs_0 = 661.4786; // speed of sound at sea level, knots
|
||||
const double P_0 = 29.92126; // (standard) sea-level pressure
|
||||
const double iasCsRatio = iasKnots / Cs_0;
|
||||
const double P = pressureAtAltitude(altitudeFt);
|
||||
// differential pressure
|
||||
const double DP = P_0 * (pow(1.0 + 0.2 * pow(iasCsRatio, 2.0), 3.5) - 1.0);
|
||||
|
||||
const double pressureRatio = DP / P + 1.0;
|
||||
const double M = pow(5.0 * (pow(pressureRatio, 2.0 / 7.0) - 1.0), 0.5);
|
||||
if (M > 1.0) {
|
||||
SG_LOG(SG_GENERAL, SG_INFO, "computeMachFromIAS: computed Mach is supersonic, fix for shock wave");
|
||||
}
|
||||
return M;
|
||||
}
|
||||
|
||||
double AircraftPerformance::machForCAS(int altitudeFt, double cas)
|
||||
{
|
||||
return computeMachFromIAS(static_cast<int>(cas), altitudeFt);
|
||||
}
|
||||
|
||||
|
||||
double AircraftPerformance::groundSpeedForCAS(int altitudeFt, double cas)
|
||||
{
|
||||
return groundSpeedForMach(altitudeFt, computeMachFromIAS(cas, altitudeFt));
|
||||
}
|
||||
|
||||
double AircraftPerformance::groundSpeedForMach(int altitudeFt, double mach)
|
||||
{
|
||||
// CS = sound speed= 38.967854*sqrt(T+273.15) where T is the OAT in celsius.
|
||||
const double CS = 38.967854 * sqrt(oatKForAltitudeFt(altitudeFt));
|
||||
const double TAS = mach * CS;
|
||||
return TAS;
|
||||
}
|
||||
|
||||
int AircraftPerformance::Bracket::gsForAltitude(int altitude) const
|
||||
{
|
||||
double M = 0.0;
|
||||
if (speedIsMach) {
|
||||
M = speedIASOrMach; // simple
|
||||
} else {
|
||||
M = computeMachFromIAS(speedIASOrMach, altitude);
|
||||
}
|
||||
|
||||
return groundSpeedForMach(altitude, M);
|
||||
}
|
||||
|
||||
double AircraftPerformance::Bracket::climbTime(int alt1, int alt2) const
|
||||
{
|
||||
return (alt2 - alt1) / static_cast<double>(climbRateFPM) * 60.0;
|
||||
}
|
||||
|
||||
double AircraftPerformance::Bracket::climbDistanceM(int alt1, int alt2) const
|
||||
{
|
||||
const double t = climbTime(alt1, alt2);
|
||||
return distanceForTimeAndSpeeds(t,
|
||||
SG_KT_TO_MPS * gsForAltitude(alt1),
|
||||
SG_KT_TO_MPS * gsForAltitude(alt2));
|
||||
}
|
||||
|
||||
double AircraftPerformance::Bracket::descendTime(int alt1, int alt2) const
|
||||
{
|
||||
return (alt1 - alt2) / static_cast<double>(descentRateFPM) * 60.0;
|
||||
}
|
||||
|
||||
double AircraftPerformance::Bracket::descendDistanceM(int alt1, int alt2) const
|
||||
{
|
||||
const double t = descendTime(alt1, alt2);
|
||||
return distanceForTimeAndSpeeds(t,
|
||||
SG_KT_TO_MPS * gsForAltitude(alt1),
|
||||
SG_KT_TO_MPS * gsForAltitude(alt2));
|
||||
}
|
||||
|
||||
double AircraftPerformance::turnRadiusMForAltitude(int altitudeFt) const
|
||||
{
|
||||
#if 0
|
||||
From the aviation formulary again
|
||||
In a steady turn, in no wind, with bank angle, b at an airspeed v
|
||||
|
||||
tan(b)= v^2/(R g)
|
||||
|
||||
With R in feet, v in knots, b in degrees and w in degrees/sec (inconsistent units!), numerical constants are introduced:
|
||||
|
||||
R =v^2/(11.23*tan(0.01745*b))
|
||||
(Example) At 100 knots, with a 45 degree bank, the radius of turn is 100^2/(11.23*tan(0.01745*45))= 891 feet.
|
||||
|
||||
The bank angle b_s for a standard rate turn is given by:
|
||||
|
||||
b_s = 57.3*atan(v/362.1)
|
||||
(Example) for 100 knots, b_s = 57.3*atan(100/362.1) = 15.4 degrees
|
||||
|
||||
Working in meter-per-second and radians removes a bunch of constants again.
|
||||
#endif
|
||||
const double gsKts = groundSpeedForAltitudeKnots(altitudeFt);
|
||||
const double gs = gsKts * SG_KT_TO_MPS;
|
||||
const double bankAngleRad = atan(gsKts/362.1);
|
||||
const double r = (gs * gs)/(SG_g0_m_p_s2 * tan(bankAngleRad));
|
||||
return r;
|
||||
}
|
||||
|
||||
117
src/Aircraft/AircraftPerformance.hxx
Normal file
117
src/Aircraft/AircraftPerformance.hxx
Normal file
@@ -0,0 +1,117 @@
|
||||
// AircraftPerformance.hxx - compute data about planned acft performance
|
||||
//
|
||||
// Copyright (C) 2018 James Turner <james@flightgear.org>
|
||||
// This program is free software; you can redistribute it and/or
|
||||
// modify it under the terms of the GNU General Public License as
|
||||
// published by the Free Software Foundation; either version 2 of the
|
||||
// License, or (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful, but
|
||||
// WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program; if not, write to the Free Software
|
||||
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
|
||||
#ifndef AIRCRAFTPERFORMANCE_HXX
|
||||
#define AIRCRAFTPERFORMANCE_HXX
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <functional>
|
||||
|
||||
namespace flightgear
|
||||
{
|
||||
|
||||
const char ICAO_AIRCRAFT_CATEGORY_A = 'A';
|
||||
const char ICAO_AIRCRAFT_CATEGORY_B = 'B';
|
||||
const char ICAO_AIRCRAFT_CATEGORY_C = 'C';
|
||||
const char ICAO_AIRCRAFT_CATEGORY_D = 'D';
|
||||
const char ICAO_AIRCRAFT_CATEGORY_E = 'E';
|
||||
|
||||
/**
|
||||
* Calculate flight parameter based on aircraft performance data.
|
||||
* This is based on simple rules: it does not (yet) include data
|
||||
* such as winds aloft, payload or temperature impact on engine
|
||||
* performance. */
|
||||
class AircraftPerformance
|
||||
{
|
||||
public:
|
||||
AircraftPerformance();
|
||||
|
||||
double turnRateDegSec() const;
|
||||
|
||||
double turnRadiusMForAltitude(int altitudeFt) const;
|
||||
|
||||
double groundSpeedForAltitudeKnots(int altitudeFt) const;
|
||||
|
||||
int computePreviousAltitude(double distanceM, int targetAltFt) const;
|
||||
int computeNextAltitude(double distanceM, int initialAltFt) const;
|
||||
|
||||
double distanceNmBetween(int initialElevationFt, int targetElevationFt) const;
|
||||
double timeBetween(int initialElevationFt, int targetElevationFt) const;
|
||||
|
||||
double timeToCruise(double cruiseDistanceNm, int cruiseAltitudeFt) const;
|
||||
|
||||
static double groundSpeedForCAS(int altitudeFt, double cas);
|
||||
static double machForCAS(int altitudeFt, double cas);
|
||||
static double groundSpeedForMach(int altitudeFt, double mach);
|
||||
|
||||
private:
|
||||
void readPerformanceData();
|
||||
|
||||
void icaoCategoryData();
|
||||
|
||||
/**
|
||||
* @brief heuristicCatergoryFromTags - based on the aircraft tags, figure
|
||||
* out a plausible ICAO category. Returns cat A if nothing better could
|
||||
* be determined.
|
||||
* @return a string containing a single ICAO category character A..E
|
||||
*/
|
||||
std::string heuristicCatergoryFromTags() const;
|
||||
|
||||
class Bracket
|
||||
{
|
||||
public:
|
||||
Bracket(int atOrBelow, int climb, int descent, double speed, bool isMach = false) :
|
||||
atOrBelowAltitudeFt(atOrBelow),
|
||||
climbRateFPM(climb),
|
||||
descentRateFPM(descent),
|
||||
speedIASOrMach(speed),
|
||||
speedIsMach(isMach)
|
||||
{ }
|
||||
|
||||
int gsForAltitude(int altitude) const;
|
||||
|
||||
double climbTime(int alt1, int alt2) const;
|
||||
double climbDistanceM(int alt1, int alt2) const;
|
||||
double descendTime(int alt1, int alt2) const;
|
||||
double descendDistanceM(int alt1, int alt2) const;
|
||||
|
||||
int atOrBelowAltitudeFt;
|
||||
int climbRateFPM;
|
||||
int descentRateFPM;
|
||||
double speedIASOrMach;
|
||||
bool speedIsMach = false;
|
||||
};
|
||||
|
||||
using PerformanceVec = std::vector<Bracket>;
|
||||
|
||||
using BracketRange = std::pair<PerformanceVec::const_iterator, PerformanceVec::const_iterator>;
|
||||
|
||||
PerformanceVec::const_iterator bracketForAltitude(int altitude) const;
|
||||
BracketRange rangeForAltitude(int lowAltitude, int highAltitude) const;
|
||||
|
||||
|
||||
using TraversalFunc = std::function<void(const Bracket& bk, int alt1, int alt2)>;
|
||||
void traverseAltitudeRange(int initialElevationFt, int targetElevationFt, TraversalFunc tf) const;
|
||||
|
||||
|
||||
PerformanceVec _perfData;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // AIRCRAFTPERFORMANCE_HXX
|
||||
26
src/Aircraft/CMakeLists.txt
Normal file
26
src/Aircraft/CMakeLists.txt
Normal file
@@ -0,0 +1,26 @@
|
||||
include(FlightGearComponent)
|
||||
|
||||
set(SOURCES
|
||||
controls.cxx
|
||||
replay.cxx
|
||||
flightrecorder.cxx
|
||||
FlightHistory.cxx
|
||||
initialstate.cxx
|
||||
AircraftPerformance.cxx
|
||||
replay-internal.cxx
|
||||
continuous.cxx
|
||||
)
|
||||
|
||||
set(HEADERS
|
||||
controls.hxx
|
||||
replay.hxx
|
||||
flightrecorder.hxx
|
||||
FlightHistory.hxx
|
||||
initialstate.hxx
|
||||
AircraftPerformance.hxx
|
||||
continuous.hxx
|
||||
replay-internal.hxx
|
||||
)
|
||||
|
||||
|
||||
flightgear_component(Aircraft "${SOURCES}" "${HEADERS}")
|
||||
229
src/Aircraft/FlightHistory.cxx
Normal file
229
src/Aircraft/FlightHistory.cxx
Normal file
@@ -0,0 +1,229 @@
|
||||
// FlightHistory
|
||||
//
|
||||
// Written by James Turner, started December 2012.
|
||||
//
|
||||
// Copyright (C) 2012 James Turner - zakalawe (at) mac com
|
||||
//
|
||||
// This program is free software; you can redistribute it and/or
|
||||
// modify it under the terms of the GNU General Public License as
|
||||
// published by the Free Software Foundation; either version 2 of the
|
||||
// License, or (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful, but
|
||||
// WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program; if not, write to the Free Software
|
||||
// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
//
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
# include "config.h"
|
||||
#endif
|
||||
|
||||
#include "FlightHistory.hxx"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include <simgear/sg_inlines.h>
|
||||
#include <simgear/debug/logstream.hxx>
|
||||
#include <simgear/props/props_io.hxx>
|
||||
#include <simgear/misc/strutils.hxx>
|
||||
#include <simgear/structure/exception.hxx>
|
||||
#include <simgear/math/SGMath.hxx>
|
||||
|
||||
#include <Main/fg_props.hxx>
|
||||
#include <Main/globals.hxx>
|
||||
|
||||
FGFlightHistory::FGFlightHistory() :
|
||||
m_sampleInterval(5.0),
|
||||
m_validSampleCount(SAMPLE_BUCKET_WIDTH)
|
||||
{
|
||||
}
|
||||
|
||||
FGFlightHistory::~FGFlightHistory()
|
||||
{
|
||||
}
|
||||
|
||||
void FGFlightHistory::init()
|
||||
{
|
||||
m_enabled = fgGetNode("/sim/history/enabled", true);
|
||||
m_sampleInterval = fgGetDouble("/sim/history/sample-interval-sec", 1.0);
|
||||
if (m_sampleInterval <= 0.0) { // would be bad
|
||||
SG_LOG(SG_FLIGHT, SG_INFO, "invalid flight-history sample interval:" << m_sampleInterval
|
||||
<< ", defaulting to " << m_sampleInterval);
|
||||
m_sampleInterval = 1.0;
|
||||
}
|
||||
|
||||
// cap memory use at 4MB
|
||||
m_maxMemoryUseBytes = fgGetInt("/sim/history/max-memory-use-bytes", 1024 * 1024 * 4);
|
||||
m_weightOnWheels = NULL;
|
||||
// reset the history when we detect a take-off
|
||||
if (fgGetBool("/sim/history/clear-on-takeoff", true)) {
|
||||
m_weightOnWheels = fgGetNode("/gear/gear[1]/wow", 0, true);
|
||||
m_lastWoW = m_weightOnWheels->getBoolValue();
|
||||
}
|
||||
|
||||
// force bucket re-allocation
|
||||
m_validSampleCount = SAMPLE_BUCKET_WIDTH;
|
||||
m_lastCaptureTime = globals->get_sim_time_sec();
|
||||
}
|
||||
|
||||
void FGFlightHistory::shutdown()
|
||||
{
|
||||
clear();
|
||||
}
|
||||
|
||||
void FGFlightHistory::reinit()
|
||||
{
|
||||
shutdown();
|
||||
init();
|
||||
}
|
||||
|
||||
void FGFlightHistory::update(double dt)
|
||||
{
|
||||
if ((dt == 0.0) || !m_enabled->getBoolValue()) {
|
||||
return; // paused or disabled
|
||||
}
|
||||
|
||||
if (m_weightOnWheels) {
|
||||
|
||||
if (m_lastWoW && !m_weightOnWheels->getBoolValue()) {
|
||||
SG_LOG(SG_FLIGHT, SG_INFO, "history: detected main-gear takeoff, clearing history");
|
||||
clear();
|
||||
}
|
||||
} // of rest-on-takeoff enabled
|
||||
|
||||
// spatial check - moved at least 1m since last capture
|
||||
if (!m_buckets.empty()) {
|
||||
SGVec3d lastCaptureCart(SGVec3d::fromGeod(m_buckets.back()->samples[m_validSampleCount - 1].position));
|
||||
double d2 = distSqr(lastCaptureCart, globals->get_aircraft_position_cart());
|
||||
if (d2 <= 1.0) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
double elapsed = globals->get_sim_time_sec() - m_lastCaptureTime;
|
||||
if (elapsed > m_sampleInterval) {
|
||||
capture();
|
||||
}
|
||||
}
|
||||
|
||||
void FGFlightHistory::allocateNewBucket()
|
||||
{
|
||||
SampleBucket* bucket = NULL;
|
||||
if (!m_buckets.empty() && (currentMemoryUseBytes() > m_maxMemoryUseBytes)) {
|
||||
bucket = m_buckets.front();
|
||||
m_buckets.erase(m_buckets.begin());
|
||||
} else {
|
||||
bucket = new SampleBucket;
|
||||
}
|
||||
|
||||
m_buckets.push_back(bucket);
|
||||
m_validSampleCount = 0;
|
||||
}
|
||||
|
||||
void FGFlightHistory::capture()
|
||||
{
|
||||
if (m_validSampleCount == SAMPLE_BUCKET_WIDTH) {
|
||||
// bucket is full, allocate a new one
|
||||
allocateNewBucket();
|
||||
}
|
||||
|
||||
m_lastCaptureTime = globals->get_sim_time_sec();
|
||||
Sample* sample = m_buckets.back()->samples + m_validSampleCount;
|
||||
|
||||
sample->simTimeMSec = static_cast<size_t>(m_lastCaptureTime * 1000.0);
|
||||
sample->position = globals->get_aircraft_position();
|
||||
|
||||
double heading, pitch, roll;
|
||||
globals->get_aircraft_orientation(heading, pitch, roll);
|
||||
sample->heading = static_cast<float>(heading);
|
||||
sample->pitch = static_cast<float>(pitch);
|
||||
sample->roll = static_cast<float>(roll);
|
||||
|
||||
++m_validSampleCount;
|
||||
}
|
||||
|
||||
PagedPathForHistory_ptr FGFlightHistory::pagedPathForHistory(size_t max_entries, size_t newerThan ) const
|
||||
{
|
||||
PagedPathForHistory_ptr result = new PagedPathForHistory();
|
||||
if (m_buckets.empty()) {
|
||||
return result;
|
||||
}
|
||||
|
||||
for (auto bucket : m_buckets) {
|
||||
unsigned int count = (bucket == m_buckets.back() ? m_validSampleCount : SAMPLE_BUCKET_WIDTH);
|
||||
|
||||
// iterate over all the valid samples in the bucket
|
||||
for (unsigned int index = 0; index < count; ++index) {
|
||||
// skip older entries
|
||||
// TODO: bisect!
|
||||
if( bucket->samples[index].simTimeMSec <= newerThan )
|
||||
continue;
|
||||
|
||||
if( max_entries ) {
|
||||
max_entries--;
|
||||
SGGeod g = bucket->samples[index].position;
|
||||
result->path.push_back(g);
|
||||
result->last_seen = bucket->samples[index].simTimeMSec;
|
||||
} else {
|
||||
goto exit;
|
||||
}
|
||||
|
||||
} // of samples iteration
|
||||
} // of buckets iteration
|
||||
|
||||
exit:
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
SGGeodVec FGFlightHistory::pathForHistory(double minEdgeLengthM) const
|
||||
{
|
||||
SGGeodVec result;
|
||||
if (m_buckets.empty()) {
|
||||
return result;
|
||||
}
|
||||
|
||||
result.push_back(m_buckets.front()->samples[0].position);
|
||||
SGVec3d lastOutputCart = SGVec3d::fromGeod(result.back());
|
||||
double minLengthSqr = minEdgeLengthM * minEdgeLengthM;
|
||||
|
||||
for (auto bucket : m_buckets) {
|
||||
unsigned int count = (bucket == m_buckets.back() ? m_validSampleCount : SAMPLE_BUCKET_WIDTH);
|
||||
|
||||
// iterate over all the valid samples in the bucket
|
||||
for (unsigned int index = 0; index < count; ++index) {
|
||||
SGGeod g = bucket->samples[index].position;
|
||||
SGVec3d cart(SGVec3d::fromGeod(g));
|
||||
if (distSqr(cart, lastOutputCart) > minLengthSqr) {
|
||||
lastOutputCart = cart;
|
||||
result.push_back(g);
|
||||
}
|
||||
} // of samples iteration
|
||||
} // of buckets iteration
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void FGFlightHistory::clear()
|
||||
{
|
||||
for (auto ptr : m_buckets) {
|
||||
delete ptr;
|
||||
}
|
||||
m_buckets.clear();
|
||||
m_validSampleCount = SAMPLE_BUCKET_WIDTH;
|
||||
}
|
||||
|
||||
size_t FGFlightHistory::currentMemoryUseBytes() const
|
||||
{
|
||||
return sizeof(SampleBucket) * m_buckets.size();
|
||||
}
|
||||
|
||||
|
||||
// Register the subsystem.
|
||||
SGSubsystemMgr::Registrant<FGFlightHistory> registrantFGFlightHistory;
|
||||
132
src/Aircraft/FlightHistory.hxx
Normal file
132
src/Aircraft/FlightHistory.hxx
Normal file
@@ -0,0 +1,132 @@
|
||||
// FlightHistory
|
||||
//
|
||||
// Written by James Turner, started December 2012.
|
||||
//
|
||||
// Copyright (C) 2012 James Turner - zakalawe (at) mac com
|
||||
//
|
||||
// This program is free software; you can redistribute it and/or
|
||||
// modify it under the terms of the GNU General Public License as
|
||||
// published by the Free Software Foundation; either version 2 of the
|
||||
// License, or (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful, but
|
||||
// WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program; if not, write to the Free Software
|
||||
// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
//
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#ifndef FG_AIRCRAFT_FLIGHT_HISTORY_HXX
|
||||
#define FG_AIRCRAFT_FLIGHT_HISTORY_HXX
|
||||
|
||||
#include <simgear/structure/subsystem_mgr.hxx>
|
||||
#include <simgear/props/props.hxx>
|
||||
#include <simgear/math/SGMath.hxx>
|
||||
|
||||
#include <vector>
|
||||
|
||||
typedef std::vector<SGGeod> SGGeodVec;
|
||||
|
||||
class PagedPathForHistory : public SGReferenced
|
||||
{
|
||||
public:
|
||||
PagedPathForHistory() : last_seen(0) {}
|
||||
virtual ~PagedPathForHistory() {}
|
||||
SGGeodVec path;
|
||||
time_t last_seen;
|
||||
};
|
||||
|
||||
typedef SGSharedPtr<PagedPathForHistory> PagedPathForHistory_ptr;
|
||||
|
||||
const unsigned int SAMPLE_BUCKET_WIDTH = 1024;
|
||||
|
||||
/**
|
||||
* record the history of the aircraft's movements, making it available
|
||||
* as a contiguous block. This can be used to show the historical flight-path
|
||||
* over a long period of time (unlike the replay system), but only a small,
|
||||
* fixed set of properties are recorded. (Positioned and orientation, but
|
||||
* not velocity, acceleration, control inputs, or so on)
|
||||
*/
|
||||
class FGFlightHistory : public SGSubsystem
|
||||
{
|
||||
public:
|
||||
FGFlightHistory();
|
||||
virtual ~FGFlightHistory();
|
||||
|
||||
// Subsystem API.
|
||||
void init() override;
|
||||
void reinit() override;
|
||||
void shutdown() override;
|
||||
void update(double dt) override;
|
||||
|
||||
// Subsystem identification.
|
||||
static const char* staticSubsystemClassId() { return "history"; }
|
||||
|
||||
PagedPathForHistory_ptr pagedPathForHistory(size_t max_entries, size_t newerThan = 0) const;
|
||||
/**
|
||||
* retrieve the path, collapsing segments shorter than
|
||||
* the specified minimum length
|
||||
*/
|
||||
SGGeodVec pathForHistory(double minEdgeLengthM = 50.0) const;
|
||||
|
||||
/**
|
||||
* clear the history
|
||||
*/
|
||||
|
||||
void clear();
|
||||
|
||||
private:
|
||||
/**
|
||||
* @class A single data sample in the history system.
|
||||
*/
|
||||
class Sample
|
||||
{
|
||||
public:
|
||||
SGGeod position;
|
||||
/// heading, pitch and roll can be recorded at lower precision
|
||||
/// than a double - actually 16 bits might be sufficient
|
||||
float heading, pitch, roll;
|
||||
size_t simTimeMSec;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Bucket is a fixed-size container of samples. This is a crude slab
|
||||
* allocation of samples, in chunks defined by the width constant above.
|
||||
* Keep in mind that even with a 1Hz sample frequency, we use less than
|
||||
* 200kbytes per hour - avoiding continous malloc traffic, or expensive
|
||||
* std::vector reallocations, is the key factor here.
|
||||
*/
|
||||
class SampleBucket
|
||||
{
|
||||
public:
|
||||
Sample samples[SAMPLE_BUCKET_WIDTH];
|
||||
};
|
||||
|
||||
double m_lastCaptureTime;
|
||||
double m_sampleInterval; ///< sample interval in seconds
|
||||
/// our store of samples (in buckets). The last bucket is partially full,
|
||||
/// with the number of valid samples indicated by m_validSampleCount
|
||||
std::vector<SampleBucket*> m_buckets;
|
||||
|
||||
/// number of valid samples in the final bucket
|
||||
unsigned int m_validSampleCount;
|
||||
|
||||
SGPropertyNode_ptr m_weightOnWheels;
|
||||
SGPropertyNode_ptr m_enabled;
|
||||
|
||||
bool m_lastWoW;
|
||||
size_t m_maxMemoryUseBytes;
|
||||
|
||||
void allocateNewBucket();
|
||||
|
||||
void capture();
|
||||
|
||||
size_t currentMemoryUseBytes() const;
|
||||
};
|
||||
|
||||
#endif
|
||||
987
src/Aircraft/continuous.cxx
Normal file
987
src/Aircraft/continuous.cxx
Normal file
@@ -0,0 +1,987 @@
|
||||
#include "continuous.hxx"
|
||||
|
||||
#include <Aircraft/flightrecorder.hxx>
|
||||
#include <Main/fg_props.hxx>
|
||||
#include <MultiPlayer/mpmessages.hxx>
|
||||
#include <Viewer/FGEventHandler.hxx>
|
||||
#include <Viewer/renderer.hxx>
|
||||
#include <Viewer/viewmgr.hxx>
|
||||
|
||||
#include <simgear/io/iostreams/zlibstream.hxx>
|
||||
#include <simgear/props/props_io.hxx>
|
||||
#include <simgear/structure/commands.hxx>
|
||||
|
||||
#include <osgViewer/ViewerBase>
|
||||
|
||||
#include <assert.h>
|
||||
#include <string.h>
|
||||
|
||||
|
||||
Continuous::Continuous(std::shared_ptr<FGFlightRecorder> flight_recorder)
|
||||
:
|
||||
m_flight_recorder(flight_recorder)
|
||||
{
|
||||
SGPropertyNode* record_continuous = fgGetNode("/sim/replay/record-continuous", true);
|
||||
SGPropertyNode* fdm_initialized = fgGetNode("/sim/signals/fdm-initialized", true);
|
||||
record_continuous->addChangeListener(this, true /*initial*/);
|
||||
fdm_initialized->addChangeListener(this, true /*initial*/);
|
||||
}
|
||||
|
||||
// Reads binary data from a stream into an instance of a type.
|
||||
template<typename T>
|
||||
static void readRaw(std::istream& in, T& data)
|
||||
{
|
||||
in.read(reinterpret_cast<char*>(&data), sizeof(data));
|
||||
}
|
||||
|
||||
// Writes instance of a type as binary data to a stream.
|
||||
template<typename T>
|
||||
static void writeRaw(std::ostream& out, const T& data)
|
||||
{
|
||||
out.write(reinterpret_cast<const char*>(&data), sizeof(data));
|
||||
}
|
||||
|
||||
// Reads uncompressed vector<char> from file. Throws if length field is longer
|
||||
// than <max_length>.
|
||||
template<typename SizeType>
|
||||
static SizeType VectorRead(std::istream& in, std::vector<char>& out, uint32_t max_length=(1u << 31))
|
||||
{
|
||||
SizeType length;
|
||||
readRaw(in, length);
|
||||
if (sizeof(length) + length > max_length)
|
||||
{
|
||||
SG_LOG(SG_SYSTEMS, SG_ALERT, "recording data vector too long."
|
||||
<< " max_length=" << max_length
|
||||
<< " sizeof(length)=" << sizeof(length)
|
||||
<< " length=" << length
|
||||
);
|
||||
throw std::runtime_error("Failed to read vector in recording");
|
||||
}
|
||||
out.resize(length);
|
||||
in.read(&out.front(), length);
|
||||
return sizeof(length) + length;
|
||||
}
|
||||
|
||||
static int16_t read_int16(std::istream& in, size_t& pos)
|
||||
{
|
||||
int16_t a;
|
||||
readRaw(in, a);
|
||||
pos += sizeof(a);
|
||||
return a;
|
||||
}
|
||||
static std::string read_string(std::istream& in, size_t& pos)
|
||||
{
|
||||
int16_t length = read_int16(in, pos);
|
||||
std::vector<char> path(length);
|
||||
in.read(&path[0], length);
|
||||
pos += length;
|
||||
std::string ret(&path[0], length);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int PropertiesWrite(SGPropertyNode* root, std::ostream& out)
|
||||
{
|
||||
stringstream buffer;
|
||||
writeProperties(buffer, root, true /*write_all*/);
|
||||
uint32_t buffer_len = buffer.str().size() + 1;
|
||||
writeRaw(out, buffer_len);
|
||||
out.write(buffer.str().c_str(), buffer_len);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Reads extra-property change items in next <length> bytes. Throws if we don't
|
||||
// exactly read <length> bytes.
|
||||
static void ReadFGReplayDataExtraProperties(std::istream& in, FGReplayData* replay_data, uint32_t length)
|
||||
{
|
||||
SG_LOG(SG_SYSTEMS, SG_BULK, "reading extra-properties. length=" << length);
|
||||
size_t pos=0;
|
||||
for(;;)
|
||||
{
|
||||
if (pos == length)
|
||||
{
|
||||
break;
|
||||
}
|
||||
if (pos > length)
|
||||
{
|
||||
SG_LOG(SG_SYSTEMS, SG_ALERT, "Overrun while reading extra-properties:"
|
||||
" length=" << length << ": pos=" << pos);
|
||||
in.setstate(std::ios_base::failbit);
|
||||
break;
|
||||
}
|
||||
SG_LOG(SG_SYSTEMS, SG_BULK, "length=" << length<< " pos=" << pos);
|
||||
std::string path = read_string(in, pos);
|
||||
if (path == "")
|
||||
{
|
||||
path = read_string(in, pos);
|
||||
SG_LOG(SG_SYSTEMS, SG_DEBUG, "property deleted: " << path);
|
||||
replay_data->replay_extra_property_removals.push_back(path);
|
||||
}
|
||||
else
|
||||
{
|
||||
std::string value = read_string(in, pos);
|
||||
SG_LOG(SG_SYSTEMS, SG_DEBUG, "property changed: " << path << "=" << value);
|
||||
replay_data->replay_extra_property_changes[path] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static bool ReadFGReplayData2(
|
||||
std::istream& in,
|
||||
SGPropertyNode* config,
|
||||
bool load_signals,
|
||||
bool load_multiplayer,
|
||||
bool load_extra_properties,
|
||||
FGReplayData* ret
|
||||
)
|
||||
{
|
||||
ret->raw_data.resize(0);
|
||||
for (auto data: config->getChildren("data"))
|
||||
{
|
||||
std::string data_type = data->getStringValue();
|
||||
SG_LOG(SG_SYSTEMS, SG_BULK, "in.tellg()=" << in.tellg() << " data_type=" << data_type);
|
||||
uint32_t length;
|
||||
readRaw(in, length);
|
||||
SG_LOG(SG_SYSTEMS, SG_DEBUG, "length=" << length);
|
||||
if (!in) break;
|
||||
if (load_signals && data_type == "signals")
|
||||
{
|
||||
ret->raw_data.resize(length);
|
||||
in.read(&ret->raw_data.front(), ret->raw_data.size());
|
||||
}
|
||||
else if (load_multiplayer && data_type == "multiplayer")
|
||||
{
|
||||
/* Multiplayer information is a vector of vectors. */
|
||||
ret->multiplayer_messages.clear();
|
||||
uint32_t pos = 0;
|
||||
for(;;)
|
||||
{
|
||||
assert(pos <= length);
|
||||
if (pos == length) break;
|
||||
std::shared_ptr<std::vector<char>> v(new std::vector<char>);
|
||||
ret->multiplayer_messages.push_back(v);
|
||||
pos += VectorRead<uint16_t>(in, *ret->multiplayer_messages.back(), length - pos);
|
||||
SG_LOG(SG_SYSTEMS, SG_BULK, "replaying multiplayer data"
|
||||
<< " ret->sim_time=" << ret->sim_time
|
||||
<< " length=" << length
|
||||
<< " pos=" << pos
|
||||
<< " callsign=" << ((T_MsgHdr*) &v->front())->Callsign
|
||||
);
|
||||
}
|
||||
}
|
||||
else if (load_extra_properties && data_type == "extra-properties")
|
||||
{
|
||||
ReadFGReplayDataExtraProperties(in, ret, length);
|
||||
}
|
||||
else
|
||||
{
|
||||
SG_LOG(SG_GENERAL, SG_BULK, "Skipping unrecognised/unwanted data: " << data_type);
|
||||
in.seekg(length, std::ios_base::cur);
|
||||
}
|
||||
if (!in) break;
|
||||
}
|
||||
if (!in)
|
||||
{
|
||||
SG_LOG(SG_SYSTEMS, SG_DEBUG, "Failed to read fgtape data");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/* Removes items more than <n> away from <it>. <n> can be -ve. */
|
||||
template<typename Container, typename Iterator>
|
||||
static void remove_far_away(Container& container, Iterator it, int n)
|
||||
{
|
||||
SG_LOG(SG_GENERAL, SG_DEBUG, "container.size()=" << container.size());
|
||||
if (n > 0)
|
||||
{
|
||||
for (int i=0; i<n; ++i)
|
||||
{
|
||||
if (it == container.end()) return;
|
||||
++it;
|
||||
}
|
||||
container.erase(it, container.end());
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int i=0; i<-n-1; ++i)
|
||||
{
|
||||
if (it == container.begin()) return;
|
||||
--it;
|
||||
}
|
||||
container.erase(container.begin(), it);
|
||||
}
|
||||
SG_LOG(SG_GENERAL, SG_DEBUG, "container.size()=" << container.size());
|
||||
}
|
||||
|
||||
/* Returns FGReplayData for frame at specified position in file. Uses
|
||||
continuous.m_in_pos_to_frame as a cache, and trims this cache using
|
||||
remove_far_away(). */
|
||||
static std::shared_ptr<FGReplayData> ReadFGReplayData(
|
||||
Continuous& continuous,
|
||||
std::ifstream& in,
|
||||
size_t pos,
|
||||
SGPropertyNode* config,
|
||||
bool load_signals,
|
||||
bool load_multiplayer,
|
||||
bool load_extra_properties,
|
||||
int in_compression
|
||||
)
|
||||
{
|
||||
std::shared_ptr<FGReplayData> ret;
|
||||
auto it = continuous.m_in_pos_to_frame.find(pos);
|
||||
|
||||
if (it != continuous.m_in_pos_to_frame.end())
|
||||
{
|
||||
if (0
|
||||
|| (load_signals && !it->second->load_signals)
|
||||
|| (load_multiplayer && !it->second->load_multiplayer)
|
||||
|| (load_extra_properties && !it->second->load_extra_properties)
|
||||
)
|
||||
{
|
||||
/* This frame is in the continuous.m_in_pos_to_frame cache, but
|
||||
doesn't contain all of the required items, so we need to reload. */
|
||||
continuous.m_in_pos_to_frame.erase(it);
|
||||
it = continuous.m_in_pos_to_frame.end();
|
||||
}
|
||||
}
|
||||
if (it == continuous.m_in_pos_to_frame.end())
|
||||
{
|
||||
/* Load FGReplayData at offset <pos>.
|
||||
|
||||
We need to clear any eof bit, otherwise seekg() will not work (which is
|
||||
pretty unhelpful). E.g. see:
|
||||
https://stackoverflow.com/questions/16364301/whats-wrong-with-the-ifstream-seekg
|
||||
*/
|
||||
SG_LOG(SG_SYSTEMS, SG_BULK, "reading frame. pos=" << pos);
|
||||
in.clear();
|
||||
in.seekg(pos);
|
||||
|
||||
ret.reset(new FGReplayData);
|
||||
|
||||
readRaw(in, ret->sim_time);
|
||||
if (!in)
|
||||
{
|
||||
SG_LOG(SG_SYSTEMS, SG_DEBUG, "Failed to read fgtape frame at offset " << pos);
|
||||
return nullptr;
|
||||
}
|
||||
bool ok;
|
||||
if (in_compression)
|
||||
{
|
||||
uint8_t flags;
|
||||
uint32_t compressed_size;
|
||||
in.read((char*) &flags, sizeof(flags));
|
||||
in.read((char*) &compressed_size, sizeof(compressed_size));
|
||||
simgear::ZlibDecompressorIStream in_decompress(in, SGPath(), simgear::ZLibCompressionFormat::ZLIB_RAW);
|
||||
ok = ReadFGReplayData2(in_decompress, config, load_signals, load_multiplayer, load_extra_properties, ret.get());
|
||||
}
|
||||
else
|
||||
{
|
||||
ok = ReadFGReplayData2(in, config, load_signals, load_multiplayer, load_extra_properties, ret.get());
|
||||
}
|
||||
if (!ok)
|
||||
{
|
||||
SG_LOG(SG_SYSTEMS, SG_DEBUG, "Failed to read fgtape frame at offset " << pos);
|
||||
return nullptr;
|
||||
}
|
||||
it = continuous.m_in_pos_to_frame.lower_bound(pos);
|
||||
it = continuous.m_in_pos_to_frame.insert(it, std::make_pair(pos, ret));
|
||||
|
||||
/* Delete faraway items. */
|
||||
size_t size_old = continuous.m_in_pos_to_frame.size();
|
||||
int n = 2;
|
||||
size_t size_max = 2*n - 1;
|
||||
remove_far_away(continuous.m_in_pos_to_frame, it, n);
|
||||
remove_far_away(continuous.m_in_pos_to_frame, it, -n);
|
||||
size_t size_new = continuous.m_in_pos_to_frame.size();
|
||||
SG_LOG(SG_GENERAL, SG_DEBUG, ""
|
||||
<< " n=" << size_old
|
||||
<< " size_max=" << size_max
|
||||
<< " size_old=" << size_old
|
||||
<< " size_new=" << size_new
|
||||
);
|
||||
assert(size_new <= size_max);
|
||||
}
|
||||
else
|
||||
{
|
||||
ret = it->second;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
// streambuf that compresses using deflate().
|
||||
struct compression_streambuf : std::streambuf
|
||||
{
|
||||
compression_streambuf(
|
||||
std::ostream& out,
|
||||
size_t buffer_uncompressed_size,
|
||||
size_t buffer_compressed_size
|
||||
)
|
||||
:
|
||||
std::streambuf(),
|
||||
out(out),
|
||||
buffer_uncompressed(new char[buffer_uncompressed_size]),
|
||||
buffer_uncompressed_size(buffer_uncompressed_size),
|
||||
buffer_compressed(new char[buffer_compressed_size]),
|
||||
buffer_compressed_size(buffer_compressed_size)
|
||||
{
|
||||
zstream.zalloc = nullptr;
|
||||
zstream.zfree = nullptr;
|
||||
zstream.opaque = nullptr;
|
||||
|
||||
zstream.next_in = nullptr;
|
||||
zstream.avail_in = 0;
|
||||
|
||||
zstream.next_out = (unsigned char*) &buffer_compressed[0];
|
||||
zstream.avail_out = buffer_compressed_size;
|
||||
|
||||
int e = deflateInit2(
|
||||
&zstream,
|
||||
Z_DEFAULT_COMPRESSION,
|
||||
Z_DEFLATED,
|
||||
-15 /*windowBits*/,
|
||||
8 /*memLevel*/,
|
||||
Z_DEFAULT_STRATEGY
|
||||
);
|
||||
if (e != Z_OK)
|
||||
{
|
||||
throw std::runtime_error("deflateInit2() failed");
|
||||
}
|
||||
// We leave space for one character to simplify overflow().
|
||||
setp(&buffer_uncompressed[0], &buffer_uncompressed[0] + buffer_uncompressed_size - 1);
|
||||
}
|
||||
|
||||
// Flush compressed data to .out and reset zstream.next_out.
|
||||
void _flush()
|
||||
{
|
||||
// Send all data in .buffer_compressed to .out.
|
||||
size_t n = (char*) zstream.next_out - &buffer_compressed[0];
|
||||
out.write(&buffer_compressed[0], n);
|
||||
zstream.next_out = (unsigned char*) &buffer_compressed[0];
|
||||
zstream.avail_out = buffer_compressed_size;
|
||||
}
|
||||
|
||||
// Compresses specified bytes from buffer_uncompressed into
|
||||
// buffer_compressed, flushing to .out as necessary. Returns true if we get
|
||||
// EOF writing to .out.
|
||||
bool _deflate(size_t n, bool flush)
|
||||
{
|
||||
assert(this->pbase() == &buffer_uncompressed[0]);
|
||||
zstream.next_in = (unsigned char*) &buffer_uncompressed[0];
|
||||
zstream.avail_in = n;
|
||||
for(;;)
|
||||
{
|
||||
if (!flush && !zstream.avail_in) break;
|
||||
if (!zstream.avail_out) _flush();
|
||||
int e = deflate(&zstream, (!zstream.avail_in && flush) ? Z_FINISH : Z_NO_FLUSH);
|
||||
if (e != Z_OK && e != Z_STREAM_END)
|
||||
{
|
||||
throw std::runtime_error("zip_deflate() failed");
|
||||
}
|
||||
if (e == Z_STREAM_END) break;
|
||||
}
|
||||
if (flush) _flush();
|
||||
// We leave space for one character to simplify overflow().
|
||||
setp(&buffer_uncompressed[0], &buffer_uncompressed[0] + buffer_uncompressed_size - 1);
|
||||
if (!out) return true; // EOF.
|
||||
return false;
|
||||
}
|
||||
|
||||
int overflow(int c) override
|
||||
{
|
||||
// We've deliberately left space for one character, into which we write <c>.
|
||||
assert(this->pptr() == &buffer_uncompressed[0] + buffer_uncompressed_size - 1);
|
||||
*this->pptr() = (char) c;
|
||||
if (_deflate(buffer_uncompressed_size, false /*flush*/)) return EOF;
|
||||
return c;
|
||||
}
|
||||
|
||||
int sync() override
|
||||
{
|
||||
_deflate(pptr() - &buffer_uncompressed[0], true /*flush*/);
|
||||
return 0;
|
||||
}
|
||||
|
||||
~compression_streambuf()
|
||||
{
|
||||
deflateEnd(&zstream);
|
||||
}
|
||||
|
||||
std::ostream& out;
|
||||
z_stream zstream;
|
||||
std::unique_ptr<char[]> buffer_uncompressed;
|
||||
size_t buffer_uncompressed_size;
|
||||
std::unique_ptr<char[]> buffer_compressed;
|
||||
size_t buffer_compressed_size;
|
||||
};
|
||||
|
||||
|
||||
// Accepts uncompressed data via .write(), operator<< etc, and writes
|
||||
// compressed data to the supplied std::ostream.
|
||||
struct compression_ostream : std::ostream
|
||||
{
|
||||
compression_ostream(
|
||||
std::ostream& out,
|
||||
size_t buffer_uncompressed_size,
|
||||
size_t buffer_compressed_size
|
||||
)
|
||||
:
|
||||
std::ostream(&streambuf),
|
||||
streambuf(out, buffer_uncompressed_size, buffer_compressed_size)
|
||||
{
|
||||
}
|
||||
|
||||
compression_streambuf streambuf;
|
||||
};
|
||||
|
||||
|
||||
static void writeFrame2(FGReplayData* r, std::ostream& out, SGPropertyNode_ptr config)
|
||||
{
|
||||
for (auto data: config->getChildren("data"))
|
||||
{
|
||||
std::string data_type = data->getStringValue();
|
||||
if (data_type == "signals")
|
||||
{
|
||||
uint32_t signals_size = r->raw_data.size();
|
||||
writeRaw(out, signals_size);
|
||||
out.write(&r->raw_data.front(), r->raw_data.size());
|
||||
}
|
||||
else if (data_type == "multiplayer")
|
||||
{
|
||||
uint32_t length = 0;
|
||||
for (auto message: r->multiplayer_messages)
|
||||
{
|
||||
length += sizeof(uint16_t) + message->size();
|
||||
}
|
||||
SG_LOG(SG_SYSTEMS, SG_DEBUG, "data_type=" << data_type << " out.tellp()=" << out.tellp()
|
||||
<< " length=" << length);
|
||||
writeRaw(out, length);
|
||||
for (auto message: r->multiplayer_messages)
|
||||
{
|
||||
uint16_t message_size = message->size();
|
||||
writeRaw(out, message_size);
|
||||
out.write(&message->front(), message_size);
|
||||
}
|
||||
}
|
||||
else if (data_type == "extra-properties")
|
||||
{
|
||||
uint32_t length = r->extra_properties.size();
|
||||
SG_LOG(SG_SYSTEMS, SG_DEBUG, "data_type=" << data_type << " out.tellp()=" << out.tellp()
|
||||
<< " length=" << length);
|
||||
writeRaw(out, length);
|
||||
out.write(&r->extra_properties[0], length);
|
||||
}
|
||||
else
|
||||
{
|
||||
SG_LOG(SG_SYSTEMS, SG_ALERT, "unrecognised data_type=" << data_type);
|
||||
assert(0);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
bool continuousWriteFrame(
|
||||
Continuous& continuous,
|
||||
FGReplayData* r,
|
||||
std::ostream& out,
|
||||
SGPropertyNode_ptr config,
|
||||
FGTapeType tape_type
|
||||
)
|
||||
{
|
||||
SG_LOG(SG_SYSTEMS, SG_BULK, "writing frame."
|
||||
<< " out.tellp()=" << out.tellp()
|
||||
<< " r->sim_time=" << r->sim_time
|
||||
);
|
||||
// Don't write frame if no data to write.
|
||||
//bool r_has_data = false;
|
||||
bool has_signals = false;
|
||||
bool has_multiplayer = false;
|
||||
bool has_extra_properties = false;
|
||||
for (auto data: config->getChildren("data"))
|
||||
{
|
||||
std::string data_type = data->getStringValue();
|
||||
if (data_type == "signals")
|
||||
{
|
||||
has_signals = true;
|
||||
}
|
||||
else if (data_type == "multiplayer")
|
||||
{
|
||||
if (!r->multiplayer_messages.empty())
|
||||
{
|
||||
has_multiplayer = true;
|
||||
}
|
||||
}
|
||||
else if (data_type == "extra-properties")
|
||||
{
|
||||
if (!r->extra_properties.empty())
|
||||
{
|
||||
has_extra_properties = true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
SG_LOG(SG_SYSTEMS, SG_ALERT, "unrecognised data_type=" << data_type);
|
||||
assert(0);
|
||||
}
|
||||
}
|
||||
if (!has_signals && !has_multiplayer && !has_extra_properties)
|
||||
{
|
||||
SG_LOG(SG_SYSTEMS, SG_DEBUG, "Not writing frame because no data to write");
|
||||
return true;
|
||||
}
|
||||
|
||||
writeRaw(out, r->sim_time);
|
||||
|
||||
if (tape_type == FGTapeType_CONTINUOUS && continuous.m_out_compression)
|
||||
{
|
||||
uint8_t flags = 0;
|
||||
if (has_signals) flags |= 1;
|
||||
if (has_multiplayer) flags |= 2;
|
||||
if (has_extra_properties) flags |= 4;
|
||||
out.write((char*) &flags, sizeof(flags));
|
||||
|
||||
/* We need to first write the size of the compressed data so compress
|
||||
to a temporary ostringstream first. */
|
||||
std::ostringstream compressed;
|
||||
compression_ostream out_compressing(compressed, 1024, 1024);
|
||||
writeFrame2(r, out_compressing, config);
|
||||
out_compressing.flush();
|
||||
|
||||
uint32_t compressed_size = compressed.str().size();
|
||||
out.write((char*) &compressed_size, sizeof(compressed_size));
|
||||
out.write((char*) compressed.str().c_str(), compressed.str().size());
|
||||
}
|
||||
else
|
||||
{
|
||||
writeFrame2(r, out, config);
|
||||
}
|
||||
bool ok = true;
|
||||
if (!out) ok = false;
|
||||
return ok;
|
||||
}
|
||||
|
||||
SGPropertyNode_ptr continuousWriteHeader(
|
||||
Continuous& continuous,
|
||||
FGFlightRecorder* flight_recorder,
|
||||
std::ofstream& out,
|
||||
const SGPath& path,
|
||||
FGTapeType tape_type
|
||||
)
|
||||
{
|
||||
continuous.m_out_compression = fgGetInt("/sim/replay/record-continuous-compression");
|
||||
SGPropertyNode_ptr config = saveSetup(NULL /*Extra*/, path, 0 /*Duration*/,
|
||||
tape_type, continuous.m_out_compression);
|
||||
SGPropertyNode* signals = config->getNode("signals", true /*create*/);
|
||||
flight_recorder->getConfig(signals);
|
||||
|
||||
out.open(path.c_str(), std::ofstream::binary | std::ofstream::trunc);
|
||||
out.write(FlightRecorderFileMagic, strlen(FlightRecorderFileMagic)+1);
|
||||
PropertiesWrite(config, out);
|
||||
|
||||
if (tape_type == FGTapeType_CONTINUOUS)
|
||||
{
|
||||
// Ensure that all recorded properties are written in first frame.
|
||||
//
|
||||
flight_recorder->resetExtraProperties();
|
||||
}
|
||||
|
||||
if (!out)
|
||||
{
|
||||
out.close();
|
||||
config = nullptr;
|
||||
}
|
||||
return config;
|
||||
}
|
||||
|
||||
/* Replays one frame from Continuous recording. <offset> and <offset_old> are
|
||||
offsets in file of frames that are >= and < <time> respectively. <offset_old>
|
||||
may be 0, in which case it is ignored.
|
||||
|
||||
We load the frame(s) from disc, omitting some data depending on
|
||||
replay_signals, replay_multiplayer and replay_extra_properties. Then call
|
||||
m_pRecorder->replay(), which updates the global state.
|
||||
|
||||
Returns true on success, otherwise we failed to read from Continuous recording.
|
||||
*/
|
||||
static bool replayContinuousInternal(
|
||||
Continuous& continuous,
|
||||
FGFlightRecorder* recorder,
|
||||
double time,
|
||||
size_t offset,
|
||||
size_t offset_old,
|
||||
bool replay_signals,
|
||||
bool replay_multiplayer,
|
||||
bool replay_extra_properties,
|
||||
int* xpos,
|
||||
int* ypos,
|
||||
int* xsize,
|
||||
int* ysize
|
||||
)
|
||||
{
|
||||
std::shared_ptr<FGReplayData> replay_data = ReadFGReplayData(
|
||||
continuous,
|
||||
continuous.m_in,
|
||||
offset,
|
||||
continuous.m_in_config,
|
||||
replay_signals,
|
||||
replay_multiplayer,
|
||||
replay_extra_properties,
|
||||
continuous.m_in_compression
|
||||
);
|
||||
if (!replay_data)
|
||||
{
|
||||
SG_LOG(SG_SYSTEMS, SG_DEBUG, "Failed to read fgtape frame at offset=" << offset << " time=" << time);
|
||||
return false;
|
||||
}
|
||||
assert(replay_data.get());
|
||||
std::shared_ptr<FGReplayData> replay_data_old;
|
||||
if (offset_old)
|
||||
{
|
||||
replay_data_old = ReadFGReplayData(
|
||||
continuous,
|
||||
continuous.m_in,
|
||||
offset_old,
|
||||
continuous.m_in_config,
|
||||
replay_signals,
|
||||
replay_multiplayer,
|
||||
replay_extra_properties,
|
||||
continuous.m_in_compression
|
||||
);
|
||||
}
|
||||
if (replay_extra_properties) SG_LOG(SG_SYSTEMS, SG_DEBUG,
|
||||
"replay():"
|
||||
<< " time=" << time
|
||||
<< " offset=" << offset
|
||||
<< " offset_old=" << offset_old
|
||||
<< " replay_data_old=" << replay_data_old
|
||||
<< " replay_data->raw_data.size()=" << replay_data->raw_data.size()
|
||||
<< " replay_data->multiplayer_messages.size()=" << replay_data->multiplayer_messages.size()
|
||||
<< " replay_data->extra_properties.size()=" << replay_data->extra_properties.size()
|
||||
<< " replay_data->replay_extra_property_changes.size()=" << replay_data->replay_extra_property_changes.size()
|
||||
);
|
||||
recorder->replay(time, replay_data.get(), replay_data_old.get(), xpos, ypos, xsize, ysize);
|
||||
return true;
|
||||
}
|
||||
|
||||
// fixme: this is duplicated in replay.cxx.
|
||||
static void popupTip(const char* message, int delay)
|
||||
{
|
||||
SGPropertyNode_ptr args(new SGPropertyNode);
|
||||
args->setStringValue("label", message);
|
||||
args->setIntValue("delay", delay);
|
||||
globals->get_commands()->execute("show-message", args);
|
||||
}
|
||||
|
||||
void continuous_replay_video_end(Continuous& continuous)
|
||||
{
|
||||
if (continuous.m_replay_create_video)
|
||||
{
|
||||
SG_LOG(SG_GENERAL, SG_ALERT, "Stopping replay create-video");
|
||||
auto view_mgr = globals->get_subsystem<FGViewMgr>();
|
||||
if (view_mgr)
|
||||
{
|
||||
view_mgr->video_stop();
|
||||
}
|
||||
continuous.m_replay_create_video = false;
|
||||
}
|
||||
if (continuous.m_replay_fixed_dt_prev != -1)
|
||||
{
|
||||
SG_LOG(SG_GENERAL, SG_ALERT, "Resetting fixed-dt to" << continuous.m_replay_fixed_dt_prev);
|
||||
fgSetDouble("/sim/time/fixed-dt", continuous.m_replay_fixed_dt_prev);
|
||||
continuous.m_replay_fixed_dt_prev = -1;
|
||||
}
|
||||
}
|
||||
|
||||
bool replayContinuous(FGReplayInternal& self, double time)
|
||||
{
|
||||
// We need to detect whether replay() updates the values for the main
|
||||
// window's position and size.
|
||||
int xpos0 = self.m_sim_startup_xpos->getIntValue();
|
||||
int ypos0 = self.m_sim_startup_xpos->getIntValue();
|
||||
int xsize0 = self.m_sim_startup_xpos->getIntValue();
|
||||
int ysize0 = self.m_sim_startup_xpos->getIntValue();
|
||||
|
||||
int xpos = xpos0;
|
||||
int ypos = ypos0;
|
||||
int xsize = xsize0;
|
||||
int ysize = ysize0;
|
||||
|
||||
double multiplayer_recent = 3;
|
||||
|
||||
// We replay all frames from just after the previously-replayed frame,
|
||||
// in order to replay extra properties and multiplayer aircraft
|
||||
// correctly.
|
||||
//
|
||||
double t_begin = self.m_continuous->m_in_frame_time_last;
|
||||
|
||||
if (time < self.m_continuous->m_in_time_last)
|
||||
{
|
||||
// We have gone backwards, e.g. user has clicked on the back
|
||||
// buttons in the Replay dialogue.
|
||||
//
|
||||
|
||||
if (self.m_continuous->m_in_multiplayer)
|
||||
{
|
||||
// Continuous recording has multiplayer data, so replay recent
|
||||
// ones.
|
||||
//
|
||||
t_begin = time - multiplayer_recent;
|
||||
}
|
||||
|
||||
if (self.m_continuous->m_in_extra_properties)
|
||||
{
|
||||
// Continuous recording has property changes. we need to replay
|
||||
// all property changes from the beginning.
|
||||
//
|
||||
t_begin = -1;
|
||||
}
|
||||
|
||||
SG_LOG(SG_SYSTEMS, SG_DEBUG, "Have gone backwards."
|
||||
<< " m_in_time_last=" << self.m_continuous->m_in_time_last
|
||||
<< " time=" << time
|
||||
<< " t_begin=" << t_begin
|
||||
<< " m_in_extra_properties=" << self.m_continuous->m_in_extra_properties
|
||||
);
|
||||
}
|
||||
|
||||
// Prepare to replay signals from Continuoue recording file. We want
|
||||
// to find a pair of frames that straddle the requested <time> so that
|
||||
// we can interpolate.
|
||||
//
|
||||
auto p = self.m_continuous->m_in_time_to_frameinfo.lower_bound(time);
|
||||
bool ret = false;
|
||||
|
||||
size_t offset;
|
||||
size_t offset_prev = 0;
|
||||
|
||||
if (p == self.m_continuous->m_in_time_to_frameinfo.end())
|
||||
{
|
||||
// We are at end of recording; replay last frame.
|
||||
continuous_replay_video_end(*self.m_continuous);
|
||||
--p;
|
||||
offset = p->second.offset;
|
||||
ret = true;
|
||||
}
|
||||
else if (p->first > time)
|
||||
{
|
||||
// Look for preceding item.
|
||||
if (p == self.m_continuous->m_in_time_to_frameinfo.begin())
|
||||
{
|
||||
// <time> is before beginning of recording.
|
||||
offset = p->second.offset;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Interpolate between pair of items that straddle <time>.
|
||||
auto prev = p;
|
||||
--prev;
|
||||
offset_prev = prev->second.offset;
|
||||
offset = p->second.offset;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Exact match.
|
||||
offset = p->second.offset;
|
||||
}
|
||||
|
||||
// Before interpolating signals, we replay all property changes from
|
||||
// all frame times t satisfying t_prop_begin < t < time. We also replay
|
||||
// all recent multiplayer packets in this range, i.e. for which t >
|
||||
// time - multiplayer_recent.
|
||||
//
|
||||
// todo: figure out how to interpolate view position/direction, to
|
||||
// smooth things out if replay fps is different from record fps e.g.
|
||||
// with new fixed dt support.
|
||||
//
|
||||
for (auto p_before = self.m_continuous->m_in_time_to_frameinfo.upper_bound(t_begin);
|
||||
p_before != self.m_continuous->m_in_time_to_frameinfo.end();
|
||||
++p_before)
|
||||
{
|
||||
if (p_before->first >= p->first)
|
||||
{
|
||||
break;
|
||||
}
|
||||
// Replaying a frame is expensive because we read frame data
|
||||
// from disc each time. So we only replay this frame if it has
|
||||
// extra_properties, or if it has multiplayer packets and we are
|
||||
// within <multiplayer_recent> seconds of current time.
|
||||
//
|
||||
bool replay_this_frame = p_before->second.has_extra_properties;
|
||||
if (p_before->second.has_multiplayer && p_before->first > time - multiplayer_recent)
|
||||
{
|
||||
replay_this_frame = true;
|
||||
}
|
||||
|
||||
SG_LOG(SG_SYSTEMS, SG_DEBUG, "Looking at extra property changes."
|
||||
<< " replay_this_frame=" << replay_this_frame
|
||||
<< " m_continuous->m_in_time_last=" << self.m_continuous->m_in_time_last
|
||||
<< " m_continuous->m_in_frame_time_last=" << self.m_continuous->m_in_frame_time_last
|
||||
<< " time=" << time
|
||||
<< " t_begin=" << t_begin
|
||||
<< " p_before->first=" << p_before->first
|
||||
<< " p_before->second=" << p_before->second
|
||||
);
|
||||
|
||||
if (replay_this_frame)
|
||||
{
|
||||
size_t pos_prev = 0;
|
||||
if (p_before != self.m_continuous->m_in_time_to_frameinfo.begin())
|
||||
{
|
||||
auto p_before_prev = p_before;
|
||||
--p_before_prev;
|
||||
pos_prev = p_before_prev->second.offset;
|
||||
}
|
||||
bool ok = replayContinuousInternal(
|
||||
*self.m_continuous,
|
||||
self.m_flight_recorder.get(),
|
||||
p_before->first,
|
||||
p_before->second.offset,
|
||||
pos_prev /*offset_old*/,
|
||||
false /*replay_signals*/,
|
||||
p_before->first > time - multiplayer_recent /*replay_multiplayer*/,
|
||||
true /*replay_extra_properties*/,
|
||||
&xpos,
|
||||
&ypos,
|
||||
&xsize,
|
||||
&ysize
|
||||
);
|
||||
if (!ok)
|
||||
{
|
||||
if (!self.m_replay_error->getBoolValue())
|
||||
{
|
||||
SG_LOG(SG_SYSTEMS, SG_ALERT, "Replay failed: cannot read fgtape data");
|
||||
popupTip("Replay failed: cannot read fgtape data", 10);
|
||||
self.m_replay_error->setBoolValue(true);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Now replay signals, interpolating between frames atoffset_prev and
|
||||
offset. */
|
||||
bool ok = replayContinuousInternal(
|
||||
*self.m_continuous,
|
||||
self.m_flight_recorder.get(),
|
||||
time,
|
||||
offset,
|
||||
offset_prev /*offset_old*/,
|
||||
true /*replay_signals*/,
|
||||
true /*replay_multiplayer*/,
|
||||
true /*replay_extra_properties*/,
|
||||
&xpos,
|
||||
&ypos,
|
||||
&xsize,
|
||||
&ysize
|
||||
);
|
||||
if (!ok)
|
||||
{
|
||||
if (!self.m_replay_error->getBoolValue())
|
||||
{
|
||||
SG_LOG(SG_SYSTEMS, SG_ALERT, "Replay failed: cannot read fgtape data");
|
||||
popupTip("Replay failed: cannot read fgtape data", 10);
|
||||
self.m_replay_error->setBoolValue(true);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
if (0
|
||||
|| xpos != xpos0
|
||||
|| ypos != ypos0
|
||||
|| xsize != xsize0
|
||||
|| ysize != ysize0
|
||||
)
|
||||
{
|
||||
// Move/resize the main window to reflect the updated values.
|
||||
globals->get_props()->setIntValue("/sim/startup/xpos", xpos);
|
||||
globals->get_props()->setIntValue("/sim/startup/ypos", ypos);
|
||||
globals->get_props()->setIntValue("/sim/startup/xsize", xsize);
|
||||
globals->get_props()->setIntValue("/sim/startup/ysize", ysize);
|
||||
|
||||
osgViewer::ViewerBase* viewer_base = globals->get_renderer()->getViewerBase();
|
||||
if (viewer_base)
|
||||
{
|
||||
std::vector<osgViewer::GraphicsWindow*> windows;
|
||||
viewer_base->getWindows(windows);
|
||||
osgViewer::GraphicsWindow* window = windows[0];
|
||||
|
||||
// We use FGEventHandler::setWindowRectangle() to move the
|
||||
// window, because it knows how to convert from window work-area
|
||||
// coordinates to window-including-furniture coordinates.
|
||||
//
|
||||
flightgear::FGEventHandler* event_handler = globals->get_renderer()->getEventHandler();
|
||||
event_handler->setWindowRectangleInteriorWithCorrection(window, xpos, ypos, xsize, ysize);
|
||||
}
|
||||
}
|
||||
|
||||
self.m_continuous->m_in_time_last = time;
|
||||
self.m_continuous->m_in_frame_time_last = p->first;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* SGPropertyChangeListener callback for detecing when FDM is initialised and
|
||||
for when continuous recording is started or stopped. */
|
||||
void Continuous::valueChanged(SGPropertyNode * node)
|
||||
{
|
||||
bool prop_continuous = fgGetBool("/sim/replay/record-continuous");
|
||||
bool prop_fdm = fgGetBool("/sim/signals/fdm-initialized");
|
||||
|
||||
bool continuous = prop_continuous && prop_fdm;
|
||||
if (continuous == (m_out.is_open() ? true : false))
|
||||
{
|
||||
// No change.
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_out.is_open())
|
||||
{
|
||||
// Stop existing continuous recording.
|
||||
SG_LOG(SG_SYSTEMS, SG_ALERT, "Stopping continuous recording");
|
||||
m_out.close();
|
||||
popupTip("Continuous record to file stopped", 5 /*delay*/);
|
||||
}
|
||||
|
||||
if (continuous)
|
||||
{
|
||||
// Start continuous recording.
|
||||
SGPath path_timeless;
|
||||
SGPath path = makeSavePath(FGTapeType_CONTINUOUS, &path_timeless);
|
||||
m_out_config = continuousWriteHeader(
|
||||
*this,
|
||||
m_flight_recorder.get(),
|
||||
m_out,
|
||||
path,
|
||||
FGTapeType_CONTINUOUS
|
||||
);
|
||||
if (!m_out_config)
|
||||
{
|
||||
SG_LOG(SG_SYSTEMS, SG_ALERT, "Failed to start continuous recording");
|
||||
popupTip("Continuous record to file failed to start", 5 /*delay*/);
|
||||
return;
|
||||
}
|
||||
|
||||
SG_LOG(SG_SYSTEMS, SG_ALERT, "Starting continuous recording");
|
||||
|
||||
/* Make a convenience link to the recording. E.g.
|
||||
harrier-gr3-continuous.fgtape -> harrier-gr3-20201224-005034-continuous.fgtape.
|
||||
|
||||
Link destination is in same directory as link so we use leafname
|
||||
path.file(). */
|
||||
path_timeless.remove();
|
||||
bool ok = path_timeless.makeLink(path.file());
|
||||
if (!ok)
|
||||
{
|
||||
SG_LOG(SG_SYSTEMS, SG_ALERT, "Failed to create link " << path_timeless.c_str() << " => " << path.file());
|
||||
}
|
||||
SG_LOG(SG_SYSTEMS, SG_DEBUG, "Starting continuous recording to " << path);
|
||||
if (m_out_compression)
|
||||
{
|
||||
popupTip("Continuous+compressed record to file started", 5 /*delay*/);
|
||||
}
|
||||
else
|
||||
{
|
||||
popupTip("Continuous record to file started", 5 /*delay*/);
|
||||
}
|
||||
}
|
||||
}
|
||||
97
src/Aircraft/continuous.hxx
Normal file
97
src/Aircraft/continuous.hxx
Normal file
@@ -0,0 +1,97 @@
|
||||
#pragma once
|
||||
|
||||
#include "replay-internal.hxx"
|
||||
|
||||
#include <simgear/props/props.hxx>
|
||||
|
||||
#include <fstream>
|
||||
|
||||
#include <mutex>
|
||||
#include <thread>
|
||||
|
||||
|
||||
struct Continuous : SGPropertyChangeListener
|
||||
{
|
||||
Continuous(std::shared_ptr<FGFlightRecorder> flight_recorder);
|
||||
|
||||
/* Callback for SGPropertyChangeListener. */
|
||||
void valueChanged(SGPropertyNode * node) override;
|
||||
|
||||
std::shared_ptr<FGFlightRecorder> m_flight_recorder;
|
||||
|
||||
std::ifstream m_in;
|
||||
bool m_in_multiplayer = false;
|
||||
bool m_in_extra_properties = false;
|
||||
std::mutex m_in_time_to_frameinfo_lock;
|
||||
std::map<double, FGFrameInfo> m_in_time_to_frameinfo;
|
||||
SGPropertyNode_ptr m_in_config;
|
||||
double m_in_time_last = 0;
|
||||
double m_in_frame_time_last = 0;
|
||||
std::map<size_t, std::shared_ptr<FGReplayData>>
|
||||
m_in_pos_to_frame;
|
||||
|
||||
std::ifstream m_indexing_in;
|
||||
std::streampos m_indexing_pos;
|
||||
|
||||
bool m_replay_create_video = false;
|
||||
double m_replay_fixed_dt = -1;
|
||||
double m_replay_fixed_dt_prev = -1;
|
||||
|
||||
// Only used for gathering statistics that are then written into
|
||||
// properties.
|
||||
//
|
||||
int m_num_frames_extra_properties = 0;
|
||||
int m_num_frames_multiplayer = 0;
|
||||
|
||||
// For writing Continuous fgtape file.
|
||||
SGPropertyNode_ptr m_out_config;
|
||||
std::ofstream m_out;
|
||||
int m_out_compression = 0;
|
||||
int m_in_compression = 0;
|
||||
};
|
||||
|
||||
/* Attempts to load Continuous recording header properties into
|
||||
<properties>. If in is null we use internal std::fstream, otherwise we use *in.
|
||||
|
||||
Returns 0 on success, +1 if we may succeed after further download, or -1 if
|
||||
recording is not a Continuous recording. */
|
||||
int loadContinuousHeader(const std::string& path, std::istream* in, SGPropertyNode* properties);
|
||||
|
||||
|
||||
/* Writes one frame of continuous record information. */
|
||||
bool continuousWriteFrame(
|
||||
Continuous& continuous,
|
||||
FGReplayData* r,
|
||||
std::ostream& out,
|
||||
SGPropertyNode_ptr config,
|
||||
FGTapeType tape_type
|
||||
);
|
||||
|
||||
/* Opens continuous recording file and writes header.
|
||||
|
||||
If MetaData is unset, we initialise it by calling saveSetup(). Otherwise should
|
||||
be already set up.
|
||||
|
||||
If Config is unset, we make it point to a new node populated by
|
||||
m_pRecorder->getConfig(). Otherwise it should be already set up to point to
|
||||
such information.
|
||||
|
||||
If path_override is not "", we use it as the path (instead of the path
|
||||
determined by saveSetup(). */
|
||||
SGPropertyNode_ptr continuousWriteHeader(
|
||||
Continuous& continuous,
|
||||
FGFlightRecorder* m_pRecorder,
|
||||
std::ofstream& out,
|
||||
const SGPath& path,
|
||||
FGTapeType tape_type
|
||||
);
|
||||
|
||||
/* Replays one frame from Continuous recording.
|
||||
|
||||
Returns true on success, otherwise we failed to read from Continuous recording.
|
||||
*/
|
||||
bool replayContinuous(FGReplayInternal& self, double time);
|
||||
|
||||
/* Stops any video recording that was started because of
|
||||
continuous->m_replay_create_video. */
|
||||
void continuous_replay_video_end(Continuous& continuous);
|
||||
1840
src/Aircraft/controls.cxx
Normal file
1840
src/Aircraft/controls.cxx
Normal file
File diff suppressed because it is too large
Load Diff
686
src/Aircraft/controls.hxx
Normal file
686
src/Aircraft/controls.hxx
Normal file
@@ -0,0 +1,686 @@
|
||||
// controls.hxx -- defines a standard interface to all flight sim controls
|
||||
//
|
||||
// Written by Curtis Olson, started May 1997.
|
||||
//
|
||||
// Copyright (C) 1997 Curtis L. Olson - http://www.flightgear.org/~curt
|
||||
//
|
||||
// This program is free software; you can redistribute it and/or
|
||||
// modify it under the terms of the GNU General Public License as
|
||||
// published by the Free Software Foundation; either version 2 of the
|
||||
// License, or (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful, but
|
||||
// WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program; if not, write to the Free Software
|
||||
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
//
|
||||
// $Id$
|
||||
|
||||
|
||||
#ifndef _CONTROLS_HXX
|
||||
#define _CONTROLS_HXX
|
||||
|
||||
#include <simgear/structure/subsystem_mgr.hxx>
|
||||
#include <simgear/props/tiedpropertylist.hxx>
|
||||
|
||||
// Define a structure containing the control parameters
|
||||
|
||||
class FGControls : public SGSubsystem
|
||||
{
|
||||
public:
|
||||
enum {
|
||||
ALL_ENGINES = -1,
|
||||
MAX_ENGINES = 12
|
||||
};
|
||||
|
||||
enum {
|
||||
ALL_WHEELS = -1,
|
||||
MAX_WHEELS = 3
|
||||
};
|
||||
|
||||
enum {
|
||||
ALL_TANKS = -1,
|
||||
MAX_TANKS = 8
|
||||
};
|
||||
|
||||
enum {
|
||||
ALL_BOOSTPUMPS = -1,
|
||||
MAX_BOOSTPUMPS = 2
|
||||
};
|
||||
|
||||
enum {
|
||||
ALL_HYD_SYSTEMS = -1,
|
||||
MAX_HYD_SYSTEMS = 4
|
||||
};
|
||||
|
||||
enum {
|
||||
ALL_PACKS = -1,
|
||||
MAX_PACKS = 4
|
||||
};
|
||||
|
||||
enum {
|
||||
ALL_LIGHTS = -1,
|
||||
MAX_LIGHTS = 4
|
||||
};
|
||||
|
||||
enum {
|
||||
ALL_STATIONS = -1,
|
||||
MAX_STATIONS = 12
|
||||
};
|
||||
|
||||
enum {
|
||||
ALL_AUTOPILOTS = -1,
|
||||
MAX_AUTOPILOTS = 3
|
||||
};
|
||||
|
||||
enum {
|
||||
ALL_EJECTION_SEATS = -1,
|
||||
MAX_EJECTION_SEATS = 10
|
||||
};
|
||||
|
||||
enum {
|
||||
SEAT_SAFED = -1,
|
||||
SEAT_ARMED = 0,
|
||||
SEAT_FAIL = 1
|
||||
};
|
||||
|
||||
enum {
|
||||
CMD_SEL_NORM = -1,
|
||||
CMD_SEL_AFT = 0,
|
||||
CMD_SEL_SOLO = 1
|
||||
};
|
||||
|
||||
private:
|
||||
// controls/flight/
|
||||
double aileron;
|
||||
double aileron_trim;
|
||||
double elevator;
|
||||
double elevator_trim;
|
||||
double rudder;
|
||||
double rudder_trim;
|
||||
double flaps;
|
||||
double slats;
|
||||
bool BLC; // Boundary Layer Control
|
||||
double spoilers;
|
||||
double speedbrake;
|
||||
double wing_sweep;
|
||||
bool wing_fold;
|
||||
bool drag_chute;
|
||||
|
||||
// controls/engines/
|
||||
bool throttle_idle;
|
||||
|
||||
// controls/engines/engine[n]/
|
||||
double throttle[MAX_ENGINES];
|
||||
bool starter[MAX_ENGINES];
|
||||
bool fuel_pump[MAX_ENGINES];
|
||||
bool fire_switch[MAX_ENGINES];
|
||||
bool fire_bottle_discharge[MAX_ENGINES];
|
||||
bool cutoff[MAX_ENGINES];
|
||||
double mixture[MAX_ENGINES];
|
||||
double prop_advance[MAX_ENGINES];
|
||||
int magnetos[MAX_ENGINES];
|
||||
int feed_tank[MAX_ENGINES];
|
||||
bool nitrous_injection[MAX_ENGINES]; // War Emergency Power
|
||||
double cowl_flaps_norm[MAX_ENGINES];
|
||||
bool feather[MAX_ENGINES];
|
||||
int ignition[MAX_ENGINES];
|
||||
bool augmentation[MAX_ENGINES];
|
||||
bool reverser[MAX_ENGINES];
|
||||
bool water_injection[MAX_ENGINES];
|
||||
double condition[MAX_ENGINES]; // turboprop speed select
|
||||
|
||||
// controls/fuel/
|
||||
bool dump_valve;
|
||||
|
||||
// controls/fuel/tank[n]/
|
||||
bool fuel_selector[MAX_TANKS];
|
||||
int to_engine[MAX_TANKS];
|
||||
int to_tank[MAX_TANKS];
|
||||
|
||||
// controls/fuel/tank[n]/pump[p]/
|
||||
bool boost_pump[MAX_TANKS * MAX_BOOSTPUMPS];
|
||||
|
||||
// controls/gear/
|
||||
double brake_left;
|
||||
double brake_right;
|
||||
double copilot_brake_left;
|
||||
double copilot_brake_right;
|
||||
double brake_parking;
|
||||
double steering;
|
||||
bool nose_wheel_steering;
|
||||
bool gear_down;
|
||||
bool antiskid;
|
||||
bool tailhook;
|
||||
bool launchbar;
|
||||
bool catapult_launch_cmd;
|
||||
bool tailwheel_lock;
|
||||
|
||||
// controls/gear/wheel[n]/
|
||||
bool alternate_extension[MAX_WHEELS];
|
||||
|
||||
// controls/anti-ice/
|
||||
bool wing_heat;
|
||||
bool pitot_heat;
|
||||
int wiper;
|
||||
bool window_heat;
|
||||
|
||||
// controls/anti-ice/engine[n]/
|
||||
bool carb_heat[MAX_ENGINES];
|
||||
bool inlet_heat[MAX_ENGINES];
|
||||
|
||||
// controls/hydraulic/system[n]/
|
||||
bool engine_pump[MAX_HYD_SYSTEMS];
|
||||
bool electric_pump[MAX_HYD_SYSTEMS];
|
||||
|
||||
// controls/electric/
|
||||
bool battery_switch;
|
||||
bool external_power;
|
||||
bool APU_generator;
|
||||
|
||||
// controls/electric/engine[n]/
|
||||
bool generator_breaker[MAX_ENGINES];
|
||||
bool bus_tie[MAX_ENGINES];
|
||||
|
||||
// controls/pneumatic/
|
||||
bool APU_bleed;
|
||||
|
||||
// controls/pneumatic/engine[n]/
|
||||
bool engine_bleed[MAX_ENGINES];
|
||||
|
||||
// controls/pressurization/
|
||||
int mode;
|
||||
bool dump;
|
||||
double outflow_valve;
|
||||
|
||||
// controls/pressurization/pack[n]/
|
||||
bool pack_on[MAX_PACKS];
|
||||
|
||||
// controls/lighting/
|
||||
bool landing_lights;
|
||||
bool turn_off_lights;
|
||||
bool taxi_light;
|
||||
bool logo_lights;
|
||||
bool nav_lights;
|
||||
bool beacon;
|
||||
bool strobe;
|
||||
double panel_norm;
|
||||
double instruments_norm;
|
||||
double dome_norm;
|
||||
|
||||
// controls/armament/
|
||||
bool master_arm;
|
||||
int station_select;
|
||||
bool release_ALL;
|
||||
|
||||
// controls/armament/station[n]/
|
||||
int stick_size[MAX_STATIONS];
|
||||
bool release_stick[MAX_STATIONS];
|
||||
bool release_all[MAX_STATIONS];
|
||||
bool jettison_all[MAX_STATIONS];
|
||||
|
||||
// controls/seat/
|
||||
double vertical_adjust;
|
||||
double fore_aft_adjust;
|
||||
bool eject[MAX_EJECTION_SEATS];
|
||||
int eseat_status[MAX_EJECTION_SEATS];
|
||||
int cmd_selector_valve;
|
||||
|
||||
// controls/APU/
|
||||
int off_start_run;
|
||||
bool APU_fire_switch;
|
||||
|
||||
// controls/autoflight/autopilot[n]/
|
||||
bool autopilot_engage[MAX_AUTOPILOTS];
|
||||
|
||||
// controls/autoflight/
|
||||
bool autothrottle_arm;
|
||||
bool autothrottle_engage;
|
||||
double heading_select;
|
||||
double altitude_select;
|
||||
double bank_angle_select;
|
||||
double vertical_speed_select;
|
||||
double speed_select;
|
||||
double mach_select;
|
||||
int vertical_mode;
|
||||
int lateral_mode;
|
||||
|
||||
SGPropertyNode_ptr auto_coordination;
|
||||
SGPropertyNode_ptr auto_coordination_factor;
|
||||
simgear::TiedPropertyList _tiedProperties;
|
||||
|
||||
// we need to node pointers as well, so we can manually
|
||||
// fire valueChanged for these
|
||||
SGPropertyNode_ptr _aileronNode;
|
||||
SGPropertyNode_ptr _elevatorNode;
|
||||
SGPropertyNode_ptr _aileronTrimNode;
|
||||
SGPropertyNode_ptr _elevatorTrimNode;
|
||||
SGPropertyNode_ptr _rudderNode;
|
||||
|
||||
simgear::PropertyList _engineThrottleNodes;
|
||||
simgear::PropertyList _engineMixtureNodes;
|
||||
simgear::PropertyList _engineStarterNodes;
|
||||
simgear::PropertyList _engineCutoffNodes;
|
||||
simgear::PropertyList _engineReverserNodes;
|
||||
simgear::PropertyList _engineWaterInjectionNodes;
|
||||
simgear::PropertyList _engineMagnetoNodes;
|
||||
simgear::PropertyList _engineAugmentationNodes;
|
||||
|
||||
public:
|
||||
FGControls();
|
||||
~FGControls();
|
||||
|
||||
// Subsystem API.
|
||||
void bind() override;
|
||||
void init() override;
|
||||
void reinit() override;
|
||||
void unbind() override;
|
||||
void update(double dt) override;
|
||||
|
||||
// Subsystem identification.
|
||||
static const char* staticSubsystemClassId() { return "controls"; }
|
||||
|
||||
// Reset function
|
||||
void reset_all(void);
|
||||
|
||||
// Query functions
|
||||
// controls/flight/
|
||||
inline double get_aileron() const { return aileron; }
|
||||
inline double get_aileron_trim() const { return aileron_trim; }
|
||||
inline double get_elevator() const { return elevator; }
|
||||
inline double get_elevator_trim() const { return elevator_trim; }
|
||||
inline double get_rudder() const { return rudder; }
|
||||
inline double get_rudder_trim() const { return rudder_trim; }
|
||||
inline double get_flaps() const { return flaps; }
|
||||
inline double get_slats() const { return slats; }
|
||||
inline bool get_BLC() const { return BLC; }
|
||||
inline double get_spoilers() const { return spoilers; }
|
||||
inline double get_speedbrake() const { return speedbrake; }
|
||||
inline double get_wing_sweep() const { return wing_sweep; }
|
||||
inline bool get_wing_fold() const { return wing_fold; }
|
||||
inline bool get_drag_chute() const { return drag_chute; }
|
||||
|
||||
// controls/engines/
|
||||
inline bool get_throttle_idle() const { return throttle_idle; }
|
||||
|
||||
// controls/engines/engine[n]/
|
||||
inline double get_throttle(int engine) const { return throttle[engine]; }
|
||||
inline bool get_starter(int engine) const { return starter[engine]; }
|
||||
inline bool get_fuel_pump(int engine) const { return fuel_pump[engine]; }
|
||||
inline bool get_fire_switch(int engine) const { return fire_switch[engine]; }
|
||||
inline bool get_fire_bottle_discharge(int engine) const {
|
||||
return fire_bottle_discharge[engine];
|
||||
}
|
||||
inline bool get_cutoff(int engine) const { return cutoff[engine]; }
|
||||
inline double get_mixture(int engine) const { return mixture[engine]; }
|
||||
inline double get_prop_advance(int engine) const {
|
||||
return prop_advance[engine];
|
||||
}
|
||||
inline int get_magnetos(int engine) const { return magnetos[engine]; }
|
||||
inline int get_feed_tank(int engine) const { return feed_tank[engine]; }
|
||||
inline bool get_nitrous_injection(int engine) const {
|
||||
return nitrous_injection[engine];
|
||||
}
|
||||
inline double get_cowl_flaps_norm(int engine) const {
|
||||
return cowl_flaps_norm[engine];
|
||||
}
|
||||
inline bool get_feather(int engine) const { return feather[engine]; }
|
||||
inline int get_ignition(int engine) const { return ignition[engine]; }
|
||||
inline bool get_augmentation(int engine) const { return augmentation[engine]; }
|
||||
inline bool get_reverser(int engine) const { return reverser[engine]; }
|
||||
inline bool get_water_injection(int engine) const {
|
||||
return water_injection[engine];
|
||||
}
|
||||
inline double get_condition(int engine) const { return condition[engine]; }
|
||||
|
||||
// controls/fuel/
|
||||
inline bool get_dump_valve() const { return dump_valve; }
|
||||
|
||||
// controls/fuel/tank[n]/
|
||||
inline bool get_fuel_selector(int tank) const {
|
||||
return fuel_selector[tank];
|
||||
}
|
||||
inline int get_to_engine(int tank) const { return to_engine[tank]; }
|
||||
inline int get_to_tank(int tank) const { return to_tank[tank]; }
|
||||
|
||||
// controls/fuel/tank[n]/pump[p]/
|
||||
inline bool get_boost_pump(int index) const {
|
||||
return boost_pump[index];
|
||||
}
|
||||
|
||||
// controls/gear/
|
||||
inline double get_brake_left() const { return brake_left; }
|
||||
inline double get_brake_right() const { return brake_right; }
|
||||
inline double get_copilot_brake_left() const { return copilot_brake_left; }
|
||||
inline double get_copilot_brake_right() const { return copilot_brake_right; }
|
||||
inline double get_brake_parking() const { return brake_parking; }
|
||||
inline double get_steering() const { return steering; }
|
||||
inline bool get_nose_wheel_steering() const { return nose_wheel_steering; }
|
||||
inline bool get_gear_down() const { return gear_down; }
|
||||
inline bool get_antiskid() const { return antiskid; }
|
||||
inline bool get_tailhook() const { return tailhook; }
|
||||
inline bool get_launchbar() const { return launchbar; }
|
||||
inline bool get_catapult_launch_cmd() const { return catapult_launch_cmd; }
|
||||
inline bool get_tailwheel_lock() const { return tailwheel_lock; }
|
||||
|
||||
// controls/gear/wheel[n]/
|
||||
inline bool get_alternate_extension(int wheel) const {
|
||||
return alternate_extension[wheel];
|
||||
}
|
||||
|
||||
// controls/anti-ice/
|
||||
inline bool get_wing_heat() const { return wing_heat; }
|
||||
inline bool get_pitot_heat() const { return pitot_heat; }
|
||||
inline int get_wiper() const { return wiper; }
|
||||
inline bool get_window_heat() const { return window_heat; }
|
||||
|
||||
// controls/anti-ice/engine[n]/
|
||||
inline bool get_carb_heat(int engine) const { return carb_heat[engine]; }
|
||||
inline bool get_inlet_heat(int engine) const { return inlet_heat[engine]; }
|
||||
|
||||
// controls/hydraulic/system[n]/
|
||||
inline bool get_engine_pump(int system) const { return engine_pump[system]; }
|
||||
inline bool get_electric_pump(int system) const { return electric_pump[system]; }
|
||||
|
||||
// controls/electric/
|
||||
inline bool get_battery_switch() const { return battery_switch; }
|
||||
inline bool get_external_power() const { return external_power; }
|
||||
inline bool get_APU_generator() const { return APU_generator; }
|
||||
|
||||
// controls/electric/engine[n]/
|
||||
inline bool get_generator_breaker(int engine) const {
|
||||
return generator_breaker[engine];
|
||||
}
|
||||
inline bool get_bus_tie(int engine) const { return bus_tie[engine]; }
|
||||
|
||||
// controls/pneumatic/
|
||||
inline bool get_APU_bleed() const { return APU_bleed; }
|
||||
|
||||
// controls/pneumatic/engine[n]/
|
||||
inline bool get_engine_bleed(int engine) const { return engine_bleed[engine]; }
|
||||
|
||||
// controls/pressurization/
|
||||
inline int get_mode() const { return mode; }
|
||||
inline double get_outflow_valve() const { return outflow_valve; }
|
||||
inline bool get_dump() const { return dump; }
|
||||
|
||||
// controls/pressurization/pack[n]/
|
||||
inline bool get_pack_on(int pack) const { return pack_on[pack]; }
|
||||
|
||||
// controls/lighting/
|
||||
inline bool get_landing_lights() const { return landing_lights; }
|
||||
inline bool get_turn_off_lights() const { return turn_off_lights; }
|
||||
inline bool get_taxi_light() const { return taxi_light; }
|
||||
inline bool get_logo_lights() const { return logo_lights; }
|
||||
inline bool get_nav_lights() const { return nav_lights; }
|
||||
inline bool get_beacon() const { return beacon; }
|
||||
inline bool get_strobe() const { return strobe; }
|
||||
inline double get_panel_norm() const { return panel_norm; }
|
||||
inline double get_instruments_norm() const { return instruments_norm; }
|
||||
inline double get_dome_norm() const { return dome_norm; }
|
||||
|
||||
// controls/armament/
|
||||
inline bool get_master_arm() const { return master_arm; }
|
||||
inline int get_station_select() const { return station_select; }
|
||||
inline bool get_release_ALL() const { return release_ALL; }
|
||||
|
||||
// controls/armament/station[n]/
|
||||
inline int get_stick_size(int station) const { return stick_size[station]; }
|
||||
inline bool get_release_stick(int station) const { return release_stick[station]; }
|
||||
inline bool get_release_all(int station) const { return release_all[station]; }
|
||||
inline bool get_jettison_all(int station) const { return jettison_all[station]; }
|
||||
|
||||
// controls/seat/
|
||||
inline double get_vertical_adjust() const { return vertical_adjust; }
|
||||
inline double get_fore_aft_adjust() const { return fore_aft_adjust; }
|
||||
inline bool get_ejection_seat( int which_seat ) const {
|
||||
return eject[which_seat];
|
||||
}
|
||||
inline int get_eseat_status( int which_seat ) const {
|
||||
return eseat_status[which_seat];
|
||||
}
|
||||
inline int get_cmd_selector_valve() const { return cmd_selector_valve; }
|
||||
|
||||
|
||||
// controls/APU/
|
||||
inline int get_off_start_run() const { return off_start_run; }
|
||||
inline bool get_APU_fire_switch() const { return APU_fire_switch; }
|
||||
|
||||
// controls/autoflight/
|
||||
inline bool get_autothrottle_arm() const { return autothrottle_arm; }
|
||||
inline bool get_autothrottle_engage() const { return autothrottle_engage; }
|
||||
inline double get_heading_select() const { return heading_select; }
|
||||
inline double get_altitude_select() const { return altitude_select; }
|
||||
inline double get_bank_angle_select() const { return bank_angle_select; }
|
||||
inline double get_vertical_speed_select() const {
|
||||
return vertical_speed_select;
|
||||
}
|
||||
inline double get_speed_select() const { return speed_select; }
|
||||
inline double get_mach_select() const { return mach_select; }
|
||||
inline int get_vertical_mode() const { return vertical_mode; }
|
||||
inline int get_lateral_mode() const { return lateral_mode; }
|
||||
|
||||
// controls/autoflight/autopilot[n]/
|
||||
inline bool get_autopilot_engage(int ap) const {
|
||||
return autopilot_engage[ap];
|
||||
}
|
||||
|
||||
void set_elevator( double pos );
|
||||
void set_aileron_trim( double pos );
|
||||
void set_elevator_trim( double pos );
|
||||
void set_aileron( double pos );
|
||||
void set_rudder( double pos );
|
||||
|
||||
void set_throttle( int engine, double pos );
|
||||
void set_cutoff( int engine, bool val );
|
||||
void set_augmentation( int engine, bool val );
|
||||
void set_reverser( int engine, bool val );
|
||||
void set_water_injection( int engine, bool val );
|
||||
void set_magnetos( int engine, int pos );
|
||||
void set_starter( int engine, bool flag );
|
||||
void set_mixture( int engine, double pos );
|
||||
|
||||
private:
|
||||
// IMPORTANT: do *not* make these setters public, or you will violate
|
||||
// the listener-safety of them. If you need to make an accessor public,
|
||||
// make these as 'inner', and make a public wrapper which correctly calls
|
||||
// valueChanged (see, set_throttle, set_elevator etc for examples)
|
||||
|
||||
// Update functions
|
||||
// controls/flight/
|
||||
void _inner_set_aileron( double pos );
|
||||
void move_aileron( double amt );
|
||||
void _inner_set_aileron_trim( double pos );
|
||||
void move_aileron_trim( double amt );
|
||||
void _inner_set_elevator( double pos );
|
||||
void move_elevator( double amt );
|
||||
void _inner_set_elevator_trim( double pos );
|
||||
void move_elevator_trim( double amt );
|
||||
void _inner_set_rudder( double pos );
|
||||
void move_rudder( double amt );
|
||||
void set_rudder_trim( double pos );
|
||||
void move_rudder_trim( double amt );
|
||||
void set_flaps( double pos );
|
||||
void move_flaps( double amt );
|
||||
void set_slats( double pos );
|
||||
void move_slats( double amt );
|
||||
void set_BLC( bool val );
|
||||
void set_spoilers( double pos );
|
||||
void move_spoilers( double amt );
|
||||
void set_speedbrake( double pos );
|
||||
void move_speedbrake( double amt );
|
||||
void set_wing_sweep( double pos );
|
||||
void move_wing_sweep( double amt );
|
||||
void set_wing_fold( bool val );
|
||||
void set_drag_chute( bool val );
|
||||
|
||||
// controls/engines/
|
||||
void set_throttle_idle( bool val );
|
||||
|
||||
// controls/engines/engine[n]/
|
||||
void _inner_set_throttle( int engine, double pos );
|
||||
void move_throttle( int engine, double amt );
|
||||
void _inner_set_starter( int engine, bool flag );
|
||||
void set_fuel_pump( int engine, bool val );
|
||||
void set_fire_switch( int engine, bool val );
|
||||
void set_fire_bottle_discharge( int engine, bool val );
|
||||
void _inner_set_cutoff( int engine, bool val );
|
||||
void _inner_set_mixture( int engine, double pos );
|
||||
void move_mixture( int engine, double amt );
|
||||
void set_prop_advance( int engine, double pos );
|
||||
void move_prop_advance( int engine, double amt );
|
||||
void _inner_set_magnetos( int engine, int pos );
|
||||
void move_magnetos( int engine, int amt );
|
||||
void set_feed_tank( int engine, int tank );
|
||||
void set_nitrous_injection( int engine, bool val );
|
||||
void set_cowl_flaps_norm( int engine, double pos );
|
||||
void move_cowl_flaps_norm( int engine, double amt );
|
||||
void set_feather( int engine, bool val );
|
||||
void set_ignition( int engine, int val );
|
||||
void _inner_set_augmentation( int engine, bool val );
|
||||
void _inner_set_reverser( int engine, bool val );
|
||||
void _inner_set_water_injection( int engine, bool val );
|
||||
void set_condition( int engine, double val );
|
||||
|
||||
// controls/fuel
|
||||
void set_dump_valve( bool val );
|
||||
|
||||
// controls/fuel/tank[n]/
|
||||
void set_fuel_selector( int tank, bool pos );
|
||||
void set_to_engine( int tank, int engine );
|
||||
void set_to_tank( int tank, int dest_tank );
|
||||
|
||||
// controls/fuel/tank[n]/pump[p]
|
||||
void set_boost_pump( int index, bool val );
|
||||
|
||||
// controls/gear/
|
||||
void set_brake_left( double pos );
|
||||
void move_brake_left( double amt );
|
||||
void set_brake_right( double pos );
|
||||
void move_brake_right( double amt );
|
||||
void set_copilot_brake_left( double pos );
|
||||
void set_copilot_brake_right( double pos );
|
||||
void set_brake_parking( double pos );
|
||||
void set_steering( double pos );
|
||||
void move_steering( double amt );
|
||||
void set_nose_wheel_steering( bool nws );
|
||||
void set_gear_down( bool gear );
|
||||
void set_antiskid( bool val );
|
||||
void set_tailhook( bool val );
|
||||
void set_launchbar( bool val );
|
||||
void set_catapult_launch_cmd( bool val );
|
||||
void set_tailwheel_lock( bool val );
|
||||
|
||||
// controls/gear/wheel[n]/
|
||||
void set_alternate_extension( int wheel, bool val );
|
||||
|
||||
// controls/anti-ice/
|
||||
void set_wing_heat( bool val );
|
||||
void set_pitot_heat( bool val );
|
||||
void set_wiper( int speed );
|
||||
void set_window_heat( bool val );
|
||||
|
||||
// controls/anti-ice/engine[n]/
|
||||
void set_carb_heat( int engine, bool val );
|
||||
void set_inlet_heat( int engine, bool val );
|
||||
|
||||
// controls/hydraulic/system[n]/
|
||||
void set_engine_pump( int system, bool val );
|
||||
void set_electric_pump( int system, bool val );
|
||||
|
||||
// controls/electric/
|
||||
void set_battery_switch( bool val );
|
||||
void set_external_power( bool val );
|
||||
void set_APU_generator( bool val );
|
||||
|
||||
// controls/electric/engine[n]/
|
||||
void set_generator_breaker( int engine, bool val );
|
||||
void set_bus_tie( int engine, bool val );
|
||||
|
||||
// controls/pneumatic/
|
||||
void set_APU_bleed( bool val );
|
||||
|
||||
// controls/pneumatic/engine[n]/
|
||||
void set_engine_bleed( int engine, bool val );
|
||||
|
||||
// controls/pressurization/
|
||||
void set_mode( int mode );
|
||||
void set_outflow_valve( double pos );
|
||||
void move_outflow_valve( double amt );
|
||||
void set_dump( bool val );
|
||||
|
||||
// controls/pressurization/pack[n]/
|
||||
void set_pack_on( int pack, bool val );
|
||||
|
||||
// controls/lighting/
|
||||
void set_landing_lights( bool val );
|
||||
void set_turn_off_lights( bool val );
|
||||
void set_taxi_light( bool val );
|
||||
void set_logo_lights( bool val );
|
||||
void set_nav_lights( bool val );
|
||||
void set_beacon( bool val );
|
||||
void set_strobe( bool val );
|
||||
void set_panel_norm( double intensity );
|
||||
void move_panel_norm( double amt );
|
||||
void set_instruments_norm( double intensity );
|
||||
void move_instruments_norm( double amt );
|
||||
void set_dome_norm( double intensity );
|
||||
void move_dome_norm( double amt );
|
||||
|
||||
// controls/armament/
|
||||
void set_master_arm( bool val );
|
||||
void set_station_select( int station );
|
||||
void set_release_ALL( bool val );
|
||||
|
||||
// controls/armament/station[n]/
|
||||
void set_stick_size( int station, int size );
|
||||
void set_release_stick( int station, bool val );
|
||||
void set_release_all( int station, bool val );
|
||||
void set_jettison_all( int station, bool val );
|
||||
|
||||
// controls/seat/
|
||||
void set_vertical_adjust( double pos );
|
||||
void move_vertical_adjust( double amt );
|
||||
void set_fore_aft_adjust( double pos );
|
||||
void move_fore_aft_adjust( double amt );
|
||||
void set_ejection_seat( int which_seat, bool val );
|
||||
void set_eseat_status( int which_seat, int val );
|
||||
void set_cmd_selector_valve( int val );
|
||||
|
||||
// controls/APU/
|
||||
void set_off_start_run( int pos );
|
||||
void set_APU_fire_switch( bool val );
|
||||
|
||||
// controls/autoflight/
|
||||
void set_autothrottle_arm( bool val );
|
||||
void set_autothrottle_engage( bool val );
|
||||
void set_heading_select( double heading );
|
||||
void move_heading_select( double amt );
|
||||
void set_altitude_select( double altitude );
|
||||
void move_altitude_select( double amt );
|
||||
void set_bank_angle_select( double angle );
|
||||
void move_bank_angle_select( double amt );
|
||||
void set_vertical_speed_select( double vs );
|
||||
void move_vertical_speed_select( double amt );
|
||||
void set_speed_select( double speed );
|
||||
void move_speed_select( double amt );
|
||||
void set_mach_select( double mach );
|
||||
void move_mach_select( double amt );
|
||||
void set_vertical_mode( int mode );
|
||||
void set_lateral_mode( int mode );
|
||||
|
||||
// controls/autoflight/autopilot[n]/
|
||||
void set_autopilot_engage( int ap, bool val );
|
||||
|
||||
void do_autocoordination();
|
||||
|
||||
void fireEngineValueChanged(int index, simgear::PropertyList& props);
|
||||
};
|
||||
|
||||
#endif // _CONTROLS_HXX
|
||||
|
||||
|
||||
1119
src/Aircraft/flightrecorder.cxx
Normal file
1119
src/Aircraft/flightrecorder.cxx
Normal file
File diff suppressed because it is too large
Load Diff
118
src/Aircraft/flightrecorder.hxx
Normal file
118
src/Aircraft/flightrecorder.hxx
Normal file
@@ -0,0 +1,118 @@
|
||||
// flightrecorder.hxx
|
||||
//
|
||||
// Written by Thorsten Brehm, started August 2011.
|
||||
//
|
||||
// Copyright (C) 2011 Thorsten Brehm - brehmt (at) gmail com
|
||||
//
|
||||
// This program is free software; you can redistribute it and/or
|
||||
// modify it under the terms of the GNU General Public License as
|
||||
// published by the Free Software Foundation; either version 2 of the
|
||||
// License, or (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful, but
|
||||
// WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program; if not, write to the Free Software
|
||||
// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
//
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#ifndef FLIGHTRECORDER_HXX_
|
||||
#define FLIGHTRECORDER_HXX_
|
||||
|
||||
#include <simgear/props/props.hxx>
|
||||
#include <MultiPlayer/multiplaymgr.hxx>
|
||||
#include "replay-internal.hxx"
|
||||
|
||||
namespace FlightRecorder
|
||||
{
|
||||
|
||||
typedef enum
|
||||
{
|
||||
discrete = 0, // no interpolation
|
||||
linear = 1, // linear interpolation
|
||||
angular_rad = 2, // angular interpolation, value in radians
|
||||
angular_deg = 3 // angular interpolation, value in degrees
|
||||
} TInterpolation;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
SGPropertyNode_ptr Signal;
|
||||
TInterpolation Interpolation;
|
||||
} TCapture;
|
||||
|
||||
typedef std::vector<TCapture> TSignalList;
|
||||
|
||||
}
|
||||
|
||||
class FGFlightRecorder
|
||||
{
|
||||
public:
|
||||
FGFlightRecorder(const char* pConfigName);
|
||||
virtual ~FGFlightRecorder();
|
||||
|
||||
void reinit (void);
|
||||
void reinit (SGPropertyNode_ptr ConfigNode);
|
||||
FGReplayData* capture (double SimTime, FGReplayData* pRecycledBuffer);
|
||||
|
||||
// Updates main_window_* out-params if we find window move/resize events
|
||||
// and replay of such events is enabled.
|
||||
void replay (double SimTime, const FGReplayData* pNextBuffer,
|
||||
const FGReplayData* pLastBuffer,
|
||||
int* main_window_xpos,
|
||||
int* main_window_ypos,
|
||||
int* main_window_xsize,
|
||||
int* main_window_ysize
|
||||
);
|
||||
int getRecordSize (void) { return m_TotalRecordSize;}
|
||||
void getConfig (SGPropertyNode* root);
|
||||
void resetExtraProperties();
|
||||
|
||||
private:
|
||||
SGPropertyNode_ptr getDefault(void);
|
||||
void initSignalList(const char* pSignalType, FlightRecorder::TSignalList& SignalList,
|
||||
SGPropertyNode_ptr BaseNode);
|
||||
void processSignalList(const char* pSignalType, FlightRecorder::TSignalList& SignalList,
|
||||
SGPropertyNode_ptr SignalListNode,
|
||||
std::string PropPrefix="", int Count = 1);
|
||||
bool haveProperty(FlightRecorder::TSignalList& Capture,SGPropertyNode* pProperty);
|
||||
bool haveProperty(SGPropertyNode* pProperty);
|
||||
|
||||
int getConfig(SGPropertyNode* root, const char* typeStr, const FlightRecorder::TSignalList& SignalList);
|
||||
|
||||
SGPropertyNode_ptr m_RecorderNode;
|
||||
SGPropertyNode_ptr m_ConfigNode;
|
||||
|
||||
SGPropertyNode_ptr m_ReplayMultiplayer;
|
||||
SGPropertyNode_ptr m_ReplayExtraProperties;
|
||||
SGPropertyNode_ptr m_ReplayMainView;
|
||||
SGPropertyNode_ptr m_ReplayMainWindowPosition;
|
||||
SGPropertyNode_ptr m_ReplayMainWindowSize;
|
||||
|
||||
SGPropertyNode_ptr m_RecordContinuous;
|
||||
SGPropertyNode_ptr m_RecordExtraProperties;
|
||||
|
||||
SGPropertyNode_ptr m_LogRawSpeed;
|
||||
|
||||
// This contains copy of all properties that we are recording, so that we
|
||||
// can send only differences.
|
||||
//
|
||||
SGPropertyNode_ptr m_RecordExtraPropertiesReference;
|
||||
|
||||
FlightRecorder::TSignalList m_CaptureDouble;
|
||||
FlightRecorder::TSignalList m_CaptureFloat;
|
||||
FlightRecorder::TSignalList m_CaptureInteger;
|
||||
FlightRecorder::TSignalList m_CaptureInt16;
|
||||
FlightRecorder::TSignalList m_CaptureInt8;
|
||||
FlightRecorder::TSignalList m_CaptureBool;
|
||||
|
||||
unsigned m_TotalRecordSize;
|
||||
std::string m_ConfigName;
|
||||
bool m_usingDefaultConfig;
|
||||
FGMultiplayMgr* m_MultiplayMgr;
|
||||
};
|
||||
|
||||
#endif /* FLIGHTRECORDER_HXX_ */
|
||||
100
src/Aircraft/initialstate.cxx
Normal file
100
src/Aircraft/initialstate.cxx
Normal file
@@ -0,0 +1,100 @@
|
||||
// initialstate.cxx -- setup initial state of the aircraft
|
||||
//
|
||||
// Written by James Turner
|
||||
//
|
||||
// Copyright (C) 2016 James Turner <zakalawe@mac.com>
|
||||
//
|
||||
// This program is free software; you can redistribute it and/or
|
||||
// modify it under the terms of the GNU General Public License as
|
||||
// published by the Free Software Foundation; either version 2 of the
|
||||
// License, or (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful, but
|
||||
// WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program; if not, write to the Free Software
|
||||
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
//
|
||||
// $Id$
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "initialstate.hxx"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include <simgear/debug/logstream.hxx>
|
||||
#include <simgear/props/props_io.hxx>
|
||||
|
||||
#include <Main/fg_props.hxx>
|
||||
#include <GUI/MessageBox.hxx>
|
||||
|
||||
using namespace simgear;
|
||||
|
||||
namespace {
|
||||
|
||||
class NodeValue
|
||||
{
|
||||
public:
|
||||
NodeValue(const std::string& s) : v(s) {}
|
||||
bool operator()(const SGPropertyNode_ptr n) const
|
||||
{
|
||||
return (v == n->getStringValue());
|
||||
}
|
||||
private:
|
||||
std::string v;
|
||||
};
|
||||
|
||||
SGPropertyNode_ptr nodeForState(const std::string& nm)
|
||||
{
|
||||
SGPropertyNode_ptr sim = fgGetNode("/sim");
|
||||
const PropertyList& states = sim->getChildren("state");
|
||||
PropertyList::const_iterator it;
|
||||
for (it = states.begin(); it != states.end(); ++it) {
|
||||
const PropertyList& names = (*it)->getChildren("name");
|
||||
if (std::find_if(names.begin(), names.end(), NodeValue(nm)) != names.end()) {
|
||||
return *it;
|
||||
}
|
||||
}
|
||||
|
||||
return SGPropertyNode_ptr();
|
||||
}
|
||||
|
||||
} // of anonymous namespace
|
||||
|
||||
namespace flightgear
|
||||
{
|
||||
|
||||
bool isInitialStateName(const std::string& name)
|
||||
{
|
||||
SGPropertyNode_ptr n = nodeForState(name);
|
||||
return n.valid();
|
||||
}
|
||||
|
||||
void applyInitialState()
|
||||
{
|
||||
std::string nm = fgGetString("/sim/aircraft-state");
|
||||
if (nm.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
SGPropertyNode_ptr stateNode = nodeForState(nm);
|
||||
if (!stateNode) {
|
||||
SG_LOG(SG_AIRCRAFT, SG_WARN, "missing state node for:" << nm);
|
||||
std::string aircraft = fgGetString("/sim/aircraft");
|
||||
modalMessageBox("Unknown aircraft state",
|
||||
"The selected aircraft (" + aircraft + ") does not have a state '" + nm + "'");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
SG_LOG(SG_AIRCRAFT, SG_INFO, "Applying aircraft state:" << nm);
|
||||
|
||||
// copy all overlay properties to the tree
|
||||
copyProperties(stateNode->getChild("overlay"), globals->get_props());
|
||||
}
|
||||
|
||||
} // of namespace flightgear
|
||||
41
src/Aircraft/initialstate.hxx
Normal file
41
src/Aircraft/initialstate.hxx
Normal file
@@ -0,0 +1,41 @@
|
||||
// initialstate.hxx -- setup initial state of the aircraft
|
||||
//
|
||||
// Written by James Turner
|
||||
//
|
||||
// Copyright (C) 2016 James Turner <zakalawe@mac.com>
|
||||
//
|
||||
// This program is free software; you can redistribute it and/or
|
||||
// modify it under the terms of the GNU General Public License as
|
||||
// published by the Free Software Foundation; either version 2 of the
|
||||
// License, or (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful, but
|
||||
// WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program; if not, write to the Free Software
|
||||
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
//
|
||||
// $Id$
|
||||
|
||||
|
||||
#ifndef FG_AIRCRAFT_INITIAL_STATE_HXX
|
||||
#define FG_AIRCRAFT_INITIAL_STATE_HXX
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace flightgear
|
||||
{
|
||||
|
||||
/**
|
||||
* @brief is the supplied name a defined initial-state, or alias of one
|
||||
*/
|
||||
bool isInitialStateName(const std::string& name);
|
||||
|
||||
void applyInitialState();
|
||||
|
||||
} // of namespace flightgear
|
||||
|
||||
#endif // FG_AIRCRAFT_INITIAL_STATE_HXX
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user