1 // FGAIShip - FGAIBase-derived class creates an AI ship
3 // Written by David Culp, started October 2003.
4 // with major amendments and additions by Vivian Meazza, 2004 - 2007
6 // This program is free software; you can redistribute it and/or
7 // modify it under the terms of the GNU General Public License as
8 // published by the Free Software Foundation; either version 2 of the
9 // License, or (at your option) any later version.
11 // This program is distributed in the hope that it will be useful, but
12 // WITHOUT ANY WARRANTY; without even the implied warranty of
13 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 // General Public License for more details.
16 // You should have received a copy of the GNU General Public License
17 // along with this program; if not, write to the Free Software
18 // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
26 # define finite _finite
27 #elif defined(__sun) || defined(sgi)
32 #include <simgear/math/sg_geodesy.hxx>
33 #include <simgear/math/sg_random.h>
38 FGAIShip::FGAIShip(object_type ot) :
45 FGAIShip::~FGAIShip() {
48 void FGAIShip::readFromScenario(SGPropertyNode* scFileNode) {
52 FGAIBase::readFromScenario(scFileNode);
54 setRudder(scFileNode->getFloatValue("rudder", 0.0));
55 setName(scFileNode->getStringValue("name", "Titanic"));
56 setRadius(scFileNode->getDoubleValue("turn-radius-ft", 2000));
57 std::string flightplan = scFileNode->getStringValue("flightplan");
58 setRepeat(scFileNode->getBoolValue("repeat", false));
60 if (!flightplan.empty()) {
61 FGAIFlightPlan* fp = new FGAIFlightPlan(flightplan);
67 bool FGAIShip::init(bool search_in_AI_path) {
68 prev = 0; // the one behind you
69 curr = 0; // the one ahead
70 next = 0; // the next plus 1
72 props->setStringValue("name", _name.c_str());
73 props->setStringValue("position/waypoint-name-prev", _prev_name.c_str());
74 props->setStringValue("position/waypoint-name-curr", _curr_name.c_str());
75 props->setStringValue("position/waypoint-name-next", _next_name.c_str());
81 _rudder_constant = 0.5;
82 _roll_constant = 0.001;
83 _speed_constant = 0.05;
86 _rd_turn_radius_ft = _sp_turn_radius_ft = turn_radius_ft;
95 _missed_time_sec = 30;
97 _wp_range = _old_range = 0;
101 _fp_init = initFlightPlan();
103 return FGAIBase::init(search_in_AI_path);
107 void FGAIShip::bind() {
110 props->tie("surface-positions/rudder-pos-deg",
111 SGRawValuePointer<float>(&_rudder));
112 props->tie("controls/heading-lock",
113 SGRawValuePointer<bool>(&_hdg_lock));
114 props->tie("controls/tgt-speed-kts",
115 SGRawValuePointer<double>(&tgt_speed));
116 props->tie("controls/tgt-heading-degs",
117 SGRawValuePointer<double>(&tgt_heading));
118 props->tie("controls/constants/rudder",
119 SGRawValuePointer<double>(&_rudder_constant));
120 props->tie("controls/constants/roll",
121 SGRawValuePointer<double>(&_roll_constant));
122 props->tie("controls/constants/rudder",
123 SGRawValuePointer<double>(&_rudder_constant));
124 props->tie("controls/constants/speed",
125 SGRawValuePointer<double>(&_speed_constant));
126 props->tie("position/waypoint-range-nm",
127 SGRawValuePointer<double>(&_wp_range));
128 props->tie("position/waypoint-range-old-nm",
129 SGRawValuePointer<double>(&_old_range));
130 props->tie("position/waypoint-range-rate-nm-sec",
131 SGRawValuePointer<double>(&_range_rate));
132 props->tie("position/waypoint-new",
133 SGRawValuePointer<bool>(&_new_waypoint));
134 props->tie("position/waypoint-missed",
135 SGRawValuePointer<bool>(&_missed));
136 props->tie("position/waypoint-missed-count",
137 SGRawValuePointer<double>(&_missed_count));
138 props->tie("position/waypoint-missed-time-sec",
139 SGRawValuePointer<double>(&_missed_time_sec));
140 props->tie("position/waypoint-wait-count",
141 SGRawValuePointer<double>(&_wait_count));
142 props->tie("position/waypoint-waiting",
143 SGRawValuePointer<bool>(&_waiting));
146 void FGAIShip::unbind() {
148 props->untie("surface-positions/rudder-pos-deg");
149 props->untie("controls/heading-lock");
150 props->untie("controls/tgt-speed-kts");
151 props->untie("controls/tgt-heading-degs");
152 props->untie("controls/constants/roll");
153 props->untie("controls/constants/rudder");
154 props->untie("controls/constants/speed");
155 props->untie("position/waypoint-range-nm");
156 props->untie("position/waypoint-range-old-nm");
157 props->untie("position/waypoint-range-rate-nm-sec");
158 props->untie("position/waypoint-new");
159 props->untie("position/waypoint-missed");
160 props->untie("position/waypoint-wait-count");
161 props->untie("position/waypoint-waiting");
162 props->untie("position/waypoint-missed-time-sec");
165 void FGAIShip::update(double dt) {
166 FGAIBase::update(dt);
171 void FGAIShip::Run(double dt) {
174 ProcessFlightPlan(dt);
176 double speed_north_deg_sec;
177 double speed_east_deg_sec;
183 double speed_diff = tgt_speed - speed;
185 if (fabs(speed_diff) > 0.1) {
187 if (speed_diff > 0.0)
188 speed += _speed_constant * dt;
190 if (speed_diff < 0.0)
191 speed -= _speed_constant * dt;
194 // do not allow unreasonable ship speeds
198 // convert speed to degrees per second
199 speed_north_deg_sec = cos(hdg / SGD_RADIANS_TO_DEGREES)
200 * speed * 1.686 / ft_per_deg_lat;
201 speed_east_deg_sec = sin(hdg / SGD_RADIANS_TO_DEGREES)
202 * speed * 1.686 / ft_per_deg_lon;
205 pos.setLatitudeDeg(pos.getLatitudeDeg() + speed_north_deg_sec * dt);
206 pos.setLongitudeDeg(pos.getLongitudeDeg() + speed_east_deg_sec * dt);
208 // adjust heading based on current _rudder angle
210 //cout << "turn_radius_ft " << turn_radius_ft ;
212 if (turn_radius_ft <= 0)
213 turn_radius_ft = 0; // don't allow nonsense values
221 //we assume that at slow speed ships will manoeuvre using engines/bow thruster
223 _sp_turn_radius_ft = 500;
225 // adjust turn radius for speed. The equation is very approximate.
226 // we need to allow for negative speeds
227 _sp_turn_radius_ft = 10 * pow ((fabs(speed) - 15), 2) + turn_radius_ft;
229 //cout << " speed turn radius " << _sp_turn_radius_ft ;
231 if (_rudder <= -0.25 || _rudder >= 0.25) {
232 // adjust turn radius for _rudder angle. The equation is even more approximate.
237 _rd_turn_radius_ft = (a * exp(b * fabs(_rudder)) + c) * _sp_turn_radius_ft;
239 //cout <<" _rudder turn radius " << _rd_turn_radius_ft << endl;
241 // calculate the angle, alpha, subtended by the arc traversed in time dt
242 alpha = ((speed * 1.686 * dt) / _rd_turn_radius_ft) * SG_RADIANS_TO_DEGREES;
244 // make sure that alpha is applied in the right direction
245 hdg += alpha * sign(_rudder);
253 //adjust roll for _rudder angle and speed. Another bit of voodoo
254 raw_roll = -0.0166667 * speed * _rudder;
256 // _rudder angle is 0
264 roll = (raw_roll * _roll_constant) + (roll * (1 - _roll_constant));
266 // adjust target _rudder angle if heading lock engaged
268 double rudder_sense = 0.0;
269 double diff = fabs(hdg - tgt_heading);
270 //cout << "_rudder diff" << diff << endl;
272 diff = fabs(diff - 360);
274 double sum = hdg + diff;
279 if (fabs(sum - tgt_heading)< 1.0)
285 rudder_sense = -rudder_sense;
288 _tgt_rudder = diff * rudder_sense;
290 _tgt_rudder = 45 * rudder_sense;
293 // adjust _rudder angle
294 double rudder_diff = _tgt_rudder - _rudder;
295 // set the _rudder limit by speed
297 rudder_limit = (-0.825 * speed) + 35;
301 if (fabs(rudder_diff)> 0.1) { // apply dead zone
303 if (rudder_diff > 0.0) {
304 _rudder += _rudder_constant * dt;
306 if (_rudder > rudder_limit) // apply the _rudder limit
307 _rudder = rudder_limit;
309 } else if (rudder_diff < 0.0) {
310 _rudder -= _rudder_constant * dt;
312 if (_rudder < -rudder_limit)
313 _rudder = -rudder_limit;
320 void FGAIShip::AccelTo(double speed) {
324 void FGAIShip::PitchTo(double angle) {
328 void FGAIShip::RollTo(double angle) {
332 void FGAIShip::YawTo(double angle) {
335 void FGAIShip::ClimbTo(double altitude) {
339 void FGAIShip::TurnTo(double heading) {
340 tgt_heading = heading;
344 double FGAIShip::sign(double x) {
351 void FGAIShip::setFlightPlan(FGAIFlightPlan* f) {
355 void FGAIShip::setName(const string& n) {
359 void FGAIShip::setCurrName(const string& c) {
361 props->setStringValue("position/waypoint-name-curr", _curr_name.c_str());
364 void FGAIShip::setNextName(const string& n) {
366 props->setStringValue("position/waypoint-name-next", _next_name.c_str());
369 void FGAIShip::setPrevName(const string& p) {
371 props->setStringValue("position/waypoint-name-prev", _prev_name.c_str());
374 void FGAIShip::setRepeat(bool r) {
378 void FGAIShip::setMissed(bool m) {
380 props->setBoolValue("position/waypoint-missed", _missed);
383 void FGAIShip::ProcessFlightPlan(double dt) {
388 ///////////////////////////////////////////////////////////////////////////
389 // Check Execution time (currently once every 1 sec)
390 // Add a bit of randomization to prevent the execution of all flight plans
391 // in synchrony, which can add significant periodic framerate flutter.
392 ///////////////////////////////////////////////////////////////////////////
393 if (_dt_count < _next_run)
396 _next_run = 1.0 + (0.5 * sg_random());
398 // check to see if we've reached the point for our next turn
399 // if the range to the waypoint is less than the calculated turn
400 // radius we can start the turn to the next leg
401 _wp_range = getRange(pos.getLatitudeDeg(), pos.getLongitudeDeg(), curr->latitude, curr->longitude);
402 _range_rate = (_wp_range - _old_range) / _dt_count;
403 double sp_turn_radius_nm = _sp_turn_radius_ft / 6076.1155;
405 // we need to try to identify a _missed waypoint
407 // calculate the time needed to turn through an arc of 90 degrees, and allow an error of 30 secs
409 _missed_time_sec = 30 + ((SGD_PI * sp_turn_radius_nm * 60 * 60) / (2 * fabs(speed)));
411 _missed_time_sec = 30;
413 if ((_range_rate > 0) && (_wp_range < 3 * sp_turn_radius_nm) && !_new_waypoint)
414 _missed_count += _dt_count;
417 if (_missed_count >= _missed_time_sec) {
423 _old_range = _wp_range;
425 if ((_wp_range < sp_turn_radius_nm) || _missed || _waiting && !_new_waypoint) {
427 if (_next_name == "END") {
430 SG_LOG(SG_GENERAL, SG_INFO, "AIShip: Flightplan restarting ");
433 curr = fp->getCurrentWaypoint();
434 next = fp->getNextWaypoint();
436 _wp_range = getRange(pos.getLatitudeDeg(), pos.getLongitudeDeg(), curr->latitude, curr->longitude);
437 _old_range = _wp_range;
439 _new_waypoint = true;
441 AccelTo(prev->speed);
443 SG_LOG(SG_GENERAL, SG_INFO, "AIShip: Flightplan dieing ");
449 } else if (_next_name == "WAIT") {
451 if (_wait_count < next->wait_time) {
452 SG_LOG(SG_GENERAL, SG_INFO, "AIShip: " << _name << " _waiting ");
455 _wait_count += _dt_count;
459 SG_LOG(SG_GENERAL, SG_INFO, "AIShip: " << _name << " wait done: getting new waypoints ");
461 fp->IncrementWaypoint(false);
462 fp->IncrementWaypoint(false); // do it twice
463 curr = fp->getCurrentWaypoint();
464 next = fp->getNextWaypoint();
470 //now reorganise the waypoints, so that next becomes current and so on
471 SG_LOG(SG_GENERAL, SG_INFO, "AIShip: " << _name << " getting new waypoints ");
472 fp->IncrementWaypoint(false);
473 prev = fp->getPreviousWaypoint(); //first waypoint
474 curr = fp->getCurrentWaypoint(); //second waypoint
475 next = fp->getNextWaypoint(); //third waypoint (might not exist!)
479 _new_waypoint = true;
482 _wp_range = getRange(pos.getLatitudeDeg(), pos.getLongitudeDeg(), curr->latitude, curr->longitude);
483 _old_range = _wp_range;
484 AccelTo(prev->speed);
486 _new_waypoint = false;
489 // now revise the required course for the next way point
490 double course = getCourse(pos.getLatitudeDeg(), pos.getLongitudeDeg(), curr->latitude, curr->longitude);
495 SG_LOG(SG_GENERAL, SG_ALERT, "AIShip: Bearing or Range is not a finite number");
498 } // end Processing FlightPlan
500 void FGAIShip::setRudder(float r) {
504 void FGAIShip::setRoll(double rl) {
508 double FGAIShip::getRange(double lat, double lon, double lat2, double lon2) const {
510 double course, distance, az2;
512 //calculate the bearing and range of the second pos from the first
513 geo_inverse_wgs_84(lat, lon, lat2, lon2, &course, &az2, &distance);
514 distance *= SG_METER_TO_NM;
518 double FGAIShip::getCourse(double lat, double lon, double lat2, double lon2) const {
520 double course, distance, recip;
522 //calculate the bearing and range of the second pos from the first
523 geo_inverse_wgs_84(lat, lon, lat2, lon2, &course, &recip, &distance);
524 if (tgt_speed >= 0) {
531 bool FGAIShip::initFlightPlan() {
532 SG_LOG(SG_GENERAL, SG_ALERT, "AIShip: " << _name << " initialising waypoints ");
534 fp->IncrementWaypoint(false);
536 prev = fp->getPreviousWaypoint(); //first waypoint
537 curr = fp->getCurrentWaypoint(); //second waypoint
538 next = fp->getNextWaypoint(); //third waypoint (might not exist!)
540 if (curr->name == "WAIT") { // don't wait when initialising
541 SG_LOG(SG_GENERAL, SG_ALERT, "AIShip: " << _name << " re-initialising waypoints ");
542 fp->IncrementWaypoint(false);
543 curr = fp->getCurrentWaypoint();
544 next = fp->getNextWaypoint();
548 setLatitude(prev->latitude);
549 setLongitude(prev->longitude);
550 setSpeed(prev->speed);
551 setHeading(getCourse(prev->latitude, prev->longitude, curr->latitude, curr->longitude));
553 _wp_range = getRange(pos.getLatitudeDeg(), pos.getLongitudeDeg(), curr->latitude, curr->longitude);
554 _old_range = _wp_range;
558 _new_waypoint = true;
560 SG_LOG(SG_GENERAL, SG_INFO, "AIShip: " << _name << " done initialising waypoints ");
567 } // end of initialization
569 void FGAIShip::setWPNames() {
572 setPrevName(prev->name);
576 setCurrName(curr->name);
579 setNextName(next->name);
583 SG_LOG(SG_GENERAL, SG_INFO, "AIShip: prev wp name " << prev->name);
584 SG_LOG(SG_GENERAL, SG_INFO, "AIShip: current wp name " << curr->name);
585 SG_LOG(SG_GENERAL, SG_INFO, "AIShip: next wp name " << next->name);