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);
|
||||
};
|
||||
Reference in New Issue
Block a user