Skip to content

Commit

Permalink
Add sensor preview widget
Browse files Browse the repository at this point in the history
Draw sensor position like an artifical horizon instrument.
This should be very intuitive to understand for accelerometer and
not much more difficult for gyroscope. Only draw the diagonal zones for
the XY plane to avoid cluttering.

Add the required interfaces to JoySensor. Use dummies for non-trivial
interfaces.
  • Loading branch information
mmmaisel committed May 5, 2022
1 parent 4d9aa62 commit 83feffe
Show file tree
Hide file tree
Showing 5 changed files with 336 additions and 0 deletions.
2 changes: 2 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,7 @@ set(antimicrox_SOURCES
src/joysensor.cpp
src/joysensorbuttonpushbutton.cpp
src/joysensorpushbutton.cpp
src/joysensorstatusbox.cpp
src/joystick.cpp
src/keyboard/virtualkeyboardmousewidget.cpp
src/keyboard/virtualkeypushbutton.cpp
Expand Down Expand Up @@ -315,6 +316,7 @@ set(antimicrox_HEADERS
src/joysensordirection.h
src/joysensorbuttonpushbutton.h
src/joysensorpushbutton.h
src/joysensorstatusbox.h
src/joysensortype.h
src/joystick.h
src/keyboard/virtualkeyboardmousewidget.h
Expand Down
22 changes: 22 additions & 0 deletions src/joysensor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,24 @@ JoySensorType JoySensor::getType() const { return m_type; }
*/
JoySensorDirection JoySensor::getCurrentDirection() const { return m_current_direction; }

/**
* @brief Get the assigned dead zone value.
* @return Assigned dead zone value in degree or degree/s.
*/
double JoySensor::getDeadZone() const { return m_dead_zone * 180 / M_PI; }

/**
* @brief Get the assigned diagonal range value.
* @return Assigned diagonal range in degree or degree/s.
*/
double JoySensor::getDiagonalRange() const { return m_diagonal_range * 180 / M_PI; }

/**
* @brief Get the assigned max zone value.
* @return Assigned max zone value in degree or degree/s.
*/
double JoySensor::getMaxZone() const { return m_max_zone * 180 / M_PI; }

/**
* @brief Get the value for the corresponding X axis.
* @return X axis value in m/s^2 for accelerometer or °/s for gyroscope.
Expand Down Expand Up @@ -203,6 +221,10 @@ QString JoySensor::sensorTypeName() const

bool JoySensor::inDeadZone(float *values) const { return false; }

double JoySensor::calculatePitch() const { return false; }

double JoySensor::calculateRoll() const { return false; }

double JoySensor::calculateDirectionalDistance(JoySensorDirection direction) const { return 0; }

/**
Expand Down
8 changes: 8 additions & 0 deletions src/joysensor.h
Original file line number Diff line number Diff line change
Expand Up @@ -53,12 +53,17 @@ class JoySensor : public QObject

JoySensorType getType() const;
JoySensorDirection getCurrentDirection() const;
double getDeadZone() const;
double getDiagonalRange() const;
double getMaxZone() const;
float getXCoordinate() const;
float getYCoordinate() const;
float getZCoordinate() const;
QString sensorTypeName() const;

bool inDeadZone(float *values) const;
double calculatePitch() const;
double calculateRoll() const;
double calculateDirectionalDistance(JoySensorDirection direction) const;

QHash<JoySensorDirection, JoySensorButton *> *getButtons();
Expand All @@ -84,6 +89,9 @@ class JoySensor : public QObject
void populateButtons();

JoySensorType m_type;
double m_dead_zone;
double m_diagonal_range;
double m_max_zone;

float m_current_value[3];
float m_pending_value[3];
Expand Down
254 changes: 254 additions & 0 deletions src/joysensorstatusbox.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,254 @@
/* antimicrox Gamepad to KB+M event mapper
* Copyright (C) 2022 Max Maisel <max.maisel@posteo.de>
*
* This program is free software: you can redistribute it and/or modify
* 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.
* This program is distributed in the hope that it will be useful,
* 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 this program. If not, see <http://www.gnu.org/licenses/>.
*/

#include "joysensorstatusbox.h"

#include "common.h"
#include "globalvariables.h"
#include "joyaxis.h"
#include "joysensor.h"

#include <qdrawutil.h>

#include <QDebug>
#include <QLinearGradient>
#include <QList>
#include <QPaintEvent>
#include <QPainter>
#include <QPainterPath>
#include <QSizePolicy>

JoySensorStatusBox::JoySensorStatusBox(QWidget *parent)
: QWidget(parent)
, m_sensor(nullptr)
{
}

/**
* @brief Sets the to be visualized sensor.
*
* It disconnects all events to the previous sensor if there was any, and
* connects them to the new sensor.
*/
void JoySensorStatusBox::setSensor(JoySensor *sensor)
{
if (m_sensor != nullptr)
{
disconnect(m_sensor, SIGNAL(deadZoneChanged(double)), this, nullptr);
disconnect(m_sensor, SIGNAL(moved(float, float, float)), this, nullptr);
disconnect(m_sensor, SIGNAL(diagonalRangeChanged(double)), this, nullptr);
disconnect(m_sensor, SIGNAL(maxZoneChanged(double)), this, nullptr);
}

m_sensor = sensor;
connect(m_sensor, SIGNAL(deadZoneChanged(double)), this, SLOT(update()));
connect(m_sensor, SIGNAL(moved(float, float, float)), this, SLOT(update()));
connect(m_sensor, SIGNAL(diagonalRangeChanged(double)), this, SLOT(update()));
connect(m_sensor, SIGNAL(maxZoneChanged(double)), this, SLOT(update()));

update();
}

/**
* @brief Get the visualized sensor object
*/
JoySensor *JoySensorStatusBox::getSensor() const { return m_sensor; }

QSize JoySensorStatusBox::sizeHint() const { return QSize(-1, -1); }

void JoySensorStatusBox::paintEvent(QPaintEvent *event)
{
Q_UNUSED(event);

PadderCommon::inputDaemonMutex.lock();
drawArtificialHorizon();
PadderCommon::inputDaemonMutex.unlock();
}

/**
* @brief draws the artificial horizon instrument on the screen
*/
void JoySensorStatusBox::drawArtificialHorizon()
{
QPainter paint(this);
paint.setRenderHint(QPainter::Antialiasing, true);

int side = qMin(width(), height());

QPen pen;
QPixmap pix(side, side);
pix.fill(Qt::transparent);
QPainter painter(&pix);
painter.setRenderHint(QPainter::Antialiasing, true);

// Switch to centric coordinate system
painter.translate(side / 2.0, side / 2.0);
painter.scale(side * 0.45, -side * 0.45);
painter.save();

// Draw moving instrument parts
QPainterPath clippingPath;
clippingPath.addEllipse(QPointF(0, 0), 1, 1);
painter.setClipPath(clippingPath);
JoySensorType type;

double pitch, roll, yaw;
if (m_sensor != nullptr)
{
type = m_sensor->getType();
if (type == ACCELEROMETER)
{
pitch = -m_sensor->calculatePitch() * 180 / M_PI;
roll = m_sensor->calculateRoll() * 180 / M_PI;
yaw = 0;
} else
{
pitch = -m_sensor->getXCoordinate();
roll = m_sensor->getYCoordinate();
yaw = -m_sensor->getZCoordinate();
}
} else
{
type = ACCELEROMETER;
pitch = 0;
roll = 0;
yaw = 0;
}

pitch = qBound(-180.0, pitch, 180.0);
roll = qBound(-180.0, roll, 180.0);
yaw = qBound(-180.0, yaw, 180.0);
painter.translate(yaw / 90, pitch / 90);
painter.rotate(roll);

pen.setColor(Qt::transparent);
painter.setPen(pen);
painter.setBrush(QBrush(QColor(64, 128, 255)));
painter.drawRect(QRectF(-10, 0, 20, 10));
painter.setBrush(QBrush(Qt::black));
painter.drawRect(QRectF(-10, -10, 20, 10));

// Draw dead zone
pen.setColor(Qt::red);
pen.setWidthF(0.02);
painter.setPen(pen);
painter.setBrush(QBrush(QColor(255, 0, 0, 128)));
double deadZone = m_sensor != nullptr ? m_sensor->getDeadZone() : 0.0;
painter.drawEllipse(QPointF(0, 0), deadZone / 90, deadZone / 90);

// Draw max zone
QPainterPath maxZonePath;
double maxZone = m_sensor != nullptr ? m_sensor->getMaxZone() : 0.0;
maxZonePath.addEllipse(QPointF(0, 0), 10, 10);
maxZonePath.addEllipse(QPointF(0, 0), maxZone / 90, maxZone / 90);
pen.setColor(Qt::darkGreen);
pen.setWidthF(0.02);
painter.setPen(pen);
painter.setBrush(QBrush(QColor(0, 128, 0, 128)));
painter.drawPath(maxZonePath);

// Draw diagonal zones
pen.setColor(Qt::green);
painter.setPen(pen);
painter.setBrush(QBrush(QColor(0, 255, 0, 128)));

double diagonalRange = m_sensor != nullptr ? m_sensor->getDiagonalRange() : 0.0;
if (type == GYROSCOPE)
{
for (int i = 0; i < 4; ++i)
{
painter.drawPie(QRectF(-maxZone / 90, -maxZone / 90, 2 * maxZone / 90, 2 * maxZone / 90),
(45 + 90 * i - diagonalRange / 2) * 16, diagonalRange * 16);
}
} else
{
painter.drawPie(QRectF(-maxZone / 90, -maxZone / 90, 2 * maxZone / 90, 2 * maxZone / 90),
(135 - diagonalRange / 2) * 16, (diagonalRange + 90) * 16);
painter.drawPie(QRectF(-maxZone / 90, -maxZone / 90, 2 * maxZone / 90, 2 * maxZone / 90),
(-45 - diagonalRange / 2) * 16, (diagonalRange + 90) * 16);
}

// Pitch scale: 30deg per line
pen.setColor(Qt::white);
pen.setWidthF(0.025);
painter.setPen(pen);
painter.setBrush(QBrush(Qt::transparent));
for (int j = -180; j <= 180; j += 30)
{
painter.drawLine(QPointF(-10, j / 90.0), QPointF(10, j / 90.0));
}

// Yaw scale: 30deg per line
if (type == GYROSCOPE)
{
pen.setColor(Qt::white);
pen.setWidthF(0.025);
painter.setPen(pen);
painter.setBrush(QBrush(Qt::transparent));
for (int j = -180; j <= 180; j += 30)
{
painter.drawLine(QPointF(j / 90.0, -10), QPointF(j / 90.0, 10));
}
}

// Draw fixed instrument parts
painter.restore();
painter.save();
pen.setColor(QColor(80, 80, 80));
pen.setWidthF(0.2);
painter.setPen(pen);
painter.setBrush(Qt::NoBrush);
painter.drawEllipse(QPointF(0, 0), 1, 1);

// Draw scale
pen.setWidthF(0.05);
pen.setColor(Qt::yellow);
painter.setPen(pen);

painter.drawLine(QPointF(0.3, 0), QPointF(0.2, 0));
painter.drawLine(QPointF(-0.3, 0), QPointF(-0.2, 0));
painter.drawArc(QRectF(-0.2, -0.2, 0.4, 0.4), 0 * 16, 180 * 16);
painter.drawPoint(QPointF(0, 0));

pen.setColor(Qt::white);
painter.setPen(pen);
for (int j = 0; j < 19; ++j)
{
painter.drawLine(QPointF(1, 0), QPointF(0.9, 0));
painter.rotate(10.0);
}

// Draw dead zone
painter.restore();
pen.setColor(Qt::red);
pen.setWidthF(0.1);
painter.setPen(pen);
painter.setOpacity(0.5);
painter.drawArc(QRectF(-1, -1, 2, 2), -16 * deadZone, 16 * deadZone * 2);
painter.drawArc(QRectF(-1, -1, 2, 2), 16 * (180 - deadZone), 16 * deadZone * 2);

// Draw max zone
pen.setColor(Qt::darkGreen);
painter.setPen(pen);
double tmpMaxZone = std::min(maxZone, 90.0);
painter.drawArc(QRectF(-1, -1, 2, 2), 16 * (90 - (90 - tmpMaxZone)), 16 * (90 - tmpMaxZone) * 2);
painter.drawArc(QRectF(-1, -1, 2, 2), 16 * (270 - (90 - tmpMaxZone)), 16 * (90 - tmpMaxZone) * 2);

// Draw to window
paint.setCompositionMode(QPainter::CompositionMode_SourceOver);
paint.drawPixmap(pix.rect(), pix);
}
50 changes: 50 additions & 0 deletions src/joysensorstatusbox.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/* antimicrox Gamepad to KB+M event mapper
* Copyright (C) 2022 Max Maisel <max.maisel@posteo.de>
*
* This program is free software: you can redistribute it and/or modify
* 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.
* This program is distributed in the hope that it will be useful,
* 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 this program. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once

#include <QSize>
#include <QWidget>

class JoySensor;
class QPaintEvent;

/**
* @brief The GUI sensor position indicator widget
*
* It shows the yaw and roll angles on an artificial horizon instrument.
* In case of a gyroscope, the inner parts has an additional horizontal
* degree of freedom.
*/
class JoySensorStatusBox : public QWidget
{
Q_OBJECT

public:
explicit JoySensorStatusBox(QWidget *parent = nullptr);

void setSensor(JoySensor *sensor);
JoySensor *getSensor() const;

virtual QSize sizeHint() const override;

protected:
virtual void paintEvent(QPaintEvent *event) override;
void drawArtificialHorizon();

private:
JoySensor *m_sensor;
};

0 comments on commit 83feffe

Please sign in to comment.