]> git.mxchange.org Git - flightgear.git/commitdiff
Merge branches 'jmt/spatial', 'jmt/ref_ptr', 'jmt/navradio' and 'jmt/gps'
authorTim Moore <timoore@redhat.com>
Mon, 11 Jan 2010 23:09:19 +0000 (00:09 +0100)
committerTim Moore <timoore@redhat.com>
Mon, 11 Jan 2010 23:09:19 +0000 (00:09 +0100)
23 files changed:
src/ATCDCL/AILocalTraffic.cxx
src/ATCDCL/tower.cxx
src/Autopilot/route_mgr.cxx
src/FDM/YASim/Airplane.cpp
src/Instrumentation/gps.cxx
src/Instrumentation/gps.hxx
src/Instrumentation/navradio.cxx
src/Instrumentation/navradio.hxx
src/Instrumentation/tacan.cxx
src/Instrumentation/testgps.cxx
src/Main/fg_props.cxx
src/Main/renderer.cxx
src/Model/panelnode.cxx
src/Navaids/navdb.cxx
src/Navaids/navrecord.cxx
src/Navaids/positioned.cxx
src/Scripting/NasalSys.cxx
tests/Makefile.am
tests/est-epsilon.c [deleted file]
tests/est-epsilon.cxx [new file with mode: 0644]
tests/gl-info.c [deleted file]
tests/gl-info.cxx [new file with mode: 0644]
utils/Modeller/yasim_import.py [new file with mode: 0644]

index 6c1b77d8f5da776381435328fe7c13074fdfcacc..c4e467a6fc5c2db56375577de908931161808675 100644 (file)
@@ -841,7 +841,7 @@ void FGAILocalTraffic::FlyTrafficPattern(double dt) {
                // TODO - At hot 'n high airports this may be 500ft AGL though - need to make this a variable.
                if((_pos.getElevationM() - rwy.threshold_pos.getElevationM()) * SG_METER_TO_FEET > 700) {
                        double cc = 0.0;
-                       if(tower && tower->GetCrosswindConstraint(cc)) {
+                       if(_controlled && tower->GetCrosswindConstraint(cc)) {
                                if(orthopos.y() > cc) {
                                        //cout << "Turning to crosswind, distance from threshold = " << orthopos.y() << '\n'; 
                                        leg = TURN1;
@@ -884,7 +884,7 @@ void FGAILocalTraffic::FlyTrafficPattern(double dt) {
                // turn 1000m out for now, taking other traffic into accout
                if(fabs(orthopos.x()) > 900) {
                        double dd = 0.0;
-                       if(tower && tower->GetDownwindConstraint(dd)) {
+                       if(_controlled && tower->GetDownwindConstraint(dd)) {
                                if(fabs(orthopos.x()) > fabs(dd)) {
                                        //cout << "Turning to downwind, distance from centerline = " << fabs(orthopos.x()) << '\n'; 
                                        leg = TURN2;
@@ -930,7 +930,7 @@ void FGAILocalTraffic::FlyTrafficPattern(double dt) {
                        // For now we're assuming that we aim to follow the same glidepath regardless of wind.
                        double d1;
                        double d2;
-                       CalculateSoD(((tower && tower->GetBaseConstraint(d1)) ? d1 : -1000.0), ((tower && tower->GetDownwindConstraint(d2)) ? d2 : 1000.0 * patternDirection), (patternDirection ? true : false));
+                       CalculateSoD(((_controlled && tower->GetBaseConstraint(d1)) ? d1 : -1000.0), ((_controlled && tower->GetDownwindConstraint(d2)) ? d2 : 1000.0 * patternDirection), (patternDirection ? true : false));
                        if(SoD.leg == DOWNWIND) {
                                descending = (orthopos.y() < SoD.y ? true : false);
                        }
@@ -950,7 +950,7 @@ void FGAILocalTraffic::FlyTrafficPattern(double dt) {
                if(orthopos.y() < -1000.0 + turn_radius) {
                //if(orthopos.y() < -980) {
                        double bb = 0.0;
-                       if(tower && tower->GetBaseConstraint(bb)) {
+                       if(_controlled && tower->GetBaseConstraint(bb)) {
                                if(fabs(orthopos.y()) > fabs(bb)) {
                                        //cout << "Turning to base, distance from threshold = " << fabs(orthopos.y()) << '\n'; 
                                        leg = TURN3;
@@ -982,7 +982,7 @@ void FGAILocalTraffic::FlyTrafficPattern(double dt) {
                        double d1;
                        // Make downwind leg position artifically large to avoid any chance of SoD being returned as
                        // on downwind when we are already on base.
-                       CalculateSoD(((tower && tower->GetBaseConstraint(d1)) ? d1 : -1000.0), (10000.0 * patternDirection), (patternDirection ? true : false));
+                       CalculateSoD(((_controlled && tower->GetBaseConstraint(d1)) ? d1 : -1000.0), (10000.0 * patternDirection), (patternDirection ? true : false));
                        if(SoD.leg == BASE) {
                                descending = (fabs(orthopos.y()) < fabs(SoD.y) ? true : false);
                        }
@@ -1146,24 +1146,8 @@ void FGAILocalTraffic::FlyTrafficPattern(double dt) {
                double axx = gxx - wxx; // Plane in-air velocity x component
                double ayy = gyy - wyy; // Plane in-air velocity y component
                // Now we want the angle between gxx and axx (which is the crab)
-               double maga = sqrt(axx*axx + ayy*ayy);
-               double magg = sqrt(gxx*gxx + gyy*gyy);
-               crab = acos((axx*gxx + ayy*gyy) / (maga * magg));
-               // At this point this works except we're getting the modulus of the angle
+               crab = atan2(ayy - gyy, axx - gxx) * DCL_RADIANS_TO_DEGREES;
                //cout << "crab = " << crab << '\n';
-               
-               // Make sure both headings are in the 0->360 circle in order to get sane differences
-               dclBoundHeading(wind_from);
-               dclBoundHeading(track);
-               if(track > wind_from) {
-                       if((track - wind_from) <= 180) {
-                               crab *= -1.0;
-                       }
-               } else {
-                       if((wind_from - track) >= 180) {
-                               crab *= -1.0;
-                       }
-               }
        } else {        // on the ground - crab dosen't apply
                crab = 0.0;
        }
@@ -1171,6 +1155,7 @@ void FGAILocalTraffic::FlyTrafficPattern(double dt) {
        //cout << "X " << orthopos.x() << "  Y " << orthopos.y() << "  SLOPE " << slope << "  elev " << _pos.elev() * SG_METER_TO_FEET << '\n';
        
        _hdg = track + crab;
+       dclBoundHeading(_hdg);
        dist = vel * 0.514444 * dt;
        _pos = dclUpdatePosition(_pos, track, slope, dist);
 }
@@ -1209,7 +1194,7 @@ void FGAILocalTraffic::TransmitPatternPositionReport(void) {
        // airport name + "traffic" + airplane callsign + pattern direction + pattern leg + rwy + ?
        string trns;
        int code = 0;
-       const string& apt_name = tower ? tower->get_name() : airportID;
+       const string& apt_name = _controlled ? tower->get_name() : airportID;
 
        trns += apt_name;
        trns += " Traffic ";
index d1886c3389691a8ca6226f9df68ab6db253d115b..fa54b15cf8804d352001b347424c34057df33eb3 100644 (file)
@@ -55,6 +55,8 @@ using std::cout;
 
 TowerPlaneRec::TowerPlaneRec() :
        planePtr(NULL),
+       eta(0),
+       dist_out(0),
        clearedToLand(false),
        clearedToLineUp(false),
        clearedToTakeOff(false),
@@ -85,6 +87,8 @@ TowerPlaneRec::TowerPlaneRec() :
 
 TowerPlaneRec::TowerPlaneRec(const PlaneRec& p) :
        planePtr(NULL),
+       eta(0),
+       dist_out(0),
        clearedToLand(false),
        clearedToLineUp(false),
        clearedToTakeOff(false),
@@ -115,6 +119,8 @@ TowerPlaneRec::TowerPlaneRec(const PlaneRec& p) :
 
 TowerPlaneRec::TowerPlaneRec(const SGGeod& pt) :
        planePtr(NULL),
+       eta(0),
+       dist_out(0),
        clearedToLand(false),
        clearedToLineUp(false),
        clearedToTakeOff(false),
@@ -146,6 +152,8 @@ TowerPlaneRec::TowerPlaneRec(const SGGeod& pt) :
 
 TowerPlaneRec::TowerPlaneRec(const PlaneRec& p, const SGGeod& pt) :
        planePtr(NULL),
+       eta(0),
+       dist_out(0),
        clearedToLand(false),
        clearedToLineUp(false),
        clearedToTakeOff(false),
@@ -2125,6 +2133,7 @@ void FGTower::VFRArrivalContact(const string& ID, const LandingType& opt) {
        
        t->plane.type = GA_SINGLE;      // FIXME - Another assumption!
        t->plane.callsign = usercall;
+       CalcETA(t);
        
        t->vfrArrivalReported = true;
        responseReqd = true;
index 313cd33ed6564d6901776a82e71c403f33a99df4..bbd2db96b58531c063ecbee448a26ee897395420 100644 (file)
@@ -775,7 +775,11 @@ const char* FGRouteMgr::getDepartureName() const
 
 void FGRouteMgr::setDepartureICAO(const char* aIdent)
 {
-  _departure = FGAirport::findByIdent(aIdent);
+  if ((aIdent == NULL) || (strlen(aIdent) < 4)) {
+    _departure = NULL;
+  } else {
+    _departure = FGAirport::findByIdent(aIdent);
+  }
 }
 
 const char* FGRouteMgr::getDestinationICAO() const
@@ -798,6 +802,10 @@ const char* FGRouteMgr::getDestinationName() const
 
 void FGRouteMgr::setDestinationICAO(const char* aIdent)
 {
-  _destination = FGAirport::findByIdent(aIdent);
+  if ((aIdent == NULL) || (strlen(aIdent) < 4)) {
+    _destination = NULL;
+  } else {
+    _destination = FGAirport::findByIdent(aIdent);
+  }
 }
     
index 1b3fbd4ba8ebfa47c4b2980f57ce8dbb8e6ccefe..483da426911dcdcd73addc1da6bb6991d6068551 100644 (file)
@@ -51,6 +51,8 @@ Airplane::Airplane()
     _liftRatio = 1;
     _cruiseAoA = 0;
     _tailIncidence = 0;
+
+    _failureMsg = 0;
 }
 
 Airplane::~Airplane()
@@ -507,6 +509,10 @@ float Airplane::compileFuselage(Fuselage* f)
     float fwd[3];
     Math::sub3(f->front, f->back, fwd);
     float len = Math::mag3(fwd);
+    if (len == 0) {
+        _failureMsg = "Zero length fuselage";
+       return 0;
+    }
     float wid = f->width;
     int segs = (int)Math::ceil(len/wid);
     float segWgt = len*wid/segs;
@@ -688,6 +694,10 @@ void Airplane::compile()
         _model.setGroundEffect(gepos, gespan, 0.15f);
     }
 
+    // solve function below resets failure message
+    // so check if we have any problems and abort here
+    if (_failureMsg) return;
+
     solveGear();
     if(_wing && _tail) solve();
     else
index 4477dd4eddaf23e5ce1e77b7ad6a0ddfda24694a..3303b669c4882fe0130df2f49420bda02a87a710 100644 (file)
@@ -74,28 +74,6 @@ SGGeod SGGeodProperty::get() const
     }
 }
 
-static void tieSGGeod(SGPropertyNode* aNode, SGGeod& aRef, 
-  const char* lonStr, const char* latStr, const char* altStr)
-{
-  aNode->tie(lonStr, SGRawValueMethods<SGGeod, double>(aRef, &SGGeod::getLongitudeDeg, &SGGeod::setLongitudeDeg));
-  aNode->tie(latStr, SGRawValueMethods<SGGeod, double>(aRef, &SGGeod::getLatitudeDeg, &SGGeod::setLatitudeDeg));
-  
-  if (altStr) {
-    aNode->tie(altStr, SGRawValueMethods<SGGeod, double>(aRef, &SGGeod::getElevationFt, &SGGeod::setElevationFt));
-  }
-}
-
-static void tieSGGeodReadOnly(SGPropertyNode* aNode, SGGeod& aRef, 
-  const char* lonStr, const char* latStr, const char* altStr)
-{
-  aNode->tie(lonStr, SGRawValueMethods<SGGeod, double>(aRef, &SGGeod::getLongitudeDeg, NULL));
-  aNode->tie(latStr, SGRawValueMethods<SGGeod, double>(aRef, &SGGeod::getLatitudeDeg, NULL));
-  
-  if (altStr) {
-    aNode->tie(altStr, SGRawValueMethods<SGGeod, double>(aRef, &SGGeod::getElevationFt, NULL));
-  }
-}
-
 static const char* makeTTWString(double TTW)
 {
   if ((TTW <= 0.0) || (TTW >= 356400.5)) { // 99 hours
@@ -220,20 +198,21 @@ GPS::Config::Config() :
   _extCourseSource = fgGetNode("/instrumentation/nav[0]/radials/selected-deg", true);
 }
 
-void GPS::Config::init(SGPropertyNode* aCfgNode)
+void GPS::Config::bind(GPS* aOwner, SGPropertyNode* aCfg)
 {
-  aCfgNode->tie("turn-rate-deg-sec", SGRawValuePointer<double>(&_turnRate));
-  aCfgNode->tie("turn-anticipation", SGRawValuePointer<bool>(&_enableTurnAnticipation));
-  aCfgNode->tie("wpt-alert-time", SGRawValuePointer<double>(&_waypointAlertTime));
-  aCfgNode->tie("tune-nav-radio-to-ref-vor", SGRawValuePointer<bool>(&_tuneRadio1ToRefVor));
-  aCfgNode->tie("min-runway-length-ft", SGRawValuePointer<double>(&_minRunwayLengthFt));
-  aCfgNode->tie("hard-surface-runways-only", SGRawValuePointer<bool>(&_requireHardSurface));
+  aOwner->tie(aCfg, "turn-rate-deg-sec", SGRawValuePointer<double>(&_turnRate));
+  
+  aOwner->tie(aCfg, "turn-anticipation", SGRawValuePointer<bool>(&_enableTurnAnticipation));
+  aOwner->tie(aCfg, "wpt-alert-time", SGRawValuePointer<double>(&_waypointAlertTime));
+  aOwner->tie(aCfg, "tune-nav-radio-to-ref-vor", SGRawValuePointer<bool>(&_tuneRadio1ToRefVor));
+  aOwner->tie(aCfg, "min-runway-length-ft", SGRawValuePointer<double>(&_minRunwayLengthFt));
+  aOwner->tie(aCfg, "hard-surface-runways-only", SGRawValuePointer<bool>(&_requireHardSurface));
   
-  aCfgNode->tie("course-source", SGRawValueMethods<GPS::Config, const char*>
+  aOwner->tie(aCfg, "course-source", SGRawValueMethods<GPS::Config, const char*>
     (*this, &GPS::Config::getCourseSource, &GPS::Config::setCourseSource));
     
-  aCfgNode->tie("cdi-max-deflection-nm", SGRawValuePointer<double>(&_cdiMaxDeflectionNm));
-  aCfgNode->tie("drive-autopilot", SGRawValuePointer<bool>(&_driveAutopilot));
+  aOwner->tie(aCfg, "cdi-max-deflection-nm", SGRawValuePointer<double>(&_cdiMaxDeflectionNm));
+  aOwner->tie(aCfg, "drive-autopilot", SGRawValuePointer<bool>(&_driveAutopilot));
 }
 
 const char* 
@@ -291,6 +270,9 @@ GPS::GPS ( SGPropertyNode *node) :
   _anticipateTurn(false),
   _inTurn(false)
 {
+  string branch = "/instrumentation/" + _name;
+  _gpsNode = fgGetNode(branch.c_str(), _num, true );
+  _scratchNode = _gpsNode->getChild("scratch", 0, true);
 }
 
 GPS::~GPS ()
@@ -300,112 +282,39 @@ GPS::~GPS ()
 void
 GPS::init ()
 {
-    _routeMgr = (FGRouteMgr*) globals->get_subsystem("route-manager");
-    assert(_routeMgr);
+  _routeMgr = (FGRouteMgr*) globals->get_subsystem("route-manager");
+  assert(_routeMgr);
   
-    string branch;
-    branch = "/instrumentation/" + _name;
-
-  SGPropertyNode *node = fgGetNode(branch.c_str(), _num, true );
-  _config.init(node->getChild("config", 0, true));
-    
   _position.init("/position/longitude-deg", "/position/latitude-deg", "/position/altitude-ft");
   _magvar_node = fgGetNode("/environment/magnetic-variation-deg", true);
-  _serviceable_node = node->getChild("serviceable", 0, true);
+  _serviceable_node = _gpsNode->getChild("serviceable", 0, true);
   _serviceable_node->setBoolValue(true);
   _electrical_node = fgGetNode("/systems/electrical/outputs/gps", true);
 
 // basic GPS outputs
-  node->tie("selected-course-deg", SGRawValueMethods<GPS, double>(*this, &GPS::getSelectedCourse, NULL));
-  
-  _raim_node = node->getChild("raim", 0, true);
-
-  tieSGGeodReadOnly(node, _indicated_pos, "indicated-longitude-deg", 
-        "indicated-latitude-deg", "indicated-altitude-ft");
-
-  node->tie("indicated-vertical-speed", SGRawValueMethods<GPS, double>
-    (*this, &GPS::getVerticalSpeed, NULL));
-  node->tie("indicated-track-true-deg", SGRawValueMethods<GPS, double>
-    (*this, &GPS::getTrueTrack, NULL));
-  node->tie("indicated-track-magnetic-deg", SGRawValueMethods<GPS, double>
-    (*this, &GPS::getMagTrack, NULL));
-  node->tie("indicated-ground-speed-kt", SGRawValueMethods<GPS, double>
-    (*this, &GPS::getGroundspeedKts, NULL));
-        
-  _odometer_node = node->getChild("odometer", 0, true);
-  _trip_odometer_node = node->getChild("trip-odometer", 0, true);
-  _true_bug_error_node = node->getChild("true-bug-error-deg", 0, true);
-  _magnetic_bug_error_node = node->getChild("magnetic-bug-error-deg", 0, true);
-
-// command system    
-  node->tie("mode", SGRawValueMethods<GPS, const char*>(*this, &GPS::getMode, NULL));
-  node->tie("command", SGRawValueMethods<GPS, const char*>(*this, &GPS::getCommand, &GPS::setCommand));
-    
-  _scratchNode = node->getChild("scratch", 0, true);
-  tieSGGeod(_scratchNode, _scratchPos, "longitude-deg", "latitude-deg", "altitude-ft");
-  _scratchNode->tie("valid", SGRawValueMethods<GPS, bool>(*this, &GPS::getScratchValid, NULL));
-  _scratchNode->tie("distance-nm", SGRawValueMethods<GPS, double>(*this, &GPS::getScratchDistance, NULL));
-  _scratchNode->tie("true-bearing-deg", SGRawValueMethods<GPS, double>(*this, &GPS::getScratchTrueBearing, NULL));
-  _scratchNode->tie("mag-bearing-deg", SGRawValueMethods<GPS, double>(*this, &GPS::getScratchMagBearing, NULL));
-  _scratchNode->tie("has-next", SGRawValueMethods<GPS, bool>(*this, &GPS::getScratchHasNext, NULL));
-  _scratchValid = false;
-  
-// waypoint data (including various historical things)
-  SGPropertyNode *wp_node = node->getChild("wp", 0, true);
-  SGPropertyNode *wp0_node = wp_node->getChild("wp", 0, true);
+  _raim_node = _gpsNode->getChild("raim", 0, true);
+  _odometer_node = _gpsNode->getChild("odometer", 0, true);
+  _trip_odometer_node = _gpsNode->getChild("trip-odometer", 0, true);
+  _true_bug_error_node = _gpsNode->getChild("true-bug-error-deg", 0, true);
+  _magnetic_bug_error_node = _gpsNode->getChild("magnetic-bug-error-deg", 0, true);
+  
+// waypoints
+  SGPropertyNode *wp_node = _gpsNode->getChild("wp", 0, true);
   SGPropertyNode *wp1_node = wp_node->getChild("wp", 1, true);
 
-  tieSGGeodReadOnly(wp0_node, _wp0_position, "longitude-deg", "latitude-deg", "altitude-ft");
-  wp0_node->tie("ID", SGRawValueMethods<GPS, const char*>
-    (*this, &GPS::getWP0Ident, NULL));
-  wp0_node->tie("name", SGRawValueMethods<GPS, const char*>
-    (*this, &GPS::getWP0Name, NULL));
-    
-  tieSGGeodReadOnly(wp1_node, _wp1_position, "longitude-deg", "latitude-deg", "altitude-ft");
-  wp1_node->tie("ID", SGRawValueMethods<GPS, const char*>
-    (*this, &GPS::getWP1Ident, NULL));
-  wp1_node->tie("name", SGRawValueMethods<GPS, const char*>
-    (*this, &GPS::getWP1Name, NULL));
-
   // for compatability, alias selected course down to wp/wp[1]/desired-course-deg
   SGPropertyNode* wp1Crs = wp1_node->getChild("desired-course-deg", 0, true);
-  wp1Crs->alias(node->getChild("selected-course-deg"));
+  wp1Crs->alias(_gpsNode->getChild("selected-course-deg"));
     
 //    _true_wp1_bearing_error_node =
 //        wp1_node->getChild("true-bearing-error-deg", 0, true);
 //    _magnetic_wp1_bearing_error_node =
   //      wp1_node->getChild("magnetic-bearing-error-deg", 0, true);
 
-  wp1_node->tie("distance-nm", SGRawValueMethods<GPS, double>
-    (*this, &GPS::getWP1Distance, NULL));
-  wp1_node->tie("bearing-true-deg", SGRawValueMethods<GPS, double>
-    (*this, &GPS::getWP1Bearing, NULL));
-  wp1_node->tie("bearing-mag-deg", SGRawValueMethods<GPS, double>
-    (*this, &GPS::getWP1MagBearing, NULL));
-  wp1_node->tie("TTW-sec", SGRawValueMethods<GPS, double>
-    (*this, &GPS::getWP1TTW, NULL));
-  wp1_node->tie("TTW", SGRawValueMethods<GPS, const char*>
-    (*this, &GPS::getWP1TTWString, NULL));
-  
-  wp1_node->tie("course-deviation-deg", SGRawValueMethods<GPS, double>
-    (*this, &GPS::getWP1CourseDeviation, NULL));
-  wp1_node->tie("course-error-nm", SGRawValueMethods<GPS, double>
-    (*this, &GPS::getWP1CourseErrorNm, NULL));
-  wp1_node->tie("to-flag", SGRawValueMethods<GPS, bool>
-    (*this, &GPS::getWP1ToFlag, NULL));
-  wp1_node->tie("from-flag", SGRawValueMethods<GPS, bool>
-    (*this, &GPS::getWP1FromFlag, NULL));
-    
-  _tracking_bug_node = node->getChild("tracking-bug", 0, true);
+  _tracking_bug_node = _gpsNode->getChild("tracking-bug", 0, true);
          
-// leg properties (only valid in DTO/LEG modes, not OBS)
-  wp_node->tie("leg-distance-nm", SGRawValueMethods<GPS, double>(*this, &GPS::getLegDistance, NULL));
-  wp_node->tie("leg-true-course-deg", SGRawValueMethods<GPS, double>(*this, &GPS::getLegCourse, NULL));
-  wp_node->tie("leg-mag-course-deg", SGRawValueMethods<GPS, double>(*this, &GPS::getLegMagCourse, NULL));
-  wp_node->tie("alt-dist-ratio", SGRawValueMethods<GPS, double>(*this, &GPS::getAltDistanceRatio, NULL));
-
 // reference navid
-  SGPropertyNode_ptr ref_navaid = node->getChild("ref-navaid", 0, true);
+  SGPropertyNode_ptr ref_navaid = _gpsNode->getChild("ref-navaid", 0, true);
   _ref_navaid_id_node = ref_navaid->getChild("id", 0, true);
   _ref_navaid_name_node = ref_navaid->getChild("name", 0, true);
   _ref_navaid_bearing_node = ref_navaid->getChild("bearing-deg", 0, true);
@@ -417,8 +326,8 @@ GPS::init ()
     
 // route properties    
   // should these move to the route manager?
-  _routeDistanceNm = node->getChild("route-distance-nm", 0, true);
-  _routeETE = node->getChild("ETE", 0, true);
+  _routeDistanceNm = _gpsNode->getChild("route-distance-nm", 0, true);
+  _routeETE = _gpsNode->getChild("ETE", 0, true);
   _routeEditedSignal = fgGetNode("/autopilot/route-manager/signals/edited", true);
   _routeFinishedSignal = fgGetNode("/autopilot/route-manager/signals/finished", true);
   
@@ -433,16 +342,12 @@ GPS::init ()
   _routeFinishedSignal->addChangeListener(_listener);
   
 // navradio slaving properties  
-  node->tie("cdi-deflection", SGRawValueMethods<GPS,double>
-    (*this, &GPS::getCDIDeflection));
-
-  SGPropertyNode* toFlag = node->getChild("to-flag", 0, true);
+  SGPropertyNode* toFlag = _gpsNode->getChild("to-flag", 0, true);
   toFlag->alias(wp1_node->getChild("to-flag"));
   
-  SGPropertyNode* fromFlag = node->getChild("from-flag", 0, true);
+  SGPropertyNode* fromFlag = _gpsNode->getChild("from-flag", 0, true);
   fromFlag->alias(wp1_node->getChild("from-flag"));
     
-        
 // autopilot drive properties
   _apTrueHeading = fgGetNode("/autopilot/settings/true-heading-deg",true);
   _apTargetAltitudeFt = fgGetNode("/autopilot/settings/target-altitude-ft", true);
@@ -455,11 +360,101 @@ GPS::init ()
   }
   
   // last thing, add the deprecated prop watcher
-  new DeprecatedPropListener(node);
+  new DeprecatedPropListener(_gpsNode);
   
   clearOutput();
 }
 
+void
+GPS::bind()
+{
+  _config.bind(this, _gpsNode->getChild("config", 0, true));
+// basic GPS outputs
+  tie(_gpsNode, "selected-course-deg", SGRawValueMethods<GPS, double>
+    (*this, &GPS::getSelectedCourse, NULL));
+  
+  
+  tieSGGeodReadOnly(_gpsNode, _indicated_pos, "indicated-longitude-deg", 
+        "indicated-latitude-deg", "indicated-altitude-ft");
+
+  tie(_gpsNode, "indicated-vertical-speed", SGRawValueMethods<GPS, double>
+    (*this, &GPS::getVerticalSpeed, NULL));
+  tie(_gpsNode, "indicated-track-true-deg", SGRawValueMethods<GPS, double>
+    (*this, &GPS::getTrueTrack, NULL));
+  tie(_gpsNode, "indicated-track-magnetic-deg", SGRawValueMethods<GPS, double>
+    (*this, &GPS::getMagTrack, NULL));
+  tie(_gpsNode, "indicated-ground-speed-kt", SGRawValueMethods<GPS, double>
+    (*this, &GPS::getGroundspeedKts, NULL));
+  
+// command system    
+  tie(_gpsNode, "mode", SGRawValueMethods<GPS, const char*>(*this, &GPS::getMode, NULL));
+  tie(_gpsNode, "command", SGRawValueMethods<GPS, const char*>(*this, &GPS::getCommand, &GPS::setCommand));
+    
+  tieSGGeod(_scratchNode, _scratchPos, "longitude-deg", "latitude-deg", "altitude-ft");
+  tie(_scratchNode, "valid", SGRawValueMethods<GPS, bool>(*this, &GPS::getScratchValid, NULL));
+  tie(_scratchNode, "distance-nm", SGRawValueMethods<GPS, double>(*this, &GPS::getScratchDistance, NULL));
+  tie(_scratchNode, "true-bearing-deg", SGRawValueMethods<GPS, double>(*this, &GPS::getScratchTrueBearing, NULL));
+  tie(_scratchNode, "mag-bearing-deg", SGRawValueMethods<GPS, double>(*this, &GPS::getScratchMagBearing, NULL));
+  tie(_scratchNode, "has-next", SGRawValueMethods<GPS, bool>(*this, &GPS::getScratchHasNext, NULL));
+  _scratchValid = false;
+  
+// waypoint data (including various historical things)
+  SGPropertyNode *wp_node = _gpsNode->getChild("wp", 0, true);
+  SGPropertyNode *wp0_node = wp_node->getChild("wp", 0, true);
+  SGPropertyNode *wp1_node = wp_node->getChild("wp", 1, true);
+
+  tieSGGeodReadOnly(wp0_node, _wp0_position, "longitude-deg", "latitude-deg", "altitude-ft");
+  tie(wp0_node, "ID", SGRawValueMethods<GPS, const char*>
+    (*this, &GPS::getWP0Ident, NULL));
+  tie(wp0_node, "name", SGRawValueMethods<GPS, const char*>
+    (*this, &GPS::getWP0Name, NULL));
+    
+  tieSGGeodReadOnly(wp1_node, _wp1_position, "longitude-deg", "latitude-deg", "altitude-ft");
+  tie(wp1_node, "ID", SGRawValueMethods<GPS, const char*>
+    (*this, &GPS::getWP1Ident, NULL));
+  tie(wp1_node, "name", SGRawValueMethods<GPS, const char*>
+    (*this, &GPS::getWP1Name, NULL));
+  
+  tie(wp1_node, "distance-nm", SGRawValueMethods<GPS, double>
+    (*this, &GPS::getWP1Distance, NULL));
+  tie(wp1_node, "bearing-true-deg", SGRawValueMethods<GPS, double>
+    (*this, &GPS::getWP1Bearing, NULL));
+  tie(wp1_node, "bearing-mag-deg", SGRawValueMethods<GPS, double>
+    (*this, &GPS::getWP1MagBearing, NULL));
+  tie(wp1_node, "TTW-sec", SGRawValueMethods<GPS, double>
+    (*this, &GPS::getWP1TTW, NULL));
+  tie(wp1_node, "TTW", SGRawValueMethods<GPS, const char*>
+    (*this, &GPS::getWP1TTWString, NULL));
+  
+  tie(wp1_node, "course-deviation-deg", SGRawValueMethods<GPS, double>
+    (*this, &GPS::getWP1CourseDeviation, NULL));
+  tie(wp1_node, "course-error-nm", SGRawValueMethods<GPS, double>
+    (*this, &GPS::getWP1CourseErrorNm, NULL));
+  tie(wp1_node, "to-flag", SGRawValueMethods<GPS, bool>
+    (*this, &GPS::getWP1ToFlag, NULL));
+  tie(wp1_node, "from-flag", SGRawValueMethods<GPS, bool>
+    (*this, &GPS::getWP1FromFlag, NULL));
+
+// leg properties (only valid in DTO/LEG modes, not OBS)
+  tie(wp_node, "leg-distance-nm", SGRawValueMethods<GPS, double>(*this, &GPS::getLegDistance, NULL));
+  tie(wp_node, "leg-true-course-deg", SGRawValueMethods<GPS, double>(*this, &GPS::getLegCourse, NULL));
+  tie(wp_node, "leg-mag-course-deg", SGRawValueMethods<GPS, double>(*this, &GPS::getLegMagCourse, NULL));
+  tie(wp_node, "alt-dist-ratio", SGRawValueMethods<GPS, double>(*this, &GPS::getAltDistanceRatio, NULL));
+
+// navradio slaving properties  
+  tie(_gpsNode, "cdi-deflection", SGRawValueMethods<GPS,double>
+    (*this, &GPS::getCDIDeflection));
+}
+
+void
+GPS::unbind()
+{
+  for (unsigned int t=0; t<_tiedNodes.size(); ++t) {
+    _tiedNodes[t]->untie();
+  }
+  _tiedNodes.clear();
+}
+
 void
 GPS::clearOutput()
 {
@@ -1008,8 +1003,12 @@ void GPS::driveAutopilot()
     return;
   }
  
-  // FIXME: we want to set desired track, not heading, here
-  _apTrueHeading->setDoubleValue(getWP1Bearing());
+  // compatability feature - allow the route-manager / GPS to drive the
+  // generic autopilot heading hold *in leg mode only* 
+  if (_mode == "leg") {
+    // FIXME: we want to set desired track, not heading, here
+    _apTrueHeading->setDoubleValue(getWP1Bearing());
+  }
 }
 
 void GPS::wp1Changed()
@@ -1386,7 +1385,7 @@ void GPS::loadRouteWaypoint()
   int index = _scratchNode->getIntValue("index", -9999);
   clearScratch();
   
-  if (index == -9999) { // no index supplied, use current wp
+  if ((index < 0) || (index >= _routeMgr->size())) { // no index supplied, use current wp
     index = _routeMgr->currentWaypoint();
   }
   
@@ -1428,10 +1427,15 @@ void GPS::loadNearest()
   int limitCount = _scratchNode->getIntValue("max-results", 1);
   double cutoffDistance = _scratchNode->getDoubleValue("cutoff-nm", 400.0);
   
-  clearScratch(); // clear now, regardless of whether we find a match or not
+  SGGeod searchPos = _indicated_pos;
+  if (isScratchPositionValid()) {
+    searchPos = _scratchPos;
+  }
   
+  clearScratch(); // clear now, regardless of whether we find a match or not
+    
   _searchResults = 
-    FGPositioned::findClosestN(_indicated_pos, limitCount, cutoffDistance, f.get());
+    FGPositioned::findClosestN(searchPos, limitCount, cutoffDistance, f.get());
   _searchResultsCached = true;
   _searchResultIndex = 0;
   _searchIsRoute = false;
@@ -1763,4 +1767,26 @@ void GPS::removeWaypointAtIndex(int aIndex)
   _routeMgr->pop_waypoint(aIndex);
 }
 
+void GPS::tieSGGeod(SGPropertyNode* aNode, SGGeod& aRef, 
+  const char* lonStr, const char* latStr, const char* altStr)
+{
+  tie(aNode, lonStr, SGRawValueMethods<SGGeod, double>(aRef, &SGGeod::getLongitudeDeg, &SGGeod::setLongitudeDeg));
+  tie(aNode, latStr, SGRawValueMethods<SGGeod, double>(aRef, &SGGeod::getLatitudeDeg, &SGGeod::setLatitudeDeg));
+  
+  if (altStr) {
+    tie(aNode, altStr, SGRawValueMethods<SGGeod, double>(aRef, &SGGeod::getElevationFt, &SGGeod::setElevationFt));
+  }
+}
+
+void GPS::tieSGGeodReadOnly(SGPropertyNode* aNode, SGGeod& aRef, 
+  const char* lonStr, const char* latStr, const char* altStr)
+{
+  tie(aNode, lonStr, SGRawValueMethods<SGGeod, double>(aRef, &SGGeod::getLongitudeDeg, NULL));
+  tie(aNode, latStr, SGRawValueMethods<SGGeod, double>(aRef, &SGGeod::getLatitudeDeg, NULL));
+  
+  if (altStr) {
+    tie(aNode, altStr, SGRawValueMethods<SGGeod, double>(aRef, &SGGeod::getElevationFt, NULL));
+  }
+}
+
 // end of gps.cxx
index e745418d6c251d5d8022f9abf3a48272b2e88ed2..76596773a7a5eb4e769e75c24c9be6d8f8da617d 100644 (file)
@@ -82,7 +82,9 @@ public:
 
     virtual void init ();
     virtual void update (double delta_time_sec);
-
+    
+    virtual void bind();
+    virtual void unbind();
 private:
     friend class GPSListener;
     friend class SearchFilter;
@@ -94,8 +96,8 @@ private:
     {
     public:
       Config();
-      
-      void init(SGPropertyNode*);
+            
+      void bind(GPS* aOwner, SGPropertyNode* aCfg);
       
       bool turnAnticipationEnabled() const
       { return _enableTurnAnticipation; }
@@ -307,9 +309,28 @@ private:
   
   // true-bearing-error and mag-bearing-error
   
+
+  /**
+   * Tied-properties helper, record nodes which are tied for easy un-tie-ing
+   */
+  template <typename T>
+  void tie(SGPropertyNode* aNode, const char* aRelPath, const SGRawValue<T>& aRawValue)
+  {
+    SGPropertyNode* nd = aNode->getNode(aRelPath, true);
+    _tiedNodes.push_back(nd);
+    nd->tie(aRawValue);
+  }
+
+  /// helper, tie the lat/lon/elev of a SGGeod to the named children of aNode
+  void tieSGGeod(SGPropertyNode* aNode, SGGeod& aRef, 
+    const char* lonStr, const char* latStr, const char* altStr);
   
-  
+  /// helper, tie a SGGeod to proeprties, but read-only
+  void tieSGGeodReadOnly(SGPropertyNode* aNode, SGGeod& aRef, 
+    const char* lonStr, const char* latStr, const char* altStr);
+
 // members
+  SGPropertyNode_ptr _gpsNode;
   SGPropertyNode_ptr _magvar_node;
   SGPropertyNode_ptr _serviceable_node;
   SGPropertyNode_ptr _electrical_node;
@@ -397,6 +418,8 @@ private:
   SGPropertyNode_ptr _apTrueHeading;
   SGPropertyNode_ptr _apTargetAltitudeFt;
   SGPropertyNode_ptr _apAltitudeLock;
+  
+  std::vector<SGPropertyNode*> _tiedNodes;
 };
 
 
index 40a33844bd6a0f3689d762f7ae7120b80e8087c9..7a8215f3eb9dc53fae787a46dd29b1941421189a 100644 (file)
@@ -94,56 +94,6 @@ FGNavRadio::FGNavRadio(SGPropertyNode *node) :
     lon_node(fgGetNode("/position/longitude-deg", true)),
     lat_node(fgGetNode("/position/latitude-deg", true)),
     alt_node(fgGetNode("/position/altitude-ft", true)),
-    is_valid_node(NULL),
-    power_btn_node(NULL),
-    freq_node(NULL),
-    alt_freq_node(NULL),
-    sel_radial_node(NULL),
-    vol_btn_node(NULL),
-    ident_btn_node(NULL),
-    audio_btn_node(NULL),
-    backcourse_node(NULL),
-    nav_serviceable_node(NULL),
-    cdi_serviceable_node(NULL),
-    gs_serviceable_node(NULL),
-    tofrom_serviceable_node(NULL),
-    dme_serviceable_node(NULL),
-    fmt_freq_node(NULL),
-    fmt_alt_freq_node(NULL),
-    heading_node(NULL),
-    radial_node(NULL),
-    recip_radial_node(NULL),
-    target_radial_true_node(NULL),
-    target_auto_hdg_node(NULL),
-    time_to_intercept(NULL),
-    to_flag_node(NULL),
-    from_flag_node(NULL),
-    inrange_node(NULL),
-    signal_quality_norm_node(NULL),
-    cdi_deflection_node(NULL),
-    cdi_deflection_norm_node(NULL),
-    cdi_xtrack_error_node(NULL),
-    cdi_xtrack_hdg_err_node(NULL),
-    has_gs_node(NULL),
-    loc_node(NULL),
-    loc_dist_node(NULL),
-    gs_deflection_node(NULL),
-    gs_deflection_deg_node(NULL),
-    gs_deflection_norm_node(NULL),
-    gs_rate_of_climb_node(NULL),
-    gs_dist_node(NULL),
-    gs_inrange_node(NULL),
-    nav_id_node(NULL),
-    id_c1_node(NULL),
-    id_c2_node(NULL),
-    id_c3_node(NULL),
-    id_c4_node(NULL),
-    nav_slaved_to_gps_node(NULL),
-    gps_cdi_deflection_node(NULL),
-    gps_to_flag_node(NULL),
-    gps_from_flag_node(NULL),
-    gps_has_gs_node(NULL),
-    gps_xtrack_error_nm_node(NULL),
     play_count(0),
     last_time(0),
     target_radial(0.0),
@@ -156,7 +106,6 @@ FGNavRadio::FGNavRadio(SGPropertyNode *node) :
     _name(node->getStringValue("name", "nav")),
     _num(node->getIntValue("number", 0)),
     _time_before_search_sec(-1.0),
-    _falseCoursesEnabled(true),
     _sgr(NULL)
 {
     SGPath path( globals->get_fg_root() );
@@ -170,6 +119,10 @@ FGNavRadio::FGNavRadio(SGPropertyNode *node) :
     term_tbl = new SGInterpTable( term.str() );
     low_tbl = new SGInterpTable( low.str() );
     high_tbl = new SGInterpTable( high.str() );
+    
+    
+    string branch("/instrumentation/" + _name);
+    _radio_node = fgGetNode(branch.c_str(), _num, true);
 }
 
 
@@ -191,11 +144,7 @@ FGNavRadio::init ()
 
     morse.init();
 
-    string branch;
-    branch = "/instrumentation/" + _name;
-
-    SGPropertyNode *node = fgGetNode(branch.c_str(), _num, true );
-
+    SGPropertyNode* node = _radio_node.get();
     bus_power_node = 
        fgGetNode(("/systems/electrical/outputs/" + _name).c_str(), true);
 
@@ -217,9 +166,14 @@ FGNavRadio::init ()
     tofrom_serviceable_node = createServiceableProp(node, "to-from");
     dme_serviceable_node = createServiceableProp(node, "dme");
     
-    globals->get_props()->tie("sim/realism/false-radio-courses-enabled", 
-      SGRawValuePointer<bool>(&_falseCoursesEnabled));
-    
+    falseCoursesEnabledNode = 
+      fgGetNode("/sim/realism/false-radio-courses-enabled");
+    if (!falseCoursesEnabledNode) {
+      falseCoursesEnabledNode = 
+        fgGetNode("/sim/realism/false-radio-courses-enabled", true);
+      falseCoursesEnabledNode->setBoolValue(true);
+    }
+
     // frequencies
     SGPropertyNode *subnode = node->getChild("frequencies", 0, true);
     freq_node = subnode->getChild("selected-mhz", 0, true);
@@ -263,8 +217,6 @@ FGNavRadio::init ()
     id_c3_node = node->getChild("nav-id_asc3", 0, true);
     id_c4_node = node->getChild("nav-id_asc4", 0, true);
 
-    node->tie("dme-in-range", SGRawValuePointer<bool>(&_dmeInRange));
-        
     // gps slaving support
     nav_slaved_to_gps_node = node->getChild("slaved-to-gps", 0, true);
     gps_cdi_deflection_node = fgGetNode("/instrumentation/gps/cdi-deflection", true);
@@ -285,13 +237,18 @@ FGNavRadio::init ()
 void
 FGNavRadio::bind ()
 {
-  
+  tie("dme-in-range", SGRawValuePointer<bool>(&_dmeInRange));
+  tie("operable", SGRawValueMethods<FGNavRadio, bool>(*this, &FGNavRadio::isOperable, NULL));
 }
 
 
 void
 FGNavRadio::unbind ()
 {
+  for (unsigned int t=0; t<_tiedNodes.size(); ++t) {
+    _tiedNodes[t]->untie();
+  }
+  _tiedNodes.clear();
 }
 
 
@@ -383,7 +340,8 @@ FGNavRadio::update(double dt)
   if (power_btn_node->getBoolValue() 
       && (bus_power_node->getDoubleValue() > 1.0)
       && nav_serviceable_node->getBoolValue() )
-  {   
+  {
+    _operable = true;
     if (nav_slaved_to_gps_node->getBoolValue()) {
       updateGPSSlaved();
     } else {
@@ -415,6 +373,7 @@ void FGNavRadio::clearOutputs()
   from_flag_node->setBoolValue( false );
   
   _dmeInRange = false;
+  _operable = false;
 }
 
 void FGNavRadio::updateReceiver(double dt)
@@ -532,7 +491,7 @@ void FGNavRadio::updateReceiver(double dt)
   SG_NORMALIZE_RANGE(r, -180.0, 180.0);
   
   if ( is_loc ) {
-    if (_falseCoursesEnabled) {
+    if (falseCoursesEnabledNode->getBoolValue()) {
       // The factor of 30.0 gives a period of 120 which gives us 3 cycles and six 
       // zeros i.e. six courses: one front course, one back course, and four 
       // false courses. Three of the six are reverse sensing.
@@ -604,7 +563,7 @@ void FGNavRadio::updateGlideSlope(double dt, const SGVec3d& aircraft, double sig
   double angle = atan2(dot_v, dot_h) * SGD_RADIANS_TO_DEGREES;
   double deflectionAngle = target_gs - angle;
   
-  if (_falseCoursesEnabled) {
+  if (falseCoursesEnabledNode->getBoolValue()) {
     // Construct false glideslopes.  The scale factor of 1.5 
     // in the sawtooth gives a period of 6 degrees.
     // There will be zeros at 3, 6r, 9, 12r et cetera
index 88f67685a1f5b77f348c28d2cdb72a2309bc8d62..fe724a3bd4c1abd784286d678310db93b305094e 100644 (file)
@@ -47,6 +47,7 @@ class FGNavRadio : public SGSubsystem
     SGInterpTable *low_tbl;
     SGInterpTable *high_tbl;
 
+    SGPropertyNode_ptr _radio_node;
     SGPropertyNode_ptr lon_node;
     SGPropertyNode_ptr lat_node;
     SGPropertyNode_ptr alt_node;
@@ -120,8 +121,12 @@ class FGNavRadio : public SGSubsystem
     SGPropertyNode_ptr gps_xtrack_error_nm_node;
     SGPropertyNode_ptr _magvarNode;
     
+    // realism setting, are false courses and GS lobes enabled?
+    SGPropertyNode_ptr falseCoursesEnabledNode;
+
     // internal (private) values
 
+    bool _operable; ///< is the unit serviceable, on, powered, etc
     int play_count;
     time_t last_time;
     FGNavRecordPtr _navaid;
@@ -161,10 +166,8 @@ class FGNavRadio : public SGSubsystem
     double _gsNeedleDeflection;
     double _gsNeedleDeflectionNorm;
     
-    // realism setting, are false courses and GS lobes enabled?
-    bool _falseCoursesEnabled;
-
     SGSharedPtr<SGSampleGroup> _sgr;
+    std::vector<SGPropertyNode*> _tiedNodes;
     
     bool updateWithPower(double aDt);
 
@@ -193,6 +196,21 @@ class FGNavRadio : public SGSubsystem
      */
     double localizerWidth(FGNavRecord* aLOC);
     FGNavRecord* findPrimaryNavaid(const SGGeod& aPos, double aFreqMHz);
+    
+    /// accessor for tied, read-only 'operable' property
+    bool isOperable() const
+      { return _operable; }
+      
+    /**
+     * Tied-properties helper, record nodes which are tied for easy un-tie-ing
+     */
+    template <typename T>
+    void tie(const char* aRelPath, const SGRawValue<T>& aRawValue)
+    {
+      SGPropertyNode* nd = _radio_node->getNode(aRelPath, true);
+      _tiedNodes.push_back(nd);
+      nd->tie(aRawValue);
+    }
 public:
 
     FGNavRadio(SGPropertyNode *node);
index e00ac4f1a98ae8158f1d525e9fd2fc6041f80c4e..b17c8bb346386e06a01cc995085621808b966d48 100755 (executable)
@@ -127,6 +127,9 @@ TACAN::init ()
 void
 TACAN::update (double delta_time_sec)
 {
+    // don't do anything when paused
+    if (delta_time_sec == 0) return;
+
     if (!_serviceable_node->getBoolValue() || !_electrical_node->getBoolValue()) {
         _last_distance_nm = 0;
         _in_range_node->setBoolValue(false);
@@ -237,7 +240,7 @@ TACAN::update (double delta_time_sec)
         }
         _distance_node->setDoubleValue( tmp_dist );
         _speed_node->setDoubleValue(speed_kt);
-        _time_node->setDoubleValue(distance_nm/speed_kt*60.0);
+        _time_node->setDoubleValue(speed_kt > 0 ? (distance_nm/speed_kt*60.0) : 0);
         _bearing_node->setDoubleValue(bearing);
         _x_shift_node->setDoubleValue(x_shift);
         _y_shift_node->setDoubleValue(y_shift);
index 64060041cf2e653bc3b6d26af9b8833e03cdb95c..cc73bded0efbb8468fee3a78dff97603eb7e76c3 100644 (file)
@@ -72,6 +72,21 @@ int main(int argc, char* argv[])
   
   SG_LOG(SG_GENERAL, SG_ALERT, "hello world!");
   
+  const FGAirport* egph = fgFindAirportID("EGPH");
+  SG_LOG(SG_GENERAL, SG_ALERT, "egph: cart location:" << egph->cart());
+  
+  FGAirport::AirportFilter af;
+  FGPositioned::List l = FGPositioned::findClosestN(egph->geod(), 20, 2000.0, &af);
+  for (unsigned int i=0; i<l.size(); ++i) {
+    SG_LOG(SG_GENERAL, SG_ALERT, "\t" << l[i]->ident() << "/" << l[i]->name());
+  }
+  
+  //l = FGPositioned::findWithinRange(egph->geod(), 500.0, &af);
+  //for (unsigned int i=0; i<l.size(); ++i) {
+  //  SG_LOG(SG_GENERAL, SG_ALERT, "\t" << l[i]->ident() << "/" << l[i]->name());
+  //}
+  
+
   FGRouteMgr* rm = new FGRouteMgr;
   globals->add_subsystem( "route-manager", rm );
   
@@ -84,7 +99,7 @@ int main(int argc, char* argv[])
   GPS* gps = new GPS(nd);
   globals->add_subsystem("gps", gps);
   
-  const FGAirport* egph = fgFindAirportID("EGPH");
+  
   testSetPosition(egph->geod());
   
   // startup the route manager
index 066f0262b0e56d85f14bc3df5ed308221a9f7d9d..31f4ae0dac7f5403c1d706349c7ba24d51ff7293 100644 (file)
@@ -85,20 +85,26 @@ LogClassMapping log_class_mappings [] = {
 /**
  * Get the logging classes.
  */
+// XXX Making the result buffer be global is a band-aid that hopefully
+// delays its destruction 'til after its last use.
+namespace
+{
+string loggingResult;
+}
+
 static const char *
 getLoggingClasses ()
 {
   sgDebugClass classes = logbuf::get_log_classes();
-  static string result;
-  result = "";
+  loggingResult.clear();
   for (int i = 0; log_class_mappings[i].c != SG_UNDEFD; i++) {
     if ((classes&log_class_mappings[i].c) > 0) {
-      if (!result.empty())
-       result += '|';
-      result += log_class_mappings[i].name;
+      if (!loggingResult.empty())
+       loggingResult += '|';
+      loggingResult += log_class_mappings[i].name;
     }
   }
-  return result.c_str();
+  return loggingResult.c_str();
 }
 
 
index cfe5f29a398375d357de90ab72bb2b6b1ca735a4..32d7953380b417271fbedd5aead6ba626d1ae3df 100644 (file)
@@ -139,6 +139,7 @@ public:
   {
     // Dynamic stuff, do not store geometry
     setUseDisplayList(false);
+    setDataVariance(Object::DYNAMIC);
 
     osg::StateSet* stateSet = getOrCreateStateSet();
     stateSet->setRenderBinDetails(1001, "RenderBin");
@@ -184,6 +185,7 @@ public:
   {
     // Dynamic stuff, do not store geometry
     setUseDisplayList(false);
+    setDataVariance(Object::DYNAMIC);
 
     osg::StateSet* stateSet = getOrCreateStateSet();
     stateSet->setRenderBinDetails(1000, "RenderBin");
index 6ac60caaaeb7b41b16b3b70d2526ecd4c6a78e4f..be6889fda55d048d80e0a63044be888fcfbb5d82 100644 (file)
@@ -102,6 +102,11 @@ FGPanelNode::FGPanelNode(SGPropertyNode* props)
         m(1,i) *= 1.0/_ymax;
     }
 
+    _lastViewport[0] = 0;
+    _lastViewport[1] = 0;
+    _lastViewport[2] = 0;
+    _lastViewport[3] = 0;
+
     dirtyBound();
 
     // All done.  Add us to the list
@@ -152,6 +157,11 @@ FGPanelNode::computeBound() const
 
 bool FGPanelNode::doMouseAction(int button, int updown, int x, int y)
 {
+    if (_lastViewport[2] == 0 || _lastViewport[3] == 0) {
+        // we haven't been drawn yet, presumably
+        return false;
+    }
+
     // Covert the screen coordinates to viewport coordinates in the
     // range [0:1], then transform to OpenGL "post projection" coords
     // in [-1:1].  Remember the difference in Y direction!
index f7c0cd8e1733f5502486c699ee0c08ff2ea4a3b9..4f7c10e7f8e89241e3d67d532ee6d7cfac6e0856 100644 (file)
@@ -240,11 +240,10 @@ FGRunway* getRunwayFromName(const std::string& aName)
     return NULL;
   }
   
-  FGRunway* runway = apt->getRunwayByIdent(parts[1]);
-  if (!runway) {
+  if (!apt->hasRunwayWithIdent(parts[1])) {
     SG_LOG(SG_GENERAL, SG_WARN, "navaid " << aName << " associated with bogus runway ID:" << parts[1]);
     return NULL;
   }
 
-  return runway;
+  return apt->getRunwayByIdent(parts[1]);
 }
index 6bb71a54c9d2e40e3506b4e17e451b910e80fd94..e75339804036ea7fbb73b929a1b64fe15d3184e6 100644 (file)
@@ -91,6 +91,9 @@ void FGNavRecord::initAirportRelation()
   }
   
   mRunway = getRunwayFromName(_name);  
+  if (!mRunway) {
+    return;
+  }
   
   if (type() != GS) {
     readAirportSceneryData();
index f3e683fb9e48f7b0a1163f4fbaf6bb86bf1342a2..bbd0443063330acacd69b9d72c22a3cc7088f3cb 100644 (file)
@@ -25,9 +25,7 @@
 #include <map>
 #include <set>
 #include <algorithm> // for sort
-#include <locale> // for char-traits toupper
-
-#include <iostream>
+#include <queue>
 
 #include <boost/algorithm/string/case_conv.hpp>
 #include <boost/algorithm/string/predicate.hpp>
@@ -36,6 +34,7 @@
 #include <simgear/timing/timestamp.hxx>
 #include <simgear/debug/logstream.hxx>
 #include <simgear/structure/exception.hxx>
+#include <simgear/math/SGBox.hxx>
 
 #include "positioned.hxx"
 
@@ -45,62 +44,351 @@ typedef std::pair<NamedPositionedIndex::const_iterator, NamedPositionedIndex::co
 using std::lower_bound;
 using std::upper_bound;
 
+static NamedPositionedIndex global_identIndex;
+static NamedPositionedIndex global_nameIndex;
+
+//////////////////////////////////////////////////////////////////////////////
+
+namespace Octree
+{
+
+const double LEAF_SIZE = SG_NM_TO_METER * 8.0;
+const double LEAF_SIZE_SQR = LEAF_SIZE * LEAF_SIZE;
+
+typedef SGBox<double> SGBoxd;
+
+template<typename T1, typename T2>
+inline bool
+intersects(const SGVec3<T1>& v, const SGBox<T2>& box)
+{
+  if (v[0] < box.getMin()[0])
+    return false;
+  if (box.getMax()[0] < v[0])
+    return false;
+  if (v[1] < box.getMin()[1])
+    return false;
+  if (box.getMax()[1] < v[1])
+    return false;
+  if (v[2] < box.getMin()[2])
+    return false;
+  if (box.getMax()[2] < v[2])
+    return false;
+  return true;
+}
+
 /**
- * Order positioned elements by type, then pointer address. This allows us to
- * use range searches (lower_ and upper_bound) to grab items of a particular
- * type out of bucket efficently.
+ * Decorate an object with a double value, and use that value to order 
+ * items, for the purpoises of the STL algorithms
  */
-class OrderByType
+template <class T>
+class Ordered
 {
 public:
-  bool operator()(const FGPositioned* a, const FGPositioned* b) const
-  {
-    if (a->type() == b->type()) return a < b;
-    return a->type() < b->type();
-  }
+    Ordered(const T& v, double x) :
+        _order(x),
+        _inner(v)
+    {
+    }
+    
+    Ordered(const Ordered<T>& a) :
+        _order(a._order),
+        _inner(a._inner)
+    {
+    }
+    
+    Ordered<T>& operator=(const Ordered<T>& a)
+    {
+        _order = a._order;
+        _inner = a._inner;
+        return *this;
+    }
+    
+    bool operator<(const Ordered<T>& other) const
+    {
+        return _order < other._order;
+    }
+    
+    bool operator>(const Ordered<T>& other) const
+    {
+        return _order > other._order;
+    }
+    
+    const T& get() const
+        { return _inner; }
+    
+    double order() const
+        { return _order; }
+    
+private:    
+    double _order;
+    T _inner;
 };
 
-class LowerLimitOfType
+class Node;
+typedef Ordered<Node*> OrderedNode;
+typedef std::greater<OrderedNode> FNPQCompare; 
+
+/**
+ * the priority queue is fundamental to our search algorithm. When searching,
+ * we know the front of the queue is the nearest unexpanded node (to the search
+ * location). The default STL pqueue returns the 'largest' item from top(), so
+ * to get the smallest, we need to replace the default Compare functor (less<>)
+ * with greater<>.
+ */
+typedef std::priority_queue<OrderedNode, std::vector<OrderedNode>, FNPQCompare> FindNearestPQueue;
+
+typedef Ordered<FGPositioned*> OrderedPositioned;
+typedef std::vector<OrderedPositioned> FindNearestResults;
+
+Node* global_spatialOctree = NULL;
+
+/**
+ * Octree node base class, tracks its bounding box and provides various
+ * queries relating to it
+ */
+class Node
 {
 public:
-  bool operator()(const FGPositioned* a, const FGPositioned::Type b) const
-  {
-    return a->type() < b;
-  }
-
-  bool operator()(const FGPositioned::Type a, const FGPositioned* b) const
-  {
-    return a < b->type();
-  }
+    bool contains(const SGVec3d& aPos) const
+    {
+        return intersects(aPos, _box);
+    }
 
-  // The operator below is required by VS2005 in debug mode
-  bool operator()(const FGPositioned* a, const FGPositioned* b) const
-  {
-    return a->type() < b->type();
-  }
+    double distSqrToNearest(const SGVec3d& aPos) const
+    {
+        return distSqr(aPos, getClosestPoint(aPos));
+    }
+    
+    virtual void insert(FGPositioned* aP) = 0;
+    
+    SGVec3d getClosestPoint(const SGVec3d& aPos) const
+    {
+      SGVec3d r;
+      
+      for (unsigned int i=0;i<3; ++i) {
+        if (aPos[i] < _box.getMin()[i]) {
+          r[i] = _box.getMin()[i];
+        } else if (aPos[i] > _box.getMax()[i]) {
+          r[i] = _box.getMax()[i];
+        } else {
+          r[i] = aPos[i];
+        }
+      } // of axis iteration
+      
+      return r;
+    }
+    
+    virtual void visit(const SGVec3d& aPos, double aCutoff, 
+      FGPositioned::Filter* aFilter, 
+      FindNearestResults& aResults, FindNearestPQueue&) = 0;
+protected:
+    Node(const SGBoxd &aBox) :
+        _box(aBox)
+    {
+    }
+    
+    const SGBoxd _box;
 };
 
+class Leaf : public Node
+{
+public:
+    Leaf(const SGBoxd& aBox) :
+        Node(aBox)
+    {
+    }
+    
+    const FGPositioned::List& members() const
+    { return _members; }
+    
+    virtual void insert(FGPositioned* aP)
+    {
+        _members.push_back(aP);
+    }
+    
+    virtual void visit(const SGVec3d& aPos, double aCutoff, 
+      FGPositioned::Filter* aFilter, 
+      FindNearestResults& aResults, FindNearestPQueue&)
+    {
+        int previousResultsSize = aResults.size();
+        int addedCount = 0;
+        
+        for (unsigned int i=0; i<_members.size(); ++i) {
+            FGPositioned* p = _members[i];
+            double d2 = distSqr(aPos, p->cart());
+            if (d2 > aCutoff) {
+                continue;
+            }
+            
+            if (aFilter) {
+              if (aFilter->hasTypeRange() && !aFilter->passType(p->type())) {
+                continue;
+              }
+      
+              if (!aFilter->pass(p)) {
+                continue;
+              }
+            } // of have a filter
 
-typedef std::set<FGPositioned*, OrderByType> BucketEntry;
-typedef std::map<long int, BucketEntry> SpatialPositionedIndex;
+            ++addedCount;
+            aResults.push_back(OrderedPositioned(p, d2));
+        }
+        
+        if (addedCount == 0) {
+          return;
+        }
+        
+      // keep aResults sorted
+        // sort the new items, usually just one or two items
+        std::sort(aResults.begin() + previousResultsSize, aResults.end());
+        
+        // merge the two sorted ranges together - in linear time
+        std::inplace_merge(aResults.begin(), 
+          aResults.begin() + previousResultsSize, aResults.end());
+      }
+private:
+    FGPositioned::List _members;
+};
 
-static NamedPositionedIndex global_identIndex;
-static NamedPositionedIndex global_nameIndex;
-static SpatialPositionedIndex global_spatialIndex;
+class Branch : public Node
+{
+public:
+    Branch(const SGBoxd& aBox) :
+        Node(aBox)
+    {
+        memset(children, 0, sizeof(Node*) * 8);
+    }
+    
+    virtual void insert(FGPositioned* aP)
+    {
+        SGVec3d cart(aP->cart());
+        assert(contains(cart));
+        int childIndex = 0;
+        
+        SGVec3d center(_box.getCenter());
+    // tests must match indices in SGbox::getCorner
+        if (cart.x() < center.x()) {
+            childIndex += 1;
+        }
+        
+        if (cart.y() < center.y()) {
+            childIndex += 2;
+        }
+        
+        if (cart.z() < center.z()) {
+            childIndex += 4;
+        }
+        
+        Node* child = children[childIndex];
+        if (!child) { // lazy building of children
+            SGBoxd cb(boxForChild(childIndex));            
+            double d2 = dot(cb.getSize(), cb.getSize());
+            if (d2 < LEAF_SIZE_SQR) {
+                child = new Leaf(cb);
+            } else {
+                child = new Branch(cb);
+            }
+            
+            children[childIndex] = child; 
+        }
+        
+        child->insert(aP);
+    }
+    
+    virtual void visit(const SGVec3d& aPos, double aCutoff, 
+      FGPositioned::Filter*, 
+      FindNearestResults&, FindNearestPQueue& aQ)
+    {    
+        for (unsigned int i=0; i<8; ++i) {
+            if (!children[i]) {
+                continue;
+            }
+            
+            double d2 = children[i]->distSqrToNearest(aPos);
+            if (d2 > aCutoff) {
+                continue; // exceeded cutoff
+            }
+            
+            aQ.push(Ordered<Node*>(children[i], d2));
+        } // of child iteration
+    }
+    
+    
+private:
+    /**
+     * Return the box for a child touching the specified corner
+     */
+    SGBoxd boxForChild(unsigned int aCorner) const
+    {
+        SGBoxd r(_box.getCenter());
+        r.expandBy(_box.getCorner(aCorner));
+        return r;
+    }
+    
+    Node* children[8];
+};
 
-SpatialPositionedIndex::iterator
-bucketEntryForPositioned(FGPositioned* aPos)
+void findNearestN(const SGVec3d& aPos, unsigned int aN, double aCutoffM, FGPositioned::Filter* aFilter, FGPositioned::List& aResults)
 {
-  int bucketIndex = aPos->bucket().gen_index();
-  SpatialPositionedIndex::iterator it = global_spatialIndex.find(bucketIndex);
-  if (it != global_spatialIndex.end()) {
-    return it;
-  }
-  
-  // create a new BucketEntry
-  return global_spatialIndex.insert(it, std::make_pair(bucketIndex, BucketEntry()));
+    aResults.clear();
+    FindNearestPQueue pq;
+    FindNearestResults results;
+    pq.push(Ordered<Node*>(global_spatialOctree, 0));
+    double cut = aCutoffM * aCutoffM;
+    
+    while (!pq.empty()) {
+        if (!results.empty()) {
+          // terminate the search if we have sufficent results, and we are
+          // sure no node still on the queue contains a closer match
+          double furthestResultOrder = results.back().order();
+          if ((results.size() >= aN) && (furthestResultOrder < pq.top().order())) {
+            break;
+          }
+        }
+        
+        Node* nd = pq.top().get();
+        pq.pop();
+        
+        nd->visit(aPos, cut, aFilter, results, pq);
+    } // of queue iteration
+    
+    // depending on leaf population, we may have (slighty) more results
+    // than requested
+    unsigned int numResults = std::min((unsigned int) results.size(), aN);
+  // copy results out
+    aResults.resize(numResults);
+    for (unsigned int r=0; r<numResults; ++r) {
+      aResults[r] = results[r].get();
+    }
+}
+
+void findAllWithinRange(const SGVec3d& aPos, double aRangeM, FGPositioned::Filter* aFilter, FGPositioned::List& aResults)
+{
+    aResults.clear();
+    FindNearestPQueue pq;
+    FindNearestResults results;
+    pq.push(Ordered<Node*>(global_spatialOctree, 0));
+    double rng = aRangeM * aRangeM;
+    
+    while (!pq.empty()) {
+        Node* nd = pq.top().get();
+        pq.pop();
+        
+        nd->visit(aPos, rng, aFilter, results, pq);
+    } // of queue iteration
+    
+    unsigned int numResults = results.size();
+  // copy results out
+    aResults.resize(numResults);
+    for (unsigned int r=0; r<numResults; ++r) {
+      aResults[r] = results[r].get();
+    }
 }
 
+} // of namespace Octree
+
+//////////////////////////////////////////////////////////////////////////////
+
 static void
 addToIndices(FGPositioned* aPos)
 {
@@ -115,9 +403,12 @@ addToIndices(FGPositioned* aPos)
                              std::make_pair(aPos->name(), aPos));
   }
 
-  
-  SpatialPositionedIndex::iterator it = bucketEntryForPositioned(aPos);
-  it->second.insert(aPos);
+  if (!Octree::global_spatialOctree) {
+    double RADIUS_EARTH_M = 7000 * 1000.0; // 7000km is plenty
+    SGVec3d earthExtent(RADIUS_EARTH_M, RADIUS_EARTH_M, RADIUS_EARTH_M);
+    Octree::global_spatialOctree = new Octree::Branch(SGBox<double>(-earthExtent, earthExtent));
+  }
+  Octree::global_spatialOctree->insert(aPos);
 }
 
 static void
@@ -148,88 +439,6 @@ removeFromIndices(FGPositioned* aPos)
       ++it;
     } // of multimap walk
   }
-
-  SpatialPositionedIndex::iterator sit = bucketEntryForPositioned(aPos);
-  sit->second.erase(aPos);
-}
-
-static void
-spatialFilterInBucket(const SGBucket& aBucket, FGPositioned::Filter* aFilter, FGPositioned::List& aResult)
-{
-  SpatialPositionedIndex::const_iterator it;
-  it = global_spatialIndex.find(aBucket.gen_index());
-  if (it == global_spatialIndex.end()) {
-    return;
-  }
-  
-  BucketEntry::const_iterator l = it->second.begin();
-  BucketEntry::const_iterator u = it->second.end();
-
-  if (!aFilter) { // pass everything
-    aResult.insert(aResult.end(), l, u);
-    return;
-  }
-
-  if (aFilter->hasTypeRange()) {
-    // avoid many calls to the filter hook
-    l = lower_bound(it->second.begin(), it->second.end(), aFilter->minType(), LowerLimitOfType());
-    u = upper_bound(l, it->second.end(), aFilter->maxType(), LowerLimitOfType());
-  }
-
-  for ( ; l != u; ++l) {
-    if ((*aFilter)(*l)) {
-      aResult.push_back(*l);
-    }
-  }
-}
-
-static void
-spatialFind(const SGGeod& aPos, double aRange, 
-  FGPositioned::Filter* aFilter, FGPositioned::List& aResult)
-{
-  SGBucket buck(aPos);
-  double lat = aPos.getLatitudeDeg(),
-    lon = aPos.getLongitudeDeg();
-  
-  int bx = (int)( aRange*SG_NM_TO_METER / buck.get_width_m() / 2);
-  int by = (int)( aRange*SG_NM_TO_METER / buck.get_height_m() / 2 );
-    
-  // loop over bucket range 
-  for ( int i=-bx; i<=bx; i++) {
-    for ( int j=-by; j<=by; j++) {
-      spatialFilterInBucket(sgBucketOffset(lon, lat, i, j), aFilter, aResult);
-    } // of j-iteration
-  } // of i-iteration  
-}
-
-/**
- */
-class RangePredictate
-{
-public:
-  RangePredictate(const SGGeod& aOrigin, double aRange) :
-    mOrigin(SGVec3d::fromGeod(aOrigin)),
-    mRangeSqr(aRange * aRange)
-  { ; }
-  
-  bool operator()(const FGPositionedRef& aPos)
-  {
-    double dSqr = distSqr(aPos->cart(), mOrigin);
-    return (dSqr > mRangeSqr);
-  }
-  
-private:
-  SGVec3d mOrigin;
-  double mRangeSqr;
-};
-
-static void
-filterListByRange(const SGGeod& aPos, double aRange, FGPositioned::List& aResult)
-{
-  RangePredictate pred(aPos, aRange * SG_NM_TO_METER);
-  FGPositioned::List::iterator newEnd; 
-  newEnd = std::remove_if(aResult.begin(), aResult.end(), pred);
-  aResult.erase(newEnd, aResult.end());
 }
 
 class DistanceOrdering
@@ -317,51 +526,6 @@ namedFindClosest(const NamedPositionedIndex& aIndex, const std::string& aName,
   return result;
 }
 
-static FGPositioned::List
-spatialGetClosest(const SGGeod& aPos, unsigned int aN, double aCutoffNm, FGPositioned::Filter* aFilter)
-{
-  FGPositioned::List result;
-  int radius = 1; // start at 1, radius 0 is handled explicitly
-  SGBucket buck;
-  double lat = aPos.getLatitudeDeg(),
-    lon = aPos.getLongitudeDeg();
-  // final cutoff is in metres, and scaled to account for testing the corners
-  // of the 'box' instead of the centre of each edge
-  double cutoffM = aCutoffNm * SG_NM_TO_METER * 1.5;
-  
-  // base case, simplifes loop to do it seperately here
-  spatialFilterInBucket(sgBucketOffset(lon, lat, 0, 0), aFilter, result);
-
-  for (;result.size() < aN; ++radius) {
-    // cutoff check
-    double az1, az2, d1, d2;
-    SGGeodesy::inverse(aPos, sgBucketOffset(lon, lat, -radius, -radius).get_center(), az1, az2, d1);
-    SGGeodesy::inverse(aPos, sgBucketOffset(lon, lat, radius, radius).get_center(), az1, az2, d2);  
-      
-    if ((d1 > cutoffM) && (d2 > cutoffM)) {
-      //std::cerr << "spatialGetClosest terminating due to range cutoff" << std::endl;
-      break;
-    }
-    
-    FGPositioned::List hits;
-    for ( int i=-radius; i<=radius; i++) {
-      spatialFilterInBucket(sgBucketOffset(lon, lat, i, -radius), aFilter, hits);
-      spatialFilterInBucket(sgBucketOffset(lon, lat, -radius, i), aFilter, hits);
-      spatialFilterInBucket(sgBucketOffset(lon, lat, i, radius), aFilter, hits);
-      spatialFilterInBucket(sgBucketOffset(lon, lat, radius, i), aFilter, hits);
-    }
-
-    result.insert(result.end(), hits.begin(), hits.end()); // append
-  } // of outer loop
-  
-  sortByDistance(aPos, result);
-  if (result.size() > aN) {
-    result.resize(aN); // truncate at requested number of matches
-  }
-  
-  return result;
-}
-
 //////////////////////////////////////////////////////////////////////////////
 
 class OrderByName
@@ -598,6 +762,7 @@ FGPositioned::Type FGPositioned::typeFromName(const std::string& aName)
   // aliases
     {"waypoint", WAYPOINT},
     {"apt", AIRPORT},
+    {"arpt", AIRPORT},
     {"any", INVALID},
     {"all", INVALID},
     
@@ -656,8 +821,8 @@ FGPositioned::List
 FGPositioned::findWithinRange(const SGGeod& aPos, double aRangeNm, Filter* aFilter)
 {
   List result;
-  spatialFind(aPos, aRangeNm, aFilter, result);
-  filterListByRange(aPos, aRangeNm, result);
+  Octree::findAllWithinRange(SGVec3d::fromGeod(aPos), 
+    aRangeNm * SG_NM_TO_METER, aFilter, result);
   return result;
 }
 
@@ -676,7 +841,7 @@ FGPositioned::findAllWithNameSortedByRange(const std::string& aName, const SGGeo
 FGPositionedRef
 FGPositioned::findClosest(const SGGeod& aPos, double aCutoffNm, Filter* aFilter)
 {
-   FGPositioned::List l(spatialGetClosest(aPos, 1, aCutoffNm, aFilter));
+   List l(findClosestN(aPos, 1, aCutoffNm, aFilter));
    if (l.empty()) {
       return NULL;
    }
@@ -688,12 +853,18 @@ FGPositioned::findClosest(const SGGeod& aPos, double aCutoffNm, Filter* aFilter)
 FGPositioned::List
 FGPositioned::findClosestN(const SGGeod& aPos, unsigned int aN, double aCutoffNm, Filter* aFilter)
 {
-  return spatialGetClosest(aPos, aN, aCutoffNm, aFilter);
+  List result;
+  Octree::findNearestN(SGVec3d::fromGeod(aPos), aN, aCutoffNm * SG_NM_TO_METER, aFilter, result);
+  return result;
 }
 
 FGPositionedRef
 FGPositioned::findNextWithPartialId(FGPositionedRef aCur, const std::string& aId, Filter* aFilter)
 {
+  if (aId.empty()) {
+    return NULL;
+  }
+  
   std::string id(boost::to_upper_copy(aId));
 
   // It is essential to bound our search, to avoid iterating all the way to the end of the database.
@@ -785,8 +956,8 @@ findClosestWithPartial(const SGGeod& aPos, FGPositioned::Filter* aFilter, int aO
 {
   // why aOffset +2 ? at offset=3, we want the fourth search result, but also
   // to know if the fifth result exists (to set aNext flag for iterative APIs)
-  FGPositioned::List matches = 
-    spatialGetClosest(aPos, aOffset + 2, 1000.0, aFilter);
+  FGPositioned::List matches;
+  Octree::findNearestN(SGVec3d::fromGeod(aPos), aOffset + 2, 1000 * SG_NM_TO_METER, aFilter, matches);
   
   if ((int) matches.size() <= aOffset) {
     SG_LOG(SG_GENERAL, SG_INFO, "findClosestWithPartial, couldn't match enough with prefix");
index 502a406b2e6f88fed49e6bf310c7c7d2b459212f..3b1d995293ff896eef9ef91bcbf74065617fdd54 100644 (file)
@@ -32,6 +32,7 @@
 #include <Main/fg_props.hxx>
 #include <Main/util.hxx>
 #include <Scenery/scenery.hxx>
+#include <Navaids/navrecord.hxx>
 
 #include "NasalSys.hxx"
 
@@ -593,6 +594,11 @@ static naRef f_airportinfo(naContext c, naRef me, int argc, naRef* args)
         HASHSET("width", 5, naNum(rwy->widthM()));
         HASHSET("threshold", 9, naNum(rwy->displacedThresholdM()));
         HASHSET("stopway", 7, naNum(rwy->stopwayM()));
+        
+        if (rwy->ILS()) {
+          HASHSET("ils_frequency_mhz", 17, naNum(rwy->ILS()->get_freq() / 100.0));
+        }
+        
 #undef HASHSET
         naHash_set(rwys, rwyid, rwydata);
     }
index 103660b06147a4925345c5137624db541ffd710d..2062c36ba29a7021bd902d2e9c2577644d3ee310 100644 (file)
@@ -8,10 +8,10 @@ else
 test_up_PLIB_LIBS = -lplibsg -lplibul
 endif
 
-est_epsilon_SOURCES = est-epsilon.c
+est_epsilon_SOURCES = est-epsilon.cxx
 est_epsilon_LDADD =  $(opengl_LIBS)
 
-gl_info_SOURCES = gl-info.c
+gl_info_SOURCES = gl-info.cxx
 gl_info_LDADD = $(opengl_LIBS)
 
 alcinfo_SOURCES = alcinfo.cxx
diff --git a/tests/est-epsilon.c b/tests/est-epsilon.c
deleted file mode 100644 (file)
index 5ff0a8a..0000000
+++ /dev/null
@@ -1,34 +0,0 @@
-#ifdef HAVE_CONFIG_H
-#  include <config.h>
-#endif
-
-#ifdef HAVE_WINDOWS_H
-#  include <windows.h>
-#endif
-
-#include <stdio.h>
-
-#include <simgear/compiler.h>
-#if defined( __APPLE__)
-# include <OpenGL/OpenGL.h>
-#else
-# include <GL/gl.h>
-#endif
-
-int main() {
-    GLfloat a, t;
-
-    a = 1.0;
-
-    do {
-       printf("a = %.10f\n", a);
-       a = a / 2.0;
-       t = 1.0 + a;
-    } while ( t > 1.0 );
-
-    a = a + a;
-
-    printf("Estimated GLfloat epsilon = %.10f\n", a);
-
-    return(0);
-}
diff --git a/tests/est-epsilon.cxx b/tests/est-epsilon.cxx
new file mode 100644 (file)
index 0000000..5ff0a8a
--- /dev/null
@@ -0,0 +1,34 @@
+#ifdef HAVE_CONFIG_H
+#  include <config.h>
+#endif
+
+#ifdef HAVE_WINDOWS_H
+#  include <windows.h>
+#endif
+
+#include <stdio.h>
+
+#include <simgear/compiler.h>
+#if defined( __APPLE__)
+# include <OpenGL/OpenGL.h>
+#else
+# include <GL/gl.h>
+#endif
+
+int main() {
+    GLfloat a, t;
+
+    a = 1.0;
+
+    do {
+       printf("a = %.10f\n", a);
+       a = a / 2.0;
+       t = 1.0 + a;
+    } while ( t > 1.0 );
+
+    a = a + a;
+
+    printf("Estimated GLfloat epsilon = %.10f\n", a);
+
+    return(0);
+}
diff --git a/tests/gl-info.c b/tests/gl-info.c
deleted file mode 100644 (file)
index 7d5be7d..0000000
+++ /dev/null
@@ -1,123 +0,0 @@
-/*
-From: Steve Baker <sbaker@link.com>
-Sender: root@fatcity.com
-To: OPENGL-GAMEDEV-L <OPENGL-GAMEDEV-L@fatcity.com>
-Subject: Re: Win32 OpenGL Resource Page
-Date: Fri, 24 Apr 1998 07:33:51 -0800
-*/
-
-
-#ifdef HAVE_CONFIG_H
-#  include <config.h>
-#endif
-
-#ifdef HAVE_WINDOWS_H
-#  include <windows.h>
-#endif
-
-#include <stdio.h>
-#include <stdlib.h>
-
-#include <simgear/compiler.h>
-#if defined( __APPLE__)
-# include <OpenGL/OpenGL.h>
-# include <GLUT/glut.h>
-#else
-# include <GL/gl.h>
-# ifdef HAVE_GLUT_H
-#  include <GL/glut.h>
-# endif
-#endif
-
-
-void getPrints ( GLenum token, char *string )
-{
-  printf ( "%s = \"%s\"\n", string, glGetString ( token ) ) ;
-}
-
-void getPrint2f ( GLenum token, char *string )
-{
-  GLfloat f[2] ;
-  glGetFloatv ( token, f ) ;
-  printf ( "%s = %g,%g\n", string, f[0],f[1] ) ;
-}
-
-void getPrintf ( GLenum token, char *string )
-{
-  GLfloat f ;
-  glGetFloatv ( token, &f ) ;
-  printf ( "%s = %g\n", string, f ) ;
-}
-
-void getPrint2i ( GLenum token, char *string )
-{
-  GLint i[2] ;
-  glGetIntegerv ( token, i ) ;
-  printf ( "%s = %d,%d\n", string, i[0],i[1] ) ;
-}
-
-void getPrinti ( GLenum token, char *string )
-{
-  GLint i ;
-  glGetIntegerv ( token, &i ) ;
-  printf ( "%s = %d\n", string, i ) ;
-}
-
-int main ( int argc, char **argv )
-{
-#ifdef HAVE_GLUT_H
-  glutInit            ( &argc, argv ) ;
-  glutInitDisplayMode ( GLUT_RGB | GLUT_DOUBLE | GLUT_DEPTH ) ;
-  glutCreateWindow    ( "You should never see this window!"  ) ;
-
-  getPrints ( GL_VENDOR      , "GL_VENDOR"     ) ;
-  getPrints ( GL_RENDERER    , "GL_RENDERER"   ) ;
-  getPrints ( GL_VERSION     , "GL_VERSION"    ) ;
-  getPrints ( GL_EXTENSIONS  , "GL_EXTENSIONS" ) ;
-
-  getPrinti ( GL_RED_BITS    , "GL_RED_BITS"   ) ;
-  getPrinti ( GL_GREEN_BITS  , "GL_GREEN_BITS" ) ;
-  getPrinti ( GL_BLUE_BITS   , "GL_BLUE_BITS"  ) ;
-  getPrinti ( GL_ALPHA_BITS  , "GL_ALPHA_BITS" ) ;
-  getPrinti ( GL_DEPTH_BITS  , "GL_DEPTH_BITS" ) ;
-  getPrinti ( GL_INDEX_BITS  , "GL_INDEX_BITS" ) ;
-  getPrinti ( GL_STENCIL_BITS, "GL_STENCIL_BITS" ) ;
-
-  getPrinti ( GL_ACCUM_RED_BITS  , "GL_ACCUM_RED_BITS"   ) ;
-  getPrinti ( GL_ACCUM_GREEN_BITS, "GL_ACCUM_GREEN_BITS" ) ;
-  getPrinti ( GL_ACCUM_BLUE_BITS , "GL_ACCUM_BLUE_BITS"  ) ;
-  getPrinti ( GL_ACCUM_ALPHA_BITS, "GL_ACCUM_ALPHA_BITS" ) ;
-
-  getPrinti ( GL_AUX_BUFFERS, "GL_AUX_BUFFERS" ) ;
-
-  getPrinti ( GL_MAX_ATTRIB_STACK_DEPTH    , "GL_MAX_ATTRIB_STACK_DEPTH"     ) ;
-  getPrinti ( GL_MAX_NAME_STACK_DEPTH      , "GL_MAX_NAME_STACK_DEPTH"       ) ;
-  getPrinti ( GL_MAX_TEXTURE_STACK_DEPTH   , "GL_MAX_TEXTURE_STACK_DEPTH"    ) ;
-  getPrinti ( GL_MAX_PROJECTION_STACK_DEPTH, "GL_MAX_PROJECTION_STACK_DEPTH" ) ;
-  getPrinti ( GL_MAX_MODELVIEW_STACK_DEPTH , "GL_MAX_MODELVIEW_STACK_DEPTH"  ) ;
-
-  getPrinti ( GL_MAX_CLIP_PLANES    , "GL_MAX_CLIP_PLANES"     ) ;
-  getPrinti ( GL_MAX_EVAL_ORDER     , "GL_MAX_EVAL_ORDER"      ) ;
-  getPrinti ( GL_MAX_LIGHTS         , "GL_MAX_LIGHTS"          ) ;
-  getPrinti ( GL_MAX_LIST_NESTING   , "GL_MAX_LIST_NESTING"    ) ;
-  getPrinti ( GL_MAX_TEXTURE_SIZE   , "GL_MAX_TEXTURE_SIZE"    ) ;
-  getPrint2i( GL_MAX_VIEWPORT_DIMS  , "GL_MAX_VIEWPORT_DIMS"   ) ;
-
-  getPrintf ( GL_POINT_SIZE_GRANULARITY, "GL_POINT_SIZE_GRANULARITY" ) ;
-  getPrint2f( GL_POINT_SIZE_RANGE      , "GL_POINT_SIZE_RANGE" ) ;
-
-  printf("Default values:\n\n");
-
-  getPrinti( GL_UNPACK_ALIGNMENT  , "GL_UNPACK_ALIGNMENT"   ) ;
-  getPrinti( GL_UNPACK_ROW_LENGTH  , "GL_UNPACK_ROW_LENGTH"   ) ;
-  getPrinti( GL_UNPACK_SKIP_PIXELS  , "GL_UNPACK_SKIP_PIXELS"   ) ;
-  getPrinti( GL_UNPACK_SKIP_ROWS  , "GL_UNPACK_SKIP_ROWS"   ) ;
-  getPrinti( GL_BLEND_SRC  , "GL_BLEND_SRC"   ) ;
-  getPrinti( GL_BLEND_DST  , "GL_BLEND_DST"   ) ;
-#else
-
-  printf("GL Utility Toolkit (glut) was not found on this system.\n");
-#endif
-
-  return 0 ;
-}
diff --git a/tests/gl-info.cxx b/tests/gl-info.cxx
new file mode 100644 (file)
index 0000000..7d5be7d
--- /dev/null
@@ -0,0 +1,123 @@
+/*
+From: Steve Baker <sbaker@link.com>
+Sender: root@fatcity.com
+To: OPENGL-GAMEDEV-L <OPENGL-GAMEDEV-L@fatcity.com>
+Subject: Re: Win32 OpenGL Resource Page
+Date: Fri, 24 Apr 1998 07:33:51 -0800
+*/
+
+
+#ifdef HAVE_CONFIG_H
+#  include <config.h>
+#endif
+
+#ifdef HAVE_WINDOWS_H
+#  include <windows.h>
+#endif
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#include <simgear/compiler.h>
+#if defined( __APPLE__)
+# include <OpenGL/OpenGL.h>
+# include <GLUT/glut.h>
+#else
+# include <GL/gl.h>
+# ifdef HAVE_GLUT_H
+#  include <GL/glut.h>
+# endif
+#endif
+
+
+void getPrints ( GLenum token, char *string )
+{
+  printf ( "%s = \"%s\"\n", string, glGetString ( token ) ) ;
+}
+
+void getPrint2f ( GLenum token, char *string )
+{
+  GLfloat f[2] ;
+  glGetFloatv ( token, f ) ;
+  printf ( "%s = %g,%g\n", string, f[0],f[1] ) ;
+}
+
+void getPrintf ( GLenum token, char *string )
+{
+  GLfloat f ;
+  glGetFloatv ( token, &f ) ;
+  printf ( "%s = %g\n", string, f ) ;
+}
+
+void getPrint2i ( GLenum token, char *string )
+{
+  GLint i[2] ;
+  glGetIntegerv ( token, i ) ;
+  printf ( "%s = %d,%d\n", string, i[0],i[1] ) ;
+}
+
+void getPrinti ( GLenum token, char *string )
+{
+  GLint i ;
+  glGetIntegerv ( token, &i ) ;
+  printf ( "%s = %d\n", string, i ) ;
+}
+
+int main ( int argc, char **argv )
+{
+#ifdef HAVE_GLUT_H
+  glutInit            ( &argc, argv ) ;
+  glutInitDisplayMode ( GLUT_RGB | GLUT_DOUBLE | GLUT_DEPTH ) ;
+  glutCreateWindow    ( "You should never see this window!"  ) ;
+
+  getPrints ( GL_VENDOR      , "GL_VENDOR"     ) ;
+  getPrints ( GL_RENDERER    , "GL_RENDERER"   ) ;
+  getPrints ( GL_VERSION     , "GL_VERSION"    ) ;
+  getPrints ( GL_EXTENSIONS  , "GL_EXTENSIONS" ) ;
+
+  getPrinti ( GL_RED_BITS    , "GL_RED_BITS"   ) ;
+  getPrinti ( GL_GREEN_BITS  , "GL_GREEN_BITS" ) ;
+  getPrinti ( GL_BLUE_BITS   , "GL_BLUE_BITS"  ) ;
+  getPrinti ( GL_ALPHA_BITS  , "GL_ALPHA_BITS" ) ;
+  getPrinti ( GL_DEPTH_BITS  , "GL_DEPTH_BITS" ) ;
+  getPrinti ( GL_INDEX_BITS  , "GL_INDEX_BITS" ) ;
+  getPrinti ( GL_STENCIL_BITS, "GL_STENCIL_BITS" ) ;
+
+  getPrinti ( GL_ACCUM_RED_BITS  , "GL_ACCUM_RED_BITS"   ) ;
+  getPrinti ( GL_ACCUM_GREEN_BITS, "GL_ACCUM_GREEN_BITS" ) ;
+  getPrinti ( GL_ACCUM_BLUE_BITS , "GL_ACCUM_BLUE_BITS"  ) ;
+  getPrinti ( GL_ACCUM_ALPHA_BITS, "GL_ACCUM_ALPHA_BITS" ) ;
+
+  getPrinti ( GL_AUX_BUFFERS, "GL_AUX_BUFFERS" ) ;
+
+  getPrinti ( GL_MAX_ATTRIB_STACK_DEPTH    , "GL_MAX_ATTRIB_STACK_DEPTH"     ) ;
+  getPrinti ( GL_MAX_NAME_STACK_DEPTH      , "GL_MAX_NAME_STACK_DEPTH"       ) ;
+  getPrinti ( GL_MAX_TEXTURE_STACK_DEPTH   , "GL_MAX_TEXTURE_STACK_DEPTH"    ) ;
+  getPrinti ( GL_MAX_PROJECTION_STACK_DEPTH, "GL_MAX_PROJECTION_STACK_DEPTH" ) ;
+  getPrinti ( GL_MAX_MODELVIEW_STACK_DEPTH , "GL_MAX_MODELVIEW_STACK_DEPTH"  ) ;
+
+  getPrinti ( GL_MAX_CLIP_PLANES    , "GL_MAX_CLIP_PLANES"     ) ;
+  getPrinti ( GL_MAX_EVAL_ORDER     , "GL_MAX_EVAL_ORDER"      ) ;
+  getPrinti ( GL_MAX_LIGHTS         , "GL_MAX_LIGHTS"          ) ;
+  getPrinti ( GL_MAX_LIST_NESTING   , "GL_MAX_LIST_NESTING"    ) ;
+  getPrinti ( GL_MAX_TEXTURE_SIZE   , "GL_MAX_TEXTURE_SIZE"    ) ;
+  getPrint2i( GL_MAX_VIEWPORT_DIMS  , "GL_MAX_VIEWPORT_DIMS"   ) ;
+
+  getPrintf ( GL_POINT_SIZE_GRANULARITY, "GL_POINT_SIZE_GRANULARITY" ) ;
+  getPrint2f( GL_POINT_SIZE_RANGE      , "GL_POINT_SIZE_RANGE" ) ;
+
+  printf("Default values:\n\n");
+
+  getPrinti( GL_UNPACK_ALIGNMENT  , "GL_UNPACK_ALIGNMENT"   ) ;
+  getPrinti( GL_UNPACK_ROW_LENGTH  , "GL_UNPACK_ROW_LENGTH"   ) ;
+  getPrinti( GL_UNPACK_SKIP_PIXELS  , "GL_UNPACK_SKIP_PIXELS"   ) ;
+  getPrinti( GL_UNPACK_SKIP_ROWS  , "GL_UNPACK_SKIP_ROWS"   ) ;
+  getPrinti( GL_BLEND_SRC  , "GL_BLEND_SRC"   ) ;
+  getPrinti( GL_BLEND_DST  , "GL_BLEND_DST"   ) ;
+#else
+
+  printf("GL Utility Toolkit (glut) was not found on this system.\n");
+#endif
+
+  return 0 ;
+}
diff --git a/utils/Modeller/yasim_import.py b/utils/Modeller/yasim_import.py
new file mode 100644 (file)
index 0000000..6a862cb
--- /dev/null
@@ -0,0 +1,825 @@
+#!BPY
+
+# """
+# Name: 'YASim (.xml)'
+# Blender: 245
+# Group: 'Import'
+# Tooltip: 'Loads and visualizes a YASim FDM geometry'
+# """
+
+__author__ = "Melchior FRANZ < mfranz # aon : at >"
+__url__ = ["http://www.flightgear.org/", "http://cvs.flightgear.org/viewvc/source/utils/Modeller/yasim_import.py"]
+__version__ = "0.2"
+__bpydoc__ = """\
+yasim_import.py loads and visualizes a YASim FDM geometry
+=========================================================
+
+It is recommended to load the model superimposed over a greyed out and immutable copy of the aircraft model:
+
+  (0) put this script into ~/.blender/scripts/
+  (1) load or import aircraft model (menu -> "File" -> "Import" -> "AC3D (.ac) ...")
+  (2) create new *empty* scene (menu -> arrow button left of "SCE:scene1" combobox -> "ADD NEW" -> "empty")
+  (3) rename scene to yasim (not required)
+  (4) link to scene1 (F10 -> "Output" tab in "Buttons Window" -> arrow button left of text entry "No Set Scene" -> "scene1")
+  (5) now load the YASim config file (menu -> "File" -> "Import" -> "YASim (.xml) ...")
+
+This is good enough for simple checks. But if you are working on the YASim configuration, then you need a
+quick and convenient way to reload the file. In that case continue after (4):
+
+  (5) switch the button area at the bottom of the blender screen to "Scripts Window" mode (green python snake icon)
+  (6) load the YASim config file (menu -> "Scripts" -> "Import" -> "YASim (.xml) ...")
+  (7) make the "Scripts Window" area as small as possible by dragging the area separator down
+  (8) optionally split the "3D View" area and switch the right part to the "Outliner"
+  (9) press the "Reload YASim" button in the script area to reload the file
+
+
+If the 3D model is displaced with respect to the FDM model, then the <offsets> values from the
+model animation XML file should be added as comment to the YASim config file, as a line all by
+itself, with no spaces surrounding the equal signs. Spaces elsewhere are allowed. For example:
+
+  <offsets>
+      <x-m>3.45</x-m>
+      <z-m>-0.4</z-m>
+      <pitch-deg>5</pitch-deg>
+  </offsets>
+
+becomes:
+
+  <!-- offsets: x=3.45 z=-0.4 p=5 -->
+
+Possible variables are:
+
+  x ... <x-m>
+  y ... <y-m>
+  z ... <z-m>
+  h ... <heading-deg>
+  p ... <pitch-deg>
+  r ... <roll-deg>
+
+Of course, absolute FDM coordinates can then no longer directly be read from Blender's 3D view.
+The cursor coordinates display in the script area, however, shows the coordinates in YASim space.
+Note that object names don't contain XML indices but element numbers. YASim_flap0#2 is the third
+flap0 in the whole file, not necessarily in its parent XML group. A floating point part in the
+object name (e.g. YASim_flap0#2.004) only means that the geometry has been reloaded that often.
+It's an unavoidable consequence of how Blender deals with meshes.
+
+
+Elements are displayed as follows:
+
+  cockpit                             -> monkey head
+  fuselage                            -> blue "tube" (with only 12 sides for less clutter); center at "a"
+  vstab                               -> red with yellow control surfaces (flap0, flap1, slat, spoiler)
+  wing/mstab/hstab                    -> green with yellow control surfaces (which are always 20 cm deep);
+                                         symmetric surfaces are only displayed on the left side, unless
+                                         the "Mirror" button is active
+  thrusters (jet/propeller/thruster)  -> dashed line from center to actionpt;
+                                         arrow from actionpt along thrust vector (always 1 m long);
+                                         propeller circle
+  rotor                               -> radius and rel_len_blade_start circle, normal and forward vector,
+                                         one blade at phi0 with direction arrow near blade tip
+  gear                                -> contact point and compression vector (no arrow head)
+  tank                                -> magenta cube (10 cm side length)
+  weight                              -> inverted cyan cone
+  ballast                             -> yellow cylinder
+  hitch                               -> hexagon (10 cm diameter)
+  hook                                -> dashed line for up angle, T-line for down angle
+  launchbar                           -> dashed line for up angles, T-line for down angles
+                                         (launchbar and holdback each)
+
+
+The Mirror button complements symmetrical surfaces (wing/hstab/mstab) and control surfaces
+(flap0/flap1/slat/spoiler). This is useful for asymmetrical aircraft, but has the disadvantage
+that it moves the surfaces' object centers from their usual place, yasim's [x, y, z] value,
+to [0, 0, 0]. Turning mirroring off restores the object center.
+
+
+
+Environment variable BLENDER_YASIM_IMPORT can be set to a space-separated list of options:
+
+  $ BLENDER_YASIM_IMPORT="mirror verbose"  blender
+
+whereby:
+
+  verbose  ... enables verbose logs
+  mirror   ... enables mirroring of symmetric surfaces
+"""
+
+
+#--------------------------------------------------------------------------------
+# Copyright (C) 2009  Melchior FRANZ  < mfranz # aon : at >
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License as
+# published by the Free Software Foundation; either version 2 of the
+# License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+#--------------------------------------------------------------------------------
+
+
+import Blender, BPyMessages, string, math, os
+from Blender.Mathutils import *
+from xml.sax import handler, make_parser
+
+
+CONFIG = string.split(os.getenv("BLENDER_YASIM_IMPORT") or "")
+YASIM_MATRIX = Matrix([-1, 0, 0, 0], [0, -1, 0, 0], [0, 0, 1, 0], [0, 0, 0, 1])
+ORIGIN = Vector(0, 0, 0)
+X = Vector(1, 0, 0)
+Y = Vector(0, 1, 0)
+Z = Vector(0, 0, 1)
+DEG2RAD = math.pi / 180
+RAD2DEG = 180 / math.pi
+
+NO_EVENT = 0
+RELOAD_BUTTON = 1
+CURSOR_BUTTON = 2
+MIRROR_BUTTON = 3
+
+
+
+class Global:
+       verbose = "verbose" in CONFIG
+       path = ""
+       matrix = None
+       data = None
+       cursor = ORIGIN
+       last_cursor = Vector(Blender.Window.GetCursorPos())
+       mirror_button = Blender.Draw.Create("mirror" in CONFIG)
+
+
+
+class Abort(Exception):
+       def __init__(self, msg, term = None):
+               self.msg = msg
+               self.term = term
+
+
+
+def log(msg):
+       if Global.verbose:
+               print(msg)
+
+
+
+def draw_dashed_line(mesh, start, end):
+       w = 0.04
+       step = w * (end - start).normalize()
+       n = len(mesh.verts)
+       for i in range(int(1 + 0.5 * (end - start).length / w)):
+               a = start + 2 * i * step
+               b = a + step
+               if (b - end).length < step.length:
+                       b = end
+               mesh.verts.extend([a, b])
+               mesh.edges.extend([n + 2 * i, n + 2 * i + 1])
+
+
+
+def draw_arrow(mesh, start, end):
+       v = end - start
+       m = v.toTrackQuat('x', 'z').toMatrix().resize4x4() * TranslationMatrix(start)
+       v = v.length * X
+       n = len(mesh.verts)
+       mesh.verts.extend([ORIGIN * m , v * m, (v - 0.05 * X + 0.05 * Y) * m, (v - 0.05 * X - 0.05 * Y) * m]) # head
+       mesh.verts.extend([(ORIGIN + 0.05 * Y) * m, (ORIGIN - 0.05 * Y) * m]) # base
+       mesh.edges.extend([[n, n + 1], [n + 1, n + 2], [n + 1, n + 3], [n + 4, n + 5]])
+
+
+
+def draw_circle(mesh, numpoints, radius, matrix):
+       n = len(mesh.verts)
+       for i in range(numpoints):
+               angle = 2.0 * math.pi * i / numpoints
+               v = Vector(radius * math.cos(angle), radius * math.sin(angle), 0)
+               mesh.verts.extend([v * matrix])
+       for i in range(numpoints):
+               i1 = (i + 1) % numpoints
+               mesh.edges.extend([[n + i, n + i1]])
+
+
+
+class Item:
+       scene = Blender.Scene.GetCurrent()
+
+       def make_twosided(self, mesh):
+               mesh.faceUV = True
+               for f in mesh.faces:
+                       f.mode |= Blender.Mesh.FaceModes.TWOSIDE | Blender.Mesh.FaceModes.OBCOL
+
+       def set_color(self, obj, color):
+               mat = Blender.Material.New()
+               mat.setRGBCol(color[0], color[1], color[2])
+               mat.setAlpha(color[3])
+               mat.mode |= Blender.Material.Modes.ZTRANSP | Blender.Material.Modes.TRANSPSHADOW
+               obj.transp = True
+
+               mesh = obj.getData(mesh = True)
+               mesh.materials += [mat]
+
+               for f in mesh.faces:
+                       f.smooth = True
+               mesh.calcNormals()
+
+
+
+class Cockpit(Item):
+       def __init__(self, center):
+               mesh = Blender.Mesh.Primitives.Monkey()
+               mesh.transform(ScaleMatrix(0.13, 4) * Euler(90, 0, 90).toMatrix().resize4x4() * TranslationMatrix(Vector(-0.1, 0, -0.032)))
+               obj = self.scene.objects.new(mesh, "YASim_cockpit")
+               obj.setMatrix(TranslationMatrix(center) * Global.matrix)
+
+
+
+class Tank(Item):
+       def __init__(self, name, center):
+               mesh = Blender.Mesh.Primitives.Cube()
+               mesh.transform(ScaleMatrix(0.05, 4))
+               obj = self.scene.objects.new(mesh, name)
+               obj.setMatrix(TranslationMatrix(center) * Global.matrix)
+               self.set_color(obj, [1, 0, 1, 0.5])
+
+
+
+class Ballast(Item):
+       def __init__(self, name, center):
+               mesh = Blender.Mesh.Primitives.Cylinder()
+               mesh.transform(ScaleMatrix(0.05, 4))
+               obj = self.scene.objects.new(mesh, name)
+               obj.setMatrix(TranslationMatrix(center) * Global.matrix)
+               self.set_color(obj, [1, 1, 0, 0.5])
+
+
+
+class Weight(Item):
+       def __init__(self, name, center):
+               mesh = Blender.Mesh.Primitives.Cone()
+               mesh.transform(ScaleMatrix(0.05, 4))
+               obj = self.scene.objects.new(mesh, name)
+               obj.setMatrix(TranslationMatrix(center) * Global.matrix)
+               self.set_color(obj, [0, 1, 1, 0.5])
+
+
+
+class Gear(Item):
+       def __init__(self, name, center, compression):
+               mesh = Blender.Mesh.New()
+               mesh.verts.extend([ORIGIN, compression])
+               mesh.edges.extend([0, 1])
+               obj = self.scene.objects.new(mesh, name)
+               obj.setMatrix(TranslationMatrix(center) * Global.matrix)
+
+
+
+class Hook(Item):
+       def __init__(self, name, center, length, up_angle, dn_angle):
+               mesh = Blender.Mesh.New()
+               up = ORIGIN - length * math.cos(up_angle * DEG2RAD) * X - length * math.sin(up_angle * DEG2RAD) * Z
+               dn = ORIGIN - length * math.cos(dn_angle * DEG2RAD) * X - length * math.sin(dn_angle * DEG2RAD) * Z
+               mesh.verts.extend([ORIGIN, dn, dn + 0.05 * Y, dn - 0.05 * Y])
+               mesh.edges.extend([[0, 1], [2, 3]])
+               draw_dashed_line(mesh, ORIGIN, up)
+               draw_dashed_line(mesh, ORIGIN, dn)
+               obj = self.scene.objects.new(mesh, name)
+               obj.setMatrix(TranslationMatrix(center) * Global.matrix)
+
+
+
+class Launchbar(Item):
+       def __init__(self, name, lb, lb_length, hb, hb_length, up_angle, dn_angle):
+               mesh = Blender.Mesh.New()
+               hb = hb - lb
+               lb_tip = ORIGIN + lb_length * math.cos(dn_angle * DEG2RAD) * X - lb_length * math.sin(dn_angle * DEG2RAD) * Z
+               hb_tip = hb - hb_length * math.cos(dn_angle * DEG2RAD) * X - hb_length * math.sin(dn_angle * DEG2RAD) * Z
+               mesh.verts.extend([lb_tip, ORIGIN, hb, hb_tip, lb_tip + 0.05 * Y, lb_tip - 0.05 * Y, hb_tip + 0.05 * Y, hb_tip - 0.05 * Y])
+               mesh.edges.extend([[0, 1], [1, 2], [2, 3], [4, 5], [6, 7]])
+               draw_dashed_line(mesh, ORIGIN, lb_length * math.cos(up_angle * DEG2RAD) * X - lb_length * math.sin(up_angle * DEG2RAD) * Z)
+               draw_dashed_line(mesh, hb, hb - hb_length * math.cos(up_angle * DEG2RAD) * X - hb_length * math.sin(up_angle * DEG2RAD) * Z)
+               obj = self.scene.objects.new(mesh, name)
+               obj.setMatrix(TranslationMatrix(lb) * Global.matrix)
+
+
+
+class Hitch(Item):
+       def __init__(self, name, center):
+               mesh = Blender.Mesh.Primitives.Circle(6, 0.1)
+               obj = self.scene.objects.new(mesh, name)
+               obj.setMatrix(RotationMatrix(90, 4, "x") * TranslationMatrix(center) * Global.matrix)
+
+
+
+class Thrust:
+       def set_actionpt(self, p):
+               self.actionpt = p
+
+       def set_dir(self, d):
+               self.thrustvector = d
+
+
+
+class Thruster(Thrust, Item):
+       def __init__(self, name, center, thrustvector):
+               (self.name, self.center, self.actionpt, self.thrustvector) = (name, center, center, thrustvector)
+
+       def __del__(self):
+               a = self.actionpt - self.center
+               mesh = Blender.Mesh.New()
+               draw_dashed_line(mesh, ORIGIN, a)
+               draw_arrow(mesh, a, a + self.thrustvector.normalize())
+               obj = self.scene.objects.new(mesh, self.name)
+               obj.setMatrix(TranslationMatrix(self.center) * Global.matrix)
+
+
+
+class Propeller(Thrust, Item):
+       def __init__(self, name, center, radius):
+               (self.name, self.center, self.radius, self.actionpt, self.thrustvector) = (name, center, radius, center, -X)
+
+       def __del__(self):
+               a = self.actionpt - self.center
+               matrix = self.thrustvector.toTrackQuat('z', 'x').toMatrix().resize4x4() * TranslationMatrix(a)
+
+               mesh = Blender.Mesh.New()
+               mesh.verts.extend([ORIGIN * matrix, (ORIGIN + self.radius * X) * matrix])
+               mesh.edges.extend([[0, 1]])
+               draw_dashed_line(mesh, ORIGIN, a)
+               draw_arrow(mesh, a, a + self.thrustvector.normalize())
+
+               draw_circle(mesh, 128, self.radius, matrix)
+               obj = self.scene.objects.new(mesh, self.name)
+               obj.setMatrix(TranslationMatrix(self.center) * Global.matrix)
+
+
+
+class Jet(Thrust, Item):
+       def __init__(self, name, center, rotate):
+               (self.name, self.center, self.actionpt) = (name, center, center)
+               self.thrustvector = -X * RotationMatrix(rotate, 4, "y")
+
+       def __del__(self):
+               a = self.actionpt - self.center
+               mesh = Blender.Mesh.New()
+               draw_dashed_line(mesh, ORIGIN, a)
+               draw_arrow(mesh, a, a + self.thrustvector.normalize())
+               obj = self.scene.objects.new(mesh, self.name)
+               obj.setMatrix(TranslationMatrix(self.center) * Global.matrix)
+
+
+
+class Fuselage(Item):
+       def __init__(self, name, a, b, width, taper, midpoint):
+               numvert = 12
+               angle = []
+               for i in range(numvert):
+                       alpha = i * 2 * math.pi / float(numvert)
+                       angle.append([math.cos(alpha), math.sin(alpha)])
+
+               axis = b - a
+               length = axis.length
+               mesh = Blender.Mesh.New()
+
+               for i in range(numvert):
+                       mesh.verts.extend([[0, 0.5 * width * taper * angle[i][0], 0.5 * width * taper * angle[i][1]]])
+               for i in range(numvert):
+                       mesh.verts.extend([[midpoint * length, 0.5 * width * angle[i][0], 0.5 * width * angle[i][1]]])
+               for i in range(numvert):
+                       mesh.verts.extend([[length, 0.5 * width * taper * angle[i][0], 0.5 * width * taper * angle[i][1]]])
+               for i in range(numvert):
+                       i1 = (i + 1) % numvert
+                       mesh.faces.extend([[i, i1, i1 + numvert, i + numvert]])
+                       mesh.faces.extend([[i + numvert, i1 + numvert, i1 + 2 * numvert, i + 2 * numvert]])
+
+               mesh.verts.extend([ORIGIN, length * X])
+               obj = self.scene.objects.new(mesh, name)
+               obj.setMatrix(axis.toTrackQuat('x', 'y').toMatrix().resize4x4() * TranslationMatrix(a) * Global.matrix)
+               self.set_color(obj, [0, 0, 0.5, 0.4])
+
+
+
+class Rotor(Item):
+       def __init__(self, name, center, up, fwd, numblades, radius, chord, twist, taper, rel_len_blade_start, phi0, ccw):
+               matrix = RotationMatrix(phi0, 4, "z") * up.toTrackQuat('z', 'x').toMatrix().resize4x4()
+               invert = matrix.copy().invert()
+               direction = [-1, 1][ccw]
+               twist *= DEG2RAD
+               a = ORIGIN + rel_len_blade_start * radius * X
+               b = ORIGIN + radius * X
+               tw = 0.5 * chord * taper * math.cos(twist) * Y + 0.5 * direction * chord * taper * math.sin(twist) * Z
+
+               mesh = Blender.Mesh.New()
+               mesh.verts.extend([ORIGIN, a, b, a + 0.5 * chord * Y, a - 0.5 * chord * Y, b + tw, b - tw])
+               mesh.edges.extend([[0, 1], [1, 2], [1, 3], [1, 4], [3, 5], [4, 6], [5, 6]])
+               draw_circle(mesh, 64, rel_len_blade_start * radius, Matrix())
+               draw_circle(mesh, 128, radius, Matrix())
+               draw_arrow(mesh, ORIGIN, up * invert)
+               draw_arrow(mesh, ORIGIN, fwd * invert)
+               b += 0.1 * X + direction * chord * Y
+               draw_arrow(mesh, b, b + min(0.5 * radius, 1) * direction * Y)
+               obj = self.scene.objects.new(mesh, name)
+               obj.setMatrix(matrix * TranslationMatrix(center) * Global.matrix)
+
+
+
+class Wing(Item):
+       def __init__(self, name, root, length, chord, incidence, twist, taper, sweep, dihedral):
+               #  <1--0--2
+               #   \  |  /
+               #    4-3-5
+               self.is_symmetric = not name.startswith("YASim_vstab#")
+               mesh = Blender.Mesh.New()
+               mesh.verts.extend([ORIGIN, ORIGIN + 0.5 * chord * X, ORIGIN - 0.5 * chord * X])
+               tip = ORIGIN + math.cos(sweep * DEG2RAD) * length * Y - math.sin(sweep * DEG2RAD) * length * X
+               tipfore = tip + 0.5 * taper * chord * math.cos(twist * DEG2RAD) * X + 0.5 * taper * chord * math.sin(twist * DEG2RAD) * Z
+               tipaft = tip + tip - tipfore
+               mesh.verts.extend([tip, tipfore, tipaft])
+               mesh.faces.extend([[0, 1, 4, 3], [2, 0, 3, 5]])
+
+               self.make_twosided(mesh)
+
+               obj = self.scene.objects.new(mesh, name)
+               mesh.transform(Euler(dihedral, -incidence, 0).toMatrix().resize4x4())
+               self.set_color(obj, [[0.5, 0.0, 0, 0.5], [0.0, 0.5, 0, 0.5]][self.is_symmetric])
+               (self.obj, self.mesh) = (obj, mesh)
+
+               if self.is_symmetric and Global.mirror_button.val:
+                       mod = obj.modifiers.append(Blender.Modifier.Type.MIRROR)
+                       mod[Blender.Modifier.Settings.AXIS_X] = False
+                       mod[Blender.Modifier.Settings.AXIS_Y] = True
+                       mod[Blender.Modifier.Settings.AXIS_Z] = False
+                       mesh.transform(TranslationMatrix(root)) # must move object center to x axis
+                       obj.setMatrix(Global.matrix)
+               else:
+                       obj.setMatrix(TranslationMatrix(root) * Global.matrix)
+
+       def add_flap(self, name, start, end):
+               a = Vector(self.mesh.verts[2].co)
+               b = Vector(self.mesh.verts[5].co)
+               c = 0.2 * (Vector(self.mesh.verts[0].co - a)).normalize()
+               m = self.obj.getMatrix()
+
+               mesh = Blender.Mesh.New()
+               i0 = a + start * (b - a)
+               i1 = a + end * (b - a)
+               mesh.verts.extend([i0, i1, i0 + c, i1 + c])
+               mesh.faces.extend([[0, 1, 3, 2]])
+
+               self.make_twosided(mesh)
+
+               obj = self.scene.objects.new(mesh, name)
+               obj.setMatrix(m)
+               self.set_color(obj, [0.8, 0.8, 0, 0.9])
+
+               if self.is_symmetric and Global.mirror_button.val:
+                       mod = obj.modifiers.append(Blender.Modifier.Type.MIRROR)
+                       mod[Blender.Modifier.Settings.AXIS_X] = False
+                       mod[Blender.Modifier.Settings.AXIS_Y] = True
+                       mod[Blender.Modifier.Settings.AXIS_Z] = False
+
+
+
+class import_yasim(handler.ErrorHandler, handler.ContentHandler):
+       ignored = ["cruise", "approach", "control-input", "control-output", "control-speed", \
+                       "control-setting", "stall", "airplane", "piston-engine", "turbine-engine", \
+                       "rotorgear", "tow", "winch", "solve-weight"]
+
+
+       # err_handler
+       def warning(self, exception):
+               print((self.error_string("Warning", exception)))
+
+       def error(self, exception):
+               print((self.error_string("Error", exception)))
+
+       def fatalError(self, exception):
+               raise Abort(str(exception), self.error_string("Fatal", exception))
+
+       def error_string(self, tag, e):
+               (column, line) = (e.getColumnNumber(), e.getLineNumber())
+               return "%s: %s\n%s%s^"  % (tag, str(e), Global.data[line - 1], column * ' ')
+
+
+       # doc_handler
+       def setDocumentLocator(self, locator):
+               self.locator = locator
+
+       def startDocument(self):
+               self.tags = []
+               self.counter = {}
+               self.items = [None]
+
+       def endDocument(self):
+               for o in Item.scene.objects:
+                       o.sel = True
+
+       def startElement(self, tag, attrs):
+               if len(self.tags) == 0 and tag != "airplane":
+                       raise Abort("this isn't a YASim config file (bad root tag at line %d)" % self.locator.getLineNumber())
+
+               self.tags.append(tag)
+               path = string.join(self.tags, '/')
+               item = Item()
+               parent = self.items[-1]
+
+               if self.counter.has_key(tag):
+                       self.counter[tag] += 1
+               else:
+                       self.counter[tag] = 0
+
+               if tag == "cockpit":
+                       c = Vector(float(attrs["x"]), float(attrs["y"]), float(attrs["z"]))
+                       log("\033[31mcockpit x=%f y=%f z=%f\033[m" % (c[0], c[1], c[2]))
+                       item = Cockpit(c)
+
+               elif tag == "fuselage":
+                       a = Vector(float(attrs["ax"]), float(attrs["ay"]), float(attrs["az"]))
+                       b = Vector(float(attrs["bx"]), float(attrs["by"]), float(attrs["bz"]))
+                       width = float(attrs["width"])
+                       taper = float(attrs.get("taper", 1))
+                       midpoint = float(attrs.get("midpoint", 0.5))
+                       log("\033[32mfuselage ax=%f ay=%f az=%f bx=%f by=%f bz=%f width=%f taper=%f midpoint=%f\033[m" % \
+                                       (a[0], a[1], a[2], b[0], b[1], b[2], width, taper, midpoint))
+                       item = Fuselage("YASim_%s#%d" % (tag, self.counter[tag]), a, b, width, taper, midpoint)
+
+               elif tag == "gear":
+                       c = Vector(float(attrs["x"]), float(attrs["y"]), float(attrs["z"]))
+                       compression = float(attrs.get("compression", 1))
+                       up = Z * compression
+                       if attrs.has_key("upx"):
+                               up = Vector(float(attrs["upx"]), float(attrs["upy"]), float(attrs["upz"])).normalize() * compression
+                       log("\033[35;1mgear x=%f y=%f z=%f compression=%f upx=%f upy=%f upz=%f\033[m" \
+                                       % (c[0], c[1], c[2], compression, up[0], up[1], up[2]))
+                       item = Gear("YASim_gear#%d" % self.counter[tag], c, up)
+
+               elif tag == "jet":
+                       c = Vector(float(attrs["x"]), float(attrs["y"]), float(attrs["z"]))
+                       rotate = float(attrs.get("rotate", 0))
+                       log("\033[36;1mjet x=%f y=%f z=%f rotate=%f\033[m" % (c[0], c[1], c[2], rotate))
+                       item = Jet("YASim_jet#%d" % self.counter[tag], c, rotate)
+
+               elif tag == "propeller":
+                       c = Vector(float(attrs["x"]), float(attrs["y"]), float(attrs["z"]))
+                       radius = float(attrs["radius"])
+                       log("\033[36;1m%s x=%f y=%f z=%f radius=%f\033[m" % (tag, c[0], c[1], c[2], radius))
+                       item = Propeller("YASim_propeller#%d" % self.counter[tag], c, radius)
+
+               elif tag == "thruster":
+                       c = Vector(float(attrs["x"]), float(attrs["y"]), float(attrs["z"]))
+                       v = Vector(float(attrs["vx"]), float(attrs["vy"]), float(attrs["vz"]))
+                       log("\033[36;1m%s x=%f y=%f z=%f vx=%f vy=%f vz=%f\033[m" % (tag, c[0], c[1], c[2], v[0], v[1], v[2]))
+                       item = Thruster("YASim_thruster#%d" % self.counter[tag], c, v)
+
+               elif tag == "actionpt":
+                       if not isinstance(parent, Thrust):
+                               raise Abort("%s is not part of a thruster/propeller/jet at line %d" \
+                                               % (path, self.locator.getLineNumber()))
+
+                       c = Vector(float(attrs["x"]), float(attrs["y"]), float(attrs["z"]))
+                       log("\t\033[36mactionpt x=%f y=%f z=%f\033[m" % (c[0], c[1], c[2]))
+                       parent.set_actionpt(c)
+
+               elif tag == "dir":
+                       if not isinstance(parent, Thrust):
+                               raise Abort("%s is not part of a thruster/propeller/jet at line %d" \
+                                               % (path, self.locator.getLineNumber()))
+
+                       c = Vector(float(attrs["x"]), float(attrs["y"]), float(attrs["z"]))
+                       log("\t\033[36mdir x=%f y=%f z=%f\033[m" % (c[0], c[1], c[2]))
+                       parent.set_dir(c)
+
+               elif tag == "tank":
+                       c = Vector(float(attrs["x"]), float(attrs["y"]), float(attrs["z"]))
+                       log("\033[34;1m%s x=%f y=%f z=%f\033[m" % (tag, c[0], c[1], c[2]))
+                       item = Tank("YASim_tank#%d" % self.counter[tag], c)
+
+               elif tag == "ballast":
+                       c = Vector(float(attrs["x"]), float(attrs["y"]), float(attrs["z"]))
+                       log("\033[34m%s x=%f y=%f z=%f\033[m" % (tag, c[0], c[1], c[2]))
+                       item = Ballast("YASim_ballast#%d" % self.counter[tag], c)
+
+               elif tag == "weight":
+                       c = Vector(float(attrs["x"]), float(attrs["y"]), float(attrs["z"]))
+                       log("\033[34m%s x=%f y=%f z=%f\033[m" % (tag, c[0], c[1], c[2]))
+                       item = Weight("YASim_weight#%d" % self.counter[tag], c)
+
+               elif tag == "hook":
+                       c = Vector(float(attrs["x"]), float(attrs["y"]), float(attrs["z"]))
+                       length = float(attrs.get("length", 1))
+                       up_angle = float(attrs.get("up-angle", 0))
+                       down_angle = float(attrs.get("down-angle", 70))
+                       log("\033[35m%s x=%f y=%f z=%f length=%f up-angle=%f down-angle=%f\033[m" \
+                                       % (tag, c[0], c[1], c[2], length, up_angle, down_angle))
+                       item = Hook("YASim_hook#%d" % self.counter[tag], c, length, up_angle, down_angle)
+
+               elif tag == "hitch":
+                       c = Vector(float(attrs["x"]), float(attrs["y"]), float(attrs["z"]))
+                       log("\033[35m%s x=%f y=%f z=%f\033[m" % (tag, c[0], c[1], c[2]))
+                       item = Hitch("YASim_hitch#%d" % self.counter[tag], c)
+
+               elif tag == "launchbar":
+                       c = Vector(float(attrs["x"]), float(attrs["y"]), float(attrs["z"]))
+                       length = float(attrs.get("length", 1))
+                       up_angle = float(attrs.get("up-angle", -45))
+                       down_angle = float(attrs.get("down-angle", 45))
+                       holdback = Vector(float(attrs.get("holdback-x", c[0])), float(attrs.get("holdback-y", c[1])), float(attrs.get("holdback-z", c[2])))
+                       holdback_length = float(attrs.get("holdback-length", 2))
+                       log("\033[35m%s x=%f y=%f z=%f length=%f down-angle=%f up-angle=%f holdback-x=%f holdback-y=%f holdback-z+%f holdback-length=%f\033[m" \
+                                       % (tag, c[0], c[1], c[2], length, down_angle, up_angle, \
+                                       holdback[0], holdback[1], holdback[2], holdback_length))
+                       item = Launchbar("YASim_launchbar#%d" % self.counter[tag], c, length, holdback, holdback_length, up_angle, down_angle)
+
+               elif tag == "wing" or tag == "hstab" or tag == "vstab" or tag == "mstab":
+                       root = Vector(float(attrs["x"]), float(attrs["y"]), float(attrs["z"]))
+                       length = float(attrs["length"])
+                       chord = float(attrs["chord"])
+                       incidence = float(attrs.get("incidence", 0))
+                       twist = float(attrs.get("twist", 0))
+                       taper = float(attrs.get("taper", 1))
+                       sweep = float(attrs.get("sweep", 0))
+                       dihedral = float(attrs.get("dihedral", [0, 90][tag == "vstab"]))
+                       log("\033[33;1m%s x=%f y=%f z=%f length=%f chord=%f incidence=%f twist=%f taper=%f sweep=%f dihedral=%f\033[m" \
+                                       % (tag, root[0], root[1], root[2], length, chord, incidence, twist, taper, sweep, dihedral))
+                       item = Wing("YASim_%s#%d" % (tag, self.counter[tag]), root, length, chord, incidence, twist, taper, sweep, dihedral)
+
+               elif tag == "flap0" or tag == "flap1" or tag == "slat" or tag == "spoiler":
+                       if not isinstance(parent, Wing):
+                               raise Abort("%s is not part of a wing or stab at line %d" \
+                                               % (path, self.locator.getLineNumber()))
+
+                       start = float(attrs["start"])
+                       end = float(attrs["end"])
+                       log("\t\033[33m%s start=%f end=%f\033[m" % (tag, start, end))
+                       parent.add_flap("YASim_%s#%d" % (tag, self.counter[tag]), start, end)
+
+               elif tag == "rotor":
+                       c = Vector(float(attrs.get("x", 0)), float(attrs.get("y", 0)), float(attrs.get("z", 0)))
+                       norm = Vector(float(attrs.get("nx", 0)), float(attrs.get("ny", 0)), float(attrs.get("nz", 1)))
+                       fwd = Vector(float(attrs.get("fx", 1)), float(attrs.get("fy", 0)), float(attrs.get("fz", 0)))
+                       diameter = float(attrs.get("diameter", 10.2))
+                       numblades = int(attrs.get("numblades", 4))
+                       chord = float(attrs.get("chord", 0.3))
+                       twist = float(attrs.get("twist", 0))
+                       taper = float(attrs.get("taper", 1))
+                       rel_len_blade_start = float(attrs.get("rel-len-blade-start", 0))
+                       phi0 = float(attrs.get("phi0", 0))
+                       ccw = not not int(attrs.get("ccw", 0))
+
+                       log(("\033[36;1mrotor x=%f y=%f z=%f nx=%f ny=%f nz=%f fx=%f fy=%f fz=%f numblades=%d diameter=%f " \
+                                       + "chord=%f twist=%f taper=%f rel_len_blade_start=%f phi0=%f ccw=%d\033[m") \
+                                       % (c[0], c[1], c[2], norm[0], norm[1], norm[2], fwd[0], fwd[1], fwd[2], numblades, \
+                                       diameter, chord, twist, taper, rel_len_blade_start, phi0, ccw))
+                       item = Rotor("YASim_rotor#%d" % self.counter[tag], c, norm, fwd, numblades, 0.5 * diameter, chord, \
+                                       twist, taper, rel_len_blade_start, phi0, ccw)
+
+               elif tag not in self.ignored:
+                       log("\033[30;1m%s\033[m" % path)
+
+               self.items.append(item)
+
+       def endElement(self, tag):
+               self.tags.pop()
+               self.items.pop()
+
+
+
+def extract_matrix(filedata, tag):
+       v = { 'x': 0.0, 'y': 0.0, 'z': 0.0, 'h': 0.0, 'p': 0.0, 'r': 0.0 }
+       has_offsets = False
+       for line in filedata:
+               line = string.strip(line)
+               if not line.startswith("<!--") or not line.endswith("-->"):
+                       continue
+               line = string.strip(line[4:-3])
+               if not string.lower(line).startswith("%s:" % tag):
+                       continue
+               line = string.strip(line[len(tag) + 1:])
+               for assignment in string.split(line):
+                       (key, value) = string.split(assignment, '=', 2)
+                       v[string.strip(key)] = float(string.strip(value))
+                       has_offsets = True
+
+       if not has_offsets:
+               return None
+
+       print(("using offsets: x=%f y=%f z=%f h=%f p=%f r=%f" % (v['x'], v['y'], v['z'], v['h'], v['p'], v['r'])))
+       return Euler(v['r'], v['p'], v['h']).toMatrix().resize4x4() * TranslationMatrix(Vector(v['x'], v['y'], v['z']))
+
+
+
+def load_yasim_config(path):
+       if BPyMessages.Error_NoFile(path):
+               return
+
+       Blender.Window.WaitCursor(1)
+       Blender.Window.EditMode(0)
+
+       print(("loading '%s'" % path))
+       try:
+               for o in Item.scene.objects:
+                       if o.name.startswith("YASim_"):
+                               Item.scene.objects.unlink(o)
+
+               f = open(path)
+               Global.data = f.readlines()
+               f.close
+
+               Global.path = path
+               Global.matrix = YASIM_MATRIX
+               matrix = extract_matrix(Global.data, "offsets")
+               if matrix:
+                       Global.matrix *= matrix.invert()
+
+               Global.yasim.parse(path)
+               Blender.Registry.SetKey("FGYASimImportExport", { "path": path }, False)
+               Global.data = None
+
+       except Abort, e:
+               print(("%s\nAborting ..." % (e.term or e.msg)))
+               Blender.Draw.PupMenu("Error%t|" + e.msg)
+
+       Blender.Window.RedrawAll()
+       Blender.Window.WaitCursor(0)
+
+
+
+def gui_draw():
+       from Blender import BGL, Draw
+       (width, height) = Blender.Window.GetAreaSize()
+
+       BGL.glClearColor(0.4, 0.4, 0.45, 1)
+       BGL.glClear(BGL.GL_COLOR_BUFFER_BIT)
+
+       BGL.glColor3f(1, 1, 1)
+       BGL.glRasterPos2f(5, 55)
+       Draw.Text("FlightGear YASim Import:   '%s'" % Global.path)
+
+       Draw.PushButton("Reload", RELOAD_BUTTON, 5, 5, 80, 32, "reload YASim config file")
+       Global.mirror_button = Draw.Toggle("Mirror", MIRROR_BUTTON, 100, 5, 50, 16, Global.mirror_button.val, \
+                       "show symmetric surfaces on both sides (reloads config)")
+       Draw.PushButton("Update Cursor", CURSOR_BUTTON, width - 650, 5, 100, 32, "update cursor display (in YASim coordinate system)")
+
+       BGL.glRasterPos2f(width - 530 + Blender.Draw.GetStringWidth("Vector from last") - Blender.Draw.GetStringWidth("Current"), 24)
+       Draw.Text("Current cursor pos:    x = %+.3f    y = %+.3f    z = %+.3f" % tuple(Global.cursor))
+
+       c = Global.cursor - Global.last_cursor
+       BGL.glRasterPos2f(width - 530, 7)
+       Draw.Text("Vector from last cursor pos:    x = %+.3f    y = %+.3f    z = %+.3f    length = %.3f m" % (c[0], c[1], c[2], c.length))
+
+
+
+def gui_event(ev, value):
+       if ev == Blender.Draw.ESCKEY:
+               Blender.Draw.Exit()
+
+
+
+def gui_button(n):
+       if n == NO_EVENT:
+               return
+
+       elif n == RELOAD_BUTTON:
+               load_yasim_config(Global.path)
+
+       elif n == CURSOR_BUTTON:
+               Global.last_cursor = Global.cursor
+               Global.cursor = Vector(Blender.Window.GetCursorPos()) * Global.matrix.invert()
+               d = Global.cursor - Global.last_cursor
+               print(("cursor:   x=\"%f\" y=\"%f\" z=\"%f\"   dx=%f dy=%f dz=%f   length=%f" \
+                               % (Global.cursor[0], Global.cursor[1], Global.cursor[2], d[0], d[1], d[2], d.length)))
+
+       elif n == MIRROR_BUTTON:
+               load_yasim_config(Global.path)
+
+       Blender.Draw.Redraw(1)
+
+
+
+def main():
+       log(6 * "\n")
+       registry = Blender.Registry.GetKey("FGYASimImportExport", False)
+       if registry and "path" in registry and Blender.sys.exists(Blender.sys.expandpath(registry["path"])):
+               path = registry["path"]
+       else:
+               path = ""
+
+       xml_handler = import_yasim()
+       Global.yasim = make_parser()
+       Global.yasim.setContentHandler(xml_handler)
+       Global.yasim.setErrorHandler(xml_handler)
+
+       if Blender.Window.GetScreenInfo(Blender.Window.Types.SCRIPT):
+               Blender.Draw.Register(gui_draw, gui_event, gui_button)
+
+       Blender.Window.FileSelector(load_yasim_config, "Import YASim Configuration File", path)
+
+
+
+main()
+