]> git.mxchange.org Git - flightgear.git/blob - src/ATC/AIGAVFRTraffic.cxx
Initial revision of class for AI VFR GA traffic
[flightgear.git] / src / ATC / AIGAVFRTraffic.cxx
1 // FGAILocalTraffic - AIEntity derived class with enough logic to
2 // fly and interact with the traffic pattern.
3 //
4 // Written by David Luff, started March 2002.
5 //
6 // Copyright (C) 2002  David C. Luff - david.luff@nottingham.ac.uk
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., 675 Mass Ave, Cambridge, MA 02139, USA.
21
22 #ifdef HAVE_CONFIG_H
23 #  include <config.h>
24 #endif
25
26 //#include <simgear/scene/model/location.hxx>
27
28 #include <Airports/runways.hxx>
29 #include <Main/globals.hxx>
30 //#include <Scenery/scenery.hxx>
31 //#include <Scenery/tilemgr.hxx>
32 #include <simgear/math/point3d.hxx>
33 //#include <simgear/math/sg_geodesy.hxx>
34 //#include <simgear/misc/sg_path.hxx>
35 #include <string>
36 #include <math.h>
37
38 SG_USING_STD(string);
39
40 #include "ATCmgr.hxx"
41 #include "AILocalTraffic.hxx"
42 #include "AIGAVFRTraffic.hxx"
43 #include "ATCutils.hxx"
44
45 FGAIGAVFRTraffic::FGAIGAVFRTraffic() {
46         ATC = globals->get_ATC_mgr();
47         _towerContactedIncoming = false;
48         _clearedStraightIn = false;
49         _clearedDownwindEntry = false;
50         _incoming = false;
51         _straightIn = false;
52         _downwindEntry = false;
53         _climbout = false;
54         _local = false;
55         _established = false;
56         _e45 = false;
57         _entering = false;
58         _turning = false;
59         _cruise_climb_ias = 90.0;
60         _cruise_ias = 110.0;
61         patternDirection = -1.0;
62         
63         // TESTING - REMOVE OR COMMENT OUT BEFORE COMMIT!!!
64         //_towerContactPrinted = false;
65 }
66
67 FGAIGAVFRTraffic::~FGAIGAVFRTraffic() {
68 }
69
70 // We should never need to Init FGAIGAVFRTraffic in the pattern since that implies arrivel
71 // and we can just use an FGAILocalTraffic instance for that instead.
72
73 // Init en-route to destID at point pt.
74 // TODO - no idea what to do if pt is above planes ceiling due mountains!!
75 bool FGAIGAVFRTraffic::Init(Point3D pt, string destID, const string& callsign) {
76         FGAILocalTraffic::Init(callsign, destID, EN_ROUTE);
77         // TODO FIXME - to get up and running we're going to ignore elev and get FGAIMgr to 
78         // pass in known good values for the test location.  Need to fix this!!! (or at least canonically decide who has responsibility for setting elev).
79         _enroute = true;
80         _destID = destID;
81         _pos = pt;
82         _destPos = dclGetAirportPos(destID);    // TODO - check if we are within the tower catchment area already.
83         _cruise_alt = (_destPos.elev() + 2500.0) * SG_FEET_TO_METER;    // TODO look at terrain elevation as well
84         _pos.setelev(_cruise_alt);
85         // initially set waypoint as airport location
86         _wp = _destPos;
87         _hdg = GetHeadingFromTo(_pos, _wp);
88         _roll = 0.0;
89         _pitch = 0.0;
90         slope = 0.0;
91         // TODO - set climbout if altitude is below normal cruising altitude?
92         //Transform();
93         // Assume it's OK to set the plane visible
94         _aip.setVisible(true);
95         //cout << "Setting visible true\n";
96         Transform();
97         return(true);
98 }
99
100 // Init at srcID to fly to destID
101 bool FGAIGAVFRTraffic::Init(string srcID, string destID, const string& callsign, OperatingState state) {
102         _enroute = false;
103         FGAILocalTraffic::Init(callsign, srcID, PARKED);
104         return(true);
105 }
106
107 void FGAIGAVFRTraffic::Update(double dt) {
108         if(_enroute) {
109                 //cout << "_enroute\n";
110                 //cout << "e" << flush;
111                 FlyPlane(dt);
112                 //cout << "f" << flush;
113                 Transform();
114                 //cout << "g" << flush;
115                 FGAIPlane::Update(dt);
116                 //cout << "h" << flush;
117                 responseCounter += dt;
118                 
119                 // we shouldn't really need this since there's a LOD of 10K on the whole plane anyway I think.
120                 // There are two _aip.setVisible statements set when _local = true that can be removed if the below is removed.
121                 if(dclGetHorizontalSeparation(_pos, Point3D(fgGetDouble("/position/longitude-deg"), fgGetDouble("/position/latitude-deg"), 0.0)) > 8000) _aip.setVisible(false);
122                 else _aip.setVisible(true);
123                 
124         } else if(_local) {
125                 //cout << "L";
126                 //cout << "_local\n";
127                 FGAILocalTraffic::Update(dt);
128         }
129 }
130
131 void FGAIGAVFRTraffic::FlyPlane(double dt) {
132         if(_climbout) {
133                 // Check whether to level off
134                 if(_pos.elev() >= _cruise_alt) {
135                         slope = 0.0;
136                         _pitch = 0.0;
137                         IAS = _cruise_ias;              // FIXME - use smooth transistion to new speed and attitude.
138                         _climbout = false;
139                 } else {
140                         slope = 4.0;
141                         _pitch = 5.0;
142                         IAS = _cruise_climb_ias;
143                 }
144         } else {
145                 // TESTING
146                 /*
147                 if(dclGetHorizontalSeparation(_destPos, _pos) / 1600.0 < 8.1) {
148                         if(!_towerContactPrinted) {
149                                 if(airportID == "KSQL") {
150                                         cout << "****************************************************************\n";
151                                         cout << "++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++\n";
152                                         cout << "****************************************************************\n";
153                                 }
154                                 _towerContactPrinted = true;
155                         }
156                 }
157                 */
158                 
159                 // if distance to destination is less than 6 - 9 miles contact tower
160                 // and prepare to become _incoming after response.
161                 // Possibly check whether to start descent before this?
162                 //cout << "." << flush;
163                 //cout << "sep = " << dclGetHorizontalSeparation(_destPos, _pos) / 1600.0 << '\n';
164                 if(dclGetHorizontalSeparation(_destPos, _pos) / 1600.0 < 8.0) {
165                         //cout << "-" << flush;
166                         if(!_towerContactedIncoming) {
167                                 //cout << "_" << flush;
168                                 GetAirportDetails(airportID);
169                                 //cout << "L" << flush;
170                                 // TODO FIXME TODO - need to check that tower is valid before this else if problem -> BOOM!
171                                 freq = (double)tower->get_freq() / 100.0;
172                                 tuned_station = tower;
173                                 //cout << "freq = " << freq << endl;
174                                 GetRwyDetails(airportID);
175                                 //"@AP Tower @CS @MI miles @CD of the airport for full stop with the ATIS"
176                                 // At the bare minimum we ought to make sure it goes the right way at dual parallel rwy airports!
177                                 if(rwy.rwyID.size() == 3) {
178                                         patternDirection = (rwy.rwyID.substr(2,1) == "R" ? 1 : -1);
179                                 }
180                                 pending_transmission = tower->get_name();
181                                 pending_transmission += " Tower ";
182                                 pending_transmission += plane.callsign;
183                                 //char buf[10];
184                                 int dist_miles = (int)dclGetHorizontalSeparation(_pos, _destPos) / 1600;
185                                 //sprintf(buf, " %i ", dist_miles);
186                                 pending_transmission += " ";
187                                 pending_transmission += ConvertNumToSpokenDigits(dist_miles);
188                                 if(dist_miles > 1) pending_transmission += " miles ";
189                                 else pending_transmission += " mile ";
190                                 pending_transmission += GetCompassDirection(GetHeadingFromTo(_destPos, _pos));
191                                 pending_transmission += " of the airport for full stop with the ATIS";
192                                 //cout << pending_transmission << endl;
193                                 Transmit(14);   // 14 is the callback code, NOT the timeout!
194                                 responseCounter = 0;
195                                 _towerContactedIncoming = true;
196                         } else {
197                                 //cout << "?" << flush;
198                                 if(_clearedStraightIn && responseCounter > 5.5) {
199                                         //cout << "5 " << flush;
200                                         _clearedStraightIn = false;
201                                         _straightIn = true;
202                                         _incoming = true;
203                                         _wp = GetPatternApproachPos();
204                                         _hdg = GetHeadingFromTo(_pos, _wp);     // TODO - turn properly!
205                                         slope = atan((_wp.elev() - _pos.elev()) / dclGetHorizontalSeparation(_wp, _pos)) * DCL_RADIANS_TO_DEGREES;
206                                         double thesh_offset = 0.0;
207                                         Point3D opos = ortho.ConvertToLocal(_pos);
208                                         double angToApt = atan((_pos.elev() - dclGetAirportElev(airportID)) / (opos.y() - thesh_offset)) * DCL_RADIANS_TO_DEGREES;
209                                         //cout << "angToApt = " << angToApt << ' ';
210                                         slope = (angToApt > -5.0 ? 0.0 : angToApt);
211                                         //cout << "slope = " << slope << '\n';
212                                         pending_transmission = "Straight-in ";
213                                         pending_transmission += ConvertRwyNumToSpokenString(rwy.rwyID);
214                                         pending_transmission += " ";
215                                         pending_transmission += plane.callsign;
216                                         //cout << pending_transmission << '\n';
217                                         ConditionalTransmit(4);
218                                 } else if(_clearedDownwindEntry && responseCounter > 5.5) {
219                                         //cout << "6" << flush;
220                                         _clearedDownwindEntry = false;
221                                         _downwindEntry = true;
222                                         _incoming = true;
223                                         _wp = GetPatternApproachPos();
224                                         _hdg = GetHeadingFromTo(_pos, _wp);     // TODO - turn properly!
225                                         slope = atan((_wp.elev() - _pos.elev()) / dclGetHorizontalSeparation(_wp, _pos)) * DCL_RADIANS_TO_DEGREES;
226                                         //cout << "slope = " << slope << '\n';
227                                         pending_transmission = "Report ";
228                                         pending_transmission += (patternDirection == 1 ? "right downwind " : "left downwind ");
229                                         pending_transmission += ConvertRwyNumToSpokenString(rwy.rwyID);
230                                         pending_transmission += " ";
231                                         pending_transmission += plane.callsign;
232                                         //cout << pending_transmission << '\n';
233                                         ConditionalTransmit(4);
234                                 }
235                         }
236                         if(_pos.elev() < (dclGetAirportElev(airportID) + (1000.0 * SG_FEET_TO_METER))) slope = 0.0;     
237                 }
238         }
239         if(_incoming) {
240                 //cout << "i" << '\n';
241                 Point3D orthopos = ortho.ConvertToLocal(_pos);
242                 // TODO - Check whether to start descent
243                 // become _local after the 3 mile report.
244                 if(_pos.elev() < (dclGetAirportElev(airportID) + (1000.0 * SG_FEET_TO_METER))) slope = 0.0;     
245                 // TODO - work out why I needed to add the above line to stop the plane going underground!!!
246                 // (Although it's worth leaving it in as a robustness check anyway).
247                 if(_straightIn) {
248                         //cout << "A " << flush;
249                         if(fabs(orthopos.x()) < 10.0 && !_established) {
250                                 _hdg = rwy.hdg;         // MEGA MEGA HACK - FIXME!!!!!!!
251                                 _established = true;
252                                 //cout << "Established at " << orthopos << '\n';
253                         }
254                         double thesh_offset = 30.0;
255                         //cout << "orthopos.y = " << orthopos.y() << " alt = " << _pos.elev() - dclGetAirportElev(airportID) << '\n';
256                         if(_established && (orthopos.y() > -5400.0)) {
257                                 slope = atan((_pos.elev() - dclGetAirportElev(airportID)) / (orthopos.y() - thesh_offset)) * DCL_RADIANS_TO_DEGREES;
258                                 //cout << "slope0 = " << slope << '\n';
259                         }
260                         //cout << "slope1 = " << slope << '\n';
261                         if(slope > -5.5) slope = 0.0;   // ie we're too low.
262                         //cout << "slope2 = " << slope << '\n';
263                         slope += 0.001;         // To avoid yo-yoing with the above.
264                         //if(_established && (orthopos.y() > -5400.0)) slope = -5.5;
265                         if(_established && (orthopos.y() > -4800.0)) {
266                                 pending_transmission = "3 mile final Runway ";
267                                 pending_transmission += ConvertRwyNumToSpokenString(rwy.rwyID);
268                                 pending_transmission += " ";
269                                 pending_transmission += plane.callsign;
270                                 //cout << pending_transmission << '\n';
271                                 ConditionalTransmit(35);
272                                 _local = true;
273                                 _aip.setVisible(true);  // HACK
274                                 _enroute = false;
275                                 StraightInEntry(true);
276                         }
277                 } else if(_downwindEntry) {
278                         //cout << "B" << flush;
279                         if(_entering) {
280                                 //cout << "C" << flush;
281                                 if(_turning) {
282                                         double tgt_hdg = rwy.hdg + 180.0;
283                                         while((tgt_hdg - _hdg) > 180.0) _hdg += 360.0;
284                                         while((_hdg - tgt_hdg) > 180.0) _hdg -= 360.0;
285                                         double turn_time = 60.0;
286                                         _hdg += (360.0 / turn_time) * dt * (tgt_hdg > _hdg ? 1.0 : -1.0);
287                                         Bank(25.0 * (tgt_hdg > _hdg ? 1.0 : -1.0));
288                                         if(fabs(_hdg - tgt_hdg) < 2.0) {
289                                                 //cout << "Going Local...\n";
290                                                 _hdg = rwy.hdg + 180.0; // TODO - FIX THIS UGLY HACK!!!!!!!
291                                                 leg = DOWNWIND;
292                                                 _local = true;
293                                                 _aip.setVisible(true);  // HACK
294                                                 _enroute = false;
295                                                 _entering = false;
296                                                 _turning = false;
297                                                 DownwindEntry();
298                                         }
299                                 }
300                                 if(fabs(orthopos.x() - (patternDirection == 1 ? 1000 : -1000)) < (_e45 ? 175 : 550)) {  // Caution - hardwired turn clearances.
301                                         //cout << "_turning...\n";
302                                         _turning = true;
303                                 }       // TODO - need to check for other traffic in the pattern and enter much more integilently than that!!!
304                         } else {
305                                 //cout << "D" << flush;
306                                 //cout << '\n' << dclGetHorizontalSeparation(_wp, _pos) << '\n';
307                                 //cout << ortho.ConvertToLocal(_pos);
308                                 //cout << ortho.ConvertToLocal(_wp);
309                                 if(dclGetHorizontalSeparation(_wp, _pos) < 100.0) {
310                                         pending_transmission = "2 miles out for ";
311                                         pending_transmission += (patternDirection == 1 ? "right " : "left ");
312                                         pending_transmission += "downwind Runway ";
313                                         pending_transmission += ConvertRwyNumToSpokenString(rwy.rwyID);
314                                         pending_transmission += " ";
315                                         pending_transmission += plane.callsign;
316                                         //cout << pending_transmission << '\n';
317                                         // TODO - are we at pattern altitude??
318                                         slope = 0.0;
319                                         ConditionalTransmit(30);
320                                         if(_e45) {
321                                                 _hdg = (patternDirection == 1 ? rwy.hdg - 135.0 : rwy.hdg + 135.0);
322                                         } else {
323                                                 _hdg = (patternDirection == 1 ? rwy.hdg + 90.0 : rwy.hdg - 90.0);
324                                         }
325                                         if(_hdg < 0.0) _hdg += 360.0;
326                                         _entering = true;
327                                 }
328                         }       
329                 }
330         } else {
331                 // !_incoming
332                 slope = 0.0;
333         }
334         // FIXME - lots of hackery in the next six lines!!!!
335         double track = _hdg;
336         double crab = 0.0;      
337         _hdg = track + crab;
338         double vel = _cruise_ias;
339         double dist = vel * 0.514444 * dt;
340         _pos = dclUpdatePosition(_pos, track, slope, dist);
341 }
342
343 void FGAIGAVFRTraffic::RegisterTransmission(int code) {
344         switch(code) {
345         case 1: // taxi request cleared
346                 FGAILocalTraffic::RegisterTransmission(code);
347                 break;
348         case 2: // contact tower
349                 FGAILocalTraffic::RegisterTransmission(code);
350                 break;
351         case 3: // Cleared to line up
352                 FGAILocalTraffic::RegisterTransmission(code);
353                 break;
354         case 4: // cleared to take-off
355                 FGAILocalTraffic::RegisterTransmission(code);
356                 break;
357         case 5: // contact ground
358                 FGAILocalTraffic::RegisterTransmission(code);
359                 break;
360         case 6: // taxi to the GA parking
361                 FGAILocalTraffic::RegisterTransmission(code);
362                 break;
363         case 7: // Cleared to land
364                 FGAILocalTraffic::RegisterTransmission(code);
365                 break;
366         case 13: // Go around!
367                 FGAILocalTraffic::RegisterTransmission(code);
368                 break;
369         case 14: // VFR approach for straight-in
370                 responseCounter = 0;
371                 _clearedStraightIn = true;
372                 break;
373         case 15: // VFR approach for downwind entry
374                 responseCounter = 0;
375                 _clearedDownwindEntry = true;
376                 break;
377         default:
378                 break;
379         }
380 }
381
382 // Callback handler
383 // TODO - Really should enumerate these coded values.
384 void FGAIGAVFRTraffic::ProcessCallback(int code) {
385         // 1 - Request Departure from ground
386         // 2 - Report at hold short
387         // 10 - report crosswind
388         // 11 - report downwind
389         // 12 - report base
390         // 13 - report final
391         // 14 - Contact Tower for VFR arrival
392         if(code < 14) {
393                 FGAILocalTraffic::ProcessCallback(code);
394         } else if(code == 14) {
395                 tower->VFRArrivalContact(plane, this, FULL_STOP);
396         }
397 }
398
399 // Return an appropriate altitude to fly at based on the desired altitude and direction
400 // whilst respecting the quadrangle rule.
401 int FGAIGAVFRTraffic::GetQuadrangleAltitude(int dir, int des_alt) {
402         return(8888);
403         // TODO - implement me!
404 }
405
406 // Calculates the position needed to set up for either pattern entry or straight in approach.
407 // Currently returns one of three positions dependent on initial position wrt threshold of active rwy.
408 // 1/ A few miles out on extended centreline for straight-in.
409 // 2/ At an appropriate point on circuit side of rwy for a 45deg entry to downwind.
410 // 3/ At and appropriate point on non-circuit side of rwy at take-off end for perpendicular entry to circuit overflying end-of-rwy.
411 Point3D FGAIGAVFRTraffic::GetPatternApproachPos() {
412         //cout << "\n\n";
413         //cout << "PPPPPPPPPPPPPPPPPPPPPPPppppppppppppppp\n";
414         //cout << "Calculating pattern approach pos for " << plane.callsign << '\n';
415         Point3D orthopos = ortho.ConvertToLocal(_pos);
416         Point3D tmp;
417         //cout << "patternDirection = " << patternDirection << '\n';
418         if(orthopos.y() >= -1000.0) {   // Note that this has to be set the same as the calculation in tower.cxx - at the moment approach type is not transmitted properly between the two.
419                 //cout << "orthopos.x = " << orthopos.x() << '\n';
420                 if((orthopos.x() * patternDirection) > 0.0) {   // 45 deg entry
421                         tmp.setx(2000 * patternDirection);
422                         tmp.sety((rwy.end2ortho.y() / 2.0) + 2000);
423                         tmp.setelev(dclGetAirportElev(airportID) + (1000 * SG_FEET_TO_METER));
424                         _e45 = true;
425                         //cout << "45 deg entry... ";
426                 } else {
427                         tmp.setx(1000 * patternDirection * -1);
428                         tmp.sety(rwy.end2ortho.y());
429                         tmp.setelev(dclGetAirportElev(airportID) + (1000 * SG_FEET_TO_METER));
430                         _e45 = false;
431                         //cout << "90 deg entry... ";
432                 }
433         } else {
434                 tmp.setx(0);
435                 tmp.sety(-5400);
436                 tmp.setelev((5400.0 / 6.0) + dclGetAirportElev(airportID) + 10.0);
437                 //cout << "Straight in... ";
438         }
439         //cout << "Waypoint is " << tmp << '\n';
440         //cout << ortho.ConvertFromLocal(tmp) << '\n';
441         //cout << '\n';
442         //exit(-1);
443         return ortho.ConvertFromLocal(tmp);
444 }
445
446 //FGAIGAVFRTraffic::
447
448 //FGAIGAVFRTraffic::
449
450 //FGAIGAVFRTraffic::