]> git.mxchange.org Git - flightgear.git/blob - src/AIModel/AICarrier.cxx
Vivian MEAZZA:
[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., 675 Mass Ave, Cambridge, MA 02139, USA.
19
20 #ifdef HAVE_CONFIG_H
21 #  include <config.h>
22 #endif
23
24 #include <string>
25 #include <vector>
26
27 #include <simgear/math/point3d.hxx>
28 #include <simgear/math/sg_geodesy.hxx>
29 #include <math.h>
30 #include <Main/util.hxx>
31 #include <Main/viewer.hxx>
32
33 #include "AICarrier.hxx"
34
35 #include "AIScenario.hxx"
36
37 /** Value of earth radius (meters) */
38 #define RADIUS_M   SG_EQUATORIAL_RADIUS_M
39
40
41
42 FGAICarrier::FGAICarrier(FGAIManager* mgr) : FGAIShip(mgr) {
43   _type_str = "carrier";
44   _otype = otCarrier;
45   
46   
47 }
48
49 FGAICarrier::~FGAICarrier() {
50 }
51
52 void FGAICarrier::setWind_from_east(double fps) {
53    wind_from_east = fps;
54 }
55
56 void FGAICarrier::setWind_from_north(double fps) {
57    wind_from_north = fps;
58 }
59
60 void FGAICarrier::setMaxLat(double deg) {
61    max_lat = fabs(deg);
62 }
63
64 void FGAICarrier::setMinLat(double deg) {
65    min_lat = fabs(deg);
66 }
67
68 void FGAICarrier::setMaxLong(double deg) {
69    max_long = fabs(deg);
70 }
71
72 void FGAICarrier::setMinLong(double deg) {
73    min_long = fabs(deg);
74 }
75
76 void FGAICarrier::setSolidObjects(const list<string>& so) {
77   solid_objects = so;
78 }
79
80 void FGAICarrier::setWireObjects(const list<string>& wo) {
81   wire_objects = wo;
82 }
83
84 void FGAICarrier::setCatapultObjects(const list<string>& co) {
85   catapult_objects = co;
86 }
87
88 void FGAICarrier::setParkingPositions(const list<ParkPosition>& p) {
89   ppositions = p;
90 }
91
92 void FGAICarrier::setSign(const string& s) {
93   sign = s;
94 }
95
96 void FGAICarrier::setTACANChannelID(const string& id) {
97    TACAN_channel_id = id;
98 }
99
100 void FGAICarrier::setFlolsOffset(const Point3D& off) {
101   flols_off = off;
102 }
103
104 void FGAICarrier::getVelocityWrtEarth(sgdVec3& v, sgdVec3& omega, sgdVec3& pivot) {
105   sgdCopyVec3(v, vel_wrt_earth );
106   sgdCopyVec3(omega, rot_wrt_earth );
107   sgdCopyVec3(pivot, rot_pivot_wrt_earth );
108 }
109
110 void FGAICarrier::update(double dt) {
111    
112    // For computation of rotation speeds we just use finite differences her.
113    // That is perfectly valid since this thing is not driven by accelerations
114    // but by just apply discrete changes at its velocity variables.
115    double old_hdg = hdg;
116    double old_roll = roll;
117    double old_pitch = pitch;
118
119    // Update the velocity information stored in those nodes.
120    double v_north = 0.51444444*speed*cos(hdg * SGD_DEGREES_TO_RADIANS);
121    double v_east  = 0.51444444*speed*sin(hdg * SGD_DEGREES_TO_RADIANS);
122
123    double sin_lat = sin(pos.lat() * SGD_DEGREES_TO_RADIANS);
124    double cos_lat = cos(pos.lat() * SGD_DEGREES_TO_RADIANS);
125    double sin_lon = sin(pos.lon() * SGD_DEGREES_TO_RADIANS);
126    double cos_lon = cos(pos.lon() * SGD_DEGREES_TO_RADIANS);
127    double sin_roll = sin(roll * SGD_DEGREES_TO_RADIANS);
128    double cos_roll = cos(roll * SGD_DEGREES_TO_RADIANS);
129    double sin_pitch = sin(pitch * SGD_DEGREES_TO_RADIANS);
130    double cos_pitch = cos(pitch * SGD_DEGREES_TO_RADIANS);
131    double sin_hdg = sin(hdg * SGD_DEGREES_TO_RADIANS);
132    double cos_hdg = cos(hdg * SGD_DEGREES_TO_RADIANS);
133
134    // Transform this back the the horizontal local frame.
135    sgdMat3 trans;
136    
137    // set up the transform matrix
138    trans[0][0] =          cos_pitch*cos_hdg;
139    trans[0][1] = sin_roll*sin_pitch*cos_hdg - cos_roll*sin_hdg;
140    trans[0][2] = cos_roll*sin_pitch*cos_hdg + sin_roll*sin_hdg;
141    
142    trans[1][0] =          cos_pitch*sin_hdg;
143    trans[1][1] = sin_roll*sin_pitch*sin_hdg + cos_roll*cos_hdg;
144    trans[1][2] = cos_roll*sin_pitch*sin_hdg - sin_roll*cos_hdg;
145    
146    trans[2][0] =         -sin_pitch;
147    trans[2][1] = sin_roll*cos_pitch;
148    trans[2][2] = cos_roll*cos_pitch;
149    
150    sgdSetVec3( vel_wrt_earth,
151               - cos_lon*sin_lat*v_north - sin_lon*v_east,
152               - sin_lon*sin_lat*v_north + cos_lon*v_east,
153                 cos_lat*v_north );
154    sgGeodToCart(pos.lat() * SGD_DEGREES_TO_RADIANS,
155                 pos.lon() * SGD_DEGREES_TO_RADIANS,
156                 pos.elev(), rot_pivot_wrt_earth);
157
158    // Now update the position and heading. This will compute new hdg and
159    // roll values required for the rotation speed computation.
160    FGAIShip::update(dt);
161    
162    
163    //automatic turn into wind with a target wind of 25 kts otd
164    if(turn_to_launch_hdg){
165        TurnToLaunch();
166    } else if(OutsideBox() || returning) {// check that the carrier is inside the operating box
167        ReturnToBox();
168    } else {                   //if(!returning
169        TurnToBase();
170    }  //end if  
171
172    // Only change these values if we are able to compute them safely
173    if (dt < DBL_MIN)
174      sgdSetVec3( rot_wrt_earth, 0.0, 0.0, 0.0);
175    else {
176      // Compute the change of the euler angles.
177      double hdg_dot = SGD_DEGREES_TO_RADIANS * (hdg-old_hdg)/dt;
178      // Allways assume that the movement was done by the shorter way.
179      if (hdg_dot < - SGD_DEGREES_TO_RADIANS * 180)
180        hdg_dot += SGD_DEGREES_TO_RADIANS * 360;
181      if (hdg_dot > SGD_DEGREES_TO_RADIANS * 180)
182        hdg_dot -= SGD_DEGREES_TO_RADIANS * 360;
183      double pitch_dot = SGD_DEGREES_TO_RADIANS * (pitch-old_pitch)/dt;
184      // Allways assume that the movement was done by the shorter way.
185      if (pitch_dot < - SGD_DEGREES_TO_RADIANS * 180)
186        pitch_dot += SGD_DEGREES_TO_RADIANS * 360;
187      if (pitch_dot > SGD_DEGREES_TO_RADIANS * 180)
188        pitch_dot -= SGD_DEGREES_TO_RADIANS * 360;
189      double roll_dot = SGD_DEGREES_TO_RADIANS * (roll-old_roll)/dt;
190      // Allways assume that the movement was done by the shorter way.
191      if (roll_dot < - SGD_DEGREES_TO_RADIANS * 180)
192        roll_dot += SGD_DEGREES_TO_RADIANS * 360;
193      if (roll_dot > SGD_DEGREES_TO_RADIANS * 180)
194        roll_dot -= SGD_DEGREES_TO_RADIANS * 360;
195      /*cout << "euler derivatives = "
196           << roll_dot << " " << pitch_dot << " " << hdg_dot << endl;*/
197
198      // Now Compute the rotation vector in the carriers coordinate frame
199      // originating from the euler angle changes.
200      sgdVec3 body;
201      body[0] = roll_dot - hdg_dot*sin_pitch;
202      body[1] = pitch_dot*cos_roll + hdg_dot*sin_roll*cos_pitch;
203      body[2] = -pitch_dot*sin_roll + hdg_dot*cos_roll*cos_pitch;
204
205      // Transform that back to the horizontal local frame.
206      sgdVec3 hl;
207      hl[0] = body[0]*trans[0][0] + body[1]*trans[0][1] + body[2]*trans[0][2];
208      hl[1] = body[0]*trans[1][0] + body[1]*trans[1][1] + body[2]*trans[1][2];
209      hl[2] = body[0]*trans[2][0] + body[1]*trans[2][1] + body[2]*trans[2][2];
210
211      // Now we need to project out rotation components ending in speeds in y
212      // direction in the hoirizontal local frame.
213      hl[1] = 0;
214
215      // Transform that to the earth centered frame.
216      sgdSetVec3(rot_wrt_earth,
217                - cos_lon*sin_lat*hl[0] - sin_lon*hl[1] - cos_lat*cos_lon*hl[2],
218                - sin_lon*sin_lat*hl[0] + cos_lon*hl[1] - cos_lat*sin_lon*hl[2],
219                  cos_lat*hl[0] - sin_lat*hl[2]);
220    }
221
222    UpdateWind(dt);
223    UpdateFlols(trans);
224    UpdateElevator(dt, transition_time);
225 } //end update
226
227 bool FGAICarrier::init() {
228    if (!FGAIShip::init())
229       return false;
230
231    // process the 3d model here
232    // mark some objects solid, mark the wires ...
233
234    // The model should be used for altitude computations.
235    // To avoid that every detail in a carrier 3D model will end into
236    // the aircraft local cache, only set the HOT traversal bit on
237    // selected objects.
238    ssgEntity *sel = aip.getSceneGraph();
239    // Clear the HOT traversal flag
240    mark_nohot(sel);
241    // Selectively set that flag again for wires/cats/solid objects.
242    // Attach a pointer to this carrier class to those objects.
243    mark_wires(sel, wire_objects);
244    mark_cat(sel, catapult_objects);
245    mark_solid(sel, solid_objects);
246
247    _longitude_node = fgGetNode("/position/longitude-deg", true);
248    _latitude_node = fgGetNode("/position/latitude-deg", true);
249    _altitude_node = fgGetNode("/position/altitude-ft", true);
250 //   _elevator_node = fgGetNode("/controls/elevators", true);
251
252    _surface_wind_from_deg_node = 
253               fgGetNode("/environment/config/boundary/entry[0]/wind-from-heading-deg", true);
254    _surface_wind_speed_node = 
255               fgGetNode("/environment/config/boundary/entry[0]/wind-speed-kt", true);
256    
257   
258    turn_to_launch_hdg = false;
259    returning = false;
260   
261    initialpos = pos;
262    base_course = hdg;
263    base_speed = speed;
264    
265    step = 0;
266    pos_norm = 0;
267    elevators = false;
268    transition_time = 150;
269    time_constant = 0.005;
270
271
272    return true;
273 }
274
275 void FGAICarrier::bind() {
276    FGAIShip::bind();
277
278    props->untie("velocities/true-airspeed-kt");
279    
280    props->tie("controls/flols/source-lights",
281                 SGRawValuePointer<int>(&source));
282    props->tie("controls/flols/distance-m",
283                 SGRawValuePointer<double>(&dist));
284    props->tie("controls/flols/angle-degs",
285                 SGRawValuePointer<double>(&angle));
286    props->tie("controls/turn-to-launch-hdg",
287                 SGRawValuePointer<bool>(&turn_to_launch_hdg));
288    props->tie("controls/in-to-wind",
289                 SGRawValuePointer<bool>(&turn_to_launch_hdg));
290    props->tie("controls/base-course-deg",
291                 SGRawValuePointer<double>(&base_course));
292    props->tie("controls/base-speed-kts",
293                 SGRawValuePointer<double>(&base_speed));
294    props->tie("controls/start-pos-lat-deg",
295                 SGRawValuePointer<double>(&initialpos[1]));
296    props->tie("controls/start-pos-long-deg",
297                 SGRawValuePointer<double>(&initialpos[0]));
298    props->tie("velocities/speed-kts",  
299                 SGRawValuePointer<double>(&speed));
300    props->tie("environment/surface-wind-speed-true-kts",  
301                 SGRawValuePointer<double>(&wind_speed_kts));
302    props->tie("environment/surface-wind-from-true-degs",  
303                 SGRawValuePointer<double>(&wind_from_deg));
304    props->tie("environment/rel-wind-from-degs",  
305                 SGRawValuePointer<double>(&rel_wind_from_deg));
306    props->tie("environment/rel-wind-from-carrier-hdg-degs",  
307                 SGRawValuePointer<double>(&rel_wind));
308    props->tie("environment/rel-wind-speed-kts",  
309                 SGRawValuePointer<double>(&rel_wind_speed_kts));
310    props->tie("controls/flols/wave-off-lights",  
311                 SGRawValuePointer<bool>(&wave_off_lights));
312    props->tie("controls/elevators",
313                 SGRawValuePointer<bool>(&elevators));
314    props->tie("surface-positions/elevators-pos-norm",
315                 SGRawValuePointer<double>(&pos_norm));
316    props->tie("controls/elevators-trans-time-s",
317                 SGRawValuePointer<double>(&transition_time));
318    props->tie("controls/elevators-time-constant",
319                 SGRawValuePointer<double>(&time_constant));
320                     
321    props->setBoolValue("controls/flols/cut-lights", false);
322    props->setBoolValue("controls/flols/wave-off-lights", false);
323    props->setBoolValue("controls/flols/cond-datum-lights", true);
324    props->setBoolValue("controls/crew", false);
325    props->setStringValue("navaids/tacan/channel-ID", TACAN_channel_id.c_str());
326    props->setStringValue("sign", sign.c_str());
327 }
328
329 void FGAICarrier::unbind() {
330     FGAIShip::unbind();
331     
332     props->untie("velocities/true-airspeed-kt");
333     
334     props->untie("controls/flols/source-lights");
335     props->untie("controls/flols/distance-m");
336     props->untie("controls/flols/angle-degs");
337     props->untie("controls/turn-to-launch-hdg");
338     props->untie("velocities/speed-kts");
339     props->untie("environment/wind-speed-true-kts");
340     props->untie("environment/wind-from-true-degs");
341     props->untie("environment/rel-wind-from-degs");
342     props->untie("environment/rel-wind-speed-kts");
343     props->untie("controls/flols/wave-off-lights");
344     props->untie("controls/elevators");
345     props->untie("surface-positions/elevators-pos-norm");
346     props->untie("controls/elevators-trans-time-secs");
347     props->untie("controls/elevators-time-constant");
348 }
349
350
351 bool FGAICarrier::getParkPosition(const string& id, Point3D& geodPos,
352                                   double& hdng, sgdVec3 uvw)
353 {
354
355   // FIXME: does not yet cover rotation speeds.
356   list<ParkPosition>::iterator it = ppositions.begin();
357   while (it != ppositions.end()) {
358     // Take either the specified one or the first one ...
359     if ((*it).name == id || id.empty()) {
360       ParkPosition ppos = *it;
361       geodPos = getGeocPosAt(ppos.offset);
362       hdng = hdg + ppos.heading_deg;
363       double shdng = sin(ppos.heading_deg * SGD_DEGREES_TO_RADIANS);
364       double chdng = cos(ppos.heading_deg * SGD_DEGREES_TO_RADIANS);
365       double speed_fps = speed*1.6878099;
366       sgdSetVec3(uvw, chdng*speed_fps, shdng*speed_fps, 0);
367       return true;
368     }
369     ++it;
370   }
371
372   return false;
373 }
374
375 void FGAICarrier::mark_nohot(ssgEntity* e) {
376   if (e->isAKindOf(ssgTypeBranch())) {
377     ssgBranch* br = (ssgBranch*)e;
378     ssgEntity* kid;
379     for ( kid = br->getKid(0); kid != NULL ; kid = br->getNextKid() )
380       mark_nohot(kid);
381
382     br->clrTraversalMaskBits(SSGTRAV_HOT);
383     
384   } else if (e->isAKindOf(ssgTypeLeaf())) {
385
386     e->clrTraversalMaskBits(SSGTRAV_HOT);
387
388   }
389 }
390
391 bool FGAICarrier::mark_wires(ssgEntity* e, const list<string>& wire_objects, bool mark) {
392   bool found = false;
393   if (e->isAKindOf(ssgTypeBranch())) {
394     ssgBranch* br = (ssgBranch*)e;
395     ssgEntity* kid;
396
397     list<string>::const_iterator it;
398     for (it = wire_objects.begin(); it != wire_objects.end(); ++it)
399       mark = mark || (e->getName() && (*it) == e->getName());
400
401     for ( kid = br->getKid(0); kid != NULL ; kid = br->getNextKid() )
402       found = mark_wires(kid, wire_objects, mark) || found;
403
404     if (found)
405       br->setTraversalMaskBits(SSGTRAV_HOT);
406     
407   } else if (e->isAKindOf(ssgTypeLeaf())) {
408     list<string>::const_iterator it;
409     for (it = wire_objects.begin(); it != wire_objects.end(); ++it) {
410       if (mark || (e->getName() && (*it) == e->getName())) {
411         e->setTraversalMaskBits(SSGTRAV_HOT);
412         ssgBase* ud = e->getUserData();
413         if (ud) {
414           FGAICarrierHardware* ch = dynamic_cast<FGAICarrierHardware*>(ud);
415           if (ch) {
416             SG_LOG(SG_GENERAL, SG_WARN,
417                    "AICarrier: Carrier hardware gets marked twice!\n"
418                    "           You have propably a whole branch marked as"
419                    " a wire which also includes other carrier hardware."
420                    );
421           } else {
422             SG_LOG(SG_GENERAL, SG_ALERT,
423                    "AICarrier: Found user data attached to a leaf node which "
424                    "should be marked as a wire!\n    ****Skipping!****");
425           }
426         } else {
427           e->setUserData( FGAICarrierHardware::newWire( this ) );
428           ssgLeaf *l = (ssgLeaf*)e;
429           if ( l->getNumLines() != 1 ) {
430             SG_LOG(SG_GENERAL, SG_ALERT,
431                    "AICarrier: Found wires not modelled with exactly one line!");
432           }
433           found = true;
434         }
435       }
436     }
437   }
438   return found;
439 }
440
441 bool FGAICarrier::mark_solid(ssgEntity* e, const list<string>& solid_objects, bool mark) {
442   bool found = false;
443   if (e->isAKindOf(ssgTypeBranch())) {
444     ssgBranch* br = (ssgBranch*)e;
445     ssgEntity* kid;
446
447     list<string>::const_iterator it;
448     for (it = solid_objects.begin(); it != solid_objects.end(); ++it)
449       mark = mark || (e->getName() && (*it) == e->getName());
450
451     for ( kid = br->getKid(0); kid != NULL ; kid = br->getNextKid() )
452       found = mark_solid(kid, solid_objects, mark) || found;
453
454     if (found)
455       br->setTraversalMaskBits(SSGTRAV_HOT);
456     
457   } else if (e->isAKindOf(ssgTypeLeaf())) {
458     list<string>::const_iterator it;
459     for (it = solid_objects.begin(); it != solid_objects.end(); ++it) {
460       if (mark || (e->getName() && (*it) == e->getName())) {
461         e->setTraversalMaskBits(SSGTRAV_HOT);
462         ssgBase* ud = e->getUserData();
463         if (ud) {
464           FGAICarrierHardware* ch = dynamic_cast<FGAICarrierHardware*>(ud);
465           if (ch) {
466             SG_LOG(SG_GENERAL, SG_WARN,
467                    "AICarrier: Carrier hardware gets marked twice!\n"
468                    "           You have propably a whole branch marked solid"
469                    " which also includes other carrier hardware."
470                    );
471           } else {
472             SG_LOG(SG_GENERAL, SG_ALERT,
473                    "AICarrier: Found user data attached to a leaf node which "
474                    "should be marked solid!\n    ****Skipping!****");
475           }
476         } else {
477           e->setUserData( FGAICarrierHardware::newSolid( this ) );
478           found = true;
479         }
480       }
481     }
482   }
483   return found;
484 }
485
486 bool FGAICarrier::mark_cat(ssgEntity* e, const list<string>& cat_objects, bool mark) {
487   bool found = false;
488   if (e->isAKindOf(ssgTypeBranch())) {
489     ssgBranch* br = (ssgBranch*)e;
490     ssgEntity* kid;
491
492     list<string>::const_iterator it;
493     for (it = cat_objects.begin(); it != cat_objects.end(); ++it)
494       mark = mark || (e->getName() && (*it) == e->getName());
495
496     for ( kid = br->getKid(0); kid != NULL ; kid = br->getNextKid() )
497       found = mark_cat(kid, cat_objects, mark) || found;
498
499     if (found)
500       br->setTraversalMaskBits(SSGTRAV_HOT);
501     
502   } else if (e->isAKindOf(ssgTypeLeaf())) {
503     list<string>::const_iterator it;
504     for (it = cat_objects.begin(); it != cat_objects.end(); ++it) {
505       if (mark || (e->getName() && (*it) == e->getName())) {
506         e->setTraversalMaskBits(SSGTRAV_HOT);
507         ssgBase* ud = e->getUserData();
508         if (ud) {
509           FGAICarrierHardware* ch = dynamic_cast<FGAICarrierHardware*>(ud);
510           if (ch) {
511             SG_LOG(SG_GENERAL, SG_WARN,
512                    "AICarrier: Carrier hardware gets marked twice!\n"
513                    "You have probably a whole branch marked as"
514                    "a catapult which also includes other carrier hardware."
515                     );
516           } else {
517             SG_LOG(SG_GENERAL, SG_ALERT,
518                    "AICarrier: Found user data attached to a leaf node which "
519                    "should be marked as a catapult!\n    ****Skipping!****");
520           }
521         } else {
522           e->setUserData( FGAICarrierHardware::newCatapult( this ) );
523           ssgLeaf *l = (ssgLeaf*)e;
524           if ( l->getNumLines() != 1 ) {
525             SG_LOG(SG_GENERAL, SG_ALERT,
526                    "AICarrier: Found a cat not modelled with exactly "
527                    "one line!");
528           } else {
529             // Now some special code to make sure the cat points in the right
530             // direction. The 0 index must be the backward end, the 1 index
531             // the forward end.
532             // Forward is positive x-direction in our 3D model, also the model
533             // as such is flattened when it is loaded, so we do not need to
534             // care for transforms ...
535             short v[2];
536             l->getLine(0, v, v+1 );
537             sgVec3 ends[2];
538             for (int k=0; k<2; ++k)
539               sgCopyVec3( ends[k], l->getVertex( v[k] ) );
540             
541             // When the 1 end is behind the 0 end, swap the coordinates.
542             if (ends[0][0] < ends[1][0]) {
543               sgCopyVec3( l->getVertex( v[0] ), ends[1] );
544               sgCopyVec3( l->getVertex( v[1] ), ends[0] );
545             }
546
547             found = true;
548           }
549         }
550       }
551     }
552   }
553   return found;
554 }
555
556 void FGAICarrier::UpdateFlols(const sgdMat3& trans) {
557     
558     float in[3];
559     float out[3];
560
561     double flolsXYZ[3], eyeXYZ[3]; 
562     double lat, lon, alt;
563     Point3D eyepos;
564     Point3D flolspos;   
565
566 /*    cout << "x_offset " << flols_x_offset 
567           << " y_offset " << flols_y_offset 
568           << " z_offset " << flols_z_offset << endl;
569         
570      cout << "roll " << roll 
571           << " heading " << hdg
572           << " pitch " << pitch << endl;
573         
574      cout << "carrier lon " << pos[0] 
575           << " lat " <<  pos[1]
576           << " alt " << pos[2] << endl;*/
577         
578 // set the Flols intitial position to the carrier position
579  
580   flolspos = pos;
581   
582 /*  cout << "flols lon " << flolspos[0] 
583           << " lat " <<  flolspos[1]
584           << " alt " << flolspos[2] << endl;*/
585           
586 // set the offsets in metres
587
588 /*  cout << "flols_x_offset " << flols_x_offset << endl
589        << "flols_y_offset " << flols_y_offset << endl
590        << "flols_z_offset " << flols_z_offset << endl;*/
591      
592   in[0] = flols_off.x();  
593   in[1] = flols_off.y();
594   in[2] = flols_off.z();    
595
596 // multiply the input and transform matrices
597
598    out[0] = in[0] * trans[0][0] + in[1] * trans[0][1] + in[2] * trans[0][2];
599    out[1] = in[0] * trans[1][0] + in[1] * trans[1][1] + in[2] * trans[1][2];
600    out[2] = in[0] * trans[2][0] + in[1] * trans[2][1] + in[2] * trans[2][2];
601  
602 // convert meters to ft to degrees of latitude
603    out[0] = (out[0] * 3.28083989501) /(366468.96 - 3717.12 * cos(flolspos[0] * SG_DEGREES_TO_RADIANS));
604
605 // convert meters to ft to degrees of longitude
606    out[1] = (out[1] * 3.28083989501)/(365228.16 * cos(flolspos[1] * SG_DEGREES_TO_RADIANS));
607
608 //print out the result
609 /*   cout  << "lat adjust deg" << out[0] 
610         << " lon adjust deg " << out[1] 
611         << " alt adjust m " << out[2]  << endl;*/
612
613 // adjust Flols position    
614    flolspos[0] += out[0];
615    flolspos[1] += out[1];
616    flolspos[2] += out[2];   
617
618 // convert flols position to cartesian co-ordinates 
619
620   sgGeodToCart(flolspos[1] * SG_DEGREES_TO_RADIANS,
621                flolspos[0] * SG_DEGREES_TO_RADIANS,
622                flolspos[2] , flolsXYZ );
623
624
625 /*  cout << "flols X " << flolsXYZ[0] 
626        << " Y " <<  flolsXYZ[1]
627        << " Z " << flolsXYZ[2] << endl; 
628
629 // check the conversion
630          
631   sgCartToGeod(flolsXYZ, &lat, &lon, &alt);
632  
633   cout << "flols check lon " << lon   
634         << " lat " << lat 
635         << " alt " << alt << endl;      */
636                
637 //get the current position of the pilot's eyepoint (cartesian cordinates)
638
639   sgdCopyVec3( eyeXYZ, globals->get_current_view()->get_absolute_view_pos() );
640   
641  /* cout  << "Eye_X "  << eyeXYZ[0] 
642         << " Eye_Y " << eyeXYZ[1] 
643         << " Eye_Z " << eyeXYZ[2]  << endl; */
644         
645   sgCartToGeod(eyeXYZ, &lat, &lon, &alt);
646   
647   eyepos[0] = lon * SG_RADIANS_TO_DEGREES;
648   eyepos[1] = lat * SG_RADIANS_TO_DEGREES;
649   eyepos[2] = alt;
650   
651 /*  cout << "eye lon " << eyepos[0]
652         << " eye lat " << eyepos[1] 
653         << " eye alt " << eyepos[2] << endl; */
654
655 //calculate the ditance from eye to flols
656       
657   dist = sgdDistanceVec3( flolsXYZ, eyeXYZ );
658
659 //apply an index error
660
661   dist -= 100;
662   
663   //cout << "distance " << dist << endl; 
664   
665   if ( dist < 5000 ) {
666        // calculate height above FLOLS 
667        double y = eyepos[2] - flolspos[2];
668        
669        // calculate the angle from the flols to eye
670        // above the horizontal
671        // double angle;
672        
673        if ( dist != 0 ) {
674            angle = asin( y / dist );
675          } else {
676            angle = 0.0;
677          }
678         
679        angle *= SG_RADIANS_TO_DEGREES;
680         
681       
682   // cout << " height " << y << " angle " << angle ;
683
684 // set the value of source  
685         
686        if ( angle <= 4.35 && angle > 4.01 )
687          { source = 1; }
688          else if ( angle <= 4.01 && angle > 3.670 )
689          { source = 2; }
690          else if ( angle <= 3.670 && angle > 3.330 )
691          { source = 3; }
692          else if ( angle <= 3.330 && angle > 2.990 )
693          { source = 4; }
694          else if ( angle <= 2.990 && angle > 2.650 )
695          { source = 5; }
696          else if ( angle <= 2.650  )
697          { source = 6; }
698          else
699          { source = 0; }
700          
701 //         cout << " source " << source << endl;
702                      
703    }   
704 } // end updateflols
705
706 // find relative wind
707
708
709
710
711 void FGAICarrier::UpdateWind( double dt) {
712
713     double recip;
714     
715     //calculate the reciprocal hdg
716     
717     if (hdg >= 180){
718         recip = hdg - 180;
719     }
720     else{
721         recip = hdg + 180;
722     }
723     
724     //cout <<" heading: " << hdg << "recip: " << recip << endl;
725     
726     //get the surface wind speed and direction
727     wind_from_deg = _surface_wind_from_deg_node->getDoubleValue();
728     wind_speed_kts  = _surface_wind_speed_node->getDoubleValue();
729     
730     //calculate the surface wind speed north and east in kts   
731     double wind_speed_from_north_kts = cos( wind_from_deg / SGD_RADIANS_TO_DEGREES )* wind_speed_kts ;
732     double wind_speed_from_east_kts  = sin( wind_from_deg / SGD_RADIANS_TO_DEGREES )* wind_speed_kts ;
733     
734     //calculate the carrier speed north and east in kts   
735     double speed_north_kts = cos( hdg / SGD_RADIANS_TO_DEGREES )* speed ;
736     double speed_east_kts  = sin( hdg / SGD_RADIANS_TO_DEGREES )* speed ;
737     
738     //calculate the relative wind speed north and east in kts
739     double rel_wind_speed_from_east_kts = wind_speed_from_east_kts + speed_east_kts;
740     double rel_wind_speed_from_north_kts = wind_speed_from_north_kts + speed_north_kts;
741     
742     //combine relative speeds north and east to get relative windspeed in kts                          
743     rel_wind_speed_kts = sqrt((rel_wind_speed_from_east_kts * rel_wind_speed_from_east_kts) 
744     + (rel_wind_speed_from_north_kts * rel_wind_speed_from_north_kts));
745     
746     //calculate the relative wind direction
747     rel_wind_from_deg = atan(rel_wind_speed_from_east_kts/rel_wind_speed_from_north_kts) 
748                             * SG_RADIANS_TO_DEGREES;
749     
750     // rationalise the output
751     if (rel_wind_speed_from_north_kts <= 0){
752         rel_wind_from_deg = 180 + rel_wind_from_deg;
753     }
754     else{
755         if(rel_wind_speed_from_east_kts <= 0){
756             rel_wind_from_deg = 360 + rel_wind_from_deg;
757         }    
758     }
759     
760     //calculate rel wind
761     rel_wind = rel_wind_from_deg - hdg  ;
762     if (rel_wind > 180) rel_wind -= 360;
763     
764     //switch the wave-off lights
765     if (InToWind()){
766        wave_off_lights = false;
767     }else{
768        wave_off_lights = true;
769     }    
770        
771     // cout << "rel wind: " << rel_wind << endl;
772
773 }// end update wind
774
775 void FGAICarrier::TurnToLaunch(){
776     
777      //calculate tgt speed
778        double tgt_speed = 25 - wind_speed_kts;
779        if (tgt_speed < 10) tgt_speed = 10;
780        
781      //turn the carrier
782        FGAIShip::TurnTo(wind_from_deg); 
783        FGAIShip::AccelTo(tgt_speed); 
784            
785      
786         
787 }  // end turn to launch
788    
789 void FGAICarrier::TurnToBase(){
790     
791     //turn the carrier
792        FGAIShip::TurnTo(base_course); 
793        FGAIShip::AccelTo(base_speed); 
794     
795 } //  end turn to base  
796
797 void FGAICarrier::ReturnToBox(){
798     double course, distance, az2;
799         
800     //get the carrier position
801     carrierpos = pos;
802     
803     //cout << "lat: " << carrierpos[1] << " lon: " << carrierpos[0] << endl;
804     
805     //calculate the bearing and range of the initial position from the carrier
806     geo_inverse_wgs_84(carrierpos[2],
807                        carrierpos[1],
808                        carrierpos[0],
809                        initialpos[1],
810                        initialpos[0],
811                        &course, &az2, &distance);
812                      
813     distance *= SG_METER_TO_NM;
814
815     //cout << "return course: " << course << " distance: " << distance << endl;
816     //turn the carrier
817        FGAIShip::TurnTo(course); 
818        FGAIShip::AccelTo(base_speed);
819        if (distance >= 1 ){
820            returning = true;
821        }else{
822            returning = false;
823        }        
824     
825 } //  end turn to base  
826  
827     
828                          
829
830 bool FGAICarrier::OutsideBox(){ //returns true if the carrier is outside operating box
831
832     if ( max_lat == 0 && min_lat == 0 && max_long == 0 && min_long == 0) {
833        SG_LOG(SG_GENERAL, SG_DEBUG, "AICarrier: No Operating Box defined" );
834        return false;
835     }        
836      
837     if (initialpos[1] >= 0){//northern hemisphere
838         if (pos[1] >= initialpos[1] + max_lat) {return true;}
839         else if (pos[1] <= initialpos[1] - min_lat) {return true;}
840     }else{                  //southern hemisphere
841         if (pos[1] <= initialpos[1] - max_lat) {return true;}
842         else if (pos[1] >= initialpos[1] + min_lat) {return true;}
843     }
844     
845     if (initialpos[0] >=0) {//eastern hemisphere
846         if (pos[0] >= initialpos[0] + max_long) {return true;}
847         else if (pos[0] <= initialpos[0] - min_long) {return true;}
848     }else{                 //western hemisphere
849         if (pos[0] <= initialpos[0] - max_long) {return true;}
850         else if (pos[0] >= initialpos[0] + min_long) {return true;}
851     }
852     
853     SG_LOG(SG_GENERAL, SG_DEBUG, "AICarrier: Inside Operating Box" );
854    
855     return false;   
856
857 } // end OutsideBox
858
859 // return the distance to the horizon, given the altitude and the radius of the earth
860 float FGAICarrier::Horizon(float h) { return RADIUS_M * acos(RADIUS_M / (RADIUS_M + h)); }
861     
862 bool FGAICarrier::InToWind(){
863     
864     // test
865     if ( fabs(rel_wind) < 5 ) return true;
866     return false;
867     
868 } //end InToWind     
869
870 void FGAICarrier::UpdateElevator(double dt, double transition_time) {
871
872     if ((elevators && pos_norm >= 1 ) || (!elevators && pos_norm <= 0 ))
873         return;
874
875     // move the elevators
876     if ( elevators ) {
877         step += dt/transition_time;
878         if ( step > 1 )
879             step = 1;
880
881     } else {
882         step -= dt/transition_time;
883         if ( step < 0 )
884             step = 0;
885     }
886     // assume a linear relationship
887     raw_pos_norm = step;
888     if (raw_pos_norm >= 1) {
889         raw_pos_norm = 1;
890     } else if (raw_pos_norm <= 0) {
891         raw_pos_norm = 0;
892     }
893
894     //low pass filter
895     pos_norm = (raw_pos_norm * time_constant) + (pos_norm * (1 - time_constant));
896     return;
897
898 } // end UpdateElevator
899
900
901 int FGAICarrierHardware::unique_id = 1;