From 0adae696aad246f2316e2d1dfea43d3291cec585 Mon Sep 17 00:00:00 2001 From: daveluff Date: Sun, 19 Oct 2003 20:38:08 +0000 Subject: [PATCH] AI plane should go around instead of landing on user if user dawdles on runway now --- src/ATC/AILocalTraffic.cxx | 122 +++++++++++++++++++++++++------------ src/ATC/AILocalTraffic.hxx | 2 + src/ATC/tower.cxx | 84 ++++++++++++++++++++++++- src/ATC/tower.hxx | 7 +++ 4 files changed, 176 insertions(+), 39 deletions(-) diff --git a/src/ATC/AILocalTraffic.cxx b/src/ATC/AILocalTraffic.cxx index 81c2cfa21..9c45bc1dc 100644 --- a/src/ATC/AILocalTraffic.cxx +++ b/src/ATC/AILocalTraffic.cxx @@ -85,6 +85,8 @@ FGAILocalTraffic::FGAILocalTraffic() { descending = false; targetDescentRate = 0.0; + goAround = false; + goAroundCalled = false; } FGAILocalTraffic::~FGAILocalTraffic() { @@ -189,6 +191,9 @@ bool FGAILocalTraffic::Init(string ICAO, OperatingState initialState, PatternLeg } 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; @@ -233,28 +238,11 @@ bool FGAILocalTraffic::Init(string ICAO, OperatingState initialState, PatternLeg // since we've got the implementation for this case already. // TODO - implement proper generic in_pattern startup. - tuned_station = tower; - - // 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; @@ -264,7 +252,42 @@ bool FGAILocalTraffic::Init(string ICAO, OperatingState initialState, PatternLeg 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(); @@ -370,15 +393,17 @@ void FGAILocalTraffic::Update(double dt) { 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); @@ -451,7 +476,7 @@ void FGAILocalTraffic::Update(double dt) { 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 @@ -544,11 +569,11 @@ 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; + 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; } @@ -612,9 +637,11 @@ void FGAILocalTraffic::FlyTrafficPattern(double dt) { 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) { @@ -622,6 +649,7 @@ void FGAILocalTraffic::FlyTrafficPattern(double dt) { leg = TURN1; } } 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; } @@ -633,6 +661,14 @@ void FGAILocalTraffic::FlyTrafficPattern(double dt) { 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; @@ -642,6 +678,7 @@ void FGAILocalTraffic::FlyTrafficPattern(double dt) { } break; case CROSSWIND: + goAround = false; LevelWings(); track = rwy.hdg + (90.0 * patternDirection); if((pos.elev() - rwy.threshold_pos.elev()) * SG_METER_TO_FEET > 1000) { @@ -783,6 +820,15 @@ void FGAILocalTraffic::FlyTrafficPattern(double dt) { } 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(); diff --git a/src/ATC/AILocalTraffic.hxx b/src/ATC/AILocalTraffic.hxx index 7df7fb60f..d6f21c735 100644 --- a/src/ATC/AILocalTraffic.hxx +++ b/src/ATC/AILocalTraffic.hxx @@ -176,6 +176,8 @@ private: bool clearedToLineUp; bool clearedToTakeOff; bool liningUp; // Set true when the turn onto the runway heading is commenced when taxiing out + bool goAround; // Set true if need to go-around + bool goAroundCalled; // Set true during go-around only after we have called our go-around on the radio bool contactTower; // we have been told to contact tower bool contactGround; // we have been told to contact ground bool changeFreq; // true when we need to change frequency diff --git a/src/ATC/tower.cxx b/src/ATC/tower.cxx index 11cb741c2..acef4984e 100644 --- a/src/ATC/tower.cxx +++ b/src/ATC/tower.cxx @@ -44,6 +44,7 @@ longFinalReported(false), longFinalAcknowledged(false), finalReported(false), finalAcknowledged(false), +instructedToGoAround(false), onRwy(false), nextOnRwy(false), opType(TTT_UNKNOWN), @@ -64,6 +65,7 @@ longFinalReported(false), longFinalAcknowledged(false), finalReported(false), finalAcknowledged(false), +instructedToGoAround(false), onRwy(false), nextOnRwy(false), opType(TTT_UNKNOWN), @@ -84,6 +86,7 @@ longFinalReported(false), longFinalAcknowledged(false), finalReported(false), finalAcknowledged(false), +instructedToGoAround(false), onRwy(false), nextOnRwy(false), opType(TTT_UNKNOWN), @@ -105,6 +108,7 @@ longFinalReported(false), longFinalAcknowledged(false), finalReported(false), finalAcknowledged(false), +instructedToGoAround(false), onRwy(false), nextOnRwy(false), opType(TTT_UNKNOWN), @@ -273,6 +277,7 @@ void FGTower::Update(double dt) { // cout << " dt = " << dt << " timeSinceLastDeparture = " << timeSinceLastDeparture << '\n'; } + //cout << ident << " respond = " << respond << " responseReqd = " << responseReqd << '\n'; if(respond) { if(!responseReqd) SG_LOG(SG_ATC, SG_ALERT, "ERROR - respond is true and responseReqd is false in FGTower::Update(...)"); Respond(); @@ -411,6 +416,7 @@ void FGTower::Respond() { } t->holdShortReported = false; } else if(t->finalReported && !(t->finalAcknowledged)) { + bool disp = true; string trns = t->plane.callsign; if(t->nextOnRwy && !rwyOccupied) { if(t->landingType == FULL_STOP) { @@ -420,11 +426,15 @@ void FGTower::Respond() { } // TODO - add winds t->clearedToLand = true; + } else if(t->eta < 20) { + // Do nothing - we'll be telling it to go around in less than 10 seconds if the + // runway doesn't clear so no point in calling "continue approach". + disp = false; } else { trns += " continue approach"; t->clearedToLand = false; } - if(display) { + if(display && disp) { globals->get_ATC_display()->RegisterSingleMessage(trns, 0); } t->finalAcknowledged = true; @@ -714,6 +724,7 @@ void FGTower::CheckCircuitList(double dt) { case CROSSWIND: crosswind_leg_pos = tortho.y(); //cout << "crosswind_leg_pos = " << crosswind_leg_pos << '\n'; + t->instructedToGoAround = false; break; case TURN1: // Fall through to climbout @@ -735,6 +746,22 @@ void FGTower::CheckCircuitList(double dt) { if(t->leg == FINAL) { if(t->landingType == FULL_STOP) t->opType = INBOUND; + if(t->eta < 12 && rwyList.size() && !(t->instructedToGoAround)) { + // TODO - need to make this more sophisticated + // eg. is the plane accelerating down the runway taking off [OK], + // or stationary near the start [V. BAD!!]. + // For now this should stop the AI plane landing on top of the user. + string trns = t->plane.callsign; + trns += " GO AROUND TRAFFIC ON RUNWAY I REPEAT GO AROUND"; + if(display) { + globals->get_ATC_display()->RegisterSingleMessage(trns, 0); + } + t->instructedToGoAround = true; + if(t->planePtr) { + cout << "Registering Go-around transmission with AI plane\n"; + t->planePtr->RegisterTransmission(13); + } + } } else if(t->leg == LANDING_ROLL) { rwyList.push_front(t); // TODO - if(!clearedToLand) shout something!! @@ -1053,6 +1080,37 @@ bool FGTower::AddToTrafficList(TowerPlaneRec* t, bool holding) { return(holding ? firstTime : conflict); } +// Add a tower plane rec with ETA to the circuit list in the correct position ETA-wise +// Returns true if this might cause a separation conflict (based on ETA) with other traffic, false otherwise. +bool FGTower::AddToCircuitList(TowerPlaneRec* t) { + //cout << "ADD: " << circuitList.size(); + //cout << "AddToCircuitList called, currently size = " << circuitList.size() << endl; + double separation_time = 60.0; // seconds - this is currently a guess for light plane separation, and includes a few seconds for a holding plane to taxi onto the rwy. + bool conflict = false; + tower_plane_rec_list_iterator twrItr; + for(twrItr = circuitList.begin(); twrItr != circuitList.end(); twrItr++) { + TowerPlaneRec* tpr = *twrItr; + + if(t->eta < tpr->eta) { + // Ugg - this one's tricky. + // It depends on what the two planes are doing and whether there's a conflict what we do. + if(tpr->eta - t->eta > separation_time) { // No probs, plane 2 can squeeze in before plane 1 with no apparent conflict + circuitList.insert(twrItr, t); + } else { // Ooops - this ones tricky - we have a potential conflict! + conflict = true; + // HACK - just add anyway for now and flag conflict. + circuitList.insert(twrItr, t); + } + //cout << "\tC\t" << circuitList.size() << '\n'; + return(conflict); + } + } + // If we get here we must be at the end of the list, or maybe the list is empty. + circuitList.push_back(t); // TODO - check the separation with the preceding plane for the conflict flag. + //cout << "\tE\t" << circuitList.size() << endl; + return(conflict); +} + // Calculate the eta of a plane to the threshold. // For ground traffic this is the fastest they can get there. @@ -1267,8 +1325,10 @@ void FGTower::ContactAtHoldShort(PlaneRec plane, FGAIPlane* requestee, tower_tra t->clearedToLineUp = false; t->clearedToTakeOff = false; t->opType = operation; + t->pos = requestee->GetPos(); //cout << "Hold Short reported by " << plane.callsign << '\n'; + SG_LOG(SG_ATC, SG_BULK, "Hold Short reported by " << plane.callsign); /* bool next = AddToTrafficList(t, true); @@ -1288,6 +1348,28 @@ void FGTower::ContactAtHoldShort(PlaneRec plane, FGAIPlane* requestee, tower_tra responseReqd = true; } +// Register the presence of an AI plane at a point where contact would already have been made in real life +// CAUTION - currently it is assumed that this plane's callsign is unique - it is up to AIMgr to generate unique callsigns. +void FGTower::RegisterAIPlane(PlaneRec plane, FGAIPlane* ai, tower_traffic_type op, PatternLeg lg) { + // At the moment this is only going to be tested with inserting an AI plane on downwind + TowerPlaneRec* t = new TowerPlaneRec; + t->plane = plane; + t->planePtr = ai; + t->opType = op; + t->leg = lg; + t->pos = ai->GetPos(); + + CalcETA(t); + + if(op == CIRCUIT && lg != LEG_UNKNOWN) { + AddToCircuitList(t); + } else { + // FLAG A WARNING + } + + doThresholdUseOrder(); +} + void FGTower::RequestLandingClearance(string ID) { //cout << "Request Landing Clearance called...\n"; diff --git a/src/ATC/tower.hxx b/src/ATC/tower.hxx index 9c57c61fc..21095126b 100644 --- a/src/ATC/tower.hxx +++ b/src/ATC/tower.hxx @@ -82,6 +82,7 @@ public: bool longFinalAcknowledged; bool finalReported; bool finalAcknowledged; + bool instructedToGoAround; // set true if told by tower to go around bool onRwy; // is physically on the runway bool nextOnRwy; // currently projected by tower to be the next on the runway @@ -128,6 +129,10 @@ public: // Contact tower when at a hold short for departure - for now we'll assume plane - maybe vehicles might want to cross runway eventually? void ContactAtHoldShort(PlaneRec plane, FGAIPlane* requestee, tower_traffic_type operation); + // Register the presence of an AI plane at a point where contact would already have been made in real life + // CAUTION - currently it is assumed that this plane's callsign is unique - it is up to AIMgr to generate unique callsigns. + void RegisterAIPlane(PlaneRec plane, FGAIPlane* ai, tower_traffic_type op, PatternLeg lg = LEG_UNKNOWN); + // Public interface to the active runway - this will get more complex // in the future and consider multi-runway use, airplane weight etc. inline string GetActiveRunway() { return activeRwy; } @@ -272,6 +277,8 @@ private: // Add a tower plane rec with ETA to the traffic list in the correct position ETA-wise. // Returns true if this could cause a threshold ETA conflict with other traffic, false otherwise. bool AddToTrafficList(TowerPlaneRec* t, bool holding = false); + + bool AddToCircuitList(TowerPlaneRec* t); // Ground can be separate or handled by tower in real life. // In the program we will always use a separate FGGround class, but we need to know -- 2.39.5