How to rotate a QGraphicsPixmap around a point according to mouseMoveEvent? - c++

I want rotate a QGraphicsPixmapItem around a point according to mouse position.
So i tried this:
void Game::mouseMoveEvent(QMouseEvent* e){
setMouseTracking(true);
QPoint midPos((sceneRect().width() / 2), 0), currPos;
currPos = QPoint(mapToScene(e->pos()).x(), mapToScene(e->pos()).y());
QPoint itemPos((midPos.x() - cannon->scenePos().x()), (midPos.y() - cannon->scenePos().y()));
double angle = atan2(currPos.y(), midPos.x()) - atan2(midPos.y(), currPos.x());
cannon->setTransformOriginPoint(itemPos);
cannon->setRotation(angle); }
But the pixmap moves a few of pixels.
I want a result like this:

Besides the mixup of degrees and radians that #rafix07 pointed out there is a bug in the angle calculation. You basically need the angle of the line from midPos to currPos which you calculate by
double angle = atan2(currPos.y() - midPos.y(), currPos.x() - midPos.x());
Additionally the calculation of the transformation origin assumes the wrong coordinate system. The origin must be given in the coordinate system of the item in question (see QGraphicsItem::setTransformOriginPoint), not in scene coordinates. Since you want to rotate around the center of that item it would just be:
QPointF itemPos(cannon->boundingRect().center());
Then there is the question whether midPos is actually the point highlighted in your image in the middle of the canon. The y-coordinate is set to 0 which would normally be the edge of the screen, but your coordinate system may be different.
I would assume the itemPos calculated above is just the right point, you only need to map it to scene coordinates (cannon->mapToScene(itemPos)).
Lastly I would strongly advise against rounding scene coordinates (which are doubles) to ints as it is done in the code by forcing it to QPoints instead of QPointFs. Just use QPointF whenever you are dealing with scene coordinates.

Related

Getting the QTransform of a resizable selection area

I have built a small custom qml item that is used as a selection area (something like the QRubberBand component provided in Qt Widgets). The item also give the ability to user to resize the content of the selection, so by grabbing the bottom corner of the selection rectangle it is possible to drag to enlarge the content. After the user has done resizing I would like to compute the QTransform matrix of the transformation. QTransform provides a convenient QTransform::scale method to get a scale transformation matrix (which I can use by comparing the width and height ratio with the previous size of the selection). The problem is that QTransform::scale assumes that the center point of the transformation is the center of the object, but I would like my transformation origin to be the top left of the selection (since the user is dragging from the bottom-right).
So for example, if I have the following code:
QRectF selectionRect = QRectF(QPointF(10,10), QPointF(200,100));
// let's resize the rectangle by changing its bottom-right corner
auto newSelectionRect = selectionRect;
newSelectionRect.setBottomRight(QPointF(250, 120));
QTransform t;
t.scale(newSelectionRect.width()/selectionRect.width(), newSelectionRect.height()/selectionRect.height());
The problem here is that if I apply the transformation t to my original selectionRect I don't get my new rectangle newSelectionRect back, but I get the following:
QRectF selectionRect = QRectF(QPointF(10,10)*sx, QPointF(200,100)*sy);
where sx and sy are the scale factors of the transform. I would like a way to compute the QTransform of my transformation that gives back newSelectionRect when applied to selectionRect.
The problem lies in this assumption:
QTransform::scale assumes that the center point of the transformation is the center of the object
All transformations performed by QTransform are referred to the origin of the axis, is just an application of various tranformation matrixes (https://en.wikipedia.org/wiki/Transformation_matrix):
Also, QTransform::translate (https://doc.qt.io/qt-5/qtransform.html#translate) states:
Moves the coordinate system dx along the x axis and dy along the y axis, and returns a reference to the matrix.
Thereby, what you are looking for is:
QTransform t;
t.translate(+10, +10); // Move the origin to the top left corner of the rectangle
t.scale(newSelectionRect.width()/selectionRect.width(), newSelectionRect.height()/selectionRect.height()); // scale
t.translate(-10, -10); // move the origin back to where it was
QRectF resultRect = t.mapRect(selectionRect); // resultRect == newSelectionRect!

World position pointed by view camera vector

I've implemented a fps camera based on the up, right and view vectors from this.
Right now I want to be able to interact with the world by placing cubes in a minecraft style.
My lookAt vector is the sum of the view vector and the camera position, so my first attempt was to draw a cube at lookAt, but this is causing a strange behaviour.
I compute every vector like in the web I mentioned (such that lookAt = camera_position + view_direction) but the cube drawn is always arround me. I've tried several things like actually placing it (rounding the lookAt) and it appears near the wanted position but not at the place i'm looking at.
Given these vectors, how can I draw that's centered at the position that my camera is looking but a little bit further (exactly like minecraft)?
but the cube drawn is always arround me.
Yeah and that's obvious. You place cubes on the sphere surface of radius view_direction with center at camera_position.
Given these vectors, how can I draw that's centered at the position
that my camera is looking but a little bit further (exactly like
minecraft)?
You need to place cubes at the intersection of the view vector with the scene geometry. In the simplest case, it can be just "ground" plane, so you need intersect view vector with "ground" plane. Then you need to round the intersection xyz coordinates to the nearest grid node xyz = round(xyz / cubexyz)*cubexyz where cubexyz - cube size.
Approximate code:
Vector3D intersectPoint(Vector3D rayVector, Vector3D rayPoint, Vector3D planeNormal, Vector3D planePoint) {
Vector3D diff = rayPoint - planePoint;
double prod1 = diff.dot(planeNormal);
double prod2 = rayVector.dot(planeNormal);
double prod3 = prod1 / prod2;
return rayPoint - rayVector * prod3;
}
.......
Vector3D cubePos = intersectPoint(view_direction, camera_position, Vector3D(0, 1, 0), Vector3D(0, 0, 0));
cubePos = round(cubePos / cubeSize) * cubeSize;
AddCube(cubePos);
It's hard to tell without having images to look at, but lookAt is most likely your normalized forward vector? If i understood you correctly, you'd want to do something like objectpos = camerapos + forward * 10f (where 10f is the distance you want to place the object in front of you in 3d space units) to make sure that it's placed a few units in front of your fps controller.
actually, if view_direction is your normalized forward vector and your lookAt is camera_pos + view_direction, then you'd end up with something very close to your camera position, which would explain why the cube spawns inside you. either way, my suggestion should still work :)

Error control of directx camera rotation?

I use the mouse to control camera rotation in my program(using Directx 9.0c). Mouse X controls the camera to rotate around the Up Vector and Mouse Y controls the rotation around the Right Vector. Rotation caculation is as below:
void Camera::RotateCameraUp(float angle)
{
D3DXMATRIX RoMatrix;
D3DXMatrixRotationAxis(&RoMatrix, &vUp, angle);
D3DXVec3TransformCoord(&vLook, &vLook, &RoMatrix);
D3DXVec3TransformCoord(&vRight, &vRight, &RoMatrix);
}
void Camera::RotateCameraRight(float angle)
{
D3DXMATRIX RoMatrix;
D3DXMatrixRotationAxis(&RoMatrix, &vRight, angle);
D3DXVec3TransformCoord(&vLook, &vLook, &RoMatrix);
D3DXVec3TransformCoord(&vUp, &vUp, &RoMatrix);
}
It is supposed that rotation around Up or Right vector should not leads to rotation around the "LookAt" vector, but if I circle my mouse for a while and stop it at the starting point, rotation around the "LookAt" vector has happened. I think it's because of the error while caculating, but I don't know how to eliminate it or control it. Any idea?
This is a common problem. You apply many rotations, and over time, the rounding errors sum up. After a while, the three vectors vUp, vLook and vRight are not normalized and orthogonal anymore.
I would use one of two options:
1.
Don't store vLook and vRight; instead, just store 2 angles. Assuming x is right, y is top, z is back, store a) the angle between your view axis and the xz-Plane, and b) the angle between the projection of your view axis on the xz-Plane and the z-Axis or x-Axis. Update these angles according to mouse move and calculate vLook and vRight from them.
2.
Set the y-component of vRight to 0, as vRight should be in the xz-Plane. Then re-orthonormalize the vectors (you know the vectors should be perpendicular to each other and have length 1). So after calculating the new vLook and vRight, apply these corrections:
vRight.y = 0
vRight = Normalize(vRight)
vUp = Normalize(Cross(vLook, vRight))
vLook = Normalize(Cross(vRight, vUp))

Rotate camera relative to a point

I need to rotate the camera around a player from a third person view. (Nothing fancy).
Here it is how I try it:
// Forward, right, and position define the plane - they have x,y,z components.
void rotate ( float angle, Vector interestPoint )
{
Vector oldForward ( Forward );
forward = forward * cos(angle) + right * sin(angle);
forward.Normalize();
right = forward.CrossProduct ( up );
right.Normalize();
position = ( position + old_forward * position.Distance( interestPoint ) ) - (forward * position.Distance( interestPoint ) );
this->angle += angle;
}
The problem is that if, let's say just do turn left a lot, the distance between the object and the camera increases.
For a very simple orbit camera, like most 3rd person adventure games, you will need 4 things:
The position of the target
The distance from the target
The azimuthal angle
The polar angle
(If you want your camera to be always relative to the target in orientation, you need to provide the target's orientation as well, in this case I will simply use the world orientation)
See Spherical coordinate systems for a reference.
You should map your azimuthal angle on your horizontal control (and make it loop around when you reach 2 * PI) and your polar angle should be mapped on your vertical control (or inverted if the player selects that option and make it clamped between -PI and PI - watch out for calculations based on the world Up vector if you go parallel to it (-PI or PI)
The distance can be fixed or driven by a spline, for this case we will assume a fixed distance.
So, to compute your position you start with WorldForward, which is a unit vector pointing in the axis that you generally consider to be your forward, for example (1,0,0) (here, if we were building a relative camera, we would use our target's forward vector) and you invert it (* -1) to go "from the target" "to your camera".
(The following is untested pseudo code, but you should get the gist - also, keep note that it can be simplified, I just went for clarity)
Next step is to rotate this vector using our azimuth angle, which is the horizontal orientation component of your camera. Something like:
Vector toCamera = WorldForward * -1;
Matrix horizontalRotation = Matrix.CreateRotationZ(azimuth); // assuming Z is up
Vector horizontalRotationPosition = horizontalRotation.Transform(toCamera);
At this point, you have a camera that can rotate horizontally around your target, now to add the other axis, you simply transform again using the polar angle rotation:
Matrix verticalRotation = Matrix.CreateRotationY(polar); // assuming Y is right
Vector finalRotatedVector = verticalRotation.Transform(horizontalRotationPosition);
Now, what we have is a unit vector that points to the position where the camera should be, if you multiply it by the distance you want to keep from your target and add the position of your target, you should get your final position. Keep in mind that this unit vector, if negated, represents the forward vector of your camera.
Vector cameraPosition = targetPosition + finalRotatedVector * distanceFromTarget;

Mirroring the Y axis in SFML

Hey so I'm integrating box2d and SFML, and box2D has the same odd, mirrored Y-axis coordinate system as SFML, meaning everything is rendered upside down. Is there some kind of function or short amount of code I can put that simply mirrors the window's render contents?
I'm thinking I can put something in sf::view to help with this...
How can i easily flip the Y-axis easily, for rendering purposes, not effecting the bodies dimensions/locations?
I don't know what is box2d but when I wanted to flip Y axis using openGL, I just applied negative scaling factor to projection matrix, like:
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
glScalef(1.0f, -1.0f, 1.0f);
If you want to do it independent of openGL simply apply a sf::View with a negative x value.
It sounds like your model uses a conventional coordinate system (positive y points up), and you need to translate that to the screen coordinate system (positive y points down).
When copying model/Box2D position data to any sf::Drawable, manually transform between the model and screen coordinate systems:
b2Vec2 position = body->GetPosition();
sprite.SetPosition( position.x, window.GetHeight() - position.y )
You can hide this in a wrapper class or function, but it needs to sit between the model and renderer as a pre-render transform. I don't see a place to set that in SFML.
I think Box2D has the coordinate system you want; just set the gravity vector based on your model (0, -10) instead of the screen.
How can i easily flip the Y-axis easily, for rendering purposes, not effecting the bodies dimensions/locations?
By properly applying transforms. First, you can apply a transform that sets the window's bottom-left corner as the origin. Then, scale the Y axis by a factor of -1 to flip it as the second transform.
For this, you can use sf::Transformable to specify each transformation individually (i.e., the setting of the origin and the scaling) and then – by calling sf::Transformable::getTransform() – obtain an sf::Transform object that corresponds to the composed transform.
Finally, when rendering the corresponding object, pass this transform object to the sf::RenderTarget::draw() member function as its second argument. An sf::Transform object implicitly converts to a sf::RenderStates which is the second parameter type of the corresponding sf::RenderTarget::draw() overload.
As an example:
#include <SFML/Graphics.hpp>
auto main() -> int {
auto const width = 300, height = 300;
sf::RenderWindow win(sf::VideoMode(width, height), "Transformation");
win.setFramerateLimit(60);
// create the composed transform object
const sf::Transform transform = [height]{
sf::Transformable transformation;
transformation.setOrigin(0, height); // 1st transform
transformation.setScale(1.f, -1.f); // 2nd transform
return transformation.getTransform();
}();
sf::RectangleShape rect({30, 30});
while (win.isOpen()) {
sf::Event event;
while (win.pollEvent(event))
if (event.type == sf::Event::Closed)
win.close();
// update rectangle's position
rect.move(0, 1);
win.clear();
rect.setFillColor(sf::Color::Blue);
win.draw(rect); // no transformation applied
rect.setFillColor(sf::Color::Red);
win.draw(rect, transform); // transformation applied
win.display();
}
}
There is a single sf::RectangleShape object that is rendered twice with different colors:
Blue: no transform was applied.
Red: the composed transform was applied.
They move in opposite directions as a result of flipping the Y axis.
Note that the object space position coordinates remain the same. Both rendered rectangles correspond to the same object, i.e., there is just a single sf::RectangleShape object, rect – only the color is changed. The object space position is rect.getPosition().
What is different for these two rendered rectangles is the coordinate reference system. Therefore, the absolute space position coordinates of these two rendered rectangles also differ.
You can use this approach in a scene tree. In such a tree, the transforms are applied in a top-down manner from the parents to their children, starting from the root. The net effect is that children's coordinates are relative to their parent's absolute position.