Skip to content
HUD.cc 49.2 KiB
Newer Older
pixhawk's avatar
pixhawk committed
/*=====================================================================

QGroundControl Open Source Ground Control Station
pixhawk's avatar
pixhawk committed

(c) 2009, 2010 QGROUNDCONTROL PROJECT <http://www.qgroundcontrol.org>
pixhawk's avatar
pixhawk committed

This file is part of the QGROUNDCONTROL project
pixhawk's avatar
pixhawk committed

    QGROUNDCONTROL is free software: you can redistribute it and/or modify
pixhawk's avatar
pixhawk committed
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    QGROUNDCONTROL is distributed in the hope that it will be useful,
pixhawk's avatar
pixhawk committed
    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 QGROUNDCONTROL. If not, see <http://www.gnu.org/licenses/>.
pixhawk's avatar
pixhawk committed

======================================================================*/

/**
 * @file
 *   @brief Head Up Display (HUD)
 *
 *   @author Lorenz Meier <mavteam@student.ethz.ch>
 *
 */

#include <QPaintEvent>
pixhawk's avatar
pixhawk committed
#include <QDebug>
pixhawk's avatar
pixhawk committed
#include <cmath>
pixhawk's avatar
pixhawk committed
#include <qmath.h>
pixhawk's avatar
pixhawk committed
#include <limits>
pixhawk's avatar
pixhawk committed

#include "UASManager.h"
pixhawk's avatar
pixhawk committed
#include "HUD.h"
Don Gagne's avatar
Don Gagne committed
#include "QGCFileDialog.h"
pixhawk's avatar
pixhawk committed

/**
 * @warning The HUD widget will not start painting its content automatically
 *          to update the view, start the auto-update by calling HUD::start().
 *
 * @param width
 * @param height
 * @param parent
 */
HUD::HUD(int width, int height, QWidget* parent)
    : QLabel(parent),
Don Gagne's avatar
Don Gagne committed
      image(NULL),
      uas(NULL),
      yawInt(0.0f),
      mode(tr("UNKNOWN MODE")),
      state(tr("UNKNOWN STATE")),
      fuelStatus(tr("00.0V (00m:00s)")),
      xCenterOffset(0.0f),
      yCenterOffset(0.0f),
      vwidth(200.0f),
      vheight(150.0f),
      vGaugeSpacing(65.0f),
      vPitchPerDeg(6.0f), ///< 4 mm y translation per degree)
      rawBuffer1(NULL),
      rawBuffer2(NULL),
      rawImage(NULL),
      rawLastIndex(0),
      rawExpectedBytes(0),
      bytesPerLine(1),
      imageStarted(false),
      receivedDepth(8),
      receivedChannels(1),
      receivedWidth(640),
      receivedHeight(480),
      warningBlinkRate(5),
      refreshTimer(new QTimer(this)),
      noCamera(true),
      hardwareAcceleration(true),
      strongStrokeWidth(1.5f),
      normalStrokeWidth(1.0f),
      fineStrokeWidth(0.5f),
      waypointName(""),
      roll(0.0f),
      pitch(0.0f),
      yaw(0.0f),
      rollLP(0.0f),
      pitchLP(0.0f),
      yawLP(0.0f),
      yawDiff(0.0f),
      xPos(0.0),
      yPos(0.0),
      zPos(0.0),
      xSpeed(0.0),
      ySpeed(0.0),
      zSpeed(0.0),
      lastSpeedUpdate(0),
      totalSpeed(0.0),
      totalAcc(0.0),
      lat(0.0),
      lon(0.0),
      alt(0.0),
      load(0.0f),
      offlineDirectory(""),
      nextOfflineImage(""),
      HUDInstrumentsEnabled(false),
Don Gagne's avatar
Don Gagne committed
      imageLoggingEnabled(false),
      xImageFactor(1.0),
Don Gagne's avatar
Don Gagne committed
      imageRequested(false)
pixhawk's avatar
pixhawk committed
{
pixhawk's avatar
pixhawk committed
    // Set auto fill to false
    setAutoFillBackground(false);

    // Set minimum size
    setMinimumSize(80, 60);
    // Set preferred size
    setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
LM's avatar
LM committed
    scalingFactor = this->width()/vwidth;
    // Set up the initial color theme. This can be updated by a styleChanged
    // signal from MainWindow.
    styleChanged(((MainWindow*)parent)->getStyle());
pixhawk's avatar
pixhawk committed

    // Refresh timer
    refreshTimer->setInterval(updateInterval);
    connect(refreshTimer, SIGNAL(timeout()), this, SLOT(repaint()));
pixhawk's avatar
pixhawk committed

    // Resize to correct size and fill with image
    QWidget::resize(this->width(), this->height());
pixhawk's avatar
pixhawk committed

    fontDatabase = QFontDatabase();
    const QString fontFileName = ":/general/vera.ttf"; ///< Font file is part of the QRC file and compiled into the app
    const QString fontFamilyName = "Bitstream Vera Sans";
    if(!QFile::exists(fontFileName)) qDebug() << "ERROR! font file: " << fontFileName << " DOES NOT EXIST!";

    fontDatabase.addApplicationFont(fontFileName);
    font = fontDatabase.font(fontFamilyName, "Roman", qMax(5,(int)(10.0f*scalingFactor*1.2f+0.5f)));
    QFont* fontPtr = &font;
    if (!fontPtr) {
        qDebug() << "ERROR! FONT NOT LOADED!";
        if (font.family() != fontFamilyName) qDebug() << "ERROR! WRONG FONT LOADED: " << fontFamilyName;
    }
pixhawk's avatar
pixhawk committed

    // Connect the themeChanged signal from the MainWindow to this widget, so it
    // can change it's styling accordingly.
    connect((MainWindow*)parent, SIGNAL(styleChanged(MainWindow::QGC_MAINWINDOW_STYLE)),
            this, SLOT(styleChanged(MainWindow::QGC_MAINWINDOW_STYLE)));

pixhawk's avatar
pixhawk committed
    // Connect with UAS
    connect(UASManager::instance(), SIGNAL(activeUASSet(UASInterface*)), this, SLOT(setActiveUAS(UASInterface*)));

    createActions();

    if (UASManager::instance()->getActiveUAS() != NULL) setActiveUAS(UASManager::instance()->getActiveUAS());
pixhawk's avatar
pixhawk committed
}

HUD::~HUD()
{
    refreshTimer->stop();
pixhawk's avatar
pixhawk committed
}

    return QSize(width(), (width()*3.0f)/4);
void HUD::styleChanged(MainWindow::QGC_MAINWINDOW_STYLE newTheme)
{
    // Generate a background image that's dependent on the current color scheme.
    QImage fill = QImage(width(), height(), QImage::Format_Indexed8);
    if (newTheme == MainWindow::QGC_MAINWINDOW_STYLE_LIGHT)
    {
        fill.fill(255);
    }
    else
    {
        fill.fill(0);
    }
    glImage = QGLWidget::convertToGLFormat(fill);

    // Now set the other default colors based on the current color scheme.
    if (newTheme == MainWindow::QGC_MAINWINDOW_STYLE_LIGHT)
    {
        defaultColor = QColor(0x01, 0x47, 0x01);
        setPointColor = QColor(0x82, 0x17, 0x82);
        warningColor = Qt::darkYellow;
        criticalColor = Qt::darkRed;
        infoColor = QColor(0x07, 0x82, 0x07);
        fuelColor = criticalColor;
    }
    else
    {
        defaultColor = QColor(70, 200, 70);
        setPointColor = QColor(200, 20, 200);
        warningColor = Qt::yellow;
        criticalColor = Qt::red;
        infoColor = QColor(20, 200, 20);
        fuelColor = criticalColor;
    }
}

void HUD::showEvent(QShowEvent* event)
{
    // React only to internal (pre-display)
    // events
    refreshTimer->start(updateInterval);
LM's avatar
LM committed
    emit visibilityChanged(true);
}

void HUD::hideEvent(QHideEvent* event)
{
    // React only to internal (pre-display)
    // events
    refreshTimer->stop();
LM's avatar
LM committed
    emit visibilityChanged(false);
pixhawk's avatar
pixhawk committed
}

void HUD::contextMenuEvent (QContextMenuEvent* event)
{
    QMenu menu(this);
    // Update actions
    enableHUDAction->setChecked(HUDInstrumentsEnabled);
    enableVideoAction->setChecked(videoEnabled);

    menu.addAction(enableHUDAction);
    //menu.addAction(selectHUDColorAction);
    menu.addAction(enableVideoAction);
    menu.addAction(selectOfflineDirectoryAction);
    menu.addAction(selectSaveDirectoryAction);
    menu.exec(event->globalPos());
}

void HUD::createActions()
{
    enableHUDAction = new QAction(tr("Enable HUD"), this);
    enableHUDAction->setStatusTip(tr("Show the HUD instruments in this window"));
    enableHUDAction->setCheckable(true);
    enableHUDAction->setChecked(HUDInstrumentsEnabled);
    connect(enableHUDAction, SIGNAL(triggered(bool)), this, SLOT(enableHUDInstruments(bool)));

    enableVideoAction = new QAction(tr("Enable Video Live feed"), this);
    enableVideoAction->setStatusTip(tr("Show the video live feed"));
    enableVideoAction->setCheckable(true);
    enableVideoAction->setChecked(videoEnabled);
    connect(enableVideoAction, SIGNAL(triggered(bool)), this, SLOT(enableVideo(bool)));

    selectOfflineDirectoryAction = new QAction(tr("Load image log"), this);
    selectOfflineDirectoryAction->setStatusTip(tr("Load previously logged images into simulation / replay"));
    connect(selectOfflineDirectoryAction, SIGNAL(triggered()), this, SLOT(selectOfflineDirectory()));

    selectSaveDirectoryAction = new QAction(tr("Save images to directory"), this);
    selectSaveDirectoryAction->setStatusTip(tr("Save images from image stream to a directory"));
    selectSaveDirectoryAction->setCheckable(true);
    connect(selectSaveDirectoryAction, SIGNAL(triggered(bool)), this, SLOT(saveImages(bool)));
pixhawk's avatar
pixhawk committed
/**
 *
 * @param uas the UAS/MAV to monitor/display with the HUD
 */
void HUD::setActiveUAS(UASInterface* uas)
{
    if (this->uas != NULL) {
pixhawk's avatar
pixhawk committed
        // Disconnect any previously connected active MAV
dongfang's avatar
dongfang committed
        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(batteryChanged(UASInterface*, double, double, double, int)), this, SLOT(updateBattery(UASInterface*, double, double, double, int)));
        disconnect(this->uas, SIGNAL(statusChanged(UASInterface*,QString,QString)), this, SLOT(updateState(UASInterface*,QString)));
        disconnect(this->uas, SIGNAL(modeChanged(int,QString,QString)), this, SLOT(updateMode(int,QString,QString)));
        disconnect(this->uas, SIGNAL(heartbeat(UASInterface*)), this, SLOT(receiveHeartbeat(UASInterface*)));
        disconnect(this->uas, SIGNAL(localPositionChanged(UASInterface*,double,double,double,quint64)), this, SLOT(updateLocalPosition(UASInterface*,double,double,double,quint64)));
        disconnect(this->uas, SIGNAL(globalPositionChanged(UASInterface*,double,double,double,double,quint64)), this, SLOT(updateGlobalPosition(UASInterface*,double,double,double,double,quint64)));
        disconnect(this->uas, SIGNAL(velocityChanged_NEDspeedChanged(UASInterface*,double,double,double,quint64)), this, SLOT(updateSpeed(UASInterface*,double,double,double,quint64)));
        disconnect(this->uas, SIGNAL(waypointSelected(int,int)), this, SLOT(selectWaypoint(int, int)));
pixhawk's avatar
pixhawk committed

        UAS* u = dynamic_cast<UAS*>(this->uas);
Lorenz Meier's avatar
Lorenz Meier committed
            disconnect(u, SIGNAL(imageReady(UASInterface*)), this, SLOT(copyImage(UASInterface*)));
        // 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)));
dongfang's avatar
dongfang committed
        connect(uas, SIGNAL(batteryChanged(UASInterface*, double, double, double, int)), this, SLOT(updateBattery(UASInterface*, double, double, double, int)));
        connect(uas, SIGNAL(statusChanged(UASInterface*,QString,QString)), this, SLOT(updateState(UASInterface*,QString)));
        connect(uas, SIGNAL(modeChanged(int,QString,QString)), this, SLOT(updateMode(int,QString,QString)));
        connect(uas, SIGNAL(heartbeat(UASInterface*)), this, SLOT(receiveHeartbeat(UASInterface*)));

        connect(uas, SIGNAL(localPositionChanged(UASInterface*,double,double,double,quint64)), this, SLOT(updateLocalPosition(UASInterface*,double,double,double,quint64)));
        connect(uas, SIGNAL(globalPositionChanged(UASInterface*,double,double,double,double,quint64)), this, SLOT(updateGlobalPosition(UASInterface*,double,double,double,double,quint64)));
        connect(uas, SIGNAL(velocityChanged_NED(UASInterface*,double,double,double,quint64)), this, SLOT(updateSpeed(UASInterface*,double,double,double,quint64)));
        connect(uas, SIGNAL(waypointSelected(int,int)), this, SLOT(selectWaypoint(int, int)));

        // Try to connect the image link
Lorenz Meier's avatar
Lorenz Meier committed
        UAS* u = qobject_cast<UAS*>(uas);
Lorenz Meier's avatar
Lorenz Meier committed
            connect(u, SIGNAL(imageReady(UASInterface*)), this, SLOT(copyImage(UASInterface*)));
pixhawk's avatar
pixhawk committed
}

//void HUD::updateAttitudeThrustSetPoint(UASInterface* uas, double rollDesired, double pitchDesired, double yawDesired, double thrustDesired, quint64 msec)
//{
////    updateValue(uas, "roll desired", rollDesired, msec);
////    updateValue(uas, "pitch desired", pitchDesired, msec);
////    updateValue(uas, "yaw desired", yawDesired, msec);
////    updateValue(uas, "thrust desired", thrustDesired, msec);
//}
pixhawk's avatar
pixhawk committed

void HUD::updateAttitude(UASInterface* uas, double roll, double pitch, double yaw, quint64 timestamp)
{
    Q_UNUSED(uas);
    Q_UNUSED(timestamp);
    if (!isnan(roll) && !isinf(roll) && !isnan(pitch) && !isinf(pitch) && !isnan(yaw) && !isinf(yaw))
    {
        this->roll = roll;
        this->pitch = pitch*3.35f; // Constant here is the 'focal length' of the projection onto the plane
        this->yaw = yaw;
    }
pixhawk's avatar
pixhawk committed
}

void HUD::updateAttitude(UASInterface* uas, int component, double roll, double pitch, double yaw, quint64 timestamp)
{
    Q_UNUSED(uas);
    Q_UNUSED(timestamp);
    if (!isnan(roll) && !isinf(roll) && !isnan(pitch) && !isinf(pitch) && !isnan(yaw) && !isinf(yaw))
    {
        attitudes.insert(component, QVector3D(roll, pitch*3.35f, yaw)); // Constant here is the 'focal length' of the projection onto the plane
    }
dongfang's avatar
dongfang committed
void HUD::updateBattery(UASInterface* uas, double voltage, double current, double percent, int seconds)
pixhawk's avatar
pixhawk committed
{
dongfang's avatar
dongfang committed
    Q_UNUSED(current);
    fuelStatus = tr("BAT [%1% | %2V]").arg(percent, 2, 'f', 0, QChar('0')).arg(voltage, 4, 'f', 1, QChar('0'));
    if (percent < 20.0f) {
pixhawk's avatar
pixhawk committed
        fuelColor = warningColor;
    } else if (percent < 10.0f) {
pixhawk's avatar
pixhawk committed
        fuelColor = criticalColor;
pixhawk's avatar
pixhawk committed
        fuelColor = infoColor;
    }
}

void HUD::receiveHeartbeat(UASInterface*)
{
}

void HUD::updateThrust(UASInterface* uas, double thrust)
pixhawk's avatar
pixhawk committed
{
    Q_UNUSED(uas);
    Q_UNUSED(thrust);
//    updateValue(uas, "thrust", thrust, MG::TIME::getGroundTimeNow());
pixhawk's avatar
pixhawk committed
}

void HUD::updateLocalPosition(UASInterface* uas,double x,double y,double z,quint64 timestamp)
{
    Q_UNUSED(uas);
    Q_UNUSED(timestamp);
    this->xPos = x;
    this->yPos = y;
    this->zPos = z;
pixhawk's avatar
pixhawk committed
}

void HUD::updateGlobalPosition(UASInterface* uas,double lat, double lon, double altitudeAMSL, double altitudeWGS84, quint64 timestamp)
pixhawk's avatar
pixhawk committed
{
    Q_UNUSED(altitudeAMSL);
    Q_UNUSED(timestamp);
    this->lat = lat;
    this->lon = lon;
    this->alt = altitudeWGS84;
pixhawk's avatar
pixhawk committed
}

void HUD::updateSpeed(UASInterface* uas,double x,double y,double z,quint64 timestamp)
{
    Q_UNUSED(uas);
    Q_UNUSED(timestamp);
    this->xSpeed = x;
    this->ySpeed = y;
    this->zSpeed = z;
pixhawk's avatar
pixhawk committed
    double newTotalSpeed = sqrt(xSpeed*xSpeed + ySpeed*ySpeed + zSpeed*zSpeed);
    totalAcc = (newTotalSpeed - totalSpeed) / ((double)(lastSpeedUpdate - timestamp)/1000.0);
    totalSpeed = newTotalSpeed;
pixhawk's avatar
pixhawk committed
}

/**
 * Updates the current system state, but only if the uas matches the currently monitored uas.
 *
 * @param uas the system the state message originates from
 * @param state short state text, displayed in HUD
 */
void HUD::updateState(UASInterface* uas,QString state)
pixhawk's avatar
pixhawk committed
{
    // Only one UAS is connected at a time
    Q_UNUSED(uas);
    this->state = state;
}

/**
 * Updates the current system mode, but only if the uas matches the currently monitored uas.
 *
 * @param uas the system the state message originates from
 * @param mode short mode text, displayed in HUD
 */
void HUD::updateMode(int id,QString mode, QString description)
    // Only one UAS is connected at a time
    this->mode = mode;
pixhawk's avatar
pixhawk committed
}

void HUD::updateLoad(UASInterface* uas, double load)
{
    Q_UNUSED(uas);
    this->load = load;
    //updateValue(uas, "load", load, MG::TIME::getGroundTimeNow());
pixhawk's avatar
pixhawk committed
}

/**
 * @param y coordinate in pixels to be converted to reference mm units
 * @return the screen coordinate relative to the QGLWindow origin
 */
float HUD::refToScreenX(float x)
{
    //qDebug() << "sX: " << (scalingFactor * x) << "Orig:" << x;
pixhawk's avatar
pixhawk committed
    return (scalingFactor * x);
}
/**
 * @param x coordinate in pixels to be converted to reference mm units
 * @return the screen coordinate relative to the QGLWindow origin
 */
float HUD::refToScreenY(float y)
{
    //qDebug() << "sY: " << (scalingFactor * y);
    return (scalingFactor * y);
}

/**
 * Paint text on top of the image and OpenGL drawings
 *
 * @param text chars to write
 * @param color text color
 * @param fontSize text size in mm
 * @param refX position in reference units (mm of the real instrument). This is relative to the measurement unit position, NOT in pixels.
 * @param refY position in reference units (mm of the real instrument). This is relative to the measurement unit position, NOT in pixels.
 */
void HUD::paintText(QString text, QColor color, float fontSize, float refX, float refY, QPainter* painter)
{
    QPen prevPen = painter->pen();
    float pPositionX = refToScreenX(refX) - (fontSize*scalingFactor*0.072f);
    float pPositionY = refToScreenY(refY) - (fontSize*scalingFactor*0.212f);

    QFont font("Bitstream Vera Sans");
    // Enforce minimum font size of 5 pixels
    int fSize = qMax(5, (int)(fontSize*scalingFactor*1.26f));
pixhawk's avatar
pixhawk committed

    QFontMetrics metrics = QFontMetrics(font);
    int border = qMax(4, metrics.leading());
    QRect rect = metrics.boundingRect(0, 0, width() - 2*border, int(height()*0.125),
                                      Qt::AlignLeft | Qt::TextWordWrap, text);
    painter->setPen(color);
    painter->setFont(font);
    painter->setRenderHint(QPainter::TextAntialiasing);
    painter->drawText(pPositionX, pPositionY,
                      rect.width(), rect.height(),
                      Qt::AlignCenter | Qt::TextWordWrap, text);
    painter->setPen(prevPen);
}

/**
 * @param referencePositionX horizontal position in the reference mm-unit space
 * @param referencePositionY horizontal position in the reference mm-unit space
 * @param referenceWidth width in the reference mm-unit space
 * @param referenceHeight width in the reference mm-unit space
 */
void HUD::setupGLView(float referencePositionX, float referencePositionY, float referenceWidth, float referenceHeight)
{
    Q_UNUSED(referencePositionX);
    Q_UNUSED(referencePositionY);
    Q_UNUSED(referenceWidth);
    Q_UNUSED(referenceHeight);
#if 0
    // code ifdef'ed out but left in to silence warnings
pixhawk's avatar
pixhawk committed
    int pixelWidth  = (int)(referenceWidth * scalingFactor);
    int pixelHeight = (int)(referenceHeight * scalingFactor);
    // Translate and scale the GL view in the virtual reference coordinate units on the screen
    int pixelPositionX = (int)((referencePositionX * scalingFactor) + xCenterOffset);
    int pixelPositionY = this->height() - (referencePositionY * scalingFactor) + yCenterOffset - pixelHeight;
pixhawk's avatar
pixhawk committed
}

void HUD::paintRollPitchStrips()
{
}


pixhawk's avatar
pixhawk committed
void HUD::paintEvent(QPaintEvent *event)
pixhawk's avatar
pixhawk committed
{
    paintHUD();
    if (isVisible()) {
        //    static quint64 interval = 0;
        //    qDebug() << "INTERVAL:" << MG::TIME::getGroundTimeNow() - interval << __FILE__ << __LINE__;
        //    interval = MG::TIME::getGroundTimeNow();
lm's avatar
lm committed

        qDebug() << "EVENTLOOP:" << __FILE__ << __LINE__;
        // Read out most important values to limit hash table lookups
        // Low-pass roll, pitch and yaw
        rollLP = roll;//rollLP * 0.2f + 0.8f * roll;
        pitchLP = pitch;//pitchLP * 0.2f + 0.8f * pitch;
        yawLP = (!isinf(yaw) && !isnan(yaw)) ? yaw : yawLP;//yawLP * 0.2f + 0.8f * yaw;
pixhawk's avatar
pixhawk committed

        // Translate for yaw
        const float maxYawTrans = 60.0f;
        float newYawDiff = yawDiff;
        if (isinf(newYawDiff)) newYawDiff = yawDiff;
        if (newYawDiff > M_PI) newYawDiff = newYawDiff - M_PI;
        if (newYawDiff < -M_PI) newYawDiff = newYawDiff + M_PI;
        newYawDiff = yawDiff * 0.8 + newYawDiff * 0.2;
        yawDiff = newYawDiff;
        yawInt += newYawDiff;
        if (yawInt > M_PI) yawInt = (float)M_PI;
        if (yawInt < -M_PI) yawInt = (float)-M_PI;
        float yawTrans = yawInt * (float)maxYawTrans;
        yawInt *= 0.6f;
lm's avatar
lm committed

        if ((yawTrans < 5.0) && (yawTrans > -5.0)) yawTrans = 0;
lm's avatar
lm committed

        // Negate to correct direction
        yawTrans = -yawTrans;
lm's avatar
lm committed

        //qDebug() << "yaw translation" << yawTrans << "integral" << yawInt << "difference" << yawDiff << "yaw" << yaw;
        // Update scaling factor
        // adjust scaling to fit both horizontally and vertically
        scalingFactor = this->width()/vwidth;
        double scalingFactorH = this->height()/vheight;
        if (scalingFactorH < scalingFactor) scalingFactor = scalingFactorH;
Bryant's avatar
Bryant committed
        // And if either video or the data stream is enabled, draw the next frame.
LM's avatar
LM committed
            xImageFactor = width() / (float)glImage.width();
            yImageFactor = height() / (float)glImage.height();
        QPainter painter;
        painter.begin(this);
        painter.setRenderHint(QPainter::Antialiasing, true);
        painter.setRenderHint(QPainter::HighQualityAntialiasing, true);
        QPixmap pmap = QPixmap::fromImage(glImage).scaledToWidth(width());
        painter.drawPixmap(0, (height() - pmap.height()) / 2, pmap);

        // END OF OPENGL PAINTING

        if (HUDInstrumentsEnabled)
Bryant's avatar
Bryant committed
        {
            //glEnable(GL_MULTISAMPLE);

            // QT PAINTING
            //makeCurrent();
            painter.translate((this->vwidth/2.0+xCenterOffset)*scalingFactor, (this->vheight/2.0+yCenterOffset)*scalingFactor);

            // COORDINATE FRAME IS NOW (0,0) at CENTER OF WIDGET

            // Draw all fixed indicators
            // BATTERY
            paintText(fuelStatus, fuelColor, 6.0f, (-vwidth/2.0) + 10, -vheight/2.0 + 6, &painter);
            paintText(waypointName, defaultColor, 6.0f, (-vwidth/3.0) + 10, +vheight/3.0 + 15, &painter);

            QPen linePen(Qt::SolidLine);
            linePen.setWidth(refLineWidthToPen(1.0f));
            linePen.setColor(defaultColor);
            painter.setBrush(Qt::NoBrush);
            painter.setPen(linePen);
            const float yawIndicatorWidth = 12.0f;
            const float yawIndicatorY = vheight/2.0f - 15.0f;
            QPolygon yawIndicator(4);
            yawIndicator.setPoint(0, QPoint(refToScreenX(0.0f), refToScreenY(yawIndicatorY)));
            yawIndicator.setPoint(1, QPoint(refToScreenX(yawIndicatorWidth/2.0f), refToScreenY(yawIndicatorY+yawIndicatorWidth)));
            yawIndicator.setPoint(2, QPoint(refToScreenX(-yawIndicatorWidth/2.0f), refToScreenY(yawIndicatorY+yawIndicatorWidth)));
            yawIndicator.setPoint(3, QPoint(refToScreenX(0.0f), refToScreenY(yawIndicatorY)));
            painter.drawPolyline(yawIndicator);
            painter.setPen(linePen);
pixhawk's avatar
pixhawk committed

pixhawk's avatar
pixhawk committed

            const float hIndicatorWidth = 20.0f;
            const float hIndicatorY = -25.0f;
            const float hIndicatorYLow = hIndicatorY + hIndicatorWidth / 6.0f;
            const float hIndicatorSegmentWidth = hIndicatorWidth / 7.0f;
            QPolygon hIndicator(7);
            hIndicator.setPoint(0, QPoint(refToScreenX(0.0f-hIndicatorWidth/2.0f), refToScreenY(hIndicatorY)));
            hIndicator.setPoint(1, QPoint(refToScreenX(0.0f-hIndicatorWidth/2.0f+hIndicatorSegmentWidth*1.75f), refToScreenY(hIndicatorY)));
            hIndicator.setPoint(2, QPoint(refToScreenX(0.0f-hIndicatorSegmentWidth*1.0f), refToScreenY(hIndicatorYLow)));
            hIndicator.setPoint(3, QPoint(refToScreenX(0.0f), refToScreenY(hIndicatorY)));
            hIndicator.setPoint(4, QPoint(refToScreenX(0.0f+hIndicatorSegmentWidth*1.0f), refToScreenY(hIndicatorYLow)));
            hIndicator.setPoint(5, QPoint(refToScreenX(0.0f+hIndicatorWidth/2.0f-hIndicatorSegmentWidth*1.75f), refToScreenY(hIndicatorY)));
            hIndicator.setPoint(6, QPoint(refToScreenX(0.0f+hIndicatorWidth/2.0f), refToScreenY(hIndicatorY)));
            painter.drawPolyline(hIndicator);
            const float centerWidth = 8.0f;
            // TODO
            //painter.drawEllipse(QPointF(refToScreenX(qMin(10.0f, values.value("roll desired", 0.0f) * 10.0f)), refToScreenY(qMin(10.0f, values.value("pitch desired", 0.0f) * 10.0f))), refToScreenX(centerWidth/2.0f), refToScreenX(centerWidth/2.0f));
pixhawk's avatar
pixhawk committed

            const float centerCrossWidth = 20.0f;
            // left
            painter.drawLine(QPointF(refToScreenX(-centerWidth / 2.0f), refToScreenY(0.0f)), QPointF(refToScreenX(-centerCrossWidth / 2.0f), refToScreenY(0.0f)));
            // right
            painter.drawLine(QPointF(refToScreenX(centerWidth / 2.0f), refToScreenY(0.0f)), QPointF(refToScreenX(centerCrossWidth / 2.0f), refToScreenY(0.0f)));
            // top
            painter.drawLine(QPointF(refToScreenX(0.0f), refToScreenY(-centerWidth / 2.0f)), QPointF(refToScreenX(0.0f), refToScreenY(-centerCrossWidth / 2.0f)));
pixhawk's avatar
pixhawk committed

pixhawk's avatar
pixhawk committed

            const float compassY = -vheight/2.0f + 6.0f;
            QRectF compassRect(QPointF(refToScreenX(-12.0f), refToScreenY(compassY)), QSizeF(refToScreenX(24.0f), refToScreenY(12.0f)));
            painter.setPen(linePen);
            painter.drawRoundedRect(compassRect, 3, 3);
pixhawk's avatar
pixhawk committed

            //    const float yawDeg = ((values.value("yaw", 0.0f)/M_PI)*180.0f)+180.f;
pixhawk's avatar
pixhawk committed

            // YAW is in compass-human readable format, so 0 .. 360 deg.
barthess's avatar
barthess committed
            float yawDeg = (yawLP / M_PI) * 180.0f;
            if (yawDeg < 0) yawDeg += 360;
            if (yawDeg > 360) yawDeg -= 360;
            /* final safeguard for really stupid systems */
            int yawCompass = static_cast<int>(yawDeg) % 360;
            yawAngle.sprintf("%03d", yawCompass);
            paintText(yawAngle, defaultColor,8.5f, -9.8f, compassY+ 1.7f, &painter);

            painter.setBrush(Qt::NoBrush);
            painter.setPen(linePen);
pixhawk's avatar
pixhawk committed

            drawChangeRateStrip(-95.0f, -60.0f, 40.0f, -10.0f, 10.0f, -zSpeed, &painter);
pixhawk's avatar
pixhawk committed

            drawChangeRateStrip(95.0f, -60.0f, 40.0f, -10.0f, 10.0f, totalAcc, &painter,true);
pixhawk's avatar
pixhawk committed

pixhawk's avatar
pixhawk committed
            float gaugeAltitude;

            if (this->alt != 0) {
pixhawk's avatar
pixhawk committed
                gaugeAltitude = alt;
pixhawk's avatar
pixhawk committed
                gaugeAltitude = -zPos;
            }

            painter.setBrush(Qt::NoBrush);
            painter.setPen(linePen);

            drawChangeIndicatorGauge(-vGaugeSpacing, 35.0f, 15.0f, 10.0f, gaugeAltitude, defaultColor, &painter, false);
            paintText("alt m", defaultColor, 5.5f, -73.0f, 50, &painter);
            drawChangeIndicatorGauge(vGaugeSpacing, 35.0f, 15.0f, 10.0f, totalSpeed, defaultColor, &painter, false);
            paintText("v m/s", defaultColor, 5.5f, 55.0f, 50, &painter);
            // Waypoint name
            if (waypointName != "") paintText(waypointName, defaultColor, 2.0f, (-vwidth/3.0) + 10, +vheight/3.0 + 15, &painter);

            // MOVING PARTS


            painter.translate(refToScreenX(yawTrans), 0);

            // Old single-component pitch drawing
//            // Rotate view and draw all roll-dependent indicators
//            painter.rotate((rollLP/M_PI)* -180.0f);

//            painter.translate(0, (-pitchLP/(float)M_PI)* -180.0f * refToScreenY(1.8f));

//            //qDebug() << "ROLL" << roll << "PITCH" << pitch << "YAW DIFF" << valuesDot.value("roll", 0.0f);
//            paintPitchLines(pitchLP, &painter);
            QColor attColor = painter.pen().color();

            // Draw multi-component attitude
            foreach (QVector3D att, attitudes.values())
            {
                attColor = attColor.darker(200);
                painter.setPen(attColor);
                // Rotate view and draw all roll-dependent indicators
                painter.rotate((att.x()/M_PI)* -180.0f);

                painter.translate(0, (-att.y()/(float)M_PI)* -180.0f * refToScreenY(1.8f));

                //qDebug() << "ROLL" << roll << "PITCH" << pitch << "YAW DIFF" << valuesDot.value("roll", 0.0f);

                // PITCH

                paintPitchLines(att.y(), &painter);
                painter.translate(0, -(-att.y()/(float)M_PI)* -180.0f * refToScreenY(1.8f));
                painter.rotate(-(att.x()/M_PI)* -180.0f);
pixhawk's avatar
pixhawk committed

/**
 * @param pitch pitch angle in degrees (-180 to 180)
 */
void HUD::paintPitchLines(float pitch, QPainter* painter)
{
    QString label;

    const float yDeg = vPitchPerDeg;
    const float lineDistance = 5.0f; ///< One pitch line every 10 degrees
    const float posIncrement = yDeg * lineDistance;
    float posY = posIncrement;
    const float posLimit = sqrt(pow(vwidth, 2.0f) + pow(vheight, 2.0f))*3.0f;
pixhawk's avatar
pixhawk committed

    const float offsetAbs = pitch * yDeg;

    float offset = pitch;
    if (offset < 0) offset = -offset;
    int offsetCount = 0;
    while (offset > lineDistance) {
        offset -= lineDistance;
        offsetCount++;
    }
pixhawk's avatar
pixhawk committed

    int iPos = (int)(0.5f + lineDistance); ///< The first line
    int iNeg = (int)(-0.5f - lineDistance); ///< The first line

    offset *= yDeg;


    painter->setPen(defaultColor);

    posY = -offsetAbs + posIncrement; //+ 100;// + lineDistance;

    while (posY < posLimit) {
pixhawk's avatar
pixhawk committed
        paintPitchLinePos(label.sprintf("%3d", iPos), 0.0f, -posY, painter);
        posY += posIncrement;
        iPos += (int)lineDistance;
    }



    // HORIZON
    //
    //    ------------    ------------
    //
    const float pitchWidth = 30.0f;
    const float pitchGap = pitchWidth / 2.5f;
    const QColor horizonColor = defaultColor;
    const float diagonal = sqrt(pow(vwidth, 2.0f) + pow(vheight, 2.0f));
    const float lineWidth = refLineWidthToPen(0.5f);

    // Left horizon
    drawLine(0.0f-diagonal, offsetAbs, 0.0f-pitchGap/2.0f, offsetAbs, lineWidth, horizonColor, painter);
    // Right horizon
    drawLine(0.0f+pitchGap/2.0f, offsetAbs, 0.0f+diagonal, offsetAbs, lineWidth, horizonColor, painter);



    label.clear();

    posY = offsetAbs  + posIncrement;


    while (posY < posLimit) {
pixhawk's avatar
pixhawk committed
        paintPitchLineNeg(label.sprintf("%3d", iNeg), 0.0f, posY, painter);
        posY += posIncrement;
        iNeg -= (int)lineDistance;
    }
}

void HUD::paintPitchLinePos(QString text, float refPosX, float refPosY, QPainter* painter)
{
    //painter->setPen(QPen(QBrush, normalStrokeWidth));

    const float pitchWidth = 30.0f;
    const float pitchGap = pitchWidth / 2.5f;
    const float pitchHeight = pitchWidth / 12.0f;
    const float textSize = pitchHeight * 1.6f;
    const float lineWidth = 1.5f;
pixhawk's avatar
pixhawk committed

    // Positive pitch indicator:
    //
    //      _______      _______
    //     |10                  |
    //

    // Left vertical line
    drawLine(refPosX-pitchWidth/2.0f, refPosY, refPosX-pitchWidth/2.0f, refPosY+pitchHeight, lineWidth, defaultColor, painter);
    // Left horizontal line
    drawLine(refPosX-pitchWidth/2.0f, refPosY, refPosX-pitchGap/2.0f, refPosY, lineWidth, defaultColor, painter);
    // Text left
    paintText(text, defaultColor, textSize, refPosX-pitchWidth/2.0 + 0.75f, refPosY + pitchHeight - 1.3f, painter);
pixhawk's avatar
pixhawk committed

    // Right vertical line
    drawLine(refPosX+pitchWidth/2.0f, refPosY, refPosX+pitchWidth/2.0f, refPosY+pitchHeight, lineWidth, defaultColor, painter);
    // Right horizontal line
    drawLine(refPosX+pitchWidth/2.0f, refPosY, refPosX+pitchGap/2.0f, refPosY, lineWidth, defaultColor, painter);
}

void HUD::paintPitchLineNeg(QString text, float refPosX, float refPosY, QPainter* painter)
{
    const float pitchWidth = 30.0f;
    const float pitchGap = pitchWidth / 2.5f;
    const float pitchHeight = pitchWidth / 12.0f;
    const float textSize = pitchHeight * 1.6f;
pixhawk's avatar
pixhawk committed
    const float segmentWidth = ((pitchWidth - pitchGap)/2.0f) / 7.0f; ///< Four lines and three gaps -> 7 segments

    const float lineWidth = 1.5f;
pixhawk's avatar
pixhawk committed

    // Negative pitch indicator:
    //
    //      -10
    //     _ _ _ _|     |_ _ _ _
    //
    //

    // Left vertical line
    drawLine(refPosX-pitchGap/2.0, refPosY, refPosX-pitchGap/2.0, refPosY-pitchHeight, lineWidth, defaultColor, painter);
    // Left horizontal line with four segments
    for (int i = 0; i < 7; i+=2) {
pixhawk's avatar
pixhawk committed
        drawLine(refPosX-pitchWidth/2.0+(i*segmentWidth), refPosY, refPosX-pitchWidth/2.0+(i*segmentWidth)+segmentWidth, refPosY, lineWidth, defaultColor, painter);
    }
    // Text left
    paintText(text, defaultColor, textSize, refPosX-pitchWidth/2.0f + 0.75f, refPosY + pitchHeight - 1.3f, painter);
pixhawk's avatar
pixhawk committed

    // Right vertical line
    drawLine(refPosX+pitchGap/2.0, refPosY, refPosX+pitchGap/2.0, refPosY-pitchHeight, lineWidth, defaultColor, painter);
    // Right horizontal line with four segments
    for (int i = 0; i < 7; i+=2) {
pixhawk's avatar
pixhawk committed
        drawLine(refPosX+pitchWidth/2.0f-(i*segmentWidth), refPosY, refPosX+pitchWidth/2.0f-(i*segmentWidth)-segmentWidth, refPosY, lineWidth, defaultColor, painter);
    }
}

void rotatePointClockWise(QPointF& p, float angle)
{
    // Standard 2x2 rotation matrix, counter-clockwise
    //
    //   |  cos(phi)   sin(phi) |
    //   | -sin(phi)   cos(phi) |
    //

    //p.setX(cos(angle) * p.x() + sin(angle) * p.y());
    //p.setY(-sin(angle) * p.x() + cos(angle) * p.y());


    p.setX(cos(angle) * p.x() + sin(angle)* p.y());
    p.setY((-1.0f * sin(angle) * p.x()) + cos(angle) * p.y());
}

float HUD::refLineWidthToPen(float line)
{
    return line * 2.50f;
}

/**
 * Rotate a polygon around a point
 *
 * @param p polygon to rotate
 * @param origin the rotation center
 * @param angle rotation angle, in radians
 * @return p Polygon p rotated by angle around the origin point
 */
void HUD::rotatePolygonClockWiseRad(QPolygonF& p, float angle, QPointF origin)
{
    // Standard 2x2 rotation matrix, counter-clockwise
    //
    //   |  cos(phi)   sin(phi) |
    //   | -sin(phi)   cos(phi) |
    //
    for (int i = 0; i < p.size(); i++) {
pixhawk's avatar
pixhawk committed
        QPointF curr = p.at(i);

        const float x = curr.x();
        const float y = curr.y();

        curr.setX(((cos(angle) * (x-origin.x())) + (-sin(angle) * (y-origin.y()))) + origin.x());
        curr.setY(((sin(angle) * (x-origin.x())) + (cos(angle) * (y-origin.y()))) + origin.y());
        p.replace(i, curr);
    }
}

void HUD::drawPolygon(QPolygonF refPolygon, QPainter* painter)
{
    // Scale coordinates
    QPolygonF draw(refPolygon.size());
    for (int i = 0; i < refPolygon.size(); i++) {
pixhawk's avatar
pixhawk committed
        QPointF curr;
        curr.setX(refToScreenX(refPolygon.at(i).x()));
        curr.setY(refToScreenY(refPolygon.at(i).y()));
        draw.replace(i, curr);
    }
    painter->drawPolygon(draw);
}

void HUD::drawChangeRateStrip(float xRef, float yRef, float height, float minRate, float maxRate, float value, QPainter* painter,bool reverse)
pixhawk's avatar
pixhawk committed
{
    float scaledValue = value;

    // Saturate value
    if (value > maxRate) scaledValue = maxRate;
    if (value < minRate) scaledValue = minRate;

    //           x (Origin: xRef, yRef)
    //           -
    //           |
    //           |
    //           |
    //           =
    //           |
    //   -0.005 >|
    //           |
    //           -

    const float width = height / 8.0f;
    const float lineWidth = 1.5f;
pixhawk's avatar
pixhawk committed

    // Indicator lines
    // Top horizontal line
    if (reverse)
    {
        drawLine(xRef, yRef, xRef-width, yRef, lineWidth, defaultColor, painter);
        // Vertical main line
        drawLine(xRef-width/2.0f, yRef, xRef-width/2.0f, yRef+height, lineWidth, defaultColor, painter);
        // Zero mark