GraphicsScene 中的轴承公式计算产生不稳定的结果

Bearing Formula calculations in GraphicsScene producing erratic results

我使用基本方位计算得到了非常不可预测的结果。我需要做的是,当我根据刻度盘的角度绘制这些线条时,这些线条需要均匀地绘制到视图的中心。现在,我只是得到不稳定的结果,我希望这里有人可以阐明我的 问题。最好亲自看看结果。

我对 mainwindow.ui 表单所做的唯一事情是:

  1. 将 centralWidget 设置为宽度:530,高度:633

  2. 添加一个QGraphicsView显示控件到X:8,Y:8,Width/Height:256.

  3. 添加QDial X:210,Y:530,Width/Height:100,最大值:359,wrapping:true

所有其他默认值应该没问题。

//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);
}

这里有几点需要注意。首先在 getBearing 方法中,您会看到我成功使用的 3 个主要公式,尝试了许多其他公式,但这些是唯一产生连贯线的公式。我添加了一些注释,应该有助于概括这些公式的作用。第一个公式,没有注释掉,最接近我希望达到的效果。

它有两个主要问题:1) 圆的左上象​​限是死的,points/lines 根本没有移动 2) 移动到中心的点没有遵循稳定的进程。有些点争先恐后地居中,而另一些则落后。当我提到空间关系时,这就是我在评论中所指的内容。绘制的每条线都应移动到在它之前绘制的线之后的中心,并在之后绘制的线之前。

其他 2 个被注释掉的公式在没有死象限方面产生了更接近的行为,但它们都绘制了一条镜像线,并且每条线都出于某种原因闪烁。

我对正在发生的事情的最佳猜测是发生了一些坐标混乱。我的另一个想法是,也许我没有正确确定场景的中心和向内绘制的点的中心。

你会注意到在内部静态圆的绘图上,我有一个“circCenter + 1”偏移量。如果没有这个小偏移量,圆就不会完全位于中心。

我已经研究了 3 个星期了,希望得到一些帮助来解决这个问题。

*请原谅任何拼写不一致的地方,因为我必须手动将此代码从一台机器传输到另一台机器。

我认为有几种方法可以简化这一点,这会有所帮助。

  1. 我会在 GraphicsScene 中以 0,0 为中心 - 这将使数学更加清晰。

  2. 而不是使用一组 QLines,我真的认为你想要的是一组所有点都向原点移动的点。因此,与其使用多个 QGraphicsLineItem,不如使用一个 QGraphicsPathItem 并对路径进行更新。不是存储大量图形项,而是存储一组点 - 在我的示例中为 QList<QPointF> m_points

  3. 在可能的情况下,使用内置的 Qt 几何基元,如 QLineF 和 QPointF 来完成几何工作,而不是自己滚动。

我的解决方案的完整代码位于 https://gist.github.com/docsteer/64483cc8f44ca53565912c50d11cf4a9,但关键函数:

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);
}