地面站终端 App
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1580 lines
59 KiB

#include "PrimaryFlightDisplay.h"
#include "UASManager.h"
//#include "ui_primaryflightdisplay.h"
#include <QDebug>
#include <QRectF>
#include <cmath>
#include <QPen>
#include <QPainter>
#include <QPainterPath>
#include <QResizeEvent>
#include <QtCore/qmath.h>
//#include <cmath>
#define SEPARATE_COMPASS_ASPECTRATIO (3.0f/4.0f)
#define LINEWIDTH 0.0036f
//#define TAPES_TEXT_SIZE 0.028
//#define AI_TEXT_SIZE 0.040
//#define AI_TEXT_MIN_PIXELS 12
//#define AI_TEXT_MAX_PIXELS 36
//#define PANELS_TEXT_SIZE 0.030
//#define COMPASS_SCALE_TEXT_SIZE 0.16
#define SMALL_TEXT_SIZE 0.03f
#define MEDIUM_TEXT_SIZE (SMALL_TEXT_SIZE*1.2f)
#define LARGE_TEXT_SIZE (MEDIUM_TEXT_SIZE*1.2f)
#define SHOW_ZERO_ON_SCALES true
// all in units of display height
#define ROLL_SCALE_RADIUS 0.42f
#define ROLL_SCALE_TICKMARKLENGTH 0.04f
#define ROLL_SCALE_MARKERWIDTH 0.06f
#define ROLL_SCALE_MARKERHEIGHT 0.04f
// scale max. degrees
#define ROLL_SCALE_RANGE 60
// fraction of height to translate for each degree of pitch.
#define PITCHTRANSLATION 65.0
// 10 degrees for each line
#define PITCH_SCALE_RESOLUTION 5
#define PITCH_SCALE_MAJORWIDTH 0.1
#define 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.
#define PITCH_SCALE_WIDTHREDUCTION_FROM 30
#define PITCH_SCALE_WIDTHREDUCTION 0.3
#define 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.
#define COMPASS_DISK_MAJORTICK 10
#define COMPASS_DISK_ARROWTICK 45
#define COMPASS_DISK_MAJORLINEWIDTH 0.006
#define COMPASS_DISK_MINORLINEWIDTH 0.004
#define COMPASS_DISK_RESOLUTION 10
#define COMPASS_SEPARATE_DISK_RESOLUTION 5
#define COMPASS_DISK_MARKERWIDTH 0.2
#define COMPASS_DISK_MARKERHEIGHT 0.133
#define CROSSTRACK_MAX 1000
#define CROSSTRACK_RADIUS 0.6
#define TAPE_GAUGES_TICKWIDTH_MAJOR 0.25
#define TAPE_GAUGES_TICKWIDTH_MINOR 0.15
// The altitude difference between top and bottom of scale
#define ALTIMETER_LINEAR_SPAN 50
// every 5 meters there is a tick mark
#define ALTIMETER_LINEAR_RESOLUTION 5
// every 10 meters there is a number
#define 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
#define ALTIMETER_PROJECTED_SPAN 50
// every 5 meters there is a tick mark
#define ALTIMETER_PROJECTED_RESOLUTION 5
// every 10 meters there is a number
#define ALTIMETER_PROJECTED_MAJOR_RESOLUTION 10
// min. and max. vertical velocity
//#define ALTIMETER_PROJECTED
// min. and max. vertical velocity
#define ALTIMETER_VVI_SPAN 5
#define ALTIMETER_VVI_WIDTH 0.2
// Now the same thing for airspeed!
#define AIRSPEED_LINEAR_SPAN 15
#define AIRSPEED_LINEAR_RESOLUTION 1
#define AIRSPEED_LINEAR_MAJOR_RESOLUTION 5
#define UNKNOWN_BATTERY -1
#define UNKNOWN_ATTITUDE 0
#define UNKNOWN_ALTITUDE -1000
#define UNKNOWN_SPEED -1
#define UNKNOWN_COUNT -1
#define UNKNOWN_GPSFIXTYPE -1
/*
*@TODO:
* global fixed pens (and painters too?)
* 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);
}
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),
altimeterMode(GPS_MAIN),
altimeterFrame(ASL),
speedMode(GROUND_MAIN),
roll(UNKNOWN_ATTITUDE),
pitch(UNKNOWN_ATTITUDE),
// heading(NAN),
heading(UNKNOWN_ATTITUDE),
primaryAltitude(UNKNOWN_ALTITUDE),
GPSAltitude(UNKNOWN_ALTITUDE),
aboveHomeAltitude(UNKNOWN_ALTITUDE),
primarySpeed(UNKNOWN_SPEED),
groundspeed(UNKNOWN_SPEED),
verticalVelocity(UNKNOWN_ALTITUDE),
font("Bitstream Vera Sans"),
refreshTimer(new QTimer(this)),
navigationCrosstrackError(0),
navigationTargetBearing(UNKNOWN_ATTITUDE),
layout(COMPASS_INTEGRATED),
style(OVERLAY_HSI),
redColor(QColor::fromHsvF(0, 0.75, 0.9)),
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)),
// AIEdgePen(QColor::fromHsvF(0, 0, 0.65, 0.5)),
instrumentBackground(QColor::fromHsvF(0, 0, 0.3, 0.3)),
instrumentOpagueBackground(QColor::fromHsvF(0, 0, 0.3, 1.0))
{
setMinimumSize(120, 80);
setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
// Connect with UAS
connect(UASManager::instance(), SIGNAL(activeUASSet(UASInterface*)), this, SLOT(setActiveUAS(UASInterface*)));
if (UASManager::instance()->getActiveUAS() != NULL) setActiveUAS(UASManager::instance()->getActiveUAS());
// Refresh timer
refreshTimer->setInterval(updateInterval);
// connect(refreshTimer, SIGNAL(timeout()), this, SLOT(paintHUD()));
connect(refreshTimer, SIGNAL(timeout()), this, SLOT(update()));
}
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);
}
qreal constrain(qreal value, qreal min, qreal max) {
if (value<min) value=min;
else if(value>max) value=max;
return value;
}
void PrimaryFlightDisplay::resizeEvent(QResizeEvent *e) {
QWidget::resizeEvent(e);
qreal size = e->size().width();
//if(e->size().height()<size) size = e->size().height();
lineWidth = constrain(size*LINEWIDTH, 1, 6);
fineLineWidth = constrain(size*LINEWIDTH*2/3, 1, 2);
instrumentEdgePen.setWidthF(fineLineWidth);
//AIEdgePen.setWidthF(fineLineWidth);
smallTestSize = 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)
layout = COMPASS_SEPARATED;
else
layout = COMPASS_INTEGRATED;
*/
// qDebug("Width %d height %d decision %d", e->size().width(), e->size().height(), layout);
}
void PrimaryFlightDisplay::paintEvent(QPaintEvent *event)
{
// Event is not needed
// the event is ignored as this widget
// is refreshed automatically
Q_UNUSED(event);
//makeDummyData();
doPaint();
}
/*
* Interface towards qgroundcontrol
*/
/**
*
* @param uas the UAS/MAV to monitor/display with the HUD
*/
void PrimaryFlightDisplay::setActiveUAS(UASInterface* uas)
{
if (this->uas != NULL) {
// 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*, AltitudeMeasurementSource, 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)));
}
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)));
//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*, AltitudeMeasurementSource, 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)));
// 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);
if (!isnan(roll) && !isinf(roll) && !isnan(pitch) && !isinf(pitch) && !isnan(yaw) && !isinf(yaw))
{
// TODO: Units conversion? // Called from UAS.cc l. 646
this->roll = roll * (180.0 / M_PI);
this->pitch = pitch * (180.0 / M_PI);
yaw = yaw * (180.0 / M_PI);
if (yaw<0) yaw+=360;
this->heading = yaw;
}
// TODO: Else-part. We really should have an "attitude bad or unknown" indication instead of just freezing.
//qDebug("r,p,y: %f,%f,%f", roll, pitch, yaw);
}
/*
* TODO! Implementation or removal of this.
* Currently a dummy.
*/
void PrimaryFlightDisplay::updateAttitude(UASInterface* uas, int component, double roll, double pitch, double yaw, quint64 timestamp)
{
Q_UNUSED(uas);
Q_UNUSED(component);
Q_UNUSED(timestamp);
// Called from UAS.cc l. 616
if (!isnan(roll) && !isinf(roll) && !isnan(pitch) && !isinf(pitch) && !isnan(yaw) && !isinf(yaw)) {
this->roll = roll * (180.0 / M_PI);
this->pitch = pitch * (180.0 / M_PI);
yaw = yaw * (180.0 / M_PI);
if (yaw<0) yaw+=360;
this->heading = yaw;
}
// qDebug("(2) r,p,y: %f,%f,%f", roll, pitch, yaw);
}
/*
* TODO! Examine what data comes with this call, should we consider it airspeed, ground speed or
* should we not consider it at all?
*/
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)
{
Q_UNUSED(uas);
Q_UNUSED(timestamp);
groundspeed = speed;
if (!didReceivePrimarySpeed)
primarySpeed = speed;
}
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;
}
void PrimaryFlightDisplay::updateNavigationControllerErrors(UASInterface* uas, double altitudeError, double speedError, double xtrackError) {
Q_UNUSED(uas);
this->navigationAltitudeError = altitudeError;
this->navigationSpeedError = speedError;
this->navigationCrosstrackError = xtrackError;
}
/*
* 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;
}
void PrimaryFlightDisplay::drawTextCenter (
QPainter& painter,
QString text,
float pixelSize,
float x,
float y)
{
font.setPixelSize(pixelSize);
painter.setFont(font);
QFontMetrics metrics = QFontMetrics(font);
QRect bounds = metrics.boundingRect(text);
int flags = Qt::AlignCenter | Qt::TextDontClip; // For some reason the bounds rect is too small!
painter.drawText(x /*+bounds.x()*/ -bounds.width()/2, y /*+bounds.y()*/ -bounds.height()/2, bounds.width(), bounds.height(), flags, text);
}
void PrimaryFlightDisplay::drawTextLeftCenter (
QPainter& painter,
QString text,
float pixelSize,
float x,
float y)
{
font.setPixelSize(pixelSize);
painter.setFont(font);
QFontMetrics metrics = QFontMetrics(font);
QRect bounds = metrics.boundingRect(text);
int flags = Qt::AlignLeft | Qt::TextDontClip; // For some reason the bounds rect is too small!
painter.drawText(x /*+bounds.x()*/, y /*+bounds.y()*/ -bounds.height()/2, bounds.width(), bounds.height(), flags, text);
}
void PrimaryFlightDisplay::drawTextRightCenter (
QPainter& painter,
QString text,
float pixelSize,
float x,
float y)
{
font.setPixelSize(pixelSize);
painter.setFont(font);
QFontMetrics metrics = QFontMetrics(font);
QRect bounds = metrics.boundingRect(text);
int flags = Qt::AlignRight | Qt::TextDontClip; // For some reason the bounds rect is too small!
painter.drawText(x /*+bounds.x()*/ -bounds.width(), y /*+bounds.y()*/ -bounds.height()/2, bounds.width(), bounds.height(), flags, text);
}
void PrimaryFlightDisplay::drawTextCenterTop (
QPainter& painter,
QString text,
float pixelSize,
float x,
float y)
{
font.setPixelSize(pixelSize);
painter.setFont(font);
QFontMetrics metrics = QFontMetrics(font);
QRect bounds = metrics.boundingRect(text);
int flags = Qt::AlignCenter | Qt::TextDontClip; // For some reason the bounds rect is too small!
painter.drawText(x /*+bounds.x()*/ -bounds.width()/2, y+bounds.height() /*+bounds.y()*/, bounds.width(), bounds.height(), flags, text);
}
void PrimaryFlightDisplay::drawTextCenterBottom (
QPainter& painter,
QString text,
float pixelSize,
float x,
float y)
{
font.setPixelSize(pixelSize);
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);
}
qreal pitchAngleToTranslation(qreal viewHeight, float pitch) {
return pitch * viewHeight / PITCHTRANSLATION;
}
void PrimaryFlightDisplay::drawAIAirframeFixedFeatures(QPainter& painter, QRectF area) {
// red line from -7/10 to -5/10 half-width
// red line from 7/10 to 5/10 half-width
// red slanted line from -2/10 half-width to 0
// red slanted line from 2/10 half-width to 0
// red arrow thing under roll scale
// prepareTransform(painter, width, height);
painter.resetTransform();
painter.translate(area.center());
qreal w = area.width();
qreal h = area.height();
QPen pen;
pen.setWidthF(lineWidth * 1.5);
pen.setColor(redColor);
painter.setPen(pen);
float length = 0.15;
float side = 0.5;
// The 2 lines at sides.
painter.drawLine(QPointF(-side*w, 0), QPointF(-(side-length)*w, 0));
painter.drawLine(QPointF(side*w, 0), QPointF((side-length)*w, 0));
float rel = length/qSqrt(2);
// The gull
painter.drawLine(QPointF(rel*w, rel*w/2), QPoint(0, 0));
painter.drawLine(QPointF(-rel*w, rel*w/2), QPoint(0, 0));
// The roll scale marker.
QPainterPath markerPath(QPointF(0, -w*ROLL_SCALE_RADIUS+1));
markerPath.lineTo(-h*ROLL_SCALE_MARKERWIDTH/2, -w*(ROLL_SCALE_RADIUS-ROLL_SCALE_MARKERHEIGHT)+1);
markerPath.lineTo(h*ROLL_SCALE_MARKERWIDTH/2, -w*(ROLL_SCALE_RADIUS-ROLL_SCALE_MARKERHEIGHT)+1);
markerPath.closeSubpath();
painter.drawPath(markerPath);
}
inline qreal min4(qreal a, qreal b, qreal c, qreal d) {
if(b<a) a=b;
if(c<a) a=c;
if(d<a) a=d;
return a;
}
inline qreal max4(qreal a, qreal b, qreal c, qreal d) {
if(b>a) a=b;
if(c>a) a=c;
if(d>a) a=d;
return a;
}
void PrimaryFlightDisplay::drawAIGlobalFeatures(
QPainter& painter,
QRectF mainArea,
QRectF paintArea) {
painter.resetTransform();
painter.translate(mainArea.center());
qreal pitchPixels = pitchAngleToTranslation(mainArea.height(), pitch);
qreal gradientEnd = pitchAngleToTranslation(mainArea.height(), 60);
//painter.rotate(-roll);
//painter.translate(0, pitchPixels);
// QTransform forwardTransform;
//forwardTransform.translate(mainArea.center().x(), mainArea.center().y());
painter.rotate(-roll);
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);
skyPath.closeSubpath();
// TODO: The gradient is wrong now.
QLinearGradient skyGradient(0, -gradientEnd, 0, 0);
skyGradient.setColorAt(0, QColor::fromHsvF(0.6, 1.0, 0.7));
skyGradient.setColorAt(1, QColor::fromHsvF(0.6, 0.25, 0.9));
QBrush skyBrush(skyGradient);
painter.fillPath(skyPath, skyBrush);
QPainterPath groundPath(hzonRight);
groundPath.lineTo(maxx, maxy);
groundPath.lineTo(minx, maxy);
groundPath.lineTo(hzonLeft);
groundPath.closeSubpath();
QLinearGradient groundGradient(0, gradientEnd, 0, 0);
groundGradient.setColorAt(0, QColor::fromHsvF(0.25, 1, 0.5));
groundGradient.setColorAt(1, QColor::fromHsvF(0.25, 0.25, 0.5));
QBrush groundBrush(groundGradient);
painter.fillPath(groundPath, groundBrush);
QPen pen;
pen.setWidthF(lineWidth);
pen.setColor(greenColor);
painter.setPen(pen);
QPointF start(-mainArea.width(), 0);
QPoint end(mainArea.width(), 0);
painter.drawLine(start, end);
}
void PrimaryFlightDisplay::drawPitchScale(
QPainter& painter,
QRectF area,
float intrusion,
bool drawNumbersLeft,
bool drawNumbersRight
) {
// The area should be quadratic but if not width is the major size.
qreal w = area.width();
if (w<area.height()) w = area.height();
QPen pen;
pen.setWidthF(lineWidth);
pen.setColor(Qt::white);
painter.setPen(pen);
QTransform savedTransform = painter.transform();
// find the mark nearest center
int snap = qRound((double)(pitch/PITCH_SCALE_RESOLUTION))*PITCH_SCALE_RESOLUTION;
int _min = snap-PITCH_SCALE_HALFRANGE;
int _max = snap+PITCH_SCALE_HALFRANGE;
for (int degrees=_min; degrees<=_max; degrees+=PITCH_SCALE_RESOLUTION) {
bool isMajor = degrees % (PITCH_SCALE_RESOLUTION*2) == 0;
float linewidth = isMajor ? PITCH_SCALE_MAJORWIDTH : PITCH_SCALE_MINORWIDTH;
if (abs(degrees) > PITCH_SCALE_WIDTHREDUCTION_FROM) {
// we want: 1 at PITCH_SCALE_WIDTHREDUCTION_FROM and PITCH_SCALE_WIDTHREDUCTION at 90.
// That is PITCH_SCALE_WIDTHREDUCTION + (1-PITCH_SCALE_WIDTHREDUCTION) * f(pitch)
// where f(90)=0 and f(PITCH_SCALE_WIDTHREDUCTION_FROM)=1
// f(p) = (90-p) * 1/(90-PITCH_SCALE_WIDTHREDUCTION_FROM)
// or PITCH_SCALE_WIDTHREDUCTION + f(pitch) - f(pitch) * PITCH_SCALE_WIDTHREDUCTION
// or PITCH_SCALE_WIDTHREDUCTION (1-f(pitch)) + f(pitch)
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, pitch-degrees);
// TODO: Intrusion detection and evasion. That is, don't draw
// where the compass has intruded.
painter.translate(0, shift);
QPointF start(-linewidth*w, 0);
QPointF end(linewidth*w, 0);
painter.drawLine(start, end);
if (isMajor && (drawNumbersLeft||drawNumbersRight)) {
int displayDegrees = degrees;
if(displayDegrees>90) displayDegrees = 180-displayDegrees;
else if (displayDegrees<-90) displayDegrees = -180 - displayDegrees;
if (SHOW_ZERO_ON_SCALES || degrees) {
QString s_number; //= QString("%d").arg(degrees);
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);
}
}
painter.setTransform(savedTransform);
}
}
void PrimaryFlightDisplay::drawRollScale(
QPainter& painter,
QRectF area,
bool drawTicks,
bool drawNumbers) {
qreal w = area.width();
if (w<area.height()) w = area.height();
QPen pen;
pen.setWidthF(lineWidth);
pen.setColor(Qt::white);
painter.setPen(pen);
// We should really do these transforms but they are assumed done by caller.
// painter.resetTransform();
// painter.translate(area.center());
// painter.rotate(roll);
qreal _size = w * ROLL_SCALE_RADIUS*2;
QRectF arcArea(-_size/2, - _size/2, _size, _size);
painter.drawArc(arcArea, (90-ROLL_SCALE_RANGE)*16, ROLL_SCALE_RANGE*2*16);
// painter.drawEllipse(QPoint(0,0),200,200);
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);
}
}
}
}
void PrimaryFlightDisplay::drawAIAttitudeScales(
QPainter& painter,
QRectF area,
float intrusion
) {
// To save computations, we do these transformations once for both scales:
painter.resetTransform();
painter.translate(area.center());
painter.rotate(-roll);
QTransform saved = painter.transform();
drawRollScale(painter, area, true, true);
painter.setTransform(saved);
drawPitchScale(painter, area, intrusion, true, true);
}
void PrimaryFlightDisplay::drawAICompassDisk(QPainter& painter, QRectF area, float halfspan) {
float start = heading - halfspan;
float end = heading + halfspan;
int firstTick = ceil(start / COMPASS_DISK_RESOLUTION) * COMPASS_DISK_RESOLUTION;
int lastTick = floor(end / COMPASS_DISK_RESOLUTION) * COMPASS_DISK_RESOLUTION;
float radius = area.width()/2;
float innerRadius = radius * 0.96;
painter.resetTransform();
painter.setBrush(instrumentBackground);
painter.setPen(instrumentEdgePen);
painter.drawEllipse(area);
painter.setBrush(Qt::NoBrush);
QPen scalePen(Qt::black);
scalePen.setWidthF(fineLineWidth);
for (int tickYaw = firstTick; tickYaw <= lastTick; tickYaw += COMPASS_DISK_RESOLUTION) {
int displayTick = tickYaw;
if (displayTick < 0) displayTick+=360;
else if (displayTick>=360) displayTick-=360;
// yaw is in center.
float off = tickYaw - heading;
// wrap that to ]-180..180]
if (off<=-180) off+= 360; else if (off>180) off -= 360;
painter.translate(area.center());
painter.rotate(off);
bool drewArrow = false;
bool isMajor = displayTick % COMPASS_DISK_MAJORTICK == 0;
if (displayTick==30 || displayTick==60 ||
displayTick==120 || displayTick==150 ||
displayTick==210 || displayTick==240 ||
displayTick==300 || displayTick==330) {
// draw a number
QString s_number;
s_number.sprintf("%d", displayTick/10);
painter.setPen(scalePen);
drawTextCenter(painter, s_number, /*COMPASS_SCALE_TEXT_SIZE*radius*/ smallTestSize, 0, -innerRadius*0.75);
} else {
if (displayTick % COMPASS_DISK_ARROWTICK == 0) {
if (displayTick!=0) {
QPainterPath markerPath(QPointF(0, -innerRadius*(1-COMPASS_DISK_MARKERHEIGHT/2)));
markerPath.lineTo(innerRadius*COMPASS_DISK_MARKERWIDTH/4, -innerRadius);
markerPath.lineTo(-innerRadius*COMPASS_DISK_MARKERWIDTH/4, -innerRadius);
markerPath.closeSubpath();
painter.setPen(scalePen);
painter.setBrush(Qt::SolidPattern);
painter.drawPath(markerPath);
painter.setBrush(Qt::NoBrush);
drewArrow = true;
}
if (displayTick%90 == 0) {
// Also draw a label
QString name = compassWindNames[displayTick / 45];
painter.setPen(scalePen);
drawTextCenter(painter, name, mediumTextSize, 0, -innerRadius*0.75);
}
}
}
// draw the scale lines. If an arrow was drawn, stay off from it.
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);
painter.setPen(scalePen);
painter.drawLine(p_start, p_end);
painter.resetTransform();
}
painter.setPen(scalePen);
//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);
painter.setPen(instrumentEdgePen);
painter.drawRoundedRect(digitalCompassRect, instrumentEdgePen.widthF()*2/3, instrumentEdgePen.widthF()*2/3);
/* final safeguard for really stupid systems */
int digitalCompassValue = static_cast<int>(qRound((double)heading)) % 360;
QString s_digitalCompass;
s_digitalCompass.sprintf("%03d", digitalCompassValue);
QPen pen;
pen.setWidthF(lineWidth);
pen.setColor(Qt::white);
painter.setPen(pen);
drawTextCenter(painter, s_digitalCompass, largeTextSize, 0, -radius*0.38-digitalCompassUpshift);
// 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);
painter.setPen(pen);
painter.drawLine(QPointF(x, y), QPointF(x, -y));
}
}
/*
void PrimaryFlightDisplay::drawSeparateCompassDisk(QPainter& painter, QRectF area) {
float radius = area.width()/2;
float innerRadius = radius * 0.96;
painter.resetTransform();
painter.setBrush(instrumentOpagueBackground);
painter.setPen(instrumentEdgePen);
painter.drawEllipse(area);
painter.setBrush(Qt::NoBrush);
QPen scalePen(Qt::white);
scalePen.setWidthF(fineLineWidth);
for (int displayTick=0; displayTick<360; displayTick+=COMPASS_SEPARATE_DISK_RESOLUTION) {
// yaw is in center.
float off = displayTick - heading;
// wrap that to ]-180..180]
if (off<=-180) off+= 360; else if (off>180) off -= 360;
painter.translate(area.center());
painter.rotate(off);
bool drewArrow = false;
bool isMajor = displayTick % COMPASS_DISK_MAJORTICK == 0;
if (displayTick==30 || displayTick==60 ||
displayTick==120 || displayTick==150 ||
displayTick==210 || displayTick==240 ||
displayTick==300 || displayTick==330) {
// draw a number
QString s_number;
s_number.sprintf("%d", displayTick/10);
painter.setPen(scalePen);
drawTextCenter(painter, s_number, mediumTextSize, 0, -innerRadius*0.75);
} else {
if (displayTick % COMPASS_DISK_ARROWTICK == 0) {
if (displayTick!=0) {
QPainterPath markerPath(QPointF(0, -innerRadius*(1-COMPASS_DISK_MARKERHEIGHT/2)));
markerPath.lineTo(innerRadius*COMPASS_DISK_MARKERWIDTH/4, -innerRadius);
markerPath.lineTo(-innerRadius*COMPASS_DISK_MARKERWIDTH/4, -innerRadius);
markerPath.closeSubpath();
painter.setPen(scalePen);
painter.setBrush(Qt::SolidPattern);
painter.drawPath(markerPath);
painter.setBrush(Qt::NoBrush);
drewArrow = true;
}
if (displayTick%90 == 0) {
// Also draw a label
QString name = compassWindNames[displayTick / 45];
painter.setPen(scalePen);
drawTextCenter(painter, name, mediumTextSize, 0, -innerRadius*0.75);
}
}
}
// draw the scale lines. If an arrow was drawn, stay off from it.
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);
painter.setPen(scalePen);
painter.drawLine(p_start, p_end);
painter.resetTransform();
}
painter.setPen(scalePen);
//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);
QRectF headingNumberRect(-radius/3, radius*0.12, radius*2/3, radius*0.28);
painter.setPen(instrumentEdgePen);
painter.drawRoundedRect(headingNumberRect, instrumentEdgePen.widthF()*2/3, instrumentEdgePen.widthF()*2/3);
// if (heading < 0) heading += 360;
// else if (heading >= 360) heading -= 360;
int yawCompass = static_cast<int>(heading) % 360;
QString yawAngle;
yawAngle.sprintf("%03d", yawCompass);
QPen pen;
pen.setWidthF(lineWidth);
pen.setColor(Qt::white);
painter.setPen(pen);
drawTextCenter(painter, yawAngle, largeTextSize, 0, radius/4);
}
*/
void PrimaryFlightDisplay::drawAltimeter(
QPainter& painter,
QRectF area, // the area where to draw the tape.
float primaryAltitude,
float secondaryAltitude,
float vv
) {
painter.resetTransform();
fillInstrumentBackground(painter, area);
QPen pen;
pen.setWidthF(lineWidth);
pen.setColor(Qt::white);
painter.setPen(pen);
float h = area.height();
float w = area.width();
float secondaryAltitudeBoxHeight = mediumTextSize * 2;
// The height where we being with new tickmarks.
float effectiveHalfHeight = h*0.45;
// not yet implemented: Display of secondary altitude.
// if (isAirplane())
// 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 numbersLeft = 0.42*w;
float markerTip = (tickmarkLeft*2+tickmarkRightMajor)/3;
float scaleCenterAltitude = primaryAltitude == UNKNOWN_ALTITUDE ? 0 : primaryAltitude;
// altitude scale
#ifdef ALTIMETER_PROJECTED
float range = 1.2;
float start = scaleCenterAltitude - ALTIMETER_PROJECTED_SPAN/2;
float end = scaleCenterAltitude + ALTIMETER_PROJECTED_SPAN/2;
int firstTick = ceil(start / ALTIMETER_PROJECTED_RESOLUTION) * ALTIMETER_PROJECTED_RESOLUTION;
int lastTick = floor(end / ALTIMETER_PROJECTED_RESOLUTION) * ALTIMETER_PROJECTED_RESOLUTION;
for (int tickAlt = firstTick; tickAlt <= lastTick; tickAlt += ALTIMETER_PROJECTED_RESOLUTION) {
// a number between 0 and 1. Use as radians directly.
float r = range*(tickAlt-altitude)/(ALTIMETER_PROJECTED_SPAN/2);
float y = effectiveHalfHeight * sin(r);
scale = cos(r);
if (scale<0) scale = -scale;
bool hasText = tickAlt % ALTIMETER_PROJECTED_MAJOR_RESOLUTION == 0;
#else
float start = scaleCenterAltitude - ALTIMETER_LINEAR_SPAN/2;
float end = scaleCenterAltitude + ALTIMETER_LINEAR_SPAN/2;
int firstTick = ceil(start / ALTIMETER_LINEAR_RESOLUTION) * ALTIMETER_LINEAR_RESOLUTION;
int lastTick = floor(end / ALTIMETER_LINEAR_RESOLUTION) * ALTIMETER_LINEAR_RESOLUTION;
for (int tickAlt = firstTick; tickAlt <= lastTick; tickAlt += ALTIMETER_LINEAR_RESOLUTION) {
float y = (tickAlt-scaleCenterAltitude)*effectiveHalfHeight/(ALTIMETER_LINEAR_SPAN/2);
bool isMajor = tickAlt % ALTIMETER_LINEAR_MAJOR_RESOLUTION == 0;
#endif
painter.resetTransform();
painter.translate(area.left(), area.center().y() - y);
pen.setColor(tickAlt<0 ? redColor : Qt::white);
painter.setPen(pen);
if (isMajor) {
painter.drawLine(tickmarkLeft, 0, tickmarkRightMajor, 0);
QString s_alt;
s_alt.sprintf("%d", abs(tickAlt));
drawTextLeftCenter(painter, s_alt, mediumTextSize, numbersLeft, 0);
} else {
painter.drawLine(tickmarkLeft, 0, tickmarkRightMinor, 0);
}
}
QPainterPath markerPath(QPoint(markerTip, 0));
markerPath.lineTo(markerTip+markerHalfHeight, markerHalfHeight);
markerPath.lineTo(rightEdge, markerHalfHeight);
markerPath.lineTo(rightEdge, -markerHalfHeight);
markerPath.lineTo(markerTip+markerHalfHeight, -markerHalfHeight);
markerPath.closeSubpath();
painter.resetTransform();
painter.translate(area.left(), area.center().y());
pen.setWidthF(lineWidth);
pen.setColor(Qt::white);
painter.setPen(pen);
painter.setBrush(Qt::SolidPattern);
painter.drawPath(markerPath);
painter.setBrush(Qt::NoBrush);
pen.setColor(Qt::white);
painter.setPen(pen);
QString s_alt;
if(primaryAltitude == UNKNOWN_ALTITUDE)
s_alt.sprintf("---");
else
s_alt.sprintf("%3.0f", primaryAltitude);
float xCenter = (markerTip+rightEdge)/2;
drawTextCenter(painter, s_alt, /* TAPES_TEXT_SIZE*width()*/ mediumTextSize, xCenter, 0);
if (vv == UNKNOWN_ALTITUDE) return;
float vvPixHeight = -vv/ALTIMETER_VVI_SPAN * effectiveHalfHeight;
if (abs (vvPixHeight)<markerHalfHeight) return; // hidden behind marker.
float vvSign = vvPixHeight>0 ? 1 : -1; // reverse y sign
// QRectF vvRect(rightEdge - w*ALTIMETER_VVI_WIDTH, markerHalfHeight*vvSign, w*ALTIMETER_VVI_WIDTH, abs(vvPixHeight)*vvSign);
QPointF vvArrowBegin(rightEdge - w*ALTIMETER_VVI_WIDTH/2, markerHalfHeight*vvSign);
QPointF vvArrowEnd(rightEdge - w*ALTIMETER_VVI_WIDTH/2, vvPixHeight);
painter.drawLine(vvArrowBegin, vvArrowEnd);
// Yeah this is a repitition of above code but we are goigd to trash it all anyway, so no fix.
float vvArowHeadSize = abs(vvPixHeight - markerHalfHeight*vvSign);
if (vvArowHeadSize > w*ALTIMETER_VVI_WIDTH/3) vvArowHeadSize = w*ALTIMETER_VVI_WIDTH/3;
float xcenter = rightEdge-w*ALTIMETER_VVI_WIDTH/2;
QPointF vvArrowHead(xcenter+vvArowHeadSize, vvPixHeight - vvSign *vvArowHeadSize);
painter.drawLine(vvArrowHead, vvArrowEnd);
vvArrowHead = QPointF(xcenter-vvArowHeadSize, vvPixHeight - vvSign * vvArowHeadSize);
painter.drawLine(vvArrowHead, vvArrowEnd);
}
void PrimaryFlightDisplay::drawVelocityMeter(
QPainter& painter,
QRectF area,
float speed,
float secondarySpeed
) {
painter.resetTransform();
fillInstrumentBackground(painter, area);
QPen pen;
pen.setWidthF(lineWidth);
float h = area.height();
float w = area.width();
float effectiveHalfHeight = h*0.45;
float markerHalfHeight = mediumTextSize;
float leftEdge = instrumentEdgePen.widthF()*2;
float tickmarkRight = w-leftEdge;
float tickmarkLeftMajor = tickmarkRight-w*TAPE_GAUGES_TICKWIDTH_MAJOR;
float tickmarkLeftMinor = tickmarkRight-w*TAPE_GAUGES_TICKWIDTH_MINOR;
float numbersRight = 0.42*w;
float markerTip = (tickmarkLeftMajor+tickmarkRight*2)/3;
// Select between air and ground speed:
float centerScaleSpeed =
speed == UNKNOWN_SPEED ? 0 : speed;
float start = centerScaleSpeed - AIRSPEED_LINEAR_SPAN/2;
float end = centerScaleSpeed + AIRSPEED_LINEAR_SPAN/2;
int firstTick = ceil(start / AIRSPEED_LINEAR_RESOLUTION) * AIRSPEED_LINEAR_RESOLUTION;
int lastTick = floor(end / AIRSPEED_LINEAR_RESOLUTION) * AIRSPEED_LINEAR_RESOLUTION;
for (int tickSpeed = firstTick; tickSpeed <= lastTick; tickSpeed += AIRSPEED_LINEAR_RESOLUTION) {
pen.setColor(tickSpeed<0 ? redColor : Qt::white);
painter.setPen(pen);
float y = (tickSpeed-centerScaleSpeed)*effectiveHalfHeight/(AIRSPEED_LINEAR_SPAN/2);
bool hasText = tickSpeed % AIRSPEED_LINEAR_MAJOR_RESOLUTION == 0;
painter.resetTransform();
painter.translate(area.left(), area.center().y() - y);
if (hasText) {
painter.drawLine(tickmarkLeftMajor, 0, tickmarkRight, 0);
QString s_speed;
s_speed.sprintf("%d", abs(tickSpeed));
drawTextRightCenter(painter, s_speed, mediumTextSize, numbersRight, 0);
} else {
painter.drawLine(tickmarkLeftMinor, 0, tickmarkRight, 0);
}
}
QPainterPath markerPath(QPoint(markerTip, 0));
markerPath.lineTo(markerTip-markerHalfHeight, markerHalfHeight);
markerPath.lineTo(leftEdge, markerHalfHeight);
markerPath.lineTo(leftEdge, -markerHalfHeight);
markerPath.lineTo(markerTip-markerHalfHeight, -markerHalfHeight);
markerPath.closeSubpath();
painter.resetTransform();
painter.translate(area.left(), area.center().y());
pen.setWidthF(lineWidth);
pen.setColor(Qt::white);
painter.setPen(pen);
painter.setBrush(Qt::SolidPattern);
painter.drawPath(markerPath);
painter.setBrush(Qt::NoBrush);
pen.setColor(Qt::white);
painter.setPen(pen);
QString s_alt;
if (speed == UNKNOWN_SPEED)
s_alt.sprintf("---");
else
s_alt.sprintf("%3.1f", speed);
float xCenter = (markerTip+leftEdge)/2;
drawTextCenter(painter, s_alt, /* TAPES_TEXT_SIZE*width()*/ mediumTextSize, xCenter, 0);
}
#define TOP (1<<0)
#define BOTTOM (1<<1)
#define LEFT (1<<2)
#define RIGHT (1<<3)
#define TOP_2 (1<<4)
#define BOTTOM_2 (1<<5)
#define LEFT_2 (1<<6)
#define RIGHT_2 (1<<7)
void applyMargin(QRectF& area, float margin, int where) {
if (margin < 0.01) return;
QRectF save(area);
qreal consumed;
if (where & LEFT) {
area.setX(save.x() + (consumed = margin));
} else if (where & LEFT_2) {
area.setX(save.x() + (consumed = margin/2));
} else {
consumed = 0;
}
if (where & RIGHT) {
area.setWidth(save.width()-consumed-margin);
} else if (where & RIGHT_2) {
area.setWidth(save.width()-consumed-margin/2);
} else {
area.setWidth(save.width()-consumed);
}
if (where & TOP) {
area.setY(save.y() + (consumed = margin));
} else if (where & TOP_2) {
area.setY(save.y() + (consumed = margin/2));
} else {
consumed = 0;
}
if (where & BOTTOM) {
area.setHeight(save.height()-consumed-margin);
} else if (where & BOTTOM_2) {
area.setHeight(save.height()-consumed-margin/2);
} else {
area.setHeight(save.height()-consumed);
}
}
void setMarginsForInlineLayout(qreal margin, QRectF& panel1, QRectF& panel2, QRectF& panel3, QRectF& panel4) {
applyMargin(panel1, margin, BOTTOM|LEFT|RIGHT_2);
applyMargin(panel2, margin, BOTTOM|LEFT_2|RIGHT_2);
applyMargin(panel3, margin, BOTTOM|LEFT_2|RIGHT_2);
applyMargin(panel4, margin, BOTTOM|LEFT_2|RIGHT);
}
void setMarginsForCornerLayout(qreal margin, QRectF& panel1, QRectF& panel2, QRectF& panel3, QRectF& panel4) {
applyMargin(panel1, margin, BOTTOM|LEFT|RIGHT_2);
applyMargin(panel2, margin, BOTTOM|LEFT_2|RIGHT_2);
applyMargin(panel3, margin, BOTTOM|LEFT_2|RIGHT_2);
applyMargin(panel4, margin, BOTTOM|LEFT_2|RIGHT);
}
inline qreal tapesGaugeWidthFor(qreal containerWidth, qreal preferredAIWidth) {
qreal result = (containerWidth - preferredAIWidth) / 2.0f;
qreal minimum = containerWidth / 5.5f;
if (result < minimum) result = minimum;
return result;
}
void PrimaryFlightDisplay::doPaint() {
QPainter painter;
painter.begin(this);
painter.setRenderHint(QPainter::Antialiasing, true);
painter.setRenderHint(QPainter::HighQualityAntialiasing, true);
qreal margin = height()/100.0f;
// The AI centers on this area.
QRectF AIMainArea;
// The AI paints on this area. It should contain the AIMainArea.
QRectF AIPaintArea;
QRectF compassArea;
QRectF altimeterArea;
QRectF velocityMeterArea;
QRectF sensorsStatsArea;
QRectF linkStatsArea;
QRectF sysStatsArea;
QRectF missionStatsArea;
painter.fillRect(rect(), Qt::black);
qreal tapeGaugeWidth;
qreal compassHalfSpan = 180;
float compassAIIntrusion = 0;
switch(layout) {
/*
case FEATUREPANELS_IN_CORNERS: {
tapeGaugeWidth = tapesGaugeWidthFor(width(), height());
// A layout optimal for a container wider than it is high.
// The AI gets full height and if tapes are transparent, also full width. If tapes are opague, then
// the AI gets a width to be perfectly square.
AIMainArea = QRectF(
style == NO_OVERLAYS ? tapeGaugeWidth : 0,
0,
style == NO_OVERLAYS ? width() - tapeGaugeWidth * 2: width(),
height());
AIPaintArea = AIMainArea;
// Tape gauges get so much width that the AI area not covered by them is perfectly square.
qreal sidePanelsHeight = height();
altimeterArea = QRectF(AIMainArea.right(), height()/5, tapeGaugeWidth, sidePanelsHeight*3/5);
velocityMeterArea = QRectF (0, height()/5, tapeGaugeWidth, sidePanelsHeight*3/5);
sensorsStatsArea = QRectF(0, 0, tapeGaugeWidth, sidePanelsHeight/5);
linkStatsArea = QRectF(AIMainArea.right(), 0, tapeGaugeWidth, sidePanelsHeight/5);
sysStatsArea = QRectF(0, sidePanelsHeight*4/5, tapeGaugeWidth, sidePanelsHeight/5);
missionStatsArea =QRectF(AIMainArea.right(), sidePanelsHeight*4/5, tapeGaugeWidth, sidePanelsHeight/5);
if (style == NO_OVERLAYS) {
applyMargin(AIMainArea, margin, TOP|BOTTOM);
applyMargin(altimeterArea, margin, TOP|BOTTOM|RIGHT);
applyMargin(velocityMeterArea, margin, TOP|BOTTOM|LEFT);
setMarginsForCornerLayout(margin, sensorsStatsArea, linkStatsArea, sysStatsArea, missionStatsArea);
}
// Compass is inside the AI ans within its margins also.
compassArea = QRectF(AIMainArea.x()+AIMainArea.width()*0.60, AIMainArea.y()+AIMainArea.height()*0.80,
AIMainArea.width()/2, AIMainArea.width()/2);
break;
}
case FEATUREPANELS_AT_BOTTOM: {
// A layout for containers with about the same width and height.
// qreal minor = min(width(), height());
// qreal major = max(width(), height());
qreal aiheight = height()*4.0f/5;
tapeGaugeWidth = tapesGaugeWidthFor(width(), aiheight);
AIMainArea = QRectF(
style == NO_OVERLAYS ? tapeGaugeWidth : 0,
0,
style == NO_OVERLAYS ? width() - tapeGaugeWidth*2 : width(),
aiheight);
AIPaintArea = AIMainArea;
// Tape gauges get so much width that the AI area not covered by them is perfectly square.
altimeterArea = QRectF(AIMainArea.right(), 0, tapeGaugeWidth, aiheight);
velocityMeterArea = QRectF (0, 0, tapeGaugeWidth, aiheight);
qreal panelsHeight = height() / 5.0f;
qreal panelsWidth = width() / 4.0f;
sensorsStatsArea = QRectF(0, AIMainArea.bottom(), panelsWidth, panelsHeight);
linkStatsArea = QRectF(panelsWidth, AIMainArea.bottom(), panelsWidth, panelsHeight);
sysStatsArea = QRectF(panelsWidth*2, AIMainArea.bottom(), panelsWidth, panelsHeight);
missionStatsArea =QRectF(panelsWidth*3, AIMainArea.bottom(), panelsWidth, panelsHeight);
if (style == NO_OVERLAYS) {
applyMargin(AIMainArea, margin, TOP|BOTTOM);
applyMargin(altimeterArea, margin, TOP|BOTTOM|RIGHT);
applyMargin(velocityMeterArea, margin, TOP|BOTTOM|LEFT);
setMarginsForInlineLayout(margin, sensorsStatsArea, linkStatsArea, sysStatsArea, missionStatsArea);
}
// Compass is inside the AI ans within its margins also.
compassArea = QRectF(AIMainArea.x()+AIMainArea.width()*0.60, AIMainArea.y()+AIMainArea.height()*0.80,
AIMainArea.width()/2, AIMainArea.width()/2);
break;
}
*/
case COMPASS_INTEGRATED: {
tapeGaugeWidth = tapesGaugeWidthFor(width(), width());
qreal aiheight = height();
qreal aiwidth = width()-tapeGaugeWidth*2;
if (aiheight > aiwidth) aiheight = aiwidth;
AIMainArea = QRectF(
tapeGaugeWidth,
0,
aiwidth,
aiheight);
AIPaintArea = QRectF(
0,
0,
width(),
height());
// Tape gauges get so much width that the AI area not covered by them is perfectly square.
velocityMeterArea = QRectF (0, 0, tapeGaugeWidth, aiheight);
altimeterArea = QRectF(AIMainArea.right(), 0, tapeGaugeWidth, aiheight);
if (style == NO_OVERLAYS) {
applyMargin(AIMainArea, margin, TOP|BOTTOM);
applyMargin(altimeterArea, margin, TOP|BOTTOM|RIGHT);
applyMargin(velocityMeterArea, margin, TOP|BOTTOM|LEFT);
setMarginsForInlineLayout(margin, sensorsStatsArea, linkStatsArea, sysStatsArea, missionStatsArea);
}
qreal compassRelativeWidth = 0.75;
qreal compassBottomMargin = 0.78;
qreal compassSize = compassRelativeWidth * AIMainArea.width(); // Diameter is this times the width.
qreal compassCenterY;
//if (heightSurplus >= 0) compassCenterY = AIMainArea.bottom() + compassSize/2;
compassCenterY = AIMainArea.bottom() + compassSize / 4;
if (height() - compassCenterY > AIMainArea.width()/2*compassBottomMargin)
compassCenterY = height()-AIMainArea.width()/2*compassBottomMargin;
// TODO: This is bad style...
compassCenterY = (compassCenterY * 2 + AIMainArea.bottom() + compassSize / 4) / 3;
compassArea = QRectF(AIMainArea.x()+(1-compassRelativeWidth)/2*AIMainArea.width(),
compassCenterY-compassSize/2,
compassSize,
compassSize);
if (height()-compassCenterY < compassSize/2) {
compassHalfSpan = acos((compassCenterY-height())*2/compassSize) * 180/M_PI + COMPASS_DISK_RESOLUTION;
if (compassHalfSpan > 180) compassHalfSpan = 180;
}
compassAIIntrusion = compassSize/2 + AIMainArea.bottom() - compassCenterY;
if (compassAIIntrusion<0) compassAIIntrusion = 0;
break;
}
case COMPASS_SEPARATED: {
// A layout for containers higher than their width.
tapeGaugeWidth = tapesGaugeWidthFor(width(), width());
qreal aiheight = width() - tapeGaugeWidth*2;
qreal panelsHeight = 0;
AIMainArea = QRectF(
tapeGaugeWidth,
0,
width()-tapeGaugeWidth*2,
aiheight);
AIPaintArea = style == OVERLAY_HSI ?
QRectF(
0,
0,
width(),
height() - panelsHeight) : AIMainArea;
velocityMeterArea = QRectF (0, 0, tapeGaugeWidth, aiheight);
altimeterArea = QRectF(AIMainArea.right(), 0, tapeGaugeWidth, aiheight);
/*
qreal panelsWidth = width() / 4.0f;
if(remainingHeight > width()) {
// very tall layout, place panels below compass.
sensorsStatsArea = QRectF(0, height()-panelsHeight, panelsWidth, panelsHeight);
linkStatsArea = QRectF(panelsWidth, height()-panelsHeight, panelsWidth, panelsHeight);
sysStatsArea = QRectF(panelsWidth*2, height()-panelsHeight, panelsWidth, panelsHeight);
missionStatsArea =QRectF(panelsWidth*3, height()-panelsHeight, panelsWidth, panelsHeight);
if (style == OPAGUE_TAPES) {
setMarginsForInlineLayout(margin, sensorsStatsArea, linkStatsArea, sysStatsArea, missionStatsArea);
}
compassCenter = QPoint(width()/2, (AIArea.bottom()+height()-panelsHeight)/2);
maxCompassDiam = fmin(width(), height()-AIArea.height()-panelsHeight);
} else {
// Remaining part is wider than high; place panels in corners around compass
// Naaah it is really ugly, do not do that.
sensorsStatsArea = QRectF(0, AIArea.bottom(), panelsWidth, panelsHeight);
linkStatsArea = QRectF(width()-panelsWidth, AIArea.bottom(), panelsWidth, panelsHeight);
sysStatsArea = QRectF(0, height()-panelsHeight, panelsWidth, panelsHeight);
missionStatsArea =QRectF(width()-panelsWidth, height()-panelsHeight, panelsWidth, panelsHeight);
if (style == OPAGUE_TAPES) {
setMarginsForCornerLayout(margin, sensorsStatsArea, linkStatsArea, sysStatsArea, missionStatsArea);
}
compassCenter = QPoint(width()/2, (AIArea.bottom()+height())/2);
// diagonal between 2 panel corners
qreal xd = width()-panelsWidth*2;
qreal yd = height()-panelsHeight - AIArea.bottom();
maxCompassDiam = qSqrt(xd*xd + yd*yd);
if (maxCompassDiam > remainingHeight) {
maxCompassDiam = width() - panelsWidth*2;
if (maxCompassDiam > remainingHeight) {
// If still too large, lower.
// compassCenter.setY();
}
}
}
*/
/*
sensorsStatsArea = QRectF(0, height()-panelsHeight, panelsWidth, panelsHeight);
linkStatsArea = QRectF(panelsWidth, height()-panelsHeight, panelsWidth, panelsHeight);
sysStatsArea = QRectF(panelsWidth*2, height()-panelsHeight, panelsWidth, panelsHeight);
missionStatsArea =QRectF(panelsWidth*3, height()-panelsHeight, panelsWidth, panelsHeight);
*/
QPoint compassCenter = QPoint(width()/2, AIMainArea.bottom()+width()/2);
qreal compassDiam = width() * 0.8;
compassArea = QRectF(compassCenter.x()-compassDiam/2, compassCenter.y()-compassDiam/2, compassDiam, compassDiam);
break;
}
}
bool hadClip = painter.hasClipping();
painter.setClipping(true);
painter.setClipRect(AIPaintArea);
drawAIGlobalFeatures(painter, AIMainArea, AIPaintArea);
drawAIAttitudeScales(painter, AIMainArea, compassAIIntrusion);
drawAIAirframeFixedFeatures(painter, AIMainArea);
// if(layout ==COMPASS_SEPARATED)
//drawSeparateCompassDisk(painter, compassArea);
// else
drawAICompassDisk(painter, compassArea, compassHalfSpan);
painter.setClipping(hadClip);
drawAltimeter(painter, altimeterArea, primaryAltitude, GPSAltitude, verticalVelocity);
drawVelocityMeter(painter, velocityMeterArea, primarySpeed, groundspeed);
/*
drawSensorsStatsPanel(painter, sensorsStatsArea);
drawLinkStatsPanel(painter, linkStatsArea);
drawSysStatsPanel(painter, sysStatsArea);
drawMissionStatsPanel(painter, missionStatsArea);
*/
/*
if (style == OPAGUE_TAPES) {
drawInstrumentBackground(painter, AIArea);
}
*/
painter.end();
}
void PrimaryFlightDisplay:: createActions() {}