#include <cassert>
#include <boost/foreach.hpp>
+#include <boost/algorithm/string/case_conv.hpp>
#include <algorithm>
#include <osg/Array>
#include <simgear/scene/model/model.hxx>
#include <simgear/structure/exception.hxx>
#include <simgear/misc/sg_path.hxx>
+#include <simgear/misc/strutils.hxx>
#include <simgear/math/sg_geodesy.hxx>
#include <sstream>
using std::setfill;
using std::cout;
using std::endl;
+using std::map;
+using std::string;
#include <Main/fg_props.hxx>
#include <Main/globals.hxx>
return osg::Vec2(r.x(), r.y());
}
-///////////////////////////////////////////////////////////////////
-
-class SymbolDef
+class NavDisplay::CacheListener : public SGPropertyChangeListener
{
public:
- SymbolDef() :
- enable(NULL)
+ CacheListener(NavDisplay *nd) :
+ _nd(nd)
{}
+
+ virtual void valueChanged (SGPropertyNode * prop)
+ {
+ _nd->invalidatePositionedCache();
+ }
+private:
+ NavDisplay* _nd;
+};
+
+class NavDisplay::ForceUpdateListener : public SGPropertyChangeListener
+{
+public:
+ ForceUpdateListener(NavDisplay *nd) :
+ _nd(nd)
+ {}
- bool initFromNode(SGPropertyNode* node)
+ virtual void valueChanged (SGPropertyNode * prop)
+ {
+ _nd->forceUpdate();
+ }
+private:
+ NavDisplay* _nd;
+};
+
+///////////////////////////////////////////////////////////////////
+
+class SymbolRule
+{
+public:
+ SymbolRule()
+ {
+
+ }
+
+ bool initFromNode(SGPropertyNode* node, NavDisplay* owner)
{
+ if (!node->getChild("type")) {
+ return false;
+ }
+
type = node->getStringValue("type");
+ boost::to_lower(type);
SGPropertyNode* enableNode = node->getChild("enable");
if (enableNode) {
- enable = sgReadCondition(fgGetNode("/"), enableNode);
+ enable.reset(sgReadCondition(fgGetNode("/"), enableNode));
}
-
+
int n=0;
while (node->hasChild("state", n)) {
string m = node->getChild("state", n++)->getStringValue();
}
} // of matches parsing
- xy0.x() = node->getFloatValue("x0", -5);
- xy0.y() = node->getFloatValue("y0", -5);
- xy1.x() = node->getFloatValue("x1", 5);
- xy1.y() = node->getFloatValue("y1", 5);
+
+ return true;
+ }
+
+ void setDefinition(SymbolDef* d)
+ {
+ definition = d;
+ }
+
+ SymbolDef* getDefinition() const
+ { return definition; }
+
+ bool matches(const string_set& states) const
+ {
+ BOOST_FOREACH(const string& s, required_states) {
+ if (states.count(s) == 0) {
+ return false;
+ }
+ }
+
+ BOOST_FOREACH(const string& s, excluded_states) {
+ if (states.count(s) != 0) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ // return if the enabled state changed (needs a cache update)
+ bool checkEnabled()
+ {
+ if (enable.get()) {
+ bool wasEnabled = enabled;
+ enabled = enable->test();
+ return (enabled != wasEnabled);
+ } else {
+ enabled = true;
+ return false;
+ }
+ }
+
+ bool enabled; // cached enabled state
+ std::string type;
+
+ // record instances for limiting by count
+ int instanceCount;
+private:
+ SymbolDef* definition;
+
+ std::auto_ptr<SGCondition> enable;
+ string_set required_states;
+ string_set excluded_states;
+};
+
+class SymbolDef
+{
+public:
+ SymbolDef() : limitCount(0) { }
+
+ bool initFromNode(SGPropertyNode* node, NavDisplay* owner)
+ {
+ if (node->getChild("type")) {
+ SymbolRule* builtinRule = new SymbolRule;
+ builtinRule->initFromNode(node, owner);
+ builtinRule->setDefinition(this);
+ owner->addRule(builtinRule);
+ }
- double texSize = node->getFloatValue("texture-size", 1.0);
+ if (node->hasChild("width")) {
+ float w = node->getFloatValue("width");
+ float h = node->getFloatValue("height", w);
+ xy0.x() = -w * 0.5;
+ xy0.y() = -h * 0.5;
+ xy1.x() = w * 0.5;
+ xy1.y() = h * 0.5;
+ } else {
+ xy0.x() = node->getFloatValue("x0", 0.0);
+ xy0.y() = node->getFloatValue("y0", 0.0);
+ xy1.x() = node->getFloatValue("x1", 5);
+ xy1.y() = node->getFloatValue("y1", 5);
+ }
+
+ double texSize = node->getFloatValue("texture-size", owner->textureSize());
uv0.x() = node->getFloatValue("u0", 0) / texSize;
uv0.y() = node->getFloatValue("v0", 0) / texSize;
textOffset.x() = node->getFloatValue("text-offset-x", 0);
textOffset.y() = node->getFloatValue("text-offset-y", 0);
textColor = readColor(node->getChild("text-color"), color);
+
+ SGPropertyNode* enableNode = node->getChild("text-enable");
+ if (enableNode) {
+ textEnable.reset(sgReadCondition(fgGetNode("/"), enableNode));
+ }
}
drawLine = node->getBoolValue("draw-line", false);
lineColor = readColor(node->getChild("line-color"), color);
- drawRouteLeg = node->getBoolValue("draw-line", false);
+ drawRouteLeg = node->getBoolValue("draw-leg", false);
stretchSymbol = node->getBoolValue("stretch-symbol", false);
if (stretchSymbol) {
stretchY2 = node->getFloatValue("y2");
stretchY3 = node->getFloatValue("y3");
- stretchV2 = node->getFloatValue("v2");
- stretchV3 = node->getFloatValue("v3");
+ stretchV2 = node->getFloatValue("v2") / texSize;
+ stretchV3 = node->getFloatValue("v3") / texSize;
}
+ SGPropertyNode* limitNode = node->getChild("limit");
+ if (limitNode) {
+ limitCount = limitNode->getIntValue();
+ }
+
return true;
}
- SGCondition* enable;
- bool enabled; // cached enabled state
-
- std::string type;
- string_set required_states;
- string_set excluded_states;
-
osg::Vec2 xy0, xy1;
osg::Vec2 uv0, uv1;
osg::Vec4 color;
bool rotateToHeading;
bool roundPos; ///< should position be rounded to integer values
bool hasText;
+ std::auto_ptr<SGCondition> textEnable;
+ bool textEnabled; ///< cache condition result
osg::Vec4 textColor;
osg::Vec2 textOffset;
osgText::Text::AlignmentType alignment;
bool drawRouteLeg;
- bool matches(const string_set& states) const
- {
- string_set::const_iterator it = states.begin(),
- end = states.end();
- for (; it != end; ++it) {
- if (!required_states.empty() && (required_states.count(*it) == 0)) {
- // required state not matched
- return false;
- }
-
- if (excluded_states.count(*it) > 0) {
- // excluded state matched
- return false;
- }
- } // of applicable states iteration
-
- return true; // matches!
- }
+ int limitCount, instanceCount;
};
class SymbolInstance
string text() const
{
assert(definition->hasText);
- string r;
-
- size_t pos = 0;
- int lastPos = 0;
+ string r;
+ size_t lastPos = 0;
- for (; pos < definition->textTemplate.size();) {
- pos = definition->textTemplate.find('{', pos);
- if (pos == string::npos) { // no more replacements
+ while (true) {
+ size_t pos = definition->textTemplate.find('{', lastPos);
+ if (pos == string::npos) { // no more replacements
r.append(definition->textTemplate.substr(lastPos));
break;
}
string spec = definition->textTemplate.substr(pos + 1, endReplacement - (pos + 1));
// look for formatter in spec
size_t colonPos = spec.find(':');
- if (colonPos == string::npos) {
+ if (colonPos == string::npos) {
// simple replacement
r.append(props->getStringValue(spec));
} else {
_num(node->getIntValue("number", 0)),
_time(0.0),
_updateInterval(node->getDoubleValue("update-interval-sec", 0.1)),
+ _forceUpdate(true),
_odg(0),
_scale(0),
_view_heading(0),
- _resultTexture(0),
_font_size(0),
_font_spacing(0),
- _rangeNm(0)
+ _rangeNm(0),
+ _maxSymbols(100)
{
_Instrument = fgGetNode(string("/instrumentation/" + _name).c_str(), _num, true);
_font_node = _Instrument->getNode("font", true);
INITFONT("color/alpha", 1, Float);
#undef INITFONT
+ _textureSize = _Instrument->getNode("symbol-texture-size", true)->getIntValue();
SGPropertyNode* symbolsNode = node->getNode("symbols");
SGPropertyNode* symbol;
-
+
+ map<string, SymbolDef*> definitionDict;
for (int i = 0; (symbol = symbolsNode->getChild("symbol", i)) != NULL; ++i) {
SymbolDef* def = new SymbolDef;
- if (!def->initFromNode(symbol)) {
+ if (!def->initFromNode(symbol, this)) {
delete def;
continue;
}
- _rules.push_back(def);
+ const char* id = symbol->getStringValue("id");
+ if (id && strlen(id)) {
+ definitionDict[id] = def;
+ }
+
+ _definitions.push_back(def);
} // of symbol definition parsing
+
+ SGPropertyNode* rulesNode = node->getNode("rules");
+ if (rulesNode) {
+ SGPropertyNode* rule;
+
+ for (int i = 0; (rule = rulesNode->getChild("rule", i)) != NULL; ++i) {
+ SymbolRule* r = new SymbolRule;
+ if (!r->initFromNode(rule, this)) {
+ delete r;
+ continue;
+ }
+
+ const char* id = symbol->getStringValue("symbol");
+ if (id && strlen(id) && (definitionDict.find(id) != definitionDict.end())) {
+ r->setDefinition(definitionDict[id]);
+ } else {
+ SG_LOG(SG_INSTR, SG_WARN, "symbol rule has missing/unknown definition id:" << id);
+ delete r;
+ continue;
+ }
+
+ addRule(r);
+ } // of symbol rule parsing
+ }
+
}
NavDisplay::~NavDisplay()
{
+ delete _odg;
}
-
void
NavDisplay::init ()
{
+ _cachedItemsValid = false;
+ _cacheListener.reset(new CacheListener(this));
+ _forceUpdateListener.reset(new ForceUpdateListener(this));
+
_serviceable_node = _Instrument->getNode("serviceable", true);
-
+ _rangeNode = _Instrument->getNode("range", true);
+ if (!_rangeNode->hasValue()) {
+ _rangeNode->setDoubleValue(40.0);
+ }
+ _rangeNode->addChangeListener(_cacheListener.get());
+ _rangeNode->addChangeListener(_forceUpdateListener.get());
+
+ _xCenterNode = _Instrument->getNode("x-center");
+ if (!_xCenterNode->hasValue()) {
+ _xCenterNode->setDoubleValue(0.5);
+ }
+ _xCenterNode->addChangeListener(_forceUpdateListener.get());
+ _yCenterNode = _Instrument->getNode("y-center");
+ if (!_yCenterNode->hasValue()) {
+ _yCenterNode->setDoubleValue(0.5);
+ }
+ _yCenterNode->addChangeListener(_forceUpdateListener.get());
+
// texture name to use in 2D and 3D instruments
_texture_path = _Instrument->getStringValue("radar-texture-path",
"Aircraft/Instruments/Textures/od_wxradar.rgb");
- _resultTexture = FGTextureManager::createTexture(_texture_path.c_str(), false);
string path = _Instrument->getStringValue("symbol-texture-path",
"Aircraft/Instruments/Textures/nd-symbols.png");
SGPath tpath = globals->resolve_aircraft_path(path);
-
+ if (!tpath.exists()) {
+ SG_LOG(SG_INSTR, SG_WARN, "ND symbol texture not found:" << path);
+ }
+
// no mipmap or else alpha will mix with pixels on the border of shapes, ruining the effect
_symbolTexture = SGLoadTexture2D(tpath, NULL, false, false);
- FGInstrumentMgr *imgr = (FGInstrumentMgr *)globals->get_subsystem("instrumentation");
- _odg = (FGODGauge *)imgr->get_subsystem("od_gauge");
+ _odg = new FGODGauge;
_odg->setSize(_Instrument->getIntValue("texture-size", 512));
-
- _user_lat_node = fgGetNode("/position/latitude-deg", true);
- _user_lon_node = fgGetNode("/position/longitude-deg", true);
- _user_alt_node = fgGetNode("/position/altitude-ft", true);
-
_route = static_cast<FGRouteMgr*>(globals->get_subsystem("route-manager"));
_navRadio1Node = fgGetNode("/instrumentation/nav[0]", true);
_excessDataNode = _Instrument->getChild("excess-data", 0, true);
_excessDataNode->setBoolValue(false);
+ _testModeNode = _Instrument->getChild("test-mode", 0, true);
+ _testModeNode->setBoolValue(false);
+ _viewHeadingNode = _Instrument->getChild("view-heading-deg", 0, true);
// OSG geometry setup
_radarGeode = new osg::Geode;
osg::StateSet *stateSet = _geom->getOrCreateStateSet();
stateSet->setTextureAttributeAndModes(0, _symbolTexture.get());
-
+ stateSet->setDataVariance(osg::Object::STATIC);
+
// Initially allocate space for 128 quads
_vertices = new osg::Vec2Array;
_vertices->setDataVariance(osg::Object::DYNAMIC);
_geom->setTexCoordArray(0, _texCoords);
_quadColors = new osg::Vec4Array;
+ _quadColors->setDataVariance(osg::Object::DYNAMIC);
_geom->setColorBinding(osg::Geometry::BIND_PER_VERTEX);
_geom->setColorArray(_quadColors);
_geom->setInitialBound(osg::BoundingBox(osg::Vec3f(-256.0f, -256.0f, 0.0f),
osg::Vec3f(256.0f, 256.0f, 0.0f)));
+
_radarGeode->addDrawable(_geom);
_odg->allocRT();
// Texture in the 2D panel system
return;
}
- _time += delta_time_sec;
- if (_time < _updateInterval){
- return;
+ if (_forceUpdate) {
+ _forceUpdate = false;
+ _time = 0.0;
+ } else {
+ _time += delta_time_sec;
+ if (_time < _updateInterval){
+ return;
+ }
+ _time -= _updateInterval;
}
- _time -= _updateInterval;
- _rangeNm = _Instrument->getFloatValue("range", 40.0);
- if (_Instrument->getBoolValue("aircraft-heading-up", true)) {
+ _rangeNm = _rangeNode->getFloatValue();
+ if (_testModeNode->getBoolValue()) {
+ _view_heading = 90;
+ } else if (_Instrument->getBoolValue("aircraft-heading-up", true)) {
_view_heading = fgGetDouble("/orientation/heading-deg");
} else {
_view_heading = _Instrument->getFloatValue("heading-up-deg", 0.0);
}
+ _viewHeadingNode->setDoubleValue(_view_heading);
- _scale = _odg->size() / _rangeNm;
+ double xCenterFrac = _xCenterNode->getDoubleValue();
+ double yCenterFrac = _yCenterNode->getDoubleValue();
+ int pixelSize = _odg->size();
+
+ int rangePixels = _Instrument->getIntValue("range-pixels", -1);
+ if (rangePixels < 0) {
+ // hacky - assume (as is very common) that x-frac doesn't vary, and
+ // y-frac is used to position the center at either the top or bottom of
+ // the pixel area. Measure from the center to the furthest edge (top or bottom)
+ rangePixels = pixelSize * std::max(fabs(1.0 - yCenterFrac), fabs(yCenterFrac));
+ }
- double xCenterFrac = _Instrument->getDoubleValue("x-center", 0.5);
- double yCenterFrac = _Instrument->getDoubleValue("y-center", 0.5);
- _centerTrans = osg::Matrixf::translate(xCenterFrac * _odg->size(),
- yCenterFrac * _odg->size(), 0.0);
+ _scale = rangePixels / _rangeNm;
+ _Instrument->setDoubleValue("scale", _scale);
+
+
+ _centerTrans = osg::Matrixf::translate(xCenterFrac * pixelSize,
+ yCenterFrac * pixelSize, 0.0);
// scale from nm to display units, rotate so aircraft heading is up
// (as opposed to north), and compensate for centering
_projectMat = osg::Matrixf::scale(_scale, _scale, 1.0) *
degRotation(-_view_heading) * _centerTrans;
- _pos = SGGeod::fromDegFt(_user_lon_node->getDoubleValue(),
- _user_lat_node->getDoubleValue(),
- _user_alt_node->getDoubleValue());
+ _pos = globals->get_aircraft_position();
+
+ // invalidate the cache of positioned items, if we travelled more than 1nm
+ if (_cachedItemsValid) {
+ SGVec3d cartNow(SGVec3d::fromGeod(_pos));
+ double movedNm = dist(_cachedPos, cartNow) * SG_METER_TO_NM;
+ _cachedItemsValid = (movedNm < 1.0);
+ }
_vertices->clear();
_lineVertices->clear();
_lineColors->clear();
+ _quadColors->clear();
_texCoords->clear();
_textGeode->removeDrawables(0, _textGeode->getNumDrawables());
- BOOST_FOREACH(SymbolDef* def, _rules) {
- if (def->enable) {
- def->enabled = def->enable->test();
- } else {
- def->enabled = true;
- }
+ BOOST_FOREACH(SymbolInstance* si, _symbols) {
+ delete si;
+ }
+ _symbols.clear();
+
+ BOOST_FOREACH(SymbolDef* d, _definitions) {
+ d->instanceCount = 0;
+ d->textEnabled = d->textEnable.get() ? d->textEnable->test() : true;
+ }
+
+ bool enableChanged = false;
+ BOOST_FOREACH(SymbolRule* r, _rules) {
+ enableChanged |= r->checkEnabled();
}
- processRoute();
- processNavRadios();
- processAI();
- findItems();
- limitDisplayedSymbols();
+ if (enableChanged) {
+ SG_LOG(SG_INSTR, SG_INFO, "NS rule enables changed, rebuilding cache");
+ _cachedItemsValid = false;
+ }
+
+ if (_testModeNode->getBoolValue()) {
+ addTestSymbols();
+ } else {
+ processRoute();
+ processNavRadios();
+ processAI();
+ findItems();
+ limitDisplayedSymbols();
+ }
+
addSymbolsToScene();
_symbolPrimSet->set(osg::PrimitiveSet::QUADS, 0, _vertices->size());
verts[3] = osg::Vec2(def->xy0.x(), def->xy1.y());
if (def->rotateToHeading) {
- osg::Matrixf m(degRotation(sym->headingDeg));
+ osg::Matrixf m(degRotation(sym->headingDeg - _view_heading));
for (int i=0; i<4; ++i) {
verts[i] = mult(verts[i], m);
}
_texCoords->push_back(osg::Vec2(def->uv1.x(), def->uv0.y()));
_texCoords->push_back(def->uv1);
_texCoords->push_back(osg::Vec2(def->uv0.x(), def->uv1.y()));
-
+
for (int i=0; i<4; ++i) {
_vertices->push_back(verts[i] + pos);
_quadColors->push_back(def->color);
stretchVerts[2] = osg::Vec2(def->xy1.x(), def->stretchY3);
stretchVerts[3] = osg::Vec2(def->xy0.x(), def->stretchY3);
- osg::Matrixf m(degRotation(sym->headingDeg));
+ osg::Matrixf m(degRotation(sym->headingDeg - _view_heading));
for (int i=0; i<4; ++i) {
stretchVerts[i] = mult(stretchVerts[i], m);
}
addLine(sym->pos, sym->endPos, def->lineColor);
}
- if (!def->hasText) {
+ if (!def->hasText || !def->textEnabled) {
return;
}
void NavDisplay::limitDisplayedSymbols()
{
- unsigned int maxSymbols = _Instrument->getIntValue("max-symbols", 100);
- if (_symbols.size() <= maxSymbols) {
+// gloabl symbol limit
+ _maxSymbols= _Instrument->getIntValue("max-symbols", _maxSymbols);
+ if ((int) _symbols.size() <= _maxSymbols) {
_excessDataNode->setBoolValue(false);
return;
}
std::sort(_symbols.begin(), _symbols.end(), OrderByPriority());
- _symbols.resize(maxSymbols);
+ _symbols.resize(_maxSymbols);
_excessDataNode->setBoolValue(true);
}
class Filter : public FGPositioned::Filter
{
public:
+ Filter(NavDisplay* nd) : _owner(nd) { }
+
+ double minRunwayLengthFt;
+
virtual bool pass(FGPositioned* aPos) const
{
if (aPos->type() == FGPositioned::FIX) {
}
}
- return true;
+ if (aPos->type() == FGPositioned::AIRPORT) {
+ FGAirport* apt = (FGAirport*) aPos;
+ if (!apt->hasHardRunwayOfLengthFt(minRunwayLengthFt)) {
+ return false;
+ }
+ }
+
+ // check against current rule states
+ return _owner->isPositionedShown(aPos);
}
virtual FGPositioned::Type minType() const {
virtual FGPositioned::Type maxType() const {
return FGPositioned::OBSTACLE;
}
+
+private:
+ NavDisplay* _owner;
};
void NavDisplay::findItems()
{
- Filter filt;
- FGPositioned::List items =
- FGPositioned::findWithinRange(_pos, _rangeNm, &filt);
-
- FGPositioned::List::const_iterator it;
- for (it = items.begin(); it != items.end(); ++it) {
- foundPositionedItem(*it);
+ if (!_cachedItemsValid) {
+ Filter filt(this);
+ filt.minRunwayLengthFt = fgGetDouble("/sim/navdb/min-runway-length-ft", 2000);
+ _itemsInRange = FGPositioned::findClosestN(_pos, _maxSymbols, _rangeNm, &filt);
+ _cachedItemsValid = true;
+ _cachedPos = SGVec3d::fromGeod(_pos);
+ }
+
+ // sort by distance from pos, so symbol limits are accurate
+ FGPositioned::sortByRange(_itemsInRange, _pos);
+
+ BOOST_FOREACH(FGPositioned* pos, _itemsInRange) {
+ foundPositionedItem(pos);
}
}
void NavDisplay::processRoute()
{
_routeSources.clear();
- RoutePath path(_route->waypts());
+ flightgear::FlightPlan* fp = _route->flightPlan();
+ RoutePath path(fp);
int current = _route->currentIndex();
- for (int w=0; w<_route->numWaypts(); ++w) {
- flightgear::WayptRef wpt(_route->wayptAtIndex(w));
+ for (int l=0; l<fp->numLegs(); ++l) {
+ flightgear::FlightPlan::Leg* leg = fp->legAtIndex(l);
+ flightgear::WayptRef wpt(leg->waypoint());
_routeSources.insert(wpt->source());
string_set state;
state.insert("on-active-route");
- if (w < current) {
+ if (l < current) {
state.insert("passed");
}
- if (w == current) {
+ if (l == current) {
state.insert("current-wp");
}
- if (w > current) {
+ if (l > current) {
state.insert("future");
}
- if (w == (current + 1)) {
+ if (l == (current + 1)) {
state.insert("next-wp");
}
- SymbolDefVector rules;
+ SymbolRuleVector rules;
findRules("waypoint" , state, rules);
if (rules.empty()) {
return; // no rules matched, we can skip this item
}
- SGGeod g = path.positionForIndex(w);
- SGPropertyNode* vars = _route->wayptNodeAtIndex(w);
+ SGGeod g = path.positionForIndex(l);
+ SGPropertyNode* vars = _route->wayptNodeAtIndex(l);
+ if (!vars) {
+ continue; // shouldn't happen, but let's guard against it
+ }
+
double heading;
computeWayptPropsAndHeading(wpt, g, vars, heading);
osg::Vec2 projected = projectGeod(g);
- BOOST_FOREACH(SymbolDef* r, rules) {
- addSymbolInstance(projected, heading, r, vars);
+ BOOST_FOREACH(SymbolRule* r, rules) {
+ addSymbolInstance(projected, heading, r->getDefinition(), vars);
- if (r->drawRouteLeg) {
- SGGeodVec gv(path.pathForIndex(w));
+ if (r->getDefinition()->drawRouteLeg) {
+ SGGeodVec gv(path.pathForIndex(l));
if (!gv.empty()) {
osg::Vec2 pr = projectGeod(gv[0]);
for (unsigned int i=1; i<gv.size(); ++i) {
osg::Vec2 p = projectGeod(gv[i]);
- addLine(pr, p, r->lineColor);
+ addLine(pr, p, r->getDefinition()->lineColor);
pr = p;
}
}
bool NavDisplay::anyRuleForType(const string& type) const
{
- BOOST_FOREACH(SymbolDef* r, _rules) {
+ BOOST_FOREACH(SymbolRule* r, _rules) {
if (!r->enabled) {
continue;
}
return false;
}
-bool NavDisplay::anyRuleMatches(const string& type, const string_set& states) const
+void NavDisplay::findRules(const string& type, const string_set& states, SymbolRuleVector& rules)
{
- BOOST_FOREACH(SymbolDef* r, _rules) {
- if (!r->enabled || (r->type != type)) {
- continue;
- }
-
- if (r->matches(states)) {
- return true;
- }
- } // of rules iteration
-
- return false;
-}
-
-void NavDisplay::findRules(const string& type, const string_set& states, SymbolDefVector& rules)
-{
- BOOST_FOREACH(SymbolDef* candidate, _rules) {
+ BOOST_FOREACH(SymbolRule* candidate, _rules) {
if (!candidate->enabled || (candidate->type != type)) {
continue;
}
}
}
+bool NavDisplay::isPositionedShown(FGPositioned* pos)
+{
+ SymbolRuleVector rules;
+ isPositionedShownInner(pos, rules);
+ return !rules.empty();
+}
+
+void NavDisplay::isPositionedShownInner(FGPositioned* pos, SymbolRuleVector& rules)
+{
+ string type = FGPositioned::nameForType(pos->type());
+ boost::to_lower(type);
+ if (!anyRuleForType(type)) {
+ return; // not diplayed at all, we're done
+ }
+
+ string_set states;
+ computePositionedState(pos, states);
+
+ findRules(type, states, rules);
+}
+
void NavDisplay::foundPositionedItem(FGPositioned* pos)
{
if (!pos) {
return;
}
- string type = FGPositioned::nameForType(pos->type());
- if (!anyRuleForType(type)) {
- return; // not diplayed at all, we're done
- }
-
- string_set states;
- computePositionedState(pos, states);
-
- SymbolDefVector rules;
- findRules(type, states, rules);
+ SymbolRuleVector rules;
+ isPositionedShownInner(pos, rules);
if (rules.empty()) {
- return; // no rules matched, we can skip this item
+ return;
}
-
+
SGPropertyNode_ptr vars(new SGPropertyNode);
double heading;
computePositionedPropsAndHeading(pos, vars, heading);
osg::Vec2 projected = projectGeod(pos->geod());
- BOOST_FOREACH(SymbolDef* r, rules) {
- addSymbolInstance(projected, heading, r, vars);
+ if (pos->type() == FGPositioned::RUNWAY) {
+ FGRunway* rwy = (FGRunway*) pos;
+ projected = projectGeod(rwy->threshold());
+ }
+
+ BOOST_FOREACH(SymbolRule* r, rules) {
+ SymbolInstance* ins = addSymbolInstance(projected, heading, r->getDefinition(), vars);
+ if (pos->type() == FGPositioned::RUNWAY) {
+ FGRunway* rwy = (FGRunway*) pos;
+ ins->endPos = projectGeod(rwy->end());
+ }
}
}
nd->setStringValue("name", pos->name());
nd->setDoubleValue("elevation-ft", pos->elevation());
nd->setIntValue("heading-deg", 0);
+ heading = 0.0;
switch (pos->type()) {
case FGPositioned::VOR:
- case FGPositioned::LOC: {
+ case FGPositioned::LOC:
+ case FGPositioned::TACAN: {
FGNavRecord* nav = static_cast<FGNavRecord*>(pos);
nd->setDoubleValue("frequency-mhz", nav->get_freq());
if (pos == _nav1Station) {
- nd->setIntValue("heading-deg", _navRadio1Node->getDoubleValue("radials/target-radial-deg"));
+ heading = _navRadio1Node->getDoubleValue("radials/target-radial-deg");
} else if (pos == _nav2Station) {
- nd->setIntValue("heading-deg", _navRadio2Node->getDoubleValue("radials/target-radial-deg"));
+ heading = _navRadio2Node->getDoubleValue("radials/target-radial-deg");
}
+ nd->setIntValue("heading-deg", heading);
break;
}
case FGPositioned::RUNWAY: {
FGRunway* rwy = static_cast<FGRunway*>(pos);
- nd->setDoubleValue("heading-deg", rwy->headingDeg());
+ heading = rwy->headingDeg();
+ nd->setDoubleValue("heading-deg", heading);
nd->setIntValue("length-ft", rwy->lengthFt());
nd->setStringValue("airport", rwy->airport()->ident());
break;
states.insert("on-active-route");
}
+ flightgear::FlightPlan* fp = _route->flightPlan();
switch (pos->type()) {
case FGPositioned::VOR:
case FGPositioned::LOC:
// mark alternates!
// once the FMS system has some way to tell us about them, of course
- if (pos == _route->departureAirport()) {
+ if (pos == fp->departureAirport()) {
states.insert("departure");
}
- if (pos == _route->destinationAirport()) {
+ if (pos == fp->destinationAirport()) {
states.insert("destination");
}
break;
case FGPositioned::RUNWAY:
- if (pos == _route->departureRunway()) {
+ if (pos == fp->departureRunway()) {
states.insert("departure");
}
- if (pos == _route->destinationRunway()) {
+ if (pos == fp->destinationRunway()) {
states.insert("destination");
}
break;
} // FGPositioned::Type switch
}
+static string mapAINodeToType(SGPropertyNode* model)
+{
+ // assume all multiplayer items are aircraft for the moment. Not ideal.
+ if (!strcmp(model->getName(), "multiplayer")) {
+ return "ai-aircraft";
+ }
+
+ return string("ai-") + model->getName();
+}
+
void NavDisplay::processAI()
{
SGPropertyNode *ai = fgGetNode("/ai/models", true);
// with fg-positioned.
string_set ss;
computeAIStates(model, ss);
- SymbolDefVector rules;
- findRules(string("ai-") + model->getName(), ss, rules);
+ SymbolRuleVector rules;
+ findRules(mapAINodeToType(model), ss, rules);
if (rules.empty()) {
return; // no rules matched, we can skip this item
}
model->setIntValue("flight-level", fl * 10);
osg::Vec2 projected = projectGeod(aiModelPos);
- BOOST_FOREACH(SymbolDef* r, rules) {
- addSymbolInstance(projected, heading, r, (SGPropertyNode*) model);
+ BOOST_FOREACH(SymbolRule* r, rules) {
+ addSymbolInstance(projected, heading, r->getDefinition(), (SGPropertyNode*) model);
}
} // of ai models iteration
}
void NavDisplay::computeAIStates(const SGPropertyNode* ai, string_set& states)
{
int threatLevel = ai->getIntValue("tcas/threat-level",-1);
- if (threatLevel >= 0) {
- states.insert("tcas");
- // states.insert("tcas-threat-level-" + itoa(threatLevel));
- }
-
+ if (threatLevel < 1)
+ threatLevel = 0;
+
+ states.insert("tcas");
+
+ std::ostringstream os;
+ os << "tcas-threat-level-" << threatLevel;
+ states.insert(os.str());
+
double vspeed = ai->getDoubleValue("velocities/vertical-speed-fps");
if (vspeed < -3.0) {
states.insert("descending");
}
}
-void NavDisplay::addSymbolInstance(const osg::Vec2& proj, double heading, SymbolDef* def, SGPropertyNode* vars)
+SymbolInstance* NavDisplay::addSymbolInstance(const osg::Vec2& proj, double heading, SymbolDef* def, SGPropertyNode* vars)
{
+ if (isProjectedClipped(proj)) {
+ return NULL;
+ }
+
+ if ((def->limitCount > 0) && (def->instanceCount >= def->limitCount)) {
+ return NULL;
+ }
+
+ ++def->instanceCount;
SymbolInstance* sym = new SymbolInstance(proj, heading, def, vars);
_symbols.push_back(sym);
+ return sym;
+}
+
+bool NavDisplay::isProjectedClipped(const osg::Vec2& projected) const
+{
+ double size = _odg->size();
+ return (projected.x() < 0.0) ||
+ (projected.y() < 0.0) ||
+ (projected.x() >= size) ||
+ (projected.y() >= size);
}
+void NavDisplay::addTestSymbol(const std::string& type, const std::string& states, const SGGeod& pos, double heading, SGPropertyNode* vars)
+{
+ string_set stateSet;
+ BOOST_FOREACH(std::string s, simgear::strutils::split(states, ",")) {
+ stateSet.insert(s);
+ }
+
+ SymbolRuleVector rules;
+ findRules(type, stateSet, rules);
+ if (rules.empty()) {
+ return; // no rules matched, we can skip this item
+ }
+
+ osg::Vec2 projected = projectGeod(pos);
+ BOOST_FOREACH(SymbolRule* r, rules) {
+ addSymbolInstance(projected, heading, r->getDefinition(), vars);
+ }
+}
+void NavDisplay::addTestSymbols()
+{
+ _pos = SGGeod::fromDeg(-122.3748889, 37.6189722); // KSFO
+
+ SGGeod a1;
+ double dummy;
+ SGGeodesy::direct(_pos, 45.0, 20.0 * SG_NM_TO_METER, a1, dummy);
+
+ addTestSymbol("airport", "", a1, 0.0, NULL);
+
+ SGGeodesy::direct(_pos, 95.0, 40.0 * SG_NM_TO_METER, a1, dummy);
+
+ addTestSymbol("vor", "", a1, 0.0, NULL);
+
+ SGGeodesy::direct(_pos, 120, 80.0 * SG_NM_TO_METER, a1, dummy);
+
+ addTestSymbol("airport", "destination", a1, 0.0, NULL);
+
+ SGGeodesy::direct(_pos, 80.0, 20.0 * SG_NM_TO_METER, a1, dummy);
+ addTestSymbol("fix", "", a1, 0.0, NULL);
+
+
+ SGGeodesy::direct(_pos, 140.0, 20.0 * SG_NM_TO_METER, a1, dummy);
+ addTestSymbol("fix", "", a1, 0.0, NULL);
+
+ SGGeodesy::direct(_pos, 110.0, 10.0 * SG_NM_TO_METER, a1, dummy);
+ addTestSymbol("fix", "", a1, 0.0, NULL);
+
+ SGGeodesy::direct(_pos, 110.0, 5.0 * SG_NM_TO_METER, a1, dummy);
+ addTestSymbol("fix", "", a1, 0.0, NULL);
+}
+
+void NavDisplay::addRule(SymbolRule* r)
+{
+ _rules.push_back(r);
+}