#include <Airports/runways.hxx>
#include <Main/globals.hxx>
+#include <Main/viewer.hxx>
#include <Scenery/scenery.hxx>
#include <Scenery/tilemgr.hxx>
#include <simgear/math/point3d.hxx>
contactTower = false;
contactGround = false;
_taxiToGA = false;
+ _removeSelf = false;
descending = false;
targetDescentRate = 0.0;
_savedSlope = 0.0;
_controlled = false;
+
+ _invisible = false;
}
FGAILocalTraffic::~FGAILocalTraffic() {
AirportATC a;
if(ATC->GetAirportATCDetails(airportID, &a)) {
if(a.tower_freq) { // Has a tower - TODO - check the opening hours!!!
- tower = (FGTower*)ATC->GetATCPointer(airportID, TOWER); // Maybe need some error checking here
+ tower = (FGTower*)ATC->GetATCPointer(airportID, TOWER);
if(tower == NULL) {
// Something has gone wrong - abort or carry on with un-towered operation?
SG_LOG(SG_ATC, SG_ALERT, "ERROR - can't get a tower pointer from tower control for " << airportID << " in FGAILocalTraffic::GetAirportDetails() :-(");
void FGAILocalTraffic::GetRwyDetails(string id) {
//cout << "GetRwyDetails called" << endl;
- rwy.rwyID = tower->GetActiveRunway();
+ if(_controlled) {
+ rwy.rwyID = tower->GetActiveRunway();
+ } else {
+ // TODO - get a proper runway ID from uncontrolled airports
+ rwy.rwyID = "00";
+ }
// Now we need to get the threshold position and rwy heading
FGRunway runway;
bool rwyGood = globals->get_runways()->search(id, rwy.rwyID, &runway);
if(rwyGood) {
- double hdg = runway.heading;
+ double hdg = runway._heading;
double other_way = hdg - 180.0;
while(other_way <= 0.0) {
other_way += 360.0;
}
// move to the +l end/center of the runway
- //cout << "Runway center is at " << runway.lon << ", " << runway.lat << '\n';
- Point3D origin = Point3D(runway.lon, runway.lat, aptElev);
+ //cout << "Runway center is at " << runway._lon << ", " << runway._lat << '\n';
+ Point3D origin = Point3D(runway._lon, runway._lat, aptElev);
Point3D ref = origin;
double tshlon, tshlat, tshr;
double tolon, tolat, tor;
- rwy.length = runway.length * SG_FEET_TO_METER;
- rwy.width = runway.width * SG_FEET_TO_METER;
+ rwy.length = runway._length * SG_FEET_TO_METER;
+ rwy.width = runway._width * SG_FEET_TO_METER;
geo_direct_wgs_84 ( aptElev, ref.lat(), ref.lon(), other_way,
rwy.length / 2.0 - 25.0, &tshlat, &tshlon, &tshr );
geo_direct_wgs_84 ( aptElev, ref.lat(), ref.lon(), hdg,
rwy.end1ortho = ortho.ConvertToLocal(rwy.threshold_pos); // should come out as zero
rwy.end2ortho = ortho.ConvertToLocal(takeoff_end);
} else {
- SG_LOG(SG_ATC, SG_ALERT, "Help - can't get good runway in FGAILocalTraffic!!\n");
+ SG_LOG(SG_ATC, SG_ALERT, "Help - can't get good runway at airport " << id << " in FGAILocalTraffic!!");
}
}
patternDirection = (rwy.rwyID.substr(2,1) == "R" ? 1 : -1);
}
- // TODO - this assumes a controlled airport - make sure we revert to CTAF etc if uncontrolled or after-hours.
- if((initialState == PARKED) || (initialState == TAXIING)) {
- freq = (double)ground->get_freq() / 100.0;
+ if(_controlled) {
+ if((initialState == PARKED) || (initialState == TAXIING)) {
+ freq = (double)ground->get_freq() / 100.0;
+ } else {
+ freq = (double)tower->get_freq() / 100.0;
+ }
} else {
- freq = (double)tower->get_freq() / 100.0;
+ freq = 122.8;
+ // TODO - find the proper freq if CTAF or unicom or after-hours.
}
//cout << "In Init(), initialState = " << initialState << endl;
// FIXME - implement this case properly
// For now we'll assume that the plane should start at the hold short in this case
// and that we're working without ground network elements. Ie. an airport with no facility file.
- tuned_station = tower;
+ if(_controlled) {
+ tuned_station = tower;
+ } else {
+ tuned_station = NULL;
+ }
freeTaxi = true;
// Set a position and orientation in an approximate place for hold short.
//cout << "rwy.width = " << rwy.width << '\n';
//cout << "Starting in pattern...\n";
- tuned_station = tower;
+ if(_controlled) {
+ tuned_station = tower;
+ } else {
+ tuned_station = NULL;
+ }
circuitsToFly = 0; // ie just fly this circuit and then stop
touchAndGo = false;
leg = DOWNWIND;
elevInitGood = false;
inAir = true;
- track = rwy.hdg - (180 * patternDirection); //should tend to bring track back into the 0->360 range
+ SetTrack(rwy.hdg - (180 * patternDirection));
slope = 0.0;
_pitch = 0.0;
_roll = 0.0;
IAS = 90.0;
descending = false;
_aip.setVisible(true);
- tower->RegisterAIPlane(plane, this, CIRCUIT, DOWNWIND);
+ if(_controlled) {
+ tower->RegisterAIPlane(plane, this, CIRCUIT, DOWNWIND);
+ }
Transform();
} else {
// Default to initial position on threshold for now
leg = DOWNWIND;
elevInitGood = false;
inAir = true;
- track = rwy.hdg - (180 * patternDirection); //should tend to bring track back into the 0->360 range
+ SetTrack(rwy.hdg - (180 * patternDirection));
slope = 0.0;
_pitch = 0.0;
_roll = 0.0;
leg = FINAL;
elevInitGood = false;
inAir = true;
- track = rwy.hdg;
+ SetTrack(rwy.hdg);
transmitted = true; // TODO - fix this hack.
// TODO - set up the next 5 properly for a descent!
slope = -5.5;
// Run the internal calculations
void FGAILocalTraffic::Update(double dt) {
//cout << "U" << flush;
+
+ // we shouldn't really need this since there's a LOD of 10K on the whole plane anyway I think.
+ // At the moment though I need to to avoid DList overflows - the whole plane LOD obviously isn't getting picked up.
+ if(!_invisible) {
+ if(dclGetHorizontalSeparation(_pos, Point3D(fgGetDouble("/position/longitude-deg"), fgGetDouble("/position/latitude-deg"), 0.0)) > 8000) _aip.setVisible(false);
+ else _aip.setVisible(true);
+ } else {
+ _aip.setVisible(false);
+ }
+
//double responseTime = 10.0; // seconds - this should get more sophisticated at some point
responseCounter += dt;
if((contactTower) && (responseCounter >= 8.0)) {
string trns = "GA Parking, Thank you and Good Day";
//double f = globals->get_ATC_mgr()->GetFrequency(airportID, GROUND) / 100.0;
pending_transmission = trns;
- ConditionalTransmit(5.0);
- tower->DeregisterAIPlane(plane.callsign);
+ ConditionalTransmit(5.0, 99);
_taxiToGA = false;
- // HACK - check if we are at a simple airport or not first
- globals->get_AI_mgr()->ScheduleRemoval(plane.callsign);
- }
+ if(_controlled) {
+ tower->DeregisterAIPlane(plane.callsign);
+ }
+ // NOTE - we can't delete this instance yet since then the frequency won't get release when the message display finishes.
+ }
+
+ if((_removeSelf) && (responseCounter >= 8.0)) {
+ _removeSelf = false;
+ // MEGA HACK - check if we are at a simple airport or not first instead of simply hardwiring KEMT as the only non-simple airport.
+ // TODO FIXME TODO FIXME !!!!!!!
+ if(airportID != "KEMT") globals->get_AI_mgr()->ScheduleRemoval(plane.callsign);
+ }
if((changeFreq) && (responseCounter > 8.0)) {
switch(changeFreqType) {
case TOWER:
+ if(!tower) {
+ SG_LOG(SG_ATC, SG_ALERT, "ERROR: Trying to change frequency to tower in FGAILocalTraffic, but tower is NULL!!!");
+ break;
+ }
tuned_station = tower;
freq = (double)tower->get_freq() / 100.0;
//Transmit("DING!");
Transmit(2);
break;
case GROUND:
+ if(!tower) {
+ SG_LOG(SG_ATC, SG_ALERT, "ERROR: Trying to change frequency to ground in FGAILocalTraffic, but tower is NULL!!!");
+ break;
+ }
+ if(!ground) {
+ SG_LOG(SG_ATC, SG_ALERT, "ERROR: Trying to change frequency to ground in FGAILocalTraffic, but ground is NULL!!!");
+ break;
+ }
tower->DeregisterAIPlane(plane.callsign);
tuned_station = ground;
freq = (double)ground->get_freq() / 100.0;
- // HACK - check if we are at a simple airport or not first
- // TODO FIXME TODO FIXME !!!!!!!
- if(airportID != "KEMT") globals->get_AI_mgr()->ScheduleRemoval(plane.callsign);
break;
// And to avoid compiler warnings...
case APPROACH: break;
// Do some communication
// airport name + tower + airplane callsign + location + request taxi for + operation type + ?
string trns = "";
- trns += tower->get_name();
- trns += " tower ";
+ if(_controlled) {
+ trns += tower->get_name();
+ trns += " tower ";
+ } else {
+ trns += "Traffic ";
+ // TODO - get the airport name somehow if uncontrolled
+ }
trns += plane.callsign;
trns += " on apron parking request taxi for traffic pattern";
//cout << "trns = " << trns << endl;
//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
+ // And finally, call parent.
FGAIPlane::Update(dt);
}
IAS = vel + (cos((_hdg - wind_from) * DCL_DEGREES_TO_RADIANS) * wind_speed);
if(IAS >= 70) {
leg = CLIMBOUT;
+ SetTrack(rwy.hdg); // Hands over control of turning to AIPlane
_pitch = 10.0;
IAS = best_rate_of_climb_speed;
//slope = 7.0;
}
break;
case CLIMBOUT:
- track = rwy.hdg;
// Turn to crosswind if above 700ft AND if other traffic allows
// (decided in FGTower and accessed through GetCrosswindConstraint(...)).
// According to AIM, traffic should climb to within 300ft of pattern altitude before commencing crosswind turn.
}
break;
case TURN1:
- track += (360.0 / turn_time) * dt * patternDirection;
- Bank(25.0 * patternDirection);
+ SetTrack(rwy.hdg + (90.0 * patternDirection));
if((track < (rwy.hdg - 89.0)) || (track > (rwy.hdg + 89.0))) {
leg = CROSSWIND;
}
break;
case CROSSWIND:
goAround = false;
- LevelWings();
- track = rwy.hdg + (90.0 * patternDirection);
if((_pos.elev() - rwy.threshold_pos.elev()) * SG_METER_TO_FEET > 1000) {
slope = 0.0;
_pitch = 0.0;
}
break;
case TURN2:
- track += (360.0 / turn_time) * dt * patternDirection;
- Bank(25.0 * patternDirection);
+ SetTrack(rwy.hdg - (180 * patternDirection));
// just in case we didn't make height on crosswind
if((_pos.elev() - rwy.threshold_pos.elev()) * SG_METER_TO_FEET > 1000) {
slope = 0.0;
if((track < (rwy.hdg - 179.0)) || (track > (rwy.hdg + 179.0))) {
leg = DOWNWIND;
transmitted = false;
- //roll = 0.0;
}
break;
case DOWNWIND:
- LevelWings();
- track = rwy.hdg - (180 * patternDirection); //should tend to bring track back into the 0->360 range
// just in case we didn't make height on crosswind
if(((_pos.elev() - rwy.threshold_pos.elev()) * SG_METER_TO_FEET > 995) && ((_pos.elev() - rwy.threshold_pos.elev()) * SG_METER_TO_FEET < 1015)) {
slope = 0.0;
}
break;
case TURN3:
- track += (360.0 / turn_time) * dt * patternDirection;
- Bank(25.0 * patternDirection);
+ SetTrack(rwy.hdg - (90 * patternDirection));
if(fabs(rwy.hdg - track) < 91.0) {
leg = BASE;
}
break;
case BASE:
- LevelWings();
if(!transmitted) {
// Base report should only be transmitted at uncontrolled airport - not towered.
if(!_controlled) TransmitPatternPositionReport();
IAS = 70.0;
}
- track = rwy.hdg - (90 * patternDirection);
-
// Try and arrange to turn nicely onto final
turn_circumference = IAS * 0.514444 * turn_time;
//Hmmm - this is an interesting one - ground vs airspeed in relation to turn radius
}
break;
case TURN4:
- track += (360.0 / turn_time) * dt * patternDirection;
- Bank(25.0 * patternDirection);
+ SetTrack(rwy.hdg);
if(fabs(track - rwy.hdg) < 0.6) {
leg = FINAL;
vel = nominal_final_speed;
}
}
// Try and track the extended centreline
- track = rwy.hdg - (0.2 * orthopos.x());
+ SetTrack(rwy.hdg - (0.2 * orthopos.x()));
//cout << "orthopos.x() = " << orthopos.x() << " hdg = " << hdg << '\n';
if(_pos.elev() < (rwy.threshold_pos.elev()+20.0+wheelOffset)) {
DoGroundElev(); // Need to call it here expicitly on final since it's only called
_pitch = 0.0;
leg = LANDING_ROLL;
inAir = false;
+ LevelWings();
+ ClearTrack(); // Take over explicit track handling since AIPlane currently always banks when changing course
}
} // else need a fallback position based on arpt elev in case ground elev determination fails?
} else {
tower->ReportDownwind(plane.callsign);
} else if(code == 13) {
tower->ReportFinal(plane.callsign);
+ } else if(code == 99) { // Flag this instance for deletion
+ responseCounter = 0;
+ _removeSelf = true;
+ SG_LOG(SG_ATC, SG_INFO, "AI local traffic " << plane.callsign << " delete instance callback called.");
}
}
} else {
// Something must have gone wrong with the ground network file - or there is only a rwy here and no exits defined
SG_LOG(SG_ATC, SG_INFO, "No exits found by FGAILocalTraffic from runway " << rwy.rwyID << " at " << airportID << '\n');
- //cout << "No exits found by " << plane.callsign << " from runway " << rwy.rwyID << " at " << airportID << '\n';
+ //if(airportID == "KRHV") cout << "No exits found by " << plane.callsign << " from runway " << rwy.rwyID << " at " << airportID << '\n';
// What shall we do - just remove the plane from sight?
_aip.setVisible(false);
+ _invisible = true;
//cout << "Setting visible false\n";
//tower->ReportRunwayVacated(plane.callsign);
string trns = "Clear of the runway ";
// Either this function or the logic of how often it is called
// will almost certainly change.
void FGAILocalTraffic::DoGroundElev() {
-
// It would be nice if we could set the correct tile center here in order to get a correct
// answer with one call to the function, but what I tried in the two commented-out lines
// below only intermittently worked, and I haven't quite groked why yet.
//SGBucket buck(pos.lon(), pos.lat());
//aip.getSGLocation()->set_tile_center(Point3D(buck.get_center_lon(), buck.get_center_lat(), 0.0));
+ // Only do the proper hitlist stuff if we are within visible range of the viewer.
double visibility_meters = fgGetDouble("/environment/visibility-m");
+ FGViewer* vw = globals->get_current_view();
+ if(dclGetHorizontalSeparation(_pos, Point3D(vw->getLongitude_deg(), vw->getLatitude_deg(), 0.0)) > visibility_meters) {
+ _aip.getSGLocation()->set_cur_elev_m(aptElev);
+ return;
+ }
+
+
//globals->get_tile_mgr()->prep_ssg_nodes( acmodel_location,
globals->get_tile_mgr()->prep_ssg_nodes( _aip.getSGLocation(), visibility_meters );
- Point3D scenery_center = globals->get_scenery()->get_center();
+ Point3D scenery_center = globals->get_scenery()->get_center();
globals->get_tile_mgr()->update( _aip.getSGLocation(), visibility_meters, (_aip.getSGLocation())->get_absolute_view_pos( scenery_center ) );
// save results of update in SGLocation for fdm...