]> git.mxchange.org Git - flightgear.git/blob - src/Cockpit/NavDisplay.cxx
Expose more runway methods to Nasal
[flightgear.git] / src / Cockpit / NavDisplay.cxx
1 // navigation display texture
2 //
3 // Written by James Turner, forked from wxradar code
4 //
5 //
6 // This program is free software; you can redistribute it and/or
7 // modify it under the terms of the GNU General Public License as
8 // published by the Free Software Foundation; either version 2 of the
9 // License, or (at your option) any later version.
10 //
11 // This program is distributed in the hope that it will be useful, but
12 // WITHOUT ANY WARRANTY; without even the implied warranty of
13 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14 // General Public License for more details.
15 //
16 // You should have received a copy of the GNU General Public License
17 // along with this program; if not, write to the Free Software
18 // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
19 //
20 //
21
22 #ifdef HAVE_CONFIG_H
23 #  include "config.h"
24 #endif
25
26 #include "NavDisplay.hxx"
27
28 #include <cassert>
29 #include <boost/foreach.hpp>
30 #include <boost/algorithm/string/case_conv.hpp>
31 #include <algorithm>
32
33 #include <osg/Array>
34 #include <osg/Geometry>
35 #include <osg/Matrixf>
36 #include <osg/PrimitiveSet>
37 #include <osg/StateSet>
38 #include <osg/LineWidth>
39 #include <osg/Version>
40
41 #include <simgear/constants.h>
42 #include <simgear/misc/sg_path.hxx>
43 #include <simgear/scene/model/model.hxx>
44 #include <simgear/structure/exception.hxx>
45 #include <simgear/misc/sg_path.hxx>
46 #include <simgear/misc/strutils.hxx>
47 #include <simgear/math/sg_geodesy.hxx>
48
49 #include <cstdio>
50 #include <sstream>
51 #include <iomanip>
52 #include <iostream>             // for cout, endl
53
54 using std::stringstream;
55 using std::endl;
56 using std::setprecision;
57 using std::fixed;
58 using std::setw;
59 using std::setfill;
60 using std::cout;
61 using std::endl;
62 using std::map;
63 using std::string;
64
65 #include <Main/fg_props.hxx>
66 #include <Main/globals.hxx>
67 #include "panel.hxx"
68 #include <Navaids/routePath.hxx>
69 #include <Autopilot/route_mgr.hxx>
70 #include <Navaids/navrecord.hxx>
71 #include <Navaids/navlist.hxx>
72 #include <Navaids/fix.hxx>
73 #include <Airports/airport.hxx>
74 #include <Airports/runways.hxx>
75 #include "od_gauge.hxx"
76
77 static const char *DEFAULT_FONT = "typewriter.txf";
78
79 static
80 osg::Matrixf degRotation(float angle)
81 {
82     return osg::Matrixf::rotate(angle * SG_DEGREES_TO_RADIANS, 0.0f, 0.0f, -1.0f);
83 }
84
85 static osg::Vec4 readColor(SGPropertyNode* colorNode, const osg::Vec4& c)
86 {
87     osg::Vec4 result;
88     result.r() = colorNode->getDoubleValue("red",   c.r());
89     result.g() = colorNode->getDoubleValue("green", c.g());
90     result.b() = colorNode->getDoubleValue("blue",  c.b());
91     result.a() = colorNode->getDoubleValue("alpha", c.a());
92     return result;
93 }
94
95 static osgText::Text::AlignmentType readAlignment(const std::string& t)
96 {
97     if (t == "left-top") {
98         return osgText::Text::LEFT_TOP;
99     } else if (t == "left-center") {
100         return osgText::Text::LEFT_CENTER;
101     } else if (t == "left-bottom") {
102         return osgText::Text::LEFT_BOTTOM;
103     } else if (t == "center-top") {
104         return osgText::Text::CENTER_TOP;
105     } else if (t == "center-center") {
106         return osgText::Text::CENTER_CENTER;
107     } else if (t == "center-bottom") {
108         return osgText::Text::CENTER_BOTTOM;
109     } else if (t == "right-top") {
110         return osgText::Text::RIGHT_TOP;
111     } else if (t == "right-center") {
112         return osgText::Text::RIGHT_CENTER;
113     } else if (t == "right-bottom") {
114         return osgText::Text::RIGHT_BOTTOM;
115     } else if (t == "left-baseline") {
116         return osgText::Text::LEFT_BASE_LINE;
117     } else if (t == "center-baseline") {
118         return osgText::Text::CENTER_BASE_LINE;
119     } else if (t == "right-baseline") {
120         return osgText::Text::RIGHT_BASE_LINE;
121     }
122     
123     return osgText::Text::BASE_LINE;
124 }
125
126 static string formatPropertyValue(SGPropertyNode* nd, const string& format)
127 {
128     assert(nd);
129     static char buf[512];
130     if (format.find('d') != string::npos) {
131         ::snprintf(buf, 512, format.c_str(), nd->getIntValue());
132         return buf;
133     }
134     
135     if (format.find('s') != string::npos) {
136         ::snprintf(buf, 512, format.c_str(), nd->getStringValue());
137         return buf;
138     }
139     
140 // assume it's a double/float
141     ::snprintf(buf, 512, format.c_str(), nd->getDoubleValue());
142     return buf;
143 }
144
145 static osg::Vec2 mult(const osg::Vec2& v, const osg::Matrixf& m)
146 {
147     osg::Vec3 r = m.preMult(osg::Vec3(v.x(), v.y(), 0.0));
148     return osg::Vec2(r.x(), r.y());
149 }
150
151 class NavDisplay::CacheListener : public SGPropertyChangeListener
152 {
153 public:
154     CacheListener(NavDisplay *nd) : 
155         _nd(nd)
156     {}
157     
158     virtual void valueChanged (SGPropertyNode * prop)
159     {
160         _nd->invalidatePositionedCache();
161     }
162 private:
163     NavDisplay* _nd;
164 };
165
166 class NavDisplay::ForceUpdateListener : public SGPropertyChangeListener
167 {
168 public:
169   ForceUpdateListener(NavDisplay *nd) : 
170     _nd(nd)
171   {}
172   
173   virtual void valueChanged (SGPropertyNode * prop)
174   {
175     _nd->forceUpdate();
176   }
177 private:
178   NavDisplay* _nd;
179 };
180
181 ///////////////////////////////////////////////////////////////////
182
183 class SymbolRule
184 {
185 public:
186   SymbolRule()
187     {
188         
189     }
190     
191     bool initFromNode(SGPropertyNode* node, NavDisplay* owner)
192     {
193         if (!node->getChild("type")) {
194             return false;
195         }
196         
197         type = node->getStringValue("type");
198         boost::to_lower(type);
199         SGPropertyNode* enableNode = node->getChild("enable");
200         if (enableNode) { 
201             enable.reset(sgReadCondition(fgGetNode("/"), enableNode));
202         }
203         
204         int n=0;
205         while (node->hasChild("state", n)) {
206             string m = node->getChild("state", n++)->getStringValue();
207             if (m[0] == '!') {
208                 excluded_states.insert(m.substr(1));
209             } else {
210                 required_states.insert(m);
211             }
212         } // of matches parsing
213         
214           
215         return true;
216     }
217     
218     void setDefinition(SymbolDef* d)
219     {
220         definition = d;
221     }
222     
223     SymbolDef* getDefinition() const
224     { return definition; }
225     
226     bool matches(const string_set& states) const
227     {
228         BOOST_FOREACH(const string& s, required_states) {
229             if (states.count(s) == 0) {
230                 return false;
231             }
232         }
233         
234         BOOST_FOREACH(const string& s, excluded_states) {
235             if (states.count(s) != 0) {
236                 return false;
237             }
238         }
239         
240         return true;
241     }
242     
243   // return if the enabled state changed (needs a cache update)
244     bool checkEnabled()
245     {
246         if (enable.get()) {
247             bool wasEnabled = enabled;
248             enabled = enable->test();
249             return (enabled != wasEnabled);
250         } else {
251             enabled = true;
252             return false;
253         }
254     }
255     
256     bool enabled; // cached enabled state
257     std::string type;
258     
259   // record instances for limiting by count
260     int instanceCount;
261 private:
262     SymbolDef* definition;
263     
264     std::auto_ptr<SGCondition> enable;
265     string_set required_states;
266     string_set excluded_states;
267 };
268
269 class SymbolDef
270 {
271 public:
272     SymbolDef() : limitCount(0) { }
273   
274     bool initFromNode(SGPropertyNode* node, NavDisplay* owner)
275     {
276         if (node->getChild("type")) {
277             SymbolRule* builtinRule = new SymbolRule;
278             builtinRule->initFromNode(node, owner);
279             builtinRule->setDefinition(this);
280             owner->addRule(builtinRule);
281         }
282         
283         if (node->hasChild("width")) {
284             float w = node->getFloatValue("width");
285             float h = node->getFloatValue("height", w);
286             xy0.x() = -w * 0.5;
287             xy0.y() = -h * 0.5;
288             xy1.x() = w * 0.5;
289             xy1.y() = h * 0.5;
290         } else {
291             xy0.x()  = node->getFloatValue("x0", 0.0);
292             xy0.y()  = node->getFloatValue("y0", 0.0);
293             xy1.x()  = node->getFloatValue("x1", 5);
294             xy1.y()  = node->getFloatValue("y1", 5);
295         }
296       
297         double texSize = node->getFloatValue("texture-size", owner->textureSize());
298         
299         uv0.x()  = node->getFloatValue("u0", 0) / texSize;
300         uv0.y()  = node->getFloatValue("v0", 0) / texSize;
301         uv1.x()  = node->getFloatValue("u1", 1) / texSize;
302         uv1.y()  = node->getFloatValue("v1", 1) / texSize;
303         
304         color = readColor(node->getChild("color"), osg::Vec4(1, 1, 1, 1));
305         priority = node->getIntValue("priority", 0);
306         zOrder = node->getIntValue("zOrder", 0);
307         rotateToHeading = node->getBoolValue("rotate-to-heading", false);
308         roundPos = node->getBoolValue("round-position", true);
309         hasText = false;
310         if (node->hasChild("text")) {
311             hasText = true;
312             alignment = readAlignment(node->getStringValue("text-align"));
313             textTemplate = node->getStringValue("text");
314             textOffset.x() = node->getFloatValue("text-offset-x", 0);
315             textOffset.y() = node->getFloatValue("text-offset-y", 0);
316             textColor = readColor(node->getChild("text-color"), color);
317           
318             SGPropertyNode* enableNode = node->getChild("text-enable");
319             if (enableNode) { 
320               textEnable.reset(sgReadCondition(fgGetNode("/"), enableNode));
321             }
322         }
323         
324         drawLine = node->getBoolValue("draw-line", false);
325         lineColor = readColor(node->getChild("line-color"), color);
326         drawRouteLeg = node->getBoolValue("draw-leg", false);
327         
328         stretchSymbol = node->getBoolValue("stretch-symbol", false);
329         if (stretchSymbol) {
330             stretchY2 = node->getFloatValue("y2");
331             stretchY3 = node->getFloatValue("y3");
332             stretchV2 = node->getFloatValue("v2") / texSize;
333             stretchV3 = node->getFloatValue("v3") / texSize;
334         }
335       
336         SGPropertyNode* limitNode = node->getChild("limit");
337         if (limitNode) {
338           limitCount = limitNode->getIntValue();
339         }
340
341         return true;
342     }
343     
344     osg::Vec2 xy0, xy1;
345     osg::Vec2 uv0, uv1;
346     osg::Vec4 color;
347     
348     int priority;
349     int zOrder;
350     bool rotateToHeading;
351     bool roundPos; ///< should position be rounded to integer values
352     bool hasText;
353     std::auto_ptr<SGCondition> textEnable;
354     bool textEnabled; ///< cache condition result
355     osg::Vec4 textColor;
356     osg::Vec2 textOffset;
357     osgText::Text::AlignmentType alignment;
358     string textTemplate;
359     
360     bool drawLine;
361     osg::Vec4 lineColor;
362     
363 // symbol stretching creates three quads (instead of one) - a start,
364 // middle and end quad, positioned along the line of the symbol.
365 // X (and U) axis values determined by the values above, so we only need
366 // to define the Y (and V) values to build the other quads.
367     bool stretchSymbol;
368     double stretchY2, stretchY3;
369     double stretchV2, stretchV3;
370     
371     bool drawRouteLeg;
372     
373     int limitCount, instanceCount;
374 };
375
376 class SymbolInstance
377 {
378 public:
379     SymbolInstance(const osg::Vec2& p, double h, SymbolDef* def, SGPropertyNode* vars) :
380         pos(p),
381         headingDeg(h),
382         definition(def),
383         props(vars)
384     { }
385     
386     osg::Vec2 pos; // projected position
387     osg::Vec2 endPos;
388     double headingDeg;
389     SymbolDef* definition;
390     SGPropertyNode_ptr props;
391     
392     string text() const
393     {
394         assert(definition->hasText);
395         string r;        
396         size_t lastPos = 0;
397         
398         while (true) {
399             size_t pos = definition->textTemplate.find('{', lastPos);
400             if (pos == string::npos) { // no more replacements
401                 r.append(definition->textTemplate.substr(lastPos));
402                 break;
403             }
404             
405             r.append(definition->textTemplate.substr(lastPos, pos - lastPos));
406             
407             size_t endReplacement = definition->textTemplate.find('}', pos+1);
408             if (endReplacement <= pos) {
409                 return "bad replacement";
410             }
411
412             string spec = definition->textTemplate.substr(pos + 1, endReplacement - (pos + 1));
413         // look for formatter in spec
414             size_t colonPos = spec.find(':');
415             if (colonPos == string::npos) {
416             // simple replacement
417                 r.append(props->getStringValue(spec));
418             } else {
419                 string format = spec.substr(colonPos + 1);
420                 string prop = spec.substr(0, colonPos);
421                 r.append(formatPropertyValue(props->getNode(prop), format));
422             }
423             
424             lastPos = endReplacement + 1;
425         }
426         
427         return r;
428     }
429 };
430
431 //////////////////////////////////////////////////////////////////
432
433 NavDisplay::NavDisplay(SGPropertyNode *node) :
434     _name(node->getStringValue("name", "nd")),
435     _num(node->getIntValue("number", 0)),
436     _time(0.0),
437     _updateInterval(node->getDoubleValue("update-interval-sec", 0.1)),
438     _forceUpdate(true),
439     _odg(0),
440     _scale(0),
441     _view_heading(0),
442     _font_size(0),
443     _font_spacing(0),
444     _rangeNm(0),
445     _maxSymbols(100)
446 {
447     _Instrument = fgGetNode(string("/instrumentation/" + _name).c_str(), _num, true);
448     _font_node = _Instrument->getNode("font", true);
449
450 #define INITFONT(p, val, type) if (!_font_node->hasValue(p)) _font_node->set##type##Value(p, val)
451     INITFONT("name", DEFAULT_FONT, String);
452     INITFONT("size", 8, Float);
453     INITFONT("line-spacing", 0.25, Float);
454     INITFONT("color/red", 0, Float);
455     INITFONT("color/green", 0.8, Float);
456     INITFONT("color/blue", 0, Float);
457     INITFONT("color/alpha", 1, Float);
458 #undef INITFONT
459
460     _textureSize = _Instrument->getNode("symbol-texture-size", true)->getIntValue();
461     SGPropertyNode* symbolsNode = node->getNode("symbols");
462     SGPropertyNode* symbol;
463
464     map<string, SymbolDef*> definitionDict;
465     for (int i = 0; (symbol = symbolsNode->getChild("symbol", i)) != NULL; ++i) {
466         SymbolDef* def = new SymbolDef;
467         if (!def->initFromNode(symbol, this)) {
468           delete def;
469           continue;
470         }
471         
472         const char* id = symbol->getStringValue("id");
473         if (id && strlen(id)) {
474             definitionDict[id] = def;
475         }
476         
477         _definitions.push_back(def);
478     } // of symbol definition parsing
479     
480     BOOST_FOREACH(SGPropertyNode* rule, symbolsNode->getChildren("rule")) {
481         SymbolRule* r = new SymbolRule;
482         if (!r->initFromNode(rule, this)) {
483             delete r;
484             continue;
485         }
486         
487         const char* id = rule->getStringValue("symbol");
488         if (id && strlen(id) && (definitionDict.find(id) != definitionDict.end())) {
489             r->setDefinition(definitionDict[id]);
490         } else {
491             SG_LOG(SG_INSTR, SG_WARN, "symbol rule has missing/unknown definition id:" << id);
492             delete r;
493             continue;
494         }
495         
496         addRule(r);
497     }
498     
499 }
500
501
502 NavDisplay::~NavDisplay()
503 {
504   delete _odg;
505 }
506
507 void
508 NavDisplay::init ()
509 {
510     _cachedItemsValid = false;
511     _cacheListener.reset(new CacheListener(this));
512     _forceUpdateListener.reset(new ForceUpdateListener(this));
513   
514     _serviceable_node = _Instrument->getNode("serviceable", true);
515     _rangeNode = _Instrument->getNode("range", true);
516     if (!_rangeNode->hasValue()) {
517       _rangeNode->setDoubleValue(40.0);
518     }
519     _rangeNode->addChangeListener(_cacheListener.get());
520     _rangeNode->addChangeListener(_forceUpdateListener.get());
521   
522     _xCenterNode = _Instrument->getNode("x-center");
523     if (!_xCenterNode->hasValue()) {
524       _xCenterNode->setDoubleValue(0.5);
525     }
526     _xCenterNode->addChangeListener(_forceUpdateListener.get());
527     _yCenterNode = _Instrument->getNode("y-center");
528     if (!_yCenterNode->hasValue()) {
529       _yCenterNode->setDoubleValue(0.5);
530     }
531     _yCenterNode->addChangeListener(_forceUpdateListener.get());
532   
533     // texture name to use in 2D and 3D instruments
534     _texture_path = _Instrument->getStringValue("radar-texture-path",
535         "Aircraft/Instruments/Textures/od_wxradar.rgb");
536
537     string path = _Instrument->getStringValue("symbol-texture-path",
538         "Aircraft/Instruments/Textures/nd-symbols.png");
539     SGPath tpath = globals->resolve_aircraft_path(path);
540     if (!tpath.exists()) {
541       SG_LOG(SG_INSTR, SG_WARN, "ND symbol texture not found:" << path);
542     }
543   
544     // no mipmap or else alpha will mix with pixels on the border of shapes, ruining the effect
545     _symbolTexture = SGLoadTexture2D(tpath, NULL, false, false);
546
547     _odg = new FGODGauge;
548     _odg->setSize(_Instrument->getIntValue("texture-size", 512));
549
550     _route = static_cast<FGRouteMgr*>(globals->get_subsystem("route-manager"));
551     
552     _navRadio1Node = fgGetNode("/instrumentation/nav[0]", true);
553     _navRadio2Node = fgGetNode("/instrumentation/nav[1]", true);
554     
555     _excessDataNode = _Instrument->getChild("excess-data", 0, true);
556     _excessDataNode->setBoolValue(false);
557     _testModeNode = _Instrument->getChild("test-mode", 0, true);
558     _testModeNode->setBoolValue(false);
559   
560     _viewHeadingNode = _Instrument->getChild("view-heading-deg", 0, true);
561     _userLatNode = _Instrument->getChild("user-latitude-deg", 0, true);
562     _userLonNode = _Instrument->getChild("user-longitude-deg", 0, true);
563     _userPositionEnable = _Instrument->getChild("user-position", 0, true);
564     
565     _customSymbols = _Instrument->getChild("symbols", 0, true);
566     
567 // OSG geometry setup
568     _radarGeode = new osg::Geode;
569
570     _geom = new osg::Geometry;
571     _geom->setUseDisplayList(false);
572     
573     osg::StateSet *stateSet = _geom->getOrCreateStateSet();
574     stateSet->setTextureAttributeAndModes(0, _symbolTexture.get());
575     stateSet->setDataVariance(osg::Object::STATIC);
576   
577     // Initially allocate space for 128 quads
578     _vertices = new osg::Vec2Array;
579     _vertices->setDataVariance(osg::Object::DYNAMIC);
580     _vertices->reserve(128 * 4);
581     _geom->setVertexArray(_vertices);
582     _texCoords = new osg::Vec2Array;
583     _texCoords->setDataVariance(osg::Object::DYNAMIC);
584     _texCoords->reserve(128 * 4);
585     _geom->setTexCoordArray(0, _texCoords);
586     
587     _quadColors = new osg::Vec4Array;
588     _quadColors->setDataVariance(osg::Object::DYNAMIC);
589     _geom->setColorBinding(osg::Geometry::BIND_PER_VERTEX);
590     _geom->setColorArray(_quadColors);
591     
592     _symbolPrimSet = new osg::DrawArrays(osg::PrimitiveSet::QUADS);
593     _symbolPrimSet->setDataVariance(osg::Object::DYNAMIC);
594     _geom->addPrimitiveSet(_symbolPrimSet);
595     
596     _geom->setInitialBound(osg::BoundingBox(osg::Vec3f(-256.0f, -256.0f, 0.0f),
597         osg::Vec3f(256.0f, 256.0f, 0.0f)));
598   
599     _radarGeode->addDrawable(_geom);
600     _odg->allocRT();
601     // Texture in the 2D panel system
602     FGTextureManager::addTexture(_texture_path.c_str(), _odg->getTexture());
603
604     _lineGeometry = new osg::Geometry;
605     _lineGeometry->setUseDisplayList(false);
606     stateSet = _lineGeometry->getOrCreateStateSet();    
607     osg::LineWidth *lw = new osg::LineWidth();
608     lw->setWidth(2.0);
609     stateSet->setAttribute(lw);
610     
611     _lineVertices = new osg::Vec2Array;
612     _lineVertices->setDataVariance(osg::Object::DYNAMIC);
613     _lineVertices->reserve(128 * 4);
614     _lineGeometry->setVertexArray(_lineVertices);
615     
616                   
617     _lineColors = new osg::Vec4Array;
618     _lineColors->setDataVariance(osg::Object::DYNAMIC);
619     _lineGeometry->setColorBinding(osg::Geometry::BIND_PER_VERTEX);
620     _lineGeometry->setColorArray(_lineColors);
621     
622     _linePrimSet = new osg::DrawArrays(osg::PrimitiveSet::LINES);
623     _linePrimSet->setDataVariance(osg::Object::DYNAMIC);
624     _lineGeometry->addPrimitiveSet(_linePrimSet);
625     
626     _lineGeometry->setInitialBound(osg::BoundingBox(osg::Vec3f(-256.0f, -256.0f, 0.0f),
627                                             osg::Vec3f(256.0f, 256.0f, 0.0f)));
628
629     _radarGeode->addDrawable(_lineGeometry);              
630                   
631     _textGeode = new osg::Geode;
632
633     osg::Camera *camera = _odg->getCamera();
634     camera->addChild(_radarGeode.get());
635     camera->addChild(_textGeode.get());
636     osg::Texture2D* tex = _odg->getTexture();
637     camera->setProjectionMatrixAsOrtho2D(0, tex->getTextureWidth(), 
638         0, tex->getTextureHeight());
639     
640     updateFont();
641 }
642
643 void
644 NavDisplay::update (double delta_time_sec)
645 {
646   if (!fgGetBool("sim/sceneryloaded", false)) {
647     return;
648   }
649
650   if (!_odg || !_serviceable_node->getBoolValue()) {
651     _Instrument->setStringValue("status", "");
652     return;
653   }
654   
655   if (_forceUpdate) {
656     _forceUpdate = false;
657     _time = 0.0;
658   } else {
659     _time += delta_time_sec;
660     if (_time < _updateInterval){
661       return;
662     }
663     _time -= _updateInterval;
664   }
665
666   _rangeNm = _rangeNode->getFloatValue();
667   if (_testModeNode->getBoolValue()) {
668     _view_heading = 90;
669   } else if (_Instrument->getBoolValue("aircraft-heading-up", true)) {
670     _view_heading = fgGetDouble("/orientation/heading-deg");
671   } else {
672     _view_heading = _Instrument->getFloatValue("heading-up-deg", 0.0);
673   }
674   _viewHeadingNode->setDoubleValue(_view_heading);
675   
676   double xCenterFrac = _xCenterNode->getDoubleValue();
677   double yCenterFrac = _yCenterNode->getDoubleValue();
678   int pixelSize = _odg->size();
679   
680   int rangePixels = _Instrument->getIntValue("range-pixels", -1);
681   if (rangePixels < 0) {
682     // hacky - assume (as is very common) that x-frac doesn't vary, and
683     // y-frac is used to position the center at either the top or bottom of
684     // the pixel area. Measure from the center to the furthest edge (top or bottom)
685     rangePixels = pixelSize * std::max(fabs(1.0 - yCenterFrac), fabs(yCenterFrac));
686   }
687   
688   _scale = rangePixels / _rangeNm;
689   _Instrument->setDoubleValue("scale", _scale);
690   
691   
692   _centerTrans = osg::Matrixf::translate(xCenterFrac * pixelSize, 
693       yCenterFrac * pixelSize, 0.0);
694
695 // scale from nm to display units, rotate so aircraft heading is up
696 // (as opposed to north), and compensate for centering
697   _projectMat = osg::Matrixf::scale(_scale, _scale, 1.0) * 
698       degRotation(-_view_heading) * _centerTrans;
699   
700     if (_userPositionEnable->getBoolValue()) {
701         _pos = SGGeod::fromDeg(_userLonNode->getDoubleValue(), _userLatNode->getDoubleValue());
702     } else {
703         _pos = globals->get_aircraft_position();
704     }
705     
706     // invalidate the cache of positioned items, if we travelled more than 1nm
707     if (_cachedItemsValid) {
708         SGVec3d cartNow(SGVec3d::fromGeod(_pos));
709         double movedNm = dist(_cachedPos, cartNow) * SG_METER_TO_NM;
710         _cachedItemsValid = (movedNm < 1.0);
711     }
712     
713   _vertices->clear();
714   _lineVertices->clear();
715   _lineColors->clear();
716   _quadColors->clear();
717   _texCoords->clear();
718   _textGeode->removeDrawables(0, _textGeode->getNumDrawables());
719   
720   BOOST_FOREACH(SymbolInstance* si, _symbols) {
721       delete si;
722   }
723   _symbols.clear();
724   
725   BOOST_FOREACH(SymbolDef* d, _definitions) {
726     d->instanceCount = 0;
727     d->textEnabled = d->textEnable.get() ? d->textEnable->test() : true;
728   }
729   
730   bool enableChanged = false;
731   BOOST_FOREACH(SymbolRule* r, _rules) {
732       enableChanged |= r->checkEnabled();
733   }
734   
735   if (enableChanged) {
736     SG_LOG(SG_INSTR, SG_INFO, "NS rule enables changed, rebuilding cache");
737     _cachedItemsValid = false;
738   }
739   
740   if (_testModeNode->getBoolValue()) {
741     addTestSymbols();
742   } else {
743     processRoute();
744     processNavRadios();
745     processAI();
746     processCustomSymbols();
747     findItems();
748     limitDisplayedSymbols();
749   }
750
751   addSymbolsToScene();
752   
753   _symbolPrimSet->set(osg::PrimitiveSet::QUADS, 0, _vertices->size());
754   _symbolPrimSet->dirty();
755   _linePrimSet->set(osg::PrimitiveSet::LINES, 0, _lineVertices->size());
756   _linePrimSet->dirty();
757 }
758
759
760 void
761 NavDisplay::updateFont()
762 {
763     float red = _font_node->getFloatValue("color/red");
764     float green = _font_node->getFloatValue("color/green");
765     float blue = _font_node->getFloatValue("color/blue");
766     float alpha = _font_node->getFloatValue("color/alpha");
767     _font_color.set(red, green, blue, alpha);
768
769     _font_size = _font_node->getFloatValue("size");
770     _font_spacing = _font_size * _font_node->getFloatValue("line-spacing");
771     string path = _font_node->getStringValue("name", DEFAULT_FONT);
772
773     SGPath tpath;
774     if (path[0] != '/') {
775         tpath = globals->get_fg_root();
776         tpath.append("Fonts");
777         tpath.append(path);
778     } else {
779         tpath = path;
780     }
781
782     osg::ref_ptr<osgDB::ReaderWriter::Options> fontOptions = new osgDB::ReaderWriter::Options("monochrome");
783     osg::ref_ptr<osgText::Font> font = osgText::readFontFile(tpath.c_str(), fontOptions.get());
784
785     if (font != 0) {
786         _font = font;
787         _font->setMinFilterHint(osg::Texture::NEAREST);
788         _font->setMagFilterHint(osg::Texture::NEAREST);
789         _font->setGlyphImageMargin(0);
790         _font->setGlyphImageMarginRatio(0);
791     }
792 }
793
794 void NavDisplay::addSymbolToScene(SymbolInstance* sym)
795 {
796     SymbolDef* def = sym->definition;
797     
798     osg::Vec2 verts[4];
799     verts[0] = def->xy0;
800     verts[1] = osg::Vec2(def->xy1.x(), def->xy0.y());
801     verts[2] = def->xy1;
802     verts[3] = osg::Vec2(def->xy0.x(), def->xy1.y());
803     
804     if (def->rotateToHeading) {
805         osg::Matrixf m(degRotation(sym->headingDeg - _view_heading));
806         for (int i=0; i<4; ++i) {
807             verts[i] = mult(verts[i], m);
808         }
809     }
810     
811     osg::Vec2 pos = sym->pos;
812     if (def->roundPos) {
813         pos = osg::Vec2((int) pos.x(), (int) pos.y());
814     }
815     
816     _texCoords->push_back(def->uv0);
817     _texCoords->push_back(osg::Vec2(def->uv1.x(), def->uv0.y()));
818     _texCoords->push_back(def->uv1);
819     _texCoords->push_back(osg::Vec2(def->uv0.x(), def->uv1.y()));
820
821     for (int i=0; i<4; ++i) {
822         _vertices->push_back(verts[i] + pos);
823         _quadColors->push_back(def->color);
824     }
825     
826     if (def->stretchSymbol) {
827         osg::Vec2 stretchVerts[4];
828         stretchVerts[0] = osg::Vec2(def->xy0.x(), def->stretchY2);
829         stretchVerts[1] = osg::Vec2(def->xy1.x(), def->stretchY2);
830         stretchVerts[2] = osg::Vec2(def->xy1.x(), def->stretchY3);
831         stretchVerts[3] = osg::Vec2(def->xy0.x(), def->stretchY3);
832         
833         osg::Matrixf m(degRotation(sym->headingDeg - _view_heading));
834         for (int i=0; i<4; ++i) {
835             stretchVerts[i] = mult(stretchVerts[i], m);
836         }
837         
838     // stretched quad
839         _vertices->push_back(verts[2] + pos);
840         _vertices->push_back(stretchVerts[1] + sym->endPos);
841         _vertices->push_back(stretchVerts[0] + sym->endPos);
842         _vertices->push_back(verts[3] + pos);
843         
844         _texCoords->push_back(def->uv1);
845         _texCoords->push_back(osg::Vec2(def->uv1.x(), def->stretchV2));
846         _texCoords->push_back(osg::Vec2(def->uv0.x(), def->stretchV2));
847         _texCoords->push_back(osg::Vec2(def->uv0.x(), def->uv1.y()));
848         
849         for (int i=0; i<4; ++i) {
850             _quadColors->push_back(def->color);
851         }
852         
853     // quad three, for the end portion
854         for (int i=0; i<4; ++i) {
855             _vertices->push_back(stretchVerts[i] + sym->endPos);
856             _quadColors->push_back(def->color);
857         }
858         
859         _texCoords->push_back(osg::Vec2(def->uv0.x(), def->stretchV2));
860         _texCoords->push_back(osg::Vec2(def->uv1.x(), def->stretchV2));
861         _texCoords->push_back(osg::Vec2(def->uv1.x(), def->stretchV3));
862         _texCoords->push_back(osg::Vec2(def->uv0.x(), def->stretchV3));
863     }
864     
865     if (def->drawLine) {
866         addLine(sym->pos, sym->endPos, def->lineColor);
867     }
868     
869     if (!def->hasText || !def->textEnabled) {
870         return;
871     }
872     
873     osgText::Text* t = new osgText::Text;
874     t->setFont(_font.get());
875     t->setFontResolution(12, 12);
876     t->setCharacterSize(_font_size);
877     t->setLineSpacing(_font_spacing);
878     t->setColor(def->textColor);
879     t->setAlignment(def->alignment);
880     t->setText(sym->text());
881
882
883     osg::Vec2 textPos = def->textOffset + pos;
884 // ensure we use ints here, or text visual quality goes bad
885     t->setPosition(osg::Vec3((int)textPos.x(), (int)textPos.y(), 0));
886     _textGeode->addDrawable(t);
887 }
888
889 class OrderByPriority
890 {
891 public:
892     bool operator()(SymbolInstance* a, SymbolInstance* b)
893     {
894         return a->definition->priority > b->definition->priority;
895     }    
896 };
897
898 void NavDisplay::limitDisplayedSymbols()
899 {
900 // gloabl symbol limit
901     _maxSymbols= _Instrument->getIntValue("max-symbols", _maxSymbols);
902     if ((int) _symbols.size() <= _maxSymbols) {
903         _excessDataNode->setBoolValue(false);
904         return;
905     }
906     
907     std::sort(_symbols.begin(), _symbols.end(), OrderByPriority());
908     _symbols.resize(_maxSymbols);
909     _excessDataNode->setBoolValue(true);
910 }
911
912 class OrderByZ
913 {
914 public:
915     bool operator()(SymbolInstance* a, SymbolInstance* b)
916     {
917         return a->definition->zOrder > b->definition->zOrder;
918     }
919 };
920
921 void NavDisplay::addSymbolsToScene()
922 {
923     std::sort(_symbols.begin(), _symbols.end(), OrderByZ());
924     BOOST_FOREACH(SymbolInstance* sym, _symbols) {
925         addSymbolToScene(sym);
926     }
927 }
928
929 void NavDisplay::addLine(osg::Vec2 a, osg::Vec2 b, const osg::Vec4& color)
930 {    
931     _lineVertices->push_back(a);
932     _lineVertices->push_back(b);
933     _lineColors->push_back(color);
934     _lineColors->push_back(color);
935 }
936
937 osg::Vec2 NavDisplay::projectBearingRange(double bearingDeg, double rangeNm) const
938 {
939     osg::Vec3 p(0, rangeNm, 0.0);
940     p = degRotation(bearingDeg).preMult(p);
941     p = _projectMat.preMult(p);
942     return osg::Vec2(p.x(), p.y());
943 }
944
945 osg::Vec2 NavDisplay::projectGeod(const SGGeod& geod) const
946 {
947     double rangeM, bearing, az2;
948     SGGeodesy::inverse(_pos, geod, bearing, az2, rangeM);
949     return projectBearingRange(bearing, rangeM * SG_METER_TO_NM);
950 }
951
952 class Filter : public FGPositioned::Filter
953 {
954 public:
955     Filter(NavDisplay* nd) : _owner(nd) { }
956   
957     double minRunwayLengthFt;
958   
959     virtual bool pass(FGPositioned* aPos) const
960     {
961         if (aPos->type() == FGPositioned::FIX) {
962             string ident(aPos->ident());
963             // ignore fixes which end in digits
964             if ((ident.size() > 4) && isdigit(ident[3]) && isdigit(ident[4])) {
965                 return false;
966             }
967         }
968
969         if (aPos->type() == FGPositioned::AIRPORT) {
970           FGAirport* apt = (FGAirport*) aPos;
971           if (!apt->hasHardRunwayOfLengthFt(minRunwayLengthFt)) {
972             return false;
973           }
974         }
975       
976       // check against current rule states
977         return _owner->isPositionedShown(aPos);
978     }
979
980     virtual FGPositioned::Type minType() const {
981         return FGPositioned::AIRPORT;
982     }
983
984     virtual FGPositioned::Type maxType() const {
985         return FGPositioned::OBSTACLE;
986     }
987   
988 private:
989     NavDisplay* _owner;
990 };
991
992 void NavDisplay::findItems()
993 {
994     if (!_cachedItemsValid) {
995         Filter filt(this);
996         filt.minRunwayLengthFt = fgGetDouble("/sim/navdb/min-runway-length-ft", 2000);
997         bool wasTimeLimited;
998         _itemsInRange = FGPositioned::findClosestNPartial(_pos, _maxSymbols, _rangeNm,
999                                                           &filt, wasTimeLimited);
1000         _cachedItemsValid = true;
1001         _cachedPos = SGVec3d::fromGeod(_pos);
1002         
1003         if (wasTimeLimited) {
1004             // re-query next frame, to load incrementally
1005             _cachedItemsValid = false;
1006         }
1007     }
1008     
1009   // sort by distance from pos, so symbol limits are accurate
1010     FGPositioned::sortByRange(_itemsInRange, _pos);
1011   
1012     BOOST_FOREACH(FGPositioned* pos, _itemsInRange) {
1013         foundPositionedItem(pos);
1014     }
1015 }
1016
1017 void NavDisplay::processRoute()
1018 {
1019     _routeSources.clear();
1020     flightgear::FlightPlan* fp = _route->flightPlan();
1021     RoutePath path(fp);
1022     int current = _route->currentIndex();
1023     
1024     for (int l=0; l<fp->numLegs(); ++l) {
1025         flightgear::FlightPlan::Leg* leg = fp->legAtIndex(l);
1026         flightgear::WayptRef wpt(leg->waypoint());
1027         _routeSources.insert(wpt->source());
1028         
1029         string_set state;
1030         state.insert("on-active-route");
1031         
1032         if (l < current) {
1033             state.insert("passed");
1034         }
1035         
1036         if (l == current) {
1037             state.insert("current-wp");
1038         }
1039         
1040         if (l > current) {
1041             state.insert("future");
1042         }
1043         
1044         if (l == (current + 1)) {
1045             state.insert("next-wp");
1046         }
1047         
1048         SymbolRuleVector rules;
1049         findRules("waypoint" , state, rules);
1050         if (rules.empty()) {
1051             return; // no rules matched, we can skip this item
1052         }
1053
1054         SGGeod g = path.positionForIndex(l);
1055         SGPropertyNode* vars = _route->wayptNodeAtIndex(l);
1056         if (!vars) {
1057           continue; // shouldn't happen, but let's guard against it
1058         }
1059       
1060         double heading;
1061         computeWayptPropsAndHeading(wpt, g, vars, heading);
1062
1063         osg::Vec2 projected = projectGeod(g);
1064         BOOST_FOREACH(SymbolRule* r, rules) {
1065             addSymbolInstance(projected, heading, r->getDefinition(), vars);
1066             
1067             if (r->getDefinition()->drawRouteLeg) {
1068                 SGGeodVec gv(path.pathForIndex(l));
1069                 if (!gv.empty()) {
1070                     osg::Vec2 pr = projectGeod(gv[0]);
1071                     for (unsigned int i=1; i<gv.size(); ++i) {
1072                         osg::Vec2 p = projectGeod(gv[i]);
1073                         addLine(pr, p, r->getDefinition()->lineColor);
1074                         pr = p;
1075                     }
1076                 }
1077             } // of leg drawing enabled
1078         } // of matching rules iteration
1079     } // of waypoints iteration
1080 }
1081
1082 void NavDisplay::computeWayptPropsAndHeading(flightgear::Waypt* wpt, const SGGeod& pos, SGPropertyNode* nd, double& heading)
1083 {
1084     double rangeM, az2;
1085     SGGeodesy::inverse(_pos, pos, heading, az2, rangeM);
1086     nd->setIntValue("radial", heading);
1087     nd->setDoubleValue("distance-nm", rangeM * SG_METER_TO_NM);
1088     
1089     heading = nd->getDoubleValue("leg-bearing-true-deg");
1090 }
1091
1092 void NavDisplay::processNavRadios()
1093 {
1094     _nav1Station = processNavRadio(_navRadio1Node);
1095     _nav2Station = processNavRadio(_navRadio2Node);
1096     
1097     foundPositionedItem(_nav1Station);
1098     foundPositionedItem(_nav2Station);
1099 }
1100
1101 FGNavRecord* NavDisplay::processNavRadio(const SGPropertyNode_ptr& radio)
1102 {
1103   double mhz = radio->getDoubleValue("frequencies/selected-mhz", 0.0);
1104   FGNavRecord* nav = FGNavList::findByFreq(mhz, _pos, FGNavList::navFilter());
1105     if (!nav || (nav->ident() != radio->getStringValue("nav-id"))) {
1106         // station was not found
1107         return NULL;
1108     }
1109     
1110     
1111     return nav;
1112 }
1113
1114 bool NavDisplay::anyRuleForType(const string& type) const
1115 {
1116     BOOST_FOREACH(SymbolRule* r, _rules) {
1117         if (!r->enabled) {
1118             continue;
1119         }
1120     
1121         if (r->type == type) {
1122             return true;
1123         }
1124     }
1125     
1126     return false;
1127 }
1128
1129 void NavDisplay::findRules(const string& type, const string_set& states, SymbolRuleVector& rules)
1130 {
1131     BOOST_FOREACH(SymbolRule* candidate, _rules) {
1132         if (!candidate->enabled || (candidate->type != type)) {
1133             continue;
1134         }
1135         
1136         if (candidate->matches(states)) {
1137             rules.push_back(candidate);
1138         }
1139     }
1140 }
1141
1142 bool NavDisplay::isPositionedShown(FGPositioned* pos)
1143 {
1144   SymbolRuleVector rules;
1145   isPositionedShownInner(pos, rules);
1146   return !rules.empty();
1147 }
1148
1149 void NavDisplay::isPositionedShownInner(FGPositioned* pos, SymbolRuleVector& rules)
1150 {
1151   string type = FGPositioned::nameForType(pos->type());
1152   boost::to_lower(type);
1153   if (!anyRuleForType(type)) {
1154     return; // not diplayed at all, we're done
1155   }
1156   
1157   string_set states;
1158   computePositionedState(pos, states);
1159   
1160   findRules(type, states, rules);
1161 }
1162
1163 void NavDisplay::foundPositionedItem(FGPositioned* pos)
1164 {
1165     if (!pos) {
1166         return;
1167     }
1168     
1169     SymbolRuleVector rules;
1170     isPositionedShownInner(pos, rules);
1171     if (rules.empty()) {
1172       return;
1173     }
1174   
1175     SGPropertyNode_ptr vars(new SGPropertyNode);
1176     double heading;
1177     computePositionedPropsAndHeading(pos, vars, heading);
1178     
1179     osg::Vec2 projected = projectGeod(pos->geod());
1180     if (pos->type() == FGPositioned::RUNWAY) {
1181         FGRunway* rwy = (FGRunway*) pos;
1182         projected = projectGeod(rwy->threshold());
1183     }
1184     
1185     BOOST_FOREACH(SymbolRule* r, rules) {
1186         SymbolInstance* ins = addSymbolInstance(projected, heading, r->getDefinition(), vars);
1187         if ((ins)&&(pos->type() == FGPositioned::RUNWAY)) {
1188             FGRunway* rwy = (FGRunway*) pos;
1189             ins->endPos = projectGeod(rwy->end());
1190         }
1191     }
1192 }
1193
1194 void NavDisplay::computePositionedPropsAndHeading(FGPositioned* pos, SGPropertyNode* nd, double& heading)
1195 {
1196     nd->setStringValue("id", pos->ident());
1197     nd->setStringValue("name", pos->name());
1198     nd->setDoubleValue("elevation-ft", pos->elevation());
1199     nd->setIntValue("heading-deg", 0);
1200     heading = 0.0;
1201     
1202     switch (pos->type()) {
1203     case FGPositioned::VOR:
1204     case FGPositioned::LOC: 
1205     case FGPositioned::TACAN: {
1206         FGNavRecord* nav = static_cast<FGNavRecord*>(pos);
1207         nd->setDoubleValue("frequency-mhz", nav->get_freq());
1208         
1209         if (pos == _nav1Station) {
1210             heading = _navRadio1Node->getDoubleValue("radials/target-radial-deg");
1211         } else if (pos == _nav2Station) {
1212             heading = _navRadio2Node->getDoubleValue("radials/target-radial-deg");
1213         }
1214         
1215         nd->setIntValue("heading-deg", heading);
1216         break;
1217     }
1218
1219     case FGPositioned::AIRPORT:
1220     case FGPositioned::SEAPORT:
1221     case FGPositioned::HELIPORT:
1222         
1223         break;
1224         
1225     case FGPositioned::RUNWAY: {
1226         FGRunway* rwy = static_cast<FGRunway*>(pos);
1227         heading = rwy->headingDeg();
1228         nd->setDoubleValue("heading-deg", heading);
1229         nd->setIntValue("length-ft", rwy->lengthFt());
1230         nd->setStringValue("airport", rwy->airport()->ident());
1231         break;
1232     }
1233
1234     default:
1235         break; 
1236     }
1237 }
1238
1239 void NavDisplay::computePositionedState(FGPositioned* pos, string_set& states)
1240 {
1241     if (_routeSources.count(pos) != 0) {
1242         states.insert("on-active-route");
1243     }
1244     
1245     flightgear::FlightPlan* fp = _route->flightPlan();
1246     switch (pos->type()) {
1247     case FGPositioned::VOR:
1248     case FGPositioned::LOC:
1249         if (pos == _nav1Station) {
1250             states.insert("tuned");
1251             states.insert("nav1");
1252         }
1253         
1254         if (pos == _nav2Station) {
1255             states.insert("tuned");
1256             states.insert("nav2");
1257         }
1258         break;
1259     
1260     case FGPositioned::AIRPORT:
1261     case FGPositioned::SEAPORT:
1262     case FGPositioned::HELIPORT:
1263         // mark alternates!
1264         // once the FMS system has some way to tell us about them, of course
1265         
1266         if (pos == fp->departureAirport()) {
1267             states.insert("departure");
1268         }
1269         
1270         if (pos == fp->destinationAirport()) {
1271             states.insert("destination");
1272         }
1273         break;
1274     
1275     case FGPositioned::RUNWAY:
1276         if (pos == fp->departureRunway()) {
1277             states.insert("departure");
1278         }
1279         
1280         if (pos == fp->destinationRunway()) {
1281             states.insert("destination");
1282         }
1283         break;
1284     
1285     case FGPositioned::OBSTACLE:
1286     #if 0    
1287         FGObstacle* obs = (FGObstacle*) pos;
1288         if (obj->isLit()) {
1289             states.insert("lit");
1290         }
1291         
1292         if (obj->getHeightAGLFt() >= 1000) {
1293             states.insert("greater-1000-ft");
1294         }
1295     #endif
1296         break;
1297     
1298     default:
1299         break;
1300     } // FGPositioned::Type switch
1301 }
1302
1303 static string mapAINodeToType(SGPropertyNode* model)
1304 {
1305   // assume all multiplayer items are aircraft for the moment. Not ideal.
1306   if (!strcmp(model->getName(), "multiplayer")) {
1307     return "ai-aircraft";
1308   }
1309   
1310   return string("ai-") + model->getName();
1311 }
1312
1313 void NavDisplay::processAI()
1314 {
1315     SGPropertyNode *ai = fgGetNode("/ai/models", true);
1316     for (int i = ai->nChildren() - 1; i >= 0; i--) {
1317         SGPropertyNode *model = ai->getChild(i);
1318         if (!model->nChildren()) {
1319             continue;
1320         }
1321         
1322     // prefix types with 'ai-', to avoid any chance of namespace collisions
1323     // with fg-positioned.
1324         string_set ss;
1325         computeAIStates(model, ss);        
1326         SymbolRuleVector rules;
1327         findRules(mapAINodeToType(model), ss, rules);
1328         if (rules.empty()) {
1329             return; // no rules matched, we can skip this item
1330         }
1331
1332         double heading = model->getDoubleValue("orientation/true-heading-deg");
1333         SGGeod aiModelPos = SGGeod::fromDegFt(model->getDoubleValue("position/longitude-deg"), 
1334                                             model->getDoubleValue("position/latitude-deg"), 
1335                                             model->getDoubleValue("position/altitude-ft"));
1336     // compute some additional props
1337         int fl = (aiModelPos.getElevationFt() / 1000);
1338         model->setIntValue("flight-level", fl * 10);
1339                                             
1340         osg::Vec2 projected = projectGeod(aiModelPos);
1341         BOOST_FOREACH(SymbolRule* r, rules) {
1342             addSymbolInstance(projected, heading, r->getDefinition(), (SGPropertyNode*) model);
1343         }
1344     } // of ai models iteration
1345 }
1346
1347 void NavDisplay::computeAIStates(const SGPropertyNode* ai, string_set& states)
1348 {
1349     int threatLevel = ai->getIntValue("tcas/threat-level",-1);
1350     if (threatLevel < 1)
1351       threatLevel = 0;
1352   
1353     states.insert("tcas");
1354   
1355     std::ostringstream os;
1356     os << "tcas-threat-level-" << threatLevel;
1357     states.insert(os.str());
1358
1359     double vspeed = ai->getDoubleValue("velocities/vertical-speed-fps");
1360     if (vspeed < -3.0) {
1361         states.insert("descending");
1362     } else if (vspeed > 3.0) {
1363         states.insert("climbing");
1364     }
1365 }
1366
1367 SymbolInstance* NavDisplay::addSymbolInstance(const osg::Vec2& proj, double heading, SymbolDef* def, SGPropertyNode* vars)
1368 {
1369     if (isProjectedClipped(proj)) {
1370         return NULL;
1371     }
1372     
1373     if ((def->limitCount > 0) && (def->instanceCount >= def->limitCount)) {
1374       return NULL;
1375     }
1376   
1377     ++def->instanceCount;
1378     SymbolInstance* sym = new SymbolInstance(proj, heading, def, vars);
1379     _symbols.push_back(sym);
1380     return sym;
1381 }
1382
1383 bool NavDisplay::isProjectedClipped(const osg::Vec2& projected) const
1384 {
1385     double size = _odg->size();
1386     return (projected.x() < 0.0) ||
1387         (projected.y() < 0.0) ||
1388         (projected.x() >= size) ||
1389             (projected.y() >= size);
1390 }
1391
1392 void NavDisplay::addTestSymbol(const std::string& type, const std::string& states, const SGGeod& pos, double heading, SGPropertyNode* vars)
1393 {
1394   string_set stateSet;
1395   BOOST_FOREACH(std::string s, simgear::strutils::split(states, ",")) {
1396     stateSet.insert(s);
1397   }
1398   
1399   SymbolRuleVector rules;
1400   findRules(type, stateSet, rules);
1401   if (rules.empty()) {
1402     return; // no rules matched, we can skip this item
1403   }
1404     
1405   osg::Vec2 projected = projectGeod(pos);
1406   BOOST_FOREACH(SymbolRule* r, rules) {
1407     addSymbolInstance(projected, heading, r->getDefinition(), vars);
1408   }
1409 }
1410
1411 void NavDisplay::addTestSymbols()
1412 {
1413   _pos = SGGeod::fromDeg(-122.3748889, 37.6189722); // KSFO
1414   
1415   SGGeod a1;
1416   double dummy;
1417   SGGeodesy::direct(_pos, 45.0, 20.0 * SG_NM_TO_METER, a1, dummy);
1418   
1419   addTestSymbol("airport", "", a1, 0.0, NULL);
1420   
1421   SGGeodesy::direct(_pos, 95.0, 40.0 * SG_NM_TO_METER, a1, dummy);
1422   
1423   addTestSymbol("vor", "", a1, 0.0, NULL);
1424   
1425   SGGeodesy::direct(_pos, 120, 80.0 * SG_NM_TO_METER, a1, dummy);
1426   
1427   addTestSymbol("airport", "destination", a1, 0.0, NULL);
1428   
1429   SGGeodesy::direct(_pos, 80.0, 20.0 * SG_NM_TO_METER, a1, dummy);  
1430   addTestSymbol("fix", "", a1, 0.0, NULL);
1431
1432   
1433   SGGeodesy::direct(_pos, 140.0, 20.0 * SG_NM_TO_METER, a1, dummy);  
1434   addTestSymbol("fix", "", a1, 0.0, NULL);
1435   
1436   SGGeodesy::direct(_pos, 110.0, 10.0 * SG_NM_TO_METER, a1, dummy);  
1437   addTestSymbol("fix", "", a1, 0.0, NULL);
1438   
1439   SGGeodesy::direct(_pos, 110.0, 5.0 * SG_NM_TO_METER, a1, dummy);  
1440   addTestSymbol("fix", "", a1, 0.0, NULL);
1441 }
1442
1443 void NavDisplay::addRule(SymbolRule* r)
1444 {
1445     _rules.push_back(r);
1446 }
1447
1448 void NavDisplay::computeCustomSymbolStates(const SGPropertyNode* sym, string_set& states)
1449 {
1450   BOOST_FOREACH(SGPropertyNode* st, sym->getChildren("state")) {
1451     states.insert(st->getStringValue());
1452   }
1453 }
1454
1455 void NavDisplay::processCustomSymbols()
1456 {
1457   for (int i = _customSymbols->nChildren() - 1; i >= 0; i--) {
1458     SGPropertyNode *symNode = _customSymbols->getChild(i);
1459     if (!symNode->nChildren()) {
1460       continue;
1461     }
1462     string_set ss;
1463     computeCustomSymbolStates(symNode, ss);
1464     SymbolRuleVector rules;
1465     findRules(symNode->getName(), ss, rules);
1466     if (rules.empty()) {
1467       return; // no rules matched, we can skip this item
1468     }
1469     
1470     double heading = symNode->getDoubleValue("true-heading-deg", 0.0);
1471     SGGeod pos = SGGeod::fromDegFt(symNode->getDoubleValue("longitude-deg"),
1472                                           symNode->getDoubleValue("latitude-deg"),
1473                                           symNode->getDoubleValue("altitude-ft"));
1474  
1475     
1476     osg::Vec2 projected = projectGeod(pos);
1477     BOOST_FOREACH(SymbolRule* r, rules) {
1478       addSymbolInstance(projected, heading, r->getDefinition(), symNode);
1479     }
1480   } // of custom symbols iteration
1481 }
1482
1483