From: Tim Moore Date: Mon, 11 Jan 2010 23:09:19 +0000 (+0100) Subject: Merge branches 'jmt/spatial', 'jmt/ref_ptr', 'jmt/navradio' and 'jmt/gps' X-Git-Url: https://git.mxchange.org/?a=commitdiff_plain;h=3836abcf4b60844d4942e6c5da237fb3a1f93d24;hp=7a007d9638761132cf81743f41b8fdd1aa5f7ee2;p=flightgear.git Merge branches 'jmt/spatial', 'jmt/ref_ptr', 'jmt/navradio' and 'jmt/gps' --- diff --git a/src/ATCDCL/AILocalTraffic.cxx b/src/ATCDCL/AILocalTraffic.cxx index 6c1b77d8f..c4e467a6f 100644 --- a/src/ATCDCL/AILocalTraffic.cxx +++ b/src/ATCDCL/AILocalTraffic.cxx @@ -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 "; diff --git a/src/ATCDCL/tower.cxx b/src/ATCDCL/tower.cxx index d1886c338..fa54b15cf 100644 --- a/src/ATCDCL/tower.cxx +++ b/src/ATCDCL/tower.cxx @@ -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; diff --git a/src/Autopilot/route_mgr.cxx b/src/Autopilot/route_mgr.cxx index 313cd33ed..bbd2db96b 100644 --- a/src/Autopilot/route_mgr.cxx +++ b/src/Autopilot/route_mgr.cxx @@ -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); + } } diff --git a/src/FDM/YASim/Airplane.cpp b/src/FDM/YASim/Airplane.cpp index 1b3fbd4ba..483da4269 100644 --- a/src/FDM/YASim/Airplane.cpp +++ b/src/FDM/YASim/Airplane.cpp @@ -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 diff --git a/src/Instrumentation/gps.cxx b/src/Instrumentation/gps.cxx index 4477dd4ed..3303b669c 100644 --- a/src/Instrumentation/gps.cxx +++ b/src/Instrumentation/gps.cxx @@ -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(aRef, &SGGeod::getLongitudeDeg, &SGGeod::setLongitudeDeg)); - aNode->tie(latStr, SGRawValueMethods(aRef, &SGGeod::getLatitudeDeg, &SGGeod::setLatitudeDeg)); - - if (altStr) { - aNode->tie(altStr, SGRawValueMethods(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(aRef, &SGGeod::getLongitudeDeg, NULL)); - aNode->tie(latStr, SGRawValueMethods(aRef, &SGGeod::getLatitudeDeg, NULL)); - - if (altStr) { - aNode->tie(altStr, SGRawValueMethods(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(&_turnRate)); - aCfgNode->tie("turn-anticipation", SGRawValuePointer(&_enableTurnAnticipation)); - aCfgNode->tie("wpt-alert-time", SGRawValuePointer(&_waypointAlertTime)); - aCfgNode->tie("tune-nav-radio-to-ref-vor", SGRawValuePointer(&_tuneRadio1ToRefVor)); - aCfgNode->tie("min-runway-length-ft", SGRawValuePointer(&_minRunwayLengthFt)); - aCfgNode->tie("hard-surface-runways-only", SGRawValuePointer(&_requireHardSurface)); + aOwner->tie(aCfg, "turn-rate-deg-sec", SGRawValuePointer(&_turnRate)); + + aOwner->tie(aCfg, "turn-anticipation", SGRawValuePointer(&_enableTurnAnticipation)); + aOwner->tie(aCfg, "wpt-alert-time", SGRawValuePointer(&_waypointAlertTime)); + aOwner->tie(aCfg, "tune-nav-radio-to-ref-vor", SGRawValuePointer(&_tuneRadio1ToRefVor)); + aOwner->tie(aCfg, "min-runway-length-ft", SGRawValuePointer(&_minRunwayLengthFt)); + aOwner->tie(aCfg, "hard-surface-runways-only", SGRawValuePointer(&_requireHardSurface)); - aCfgNode->tie("course-source", SGRawValueMethods + aOwner->tie(aCfg, "course-source", SGRawValueMethods (*this, &GPS::Config::getCourseSource, &GPS::Config::setCourseSource)); - aCfgNode->tie("cdi-max-deflection-nm", SGRawValuePointer(&_cdiMaxDeflectionNm)); - aCfgNode->tie("drive-autopilot", SGRawValuePointer(&_driveAutopilot)); + aOwner->tie(aCfg, "cdi-max-deflection-nm", SGRawValuePointer(&_cdiMaxDeflectionNm)); + aOwner->tie(aCfg, "drive-autopilot", SGRawValuePointer(&_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(*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 - (*this, &GPS::getVerticalSpeed, NULL)); - node->tie("indicated-track-true-deg", SGRawValueMethods - (*this, &GPS::getTrueTrack, NULL)); - node->tie("indicated-track-magnetic-deg", SGRawValueMethods - (*this, &GPS::getMagTrack, NULL)); - node->tie("indicated-ground-speed-kt", SGRawValueMethods - (*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(*this, &GPS::getMode, NULL)); - node->tie("command", SGRawValueMethods(*this, &GPS::getCommand, &GPS::setCommand)); - - _scratchNode = node->getChild("scratch", 0, true); - tieSGGeod(_scratchNode, _scratchPos, "longitude-deg", "latitude-deg", "altitude-ft"); - _scratchNode->tie("valid", SGRawValueMethods(*this, &GPS::getScratchValid, NULL)); - _scratchNode->tie("distance-nm", SGRawValueMethods(*this, &GPS::getScratchDistance, NULL)); - _scratchNode->tie("true-bearing-deg", SGRawValueMethods(*this, &GPS::getScratchTrueBearing, NULL)); - _scratchNode->tie("mag-bearing-deg", SGRawValueMethods(*this, &GPS::getScratchMagBearing, NULL)); - _scratchNode->tie("has-next", SGRawValueMethods(*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 - (*this, &GPS::getWP0Ident, NULL)); - wp0_node->tie("name", SGRawValueMethods - (*this, &GPS::getWP0Name, NULL)); - - tieSGGeodReadOnly(wp1_node, _wp1_position, "longitude-deg", "latitude-deg", "altitude-ft"); - wp1_node->tie("ID", SGRawValueMethods - (*this, &GPS::getWP1Ident, NULL)); - wp1_node->tie("name", SGRawValueMethods - (*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 - (*this, &GPS::getWP1Distance, NULL)); - wp1_node->tie("bearing-true-deg", SGRawValueMethods - (*this, &GPS::getWP1Bearing, NULL)); - wp1_node->tie("bearing-mag-deg", SGRawValueMethods - (*this, &GPS::getWP1MagBearing, NULL)); - wp1_node->tie("TTW-sec", SGRawValueMethods - (*this, &GPS::getWP1TTW, NULL)); - wp1_node->tie("TTW", SGRawValueMethods - (*this, &GPS::getWP1TTWString, NULL)); - - wp1_node->tie("course-deviation-deg", SGRawValueMethods - (*this, &GPS::getWP1CourseDeviation, NULL)); - wp1_node->tie("course-error-nm", SGRawValueMethods - (*this, &GPS::getWP1CourseErrorNm, NULL)); - wp1_node->tie("to-flag", SGRawValueMethods - (*this, &GPS::getWP1ToFlag, NULL)); - wp1_node->tie("from-flag", SGRawValueMethods - (*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(*this, &GPS::getLegDistance, NULL)); - wp_node->tie("leg-true-course-deg", SGRawValueMethods(*this, &GPS::getLegCourse, NULL)); - wp_node->tie("leg-mag-course-deg", SGRawValueMethods(*this, &GPS::getLegMagCourse, NULL)); - wp_node->tie("alt-dist-ratio", SGRawValueMethods(*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 - (*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 + (*this, &GPS::getSelectedCourse, NULL)); + + + tieSGGeodReadOnly(_gpsNode, _indicated_pos, "indicated-longitude-deg", + "indicated-latitude-deg", "indicated-altitude-ft"); + + tie(_gpsNode, "indicated-vertical-speed", SGRawValueMethods + (*this, &GPS::getVerticalSpeed, NULL)); + tie(_gpsNode, "indicated-track-true-deg", SGRawValueMethods + (*this, &GPS::getTrueTrack, NULL)); + tie(_gpsNode, "indicated-track-magnetic-deg", SGRawValueMethods + (*this, &GPS::getMagTrack, NULL)); + tie(_gpsNode, "indicated-ground-speed-kt", SGRawValueMethods + (*this, &GPS::getGroundspeedKts, NULL)); + +// command system + tie(_gpsNode, "mode", SGRawValueMethods(*this, &GPS::getMode, NULL)); + tie(_gpsNode, "command", SGRawValueMethods(*this, &GPS::getCommand, &GPS::setCommand)); + + tieSGGeod(_scratchNode, _scratchPos, "longitude-deg", "latitude-deg", "altitude-ft"); + tie(_scratchNode, "valid", SGRawValueMethods(*this, &GPS::getScratchValid, NULL)); + tie(_scratchNode, "distance-nm", SGRawValueMethods(*this, &GPS::getScratchDistance, NULL)); + tie(_scratchNode, "true-bearing-deg", SGRawValueMethods(*this, &GPS::getScratchTrueBearing, NULL)); + tie(_scratchNode, "mag-bearing-deg", SGRawValueMethods(*this, &GPS::getScratchMagBearing, NULL)); + tie(_scratchNode, "has-next", SGRawValueMethods(*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 + (*this, &GPS::getWP0Ident, NULL)); + tie(wp0_node, "name", SGRawValueMethods + (*this, &GPS::getWP0Name, NULL)); + + tieSGGeodReadOnly(wp1_node, _wp1_position, "longitude-deg", "latitude-deg", "altitude-ft"); + tie(wp1_node, "ID", SGRawValueMethods + (*this, &GPS::getWP1Ident, NULL)); + tie(wp1_node, "name", SGRawValueMethods + (*this, &GPS::getWP1Name, NULL)); + + tie(wp1_node, "distance-nm", SGRawValueMethods + (*this, &GPS::getWP1Distance, NULL)); + tie(wp1_node, "bearing-true-deg", SGRawValueMethods + (*this, &GPS::getWP1Bearing, NULL)); + tie(wp1_node, "bearing-mag-deg", SGRawValueMethods + (*this, &GPS::getWP1MagBearing, NULL)); + tie(wp1_node, "TTW-sec", SGRawValueMethods + (*this, &GPS::getWP1TTW, NULL)); + tie(wp1_node, "TTW", SGRawValueMethods + (*this, &GPS::getWP1TTWString, NULL)); + + tie(wp1_node, "course-deviation-deg", SGRawValueMethods + (*this, &GPS::getWP1CourseDeviation, NULL)); + tie(wp1_node, "course-error-nm", SGRawValueMethods + (*this, &GPS::getWP1CourseErrorNm, NULL)); + tie(wp1_node, "to-flag", SGRawValueMethods + (*this, &GPS::getWP1ToFlag, NULL)); + tie(wp1_node, "from-flag", SGRawValueMethods + (*this, &GPS::getWP1FromFlag, NULL)); + +// leg properties (only valid in DTO/LEG modes, not OBS) + tie(wp_node, "leg-distance-nm", SGRawValueMethods(*this, &GPS::getLegDistance, NULL)); + tie(wp_node, "leg-true-course-deg", SGRawValueMethods(*this, &GPS::getLegCourse, NULL)); + tie(wp_node, "leg-mag-course-deg", SGRawValueMethods(*this, &GPS::getLegMagCourse, NULL)); + tie(wp_node, "alt-dist-ratio", SGRawValueMethods(*this, &GPS::getAltDistanceRatio, NULL)); + +// navradio slaving properties + tie(_gpsNode, "cdi-deflection", SGRawValueMethods + (*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(aRef, &SGGeod::getLongitudeDeg, &SGGeod::setLongitudeDeg)); + tie(aNode, latStr, SGRawValueMethods(aRef, &SGGeod::getLatitudeDeg, &SGGeod::setLatitudeDeg)); + + if (altStr) { + tie(aNode, altStr, SGRawValueMethods(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(aRef, &SGGeod::getLongitudeDeg, NULL)); + tie(aNode, latStr, SGRawValueMethods(aRef, &SGGeod::getLatitudeDeg, NULL)); + + if (altStr) { + tie(aNode, altStr, SGRawValueMethods(aRef, &SGGeod::getElevationFt, NULL)); + } +} + // end of gps.cxx diff --git a/src/Instrumentation/gps.hxx b/src/Instrumentation/gps.hxx index e745418d6..76596773a 100644 --- a/src/Instrumentation/gps.hxx +++ b/src/Instrumentation/gps.hxx @@ -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 + void tie(SGPropertyNode* aNode, const char* aRelPath, const SGRawValue& 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 _tiedNodes; }; diff --git a/src/Instrumentation/navradio.cxx b/src/Instrumentation/navradio.cxx index 40a33844b..7a8215f3e 100644 --- a/src/Instrumentation/navradio.cxx +++ b/src/Instrumentation/navradio.cxx @@ -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(&_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(&_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(&_dmeInRange)); + tie("operable", SGRawValueMethods(*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 diff --git a/src/Instrumentation/navradio.hxx b/src/Instrumentation/navradio.hxx index 88f67685a..fe724a3bd 100644 --- a/src/Instrumentation/navradio.hxx +++ b/src/Instrumentation/navradio.hxx @@ -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 _sgr; + std::vector _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 + void tie(const char* aRelPath, const SGRawValue& aRawValue) + { + SGPropertyNode* nd = _radio_node->getNode(aRelPath, true); + _tiedNodes.push_back(nd); + nd->tie(aRawValue); + } public: FGNavRadio(SGPropertyNode *node); diff --git a/src/Instrumentation/tacan.cxx b/src/Instrumentation/tacan.cxx index e00ac4f1a..b17c8bb34 100755 --- a/src/Instrumentation/tacan.cxx +++ b/src/Instrumentation/tacan.cxx @@ -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); diff --git a/src/Instrumentation/testgps.cxx b/src/Instrumentation/testgps.cxx index 64060041c..cc73bded0 100644 --- a/src/Instrumentation/testgps.cxx +++ b/src/Instrumentation/testgps.cxx @@ -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; iident() << "/" << l[i]->name()); + } + + //l = FGPositioned::findWithinRange(egph->geod(), 500.0, &af); + //for (unsigned int i=0; iident() << "/" << 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 diff --git a/src/Main/fg_props.cxx b/src/Main/fg_props.cxx index 066f0262b..31f4ae0da 100644 --- a/src/Main/fg_props.cxx +++ b/src/Main/fg_props.cxx @@ -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(); } diff --git a/src/Main/renderer.cxx b/src/Main/renderer.cxx index cfe5f29a3..32d795338 100644 --- a/src/Main/renderer.cxx +++ b/src/Main/renderer.cxx @@ -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"); diff --git a/src/Model/panelnode.cxx b/src/Model/panelnode.cxx index 6ac60caaa..be6889fda 100644 --- a/src/Model/panelnode.cxx +++ b/src/Model/panelnode.cxx @@ -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! diff --git a/src/Navaids/navdb.cxx b/src/Navaids/navdb.cxx index f7c0cd8e1..4f7c10e7f 100644 --- a/src/Navaids/navdb.cxx +++ b/src/Navaids/navdb.cxx @@ -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]); } diff --git a/src/Navaids/navrecord.cxx b/src/Navaids/navrecord.cxx index 6bb71a54c..e75339804 100644 --- a/src/Navaids/navrecord.cxx +++ b/src/Navaids/navrecord.cxx @@ -91,6 +91,9 @@ void FGNavRecord::initAirportRelation() } mRunway = getRunwayFromName(_name); + if (!mRunway) { + return; + } if (type() != GS) { readAirportSceneryData(); diff --git a/src/Navaids/positioned.cxx b/src/Navaids/positioned.cxx index f3e683fb9..bbd044306 100644 --- a/src/Navaids/positioned.cxx +++ b/src/Navaids/positioned.cxx @@ -25,9 +25,7 @@ #include #include #include // for sort -#include // for char-traits toupper - -#include +#include #include #include @@ -36,6 +34,7 @@ #include #include #include +#include #include "positioned.hxx" @@ -45,62 +44,351 @@ typedef std::pair SGBoxd; + +template +inline bool +intersects(const SGVec3& v, const SGBox& 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 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& a) : + _order(a._order), + _inner(a._inner) + { + } + + Ordered& operator=(const Ordered& a) + { + _order = a._order; + _inner = a._inner; + return *this; + } + + bool operator<(const Ordered& other) const + { + return _order < other._order; + } + + bool operator>(const Ordered& 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 OrderedNode; +typedef std::greater 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, FNPQCompare> FindNearestPQueue; + +typedef Ordered OrderedPositioned; +typedef std::vector 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 BucketEntry; -typedef std::map 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(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(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(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; rname(), 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(-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"); diff --git a/src/Scripting/NasalSys.cxx b/src/Scripting/NasalSys.cxx index 502a406b2..3b1d99529 100644 --- a/src/Scripting/NasalSys.cxx +++ b/src/Scripting/NasalSys.cxx @@ -32,6 +32,7 @@ #include
#include
#include +#include #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); } diff --git a/tests/Makefile.am b/tests/Makefile.am index 103660b06..2062c36ba 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -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 index 5ff0a8aca..000000000 --- a/tests/est-epsilon.c +++ /dev/null @@ -1,34 +0,0 @@ -#ifdef HAVE_CONFIG_H -# include -#endif - -#ifdef HAVE_WINDOWS_H -# include -#endif - -#include - -#include -#if defined( __APPLE__) -# include -#else -# include -#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 index 000000000..5ff0a8aca --- /dev/null +++ b/tests/est-epsilon.cxx @@ -0,0 +1,34 @@ +#ifdef HAVE_CONFIG_H +# include +#endif + +#ifdef HAVE_WINDOWS_H +# include +#endif + +#include + +#include +#if defined( __APPLE__) +# include +#else +# include +#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 index 7d5be7d37..000000000 --- a/tests/gl-info.c +++ /dev/null @@ -1,123 +0,0 @@ -/* -From: Steve Baker -Sender: root@fatcity.com -To: OPENGL-GAMEDEV-L -Subject: Re: Win32 OpenGL Resource Page -Date: Fri, 24 Apr 1998 07:33:51 -0800 -*/ - - -#ifdef HAVE_CONFIG_H -# include -#endif - -#ifdef HAVE_WINDOWS_H -# include -#endif - -#include -#include - -#include -#if defined( __APPLE__) -# include -# include -#else -# include -# ifdef HAVE_GLUT_H -# include -# 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 index 000000000..7d5be7d37 --- /dev/null +++ b/tests/gl-info.cxx @@ -0,0 +1,123 @@ +/* +From: Steve Baker +Sender: root@fatcity.com +To: OPENGL-GAMEDEV-L +Subject: Re: Win32 OpenGL Resource Page +Date: Fri, 24 Apr 1998 07:33:51 -0800 +*/ + + +#ifdef HAVE_CONFIG_H +# include +#endif + +#ifdef HAVE_WINDOWS_H +# include +#endif + +#include +#include + +#include +#if defined( __APPLE__) +# include +# include +#else +# include +# ifdef HAVE_GLUT_H +# include +# 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 index 000000000..6a862cbd6 --- /dev/null +++ b/utils/Modeller/yasim_import.py @@ -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 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: + + + 3.45 + -0.4 + 5 + + +becomes: + + + +Possible variables are: + + x ... + y ... + z ... + h ... + p ... + r ... + +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(""): + 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() +