From 7d547d128702a643c40b54809e2b4e1a38c4a16d Mon Sep 17 00:00:00 2001 From: James Turner Date: Fri, 5 Oct 2012 18:12:46 +0100 Subject: [PATCH] Make traffic take-off roll look a little better. Expand the performance DB logic to support aliases, and select based on aircraft type as well as class. This allows to introduce some variation into AI traffic performance. Change the initial climb-out waypoints to use pitch-hold until passing 3000', which looks much more convincing --- src/AIModel/AIAircraft.cxx | 15 ++--- src/AIModel/AIAircraft.hxx | 6 +- src/AIModel/AIFlightPlanCreate.cxx | 89 ++++++++++++++++-------------- src/AIModel/performancedb.cxx | 65 +++++++++++++++++++--- src/AIModel/performancedb.hxx | 21 +++++-- src/ATC/atc_mgr.cxx | 2 +- src/Traffic/Schedule.cxx | 2 +- src/Traffic/TrafficMgr.cxx | 2 - 8 files changed, 136 insertions(+), 66 deletions(-) diff --git a/src/AIModel/AIAircraft.cxx b/src/AIModel/AIAircraft.cxx index 82f21a4e4..fe93d938b 100644 --- a/src/AIModel/AIAircraft.cxx +++ b/src/AIModel/AIAircraft.cxx @@ -107,7 +107,7 @@ void FGAIAircraft::readFromScenario(SGPropertyNode* scFileNode) { FGAIBase::readFromScenario(scFileNode); - setPerformance(scFileNode->getStringValue("class", "jet_transport")); + setPerformance("", scFileNode->getStringValue("class", "jet_transport")); setFlightPlan(scFileNode->getStringValue("flightplan"), scFileNode->getBoolValue("repeat", false)); setCallSign(scFileNode->getStringValue("callsign")); @@ -131,16 +131,17 @@ void FGAIAircraft::update(double dt) { Transform(); } -void FGAIAircraft::setPerformance(const std::string& acclass) { - static PerformanceDB perfdb; //TODO make it a global service - setPerformance(perfdb.getDataFor(acclass)); - } - +void FGAIAircraft::setPerformance(const std::string& acType, const std::string& acclass) +{ + static PerformanceDB perfdb; //TODO make it a global service + _performance = perfdb.getDataFor(acType, acclass); +} +#if 0 void FGAIAircraft::setPerformance(PerformanceData *ps) { _performance = ps; } - +#endif void FGAIAircraft::Run(double dt) { FGAIAircraft::dt = dt; diff --git a/src/AIModel/AIAircraft.hxx b/src/AIModel/AIAircraft.hxx index ea2d08e7c..73e98aeb3 100644 --- a/src/AIModel/AIAircraft.hxx +++ b/src/AIModel/AIAircraft.hxx @@ -44,8 +44,8 @@ public: virtual void bind(); virtual void update(double dt); - void setPerformance(const std::string& perfString); - void setPerformance(PerformanceData *ps); + void setPerformance(const std::string& acType, const std::string& perfString); + // void setPerformance(PerformanceData *ps); void setFlightPlan(const std::string& fp, bool repat = false); void SetFlightPlan(FGAIFlightPlan *f); @@ -68,6 +68,8 @@ public: double getBearing(double crse); void setAcType(const std::string& ac) { acType = ac; }; + std::string getAcType() const { return acType; } + void setCompany(const std::string& comp) { company = comp;}; void announcePositionToController(); //TODO have to be public? diff --git a/src/AIModel/AIFlightPlanCreate.cxx b/src/AIModel/AIFlightPlanCreate.cxx index c49b4abe8..47026b60e 100644 --- a/src/AIModel/AIFlightPlanCreate.cxx +++ b/src/AIModel/AIFlightPlanCreate.cxx @@ -145,6 +145,7 @@ FGAIWaypoint * FGAIFlightPlan::createInAir(FGAIAircraft * ac, wpt->setGear_down (false ); wpt->setFlaps_down (false ); wpt->setOn_ground (false ); + wpt->setCrossat (aElev ); return wpt; } @@ -427,6 +428,22 @@ bool FGAIFlightPlan::createLandingTaxi(FGAIAircraft * ac, FGAirport * apt, return true; } +static double accelDistance(double v0, double v1, double accel) +{ + double t = fabs(v1 - v0) / accel; // time in seconds to change velocity + // area under the v/t graph: (t * v0) + (dV / 2t) where (dV = v1 - v0) + return t * 0.5 * (v1 + v0); +} + +// find the horizontal distance to gain the specific altiude, holding +// a constant pitch angle. Used to compute distance based on standard FD/AP +// PITCH mode prior to VS or CLIMB engaging. Visually, we want to avoid +// a dip in the nose angle after rotation, during initial climb-out. +static double pitchDistance(double pitchAngleDeg, double altGainM) +{ + return altGainM / tan(pitchAngleDeg * SG_DEGREES_TO_RADIANS); +} + /******************************************************************* * CreateTakeOff * A note on units: @@ -442,27 +459,22 @@ bool FGAIFlightPlan::createTakeOff(FGAIAircraft * ac, bool firstFlight, FGAirport * apt, double speed, const string & fltType) { + const double ACCEL_POINT = 105.0; + const double KNOTS_HOUR_TO_MSEC = SG_NM_TO_METER / 3600.0; + // climb-out angle in degrees. could move this to the perf-db but this + // value is pretty sane + const double INITIAL_PITCH_ANGLE = 12.5; + double accel = ac->getPerformance()->acceleration(); double vTaxi = ac->getPerformance()->vTaxi(); double vRotate = ac->getPerformance()->vRotate(); double vTakeoff = ac->getPerformance()->vTakeoff(); - //double vClimb = ac->getPerformance()->vClimb(); - - double accelMetric = (accel * SG_NM_TO_METER) / 3600; - double vTaxiMetric = (vTaxi * SG_NM_TO_METER) / 3600; - double vRotateMetric = (vRotate * SG_NM_TO_METER) / 3600; - double vTakeoffMetric = (vTakeoff * SG_NM_TO_METER) / 3600; - //double vClimbMetric = (vClimb * SG_NM_TO_METER) / 3600; - // Acceleration = dV / dT - // Acceleration X dT = dV - // dT = dT / Acceleration - //d = (Vf^2 - Vo^2) / (2*a) - //double accelTime = (vRotate - vTaxi) / accel; - //cerr << "Using " << accelTime << " as total acceleration time" << endl; - double accelDistance = - (vRotateMetric * vRotateMetric - - vTaxiMetric * vTaxiMetric) / (2 * accelMetric); - //cerr << "Using " << accelDistance << " " << accelMetric << " " << vRotateMetric << endl; + + double accelMetric = accel * KNOTS_HOUR_TO_MSEC; + double vTaxiMetric = vTaxi * KNOTS_HOUR_TO_MSEC; + double vRotateMetric = vRotate * KNOTS_HOUR_TO_MSEC; + double vTakeoffMetric = vTakeoff * KNOTS_HOUR_TO_MSEC; + FGAIWaypoint *wpt; // Get the current active runway, based on code from David Luff // This should actually be unified and extended to include @@ -475,41 +487,36 @@ bool FGAIFlightPlan::createTakeOff(FGAIAircraft * ac, bool firstFlight, apt->getDynamics()->getActiveRunway(rwyClass, 1, activeRunway, heading); } + FGRunway * rwy = apt->getRunwayByIdent(activeRunway); assert( rwy != NULL ); - double airportElev = apt->getElevation(); - - accelDistance = - (vTakeoffMetric * vTakeoffMetric - - vTaxiMetric * vTaxiMetric) / (2 * accelMetric); - //cerr << "Using " << accelDistance << " " << accelMetric << " " << vTakeoffMetric << endl; - SGGeod accelPoint = rwy->pointOnCenterline(105.0 + accelDistance); + double d = accelDistance(vTaxiMetric, vRotateMetric, accelMetric) + ACCEL_POINT; + + SGGeod accelPoint = rwy->pointOnCenterline(d); wpt = createOnGround(ac, "rotate", accelPoint, airportElev, vTakeoff); pushBackWaypoint(wpt); - accelDistance = - ((vTakeoffMetric * 1.1) * (vTakeoffMetric * 1.1) - - vTaxiMetric * vTaxiMetric) / (2 * accelMetric); - //cerr << "Using " << accelDistance << " " << accelMetric << " " << vTakeoffMetric << endl; - accelPoint = rwy->pointOnCenterline(105.0 + accelDistance); - wpt = - createOnGround(ac, "rotate", accelPoint, airportElev + 1000, - vTakeoff * 1.1); + double vRef = vTakeoffMetric + 20; // climb-out at v2 + 20kts + double gearUpDist = d + pitchDistance(INITIAL_PITCH_ANGLE, 400 * SG_FEET_TO_METER); + accelPoint = rwy->pointOnCenterline(gearUpDist); + + wpt = cloneWithPos(ac, wpt, "gear-up", accelPoint); + wpt->setSpeed(vRef); + wpt->setCrossat(airportElev + 400); wpt->setOn_ground(false); + wpt->setGear_down(false); pushBackWaypoint(wpt); - - wpt = cloneWithPos(ac, wpt, "3000 ft", rwy->end()); - wpt->setAltitude(airportElev + 3000); + + double climbOut = d + pitchDistance(INITIAL_PITCH_ANGLE, 2000 * SG_FEET_TO_METER); + accelPoint = rwy->pointOnCenterline(climbOut); + wpt = createInAir(ac, "2000'", accelPoint, airportElev + 2000, vRef); pushBackWaypoint(wpt); - // Finally, add two more waypoints, so that aircraft will remain under - // Tower control until they have reached the 3000 ft climb point - SGGeod pt = rwy->pointOnCenterline(5000 + rwy->lengthM() * 0.5); - wpt = cloneWithPos(ac, wpt, "5000 ft", pt); - wpt->setAltitude(airportElev + 5000); - pushBackWaypoint(wpt); + // as soon as we pass 2000', hand off to departure so the next acft can line up + // ideally the next aircraft would be able to line-up + hold but that's tricky + // with the current design. return true; } diff --git a/src/AIModel/performancedb.cxx b/src/AIModel/performancedb.cxx index 37660e6a0..f218e851d 100644 --- a/src/AIModel/performancedb.cxx +++ b/src/AIModel/performancedb.cxx @@ -1,3 +1,11 @@ +#ifdef HAVE_CONFIG_H + #include "config.h" +#endif + +#include "performancedb.hxx" + +#include + #include #include #include @@ -7,7 +15,7 @@ #include #include -#include "performancedb.hxx" +#include "performancedata.hxx" using std::string; using std::cerr; @@ -36,14 +44,28 @@ void PerformanceDB::registerPerformanceData(const std::string& id, const std::st registerPerformanceData(id, new PerformanceData(filename)); } -PerformanceData* PerformanceDB::getDataFor(const std::string& id) { - if (_db.find(id) == _db.end()) // id not found -> return jet_transport data +PerformanceData* PerformanceDB::getDataFor(const string& acType, const string& acClass) +{ + // first, try with the specific aircraft type, such as 738 or A322 + if (_db.find(acType) != _db.end()) { + return _db[acType]; + } + + string alias = findAlias(acType); + if (_db.find(alias) != _db.end()) { + return _db[alias]; + } + + SG_LOG(SG_AI, SG_INFO, "no performance data for " << acType); + + if (_db.find(acClass) == _db.end()) { return _db["jet_transport"]; - - return _db[id]; + } + + return _db[acClass]; } -void PerformanceDB::load(SGPath filename) { +void PerformanceDB::load(const SGPath& filename) { string name; double acceleration; double deceleration; @@ -67,8 +89,9 @@ void PerformanceDB::load(SGPath filename) { } SGPropertyNode * node = root.getNode("performancedb"); - for (int i = 0; i < node->nChildren(); i++) { + for (int i = 0; i < node->nChildren(); i++) { SGPropertyNode * db_node = node->getChild(i); + if (!strcmp(db_node->getName(), "aircraft")) { name = db_node->getStringValue("type", "heavy_jet"); acceleration = db_node->getDoubleValue("acceleration-kts-hour", 4.0); deceleration = db_node->getDoubleValue("deceleration-kts-hour", 2.0); @@ -85,6 +108,32 @@ void PerformanceDB::load(SGPath filename) { registerPerformanceData(name, new PerformanceData( acceleration, deceleration, climbRate, descentRate, vRotate, vTakeOff, vClimb, vCruise, vDescent, vApproach, vTouchdown, vTaxi)); - } + } else if (!strcmp(db_node->getName(), "alias")) { + string alias(db_node->getStringValue("alias")); + if (alias.empty()) { + SG_LOG(SG_AI, SG_ALERT, "performance DB alias entry with no definition"); + continue; + } + + BOOST_FOREACH(SGPropertyNode* matchNode, db_node->getChildren("match")) { + string match(matchNode->getStringValue()); + _aliases.push_back(StringPair(match, alias)); + } + } else { + SG_LOG(SG_AI, SG_ALERT, "unrecognized performance DB entry:" << db_node->getName()); + } + } // of nodes iteration } +string PerformanceDB::findAlias(const string& acType) const +{ + BOOST_FOREACH(const StringPair& alias, _aliases) { + if (acType.find(alias.first) == 0) { // matched! + return alias.second; + } + } // of alias iteration + + return string(); +} + + diff --git a/src/AIModel/performancedb.hxx b/src/AIModel/performancedb.hxx index 61d040da0..ad3066cb8 100644 --- a/src/AIModel/performancedb.hxx +++ b/src/AIModel/performancedb.hxx @@ -2,10 +2,11 @@ #define PERFORMANCEDB_HXX #include -#include #include +#include -#include "performancedata.hxx" +class PerformanceData; +class SGPath; /** * Registry for performance data. @@ -25,11 +26,23 @@ public: void registerPerformanceData(const std::string& id, PerformanceData* data); void registerPerformanceData(const std::string& id, const std::string& filename); - PerformanceData* getDataFor(const std::string& id); - void load(SGPath path); + /** + * get performance data for an aircraft type / class. Type is specific, eg + * '738' or 'A319'. Class is more generic, such as 'jet_transport'. + */ + PerformanceData* getDataFor(const std::string& acType, const std::string& acClass); + void load(const SGPath& path); private: std::map _db; + + std::string findAlias(const std::string& acType) const; + + typedef std::pair StringPair; + /// alias list, to allow type/class names to share data. This is used to merge + /// related types together. Note it's ordered, and not a map since we permit + /// partial matches when merging - the first matching alias is used. + std::vector _aliases; }; #endif diff --git a/src/ATC/atc_mgr.cxx b/src/ATC/atc_mgr.cxx index c43721739..bbfc73311 100644 --- a/src/ATC/atc_mgr.cxx +++ b/src/ATC/atc_mgr.cxx @@ -74,7 +74,7 @@ void FGATCManager::init() { ai_ac.setLongitude( longitude ); ai_ac.setLatitude ( latitude ); ai_ac.setAltitude ( altitude ); - ai_ac.setPerformance("jet_transport"); + ai_ac.setPerformance("", "jet_transport"); // NEXT UP: Create a traffic Schedule and fill that with appropriate information. This we can use to flight planning. // Note that these are currently only defaults. diff --git a/src/Traffic/Schedule.cxx b/src/Traffic/Schedule.cxx index 9af6a0c27..3d4db5cb7 100644 --- a/src/Traffic/Schedule.cxx +++ b/src/Traffic/Schedule.cxx @@ -346,7 +346,7 @@ bool FGAISchedule::createAIAircraft(FGScheduledFlight* flight, double speedKnots } FGAIAircraft *aircraft = new FGAIAircraft(this); - aircraft->setPerformance(m_class); //"jet_transport"; + aircraft->setPerformance(acType, m_class); //"jet_transport"; aircraft->setCompany(airline); //i->getAirline(); aircraft->setAcType(acType); //i->getAcType(); aircraft->setPath(modelPath.c_str()); diff --git a/src/Traffic/TrafficMgr.cxx b/src/Traffic/TrafficMgr.cxx index 2510b7a62..464dca5e0 100644 --- a/src/Traffic/TrafficMgr.cxx +++ b/src/Traffic/TrafficMgr.cxx @@ -782,8 +782,6 @@ void FGTrafficManager::endAircraft() acCounter++; requiredAircraft = ""; homePort = ""; - SG_LOG(SG_GENERAL, SG_BULK, "Reading aircraft : " - << registration << " with prioritization score " << score); score = 0; } -- 2.39.5