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

标签 c++ qt qgraphicsview qgraphicsscene

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

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

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

  2. 将 QGraphicsView 显示小部件添加到 X:8,Y:8,宽度/高度:256。

  3. 添加一个 QDial 到 X:210,Y:530,宽度/高度:100,最大值:359,wrapping:true

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

//主窗口.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

//主窗口.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) 圆的左上象​​限是死的,根本没有点/线移动 2) 移动到中心的点没有遵循稳定的进程。有些点争先恐后地居中,而另一些则落后。当我提到空间关系时,这就是我在评论中所指的内容。绘制的每条线都应移动到在它之前绘制的线之后的中心,并在之后绘制的线之前。

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

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

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

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

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

最佳答案

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

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

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

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

我的解决方案的完整代码在 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);
}

关于c++ - GraphicsScene 中的轴承公式计算产生不稳定的结果,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/43147889/

相关文章:

c++ - 不能包含 <QtCharts/QLineSeries>

qt - QGraphicsView 中的折线和样条线

c++ - 固定 QGraphicsItem 位置,不改变场景中其他 QGraphicsItem 的行为

C++ 泛型编程 CRTP 基类继承自派生类型提供的类

c++ - 为什么这个简单的逻辑不起作用?

c++ - QSerialPort readyRead() 信号不能正常工作

c++ - Qt:通过深拷贝访问列表中的数据结构是否应该比通过指针访问它慢得多

c++ - 视频不适合 QGraphicsView

c++ - 在模板类中通过引用传递

c++ - 为什么 std::initializer_list 会复制项目?