]> git.mxchange.org Git - flightgear.git/blob - src/AIModel/AIBallistic.cxx
Revert to old behavior of life randomness
[flightgear.git] / src / AIModel / AIBallistic.cxx
1 // FGAIBallistic - FGAIBase-derived class creates a ballistic object
2 //
3 // Written by David Culp, started November 2003.
4 // - davidculp2@comcast.net
5 //
6 // With major additions by Mathias Froehlich & Vivian Meazza 2004-2008
7 //
8 // This program is free software; you can redistribute it and/or
9 // modify it under the terms of the GNU General Public License as
10 // published by the Free Software Foundation; either version 2 of the
11 // License, or (at your option) any later version.
12 //
13 // This program is distributed in the hope that it will be useful, but
14 // WITHOUT ANY WARRANTY; without even the implied warranty of
15 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
16 // General Public License for more details.
17 //
18 // You should have received a copy of the GNU General Public License
19 // along with this program; if not, write to the Free Software
20 // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
21
22 #ifdef HAVE_CONFIG_H
23 #  include <config.h>
24 #endif
25
26 #include <simgear/math/sg_random.h>
27 #include <simgear/math/sg_geodesy.hxx>
28 #include <simgear/scene/model/modellib.hxx>
29
30 #include <Scenery/scenery.hxx>
31
32 #include "AIBallistic.hxx"
33
34 #include <Main/util.hxx>
35 #include <Environment/gravity.hxx>
36
37 using namespace simgear;
38 using std::string;
39
40 const double FGAIBallistic::slugs_to_kgs = 14.5939029372;
41 const double FGAIBallistic::slugs_to_lbs = 32.1740485564;
42
43 FGAIBallistic::FGAIBallistic(object_type ot) :
44 FGAIBase(ot, false),
45 _height(0.0),
46 _speed(0),
47 _ht_agl_ft(0.0),
48 _azimuth(0.0),
49 _elevation(0.0),
50 _rotation(0.0),
51 hs(0),
52 _az_random_error(0.0),
53 _el_random_error(0.0),
54 _elapsed_time(0),
55 _aero_stabilised(false),
56 _drag_area(0.007),
57 _cd(0.029),
58 _init_cd(0.029),
59 _cd_randomness(0.0),
60 _buoyancy(0),
61 _life_timer(0.0),
62 _wind(true),
63 _mass(0),
64 _random(false),
65 _life_randomness(0.0),
66 _load_resistance(0),
67 _solid(false),
68 _force_stabilised(false),
69 _slave_to_ac(false),
70 _slave_load_to_ac(false),
71 _contents_lb(0),
72 _report_collision(false),
73 _report_impact(false),
74 _external_force(false),
75 _report_expiry(false),
76 _impact_report_node(fgGetNode("/ai/models/model-impact", true))
77
78 {
79     no_roll = false;
80 }
81
82 FGAIBallistic::~FGAIBallistic() {
83 }
84
85 void FGAIBallistic::readFromScenario(SGPropertyNode* scFileNode) {
86     if (!scFileNode){
87         return;
88     }
89
90     FGAIBase::readFromScenario(scFileNode);
91
92     //setPath(scFileNode->getStringValue("model", "Models/Geometry/rocket.ac")); 
93     setRandom(scFileNode->getBoolValue("random", false));
94     setAzimuth(scFileNode->getDoubleValue("azimuth", 0.0));
95     setElevation(scFileNode->getDoubleValue("elevation", 0));
96     setDragArea(scFileNode->getDoubleValue("eda", 0.007));
97     setLife(scFileNode->getDoubleValue("life", 900.0));
98     setBuoyancy(scFileNode->getDoubleValue("buoyancy", 0));
99     //setWind_from_east(scFileNode->getDoubleValue("wind_from_east", 0));
100     //setWind_from_north(scFileNode->getDoubleValue("wind_from_north", 0));
101     setWind(scFileNode->getBoolValue("wind", false));
102     setRoll(scFileNode->getDoubleValue("roll", 0.0));
103     setCd(scFileNode->getDoubleValue("cd", 0.029));
104     //setMass(scFileNode->getDoubleValue("mass", 0.007));
105     setWeight(scFileNode->getDoubleValue("weight", 0.25));
106     setStabilisation(scFileNode->getBoolValue("aero-stabilised", false));
107     setNoRoll(scFileNode->getBoolValue("no-roll", false));
108     setImpact(scFileNode->getBoolValue("impact", false));
109     setExpiry(scFileNode->getBoolValue("expiry", false));
110     setCollision(scFileNode->getBoolValue("collision", false));
111     setImpactReportNode(scFileNode->getStringValue("impact-reports"));
112     setName(scFileNode->getStringValue("name", "Rocket"));
113     setFuseRange(scFileNode->getDoubleValue("fuse-range", 0.0));
114     setSMPath(scFileNode->getStringValue("submodel-path", ""));
115     setSubID(scFileNode->getIntValue("SubID", 0));
116     setExternalForce(scFileNode->getBoolValue("external-force", false));
117     setForcePath(scFileNode->getStringValue("force-path", ""));
118     setForceStabilisation(scFileNode->getBoolValue("force-stabilised", false));
119     setXoffset(scFileNode->getDoubleValue("x-offset", 0.0));
120     setYoffset(scFileNode->getDoubleValue("y-offset", 0.0));
121     setZoffset(scFileNode->getDoubleValue("z-offset", 0.0));
122     setPitchoffset(scFileNode->getDoubleValue("pitch-offset", 0.0));
123     setRolloffset(scFileNode->getDoubleValue("roll-offset", 0.0));
124     setYawoffset(scFileNode->getDoubleValue("yaw-offset", 0.0));
125     setGroundOffset(scFileNode->getDoubleValue("ground-offset", 0.0));
126     setLoadOffset(scFileNode->getDoubleValue("load-offset", 0.0));
127     setSlaved(scFileNode->getBoolValue("slaved", false));
128     setSlavedLoad(scFileNode->getBoolValue("slaved-load", false));
129     setContentsPath(scFileNode->getStringValue("contents"));
130     setParentName(scFileNode->getStringValue("parent"));
131 }
132
133 bool FGAIBallistic::init(bool search_in_AI_path) {
134     FGAIBase::init(search_in_AI_path);
135     reinit();
136     return true;
137 }
138
139 void FGAIBallistic::reinit() {
140     _impact_reported = false;
141     _collision_reported = false;
142     _expiry_reported = false;
143
144     _impact_lat = 0;
145     _impact_lon = 0;
146     _impact_elev = 0;
147     _impact_hdg = 0;
148     _impact_pitch = 0;
149     _impact_roll = 0;
150     _impact_speed = 0;
151
152     invisible = false;
153
154     _elapsed_time += (sg_random() * 100);
155
156     _life_timer = 0;
157
158     props->setStringValue("material/name", "");
159     props->setStringValue("name", _name.c_str());
160     props->setStringValue("submodels/path", _path.c_str());
161
162     if (_slave_to_ac) {
163         props->setStringValue("force/path", _force_path.c_str());
164         props->setStringValue("contents/path", _contents_path.c_str());
165     }
166
167     //cout << "init: name " << _name.c_str() << " _life_timer " << _life_timer 
168     //    << endl;
169
170     //if(_parent != ""){
171     //    setParentNode();
172     //}
173
174     //setParentNodes(_selected_ac);
175
176     //props->setStringValue("vector/path", _vector_path.c_str());
177
178     // start with high value so that animations don't trigger yet
179     _ht_agl_ft = 1e10;
180     hdg = _azimuth;
181     pitch = _elevation;
182     roll = _rotation;
183
184     Transform();
185
186     if (_parent != "") {
187         setParentNode();
188     }
189
190     setParentNodes(_selected_ac);
191
192     FGAIBase::reinit();
193 }
194
195 void FGAIBallistic::bind() {
196     //    FGAIBase::bind();
197
198     _tiedProperties.setRoot(props);
199     tie("sim/time/elapsed-sec",
200         SGRawValueMethods<FGAIBallistic,double>(*this,
201         &FGAIBallistic::_getTime, &FGAIBallistic::setTime));
202     //tie("mass-slug",
203     //    SGRawValueMethods<FGAIBallistic,double>(*this,
204     //    &FGAIBallistic::getMass));
205
206     tie("material/solid",
207         SGRawValuePointer<bool>(&_solid));
208     tie("altitude-agl-ft",
209         SGRawValuePointer<double>(&_ht_agl_ft));
210     tie("controls/slave-to-ac",
211         SGRawValueMethods<FGAIBallistic,bool>
212         (*this, &FGAIBallistic::getSlaved, &FGAIBallistic::setSlaved));
213     tie("controls/invisible",
214         SGRawValuePointer<bool>(&invisible));
215
216     if (_external_force || _slave_to_ac) {
217         tie("controls/force_stabilized",
218             SGRawValuePointer<bool>(&_force_stabilised));
219         tie("position/global-x",
220             SGRawValueMethods<FGAIBase,double>(*this, &FGAIBase::_getCartPosX, 0));
221         tie("position/global-y",
222             SGRawValueMethods<FGAIBase,double>(*this, &FGAIBase::_getCartPosY, 0));
223         tie("position/global-z",
224             SGRawValueMethods<FGAIBase,double>(*this, &FGAIBase::_getCartPosZ, 0));
225         tie("velocities/vertical-speed-fps",
226             SGRawValuePointer<double>(&vs));
227         tie("velocities/true-airspeed-kt",
228             SGRawValuePointer<double>(&speed));
229         tie("velocities/horizontal-speed-fps",
230             SGRawValuePointer<double>(&hs));
231         tie("position/altitude-ft",
232             SGRawValueMethods<FGAIBase,double>(*this, &FGAIBase::_getElevationFt, &FGAIBase::_setAltitude));
233         tie("position/latitude-deg",
234             SGRawValueMethods<FGAIBase,double>(*this, &FGAIBase::_getLatitude, &FGAIBase::_setLatitude));
235         tie("position/longitude-deg",
236             SGRawValueMethods<FGAIBase,double>(*this, &FGAIBase::_getLongitude, &FGAIBase::_setLongitude));
237         tie("orientation/hdg-deg",
238             SGRawValuePointer<double>(&hdg));
239         tie("orientation/pitch-deg",
240             SGRawValuePointer<double>(&pitch));
241         tie("orientation/roll-deg",
242             SGRawValuePointer<double>(&roll));
243         tie("controls/slave-load-to-ac",
244             SGRawValueMethods<FGAIBallistic,bool>
245             (*this, &FGAIBallistic::getSlavedLoad, &FGAIBallistic::setSlavedLoad));
246         tie("position/load-offset",
247             SGRawValueMethods<FGAIBallistic,double>
248             (*this, &FGAIBallistic::getLoadOffset, &FGAIBallistic::setLoadOffset));
249         tie("load/distance-to-hitch-ft",
250             SGRawValueMethods<FGAIBallistic,double>
251             (*this, &FGAIBallistic::getDistanceToHitch));
252         tie("load/elevation-to-hitch-deg",
253             SGRawValueMethods<FGAIBallistic,double>
254             (*this, &FGAIBallistic::getElevToHitch));
255         tie("load/bearing-to-hitch-deg",
256             SGRawValueMethods<FGAIBallistic,double>
257             (*this, &FGAIBallistic::getBearingToHitch));
258         tie("material/load-resistance",
259         SGRawValuePointer<double>(&_load_resistance));
260     }
261 }
262
263 void FGAIBallistic::update(double dt)
264 {
265     FGAIBase::update(dt);
266
267     if (_slave_to_ac) {
268         slaveToAC(dt);
269         Transform();
270     }
271     else if (!invisible) {
272         Run(dt);
273         Transform();
274     }
275
276 }
277
278 void FGAIBallistic::setAzimuth(double az) {
279     if (_random)
280         hdg = _azimuth = az - _az_random_error + 2 * _az_random_error * sg_random();
281     else 
282         hdg = _azimuth = az;
283 }
284
285 void FGAIBallistic::setAzimuthRandomError(double error) {
286     _az_random_error = error;
287 }
288
289 void FGAIBallistic::setElevationRandomError(double error) {
290     _el_random_error = error;
291 }
292
293 void FGAIBallistic::setElevation(double el) {
294     if (_random)
295         pitch = _elevation = el - _el_random_error + 2 * _el_random_error * sg_random();
296     else
297         pitch = _elevation = el;
298 }
299
300 void FGAIBallistic::setRoll(double rl) {
301     roll = _rotation = rl;
302 }
303
304 void FGAIBallistic::setStabilisation(bool val) {
305     _aero_stabilised = val;
306 }
307
308 void FGAIBallistic::setForceStabilisation(bool val) {
309     _force_stabilised = val;
310 }
311
312 void FGAIBallistic::setNoRoll(bool nr) {
313     no_roll = nr;
314 }
315
316 void FGAIBallistic::setDragArea(double a) {
317     _drag_area = a;
318 }
319
320 void FGAIBallistic::setLife(double seconds) {
321     if (_random)
322         life = seconds * _life_randomness + (seconds * (1 -_life_randomness) * sg_random());
323     else
324         life = seconds;
325 }
326
327 void FGAIBallistic::setBuoyancy(double fpss) {
328     _buoyancy = fpss;
329 }
330
331 void FGAIBallistic::setWind_from_east(double fps) {
332     _wind_from_east = fps;
333 }
334
335 void FGAIBallistic::setWind_from_north(double fps) {
336     _wind_from_north = fps;
337 }
338
339 void FGAIBallistic::setWind(bool val) {
340     _wind = val;
341 }
342
343 void FGAIBallistic::setCd(double cd) {
344     _cd = cd;
345     _init_cd = cd;
346 }
347
348 void FGAIBallistic::setCdRandomness(double randomness) {
349     _cd_randomness = randomness;
350 }
351
352 void FGAIBallistic::setMass(double m) {
353     _mass = m;
354 }
355
356 void FGAIBallistic::setWeight(double w) {
357     _weight_lb = w;
358 }
359
360 void FGAIBallistic::setLifeRandomness(double randomness) {
361     _life_randomness = randomness;
362 }
363
364 void FGAIBallistic::setRandom(bool r) {
365     _random = r;
366 }
367
368 void FGAIBallistic::setImpact(bool i) {
369     _report_impact = i;
370 }
371
372 void FGAIBallistic::setCollision(bool c) {
373     _report_collision = c;
374 }
375
376 void FGAIBallistic::setExpiry(bool e) {
377     _report_expiry = e;
378 }
379
380 void FGAIBallistic::setExternalForce(bool f) {
381     _external_force = f;
382 }
383
384 void FGAIBallistic::setImpactReportNode(const string& path) {
385     if (!path.empty())
386         _impact_report_node = fgGetNode(path.c_str(), true);
387 }
388
389 void FGAIBallistic::setSMPath(const string& s) {
390     _path = s;
391     //cout << "submodel path " << _path << endl;
392 }
393
394 void FGAIBallistic::setFuseRange(double f) {
395     _fuse_range = f;
396 }
397
398 void FGAIBallistic::setSubID(int i) {
399     _subID = i;
400 }
401
402 void FGAIBallistic::setSubmodel(const string& s) {
403     _submodel = s;
404 }
405
406 void FGAIBallistic::setGroundOffset(double g) {
407     _ground_offset = g;
408 }
409
410 void FGAIBallistic::setLoadOffset(double l) {
411     _load_offset = l;
412 }
413
414 double FGAIBallistic::getLoadOffset() const {
415     return _load_offset;
416 }
417
418 void FGAIBallistic::setSlaved(bool s) {
419     _slave_to_ac = s;
420 }
421
422 void FGAIBallistic::setContentsPath(const string& path) {
423     _contents_path = path;
424
425     if (!path.empty()) {
426         _contents_node = fgGetNode(path.c_str(), true);
427     }
428 }
429
430 void FGAIBallistic::setContentsNode(SGPropertyNode_ptr node) {
431     if (node != 0) {
432         _contents_node = node;
433         _contents_path = _contents_node->getDisplayName();
434     }
435 }
436
437 void FGAIBallistic::setParentNodes(SGPropertyNode_ptr node) {
438     if (node != 0) {
439         _pnode = node;
440         _p_pos_node = _pnode->getChild("position", 0, true);
441         _p_lat_node = _p_pos_node->getChild("latitude-deg", 0, true);
442         _p_lon_node = _p_pos_node->getChild("longitude-deg", 0, true);
443         _p_alt_node = _p_pos_node->getChild("altitude-ft", 0, true);
444         _p_agl_node = _p_pos_node->getChild("altitude-agl-ft", 0, true);
445
446
447         _p_ori_node = _pnode->getChild("orientation", 0, true);
448         _p_pch_node = _p_ori_node->getChild("pitch-deg", 0, true);
449         _p_rll_node = _p_ori_node->getChild("roll-deg", 0, true);
450         _p_hdg_node = _p_ori_node->getChild("true-heading-deg",0, true);
451
452         _p_vel_node = _pnode->getChild("velocities", 0, true);
453         _p_spd_node = _p_vel_node->getChild("true-airspeed-kt", 0, true);
454     }
455 }
456
457 void FGAIBallistic::setParentPos() {
458     if (_pnode != 0) { 
459         double lat = _p_lat_node->getDoubleValue();
460         double lon = _p_lon_node->getDoubleValue();
461         double alt = _p_alt_node->getDoubleValue();
462
463         _parentpos.setLongitudeDeg(lon);
464         _parentpos.setLatitudeDeg(lat);
465         _parentpos.setElevationFt(alt);
466     }
467 }
468
469 bool FGAIBallistic::getSlaved() const {
470     return _slave_to_ac;
471 }
472
473 double FGAIBallistic::getMass() const {
474     return _mass;
475 }
476
477 double FGAIBallistic::getContents() {
478     if (_contents_node) {
479         _contents_lb = _contents_node->getChild("level-lbs", 0, 1)->getDoubleValue();
480     }
481     return _contents_lb;
482 }
483
484 void FGAIBallistic::setContents(double c) {
485     if (_contents_node)
486         _contents_lb = _contents_node->getChild("level-gal_us", 0, 1)->setDoubleValue(c);
487 }
488
489 void FGAIBallistic::setSlavedLoad(bool l) {
490     _slave_load_to_ac = l;
491 }
492
493 bool FGAIBallistic::getSlavedLoad() const {
494     return _slave_load_to_ac;
495 }
496
497 void FGAIBallistic::setForcePath(const string& p) {
498     _force_path = p;
499     if (!_force_path.empty()) {
500         SGPropertyNode *fnode = fgGetNode(_force_path.c_str(), 0, true );
501         _force_node = fnode->getChild("force-lb", 0, true);
502         _force_azimuth_node = fnode->getChild("force-azimuth-deg", 0, true);
503         _force_elevation_node = fnode->getChild("force-elevation-deg", 0, true);
504     }
505 }
506
507 bool FGAIBallistic::getHtAGL(double start) {
508     const simgear::BVHMaterial* mat = 0;
509     if (getGroundElevationM(SGGeod::fromGeodM(pos, start),
510         _elevation_m, &mat)) {
511             const SGMaterial* material = dynamic_cast<const SGMaterial*>(mat);
512             _ht_agl_ft = pos.getElevationFt() - _elevation_m * SG_METER_TO_FEET;
513
514             if (material) {
515                 const std::vector<string>& names = material->get_names();
516                 _solid = material->get_solid();
517                 _load_resistance = material->get_load_resistance();
518                 _frictionFactor = material->get_friction_factor();
519
520                 if (!names.empty())
521                     props->setStringValue("material/name", names[0].c_str());
522                 else
523                     props->setStringValue("material/name", "");
524
525                 _mat_name = names[0];
526
527                 //cout << "material " << _mat_name 
528                 //<< " solid " << _solid 
529                 //<< " load " << _load_resistance
530                 //<< " frictionFactor " << _frictionFactor
531                 //<< endl;
532             }
533
534             return true;
535     }
536     else {
537         return false;
538     }
539 }
540
541 double FGAIBallistic::getRecip(double az) {
542     // calculate the reciprocal of the input azimuth 
543     if (az - 180 < 0) {
544         return az + 180;
545     }
546     else {
547         return az - 180; 
548     }
549 }
550
551 void FGAIBallistic::setPch(double e, double dt, double coeff) {
552     double c = dt / (coeff + dt);
553     pitch = (e * c) + (pitch * (1 - c));
554 }
555
556 void FGAIBallistic::setBnk(double r, double dt, double coeff) {
557     double c = dt / (coeff + dt);
558     roll = (r * c) + (roll * (1 - c));
559 }
560
561 void FGAIBallistic::setSpd(double s, double dt, double coeff) {
562     double c = dt / (coeff + dt);
563     _speed = (s * c) + (_speed * (1 - c));
564 }
565
566 void FGAIBallistic::setHt(double h, double dt, double coeff) {
567     double c = dt / (coeff + dt);
568     _height = (h * c) + (_height * (1 - c));
569 }
570
571 int FGAIBallistic::setHdg(double tgt_hdg, double dt, double coeff) {
572     double recip = getRecip(hdg);
573     double c = dt / (coeff + dt);
574     //cout << "set heading " << tgt_hdg << endl;
575     //we need to ensure that we turn the short way to the new hdg
576     if (tgt_hdg < recip && tgt_hdg < hdg && hdg > 180) {
577         hdg = ((tgt_hdg + 360) * c) + (hdg * (1 - c));
578 //        cout << "case 1: right turn" << endl;
579     } else if (tgt_hdg > recip && tgt_hdg > hdg && hdg <= 180){
580         hdg = ((tgt_hdg - 360) * c) + (hdg * (1 - c));
581 //        cout << "case 2: left turn" << endl;
582     } else {
583         hdg = (tgt_hdg * c) + (hdg * (1 - c));
584 //        cout << "case 4: left turn" << endl;
585     }
586     return -1;
587 }
588
589 double  FGAIBallistic::getTgtXOffset() const {
590     return _tgt_x_offset;
591 }
592
593 double  FGAIBallistic::getTgtYOffset() const {
594     return _tgt_y_offset;
595
596
597 double  FGAIBallistic::getTgtZOffset() const {
598     return _tgt_z_offset;
599 }
600
601 void FGAIBallistic::setTgtXOffset(double x) {
602     _tgt_x_offset = x;
603 }
604
605 void FGAIBallistic::setTgtYOffset(double y) {
606     _tgt_y_offset = y;
607 }
608
609 void FGAIBallistic::setTgtZOffset(double z) {
610     _tgt_z_offset = z;
611 }
612
613 void FGAIBallistic::slaveToAC(double dt) {
614     if (invisible)
615         return;
616
617     double hdg, pch, rll;//, agl = 0;
618
619     if (_pnode != 0) {
620         setParentPos();
621         hdg = _p_hdg_node->getDoubleValue();
622         pch = _p_pch_node->getDoubleValue();
623         rll = _p_rll_node->getDoubleValue();
624 //        agl = _p_agl_node->getDoubleValue();
625         setOffsetPos(_parentpos, hdg, pch, rll);
626         setSpeed(_p_spd_node->getDoubleValue());
627     }
628     else {
629         hdg = manager->get_user_heading();
630         pch = manager->get_user_pitch();
631         rll = manager->get_user_roll();
632 //        agl = manager->get_user_agl();
633         setOffsetPos(globals->get_aircraft_position(), hdg, pch, rll);
634         setSpeed(manager->get_user_speed());
635     }
636
637     pos.setLatitudeDeg(_offsetpos.getLatitudeDeg());
638     pos.setLongitudeDeg(_offsetpos.getLongitudeDeg());
639     pos.setElevationFt(_offsetpos.getElevationFt());
640     setHeading(hdg);
641     setPitch(pch + _pitch_offset);
642     setBank(rll + _roll_offset);
643     setOffsetVelocity(dt, pos);
644     setTime(0);
645
646     //update the mass (slugs)
647     _mass = (_weight_lb + getContents()) / slugs_to_lbs;
648
649     _impact_reported = false;
650
651     //cout << _name << " _mass "<<_mass <<" " << getContents() 
652     //<< " " << getContents() / slugs_to_lbs << " weight " << _weight_lb << endl;
653     //    cout << _name << " update hs " << hs << " vs " << vs << endl;
654 }
655
656 void FGAIBallistic::Run(double dt) {
657     _life_timer += dt;
658     
659     //_pass += 1;
660     //cout<<"AIBallistic run: name " << _name.c_str() 
661     //    << " dt " << dt <<  " _life_timer " << _life_timer << " pass " << _pass << endl;
662
663     // if life = -1 the object does not die
664     if (_life_timer > life && life != -1) {
665         if (_report_expiry && !_expiry_reported && !_impact_reported && !_collision_reported) {
666             //cout<<"AIBallistic run: name " << _name.c_str() << " expiry " 
667                 //<< " _life_timer " << _life_timer<< endl;
668             handle_expiry();
669         }
670         else {
671             //cout<<"AIBallistic run: name " << _name.c_str() 
672             //    << " die " <<  " _life_timer " << _life_timer << endl;
673             setDie(true);
674         }
675
676         setTime(0);
677     }
678
679     // Set the contents in the appropriate tank or other property in the parent to zero
680     setContents(0);
681
682     if (_random) {
683         // Keep the new Cd within +- 10% of the current Cd to avoid a fluctuating value
684         double cd_min = _cd * 0.9;
685         double cd_max = _cd * 1.1;
686
687         // Randomize Cd by +- a certain percentage of the initial Cd
688         _cd = _init_cd * (1 - _cd_randomness + 2 * _cd_randomness * sg_random());
689
690         if (_cd < cd_min) _cd = cd_min;
691         if (_cd > cd_max) _cd = cd_max;
692     }
693
694     // Adjust Cd by Mach number. The equations are based on curves
695     // for a conventional shell/bullet (no boat-tail).
696     double Cdm;
697
698     if (Mach < 0.7)
699         Cdm = 0.0125 * Mach + _cd;
700     else if (Mach < 1.2)
701         Cdm = 0.3742 * pow(Mach, 2) - 0.252 * Mach + 0.0021 + _cd;
702     else
703         Cdm = 0.2965 * pow(Mach, -1.1506) + _cd;
704
705     //cout <<_name << " Mach " << Mach << " Cdm " << Cdm 
706     //    << " ballistic speed kts "<< speed <<  endl;
707
708     // drag = Cd * 0.5 * rho * speed * speed * drag_area;
709     // rho is adjusted for altitude in void FGAIBase::update,
710     // using Standard Atmosphere (sealevel temperature 15C)
711     // acceleration = drag/mass;
712     // adjust speed by drag
713     speed -= (Cdm * 0.5 * rho * speed * speed * _drag_area/_mass) * dt;
714
715     // don't let speed become negative
716     if (speed < 0.0)
717         speed = 0.0;
718
719 //    double speed_fps = speed * SG_KT_TO_FPS;
720
721     // calculate vertical and horizontal speed components
722     calcVSHS();
723
724     //resolve horizontal speed into north and east components:
725     //and convert horizontal speed (fps) to degrees per second
726     calcNE();
727
728     // If wind not required, set to zero
729     if (!_wind) {
730         _wind_from_north = 0;
731         _wind_from_east = 0;
732     }
733     else {
734         _wind_from_north = manager->get_wind_from_north();
735         _wind_from_east = manager->get_wind_from_east();
736     }
737
738     // Calculate velocity due to external force
739     double force_speed_north_deg_sec = 0;
740     double force_speed_east_deg_sec = 0;
741     double hs_force_fps = 0;
742     double v_force_acc_fpss = 0;
743     double force_speed_north_fps = 0;
744     double force_speed_east_fps = 0;
745     double h_force_lbs = 0;
746     double normal_force_lbs = 0;
747     double normal_force_fpss = 0;
748     double static_friction_force_lbs = 0;
749     double dynamic_friction_force_lbs = 0;
750     double friction_force_speed_north_fps = 0;
751     double friction_force_speed_east_fps = 0;
752     double friction_force_speed_north_deg_sec = 0;
753     double friction_force_speed_east_deg_sec = 0;
754     double force_elevation_deg = 0;
755     double force_azimuth_deg  = 0;
756     double force_lbs = 0;
757
758     if (_external_force) {
759         //cout << _name << " external force " <<  hdg << " az " << _azimuth << endl;
760
761         SGPropertyNode *n = fgGetNode(_force_path.c_str(), true);
762         force_lbs            = n->getChild("force-lb", 0, true)->getDoubleValue();
763         force_elevation_deg  = n->getChild("force-elevation-deg", 0, true)->getDoubleValue();
764         force_azimuth_deg    = n->getChild("force-azimuth-deg", 0, true)->getDoubleValue();
765         
766         // Resolve force into vertical and horizontal components:
767         double v_force_lbs = force_lbs * sin( force_elevation_deg * SG_DEGREES_TO_RADIANS );
768         h_force_lbs = force_lbs * cos( force_elevation_deg * SG_DEGREES_TO_RADIANS );
769
770         // Perform ground interaction if impacts are not calculated
771         if (!_report_impact && getHtAGL(10000)) {
772             double deadzone = 0.1;
773
774             if (_ht_agl_ft <= (0 + _ground_offset + deadzone) && _solid) {
775                 normal_force_lbs = (_mass * slugs_to_lbs) - v_force_lbs;
776
777                 if (normal_force_lbs < 0)
778                     normal_force_lbs = 0;
779
780                 pos.setElevationFt(0 + _ground_offset);
781                 if (vs < 0)
782                     vs = -vs * 0.5;
783
784                 // Calculate friction. We assume a static coefficient of
785                 // friction (mu) of 0.62 (wood on concrete)
786                 double mu = 0.62;
787
788                 static_friction_force_lbs = mu * normal_force_lbs * _frictionFactor;
789
790                 // Adjust horizontal force. We assume that a speed of <= 5 fps is static
791                 if (h_force_lbs <= static_friction_force_lbs && hs <= 5) {
792                     h_force_lbs = hs = 0;
793                     _speed_north_fps = _speed_east_fps = 0;
794                 }
795                 else
796                     dynamic_friction_force_lbs = (static_friction_force_lbs * 0.95);
797
798                 // Ignore wind when on the ground for now
799                 //TODO fix this
800                 _wind_from_north = 0;
801                 _wind_from_east = 0;
802             }
803         }
804
805         //acceleration = (force(lbsf)/mass(slugs))
806         v_force_acc_fpss = v_force_lbs / _mass;
807         normal_force_fpss = normal_force_lbs / _mass;
808         double h_force_acc_fpss = h_force_lbs / _mass;
809         double dynamic_friction_acc_fpss = dynamic_friction_force_lbs / _mass;
810
811         // velocity = acceleration * dt
812         hs_force_fps = h_force_acc_fpss * dt;
813         double friction_force_fps = dynamic_friction_acc_fpss * dt;
814
815         //resolve horizontal speeds into north and east components:
816         force_speed_north_fps   = cos(force_azimuth_deg * SG_DEGREES_TO_RADIANS) * hs_force_fps;
817         force_speed_east_fps    = sin(force_azimuth_deg * SG_DEGREES_TO_RADIANS) * hs_force_fps;
818
819         friction_force_speed_north_fps = cos(getRecip(hdg) * SG_DEGREES_TO_RADIANS) * friction_force_fps;
820         friction_force_speed_east_fps  = sin(getRecip(hdg) * SG_DEGREES_TO_RADIANS) * friction_force_fps;
821
822         // convert horizontal speed (fps) to degrees per second
823         force_speed_north_deg_sec = force_speed_north_fps / ft_per_deg_lat;
824         force_speed_east_deg_sec  = force_speed_east_fps / ft_per_deg_lon;
825
826         friction_force_speed_north_deg_sec = friction_force_speed_north_fps / ft_per_deg_lat;
827         friction_force_speed_east_deg_sec  = friction_force_speed_east_fps / ft_per_deg_lon;
828     }
829
830     // convert wind speed (fps) to degrees lat/lon per second
831     double wind_speed_from_north_deg_sec = _wind_from_north / ft_per_deg_lat;
832     double wind_speed_from_east_deg_sec  = _wind_from_east / ft_per_deg_lon;
833
834     //recombine the horizontal velocity components
835     hs = sqrt(((_speed_north_fps + force_speed_north_fps + friction_force_speed_north_fps) 
836         * (_speed_north_fps + force_speed_north_fps + friction_force_speed_north_fps))
837         + ((_speed_east_fps + force_speed_east_fps + friction_force_speed_east_fps) 
838         * (_speed_east_fps + force_speed_east_fps + friction_force_speed_east_fps)));
839
840     if (hs <= 0.00001)
841         hs = 0;
842
843     // adjust vertical speed for acceleration of gravity, buoyancy, and vertical force
844     double gravity = SG_METER_TO_FEET * (Environment::Gravity::instance()->getGravity(pos));
845     vs -= (gravity - _buoyancy - v_force_acc_fpss - normal_force_fpss) * dt;
846
847     if (vs <= 0.00001 && vs >= -0.00001)
848         vs = 0;
849
850     // set new position
851     if (_slave_load_to_ac) {
852         setOffsetPos(pos, 
853             manager->get_user_heading(),
854             manager->get_user_pitch(), 
855             manager->get_user_roll()
856             );
857         pos.setLatitudeDeg(_offsetpos.getLatitudeDeg());
858         pos.setLongitudeDeg(_offsetpos.getLongitudeDeg());
859         pos.setElevationFt(_offsetpos.getElevationFt());
860
861         if (getHtAGL(10000)) {
862             double deadzone = 0.1;
863
864             if (_ht_agl_ft <= (0 + _ground_offset + deadzone) && _solid) {
865                 pos.setElevationFt(0 + _ground_offset);
866             }
867             else {
868                 pos.setElevationFt(_offsetpos.getElevationFt() + _load_offset);
869             }
870         }
871     }
872     else {
873         pos.setLatitudeDeg( pos.getLatitudeDeg()
874             + (speed_north_deg_sec - wind_speed_from_north_deg_sec 
875             + force_speed_north_deg_sec + friction_force_speed_north_deg_sec) * dt );
876         pos.setLongitudeDeg( pos.getLongitudeDeg()
877             + (speed_east_deg_sec - wind_speed_from_east_deg_sec 
878             + force_speed_east_deg_sec + friction_force_speed_east_deg_sec) * dt );
879         pos.setElevationFt(pos.getElevationFt() + vs * dt);
880     }
881
882 //    cout << _name << " run hs " << hs << " vs " << vs << endl;
883
884     // recalculate total speed
885     if ( vs == 0 && hs == 0)
886         speed = 0;
887     else
888         speed = sqrt( vs * vs + hs * hs) / SG_KT_TO_FPS;
889
890     // recalculate elevation and azimuth (velocity vectors)
891     _elevation = atan2( vs, hs ) * SG_RADIANS_TO_DEGREES;
892     _azimuth =  atan2((_speed_east_fps + force_speed_east_fps + friction_force_speed_east_fps), 
893         (_speed_north_fps + force_speed_north_fps + friction_force_speed_north_fps))
894         * SG_RADIANS_TO_DEGREES;
895
896     // rationalise azimuth
897     if (_azimuth < 0)
898         _azimuth += 360;
899
900     if (_aero_stabilised) { // we simulate rotational moment of inertia by using a filter
901         //cout<< "_aero_stabilised " << hdg << " az " << _azimuth << endl;
902         const double coeff = 0.9;
903
904         // we assume a symetrical MI about the pitch and yaw axis
905         setPch(_elevation, dt, coeff);
906         setHdg(_azimuth, dt, coeff);
907     }
908     else if (_force_stabilised) { // we simulate rotational moment of inertia by using a filter
909         //cout<< "_force_stabilised "<< endl;
910         
911         const double coeff = 0.9;
912         double ratio = h_force_lbs/(_mass * slugs_to_lbs);
913
914         if (ratio >  1) ratio =  1;
915         if (ratio < -1) ratio = -1;
916
917         double force_pitch = acos(ratio) * SG_RADIANS_TO_DEGREES;
918
919         if (force_pitch <= force_elevation_deg)
920             force_pitch = force_elevation_deg;
921
922         // we assume a symetrical MI about the pitch and yaw axis
923         setPch(force_pitch,dt, coeff);
924         setHdg(_azimuth, dt, coeff);
925     }
926
927     // Do impacts and collisions
928     if (_report_impact && !_impact_reported)
929         handle_impact();
930
931     if (_report_collision && !_collision_reported)
932         handle_collision();
933
934     // Set destruction flag if altitude less than sea level -1000
935     if (altitude_ft < -1000.0 && life != -1)
936         setDie(true);
937 }
938
939 double FGAIBallistic::_getTime() const {
940     return _life_timer;
941 }
942
943 void FGAIBallistic::setTime(double s) {
944     _life_timer = s;
945 }
946
947 void FGAIBallistic::handleEndOfLife(double elevation) {
948     report_impact(elevation);
949
950     // Make the submodel invisible if the submodel is immortal, otherwise kill it if it has no subsubmodels
951     if (life == -1) {
952         invisible = true;
953     }
954     else if (_subID == 0) {
955         // Kill the AIObject if there is no subsubmodel
956         setDie(true);
957     }
958 }
959
960 void FGAIBallistic::handle_impact() {
961     // Try terrain intersection
962     double start = pos.getElevationM() + 100;
963
964     if (!getHtAGL(start))
965         return;
966
967     if (_ht_agl_ft <= 0) {
968         SG_LOG(SG_AI, SG_DEBUG, "AIBallistic: terrain impact material" << _mat_name);
969         _impact_reported = true;
970         handleEndOfLife(_elevation_m);
971     } 
972 }
973
974 void FGAIBallistic::handle_expiry() {
975     _expiry_reported = true;
976     handleEndOfLife(pos.getElevationM());
977 }
978
979 void FGAIBallistic::handle_collision()
980 {
981     const FGAIBase *object = manager->calcCollision(pos.getElevationFt(),
982         pos.getLatitudeDeg(),pos.getLongitudeDeg(), _fuse_range);
983
984     if (object) {
985         report_impact(pos.getElevationM(), object);
986         _collision_reported = true;
987     }
988 }
989
990 void FGAIBallistic::report_impact(double elevation, const FGAIBase *object)
991 {
992     _impact_lat    = pos.getLatitudeDeg();
993     _impact_lon    = pos.getLongitudeDeg();
994     _impact_elev   = elevation;
995     _impact_speed  = speed * SG_KT_TO_MPS;
996     _impact_hdg    = hdg;
997     _impact_pitch  = pitch;
998     _impact_roll   = roll;
999
1000     SGPropertyNode *n = props->getNode("impact", true);
1001
1002     if (object)
1003         n->setStringValue("type", object->getTypeString());
1004     else
1005         n->setStringValue("type", "terrain");
1006
1007     SG_LOG(SG_AI, SG_DEBUG, "AIBallistic: object impact " << _name 
1008         << " lon " <<_impact_lon << " lat " <<_impact_lat << " sec " << _life_timer);
1009
1010     n->setDoubleValue("longitude-deg", _impact_lon);
1011     n->setDoubleValue("latitude-deg", _impact_lat);
1012     n->setDoubleValue("elevation-m", _impact_elev);
1013     n->setDoubleValue("heading-deg", _impact_hdg);
1014     n->setDoubleValue("pitch-deg", _impact_pitch);
1015     n->setDoubleValue("roll-deg", _impact_roll);
1016     n->setDoubleValue("speed-mps", _impact_speed);
1017
1018     _impact_report_node->setStringValue(props->getPath());
1019 }
1020
1021 SGVec3d FGAIBallistic::getCartHitchPos() const {
1022     // convert geodetic positions to geocentered
1023     SGVec3d cartuserPos = globals->get_aircraft_position_cart();
1024     
1025     //SGVec3d cartPos = getCartPos();
1026
1027     // Transform to the right coordinate frame, configuration is done in
1028     // the x-forward, y-right, z-up coordinates (feet), computation
1029     // in the simulation usual body x-forward, y-right, z-down coordinates
1030     // (meters) )
1031     SGVec3d _off(_x_offset * SG_FEET_TO_METER,
1032             _y_offset * SG_FEET_TO_METER,
1033             -_z_offset * SG_FEET_TO_METER);
1034
1035     // Transform the user position to the horizontal local coordinate system.
1036     SGQuatd hlTrans = SGQuatd::fromLonLat(globals->get_aircraft_position());
1037
1038     // and postrotate the orientation of the user model wrt the horizontal
1039     // local frame
1040     hlTrans *= SGQuatd::fromYawPitchRollDeg(
1041         manager->get_user_heading(),
1042         manager->get_user_pitch(),
1043         manager->get_user_roll());
1044
1045     // The offset converted to the usual body fixed coordinate system
1046     // rotated to the earth-fixed coordinates axis
1047     SGVec3d off = hlTrans.backTransform(_off);
1048
1049     // Add the position offset of the user model to get the geocentered position
1050     SGVec3d offsetPos = cartuserPos + off;
1051     return offsetPos;
1052 }
1053
1054 void FGAIBallistic::setOffsetPos(SGGeod inpos, double heading, double pitch, double roll) {
1055     // Convert the hitch geocentered position to geodetic
1056     SGVec3d cartoffsetPos = getCartOffsetPos(inpos, heading, pitch, roll);
1057     SGGeodesy::SGCartToGeod(cartoffsetPos, _offsetpos);
1058 }
1059
1060 double FGAIBallistic::getDistanceToHitch() const {
1061     //calculate the distance load to hitch 
1062     SGVec3d carthitchPos = getCartHitchPos();
1063     SGVec3d cartPos = getCartPos();
1064
1065     SGVec3d diff = carthitchPos - cartPos;
1066     double distance = norm(diff);
1067     return distance * SG_METER_TO_FEET;
1068 }
1069
1070 double FGAIBallistic::getElevToHitch() const {
1071     // now the angle, positive angles are upwards
1072     double distance = getDistanceToHitch() * SG_FEET_TO_METER;
1073     double angle = 0;
1074     double daltM = _offsetpos.getElevationM() - pos.getElevationM();
1075
1076     if (fabs(distance) < SGLimits<float>::min()) {
1077         angle = 0;
1078     } else {
1079         double sAngle = daltM/distance;
1080         sAngle = SGMiscd::min(1, SGMiscd::max(-1, sAngle));
1081         angle = SGMiscd::rad2deg(asin(sAngle));
1082     }
1083
1084     return angle;
1085 }
1086
1087 double FGAIBallistic::getBearingToHitch() const {
1088     //calculate the bearing and range of the second pos from the first
1089     double distance = getDistanceToHitch() * SG_FEET_TO_METER;
1090     double az1, az2;
1091
1092     geo_inverse_wgs_84(pos, _offsetpos, &az1, &az2, &distance);
1093
1094     return az1;
1095 }
1096
1097 double FGAIBallistic::getRelBrgHitchToUser() const {
1098     //calculate the relative bearing 
1099     double az1, az2, distance;
1100
1101     geo_inverse_wgs_84(_offsetpos, globals->get_aircraft_position(), &az1, &az2, &distance);
1102
1103     double rel_brg = az1 - hdg;
1104
1105     SG_NORMALIZE_RANGE(rel_brg, -180.0, 180.0);
1106
1107     return rel_brg;
1108 }
1109
1110 double FGAIBallistic::getElevHitchToUser() const {
1111     // Calculate the distance from the user position
1112     SGVec3d carthitchPos = getCartHitchPos();
1113     SGVec3d cartuserPos = globals->get_aircraft_position_cart();
1114
1115     SGVec3d diff = cartuserPos - carthitchPos;
1116
1117     double distance = norm(diff);
1118     double angle = 0;
1119
1120     double daltM = globals->get_aircraft_position().getElevationM() - _offsetpos.getElevationM();
1121
1122     // Now the angle, positive angles are upwards
1123     if (fabs(distance) < SGLimits<float>::min()) {
1124         angle = 0;
1125     }
1126     else {
1127         double sAngle = daltM/distance;
1128         sAngle = SGMiscd::min(1, SGMiscd::max(-1, sAngle));
1129         angle = SGMiscd::rad2deg(asin(sAngle));
1130     }
1131
1132     return angle;
1133 }
1134
1135 void FGAIBallistic::setTgtOffsets(double dt, double coeff) {
1136     double c = dt / (coeff + dt);
1137
1138     _x_offset = (_tgt_x_offset * c) + (_x_offset * (1 - c));
1139     _y_offset = (_tgt_y_offset * c) + (_y_offset * (1 - c));
1140     _z_offset = (_tgt_z_offset * c) + (_z_offset * (1 - c));
1141 }
1142
1143 void FGAIBallistic::calcVSHS() {
1144     // Calculate vertical and horizontal speed components
1145     double speed_fps = speed * SG_KT_TO_FPS;
1146
1147     if (speed == 0.0) {
1148         hs = vs = 0.0;
1149     }
1150     else {
1151         vs = sin( _elevation * SG_DEGREES_TO_RADIANS ) * speed_fps;
1152         hs = cos( _elevation * SG_DEGREES_TO_RADIANS ) * speed_fps;
1153     }
1154 }
1155
1156 void FGAIBallistic::calcNE() {
1157     // Resolve horizontal speed into north and east components:
1158     _speed_north_fps = cos(_azimuth / SG_RADIANS_TO_DEGREES) * hs;
1159     _speed_east_fps = sin(_azimuth / SG_RADIANS_TO_DEGREES) * hs;
1160
1161     // Convert horizontal speed (fps) to degrees per second
1162     speed_north_deg_sec = _speed_north_fps / ft_per_deg_lat;
1163     speed_east_deg_sec  = _speed_east_fps / ft_per_deg_lon;
1164 }
1165
1166 SGVec3d FGAIBallistic::getCartOffsetPos(SGGeod inpos, double user_heading, 
1167                                         double user_pitch, double user_roll
1168                                         ) const {
1169     // Convert geodetic positions to geocentered
1170     SGVec3d cartuserPos = SGVec3d::fromGeod(inpos);
1171
1172     // Transform to the right coordinate frame, configuration is done in
1173     // the x-forward, y-right, z-up coordinates (feet), computation
1174     // in the simulation usual body x-forward, y-right, z-down coordinates
1175     // (meters) )
1176     SGVec3d _off(_x_offset * SG_FEET_TO_METER,
1177             _y_offset * SG_FEET_TO_METER,
1178             -_z_offset * SG_FEET_TO_METER);
1179
1180     // Transform the user position to the horizontal local coordinate system.
1181     SGQuatd hlTrans = SGQuatd::fromLonLat(inpos);
1182
1183     // And postrotate the orientation of the user model wrt the horizontal
1184     // local frame
1185     hlTrans *= SGQuatd::fromYawPitchRollDeg(
1186         user_heading,
1187         user_pitch,
1188         user_roll);
1189
1190     // The offset converted to the usual body fixed coordinate system
1191     // rotated to the earth-fixed coordinates axis
1192     SGVec3d off = hlTrans.backTransform(_off);
1193
1194     // Add the position offset of the user model to get the geocentered position
1195     SGVec3d offsetPos = cartuserPos + off;
1196
1197     return offsetPos;
1198 }
1199
1200 void FGAIBallistic::setOffsetVelocity(double dt, SGGeod offsetpos) {
1201     // Calculate the distance from the previous offset position
1202     SGVec3d cartoffsetPos = SGVec3d::fromGeod(offsetpos);
1203     SGVec3d diff = cartoffsetPos - _oldcartoffsetPos;
1204
1205     double distance = norm(diff);
1206     // Calculate speed knots
1207     speed = (distance / dt) * SG_MPS_TO_KT;
1208
1209     // Now calulate the angle between the old and current postion positions (degrees)
1210     double angle = 0;
1211     double daltM = offsetpos.getElevationM() - _oldoffsetpos.getElevationM();
1212
1213     if (fabs(distance) < SGLimits<float>::min()) {
1214         angle = 0;
1215     }
1216     else {
1217         double sAngle = daltM / distance;
1218         sAngle = SGMiscd::min(1, SGMiscd::max(-1, sAngle));
1219         angle = SGMiscd::rad2deg(asin(sAngle));
1220     }
1221
1222     _elevation = angle;
1223
1224     // Calculate vertical and horizontal speed components
1225     calcVSHS();
1226
1227     // Calculate the bearing of the new offset position from the old
1228     // Don't do this if speed is low
1229     //cout << "speed " << speed << endl;
1230     if (speed > 0.1) {
1231         double az1, az2, dist;
1232         geo_inverse_wgs_84(_oldoffsetpos, offsetpos, &az1, &az2, &dist);
1233         _azimuth = az1;
1234         //cout << "offset az " << _azimuth << endl;
1235     }
1236     else {
1237         _azimuth = hdg;
1238         //cout << " slow offset az " << _azimuth << endl;
1239     }
1240
1241     // Resolve horizontal speed into north and east components
1242     calcNE();
1243
1244     // And finally store the new values
1245     _oldcartoffsetPos = cartoffsetPos;
1246     _oldoffsetpos = offsetpos;
1247 }