Transformations with Qt and C++ - c++

I am doing a program to draw an arc and move/rotate it according to the position of the mouse cursor :
This image illustrates the scene when the program is first run.
The following images illustrates what is displayed after a mouse click. The mouse click happened at the top right point of the line.
After getting the coordinates of the intersection point (between the line and the circle), I want to set the position of the center of the arc to the intersection point.
But, as you can see, the arc is not where I wish it was. Strangely, when I draw a rectangle whose topLeft point is at the intersection point, it works :
I guess the problem has to be with scene/parent/item coordinates... But I can't find where :/
Here is a sample of the code : (DrawingScene inherits QGraphicsScene)
void DrawingScene::drawStates(){
m_ellipse = new QGraphicsEllipseItem(80.0, 80.0,120.0,120.0);
addItem(m_ellipse);
m_arc = new GraphicArcItem(62.0, 62.0,50.0,50.0);
m_arc->setParentItem(m_ellipse);
m_arc->setStartAngle(0);
m_arc->setSpanAngle(270 * 16);
QLineF line_vertical(140.0,80.0,140.0,200.0);
addLine(line_vertical);
QLineF line_horizontal(80.0,140.0,200.0,140.0);
addLine(line_horizontal);
QLineF line(QPointF(62.0,62.0), QPointF(140.0,140.0));
m_lineToCenter = new QGraphicsLineItem(line);
addItem(m_lineToCenter);
}
void DrawingScene::mousePressEvent(QGraphicsSceneMouseEvent *event){
p1 = event->scenePos();
QLineF lineToCenter(QPointF(140.0,140.0), p1);
m_lineToCenter->setLine(lineToCenter);
QLineF horizontalLine(QPointF(140.0,140.0),QPointF(200.0,140.0));
double angleBetweenLines = horizontalLine.angleTo(lineToCenter);
double x = 60.0 * cos(angleBetweenLines * 3.14 / 180.0);
double y = -60.0 * sin(angleBetweenLines * 3.14 / 180.0);
QPointF intersectionPoint(x,y);
QPointF topLeft = intersectionPoint + QPointF(140.0,140.0);
addRect(QRectF(topLeft, QSizeF(60.0,60.0)));
m_arc->setPos(topLeft);
}
Any help would be more than welcome :)
edit :
Working code for moving the arc :
p1 = event->scenePos();
QLineF lineToCenter(QPointF(140.0,140.0), p1);//center of circle to mouse position
double angleBetweenPositions = lineToCenter.angleTo(m_lineToCenter->line());
m_lineToCenter->setLine(lineToCenter);
QLineF horizontalLine(QPointF(140.0,140.0),QPointF(200.0,140.0));
double angleBetweenLines = horizontalLine.angleTo(lineToCenter);
double x = 60.0 * cos(angleBetweenLines * 3.14 / 180.0);
double y = -60.0 * sin(angleBetweenLines * 3.14 / 180.0);
QPointF newPoint(x,y);
QPointF ellipse_center = m_ellipse->rect().center();
QPointF intersection_point = intersection_point + ellipse_center;
GraphicArcItem *arc2 = new GraphicArcItem(intersection_point.rx()- 25.0,
intersection_point.ry() - 25.0,50.0,50.0);
addItem(arc2);
m_arc->setPos(intersection_point.rx()-85.0, intersection_point.ry() - 85.0);//why 85 ??
Code for the rotation :
m_arc->setCurrentRotation(m_arc->getCurrentRotation() + angleBetweenPositions);
m_arc->setTransformOriginPoint(m_arc->getCenter());
m_arc->setRotation(m_arc->getCurrentRotation());
Edit : Here are the key parts of the code solving the problem :
/*Return the center point of the arc in the parent coordinates*/
QPointF GraphicArcItem::getCenter(){
int xCenter = rect().x() + rect().width()/2;
int yCenter = rect().y() + rect().height()/2;
QPointF center = /*mapToParent(*/QPointF(xCenter,yCenter)/*)*/;
return center;
}
p1 = event->scenePos();
QPointF ellipse_center = m_ellipse->rect().center();
QLineF lineToCenter(ellipse_center, p1);//center of circle to mouse position
double angleBetweenPositions = lineToCenter.angleTo(m_lineToCenter->line());
QLineF horizontalLine(ellipse_center,QPointF(200.0,140.0));
double angleBetweenLines = horizontalLine.angleTo(lineToCenter);
double x = 60.0 * cos(angleBetweenLines * 3.14 / 180.0);
double y = -60.0 * sin(angleBetweenLines * 3.14 / 180.0);
QPointF newPoint(x,y);
QPointF intersection_point = newPoint + ellipse_center;
m_arc->setPos(intersection_point.rx() - 85.0, intersection_point.ry() - 85.0);
m_arc->setCurrentRotation(angleBetweenPositions);
QPointF rotation_center = m_arc->mapFromItem(m_arc, m_arc->getCenter());
m_arc->setTransformOriginPoint(rotation_center);
m_arc->setRotation(m_arc->getCurrentRotation());

The rectangle and the arc have different parents (the scene is the parent of the rectangle and m_ellipse of the arc, hence the references of their coordinates are different. To test it just add a new arc/circle (different from m_arc and without using setParentItem(m_ellipse);) to the scene - it should have the correct screen position. To achieve the desired result I would suggest you to play with mapTo<something>/mapFrom<something> methods. I presume that mapToParent will do the trick, but you should check it anyway.

Related

BoundingRec from a line with inclination in Qt

I have redefined my own QGraphicsItem to show a LineString. I redefined this because I need to create my own boundingbox and painter redefined the abstract method.
Now I have this code:
QRectF myQGraphicsLine::boundingRect() const
{
double lx = qMin(myLine->getX(0), myLine->getX(1));
double rx = qMax(myLine->getX(0), myLine->getX(1));
double ty = qMin(-myLine->getY(0), -myLine->getY(1));
double by = qMax(-myLine->getY(0), -myLine->getY(1));
return QRectF(lx-size/2,ty, rx -lx + size, by-ty).;
}
void myQGraphicsLine::paint(QPainter * painter, const QStyleOptionGraphicsItem * option, QWidget * widget)
{
pen.setColor(Qt::red);
QLine line(myLine->getX(0), -myLine->getY(0), myLine->getX(1), -myLine->getY(1));
pen.setWidth(size);
painter->setPen(pen);
painter->setBrush(brush);
painter->drawLine(line);
}
This all work fine, but I have a little problem with the boundingRec.
If the line follows the x- or y-axis I get this result:
And in other position I get this:
and I need this:
Does anyone know any way to rotate the boundinRec? Thanks a lot!
I fixed the problem redefining QGraphicsItem::shape() method.
For use this, I created a QPoligonF with my line shape, in my case I used this function:
void myQGraphicsLine::createSelectionPolygon()
{
QPolygonF nPolygon;
QLineF line(myLine->getX(0), -myLine->getY(0), myLine->getX(1), -myLine->getY(1));
qreal radAngle = line.angle()* M_PI / 180; //This the angle of my line vs X axe
qreal dx = size/2 * sin(radAngle);
qreal dy = size/2 * cos(radAngle);
QPointF offset1 = QPointF(dx, dy);
QPointF offset2 = QPointF(-dx, -dy);
nPolygon << line.p1() + offset1
<< line.p1() + offset2
<< line.p2() + offset2
<< line.p2() + offset1;
selectionPolygon = nPolygon;
update();
}
The paint(),boundingRect() and shape() methods stayed like this:
QRectF myQGraphicsLine::boundingRect() const
{
return selectionPolygon.boundingRect();
}
void myQGraphicsLine::paint(QPainter * painter, const QStyleOptionGraphicsItem * option, QWidget * widget)
{
pen.setColor(Qt::red);
QLineF line(myLine->getX(0), -myLine->getY(0), myLine->getX(1), -myLine->getY(1));
pen.setWidth(size);
painter->setPen(pen);
painter->setBrush(brush);
painter->drawLine(line);
}
QPainterPath myQGraphicsLine::shape() const
{
QPainterPath path;
path.addPolygon(selectionPolygon);
return path;
}
Thanks all for your answers!!
A QRect or QRectf cannot be rotated. Its sides are always parallel to the vertical and horizontal axes. Think of it as a point (x, y) giving the upper left corner of the rectangle plus 'width' and 'height' members. No matter what transformation you perform on the QRect it will always interpret 'width' as a horizontal distance and 'height' as vertical distance.
You can extract the four corners of the QRect as points and then rotate those points, but there will be no way to convert the rotated points back to a QRect unless they just happen to be parallel to the sides of the viewport/window.

Qt Rotating text around its center point

I am using Qt 5.6, I want to draw a number of text labels around a circle and rotate the text labels to orientate the text according to its position around the circle, so 12 o'clock would have a 0 degree rotation, 3 o'clock would be rotated 90 degrees, 6 o'clock would be rotated 180 degrees etc.
I am aligning the text around its centre position:
void drawText(QPainter* pobjPainter, qreal fltX, qreal fltY
,int intFlags, const QString* pobjText) {
const qreal fltSize = 32767.0;
QPointF ptfCorner(fltX, fltY - fltSize);
if ( (intFlags & Qt::AlignHCenter) == Qt::AlignHCenter ) {
ptfCorner.rx() -= fltSize / 2.0;
} else if ( (intFlags & Qt::AlignRight) == Qt::AlignRight ) {
ptfCorner.rx() -= fltSize;
}
if ( (intFlags & Qt::AlignVCenter) == Qt::AlignVCenter ) {
ptfCorner.ry() += fltSize / 2.0;
} else if ( (intFlags & Qt::AlignTop) == Qt::AlignTop ) {
ptfCorner.ry() += fltSize;
}
QRectF rctPos(ptfCorner, QSizeF(fltSize, fltSize));
pobjPainter->drawText(rctPos, intFlags, *pobjText);
}
I would like to apply a rotation to the text.
I would like to reproduce something similar to what is shown:
http://www.informit.com/articles/article.aspx?p=1405545&seqNum=2
It seems that the rotate function rotates the entire painter canvas, so the coordinates must take into account the rotation which is really giving me a hard time. I want to position the text around the ellipse and then rotate it, how do I know what the coordinates should be?
Sticking with the clock example you could try something like...
virtual void paintEvent (QPaintEvent *event) override
{
QPainter painter(this);
double radius = std::min(width(), height()) / 3;
for (int i = 0; i < 12; ++i) {
int numeral = i + 1;
double radians = numeral * 2.0 * 3.141592654 / 12;
/*
* Calculate the position of the text centre as it would be required
* in the absence of a transform.
*/
QPoint pos = rect().center() + QPoint(radius * std::sin(radians), -radius * std::cos(radians));
/*
* Set up the transform.
*/
QTransform t;
t.translate(pos.x(), pos.y());
t.rotateRadians(radians);
painter.setTransform(t);
/*
* Specify a huge bounding rectangle centred at the origin. The
* transform should take care of position and orientation.
*/
painter.drawText(QRect(-(INT_MAX / 2), -(INT_MAX / 2), INT_MAX, INT_MAX), Qt::AlignCenter, QString("%1").arg(numeral));
}
}

drawtext doesn't work in Qt

i m working on a project that consist on creating an uml tool with qt and for now i have a problem with drawingtext on an arrow so this is my code :
void Arrow::paint(QPainter *painter, const QStyleOptionGraphicsItem * ,QWidget *)
{
if (myStartItem->collidesWithItem(myEndItem))
return;
QPen myPen = pen();
myPen.setColor(myColor);
qreal arrowSize = 20;
painter->setPen(myPen);
painter->setBrush(myColor);
QLineF centerLine(myStartItem->pos(), myEndItem->pos());
QPolygonF endPolygon = myEndItem->polygon();
QPointF p1 = endPolygon.first() + myEndItem->pos();
QPointF p2;
QPointF intersectPoint;
QLineF polyLine;
for (int i = 1; i < endPolygon.count(); ++i)
{
p2 = endPolygon.at(i) + myEndItem->pos();
polyLine = QLineF(p1, p2);
QLineF::IntersectType intersectType =
polyLine.intersect(centerLine, &intersectPoint);
if (intersectType == QLineF::BoundedIntersection)
break;
p1 = p2;
}
setLine(QLineF(intersectPoint, myStartItem->pos()));
double angle = ::acos(line().dx() / line().length());
if (line().dy() >= 0)
angle = (Pi * 2) - angle;
QPointF arrowP1 = line().p1() + QPointF(sin(angle + Pi / 3) * arrowSize,
cos(angle + Pi / 3) * arrowSize);
QPointF arrowP2 = line().p1() + QPointF(sin(angle + Pi - Pi / 3) * arrowSize,
cos(angle + Pi - Pi / 3) * arrowSize);
arrowHead.clear();
arrowHead << line().p1() << arrowP1 << arrowP2;
painter->drawLine(line());
//painter->drawPolygon(arrowHead);
if (isSelected())
{
painter->setPen(QPen(myColor, 1, Qt::DashLine));
QLineF myLine = line();
myLine.translate(0, 4.0);
painter->drawLine(myLine);
myLine.translate(0,-8.0);
painter->drawLine(myLine);
QPoint point = QPoint( 10, 20 );
painter->drawText( point, "You can draw text from a point..." );
}
}
and nothing happens i can draw the arrow but the text does not appear on the arrow what should i do ? please i need some help
IMO you did that wrong.
You should compose your graphics item using QGraphicsPathItem, QGraphicsPolygonItem, QGraphicsRectItem, and QGraphicsSimpleTextItem instead drawing everything yourself.
Just provide some root item responsible for managing children (lines text and polygons). It will be easier to do this properly.
Secondly your paint method is faulty. You should restore initial state of the painter!
And finally I'm pretty sure your problem is caused by incorrect implementation of boundingRect. It is quite common mistake when performing such complex drawing in paint method.

Qt How can add an item to scene with an offset?

I add a QGraphicsItem(bullet) to the base of a line. The advance method moves the item through the item making it seem as if it comes from the tip of line. After adding collision this doesn't work. Is there a way to add an offset to the setPos(x,y) value to make it appear at the tip of the line instead of the base.
Also the line rotates in a 360 degree angle so it needs to translate to where ever the line is pointing.
//function that adds item to base of line created
qreal dirx = m_FireTarget1.x()+140;
qreal diry = m_FireTarget1.y()-195;
qreal length = sqrt(dirx*dirx+diry*diry);
if (length!=0)
{
// normalized direction vector
qreal invLength= 1.0/length;
dirx *= invLength;
diry *= invLength;
// creating an angle perturbation of +/- 3°
qreal alphaPerturbation = static_cast<qreal>(qrand()%6-3) * M_PI / 180.0;
qreal xPerturbation = cos(alphaPerturbation);
qreal yPerturbation = sin(alphaPerturbation);
dirx = dirx*xPerturbation - diry*yPerturbation;
diry = diry*xPerturbation + dirx*yPerturbation;
GraphicsCircle * circle = new GraphicsCircle(dirx, diry, -140, 195);
addItem(circle);
The -140, 195 is the base of where line is created. Seems like I already did what you're saying to do I believe.
Let's say your line has a certain degreeAngle and you want to move the bullet to a certain distance in that direction, you'll have to do:
// cos and sin functions get radians angle as argument so you must convert it
radiansAngle = degreeAngle * PI / 180;
offsetX = distance * cos(radiansAngle);
offsetY = distance * sin(radiansAngle);
In your case this translate to:
qreal radiansAngle = line.angle() * M_PI / 180;
qreal offsetX = line.length() * cos(radiansAngle);
qreal offsetY = line.length() * sin(radiansAngle);
So your new position is the old one plus the offset:
qreal newX = -140 + offsetX;
qreal newY = 195 + offsetY;
And then I'm sorry but I can't understand how you pass arguments to the GraphicsCircle constructor but if GraphicsCircle * circle = new GraphicsCircle(dirx, diry, -140, 195); place the circle to the coordinates (-140,195), then you should use...
GraphicsCircle * circle = new GraphicsCircle(dirx, diry, newX, newY);
addItem(circle);
...to place it to the new coordinates.

How to make manually calculated orbital paths agree with Qt's ellipse drawing method?

I'm attempting to draw celestial bodies moving around on simplified, perfectly circular orbits. I'm also drawing the projected orbital paths these objects will take. However, the problem is that the actual path the objects take doesn't agree with the projection on zooming in closely enough.
Video demonstrating the issue: https://www.youtube.com/watch?v=ALSVfx48zXw
If zoomed out, the problem is non-existent, because the deviation is too small. The apparent size of the deviation appears to be affected primarily by the visible curvature of the circles - notice how the paths of the moons agree with their motion. If one were to zoom in so that the moons' projected paths appear close to straight lines, they would have the same pattern of deviations as the planet shows.
Coordinates calculating methods:
double getX (long int time) {
return orbit * cos(offset + time * speed);
}
double getY (long int time) {
return orbit * sin(offset + time * speed);
}
Projected orbit drawing:
ellipse = scene->addEllipse(system.starX-body.orbit,
system.starY-body.orbit,
body.orbit*2,body.orbit*2,greenPen,transBrush);
Drawing the celestial bodies where they actually appear:
ellipse = scene->addEllipse(-body.radius,
-body.radius,
body.radius*2,body.radius*2,blackPen,greenBrush);
ellipse->setFlag(QGraphicsItem::ItemIgnoresTransformations);
ellipse->setPos(system.starX+body.getX(date2days(game.date)),
system.starY+body.getY(date2days(game.date)));
How do I fix this so that the celestial bodies are always on the predicted curve?
EDIT1:
I have attempted using the suggested algorithm for drawing my own ellipse. The version adapted for use with Qt I reproduce here:
QPoint get_point(double a, double b, double theta, QPoint center)
{
QPoint point;
point.setX(center.x() + a * cos(theta));
point.setY(center.y() + b * sin(theta));
return point;
}
void draw_ellipse(double a, double b, QPoint center, double zoom_factor, QGraphicsScene * scene, QPen pen)
{
double d_theta = 1.0d / zoom_factor;
double theta = 0.0d;
int count = 2.0d * 3.14159265358979323846 / d_theta;
QPoint p1, p2;
p1 = get_point(a, b, 0.0f, center);
for (int i = 0; i <= count; i++)
{
theta += d_theta;
p2 = p1;
p1 = get_point(a, b, theta, center);
scene->addLine(p1.x(),p1.y(),p2.x(),p2.y(),pen);
}
}
The results weren't encouraging:
In addition to not looking pretty at zoom_factor 360, the application ran extremely sluggishly, using much more resources than previously.
EDIT2:
The improved version gives much better results, but still slow. Here is the code:
QPointF get_point(qreal a, qreal b, qreal theta, QPointF center)
{
QPointF point;
point.setX(center.x() + a * cos(theta));
point.setY(center.y() + b * sin(theta));
return point;
}
void draw_ellipse(qreal a, qreal b, QPointF center, qreal zoom_factor, QGraphicsScene * scene, QPen pen)
{
qreal d_theta = 1.0d / zoom_factor;
qreal theta = 0.0d;
int count = 2.0d * 3.14159265358979323846 / d_theta;
QPointF p1, p2;
p1 = get_point(a, b, 0.0f, center);
for (int i = 0; i <= count; i++)
{
theta = i * d_theta;
p2 = p1;
p1 = get_point(a, b, theta, center);
scene->addLine(p1.x(),p1.y(),p2.x(),p2.y(),pen);
}
}
It appears that Qt does not auto-adjust the drawing precision or 'sampling resolution'.
You could try to draw the ellipse yourself, by drawing a loop of lines. Increase the sample resolution of the drawing when you zoom in - i.e. make the sampled points closer to each other.
Take the parametric equation of an ellipse
x = a cos (theta), y = b sin (theta)
where a and b are the semi-major and semi-minor axes of the ellipse, and sample the points with it:
(pseudo C++-style code)
point get_point(float theta, point center)
{
return point(center.x + a * cos(theta), center.y + b * sin(theta));
}
void draw_ellipse(float a, float b, point center, float zoom_factor)
{
float d_theta = 1.0f / zoom_factor;
float theta = 0.0f;
int count = 2.0f * PI / d_theta;
point p1, p2;
p1 = get_point(0.0f, center);
for (int i = 0; i < count; i++)
{
theta += d_theta;
p2 = p1;
p1 = get_point(theta, center);
drawline(p1, p2);
}
}
Sorry if the code looks arbitrary (I'm not familiar with Qt), but you get the point.
Assuming that all of the parameters you pass to addEllipse are of sufficient resolution, the issue seems to be with how Qt renders ellipses. The discretization used in ellipse drawing is not dependent on the transformation matrix of the view.
When a QGraphicsItem is being rendered in a view, its paint method certainly has access to the paint device (in this case: a widget). It could certainly determine the proper discretization step in terms of angle. Even if a graphics item were to render using regular painter calls, the painter has the same information, and the paint device certainly has this information in full. Thus there's no reason for Qt to do what it does, I think. I'll have to trace into this code and see why it fails so badly.
The only fix is for you to implement your own ellipse item, and chose the discretization step and begin/end angles according to the viewport size at the time of rendering.
qreal is a double - so that shouldn't be an issue unless Qt is configured with -qreal float.