Skip to content
Snippets Groups Projects
PrimaryFlightDisplay.cc 47.1 KiB
Newer Older
  • Learn to ignore specific revisions
  • #include "PrimaryFlightDisplay.h"
    #include "UASManager.h"
    
    #include <QDebug>
    #include <QRectF>
    #include <cmath>
    #include <QPen>
    #include <QPainter>
    #include <QPainterPath>
    #include <QResizeEvent>
    
    dongfang's avatar
    dongfang committed
    #include <QtCore/qmath.h>
    
    static const float LINEWIDTH = 0.0036f;
    static const float SMALL_TEXT_SIZE = 0.028f;
    static const float MEDIUM_TEXT_SIZE = SMALL_TEXT_SIZE*1.2f;
    static const float LARGE_TEXT_SIZE = MEDIUM_TEXT_SIZE*1.2f;
    
    static const bool SHOW_ZERO_ON_SCALES = true;
    
    
    // all in units of display height
    
    static const float ROLL_SCALE_RADIUS = 0.42f;
    static const float ROLL_SCALE_TICKMARKLENGTH = 0.04f;
    static const float ROLL_SCALE_MARKERWIDTH = 0.06f;
    static const float ROLL_SCALE_MARKERHEIGHT = 0.04f;
    
    // scale max. degrees
    
    static const int ROLL_SCALE_RANGE = 60;
    
    
    // fraction of height to translate for each degree of pitch.
    
    static const float PITCHTRANSLATION = 65;
    // 5 degrees for each line
    static const int PITCH_SCALE_RESOLUTION = 5;
    static const float PITCH_SCALE_MAJORWIDTH = 0.1f;
    
    Don Gagne's avatar
    Don Gagne committed
    static const float PITCH_SCALE_MINORWIDTH = 0.066f;
    
    
    // Beginning from PITCH_SCALE_WIDTHREDUCTION_FROM degrees of +/- pitch, the
    // width of the lines is reduced, down to PITCH_SCALE_WIDTHREDUCTION times
    // the normal width. This helps keep orientation in extreme attitudes.
    
    static const int PITCH_SCALE_WIDTHREDUCTION_FROM = 30;
    static const float PITCH_SCALE_WIDTHREDUCTION = 0.3f;
    
    static const int PITCH_SCALE_HALFRANGE = 15;
    
    
    // The number of degrees to either side of the heading to draw the compass disk.
    // 180 is valid, this will draw a complete disk. If the disk is partly clipped
    // away, less will do.
    
    
    static const int  COMPASS_DISK_MAJORTICK = 10;
    static const int  COMPASS_DISK_ARROWTICK = 45;
    static const int  COMPASS_DISK_RESOLUTION = 10;
    
    Don Gagne's avatar
    Don Gagne committed
    static const float COMPASS_DISK_MARKERWIDTH = 0.2f;
    static const float COMPASS_DISK_MARKERHEIGHT = 0.133f;
    
    static const int  CROSSTRACK_MAX = 1000;
    
    Don Gagne's avatar
    Don Gagne committed
    static const float CROSSTRACK_RADIUS = 0.6f;
    
    Don Gagne's avatar
    Don Gagne committed
    static const float TAPE_GAUGES_TICKWIDTH_MAJOR = 0.25f;
    static const float TAPE_GAUGES_TICKWIDTH_MINOR = 0.15f;
    
    
    // The altitude difference between top and bottom of scale
    
    static const int ALTIMETER_LINEAR_SPAN = 50;
    
    // every 5 meters there is a tick mark
    
    static const int ALTIMETER_LINEAR_RESOLUTION = 5;
    
    // every 10 meters there is a number
    
    static const int ALTIMETER_LINEAR_MAJOR_RESOLUTION = 10;
    
    
    // min. and max. vertical velocity
    
    static const int ALTIMETER_VVI_SPAN = 5;
    static const float ALTIMETER_VVI_WIDTH = 0.2f;
    
    
    // Now the same thing for airspeed!
    
    static const int AIRSPEED_LINEAR_SPAN = 15;
    static const int AIRSPEED_LINEAR_RESOLUTION = 1;
    static const int AIRSPEED_LINEAR_MAJOR_RESOLUTION = 5;
    
    dongfang's avatar
    dongfang committed
     * global fixed pens (and painters too?)
    
     * repaint on demand multiple canvases
     * multi implementation with shared model class
     */
    
    dongfang's avatar
    dongfang committed
    double PrimaryFlightDisplay_round(double value, int digits=0)
    
        return floor(value * pow(10.0, digits) + 0.5) / pow(10.0, digits);
    
    qreal PrimaryFlightDisplay_constrain(qreal value, qreal min, qreal max) {
        if (value<min) value=min;
        else if(value>max) value=max;
        return value;
    }
    
    
    const int PrimaryFlightDisplay::tickValues[] = {10, 20, 30, 45, 60};
    const QString PrimaryFlightDisplay::compassWindNames[] = {
        QString("N"),
        QString("NE"),
        QString("E"),
        QString("SE"),
        QString("S"),
        QString("SW"),
        QString("W"),
        QString("NW")
    };
    
    
    PrimaryFlightDisplay::PrimaryFlightDisplay(QWidget *parent) :
    
        _valuesChanged(false),
        _valuesLastPainted(QGC::groundTimeMilliseconds()),
    
    
    Don Gagne's avatar
    Don Gagne committed
        altitudeAMSL(std::numeric_limits<double>::quiet_NaN()),
    
        altitudeWGS84(std::numeric_limits<double>::quiet_NaN()),
    
    Don Gagne's avatar
    Don Gagne committed
        altitudeRelative(std::numeric_limits<double>::quiet_NaN()),
    
    Don Gagne's avatar
    Don Gagne committed
        groundSpeed(std::numeric_limits<double>::quiet_NaN()),
        airSpeed(std::numeric_limits<double>::quiet_NaN()),
        climbRate(std::numeric_limits<double>::quiet_NaN()),
    
        navigationCrosstrackError(std::numeric_limits<double>::quiet_NaN()),
    
    Don Gagne's avatar
    Don Gagne committed
        navigationTargetBearing(std::numeric_limits<double>::quiet_NaN()),
    
        layout(COMPASS_INTEGRATED),
        style(OVERLAY_HSI),
    
    dongfang's avatar
    dongfang committed
        redColor(QColor::fromHsvF(0, 0.75, 0.9)),
    
    dongfang's avatar
    dongfang committed
        amberColor(QColor::fromHsvF(0.12, 0.6, 1.0)),
        greenColor(QColor::fromHsvF(0.25, 0.8, 0.8)),
    
        lineWidth(2),
        fineLineWidth(1),
    
        instrumentEdgePen(QColor::fromHsvF(0, 0, 0.65, 0.5)),
    
    dongfang's avatar
    dongfang committed
        instrumentBackground(QColor::fromHsvF(0, 0, 0.3, 0.3)),
    
        instrumentOpagueBackground(QColor::fromHsvF(0, 0, 0.3, 1.0)),
    
        font("Bitstream Vera Sans"),
    
        refreshTimer(new QTimer(this))
    
    dongfang's avatar
    dongfang committed
    {
    
        setMinimumSize(120, 80);
        setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
    
    
    Lorenz Meier's avatar
    Lorenz Meier committed
        setActiveUAS(UASManager::instance()->getActiveUAS());
    
    
        // Connect with UAS signal
    
        connect(UASManager::instance(), SIGNAL(UASDeleted(UASInterface*)), this, SLOT(forgetUAS(UASInterface*)));
    
        connect(UASManager::instance(), SIGNAL(activeUASSet(UASInterface*)), this, SLOT(setActiveUAS(UASInterface*)));
    
        // Refresh timer
        refreshTimer->setInterval(updateInterval);
    
        connect(refreshTimer, SIGNAL(timeout()), this, SLOT(checkUpdate()));
    
    }
    
    PrimaryFlightDisplay::~PrimaryFlightDisplay()
    {
        refreshTimer->stop();
    }
    
    QSize PrimaryFlightDisplay::sizeHint() const
    {
    
        return QSize(width(), (int)(width() * 3.0f / 4.0f));
    
    void PrimaryFlightDisplay::showEvent(QShowEvent* event)
    {
    
        // React only to internal (pre-display) events
    
        QWidget::showEvent(event);
        refreshTimer->start(updateInterval);
        emit visibilityChanged(true);
    }
    
    void PrimaryFlightDisplay::hideEvent(QHideEvent* event)
    {
    
        // React only to internal (pre-display) events
    
        refreshTimer->stop();
        QWidget::hideEvent(event);
        emit visibilityChanged(false);
    }
    
    
    dongfang's avatar
    dongfang committed
    void PrimaryFlightDisplay::resizeEvent(QResizeEvent *e) {
        QWidget::resizeEvent(e);
    
        qreal size = e->size().width();
    
    
        lineWidth = PrimaryFlightDisplay_constrain(size*LINEWIDTH, 1, 6);
        fineLineWidth = PrimaryFlightDisplay_constrain(size*LINEWIDTH*2/3, 1, 2);
    
    dongfang's avatar
    dongfang committed
    
        instrumentEdgePen.setWidthF(fineLineWidth);
    
    
        smallTextSize = size * SMALL_TEXT_SIZE;
    
    dongfang's avatar
    dongfang committed
        mediumTextSize = size * MEDIUM_TEXT_SIZE;
        largeTextSize = size * LARGE_TEXT_SIZE;
    }
    
    dongfang's avatar
    dongfang committed
    void PrimaryFlightDisplay::paintEvent(QPaintEvent *event)
    {
        Q_UNUSED(event);
        doPaint();
    }
    
    void PrimaryFlightDisplay::checkUpdate()
    {
        if (uas && (_valuesChanged || (QGC::groundTimeMilliseconds() - _valuesLastPainted) > 260)) {
            update();
            _valuesChanged = false;
            _valuesLastPainted = QGC::groundTimeMilliseconds();
        }
    }
    
    
    void PrimaryFlightDisplay::forgetUAS(UASInterface* uas)
    
        if (this->uas != NULL && this->uas == uas) {
    
            // Disconnect any previously connected active MAV
    
            disconnect(this->uas, SIGNAL(attitudeChanged(UASInterface*,double,double,double,quint64)), this, SLOT(updateAttitude(UASInterface*, double, double, double, quint64)));
            disconnect(this->uas, SIGNAL(attitudeChanged(UASInterface*,int,double,double,double,quint64)), this, SLOT(updateAttitude(UASInterface*,int,double, double, double, quint64)));
            disconnect(this->uas, SIGNAL(speedChanged(UASInterface*, double, double, quint64)), this, SLOT(updateSpeed(UASInterface*, double, double, quint64)));
    
    Don Gagne's avatar
    Don Gagne committed
            disconnect(this->uas, SIGNAL(altitudeChanged(UASInterface*, double, double, double, double, quint64)), this, SLOT(updateAltitude(UASInterface*, double, double, double, double, quint64)));
    
            disconnect(this->uas, SIGNAL(navigationControllerErrorsChanged(UASInterface*, double, double, double)), this, SLOT(updateNavigationControllerErrors(UASInterface*, double, double, double)));
    
            disconnect(this->uas, &UASInterface::NavigationControllerDataChanged, this, &PrimaryFlightDisplay::UpdateNavigationControllerData);
    
    Don Gagne's avatar
    Don Gagne committed
        this->uas = NULL;
    
    }
    
    /**
     *
     * @param uas the UAS/MAV to monitor/display with the HUD
     */
    void PrimaryFlightDisplay::setActiveUAS(UASInterface* uas)
    {
    
    tstellanova's avatar
    tstellanova committed
        if (uas == this->uas)
            return; //no need to rewire
    
    
        // Disconnect the previous one (if any)
        forgetUAS(this->uas);
    
    
        if (uas) {
            // Now connect the new UAS
            // Setup communication
            connect(uas, SIGNAL(attitudeChanged(UASInterface*,double,double,double,quint64)), this, SLOT(updateAttitude(UASInterface*, double, double, double, quint64)));
            connect(uas, SIGNAL(attitudeChanged(UASInterface*,int,double,double,double,quint64)), this, SLOT(updateAttitude(UASInterface*,int,double, double, double, quint64)));
    
            connect(uas, SIGNAL(speedChanged(UASInterface*, double, double, quint64)), this, SLOT(updateSpeed(UASInterface*, double, double, quint64)));
    
            connect(uas, SIGNAL(altitudeChanged(UASInterface*, double, double, double, double, quint64)), this, SLOT(updateAltitude(UASInterface*, double, double, double, double, quint64)));
    
            connect(uas, SIGNAL(navigationControllerErrorsChanged(UASInterface*, double, double, double)), this, SLOT(updateNavigationControllerErrors(UASInterface*, double, double, double)));
    
            connect(uas, &UASInterface::NavigationControllerDataChanged, this, &PrimaryFlightDisplay::UpdateNavigationControllerData);
    
    
            // Set new UAS
            this->uas = uas;
        }
    }
    
    void PrimaryFlightDisplay::updateAttitude(UASInterface* uas, double roll, double pitch, double yaw, quint64 timestamp)
    {
        Q_UNUSED(uas);
        Q_UNUSED(timestamp);
    
    Don Gagne's avatar
    Don Gagne committed
                this->roll = std::numeric_limits<double>::quiet_NaN();
    
    
                float rolldeg = roll * (180.0 / M_PI);
    
    
    Don Gagne's avatar
    Don Gagne committed
                if (fabsf((float)roll - rolldeg) > 2.5f) {
    
                    _valuesChanged = true;
                }
    
                this->roll = rolldeg;
    
    Don Gagne's avatar
    Don Gagne committed
                this->pitch = std::numeric_limits<double>::quiet_NaN();
    
    
                float pitchdeg = pitch * (180.0 / M_PI);
    
    
    Don Gagne's avatar
    Don Gagne committed
                if (fabsf((float)pitch - pitchdeg) > 2.5f) {
    
                    _valuesChanged = true;
                }
    
                this->pitch = pitchdeg;
    
    Don Gagne's avatar
    Don Gagne committed
                this->heading = std::numeric_limits<double>::quiet_NaN();
    
                yaw = yaw * (180.0 / M_PI);
                if (yaw<0) yaw+=360;
    
    Don Gagne's avatar
    Don Gagne committed
                if (fabs(heading - yaw) > 10.0) {
    
                this->heading = yaw;
            }
    
    Lorenz Meier's avatar
    Lorenz Meier committed
    
    
    }
    
    void PrimaryFlightDisplay::updateAttitude(UASInterface* uas, int component, double roll, double pitch, double yaw, quint64 timestamp)
    {
        Q_UNUSED(component);
    
        this->updateAttitude(uas, roll, pitch, yaw, timestamp);
    
    void PrimaryFlightDisplay::updateSpeed(UASInterface* uas, double _groundSpeed, double _airSpeed, quint64 timestamp)
    
    Don Gagne's avatar
    Don Gagne committed
        if (fabs(groundSpeed - _groundSpeed) > 0.5) {
    
    Don Gagne's avatar
    Don Gagne committed
        if (fabs(airSpeed - _airSpeed) > 1.0) {
    
        groundSpeed = _groundSpeed;
        airSpeed = _airSpeed;
    
    void PrimaryFlightDisplay::updateAltitude(UASInterface* uas, double _altitudeAMSL, double _altitudeWGS84, double _altitudeRelative, double _climbRate, quint64 timestamp) {
    
        Q_UNUSED(uas);
        Q_UNUSED(timestamp);
    
    Don Gagne's avatar
    Don Gagne committed
        if (fabs(altitudeAMSL - _altitudeAMSL) > 0.5) {
    
    Don Gagne's avatar
    Don Gagne committed
        if (fabs(altitudeWGS84 - _altitudeWGS84) > 0.5) {
    
            _valuesChanged = true;
        }
    
    
    Don Gagne's avatar
    Don Gagne committed
        if (fabs(altitudeRelative - _altitudeRelative) > 0.5) {
    
    Don Gagne's avatar
    Don Gagne committed
        if (fabs(climbRate - _climbRate) > 0.5) {
    
        altitudeWGS84 = _altitudeWGS84;
    
        altitudeRelative = _altitudeRelative;
        climbRate = _climbRate;
    
    void PrimaryFlightDisplay::UpdateNavigationControllerData(UASInterface *uas, float navRoll, float navPitch, float navBearing, float targetBearing, float targetDistance) {
        Q_UNUSED(navRoll);
        Q_UNUSED(navPitch);
        Q_UNUSED(navBearing);
        Q_UNUSED(targetDistance);
        if (this->uas == uas) {
            this->navigationTargetBearing = targetBearing;
        }
    }
    
    
    void PrimaryFlightDisplay::updateNavigationControllerErrors(UASInterface* uas, double altitudeError, double speedError, double xtrackError) {
        Q_UNUSED(uas);
        this->navigationAltitudeError = altitudeError;
        this->navigationSpeedError = speedError;
        this->navigationCrosstrackError = xtrackError;
    }
    
    
    
    
    // TODO: Move to UAS. Real working implementation.
    bool PrimaryFlightDisplay::isAirplane() {
        if (!this->uas)
            return false;
        switch(this->uas->getSystemType()) {
        case MAV_TYPE_GENERIC:
        case MAV_TYPE_FIXED_WING:
        case MAV_TYPE_AIRSHIP:
        case MAV_TYPE_FLAPPING_WING:
            return true;
        default:
            return false;
        }
    }
    
    
    // TODO: Implement. Should return true when navigating.
    // That would be (APM) in AUTO and RTL modes.
    // This could forward to a virtual on UAS bool isNavigatingAutonomusly() or whatever.
    bool PrimaryFlightDisplay::shouldDisplayNavigationData() {
        return true;
    }
    
    
    void PrimaryFlightDisplay::drawTextCenter (
            QPainter& painter,
            QString text,
    
    dongfang's avatar
    dongfang committed
            float pixelSize,
    
    dongfang's avatar
    dongfang committed
        font.setPixelSize(pixelSize);
    
        painter.setFont(font);
    
        QFontMetrics metrics = QFontMetrics(font);
        QRect bounds = metrics.boundingRect(text);
        int flags = Qt::AlignCenter |  Qt::TextDontClip; // For some reason the bounds rect is too small!
    
        painter.drawText(x - bounds.width()/2, y - bounds.height()/2, bounds.width(), bounds.height(), flags, text);
    
    }
    
    void PrimaryFlightDisplay::drawTextLeftCenter (
            QPainter& painter,
            QString text,
    
    dongfang's avatar
    dongfang committed
            float pixelSize,
    
    dongfang's avatar
    dongfang committed
        font.setPixelSize(pixelSize);
    
        painter.setFont(font);
    
        QFontMetrics metrics = QFontMetrics(font);
        QRect bounds = metrics.boundingRect(text);
        int flags = Qt::AlignLeft | Qt::TextDontClip; // For some reason the bounds rect is too small!
    
        painter.drawText(x, y - bounds.height()/2, bounds.width(), bounds.height(), flags, text);
    
    }
    
    void PrimaryFlightDisplay::drawTextRightCenter (
            QPainter& painter,
            QString text,
    
    dongfang's avatar
    dongfang committed
            float pixelSize,
    
    dongfang's avatar
    dongfang committed
        font.setPixelSize(pixelSize);
    
        painter.setFont(font);
    
        QFontMetrics metrics = QFontMetrics(font);
        QRect bounds = metrics.boundingRect(text);
        int flags = Qt::AlignRight | Qt::TextDontClip; // For some reason the bounds rect is too small!
    
        painter.drawText(x - bounds.width(), y - bounds.height()/2, bounds.width(), bounds.height(), flags, text);
    
    }
    
    void PrimaryFlightDisplay::drawTextCenterTop (
            QPainter& painter,
            QString text,
    
    dongfang's avatar
    dongfang committed
            float pixelSize,
    
    dongfang's avatar
    dongfang committed
        font.setPixelSize(pixelSize);
    
        painter.setFont(font);
    
        QFontMetrics metrics = QFontMetrics(font);
        QRect bounds = metrics.boundingRect(text);
        int flags = Qt::AlignCenter | Qt::TextDontClip; // For some reason the bounds rect is too small!
    
        painter.drawText(x - bounds.width()/2, y+bounds.height(), bounds.width(), bounds.height(), flags, text);
    
    }
    
    void PrimaryFlightDisplay::drawTextCenterBottom (
            QPainter& painter,
            QString text,
    
    dongfang's avatar
    dongfang committed
            float pixelSize,
    
    dongfang's avatar
    dongfang committed
        font.setPixelSize(pixelSize);
    
        QFontMetrics metrics(font);
    
        QRect bounds = metrics.boundingRect(text);
        int flags = Qt::AlignCenter;
    
        painter.drawText(x - bounds.width()/2, y, bounds.width(), bounds.height(), flags, text);
    
    }
    
    void PrimaryFlightDisplay::drawInstrumentBackground(QPainter& painter, QRectF edge) {
        painter.setPen(instrumentEdgePen);
        painter.drawRect(edge);
    }
    
    void PrimaryFlightDisplay::fillInstrumentBackground(QPainter& painter, QRectF edge) {
        painter.setPen(instrumentEdgePen);
        painter.setBrush(instrumentBackground);
        painter.drawRect(edge);
        painter.setBrush(Qt::NoBrush);
    }
    
    
    dongfang's avatar
    dongfang committed
    void PrimaryFlightDisplay::fillInstrumentOpagueBackground(QPainter& painter, QRectF edge) {
        painter.setPen(instrumentEdgePen);
        painter.setBrush(instrumentOpagueBackground);
        painter.drawRect(edge);
        painter.setBrush(Qt::NoBrush);
    }
    
    
    qreal pitchAngleToTranslation(qreal viewHeight, float pitch) {
    
        return pitch * viewHeight / PITCHTRANSLATION;
    
    }
    
    void PrimaryFlightDisplay::drawAIAirframeFixedFeatures(QPainter& painter, QRectF area) {
        // red line from -7/10 to -5/10 half-width
        // red line from 7/10 to 5/10 half-width
        // red slanted line from -2/10 half-width to 0
        // red slanted line from 2/10 half-width to 0
        // red arrow thing under roll scale
        // prepareTransform(painter, width, height);
        painter.resetTransform();
        painter.translate(area.center());
    
        qreal w = area.width();
        qreal h = area.height();
    
    
    dongfang's avatar
    dongfang committed
        QPen pen;
    
        pen.setWidthF(lineWidth * 1.5f);
    
    dongfang's avatar
    dongfang committed
        pen.setColor(redColor);
        painter.setPen(pen);
    
        float length = 0.15f;
        float side = 0.5f;
    
    dongfang's avatar
    dongfang committed
        // The 2 lines at sides.
        painter.drawLine(QPointF(-side*w, 0), QPointF(-(side-length)*w, 0));
        painter.drawLine(QPointF(side*w, 0), QPointF((side-length)*w, 0));
    
    
        float rel = length/qSqrt(2.0f);
    
    dongfang's avatar
    dongfang committed
        // The gull
        painter.drawLine(QPointF(rel*w, rel*w/2), QPoint(0, 0));
        painter.drawLine(QPointF(-rel*w, rel*w/2), QPoint(0, 0));
    
        // The roll scale marker.
        QPainterPath markerPath(QPointF(0, -w*ROLL_SCALE_RADIUS+1));
        markerPath.lineTo(-h*ROLL_SCALE_MARKERWIDTH/2, -w*(ROLL_SCALE_RADIUS-ROLL_SCALE_MARKERHEIGHT)+1);
        markerPath.lineTo(h*ROLL_SCALE_MARKERWIDTH/2, -w*(ROLL_SCALE_RADIUS-ROLL_SCALE_MARKERHEIGHT)+1);
    
        markerPath.closeSubpath();
        painter.drawPath(markerPath);
    }
    
    
    inline qreal min4(qreal a, qreal b, qreal c, qreal d) {
        if(b<a) a=b;
        if(c<a) a=c;
        if(d<a) a=d;
        return a;
    }
    
    inline qreal max4(qreal a, qreal b, qreal c, qreal d) {
        if(b>a) a=b;
        if(c>a) a=c;
        if(d>a) a=d;
        return a;
    }
    
    
    void PrimaryFlightDisplay::drawAIGlobalFeatures(
            QPainter& painter,
    
            QRectF mainArea,
            QRectF paintArea) {
    
        float displayRoll = this->roll;
    
            displayRoll = 0;
    
    
        painter.translate(mainArea.center());
    
        qreal pitchPixels = pitchAngleToTranslation(mainArea.height(), pitch);
        qreal gradientEnd = pitchAngleToTranslation(mainArea.height(), 60);
    
        painter.rotate(-displayRoll);
    
        painter.translate(0, pitchPixels);
    
        // Calculate the radius of area we need to paint to cover all.
        QTransform rtx = painter.transform().inverted();
    
        QPointF topLeft = rtx.map(paintArea.topLeft());
        QPointF topRight = rtx.map(paintArea.topRight());
        QPointF bottomLeft = rtx.map(paintArea.bottomLeft());
        QPointF bottomRight = rtx.map(paintArea.bottomRight());
    
        // Just KISS... make a rectangluar basis.
        qreal minx = min4(topLeft.x(), topRight.x(), bottomLeft.x(), bottomRight.x());
        qreal maxx = max4(topLeft.x(), topRight.x(), bottomLeft.x(), bottomRight.x());
        qreal miny = min4(topLeft.y(), topRight.y(), bottomLeft.y(), bottomRight.y());
        qreal maxy = max4(topLeft.y(), topRight.y(), bottomLeft.y(), bottomRight.y());
    
        QPointF hzonLeft = QPoint(minx, 0);
        QPointF hzonRight = QPoint(maxx, 0);
    
        QPainterPath skyPath(hzonLeft);
        skyPath.lineTo(QPointF(minx, miny));
        skyPath.lineTo(QPointF(maxx, miny));
        skyPath.lineTo(hzonRight);
    
        QLinearGradient skyGradient(0, -gradientEnd, 0, 0);
    
        skyGradient.setColorAt(0, QColor::fromHsvF(0.6, 1.0, 0.7));
        skyGradient.setColorAt(1, QColor::fromHsvF(0.6, 0.25, 0.9));
        QBrush skyBrush(skyGradient);
        painter.fillPath(skyPath, skyBrush);
    
    
        QPainterPath groundPath(hzonRight);
        groundPath.lineTo(maxx, maxy);
        groundPath.lineTo(minx, maxy);
        groundPath.lineTo(hzonLeft);
    
        QLinearGradient groundGradient(0, gradientEnd, 0, 0);
    
        groundGradient.setColorAt(0, QColor::fromHsvF(0.25, 1, 0.5));
        groundGradient.setColorAt(1, QColor::fromHsvF(0.25, 0.25, 0.5));
        QBrush groundBrush(groundGradient);
        painter.fillPath(groundPath, groundBrush);
    
    
    dongfang's avatar
    dongfang committed
        QPen pen;
        pen.setWidthF(lineWidth);
        pen.setColor(greenColor);
        painter.setPen(pen);
    
    
        QPointF start(-mainArea.width(), 0);
        QPoint end(mainArea.width(), 0);
    
        painter.drawLine(start, end);
    }
    
    void PrimaryFlightDisplay::drawPitchScale(
            QPainter& painter,
            QRectF area,
    
    dongfang's avatar
    dongfang committed
            float intrusion,
    
        float displayPitch = this->pitch;
    
            displayPitch = 0;
    
    
    dongfang's avatar
    dongfang committed
        // The area should be quadratic but if not width is the major size.
        qreal w = area.width();
        if (w<area.height()) w = area.height();
    
    dongfang's avatar
    dongfang committed
        QPen pen;
        pen.setWidthF(lineWidth);
        pen.setColor(Qt::white);
        painter.setPen(pen);
    
    
        QTransform savedTransform = painter.transform();
    
        // find the mark nearest center
    
        int snap = qRound((double)(displayPitch/PITCH_SCALE_RESOLUTION))*PITCH_SCALE_RESOLUTION;
    
        int _min = snap-PITCH_SCALE_HALFRANGE;
        int _max = snap+PITCH_SCALE_HALFRANGE;
        for (int degrees=_min; degrees<=_max; degrees+=PITCH_SCALE_RESOLUTION) {
            bool isMajor = degrees % (PITCH_SCALE_RESOLUTION*2) == 0;
    
            float linewidth =  isMajor ? PITCH_SCALE_MAJORWIDTH : PITCH_SCALE_MINORWIDTH;
            if (abs(degrees) > PITCH_SCALE_WIDTHREDUCTION_FROM) {
                // we want: 1 at PITCH_SCALE_WIDTHREDUCTION_FROM and PITCH_SCALE_WIDTHREDUCTION at 90.
                // That is PITCH_SCALE_WIDTHREDUCTION + (1-PITCH_SCALE_WIDTHREDUCTION) * f(pitch)
                // where f(90)=0 and f(PITCH_SCALE_WIDTHREDUCTION_FROM)=1
                // f(p) = (90-p) * 1/(90-PITCH_SCALE_WIDTHREDUCTION_FROM)
                // or PITCH_SCALE_WIDTHREDUCTION + f(pitch) - f(pitch) * PITCH_SCALE_WIDTHREDUCTION
                // or PITCH_SCALE_WIDTHREDUCTION (1-f(pitch)) + f(pitch)
    
    Don Gagne's avatar
    Don Gagne committed
                int fromVertical = fabs(pitch>=0 ? 90-pitch : -90-pitch);
    
                float temp = fromVertical * 1/(90.0f-PITCH_SCALE_WIDTHREDUCTION_FROM);
                linewidth *= (PITCH_SCALE_WIDTHREDUCTION * (1-temp) + temp);
            }
    
    dongfang's avatar
    dongfang committed
    
    
            float shift = pitchAngleToTranslation(w, displayPitch-degrees);
    
    dongfang's avatar
    dongfang committed
    
            // TODO: Intrusion detection and evasion. That is, don't draw
            // where the compass has intruded.
    
    
    dongfang's avatar
    dongfang committed
            QPointF start(-linewidth*w, 0);
            QPointF end(linewidth*w, 0);
    
            painter.drawLine(start, end);
    
            if (isMajor && (drawNumbersLeft||drawNumbersRight)) {
                int displayDegrees = degrees;
                if(displayDegrees>90) displayDegrees = 180-displayDegrees;
                else if (displayDegrees<-90) displayDegrees = -180 - displayDegrees;
                if (SHOW_ZERO_ON_SCALES || degrees) {
    
                    QString s_number;
    
                        s_number.sprintf("-");
                    else
                        s_number.sprintf("%d", displayDegrees);
    
    dongfang's avatar
    dongfang committed
                    if (drawNumbersLeft)  drawTextRightCenter(painter, s_number, mediumTextSize, -PITCH_SCALE_MAJORWIDTH * w-10, 0);
                    if (drawNumbersRight) drawTextLeftCenter(painter, s_number, mediumTextSize, PITCH_SCALE_MAJORWIDTH * w+10, 0);
    
                }
            }
    
            painter.setTransform(savedTransform);
        }
    }
    
    void PrimaryFlightDisplay::drawRollScale(
            QPainter& painter,
            QRectF area,
            bool drawTicks,
            bool drawNumbers) {
    
    
        qreal w = area.width();
    
    dongfang's avatar
    dongfang committed
        if (w<area.height()) w = area.height();
    
    dongfang's avatar
    dongfang committed
        QPen pen;
        pen.setWidthF(lineWidth);
        pen.setColor(Qt::white);
        painter.setPen(pen);
    
        // We should really do these transforms but they are assumed done by caller:
    
        // painter.resetTransform();
        // painter.translate(area.center());
        // painter.rotate(roll);
    
    
        qreal _size = w * ROLL_SCALE_RADIUS*2;
    
    dongfang's avatar
    dongfang committed
        QRectF arcArea(-_size/2, - _size/2, _size, _size);
    
        painter.drawArc(arcArea, (90-ROLL_SCALE_RANGE)*16, ROLL_SCALE_RANGE*2*16);
        if (drawTicks) {
            int length = sizeof(tickValues)/sizeof(int);
            qreal previousRotation = 0;
            for (int i=0; i<length*2+1; i++) {
                int degrees = (i==length) ? 0 : (i>length) ?-tickValues[i-length-1] : tickValues[i];
                painter.rotate(degrees - previousRotation);
                previousRotation = degrees;
    
                QPointF start(0, -_size/2);
                QPointF end(0, -(1.0+ROLL_SCALE_TICKMARKLENGTH)*_size/2);
    
                painter.drawLine(start, end);
    
    
                if (SHOW_ZERO_ON_SCALES || degrees)
                    s_number.sprintf("%d", abs(degrees));
    
                if (drawNumbers) {
    
                    drawTextCenterBottom(painter, s_number, mediumTextSize, 0, -(ROLL_SCALE_RADIUS+ROLL_SCALE_TICKMARKLENGTH*1.7)*w);
    
                }
            }
        }
    }
    
    void PrimaryFlightDisplay::drawAIAttitudeScales(
            QPainter& painter,
    
    dongfang's avatar
    dongfang committed
            QRectF area,
            float intrusion
    
    dongfang's avatar
    dongfang committed
            ) {
    
        float displayRoll = this->roll;
    
            displayRoll = 0;
    
        // To save computations, we do these transformations once for both scales:
        painter.resetTransform();
        painter.translate(area.center());
    
        painter.rotate(-displayRoll);
    
        QTransform saved = painter.transform();
    
        drawRollScale(painter, area, true, true);
        painter.setTransform(saved);
    
    dongfang's avatar
    dongfang committed
        drawPitchScale(painter, area, intrusion, true, true);
    
    dongfang's avatar
    dongfang committed
    void PrimaryFlightDisplay::drawAICompassDisk(QPainter& painter, QRectF area, float halfspan) {
    
        float displayHeading = this->heading;
    
            displayHeading = 0;
    
        float start = displayHeading - halfspan;
        float end = displayHeading + halfspan;
    
    dongfang's avatar
    dongfang committed
    
    
    dongfang's avatar
    dongfang committed
        int firstTick = ceil(start / COMPASS_DISK_RESOLUTION) * COMPASS_DISK_RESOLUTION;
        int lastTick = floor(end / COMPASS_DISK_RESOLUTION) * COMPASS_DISK_RESOLUTION;
    
    
        float radius = area.width()/2;
        float innerRadius = radius * 0.96;
        painter.resetTransform();
    
    dongfang's avatar
    dongfang committed
        painter.setBrush(instrumentBackground);
        painter.setPen(instrumentEdgePen);
    
        painter.drawEllipse(area);
        painter.setBrush(Qt::NoBrush);
    
    
    dongfang's avatar
    dongfang committed
        QPen scalePen(Qt::black);
        scalePen.setWidthF(fineLineWidth);
    
    dongfang's avatar
    dongfang committed
        for (int tickYaw = firstTick; tickYaw <= lastTick; tickYaw += COMPASS_DISK_RESOLUTION) {
    
            int displayTick = tickYaw;
            if (displayTick < 0) displayTick+=360;
            else if (displayTick>=360) displayTick-=360;
    
            // yaw is in center.
    
            float off = tickYaw - displayHeading;
    
    dongfang's avatar
    dongfang committed
            if (off<=-180) off+= 360; else if (off>180) off -= 360;
    
    
            painter.translate(area.center());
            painter.rotate(off);
            bool drewArrow = false;
    
    dongfang's avatar
    dongfang committed
            bool isMajor = displayTick % COMPASS_DISK_MAJORTICK == 0;
    
            // If heading unknown, still draw marks but no numbers.
    
                    (displayTick==30 || displayTick==60 ||
    
    dongfang's avatar
    dongfang committed
                    displayTick==120 || displayTick==150 ||
                    displayTick==210 || displayTick==240 ||
    
                    displayTick==300 || displayTick==330)
            ) {
    
                // draw a number
                QString s_number;
                s_number.sprintf("%d", displayTick/10);
    
    dongfang's avatar
    dongfang committed
                painter.setPen(scalePen);
    
                drawTextCenter(painter, s_number, smallTextSize, 0, -innerRadius*0.75);
    
    dongfang's avatar
    dongfang committed
                if (displayTick % COMPASS_DISK_ARROWTICK == 0) {
    
                    if (displayTick!=0) {
                        QPainterPath markerPath(QPointF(0, -innerRadius*(1-COMPASS_DISK_MARKERHEIGHT/2)));
                        markerPath.lineTo(innerRadius*COMPASS_DISK_MARKERWIDTH/4, -innerRadius);
                        markerPath.lineTo(-innerRadius*COMPASS_DISK_MARKERWIDTH/4, -innerRadius);
                        markerPath.closeSubpath();
    
    dongfang's avatar
    dongfang committed
                        painter.setPen(scalePen);
    
                        painter.setBrush(Qt::SolidPattern);
                        painter.drawPath(markerPath);
                        painter.setBrush(Qt::NoBrush);
                        drewArrow = true;
                    }
    
                    // If heading unknown, still draw marks but no N S E W.
    
                    if (!isnan(this->heading) && displayTick%90 == 0) {
    
    dongfang's avatar
    dongfang committed
                        QString name = compassWindNames[displayTick / 45];
                        painter.setPen(scalePen);
                        drawTextCenter(painter, name, mediumTextSize, 0, -innerRadius*0.75);
    
    dongfang's avatar
    dongfang committed
                }
    
    dongfang's avatar
    dongfang committed
            // draw the scale lines. If an arrow was drawn, stay off from it.
    
    dongfang's avatar
    dongfang committed
            QPointF p_start = drewArrow ? QPoint(0, -innerRadius*0.94) : QPoint(0, -innerRadius);
            QPoint p_end = isMajor ? QPoint(0, -innerRadius*0.86) : QPoint(0, -innerRadius*0.90);
    
    dongfang's avatar
    dongfang committed
            painter.setPen(scalePen);
    
            painter.drawLine(p_start, p_end);
            painter.resetTransform();
        }
    
    
    dongfang's avatar
    dongfang committed
        painter.setPen(scalePen);
    
        painter.translate(area.center());
        QPainterPath markerPath(QPointF(0, -radius-2));
        markerPath.lineTo(radius*COMPASS_DISK_MARKERWIDTH/2,  -radius-radius*COMPASS_DISK_MARKERHEIGHT-2);
        markerPath.lineTo(-radius*COMPASS_DISK_MARKERWIDTH/2, -radius-radius*COMPASS_DISK_MARKERHEIGHT-2);
        markerPath.closeSubpath();
        painter.drawPath(markerPath);
    
    
    dongfang's avatar
    dongfang committed
        qreal digitalCompassYCenter = -radius*0.52;
        qreal digitalCompassHeight = radius*0.28;
    
        QPointF digitalCompassBottom(0, digitalCompassYCenter+digitalCompassHeight);
        QPointF  digitalCompassAbsoluteBottom = painter.transform().map(digitalCompassBottom);
    
        qreal digitalCompassUpshift = digitalCompassAbsoluteBottom.y()>height() ? digitalCompassAbsoluteBottom.y()-height() : 0;
    
        QRectF digitalCompassRect(-radius/3, -radius*0.52-digitalCompassUpshift, radius*2/3, radius*0.28);
    
    dongfang's avatar
    dongfang committed
        painter.drawRoundedRect(digitalCompassRect, instrumentEdgePen.widthF()*2/3, instrumentEdgePen.widthF()*2/3);
    
    dongfang's avatar
    dongfang committed
        QString s_digitalCompass;
    
            s_digitalCompass.sprintf("---");
        else {
        /* final safeguard for really stupid systems */
            int digitalCompassValue = static_cast<int>(qRound((double)heading)) % 360;
            s_digitalCompass.sprintf("%03d", digitalCompassValue);
        }
    
    dongfang's avatar
    dongfang committed
        QPen pen;
        pen.setWidthF(lineWidth);
        pen.setColor(Qt::white);
        painter.setPen(pen);
    
    dongfang's avatar
    dongfang committed
        drawTextCenter(painter, s_digitalCompass, largeTextSize, 0, -radius*0.38-digitalCompassUpshift);
    
        // We only display this navigation data if both the target bearing and crosstrack error are valid
        if (shouldDisplayNavigationData() && !isnan(navigationTargetBearing) && !isnan(navigationCrosstrackError)) {
    
            painter.resetTransform();
            painter.translate(area.center());
            // TODO : Sign might be wrong?
            // TODO : The case where error exceeds max. Truncate to max. and make that visible somehow.
            bool errorBeyondRadius = false;
    
    Don Gagne's avatar
    Don Gagne committed
            if (fabs(navigationCrosstrackError) > CROSSTRACK_MAX) {
    
                errorBeyondRadius = true;
                navigationCrosstrackError = navigationCrosstrackError>0 ? CROSSTRACK_MAX : -CROSSTRACK_MAX;
            }
    
            float r = radius * CROSSTRACK_RADIUS;
            float x = navigationCrosstrackError / CROSSTRACK_MAX * r;
            float y = qSqrt(r*r - x*x); // the positive y, there is also a negative.
    
    dongfang's avatar
    dongfang committed
    
    
            float sillyHeading = 0;
            float angle = sillyHeading - navigationTargetBearing; // TODO: sign.
            painter.rotate(-angle);
    
            QPen pen;
            pen.setWidthF(lineWidth);
            pen.setColor(Qt::black);
    
            if(errorBeyondRadius) {
                pen.setStyle(Qt::DotLine);
            }
    
            painter.setPen(pen);
    
            painter.drawLine(QPointF(x, y), QPointF(x, -y));
        }
    }
    
    
    void PrimaryFlightDisplay::drawAltimeter(
            QPainter& painter,
    
        float primaryAltitude = altitudeWGS84;
    
        float secondaryAltitude = std::numeric_limits<double>::quiet_NaN();
    
        fillInstrumentBackground(painter, area);
    
    dongfang's avatar
    dongfang committed
        QPen pen;
        pen.setWidthF(lineWidth);
    
    dongfang's avatar
    dongfang committed
    
    
    dongfang's avatar
    dongfang committed
        pen.setColor(Qt::white);
        painter.setPen(pen);
    
    dongfang's avatar
    dongfang committed
        float h = area.height();
        float w = area.width();
    
        float secondaryAltitudeBoxHeight = mediumTextSize * 2;
    
    dongfang's avatar
    dongfang committed
        // The height where we being with new tickmarks.
    
    dongfang's avatar
    dongfang committed
        float effectiveHalfHeight = h*0.45;
    
    
        // not yet implemented: Display of secondary altitude.
    
            effectiveHalfHeight -= secondaryAltitudeBoxHeight;
    
        float markerHalfHeight = mediumTextSize;
    
    dongfang's avatar
    dongfang committed
        float leftEdge = instrumentEdgePen.widthF()*2;
        float rightEdge = w-leftEdge;
        float tickmarkLeft = leftEdge;
        float tickmarkRightMajor = tickmarkLeft+TAPE_GAUGES_TICKWIDTH_MAJOR*w;
        float tickmarkRightMinor = tickmarkLeft+TAPE_GAUGES_TICKWIDTH_MINOR*w;
    
    dongfang's avatar
    dongfang committed
        float numbersLeft = 0.42*w;
    
    dongfang's avatar
    dongfang committed
        float markerTip = (tickmarkLeft*2+tickmarkRightMajor)/3;
    
    	float markerOffset = 0.2* markerHalfHeight;
    	float scaleCenterAltitude = isnan(primaryAltitude) ? 0 : primaryAltitude;
    	
    
    dongfang's avatar
    dongfang committed
        float start = scaleCenterAltitude - ALTIMETER_LINEAR_SPAN/2;
        float end = scaleCenterAltitude + ALTIMETER_LINEAR_SPAN/2;
    
        int firstTick = ceil(start / ALTIMETER_LINEAR_RESOLUTION) * ALTIMETER_LINEAR_RESOLUTION;
        int lastTick = floor(end / ALTIMETER_LINEAR_RESOLUTION) * ALTIMETER_LINEAR_RESOLUTION;
        for (int tickAlt = firstTick; tickAlt <= lastTick; tickAlt += ALTIMETER_LINEAR_RESOLUTION) {
    
    dongfang's avatar
    dongfang committed
            float y = (tickAlt-scaleCenterAltitude)*effectiveHalfHeight/(ALTIMETER_LINEAR_SPAN/2);
            bool isMajor = tickAlt % ALTIMETER_LINEAR_MAJOR_RESOLUTION == 0;
    
            painter.resetTransform();
            painter.translate(area.left(), area.center().y() - y);
    
    dongfang's avatar
    dongfang committed
            pen.setColor(tickAlt<0 ? redColor : Qt::white);
            painter.setPen(pen);
            if (isMajor) {
                painter.drawLine(tickmarkLeft, 0, tickmarkRightMajor, 0);
    
    dongfang's avatar
    dongfang committed
                s_alt.sprintf("%d", abs(tickAlt));
    
    dongfang's avatar
    dongfang committed
                drawTextLeftCenter(painter, s_alt, mediumTextSize, numbersLeft, 0);
    
    dongfang's avatar
    dongfang committed
            } else {
                painter.drawLine(tickmarkLeft, 0, tickmarkRightMinor, 0);
    
        QPainterPath primaryMarkerPath(QPoint(markerTip, 0));
    	primaryMarkerPath.lineTo(markerTip + markerHalfHeight, markerHalfHeight);
    	primaryMarkerPath.lineTo(rightEdge, markerHalfHeight);
    	primaryMarkerPath.lineTo(rightEdge, -markerHalfHeight);
    	primaryMarkerPath.lineTo(markerTip + markerHalfHeight, -markerHalfHeight);
    	primaryMarkerPath.closeSubpath();
    
    	QPainterPath secondaryMarkerPath(QPoint(markerTip + markerHalfHeight, markerHalfHeight + markerOffset));
    	if (!isnan(climbRate)) {
    		secondaryMarkerPath.lineTo(markerTip + markerHalfHeight, 2 * markerHalfHeight + markerOffset);
    		secondaryMarkerPath.lineTo(rightEdge, 2 * markerHalfHeight + markerOffset);
    		secondaryMarkerPath.lineTo(rightEdge, 1 * markerHalfHeight + markerOffset);
    		secondaryMarkerPath.closeSubpath();
    	}
    
    	painter.resetTransform();
    
        painter.translate(area.left(), area.center().y());
    
    dongfang's avatar
    dongfang committed
    
    
    dongfang's avatar
    dongfang committed
        pen.setWidthF(lineWidth);
    
    dongfang's avatar
    dongfang committed
        pen.setColor(Qt::white);
        painter.setPen(pen);
    
    
        painter.drawPath(primaryMarkerPath);
    	if (!isnan(climbRate)) painter.drawPath(secondaryMarkerPath);
    
    dongfang's avatar
    dongfang committed
        pen.setColor(Qt::white);
        painter.setPen(pen);