]> git.mxchange.org Git - flightgear.git/blob - src/AIModel/AICarrier.cxx
ff0d5441ff46d6db15054f593288cace1da66cc8
[flightgear.git] / src / AIModel / AICarrier.cxx
1 // FGAICarrier - FGAIShip-derived class creates an AI aircraft carrier
2 //
3 // Written by David Culp, started October 2004.
4 // - davidculp2@comcast.net
5 //
6 // This program is free software; you can redistribute it and/or
7 // modify it under the terms of the GNU General Public License as
8 // published by the Free Software Foundation; either version 2 of the
9 // License, or (at your option) any later version.
10 //
11 // This program is distributed in the hope that it will be useful, but
12 // WITHOUT ANY WARRANTY; without even the implied warranty of
13 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14 // General Public License for more details.
15 //
16 // You should have received a copy of the GNU General Public License
17 // along with this program; if not, write to the Free Software
18 // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
19
20 #ifdef HAVE_CONFIG_H
21 #  include <config.h>
22 #endif
23
24 #include <algorithm>
25 #include <string>
26 #include <vector>
27
28 #include <osg/Geode>
29 #include <osg/Drawable>
30 #include <osg/Transform>
31 #include <osg/NodeVisitor>
32 #include <osg/TemplatePrimitiveFunctor>
33
34 #include <simgear/sg_inlines.h>
35 #include <simgear/math/SGMath.hxx>
36 #include <simgear/math/sg_geodesy.hxx>
37 #include <simgear/scene/util/SGNodeMasks.hxx>
38 #include <simgear/scene/util/SGSceneUserData.hxx>
39 #include <simgear/scene/bvh/BVHGroup.hxx>
40 #include <simgear/scene/bvh/BVHLineGeometry.hxx>
41
42 #include <math.h>
43 #include <Main/util.hxx>
44 #include <Main/viewer.hxx>
45
46 #include "AICarrier.hxx"
47
48 /// Hmm: move that kind of configuration into the model file???
49 class LineCollector : public osg::NodeVisitor {
50     struct LinePrimitiveFunctor {
51         LinePrimitiveFunctor() : _lineCollector(0)
52         { }
53         void operator() (const osg::Vec3&, bool)
54         { }
55         void operator() (const osg::Vec3& v1, const osg::Vec3& v2, bool)
56         { if (_lineCollector) _lineCollector->addLine(v1, v2); }
57         void operator() (const osg::Vec3&, const osg::Vec3&, const osg::Vec3&,
58                          bool)
59         { }
60         void operator() (const osg::Vec3&, const osg::Vec3&, const osg::Vec3&,
61                          const osg::Vec3&, bool)
62         { }
63         LineCollector* _lineCollector;
64     };
65     
66 public:
67     LineCollector() :
68         osg::NodeVisitor(osg::NodeVisitor::NODE_VISITOR,
69                          osg::NodeVisitor::TRAVERSE_ALL_CHILDREN)
70     { }
71     virtual void apply(osg::Geode& geode)
72     {
73         osg::TemplatePrimitiveFunctor<LinePrimitiveFunctor> pf;
74         pf._lineCollector = this;
75         for (unsigned i = 0; i < geode.getNumDrawables(); ++i) {
76             geode.getDrawable(i)->accept(pf);
77         }
78     }
79     virtual void apply(osg::Node& node)
80     {
81         traverse(node);
82     }
83     virtual void apply(osg::Transform& transform)
84     {
85         osg::Matrix matrix = _matrix;
86         if (transform.computeLocalToWorldMatrix(_matrix, this))
87             traverse(transform);
88         _matrix = matrix;
89     }
90     
91     const std::vector<SGLineSegmentf>& getLineSegments() const
92     { return _lineSegments; }
93     
94     void addLine(const osg::Vec3& v1, const osg::Vec3& v2)
95     {
96         // Trick to get the ends in the right order.
97         // Use the x axis in the original coordinate system. Choose the
98         // most negative x-axis as the one pointing forward
99         SGVec3f tv1(_matrix.preMult(v1));
100         SGVec3f tv2(_matrix.preMult(v2));
101         if (tv1[0] > tv2[0])
102             _lineSegments.push_back(SGLineSegmentf(tv1, tv2));
103         else
104             _lineSegments.push_back(SGLineSegmentf(tv2, tv1));
105     }
106
107     void addBVHElements(osg::Node& node, simgear::BVHLineGeometry::Type type)
108     {
109         if (_lineSegments.empty())
110             return;
111
112         SGSceneUserData* userData;
113         userData = SGSceneUserData::getOrCreateSceneUserData(&node);
114
115         simgear::BVHNode* bvNode = userData->getBVHNode();
116         if (!bvNode && _lineSegments.size() == 1) {
117             simgear::BVHLineGeometry* bvLine;
118             bvLine = new simgear::BVHLineGeometry(_lineSegments.front(), type);
119             userData->setBVHNode(bvLine);
120             return;
121         }
122
123         simgear::BVHGroup* group = new simgear::BVHGroup;
124         if (bvNode)
125             group->addChild(bvNode);
126
127         for (unsigned i = 0; i < _lineSegments.size(); ++i) {
128             simgear::BVHLineGeometry* bvLine;
129             bvLine = new simgear::BVHLineGeometry(_lineSegments[i], type);
130             group->addChild(bvLine);
131         }
132         userData->setBVHNode(group);
133     }
134     
135 private:
136     osg::Matrix _matrix;
137     std::vector<SGLineSegmentf> _lineSegments;
138 };
139
140 class FGCarrierVisitor : public osg::NodeVisitor {
141 public:
142     FGCarrierVisitor(FGAICarrier* carrier,
143                      const std::list<std::string>& wireObjects,
144                      const std::list<std::string>& catapultObjects) :
145         osg::NodeVisitor(osg::NodeVisitor::NODE_VISITOR,
146                          osg::NodeVisitor::TRAVERSE_ALL_CHILDREN),
147         mWireObjects(wireObjects),
148         mCatapultObjects(catapultObjects)
149     { }
150     virtual void apply(osg::Node& node)
151     {
152         if (std::find(mWireObjects.begin(), mWireObjects.end(), node.getName())
153             != mWireObjects.end()) {
154             LineCollector lineCollector;
155             node.accept(lineCollector);
156             simgear::BVHLineGeometry::Type type;
157             type = simgear::BVHLineGeometry::CarrierWire;
158             lineCollector.addBVHElements(node, type);
159         }
160         if (std::find(mCatapultObjects.begin(), mCatapultObjects.end(),
161                       node.getName()) != mCatapultObjects.end()) {
162             LineCollector lineCollector;
163             node.accept(lineCollector);
164             simgear::BVHLineGeometry::Type type;
165             type = simgear::BVHLineGeometry::CarrierCatapult;
166             lineCollector.addBVHElements(node, type);
167         }
168         
169         traverse(node);
170     }
171     
172 private:
173     std::list<std::string> mWireObjects;
174     std::list<std::string> mCatapultObjects;
175 };
176
177 FGAICarrier::FGAICarrier() : FGAIShip(otCarrier) {
178 }
179
180 FGAICarrier::~FGAICarrier() {
181 }
182
183 void FGAICarrier::readFromScenario(SGPropertyNode* scFileNode) {
184   if (!scFileNode)
185     return;
186
187   FGAIShip::readFromScenario(scFileNode);
188
189   setRadius(scFileNode->getDoubleValue("turn-radius-ft", 2000));
190   setSign(scFileNode->getStringValue("pennant-number"));
191   setWind_from_east(scFileNode->getDoubleValue("wind_from_east", 0));
192   setWind_from_north(scFileNode->getDoubleValue("wind_from_north", 0));
193   setTACANChannelID(scFileNode->getStringValue("TACAN-channel-ID", "029Y"));
194   setMaxLat(scFileNode->getDoubleValue("max-lat", 0));
195   setMinLat(scFileNode->getDoubleValue("min-lat", 0));
196   setMaxLong(scFileNode->getDoubleValue("max-long", 0));
197   setMinLong(scFileNode->getDoubleValue("min-long", 0));
198
199   SGPropertyNode* flols = scFileNode->getChild("flols-pos");
200   if (flols) {
201     // Transform to the right coordinate frame, configuration is done in
202     // the usual x-back, y-right, z-up coordinates, computations
203     // in the simulation usual body x-forward, y-right, z-down coordinates
204     flols_off(0) = - flols->getDoubleValue("x-offset-m", 0);
205     flols_off(1) = flols->getDoubleValue("y-offset-m", 0);
206     flols_off(2) = - flols->getDoubleValue("z-offset-m", 0);
207   } else
208     flols_off = SGVec3d::zeros();
209
210   std::vector<SGPropertyNode_ptr> props = scFileNode->getChildren("wire");
211   std::vector<SGPropertyNode_ptr>::const_iterator it;
212   for (it = props.begin(); it != props.end(); ++it) {
213     std::string s = (*it)->getStringValue();
214     if (!s.empty())
215       wire_objects.push_back(s);
216   }
217
218   props = scFileNode->getChildren("catapult");
219   for (it = props.begin(); it != props.end(); ++it) {
220     std::string s = (*it)->getStringValue();
221     if (!s.empty())
222       catapult_objects.push_back(s);
223   }
224
225   props = scFileNode->getChildren("parking-pos");
226   for (it = props.begin(); it != props.end(); ++it) {
227     string name = (*it)->getStringValue("name", "unnamed");
228     // Transform to the right coordinate frame, configuration is done in
229     // the usual x-back, y-right, z-up coordinates, computations
230     // in the simulation usual body x-forward, y-right, z-down coordinates
231     double offset_x = -(*it)->getDoubleValue("x-offset-m", 0);
232     double offset_y = (*it)->getDoubleValue("y-offset-m", 0);
233     double offset_z = -(*it)->getDoubleValue("z-offset-m", 0);
234     double hd = (*it)->getDoubleValue("heading-offset-deg", 0);
235     ParkPosition pp(name, SGVec3d(offset_x, offset_y, offset_z), hd);
236     ppositions.push_back(pp);
237   }
238 }
239
240 void FGAICarrier::setWind_from_east(double fps) {
241     wind_from_east = fps;
242 }
243
244 void FGAICarrier::setWind_from_north(double fps) {
245     wind_from_north = fps;
246 }
247
248 void FGAICarrier::setMaxLat(double deg) {
249     max_lat = fabs(deg);
250 }
251
252 void FGAICarrier::setMinLat(double deg) {
253     min_lat = fabs(deg);
254 }
255
256 void FGAICarrier::setMaxLong(double deg) {
257     max_long = fabs(deg);
258 }
259
260 void FGAICarrier::setMinLong(double deg) {
261     min_long = fabs(deg);
262 }
263
264 void FGAICarrier::setSign(const string& s) {
265     sign = s;
266 }
267
268 void FGAICarrier::setTACANChannelID(const string& id) {
269     TACAN_channel_id = id;
270 }
271
272 void FGAICarrier::update(double dt) {
273     // For computation of rotation speeds we just use finite differences here.
274     // That is perfectly valid since this thing is not driven by accelerations
275     // but by just apply discrete changes at its velocity variables.
276     // Update the velocity information stored in those nodes.
277     // Transform that one to the horizontal local coordinate system.
278     SGQuatd ec2hl = SGQuatd::fromLonLat(pos);
279     // The orientation of the carrier wrt the horizontal local frame
280     SGQuatd hl2body = SGQuatd::fromYawPitchRollDeg(hdg, pitch, roll);
281     // and postrotate the orientation of the AIModel wrt the horizontal
282     // local frame
283     SGQuatd ec2body = ec2hl*hl2body;
284     // The cartesian position of the carrier in the wgs84 world
285     SGVec3d cartPos = SGVec3d::fromGeod(pos);
286
287     // Compute the velocity in m/s in the body frame
288     aip.setBodyLinearVelocity(SGVec3d(0.51444444*speed, 0, 0));
289
290     // Now update the position and heading. This will compute new hdg and
291     // roll values required for the rotation speed computation.
292     FGAIShip::update(dt);
293
294
295     //automatic turn into wind with a target wind of 25 kts otd
296     if(turn_to_launch_hdg){
297         TurnToLaunch();
298     } else if(OutsideBox() || returning) {// check that the carrier is inside the operating box
299         ReturnToBox();
300     } else {
301         TurnToBase();
302     }
303
304     // Only change these values if we are able to compute them safely
305     if (SGLimits<double>::min() < dt) {
306       // Now here is the finite difference ...
307
308       // Transform that one to the horizontal local coordinate system.
309       SGQuatd ec2hlNew = SGQuatd::fromLonLat(pos);
310       // compute the new orientation
311       SGQuatd hl2bodyNew = SGQuatd::fromYawPitchRollDeg(hdg, pitch, roll);
312       // The rotation difference
313       SGQuatd dOr = inverse(ec2body)*ec2hlNew*hl2bodyNew;
314       SGVec3d dOrAngleAxis;
315       dOr.getAngleAxis(dOrAngleAxis);
316       // divided by the time difference provides a rotation speed vector
317       dOrAngleAxis /= dt;
318
319       aip.setBodyAngularVelocity(dOrAngleAxis);
320     }
321
322     UpdateWind(dt);
323     UpdateElevator(dt, transition_time);
324     UpdateJBD(dt, jbd_transition_time);
325     // For the flols reuse some computations done above ...
326
327     // The position of the eyepoint - at least near that ...
328     SGVec3d eyePos(globals->get_current_view()->get_view_pos());
329     // Add the position offset of the AIModel to gain the earth
330     // centered position
331     SGVec3d eyeWrtCarrier = eyePos - cartPos;
332     // rotate the eyepoint wrt carrier vector into the carriers frame
333     eyeWrtCarrier = ec2body.transform(eyeWrtCarrier);
334     // the eyepoints vector wrt the flols position
335     SGVec3d eyeWrtFlols = eyeWrtCarrier - flols_off;
336     
337     // the distance from the eyepoint to the flols
338     dist = norm(eyeWrtFlols);
339     
340     // now the angle, positive angles are upwards
341     if (fabs(dist) < SGLimits<float>::min()) {
342       angle = 0;
343     } else {
344       double sAngle = -eyeWrtFlols(2)/dist;
345       sAngle = SGMiscd::min(1, SGMiscd::max(-1, sAngle));
346       angle = SGMiscd::rad2deg(asin(sAngle));
347     }
348     
349     // set the value of source
350     if ( angle <= 4.35 && angle > 4.01 )
351       source = 1;
352     else if ( angle <= 4.01 && angle > 3.670 )
353       source = 2;
354     else if ( angle <= 3.670 && angle > 3.330 )
355       source = 3;
356     else if ( angle <= 3.330 && angle > 2.990 )
357       source = 4;
358     else if ( angle <= 2.990 && angle > 2.650 )
359       source = 5;
360     else if ( angle <= 2.650 )
361       source = 6;
362     else
363       source = 0;
364 } //end update
365
366 bool FGAICarrier::init(bool search_in_AI_path) {
367     if (!FGAIShip::init(search_in_AI_path))
368         return false;
369
370     _longitude_node = fgGetNode("/position/longitude-deg", true);
371     _latitude_node = fgGetNode("/position/latitude-deg", true);
372     _altitude_node = fgGetNode("/position/altitude-ft", true);
373
374     _launchbar_state_node = fgGetNode("/gear/launchbar/state", true);
375
376     _surface_wind_from_deg_node =
377             fgGetNode("/environment/config/boundary/entry[0]/wind-from-heading-deg", true);
378     _surface_wind_speed_node =
379             fgGetNode("/environment/config/boundary/entry[0]/wind-speed-kt", true);
380
381
382     turn_to_launch_hdg = false;
383     returning = false;
384
385     mOpBoxPos = pos;
386     base_course = hdg;
387     base_speed = speed;
388
389     pos_norm = 0;
390     elevators = false;
391     transition_time = 150;
392     time_constant = 0.005;
393     jbd_pos_norm = raw_jbd_pos_norm = 0;
394     jbd = false ;
395     jbd_transition_time = 3;
396     jbd_time_constant = 0.1;
397     return true;
398 }
399
400 void FGAICarrier::initModel(osg::Node *node)
401 {
402     // SG_LOG(SG_GENERAL, SG_BULK, "AICarrier::initModel()" );
403     FGAIShip::initModel(node);
404     // process the 3d model here
405     // mark some objects solid, mark the wires ...
406     FGCarrierVisitor carrierVisitor(this, wire_objects, catapult_objects);
407     model->accept(carrierVisitor);
408     model->setNodeMask(model->getNodeMask() | SG_NODEMASK_TERRAIN_BIT);
409 }
410
411 void FGAICarrier::bind() {
412     FGAIShip::bind();
413
414     props->untie("velocities/true-airspeed-kt");
415
416     props->tie("controls/flols/source-lights",
417                 SGRawValuePointer<int>(&source));
418     props->tie("controls/flols/distance-m",
419                 SGRawValuePointer<double>(&dist));
420     props->tie("controls/flols/angle-degs",
421                 SGRawValuePointer<double>(&angle));
422     props->tie("controls/turn-to-launch-hdg",
423                 SGRawValuePointer<bool>(&turn_to_launch_hdg));
424     props->tie("controls/in-to-wind",
425                 SGRawValuePointer<bool>(&turn_to_launch_hdg));
426     props->tie("controls/base-course-deg",
427                 SGRawValuePointer<double>(&base_course));
428     props->tie("controls/base-speed-kts",
429                 SGRawValuePointer<double>(&base_speed));
430     props->tie("controls/start-pos-lat-deg",
431                SGRawValueMethods<SGGeod,double>(pos, &SGGeod::getLatitudeDeg));
432     props->tie("controls/start-pos-long-deg",
433                SGRawValueMethods<SGGeod,double>(pos, &SGGeod::getLongitudeDeg));
434     props->tie("velocities/speed-kts",
435                 SGRawValuePointer<double>(&speed));
436     props->tie("environment/surface-wind-speed-true-kts",
437                 SGRawValuePointer<double>(&wind_speed_kts));
438     props->tie("environment/surface-wind-from-true-degs",
439                 SGRawValuePointer<double>(&wind_from_deg));
440     props->tie("environment/rel-wind-from-degs",
441                 SGRawValuePointer<double>(&rel_wind_from_deg));
442     props->tie("environment/rel-wind-from-carrier-hdg-degs",
443                 SGRawValuePointer<double>(&rel_wind));
444     props->tie("environment/rel-wind-speed-kts",
445                 SGRawValuePointer<double>(&rel_wind_speed_kts));
446     props->tie("controls/flols/wave-off-lights",
447                 SGRawValuePointer<bool>(&wave_off_lights));
448     props->tie("controls/elevators",
449                 SGRawValuePointer<bool>(&elevators));
450     props->tie("surface-positions/elevators-pos-norm",
451                 SGRawValuePointer<double>(&pos_norm));
452     props->tie("controls/elevators-trans-time-s",
453                 SGRawValuePointer<double>(&transition_time));
454     props->tie("controls/elevators-time-constant",
455                 SGRawValuePointer<double>(&time_constant));
456     props->tie("controls/jbd",
457         SGRawValuePointer<bool>(&jbd));
458     props->tie("surface-positions/jbd-pos-norm",
459         SGRawValuePointer<double>(&jbd_pos_norm));
460     props->tie("controls/jbd-trans-time-s",
461         SGRawValuePointer<double>(&jbd_transition_time));
462     props->tie("controls/jbd-time-constant",
463         SGRawValuePointer<double>(&jbd_time_constant));
464
465     props->setBoolValue("controls/flols/cut-lights", false);
466     props->setBoolValue("controls/flols/wave-off-lights", false);
467     props->setBoolValue("controls/flols/cond-datum-lights", true);
468     props->setBoolValue("controls/crew", false);
469     props->setStringValue("navaids/tacan/channel-ID", TACAN_channel_id.c_str());
470     props->setStringValue("sign", sign.c_str());
471     props->setBoolValue("controls/lighting/deck-lights", false);
472     props->setDoubleValue("controls/lighting/flood-lights-red-norm", 0);
473 }
474
475
476 void FGAICarrier::unbind() {
477     FGAIShip::unbind();
478
479     props->untie("velocities/true-airspeed-kt");
480     props->untie("controls/flols/source-lights");
481     props->untie("controls/flols/distance-m");
482     props->untie("controls/flols/angle-degs");
483     props->untie("controls/turn-to-launch-hdg");
484     props->untie("velocities/speed-kts");
485     props->untie("environment/wind-speed-true-kts");
486     props->untie("environment/wind-from-true-degs");
487     props->untie("environment/rel-wind-from-degs");
488     props->untie("environment/rel-wind-speed-kts");
489     props->untie("controls/flols/wave-off-lights");
490     props->untie("controls/elevators");
491     props->untie("surface-positions/elevators-pos-norm");
492     props->untie("controls/elevators-trans-time-secs");
493     props->untie("controls/elevators-time-constant");
494     props->untie("controls/jbd");
495     props->untie("surface-positions/jbd-pos-norm");
496     props->untie("controls/jbd-trans-time-s");
497     props->untie("controls/jbd-time-constant");
498
499 }
500
501
502 bool FGAICarrier::getParkPosition(const string& id, SGGeod& geodPos,
503                                   double& hdng, SGVec3d& uvw)
504 {
505
506     // FIXME: does not yet cover rotation speeds.
507     list<ParkPosition>::iterator it = ppositions.begin();
508     while (it != ppositions.end()) {
509         // Take either the specified one or the first one ...
510         if ((*it).name == id || id.empty()) {
511             ParkPosition ppos = *it;
512             SGVec3d cartPos = getCartPosAt(ppos.offset);
513             geodPos = SGGeod::fromCart(cartPos);
514             hdng = hdg + ppos.heading_deg;
515             double shdng = sin(ppos.heading_deg * SGD_DEGREES_TO_RADIANS);
516             double chdng = cos(ppos.heading_deg * SGD_DEGREES_TO_RADIANS);
517             double speed_fps = speed*1.6878099;
518             uvw = SGVec3d(chdng*speed_fps, shdng*speed_fps, 0);
519             return true;
520         }
521         ++it;
522     }
523
524     return false;
525 }
526
527 // find relative wind
528 void FGAICarrier::UpdateWind( double dt) {
529
530     //get the surface wind speed and direction
531     wind_from_deg = _surface_wind_from_deg_node->getDoubleValue();
532     wind_speed_kts  = _surface_wind_speed_node->getDoubleValue();
533
534     //calculate the surface wind speed north and east in kts
535     double wind_speed_from_north_kts = cos( wind_from_deg / SGD_RADIANS_TO_DEGREES )* wind_speed_kts ;
536     double wind_speed_from_east_kts  = sin( wind_from_deg / SGD_RADIANS_TO_DEGREES )* wind_speed_kts ;
537
538     //calculate the carrier speed north and east in kts
539     double speed_north_kts = cos( hdg / SGD_RADIANS_TO_DEGREES )* speed ;
540     double speed_east_kts  = sin( hdg / SGD_RADIANS_TO_DEGREES )* speed ;
541
542     //calculate the relative wind speed north and east in kts
543     double rel_wind_speed_from_east_kts = wind_speed_from_east_kts + speed_east_kts;
544     double rel_wind_speed_from_north_kts = wind_speed_from_north_kts + speed_north_kts;
545
546     //combine relative speeds north and east to get relative windspeed in kts
547     rel_wind_speed_kts = sqrt((rel_wind_speed_from_east_kts * rel_wind_speed_from_east_kts)
548     + (rel_wind_speed_from_north_kts * rel_wind_speed_from_north_kts));
549
550     //calculate the relative wind direction
551     rel_wind_from_deg = atan2(rel_wind_speed_from_east_kts, rel_wind_speed_from_north_kts)
552                             * SG_RADIANS_TO_DEGREES;
553
554     //calculate rel wind
555     rel_wind = rel_wind_from_deg - hdg;
556     SG_NORMALIZE_RANGE(rel_wind, -180.0, 180.0);
557
558     //switch the wave-off lights
559     if (InToWind())
560        wave_off_lights = false;
561     else
562        wave_off_lights = true;
563
564     // cout << "rel wind: " << rel_wind << endl;
565
566 }// end update wind
567
568
569 void FGAICarrier::TurnToLaunch(){
570
571     //calculate tgt speed
572     double tgt_speed = 25 - wind_speed_kts;
573     if (tgt_speed < 10)
574         tgt_speed = 10;
575
576     //turn the carrier
577     FGAIShip::TurnTo(wind_from_deg);
578     FGAIShip::AccelTo(tgt_speed);
579
580 }
581
582
583 void FGAICarrier::TurnToBase(){
584
585     //turn the carrier
586     FGAIShip::TurnTo(base_course);
587     FGAIShip::AccelTo(base_speed);
588
589 }
590
591
592 void FGAICarrier::ReturnToBox(){
593     double course, distance, az2;
594
595     //calculate the bearing and range of the initial position from the carrier
596     geo_inverse_wgs_84(pos, mOpBoxPos, &course, &az2, &distance);
597
598     distance *= SG_METER_TO_NM;
599
600     //cout << "return course: " << course << " distance: " << distance << endl;
601     //turn the carrier
602     FGAIShip::TurnTo(course);
603     FGAIShip::AccelTo(base_speed);
604
605     if (distance >= 1)
606         returning = true;
607     else
608         returning = false;
609
610 } //  end turn to base
611
612
613 bool FGAICarrier::OutsideBox() { //returns true if the carrier is outside operating box
614
615     if ( max_lat == 0 && min_lat == 0 && max_long == 0 && min_long == 0) {
616         SG_LOG(SG_GENERAL, SG_DEBUG, "AICarrier: No Operating Box defined" );
617         return false;
618     }
619
620     if (mOpBoxPos.getLatitudeDeg() >= 0) { //northern hemisphere
621         if (pos.getLatitudeDeg() >= mOpBoxPos.getLatitudeDeg() + max_lat)
622             return true;
623
624         if (pos.getLatitudeDeg() <= mOpBoxPos.getLatitudeDeg() - min_lat)
625             return true;
626
627     } else {                  //southern hemisphere
628         if (pos.getLatitudeDeg() <= mOpBoxPos.getLatitudeDeg() - max_lat)
629             return true;
630
631         if (pos.getLatitudeDeg() >= mOpBoxPos.getLatitudeDeg() + min_lat)
632             return true;
633     }
634
635     if (mOpBoxPos.getLongitudeDeg() >=0) { //eastern hemisphere
636         if (pos.getLongitudeDeg() >= mOpBoxPos.getLongitudeDeg() + max_long)
637             return true;
638
639         if (pos.getLongitudeDeg() <= mOpBoxPos.getLongitudeDeg() - min_long)
640             return true;
641
642     } else {                 //western hemisphere
643         if (pos.getLongitudeDeg() <= mOpBoxPos.getLongitudeDeg() - max_long)
644             return true;
645
646         if (pos.getLongitudeDeg() >= mOpBoxPos.getLongitudeDeg() + min_long)
647             return true;
648     }
649
650     SG_LOG(SG_GENERAL, SG_DEBUG, "AICarrier: Inside Operating Box" );
651     return false;
652
653 } // end OutsideBox
654
655
656 bool FGAICarrier::InToWind() {
657     if ( fabs(rel_wind) < 5 )
658         return true;
659
660     return false;
661 }
662
663
664 void FGAICarrier::UpdateElevator(double dt, double transition_time) {
665
666     double step = 0;
667
668     if ((elevators && pos_norm >= 1 ) || (!elevators && pos_norm <= 0 ))
669         return;
670
671     // move the elevators
672     if ( elevators ) {
673         step = dt/transition_time;
674         if ( step > 1 )
675             step = 1;
676     } else {
677         step = -dt/transition_time;
678         if ( step < -1 )
679             step = -1;
680     }
681     // assume a linear relationship
682     raw_pos_norm += step;
683
684     //low pass filter
685     pos_norm = (raw_pos_norm * time_constant) + (pos_norm * (1 - time_constant));
686
687     //sanitise the output
688     if (raw_pos_norm >= 1) {
689         raw_pos_norm = 1;
690     } else if (raw_pos_norm <= 0) {
691         raw_pos_norm = 0;
692     }
693     return;
694
695 } // end UpdateElevator
696
697 void FGAICarrier::UpdateJBD(double dt, double jbd_transition_time) {
698
699     string launchbar_state = _launchbar_state_node->getStringValue();
700     double step = 0;
701
702     if (launchbar_state == "Engaged"){
703         jbd = true;
704     } else {
705         jbd = false;
706     }
707
708     if (( jbd && jbd_pos_norm >= 1 ) || ( !jbd && jbd_pos_norm <= 0 )){
709         return;
710     }
711
712     // move the jbds
713     if ( jbd ) {
714         step = dt/jbd_transition_time;
715         if ( step > 1 )
716             step = 1;
717     } else {
718         step = -dt/jbd_transition_time;
719         if ( step < -1 )
720             step = -1;
721     }
722
723     // assume a linear relationship
724     raw_jbd_pos_norm += step;
725
726     //low pass filter
727     jbd_pos_norm = (raw_jbd_pos_norm * jbd_time_constant) + (jbd_pos_norm * (1 - jbd_time_constant));
728
729     //sanitise the output
730     if (jbd_pos_norm >= 1) {
731         jbd_pos_norm = 1;
732     } else if (jbd_pos_norm <= 0) {
733         jbd_pos_norm = 0;
734     }
735
736     return;
737
738 } // end UpdateJBD