]> git.mxchange.org Git - flightgear.git/blob - src/AIModel/AICarrier.cxx
4e98e29acc60e299b3119514ffb39bd909a8abfc
[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/SGSceneUserData.hxx>
38 #include <simgear/scene/bvh/BVHGroup.hxx>
39 #include <simgear/scene/bvh/BVHLineGeometry.hxx>
40
41 #include <math.h>
42 #include <Main/util.hxx>
43 #include <Main/viewer.hxx>
44
45 #include "AICarrier.hxx"
46
47 /// Hmm: move that kind of configuration into the model file???
48 class LineCollector : public osg::NodeVisitor {
49     struct LinePrimitiveFunctor {
50         LinePrimitiveFunctor() : _lineCollector(0)
51         { }
52         void operator() (const osg::Vec3&, bool)
53         { }
54         void operator() (const osg::Vec3& v1, const osg::Vec3& v2, bool)
55         { if (_lineCollector) _lineCollector->addLine(v1, v2); }
56         void operator() (const osg::Vec3&, const osg::Vec3&, const osg::Vec3&,
57                          bool)
58         { }
59         void operator() (const osg::Vec3&, const osg::Vec3&, const osg::Vec3&,
60                          const osg::Vec3&, bool)
61         { }
62         LineCollector* _lineCollector;
63     };
64     
65 public:
66     LineCollector() :
67         osg::NodeVisitor(osg::NodeVisitor::NODE_VISITOR,
68                          osg::NodeVisitor::TRAVERSE_ALL_CHILDREN)
69     { }
70     virtual void apply(osg::Geode& geode)
71     {
72         osg::TemplatePrimitiveFunctor<LinePrimitiveFunctor> pf;
73         pf._lineCollector = this;
74         for (unsigned i = 0; i < geode.getNumDrawables(); ++i) {
75             geode.getDrawable(i)->accept(pf);
76         }
77     }
78     virtual void apply(osg::Node& node)
79     {
80         traverse(node);
81     }
82     virtual void apply(osg::Transform& transform)
83     {
84         osg::Matrix matrix = _matrix;
85         if (transform.computeLocalToWorldMatrix(_matrix, this))
86             traverse(transform);
87         _matrix = matrix;
88     }
89     
90     const std::vector<SGLineSegmentf>& getLineSegments() const
91     { return _lineSegments; }
92     
93     void addLine(const osg::Vec3& v1, const osg::Vec3& v2)
94     {
95         // Trick to get the ends in the right order.
96         // Use the x axis in the original coordinate system. Choose the
97         // most negative x-axis as the one pointing forward
98         SGVec3f tv1(_matrix.preMult(v1));
99         SGVec3f tv2(_matrix.preMult(v2));
100         if (tv1[0] > tv2[0])
101             _lineSegments.push_back(SGLineSegmentf(tv1, tv2));
102         else
103             _lineSegments.push_back(SGLineSegmentf(tv2, tv1));
104     }
105
106     void addBVHElements(osg::Node& node, simgear::BVHLineGeometry::Type type)
107     {
108         if (_lineSegments.empty())
109             return;
110
111         SGSceneUserData* userData;
112         userData = SGSceneUserData::getOrCreateSceneUserData(&node);
113
114         simgear::BVHNode* bvNode = userData->getBVHNode();
115         if (!bvNode && _lineSegments.size() == 1) {
116             simgear::BVHLineGeometry* bvLine;
117             bvLine = new simgear::BVHLineGeometry(_lineSegments.front(), type);
118             userData->setBVHNode(bvLine);
119             return;
120         }
121
122         simgear::BVHGroup* group = new simgear::BVHGroup;
123         if (bvNode)
124             group->addChild(bvNode);
125
126         for (unsigned i = 0; i < _lineSegments.size(); ++i) {
127             simgear::BVHLineGeometry* bvLine;
128             bvLine = new simgear::BVHLineGeometry(_lineSegments[i], type);
129             group->addChild(bvLine);
130         }
131         userData->setBVHNode(group);
132     }
133     
134 private:
135     osg::Matrix _matrix;
136     std::vector<SGLineSegmentf> _lineSegments;
137 };
138
139 class FGCarrierVisitor : public osg::NodeVisitor {
140 public:
141     FGCarrierVisitor(FGAICarrier* carrier,
142                      const std::list<std::string>& wireObjects,
143                      const std::list<std::string>& catapultObjects) :
144         osg::NodeVisitor(osg::NodeVisitor::NODE_VISITOR,
145                          osg::NodeVisitor::TRAVERSE_ALL_CHILDREN),
146         mWireObjects(wireObjects),
147         mCatapultObjects(catapultObjects)
148     { }
149     virtual void apply(osg::Node& node)
150     {
151         if (std::find(mWireObjects.begin(), mWireObjects.end(), node.getName())
152             != mWireObjects.end()) {
153             LineCollector lineCollector;
154             node.accept(lineCollector);
155             simgear::BVHLineGeometry::Type type;
156             type = simgear::BVHLineGeometry::CarrierWire;
157             lineCollector.addBVHElements(node, type);
158         }
159         if (std::find(mCatapultObjects.begin(), mCatapultObjects.end(),
160                       node.getName()) != mCatapultObjects.end()) {
161             LineCollector lineCollector;
162             node.accept(lineCollector);
163             simgear::BVHLineGeometry::Type type;
164             type = simgear::BVHLineGeometry::CarrierCatapult;
165             lineCollector.addBVHElements(node, type);
166         }
167         
168         traverse(node);
169     }
170     
171 private:
172     std::list<std::string> mWireObjects;
173     std::list<std::string> mCatapultObjects;
174 };
175
176 FGAICarrier::FGAICarrier() : FGAIShip(otCarrier) {
177 }
178
179 FGAICarrier::~FGAICarrier() {
180 }
181
182 void FGAICarrier::readFromScenario(SGPropertyNode* scFileNode) {
183   if (!scFileNode)
184     return;
185
186   FGAIShip::readFromScenario(scFileNode);
187
188   setRadius(scFileNode->getDoubleValue("turn-radius-ft", 2000));
189   setSign(scFileNode->getStringValue("pennant-number"));
190   setWind_from_east(scFileNode->getDoubleValue("wind_from_east", 0));
191   setWind_from_north(scFileNode->getDoubleValue("wind_from_north", 0));
192   setTACANChannelID(scFileNode->getStringValue("TACAN-channel-ID", "029Y"));
193   setMaxLat(scFileNode->getDoubleValue("max-lat", 0));
194   setMinLat(scFileNode->getDoubleValue("min-lat", 0));
195   setMaxLong(scFileNode->getDoubleValue("max-long", 0));
196   setMinLong(scFileNode->getDoubleValue("min-long", 0));
197   setMPControl(scFileNode->getBoolValue("mp-control", false));
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::setMPControl(bool c) {
273     MPControl = c;
274 }
275
276 void FGAICarrier::update(double dt) {
277     // Now update the position and heading. This will compute new hdg and
278     // roll values required for the rotation speed computation.
279     FGAIShip::update(dt);
280
281     //automatic turn into wind with a target wind of 25 kts otd
282     //SG_LOG(SG_GENERAL, SG_ALERT, "AICarrier: MPControl " << MPControl );
283     if (!MPControl){
284
285         if(turn_to_launch_hdg){
286             TurnToLaunch();
287         } else if(turn_to_recovery_hdg ){
288             TurnToRecover();
289         } else if(OutsideBox() || returning ) {// check that the carrier is inside 
290             ReturnToBox();                     // the operating box,  
291         } else {
292             TurnToBase();
293         }
294
295     } else {
296         FGAIShip::TurnTo(tgt_heading);
297         FGAIShip::AccelTo(tgt_speed);
298     }
299
300     UpdateWind(dt);
301     UpdateElevator(dt, transition_time);
302     UpdateJBD(dt, jbd_transition_time);
303
304     // Transform that one to the horizontal local coordinate system.
305     SGQuatd ec2hl = SGQuatd::fromLonLat(pos);
306     // The orientation of the carrier wrt the horizontal local frame
307     SGQuatd hl2body = SGQuatd::fromYawPitchRollDeg(hdg, pitch, roll);
308     // and postrotate the orientation of the AIModel wrt the horizontal
309     // local frame
310     SGQuatd ec2body = ec2hl*hl2body;
311     // The cartesian position of the carrier in the wgs84 world
312     SGVec3d cartPos = SGVec3d::fromGeod(pos);
313
314     // The position of the eyepoint - at least near that ...
315     SGVec3d eyePos(globals->get_current_view()->get_view_pos());
316     // Add the position offset of the AIModel to gain the earth
317     // centered position
318     SGVec3d eyeWrtCarrier = eyePos - cartPos;
319     // rotate the eyepoint wrt carrier vector into the carriers frame
320     eyeWrtCarrier = ec2body.transform(eyeWrtCarrier);
321     // the eyepoints vector wrt the flols position
322     SGVec3d eyeWrtFlols = eyeWrtCarrier - flols_off;
323     
324     // the distance from the eyepoint to the flols
325     dist = norm(eyeWrtFlols);
326     
327     // now the angle, positive angles are upwards
328     if (fabs(dist) < SGLimits<float>::min()) {
329       angle = 0;
330     } else {
331       double sAngle = -eyeWrtFlols(2)/dist;
332       sAngle = SGMiscd::min(1, SGMiscd::max(-1, sAngle));
333       angle = SGMiscd::rad2deg(asin(sAngle));
334     }
335     
336     // set the value of source
337     if ( angle <= 4.35 && angle > 4.01 )
338       source = 1;
339     else if ( angle <= 4.01 && angle > 3.670 )
340       source = 2;
341     else if ( angle <= 3.670 && angle > 3.330 )
342       source = 3;
343     else if ( angle <= 3.330 && angle > 2.990 )
344       source = 4;
345     else if ( angle <= 2.990 && angle > 2.650 )
346       source = 5;
347     else if ( angle <= 2.650 )
348       source = 6;
349     else
350       source = 0;
351 } //end update
352
353 bool FGAICarrier::init(bool search_in_AI_path) {
354     if (!FGAIShip::init(search_in_AI_path))
355         return false;
356
357     _longitude_node = fgGetNode("/position/longitude-deg", true);
358     _latitude_node = fgGetNode("/position/latitude-deg", true);
359     _altitude_node = fgGetNode("/position/altitude-ft", true);
360
361     _launchbar_state_node = fgGetNode("/gear/launchbar/state", true);
362
363     _surface_wind_from_deg_node =
364             fgGetNode("/environment/config/boundary/entry[0]/wind-from-heading-deg", true);
365     _surface_wind_speed_node =
366             fgGetNode("/environment/config/boundary/entry[0]/wind-speed-kt", true);
367
368
369     turn_to_launch_hdg = false;
370     turn_to_recovery_hdg = false;
371     turn_to_base_course = true;
372     returning = false;
373     in_to_wind = false;
374
375     mOpBoxPos = pos;
376     base_course = hdg;
377     base_speed = speed;
378
379     pos_norm = raw_pos_norm = 0;
380     elevators = false;
381     transition_time = 150;
382     time_constant = 0.005;
383     jbd_pos_norm = raw_jbd_pos_norm = 0;
384     jbd = false ;
385     jbd_transition_time = 3;
386     jbd_time_constant = 0.1;
387     return true;
388 }
389
390 void FGAICarrier::initModel(osg::Node *node)
391 {
392     // SG_LOG(SG_GENERAL, SG_BULK, "AICarrier::initModel()" );
393     FGAIShip::initModel(node);
394     // process the 3d model here
395     // mark some objects solid, mark the wires ...
396     FGCarrierVisitor carrierVisitor(this, wire_objects, catapult_objects);
397     node->accept(carrierVisitor);
398 }
399
400 void FGAICarrier::bind() {
401     FGAIShip::bind();
402
403     props->untie("velocities/true-airspeed-kt");
404
405     props->tie("controls/flols/source-lights",
406                 SGRawValuePointer<int>(&source));
407     props->tie("controls/flols/distance-m",
408                 SGRawValuePointer<double>(&dist));
409     props->tie("controls/flols/angle-degs",
410                 SGRawValuePointer<double>(&angle));
411     props->tie("controls/turn-to-launch-hdg",
412                 SGRawValuePointer<bool>(&turn_to_launch_hdg));
413     props->tie("controls/in-to-wind",
414                 SGRawValuePointer<bool>(&turn_to_launch_hdg));
415     props->tie("controls/base-course-deg",
416                 SGRawValuePointer<double>(&base_course));
417     props->tie("controls/base-speed-kts",
418                 SGRawValuePointer<double>(&base_speed));
419     props->tie("controls/start-pos-lat-deg",
420                SGRawValueMethods<SGGeod,double>(pos, &SGGeod::getLatitudeDeg));
421     props->tie("controls/start-pos-long-deg",
422                SGRawValueMethods<SGGeod,double>(pos, &SGGeod::getLongitudeDeg));
423     props->tie("controls/mp-control",
424         SGRawValuePointer<bool>(&MPControl));
425     props->tie("velocities/speed-kts",
426                 SGRawValuePointer<double>(&speed));
427     props->tie("environment/surface-wind-speed-true-kts",
428                 SGRawValuePointer<double>(&wind_speed_kts));
429     props->tie("environment/surface-wind-from-true-degs",
430                 SGRawValuePointer<double>(&wind_from_deg));
431     props->tie("environment/rel-wind-from-degs",
432                 SGRawValuePointer<double>(&rel_wind_from_deg));
433     props->tie("environment/rel-wind-from-carrier-hdg-degs",
434                 SGRawValuePointer<double>(&rel_wind));
435     props->tie("environment/rel-wind-speed-kts",
436                 SGRawValuePointer<double>(&rel_wind_speed_kts));
437     props->tie("environment/in-to-wind",
438         SGRawValuePointer<bool>(&in_to_wind));
439     props->tie("controls/flols/wave-off-lights",
440                 SGRawValuePointer<bool>(&wave_off_lights));
441     props->tie("controls/elevators",
442                 SGRawValuePointer<bool>(&elevators));
443     props->tie("surface-positions/elevators-pos-norm",
444                 SGRawValuePointer<double>(&pos_norm));
445     props->tie("controls/constants/elevators/trans-time-s",
446                 SGRawValuePointer<double>(&transition_time));
447     props->tie("controls/constants/elevators/time-constant",
448                 SGRawValuePointer<double>(&time_constant));
449     props->tie("controls/jbd",
450         SGRawValuePointer<bool>(&jbd));
451     props->tie("surface-positions/jbd-pos-norm",
452         SGRawValuePointer<double>(&jbd_pos_norm));
453     props->tie("controls/constants/jbd/trans-time-s",
454         SGRawValuePointer<double>(&jbd_transition_time));
455     props->tie("controls/constants/jbd/time-constant",
456         SGRawValuePointer<double>(&jbd_time_constant));
457     props->tie("controls/turn-to-recovery-hdg",
458         SGRawValuePointer<bool>(&turn_to_recovery_hdg));
459     props->tie("controls/turn-to-base-course",
460         SGRawValuePointer<bool>(&turn_to_base_course));
461
462
463     props->setBoolValue("controls/flols/cut-lights", false);
464     props->setBoolValue("controls/flols/wave-off-lights", false);
465     props->setBoolValue("controls/flols/cond-datum-lights", true);
466     props->setBoolValue("controls/crew", false);
467     props->setStringValue("navaids/tacan/channel-ID", TACAN_channel_id.c_str());
468     props->setStringValue("sign", sign.c_str());
469     props->setBoolValue("controls/lighting/deck-lights", false);
470     props->setDoubleValue("controls/lighting/flood-lights-red-norm", 0);
471 }
472
473
474 void FGAICarrier::unbind() {
475     FGAIShip::unbind();
476
477     props->untie("velocities/true-airspeed-kt");
478     props->untie("controls/flols/source-lights");
479     props->untie("controls/flols/distance-m");
480     props->untie("controls/flols/angle-degs");
481     props->untie("controls/turn-to-launch-hdg");
482     props->untie("velocities/speed-kts");
483     props->untie("environment/wind-speed-true-kts");
484     props->untie("environment/wind-from-true-degs");
485     props->untie("environment/rel-wind-from-degs");
486     props->untie("environment/rel-wind-speed-kts");
487     props->untie("environment/in-to-wind");
488     props->untie("controls/flols/wave-off-lights");
489     props->untie("controls/elevators");
490     props->untie("surface-positions/elevators-pos-norm");
491     props->untie("controls/constants/elevators/trans-time-secs");
492     props->untie("controls/constants/elevators/time-constant");
493     props->untie("controls/jbd");
494     props->untie("surface-positions/jbd/pos-norm");
495     props->untie("controls/constants/jbd/trans-time-s");
496     props->untie("controls/jbd-time-constant");
497     props->untie("controls/mp-control");
498     props->untie("controls/turn-to-recovery-hdg");
499     props->untie("controls/turn-to-base-course");
500 }
501
502
503 bool FGAICarrier::getParkPosition(const string& id, SGGeod& geodPos,
504                                   double& hdng, SGVec3d& uvw)
505 {
506
507     // FIXME: does not yet cover rotation speeds.
508     list<ParkPosition>::iterator it = ppositions.begin();
509     while (it != ppositions.end()) {
510         // Take either the specified one or the first one ...
511         if ((*it).name == id || id.empty()) {
512             ParkPosition ppos = *it;
513             SGVec3d cartPos = getCartPosAt(ppos.offset);
514             geodPos = SGGeod::fromCart(cartPos);
515             hdng = hdg + ppos.heading_deg;
516             double shdng = sin(ppos.heading_deg * SGD_DEGREES_TO_RADIANS);
517             double chdng = cos(ppos.heading_deg * SGD_DEGREES_TO_RADIANS);
518             double speed_fps = speed*1.6878099;
519             uvw = SGVec3d(chdng*speed_fps, shdng*speed_fps, 0);
520             return true;
521         }
522         ++it;
523     }
524
525     return false;
526 }
527
528 // find relative wind
529 void FGAICarrier::UpdateWind( double dt) {
530
531     //get the surface wind speed and direction
532     wind_from_deg = _surface_wind_from_deg_node->getDoubleValue();
533     wind_speed_kts  = _surface_wind_speed_node->getDoubleValue();
534
535     //calculate the surface wind speed north and east in kts
536     double wind_speed_from_north_kts = cos( wind_from_deg / SGD_RADIANS_TO_DEGREES )* wind_speed_kts ;
537     double wind_speed_from_east_kts  = sin( wind_from_deg / SGD_RADIANS_TO_DEGREES )* wind_speed_kts ;
538
539     //calculate the carrier speed north and east in kts
540     double speed_north_kts = cos( hdg / SGD_RADIANS_TO_DEGREES )* speed ;
541     double speed_east_kts  = sin( hdg / SGD_RADIANS_TO_DEGREES )* speed ;
542
543     //calculate the relative wind speed north and east in kts
544     double rel_wind_speed_from_east_kts = wind_speed_from_east_kts + speed_east_kts;
545     double rel_wind_speed_from_north_kts = wind_speed_from_north_kts + speed_north_kts;
546
547     //combine relative speeds north and east to get relative windspeed in kts
548     rel_wind_speed_kts = sqrt((rel_wind_speed_from_east_kts * rel_wind_speed_from_east_kts)
549     + (rel_wind_speed_from_north_kts * rel_wind_speed_from_north_kts));
550
551     //calculate the relative wind direction
552     rel_wind_from_deg = atan2(rel_wind_speed_from_east_kts, rel_wind_speed_from_north_kts)
553                             * SG_RADIANS_TO_DEGREES;
554
555     //calculate rel wind
556     rel_wind = rel_wind_from_deg - hdg;
557     SG_NORMALIZE_RANGE(rel_wind, -180.0, 180.0);
558
559     //switch the wave-off lights
560     if (InToWind())
561        wave_off_lights = false;
562     else
563        wave_off_lights = true;
564
565     // cout << "rel wind: " << rel_wind << endl;
566
567 }// end update wind
568
569
570 void FGAICarrier::TurnToLaunch(){
571
572     // calculate tgt heading
573     if (wind_speed_kts < 3){
574         tgt_heading = base_course;
575     } else {
576         tgt_heading = wind_from_deg;
577     }
578
579     //calculate tgt speed
580     double tgt_speed = 25 - wind_speed_kts;
581     if (tgt_speed < 10)
582         tgt_speed = 10;
583
584     //turn the carrier
585     FGAIShip::TurnTo(tgt_heading);
586     FGAIShip::AccelTo(tgt_speed);
587
588 }
589
590 void FGAICarrier::TurnToRecover(){
591
592     //these are the rules for adjusting heading to provide a relative wind
593     //down the angled flightdeck
594
595     if (wind_speed_kts < 3){
596         tgt_heading = base_course + 60;
597     } else if (rel_wind < -9 && rel_wind >= -180){
598         tgt_heading = wind_from_deg; 
599     } else if (rel_wind > -7 && rel_wind < 45){
600         tgt_heading = wind_from_deg + 60;
601     } else if (rel_wind >=45 && rel_wind < 180){
602         tgt_heading = wind_from_deg + 45;
603     } else 
604         tgt_heading = hdg;
605
606     SG_NORMALIZE_RANGE(tgt_heading, 0.0, 360.0);
607
608     //calculate tgt speed
609     double tgt_speed = 26 - wind_speed_kts;
610     if (tgt_speed < 10)
611         tgt_speed = 10;
612
613     //turn the carrier
614     FGAIShip::TurnTo(tgt_heading);
615     FGAIShip::AccelTo(tgt_speed);
616
617 }
618 void FGAICarrier::TurnToBase(){
619
620     //turn the carrier
621     FGAIShip::TurnTo(base_course);
622     FGAIShip::AccelTo(base_speed);
623
624 }
625
626
627 void FGAICarrier::ReturnToBox(){
628     double course, distance, az2;
629
630     //calculate the bearing and range of the initial position from the carrier
631     geo_inverse_wgs_84(pos, mOpBoxPos, &course, &az2, &distance);
632
633     distance *= SG_METER_TO_NM;
634
635     //cout << "return course: " << course << " distance: " << distance << endl;
636     //turn the carrier
637     FGAIShip::TurnTo(course);
638     FGAIShip::AccelTo(base_speed);
639
640     if (distance >= 1)
641         returning = true;
642     else
643         returning = false;
644
645 } //  end turn to base
646
647
648 bool FGAICarrier::OutsideBox() { //returns true if the carrier is outside operating box
649
650     if ( max_lat == 0 && min_lat == 0 && max_long == 0 && min_long == 0) {
651         SG_LOG(SG_GENERAL, SG_DEBUG, "AICarrier: No Operating Box defined" );
652         return false;
653     }
654
655     if (mOpBoxPos.getLatitudeDeg() >= 0) { //northern hemisphere
656         if (pos.getLatitudeDeg() >= mOpBoxPos.getLatitudeDeg() + max_lat)
657             return true;
658
659         if (pos.getLatitudeDeg() <= mOpBoxPos.getLatitudeDeg() - min_lat)
660             return true;
661
662     } else {                  //southern hemisphere
663         if (pos.getLatitudeDeg() <= mOpBoxPos.getLatitudeDeg() - max_lat)
664             return true;
665
666         if (pos.getLatitudeDeg() >= mOpBoxPos.getLatitudeDeg() + min_lat)
667             return true;
668     }
669
670     if (mOpBoxPos.getLongitudeDeg() >=0) { //eastern hemisphere
671         if (pos.getLongitudeDeg() >= mOpBoxPos.getLongitudeDeg() + max_long)
672             return true;
673
674         if (pos.getLongitudeDeg() <= mOpBoxPos.getLongitudeDeg() - min_long)
675             return true;
676
677     } else {                 //western hemisphere
678         if (pos.getLongitudeDeg() <= mOpBoxPos.getLongitudeDeg() - max_long)
679             return true;
680
681         if (pos.getLongitudeDeg() >= mOpBoxPos.getLongitudeDeg() + min_long)
682             return true;
683     }
684
685     SG_LOG(SG_GENERAL, SG_DEBUG, "AICarrier: Inside Operating Box" );
686     return false;
687
688 } // end OutsideBox
689
690
691 bool FGAICarrier::InToWind() {
692     in_to_wind = false;
693
694     if ( fabs(rel_wind) < 10 ){
695         in_to_wind = true;
696         return true;
697     }
698     return false;
699 }
700
701
702 void FGAICarrier::UpdateElevator(double dt, double transition_time) {
703
704     double step = 0;
705
706     if ((elevators && pos_norm >= 1 ) || (!elevators && pos_norm <= 0 ))
707         return;
708
709     // move the elevators
710     if ( elevators ) {
711         step = dt/transition_time;
712         if ( step > 1 )
713             step = 1;
714     } else {
715         step = -dt/transition_time;
716         if ( step < -1 )
717             step = -1;
718     }
719     // assume a linear relationship
720     raw_pos_norm += step;
721
722     //low pass filter
723     pos_norm = (raw_pos_norm * time_constant) + (pos_norm * (1 - time_constant));
724
725     //sanitise the output
726     if (raw_pos_norm >= 1) {
727         raw_pos_norm = 1;
728     } else if (raw_pos_norm <= 0) {
729         raw_pos_norm = 0;
730     }
731     return;
732
733 } // end UpdateElevator
734
735 void FGAICarrier::UpdateJBD(double dt, double jbd_transition_time) {
736
737     string launchbar_state = _launchbar_state_node->getStringValue();
738     double step = 0;
739
740     if (launchbar_state == "Engaged"){
741         jbd = true;
742     } else {
743         jbd = false;
744     }
745
746     if (( jbd && jbd_pos_norm >= 1 ) || ( !jbd && jbd_pos_norm <= 0 )){
747         return;
748     }
749
750     // move the jbds
751     if ( jbd ) {
752         step = dt/jbd_transition_time;
753         if ( step > 1 )
754             step = 1;
755     } else {
756         step = -dt/jbd_transition_time;
757         if ( step < -1 )
758             step = -1;
759     }
760
761     // assume a linear relationship
762     raw_jbd_pos_norm += step;
763
764     //low pass filter
765     jbd_pos_norm = (raw_jbd_pos_norm * jbd_time_constant) + (jbd_pos_norm * (1 - jbd_time_constant));
766
767     //sanitise the output
768     if (jbd_pos_norm >= 1) {
769         jbd_pos_norm = 1;
770     } else if (jbd_pos_norm <= 0) {
771         jbd_pos_norm = 0;
772     }
773
774     return;
775
776 } // end UpdateJBD