// along with this program; if not, write to the Free Software
// Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+/*==========================================================
+
+TODO list.
+
+Should get pattern direction from tower.
+
+Need to continually monitor and adjust deviation from glideslope
+during descent to avoid occasionally landing short or long.
+
+============================================================*/
+
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif
nominal_final_speed = 65.0;
//nominal_approach_speed;
//stall_speed_landing_config;
- nominalTaxiSpeed = 8.0;
+ nominalTaxiSpeed = 7.5;
taxiTurnRadius = 8.0;
wheelOffset = 1.45; // Warning - hardwired to the C172 - we need to read this in from file.
elevInitGood = false;
descending = false;
targetDescentRate = 0.0;
+ goAround = false;
+ goAroundCalled = false;
}
FGAILocalTraffic::~FGAILocalTraffic() {
}
}
+
/*
There are two possible scenarios during initialisation:
The first is that the user is flying towards the airport, and hence the traffic
hence the traffic must be initialised with respect to the user as well as each other.
To a certain extent it's FGAIMgr that has to worry about this, but we need to provide
sufficient initialisation functionality within the plane classes to allow the manager
-to initialy position them where and how required.
+to initially position them where and how required.
*/
bool FGAILocalTraffic::Init(string ICAO, OperatingState initialState, PatternLeg initialLeg) {
//cout << "FGAILocalTraffic.Init(...) called" << endl;
// Hack alert - Hardwired path!!
string planepath = "Aircraft/c172/Models/c172-dpm.ac";
- SGPath path = globals->get_fg_root();
- path.append(planepath);
- ssgBranch *model = sgLoad3DModel( path.str(),
- planepath.c_str(),
- globals->get_props(),
- globals->get_sim_time_sec() );
+ ssgBranch *model = sgLoad3DModel( globals->get_fg_root(),
+ planepath.c_str(),
+ globals->get_props(),
+ globals->get_sim_time_sec() );
aip.init( model );
aip.setVisible(false); // This will be set to true once a valid ground elevation has been determined
globals->get_scenery()->get_scene_graph()->addKid(aip.getSceneGraph());
AirportATC a;
if(ATC->GetAirportATCDetails(airportID, &a)) {
if(a.tower_freq) { // Has a tower
- tower = (FGTower*)ATC->GetATCPointer((string)airportID, TOWER); // Maybe need some error checking here
+ tower = (FGTower*)ATC->GetATCPointer(airportID, TOWER); // Maybe need some error checking here
if(tower == NULL) {
// Something has gone wrong - abort or carry on with un-towered operation?
return(false);
}
//cout << "AILocalTraffic freq is " << freq << '\n';
} else {
- // Check CTAF, unicom etc
+ // TODO - Check CTAF, unicom etc
}
} else {
//cout << "Unable to find airport details in FGAILocalTraffic::Init()\n";
}
+
+ // Get the active runway details (and copy them into rwy)
+ GetRwyDetails();
// Get the airport elevation
aptElev = dclGetAirportElev(airportID.c_str()) * SG_FEET_TO_METER;
operatingState = initialState;
switch(operatingState) {
case PARKED:
+ tuned_station = ground;
ourGate = ground->GetGateNode();
if(ourGate == NULL) {
// Implies no available gates - what shall we do?
Transform();
break;
case TAXIING:
+ tuned_station = ground;
// FIXME - implement this case properly
return(false); // remove this line when fixed!
break;
// since we've got the implementation for this case already.
// TODO - implement proper generic in_pattern startup.
- // Get the active runway details (and copy them into rwy)
- GetRwyDetails();
-
- // Initial position on threshold for now
- pos.setlat(rwy.threshold_pos.lat());
- pos.setlon(rwy.threshold_pos.lon());
- pos.setelev(rwy.threshold_pos.elev());
- hdg = rwy.hdg;
+ // 18/10/03 - adding the ability to start on downwind (mainly to speed testing of the go-around code!!)
- // Now we've set the position we can do the ground elev
- // This might not always be necessary if we implement in-air start
- elevInitGood = false;
- inAir = false;
- DoGroundElev();
+ //cout << "Starting in pattern...\n";
- pitch = 0.0;
- roll = 0.0;
- leg = TAKEOFF_ROLL;
- vel = 0.0;
- slope = 0.0;
+ tuned_station = tower;
circuitsToFly = 0; // ie just fly this circuit and then stop
touchAndGo = false;
if(rwy.rwyID.size() == 3) {
patternDirection = (rwy.rwyID.substr(2,1) == "R" ? 1 : -1);
}
-
+
+ if(initialLeg == DOWNWIND) {
+ pos = ortho.ConvertFromLocal(Point3D(1000*patternDirection, 800, 0.0));
+ pos.setelev(rwy.threshold_pos.elev() + 1000 * SG_FEET_TO_METER);
+ hdg = rwy.hdg + 180.0;
+ leg = DOWNWIND;
+ elevInitGood = false;
+ inAir = true;
+ track = rwy.hdg - (180 * patternDirection); //should tend to bring track back into the 0->360 range
+ slope = 0.0;
+ pitch = 0.0;
+ roll = 0.0;
+ IAS = 90.0;
+ descending = false;
+ aip.setVisible(true);
+ tower->RegisterAIPlane(plane, this, CIRCUIT, DOWNWIND);
+ } else {
+ // Default to initial position on threshold for now
+ pos.setlat(rwy.threshold_pos.lat());
+ pos.setlon(rwy.threshold_pos.lon());
+ pos.setelev(rwy.threshold_pos.elev());
+ hdg = rwy.hdg;
+
+ // Now we've set the position we can do the ground elev
+ // This might not always be necessary if we implement in-air start
+ elevInitGood = false;
+ inAir = false;
+ DoGroundElev();
+
+ pitch = 0.0;
+ roll = 0.0;
+ leg = TAKEOFF_ROLL;
+ vel = 0.0;
+ slope = 0.0;
+ }
+
operatingState = IN_PATTERN;
Transform();
return(true);
}
+
+// Return what type of landing we're doing on this circuit
+LandingType FGAILocalTraffic::GetLandingOption() {
+ //cout << "circuitsToFly = " << circuitsToFly << '\n';
+ if(circuitsToFly) {
+ return(touchAndGo ? TOUCH_AND_GO : STOP_AND_GO);
+ } else {
+ return(FULL_STOP);
+ }
+}
+
+
// Commands to do something from higher level logic
void FGAILocalTraffic::FlyCircuits(int numCircuits, bool tag) {
//cout << "FlyCircuits called" << endl;
return;
break;
case TAXIING:
- // For now we'll punt this and do nothing
+ // TODO - For now we'll punt this and do nothing
break;
case PARKED:
circuitsToFly = numCircuits; // Note that one too many circuits gets flown because we only test and decrement circuitsToFly after landing
// thus flying one too many circuits. TODO - Need to sort this out better!
touchAndGo = tag;
-#if 0
- // Get the active runway details (and copy them into rwy)
- GetRwyDetails();
-
- // Get the takeoff node for the active runway, get a path to it and start taxiing
- path = ground->GetPath(ourGate, rwy.rwyID);
- if(path.size() < 2) {
- // something has gone wrong
- SG_LOG(SG_ATC, SG_ALERT, "Invalid path from gate to theshold in FGAILocalTraffic::FlyCircuits\n");
- return;
- }
- /*
- cout << "path returned was:" << endl;
- for(unsigned int i=0; i<path.size(); ++i) {
- switch(path[i]->struct_type) {
- case NODE:
- cout << "NODE " << ((node*)(path[i]))->nodeID << endl;
- break;
- case ARC:
- cout << "ARC\n";
- break;
- }
- }
- */
- // pop the gate - we're here already!
- path.erase(path.begin());
- //path.erase(path.begin());
- /*
- cout << "path after popping front is:" << endl;
- for(unsigned int i=0; i<path.size(); ++i) {
- switch(path[i]->struct_type) {
- case NODE:
- cout << "NODE " << ((node*)(path[i]))->nodeID << endl;
- break;
- case ARC:
- cout << "ARC\n";
- break;
- }
- }
- */
-
- taxiState = TD_OUTBOUND;
- StartTaxi();
-
- // Maybe the below should be set when we get to the threshold and prepare for TO?
- // FIXME TODO - pattern direction is still hardwired
- patternDirection = -1; // Left
- // At the bare minimum we ought to make sure it goes the right way at dual parallel rwy airports!
- if(rwy.rwyID.size() == 3) {
- patternDirection = (rwy.rwyID.substr(2,1) == "R" ? 1 : -1);
- }
-
- Transform();
-#endif
break;
}
}
// Run the internal calculations
void FGAILocalTraffic::Update(double dt) {
//cout << "A" << flush;
- double responseTime = 10.0; // seconds - this should get more sophisticated at some point
+ //double responseTime = 10.0; // seconds - this should get more sophisticated at some point
responseCounter += dt;
if((contactTower) && (responseCounter >= 8.0)) {
// Acknowledge request before changing frequency so it gets rendered if the user is on the same freq
string trns = "Tower ";
double f = globals->get_ATC_mgr()->GetFrequency(airportID, TOWER) / 100.0;
char buf[10];
- sprintf(buf, "%f", f);
+ sprintf(buf, "%.2f", f);
trns += buf;
trns += " ";
trns += plane.callsign;
- Transmit(trns);
+ pending_transmission = trns;
+ ConditionalTransmit(30.0);
responseCounter = 0.0;
contactTower = false;
changeFreq = true;
if((changeFreq) && (responseCounter > 8.0)) {
switch(changeFreqType) {
case TOWER:
+ tuned_station = tower;
freq = (double)tower->get_freq() / 100.0;
//Transmit("DING!");
// Contact the tower, even if only virtually
changeFreq = false;
- tower->ContactAtHoldShort(plane, this, CIRCUIT);
+ pending_transmission = plane.callsign;
+ pending_transmission += " at hold short for runway ";
+ pending_transmission += ConvertRwyNumToSpokenString(rwy.rwyID);
+ pending_transmission += " traffic pattern ";
+ if(circuitsToFly) {
+ pending_transmission += ConvertNumToSpokenDigits(circuitsToFly + 1);
+ pending_transmission += " circuits touch and go";
+ } else {
+ pending_transmission += " one circuit to full stop";
+ }
+ Transmit(2);
break;
case GROUND:
+ tuned_station = ground;
freq = (double)ground->get_freq() / 100.0;
break;
// And to avoid compiler warnings...
- case APPROACH:
- break;
- case ATIS:
- break;
- case ENROUTE:
- break;
- case DEPARTURE:
- break;
- case INVALID:
- break;
+ case APPROACH: break;
+ case ATIS: break;
+ case ENROUTE: break;
+ case DEPARTURE: break;
+ case INVALID: break;
}
}
switch(operatingState) {
case IN_PATTERN:
//cout << "In IN_PATTERN\n";
- if(!inAir) DoGroundElev();
- if(!elevInitGood) {
- if(aip.getSGLocation()->get_cur_elev_m() > -9990.0) {
- pos.setelev(aip.getSGLocation()->get_cur_elev_m() + wheelOffset);
- //cout << "TAKEOFF_ROLL, POS = " << pos.lon() << ", " << pos.lat() << ", " << pos.elev() << '\n';
- //Transform();
- aip.setVisible(true);
- //cout << "Making plane visible!\n";
- elevInitGood = true;
+ if(!inAir) {
+ DoGroundElev();
+ if(!elevInitGood) {
+ if(aip.getSGLocation()->get_cur_elev_m() > -9990.0) {
+ pos.setelev(aip.getSGLocation()->get_cur_elev_m() + wheelOffset);
+ //cout << "TAKEOFF_ROLL, POS = " << pos.lon() << ", " << pos.lat() << ", " << pos.elev() << '\n';
+ //Transform();
+ aip.setVisible(true);
+ //cout << "Making plane visible!\n";
+ elevInitGood = true;
+ }
}
}
FlyTrafficPattern(dt);
holdingShort = false;
string trns = "Cleared for take-off ";
trns += plane.callsign;
- Transmit(trns);
+ pending_transmission = trns;
+ Transmit();
StartTaxi();
}
//cout << "^" << flush;
Transform();
break;
- case PARKED:
+ case PARKED:
//cout << "In PARKED\n";
if(!elevInitGood) {
DoGroundElev();
if(circuitsToFly) {
if((taxiRequestPending) && (taxiRequestCleared)) {
//cout << "&" << flush;
- // Get the active runway details (and copy them into rwy)
+ // Get the active runway details (in case they've changed since init)
GetRwyDetails();
// Get the takeoff node for the active runway, get a path to it and start taxiing
StartTaxi();
} else if(!taxiRequestPending) {
//cout << "(" << flush;
- ground->RequestDeparture(plane, this);
// Do some communication
// airport name + tower + airplane callsign + location + request taxi for + operation type + ?
string trns = "";
trns += plane.callsign;
trns += " on apron parking request taxi for traffic pattern";
//cout << "trns = " << trns << endl;
- Transmit(trns);
+ pending_transmission = trns;
+ Transmit(1);
taxiRequestCleared = false;
taxiRequestPending = true;
}
fgSetDouble("/AI/Local1/ortho-x", (ortho.ConvertToLocal(pos)).x());
fgSetDouble("/AI/Local1/ortho-y", (ortho.ConvertToLocal(pos)).y());
fgSetDouble("/AI/Local1/elev", pos.elev() * SG_METER_TO_FEET);
+
+ // And finally, call parent for transmission rendering
+ FGAIPlane::Update(dt);
}
void FGAILocalTraffic::RegisterTransmission(int code) {
clearedToTakeOff = true;
SG_LOG(SG_ATC, SG_INFO, "AI local traffic " << plane.callsign << " cleared to take-off...");
break;
+ case 13: // Go around!
+ responseCounter = 0;
+ goAround = true;
+ SG_LOG(SG_ATC, SG_INFO, "AI local traffic " << plane.callsign << " told to go-around!!");
+ break;
default:
break;
}
break;
case CLIMBOUT:
track = rwy.hdg;
- // Turn to crosswind if above 600ft AND if other traffic allows
+ // Turn to crosswind if above 700ft AND if other traffic allows
// (decided in FGTower and accessed through GetCrosswindConstraint(...)).
- if((pos.elev() - rwy.threshold_pos.elev()) * SG_METER_TO_FEET > 600) {
+ // According to AIM, traffic should climb to within 300ft of pattern altitude before commencing crosswind turn.
+ // TODO - At hot 'n high airports this may be 500ft AGL though - need to make this a variable.
+ if((pos.elev() - rwy.threshold_pos.elev()) * SG_METER_TO_FEET > 700) {
double cc = 0.0;
if(tower->GetCrosswindConstraint(cc)) {
if(orthopos.y() > cc) {
cout << "Turning to crosswind, distance from threshold = " << orthopos.y() << '\n';
leg = TURN1;
}
- } else {
+ } else if(orthopos.y() > 1500.0) { // Added this constraint as a hack to prevent turning too early when going around.
+ // TODO - We should be doing it as a distance from takeoff end, not theshold end though.
cout << "Turning to crosswind, distance from threshold = " << orthopos.y() << '\n';
leg = TURN1;
}
pitch = 0.0;
IAS = 80.0; // FIXME - use smooth transistion to new speed and attitude.
}
+ if(goAround && !goAroundCalled) {
+ if(responseCounter > 5.5) {
+ pending_transmission = plane.callsign;
+ pending_transmission += " going around";
+ Transmit();
+ goAroundCalled = true;
+ }
+ }
break;
case TURN1:
track += (360.0 / turn_time) * dt * patternDirection;
}
break;
case CROSSWIND:
+ goAround = false;
LevelWings();
track = rwy.hdg + (90.0 * patternDirection);
if((pos.elev() - rwy.threshold_pos.elev()) * SG_METER_TO_FEET > 1000) {
}
break;
case FINAL:
+ if(goAround && responseCounter > 2.0) {
+ leg = CLIMBOUT;
+ pitch = 8.0;
+ IAS = best_rate_of_climb_speed;
+ slope = 5.0; // A bit less steep than the initial climbout.
+ inAir = true;
+ goAroundCalled = false;
+ break;
+ }
LevelWings();
if(!transmitted) {
TransmitPatternPositionReport();
break;
case LANDING_ROLL:
//inAir = false;
+ descending = false;
if(aip.getSGLocation()->get_cur_elev_m() > -9990.0) {
pos.setelev(aip.getSGLocation()->get_cur_elev_m() + wheelOffset);
}
//double turn_allowance = 150.0; // Approximate distance in meters that a 90deg corner is shortened by turned in a light plane.
double stod = pa / tan(ga * DCL_DEGREES_TO_RADIANS); // distance in meters from touchdown point to start descent
- cout << "Descent to start = " << stod << " meters out\n";
+ //cout << "Descent to start = " << stod << " meters out\n";
if(stod < blp) { // Start descending on final
SoD.leg = FINAL;
SoD.y = stod * -1.0;
void FGAILocalTraffic::TransmitPatternPositionReport(void) {
// airport name + "traffic" + airplane callsign + pattern direction + pattern leg + rwy + ?
string trns = "";
+ int code = 0;
trns += tower->get_name();
trns += " Traffic ";
// Fall through to DOWNWIND
case DOWNWIND:
trns += "downwind ";
+ code = 11;
break;
case TURN3:
// Fall through to BASE
// Fall through to FINAL
case FINAL: // maybe this should include long/short final if appropriate?
trns += "final ";
+ code = 13;
break;
default: // Hopefully this won't be used
trns += "pattern ";
break;
}
- // FIXME - I've hardwired the runway call as well!! (We could work this out from rwy heading and mag deviation)
- trns += ConvertRwyNumToSpokenString(1);
+ trns += ConvertRwyNumToSpokenString(rwy.rwyID);
// And add the airport name again
trns += tower->get_name();
- Transmit(trns);
+ pending_transmission = trns; // FIXME - make up pending_transmission natively
+ ConditionalTransmit(90.0, code); // Assume a report of this leg will be invalid if we can't transmit within a minute and a half.
+}
+
+// Callback handler
+// TODO - Really should enumerate these coded values.
+void FGAILocalTraffic::ProcessCallback(int code) {
+ // 1 - Request Departure from ground
+ // 2 - Report at hold short
+ // 10 - report crosswind
+ // 11 - report downwind
+ // 12 - report base
+ // 13 - report final
+ if(code == 1) {
+ ground->RequestDeparture(plane, this);
+ } else if(code == 2) {
+ tower->ContactAtHoldShort(plane, this, CIRCUIT);
+ } else if(code == 11) {
+ tower->ReportDownwind(plane.callsign);
+ } else if(code == 13) {
+ tower->ReportFinal(plane.callsign);
+ }
}
void FGAILocalTraffic::ExitRunway(Point3D orthopos) {