使用基本轴承计算,我得到了非常不可预测的结果。 我需要做的是,当我根据表盘的角度绘制这些线条时,这些线条需要均匀地绘制到视图的中心。现在,我只是得到了不稳定的结果,我希望这里有人可以阐明我的 问题。最好自己看看结果。
我对主窗口.ui表单所做的唯一一件事是:
-
将 centralWidget 设置为 宽度:530,高度:633
-
将 QGraphicsView 显示小部件添加到 X: 8, Y: 8, 宽度/高度: 256。
-
将 QDial 添加到 X: 210, Y: 530, 宽度/高度: 100, 最大值: 359, 换行:真
所有其他默认值应该没问题。
//主窗口.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);
}
所以这里有几件事需要注意。首先在getInbearing方法中,您将看到我成功的3个主要公式,尝试了许多其他公式,但这些是唯一产生连贯线条的公式。我添加了一些注释,这些注释应该有助于概括这些公式的作用。 第一个公式,没有注释掉的公式,是最接近我希望实现的。
它有两个主要问题:1)圆的左上象限是死的,根本没有点/线移动2)移动到中心的点不遵循稳定的进展。有些点争先恐后,而另一些则落后。这就是我在评论中提到空间关系时所指的。 绘制的每条线都应移动到之前绘制的线后面的中心,以及之后绘制的线的前面。
被注释掉的其他 2 个公式在没有死象限方面产生了更接近的行为,但它们都绘制了一条镜像线,并且由于某种原因,每条线都会闪烁。
我对正在发生的事情的最佳猜测是有一些坐标混乱正在发生。我的另一个想法是,也许我没有正确确定场景的中心和向内绘制的点的中心。
您会注意到在绘制内部静态圆时,我有一个" circCenter + 1"偏移。如果没有这个小偏移量,圆就不完全在中心。
我已经在这个上面呆了 3 周了,希望得到一些帮助来解决这个问题。
*请原谅任何拼写不一致,因为我不得不手动将此代码从一台机器传输到另一台机器。
我认为有几种方法可以简化这一点,这会有所帮助。
-
我会在图形场景中将大约 0,0 的所有内容都集中在 - 这将使数学更加清晰。
-
与其使用一组 QLine,不如说我认为您追求的是一组全部向原点移动的点。因此,与其使用多个 QGraphicsLineItems,不如使用一个 QGraphicsPathItem 并对路径进行更新。与其存储大量图形项,不如存储一组点 -
。QList<QPointF> m_points
在我的示例中 -
在可能的情况下,使用内置的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);
}