Bearing Formula calculations in GraphicsScene producing erratic results
Asked Answered
W

1

0

I'm getting very unpredictable results using basic bearing calculations. What I need to happen is that as I draw these lines based on the angle of the dial, the lines need to uniformly get drawn into the center of the view. Right now, I'm just getting erratic results and I'm hoping someone here can elucidate my issues. Best to see the results for yourself.

Only thing I did to the mainwindow.ui form was:

  1. set centralWidget to Width: 530, Height: 633

  2. add a QGraphicsView display widget to X: 8, Y: 8, Width/Height: 256.

  3. add a QDial to X: 210, Y: 530, Width/Height: 100, maximum: 359, wrapping:true

All other default values should be fine.

//mainwindow.h

#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>
#include <QGraphicsScene>
#include <QGraphicsView>
#include <QLineF>
#include <QDial>

namespace Ui {
class MainWindow;
}

class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    explicit MainWindow(QWidget *parent = 0);
    ~MainWindow();

public slots:
    void slt_updateAngleFromDial(int angle);
private slots:
    void slt_drainTheBowl();

private:
    Ui::MainWindow *ui;
    QGraphicsScene *scene;
    QGraphicsView *view;
    QDial dial;
    QGraphicsLineItem *line;
    QGraphicsLineItem *green_needle;
    QGraphicsEllipseItem *mCircle;
    QGraphicsEllipseItem *cCircle;
    QList<QGraphicsLineItem*> m_line_list;
    QLineF green_line;
    QLineF history_line;
    QPointF sceneCenter;
    QPointF drawing_point;
    QPointF historyPointA;
    QPointF historyPointB;
    int historyPoint_count;
    int draw_Radius;
    int draw_angle;
    int viewSize;
    bool started;

    double getBearing(QPointF point);
    double getPointRange(double Xpos, double Ypos);
    QPointF calculate_Bearing_Range(double screenCenter, double bearing, double range, double offset);
    QPointF setPointPosition(double bearing, double range, double centerPos);
    QPointF getQPointOnDisplay(double bearing, double range);
    void addNewHistoryPoint(int drawBearing);
    void drawpath();
    void drainTheBowl_Timer();
    void addNewHistoryPoint(int drawBearing);
};
#endif //MAINWINDOW_H

//mainwindow.cpp

#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <QtMath>
#include <QTimer>

MainWindow::MainWindow(QWidget* parent) : QMainWindow(parent), ui(new Ui::MainWindow)
{
    ui->setupUi(this);
    scene = new QGraphicsScene(this);
    view = ui->graphicsView;
    view->setScene(scene);
    view->setSceneRect(0,0,512,512);
    view->setHorizontalScrollBarPolicy(Qt::ScrollbarAlwaysOff);
    view->setVerticalScrollBarPolicy(Qt::ScrollbarAlwaysOff);
    viewSize = view->width() /2;
    sceneCenter = QPointF(viewSize,viewSize);
    draw_Radius = 200;
    connect(ui->dial, &QDial::valueChanged, this, &MainWindow::slt_updateAngleFromDial);

    //add drawing line
    drawing_point = sceneCenter + QPointF(0,draw_Radius);
    green_line = QLineF(drawing_point, sceneCenter + QPointF(0, draw_Radius + 20));
    QPen dirLine(Qt::green, Qt::SolidLine);
    dirLine.setWidth(3);
    green_needle = scene->addLine(green_line, dirLine);
    green_needle->setTransformOriginPoint(sceneCenter);

    //draw static outer circle
    int mSize = draw_Radius *2;
    QRectF rimCircle(QPointF(mSize,mSize),QSize(mSize,mSize));
    int mCenter = rimCircle.center().x();
    mCircle = new QGraphicsEllipseItem(rimCircle);
    QBrush rimTip(Qt::darkCyan, Qt::NoBrush);
    QPen rimPen(Qt::darkCyan, Qt:;SolidLine);
    mCircle->setBrush(rimTip);
    mCircle->setPen(rimPen);
    mCircle->setPos(setPointPosition(0,0, mCenter));
    scene->addItem(mCircle);

    //draw static inner circle
    int cSize = 3;
    QRectF circ(QPointF(cSize,cSize),QSize(cSize,cSize));
    int circCenter = circ.center().x();
    cCircle = new QGraphicsEllipseItem(circ);
    QBrush rimTip2(Qt::black, Qt::SolidPattern);
    QPen rimPen2(Qt::black, Qt:;SolidLine);
    cCircle->setBrush(rimTip2);
    cCircle->setPen(rimPen2);
    cCircle->setPos(setPointPosition(0,0, circCenter + 1));// +1 offset to get to center
    scene->addItem(cCircle);

    started = false;
    historyPoint_count = 0;
    draw_angle = 0;
    drainTheBowl_Timer();
    view->centerOn(sceneCenter);

}

MainWindow::~MainWindow(){delete ui;}

void MainWindow::slt_updateAngleFromDial(int angle){
    draw_angle = angle;
    green_needle->setRotation(draw_angle);
}

QPointF MainWindow::setPointPosition(double bearing, double range, double centerPos){
    double pos = viewSize - centerPos;
    QPointF newPoint = calculate_Bearing_Range(pos,bearing,range,90);
    return newPoint;
}

//using info, get new position in scene, account for offset
QPointF MainWindow::calculate_Bearing_Range(double screenCenter, double bearing, double range, double offset){
    double oldX = screenCenter;
    double oldY = oldX;
    double newX = oldX + qCos(qDegreesToRadians(bearing - offset)) * range;
    double newY = oldY + qSin(qDegreesToRadians(bearing - offset)) * range;
    QPointF pos = QPointF(newX, newY);
    return pos;
 }

double MainWindow::getBearing(QPointF point){
    double cX = viewSize;
    double cY = cX;
    double nX = point.x();
    double nY = point.y();

    /** Inverted Y parameter of atan2
    correct look (no mirroring-no blinking), but upper quadrant dead, spatial relationships horrible*/
    double bearing = qRadiansToDegrees(M_PI_2 - atan2(cY - nY, nX - cX)); 

    /** "Correct" Bearing formula
    left quadrants move at correct speed for the most part, right quad speeds to center, best spatial relationships, but not perfect*/
    //double bearing = qRadiansToDegrees(M_PI_2 - atan2(nY - cY, nX - cX));

    /** Invert both parameters of atan2
    no dead quadrants, but mirrored and blinking, spatial relationships horrible*/
    //double bearing = qRadiansToDegrees(M_PI_2 - atan2(cY - nY, cX - nX));

    if(bearing < 0)
        bearing += 360;

    return bearing;
} 

double Mainwindow::getPointRange(double xPos, double yPos){
    double centerX = viewSize;
    double center = centerX;
    double newX = centerX - Xpos;
    double newY = center - Ypos;
    //pythagoros
    double distance = qPow(newX,2) + qPow(newY,2);
    double range = qSqrt(distance);
    return range;
 }

//gather 2 points from angle of dial to draw a line
void MainWindow::addNewHistoryPoint(int drawBearing){
    double pos = viewSize;
    double range = draw_Radius;
    QPointF pt = calculate_Bearing_Range(pos, drawBearing, range, -90);//align to draw point
    historyPoint_count++;

    switch(historyPoint_count){
        case 1:
            historyPointA = pt;
            break;
        case 2:
            historyPointB = pt;
            historyPoint_count = 0;
            break;
     }
}

void MainWindow::drainTheBowl_Timer(){
    QTimer* drainTimer = new QTimer(this);
    connect(drainTimer, SIGNAL(timeout()), this, SLOT(slt_drainTheBowl()));
    drainTimer->start(100);
}

//perform all updates
void MainWindow::slt_drainTheBowl(){
   //always add new points for continuous line
   addNewHistoryPoint(draw_angle);

   //handle moving lines to center
   foreach(QGraphicsLineItem* line, m_line_list){

        //get coordinates of the 2 points for this line
        QLineF adjLine = line->line();
        int adjLine_pt1_x = adjLine.p1().x();
        int adjLine_pt1_y = adjLine.p1().y();
        int adjLine_pt2_x = adjLine.p2().x();
        int adjLine_pt2_y = adjLine.p2().y();

        //find range of the points
        double pt1_range = getPointRange( adjLine_pt1_x, adjLine_pt1_y);
        double pt2_range = getPointRange( adjLine_pt2_x, adjLine_pt2_y);

        //reduce the range towards center
        pt1_range = (pt1_range - 1);
        pt2_range = (pt2_range - 1);

        //determine bearing of the points
        double pt1Bearing = qRound(getBearing(QPointF(adjLine_pt1_x, adjLine_pt1_y)));
        double pt2Bearing = qRound(getBearing(QPointF(adjLine_pt2_x, adjLine_pt2_y)));

        QPointF newOffset1;
        QPointF newOffset2;

        //handle how points get to center
        if(pt1_range > 1.0)
            newOffset1 = QPointF(getQPointOnDisplay(pt1Bearing, pt1_range));
        else{
            pt1_range = 0.0;
            newOffset1 = QPointF(getQPointOnDisplay(pt1Bearing, pt1_range));
        }

        if(pt2_range > 1.0)
            newOffset2 = QPointF(getQPointOnDisplay(pt2Bearing, pt2_range));
        else{
            pt2_range = 0.0;
            newOffset2 = QPointF(getQPointOnDisplay(pt2Bearing, pt2_range));
            //! scene->removeItem(line); //remove line generates errors
            //! m_line_list.removeFirst();// because points don't get to center in order, everything breaks
        }

        //apply new adjustments to this line
        adjLine.setP1(newOffset1);
        adjLine.setPt1(newOffset2);
        line->setline(adjLine);
   } 

   drawPath();//connect the dots
}

//track the tip of the needle for drawing
QPointF MainWindow::getQPointOnDisplay(double bearing, double range){
    int offset = 90; 
    double pos = viewSize;
    QPointF newPoint = calculate_Bearing_Range(pos, bearing, range, offset);
    return newPoint;
}

//draw the new line segment base on history points gathered above
void MainWindow:drawPath(){
    history_line = QLineF(historyPointA, historyPointB);
    QPen mainline(Qt::blue, Qt::SolidLine);
    mainline.setWidth(2);
    line = scene->addLine(history_line, mainline);

    //remove the initial line drawn at 0,0
    if(!started){
         scene->removeItem(line);
         started = true;
    }
    m_line_list.append(line);
}

So a couple of things to note here. First inside of the getBearing method, you'll see the 3 main formulas I have had success with, tried many others, but these are the only ones that produced coherent lines. I added some comments that should help generalize what those formulas are doing. The first formula, the one not commented out, is the closest to what I'm hoping to achieve.

Two main things wrong with it: 1) the upper left quadrant of the circle is dead, no points/lines move at all 2) the points that do move to center do not follow a steady progression. Some points race to center while others drag behind. This is what I'm referring to in my comments when I mention the spatial relationships. Each line drawn should move to center behind the line drawn before it, and ahead of the line drawn afterwards.

The other 2 formulas that are commented out produce a closer behavior in regards to there being no dead quadrants, but they both have a mirrored line being drawn, and every line blinks for some reason.

My best guess as to what is happening is there is some coordinate confusion going on. Another thought I had is that maybe I'm not correctly determining the center of the scene and the center of the points being drawn inwards.

You'll notice on the drawing of the inner static circle, I have a " circCenter + 1" offset. Without this small offset, the circle is not quite in the center.

I've been on this for 3 weeks now and would love some assistance figuring this out.

*Please forgive any spelling inconsistencies as I had to transfer this code by hand from one machine to the other.

Winkelman answered 31/3, 2017 at 19:1 Comment(0)
S
1

I think there are a few ways to simplify this which would help.

  1. I would center everything about 0,0 in the GraphicsScene - that will make the mathematics more clear.

  2. Rather than using a set of QLines, really I think what you after is a set of points which all move towards the origin. So rather than using multiple QGraphicsLineItems, use one QGraphicsPathItem and do the updates to the path. Instead of storing lots of graphics items, store a set of points - QList<QPointF> m_points in my example.

  3. Where possible, use inbuilt Qt geometry primitives like QLineF and QPointF to do the geometry work rather than rolling your own.

Full code for my solution at https://gist.github.com/docsteer/64483cc8f44ca53565912c50d11cf4a9, but the key function:

void MainWindow::slt_drainTheBowl()
{
    // Move the points towards center
    QMutableListIterator<QPointF> i(m_points);
    while(i.hasNext())
    {
        i.next();
        QLineF line(QPointF(0,0), i.value());
        // We move a point by decreasing the length from the origin to the point by 1
        qreal length = line.length();
        length -=1;
        line.setLength(length);
        // If the point is now at (or past) the origin, remove from the list
        if(length<=0)
        {
            i.remove();
        }
        else
        {
            // Update the point in the list
            i.setValue(line.p2());
        }
    }


    // Add a new point to the list based on the current angle
    QPointF newPoint;
    newPoint.setY( qSin(qDegreesToRadians((double)draw_angle)) * 200 );
    newPoint.setX( qCos(qDegreesToRadians((double)draw_angle)) * 200 );


    // Set the points into the path item
    QPainterPath path;
    path.moveTo(newPoint);
    for(int i=0; i<m_points.count(); i++)
        path.lineTo(m_points[i]);

    m_points << newPoint;

    m_pathItem->setPath(path);
}
Still answered 1/4, 2017 at 4:29 Comment(2)
Wow, that cleans things up a lot, thank you. For some reason however, centerOn(0,0) isn't working. The entire circle stays up in the left corner and I can't get it centered in the view. If I use fitInView I can at least see the whole circle, but this presents other issues. Thank you again, I feel like a fool for wasting so much of my time. I still would love to know why the bearing calculations were wonky, but I'm just glad to see it finally working the way it should.Winkelman
Yep, I noticed that centerOn() didn't work also, not quite sure why but if you put it outside the constructor, like in a resizeEvent(), it works. Not 100% sure why your original calculations wouldn't work but simplifying things wherever possible usually helps me out :)Still

© 2022 - 2024 — McMap. All rights reserved.