13double fgIsFinite(
double x) {
return _finite(x); }
18#include <simgear/math/sg_geodesy.hxx>
19#include <simgear/math/sg_random.hxx>
20#include <simgear/sg_inlines.h>
21#include <simgear/timing/sg_time.hxx>
25#include <simgear/scene/util/SGNodeMasks.hxx>
45 _roll_constant(0.001),
46 _roll_factor(-0.0083335),
70 setRudder(scFileNode->getFloatValue(
"rudder", 0.0));
71 setName(scFileNode->getStringValue(
"name",
"Titanic"));
72 setRadius(scFileNode->getDoubleValue(
"turn-radius-ft", 2000));
73 const std::string& flightplan = scFileNode->getStringValue(
"flightplan");
74 setRepeat(scFileNode->getBoolValue(
"repeat",
false));
75 setRestart(scFileNode->getBoolValue(
"restart",
false));
76 setStartTime(scFileNode->getStringValue(
"time",
""));
83 setSMPath(scFileNode->getStringValue(
"submodel-path",
""));
86 if (!flightplan.empty()) {
87 std::unique_ptr<FGAIFlightPlan> plan(
new FGAIFlightPlan(flightplan));
105 props->setStringValue(
"waypoint/name-prev", _prev_name.c_str());
106 props->setStringValue(
"waypoint/name-curr", _curr_name.c_str());
107 props->setStringValue(
"waypoint/name-next", _next_name.c_str());
108 props->setStringValue(
"submodels/path",
_path.c_str());
109 props->setStringValue(
"waypoint/start-time", _start_time.c_str());
110 props->setStringValue(
"waypoint/wait-until-time", _until_time.c_str());
119 _fp_init = initFlightPlan();
128 tie(
"surface-positions/rudder-pos-deg",
129 SGRawValuePointer<float>(&_rudder));
130 tie(
"controls/heading-lock",
132 tie(
"controls/tgt-speed-kts",
134 tie(
"controls/tgt-heading-degs",
136 tie(
"controls/constants/roll-factor",
137 SGRawValuePointer<double>(&_roll_factor));
138 tie(
"controls/constants/roll",
139 SGRawValuePointer<double>(&_roll_constant));
140 tie(
"controls/constants/rudder",
142 tie(
"controls/constants/speed",
144 tie(
"waypoint/range-nm",
146 tie(
"waypoint/brg-deg",
147 SGRawValuePointer<double>(&_course));
148 tie(
"waypoint/rangerate-nm-sec",
149 SGRawValuePointer<double>(&_range_rate));
152 tie(
"waypoint/missed",
153 SGRawValuePointer<bool>(&_missed));
154 tie(
"waypoint/missed-count-sec",
156 tie(
"waypoint/missed-range-nm",
158 tie(
"waypoint/missed-time-sec",
159 SGRawValuePointer<double>(&_missed_time_sec));
160 tie(
"waypoint/wait-count-sec",
162 tie(
"waypoint/xtrack-error-ft",
163 SGRawValuePointer<double>(&_xtrack_error));
164 tie(
"waypoint/waiting",
165 SGRawValuePointer<bool>(&
_waiting));
166 tie(
"waypoint/lead-angle-deg",
167 SGRawValuePointer<double>(&_lead_angle));
168 tie(
"waypoint/tunnel",
169 SGRawValuePointer<bool>(&
_tunnel));
170 tie(
"waypoint/alt-curr-m",
171 SGRawValuePointer<double>(&_curr_alt));
172 tie(
"waypoint/alt-prev-m",
173 SGRawValuePointer<double>(&_prev_alt));
174 tie(
"submodels/serviceable",
176 tie(
"controls/turn-radius-ft",
178 tie(
"controls/turn-radius-corrected-ft",
179 SGRawValuePointer<double>(&_rd_turn_radius_ft));
180 tie(
"controls/constants/lead-angle/gain",
181 SGRawValuePointer<double>(&_lead_angle_gain));
182 tie(
"controls/constants/lead-angle/limit-deg",
183 SGRawValuePointer<double>(&_lead_angle_limit));
184 tie(
"controls/constants/lead-angle/proportion",
185 SGRawValuePointer<double>(&_proportion));
186 tie(
"controls/fixed-turn-radius-ft",
187 SGRawValuePointer<double>(&_fixed_turn_radius));
188 tie(
"controls/restart",
189 SGRawValuePointer<bool>(&
_restart));
190 tie(
"velocities/speed-kts",
191 SGRawValuePointer<double>(&
speed));
192 tie(
"velocities/uBody-fps",
208 SGQuatd ec2hl = SGQuatd::fromLonLat(
pos);
210 SGQuatd hl2body = SGQuatd::fromYawPitchRollDeg(
hdg,
pitch,
roll);
213 SGQuatd ec2body = ec2hl * hl2body;
218 aip.setReferenceTime(
globals->get_sim_time_sec());
222 aip.setBodyLinearVelocity(SGVec3d(
speed * SG_KT_TO_MPS, 0, 0));
238 if (SGLimits<double>::min() < dt) {
242 SGQuatd ec2hlNew = SGQuatd::fromLonLat(
pos);
244 SGQuatd hl2bodyNew = SGQuatd::fromYawPitchRollDeg(
hdg,
pitch,
roll);
246 SGQuatd dOr = inverse(ec2body) * ec2hlNew * hl2bodyNew;
247 SGVec3d dOrAngleAxis;
248 dOr.getAngleAxis(dOrAngleAxis);
252 aip.setBodyAngularVelocity(dOrAngleAxis);
259void FGAIShip::Run(
double dt)
272 if (fabs(speed_diff) > 0.1) {
273 if (speed_diff > 0.0)
276 if (speed_diff < 0.0)
289 SGQuatd hlOr = SGQuatd::fromLonLat(
pos);
293 SGQuatd hlToBody = SGQuatd::fromYawPitchRollDeg(
hdg,
pitch,
roll);
297 SGQuatd ec2body = hlOr * hlToBody;
300 SGVec3d position = SGVec3d::fromGeod(
pos);
305 SGQuatd q(-0.5, -0.5, 0.5, 0.5);
307 SGVec3d offset(0, 0, -
speed * SG_KT_TO_MPS * dt);
308 position += (ec2body * q).backTransform(offset);
309 pos = SGGeod::fromCart(position);
326 if (type ==
"ship" || type ==
"carrier" || type ==
"escort") {
327 if (fabs(
speed) <= 5)
328 _sp_turn_radius_ft = _fixed_turn_radius;
335 if (fabs(
speed) <= 40)
336 _sp_turn_radius_ft = _fixed_turn_radius;
343 if (_rudder <= -0.25 || _rudder >= 0.25) {
349 _rd_turn_radius_ft = (a * exp(b * fabs(_rudder)) + c) * _sp_turn_radius_ft;
352 double alpha = ((
speed * 1.686 * dt) / _rd_turn_radius_ft) * SG_RADIANS_TO_DEGREES;
357 SG_NORMALIZE_RANGE(
hdg, 0.0, 360.0);
360 raw_roll = _roll_factor *
speed * _rudder;
370 roll = (raw_roll * _roll_constant) + (
roll * (1 - _roll_constant));
373 double rudder_diff = 0.0;
375 double rudder_sense = 0.0;
379 diff = fabs(diff - 360);
381 double sum =
hdg + diff;
392 rudder_sense = -rudder_sense;
395 _tgt_rudder = diff * rudder_sense;
397 _tgt_rudder = 45 * rudder_sense;
399 rudder_diff = _tgt_rudder - _rudder;
403 if (type ==
"ship" || type ==
"carrier" || type ==
"escort") {
405 rudder_limit = (-0.825 *
speed) + 35;
411 if (fabs(rudder_diff) > 0.1) {
413 if (rudder_diff > 0.0) {
416 if (_rudder > rudder_limit)
417 _rudder = rudder_limit;
419 }
else if (rudder_diff < 0.0) {
422 if (_rudder < -rudder_limit)
423 _rudder = -rudder_limit;
447void FGAIShip::YawTo(
double angle) {
472void FGAIShip::setStartTime(
const std::string& st)
477void FGAIShip::setUntilTime(
const std::string& ut)
480 props->setStringValue(
"waypoint/wait-until-time", _until_time.c_str());
486 props->setStringValue(
"waypoint/name-curr", _curr_name.c_str());
492 props->setStringValue(
"waypoint/name-next", _next_name.c_str());
498 props->setStringValue(
"waypoint/name-prev", _prev_name.c_str());
501void FGAIShip::setRepeat(
bool r)
506void FGAIShip::setRestart(
bool r)
511void FGAIShip::setMissed(
bool m)
514 props->setBoolValue(
"waypoint/missed", _missed);
529 _lead_angle_gain = g;
534 _lead_angle_limit = l;
554 _fixed_turn_radius = ftr;
559 _roll_factor = rf * -0.0083335;
584 SG_LOG(SG_AI, SG_ALERT,
"AIShip: current wp name error");
592 SG_LOG(SG_AI, SG_DEBUG,
"AIShip: prev wp name " <<
prev->getName());
593 SG_LOG(SG_AI, SG_DEBUG,
"AIShip: current wp name " <<
curr->getName());
594 SG_LOG(SG_AI, SG_DEBUG,
"AIShip: next wp name " <<
next->getName());
597double FGAIShip::getRange(
double lat,
double lon,
double lat2,
double lon2)
const
599 double course, distance, az2;
602 geo_inverse_wgs_84(lat, lon, lat2, lon2, &course, &az2, &distance);
603 distance *= SG_METER_TO_NM;
607double FGAIShip::getCourse(
double lat,
double lon,
double lat2,
double lon2)
const
609 double course, distance, recip;
612 geo_inverse_wgs_84(lat, lon, lat2, lon2, &course, &recip, &distance);
614 SG_LOG(SG_AI, SG_DEBUG,
"AIShip: course " << course);
617 SG_LOG(SG_AI, SG_DEBUG,
"AIShip: recip " << recip);
628 double time_sec = getDaySeconds();
642 _next_run = 0.05 + (0.025 * sg_random());
651 double sp_turn_radius_nm = _sp_turn_radius_ft / 6076.1155;
657 _missed_time_sec = 10 + ((SGD_PI * sp_turn_radius_nm * 60 * 60) / (2 * fabs(
speed)));
659 _missed_time_sec = 10;
680 if (_next_name ==
"TUNNEL") {
683 SG_LOG(SG_AI, SG_DEBUG,
"AIShip: " <<
_name <<
" " << sp_turn_radius_nm);
685 fp->IncrementWaypoint(
false);
686 next =
fp->getNextWaypoint();
688 if (
next->getName() ==
"WAITUNTIL" ||
next->getName() ==
"WAIT" ||
next->getName() ==
"END" ||
next->getName() ==
"TUNNEL")
692 fp->IncrementWaypoint(
false);
693 curr =
fp->getCurrentWaypoint();
694 next =
fp->getNextWaypoint();
696 }
else if (_next_name ==
"END" ||
fp->getNextWaypoint() == 0) {
698 SG_LOG(SG_AI, SG_INFO,
"AIShip: " <<
_name <<
" Flightplan repeating ");
701 curr =
fp->getCurrentWaypoint();
702 next =
fp->getNextWaypoint();
712 SG_LOG(SG_AI, SG_INFO,
"AIShip: " <<
_name <<
" Flightplan restarting ");
716 SG_LOG(SG_AI, SG_ALERT,
"AIShip: " <<
_name <<
" Flightplan dying ");
722 }
else if (_next_name ==
"WAIT") {
724 SG_LOG(SG_AI, SG_DEBUG,
"AIShip: " <<
_name <<
" waiting ");
732 SG_LOG(SG_AI, SG_DEBUG,
"AIShip: " <<
_name <<
" wait done: getting new waypoints ");
735 fp->IncrementWaypoint(
false);
736 next =
fp->getNextWaypoint();
738 if (
next->getName() ==
"WAITUNTIL" ||
next->getName() ==
"WAIT" ||
next->getName() ==
"END" ||
next->getName() ==
"TUNNEL")
742 fp->IncrementWaypoint(
false);
743 curr =
fp->getCurrentWaypoint();
744 next =
fp->getNextWaypoint();
747 }
else if (_next_name ==
"WAITUNTIL") {
748 time_sec = getDaySeconds();
749 double until_time_sec = processTimeString(
next->getTime());
750 _until_time =
next->getTime();
751 setUntilTime(
next->getTime());
752 if (until_time_sec > time_sec) {
753 SG_LOG(SG_AI, SG_INFO,
"AIShip: " <<
_name <<
" " <<
curr->getName() <<
" waiting until: " << _until_time <<
" " << until_time_sec <<
" now " << time_sec);
759 SG_LOG(SG_AI, SG_INFO,
"AIShip: " <<
_name <<
" wait until done: getting new waypoints ");
761 fp->IncrementWaypoint(
false);
763 while (
next->getName() ==
"WAITUNTIL") {
764 fp->IncrementWaypoint(
false);
765 next =
fp->getNextWaypoint();
768 if (
next->getName() ==
"WAIT")
772 fp->IncrementWaypoint(
false);
773 curr =
fp->getCurrentWaypoint();
774 next =
fp->getNextWaypoint();
780 SG_LOG(SG_AI, SG_DEBUG,
"AIShip: " <<
_name <<
" getting new waypoints ");
781 fp->IncrementWaypoint(
false);
782 prev =
fp->getPreviousWaypoint();
783 curr =
fp->getCurrentWaypoint();
784 next =
fp->getNextWaypoint();
800 _curr_alt =
curr->getAltitude();
801 _prev_alt =
prev->getAltitude();
808 _course = getCourse(
pos.getLatitudeDeg(),
pos.getLongitudeDeg(),
curr->getLatitude(),
curr->getLongitude());
813 SG_LOG(SG_AI, SG_ALERT,
"AIShip: Bearing or Range is not a finite number");
818bool FGAIShip::initFlightPlan()
820 SG_LOG(SG_AI, SG_ALERT,
"AIShip: " <<
_name <<
" initializing waypoints ");
827 fp->IncrementWaypoint(
false);
829 prev =
fp->getPreviousWaypoint();
830 curr =
fp->getCurrentWaypoint();
831 next =
fp->getNextWaypoint();
834 SG_LOG(SG_AI, SG_DEBUG,
"AIShip: " <<
_name <<
" re-initializing waypoints ");
835 fp->IncrementWaypoint(
false);
836 curr =
fp->getCurrentWaypoint();
837 next =
fp->getNextWaypoint();
840 if (!_start_time.empty()) {
841 _start_sec = processTimeString(_start_time);
842 double day_sec = getDaySeconds();
844 if (_start_sec < day_sec) {
846 doInit = advanceFlightPlan(_start_sec, day_sec);
848 }
else if (_start_sec > day_sec && _repeat) {
852 doInit = advanceFlightPlan(_start_sec, day_sec);
859 fp->IncrementWaypoint(
false);
860 prev =
fp->getPreviousWaypoint();
861 curr =
fp->getCurrentWaypoint();
862 next =
fp->getNextWaypoint();
886 SG_LOG(SG_AI, SG_ALERT,
"AIShip: " <<
_name <<
" done initialising waypoints " <<
_tunnel);
898double FGAIShip::processTimeString(
const std::string& theTime)
906 Hour =
atoi(theTime.substr(0, 2).c_str());
907 Minute =
atoi(theTime.substr(3, 5).c_str());
908 Second =
atoi(theTime.substr(6, 8).c_str());
911 double time_seconds = Hour * 3600 + Minute * 60 + Second;
916double FGAIShip::getDaySeconds()
921 double day_seconds = t->tm_hour * 3600 + t->tm_min * 60 + t->tm_sec;
926bool FGAIShip::advanceFlightPlan(
double start_sec,
double day_sec)
928 double elapsed_sec = start_sec;
929 double distance_nm = 0;
933 while (elapsed_sec < day_sec) {
934 if (
next->getName() ==
"END" ||
fp->getNextWaypoint() == 0) {
938 curr =
fp->getCurrentWaypoint();
939 next =
fp->getNextWaypoint();
946 }
else if (
next->getName() ==
"WAIT") {
950 elapsed_sec +=
next->getTime_sec();
952 if (elapsed_sec >= day_sec)
955 fp->IncrementWaypoint(
false);
956 next =
fp->getNextWaypoint();
958 if (
next->getName() !=
"WAITUNTIL" &&
next->getName() !=
"WAIT" &&
next->getName() !=
"END") {
960 fp->IncrementWaypoint(
false);
961 curr =
fp->getCurrentWaypoint();
962 next =
fp->getNextWaypoint();
965 }
else if (
next->getName() ==
"WAITUNTIL") {
966 double until_sec = processTimeString(
next->getTime());
968 if (until_sec > _start_sec && start_sec < 0)
971 if (elapsed_sec < until_sec)
972 elapsed_sec = until_sec;
974 if (elapsed_sec >= day_sec)
977 fp->IncrementWaypoint(
false);
978 next =
fp->getNextWaypoint();
980 if (
next->getName() !=
"WAITUNTIL" &&
next->getName() !=
"WAIT") {
982 fp->IncrementWaypoint(
false);
983 curr =
fp->getCurrentWaypoint();
984 next =
fp->getNextWaypoint();
991 distance_nm = getRange(
prev->getLatitude(),
prev->getLongitude(),
curr->getLatitude(),
curr->getLongitude());
992 elapsed_sec += distance_nm * 60 * 60 / fabs(
prev->getSpeed());
994 if (elapsed_sec >= day_sec)
997 fp->IncrementWaypoint(
false);
998 prev =
fp->getPreviousWaypoint();
999 curr =
fp->getCurrentWaypoint();
1000 next =
fp->getNextWaypoint();
1012 double time_diff = elapsed_sec - day_sec;
1013 double lat, lon, recip;
1017 if (
next->getName() ==
"WAIT") {
1019 lat =
curr->getLatitude();
1020 lon =
curr->getLongitude();
1023 }
else if (
next->getName() ==
"WAITUNTIL") {
1025 lat =
curr->getLatitude();
1026 lon =
curr->getLongitude();
1030 distance_nm =
speed * time_diff / (60 * 60);
1031 double brg = getCourse(
curr->getLatitude(),
curr->getLongitude(),
prev->getLatitude(),
prev->getLongitude());
1037 lat = geo_direct_wgs_84(
curr->getLatitude(),
curr->getLongitude(), brg,
1038 distance_nm * SG_NM_TO_METER, &lat, &lon, &recip);
1039 lon = geo_direct_wgs_84(
curr->getLatitude(),
curr->getLongitude(), brg,
1040 distance_nm * SG_NM_TO_METER, &lat, &lon, &recip);
1041 recip = geo_direct_wgs_84(
curr->getLatitude(),
curr->getLongitude(), brg,
1042 distance_nm * SG_NM_TO_METER, &lat, &lon, &recip);
1053 if (
curr->getName() ==
"END" ||
curr->getName() ==
"WAIT" ||
curr->getName() ==
"WAITUNTIL" ||
curr->getName() ==
"TUNNEL") {
1058 wppos.setLatitudeDeg(
curr->getLatitude());
1059 wppos.setLongitudeDeg(
curr->getLongitude());
1060 wppos.setElevationM(0);
1062 if (
curr->getOn_ground()) {
1063 double elevation_m = 0;
1064 if (
globals->get_scenery()->get_elevation_m(
1065 SGGeod::fromGeodM(wppos, 3000), elevation_m, NULL, 0)) {
1066 wppos.setElevationM(elevation_m);
1069 wppos.setElevationM(
curr->getAltitude());
1072 curr->setAltitude(wppos.getElevationM());
1075void FGAIShip::setXTrackError()
1079 double brg = getCourse(
pos.getLatitudeDeg(),
pos.getLongitudeDeg(),
1081 double xtrack_error_nm = sin((course - brg) * SG_DEGREES_TO_RADIANS) *
_wp_range;
1082 double factor = -0.0045 *
speed + 1;
1083 double limit = _lead_angle_limit * factor;
1086 _lead_angle = atan2(xtrack_error_nm, (
_wp_range * _proportion)) * SG_RADIANS_TO_DEGREES;
1090 _lead_angle *= _lead_angle_gain * factor;
1091 _xtrack_error = xtrack_error_nm * 6076.1155;
1093 SG_CLAMP_RANGE(_lead_angle, -limit, limit);
double fgIsFinite(double x)
double fgIsFinite(double x)
void setSpeed(double speed_KTAS)
void setName(const std::string &n)
void setLatitude(double latitude)
virtual void readFromScenario(SGPropertyNode *scFileNode)
void _setAltitude(double _alt)
void setFlightPlan(std::unique_ptr< FGAIFlightPlan > f)
FGAIBase(object_type ot, bool enableHot)
virtual bool init(ModelSearchOrder searchOrder)
void setRadius(double radius)
virtual void update(double dt)
std::unique_ptr< FGAIFlightPlan > fp
double UpdateRadar(FGAIManager *manager)
void setSMPath(const std::string &p)
void setLongitude(double longitude)
void setHeading(double heading)
SGPropertyNode_ptr replay_time
void tie(const char *aRelPath, const SGRawValue< T > &aRawValue)
Tied-properties helper, record nodes which are tied for easy un-tie-ing.
void readFromScenario(SGPropertyNode *scFileNode) override
void setLeadAngleProp(double p)
void PitchTo(double angle)
void RollTo(double angle)
void setCurrName(const std::string &)
void setSpeedConstant(double sc)
void ClimbTo(double altitude)
void setRollFactor(double rf)
void setNextName(const std::string &)
void setLeadAngleLimit(double l)
void update(double dt) override
void TurnTo(double heading)
void AccelTo(double speed)
void ProcessFlightPlan(double dt)
FGAIShip(object_type ot=object_type::otShip)
void setLeadAngleGain(double g)
void setRudderConstant(double rc)
void setPrevName(const std::string &)
bool init(ModelSearchOrder searchOrder) override
std::string_view getTypeString(void) const override
void setFixedTurnRadius(double ft)
void setInitialTunnel(bool t)
double getLatitude() const
const std::string & getName()
double getLongitude() const
SGTime * get_time_params() const
static int atoi(const string &str)