FlightGear next
AIShip.cxx
Go to the documentation of this file.
1/*
2 * SPDX-FileName: AIShip.cxx
3 * SPDX-FileComment: AIBase-derived class creates an AI ship
4 * SPDX-FileCopyrightText: Copyright (C) 2003 David P. Culp - davidculp2@comcast.net
5 * SPDX-FileContributor: with major amendments and additions by Vivian Meazza, 2004 - 2007
6 * SPDX-License-Identifier: GPL-2.0-or-later
7 */
8
9#include <cmath>
10
11#ifdef _MSC_VER
12#include <float.h>
13double fgIsFinite(double x) { return _finite(x); }
14#else
15double fgIsFinite(double x) { return std::isfinite(x); }
16#endif
17
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>
22
23#include <Main/globals.hxx>
24#include <Scenery/scenery.hxx>
25#include <simgear/scene/util/SGNodeMasks.hxx>
26
27#include "AIShip.hxx"
28
29
30FGAIShip::FGAIShip(object_type ot) : // allow HOT to be enabled
31 FGAIBase(ot, true),
32 _waiting(false),
33 _new_waypoint(true),
34 _tunnel(false),
35 _initial_tunnel(false),
36 _restart(false),
37 _hdg_constant(0.01),
38 _limit(100),
40 _tow_angle(0),
42 _wp_range(0),
43 _dt_count(0),
44 _next_run(0),
45 _roll_constant(0.001),
46 _roll_factor(-0.0083335),
47 _old_range(0),
48 _range_rate(0),
49 _missed_time_sec(30),
50 _day(86400),
51 _lead_angle(0),
52 _xtrack_error(0),
53 _curr_alt(0),
54 _prev_alt(0),
55 _until_time(""),
56 _fp_init(false),
57 _missed(false)
58{
59 _elevation_m = 0.0;
60 invisible = false;
61}
62
63void FGAIShip::readFromScenario(SGPropertyNode* scFileNode)
64{
65 if (!scFileNode)
66 return;
67
69
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", ""));
77 setLeadAngleGain(scFileNode->getDoubleValue("lead-angle-gain", 1.5));
78 setLeadAngleLimit(scFileNode->getDoubleValue("lead-angle-limit-deg", 15));
79 setLeadAngleProp(scFileNode->getDoubleValue("lead-angle-proportion", 0.75));
80 setRudderConstant(scFileNode->getDoubleValue("rudder-constant", 0.5));
81 setFixedTurnRadius(scFileNode->getDoubleValue("fixed-turn-radius-ft", 500));
82 setSpeedConstant(scFileNode->getDoubleValue("speed-constant", 0.5));
83 setSMPath(scFileNode->getStringValue("submodel-path", ""));
84 setRollFactor(scFileNode->getDoubleValue("roll-factor", 1));
85
86 if (!flightplan.empty()) {
87 std::unique_ptr<FGAIFlightPlan> plan(new FGAIFlightPlan(flightplan));
88 setFlightPlan(std::move(plan));
89 }
90}
91
93{
94 reinit();
95 return FGAIBase::init(searchOrder);
96}
97
99{
100 prev = 0; // the one behind you
101 curr = 0; // the one ahead
102 next = 0; // the next plus 1
103
104 props->setStringValue("name", _name.c_str());
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());
111
112 _hdg_lock = false;
113 _rudder = 0.0;
114 no_roll = false;
115
116 _rd_turn_radius_ft = _sp_turn_radius_ft = turn_radius_ft;
117
118 if (fp)
119 _fp_init = initFlightPlan();
120
122}
123
125{
127
128 tie("surface-positions/rudder-pos-deg",
129 SGRawValuePointer<float>(&_rudder));
130 tie("controls/heading-lock",
131 SGRawValuePointer<bool>(&_hdg_lock));
132 tie("controls/tgt-speed-kts",
133 SGRawValuePointer<double>(&tgt_speed));
134 tie("controls/tgt-heading-degs",
135 SGRawValuePointer<double>(&tgt_heading));
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",
141 SGRawValuePointer<double>(&_rudder_constant));
142 tie("controls/constants/speed",
143 SGRawValuePointer<double>(&_speed_constant));
144 tie("waypoint/range-nm",
145 SGRawValuePointer<double>(&_wp_range));
146 tie("waypoint/brg-deg",
147 SGRawValuePointer<double>(&_course));
148 tie("waypoint/rangerate-nm-sec",
149 SGRawValuePointer<double>(&_range_rate));
150 tie("waypoint/new",
151 SGRawValuePointer<bool>(&_new_waypoint));
152 tie("waypoint/missed",
153 SGRawValuePointer<bool>(&_missed));
154 tie("waypoint/missed-count-sec",
155 SGRawValuePointer<double>(&_missed_count));
156 tie("waypoint/missed-range-nm",
157 SGRawValuePointer<double>(&_missed_range));
158 tie("waypoint/missed-time-sec",
159 SGRawValuePointer<double>(&_missed_time_sec));
160 tie("waypoint/wait-count-sec",
161 SGRawValuePointer<double>(&_wait_count));
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",
175 SGRawValuePointer<bool>(&_serviceable));
176 tie("controls/turn-radius-ft",
177 SGRawValuePointer<double>(&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",
193 SGRawValuePointer<double>(&speed_fps));
194 // _tiedProperties is /ai/models/carrier[].
195 // this->props is /ai/models/carrier[].
196}
197
198
199void FGAIShip::update(double dt)
200{
201 if (replay_time->getDoubleValue() <= 0) {
202 //SG_LOG(SG_AI, SG_ALERT, "updating Ship: " << _name <<hdg<<pitch<<roll);
203 // For computation of rotation speeds we just use finite differences here.
204 // That is perfectly valid since this thing is not driven by accelerations
205 // but by just apply discrete changes at its velocity variables.
206 // Update the velocity information stored in those nodes.
207 // Transform that one to the horizontal local coordinate system.
208 SGQuatd ec2hl = SGQuatd::fromLonLat(pos);
209 // The orientation of the ship wrt the horizontal local frame
210 SGQuatd hl2body = SGQuatd::fromYawPitchRollDeg(hdg, pitch, roll);
211 // and postrotate the orientation of the AIModel wrt the horizontal
212 // local frame
213 SGQuatd ec2body = ec2hl * hl2body;
214 // The cartesian position of the ship in the wgs84 world
215 //SGVec3d cartPos = SGVec3d::fromGeod(pos);
216
217 // The simulation time this transform is meant for
218 aip.setReferenceTime(globals->get_sim_time_sec());
219
220 // Compute the velocity in m/s in the body frame
221 // <speed> is in knots.
222 aip.setBodyLinearVelocity(SGVec3d(speed * SG_KT_TO_MPS, 0, 0));
223
224 // Update speed_fps so that velocities/uBody-fps will be set. <speed>
225 // is in knots.
226 //
227 {
228 speed_fps = speed * SG_KT_TO_FPS;
229 }
231 Run(dt);
232 Transform();
233
234 if (fp)
235 setXTrackError();
236
237 // Only change these values if we are able to compute them safely
238 if (SGLimits<double>::min() < dt) {
239 // Now here is the finite difference ...
240
241 // Transform that one to the horizontal local coordinate system.
242 SGQuatd ec2hlNew = SGQuatd::fromLonLat(pos);
243 // compute the new orientation
244 SGQuatd hl2bodyNew = SGQuatd::fromYawPitchRollDeg(hdg, pitch, roll);
245 // The rotation difference
246 SGQuatd dOr = inverse(ec2body) * ec2hlNew * hl2bodyNew;
247 SGVec3d dOrAngleAxis;
248 dOr.getAngleAxis(dOrAngleAxis);
249 // divided by the time difference provides a rotation speed vector
250 dOrAngleAxis /= dt;
251
252 aip.setBodyAngularVelocity(dOrAngleAxis);
253 }
254 } else {
255 Transform();
256 }
257}
258
259void FGAIShip::Run(double dt)
260{
261 if (_fp_init)
263
264 auto type = getTypeString();
265
266 double rudder_limit;
267 double raw_roll;
268
269 // adjust speed
270 double speed_diff = tgt_speed - speed;
271
272 if (fabs(speed_diff) > 0.1) {
273 if (speed_diff > 0.0)
274 speed += _speed_constant * dt;
275
276 if (speed_diff < 0.0)
277 speed -= _speed_constant * dt;
278
279 } else {
281 }
282
283 // do not allow unreasonable speeds
284 SG_CLAMP_RANGE(speed, -_limit * 0.75, _limit);
285
286 {
287 /* The rotation rotating from the earth centered frame to the horizontal
288 local frame. */
289 SGQuatd hlOr = SGQuatd::fromLonLat(pos);
290
291 /* The rotation from the horizontal local frame to the basic view
292 orientation. */
293 SGQuatd hlToBody = SGQuatd::fromYawPitchRollDeg(hdg, pitch, roll);
294
295 /* Compute the eyepoints orientation and position wrt the earth
296 centered frame - that is global coordinates. */
297 SGQuatd ec2body = hlOr * hlToBody;
298
299 /* The cartesian position of the basic view coordinate. */
300 SGVec3d position = SGVec3d::fromGeod(pos);
301
302 /* This is rotates the x-forward, y-right, z-down coordinate system the
303 where simulation runs into the OpenGL camera system with x-right, y-up,
304 z-back. */
305 SGQuatd q(-0.5, -0.5, 0.5, 0.5);
306
307 SGVec3d offset(0, 0, -speed * SG_KT_TO_MPS * dt);
308 position += (ec2body * q).backTransform(offset);
309 pos = SGGeod::fromCart(position);
310
312 }
313
314 // adjust heading based on current _rudder angle
315 if (turn_radius_ft <= 0)
316 turn_radius_ft = 0; // don't allow nonsense values
317
318 if (_rudder > 45)
319 _rudder = 45;
320
321 if (_rudder < -45)
322 _rudder = -45;
323
324
325 // we assume that at slow speed ships will manoeuver using engines/bow thruster
326 if (type == "ship" || type == "carrier" || type == "escort") { // TODO: fragile code... should this be an enum???
327 if (fabs(speed) <= 5)
328 _sp_turn_radius_ft = _fixed_turn_radius;
329 else {
330 // adjust turn radius for speed. The equation is very approximate.
331 // we need to allow for negative speeds
332 _sp_turn_radius_ft = 10 * pow((fabs(speed) - 15), 2) + turn_radius_ft;
333 }
334 } else {
335 if (fabs(speed) <= 40)
336 _sp_turn_radius_ft = _fixed_turn_radius;
337 else {
338 // adjust turn radius for speed.
339 _sp_turn_radius_ft = turn_radius_ft;
340 }
341 }
342
343 if (_rudder <= -0.25 || _rudder >= 0.25) {
344 // adjust turn radius for _rudder angle. The equation is even more approximate.
345 float a = 19;
346 float b = -0.2485;
347 float c = 0.543;
348
349 _rd_turn_radius_ft = (a * exp(b * fabs(_rudder)) + c) * _sp_turn_radius_ft;
350
351 // calculate the angle, alpha, subtended by the arc traversed in time dt
352 double alpha = ((speed * 1.686 * dt) / _rd_turn_radius_ft) * SG_RADIANS_TO_DEGREES;
353 //cout << _name << " alpha " << alpha << endl;
354 // make sure that alpha is applied in the right direction
355 hdg += alpha * sign(_rudder);
356
357 SG_NORMALIZE_RANGE(hdg, 0.0, 360.0);
358
359 //adjust roll for rudder angle and speed. Another bit of voodoo
360 raw_roll = _roll_factor * speed * _rudder;
361 } else {
362 // _rudder angle is 0
363 raw_roll = 0;
364 }
365
366 //low pass filter
367 if (speed < 0)
368 roll = -roll;
369
370 roll = (raw_roll * _roll_constant) + (roll * (1 - _roll_constant));
371
372 // adjust target _rudder angle if heading lock engaged
373 double rudder_diff = 0.0;
374 if (_hdg_lock) {
375 double rudder_sense = 0.0;
376 double diff = fabs(hdg - tgt_heading);
377 //cout << "_rudder diff" << diff << endl;
378 if (diff > 180)
379 diff = fabs(diff - 360);
380
381 double sum = hdg + diff;
382
383 if (sum > 360.0)
384 sum -= 360.0;
385
386 if (fabs(sum - tgt_heading) < 1.0)
387 rudder_sense = 1.0;
388 else
389 rudder_sense = -1.0;
390
391 if (speed < 0)
392 rudder_sense = -rudder_sense;
393
394 if (diff < 15)
395 _tgt_rudder = diff * rudder_sense;
396 else
397 _tgt_rudder = 45 * rudder_sense;
398
399 rudder_diff = _tgt_rudder - _rudder;
400 }
401
402 // set the _rudder limit by speed
403 if (type == "ship" || type == "carrier" || type == "escort") { // TODO: fragile code... should this be an enum???
404 if (speed <= 40)
405 rudder_limit = (-0.825 * speed) + 35;
406 else
407 rudder_limit = 2;
408 } else
409 rudder_limit = 20;
410
411 if (fabs(rudder_diff) > 0.1) { // apply dead zone
412
413 if (rudder_diff > 0.0) {
414 _rudder += _rudder_constant * dt;
415
416 if (_rudder > rudder_limit) // apply the _rudder limit
417 _rudder = rudder_limit;
418
419 } else if (rudder_diff < 0.0) {
420 _rudder -= _rudder_constant * dt;
421
422 if (_rudder < -rudder_limit)
423 _rudder = -rudder_limit;
424 }
425
426 // do calculations for radar
428 }
429} //end function
430
432{
434}
435
436void FGAIShip::PitchTo(double angle)
437{
438 tgt_pitch = angle;
439}
440
441void FGAIShip::RollTo(double angle)
442{
443 tgt_roll = angle;
444}
445
446#if 0
447void FGAIShip::YawTo(double angle) {
448}
449#endif
450
456
457void FGAIShip::TurnTo(double heading)
458{
459 tgt_heading = heading - _lead_angle + _tow_angle;
460 SG_NORMALIZE_RANGE(tgt_heading, 0.0, 360.0);
461 _hdg_lock = true;
462}
463
464double FGAIShip::sign(double x)
465{
466 if (x < 0.0)
467 return -1.0;
468 else
469 return 1.0;
470}
471
472void FGAIShip::setStartTime(const std::string& st)
473{
474 _start_time = st;
475}
476
477void FGAIShip::setUntilTime(const std::string& ut)
478{
479 _until_time = ut;
480 props->setStringValue("waypoint/wait-until-time", _until_time.c_str());
481}
482
483void FGAIShip::setCurrName(const std::string& c)
484{
485 _curr_name = c;
486 props->setStringValue("waypoint/name-curr", _curr_name.c_str());
487}
488
489void FGAIShip::setNextName(const std::string& n)
490{
491 _next_name = n;
492 props->setStringValue("waypoint/name-next", _next_name.c_str());
493}
494
495void FGAIShip::setPrevName(const std::string& p)
496{
497 _prev_name = p;
498 props->setStringValue("waypoint/name-prev", _prev_name.c_str());
499}
500
501void FGAIShip::setRepeat(bool r)
502{
503 _repeat = r;
504}
505
506void FGAIShip::setRestart(bool r)
507{
508 _restart = r;
509}
510
511void FGAIShip::setMissed(bool m)
512{
513 _missed = m;
514 props->setBoolValue("waypoint/missed", _missed);
515}
516
518{
519 _rudder = r;
520}
521
522void FGAIShip::setRoll(double rl)
523{
524 roll = rl;
525}
526
528{
529 _lead_angle_gain = g;
530}
531
533{
534 _lead_angle_limit = l;
535}
536
538{
539 _proportion = p;
540}
541
543{
544 _rudder_constant = rc;
545}
546
548{
549 _speed_constant = sc;
550}
551
553{
554 _fixed_turn_radius = ftr;
555}
556
558{
559 _roll_factor = rf * -0.0083335;
560}
561
567
569{
570 _tunnel = t;
571}
572
574{
575 if (prev != 0)
576 setPrevName(prev->getName());
577 else
578 setPrevName("");
579
580 if (curr != 0)
581 setCurrName(curr->getName());
582 else {
583 setCurrName("");
584 SG_LOG(SG_AI, SG_ALERT, "AIShip: current wp name error");
585 }
586
587 if (next != 0)
588 setNextName(next->getName());
589 else
590 setNextName("");
591
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());
595}
596
597double FGAIShip::getRange(double lat, double lon, double lat2, double lon2) const
598{
599 double course, distance, az2;
600
601 // calculate the bearing and range of the second pos from the first
602 geo_inverse_wgs_84(lat, lon, lat2, lon2, &course, &az2, &distance);
603 distance *= SG_METER_TO_NM;
604 return distance;
605}
606
607double FGAIShip::getCourse(double lat, double lon, double lat2, double lon2) const
608{
609 double course, distance, recip;
610
611 // calculate the bearing and range of the second pos from the first
612 geo_inverse_wgs_84(lat, lon, lat2, lon2, &course, &recip, &distance);
613 if (tgt_speed >= 0) {
614 SG_LOG(SG_AI, SG_DEBUG, "AIShip: course " << course);
615 return course;
616 } else {
617 SG_LOG(SG_AI, SG_DEBUG, "AIShip: recip " << recip);
618 return recip;
619 }
620}
621
623{
624 if (dt < 0.00001) {
625 return;
626 }
627
628 double time_sec = getDaySeconds();
629
630 _dt_count += dt;
631
633 // Check Execution time (currently once every 1 sec)
634 // Add a bit of randomization to prevent the execution of all flight plans
635 // in synchrony, which can add significant periodic framerate flutter.
637
638 //cout << "_start_sec " << _start_sec << " time_sec " << time_sec << endl;
639 if (_dt_count < _next_run && _start_sec < time_sec)
640 return;
641
642 _next_run = 0.05 + (0.025 * sg_random());
643
644 _missed = false;
645
646 // check to see if we've reached the point for our next turn
647 // if the range to the waypoint is less than the calculated turn
648 // radius we can start the turn to the next leg
649 _wp_range = getRange(pos.getLatitudeDeg(), pos.getLongitudeDeg(), curr->getLatitude(), curr->getLongitude());
650 _range_rate = (_wp_range - _old_range) / _dt_count;
651 double sp_turn_radius_nm = _sp_turn_radius_ft / 6076.1155;
652 // we need to try to identify a _missed waypoint
653
654 // calculate the time needed to turn through an arc of 90 degrees,
655 // and allow a time error
656 if (speed != 0)
657 _missed_time_sec = 10 + ((SGD_PI * sp_turn_radius_nm * 60 * 60) / (2 * fabs(speed)));
658 else
659 _missed_time_sec = 10;
660
661 _missed_range = 4 * sp_turn_radius_nm;
662
663 //cout << _name << " range_rate " << _range_rate << " " << _new_waypoint<< endl ;
664 //if ((_range_rate > 0) && !_new_waypoint){
665 if (_range_rate > 0 && _wp_range < _missed_range && !_new_waypoint) {
667 }
668
669 if (_missed_count >= 120)
670 setMissed(true);
671 else if (_missed_count >= _missed_time_sec)
672 setMissed(true);
673 else
674 setMissed(false);
675
676 _old_range = _wp_range;
677 setWPNames();
678
679 if ((_wp_range < (sp_turn_radius_nm * 1.25)) || _missed || (_waiting && !_new_waypoint)) {
680 if (_next_name == "TUNNEL") {
681 _tunnel = !_tunnel;
682
683 SG_LOG(SG_AI, SG_DEBUG, "AIShip: " << _name << " " << sp_turn_radius_nm);
684
685 fp->IncrementWaypoint(false);
686 next = fp->getNextWaypoint();
687
688 if (next->getName() == "WAITUNTIL" || next->getName() == "WAIT" || next->getName() == "END" || next->getName() == "TUNNEL")
689 return;
690
691 prev = curr;
692 fp->IncrementWaypoint(false);
693 curr = fp->getCurrentWaypoint();
694 next = fp->getNextWaypoint();
695
696 } else if (_next_name == "END" || fp->getNextWaypoint() == 0) {
697 if (_repeat) {
698 SG_LOG(SG_AI, SG_INFO, "AIShip: " << _name << " Flightplan repeating ");
699 fp->restart();
700 prev = curr;
701 curr = fp->getCurrentWaypoint();
702 next = fp->getNextWaypoint();
703 setWPNames();
704 _wp_range = getRange(pos.getLatitudeDeg(), pos.getLongitudeDeg(), curr->getLatitude(), curr->getLongitude());
705 _old_range = _wp_range;
706 _range_rate = 0;
707 _new_waypoint = true;
708 _missed_count = 0;
709 _lead_angle = 0;
710 AccelTo(prev->getSpeed());
711 } else if (_restart) {
712 SG_LOG(SG_AI, SG_INFO, "AIShip: " << _name << " Flightplan restarting ");
713 _missed_count = 0;
714 initFlightPlan();
715 } else {
716 SG_LOG(SG_AI, SG_ALERT, "AIShip: " << _name << " Flightplan dying ");
717 setDie(true);
718 _dt_count = 0;
719 return;
720 }
721
722 } else if (_next_name == "WAIT") {
723 if (_wait_count < next->getTime_sec()) {
724 SG_LOG(SG_AI, SG_DEBUG, "AIShip: " << _name << " waiting ");
725 setSpeed(0);
726 _waiting = true;
728 _dt_count = 0;
729 _lead_angle = 0;
730 return;
731 } else {
732 SG_LOG(SG_AI, SG_DEBUG, "AIShip: " << _name << " wait done: getting new waypoints ");
733 _waiting = false;
734 _wait_count = 0;
735 fp->IncrementWaypoint(false);
736 next = fp->getNextWaypoint();
737
738 if (next->getName() == "WAITUNTIL" || next->getName() == "WAIT" || next->getName() == "END" || next->getName() == "TUNNEL")
739 return;
740
741 prev = curr;
742 fp->IncrementWaypoint(false);
743 curr = fp->getCurrentWaypoint();
744 next = fp->getNextWaypoint();
745 }
746
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);
754 setSpeed(0);
755 _lead_angle = 0;
756 _waiting = true;
757 return;
758 } else {
759 SG_LOG(SG_AI, SG_INFO, "AIShip: " << _name << " wait until done: getting new waypoints ");
760 setUntilTime("");
761 fp->IncrementWaypoint(false);
762
763 while (next->getName() == "WAITUNTIL") {
764 fp->IncrementWaypoint(false);
765 next = fp->getNextWaypoint();
766 }
767
768 if (next->getName() == "WAIT")
769 return;
770
771 prev = curr;
772 fp->IncrementWaypoint(false);
773 curr = fp->getCurrentWaypoint();
774 next = fp->getNextWaypoint();
775 _waiting = false;
776 }
777
778 } else {
779 //now reorganize the waypoints, so that next becomes current and so on
780 SG_LOG(SG_AI, SG_DEBUG, "AIShip: " << _name << " getting new waypoints ");
781 fp->IncrementWaypoint(false);
782 prev = fp->getPreviousWaypoint(); //first waypoint
783 curr = fp->getCurrentWaypoint(); //second waypoint
784 next = fp->getNextWaypoint(); //third waypoint (might not exist!)
785 }
786
787 setWPNames();
788 _new_waypoint = true;
789 _missed_count = 0;
790 _range_rate = 0;
791 _lead_angle = 0;
792 _wp_range = getRange(pos.getLatitudeDeg(), pos.getLongitudeDeg(), curr->getLatitude(), curr->getLongitude());
793 _old_range = _wp_range;
794 setWPPos();
795 object_type type = getType();
796
797 if (type != object_type::otGroundVehicle) // is this correct???
798 AccelTo(prev->getSpeed());
799
800 _curr_alt = curr->getAltitude();
801 _prev_alt = prev->getAltitude();
802
803 } else {
804 _new_waypoint = false;
805 }
806
807 // now revise the required course for the next way point
808 _course = getCourse(pos.getLatitudeDeg(), pos.getLongitudeDeg(), curr->getLatitude(), curr->getLongitude());
809
810 if (fgIsFinite(_course))
811 TurnTo(_course);
812 else
813 SG_LOG(SG_AI, SG_ALERT, "AIShip: Bearing or Range is not a finite number");
814
815 _dt_count = 0;
816} // end Processing FlightPlan
817
818bool FGAIShip::initFlightPlan()
819{
820 SG_LOG(SG_AI, SG_ALERT, "AIShip: " << _name << " initializing waypoints ");
821
822 bool doInit = false;
823 _start_sec = 0;
825
826 fp->restart();
827 fp->IncrementWaypoint(false);
828
829 prev = fp->getPreviousWaypoint(); //first waypoint
830 curr = fp->getCurrentWaypoint(); //second waypoint
831 next = fp->getNextWaypoint(); //third waypoint (might not exist!)
832
833 while (curr && (curr->getName() == "WAIT" || curr->getName() == "WAITUNTIL")) { // don't wait when initialising
834 SG_LOG(SG_AI, SG_DEBUG, "AIShip: " << _name << " re-initializing waypoints ");
835 fp->IncrementWaypoint(false);
836 curr = fp->getCurrentWaypoint();
837 next = fp->getNextWaypoint();
838 }
839
840 if (!_start_time.empty()) {
841 _start_sec = processTimeString(_start_time);
842 double day_sec = getDaySeconds();
843
844 if (_start_sec < day_sec) {
845 //cout << "flight plan has already started " << _start_time << endl;
846 doInit = advanceFlightPlan(_start_sec, day_sec);
847
848 } else if (_start_sec > day_sec && _repeat) {
849 //cout << "flight plan has not started, " << _start_time;
850 //cout << "offsetting start time by -24 hrs" << endl;
851 _start_sec -= _day;
852 doInit = advanceFlightPlan(_start_sec, day_sec);
853 }
854
855 if (doInit)
856 _start_sec = 0; // set to zero for an immediate start of the Flight Plan
857 else {
858 fp->restart();
859 fp->IncrementWaypoint(false);
860 prev = fp->getPreviousWaypoint();
861 curr = fp->getCurrentWaypoint();
862 next = fp->getNextWaypoint();
863 return false;
864 }
865
866 } else if (prev) {
867 setLatitude(prev->getLatitude());
868 setLongitude(prev->getLongitude());
869 setSpeed(prev->getSpeed());
870 }
871
872 setWPNames();
873
874 if (prev && curr) {
875 setHeading(getCourse(prev->getLatitude(), prev->getLongitude(), curr->getLatitude(), curr->getLongitude()));
876 _wp_range = getRange(prev->getLatitude(), prev->getLongitude(), curr->getLatitude(), curr->getLongitude());
877 _old_range = _wp_range;
878 }
879
880 _range_rate = 0;
881 _hdg_lock = true;
882 _missed = false;
883 _missed_count = 0;
884 _new_waypoint = true;
885
886 SG_LOG(SG_AI, SG_ALERT, "AIShip: " << _name << " done initialising waypoints " << _tunnel);
887
888 if (prev)
889 doInit = true;
890
891 if (doInit)
892 return true;
893 else
894 return false;
895} // end of initialization
896
897
898double FGAIShip::processTimeString(const std::string& theTime)
899{
900 int Hour;
901 int Minute;
902 int Second;
903
904 // first split theTime string into
905 // hour, minute, second and convert to int;
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());
909
910 // offset by a day-sec to allow for starting a day earlier
911 double time_seconds = Hour * 3600 + Minute * 60 + Second;
912
913 return time_seconds;
914}
915
916double FGAIShip::getDaySeconds()
917{
918 // Date and time
919 struct tm* t = globals->get_time_params()->getGmt();
920
921 double day_seconds = t->tm_hour * 3600 + t->tm_min * 60 + t->tm_sec;
922
923 return day_seconds;
924}
925
926bool FGAIShip::advanceFlightPlan(double start_sec, double day_sec)
927{
928 double elapsed_sec = start_sec;
929 double distance_nm = 0;
930
931 //cout << "advancing flight plan start_sec: " << start_sec << " " << day_sec << endl;
932
933 while (elapsed_sec < day_sec) {
934 if (next->getName() == "END" || fp->getNextWaypoint() == 0) {
935 if (_repeat) {
936 //cout << _name << ": " << "restarting flightplan" << endl;
937 fp->restart();
938 curr = fp->getCurrentWaypoint();
939 next = fp->getNextWaypoint();
940 } else {
941 //cout << _name << ": " << "ending flightplan" << endl;
942 setDie(true);
943 return false;
944 }
945
946 } else if (next->getName() == "WAIT") {
947 //cout << _name << ": begin WAIT: " << prev->name << " ";
948 //cout << curr->name << " " << next->name << endl;
949
950 elapsed_sec += next->getTime_sec();
951
952 if (elapsed_sec >= day_sec)
953 continue;
954
955 fp->IncrementWaypoint(false);
956 next = fp->getNextWaypoint();
957
958 if (next->getName() != "WAITUNTIL" && next->getName() != "WAIT" && next->getName() != "END") {
959 prev = curr;
960 fp->IncrementWaypoint(false);
961 curr = fp->getCurrentWaypoint();
962 next = fp->getNextWaypoint();
963 }
964
965 } else if (next->getName() == "WAITUNTIL") {
966 double until_sec = processTimeString(next->getTime());
967
968 if (until_sec > _start_sec && start_sec < 0)
969 until_sec -= _day;
970
971 if (elapsed_sec < until_sec)
972 elapsed_sec = until_sec;
973
974 if (elapsed_sec >= day_sec)
975 break;
976
977 fp->IncrementWaypoint(false);
978 next = fp->getNextWaypoint();
979
980 if (next->getName() != "WAITUNTIL" && next->getName() != "WAIT") {
981 prev = curr;
982 fp->IncrementWaypoint(false);
983 curr = fp->getCurrentWaypoint();
984 next = fp->getNextWaypoint();
985 }
986
987 //cout << _name << ": end WAITUNTIL: ";
988 //cout << prev->name << " " << curr->name << " " << next->name << endl;
989
990 } else {
991 distance_nm = getRange(prev->getLatitude(), prev->getLongitude(), curr->getLatitude(), curr->getLongitude());
992 elapsed_sec += distance_nm * 60 * 60 / fabs(prev->getSpeed());
993
994 if (elapsed_sec >= day_sec)
995 continue;
996
997 fp->IncrementWaypoint(false);
998 prev = fp->getPreviousWaypoint();
999 curr = fp->getCurrentWaypoint();
1000 next = fp->getNextWaypoint();
1001 }
1002
1003 } // end while
1004
1005 // the required position lies between the previous and current waypoints
1006 // so we will calculate the distance back up the track from the current waypoint
1007 // then calculate the lat and lon.
1008
1009 //cout << "advancing flight plan done elapsed_sec: " << elapsed_sec
1010 // << " " << day_sec << endl;
1011
1012 double time_diff = elapsed_sec - day_sec;
1013 double lat, lon, recip;
1014
1015 //cout << " time diff " << time_diff << endl;
1016
1017 if (next->getName() == "WAIT") {
1018 setSpeed(0);
1019 lat = curr->getLatitude();
1020 lon = curr->getLongitude();
1021 _wait_count = time_diff;
1022 _waiting = true;
1023 } else if (next->getName() == "WAITUNTIL") {
1024 setSpeed(0);
1025 lat = curr->getLatitude();
1026 lon = curr->getLongitude();
1027 _waiting = true;
1028 } else {
1029 setSpeed(prev->getSpeed());
1030 distance_nm = speed * time_diff / (60 * 60);
1031 double brg = getCourse(curr->getLatitude(), curr->getLongitude(), prev->getLatitude(), prev->getLongitude());
1032
1033 //cout << " brg " << brg << " from " << curr->name << " to " << prev->name << " "
1034 // << " lat " << curr->latitude << " lon " << curr->longitude
1035 // << " distance m " << distance_nm * SG_NM_TO_METER << endl;
1036
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);
1043 }
1044
1045 setLatitude(lat);
1046 setLongitude(lon);
1047
1048 return true;
1049}
1050
1052{
1053 if (curr->getName() == "END" || curr->getName() == "WAIT" || curr->getName() == "WAITUNTIL" || curr->getName() == "TUNNEL") {
1054 //cout << curr->name << " returning" << endl;
1055 return;
1056 }
1057
1058 wppos.setLatitudeDeg(curr->getLatitude());
1059 wppos.setLongitudeDeg(curr->getLongitude());
1060 wppos.setElevationM(0);
1061
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);
1067 }
1068 } else {
1069 wppos.setElevationM(curr->getAltitude());
1070 }
1071
1072 curr->setAltitude(wppos.getElevationM());
1073}
1074
1075void FGAIShip::setXTrackError()
1076{
1077 double course = getCourse(prev->getLatitude(), prev->getLongitude(),
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;
1084
1085 if (_wp_range > 0) {
1086 _lead_angle = atan2(xtrack_error_nm, (_wp_range * _proportion)) * SG_RADIANS_TO_DEGREES;
1087 } else
1088 _lead_angle = 0;
1089
1090 _lead_angle *= _lead_angle_gain * factor;
1091 _xtrack_error = xtrack_error_nm * 6076.1155;
1092
1093 SG_CLAMP_RANGE(_lead_angle, -limit, limit);
1094}
double altitude
Definition ADA.cxx:46
double fgIsFinite(double x)
Definition AIShip.cxx:15
double fgIsFinite(double x)
Definition AIShip.cxx:15
#define p(x)
void setSpeed(double speed_KTAS)
Definition AIBase.hxx:404
void setName(const std::string &n)
Definition AIBase.hxx:491
double speed_fps
Definition AIBase.hxx:217
double tgt_pitch
Definition AIBase.hxx:233
object_type getType()
Definition AIBase.hxx:510
double tgt_heading
Definition AIBase.hxx:229
double turn_radius_ft
Definition AIBase.hxx:222
SGGeod pos
Definition AIBase.hxx:212
void setLatitude(double latitude)
Definition AIBase.hxx:446
virtual void readFromScenario(SGPropertyNode *scFileNode)
Definition AIBase.cxx:249
void setDie(bool die)
Definition AIBase.hxx:506
void _setAltitude(double _alt)
Definition AIBase.cxx:1044
std::string _name
Definition AIBase.hxx:190
void setFlightPlan(std::unique_ptr< FGAIFlightPlan > f)
Definition AIBase.cxx:1161
bool invisible
Definition AIBase.hxx:256
FGAIBase(object_type ot, bool enableHot)
Definition AIBase.cxx:146
virtual bool init(ModelSearchOrder searchOrder)
Definition AIBase.cxx:633
void setRadius(double radius)
Definition AIBase.hxx:409
virtual void update(double dt)
Definition AIBase.cxx:294
double speed
Definition AIBase.hxx:216
std::unique_ptr< FGAIFlightPlan > fp
Definition AIBase.hxx:264
double UpdateRadar(FGAIManager *manager)
Definition AIBase.cxx:819
double hdg
Definition AIBase.hxx:213
void Transform()
Definition AIBase.cxx:518
void setSMPath(const std::string &p)
Definition AIBase.hxx:394
double tgt_speed
Definition AIBase.hxx:231
double pitch
Definition AIBase.hxx:215
SGModelPlacement aip
Definition AIBase.hxx:253
std::string _path
Definition AIBase.hxx:187
void setLongitude(double longitude)
Definition AIBase.hxx:441
ModelSearchOrder
Definition AIBase.hxx:63
double _elevation_m
Definition AIBase.hxx:172
double tgt_altitude_ft
Definition AIBase.hxx:230
void setHeading(double heading)
Definition AIBase.hxx:414
double tgt_roll
Definition AIBase.hxx:232
virtual void reinit()
Definition AIBase.hxx:74
SGPropertyNode_ptr replay_time
Definition AIBase.hxx:207
double roll
Definition AIBase.hxx:214
void tie(const char *aRelPath, const SGRawValue< T > &aRawValue)
Tied-properties helper, record nodes which are tied for easy un-tie-ing.
Definition AIBase.hxx:198
SGPropertyNode_ptr props
Definition AIBase.hxx:205
FGAIManager * manager
Definition AIBase.hxx:209
virtual void bind()
Definition AIBase.cxx:713
bool no_roll
Definition AIBase.hxx:257
void readFromScenario(SGPropertyNode *scFileNode) override
Definition AIShip.cxx:63
double _missed_range
Definition AIShip.hxx:79
double _tow_angle
Definition AIShip.hxx:80
void setLeadAngleProp(double p)
Definition AIShip.cxx:537
void PitchTo(double angle)
Definition AIShip.cxx:436
FGAIWaypoint * next
Definition AIShip.hxx:87
bool _waiting
Definition AIShip.hxx:70
double _next_run
Definition AIShip.hxx:83
void RollTo(double angle)
Definition AIShip.cxx:441
void setCurrName(const std::string &)
Definition AIShip.cxx:483
FGAIWaypoint * curr
Definition AIShip.hxx:86
void setSpeedConstant(double sc)
Definition AIShip.cxx:547
double _wp_range
Definition AIShip.hxx:82
void ClimbTo(double altitude)
Definition AIShip.cxx:451
void setWPNames()
Definition AIShip.cxx:573
void setRollFactor(double rf)
Definition AIShip.cxx:557
void setNextName(const std::string &)
Definition AIShip.cxx:489
bool _tunnel
Definition AIShip.hxx:72
bool _new_waypoint
Definition AIShip.hxx:71
void setLeadAngleLimit(double l)
Definition AIShip.cxx:532
void update(double dt) override
Definition AIShip.cxx:199
void bind() override
Definition AIShip.cxx:124
void TurnTo(double heading)
Definition AIShip.cxx:457
void AccelTo(double speed)
Definition AIShip.cxx:431
double _dt_count
Definition AIShip.hxx:83
bool _initial_tunnel
Definition AIShip.hxx:72
bool _restart
Definition AIShip.hxx:73
void ProcessFlightPlan(double dt)
Definition AIShip.cxx:622
FGAIShip(object_type ot=object_type::otShip)
Definition AIShip.cxx:30
double sign(double x)
Definition AIShip.cxx:464
void setLeadAngleGain(double g)
Definition AIShip.cxx:527
double _missed_count
Definition AIShip.hxx:82
bool _serviceable
Definition AIShip.hxx:69
void setRudder(float r)
Definition AIShip.cxx:517
void setRudderConstant(double rc)
Definition AIShip.cxx:542
double _elevation_ft
Definition AIShip.hxx:78
void setPrevName(const std::string &)
Definition AIShip.cxx:495
bool init(ModelSearchOrder searchOrder) override
Definition AIShip.cxx:92
std::string_view getTypeString(void) const override
Definition AIShip.hxx:27
void setTunnel(bool t)
Definition AIShip.cxx:568
double _speed_constant
Definition AIShip.hxx:76
double _wait_count
Definition AIShip.hxx:81
double _limit
Definition AIShip.hxx:77
void setFixedTurnRadius(double ft)
Definition AIShip.cxx:552
void setWPPos()
Definition AIShip.cxx:1051
void setInitialTunnel(bool t)
Definition AIShip.cxx:562
double _hdg_constant
Definition AIShip.hxx:77
void reinit() override
Definition AIShip.cxx:98
bool _hdg_lock
Definition AIShip.hxx:68
FGAIWaypoint * prev
Definition AIShip.hxx:85
void setRoll(double rl)
Definition AIShip.cxx:522
double _rudder_constant
Definition AIShip.hxx:75
double getLatitude() const
const std::string & getName()
double getLongitude() const
SGTime * get_time_params() const
Definition globals.hxx:311
FGGlobals * globals
Definition globals.cxx:142
static int atoi(const string &str)
Definition options.cxx:113