Skip to content
Snippets Groups Projects
HUD.cc 53.3 KiB
Newer Older
  • Learn to ignore specific revisions
  • 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 <QContextMenuEvent>
    #include <QMenu>
    #include <QDesktopServices>
    #include <QFileDialog>
    
    pixhawk's avatar
    pixhawk committed
    #include <QDebug>
    #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"
    #include "MG.h"
    
    pixhawk's avatar
    pixhawk committed
    
    // Fix for some platforms, e.g. windows
    #ifndef GL_MULTISAMPLE
    #define GL_MULTISAMPLE  0x809D
    #endif
    
    /**
     * @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)
    
        : QGLWidget(QGLFormat(QGL::SampleBuffers), parent),
    
          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),
          defaultColor(QColor(70, 200, 70)),
          setPointColor(QColor(200, 20, 200)),
          warningColor(Qt::yellow),
          criticalColor(Qt::red),
          infoColor(QColor(20, 200, 20)),
          fuelColor(criticalColor),
          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(true),
          videoEnabled(false),
          xImageFactor(1.0),
    
          imageRequested(false),
          imageLoggingEnabled(false)
    
    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;
    
    pixhawk's avatar
    pixhawk committed
        // Fill with black background
        QImage fill = QImage(width, height, QImage::Format_Indexed8);
        fill.setNumColors(3);
        fill.setColor(0, qRgb(0, 0, 0));
        fill.setColor(1, qRgb(0, 0, 0));
        fill.setColor(2, qRgb(0, 0, 0));
        fill.fill(0);
    
    
        //QString imagePath = "/Users/user/Desktop/frame0000.png";
        //qDebug() << __FILE__ << __LINE__ << "template image:" << imagePath;
        //fill = QImage(imagePath);
    
    pixhawk's avatar
    pixhawk committed
    
    
    LM's avatar
    LM committed
        glImage = QGLWidget::convertToGLFormat(fill);
    
    pixhawk's avatar
    pixhawk committed
    
        // Refresh timer
    
        refreshTimer->setInterval(updateInterval);
    
        connect(refreshTimer, SIGNAL(timeout()), this, SLOT(paintHUD()));
    
    pixhawk's avatar
    pixhawk committed
    
        // Resize to correct size and fill with image
    
    pixhawk's avatar
    pixhawk committed
        resize(this->width(), this->height());
    
        //glDrawPixels(glImage.width(), glImage.height(), GL_RGBA, GL_UNSIGNED_BYTE, glImage.bits());
    
    pixhawk's avatar
    pixhawk committed
    
        // Set size once
        //setFixedSize(fill.size());
        //setMinimumSize(fill.size());
        //setMaximumSize(fill.size());
        // Lock down the size
        //setSizePolicy(QSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed));
    
        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 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::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
        QGLWidget::hideEvent(event);
        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
    
            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, int)), this, SLOT(updateBattery(UASInterface*, 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)));
    
    pixhawk's avatar
    pixhawk committed
            disconnect(this->uas, SIGNAL(globalPositionChanged(UASInterface*,double,double,double,quint64)), this, SLOT(updateGlobalPosition(UASInterface*,double,double,double,quint64)));
    
            disconnect(this->uas, SIGNAL(speedChanged(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);
    
                disconnect(u, SIGNAL(imageStarted(quint64)), this, SLOT(startImage(quint64)));
    
    LM's avatar
    LM committed
                disconnect(u, SIGNAL(imageReady(UASInterface*)), this, SLOT(copyImage()));
    
            // 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(batteryChanged(UASInterface*, double, double, int)), this, SLOT(updateBattery(UASInterface*, 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)));
    
    pixhawk's avatar
    pixhawk committed
            connect(uas, SIGNAL(globalPositionChanged(UASInterface*,double,double,double,quint64)), this, SLOT(updateGlobalPosition(UASInterface*,double,double,double,quint64)));
    
            connect(uas, SIGNAL(speedChanged(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
            UAS* u = dynamic_cast<UAS*>(uas);
    
                connect(u, SIGNAL(imageStarted(quint64)), this, SLOT(startImage(quint64)));
    
    LM's avatar
    LM committed
                connect(u, SIGNAL(imageReady(UASInterface*)), this, SLOT(copyImage()));
    
    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
        }
    
    pixhawk's avatar
    pixhawk committed
    void HUD::updateBattery(UASInterface* uas, double voltage, double percent, int seconds)
    {
    
        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 altitude, quint64 timestamp)
    
    pixhawk's avatar
    pixhawk committed
    {
    
        Q_UNUSED(uas);
        Q_UNUSED(timestamp);
        this->lat = lat;
        this->lon = lon;
        this->alt = altitude;
    
    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);
    }
    
    /**
     * This functions works in the OpenGL view, which is already translated by
     * the x and y center offsets.
     *
     */
    void HUD::paintCenterBackground(float roll, float pitch, float yaw)
    {
    
    pixhawk's avatar
    pixhawk committed
        // Center indicator is 100 mm wide
        float referenceWidth = 70.0;
        float referenceHeight = 70.0;
    
        // HUD is assumed to be 200 x 150 mm
        // so that positions can be hardcoded
        // but can of course be scaled.
    
        double referencePositionX = vwidth / 2.0 - referenceWidth/2.0;
        double referencePositionY = vheight / 2.0 - referenceHeight/2.0;
    
        //this->width()/2.0+(xCenterOffset*scalingFactor), this->height()/2.0+(yCenterOffset*scalingFactor);
    
        setupGLView(referencePositionX, referencePositionY, referenceWidth, referenceHeight);
    
        // Store current position in the model view
        // the position will be restored after drawing
        glMatrixMode(GL_MODELVIEW);
        glPushMatrix();
    
        // Move to the center of the window
        glTranslatef(referenceWidth/2.0f,referenceHeight/2.0f,0);
    
    
        // Move based on the yaw difference
    
        //glTranslatef(yaw, 0.0f, 0.0f);
    
    pixhawk's avatar
    pixhawk committed
        // Rotate based on the bank
    
        glRotatef((roll/M_PI)*180.0f, 0.0f, 0.0f, 1.0f);
    
    pixhawk's avatar
    pixhawk committed
    
        // Translate in the direction of the rotation based
        // on the pitch. On the 777, a pitch of 1 degree = 2 mm
    
        //glTranslatef(0, ((-pitch/M_PI)*180.0f * vPitchPerDeg), 0);
    
    pixhawk's avatar
    pixhawk committed
        glTranslatef(0.0f, (-pitch * vPitchPerDeg * 16.5f), 0.0f);
    
    pixhawk's avatar
    pixhawk committed
    
        // Ground
        glColor3ub(179,102,0);
    
        glBegin(GL_POLYGON);
    
    pixhawk's avatar
    pixhawk committed
        glVertex2f(-300,0);
        glVertex2f(300,0);
    
        glVertex2f(300,-900);
        glVertex2f(-300,-900);
    
    pixhawk's avatar
    pixhawk committed
        glEnd();
    
        // Sky
        glColor3ub(0,153,204);
    
        glBegin(GL_POLYGON);
        glVertex2f(-300,0);
    
        glVertex2f(-300,900);
        glVertex2f(300,900);
    
    pixhawk's avatar
    pixhawk committed
        glVertex2f(300,0);
        glVertex2f(-300,0);
    
        glEnd();
    }
    
    /**
     * 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);
    }
    
    void HUD::initializeGL()
    {
        bool antialiasing = true;
    
        // Antialiasing setup
    
        if(antialiasing) {
    
    pixhawk's avatar
    pixhawk committed
            glEnable(GL_MULTISAMPLE);
    
    pixhawk's avatar
    pixhawk committed
            glEnable(GL_BLEND);
    
            glBlendFunc(GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA);
    
            glEnable(GL_POINT_SMOOTH);
            glEnable(GL_LINE_SMOOTH);
    
            glHint(GL_POINT_SMOOTH_HINT, GL_NICEST);
            glHint(GL_LINE_SMOOTH_HINT, GL_NICEST);
    
            glDisable(GL_BLEND);
            glDisable(GL_POINT_SMOOTH);
            glDisable(GL_LINE_SMOOTH);
    
    pixhawk's avatar
    pixhawk committed
        }
    }
    
    /**
     * @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)
    {
        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;
    
        //qDebug() << "Pixel x" << pixelPositionX << "pixelY" << pixelPositionY;
        //qDebug() << "xCenterOffset:" << xCenterOffset << "yCenterOffest" << yCenterOffset
    
    
        //The viewport is established at the correct pixel position and clips everything
        // out of the desired instrument location
        glViewport(pixelPositionX, pixelPositionY, pixelWidth, pixelHeight);
        glMatrixMode(GL_PROJECTION);
        glLoadIdentity();
    
        // The ortho projection is setup in a way that so that the drawing is done in the
        // reference coordinate space
        glOrtho(0, referenceWidth, 0, referenceHeight, -1, 1);
        glMatrixMode(GL_MODELVIEW);
        glLoadIdentity();
        //glScalef(scaleX, scaleY, 1.0f);
    }
    
    void HUD::paintRollPitchStrips()
    {
    }
    
    
    
    pixhawk's avatar
    pixhawk committed
    void HUD::paintEvent(QPaintEvent *event)
    
    pixhawk's avatar
    pixhawk committed
    {
    
        // Event is not needed
    
        // the event is ignored as this widget
        // is refreshed automatically
    
        Q_UNUSED(event);
    
        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;
    
            // OPEN GL PAINTING
            // Store model view matrix to be able to reset it to the previous state
            makeCurrent();
            glMatrixMode(GL_MODELVIEW);
            glPushMatrix();
            glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    
            // Fill with black background
    
            if (videoEnabled) {
                if (nextOfflineImage != "" && QFileInfo(nextOfflineImage).exists()) {
    
                    qDebug() << __FILE__ << __LINE__ << "template image:" << nextOfflineImage;
                    QImage fill = QImage(nextOfflineImage);
    
                    glImage = QGLWidget::convertToGLFormat(fill);
    
                    // Reset to save load efforts
                    nextOfflineImage = "";
                }
    
            }
    
            if (dataStreamEnabled || videoEnabled)
            {
    
    pixhawk's avatar
    pixhawk committed
                glRasterPos2i(0, 0);
    
    
    LM's avatar
    LM committed
                xImageFactor = width() / (float)glImage.width();
                yImageFactor = height() / (float)glImage.height();
                float imageFactor = qMin(xImageFactor, yImageFactor);
                glPixelZoom(imageFactor, imageFactor);
    
                // Resize to correct size and fill with image
                glDrawPixels(glImage.width(), glImage.height(), GL_RGBA, GL_UNSIGNED_BYTE, glImage.bits());
    
    LM's avatar
    LM committed
                //qDebug() << "DRAWING GL IMAGE";
    
                // Blue / brown background
                paintCenterBackground(roll, pitch, yawTrans);
    
            }
    
            glMatrixMode(GL_MODELVIEW);
            glPopMatrix();
    
            // END OF OPENGL PAINTING
    
    
            if (hudInstrumentsEnabled) {
    
                //glEnable(GL_MULTISAMPLE);
    
                // QT PAINTING
                //makeCurrent();
                QPainter painter;
                painter.begin(this);
                painter.setRenderHint(QPainter::Antialiasing, true);
                painter.setRenderHint(QPainter::HighQualityAntialiasing, true);
                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);
    
            //glDisable(GL_MULTISAMPLE);
    
    
            //glFlush();
        }
    
    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)
    {