]> git.mxchange.org Git - flightgear.git/blob - src/AIModel/AIGroundVehicle.cxx
82901e527059d4a1ed8024748e77211f369a42ae
[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, 10000), 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, 10000), 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, 10000),
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             curr_alt = curr->altitude * SG_METER_TO_FEET;
300             prev_alt = prev->altitude * SG_METER_TO_FEET;
301             d_alt = curr_alt - prev_alt;
302
303             distance = SGGeodesy::distanceM(SGGeod::fromDeg(prev->longitude, prev->latitude),
304             SGGeod::fromDeg(curr->longitude, curr->latitude));
305
306             _pitch = atan2(d_alt, distance * SG_METER_TO_FEET) * SG_RADIANS_TO_DEGREES;
307         }
308
309
310         double distance_to_go = SGGeodesy::distanceM(SGGeod::fromDeg(pos.getLongitudeDeg(), pos.getLatitudeDeg()),
311             SGGeod::fromDeg(curr->longitude, curr->latitude));
312
313         //cout << "distance curr & prev " << prev->name << " " << curr->name << " " << distance * SG_METER_TO_FEET
314         //     << " distance to go " << distance_to_go * SG_METER_TO_FEET
315         //     << " d_alt ft " << d_alt
316         //     << endl;
317
318         if (distance_to_go > distance)
319             _elevation = prev_alt;
320         else
321             _elevation = curr_alt - (tan(_pitch * SG_DEGREES_TO_RADIANS) * distance_to_go * SG_METER_TO_FEET);
322
323     }
324
325     getGroundElev(pos);
326
327     return true;
328 }
329
330 void FGAIGroundVehicle::setParent() {
331
332     const SGPropertyNode *ai = fgGetNode("/ai/models", true);
333
334     for (int i = ai->nChildren() - 1; i >= -1; i--) {
335         const SGPropertyNode *model;
336
337         if (i < 0) { // last iteration: selected model
338             model = _selected_ac;
339         } else {
340             model = ai->getChild(i);
341             string path = ai->getPath();
342             const string name = model->getStringValue("name");
343
344             if (!model->nChildren()){
345                 continue;
346             }
347             if (name == _parent) {
348                 _selected_ac = model;  // save selected model for last iteration
349                 break;
350             }
351
352         }
353         if (!model)
354             continue;
355
356     }// end for loop
357
358     if (_selected_ac != 0){
359         const string name = _selected_ac->getStringValue("name");
360         double lat = _selected_ac->getDoubleValue("position/latitude-deg");
361         double lon = _selected_ac->getDoubleValue("position/longitude-deg");
362         double elevation = _selected_ac->getDoubleValue("position/altitude-ft");
363         double hitch_x_offset_m = _selected_ac->getDoubleValue("hitch/x-offset-ft")
364             * SG_FEET_TO_METER;
365         double hitch_y_offset_m = _selected_ac->getDoubleValue("hitch/y-offset-ft")
366             * SG_FEET_TO_METER;
367
368         _selectedpos.setLatitudeDeg(lat);
369         _selectedpos.setLongitudeDeg(lon);
370         _selectedpos.setElevationFt(elevation);
371
372         _parent_x_offset = _selected_ac->getDoubleValue("hitch/x-offset-ft");
373         _parent_y_offset = _selected_ac->getDoubleValue("hitch/y-offset-ft");
374         _parent_speed    = _selected_ac->getDoubleValue("velocities/true-airspeed-kt");
375
376         SGVec3d rear_hitch(-hitch_x_offset_m, hitch_y_offset_m, 0);
377         SGVec3d RearHitch = getCartHitchPosAt(rear_hitch);
378
379         SGGeod rearpos = SGGeod::fromCart(RearHitch);
380
381         double user_lat = rearpos.getLatitudeDeg();
382         double user_lon = rearpos.getLongitudeDeg();
383
384         double range, bearing;
385
386         calcRangeBearing(pos.getLatitudeDeg(), pos.getLongitudeDeg(),
387             user_lat, user_lon, range, bearing);
388         _range_ft = range * 6076.11549;
389         _relbrg = calcRelBearingDeg(bearing, hdg);
390     } else {
391         SG_LOG(SG_GENERAL, SG_ALERT, "AIGroundVeh1cle: " << _name
392                 << " parent not found: dying ");
393         setDie(true);
394     }
395
396 }
397
398 void FGAIGroundVehicle::calcRangeBearing(double lat, double lon, double lat2, double lon2,
399                             double &range, double &bearing) const
400 {
401     // calculate the bearing and range of the second pos from the first
402     double az2, distance;
403     geo_inverse_wgs_84(lat, lon, lat2, lon2, &bearing, &az2, &distance);
404     range = distance * SG_METER_TO_NM;
405 }
406
407 double FGAIGroundVehicle::calcRelBearingDeg(double bearing, double heading)
408 {
409     double angle = bearing - heading;
410     SG_NORMALIZE_RANGE(angle, -180.0, 180.0);
411     return angle;
412 }
413
414 SGVec3d FGAIGroundVehicle::getCartHitchPosAt(const SGVec3d& _off) const {
415     double hdg = _selected_ac->getDoubleValue("orientation/true-heading-deg");
416     double pitch = _selected_ac->getDoubleValue("orientation/pitch-deg");
417     double roll = _selected_ac->getDoubleValue("orientation/roll-deg");
418
419     // Transform that one to the horizontal local coordinate system.
420     SGQuatd hlTrans = SGQuatd::fromLonLat(_selectedpos);
421
422     // and postrotate the orientation of the AIModel wrt the horizontal
423     // local frame
424     hlTrans *= SGQuatd::fromYawPitchRollDeg(hdg, pitch, roll);
425
426     // The offset converted to the usual body fixed coordinate system
427     // rotated to the earth fiexed coordinates axis
428     SGVec3d off = hlTrans.backTransform(_off);
429
430     // Add the position offset of the AIModel to gain the earth centered position
431     SGVec3d cartPos = SGVec3d::fromGeod(_selectedpos);
432
433     return cartPos + off;
434 }
435
436 void FGAIGroundVehicle::AdvanceFP(){
437
438     double count = 0;
439     string parent_next_name =_selected_ac->getStringValue("waypoint/name-next");
440
441     while(fp->getNextWaypoint() != 0 && fp->getNextWaypoint()->name != "END" && count < 5){
442         SG_LOG(SG_GENERAL, SG_DEBUG, "AIGroundVeh1cle: " << _name
443             <<" advancing waypoint to: " << parent_next_name);
444
445         if (fp->getNextWaypoint()->name == parent_next_name){
446             SG_LOG(SG_GENERAL, SG_DEBUG, "AIGroundVeh1cle: " << _name
447                 << " not setting waypoint already at: " << fp->getNextWaypoint()->name);
448             return;
449         }
450
451         prev = curr;
452         fp->IncrementWaypoint(false);
453         curr = fp->getCurrentWaypoint();
454         next = fp->getNextWaypoint();
455
456         if (fp->getNextWaypoint()->name == parent_next_name){
457             SG_LOG(SG_GENERAL, SG_DEBUG, "AIGroundVeh1cle: " << _name
458             << " waypoint set to: " << fp->getNextWaypoint()->name);
459             return;
460         }
461
462         count++;
463
464     }// end while loop
465
466     while(fp->getPreviousWaypoint() != 0 && fp->getPreviousWaypoint()->name != "END"
467         && count > -10){
468             SG_LOG(SG_GENERAL, SG_DEBUG, "AIGroundVeh1cle: " << _name
469             << " retreating waypoint to: " << parent_next_name
470             << " at: " << fp->getNextWaypoint()->name);
471
472         if (fp->getNextWaypoint()->name == parent_next_name){
473             SG_LOG(SG_GENERAL, SG_DEBUG, "AIGroundVeh1cle: " << _name
474                 << " not setting waypoint already at:" << fp->getNextWaypoint()->name );
475             return;
476         }
477
478         prev = curr;
479         fp->DecrementWaypoint(false);
480         curr = fp->getCurrentWaypoint();
481         next = fp->getNextWaypoint();
482
483         if (fp->getNextWaypoint()->name == parent_next_name){
484             SG_LOG(SG_GENERAL, SG_DEBUG, "AIGroundVeh1cle: " << _name
485             << " waypoint set to: " << fp->getNextWaypoint()->name);
486             return;
487         }
488
489         count--;
490
491     }// end while loop
492 }
493
494 void FGAIGroundVehicle::setTowSpeed(){
495
496     double diff = _range_ft - _x_offset;
497     double  x = 0;
498
499     if (_range_ft > _x_offset * 3) x = 50;
500
501     if (_relbrg < -90 || _relbrg > 90){
502         setSpeed(_parent_speed - 5 - x);
503         //cout << _name << " case 1r _relbrg spd - 5 " << _relbrg << " " << diff << endl;
504     }else if (_range_ft > _x_offset + 0.25 && _relbrg >= -90 && _relbrg <= 90){
505         setSpeed(_parent_speed + 1 + x);
506         //cout << _name << " case 2r _relbrg spd + 1 " << _relbrg << " "
507         //    << diff << " range " << _range_ft << endl;
508     } else if (_range_ft < _x_offset - 0.25 && _relbrg >= -90 && _relbrg <= 90){
509         setSpeed(_parent_speed - 1 - x);
510         //cout << _name << " case 3r _relbrg spd - 2 " << _relbrg << " "
511         //    << diff << " " << _range_ft << endl;
512     } else {
513         setSpeed(_parent_speed);
514         //cout << _name << " else r _relbrg " << _relbrg << " " << diff << endl;
515     }
516
517 }
518
519 void FGAIGroundVehicle::RunGroundVehicle(double dt){
520
521     _dt_count += dt;
522
523     ///////////////////////////////////////////////////////////////////////////
524     // Check execution time (currently once every 0.05 sec or 20 fps)
525     // Add a bit of randomization to prevent the execution of all flight plans
526     // in synchrony, which can add significant periodic framerate flutter.
527     ///////////////////////////////////////////////////////////////////////////
528
529     //cout << "_start_sec " << _start_sec << " time_sec " << time_sec << endl;
530     if (_dt_count < _next_run)
531         return;
532
533     _next_run = 0.055 + (0.015 * sg_random());
534
535     if (getPitch()){
536         setElevation(_elevation, _dt_count, _elevation_coeff);
537         ClimbTo(_elevation_ft);
538         setPitch(_pitch, _dt_count, _pitch_coeff);
539         PitchTo(_pitch_deg);
540     }
541
542     if(_parent == ""){
543         AccelTo(prev->speed);
544         FGAIShip::update(_dt_count);
545         _dt_count = 0;
546         return;
547     }
548
549     setParent();
550
551     string parent_next_name = _selected_ac->getStringValue("waypoint/name-next");
552     bool parent_waiting = _selected_ac->getBoolValue("waypoint/waiting");
553
554     if (parent_next_name == "END" && fp->getNextWaypoint()->name != "END" ){
555         SG_LOG(SG_GENERAL, SG_DEBUG, "AIGroundVeh1cle: " << _name
556             << " setting END: getting new waypoints ");
557         AdvanceFP();
558         setWPNames();
559         /*} else if (parent_next_name == "WAIT" && fp->getNextWaypoint()->name != "WAIT" ){*/
560     } else if (parent_waiting && !_waiting){
561         SG_LOG(SG_GENERAL, SG_DEBUG, "AIGroundVeh1cle: " << _name
562             << " setting WAIT/WAITUNTIL: getting new waypoints ");
563         AdvanceFP();
564         setWPNames();
565         _waiting = true;
566     } else if (parent_next_name != "WAIT" && fp->getNextWaypoint()->name == "WAIT"){
567         SG_LOG(SG_GENERAL, SG_DEBUG, "AIGroundVeh1cle: " << _name
568             << " wait done: getting new waypoints ");
569         _waiting = false;
570         _wait_count = 0;
571         fp->IncrementWaypoint(false);
572         next = fp->getNextWaypoint();
573
574         if (next->name == "WAITUNTIL" || next->name == "WAIT"
575             || next->name == "END"){
576         } else {
577             prev = curr;
578             fp->IncrementWaypoint(false);
579             curr = fp->getCurrentWaypoint();
580             next = fp->getNextWaypoint();
581         }
582
583         setWPNames();
584     } else if (_range_ft > _parent_x_offset * 4){
585         SG_LOG(SG_GENERAL, SG_INFO, "AIGroundVeh1cle: " << _name
586             << " rescue: reforming train " << _range_ft << " " << _x_offset * 15);
587
588         setTowAngle(0, dt, 1);
589         setSpeed(_parent_speed * 2);
590
591     } else if (_parent_speed > 1){
592
593         setTowSpeed();
594         setTowAngle(_relbrg, dt, 1);
595
596     } else if (_parent_speed < -1){
597
598         setTowSpeed();
599
600         if (_relbrg < 0)
601             setTowAngle(-(180 - (360 + _relbrg)), dt, 1);
602         else
603             setTowAngle(-(180 - _relbrg), dt, 1);
604
605     } else
606         setSpeed(_parent_speed);
607
608     FGAIShip::update(_dt_count);
609     _dt_count = 0;
610
611 }
612
613 // end AIGroundvehicle