]> git.mxchange.org Git - flightgear.git/blob - src/AIModel/AIGroundVehicle.cxx
Merge branch 'ehofman/sound'
[flightgear.git] / src / AIModel / AIGroundVehicle.cxx
1 // FGAIGroundVehicle - FGAIShip-derived class creates an AI Ground Vehicle
2 // by adding a ground following utility
3 //
4 // Written by Vivian Meazza, started August 2009.
5 // - vivian.meazza at lineone.net
6 //
7 // This program is free software; you can redistribute it and/or
8 // modify it under the terms of the GNU General Public License as
9 // published by the Free Software Foundation; either version 2 of the
10 // License, or (at your option) any later version.
11 //
12 // This program is distributed in the hope that it will be useful, but
13 // WITHOUT ANY WARRANTY; without even the implied warranty of
14 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15 // General Public License for more details.
16 //
17 // You should have received a copy of the GNU General Public License
18 // along with this program; if not, write to the Free Software
19 // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
20
21 #ifdef HAVE_CONFIG_H
22 #  include <config.h>
23 #endif
24
25 #include <simgear/sg_inlines.h>
26
27 #include <Main/viewer.hxx>
28 #include <Scenery/scenery.hxx>
29 #include <Scenery/tilemgr.hxx>
30 #include <Airports/dynamics.hxx>
31
32 #include "AIGroundVehicle.hxx"
33
34 FGAIGroundVehicle::FGAIGroundVehicle() :
35 FGAIShip(otGroundVehicle),
36
37 _pitch(0),
38 _pitch_deg(0),
39 _speed_kt(0),
40 _selected_ac(0),
41 _range_ft(0),
42 _relbrg (0),
43 _parent_speed(0),
44 _dt_count(0),
45 _next_run(0),
46 _parent_x_offset(0),
47 _parent_y_offset(0),
48 _parent("")
49
50 {
51     invisible = false;
52 }
53
54 FGAIGroundVehicle::~FGAIGroundVehicle() {}
55
56 void FGAIGroundVehicle::readFromScenario(SGPropertyNode* scFileNode) {
57     if (!scFileNode)
58         return;
59
60     FGAIShip::readFromScenario(scFileNode);
61
62     setNoRoll(scFileNode->getBoolValue("no-roll", true));
63     setName(scFileNode->getStringValue("name", "groundvehicle"));
64     setSMPath(scFileNode->getStringValue("submodel-path", ""));
65     setContactX1offset(scFileNode->getDoubleValue("contact-x1-offset", 0.0));
66     setContactX2offset(scFileNode->getDoubleValue("contact-x2-offset", 0.0));
67     setXOffset(scFileNode->getDoubleValue("hitch-x-offset", 38.55));
68     setYOffset(scFileNode->getDoubleValue("hitch-y-offset", 0.0));
69     setPitchoffset(scFileNode->getDoubleValue("pitch-offset", 0.0));
70     setRolloffset(scFileNode->getDoubleValue("roll-offset", 0.0));
71     setYawoffset(scFileNode->getDoubleValue("yaw-offset", 0.0));
72     setPitchCoeff(scFileNode->getDoubleValue("pitch-coefficient", 0.1));
73     setElevCoeff(scFileNode->getDoubleValue("elevation-coefficient", 0.25));
74     setParentName(scFileNode->getStringValue("parent", ""));
75     setTowAngleGain(scFileNode->getDoubleValue("tow-angle-gain", 2.0));
76     setTowAngleLimit(scFileNode->getDoubleValue("tow-angle-limit-deg", 2.0));
77     //we may need these later for towed vehicles
78     //    setSubID(scFileNode->getIntValue("SubID", 0));
79     //    setGroundOffset(scFileNode->getDoubleValue("ground-offset", 0.0));
80     //    setFormate(scFileNode->getBoolValue("formate", true));
81 }
82
83 void FGAIGroundVehicle::bind() {
84     FGAIShip::bind();
85
86     props->tie("controls/constants/elevation-coeff",
87         SGRawValuePointer<double>(&_elevation_coeff));
88     props->tie("controls/constants/pitch-coeff",
89         SGRawValuePointer<double>(&_pitch_coeff));
90     props->tie("position/ht-AGL-ft",
91         SGRawValuePointer<double>(&_ht_agl_ft));
92     props->tie("hitch/rel-bearing-deg",
93          SGRawValuePointer<double>(&_relbrg));
94     props->tie("hitch/tow-angle-deg",
95          SGRawValuePointer<double>(&_tow_angle));
96     props->tie("hitch/range-ft",
97         SGRawValuePointer<double>(&_range_ft));
98     props->tie("hitch/x-offset-ft",
99         SGRawValuePointer<double>(&_x_offset));
100     props->tie("hitch/y-offset-ft",
101         SGRawValuePointer<double>(&_y_offset));
102     props->tie("hitch/parent-x-offset-ft",
103         SGRawValuePointer<double>(&_parent_x_offset));
104     props->tie("hitch/parent-y-offset-ft",
105         SGRawValuePointer<double>(&_parent_y_offset));
106     props->tie("controls/constants/tow-angle/gain",
107         SGRawValuePointer<double>(&_tow_angle_gain));
108     props->tie("controls/constants/tow-angle/limit-deg",
109         SGRawValuePointer<double>(&_tow_angle_limit));
110     props->tie("controls/contact-x1-offset-ft",
111         SGRawValuePointer<double>(&_contact_x1_offset));
112     props->tie("controls/contact-x2-offset-ft",
113         SGRawValuePointer<double>(&_contact_x2_offset));
114 }
115
116 void FGAIGroundVehicle::unbind() {
117     FGAIShip::unbind();
118
119     props->untie("controls/constants/elevation-coeff");
120     props->untie("position/ht-AGL-ft");
121     props->untie("controls/constants/pitch-coeff");
122     props->untie("hitch/rel-bearing-deg");
123     props->untie("hitch/tow-angle-deg");
124     props->untie("hitch/range-ft");
125     props->untie("hitch/x-offset-ft");
126     props->untie("hitch/y-offset-ft");
127     props->untie("hitch/parent-x-offset-ft");
128     props->untie("hitch/parent-y-offset-ft");
129     props->untie("controls/constants/tow-angle/gain");
130     props->untie("controls/constants/tow-angle/limit-deg");
131     props->untie("controls/contact-x1-offset-ft");
132     props->untie("controls/contact-x2-offset-ft");
133 }
134
135 bool FGAIGroundVehicle::init(bool search_in_AI_path) {
136     if (!FGAIShip::init(search_in_AI_path))
137         return false;
138
139     invisible = false;
140     _limit = 200;
141     no_roll = true;
142
143     return true;
144 }
145
146 void FGAIGroundVehicle::update(double dt) {
147     //    SG_LOG(SG_GENERAL, SG_ALERT, "updating GroundVehicle: " << _name );
148
149     RunGroundVehicle(dt);
150 //    FGAIShip::update(dt);
151 }
152
153 void FGAIGroundVehicle::setNoRoll(bool nr) {
154     no_roll = nr;
155 }
156
157 void FGAIGroundVehicle::setContactX1offset(double x1) {
158     _contact_x1_offset = x1;
159 }
160
161 void FGAIGroundVehicle::setContactX2offset(double x2) {
162     _contact_x2_offset = x2;
163 }
164
165 void FGAIGroundVehicle::setXOffset(double x) {
166     _x_offset = x;
167 }
168
169 void FGAIGroundVehicle::setYOffset(double y) {
170     _y_offset = y;
171 }
172
173 void FGAIGroundVehicle::setPitchCoeff(double pc) {
174     _pitch_coeff = pc;
175 }
176
177 void FGAIGroundVehicle::setElevCoeff(double ec) {
178     _elevation_coeff = ec;
179 }
180
181 void FGAIGroundVehicle::setTowAngleGain(double g) {
182     _tow_angle_gain = g;
183 }
184
185 void FGAIGroundVehicle::setTowAngleLimit(double l) {
186     _tow_angle_limit = l;
187 }
188
189 void FGAIGroundVehicle::setElevation(double h, double dt, double coeff){
190     double c = dt / (coeff + dt);
191     _elevation_ft = (h * c) + (_elevation_ft * (1 - c));
192 }
193
194 void FGAIGroundVehicle::setPitch(double p, double dt, double coeff){
195     double c = dt / (coeff + dt);
196     _pitch_deg = (p * c) + (_pitch_deg * (1 - c));
197 }
198
199 void FGAIGroundVehicle::setParentName(const string& p) {
200     _parent = p;
201 }
202
203 void FGAIGroundVehicle::setTowAngle(double ta, double dt, double coeff){
204     //_tow_angle = ta * _tow_angle_gain;
205     _tow_angle = pow(ta,2) * sign(ta);
206     SG_CLAMP_RANGE(_tow_angle, -_tow_angle_limit, _tow_angle_limit);
207 }
208
209 bool FGAIGroundVehicle::getGroundElev(SGGeod inpos) {
210
211     double height_m ;
212
213     if (globals->get_scenery()->get_elevation_m(SGGeod::fromGeodM(inpos, 3000), height_m, &_material,0)){
214             _ht_agl_ft = inpos.getElevationFt() - height_m * SG_METER_TO_FEET;
215
216             if (_material) {
217                 const vector<string>& names = _material->get_names();
218
219                 _solid = _material->get_solid();
220
221                 if (!names.empty())
222                     props->setStringValue("material/name", names[0].c_str());
223                 else
224                     props->setStringValue("material/name", "");
225
226                 //cout << "material " << names[0].c_str()
227                 //    << " _elevation_m " << _elevation_m
228                 //    << " solid " << _solid
229                 //    << " load " << _load_resistance
230                 //    << " frictionFactor " << _frictionFactor
231                 //    << endl;
232
233             }
234
235             return true;
236     } else {
237         return false;
238     }
239
240 }
241
242 bool FGAIGroundVehicle::getPitch() {
243
244     if (!_tunnel){
245
246         double vel = props->getDoubleValue("velocities/true-airspeed-kt", 0);
247         double contact_offset_x1_m = _contact_x1_offset * SG_FEET_TO_METER;
248         double contact_offset_x2_m = _contact_x2_offset * SG_FEET_TO_METER;
249
250         SGVec3d front(-contact_offset_x1_m, 0, 0);
251         SGVec3d rear(-contact_offset_x2_m, 0, 0);
252         SGVec3d Front = getCartPosAt(front);
253         SGVec3d Rear = getCartPosAt(rear);
254
255         SGGeod geodFront = SGGeod::fromCart(Front);
256         SGGeod geodRear = SGGeod::fromCart(Rear);
257
258         double front_elev_m = 0;
259         double rear_elev_m = 0;
260         double elev_front = 0;
261         double elev_rear = 0;
262         double max_alt = 10000;
263
264         if (globals->get_scenery()->get_elevation_m(SGGeod::fromGeodM(geodFront, 3000), elev_front,
265              &_material, 0)){
266                 front_elev_m = elev_front;
267         } else
268             return false;
269
270         if (globals->get_scenery()->get_elevation_m(SGGeod::fromGeodM(geodRear, 3000),
271             elev_rear, &_material, 0)){
272                 rear_elev_m = elev_rear;
273         } else
274             return false;
275
276         if (vel >= 0){
277             double diff = front_elev_m - rear_elev_m;
278             _pitch = atan2 (diff,
279                 fabs(contact_offset_x1_m) + fabs(contact_offset_x2_m)) * SG_RADIANS_TO_DEGREES;
280             _elevation = (rear_elev_m + diff/2) * SG_METER_TO_FEET;
281         } else {
282             double diff = rear_elev_m - front_elev_m;
283             _pitch = atan2 (diff,
284                 fabs(contact_offset_x1_m) + fabs(contact_offset_x2_m)) * SG_RADIANS_TO_DEGREES;
285             _elevation = (front_elev_m + diff/2) * SG_METER_TO_FEET;
286             _pitch = -_pitch;
287         }
288
289     } else {
290
291         if (prev->altitude == 0 || curr->altitude == 0) return false;
292
293         static double distance;
294         static double d_alt;
295         static double curr_alt;
296         static double prev_alt;
297
298         if (_new_waypoint){
299             cout << "new waypoint, calculating pitch " << endl;
300             curr_alt = curr->altitude * SG_METER_TO_FEET;
301             prev_alt = prev->altitude * SG_METER_TO_FEET;
302             d_alt = curr_alt - prev_alt;
303
304             distance = SGGeodesy::distanceM(SGGeod::fromDeg(prev->longitude, prev->latitude),
305             SGGeod::fromDeg(curr->longitude, curr->latitude));
306
307             _pitch = atan2(d_alt, distance * SG_METER_TO_FEET) * SG_RADIANS_TO_DEGREES;
308 //            cout << "new waypoint, calculating pitch " <<  _pitch << endl;
309         }
310
311
312         double distance_to_go = SGGeodesy::distanceM(SGGeod::fromDeg(pos.getLongitudeDeg(), pos.getLatitudeDeg()),
313             SGGeod::fromDeg(curr->longitude, curr->latitude));
314
315         //cout << "tunnel " << _tunnel
316         //     << " distance curr & prev " << prev->name << " " << curr->name << " " << distance * SG_METER_TO_FEET
317         //     << " distance to go " << distance_to_go * SG_METER_TO_FEET
318         //     << " d_alt ft " << d_alt
319         //     << endl;
320
321         if (distance_to_go > distance)
322             _elevation = prev_alt;
323         else
324             _elevation = curr_alt - (tan(_pitch * SG_DEGREES_TO_RADIANS) * distance_to_go * SG_METER_TO_FEET);
325
326     }
327
328     getGroundElev(pos);
329
330     return true;
331 }
332
333 void FGAIGroundVehicle::setParent() {
334
335     const SGPropertyNode *ai = fgGetNode("/ai/models", true);
336
337     for (int i = ai->nChildren() - 1; i >= -1; i--) {
338         const SGPropertyNode *model;
339
340         if (i < 0) { // last iteration: selected model
341             model = _selected_ac;
342         } else {
343             model = ai->getChild(i);
344             string path = ai->getPath();
345             const string name = model->getStringValue("name");
346
347             if (!model->nChildren()){
348                 continue;
349             }
350             if (name == _parent) {
351                 _selected_ac = model;  // save selected model for last iteration
352                 break;
353             }
354
355         }
356         if (!model)
357             continue;
358
359     }// end for loop
360
361     if (_selected_ac != 0){
362         const string name = _selected_ac->getStringValue("name");
363         double lat = _selected_ac->getDoubleValue("position/latitude-deg");
364         double lon = _selected_ac->getDoubleValue("position/longitude-deg");
365         double elevation = _selected_ac->getDoubleValue("position/altitude-ft");
366         double hitch_x_offset_m = _selected_ac->getDoubleValue("hitch/x-offset-ft")
367             * SG_FEET_TO_METER;
368         double hitch_y_offset_m = _selected_ac->getDoubleValue("hitch/y-offset-ft")
369             * SG_FEET_TO_METER;
370
371         _selectedpos.setLatitudeDeg(lat);
372         _selectedpos.setLongitudeDeg(lon);
373         _selectedpos.setElevationFt(elevation);
374
375         _parent_x_offset = _selected_ac->getDoubleValue("hitch/x-offset-ft");
376         _parent_y_offset = _selected_ac->getDoubleValue("hitch/y-offset-ft");
377         _parent_speed    = _selected_ac->getDoubleValue("velocities/true-airspeed-kt");
378
379         SGVec3d rear_hitch(-hitch_x_offset_m, hitch_y_offset_m, 0);
380         SGVec3d RearHitch = getCartHitchPosAt(rear_hitch);
381
382         SGGeod rearpos = SGGeod::fromCart(RearHitch);
383
384         double user_lat = rearpos.getLatitudeDeg();
385         double user_lon = rearpos.getLongitudeDeg();
386
387         double range, bearing;
388
389         calcRangeBearing(pos.getLatitudeDeg(), pos.getLongitudeDeg(),
390             user_lat, user_lon, range, bearing);
391         _range_ft = range * 6076.11549;
392         _relbrg = calcRelBearingDeg(bearing, hdg);
393     } else {
394         SG_LOG(SG_GENERAL, SG_ALERT, "AIGroundVeh1cle: " << _name
395                 << " parent not found: dying ");
396         setDie(true);
397     }
398
399 }
400
401 void FGAIGroundVehicle::calcRangeBearing(double lat, double lon, double lat2, double lon2,
402                             double &range, double &bearing) const
403 {
404     // calculate the bearing and range of the second pos from the first
405     double az2, distance;
406     geo_inverse_wgs_84(lat, lon, lat2, lon2, &bearing, &az2, &distance);
407     range = distance * SG_METER_TO_NM;
408 }
409
410 double FGAIGroundVehicle::calcRelBearingDeg(double bearing, double heading)
411 {
412     double angle = bearing - heading;
413     SG_NORMALIZE_RANGE(angle, -180.0, 180.0);
414     return angle;
415 }
416
417 SGVec3d FGAIGroundVehicle::getCartHitchPosAt(const SGVec3d& _off) const {
418     double hdg = _selected_ac->getDoubleValue("orientation/true-heading-deg");
419     double pitch = _selected_ac->getDoubleValue("orientation/pitch-deg");
420     double roll = _selected_ac->getDoubleValue("orientation/roll-deg");
421
422     // Transform that one to the horizontal local coordinate system.
423     SGQuatd hlTrans = SGQuatd::fromLonLat(_selectedpos);
424
425     // and postrotate the orientation of the AIModel wrt the horizontal
426     // local frame
427     hlTrans *= SGQuatd::fromYawPitchRollDeg(hdg, pitch, roll);
428
429     // The offset converted to the usual body fixed coordinate system
430     // rotated to the earth fiexed coordinates axis
431     SGVec3d off = hlTrans.backTransform(_off);
432
433     // Add the position offset of the AIModel to gain the earth centered position
434     SGVec3d cartPos = SGVec3d::fromGeod(_selectedpos);
435
436     return cartPos + off;
437 }
438
439 void FGAIGroundVehicle::AdvanceFP(){
440
441     double count = 0;
442     string parent_next_name =_selected_ac->getStringValue("waypoint/name-next");
443
444     while(fp->getNextWaypoint() != 0 && fp->getNextWaypoint()->name != "END" && count < 5){
445         SG_LOG(SG_GENERAL, SG_DEBUG, "AIGroundVeh1cle: " << _name
446             <<" advancing waypoint to: " << parent_next_name);
447
448         if (fp->getNextWaypoint()->name == parent_next_name){
449             SG_LOG(SG_GENERAL, SG_DEBUG, "AIGroundVeh1cle: " << _name
450                 << " not setting waypoint already at: " << fp->getNextWaypoint()->name);
451             return;
452         }
453
454         prev = curr;
455         fp->IncrementWaypoint(false);
456         curr = fp->getCurrentWaypoint();
457         next = fp->getNextWaypoint();
458
459         if (fp->getNextWaypoint()->name == parent_next_name){
460             SG_LOG(SG_GENERAL, SG_DEBUG, "AIGroundVeh1cle: " << _name
461             << " waypoint set to: " << fp->getNextWaypoint()->name);
462             return;
463         }
464
465         count++;
466
467     }// end while loop
468
469     while(fp->getPreviousWaypoint() != 0 && fp->getPreviousWaypoint()->name != "END"
470         && count > -10){
471             SG_LOG(SG_GENERAL, SG_DEBUG, "AIGroundVeh1cle: " << _name
472             << " retreating waypoint to: " << parent_next_name
473             << " at: " << fp->getNextWaypoint()->name);
474
475         if (fp->getNextWaypoint()->name == parent_next_name){
476             SG_LOG(SG_GENERAL, SG_DEBUG, "AIGroundVeh1cle: " << _name
477                 << " not setting waypoint already at:" << fp->getNextWaypoint()->name );
478             return;
479         }
480
481         prev = curr;
482         fp->DecrementWaypoint(false);
483         curr = fp->getCurrentWaypoint();
484         next = fp->getNextWaypoint();
485
486         if (fp->getNextWaypoint()->name == parent_next_name){
487             SG_LOG(SG_GENERAL, SG_DEBUG, "AIGroundVeh1cle: " << _name
488             << " waypoint set to: " << fp->getNextWaypoint()->name);
489             return;
490         }
491
492         count--;
493
494     }// end while loop
495 }
496
497 void FGAIGroundVehicle::setTowSpeed(){
498
499     double diff = _range_ft - _x_offset;
500     double  x = 0;
501
502     if (_range_ft > _x_offset * 3) x = 50;
503
504     if (_relbrg < -90 || _relbrg > 90){
505         setSpeed(_parent_speed - 5 - x);
506         //cout << _name << " case 1r _relbrg spd - 5 " << _relbrg << " " << diff << endl;
507     }else if (_range_ft > _x_offset + 0.25 && _relbrg >= -90 && _relbrg <= 90){
508         setSpeed(_parent_speed + 1 + x);
509         //cout << _name << " case 2r _relbrg spd + 1 " << _relbrg << " "
510         //    << diff << " range " << _range_ft << endl;
511     } else if (_range_ft < _x_offset - 0.25 && _relbrg >= -90 && _relbrg <= 90){
512         setSpeed(_parent_speed - 1 - x);
513         //cout << _name << " case 3r _relbrg spd - 2 " << _relbrg << " "
514         //    << diff << " " << _range_ft << endl;
515     } else {
516         setSpeed(_parent_speed);
517         //cout << _name << " else r _relbrg " << _relbrg << " " << diff << endl;
518     }
519
520 }
521
522 void FGAIGroundVehicle::RunGroundVehicle(double dt){
523
524     _dt_count += dt;
525
526     ///////////////////////////////////////////////////////////////////////////
527     // Check execution time (currently once every 0.05 sec or 20 fps)
528     // Add a bit of randomization to prevent the execution of all flight plans
529     // in synchrony, which can add significant periodic framerate flutter.
530     // Randomization removed to get better appearance
531     ///////////////////////////////////////////////////////////////////////////
532
533     //cout << "_start_sec " << _start_sec << " time_sec " << time_sec << endl;
534     if (_dt_count < _next_run)
535         return;
536
537     _next_run = 0.055 /*+ (0.015 * sg_random())*/;
538
539     if (getPitch()){
540         setElevation(_elevation, _dt_count, _elevation_coeff);
541         ClimbTo(_elevation_ft);
542         setPitch(_pitch, _dt_count, _pitch_coeff);
543         PitchTo(_pitch_deg);
544     }
545
546     if(_parent == ""){
547         AccelTo(prev->speed);
548         FGAIShip::update(_dt_count);
549         _dt_count = 0;
550         return;
551     }
552
553     setParent();
554
555     string parent_next_name = _selected_ac->getStringValue("waypoint/name-next");
556     bool parent_waiting = _selected_ac->getBoolValue("waypoint/waiting");
557
558     if (parent_next_name == "END" && fp->getNextWaypoint()->name != "END" ){
559         SG_LOG(SG_GENERAL, SG_DEBUG, "AIGroundVeh1cle: " << _name
560             << " setting END: getting new waypoints ");
561         AdvanceFP();
562         setWPNames();
563         /*} else if (parent_next_name == "WAIT" && fp->getNextWaypoint()->name != "WAIT" ){*/
564     } else if (parent_waiting && !_waiting){
565         SG_LOG(SG_GENERAL, SG_DEBUG, "AIGroundVeh1cle: " << _name
566             << " setting WAIT/WAITUNTIL: getting new waypoints ");
567         AdvanceFP();
568         setWPNames();
569         _waiting = true;
570     } else if (parent_next_name != "WAIT" && fp->getNextWaypoint()->name == "WAIT"){
571         SG_LOG(SG_GENERAL, SG_DEBUG, "AIGroundVeh1cle: " << _name
572             << " wait done: getting new waypoints ");
573         _waiting = false;
574         _wait_count = 0;
575         fp->IncrementWaypoint(false);
576         next = fp->getNextWaypoint();
577
578         if (next->name == "WAITUNTIL" || next->name == "WAIT"
579             || next->name == "END"){
580         } else {
581             prev = curr;
582             fp->IncrementWaypoint(false);
583             curr = fp->getCurrentWaypoint();
584             next = fp->getNextWaypoint();
585         }
586
587         setWPNames();
588     } else if (_range_ft > _parent_x_offset * 4){
589         SG_LOG(SG_GENERAL, SG_INFO, "AIGroundVeh1cle: " << _name
590             << " rescue: reforming train " << _range_ft << " " << _x_offset * 15);
591
592         setTowAngle(0, dt, 1);
593         setSpeed(_parent_speed * 2);
594
595     } else if (_parent_speed > 1){
596
597         setTowSpeed();
598         setTowAngle(_relbrg, dt, 1);
599
600     } else if (_parent_speed < -1){
601
602         setTowSpeed();
603
604         if (_relbrg < 0)
605             setTowAngle(-(180 - (360 + _relbrg)), dt, 1);
606         else
607             setTowAngle(-(180 - _relbrg), dt, 1);
608
609     } else
610         setSpeed(_parent_speed);
611
612     FGAIShip::update(_dt_count);
613     _dt_count = 0;
614
615 }
616
617 // end AIGroundvehicle