first commit

This commit is contained in:
Your Name
2022-10-20 20:29:11 +08:00
commit 4d531f8044
3238 changed files with 1387862 additions and 0 deletions

View File

@@ -0,0 +1,100 @@
// Copyright (C) 2019 James Turner <james@flightgear.org>
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation; either version 2 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#include "config.h"
#include <simgear/debug/logstream.hxx>
#include <Instrumentation/AbstractInstrument.hxx>
#include <Main/fg_props.hxx>
void AbstractInstrument::readConfig(SGPropertyNode* config,
std::string defaultName)
{
_name = config->getStringValue("name", defaultName.c_str());
_index = config->getIntValue("number", 0);
if (_powerSupplyPath.empty()) {
_powerSupplyPath = "/systems/electrical/outputs/" + defaultName;
}
if (config->hasChild("power-supply")) {
_powerSupplyPath = config->getStringValue("power-supply");
}
// the default output values are volts, but various places have been
// treating the value as a bool, so we default to 1.0 as our minimum
// supply volts
_minimumSupplyVolts = config->getDoubleValue("minimum-supply-volts", 1.0);
}
std::string AbstractInstrument::nodePath() const
{
return "/instrumentation/" + _name + "[" + std::to_string(_index) + "]";
}
void AbstractInstrument::initServicePowerProperties(SGPropertyNode* node)
{
_serviceableNode = node->getNode("serviceable", 0, true);
if (_serviceableNode->getType() == simgear::props::NONE)
_serviceableNode->setBoolValue(true);
_powerButtonNode = node->getChild("power-btn", 0, true);
// if the user didn't define a node, default to true
if (_powerButtonNode->getType() == simgear::props::NONE)
_powerButtonNode->setBoolValue(true);
if (_powerSupplyPath != "NO_DEFAULT") {
_powerSupplyNode = fgGetNode(_powerSupplyPath, true);
}
node->tie( "operable", SGRawValueMethods<AbstractInstrument,bool>
( *this, &AbstractInstrument::isServiceableAndPowered ) );
}
void AbstractInstrument::unbind()
{
auto nd = fgGetNode(nodePath());
if (nd) {
nd->untie("operable");
}
}
bool AbstractInstrument::isServiceableAndPowered() const
{
if (!_serviceableNode->getBoolValue() || !isPowerSwitchOn())
return false;
if (_powerSupplyNode && (_powerSupplyNode->getDoubleValue() < _minimumSupplyVolts))
return false;
return true;
}
void AbstractInstrument::setDefaultPowerSupplyPath(const std::string &p)
{
_powerSupplyPath = p;
}
void AbstractInstrument::setMinimumSupplyVolts(double v)
{
_minimumSupplyVolts = v;
}
bool AbstractInstrument::isPowerSwitchOn() const
{
return _powerButtonNode->getBoolValue();
}

View File

@@ -0,0 +1,64 @@
// Copyright (C) 2019 James Turner <james@flightgear.org>
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation; either version 2 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#ifndef FG_ABSTRACT_INSTRUMENT_HXX
#define FG_ABSTRACT_INSTRUMENT_HXX
#include <simgear/props/props.hxx>
#include <simgear/structure/subsystem_mgr.hxx>
class AbstractInstrument : public SGSubsystem
{
public:
protected:
void readConfig(SGPropertyNode* config,
std::string defaultName);
void initServicePowerProperties(SGPropertyNode* node);
bool isServiceableAndPowered() const;
// build the path /instrumentation/<name>[number]
std::string nodePath() const;
std::string name() const { return _name; }
int number() const { return _index; }
void unbind() override;
void setMinimumSupplyVolts(double v);
/**
* specify the default path to use to power the instrument, if it's non-
* standard.
*/
void setDefaultPowerSupplyPath(const std::string &p);
virtual bool isPowerSwitchOn() const;
private:
std::string _name;
int _index = 0;
std::string _powerSupplyPath;
SGPropertyNode_ptr _serviceableNode;
SGPropertyNode_ptr _powerButtonNode;
double _minimumSupplyVolts;
SGPropertyNode_ptr _powerSupplyNode;
};
#endif // of FG_ABSTRACT_INSTRUMENT_HXX

View File

@@ -0,0 +1,118 @@
include(FlightGearComponent)
set(SOURCES
AbstractInstrument.cxx
adf.cxx
airspeed_indicator.cxx
altimeter.cxx
attitude_indicator.cxx
clock.cxx
dclgps.cxx
dme.cxx
gps.cxx
gsdi.cxx
gyro.cxx
heading_indicator.cxx
heading_indicator_dg.cxx
heading_indicator_fg.cxx
inst_vertical_speed_indicator.cxx
instrument_mgr.cxx
kr_87.cxx
mag_compass.cxx
marker_beacon.cxx
mk_viii.cxx
mrg.cxx
navradio.cxx
newnavradio.cxx
commradio.cxx
rad_alt.cxx
rnav_waypt_controller.cxx
slip_skid_ball.cxx
tacan.cxx
tcas.cxx
transponder.cxx
turn_indicator.cxx
vertical_speed_indicator.cxx
HUD/HUD.cxx
HUD/HUD_dial.cxx
HUD/HUD_gauge.cxx
HUD/HUD_instrument.cxx
HUD/HUD_label.cxx
HUD/HUD_ladder.cxx
HUD/HUD_misc.cxx
HUD/HUD_runway.cxx
HUD/HUD_scale.cxx
HUD/HUD_tape.cxx
HUD/HUD_tbi.cxx
KLN89/kln89.cxx
KLN89/kln89_page.cxx
KLN89/kln89_page_act.cxx
KLN89/kln89_page_apt.cxx
KLN89/kln89_page_cal.cxx
KLN89/kln89_page_dir.cxx
KLN89/kln89_page_fpl.cxx
KLN89/kln89_page_int.cxx
KLN89/kln89_page_nav.cxx
KLN89/kln89_page_ndb.cxx
KLN89/kln89_page_nrst.cxx
KLN89/kln89_page_oth.cxx
KLN89/kln89_page_set.cxx
KLN89/kln89_page_usr.cxx
KLN89/kln89_page_vor.cxx
KLN89/kln89_page_alt.cxx
)
set(HEADERS
AbstractInstrument.hxx
adf.hxx
airspeed_indicator.hxx
altimeter.hxx
attitude_indicator.hxx
clock.hxx
dclgps.hxx
dme.hxx
gps.hxx
gsdi.hxx
gyro.hxx
heading_indicator.hxx
heading_indicator_dg.hxx
heading_indicator_fg.hxx
inst_vertical_speed_indicator.hxx
instrument_mgr.hxx
kr_87.hxx
mag_compass.hxx
marker_beacon.hxx
mk_viii.hxx
mrg.hxx
navradio.hxx
newnavradio.hxx
commradio.hxx
rad_alt.hxx
rnav_waypt_controller.hxx
slip_skid_ball.hxx
tacan.hxx
tcas.hxx
transponder.hxx
turn_indicator.hxx
vertical_speed_indicator.hxx
HUD/HUD.hxx
HUD/HUD_private.hxx
KLN89/kln89.hxx
KLN89/kln89_page.hxx
KLN89/kln89_page_act.hxx
KLN89/kln89_page_apt.hxx
KLN89/kln89_page_cal.hxx
KLN89/kln89_page_dir.hxx
KLN89/kln89_page_fpl.hxx
KLN89/kln89_page_int.hxx
KLN89/kln89_page_nav.hxx
KLN89/kln89_page_ndb.hxx
KLN89/kln89_page_nrst.hxx
KLN89/kln89_page_oth.hxx
KLN89/kln89_page_set.hxx
KLN89/kln89_page_usr.hxx
KLN89/kln89_page_vor.hxx
KLN89/kln89_page_alt.hxx
)
flightgear_component(Instruments "${SOURCES}" "${HEADERS}")

View File

@@ -0,0 +1,734 @@
// HUD.cxx -- Head Up Display
//
// Written by Michele America, started September 1997.
//
// Copyright (C) 1997 Michele F. America [micheleamerica#geocities:com]
// Copyright (C) 2006 Melchior FRANZ [mfranz#aon:at]
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation; either version 2 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#include <simgear/compiler.h>
#include <simgear/structure/exception.hxx>
#include <string>
#include <fstream>
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif
#include <simgear/constants.h>
#include <simgear/misc/sg_path.hxx>
#include <simgear/io/iostreams/sgstream.hxx>
#include <simgear/props/props_io.hxx>
#include <osg/GLU>
#include "fnt.h"
#include <Main/globals.hxx>
#include <Main/fg_props.hxx>
#include <Viewer/viewmgr.hxx>
#include <Viewer/view.hxx>
#include <GUI/FGFontCache.hxx>
#include <GUI/gui.h> // for guiErrorMessage
#include "HUD.hxx"
#include "HUD_private.hxx"
using std::endl;
using std::ifstream;
using std::string;
using std::deque;
using std::vector;
static float clamp(float f)
{
return f < 0.0f ? 0.0f : f > 1.0f ? 1.0f : f;
}
HUD::Input::Input(const SGPropertyNode *n, float factor, float offset,
float min, float max) :
_valid(false),
_property(0),
_damped(SGLimitsf::max())
{
if (!n)
return;
_factor = n->getFloatValue("factor", factor);
_offset = n->getFloatValue("offset", offset);
_min = n->getFloatValue("min", min);
_max = n->getFloatValue("max", max);
_coeff = 1.0 - 1.0 / powf(10, fabs(n->getFloatValue("damp", 0.0)));
SGPropertyNode *p = ((SGPropertyNode *)n)->getNode("property", false);
if (p) {
string path = p->getStringValue();
if (!path.empty()) {
_property = fgGetNode(path, true);
_valid = true;
}
}
}
HUD::HUD() :
_currentPath(fgGetNode("/sim/hud/current-path", true)),
_currentColor(fgGetNode("/sim/hud/current-color", true)),
_visibility(fgGetNode("/sim/hud/visibility[1]", true)),
_3DenabledN(fgGetNode("/sim/hud/enable3d[1]", true)),
_antialiasing(fgGetNode("/sim/hud/color/antialiased", true)),
_transparency(fgGetNode("/sim/hud/color/transparent", true)),
_red(fgGetNode("/sim/hud/color/red", true)),
_green(fgGetNode("/sim/hud/color/green", true)),
_blue(fgGetNode("/sim/hud/color/blue", true)),
_alpha(fgGetNode("/sim/hud/color/alpha", true)),
_alpha_clamp(fgGetNode("/sim/hud/color/alpha-clamp", true)),
_brightness(fgGetNode("/sim/hud/color/brightness", true)),
_visible(false),
_loaded(false),
_antialiased(false),
_transparent(false),
_a(0.67), // FIXME better names
_cl(0.01),
//
_scr_widthN(fgGetNode("/sim/startup/xsize", true)),
_scr_heightN(fgGetNode("/sim/startup/ysize", true)),
_unitsN(fgGetNode("/sim/startup/units", true)),
_timer(0.0),
//
_font_renderer(new fntRenderer()),
_font(0),
_font_size(0.0),
_style(0),
_listener_active(false),
_clip_box(0)
{
SG_LOG(SG_COCKPIT, SG_INFO, "Initializing HUD Instrument");
SGPropertyNode* hud = fgGetNode("/sim/hud");
hud->addChangeListener(this);
}
HUD::~HUD()
{
SGPropertyNode* hud = fgGetNode("/sim/hud");
hud->removeChangeListener(this);
deinit();
}
void HUD::init()
{
std::string fontName;
if (!_font) {
fontName = fgGetString("/sim/hud/font/name", "Helvetica.txf");
_font = FGFontCache::instance()->getTexFont(fontName);
}
if (!_font)
throw sg_io_exception("/sim/hud/font/name is not a texture font",
sg_location(fontName));
_font_size = fgGetFloat("/sim/hud/font/size", 8);
_font_renderer->setFont(_font);
_font_renderer->setPointSize(_font_size);
_text_list.setFont(_font_renderer);
_loaded = false;
currentColorChanged();
_currentPath->fireValueChanged();
}
void HUD::deinit()
{
deque<Item *>::const_iterator it, end = _items.end();
for (it = _items.begin(); it != end; ++it)
delete *it;
end = _ladders.end();
for (it = _ladders.begin(); it != end; ++it)
delete *it;
_items.clear();
_ladders.clear();
delete _clip_box;
_clip_box = NULL;
_loaded = false;
}
void HUD::reinit()
{
deinit();
_currentPath->fireValueChanged();
}
void HUD::update(double dt)
{
_timer += dt;
}
void HUD::draw(osg::State&)
{
if (!isVisible())
return;
if (_items.empty() && _ladders.empty())
return;
if (is3D()) {
draw3D();
return;
}
const float normal_aspect = 640.0f / 480.0f;
// note: aspect_ratio is Y/X
float current_aspect = 1.0f / globals->get_current_view()->get_aspect_ratio();
if (current_aspect > normal_aspect) {
float aspect_adjust = current_aspect / normal_aspect;
float adjust = 320.0f * aspect_adjust - 320.0f;
draw2D(-adjust, 0.0f, 640.0f + adjust, 480.0f);
} else {
float aspect_adjust = normal_aspect / current_aspect;
float adjust = 240.0f * aspect_adjust - 240.0f;
draw2D(0.0f, -adjust, 640.0f, 480.0f + adjust);
}
glViewport(0, 0, _scr_width, _scr_height);
}
void HUD::draw3D()
{
using namespace osg;
flightgear::View* view = globals->get_current_view();
// Standard fgfs projection, with essentially meaningless clip
// planes (we'll map the whole HUD plane to z=-1)
glMatrixMode(GL_PROJECTION);
glPushMatrix();
Matrixf proj
= Matrixf::perspective(view->get_v_fov(), 1/view->get_aspect_ratio(),
0.1, 10);
glLoadMatrix(proj.ptr());
glMatrixMode(GL_MODELVIEW);
glPushMatrix();
// Standard fgfs view direction computation
Vec3f lookat;
lookat[0] = -sin(SG_DEGREES_TO_RADIANS * view->getHeadingOffset_deg());
lookat[1] = tan(SG_DEGREES_TO_RADIANS * view->getPitchOffset_deg());
lookat[2] = -cos(SG_DEGREES_TO_RADIANS * view->getHeadingOffset_deg());
if (fabs(lookat[1]) > 9999)
lookat[1] = 9999; // FPU sanity
Matrixf mv = Matrixf::lookAt(Vec3f(0.0, 0.0, 0.0), lookat,
Vec3f(0.0, 1.0, 0.0));
glLoadMatrix(mv.ptr());
// Map the -1:1 square to a 55.0x41.25 degree wide patch at z=1.
// This is the default fgfs field of view, which the HUD files are
// written to assume.
float dx = 0.52056705; // tan(55/2)
float dy = dx * 0.75; // assumes 4:3 aspect ratio
float m[16];
m[0] = dx, m[4] = 0, m[ 8] = 0, m[12] = 0;
m[1] = 0, m[5] = dy, m[ 9] = 0, m[13] = 0;
m[2] = 0, m[6] = 0, m[10] = 1, m[14] = 0;
m[3] = 0, m[7] = 0, m[11] = 0, m[15] = 1;
glMultMatrixf(m);
// Convert the 640x480 "HUD standard" coordinate space to a square
// about the origin in the range [-1:1] at depth of -1
glScalef(1.0 / 320, 1.0 / 240, 1);
glTranslatef(-320, -240, -1);
common_draw();
glMatrixMode(GL_PROJECTION);
glPopMatrix();
glMatrixMode(GL_MODELVIEW);
glPopMatrix();
}
void HUD::draw2D(GLfloat x_start, GLfloat y_start, GLfloat x_end, GLfloat y_end)
{
using namespace osg;
glMatrixMode(GL_PROJECTION);
glPushMatrix();
Matrixf proj = Matrixf::ortho2D(x_start, x_end, y_start, y_end);
glLoadMatrix(proj.ptr());
glMatrixMode(GL_MODELVIEW);
glPushMatrix();
glLoadIdentity();
common_draw();
glMatrixMode(GL_PROJECTION);
glPopMatrix();
glMatrixMode(GL_MODELVIEW);
glPopMatrix();
}
void HUD::common_draw()
{
_text_list.erase();
_line_list.erase();
_stipple_line_list.erase();
glDisable(GL_DEPTH_TEST);
glDisable(GL_LIGHTING);
glEnable(GL_BLEND);
if (isTransparent())
glBlendFunc(GL_SRC_ALPHA, GL_ONE);
else
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
if (isAntialiased()) {
glEnable(GL_LINE_SMOOTH);
glAlphaFunc(GL_GREATER, alphaClamp());
glHint(GL_LINE_SMOOTH_HINT, GL_DONT_CARE);
//glLineWidth(1.5);
} else {
//glLineWidth(1.0);
}
setColor();
_clip_box->set();
deque<Item *>::const_iterator it, end = _items.end();
for (it = _items.begin(); it != end; ++it)
if ((*it)->isEnabled())
(*it)->draw();
_text_list.draw();
_line_list.draw();
if (! _stipple_line_list.empty()) {
glEnable(GL_LINE_STIPPLE);
glLineStipple(1, 0x00FF);
_stipple_line_list.draw();
glDisable(GL_LINE_STIPPLE);
}
// ladders last, as they can have their own clip planes
end = _ladders.end();
for (it = _ladders.begin(); it != end; ++it)
if ((*it)->isEnabled())
(*it)->draw();
_clip_box->unset();
if (isAntialiased()) {
glDisable(GL_ALPHA_TEST);
glDisable(GL_LINE_SMOOTH);
//glLineWidth(1.0);
}
if (isTransparent())
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glEnable(GL_DEPTH_TEST);
glEnable(GL_LIGHTING);
}
int HUD::load(const char *file, float x, float y, int level, const string& indent)
{
const sgDebugPriority TREE = SG_INFO;
const int MAXNEST = 10;
SGPath path(globals->resolve_maybe_aircraft_path(file));
if (path.isNull())
{
SG_LOG(SG_INPUT, SG_ALERT, "HUD: Cannot find configuration file '" << file << "'.");
return 0x2;
}
if (!level) {
SG_LOG(SG_INPUT, TREE, "load " << file);
_items.erase(_items.begin(), _items.end());
_ladders.erase(_ladders.begin(), _ladders.end());
} else if (level > MAXNEST) {
SG_LOG(SG_INPUT, SG_ALERT, "HUD: files nested more than " << MAXNEST << " levels");
return 0x1;
} else if (!file || !file[0]) {
SG_LOG(SG_INPUT, SG_ALERT, "HUD: invalid filename ");
return 0x2;
}
int ret = 0;
sg_ifstream input(path);
if (!input.good()) {
SG_LOG(SG_INPUT, SG_ALERT, "HUD: Cannot read configuration from '" << path << "'");
return 0x4;
}
SGPropertyNode root;
try {
readProperties(input, &root);
} catch (const sg_exception &e) {
input.close();
guiErrorMessage("HUD: Error ", e);
return 0x8;
}
delete _clip_box;
_clip_box = new ClipBox(fgGetNode("/sim/hud/clipping"), x, y);
for (int i = 0; i < root.nChildren(); i++) {
SGPropertyNode *n = root.getChild(i);
string d = n->getStringValue("name", "");
string desc;
if (!d.empty())
desc = string(": \"") + d + '"';
const string name = n->getNameString();
if (name == "name") {
continue;
} else if (name == "enable3d") {
// set in the tree so that valueChanged() picks it up
_3DenabledN->setBoolValue(n->getBoolValue());
continue;
} else if (name == "import") {
string fn = n->getStringValue("path", "");
float xoffs = n->getFloatValue("x-offset", 0.0f);
float yoffs = n->getFloatValue("y-offset", 0.0f);
SG_LOG(SG_INPUT, TREE, indent << "|__import " << fn << desc);
string ind = indent + string(i + 1 < root.nChildren() ? "| " : " ");
ret |= load(fn.c_str(), x + xoffs, y + yoffs, level + 1, ind);
continue;
}
SG_LOG(SG_INPUT, TREE, indent << "|__" << name << desc);
Item *item;
if (name == "label") {
item = static_cast<Item *>(new Label(this, n, x, y));
} else if (name == "gauge") {
item = static_cast<Item *>(new Gauge(this, n, x, y));
} else if (name == "tape") {
item = static_cast<Item *>(new Tape(this, n, x, y));
} else if (name == "dial") {
item = static_cast<Item *>(new Dial(this, n, x, y));
} else if (name == "turn-bank-indicator") {
item = static_cast<Item *>(new TurnBankIndicator(this, n, x, y));
} else if (name == "ladder") {
item = static_cast<Item *>(new Ladder(this, n, x, y));
_ladders.insert(_ladders.begin(), item);
continue;
} else if (name == "runway") {
item = static_cast<Item *>(new Runway(this, n, x, y));
} else if (name == "aiming-reticle") {
item = static_cast<Item *>(new AimingReticle(this, n, x, y));
} else {
SG_LOG(SG_INPUT, TREE, indent << " \\...unsupported!");
continue;
}
_items.insert(_items.begin(), item);
}
input.close();
SG_LOG(SG_INPUT, TREE, indent);
return ret;
}
void HUD::valueChanged(SGPropertyNode *node)
{
if (_listener_active)
return;
_listener_active = true;
bool loadNow = false;
_visible = _visibility->getBoolValue();
if (_visible && !_loaded) {
loadNow = true;
}
if (node->getNameString() == "current-path" && _visible) {
loadNow = true;
}
if (loadNow) {
int pathIndex = _currentPath->getIntValue();
SGPropertyNode* pathNode = fgGetNode("/sim/hud/path", pathIndex);
std::string path("Huds/default.xml");
if (pathNode && pathNode->hasValue()) {
path = pathNode->getStringValue();
SG_LOG(SG_INSTR, SG_INFO, "will load Hud from " << path);
}
_loaded = true;
load(path.c_str());
}
if (node->getNameString() == "current-color") {
currentColorChanged();
}
_scr_width = _scr_widthN->getIntValue();
_scr_height = _scr_heightN->getIntValue();
_3Denabled = _3DenabledN->getBoolValue();
_transparent = _transparency->getBoolValue();
_antialiased = _antialiasing->getBoolValue();
float brt = _brightness->getFloatValue();
_r = clamp(brt * _red->getFloatValue());
_g = clamp(brt * _green->getFloatValue());
_b = clamp(brt * _blue->getFloatValue());
_a = clamp(_alpha->getFloatValue());
_cl = clamp(_alpha_clamp->getFloatValue());
_units = _unitsN->getStringValue() != "feet" ? METER : FEET;
_listener_active = false;
}
void HUD::currentColorChanged()
{
SGPropertyNode *n = fgGetNode("/sim/hud/palette", true);
int index = _currentColor->getIntValue();
if (index < 0) {
index = 0;
}
n = n->getChild("color", index, false);
if (!n) {
return;
}
if (n->hasValue("red"))
_red->setFloatValue(n->getFloatValue("red", 1.0));
if (n->hasValue("green"))
_green->setFloatValue(n->getFloatValue("green", 1.0));
if (n->hasValue("blue"))
_blue->setFloatValue(n->getFloatValue("blue", 1.0));
if (n->hasValue("alpha"))
_alpha->setFloatValue(n->getFloatValue("alpha", 0.67));
if (n->hasValue("alpha-clamp"))
_alpha_clamp->setFloatValue(n->getFloatValue("alpha-clamp", 0.01));
if (n->hasValue("brightness"))
_brightness->setFloatValue(n->getFloatValue("brightness", 0.75));
if (n->hasValue("antialiased"))
_antialiasing->setBoolValue(n->getBoolValue("antialiased", false));
if (n->hasValue("transparent"))
_transparency->setBoolValue(n->getBoolValue("transparent", false));
}
void HUD::setColor() const
{
if (_antialiased)
glColor4f(_r, _g, _b, _a);
else
glColor3f(_r, _g, _b);
}
void HUD::textAlign(fntRenderer *rend, const char *s, int align,
float *x, float *y, float *l, float *r, float *b, float *t)
{
fntFont *font = rend->getFont();
float gap = font->getGap();
float left, right, bot, top;
font->getBBox(s, rend->getPointSize(), rend->getSlant(), &left, &right, &bot, &top);
if (align & HUD::HCENTER)
*x -= left - gap + (right - left - gap) / 2.0;
else if (align & HUD::RIGHT)
*x -= right;
else if (align & HUD::LEFT)
*x -= left;
if (align & HUD::VCENTER)
*y -= bot + (top - bot) / 2.0;
else if (align & HUD::TOP)
*y -= top;
else if (align & HUD::BOTTOM)
*y -= bot;
*l = *x + left;
*r = *x + right;
*b = *y + bot;
*t = *y + top;
}
// HUDText -- text container for TextList vector
HUDText::HUDText(fntRenderer *fnt, float x, float y, const char *s, int align, int d) :
_fnt(fnt),
_x(x),
_y(y),
_digits(d)
{
strncpy(_msg, s, BUFSIZE - 1);
_msg[BUFSIZE - 1] = '\0';
if (!align || !s[0])
return;
float ign;
HUD::textAlign(fnt, s, align, &_x, &_y, &ign, &ign, &ign, &ign);
}
void HUDText::draw()
{
if (!_digits) { // show all digits in same size
_fnt->start2f(_x, _y);
_fnt->puts(_msg);
return;
}
// FIXME
// this code is changed to display Numbers with big/small digits
// according to MIL Standards for example Altitude above 10000 ft
// is shown as 10ooo.
int c = 0, i = 0;
char *t = _msg;
int p = 4;
if (t[0] == '-') {
//if negative value then increase the c and p values
//for '-' sign.
c++; // was moved to the comment. Unintentionally? TODO
p++;
}
char *tmp = _msg;
while (tmp[i] != '\0') {
if ((tmp[i] >= '0') && (tmp[i] <= '9'))
c++;
i++;
}
float orig_size = _fnt->getPointSize();
if (c > p) {
_fnt->setPointSize(orig_size * 0.8);
int p1 = c - 3;
char *tmp1 = _msg + p1;
int p2 = p1 * 8;
_fnt->start2f(_x + p2, _y);
_fnt->puts(tmp1);
_fnt->setPointSize(orig_size * 1.2);
char tmp2[BUFSIZE];
strncpy(tmp2, _msg, p1);
tmp2[p1] = '\0';
_fnt->start2f(_x, _y);
_fnt->puts(tmp2);
} else {
_fnt->setPointSize(orig_size * 1.2);
_fnt->start2f(_x, _y);
_fnt->puts(tmp);
}
_fnt->setPointSize(orig_size);
}
// Register the subsystem.
SGSubsystemMgr::Registrant<HUD> registrantHUD;
void TextList::align(const char *s, int align, float *x, float *y,
float *l, float *r, float *b, float *t) const
{
HUD::textAlign(_font, s, align, x, y, l, r, b, t);
}
void TextList::draw()
{
assert(_font);
// FIXME
glPushAttrib(GL_COLOR_BUFFER_BIT);
glEnable(GL_BLEND);
_font->begin();
vector<HUDText>::iterator it, end = _list.end();
for (it = _list.begin(); it != end; ++it)
it->draw();
_font->end();
glDisable(GL_TEXTURE_2D);
glPopAttrib();
}
ClipBox::ClipBox(const SGPropertyNode *n, float xoffset, float yoffset) :
_active(false),
_xoffs(xoffset),
_yoffs(yoffset)
{
if (!n)
return;
// const_cast is necessary because ATM there's no matching getChild(const ...)
// prototype and getNode(const ..., <bool>) is wrongly interpreted as
// getNode(const ..., <int>)
_top_node = (const_cast<SGPropertyNode *>(n))->getChild("top", 0, true);
_bot_node = (const_cast<SGPropertyNode *>(n))->getChild("bottom", 0, true);
_left_node = (const_cast<SGPropertyNode *>(n))->getChild("left", 0, true);
_right_node = (const_cast<SGPropertyNode *>(n))->getChild("right", 0, true);
_left[0] = 1.0, _left[1] = _left[2] = 0.0;
_right[0] = -1.0, _right[1] = _right[2] = 0.0;
_top[0] = 0.0, _top[1] = -1.0, _top[2] = 0.0;
_bot[0] = 0.0, _bot[1] = 1.0, _bot[2] = 0.0;
_active = true;
}
void ClipBox::set()
{
if (!_active)
return;
_left[3] = -_left_node->getDoubleValue() - _xoffs;
_right[3] = _right_node->getDoubleValue() + _xoffs;
_bot[3] = -_bot_node->getDoubleValue() - _yoffs;
_top[3] = _top_node->getDoubleValue() + _yoffs;
glClipPlane(GL_CLIP_PLANE0, _top);
glEnable(GL_CLIP_PLANE0);
glClipPlane(GL_CLIP_PLANE1, _bot);
glEnable(GL_CLIP_PLANE1);
glClipPlane(GL_CLIP_PLANE2, _left);
glEnable(GL_CLIP_PLANE2);
glClipPlane(GL_CLIP_PLANE3, _right);
glEnable(GL_CLIP_PLANE3);
}
void ClipBox::unset()
{
if (_active) {
glDisable(GL_CLIP_PLANE0);
glDisable(GL_CLIP_PLANE1);
glDisable(GL_CLIP_PLANE2);
glDisable(GL_CLIP_PLANE3);
}
}

View File

@@ -0,0 +1,232 @@
// HUD.hxx -- Head Up Display
//
// Written by Michele America, started September 1997.
//
// Copyright (C) 1997 Michele F. America [micheleamerica#geocities:com]
// Copyright (C) 2006 Melchior FRANZ [mfranz#aon:at]
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation; either version 2 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#ifndef _HUD_HXX
#define _HUD_HXX
#include <simgear/compiler.h>
#include <vector>
#include <deque>
#include <osg/State>
#include <simgear/math/SGLimits.hxx>
#include <simgear/constants.h>
#include <simgear/structure/subsystem_mgr.hxx>
#include <simgear/props/props.hxx>
class FGFontCache;
class fntRenderer;
class fntTexFont;
class FGViewer;
class ClipBox;
class LineSegment
{
public:
LineSegment(GLfloat x0, GLfloat y0, GLfloat x1, GLfloat y1)
: _x0(x0), _y0(y0), _x1(x1), _y1(y1) {}
void draw() const {
glVertex2f(_x0, _y0);
glVertex2f(_x1, _y1);
}
private:
GLfloat _x0, _y0, _x1, _y1;
};
class LineList
{
public:
void add(const LineSegment& seg) { _list.push_back(seg); }
void erase() { _list.erase(_list.begin(), _list.end()); }
inline unsigned int size() const { return _list.size(); }
inline bool empty() const { return _list.empty(); }
void draw() {
glBegin(GL_LINES);
std::vector<LineSegment>::const_iterator it, end = _list.end();
for (it = _list.begin(); it != end; ++it)
it->draw();
glEnd();
}
private:
std::vector<LineSegment> _list;
};
class HUDText
{
public:
HUDText(fntRenderer *f, float x, float y, const char *s, int align = 0, int digits = 0);
void draw();
private:
fntRenderer *_fnt;
float _x, _y;
int _digits;
static const int BUFSIZE = 64;
char _msg[BUFSIZE];
};
class TextList
{
public:
TextList() { _font = 0; }
void setFont(fntRenderer *Renderer) { _font = Renderer; }
void add(float x, float y, const char *s, int align = 0, int digit = 0) {
_list.push_back(HUDText(_font, x, y, s, align, digit));
}
void erase() { _list.erase(_list.begin(), _list.end()); }
void align(const char *s, int align, float *x, float *y,
float *l, float *r, float *b, float *t) const;
void draw();
private:
fntRenderer *_font;
std::vector<HUDText> _list;
};
class HUD : public SGSubsystem,
public SGPropertyChangeListener
{
public:
HUD();
~HUD();
// Subsystem API.
void init() override;
void reinit() override;
void update(double) override;
// Subsystem identification.
static const char* staticSubsystemClassId() { return "hud"; }
// called from Main/renderer.cxx to draw 2D and 3D HUD
void draw(osg::State&);
// listener callback to read various HUD related properties
void valueChanged(SGPropertyNode *);
// set current glColor
void setColor() const;
inline bool isVisible() const { return _visible; }
inline bool isAntialiased() const { return _antialiased; }
inline bool isTransparent() const { return _transparent; }
inline bool is3D() const { return _3Denabled; }
inline float alphaClamp() const { return _cl; }
inline double timer() const { return _timer; }
static void textAlign(fntRenderer *rend, const char *s, int align, float *x, float *y,
float *l, float *r, float *b, float *t);
enum Units { FEET, METER };
Units getUnits() const { return _units; }
enum {
HORIZONTAL = 0x0000, // keep that at zero?
VERTICAL = 0x0001,
TOP = 0x0002,
BOTTOM = 0x0004,
LEFT = 0x0008,
RIGHT = 0x0010,
NOTICKS = 0x0020,
NOTEXT = 0x0040,
BOTH = (LEFT|RIGHT),
// for alignment (with LEFT, RIGHT, TOP, BOTTOM)
HCENTER = 0x0080,
VCENTER = 0x0100,
CENTER = (HCENTER|VCENTER),
};
protected:
void common_draw();
int load(const char *, float x = 320.0f, float y = 240.0f,
int level = 0, const std::string& indent = "");
private:
void deinit();
void draw3D();
void draw2D(GLfloat, GLfloat, GLfloat, GLfloat);
void currentColorChanged();
class Input;
class Item;
class Label;
class Scale;
class Gauge;
class Tape;
class Dial;
class TurnBankIndicator;
class Ladder;
class Runway;
class AimingReticle;
std::deque<Item *> _items;
std::deque<Item *> _ladders;
SGPropertyNode_ptr _currentPath;
SGPropertyNode_ptr _currentColor;
SGPropertyNode_ptr _visibility;
SGPropertyNode_ptr _3DenabledN;
SGPropertyNode_ptr _antialiasing;
SGPropertyNode_ptr _transparency;
SGPropertyNode_ptr _red, _green, _blue, _alpha;
SGPropertyNode_ptr _alpha_clamp;
SGPropertyNode_ptr _brightness;
bool _visible;
bool _loaded;
bool _3Denabled;
bool _antialiased;
bool _transparent;
float _r, _g, _b, _a, _cl;
SGPropertyNode_ptr _scr_widthN, _scr_heightN;
int _scr_width, _scr_height;
SGPropertyNode_ptr _unitsN;
Units _units;
double _timer;
fntRenderer *_font_renderer;
FGFontCache *_font_cache;
fntTexFont *_font;
float _font_size;
int _style;
bool _listener_active;
ClipBox *_clip_box;
TextList _text_list;
LineList _line_list;
LineList _stipple_line_list;
};
#endif // _HUD_HXX

View File

@@ -0,0 +1,87 @@
// HUD_dial.cxx -- HUD Dial Instrument
//
// Written by Michele America, started September 1997.
//
// Copyright (C) 1997 Michele F. America [micheleamerica#geocities:com]
// Copyright (C) 2006 Melchior FRANZ [mfranz#aon:at]
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation; either version 2 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#include "HUD.hxx"
#include "HUD_private.hxx"
HUD::Dial::Dial(HUD *hud, const SGPropertyNode *n, float x, float y) :
Scale(hud, n, x, y),
_radius(n->getFloatValue("radius")),
_divisions(n->getIntValue("divisions"))
{
}
void HUD::Dial::draw(void)
{
if (!_input.isValid())
return;
const int BUFSIZE = 80;
char buf[BUFSIZE];
glEnable(GL_POINT_SMOOTH);
glPointSize(3.0);
float incr = 360.0 / _divisions;
for (float i = 0.0; i < 360.0; i += incr) {
float i1 = i * SGD_DEGREES_TO_RADIANS;
float x1 = _x + _radius * cos(i1);
float y1 = _y + _radius * sin(i1);
glBegin(GL_POINTS);
glVertex2f(x1, y1);
glEnd();
}
glPointSize(1.0);
glDisable(GL_POINT_SMOOTH);
float offset = 90.0 * SGD_DEGREES_TO_RADIANS;
const float R = 10.0; //size of carrot
float theta = _input.getFloatValue();
float theta1 = -theta * SGD_DEGREES_TO_RADIANS + offset;
float x1 = _x + _radius * cos(theta1);
float y1 = _y + _radius * sin(theta1);
float x2 = x1 - R * cos(theta1 - 30.0 * SGD_DEGREES_TO_RADIANS);
float y2 = y1 - R * sin(theta1 - 30.0 * SGD_DEGREES_TO_RADIANS);
float x3 = x1 - R * cos(theta1 + 30.0 * SGD_DEGREES_TO_RADIANS);
float y3 = y1 - R * sin(theta1 + 30.0 * SGD_DEGREES_TO_RADIANS);
// draw carrot
draw_line(x1, y1, x2, y2);
draw_line(x1, y1, x3, y3);
snprintf(buf, BUFSIZE, "%3.1f\n", theta);
// draw value
int l = abs((int)theta);
if (l) {
if (l < 10)
draw_text(_x, _y, buf, 0);
else if (l < 100)
draw_text(_x - 1.0, _y, buf, 0);
else if (l < 360)
draw_text(_x - 2.0, _y, buf, 0);
}
}

View File

@@ -0,0 +1,264 @@
// HUD_gauge.cxx -- HUD Gauge Instrument
//
// Written by Michele America, started September 1997.
//
// Copyright (C) 1997 Michele F. America [micheleamerica#geocities:com]
// Copyright (C) 2006 Melchior FRANZ [mfranz#aon:at]
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation; either version 2 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#include "HUD.hxx"
#include "HUD_private.hxx"
HUD::Gauge::Gauge(HUD *hud, const SGPropertyNode *n, float x, float y) :
Scale(hud, n, x, y)
{
}
// As implemented, draw only correctly draws a horizontal or vertical
// scale. It should contain a variation that permits clock type displays.
// Now is supports "tickless" displays such as control surface indicators.
// This routine should be worked over before using. Current value would be
// fetched and not used if not commented out. Clearly that is intollerable.
void HUD::Gauge::draw(void)
{
if (!_input.isValid())
return;
float marker_xs, marker_xe;
float marker_ys, marker_ye;
float text_y;
int i;
const int BUFSIZE = 80;
char buf[BUFSIZE];
bool condition;
int disp_val = 0;
float vmin = _input.min();
float vmax = _input.max();
float cur_value = _input.getFloatValue();
float right = _x + _w;
float top = _y + _h;
float bottom_4 = _h / 4.0; // FIXME
float right_4 = _w / 4.0;
// Draw the basic markings for the scale...
if (option_vert()) { // Vertical scale
// Bottom tick bar
draw_line(_x, _y, right, _y);
// Top tick bar
draw_line(_x, top, right, top);
marker_xs = _x;
marker_xe = right;
if (option_left()) { // Read left, so line down right side
draw_line(right, _y, right, top);
marker_xs = marker_xe - _w / 2.0; // Adjust tick
}
if (option_right()) { // Read right, so down left sides
draw_line(_x, _y, _x, top);
marker_xe = _x + _w / 2.0; // Adjust tick
}
// At this point marker x_start and x_end values are transposed.
// To keep this from confusing things they are now interchanged.
if (option_both()) {
marker_ye = marker_xs;
marker_xs = marker_xe;
marker_xe = marker_ye;
}
// Work through from bottom to top of scale. Calculating where to put
// minor and major ticks.
if (!option_noticks()) { // If not no ticks...:)
// Calculate x marker offsets
int last = (int)vmax + 1;
i = (int)vmin;
for (; i < last; i++) {
// Calculate the location of this tick
marker_ys = _y + (i - vmin) * factor()/* +.5f*/;
// We compute marker_ys even though we don't know if we will use
// either major or minor divisions. Simpler.
if (_minor_divs) { // Minor tick marks
if (!(i % (int)_minor_divs)) {
if (option_left() && option_right()) {
draw_line(_x, marker_ys, marker_xs - 4, marker_ys);
draw_line(marker_xe + 4, marker_ys, right, marker_ys);
} else if (option_left()) {
draw_line(marker_xs + 4, marker_ys, marker_xe, marker_ys);
} else {
draw_line(marker_xs, marker_ys, marker_xe - 4, marker_ys);
}
}
}
// Now we work on the major divisions. Since these are also labeled
// and no labels are drawn otherwise, we label inside this if
// statement.
if (_major_divs) { // Major tick mark
if (!(i % (int)_major_divs)) {
if (option_left() && option_right()) {
draw_line(_x, marker_ys, marker_xs, marker_ys);
draw_line(marker_xe, marker_ys, right, marker_ys);
} else {
draw_line(marker_xs, marker_ys, marker_xe, marker_ys);
}
if (!option_notext()) {
disp_val = i;
snprintf(buf, BUFSIZE, "%d",
int(disp_val * _input.factor()/*+.5*/)); /// was data_scaling(), which makes no sense
if (option_left() && option_right())
draw_text(_center_x, marker_ys, buf, CENTER);
else if (option_left())
draw_text(marker_xs, marker_ys, buf, RIGHT|VCENTER);
else
draw_text(marker_xe, marker_ys, buf, LEFT|VCENTER);
}
}
}
}
}
// Now that the scale is drawn, we draw in the pointer(s).
text_y = _y + ((cur_value - vmin) * factor() /*+.5f*/);
if (option_right()) {
_hud->_line_list.add(LineSegment(_x, text_y + right_4, marker_xe, text_y));
_hud->_line_list.add(LineSegment(marker_xe, text_y, _x, text_y - right_4));
}
if (option_left()) {
_hud->_line_list.add(LineSegment(right, text_y + right_4, marker_xs, text_y));
_hud->_line_list.add(LineSegment(marker_xs, text_y, right, text_y - right_4));
}
// End if VERTICAL SCALE TYPE
} else { // Horizontal scale by default
// left tick bar
draw_line(_x, _y, _x, top);
// right tick bar
draw_line(right, _y, right, top );
marker_ys = _y; // Starting point for
marker_ye = top; // tick y location calcs
marker_xs = _x + (cur_value - vmin) * factor() /*+ .5f*/;
if (option_top()) {
// Bottom box line
draw_line(_x, _y, right, _y);
marker_ye = _y + _h / 2.0; // Tick point adjust
// Bottom arrow
_hud->_line_list.add(LineSegment(marker_xs - bottom_4, _y, marker_xs, marker_ye));
_hud->_line_list.add(LineSegment(marker_xs, marker_ye, marker_xs + bottom_4, _y));
}
if (option_bottom()) {
// Top box line
draw_line(_x, top, right, top);
// Tick point adjust
marker_ys = top - _h / 2.0;
// Top arrow
_hud->_line_list.add(LineSegment(marker_xs + bottom_4, top, marker_xs, marker_ys));
_hud->_line_list.add(LineSegment(marker_xs, marker_ys, marker_xs - bottom_4, top));
}
int last = (int)vmax + 1;
i = (int)vmin;
for (; i <last ; i++) {
condition = true;
if (!_modulo && i < _input.min())
condition = false;
if (condition) {
marker_xs = _x + (i - vmin) * factor()/* +.5f*/;
// marker_xs = _x + (int)((i - vmin) * factor() + .5f);
if (_minor_divs) {
if (!(i % (int)_minor_divs)) {
// draw in ticks only if they aren't too close to the edge.
if (((marker_xs + 5) > _x)
|| ((marker_xs - 5) < right)) {
if (option_both()) {
draw_line(marker_xs, _y, marker_xs, marker_ys - 4);
draw_line(marker_xs, marker_ye + 4, marker_xs, top);
} else if (option_top()) {
draw_line(marker_xs, marker_ys, marker_xs, marker_ye - 4);
} else {
draw_line(marker_xs, marker_ys + 4, marker_xs, marker_ye);
}
}
}
}
if (_major_divs) {
if (!(i % (int)_major_divs)) {
if (_modulo) {
if (disp_val < 0) {
while (disp_val < 0)
disp_val += _modulo;
}
disp_val = i % (int)_modulo;
} else {
disp_val = i;
}
snprintf(buf, BUFSIZE, "%d",
int(disp_val * _input.factor()/* +.5*/)); // was data_scaling(), which makes no sense
// Draw major ticks and text only if far enough from the edge.
if (((marker_xs - 10) > _x)
&& ((marker_xs + 10) < right)) {
if (option_both()) {
draw_line(marker_xs, _y, marker_xs, marker_ys);
draw_line(marker_xs, marker_ye, marker_xs, top);
if (!option_notext())
draw_text(marker_xs, marker_ys, buf, CENTER);
} else {
draw_line(marker_xs, marker_ys, marker_xs, marker_ye);
if (!option_notext()) {
if (option_top())
draw_text(marker_xs, top, buf, TOP|HCENTER);
else
draw_text(marker_xs, _y, buf, BOTTOM|HCENTER);
}
}
}
}
}
}
}
}
}

View File

@@ -0,0 +1,213 @@
// HUD_instrument.cxx -- HUD Common Instrument Base
//
// Written by Michele America, started September 1997.
//
// Copyright (C) 1997 Michele F. America [micheleamerica#geocities:com]
// Copyright (C) 2006 Melchior FRANZ [mfranz#aon:at]
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation; either version 2 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif
#include <simgear/math/SGLimits.hxx>
#include <simgear/props/condition.hxx>
#include "HUD.hxx"
#include "HUD_private.hxx"
#include <Main/globals.hxx>
using std::vector;
HUD::Item::Item(HUD *hud, const SGPropertyNode *n, float x, float y) :
_hud(hud),
_name(n->getStringValue("name", "[unnamed]")),
_options(0),
_condition(0),
_digits(n->getIntValue("digits"))
{
const SGPropertyNode *node = n->getNode("condition");
if (node)
_condition = sgReadCondition(globals->get_props(), node);
_x = n->getFloatValue("x") + x;
_y = n->getFloatValue("y") + y;
_w = n->getFloatValue("width");
_h = n->getFloatValue("height");
vector<SGPropertyNode_ptr> opt = n->getChildren("option");
for (unsigned int i = 0; i < opt.size(); i++) {
string o = opt[i]->getStringValue();
if (o == "vertical")
_options |= VERTICAL;
else if (o == "horizontal")
_options |= HORIZONTAL;
else if (o == "top")
_options |= TOP;
else if (o == "left")
_options |= LEFT;
else if (o == "bottom")
_options |= BOTTOM;
else if (o == "right")
_options |= RIGHT;
else if (o == "both")
_options |= (LEFT|RIGHT);
else if (o == "noticks")
_options |= NOTICKS;
else if (o == "notext")
_options |= NOTEXT;
else
SG_LOG(SG_INPUT, SG_WARN, "HUD: unsupported option: " << o);
}
// Set up convenience values for centroid of the box and
// the span values according to orientation
if (_options & VERTICAL) {
_scr_span = _h;
} else {
_scr_span = _w;
}
_center_x = _x + _w / 2.0;
_center_y = _y + _h / 2.0;
}
bool HUD::Item::isEnabled()
{
return _condition ? _condition->test() : true;
}
void HUD::Item::draw_line(float x1, float y1, float x2, float y2)
{
_hud->_line_list.add(LineSegment(x1, y1, x2, y2));
}
void HUD::Item::draw_stipple_line(float x1, float y1, float x2, float y2)
{
_hud->_stipple_line_list.add(LineSegment(x1, y1, x2, y2));
}
void HUD::Item::draw_text(float x, float y, const char *msg, int align, int digit)
{
_hud->_text_list.add(x, y, msg, align, digit);
}
void HUD::Item::draw_circle(float xoffs, float yoffs, float r) const
{
float step = SG_PI / r;
double prevX = r;
double prevY = 0.0;
for (float alpha = step; alpha < SG_PI * 2.0; alpha += step) {
float x = r * cos(alpha);
float y = r * sin(alpha);
_hud->_line_list.add(LineSegment(prevX + xoffs, prevY + yoffs,
x + xoffs, y + yoffs));
prevX = x;
prevY = y;
}
}
void HUD::Item::draw_arc(float xoffs, float yoffs, float t0, float t1, float r) const
{
float step = SG_PI / r;
t0 = t0 * SG_DEGREES_TO_RADIANS;
t1 = t1 * SG_DEGREES_TO_RADIANS;
double prevX = r * cos(t0);
double prevY = r * sin(t0);
for (float alpha = t0 + step; alpha < t1; alpha += step) {
float x = r * cos(alpha);
float y = r * sin(alpha);
_hud->_line_list.add(LineSegment(prevX + xoffs, prevY + yoffs,
x + xoffs, y + yoffs));
prevX = x;
prevY = y;
}
}
void HUD::Item::draw_bullet(float x, float y, float size)
{
glEnable(GL_POINT_SMOOTH);
glPointSize(size);
glBegin(GL_POINTS);
glVertex2f(x, y);
glEnd();
glPointSize(1.0);
glDisable(GL_POINT_SMOOTH);
}
// make sure the format matches '[ -+#]?\d*(\.\d*)?(l?[df]|s)'
//
HUD::Item::Format HUD::Item::check_format(const char *f) const
{
bool l = false;
Format fmt = STRING;
for (; *f; f++) {
if (*f == '%') {
if (f[1] == '%')
f++;
else
break;
}
}
if (*f++ != '%')
return NONE;
if (*f == ' ' || *f == '+' || *f == '-' || *f == '#')
f++;
while (*f && isdigit(*f))
f++;
if (*f == '.') {
f++;
while (*f && isdigit(*f))
f++;
}
if (*f == 'l')
l = true, f++;
if (*f == 'd')
fmt = l ? LONG : INT;
else if (*f == 'f')
fmt = l ? DOUBLE : FLOAT;
else if (*f == 's') {
if (l)
return INVALID;
fmt = STRING;
} else
return INVALID;
for (++f; *f; f++) {
if (*f == '%') {
if (f[1] == '%')
f++;
else
return INVALID;
}
}
return fmt;
}

View File

@@ -0,0 +1,185 @@
// HUD_label.cxx -- HUD Label
//
// Written by Michele America, started September 1997.
//
// Copyright (C) 1997 Michele F. America [micheleamerica#geocities:com]
// Copyright (C) 2006 Melchior FRANZ [mfranz#aon:at]
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation; either version 2 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif
#include "HUD.hxx"
#include "HUD_private.hxx"
#include <Main/globals.hxx>
HUD::Label::Label(HUD *hud, const SGPropertyNode *n, float x, float y) :
Item(hud, n, x, y),
_input(n->getNode("input", false)),
_box(n->getBoolValue("box", false)),
_pointer_width(n->getFloatValue("pointer-width", 7.0)),
_pointer_length(n->getFloatValue("pointer-length", 5.0)),
_blink_condition(0),
_blink_interval(n->getFloatValue("blinking/interval", -1.0f)),
_blink_target(0.0),
_blink_state(true)
{
const SGPropertyNode *node = n->getNode("blinking/condition");
if (node)
_blink_condition = sgReadCondition(globals->get_props(), node);
string halign = n->getStringValue("halign", "center");
if (halign == "left")
_halign = LEFT;
else if (halign == "right")
_halign = RIGHT;
else
_halign = HCENTER;
_halign |= VCENTER;
string pre = n->getStringValue("prefix", "");
string post = n->getStringValue("postfix", "");
string fmt = n->getStringValue("format", "");
if (!pre.empty())
_format = pre;
if (!fmt.empty())
_format += fmt;
else
_format += "%s";
if (!post.empty())
_format += post;
_mode = check_format(_format.c_str());
if (_mode == INVALID) {
SG_LOG(SG_INPUT, SG_ALERT, "HUD: invalid format '" << _format.c_str()
<< "' in <label> '" << _name << '\'');
_format = "INVALID";
_mode = NONE;
}
blink();
}
void HUD::Label::draw(void)
{
if (!((_mode == NONE || _input.isValid()) && blink()))
return;
if (_box) {
float l, r, p;
float pw = _pointer_width / 2.0;
l = _center_x - pw;
r = _center_x + pw;
bool draw_parallel = fabs(_pointer_width - _w) > 2.0; // draw lines left and right of arrow?
if (option_bottom()) {
if (draw_parallel) {
draw_line(_x, _y, l, _y);
draw_line(r, _y, _x + _w, _y);
}
p = _y - _pointer_length;
draw_line(l, _y, _center_x, p);
draw_line(_center_x, p, r, _y);
} else
draw_line(_x, _y, _x + _w, _y);
if (option_top()) {
if (draw_parallel) {
draw_line(_x, _y + _h, l, _y + _h);
draw_line(r, _y + _h, _x + _w, _y + _h);
}
p = _y + _h + _pointer_length;
draw_line(l, _y + _h, _center_x, p);
draw_line(_center_x, p, r, _y + _h);
} else
draw_line(_x + _w, _y + _h, _x, _y + _h);
l = _center_y - pw;
r = _center_y + pw;
draw_parallel = fabs(_pointer_width - _h) > 2.0;
if (option_left()) {
if (draw_parallel) {
draw_line(_x, _y, _x, l);
draw_line(_x, r, _x, _y + _h);
}
p = _x - _pointer_length;
draw_line(_x, l, p, _center_y);
draw_line(p, _center_y, _x, r);
} else
draw_line(_x, _y + _h, _x, _y);
if (option_right()) {
if (draw_parallel) {
draw_line(_x + _w, _y, _x + _w, l);
draw_line(_x + _w, r, _x + _w, _y + _h);
}
p = _x + _w + _pointer_length;
draw_line(_x + _w, l, p, _center_y);
draw_line(p, _center_y, _x + _w, r);
} else
draw_line(_x + _w, _y, _x + _w, _y + _h);
}
const int BUFSIZE = 256;
char buf[BUFSIZE+1];
buf[ BUFSIZE] = '\0'; // Be sure to terminate properly
if (_mode == NONE)
snprintf(buf, BUFSIZE, _format.c_str(), 0);
else if (_mode == STRING)
snprintf(buf, BUFSIZE, _format.c_str(), _input.getStringValue().c_str());
else if (_mode == INT)
snprintf(buf, BUFSIZE, _format.c_str(), int(_input.getFloatValue()));
else if (_mode == LONG)
snprintf(buf, BUFSIZE, _format.c_str(), long(_input.getFloatValue()));
else if (_mode == FLOAT)
snprintf(buf, BUFSIZE, _format.c_str(), float(_input.getFloatValue()));
else if (_mode == DOUBLE) // not really supported yet
snprintf(buf, BUFSIZE, _format.c_str(), double(_input.getFloatValue()));
if (_halign & HCENTER)
draw_text(_center_x, _center_y, buf, _halign, get_digits());
else if (_halign & LEFT)
draw_text(_x, _center_y, buf, _halign, get_digits());
else // if (_halign & RIGHT)
draw_text(_x + _w, _center_y, buf, _halign, get_digits());
}
bool HUD::Label::blink()
{
if (_blink_interval < 0.0f)
return true;
if (_blink_condition && !_blink_condition->test())
return true;
if (_hud->timer() < _blink_target)
return _blink_state;
_blink_target = _hud->timer() + _blink_interval;
return _blink_state = !_blink_state;
}

View File

@@ -0,0 +1,764 @@
// HUD_ladder.cxx -- HUD Ladder Instrument
//
// Written by Michele America, started September 1997.
//
// Copyright (C) 1997 Michele F. America [micheleamerica#geocities:com]
// Copyright (C) 2006 Melchior FRANZ [mfranz#aon:at]
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation; either version 2 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif
#include <sstream>
#include <simgear/math/SGGeometry.hxx>
#include <Viewer/view.hxx>
#include "HUD.hxx"
#include "HUD_private.hxx"
#include <Main/fg_props.hxx>
using std::string;
// FIXME
static float get__heading() { return fgGetFloat("/orientation/heading-deg") * M_PI / 180.0; }
static float get__throttleval() { return fgGetFloat("/controls/engines/engine/throttle"); }
static float get__Vx() { return fgGetFloat("/velocities/uBody-fps"); }
static float get__Vy() { return fgGetFloat("/velocities/vBody-fps"); }
static float get__Vz() { return fgGetFloat("/velocities/wBody-fps"); }
static float get__Ax() { return fgGetFloat("/accelerations/pilot/x-accel-fps_sec"); }
static float get__Ay() { return fgGetFloat("/accelerations/pilot/y-accel-fps_sec"); }
static float get__Az() { return fgGetFloat("/accelerations/pilot/z-accel-fps_sec"); }
static float get__alpha() { return fgGetFloat("/orientation/alpha-deg"); }
static float get__beta() { return fgGetFloat("/orientation/side-slip-deg"); }
#undef ENABLE_SP_FDM
HUD::Ladder::Ladder(HUD *hud, const SGPropertyNode *n, float x, float y) :
Item(hud, n, x, y),
_pitch(n->getNode("pitch-input", false)),
_roll(n->getNode("roll-input", false)),
_width_units(int(n->getFloatValue("display-span"))),
_div_units(int(fabs(n->getFloatValue("divisions")))),
_scr_hole(fabs(n->getFloatValue("screen-hole")) * 0.5f),
_zero_bar_overlength(n->getFloatValue("zero-bar-overlength", 10)),
_dive_bar_angle(n->getBoolValue("enable-dive-bar-angle")),
_tick_length(n->getFloatValue("tick-length")),
_compression(n->getFloatValue("compression-factor")),
_dynamic_origin(n->getBoolValue("enable-dynamic-origin")),
_frl(n->getBoolValue("enable-fuselage-ref-line")),
_target_spot(n->getBoolValue("enable-target-spot")),
_target_markers(n->getBoolValue("enable-target-markers")),
_velocity_vector(n->getBoolValue("enable-velocity-vector")),
_ground_velocity_vector(n->getBoolValue("enable-ground-velocity-vector")),
_drift_marker(n->getBoolValue("enable-drift-marker")),
_alpha_bracket(n->getBoolValue("enable-alpha-bracket")),
_energy_marker(n->getBoolValue("enable-energy-marker")),
_climb_dive_marker(n->getBoolValue("enable-climb-dive-marker")),
_glide_slope_marker(n->getBoolValue("enable-glide-slope-marker")),
_glide_slope(n->getFloatValue("glide-slope", -4.0)),
_energy_worm(n->getBoolValue("enable-energy-marker")),
_waypoint_marker(n->getBoolValue("enable-waypoint-marker")),
_zenith(n->getBoolValue("enable-zenith")),
_nadir(n->getBoolValue("enable-nadir")),
_hat(n->getBoolValue("enable-hat")),
_clip_box(new ClipBox(n->getNode("clipping")))
{
string t = n->getStringValue("type");
_type = t != "climb-dive" ? PITCH : CLIMB_DIVE;
if (!_width_units)
_width_units = 45;
_vmax = _width_units / 2;
_vmin = -_vmax;
}
HUD::Ladder::~Ladder()
{
delete _clip_box;
}
void HUD::Ladder::draw(void)
{
if (!_pitch.isValid() || !_roll.isValid())
return;
float roll_value = _roll.getFloatValue() * SGD_DEGREES_TO_RADIANS;
float pitch_value = _pitch.getFloatValue();
//**************************************************************
glPushMatrix();
glTranslatef(_center_x, _center_y, 0);
// OBJECT STATIC RETICLE
// TYPE FRL (FUSELAGE REFERENCE LINE)
// ATTRIB - ALWAYS
// Draw the FRL spot and line
if (_frl) {
#define FRL_DIAMOND_SIZE 2.0
glBegin(GL_LINE_LOOP);
glVertex2f(-FRL_DIAMOND_SIZE, 0.0);
glVertex2f(0.0, FRL_DIAMOND_SIZE);
glVertex2f(FRL_DIAMOND_SIZE, 0.0);
glVertex2f(0.0, -FRL_DIAMOND_SIZE);
glEnd();
glBegin(GL_LINE_STRIP);
glVertex2f(0, FRL_DIAMOND_SIZE);
glVertex2f(0, 8.0);
glEnd();
#undef FRL_DIAMOND_SIZE
}
// TYPE WATERLINE_MARK (W shaped _ _ ) // TODO (-> HUD_misc.cxx)
// \/\/
//****************************************************************
// TYPE TARGET_SPOT
// Draw the target spot.
if (_target_spot) {
#define CENTER_DIAMOND_SIZE 6.0
glBegin(GL_LINE_LOOP);
glVertex2f(-CENTER_DIAMOND_SIZE, 0.0);
glVertex2f(0.0, CENTER_DIAMOND_SIZE);
glVertex2f(CENTER_DIAMOND_SIZE, 0.0);
glVertex2f(0.0, -CENTER_DIAMOND_SIZE);
glEnd();
#undef CENTER_DIAMOND_SIZE
}
//****************************************************************
//velocity vector reticle - computations
float xvvr, /* yvvr, */ Vxx = 0.0, Vyy = 0.0, Vzz = 0.0;
float Axx = 0.0, Ayy = 0.0, Azz = 0.0, total_vel = 0.0, pot_slope; //, t1;
float up_vel, ground_vel, actslope = 0.0, psi = 0.0;
float vel_x = 0.0, vel_y = 0.0, drift;
float alpha;
if (_ground_velocity_vector || _velocity_vector) {
Vxx = get__Vx();
Vyy = get__Vy();
Vzz = get__Vz();
}
if (_ground_velocity_vector) {
double gvel_x = atan2(Vyy, Vxx) * SGD_RADIANS_TO_DEGREES * _compression;
double gvel_y = -atan2(Vzz, Vxx) * SGD_RADIANS_TO_DEGREES * _compression;
/* \ /
|
*/
int outer = 8;
int inner = 2;
glBegin(GL_LINE_STRIP);
glVertex2f(gvel_x - outer, gvel_y + outer);
glVertex2f(gvel_x - inner, gvel_y + inner);
glEnd();
glBegin(GL_LINE_STRIP);
glVertex2f(gvel_x + outer, gvel_y + outer);
glVertex2f(gvel_x + inner, gvel_y + inner);
glEnd();
glBegin(GL_LINE_STRIP);
glVertex2f(gvel_x + 0, gvel_y - inner*3/2);
glVertex2f(gvel_x + 0, gvel_y - outer*3/2);
glEnd();
}
if (_velocity_vector) {
drift = get__beta();
alpha = get__alpha();
Axx = get__Ax();
Ayy = get__Ay();
Azz = get__Az();
psi = get__heading();
if (psi > 180.0)
psi = psi - 360;
total_vel = sqrt(Vxx * Vxx + Vyy * Vyy + Vzz * Vzz);
ground_vel = sqrt(Vxx * Vxx + Vyy * Vyy);
up_vel = Vzz;
if (ground_vel < 2.0) {
if (fabs(up_vel) < 2.0)
actslope = 0.0;
else
actslope = (up_vel / fabs(up_vel)) * 90.0;
} else {
actslope = atan(up_vel / ground_vel) * SGD_RADIANS_TO_DEGREES;
}
xvvr = drift * _compression;
// drift = ((atan2(Vyy, Vxx) * SGD_RADIANS_TO_DEGREES) - psi);
// yvvr = (-alpha * _compression);
// vel_y = (-alpha * cos(roll_value) + drift * sin(roll_value)) * _compression;
// vel_x = (alpha * sin(roll_value) + drift * cos(roll_value))
// * (_compression / globals->get_current_view()->get_aspect_ratio());
vel_y = -alpha * _compression;
vel_x = drift * _compression;
// printf("%f %f %f %f\n",vel_x, vel_y, drift, psi);
//****************************************************************
// OBJECT MOVING RETICLE
// TYPE - DRIFT MARKER
// ATTRIB - ALWAYS
// drift marker
if (_drift_marker) {
glBegin(GL_LINE_STRIP);
glVertex2f((xvvr * 25 / 120) - 6, -4);
glVertex2f(xvvr * 25 / 120, 8);
glVertex2f((xvvr * 25 / 120) + 6, -4);
glEnd();
}
//****************************************************************
// OBJECT MOVING RETICLE
// TYPE VELOCITY VECTOR
// ATTRIB - ALWAYS
// velocity vector
draw_circle(vel_x, vel_y, 6);
//velocity vector reticle orientation lines
glBegin(GL_LINE_STRIP);
glVertex2f(vel_x - 12, vel_y);
glVertex2f(vel_x - 6, vel_y);
glEnd();
glBegin(GL_LINE_STRIP);
glVertex2f(vel_x + 12, vel_y);
glVertex2f(vel_x + 6, vel_y);
glEnd();
glBegin(GL_LINE_STRIP);
glVertex2f(vel_x, vel_y + 12);
glVertex2f(vel_x, vel_y + 6);
glEnd();
#ifdef ENABLE_SP_FDM
int lgear = get__iaux3();
int ihook = get__iaux6();
// OBJECT MOVING RETICLE
// TYPE LINE
// ATTRIB - ON CONDITION
if (lgear == 1) {
// undercarriage status
glBegin(GL_LINE_STRIP);
glVertex2f(vel_x + 8, vel_y);
glVertex2f(vel_x + 8, vel_y - 4);
glEnd();
// OBJECT MOVING RETICLE
// TYPE LINE
// ATTRIB - ON CONDITION
glBegin(GL_LINE_STRIP);
glVertex2f(vel_x - 8, vel_y);
glVertex2f(vel_x - 8, vel_y - 4);
glEnd();
// OBJECT MOVING RETICLE
// TYPE LINE
// ATTRIB - ON CONDITION
glBegin(GL_LINE_STRIP);
glVertex2f(vel_x, vel_y - 6);
glVertex2f(vel_x, vel_y - 10);
glEnd();
}
// OBJECT MOVING RETICLE
// TYPE V
// ATTRIB - ON CONDITION
if (ihook == 1) {
// arrestor hook status
glBegin(GL_LINE_STRIP);
glVertex2f(vel_x - 4, vel_y - 8);
glVertex2f(vel_x, vel_y - 10);
glVertex2f(vel_x + 4, vel_y - 8);
glEnd();
}
#endif
} // if _velocity_vector
// draw hud markers on top of each AI/MP target
if (_target_markers) {
SGPropertyNode *models = globals->get_props()->getNode("/ai/models", true);
for (int i = 0; i < models->nChildren(); i++) {
SGPropertyNode *chld = models->getChild(i);
string name;
name = chld->getNameString();
if (name == "tanker" || name == "aircraft" || name == "multiplayer") {
bool valid = chld->getBoolValue("valid");
bool in_range = chld->getBoolValue("radar/in-range", true);
if (valid && in_range) {
float h_deg = chld->getFloatValue("radar/h-offset");
float v_deg = chld->getFloatValue("radar/v-offset");
float pos_x = (h_deg * cos(roll_value) -
v_deg * sin(roll_value)) * _compression;
float pos_y = (v_deg * cos(roll_value) +
h_deg * sin(roll_value)) * _compression;
draw_circle(pos_x, pos_y, 8);
}
}
}
}
//***************************************************************
// OBJECT MOVING RETICLE
// TYPE - SQUARE_BRACKET
// ATTRIB - ON CONDITION
// alpha bracket
#ifdef ENABLE_SP_FDM
alpha = get__alpha();
if (_alpha_bracket && ihook == 1) {
glBegin(GL_LINE_STRIP);
glVertex2f(vel_x - 20, vel_y - (16 - alpha) * _compression);
glVertex2f(vel_x - 17, vel_y - (16 - alpha) * _compression);
glVertex2f(vel_x - 17, vel_y - (14 - alpha) * _compression);
glVertex2f(vel_x - 20, vel_y - (14 - alpha) * _compression);
glEnd();
glBegin(GL_LINE_STRIP);
glVertex2f(vel_x + 20, vel_y - (16 - alpha) * _compression);
glVertex2f(vel_x + 17, vel_y - (16 - alpha) * _compression);
glVertex2f(vel_x + 17, vel_y - (14 - alpha) * _compression);
glVertex2f(vel_x + 20, vel_y - (14 - alpha) * _compression);
glEnd();
}
#endif
//printf("xvr=%f, yvr=%f, Vx=%f, Vy=%f, Vz=%f\n",xvvr, yvvr, Vx, Vy, Vz);
//printf("Ax=%f, Ay=%f, Az=%f\n",Ax, Ay, Az);
//****************************************************************
// OBJECT MOVING RETICLE
// TYPE ENERGY_MARKERS
// ATTRIB - ALWAYS
//energy markers - compute potential slope
float pla = get__throttleval();
float t2 = 0.0;
if (_energy_marker) {
if (total_vel < 5.0) {
// t1 = 0;
t2 = 0;
} else {
// t1 = up_vel / total_vel;
t2 = asin((Vxx * Axx + Vyy * Ayy + Vzz * Azz) / (9.81 * total_vel));
}
pot_slope = ((t2 / 3) * SGD_RADIANS_TO_DEGREES) * _compression + vel_y;
// if (pot_slope < (vel_y - 45)) pot_slope = vel_y - 45;
// if (pot_slope > (vel_y + 45)) pot_slope = vel_y + 45;
//energy markers
glBegin(GL_LINE_STRIP);
glVertex2f(vel_x - 20, pot_slope - 5);
glVertex2f(vel_x - 15, pot_slope);
glVertex2f(vel_x - 20, pot_slope + 5);
glEnd();
glBegin(GL_LINE_STRIP);
glVertex2f(vel_x + 20, pot_slope - 5);
glVertex2f(vel_x + 15, pot_slope);
glVertex2f(vel_x + 20, pot_slope + 5);
glEnd();
if (pla > (105.0 / 131.0)) {
glBegin(GL_LINE_STRIP);
glVertex2f(vel_x - 24, pot_slope - 5);
glVertex2f(vel_x - 19, pot_slope);
glVertex2f(vel_x - 24, pot_slope + 5);
glEnd();
glBegin(GL_LINE_STRIP);
glVertex2f(vel_x + 24, pot_slope - 5);
glVertex2f(vel_x + 19, pot_slope);
glVertex2f(vel_x + 24, pot_slope + 5);
glEnd();
}
}
//**********************************************************
// ramp reticle
// OBJECT STATIC RETICLE
// TYPE LINE
// ATTRIB - ON CONDITION
#ifdef ENABLE_SP_FDM
int ilcanclaw = get__iaux2();
if (_energy_worm && ilcanclaw == 1) {
glBegin(GL_LINE_STRIP);
glVertex2f(-15, -134);
glVertex2f(15, -134);
glEnd();
// OBJECT MOVING RETICLE
// TYPE BOX
// ATTRIB - ON CONDITION
glBegin(GL_LINE_STRIP);
glVertex2f(-6, -134);
glVertex2f(-6, t2 * SGD_RADIANS_TO_DEGREES * 4.0 - 134);
glVertex2f(+6, t2 * SGD_RADIANS_TO_DEGREES * 4.0 - 134);
glVertex2f(6, -134);
glEnd();
// OBJECT MOVING RETICLE
// TYPE DIAMOND
// ATTRIB - ON CONDITION
glBegin(GL_LINE_LOOP);
glVertex2f(-6, actslope * 4.0 - 134);
glVertex2f(0, actslope * 4.0 -134 + 3);
glVertex2f(6, actslope * 4.0 - 134);
glVertex2f(0, actslope * 4.0 -134 -3);
glEnd();
}
#endif
//*************************************************************
// OBJECT MOVING RETICLE
// TYPE DIAMOND
// ATTRIB - ALWAYS
// Draw the locked velocity vector.
if (_climb_dive_marker) {
glBegin(GL_LINE_LOOP);
glVertex2f(-3.0, 0.0 + vel_y);
glVertex2f(0.0, 6.0 + vel_y);
glVertex2f(3.0, 0.0 + vel_y);
glVertex2f(0.0, -6.0 + vel_y);
glEnd();
}
//****************************************************************
_clip_box->set();
if (_dynamic_origin) {
// ladder moves with alpha/beta offset projected onto horizon
// line (so that the horizon line always aligns with the
// actual horizon.
_vmin = pitch_value - _width_units * 0.5f;
_vmax = pitch_value + _width_units * 0.5f;
{
// the hud ladder center point should move relative to alpha/beta
// however the horizon line should always stay on the horizon. We
// project the alpha/beta offset onto the horizon line to get the
// result we want.
SGVec3d d(cos(roll_value), sin(roll_value), 0.0);
SGRayd r(SGVec3d::zeros(), d);
SGVec3d p = r.getClosestPointTo(SGVec3d(vel_x, vel_y, 0.0));
glTranslatef(p[0], p[1], 0);
}
} else {
// ladder position is fixed relative to the center of the screen.
_vmin = pitch_value - _width_units * 0.5f;
_vmax = pitch_value + _width_units * 0.5f;
}
glRotatef(roll_value * SGD_RADIANS_TO_DEGREES, 0.0, 0.0, 1.0);
// FRL marker not rotated - this line shifted below
float half_span = _w * 0.5f;
float y = 0;
struct { float x, y; } lo, li, ri, ro, numoffs; // left/right inner/outer
if (_div_units) {
_locTextList.setFont(_hud->_font_renderer);
_locTextList.erase();
_locLineList.erase();
_locStippleLineList.erase();
for (int i = int(_vmin); i < int(_vmax) + 1; i++) {
if (i % _div_units)
continue;
if (_type == PITCH)
y = float(i - pitch_value) * _compression + .5;
else // _type == CLIMB_DIVE
y = float(i - actslope) * _compression + .5;
// OBJECT LADDER MARK
// TYPE LINE
// ATTRIB - ON CONDITION
// draw approach glide slope marker
#ifdef ENABLE_SP_FDM
if (_glide_slope_marker && ihook) {
draw_line(-half_span + 15, (_glide_slope - actslope) * _compression,
-half_span + hole, (_glide_slope - actslope) * _compression);
draw_line(half_span - 15, (_glide_slope - actslope) * _compression,
half_span - hole, (_glide_slope - actslope) * _compression);
}
#endif
// draw symbols
if (i == 90 && _zenith)
draw_zenith(0.0, y);
else if (i == -90 && _nadir)
draw_nadir(0.0, y);
if ((_zenith && i > 85) || i > 90)
continue;
if ((_nadir && i < -85) || i < -90)
continue;
lo.x = -half_span;
ro.x = half_span;
li.x = ri.x = 0;
lo.y = ro.y = li.y = ri.y = y;
numoffs.x = 4;
numoffs.y = 0;
if (i == 0) {
lo.x -= _zero_bar_overlength;
ro.x += _zero_bar_overlength;
}
if (_scr_hole > 0.0f) {
li.x = -_scr_hole;
ri.x = _scr_hole;
if (_dive_bar_angle && i < 0) {
float alpha = i * SG_DEGREES_TO_RADIANS * 0.5;
float xoffs = (ro.x - ri.x) * cos(alpha);
float yoffs = (ro.x - ri.x) * sin(alpha);
lo.x = li.x - xoffs;
ro.x = ri.x + xoffs;
lo.y = ro.y = li.y + yoffs;
numoffs.x = 0;
numoffs.y = 4 - yoffs * 0.3;
}
}
// draw bars
if (_scr_hole) {
draw_line(li.x, li.y, lo.x, lo.y, i < 0);
draw_line(ri.x, ri.y, ro.x, ro.y, i < 0);
} else {
draw_line(lo.x, lo.y, ro.x, ro.y, i < 0);
}
// draw ticks
if (_tick_length) {
if (i < 0) {
draw_line(li.x, li.y, li.x, li.y + _tick_length);
draw_line(ri.x, ri.y, ri.x, ri.y + _tick_length);
} else if (i > 0 || _zero_bar_overlength == 0) {
if (_tick_length > 0) {
numoffs.x = -0.3;
numoffs.y = -0.3;
draw_line(lo.x, lo.y, lo.x, lo.y - _tick_length);
draw_line(ro.x, ro.y, ro.x, ro.y - _tick_length);
} else {
draw_line(li.x, li.y, li.x, li.y - _tick_length);
draw_line(ri.x, ri.y, ri.x, ri.y - _tick_length);
}
}
}
// draw numbers
std::ostringstream str;
str << i;
// must keep this string, otherwise it will free the c_str!
string num_str = str.str();
const char *num = num_str.c_str();
int valign = numoffs.y > 0 ? BOTTOM : numoffs.y < 0 ? TOP : VCENTER;
draw_text(lo.x - numoffs.x, lo.y + numoffs.y, num,
valign | (numoffs.x == 0 ? CENTER : numoffs.x > 0 ? RIGHT : LEFT));
draw_text(ro.x + numoffs.x, lo.y + numoffs.y, num,
valign | (numoffs.x == 0 ? CENTER : numoffs.x > 0 ? LEFT : RIGHT));
}
_locTextList.draw();
glLineWidth(0.2);
_locLineList.draw();
glEnable(GL_LINE_STIPPLE);
glLineStipple(1, 0x00FF);
_locStippleLineList.draw();
glDisable(GL_LINE_STIPPLE);
}
_clip_box->unset();
glPopMatrix();
//*************************************************************
//*************************************************************
#ifdef ENABLE_SP_FDM
if (_waypoint_marker) {
//waypoint marker computation
float fromwp_lat, towp_lat, fromwp_lon, towp_lon, dist, delx, dely, hyp, theta, brg;
fromwp_lon = get__longitude() * SGD_DEGREES_TO_RADIANS;
fromwp_lat = get__latitude() * SGD_DEGREES_TO_RADIANS;
towp_lon = get__aux2() * SGD_DEGREES_TO_RADIANS;
towp_lat = get__aux1() * SGD_DEGREES_TO_RADIANS;
dist = acos(sin(fromwp_lat) * sin(towp_lat) + cos(fromwp_lat)
* cos(towp_lat) * cos(fabs(fromwp_lon - towp_lon)));
delx= towp_lat - fromwp_lat;
dely = towp_lon - fromwp_lon;
hyp = sqrt(pow(delx, 2) + pow(dely, 2));
if (hyp != 0)
theta = asin(dely / hyp);
else
theta = 0.0;
brg = theta * SGD_RADIANS_TO_DEGREES;
if (brg > 360.0)
brg = 0.0;
if (delx < 0)
brg = 180 - brg;
// {Brg = asin(cos(towp_lat)*sin(fabs(fromwp_lon-towp_lon))/ sin(dist));
// Brg = Brg * SGD_RADIANS_TO_DEGREES; }
dist *= SGD_RADIANS_TO_DEGREES * 60.0 * 1852.0; //rad->deg->nm->m
// end waypoint marker computation
//*********************************************************
// OBJECT MOVING RETICLE
// TYPE ARROW
// waypoint marker
if (fabs(brg - psi) > 10.0) {
glPushMatrix();
glTranslatef(_center_x, _center_y, 0);
glTranslatef(vel_x, vel_y, 0);
glRotatef(brg - psi, 0.0, 0.0, -1.0);
glBegin(GL_LINE_LOOP);
glVertex2f(-2.5, 20.0);
glVertex2f(-2.5, 30.0);
glVertex2f(-5.0, 30.0);
glVertex2f(0.0, 35.0);
glVertex2f(5.0, 30.0);
glVertex2f(2.5, 30.0);
glVertex2f(2.5, 20.0);
glEnd();
glPopMatrix();
}
// waypoint marker on heading scale
if (fabs(brg - psi) < 12.0) {
if (!_hat) {
glBegin(GL_LINE_LOOP);
GLfloat x = (brg - psi) * 60 / 25;
glVertex2f(x + 320, 240.0);
glVertex2f(x + 326, 240.0 - 4);
glVertex2f(x + 323, 240.0 - 4);
glVertex2f(x + 323, 240.0 - 8);
glVertex2f(x + 317, 240.0 - 8);
glVertex2f(x + 317, 240.0 - 4);
glVertex2f(x + 314, 240.0 - 4);
glEnd();
} else { // if (_hat)
float x = (brg - psi) * 60 / 25 + 320, y = 240.0, r = 5.0;
float x1, y1;
glEnable(GL_POINT_SMOOTH);
glBegin(GL_POINTS);
for (int count = 0; count <= 200; count++) {
float temp = count * SG_PI * 3 / (200.0 * 2.0);
float temp1 = temp - (45.0 * SGD_DEGREES_TO_RADIANS);
x1 = x + r * cos(temp1);
y1 = y + r * sin(temp1);
glVertex2f(x1, y1);
}
glEnd();
glDisable(GL_POINT_SMOOTH);
}
} //brg<12
} // if _waypoint_marker
#endif
}//draw
/******************************************************************/
// draws the zenith symbol (highest possible climb angle i.e. 90 degree climb angle)
//
void HUD::Ladder::draw_zenith(float x, float y)
{
draw_line(x - 9.0, y, x - 3.0, y + 1.3);
draw_line(x - 9.0, y, x - 3.0, y - 1.3);
draw_line(x + 9.0, y, x + 3.0, y + 1.3);
draw_line(x + 9.0, y, x + 3.0, y - 1.3);
draw_line(x, y + 9.0, x - 1.3, y + 3.0);
draw_line(x, y + 9.0, x + 1.3, y + 3.0);
draw_line(x - 3.9, y + 3.9, x - 3.0, y + 1.3);
draw_line(x - 3.9, y + 3.9, x - 1.3, y + 3.0);
draw_line(x + 3.9, y + 3.9, x + 1.3, y + 3.0);
draw_line(x + 3.9, y + 3.9, x + 3.0, y + 1.3);
draw_line(x - 3.9, y - 3.9, x - 3.0, y - 1.3);
draw_line(x - 3.9, y - 3.9, x - 1.3, y - 2.6);
draw_line(x + 3.9, y - 3.9, x + 3.0, y - 1.3);
draw_line(x + 3.9, y - 3.9, x + 1.3, y - 2.6);
draw_line(x - 1.3, y - 2.6, x, y - 27.0);
draw_line(x + 1.3, y - 2.6, x, y - 27.0);
}
// draws the nadir symbol (lowest possible dive angle i.e. 90 degree dive angle))
//
void HUD::Ladder::draw_nadir(float x, float y)
{
const float R = 7.5;
draw_circle(x, y, R);
draw_line(x, y + R, x, y + 22.5); // line above the circle
draw_line(x - R, y, x + R, y); // line at middle of circle
float theta = asin(2.5 / R);
float theta1 = asin(5.0 / R);
float x1, y1, x2, y2;
x1 = x + R * cos(theta);
y1 = y + 2.5;
x2 = x + R * cos((180.0 * SGD_DEGREES_TO_RADIANS) - theta);
y2 = y + 2.5;
draw_line(x1, y1, x2, y2);
x1 = x + R * cos(theta1);
y1 = y + 5.0;
x2 = x + R * cos((180.0 * SGD_DEGREES_TO_RADIANS) - theta1);
y2 = y + 5.0;
draw_line(x1, y1, x2, y2);
x1 = x + R * cos((180.0 * SGD_DEGREES_TO_RADIANS) + theta);
y1 = y - 2.5;
x2 = x + R * cos((360.0 * SGD_DEGREES_TO_RADIANS) - theta);
y2 = y - 2.5;
draw_line(x1, y1, x2, y2);
x1 = x + R * cos((180.0 * SGD_DEGREES_TO_RADIANS) + theta1);
y1 = y - 5.0;
x2 = x + R * cos((360.0 * SGD_DEGREES_TO_RADIANS) - theta1);
y2 = y - 5.0;
draw_line(x1, y1, x2, y2);
}

View File

@@ -0,0 +1,132 @@
// HUD_misc.cxx -- HUD miscellaneous elements
//
// Written by Melchior FRANZ, started September 2006.
//
// Copyright (C) 2006 Melchior FRANZ [mfranz#aon:at]
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation; either version 2 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif
#include "HUD.hxx"
#include "HUD_private.hxx"
#include <Main/globals.hxx>
// MIL-STD-1787B aiming reticle
HUD::AimingReticle::AimingReticle(HUD *hud, const SGPropertyNode *n, float x, float y) :
Item(hud, n, x, y),
_active_condition(0),
_tachy_condition(0),
_align_condition(0),
_diameter(n->getNode("diameter-input", false)),
_pitch(n->getNode("pitch-input", false)),
_yaw(n->getNode("yaw-input", false)),
_speed(n->getNode("speed-input", false)),
_range(n->getNode("range-input", false)),
_t0(n->getNode("arc-start-input", false)),
_t1(n->getNode("arc-stop-input", false)),
_offset_x(n->getNode("offset-x-input", false)),
_offset_y(n->getNode("offset-y-input", false)),
_bullet_size(_w / 6.0),
_inner_radius(_w / 2.0),
_compression(n->getFloatValue("compression-factor")),
_limit_x(n->getFloatValue("limit-x")),
_limit_y(n->getFloatValue("limit-y"))
{
const SGPropertyNode *node = n->getNode("active-condition");
if (node)
_active_condition = sgReadCondition(globals->get_props(), node);
const SGPropertyNode *tnode = n->getNode("tachy-condition");
if (tnode)
_tachy_condition = sgReadCondition(globals->get_props(), tnode);
const SGPropertyNode *anode = n->getNode("align-condition");
if (anode)
_align_condition = sgReadCondition(globals->get_props(), anode);
}
void HUD::AimingReticle::draw(void)
{
bool active = _active_condition ? _active_condition->test() : true;
bool tachy = _tachy_condition ? _tachy_condition->test() : false;
bool align = _align_condition ? _align_condition->test() : false;
float diameter = _diameter.isValid() ? _diameter.getFloatValue() : 2.0f; // outer circle
float x = _center_x + (_offset_x.isValid() ? _offset_x.getFloatValue() : 0);
float y = _center_y + (_offset_y.isValid() ? _offset_y.getFloatValue() : 0);
if (active) { // stadiametric (4.2.4.4)
draw_bullet(x, y, _bullet_size);
draw_circle(x, y, _inner_radius);
draw_circle(x, y, diameter * _inner_radius);
} else if (tachy){//tachiametric
float t0 = _t0.isValid() ? _t0.getFloatValue() : 2.0f; // start arc
float t1 = _t1.isValid() ? _t1.getFloatValue() : 2.0f; // stop arc
float yaw_value = _yaw.getFloatValue();
float pitch_value = _pitch.getFloatValue();
float tof_value = _range.getFloatValue()* 3 / _speed.getFloatValue();
draw_bullet(x, y, _bullet_size);
draw_circle(x, y, _inner_radius);
draw_line(x + _inner_radius, y, x + _inner_radius * 3, y);
draw_line(x - _inner_radius, y, x - _inner_radius * 3, y);
draw_line(x, y + _inner_radius, x, y + _inner_radius * 3);
draw_line(x, y - _inner_radius, x, y - _inner_radius * 3);
if(align){
draw_line(x + _limit_x, y + _limit_y, x - _limit_x, y + _limit_y);
draw_line(x + _limit_x, y - _limit_y, x - _limit_x, y - _limit_y);
draw_line(x + _limit_x, y + _limit_y, x + _limit_x, y - _limit_y);
draw_line(x - _limit_x, y + _limit_y, x - _limit_x, y - _limit_y);
}
float limit_offset = diameter * _inner_radius;
float pos_x = x + (yaw_value * tof_value)
* _compression;
pos_x > x + _limit_x - limit_offset ?
pos_x = x + _limit_x - limit_offset : pos_x;
pos_x < x - _limit_x + limit_offset ?
pos_x = x - _limit_x + limit_offset: pos_x;
float pos_y = y + (pitch_value * tof_value)
* _compression;
pos_y > y + _limit_y - limit_offset ?
pos_y = y + _limit_y - limit_offset : pos_y;
pos_y < y - _limit_y + limit_offset?
pos_y = y - _limit_y + limit_offset: pos_y;
draw_circle(pos_x, pos_y, diameter * _inner_radius);
draw_arc(x, y, t0, t1, (diameter + 2) * _inner_radius );
} else { // standby (4.2.4.5)
// TODO
}
}

View File

@@ -0,0 +1,427 @@
// HUD_private.hxx -- Intenral delcerations for the HUD
//
// Written by Michele America, started September 1997.
//
// Copyright (C) 1997 Michele F. America [micheleamerica#geocities:com]
// Copyright (C) 2006 Melchior FRANZ [mfranz#aon:at]
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation; either version 2 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#ifndef _HUD_PRIVATE_HXX
#define _HUD_PRIVATE_HXX
#include <simgear/compiler.h>
#include <simgear/props/condition.hxx>
#include <vector>
#include <deque>
#include <cassert>
#include <osg/State>
#include <simgear/math/SGLimits.hxx>
#include <simgear/constants.h>
#include <simgear/props/props.hxx>
#include <plib/sg.h> // for lingering sgdVec3 usage below
class FGFontCache;
class fntRenderer;
class fntTexFont;
class FGViewer;
class FGRunway;
class ClipBox {
public:
ClipBox(const SGPropertyNode *, float xoffset = 0, float yoffset = 0);
void set();
void unset();
private:
bool _active;
float _xoffs, _yoffs;
SGConstPropertyNode_ptr _top_node;
SGConstPropertyNode_ptr _bot_node;
SGConstPropertyNode_ptr _left_node;
SGConstPropertyNode_ptr _right_node;
GLdouble _top[4];
GLdouble _bot[4];
GLdouble _left[4];
GLdouble _right[4];
};
class HUD::Input {
public:
Input(const SGPropertyNode *n, float factor = 1.0, float offset = 0.0,
float min = -SGLimitsf::max(), float max = SGLimitsf::max());
bool getBoolValue() const {
assert(_property);
return _property->getBoolValue();
}
std::string getStringValue() const {
assert(_property);
return _property->getStringValue();
}
float getFloatValue() {
assert(_property);
float f = _property->getFloatValue() * _factor + _offset;
if (_damped == SGLimitsf::max())
_damped = f;
if (_coeff > 0.0f)
f = _damped = f * (1.0f - _coeff) + _damped * _coeff;
return clamp(f);
}
inline float isValid() const { return _valid; }
inline float min() const { return _min; }
inline float max() const { return _max; }
inline float factor() const { return _factor; }
float clamp(float v) const { return v < _min ? _min : v > _max ? _max : v; }
void set_min(float m, bool force = true) {
if (force || _min == -SGLimitsf::max())
_min = m;
}
void set_max(float m, bool force = true) {
if (force || _max == SGLimitsf::max())
_max = m;
}
private:
bool _valid;
SGConstPropertyNode_ptr _property;
float _factor;
float _offset;
float _min;
float _max;
float _coeff;
float _damped;
};
class HUD::Item {
public:
Item(HUD *parent, const SGPropertyNode *, float x = 0.0f, float y = 0.0f);
virtual ~Item () {}
virtual void draw() = 0;
virtual bool isEnabled();
protected:
enum Format {
INVALID,
NONE,
INT,
LONG,
FLOAT,
DOUBLE,
STRING,
};
Format check_format(const char *) const;
inline float get_span() const { return _scr_span; }
inline int get_digits() const { return _digits; }
inline bool option_vert() const { return (_options & VERTICAL) == VERTICAL; }
inline bool option_left() const { return (_options & LEFT) == LEFT; }
inline bool option_right() const { return (_options & RIGHT) == RIGHT; }
inline bool option_both() const { return (_options & BOTH) == BOTH; }
inline bool option_noticks() const { return (_options & NOTICKS) == NOTICKS; }
inline bool option_notext() const { return (_options & NOTEXT) == NOTEXT; }
inline bool option_top() const { return (_options & TOP) == TOP; }
inline bool option_bottom() const { return (_options & BOTTOM) == BOTTOM; }
void draw_line(float x1, float y1, float x2, float y2);
void draw_stipple_line(float x1, float y1, float x2, float y2);
void draw_text(float x, float y, const char *msg, int align = 0, int digit = 0);
void draw_circle(float x1, float y1, float r) const;
void draw_arc(float x1, float y1, float t0, float t1, float r) const;
void draw_bullet(float, float, float);
HUD *_hud;
std::string _name;
int _options;
float _x, _y, _w, _h;
float _center_x, _center_y;
private:
SGSharedPtr<SGCondition> _condition;
float _scr_span; // Working values for draw;
int _digits;
};
class HUD::Label : public Item {
public:
Label(HUD *parent, const SGPropertyNode *, float x, float y);
virtual void draw();
private:
bool blink();
Input _input;
Format _mode;
std::string _format;
int _halign; // HUDText alignment
bool _box;
float _pointer_width;
float _pointer_length;
SGSharedPtr<SGCondition> _blink_condition;
double _blink_interval;
double _blink_target; // time for next blink state change
bool _blink_state;
};
// abstract base class for both moving scale and moving needle (fixed scale)
// indicators.
//
class HUD::Scale : public Item {
public:
Scale(HUD *parent, const SGPropertyNode *, float x, float y);
virtual void draw ( void ) {} // No-op here. Defined in derived classes.
protected:
inline float factor() const { return _display_factor; }
inline float range_to_show() const { return _range_shown; }
Input _input;
float _major_divs; // major division marker units
float _minor_divs; // minor division marker units
unsigned int _modulo; // Roll over point
private:
float _range_shown; // Width Units.
float _display_factor; // factor => screen units/range values.
};
class HUD::Gauge : public Scale {
public:
Gauge(HUD *parent, const SGPropertyNode *, float x, float y);
virtual void draw();
};
// displays the indicated quantity on a scale that moves past the
// pointer. It may be horizontal or vertical.
//
class HUD::Tape : public Scale {
public:
Tape(HUD *parent, const SGPropertyNode *, float x, float y);
virtual void draw();
protected:
void draw_vertical(float);
void draw_horizontal(float);
void draw_fixed_pointer(float, float, float, float, float, float);
char *format_value(float);
private:
float _val_span;
float _half_width_units;
bool _draw_tick_bottom;
bool _draw_tick_top;
bool _draw_tick_right;
bool _draw_tick_left;
bool _draw_cap_bottom;
bool _draw_cap_top;
bool _draw_cap_right;
bool _draw_cap_left;
float _marker_offset;
float _label_offset;
float _label_gap;
bool _pointer;
Format _label_fmt;
std::string _format;
int _div_ratio; // _major_divs/_minor_divs
bool _odd_type; // whether to put numbers at 0/2/4 or 1/3/5
enum { BUFSIZE = 64 };
char _buf[BUFSIZE];
enum PointerType { FIXED, MOVING } _pointer_type;
enum TickType { LINE, CIRCLE } _tick_type;
enum TickLength { VARIABLE, CONSTANT } _tick_length;
};
class HUD::Dial : public Scale {
public:
Dial(HUD *parent, const SGPropertyNode *, float x, float y);
virtual void draw();
private:
float _radius;
int _divisions;
};
class HUD::TurnBankIndicator : public Item {
public:
TurnBankIndicator(HUD *parent, const SGPropertyNode *, float x, float y);
virtual void draw();
private:
void draw_scale();
void draw_tee();
void draw_line(float, float, float, float);
void draw_tick(float angle, float r1, float r2, int side);
Input _bank;
Input _sideslip;
float _gap_width;
bool _bank_scale;
};
class HUD::Ladder : public Item {
public:
Ladder(HUD *parent, const SGPropertyNode *, float x, float y);
~Ladder();
virtual void draw();
private:
void draw_zenith(float, float);
void draw_nadir(float, float);
void draw_text(float x, float y, const char *s, int align = 0) {
_locTextList.add(x, y, s, align, 0);
}
void draw_line(float x1, float y1, float x2, float y2, bool stipple = false) {
if (stipple)
_locStippleLineList.add(LineSegment(x1, y1, x2, y2));
else
_locLineList.add(LineSegment(x1, y1, x2, y2));
}
enum Type { PITCH, CLIMB_DIVE } _type;
Input _pitch;
Input _roll;
float _width_units;
int _div_units;
float _scr_hole;
float _zero_bar_overlength;
bool _dive_bar_angle;
float _tick_length;
float _vmax;
float _vmin;
float _compression;
bool _dynamic_origin;
bool _frl; // fuselage reference line
bool _target_spot;
bool _target_markers;
bool _velocity_vector;
bool _ground_velocity_vector;
bool _drift_marker;
bool _alpha_bracket;
bool _energy_marker;
bool _climb_dive_marker;
bool _glide_slope_marker;
float _glide_slope;
bool _energy_worm;
bool _waypoint_marker;
bool _zenith;
bool _nadir;
bool _hat;
ClipBox *_clip_box;
// The Ladder has its own temporary display lists
TextList _locTextList;
LineList _locLineList;
LineList _locStippleLineList;
};
// responsible for rendering the active runway in the hud (if visible).
//
class HUD::Runway : public Item {
public:
Runway(HUD *parent, const SGPropertyNode *, float x, float y);
virtual void draw();
private:
void boundPoint(const sgdVec3& v, sgdVec3& m);
bool boundOutsidePoints(sgdVec3& v, sgdVec3& m);
bool drawLine(const sgdVec3& a1, const sgdVec3& a2, const sgdVec3& p1, const sgdVec3& p2);
void drawArrow();
FGRunway* get_active_runway();
void get_rwy_points(sgdVec3 *points);
void setLineWidth();
SGPropertyNode_ptr _agl;
sgdVec3 _points3d[6], _points2d[6];
double _mm[16];
double _pm[16];
double _arrow_scale; // scales of runway indication arrow
double _arrow_radius;
double _line_scale; // maximum line scale
double _scale_dist; // distance where to start scaling the lines
double _default_pitch;
double _default_heading;
GLint _view[4];
FGRunway* _runway;
unsigned short _stipple_out; // stipple pattern of the outline of the runway
unsigned short _stipple_center; // stipple pattern of the center line of the runway
bool _draw_arrow; // draw arrow when runway is not visible in HUD
bool _draw_arrow_always; // always draws arrow
float _left, _right, _top, _bottom;
};
class HUD::AimingReticle : public Item {
public:
AimingReticle(HUD *parent, const SGPropertyNode *, float x, float y);
virtual void draw();
private:
SGSharedPtr<SGCondition> _active_condition; // stadiametric (true) or standby (false)
SGSharedPtr<SGCondition> _tachy_condition; // tachymetric (true) or standby (false)
SGSharedPtr<SGCondition> _align_condition; // tachymetric (true) or standby (false)
Input _diameter; // inner/outer radius relation
Input _pitch;
Input _yaw;
Input _speed;
Input _range;
Input _t0;
Input _t1;
Input _offset_x;
Input _offset_y;
float _bullet_size;
float _inner_radius;
float _compression;
float _limit_x;
float _limit_y;
};
#endif // _HUD_HXX

View File

@@ -0,0 +1,422 @@
// HUD_runway.cxx -- An instrument that renders a virtual runway on the HUD
//
// Written by Aaron Wilson & Phillip Merritt, Nov 2004.
//
// Copyright (C) 2004 Aaron Wilson, Aaron.I.Wilson@nasa.gov
// Copyright (C) 2004 Phillip Merritt, Phillip.M.Merritt@nasa.gov
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation; either version 2 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif
#include <simgear/compiler.h>
#include <simgear/math/sg_geodesy.hxx>
#include <simgear/scene/util/project.hxx>
#include <Main/globals.hxx>
#include <Main/fg_props.hxx>
#include <Scenery/scenery.hxx>
#include <Aircraft/controls.hxx>
#include <FDM/flight.hxx>
#include <Environment/environment.hxx>
#include <Environment/environment_mgr.hxx>
#include <Viewer/view.hxx>
#include <Viewer/viewmgr.hxx>
#include <Airports/airport.hxx>
#include "HUD.hxx"
#include "HUD_private.hxx"
HUD::Runway::Runway(HUD *hud, const SGPropertyNode *node, float x, float y) :
Item(hud, node, x, y),
_agl(fgGetNode("/position/altitude-agl-ft", true)),
_arrow_scale(node->getDoubleValue("arrow-scale", 1.0)),
_arrow_radius(node->getDoubleValue("arrow-radius")),
_line_scale(node->getDoubleValue("line-scale", 1.0)),
_scale_dist(node->getDoubleValue("scale-dist-nm")),
_default_pitch(fgGetDouble("/sim/view[0]/config/pitch-pitch-deg", 0.0)),
_default_heading(fgGetDouble("/sim/view[0]/config/pitch-heading-deg", 0.0)),
_stipple_out(node->getIntValue("outer_stipple", 0xFFFF)),
_stipple_center(node->getIntValue("center-stipple", 0xFFFF)),
_draw_arrow(_arrow_scale > 0 ? true : false),
_draw_arrow_always(_arrow_scale > 0 ? node->getBoolValue("arrow-always") : false)
{
_view[0] = 0;
_view[1] = 0;
_view[2] = 640;
_view[3] = 480;
_center_x = _view[2] / 2;
_center_y = _view[3] / 2;
_left = _center_x - (_w / 2) + _x;
_right = _center_x + (_w / 2) + _x;
_bottom = _center_y - (_h / 2) + _y;
_top = _center_y + (_h / 2) + _y;
}
void HUD::Runway::draw()
{
_runway = get_active_runway();
if (!_runway)
return;
glPushAttrib(GL_LINE_STIPPLE | GL_LINE_STIPPLE_PATTERN | GL_LINE_WIDTH);
float projMat[4][4]={{0,0,0,0},{0,0,0,0},{0,0,0,0},{0,0,0,0}};
float modelView[4][4];
bool anyLines;
//Get the current view
// FGViewer* curr_view = globals->get_viewmgr()->get_current_view();
// int curr_view_id = globals->get_viewmgr()->get_current();
// double gpo = curr_view->getGoalPitchOffset_deg();
// double gho = curr_view->getGoalHeadingOffset_deg();
// double po = curr_view->getPitchOffset_deg();
// double ho = curr_view->getHeadingOffset_deg();
flightgear::View* cockpitView = globals->get_viewmgr()->get_view(0);
double yaw = -(cockpitView->getHeadingOffset_deg() - _default_heading) * SG_DEGREES_TO_RADIANS;
double pitch = (cockpitView->getPitchOffset_deg() - _default_pitch) * SG_DEGREES_TO_RADIANS;
//double roll = fgGetDouble("/sim/view[0]/config/roll-offset-deg",0.0) //TODO: adjust for default roll offset
double sPitch = sin(pitch), cPitch = cos(pitch),
sYaw = sin(yaw), cYaw = cos(yaw);
//Set the camera to the cockpit view to get the view of the runway from the cockpit
// OSGFIXME
// ssgSetCamera((sgVec4 *)_cockpit_view->get_VIEW());
get_rwy_points(_points3d);
//Get the current project matrix
// OSGFIXME
// ssgGetProjectionMatrix(projMat);
// const sgVec4 *viewMat = globals->get_current_view()->get_VIEW();
//Get the current model view matrix (cockpit view)
// OSGFIXME
// ssgGetModelviewMatrix(modelView);
//Create a rotation matrix to correct for any offsets (other than default offsets) to the model view matrix
sgMat4 xy; //rotation about the Rxy, negate the sin's on Ry
xy[0][0] = cYaw, xy[1][0] = 0.0f, xy[2][0] = -sYaw, xy[3][0] = 0.0f;
xy[0][1] = sPitch*-sYaw, xy[1][1] = cPitch, xy[2][1] = -sPitch*cYaw, xy[3][1] = 0.0f;
xy[0][2] = cPitch*sYaw, xy[1][2] = sPitch, xy[2][2] = cPitch*cYaw, xy[3][2] = 0.0f;
xy[0][3] = 0.0f, xy[1][3] = 0.0f, xy[2][3] = 0.0f, xy[3][3] = 1.0f;
//Re-center the model view
sgPostMultMat4(modelView,xy);
//copy float matrices to double
for (int i = 0; i < 4; i++) {
for (int j = 0; j < 4; j++) {
int idx = (i * 4) + j;
_mm[idx] = (double)modelView[i][j];
_pm[idx] = (double)projMat[i][j];
}
}
//Calculate the 2D points via gluProject
// int result = GL_TRUE;
for (int i = 0; i < 6; i++) {
/*result = */simgear::project(_points3d[i][0], _points3d[i][1], _points3d[i][2],
_mm, _pm, _view,
&_points2d[i][0], &_points2d[i][1], &_points2d[i][2]);
}
//set the line width based on our distance from the runway
setLineWidth();
//Draw the runway lines on the HUD
glEnable(GL_LINE_STIPPLE);
glLineStipple(1, _stipple_out);
anyLines =
drawLine(_points3d[0], _points3d[1], _points2d[0], _points2d[1]) | //draw top
drawLine(_points3d[2], _points3d[1], _points2d[2], _points2d[1]) | //draw right
drawLine(_points3d[2], _points3d[3], _points2d[2], _points2d[3]) | //draw bottom
drawLine(_points3d[3], _points3d[0], _points2d[3], _points2d[0]); //draw left
glLineStipple(1, _stipple_center);
anyLines |= drawLine(_points3d[5], _points3d[4], _points2d[5], _points2d[4]); //draw center
//Check to see if arrow needs drawn
if ((!anyLines && _draw_arrow) || _draw_arrow_always) {
drawArrow(); //draw indication arrow
}
//Set the camera back to the current view
// OSGFIXME
// ssgSetCamera((sgVec4 *)curr_view);
glPopAttrib();
}
FGRunway* HUD::Runway::get_active_runway()
{
const FGAirport* apt = fgFindAirportID(fgGetString("/sim/presets/airport-id"));
if (!apt) return NULL;
return apt->getActiveRunwayForUsage();
}
void HUD::Runway::get_rwy_points(sgdVec3 *_points3d)
{
double alt = _runway->geod().getElevationM();
double length = _runway->lengthM() * 0.5;
double width = _runway->widthM() * 0.5;
double frontLat = 0.0, frontLon = 0.0, backLat = 0.0, backLon = 0.0, az = 0.0, tempLat = 0.0, tempLon = 0.0;
geo_direct_wgs_84(alt, _runway->latitude(), _runway->longitude(), _runway->headingDeg(), length, &backLat, &backLon, &az);
sgGeodToCart(backLat * SG_DEGREES_TO_RADIANS, backLon * SG_DEGREES_TO_RADIANS, alt, _points3d[4]);
geo_direct_wgs_84(alt, _runway->latitude(), _runway->longitude(), _runway->headingDeg() + 180, length, &frontLat, &frontLon, &az);
sgGeodToCart(frontLat * SG_DEGREES_TO_RADIANS, frontLon * SG_DEGREES_TO_RADIANS, alt, _points3d[5]);
geo_direct_wgs_84(alt, backLat, backLon, _runway->headingDeg() + 90, width, &tempLat, &tempLon, &az);
sgGeodToCart(tempLat * SG_DEGREES_TO_RADIANS, tempLon * SG_DEGREES_TO_RADIANS, alt, _points3d[0]);
geo_direct_wgs_84(alt, backLat, backLon, _runway->headingDeg() - 90, width, &tempLat, &tempLon, &az);
sgGeodToCart(tempLat * SG_DEGREES_TO_RADIANS, tempLon * SG_DEGREES_TO_RADIANS, alt, _points3d[1]);
geo_direct_wgs_84(alt, frontLat, frontLon, _runway->headingDeg() - 90, width, &tempLat, &tempLon, &az);
sgGeodToCart(tempLat * SG_DEGREES_TO_RADIANS, tempLon * SG_DEGREES_TO_RADIANS, alt, _points3d[2]);
geo_direct_wgs_84(alt, frontLat, frontLon, _runway->headingDeg() + 90, width, &tempLat, &tempLon, &az);
sgGeodToCart(tempLat * SG_DEGREES_TO_RADIANS, tempLon * SG_DEGREES_TO_RADIANS, alt, _points3d[3]);
}
bool HUD::Runway::drawLine(const sgdVec3& a1, const sgdVec3& a2, const sgdVec3& point1, const sgdVec3& point2)
{
sgdVec3 p1, p2;
sgdCopyVec3(p1, point1);
sgdCopyVec3(p2, point2);
bool p1Inside = (p1[0] >= _left && p1[0] <= _right && p1[1] >= _bottom && p1[1] <= _top);
bool p1Insight = (p1[2] >= 0.0 && p1[2] < 1.0);
bool p1Valid = p1Insight && p1Inside;
bool p2Inside = (p2[0] >= _left && p2[0] <= _right && p2[1] >= _bottom && p2[1] <= _top);
bool p2Insight = (p2[2] >= 0.0 && p2[2] < 1.0);
bool p2Valid = p2Insight && p2Inside;
if (p1Valid && p2Valid) { //Both project points are valid, draw the line
glBegin(GL_LINES);
glVertex2d(p1[0],p1[1]);
glVertex2d(p2[0],p2[1]);
glEnd();
} else if (p1Valid) { //p1 is valid and p2 is not, calculate a new valid point
sgdVec3 vec = {a2[0] - a1[0], a2[1] - a1[1], a2[2] - a1[2]};
//create the unit vector
sgdScaleVec3(vec, 1.0 / sgdLengthVec3(vec));
sgdVec3 newPt;
sgdCopyVec3(newPt, a1);
sgdAddVec3(newPt, vec);
if (simgear::project(newPt[0], newPt[1], newPt[2], _mm, _pm, _view,
&p2[0], &p2[1], &p2[2])
&& (p2[2] > 0 && p2[2] < 1.0)) {
boundPoint(p1, p2);
glBegin(GL_LINES);
glVertex2d(p1[0], p1[1]);
glVertex2d(p2[0], p2[1]);
glEnd();
}
} else if (p2Valid) { //p2 is valid and p1 is not, calculate a new valid point
sgdVec3 vec = {a1[0] - a2[0], a1[1] - a2[1], a1[2] - a2[2]};
//create the unit vector
sgdScaleVec3(vec, 1.0 / sgdLengthVec3(vec));
sgdVec3 newPt;
sgdCopyVec3(newPt, a2);
sgdAddVec3(newPt, vec);
if (simgear::project(newPt[0], newPt[1], newPt[2], _mm, _pm, _view,
&p1[0], &p1[1], &p1[2])
&& (p1[2] > 0 && p1[2] < 1.0)) {
boundPoint(p2, p1);
glBegin(GL_LINES);
glVertex2d(p2[0], p2[1]);
glVertex2d(p1[0], p1[1]);
glEnd();
}
} else if (p1Insight && p2Insight) { //both points are insight, but not inside
bool v = boundOutsidePoints(p1, p2);
if (v) {
glBegin(GL_LINES);
glVertex2d(p1[0], p1[1]);
glVertex2d(p2[0], p2[1]);
glEnd();
}
return v;
}
//else both points are not insight, don't draw anything
return (p1Valid && p2Valid);
}
void HUD::Runway::boundPoint(const sgdVec3& v, sgdVec3& m)
{
double y = v[1];
if (m[1] < v[1])
y = _bottom;
else if (m[1] > v[1])
y = _top;
if (m[0] == v[0]) {
m[1] = y;
return; //prevent divide by zero
}
double slope = (m[1] - v[1]) / (m[0] - v[0]);
m[0] = (y - v[1]) / slope + v[0];
m[1] = y;
if (m[0] < _left) {
m[0] = _left;
m[1] = slope * (_left - v[0]) + v[1];
} else if (m[0] > _right) {
m[0] = _right;
m[1] = slope * (_right - v[0]) + v[1];
}
}
bool HUD::Runway::boundOutsidePoints(sgdVec3& v, sgdVec3& m)
{
bool pointsInvalid = (v[1] > _top && m[1] > _top) ||
(v[1] < _bottom && m[1] < _bottom) ||
(v[0] > _right && m[0] > _right) ||
(v[0] < _left && m[0] < _left);
if (pointsInvalid)
return false;
if (m[0] == v[0]) {//x's are equal, vertical line
if (m[1] > v[1]) {
m[1] = _top;
v[1] = _bottom;
} else {
v[1] = _top;
m[1] = _bottom;
}
return true;
}
if (m[1] == v[1]) { //y's are equal, horizontal line
if (m[0] > v[0]) {
m[0] = _right;
v[0] = _left;
} else {
v[0] = _right;
m[0] = _left;
}
return true;
}
double slope = (m[1] - v[1]) / (m[0] - v[0]);
double b = v[1] - (slope * v[0]);
double y1 = slope * _left + b;
double y2 = slope * _right + b;
double x1 = (_bottom - b) / slope;
double x2 = (_top - b) / slope;
int counter = 0;
if (y1 >= _bottom && y1 <= _top) {
v[0] = _left;
v[1] = y1;
counter++;
}
if (y2 >= _bottom && y2 <= _top) {
if (counter > 0) {
m[0] = _right;
m[1] = y2;
} else {
v[0] = _right;
v[1] = y2;
}
counter++;
}
if (x1 >= _left && x1 <= _right) {
if (counter > 0) {
m[0] = x1;
m[1] = _bottom;
} else {
v[0] = x1;
v[1] = _bottom;
}
counter++;
}
if (x2 >= _left && x2 <= _right) {
m[0] = x1;
m[1] = _bottom;
counter++;
}
return (counter == 2);
}
void HUD::Runway::drawArrow()
{
SGGeod acPos(SGGeod::fromDeg(
fgGetDouble("/position/longitude-deg"),
fgGetDouble("/position/latitude-deg")));
float theta = SGGeodesy::courseDeg(acPos, _runway->geod());
theta -= fgGetDouble("/orientation/heading-deg");
theta = -theta;
glMatrixMode(GL_MODELVIEW);
glPushMatrix();
glTranslated((_right + _left) / 2.0, (_top + _bottom) / 2.0, 0.0);
glRotated(theta, 0.0, 0.0, 1.0);
glTranslated(0.0, _arrow_radius, 0.0);
glScaled(_arrow_scale, _arrow_scale, 0.0);
glBegin(GL_TRIANGLES);
glVertex2d(-5.0, 12.5);
glVertex2d(0.0, 25.0);
glVertex2d(5.0, 12.5);
glEnd();
glBegin(GL_QUADS);
glVertex2d(-2.5, 0.0);
glVertex2d(-2.5, 12.5);
glVertex2d(2.5, 12.5);
glVertex2d(2.5, 0.0);
glEnd();
glPopMatrix();
}
void HUD::Runway::setLineWidth()
{
//Calculate the distance from the runway, A
SGGeod acPos(SGGeod::fromDeg(
fgGetDouble("/position/longitude-deg"),
fgGetDouble("/position/latitude-deg")));
double distance = SGGeodesy::distanceNm(acPos, _runway->geod());
//Get altitude above runway, B
double alt_nm = _agl->getDoubleValue();
if (_hud->getUnits() == FEET)
alt_nm *= SG_FEET_TO_METER;
alt_nm *= SG_METER_TO_NM;
//Calculate distance away from runway, C = v(A≤+B≤)
distance = sqrt(alt_nm * alt_nm + distance*distance);
if (distance < _scale_dist)
glLineWidth(1.0 + ((_line_scale - 1) * ((_scale_dist - distance) / _scale_dist)));
else
glLineWidth(1.0);
}

View File

@@ -0,0 +1,43 @@
// HUD_scale.cxx -- HUD Common Scale Base (inherited from Gauge/Tape/Dial)
//
// Written by Michele America, started September 1997.
//
// Copyright (C) 1997 Michele F. America [micheleamerica#geocities:com]
// Copyright (C) 2006 Melchior FRANZ [mfranz#aon:at]
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation; either version 2 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#include "HUD.hxx"
#include "HUD_private.hxx"
HUD::Scale::Scale( HUD *hud, const SGPropertyNode *n, float x, float y) :
Item(hud, n, x, y),
_input(n->getNode("input", false)),
_major_divs(n->getFloatValue("major-divisions")),
_minor_divs(n->getFloatValue("minor-divisions")),
_modulo(n->getIntValue("modulo"))
{
if (n->hasValue("display-span"))
_range_shown = n->getFloatValue("display-span");
else
_range_shown = _input.max() - _input.min();
_display_factor = get_span() / _range_shown;
if (_range_shown < 0)
_range_shown = -_range_shown;
}

View File

@@ -0,0 +1,561 @@
// HUD_tape.cxx -- HUD Tape Instrument
//
// Written by Michele America, started September 1997.
//
// Copyright (C) 1997 Michele F. America [micheleamerica#geocities:com]
// Copyright (C) 2006 Melchior FRANZ [mfranz#aon:at]
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation; either version 2 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#include "HUD.hxx"
#include "HUD_private.hxx"
static const float TICK_OFFSET = 2.f;
HUD::Tape::Tape(HUD *hud, const SGPropertyNode *n, float x, float y) :
Scale(hud, n, x, y),
_draw_tick_bottom(n->getBoolValue("tick-bottom", false)),
_draw_tick_top(n->getBoolValue("tick-top", false)),
_draw_tick_right(n->getBoolValue("tick-right", false)),
_draw_tick_left(n->getBoolValue("tick-left", false)),
_draw_cap_bottom(n->getBoolValue("cap-bottom", false)),
_draw_cap_top(n->getBoolValue("cap-top", false)),
_draw_cap_right(n->getBoolValue("cap-right", false)),
_draw_cap_left(n->getBoolValue("cap-left", false)),
_marker_offset(n->getFloatValue("marker-offset")),
_label_offset(n->getFloatValue("label-offset", 3.0)),
_label_gap(n->getFloatValue("label-gap-width") / 2.0),
_pointer(n->getBoolValue("enable-pointer", true)),
_format(n->getStringValue("format", "%d"))
{
_half_width_units = range_to_show() / 2.0;
string s = n->getStringValue("pointer-type");
_pointer_type = s != "moving" ? FIXED : MOVING; // "fixed", "moving"
s = n->getStringValue("tick-type");
_tick_type = s != "bullet" ? LINE : CIRCLE; // "bullet", "line"
s = n->getStringValue("tick-length"); // "variable", "constant"
_tick_length = s != "constant" ? VARIABLE : CONSTANT;
_label_fmt = check_format(_format.c_str());
if (_label_fmt != INT && _label_fmt != LONG
&& _label_fmt != FLOAT && _label_fmt != DOUBLE) {
SG_LOG(SG_INPUT, SG_ALERT, "HUD: invalid <format> '" << _format.c_str()
<< "' in <tape> '" << _name << "' (must be number format)");
_label_fmt = INT;
_format = "%d";
}
if (_minor_divs != 0.0f)
_div_ratio = int(_major_divs / _minor_divs + 0.5f);
else
_div_ratio = 0, _minor_divs = _major_divs;
// int k; //odd or even values for ticks // FIXME odd scale
_odd_type = false;
if (_input.max() + .5f < float(SGLimits<long>::max()))
_odd_type = long(floorf(_input.max() + 0.5f)) & 1 ? true : false;
}
void HUD::Tape::draw(void) // (HUD_scale * pscale)
{
if (!_input.isValid())
return;
float value = _input.getFloatValue();
if (option_vert())
draw_vertical(value);
else
draw_horizontal(value);
}
void HUD::Tape::draw_vertical(float value)
{
float vmin = 0.0;//, vmax = 0.0;
float marker_xs;
float marker_xe;
float marker_ye;
float text_y = 0.0;
float top = _y + _h;
float right = _x + _w;
if (!_pointer) {
vmin = value - _half_width_units; // width units == needle travel
// vmax = value + _half_width_units; // or picture unit span.
text_y = _center_y;
} else if (_pointer_type == MOVING) {
vmin = _input.min();
// vmax = _input.max();
} else { // FIXED
vmin = value - _half_width_units; // width units == needle travel
// vmax = value + _half_width_units; // or picture unit span.
text_y = _center_y;
}
// Bottom tick bar
if (_draw_tick_bottom)
draw_line(_x, _y, right, _y);
// Top tick bar
if (_draw_tick_top)
draw_line(_x, top, right, top);
marker_xs = _x; // x start
marker_xe = right; // x extent
marker_ye = top;
// We do not use else in the following so that combining the
// two options produces a "caged" display with double
// carrots. The same is done for horizontal card indicators.
// draw capping lines and pointers
if (option_left()) { // Calculate x marker offset
if (_draw_cap_right)
draw_line(marker_xe, _y, marker_xe, marker_ye);
marker_xs = marker_xe - _w / 3.0;
// draw_line(marker_xs, _center_y, marker_xe, _center_y + _w / 6);
// draw_line(marker_xs, _center_y, marker_xe, _center_y - _w / 6);
if (_pointer) {
if (_pointer_type == MOVING) {
float ycentre, ypoint, xpoint;
float range, right;
if (_input.min() >= 0.0)
ycentre = _y;
else if (_input.max() + _input.min() == 0.0)
ycentre = _center_y;
else if (_odd_type)
ycentre = _y + (1.0 - _input.min()) * _h / (_input.max() - _input.min());
else
ycentre = _y + _input.min() * _h / (_input.max() - _input.min());
range = _h;
right = _x + _w;
if (_odd_type)
ypoint = ycentre + ((value - 1.0) * range / _val_span);
else
ypoint = ycentre + (value * range / _val_span);
xpoint = right + _marker_offset;
draw_line(xpoint, ycentre, xpoint, ypoint);
draw_line(xpoint, ypoint, xpoint - _marker_offset, ypoint);
draw_line(xpoint - _marker_offset, ypoint, xpoint - 5.0, ypoint + 5.0);
draw_line(xpoint - _marker_offset, ypoint, xpoint - 5.0, ypoint - 5.0);
} else { // FIXED
draw_fixed_pointer(_marker_offset + marker_xe, text_y + _w / 6,
_marker_offset + marker_xs, text_y, _marker_offset + marker_xe,
text_y - _w / 6);
}
}
} // if (option_left())
// draw capping lines and pointers
if (option_right()) {
if (_draw_cap_left)
draw_line(_x, _y, _x, marker_ye);
marker_xe = _x + _w / 3.0;
// Indicator carrot
// draw_line(_x, _center_y + _w / 6, marker_xe, _center_y);
// draw_line(_x, _center_y - _w / 6, marker_xe, _center_y);
if (_pointer) {
if (_pointer_type == MOVING) {
float ycentre, ypoint, xpoint;
float range;
if (_input.min() >= 0.0)
ycentre = _y;
else if (_input.max() + _input.min() == 0.0)
ycentre = _center_y;
else if (_odd_type)
ycentre = _y + (1.0 - _input.min()) * _h / (_input.max() - _input.min());
else
ycentre = _y + _input.min() * _h / (_input.max() - _input.min());
range = _h;
if (_odd_type)
ypoint = ycentre + ((value - 1.0) * range / _val_span);
else
ypoint = ycentre + (value * range / _val_span);
xpoint = _x - _marker_offset;
draw_line(xpoint, ycentre, xpoint, ypoint);
draw_line(xpoint, ypoint, xpoint + _marker_offset, ypoint);
draw_line(xpoint + _marker_offset, ypoint, xpoint + 5.0, ypoint + 5.0);
draw_line(xpoint + _marker_offset, ypoint, xpoint + 5.0, ypoint - 5.0);
} else { // FIXED
draw_fixed_pointer(-_marker_offset + _x, text_y + _w / 6,
-_marker_offset + marker_xe, text_y, -_marker_offset + _x,
text_y - _w / 6);
}
} // if (_pointer)
} // if (option_right())
// At this point marker x_start and x_end values are transposed.
// To keep this from confusing things they are now swapped.
if (option_both())
marker_ye = marker_xs, marker_xs = marker_xe, marker_xe = marker_ye;
// Work through from bottom to top of scale. Calculating where to put
// minor and major ticks.
// draw scale or tape
float vstart = floorf(vmin / _major_divs) * _major_divs;
float min_diff = _w / 6.0; // length difference between major & minor tick
// FIXME consider oddtype
for (int i = 0; ; i++) {
float v = vstart + i * _minor_divs;
if (!_modulo) {
if (v < _input.min())
continue;
else if (v > _input.max())
break;
}
float y = _y + (v - vmin) * factor();
if (y < _y + TICK_OFFSET)
continue;
if (y > top - TICK_OFFSET)
break;
if (_div_ratio && i % _div_ratio) { // minor div
if (option_both()) {
if (_tick_type == LINE) {
if (_tick_length == VARIABLE) {
draw_line(_x, y, marker_xs, y);
draw_line(marker_xe, y, right, y);
} else {
draw_line(_x, y, marker_xs, y);
draw_line(marker_xe, y, right, y);
}
} else { // _tick_type == CIRCLE
draw_bullet(_x, y, 3.0);
}
} else if (option_left()) {
if (_tick_type == LINE) {
if (_tick_length == VARIABLE) {
draw_line(marker_xs + min_diff, y, marker_xe, y);
} else {
draw_line(marker_xs, y, marker_xe, y);
}
} else { // _tick_type == CIRCLE
draw_bullet(marker_xs + 4, y, 3.0);
}
} else { // if (option_right())
if (_tick_type == LINE) {
if (_tick_length == VARIABLE) {
draw_line(marker_xs, y, marker_xe - min_diff, y);
} else {
draw_line(marker_xs, y, marker_xe, y);
}
} else { // _tick_type == CIRCLE
draw_bullet(marker_xe - 4, y, 3.0);
}
} // end huds both
} else { // major div
if (_modulo)
v = fmodf(v + _modulo, _modulo);
float x;
int align;
if (option_both()) {
if (_tick_type == LINE) {
draw_line(_x, y, marker_xs, y);
draw_line(marker_xs, y, right, y);
} else { // _tick_type == CIRCLE
draw_bullet(_x, y, 5.0);
}
x = marker_xs, align = CENTER;
} else {
if (_tick_type == LINE)
draw_line(marker_xs, y, marker_xe, y);
else // _tick_type == CIRCLE
draw_bullet(marker_xs + 4, y, 5.0);
if (option_left())
x = marker_xs - _label_offset, align = RIGHT|VCENTER;
else
x = marker_xe + _label_offset, align = LEFT|VCENTER;
}
if (!option_notext()) {
char *s = format_value(v);
float l, r, b, t;
_hud->_text_list.align(s, align, &x, &y, &l, &r, &b, &t);
if (b < _y || t > top)
continue;
if (_label_gap == 0.0
|| (b < _center_y - _label_gap && t < _center_y - _label_gap)
|| (b > _center_y + _label_gap && t > _center_y + _label_gap)) {
draw_text(x, y, s);
}
}
}
} // for
}
void HUD::Tape::draw_horizontal(float value)
{
float vmin = 0.0;//, vmax = 0.0;
float marker_xs;
// float marker_xe;
float marker_ys;
float marker_ye;
// float text_y = 0.0;
float top = _y + _h;
float right = _x + _w;
if (!_pointer) {
vmin = value - _half_width_units; // width units == needle travel
// vmax = value + _half_width_units; // or picture unit span.
// text_y = _center_y;
} else if (_pointer_type == MOVING) {
vmin = _input.min();
// vmax = _input.max();
} else { // FIXED
vmin = value - _half_width_units; // width units == needle travel
// vmax = value + _half_width_units; // or picture unit span.
// text_y = _center_y;
}
// left tick bar
if (_draw_tick_left)
draw_line(_x, _y, _x, top);
// right tick bar
if (_draw_tick_right)
draw_line(right, _y, right, top);
marker_ys = _y; // Starting point for
marker_ye = top; // tick y location calcs
// marker_xe = right;
marker_xs = _x + ((value - vmin) * factor());
if (option_top()) {
if (_draw_cap_bottom)
draw_line(_x, _y, right, _y);
// Tick point adjust
marker_ye = _y + _h / 2;
// Bottom arrow
// draw_line(_center_x, marker_ye, _center_x - _h / 4, _y);
// draw_line(_center_x, marker_ye, _center_x + _h / 4, _y);
// draw pointer
if (_pointer) {
if (_pointer_type == MOVING) {
float xcentre = _center_x;
float range = _w;
float xpoint = xcentre + (value * range / _val_span);
float ypoint = _y - _marker_offset;
draw_line(xcentre, ypoint, xpoint, ypoint);
draw_line(xpoint, ypoint, xpoint, ypoint + _marker_offset);
draw_line(xpoint, ypoint + _marker_offset, xpoint + 5.0, ypoint + 5.0);
draw_line(xpoint, ypoint + _marker_offset, xpoint - 5.0, ypoint + 5.0);
} else { // FIXED
draw_fixed_pointer(marker_xs - _h / 4, _y, marker_xs,
marker_ye, marker_xs + _h / 4, _y);
}
}
} // if (option_top())
if (option_bottom()) {
if (_draw_cap_top)
draw_line(_x, top, right, top);
// Tick point adjust
marker_ys = top - _h / 2;
// Top arrow
// draw_line(_center_x + _h / 4, _y + _h, _center_x, marker_ys);
// draw_line(_center_x - _h / 4, _y + _h, _center_x , marker_ys);
if (_pointer) {
if (_pointer_type == MOVING) {
float xcentre = _center_x;
float range = _w;
float hgt = _y + _h;
float xpoint = xcentre + (value * range / _val_span);
float ypoint = hgt + _marker_offset;
draw_line(xcentre, ypoint, xpoint, ypoint);
draw_line(xpoint, ypoint, xpoint, ypoint - _marker_offset);
draw_line(xpoint, ypoint - _marker_offset, xpoint + 5.0, ypoint - 5.0);
draw_line(xpoint, ypoint - _marker_offset, xpoint - 5.0, ypoint - 5.0);
} else { // FIXED
draw_fixed_pointer(marker_xs + _h / 4, top, marker_xs, marker_ys,
marker_xs - _h / 4, top);
}
}
} // if (option_bottom())
float vstart = floorf(vmin / _major_divs) * _major_divs;
float min_diff = _h / 6.0; // length difference between major & minor tick
// FIXME consider oddtype
for (int i = 0; ; i++) {
float v = vstart + i * _minor_divs;
if (!_modulo) {
if (v < _input.min())
continue;
else if (v > _input.max())
break;
}
float x = _x + (v - vmin) * factor();
if (x < _x + TICK_OFFSET)
continue;
if (x > right - TICK_OFFSET)
break;
if (_div_ratio && i % _div_ratio) { // minor div
if (option_both()) {
if (_tick_length == VARIABLE) {
draw_line(x, _y, x, marker_ys - 4);
draw_line(x, marker_ye + 4, x, top);
} else {
draw_line(x, _y, x, marker_ys);
draw_line(x, marker_ye, x, top);
}
} else {
if (option_top()) {
// draw minor ticks
if (_tick_length == VARIABLE)
draw_line(x, marker_ys, x, marker_ye - min_diff);
else
draw_line(x, marker_ys, x, marker_ye);
} else if (_tick_length == VARIABLE) {
draw_line(x, marker_ys + 4, x, marker_ye);
} else {
draw_line(x, marker_ys, x, marker_ye);
}
}
} else { // major divs
if (_modulo)
v = fmodf(v + _modulo, _modulo);
float y;
int align;
if (option_both()) {
draw_line(x, _y, x, marker_ye);
draw_line(x, marker_ye, x, _y + _h);
y = marker_ys, align = CENTER;
} else {
draw_line(x, marker_ys, x, marker_ye);
if (option_top())
y = top - _label_offset, align = TOP|HCENTER;
else
y = _y + _label_offset, align = BOTTOM|HCENTER;
}
if (!option_notext()) {
char *s = format_value(v);
float l, r, b, t;
_hud->_text_list.align(s, align, &x, &y, &l, &r, &b, &t);
if (l < _x || r > right)
continue;
if (_label_gap == 0.0
|| (l < _center_x - _label_gap && r < _center_x - _label_gap)
|| (l > _center_x + _label_gap && r > _center_x + _label_gap)) {
draw_text(x, y, s);
}
}
}
} // for
}
char *HUD::Tape::format_value(float v)
{
if (fabs(v) < 1e-8) // avoid -0.0
v = 0.0f;
if (_label_fmt == INT)
snprintf(_buf, BUFSIZE, _format.c_str(), int(v));
else if (_label_fmt == LONG)
snprintf(_buf, BUFSIZE, _format.c_str(), long(v));
else if (_label_fmt == FLOAT)
snprintf(_buf, BUFSIZE, _format.c_str(), v);
else // _label_fmt == DOUBLE
snprintf(_buf, BUFSIZE, _format.c_str(), double(v));
return _buf;
}
void HUD::Tape::draw_fixed_pointer(float x1, float y1, float x2, float y2, float x3, float y3)
{
glBegin(GL_LINE_STRIP);
glVertex2f(x1, y1);
glVertex2f(x2, y2);
glVertex2f(x3, y3);
glEnd();
}

View File

@@ -0,0 +1,220 @@
// HUD_tbi.cxx -- HUD Turn-Bank-Indicator Instrument
//
// Written by Michele America, started September 1997.
//
// Copyright (C) 1997 Michele F. America [micheleamerica#geocities:com]
// Copyright (C) 2006 Melchior FRANZ [mfranz#aon:at]
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation; either version 2 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#include "HUD.hxx"
#include "HUD_private.hxx"
HUD::TurnBankIndicator::TurnBankIndicator(HUD *hud, const SGPropertyNode *n, float x, float y) :
Item(hud, n, x, y),
_bank(n->getNode("bank-input", false)),
_sideslip(n->getNode("sideslip-input", false)),
_gap_width(n->getFloatValue("gap-width", 5)),
_bank_scale(n->getBoolValue("bank-scale", false))
{
if (!_bank_scale) {
_bank.set_max(30.0, false);
_bank.set_min(-30.0, false);
_sideslip.set_max(20.0, false);
_sideslip.set_min(-20.0, false);
}
}
void HUD::TurnBankIndicator::draw(void)
{
if (!_bank.isValid() || !_sideslip.isValid())
return;
if (_bank_scale)
draw_scale();
else
draw_tee();
}
void HUD::TurnBankIndicator::draw_tee()
{
// ___________ /\ ___________
// | \/ |
float bank = _bank.getFloatValue();
float sideslip = _sideslip.getFloatValue();
float span = get_span();
float tee = -_h;
// sideslip angle pixels per deg (width represents 40 deg)
float ss_const = 2 * sideslip * span / 40.0;
glPushMatrix();
glTranslatef(_center_x, _center_y, 0.0);
glRotatef(-bank, 0.0, 0.0, 1.0);
glBegin(GL_LINES);
if (!_gap_width) {
glVertex2f(-span, 0.0);
glVertex2f(span, 0.0);
} else {
glVertex2f(-span, 0.0);
glVertex2f(-_gap_width, 0.0);
glVertex2f(_gap_width, 0.0);
glVertex2f(span, 0.0);
}
// draw teemarks
glVertex2f(_gap_width, 0.0);
glVertex2f(_gap_width, tee);
glVertex2f(-_gap_width, 0.0);
glVertex2f(-_gap_width, tee);
glEnd();
glBegin(GL_LINE_LOOP);
glVertex2f(ss_const, -_gap_width);
glVertex2f(ss_const + _gap_width, 0.0);
glVertex2f(ss_const, _gap_width);
glVertex2f(ss_const - _gap_width, 0.0);
glEnd();
glPopMatrix();
}
void HUD::TurnBankIndicator::draw_scale()
{
// MIL-STD 1878B/4.2.2.4 bank scale
float bank = _bank.getFloatValue();
float sideslip = _sideslip.getFloatValue();
float cx = _center_x;
float cy = _center_y;
float r = _w / 2.0;
float minor = r - r * 3.0 / 70.0;
float major = r - r * 5.0 / 70.0;
// hollow 0 degree mark
float w = r / 70.0;
if (w < 1.0)
w = 1.0;
float h = r * 6.0 / 70.0;
draw_line(cx - w, _y, cx + w, _y);
draw_line(cx - w, _y, cx - w, _y + h);
draw_line(cx + w, _y, cx + w, _y + h);
draw_line(cx - w, _y + h, cx + w, _y + h);
// tick lines
draw_tick(10, r, minor, 0);
draw_tick(20, r, minor, 0);
draw_tick(30, r, major, 0);
int dir = bank > 0 ? 1 : -1;
if (fabs(bank) > 25) {
draw_tick(45, r, minor, dir);
draw_tick(60, r, major, dir);
}
if (fabs(bank) > 55) {
draw_tick(90, r, major, dir);
draw_tick(135, r, major, dir);
}
// bank marker
float a;
float rr = r + r * 0.5 / 70.0; // little gap for the arrow peak
a = (bank + 270.0) * SGD_DEGREES_TO_RADIANS;
float x1 = cx + rr * cos(a);
float y1 = cy + rr * sin(a);
rr = r * 3.0 / 70.0;
a = (bank + 240.0) * SGD_DEGREES_TO_RADIANS;
float x2 = x1 + rr * cos(a);
float y2 = y1 + rr * sin(a);
a = (bank + 300.0) * SGD_DEGREES_TO_RADIANS;
float x3 = x1 + rr * cos(a);
float y3 = y1 + rr * sin(a);
draw_line(x1, y1, x2, y2);
draw_line(x2, y2, x3, y3);
draw_line(x3, y3, x1, y1);
// sideslip marker
rr = r + r * 0.5 / 70.0;
a = (bank + sideslip + 270.0) * SGD_DEGREES_TO_RADIANS;
x1 = cx + rr * cos(a);
y1 = cy + rr * sin(a);
rr = r * 3.0 / 70.0;
a = (bank + sideslip + 240.0) * SGD_DEGREES_TO_RADIANS;
x2 = x1 + rr * cos(a);
y2 = y1 + rr * sin(a);
a = (bank + sideslip + 300.0) * SGD_DEGREES_TO_RADIANS;
x3 = x1 + rr * cos(a);
y3 = y1 + rr * sin(a);
rr = r * 6.0 / 70.0;
a = (bank + sideslip + 240.0) * SGD_DEGREES_TO_RADIANS;
float x4 = x1 + rr * cos(a);
float y4 = y1 + rr * sin(a);
a = (bank + sideslip + 300.0) * SGD_DEGREES_TO_RADIANS;
float x5 = x1 + rr * cos(a);
float y5 = y1 + rr * sin(a);
draw_line(x2, y2, x3, y3);
draw_line(x3, y3, x5, y5);
draw_line(x5, y5, x4, y4);
draw_line(x4, y4, x2, y2);
}
void HUD::TurnBankIndicator::draw_tick(float angle, float r1, float r2, int side)
{
float a = (270 - angle) * SGD_DEGREES_TO_RADIANS;
float c = cos(a);
float s = sin(a);
float x1 = r1 * c;
float x2 = r2 * c;
float y1 = _center_y + r1 * s;
float y2 = _center_y + r2 * s;
if (side >= 0)
draw_line(_center_x - x1, y1, _center_x - x2, y2);
if (side <= 0)
draw_line(_center_x + x1, y1, _center_x + x2, y2);
}
void HUD::TurnBankIndicator::draw_line(float x1, float y1, float x2, float y2)
{
if (option_top()) {
float y = 2.0 * _center_y; // mirror vertically
Item::draw_line(x1, y - y1, x2, y - y2);
} else
Item::draw_line(x1, y1, x2, y2);
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,363 @@
// kln89_page.hxx - a class to manage the simulation of a KLN89
// GPS unit. Note that this is primarily the
// simulation of the user interface and display
// - the core GPS calculations such as position
// and waypoint sequencing are done (or should
// be done) by FG code.
//
// Written by David Luff, started 2005.
//
// Copyright (C) 2005 - David C Luff - daveluff AT ntlworld.com
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation; either version 2 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
//
// $Id$
#ifndef _KLN89_HXX
#define _KLN89_HXX
#include <Instrumentation/dclgps.hxx>
#include "kln89_page.hxx"
class KLN89Page;
const int KLN89MapScales[2][21] = {{1, 2, 3, 5, 7, 10, 12, 15, 17, 20, 25, 30, 40, 60, 80, 100, 120, 160, 240, 320, 500},
{2, 4, 6, 9, 13, 18, 22, 28, 32, 37, 46, 55, 75, 110, 150, 185, 220, 300, 440, 600, 925}};
enum KLN89Mode {
KLN89_MODE_DISP,
KLN89_MODE_CRSR
};
enum KLN89DistanceUnits {
GPS_DIST_UNITS_NM = 0,
GPS_DIST_UNITS_KM
};
enum KLN89SpeedUnits {
GPS_VEL_UNITS_KT,
GPS_VEL_UNITS_KPH
};
enum KLN89AltitudeUnits {
GPS_ALT_UNITS_FT,
GPS_ALT_UNITS_M
};
enum KLN89PressureUnits {
GPS_PRES_UNITS_IN = 1,
GPS_PRES_UNITS_MB,
GPS_PRES_UNITS_HP
};
/*
const char* KLN89TimeCodes[20] = { "UTC", "GST", "GDT", "ATS", "ATD", "EST", "EDT", "CST", "CDT", "MST",
"MDT", "PST", "PDT", "AKS", "AKD", "HAS", "HAD", "SST", "SDT", "LCL" };
*/
// Used for storing airport town and county mapped by ID, since currently FG does not store this
typedef std::map<std::string, std::string> airport_id_str_map_type;
typedef airport_id_str_map_type::iterator airport_id_str_map_iterator;
typedef std::vector<KLN89Page*> kln89_page_list_type;
typedef kln89_page_list_type::iterator kln89_page_list_itr;
class KLN89 : public DCLGPS
{
friend class KLN89Page;
friend class KLN89AptPage;
friend class KLN89VorPage;
friend class KLN89NDBPage;
friend class KLN89IntPage;
friend class KLN89UsrPage;
friend class KLN89ActPage;
friend class KLN89NavPage;
friend class KLN89FplPage;
friend class KLN89CalPage;
friend class KLN89SetPage;
friend class KLN89OthPage;
friend class KLN89AltPage;
friend class KLN89DirPage;
friend class KLN89NrstPage;
public:
KLN89(RenderArea2D* instrument);
~KLN89();
// Subsystem API.
void bind() override;
void init() override;
void unbind() override;
void update(double dt) override;
// Subsystem identification.
static const char* staticSubsystemClassId() { return "KLN89"; }
// Set Units
// m if true, ft if false
inline void SetAltUnitsSI(bool b) { _altUnits = (b ? GPS_ALT_UNITS_M : GPS_ALT_UNITS_FT); }
// Returns true if alt units are SI (m), false if ft
inline bool GetAltUnitsSI() { return(_altUnits == GPS_ALT_UNITS_M ? true : false); }
// km and k/h if true, nm and kt if false
inline void SetDistVelUnitsSI(bool b) { _distUnits = (b ? GPS_DIST_UNITS_KM : GPS_DIST_UNITS_NM); _velUnits = (b ? GPS_VEL_UNITS_KPH : GPS_VEL_UNITS_KT); }
// Returns true if dist/vel units are SI
inline bool GetDistVelUnitsSI() { return(_distUnits == GPS_DIST_UNITS_KM && _velUnits == GPS_VEL_UNITS_KPH ? true : false); }
// Set baro units - 1 = in, 2 = mB, 3 = hP Wrapping if for the convienience of the GPS setter.
void SetBaroUnits(int n, bool wrap = false);
// Get baro units: 1 = in, 2 = mB, 3 = hP
inline int GetBaroUnits() { return((int)_baroUnits); }
inline void SetTurnAnticipation(bool b) { _turnAnticipationEnabled = b; }
inline bool GetTurnAnticipation() { return(_turnAnticipationEnabled); }
inline void SetSuaAlertEnabled(bool b) { _suaAlertEnabled = b; }
inline bool GetSuaAlertEnabled() { return(_suaAlertEnabled); }
inline void SetAltAlertEnabled(bool b) { _altAlertEnabled = b; }
inline bool GetAltAlertEnabled() { return(_altAlertEnabled); }
void SetMinDisplayBrightness(int n); // Set minDisplayBrightness (between 1 and 9)
void DecrementMinDisplayBrightness(); // Decrease by 1
void IncrementMinDisplayBrightness(); // Increase by 1
inline int GetMinDisplayBrightness() { return(_minDisplayBrightness); }
inline bool GetMsgAlert() const { return(!_messageStack.empty()); }
void Knob1Right1();
void Knob1Left1();
void Knob2Right1();
void Knob2Left1();
void CrsrPressed();
void EntPressed();
void ClrPressed();
void DtoPressed();
void NrstPressed();
void AltPressed();
void OBSPressed();
void MsgPressed();
void CreateDefaultFlightPlans();
private:
void ToggleOBSMode();
// Initiate Direct To operation to the supplied ID.
void DtoInitiate(const std::string& id);
//----------------------- Drawing functions which take CHARACTER units -------------------------
// Render string s in display field field at position x, y
// WHERE POSITION IS IN CHARACTER UNITS!
// zero y at bottom?
// invert: -1 => no inversion, 0 -> n => 1 char - s[invert] gets inverted, 99 => entire string gets inverted
void DrawText(const std::string& s, int field, int px, int py, bool bold = false, int invert = -1);
void DrawLatitude(double d, int field, int px, int py);
void DrawLongitude(double d, int field, int px, int py);
// Draw a frequency as xxx.xx
void DrawFreq(double d, int field, int px, int py);
// Draw a time in seconds as hh:mm
// NOTE: px is RIGHT JUSTIFIED!
void DrawTime(double time, int field, int px, int py);
// Draw an integer heading, where px specifies the position of the degrees sign at the RIGHT of the value.
void DrawHeading(int h, int field, int px, int py);
// Draw a distance spec'd as nm as an integer (TODO - may need 1 decimal place if < 100) where px specifies RHS of units.
// Some uses definately don't want decimal place though (as at present), so would have to be arg.
void DrawDist(double d, int field, int px, int py);
// Draw a speed specifed in knots. px is RHS of the units. Can draw up to 2 decimal places.
void DrawSpeed(double v, int field, int px, int py, int decimals = 0);
void Underline(int field, int px, int py, int len);
// Render a char at a given position as above (position in CHARACTER units)
void DrawChar(char c, int field, int px, int py, bool bold = false, bool invert = false);
void DrawSpecialChar(char c, int field, int cx, int cy, bool bold = false);
// Draws the dir/dist field at the bottom of the main field
void DrawDirDistField(double lat, double lon, int field, int px, int py, bool to_flag = true, bool cursel = false);
//
//--------------------------------- end char units -----------------------------------------------
//----------------------- Drawing functions which take PIXEL units ------------------------------
//
// Takes instrument *pixel* co-ordinates NOT character units
// Position is specified by the bottom of the *visible* portion, by default the left position unless align_right is true.
// The return value is the pixel width of the visible portion
int DrawSmallChar(char c, int x, int y, bool align_right = false);
void DrawFreeChar(char c, int x, int y, bool draw_background = false);
//
//----------------------------------- end pixel unit functions -----------------------------------
void DrawDivider();
void DrawEnt(int field = 1, int px = 0, int py = 1);
void DrawMessageAlert();
void DrawKPH(int field, int cx, int cy);
void DrawDTO(int field, int cx, int cy);
// Draw the bar that indicates which page we're on (zero-based)
void DrawBar(int page);
void DrawCDI();
void DrawLegTail(int py);
void DrawLongLegTail(int py);
void DrawHalfLegTail(int py);
void UpdateMapHeading();
// Draw the moving map
// Apt, VOR and SUA drawing can be suspended by setting draw_avs to false, without affecting the stored drawing preference state.
void DrawMap(bool draw_avs = true);
// Set whether the display should be drawn pixelated (more primitives, but might be closer to real-life)
// or not (in which case it is assumed that pixels are square and can be merged into quads).
bool _pixelated;
// Flashing output should be hidden when blink is true
bool _blink;
double _cum_dt;
// In Crsr mode, CRSR pressed events are passed to the active page, in disp mode they change which page is active
KLN89Mode _mode;
// And the facility to save a mode
KLN89Mode _lastMode;
// Increment/Decrement a character in the KLN89 A-Z,0-9 scheme.
// Set gap to true to get a space between A and 9 when wrapping, set wrap to false to disable wrap.
char IncChar(char c, bool gap = false, bool wrap = true);
char DecChar(char c, bool gap = false, bool wrap = true);
// ==================== Page organisation stuff =============
// The list of cyclical pages that the user can cycle through
kln89_page_list_type _pages;
// The currently active page
KLN89Page* _activePage;
// And a facility to save the immediately preceding active page
KLN89Page* _lastActivePage;
// Ugly hack. Housekeeping to allow us to temporarily display one page, while remembering which
// other page to "jump" back to. Used when the waypoint pages are used to review waypoint entry
// from the flightplan page.
int _entJump; // The page to jump back to if ENT is pressed. -1 indicates no jump.
int _clrJump; // The page to jump back to if CLR is pressed. -1 indicates no jump.
bool _jumpRestoreCrsr; // Indicates that jump back at this point should restore cursor mode.
// Misc pages that aren't in the cyclic list.
// ALT
KLN89Page* _alt_page;
// Direct To
KLN89Page* _dir_page;
// Nearest
KLN89Page* _nrst_page;
// ====================== end of page stuff ===================
// Moving-map display stuff
int _mapOrientation; // 0 => North (true) up, 1 => DTK up, 2 => TK up, 3 => heading up (only when connected to external heading source).
double _mapHeading; // Degrees. The actual map heading gets updated at a lower frequency than DrawMap() is called at, hence we need to store it.
double _mapHeadingUpdateTimer; // Timer to determine when to update the above.
bool _mapScaleAuto; // Indicates that map should autoscale when true.
int _mapScaleIndex; // Index into array of available map scales.
int _mapScaleUnits; // 0 => nm, 1 => km.
double _mapScale; // nm or km from aircraft position to top of map.
// Note that aircraft position differs depending on orientation, but 'scale' retains the same meaning,
// so the scale per pixel alters to suit the defined scale when the rendered aircraft position changes.
bool _drawSUA; // special user airspace
bool _drawVOR;
bool _drawApt;
// Convert map to instrument coordinates
void MapToInstrument(int &x, int &y);
// The following map drawing functions all take MAP co-ordinates, NOT instrument co-ordinates!
// Draw the diamond style of user pos
void DrawUser1(int x, int y);
// Draw the airplane style of user pos
void DrawUser2(int x, int y);
// Draw an airport symbol on the moving map
void DrawApt(int x, int y);
// Draw a waypoint on the moving map
void DrawWaypoint(int x, int y);
// Draw a VOR on the moving map
void DrawVOR(int x, int y);
// Draw an airport or waypoint label on the moving map
// Specify position by the map pixel co-ordinate of the left or right, bottom, of the *visible* portion of the label.
// The black background quad will automatically overlap this by 1 pixel.
void DrawLabel(const std::string& s, int x1, int y1, bool right_align = false);
int GetLabelQuadrant(double h);
int GetLabelQuadrant(double h1, double h2);
// Draw a line on the moving map
void DrawLine(int x1, int y1, int x2, int y2);
// Draw normal sized text on the moving map
void DrawMapText(const std::string& s, int x, int y, bool draw_background = false);
void DrawMapUpArrow(int x, int y);
// Draw a Quad on the moving map
void DrawMapQuad(int x1, int y1, int x2, int y2, bool invert = false);
// Airport town and state mapped by ID, since currently FG does not store this
airport_id_str_map_type _airportTowns;
airport_id_str_map_type _airportStates;
// NOTE - It is a deliberate decision not to have a proper message page class,
// since button events get directed to the page that was active before the
// message was displayed, not the message page itself.
bool _dispMsg; // Set true while the message page is being displayed
// Sometimes the datapages can be used to review a waypoint whilst the user makes a decision,
// and we need to remember why.
bool _dtoReview; // Set true when we a reviewing a waypoint for DTO operation.
// Configuration settings that the user can set via. the KLN89 SET pages.
KLN89SpeedUnits _velUnits;
KLN89DistanceUnits _distUnits;
KLN89PressureUnits _baroUnits;
KLN89AltitudeUnits _altUnits;
bool _suaAlertEnabled; // Alert user to potential SUA entry
bool _altAlertEnabled; // Alert user to min safe alt violation
int _minDisplayBrightness; // Minimum display brightness in low light.
char _defaultFirstChar; // Default first waypoint character.
// The user-settable barometric pressure.
// This can be set in the range 22.00 -> 32.99", or 745 -> 1117mB/hPa.
// For user input, we maintain a single integer value that is either between 2200 and 3299 (")
// or between 745 and 1117 (mB/hPa). It gets converted from one to the other only when the
// units are changed.
// For internal VNAV calculations (which we don't currently do) this will be converted to a floating
// point value before use.
int _userBaroSetting;
};
#endif // _KLN89_HXX

View File

@@ -0,0 +1,228 @@
// kln89_page.cxx - base class for the "pages" that
// are used in the KLN89 GPS unit simulation.
//
// Written by David Luff, started 2005.
//
// Copyright (C) 2005 - David C Luff - daveluff AT ntlworld.com
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation; either version 2 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
//
// $Id$
#include "kln89_page.hxx"
#include <Main/fg_props.hxx>
#include <cstdio>
using std::string;
KLN89Page::KLN89Page(KLN89* parent) {
_kln89 = parent;
_entInvert = false;
_to_flag = true;
_subPage = 0;
}
KLN89Page::~KLN89Page() {
}
void KLN89Page::Update(double dt) {
bool crsr = (_kln89->_mode == KLN89_MODE_CRSR ? true : false);
bool nav1 = (_name == "NAV" && _subPage == 0);
bool nav4 = (_name == "NAV" && _subPage == 3);
// The extra level of check for the ACT page is necessary since
// ACT is implemented by using the other waypoint pages as
// appropriate.
bool act = (_kln89->_activePage->GetName() == "ACT");
_kln89->DrawDivider();
if(crsr) {
if(!nav4) _kln89->DrawText("*CRSR*", 1, 0, 0);
if(_uLinePos == 0) _kln89->Underline(1, 3, 1, 3);
} else {
if(!nav4) {
if(act) {
_kln89->DrawText("ACT", 1, 0, 0);
} else {
_kln89->DrawText(_name, 1, 0, 0);
}
if(_name == "DIR") {
// Don't draw a subpage number
} else if(_name == "USR" || _name == "FPL") {
// Zero-based
_kln89->DrawText(GPSitoa(_subPage), 1, 4, 0);
} else {
// One-based
_kln89->DrawText(GPSitoa(_subPage+1), 1, 4, 0);
}
}
}
if(crsr && _uLinePos == 0 && _kln89->_blink) {
// Don't draw
} else {
if(_kln89->_obsMode) {
_kln89->DrawText(GPSitoa(_kln89->_obsHeading), 1, 3, 1);
} else {
_kln89->DrawText("Leg", 1, 3, 1);
}
}
_kln89->DrawText((_kln89->GetDistVelUnitsSI() ? "km" : "nm"), 1, 4, 3);
GPSWaypoint* awp = _kln89->GetActiveWaypoint();
if(_kln89->_navFlagged) {
_kln89->DrawText("--.-", 1, 0 ,3);
// Only nav1 still gets speed drawn if nav is flagged - not ACT
if(!nav1) _kln89->DrawText("------", 1, 0, 2);
} else {
char buf[8];
float f = _kln89->GetDistToActiveWaypoint() * (_kln89->GetDistVelUnitsSI() ? 0.001 : SG_METER_TO_NM);
snprintf(buf, 5, (f >= 100.0 ? "%4.0f" : "%4.1f"), f);
string s = buf;
_kln89->DrawText(s, 1, 4 - s.size(), 3, true);
// Draw active waypoint ID, except for
// nav1, act, and any waypoint pages matching
// active waypoint that need speed drawn instead.
if(act || nav1 || (awp && awp->id == _id)) {
_kln89->DrawSpeed(_kln89->_groundSpeed_kts, 1, 5, 2);
} else {
if(!(_kln89->_waypointAlert && _kln89->_blink)) _kln89->DrawText(awp->id, 1, 0, 2);
}
}
/*
if(_noNrst) {
_kln89->DrawText(" No ", 1, 0, 1, false, 99);
_kln89->DrawText(" Nrst ", 1, 0, 0, false, 99);
}
*/
if(_scratchpadMsg) {
_kln89->DrawText(_scratchpadLine1, 1, 0, 1, false, 99);
_kln89->DrawText(_scratchpadLine2, 1, 0, 0, false, 99);
_scratchpadTimer += dt;
if(_scratchpadTimer > 4.0) {
_scratchpadMsg = false;
_scratchpadTimer = 0.0;
}
}
}
void KLN89Page::ShowScratchpadMessage(const string& line1, const string& line2) {
_scratchpadLine1 = line1;
_scratchpadLine2 = line2;
_scratchpadTimer = 0.0;
_scratchpadMsg = true;
}
void KLN89Page::Knob1Left1() {
if(_kln89->_mode == KLN89_MODE_CRSR) {
if(_uLinePos > 0) _uLinePos--;
}
}
void KLN89Page::Knob1Right1() {
if(_kln89->_mode == KLN89_MODE_CRSR) {
if(_uLinePos < _maxULinePos) _uLinePos++;
}
}
void KLN89Page::Knob2Left1() {
if(_kln89->_mode != KLN89_MODE_CRSR && !fgGetBool("/instrumentation/kln89/scan-pull")) {
_kln89->_activePage->LooseFocus();
_subPage--;
if(_subPage < 0) _subPage = _nSubPages - 1;
} else {
if(_uLinePos == 0 && _kln89->_obsMode) {
_kln89->_obsHeading--;
if(_kln89->_obsHeading < 0) {
_kln89->_obsHeading += 360;
}
_kln89->SetOBSFromWaypoint();
}
}
}
void KLN89Page::Knob2Right1() {
if(_kln89->_mode != KLN89_MODE_CRSR && !fgGetBool("/instrumentation/kln89/scan-pull")) {
_kln89->_activePage->LooseFocus();
_subPage++;
if(_subPage >= _nSubPages) _subPage = 0;
} else {
if(_uLinePos == 0 && _kln89->_obsMode) {
_kln89->_obsHeading++;
if(_kln89->_obsHeading > 359) {
_kln89->_obsHeading -= 360;
}
_kln89->SetOBSFromWaypoint();
}
}
}
void KLN89Page::CrsrPressed() {
// Stick some sensible defaults in
if(_kln89->_obsMode) {
_uLinePos = 0;
} else {
_uLinePos = 1;
}
_maxULinePos = 1;
}
void KLN89Page::EntPressed() {}
void KLN89Page::ClrPressed() {}
void KLN89Page::DtoPressed() {}
void KLN89Page::NrstPressed() {}
void KLN89Page::AltPressed() {}
void KLN89Page::OBSPressed() {
if(_kln89->_obsMode) {
// If ORS2 and not slaved to gps
_uLinePos = 0;
} else {
// Don't leave the cursor on in the leg position.
if(_uLinePos == 0) {
_kln89->_mode = KLN89_MODE_DISP;
}
}
}
void KLN89Page::MsgPressed() {}
void KLN89Page::CleanUp() {
_kln89->_cleanUpPage = -1;
}
void KLN89Page::LooseFocus() {
_entInvert = false;
}
void KLN89Page::SetId(const string& s) {
_id = s;
}
void KLN89Page::SetSubPage(int n) {
if(n < 0) n = 0;
if(n >= _nSubPages) n = _nSubPages-1;
_subPage = n;
}
const string& KLN89Page::GetId() {
return(_id);
}
// TODO - this function probably shouldn't be here - FG almost certainly has better handling
// of this somewhere already.
string KLN89Page::GPSitoa(int n) {
char buf[6];
snprintf(buf, 6, "%i", n);
string s = buf;
return(s);
}

View File

@@ -0,0 +1,114 @@
// kln89_page.hxx - base class for the "pages" that
// are used in the KLN89 GPS unit simulation.
//
// Written by David Luff, started 2005.
//
// Copyright (C) 2005 - David C Luff - daveluff AT ntlworld.com
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation; either version 2 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
//
// $Id$
#ifndef _KLN89_PAGE_HXX
#define _KLN89_PAGE_HXX
#include <Instrumentation/dclgps.hxx>
#include "kln89.hxx"
class KLN89;
class KLN89Page {
public:
KLN89Page(KLN89* parent);
virtual ~KLN89Page();
virtual void Update(double dt);
virtual void Knob1Left1();
virtual void Knob1Right1();
virtual void Knob2Left1();
virtual void Knob2Right1();
virtual void CrsrPressed();
virtual void EntPressed();
virtual void ClrPressed();
// Even though some/all of the buttons below aren't processed directly by the current page,
// the current page often needs to save or change some state when they are pressed, and
// hence should provide a function to handle them.
virtual void DtoPressed();
virtual void NrstPressed();
virtual void AltPressed();
virtual void OBSPressed();
virtual void MsgPressed();
// Sometimes a page needs to maintain state for some return paths,
// but change it for others. The CleanUp function can be used for
// changing state for non-ENT return paths in conjunction with
// GPS::_cleanUpPage
virtual void CleanUp();
// The LooseFocus function is called when a page or subpage looses focus
// and allows pages to clean up state that is maintained whilst focus is
// retained, but lost on return.
virtual void LooseFocus();
inline void SetEntInvert(bool b) { _entInvert = b; }
// Get / Set a waypoint id, NOT the page name!
virtual void SetId(const std::string& s);
virtual const std::string& GetId();
inline int GetSubPage() { return(_subPage); }
void SetSubPage(int n);
inline int GetNSubPages() { return(_nSubPages); }
inline const std::string& GetName() { return(_name); }
protected:
KLN89* _kln89;
std::string _name; // eg. "APT", "NAV" etc
int _nSubPages;
// _subpage is zero based
int _subPage; // The subpage gets remembered when other pages are displayed
// Underline position in cursor mode is not persistant when subpage is changed - hence we only need one variable per page for it.
// Note that pos 0 is special - this is the leg pos in field 1, so pos will normally be set to 1 when crsr is pressed.
// Also note that in general it doesn't seem to wrap.
unsigned int _uLinePos;
unsigned int _maxULinePos;
// This is NOT the main gps to/from flag - derived page classes can use this flag
// for any purpose, typically whether a radial bearing should be displayed to or from.
bool _to_flag; // true for TO, false for FROM
// Invert ID and display ENT in field 1
bool _entInvert;
std::string _id; // The ID of the waypoint that the page is displaying.
// Doesn't make sense for all pages, but does for all the data pages.
void ShowScratchpadMessage(const std::string& line1, const std::string& line2);
bool _scratchpadMsg; // Set true when there is a scratchpad message to display
double _scratchpadTimer; // Used for displaying the scratchpad messages for the right amount of time.
std::string _scratchpadLine1;
std::string _scratchpadLine2;
// TODO - remove this function from this class and use a built in method instead.
std::string GPSitoa(int n);
};
#endif // _KLN89_PAGE_HXX

View File

@@ -0,0 +1,141 @@
// kln89_page_*.[ch]xx - this file is one of the "pages" that
// are used in the KLN89 GPS unit simulation.
//
// Written by David Luff, started 2005.
//
// Copyright (C) 2005 - David C Luff - daveluff AT ntlworld.com
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation; either version 2 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
//
// $Id$
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
#include "kln89_page_act.hxx"
#include "kln89_page_apt.hxx"
#include "kln89_page_vor.hxx"
#include "kln89_page_ndb.hxx"
#include "kln89_page_int.hxx"
#include "kln89_page_usr.hxx"
KLN89ActPage::KLN89ActPage(KLN89* parent)
: KLN89Page(parent) {
_nSubPages = 1;
_subPage = 0;
_name = "ACT";
_actWp = NULL;
_actWpId = -1;
_actPage = NULL;
_aptPage = new KLN89AptPage(parent);
_vorPage = new KLN89VorPage(parent);
_ndbPage = new KLN89NDBPage(parent);
_intPage = new KLN89IntPage(parent);
_usrPage = new KLN89UsrPage(parent);
}
KLN89ActPage::~KLN89ActPage() {
delete _aptPage;
delete _vorPage;
delete _ndbPage;
delete _intPage;
delete _usrPage;
}
void KLN89ActPage::Update(double dt) {
if(!_actWp) {
_actWp = _kln89->GetActiveWaypoint();
_actWpId = _kln89->GetActiveWaypointIndex();
}
if(_actWp) {
switch(_actWp->type) {
case GPS_WP_APT: _actPage = _aptPage; break;
case GPS_WP_VOR: _actPage = _vorPage; break;
case GPS_WP_NDB: _actPage = _ndbPage; break;
case GPS_WP_INT: _actPage = _intPage; break;
case GPS_WP_USR: _actPage = _usrPage; break;
default:
_actPage = NULL;
// ASSERT(0); // ie. we shouldn't ever get here.
}
}
_id = _actWp->id;
if(_actPage) {
_actPage->SetId(_actWp->id);
_actPage->Update(dt);
} else {
KLN89Page::Update(dt);
}
}
void KLN89ActPage::CrsrPressed() {
if(_actPage) {
_actPage->CrsrPressed();
} else {
KLN89Page::CrsrPressed();
}
}
void KLN89ActPage::EntPressed() {
if(_actPage) {
_actPage->EntPressed();
} else {
KLN89Page::EntPressed();
}
}
void KLN89ActPage::ClrPressed() {
if(_actPage) {
_actPage->ClrPressed();
} else {
KLN89Page::ClrPressed();
}
}
void KLN89ActPage::Knob1Left1() {
if(_actPage) {
_actPage->Knob1Left1();
}
}
void KLN89ActPage::Knob1Right1() {
if(_actPage) {
_actPage->Knob1Right1();
}
}
void KLN89ActPage::Knob2Left1() {
if((_kln89->_mode != KLN89_MODE_CRSR) && (_actPage)) {
_actPage->Knob2Left1();
}
}
void KLN89ActPage::Knob2Right1() {
if((_kln89->_mode != KLN89_MODE_CRSR) && (_actPage)) {
_actPage->Knob2Right1();
}
}
void KLN89ActPage::LooseFocus() {
// Setting to NULL and -1 is better than resetting to
// active waypoint and index since we can't guarantee that
// the fpl active waypoint won't change behind our backs
// when we don't have focus.
_actWp = NULL;
_actWpId = -1;
}

View File

@@ -0,0 +1,64 @@
// kln89_page_*.[ch]xx - this file is one of the "pages" that
// are used in the KLN89 GPS unit simulation.
//
// Written by David Luff, started 2005.
//
// Copyright (C) 2005 - David C Luff - daveluff AT ntlworld.com
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation; either version 2 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
//
// $Id$
#ifndef _KLN89_PAGE_ACT_HXX
#define _KLN89_PAGE_ACT_HXX
#include "kln89.hxx"
class KLN89ActPage : public KLN89Page {
public:
KLN89ActPage(KLN89* parent);
~KLN89ActPage();
void Update(double dt);
void CrsrPressed();
void EntPressed();
void ClrPressed();
void Knob1Left1();
void Knob1Right1();
void Knob2Left1();
void Knob2Right1();
void LooseFocus();
private:
// Position of the currently displayed waypoint within the active flightplan.
// -1 indicates no active flightplan.
int _actWpId;
GPSWaypoint* _actWp;
// The actual ACT page that gets displayed...
KLN89Page* _actPage;
// ...which points to one of the below.
KLN89Page* _aptPage;
KLN89Page* _vorPage;
KLN89Page* _ndbPage;
KLN89Page* _intPage;
KLN89Page* _usrPage;
};
#endif // _KLN89_PAGE_ACT_HXX

View File

@@ -0,0 +1,129 @@
// kln89_page_*.[ch]xx - this file is one of the "pages" that
// are used in the KLN89 GPS unit simulation.
//
// Written by David Luff, started 2010.
//
// Copyright (C) 2010 - David C Luff - daveluff AT ntlworld.com
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation; either version 2 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
#include <cstdio>
#include "kln89_page_alt.hxx"
using std::string;
KLN89AltPage::KLN89AltPage(KLN89* parent)
: KLN89Page(parent) {
_nSubPages = 2;
_subPage = 0;
_name = "ALT";
_uLinePos = 1;
_maxULinePos = 1;
}
KLN89AltPage::~KLN89AltPage() {
}
void KLN89AltPage::Update(double dt) {
if(_subPage == 0) {
_kln89->DrawText("BARO:", 2, 2, 3);
if(_kln89->_baroUnits == GPS_PRES_UNITS_IN) {
// If the units are not consistent with the setting, then convert to the correct
// units. We do it here instead of where the units are set in order to avoid any
// possible value creep with multiple unit toggling.
if(_kln89->_userBaroSetting >= 745 && _kln89->_userBaroSetting <= 1117) {
_kln89->_userBaroSetting = (int)((float)_kln89->_userBaroSetting * 0.0295301 * 100 + 0.5);
}
char buf[14];
snprintf(buf, sizeof(buf), "%2i.%02i", _kln89->_userBaroSetting/100, _kln89->_userBaroSetting % 100);
string s = buf;
if(!(_kln89->_mode == KLN89_MODE_CRSR && _uLinePos == 1 && _kln89->_blink)) {
_kln89->DrawText(s, 2, 7, 3);
}
if(_kln89->_mode == KLN89_MODE_CRSR && _uLinePos == 1) {
_kln89->Underline(2, 7, 3, 5);
}
_kln89->DrawText("\"", 2, 12, 3);
} else {
// If the units are not consistent with the setting, then convert to the correct
// units. We do it here instead of where the units are set in order to avoid any
// possible value creep with multiple unit toggling.
if(_kln89->_userBaroSetting >= 2200 && _kln89->_userBaroSetting <= 3299) {
_kln89->_userBaroSetting = (int)(((float)_kln89->_userBaroSetting / 100.0) * 33.8637526 + 0.5);
}
char buf[5];
snprintf(buf, sizeof(buf), "%4i", _kln89->_userBaroSetting);
string s = buf;
if(!(_kln89->_mode == KLN89_MODE_CRSR && _uLinePos == 1 && _kln89->_blink)) {
_kln89->DrawText(s, 2, 8, 3);
}
if(_kln89->_mode == KLN89_MODE_CRSR && _uLinePos == 1) {
_kln89->Underline(2, 8, 3, 4);
}
_kln89->DrawText(_kln89->_baroUnits == GPS_PRES_UNITS_MB ? "mB" : "hP", 2, 12, 3);
}
_kln89->DrawText("MSA", 2, 2, 1);
_kln89->DrawText("ESA", 2, 2, 0);
// At the moment we have no obstruction database, so dash out MSA & ESA
_kln89->DrawText("----", 2, 8, 1);
_kln89->DrawText("----", 2, 8, 0);
if(_kln89->_altUnits == GPS_ALT_UNITS_FT) {
_kln89->DrawText("ft", 2, 12, 1);
_kln89->DrawText("ft", 2, 12, 0);
} else {
_kln89->DrawText("m", 2, 12, 1);
_kln89->DrawText("m", 2, 12, 0);
}
} else {
_kln89->DrawText("Vnv Inactive", 2, 0, 3);
}
KLN89Page::Update(dt);
}
void KLN89AltPage::CrsrPressed() {
}
void KLN89AltPage::EntPressed() {
}
void KLN89AltPage::Knob2Left1() {
_kln89->_userBaroSetting--;
if(_kln89->_baroUnits == GPS_PRES_UNITS_IN) {
if(_kln89->_userBaroSetting < 2200) _kln89->_userBaroSetting = 3299;
} else {
if(_kln89->_userBaroSetting < 745) _kln89->_userBaroSetting = 1117;
}
}
void KLN89AltPage::Knob2Right1() {
_kln89->_userBaroSetting++;
if(_kln89->_baroUnits == GPS_PRES_UNITS_IN) {
if(_kln89->_userBaroSetting > 3299) _kln89->_userBaroSetting = 2200;
} else {
if(_kln89->_userBaroSetting > 1117) _kln89->_userBaroSetting = 745;
}
}
void KLN89AltPage::LooseFocus() {
_uLinePos = 1;
}

View File

@@ -0,0 +1,47 @@
// kln89_page_*.[ch]xx - this file is one of the "pages" that
// are used in the KLN89 GPS unit simulation.
//
// Written by David Luff, started 2010.
//
// Copyright (C) 2010 - David C Luff - daveluff AT ntlworld.com
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation; either version 2 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#ifndef _KLN89_PAGE_ALT
#define _KLN89_PAGE_ALT
#include "kln89.hxx"
class KLN89AltPage : public KLN89Page {
public:
KLN89AltPage(KLN89* parent);
~KLN89AltPage();
void Update(double dt);
//void AltPressed();
void CrsrPressed();
void EntPressed();
//void ClrPressed();
//void Knob1Left1();
//void Knob1Right1();
void Knob2Left1();
void Knob2Right1();
void LooseFocus();
};
#endif // _KLN89_PAGE_ALT

View File

@@ -0,0 +1,875 @@
// kln89_page_*.[ch]xx - this file is one of the "pages" that
// are used in the KLN89 GPS unit simulation.
//
// Written by David Luff, started 2005.
//
// Copyright (C) 2005 - David C Luff - daveluff AT ntlworld.com
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation; either version 2 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
//
// $Id$
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
#include "kln89_page_apt.hxx"
#include <simgear/structure/exception.hxx>
#include <cassert>
#include <cstdio>
#include <ATC/CommStation.hxx>
#include <Main/globals.hxx>
#include <Airports/runways.hxx>
#include <Airports/airport.hxx>
using std::string;
KLN89AptPage::KLN89AptPage(KLN89* parent)
: KLN89Page(parent) {
_nSubPages = 8;
_subPage = 0;
_name = "APT";
_apt_id = "KHWD";
// Make sure that _last_apt_id doesn't match at startup to force airport data to be fetched on first update.
_last_apt_id = "XXXX";
_nRwyPages = 1;
_curRwyPage = 0;
_nFreqPages = 1;
_curFreqPage = 0;
ap = NULL;
_iapStart = 0;
_iafStart = 0;
_fStart = 0;
_iaps.clear();
_iafDialog = false;
_addDialog = false;
_replaceDialog = false;
_curIap = 0;
_curIaf = 0;
}
KLN89AptPage::~KLN89AptPage() {
}
void KLN89AptPage::Update(double dt) {
bool actPage = (_kln89->_activePage->GetName() == "ACT" ? true : false);
bool multi; // Not set by FindFirst...
bool exact = false;
if(_apt_id.size() == 4) exact = true;
// TODO - move this search out to where the button is pressed, and cache the result!
if(_apt_id != _last_apt_id || ap == NULL) ap = _kln89->FindFirstAptById(_apt_id, multi, exact);
//if(np == NULL) cout << "NULL... ";
//if(b == false) cout << "false...\n";
/*
if(np && b) {
cout << "VOR FOUND!\n";
} else {
cout << ":-(\n";
}
*/
if(ap) {
//cout << "Valid airport found! id = " << ap->getId() << ", elev = " << ap->getElevation() << '\n';
if(_apt_id != _last_apt_id) {
UpdateAirport(ap->getId());
_last_apt_id = _apt_id;
_curFreqPage = 0;
_curRwyPage = 0;
}
_apt_id = ap->getId();
if(_kln89->GetActiveWaypoint()) {
if(_apt_id == _kln89->GetActiveWaypoint()->id) {
if(!(_kln89->_waypointAlert && _kln89->_blink)) {
// Active waypoint arrow
_kln89->DrawSpecialChar(4, 2, 0, 3);
}
}
}
if(_kln89->_mode != KLN89_MODE_CRSR) {
if(!(_subPage == 7 && (_iafDialog || _addDialog || _replaceDialog))) { // Don't draw the airport name when the IAP dialogs are active
if(!_entInvert) {
if(!actPage) {
_kln89->DrawText(ap->getId(), 2, 1, 3);
} else {
// If it's the ACT page, The ID is shifted slightly right to make space for the waypoint index.
_kln89->DrawText(ap->getId(), 2, 4, 3);
char buf[3];
int n = snprintf(buf, 3, "%i", _kln89->GetActiveWaypointIndex() + 1);
_kln89->DrawText((string)buf, 2, 3 - n, 3);
}
} else {
if(!_kln89->_blink) {
_kln89->DrawText(ap->getId(), 2, 1, 3, false, 99);
_kln89->DrawEnt();
}
}
}
}
if(_subPage == 0) {
// Name
_kln89->DrawText(ap->getName(), 2, 0, 2);
// Elevation
_kln89->DrawText(_kln89->_altUnits == GPS_ALT_UNITS_FT ? "ft" : "m", 2, 14, 3);
char buf[6];
int n = snprintf(buf, 5, "%i", (_kln89->_altUnits == GPS_ALT_UNITS_FT ? (int)(ap->getElevation()) : (int)((double)ap->getElevation() * SG_FEET_TO_METER)));
_kln89->DrawText((string)buf, 2, 14 - n, 3);
// Town
airport_id_str_map_iterator itr = _kln89->_airportTowns.find(_apt_id);
if(itr != _kln89->_airportTowns.end()) {
_kln89->DrawText(itr->second, 2, 0, 1);
}
// State / Province / Country
itr = _kln89->_airportStates.find(_apt_id);
if(itr != _kln89->_airportStates.end()) {
_kln89->DrawText(itr->second, 2, 0, 0);
}
} else if(_subPage == 1) {
_kln89->DrawLatitude(ap->getLatitude(), 2, 3, 2);
_kln89->DrawLongitude(ap->getLongitude(), 2, 3, 1);
_kln89->DrawDirDistField(ap->getLatitude() * SG_DEGREES_TO_RADIANS, ap->getLongitude() * SG_DEGREES_TO_RADIANS,
2, 0, 0, _to_flag, (_kln89->_mode == KLN89_MODE_CRSR && _uLinePos == 5 ? true : false));
} else if(_subPage == 2) {
// Try and calculate a realistic difference from UTC based on longitude
// Since 0 longitude is the middle of UTC, the boundaries will be at 7.5, 22.5, 37.5 etc.
int hrDiff = ((int)((fabs(ap->getLongitude())) + 7.5)) / 15;
_kln89->DrawText("UTC", 2, 0, 2);
if(hrDiff != 0) {
_kln89->DrawText(ap->getLongitude() >= 0.0 ? "+" : "-", 2, 3, 2);
char buf[10];
snprintf(buf, sizeof(buf), "%02i", hrDiff);
_kln89->DrawText((string)buf, 2, 4, 2);
_kln89->DrawText("( DT)", 2, 6, 2);
if(ap->getLongitude() >= 0.0) {
hrDiff++;
} else {
hrDiff--;
}
_kln89->DrawText(ap->getLongitude() >= 0.0 ? "+" : "-", 2, 7, 2);
snprintf(buf, sizeof(buf), "%02i", hrDiff);
_kln89->DrawText((string)buf, 2, 8, 2);
}
// I guess we can make a heuristic guess as to fuel availability from the runway sizes
// For now assume that airports with asphalt or concrete runways will have at least 100L,
// and that runways over 4000ft will have JET.
if(_aptRwys[0]->surface() <= 2) {
if(_aptRwys[0]->lengthFt() >= 4000) {
_kln89->DrawText("JET 100L", 2, 0, 1);
} else {
_kln89->DrawText("100L", 2, 0, 1);
}
}
if(_iaps.empty()) {
_kln89->DrawText("NO APR", 2, 0, 0);
} else {
// TODO - output proper differentiation of ILS and NP APR and NP APR type eg GPS(R)
_kln89->DrawText("NP APR", 2, 0, 0);
}
} else if(_subPage == 3) {
if(_nRwyPages > 1) {
_kln89->DrawChar('+', 1, 3, 0);
}
unsigned int i = _curRwyPage * 2;
string s;
if(i < _aptRwys.size()) {
// Rwy No.
string s = _aptRwys[i]->ident();
_kln89->DrawText(s, 2, 9, 3);
_kln89->DrawText("/", 2, 12, 3);
string recipIdent = _aptRwys[i]->reciprocalRunway()->ident();
_kln89->DrawText(recipIdent, 2, 13, 3);
// Length
s = GPSitoa(int(float(_aptRwys[i]->lengthFt()) * (_kln89->_altUnits == GPS_ALT_UNITS_FT ? 1.0 : SG_FEET_TO_METER) + 0.5));
_kln89->DrawText(s, 2, 5 - s.size(), 2);
_kln89->DrawText((_kln89->_altUnits == GPS_ALT_UNITS_FT ? "ft" : "m"), 2, 5, 2);
// Surface
// TODO - why not store these strings as an array?
switch(_aptRwys[i]->surface()) {
case 1:
// Asphalt - fall through
case 2:
// Concrete
_kln89->DrawText("HRD", 2, 9, 2);
break;
case 3:
case 8:
// Turf / Turf helipad
_kln89->DrawText("TRF", 2, 9, 2);
break;
case 4:
case 9:
// Dirt / Dirt helipad
_kln89->DrawText("DRT", 2, 9, 2);
break;
case 5:
// Gravel
_kln89->DrawText("GRV", 2, 9, 2);
break;
case 6:
// Asphalt helipad - fall through
case 7:
// Concrete helipad
_kln89->DrawText("HRD", 2, 9, 2);
break;
case 12:
// Lakebed
_kln89->DrawText("CLY", 2, 9, 2);
break;
default:
// erm? ...
_kln89->DrawText("MAT", 2, 9, 2);
}
}
i++;
if(i < _aptRwys.size()) {
// Rwy No.
string s = _aptRwys[i]->ident();
_kln89->DrawText(s, 2, 9, 1);
_kln89->DrawText("/", 2, 12, 1);
string recip = _aptRwys[i]->reciprocalRunway()->ident();
_kln89->DrawText(recip, 2, 13, 1);
// Length
s = GPSitoa(int(float(_aptRwys[i]->lengthFt()) * (_kln89->_altUnits == GPS_ALT_UNITS_FT ? 1.0 : SG_FEET_TO_METER) + 0.5));
_kln89->DrawText(s, 2, 5 - s.size(), 0);
_kln89->DrawText((_kln89->_altUnits == GPS_ALT_UNITS_FT ? "ft" : "m"), 2, 5, 0);
// Surface
// TODO - why not store these strings as an array?
switch(_aptRwys[i]->surface()) {
case 1:
// Asphalt - fall through
case 2:
// Concrete
_kln89->DrawText("HRD", 2, 9, 0);
break;
case 3:
case 8:
// Turf / Turf helipad
_kln89->DrawText("TRF", 2, 9, 0);
break;
case 4:
case 9:
// Dirt / Dirt helipad
_kln89->DrawText("DRT", 2, 9, 0);
break;
case 5:
// Gravel
_kln89->DrawText("GRV", 2, 9, 0);
break;
case 6:
// Asphalt helipad - fall through
case 7:
// Concrete helipad
_kln89->DrawText("HRD", 2, 9, 0);
break;
case 12:
// Lakebed
_kln89->DrawText("CLY", 2, 9, 0);
break;
default:
// erm? ...
_kln89->DrawText("MAT", 2, 9, 0);
}
}
} else if(_subPage == 4) {
if(_nFreqPages > 1) {
_kln89->DrawChar('+', 1, 3, 0);
}
unsigned int i = _curFreqPage * 3;
if(i < _aptFreqs.size()) {
_kln89->DrawText(_aptFreqs[i].service, 2, 0, 2);
_kln89->DrawFreq(_aptFreqs[i].freq, 2, 7, 2);
}
i++;
if(i < _aptFreqs.size()) {
_kln89->DrawText(_aptFreqs[i].service, 2, 0, 1);
_kln89->DrawFreq(_aptFreqs[i].freq, 2, 7, 1);
}
i++;
if(i < _aptFreqs.size()) {
_kln89->DrawText(_aptFreqs[i].service, 2, 0, 0);
_kln89->DrawFreq(_aptFreqs[i].freq, 2, 7, 0);
}
} else if(_subPage == 5) {
// TODO - user ought to be allowed to leave persistent remarks
_kln89->DrawText("[Remarks]", 2, 2, 2);
} else if(_subPage == 6) {
// We don't have SID/STAR database yet
// TODO
_kln89->DrawText("No SID/STAR", 2, 3, 2);
_kln89->DrawText("In Data Base", 2, 2, 1);
_kln89->DrawText("For This Airport", 2, 0, 0);
} else if(_subPage == 7) {
if(_iaps.empty()) {
_kln89->DrawText("IAP", 2, 11, 3);
_kln89->DrawText("No Approach", 2, 3, 2);
_kln89->DrawText("In Data Base", 2, 2, 1);
_kln89->DrawText("For This Airport", 2, 0, 0);
} else {
if(_iafDialog) {
_kln89->DrawText(_iaps[_curIap]->_ident, 2, 1, 3);
_kln89->DrawText(_iaps[_curIap]->_rwyStr, 2, 7, 3);
_kln89->DrawText(_iaps[_curIap]->_aptIdent, 2, 12, 3);
_kln89->DrawText("IAF", 2, 2, 2);
unsigned int line = 0;
for(unsigned int i=_iafStart; i<_approachRoutes.size(); ++i) {
if(line == 2) {
i = _approachRoutes.size() - 1;
}
// Assume that the IAF number is always single digit!
_kln89->DrawText(GPSitoa(i+1), 2, 6, 2-line);
if(!(_kln89->_mode == KLN89_MODE_CRSR && _kln89->_blink && _uLinePos == (line + 1))) {
_kln89->DrawText(_approachRoutes[i]->waypoints[0]->id, 2, 8, 2-line);
}
if(_kln89->_mode == KLN89_MODE_CRSR && _uLinePos == (line + 1) && !(_kln89->_blink )) {
_kln89->Underline(2, 8, 2-line, 5);
}
++line;
}
if(_uLinePos > 0 && !(_kln89->_blink)) {
_kln89->DrawEnt();
}
} else if(_addDialog) {
_kln89->DrawText(_iaps[_curIap]->_ident, 2, 1, 3);
_kln89->DrawText(_iaps[_curIap]->_rwyStr, 2, 7, 3);
_kln89->DrawText(_iaps[_curIap]->_aptIdent, 2, 12, 3);
string s = GPSitoa(_fStart + 1);
_kln89->DrawText(s, 2, 2-s.size(), 2);
s = GPSitoa(_kln89->_approachFP->waypoints.size());
_kln89->DrawText(s, 2, 2-s.size(), 1);
if(!(_uLinePos == _fStart+1 && _kln89->_blink)) {
_kln89->DrawText(_kln89->_approachFP->waypoints[_fStart]->id, 2, 4, 2);
if(_uLinePos == _fStart+1) _kln89->Underline(2, 4, 2, 6);
}
if(!(_uLinePos == _maxULinePos-1 && _kln89->_blink)) {
_kln89->DrawText(_kln89->_approachFP->waypoints[_kln89->_approachFP->waypoints.size()-1]->id, 2, 4, 1);
if(_uLinePos == _maxULinePos-1) _kln89->Underline(2, 4, 1, 6);
}
if(!(_uLinePos > _kln89->_approachFP->waypoints.size() && _kln89->_blink)) {
_kln89->DrawText("ADD TO FPL 0?", 2, 2, 0);
if(_uLinePos > _kln89->_approachFP->waypoints.size()) {
_kln89->Underline(2, 2, 0, 13);
_kln89->DrawEnt();
}
}
} else if(_replaceDialog) {
_kln89->DrawText(_iaps[_curIap]->_ident, 2, 1, 3);
_kln89->DrawText(_iaps[_curIap]->_rwyStr, 2, 7, 3);
_kln89->DrawText(_iaps[_curIap]->_aptIdent, 2, 12, 3);
_kln89->DrawText("Replace Existing", 2, 0, 2);
_kln89->DrawText("Approach", 2, 4, 1);
if(_uLinePos > 0 && !(_kln89->_blink)) {
_kln89->DrawText("APPROVE?", 2, 4, 0);
_kln89->Underline(2, 4, 0, 8);
_kln89->DrawEnt();
}
} else {
_kln89->DrawText("IAP", 2, 11, 3);
bool selApp = false;
if(_kln89->_mode == KLN89_MODE_CRSR && _uLinePos > 4) {
selApp = true;
if(!_kln89->_blink) _kln89->DrawEnt();
}
// _maxULine pos should be 4 + iaps.size() at this point.
// Draw a maximum of 3 IAPs.
// If there are more than 3 IAPs for this airport, then we need to offset the start
// of the list if _uLinePos is pointing at the 4th or later IAP.
unsigned int offset = 0;
unsigned int index;
if(_uLinePos > 7) {
offset = _uLinePos - 7;
}
for(unsigned int i=0; i<3; ++i) {
index = offset + i;
if(index < _iaps.size()) {
string s = GPSitoa(index+1);
_kln89->DrawText(s, 2, 2 - s.size(), 2-i);
if(!(selApp && _uLinePos == index+5 && _kln89->_blink)) {
_kln89->DrawText(_iaps[index]->_ident, 2, 3, 2-i);
_kln89->DrawText(_iaps[index]->_rwyStr, 2, 9, 2-i);
}
if(selApp && _uLinePos == index+5 && !_kln89->_blink) {
_kln89->Underline(2, 3, 2-i, 9);
}
} else {
break;
}
}
}
}
}
} else {
if(_kln89->_mode != KLN89_MODE_CRSR) _kln89->DrawText(_apt_id, 2, 1, 3);
if(_subPage == 0) {
/*
_kln89->DrawText("----.-", 2, 9, 3);
_kln89->DrawText("--------------", 2, 0, 2);
_kln89->DrawText("- -- --.--'", 2, 3, 1);
_kln89->DrawText("---- --.--'", 2, 3, 0);
_kln89->DrawSpecialChar(0, 2, 7, 1);
_kln89->DrawSpecialChar(0, 2, 7, 0);
*/
}
}
if(_kln89->_mode == KLN89_MODE_CRSR) {
if(!(_subPage == 7 && (_iafDialog || _addDialog || _replaceDialog))) {
if(_uLinePos > 0 && _uLinePos < 5) {
// TODO - blink as well
_kln89->Underline(2, _uLinePos, 3, 1);
}
for(unsigned int i = 0; i < _apt_id.size(); ++i) {
if(_uLinePos != (i + 1)) {
_kln89->DrawChar(_apt_id[i], 2, i + 1, 3);
} else {
if(!_kln89->_blink) _kln89->DrawChar(_apt_id[i], 2, i + 1, 3);
}
}
}
}
_id = _apt_id;
KLN89Page::Update(dt);
}
void KLN89AptPage::SetId(const string& s) {
if(s != _apt_id || s != _last_apt_id) {
UpdateAirport(s); // If we don't do this here we break things if s is the same as the current ID since the update wouldn't get called then.
/*
DCL: Hmmm - I wrote the comment above, but I don't quite understand it!
I'm not quite sure why I don't simply set _apt_id here (and NOT _last_apt_id)
and let the logic in Update(...) handle the airport details cache update.
*/
}
_last_apt_id = _apt_id;
_save_apt_id = _apt_id;
_apt_id = s;
}
// Update the cached airport details
void KLN89AptPage::UpdateAirport(const string& id) {
// Frequencies
_aptFreqs.clear();
const FGAirport* apt = fgFindAirportID(id);
if (!apt) {
throw sg_exception("UpdateAirport: unknown airport id " + id);
}
for (unsigned int c=0; c<apt->commStations().size(); ++c) {
flightgear::CommStation* comm = apt->commStations()[c];
AptFreq aq;
aq.freq = comm->freqKHz();
switch (comm->type()) {
case FGPositioned::FREQ_ATIS:
aq.service = "ATIS*"; break;
case FGPositioned::FREQ_GROUND:
aq.service = "GRND*"; break;
case FGPositioned::FREQ_TOWER:
aq.service = "TWR *"; break;
case FGPositioned::FREQ_APP_DEP:
aq.service = "APR *"; break;
default:
continue;
}
}
_nFreqPages = (unsigned int)ceil((float(_aptFreqs.size())) / 3.0f);
// Runways
_aptRwys.clear();
// build local array, longest runway first
for (unsigned int r=0; r<apt->numRunways(); ++r) {
FGRunway* rwy(apt->getRunwayByIndex(r));
if ((r > 0) && (rwy->lengthFt() > _aptRwys.front()->lengthFt())) {
_aptRwys.insert(_aptRwys.begin(), rwy);
} else {
_aptRwys.push_back(rwy);
}
}
_nRwyPages = (_aptRwys.size() + 1) / 2; // 2 runways per page.
if(_nFreqPages < 1) _nFreqPages = 1;
if(_nRwyPages < 1) _nRwyPages = 1;
// Instrument approaches
// Only non-precision for now - TODO - handle precision approaches if necessary
_iaps.clear();
iap_map_iterator itr = _kln89->_np_iap.find(id);
if(itr != _kln89->_np_iap.end()) {
_iaps = itr->second;
}
if(_subPage == 7) {
if(_iafDialog || _addDialog || _replaceDialog) {
// Eek - major logic error if an airport details cache update occurs
// with one of these dialogs active.
// TODO - output a warning.
//cout << "HELP!!!!!!!!!!\n";
} else {
_maxULinePos = 4 + _iaps.size(); // We shouldn't need to check the crsr for out-of-bounds here since we only update the airport details when the airport code is changed - ie. _uLinePos <= 4!
}
}
}
void KLN89AptPage::CrsrPressed() {
if(_kln89->_mode == KLN89_MODE_DISP) {
if(_subPage == 7) {
// Pressing crsr jumps back to vanilla IAP page.
_iafDialog = false;
_addDialog = false;
_replaceDialog = false;
}
return;
}
if(_kln89->_obsMode) {
_uLinePos = 0;
} else {
_uLinePos = 1;
}
if(_subPage == 0) {
_maxULinePos = 32;
} else if(_subPage == 7) {
// Don't *think* we need some of this since some of it we can only get to by pressing ENT, not CRSR.
if(_iafDialog) {
_maxULinePos = _approachRoutes.size();
_uLinePos = 1;
} else if(_addDialog) {
_maxULinePos = 1;
_uLinePos = 1;
} else if(_replaceDialog) {
_maxULinePos = 1;
_uLinePos = 1;
} else {
_maxULinePos = 4 + _iaps.size();
if(_iaps.empty()) {
_uLinePos = 1;
} else {
_uLinePos = 5;
}
}
} else {
_maxULinePos = 5;
}
}
void KLN89AptPage::ClrPressed() {
if(_subPage == 1 && _uLinePos == 5) {
_to_flag = !_to_flag;
} else if(_subPage == 7) {
// Clear backs out IAP selection one step at a time
if(_iafDialog) {
_iafDialog = false;
_maxULinePos = 4 + _iaps.size();
if(_iaps.empty()) {
_uLinePos = 1;
} else {
_uLinePos = 5;
}
} else if(_addDialog) {
_addDialog = false;
if(_approachRoutes.size() > 1) {
_iafDialog = true;
_maxULinePos = 1;
// Don't reset _curIaf since it is remembed.
_uLinePos = 1 + _curIaf; // TODO - make this robust to more than 3 IAF
} else {
_maxULinePos = 4 + _iaps.size();
if(_iaps.empty()) {
_uLinePos = 1;
} else {
_uLinePos = 5;
}
}
} else if(_replaceDialog) {
_replaceDialog = false;
_addDialog = true;
_maxULinePos = 1;
_uLinePos = 1;
}
}
}
void KLN89AptPage::EntPressed() {
if(_entInvert) {
_entInvert = false;
if(_kln89->_dtoReview) {
_kln89->DtoInitiate(_apt_id);
} else {
_last_apt_id = _apt_id;
_apt_id = _save_apt_id;
}
} else if(_subPage == 7 && _kln89->_mode == KLN89_MODE_CRSR && _uLinePos > 0) {
// We are selecting an approach
if(_iafDialog) {
if(_uLinePos > 0) {
// Record the IAF that was picked
if(_uLinePos == 3) {
_curIaf = _approachRoutes.size() - 1;
} else {
_curIaf = _uLinePos - 1 + _iafStart;
}
//cout << "_curIaf = " << _curIaf << '\n';
// TODO - delete the waypoints inside _approachFP before clearing them!!!!!!!
_kln89->_approachFP->waypoints.clear();
GPSWaypoint* wp = new GPSWaypoint;
*wp = *(_approachRoutes[_curIaf]->waypoints[0]); // Need to make copies here since we're going to alter ID and type sometimes
string iafid = wp->id;
_kln89->_approachFP->waypoints.push_back(wp);
for(unsigned int i=0; i<_IAP.size(); ++i) {
if(_IAP[i]->id != iafid) { // Don't duplicate waypoints that are part of the initial fix list and the approach procedure list.
// FIXME - allow the same waypoint to be both the IAF and the FAF in some
// approaches that have a procedure turn eg. KDLL
// Also allow MAF to be the same as IAF!
wp = new GPSWaypoint;
*wp = *_IAP[i];
//cout << "Adding waypoint " << wp->id << ", type is " << wp->appType << '\n';
//if(wp->appType == GPS_FAF) wp->id += 'f';
//if(wp->appType == GPS_MAP) wp->id += 'm';
//cout << "New id = " << wp->id << '\n';
_kln89->_approachFP->waypoints.push_back(wp);
}
}
_iafDialog = false;
_addDialog = true;
_maxULinePos = _kln89->_approachFP->waypoints.size() + 1;
_uLinePos = _maxULinePos;
}
} else if(_addDialog) {
if(_uLinePos == _maxULinePos) {
_addDialog = false;
if(_kln89->ApproachLoaded()) {
_replaceDialog = true;
_uLinePos = 1;
_maxULinePos = 1;
} else {
// Now load the approach into the active flightplan.
// As far as I can tell, the rules are this:
// If the airport of the approach is in the flightplan, insert it prior to this. (Not sure what happens if airport has already been passed).
// If the airport is not in the flightplan, append the approach to the flightplan, even if it is closer than the current active leg,
// in which case reorientate to flightplan might put us on the approach, but unable to activate it.
// However, it appears from the sim as if this can indeed happen if the user is not carefull.
bool added = false;
for(unsigned int i=0; i<_kln89->_activeFP->waypoints.size(); ++i) {
if(_kln89->_activeFP->waypoints[i]->id == _apt_id) {
_kln89->_activeFP->waypoints.insert(_kln89->_activeFP->waypoints.begin()+i, _kln89->_approachFP->waypoints.begin(), _kln89->_approachFP->waypoints.end());
added = true;
break;
}
}
if(!added) {
_kln89->_activeFP->waypoints.insert(_kln89->_activeFP->waypoints.end(), _kln89->_approachFP->waypoints.begin(), _kln89->_approachFP->waypoints.end());
}
_kln89->_approachID = _apt_id;
_kln89->_approachAbbrev = _iaps[_curIap]->_ident;
_kln89->_approachRwyStr = _iaps[_curIap]->_rwyStr;
_kln89->_approachLoaded = true;
//_kln89->_messageStack.push_back("*Press ALT To Set Baro");
// Actually - this message is only sent when we go into appraoch-arm mode.
// TODO - check the flightplan for consistency
_kln89->OrientateToActiveFlightPlan();
_kln89->_mode = KLN89_MODE_DISP;
_kln89->_curPage = 7;
_kln89->_activePage = _kln89->_pages[7]; // Do we need to clean up here at all before jumping?
}
}
} else if(_replaceDialog) {
// TODO - load the approach!
} else if(_uLinePos > 4) {
_approachRoutes.clear();
_IAP.clear();
_curIaf = 0;
_approachRoutes = ((FGNPIAP*)(_iaps[_uLinePos-5]))->_approachRoutes;
_IAP = ((FGNPIAP*)(_iaps[_uLinePos-5]))->_IAP;
_curIap = _uLinePos - 5; // TODO - handle the start of list ! no. 1, and the end of list not sequential!
_uLinePos = 1;
if(_approachRoutes.size() > 1) {
// More than 1 IAF - display the selection dialog
_iafDialog = true;
_maxULinePos = _approachRoutes.size();
} else {
// There is only 1 IAF, so load the waypoints into the approach flightplan here.
// TODO - there is nasty code duplication loading the approach FP between the case here where we have only one
// IAF and the case where we must choose the IAF from a list. Try to tidy this after it is all working properly.
_kln89->_approachFP->waypoints.clear();
GPSWaypoint* wp = new GPSWaypoint;
*wp = *(_approachRoutes[0]->waypoints[0]); // Need to make copies here since we're going to alter ID and type sometimes
string iafid = wp->id;
_kln89->_approachFP->waypoints.push_back(wp);
for(unsigned int i=0; i<_IAP.size(); ++i) {
if(_IAP[i]->id != iafid) { // Don't duplicate waypoints that are part of the initial fix list and the approach procedure list.
// FIXME - allow the same waypoint to be both the IAF and the FAF in some
// approaches that have a procedure turn eg. KDLL
// Also allow MAF to be the same as IAF!
wp = new GPSWaypoint;
*wp = *_IAP[i];
_kln89->_approachFP->waypoints.push_back(wp);
}
}
_addDialog = true;
_maxULinePos = 1;
}
}
}
}
void KLN89AptPage::Knob1Left1() {
if(_kln89->_mode == KLN89_MODE_CRSR && _subPage == 7 && _addDialog) {
if(_uLinePos == _maxULinePos) {
_uLinePos--;
if(_kln89->_approachFP->waypoints.size() > 1) _fStart = _kln89->_approachFP->waypoints.size() - 2;
} else if(_uLinePos == _maxULinePos - 1) {
_uLinePos--;
} else if(_uLinePos > 0) {
if(_fStart == 0) {
_uLinePos--;
} else {
_uLinePos--;
_fStart--;
}
}
} else {
KLN89Page::Knob1Left1();
}
}
void KLN89AptPage::Knob1Right1() {
if(_kln89->_mode == KLN89_MODE_CRSR && _subPage == 7 && _addDialog) {
if(_uLinePos == _maxULinePos) {
// no-op
} else if(_uLinePos == _maxULinePos - 1) {
_uLinePos++;
_fStart = 0;
} else if(_uLinePos > 0) {
if(_fStart >= _kln89->_approachFP->waypoints.size() - 2) {
_uLinePos++;
} else {
_uLinePos++;
_fStart++;
}
} else if(_uLinePos == 0) {
_uLinePos++;
_fStart = 0;
}
} else {
KLN89Page::Knob1Right1();
}
}
void KLN89AptPage::Knob2Left1() {
if(_kln89->_mode != KLN89_MODE_CRSR || _uLinePos == 0) {
if(_uLinePos == 0 && _kln89->_mode == KLN89_MODE_CRSR && _kln89->_obsMode) {
KLN89Page::Knob2Left1();
} else if(_subPage == 5) {
_subPage = 4;
_curFreqPage = _nFreqPages - 1;
} else if(_subPage == 4) {
// Freqency pages
if(_curFreqPage == 0) {
_subPage = 3;
_curRwyPage = _nRwyPages - 1;
} else {
_curFreqPage--;
}
} else if(_subPage == 3) {
if(_curRwyPage == 0) {
KLN89Page::Knob2Left1();
} else {
_curRwyPage--;
}
} else if(_subPage == 0) {
_subPage = 7;
// We have to set _uLinePos here even though the cursor isn't pressed, to
// ensure that the list displays properly.
if(_iaps.empty()) {
_uLinePos = 1;
} else {
_uLinePos = 5;
}
} else {
KLN89Page::Knob2Left1();
}
} else {
if(_uLinePos < 5 && !(_subPage == 7 && (_iafDialog || _addDialog || _replaceDialog))) {
// Same logic for all pages - set the ID
_apt_id = _apt_id.substr(0, _uLinePos);
// ASSERT(_uLinePos > 0);
if(_uLinePos == (_apt_id.size() + 1)) {
_apt_id += '9';
} else {
_apt_id[_uLinePos - 1] = _kln89->DecChar(_apt_id[_uLinePos - 1], (_uLinePos == 1 ? false : true));
}
} else {
if(_subPage == 0) {
// TODO - set by name
} else {
// NO-OP - to/fr is cycled by clr button
}
}
}
}
void KLN89AptPage::Knob2Right1() {
if(_kln89->_mode != KLN89_MODE_CRSR || _uLinePos == 0) {
if(_uLinePos == 0 && _kln89->_mode == KLN89_MODE_CRSR && _kln89->_obsMode) {
KLN89Page::Knob2Right1();
} else if(_subPage == 2) {
_subPage = 3;
_curRwyPage = 0;
} else if(_subPage == 3) {
if(_curRwyPage == _nRwyPages - 1) {
_subPage = 4;
_curFreqPage = 0;
} else {
_curRwyPage++;
}
} else if(_subPage == 4) {
if(_curFreqPage == _nFreqPages - 1) {
_subPage = 5;
} else {
_curFreqPage++;
}
} else if(_subPage == 6) {
_subPage = 7;
// We have to set _uLinePos here even though the cursor isn't pressed, to
// ensure that the list displays properly.
if(_iaps.empty()) {
_uLinePos = 1;
} else {
_uLinePos = 5;
}
} else {
KLN89Page::Knob2Right1();
}
} else {
if(_uLinePos < 5 && !(_subPage == 7 && (_iafDialog || _addDialog || _replaceDialog))) {
// Same logic for all pages - set the ID
_apt_id = _apt_id.substr(0, _uLinePos);
// ASSERT(_uLinePos > 0);
if(_uLinePos == (_apt_id.size() + 1)) {
_apt_id += 'A';
} else {
_apt_id[_uLinePos - 1] = _kln89->IncChar(_apt_id[_uLinePos - 1], (_uLinePos == 1 ? false : true));
}
} else {
if(_subPage == 0) {
// TODO - set by name
} else {
// NO-OP - to/fr is cycled by clr button
}
}
}
}

View File

@@ -0,0 +1,95 @@
// kln89_page_*.[ch]xx - this file is one of the "pages" that
// are used in the KLN89 GPS unit simulation.
//
// Written by David Luff, started 2005.
//
// Copyright (C) 2005 - David C Luff - daveluff AT ntlworld.com
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation; either version 2 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
//
// $Id$
#ifndef _KLN89_PAGE_APT
#define _KLN89_PAGE_APT
#include "kln89.hxx"
class FGRunway;
struct AptFreq {
std::string service;
unsigned short int freq;
};
class KLN89AptPage : public KLN89Page {
public:
KLN89AptPage(KLN89* parent);
~KLN89AptPage();
void Update(double dt);
void CrsrPressed();
void ClrPressed();
void EntPressed();
void Knob1Left1();
void Knob1Right1();
void Knob2Left1();
void Knob2Right1();
void SetId(const std::string& s);
private:
// Update the cached airport details
void UpdateAirport(const std::string& id);
std::string _apt_id;
std::string _last_apt_id;
std::string _save_apt_id;
const FGAirport* ap;
vector<FGRunway*> _aptRwys;
vector<AptFreq> _aptFreqs;
iap_list_type _iaps;
unsigned int _curIap; // The index into _iaps of the IAP we are currently selecting
vector<GPSFlightPlan*> _approachRoutes; // The approach route(s) from the IAF(s) to the IF.
vector<GPSWaypoint*> _IAP; // The compulsory waypoints of the approach procedure (may duplicate one of the above).
// _IAP includes the FAF and MAF.
vector<GPSWaypoint*> _MAP; // The missed approach procedure (doesn't include the MAF).
unsigned int _curIaf; // The index into _approachRoutes of the IAF we are currently selecting, and then remembered as the one we selected
// Position in rwy pages
unsigned int _curRwyPage;
unsigned int _nRwyPages;
// Position in freq pages
unsigned int _curFreqPage;
unsigned int _nFreqPages;
// Position in IAP list (0-based number of first IAP displayed)
unsigned int _iapStart;
// ditto for IAF list (can't test this since can't find an approach with > 3 IAF at the moment!)
unsigned int _iafStart;
// ditto for list of approach fixes when asking load confirmation
unsigned int _fStart;
// Various IAP related dialog states that we might need to remember
bool _iafDialog;
bool _addDialog;
bool _replaceDialog;
};
#endif // _KLN89_PAGE_APT

View File

@@ -0,0 +1,338 @@
// kln89_page_*.[ch]xx - this file is one of the "pages" that
// are used in the KLN89 GPS unit simulation.
//
// Written by David Luff, started 2005.
//
// Copyright (C) 2005 - David C Luff: daveluff --AT-- ntlworld --D0T-- com
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation; either version 2 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
//
// $Id$
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
#include <cstdlib>
#include <cstdio>
#include <Main/fg_props.hxx>
#include "kln89_page_cal.hxx"
using std::string;
KLN89CalPage::KLN89CalPage(KLN89* parent)
: KLN89Page(parent) {
_nSubPages = 8;
_subPage = 0;
_name = "CAL";
_nFp0 = 0;
_ground_speed_ms = 110 * 0.514444444444;
_alarmAnnotate = false;
_alarmSet = false;
}
KLN89CalPage::~KLN89CalPage() {
}
void KLN89CalPage::Update(double dt) {
bool crsr = (_kln89->_mode == KLN89_MODE_CRSR);
bool blink = _kln89->_blink;
if(_subPage == 0) {
if(1) { // TODO - fix this hardwiring!
// Flightplan calc
_kln89->DrawText(">Fpl:", 2, 0, 3);
_kln89->DrawText("0", 2, 6, 3);// TODO - fix this hardwiring!
GPSFlightPlan* fp = _kln89->_flightPlans[_nFp0];
if(fp) {
unsigned int n = fp->waypoints.size();
if(n < 2) {
// TODO - check that this is what really happens
_kln89->DrawText("----", 2, 9, 3);
_kln89->DrawText("----", 2, 9, 2);
} else {
_kln89->DrawText(fp->waypoints[0]->id, 2, 9, 3);
_kln89->DrawText(fp->waypoints[n-1]->id, 2, 9, 2);
double cum_tot_m = 0.0;
for(unsigned int i = 1; i < fp->waypoints.size(); ++i) {
cum_tot_m += _kln89->GetGreatCircleDistance(fp->waypoints[i-1]->lat, fp->waypoints[i-1]->lon,
fp->waypoints[i]->lat, fp->waypoints[i]->lon);
}
double ete = (cum_tot_m * SG_NM_TO_METER) / _ground_speed_ms;
_kln89->DrawDist(cum_tot_m, 2, 5, 1);
_kln89->DrawSpeed(_ground_speed_ms / 0.5144444444, 2, 5, 0);
_kln89->DrawTime(ete, 2, 14, 0);
}
} else {
_kln89->DrawText("----", 2, 9, 3);
_kln89->DrawText("----", 2, 9, 2);
}
} else {
_kln89->DrawText(">Wpt:", 2, 0, 3);
}
_kln89->DrawText("To", 2, 6, 2);
_kln89->DrawText("ESA ----'", 2, 7, 1); // TODO - implement an ESA calc
_kln89->DrawText("ETE", 2, 7, 0);
} else if(_subPage == 1) {
_kln89->DrawText(">Fpl: 0", 2, 0, 3); // TODO - fix this hardwiring!
_kln89->DrawText("FF:", 2, 0, 2);
_kln89->DrawText("Res:", 2, 7, 1);
_kln89->DrawText("Fuel Req", 2, 0, 0);
} else if(_subPage == 2) {
_kln89->DrawText("Time:", 2, 0, 3);
// TODO - hardwired to UTC at the moment
if(!(_uLinePos == 1 && crsr && blink)) { _kln89->DrawText("UTC", 2, 6, 3); }
if(_uLinePos == 1 && crsr) { _kln89->Underline(2, 6, 3, 3); }
string th = fgGetString("/instrumentation/clock/indicated-hour");
string tm = fgGetString("/instrumentation/clock/indicated-min");
ClockTime t(std::stoi(th), std::stoi(tm));
if(th.size() == 1) th = "0" + th;
if(tm.size() == 1) tm = "0" + tm;
_kln89->DrawText(th + tm, 2, 11, 3);
char buf[12];
_kln89->DrawText("Alarm at:", 2, 0, 2);
_kln89->DrawText("in:", 2, 6, 1);
if(_alarmAnnotate) {
_alarmIn = _alarmTime - t;
snprintf(buf, 5, "%02i", _alarmTime.hr());
if(!(_uLinePos == 2 && crsr && blink)) { _kln89->DrawText((string)buf, 2, 11, 2); }
snprintf(buf, 5, "%02i", _alarmTime.min());
if(!(_uLinePos == 3 && crsr && blink)) { _kln89->DrawText((string)buf, 2, 13, 2); }
} else {
if(!(_uLinePos == 2 && crsr && blink)) { _kln89->DrawText("--", 2, 11, 2); }
if(!(_uLinePos == 3 && crsr && blink)) { _kln89->DrawText("--", 2, 13, 2); }
}
if(_alarmAnnotate && _alarmIn.hr() < 10) {
snprintf(buf, sizeof(buf), "%01i", _alarmIn.hr());
if(!(_uLinePos == 4 && crsr && blink)) { _kln89->DrawText((string)buf, 2, 11, 1); }
snprintf(buf, sizeof(buf), "%02i", _alarmIn.min());
if(!(_uLinePos == 5 && crsr && blink)) { _kln89->DrawText((string)buf, 2, 13, 1); }
} else {
if(!(_uLinePos == 4 && crsr && blink)) { _kln89->DrawText("-", 2, 11, 1); }
if(!(_uLinePos == 5 && crsr && blink)) { _kln89->DrawText("--", 2, 13, 1); }
}
_kln89->DrawText(":", 2, 12, 1);
if(crsr) {
if(_uLinePos == 2) { _kln89->Underline(2, 11, 2, 2); }
if(_uLinePos == 3) { _kln89->Underline(2, 13, 2, 2); }
if(_uLinePos == 4) { _kln89->Underline(2, 11, 1, 1); }
if(_uLinePos == 5) { _kln89->Underline(2, 13, 1, 2); }
}
// TODO
_kln89->DrawText("Elapsed", 2, 0, 0);
ClockTime te = t - _kln89->_powerOnTime;
// There is the possibility that when we reset it we may end up a minute ahead of the
// alarm time for a second, so treat 23:59 as 0.00
if(te.hr() == 23 && te.min() == 59) {
te.set_hr(0);
te.set_min(0);
}
if(!(_uLinePos == 6 && crsr && blink)) {
if(te.hr() > 9) {
// The elapsed time blanks out
// when past 9:59 on the kln89 sim.
_kln89->DrawText("-:--", 2, 11, 0);
} else {
snprintf(buf, 5, "%01i:%02i", te.hr(), te.min());
_kln89->DrawText((string)buf, 2, 11, 0);
}
}
if(_uLinePos == 6 && crsr) { _kln89->Underline(2, 11, 0, 4); }
} else if(_subPage == 3) {
_kln89->DrawText("PRESSURE ALT", 2, 1, 3);
_kln89->DrawText("Ind:", 2, 0, 2);
_kln89->DrawText("Baro:", 2, 0, 1);
_kln89->DrawText("Prs", 2, 0, 0);
} else if(_subPage == 4) {
_kln89->DrawText("DENSITY ALT", 2, 1, 3);
_kln89->DrawText("Prs:", 2, 0, 2);
_kln89->DrawText("Temp:", 2, 0, 1);
_kln89->DrawText("Den", 2, 0, 0);
} else if(_subPage == 5) {
_kln89->DrawText("CAS:", 2, 0, 3);
_kln89->DrawText("Prs:", 2, 0, 2);
_kln89->DrawText("Temp:", 2, 0, 1);
_kln89->DrawText("TAS", 2, 0, 0);
} else if(_subPage == 6) {
_kln89->DrawText("TAS:", 2, 0, 3);
_kln89->DrawText("Hdg:", 2, 0, 2);
_kln89->DrawText("Headwind:", 2, 0, 1);
_kln89->DrawText("True", 2, 4, 0);
} else {
_kln89->DrawText("SUNRISE", 2, 0, 1);
_kln89->DrawText("SUNSET", 2, 0, 0);
}
KLN89Page::Update(dt);
}
void KLN89CalPage::CrsrPressed() {
if(_kln89->_obsMode) {
_uLinePos = 0;
} else {
_uLinePos = 1;
}
if(_subPage == 2) {
_maxULinePos = 6;
}
}
void KLN89CalPage::ClrPressed() {
if(_kln89->_mode != KLN89_MODE_CRSR) {
KLN89Page::ClrPressed();
}
if(_subPage == 2 && _uLinePos == 6) {
_kln89->ResetPowerOnTimer();
} else {
KLN89Page::ClrPressed();
}
}
void KLN89CalPage::Knob2Left1() {
if(_kln89->_mode != KLN89_MODE_CRSR) {
KLN89Page::Knob2Left1();
return;
}
if(_subPage == 2) {
if(_uLinePos == 1) {
// TODO - allow time zone to be changed
} else if(_uLinePos == 2) {
ClockTime t(1,0);
if(_alarmAnnotate) {
_alarmTime = _alarmTime - t;
} else {
_alarmTime.set_hr(std::stoi(fgGetString("/instrumentation/clock/indicated-hour")));
_alarmTime.set_min(std::stoi(fgGetString("/instrumentation/clock/indicated-min")));
_alarmTime = _alarmTime - t;
_alarmAnnotate = true;
}
_alarmSet = true;
} else if(_uLinePos == 3) {
ClockTime t(0,1);
if(_alarmAnnotate) {
_alarmTime = _alarmTime - t;
} else {
_alarmTime.set_hr(std::stoi(fgGetString("/instrumentation/clock/indicated-hour")));
_alarmTime.set_min(std::stoi(fgGetString("/instrumentation/clock/indicated-min")));
_alarmTime = _alarmTime - t;
_alarmAnnotate = true;
}
_alarmSet = true;
} else if(_uLinePos == 4) {
ClockTime t(1,0);
// If the _alarmIn time is dashed out due to being > 9:59
// then changing it starts from zero again.
if(_alarmAnnotate && _alarmIn.hr() < 10) {
_alarmIn = _alarmIn - t;
if(_alarmIn.hr() > 9) { _alarmIn.set_hr(9); }
} else {
_alarmIn.set_hr(9);
_alarmIn.set_min(0);
_alarmAnnotate = true;
}
_alarmSet = true;
t.set_hr(std::stoi(fgGetString("/instrumentation/clock/indicated-hour")));
t.set_min(std::stoi(fgGetString("/instrumentation/clock/indicated-min")));
_alarmTime = t + _alarmIn;
} else if(_uLinePos == 5) {
ClockTime t(0,1);
if(_alarmAnnotate && _alarmIn.hr() < 10) {
_alarmIn = _alarmIn - t;
if(_alarmIn.hr() > 9) { _alarmIn.set_hr(9); }
} else {
_alarmIn.set_hr(9);
_alarmIn.set_min(59);
_alarmAnnotate = true;
}
_alarmSet = true;
t.set_hr(std::stoi(fgGetString("/instrumentation/clock/indicated-hour")));
t.set_min(std::stoi(fgGetString("/instrumentation/clock/indicated-min")));
_alarmTime = t + _alarmIn;
}
}
}
void KLN89CalPage::Knob2Right1() {
if(_kln89->_mode != KLN89_MODE_CRSR) {
KLN89Page::Knob2Right1();
return;
}
if(_subPage == 2) {
if(_uLinePos == 1) {
// TODO - allow time zone to be changed
} else if(_uLinePos == 2) {
ClockTime t(1,0);
if(_alarmAnnotate) {
_alarmTime = _alarmTime + t;
} else {
_alarmTime.set_hr(std::stoi(fgGetString("/instrumentation/clock/indicated-hour")));
_alarmTime.set_min(std::stoi(fgGetString("/instrumentation/clock/indicated-min")));
_alarmTime = _alarmTime + t;
_alarmAnnotate = true;
}
_alarmSet = true;
} else if(_uLinePos == 3) {
ClockTime t(0,1);
if(_alarmAnnotate) {
_alarmTime = _alarmTime + t;
} else {
_alarmTime.set_hr(std::stoi(fgGetString("/instrumentation/clock/indicated-hour")));
_alarmTime.set_min(std::stoi(fgGetString("/instrumentation/clock/indicated-min")));
_alarmTime = _alarmTime + t;
_alarmAnnotate = true;
}
_alarmSet = true;
} else if(_uLinePos == 4) {
ClockTime t(1,0);
if(_alarmAnnotate && _alarmIn.hr() < 10) {
_alarmIn = _alarmIn + t;
if(_alarmIn.hr() > 9) { _alarmIn.set_hr(0); }
} else {
_alarmIn.set_hr(1);
_alarmIn.set_min(0);
_alarmAnnotate = true;
}
_alarmSet = true;
t.set_hr(std::stoi(fgGetString("/instrumentation/clock/indicated-hour")));
t.set_min(std::stoi(fgGetString("/instrumentation/clock/indicated-min")));
_alarmTime = t + _alarmIn;
} else if(_uLinePos == 5) {
ClockTime t(0,1);
if(_alarmAnnotate && _alarmIn.hr() < 10) {
_alarmIn = _alarmIn + t;
if(_alarmIn.hr() > 9) { _alarmIn.set_hr(0); }
} else {
_alarmIn.set_hr(0);
_alarmIn.set_min(1);
_alarmAnnotate = true;
}
_alarmSet = true;
t.set_hr(std::stoi(fgGetString("/instrumentation/clock/indicated-hour")));
t.set_min(std::stoi(fgGetString("/instrumentation/clock/indicated-min")));
_alarmTime = t + _alarmIn;
}
}
}
void KLN89CalPage::LooseFocus() {
if(_alarmSet) {
_kln89->SetAlarm(_alarmTime.hr(), _alarmTime.min());
_alarmSet = false;
}
}

View File

@@ -0,0 +1,78 @@
// kln89_page_*.[ch]xx - this file is one of the "pages" that
// are used in the KLN89 GPS unit simulation.
//
// Written by David Luff, started 2005.
//
// Copyright (C) 2005 - David C Luff: daveluff --AT-- ntlworld --D0T-- com
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation; either version 2 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
//
// $Id$
#ifndef _KLN89_PAGE_CAL
#define _KLN89_PAGE_CAL
#include "kln89.hxx"
class KLN89CalPage : public KLN89Page {
public:
KLN89CalPage(KLN89* parent);
~KLN89CalPage();
void Update(double dt);
void CrsrPressed();
void ClrPressed();
void Knob2Left1();
void Knob2Right1();
void LooseFocus();
private:
unsigned int _nFp0; // flightplan no. displayed on page 1 (_subPage == 0).
double _ground_speed_ms; // Assumed ground speed for ete calc on page 1 (user can alter this).
// CAL3 (alarm) page
ClockTime _alarmTime;
ClockTime _alarmIn;
// _alarmAnnotate shows that the alarm has been set at least once
// so the time should now be always annotated (there seems to be
// no way to remove it once set once!).
bool _alarmAnnotate;
// _alarmSet indicates that the alarm has been changed by the user
// and should be set in the main unit when the page looses focus
// (I don't think the alarm goes off unless the user leaves the
// CAL3 page after setting it).
bool _alarmSet;
// Calculate the alarm time based on the alarm-in value
void CalcAlarmTime();
// Calculate alarm-in based on the alarm time.
void CalcAlarmIn();
// Calculate the difference between 2 hr:min times.
// It is assumed that the second time is always later than the first one
// ie. that the day has wrapped if the second one is less,
// but is limited to intervals of < 24hr.
void TimeDiff(int hr1, int min1, int hr2, int min2, int &hrDiff, int &minDiff);
};
inline std::ostream& operator<< (std::ostream& out, const ClockTime& t) {
return(out << t._hr << ':' << t._min);
}
#endif // _KLN89_PAGE_CAL

View File

@@ -0,0 +1,312 @@
// kln89_page_*.[ch]xx - this file is one of the "pages" that
// are used in the KLN89 GPS unit simulation.
//
// Written by David Luff, started 2005.
//
// Copyright (C) 2005 - David C Luff - daveluff AT ntlworld.com
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation; either version 2 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
//
// $Id$
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
#include "kln89_page_dir.hxx"
#include <Main/fg_props.hxx>
using std::string;
KLN89DirPage::KLN89DirPage(KLN89* parent)
: KLN89Page(parent) {
_nSubPages = 1;
_subPage = 0;
_name = "DIR";
_maxULinePos = 4;
_DToWpDispMode = 2;
}
KLN89DirPage::~KLN89DirPage() {
}
void KLN89DirPage::Update(double dt) {
// TODO - this can apparently be "ACTIVATE:" under some circumstances
_kln89->DrawText("DIRECT TO:", 2, 2, 3);
if(_kln89->_mode == KLN89_MODE_CRSR) {
string s = _id;
while(s.size() < 5) s += ' ';
if(_DToWpDispMode == 0) {
if(!_kln89->_blink) {
_kln89->DrawText(s, 2, 4, 1, false, 99);
_kln89->DrawEnt(1, 0, 1);
}
} else if(_DToWpDispMode == 1) {
if(!_kln89->_blink) {
_kln89->DrawText(s, 2, 4, 1, false, _uLinePos);
_kln89->DrawEnt(1, 0, 1);
}
_kln89->Underline(2, 4, 1, 5);
} else {
if(!_kln89->_blink) _kln89->DrawText("_____", 2, 4, 1);
_kln89->Underline(2, 4, 1, 5);
}
} else {
_kln89->DrawText("_____", 2, 4, 1);
}
KLN89Page::Update(dt);
}
// This can only be called from the KLN89 when DTO is pressed from outside of the DIR page.
// DO NOT USE IT to set _id internally from the DIR page, since it initialises various state
// based on the assumption that the DIR page is being first entered.
void KLN89DirPage::SetId(const string& s) {
if(s.size()) {
_id = s;
_DToWpDispMode = 0;
if(!_kln89->_activeFP->IsEmpty()) {
_DToWpDispIndex = (int)_kln89->_activeFP->waypoints.size() - 1;
}
} else {
_DToWpDispMode = 2;
}
_saveMasterMode = _kln89->_mode;
_uLinePos = 1; // Needed to stop Leg flashing
}
void KLN89DirPage::CrsrPressed() {
// Pressing CRSR clears the ID field (from sim).
_DToWpDispMode = 2;
}
void KLN89DirPage::ClrPressed() {
if(_kln89->_mode == KLN89_MODE_CRSR) {
if(_DToWpDispMode <= 1) {
_DToWpDispMode = 2;
_id.clear();
} else {
// Restore the original master mode
_kln89->_mode = _saveMasterMode;
// Stop displaying dir page
_kln89->_activePage = _kln89->_pages[_kln89->_curPage];
}
} else {
// TODO
}
}
void KLN89DirPage::EntPressed() {
// Trim any RH whitespace from _id
while(!_id.empty()) {
if(_id[_id.size()-1] == ' ') {
_id = _id.substr(0, _id.size()-1);
} else {
// Important to break, since usr waypoint names may contain space.
break;
}
}
if(_DToWpDispMode == 2 || _id.empty()) {
_kln89->DtoCancel();
} else {
if(_DToWpDispMode == 0) {
// It's a waypoint from the active flightplan - these get processed without data page review.
_kln89->DtoInitiate(_id);
} else {
// Display the appropriate data page for review (USR page if the ident is not currently valid)
_kln89->_dtoReview = true;
GPSWaypoint* wp = _kln89->FindFirstByExactId(_id);
if(wp) {
// Set the current page to be the appropriate data page
_kln89->_curPage = wp->type;
delete wp;
} else {
// Set the current page to be the user page
_kln89->_curPage = 4;
}
// set the page ID and entInvert, and activate the current page.
_kln89->_activePage = _kln89->_pages[_kln89->_curPage];
_kln89->_activePage->SetId(_id);
_kln89->_activePage->SetEntInvert(true);
}
}
}
void KLN89DirPage::Knob2Left1() {
if(_kln89->_mode == KLN89_MODE_CRSR) {
if(fgGetBool("/instrumentation/kln89/scan-pull")) {
if(_DToWpDispMode == 2) {
if(!_kln89->_activeFP->IsEmpty()) {
// Switch to mode 0, set the position to the end of the active flightplan *and* run the mode 0 case.
_DToWpDispMode = 0;
_DToWpDispIndex = (int)_kln89->_activeFP->waypoints.size() - 1;
}
}
if(_DToWpDispMode == 0) {
// If the knob is pulled out, then the unit cycles through the waypoints of the active flight plan
// (This is deduced from the Bendix-King sim, I haven't found it documented in the pilot guide).
// If the active flight plan is empty it clears the field (this is possible, e.g. if a data page was
// active when DTO was pressed).
if(!_kln89->_activeFP->IsEmpty()) {
if(_DToWpDispIndex == 0) {
_DToWpDispIndex = (int)_kln89->_activeFP->waypoints.size() - 1;
} else {
_DToWpDispIndex--;
}
_id = _kln89->_activeFP->waypoints[_DToWpDispIndex]->id;
} else {
_DToWpDispMode = 2;
}
}
// _DToWpDispMode == 1 is a NO-OP when the knob is out.
} else {
if(_DToWpDispMode == 0) {
// If the knob is not pulled out, then turning it transitions the DIR page to the waypoint selection mode
// and sets the waypoint to the first beginning with '9'
_id = "9";
GPSWaypoint* wp = _kln89->FindFirstById(_id);
if(wp) {
_id = wp->id;
delete wp;
}
_uLinePos = 0;
_DToWpDispMode = 1;
} else if(_DToWpDispMode == 1) {
while(_id.size() < (_uLinePos + 1)) {
_id += ' ';
}
char ch = _id[_uLinePos];
if(ch == ' ') {
ch = '9';
} else if(ch == '0') {
ch = 'Z';
} else if(ch == 'A') {
// It seems that blanks are allowed within the name, but not for the first character
if(_uLinePos == 0) {
ch = '9';
} else {
ch = ' ';
}
} else {
ch--;
}
_id[_uLinePos] = ch;
GPSWaypoint* wp = _kln89->FindFirstById(_id.substr(0, _uLinePos+1));
if(wp) {
_id = wp->id;
delete wp;
}
} else {
_id = "9";
GPSWaypoint* wp = _kln89->FindFirstById(_id);
if(wp) {
_id = wp->id;
delete wp;
}
_uLinePos = 0;
_DToWpDispMode = 1;
}
}
} else {
// If the cursor is not displayed, then we return to the page that was displayed prior to DTO being pressed,
// and pass the knob turn to that page, whether pulled out or not.
_kln89->_activePage = _kln89->_pages[_kln89->_curPage];
_kln89->_activePage->Knob2Left1();
}
}
void KLN89DirPage::Knob2Right1() {
if(_kln89->_mode == KLN89_MODE_CRSR) {
if(fgGetBool("/instrumentation/kln89/scan-pull")) {
if(_DToWpDispMode == 2) {
if(!_kln89->_activeFP->IsEmpty()) {
// Switch to mode 0, set the position to the end of the active flightplan *and* run the mode 0 case.
_DToWpDispMode = 0;
_DToWpDispIndex = (int)_kln89->_activeFP->waypoints.size() - 1;
}
}
if(_DToWpDispMode == 0) {
// If the knob is pulled out, then the unit cycles through the waypoints of the active flight plan
// (This is deduced from the Bendix-King sim, I haven't found it documented in the pilot guide).
// If the active flight plan is empty it clears the field (this is possible, e.g. if a data page was
// active when DTO was pressed).
if(!_kln89->_activeFP->IsEmpty()) {
if(_DToWpDispIndex == (int)_kln89->_activeFP->waypoints.size() - 1) {
_DToWpDispIndex = 0;
} else {
_DToWpDispIndex++;
}
_id = _kln89->_activeFP->waypoints[_DToWpDispIndex]->id;
} else {
_DToWpDispMode = 2;
}
}
// _DToWpDispMode == 1 is a NO-OP when the knob is out.
} else {
if(_DToWpDispMode == 0) {
// If the knob is not pulled out, then turning it transitions the DIR page to the waypoint selection mode
// and sets the waypoint to the first beginning with 'A'
_id = "A";
GPSWaypoint* wp = _kln89->FindFirstById(_id);
if(wp) {
_id = wp->id;
delete wp;
}
_uLinePos = 0;
_DToWpDispMode = 1;
} else if(_DToWpDispMode == 1) {
while(_id.size() < (_uLinePos + 1)) {
_id += ' ';
}
char ch = _id[_uLinePos];
if(ch == ' ') {
ch = 'A';
} else if(ch == 'Z') {
ch = '0';
} else if(ch == '9') {
// It seems that blanks are allowed within the name, but not for the first character
if(_uLinePos == 0) {
ch = 'A';
} else {
ch = ' ';
}
} else {
ch++;
}
_id[_uLinePos] = ch;
GPSWaypoint* wp = _kln89->FindFirstById(_id.substr(0, _uLinePos+1));
if(wp) {
_id = wp->id;
delete wp;
}
} else {
_id = "A";
GPSWaypoint* wp = _kln89->FindFirstById(_id);
if(wp) {
_id = wp->id;
delete wp;
}
_uLinePos = 0;
_DToWpDispMode = 1;
}
}
} else {
// If the cursor is not displayed, then we return to the page that was displayed prior to DTO being pressed,
// and pass the knob turn to that page, whether pulled out or not.
_kln89->_activePage = _kln89->_pages[_kln89->_curPage];
_kln89->_activePage->Knob2Right1();
}
}

View File

@@ -0,0 +1,61 @@
// kln89_page_*.[ch]xx - this file is one of the "pages" that
// are used in the KLN89 GPS unit simulation.
//
// Written by David Luff, started 2005.
//
// Copyright (C) 2005 - David C Luff - daveluff AT ntlworld.com
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation; either version 2 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
//
// $Id$
#ifndef _KLN89_PAGE_DIR
#define _KLN89_PAGE_DIR
#include "kln89.hxx"
class KLN89DirPage : public KLN89Page {
public:
KLN89DirPage(KLN89* parent);
~KLN89DirPage();
void Update(double dt);
void SetId(const std::string& s);
void CrsrPressed();
void ClrPressed();
void EntPressed();
void Knob2Left1();
void Knob2Right1();
private:
// Waypoint display mode.
// There are a number of ways that the waypoint can be displayed in the DIR page:
// 0 => Whole candidate waypoint displayed, entirely inverted. This is normally how the page is initially displayed, unless a candidate waypoint cannot be determined.
// 1 => Waypoint being entered, with a corresponding cursor position, and only the cursor position inverted.
// 2 => Blanks. These can be displayed flashing when the cursor is active (eg. when CLR is pressed) and are always displayed if the cursor is turned off.
int _DToWpDispMode;
// Position of the list in the mode that scans through the active flight plan.
// This should be initialised to point at the final waypoint of the active flight plan when we enter mode zero above.
int _DToWpDispIndex;
// We need to save the mode when DTO gets pressed, since potentially this class handles page exit via. the CLR event handler
KLN89Mode _saveMasterMode;
};
#endif // _KLN89_PAGE_DIR

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,91 @@
// kln89_page_*.[ch]xx - this file is one of the "pages" that
// are used in the KLN89 GPS unit simulation.
//
// Written by David Luff, started 2005.
//
// Copyright (C) 2005 - David C Luff - daveluff AT ntlworld.com
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation; either version 2 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
//
// $Id$
#ifndef _KLN89_PAGE_FPL_HXX
#define _KLN89_PAGE_FPL_HXX
#include "kln89.hxx"
class KLN89FplPage : public KLN89Page {
public:
KLN89FplPage(KLN89* parent);
~KLN89FplPage();
void Update(double dt);
void CrsrPressed();
void EntPressed();
void ClrPressed();
void Knob1Left1();
void Knob1Right1();
void Knob2Left1();
void Knob2Right1();
void CleanUp();
void LooseFocus();
// Override the base class GetId function to return the waypoint ID under the cursor
// on FPL0 page, if there is one and the cursor is on.
// Otherwise return an empty string.
inline const std::string& GetId() { return(_fp0SelWpId); }
private:
int _fpMode; // 0 = Dis, 1 = Dtk
int _actFpMode; // 0 = Dis, 1 = ETE, 2 = ETA, 3 = Dtk/OBS
bool _bEntWp; // set true when a waypoint is being entered
bool _bEntExp; // Set true when ent is expected to set the currently entered waypoint as entered.
std::string _entWpStr; // The currently entered wp ID (need not be valid)
GPSWaypoint* _entWp; // Waypoint being currently entered
// The position of the cursor in a waypoint being entered
unsigned int _wLinePos;
unsigned int _fplPos; // The position of the start of the FP (NOT the active one) (zero-based).
// since this is reset when subpage changes we only need 1, not an array of 25!
bool _resetFplPos0; // Set true when a recalculation of _fplPos for the active flightplan page is required.
int _hdrPos;
int _fencePos;
// Get the waypoint (returned thru the pointer) at the zero-based position (pos)
// in the waypoint display of the current page.
// Returns 0 if no waypoint, 1 if sucessfull, 2 if appr header, 3 if approach fence.
//int GetFPWaypoint(GPSWaypoint* wp, int pos);
bool _delFP; // Set true when the delete FP? dialogue is being displayed
bool _delWp; // The position of the waypoint to delete is given by _uLinePos
bool _delAppr; // Set true when the delete approach dialogue is being displayed
bool _changeAppr;
void DrawFpMode(int ypos);
void Calc();
// The ID of the waypoint under the cursor in fpl0, if those conditions exist!
std::string _fp0SelWpId;
std::vector<std::string> _params;
};
#endif // _KLN89_PAGE_FPL_HXX

View File

@@ -0,0 +1,257 @@
// kln89_page_*.[ch]xx - this file is one of the "pages" that
// are used in the KLN89 GPS unit simulation.
//
// Written by David Luff, started 2005.
//
// Copyright (C) 2005 - David C Luff - daveluff AT ntlworld.com
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation; either version 2 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
//
// $Id$
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
#include <cstdio>
#include "kln89_page_int.hxx"
#include <Navaids/fix.hxx>
#include <Navaids/navrecord.hxx>
using std::string;
KLN89IntPage::KLN89IntPage(KLN89* parent)
: KLN89Page(parent) {
_nSubPages = 2;
_subPage = 0;
_name = "INT";
_int_id = "PORTE";
_last_int_id = "";
_fp = NULL;
_nearestVor = NULL;
_refNav = NULL;
}
KLN89IntPage::~KLN89IntPage() {
}
void KLN89IntPage::Update(double dt) {
bool actPage = (_kln89->_activePage->GetName() == "ACT" ? true : false);
bool multi; // Not set by FindFirst...
bool exact = false;
if(_int_id.size() == 5) exact = true;
if(_fp == NULL) {
_fp = _kln89->FindFirstIntById(_int_id, multi, exact);
} else if(_fp->get_ident() != _int_id) {
_fp = _kln89->FindFirstIntById(_int_id, multi, exact);
}
if(_fp) {
_int_id = _fp->get_ident();
if(_kln89->GetActiveWaypoint()) {
if(_int_id == _kln89->GetActiveWaypoint()->id) {
if(!(_kln89->_waypointAlert && _kln89->_blink)) {
// Active waypoint arrow
_kln89->DrawSpecialChar(4, 2, 0, 3);
}
}
}
if(_int_id != _last_int_id) {
_nearestVor = _kln89->FindClosestVor(_fp->get_lat() * SG_DEGREES_TO_RADIANS, _fp->get_lon() * SG_DEGREES_TO_RADIANS);
if(_nearestVor) {
_nvRadial = _kln89->GetMagHeadingFromTo(_nearestVor->get_lat() * SG_DEGREES_TO_RADIANS, _nearestVor->get_lon() * SG_DEGREES_TO_RADIANS,
_fp->get_lat() * SG_DEGREES_TO_RADIANS, _fp->get_lon() * SG_DEGREES_TO_RADIANS);
_nvDist = _kln89->GetGreatCircleDistance(_nearestVor->get_lat() * SG_DEGREES_TO_RADIANS, _nearestVor->get_lon() * SG_DEGREES_TO_RADIANS,
_fp->get_lat() * SG_DEGREES_TO_RADIANS, _fp->get_lon() * SG_DEGREES_TO_RADIANS);
_refNav = _nearestVor; // TODO - check that this *always* holds - eg. when changing INT id after explicitly setting ref nav
// but with no loss of focus.
} else {
_refNav = NULL;
}
_last_int_id = _int_id;
}
if(_kln89->_mode != KLN89_MODE_CRSR) {
if(!_entInvert) {
if(!actPage) {
_kln89->DrawText(_fp->get_ident(), 2, 1, 3);
} else {
// If it's the ACT page, The ID is shifted slightly right to make space for the waypoint index.
_kln89->DrawText(_fp->get_ident(), 2, 4, 3);
char buf[3];
int n = snprintf(buf, 3, "%i", _kln89->GetActiveWaypointIndex() + 1);
_kln89->DrawText((string)buf, 2, 3 - n, 3);
// We also draw an I to differentiate INT from USR when it's the ACT page
_kln89->DrawText("I", 2, 11, 3);
}
} else {
if(!_kln89->_blink) {
_kln89->DrawText(_fp->get_ident(), 2, 1, 3, false, 99);
_kln89->DrawEnt();
}
}
}
if(_subPage == 0) {
_kln89->DrawLatitude(_fp->get_lat(), 2, 3, 2);
_kln89->DrawLongitude(_fp->get_lon(), 2, 3, 1);
_kln89->DrawDirDistField(_fp->get_lat() * SG_DEGREES_TO_RADIANS, _fp->get_lon() * SG_DEGREES_TO_RADIANS, 2, 0, 0,
_to_flag, (_kln89->_mode == KLN89_MODE_CRSR && _uLinePos == 6 ? true : false));
} else {
_kln89->DrawText("Ref:", 2, 1, 2);
_kln89->DrawText("Rad:", 2, 1, 1);
_kln89->DrawText("Dis:", 2, 1, 0);
if(_refNav) {
_kln89->DrawText(_refNav->get_ident(), 2, 9, 2); // TODO - flash and allow to change if under cursor
//_kln89->DrawHeading(_nvRadial, 2, 11, 1);
//_kln89->DrawDist(_nvDist, 2, 11, 0);
// Currently our draw heading and draw dist functions don't do as many decimal points as we want here,
// so draw it ourselves!
// Heading
char buf[10];
snprintf(buf, 6, "%.1f", _nvRadial);
string s = buf;
_kln89->DrawText(s, 2, 13 - s.size(), 1);
_kln89->DrawSpecialChar(0, 2, 13, 1); // Degrees symbol
// Dist
double d = _nvDist;
d *= (_kln89->_distUnits == GPS_DIST_UNITS_NM ? 1.0 : SG_NM_TO_METER * 0.001);
snprintf(buf, 9, "%.1f", d);
s = buf;
s += (_kln89->_distUnits == GPS_DIST_UNITS_NM ? "nm" : "Km");
_kln89->DrawText(s, 2, 14 - s.size(), 0);
}
}
} else {
// TODO - when we leave the page with invalid id and return it should
// revert to showing the last valid id. Same for vor/ndb/probably apt etc.
if(_kln89->_mode != KLN89_MODE_CRSR) _kln89->DrawText(_int_id, 2, 1, 3);
if(_subPage == 0) {
_kln89->DrawText("- -- --.--'", 2, 3, 2);
_kln89->DrawText("---- --.--'", 2, 3, 1);
_kln89->DrawSpecialChar(0, 2, 7, 2);
_kln89->DrawSpecialChar(0, 2, 7, 1);
_kln89->DrawText(">--- ----", 2, 0, 0);
_kln89->DrawSpecialChar(0, 2, 4, 0);
_kln89->DrawText(_to_flag ? "To" : "Fr", 2, 5, 0);
_kln89->DrawText(_kln89->_distUnits == GPS_DIST_UNITS_NM ? "nm" : "km", 2, 12, 0);
}
}
if(_kln89->_mode == KLN89_MODE_CRSR) {
if(_uLinePos > 0 && _uLinePos < 6) {
// TODO - blink as well
_kln89->Underline(2, _uLinePos, 3, 1);
}
for(unsigned int i = 0; i < _int_id.size(); ++i) {
if(_uLinePos != (i + 1)) {
_kln89->DrawChar(_int_id[i], 2, i + 1, 3);
} else {
if(!_kln89->_blink) _kln89->DrawChar(_int_id[i], 2, i + 1, 3);
}
}
}
// TODO - fix this duplication - use _id instead of _apt_id, _vor_id, _ndb_id, _int_id etc!
_id = _int_id;
KLN89Page::Update(dt);
}
void KLN89IntPage::SetId(const string& s) {
_last_int_id = _int_id;
_save_int_id = _int_id;
_int_id = s;
_fp = NULL;
}
void KLN89IntPage::CrsrPressed() {
if(_kln89->_mode == KLN89_MODE_DISP) return;
if(_kln89->_obsMode) {
_uLinePos = 0;
} else {
_uLinePos = 1;
}
if(_subPage == 0) {
_maxULinePos = 6;
} else {
_maxULinePos = 6;
}
}
void KLN89IntPage::ClrPressed() {
if(_subPage == 0 && _uLinePos == 6) {
_to_flag = !_to_flag;
}
}
void KLN89IntPage::EntPressed() {
if(_entInvert) {
_entInvert = false;
_entInvert = false;
if(_kln89->_dtoReview) {
_kln89->DtoInitiate(_int_id);
} else {
_last_int_id = _int_id;
_int_id = _save_int_id;
}
}
}
void KLN89IntPage::Knob2Left1() {
if(_kln89->_mode != KLN89_MODE_CRSR || _uLinePos == 0) {
KLN89Page::Knob2Left1();
} else {
if(_uLinePos < 6) {
// Same logic for both pages - set the ID
_int_id = _int_id.substr(0, _uLinePos);
// ASSERT(_uLinePos > 0);
if(_uLinePos == (_int_id.size() + 1)) {
_int_id += '9';
} else {
_int_id[_uLinePos - 1] = _kln89->DecChar(_int_id[_uLinePos - 1], (_uLinePos == 1 ? false : true));
}
} else {
if(_subPage == 0) {
// NO-OP - from/to field is switched by clr button, not inner knob.
} else {
// TODO - LNR type field.
}
}
}
}
void KLN89IntPage::Knob2Right1() {
if(_kln89->_mode != KLN89_MODE_CRSR || _uLinePos == 0) {
KLN89Page::Knob2Right1();
} else {
if(_uLinePos < 6) {
// Same logic for both pages - set the ID
_int_id = _int_id.substr(0, _uLinePos);
// ASSERT(_uLinePos > 0);
if(_uLinePos == (_int_id.size() + 1)) {
_int_id += 'A';
} else {
_int_id[_uLinePos - 1] = _kln89->IncChar(_int_id[_uLinePos - 1], (_uLinePos == 1 ? false : true));
}
} else {
if(_subPage == 0) {
// NO-OP - from/to field is switched by clr button, not inner knob.
} else {
// TODO - LNR type field.
}
}
}
}

View File

@@ -0,0 +1,58 @@
// kln89_page_*.[ch]xx - this file is one of the "pages" that
// are used in the KLN89 GPS unit simulation.
//
// Written by David Luff, started 2005.
//
// Copyright (C) 2005 - David C Luff - daveluff AT ntlworld.com
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation; either version 2 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
//
// $Id$
#ifndef _KLN89_PAGE_INT_HXX
#define _KLN89_PAGE_INT_HXX
#include "kln89.hxx"
class FGFix;
class KLN89IntPage : public KLN89Page {
public:
KLN89IntPage(KLN89* parent);
~KLN89IntPage();
void Update(double dt);
void CrsrPressed();
void ClrPressed();
void EntPressed();
void Knob2Left1();
void Knob2Right1();
void SetId(const std::string& s);
private:
std::string _int_id;
std::string _last_int_id;
std::string _save_int_id;
const FGFix* _fp;
FGNavRecord* _nearestVor;
FGNavRecord* _refNav; // Will usually be the same as _nearestVor, and gets reset to _nearestVor when page looses focus.
double _nvRadial; // radial from nearest VOR
double _nvDist; // distance to nearest VOR
};
#endif // _KLN89_PAGE_INT_HXX

View File

@@ -0,0 +1,610 @@
// kln89_page_*.[ch]xx - this file is one of the "pages" that
// are used in the KLN89 GPS unit simulation.
//
// Written by David Luff, started 2005.
//
// Copyright (C) 2005 - David C Luff - daveluff AT ntlworld.com
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation; either version 2 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
//
// $Id$
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
#include <cstdlib>
#include <cstdio>
#include "kln89_page_nav.hxx"
#include <Main/fg_props.hxx>
using std::string;
KLN89NavPage::KLN89NavPage(KLN89* parent)
: KLN89Page(parent) {
_nSubPages = 4;
_subPage = 0;
_name = "NAV";
_posFormat = 0; // Check - should this default to ref from waypoint?
_vnv = 0;
_nav4DataSnippet = 0;
_cdiFormat = 0;
_menuActive = false;
_menuPos = 0;
_suspendAVS = false;
_scanWpSet = false;
_scanWpIndex = -1;
}
KLN89NavPage::~KLN89NavPage() {
}
void KLN89NavPage::Update(double dt) {
GPSFlightPlan* fp = _kln89->_activeFP;
GPSWaypoint* awp = _kln89->GetActiveWaypoint();
// Scan-pull out on nav4 page switches off the cursor
if(3 == _subPage && fgGetBool("/instrumentation/kln89/scan-pull")) { _kln89->_mode = KLN89_MODE_DISP; }
bool crsr = (_kln89->_mode == KLN89_MODE_CRSR);
bool blink = _kln89->_blink;
double lat = _kln89->_gpsLat * SG_RADIANS_TO_DEGREES;
double lon = _kln89->_gpsLon * SG_RADIANS_TO_DEGREES;
if(_subPage != 3) { _scanWpSet = false; }
if(0 == _subPage) {
if(_kln89->_navFlagged) {
_kln89->DrawText("> F L A G", 2, 0, 2);
_kln89->DrawText("DTK --- TK ---", 2, 0, 1);
_kln89->DrawText(">--- To --:--", 2, 0, 0);
_kln89->DrawSpecialChar(0, 2, 7, 1);
_kln89->DrawSpecialChar(0, 2, 15, 1);
_kln89->DrawSpecialChar(0, 2, 4, 0);
_kln89->DrawSpecialChar(1, 2, 3, 2);
_kln89->DrawSpecialChar(1, 2, 4, 2);
_kln89->DrawSpecialChar(1, 2, 6, 2);
_kln89->DrawSpecialChar(1, 2, 10, 2);
_kln89->DrawSpecialChar(1, 2, 12, 2);
_kln89->DrawSpecialChar(1, 2, 13, 2);
} else {
if(_kln89->_dto) {
_kln89->DrawDTO(2, 7, 3);
} else {
if(!(_kln89->_waypointAlert && _kln89->_blink)) {
_kln89->DrawSpecialChar(3, 2, 8, 3);
}
}
_kln89->DrawText(awp->id, 2, 10, 3);
if(!_kln89->_dto && !_kln89->_obsMode && !_kln89->_fromWaypoint.id.empty()) {
if(_kln89->_fromWaypoint.type != GPS_WP_VIRT) { // Don't draw the virtual waypoint names
_kln89->DrawText(_kln89->_fromWaypoint.id, 2, 1, 3);
}
}
if(!(crsr && blink && _uLinePos == 1)) {
if(_cdiFormat == 0) {
_kln89->DrawCDI();
} else if(_cdiFormat == 1) {
_kln89->DrawText("Fly", 2, 2, 2);
double x = _kln89->CalcCrossTrackDeviation();
// TODO - check the R/L from sign of x below - I *think* it holds but not sure!
// Note also that we're setting Fly R or L based on the aircraft
// position only, not the heading. Not sure if this is correct or not.
_kln89->DrawText(x < 0.0 ? "R" : "L", 2, 6, 2);
char buf[6];
int n;
x = fabs(x * (_kln89->_distUnits == GPS_DIST_UNITS_NM ? 1.0 : SG_NM_TO_METER * 0.001));
if(x < 1.0) {
n = snprintf(buf, 6, "%0.2f", x);
} else if(x < 100.0) {
n = snprintf(buf, 6, "%0.1f", x);
} else {
n = snprintf(buf, 6, "%i", (int)(x+0.5));
}
_kln89->DrawText((string)buf, 2, 13-n, 2);
_kln89->DrawText(_kln89->_distUnits == GPS_DIST_UNITS_NM ? "nm" : "km", 2, 13, 2);
} else {
_kln89->DrawText("CDI Scale:", 2, 1, 2);
double d = _kln89->_cdiScales[_kln89->_currentCdiScaleIndex] * (_kln89->_distUnits == GPS_DIST_UNITS_NM ? 1.0 : SG_NM_TO_METER * 0.001);
char buf[5];
string s;
if(d >= 1.0) {
snprintf(buf, 4, "%2.1f", d);
s = buf;
} else {
snprintf(buf, 5, "%2.2f", d);
// trim the leading zero
s = buf;
s = s.substr(1, s.size() - 1);
}
_kln89->DrawText(s, 2, 11, 2);
_kln89->DrawText(_kln89->_distUnits == GPS_DIST_UNITS_NM ? "nm" : "km", 2, 14, 2);
}
}
_kln89->DrawChar('>', 2, 0, 2);
_kln89->DrawChar('>', 2, 0, 0);
if(crsr) {
if(_uLinePos == 1) _kln89->Underline(2, 1, 2, 15);
else if(_uLinePos == 2) _kln89->Underline(2, 1, 0, 9);
}
// Desired and actual magnetic track
if(!_kln89->_obsMode) {
_kln89->DrawText("DTK", 2, 0, 1);
_kln89->DrawHeading((int)_kln89->_dtkMag, 2, 7, 1);
}
_kln89->DrawText("TK", 2, 9, 1);
if(_kln89->_groundSpeed_ms > 3) { // about 6 knots, don't know exactly what value to disable track
// The trouble with relying on FG gps's track value is we don't know when it's valid.
_kln89->DrawHeading((int)_kln89->_magTrackDeg, 2, 15, 1);
} else {
_kln89->DrawText("---", 2, 12, 1);
_kln89->DrawSpecialChar(0, 2, 15, 1);
}
// Radial to/from active waypoint.
// TODO - Not sure if this either is or should be true or mag!!!!!!!
if(!(crsr && blink && _uLinePos == 2)) {
if(0 == _vnv) {
_kln89->DrawHeading((int)_kln89->GetHeadingToActiveWaypoint(), 2, 4, 0);
_kln89->DrawText("To", 2, 5, 0);
} else if(1 == _vnv) {
_kln89->DrawHeading((int)_kln89->GetHeadingFromActiveWaypoint(), 2, 4, 0);
_kln89->DrawText("Fr", 2, 5, 0);
} else {
_kln89->DrawText("Vnv Off", 2, 1, 0);
}
}
// It seems that the floating point groundspeed must be at least 30kt
// for an ETA to be calculated. Note that this means that (integer) 30kt
// can appear in the frame 1 display both with and without an ETA displayed.
// TODO - need to switch off track (and heading bug change) based on instantaneous speed as well
// since the long gps lag filter means we can still be displaying when stopped on ground.
if(_kln89->_groundSpeed_kts > 30.0) {
// Assuming eta display is always hh:mm
// Does it ever switch to seconds when close?
if(_kln89->_eta / 3600.0 > 100.0) {
// More that 100 hours ! - Doesn't fit.
_kln89->DrawText("--:--", 2, 11, 0);
} else {
_kln89->DrawTime(_kln89->_eta, 2, 15, 0);
}
} else {
_kln89->DrawText("--:--", 2, 11, 0);
}
}
} else if(1 == _subPage) {
// Present position
_kln89->DrawChar('>', 2, 1, 3);
if(!(crsr && blink && _uLinePos == 1)) _kln89->DrawText("PRESENT POSN", 2, 2, 3);
if(crsr && _uLinePos == 1) _kln89->Underline(2, 2, 3, 12);
if(0 == _posFormat) {
// Lat/lon
_kln89->DrawLatitude(lat, 2, 3, 1);
_kln89->DrawLongitude(lon, 2, 3, 0);
} else {
// Ref from wp - defaults to nearest vor (and resets to default when page left and re-entered).
}
} else if(2 == _subPage) {
_kln89->DrawText("Time", 2, 0, 3);
// TODO - hardwired to UTC at the moment
_kln89->DrawText("UTC", 2, 6, 3);
string th = fgGetString("/instrumentation/clock/indicated-hour");
string tm = fgGetString("/instrumentation/clock/indicated-min");
if(th.size() == 1) th = "0" + th;
if(tm.size() == 1) tm = "0" + tm;
_kln89->DrawText(th + tm, 2, 11, 3);
_kln89->DrawText("Depart", 2, 0, 2);
_kln89->DrawText(_kln89->_departureTimeString, 2, 11, 2);
_kln89->DrawText("ETA", 2, 0, 1);
if(_kln89->_departed) {
/* Rules of ETA waypoint are:
If the active waypoint is part of the active flightplan, then display
the ETA to the final (destination) waypoint of the active flightplan.
If the active waypoint is not part of the active flightplan, then
display the ETA to the active waypoint. */
// TODO - implement the above properly - we haven't below!
string wid = "";
if(fp->waypoints.size()) {
wid = fp->waypoints[fp->waypoints.size() - 1]->id;
} else if(awp) {
wid = awp->id;
}
if(!wid.empty()) {
_kln89->DrawText(wid, 2, 4, 1);
double tsec = _kln89->GetTimeToWaypoint(wid);
if(tsec < 0.0) {
_kln89->DrawText("----", 2, 11, 1);
} else {
int etah = (int)tsec / 3600;
int etam = ((int)tsec - etah * 3600) / 60;
etah += std::stoi(fgGetString("/instrumentation/clock/indicated-hour"));
etam += std::stoi(fgGetString("/instrumentation/clock/indicated-min"));
while(etam > 59) {
etam -= 60;
etah += 1;
}
while(etah > 23) {
etah -= 24;
}
char buf[6];
int n = snprintf(buf, 6, "%02i%02i", etah, etam);
_kln89->DrawText((string)buf, 2, 15-n, 1);
}
} else {
_kln89->DrawText("----", 2, 11, 1);
}
} else {
_kln89->DrawText("----", 2, 11, 1);
}
_kln89->DrawText("Flight", 2, 0, 0);
if(_kln89->_departed) {
int eh = (int)_kln89->_elapsedTime / 3600;
int em = ((int)_kln89->_elapsedTime - eh * 3600) / 60;
char buf[6];
int n = snprintf(buf, 6, "%i:%02i", eh, em);
_kln89->DrawText((string)buf, 2, 15-n, 0);
} else {
_kln89->DrawText("-:--", 2, 11, 0);
}
} else { // if(3 == _subPage)
//
// Switch the cursor off if scan-pull is out on this page.
//
if(fgGetBool("/instrumentation/kln89/scan-pull")) { _kln89->_mode = KLN89_MODE_DISP; }
//
// Draw the moving map if valid.
// We call the core KLN89 class to do this.
//
if(_kln89->_mapOrientation == 2 && _kln89->_groundSpeed_kts < 2) {
// Don't draw it if in track up mode and groundspeed < 2kts, as per real-life unit.
} else {
_kln89->DrawMap(!_suspendAVS);
}
//
// Now that the map has been drawn, add the annotation (scale, etc).
//
int scale = KLN89MapScales[_kln89->_mapScaleUnits][_kln89->_mapScaleIndex];
string scle_str = GPSitoa(scale);
if(crsr) {
if(_menuActive) {
// Draw a background quad to encompass on/off for the first three at 'off' length
_kln89->DrawMapQuad(28, 9, 48, 36, true);
_kln89->DrawMapText("SUA:", 1, 27, true);
if(!(_menuPos == 0 && _kln89->_blink)) _kln89->DrawMapText((_kln89->_drawSUA ? "on" : "off"), 29, 27, true);
if(_menuPos == 0) _kln89->DrawLine(28, 27, 48, 27);
_kln89->DrawMapText("VOR:", 1, 18, true);
if(!(_menuPos == 1 && _kln89->_blink)) _kln89->DrawMapText((_kln89->_drawVOR ? "on" : "off"), 29, 18, true);
if(_menuPos == 1) _kln89->DrawLine(28, 18, 48, 18);
_kln89->DrawMapText("APT:", 1, 9, true);
if(!(_menuPos == 2 && _kln89->_blink)) _kln89->DrawMapText((_kln89->_drawApt ? "on" : "off"), 29, 9, true);
if(_menuPos == 2) _kln89->DrawLine(28, 9, 48, 9);
_kln89->DrawMapQuad(0, 0, 27, 8, true);
if(!(_menuPos == 3 && _kln89->_blink)) {
if(_kln89->_mapOrientation == 0) {
_kln89->DrawMapText("N", 1, 0, true);
_kln89->DrawMapUpArrow(7, 1);
} else if(_kln89->_mapOrientation == 1) {
_kln89->DrawMapText("DTK", 1, 0, true);
_kln89->DrawMapUpArrow(21, 1);
} else {
// Don't bother with heading up for now!
_kln89->DrawMapText("TK", 1, 0, true);
_kln89->DrawMapUpArrow(14, 1);
}
}
if(_menuPos == 3) _kln89->DrawLine(0, 0, 27, 0);
} else {
if(_uLinePos == 2) {
if(!_kln89->_blink) {
_kln89->DrawMapText("Menu?", 1, 9, true);
_kln89->DrawEnt();
_kln89->DrawLine(0, 9, 34, 9);
} else {
_kln89->DrawMapQuad(0, 9, 34, 17, true);
}
} else {
_kln89->DrawMapText("Menu?", 1, 9, true);
}
// right-justify the scale when _uLinePos == 3
if(!(_uLinePos == 3 && _kln89->_blink)) _kln89->DrawMapText(scle_str, (_uLinePos == 3 ? 29 - (scle_str.size() * 7) : 1), 0, true);
if(_uLinePos == 3) _kln89->DrawLine(0, 0, 27, 0);
}
} else {
// Just draw the scale
_kln89->DrawMapText(scle_str, 1, 0, true);
}
// If the scan-pull knob is out, draw one of the waypoints (if applicable).
if(fgGetBool("/instrumentation/kln89/scan-pull")) {
if(_kln89->_activeFP->waypoints.size()) {
//cout << "Need to draw a waypoint!\n";
_kln89->DrawLine(70, 0, 111, 0);
if(!_kln89->_blink) {
//_kln89->DrawMapQuad(45, 0, 97, 8, true);
if(!_scanWpSet) {
_scanWpIndex = _kln89->GetActiveWaypointIndex();
_scanWpSet = true;
}
_kln89->DrawMapText(_kln89->_activeFP->waypoints[_scanWpIndex]->id, 71, 0, true);
}
}
}
//
// Do part of the field 1 update, since NAV 4 is a special case for the last line.
//
_kln89->DrawChar('>', 1, 0, 0);
if(crsr && _uLinePos == 1) _kln89->Underline(1, 1, 0, 5);
if(!(crsr && _uLinePos == 1 && _kln89->_blink)) {
if(_kln89->_obsMode && _nav4DataSnippet == 0) _nav4DataSnippet = 1;
double tsec;
switch(_nav4DataSnippet) {
case 0:
// DTK
_kln89->DrawLabel("DTK", -39, 6);
// TODO - check we have an active FP / dtk and draw dashes if not.
char buf0[4];
snprintf(buf0, 4, "%03i", (int)(_kln89->_dtkMag));
_kln89->DrawText((string)buf0, 1, 3, 0);
break;
case 1:
// groundspeed
_kln89->DrawSpeed(_kln89->_groundSpeed_kts, 1, 5, 0);
break;
case 2:
// ETE
tsec = _kln89->GetETE();
if(tsec < 0.0) {
_kln89->DrawText("--:--", 1, 1, 0);
} else {
int eteh = (int)tsec / 3600;
int etem = ((int)tsec - eteh * 3600) / 60;
char buf[6];
int n = snprintf(buf, 6, "%02i:%02i", eteh, etem);
_kln89->DrawText((string)buf, 1, 6-n, 0);
}
break;
case 3:
// Cross-track correction
double x = _kln89->CalcCrossTrackDeviation();
if(x < 0.0) {
_kln89->DrawSpecialChar(3, 1, 5, 0);
} else {
_kln89->DrawSpecialChar(7, 1, 5, 0);
}
char buf3[6];
int n;
x = fabs(x * (_kln89->_distUnits == GPS_DIST_UNITS_NM ? 1.0 : SG_NM_TO_METER * 0.001));
if(x < 1.0) {
n = snprintf(buf3, 6, "%0.2f", x);
} else if(x < 100.0) {
n = snprintf(buf3, 6, "%0.1f", x);
} else {
n = snprintf(buf3, 6, "%i", (int)(x+0.5));
}
_kln89->DrawText((string)buf3, 1, 5-n, 0);
break;
}
}
}
KLN89Page::Update(dt);
}
// Returns the id string of the selected waypoint on NAV4 if valid, else returns an empty string.
string KLN89NavPage::GetNav4WpId() {
if(3 == _subPage) {
if(fgGetBool("/instrumentation/kln89/scan-pull")) {
if(_kln89->_activeFP->waypoints.size()) {
if(!_scanWpSet) {
return(_kln89->_activeWaypoint.id);
} else {
return(_kln89->_activeFP->waypoints[_scanWpIndex]->id);
}
}
}
}
return("");
}
void KLN89NavPage::LooseFocus() {
_suspendAVS = false;
_scanWpSet = false;
}
void KLN89NavPage::CrsrPressed() {
if(_kln89->_mode == KLN89_MODE_DISP) {
// Crsr just switched off
_menuActive = false;
} else {
// Crsr just switched on
if(_subPage < 3) {
_uLinePos = 1;
} else {
_uLinePos = 3;
}
}
}
void KLN89NavPage::EntPressed() {
if(_kln89->_mode == KLN89_MODE_CRSR) {
if(_subPage == 3 && _uLinePos == 2 && !_menuActive) {
_menuActive = true;
_menuPos = 0;
_suspendAVS = false;
}
}
}
void KLN89NavPage::ClrPressed() {
if(_kln89->_mode == KLN89_MODE_CRSR) {
if(_subPage == 0) {
if(_uLinePos == 1) {
_cdiFormat++;
if(_cdiFormat > 2) _cdiFormat = 0;
} else if(_uLinePos == 2) {
_vnv++;
if(_vnv > 2) _vnv = 0;
}
}
if(_subPage == 3) {
if(_uLinePos > 1) {
_suspendAVS = !_suspendAVS;
_menuActive = false;
} else if(_uLinePos == 1) {
_nav4DataSnippet++;
if(_nav4DataSnippet > 3) _nav4DataSnippet = 0;
}
}
} else {
if(_subPage == 3) {
_suspendAVS = !_suspendAVS;
}
}
}
void KLN89NavPage::Knob1Left1() {
if(_kln89->_mode == KLN89_MODE_CRSR) {
if(!(_subPage == 3 && _menuActive)) {
if(_uLinePos > 0) _uLinePos--;
} else {
if(_menuPos > 0) _menuPos--;
}
}
}
void KLN89NavPage::Knob1Right1() {
if(_kln89->_mode == KLN89_MODE_CRSR) {
if(_subPage < 2) {
if(_uLinePos < 2) _uLinePos++;
} else if(_subPage == 2) {
_uLinePos = 1;
} else {
// NAV 4 - this is complicated by whether the menu is displayed or not.
if(_menuActive) {
if(_menuPos < 3) _menuPos++;
} else {
if(_uLinePos < 3) _uLinePos++;
}
}
}
}
void KLN89NavPage::Knob2Left1() {
// If the inner-knob is out on the nav4 page, the only effect is to cycle the displayed waypoint.
if(3 == _subPage && fgGetBool("/instrumentation/kln89/scan-pull")) {
if(_kln89->_activeFP->waypoints.size()) { // TODO - find out what happens when scan-pull is on on nav4 without an active FP.
// It's unlikely that we could get here without _scanWpSet, but theoretically possible, so we need to cover it.
if(!_scanWpSet) {
_scanWpIndex = _kln89->GetActiveWaypointIndex();
_scanWpSet = true;
} else {
if(0 == _scanWpIndex) {
_scanWpIndex = _kln89->_activeFP->waypoints.size() - 1;
} else {
_scanWpIndex--;
}
}
}
return;
}
if(_kln89->_mode != KLN89_MODE_CRSR || _uLinePos == 0) {
KLN89Page::Knob2Left1();
return;
}
if(_subPage == 0) {
if(_uLinePos == 1 && _cdiFormat == 2) {
_kln89->CDIFSDIncrease();
}
} else if(_subPage == 3) {
if(_menuActive) {
if(_menuPos == 0) {
_kln89->_drawSUA = !_kln89->_drawSUA;
} else if(_menuPos == 1) {
_kln89->_drawVOR = !_kln89->_drawVOR;
} else if(_menuPos == 2) {
_kln89->_drawApt = !_kln89->_drawApt;
} else {
if(_kln89->_mapOrientation == 0) {
// Don't allow heading up for now
_kln89->_mapOrientation = 2;
} else {
_kln89->_mapOrientation--;
}
_kln89->UpdateMapHeading();
}
} else if(_uLinePos == 3) {
// TODO - add AUTO
if(_kln89->_mapScaleIndex == 0) {
_kln89->_mapScaleIndex = 20;
} else {
_kln89->_mapScaleIndex--;
}
}
}
}
void KLN89NavPage::Knob2Right1() {
// If the inner-knob is out on the nav4 page, the only effect is to cycle the displayed waypoint.
if(3 == _subPage && fgGetBool("/instrumentation/kln89/scan-pull")) {
if(_kln89->_activeFP->waypoints.size()) { // TODO - find out what happens when scan-pull is on on nav4 without an active FP.
// It's unlikely that we could get here without _scanWpSet, but theoretically possible, so we need to cover it.
if(!_scanWpSet) {
_scanWpIndex = _kln89->GetActiveWaypointIndex();
_scanWpSet = true;
} else {
_scanWpIndex++;
if(_scanWpIndex > static_cast<int>(_kln89->_activeFP->waypoints.size()) - 1) {
_scanWpIndex = 0;
}
}
}
return;
}
if(_kln89->_mode != KLN89_MODE_CRSR || _uLinePos == 0) {
KLN89Page::Knob2Right1();
return;
}
if(_subPage == 0) {
if(_uLinePos == 1 && _cdiFormat == 2) {
_kln89->CDIFSDDecrease();
}
} else if(_subPage == 3) {
if(_menuActive) {
if(_menuPos == 0) {
_kln89->_drawSUA = !_kln89->_drawSUA;
} else if(_menuPos == 1) {
_kln89->_drawVOR = !_kln89->_drawVOR;
} else if(_menuPos == 2) {
_kln89->_drawApt = !_kln89->_drawApt;
} else {
if(_kln89->_mapOrientation >= 2) {
// Don't allow heading up for now
_kln89->_mapOrientation = 0;
} else {
_kln89->_mapOrientation++;
}
_kln89->UpdateMapHeading();
}
} else if(_uLinePos == 3) {
// TODO - add AUTO
if(_kln89->_mapScaleIndex == 20) {
_kln89->_mapScaleIndex = 0;
} else {
_kln89->_mapScaleIndex++;
}
}
}
}

View File

@@ -0,0 +1,70 @@
// kln89_page_*.[ch]xx - this file is one of the "pages" that
// are used in the KLN89 GPS unit simulation.
//
// Written by David Luff, started 2005.
//
// Copyright (C) 2005 - David C Luff - daveluff AT ntlworld.com
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation; either version 2 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
//
// $Id$
#include "kln89.hxx"
class KLN89NavPage : public KLN89Page {
public:
KLN89NavPage(KLN89* parent);
~KLN89NavPage();
void Update(double dt);
void CrsrPressed();
void EntPressed();
void ClrPressed();
void Knob1Left1();
void Knob1Right1();
void Knob2Left1();
void Knob2Right1();
void LooseFocus();
// Returns the id string of the selected waypoint on NAV4 if valid, else returns an empty string.
std::string GetNav4WpId();
private:
int _posFormat; // 0 => lat,lon; 1 => ref to wp.
int _vnv; // 0 => To, 1 => Fr, 2 => off.
// The data snippet to be displayed in field 1 when the moving map is active (NAV 4)
int _nav4DataSnippet; // 0 => DTK, 1 => groundspeed, 2 => ETE, 3 => cross-track correction.
// Format to draw in the CDI field.
// 0 => CDI, 1 => Cross track correction (eg " Fly R 2.15nm"), 2 => cdi scale (eg "CDI Scale:5.0nm")
int _cdiFormat;
// Drawing of apt, vor and sua on the moving map can be temporarily suspended
// Note that this should be cleared when page focus is lost, or when the menu is displayed.
bool _suspendAVS;
// NAV 4 menu stuff
bool _menuActive;
int _menuPos;
// NAV 4 waypoint scan drawing housekeeping.
bool _scanWpSet;
int _scanWpIndex;
};

View File

@@ -0,0 +1,218 @@
// kln89_page_*.[ch]xx - this file is one of the "pages" that
// are used in the KLN89 GPS unit simulation.
//
// Written by David Luff, started 2005.
//
// Copyright (C) 2005 - David C Luff - daveluff AT ntlworld.com
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation; either version 2 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
//
// $Id$
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
#include <cstdio>
#include "kln89_page_ndb.hxx"
#include <Navaids/navrecord.hxx>
using std::string;
KLN89NDBPage::KLN89NDBPage(KLN89* parent)
: KLN89Page(parent) {
_nSubPages = 2;
_subPage = 0;
_name = "NDB";
_ndb_id = "SF";
np = NULL;
}
KLN89NDBPage::~KLN89NDBPage() {
}
void KLN89NDBPage::Update(double dt) {
bool actPage = (_kln89->_activePage->GetName() == "ACT" ? true : false);
bool multi; // Not set by FindFirst...
bool exact = false;
if(_ndb_id.size() == 3) exact = true;
if(np == NULL) {
np = _kln89->FindFirstNDBById(_ndb_id, multi, exact);
} else if(np->get_ident() != _ndb_id) {
np = _kln89->FindFirstNDBById(_ndb_id, multi, exact);
}
//if(np == NULL) cout << "NULL... ";
//if(b == false) cout << "false...\n";
/*
if(np && b) {
cout << "VOR FOUND!\n";
} else {
cout << ":-(\n";
}
*/
if(np) {
//cout << np->id << '\n';
_ndb_id = np->get_ident();
if(_kln89->GetActiveWaypoint()) {
if(_ndb_id == _kln89->GetActiveWaypoint()->id) {
if(!(_kln89->_waypointAlert && _kln89->_blink)) {
// Active waypoint arrow
_kln89->DrawSpecialChar(4, 2, 0, 3);
}
}
}
if(_kln89->_mode != KLN89_MODE_CRSR) {
if(!_entInvert) {
if(!actPage) {
_kln89->DrawText(np->get_ident(), 2, 1, 3);
} else {
// If it's the ACT page, The ID is shifted slightly right to make space for the waypoint index.
_kln89->DrawText(np->get_ident(), 2, 4, 3);
char buf[3];
int n = snprintf(buf, 3, "%i", _kln89->GetActiveWaypointIndex() + 1);
_kln89->DrawText((string)buf, 2, 3 - n, 3);
}
} else {
if(!_kln89->_blink) {
_kln89->DrawText(np->get_ident(), 2, 1, 3, false, 99);
_kln89->DrawEnt();
}
}
}
if(_subPage == 0) {
// TODO - trim VOR-DME from the name, convert to uppercase, abbreviate, etc
_kln89->DrawText(np->name(), 2, 0, 2);
_kln89->DrawLatitude(np->get_lat(), 2, 3, 1);
_kln89->DrawLongitude(np->get_lon(), 2, 3, 0);
} else {
_kln89->DrawDirDistField(np->get_lat() * SG_DEGREES_TO_RADIANS, np->get_lon() * SG_DEGREES_TO_RADIANS,
2, 0, 0, _to_flag, (_kln89->_mode == KLN89_MODE_CRSR && _uLinePos == 4 ? true : false));
}
} else {
if(_kln89->_mode != KLN89_MODE_CRSR) _kln89->DrawText(_ndb_id, 2, 1, 3);
if(_subPage == 0) {
_kln89->DrawText("----.-", 2, 9, 3);
_kln89->DrawText("--------------", 2, 0, 2);
_kln89->DrawText("- -- --.--'", 2, 3, 1);
_kln89->DrawText("---- --.--'", 2, 3, 0);
_kln89->DrawSpecialChar(0, 2, 7, 1);
_kln89->DrawSpecialChar(0, 2, 7, 0);
}
}
if(_kln89->_mode == KLN89_MODE_CRSR) {
if(_uLinePos > 0 && _uLinePos < 4) {
// TODO - blink as well
_kln89->Underline(2, _uLinePos, 3, 1);
}
for(unsigned int i = 0; i < _ndb_id.size(); ++i) {
if(_uLinePos != (i + 1)) {
_kln89->DrawChar(_ndb_id[i], 2, i + 1, 3);
} else {
if(!_kln89->_blink) _kln89->DrawChar(_ndb_id[i], 2, i + 1, 3);
}
}
}
_id = _ndb_id;
KLN89Page::Update(dt);
}
void KLN89NDBPage::SetId(const string& s) {
_last_ndb_id = _ndb_id;
_save_ndb_id = _ndb_id;
_ndb_id = s;
np = NULL;
}
void KLN89NDBPage::CrsrPressed() {
if(_kln89->_mode == KLN89_MODE_DISP) return;
if(_kln89->_obsMode) {
_uLinePos = 0;
} else {
_uLinePos = 1;
}
if(_subPage == 0) {
_maxULinePos = 17;
} else {
_maxULinePos = 4;
}
}
void KLN89NDBPage::ClrPressed() {
if(_subPage == 1 && _uLinePos == 4) {
_to_flag = !_to_flag;
}
}
void KLN89NDBPage::EntPressed() {
if(_entInvert) {
_entInvert = false;
if(_kln89->_dtoReview) {
_kln89->DtoInitiate(_ndb_id);
} else {
_last_ndb_id = _ndb_id;
_ndb_id = _save_ndb_id;
}
}
}
void KLN89NDBPage::Knob2Left1() {
if(_kln89->_mode != KLN89_MODE_CRSR || _uLinePos == 0) {
KLN89Page::Knob2Left1();
} else {
if(_uLinePos < 4) {
// Same logic for both pages - set the ID
_ndb_id = _ndb_id.substr(0, _uLinePos);
// ASSERT(_uLinePos > 0);
if(_uLinePos == (_ndb_id.size() + 1)) {
_ndb_id += '9';
} else {
_ndb_id[_uLinePos - 1] = _kln89->DecChar(_ndb_id[_uLinePos - 1], (_uLinePos == 1 ? false : true));
}
} else {
if(_subPage == 0) {
// set by name
} else {
// NO-OP - from/to field is switched by clr button, not inner knob.
}
}
}
}
void KLN89NDBPage::Knob2Right1() {
if(_kln89->_mode != KLN89_MODE_CRSR || _uLinePos == 0) {
KLN89Page::Knob2Right1();
} else {
if(_uLinePos < 4) {
// Same logic for both pages - set the ID
_ndb_id = _ndb_id.substr(0, _uLinePos);
// ASSERT(_uLinePos > 0);
if(_uLinePos == (_ndb_id.size() + 1)) {
_ndb_id += 'A';
} else {
_ndb_id[_uLinePos - 1] = _kln89->IncChar(_ndb_id[_uLinePos - 1], (_uLinePos == 1 ? false : true));
}
} else {
if(_subPage == 0) {
// set by name
} else {
// NO-OP - from/to field is switched by clr button, not inner knob.
}
}
}
}

View File

@@ -0,0 +1,52 @@
// kln89_page_*.[ch]xx - this file is one of the "pages" that
// are used in the KLN89 GPS unit simulation.
//
// Written by David Luff, started 2005.
//
// Copyright (C) 2005 - David C Luff - daveluff AT ntlworld.com
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation; either version 2 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
//
// $Id$
#ifndef _KLN89_PAGE_NDB_HXX
#define _KLN89_PAGE_NDB_HXX
#include "kln89.hxx"
class KLN89NDBPage : public KLN89Page {
public:
KLN89NDBPage(KLN89* parent);
~KLN89NDBPage();
void Update(double dt);
void CrsrPressed();
void ClrPressed();
void EntPressed();
void Knob2Left1();
void Knob2Right1();
void SetId(const std::string& s);
private:
std::string _ndb_id;
std::string _last_ndb_id;
std::string _save_ndb_id;
FGNavRecord* np;
};
#endif // _KLN89_PAGE_NDB_HXX

View File

@@ -0,0 +1,90 @@
// kln89_page_*.[ch]xx - this file is one of the "pages" that
// are used in the KLN89 GPS unit simulation.
//
// Written by David Luff, started 2005.
//
// Copyright (C) 2005 - David C Luff - daveluff AT ntlworld.com
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation; either version 2 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
//
// $Id$
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
#include "kln89_page_nrst.hxx"
KLN89NrstPage::KLN89NrstPage(KLN89* parent)
: KLN89Page(parent) {
_nSubPages = 2;
_subPage = 0;
_name = "NRST";
_uLinePos = 1;
_maxULinePos = 8;
}
KLN89NrstPage::~KLN89NrstPage() {
}
void KLN89NrstPage::Update(double dt) {
// crsr is always on on nearest page
bool blink = _kln89->_blink;
_kln89->DrawText("NEAREST", 2, 3, 3);
if(!(_uLinePos == 1 && blink)) _kln89->DrawText("APT?", 2, 0, 2);
if(!(_uLinePos == 2 && blink)) _kln89->DrawText("VOR?", 2, 5, 2);
if(!(_uLinePos == 3 && blink)) _kln89->DrawText("NDB?", 2, 10, 2);
if(!(_uLinePos == 4 && blink)) _kln89->DrawText("INT?", 2, 0, 1);
if(!(_uLinePos == 5 && blink)) _kln89->DrawText("USR?", 2, 5, 1);
if(!(_uLinePos == 6 && blink)) _kln89->DrawText("SUA?", 2, 10, 1);
if(!(_uLinePos == 7 && blink)) _kln89->DrawText("FSS?", 2, 0, 0);
if(!(_uLinePos == 8 && blink)) _kln89->DrawText("CTR?", 2, 5, 0);
switch(_uLinePos) {
case 1: _kln89->Underline(2, 0, 2, 4); break;
case 2: _kln89->Underline(2, 5, 2, 4); break;
case 3: _kln89->Underline(2, 10, 2, 4); break;
case 4: _kln89->Underline(2, 0, 1, 4); break;
case 5: _kln89->Underline(2, 5, 1, 4); break;
case 6: _kln89->Underline(2, 10, 1, 4); break;
case 7: _kln89->Underline(2, 0, 0, 4); break;
case 8: _kln89->Underline(2, 5, 0, 4); break;
}
// Actually, the kln89 sim from Bendix-King dosn't draw the 'ENT'
// for 'APT?' if it was on 'Leg' (pos 0) immediately previously, but does if
// it was not on 'Leg' immediately previously. I think we can desist from
// reproducing this probable bug.
if(_uLinePos > 0) {
if(!blink) _kln89->DrawEnt();
}
KLN89Page::Update(dt);
}
void KLN89NrstPage::CrsrPressed() {
}
void KLN89NrstPage::EntPressed() {
if(_uLinePos > 4) {
ShowScratchpadMessage(" No ", " Nrst ");
}
}
void KLN89NrstPage::LooseFocus() {
_uLinePos = 1;
_scratchpadMsg = false;
}

View File

@@ -0,0 +1,48 @@
// kln89_page_*.[ch]xx - this file is one of the "pages" that
// are used in the KLN89 GPS unit simulation.
//
// Written by David Luff, started 2005.
//
// Copyright (C) 2005 - David C Luff - daveluff AT ntlworld.com
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation; either version 2 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
//
// $Id$
#ifndef _KLN89_PAGE_NRST
#define _KLN89_PAGE_NRST
#include "kln89.hxx"
class KLN89NrstPage : public KLN89Page {
public:
KLN89NrstPage(KLN89* parent);
~KLN89NrstPage();
void Update(double dt);
void CrsrPressed();
void EntPressed();
//void ClrPressed();
//void Knob1Left1();
//void Knob1Right1();
//void Knob2Left1();
//void Knob2Right1();
void LooseFocus();
};
#endif // _KLN89_PAGE_NRST

View File

@@ -0,0 +1,66 @@
// kln89_page_*.[ch]xx - this file is one of the "pages" that
// are used in the KLN89 GPS unit simulation.
//
// Written by David Luff, started 2005.
//
// Copyright (C) 2005 - David C Luff - daveluff AT ntlworld.com
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation; either version 2 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
//
// $Id$
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
#include <cstdio>
#include "kln89_page_oth.hxx"
using std::string;
KLN89OthPage::KLN89OthPage(KLN89* parent)
: KLN89Page(parent) {
_nSubPages = 12;
_subPage = 0;
_name = "OTH";
}
KLN89OthPage::~KLN89OthPage() {
}
void KLN89OthPage::Update(double dt) {
// The OTH pages aren't terribly important at the moment, since we don't simulate
// error and failure, but lets hardwire some representitive output anyway.
if(_subPage == 0) {
_kln89->DrawText("State", 2, 0, 3);
_kln89->DrawText("GPS Alt", 2, 0, 2);
_kln89->DrawText("Estimated Posn", 2, 0, 1);
_kln89->DrawText("Error", 2, 1, 0);
// FIXME - hardwired value.
_kln89->DrawText("NAV D", 2, 9, 3);
// TODO - add error physics to FG GPS where the alt value comes from.
char buf[6];
int n = snprintf(buf, 5, "%i", _kln89->_altUnits == GPS_ALT_UNITS_FT ? (int)_kln89->_alt : (int)(_kln89->_alt * SG_FEET_TO_METER));
_kln89->DrawText((string)buf, 2, 13-n, 2);
_kln89->DrawText(_kln89->_altUnits == GPS_ALT_UNITS_FT ? "ft" : "m", 2, 13, 2);
// FIXME - hardwired values.
// Note that a 5th digit if required is left padded one further at position 7.
_kln89->DrawText(_kln89->_distUnits == GPS_DIST_UNITS_NM ? "0.02nm" : "0.03km", 2, 8, 0);
}
KLN89Page::Update(dt);
}

View File

@@ -0,0 +1,39 @@
// kln89_page_*.[ch]xx - this file is one of the "pages" that
// are used in the KLN89 GPS unit simulation.
//
// Written by David Luff, started 2005.
//
// Copyright (C) 2005 - David C Luff - daveluff AT ntlworld.com
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation; either version 2 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
//
// $Id$
#ifndef _KLN89_PAGE_OTH
#define _KLN89_PAGE_OTH
#include "kln89.hxx"
class KLN89OthPage : public KLN89Page {
public:
KLN89OthPage(KLN89* parent);
~KLN89OthPage();
void Update(double dt);
};
#endif // _KLN89_PAGE_OTH

View File

@@ -0,0 +1,332 @@
// kln89_page_*.[ch]xx - this file is one of the "pages" that
// are used in the KLN89 GPS unit simulation.
//
// Written by David Luff, started 2005.
//
// Copyright (C) 2005 - David C Luff - daveluff AT ntlworld.com
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation; either version 2 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
//
// $Id$
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
#include "kln89_page_set.hxx"
#include <iostream>
using namespace std;
KLN89SetPage::KLN89SetPage(KLN89* parent)
: KLN89Page(parent) {
_nSubPages = 11;
_subPage = 0;
_name = "SET";
}
KLN89SetPage::~KLN89SetPage() {
}
void KLN89SetPage::Update(double dt) {
string sBaro, sAlt, sVel;
switch(_subPage+1) {
case 1:
_kln89->DrawText("INIT POS:", 2, 0, 3);
break;
case 2:
_kln89->DrawText("DATE", 2, 0, 3);
_kln89->DrawText("TIME", 2, 0, 2);
_kln89->DrawText("Cord", 2, 0, 1);
_kln89->DrawText("Mag Var:", 2, 0, 0);
break;
case 3:
_kln89->DrawText("Update DB on", 2, 1, 3);
_kln89->DrawText("ground only", 2, 1, 2);
_kln89->DrawText("Key", 2, 0, 1);
_kln89->DrawText("Update pub DB?", 2, 0, 0);
break;
case 4:
//cout << "_uLinePos = " << _uLinePos << '\n';
_kln89->DrawText("TURN", 2, 5, 3);
_kln89->DrawText("ANTICIPATION", 2, 1, 2);
if(_kln89->_mode == KLN89_MODE_CRSR && _uLinePos == 1) {
if(!_kln89->_blink) {
_kln89->DrawText((_kln89->GetTurnAnticipation() ? "ENABLED" : "DISABLED"), 2, 3, 1);
}
_kln89->Underline(2, 3, 1, 8);
} else {
_kln89->DrawText((_kln89->GetTurnAnticipation() ? "ENABLED" : "DISABLED"), 2, 3, 1);
}
break;
case 5:
_kln89->DrawText("Default First", 2, 0, 3);
_kln89->DrawText("Character of", 2, 1, 2);
_kln89->DrawText("Wpt identifier", 2, 0, 1);
_kln89->DrawText("Entry:", 2, 3, 0);
if(_kln89->_mode == KLN89_MODE_CRSR && _uLinePos == 1) {
if(!_kln89->_blink) {
_kln89->DrawChar(_kln89->_defaultFirstChar, 2, 10, 0);
}
_kln89->Underline(2, 10, 0, 1);
} else {
_kln89->DrawChar(_kln89->_defaultFirstChar, 2, 10, 0);
}
break;
case 6:
_kln89->DrawText("NEAREST APT", 2, 1, 3);
_kln89->DrawText("CRITERIA", 2, 3, 2);
_kln89->DrawText("Length:", 2, 0, 1);
_kln89->DrawText("Surface:", 2, 0, 0);
break;
case 7:
_kln89->DrawText("SUA ALERT", 2, 3, 3);
if(_kln89->_mode == KLN89_MODE_CRSR && _uLinePos == 1) {
if(!_kln89->_blink) {
_kln89->DrawText((_kln89->GetSuaAlertEnabled() ? "ENABLED" : "DISABLED"), 2, 4, 2);
}
_kln89->Underline(2, 4, 2, 8);
} else {
_kln89->DrawText((_kln89->GetSuaAlertEnabled() ? "ENABLED" : "DISABLED"), 2, 4, 2);
}
if(_kln89->GetSuaAlertEnabled()) {
_kln89->DrawText("Buffer:", 2, 0, 1);
_kln89->DrawSpecialChar(5, 2, 7, 1); // +- sign.
if(_kln89->_mode == KLN89_MODE_CRSR && _uLinePos == 2) {
if(!_kln89->_blink) {
_kln89->DrawText("00300", 2, 8, 1); // TODO - fix this hardwiring!!!!
}
_kln89->Underline(2, 8, 1, 5);
} else {
_kln89->DrawText("00300", 2, 8, 1); // TODO - fix this hardwiring!!!!
}
_kln89->DrawText("ft", 2, 13, 1); // TODO - fix this hardwiring!!!!
}
break;
case 8:
_kln89->DrawText("SET UNITS:", 2, 3, 3);
_kln89->DrawText("Baro :", 2, 0, 2);
_kln89->DrawText("Alt-APT :", 2, 0, 1);
_kln89->DrawText("Dist-Vel:", 2, 0, 0);
switch(_kln89->_baroUnits) {
case GPS_PRES_UNITS_IN:
sBaro = "\"";
break;
case GPS_PRES_UNITS_MB:
sBaro = "mB";
break;
case GPS_PRES_UNITS_HP:
sBaro = "hP";
break;
}
if(_kln89->_altUnits == GPS_ALT_UNITS_FT) sAlt = "ft";
else sAlt = "m";
if(_kln89->_distUnits == GPS_DIST_UNITS_NM) sVel = "nm-kt";
else sVel = "km-";
if(_kln89->_mode == KLN89_MODE_CRSR && _uLinePos == 1) {
if(!_kln89->_blink) {
_kln89->DrawText(sBaro, 2, 10, 2);
}
_kln89->Underline(2, 10, 2, 2);
} else {
_kln89->DrawText(sBaro, 2, 10, 2);
}
if(_kln89->_mode == KLN89_MODE_CRSR && _uLinePos == 2) {
if(!_kln89->_blink) {
_kln89->DrawText(sAlt, 2, 10, 1);
}
_kln89->Underline(2, 10, 1, 2);
} else {
_kln89->DrawText(sAlt, 2, 10, 1);
}
if(_kln89->_mode == KLN89_MODE_CRSR && _uLinePos == 3) {
if(!_kln89->_blink) {
_kln89->DrawText(sVel, 2, 10, 0);
if(_kln89->_distUnits != GPS_DIST_UNITS_NM) _kln89->DrawKPH(2, 13, 0);
}
_kln89->Underline(2, 10, 0, 5);
} else {
_kln89->DrawText(sVel, 2, 10, 0);
if(_kln89->_distUnits != GPS_DIST_UNITS_NM) _kln89->DrawKPH(2, 13, 0);
}
break;
case 9:
_kln89->DrawText("Altitude", 2, 3, 3);
_kln89->DrawText("Alert:", 2, 1, 2);
break;
case 10:
_kln89->DrawText("BUS MONITOR", 2, 2, 3);
_kln89->DrawText("Bus Volt", 2, 0, 2);
_kln89->DrawText("Alert Volt", 2, 0, 1);
_kln89->DrawText("Alert Delay", 2, 0, 0);
break;
case 11:
_kln89->DrawText("MIN DISPLAY", 2, 2, 3);
_kln89->DrawText("BRIGHTNESS ADJ", 2, 1, 2);
if(_kln89->_mode == KLN89_MODE_CRSR && _uLinePos == 1) {
if(!_kln89->_blink) {
_kln89->DrawChar('0' + _kln89->GetMinDisplayBrightness(), 2, 6, 0);
}
_kln89->Underline(2, 6, 0, 1);
} else {
_kln89->DrawChar('0' + _kln89->GetMinDisplayBrightness(), 2, 6, 0);
}
if(_kln89->GetMinDisplayBrightness() == 4) {
_kln89->DrawText("Default", 2, 8, 0);
}
break;
}
KLN89Page::Update(dt);
}
void KLN89SetPage::CrsrPressed() {
if(_kln89->_mode == KLN89_MODE_DISP) return;
if(_kln89->_obsMode) {
_uLinePos = 0;
} else {
_uLinePos = 1;
}
switch(_subPage+1) {
case 1:
break;
case 2:
break;
case 3:
break;
case 4:
_maxULinePos = 1;
break;
case 5:
_maxULinePos = 1;
break;
case 6:
_maxULinePos = 2;
break;
case 7:
if(_kln89->GetSuaAlertEnabled()) _maxULinePos = 2;
else _maxULinePos = 1;
break;
case 8:
_maxULinePos = 3;
break;
case 9:
break;
case 10:
break;
case 11:
_maxULinePos = 1;
break;
}
}
void KLN89SetPage::Knob2Left1() {
if(_kln89->_mode != KLN89_MODE_CRSR || _uLinePos == 0) {
KLN89Page::Knob2Left1();
} else {
switch(_subPage+1) {
case 1:
break;
case 2:
break;
case 3:
break;
case 4:
if(_uLinePos == 1) {
_kln89->SetTurnAnticipation(!_kln89->GetTurnAnticipation());
}
break;
case 5:
if(_uLinePos == 1) {
_kln89->_defaultFirstChar = _kln89->DecChar(_kln89->_defaultFirstChar, false, true);
}
break;
case 6:
break;
case 7:
if(_uLinePos == 1) {
_kln89->SetSuaAlertEnabled(!_kln89->GetSuaAlertEnabled());
_maxULinePos = (_kln89->GetSuaAlertEnabled() ? 2 : 1);
} else if(_uLinePos == 2) {
// TODO - implement variable sua alert buffer
}
break;
case 8:
if(_uLinePos == 1) { // baro units
_kln89->SetBaroUnits(_kln89->GetBaroUnits() - 1, true);
} else if(_uLinePos == 2) {
_kln89->SetAltUnitsSI(!_kln89->GetAltUnitsSI());
} else if(_uLinePos == 3) {
_kln89->SetDistVelUnitsSI(!_kln89->GetDistVelUnitsSI());
}
break;
case 11:
if(_uLinePos == 1) {
_kln89->DecrementMinDisplayBrightness();
}
break;
}
}
}
void KLN89SetPage::Knob2Right1() {
if(_kln89->_mode != KLN89_MODE_CRSR || _uLinePos == 0) {
KLN89Page::Knob2Right1();
} else {
switch(_subPage+1) {
case 1:
break;
case 2:
break;
case 3:
break;
case 4:
if(_uLinePos == 1) { // Which it should be!
_kln89->SetTurnAnticipation(!_kln89->GetTurnAnticipation());
}
break;
case 5:
if(_uLinePos == 1) {
_kln89->_defaultFirstChar = _kln89->IncChar(_kln89->_defaultFirstChar, false, true);
}
break;
case 6:
break;
case 7:
if(_uLinePos == 1) {
_kln89->SetSuaAlertEnabled(!_kln89->GetSuaAlertEnabled());
_maxULinePos = (_kln89->GetSuaAlertEnabled() ? 2 : 1);
} else if(_uLinePos == 2) {
// TODO - implement variable sua alert buffer
}
break;
case 8:
if(_uLinePos == 1) { // baro units
_kln89->SetBaroUnits(_kln89->GetBaroUnits() + 1, true);
} else if(_uLinePos == 2) {
_kln89->SetAltUnitsSI(!_kln89->GetAltUnitsSI());
} else if(_uLinePos == 3) {
_kln89->SetDistVelUnitsSI(!_kln89->GetDistVelUnitsSI());
}
break;
case 11:
if(_uLinePos == 1) {
_kln89->IncrementMinDisplayBrightness();
}
break;
}
}
}

View File

@@ -0,0 +1,42 @@
// kln89_page_*.[ch]xx - this file is one of the "pages" that
// are used in the KLN89 GPS unit simulation.
//
// Written by David Luff, started 2005.
//
// Copyright (C) 2005 - David C Luff - daveluff AT ntlworld.com
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation; either version 2 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
//
// $Id$
#ifndef _KLN89_PAGE_SET_HXX
#define _KLN89_PAGE_SET_HXX
#include "kln89.hxx"
class KLN89SetPage : public KLN89Page {
public:
KLN89SetPage(KLN89* parent);
~KLN89SetPage();
void Update(double dt);
void CrsrPressed();
void Knob2Left1();
void Knob2Right1();
};
#endif // _KLN89_PAGE_SET_HXX

View File

@@ -0,0 +1,55 @@
// kln89_page_*.[ch]xx - this file is one of the "pages" that
// are used in the KLN89 GPS unit simulation.
//
// Written by David Luff, started 2005.
//
// Copyright (C) 2005 - David C Luff - daveluff AT ntlworld.com
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation; either version 2 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
//
// $Id$
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
#include "kln89_page_usr.hxx"
KLN89UsrPage::KLN89UsrPage(KLN89* parent)
: KLN89Page(parent) {
_nSubPages = 4;
_subPage = 0;
_name = "USR";
}
KLN89UsrPage::~KLN89UsrPage() {
}
void KLN89UsrPage::Update(double dt) {
// bool actPage = (_kln89->_activePage->GetName() == "ACT" ? true : false);
bool crsr = (_kln89->_mode == KLN89_MODE_CRSR);
bool blink = _kln89->_blink;
if(_subPage == 0) {
// Hardwire no-waypoint output for now
_kln89->DrawText("0", 2, 1, 3);
_kln89->DrawText("USR at:", 2, 7, 3);
if(!(crsr && _uLinePos == 6 && blink)) _kln89->DrawText("User Pos L/L?", 2, 1, 2);
if(!(crsr && _uLinePos == 7 && blink)) _kln89->DrawText("User Pos R/D?", 2, 1, 1);
if(!(crsr && _uLinePos == 8 && blink)) _kln89->DrawText("Present Pos?", 2, 1, 0);
}
KLN89Page::Update(dt);
}

View File

@@ -0,0 +1,39 @@
// kln89_page_*.[ch]xx - this file is one of the "pages" that
// are used in the KLN89 GPS unit simulation.
//
// Written by David Luff, started 2005.
//
// Copyright (C) 2005 - David C Luff - daveluff AT ntlworld.com
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation; either version 2 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
//
// $Id$
#ifndef _KLN89_PAGE_USR_HXX
#define _KLN89_PAGE_USR_HXX
#include "kln89.hxx"
class KLN89UsrPage : public KLN89Page {
public:
KLN89UsrPage(KLN89* parent);
~KLN89UsrPage();
void Update(double dt);
};
#endif // _KLN89_PAGE_USR_HXX

View File

@@ -0,0 +1,231 @@
// kln89_page_*.[ch]xx - this file is one of the "pages" that
// are used in the KLN89 GPS unit simulation.
//
// Written by David Luff, started 2005.
//
// Copyright (C) 2005 - David C Luff - daveluff AT ntlworld.com
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation; either version 2 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
//
// $Id$
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
#include <cstdio>
#include "kln89_page_vor.hxx"
#include <Navaids/navrecord.hxx>
using std::string;
KLN89VorPage::KLN89VorPage(KLN89* parent)
: KLN89Page(parent) {
_nSubPages = 2;
_subPage = 0;
_name = "VOR";
_vor_id = "OSI"; // TODO - check a property for an initial value to allow user-override.
np = NULL;
}
KLN89VorPage::~KLN89VorPage() {
}
void KLN89VorPage::Update(double dt) {
bool actPage = (_kln89->_activePage->GetName() == "ACT" ? true : false);
bool multi; // Not set by FindFirst...
bool exact = false;
if(_vor_id.size() == 3) exact = true;
if(np == NULL) {
np = _kln89->FindFirstVorById(_vor_id, multi, exact);
} else if(np->get_ident() != _vor_id) {
np = _kln89->FindFirstVorById(_vor_id, multi, exact);
}
//if(np == NULL) cout << "NULL... ";
//if(b == false) cout << "false...\n";
/*
if(np && b) {
cout << "VOR FOUND!\n";
} else {
cout << ":-(\n";
}
*/
if(np) {
//cout << np->id << '\n';
_vor_id = np->get_ident();
if(_kln89->GetActiveWaypoint()) {
if(_vor_id == _kln89->GetActiveWaypoint()->id) {
if(!(_kln89->_waypointAlert && _kln89->_blink)) {
// Active waypoint arrow
_kln89->DrawSpecialChar(4, 2, 0, 3);
}
}
}
if(_kln89->_mode != KLN89_MODE_CRSR) {
if(!_entInvert) {
if(!actPage) {
_kln89->DrawText(np->get_ident(), 2, 1, 3);
} else {
// If it's the ACT page, The ID is shifted slightly right to make space for the waypoint index.
_kln89->DrawText(np->get_ident(), 2, 4, 3);
char buf[3];
int n = snprintf(buf, 3, "%i", _kln89->GetActiveWaypointIndex() + 1);
_kln89->DrawText((string)buf, 2, 3 - n, 3);
}
} else {
if(!_kln89->_blink) {
_kln89->DrawText(np->get_ident(), 2, 1, 3, false, 99);
_kln89->DrawEnt();
}
}
}
if(_subPage == 0) {
//// TODO - will almost certainly have to process freq below for FG
_kln89->DrawFreq(np->get_freq(), 2, 9, 3);
// TODO - trim VOR-DME from the name, convert to uppercase, abbreviate, etc
_kln89->DrawText(np->name(), 2, 0, 2);
//cout << np->lat << "... ";
_kln89->DrawLatitude(np->get_lat(), 2, 3, 1);
_kln89->DrawLongitude(np->get_lon(), 2, 3, 0);
} else {
_kln89->DrawText("Mag Var", 2, 0, 2);
////float mvf = np->magvar * SG_RADIANS_TO_DEGREES;
//// TODO FIXME BELOW
float mvf = 0.0;
_kln89->DrawChar((mvf <= 0 ? 'E' : 'W'), 2, 9, 2);
int mvi = (int)(fabs(mvf) + 0.5);
string mvs = GPSitoa(mvi);
_kln89->DrawText(mvs, 2, 13 - mvs.size(), 2);
_kln89->DrawSpecialChar(0, 2, 13, 2);
_kln89->DrawDirDistField(np->get_lat() * SG_DEGREES_TO_RADIANS, np->get_lon() * SG_DEGREES_TO_RADIANS, 2, 0, 0,
_to_flag, (_kln89->_mode == KLN89_MODE_CRSR && _uLinePos == 4 ? true : false));
}
} else {
if(_kln89->_mode != KLN89_MODE_CRSR) _kln89->DrawText(_vor_id, 2, 1, 3);
if(_subPage == 0) {
_kln89->DrawText("---.--", 2, 9, 3);
_kln89->DrawText("--------------", 2, 0, 2);
_kln89->DrawText("- -- --.--'", 2, 3, 1);
_kln89->DrawText("---- --.--'", 2, 3, 0);
_kln89->DrawSpecialChar(0, 2, 7, 1);
_kln89->DrawSpecialChar(0, 2, 7, 0);
}
}
if(_kln89->_mode == KLN89_MODE_CRSR) {
if(_uLinePos > 0 && _uLinePos < 4) {
// TODO - blink as well
_kln89->Underline(2, _uLinePos, 3, 1);
}
for(unsigned int i = 0; i < _vor_id.size(); ++i) {
if(_uLinePos != (i + 1)) {
_kln89->DrawChar(_vor_id[i], 2, i + 1, 3);
} else {
if(!_kln89->_blink) _kln89->DrawChar(_vor_id[i], 2, i + 1, 3);
}
}
}
_id = _vor_id;
KLN89Page::Update(dt);
}
void KLN89VorPage::SetId(const string& s) {
_last_vor_id = _vor_id;
_save_vor_id = _vor_id;
_vor_id = s;
np = NULL;
}
void KLN89VorPage::CrsrPressed() {
if(_kln89->_mode == KLN89_MODE_DISP) return;
if(_kln89->_obsMode) {
_uLinePos = 0;
} else {
_uLinePos = 1;
}
if(_subPage == 0) {
_maxULinePos = 17;
} else {
_maxULinePos = 4;
}
}
void KLN89VorPage::ClrPressed() {
if(_subPage == 1 && _uLinePos == 4) {
_to_flag = !_to_flag;
}
}
void KLN89VorPage::EntPressed() {
if(_entInvert) {
_entInvert = false;
_entInvert = false;
if(_kln89->_dtoReview) {
_kln89->DtoInitiate(_vor_id);
} else {
_last_vor_id = _vor_id;
_vor_id = _save_vor_id;
}
}
}
void KLN89VorPage::Knob2Left1() {
if(_kln89->_mode != KLN89_MODE_CRSR || _uLinePos == 0) {
KLN89Page::Knob2Left1();
} else {
if(_uLinePos < 4) {
// Same logic for both pages - set the ID
_vor_id = _vor_id.substr(0, _uLinePos);
// ASSERT(_uLinePos > 0);
if(_uLinePos == (_vor_id.size() + 1)) {
_vor_id += '9';
} else {
_vor_id[_uLinePos - 1] = _kln89->DecChar(_vor_id[_uLinePos - 1], (_uLinePos == 1 ? false : true));
}
} else {
if(_subPage == 0) {
// set by name
} else {
// NO-OP - from/to field is switched by clr button, not inner knob.
}
}
}
}
void KLN89VorPage::Knob2Right1() {
if(_kln89->_mode != KLN89_MODE_CRSR || _uLinePos == 0) {
KLN89Page::Knob2Right1();
} else {
if(_uLinePos < 4) {
// Same logic for both pages - set the ID
_vor_id = _vor_id.substr(0, _uLinePos);
// ASSERT(_uLinePos > 0);
if(_uLinePos == (_vor_id.size() + 1)) {
_vor_id += 'A';
} else {
_vor_id[_uLinePos - 1] = _kln89->IncChar(_vor_id[_uLinePos - 1], (_uLinePos == 1 ? false : true));
}
} else {
if(_subPage == 0) {
// set by name
} else {
// NO-OP - from/to field is switched by clr button, not inner knob.
}
}
}
}

View File

@@ -0,0 +1,52 @@
// kln89_page_*.[ch]xx - this file is one of the "pages" that
// are used in the KLN89 GPS unit simulation.
//
// Written by David Luff, started 2005.
//
// Copyright (C) 2005 - David C Luff - daveluff AT ntlworld.com
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation; either version 2 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
//
// $Id$
#ifndef _KLN89_PAGE_VOR_HXX
#define _KLN89_PAGE_VOR_HXX
#include "kln89.hxx"
class KLN89VorPage : public KLN89Page {
public:
KLN89VorPage(KLN89* parent);
~KLN89VorPage();
void Update(double dt);
void CrsrPressed();
void ClrPressed();
void EntPressed();
void Knob2Left1();
void Knob2Right1();
void SetId(const std::string& s);
private:
std::string _vor_id;
std::string _last_vor_id;
std::string _save_vor_id;
FGNavRecord* np;
};
#endif // _KLN89_PAGE_VOR_HXX

View File

@@ -0,0 +1,172 @@
// kln89_symbols.hxx - pixel-encoded symbols for the KLN89 GPS unit simulation.
//
// Written by David Luff, started 2005.
//
// Copyright (C) 2005 David C Luff: daveluff --AT-- ntlworld --D0T-- com
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation; either version 2 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
//
// $Id$
const char NumbersBold[][8] = {{0x1E, 0x3F, 0x33, 0x33, 0x33, 0x33, 0x3F, 0x1E}, // 0
{0x0C, 0x1C, 0x1C, 0x0C, 0x0C, 0x0C, 0x1E, 0x1E}, // 1
{0x1E, 0x3F, 0x33, 0x03, 0x06, 0x1C, 0x3F, 0x3F}, // 2
{0x3E, 0x3F, 0x03, 0x1F, 0x1E, 0x03, 0x3F, 0x3E}, // 3
{0x06, 0x0E, 0x16, 0x26, 0x3F, 0x3F, 0x06, 0x06}, // 4
{0x3F, 0x3F, 0x30, 0x3E, 0x3F, 0x03, 0x3F, 0x3E}, // 5
{0x0E, 0x1E, 0x30, 0x3E, 0x3F, 0x33, 0x3F, 0x1E}, // 6
{0x3F, 0x3F, 0x03, 0x06, 0x0C, 0x18, 0x18, 0x18}, // 7
{0x1E, 0x3F, 0x33, 0x3F, 0x1E, 0x33, 0x3F, 0x1E}, // 8
{0x1E, 0x3F, 0x33, 0x3F, 0x1F, 0x03, 0x1E, 0x1C}}; // 9
const char UpperAlpha[][8] = {{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // !
{0x00, 0x1B, 0x09, 0x12, 0x00, 0x00, 0x00, 0x00}, // "
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // #
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // $
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // %
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // &
{0x00, 0x06, 0x02, 0x04, 0x00, 0x00, 0x00, 0x00}, // '
{0x00, 0x02, 0x04, 0x08, 0x08, 0x08, 0x04, 0x02}, // (
{0x00, 0x08, 0x04, 0x02, 0x02, 0x02, 0x04, 0x08}, // )
{0x00, 0x00, 0x0A, 0x04, 0x1F, 0x04, 0x0A, 0x00}, // *
{0x00, 0x00, 0x04, 0x04, 0x1F, 0x04, 0x04, 0x00}, // +
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // ,
{0x00, 0x00, 0x00, 0x00, 0x1F, 0x00, 0x00, 0x00}, // -
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0C, 0x0C}, // .
{0x00, 0x00, 0x01, 0x02, 0x04, 0x08, 0x10, 0x00}, // /
{0x00, 0x0E, 0x11, 0x13, 0x15, 0x19, 0x11, 0x0E}, // 0
{0x00, 0x04, 0x0C, 0x04, 0x04, 0x04, 0x04, 0x0E}, // 1
{0x00, 0x0E, 0x11, 0x01, 0x02, 0x04, 0x08, 0x1F}, // 2
{0x00, 0x0E, 0x11, 0x01, 0x0E, 0x01, 0x11, 0x0E}, // 3
{0x00, 0x02, 0x06, 0x0A, 0x12, 0x1F, 0x02, 0x02}, // 4
{0x00, 0x1F, 0x10, 0x1E, 0x01, 0x01, 0x11, 0x0E}, // 5
{0x00, 0x06, 0x08, 0x10, 0x1E, 0x11, 0x11, 0x0E}, // 6
{0x00, 0x1F, 0x01, 0x02, 0x04, 0x08, 0x08, 0x08}, // 7
{0x00, 0x0E, 0x11, 0x11, 0x0E, 0x11, 0x11, 0x0E}, // 8
{0x00, 0x0E, 0x11, 0x11, 0x0F, 0x01, 0x02, 0x0C}, // 9
{0x00, 0x00, 0x0C, 0x0C, 0x00, 0x00, 0x0C, 0x0C}, // :
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // ;
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // <
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // =
{0x00, 0x10, 0x08, 0x04, 0x02, 0x04, 0x08, 0x10}, // >
{0x00, 0x0E, 0x11, 0x01, 0x02, 0x04, 0x00, 0x04}, // ?
{0x00, 0x0E, 0x11, 0x17, 0x15, 0x17, 0x10, 0x0F}, // @
{0x00, 0x0E, 0x11, 0x11, 0x1F, 0x11, 0x11, 0x11}, // A
{0x00, 0x1E, 0x11, 0x11, 0x1E, 0x11, 0x11, 0x1E}, // B
{0x00, 0x0E, 0x11, 0x10, 0x10, 0x10, 0x11, 0x0E}, // C
{0x00, 0x1C, 0x12, 0x11, 0x11, 0x11, 0x12, 0x1C}, // D
{0x00, 0x1F, 0x10, 0x10, 0x1E, 0x10, 0x10, 0x1F}, // E
{0x00, 0x1F, 0x10, 0x10, 0x1E, 0x10, 0x10, 0x10}, // F
{0x00, 0x0E, 0x11, 0x10, 0x10, 0x17, 0x11, 0x0E}, // G
{0x00, 0x11, 0x11, 0x11, 0x1F, 0x11, 0x11, 0x11}, // H
{0x00, 0x0E, 0x04, 0x04, 0x04, 0x04, 0x04, 0x0E}, // I
{0x00, 0x07, 0x02, 0x02, 0x02, 0x02, 0x12, 0x0C}, // J
{0x00, 0x11, 0x12, 0x14, 0x18, 0x14, 0x12, 0x11}, // K
{0x00, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x1F}, // L
{0x00, 0x11, 0x1B, 0x15, 0x15, 0x11, 0x11, 0x11}, // M
{0x00, 0x11, 0x11, 0x19, 0x15, 0x13, 0x11, 0x11}, // N
{0x00, 0x0E, 0x11, 0x11, 0x11, 0x11, 0x11, 0x0E}, // O
{0x00, 0x1E, 0x11, 0x11, 0x1E, 0x10, 0x10, 0x10}, // P
{0x00, 0x0E, 0x11, 0x11, 0x11, 0x15, 0x12, 0x0D}, // Q
{0x00, 0x1E, 0x11, 0x11, 0x1E, 0x14, 0x12, 0x11}, // R
{0x00, 0x0F, 0x10, 0x10, 0x0E, 0x01, 0x01, 0x1E}, // S
{0x00, 0x1F, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04}, // T
{0x00, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x0E}, // U
{0x00, 0x11, 0x11, 0x11, 0x11, 0x11, 0x0A, 0x04}, // V
{0x00, 0x11, 0x11, 0x11, 0x15, 0x15, 0x15, 0x0A}, // W
{0x00, 0x11, 0x11, 0x0A, 0x04, 0x0A, 0x11, 0x11}, // X
{0x00, 0x11, 0x11, 0x11, 0x0A, 0x04, 0x04, 0x04}, // Y
{0x00, 0x1F, 0x01, 0x02, 0x04, 0x08, 0x10, 0x1F}, // Z
{0x00, 0x1E, 0x10, 0x10, 0x10, 0x10, 0x10, 0x1E}, // [
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, /* \ */ // Have to be carefull with forward slash - it's multiline comment!
{0x00, 0x1E, 0x02, 0x02, 0x02, 0x02, 0x02, 0x1E}, // ]
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // ^
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1F}, // _
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // `
{0x00, 0x00, 0x00, 0x0E, 0x01, 0x0F, 0x11, 0x0F}, // a
{0x00, 0x10, 0x10, 0x10, 0x16, 0x19, 0x11, 0x1E}, // b
{0x00, 0x00, 0x00, 0x0E, 0x10, 0x10, 0x10, 0x0E}, // c
{0x00, 0x01, 0x01, 0x01, 0x0D, 0x13, 0x11, 0x0F}, // d
{0x00, 0x00, 0x00, 0x0E, 0x11, 0x1F, 0x10, 0x0E}, // e
{0x00, 0x06, 0x09, 0x08, 0x1C, 0x08, 0x08, 0x08}, // f
{0x00, 0x00, 0x0E, 0x11, 0x11, 0x0F, 0x01, 0x0E}, // g
{0x00, 0x10, 0x10, 0x16, 0x19, 0x11, 0x11, 0x11}, // h
{0x00, 0x04, 0x00, 0x0C, 0x04, 0x04, 0x04, 0x0E}, // i
{0x00, 0x02, 0x00, 0x06, 0x02, 0x02, 0x12, 0x0C}, // j (Never found j - this is a guess!)
{0x00, 0x10, 0x10, 0x11, 0x12, 0x1C, 0x12, 0x11}, // k
{0x00, 0x0C, 0x04, 0x04, 0x04, 0x04, 0x04, 0x0E}, // l
{0x00, 0x00, 0x00, 0x1A, 0x15, 0x11, 0x11, 0x11}, // m
{0x00, 0x00, 0x00, 0x16, 0x19, 0x11, 0x11, 0x11}, // n
{0x00, 0x00, 0x00, 0x0E, 0x11, 0x11, 0x11, 0x0E}, // o
{0x00, 0x00, 0x00, 0x1E, 0x11, 0x1E, 0x10, 0x10}, // p
{0x00, 0x00, 0x00, 0x0F, 0x11, 0x0F, 0x01, 0x01}, // q
{0x00, 0x00, 0x00, 0x16, 0x19, 0x10, 0x10, 0x10}, // r
{0x00, 0x00, 0x00, 0x0E, 0x10, 0x0E, 0x01, 0x0E}, // s
{0x00, 0x08, 0x08, 0x1C, 0x08, 0x08, 0x09, 0x06}, // t
{0x00, 0x00, 0x00, 0x11, 0x11, 0x11, 0x13, 0x0D}, // u
{0x00, 0x00, 0x00, 0x11, 0x11, 0x11, 0x0A, 0x04}, // v
{0x00, 0x00, 0x00, 0x11, 0x11, 0x11, 0x15, 0x0A}, // w
{0x00, 0x00, 0x00, 0x11, 0x0A, 0x04, 0x0A, 0x11}, // x
{0x00, 0x00, 0x00, 0x11, 0x11, 0x0F, 0x01, 0x0E}, // y
{0x00, 0x00, 0x00, 0x1F, 0x02, 0x04, 0x08, 0x1F}}; // z
const char SpecialChar[][8] = {{0x00, 0x04, 0x0A, 0x04, 0x00, 0x00, 0x00, 0x00}, // 0: degrees sign
{0x00, 0x00, 0x00, 0x04, 0x0E, 0x04, 0x00, 0x00}, // 1: Smaller plus sign
{0x00, 0x00, 0x00, 0x08, 0x1C, 0x08, 0x00, 0x00}, // 2: Left-shifted smaller plus sign
{0x00, 0x00, 0x04, 0x06, 0x3F, 0x06, 0x04, 0x00}, // 3: Active arrow
{0x00, 0x00, 0x04, 0x06, 0x1F, 0x06, 0x04, 0x00}, // 4: Slightly shorter active arrow
{0x00, 0x04, 0x04, 0x1F, 0x04, 0x04, 0x00, 0x1F}, // 5: +- sign.
{0x00, 0x1E, 0x11, 0x11, 0x3F, 0x11, 0x11, 0x1E}, // 6: the barred 'D' of the DTO symbol
{0x00, 0x00, 0x04, 0x0C, 0x1F, 0x0C, 0x04, 0x00}}; // 7: Left pointing arrow.
// For small char, the first char is the number of pixels horizontally that are used for drawing,
// since these chars are not fixed width. (Used for the labels in the moving map display).
// The hex values are referenced from the right-most pixel position, and the chars are 5 pixels high (last 5 chars).
const char SmallChar[][8] = {{0x03, 0x00, 0x00, 0x07, 0x05, 0x05, 0x05, 0x07}, // 0
{0x03, 0x00, 0x00, 0x02, 0x06, 0x02, 0x02, 0x07}, // 1
{0x03, 0x00, 0x00, 0x06, 0x01, 0x02, 0x04, 0x07}, // 2
{0x03, 0x00, 0x00, 0x07, 0x01, 0x03, 0x01, 0x07}, // 3
{0x03, 0x00, 0x00, 0x01, 0x03, 0x05, 0x07, 0x01}, // 4
{0x03, 0x00, 0x00, 0x07, 0x04, 0x07, 0x01, 0x07}, // 5
{0x03, 0x00, 0x00, 0x07, 0x04, 0x07, 0x05, 0x07}, // 6
{0x03, 0x00, 0x00, 0x07, 0x01, 0x02, 0x02, 0x02}, // 7
{0x03, 0x00, 0x00, 0x07, 0x05, 0x07, 0x05, 0x07}, // 8
{0x03, 0x00, 0x00, 0x07, 0x05, 0x07, 0x01, 0x07}, // 9
{0x03, 0x00, 0x00, 0x02, 0x05, 0x05, 0x07, 0x05}, // A
{0x03, 0x00, 0x00, 0x06, 0x05, 0x06, 0x05, 0x06}, // B
{0x03, 0x00, 0x00, 0x03, 0x04, 0x04, 0x04, 0x03}, // C
{0x04, 0x00, 0x00, 0x0E, 0x09, 0x09, 0x09, 0x0E}, // D
{0x03, 0x00, 0x00, 0x07, 0x04, 0x06, 0x04, 0x07}, // E
{0x03, 0x00, 0x00, 0x07, 0x04, 0x06, 0x04, 0x04}, // F
{0x04, 0x00, 0x00, 0x06, 0x08, 0x0B, 0x09, 0x06}, // G
{0x03, 0x00, 0x00, 0x05, 0x05, 0x07, 0x05, 0x05}, // H
{0x03, 0x00, 0x00, 0x07, 0x02, 0x02, 0x02, 0x07}, // I
{0x04, 0x00, 0x00, 0x01, 0x01, 0x01, 0x09, 0x06}, // J
{0x04, 0x00, 0x00, 0x09, 0x0A, 0x0C, 0x0A, 0x09}, // K
{0x03, 0x00, 0x00, 0x04, 0x04, 0x04, 0x04, 0x07}, // L
{0x05, 0x00, 0x00, 0x11, 0x1B, 0x15, 0x11, 0x11}, // M
{0x04, 0x00, 0x00, 0x09, 0x0D, 0x0F, 0x0B, 0x09}, // N
{0x04, 0x00, 0x00, 0x06, 0x09, 0x09, 0x09, 0x06}, // O
{0x03, 0x00, 0x00, 0x07, 0x05, 0x07, 0x04, 0x04}, // P
{0x04, 0x00, 0x00, 0x06, 0x09, 0x09, 0x0B, 0x07}, // Q
{0x04, 0x00, 0x00, 0x0E, 0x09, 0x0E, 0x0A, 0x09}, // R
{0x04, 0x00, 0x00, 0x07, 0x08, 0x06, 0x01, 0x0E}, // S
{0x03, 0x00, 0x00, 0x07, 0x02, 0x02, 0x02, 0x02}, // T
{0x03, 0x00, 0x00, 0x05, 0x05, 0x05, 0x05, 0x07}, // U
{0x03, 0x00, 0x00, 0x05, 0x05, 0x05, 0x05, 0x02}, // V
{0x05, 0x00, 0x00, 0x11, 0x11, 0x11, 0x15, 0x0A}, // W
{0x03, 0x00, 0x00, 0x05, 0x05, 0x02, 0x05, 0x05}, // X
{0x03, 0x00, 0x00, 0x05, 0x05, 0x07, 0x02, 0x02}, // Y
{0x03, 0x00, 0x00, 0x07, 0x01, 0x02, 0x04, 0x07}}; // Z

View File

@@ -0,0 +1,14 @@
src/Instrumentation/ - gauge and avionics support code
This directory contains code to support gauges, avionics, and other
instruments in FlightGear. The file instrument_mgr.[ch]xx contains a
subsystem group that holds all of the individual instruments. Every
instrument should extend FGSubsystem, and then should be added to the
group in the FGInstrumentMgr constructor.
Code is gradually moving into here from other areas, especially the
src/Cockpit/ directory. Eventually, there will be an XML
configuration file to select what instrumentation modules should be
available, so that different aircraft can have appropriate support.

281
src/Instrumentation/adf.cxx Normal file
View File

@@ -0,0 +1,281 @@
// adf.cxx - distance-measuring equipment.
// Written by David Megginson, started 2003.
//
// This file is in the Public Domain and comes with no warranty.
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif
#include <simgear/compiler.h>
#include <simgear/math/sg_geodesy.hxx>
#include <simgear/math/sg_random.hxx>
#include <simgear/timing/sg_time.hxx>
#include <Main/fg_props.hxx>
#include <Main/util.hxx>
#include <Navaids/navlist.hxx>
#include "adf.hxx"
#include <Sound/morse.hxx>
#include <simgear/sound/sample_group.hxx>
#include <iostream>
#include <string>
#include <sstream>
using std::string;
// Use a bigger number to be more responsive, or a smaller number
// to be more sluggish.
#define RESPONSIVENESS 0.5
/**
* Fiddle with the reception range a bit.
*
* TODO: better reception at night (??).
*/
static double
adjust_range (double transmitter_elevation_ft, double aircraft_altitude_ft,
double max_range_nm)
{
double delta_elevation_ft =
aircraft_altitude_ft - transmitter_elevation_ft;
double range_nm = max_range_nm;
// kludge slightly better reception at
// altitude
if (delta_elevation_ft < 0)
delta_elevation_ft = 200;
if (delta_elevation_ft <= 1000)
range_nm *= sqrt(delta_elevation_ft / 1000);
else if (delta_elevation_ft >= 5000)
range_nm *= sqrt(delta_elevation_ft / 5000);
if (range_nm >= max_range_nm * 3)
range_nm = max_range_nm * 3;
double rand = sg_random();
return range_nm + (range_nm * rand * rand);
}
ADF::ADF (SGPropertyNode *node )
:
_time_before_search_sec(0),
_last_frequency_khz(-1),
_transmitter_valid(false),
_transmitter_pos(SGGeod::fromDeg(0, 0)),
_transmitter_cart(0, 0, 0),
_transmitter_range_nm(0),
_ident_count(0),
_last_ident_time(0),
_last_volume(-1),
_sgr(0)
{
readConfig(node, "adf");
}
ADF::~ADF ()
{
}
void
ADF::init ()
{
string branch = nodePath();
SGPropertyNode *node = fgGetNode(branch.c_str(), true );
initServicePowerProperties(node);
// instrument properties
_error_node = node->getChild("error-deg", 0, true);
_mode_node = node->getChild("mode", 0, true);
_volume_node = node->getChild("volume-norm", 0, true);
_in_range_node = node->getChild("in-range", 0, true);
_bearing_node = node->getChild("indicated-bearing-deg", 0, true);
_ident_node = node->getChild("ident", 0, true);
_ident_audible_node = node->getChild("ident-audible", 0, true);
// frequency properties
SGPropertyNode *fnode = node->getChild("frequencies", 0, true);
_frequency_node = fnode->getChild("selected-khz", 0, true);
// foreign simulator properties
_heading_node = fgGetNode("/orientation/heading-deg", true);
// sound support (audible ident code)
SGSoundMgr *smgr = globals->get_subsystem<SGSoundMgr>();
_sgr = smgr->find("avionics", true);
_sgr->tie_to_listener();
std::ostringstream temp;
temp << name() << number();
_adf_ident = temp.str();
}
void
ADF::update (double delta_time_sec)
{
// If it's off, don't waste any time.
if (!isServiceableAndPowered()) {
_in_range_node->setBoolValue(false);
_ident_node->setStringValue("");
return;
}
string mode = _mode_node->getStringValue();
if (mode == "ant" || mode == "test") set_bearing(delta_time_sec, 90);
if (mode != "bfo" && mode != "adf") {
_in_range_node->setBoolValue(false);
_ident_node->setStringValue("");
return;
}
// Get the frequency
int frequency_khz = _frequency_node->getIntValue();
if (frequency_khz != _last_frequency_khz) {
_time_before_search_sec = 0;
_last_frequency_khz = frequency_khz;
}
SGGeod acPos(globals->get_aircraft_position());
// On timeout, scan again
_time_before_search_sec -= delta_time_sec;
if (_time_before_search_sec < 0)
search(frequency_khz, acPos);
if (!_transmitter_valid) {
_in_range_node->setBoolValue(false);
_ident_node->setStringValue("");
return;
}
// Calculate the bearing to the transmitter
SGVec3d location = globals->get_aircraft_position_cart();
double distance_nm = dist(_transmitter_cart, location) * SG_METER_TO_NM;
double range_nm = adjust_range(_transmitter_pos.getElevationFt(),
acPos.getElevationFt(),
_transmitter_range_nm);
if (distance_nm <= range_nm) {
double bearing, az2, s;
double heading = _heading_node->getDoubleValue();
geo_inverse_wgs_84(acPos, _transmitter_pos,
&bearing, &az2, &s);
_in_range_node->setBoolValue(true);
_ident_node->setStringValue(_last_ident);
bearing -= heading;
if (bearing < 0)
bearing += 360;
set_bearing(delta_time_sec, bearing);
// adf ident sound
float volume;
if ( _ident_audible_node->getBoolValue() )
volume = _volume_node->getFloatValue();
else
volume = 0.0;
if ( volume != _last_volume ) {
_last_volume = volume;
SGSoundSample *sound;
sound = _sgr->find( _adf_ident );
if ( sound != NULL )
sound->set_volume( volume );
else
SG_LOG( SG_INSTR, SG_ALERT, "Can't find adf-ident sound" );
}
time_t cur_time = globals->get_time_params()->get_cur_time();
if ( _last_ident_time < cur_time - 30 ) {
_last_ident_time = cur_time;
_ident_count = 0;
}
if ( _ident_count < 4 ) {
if ( !_sgr->is_playing(_adf_ident) && (volume > 0.05) ) {
_sgr->play_once( _adf_ident );
++_ident_count;
}
}
} else {
_in_range_node->setBoolValue(false);
_ident_node->setStringValue("");
_sgr->stop( _adf_ident );
}
}
void
ADF::search (double frequency_khz, const SGGeod& pos)
{
string ident = "";
// reset search time
_time_before_search_sec = 1.0;
FGNavList::TypeFilter filter(FGPositioned::NDB);
FGNavRecord *nav = FGNavList::findByFreq(frequency_khz, pos, &filter);
_transmitter_valid = (nav != NULL);
if ( _transmitter_valid ) {
ident = nav->get_trans_ident();
if ( ident != _last_ident ) {
_transmitter_pos = nav->geod();
_transmitter_cart = nav->cart();
_transmitter_range_nm = nav->get_range();
}
}
if ( _last_ident != ident ) {
_last_ident = ident;
_ident_node->setStringValue(ident.c_str());
if ( _sgr->exists( _adf_ident ) ) {
// stop is required! -- remove alone wouldn't stop immediately
_sgr->stop( _adf_ident );
_sgr->remove( _adf_ident );
}
SGSoundSample *sound;
sound = FGMorse::instance()->make_ident( ident, FGMorse::LO_FREQUENCY );
sound->set_volume(_last_volume = 0);
_sgr->add( sound, _adf_ident );
int offset = (int)(sg_random() * 30.0);
_ident_count = offset / 4;
_last_ident_time = globals->get_time_params()->get_cur_time() -
offset;
}
}
void
ADF::set_bearing (double dt, double bearing_deg)
{
double old_bearing_deg = _bearing_node->getDoubleValue();
while ((bearing_deg - old_bearing_deg) >= 180)
old_bearing_deg += 360;
while ((bearing_deg - old_bearing_deg) <= -180)
old_bearing_deg -= 360;
bearing_deg += _error_node->getDoubleValue();
bearing_deg =
fgGetLowPass(old_bearing_deg, bearing_deg, dt * RESPONSIVENESS);
_bearing_node->setDoubleValue(bearing_deg);
}
// Register the subsystem.
#if 0
SGSubsystemMgr::InstancedRegistrant<ADF> registrantADF(
SGSubsystemMgr::FDM,
{{"instrumentation", SGSubsystemMgr::Dependency::HARD}},
0.15);
#endif
// end of adf.cxx

View File

@@ -0,0 +1,86 @@
// adf.hxx - automatic direction finder.
// Written by David Megginson, started 2003.
//
// This file is in the Public Domain and comes with no warranty.
#ifndef __INSTRUMENTS_ADF_HXX
#define __INSTRUMENTS_ADF_HXX 1
#include <string>
#include <Instrumentation/AbstractInstrument.hxx>
#include <simgear/math/SGMath.hxx>
class SGSampleGroup;
/**
* Model an ADF radio.
*
* Input properties:
*
* /position/longitude-deg
* /position/latitude-deg
* /position/altitude-ft
* /orientation/heading-deg
* /systems/electrical/outputs/adf
* /instrumentation/adf/serviceable
* /instrumentation/adf/error-deg
* /instrumentation/adf/frequencies/selected-khz
* /instrumentation/adf/mode
* /instrumentation/adf/ident-audible
* /instrumentation/adf/volume-norm
*
* Output properties:
*
* /instrumentation/adf/in-range
* /instrumentation/adf/indicated-bearing-deg
* /instrumentation/adf/ident
*/
class ADF : public AbstractInstrument
{
public:
ADF ( SGPropertyNode *node );
virtual ~ADF ();
// Subsystem API.
void init() override;
void update(double delta_time_sec) override;
// Subsystem identification.
static const char* staticSubsystemClassId() { return "adf"; }
private:
void set_bearing (double delta_time_sec, double bearing);
void search (double frequency, const SGGeod& pos);
SGPropertyNode_ptr _heading_node;
SGPropertyNode_ptr _error_node;
SGPropertyNode_ptr _frequency_node;
SGPropertyNode_ptr _mode_node;
SGPropertyNode_ptr _in_range_node;
SGPropertyNode_ptr _bearing_node;
SGPropertyNode_ptr _ident_node;
SGPropertyNode_ptr _ident_audible_node;
SGPropertyNode_ptr _volume_node;
double _time_before_search_sec;
int _last_frequency_khz;
bool _transmitter_valid;
std::string _last_ident;
SGGeod _transmitter_pos;
SGVec3d _transmitter_cart;
double _transmitter_range_nm;
int _ident_count;
time_t _last_ident_time;
float _last_volume;
std::string _adf_ident;
SGSharedPtr<SGSampleGroup> _sgr;
};
#endif // __INSTRUMENTS_ADF_HXX

View File

@@ -0,0 +1,170 @@
// airspeed_indicator.cxx - a regular pitot-static airspeed indicator.
// Written by David Megginson, started 2002.
// Last modified by Eric van den Berg, 09 Dec 2012
//
// This file is in the Public Domain and comes with no warranty.
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
#include <algorithm>
#include <cmath>
#include <simgear/constants.h>
#include <simgear/math/interpolater.hxx>
#include "airspeed_indicator.hxx"
#include <Main/fg_props.hxx>
#include <Main/util.hxx>
#include <Environment/environment_mgr.hxx>
#include <Environment/environment.hxx>
// A higher number means more responsive.
#define RESPONSIVENESS 50.0
AirspeedIndicator::AirspeedIndicator ( SGPropertyNode *node )
:
_name(node->getStringValue("name", "airspeed-indicator")),
_num(node->getIntValue("number", 0)),
_total_pressure(node->getStringValue("total-pressure", "/systems/pitot/total-pressure-inhg")),
_static_pressure(node->getStringValue("static-pressure", "/systems/static/pressure-inhg")),
_has_overspeed(node->getBoolValue("has-overspeed-indicator",false)),
_pressure_alt_source(node->getStringValue("pressure-alt-source", "/instrumentation/altimeter/pressure-alt-ft")),
_ias_limit(node->getDoubleValue("ias-limit", 248.0)),
_mach_limit(node->getDoubleValue("mach-limit", 0.48)),
_alt_threshold(node->getDoubleValue("alt-threshold", 13200))
{
_environmentManager = NULL;
}
AirspeedIndicator::~AirspeedIndicator ()
{
}
void
AirspeedIndicator::init ()
{
std::string branch;
branch = "/instrumentation/" + _name;
SGPropertyNode *node = fgGetNode(branch.c_str(), _num, true );
_serviceable_node = node->getChild("serviceable", 0, true);
_total_pressure_node = fgGetNode(_total_pressure.c_str(), true);
_static_pressure_node = fgGetNode(_static_pressure.c_str(), true);
_density_node = fgGetNode("/environment/density-slugft3", true);
_speed_node = node->getChild("indicated-speed-kt", 0, true);
_tas_node = node->getChild("true-speed-kt", 0, true);
_mach_node = node->getChild("indicated-mach", 0, true);
// overspeed-indicator properties
if (_has_overspeed) {
_ias_limit_node = node->getNode("ias-limit",0, true);
_mach_limit_node = node->getNode("mach-limit",0, true);
_alt_threshold_node = node->getNode("alt-threshold",0, true);
if (!_ias_limit_node->hasValue()) {
_ias_limit_node->setDoubleValue(_ias_limit);
}
if (!_mach_limit_node->hasValue()) {
_mach_limit_node->setDoubleValue(_mach_limit);
}
if (!_alt_threshold_node->hasValue()) {
_alt_threshold_node->setDoubleValue(_alt_threshold);
}
_airspeed_limit = node->getChild("airspeed-limit-kt", 0, true);
_pressure_alt = fgGetNode(_pressure_alt_source.c_str(), true);
}
_environmentManager = (FGEnvironmentMgr*) globals->get_subsystem("environment");
}
void
AirspeedIndicator::reinit ()
{
_speed_node->setDoubleValue(0.0);
}
void
AirspeedIndicator::update (double dt)
{
if (!_serviceable_node->getBoolValue()) {
return;
}
double pt = _total_pressure_node->getDoubleValue() ;
double p = _static_pressure_node->getDoubleValue() ;
double qc = ( pt - p ) * SG_INHG_TO_PA ; // Impact pressure in Pa, _not_ to be confused with dynamic pressure!!!
// Now, reverse the equation (normalize impact pressure to
// avoid "nan" results from sqrt)
qc = std::max( qc , 0.0 );
// Calibrated airspeed (using compressible aerodynamics) based on impact pressure qc in m/s
// Using calibrated airspeed as indicated airspeed, neglecting any airspeed indicator errors.
double v_cal = sqrt( 7 * SG_p0_Pa/SG_rho0_kg_p_m3 * ( pow( 1 + qc/SG_p0_Pa , 1/3.5 ) -1 ) );
// Publish the indicated airspeed
double last_speed_kt = _speed_node->getDoubleValue();
double current_speed_kt = v_cal * SG_MPS_TO_KT;
double filtered_speed = fgGetLowPass(last_speed_kt,
current_speed_kt,
dt * RESPONSIVENESS);
_speed_node->setDoubleValue(filtered_speed);
computeMach();
if (!_has_overspeed) {
return;
}
double lmt = _ias_limit_node->getDoubleValue();
if (_pressure_alt->getDoubleValue() > _alt_threshold_node->getDoubleValue()) {
double mmo = _mach_limit_node->getDoubleValue();
lmt = (filtered_speed/_mach_node->getDoubleValue())* mmo;
}
_airspeed_limit->setDoubleValue(lmt);
}
void
AirspeedIndicator::computeMach()
{
if (!_environmentManager) {
return;
}
const auto env = _environmentManager->getAircraftEnvironment();
double oatK = env->get_temperature_degc() + SG_T0_K - 15.0 ; // OAT in Kelvin
oatK = std::max( oatK , 0.001 ); // should never happen, but just in case someone flies into space...
double c = sqrt(SG_gamma * SG_R_m2_p_s2_p_K * oatK); // speed-of-sound in m/s at aircraft position
double pt = _total_pressure_node->getDoubleValue() * SG_INHG_TO_PA; // total pressure in Pa
double p = _static_pressure_node->getDoubleValue() * SG_INHG_TO_PA; // static pressure in Pa
p = std::max( p , 0.001 ); // should never happen, but just in case someone flies into space...
double rho = _density_node->getDoubleValue() * SG_SLUGFT3_TO_KGPM3; // air density in kg/m3
rho = std::max( rho , 0.001 ); // should never happen, but just in case someone flies into space...
// true airspeed in m/s
pt = std::max( pt , p );
double V_true = sqrt( 7 * p/rho * (pow( 1 + (pt-p)/p , 1/3.5 ) -1 ) );
// Mach number; _see notes in systems/pitot.cxx_
double mach = V_true / c;
// publish Mach and TAS
_mach_node->setDoubleValue(mach);
_tas_node->setDoubleValue(V_true * SG_MPS_TO_KT );
}
// Register the subsystem.
#if 0
SGSubsystemMgr::InstancedRegistrant<AirspeedIndicator> registrantAirspeedIndicator(
SGSubsystemMgr::FDM,
{{"instrumentation", SGSubsystemMgr::Dependency::HARD}});
#endif
// end of airspeed_indicator.cxx

View File

@@ -0,0 +1,80 @@
// airspeed_indicator.hxx - a regular VSI tied to the static port.
// Written by David Megginson, started 2002.
//
// Last modified by Eric van den Berg, 24 Nov 2012
// This file is in the Public Domain and comes with no warranty.
#ifndef __INSTRUMENTS_AIRSPEED_INDICATOR_HXX
#define __INSTRUMENTS_AIRSPEED_INDICATOR_HXX 1
#ifndef __cplusplus
# error This library requires C++
#endif
#include <simgear/props/props.hxx>
#include <simgear/structure/subsystem_mgr.hxx>
// forward decls
class FGEnvironmentMgr;
/**
* Model an airspeed indicator tied to the pitot and static ports.
*
* Input properties:
*
* /instrumentation/"name"/serviceable
* "pitot_port"/total-pressure-inhg
* "static_port"/pressure-inhg
* /environment/density-slugft3
*
* Output properties:
*
* /instrumentation/"name"/indicated-speed-kt
* /instrumentation/"name"/true-speed-kt
* /instrumentation/"name"/indicated-mach
*/
class AirspeedIndicator : public SGSubsystem
{
public:
AirspeedIndicator ( SGPropertyNode *node );
virtual ~AirspeedIndicator ();
// Subsystem API.
void init() override;
void reinit() override;
void update(double dt) override;
// Subsystem identification.
static const char* staticSubsystemClassId() { return "airspeed-indicator"; }
private:
void computeMach();
std::string _name;
unsigned int _num;
std::string _total_pressure;
std::string _static_pressure;
bool _has_overspeed;
std::string _pressure_alt_source;
double _ias_limit;
double _mach_limit;
double _alt_threshold;
SGPropertyNode_ptr _ias_limit_node;
SGPropertyNode_ptr _mach_limit_node;
SGPropertyNode_ptr _alt_threshold_node;
SGPropertyNode_ptr _serviceable_node;
SGPropertyNode_ptr _total_pressure_node;
SGPropertyNode_ptr _static_pressure_node;
SGPropertyNode_ptr _density_node;
SGPropertyNode_ptr _speed_node;
SGPropertyNode_ptr _airspeed_limit;
SGPropertyNode_ptr _pressure_alt;
SGPropertyNode_ptr _mach_node;
SGPropertyNode_ptr _tas_node;
FGEnvironmentMgr* _environmentManager;
};
#endif // __INSTRUMENTS_AIRSPEED_INDICATOR_HXX

View File

@@ -0,0 +1,155 @@
// altimeter.cxx - an altimeter tied to the static port.
// Written by David Megginson, started 2002.
// Modified by John Denker in 2007 to use a two layer atmosphere
// model in src/Environment/atmosphere.?xx
// Last modified by Eric van den Berg, 25 Nov 2012
//
// This file is in the Public Domain and comes with no warranty.
// Example invocation, in the instrumentation.xml file:
// <altimeter>
// <name>encoder</name>
// <number>0</number>
// <static-pressure>/systems/static/pressure-inhg</static-pressure>
// <quantum>10</quantum>
// <tau>0</tau>
// </altimeter>
// Note non-default name, quantum, and tau values.
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif
#include <simgear/constants.h>
#include <simgear/math/interpolater.hxx>
#include <simgear/math/SGMath.hxx>
#include <Main/fg_props.hxx>
#include <Main/util.hxx>
#include <Environment/atmosphere.hxx>
#include "altimeter.hxx"
Altimeter::Altimeter ( SGPropertyNode *node, const std::string& aDefaultName, double quantum ) :
_name(node->getStringValue("name", aDefaultName.c_str())),
_num(node->getIntValue("number", 0)),
_static_pressure(node->getStringValue("static-pressure", "/systems/static/pressure-inhg")),
_tau(node->getDoubleValue("tau", 0.1)),
_quantum(node->getDoubleValue("quantum", quantum)),
_settingInHg(29.921260)
{
// FIXME: change default to false once all aircraft which use
// altimiter as an encoder are converted to request this explicitly
_encodeModeC = node->getBoolValue("encode-mode-c", true);
_encodeModeS = node->getBoolValue("encode-mode-s", false);
_tiedProperties.setRoot( _rootNode );
}
Altimeter::~Altimeter ()
{}
double
Altimeter::getSettingInHg() const
{
return _settingInHg;
}
void
Altimeter::setSettingInHg( double value )
{
_settingInHg = value;
}
double
Altimeter::getSettingHPa() const
{
return _settingInHg * SG_INHG_TO_PA / 100;
}
void
Altimeter::setSettingHPa( double value )
{
_settingInHg = value * SG_PA_TO_INHG * 100;
}
void
Altimeter::init ()
{
_pressure_node = fgGetNode(_static_pressure.c_str(), true);
_serviceable_node = _rootNode->getChild("serviceable", 0, true);
_press_alt_node = _rootNode->getChild("pressure-alt-ft", 0, true);
if (_encodeModeC) {
_mode_c_node = _rootNode->getChild("mode-c-alt-ft", 0, true);
}
if (_encodeModeS) {
_mode_s_node = _rootNode->getChild("mode-s-alt-ft", 0, true);
}
_altitude_node = _rootNode->getChild("indicated-altitude-ft", 0, true);
reinit();
}
void
Altimeter::reinit ()
{
_raw_PA = 0.0;
_kollsman = 0.0;
}
void
Altimeter::bind()
{
_rootNode = fgGetNode("/instrumentation/" + _name, _num, true );
_tiedProperties.setRoot(_rootNode);
_tiedProperties.Tie("setting-inhg", this, &Altimeter::getSettingInHg, &Altimeter::setSettingInHg );
_tiedProperties.Tie("setting-hpa", this, &Altimeter::getSettingHPa, &Altimeter::setSettingHPa );
}
void
Altimeter::unbind()
{
_tiedProperties.Untie();
}
void
Altimeter::update (double dt)
{
if (_serviceable_node->getBoolValue()) {
double trat = _tau > 0 ? dt/_tau : 100;
double pressure = _pressure_node->getDoubleValue();
double press_alt = _press_alt_node->getDoubleValue();
// The mechanism settles slowly toward new pressure altitude:
_raw_PA = fgGetLowPass(_raw_PA, _altimeter.press_alt_ft(pressure), trat);
if (_encodeModeC) {
_mode_c_node->setDoubleValue(100 * SGMiscd::round(_raw_PA/100));
}
if (_encodeModeS) {
_mode_s_node->setDoubleValue(10 * SGMiscd::round(_raw_PA/10));
}
_kollsman = fgGetLowPass(_kollsman, _altimeter.kollsman_ft(_settingInHg), trat);
if (_quantum)
press_alt = _quantum * SGMiscd::round(_raw_PA/_quantum);
else
press_alt = _raw_PA;
_press_alt_node->setDoubleValue(press_alt);
_altitude_node->setDoubleValue(press_alt - _kollsman);
}
}
// Register the subsystem.
#if 0
SGSubsystemMgr::InstancedRegistrant<Altimeter> registrantAltimeter(
SGSubsystemMgr::FDM,
{{"instrumentation", SGSubsystemMgr::Dependency::HARD}});
#endif
// end of altimeter.cxx

View File

@@ -0,0 +1,77 @@
// altimeter.hxx - an altimeter tied to the static port.
// Written by David Megginson, started 2002.
// Updated by John Denker to match changes in altimeter.cxx in 2007
//
// This file is in the Public Domain and comes with no warranty.
#ifndef __INSTRUMENTS_ALTIMETER_HXX
#define __INSTRUMENTS_ALTIMETER_HXX 1
#include <simgear/props/props.hxx>
#include <simgear/props/tiedpropertylist.hxx>
#include <simgear/structure/subsystem_mgr.hxx>
#include <Environment/atmosphere.hxx>
/**
* Model a barometric altimeter tied to the static port.
*
* Input properties:
*
* /instrumentation/<name>/serviceable
* /instrumentation/<name>/setting-inhg
* <static_pressure>
*
* Output properties:
*
* /instrumentation/<name>/indicated-altitude-ft
*/
class Altimeter : public SGSubsystem
{
public:
Altimeter (SGPropertyNode *node, const std::string& aDefaultName, double quantum = 0);
virtual ~Altimeter ();
// Subsystem API.
void bind() override;
void init() override;
void reinit() override;
void unbind() override;
void update(double dt) override;
// Subsystem identification.
static const char* staticSubsystemClassId() { return "altimeter"; }
double getSettingInHg() const;
void setSettingInHg( double value );
double getSettingHPa() const;
void setSettingHPa( double value );
private:
std::string _name;
int _num;
SGPropertyNode_ptr _rootNode;
std::string _static_pressure;
double _tau;
double _quantum;
double _kollsman;
double _raw_PA;
double _settingInHg;
bool _encodeModeC;
bool _encodeModeS;
SGPropertyNode_ptr _serviceable_node;
SGPropertyNode_ptr _pressure_node;
SGPropertyNode_ptr _press_alt_node;
SGPropertyNode_ptr _mode_c_node;
SGPropertyNode_ptr _mode_s_node;
SGPropertyNode_ptr _transponder_node;
SGPropertyNode_ptr _altitude_node;
FGAltimeter _altimeter;
simgear::TiedPropertyList _tiedProperties;
};
#endif // __INSTRUMENTS_ALTIMETER_HXX

View File

@@ -0,0 +1,191 @@
// attitude_indicator.cxx - a vacuum-powered attitude indicator.
// Written by David Megginson, started 2002.
//
// This file is in the Public Domain and comes with no warranty.
// TODO:
// - better spin-up
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
#include <simgear/compiler.h>
#include <iostream>
#include <string>
#include <sstream>
#include <cmath> // fabs()
#include "attitude_indicator.hxx"
#include <Main/fg_props.hxx>
#include <Main/util.hxx>
using std::string;
AttitudeIndicator::AttitudeIndicator ( SGPropertyNode *node )
:
_name(node->getStringValue("name", "attitude-indicator")),
_num(node->getIntValue("number", 0)),
_suction(node->getStringValue("suction", "/systems/vacuum/suction-inhg")),
spin_thresh(0.8),
max_roll_error(40.0),
max_pitch_error(12.0)
{
}
AttitudeIndicator::~AttitudeIndicator ()
{
}
void
AttitudeIndicator::init ()
{
string branch;
branch = "/instrumentation/" + _name;
SGPropertyNode *node = fgGetNode(branch.c_str(), _num, true );
SGPropertyNode *n;
_pitch_in_node = fgGetNode("/orientation/pitch-deg", true);
_roll_in_node = fgGetNode("/orientation/roll-deg", true);
_suction_node = fgGetNode(_suction.c_str(), true);
SGPropertyNode *cnode = node->getChild("config", 0, true);
_tumble_flag_node = cnode->getChild("tumble-flag", 0, true);
_caged_node = node->getChild("caged-flag", 0, true);
_tumble_node = node->getChild("tumble-norm", 0, true);
if( ( n = cnode->getChild("spin-thresh", 0, false ) ) != NULL )
spin_thresh = n->getDoubleValue();
if( ( n = cnode->getChild("max-roll-error-deg", 0, false ) ) != NULL )
max_roll_error = n->getDoubleValue();
if( ( n = cnode->getChild("max-pitch-error-deg", 0, false ) ) != NULL )
max_pitch_error = n->getDoubleValue();
_pitch_int_node = node->getChild("internal-pitch-deg", 0, true);
_roll_int_node = node->getChild("internal-roll-deg", 0, true);
_pitch_out_node = node->getChild("indicated-pitch-deg", 0, true);
_roll_out_node = node->getChild("indicated-roll-deg", 0, true);
reinit();
}
void
AttitudeIndicator::reinit ()
{
_roll_int_node->setDoubleValue(0.0);
_pitch_int_node->setDoubleValue(0.0);
_gyro.reinit();
}
void
AttitudeIndicator::bind ()
{
std::ostringstream temp;
string branch;
temp << _num;
branch = "/instrumentation/" + _name + "[" + temp.str() + "]";
fgTie((branch + "/serviceable").c_str(),
&_gyro, &Gyro::is_serviceable, &Gyro::set_serviceable);
fgTie((branch + "/spin").c_str(),
&_gyro, &Gyro::get_spin_norm, &Gyro::set_spin_norm);
}
void
AttitudeIndicator::unbind ()
{
std::ostringstream temp;
string branch;
temp << _num;
branch = "/instrumentation/" + _name + "[" + temp.str() + "]";
fgUntie((branch + "/serviceable").c_str());
fgUntie((branch + "/spin").c_str());
}
void
AttitudeIndicator::update (double dt)
{
// If it's caged, it doesn't indicate
if (_caged_node->getBoolValue()) {
_roll_int_node->setDoubleValue(0.0);
_pitch_int_node->setDoubleValue(0.0);
return;
}
// Get the spin from the gyro
_gyro.set_power_norm(_suction_node->getDoubleValue()/5.0);
_gyro.update(dt);
double spin = _gyro.get_spin_norm();
// Calculate the responsiveness
double responsiveness = spin * spin * spin * spin * spin * spin;
// Get the indicated roll and pitch
double roll = _roll_in_node->getDoubleValue();
double pitch = _pitch_in_node->getDoubleValue();
// Calculate the tumble for the
// next pass.
if (_tumble_flag_node->getBoolValue()) {
double tumble = _tumble_node->getDoubleValue();
if (fabs(roll) > 45.0) {
double target = (fabs(roll) - 45.0) / 45.0;
target *= target; // exponential past +-45 degrees
if (roll < 0)
target = -target;
if (fabs(target) > fabs(tumble))
tumble = target;
if (tumble > 1.0)
tumble = 1.0;
else if (tumble < -1.0)
tumble = -1.0;
}
// Reerect in 5 minutes
double step = dt/300.0;
if (tumble < -step)
tumble += step;
else if (tumble > step)
tumble -= step;
roll += tumble * 45;
_tumble_node->setDoubleValue(tumble);
}
roll = fgGetLowPass(_roll_int_node->getDoubleValue(), roll,
responsiveness);
pitch = fgGetLowPass(_pitch_int_node->getDoubleValue(), pitch,
responsiveness);
// Assign the new values
_roll_int_node->setDoubleValue(roll);
_pitch_int_node->setDoubleValue(pitch);
// add in a gyro underspin "error" if gyro is spinning too slowly
double roll_error;
double pitch_error;
if ( spin <= spin_thresh ) {
double roll_error_factor = (spin_thresh - spin) / spin_thresh;
double pitch_error_factor = (spin_thresh - spin) / spin_thresh;
roll_error = roll_error_factor * roll_error_factor * max_roll_error;
pitch_error = pitch_error_factor * pitch_error_factor * max_pitch_error;
} else {
roll_error = 0.0;
pitch_error = 0.0;
}
_roll_out_node->setDoubleValue(roll + roll_error);
_pitch_out_node->setDoubleValue(pitch + pitch_error);
}
// Register the subsystem.
#if 0
SGSubsystemMgr::InstancedRegistrant<AttitudeIndicator> registrantAttitudeIndicator(
SGSubsystemMgr::FDM,
{{"instrumentation", SGSubsystemMgr::Dependency::HARD}});
#endif
// end of attitude_indicator.cxx

View File

@@ -0,0 +1,78 @@
// attitude_indicator.hxx - a vacuum-powered attitude indicator.
// Written by David Megginson, started 2002.
//
// This file is in the Public Domain and comes with no warranty.
#ifndef __INSTRUMENTS_ATTITUDE_INDICATOR_HXX
#define __INSTRUMENTS_ATTITUDE_INDICATOR_HXX 1
#ifndef __cplusplus
# error This library requires C++
#endif
#include <simgear/props/props.hxx>
#include <simgear/structure/subsystem_mgr.hxx>
#include "gyro.hxx"
/**
* Model a vacuum-powered attitude indicator.
*
* Input properties:
*
* /instrumentation/"name"/config/tumble-flag
* /instrumentation/"name"/serviceable
* /instrumentation/"name"/caged-flag
* /instrumentation/"name"/tumble-norm
* /orientation/pitch-deg
* /orientation/roll-deg
* "vacuum-system"/suction-inhg
*
* Output properties:
*
* /instrumentation/"name"/indicated-pitch-deg
* /instrumentation/"name"/indicated-roll-deg
* /instrumentation/"name"/tumble-norm
*/
class AttitudeIndicator : public SGSubsystem
{
public:
AttitudeIndicator ( SGPropertyNode *node );
virtual ~AttitudeIndicator ();
// Subsystem API.
void bind() override;
void init() override;
void reinit() override;
void unbind() override;
void update(double dt) override;
// Subsystem identification.
static const char* staticSubsystemClassId() { return "attitude-indicator"; }
private:
std::string _name;
int _num;
std::string _suction;
Gyro _gyro;
SGPropertyNode_ptr _tumble_flag_node;
SGPropertyNode_ptr _caged_node;
SGPropertyNode_ptr _tumble_node;
SGPropertyNode_ptr _pitch_in_node;
SGPropertyNode_ptr _roll_in_node;
SGPropertyNode_ptr _suction_node;
SGPropertyNode_ptr _pitch_int_node;
SGPropertyNode_ptr _roll_int_node;
SGPropertyNode_ptr _pitch_out_node;
SGPropertyNode_ptr _roll_out_node;
double spin_thresh;
double max_roll_error;
double max_pitch_error;
};
#endif // __INSTRUMENTS_ATTITUDE_INDICATOR_HXX

View File

@@ -0,0 +1,142 @@
// clock.cxx - an electric-powered turn indicator.
// Written by Melchior FRANZ, started 2003.
//
// This file is in the Public Domain and comes with no warranty.
//
// $Id$
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif
#include <cstdio>
#include "clock.hxx"
#include <simgear/timing/sg_time.hxx>
#include <Main/fg_props.hxx>
#include <Main/util.hxx>
Clock::Clock(SGPropertyNode *node) :
_name(node->getStringValue("name", "clock")),
_num(node->getIntValue("number", 0)),
_is_serviceable(true),
_gmt_time_sec(0),
_offset_sec(0),
_indicated_sec(0),
_indicated_min(0),
_indicated_hour(0),
_local_hour(0),
_standstill_offset(0)
{
_indicated_string[0] = '\0';
}
Clock::~Clock ()
{
}
void
Clock::init ()
{
std::string branch;
branch = "/instrumentation/" + _name;
SGPropertyNode *node = fgGetNode(branch.c_str(), _num, true );
_serviceable_node = node->getChild("serviceable", 0, true);
_offset_node = node->getChild("offset-sec", 0, true);
_sec_node = node->getChild("indicated-sec", 0, true);
_min_node = node->getChild("indicated-min", 0, true);
_hour_node = node->getChild("indicated-hour", 0, true);
_lhour_node = node->getChild("local-hour", 0, true);
_string_node = node->getChild("indicated-string", 0, true);
_string_node1 = node->getChild("indicated-short-string", 0, true);
_string_node2 = node->getChild("local-short-string", 0, true);
}
void
Clock::update (double delta_time_sec)
{
if (!_serviceable_node->getBoolValue()) {
if (_is_serviceable) {
_string_node->setStringValue("");
_is_serviceable = false;
}
return;
}
struct tm *t = globals->get_time_params()->getGmt();
short hour = t->tm_hour;
short min = t->tm_min;
short sec = t->tm_sec;
// compute local time zone hour
short tzoffset_hours = globals->get_time_params()->get_local_offset() / 3600;
short lhour = hour + tzoffset_hours;
if (lhour < 0)
lhour += 24;
if (lhour >= 24)
lhour -= 24;
long gmt = (hour * 60 + min) * 60 + sec;
int offset = _offset_node->getLongValue();
if (!_is_serviceable) {
_standstill_offset -= gmt - _gmt_time_sec;
} else if (_gmt_time_sec == gmt && _offset_sec == offset)
return;
_gmt_time_sec = gmt;
_offset_sec = offset;
_indicated_sec = _gmt_time_sec + offset + _standstill_offset;
_sec_node->setLongValue(_indicated_sec);
sec += offset;
while (sec < 0) {
sec += 60;
min--;
}
while (sec >= 60) {
sec -= 60;
min++;
}
while (min < 0) {
min += 60;
hour--;
}
while (min >= 60) {
min -= 60;
hour++;
}
while (hour < 0)
hour += 24;
while (hour >= 24)
hour -= 24;
snprintf(_indicated_string, sizeof(_indicated_string), "%02d:%02d:%02d", hour, min, sec);
_string_node->setStringValue(_indicated_string);
snprintf(_indicated_short_string, sizeof(_indicated_short_string), "%02d:%02d", hour, min);
_string_node1->setStringValue(_indicated_short_string);
snprintf(_local_short_string, sizeof(_local_short_string), "%02d:%02d", lhour, min);
_string_node2->setStringValue(_local_short_string);
_is_serviceable = true;
_indicated_min = min;
_min_node->setLongValue(_indicated_min);
_indicated_hour = hour;
_hour_node->setLongValue(_indicated_hour);
_local_hour = lhour;
_lhour_node->setLongValue(_local_hour);
}
// Register the subsystem.
#if 0
SGSubsystemMgr::InstancedRegistrant<Clock> registrantClock(
SGSubsystemMgr::FDM,
{{"instrumentation", SGSubsystemMgr::Dependency::HARD}},
0.25);
#endif
// end of clock.cxx

View File

@@ -0,0 +1,69 @@
// clock.hxx.
// Written by Melchior FRANZ, started 2003.
//
// This file is in the Public Domain and comes with no warranty.
//
// $Id$
#ifndef __INSTRUMENTS_CLOCK_HXX
#define __INSTRUMENTS_CLOCK_HXX 1
#include <simgear/props/props.hxx>
#include <simgear/structure/subsystem_mgr.hxx>
/**
* Model a clock.
*
* Input properties:
*
* /instrumentation/clock/serviceable
* /instrumentation/clock/offset-sec
*
* Output properties:
*
* /instrumentation/clock/indicated-sec
* /instrumentation/clock/indicated-string
*/
class Clock : public SGSubsystem
{
public:
Clock(SGPropertyNode *node);
virtual ~Clock();
// Subsystem API.
void init() override;
void update(double dt) override;
// Subsystem identification.
static const char* staticSubsystemClassId() { return "clock"; }
private:
std::string _name;
unsigned int _num;
bool _is_serviceable;
long _gmt_time_sec;
long _offset_sec;
long _indicated_sec;
long _indicated_min;
long _indicated_hour;
long _local_hour;
char _indicated_string[16];
char _indicated_short_string[16];
char _local_short_string[16];
long _standstill_offset;
SGPropertyNode_ptr _serviceable_node;
SGPropertyNode_ptr _offset_node;
SGPropertyNode_ptr _sec_node;
SGPropertyNode_ptr _hour_node;
SGPropertyNode_ptr _lhour_node;
SGPropertyNode_ptr _min_node;
SGPropertyNode_ptr _string_node;
SGPropertyNode_ptr _string_node1;
SGPropertyNode_ptr _string_node2;
};
#endif // __INSTRUMENTS_CLOCK_HXX

View File

@@ -0,0 +1,770 @@
// commradio.cxx -- class to manage a nav radio instance
//
// Written by Torsten Dreyer, February 2014
//
// Copyright (C) 2000 - 2011 Curtis L. Olson - http://www.flightgear.org/~curt
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation; either version 2 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
//
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif
#include "commradio.hxx"
#include <assert.h>
#include <simgear/sg_inlines.h>
#include <simgear/props/propertyObject.hxx>
#include <simgear/misc/strutils.hxx>
#include <ATC/CommStation.hxx>
#include <ATC/MetarPropertiesATISInformationProvider.hxx>
#include <ATC/CurrentWeatherATISInformationProvider.hxx>
#include <Airports/airport.hxx>
#include <Main/fg_props.hxx>
#include <Navaids/navlist.hxx>
#include <Sound/soundmanager.hxx>
#include <simgear/sound/sample_group.hxx>
#include <Sound/VoiceSynthesizer.hxx>
#include "frequencyformatter.hxx"
namespace Instrumentation {
using simgear::PropertyObject;
using std::string;
class AtisSpeaker: public SGPropertyChangeListener, SoundSampleReadyListener {
public:
AtisSpeaker();
virtual ~AtisSpeaker();
virtual void valueChanged(SGPropertyNode * node);
virtual void SoundSampleReady(SGSharedPtr<SGSoundSample>);
bool hasSpokenAtis()
{
return _spokenAtis.empty() == false;
}
SGSharedPtr<SGSoundSample> getSpokenAtis()
{
return _spokenAtis.pop();
}
void setStationId(const string & stationId)
{
_stationId = stationId;
}
private:
SynthesizeRequest _synthesizeRequest;
SGLockedQueue<SGSharedPtr<SGSoundSample> > _spokenAtis;
string _stationId;
};
AtisSpeaker::AtisSpeaker()
{
_synthesizeRequest.listener = this;
if (!fgHasNode("/sim/atis/speed")) fgSetDouble("/sim/atis/speed", 1);
if (!fgHasNode("/sim/atis/pitch")) fgSetDouble("/sim/atis/pitch", 1);
if (!fgHasNode("/sim/atis/enabled")) fgSetBool("/sim/atis/enabled", true);
}
AtisSpeaker::~AtisSpeaker()
{
}
void AtisSpeaker::valueChanged(SGPropertyNode * node)
{
using namespace simgear::strutils;
if (!fgGetBool("/sim/sound/working", false))
return;
string newText = node->getStringValue();
if (_synthesizeRequest.text == newText) return;
_synthesizeRequest.text = newText;
string voice = "cmu_us_arctic_slt";
if (!_stationId.empty()) {
// lets play a bit with the voice so not every airports atis sounds alike
// but every atis of an airport has the same voice
// create a simple hash from the last two letters of the airport's id
unsigned char hash = 0;
string::iterator i = _stationId.end() - 1;
hash += *i;
if( i != _stationId.begin() ) {
--i;
hash += *i;
}
_synthesizeRequest.speed = (hash % 16) / 16.0 * fgGetDouble("/sim/atis/speed", 1);
_synthesizeRequest.pitch = (hash % 16) / 16.0 * fgGetDouble("/sim/atis/pitch", 1);
if( starts_with( _stationId, "K" ) || starts_with( _stationId, "C" ) ||
starts_with( _stationId, "P" ) ) {
voice = FLITEVoiceSynthesizer::getVoicePath("cmu_us_arctic_slt");
} else if ( starts_with( _stationId, "EG" ) ) {
voice = FLITEVoiceSynthesizer::getVoicePath("cstr_uk_female");
} else {
// Pick a random voice from the available voices
voice = FLITEVoiceSynthesizer::getVoicePath(
static_cast<FLITEVoiceSynthesizer::voice_t>(hash % FLITEVoiceSynthesizer::VOICE_UNKNOWN) );
}
}
FGSoundManager * smgr = globals->get_subsystem<FGSoundManager>();
if (!smgr) {
return;
}
SG_LOG(SG_INSTR, SG_DEBUG,"node->getPath()=" << node->getPath() << " AtisSpeaker voice is " << voice );
FLITEVoiceSynthesizer * synthesizer = dynamic_cast<FLITEVoiceSynthesizer*>(smgr->getSynthesizer(voice));
synthesizer->synthesize(_synthesizeRequest);
}
void AtisSpeaker::SoundSampleReady(SGSharedPtr<SGSoundSample> sample)
{
// we are now in the synthesizers worker thread!
_spokenAtis.push(sample);
}
SignalQualityComputer::~SignalQualityComputer()
{
}
class SimpleDistanceSquareSignalQualityComputer: public SignalQualityComputer {
public:
SimpleDistanceSquareSignalQualityComputer()
: _altitudeAgl_ft(fgGetNode("/position/altitude-agl-ft", true))
{
}
~SimpleDistanceSquareSignalQualityComputer()
{
}
double computeSignalQuality(double distance_nm) const
{
// Very simple line of sight propagation model. It's cheap but it does the trick for now.
// assume transmitter and receiver antennas are at some elevation above ground
// so we have at least a range of 5NM. Add the approx. distance to the horizon.
double range_nm = 5.0 + 1.23 * ::sqrt(SGMiscd::max(.0, _altitudeAgl_ft));
return distance_nm < range_nm ? 1.0 : (range_nm * range_nm / distance_nm / distance_nm);
}
private:
PropertyObject<double> _altitudeAgl_ft;
};
class OnExitHandler {
public:
virtual void onExit() = 0;
virtual ~OnExitHandler()
{
}
};
class OnExit {
public:
OnExit(OnExitHandler * onExitHandler)
: _onExitHandler(onExitHandler)
{
}
~OnExit()
{
_onExitHandler->onExit();
}
private:
OnExitHandler * _onExitHandler;
};
class OutputProperties: public OnExitHandler {
public:
void bind(SGPropertyNode* rn)
{
_rootNode = rn;
_PO_stationType = PropertyObject<string>(_rootNode->getNode("station-type", true));
_PO_stationName = PropertyObject<string>(_rootNode->getNode("station-name", true));
_PO_airportId = PropertyObject<string>(_rootNode->getNode("airport-id", true));
_PO_signalQuality_norm = PropertyObject<double>(_rootNode->getNode("signal-quality-norm", true));
_PO_slantDistance_m = PropertyObject<double>(_rootNode->getNode("slant-distance-m", true));
_PO_trueBearingTo_deg = PropertyObject<double>(_rootNode->getNode("true-bearing-to-deg", true));
_PO_trueBearingFrom_deg = PropertyObject<double>(_rootNode->getNode("true-bearing-from-deg", true));
_PO_trackDistance_m = PropertyObject<double>(_rootNode->getNode("track-distance-m", true));
_PO_heightAboveStation_ft = PropertyObject<double>(_rootNode->getNode("height-above-station-ft", true));
}
virtual ~OutputProperties()
{
}
protected:
SGPropertyNode_ptr _rootNode;
std::string _stationType;
std::string _stationName;
std::string _airportId;
double _signalQuality_norm = 0.0;
double _slantDistance_m = 0.0;
double _trueBearingTo_deg = 0.0;
double _trueBearingFrom_deg = 0.0;
double _trackDistance_m = 0.0;
double _heightAboveStation_ft = 0.0;
private:
PropertyObject<string> _PO_stationType;
PropertyObject<string> _PO_stationName;
PropertyObject<string> _PO_airportId;
PropertyObject<double> _PO_signalQuality_norm;
PropertyObject<double> _PO_slantDistance_m;
PropertyObject<double> _PO_trueBearingTo_deg;
PropertyObject<double> _PO_trueBearingFrom_deg;
PropertyObject<double> _PO_trackDistance_m;
PropertyObject<double> _PO_heightAboveStation_ft;
virtual void onExit()
{
_PO_stationType = _stationType;
_PO_stationName = _stationName;
_PO_airportId = _airportId;
_PO_signalQuality_norm = _signalQuality_norm;
_PO_slantDistance_m = _slantDistance_m;
_PO_trueBearingTo_deg = _trueBearingTo_deg;
_PO_trueBearingFrom_deg = _trueBearingFrom_deg;
_PO_trackDistance_m = _trackDistance_m;
_PO_heightAboveStation_ft = _heightAboveStation_ft;
}
};
/* ------------- The CommRadio implementation ---------------------- */
class MetarBridge: public SGReferenced, public SGPropertyChangeListener {
public:
void bind();
void unbind();
void requestMetarForId(std::string & id);
void clearMetar();
void setMetarPropertiesRoot(SGPropertyNode_ptr n)
{
_metarPropertiesNode = n;
}
void setAtisNode(SGPropertyNode * n)
{
_atisNode = n;
}
protected:
virtual void valueChanged(SGPropertyNode *);
private:
std::string _requestedId;
SGPropertyNode_ptr _realWxEnabledNode;
SGPropertyNode_ptr _metarPropertiesNode;
SGPropertyNode * _atisNode = nullptr;
ATISEncoder _atisEncoder;
};
typedef SGSharedPtr<MetarBridge> MetarBridgeRef;
void MetarBridge::bind()
{
_realWxEnabledNode = fgGetNode("/environment/realwx/enabled", true);
_metarPropertiesNode->getNode("valid", true)->addChangeListener(this);
}
void MetarBridge::unbind()
{
_metarPropertiesNode->getNode("valid", true)->removeChangeListener(this);
}
void MetarBridge::requestMetarForId(std::string & id)
{
std::string uppercaseId = simgear::strutils::uppercase(id);
if (_requestedId == uppercaseId) return;
_requestedId = uppercaseId;
if (_realWxEnabledNode->getBoolValue()) {
// trigger a METAR request for the associated metarproperties
_metarPropertiesNode->getNode("station-id", true)->setStringValue(uppercaseId);
_metarPropertiesNode->getNode("valid", true)->setBoolValue(false);
_metarPropertiesNode->getNode("time-to-live", true)->setDoubleValue(0.0);
} else {
// use the present weather to generate the ATIS.
if ( NULL != _atisNode && !_requestedId.empty()) {
CurrentWeatherATISInformationProvider provider(_requestedId);
_atisNode->setStringValue(_atisEncoder.encodeATIS(&provider));
}
}
}
void MetarBridge::clearMetar()
{
string empty;
requestMetarForId(empty);
}
void MetarBridge::valueChanged(SGPropertyNode * node)
{
// check for raising edge of valid flag
if ( NULL == node || !node->getBoolValue() || !_realWxEnabledNode->getBoolValue()) return;
std::string responseId = simgear::strutils::uppercase(_metarPropertiesNode->getNode("station-id", true)->getStringValue());
// unrequested metar!?
if (responseId != _requestedId) return;
if ( NULL != _atisNode) {
MetarPropertiesATISInformationProvider provider(_metarPropertiesNode);
_atisNode->setStringValue(_atisEncoder.encodeATIS(&provider));
}
}
/* ------------- 8.3kHz Channel implementation ---------------------- */
class EightPointThreeFrequencyFormatter :
public FrequencyFormatterBase,
public SGPropertyChangeListener {
public:
EightPointThreeFrequencyFormatter( SGPropertyNode_ptr root,
const char * channel,
const char * fmt,
const char * width,
const char * frq,
const char * cnum ) :
_channel( root, channel ),
_frequency( root, frq ),
_channelSpacing( root, width ),
_formattedChannel( root, fmt ),
_channelNum( root, cnum )
{
// ensure properties exist.
_channel.node(true);
_frequency.node(true);
_channelSpacing.node(true);
_channelNum.node(true);
_formattedChannel.node(true);
_channel.node()->addChangeListener( this, true );
_channelNum.node()->addChangeListener( this, true );
}
virtual ~EightPointThreeFrequencyFormatter()
{
_channel.node()->removeChangeListener( this );
_channelNum.node()->removeChangeListener( this );
}
private:
EightPointThreeFrequencyFormatter( const EightPointThreeFrequencyFormatter & );
EightPointThreeFrequencyFormatter & operator = ( const EightPointThreeFrequencyFormatter & );
void valueChanged (SGPropertyNode * prop) {
if( prop == _channel.node() )
setFrequency(prop->getDoubleValue());
else if( prop == _channelNum.node() )
setChannel(prop->getIntValue());
}
void setChannel( int channel ) {
channel %= 3040;
if( channel < 0 ) channel += 3040;
double f = 118.000 + 0.025*(channel/4) + 0.005*(channel%4);
if( f != _channel ) _channel = f;
}
void setFrequency( double channel ) {
// format as fixed decimal "nnn.nnn"
std::ostringstream buf;
buf << std::fixed
<< std::setw(6)
<< std::setfill('0')
<< std::setprecision(3)
<< _channel;
_formattedChannel = buf.str();
// sanitize range and round to nearest kHz.
unsigned c = static_cast<int>(SGMiscd::round(SGMiscd::clip( channel, 118.0, 136.99 ) * 1000));
if ( (c % 25) == 0 ) {
// legacy 25kHz channels continue to be just that.
_channelSpacing = 25.0;
_frequency = c / 1000.0;
int channelNum = (c-118000)/25*4;
if( channelNum != _channelNum ) _channelNum = channelNum;
if( _frequency != channel ) {
const double channelValue = c / 1000.0;
_channel = channelValue;
}
} else {
_channelSpacing = 8.33;
// 25kHz base frequency: xxx.000, xxx.025, xxx.050, xxx.075
unsigned base25 = (c/25) * 25;
// add n*8.33 to the 25kHz frequency
unsigned subChannel = SGMisc<unsigned>::clip((c - base25)/5-1, 0, 2 );
_frequency = (base25 + 8.33 * subChannel)/1000.0;
int channelNum = (base25-118000)/25*4 + subChannel+1;
if( channelNum != _channelNum ) _channelNum = channelNum;
// set to correct channel on bogous input
double sanitizedChannel = (base25 + 5*(subChannel+1))/1000.0;
if( sanitizedChannel != channel ) {
_channel = sanitizedChannel; // triggers recursion
}
}
}
double getFrequency() const {
return _channel;
}
PropertyObject<double> _channel;
PropertyObject<double> _frequency;
PropertyObject<double> _channelSpacing;
PropertyObject<string> _formattedChannel;
PropertyObject<int> _channelNum;
};
/* ------------- The CommRadio implementation ---------------------- */
class CommRadioImpl: public CommRadio,
OutputProperties
{
public:
CommRadioImpl(SGPropertyNode_ptr node);
virtual ~CommRadioImpl();
// Subsystem API.
void bind() override;
void init() override;
void unbind() override;
void update(double dt) override;
private:
bool _useEightPointThree = false;
MetarBridgeRef _metarBridge;
AtisSpeaker _atisSpeaker;
SGSharedPtr<FrequencyFormatterBase> _useFrequencyFormatter;
SGSharedPtr<FrequencyFormatterBase> _stbyFrequencyFormatter;
const SignalQualityComputerRef _signalQualityComputer;
double _stationTTL = 0.0;
double _frequency = -1.0;
flightgear::CommStationRef _commStationForFrequency;
PropertyObject<double> _volume_norm;
PropertyObject<string> _atis;
PropertyObject<bool> _addNoise;
PropertyObject<double> _cutoffSignalQuality;
SGPropertyNode_ptr _atis_enabled_node;
bool _atis_enabled_prev;
SGSharedPtr<SGSoundSample> _atis_sample;
std::string _soundPrefix;
void stopAudio();
void updateAudio();
SGSampleGroup* _sampleGroup = nullptr;
};
CommRadioImpl::CommRadioImpl(SGPropertyNode_ptr node) :
_metarBridge(new MetarBridge),
_signalQualityComputer(new SimpleDistanceSquareSignalQualityComputer),
_atis_enabled_prev(false)
{
// set a special value to indicate we don't require a power supply node
// by default
setDefaultPowerSupplyPath("NO_DEFAULT");
readConfig(node, "comm");
_soundPrefix = name() + "_" + std::to_string(number()) + "_";
_useEightPointThree = node->getBoolValue("eight-point-three", false );
}
CommRadioImpl::~CommRadioImpl()
{
}
void CommRadioImpl::bind()
{
SGPropertyNode_ptr n = fgGetNode(nodePath(), true);
OutputProperties::bind(n);
_volume_norm = PropertyObject<double>(_rootNode->getNode("volume", true));
_atis = PropertyObject<string>(_rootNode->getNode("atis", true));
if (!fgHasNode("/sim/atis/enabled")) fgSetBool("/sim/atis/enabled", true);
_atis_enabled_node = fgGetNode("/sim/atis/enabled");
_addNoise = PropertyObject<bool>(_rootNode->getNode("add-noise", true));
_cutoffSignalQuality = PropertyObject<double>(_rootNode->getNode("cutoff-signal-quality", true));
_metarBridge->setAtisNode(_atis.node());
_atis.node()->addChangeListener(&_atisSpeaker);
// link the metar node. /environment/metar[3] is comm1 and /environment[4] is comm2.
// see FGDATA/Environment/environment.xml
_metarBridge->setMetarPropertiesRoot(fgGetNode("/environment", true)->getNode("metar", number() + 3, true));
_metarBridge->bind();
if (_useEightPointThree) {
_useFrequencyFormatter = new EightPointThreeFrequencyFormatter(
_rootNode->getNode("frequencies", true),
"selected-mhz",
"selected-mhz-fmt",
"selected-channel-width-khz",
"selected-real-frequency-mhz",
"selected-channel"
);
_stbyFrequencyFormatter = new EightPointThreeFrequencyFormatter(
_rootNode->getNode("frequencies", true),
"standby-mhz",
"standby-mhz-fmt",
"standby-channel-width-khz",
"standby-real-frequency-mhz",
"standby-channel"
);
} else {
_useFrequencyFormatter = new FrequencyFormatter(
_rootNode->getNode("frequencies/selected-mhz", true),
_rootNode->getNode("frequencies/selected-mhz-fmt", true),
0.025, 118.0, 137.0);
_stbyFrequencyFormatter = new FrequencyFormatter(
_rootNode->getNode("frequencies/standby-mhz", true),
_rootNode->getNode("frequencies/standby-mhz-fmt", true),
0.025, 118.0, 137.0);
}
}
void CommRadioImpl::unbind()
{
_atis.node()->removeChangeListener(&_atisSpeaker);
stopAudio();
_metarBridge->unbind();
AbstractInstrument::unbind();
}
void CommRadioImpl::init()
{
initServicePowerProperties(_rootNode);
string s;
// initialize squelch to a sane value if unset
s = _cutoffSignalQuality.node()->getStringValue();
if (s.empty()) _cutoffSignalQuality = 0.4;
// initialize add-noize to true if unset
s = _addNoise.node()->getStringValue();
if (s.empty()) _addNoise = true;
auto soundManager = globals->get_subsystem<SGSoundMgr>();
if (soundManager) {
_sampleGroup = soundManager->find("atc", true);
}
}
void CommRadioImpl::update(double dt)
{
if (dt < SGLimitsd::min()) return;
_stationTTL -= dt;
// Ensure all output properties get written on exit of this method
OnExit onExit(this);
SGGeod position;
try {
position = globals->get_aircraft_position();
}
catch (std::exception &) {
return;
}
if (!isServiceableAndPowered()) {
_metarBridge->clearMetar();
_atis = "";
_stationTTL = 0.0;
stopAudio();
return;
}
if (_frequency != _useFrequencyFormatter->getFrequency()) {
_frequency = _useFrequencyFormatter->getFrequency();
_stationTTL = 0.0;
}
if (_stationTTL <= 0.0) {
_stationTTL = 30.0;
int freqKhz = static_cast<int>(_frequency * 1000 + 0.5);
// make sure the frequency is integral multiple of 25, if not using 8.333 kHz spacing
if (!_useEightPointThree && freqKhz % 25) {
freqKhz += 5;
}
_commStationForFrequency = flightgear::CommStation::findByFreq(freqKhz, position, NULL);
}
if (!_commStationForFrequency.valid()) {
stopAudio();
return;
}
_slantDistance_m = dist(_commStationForFrequency->cart(), SGVec3d::fromGeod(position));
SGGeodesy::inverse(position, _commStationForFrequency->geod(), _trueBearingTo_deg, _trueBearingFrom_deg, _trackDistance_m);
_heightAboveStation_ft = SGMiscd::max(0.0, position.getElevationFt() - _commStationForFrequency->airport()->elevation());
_signalQuality_norm = _signalQualityComputer->computeSignalQuality(_slantDistance_m * SG_METER_TO_NM);
_stationType = _commStationForFrequency->nameForType(_commStationForFrequency->type());
_stationName = _commStationForFrequency->ident();
_airportId = _commStationForFrequency->airport()->getId();
_atisSpeaker.setStationId(_airportId);
switch (_commStationForFrequency->type()) {
case FGPositioned::FREQ_ATIS:
case FGPositioned::FREQ_AWOS: {
if (_signalQuality_norm > 0.01) {
_metarBridge->requestMetarForId(_airportId);
} else {
_metarBridge->clearMetar();
_atis = "";
}
}
break;
default:
_metarBridge->clearMetar();
_atis = "";
break;
}
updateAudio();
}
void CommRadioImpl::updateAudio()
{
if (!_sampleGroup)
return;
const string noiseRef = _soundPrefix + "_noise";
const string atisRef = _soundPrefix + "_atis";
SGSoundSample* noiseSample = _sampleGroup->find(noiseRef);
// create noise sample if necessary, and play forever
if (_addNoise && !noiseSample) {
SGSharedPtr<SGSoundSample> noise = new SGSoundSample("Sounds/radionoise.wav", globals->get_fg_root());
_sampleGroup->add(noise, noiseRef);
_sampleGroup->play_looped(noiseRef);
noiseSample = noise;
}
bool atis_enabled = _atis_enabled_node->getBoolValue();
int atis_delta = 0;
if (atis_enabled && !_atis_enabled_prev) atis_delta = 1;
if (!atis_enabled && _atis_enabled_prev) atis_delta = -1;
if (_atisSpeaker.hasSpokenAtis()) {
// the speaker has created a new atis sample
// remove previous atis sample
_sampleGroup->remove(atisRef);
if (!atis_delta && atis_enabled) atis_delta = 1;
}
if (atis_delta == 1) {
// Start play of atis text. We store the most recent sample in _atis_sample
// so that we can resume if /sim/atis/enabled is changed from false to
// true.
SGSharedPtr<SGSoundSample> sample = _atisSpeaker.getSpokenAtis();
if (sample) _atis_sample = sample;
else sample = _atis_sample;
if (sample) {
SG_LOG(SG_INSTR, SG_DEBUG, "starting looped play of atis sample.");
_sampleGroup->add(sample, atisRef);
_sampleGroup->play_looped(atisRef);
}
else {
SG_LOG(SG_INSTR, SG_DEBUG, "no atis sample available");
}
}
if (atis_delta == -1) {
// Stop play of atis text.
_sampleGroup->remove(atisRef);
}
_atis_enabled_prev = atis_enabled;
// adjust volumes
const bool doSquelch = (_signalQuality_norm < _cutoffSignalQuality);
double atisVolume = doSquelch ? 0.0 : _volume_norm;
if (_addNoise) {
const double noiseVol = (1.0 - _signalQuality_norm) * _volume_norm;
atisVolume = _signalQuality_norm * _volume_norm;
noiseSample->set_volume(doSquelch ? 0.0: noiseVol);
}
SGSoundSample* s = _sampleGroup->find(atisRef);
if (s) {
s->set_volume(atisVolume);
}
}
void CommRadioImpl::stopAudio()
{
if (_sampleGroup) {
_sampleGroup->remove(_soundPrefix + "_noise");
_sampleGroup->remove(_soundPrefix + "_atis");
}
}
SGSubsystem * CommRadio::createInstance(SGPropertyNode_ptr rootNode)
{
return new CommRadioImpl(rootNode);
}
// Register the subsystem.
#if 0
SGSubsystemMgr::InstancedRegistrant<CommRadio> registrantCommRadio(
SGSubsystemMgr::FDM,
{{"instrumentation", SGSubsystemMgr::Dependency::HARD}});
#endif
} // namespace Instrumentation

View File

@@ -0,0 +1,51 @@
// commradio.hxx -- class to manage a nav radio instance
//
// Written by Torsten Dreyer, started February 2014
//
// Copyright (C) Curtis L. Olson - http://www.flightgear.org/~curt
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation; either version 2 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
//
#ifndef _FG_INSTRUMENTATION_COMMRADIO_HXX
#define _FG_INSTRUMENTATION_COMMRADIO_HXX
#include <simgear/props/props.hxx>
#include <Instrumentation/AbstractInstrument.hxx>
namespace Instrumentation {
class SignalQualityComputer : public SGReferenced
{
public:
virtual ~SignalQualityComputer();
virtual double computeSignalQuality( double distance_nm ) const = 0;
};
typedef SGSharedPtr<SignalQualityComputer> SignalQualityComputerRef;
class CommRadio : public AbstractInstrument
{
public:
// Subsystem identification.
static const char* staticSubsystemClassId() { return "comm-radio"; }
static SGSubsystem * createInstance( SGPropertyNode_ptr rootNode );
};
}
#endif // _FG_INSTRUMENTATION_COMMRADIO_HXX

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,500 @@
// dclgps.hxx - a class to extend the operation of FG's current GPS
// code, and provide support for a KLN89-specific instrument. It
// is envisioned that eventually this file and class will be split
// up between current FG code and new KLN89-specific code and removed.
//
// Written by David Luff, started 2005.
//
// Copyright (C) 2005 - David C Luff: daveluff --AT-- ntlworld --D0T-- com
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation; either version 2 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
//
// $Id$
#ifndef _DCLGPS_HXX
#define _DCLGPS_HXX
#include <Cockpit/render_area_2d.hxx>
#include <string>
#include <list>
#include <vector>
#include <map>
#include <simgear/structure/subsystem_mgr.hxx>
#include <simgear/props/props.hxx>
#include <simgear/props/tiedpropertylist.hxx>
#include <Navaids/positioned.hxx>
class SGTime;
class FGPositioned;
// XXX fix me
class FGNavRecord;
class FGAirport;
class FGFix;
// --------------------- Waypoint / Flightplan stuff -----------------------------
// This should be merged with other similar stuff in FG at some point.
// NOTE - ORDERING IS IMPORTANT HERE - it matches the Bendix-King page ordering!
enum GPSWpType {
GPS_WP_APT = 0,
GPS_WP_VOR,
GPS_WP_NDB,
GPS_WP_INT,
GPS_WP_USR,
GPS_WP_VIRT // Used for virtual waypoints, such as the start of DTO operation.
};
enum GPSAppWpType {
GPS_IAF, // Initial approach fix
GPS_IAP, // Waypoint on approach sequence that isn't any of the others.
GPS_FAF, // Final approach fix
GPS_MAP, // Missed approach point
GPS_MAHP, // Initial missed approach holding point.
GPS_HDR, // A virtual 'waypoint' to represent the approach header in the fpl page
GPS_FENCE, // A virtual 'waypoint' to represent the NO WPT SEQ fence.
GPS_APP_NONE // Not part of the approach sequence - the default.
};
std::ostream& operator << (std::ostream& os, GPSAppWpType type);
struct GPSWaypoint {
GPSWaypoint();
GPSWaypoint(const std::string& aIdent, float lat, float lon, GPSWpType aType);
static GPSWaypoint* createFromPositioned(const FGPositioned* aFix);
~GPSWaypoint();
std::string GetAprId(); // Returns the id with i, f, m or h added if appropriate. (Initial approach fix, final approach fix, etc)
std::string id;
float lat; // Radians
float lon; // Radians
GPSWpType type;
GPSAppWpType appType; // only used for waypoints that are part of an approach sequence
};
typedef std::vector < GPSWaypoint* > gps_waypoint_array;
typedef gps_waypoint_array::iterator gps_waypoint_array_iterator;
typedef std::map < std::string, gps_waypoint_array > gps_waypoint_map;
typedef gps_waypoint_map::iterator gps_waypoint_map_iterator;
typedef gps_waypoint_map::const_iterator gps_waypoint_map_const_iterator;
class GPSFlightPlan
{
public:
std::vector<GPSWaypoint*> waypoints;
inline bool IsEmpty() { return waypoints.empty(); }
};
// TODO - probably de-public the internals of the next 2 classes and add some methods!
// Instrument approach procedure base class
class FGIAP
{
public:
FGIAP();
virtual ~FGIAP() = 0;
//protected:
std::string _aptIdent; // The ident of the airport this approach is for
std::string _ident; // The approach ident.
std::string _name; // The full approach name.
std::string _rwyStr; // The string used to specify the rwy - eg "B" in this instance.
bool _precision; // True for precision approach, false for non-precision.
};
// Non-precision instrument approach procedure
class FGNPIAP : public FGIAP
{
public:
FGNPIAP();
~FGNPIAP();
//private:
public:
std::vector<GPSFlightPlan*> _approachRoutes; // The approach route(s) from the IAF(s) to the IF.
// NOTE: It is an assumption in the code that uses this that there is a unique IAF per approach route.
std::vector<GPSWaypoint*> _IAP; // The compulsory waypoints of the approach procedure (may duplicate one of the above).
// _IAP includes the FAF and MAF, and the missed approach waypoints.
};
typedef std::vector < FGIAP* > iap_list_type;
typedef std::map < std::string, iap_list_type > iap_map_type;
typedef iap_map_type::iterator iap_map_iterator;
// A class to encapsulate hr:min representation of time.
class ClockTime
{
public:
ClockTime();
ClockTime(int hr, int min);
~ClockTime();
inline void set_hr(int hr) { _hr = hr; }
inline int hr() const { return(_hr); }
inline void set_min(int min) { _min = min; }
inline int min() const { return(_min); }
ClockTime operator+ (const ClockTime& t) {
int cumMin = _hr * 60 + _min + t.hr() * 60 + t.min();
ClockTime t2(cumMin / 60, cumMin % 60);
return(t2);
}
// Operator - has a max difference of 23:59,
// and assumes the day has wrapped if the second operand
// is larger that the first.
// eg. 2:59 - 3:00 = 23:59
ClockTime operator- (const ClockTime& t) {
int diff = (_hr * 60 + _min) - (t.hr() * 60 + t.min());
if(diff < 0) { diff += 24 * 60; }
ClockTime t2(diff / 60, diff % 60);
return(t2);
}
friend std::ostream& operator<< (std::ostream& out, const ClockTime& t);
private:
int _hr;
int _min;
};
// AlignedProjection - a class to project an area local to a runway onto an orthogonal co-ordinate system
// with the origin at the threshold and the runway aligned with the y axis.
class AlignedProjection
{
public:
AlignedProjection();
AlignedProjection(const SGGeod& centre, double heading);
~AlignedProjection();
void Init(const SGGeod& centre, double heading);
// Convert a lat/lon co-ordinate (degrees) to the local projection (meters)
SGVec3d ConvertToLocal(const SGGeod& pt);
// Convert a local projection co-ordinate (meters) to lat/lon (degrees)
SGGeod ConvertFromLocal(const SGVec3d& pt);
private:
SGGeod _origin; // lat/lon of local area origin (the threshold)
double _theta; // the rotation angle for alignment in radians
double _correction_factor; // Reduction in surface distance per degree of longitude due to latitude. Saves having to do a cos() every call.
};
// ------------------------------------------------------------------------------
// TODO - merge generic GPS functions instead and split out KLN specific stuff.
class DCLGPS : public SGSubsystem
{
public:
DCLGPS(RenderArea2D* instrument);
virtual ~DCLGPS() = 0;
// Subsystem API.
void bind() override;
void init() override;
void unbind() override;
void update(double dt) override;
virtual void draw(osg::State& state);
// Expand a SIAP ident to the full procedure name.
std::string ExpandSIAPIdent(const std::string& ident);
// Render string s in display field field at position x, y
// WHERE POSITION IS IN CHARACTER UNITS!
// zero y at bottom?
virtual void DrawText(const std::string& s, int field, int px, int py, bool bold = false);
// Render a char at a given position as above
virtual void DrawChar(char c, int field, int px, int py, bool bold = false);
virtual void ToggleOBSMode();
// Set the number of fields
inline void SetNumFields(int n) { _nFields = (n > _maxFields ? _maxFields : (n < 1 ? 1 : n)); }
// It is expected that specific GPS units will override these functions.
// Increase the CDI full-scale deflection (ie. increase the nm per dot) one (GPS unit dependent) increment. Wraps if necessary (GPS unit dependent).
virtual void CDIFSDIncrease();
// Ditto for decrease the distance per dot
virtual void CDIFSDDecrease();
// Host specifc
////inline void SetOverlays(Overlays* overlays) { _overlays = overlays; }
virtual void CreateDefaultFlightPlans();
void SetOBSFromWaypoint();
GPSWaypoint* GetActiveWaypoint();
// Get the (zero-based) position of the active waypoint in the active flightplan
// Returns -1 if no active waypoint.
int GetActiveWaypointIndex();
// Ditto for an arbitrary waypoint id
int GetWaypointIndex(const std::string& id);
// Returns meters
float GetDistToActiveWaypoint();
// Returns degrees (magnetic)
float GetHeadingToActiveWaypoint();
// Returns degrees (magnetic)
float GetHeadingFromActiveWaypoint();
// Get the time to the active waypoint in seconds.
// Returns -1 if groundspeed < 30 kts
double GetTimeToActiveWaypoint();
// Get the time to the final waypoint in seconds.
// Returns -1 if groundspeed < 30 kts
double GetETE();
// Get the time to a given waypoint (spec'd by ID) in seconds.
// returns -1 if groundspeed is less than 30kts.
// If the waypoint is an unreached part of the active flight plan the time will be via each leg.
// otherwise it will be a direct-to time.
double GetTimeToWaypoint(const std::string& id);
// Return true if waypoint alerting is occuring
inline bool GetWaypointAlert() const { return(_waypointAlert); }
// Return true if in OBS mode
inline bool GetOBSMode() const { return(_obsMode); }
// Return true if in Leg mode
inline bool GetLegMode() const { return(!_obsMode); }
// Clear a flightplan
void ClearFlightPlan(int n);
void ClearFlightPlan(GPSFlightPlan* fp);
// Returns true if an approach is loaded/armed/active in the active flight plan
inline bool ApproachLoaded() const { return(_approachLoaded); }
inline bool GetApproachArm() const { return(_approachArm); }
inline bool GetApproachActive() const { return(_approachActive); }
double GetCDIDeflection() const;
inline bool GetToFlag() const { return(_headingBugTo); }
// Initiate Direct To operation to the supplied ID.
virtual void DtoInitiate(const std::string& id);
// Cancel Direct To operation
void DtoCancel();
protected:
// Maximum number of display fields for this device
int _maxFields;
// Current number of on-screen fields
int _nFields;
// Full x border
int _xBorder;
// Full y border
int _yBorder;
// Lower (y) border per field
int _yFieldBorder[4];
// Left (x) border per field
int _xFieldBorder[4];
// Field start in x dir (border is part of field since it is the normal char border - sometimes map mode etc draws in it)
int _xFieldStart[4];
// Field start in y dir (for completeness - KLN89 only has vertical divider.
int _yFieldStart[4];
// The number of pages on the cyclic knob control
unsigned int _nPages;
// The current page we're on (Not sure how this ties in with extra pages such as direct or nearest).
unsigned int _curPage;
// 2D rendering area
RenderArea2D* _instrument;
// CDI full-scale deflection, specified either as an index into a vector of values (standard values) or as a double precision float (intermediate values).
// This will influence how an externally driven CDI will display as well as the NAV1 page.
// Hence the variables are located here, not in the nav page class.
std::vector<float> _cdiScales;
unsigned int _currentCdiScaleIndex;
bool _cdiScaleTransition; // Set true when the floating CDI value is used during transitions
double _currentCdiScale; // The floating value to use.
unsigned int _targetCdiScaleIndex; // The target indexed value to attain during a transition.
unsigned int _sourceCdiScaleIndex; // The source indexed value during a transition - so we know which way we're heading!
// Timers to handle the transitions - not sure if we need these.
double _apprArmTimer;
double _apprActvTimer;
double _cdiTransitionTime; // Time for transition to occur in - normally 30sec but may be quicker if time to FAF < 30sec?
//
// Data and lookup functions
protected:
void LoadApproachData();
// Find first of any type of waypoint by id. (TODO - Possibly we should return multiple waypoints here).
GPSWaypoint* FindFirstById(const std::string& id) const;
GPSWaypoint* FindFirstByExactId(const std::string& id) const;
FGNavRecord* FindFirstVorById(const std::string& id, bool &multi, bool exact = false);
FGNavRecord* FindFirstNDBById(const std::string& id, bool &multi, bool exact = false);
const FGAirport* FindFirstAptById(const std::string& id, bool &multi, bool exact = false);
const FGFix* FindFirstIntById(const std::string& id, bool &multi, bool exact = false);
// Find the closest VOR to a position in RADIANS.
FGNavRecord* FindClosestVor(double lat_rad, double lon_rad);
// helper to implement the above FindFirstXXX methods
FGPositioned* FindTypedFirstById(const std::string& id, FGPositioned::Type ty, bool &multi, bool exact);
// Position, orientation and velocity.
// These should be read from FG's built-in GPS logic if possible.
// Use the property node pointers below to do this.
SGPropertyNode_ptr _lon_node;
SGPropertyNode_ptr _lat_node;
SGPropertyNode_ptr _alt_node;
SGPropertyNode_ptr _grnd_speed_node;
SGPropertyNode_ptr _true_track_node;
SGPropertyNode_ptr _mag_track_node;
// Present position. (Radians)
double _lat, _lon;
// Present altitude (ft). (Yuk! but it saves converting ft->m->ft every update).
double _alt;
// Reported position as measured by GPS. For now this is the same
// as present position, but in the future we might want to model
// GPS lat and lon errors.
// Note - we can depriciate _gpsLat and _gpsLon if we implement error handling in FG
// gps code and not our own.
double _gpsLat, _gpsLon; //(Radians)
// Hack - it seems that the GPS gets initialised before FG's initial position is properly set.
// By checking for abnormal slew in the position we can force a re-initialisation of active flight
// plan leg and anything else that might be affected.
// TODO - sort FlightGear's initialisation order properly!!!
double _checkLat, _checkLon; // (Radians)
double _groundSpeed_ms; // filtered groundspeed (m/s)
double _groundSpeed_kts; // ditto in knots
double _track; // filtered true track (degrees)
double _magTrackDeg; // magnetic track in degrees calculated from true track above
// _navFlagged is set true when GPS navigation is either not possible or not logical.
// This includes not receiving adequate signals, and not having an active flightplan entered.
bool _navFlagged;
// Positional functions copied from ATCutils that might get replaced
// INPUT in RADIANS, returns DEGREES!
// Magnetic
double GetMagHeadingFromTo(double latA, double lonA, double latB, double lonB);
// True
//double GetHeadingFromTo(double latA, double lonA, double latB, double lonB);
// Given two positions (lat & lon in RADIANS), get the HORIZONTAL separation (in meters)
//double GetHorizontalSeparation(double lat1, double lon1, double lat2, double lon2);
// Proper great circle positional functions from The Aviation Formulary
// Returns distance in Nm, input in RADIANS.
double GetGreatCircleDistance(double lat1, double lon1, double lat2, double lon2) const;
// Input in RADIANS, output in DEGREES.
// True
double GetGreatCircleCourse(double lat1, double lon1, double lat2, double lon2) const;
// Return a position on a radial from wp1 given distance d (nm) and magnetic heading h (degrees)
// Note that d should be less that 1/4 Earth diameter!
GPSWaypoint GetPositionOnMagRadial(const GPSWaypoint& wp1, double d, double h);
// Return a position on a radial from wp1 given distance d (nm) and TRUE heading h (degrees)
// Note that d should be less that 1/4 Earth diameter!
GPSWaypoint GetPositionOnRadial(const GPSWaypoint& wp1, double d, double h);
// Calculate the current cross-track deviation in nm.
// Returns zero if a sensible value cannot be calculated.
double CalcCrossTrackDeviation() const;
// Calculate the cross-track deviation between 2 arbitrary waypoints in nm.
// Returns zero if a sensible value cannot be calculated.
double CalcCrossTrackDeviation(const GPSWaypoint& wp1, const GPSWaypoint& wp2) const;
// Flightplans
// GPS can have up to _maxFlightPlans flightplans stored, PLUS an active FP which may or my not be one of the stored ones.
// This is from KLN89, but is probably not far off the mark for most if not all GPS.
std::vector<GPSFlightPlan*> _flightPlans;
unsigned int _maxFlightPlans;
GPSFlightPlan* _activeFP;
// Modes of operation.
// This is currently somewhat Bendix-King specific, but probably applies fundamentally to other units as well
// Mode defaults to leg, but is OBS if _obsMode is true.
bool _obsMode;
// _dto is set true for DTO operation
bool _dto;
// In leg mode, we need to know if we are displaying a from and to waypoint, or just the to waypoint (eg. when OBS mode is cancelled).
bool _fullLegMode;
// In OBS mode we need to know the set OBS heading
int _obsHeading;
// Operational variables
GPSWaypoint _activeWaypoint;
GPSWaypoint _fromWaypoint;
float _dist2Act;
float _crosstrackDist; // UNITS ??????????
double _eta; // ETA in SECONDS to active waypoint.
// Desired track for active leg, true and magnetic, in degrees
double _dtkTrue, _dtkMag;
bool _headingBugTo; // Set true when the heading bug is TO, false when FROM.
bool _waypointAlert; // Set true when waypoint alerting is happening. (This is a variable NOT a user-setting).
bool _departed; // Set when groundspeed first exceeds 30kts.
std::string _departureTimeString; // Ditto.
double _elapsedTime; // Elapsed time in seconds since departure
ClockTime _powerOnTime; // Time (hr:min) of unit power-up.
bool _powerOnTimerSet; // Indicates that we have set the above following power-up.
void SetPowerOnTimer();
public:
void ResetPowerOnTimer();
// Set the alarm to go off at a given time.
inline void SetAlarm(int hr, int min) {
_alarmTime.set_hr(hr);
_alarmTime.set_min(min);
_alarmSet = true;
}
protected:
ClockTime _alarmTime;
bool _alarmSet;
// Configuration that affects flightplan operation
bool _turnAnticipationEnabled;
std::list<std::string> _messageStack;
virtual void CreateFlightPlan(GPSFlightPlan* fp, std::vector<std::string> ids, std::vector<GPSWpType> wps);
// Orientate the GPS unit to a flightplan - ie. figure out from current position
// and possibly orientation which leg of the FP we are on.
virtual void OrientateToFlightPlan(GPSFlightPlan* fp);
// Ditto for active fp. Probably all we need really!
virtual void OrientateToActiveFlightPlan();
int _cleanUpPage; // -1 => no cleanup required.
// IAP stuff
iap_map_type _np_iap; // Non-precision approaches
iap_map_type _pr_iap; // Precision approaches
bool _approachLoaded; // Set true when an approach is loaded in the active flightplan
bool _approachArm; // Set true when in approach-arm mode
bool _approachReallyArmed; // Apparently, approach-arm mode can be set from an external GPS-APR switch outside 30nm from airport,
// but the CDI scale change doesn't happen until 30nm from airport. Bizarre that it can be armed without
// the scale change, but it's in the manual...
bool _approachActive; // Set true when in approach-active mode
GPSFlightPlan* _approachFP; // Current approach - not necessarily loaded.
std::string _approachID; // ID of the airport we have an approach loaded for - bit of a hack that can hopefully be removed in future.
// More hackery since we aren't actually storing an approach class... Doh!
std::string _approachAbbrev;
std::string _approachRwyStr;
private:
simgear::TiedPropertyList _tiedProperties;
};
#endif // _DCLGPS_HXX

264
src/Instrumentation/dme.cxx Normal file
View File

@@ -0,0 +1,264 @@
// dme.cxx - distance-measuring equipment.
// Written by David Megginson, started 2003.
//
// This file is in the Public Domain and comes with no warranty.
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif
#include <simgear/compiler.h>
#include <simgear/sg_inlines.h>
#include <simgear/math/sg_geodesy.hxx>
#include <simgear/math/sg_random.hxx>
#include <simgear/sound/sample_group.hxx>
#include <Main/fg_props.hxx>
#include <Navaids/navlist.hxx>
#include <Sound/audioident.hxx>
#include "dme.hxx"
#include <cstdio>
/**
* Adjust the range.
*
* Start by calculating the radar horizon based on the elevation
* difference, then clamp to the maximum, then add a fudge for
* borderline reception.
*/
static double
adjust_range (double transmitter_elevation_ft, double aircraft_altitude_ft,
double max_range_nm)
{
double delta_elevation_ft =
fabs(aircraft_altitude_ft - transmitter_elevation_ft);
double range_nm = 1.23 * sqrt(delta_elevation_ft);
if (range_nm > max_range_nm)
range_nm = max_range_nm;
else if (range_nm < 20.0)
range_nm = 20.0;
double rand = sg_random();
return range_nm + (range_nm * rand * rand);
}
namespace {
class DMEFilter : public FGNavList::TypeFilter
{
public:
DMEFilter() :
TypeFilter(FGPositioned::DME),
_locEnabled(fgGetBool("/sim/realism/dme-fallback-to-loc", true))
{
if (_locEnabled) {
_mintype = FGPositioned::ILS;
}
}
virtual bool pass(FGPositioned* pos) const
{
switch (pos->type()) {
case FGPositioned::DME: return true;
case FGPositioned::ILS:
case FGPositioned::LOC: return _locEnabled;
default: return false;
}
}
private:
const bool _locEnabled;
};
} // of anonymous namespace
DME::DME ( SGPropertyNode *node )
: _last_distance_nm(0),
_last_frequency_mhz(-1),
_time_before_search_sec(0),
_navrecord(NULL),
_audioIdent(NULL)
{
readConfig(node, "dme");
}
DME::~DME ()
{
delete _audioIdent;
}
void
DME::init ()
{
std::string branch = nodePath();
SGPropertyNode *node = fgGetNode(branch, true );
initServicePowerProperties(node);
SGPropertyNode *fnode = node->getChild("frequencies", 0, true);
_source_node = fnode->getChild("source", 0, true);
_frequency_node = fnode->getChild("selected-mhz", 0, true);
_in_range_node = node->getChild("in-range", 0, true);
_distance_node = node->getChild("indicated-distance-nm", 0, true);
_speed_node = node->getChild("indicated-ground-speed-kt", 0, true);
_time_node = node->getChild("indicated-time-min", 0, true);
double d = node->getDoubleValue( "volume", 1.0 );
_volume_node = node->getChild("volume", 0, true);
_volume_node->setDoubleValue( d );
bool b = node->getBoolValue( "ident", false );
_ident_btn_node = node->getChild("ident", 0, true);
_ident_btn_node->setBoolValue( b );
SGPropertyNode *subnode = node->getChild("KDI572-574", 0, true);
_distance_string = subnode->getChild("nm",0, true);
_distance_string->setStringValue("---");
_speed_string = subnode->getChild("kt", 0, true);
_speed_string->setStringValue("---");
_time_string = subnode->getChild("min",0, true);
_time_string->setStringValue("--");
std::ostringstream temp;
temp << name() << "-ident-" << number();
if( NULL == _audioIdent )
_audioIdent = new DMEAudioIdent(temp.str());
_audioIdent->init();
reinit();
}
void
DME::reinit ()
{
_time_before_search_sec = 0;
clear();
}
void
DME::update (double delta_time_sec)
{
if( delta_time_sec < SGLimitsd::min() )
return; //paused
char tmp[16];
// Figure out the source
string source = _source_node->getStringValue();
if (source.empty()) {
std::string branch;
branch = "/instrumentation/" + name() + "/frequencies/selected-mhz";
_source_node->setStringValue(branch.c_str());
source = _source_node->getStringValue();
}
// Get the frequency
double frequency_mhz = fgGetDouble(source, 108.0);
if (frequency_mhz != _last_frequency_mhz) {
_time_before_search_sec = 0;
_last_frequency_mhz = frequency_mhz;
}
_frequency_node->setDoubleValue(frequency_mhz);
// Get the aircraft position
// On timeout, scan again
_time_before_search_sec -= delta_time_sec;
if (_time_before_search_sec < 0) {
_time_before_search_sec = 1.0;
SGGeod pos(globals->get_aircraft_position());
DMEFilter filter;
_navrecord = FGNavList::findByFreq(frequency_mhz, pos, &filter);
}
// If it's off, don't bother.
if (!isServiceableAndPowered()) {
clear();
return;
}
// If it's on, but invalid source,don't bother.
if (nullptr == _navrecord) {
clear();
return;
}
// Calculate the distance to the transmitter
double distance_nm = dist(_navrecord->cart(),
globals->get_aircraft_position_cart()) * SG_METER_TO_NM;
double range_nm = adjust_range(_navrecord->get_elev_ft(),
globals->get_aircraft_position().getElevationFt(),
_navrecord->get_range());
if (distance_nm <= range_nm) {
double volume = _volume_node->getDoubleValue();
if( !_ident_btn_node->getBoolValue() )
volume = 0.0;
_audioIdent->setIdent(_navrecord->ident(), volume );
double speed_kt = (fabs(distance_nm - _last_distance_nm) *
((1 / delta_time_sec) * 3600.0));
_last_distance_nm = distance_nm;
_in_range_node->setBoolValue(true);
double tmp_dist = distance_nm - _navrecord->get_multiuse();
if ( tmp_dist < 0.0 ) {
tmp_dist = 0.0;
}
_distance_node->setDoubleValue( tmp_dist );
if ( tmp_dist >389 ) tmp_dist = 389;
if ( tmp_dist >= 100.0) {
snprintf ( tmp,16,"%3.0f",tmp_dist);
} else {
snprintf ( tmp,16,"%2.1f",tmp_dist);
}
_distance_string->setStringValue(tmp);
_speed_node->setDoubleValue(speed_kt);
double spd = speed_kt;
if(spd>999) spd=999;
snprintf ( tmp,16,"%3.0f",spd);
_speed_string->setStringValue(tmp);
if (SGLimitsd::min() < fabs(speed_kt)){
double tm = distance_nm/speed_kt*60.0;
_time_node->setDoubleValue(tm);
if (tm >99) tm= 99;
snprintf ( tmp,16,"%2.0f",tm);
_time_string->setStringValue(tmp);
}
} else {
clear();
}
_audioIdent->update( delta_time_sec );
}
void DME::clear()
{
_last_distance_nm = 0;
_in_range_node->setBoolValue(false);
_distance_node->setDoubleValue(0);
_distance_string->setStringValue("---");
_speed_node->setDoubleValue(0);
_speed_string->setStringValue("---");
_time_node->setDoubleValue(0);
_time_string->setStringValue("--");
_audioIdent->setIdent("", 0.0);
}
// Register the subsystem.
#if 0
SGSubsystemMgr::InstancedRegistrant<DME> registrantDME(
SGSubsystemMgr::FDM,
{{"instrumentation", SGSubsystemMgr::Dependency::HARD}},
1.0);
#endif
// end of dme.cxx

View File

@@ -0,0 +1,75 @@
// dme.hxx - distance-measuring equipment.
// Written by David Megginson, started 2003.
//
// This file is in the Public Domain and comes with no warranty.
#ifndef __INSTRUMENTS_DME_HXX
#define __INSTRUMENTS_DME_HXX 1
#include <Instrumentation/AbstractInstrument.hxx>
// forward decls
class FGNavRecord;
/**
* Model a DME radio.
*
* Input properties:
*
* /position/longitude-deg
* /position/latitude-deg
* /position/altitude-ft
* /systems/electrical/outputs/dme
* /instrumentation/"name"/serviceable
* /instrumentation/"name"/frequencies/source
* /instrumentation/"name"/frequencies/selected-mhz
*
* Output properties:
*
* /instrumentation/"name"/in-range
* /instrumentation/"name"/indicated-distance-nm
* /instrumentation/"name"/indicated-ground-speed-kt
* /instrumentation/"name"/indicated-time-kt
*/
class DME : public AbstractInstrument
{
public:
DME ( SGPropertyNode *node );
virtual ~DME ();
// Subsystem API.
void init() override;
void reinit() override;
void update(double delta_time_sec) override;
// Subsystem identification.
static const char* staticSubsystemClassId() { return "dme"; }
private:
void clear();
SGPropertyNode_ptr _source_node;
SGPropertyNode_ptr _frequency_node;
SGPropertyNode_ptr _in_range_node;
SGPropertyNode_ptr _distance_node;
SGPropertyNode_ptr _speed_node;
SGPropertyNode_ptr _time_node;
SGPropertyNode_ptr _ident_btn_node;
SGPropertyNode_ptr _volume_node;
SGPropertyNode_ptr _distance_string;
SGPropertyNode_ptr _speed_string;
SGPropertyNode_ptr _time_string;
double _last_distance_nm;
double _last_frequency_mhz;
double _time_before_search_sec;
FGNavRecord * _navrecord;
class AudioIdent * _audioIdent;
};
#endif // __INSTRUMENTS_DME_HXX

View File

@@ -0,0 +1,62 @@
#ifndef __FREQUENCY_FORMATTER_HXX
#define __FREQUENCY_FORMATTER_HXX
/* ------------- A NAV/COMM Frequency formatter ---------------------- */
class FrequencyFormatterBase : public SGReferenced {
public:
virtual ~FrequencyFormatterBase()
{
}
virtual double getFrequency() const = 0;
};
class FrequencyFormatter : public FrequencyFormatterBase, public SGPropertyChangeListener {
public:
FrequencyFormatter( SGPropertyNode_ptr freqNode, SGPropertyNode_ptr fmtFreqNode, double channelSpacing, double min, double max ) :
_freqNode( freqNode ),
_fmtFreqNode( fmtFreqNode ),
_channelSpacing(channelSpacing),
_min(min),
_max(max)
{
_freqNode->addChangeListener( this, true );
}
virtual ~FrequencyFormatter()
{
_freqNode->removeChangeListener( this );
}
void valueChanged (SGPropertyNode * prop)
{
// format as fixed decimal "nnn.nn"
std::ostringstream buf;
buf << std::fixed
<< std::setw(5)
<< std::setfill('0')
<< std::setprecision(2)
<< getFrequency();
_fmtFreqNode->setStringValue( buf.str() );
}
virtual double getFrequency() const
{
double d = SGMiscd::roundToInt(_freqNode->getDoubleValue() / _channelSpacing) * _channelSpacing;
// strip last digit, do not round
double f = ((int)(d*100))/100.0;
if( f < _min ) return _min;
if( f >= _max ) return _max;
return f;
}
private:
SGPropertyNode_ptr _freqNode;
SGPropertyNode_ptr _fmtFreqNode;
double _channelSpacing;
double _min;
double _max;
};
#endif //__FREQUENCY_FORMATTER_HXX

1598
src/Instrumentation/gps.cxx Normal file

File diff suppressed because it is too large Load Diff

439
src/Instrumentation/gps.hxx Normal file
View File

@@ -0,0 +1,439 @@
// gps.hxx - distance-measuring equipment.
// Written by David Megginson, started 2003.
//
// This file is in the Public Domain and comes with no warranty.
#ifndef __INSTRUMENTS_GPS_HXX
#define __INSTRUMENTS_GPS_HXX 1
#include <cassert>
#include <memory>
#include <simgear/props/props.hxx>
#include <simgear/structure/subsystem_mgr.hxx>
#include <simgear/props/tiedpropertylist.hxx>
#include <Navaids/positioned.hxx>
#include <Navaids/FlightPlan.hxx>
#include <Instrumentation/rnav_waypt_controller.hxx>
#define FG_210_COMPAT 1
/**
* Model a GPS radio.
*
* Input properties:
*
* /position/longitude-deg
* /position/latitude-deg
* /position/altitude-ft
* /environment/magnetic-variation-deg
* /systems/electrical/outputs/gps
* /instrumentation/gps/serviceable
*
*
* Output properties:
*
* /instrumentation/gps/indicated-longitude-deg
* /instrumentation/gps/indicated-latitude-deg
* /instrumentation/gps/indicated-altitude-ft
* /instrumentation/gps/indicated-vertical-speed-fpm
* /instrumentation/gps/indicated-track-true-deg
* /instrumentation/gps/indicated-track-magnetic-deg
* /instrumentation/gps/indicated-ground-speed-kt
*
* /instrumentation/gps/wp-distance-nm
* /instrumentation/gps/wp-bearing-deg
* /instrumentation/gps/wp-bearing-mag-deg
* /instrumentation/gps/TTW
* /instrumentation/gps/course-deviation-deg
* /instrumentation/gps/course-error-nm
* /instrumentation/gps/to-flag
* /instrumentation/gps/odometer
* /instrumentation/gps/trip-odometer
* /instrumentation/gps/true-bug-error-deg
* /instrumentation/gps/magnetic-bug-error-deg
*/
class GPS : public SGSubsystem,
public flightgear::RNAV,
public flightgear::FlightPlan::Delegate
{
public:
GPS (SGPropertyNode *node, bool defaultGPSMode = false);
GPS ();
virtual ~GPS();
// SGSubsystem interface
void init() override;
void reinit() override;
void bind() override;
void unbind() override;
void update (double delta_time_sec) override;
void shutdown() override;
// Subsystem identification.
static const char* staticSubsystemClassId() { return "gps"; }
// RNAV interface
SGGeod position() override;
double trackDeg() override;
double groundSpeedKts() override;
double vspeedFPM() override;
double magvarDeg() override;
double selectedMagCourse() override;
double overflightDistanceM() override;
double overflightArmDistanceM() override;
double overflightArmAngleDeg() override;
bool canFlyBy() const override;
double maxFlyByTurnAngleDeg() const override;
simgear::optional<LegData> previousLegData() override;
simgear::optional<double> nextLegTrack() override;
double turnRadiusNm(double groundSpeedKnots) override;
private:
friend class SearchFilter;
void setFlyByMaxTurnAngle(double maxAngle);
/**
* Configuration manager, track data relating to aircraft installation
*/
class Config
{
public:
Config();
void bind(GPS* aOwner, SGPropertyNode* aCfg);
bool turnAnticipationEnabled() const { return _enableTurnAnticipation; }
/**
* Desired turn rate in degrees/second. From this we derive the turn
* radius and hence how early we need to anticipate it.
*/
double turnRateDegSec() const { return _turnRate; }
/**
* Distance at which we switch to next waypoint.
*/
double overflightDistanceNm() const { return _overflightDistance; }
/**
* Distance at which we arm overflight sequencing. Once inside this
* distance, a change of the wp1 'TO' flag to false will be considered
* overlight of the wp.
*/
double overflightArmDistanceNm() const { return _overflightArmDistance; }
/**
* abs angle at which we arm overflight sequencing.
*/
double overflightArmAngleDeg() const { return _overflightArmAngle; }
/**
* Time before the next WP to activate an external annunciator
*/
double waypointAlertTime() const { return _waypointAlertTime; }
bool requireHardSurface() const { return _requireHardSurface; }
bool cdiDeflectionIsAngular() const { return (_cdiMaxDeflectionNm <= 0.0); }
double cdiDeflectionLinearPeg() const
{
assert(_cdiMaxDeflectionNm > 0.0);
return _cdiMaxDeflectionNm;
}
bool driveAutopilot() const { return _driveAutopilot; }
bool courseSelectable() const { return _courseSelectable; }
/**
* Select whether we fly the leg track between waypoints, or
* use a direct course from the turn end. Since this is likely confusing,
* look at: http://fgfs.goneabitbursar.com//screenshots/FlyByType-LegType.svg
* For fly-by waypoints, there is no difference. For fly-over waypoints,
* this selects if we fly TF or DF mode.
*/
bool followLegTrackToFix() const { return _followLegTrackToFix; }
bool delegateDoesSequencing() const { return _delegateSequencing; }
double maxFlyByTurnAngleDeg() const { return _maxFlyByTurnAngle; }
void setMaxFlyByTurnAngle(double deg)
{
_maxFlyByTurnAngle = deg;
}
private:
bool _enableTurnAnticipation;
// desired turn rate in degrees per second
double _turnRate;
// distance from waypoint to arm overflight sequencing (in nm)
double _overflightDistance;
// distance from waypoint to arm overflight sequencing (in nm)
double _overflightArmDistance;
//abs angle from course to waypoint to arm overflight sequencing (in deg)
double _overflightArmAngle;
// time before reaching a waypoint to trigger annunciator light/sound
// (in seconds)
double _waypointAlertTime;
// should we require a hard-surfaced runway when filtering?
bool _requireHardSurface;
double _cdiMaxDeflectionNm;
// should we drive the autopilot directly or not?
bool _driveAutopilot;
// is selected-course-deg read to set desired-course or not?
bool _courseSelectable;
// do we fly direct to fixes, or follow the leg track closely?
bool _followLegTrackToFix;
// do we handle waypoint sequencing ourselves, or let the delegate do it?
// default is we do it, for backwards compatability
bool _delegateSequencing = false;
double _maxFlyByTurnAngle = 90.0;
};
class SearchFilter : public FGPositioned::Filter
{
public:
virtual bool pass(FGPositioned* aPos) const;
virtual FGPositioned::Type minType() const;
virtual FGPositioned::Type maxType() const;
};
/** reset all output properties to default / non-service values */
void clearOutput();
void updateBasicData(double dt);
void updateTrackingBug();
void updateRouteData();
void driveAutopilot();
/** Update one-shot things when WP1 / leg data change */
void wp1Changed();
void clearScratch();
/** Predicate, determine if the lon/lat position in the scratch is
* valid or not. */
bool isScratchPositionValid() const;
FGPositionedRef positionedFromScratch() const;
#if FG_210_COMPAT
void setScratchFromPositioned(FGPositioned* aPos, int aIndex);
void setScratchFromCachedSearchResult();
void setScratchFromRouteWaypoint(int aIndex);
/** Add airport-specific information to a scratch result */
void addAirportToScratch(FGAirport* aAirport);
FGPositioned::Filter* createFilter(FGPositioned::Type aTy);
/** Search kernel - called each time we step through a result */
void performSearch();
// command handlers
void loadRouteWaypoint();
void loadNearest();
void search();
void nextResult();
void previousResult();
void defineWaypoint();
void insertWaypointAtIndex(int aIndex);
void removeWaypointAtIndex(int aIndex);
void commandExitHold();
// tied-property getter/setters
double getScratchDistance() const;
double getScratchMagBearing() const;
double getScratchTrueBearing() const;
bool getScratchHasNext() const;
#endif
// command handlers
void selectLegMode();
void selectOBSMode(flightgear::Waypt* waypt);
void directTo();
// tied-property getter/setters
void setCommand(const char* aCmd);
const char* getCommand() const { return ""; }
const char* getMode() const { return _mode.c_str(); }
bool getScratchValid() const { return _scratchValid; }
double getSelectedCourse() const { return _selectedCourse; }
void setSelectedCourse(double crs);
double getDesiredCourse() const { return _desiredCourse; }
double getCDIDeflection() const;
double getLegDistance() const;
double getLegCourse() const;
double getLegMagCourse() const;
double getTrueTrack() const { return _last_true_track; }
double getMagTrack() const;
double getGroundspeedKts() const { return _last_speed_kts; }
double getVerticalSpeed() const { return _last_vertical_speed; }
const char* getWP0Ident() const;
const char* getWP0Name() const;
bool getWP1IValid() const;
const char* getWP1Ident() const;
const char* getWP1Name() const;
double getWP1Distance() const;
double getWP1TTW() const;
const char* getWP1TTWString() const;
double getWP1Bearing() const;
double getWP1MagBearing() const;
double getWP1CourseDeviation() const;
double getWP1CourseErrorNm() const;
bool getWP1ToFlag() const;
bool getWP1FromFlag() const;
// true-bearing-error and mag-bearing-error
double computeTurnRadiusNm(double aGroundSpeedKts) const;
/**
* Tied-properties helper, record nodes which are tied for easy un-tie-ing
*/
template <typename T>
void tie(SGPropertyNode* aNode, const char* aRelPath, const SGRawValue<T>& aRawValue)
{
_tiedProperties.Tie(aNode->getNode(aRelPath, true), aRawValue);
}
/** helper, tie the lat/lon/elev of a SGGeod to the named children of aNode */
void tieSGGeod(SGPropertyNode* aNode, SGGeod& aRef,
const char* lonStr, const char* latStr, const char* altStr);
/** helper, tie a SGGeod to proeprties, but read-only */
void tieSGGeodReadOnly(SGPropertyNode* aNode, SGGeod& aRef,
const char* lonStr, const char* latStr, const char* altStr);
void updateCurrentWpNode(const SGGeod& p);
// FlightPlan::Delegate
void currentWaypointChanged() override;
void waypointsChanged() override;
void cleared() override;
void endOfFlightPlan() override;
void doSequence();
void routeManagerFlightPlanChanged(SGPropertyNode*);
void routeActivated(SGPropertyNode*);
// members
SGPropertyNode_ptr _gpsNode;
SGPropertyNode_ptr _currentWayptNode;
SGPropertyNode_ptr _currentWpLatNode,
_currentWpLonNode, _currentWpAltNode;
SGPropertyNode_ptr _magvar_node;
SGPropertyNode_ptr _serviceable_node;
SGPropertyNode_ptr _electrical_node;
SGPropertyNode_ptr _tracking_bug_node;
SGPropertyNode_ptr _raim_node;
SGPropertyNode_ptr _odometer_node;
SGPropertyNode_ptr _trip_odometer_node;
SGPropertyNode_ptr _true_bug_error_node;
SGPropertyNode_ptr _magnetic_bug_error_node;
SGPropertyNode_ptr _eastWestVelocity;
SGPropertyNode_ptr _northSouthVelocity;
// SGPropertyNode_ptr _route_active_node;
SGPropertyNode_ptr _route_current_wp_node;
SGPropertyNode_ptr _routeDistanceNm;
SGPropertyNode_ptr _routeETE;
SGPropertyNode_ptr _desiredCourseNode;
double _selectedCourse;
double _desiredCourse;
bool _dataValid;
SGGeod _last_pos;
bool _lastPosValid;
double _last_speed_kts;
double _last_true_track;
double _last_vertical_speed;
double _lastEWVelocity;
double _lastNSVelocity;
/**
* the instrument manager creates a default instance of us,
* if no explicit GPS is specific in the aircraft's instruments.xml file.
* This allows default route-following to work with the generic autopilot.
* This flag is set in that case, to inform us we're a 'fake' installation,
* and not to worry about electrical power or similar.
*/
bool _defaultGPSMode;
std::string _mode;
Config _config;
std::string _name;
int _num;
SGGeod _wp0_position;
SGGeod _indicated_pos;
double _legDistanceNm;
// scratch data
SGGeod _scratchPos;
SGPropertyNode_ptr _scratchNode;
bool _scratchValid;
#if FG_210_COMPAT
// search data
int _searchResultIndex;
std::string _searchQuery;
FGPositioned::Type _searchType;
bool _searchExact;
FGPositionedList _searchResults;
bool _searchIsRoute; ///< set if 'search' is actually the current route
bool _searchHasNext; ///< is there a result after this one?
bool _searchNames; ///< set if we're searching names instead of idents
#endif
simgear::optional<RNAV::LegData> _wp0Data;
std::unique_ptr<flightgear::WayptController> _wayptController;
flightgear::WayptRef _prevWaypt;
flightgear::WayptRef _currentWaypt;
// autopilot drive properties
SGPropertyNode_ptr _apDrivingFlag;
SGPropertyNode_ptr _apTrueHeading;
simgear::TiedPropertyList _tiedProperties;
flightgear::FlightPlanRef _route;
SGPropertyChangeCallback<GPS> _callbackFlightPlanChanged;
SGPropertyChangeCallback<GPS> _callbackRouteActivated;
};
#endif // __INSTRUMENTS_GPS_HXX

View File

@@ -0,0 +1,94 @@
// gsdi.cxx - Ground Speed Drift Angle Indicator (known as GSDI or GSDA)
// Written by Melchior FRANZ, started 2006.
//
// Copyright (C) 2006 Melchior FRANZ - mfranz#aon:at
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation; either version 2 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif
#include <simgear/sg_inlines.h>
#include <simgear/constants.h>
#include <Main/fg_props.hxx>
#include "gsdi.hxx"
/*
* Failures or inaccuracies are currently not modeled due to lack of data.
* The Doppler based GSDI should output unreliable data with increasing
* pitch, roll, vertical acceleration and altitude-agl.
*/
GSDI::GSDI(SGPropertyNode *node) :
_name(node->getStringValue("name", "gsdi")),
_num(node->getIntValue("number", 0))
{
}
GSDI::~GSDI()
{
}
void GSDI::init()
{
std::string branch;
branch = "/instrumentation/" + _name;
SGPropertyNode *n = fgGetNode(branch.c_str(), _num, true);
_serviceableN = n->getNode("serviceable", true);
// input
_ubodyN = fgGetNode("/velocities/uBody-fps", true);
_vbodyN = fgGetNode("/velocities/vBody-fps", true);
// output
_drift_uN = n->getNode("drift-u-kt", true);
_drift_vN = n->getNode("drift-v-kt", true);
_drift_speedN = n->getNode("drift-speed-kt", true);
_drift_angleN = n->getNode("drift-angle-deg", true);
}
void GSDI::update(double /*delta_time_sec*/)
{
if (!_serviceableN->getBoolValue())
return;
double u = _ubodyN->getDoubleValue() * SG_FPS_TO_KT;
double v = _vbodyN->getDoubleValue() * SG_FPS_TO_KT;
double speed = sqrt(u * u + v * v);
double angle = atan2(v, u) * SGD_RADIANS_TO_DEGREES;
_drift_uN->setDoubleValue(u);
_drift_vN->setDoubleValue(v);
_drift_speedN->setDoubleValue(speed);
_drift_angleN->setDoubleValue(angle);
}
// Register the subsystem.
#if 0
SGSubsystemMgr::InstancedRegistrant<GSDI> registrantGSDI(
SGSubsystemMgr::FDM,
{{"instrumentation", SGSubsystemMgr::Dependency::HARD}});
#endif
// end of gsdi.cxx

View File

@@ -0,0 +1,73 @@
// gsdi.cxx - Ground Speed Drift Angle Indicator (known as GSDI or GSDA)
// Written by Melchior FRANZ, started 2006.
//
// Copyright (C) 2006 Melchior FRANZ - mfranz#aon:at
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation; either version 2 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#ifndef __INSTRUMENTS_GSDI_HXX
#define __INSTRUMENTS_GSDI_HXX 1
#include <simgear/props/props.hxx>
#include <simgear/structure/subsystem_mgr.hxx>
/**
* Input properties:
*
* /instrumentation/gsdi/serviceable
* /orientation/heading-deg
* /velocities/uBody-fps
* /velocities/vBody-fps
* /environment/wind-from-heading-deg
* /environment/wind-speed-kt
*
* Output properties:
*
* /instrumentation/gsdi/drift-u-kt
* /instrumentation/gsdi/drift-v-kt
* /instrumentation/gsdi/drift-speed-kt
* /instrumentation/gsdi/drift-angle-deg
*/
class GSDI : public SGSubsystem
{
public:
GSDI(SGPropertyNode *node);
virtual ~GSDI();
// Subsystem API.
void init() override;
void update(double dt) override;
// Subsystem identification.
static const char* staticSubsystemClassId() { return "gsdi"; }
private:
std::string _name;
unsigned int _num;
SGPropertyNode_ptr _serviceableN;
SGPropertyNode_ptr _headingN;
SGPropertyNode_ptr _ubodyN;
SGPropertyNode_ptr _vbodyN;
SGPropertyNode_ptr _wind_speedN;
SGPropertyNode_ptr _wind_dirN;
SGPropertyNode_ptr _drift_uN;
SGPropertyNode_ptr _drift_vN;
SGPropertyNode_ptr _drift_speedN;
SGPropertyNode_ptr _drift_angleN;
};
#endif // _INSTRUMENTS_GSDI_HXX

View File

@@ -0,0 +1,77 @@
// gyro.cxx - simple implementation of a spinning gyro model.
#include "gyro.hxx"
Gyro::Gyro ()
: _serviceable(true),
_power_norm(0.0),
_spin_norm(0.0)
{
}
Gyro::~Gyro ()
{
}
void Gyro::reinit(void)
{
_power_norm = 0.0;
_spin_norm = 0.0;
}
void
Gyro::update (double delta_time_sec)
{
// spin decays 0.5% every second
_spin_norm -= 0.005 * delta_time_sec;
// power can increase spin by 25%
// every second, but only up to the
// level of power available
if (_serviceable) {
double step = 0.25 * _power_norm * delta_time_sec;
if ((_spin_norm + step) <= _power_norm)
_spin_norm += step;
} else {
_spin_norm = 0; // stop right away if the gyro breaks
}
// clamp the spin to 0.0:1.0
if (_spin_norm < 0.0)
_spin_norm = 0.0;
else if (_spin_norm > 1.0)
_spin_norm = 1.0;
}
void
Gyro::set_power_norm (double power_norm)
{
_power_norm = power_norm;
}
double
Gyro::get_spin_norm () const
{
return _spin_norm;
}
void
Gyro::set_spin_norm (double spin_norm)
{
_spin_norm = spin_norm;
}
bool
Gyro::is_serviceable () const
{
return _serviceable;
}
void
Gyro::set_serviceable (bool serviceable)
{
_serviceable = serviceable;
}
// end of gyro.cxx

View File

@@ -0,0 +1,91 @@
// gyro.hxx - simple model of a spinning gyro.
#ifndef __INSTRUMENTATION_GYRO_HXX
#define __INSTRUMENTATION_GYRO_HXX 1
/**
* Simple model of a spinning gyro.
*
* The gyro decelerates gradually if no power is available to keep it
* spinning, and spins up quickly when power becomes available.
*/
class Gyro
{
public:
/**
* Constructor.
*/
Gyro ();
/**
* Destructor.
*/
virtual ~Gyro ();
/**
* Reset the gyro.
*/
void reinit(void);
/**
* Update the gyro.
*
* @param delta_time_sec The elapsed time since the last update.
* @param power_norm The power available to drive the gyro, from
* 0.0 to 1.0.
*/
virtual void update (double delta_time_sec);
/**
* Set the power available to the gyro.
*
* @param power_norm The amount of power (vacuum or electrical)
* available to keep the gyro spinning, from 0.0 (none) to
* 1.0 (full power)
*/
virtual void set_power_norm (double power_norm);
/**
* Get the gyro's current spin.
*
* @return The spin from 0.0 (not spinning) to 1.0 (full speed).
*/
virtual double get_spin_norm () const;
/**
* Set the gyro's current spin.
*
* @spin_norm The spin from 0.0 (not spinning) to 1.0 (full speed).
*/
virtual void set_spin_norm (double spin_norm);
/**
* Test if the gyro is serviceable.
*
* @return true if the gyro is serviceable, false otherwise.
*/
virtual bool is_serviceable () const;
/**
* Set the gyro's serviceability.
*
* @param serviceable true if the gyro is functional, false otherwise.
*/
virtual void set_serviceable (bool serviceable);
private:
bool _serviceable;
double _power_norm;
double _spin_norm;
};
#endif // __INSTRUMENTATION_GYRO_HXX

View File

@@ -0,0 +1,139 @@
// heading_indicator.cxx - a vacuum-powered heading indicator.
// Written by David Megginson, started 2002.
//
// This file is in the Public Domain and comes with no warranty.
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
#include <simgear/compiler.h>
#include <simgear/sg_inlines.h>
#include <simgear/math/SGMath.hxx>
#include <iostream>
#include <string>
#include <sstream>
#include "heading_indicator.hxx"
#include <Main/fg_props.hxx>
#include <Main/util.hxx>
HeadingIndicator::HeadingIndicator ( SGPropertyNode *node )
:
_name(node->getStringValue("name", "heading-indicator")),
_num(node->getIntValue("number", 0)),
_suction(node->getStringValue("suction", "/systems/vacuum/suction-inhg"))
{
}
HeadingIndicator::~HeadingIndicator ()
{
}
void
HeadingIndicator::init ()
{
std::string branch;
branch = "/instrumentation/" + _name;
SGPropertyNode *node = fgGetNode(branch.c_str(), _num, true );
if( NULL == (_offset_node = node->getChild("offset-deg", 0, false)) ) {
_offset_node = node->getChild("offset-deg", 0, true);
_offset_node->setDoubleValue( -fgGetDouble("/environment/magnetic-variation-deg") );
}
_heading_in_node = fgGetNode("/orientation/heading-deg", true);
_suction_node = fgGetNode(_suction.c_str(), true);
_heading_out_node = node->getChild("indicated-heading-deg", 0, true);
_heading_bug_error_node = node->getChild("heading-bug-error-deg", 0, true);
_heading_bug_node = node->getChild("heading-bug-deg", 0, true);
reinit();
}
void
HeadingIndicator::reinit ()
{
_last_heading_deg = (_heading_in_node->getDoubleValue() +
_offset_node->getDoubleValue());
_gyro.reinit();
}
void
HeadingIndicator::bind ()
{
std::ostringstream temp;
std::string branch;
temp << _num;
branch = "/instrumentation/" + _name + "[" + temp.str() + "]";
fgTie((branch + "/serviceable").c_str(),
&_gyro, &Gyro::is_serviceable, &Gyro::set_serviceable);
fgTie((branch + "/spin").c_str(),
&_gyro, &Gyro::get_spin_norm, &Gyro::set_spin_norm);
}
void
HeadingIndicator::unbind ()
{
std::ostringstream temp;
std::string branch;
temp << _num;
branch = "/instrumentation/" + _name + "[" + temp.str() + "]";
fgUntie((branch + "/serviceable").c_str());
fgUntie((branch + "/spin").c_str());
}
void
HeadingIndicator::update (double dt)
{
// Get the spin from the gyro
_gyro.set_power_norm(_suction_node->getDoubleValue()/5.0);
_gyro.update(dt);
double spin = _gyro.get_spin_norm();
// Next, calculate time-based precession
double offset = _offset_node->getDoubleValue();
offset -= dt * (0.25 / 60.0); // 360deg/day
SG_NORMALIZE_RANGE(offset, -360.0, 360.0);
// TODO: movement-induced error
// Next, calculate the indicated heading,
// introducing errors.
double factor = 100 * (spin * spin * spin * spin * spin * spin);
double heading = _heading_in_node->getDoubleValue();
// Now, we have to get the current
// heading and the last heading into
// the same range.
while ((heading - _last_heading_deg) > 180)
_last_heading_deg += 360;
while ((heading - _last_heading_deg) < -180)
_last_heading_deg -= 360;
heading = fgGetLowPass(_last_heading_deg, heading, dt * factor);
_last_heading_deg = heading;
heading += offset;
SG_NORMALIZE_RANGE(heading, 0.0, 360.0);
_heading_out_node->setDoubleValue(heading);
// Calculate heading bug error normalized to +/- 180.0
double heading_bug = _heading_bug_node->getDoubleValue();
double diff = heading_bug - heading;
SG_NORMALIZE_RANGE(diff, -180.0, 180.0);
_heading_bug_error_node->setDoubleValue( diff );
}
// Register the subsystem.
#if 0
SGSubsystemMgr::InstancedRegistrant<HeadingIndicator> registrantHeadingIndicator(
SGSubsystemMgr::FDM,
{{"instrumentation", SGSubsystemMgr::Dependency::HARD}});
#endif
// end of heading_indicator.cxx

View File

@@ -0,0 +1,68 @@
// heading_indicator.hxx - a vacuum-powered heading indicator.
// Written by David Megginson, started 2002.
//
// This file is in the Public Domain and comes with no warranty.
#ifndef __INSTRUMENTS_HEADING_INDICATOR_HXX
#define __INSTRUMENTS_HEADING_INDICATOR_HXX 1
#ifndef __cplusplus
# error This library requires C++
#endif
#include <simgear/props/props.hxx>
#include <simgear/structure/subsystem_mgr.hxx>
#include "gyro.hxx"
/**
* Model a vacuum-powered heading indicator.
*
* Input properties:
*
* /instrumentation/"name"/serviceable
* /instrumentation/"name"/spin
* /instrumentation/"name"/offset-deg
* /orientation/heading-deg
* "vacuum_system"/suction-inhg
*
* Output properties:
*
* /instrumentation/"name"/indicated-heading-deg
*/
class HeadingIndicator : public SGSubsystem
{
public:
HeadingIndicator ( SGPropertyNode *node );
HeadingIndicator ();
virtual ~HeadingIndicator ();
// Subsystem API.
void bind() override;
void init() override;
void reinit() override;
void unbind() override;
void update(double dt) override;
// Subsystem identification.
static const char* staticSubsystemClassId() { return "heading-indicator"; }
private:
Gyro _gyro;
double _last_heading_deg;
std::string _name;
int _num;
std::string _suction;
SGPropertyNode_ptr _offset_node;
SGPropertyNode_ptr _heading_in_node;
SGPropertyNode_ptr _suction_node;
SGPropertyNode_ptr _heading_out_node;
SGPropertyNode_ptr _heading_bug_error_node;
SGPropertyNode_ptr _heading_bug_node;
};
#endif // __INSTRUMENTS_HEADING_INDICATOR_HXX

View File

@@ -0,0 +1,215 @@
// heading_indicator_dg.cxx - a Directional Gyro (DG) compass.
// Based on the vacuum driven Heading Indicator Written by David Megginson,
// started 2002.
//
// Written by Vivian Meazza, started 2005.
//
// This file is in the Public Domain and comes with no warranty.
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
#include <simgear/compiler.h>
#include <simgear/sg_inlines.h>
#include <simgear/math/SGMath.hxx>
#include <iostream>
#include <string>
#include <sstream>
#include <Main/fg_props.hxx>
#include <Main/util.hxx>
#include "heading_indicator_dg.hxx"
/** Macro calculating x^6 (faster than super-slow math/pow). */
#define POW6(x) (x*x*x*x*x*x)
HeadingIndicatorDG::HeadingIndicatorDG ( SGPropertyNode *node ) :
name("heading-indicator-dg"),
num(0)
{
int i;
for ( i = 0; i < node->nChildren(); ++i ) {
SGPropertyNode *child = node->getChild(i);
std::string cname = child->getNameString();
std::string cval = child->getStringValue();
if ( cname == "name" ) {
name = cval;
} else if ( cname == "number" ) {
num = child->getIntValue();
} else {
SG_LOG( SG_INSTR, SG_WARN, "Error in DG heading-indicator config logic" );
if ( name.length() ) {
SG_LOG( SG_INSTR, SG_WARN, "Section = " << name );
}
}
}
}
HeadingIndicatorDG::~HeadingIndicatorDG ()
{
}
void
HeadingIndicatorDG::init ()
{
std::string branch;
branch = "/instrumentation/" + name;
_heading_in_node = fgGetNode("/orientation/heading-deg", true);
_yaw_rate_node = fgGetNode("/orientation/yaw-rate-degps", true);
_g_node = fgGetNode("/accelerations/pilot-g", true);
SGPropertyNode *node = fgGetNode(branch.c_str(), num, true );
_offset_node = node->getChild("offset-deg", 0, true);
_serviceable_node = node->getChild("serviceable", 0, true);
_heading_bug_error_node = node->getChild("heading-bug-error-deg", 0, true);
_error_node = node->getChild("error-deg", 0, true);
_nav1_error_node = node->getChild("nav1-course-error-deg", 0, true);
_heading_out_node = node->getChild("indicated-heading-deg", 0, true);
_align_node = node->getChild("align-deg", 0, true);
_electrical_node = fgGetNode("/systems/electrical/outputs/DG", true);
reinit();
}
void
HeadingIndicatorDG::bind ()
{
std::ostringstream temp;
std::string branch;
temp << num;
branch = "/instrumentation/" + name + "[" + temp.str() + "]";
fgTie((branch + "/serviceable").c_str(),
&_gyro, &Gyro::is_serviceable, &Gyro::set_serviceable);
fgTie((branch + "/spin").c_str(),
&_gyro, &Gyro::get_spin_norm, &Gyro::set_spin_norm);
}
void
HeadingIndicatorDG::unbind ()
{
std::ostringstream temp;
std::string branch;
temp << num;
branch = "/instrumentation/" + name + "[" + temp.str() + "]";
fgUntie((branch + "/serviceable").c_str());
fgUntie((branch + "/spin").c_str());
}
void
HeadingIndicatorDG::reinit (void)
{
// reset errors/drift values
_align_node->setDoubleValue(0.0);
_error_node->setDoubleValue(0.0);
_offset_node->setDoubleValue(0.0);
_last_heading_deg = _heading_in_node->getDoubleValue();
_last_indicated_heading_dg = _last_heading_deg;
_gyro.reinit();
}
void
HeadingIndicatorDG::update (double dt)
{
// Get the spin from the gyro
_gyro.set_power_norm(_electrical_node->getDoubleValue());
_gyro.update(dt);
// read inputs
double spin = _gyro.get_spin_norm();
double heading = _heading_in_node->getDoubleValue();
double offset = _offset_node->getDoubleValue();
// calculate scaling factor
double factor = POW6(spin);
// calculate time-based precession (scaled by spin factor, since
// there is no precession when the gyro is stuck).
offset -= dt * (0.25 / 60.0) * factor; // 360deg/day
// indication should get more and more stuck at low gyro spins
if (spin < 0.9)
{
// when gyro spin is low, then any heading change results in
// increasing the offset
double diff = SGMiscd::normalizePeriodic(-180.0, 180.0, _last_heading_deg - heading);
// scaled by 1-factor, so indication is fully stuck at spin==0 (offset compensates
// any heading change)
offset += diff * (1.0-factor);
}
_last_heading_deg = heading;
// normalize offset
offset = SGMiscd::normalizePeriodic(-180.0,180.0,offset);
_offset_node->setDoubleValue(offset);
// No magvar - set the alignment manually
double align = _align_node->getDoubleValue();
// Movement-induced error
double yaw_rate = _yaw_rate_node->getDoubleValue();
double error = _error_node->getDoubleValue();
double g = _g_node->getDoubleValue();
if ( fabs ( yaw_rate ) > 5 ) {
error += 0.033 * -yaw_rate * dt * factor;
}
if ( g > 1.5 || g < -0.5){
error += 0.033 * g * dt * factor;
}
_error_node->setDoubleValue(error);
// Now, we have to get the current
// heading and the last heading into
// the same range.
while ((heading - _last_indicated_heading_dg) > 180)
_last_indicated_heading_dg += 360;
while ((heading - _last_indicated_heading_dg) < -180)
_last_indicated_heading_dg -= 360;
heading = fgGetLowPass(_last_indicated_heading_dg, heading, dt * 100);
_last_indicated_heading_dg = heading;
heading += offset + align + error;
heading = SGMiscd::normalizePeriodic(0.0,360.0,heading);
_heading_out_node->setDoubleValue(heading);
// calculate the difference between the indicated heading
// and the selected heading for use with an autopilot
SGPropertyNode *bnode
= fgGetNode( "/autopilot/settings/heading-bug-deg", false );
if ( bnode ) {
double diff = bnode->getDoubleValue() - heading;
if ( diff < -180.0 ) { diff += 360.0; }
if ( diff > 180.0 ) { diff -= 360.0; }
_heading_bug_error_node->setDoubleValue( diff );
}
// calculate the difference between the indicated heading
// and the selected nav1 radial for use with an autopilot
SGPropertyNode *nnode
= fgGetNode( "/instrumentation/nav/radials/selected-deg", false );
if ( nnode ) {
double diff = nnode->getDoubleValue() - heading;
if ( diff < -180.0 ) { diff += 360.0; }
if ( diff > 180.0 ) { diff -= 360.0; }
_nav1_error_node->setDoubleValue( diff );
}
}
// Register the subsystem.
#if 0
SGSubsystemMgr::InstancedRegistrant<HeadingIndicatorDG> registrantHeadingIndicatorDG(
SGSubsystemMgr::FDM,
{{"instrumentation", SGSubsystemMgr::Dependency::HARD}});
#endif
// end of heading_indicator_dg.cxx

View File

@@ -0,0 +1,70 @@
// heading_indicator.hxx - a vacuum-powered heading indicator.
// Written by David Megginson, started 2002.
//
// This file is in the Public Domain and comes with no warranty.
#ifndef __INSTRUMENTS_HEADING_INDICATOR_ELEC_HXX
#define __INSTRUMENTS_HEADING_INDICATOR_ELEC_HXX 1
#include <simgear/props/props.hxx>
#include <simgear/structure/subsystem_mgr.hxx>
#include <simgear/math/sg_random.hxx>
#include "gyro.hxx"
/**
* Model an electrically-powered heading indicator.
*
* Input properties:
*
* /instrumentation/"name"/serviceable
* /instrumentation/"name"/spin
* /instrumentation/"name"/offset-deg
* /orientation/heading-deg
* /systems/electrical/outputs/DG
*
* Output properties:
*
* /instrumentation/"name"/indicated-heading-deg
*/
class HeadingIndicatorDG : public SGSubsystem
{
public:
HeadingIndicatorDG ( SGPropertyNode *node );
HeadingIndicatorDG ();
virtual ~HeadingIndicatorDG ();
// Subsystem API.
void bind() override;
void init() override;
void reinit() override;
void unbind() override;
void update(double dt) override;
// Subsystem identification.
static const char* staticSubsystemClassId() { return "heading-indicator-dg"; };
private:
Gyro _gyro;
double _last_heading_deg, _last_indicated_heading_dg;
std::string name;
int num;
SGPropertyNode_ptr _offset_node;
SGPropertyNode_ptr _heading_in_node;
SGPropertyNode_ptr _serviceable_node;
SGPropertyNode_ptr _heading_out_node;
SGPropertyNode_ptr _electrical_node;
SGPropertyNode_ptr _error_node;
SGPropertyNode_ptr _nav1_error_node;
SGPropertyNode_ptr _align_node;
SGPropertyNode_ptr _yaw_rate_node;
SGPropertyNode_ptr _heading_bug_error_node;
SGPropertyNode_ptr _g_node;
};
#endif // __INSTRUMENTS_HEADING_INDICATOR_ELEC_HXX

View File

@@ -0,0 +1,195 @@
// heading_indicator_fg.cxx - a flux_gate compass.
// Based on the vacuum driven Heading Indicator Written by David Megginson, started 2002.
//
// Written by Vivian Meazza, started 2005.
//
// This file is in the Public Domain and comes with no warranty.
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
#include <simgear/compiler.h>
#include <iostream>
#include <string>
#include <sstream>
#include <simgear/math/SGMath.hxx>
#include <Main/fg_props.hxx>
#include <Main/util.hxx>
#include "heading_indicator_fg.hxx"
using std::string;
HeadingIndicatorFG::HeadingIndicatorFG ( SGPropertyNode *node )
:
name("heading-indicator-fg"),
num(0)
{
int i;
for ( i = 0; i < node->nChildren(); ++i ) {
SGPropertyNode *child = node->getChild(i);
string cname = child->getNameString();
string cval = child->getStringValue();
if ( cname == "name" ) {
name = cval;
} else if ( cname == "number" ) {
num = child->getIntValue();
} else {
SG_LOG( SG_INSTR, SG_WARN, "Error in flux-gate heading-indicator config logic" );
if ( name.length() ) {
SG_LOG( SG_INSTR, SG_WARN, "Section = " << name );
}
}
}
}
HeadingIndicatorFG::HeadingIndicatorFG ()
{
}
HeadingIndicatorFG::~HeadingIndicatorFG ()
{
}
void
HeadingIndicatorFG::init ()
{
string branch;
branch = "/instrumentation/" + name;
_heading_in_node = fgGetNode("/orientation/heading-deg", true);
SGPropertyNode *node = fgGetNode(branch.c_str(), num, true );
if( NULL == (_offset_node = node->getChild("offset-deg", 0, false)) ) {
_offset_node = node->getChild("offset-deg", 0, true);
_offset_node->setDoubleValue( -fgGetDouble("/environment/magnetic-variation-deg") );
}
_serviceable_node = node->getChild("serviceable", 0, true);
_error_node = node->getChild("heading-bug-error-deg", 0, true);
_nav1_error_node = node->getChild("nav1-course-error-deg", 0, true);
_heading_out_node = node->getChild("indicated-heading-deg", 0, true);
_off_node = node->getChild("off-flag", 0, true);
_electrical_node = fgGetNode("/systems/electrical/outputs/DG", true);
reinit();
}
void
HeadingIndicatorFG::reinit ()
{
_last_heading_deg = (_heading_in_node->getDoubleValue() +
_offset_node->getDoubleValue());
_gyro.reinit();
}
void
HeadingIndicatorFG::bind ()
{
std::ostringstream temp;
string branch;
temp << num;
branch = "/instrumentation/" + name + "[" + temp.str() + "]";
fgTie((branch + "/serviceable").c_str(),
&_gyro, &Gyro::is_serviceable, &Gyro::set_serviceable);
fgTie((branch + "/spin").c_str(),
&_gyro, &Gyro::get_spin_norm, &Gyro::set_spin_norm);
}
void
HeadingIndicatorFG::unbind ()
{
std::ostringstream temp;
string branch;
temp << num;
branch = "/instrumentation/" + name + "[" + temp.str() + "]";
fgUntie((branch + "/serviceable").c_str());
fgUntie((branch + "/spin").c_str());
}
void
HeadingIndicatorFG::update (double dt)
{
// Get the spin from the gyro
_gyro.set_power_norm(_electrical_node->getDoubleValue());
_gyro.update(dt);
double spin = _gyro.get_spin_norm();
if ( _electrical_node->getDoubleValue() > 0 && spin >= 0.25) {
_off_node->setBoolValue(false);
} else {
_off_node->setBoolValue(true);
return;
}
// No time-based precession for a flux gate compass
// We just use offset to get the magvar
double offset = _offset_node->getDoubleValue();
// TODO: movement-induced error
// Next, calculate the indicated heading,
// introducing errors.
double factor = 100 * (spin * spin * spin * spin * spin * spin);
double heading = _heading_in_node->getDoubleValue();
// Now, we have to get the current
// heading and the last heading into
// the same range.
if ((heading - _last_heading_deg) > 180)
_last_heading_deg += 360;
if ((heading - _last_heading_deg) < -180)
_last_heading_deg -= 360;
heading = fgGetLowPass(_last_heading_deg, heading, dt * factor);
_last_heading_deg = heading;
heading += offset;
if (heading < 0)
heading += 360;
if (heading > 360)
heading -= 360;
_heading_out_node->setDoubleValue(heading);
// calculate the difference between the indicated heading
// and the selected heading for use with an autopilot
SGPropertyNode *bnode
= fgGetNode( "/autopilot/settings/heading-bug-deg", false );
double diff = 0;
if ( bnode ){
diff = bnode->getDoubleValue() - heading;
if ( diff < -180.0 ) { diff += 360.0; }
if ( diff > 180.0 ) { diff -= 360.0; }
_error_node->setDoubleValue( diff );
}
// calculate the difference between the indicated heading
// and the selected nav1 radial for use with an autopilot
SGPropertyNode *nnode
= fgGetNode( "/instrumentation/nav/radials/selected-deg", true );
double ndiff = 0;
if ( nnode ){
ndiff = nnode->getDoubleValue() - heading;
if ( ndiff < -180.0 ) { ndiff += 360.0; }
if ( ndiff > 180.0 ) { ndiff -= 360.0; }
_nav1_error_node->setDoubleValue( ndiff );
}
}
// Register the subsystem.
#if 0
SGSubsystemMgr::InstancedRegistrant<HeadingIndicatorFG> registrantHeadingIndicatorFG(
SGSubsystemMgr::FDM,
{{"instrumentation", SGSubsystemMgr::Dependency::HARD}});
#endif
// end of heading_indicator_fg.cxx

View File

@@ -0,0 +1,69 @@
// heading_indicator.hxx - a vacuum-powered heading indicator.
// Written by David Megginson, started 2002.
//
// This file is in the Public Domain and comes with no warranty.
#ifndef __INSTRUMENTS_HEADING_INDICATOR_FG_HXX
#define __INSTRUMENTS_HEADING_INDICATOR_FG_HXX 1
#ifndef __cplusplus
# error This library requires C++
#endif
#include <simgear/props/props.hxx>
#include <simgear/structure/subsystem_mgr.hxx>
#include "gyro.hxx"
/**
* Model an electically-powered fluxgate compass
*
* Input properties:
*
* /instrumentation/"name"/serviceable
* /instrumentation/"name"/spin
* /instrumentation/"name"/offset-deg
* /orientation/heading-deg
* /systems/electrical/outputs/DG
*
* Output properties:
*
* /instrumentation/"name"/indicated-heading-deg
*/
class HeadingIndicatorFG : public SGSubsystem
{
public:
HeadingIndicatorFG ( SGPropertyNode *node );
HeadingIndicatorFG ();
virtual ~HeadingIndicatorFG ();
// Subsystem API.
void bind() override;
void init() override;
void reinit() override;
void unbind() override;
void update(double dt) override;
// Subsystem identification.
static const char* staticSubsystemClassId() { return "heading-indicator-fg"; }
private:
Gyro _gyro;
double _last_heading_deg;
std::string name;
int num;
SGPropertyNode_ptr _offset_node;
SGPropertyNode_ptr _heading_in_node;
SGPropertyNode_ptr _serviceable_node;
SGPropertyNode_ptr _heading_out_node;
SGPropertyNode_ptr _electrical_node;
SGPropertyNode_ptr _error_node;
SGPropertyNode_ptr _nav1_error_node;
SGPropertyNode_ptr _off_node;
};
#endif // __INSTRUMENTS_HEADING_INDICATOR_HXX

View File

@@ -0,0 +1,249 @@
// inst_vertical_speed_indicator.cxx
// -- Instantaneous VSI (emulation calibrated to standard atmosphere).
//
// Started September 2004.
//
// Copyright (C) 2004
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation; either version 2 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
//
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
#include <limits>
#include <simgear/math/interpolater.hxx>
#include "inst_vertical_speed_indicator.hxx"
#include <Main/fg_props.hxx>
#include <Main/util.hxx>
// Altitude based on pressure difference from sea level.
//
// See http://www.pdas.com/programs/atmos.f90, the tables match exactly
// environment.cxx (Standard 1976).
// Example :
// - at 27900 m the level is 20 km :
// geopotential altitude 27.9 x 6369 / ( 27.9 + 6369) = 27.778 km.
// - deltah = 27.778 - 20 = 7.778 km and tbase 216.65 degK.
// - delta = 5.403295E-2 x exp( -34.163195 x 7.778 / 216.65 ) = 0.016.
// - pressure = (1 - delta ) x 29.92 = 29.44 inhg.
// - to construct the tables, round delta to 3 digits.
// altitude ft, pressure step inHG
static double pressure_data[][2] = {
{ 0.00, 3.33 }, // guess !
{ 2952.76, 3.05 },
{ 5905.51, 2.81 },
{ 8858.27, 2.55 },
{ 11811.02, 2.33 },
{ 14763.78, 2.13 },
{ 17716.54, 1.91 },
{ 20669.29, 1.77 },
{ 23622.05, 1.58 },
{ 26574.80, 1.49 },
{ 29527.56, 1.20 },
{ 32480.31, 1.14 },
{ 35433.07, 1.05 },
{ 38385.83, 0.90 },
{ 41338.58, 0.80 },
{ 44291.34, 0.69 },
{ 47244.09, 0.60 },
{ 50196.85, 0.51 },
{ 53149.61, 0.45 },
{ 56102.36, 0.39 },
{ 59055.12, 0.33 },
{ 62007.87, 0.30 },
{ 64960.63, 0.26 },
{ 67913.39, 0.21 },
{ 70866.14, 0.18 },
{ 73818.90, 0.18 },
{ 76771.65, 0.15 },
{ 79724.41, 0.12 },
{ 82677.17, 0.12 },
{ 85629.92, 0.09 },
{ 88582.68, 0.09 },
{ 91535.43, 0.06 },
{ 94488.19, 0.06 },
{ 97440.94, 0.06 },
{ 100393.70, 0.06 },
{ -1, -1 }
};
// pressure difference inHG, altitude ft
static double altitude_data[][2] = {
{ 0.00, 0.00 },
{ 3.05, 2952.76 },
{ 5.86, 5905.51 },
{ 8.41, 8858.27 },
{ 10.74, 11811.02 },
{ 12.87, 14763.78 },
{ 14.78, 17716.54 },
{ 16.55, 20669.29 },
{ 18.13, 23622.05 },
{ 19.62, 26574.80 },
{ 20.82, 29527.56 },
{ 21.96, 32480.31 },
{ 23.01, 35433.07 },
{ 23.91, 38385.83 },
{ 24.71, 41338.58 },
{ 25.40, 44291.34 },
{ 26.00, 47244.09 },
{ 26.51, 50196.85 },
{ 26.96, 53149.61 },
{ 27.35, 56102.36 },
{ 27.68, 59055.12 },
{ 27.98, 62007.87 },
{ 28.24, 64960.63 },
{ 28.45, 67913.39 },
{ 28.63, 70866.14 },
{ 28.81, 73818.90 },
{ 28.96, 76771.65 },
{ 29.08, 79724.41 },
{ 29.20, 82677.17 },
{ 29.29, 85629.92 },
{ 29.38, 88582.68 },
{ 29.44, 91535.43 },
{ 29.50, 94488.19 },
{ 29.56, 97440.94 },
{ 29.62, 100393.70 },
{ -1, -1 }
};
// SI constants
#define SEA_LEVEL_INHG 29.92
// A higher number means more responsive.
#define RESPONSIVENESS 5.0
// External environment
#define MAX_INHG_PER_S 0.0002
InstVerticalSpeedIndicator::InstVerticalSpeedIndicator ( SGPropertyNode *node ) :
_name(node->getStringValue("name", "inst-vertical-speed-indicator")),
_num(node->getIntValue("number", 0)),
_internal_pressure_inhg( SEA_LEVEL_INHG ),
_internal_sea_inhg( SEA_LEVEL_INHG ),
_speed_ft_per_s( 0 ),
_pressure_table(new SGInterpTable),
_altitude_table(new SGInterpTable)
{
int i;
for ( i = 0; pressure_data[i][0] != -1; i++)
_pressure_table->addEntry( pressure_data[i][0], pressure_data[i][1] );
for ( i = 0; altitude_data[i][0] != -1; i++)
_altitude_table->addEntry( altitude_data[i][0], altitude_data[i][1] );
}
InstVerticalSpeedIndicator::~InstVerticalSpeedIndicator ()
{
delete _pressure_table;
delete _altitude_table;
}
void InstVerticalSpeedIndicator::init ()
{
SGPropertyNode *node = fgGetNode("/instrumentation", true)->getChild(_name, _num, true);
_serviceable_node =
node->getNode("serviceable", true);
_freeze_node =
fgGetNode("/sim/freeze/master", true);
// A real IVSI is operated by static pressure changes.
// It operates like a conventional VSI, except that an internal sensor
// detects load factors, to momentarily alters the static pressure
// (with lag).
// It appears lag free at subsonic speed; at high altitude indication may
// be less than 1/3 of actual conditions.
_pressure_node =
fgGetNode("/environment/pressure-inhg", true);
_sea_node =
fgGetNode("/environment/pressure-sea-level-inhg", true);
_speed_node =
node->getNode("indicated-speed-fps", true);
_speed_min_node =
node->getNode("indicated-speed-fpm", true);
}
void InstVerticalSpeedIndicator::reinit ()
{
// Initialize at ambient pressure
_internal_pressure_inhg = _pressure_node->getDoubleValue();
_speed_ft_per_s = 0.0;
_internal_sea_inhg = _sea_node->getDoubleValue();
}
void InstVerticalSpeedIndicator::update (double dt)
{
if (_serviceable_node->getBoolValue())
{
// avoids hang, when freeze
if( !_freeze_node->getBoolValue() && std::numeric_limits<double>::min() < fabs(dt))
{
double pressure_inhg = _pressure_node->getDoubleValue();
double sea_inhg = _sea_node->getDoubleValue();
// limit effect of external environment
double rate_sea_inhg_per_s = ( sea_inhg - _internal_sea_inhg ) / dt;
if( rate_sea_inhg_per_s > - MAX_INHG_PER_S && rate_sea_inhg_per_s < MAX_INHG_PER_S )
{
double rate_inhg_per_s = ( pressure_inhg - _internal_pressure_inhg ) / dt;
// IVSI determines alone the current altitude, without altimeter setting.
// Altimeter setting is 29.92 above 10000 or 18000 ft.
// Below this level, the slope is slightly wrong.
double altitude_ft = _altitude_table->interpolate( SEA_LEVEL_INHG - pressure_inhg );
double slope_inhg = _pressure_table->interpolate( altitude_ft );
double last_speed_ft_per_s = _speed_ft_per_s;
// slope at 900 m
_speed_ft_per_s = - rate_inhg_per_s * 2952.75591 / slope_inhg;
// filter noise
_speed_ft_per_s = fgGetLowPass( last_speed_ft_per_s, _speed_ft_per_s,
dt * RESPONSIVENESS );
}
_speed_node->setDoubleValue( _speed_ft_per_s );
_speed_min_node->setDoubleValue( _speed_ft_per_s * 60.0 );
// backup
_internal_pressure_inhg = pressure_inhg;
_internal_sea_inhg = sea_inhg;
}
}
}
// Register the subsystem.
#if 0
SGSubsystemMgr::InstancedRegistrant<InstVerticalSpeedIndicator> registrantInstVerticalSpeedIndicator(
SGSubsystemMgr::FDM,
{{"instrumentation", SGSubsystemMgr::Dependency::HARD}});
#endif
// end of inst_vertical_speed_indicator.cxx

View File

@@ -0,0 +1,86 @@
// inst_vertical_speed_indicator.hxx -- Instantaneous VSI (emulation calibrated to standard atmosphere).
//
// Started September 2004.
//
// Copyright (C) 2004
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation; either version 2 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
//
#ifndef __INST_VERTICAL_SPEED_INDICATOR_HXX
#define __INST_VERTICAL_SPEED_INDICATOR_HXX 1
#ifndef __cplusplus
# error This library requires C++
#endif
#include <simgear/props/props.hxx>
#include <simgear/structure/subsystem_mgr.hxx>
class SGInterpTable;
/**
* Model an instantaneous VSI tied to the external pressure.
*
* Input properties:
*
* /instrumentation/inst-vertical-speed-indicator/serviceable
* /environment/pressure-inhg
* /environment/pressure-sea-level-inhg
* /sim/freeze/master
*
* Output properties:
*
* /instrumentation/inst-vertical-speed-indicator/indicated-speed-fps
* /instrumentation/inst-vertical-speed-indicator/indicated-speed-fpm
*/
class InstVerticalSpeedIndicator : public SGSubsystem
{
public:
InstVerticalSpeedIndicator ( SGPropertyNode *node );
virtual ~InstVerticalSpeedIndicator ();
// Subsystem API.
void init() override;
void reinit() override;
void update(double dt) override;
// Subsystem identification.
static const char* staticSubsystemClassId() { return "inst-vertical-speed-indicator"; }
private:
std::string _name;
int _num;
double _internal_pressure_inhg;
double _internal_sea_inhg;
double _speed_ft_per_s;
SGPropertyNode_ptr _serviceable_node;
SGPropertyNode_ptr _freeze_node;
SGPropertyNode_ptr _pressure_node;
SGPropertyNode_ptr _sea_node;
SGPropertyNode_ptr _speed_up_node;
SGPropertyNode_ptr _speed_node;
SGPropertyNode_ptr _speed_min_node;
SGInterpTable * _pressure_table;
SGInterpTable * _altitude_table;
};
#endif // __INST_VERTICAL_SPEED_INDICATOR_HXX

View File

@@ -0,0 +1,242 @@
// instrument_mgr.cxx - manage aircraft instruments.
// Written by David Megginson, started 2002.
//
// This file is in the Public Domain and comes with no warranty.
#include <config.h>
#include <iostream>
#include <string>
#include <sstream>
#include <simgear/structure/exception.hxx>
#include <simgear/misc/sg_path.hxx>
#include <simgear/sg_inlines.h>
#include <simgear/props/props_io.hxx>
#include <simgear/debug/ErrorReportingCallback.hxx>
#include <Main/fg_props.hxx>
#include <Main/globals.hxx>
#include <Main/util.hxx>
#include "instrument_mgr.hxx"
#include "adf.hxx"
#include "airspeed_indicator.hxx"
#include "altimeter.hxx"
#include "attitude_indicator.hxx"
#include "clock.hxx"
#include "dme.hxx"
#include "gps.hxx"
#include "gsdi.hxx"
#include "heading_indicator.hxx"
#include "heading_indicator_fg.hxx"
#include "heading_indicator_dg.hxx"
#include "kr_87.hxx"
#include "mag_compass.hxx"
#include "marker_beacon.hxx"
#include "newnavradio.hxx"
#include "commradio.hxx"
#include "slip_skid_ball.hxx"
#include "transponder.hxx"
#include "turn_indicator.hxx"
#include "vertical_speed_indicator.hxx"
#include "inst_vertical_speed_indicator.hxx"
#include "tacan.hxx"
#include "mk_viii.hxx"
#include "mrg.hxx"
#include "rad_alt.hxx"
#include "tcas.hxx"
FGInstrumentMgr::FGInstrumentMgr () :
_explicitGps(false)
{
}
FGInstrumentMgr::~FGInstrumentMgr ()
{
}
SGSubsystem::InitStatus FGInstrumentMgr::incrementalInit()
{
init();
return INIT_DONE;
}
void FGInstrumentMgr::init()
{
SGPropertyNode_ptr config_props = new SGPropertyNode;
SGPropertyNode* path_n = fgGetNode("/sim/instrumentation/path");
if (!path_n) {
SG_LOG(SG_COCKPIT, SG_DEV_WARN, "No instrumentation model specified for this model!");
return;
}
SGPath config = globals->resolve_aircraft_path(path_n->getStringValue());
if (!config.exists()) {
SG_LOG(SG_COCKPIT, SG_DEV_ALERT, "Missing instrumentation file at:" << config);
simgear::reportFailure(simgear::LoadFailure::NotFound, simgear::ErrorCode::AircraftSystems,
"FGInstrumentMgr: Missing instrumentation file", config);
return;
}
SG_LOG( SG_COCKPIT, SG_INFO, "Reading instruments from " << config );
try {
readProperties( config, config_props );
if (!build(config_props, config)) {
throw sg_exception(
"Detected an internal inconsistency in the instrumentation\n"
"system specification file. See earlier errors for details.");
}
} catch (const sg_exception& e) {
simgear::reportFailure(simgear::LoadFailure::BadData, simgear::ErrorCode::AircraftSystems,
"Failed to load instrumentation system:" + e.getFormattedMessage(),
e.getLocation());
}
if (!_explicitGps) {
SG_LOG(SG_INSTR, SG_INFO, "creating default GPS instrument");
SGPropertyNode_ptr nd(new SGPropertyNode);
nd->setStringValue("name", "gps");
nd->setIntValue("number", 0);
_instruments.push_back("gps[0]");
set_subsystem("gps[0]", new GPS(nd, true /* default GPS mode */));
}
SGSubsystemGroup::init();
}
bool FGInstrumentMgr::build (SGPropertyNode* config_props, const SGPath& path)
{
for ( int i = 0; i < config_props->nChildren(); ++i ) {
SGPropertyNode *node = config_props->getChild(i);
std::string name = node->getNameString();
std::ostringstream subsystemname;
subsystemname << "instrument-" << i << '-'
<< node->getStringValue("name", name.c_str());
int index = node->getIntValue("number", 0);
if (index > 0)
subsystemname << '['<< index << ']';
std::string id = subsystemname.str();
if ( name == "adf" ) {
set_subsystem( id, new ADF( node ), 0.15 );
} else if ( name == "airspeed-indicator" ) {
set_subsystem( id, new AirspeedIndicator( node ) );
} else if ( name == "altimeter" ) {
set_subsystem( id, new Altimeter( node, "altimeter" ) );
} else if ( name == "attitude-indicator" ) {
set_subsystem( id, new AttitudeIndicator( node ) );
} else if ( name == "clock" ) {
set_subsystem( id, new Clock( node ), 0.25 );
} else if ( name == "dme" ) {
set_subsystem( id, new DME( node ), 1.0 );
} else if ( name == "encoder" ) {
set_subsystem( id, new Altimeter( node, "encoder" ), 0.15 );
} else if ( name == "gps" ) {
// post 2.12.0, add a new name (distinct from 'gps'), so
// it is possible to create non-default GPS instruments.
// then authors of realistic GPS and FMSs can transition to using
// that name as they choose.
set_subsystem( id, new GPS( node, true /* default GPS mode */ ) );
_explicitGps = true;
} else if ( name == "gsdi" ) {
set_subsystem( id, new GSDI( node ) );
} else if ( name == "heading-indicator" ) {
set_subsystem( id, new HeadingIndicator( node ) );
} else if ( name == "heading-indicator-fg" ) {
set_subsystem( id, new HeadingIndicatorFG( node ) );
} else if ( name == "heading-indicator-dg" ) {
set_subsystem( id, new HeadingIndicatorDG( node ) );
} else if ( name == "KR-87" ) {
set_subsystem( id, new FGKR_87( node ) );
} else if ( name == "magnetic-compass" ) {
set_subsystem( id, new MagCompass( node ) );
} else if ( name == "marker-beacon" ) {
set_subsystem(id, new FGMarkerBeacon(node));
} else if ( name == "comm-radio" ) {
set_subsystem( id, Instrumentation::CommRadio::createInstance( node ) );
} else if ( name == "nav-radio" ) {
set_subsystem( id, Instrumentation::NavRadio::createInstance( node ) );
} else if ( name == "slip-skid-ball" ) {
set_subsystem( id, new SlipSkidBall( node ), 0.03 );
} else if (( name == "transponder" ) || ( name == "KT-70" )) {
if (name == "KT-70") {
SG_LOG(SG_INSTR, SG_DEV_ALERT, "KT-70 legacy instrument compatibility. "
"Please update aircraft to use transponder directly");
// force configuration into compatibility mode
node->setBoolValue("kt70-compatibility", true);
}
set_subsystem( id, new Transponder( node ), 0.2 );
} else if ( name == "turn-indicator" ) {
set_subsystem( id, new TurnIndicator( node ) );
} else if ( name == "vertical-speed-indicator" ) {
set_subsystem( id, new VerticalSpeedIndicator( node ) );
} else if ( name == "inst-vertical-speed-indicator" ) {
set_subsystem( id, new InstVerticalSpeedIndicator( node ) );
} else if ( name == "tacan" ) {
set_subsystem( id, new TACAN( node ), 0.2 );
} else if ( name == "mk-viii" ) {
set_subsystem( id, new MK_VIII( node ), 0.2);
} else if ( name == "master-reference-gyro" ) {
set_subsystem( id, new MasterReferenceGyro( node ) );
} else if (( name == "groundradar" ) ||
( name == "radar" ) ||
( name == "air-ground-radar" ) ||
( name == "navigation-display" ))
{
// these instruments are handled by the CockpitDisplayManager
// catch them here so we can still warn about bogus names in
// the instruments file
continue;
} else if ( name == "radar-altimeter" ) {
set_subsystem( id, new RadarAltimeter( node ) );
} else if ( name == "tcas" ) {
set_subsystem( id, new TCAS( node ), 0.2);
} else {
simgear::reportFailure(simgear::LoadFailure::Misconfigured, simgear::ErrorCode::AircraftSystems,
"Unknown top level section in instrumentation:" + name,
path);
continue;
}
// only push to our array if we actually built an insturment
_instruments.push_back(id);
} // of instruments iteration
return true;
}
// Register the subsystem.
SGSubsystemMgr::Registrant<FGInstrumentMgr> registrantFGInstrumentMgr(
SGSubsystemMgr::FDM);
// end of instrument_manager.cxx

View File

@@ -0,0 +1,41 @@
// instrument_mgr.hxx - manage aircraft instruments.
// Written by David Megginson, started 2002.
//
// This file is in the Public Domain and comes with no warranty.
#ifndef __INSTRUMENT_MGR_HXX
#define __INSTRUMENT_MGR_HXX 1
#include <simgear/compiler.h>
#include <simgear/structure/subsystem_mgr.hxx>
/**
* Manage aircraft instruments.
*
* In the initial draft, the instruments present are hard-coded, but they
* will soon be configurable for individual aircraft.
*/
class FGInstrumentMgr : public SGSubsystemGroup
{
public:
FGInstrumentMgr ();
virtual ~FGInstrumentMgr ();
// Subsystem API.
void init() override;
InitStatus incrementalInit() override;
// Subsystem identification.
static const char* staticSubsystemClassId() { return "instrumentation"; }
private:
bool build (SGPropertyNode* config_props, const SGPath& path);
bool _explicitGps = false;
std::vector<std::string> _instruments;
};
#endif // __INSTRUMENT_MGR_HXX

View File

@@ -0,0 +1,549 @@
// kr-87.cxx -- class to impliment the King KR 87 Digital ADF
//
// Written by Curtis Olson, started April 2002.
//
// Copyright (C) 2002 Curtis L. Olson - http://www.flightgear.org/~curt
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation; either version 2 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
//
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif
#include <stdio.h> // snprintf
#include <simgear/compiler.h>
#include <simgear/math/sg_random.hxx>
#include <simgear/math/sg_geodesy.hxx>
#include <simgear/timing/sg_time.hxx>
#include <simgear/sound/sample_group.hxx>
#include <Navaids/navlist.hxx>
#include "kr_87.hxx"
#include <Sound/morse.hxx>
#include <string>
using std::string;
static int play_count = 0;
static time_t last_time = 0;
/**
* Boy, this is ugly! Make the VOR range vary by altitude difference.
*/
static double kludgeRange ( double stationElev, double aircraftElev,
double nominalRange)
{
// Assume that the nominal range (usually
// 50nm) applies at a 5,000 ft difference.
// Just a wild guess!
double factor = (aircraftElev - stationElev)*SG_METER_TO_FEET / 5000.0;
double range = fabs(nominalRange * factor);
// Clamp the range to keep it sane; for
// now, never less than 50% or more than
// 500% of nominal range.
if (range < nominalRange/2.0) {
range = nominalRange/2.0;
} else if (range > nominalRange*5.0) {
range = nominalRange*5.0;
}
return range;
}
// Constructor
FGKR_87::FGKR_87( SGPropertyNode *node ) :
bus_power(fgGetNode("/systems/electrical/outputs/adf", true)),
serviceable(fgGetNode("/instrumentation/adf/serviceable", true)),
need_update(true),
valid(false),
inrange(false),
dist(0.0),
heading(0.0),
goal_needle_deg(0.0),
et_flash_time(0.0),
ant_mode(0),
stby_mode(0),
timer_mode(0),
count_mode(0),
rotation(0),
power_btn(true),
audio_btn(true),
vol_btn(0.5),
adf_btn(true),
bfo_btn(false),
frq_btn(false),
last_frq_btn(false),
flt_et_btn(false),
last_flt_et_btn(false),
set_rst_btn(false),
last_set_rst_btn(false),
freq(0),
stby_freq(0),
needle_deg(0.0),
flight_timer(0.0),
elapsed_timer(0.0),
tmp_timer(0.0),
_time_before_search_sec(0),
_sgr(NULL)
{
}
// Destructor
FGKR_87::~FGKR_87() {
}
void FGKR_87::init () {
SGSoundMgr *smgr = globals->get_subsystem<SGSoundMgr>();
_sgr = smgr->find("avionics", true);
_sgr->tie_to_listener();
}
void FGKR_87::reinit () {
_time_before_search_sec = 0;
}
void FGKR_87::bind () {
_tiedProperties.setRoot(fgGetNode("/instrumentation/kr-87", true));
// internal values
_tiedProperties.Tie("internal/valid", this, &FGKR_87::get_valid);
_tiedProperties.Tie("internal/inrange", this,
&FGKR_87::get_inrange);
_tiedProperties.Tie("internal/dist", this,
&FGKR_87::get_dist);
_tiedProperties.Tie("internal/heading", this,
&FGKR_87::get_heading);
// modes
_tiedProperties.Tie("modes/ant", this,
&FGKR_87::get_ant_mode);
_tiedProperties.Tie("modes/stby", this,
&FGKR_87::get_stby_mode);
_tiedProperties.Tie("modes/timer", this,
&FGKR_87::get_timer_mode);
_tiedProperties.Tie("modes/count", this,
&FGKR_87::get_count_mode);
// input and buttons
_tiedProperties.Tie("inputs/rotation-deg", this,
&FGKR_87::get_rotation, &FGKR_87::set_rotation);
fgSetArchivable("/instrumentation/kr-87/inputs/rotation-deg");
_tiedProperties.Tie("inputs/power-btn", this,
&FGKR_87::get_power_btn,
&FGKR_87::set_power_btn);
fgSetArchivable("/instrumentation/kr-87/inputs/power-btn");
_tiedProperties.Tie("inputs/audio-btn", this,
&FGKR_87::get_audio_btn,
&FGKR_87::set_audio_btn);
fgSetArchivable("/instrumentation/kr-87/inputs/audio-btn");
_tiedProperties.Tie("inputs/volume", this,
&FGKR_87::get_vol_btn,
&FGKR_87::set_vol_btn);
fgSetArchivable("/instrumentation/kr-87/inputs/volume");
_tiedProperties.Tie("inputs/adf-btn", this,
&FGKR_87::get_adf_btn,
&FGKR_87::set_adf_btn);
_tiedProperties.Tie("inputs/bfo-btn", this,
&FGKR_87::get_bfo_btn,
&FGKR_87::set_bfo_btn);
_tiedProperties.Tie("inputs/frq-btn", this,
&FGKR_87::get_frq_btn,
&FGKR_87::set_frq_btn);
_tiedProperties.Tie("inputs/flt-et-btn", this,
&FGKR_87::get_flt_et_btn,
&FGKR_87::set_flt_et_btn);
_tiedProperties.Tie("inputs/set-rst-btn", this,
&FGKR_87::get_set_rst_btn,
&FGKR_87::set_set_rst_btn);
// outputs
_tiedProperties.Tie("outputs/selected-khz", this,
&FGKR_87::get_freq, &FGKR_87::set_freq);
fgSetArchivable("/instrumentation/kr-87/outputs/selected-khz");
_tiedProperties.Tie("outputs/standby-khz", this,
&FGKR_87::get_stby_freq, &FGKR_87::set_stby_freq);
fgSetArchivable("/instrumentation/kr-87/outputs/standby-khz");
_tiedProperties.Tie("outputs/needle-deg", this,
&FGKR_87::get_needle_deg);
_tiedProperties.Tie("outputs/flight-timer", this,
&FGKR_87::get_flight_timer);
_tiedProperties.Tie("outputs/elapsed-timer", this,
&FGKR_87::get_elapsed_timer,
&FGKR_87::set_elapsed_timer);
// annunciators
_tiedProperties.Tie("annunciators/ant", this,
&FGKR_87::get_ant_ann );
_tiedProperties.Tie("annunciators/adf", this,
&FGKR_87::get_adf_ann );
_tiedProperties.Tie("annunciators/bfo", this,
&FGKR_87::get_bfo_ann );
_tiedProperties.Tie("annunciators/frq", this,
&FGKR_87::get_frq_ann );
_tiedProperties.Tie("annunciators/flt", this,
&FGKR_87::get_flt_ann );
_tiedProperties.Tie("annunciators/et", this,
&FGKR_87::get_et_ann );
}
void FGKR_87::unbind () {
_tiedProperties.Untie();
}
// Update the various nav values based on position and valid tuned in navs
void FGKR_87::update( double dt_sec ) {
SGGeod acft = globals->get_aircraft_position();
need_update = false;
double az1, az2, s;
// On timeout, scan again
_time_before_search_sec -= dt_sec;
if ( _time_before_search_sec < 0 ) {
search();
}
////////////////////////////////////////////////////////////////////////
// Radio
////////////////////////////////////////////////////////////////////////
if ( has_power() && serviceable->getBoolValue() ) {
// buttons
if ( adf_btn == 0 ) {
ant_mode = 1;
} else {
ant_mode = 0;
}
// cout << "ant_mode = " << ant_mode << endl;
if ( frq_btn && frq_btn != last_frq_btn && stby_mode == 0 ) {
int tmp = freq;
freq = stby_freq;
stby_freq = tmp;
} else if ( frq_btn ) {
stby_mode = 0;
count_mode = 0;
}
last_frq_btn = frq_btn;
if ( flt_et_btn && flt_et_btn != last_flt_et_btn ) {
if ( stby_mode == 0 ) {
timer_mode = 0;
} else {
timer_mode = !timer_mode;
}
stby_mode = 1;
}
last_flt_et_btn = flt_et_btn;
if ( set_rst_btn == 1 && set_rst_btn != last_set_rst_btn ) {
// button depressed
tmp_timer = 0.0;
}
if ( set_rst_btn == 1 && set_rst_btn == last_set_rst_btn ) {
// button depressed and was last iteration too
tmp_timer += dt_sec;
// cout << "tmp_timer = " << tmp_timer << endl;
if ( tmp_timer > 2.0 ) {
// button held depressed for 2 seconds
// cout << "entering elapsed count down mode" << endl;
timer_mode = 1;
count_mode = 2;
elapsed_timer = 0.0;
}
}
if ( set_rst_btn == 0 && set_rst_btn != last_set_rst_btn ) {
// button released
if ( tmp_timer > 2.0 ) {
// button held depressed for 2 seconds, don't adjust
// mode, just exit
} else if ( count_mode == 2 ) {
count_mode = 1;
} else {
count_mode = 0;
elapsed_timer = 0.0;
}
}
last_set_rst_btn = set_rst_btn;
// timers
flight_timer += dt_sec;
if ( set_rst_btn == 0 ) {
// only count if set/rst button not depressed
if ( count_mode == 0 ) {
elapsed_timer += dt_sec;
} else if ( count_mode == 1 ) {
elapsed_timer -= dt_sec;
if ( elapsed_timer < 1.0 ) {
count_mode = 0;
elapsed_timer = 0.0;
}
}
}
// annunciators
ant_ann = !adf_btn;
adf_ann = adf_btn;
bfo_ann = bfo_btn;
frq_ann = !stby_mode;
flt_ann = stby_mode && !timer_mode;
if ( count_mode < 2 ) {
et_ann = stby_mode && timer_mode;
} else {
et_flash_time += dt_sec;
if ( et_ann && et_flash_time > 0.5 ) {
et_ann = false;
et_flash_time -= 0.5;
} else if ( !et_ann && et_flash_time > 0.2 ) {
et_ann = true;
et_flash_time -= 0.2;
}
}
if ( valid ) {
// cout << "adf is valid" << endl;
// staightline distance
// What a hack, dist is a class local variable
dist = sqrt(distSqr(SGVec3d::fromGeod(acft), xyz));
// wgs84 heading
geo_inverse_wgs_84( acft, SGGeod::fromDeg(stn_lon, stn_lat),
&az1, &az2, &s );
heading = az1;
// cout << " heading = " << heading
// << " dist = " << dist << endl;
effective_range = kludgeRange(stn_elev, acft.getElevationFt(), range);
if ( dist < effective_range * SG_NM_TO_METER ) {
inrange = true;
} else if ( dist < 2 * effective_range * SG_NM_TO_METER ) {
inrange = sg_random() <
( 2 * effective_range * SG_NM_TO_METER - dist ) /
(effective_range * SG_NM_TO_METER);
} else {
inrange = false;
}
// cout << "inrange = " << inrange << endl;
if ( inrange ) {
goal_needle_deg = heading
- fgGetDouble("/orientation/heading-deg");
}
} else {
inrange = false;
}
if ( ant_mode ) {
goal_needle_deg = 90.0;
}
} else {
// unit turned off
goal_needle_deg = 0.0;
flight_timer = 0.0;
elapsed_timer = 0.0;
ant_ann = false;
adf_ann = false;
bfo_ann = false;
frq_ann = false;
flt_ann = false;
et_ann = false;
}
// formatted timer
double time;
int hours, min, sec;
if ( timer_mode == 0 ) {
time = flight_timer;
} else {
time = elapsed_timer;
}
// cout << time << endl;
hours = (int)(time / 3600.0);
time -= hours * 3600.00;
min = (int)(time / 60.0);
time -= min * 60.0;
sec = (int)time;
int big, little;
if ( hours > 0 ) {
big = hours;
if ( big > 99 ) {
big = 99;
}
little = min;
} else {
big = min;
little = sec;
}
if ( big > 99 ) {
big = 99;
}
char formatted_timer[24];
// cout << big << ":" << little << endl;
snprintf(formatted_timer, 24, "%02d:%02d", big, little);
fgSetString( "/instrumentation/kr-87/outputs/timer-string",
formatted_timer );
while ( goal_needle_deg < 0.0 ) { goal_needle_deg += 360.0; }
while ( goal_needle_deg >= 360.0 ) { goal_needle_deg -= 360.0; }
double diff = goal_needle_deg - needle_deg;
while ( diff < -180.0 ) { diff += 360.0; }
while ( diff > 180.0 ) { diff -= 360.0; }
needle_deg += diff * dt_sec * 4;
while ( needle_deg < 0.0 ) { needle_deg += 360.0; }
while ( needle_deg >= 360.0 ) { needle_deg -= 360.0; }
// cout << "goal = " << goal_needle_deg << " actual = " << needle_deg
// << endl;
// cout << "flt = " << flight_timer << " et = " << elapsed_timer
// << " needle = " << needle_deg << endl;
if ( valid && inrange && serviceable->getBoolValue() ) {
// play station ident via audio system if on + ant mode,
// otherwise turn it off
if ( vol_btn >= 0.01 && audio_btn ) {
SGSoundSample *sound;
sound = _sgr->find( "adf-ident" );
if ( sound != NULL ) {
if ( !adf_btn ) {
sound->set_volume( vol_btn );
} else {
sound->set_volume( vol_btn / 4.0 );
}
} else {
SG_LOG( SG_COCKPIT, SG_ALERT, "Can't find adf-ident sound" );
}
if ( last_time <
globals->get_time_params()->get_cur_time() - 30 ) {
last_time = globals->get_time_params()->get_cur_time();
play_count = 0;
}
if ( play_count < 4 ) {
// play ADF ident
if ( !_sgr->is_playing("adf-ident") && (vol_btn > 0.05) ) {
_sgr->play_once( "adf-ident" );
++play_count;
}
}
} else {
_sgr->stop( "adf-ident" );
}
}
}
// Update current nav/adf radio stations based on current postition
void FGKR_87::search() {
SGGeod pos = globals->get_aircraft_position();
// FIXME: the panel should handle this
static string last_ident = "";
// reset search time
_time_before_search_sec = 1.0;
////////////////////////////////////////////////////////////////////////
// ADF.
////////////////////////////////////////////////////////////////////////
FGNavList::TypeFilter filter(FGPositioned::NDB);
FGNavRecord *adf = FGNavList::findByFreq( freq, pos, &filter);
if ( adf != NULL ) {
char sfreq[128];
snprintf( sfreq, 10, "%d", freq );
ident = sfreq;
ident += adf->get_ident();
// cout << "adf ident = " << ident << endl;
valid = true;
if ( last_ident != ident ) {
last_ident = ident;
trans_ident = adf->get_trans_ident();
stn_lon = adf->get_lon();
stn_lat = adf->get_lat();
stn_elev = adf->get_elev_ft();
range = adf->get_range();
effective_range = kludgeRange(stn_elev, pos.getElevationM(), range);
xyz = adf->cart();
if ( _sgr->exists( "adf-ident" ) ) {
// stop is required! -- remove alone wouldn't stop immediately
_sgr->stop( "adf-ident" );
_sgr->remove( "adf-ident" );
}
SGSoundSample *sound;
sound = FGMorse::instance()->make_ident( trans_ident, FGMorse::LO_FREQUENCY );
sound->set_volume( 0.3 );
_sgr->add( sound, "adf-ident" );
int offset = (int)(sg_random() * 30.0);
play_count = offset / 4;
last_time = globals->get_time_params()->get_cur_time() -
offset;
// cout << "offset = " << offset << " play_count = "
// << play_count << " last_time = "
// << last_time << " current time = "
// << globals->get_time_params()->get_cur_time() << endl;
// cout << "Found an adf station in range" << endl;
// cout << " id = " << nav->get_ident() << endl;
}
} else {
valid = false;
ident = "";
trans_ident = "";
_sgr->remove( "adf-ident" );
last_ident = "";
// cout << "not picking up adf. :-(" << endl;
}
}
int FGKR_87::get_stby_freq() const {
if ( stby_mode == 0 ) {
return stby_freq;
} else {
if ( timer_mode == 0 ) {
return (int)flight_timer;
} else {
return (int)elapsed_timer;
}
}
}
// Register the subsystem.
#if 0
SGSubsystemMgr::InstancedRegistrant<FGKR_87> registrantFGKR_87(
SGSubsystemMgr::FDM,
{{"instrumentation", SGSubsystemMgr::Dependency::HARD},
{"sound", SGSubsystemMgr::Dependency::HARD}});
#endif

View File

@@ -0,0 +1,192 @@
// kr-87.hxx -- class to impliment the King KR 87 Digital ADF
//
// Written by Curtis Olson, started June 2002.
//
// Copyright (C) 2002 Curtis L. Olson - http://www.flightgear.org/~curt
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation; either version 2 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
//
// $Id$
#ifndef _FG_KR_87_HXX
#define _FG_KR_87_HXX
#include <Main/fg_props.hxx>
#include <simgear/compiler.h>
#include <simgear/structure/subsystem_mgr.hxx>
#include <simgear/props/tiedpropertylist.hxx>
#include <simgear/timing/timestamp.hxx>
#include <Navaids/navlist.hxx>
class SGSampleGroup;
class FGKR_87 : public SGSubsystem
{
private:
SGPropertyNode_ptr bus_power;
SGPropertyNode_ptr serviceable;
bool need_update;
// internal values
std::string ident;
std::string trans_ident;
bool valid;
bool inrange;
double stn_lon;
double stn_lat;
double stn_elev;
double range;
double effective_range;
double dist;
double heading;
SGVec3d xyz;
double goal_needle_deg;
double et_flash_time;
// modes
int ant_mode; // 0 = ADF mode (needle active), 1 = ANT mode
// (needle turned to 90, improved audio rcpt)
int stby_mode; // 0 = show stby freq, 1 = show timer
int timer_mode; // 0 = flt, 1 = et
int count_mode; // 0 = count up, 1 = count down, 2 = set et
// count down
// input and buttons
double rotation; // compass faceplace rotation
bool power_btn; // 0 = off, 1 = powered
bool audio_btn; // 0 = off, 1 = on
double vol_btn;
bool adf_btn; // 0 = normal, 1 = depressed
bool bfo_btn; // 0 = normal, 1 = depressed
bool frq_btn; // 0 = normal, 1 = depressed
bool last_frq_btn;
bool flt_et_btn; // 0 = normal, 1 = depressed
bool last_flt_et_btn;
bool set_rst_btn; // 0 = normal, 1 = depressed
bool last_set_rst_btn; // 0 = normal, 1 = depressed
// outputs
int freq;
int stby_freq;
double needle_deg;
double flight_timer;
double elapsed_timer;
double tmp_timer;
// annunciators
bool ant_ann;
bool adf_ann;
bool bfo_ann;
bool frq_ann;
bool flt_ann;
bool et_ann;
// internal periodic station search timer
double _time_before_search_sec;
SGSharedPtr<SGSampleGroup> _sgr;
simgear::TiedPropertyList _tiedProperties;
public:
FGKR_87( SGPropertyNode *node );
~FGKR_87();
// Subsystem API.
void bind() override;
void init() override;
void reinit() override;
void unbind() override;
void update(double dt_sec) override;
// Subsystem identification.
static const char* staticSubsystemClassId() { return "KR-87"; }
// Update nav/adf radios based on current postition
void search ();
// internal values
inline const std::string& get_ident() const { return ident; }
inline bool get_valid() const { return valid; }
inline bool get_inrange() const { return inrange; }
inline double get_stn_lon() const { return stn_lon; }
inline double get_stn_lat() const { return stn_lat; }
inline double get_dist() const { return dist; }
inline double get_heading() const { return heading; }
inline bool has_power() const {
return power_btn && (bus_power->getDoubleValue() > 1.0);
}
// modes
inline int get_ant_mode() const { return ant_mode; }
inline int get_stby_mode() const { return stby_mode; }
inline int get_timer_mode() const { return timer_mode; }
inline int get_count_mode() const { return count_mode; }
// input and buttons
inline double get_rotation () const { return rotation; }
inline void set_rotation( double rot ) { rotation = rot; }
inline bool get_power_btn() const { return power_btn; }
inline void set_power_btn( bool val ) {
power_btn = val;
}
inline bool get_audio_btn() const { return audio_btn; }
inline void set_audio_btn( bool val ) {
audio_btn = val;
}
inline double get_vol_btn() const { return vol_btn; }
inline void set_vol_btn( double val ) {
if ( val < 0.0 ) val = 0.0;
if ( val > 1.0 ) val = 1.0;
vol_btn = val;
}
inline bool get_adf_btn() const { return adf_btn; }
inline void set_adf_btn( bool val ) { adf_btn = val; }
inline bool get_bfo_btn() const { return bfo_btn; }
inline void set_bfo_btn( bool val ) { bfo_btn = val; }
inline bool get_frq_btn() const { return frq_btn; }
inline void set_frq_btn( bool val ) { frq_btn = val; }
inline bool get_flt_et_btn() const { return flt_et_btn; }
inline void set_flt_et_btn( bool val ) { flt_et_btn = val; }
inline bool get_set_rst_btn() const { return set_rst_btn; }
inline void set_set_rst_btn( bool val ) { set_rst_btn = val; }
// outputs
inline int get_freq () const { return freq; }
inline void set_freq( int f ) {
freq = f;
need_update = true;
}
int get_stby_freq () const;
inline void set_stby_freq( int f ) { stby_freq = f; }
inline double get_needle_deg() const { return needle_deg; }
inline double get_flight_timer() const { return flight_timer; }
inline double get_elapsed_timer() const { return elapsed_timer; }
inline void set_elapsed_timer( double val ) { elapsed_timer = val; }
// annunciators
inline bool get_ant_ann() const { return ant_ann; }
inline bool get_adf_ann() const { return adf_ann; }
inline bool get_bfo_ann() const { return bfo_ann; }
inline bool get_frq_ann() const { return frq_ann; }
inline bool get_flt_ann() const { return flt_ann; }
inline bool get_et_ann() const { return et_ann; }
};
#endif // _FG_KR_87_HXX

View File

@@ -0,0 +1,210 @@
// mag_compass.cxx - a magnetic compass.
// Written by David Megginson, started 2003.
//
// This file is in the Public Domain and comes with no warranty.
// This implementation is derived from an earlier one by Alex Perry,
// which appeared in src/Cockpit/steam.cxx
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif
#include <simgear/sg_inlines.h>
#include <simgear/math/SGMath.hxx>
#include <Main/fg_props.hxx>
#include <Main/util.hxx>
#include "mag_compass.hxx"
MagCompass::MagCompass ( SGPropertyNode *node )
: _rate_degps(0.0),
_name(node->getStringValue("name", "magnetic-compass")),
_num(node->getIntValue("number", 0))
{
SGPropertyNode_ptr n = node->getNode( "deviation", false );
if( n ) {
SGPropertyNode_ptr deviation_table_node = n->getNode( "table", false );
if( NULL != deviation_table_node ) {
_deviation_table = new SGInterpTable( deviation_table_node );
} else {
std::string deviation_node_name = n->getStringValue();
if( !deviation_node_name.empty() )
_deviation_node = fgGetNode( deviation_node_name, true );
}
}
}
MagCompass::~MagCompass ()
{
}
void
MagCompass::init ()
{
std::string branch;
branch = "/instrumentation/" + _name;
SGPropertyNode *node = fgGetNode(branch.c_str(), _num, true );
_serviceable_node = node->getChild("serviceable", 0, true);
_pitch_offset_node = node->getChild("pitch-offset-deg", 0, true);
_roll_node = fgGetNode("/orientation/roll-deg", true);
_pitch_node = fgGetNode("/orientation/pitch-deg", true);
_heading_node = fgGetNode("/orientation/heading-magnetic-deg", true);
_beta_node = fgGetNode("/orientation/side-slip-deg", true);
_dip_node = fgGetNode("/environment/magnetic-dip-deg", true);
_x_accel_node = fgGetNode("/accelerations/pilot/x-accel-fps_sec", true);
_y_accel_node = fgGetNode("/accelerations/pilot/y-accel-fps_sec", true);
_z_accel_node = fgGetNode("/accelerations/pilot/z-accel-fps_sec", true);
_out_node = node->getChild("indicated-heading-deg", 0, true);
reinit();
}
void
MagCompass::reinit ()
{
_rate_degps = 0.0;
}
void
MagCompass::update (double delta_time_sec)
{
// This is the real magnetic
// which would be displayed
// if the compass had no errors.
//double heading_mag_deg = _heading_node->getDoubleValue();
// don't update if the compass
// is broken
if (!_serviceable_node->getBoolValue())
return;
/*
* Vassilii: commented out because this way, even when parked,
* w/o any accelerations and level, the compass is jammed.
* If somebody wants to model jamming, real forces (i.e. accelerations)
* and not sideslip angle must be considered.
*/
#if 0
// jam on excessive sideslip
if (fabs(_beta_node->getDoubleValue()) > 12.0) {
_rate_degps = 0.0;
return;
}
#endif
/*
Formula for northernly turning error from
http://williams.best.vwh.net/compass/node4.html:
Hc: compass heading
psi: magnetic heading
theta: bank angle (right positive; should be phi here)
mu: dip angle (down positive)
Hc = atan2(sin(Hm)cos(theta)-tan(mu)sin(theta), cos(Hm))
This function changes the variable names to the more common psi
for the heading, theta for the pitch, and phi for the roll (and
target_deg for Hc). It also modifies the equation to
incorporate pitch as well as roll, as suggested by Chris
Metzler.
*/
// bank angle (radians)
double phi = _roll_node->getDoubleValue() * SGD_DEGREES_TO_RADIANS;
// pitch angle (radians)
double theta = _pitch_node->getDoubleValue() * SGD_DEGREES_TO_RADIANS
+ _pitch_offset_node->getDoubleValue() * SGD_DEGREES_TO_RADIANS;
// magnetic heading (radians)
double psi = _heading_node->getDoubleValue() * SGD_DEGREES_TO_RADIANS;
// magnetic dip (radians)
double mu = _dip_node->getDoubleValue() * SGD_DEGREES_TO_RADIANS;
/*
Tilt adjustments for accelerations.
The magnitudes of these are totally made up, but in real life,
they would depend on the fluid level, the amount of friction,
etc. anyway. Basically, the compass float tilts forward for
acceleration and backward for deceleration. Tilt about 4
degrees (0.07 radians) for every G (32 fps/sec) of
acceleration.
TODO: do something with the vertical acceleration.
*/
double x_accel_g = _x_accel_node->getDoubleValue() / 32;
double y_accel_g = _y_accel_node->getDoubleValue() / 32;
//double z_accel_g = _z_accel_node->getDoubleValue() / 32;
theta -= 0.07 * x_accel_g;
phi -= 0.07 * y_accel_g;
////////////////////////////////////////////////////////////////////
// calculate target compass heading degrees
////////////////////////////////////////////////////////////////////
// these are expensive: don't repeat
double sin_phi = sin(phi);
double sin_theta = sin(theta);
double sin_mu = sin(mu);
double cos_theta = cos(theta);
double cos_psi = cos(psi);
double cos_mu = cos(mu);
double a = cos(phi) * sin(psi) * cos_mu
- sin_phi * cos_theta * sin_mu
- sin_phi* sin_theta * cos_mu * cos_psi;
double b = cos_theta * cos_psi * cos(mu)
- sin_theta * sin_mu;
// This is the value that the compass
// is *trying* to display.
double target_deg = atan2(a, b) * SGD_RADIANS_TO_DEGREES;
if( _deviation_node ) {
target_deg -= _deviation_node->getDoubleValue();
} else if( _deviation_table ) {
target_deg -= _deviation_table->interpolate( SGMiscd::normalizePeriodic( 0.0, 360.0, target_deg ) );
}
double old_deg = _out_node->getDoubleValue();
while ((target_deg - old_deg) > 180.0)
target_deg -= 360.0;
while ((target_deg - old_deg) < -180.0)
target_deg += 360.0;
// The compass has a current rate of
// rotation -- move the rate of rotation
// towards one that will turn the compass
// to the correct heading, but lag a bit.
// (so that the compass can keep overshooting
// and coming back).
double error = target_deg - old_deg;
_rate_degps = fgGetLowPass(_rate_degps, error, delta_time_sec / 5.0);
double indicated_deg = old_deg + _rate_degps * delta_time_sec;
SG_NORMALIZE_RANGE(indicated_deg, 0.0, 360.0);
// That's it -- set the messed-up heading.
_out_node->setDoubleValue(indicated_deg);
}
// Register the subsystem.
#if 0
SGSubsystemMgr::InstancedRegistrant<MagCompass> registrantMagCompass(
SGSubsystemMgr::FDM,
{{"instrumentation", SGSubsystemMgr::Dependency::HARD}});
#endif
// end of altimeter.cxx

View File

@@ -0,0 +1,77 @@
// mag_compass.hxx - an altimeter tied to the static port.
// Written by David Megginson, started 2002.
//
// This file is in the Public Domain and comes with no warranty.
#ifndef __INSTRUMENTS_MAG_COMPASS_HXX
#define __INSTRUMENTS_MAG_COMPASS_HXX 1
#ifndef __cplusplus
# error This library requires C++
#endif
#include <simgear/props/props.hxx>
#include <simgear/structure/subsystem_mgr.hxx>
#include <simgear/math/interpolater.hxx>
/**
* Model a magnetic compass.
*
* Input properties:
*
* /instrumentation/"name"/serviceable
* /instrumentation/"name"/pitch-offset-deg
* /instrumentation/"name"/max-pitch-deg
* /instrumentation/"name"/max-roll-deg
* /orientation/roll-deg
* /orientation/pitch-deg
* /orientation/heading-magnetic-deg
* /orientation/side-slip-deg
* /environment/magnetic-dip-deg
* /accelerations/pilot/north-accel-fps_sec
* /accelerations/pilot/east-accel-fps_sec
* /accelerations/pilot/down-accel-fps_sec
*
* Output properties:
*
* /instrumentation/"name"/indicated-heading-deg
*/
class MagCompass : public SGSubsystem
{
public:
MagCompass ( SGPropertyNode *node);
MagCompass ();
virtual ~MagCompass ();
// Subsystem API.
void init() override;
void reinit() override;
void update(double dt) override;
// Subsystem identification.
static const char* staticSubsystemClassId() { return "magnetic-compass"; }
private:
double _rate_degps;
std::string _name;
int _num;
SGSharedPtr<SGInterpTable> _deviation_table;
SGPropertyNode_ptr _deviation_node;
SGPropertyNode_ptr _serviceable_node;
SGPropertyNode_ptr _pitch_offset_node;
SGPropertyNode_ptr _roll_node;
SGPropertyNode_ptr _pitch_node;
SGPropertyNode_ptr _heading_node;
SGPropertyNode_ptr _beta_node;
SGPropertyNode_ptr _dip_node;
SGPropertyNode_ptr _x_accel_node;
SGPropertyNode_ptr _y_accel_node;
SGPropertyNode_ptr _z_accel_node;
SGPropertyNode_ptr _out_node;
};
#endif // __INSTRUMENTS_MAG_COMPASS_HXX

View File

@@ -0,0 +1,406 @@
// marker_beacon.cxx -- class to manage the marker beacons
//
// Written by Curtis Olson, started April 2000.
//
// Copyright (C) 2000 Curtis L. Olson - http://www.flightgear.org/~curt
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation; either version 2 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
//
// $Id$
#include <config.h>
#include <stdio.h> // snprintf
#include <simgear/compiler.h>
#include <simgear/math/sg_random.hxx>
#include <simgear/misc/sg_path.hxx>
#include <simgear/sound/sample_group.hxx>
#include <Main/fg_props.hxx>
#include <Navaids/navlist.hxx>
#include "marker_beacon.hxx"
#include <Sound/beacon.hxx>
#include <string>
using std::string;
static SGSoundSample* createSampleForBeacon(FGMarkerBeacon::fgMkrBeacType ty)
{
switch (ty) {
case FGMarkerBeacon::INNER:
return FGBeacon::instance()->get_inner();
case FGMarkerBeacon::MIDDLE:
return FGBeacon::instance()->get_middle();
case FGMarkerBeacon::OUTER:
return FGBeacon::instance()->get_outer();
default:
return nullptr;
}
}
static string sampleNameForBeacon(FGMarkerBeacon::fgMkrBeacType ty)
{
switch (ty) {
case FGMarkerBeacon::INNER: return "inner-marker";
case FGMarkerBeacon::MIDDLE: return "middle-marker";
case FGMarkerBeacon::OUTER: return "outer-marker";
default:
return {};
}
}
// Constructor
FGMarkerBeacon::FGMarkerBeacon(SGPropertyNode *node) :
_time_before_search_sec(0.0)
{
// backwards-compatability supply path
setDefaultPowerSupplyPath("/systems/electrical/outputs/nav[0]");
readConfig(node, "marker-beacon");
string blinkMode = node->getStringValue("blink-mode");
if (blinkMode == "standard") {
_blinkMode = BlinkMode::Standard;
} else if (blinkMode == "continuous") {
_blinkMode = BlinkMode::Continuous;
}
}
// Destructor
FGMarkerBeacon::~FGMarkerBeacon()
{
}
void
FGMarkerBeacon::init ()
{
SGPropertyNode *node = fgGetNode(nodePath(), true );
initServicePowerProperties(node);
// Inputs
sound_working = fgGetNode("/sim/sound/working", true);
audio_btn = node->getChild("audio-btn", 0, true);
audio_vol = node->getChild("volume", 0, true);
if (audio_btn->getType() == simgear::props::NONE)
audio_btn->setBoolValue( true );
SGSoundMgr *smgr = globals->get_subsystem<SGSoundMgr>();
if (smgr) {
_audioSampleGroup = smgr->find("avionics", true);
_audioSampleGroup->tie_to_listener();
sound_working->addChangeListener(this);
audio_btn->addChangeListener(this);
audio_vol->addChangeListener(this);
}
reinit();
}
void
FGMarkerBeacon::reinit ()
{
_time_before_search_sec = 0.0;
_lastBeacon = NOBEACON;
updateOutputProperties(false);
}
void
FGMarkerBeacon::bind ()
{
string branch = nodePath();
_innerBlinkNode = fgGetNode(branch + "/inner", true);
_middleBlinkNode = fgGetNode(branch + "/middle", true);
_outerBlinkNode = fgGetNode(branch + "/outer", true);
}
void
FGMarkerBeacon::unbind ()
{
string branch = nodePath();
fgUntie((branch + "/inner").c_str());
fgUntie((branch + "/middle").c_str());
fgUntie((branch + "/outer").c_str());
if (_audioSampleGroup) {
sound_working->removeChangeListener(this);
audio_btn->removeChangeListener(this);
audio_vol->removeChangeListener(this);
}
AbstractInstrument::unbind();
}
// Update the various nav values based on position and valid tuned in navs
void
FGMarkerBeacon::update(double dt)
{
if (!isServiceableAndPowered()) {
_lastBeacon = NOBEACON;
stopAudio();
updateOutputProperties(false);
return;
}
_time_before_search_sec -= dt;
if ( _time_before_search_sec < 0 ) {
search();
}
if (_audioPropertiesChanged) {
updateAudio();
}
if (_lastBeacon != NOBEACON) {
// compute blink to match audio
// we use our own timing here (instead of dt) since audio rate is not affected
// by pause or time acceleration, so this should stay in sync.
const int elapasedUSec = (SGTimeStamp::now() - _audioStartTime).toUSecs();
bool on = true;
if (_blinkMode != BlinkMode::Continuous) {
int t = elapasedUSec % _beaconTiming.durationUSec;
for (int i = 0; i < 4; i++) {
t -= _beaconTiming.periodsUSec.at(i);
if (t < 0) {
// if value is negative, current time is within this
// period, so we are finished.
break;
}
// each period, the sense flips
on = !on;
} // of periods iteration
}
updateOutputProperties(on);
}
}
static void lazyChangeBoolProp(SGPropertyNode* node, bool v)
{
if (node->getBoolValue() != v) {
node->setBoolValue(v);
}
}
void FGMarkerBeacon::updateOutputProperties(bool on)
{
// map our beacon nodes to indices which correspond to the fgMkrBeacType enum
// this allows to use '_lastBeacon' to select whhich index should be on
// we set all other ones to off to ensure consistency in weird cases, eg
// going from one beaon type to another in a single update.
SGPropertyNode* beacons[4] = {nullptr, _innerBlinkNode.get(), _middleBlinkNode.get(), _outerBlinkNode.get()};
for (int b = INNER; b <= OUTER; b++) {
const bool bOn = on && (_lastBeacon == b);
lazyChangeBoolProp(beacons[b], bOn);
}
}
void FGMarkerBeacon::updateAudio()
{
_audioPropertiesChanged = false;
if (!_audioSampleGroup)
return;
float volume = audio_vol->getFloatValue();
if (!audio_btn->getBoolValue()) {
// mute rather than stop, so we don't lose sync with the visual blink
volume = 0.0;
}
SGSoundSample* mkr = _audioSampleGroup->find(sampleNameForBeacon(_lastBeacon));
if (mkr) {
mkr->set_volume(volume);
}
}
static bool check_beacon_range( const SGGeod& pos,
FGPositioned *b )
{
double d = distSqr(b->cart(), SGVec3d::fromGeod(pos));
// cout << " distance = " << d << " ("
// << FG_ILS_DEFAULT_RANGE * SG_NM_TO_METER
// * FG_ILS_DEFAULT_RANGE * SG_NM_TO_METER
// << ")" << endl;
//std::cout << " range = " << sqrt(d) << std::endl;
// cout << "elev = " << elev * SG_METER_TO_FEET
// << " current->get_elev() = " << current->get_elev() << endl;
double elev_ft = pos.getElevationFt();
double delev = elev_ft - b->elevation();
// max range is the area under r = 2.4 * alt or r^2 = 4000^2 - alt^2
// whichever is smaller. The intersection point is 1538 ...
double maxrange2; // feet^2
if ( delev < 1538.0 ) {
maxrange2 = 2.4 * 2.4 * delev * delev;
} else if ( delev < 4000.0 ) {
maxrange2 = 4000 * 4000 - delev * delev;
} else {
maxrange2 = 0.0;
}
maxrange2 *= SG_FEET_TO_METER * SG_FEET_TO_METER; // convert to meter^2
//std::cout << "delev = " << delev << " maxrange = " << sqrt(maxrange2) << std::endl;
// match up to twice the published range so we can model
// reduced signal strength
if ( d < maxrange2 ) {
return true;
} else {
return false;
}
}
class BeaconFilter : public FGPositioned::Filter
{
public:
FGPositioned::Type minType() const override
{
return FGPositioned::OM;
}
FGPositioned::Type maxType() const override
{
return FGPositioned::IM;
}
};
// Update current nav/adf radio stations based on current postition
void FGMarkerBeacon::search()
{
// reset search time
_time_before_search_sec = 0.5;
const SGGeod pos = globals->get_aircraft_position();
if (!pos.isValid()) {
// avoid error flood when core positions goes wrong
return;
}
// get closest marker beacon - within a 1nm cutoff
BeaconFilter filter;
FGPositionedRef b = FGPositioned::findClosest(pos, 1.0, &filter);
fgMkrBeacType beacon_type = NOBEACON;
bool inrange = false;
if ( b != NULL ) {
if ( b->type() == FGPositioned::OM ) {
beacon_type = OUTER;
} else if ( b->type() == FGPositioned::MM ) {
beacon_type = MIDDLE;
} else if ( b->type() == FGPositioned::IM ) {
beacon_type = INNER;
}
inrange = check_beacon_range( pos, b.ptr() );
}
if ( b == NULL || !inrange || !isServiceableAndPowered())
{
beacon_type = NOBEACON;
}
changeBeaconType(beacon_type);
}
void FGMarkerBeacon::changeBeaconType(fgMkrBeacType newType)
{
if (newType == _lastBeacon)
return;
_lastBeacon = newType;
stopAudio(); // stop any existing playback
if (newType == NOBEACON) {
updateOutputProperties(false);
return;
}
if (_blinkMode == BlinkMode::Standard) {
// get correct timings from the sounds generator
switch (newType) {
case INNER:
_beaconTiming = FGBeacon::instance()->getTimingForInner();
break;
case MIDDLE:
_beaconTiming = FGBeacon::instance()->getTimingForMiddle();
break;
case OUTER:
_beaconTiming = FGBeacon::instance()->getTimingForOuter();
break;
default:
break;
}
} else if (_blinkMode == BlinkMode::BackwardsCompatible) {
// older FG versions used same timing for alll beacon types :(
_beaconTiming = FGBeacon::BeaconTiming{};
_beaconTiming.durationUSec = 500000;
_beaconTiming.periodsUSec[0] = 400000;
_beaconTiming.periodsUSec[1] = 100000;
}
if (_audioSampleGroup) {
// create sample as required
const auto name = sampleNameForBeacon(newType);
if (!_audioSampleGroup->exists(name)) {
SGSoundSample* sound = createSampleForBeacon(newType);
if (sound) {
_audioSampleGroup->add(sound, name);
}
}
_audioSampleGroup->play_looped(name);
updateAudio(); // sync volume+mute now
}
// we use this timing for visuals as well, so do this even if we have
// no audio sample group
_audioStartTime.stamp();
}
void FGMarkerBeacon::stopAudio()
{
if (_audioSampleGroup) {
_audioSampleGroup->stop("outer-marker");
_audioSampleGroup->stop("middle-marker");
_audioSampleGroup->stop("inner-marker");
}
}
void FGMarkerBeacon::valueChanged(SGPropertyNode* val)
{
_audioPropertiesChanged = true;
}
// Register the subsystem.
#if 0
SGSubsystemMgr::InstancedRegistrant<FGMarkerBeacon> registrantFGMarkerBeacon(
SGSubsystemMgr::FDM,
{{"instrumentation", SGSubsystemMgr::Dependency::HARD}},
0.2);
#endif

View File

@@ -0,0 +1,101 @@
// marker_beacon.hxx -- class to manage the marker beacons
//
// Written by Curtis Olson, started April 2000.
//
// Copyright (C) 2000 Curtis L. Olson - http://www.flightgear.org/~curt
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation; either version 2 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
//
// $Id$
#ifndef _FG_MARKER_BEACON_HXX
#define _FG_MARKER_BEACON_HXX
#include <simgear/compiler.h>
#include <Instrumentation/AbstractInstrument.hxx>
#include <Sound/beacon.hxx>
#include <simgear/timing/timestamp.hxx>
class SGSampleGroup;
class FGMarkerBeacon : public AbstractInstrument,
public SGPropertyChangeListener
{
public:
enum fgMkrBeacType {
NOBEACON = 0,
INNER,
MIDDLE,
OUTER
};
FGMarkerBeacon(SGPropertyNode *node);
~FGMarkerBeacon();
// Subsystem API.
void bind() override;
void init() override;
void reinit() override;
void unbind() override;
void update(double dt) override;
// Subsystem identification.
static const char* staticSubsystemClassId() { return "marker-beacon"; }
void search ();
void valueChanged(SGPropertyNode* val) override;
private:
// Inputs
SGPropertyNode_ptr audio_btn;
SGPropertyNode_ptr audio_vol;
SGPropertyNode_ptr sound_working;
SGPropertyNode_ptr _innerBlinkNode;
SGPropertyNode_ptr _middleBlinkNode;
SGPropertyNode_ptr _outerBlinkNode;
bool _audioPropertiesChanged = true;
// internal periodic station search timer
double _time_before_search_sec = 0.0;
SGTimeStamp _audioStartTime;
SGSharedPtr<SGSampleGroup> _audioSampleGroup;
enum class BlinkMode {
BackwardsCompatible, ///< all beacons use the OM blink rate
Standard, ///< beacones use the correct blink for their type
Continuous ///< blink disabled, so aircraft can do its own blink
};
BlinkMode _blinkMode = BlinkMode::BackwardsCompatible;
void changeBeaconType(fgMkrBeacType newType);
void updateAudio();
void stopAudio();
void updateOutputProperties(bool on);
fgMkrBeacType _lastBeacon;
FGBeacon::BeaconTiming _beaconTiming;
};
#endif // _FG_MARKER_BEACON_HXX

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

307
src/Instrumentation/mrg.cxx Normal file
View File

@@ -0,0 +1,307 @@
// MRG.cxx - an electrically powered master reference gyro.
// Written by Vivian Meazza based on work by David Megginson, started 2006.
//
// This file is in the Public Domain and comes with no warranty.
// TODO:
// - better spin-up
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
#include <simgear/compiler.h>
#include <simgear/sg_inlines.h>
#include <simgear/math/SGMath.hxx>
#include <iostream>
#include <string>
#include <sstream>
#include <cmath> // fabs()
#include <Main/fg_props.hxx>
#include <Main/util.hxx>
#include "mrg.hxx"
const double MasterReferenceGyro::gravity = -32.1740485564;
using std::string;
MasterReferenceGyro::MasterReferenceGyro ( SGPropertyNode *node ) :
_name(node->getStringValue("name", "master-reference-gyro")),
_num(node->getIntValue("number", 0))
{
}
MasterReferenceGyro::~MasterReferenceGyro ()
{
}
void
MasterReferenceGyro::init ()
{
string branch;
branch = "/instrumentation/" + _name;
_pitch_in_node = fgGetNode("/orientation/pitch-deg", true);
_roll_in_node = fgGetNode("/orientation/roll-deg", true);
_hdg_in_node = fgGetNode("/orientation/heading-deg", true);
_hdg_mag_in_node = fgGetNode("/orientation/heading-magnetic-deg", true);
_pitch_rate_node = fgGetNode("/orientation/pitch-rate-degps", true);
_roll_rate_node = fgGetNode("/orientation/roll-rate-degps", true);
_yaw_rate_node = fgGetNode("/orientation/yaw-rate-degps", true);
//_g_in_node = fgGetNode("/accelerations/pilot/z-accel-fps_sec", true);
_g_in_node = fgGetNode("/accelerations/pilot-g", true);
_electrical_node = fgGetNode("/systems/electrical/outputs/MRG", true);
_hdg_mag_in_node = fgGetNode("/orientation/heading-magnetic-deg", true);
SGPropertyNode *node = fgGetNode(branch.c_str(), _num, true );
_off_node = node->getChild("off-flag", 0, true);
_pitch_out_node = node->getChild("indicated-pitch-deg", 0, true);
_roll_out_node = node->getChild("indicated-roll-deg", 0, true);
_hdg_out_node = node->getChild("indicated-hdg-deg", 0, true);
_hdg_mag_out_node = node->getChild("indicated-mag-hdg-deg", 0, true);
_pitch_rate_out_node = node->getChild("indicated-pitch-rate-degps", 0, true);
_roll_rate_out_node = node->getChild("indicated-roll-rate-degps", 0, true);
_hdg_rate_out_node = node->getChild("indicated-hdg-rate-degps", 0, true);
_responsiveness_node = node->getChild("responsiveness", 0, true);
_error_out_node = node->getChild("heading-bug-error-deg", 0, true);
_hdg_input_source_node = node->getChild("heading-source", 0, true);
_fast_erect_node = node->getChild("fast-erect", 0, true);
reinit();
}
void
MasterReferenceGyro::reinit ()
{
_last_hdg = 0;
_last_roll = 0;
_last_pitch = 0;
_indicated_hdg = 0;
_indicated_roll = 0;
_indicated_pitch = 0;
_indicated_hdg_rate = 0;
_indicated_roll_rate = 0;
_indicated_pitch_rate = 0;
_erect_time = 180;
_last_g = 1;
_g_error = 10;
_electrical_node->setDoubleValue(0);
_responsiveness_node->setDoubleValue(0.75);
_off_node->setBoolValue(false);
_hdg_input_source_node->setBoolValue(false);
_fast_erect_node->setBoolValue(false);
_g_in_node->setDoubleValue(1);
_gyro.reinit();
}
void
MasterReferenceGyro::bind ()
{
std::ostringstream temp;
string branch;
temp << _num;
branch = "/instrumentation/" + _name + "[" + temp.str() + "]";
fgTie((branch + "/serviceable").c_str(),
&_gyro, &Gyro::is_serviceable, &Gyro::set_serviceable);
fgTie((branch + "/spin").c_str(),
&_gyro, &Gyro::get_spin_norm, &Gyro::set_spin_norm);
}
void
MasterReferenceGyro::unbind ()
{
std::ostringstream temp;
string branch;
temp << _num;
branch = "/instrumentation/" + _name + "[" + temp.str() + "]";
fgUntie((branch + "/serviceable").c_str());
fgUntie((branch + "/spin").c_str());
}
void
MasterReferenceGyro::update (double dt)
{
//sanity check
if (!fgGetBool("/sim/fdm-initialized", false)) {
return;
}
double indicated_roll = 0;
double indicated_pitch = 0;
double indicated_hdg = 0;
double indicated_roll_rate = 0;
double indicated_pitch_rate = 0;
double indicated_hdg_rate = 0;
double hdg = 0;
double erect_time_factor = 1;
const double erect_time = 180;
const double max_g_error = 10.0;
//Get the spin from the gyro
_gyro.set_power_norm( _electrical_node->getDoubleValue()/24 );
_gyro.update(dt);
double spin = _gyro.get_spin_norm();
// set the "off-flag"
if ( _electrical_node->getDoubleValue() > 0 && spin >= 0.25) {
_off_node->setBoolValue(false);
} else {
_off_node->setBoolValue(true);
return;
}
// Get the input values
if(_hdg_input_source_node->getBoolValue()){
hdg = _hdg_in_node->getDoubleValue();
} else {
hdg = _hdg_mag_in_node->getDoubleValue();
}
double roll = _roll_in_node->getDoubleValue();
double pitch = _pitch_in_node->getDoubleValue();
double g = _g_in_node->getDoubleValue()/* / gravity*/;
double roll_rate = _yaw_rate_node->getDoubleValue();
double pitch_rate = _pitch_rate_node->getDoubleValue();
double yaw_rate = _yaw_rate_node->getDoubleValue();
//modulate the input by the spin rate
double responsiveness = spin * spin * spin * spin * spin * spin;
roll = fgGetLowPass( _last_roll, roll, responsiveness );
pitch = fgGetLowPass( _last_pitch , pitch, responsiveness );
if ((hdg - _last_hdg) > 180)
_last_hdg += 360;
if ((hdg - _last_hdg) < -180)
_last_hdg -= 360;
hdg = fgGetLowPass( _last_hdg , hdg, responsiveness );
//but we need to filter the hdg and yaw_rate as well - yuk!
responsiveness = 0.1 / (spin * spin * spin * spin * spin * spin);
yaw_rate = fgGetLowPass( _last_yaw_rate , yaw_rate, responsiveness );
g = fgGetLowPass( _last_g , g, 1.5);
// store the new values
_last_roll = roll;
_last_pitch = pitch;
_last_hdg = hdg;
_last_roll_rate = roll_rate;
_last_pitch_rate = pitch_rate;
_last_yaw_rate = yaw_rate;
_last_g = g;
//the gyro only erects inside limits
if ( fabs ( yaw_rate ) <= 5
&& g <= 1.5 && g >= -0.5){
if ( !_fast_erect_node->getBoolValue() ){
erect_time_factor = 1;
} else {
erect_time_factor = 2;
}
_g_error -= (max_g_error/(erect_time * 0.33)) * dt * erect_time_factor;
} else {
_g_error += (max_g_error /(erect_time * 0.33)) * dt * 2;
//SG_LOG(SG_INSTR, SG_ALERT,_num <<
// " g input " << _g_in_node->getDoubleValue() * gravity
// <<" _erect_time " << _erect_time
// << " yaw " << yaw_rate
// << " pitch " << _pitch_rate_node->getDoubleValue()
// << " roll " << _roll_rate_node->getDoubleValue());
}
//cout << "_g_error "<< _g_error << endl;
_g_error = SGMiscd::clip(_g_error, 0, 10);
// cout << "_g_error clip "<< _g_error << endl;
indicated_roll = _last_roll + _g_error;
indicated_pitch = _last_pitch + _g_error;
indicated_hdg = _last_hdg + _g_error;
indicated_roll_rate = _last_roll_rate;
indicated_pitch_rate = _last_pitch_rate;
indicated_hdg_rate = _last_yaw_rate;
// calculate the difference between the indicated heading
// and the selected heading for use with an autopilot
SGPropertyNode *bnode
= fgGetNode( "/autopilot/settings/heading-bug-deg", false );
if ( bnode ) {
double diff = bnode->getDoubleValue() - indicated_hdg;
if ( diff < -180.0 ) { diff += 360.0; }
if ( diff > 180.0 ) { diff -= 360.0; }
_error_out_node->setDoubleValue( diff );
//SG_LOG(SG_INSTR, SG_ALERT,
//"autopilot input " << bnode->getDoubleValue()
//<< " output " << _error_out_node->getDoubleValue()<<);
}
//smooth the output
double factor = _responsiveness_node->getDoubleValue() * dt;
indicated_roll = fgGetLowPass( _indicated_roll, indicated_roll, factor );
indicated_pitch = fgGetLowPass( _indicated_pitch , indicated_pitch, factor );
//indicated_hdg = fgGetLowPass( _indicated_hdg , indicated_hdg, factor );
indicated_roll_rate = fgGetLowPass( _indicated_roll_rate, indicated_roll_rate, factor );
indicated_pitch_rate = fgGetLowPass( _indicated_pitch_rate , indicated_pitch_rate, factor );
indicated_hdg_rate = fgGetLowPass( _indicated_hdg_rate , indicated_hdg_rate, factor );
// store the new values
_indicated_roll = indicated_roll;
_indicated_pitch = indicated_pitch;
_indicated_hdg = indicated_hdg;
_indicated_roll_rate = indicated_roll_rate;
_indicated_pitch_rate = indicated_pitch_rate;
_indicated_hdg_rate = indicated_hdg_rate;
// add in a gyro underspin "error" if gyro is spinning too slowly
const double spin_thresh = 0.8;
const double max_roll_error = 40.0;
const double max_pitch_error = 12.0;
const double max_hdg_error = 140.0;
double roll_error;
double pitch_error;
double hdg_error;
if ( spin <= spin_thresh ) {
double roll_error_factor = ( spin_thresh - spin ) / spin_thresh;
double pitch_error_factor = ( spin_thresh - spin ) / spin_thresh;
double hdg_error_factor = ( spin_thresh - spin ) / spin_thresh;
roll_error = roll_error_factor * roll_error_factor * max_roll_error;
pitch_error = pitch_error_factor * pitch_error_factor * max_pitch_error;
hdg_error = hdg_error_factor * hdg_error_factor * max_hdg_error;
} else {
roll_error = 0.0;
pitch_error = 0.0;
hdg_error = 0.0;
}
_roll_out_node->setDoubleValue( _indicated_roll + roll_error );
_pitch_out_node->setDoubleValue( _indicated_pitch + pitch_error );
_hdg_out_node->setDoubleValue( _indicated_hdg + hdg_error );
_pitch_rate_out_node ->setDoubleValue( _indicated_pitch_rate );
_roll_rate_out_node ->setDoubleValue( _indicated_roll_rate );
_hdg_rate_out_node ->setDoubleValue( _indicated_hdg_rate );
}
// Register the subsystem.
#if 0
SGSubsystemMgr::InstancedRegistrant<MasterReferenceGyro> registrantMasterReferenceGyro(
SGSubsystemMgr::FDM,
{{"instrumentation", SGSubsystemMgr::Dependency::HARD}});
#endif
// end of mrg.cxx

106
src/Instrumentation/mrg.hxx Normal file
View File

@@ -0,0 +1,106 @@
// attitude_indicator.hxx - a vacuum-powered attitude indicator.
// Written by David Megginson, started 2002.
//
// This file is in the Public Domain and comes with no warranty.
#ifndef __INSTRUMENTS_MRG_HXX
#define __INSTRUMENTS_MRG_HXX 1
#include <simgear/props/props.hxx>
#include <simgear/structure/subsystem_mgr.hxx>
#include "gyro.hxx"
/**
* Model an electrically-powered master reference gyro.
*
* Input properties:
*
* /instrumentation/"name"/config/tumble-flag
* /instrumentation/"name"/serviceable
* /instrumentation/"name"/caged-flag
* /instrumentation/"name"/tumble-norm
* /orientation/pitch-deg
* /orientation/roll-deg
*
* Output properties:
*
* /instrumentation/"name"/indicated-pitch-deg
* /instrumentation/"name"/indicated-roll-deg
* /instrumentation/"name"/tumble-norm
*/
class MasterReferenceGyro : public SGSubsystem
{
public:
MasterReferenceGyro ( SGPropertyNode *node );
MasterReferenceGyro ();
virtual ~MasterReferenceGyro ();
// Subsystem API.
void bind() override;
void init() override;
void reinit() override;
void unbind() override;
void update(double dt) override;
// Subsystem identification.
static const char* staticSubsystemClassId() { return "master-reference-gyro"; }
private:
static const double gravity; //conversion factor
std::string _name;
int _num;
double _last_roll;
double _last_pitch;
double _last_hdg;
double _indicated_roll;
double _indicated_pitch;
double _indicated_hdg;
double _indicated_pitch_rate;
double _indicated_roll_rate;
double _indicated_hdg_rate;
double _last_roll_rate;
double _last_pitch_rate;
double _last_yaw_rate;
double _last_g;
double _erect_time;
double _g_error;
Gyro _gyro;
SGPropertyNode_ptr _tumble_flag_node;
SGPropertyNode_ptr _caged_node;
SGPropertyNode_ptr _off_node;
SGPropertyNode_ptr _tumble_node;
SGPropertyNode_ptr _pitch_in_node;
SGPropertyNode_ptr _roll_in_node;
SGPropertyNode_ptr _hdg_in_node;
SGPropertyNode_ptr _hdg_mag_in_node;
SGPropertyNode_ptr _g_in_node;
SGPropertyNode_ptr _electrical_node;
SGPropertyNode_ptr _pitch_int_node;
SGPropertyNode_ptr _roll_int_node;
SGPropertyNode_ptr _hdg_int_node;
SGPropertyNode_ptr _pitch_out_node;
SGPropertyNode_ptr _roll_out_node;
SGPropertyNode_ptr _hdg_out_node;
SGPropertyNode_ptr _hdg_mag_out_node;
SGPropertyNode_ptr _pitch_rate_out_node;
SGPropertyNode_ptr _roll_rate_out_node;
SGPropertyNode_ptr _hdg_rate_out_node;
SGPropertyNode_ptr _error_out_node;
SGPropertyNode_ptr _yaw_rate_node;
SGPropertyNode_ptr _roll_rate_node;
SGPropertyNode_ptr _pitch_rate_node;
SGPropertyNode_ptr _responsiveness_node;
SGPropertyNode_ptr _hdg_input_source_node;
SGPropertyNode_ptr _fast_erect_node;
};
#endif // __INSTRUMENTS_MRG_HXX

View File

@@ -0,0 +1,977 @@
// navradio.cxx -- class to manage a nav radio instance
//
// Written by Curtis Olson, started April 2000.
//
// Copyright (C) 2000 - 2002 Curtis L. Olson - http://www.flightgear.org/~curt
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation; either version 2 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
//
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif
#include <sstream>
#include <cstring>
#include <cstdio>
#include <simgear/sg_inlines.h>
#include <simgear/timing/sg_time.hxx>
#include <simgear/math/sg_random.hxx>
#include <simgear/misc/sg_path.hxx>
#include <simgear/math/sg_geodesy.hxx>
#include <simgear/structure/exception.hxx>
#include <simgear/math/interpolater.hxx>
#include <simgear/misc/strutils.hxx>
#include <simgear/sound/sample_group.hxx>
#include <Navaids/navrecord.hxx>
#include <Sound/audioident.hxx>
#include <Airports/runways.hxx>
#include <Navaids/navlist.hxx>
#include <Main/util.hxx>
#include "navradio.hxx"
using std::string;
// General-purpose sawtooth function. Graph looks like this:
// /\ .
// \/
// Odd symmetry, inversion symmetry about the origin.
// Unit slope at the origin.
// Max 1, min -1, period 4.
// Two zero-crossings per period, one with + slope, one with - slope.
// Useful for false localizer courses.
static double sawtooth(double xx)
{
return 4.0 * fabs(xx/4.0 + 0.25 - floor(xx/4.0 + 0.75)) - 1.0;
}
// Calculate a Cartesian unit vector in the
// local horizontal plane, i.e. tangent to the
// surface of the earth at the local ground zero.
// The tangent vector passes through the given <midpoint>
// and points forward along the given <heading>.
// The <heading> is given in degrees.
static SGVec3d tangentVector(const SGGeod& midpoint, const double heading)
{
// The size of the delta is presumably chosen to give
// numerical stability. I don't know how the value was chosen.
// It probably doesn't matter much. It gets divided out.
double delta(100.0); // in meters
SGGeod head, tail;
double az2; // ignored
SGGeodesy::direct(midpoint, heading, delta, head, az2);
SGGeodesy::direct(midpoint, 180+heading, delta, tail, az2);
head.setElevationM(midpoint.getElevationM());
tail.setElevationM(midpoint.getElevationM());
SGVec3d head_xyz = SGVec3d::fromGeod(head);
SGVec3d tail_xyz = SGVec3d::fromGeod(tail);
// Awkward formula here, needed because vector-by-scalar
// multiplication is defined, but not vector-by-scalar division.
return (head_xyz - tail_xyz) * (0.5/delta);
}
// Create a "serviceable" node with a default value of "true"
SGPropertyNode_ptr createServiceableProp(SGPropertyNode* aParent,
const char* aName)
{
SGPropertyNode_ptr n =
aParent->getChild(aName, 0, true)->getChild("serviceable", 0, true);
simgear::props::Type typ = n->getType();
if ((typ == simgear::props::NONE) || (typ == simgear::props::UNSPECIFIED)) {
n->setBoolValue(true);
}
return n;
}
static std::unique_ptr<SGInterpTable> static_terminalRangeInterp,
static_lowRangeInterp, static_highRangeInterp;
// Constructor
FGNavRadio::FGNavRadio(SGPropertyNode *node) :
play_count(0),
_nav_search(true),
_last_freq(0.0),
target_radial(0.0),
effective_range(0.0),
target_gs(0.0),
twist(0.0),
horiz_vel(0.0),
last_x(0.0),
last_xtrack_error(0.0),
xrate_ms(0.0),
_localizerWidth(5.0),
_time_before_search_sec(-1.0),
_gsCart(SGVec3d::zeros()),
_gsAxis(SGVec3d::zeros()),
_gsVertical(SGVec3d::zeros()),
_toFlag(false),
_fromFlag(false),
_cdiDeflection(0.0),
_cdiCrossTrackErrorM(0.0),
_gsNeedleDeflection(0.0),
_gsNeedleDeflectionNorm(0.0),
_audioIdent(NULL)
{
readConfig(node, "nav");
if (!static_terminalRangeInterp.get()) {
// one-time interpolator init
SGPath path( globals->get_fg_root() );
SGPath term = path;
term.append( "Navaids/range.term" );
SGPath low = path;
low.append( "Navaids/range.low" );
SGPath high = path;
high.append( "Navaids/range.high" );
static_terminalRangeInterp.reset(new SGInterpTable(term));
static_lowRangeInterp.reset(new SGInterpTable(low));
static_highRangeInterp.reset(new SGInterpTable(high));
}
string branch = nodePath();
_radio_node = fgGetNode(branch.c_str(), true);
}
// Destructor
FGNavRadio::~FGNavRadio()
{
if (gps_course_node) {
gps_course_node->removeChangeListener(this);
}
if (nav_slaved_to_gps_node) {
nav_slaved_to_gps_node->removeChangeListener(this);
}
delete _audioIdent;
}
void
FGNavRadio::init ()
{
SGPropertyNode* node = _radio_node.get();
initServicePowerProperties(node);
// inputs
is_valid_node = node->getChild("data-is-valid", 0, true);
vol_btn_node = node->getChild("volume", 0, true);
ident_btn_node = node->getChild("ident", 0, true);
ident_btn_node->setBoolValue( true );
audio_btn_node = node->getChild("audio-btn", 0, true);
audio_btn_node->setBoolValue( true );
backcourse_node = node->getChild("back-course-btn", 0, true);
backcourse_node->setBoolValue( false );
nav_serviceable_node = node->getChild("serviceable", 0, true);
cdi_serviceable_node = createServiceableProp(node, "cdi");
gs_serviceable_node = createServiceableProp(node, "gs");
tofrom_serviceable_node = createServiceableProp(node, "to-from");
falseCoursesEnabledNode =
fgGetNode("/sim/realism/false-radio-courses-enabled");
if (!falseCoursesEnabledNode) {
falseCoursesEnabledNode =
fgGetNode("/sim/realism/false-radio-courses-enabled", true);
falseCoursesEnabledNode->setBoolValue(true);
}
// frequencies
SGPropertyNode *subnode = node->getChild("frequencies", 0, true);
freq_node = subnode->getChild("selected-mhz", 0, true);
alt_freq_node = subnode->getChild("standby-mhz", 0, true);
freq_node->addChangeListener(this);
alt_freq_node->addChangeListener(this);
fmt_freq_node = subnode->getChild("selected-mhz-fmt", 0, true);
fmt_alt_freq_node = subnode->getChild("standby-mhz-fmt", 0, true);
is_loc_freq_node = subnode->getChild("is-localizer-frequency", 0, true );
// radials
subnode = node->getChild("radials", 0, true);
sel_radial_node = subnode->getChild("selected-deg", 0, true);
radial_node = subnode->getChild("actual-deg", 0, true);
recip_radial_node = subnode->getChild("reciprocal-radial-deg", 0, true);
target_radial_true_node = subnode->getChild("target-radial-deg", 0, true);
target_auto_hdg_node = subnode->getChild("target-auto-hdg-deg", 0, true);
// outputs
heading_node = node->getChild("heading-deg", 0, true);
time_to_intercept = node->getChild("time-to-intercept-sec", 0, true);
to_flag_node = node->getChild("to-flag", 0, true);
from_flag_node = node->getChild("from-flag", 0, true);
inrange_node = node->getChild("in-range", 0, true);
signal_quality_norm_node = node->getChild("signal-quality-norm", 0, true);
cdi_deflection_node = node->getChild("heading-needle-deflection", 0, true);
cdi_deflection_norm_node = node->getChild("heading-needle-deflection-norm", 0, true);
cdi_xtrack_error_node = node->getChild("crosstrack-error-m", 0, true);
cdi_xtrack_hdg_err_node
= node->getChild("crosstrack-heading-error-deg", 0, true);
has_gs_node = node->getChild("has-gs", 0, true);
loc_node = node->getChild("nav-loc", 0, true);
loc_dist_node = node->getChild("nav-distance", 0, true);
gs_deflection_node = node->getChild("gs-needle-deflection", 0, true);
gs_deflection_deg_node = node->getChild("gs-needle-deflection-deg", 0, true);
gs_deflection_norm_node = node->getChild("gs-needle-deflection-norm", 0, true);
gs_direct_node = node->getChild("gs-direct-deg", 0, true);
gs_rate_of_climb_node = node->getChild("gs-rate-of-climb", 0, true);
gs_rate_of_climb_fpm_node = node->getChild("gs-rate-of-climb-fpm", 0, true);
gs_dist_node = node->getChild("gs-distance", 0, true);
gs_inrange_node = node->getChild("gs-in-range", 0, true);
nav_id_node = node->getChild("nav-id", 0, true);
id_c1_node = node->getChild("nav-id_asc1", 0, true);
id_c2_node = node->getChild("nav-id_asc2", 0, true);
id_c3_node = node->getChild("nav-id_asc3", 0, true);
id_c4_node = node->getChild("nav-id_asc4", 0, true);
// gps slaving support
nav_slaved_to_gps_node = node->getChild("slaved-to-gps", 0, true);
nav_slaved_to_gps_node->addChangeListener(this);
gps_cdi_deflection_node = fgGetNode("/instrumentation/gps/cdi-deflection", true);
gps_to_flag_node = fgGetNode("/instrumentation/gps/to-flag", true);
gps_from_flag_node = fgGetNode("/instrumentation/gps/from-flag", true);
gps_has_gs_node = fgGetNode("/instrumentation/gps/has-gs", true);
gps_course_node = fgGetNode("/instrumentation/gps/desired-course-deg", true);
gps_course_node->addChangeListener(this);
gps_xtrack_error_nm_node = fgGetNode("/instrumentation/gps/wp/wp[1]/course-error-nm", true);
_magvarNode = fgGetNode("/environment/magnetic-variation-deg", true);
std::ostringstream temp;
temp << name() << "-ident-" << number();
if( NULL == _audioIdent )
_audioIdent = new VORAudioIdent( temp.str() );
_audioIdent->init();
// dme-in-range is deprecated,
// temporarily create dme-in-range alias for instrumentation/dme[0]/in-range
// remove after flightgear 2.6.0
node->getNode( "dme-in-range", true )->alias( fgGetNode("/instrumentation/dme[0]/in-range", true ) );
}
void
FGNavRadio::reinit ()
{
_time_before_search_sec = -1.0;
}
// model standard VOR/DME/TACAN service volumes as per AIM 1-1-8
double FGNavRadio::adjustNavRange( double stationElev, double aircraftElev,
double nominalRange )
{
if (nominalRange <= 0.0) {
nominalRange = FG_NAV_DEFAULT_RANGE;
}
// extend out actual usable range to be 1.3x the published safe range
const double usability_factor = 1.3;
// assumptions we model the standard service volume, plus
// ... rather than specifying a cylinder, we model a cone that
// contains the cylinder. Then we put an upside down cone on top
// to model diminishing returns at too-high altitudes.
// altitude difference
double alt = ( aircraftElev * SG_METER_TO_FEET - stationElev );
// cout << "aircraft elev = " << aircraftElev * SG_METER_TO_FEET
// << " station elev = " << stationElev << endl;
if ( nominalRange < 25.0 + SG_EPSILON ) {
// Standard Terminal Service Volume
return static_terminalRangeInterp->interpolate( alt ) * usability_factor;
} else if ( nominalRange < 50.0 + SG_EPSILON ) {
// Standard Low Altitude Service Volume
// table is based on range of 40, scale to actual range
return static_lowRangeInterp->interpolate( alt ) * nominalRange / 40.0
* usability_factor;
} else {
// Standard High Altitude Service Volume
// table is based on range of 130, scale to actual range
return static_highRangeInterp->interpolate( alt ) * nominalRange / 130.0
* usability_factor;
}
}
// model standard ILS service volumes as per AIM 1-1-9
double FGNavRadio::adjustILSRange( double stationElev, double aircraftElev,
double offsetDegrees, double distance )
{
// assumptions we model the standard service volume, plus
// altitude difference
// double alt = ( aircraftElev * SG_METER_TO_FEET - stationElev );
// double offset = fabs( offsetDegrees );
// if ( offset < 10 ) {
// return FG_ILS_DEFAULT_RANGE;
// } else if ( offset < 35 ) {
// return 10 + (35 - offset) * (FG_ILS_DEFAULT_RANGE - 10) / 25;
// } else if ( offset < 45 ) {
// return (45 - offset);
// } else if ( offset > 170 ) {
// return FG_ILS_DEFAULT_RANGE;
// } else if ( offset > 145 ) {
// return 10 + (offset - 145) * (FG_ILS_DEFAULT_RANGE - 10) / 25;
// } else if ( offset > 135 ) {
// return (offset - 135);
// } else {
// return 0;
// }
return FG_LOC_DEFAULT_RANGE;
}
// Frequencies with odd 100kHz numbers in the range from 108.00 - 111.95
// are LOC/GS (ILS) frequency pairs
// (108.00, 108.05, 108.20, 108.25.. =VOR)
// (108.10, 108.15, 108.30, 108.35.. =ILS)
static inline bool IsLocalizerFrequency( double f )
{
if( f < 108.0 || f >= 112.00 ) return false;
return (((SGMiscd::roundToInt(f * 100.0) % 100)/10) % 2) != 0;
}
//////////////////////////////////////////////////////////////////////////
// Update the various nav values based on position and valid tuned in navs
//////////////////////////////////////////////////////////////////////////
void
FGNavRadio::update(double dt)
{
if (dt <= 0.0) {
return; // paused
}
if (isServiceableAndPowered())
{
updateReceiver(dt);
updateCDI(dt);
} else {
clearOutputs();
}
updateAudio( dt );
}
void FGNavRadio::updateFormattedFrequencies()
{
// Create "formatted" versions of the nav frequencies for
// instrument displays.
char tmp[16];
sprintf( tmp, "%.2f", freq_node->getDoubleValue() );
fmt_freq_node->setStringValue(tmp);
sprintf( tmp, "%.2f", alt_freq_node->getDoubleValue() );
fmt_alt_freq_node->setStringValue(tmp);
is_loc_freq_node->setBoolValue( IsLocalizerFrequency( freq_node->getDoubleValue() ));
}
void FGNavRadio::clearOutputs()
{
inrange_node->setBoolValue( false );
signal_quality_norm_node->setDoubleValue( 0.0 );
cdi_deflection_node->setDoubleValue( 0.0 );
cdi_deflection_norm_node->setDoubleValue( 0.0 );
cdi_xtrack_error_node->setDoubleValue( 0.0 );
cdi_xtrack_hdg_err_node->setDoubleValue( 0.0 );
time_to_intercept->setDoubleValue( 0.0 );
heading_node->setDoubleValue(0.0);
gs_deflection_node->setDoubleValue( 0.0 );
gs_deflection_deg_node->setDoubleValue(0.0);
gs_deflection_norm_node->setDoubleValue(0.0);
gs_direct_node->setDoubleValue(0.0);
gs_inrange_node->setBoolValue( false );
loc_node->setBoolValue( false );
has_gs_node->setBoolValue(false);
to_flag_node->setBoolValue( false );
from_flag_node->setBoolValue( false );
is_valid_node->setBoolValue(false);
nav_id_node->setStringValue("");
_navaid = NULL;
}
void FGNavRadio::updateReceiver(double dt)
{
SGVec3d aircraft = SGVec3d::fromGeod(globals->get_aircraft_position());
double loc_dist = 0;
// Do a nav station search only once a second to reduce
// unnecessary work. (Also, make sure to do this before caching
// any values!)
_time_before_search_sec -= dt;
if ( _time_before_search_sec < 0 ) {
search();
}
if (_navaid)
{
loc_dist = dist(aircraft, _navaid->cart());
loc_dist_node->setDoubleValue( loc_dist );
}
if (nav_slaved_to_gps_node->getBoolValue()) {
// when slaved to GPS: only allow stuff above: tune NAV station
// All other data driven by GPS only.
updateGPSSlaved();
return;
}
if (!_navaid) {
_cdiDeflection = 0.0;
_cdiCrossTrackErrorM = 0.0;
_toFlag = _fromFlag = false;
_gsNeedleDeflection = 0.0;
_gsNeedleDeflectionNorm = 0.0;
heading_node->setDoubleValue(0.0);
inrange_node->setBoolValue(false);
signal_quality_norm_node->setDoubleValue(0.0);
gs_dist_node->setDoubleValue( 0.0 );
gs_inrange_node->setBoolValue(false);
return;
}
double nav_elev = _navaid->get_elev_ft();
bool is_loc = loc_node->getBoolValue();
double signal_quality_norm = signal_quality_norm_node->getDoubleValue();
double az2, s;
//////////////////////////////////////////////////////////
// compute forward and reverse wgs84 headings to localizer
//////////////////////////////////////////////////////////
double hdg;
SGGeodesy::inverse(globals->get_aircraft_position(), _navaid->geod(), hdg, az2, s);
heading_node->setDoubleValue(hdg);
double radial = az2 - twist;
double recip = radial + 180.0;
SG_NORMALIZE_RANGE(recip, 0.0, 360.0);
radial_node->setDoubleValue( radial );
recip_radial_node->setDoubleValue( recip );
//////////////////////////////////////////////////////////
// compute the target/selected radial in "true" heading
//////////////////////////////////////////////////////////
if (!is_loc) {
target_radial = sel_radial_node->getDoubleValue();
}
// VORs need twist (mag-var) added; ILS/LOCs don't but we set twist to 0.0
double trtrue = target_radial + twist;
SG_NORMALIZE_RANGE(trtrue, 0.0, 360.0);
target_radial_true_node->setDoubleValue( trtrue );
//////////////////////////////////////////////////////////
// adjust reception range for altitude
// FIXME: make sure we are using the navdata range now that
// it is valid in the data file
//////////////////////////////////////////////////////////
if ( is_loc ) {
double offset = radial - target_radial;
SG_NORMALIZE_RANGE(offset, -180.0, 180.0);
effective_range
= adjustILSRange( nav_elev, globals->get_aircraft_position().getElevationM(), offset,
loc_dist * SG_METER_TO_NM );
} else {
effective_range
= adjustNavRange( nav_elev, globals->get_aircraft_position().getElevationM(), _navaid->get_range() );
}
double effective_range_m = effective_range * SG_NM_TO_METER;
//////////////////////////////////////////////////////////
// compute signal quality
// 100% within effective_range
// decreases 1/x^2 further out
//////////////////////////////////////////////////////////
double last_signal_quality_norm = signal_quality_norm;
if ( loc_dist < effective_range_m ) {
signal_quality_norm = 1.0;
} else {
double range_exceed_norm = loc_dist/effective_range_m;
signal_quality_norm = 1/(range_exceed_norm*range_exceed_norm);
}
signal_quality_norm = fgGetLowPass( last_signal_quality_norm,
signal_quality_norm, dt );
signal_quality_norm_node->setDoubleValue( signal_quality_norm );
bool inrange = signal_quality_norm > 0.2;
inrange_node->setBoolValue( inrange );
//////////////////////////////////////////////////////////
// compute to/from flag status
//////////////////////////////////////////////////////////
if (inrange) {
if (is_loc) {
_toFlag = true;
} else {
double offset = fabs(radial - target_radial);
_toFlag = (offset > 90.0 && offset < 270.0);
}
_fromFlag = !_toFlag;
} else {
_toFlag = _fromFlag = false;
}
// CDI deflection
double r = target_radial - radial;
SG_NORMALIZE_RANGE(r, -180.0, 180.0);
if ( is_loc ) {
if (falseCoursesEnabledNode->getBoolValue()) {
// The factor of 30.0 gives a period of 120 which gives us 3 cycles and six
// zeros i.e. six courses: one front course, one back course, and four
// false courses. Three of the six are reverse sensing.
_cdiDeflection = 30.0 * sawtooth(r / 30.0);
} else {
// no false courses, but we do need to create a back course
if (fabs(r) > 90.0) { // front course
_cdiDeflection = r - copysign(180.0, r);
} else {
_cdiDeflection = r; // back course
}
_cdiDeflection = -_cdiDeflection; // reverse for outbound radial
} // of false courses disabled
const double VOR_FULL_ARC = 20.0; // VOR is -10 .. 10 degree swing
_cdiDeflection *= VOR_FULL_ARC / _localizerWidth; // increased localizer sensitivity
if (backcourse_node->getBoolValue()) {
_cdiDeflection = -_cdiDeflection;
}
} else {
// handle the TO side of the VOR
if (fabs(r) > 90.0) {
r = ( r<0.0 ? -r-180.0 : -r+180.0 );
}
_cdiDeflection = r;
} // of non-localizer case
SG_CLAMP_RANGE(_cdiDeflection, -10.0, 10.0 );
_cdiDeflection *= signal_quality_norm;
// cross-track error (in meters)
_cdiCrossTrackErrorM = loc_dist * sin(r * SGD_DEGREES_TO_RADIANS);
updateGlideSlope(dt, aircraft, signal_quality_norm);
}
void FGNavRadio::updateGlideSlope(double dt, const SGVec3d& aircraft, double signal_quality_norm)
{
bool gsInRange = (_gs && inrange_node->getBoolValue());
double gsDist = 0;
if (gsInRange)
{
gsDist = dist(aircraft, _gsCart);
gsInRange = (gsDist < (_gs->get_range() * SG_NM_TO_METER));
}
gs_inrange_node->setBoolValue(gsInRange);
gs_dist_node->setDoubleValue( gsDist );
if (!gsInRange)
{
_gsNeedleDeflection = 0.0;
_gsNeedleDeflectionNorm = 0.0;
return;
}
SGVec3d pos = aircraft - _gsCart; // relative vector from gs antenna to aircraft
// The positive GS axis points along the runway in the landing direction,
// toward the far end, not toward the approach area, so we need a - sign here:
double comp_h = -dot(pos, _gsAxis); // component in horiz direction
double comp_v = dot(pos, _gsVertical); // component in vertical direction
//double comp_b = dot(pos, _gsBaseline); // component in baseline direction
//if (comp_b) {} // ... (useful for debugging)
// _gsDirect represents the angle of elevation of the aircraft
// as seen by the GS transmitter.
_gsDirect = atan2(comp_v, comp_h) * SGD_RADIANS_TO_DEGREES;
// At this point, if the aircraft is centered on the glide slope,
// _gsDirect will be a small positive number, e.g. 3.0 degrees
// Aim the branch cut straight down
// into the ground below the GS transmitter:
if (_gsDirect < -90.0) _gsDirect += 360.0;
double deflectionAngle = target_gs - _gsDirect;
if (falseCoursesEnabledNode->getBoolValue()) {
// Construct false glideslopes. The scale factor of 1.5
// in the sawtooth gives a period of 6 degrees.
// There will be zeros at 3, 6r, 9, 12r et cetera
// where "r" indicates reverse sensing.
// This is is consistent with conventional pilot lore
// e.g. http://www.allstar.fiu.edu/aerojava/ILS.htm
// but inconsistent with
// http://www.freepatentsonline.com/3757338.html
//
// It may be that some of each exist.
if (deflectionAngle < 0) {
deflectionAngle = 1.5 * sawtooth(deflectionAngle / 1.5);
} else {
// no false GS below the true GS
}
}
// GS is documented to be 1.4 degrees thick,
// i.e. plus or minus 0.7 degrees from the midline:
SG_CLAMP_RANGE(deflectionAngle, -0.7, 0.7);
// Many older instrument xml frontends depend on
// the un-normalized gs-needle-deflection.
// Apparently the interface standard is plus or minus 3.5 "volts"
// for a full-scale deflection:
_gsNeedleDeflection = deflectionAngle * 5.0;
_gsNeedleDeflection *= signal_quality_norm;
_gsNeedleDeflectionNorm = (deflectionAngle / 0.7) * signal_quality_norm;
//////////////////////////////////////////////////////////
// Calculate desired rate of climb for intercepting the GS
//////////////////////////////////////////////////////////
double gs_diff = target_gs - _gsDirect;
// convert desired vertical path angle into a climb rate
double des_angle = _gsDirect - 10 * gs_diff;
/* printf("target_gs=%.1f angle=%.1f gs_diff=%.1f des_angle=%.1f\n",
target_gs, _gsDirect, gs_diff, des_angle); */
// estimate horizontal speed towards ILS in meters per minute
double elapsedDistance = last_x - gsDist;
last_x = gsDist;
double new_vel = ( elapsedDistance / dt );
horiz_vel = 0.99 * horiz_vel + 0.01 * new_vel;
/* printf("vel=%.1f (dist=%.1f dt=%.2f)\n", horiz_vel, elapsedDistance, dt);*/
gs_rate_of_climb_node
->setDoubleValue( -sin( des_angle * SGD_DEGREES_TO_RADIANS )
* horiz_vel * SG_METER_TO_FEET );
gs_rate_of_climb_fpm_node
->setDoubleValue( gs_rate_of_climb_node->getDoubleValue() * 60 );
}
void FGNavRadio::valueChanged (SGPropertyNode* prop)
{
if (prop == gps_course_node) {
if (!nav_slaved_to_gps_node->getBoolValue()) {
return;
}
// GPS desired course has changed, sync up our selected-course
double v = prop->getDoubleValue();
if (v != sel_radial_node->getDoubleValue()) {
sel_radial_node->setDoubleValue(v);
}
} else if (prop == nav_slaved_to_gps_node) {
if (prop->getBoolValue()) {
// slaved-to-GPS activated, clear obsolete NAV outputs and sync up selected course
clearOutputs();
sel_radial_node->setDoubleValue(gps_course_node->getDoubleValue());
}
// slave-to-GPS enabled/disabled, resync NAV station (update all outputs)
_navaid = NULL;
_time_before_search_sec = 0;
} else if ((prop == freq_node) || (prop == alt_freq_node)) {
updateFormattedFrequencies();
// force a frequency update
_time_before_search_sec = 0.0;
}
}
void FGNavRadio::updateGPSSlaved()
{
has_gs_node->setBoolValue(gps_has_gs_node->getBoolValue());
_toFlag = gps_to_flag_node->getBoolValue();
_fromFlag = gps_from_flag_node->getBoolValue();
bool gpsValid = (_toFlag | _fromFlag);
inrange_node->setBoolValue(gpsValid);
if (!gpsValid) {
signal_quality_norm_node->setDoubleValue(0.0);
_cdiDeflection = 0.0;
_cdiCrossTrackErrorM = 0.0;
_gsNeedleDeflection = 0.0;
_gsNeedleDeflectionNorm = 0.0;
return;
}
// this is unfortunate, but panel instruments use this value to decide
// if the navradio output is valid.
signal_quality_norm_node->setDoubleValue(1.0);
_cdiDeflection = gps_cdi_deflection_node->getDoubleValue();
// clmap to some range (+/- 10 degrees) as the regular deflection
SG_CLAMP_RANGE(_cdiDeflection, -10.0, 10.0 );
_cdiCrossTrackErrorM = gps_xtrack_error_nm_node->getDoubleValue() * SG_NM_TO_METER;
_gsNeedleDeflection = 0.0; // FIXME, supply this
double trtrue = gps_course_node->getDoubleValue() + _magvarNode->getDoubleValue();
SG_NORMALIZE_RANGE(trtrue, 0.0, 360.0);
target_radial_true_node->setDoubleValue( trtrue );
}
void FGNavRadio::updateCDI(double dt)
{
bool cdi_serviceable = cdi_serviceable_node->getBoolValue();
bool inrange = inrange_node->getBoolValue();
if (tofrom_serviceable_node->getBoolValue()) {
to_flag_node->setBoolValue(_toFlag);
from_flag_node->setBoolValue(_fromFlag);
} else {
to_flag_node->setBoolValue(false);
from_flag_node->setBoolValue(false);
}
if (!cdi_serviceable) {
_cdiDeflection = 0.0;
_cdiCrossTrackErrorM = 0.0;
}
cdi_deflection_node->setDoubleValue(_cdiDeflection);
cdi_deflection_norm_node->setDoubleValue(_cdiDeflection * 0.1);
cdi_xtrack_error_node->setDoubleValue(_cdiCrossTrackErrorM);
//////////////////////////////////////////////////////////
// compute an approximate ground track heading error
//////////////////////////////////////////////////////////
double hdg_error = 0.0;
if ( inrange && cdi_serviceable ) {
double vn = fgGetDouble( "/velocities/speed-north-fps" );
double ve = fgGetDouble( "/velocities/speed-east-fps" );
double gnd_trk_true = atan2( ve, vn ) * SGD_RADIANS_TO_DEGREES;
if ( gnd_trk_true < 0.0 ) { gnd_trk_true += 360.0; }
SGPropertyNode *true_hdg
= fgGetNode("/orientation/heading-deg", true);
hdg_error = gnd_trk_true - true_hdg->getDoubleValue();
// cout << "ground track = " << gnd_trk_true
// << " orientation = " << true_hdg->getDoubleValue() << endl;
}
cdi_xtrack_hdg_err_node->setDoubleValue( hdg_error );
//////////////////////////////////////////////////////////
// Calculate a suggested target heading to smoothly intercept
// a nav/ils radial.
//////////////////////////////////////////////////////////
// Now that we have cross track heading adjustment built in,
// we shouldn't need to overdrive the heading angle within 8km
// of the station.
//
// The cdi deflection should be +/-10 for a full range of deflection
// so multiplying this by 3 gives us +/- 30 degrees heading
// compensation.
double adjustment = _cdiDeflection * 3.0;
SG_CLAMP_RANGE( adjustment, -30.0, 30.0 );
// determine the target heading to fly to intercept the
// tgt_radial = target radial (true) + cdi offset adjustment -
// xtrack heading error adjustment
double nta_hdg;
double trtrue = target_radial_true_node->getDoubleValue();
if ( loc_node->getBoolValue() && backcourse_node->getBoolValue() ) {
// tuned to a localizer and backcourse mode activated
trtrue += 180.0; // reverse the target localizer heading
SG_NORMALIZE_RANGE(trtrue, 0.0, 360.0);
nta_hdg = trtrue - adjustment - hdg_error;
} else {
nta_hdg = trtrue + adjustment - hdg_error;
}
SG_NORMALIZE_RANGE(nta_hdg, 0.0, 360.0);
target_auto_hdg_node->setDoubleValue( nta_hdg );
//////////////////////////////////////////////////////////
// compute the time to intercept selected radial (based on
// current and last cross track errors and dt)
//////////////////////////////////////////////////////////
double t = 0.0;
if ( inrange && cdi_serviceable ) {
double cur_rate = (last_xtrack_error - _cdiCrossTrackErrorM) / dt;
xrate_ms = 0.99 * xrate_ms + 0.01 * cur_rate;
if ( fabs(xrate_ms) > 0.00001 ) {
t = _cdiCrossTrackErrorM / xrate_ms;
} else {
t = 9999.9;
}
}
time_to_intercept->setDoubleValue( t );
if (!gs_serviceable_node->getBoolValue() ) {
_gsNeedleDeflection = 0.0;
_gsNeedleDeflectionNorm = 0.0;
}
gs_deflection_node->setDoubleValue(_gsNeedleDeflection);
gs_deflection_deg_node->setDoubleValue(_gsNeedleDeflectionNorm * 0.7);
gs_deflection_norm_node->setDoubleValue(_gsNeedleDeflectionNorm);
gs_direct_node->setDoubleValue(_gsDirect);
last_xtrack_error = _cdiCrossTrackErrorM;
}
void FGNavRadio::updateAudio( double dt )
{
if (!_navaid || !inrange_node->getBoolValue() || !nav_serviceable_node->getBoolValue()) {
_audioIdent->setIdent("", 0.0 );
return;
}
// play station ident via audio system if on + ident,
// otherwise turn it off
if (!isServiceableAndPowered()
|| !ident_btn_node->getBoolValue()
|| !audio_btn_node->getBoolValue() ) {
_audioIdent->setIdent("", 0.0 );
return;
}
_audioIdent->setIdent( _navaid->get_trans_ident(), vol_btn_node->getFloatValue() );
_audioIdent->update( dt );
}
FGNavRecord* FGNavRadio::findPrimaryNavaid(const SGGeod& aPos, double aFreqMHz)
{
return FGNavList::findByFreq(aFreqMHz, aPos, FGNavList::navFilter());
}
// Update current nav/adf radio stations based on current position
void FGNavRadio::search()
{
// set delay for next search
_time_before_search_sec = 1.0;
double freq = freq_node->getDoubleValue();
// immediate NAV search when frequency has changed (toggle between nav and g/s search otherwise)
_nav_search |= (_last_freq != freq);
// do we need to search a new NAV station in this iteration?
if (_nav_search)
{
_last_freq = freq;
FGNavRecord* nav = findPrimaryNavaid(globals->get_aircraft_position(), freq);
if (nav == _navaid) {
if (nav && (nav->type() != FGPositioned::VOR))
_nav_search = false; // search glideslope on next iteration
return; // nav hasn't changed, we're done
}
// remember new navaid station
_navaid = nav;
}
// search glideslope station
if ((_navaid.valid()) && (_navaid->type() != FGPositioned::VOR))
{
FGNavList::TypeFilter gsFilter(FGPositioned::GS);
FGNavRecord* gs = FGNavList::findByFreq(freq, globals->get_aircraft_position(),
&gsFilter);
if ((!_nav_search) && (gs == _gs))
{
_nav_search = true; // search NAV on next iteration
return; // g/s hasn't changed, neither has nav - we're done
}
// remember new glideslope station
_gs = gs;
}
_nav_search = true; // search NAV on next iteration
// nav or gs station has changed
updateNav();
}
// Update current nav/adf/glideslope outputs when station has changed
void FGNavRadio::updateNav()
{
// update necessary, nav and/or gs has changed
FGNavRecord* nav = _navaid;
string identBuffer(4, ' ');
if (nav) {
nav_id_node->setStringValue(nav->get_ident());
identBuffer = simgear::strutils::rpad( nav->ident(), 4, ' ' );
effective_range = adjustNavRange(nav->get_elev_ft(), globals->get_aircraft_position().getElevationM(), nav->get_range());
loc_node->setBoolValue(nav->type() != FGPositioned::VOR);
twist = nav->get_multiuse();
if (nav->type() == FGPositioned::VOR) {
target_radial = sel_radial_node->getDoubleValue();
_gs = NULL;
} else { // ILS or LOC
_localizerWidth = nav->localizerWidth();
twist = 0.0;
effective_range = nav->get_range();
target_radial = nav->get_multiuse();
SG_NORMALIZE_RANGE(target_radial, 0.0, 360.0);
if (_gs) {
target_gs = _gs->glideSlopeAngleDeg();
double gs_radial = fmod(_gs->get_multiuse(), 1000.0);
SG_NORMALIZE_RANGE(gs_radial, 0.0, 360.0);
_gsCart = _gs->cart();
// GS axis unit tangent vector
// (along the runway):
_gsAxis = tangentVector(_gs->geod(), gs_radial);
// GS baseline unit tangent vector
// (transverse to the runway along the ground)
_gsBaseline = tangentVector(_gs->geod(), gs_radial + 90.0);
_gsVertical = cross(_gsBaseline, _gsAxis);
} // of have glideslope
} // of found LOC or ILS
} else { // found nothing
_gs = NULL;
nav_id_node->setStringValue("");
loc_node->setBoolValue(false);
_audioIdent->setIdent("", 0.0 );
}
has_gs_node->setBoolValue(_gs != NULL);
is_valid_node->setBoolValue(nav != NULL);
id_c1_node->setIntValue( (int)identBuffer[0] );
id_c2_node->setIntValue( (int)identBuffer[1] );
id_c3_node->setIntValue( (int)identBuffer[2] );
id_c4_node->setIntValue( (int)identBuffer[3] );
}
// Register the subsystem.
#if 0
SGSubsystemMgr::InstancedRegistrant<FGNavRadio> registrantFGNavRadio(
SGSubsystemMgr::FDM,
{{"instrumentation", SGSubsystemMgr::Dependency::HARD}});
#endif

View File

@@ -0,0 +1,189 @@
// navradio.hxx -- class to manage a nav radio instance
//
// Written by Curtis Olson, started April 2000.
//
// Copyright (C) 2000 - 2002 Curtis L. Olson - http://www.flightgear.org/~curt
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation; either version 2 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
//
// $Id$
#ifndef _FG_NAVRADIO_HXX
#define _FG_NAVRADIO_HXX
#include <Navaids/navaids_fwd.hxx>
#include <Main/fg_props.hxx>
#include <simgear/compiler.h>
#include <simgear/timing/timestamp.hxx>
#include <Instrumentation/AbstractInstrument.hxx>
class SGSampleGroup;
class FGNavRadio : public AbstractInstrument,
public SGPropertyChangeListener
{
SGPropertyNode_ptr _radio_node;
// property inputs
SGPropertyNode_ptr is_valid_node; // is station data valid (may be way out
// of range.)
SGPropertyNode_ptr freq_node; // primary freq
SGPropertyNode_ptr alt_freq_node; // standby freq
SGPropertyNode_ptr is_loc_freq_node;// is the primary freq a loc/gs (paired) freq?
SGPropertyNode_ptr sel_radial_node; // selected radial
SGPropertyNode_ptr vol_btn_node;
SGPropertyNode_ptr ident_btn_node;
SGPropertyNode_ptr audio_btn_node;
SGPropertyNode_ptr backcourse_node;
SGPropertyNode_ptr nav_serviceable_node;
SGPropertyNode_ptr cdi_serviceable_node;
SGPropertyNode_ptr gs_serviceable_node;
SGPropertyNode_ptr tofrom_serviceable_node;
// property outputs
SGPropertyNode_ptr fmt_freq_node; // formated frequency
SGPropertyNode_ptr fmt_alt_freq_node; // formated alternate frequency
SGPropertyNode_ptr heading_node; // true heading to nav station
SGPropertyNode_ptr radial_node; // current radial we are on (taking
// into consideration the vor station
// alignment which likely doesn't
// match the magnetic alignment
// exactly.)
SGPropertyNode_ptr recip_radial_node; // radial_node(val) + 180 (for
// convenience)
SGPropertyNode_ptr target_radial_true_node;
// true heading of selected radial
SGPropertyNode_ptr target_auto_hdg_node;
// suggested autopilot heading
// to intercept selected radial
SGPropertyNode_ptr time_to_intercept; // estimated time to intecept selected
// radial at current speed and heading
SGPropertyNode_ptr to_flag_node;
SGPropertyNode_ptr from_flag_node;
SGPropertyNode_ptr inrange_node;
SGPropertyNode_ptr signal_quality_norm_node;
SGPropertyNode_ptr cdi_deflection_node;
SGPropertyNode_ptr cdi_deflection_norm_node;
SGPropertyNode_ptr cdi_xtrack_error_node;
SGPropertyNode_ptr cdi_xtrack_hdg_err_node;
SGPropertyNode_ptr has_gs_node;
SGPropertyNode_ptr loc_node;
SGPropertyNode_ptr loc_dist_node;
SGPropertyNode_ptr gs_deflection_node;
SGPropertyNode_ptr gs_deflection_deg_node;
SGPropertyNode_ptr gs_deflection_norm_node;
SGPropertyNode_ptr gs_direct_node;
SGPropertyNode_ptr gs_rate_of_climb_node;
SGPropertyNode_ptr gs_rate_of_climb_fpm_node;
SGPropertyNode_ptr gs_dist_node;
SGPropertyNode_ptr gs_inrange_node;
SGPropertyNode_ptr nav_id_node;
SGPropertyNode_ptr id_c1_node;
SGPropertyNode_ptr id_c2_node;
SGPropertyNode_ptr id_c3_node;
SGPropertyNode_ptr id_c4_node;
// gps slaving support
SGPropertyNode_ptr nav_slaved_to_gps_node;
SGPropertyNode_ptr gps_cdi_deflection_node;
SGPropertyNode_ptr gps_to_flag_node;
SGPropertyNode_ptr gps_from_flag_node;
SGPropertyNode_ptr gps_has_gs_node;
SGPropertyNode_ptr gps_course_node;
SGPropertyNode_ptr gps_xtrack_error_nm_node;
SGPropertyNode_ptr _magvarNode;
// realism setting, are false courses and GS lobes enabled?
SGPropertyNode_ptr falseCoursesEnabledNode;
// internal (private) values
int play_count;
bool _nav_search;
double _last_freq;
FGNavRecordRef _navaid;
FGNavRecordRef _gs;
double target_radial;
double effective_range;
double target_gs;
double twist;
double horiz_vel;
double last_x;
double last_xtrack_error;
double xrate_ms;
double _localizerWidth; // cached localizer width in degrees
// internal periodic station search timer
double _time_before_search_sec;
SGVec3d _gsCart, _gsAxis, _gsVertical, _gsBaseline;
// CDI properties
bool _toFlag, _fromFlag;
double _cdiDeflection;
double _cdiCrossTrackErrorM;
double _gsNeedleDeflection;
double _gsNeedleDeflectionNorm;
double _gsDirect;
class AudioIdent * _audioIdent;
bool updateWithPower(double aDt);
// model standard VOR/DME/TACAN service volumes as per AIM 1-1-8
double adjustNavRange( double stationElev, double aircraftElev,
double nominalRange );
// model standard ILS service volumes as per AIM 1-1-9
double adjustILSRange( double stationElev, double aircraftElev,
double offsetDegrees, double distance );
void updateAudio( double dt );
void updateReceiver(double dt);
void updateGlideSlope(double dt, const SGVec3d& aircraft, double signal_quality_norm);
void updateGPSSlaved();
void updateCDI(double dt);
void updateFormattedFrequencies();
void clearOutputs();
FGNavRecord* findPrimaryNavaid(const SGGeod& aPos, double aFreqMHz);
// implement SGPropertyChangeListener
virtual void valueChanged (SGPropertyNode * prop);
public:
FGNavRadio(SGPropertyNode *node);
~FGNavRadio();
// Subsystem API.
void init() override;
void reinit() override;
void update(double dt) override;
// Subsystem identification.
static const char* staticSubsystemClassId() { return "old-navradio"; }
// Update nav/adf radios based on current postition
void search ();
void updateNav();
};
#endif // _FG_NAVRADIO_HXX

View File

@@ -0,0 +1,985 @@
// navradio.cxx -- class to manage a nav radio instance
//
// Written by Curtis Olson, started April 2000.
// Rewritten by Torsten Dreyer, August 2011
//
// Copyright (C) 2000 - 2011 Curtis L. Olson - http://www.flightgear.org/~curt
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation; either version 2 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
//
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif
#include "newnavradio.hxx"
#include <assert.h>
#include <simgear/math/interpolater.hxx>
#include <simgear/sg_inlines.h>
#include <simgear/props/propertyObject.hxx>
#include <simgear/misc/strutils.hxx>
#include <simgear/sound/sample_group.hxx>
#include <Main/fg_props.hxx>
#include <Navaids/navlist.hxx>
#include <Sound/audioident.hxx>
#include "navradio.hxx"
#include "frequencyformatter.hxx"
namespace Instrumentation {
using namespace std::string_literals;
using simgear::PropertyObject;
/* --------------The Navigation Indicator ----------------------------- */
class NavIndicator {
public:
NavIndicator( SGPropertyNode * rootNode ) :
_cdi( rootNode->getNode("heading-needle-deflection", true ) ),
_cdiNorm( rootNode->getNode("heading-needle-deflection-norm", true ) ),
_course( rootNode->getNode("radials/selected-deg", true ) ),
_toFlag( rootNode->getNode("to-flag", true ) ),
_fromFlag( rootNode->getNode("from-flag", true ) ),
_signalQuality( rootNode->getNode("signal-quality-norm", true ) ),
_hasGS( rootNode->getNode("has-gs", true ) ),
_gsDeflection(rootNode->getNode("gs-needle-deflection", true )),
_gsDeflectionDeg(rootNode->getNode("gs-needle-deflection-deg", true )),
_gsDeflectionNorm(rootNode->getNode("gs-needle-deflection-norm", true ))
{
}
virtual ~NavIndicator() {}
/**
* set the normalized CDI deflection
* @param norm the cdi deflection normalized [-1..1]
*/
void setCDI( double norm )
{
_cdi = norm * 10.0;
_cdiNorm = norm;
}
/**
* set the normalized GS deflection
* @param norm the gs deflection normalized to [-1..1]
*/
void setGS( double norm )
{
_gsDeflectionNorm = norm;
_gsDeflectionDeg = norm * 0.7;
_gsDeflection = norm * 3.5;
}
void setGS( bool enabled )
{
_hasGS = enabled;
if( !enabled ) {
setGS( 0.0 );
}
}
void showFrom( bool on )
{
_fromFlag = on;
}
void showTo( bool on )
{
_toFlag = on;
}
void setSelectedCourse( double course )
{
_course = course;
}
double getSelectedCourse() const
{
return SGMiscd::normalizePeriodic(0.0, 360.0, _course );
}
void setSignalQuality( double signalQuality )
{
_signalQuality = signalQuality;
}
private:
PropertyObject<double> _cdi;
PropertyObject<double> _cdiNorm;
PropertyObject<double> _course;
PropertyObject<double> _toFlag;
PropertyObject<double> _fromFlag;
PropertyObject<double> _signalQuality;
PropertyObject<double> _hasGS;
PropertyObject<double> _gsDeflection;
PropertyObject<double> _gsDeflectionDeg;
PropertyObject<double> _gsDeflectionNorm;
};
/* ---------------------------------------------------------------- */
class NavRadioComponent {
public:
NavRadioComponent( const std::string & name, SGPropertyNode_ptr rootNode );
virtual ~NavRadioComponent();
virtual void update( double dt, const SGGeod & aircraftPosition );
virtual void search( double frequency, const SGGeod & aircraftPosition );
virtual double getRange_nm( const SGGeod & aircraftPosition );
virtual void display( NavIndicator & navIndicator ) = 0;
virtual bool valid() const { return NULL != _navRecord && _serviceable; }
virtual const std::string getIdent() const { return _ident; }
protected:
virtual double computeSignalQuality_norm( const SGGeod & aircraftPosition );
virtual FGNavList::TypeFilter* getNavaidFilter() = 0;
// General-purpose sawtooth function. Graph looks like this:
// /\ .
// \/
// Odd symmetry, inversion symmetry about the origin.
// Unit slope at the origin.
// Max 1, min -1, period 4.
// Two zero-crossings per period, one with + slope, one with - slope.
// Useful for false localizer courses.
static double sawtooth(double xx)
{
return 4.0 * fabs(xx/4.0 + 0.25 - floor(xx/4.0 + 0.75)) - 1.0;
}
SGPropertyNode_ptr _rootNode;
const std::string _name;
FGNavRecord * _navRecord;
PropertyObject<bool> _serviceable;
PropertyObject<double> _signalQuality_norm;
PropertyObject<double> _trueBearingTo_deg;
PropertyObject<double> _trueBearingFrom_deg;
PropertyObject<double> _trackDistance_m;
PropertyObject<double> _slantDistance_m;
PropertyObject<double> _heightAboveStation_ft;
PropertyObject<std::string> _ident;
PropertyObject<bool> _inRange;
PropertyObject<double> _range_nm;
};
class NavRadioComponentWithIdent : public NavRadioComponent {
public:
NavRadioComponentWithIdent( const std::string & name, SGPropertyNode_ptr rootNode, AudioIdent * audioIdent );
virtual ~NavRadioComponentWithIdent();
void update( double dt, const SGGeod & aircraftPosition );
protected:
static std::string getIdentString( const std::string & name, int index );
private:
AudioIdent * _audioIdent;
PropertyObject<double> _identVolume;
PropertyObject<bool> _identEnabled;
};
std::string NavRadioComponentWithIdent::getIdentString( const std::string & name, int index )
{
std::ostringstream temp;
temp << name << "-ident-" << index;
return temp.str();
}
NavRadioComponentWithIdent::NavRadioComponentWithIdent( const std::string & name, SGPropertyNode_ptr rootNode, AudioIdent * audioIdent ) :
NavRadioComponent( name, rootNode ),
_audioIdent( audioIdent ),
_identVolume( rootNode->getNode(name,true)->getNode("ident-volume",true) ),
_identEnabled( rootNode->getNode(name,true)->getNode("ident-enabled",true) )
{
_audioIdent->init();
}
NavRadioComponentWithIdent::~NavRadioComponentWithIdent()
{
delete _audioIdent;
}
void NavRadioComponentWithIdent::update( double dt, const SGGeod & aircraftPosition )
{
NavRadioComponent::update( dt, aircraftPosition );
_audioIdent->update( dt );
if( !( valid() && _identEnabled && _signalQuality_norm > 0.1 ) ) {
_audioIdent->setIdent("", 0.0 );
return;
}
_audioIdent->setIdent( _ident, SGMiscd::clip(_identVolume, 0.0, 1.0) );
}
NavRadioComponent::NavRadioComponent( const std::string & name, SGPropertyNode_ptr rootNode ) :
_rootNode(rootNode),
_name(name),
_navRecord(NULL),
_serviceable( rootNode->getNode(name,true)->getNode("serviceable",true) ),
_signalQuality_norm( rootNode->getNode(name,true)->getNode("signal-quality-norm",true) ),
_trueBearingTo_deg( rootNode->getNode(name,true)->getNode("true-bearing-to-deg",true) ),
_trueBearingFrom_deg( rootNode->getNode(name,true)->getNode("true-bearing-from-deg",true) ),
_trackDistance_m( rootNode->getNode(name,true)->getNode("track-distance-m",true) ),
_slantDistance_m( rootNode->getNode(name,true)->getNode("slant-distance-m",true) ),
_heightAboveStation_ft( rootNode->getNode(name,true)->getNode("height-above-station-ft",true) ),
_ident( rootNode->getNode(name,true)->getNode("ident",true) ),
_inRange( rootNode->getNode(name,true)->getNode("in-range",true) ),
_range_nm( rootNode->getNode(_name,true)->getNode("range-nm",true) )
{
simgear::props::Type typ = _serviceable.node()->getType();
if ((typ == simgear::props::NONE) || (typ == simgear::props::UNSPECIFIED))
_serviceable = true;
}
NavRadioComponent::~NavRadioComponent()
{
}
double NavRadioComponent::getRange_nm( const SGGeod & aircraftPosition )
{
if( _navRecord == NULL ) return 0.0; // no station: no range
double d = _navRecord->get_range();
if( d <= SGLimitsd::min() ) return 25.0; // no configured range: arbitrary number
return d; // configured range
}
void NavRadioComponent::search( double frequency, const SGGeod & aircraftPosition )
{
_navRecord = FGNavList::findByFreq(frequency, aircraftPosition, getNavaidFilter() );
if( NULL == _navRecord ) {
SG_LOG(SG_INSTR,SG_DEBUG, "No " << _name << " available at " << frequency );
_ident = "";
return;
}
SG_LOG(SG_INSTR, SG_INFO,
"Using " << _name << " '" << _navRecord->get_ident() << "' for " <<
frequency);
_ident = _navRecord->ident();
}
double NavRadioComponent::computeSignalQuality_norm( const SGGeod & aircraftPosition )
{
if( !valid() ) return 0.0;
double distance_nm = _slantDistance_m * SG_METER_TO_NM;
double range_nm = _range_nm;
// assume signal quality is 100% up to the published range and
// decay with the distance squared further out
if ( distance_nm <= range_nm ) return 1.0;
return range_nm*range_nm/(distance_nm*distance_nm);
}
void NavRadioComponent::update( double dt, const SGGeod & aircraftPosition )
{
if( !valid() ) {
_signalQuality_norm = 0.0;
_trueBearingTo_deg = 0.0;
_trueBearingFrom_deg = 0.0;
_trackDistance_m = 0.0;
_slantDistance_m = 0.0;
return;
}
_slantDistance_m = dist(_navRecord->cart(), SGVec3d::fromGeod(aircraftPosition));
double az1 = 0.0, az2 = 0.0, dist = 0.0;
SGGeodesy::inverse(aircraftPosition, _navRecord->geod(), az1, az2, dist );
_trueBearingTo_deg = az1; _trueBearingFrom_deg = az2; _trackDistance_m = dist;
_heightAboveStation_ft = SGMiscd::max(0.0, aircraftPosition.getElevationFt() - _navRecord->get_elev_ft());
_range_nm = getRange_nm(aircraftPosition);
_signalQuality_norm = computeSignalQuality_norm( aircraftPosition );
_inRange = _signalQuality_norm > 0.2;
}
/* ---------------------------------------------------------------- */
static SGPath VORTablePath( const char * name )
{
SGPath path( globals->get_fg_root() );
path.append( "Navaids" );
path.append(name);
return path;
}
class VOR : public NavRadioComponentWithIdent {
public:
VOR( SGPropertyNode_ptr rootNode);
virtual ~VOR();
virtual void update( double dt, const SGGeod & aircraftPosition );
virtual void display( NavIndicator & navIndicator );
virtual double getRange_nm(const SGGeod & aircraftPosition);
protected:
virtual double computeSignalQuality_norm( const SGGeod & aircraftPosition );
virtual FGNavList::TypeFilter* getNavaidFilter();
private:
double _totalTime;
class ServiceVolume {
public:
ServiceVolume() :
term_tbl(VORTablePath("range.term")),
low_tbl(VORTablePath("range.low")),
high_tbl(VORTablePath("range.high")) {
}
double adjustRange( double height_ft, double nominalRange_nm );
private:
SGInterpTable term_tbl;
SGInterpTable low_tbl;
SGInterpTable high_tbl;
} _serviceVolume;
PropertyObject<double> _radial;
PropertyObject<double> _radialInbound;
};
// model standard VOR/DME/TACAN service volumes as per AIM 1-1-8
double VOR::ServiceVolume::adjustRange( double height_ft, double nominalRange_nm )
{
if (nominalRange_nm < SGLimitsd::min() )
nominalRange_nm = FG_NAV_DEFAULT_RANGE;
// extend out actual usable range to be 1.3x the published safe range
const double usability_factor = 1.3;
// assumptions we model the standard service volume, plus
// ... rather than specifying a cylinder, we model a cone that
// contains the cylinder. Then we put an upside down cone on top
// to model diminishing returns at too-high altitudes.
if ( nominalRange_nm < 25.0 + SG_EPSILON ) {
// Standard Terminal Service Volume
return term_tbl.interpolate( height_ft ) * usability_factor;
} else if ( nominalRange_nm < 50.0 + SG_EPSILON ) {
// Standard Low Altitude Service Volume
// table is based on range of 40, scale to actual range
return low_tbl.interpolate( height_ft ) * nominalRange_nm / 40.0
* usability_factor;
} else {
// Standard High Altitude Service Volume
// table is based on range of 130, scale to actual range
return high_tbl.interpolate( height_ft ) * nominalRange_nm / 130.0
* usability_factor;
}
}
VOR::VOR( SGPropertyNode_ptr rootNode) :
NavRadioComponentWithIdent("vor", rootNode,
new VORAudioIdent(getIdentString("vor"s,
rootNode->getIndex()))),
_totalTime(0.0),
_radial( rootNode->getNode(_name,true)->getNode("radial",true) ),
_radialInbound( rootNode->getNode(_name,true)->getNode("radial-inbound",true) )
{
}
VOR::~VOR()
{
}
double VOR::getRange_nm( const SGGeod & aircraftPosition )
{
return _serviceVolume.adjustRange( _heightAboveStation_ft, _navRecord->get_range() );
}
FGNavList::TypeFilter* VOR::getNavaidFilter()
{
static FGNavList::TypeFilter filter(FGPositioned::VOR);
return &filter;
}
double VOR::computeSignalQuality_norm( const SGGeod & aircraftPosition )
{
// apply cone of confusion. Some sources say it's opening angle is 53deg, others estimate
// a diameter of 1NM per 6000ft (approx. 45deg). ICAO Annex 10 says minimum 40deg.
// We use 1NM@6000ft and a distance-squared
// function to make signal-quality=100% 0.5NM@6000ft from the center and zero overhead
double cone_of_confusion_width = 0.5 * _heightAboveStation_ft / 6000.0 * SG_NM_TO_METER;
if( _trackDistance_m < cone_of_confusion_width ) {
double d = cone_of_confusion_width <= SGLimitsd::min() ? 1 :
(1 - _trackDistance_m/cone_of_confusion_width);
return 1-d*d;
}
// use default decay function outside the cone of confusion
return NavRadioComponentWithIdent::computeSignalQuality_norm( aircraftPosition );
}
void VOR::update( double dt, const SGGeod & aircraftPosition )
{
_totalTime += dt;
NavRadioComponentWithIdent::update( dt, aircraftPosition );
if( !valid() ) {
_radial = 0.0;
return;
}
// an arbitrary error function
double error = 0.5*(sin(_totalTime/11.0) + sin(_totalTime/23.0));
// add 1% error at 100% signal-quality
// add 50% error at 0% signal-quality
// of full deflection (+/-10deg)
double e = 10.0 * ( 0.01 + (1-_signalQuality_norm) * 0.49 ) * error;
// compute magnetic bearing from the station (aka current radial)
double r = SGMiscd::normalizePeriodic(0.0, 360.0, _trueBearingFrom_deg - _navRecord->get_multiuse() + e );
_radial = r;
_radialInbound = SGMiscd::normalizePeriodic(0.0,360.0, 180.0 + _radial);
}
void VOR::display( NavIndicator & navIndicator )
{
if( !valid() ) return;
double offset = SGMiscd::normalizePeriodic(-180.0,180.0,_radial - navIndicator.getSelectedCourse());
bool to = fabs(offset) >= 90.0;
if( to ) offset = -offset + copysign(180.0,offset);
navIndicator.showTo( to );
navIndicator.showFrom( !to );
// normalize to +/- 1.0 for +/- 10deg, decrease deflection with decreasing signal
navIndicator.setCDI( SGMiscd::clip( -offset/10.0, -1.0, 1.0 ) * _signalQuality_norm );
navIndicator.setSignalQuality( _signalQuality_norm );
}
/* ---------------------------------------------------------------- */
class LOC : public NavRadioComponentWithIdent {
public:
LOC( SGPropertyNode_ptr rootNode );
virtual ~LOC();
virtual void update( double dt, const SGGeod & aircraftPosition );
virtual void search( double frequency, const SGGeod & aircraftPosition );
virtual void display( NavIndicator & navIndicator );
virtual double getRange_nm(const SGGeod & aircraftPosition);
protected:
virtual double computeSignalQuality_norm( const SGGeod & aircraftPosition );
virtual FGNavList::TypeFilter* getNavaidFilter();
private:
class ServiceVolume {
public:
ServiceVolume();
double adjustRange( double azimuthAngle_deg, double elevationAngle_deg );
private:
SGInterpTable _azimuthTable;
SGInterpTable _elevationTable;
} _serviceVolume;
PropertyObject<double> _localizerOffset_norm;
PropertyObject<double> _localizerOffset_m;
PropertyObject<double> _localizerWidth_deg;
};
LOC::ServiceVolume::ServiceVolume()
{
// maybe this: http://www.tpub.com/content/aviation2/P-1244/P-12440125.htm
// ICAO Annex 10 - 3.1.3.2.2: The emission from the localizer
// shall be horizontally polarized
// very rough abstraction of a 5-element yagi antenna's
// E-plane radiation diagram
_azimuthTable.addEntry( 0.0, 1.0 );
_azimuthTable.addEntry( 10.0, 1.0 );
_azimuthTable.addEntry( 30.0, 0.75 );
_azimuthTable.addEntry( 40.0, 0.50 );
_azimuthTable.addEntry( 50.0, 0.20 );
_azimuthTable.addEntry( 60.0, 0.10 );
_azimuthTable.addEntry( 70.0, 0.20 );
_azimuthTable.addEntry( 80.0, 0.10 );
_azimuthTable.addEntry( 90.0, 0.05 );
_azimuthTable.addEntry( 105.0, 0.10 );
_azimuthTable.addEntry( 130.0, 0.05 );
_azimuthTable.addEntry( 150.0, 0.30 );
_azimuthTable.addEntry( 160.0, 0.40 );
_azimuthTable.addEntry( 170.0, 0.50 );
_azimuthTable.addEntry( 180.0, 0.50 );
_elevationTable.addEntry( 0.0, 0.1 );
_elevationTable.addEntry( 1.05, 1.0 );
_elevationTable.addEntry( 7.00, 1.0 );
_elevationTable.addEntry( 45.0, 0.3 );
_elevationTable.addEntry( 90.0, 0.1 );
_elevationTable.addEntry( 180.0, 0.01 );
}
double LOC::ServiceVolume::adjustRange( double azimuthAngle_deg, double elevationAngle_deg )
{
return _azimuthTable.interpolate( fabs(azimuthAngle_deg) ) *
_elevationTable.interpolate( fabs(elevationAngle_deg) );
}
LOC::LOC( SGPropertyNode_ptr rootNode) :
NavRadioComponentWithIdent("loc", rootNode, new LOCAudioIdent(getIdentString("loc"s,
rootNode->getIndex()))),
_serviceVolume(),
_localizerOffset_norm( rootNode->getNode(_name,true)->getNode("offset-norm",true) ),
_localizerOffset_m( rootNode->getNode(_name,true)->getNode("offset-m",true) ),
_localizerWidth_deg( rootNode->getNode(_name,true)->getNode("width-deg",true) )
{
}
LOC::~LOC()
{
}
FGNavList::TypeFilter* LOC::getNavaidFilter()
{
return FGNavList::locFilter();
}
void LOC::search( double frequency, const SGGeod & aircraftPosition )
{
NavRadioComponentWithIdent::search( frequency, aircraftPosition );
if( !valid() ) {
_localizerWidth_deg = 0.0;
return;
}
// cache slightly expensive value,
// sanitized in FGNavRecord::localizerWidth() to never become zero
_localizerWidth_deg = _navRecord->localizerWidth();
}
/* Localizer coverage (ICAO Annex 10 Volume I 3.1.3.3
25NM within +/-10 deg from the front course line
17NM between 10 and 35deg from the front course line
10NM outside of +/- 35deg if coverage is provided
at and above a height of 2000ft above threshold or
1000ft above the highest point within intermediate
and final approach areas. Upper limit is a surface
extending outward from the localizer and inclined at
7 degrees above the horizontal
*/
double LOC::getRange_nm(const SGGeod & aircraftPosition)
{
double elevationAngle = ::atan2(_heightAboveStation_ft*SG_FEET_TO_METER, _trackDistance_m)*SG_RADIANS_TO_DEGREES;
double azimuthAngle = SGMiscd::normalizePeriodic( -180.0, 180.0, _trueBearingFrom_deg + 180.0 - _navRecord->get_multiuse() );
// looks like our navrecord declared range is based on 10NM?
return _navRecord->get_range() * _serviceVolume.adjustRange( azimuthAngle, elevationAngle );
}
double LOC::computeSignalQuality_norm( const SGGeod & aircraftPosition )
{
return NavRadioComponentWithIdent::computeSignalQuality_norm( aircraftPosition );
}
void LOC::update( double dt, const SGGeod & aircraftPosition )
{
NavRadioComponentWithIdent::update( dt, aircraftPosition );
if( !valid() ) {
_localizerOffset_norm = 0.0;
_localizerOffset_m = 0.0;
return;
}
double offsetDeg = SGMiscd::normalizePeriodic( -180.0, 180.0, _trueBearingFrom_deg + 180.0 - _navRecord->get_multiuse() );
// cross-track error (in meters)
_localizerOffset_m = _trackDistance_m * sin(offsetDeg * SGD_DEGREES_TO_RADIANS);
// The factor of 30.0 gives a period of 120 which gives us 3 cycles and six
// zeros i.e. six courses: one front course, one back course, and four
// false courses. Three of the six are reverse sensing.
offsetDeg = 30.0 * sawtooth(offsetDeg / 30.0);
// normalize offsetDeg to the localizer width, scale and clip to [-1..1]
offsetDeg = SGMiscd::clip( 2.0 * offsetDeg / _localizerWidth_deg, -1.0, 1.0 );
_localizerOffset_norm = offsetDeg;
}
void LOC::display( NavIndicator & navIndicator )
{
if( !valid() )
return;
navIndicator.showTo( true );
navIndicator.showFrom( false );
navIndicator.setCDI( _localizerOffset_norm * _signalQuality_norm );
navIndicator.setSignalQuality( _signalQuality_norm );
}
class GS : public NavRadioComponent {
public:
GS( SGPropertyNode_ptr rootNode);
virtual ~GS();
virtual void update( double dt, const SGGeod & aircraftPosition );
virtual void search( double frequency, const SGGeod & aircraftPosition );
virtual void display( NavIndicator & navIndicator );
virtual double getRange_nm(const SGGeod & aircraftPosition);
protected:
virtual FGNavList::TypeFilter* getNavaidFilter();
private:
class ServiceVolume {
public:
ServiceVolume();
double adjustRange( double azimuthAngle_deg, double elevationAngle_deg );
private:
SGInterpTable _azimuthTable;
SGInterpTable _elevationTable;
} _serviceVolume;
static SGVec3d tangentVector(const SGGeod& midpoint, const double heading);
PropertyObject<double> _targetGlideslope_deg;
PropertyObject<double> _glideslopeOffset_norm;
SGVec3d _gsAxis;
SGVec3d _gsVertical;
};
GS::ServiceVolume::ServiceVolume()
{
// maybe this: http://www.tpub.com/content/aviation2/P-1244/P-12440125.htm
// ICAO Annex 10 - 3.1.5.2.2: The emission from the glide path equipment
// shall be horizontally polarized
// very rough abstraction of a 5-element yagi antenna's
// E-plane radiation diagram
_azimuthTable.addEntry( 0.0, 1.0 );
_azimuthTable.addEntry( 10.0, 1.0 );
_azimuthTable.addEntry( 30.0, 0.75 );
_azimuthTable.addEntry( 40.0, 0.50 );
_azimuthTable.addEntry( 50.0, 0.20 );
_azimuthTable.addEntry( 60.0, 0.10 );
_azimuthTable.addEntry( 70.0, 0.20 );
_azimuthTable.addEntry( 80.0, 0.10 );
_azimuthTable.addEntry( 90.0, 0.05 );
_azimuthTable.addEntry( 105.0, 0.10 );
_azimuthTable.addEntry( 130.0, 0.05 );
_azimuthTable.addEntry( 150.0, 0.30 );
_azimuthTable.addEntry( 160.0, 0.40 );
_azimuthTable.addEntry( 170.0, 0.50 );
_azimuthTable.addEntry( 180.0, 0.50 );
_elevationTable.addEntry( 0.0, 0.1 );
_elevationTable.addEntry( 1.05, 1.0 );
_elevationTable.addEntry( 7.00, 1.0 );
_elevationTable.addEntry( 45.0, 0.3 );
_elevationTable.addEntry( 90.0, 0.1 );
_elevationTable.addEntry( 180.0, 0.01 );
}
double GS::ServiceVolume::adjustRange( double azimuthAngle_deg, double elevationAngle_deg )
{
return _azimuthTable.interpolate( fabs(azimuthAngle_deg) ) *
_elevationTable.interpolate( fabs(elevationAngle_deg) );
}
GS::GS( SGPropertyNode_ptr rootNode) :
NavRadioComponent("gs", rootNode ),
_targetGlideslope_deg( rootNode->getNode(_name,true)->getNode("slope",true) ),
_glideslopeOffset_norm( rootNode->getNode(_name,true)->getNode("offset-norm",true) ),
_gsAxis(SGVec3d::zeros()),
_gsVertical(SGVec3d::zeros())
{
}
GS::~GS()
{
}
FGNavList::TypeFilter* GS::getNavaidFilter()
{
static FGNavList::TypeFilter filter(FGPositioned::GS);
return &filter;
}
double GS::getRange_nm(const SGGeod & aircraftPosition)
{
double elevationAngle = ::atan2(_heightAboveStation_ft*SG_FEET_TO_METER, _trackDistance_m)*SG_RADIANS_TO_DEGREES;
double azimuthAngle = SGMiscd::normalizePeriodic( -180.0, 180.0, _trueBearingFrom_deg + 180.0 - fmod(_navRecord->get_multiuse(), 1000.0) );
return _navRecord->get_range() * _serviceVolume.adjustRange( azimuthAngle, elevationAngle );
}
// Calculate a Cartesian unit vector in the
// local horizontal plane, i.e. tangent to the
// surface of the earth at the local ground zero.
// The tangent vector passes through the given <midpoint>
// and points forward along the given <heading>.
// The <heading> is given in degrees.
SGVec3d GS::tangentVector(const SGGeod& midpoint, const double heading)
{
// move 100m away from the midpoint - arbitrary number
const double delta(100.0);
SGGeod head, tail;
double az2; // ignored
SGGeodesy::direct(midpoint, heading, delta, head, az2);
SGGeodesy::direct(midpoint, 180+heading, delta, tail, az2);
head.setElevationM(midpoint.getElevationM());
tail.setElevationM(midpoint.getElevationM());
SGVec3d head_xyz = SGVec3d::fromGeod(head);
SGVec3d tail_xyz = SGVec3d::fromGeod(tail);
// Awkward formula here, needed because vector-by-scalar
// multiplication is defined, but not vector-by-scalar division.
return (head_xyz - tail_xyz) * (0.5/delta);
}
void GS::search( double frequency, const SGGeod & aircraftPosition )
{
NavRadioComponent::search( frequency, aircraftPosition );
if( !valid() ) {
_gsAxis = SGVec3d::zeros();
_gsVertical = SGVec3d::zeros();
_targetGlideslope_deg = 3.0;
return;
}
double gs_radial = SGMiscd::normalizePeriodic(0.0, 360.0, fmod(_navRecord->get_multiuse(), 1000.0) );
_gsAxis = tangentVector(_navRecord->geod(), gs_radial);
SGVec3d gsBaseline = tangentVector(_navRecord->geod(), gs_radial + 90.0);
_gsVertical = cross(gsBaseline, _gsAxis);
int tmp = (int)(_navRecord->get_multiuse() / 1000.0);
// catch unconfigured glideslopes here, they will cause nan later
_targetGlideslope_deg = SGMiscd::max( 1.0, (double)tmp / 100.0 );
}
void GS::update( double dt, const SGGeod & aircraftPosition )
{
NavRadioComponent::update( dt, aircraftPosition );
if( !valid() ) {
_glideslopeOffset_norm = 0.0;
return;
}
SGVec3d pos = SGVec3d::fromGeod(aircraftPosition) - _navRecord->cart(); // relative vector from gs antenna to aircraft
// The positive GS axis points along the runway in the landing direction,
// toward the far end, not toward the approach area, so we need a - sign here:
double comp_h = -dot(pos, _gsAxis); // component in horiz direction
double comp_v = dot(pos, _gsVertical); // component in vertical direction
//double comp_b = dot(pos, _gsBaseline); // component in baseline direction
//if (comp_b) {} // ... (useful for debugging)
// _gsDirect represents the angle of elevation of the aircraft
// as seen by the GS transmitter.
double gsDirect = atan2(comp_v, comp_h) * SGD_RADIANS_TO_DEGREES;
// At this point, if the aircraft is centered on the glide slope,
// _gsDirect will be a small positive number, e.g. 3.0 degrees
// Aim the branch cut straight down
// into the ground below the GS transmitter:
if (gsDirect < -90.0) gsDirect += 360.0;
double offset = _targetGlideslope_deg - gsDirect;
if( offset < 0.0 )
offset = _targetGlideslope_deg/2 * sawtooth(2.0*offset/_targetGlideslope_deg);
assert( !SGMisc<double>::isNaN(offset) );
// GS is documented to be 1.4 degrees thick,
// i.e. plus or minus 0.7 degrees from the midline:
_glideslopeOffset_norm = SGMiscd::clip(offset/0.7, -1.0, 1.0);
}
void GS::display( NavIndicator & navIndicator )
{
if( !valid() ) {
navIndicator.setGS( false );
return;
}
navIndicator.setGS( true );
navIndicator.setGS( _glideslopeOffset_norm );
}
/* ------------- The NavRadio implementation ---------------------- */
class NavRadioImpl : public NavRadio
{
public:
NavRadioImpl( SGPropertyNode_ptr node );
virtual ~NavRadioImpl();
// Subsystem API.
void init() override;
void update(double dt) override;
private:
void search();
class Legacy {
public:
Legacy( NavRadioImpl * navRadioImpl ) : _navRadioImpl( navRadioImpl ) {}
void init();
void update( double dt );
private:
NavRadioImpl * _navRadioImpl;
SGPropertyNode_ptr is_valid_node;
SGPropertyNode_ptr nav_serviceable_node;
SGPropertyNode_ptr nav_id_node;
SGPropertyNode_ptr id_c1_node;
SGPropertyNode_ptr id_c2_node;
SGPropertyNode_ptr id_c3_node;
SGPropertyNode_ptr id_c4_node;
} _legacy;
const static int VOR_COMPONENT = 0;
const static int LOC_COMPONENT = 1;
const static int GS_COMPONENT = 2;
std::string _name;
int _num;
SGPropertyNode_ptr _rootNode;
FrequencyFormatter _useFrequencyFormatter;
FrequencyFormatter _stbyFrequencyFormatter;
std::vector<NavRadioComponent*> _components;
NavIndicator _navIndicator;
double _stationTTL;
double _frequency;
PropertyObject<bool> _cdiDisconnected;
PropertyObject<std::string> _navType;
};
NavRadioImpl::NavRadioImpl( SGPropertyNode_ptr node ) :
_legacy( this ),
_name(node->getStringValue("name", "nav")),
_num(node->getIntValue("number", 0)),
_rootNode(fgGetNode( "/instrumentation/"s + _name, _num, true)),
_useFrequencyFormatter( _rootNode->getNode("frequencies/selected-mhz",true), _rootNode->getNode("frequencies/selected-mhz-fmt",true), 0.05, 108.0, 118.0 ),
_stbyFrequencyFormatter( _rootNode->getNode("frequencies/standby-mhz",true), _rootNode->getNode("frequencies/standby-mhz-fmt",true), 0.05, 108.0, 118.0 ),
_navIndicator(_rootNode),
_stationTTL(0.0),
_frequency(-1.0),
_cdiDisconnected(_rootNode->getNode("cdi-disconnected",true)),
_navType(_rootNode->getNode("nav-type",true))
{
}
NavRadioImpl::~NavRadioImpl()
{
for( auto p : _components ) {
delete p;
}
}
void NavRadioImpl::init()
{
if( ! _components.empty() )
return;
_components.push_back( new VOR(_rootNode) );
_components.push_back( new LOC(_rootNode) );
_components.push_back( new GS(_rootNode) );
_legacy.init();
}
void NavRadioImpl::search()
{
}
void NavRadioImpl::update( double dt )
{
if( dt < SGLimitsd::min() ) return;
SGGeod position;
try {
position = globals->get_aircraft_position();
}
catch( std::exception & ) {
return;
}
_stationTTL -= dt;
if( _frequency != _useFrequencyFormatter.getFrequency() ) {
_frequency = _useFrequencyFormatter.getFrequency();
_stationTTL = 0.0;
}
for( auto p : _components ) {
if( _stationTTL <= 0.0 )
p->search( _frequency, position );
p->update( dt, position );
if( !_cdiDisconnected )
p->display( _navIndicator );
}
if( _stationTTL <= 0.0 )
_stationTTL = 30.0;
if( _components[VOR_COMPONENT]->valid() ) {
_navType = "vor";
} else if( _components[LOC_COMPONENT]->valid() ) {
_navType = "loc";
} else {
_navType = "";
}
_legacy.update( dt );
}
void NavRadioImpl::Legacy::init()
{
is_valid_node = _navRadioImpl->_rootNode->getChild("data-is-valid", 0, true);
nav_serviceable_node = _navRadioImpl->_rootNode->getChild("serviceable", 0, true);
nav_id_node = _navRadioImpl->_rootNode->getChild("nav-id", 0, true );
id_c1_node = _navRadioImpl->_rootNode->getChild("nav-id_asc1", 0, true );
id_c2_node = _navRadioImpl->_rootNode->getChild("nav-id_asc2", 0, true );
id_c3_node = _navRadioImpl->_rootNode->getChild("nav-id_asc3", 0, true );
id_c4_node = _navRadioImpl->_rootNode->getChild("nav-id_asc4", 0, true );
}
void NavRadioImpl::Legacy::update( double dt )
{
is_valid_node->setBoolValue(
_navRadioImpl->_components[VOR_COMPONENT]->valid() || _navRadioImpl->_components[LOC_COMPONENT]->valid()
);
std::string ident = _navRadioImpl->_components[VOR_COMPONENT]->getIdent();
if( ident.empty() )
ident = _navRadioImpl->_components[LOC_COMPONENT]->getIdent();
nav_id_node->setStringValue( ident );
ident = simgear::strutils::rpad( ident, 4, ' ' );
id_c1_node->setIntValue( (int)ident[0] );
id_c2_node->setIntValue( (int)ident[1] );
id_c3_node->setIntValue( (int)ident[2] );
id_c4_node->setIntValue( (int)ident[3] );
}
SGSubsystem * NavRadio::createInstance( SGPropertyNode_ptr rootNode )
{
// use old navradio code by default
if( fgGetBool( "/instrumentation/use-new-navradio", false ) )
return new NavRadioImpl( rootNode );
return new FGNavRadio( rootNode );
}
// Register the subsystem.
#if 0
SGSubsystemMgr::InstancedRegistrant<NavRadio> registrantNavRadio(
SGSubsystemMgr::FDM,
{{"instrumentation", SGSubsystemMgr::Dependency::HARD}});
#endif
} // namespace Instrumentation

View File

@@ -0,0 +1,42 @@
// navradio.hxx -- class to manage a nav radio instance
//
// Written by Torsten Dreyer, started August 2011
//
// Copyright (C) 2000 - 2011 Curtis L. Olson - http://www.flightgear.org/~curt
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation; either version 2 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
//
#ifndef _FG_INSTRUMENTATION_NAVRADIO_HXX
#define _FG_INSTRUMENTATION_NAVRADIO_HXX
#include <simgear/props/props.hxx>
#include <simgear/structure/subsystem_mgr.hxx>
namespace Instrumentation {
class NavRadio : public SGSubsystem
{
public:
// Subsystem identification.
static const char* staticSubsystemClassId() { return "nav-radio"; }
static SGSubsystem * createInstance( SGPropertyNode_ptr rootNode );
};
}
#endif // _FG_INSTRUMENTATION_NAVRADIO_HXX

View File

@@ -0,0 +1,225 @@
// Radar Altimeter
//
// Written by Vivian MEAZZA, started Feb 2008.
//
//
// Copyright (C) 2008 Vivian Meazza
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation; either version 2 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
//
//
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
#include "rad_alt.hxx"
#include <simgear/scene/material/mat.hxx>
#include <Main/fg_props.hxx>
#include <Main/globals.hxx>
#include <Scenery/scenery.hxx>
RadarAltimeter::RadarAltimeter(SGPropertyNode *node) :
_time(0.0),
_interval(node->getDoubleValue("update-interval-sec", 1.0))
{
_name = node->getStringValue("name", "radar-altimeter");
_num = node->getIntValue("number", 0);
}
RadarAltimeter::~RadarAltimeter()
{
}
void
RadarAltimeter::init ()
{
std::string branch = "/instrumentation/" + _name;
_Instrument = fgGetNode(branch.c_str(), _num, true);
_sceneryLoaded = fgGetNode("/sim/sceneryloaded", true);
_serviceable_node = _Instrument->getNode("serviceable", true);
_user_alt_agl_node = fgGetNode("/position/altitude-agl-ft", true);
_rad_alt_warning_node = fgGetNode("/sim/alarms/rad-alt-warning", true);
_Instrument->setFloatValue("tilt",-85);
_Instrument->setStringValue("status","RA");
_Instrument->getDoubleValue("elev-limit", true);
_Instrument->getDoubleValue("elev-step-deg", true);
_Instrument->getDoubleValue("az-limit-deg", true);
_Instrument->getDoubleValue("az-step-deg", true);
_Instrument->getDoubleValue("max-range-m", true);
_Instrument->getDoubleValue("min-range-m", true);
_Instrument->getDoubleValue("tilt", true);
_Instrument->getDoubleValue("set-height-ft", true);
_Instrument->getDoubleValue("set-excursion-percent", true);
_antennaOffset = SGVec3d(_Instrument->getDoubleValue("antenna/x-offset-m"),
_Instrument->getDoubleValue("antenna/y-offset-m"),
_Instrument->getDoubleValue("antenna/z-offset-m"));
}
void
RadarAltimeter::update (double delta_time_sec)
{
if (!_sceneryLoaded->getBoolValue())
return;
if ( ! _serviceable_node->getBoolValue() ) {
_Instrument->setStringValue("status","");
return;
}
_time += delta_time_sec;
if (_time < _interval)
return;
_time -= _interval;
update_altitude();
updateSetHeight();
}
double
RadarAltimeter::getDistanceAntennaToHit(const SGVec3d& nearestHit) const
{
return norm(nearestHit - getCartAntennaPos());
}
void
RadarAltimeter::updateSetHeight()
{
double set_ht_ft = _Instrument->getDoubleValue("set-height-ft", 9999);
double set_excur = _Instrument->getDoubleValue("set-excursion-percent", 0);
if (set_ht_ft == 9999) {
_rad_alt_warning_node->setIntValue(9999);
return;
}
double radarAltFt = _min_radalt * SG_METER_TO_FEET;
if (radarAltFt < set_ht_ft * (100 - set_excur)/100)
_rad_alt_warning_node->setIntValue(-1);
else if (radarAltFt > set_ht_ft * (100 + set_excur)/100)
_rad_alt_warning_node->setIntValue(1);
else
_rad_alt_warning_node->setIntValue(0);
}
void
RadarAltimeter::update_altitude()
{
double el_limit = _Instrument->getDoubleValue("elev-limit", 15);
double el_step = _Instrument->getDoubleValue("elev-step-deg", 15);
double az_limit = _Instrument->getDoubleValue("az-limit-deg", 15);
double az_step = _Instrument->getDoubleValue("az-step-deg", 15);
double max_range = _Instrument->getDoubleValue("max-range-m", 1500);
double min_range = _Instrument->getDoubleValue("min-range-m", 0.001);
_min_radalt = max_range;
bool haveHit = false;
SGVec3d cartantennapos = getCartAntennaPos();
for(double brg = -az_limit; brg <= az_limit; brg += az_step){
for(double elev = el_limit; elev >= - el_limit; elev -= el_step){
SGVec3d userVec = rayVector(brg, elev);
SGVec3d nearestHit;
globals->get_scenery()->get_cart_ground_intersection(cartantennapos, userVec, nearestHit);
double measuredDistance = dist(cartantennapos, nearestHit);
if (measuredDistance >= min_range && measuredDistance <= max_range) {
if (measuredDistance < _min_radalt) {
_min_radalt = measuredDistance;
haveHit = true;
}
} // of hit within permissible range
} // of elevation step
} // of azimuth step
_Instrument->setDoubleValue("radar-altitude-ft", _min_radalt * SG_METER_TO_FEET);
if (!haveHit) {
_rad_alt_warning_node->setIntValue(9999);
}
}
SGVec3d
RadarAltimeter::getCartAntennaPos() const
{
double yaw, pitch, roll;
globals->get_aircraft_orientation(yaw, pitch, roll);
// Transform to the right coordinate frame, configuration is done in
// the x-forward, y-right, z-up coordinates (feet), computation
// in the simulation usual body x-forward, y-right, z-down coordinates
// (meters) )
// Transform the user position to the horizontal local coordinate system.
SGQuatd hlTrans = SGQuatd::fromLonLat(globals->get_aircraft_position());
// and postrotate the orientation of the user model wrt the horizontal
// local frame
hlTrans *= SGQuatd::fromYawPitchRollDeg(yaw,pitch,roll);
// The offset converted to the usual body fixed coordinate system
// rotated to the earth-fixed coordinates axis
SGVec3d ecfOffset = hlTrans.backTransform(_antennaOffset);
// Add the position offset of the user model to get the geocentered position
return globals->get_aircraft_position_cart() + ecfOffset;
}
SGVec3d RadarAltimeter::rayVector(double az, double el) const
{
double yaw, pitch, roll;
globals->get_aircraft_orientation(yaw, pitch, roll);
double tilt = _Instrument->getDoubleValue("tilt");
bool roll_stab = false,
pitch_stab = false;
SGQuatd offset = SGQuatd::fromYawPitchRollDeg(az, el + tilt, 0);
// Transform the antenna position to the horizontal local coordinate system.
SGQuatd hlTrans = SGQuatd::fromLonLat(globals->get_aircraft_position());
// and postrotate the orientation of the radar wrt the horizontal
// local frame
hlTrans *= SGQuatd::fromYawPitchRollDeg(yaw,
pitch_stab ? 0 :pitch,
roll_stab ? 0 : roll);
hlTrans *= offset;
// now rotate the rotation vector back into the
// earth centered frames coordinates
SGVec3d angleaxis(1,0,0);
return hlTrans.backTransform(angleaxis);
}
// Register the subsystem.
#if 0
SGSubsystemMgr::InstancedRegistrant<RadarAltimeter> registrantRadarAltimeter(
SGSubsystemMgr::FDM,
{{"instrumentation", SGSubsystemMgr::Dependency::HARD}});
#endif

View File

@@ -0,0 +1,69 @@
// Radar Altimeter
//
// Written by Vivian MEAZZA, started Feb 2008.
//
//
// Copyright (C) 2008 Vivain MEAZZA - vivian.meazza@lineone.net
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation; either version 2 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
//
//
#ifndef _INST_RADALT_HXX
#define _INST_RADALT_HXX
#include <simgear/structure/subsystem_mgr.hxx>
#include <simgear/props/props.hxx>
#include <simgear/math/SGMath.hxx>
class RadarAltimeter : public SGSubsystem
{
public:
RadarAltimeter ( SGPropertyNode *node );
virtual ~RadarAltimeter ();
// Subsystem API.
void init() override;
void update(double dt) override;
// Subsystem identification.
static const char* staticSubsystemClassId() { return "radar-altimeter"; }
private:
void update_altitude();
void updateSetHeight();
double getDistanceAntennaToHit(const SGVec3d& h) const;
SGVec3d getCartAntennaPos()const;
SGVec3d rayVector(double az, double el) const;
SGPropertyNode_ptr _Instrument;
SGPropertyNode_ptr _user_alt_agl_node;
SGPropertyNode_ptr _rad_alt_warning_node;
SGPropertyNode_ptr _serviceable_node;
SGPropertyNode_ptr _sceneryLoaded;
SGVec3d _antennaOffset; // in aircraft local XYZ frame
std::string _name;
int _num;
double _time;
double _interval;
double _min_radalt;
};
#endif // _INST_AGRADAR_HXX

File diff suppressed because it is too large Load Diff

Some files were not shown because too many files have changed in this diff Show More