Newer
Older
dongfang
committed
#include "PrimaryFlightDisplay.h"
#include "UASManager.h"
//#include "ui_primaryflightdisplay.h"
#include <QDebug>
#include <QRectF>
#include <cmath>
#include <QPen>
#include <QPainter>
#include <QPainterPath>
#include <QResizeEvent>
dongfang
committed
static const float SEPARATE_COMPASS_ASPECTRATIO = 3.0f/4.0f;
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;
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;
static const float PITCH_SCALE_MINORWIDTH = 0.066;
// 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 float COMPASS_DISK_MAJORLINEWIDTH = 0.006;
static const float COMPASS_DISK_MINORLINEWIDTH = 0.004;
static const int COMPASS_DISK_RESOLUTION = 10;
static const float COMPASS_SEPARATE_DISK_RESOLUTION = 5;
static const float COMPASS_DISK_MARKERWIDTH = 0.2;
static const float COMPASS_DISK_MARKERHEIGHT = 0.133;
static const int CROSSTRACK_MAX = 1000;
static const float CROSSTRACK_RADIUS = 0.6;
static const float TAPE_GAUGES_TICKWIDTH_MAJOR = 0.25;
static const float TAPE_GAUGES_TICKWIDTH_MINOR = 0.15;
// 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;
// Projected: An experiment. Make tape appear projected from a cylinder, like a French "drum" style gauge.
// The altitude difference between top and bottom of scale
static const int ALTIMETER_PROJECTED_SPAN = 50;
// every 5 meters there is a tick mark
static const int ALTIMETER_PROJECTED_RESOLUTION = 5;
// every 10 meters there is a number
static const int ALTIMETER_PROJECTED_MAJOR_RESOLUTION = 10;
// min. and max. vertical velocity
// 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;
static const int UNKNOWN_ATTITUDE = -1000;
static const int UNKNOWN_ALTITUDE = -1000;
static const int UNKNOWN_SPEED = -1;
dongfang
committed
/*
*@TODO:
dongfang
committed
* repaint on demand multiple canvases
* multi implementation with shared model class
*/
double PrimaryFlightDisplay_round(double value, int digits=0)
return floor(value * pow(10.0, digits) + 0.5) / pow(10.0, digits);
dongfang
committed
qreal PrimaryFlightDisplay_constrain(qreal value, qreal min, qreal max) {
if (value<min) value=min;
else if(value>max) value=max;
return value;
}
dongfang
committed
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(int width, int height, QWidget *parent) :
QWidget(parent),
uas(NULL),
dongfang
committed
altimeterMode(GPS_MAIN),
altimeterFrame(ASL),
speedMode(GROUND_MAIN),
roll(UNKNOWN_ATTITUDE),
pitch(UNKNOWN_ATTITUDE),
heading(UNKNOWN_ATTITUDE),
primaryAltitude(UNKNOWN_ALTITUDE),
GPSAltitude(UNKNOWN_ALTITUDE),
aboveHomeAltitude(UNKNOWN_ALTITUDE),
primarySpeed(UNKNOWN_SPEED),
groundspeed(UNKNOWN_SPEED),
verticalVelocity(UNKNOWN_ALTITUDE),
navigationCrosstrackError(0),
navigationTargetBearing(UNKNOWN_ATTITUDE),
layout(COMPASS_INTEGRATED),
style(OVERLAY_HSI),
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)),
instrumentOpagueBackground(QColor::fromHsvF(0, 0, 0.3, 1.0)),
font("Bitstream Vera Sans"),
refreshTimer(new QTimer(this))
Q_UNUSED(width);
Q_UNUSED(height);
dongfang
committed
setMinimumSize(120, 80);
setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
setActiveUAS(UASManager::instance()->getActiveUAS());
// Connect with UAS signal
//connect(UASManager::instance(), SIGNAL(UASCreated(UASInterface*)), this, SLOT(addUAS(UASInterface*)));
connect(UASManager::instance(), SIGNAL(UASDeleted(UASInterface*)), this, SLOT(forgetUAS(UASInterface*)));
dongfang
committed
connect(UASManager::instance(), SIGNAL(activeUASSet(UASInterface*)), this, SLOT(setActiveUAS(UASInterface*)));
dongfang
committed
// Refresh timer
refreshTimer->setInterval(updateInterval);
connect(refreshTimer, SIGNAL(timeout()), this, SLOT(update()));
dongfang
committed
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
}
PrimaryFlightDisplay::~PrimaryFlightDisplay()
{
refreshTimer->stop();
}
QSize PrimaryFlightDisplay::sizeHint() const
{
return QSize(width(), (width()*3.0f)/4);
}
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);
}
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);
instrumentEdgePen.setWidthF(fineLineWidth);
//AIEdgePen.setWidthF(fineLineWidth);
smallTextSize = size * SMALL_TEXT_SIZE;
mediumTextSize = size * MEDIUM_TEXT_SIZE;
largeTextSize = size * LARGE_TEXT_SIZE;
/*
* Try without layout Change-O-Matic. It was too complicated.
qreal aspect = e->size().width() / e->size().height();
if (aspect <= SEPARATE_COMPASS_ASPECTRATIO)
// qDebug("Width %d height %d decision %d", e->size().width(), e->size().height(), layout);
dongfang
committed
void PrimaryFlightDisplay::paintEvent(QPaintEvent *event)
{
Q_UNUSED(event);
doPaint();
}
dongfang
committed
///*
// * Interface towards qgroundcontrol
// */
//void PrimaryFlightDisplay::addUAS(UASInterface* uas)
//{
// if (uas)
// {
// if (!this->uas)
// {
// setActiveUAS(uas);
// }
// }
//}
void PrimaryFlightDisplay::forgetUAS(UASInterface* uas)
if (this->uas != NULL && this->uas == uas) {
dongfang
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(waypointSelected(int,int)),
// this, SLOT(selectWaypoint(int, int)));
disconnect(this->uas, SIGNAL(primarySpeedChanged(UASInterface*, double, quint64)),
this, SLOT(updatePrimarySpeed(UASInterface*,double,quint64)));
disconnect(this->uas, SIGNAL(gpsSpeedChanged(UASInterface*, double, quint64)),
this, SLOT(updateGPSSpeed(UASInterface*,double,quint64)));
disconnect(this->uas, SIGNAL(climbRateChanged(UASInterface*, double, quint64)),
this,SLOT(updateClimbRate(UASInterface*, double, quint64)));
disconnect(this->uas, SIGNAL(primaryAltitudeChanged(UASInterface*, double, quint64)),
this, SLOT(updatePrimaryAltitude(UASInterface*, double, quint64)));
disconnect(this->uas, SIGNAL(gpsAltitudeChanged(UASInterface*, double, quint64)),
this, SLOT(updateGPSAltitude(UASInterface*, double, quint64)));
disconnect(this->uas, SIGNAL(navigationControllerErrorsChanged(UASInterface*, double, double, double)),
this, SLOT(updateNavigationControllerErrors(UASInterface*, double, double, double)));
//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(armingChanged(bool)), this, SLOT(updateArmed(bool)));
//disconnect(this->uas, SIGNAL(satelliteCountChanged(double, QString)), this, SLOT(updateSatelliteCount(double, QString)));
//disconnect(this->uas, SIGNAL(localizationChanged(UASInterface* uas, int fix)), this, SLOT(updateGPSFixType(UASInterface*,int)));
dongfang
committed
}
}
/**
*
* @param uas the UAS/MAV to monitor/display with the HUD
*/
void PrimaryFlightDisplay::setActiveUAS(UASInterface* uas)
{
if (uas == this->uas)
return; //no need to rewire
// Disconnect the previous one (if any)
forgetUAS(this->uas);
dongfang
committed
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(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(armingChanged(bool)), this, SLOT(updateArmed(bool)));
//connect(uas, SIGNAL(satelliteCountChanged(double, QString)), this, SLOT(updateSatelliteCount(double, QString)));
dongfang
committed
//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,quint64)), this, SLOT(updateGlobalPosition(UASInterface*,double,double,double,quint64)));
//connect(uas, SIGNAL(waypointSelected(int,int)), this,
// SLOT(selectWaypoint(int, int)));
connect(uas, SIGNAL(primarySpeedChanged(UASInterface*, double, quint64)), this, SLOT(updatePrimarySpeed(UASInterface*,double,quint64)));
connect(uas, SIGNAL(gpsSpeedChanged(UASInterface*, double, quint64)), this, SLOT(updateGPSSpeed(UASInterface*,double,quint64)));
connect(uas, SIGNAL(climbRateChanged(UASInterface*, double, quint64)), this,
SLOT(updateClimbRate(UASInterface*, double, quint64)));
connect(uas, SIGNAL(primaryAltitudeChanged(UASInterface*, double, quint64)), this, SLOT(updatePrimaryAltitude(UASInterface*, double, quint64)));
connect(uas, SIGNAL(gpsAltitudeChanged(UASInterface*, double, quint64)), this, SLOT(updateGPSAltitude(UASInterface*, double, quint64)));
connect(uas, SIGNAL(navigationControllerErrorsChanged(UASInterface*, double, double, double)), this, SLOT(updateNavigationControllerErrors(UASInterface*, double, double, double)));
dongfang
committed
// 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);
// Called from UAS.cc l. 616
if (isnan(roll) || isinf(roll)) {
this->roll = UNKNOWN_ATTITUDE;
} else {
this->roll = roll * (180.0 / M_PI);
}
dongfang
committed
if (isnan(pitch) || isinf(pitch)) {
this->pitch = UNKNOWN_ATTITUDE;
} else {
this->pitch = pitch * (180.0 / M_PI);
}
if (isnan(yaw) || isinf(yaw)) {
this->heading = UNKNOWN_ATTITUDE;
} else {
yaw = yaw * (180.0 / M_PI);
if (yaw<0) yaw+=360;
this->heading = yaw;
}
dongfang
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);
dongfang
committed
}
void PrimaryFlightDisplay::updatePrimarySpeed(UASInterface* uas, double speed, quint64 timestamp)
{
Q_UNUSED(uas);
Q_UNUSED(timestamp);
primarySpeed = speed;
didReceivePrimarySpeed = true;
}
void PrimaryFlightDisplay::updateGPSSpeed(UASInterface* uas, double speed, quint64 timestamp)
dongfang
committed
{
Q_UNUSED(uas);
Q_UNUSED(timestamp);
groundspeed = speed;
if (!didReceivePrimarySpeed)
primarySpeed = speed;
}
dongfang
committed
void PrimaryFlightDisplay::updateClimbRate(UASInterface* uas, double climbRate, quint64 timestamp) {
Q_UNUSED(uas);
Q_UNUSED(timestamp);
verticalVelocity = climbRate;
}
void PrimaryFlightDisplay::updatePrimaryAltitude(UASInterface* uas, double altitude, quint64 timestamp) {
Q_UNUSED(uas);
Q_UNUSED(timestamp);
primaryAltitude = altitude;
didReceivePrimaryAltitude = true;
}
void PrimaryFlightDisplay::updateGPSAltitude(UASInterface* uas, double altitude, quint64 timestamp) {
Q_UNUSED(uas);
Q_UNUSED(timestamp);
GPSAltitude = altitude;
if (!didReceivePrimaryAltitude)
primaryAltitude = altitude;
dongfang
committed
}
void PrimaryFlightDisplay::updateNavigationControllerErrors(UASInterface* uas, double altitudeError, double speedError, double xtrackError) {
Q_UNUSED(uas);
this->navigationAltitudeError = altitudeError;
this->navigationSpeedError = speedError;
this->navigationCrosstrackError = xtrackError;
}
dongfang
committed
/*
* Private and such
*/
// 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;
}
dongfang
committed
void PrimaryFlightDisplay::drawTextCenter (
QPainter& painter,
QString text,
dongfang
committed
float x,
float y)
{
dongfang
committed
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.x()*/ -bounds.width()/2, y /*+bounds.y()*/ -bounds.height()/2, bounds.width(), bounds.height(), flags, text);
}
void PrimaryFlightDisplay::drawTextLeftCenter (
QPainter& painter,
QString text,
dongfang
committed
float x,
float y)
{
dongfang
committed
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 /*+bounds.x()*/, y /*+bounds.y()*/ -bounds.height()/2, bounds.width(), bounds.height(), flags, text);
}
void PrimaryFlightDisplay::drawTextRightCenter (
QPainter& painter,
QString text,
dongfang
committed
float x,
float y)
{
dongfang
committed
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.x()*/ -bounds.width(), y /*+bounds.y()*/ -bounds.height()/2, bounds.width(), bounds.height(), flags, text);
}
void PrimaryFlightDisplay::drawTextCenterTop (
QPainter& painter,
QString text,
dongfang
committed
float x,
float y)
{
dongfang
committed
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.x()*/ -bounds.width()/2, y+bounds.height() /*+bounds.y()*/, bounds.width(), bounds.height(), flags, text);
}
void PrimaryFlightDisplay::drawTextCenterBottom (
QPainter& painter,
QString text,
dongfang
committed
float x,
float y)
{
dongfang
committed
painter.setFont(font);
QFontMetrics metrics = QFontMetrics(font);
QRect bounds = metrics.boundingRect(text);
int flags = Qt::AlignCenter;
painter.drawText(x /*+bounds.x()*/ -bounds.width()/2, y /*+bounds.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);
}
void PrimaryFlightDisplay::fillInstrumentOpagueBackground(QPainter& painter, QRectF edge) {
painter.setPen(instrumentEdgePen);
painter.setBrush(instrumentOpagueBackground);
painter.drawRect(edge);
painter.setBrush(Qt::NoBrush);
}
dongfang
committed
qreal pitchAngleToTranslation(qreal viewHeight, float pitch) {
if (pitch == UNKNOWN_ATTITUDE)
return 0;
return pitch * viewHeight / PITCHTRANSLATION;
dongfang
committed
}
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
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));
// 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);
dongfang
committed
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;
}
dongfang
committed
void PrimaryFlightDisplay::drawAIGlobalFeatures(
QPainter& painter,
QRectF mainArea,
QRectF paintArea) {
dongfang
committed
float displayRoll = this->roll;
if(displayRoll == UNKNOWN_ATTITUDE)
displayRoll = 0;
dongfang
committed
painter.resetTransform();
painter.translate(mainArea.center());
dongfang
committed
qreal pitchPixels = pitchAngleToTranslation(mainArea.height(), pitch);
qreal gradientEnd = pitchAngleToTranslation(mainArea.height(), 60);
dongfang
committed
//painter.rotate(-roll);
//painter.translate(0, pitchPixels);
dongfang
committed
// QTransform forwardTransform;
//forwardTransform.translate(mainArea.center().x(), mainArea.center().y());
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);
dongfang
committed
skyPath.closeSubpath();
QLinearGradient skyGradient(0, -gradientEnd, 0, 0);
dongfang
committed
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);
dongfang
committed
groundPath.closeSubpath();
QLinearGradient groundGradient(0, gradientEnd, 0, 0);
dongfang
committed
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);
QPen pen;
pen.setWidthF(lineWidth);
pen.setColor(greenColor);
painter.setPen(pen);
QPointF start(-mainArea.width(), 0);
QPoint end(mainArea.width(), 0);
dongfang
committed
painter.drawLine(start, end);
}
void PrimaryFlightDisplay::drawPitchScale(
QPainter& painter,
QRectF area,
dongfang
committed
bool drawNumbersLeft,
bool drawNumbersRight
) {
float displayPitch = this->pitch;
if (displayPitch == UNKNOWN_ATTITUDE)
displayPitch = 0;
// 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
committed
QPen pen;
pen.setWidthF(lineWidth);
pen.setColor(Qt::white);
painter.setPen(pen);
dongfang
committed
QTransform savedTransform = painter.transform();
// find the mark nearest center
int snap = qRound((double)(displayPitch/PITCH_SCALE_RESOLUTION))*PITCH_SCALE_RESOLUTION;
dongfang
committed
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)
int fromVertical = abs(pitch>=0 ? 90-pitch : -90-pitch);
float temp = fromVertical * 1/(90.0f-PITCH_SCALE_WIDTHREDUCTION_FROM);
linewidth *= (PITCH_SCALE_WIDTHREDUCTION * (1-temp) + temp);
}
float shift = pitchAngleToTranslation(w, displayPitch-degrees);
// TODO: Intrusion detection and evasion. That is, don't draw
// where the compass has intruded.
dongfang
committed
painter.translate(0, shift);
dongfang
committed
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;
if (this->pitch == UNKNOWN_ATTITUDE)
s_number.sprintf("-");
else
s_number.sprintf("%d", displayDegrees);
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);
dongfang
committed
}
}
painter.setTransform(savedTransform);
}
}
void PrimaryFlightDisplay::drawRollScale(
QPainter& painter,
QRectF area,
bool drawTicks,
bool drawNumbers) {
dongfang
committed
QPen pen;
pen.setWidthF(lineWidth);
pen.setColor(Qt::white);
painter.setPen(pen);
dongfang
committed
// 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
committed
painter.drawArc(arcArea, (90-ROLL_SCALE_RANGE)*16, ROLL_SCALE_RANGE*2*16);
dongfang
committed
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];
//degrees = 180 - degrees;
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);
QString s_number; //= QString("%d").arg(degrees);
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);
dongfang
committed
}
}
}
}
void PrimaryFlightDisplay::drawAIAttitudeScales(
QPainter& painter,
float displayRoll = this->roll;
if (displayRoll == UNKNOWN_ATTITUDE)
displayRoll = 0;
dongfang
committed
// To save computations, we do these transformations once for both scales:
painter.resetTransform();
painter.translate(area.center());
dongfang
committed
QTransform saved = painter.transform();
drawRollScale(painter, area, true, true);
painter.setTransform(saved);
dongfang
committed
}
void PrimaryFlightDisplay::drawAICompassDisk(QPainter& painter, QRectF area, float halfspan) {
float displayHeading = this->heading;
if(displayHeading == UNKNOWN_ATTITUDE)
displayHeading = 0;
float start = displayHeading - halfspan;
float end = displayHeading + halfspan;
int firstTick = ceil(start / COMPASS_DISK_RESOLUTION) * COMPASS_DISK_RESOLUTION;
int lastTick = floor(end / COMPASS_DISK_RESOLUTION) * COMPASS_DISK_RESOLUTION;
dongfang
committed
float radius = area.width()/2;
float innerRadius = radius * 0.96;
painter.resetTransform();
painter.setBrush(instrumentBackground);
painter.setPen(instrumentEdgePen);
dongfang
committed
painter.drawEllipse(area);
painter.setBrush(Qt::NoBrush);
QPen scalePen(Qt::black);
scalePen.setWidthF(fineLineWidth);
dongfang
committed
for (int tickYaw = firstTick; tickYaw <= lastTick; tickYaw += COMPASS_DISK_RESOLUTION) {
dongfang
committed
int displayTick = tickYaw;
if (displayTick < 0) displayTick+=360;
else if (displayTick>=360) displayTick-=360;
// yaw is in center.
float off = tickYaw - displayHeading;
dongfang
committed
// wrap that to ]-180..180]
dongfang
committed
painter.translate(area.center());
painter.rotate(off);
bool drewArrow = false;
bool isMajor = displayTick % COMPASS_DISK_MAJORTICK == 0;
dongfang
committed
// If heading unknown, still draw marks but no numbers.
if (this->heading != UNKNOWN_ATTITUDE &&
(displayTick==30 || displayTick==60 ||
displayTick==120 || displayTick==150 ||
displayTick==210 || displayTick==240 ||
displayTick==300 || displayTick==330)
) {
dongfang
committed
// draw a number
QString s_number;
s_number.sprintf("%d", displayTick/10);
drawTextCenter(painter, s_number, smallTextSize, 0, -innerRadius*0.75);
dongfang
committed
} else {
dongfang
committed
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
committed
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 (this->heading != UNKNOWN_ATTITUDE && displayTick%90 == 0) {
dongfang
committed
// Also draw a label
QString name = compassWindNames[displayTick / 45];
painter.setPen(scalePen);
drawTextCenter(painter, name, mediumTextSize, 0, -innerRadius*0.75);
dongfang
committed
}
dongfang
committed
}
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
committed
dongfang
committed
painter.drawLine(p_start, p_end);
painter.resetTransform();
}
dongfang
committed
//painter.setBrush(Qt::SolidPattern);
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);
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
committed
painter.setPen(instrumentEdgePen);
painter.drawRoundedRect(digitalCompassRect, instrumentEdgePen.widthF()*2/3, instrumentEdgePen.widthF()*2/3);
dongfang
committed
if (this->heading == UNKNOWN_ATTITUDE)
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
committed
QPen pen;
pen.setWidthF(lineWidth);
pen.setColor(Qt::white);
painter.setPen(pen);
dongfang
committed
drawTextCenter(painter, s_digitalCompass, largeTextSize, 0, -radius*0.38-digitalCompassUpshift);
dongfang
committed
// dummy
// navigationTargetBearing = 10;
// navigationCrosstrackError = 500;
// The CDI
if (shouldDisplayNavigationData() && navigationTargetBearing != UNKNOWN_ATTITUDE && !isinf(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;
if (abs(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.
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));
}
}
dongfang
committed
void PrimaryFlightDisplay::drawAltimeter(
QPainter& painter,
QRectF area, // the area where to draw the tape.
float secondaryAltitude,
dongfang
committed
float vv
) {
dongfang
committed
painter.resetTransform();
fillInstrumentBackground(painter, area);
dongfang
committed
dongfang
committed
float secondaryAltitudeBoxHeight = mediumTextSize * 2;
// not yet implemented: Display of secondary altitude.
if (secondaryAltitude != UNKNOWN_ALTITUDE) {
effectiveHalfHeight-= secondaryAltitudeBoxHeight;
}
float markerHalfHeight = mediumTextSize*0.8;
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;
float markerTip = (tickmarkLeft*2+tickmarkRightMajor)/3;
float scaleCenterAltitude = primaryAltitude == UNKNOWN_ALTITUDE ? 0 : primaryAltitude;