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