1 // navigation display texture
3 // Written by James Turner, forked from wxradar code
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.
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.
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.
26 #include "NavDisplay.hxx"
29 #include <osg/Geometry>
30 #include <osg/Matrixf>
31 #include <osg/PrimitiveSet>
32 #include <osg/StateSet>
33 #include <osg/LineWidth>
35 #include <osg/Version>
36 #include <osgDB/ReaderWriter>
37 #include <osgDB/WriteFile>
39 #include <simgear/constants.h>
40 #include <simgear/misc/sg_path.hxx>
41 #include <simgear/scene/model/model.hxx>
42 #include <simgear/structure/exception.hxx>
43 #include <simgear/misc/sg_path.hxx>
44 #include <simgear/math/sg_geodesy.hxx>
48 #include <iostream> // for cout, endl
50 using std::stringstream;
52 using std::setprecision;
59 #include <Main/fg_props.hxx>
60 #include <Main/globals.hxx>
61 #include <Cockpit/panel.hxx>
62 #include <Navaids/routePath.hxx>
63 #include <Autopilot/route_mgr.hxx>
64 #include <Navaids/navrecord.hxx>
65 #include <Navaids/navlist.hxx>
66 #include <Navaids/fix.hxx>
67 #include <Airports/simple.hxx>
68 #include <Airports/runways.hxx>
70 #include "instrument_mgr.hxx"
71 #include "od_gauge.hxx"
73 static const float UNIT = 1.0f / 8.0f; // 8 symbols in a row/column in the texture
74 static const char *DEFAULT_FONT = "typewriter.txf";
76 NavDisplay::NavDisplay(SGPropertyNode *node) :
77 _name(node->getStringValue("name", "nd")),
78 _num(node->getIntValue("number", 0)),
80 _interval(node->getDoubleValue("update-interval-sec", 1.0)),
83 _sim_init_done(false),
98 _Instrument = fgGetNode(string("/instrumentation/" + _name).c_str(), _num, true);
99 _font_node = _Instrument->getNode("font", true);
101 #define INITFONT(p, val, type) if (!_font_node->hasValue(p)) _font_node->set##type##Value(p, val)
102 INITFONT("name", DEFAULT_FONT, String);
103 INITFONT("size", 8, Float);
104 INITFONT("line-spacing", 0.25, Float);
105 INITFONT("color/red", 0, Float);
106 INITFONT("color/green", 0.8, Float);
107 INITFONT("color/blue", 0, Float);
108 INITFONT("color/alpha", 1, Float);
114 NavDisplay::~NavDisplay()
122 _serviceable_node = _Instrument->getNode("serviceable", true);
124 // texture name to use in 2D and 3D instruments
125 _texture_path = _Instrument->getStringValue("radar-texture-path",
126 "Aircraft/Instruments/Textures/od_wxradar.rgb");
127 _resultTexture = FGTextureManager::createTexture(_texture_path.c_str(), false);
129 string path = _Instrument->getStringValue("symbol-texture-path",
130 "Aircraft/Instruments/Textures/nd-symbols.png");
131 SGPath tpath = globals->resolve_aircraft_path(path);
133 // no mipmap or else alpha will mix with pixels on the border of shapes, ruining the effect
134 _symbols = SGLoadTexture2D(tpath, NULL, false, false);
136 FGInstrumentMgr *imgr = (FGInstrumentMgr *)globals->get_subsystem("instrumentation");
137 _odg = (FGODGauge *)imgr->get_subsystem("od_gauge");
138 _odg->setSize(_Instrument->getIntValue("texture-size", 512));
141 _user_lat_node = fgGetNode("/position/latitude-deg", true);
142 _user_lon_node = fgGetNode("/position/longitude-deg", true);
143 _user_alt_node = fgGetNode("/position/altitude-ft", true);
145 SGPropertyNode *n = _Instrument->getNode("display-controls", true);
146 _radar_weather_node = n->getNode("WX", true);
147 _radar_position_node = n->getNode("pos", true);
148 _radar_data_node = n->getNode("data", true);
149 _radar_symbol_node = n->getNode("symbol", true);
150 _radar_centre_node = n->getNode("centre", true);
151 _radar_tcas_node = n->getNode("tcas", true);
152 _radar_absalt_node = n->getNode("abs-altitude", true);
154 _ai_enabled_node = fgGetNode("/sim/ai/enabled", true);
155 _route = static_cast<FGRouteMgr*>(globals->get_subsystem("route-manager"));
157 // OSG geometry setup
158 _radarGeode = new osg::Geode;
159 osg::StateSet *stateSet = _radarGeode->getOrCreateStateSet();
160 stateSet->setTextureAttributeAndModes(0, _symbols.get());
162 osg::LineWidth *lw = new osg::LineWidth();
164 stateSet->setAttribute(lw);
166 _geom = new osg::Geometry;
167 _geom->setUseDisplayList(false);
168 // Initially allocate space for 128 quads
169 _vertices = new osg::Vec2Array;
170 _vertices->setDataVariance(osg::Object::DYNAMIC);
171 _vertices->reserve(128 * 4);
172 _geom->setVertexArray(_vertices);
173 _texCoords = new osg::Vec2Array;
174 _texCoords->setDataVariance(osg::Object::DYNAMIC);
175 _texCoords->reserve(128 * 4);
176 _geom->setTexCoordArray(0, _texCoords);
178 osg::Vec3Array *colors = new osg::Vec3Array;
179 colors->push_back(osg::Vec3(1.0f, 1.0f, 1.0f)); // color of echos
180 _geom->setColorBinding(osg::Geometry::BIND_PER_PRIMITIVE_SET);
181 _geom->setColorArray(colors);
183 _symbolPrimSet = new osg::DrawArrays(osg::PrimitiveSet::QUADS);
184 _symbolPrimSet->setDataVariance(osg::Object::DYNAMIC);
185 _geom->addPrimitiveSet(_symbolPrimSet);
187 _geom->setInitialBound(osg::BoundingBox(osg::Vec3f(-256.0f, -256.0f, 0.0f),
188 osg::Vec3f(256.0f, 256.0f, 0.0f)));
189 _radarGeode->addDrawable(_geom);
191 // Texture in the 2D panel system
192 FGTextureManager::addTexture(_texture_path.c_str(), _odg->getTexture());
194 _lineGeometry = new osg::Geometry;
195 _lineGeometry->setUseDisplayList(false);
197 _lineVertices = new osg::Vec2Array;
198 _lineVertices->setDataVariance(osg::Object::DYNAMIC);
199 _lineVertices->reserve(128 * 4);
200 _lineGeometry->setVertexArray(_vertices);
203 _lineColors = new osg::Vec3Array;
204 _lineColors->setDataVariance(osg::Object::DYNAMIC);
205 _lineGeometry->setColorBinding(osg::Geometry::BIND_PER_VERTEX);
206 _lineGeometry->setColorArray(colors);
208 _linePrimSet = new osg::DrawArrays(osg::PrimitiveSet::LINES);
209 _linePrimSet->setDataVariance(osg::Object::DYNAMIC);
210 _lineGeometry->addPrimitiveSet(_linePrimSet);
212 _lineGeometry->setInitialBound(osg::BoundingBox(osg::Vec3f(-256.0f, -256.0f, 0.0f),
213 osg::Vec3f(256.0f, 256.0f, 0.0f)));
215 _radarGeode->addDrawable(_lineGeometry);
217 _textGeode = new osg::Geode;
219 osg::Camera *camera = _odg->getCamera();
220 camera->addChild(_radarGeode.get());
221 camera->addChild(_textGeode.get());
227 // Local coordinates for each echo
228 const osg::Vec3f symbolCoords[4] = {
229 osg::Vec3f(-.7f, -.7f, 0.0f), osg::Vec3f(.7f, -.7f, 0.0f),
230 osg::Vec3f(.7f, .7f, 0.0f), osg::Vec3f(-.7f, .7f, 0.0f)
234 const osg::Vec2f symbolTexCoords[4] = {
235 osg::Vec2f(0.0f, 0.0f), osg::Vec2f(UNIT, 0.0f),
236 osg::Vec2f(UNIT, UNIT), osg::Vec2f(0.0f, UNIT)
242 addQuad(osg::Vec2Array *vertices, osg::Vec2Array *texCoords,
243 const osg::Matrixf& transform, const osg::Vec2f& texBase)
245 for (int i = 0; i < 4; i++) {
246 const osg::Vec3f coords = transform.preMult(symbolCoords[i]);
247 texCoords->push_back(texBase + symbolTexCoords[i]);
248 vertices->push_back(osg::Vec2f(coords.x(), coords.y()));
253 // Rotate by a heading value
255 osg::Matrixf wxRotate(float angle)
257 return osg::Matrixf::rotate(angle, 0.0f, 0.0f, -1.0f);
262 NavDisplay::update (double delta_time_sec)
264 if (!fgGetBool("sim/sceneryloaded", false)) {
268 if (!_odg || !_serviceable_node->getBoolValue()) {
269 _Instrument->setStringValue("status", "");
273 _time += delta_time_sec;
274 if (_time < _interval){
279 string mode = _Instrument->getStringValue("display-mode", "arc");
280 _rangeNm = _Instrument->getFloatValue("range", 40.0);
281 _view_heading = fgGetDouble("/orientation/heading-deg") * SG_DEGREES_TO_RADIANS;
284 bool centre = _radar_centre_node->getBoolValue();
286 _centerTrans = osg::Matrixf::identity();
288 _centerTrans = osg::Matrixf::identity();
291 _drawData = _radar_data_node->getBoolValue();
292 _pos = SGGeod::fromDegFt(_user_lon_node->getDoubleValue(),
293 _user_lat_node->getDoubleValue(),
294 _user_alt_node->getDoubleValue());
297 _lineVertices->clear();
298 _lineColors->clear();
300 _textGeode->removeDrawables(0, _textGeode->getNumDrawables());
306 _symbolPrimSet->set(osg::PrimitiveSet::QUADS, 0, _vertices->size());
307 _symbolPrimSet->dirty();
308 _linePrimSet->set(osg::PrimitiveSet::LINES, 0, _lineVertices->size());
309 _linePrimSet->dirty();
312 osg::Matrixf NavDisplay::project(const SGGeod& geod) const
314 double rangeM, bearing, az2;
315 SGGeodesy::inverse(_pos, geod, bearing, az2, rangeM);
317 double radius = ((rangeM * SG_METER_TO_NM) / _rangeNm) * _scale;
318 bearing *= SG_DEGREES_TO_RADIANS;
320 return osg::Matrixf(wxRotate(_view_heading - bearing)
321 * osg::Matrixf::translate(0.0f, radius, 0.0f)
322 * wxRotate(bearing) * _centerTrans);
327 NavDisplay::addSymbol(const SGGeod& pos, int symbolIndex, const std::string& data)
329 int symbolRow = symbolIndex >> 4;
330 int symbolColumn = symbolIndex & 0x0f;
332 const osg::Vec2f texBase(UNIT * symbolColumn, UNIT * symbolRow);
333 float size = 600 * UNIT;
335 osg::Matrixf m(osg::Matrixf::scale(size, size, 1.0f)
337 addQuad(_vertices, _texCoords, m, texBase);
344 osgText::Text* text = new osgText::Text;
345 text->setFont(_font.get());
346 text->setFontResolution(12, 12);
347 text->setCharacterSize(_font_size);
349 osg::Vec3 dataPos = m.preMult(osg::Vec3(16, 16, 0));
350 text->setLineSpacing(_font_spacing);
352 text->setAlignment(osgText::Text::LEFT_CENTER);
354 text->setPosition(osg::Vec3((int) dataPos.x(), (int)dataPos.y(), 0));
355 _textGeode->addDrawable(text);
359 NavDisplay::update_route()
361 if (_route->numWaypts() < 2) {
365 RoutePath path(_route->waypts());
366 for (int w=0; w<_route->numWaypts(); ++w) {
367 bool isPast = w < _route->currentIndex();
368 SGGeodVec gv(path.pathForIndex(w));
370 osg::Vec3 color(1.0, 0.0, 1.0);
372 color = osg::Vec3(0.5, 0.5, 0.5);
375 osg::Vec3 pr = project(gv[0]).preMult(osg::Vec3(0.0, 0.0, 0.0));
376 for (unsigned int i=1; i<gv.size(); ++i) {
377 _lineVertices->push_back(osg::Vec2(pr.x(), pr.y()));
378 pr = project(gv[i]).preMult(osg::Vec3(0.0, 0.0, 0.0));
379 _lineVertices->push_back(osg::Vec2(pr.x(), pr.y()));
381 _lineColors->push_back(color);
382 _lineColors->push_back(color);
386 flightgear::WayptRef wpt(_route->wayptAtIndex(w));
387 SGGeod g = path.positionForIndex(w);
388 int symbolIndex = isPast ? 1 : 0;
389 if (!(g == SGGeod())) {
390 std::string data = wpt->ident();
391 addSymbol(g, symbolIndex, data);
393 } // of waypoints iteration
397 NavDisplay::update_aircraft()
399 if (!_ai_enabled_node->getBoolValue()) {
403 bool draw_tcas = _radar_tcas_node->getBoolValue();
404 bool draw_absolute = _radar_absalt_node->getBoolValue();
405 bool draw_echoes = _radar_position_node->getBoolValue();
406 bool draw_symbols = _radar_symbol_node->getBoolValue();
407 bool draw_data = _radar_data_node->getBoolValue();
408 if (!draw_echoes && !draw_symbols && !draw_data)
411 const SGPropertyNode *ai = fgGetNode("/ai/models", true);
412 for (int i = ai->nChildren() - 1; i >= 0; i--) {
413 const SGPropertyNode *model = ai->getChild(i);
414 if (!model->nChildren()) {
418 double echo_radius, sigma;
419 const string name = model->getName();
421 if (name == "aircraft" || name == "tanker")
422 echo_radius = 1, sigma = 1;
423 else if (name == "multiplayer" || name == "wingman" || name == "static")
424 echo_radius = 1.5, sigma = 1;
425 else if (name == "ship" || name == "carrier" || name == "escort" ||name == "storm")
426 echo_radius = 1.5, sigma = 100;
427 else if (name == "thermal")
428 echo_radius = 2, sigma = 100;
429 else if (name == "rocket")
430 echo_radius = 0.1, sigma = 0.1;
431 else if (name == "ballistic")
432 echo_radius = 0.001, sigma = 0.001;
436 SGGeod aiModelPos = SGGeod::fromDegFt(model->getDoubleValue("position/longitude-deg"),
437 model->getDoubleValue("position/latitude-deg"),
438 model->getDoubleValue("position/altitude-ft"));
440 double heading = model->getDoubleValue("orientation/true-heading-deg");
441 double rangeM, bearing, az2;
442 SGGeodesy::inverse(_pos, aiModelPos, bearing, az2, rangeM);
444 // if (!inRadarRange(sigma, rangeM))
447 bearing *= SG_DEGREES_TO_RADIANS;
448 heading *= SG_DEGREES_TO_RADIANS;
450 float radius = rangeM * _scale;
451 // float angle = relativeBearing(bearing, _view_heading);
453 bool is_tcas_contact = false;
456 is_tcas_contact = update_tcas(model,rangeM,
457 _pos.getElevationFt(),
458 aiModelPos.getElevationFt(),
459 bearing,radius,draw_absolute);
463 if (draw_symbols && (!draw_tcas)) {
464 const osg::Vec2f texBase(0, 3 * UNIT);
465 float size = 600 * UNIT;
466 osg::Matrixf m(osg::Matrixf::scale(size, size, 1.0f)
467 * wxRotate(heading - bearing)
468 * osg::Matrixf::translate(0.0f, radius, 0.0f)
469 * wxRotate(bearing) * _centerTrans);
470 addQuad(_vertices, _texCoords, m, texBase);
473 if (draw_data || is_tcas_contact) {
474 update_data(model, aiModelPos.getElevationFt(), heading, radius, bearing, false);
476 } // of ai models iteration
479 /** Update TCAS display.
480 * Return true when processed as TCAS contact, false otherwise. */
482 NavDisplay::update_tcas(const SGPropertyNode *model,double range,double user_alt,double alt,
483 double bearing,double radius,bool absMode)
487 // update TCAS symbol
489 threatLevel = model->getIntValue("tcas/threat-level",-1);
490 if (threatLevel == -1)
492 // no TCAS information (i.e. no transponder) => not visible to TCAS
495 int row = 7 - threatLevel;
497 double vspeed = model->getDoubleValue("velocities/vertical-speed-fps");
498 if (vspeed < -3.0) // descending
501 if (vspeed > 3.0) // climbing
503 texBase = osg::Vec2f(col*UNIT,row * UNIT);
504 float size = 200 * UNIT;
505 osg::Matrixf m(osg::Matrixf::scale(size, size, 1.0f)
507 * osg::Matrixf::translate(0.0f, radius, 0.0f)
508 * wxRotate(bearing) * _centerTrans);
509 addQuad(_vertices, _texCoords, m, texBase);
514 osgText::Text *altStr = new osgText::Text;
515 altStr->setFont(_font.get());
516 altStr->setFontResolution(12, 12);
517 altStr->setCharacterSize(_font_size);
518 altStr->setColor(_tcas_colors[threatLevel]);
519 osg::Matrixf m(wxRotate(-bearing)
520 * osg::Matrixf::translate(0.0f, radius, 0.0f)
521 * wxRotate(bearing) * _centerTrans);
523 osg::Vec3 pos = m.preMult(osg::Vec3(16, 16, 0));
524 // cast to int's, otherwise text comes out ugly
525 altStr->setLineSpacing(_font_spacing);
528 altStr->setAlignment(osgText::Text::LEFT_CENTER);
529 int altDif = (alt-user_alt+50)/100;
544 altStr->setPosition(osg::Vec3((int)pos.x()-30, (int)pos.y()+dy, 0));
547 // absolute altitude display
548 text << setprecision(0) << fixed
549 << setw(3) << setfill('0') << alt/100 << endl;
551 else // relative altitude display
555 << setprecision(0) << fixed
556 << setw(2) << setfill('0') << altDif << endl;
559 altStr->setText(text.str());
560 _textGeode->addDrawable(altStr);
566 void NavDisplay::update_data(const SGPropertyNode *ac, double altitude, double heading,
567 double radius, double bearing, bool selected)
569 osgText::Text *callsign = new osgText::Text;
570 callsign->setFont(_font.get());
571 callsign->setFontResolution(12, 12);
572 callsign->setCharacterSize(_font_size);
573 callsign->setColor(selected ? osg::Vec4(1, 1, 1, 1) : _font_color);
574 osg::Matrixf m(wxRotate(-bearing)
575 * osg::Matrixf::translate(0.0f, radius, 0.0f)
576 * wxRotate(bearing) * _centerTrans);
578 osg::Vec3 pos = m.preMult(osg::Vec3(16, 16, 0));
579 // cast to int's, otherwise text comes out ugly
580 callsign->setPosition(osg::Vec3((int)pos.x(), (int)pos.y(), 0));
581 callsign->setAlignment(osgText::Text::LEFT_BOTTOM_BASE_LINE);
582 callsign->setLineSpacing(_font_spacing);
584 const char *identity = ac->getStringValue("transponder-id");
586 identity = ac->getStringValue("callsign");
589 text << identity << endl
590 << setprecision(0) << fixed
591 << setw(3) << setfill('0') << heading * SG_RADIANS_TO_DEGREES << "\xB0 "
592 << setw(0) << altitude << "ft" << endl
593 << ac->getDoubleValue("velocities/true-airspeed-kt") << "kts";
595 callsign->setText(text.str());
596 _textGeode->addDrawable(callsign);
600 NavDisplay::updateFont()
602 float red = _font_node->getFloatValue("color/red");
603 float green = _font_node->getFloatValue("color/green");
604 float blue = _font_node->getFloatValue("color/blue");
605 float alpha = _font_node->getFloatValue("color/alpha");
606 _font_color.set(red, green, blue, alpha);
608 _font_size = _font_node->getFloatValue("size");
609 _font_spacing = _font_size * _font_node->getFloatValue("line-spacing");
610 string path = _font_node->getStringValue("name", DEFAULT_FONT);
613 if (path[0] != '/') {
614 tpath = globals->get_fg_root();
615 tpath.append("Fonts");
621 #if (FG_OSG_VERSION >= 21000)
622 osg::ref_ptr<osgDB::ReaderWriter::Options> fontOptions = new osgDB::ReaderWriter::Options("monochrome");
623 osg::ref_ptr<osgText::Font> font = osgText::readFontFile(tpath.c_str(), fontOptions.get());
625 osg::ref_ptr<osgText::Font> font = osgText::readFontFile(tpath.c_str());
630 _font->setMinFilterHint(osg::Texture::NEAREST);
631 _font->setMagFilterHint(osg::Texture::NEAREST);
632 _font->setGlyphImageMargin(0);
633 _font->setGlyphImageMarginRatio(0);
636 for (int i=0;i<4;i++)
638 const float defaultColors[4][3] = {{0,1,1},{0,1,1},{1,0.5,0},{1,0,0}};
639 SGPropertyNode_ptr color_node = _font_node->getNode("tcas/color",i,true);
640 float red = color_node->getFloatValue("red",defaultColors[i][0]);
641 float green = color_node->getFloatValue("green",defaultColors[i][1]);
642 float blue = color_node->getFloatValue("blue",defaultColors[i][2]);
643 float alpha = color_node->getFloatValue("alpha",1);
644 _tcas_colors[i]=osg::Vec4(red, green, blue, alpha);