GraphicsScene中的方位公式计算产生不稳定的结果

时间:2017-03-31 19:01:46

标签: c++ qt qgraphicsview qgraphicsscene

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

我对mainwindow.ui表单所做的只是:

  1. 将centralWidget设置为Width:530,Height:633

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

  3. 向X:210添加QDial,Y:530,宽度/高度:100,最大值:359,包装:true

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

    // 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)圆圈的左上象限已经死亡,没有点/线移动2)移动到中心的点不会跟随稳定的进展。有些点竞争中心而其他点落后。当我提到空间关系时,这就是我在评论中提到的。绘制的每条线应移动到在它之前绘制的线后面的中心,并且在之后绘制的线之前。

    另外两个被注释掉的公式在没有死象限方面会产生更接近的行为,但是它们都有一条镜像线被绘制,并且每一条线都因某种原因而闪烁。

    我最好的猜测是,正在发生的是一些坐标混乱。我的另一个想法是,我可能没有正确地确定场景的中心以及向内绘制点的中心。

    你会注意到内部静态圆的绘制,我有一个“circCenter + 1”偏移。没有这个小的偏移,圆圈就不在中心位置。

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

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

1 个答案:

答案 0 :(得分:1)

我认为有几种方法可以简化这项工作,这会有所帮助。

  1. 我会在GraphicsScene中将0.0的所有内容集中在一起 - 这将使数学更加清晰。

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

  3. 尽可能使用内置的Qt几何图元(如QLineF和QPointF)来完成几何图形工作,而不是自己编辑。

  4. 我在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);
    }